From 5e2e48eb5adae4f12ace0bfdaacc4e75ace9cf2f Mon Sep 17 00:00:00 2001 From: Mike Hearn Date: Tue, 15 Mar 2011 13:58:52 +0000 Subject: [PATCH] Add the start of unit tests covering chain splits/reorgs, along with supporting code. The tests don't pass yet so they are marked @Ignore. --- src/com/google/bitcoin/core/Block.java | 15 ++++- .../bitcoin/core/NetworkParameters.java | 14 +++-- .../google/bitcoin/core/BlockChainTest.java | 59 +++++++++++++++++++ 3 files changed, 81 insertions(+), 7 deletions(-) diff --git a/src/com/google/bitcoin/core/Block.java b/src/com/google/bitcoin/core/Block.java index fe1d87be7..a2b75db4c 100644 --- a/src/com/google/bitcoin/core/Block.java +++ b/src/com/google/bitcoin/core/Block.java @@ -417,11 +417,20 @@ public class Block extends Message { this.hash = null; } - /** Adds a fake coinbase transaction for unit tests. */ - void addFakeTransaction() { + static private int coinbaseCounter; + /** Adds a coinbase transaction to the block. This exists for unit tests. */ + void addCoinbaseTransaction(Address to) { transactions = new ArrayList(); Transaction coinbase = new Transaction(params); - coinbase.setFakeHashForTesting(Utils.doubleDigest("test tx".getBytes())); + // A real coinbase transaction has some stuff in the scriptSig like the extraNonce and difficulty. The + // transactions are distinguished by every TX output going to a different key. + // + // Here we will do things a bit differently so a new address isn't needed every time. We'll put a simple + // counter in the scriptSig so every transaction has a different hash. The output is also different. + // Real coinbase transactions use OP_CHECKSIG rather than a send to an address though there's + // nothing in the system that enforces that and both are just as valid. + coinbase.inputs.add(new TransactionInput(params, new byte[] { (byte) coinbaseCounter++ } )); + coinbase.outputs.add(new TransactionOutput(params, Utils.toNanoCoins(50, 0), to)); transactions.add(coinbase); } } diff --git a/src/com/google/bitcoin/core/NetworkParameters.java b/src/com/google/bitcoin/core/NetworkParameters.java index 88d20f404..3103fda15 100644 --- a/src/com/google/bitcoin/core/NetworkParameters.java +++ b/src/com/google/bitcoin/core/NetworkParameters.java @@ -69,9 +69,8 @@ public class NetworkParameters implements Serializable { return genesisBlock; } - /** The test chain created by Gavin. */ - public static NetworkParameters testNet() { - NetworkParameters n = new NetworkParameters(); + /** Sets up the given NetworkParameters with testnet values. */ + private static NetworkParameters createTestNet(NetworkParameters n) { // Genesis hash is 0000000224b1593e3ff16a0e3b61285bbc393a39f78c8aa48c456142671f7110 n.proofOfWorkLimit = new BigInteger("0000000fffffffffffffffffffffffffffffffffffffffffffffffffffffffff", 16); n.packetMagic = 0xfabfb5daL; @@ -86,6 +85,12 @@ public class NetworkParameters implements Serializable { return n; } + /** The test chain created by Gavin. */ + public static NetworkParameters testNet() { + NetworkParameters n = new NetworkParameters(); + return createTestNet(n); + } + /** The primary BitCoin chain created by Satoshi. */ public static NetworkParameters prodNet() { NetworkParameters n = new NetworkParameters(); @@ -104,7 +109,8 @@ public class NetworkParameters implements Serializable { /** Returns a testnet params modified to allow any difficulty target. */ static NetworkParameters unitTests() { - NetworkParameters n = NetworkParameters.testNet(); + NetworkParameters n = new NetworkParameters(); + n = createTestNet(n); n.proofOfWorkLimit = new BigInteger("00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", 16); n.genesisBlock.setDifficultyTarget(Block.EASIEST_DIFFICULTY_TARGET); return n; diff --git a/tests/com/google/bitcoin/core/BlockChainTest.java b/tests/com/google/bitcoin/core/BlockChainTest.java index cbe3607f5..f8f04bee9 100644 --- a/tests/com/google/bitcoin/core/BlockChainTest.java +++ b/tests/com/google/bitcoin/core/BlockChainTest.java @@ -18,6 +18,7 @@ package com.google.bitcoin.core; import com.google.bitcoin.bouncycastle.util.encoders.Hex; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import java.math.BigInteger; @@ -62,6 +63,64 @@ public class BlockChainTest { assertTrue(chain.add(b2)); } + private Block createNextBlock(Address to, Block prev) throws VerificationException { + Block b = new Block(prev.params); + b.setDifficultyTarget(Block.EASIEST_DIFFICULTY_TARGET); + b.addCoinbaseTransaction(to); + b.setPrevBlockHash(prev.getHash()); + b.solve(); + b.verify(); + return b; + } + + @Test @Ignore + public void testForking() throws Exception { + // Check that if the block chain forks, we end up using the right one. + NetworkParameters unitTestParams = NetworkParameters.unitTests(); + Wallet wallet = new Wallet(unitTestParams); + wallet.addKey(new ECKey()); + Address coinbaseTo = wallet.keychain.get(0).toAddress(unitTestParams); + // Start by building a couple of blocks on top of the genesis block. + Block b1 = createNextBlock(coinbaseTo, unitTestParams.genesisBlock); + Block b2 = createNextBlock(coinbaseTo, b1); + chain = new BlockChain(unitTestParams, wallet); + chain.add(b1); + chain.add(b2); + // We got two blocks which generated 50 coins each, to us. + assertEquals("100.00", Utils.bitcoinValueToFriendlyString(wallet.getBalance())); + // We now have the following chain: + // genesis -> b1 -> b2 + // + // so fork like this: + // + // genesis -> b1 -> b2 + // \-> b3 + // + // Nothing should happen at this point. We saw b2 first so it takes priority. + Address someOtherGuy = new ECKey().toAddress(unitTestParams); + Block b3 = createNextBlock(someOtherGuy, b1); + chain.add(b3); + assertEquals("100.00", Utils.bitcoinValueToFriendlyString(wallet.getBalance())); + // Now we add another block to make the alternative chain longer. + chain.add(createNextBlock(someOtherGuy, b3)); + // + // genesis -> b1 -> b2 + // \-> b3 -> b4 + // + // We lost some coins! b2 is no longer a part of the best chain so our balance should drop to 50 again. + assertEquals("50.00", Utils.bitcoinValueToFriendlyString(wallet.getBalance())); + // ... and back to the first chain + Block b5 = createNextBlock(coinbaseTo, b2); + Block b6 = createNextBlock(coinbaseTo, b5); + chain.add(b5); + chain.add(b6); + // + // genesis -> b1 -> b2 -> b5 -> b6 + // \-> b3 -> b4 + // + assertEquals("200.00", Utils.bitcoinValueToFriendlyString(wallet.getBalance())); + } + @Test public void testBadDifficulty() throws Exception { assertTrue(chain.add(getBlock1()));