diff --git a/core/src/test/java/com/google/bitcoin/core/FullBlockTestGenerator.java b/core/src/test/java/com/google/bitcoin/core/FullBlockTestGenerator.java new file mode 100644 index 00000000..dfcb1366 --- /dev/null +++ b/core/src/test/java/com/google/bitcoin/core/FullBlockTestGenerator.java @@ -0,0 +1,1124 @@ +package com.google.bitcoin.core; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; +import java.util.Queue; + +import com.google.bitcoin.core.Transaction.SigHash; +import com.google.common.base.Preconditions; + +class BlockAndValidity { + Block block; + boolean connects; + boolean throwsException; + Sha256Hash hashChainTipAfterBlock; + String blockName; + public BlockAndValidity(Block block, boolean connects, boolean throwsException, Sha256Hash hashChainTipAfterBlock, String blockName) { + if (connects && throwsException) + throw new RuntimeException("A block cannot connect if an exception was thrown while adding it."); + this.block = block; + this.connects = connects; + this.throwsException = throwsException; + this.hashChainTipAfterBlock = hashChainTipAfterBlock; + this.blockName = blockName; + } +} + +class TransactionOutPointWithValue { + public TransactionOutPoint outpoint; + public BigInteger value; + Script scriptPubKey; + public TransactionOutPointWithValue(TransactionOutPoint outpoint, BigInteger value, Script scriptPubKey) { + this.outpoint = outpoint; + this.value = value; + this.scriptPubKey = scriptPubKey; + } +} + +public class FullBlockTestGenerator { + // Used by BitcoindComparisonTool and FullPrunedBlockChainTest to create test cases + private NetworkParameters params; + private ECKey coinbaseOutKey; + private byte[] coinbaseOutKeyPubKey; + + public FullBlockTestGenerator(NetworkParameters params) { + this.params = params; + coinbaseOutKey = new ECKey(); + coinbaseOutKeyPubKey = coinbaseOutKey.getPubKey(); + Utils.rollMockClock(0); // Set a mock clock for timestamp tests + } + + public List getBlocksToTest(boolean addExpensiveBlocks) throws ScriptException { + List blocks = new LinkedList(); + + Queue spendableOutputs = new LinkedList(); + + int chainHeadHeight = 1; + Block chainHead = params.genesisBlock.createNextBlockWithCoinbase(coinbaseOutKeyPubKey); + blocks.add(new BlockAndValidity(chainHead, true, false, chainHead.getHash(), "Initial Block")); + spendableOutputs.offer(new TransactionOutPointWithValue( + new TransactionOutPoint(params, 0, chainHead.getTransactions().get(0).getHash()), + Utils.toNanoCoins(50, 0), chainHead.getTransactions().get(0).getOutputs().get(0).getScriptPubKey())); + for (int i = 1; i < params.getSpendableCoinbaseDepth(); i++) { + chainHead = chainHead.createNextBlockWithCoinbase(coinbaseOutKeyPubKey); + chainHeadHeight++; + blocks.add(new BlockAndValidity(chainHead, true, false, chainHead.getHash(), "Initial Block chain output generation")); + spendableOutputs.offer(new TransactionOutPointWithValue( + new TransactionOutPoint(params, 0, chainHead.getTransactions().get(0).getHash()), + Utils.toNanoCoins(50, 0), chainHead.getTransactions().get(0).getOutputs().get(0).getScriptPubKey())); + } + + // Start by building a couple of blocks on top of the genesis block. + Block b1 = createNextBlock(chainHead, chainHeadHeight + 1, spendableOutputs.poll(), null); + blocks.add(new BlockAndValidity(b1, true, false, b1.getHash(), "b1")); + spendableOutputs.offer(new TransactionOutPointWithValue( + new TransactionOutPoint(params, 0, b1.getTransactions().get(0).getHash()), + b1.getTransactions().get(0).getOutputs().get(0).getValue(), + b1.getTransactions().get(0).getOutputs().get(0).getScriptPubKey())); + + TransactionOutPointWithValue out1 = spendableOutputs.poll(); + Block b2 = createNextBlock(b1, chainHeadHeight + 2, out1, null); + blocks.add(new BlockAndValidity(b2, true, false, b2.getHash(), "b2")); + // Make sure nothing funky happens if we try to re-add b2 + blocks.add(new BlockAndValidity(b2, true, false, b2.getHash(), "b2")); + spendableOutputs.offer(new TransactionOutPointWithValue( + new TransactionOutPoint(params, 0, b2.getTransactions().get(0).getHash()), + b2.getTransactions().get(0).getOutputs().get(0).getValue(), + b2.getTransactions().get(0).getOutputs().get(0).getScriptPubKey())); + // We now have the following chain (which output is spent is in parentheses): + // genesis -> b1 (0) -> b2 (1) + // + // so fork like this: + // + // genesis -> b1 (0) -> b2 (1) + // \-> b3 (1) + // + // Nothing should happen at this point. We saw b2 first so it takes priority. + Block b3 = createNextBlock(b1, chainHeadHeight + 2, out1, null); + blocks.add(new BlockAndValidity(b3, true, false, b2.getHash(), "b3")); + // Make sure nothing breaks if we add b3 twice + blocks.add(new BlockAndValidity(b3, true, false, b2.getHash(), "b3")); + // Now we add another block to make the alternative chain longer. + TransactionOutPointWithValue out2 = spendableOutputs.poll(); + + Block b4 = createNextBlock(b3, chainHeadHeight + 3, out2, null); + blocks.add(new BlockAndValidity(b4, true, false, b4.getHash(), "b4")); + // + // genesis -> b1 (0) -> b2 (1) + // \-> b3 (1) -> b4 (2) + // + // ... and back to the first chain. + Block b5 = createNextBlock(b2, chainHeadHeight + 3, out2, null); + blocks.add(new BlockAndValidity(b5, true, false, b4.getHash(), "b5")); + spendableOutputs.offer(new TransactionOutPointWithValue( + new TransactionOutPoint(params, 0, b5.getTransactions().get(0).getHash()), + b5.getTransactions().get(0).getOutputs().get(0).getValue(), + b5.getTransactions().get(0).getOutputs().get(0).getScriptPubKey())); + + TransactionOutPointWithValue out3 = spendableOutputs.poll(); + + Block b6 = createNextBlock(b5, chainHeadHeight + 4, out3, null); + blocks.add(new BlockAndValidity(b6, true, false, b6.getHash(), "b6")); + // + // genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3) + // \-> b3 (1) -> b4 (2) + // + + // Try to create a fork that double-spends + // genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3) + // \-> b7 (2) -> b8 (4) + // \-> b3 (1) -> b4 (2) + // + Block b7 = createNextBlock(b5, chainHeadHeight + 5, out2, null); + blocks.add(new BlockAndValidity(b7, true, false, b6.getHash(), "b7")); + + TransactionOutPointWithValue out4 = spendableOutputs.poll(); + + Block b8 = createNextBlock(b7, chainHeadHeight + 6, out4, null); + blocks.add(new BlockAndValidity(b8, false, true, b6.getHash(), "b8")); + + // Try to create a block that has too much fee + // genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3) + // \-> b9 (4) + // \-> b3 (1) -> b4 (2) + // + Block b9 = createNextBlock(b6, chainHeadHeight + 5, out4, BigInteger.valueOf(1)); + blocks.add(new BlockAndValidity(b9, false, true, b6.getHash(), "b9")); + + // Create a fork that ends in a block with too much fee (the one that causes the reorg) + // genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3) + // \-> b10 (3) -> b11 (4) + // \-> b3 (1) -> b4 (2) + // + Block b10 = createNextBlock(b5, chainHeadHeight + 4, out3, null); + blocks.add(new BlockAndValidity(b10, true, false, b6.getHash(), "b10")); + + Block b11 = createNextBlock(b10, chainHeadHeight + 5, out4, BigInteger.valueOf(1)); + blocks.add(new BlockAndValidity(b11, false, true, b6.getHash(), "b11")); + + // Try again, but with a valid fork first + // genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3) + // \-> b12 (3) -> b13 (4) -> b14 (5) + // (b12 added last) + // \-> b3 (1) -> b4 (2) + // + Block b12 = createNextBlock(b5, chainHeadHeight + 4, out3, null); + spendableOutputs.offer(new TransactionOutPointWithValue( + new TransactionOutPoint(params, 0, b12.getTransactions().get(0).getHash()), + b12.getTransactions().get(0).getOutputs().get(0).getValue(), + b12.getTransactions().get(0).getOutputs().get(0).getScriptPubKey())); + + Block b13 = createNextBlock(b12, chainHeadHeight + 5, out4, null); + blocks.add(new BlockAndValidity(b13, false, false, b6.getHash(), "b13")); + // Make sure we dont die if an orphan gets added twice + blocks.add(new BlockAndValidity(b13, false, false, b6.getHash(), "b13")); + spendableOutputs.offer(new TransactionOutPointWithValue( + new TransactionOutPoint(params, 0, b13.getTransactions().get(0).getHash()), + b13.getTransactions().get(0).getOutputs().get(0).getValue(), + b13.getTransactions().get(0).getOutputs().get(0).getScriptPubKey())); + + TransactionOutPointWithValue out5 = spendableOutputs.poll(); + + Block b14 = createNextBlock(b13, chainHeadHeight + 6, out5, BigInteger.valueOf(1)); + // This will be "validly" added, though its actually invalid, it will just be marked orphan + // and will be discarded when an attempt is made to reorg to it. + // TODO: Use a WeakReference to check that it is freed properly after the reorg + blocks.add(new BlockAndValidity(b14, false, false, b6.getHash(), "b14")); + // Make sure we dont die if an orphan gets added twice + blocks.add(new BlockAndValidity(b14, false, false, b6.getHash(), "b14")); + + blocks.add(new BlockAndValidity(b12, false, true, b13.getHash(), "b12")); + + // Add a block with MAX_BLOCK_SIGOPS and one with one more sigop + // genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3) + // \-> b12 (3) -> b13 (4) -> b15 (5) -> b16 (6) + // \-> b3 (1) -> b4 (2) + // + Block b15 = createNextBlock(b13, chainHeadHeight + 6, out5, null); + { + int sigOps = 0; + for (Transaction tx : b15.transactions) { + sigOps += tx.getSigOpCount(); + } + Transaction tx = new Transaction(params); + byte[] outputScript = new byte[Block.MAX_BLOCK_SIGOPS - sigOps]; + Arrays.fill(outputScript, (byte)Script.OP_CHECKSIG); + tx.addOutput(new TransactionOutput(params, tx, BigInteger.valueOf(1), outputScript)); + addOnlyInputToTransaction(tx, new TransactionOutPointWithValue( + new TransactionOutPoint(params, 1, b15.getTransactions().get(1).getHash()), + BigInteger.valueOf(1), b15.getTransactions().get(1).getOutputs().get(1).getScriptPubKey())); + b15.addTransaction(tx); + } + b15.solve(); + + blocks.add(new BlockAndValidity(b15, true, false, b15.getHash(), "b15")); + spendableOutputs.offer(new TransactionOutPointWithValue( + new TransactionOutPoint(params, 0, b15.getTransactions().get(0).getHash()), + b15.getTransactions().get(0).getOutputs().get(0).getValue(), + b15.getTransactions().get(0).getOutputs().get(0).getScriptPubKey())); + + TransactionOutPointWithValue out6 = spendableOutputs.poll(); + + Block b16 = createNextBlock(b15, chainHeadHeight + 7, out6, null); + { + int sigOps = 0; + for (Transaction tx : b16.transactions) { + sigOps += tx.getSigOpCount(); + } + Transaction tx = new Transaction(params); + byte[] outputScript = new byte[Block.MAX_BLOCK_SIGOPS - sigOps + 1]; + Arrays.fill(outputScript, (byte)Script.OP_CHECKSIG); + tx.addOutput(new TransactionOutput(params, tx, BigInteger.valueOf(1), outputScript)); + addOnlyInputToTransaction(tx, new TransactionOutPointWithValue( + new TransactionOutPoint(params, 1, b16.getTransactions().get(1).getHash()), + BigInteger.valueOf(1), b16.getTransactions().get(1).getOutputs().get(1).getScriptPubKey())); + b16.addTransaction(tx); + } + b16.solve(); + + blocks.add(new BlockAndValidity(b16, false, true, b15.getHash(), "b16")); + + // Attempt to spend a transaction created on a different fork + // genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3) + // \-> b12 (3) -> b13 (4) -> b15 (5) -> b17 (6) + // \-> b3 (1) -> b4 (2) + // + Block b17 = createNextBlock(b15, chainHeadHeight + 7, out6, null); + { + Transaction tx = new Transaction(params); + tx.addOutput(new TransactionOutput(params, tx, BigInteger.valueOf(1), new byte[] {})); + addOnlyInputToTransaction(tx, new TransactionOutPointWithValue( + new TransactionOutPoint(params, 1, b3.getTransactions().get(1).getHash()), + BigInteger.valueOf(1), b3.getTransactions().get(1).getOutputs().get(1).getScriptPubKey())); + b17.addTransaction(tx); + } + b17.solve(); + blocks.add(new BlockAndValidity(b17, false, true, b15.getHash(), "b17")); + + // Attempt to spend a transaction created on a different fork (on a fork this time) + // genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3) + // \-> b12 (3) -> b13 (4) -> b15 (5) + // \-> b18 (5) -> b19 (6) + // \-> b3 (1) -> b4 (2) + // + Block b18 = createNextBlock(b13, chainHeadHeight + 6, out5, null); + { + Transaction tx = new Transaction(params); + tx.addOutput(new TransactionOutput(params, tx, BigInteger.valueOf(1), new byte[] {})); + addOnlyInputToTransaction(tx, new TransactionOutPointWithValue( + new TransactionOutPoint(params, 1, b3.getTransactions().get(1).getHash()), + BigInteger.valueOf(1), b3.getTransactions().get(1).getOutputs().get(1).getScriptPubKey())); + b18.addTransaction(tx); + } + b18.solve(); + blocks.add(new BlockAndValidity(b18, true, false, b15.getHash(), "b17")); + + Block b19 = createNextBlock(b18, chainHeadHeight + 7, out6, null); + blocks.add(new BlockAndValidity(b19, false, true, b15.getHash(), "b19")); + + // Attempt to spend a coinbase at depth too low + // genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3) + // \-> b12 (3) -> b13 (4) -> b15 (5) -> b20 (7) + // \-> b3 (1) -> b4 (2) + // + TransactionOutPointWithValue out7 = spendableOutputs.poll(); + + Block b20 = createNextBlock(b15, chainHeadHeight + 7, out7, null); + blocks.add(new BlockAndValidity(b20, false, true, b15.getHash(), "b20")); + + // Attempt to spend a coinbase at depth too low (on a fork this time) + // genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3) + // \-> b12 (3) -> b13 (4) -> b15 (5) + // \-> b21 (6) -> b22 (5) + // \-> b3 (1) -> b4 (2) + // + Block b21 = createNextBlock(b13, chainHeadHeight + 6, out6, null); + blocks.add(new BlockAndValidity(b21, true, false, b15.getHash(), "b21")); + Block b22 = createNextBlock(b21, chainHeadHeight + 7, out5, null); + blocks.add(new BlockAndValidity(b22, false, true, b15.getHash(), "b22")); + + // Create a block on either side of MAX_BLOCK_SIZE and make sure its accepted/rejected + // genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3) + // \-> b12 (3) -> b13 (4) -> b15 (5) -> b23 (6) + // \-> b24 (6) -> b25 (7) + // \-> b3 (1) -> b4 (2) + // + Block b23 = createNextBlock(b15, chainHeadHeight + 7, out6, null); + { + Transaction tx = new Transaction(params); + // Signature size is non-deterministic, so it may take several runs before finding any off-by-one errors + byte[] outputScript = new byte[Block.MAX_BLOCK_SIZE - b23.getMessageSize() - 138]; + Arrays.fill(outputScript, (byte)Script.OP_FALSE); + tx.addOutput(new TransactionOutput(params, tx, BigInteger.valueOf(1), outputScript)); + addOnlyInputToTransaction(tx, new TransactionOutPointWithValue( + new TransactionOutPoint(params, 1, b23.getTransactions().get(1).getHash()), + BigInteger.valueOf(1), b23.getTransactions().get(1).getOutputs().get(1).getScriptPubKey())); + b23.addTransaction(tx); + } + b23.solve(); + blocks.add(new BlockAndValidity(b23, true, false, b23.getHash(), "b23")); + spendableOutputs.offer(new TransactionOutPointWithValue( + new TransactionOutPoint(params, 0, b23.getTransactions().get(0).getHash()), + b23.getTransactions().get(0).getOutputs().get(0).getValue(), + b23.getTransactions().get(0).getOutputs().get(0).getScriptPubKey())); + + Block b24 = createNextBlock(b15, chainHeadHeight + 7, out6, null); + { + Transaction tx = new Transaction(params); + // Signature size is non-deterministic, so it may take several runs before finding any off-by-one errors + byte[] outputScript = new byte[Block.MAX_BLOCK_SIZE - b24.getMessageSize() - 135]; + Arrays.fill(outputScript, (byte)Script.OP_FALSE); + tx.addOutput(new TransactionOutput(params, tx, BigInteger.valueOf(1), outputScript)); + addOnlyInputToTransaction(tx, new TransactionOutPointWithValue( + new TransactionOutPoint(params, 1, b24.getTransactions().get(1).getHash()), + BigInteger.valueOf(1), b24.getTransactions().get(1).getOutputs().get(1).getScriptPubKey())); + b24.addTransaction(tx); + } + b24.solve(); + blocks.add(new BlockAndValidity(b24, false, true, b23.getHash(), "b24")); + + // Extend the b24 chain to make sure bitcoind isn't accepting b24 + Block b25 = createNextBlock(b24, chainHeadHeight + 8, out7, null); + blocks.add(new BlockAndValidity(b25, false, false, b23.getHash(), "b25")); + + // Create blocks with a coinbase input script size out of range + // genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3) + // \-> b12 (3) -> b13 (4) -> b15 (5) -> b23 (6) -> b30 (7) + // \-> ... (6) -> ... (7) + // \-> b3 (1) -> b4 (2) + // + Block b26 = createNextBlock(b15, chainHeadHeight + 7, out6, null); + // 1 is too small, but we already generate every other block with 2, so that is tested + b26.getTransactions().get(0).getInputs().get(0).setScriptBytes(new byte[] {0}); + b26.setMerkleRoot(null); + b26.solve(); + blocks.add(new BlockAndValidity(b26, false, true, b23.getHash(), "b26")); + + // Extend the b26 chain to make sure bitcoind isn't accepting b26 + Block b27 = createNextBlock(b26, chainHeadHeight + 8, out7, null); + blocks.add(new BlockAndValidity(b27, false, false, b23.getHash(), "b27")); + + Block b28 = createNextBlock(b15, chainHeadHeight + 7, out6, null); + { + byte[] coinbase = new byte[101]; + Arrays.fill(coinbase, (byte)0); + b28.getTransactions().get(0).getInputs().get(0).setScriptBytes(coinbase); + } + b28.setMerkleRoot(null); + b28.solve(); + blocks.add(new BlockAndValidity(b28, false, true, b23.getHash(), "b28")); + + // Extend the b28 chain to make sure bitcoind isn't accepting b28 + Block b29 = createNextBlock(b28, chainHeadHeight + 8, out7, null); + blocks.add(new BlockAndValidity(b29, false, false, b23.getHash(), "b29")); + + Block b30 = createNextBlock(b23, chainHeadHeight + 8, out7, null); + { + byte[] coinbase = new byte[100]; + Arrays.fill(coinbase, (byte)0); + b30.getTransactions().get(0).getInputs().get(0).setScriptBytes(coinbase); + } + b30.setMerkleRoot(null); + b30.solve(); + blocks.add(new BlockAndValidity(b30, true, false, b30.getHash(), "b30")); + spendableOutputs.offer(new TransactionOutPointWithValue( + new TransactionOutPoint(params, 0, b30.getTransactions().get(0).getHash()), + b30.getTransactions().get(0).getOutputs().get(0).getValue(), + b30.getTransactions().get(0).getOutputs().get(0).getScriptPubKey())); + + // Check sigops of OP_CHECKMULTISIG/OP_CHECKMULTISIGVERIFY/OP_CHECKSIGVERIFY + // 6 (3) + // 12 (3) -> b13 (4) -> b15 (5) -> b23 (6) -> b30 (7) -> b31 (8) -> b33 (9) -> b35 (10) + // \-> b36 (11) + // \-> b34 (10) + // \-> b32 (9) + // + TransactionOutPointWithValue out8 = spendableOutputs.poll(); + + Block b31 = createNextBlock(b30, chainHeadHeight + 9, out8, null); + { + int sigOps = 0; + for (Transaction tx : b31.transactions) { + sigOps += tx.getSigOpCount(); + } + Transaction tx = new Transaction(params); + byte[] outputScript = new byte[(Block.MAX_BLOCK_SIGOPS - sigOps)/20]; + Arrays.fill(outputScript, (byte)Script.OP_CHECKMULTISIG); + tx.addOutput(new TransactionOutput(params, tx, BigInteger.valueOf(1), outputScript)); + addOnlyInputToTransaction(tx, new TransactionOutPointWithValue( + new TransactionOutPoint(params, 1, b31.getTransactions().get(1).getHash()), + BigInteger.valueOf(1), b31.getTransactions().get(1).getOutputs().get(1).getScriptPubKey())); + b31.addTransaction(tx); + } + b31.solve(); + + blocks.add(new BlockAndValidity(b31, true, false, b31.getHash(), "b31")); + spendableOutputs.offer(new TransactionOutPointWithValue( + new TransactionOutPoint(params, 0, b31.getTransactions().get(0).getHash()), + b31.getTransactions().get(0).getOutputs().get(0).getValue(), + b31.getTransactions().get(0).getOutputs().get(0).getScriptPubKey())); + + TransactionOutPointWithValue out9 = spendableOutputs.poll(); + + Block b32 = createNextBlock(b31, chainHeadHeight + 10, out9, null); + { + int sigOps = 0; + for (Transaction tx : b32.transactions) { + sigOps += tx.getSigOpCount(); + } + Transaction tx = new Transaction(params); + byte[] outputScript = new byte[(Block.MAX_BLOCK_SIGOPS - sigOps)/20 + (Block.MAX_BLOCK_SIGOPS - sigOps)%20 + 1]; + Arrays.fill(outputScript, (byte)Script.OP_CHECKMULTISIG); + for (int i = 0; i < (Block.MAX_BLOCK_SIGOPS - sigOps)%20; i++) + outputScript[i] = (byte)Script.OP_CHECKSIG; + tx.addOutput(new TransactionOutput(params, tx, BigInteger.valueOf(1), outputScript)); + addOnlyInputToTransaction(tx, new TransactionOutPointWithValue( + new TransactionOutPoint(params, 1, b32.getTransactions().get(1).getHash()), + BigInteger.valueOf(1), b32.getTransactions().get(1).getOutputs().get(1).getScriptPubKey())); + b32.addTransaction(tx); + } + b32.solve(); + + blocks.add(new BlockAndValidity(b32, false, true, b31.getHash(), "b32")); + + + Block b33 = createNextBlock(b31, chainHeadHeight + 10, out9, null); + { + int sigOps = 0; + for (Transaction tx : b33.transactions) { + sigOps += tx.getSigOpCount(); + } + Transaction tx = new Transaction(params); + byte[] outputScript = new byte[(Block.MAX_BLOCK_SIGOPS - sigOps)/20]; + Arrays.fill(outputScript, (byte)Script.OP_CHECKMULTISIGVERIFY); + tx.addOutput(new TransactionOutput(params, tx, BigInteger.valueOf(1), outputScript)); + addOnlyInputToTransaction(tx, new TransactionOutPointWithValue( + new TransactionOutPoint(params, 1, b33.getTransactions().get(1).getHash()), + BigInteger.valueOf(1), b33.getTransactions().get(1).getOutputs().get(1).getScriptPubKey())); + b33.addTransaction(tx); + } + b33.solve(); + + blocks.add(new BlockAndValidity(b33, true, false, b33.getHash(), "b33")); + spendableOutputs.offer(new TransactionOutPointWithValue( + new TransactionOutPoint(params, 0, b33.getTransactions().get(0).getHash()), + b33.getTransactions().get(0).getOutputs().get(0).getValue(), + b33.getTransactions().get(0).getOutputs().get(0).getScriptPubKey())); + + TransactionOutPointWithValue out10 = spendableOutputs.poll(); + + Block b34 = createNextBlock(b33, chainHeadHeight + 11, out10, null); + { + int sigOps = 0; + for (Transaction tx : b34.transactions) { + sigOps += tx.getSigOpCount(); + } + Transaction tx = new Transaction(params); + byte[] outputScript = new byte[(Block.MAX_BLOCK_SIGOPS - sigOps)/20 + (Block.MAX_BLOCK_SIGOPS - sigOps)%20 + 1]; + Arrays.fill(outputScript, (byte)Script.OP_CHECKMULTISIGVERIFY); + for (int i = 0; i < (Block.MAX_BLOCK_SIGOPS - sigOps)%20; i++) + outputScript[i] = (byte)Script.OP_CHECKSIG; + tx.addOutput(new TransactionOutput(params, tx, BigInteger.valueOf(1), outputScript)); + addOnlyInputToTransaction(tx, new TransactionOutPointWithValue( + new TransactionOutPoint(params, 1, b34.getTransactions().get(1).getHash()), + BigInteger.valueOf(1), b34.getTransactions().get(1).getOutputs().get(1).getScriptPubKey())); + b34.addTransaction(tx); + } + b34.solve(); + + blocks.add(new BlockAndValidity(b34, false, true, b33.getHash(), "b34")); + + + Block b35 = createNextBlock(b33, chainHeadHeight + 11, out10, null); + { + int sigOps = 0; + for (Transaction tx : b35.transactions) { + sigOps += tx.getSigOpCount(); + } + Transaction tx = new Transaction(params); + byte[] outputScript = new byte[Block.MAX_BLOCK_SIGOPS - sigOps]; + Arrays.fill(outputScript, (byte)Script.OP_CHECKSIGVERIFY); + tx.addOutput(new TransactionOutput(params, tx, BigInteger.valueOf(1), outputScript)); + addOnlyInputToTransaction(tx, new TransactionOutPointWithValue( + new TransactionOutPoint(params, 1, b35.getTransactions().get(1).getHash()), + BigInteger.valueOf(1), b35.getTransactions().get(1).getOutputs().get(1).getScriptPubKey())); + b35.addTransaction(tx); + } + b35.solve(); + + blocks.add(new BlockAndValidity(b35, true, false, b35.getHash(), "b35")); + spendableOutputs.offer(new TransactionOutPointWithValue( + new TransactionOutPoint(params, 0, b35.getTransactions().get(0).getHash()), + b35.getTransactions().get(0).getOutputs().get(0).getValue(), + b35.getTransactions().get(0).getOutputs().get(0).getScriptPubKey())); + + TransactionOutPointWithValue out11 = spendableOutputs.poll(); + + Block b36 = createNextBlock(b35, chainHeadHeight + 12, out11, null); + { + int sigOps = 0; + for (Transaction tx : b36.transactions) { + sigOps += tx.getSigOpCount(); + } + Transaction tx = new Transaction(params); + byte[] outputScript = new byte[Block.MAX_BLOCK_SIGOPS - sigOps + 1]; + Arrays.fill(outputScript, (byte)Script.OP_CHECKSIGVERIFY); + tx.addOutput(new TransactionOutput(params, tx, BigInteger.valueOf(1), outputScript)); + addOnlyInputToTransaction(tx, new TransactionOutPointWithValue( + new TransactionOutPoint(params, 1, b36.getTransactions().get(1).getHash()), + BigInteger.valueOf(1), b36.getTransactions().get(1).getOutputs().get(1).getScriptPubKey())); + b36.addTransaction(tx); + } + b36.solve(); + + blocks.add(new BlockAndValidity(b36, false, true, b35.getHash(), "b36")); + + // Check spending of a transaction in a block which failed to connect + // (test block store transaction abort handling, not that it should get this far if that's broken...) + // 6 (3) + // 12 (3) -> b13 (4) -> b15 (5) -> b23 (6) -> b30 (7) -> b31 (8) -> b33 (9) -> b35 (10) + // \-> b37 (11) + // \-> b38 (11) + // + Block b37 = createNextBlock(b35, chainHeadHeight + 10, out11, null); + { + Transaction tx = new Transaction(params); + tx.addOutput(new TransactionOutput(params, tx, BigInteger.valueOf(1), new byte[] {})); + addOnlyInputToTransaction(tx, out11); // double spend out11 + b37.addTransaction(tx); + } + b37.solve(); + blocks.add(new BlockAndValidity(b37, false, true, b35.getHash(), "b37")); + + Block b38 = createNextBlock(b35, chainHeadHeight + 10, out11, null); + { + Transaction tx = new Transaction(params); + tx.addOutput(new TransactionOutput(params, tx, BigInteger.valueOf(1), new byte[] {})); + // Attempt to spend b37's first non-coinbase tx, at which point b37 was still considered valid + addOnlyInputToTransaction(tx, new TransactionOutPointWithValue( + new TransactionOutPoint(params, 1, b37.getTransactions().get(1).getHash()), + BigInteger.valueOf(1), b37.getTransactions().get(1).getOutputs().get(1).getScriptPubKey())); + b38.addTransaction(tx); + } + b38.solve(); + blocks.add(new BlockAndValidity(b38, false, true, b35.getHash(), "b38")); + + // Check P2SH SigOp counting + // 13 (4) -> b15 (5) -> b23 (6) -> b30 (7) -> b31 (8) -> b33 (9) -> b35 (10) -> b39 (11) -> b41 (12) + // \-> b40 (12) + // + // Create some P2SH outputs that will require 6 sigops to spend + byte[] b39p2shScriptPubKey; + int b39numP2SHOutputs = 0, b39sigOpsPerOutput = 6; + Block b39 = createNextBlock(b35, chainHeadHeight + 10, null, null); + { + ByteArrayOutputStream p2shScriptPubKey = new UnsafeByteArrayOutputStream(); + try { + Script.writeBytes(p2shScriptPubKey, coinbaseOutKeyPubKey); + p2shScriptPubKey.write(Script.OP_2DUP); + p2shScriptPubKey.write(Script.OP_CHECKSIGVERIFY); + p2shScriptPubKey.write(Script.OP_2DUP); + p2shScriptPubKey.write(Script.OP_CHECKSIGVERIFY); + p2shScriptPubKey.write(Script.OP_2DUP); + p2shScriptPubKey.write(Script.OP_CHECKSIGVERIFY); + p2shScriptPubKey.write(Script.OP_2DUP); + p2shScriptPubKey.write(Script.OP_CHECKSIGVERIFY); + p2shScriptPubKey.write(Script.OP_2DUP); + p2shScriptPubKey.write(Script.OP_CHECKSIGVERIFY); + p2shScriptPubKey.write(Script.OP_CHECKSIG); + } catch (IOException e) { + throw new RuntimeException(e); // Cannot happen. + } + b39p2shScriptPubKey = p2shScriptPubKey.toByteArray(); + + byte[] scriptHash = Utils.sha256hash160(b39p2shScriptPubKey); + UnsafeByteArrayOutputStream scriptPubKey = new UnsafeByteArrayOutputStream(scriptHash.length + 3); + scriptPubKey.write(Script.OP_HASH160); + try { + Script.writeBytes(scriptPubKey, scriptHash); + } catch (IOException e) { + throw new RuntimeException(e); // Cannot happen. + } + scriptPubKey.write(Script.OP_EQUAL); + + BigInteger lastOutputValue = out11.value.subtract(BigInteger.valueOf(1)); + TransactionOutPoint lastOutPoint; + { + Transaction tx = new Transaction(params); + tx.addOutput(new TransactionOutput(params, tx, BigInteger.valueOf(1), scriptPubKey.toByteArray())); + tx.addOutput(new TransactionOutput(params, tx, lastOutputValue, new byte[]{Script.OP_1})); + addOnlyInputToTransaction(tx, out11); + lastOutPoint = new TransactionOutPoint(params, 1, tx.getHash()); + b39.addTransaction(tx); + } + b39numP2SHOutputs++; + + while (b39.getMessageSize() < Block.MAX_BLOCK_SIZE) + { + Transaction tx = new Transaction(params); + + lastOutputValue = lastOutputValue.subtract(BigInteger.valueOf(1)); + tx.addOutput(new TransactionOutput(params, tx, BigInteger.valueOf(1), scriptPubKey.toByteArray())); + tx.addOutput(new TransactionOutput(params, tx, lastOutputValue, new byte[]{Script.OP_1})); + tx.addInput(new TransactionInput(params, tx, new byte[]{Script.OP_1}, lastOutPoint)); + lastOutPoint = new TransactionOutPoint(params, 1, tx.getHash()); + + if (b39.getMessageSize() + tx.getMessageSize() < Block.MAX_BLOCK_SIZE) { + b39.addTransaction(tx); + b39numP2SHOutputs++; + } else + break; + } + } + b39.solve(); + blocks.add(new BlockAndValidity(b39, true, false, b39.getHash(), "b39")); + spendableOutputs.offer(new TransactionOutPointWithValue( + new TransactionOutPoint(params, 0, b39.getTransactions().get(0).getHash()), + b39.getTransactions().get(0).getOutputs().get(0).getValue(), + b39.getTransactions().get(0).getOutputs().get(0).getScriptPubKey())); + + TransactionOutPointWithValue out12 = spendableOutputs.poll(); + + Block b40 = createNextBlock(b39, chainHeadHeight + 11, out12, null); + { + int sigOps = 0; + for (Transaction tx : b40.transactions) { + sigOps += tx.getSigOpCount(); + } + + int numTxes = (Block.MAX_BLOCK_SIGOPS - sigOps) / b39sigOpsPerOutput; + Preconditions.checkState(numTxes <= b39numP2SHOutputs); + + TransactionOutPoint lastOutPoint = new TransactionOutPoint(params, 2, b40.getTransactions().get(1).getHash()); + + byte[] scriptSig = null; + for (int i = 1; i <= numTxes; i++) { + Transaction tx = new Transaction(params); + tx.addOutput(new TransactionOutput(params, tx, BigInteger.valueOf(1), new byte[] {Script.OP_1})); + tx.addInput(new TransactionInput(params, tx, new byte[]{Script.OP_1}, lastOutPoint)); + + TransactionInput input = new TransactionInput(params, tx, new byte[]{}, + new TransactionOutPoint(params, 0, b39.getTransactions().get(i).getHash())); + tx.addInput(input); + + if (scriptSig == null) { + // Exploit the SigHash.SINGLE bug to avoid having to make more than one signature + Sha256Hash hash = tx.hashTransactionForSignature(1, b39p2shScriptPubKey, SigHash.SINGLE, false); + + // Sign input + try { + ByteArrayOutputStream bos = new UnsafeByteArrayOutputStream(73); + bos.write(coinbaseOutKey.sign(hash.getBytes())); + bos.write(SigHash.SINGLE.ordinal() + 1); + byte[] signature = bos.toByteArray(); + + ByteArrayOutputStream scriptSigBos = new UnsafeByteArrayOutputStream(signature.length + b39p2shScriptPubKey.length + 3); + Script.writeBytes(scriptSigBos, new byte[] {(byte) Script.OP_CHECKSIG}); + scriptSigBos.write(Script.createInputScript(signature)); + Script.writeBytes(scriptSigBos, b39p2shScriptPubKey); + + scriptSig = scriptSigBos.toByteArray(); + } catch (IOException e) { + throw new RuntimeException(e); // Cannot happen. + } + } + + input.setScriptBytes(scriptSig); + + lastOutPoint = new TransactionOutPoint(params, 0, tx.getHash()); + + b40.addTransaction(tx); + } + + sigOps += numTxes * b39sigOpsPerOutput; + Transaction tx = new Transaction(params); + tx.addInput(new TransactionInput(params, tx, new byte[]{Script.OP_1}, lastOutPoint)); + byte[] scriptPubKey = new byte[Block.MAX_BLOCK_SIGOPS - sigOps + 1]; + Arrays.fill(scriptPubKey, (byte)Script.OP_CHECKSIG); + tx.addOutput(new TransactionOutput(params, tx, BigInteger.ZERO, scriptPubKey)); + b40.addTransaction(tx); + } + b40.solve(); + blocks.add(new BlockAndValidity(b40, false, true, b39.getHash(), "b40")); + + + Block b41 = null; + if (addExpensiveBlocks) { + b41 = createNextBlock(b39, chainHeadHeight + 11, out12, null); + { + int sigOps = 0; + for (Transaction tx : b41.transactions) { + sigOps += tx.getSigOpCount(); + } + + int numTxes = (Block.MAX_BLOCK_SIGOPS - sigOps) + / b39sigOpsPerOutput; + Preconditions.checkState(numTxes <= b39numP2SHOutputs); + + TransactionOutPoint lastOutPoint = new TransactionOutPoint( + params, 2, b41.getTransactions().get(1).getHash()); + + byte[] scriptSig = null; + for (int i = 1; i <= numTxes; i++) { + Transaction tx = new Transaction(params); + tx.addOutput(new TransactionOutput(params, tx, BigInteger + .valueOf(1), new byte[] { Script.OP_1 })); + tx.addInput(new TransactionInput(params, tx, + new byte[] { Script.OP_1 }, lastOutPoint)); + + TransactionInput input = new TransactionInput(params, tx, + new byte[] {}, new TransactionOutPoint(params, 0, + b39.getTransactions().get(i).getHash())); + tx.addInput(input); + + if (scriptSig == null) { + // Exploit the SigHash.SINGLE bug to avoid having to make more than one signature + Sha256Hash hash = tx.hashTransactionForSignature(1, + b39p2shScriptPubKey, SigHash.SINGLE, false); + + // Sign input + try { + ByteArrayOutputStream bos = new UnsafeByteArrayOutputStream( + 73); + bos.write(coinbaseOutKey.sign(hash.getBytes())); + bos.write(SigHash.SINGLE.ordinal() + 1); + byte[] signature = bos.toByteArray(); + + ByteArrayOutputStream scriptSigBos = new UnsafeByteArrayOutputStream( + signature.length + + b39p2shScriptPubKey.length + 3); + Script.writeBytes(scriptSigBos, + new byte[] { (byte) Script.OP_CHECKSIG }); + scriptSigBos.write(Script + .createInputScript(signature)); + Script.writeBytes(scriptSigBos, b39p2shScriptPubKey); + + scriptSig = scriptSigBos.toByteArray(); + } catch (IOException e) { + throw new RuntimeException(e); // Cannot happen. + } + } + + input.setScriptBytes(scriptSig); + + lastOutPoint = new TransactionOutPoint(params, 0, + tx.getHash()); + + b41.addTransaction(tx); + } + + sigOps += numTxes * b39sigOpsPerOutput; + Transaction tx = new Transaction(params); + tx.addInput(new TransactionInput(params, tx, + new byte[] { Script.OP_1 }, lastOutPoint)); + byte[] scriptPubKey = new byte[Block.MAX_BLOCK_SIGOPS - sigOps]; + Arrays.fill(scriptPubKey, (byte) Script.OP_CHECKSIG); + tx.addOutput(new TransactionOutput(params, tx, BigInteger.ZERO, scriptPubKey)); + b41.addTransaction(tx); + } + b41.solve(); + blocks.add(new BlockAndValidity(b41, true, false, b41.getHash(), "b41")); + } + + // Fork off of b39 to create a constant base again + // b23 (6) -> b30 (7) -> b31 (8) -> b33 (9) -> b35 (10) -> b39 (11) -> b42 (12) -> b43 (13) + // \-> b41 (12) + // + Block b42 = createNextBlock(b39, chainHeadHeight + 11, out12, null); + blocks.add(new BlockAndValidity(b42, true, false, b41 == null ? b42.getHash() : b41.getHash(), "b42")); + spendableOutputs.offer(new TransactionOutPointWithValue( + new TransactionOutPoint(params, 0, b42.getTransactions().get(0).getHash()), + b42.getTransactions().get(0).getOutputs().get(0).getValue(), + b42.getTransactions().get(0).getOutputs().get(0).getScriptPubKey())); + + TransactionOutPointWithValue out13 = spendableOutputs.poll(); + + Block b43 = createNextBlock(b42, chainHeadHeight + 12, out13, null); + blocks.add(new BlockAndValidity(b43, true, false, b43.getHash(), "b43")); + spendableOutputs.offer(new TransactionOutPointWithValue( + new TransactionOutPoint(params, 0, b43.getTransactions().get(0).getHash()), + b43.getTransactions().get(0).getOutputs().get(0).getValue(), + b43.getTransactions().get(0).getOutputs().get(0).getScriptPubKey())); + + // Test a number of really invalid scenarios + // -> b31 (8) -> b33 (9) -> b35 (10) -> b39 (11) -> b42 (12) -> b43 (13) -> b44 (14) + // \-> ??? (15) + // + TransactionOutPointWithValue out14 = spendableOutputs.poll(); + + // A valid block created exactly like b44 to make sure the creation itself works + Block b44 = new Block(params); + { + b44.setDifficultyTarget(b43.getDifficultyTarget()); + b44.addCoinbaseTransaction(coinbaseOutKeyPubKey, BigInteger.ZERO); + + Transaction t = new Transaction(params); + // Entirely invalid scriptPubKey to ensure we aren't pre-verifying too much + t.addOutput(new TransactionOutput(params, t, BigInteger.valueOf(0), new byte[] { Script.OP_PUSHDATA1 - 1 })); + t.addOutput(new TransactionOutput(params, t, BigInteger.valueOf(1), Script.createOutputScript(coinbaseOutKeyPubKey))); + // Spendable output + t.addOutput(new TransactionOutput(params, t, BigInteger.ZERO, new byte[] {Script.OP_1})); + addOnlyInputToTransaction(t, out14); + b44.addTransaction(t); + + b44.setPrevBlockHash(b43.getHash()); + b44.setTime(b43.getTimeSeconds() + 1); + } + b44.solve(); + blocks.add(new BlockAndValidity(b44, true, false, b44.getHash(), "b44")); + + TransactionOutPointWithValue out15 = spendableOutputs.poll(); + + // A block with a non-coinbase as the first tx + Block b45 = new Block(params); + { + b45.setDifficultyTarget(b44.getDifficultyTarget()); + //b45.addCoinbaseTransaction(pubKey, coinbaseValue); + + Transaction t = new Transaction(params); + // Entirely invalid scriptPubKey to ensure we aren't pre-verifying too much + t.addOutput(new TransactionOutput(params, t, BigInteger.valueOf(0), new byte[] { Script.OP_PUSHDATA1 - 1 })); + t.addOutput(new TransactionOutput(params, t, BigInteger.valueOf(1), Script.createOutputScript(coinbaseOutKeyPubKey))); + // Spendable output + t.addOutput(new TransactionOutput(params, t, BigInteger.ZERO, new byte[] {Script.OP_1})); + addOnlyInputToTransaction(t, out15); + try { + b45.addTransaction(t); + } catch (RuntimeException e) { } // Should happen + if (b45.getTransactions().size() > 0) + throw new RuntimeException("addTransaction doesn't properly check for adding a non-coinbase as first tx"); + b45.addTransaction(t, false); + + b45.setPrevBlockHash(b44.getHash()); + b45.setTime(b44.getTimeSeconds() + 1); + } + b45.solve(); + blocks.add(new BlockAndValidity(b45, false, true, b44.getHash(), "b45")); + + // A block with no txn + Block b46 = new Block(params); + { + b46.transactions = new ArrayList(); + b46.setDifficultyTarget(b44.getDifficultyTarget()); + b46.setMerkleRoot(Sha256Hash.ZERO_HASH); + + b46.setPrevBlockHash(b44.getHash()); + b46.setTime(b44.getTimeSeconds() + 1); + } + b46.solve(); + blocks.add(new BlockAndValidity(b46, false, true, b44.getHash(), "b46")); + + // A block with invalid work + Block b47 = createNextBlock(b44, chainHeadHeight + 14, out15, null); + { + try { + // Inverse solve + BigInteger target = b47.getDifficultyTargetAsInteger(); + while (true) { + BigInteger h = b47.getHash().toBigInteger(); + if (h.compareTo(target) > 0) // if invalid + break; + // increment the nonce and try again. + b47.setNonce(b47.getNonce() + 1); + } + } catch (VerificationException e) { + throw new RuntimeException(e); // Cannot happen. + } + } + blocks.add(new BlockAndValidity(b47, false, true, b44.getHash(), "b47")); + + // Block with timestamp > 2h in the future + Block b48 = createNextBlock(b44, chainHeadHeight + 14, out15, null); + b48.setTime(Utils.now().getTime() / 1000 + 60*60*3); + b48.solve(); + blocks.add(new BlockAndValidity(b48, false, true, b44.getHash(), "b48")); + + // Block with invalid merkle hash + Block b49 = createNextBlock(b44, chainHeadHeight + 14, out15, null); + b49.setMerkleRoot(Sha256Hash.ZERO_HASH); + b49.solve(); + blocks.add(new BlockAndValidity(b49, false, true, b44.getHash(), "b49")); + + // Block with incorrect POW limit + Block b50 = createNextBlock(b44, chainHeadHeight + 14, out15, null); + { + long diffTarget = b44.getDifficultyTarget(); + diffTarget &= 0xFFBFFFFF; // Make difficulty one bit harder + b50.setDifficultyTarget(diffTarget); + } + b50.solve(); + blocks.add(new BlockAndValidity(b50, false, true, b44.getHash(), "b50")); + + // A block with two coinbase txn + Block b51 = createNextBlock(b44, chainHeadHeight + 14, out15, null); + { + Transaction coinbase = new Transaction(params); + coinbase.addInput(new TransactionInput(params, coinbase, new byte[]{(byte) 0xff, 110, 1})); + coinbase.addOutput(new TransactionOutput(params, coinbase, BigInteger.ONE, Script.createOutputScript(coinbaseOutKeyPubKey))); + b51.addTransaction(coinbase, false); + } + b51.solve(); + blocks.add(new BlockAndValidity(b51, false, true, b44.getHash(), "b51")); + + // A block with duplicate txn + Block b52 = createNextBlock(b44, chainHeadHeight + 14, out15, null); + { + Transaction tx = new Transaction(params); + tx.addOutput(new TransactionOutput(params, tx, BigInteger.valueOf(1), new byte[] {})); + addOnlyInputToTransaction(tx, new TransactionOutPointWithValue( + new TransactionOutPoint(params, 1, b52.getTransactions().get(1).getHash()), + BigInteger.valueOf(1), b52.getTransactions().get(1).getOutputs().get(1).getScriptPubKey())); + b52.addTransaction(tx); + b52.addTransaction(tx); + } + b52.solve(); + blocks.add(new BlockAndValidity(b52, false, true, b44.getHash(), "b52")); + + // Test block timestamp + // -> b31 (8) -> b33 (9) -> b35 (10) -> b39 (11) -> b42 (12) -> b43 (13) -> b53 (14) -> b55 (15) + // \-> b54 (15) + // \-> b44 (14) + // + Block b53 = createNextBlock(b43, chainHeadHeight + 13, out14, null); + blocks.add(new BlockAndValidity(b53, true, false, b44.getHash(), "b53")); + spendableOutputs.offer(new TransactionOutPointWithValue( + new TransactionOutPoint(params, 0, b53.getTransactions().get(0).getHash()), + b53.getTransactions().get(0).getOutputs().get(0).getValue(), + b53.getTransactions().get(0).getOutputs().get(0).getScriptPubKey())); + + // Block with invalid timestamp + Block b54 = createNextBlock(b53, chainHeadHeight + 14, out15, null); + b54.setTime(b35.getTimeSeconds() - 1); + b54.solve(); + blocks.add(new BlockAndValidity(b54, false, true, b44.getHash(), "b54")); + + // Block with valid timestamp + Block b55 = createNextBlock(b53, chainHeadHeight + 14, out15, null); + b55.setTime(b35.getTimeSeconds()); + b55.solve(); + blocks.add(new BlockAndValidity(b55, true, false, b55.getHash(), "b55")); + spendableOutputs.offer(new TransactionOutPointWithValue( + new TransactionOutPoint(params, 0, b55.getTransactions().get(0).getHash()), + b55.getTransactions().get(0).getOutputs().get(0).getValue(), + b55.getTransactions().get(0).getOutputs().get(0).getScriptPubKey())); + + // Test CVE-2012-2459 + // -> b33 (9) -> b35 (10) -> b39 (11) -> b42 (12) -> b43 (13) -> b53 (14) -> b55 (15) -> b57 (16) + // \-> b56 (16) + // + TransactionOutPointWithValue out16 = spendableOutputs.poll(); + + Block b57 = createNextBlock(b55, chainHeadHeight + 15, out16, null); + Transaction b56txToDuplicate; + { + b56txToDuplicate = new Transaction(params); + b56txToDuplicate.addOutput(new TransactionOutput(params, b56txToDuplicate, BigInteger.valueOf(1), new byte[] {})); + addOnlyInputToTransaction(b56txToDuplicate, new TransactionOutPointWithValue( + new TransactionOutPoint(params, 1, b57.getTransactions().get(1).getHash()), + BigInteger.valueOf(1), b57.getTransactions().get(1).getOutputs().get(1).getScriptPubKey())); + b57.addTransaction(b56txToDuplicate); + } + b57.solve(); + + Block b56; + try { + b56 = new Block(params, b57.bitcoinSerialize()); + } catch (ProtocolException e) { + throw new RuntimeException(e); // Cannot happen. + } + b56.addTransaction(b56txToDuplicate); + Preconditions.checkState(b56.getHash().equals(b57.getHash())); + blocks.add(new BlockAndValidity(b56, false, true, b55.getHash(), "b56")); + + blocks.add(new BlockAndValidity(b57, true, false, b57.getHash(), "b57")); + spendableOutputs.offer(new TransactionOutPointWithValue( + new TransactionOutPoint(params, 0, b57.getTransactions().get(0).getHash()), + b57.getTransactions().get(0).getOutputs().get(0).getValue(), + b57.getTransactions().get(0).getOutputs().get(0).getScriptPubKey())); + + // Test a few invalid tx types + // -> b35 (10) -> b39 (11) -> b42 (12) -> b43 (13) -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17) + // \-> ??? (17) + // + TransactionOutPointWithValue out17 = spendableOutputs.poll(); + + // tx with prevout.n out of range + Block b58 = createNextBlock(b57, chainHeadHeight + 16, out17, null); + { + Transaction tx = new Transaction(params); + tx.addOutput(new TransactionOutput(params, tx, BigInteger.ZERO, new byte[] {})); + tx.addInput(new TransactionInput(params, tx, new byte[] { Script.OP_1 }, + new TransactionOutPoint(params, 3, b58.getTransactions().get(1).getHash()))); + b58.addTransaction(tx); + } + b58.solve(); + blocks.add(new BlockAndValidity(b58, false, true, b57.getHash(), "b58")); + + // tx with output value > input value out of range + Block b59 = createNextBlock(b57, chainHeadHeight + 16, out17, null); + { + Transaction tx = new Transaction(params); + tx.addOutput(new TransactionOutput(params, tx, + b59.getTransactions().get(1).getOutputs().get(2).getValue().add(BigInteger.ONE), new byte[] {})); + tx.addInput(new TransactionInput(params, tx, new byte[] { Script.OP_1 }, + new TransactionOutPoint(params, 2, b59.getTransactions().get(1).getHash()))); + b59.addTransaction(tx); + } + b59.solve(); + blocks.add(new BlockAndValidity(b59, false, true, b57.getHash(), "b59")); + + Block b60 = createNextBlock(b57, chainHeadHeight + 16, out17, null); + blocks.add(new BlockAndValidity(b60, true, false, b60.getHash(), "b60")); + spendableOutputs.offer(new TransactionOutPointWithValue( + new TransactionOutPoint(params, 0, b60.getTransactions().get(0).getHash()), + b60.getTransactions().get(0).getOutputs().get(0).getValue(), + b60.getTransactions().get(0).getOutputs().get(0).getScriptPubKey())); + + // Test BIP30 + // -> b39 (11) -> b42 (12) -> b43 (13) -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17) + // \-> b61 (18) + // + TransactionOutPointWithValue out18 = spendableOutputs.poll(); + + Block b61 = createNextBlock(b60, chainHeadHeight + 17, out18, null); + { + byte[] scriptBytes = b61.getTransactions().get(0).getInputs().get(0).getScriptBytes(); + scriptBytes[0]--; // createNextBlock will increment the first script byte on each new block + b61.getTransactions().get(0).getInputs().get(0).setScriptBytes(scriptBytes); + b61.unCache(); + } + b61.solve(); + blocks.add(new BlockAndValidity(b61, false, true, b60.getHash(), "b61")); + + // Test tx.isFinal is properly rejected (not an exhaustive tx.isFinal test, that should be in data-driven transaction tests) + // -> b39 (11) -> b42 (12) -> b43 (13) -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17) + // \-> b62 (18) + // + Block b62 = createNextBlock(b60, chainHeadHeight + 17, null, null); + { + Transaction tx = new Transaction(params); + tx.setLockTime(0xffffffffL); + tx.addOutput(new TransactionOutput(params, tx, BigInteger.ZERO, new byte[] { Script.OP_TRUE })); + addOnlyInputToTransaction(tx, out18, 0); + b62.addTransaction(tx); + } + b62.solve(); + blocks.add(new BlockAndValidity(b62, false, true, b60.getHash(), "b62")); + + //TODO: Explicitly address MoneyRange() checks + + // (finally) return the created chain + return blocks; + } + + private Block createNextBlock(Block baseBlock, int nextBlockHeight, TransactionOutPointWithValue prevOut, + BigInteger additionalCoinbaseValue) { + BigInteger coinbaseValue = Utils.toNanoCoins(50, 0).shiftRight(nextBlockHeight / params.getSubsidyDecreaseBlockCount()) + .add((prevOut != null ? prevOut.value : BigInteger.valueOf(0))).subtract(BigInteger.valueOf(1)) + .add(additionalCoinbaseValue == null ? BigInteger.valueOf(0) : additionalCoinbaseValue); + Block block = baseBlock.createNextBlockWithCoinbase(coinbaseOutKeyPubKey, coinbaseValue); + if (prevOut != null) { + Transaction t = new Transaction(params); + // Entirely invalid scriptPubKey to ensure we aren't pre-verifying too much + t.addOutput(new TransactionOutput(params, t, BigInteger.valueOf(0), new byte[] { Script.OP_PUSHDATA1 - 1 })); + t.addOutput(new TransactionOutput(params, t, BigInteger.valueOf(1), Script.createOutputScript(coinbaseOutKeyPubKey))); + // Spendable output + t.addOutput(new TransactionOutput(params, t, BigInteger.ZERO, new byte[] {Script.OP_1})); + addOnlyInputToTransaction(t, prevOut); + block.addTransaction(t); + block.solve(); + } + return block; + } + + private void addOnlyInputToTransaction(Transaction t, TransactionOutPointWithValue prevOut) { + addOnlyInputToTransaction(t, prevOut, TransactionInput.NO_SEQUENCE); + } + + private void addOnlyInputToTransaction(Transaction t, TransactionOutPointWithValue prevOut, long sequence) { + TransactionInput input = new TransactionInput(params, t, new byte[]{}, prevOut.outpoint); + input.setSequence(sequence); + t.addInput(input); + + byte[] connectedPubKeyScript = prevOut.scriptPubKey.program; + Sha256Hash hash = t.hashTransactionForSignature(0, connectedPubKeyScript, SigHash.ALL, false); + + // Sign input + try { + ByteArrayOutputStream bos = new UnsafeByteArrayOutputStream(73); + bos.write(coinbaseOutKey.sign(hash.getBytes())); + bos.write(SigHash.ALL.ordinal() + 1); + byte[] signature = bos.toByteArray(); + + Preconditions.checkState(prevOut.scriptPubKey.isSentToRawPubKey()); + input.setScriptBytes(Script.createInputScript(signature)); + } catch (IOException e) { + throw new RuntimeException(e); // Cannot happen. + } + } +} diff --git a/core/src/test/java/com/google/bitcoin/core/FullPrunedBlockChainTest.java b/core/src/test/java/com/google/bitcoin/core/FullPrunedBlockChainTest.java index f5117837..3961d248 100644 --- a/core/src/test/java/com/google/bitcoin/core/FullPrunedBlockChainTest.java +++ b/core/src/test/java/com/google/bitcoin/core/FullPrunedBlockChainTest.java @@ -1,5 +1,6 @@ /* * Copyright 2012 Google Inc. + * Copyright 2012 Matt Corallo. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,13 +17,20 @@ package com.google.bitcoin.core; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.lang.ref.WeakReference; +import java.util.List; + +import com.google.bitcoin.core.Transaction.SigHash; import com.google.bitcoin.store.FullPrunedBlockStore; import com.google.bitcoin.store.MemoryFullPrunedBlockStore; import com.google.bitcoin.utils.BriefLogFormatter; + import org.junit.Before; import org.junit.Test; - -import java.lang.ref.WeakReference; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import static org.junit.Assert.*; @@ -31,17 +39,14 @@ import static org.junit.Assert.*; */ public class FullPrunedBlockChainTest { - // The size of spendableOutputs - private static final int MAX_BLOCK_HEIGHT = 5; + private static final Logger log = LoggerFactory.getLogger(FullPrunedBlockChainTest.class); + + // The number of undoable blocks to keep around + private static final int UNDOABLE_BLOCKS_STORED = 10; private NetworkParameters unitTestParams; - private Wallet wallet; - private Address walletAddress; private FullPrunedBlockChain chain; private FullPrunedBlockStore store; - private ECKey someOtherGuyKey; - private Block testBase; - private TransactionOutPoint[] spendableOutputs; @Before public void setUp() throws Exception { @@ -49,100 +54,42 @@ public class FullPrunedBlockChainTest { unitTestParams = NetworkParameters.unitTests(); unitTestParams.interval = 10000; - wallet = new Wallet(unitTestParams); - wallet.addKey(new ECKey()); - walletAddress = wallet.keychain.get(0).toAddress(unitTestParams); - - store = new MemoryFullPrunedBlockStore(unitTestParams, MAX_BLOCK_HEIGHT); - chain = new FullPrunedBlockChain(unitTestParams, wallet, store); - - someOtherGuyKey = new ECKey(); - byte[] someOtherGuyPubKey = someOtherGuyKey.getPubKey(); - - spendableOutputs = new TransactionOutPoint[unitTestParams.getSpendableCoinbaseDepth() + MAX_BLOCK_HEIGHT]; - // Build some blocks on genesis block for later spending - // Be lazy to give a simple list of inputs for use, though we could use inputs generated during tests - testBase = unitTestParams.genesisBlock.createNextBlockWithCoinbase(someOtherGuyPubKey); - chain.add(testBase); - spendableOutputs[0] = new TransactionOutPoint(unitTestParams, 0, testBase.getTransactions().get(0).getHash()); - for (int i = 1; i < unitTestParams.getSpendableCoinbaseDepth() + MAX_BLOCK_HEIGHT; i++) { - testBase = testBase.createNextBlockWithCoinbase(someOtherGuyPubKey); - chain.add(testBase); - spendableOutputs[i] = new TransactionOutPoint(unitTestParams, 0, testBase.getTransactions().get(0).getHash().duplicate()); - } + store = new MemoryFullPrunedBlockStore(unitTestParams, UNDOABLE_BLOCKS_STORED); + chain = new FullPrunedBlockChain(unitTestParams, store); } @Test - public void testForkSpends() throws Exception { - // Check that if the block chain forks, we end up using the right chain. - // And check that transactions that get spent on one fork or another - - // In order for this to be triggered, the reorg has to effect us, - // so use walletAddress when creating new blocks as much as possible - final boolean[] reorgHappened = new boolean[1]; - reorgHappened[0] = false; - wallet.addEventListener(new AbstractWalletEventListener() { - @Override - public void onReorganize(Wallet wallet) { - reorgHappened[0] = true; + public void testGeneratedChain() throws Exception { + // Tests various test cases from FullBlockTestGenerator + FullBlockTestGenerator generator = new FullBlockTestGenerator(unitTestParams); + List blockList = generator.getBlocksToTest(false); + for (BlockAndValidity block : blockList) { + boolean threw = false; + try { + if (chain.add(block.block) != block.connects) { + log.error("Block didn't match connects flag on block " + block.blockName); + fail(); + } + } catch (VerificationException e) { + threw = true; + if (!block.throwsException) { + log.error("Block didn't match throws flag on block " + block.blockName); + fail(); + } + if (block.connects) { + log.error("Block didn't match connects flag on block " + block.blockName); + fail(); + } + } + if (!threw && block.throwsException) { + log.error("Block didn't match throws flag on block " + block.blockName); + fail(); + } + if (!chain.getChainHead().getHeader().getHash().equals(block.hashChainTipAfterBlock)) { + log.error("New block head didn't match the correct value after block " + block.blockName); + fail(); } - }); - - // Start by building a couple of blocks on top of the testBase block. - Block b1 = testBase.createNextBlock(walletAddress, spendableOutputs[0]); - Block b2 = b1.createNextBlock(walletAddress, spendableOutputs[1]); - assertTrue(chain.add(b1)); - assertTrue(chain.add(b2)); - assertFalse(reorgHappened[0]); - // We now have the following chain (which output is spent is in parentheses): - // testBase -> b1 (0) -> b2 (1) - // - // so fork like this: - // - // testBase -> b1 (0) -> b2 (1) - // \-> b3 (1) - // - // Nothing should happen at this point. We saw b2 first so it takes priority. - Block b3 = b1.createNextBlock(walletAddress, spendableOutputs[1]); - assertTrue(chain.add(b3)); - assertFalse(reorgHappened[0]); // No re-org took place. - // Now we add another block to make the alternative chain longer. - Block b4 = b3.createNextBlock(walletAddress, spendableOutputs[2]); - assertTrue(chain.add(b4)); - assertTrue(reorgHappened[0]); // Re-org took place. - reorgHappened[0] = false; - // - // testBase -> b1 (0) -> b2 (1) - // \-> b3 (1) -> b4 (2) - // - // ... and back to the first chain. - Block b5 = b2.createNextBlock(walletAddress, spendableOutputs[2]); - Block b6 = b5.createNextBlock(walletAddress, spendableOutputs[3]); - assertTrue(chain.add(b5)); - assertTrue(chain.add(b6)); - // - // testBase -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3) - // \-> b3 (1) -> b4 (2) - // - assertTrue(reorgHappened[0]); - reorgHappened[0] = false; - // Try to create a fork that double-spends - // testBase -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3) - // \-> b3 (1) -> b4 (2) - // \-> b7 (2) -> b8 (4) - // - Block b7 = b4.createNextBlock(new ECKey().toAddress(unitTestParams), spendableOutputs[2]); - Block b8 = b7.createNextBlock(walletAddress, spendableOutputs[4]); - try{ - chain.add(b7); // This is allowed to fail as there is no guarantee that a fork's inputs will be verified - chain.add(b8); - fail(); - } catch(VerificationException e) { - // b7 should fail verification because it double-spends output 2. - } catch (Exception e) { - throw new RuntimeException(e); // Should not happen. } - assertFalse(reorgHappened[0]); } @Test @@ -150,18 +97,38 @@ public class FullPrunedBlockChainTest { // Check that we aren't accidentally leaving any references // to the full StoredUndoableBlock's lying around (ie memory leaks) - WeakReference out = - new WeakReference(store.getTransactionOutput(spendableOutputs[0].getHash(), spendableOutputs[1].getIndex())); - // Create a chain longer than MAX_BLOCK_HEIGHT - Block block1 = testBase.createNextBlock(walletAddress, spendableOutputs[0]); - chain.add(block1); - WeakReference undoBlock = new WeakReference(store.getUndoBlock(block1.getHash())); + ECKey outKey = new ECKey(); + + // Build some blocks on genesis block to create a spendable output + Block rollingBlock = unitTestParams.genesisBlock.createNextBlockWithCoinbase(outKey.getPubKey()); + chain.add(rollingBlock); + TransactionOutPoint spendableOutput = new TransactionOutPoint(unitTestParams, 0, rollingBlock.getTransactions().get(0).getHash()); + byte[] spendableOutputScriptPubKey = rollingBlock.getTransactions().get(0).getOutputs().get(0).getScriptBytes(); + for (int i = 1; i < unitTestParams.getSpendableCoinbaseDepth(); i++) { + rollingBlock = rollingBlock.createNextBlockWithCoinbase(outKey.getPubKey()); + chain.add(rollingBlock); + } + + WeakReference out = new WeakReference + (store.getTransactionOutput(spendableOutput.getHash(), spendableOutput.getIndex())); + rollingBlock = rollingBlock.createNextBlock(null); + + Transaction t = new Transaction(unitTestParams); + // Entirely invalid scriptPubKey + t.addOutput(new TransactionOutput(unitTestParams, t, Utils.toNanoCoins(50, 0), new byte[] {})); + addInputToTransaction(t, spendableOutput, spendableOutputScriptPubKey, outKey); + rollingBlock.addTransaction(t); + rollingBlock.solve(); + + chain.add(rollingBlock); + WeakReference undoBlock = new WeakReference(store.getUndoBlock(rollingBlock.getHash())); assertTrue(undoBlock.get() != null); assertTrue(undoBlock.get().getTransactions() == null); WeakReference changes = new WeakReference(undoBlock.get().getTxOutChanges()); assertTrue(changes.get() != null); - Block rollingBlock = block1; - for (int i = 0; i < MAX_BLOCK_HEIGHT; i++) { + + // Create a chain longer than UNDOABLE_BLOCKS_STORED + for (int i = 0; i < UNDOABLE_BLOCKS_STORED; i++) { rollingBlock = rollingBlock.createNextBlock(null); chain.add(rollingBlock); } @@ -171,4 +138,23 @@ public class FullPrunedBlockChainTest { assertTrue(changes.get() == null); assertTrue(out.get() == null); } -} + + private void addInputToTransaction(Transaction t, TransactionOutPoint prevOut, byte[] prevOutScriptPubKey, ECKey sigKey) { + TransactionInput input = new TransactionInput(unitTestParams, t, new byte[]{}, prevOut); + t.addInput(input); + + Sha256Hash hash = t.hashTransactionForSignature(0, prevOutScriptPubKey, SigHash.ALL, false); + + // Sign input + try { + ByteArrayOutputStream bos = new UnsafeByteArrayOutputStream(73); + bos.write(sigKey.sign(hash.getBytes())); + bos.write(SigHash.ALL.ordinal() + 1); + byte[] signature = bos.toByteArray(); + + input.setScriptBytes(Script.createInputScript(signature)); + } catch (IOException e) { + throw new RuntimeException(e); // Cannot happen. + } + } +} \ No newline at end of file