@Test public void exceptionListener() throws Exception { wallet.addEventListener( new AbstractWalletEventListener() { @Override public void onCoinsReceived( Wallet wallet, Transaction tx, BigInteger prevBalance, BigInteger newBalance) { throw new NullPointerException("boo!"); } }); final Throwable[] throwables = new Throwable[1]; Threading.uncaughtExceptionHandler = new Thread.UncaughtExceptionHandler() { @Override public void uncaughtException(Thread thread, Throwable throwable) { throwables[0] = throwable; } }; // In real usage we're not really meant to adjust the uncaught exception handler after stuff // started happening // but in the unit test environment other tests have just run so the thread is probably still // kicking around. // Force it to crash so it'll be recreated with our new handler. Threading.USER_THREAD.execute( new Runnable() { @Override public void run() { throw new RuntimeException(); } }); connect(); Transaction t1 = new Transaction(unitTestParams); t1.addInput(new TransactionInput(unitTestParams, t1, new byte[] {})); t1.addOutput(Utils.toNanoCoins(1, 0), new ECKey().toAddress(unitTestParams)); Transaction t2 = new Transaction(unitTestParams); t2.addInput(t1.getOutput(0)); t2.addOutput(Utils.toNanoCoins(1, 0), wallet.getChangeAddress()); inbound(writeTarget, t2); final InventoryItem inventoryItem = new InventoryItem(InventoryItem.Type.Transaction, t2.getInput(0).getOutpoint().getHash()); final NotFoundMessage nfm = new NotFoundMessage(unitTestParams, Lists.newArrayList(inventoryItem)); inbound(writeTarget, nfm); pingAndWait(writeTarget); Threading.waitForUserCode(); assertTrue(throwables[0] instanceof NullPointerException); Threading.uncaughtExceptionHandler = null; }
@Test public void getLargeBlock() throws Exception { connect(); Block b1 = createFakeBlock(blockStore).block; blockChain.add(b1); Block b2 = makeSolvedTestBlock(b1); Transaction t = new Transaction(unitTestParams); t.addInput(b1.getTransactions().get(0).getOutput(0)); t.addOutput( new TransactionOutput( unitTestParams, t, BigInteger.ZERO, new byte[Block.MAX_BLOCK_SIZE - 1000])); b2.addTransaction(t); // Request the block. Future<Block> resultFuture = peer.getBlock(b2.getHash()); assertFalse(resultFuture.isDone()); // Peer asks for it. GetDataMessage message = (GetDataMessage) outbound(writeTarget); assertEquals(message.getItems().get(0).hash, b2.getHash()); assertFalse(resultFuture.isDone()); // Peer receives it. inbound(writeTarget, b2); Block b = resultFuture.get(); assertEquals(b, b2); }
@Test public void testSimplePayment() throws Exception { // Create a PaymentRequest and make sure the correct values are parsed by the PaymentSession. MockPaymentSession paymentSession = new MockPaymentSession(newSimplePaymentRequest()); assertEquals(paymentRequestMemo, paymentSession.getMemo()); assertEquals(nanoCoins, paymentSession.getValue()); assertEquals(simplePaymentUrl, paymentSession.getPaymentUrl()); assertTrue(new Date(time * 1000L).equals(paymentSession.getDate())); assertTrue(paymentSession.getSendRequest().tx.equals(tx)); assertFalse(paymentSession.isExpired()); // Send the payment and verify that the correct information is sent. // Add a dummy input to tx so it is considered valid. tx.addInput(new TransactionInput(params, tx, outputToMe.getScriptBytes())); ArrayList<Transaction> txns = new ArrayList<Transaction>(); txns.add(tx); Address refundAddr = new Address(params, serverKey.getPubKeyHash()); paymentSession.sendPayment(txns, refundAddr, paymentMemo); assertEquals(1, paymentSession.getPaymentLog().size()); assertEquals(simplePaymentUrl, paymentSession.getPaymentLog().get(0).getUrl().toString()); Protos.Payment payment = paymentSession.getPaymentLog().get(0).getPayment(); assertEquals(paymentMemo, payment.getMemo()); assertEquals(merchantData, payment.getMerchantData()); assertEquals(1, payment.getRefundToCount()); assertEquals(nanoCoins.longValue(), payment.getRefundTo(0).getAmount()); TransactionOutput refundOutput = new TransactionOutput(params, null, nanoCoins, refundAddr); ByteString refundScript = ByteString.copyFrom(refundOutput.getScriptBytes()); assertTrue(refundScript.equals(payment.getRefundTo(0).getScript())); }
@Test public void testCreateMultiSigInputScript() throws AddressFormatException { // Setup transaction and signatures ECKey key1 = new DumpedPrivateKey(params, "cVLwRLTvz3BxDAWkvS3yzT9pUcTCup7kQnfT2smRjvmmm1wAP6QT") .getKey(); ECKey key2 = new DumpedPrivateKey(params, "cTine92s8GLpVqvebi8rYce3FrUYq78ZGQffBYCS1HmDPJdSTxUo") .getKey(); ECKey key3 = new DumpedPrivateKey(params, "cVHwXSPRZmL9adctwBwmn4oTZdZMbaCsR5XF6VznqMgcvt1FDDxg") .getKey(); Script multisigScript = ScriptBuilder.createMultiSigOutputScript(2, Arrays.asList(key1, key2, key3)); byte[] bytes = Hex.decode( "01000000013df681ff83b43b6585fa32dd0e12b0b502e6481e04ee52ff0fdaf55a16a4ef61000000006b483045022100a84acca7906c13c5895a1314c165d33621cdcf8696145080895cbf301119b7cf0220730ff511106aa0e0a8570ff00ee57d7a6f24e30f592a10cae1deffac9e13b990012102b8d567bcd6328fd48a429f9cf4b315b859a58fd28c5088ef3cb1d98125fc4e8dffffffff02364f1c00000000001976a91439a02793b418de8ec748dd75382656453dc99bcb88ac40420f000000000017a9145780b80be32e117f675d6e0ada13ba799bf248e98700000000"); Transaction transaction = new Transaction(params, bytes); TransactionOutput output = transaction.getOutput(1); Transaction spendTx = new Transaction(params); Address address = new Address(params, "n3CFiCmBXVt5d3HXKQ15EFZyhPz4yj5F3H"); Script outputScript = ScriptBuilder.createOutputScript(address); spendTx.addOutput(output.getValue(), outputScript); spendTx.addInput(output); Sha256Hash sighash = spendTx.hashForSignature(0, multisigScript, SigHash.ALL, false); ECKey.ECDSASignature party1Signature = key1.sign(sighash); ECKey.ECDSASignature party2Signature = key2.sign(sighash); TransactionSignature party1TransactionSignature = new TransactionSignature(party1Signature, SigHash.ALL, false); TransactionSignature party2TransactionSignature = new TransactionSignature(party2Signature, SigHash.ALL, false); // Create p2sh multisig input script Script inputScript = ScriptBuilder.createP2SHMultiSigInputScript( ImmutableList.of(party1TransactionSignature, party2TransactionSignature), multisigScript.getProgram()); // Assert that the input script contains 4 chunks assertTrue(inputScript.getChunks().size() == 4); // Assert that the input script created contains the original multisig // script as the last chunk ScriptChunk scriptChunk = inputScript.getChunks().get(inputScript.getChunks().size() - 1); Assert.assertArrayEquals(scriptChunk.data, multisigScript.getProgram()); // Create regular multisig input script inputScript = ScriptBuilder.createMultiSigInputScript( ImmutableList.of(party1TransactionSignature, party2TransactionSignature)); // Assert that the input script only contains 3 chunks assertTrue(inputScript.getChunks().size() == 3); // Assert that the input script created does not end with the original // multisig script scriptChunk = inputScript.getChunks().get(inputScript.getChunks().size() - 1); Assert.assertThat(scriptChunk.data, IsNot.not(IsEqual.equalTo(multisigScript.getProgram()))); }
@Test public void testUpdateLength() { NetworkParameters params = UnitTestParams.get(); Block block = params .getGenesisBlock() .createNextBlockWithCoinbase( Block.BLOCK_VERSION_GENESIS, new ECKey().getPubKey(), Block.BLOCK_HEIGHT_GENESIS); assertEquals(block.bitcoinSerialize().length, block.length); final int origBlockLen = block.length; Transaction tx = new Transaction(params); // this is broken until the transaction has > 1 input + output (which is required anyway...) // assertTrue(tx.length == tx.bitcoinSerialize().length && tx.length == 8); byte[] outputScript = new byte[10]; Arrays.fill(outputScript, (byte) ScriptOpCodes.OP_FALSE); tx.addOutput(new TransactionOutput(params, null, Coin.SATOSHI, outputScript)); tx.addInput( new TransactionInput( params, null, new byte[] {(byte) ScriptOpCodes.OP_FALSE}, new TransactionOutPoint(params, 0, Sha256Hash.of(new byte[] {1})))); int origTxLength = 8 + 2 + 8 + 1 + 10 + 40 + 1 + 1; assertEquals(tx.bitcoinSerialize().length, tx.length); assertEquals(origTxLength, tx.length); block.addTransaction(tx); assertEquals(block.bitcoinSerialize().length, block.length); assertEquals(origBlockLen + tx.length, block.length); block .getTransactions() .get(1) .getInputs() .get(0) .setScriptBytes(new byte[] {(byte) ScriptOpCodes.OP_FALSE, (byte) ScriptOpCodes.OP_FALSE}); assertEquals(block.length, origBlockLen + tx.length); assertEquals(tx.length, origTxLength + 1); block.getTransactions().get(1).getInputs().get(0).setScriptBytes(new byte[] {}); assertEquals(block.length, block.bitcoinSerialize().length); assertEquals(block.length, origBlockLen + tx.length); assertEquals(tx.length, origTxLength - 1); block .getTransactions() .get(1) .addInput( new TransactionInput( params, null, new byte[] {(byte) ScriptOpCodes.OP_FALSE}, new TransactionOutPoint(params, 0, Sha256Hash.of(new byte[] {1})))); assertEquals(block.length, origBlockLen + tx.length); assertEquals(tx.length, origTxLength + 41); // - 1 + 40 + 1 + 1 }
@Before public void setUp() throws Exception { unitTestParams = UnitTestParams.get(); wallet = new Wallet(unitTestParams); wallet.addKey(new ECKey()); resetBlockStore(); Transaction tx1 = createFakeTx( unitTestParams, Utils.toNanoCoins(2, 0), wallet.getKeys().get(0).toAddress(unitTestParams)); // add a second input so can test granularity of byte cache. Transaction prevTx = new Transaction(unitTestParams); TransactionOutput prevOut = new TransactionOutput( unitTestParams, prevTx, Utils.toNanoCoins(1, 0), wallet.getKeys().get(0).toAddress(unitTestParams)); prevTx.addOutput(prevOut); // Connect it. tx1.addInput(prevOut); Transaction tx2 = createFakeTx( unitTestParams, Utils.toNanoCoins(1, 0), new ECKey().toAddress(unitTestParams)); Block b1 = createFakeBlock(blockStore, tx1, tx2).block; BitcoinSerializer bs = new BitcoinSerializer(unitTestParams); ByteArrayOutputStream bos = new ByteArrayOutputStream(); bs.serialize(tx1, bos); tx1BytesWithHeader = bos.toByteArray(); tx1Bytes = tx1.bitcoinSerialize(); bos.reset(); bs.serialize(tx2, bos); tx2BytesWithHeader = bos.toByteArray(); tx2Bytes = tx2.bitcoinSerialize(); bos.reset(); bs.serialize(b1, bos); b1BytesWithHeader = bos.toByteArray(); b1Bytes = b1.bitcoinSerialize(); }
@Test public void testExpiredPaymentRequest() throws Exception { MockPaymentSession paymentSession = new MockPaymentSession(newExpiredPaymentRequest()); assertTrue(paymentSession.isExpired()); // Send the payment and verify that an exception is thrown. // Add a dummy input to tx so it is considered valid. tx.addInput(new TransactionInput(params, tx, outputToMe.getScriptBytes())); ArrayList<Transaction> txns = new ArrayList<Transaction>(); txns.add(tx); try { paymentSession.sendPayment(txns, null, null); } catch (PaymentRequestException.Expired e) { assertEquals(0, paymentSession.getPaymentLog().size()); assertEquals(e.getMessage(), "PaymentRequest is expired"); return; } fail("Expected exception due to expired PaymentRequest"); }
@Test public void skipScripts() throws Exception { store = createStore(params, 10); chain = new FullPrunedBlockChain(params, store); // Check that we aren't accidentally leaving any references // to the full StoredUndoableBlock's lying around (ie memory leaks) ECKey outKey = new ECKey(); int height = 1; // Build some blocks on genesis block to create a spendable output Block rollingBlock = params .getGenesisBlock() .createNextBlockWithCoinbase(Block.BLOCK_VERSION_GENESIS, outKey.getPubKey(), height++); chain.add(rollingBlock); TransactionOutput spendableOutput = rollingBlock.getTransactions().get(0).getOutput(0); for (int i = 1; i < params.getSpendableCoinbaseDepth(); i++) { rollingBlock = rollingBlock.createNextBlockWithCoinbase( Block.BLOCK_VERSION_GENESIS, outKey.getPubKey(), height++); chain.add(rollingBlock); } rollingBlock = rollingBlock.createNextBlock(null); Transaction t = new Transaction(params); t.addOutput(new TransactionOutput(params, t, FIFTY_COINS, new byte[] {})); TransactionInput input = t.addInput(spendableOutput); // Invalid script. input.clearScriptBytes(); rollingBlock.addTransaction(t); rollingBlock.solve(); chain.setRunScripts(false); try { chain.add(rollingBlock); } catch (VerificationException e) { fail(); } try { store.close(); } catch (Exception e) { } }
public void testTransaction( NetworkParameters params, byte[] txBytes, boolean isChild, boolean lazy, boolean retain) throws Exception { // reference serializer to produce comparison serialization output after changes to // message structure. BitcoinSerializer bsRef = new BitcoinSerializer(params, false, false); ByteArrayOutputStream bos = new ByteArrayOutputStream(); BitcoinSerializer bs = new BitcoinSerializer(params, lazy, retain); Transaction t1; Transaction tRef; t1 = (Transaction) bs.deserialize(new ByteArrayInputStream(txBytes)); tRef = (Transaction) bsRef.deserialize(new ByteArrayInputStream(txBytes)); // verify our reference BitcoinSerializer produces matching byte array. bos.reset(); bsRef.serialize(tRef, bos); assertTrue(Arrays.equals(bos.toByteArray(), txBytes)); // check lazy and retain status survive both before and after a serialization assertEquals(!lazy, t1.isParsed()); if (t1.isParsed()) assertEquals(retain, t1.isCached()); serDeser(bs, t1, txBytes, null, null); assertEquals(lazy, !t1.isParsed()); if (t1.isParsed()) assertEquals(retain, t1.isCached()); // compare to ref tx bos.reset(); bsRef.serialize(tRef, bos); serDeser(bs, t1, bos.toByteArray(), null, null); // retrieve a value from a child t1.getInputs(); assertTrue(t1.isParsed()); if (t1.getInputs().size() > 0) { assertTrue(t1.isParsed()); TransactionInput tin = t1.getInputs().get(0); assertEquals(!lazy, tin.isParsed()); if (tin.isParsed()) assertEquals(retain, tin.isCached()); // does it still match ref tx? serDeser(bs, t1, bos.toByteArray(), null, null); } // refresh tx t1 = (Transaction) bs.deserialize(new ByteArrayInputStream(txBytes)); tRef = (Transaction) bsRef.deserialize(new ByteArrayInputStream(txBytes)); // add an input if (t1.getInputs().size() > 0) { t1.addInput(t1.getInputs().get(0)); // replicate on reference tx tRef.addInput(tRef.getInputs().get(0)); assertFalse(t1.isCached()); assertTrue(t1.isParsed()); bos.reset(); bsRef.serialize(tRef, bos); byte[] source = bos.toByteArray(); // confirm we still match the reference tx. serDeser(bs, t1, source, null, null); } }
public void testBlock(byte[] blockBytes, boolean isChild, boolean lazy, boolean retain) throws Exception { // reference serializer to produce comparison serialization output after changes to // message structure. BitcoinSerializer bsRef = new BitcoinSerializer(unitTestParams, false, false); ByteArrayOutputStream bos = new ByteArrayOutputStream(); BitcoinSerializer bs = new BitcoinSerializer(unitTestParams, lazy, retain); Block b1; Block bRef; b1 = (Block) bs.deserialize(new ByteArrayInputStream(blockBytes)); bRef = (Block) bsRef.deserialize(new ByteArrayInputStream(blockBytes)); // verify our reference BitcoinSerializer produces matching byte array. bos.reset(); bsRef.serialize(bRef, bos); assertTrue(Arrays.equals(bos.toByteArray(), blockBytes)); // check lazy and retain status survive both before and after a serialization assertEquals(!lazy, b1.isParsedTransactions()); assertEquals(!lazy, b1.isParsedHeader()); if (b1.isParsedHeader()) assertEquals(retain, b1.isHeaderBytesValid()); if (b1.isParsedTransactions()) assertEquals(retain, b1.isTransactionBytesValid()); serDeser(bs, b1, blockBytes, null, null); assertEquals(!lazy, b1.isParsedTransactions()); assertEquals(!lazy, b1.isParsedHeader()); if (b1.isParsedHeader()) assertEquals(retain, b1.isHeaderBytesValid()); if (b1.isParsedTransactions()) assertEquals(retain, b1.isTransactionBytesValid()); // compare to ref block bos.reset(); bsRef.serialize(bRef, bos); serDeser(bs, b1, bos.toByteArray(), null, null); // retrieve a value from a child b1.getTransactions(); assertTrue(b1.isParsedTransactions()); if (b1.getTransactions().size() > 0) { assertTrue(b1.isParsedTransactions()); Transaction tx1 = b1.getTransactions().get(0); // this will always be true for all children of a block once they are retrieved. // the tx child inputs/outputs may not be parsed however. // no longer forced to parse if length not provided. // assertEquals(true, tx1.isParsed()); if (tx1.isParsed()) assertEquals(retain, tx1.isCached()); else assertTrue(tx1.isCached()); // does it still match ref block? serDeser(bs, b1, bos.toByteArray(), null, null); } // refresh block b1 = (Block) bs.deserialize(new ByteArrayInputStream(blockBytes)); bRef = (Block) bsRef.deserialize(new ByteArrayInputStream(blockBytes)); // retrieve a value from header b1.getDifficultyTarget(); assertTrue(b1.isParsedHeader()); assertEquals(lazy, !b1.isParsedTransactions()); // does it still match ref block? serDeser(bs, b1, bos.toByteArray(), null, null); // refresh block b1 = (Block) bs.deserialize(new ByteArrayInputStream(blockBytes)); bRef = (Block) bsRef.deserialize(new ByteArrayInputStream(blockBytes)); // retrieve a value from a child and header b1.getDifficultyTarget(); assertTrue(b1.isParsedHeader()); assertEquals(lazy, !b1.isParsedTransactions()); b1.getTransactions(); assertTrue(b1.isParsedTransactions()); if (b1.getTransactions().size() > 0) { assertTrue(b1.isParsedTransactions()); Transaction tx1 = b1.getTransactions().get(0); // no longer forced to parse if length not provided. // assertEquals(true, tx1.isParsed()); if (tx1.isParsed()) assertEquals(retain, tx1.isCached()); else assertTrue(tx1.isCached()); } // does it still match ref block? serDeser(bs, b1, bos.toByteArray(), null, null); // refresh block b1 = (Block) bs.deserialize(new ByteArrayInputStream(blockBytes)); bRef = (Block) bsRef.deserialize(new ByteArrayInputStream(blockBytes)); // change a value in header b1.setNonce(23); bRef.setNonce(23); assertTrue(b1.isParsedHeader()); assertEquals(lazy, !b1.isParsedTransactions()); assertFalse(b1.isHeaderBytesValid()); if (b1.isParsedTransactions()) assertEquals(retain, b1.isTransactionBytesValid()); else assertEquals(true, b1.isTransactionBytesValid()); // does it still match ref block? bos.reset(); bsRef.serialize(bRef, bos); serDeser(bs, b1, bos.toByteArray(), null, null); // refresh block b1 = (Block) bs.deserialize(new ByteArrayInputStream(blockBytes)); bRef = (Block) bsRef.deserialize(new ByteArrayInputStream(blockBytes)); // retrieve a value from a child of a child b1.getTransactions(); if (b1.getTransactions().size() > 0) { Transaction tx1 = b1.getTransactions().get(0); TransactionInput tin = tx1.getInputs().get(0); assertTrue(tx1.isParsed()); assertTrue(b1.isParsedTransactions()); assertEquals(!lazy, b1.isParsedHeader()); assertEquals(!lazy, tin.isParsed()); assertEquals(tin.isParsed() ? retain : true, tin.isCached()); // does it still match ref tx? bos.reset(); bsRef.serialize(bRef, bos); serDeser(bs, b1, bos.toByteArray(), null, null); } // refresh block b1 = (Block) bs.deserialize(new ByteArrayInputStream(blockBytes)); bRef = (Block) bsRef.deserialize(new ByteArrayInputStream(blockBytes)); // add an input b1.getTransactions(); if (b1.getTransactions().size() > 0) { Transaction tx1 = b1.getTransactions().get(0); if (tx1.getInputs().size() > 0) { tx1.addInput(tx1.getInputs().get(0)); // replicate on reference tx bRef.getTransactions().get(0).addInput(bRef.getTransactions().get(0).getInputs().get(0)); assertFalse(tx1.isCached()); assertTrue(tx1.isParsed()); assertFalse(b1.isTransactionBytesValid()); assertTrue(b1.isParsedHeader()); // confirm sibling cache status was unaffected if (tx1.getInputs().size() > 1) { boolean parsed = tx1.getInputs().get(1).isParsed(); assertEquals(parsed ? retain : true, tx1.getInputs().get(1).isCached()); assertEquals(!lazy, parsed); } // this has to be false. Altering a tx invalidates the merkle root. // when we have seperate merkle caching then the entire header won't need to be // invalidated. assertFalse(b1.isHeaderBytesValid()); bos.reset(); bsRef.serialize(bRef, bos); byte[] source = bos.toByteArray(); // confirm we still match the reference tx. serDeser(bs, b1, source, null, null); } // does it still match ref tx? bos.reset(); bsRef.serialize(bRef, bos); serDeser(bs, b1, bos.toByteArray(), null, null); } // refresh block b1 = (Block) bs.deserialize(new ByteArrayInputStream(blockBytes)); Block b2 = (Block) bs.deserialize(new ByteArrayInputStream(blockBytes)); bRef = (Block) bsRef.deserialize(new ByteArrayInputStream(blockBytes)); Block bRef2 = (Block) bsRef.deserialize(new ByteArrayInputStream(blockBytes)); // reparent an input b1.getTransactions(); if (b1.getTransactions().size() > 0) { Transaction tx1 = b1.getTransactions().get(0); Transaction tx2 = b2.getTransactions().get(0); if (tx1.getInputs().size() > 0) { TransactionInput fromTx1 = tx1.getInputs().get(0); tx2.addInput(fromTx1); // replicate on reference tx TransactionInput fromTxRef = bRef.getTransactions().get(0).getInputs().get(0); bRef2.getTransactions().get(0).addInput(fromTxRef); // b1 hasn't changed but it's no longer in the parent // chain of fromTx1 so has to have been uncached since it won't be // notified of changes throught the parent chain anymore. assertFalse(b1.isTransactionBytesValid()); // b2 should have it's cache invalidated because it has changed. assertFalse(b2.isTransactionBytesValid()); bos.reset(); bsRef.serialize(bRef2, bos); byte[] source = bos.toByteArray(); // confirm altered block matches altered ref block. serDeser(bs, b2, source, null, null); } // does unaltered block still match ref block? bos.reset(); bsRef.serialize(bRef, bos); serDeser(bs, b1, bos.toByteArray(), null, null); // how about if we refresh it? bRef = (Block) bsRef.deserialize(new ByteArrayInputStream(blockBytes)); bos.reset(); bsRef.serialize(bRef, bos); serDeser(bs, b1, bos.toByteArray(), null, null); } }
private void checkTimeLockedDependency(boolean shouldAccept, boolean useNotFound) throws Exception { // Initial setup. connectWithVersion(useNotFound ? 70001 : 60001); ECKey key = new ECKey(); Wallet wallet = new Wallet(unitTestParams); wallet.addKey(key); wallet.setAcceptRiskyTransactions(shouldAccept); peer.addWallet(wallet); final Transaction[] vtx = new Transaction[1]; wallet.addEventListener( new AbstractWalletEventListener() { @Override public void onCoinsReceived( Wallet wallet, Transaction tx, BigInteger prevBalance, BigInteger newBalance) { vtx[0] = tx; } }); // t1 -> t2 [locked] -> t3 (not available) Transaction t2 = new Transaction(unitTestParams); t2.setLockTime(999999); // Add a fake input to t3 that goes nowhere. Sha256Hash t3 = Sha256Hash.create("abc".getBytes(Charset.forName("UTF-8"))); t2.addInput( new TransactionInput( unitTestParams, t2, new byte[] {}, new TransactionOutPoint(unitTestParams, 0, t3))); t2.getInput(0).setSequenceNumber(0xDEADBEEF); t2.addOutput(Utils.toNanoCoins(1, 0), new ECKey()); Transaction t1 = new Transaction(unitTestParams); t1.addInput(t2.getOutput(0)); t1.addOutput(Utils.toNanoCoins(1, 0), key); // Make it relevant. // Announce t1. InventoryMessage inv = new InventoryMessage(unitTestParams); inv.addTransaction(t1); inbound(writeTarget, inv); // Send it. GetDataMessage getdata = (GetDataMessage) outbound(writeTarget); assertEquals(t1.getHash(), getdata.getItems().get(0).hash); inbound(writeTarget, t1); // Nothing arrived at our event listener yet. assertNull(vtx[0]); // We request t2. getdata = (GetDataMessage) outbound(writeTarget); assertEquals(t2.getHash(), getdata.getItems().get(0).hash); inbound(writeTarget, t2); if (!useNotFound) bouncePing(); // We request t3. getdata = (GetDataMessage) outbound(writeTarget); assertEquals(t3, getdata.getItems().get(0).hash); // Can't find it: bottom of tree. if (useNotFound) { NotFoundMessage notFound = new NotFoundMessage(unitTestParams); notFound.addItem(new InventoryItem(InventoryItem.Type.Transaction, t3)); inbound(writeTarget, notFound); } else { bouncePing(); } pingAndWait(writeTarget); Threading.waitForUserCode(); // We're done but still not notified because it was timelocked. if (shouldAccept) assertNotNull(vtx[0]); else assertNull(vtx[0]); }
public void recursiveDownload(boolean useNotFound) throws Exception { // Using ping or notfound? connectWithVersion(useNotFound ? 70001 : 60001); // Check that we can download all dependencies of an unconfirmed relevant transaction from the // mempool. ECKey to = new ECKey(); final Transaction[] onTx = new Transaction[1]; peer.addEventListener( new AbstractPeerEventListener() { @Override public void onTransaction(Peer peer1, Transaction t) { onTx[0] = t; } }, Threading.SAME_THREAD); // Make the some fake transactions in the following graph: // t1 -> t2 -> [t5] // -> t3 -> t4 -> [t6] // -> [t7] // -> [t8] // The ones in brackets are assumed to be in the chain and are represented only by hashes. Transaction t2 = TestUtils.createFakeTx(unitTestParams, Utils.toNanoCoins(1, 0), to); Sha256Hash t5 = t2.getInput(0).getOutpoint().getHash(); Transaction t4 = TestUtils.createFakeTx(unitTestParams, Utils.toNanoCoins(1, 0), new ECKey()); Sha256Hash t6 = t4.getInput(0).getOutpoint().getHash(); t4.addOutput(Utils.toNanoCoins(1, 0), new ECKey()); Transaction t3 = new Transaction(unitTestParams); t3.addInput(t4.getOutput(0)); t3.addOutput(Utils.toNanoCoins(1, 0), new ECKey()); Transaction t1 = new Transaction(unitTestParams); t1.addInput(t2.getOutput(0)); t1.addInput(t3.getOutput(0)); Sha256Hash someHash = new Sha256Hash("2b801dd82f01d17bbde881687bf72bc62e2faa8ab8133d36fcb8c3abe7459da6"); t1.addInput( new TransactionInput( unitTestParams, t1, new byte[] {}, new TransactionOutPoint(unitTestParams, 0, someHash))); Sha256Hash anotherHash = new Sha256Hash("3b801dd82f01d17bbde881687bf72bc62e2faa8ab8133d36fcb8c3abe7459da6"); t1.addInput( new TransactionInput( unitTestParams, t1, new byte[] {}, new TransactionOutPoint(unitTestParams, 1, anotherHash))); t1.addOutput(Utils.toNanoCoins(1, 0), to); t1 = TestUtils.roundTripTransaction(unitTestParams, t1); t2 = TestUtils.roundTripTransaction(unitTestParams, t2); t3 = TestUtils.roundTripTransaction(unitTestParams, t3); t4 = TestUtils.roundTripTransaction(unitTestParams, t4); // Announce the first one. Wait for it to be downloaded. InventoryMessage inv = new InventoryMessage(unitTestParams); inv.addTransaction(t1); inbound(writeTarget, inv); GetDataMessage getdata = (GetDataMessage) outbound(writeTarget); Threading.waitForUserCode(); assertEquals(t1.getHash(), getdata.getItems().get(0).hash); inbound(writeTarget, t1); pingAndWait(writeTarget); assertEquals(t1, onTx[0]); // We want its dependencies so ask for them. ListenableFuture<List<Transaction>> futures = peer.downloadDependencies(t1); assertFalse(futures.isDone()); // It will recursively ask for the dependencies of t1: t2, t3, someHash and anotherHash. getdata = (GetDataMessage) outbound(writeTarget); assertEquals(4, getdata.getItems().size()); assertEquals(t2.getHash(), getdata.getItems().get(0).hash); assertEquals(t3.getHash(), getdata.getItems().get(1).hash); assertEquals(someHash, getdata.getItems().get(2).hash); assertEquals(anotherHash, getdata.getItems().get(3).hash); long nonce = -1; if (!useNotFound) nonce = ((Ping) outbound(writeTarget)).getNonce(); // For some random reason, t4 is delivered at this point before it's needed - perhaps it was a // Bloom filter // false positive. We do this to check that the mempool is being checked for seen transactions // before // requesting them. inbound(writeTarget, t4); // Deliver the requested transactions. inbound(writeTarget, t2); inbound(writeTarget, t3); if (useNotFound) { NotFoundMessage notFound = new NotFoundMessage(unitTestParams); notFound.addItem(new InventoryItem(InventoryItem.Type.Transaction, someHash)); notFound.addItem(new InventoryItem(InventoryItem.Type.Transaction, anotherHash)); inbound(writeTarget, notFound); } else { inbound(writeTarget, new Pong(nonce)); } assertFalse(futures.isDone()); // It will recursively ask for the dependencies of t2: t5 and t4, but not t3 because it already // found t4. getdata = (GetDataMessage) outbound(writeTarget); assertEquals(getdata.getItems().get(0).hash, t2.getInput(0).getOutpoint().getHash()); // t5 isn't found and t4 is. if (useNotFound) { NotFoundMessage notFound = new NotFoundMessage(unitTestParams); notFound.addItem(new InventoryItem(InventoryItem.Type.Transaction, t5)); inbound(writeTarget, notFound); } else { bouncePing(); } assertFalse(futures.isDone()); // Continue to explore the t4 branch and ask for t6, which is in the chain. getdata = (GetDataMessage) outbound(writeTarget); assertEquals(t6, getdata.getItems().get(0).hash); if (useNotFound) { NotFoundMessage notFound = new NotFoundMessage(unitTestParams); notFound.addItem(new InventoryItem(InventoryItem.Type.Transaction, t6)); inbound(writeTarget, notFound); } else { bouncePing(); } pingAndWait(writeTarget); // That's it, we explored the entire tree. assertTrue(futures.isDone()); List<Transaction> results = futures.get(); assertTrue(results.contains(t2)); assertTrue(results.contains(t3)); assertTrue(results.contains(t4)); }