@Test public void testForking2() throws Exception { // Check that if the chain forks and new coins are received in the alternate chain our balance goes up // after the re-org takes place. Block b1 = PARAMS.getGenesisBlock().createNextBlock(someOtherGuy); Block b2 = b1.createNextBlock(someOtherGuy); assertTrue(chain.add(b1)); assertTrue(chain.add(b2)); // genesis -> b1 -> b2 // \-> b3 -> b4 assertEquals(Coin.ZERO, wallet.getBalance()); Block b3 = b1.createNextBlock(coinsTo); Block b4 = b3.createNextBlock(someOtherGuy); assertTrue(chain.add(b3)); assertEquals(Coin.ZERO, wallet.getBalance()); assertTrue(chain.add(b4)); assertEquals(FIFTY_COINS, wallet.getBalance()); }
@Test public void rollbackBlockStore() throws Exception { // This test simulates an issue on Android, that causes the VM to crash while receiving a block, so that the // block store is persisted but the wallet is not. Block b1 = PARAMS.getGenesisBlock().createNextBlock(coinbaseTo); Block b2 = b1.createNextBlock(coinbaseTo); // Add block 1, no frills. assertTrue(chain.add(b1)); assertEquals(b1.cloneAsHeader(), chain.getChainHead().getHeader()); assertEquals(1, chain.getBestChainHeight()); assertEquals(1, wallet.getLastBlockSeenHeight()); // Add block 2 while wallet is disconnected, to simulate crash. chain.removeWallet(wallet); assertTrue(chain.add(b2)); assertEquals(b2.cloneAsHeader(), chain.getChainHead().getHeader()); assertEquals(2, chain.getBestChainHeight()); assertEquals(1, wallet.getLastBlockSeenHeight()); // Add wallet back. This will detect the height mismatch and repair the damage done. chain.addWallet(wallet); assertEquals(b1.cloneAsHeader(), chain.getChainHead().getHeader()); assertEquals(1, chain.getBestChainHeight()); assertEquals(1, wallet.getLastBlockSeenHeight()); // Now add block 2 correctly. assertTrue(chain.add(b2)); assertEquals(b2.cloneAsHeader(), chain.getChainHead().getHeader()); assertEquals(2, chain.getBestChainHeight()); assertEquals(2, wallet.getLastBlockSeenHeight()); } }
@Test public void testForking6() throws Exception { // Test the case in which a side chain block contains a tx, and then it appears in the main chain too. Block b1 = PARAMS.getGenesisBlock().createNextBlock(someOtherGuy); chain.add(b1); // genesis -> b1 // -> b2 Block b2 = PARAMS.getGenesisBlock().createNextBlock(coinsTo); chain.add(b2); assertEquals(Coin.ZERO, wallet.getBalance()); // genesis -> b1 -> b3 // -> b2 Block b3 = b1.createNextBlock(someOtherGuy); b3.addTransaction(b2.transactions.get(1)); b3.solve(); chain.add(roundtrip(b3)); assertEquals(FIFTY_COINS, wallet.getBalance()); }
private void testDeprecatedBlockVersion(final long deprecatedVersion, final long newVersion) throws Exception { final BlockStore versionBlockStore = new MemoryBlockStore(PARAMS); final BlockChain versionChain = new BlockChain(PARAMS, versionBlockStore); // Build a historical chain of version 3 blocks long timeSeconds = 1231006505; int height = 0; FakeTxBuilder.BlockPair chainHead = null; // Put in just enough v2 blocks to be a minority for (height = 0; height < (PARAMS.getMajorityWindow() - PARAMS.getMajorityRejectBlockOutdated()); height++) { chainHead = FakeTxBuilder.createFakeBlock(versionBlockStore, deprecatedVersion, timeSeconds, height); versionChain.add(chainHead.block); timeSeconds += 60; } // Fill the rest of the window with v3 blocks for (; height < PARAMS.getMajorityWindow(); height++) { chainHead = FakeTxBuilder.createFakeBlock(versionBlockStore, newVersion, timeSeconds, height); versionChain.add(chainHead.block); timeSeconds += 60; } chainHead = FakeTxBuilder.createFakeBlock(versionBlockStore, deprecatedVersion, timeSeconds, height); // Trying to add a new v2 block should result in rejection thrown.expect(VerificationException.BlockVersionOutOfDate.class); try { versionChain.add(chainHead.block); } catch(final VerificationException ex) { throw (Exception) ex.getCause(); } }
@Test public void startBlockChainDownload() throws Exception { Block b1 = createFakeBlock(blockStore, Block.BLOCK_HEIGHT_GENESIS).block; blockChain.add(b1); Block b2 = makeSolvedTestBlock(b1); blockChain.add(b2); connect(); fail.set(true); peer.addChainDownloadStartedEventListener(Threading.SAME_THREAD, new ChainDownloadStartedEventListener() { @Override public void onChainDownloadStarted(Peer p, int blocksLeft) { if (p == peer && blocksLeft == 108) fail.set(false); } }); peer.startBlockChainDownload(); List<Sha256Hash> expectedLocator = new ArrayList<>(); expectedLocator.add(b2.getHash()); expectedLocator.add(b1.getHash()); expectedLocator.add(PARAMS.getGenesisBlock().getHash()); GetBlocksMessage message = (GetBlocksMessage) outbound(writeTarget); assertEquals(message.getLocator(), expectedLocator); assertEquals(Sha256Hash.ZERO_HASH, message.getStopHash()); }
@Test public void unconnectedBlocks() throws Exception { Block b1 = PARAMS.getGenesisBlock().createNextBlock(coinbaseTo); Block b2 = b1.createNextBlock(coinbaseTo); Block b3 = b2.createNextBlock(coinbaseTo); // Connected. assertTrue(chain.add(b1)); // Unconnected but stored. The head of the chain is still b1. assertFalse(chain.add(b3)); assertEquals(chain.getChainHead().getHeader(), b1.cloneAsHeader()); // Add in the middle block. assertTrue(chain.add(b2)); assertEquals(chain.getChainHead().getHeader(), b3.cloneAsHeader()); }
@Test public void orderingInsideBlock() throws Exception { // Test that transactions received in the same block have their ordering preserved when reorganising. // This covers issue 468. // Receive some money to the wallet. Transaction t1 = FakeTxBuilder.createFakeTx(PARAMS, COIN, coinsTo); final Block b1 = FakeTxBuilder.makeSolvedTestBlock(PARAMS.genesisBlock, t1); chain.add(b1); // Send a couple of payments one after the other (so the second depends on the change output of the first). wallet.allowSpendingUnconfirmedTransactions(); Transaction t2 = checkNotNull(wallet.createSend(new ECKey().toAddress(PARAMS), CENT)); wallet.commitTx(t2); Transaction t3 = checkNotNull(wallet.createSend(new ECKey().toAddress(PARAMS), CENT)); wallet.commitTx(t3); chain.add(FakeTxBuilder.makeSolvedTestBlock(b1, t2, t3)); final Coin coins0point98 = COIN.subtract(CENT).subtract(CENT); assertEquals(coins0point98, wallet.getBalance()); // Now round trip the wallet and force a re-org. ByteArrayOutputStream bos = new ByteArrayOutputStream(); wallet.saveToFileStream(bos); wallet = Wallet.loadFromFileStream(new ByteArrayInputStream(bos.toByteArray())); final Block b2 = FakeTxBuilder.makeSolvedTestBlock(b1, t2, t3); final Block b3 = FakeTxBuilder.makeSolvedTestBlock(b2); chain.add(b2); chain.add(b3); // And verify that the balance is as expected. Because new ECKey() is non-deterministic, if the order // isn't being stored correctly this should fail 50% of the time. assertEquals(coins0point98, wallet.getBalance()); }
@Test public void testForking5() throws Exception { // Test the standard case in which a block containing identical transactions appears on a side chain. Block b1 = PARAMS.getGenesisBlock().createNextBlock(coinsTo); chain.add(b1); final Transaction t = b1.transactions.get(1); assertEquals(FIFTY_COINS, wallet.getBalance()); // genesis -> b1 // -> b2 Block b2 = PARAMS.getGenesisBlock().createNextBlock(coinsTo); Transaction b2coinbase = b2.transactions.get(0); b2.transactions.clear(); b2.addTransaction(b2coinbase); b2.addTransaction(t); b2.solve(); chain.add(roundtrip(b2)); assertEquals(FIFTY_COINS, wallet.getBalance()); assertTrue(wallet.isConsistent()); assertEquals(2, wallet.getTransaction(t.getHash()).getAppearsInHashes().size()); // -> b2 -> b3 Block b3 = b2.createNextBlock(someOtherGuy); chain.add(b3); assertEquals(FIFTY_COINS, wallet.getBalance()); }
@Test public void testBasicChaining() throws Exception { // Check that we can plug a few blocks together and the futures work. ListenableFuture<StoredBlock> future = testNetChain.getHeightFuture(2); // Block 1 from the testnet. Block b1 = getBlock1(); assertTrue(testNetChain.add(b1)); assertFalse(future.isDone()); // Block 2 from the testnet. Block b2 = getBlock2(); // Let's try adding an invalid block. long n = b2.getNonce(); try { b2.setNonce(12345); testNetChain.add(b2); fail(); } catch (VerificationException e) { b2.setNonce(n); } // Now it works because we reset the nonce. assertTrue(testNetChain.add(b2)); assertTrue(future.isDone()); assertEquals(2, future.get().getHeight()); }
@Test public void getBlock() throws Exception { connect(); Block b1 = createFakeBlock(blockStore, Block.BLOCK_HEIGHT_GENESIS).block; blockChain.add(b1); Block b2 = makeSolvedTestBlock(b1); Block b3 = makeSolvedTestBlock(b2); // Request the block. Future<Block> resultFuture = peer.getBlock(b3.getHash()); assertFalse(resultFuture.isDone()); // Peer asks for it. GetDataMessage message = (GetDataMessage) outbound(writeTarget); assertEquals(message.getItems().get(0).hash, b3.getHash()); assertFalse(resultFuture.isDone()); // Peer receives it. inbound(writeTarget, b3); Block b = resultFuture.get(); assertEquals(b, b3); }
@Test public void duplicates() throws Exception { // Adding a block twice should not have any effect, in particular it should not send the block to the wallet. Block b1 = PARAMS.getGenesisBlock().createNextBlock(coinbaseTo); Block b2 = b1.createNextBlock(coinbaseTo); Block b3 = b2.createNextBlock(coinbaseTo); assertTrue(chain.add(b1)); assertEquals(b1, block[0].getHeader()); assertTrue(chain.add(b2)); assertEquals(b2, block[0].getHeader()); assertTrue(chain.add(b3)); assertEquals(b3, block[0].getHeader()); assertEquals(b3, chain.getChainHead().getHeader()); assertTrue(chain.add(b2)); assertEquals(b3, chain.getChainHead().getHeader()); // Wallet was NOT called with the new block because the duplicate add was spotted. assertEquals(b3, block[0].getHeader()); }
@Test public void difficultyTransitions() throws Exception { // Add a bunch of blocks in a loop until we reach a difficulty transition point. The unit test params have an // artificially shortened period. Block prev = PARAMS.getGenesisBlock(); Utils.setMockClock(System.currentTimeMillis()/1000); for (int height = 0; height < PARAMS.getInterval() - 1; height++) { Block newBlock = prev.createNextBlock(coinbaseTo, 1, Utils.currentTimeSeconds(), height); assertTrue(chain.add(newBlock)); prev = newBlock; // The fake chain should seem to be "fast" for the purposes of difficulty calculations. Utils.rollMockClock(2); } // Now add another block that has no difficulty adjustment, it should be rejected. try { chain.add(prev.createNextBlock(coinbaseTo, 1, Utils.currentTimeSeconds(), PARAMS.getInterval())); fail(); } catch (VerificationException e) { } // Create a new block with the right difficulty target given our blistering speed relative to the huge amount // of time it's supposed to take (set in the unit test network parameters). Block b = prev.createNextBlock(coinbaseTo, 1, Utils.currentTimeSeconds(), PARAMS.getInterval() + 1); b.setDifficultyTarget(0x201fFFFFL); b.solve(); assertTrue(chain.add(b)); // Successfully traversed a difficulty transition period. }
@Test public void receiveCoins() throws Exception { int height = 1; // Quick check that we can actually receive coins. Transaction tx1 = createFakeTx(PARAMS, COIN, wallet.currentReceiveKey().toAddress(PARAMS)); Block b1 = createFakeBlock(blockStore, height, tx1).block; chain.add(b1); assertTrue(wallet.getBalance().signum() > 0); }
@Test public void getLargeBlock() throws Exception { connect(); Block b1 = createFakeBlock(blockStore, Block.BLOCK_HEIGHT_GENESIS).block; blockChain.add(b1); Block b2 = makeSolvedTestBlock(b1); Transaction t = new Transaction(PARAMS); t.addInput(b1.getTransactions().get(0).getOutput(0)); t.addOutput(new TransactionOutput(PARAMS, t, Coin.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 lastBlockSeen() throws Exception { Coin v1 = valueOf(5, 0); Coin v2 = valueOf(0, 50); Coin v3 = valueOf(0, 25); Transaction t1 = createFakeTx(PARAMS, v1, myAddress); Transaction t2 = createFakeTx(PARAMS, v2, myAddress); Transaction t3 = createFakeTx(PARAMS, v3, myAddress); Block genesis = blockStore.getChainHead().getHeader(); Block b10 = makeSolvedTestBlock(genesis, t1); Block b11 = makeSolvedTestBlock(genesis, t2); Block b2 = makeSolvedTestBlock(b10, t3); Block b3 = makeSolvedTestBlock(b2); // Receive a block on the best chain - this should set the last block seen hash. chain.add(b10); assertEquals(b10.getHash(), wallet.getLastBlockSeenHash()); assertEquals(b10.getTimeSeconds(), wallet.getLastBlockSeenTimeSecs()); assertEquals(1, wallet.getLastBlockSeenHeight()); // Receive a block on the side chain - this should not change the last block seen hash. chain.add(b11); assertEquals(b10.getHash(), wallet.getLastBlockSeenHash()); // Receive block 2 on the best chain - this should change the last block seen hash. chain.add(b2); assertEquals(b2.getHash(), wallet.getLastBlockSeenHash()); // Receive block 3 on the best chain - this should change the last block seen hash despite having no txns. chain.add(b3); assertEquals(b3.getHash(), wallet.getLastBlockSeenHash()); }
@Test public void coinbaseTxns() throws Exception { // Covers issue 420 where the outpoint index of a coinbase tx input was being mis-serialized. Block b = PARAMS.getGenesisBlock().createNextBlockWithCoinbase(Block.BLOCK_VERSION_GENESIS, myKey.getPubKey(), FIFTY_COINS, Block.BLOCK_HEIGHT_GENESIS); Transaction coinbase = b.getTransactions().get(0); assertTrue(coinbase.isCoinBase()); BlockChain chain = new BlockChain(PARAMS, myWallet, new MemoryBlockStore(PARAMS)); assertTrue(chain.add(b)); // Wallet now has a coinbase tx in it. assertEquals(1, myWallet.getTransactions(true).size()); assertTrue(myWallet.getTransaction(coinbase.getHash()).isCoinBase()); Wallet wallet2 = roundTrip(myWallet); assertEquals(1, wallet2.getTransactions(true).size()); assertTrue(wallet2.getTransaction(coinbase.getHash()).isCoinBase()); }
@Test public void invTickle() throws Exception { connect(); Block b1 = createFakeBlock(blockStore, Block.BLOCK_HEIGHT_GENESIS).block; blockChain.add(b1); // Make a missing block. Block b2 = makeSolvedTestBlock(b1); Block b3 = makeSolvedTestBlock(b2); inbound(writeTarget, b3); InventoryMessage inv = new InventoryMessage(PARAMS); InventoryItem item = new InventoryItem(InventoryItem.Type.Block, b3.getHash()); inv.addItem(item); inbound(writeTarget, inv); GetBlocksMessage getblocks = (GetBlocksMessage)outbound(writeTarget); List<Sha256Hash> expectedLocator = new ArrayList<>(); expectedLocator.add(b1.getHash()); expectedLocator.add(PARAMS.getGenesisBlock().getHash()); assertEquals(getblocks.getLocator(), expectedLocator); assertEquals(getblocks.getStopHash(), b3.getHash()); assertNull(outbound(writeTarget)); }
@Test public void invNoDownload() throws Exception { // Don't download missing blocks. peer.setDownloadData(false); connect(); // Make a missing block that we receive. Block b1 = createFakeBlock(blockStore, Block.BLOCK_HEIGHT_GENESIS).block; blockChain.add(b1); Block b2 = makeSolvedTestBlock(b1); // Receive an inv. InventoryMessage inv = new InventoryMessage(PARAMS); InventoryItem item = new InventoryItem(InventoryItem.Type.Block, b2.getHash()); inv.addItem(item); inbound(writeTarget, inv); // Peer does nothing with it. assertNull(outbound(writeTarget)); }
@Test public void intraBlockDependencies() throws Exception { // Covers issue 166 in which transactions that depend on each other inside a block were not always being // considered relevant. Address somebodyElse = new ECKey().toAddress(PARAMS); Block b1 = PARAMS.getGenesisBlock().createNextBlock(somebodyElse); ECKey key = wallet.freshReceiveKey(); Address addr = key.toAddress(PARAMS); // Create a tx that gives us some coins, and another that spends it to someone else in the same block. Transaction t1 = FakeTxBuilder.createFakeTx(PARAMS, COIN, addr); Transaction t2 = new Transaction(PARAMS); t2.addInput(t1.getOutputs().get(0)); t2.addOutput(valueOf(2, 0), somebodyElse); b1.addTransaction(t1); b1.addTransaction(t2); b1.solve(); chain.add(b1); assertEquals(Coin.ZERO, wallet.getBalance()); }