diff --git a/core/src/test/java/org/bitcoinj/core/AbstractFullPrunedBlockChainTest.java b/core/src/test/java/org/bitcoinj/core/AbstractFullPrunedBlockChainTest.java index 0093a91c..7ff39c6f 100644 --- a/core/src/test/java/org/bitcoinj/core/AbstractFullPrunedBlockChainTest.java +++ b/core/src/test/java/org/bitcoinj/core/AbstractFullPrunedBlockChainTest.java @@ -74,9 +74,9 @@ public abstract class AbstractFullPrunedBlockChainTest chain = new FullPrunedBlockChain(params, store); for (Rule rule : blockList.list) { - if (!(rule instanceof BlockAndValidity)) + if (!(rule instanceof FullBlockTestGenerator.BlockAndValidity)) continue; - BlockAndValidity block = (BlockAndValidity) rule; + FullBlockTestGenerator.BlockAndValidity block = (FullBlockTestGenerator.BlockAndValidity) rule; log.info("Testing rule " + block.ruleName + " with block hash " + block.block.getHash()); boolean threw = false; try { diff --git a/core/src/test/java/org/bitcoinj/core/BitcoindComparisonTool.java b/core/src/test/java/org/bitcoinj/core/BitcoindComparisonTool.java index 7b1d1eac..aac6d586 100644 --- a/core/src/test/java/org/bitcoinj/core/BitcoindComparisonTool.java +++ b/core/src/test/java/org/bitcoinj/core/BitcoindComparisonTool.java @@ -60,8 +60,8 @@ public class BitcoindComparisonTool { public static void main(String[] args) throws Exception { BriefLogFormatter.init(); - System.out.println("USAGE: bitcoinjBlockStoreLocation runLargeReorgs(1/0) [port=18444]"); - boolean runLargeReorgs = args.length > 1 && Integer.parseInt(args[1]) == 1; + System.out.println("USAGE: bitcoinjBlockStoreLocation runExpensiveTests(1/0) [port=18444]"); + boolean runExpensiveTests = args.length > 1 && Integer.parseInt(args[1]) == 1; params = RegTestParams.get(); @@ -69,7 +69,7 @@ public class BitcoindComparisonTool { blockFile.deleteOnExit(); FullBlockTestGenerator generator = new FullBlockTestGenerator(params); - final RuleList blockList = generator.getBlocksToTest(false, runLargeReorgs, blockFile); + final RuleList blockList = generator.getBlocksToTest(true, runExpensiveTests, blockFile); final Map preloadedBlocks = new HashMap(); final Iterator blocks = new BlockFileLoader(params, Arrays.asList(blockFile)); @@ -220,10 +220,10 @@ public class BitcoindComparisonTool { int rulesSinceFirstFail = 0; for (Rule rule : blockList.list) { - if (rule instanceof BlockAndValidity) { - BlockAndValidity block = (BlockAndValidity) rule; + if (rule instanceof FullBlockTestGenerator.BlockAndValidity) { + FullBlockTestGenerator.BlockAndValidity block = (FullBlockTestGenerator.BlockAndValidity) rule; boolean threw = false; - Block nextBlock = preloadedBlocks.get(((BlockAndValidity) rule).blockHash); + Block nextBlock = preloadedBlocks.get(((FullBlockTestGenerator.BlockAndValidity) rule).blockHash); // Often load at least one block because sometimes we have duplicates with the same hash (b56/57) for (int i = 0; i < 1 || nextBlock == null || !nextBlock.getHash().equals(block.blockHash); @@ -283,7 +283,7 @@ public class BitcoindComparisonTool { } // bitcoind doesn't request blocks inline so we can't rely on a ping for synchronization for (int i = 0; !shouldntRequest && !blocksRequested.contains(nextBlock.getHash()); i++) { - int SLEEP_TIME = 10; + int SLEEP_TIME = 1; if (i % 1000/SLEEP_TIME == 1000/SLEEP_TIME - 1) log.error("bitcoind still hasn't requested block " + block.ruleName + " with hash " + nextBlock.getHash()); Thread.sleep(SLEEP_TIME); diff --git a/core/src/test/java/org/bitcoinj/core/FullBlockTestGenerator.java b/core/src/test/java/org/bitcoinj/core/FullBlockTestGenerator.java index 5023edc2..16cfc846 100644 --- a/core/src/test/java/org/bitcoinj/core/FullBlockTestGenerator.java +++ b/core/src/test/java/org/bitcoinj/core/FullBlockTestGenerator.java @@ -5,8 +5,6 @@ import org.bitcoinj.crypto.TransactionSignature; import org.bitcoinj.script.Script; import org.bitcoinj.script.ScriptBuilder; import com.google.common.base.Preconditions; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.Lists; import javax.annotation.Nullable; import java.io.ByteArrayOutputStream; @@ -20,7 +18,6 @@ import static org.bitcoinj.core.Coin.*; import static org.bitcoinj.script.ScriptOpCodes.*; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; -import static java.util.Collections.singletonList; /** * YOU ARE READING THIS CODE BECAUSE EITHER... @@ -41,6 +38,26 @@ import static java.util.Collections.singletonList; * IN ANY CASE, STOP READING NOW. IT WILL SAVE YOU MUCH PAIN AND MISERY LATER */ +class NewBlock { + public Block block; + private TransactionOutPointWithValue spendableOutput; + public NewBlock(Block block, TransactionOutPointWithValue spendableOutput) { + this.block = block; this.spendableOutput = spendableOutput; + } + // Wrappers to make it more block-like + public Sha256Hash getHash() { return block.getHash(); } + public void solve() { block.solve(); } + public void addTransaction(Transaction tx) { block.addTransaction(tx); } + + public TransactionOutPointWithValue getCoinbaseOutput() { + return new TransactionOutPointWithValue(block.getTransactions().get(0), 0); + } + + public TransactionOutPointWithValue getSpendableOutput() { + return spendableOutput; + } +} + class TransactionOutPointWithValue { public TransactionOutPoint outpoint; public Coin value; @@ -66,48 +83,6 @@ class Rule { } } -/** - * Represents a block which is sent to the tested application and which the application must either reject or accept, - * depending on the flags in the rule - */ -class BlockAndValidity extends Rule { - Block block; - Sha256Hash blockHash; - boolean connects; - boolean throwsException; - boolean sendOnce; // We can throw away the memory for this block once we send it the first time (if bitcoind asks again, its broken) - Sha256Hash hashChainTipAfterBlock; - int heightAfterBlock; - - public BlockAndValidity(Map blockToHeightMap, Map hashHeaderMap, Block block, - boolean connects, boolean throwsException, Sha256Hash hashChainTipAfterBlock, int heightAfterBlock, String blockName) { - super(blockName); - if (connects && throwsException) - throw new RuntimeException("A block cannot connect if an exception was thrown while adding it."); - this.block = block; - this.blockHash = block.getHash(); - this.connects = connects; - this.throwsException = throwsException; - this.hashChainTipAfterBlock = hashChainTipAfterBlock; - this.heightAfterBlock = heightAfterBlock; - - // Keep track of the set of blocks indexed by hash - hashHeaderMap.put(block.getHash(), block.cloneAsHeader()); - - // Double-check that we are always marking any given block at the same height - Integer height = blockToHeightMap.get(hashChainTipAfterBlock); - if (height != null) - checkState(height == heightAfterBlock); - else - blockToHeightMap.put(hashChainTipAfterBlock, heightAfterBlock); - } - - public BlockAndValidity setSendOnce(boolean sendOnce) { - this.sendOnce = sendOnce; - return this; - } -} - /** * A test which checks the mempool state (ie defined which transactions should be in memory pool */ @@ -135,12 +110,13 @@ public class FullBlockTestGenerator { private NetworkParameters params; private ECKey coinbaseOutKey; private byte[] coinbaseOutKeyPubKey; - + // Used to double-check that we are always using the right next-height private Map blockToHeightMap = new HashMap(); private Map hashHeaderMap = new HashMap(); - + private Map coinbaseBlockMap = new HashMap(); + public FullBlockTestGenerator(NetworkParameters params) { this.params = params; coinbaseOutKey = new ECKey(); @@ -148,7 +124,7 @@ public class FullBlockTestGenerator { Utils.setMockClock(); } - public RuleList getBlocksToTest(boolean addSigExpensiveBlocks, boolean runLargeReorgs, File blockStorageFile) throws ScriptException, ProtocolException, IOException { + public RuleList getBlocksToTest(boolean runBarelyExpensiveTests, boolean runExpensiveTests, File blockStorageFile) throws ScriptException, ProtocolException, IOException { final FileOutputStream outStream = blockStorageFile != null ? new FileOutputStream(blockStorageFile) : null; final Script OP_TRUE_SCRIPT = new ScriptBuilder().op(OP_TRUE).build(); @@ -178,41 +154,35 @@ public class FullBlockTestGenerator { } }; RuleList ret = new RuleList(blocks, hashHeaderMap, 10); - + Queue spendableOutputs = new LinkedList(); - + int chainHeadHeight = 1; Block chainHead = params.getGenesisBlock().createNextBlockWithCoinbase(coinbaseOutKeyPubKey); - blocks.add(new BlockAndValidity(blockToHeightMap, hashHeaderMap, chainHead, true, false, chainHead.getHash(), 1, "Initial Block")); + blocks.add(new BlockAndValidity(chainHead, true, false, chainHead.getHash(), 1, "Initial Block")); spendableOutputs.offer(new TransactionOutPointWithValue( new TransactionOutPoint(params, 0, chainHead.getTransactions().get(0).getHash()), FIFTY_COINS, 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(blockToHeightMap, hashHeaderMap, chainHead, true, false, chainHead.getHash(), i+1, "Initial Block chain output generation")); + blocks.add(new BlockAndValidity(chainHead, true, false, chainHead.getHash(), i+1, "Initial Block chain output generation")); spendableOutputs.offer(new TransactionOutPointWithValue( new TransactionOutPoint(params, 0, chainHead.getTransactions().get(0).getHash()), FIFTY_COINS, 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(blockToHeightMap, hashHeaderMap, b1, true, false, b1.getHash(), chainHeadHeight + 1, "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())); - + NewBlock b1 = createNextBlock(chainHead, chainHeadHeight + 1, spendableOutputs.poll(), null); + blocks.add(new BlockAndValidity(b1, true, false, b1.getHash(), chainHeadHeight + 1, "b1")); + spendableOutputs.offer(b1.getCoinbaseOutput()); + TransactionOutPointWithValue out1 = spendableOutputs.poll(); checkState(out1 != null); - Block b2 = createNextBlock(b1, chainHeadHeight + 2, out1, null); - blocks.add(new BlockAndValidity(blockToHeightMap, hashHeaderMap, b2, true, false, b2.getHash(), chainHeadHeight + 2, "b2")); + NewBlock b2 = createNextBlock(b1, chainHeadHeight + 2, out1, null); + blocks.add(new BlockAndValidity(b2, true, false, b2.getHash(), chainHeadHeight + 2, "b2")); // Make sure nothing funky happens if we try to re-add b2 - blocks.add(new BlockAndValidity(blockToHeightMap, hashHeaderMap, b2, true, false, b2.getHash(), chainHeadHeight + 2, "b2")); - spendableOutputs.offer(new TransactionOutPointWithValue( - new TransactionOutPoint(params, 0, b2.getTransactions().get(0).getHash()), - b2.getTransactions().get(0).getOutput(0).getValue(), - b2.getTransactions().get(0).getOutput(0).getScriptPubKey())); + blocks.add(new BlockAndValidity(b2, true, false, b2.getHash(), chainHeadHeight + 2, "b2")); + spendableOutputs.offer(b2.getCoinbaseOutput()); // We now have the following chain (which output is spent is in parentheses): // genesis -> b1 (0) -> b2 (1) // @@ -222,10 +192,10 @@ public class FullBlockTestGenerator { // \-> 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(blockToHeightMap, hashHeaderMap, b3, true, false, b2.getHash(), chainHeadHeight + 2, "b3")); + NewBlock b3 = createNextBlock(b1, chainHeadHeight + 2, out1, null); + blocks.add(new BlockAndValidity(b3, true, false, b2.getHash(), chainHeadHeight + 2, "b3")); // Make sure nothing breaks if we add b3 twice - blocks.add(new BlockAndValidity(blockToHeightMap, hashHeaderMap, b3, true, false, b2.getHash(), chainHeadHeight + 2, "b3")); + blocks.add(new BlockAndValidity(b3, true, false, b2.getHash(), chainHeadHeight + 2, "b3")); // Now we add another block to make the alternative chain longer. // @@ -233,21 +203,18 @@ public class FullBlockTestGenerator { // \-> b3 (1) -> b4 (2) // TransactionOutPointWithValue out2 = checkNotNull(spendableOutputs.poll()); - Block b4 = createNextBlock(b3, chainHeadHeight + 3, out2, null); - blocks.add(new BlockAndValidity(blockToHeightMap, hashHeaderMap, b4, true, false, b4.getHash(), chainHeadHeight + 3, "b4")); + NewBlock b4 = createNextBlock(b3, chainHeadHeight + 3, out2, null); + blocks.add(new BlockAndValidity(b4, true, false, b4.getHash(), chainHeadHeight + 3, "b4")); // ... and back to the first chain. - Block b5 = createNextBlock(b2, chainHeadHeight + 3, out2, null); - blocks.add(new BlockAndValidity(blockToHeightMap, hashHeaderMap, b5, true, false, b4.getHash(), chainHeadHeight + 3, "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())); - + NewBlock b5 = createNextBlock(b2, chainHeadHeight + 3, out2, null); + blocks.add(new BlockAndValidity(b5, true, false, b4.getHash(), chainHeadHeight + 3, "b5")); + spendableOutputs.offer(b5.getCoinbaseOutput()); + TransactionOutPointWithValue out3 = spendableOutputs.poll(); - - Block b6 = createNextBlock(b5, chainHeadHeight + 4, out3, null); - blocks.add(new BlockAndValidity(blockToHeightMap, hashHeaderMap, b6, true, false, b6.getHash(), chainHeadHeight + 4, "b6")); + + NewBlock b6 = createNextBlock(b5, chainHeadHeight + 4, out3, null); + blocks.add(new BlockAndValidity(b6, true, false, b6.getHash(), chainHeadHeight + 4, "b6")); // // genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3) // \-> b3 (1) -> b4 (2) @@ -258,32 +225,32 @@ public class FullBlockTestGenerator { // \-> b7 (2) -> b8 (4) // \-> b3 (1) -> b4 (2) // - Block b7 = createNextBlock(b5, chainHeadHeight + 5, out2, null); - blocks.add(new BlockAndValidity(blockToHeightMap, hashHeaderMap, b7, true, false, b6.getHash(), chainHeadHeight + 4, "b7")); - + NewBlock b7 = createNextBlock(b5, chainHeadHeight + 5, out2, null); + blocks.add(new BlockAndValidity(b7, true, false, b6.getHash(), chainHeadHeight + 4, "b7")); + TransactionOutPointWithValue out4 = spendableOutputs.poll(); - Block b8 = createNextBlock(b7, chainHeadHeight + 6, out4, null); - blocks.add(new BlockAndValidity(blockToHeightMap, hashHeaderMap, b8, false, true, b6.getHash(), chainHeadHeight + 4, "b8")); - + NewBlock b8 = createNextBlock(b7, chainHeadHeight + 6, out4, null); + blocks.add(new BlockAndValidity(b8, false, true, b6.getHash(), chainHeadHeight + 4, "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, SATOSHI); - blocks.add(new BlockAndValidity(blockToHeightMap, hashHeaderMap, b9, false, true, b6.getHash(), chainHeadHeight + 4, "b9")); + NewBlock b9 = createNextBlock(b6, chainHeadHeight + 5, out4, SATOSHI); + blocks.add(new BlockAndValidity(b9, false, true, b6.getHash(), chainHeadHeight + 4, "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(blockToHeightMap, hashHeaderMap, b10, true, false, b6.getHash(), chainHeadHeight + 4, "b10")); - - Block b11 = createNextBlock(b10, chainHeadHeight + 5, out4, SATOSHI); - blocks.add(new BlockAndValidity(blockToHeightMap, hashHeaderMap, b11, false, true, b6.getHash(), chainHeadHeight + 4, "b11")); + NewBlock b10 = createNextBlock(b5, chainHeadHeight + 4, out3, null); + blocks.add(new BlockAndValidity(b10, true, false, b6.getHash(), chainHeadHeight + 4, "b10")); + + NewBlock b11 = createNextBlock(b10, chainHeadHeight + 5, out4, SATOSHI); + blocks.add(new BlockAndValidity(b11, false, true, b6.getHash(), chainHeadHeight + 4, "b11")); // Try again, but with a valid fork first // genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3) @@ -291,120 +258,112 @@ public class FullBlockTestGenerator { // (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(blockToHeightMap, hashHeaderMap, b13, false, false, b6.getHash(), chainHeadHeight + 4, "b13")); + NewBlock b12 = createNextBlock(b5, chainHeadHeight + 4, out3, null); + spendableOutputs.offer(b12.getCoinbaseOutput()); + + NewBlock b13 = createNextBlock(b12, chainHeadHeight + 5, out4, null); + blocks.add(new BlockAndValidity(b13, false, false, b6.getHash(), chainHeadHeight + 4, "b13")); // Make sure we dont die if an orphan gets added twice - blocks.add(new BlockAndValidity(blockToHeightMap, hashHeaderMap, b13, false, false, b6.getHash(), chainHeadHeight + 4, "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())); + blocks.add(new BlockAndValidity(b13, false, false, b6.getHash(), chainHeadHeight + 4, "b13")); + spendableOutputs.offer(b13.getCoinbaseOutput()); TransactionOutPointWithValue out5 = spendableOutputs.poll(); - Block b14 = createNextBlock(b13, chainHeadHeight + 6, out5, SATOSHI); + NewBlock b14 = createNextBlock(b13, chainHeadHeight + 6, out5, SATOSHI); // 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(blockToHeightMap, hashHeaderMap, b14, false, false, b6.getHash(), chainHeadHeight + 4, "b14")); + blocks.add(new BlockAndValidity(b14, false, false, b6.getHash(), chainHeadHeight + 4, "b14")); // Make sure we dont die if an orphan gets added twice - blocks.add(new BlockAndValidity(blockToHeightMap, hashHeaderMap, b14, false, false, b6.getHash(), chainHeadHeight + 4, "b14")); - - blocks.add(new BlockAndValidity(blockToHeightMap, hashHeaderMap, b12, false, true, b13.getHash(), chainHeadHeight + 5, "b12")); - + blocks.add(new BlockAndValidity(b14, false, false, b6.getHash(), chainHeadHeight + 4, "b14")); + + blocks.add(new BlockAndValidity(b12, false, true, b13.getHash(), chainHeadHeight + 5, "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); + NewBlock b15 = createNextBlock(b13, chainHeadHeight + 6, out5, null); { int sigOps = 0; - for (Transaction tx : b15.transactions) { + for (Transaction tx : b15.block.getTransactions()) sigOps += tx.getSigOpCount(); - } Transaction tx = new Transaction(params); byte[] outputScript = new byte[Block.MAX_BLOCK_SIGOPS - sigOps]; Arrays.fill(outputScript, (byte) OP_CHECKSIG); tx.addOutput(new TransactionOutput(params, tx, SATOSHI, outputScript)); - addOnlyInputToTransaction(tx, new TransactionOutPointWithValue( - new TransactionOutPoint(params, 1, b15.getTransactions().get(1).getHash()), - SATOSHI, b15.getTransactions().get(1).getOutputs().get(1).getScriptPubKey())); + addOnlyInputToTransaction(tx, b15); b15.addTransaction(tx); + + sigOps = 0; + for (Transaction tx2 : b15.block.getTransactions()) + sigOps += tx2.getSigOpCount(); + checkState(sigOps == Block.MAX_BLOCK_SIGOPS); } b15.solve(); - - blocks.add(new BlockAndValidity(blockToHeightMap, hashHeaderMap, b15, true, false, b15.getHash(), chainHeadHeight + 6, "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())); - + + blocks.add(new BlockAndValidity(b15, true, false, b15.getHash(), chainHeadHeight + 6, "b15")); + spendableOutputs.offer(b15.getCoinbaseOutput()); + TransactionOutPointWithValue out6 = spendableOutputs.poll(); - - Block b16 = createNextBlock(b15, chainHeadHeight + 7, out6, null); + + NewBlock b16 = createNextBlock(b15, chainHeadHeight + 7, out6, null); { int sigOps = 0; - for (Transaction tx : b16.transactions) { + for (Transaction tx : b16.block.getTransactions()) { sigOps += tx.getSigOpCount(); } Transaction tx = new Transaction(params); byte[] outputScript = new byte[Block.MAX_BLOCK_SIGOPS - sigOps + 1]; Arrays.fill(outputScript, (byte) OP_CHECKSIG); tx.addOutput(new TransactionOutput(params, tx, SATOSHI, outputScript)); - addOnlyInputToTransaction(tx, new TransactionOutPointWithValue( - new TransactionOutPoint(params, 1, b16.getTransactions().get(1).getHash()), - SATOSHI, b16.getTransactions().get(1).getOutputs().get(1).getScriptPubKey())); + addOnlyInputToTransaction(tx, b16); b16.addTransaction(tx); + + sigOps = 0; + for (Transaction tx2 : b16.block.getTransactions()) + sigOps += tx2.getSigOpCount(); + checkState(sigOps == Block.MAX_BLOCK_SIGOPS + 1); } b16.solve(); - - blocks.add(new BlockAndValidity(blockToHeightMap, hashHeaderMap, b16, false, true, b15.getHash(), chainHeadHeight + 6, "b16")); - + + blocks.add(new BlockAndValidity(b16, false, true, b15.getHash(), chainHeadHeight + 6, "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); + NewBlock b17 = createNextBlock(b15, chainHeadHeight + 7, out6, null); { Transaction tx = new Transaction(params); tx.addOutput(new TransactionOutput(params, tx, SATOSHI, new byte[] {})); - addOnlyInputToTransaction(tx, new TransactionOutPointWithValue( - new TransactionOutPoint(params, 1, b3.getTransactions().get(1).getHash()), - SATOSHI, b3.getTransactions().get(1).getOutputs().get(1).getScriptPubKey())); + addOnlyInputToTransaction(tx, b3); b17.addTransaction(tx); } b17.solve(); - blocks.add(new BlockAndValidity(blockToHeightMap, hashHeaderMap, b17, false, true, b15.getHash(), chainHeadHeight + 6, "b17")); - + blocks.add(new BlockAndValidity(b17, false, true, b15.getHash(), chainHeadHeight + 6, "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); + NewBlock b18 = createNextBlock(b13, chainHeadHeight + 6, out5, null); { Transaction tx = new Transaction(params); tx.addOutput(new TransactionOutput(params, tx, SATOSHI, new byte[] {})); - addOnlyInputToTransaction(tx, new TransactionOutPointWithValue( - new TransactionOutPoint(params, 1, b3.getTransactions().get(1).getHash()), - SATOSHI, b3.getTransactions().get(1).getOutputs().get(1).getScriptPubKey())); + addOnlyInputToTransaction(tx, b3); b18.addTransaction(tx); } b18.solve(); - blocks.add(new BlockAndValidity(blockToHeightMap, hashHeaderMap, b18, true, false, b15.getHash(), chainHeadHeight + 6, "b17")); - - Block b19 = createNextBlock(b18, chainHeadHeight + 7, out6, null); - blocks.add(new BlockAndValidity(blockToHeightMap, hashHeaderMap, b19, false, true, b15.getHash(), chainHeadHeight + 6, "b19")); - + blocks.add(new BlockAndValidity(b18, true, false, b15.getHash(), chainHeadHeight + 6, "b17")); + + NewBlock b19 = createNextBlock(b18, chainHeadHeight + 7, out6, null); + blocks.add(new BlockAndValidity(b19, false, true, b15.getHash(), chainHeadHeight + 6, "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) @@ -412,109 +371,99 @@ public class FullBlockTestGenerator { // TransactionOutPointWithValue out7 = spendableOutputs.poll(); - Block b20 = createNextBlock(b15, chainHeadHeight + 7, out7, null); - blocks.add(new BlockAndValidity(blockToHeightMap, hashHeaderMap, b20, false, true, b15.getHash(), chainHeadHeight + 6, "b20")); - + NewBlock b20 = createNextBlock(b15.block, chainHeadHeight + 7, out7, null); + blocks.add(new BlockAndValidity(b20, false, true, b15.getHash(), chainHeadHeight + 6, "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(blockToHeightMap, hashHeaderMap, b21, true, false, b15.getHash(), chainHeadHeight + 6, "b21")); - Block b22 = createNextBlock(b21, chainHeadHeight + 7, out5, null); - blocks.add(new BlockAndValidity(blockToHeightMap, hashHeaderMap, b22, false, true, b15.getHash(), chainHeadHeight + 6, "b22")); - + NewBlock b21 = createNextBlock(b13, chainHeadHeight + 6, out6, null); + blocks.add(new BlockAndValidity(b21.block, true, false, b15.getHash(), chainHeadHeight + 6, "b21")); + NewBlock b22 = createNextBlock(b21, chainHeadHeight + 7, out5, null); + blocks.add(new BlockAndValidity(b22.block, false, true, b15.getHash(), chainHeadHeight + 6, "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); + NewBlock b23 = createNextBlock(b15, chainHeadHeight + 7, out6, null); { Transaction tx = new Transaction(params); - byte[] outputScript = new byte[Block.MAX_BLOCK_SIZE - b23.getMessageSize() - 65]; + byte[] outputScript = new byte[Block.MAX_BLOCK_SIZE - b23.block.getMessageSize() - 65]; Arrays.fill(outputScript, (byte) OP_FALSE); tx.addOutput(new TransactionOutput(params, tx, ZERO, outputScript)); - addOnlyInputToTransaction(tx, new TransactionOutPointWithValue( - new TransactionOutPoint(params, 2, b23.getTransactions().get(1).getHash()), - ZERO, b23.getTransactions().get(1).getOutputs().get(2).getScriptPubKey())); + addOnlyInputToTransaction(tx, b23); b23.addTransaction(tx); } b23.solve(); - checkState(b23.getMessageSize() == Block.MAX_BLOCK_SIZE); - blocks.add(new BlockAndValidity(blockToHeightMap, hashHeaderMap, b23, true, false, b23.getHash(), chainHeadHeight + 7, "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); + checkState(b23.block.getMessageSize() == Block.MAX_BLOCK_SIZE); + blocks.add(new BlockAndValidity(b23, true, false, b23.getHash(), chainHeadHeight + 7, "b23")); + spendableOutputs.offer(b23.getCoinbaseOutput()); + + NewBlock b24 = createNextBlock(b15, chainHeadHeight + 7, out6, null); { Transaction tx = new Transaction(params); - byte[] outputScript = new byte[Block.MAX_BLOCK_SIZE - b24.getMessageSize() - 64]; + byte[] outputScript = new byte[Block.MAX_BLOCK_SIZE - b24.block.getMessageSize() - 64]; Arrays.fill(outputScript, (byte) OP_FALSE); tx.addOutput(new TransactionOutput(params, tx, ZERO, outputScript)); - addOnlyInputToTransaction(tx, new TransactionOutPointWithValue( - new TransactionOutPoint(params, 2, b24.getTransactions().get(1).getHash()), - ZERO, b24.getTransactions().get(1).getOutputs().get(2).getScriptPubKey())); + addOnlyInputToTransaction(tx, b24); b24.addTransaction(tx); } b24.solve(); - checkState(b24.getMessageSize() == Block.MAX_BLOCK_SIZE + 1); - blocks.add(new BlockAndValidity(blockToHeightMap, hashHeaderMap, b24, false, true, b23.getHash(), chainHeadHeight + 7, "b24")); - + checkState(b24.block.getMessageSize() == Block.MAX_BLOCK_SIZE + 1); + blocks.add(new BlockAndValidity(b24, false, true, b23.getHash(), chainHeadHeight + 7, "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(blockToHeightMap, hashHeaderMap, b25, false, false, b23.getHash(), chainHeadHeight + 7, "b25")); - + NewBlock b25 = createNextBlock(b24, chainHeadHeight + 8, out7, null); + blocks.add(new BlockAndValidity(b25, false, false, b23.getHash(), chainHeadHeight + 7, "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); + NewBlock 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.block.getTransactions().get(0).getInputs().get(0).setScriptBytes(new byte[] {0}); + b26.block.setMerkleRoot(null); b26.solve(); - blocks.add(new BlockAndValidity(blockToHeightMap, hashHeaderMap, b26, false, true, b23.getHash(), chainHeadHeight + 7, "b26")); - + blocks.add(new BlockAndValidity(b26, false, true, b23.getHash(), chainHeadHeight + 7, "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(blockToHeightMap, hashHeaderMap, b27, false, false, b23.getHash(), chainHeadHeight + 7, "b27")); - - Block b28 = createNextBlock(b15, chainHeadHeight + 7, out6, null); + NewBlock b27 = createNextBlock(b26, chainHeadHeight + 8, out7, null); + blocks.add(new BlockAndValidity(b27, false, false, b23.getHash(), chainHeadHeight + 7, "b27")); + + NewBlock 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.block.getTransactions().get(0).getInputs().get(0).setScriptBytes(coinbase); } - b28.setMerkleRoot(null); + b28.block.setMerkleRoot(null); b28.solve(); - blocks.add(new BlockAndValidity(blockToHeightMap, hashHeaderMap, b28, false, true, b23.getHash(), chainHeadHeight + 7, "b28")); - + blocks.add(new BlockAndValidity(b28, false, true, b23.getHash(), chainHeadHeight + 7, "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(blockToHeightMap, hashHeaderMap, b29, false, false, b23.getHash(), chainHeadHeight + 7, "b29")); - - Block b30 = createNextBlock(b23, chainHeadHeight + 8, out7, null); + NewBlock b29 = createNextBlock(b28, chainHeadHeight + 8, out7, null); + blocks.add(new BlockAndValidity(b29, false, false, b23.getHash(), chainHeadHeight + 7, "b29")); + + NewBlock 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.block.getTransactions().get(0).getInputs().get(0).setScriptBytes(coinbase); } - b30.setMerkleRoot(null); + b30.block.setMerkleRoot(null); b30.solve(); - blocks.add(new BlockAndValidity(blockToHeightMap, hashHeaderMap, b30, true, false, b30.getHash(), chainHeadHeight + 8, "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())); - + blocks.add(new BlockAndValidity(b30, true, false, b30.getHash(), chainHeadHeight + 8, "b30")); + spendableOutputs.offer(b30.getCoinbaseOutput()); + // 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) @@ -524,35 +473,30 @@ public class FullBlockTestGenerator { // TransactionOutPointWithValue out8 = spendableOutputs.poll(); - Block b31 = createNextBlock(b30, chainHeadHeight + 9, out8, null); + NewBlock b31 = createNextBlock(b30, chainHeadHeight + 9, out8, null); { int sigOps = 0; - for (Transaction tx : b31.transactions) { + for (Transaction tx : b31.block.transactions) { sigOps += tx.getSigOpCount(); } Transaction tx = new Transaction(params); byte[] outputScript = new byte[(Block.MAX_BLOCK_SIGOPS - sigOps)/20]; Arrays.fill(outputScript, (byte) OP_CHECKMULTISIG); tx.addOutput(new TransactionOutput(params, tx, SATOSHI, outputScript)); - addOnlyInputToTransaction(tx, new TransactionOutPointWithValue( - new TransactionOutPoint(params, 1, b31.getTransactions().get(1).getHash()), - SATOSHI, b31.getTransactions().get(1).getOutputs().get(1).getScriptPubKey())); + addOnlyInputToTransaction(tx, b31); b31.addTransaction(tx); } b31.solve(); - - blocks.add(new BlockAndValidity(blockToHeightMap, hashHeaderMap, b31, true, false, b31.getHash(), chainHeadHeight + 9, "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())); - + + blocks.add(new BlockAndValidity(b31, true, false, b31.getHash(), chainHeadHeight + 9, "b31")); + spendableOutputs.offer(b31.getCoinbaseOutput()); + TransactionOutPointWithValue out9 = spendableOutputs.poll(); - - Block b32 = createNextBlock(b31, chainHeadHeight + 10, out9, null); + + NewBlock b32 = createNextBlock(b31, chainHeadHeight + 10, out9, null); { int sigOps = 0; - for (Transaction tx : b32.transactions) { + for (Transaction tx : b32.block.transactions) { sigOps += tx.getSigOpCount(); } Transaction tx = new Transaction(params); @@ -561,45 +505,36 @@ public class FullBlockTestGenerator { for (int i = 0; i < (Block.MAX_BLOCK_SIGOPS - sigOps)%20; i++) outputScript[i] = (byte) OP_CHECKSIG; tx.addOutput(new TransactionOutput(params, tx, SATOSHI, outputScript)); - addOnlyInputToTransaction(tx, new TransactionOutPointWithValue( - new TransactionOutPoint(params, 1, b32.getTransactions().get(1).getHash()), - SATOSHI, b32.getTransactions().get(1).getOutputs().get(1).getScriptPubKey())); + addOnlyInputToTransaction(tx, b32); b32.addTransaction(tx); } b32.solve(); - - blocks.add(new BlockAndValidity(blockToHeightMap, hashHeaderMap, b32, false, true, b31.getHash(), chainHeadHeight + 9, "b32")); - - - Block b33 = createNextBlock(b31, chainHeadHeight + 10, out9, null); + blocks.add(new BlockAndValidity(b32, false, true, b31.getHash(), chainHeadHeight + 9, "b32")); + + NewBlock b33 = createNextBlock(b31, chainHeadHeight + 10, out9, null); { int sigOps = 0; - for (Transaction tx : b33.transactions) { + for (Transaction tx : b33.block.transactions) { sigOps += tx.getSigOpCount(); } Transaction tx = new Transaction(params); byte[] outputScript = new byte[(Block.MAX_BLOCK_SIGOPS - sigOps)/20]; Arrays.fill(outputScript, (byte) OP_CHECKMULTISIGVERIFY); tx.addOutput(new TransactionOutput(params, tx, SATOSHI, outputScript)); - addOnlyInputToTransaction(tx, new TransactionOutPointWithValue( - new TransactionOutPoint(params, 1, b33.getTransactions().get(1).getHash()), - SATOSHI, b33.getTransactions().get(1).getOutputs().get(1).getScriptPubKey())); + addOnlyInputToTransaction(tx, b33); b33.addTransaction(tx); } b33.solve(); - - blocks.add(new BlockAndValidity(blockToHeightMap, hashHeaderMap, b33, true, false, b33.getHash(), chainHeadHeight + 10, "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())); - + + blocks.add(new BlockAndValidity(b33, true, false, b33.getHash(), chainHeadHeight + 10, "b33")); + spendableOutputs.offer(b33.getCoinbaseOutput()); + TransactionOutPointWithValue out10 = spendableOutputs.poll(); - - Block b34 = createNextBlock(b33, chainHeadHeight + 11, out10, null); + + NewBlock b34 = createNextBlock(b33, chainHeadHeight + 11, out10, null); { int sigOps = 0; - for (Transaction tx : b34.transactions) { + for (Transaction tx : b34.block.getTransactions()) { sigOps += tx.getSigOpCount(); } Transaction tx = new Transaction(params); @@ -608,60 +543,49 @@ public class FullBlockTestGenerator { for (int i = 0; i < (Block.MAX_BLOCK_SIGOPS - sigOps)%20; i++) outputScript[i] = (byte) OP_CHECKSIG; tx.addOutput(new TransactionOutput(params, tx, SATOSHI, outputScript)); - addOnlyInputToTransaction(tx, new TransactionOutPointWithValue( - new TransactionOutPoint(params, 1, b34.getTransactions().get(1).getHash()), - SATOSHI, b34.getTransactions().get(1).getOutputs().get(1).getScriptPubKey())); + addOnlyInputToTransaction(tx, b34); b34.addTransaction(tx); } b34.solve(); - - blocks.add(new BlockAndValidity(blockToHeightMap, hashHeaderMap, b34, false, true, b33.getHash(), chainHeadHeight + 10, "b34")); - - - Block b35 = createNextBlock(b33, chainHeadHeight + 11, out10, null); + blocks.add(new BlockAndValidity(b34, false, true, b33.getHash(), chainHeadHeight + 10, "b34")); + + NewBlock b35 = createNextBlock(b33, chainHeadHeight + 11, out10, null); { int sigOps = 0; - for (Transaction tx : b35.transactions) { + for (Transaction tx : b35.block.getTransactions()) { sigOps += tx.getSigOpCount(); } Transaction tx = new Transaction(params); byte[] outputScript = new byte[Block.MAX_BLOCK_SIGOPS - sigOps]; Arrays.fill(outputScript, (byte) OP_CHECKSIGVERIFY); tx.addOutput(new TransactionOutput(params, tx, SATOSHI, outputScript)); - addOnlyInputToTransaction(tx, new TransactionOutPointWithValue( - new TransactionOutPoint(params, 1, b35.getTransactions().get(1).getHash()), - SATOSHI, b35.getTransactions().get(1).getOutputs().get(1).getScriptPubKey())); + addOnlyInputToTransaction(tx, b35); b35.addTransaction(tx); } b35.solve(); - - blocks.add(new BlockAndValidity(blockToHeightMap, hashHeaderMap, b35, true, false, b35.getHash(), chainHeadHeight + 11, "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())); - + + blocks.add(new BlockAndValidity(b35, true, false, b35.getHash(), chainHeadHeight + 11, "b35")); + spendableOutputs.offer(b35.getCoinbaseOutput()); + TransactionOutPointWithValue out11 = spendableOutputs.poll(); - - Block b36 = createNextBlock(b35, chainHeadHeight + 12, out11, null); + + NewBlock b36 = createNextBlock(b35, chainHeadHeight + 12, out11, null); { int sigOps = 0; - for (Transaction tx : b36.transactions) { + for (Transaction tx : b36.block.getTransactions()) { sigOps += tx.getSigOpCount(); } Transaction tx = new Transaction(params); byte[] outputScript = new byte[Block.MAX_BLOCK_SIGOPS - sigOps + 1]; Arrays.fill(outputScript, (byte) OP_CHECKSIGVERIFY); tx.addOutput(new TransactionOutput(params, tx, SATOSHI, outputScript)); - addOnlyInputToTransaction(tx, new TransactionOutPointWithValue( - new TransactionOutPoint(params, 1, b36.getTransactions().get(1).getHash()), - SATOSHI, b36.getTransactions().get(1).getOutputs().get(1).getScriptPubKey())); + addOnlyInputToTransaction(tx, b36); b36.addTransaction(tx); } b36.solve(); - - blocks.add(new BlockAndValidity(blockToHeightMap, hashHeaderMap, b36, false, true, b35.getHash(), chainHeadHeight + 11, "b36")); - + + blocks.add(new BlockAndValidity(b36, false, true, b35.getHash(), chainHeadHeight + 11, "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) @@ -669,7 +593,7 @@ public class FullBlockTestGenerator { // \-> b37 (11) // \-> b38 (11) // - Block b37 = createNextBlock(b35, chainHeadHeight + 12, out11, null); + NewBlock b37 = createNextBlock(b35, chainHeadHeight + 12, out11, null); { Transaction tx = new Transaction(params); tx.addOutput(new TransactionOutput(params, tx, SATOSHI, new byte[] {})); @@ -677,21 +601,19 @@ public class FullBlockTestGenerator { b37.addTransaction(tx); } b37.solve(); - blocks.add(new BlockAndValidity(blockToHeightMap, hashHeaderMap, b37, false, true, b35.getHash(), chainHeadHeight + 11, "b37")); - - Block b38 = createNextBlock(b35, chainHeadHeight + 12, out11, null); + blocks.add(new BlockAndValidity(b37, false, true, b35.getHash(), chainHeadHeight + 11, "b37")); + + NewBlock b38 = createNextBlock(b35, chainHeadHeight + 12, out11, null); { Transaction tx = new Transaction(params); tx.addOutput(new TransactionOutput(params, tx, SATOSHI, 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()), - SATOSHI, b37.getTransactions().get(1).getOutputs().get(1).getScriptPubKey())); + addOnlyInputToTransaction(tx, b37); b38.addTransaction(tx); } b38.solve(); - blocks.add(new BlockAndValidity(blockToHeightMap, hashHeaderMap, b38, false, true, b35.getHash(), chainHeadHeight + 11, "b38")); - + blocks.add(new BlockAndValidity(b38, false, true, b35.getHash(), chainHeadHeight + 11, "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) @@ -699,7 +621,7 @@ public class FullBlockTestGenerator { // Create some P2SH outputs that will require 6 sigops to spend byte[] b39p2shScriptPubKey; int b39numP2SHOutputs = 0, b39sigOpsPerOutput = 6; - Block b39 = createNextBlock(b35, chainHeadHeight + 12, null, null); + NewBlock b39 = createNextBlock(b35, chainHeadHeight + 12, null, null); { ByteArrayOutputStream p2shScriptPubKey = new UnsafeByteArrayOutputStream(); try { @@ -719,7 +641,7 @@ public class FullBlockTestGenerator { throw new RuntimeException(e); // Cannot happen. } b39p2shScriptPubKey = p2shScriptPubKey.toByteArray(); - + byte[] scriptHash = Utils.sha256hash160(b39p2shScriptPubKey); UnsafeByteArrayOutputStream scriptPubKey = new UnsafeByteArrayOutputStream(scriptHash.length + 3); scriptPubKey.write(OP_HASH160); @@ -729,7 +651,7 @@ public class FullBlockTestGenerator { throw new RuntimeException(e); // Cannot happen. } scriptPubKey.write(OP_EQUAL); - + Coin lastOutputValue = out11.value.subtract(SATOSHI); TransactionOutPoint lastOutPoint; { @@ -741,8 +663,8 @@ public class FullBlockTestGenerator { b39.addTransaction(tx); } b39numP2SHOutputs++; - - while (b39.getMessageSize() < Block.MAX_BLOCK_SIZE) + + while (b39.block.getMessageSize() < Block.MAX_BLOCK_SIZE) { Transaction tx = new Transaction(params); @@ -751,8 +673,8 @@ public class FullBlockTestGenerator { tx.addOutput(new TransactionOutput(params, tx, lastOutputValue, new byte[]{OP_1})); tx.addInput(new TransactionInput(params, tx, new byte[]{OP_1}, lastOutPoint)); lastOutPoint = new TransactionOutPoint(params, 1, tx.getHash()); - - if (b39.getMessageSize() + tx.getMessageSize() < Block.MAX_BLOCK_SIZE) { + + if (b39.block.getMessageSize() + tx.getMessageSize() < Block.MAX_BLOCK_SIZE) { b39.addTransaction(tx); b39numP2SHOutputs++; } else @@ -760,34 +682,31 @@ public class FullBlockTestGenerator { } } b39.solve(); - blocks.add(new BlockAndValidity(blockToHeightMap, hashHeaderMap, b39, true, false, b39.getHash(), chainHeadHeight + 12, "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())); - + blocks.add(new BlockAndValidity(b39, true, false, b39.getHash(), chainHeadHeight + 12, "b39")); + spendableOutputs.offer(b39.getCoinbaseOutput()); + TransactionOutPointWithValue out12 = spendableOutputs.poll(); - - Block b40 = createNextBlock(b39, chainHeadHeight + 13, out12, null); + + NewBlock b40 = createNextBlock(b39, chainHeadHeight + 13, out12, null); { int sigOps = 0; - for (Transaction tx : b40.transactions) { + for (Transaction tx : b40.block.getTransactions()) { sigOps += tx.getSigOpCount(); } - + int numTxes = (Block.MAX_BLOCK_SIGOPS - sigOps) / b39sigOpsPerOutput; checkState(numTxes <= b39numP2SHOutputs); - - TransactionOutPoint lastOutPoint = new TransactionOutPoint(params, 2, b40.getTransactions().get(1).getHash()); - + + TransactionOutPoint lastOutPoint = new TransactionOutPoint(params, 1, b40.block.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, SATOSHI, new byte[] {OP_1})); tx.addInput(new TransactionInput(params, tx, new byte[]{OP_1}, lastOutPoint)); - + TransactionInput input = new TransactionInput(params, tx, new byte[]{}, - new TransactionOutPoint(params, 0, b39.getTransactions().get(i).getHash())); + new TransactionOutPoint(params, 0, b39.block.getTransactions().get(i).getHash())); tx.addInput(input); if (scriptSig == null) { @@ -805,17 +724,17 @@ public class FullBlockTestGenerator { Script.writeBytes(scriptSigBos, new byte[] {(byte) 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); } @@ -828,14 +747,14 @@ public class FullBlockTestGenerator { b40.addTransaction(tx); } b40.solve(); - blocks.add(new BlockAndValidity(blockToHeightMap, hashHeaderMap, b40, false, true, b39.getHash(), chainHeadHeight + 12, "b40")); - - Block b41 = null; - if (addSigExpensiveBlocks) { + blocks.add(new BlockAndValidity(b40, false, true, b39.getHash(), chainHeadHeight + 12, "b40")); + + NewBlock b41 = null; + if (runBarelyExpensiveTests) { b41 = createNextBlock(b39, chainHeadHeight + 13, out12, null); { int sigOps = 0; - for (Transaction tx : b41.transactions) { + for (Transaction tx : b41.block.getTransactions()) { sigOps += tx.getSigOpCount(); } @@ -844,7 +763,7 @@ public class FullBlockTestGenerator { checkState(numTxes <= b39numP2SHOutputs); TransactionOutPoint lastOutPoint = new TransactionOutPoint( - params, 2, b41.getTransactions().get(1).getHash()); + params, 1, b41.block.getTransactions().get(1).getHash()); byte[] scriptSig = null; for (int i = 1; i <= numTxes; i++) { @@ -856,7 +775,7 @@ public class FullBlockTestGenerator { TransactionInput input = new TransactionInput(params, tx, new byte[] {}, new TransactionOutPoint(params, 0, - b39.getTransactions().get(i).getHash())); + b39.block.getTransactions().get(i).getHash())); tx.addInput(input); if (scriptSig == null) { @@ -905,42 +824,36 @@ public class FullBlockTestGenerator { b41.addTransaction(tx); } b41.solve(); - blocks.add(new BlockAndValidity(blockToHeightMap, hashHeaderMap, b41, true, false, b41.getHash(), chainHeadHeight + 13, "b41")); + blocks.add(new BlockAndValidity(b41, true, false, b41.getHash(), chainHeadHeight + 13, "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 + 13, out12, null); - blocks.add(new BlockAndValidity(blockToHeightMap, hashHeaderMap, b42, true, false, b41 == null ? b42.getHash() : b41.getHash(), chainHeadHeight + 13, "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())); - + NewBlock b42 = createNextBlock(b39, chainHeadHeight + 13, out12, null); + blocks.add(new BlockAndValidity(b42, true, false, b41 == null ? b42.getHash() : b41.getHash(), chainHeadHeight + 13, "b42")); + spendableOutputs.offer(b42.getCoinbaseOutput()); + TransactionOutPointWithValue out13 = spendableOutputs.poll(); - - Block b43 = createNextBlock(b42, chainHeadHeight + 14, out13, null); - blocks.add(new BlockAndValidity(blockToHeightMap, hashHeaderMap, b43, true, false, b43.getHash(), chainHeadHeight + 14, "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())); - + + NewBlock b43 = createNextBlock(b42, chainHeadHeight + 14, out13, null); + blocks.add(new BlockAndValidity(b43, true, false, b43.getHash(), chainHeadHeight + 14, "b43")); + spendableOutputs.offer(b43.getCoinbaseOutput()); + // 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); byte[] outScriptBytes = ScriptBuilder.createOutputScript(ECKey.fromPublicOnly(coinbaseOutKeyPubKey)).getProgram(); { - b44.setDifficultyTarget(b43.getDifficultyTarget()); + b44.setDifficultyTarget(b43.block.getDifficultyTarget()); b44.addCoinbaseTransaction(coinbaseOutKeyPubKey, ZERO); - + Transaction t = new Transaction(params); // Entirely invalid scriptPubKey to ensure we aren't pre-verifying too much t.addOutput(new TransactionOutput(params, t, ZERO, new byte[] {OP_PUSHDATA1 - 1 })); @@ -951,19 +864,19 @@ public class FullBlockTestGenerator { b44.addTransaction(t); b44.setPrevBlockHash(b43.getHash()); - b44.setTime(b43.getTimeSeconds() + 1); + b44.setTime(b43.block.getTimeSeconds() + 1); } b44.solve(); - blocks.add(new BlockAndValidity(blockToHeightMap, hashHeaderMap, b44, true, false, b44.getHash(), chainHeadHeight + 15, "b44")); - + blocks.add(new BlockAndValidity(b44, true, false, b44.getHash(), chainHeadHeight + 15, "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, ZERO, new byte[] {OP_PUSHDATA1 - 1 })); @@ -982,8 +895,8 @@ public class FullBlockTestGenerator { b45.setTime(b44.getTimeSeconds() + 1); } b45.solve(); - blocks.add(new BlockAndValidity(blockToHeightMap, hashHeaderMap, b45, false, true, b44.getHash(), chainHeadHeight + 15, "b45")); - + blocks.add(new BlockAndValidity(b45, false, true, b44.getHash(), chainHeadHeight + 15, "b45")); + // A block with no txn Block b46 = new Block(params); { @@ -995,140 +908,128 @@ public class FullBlockTestGenerator { b46.setTime(b44.getTimeSeconds() + 1); } b46.solve(); - blocks.add(new BlockAndValidity(blockToHeightMap, hashHeaderMap, b46, false, true, b44.getHash(), chainHeadHeight + 15, "b46")); - + blocks.add(new BlockAndValidity(b46, false, true, b44.getHash(), chainHeadHeight + 15, "b46")); + // A block with invalid work - Block b47 = createNextBlock(b44, chainHeadHeight + 16, out15, null); + NewBlock b47 = createNextBlock(b44, chainHeadHeight + 16, out15, null); { try { // Inverse solve - BigInteger target = b47.getDifficultyTargetAsInteger(); + BigInteger target = b47.block.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); + b47.block.setNonce(b47.block.getNonce() + 1); } } catch (VerificationException e) { throw new RuntimeException(e); // Cannot happen. } } - blocks.add(new BlockAndValidity(blockToHeightMap, hashHeaderMap, b47, false, true, b44.getHash(), chainHeadHeight + 15, "b47")); - + blocks.add(new BlockAndValidity(b47, false, true, b44.getHash(), chainHeadHeight + 15, "b47")); + // Block with timestamp > 2h in the future - Block b48 = createNextBlock(b44, chainHeadHeight + 16, out15, null); - b48.setTime(Utils.currentTimeSeconds() + 60*60*3); + NewBlock b48 = createNextBlock(b44, chainHeadHeight + 16, out15, null); + b48.block.setTime(Utils.currentTimeSeconds() + 60 * 60 * 3); b48.solve(); - blocks.add(new BlockAndValidity(blockToHeightMap, hashHeaderMap, b48, false, true, b44.getHash(), chainHeadHeight + 15, "b48")); - + blocks.add(new BlockAndValidity(b48, false, true, b44.getHash(), chainHeadHeight + 15, "b48")); + // Block with invalid merkle hash - Block b49 = createNextBlock(b44, chainHeadHeight + 16, out15, null); + NewBlock b49 = createNextBlock(b44, chainHeadHeight + 16, out15, null); byte[] b49MerkleHash = Sha256Hash.ZERO_HASH.getBytes().clone(); b49MerkleHash[1] = (byte) 0xDE; - b49.setMerkleRoot(Sha256Hash.create(b49MerkleHash)); + b49.block.setMerkleRoot(Sha256Hash.create(b49MerkleHash)); b49.solve(); - blocks.add(new BlockAndValidity(blockToHeightMap, hashHeaderMap, b49, false, true, b44.getHash(), chainHeadHeight + 15, "b49")); - + blocks.add(new BlockAndValidity(b49, false, true, b44.getHash(), chainHeadHeight + 15, "b49")); + // Block with incorrect POW limit - Block b50 = createNextBlock(b44, chainHeadHeight + 16, out15, null); + NewBlock b50 = createNextBlock(b44, chainHeadHeight + 16, out15, null); { long diffTarget = b44.getDifficultyTarget(); diffTarget &= 0xFFBFFFFF; // Make difficulty one bit harder - b50.setDifficultyTarget(diffTarget); + b50.block.setDifficultyTarget(diffTarget); } b50.solve(); - blocks.add(new BlockAndValidity(blockToHeightMap, hashHeaderMap, b50, false, true, b44.getHash(), chainHeadHeight + 15, "b50")); - + blocks.add(new BlockAndValidity(b50, false, true, b44.getHash(), chainHeadHeight + 15, "b50")); + // A block with two coinbase txn - Block b51 = createNextBlock(b44, chainHeadHeight + 16, out15, null); + NewBlock b51 = createNextBlock(b44, chainHeadHeight + 16, 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, SATOSHI, outScriptBytes)); - b51.addTransaction(coinbase, false); + b51.block.addTransaction(coinbase, false); } b51.solve(); - blocks.add(new BlockAndValidity(blockToHeightMap, hashHeaderMap, b51, false, true, b44.getHash(), chainHeadHeight + 15, "b51")); - + blocks.add(new BlockAndValidity(b51, false, true, b44.getHash(), chainHeadHeight + 15, "b51")); + // A block with duplicate txn - Block b52 = createNextBlock(b44, chainHeadHeight + 16, out15, null); + NewBlock b52 = createNextBlock(b44, chainHeadHeight + 16, out15, null); { Transaction tx = new Transaction(params); tx.addOutput(new TransactionOutput(params, tx, SATOSHI, new byte[] {})); - addOnlyInputToTransaction(tx, new TransactionOutPointWithValue( - new TransactionOutPoint(params, 1, b52.getTransactions().get(1).getHash()), - SATOSHI, b52.getTransactions().get(1).getOutputs().get(1).getScriptPubKey())); + addOnlyInputToTransaction(tx, b52); b52.addTransaction(tx); b52.addTransaction(tx); } b52.solve(); - blocks.add(new BlockAndValidity(blockToHeightMap, hashHeaderMap, b52, false, true, b44.getHash(), chainHeadHeight + 15, "b52")); - + blocks.add(new BlockAndValidity(b52, false, true, b44.getHash(), chainHeadHeight + 15, "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 + 15, out14, null); - blocks.add(new BlockAndValidity(blockToHeightMap, hashHeaderMap, b53, true, false, b44.getHash(), chainHeadHeight + 15, "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())); - + NewBlock b53 = createNextBlock(b43, chainHeadHeight + 15, out14, null); + blocks.add(new BlockAndValidity(b53, true, false, b44.getHash(), chainHeadHeight + 15, "b53")); + spendableOutputs.offer(b53.getCoinbaseOutput()); + // Block with invalid timestamp - Block b54 = createNextBlock(b53, chainHeadHeight + 16, out15, null); - b54.setTime(b35.getTimeSeconds() - 1); + NewBlock b54 = createNextBlock(b53, chainHeadHeight + 16, out15, null); + b54.block.setTime(b35.block.getTimeSeconds() - 1); b54.solve(); - blocks.add(new BlockAndValidity(blockToHeightMap, hashHeaderMap, b54, false, true, b44.getHash(), chainHeadHeight + 15, "b54")); - + blocks.add(new BlockAndValidity(b54, false, true, b44.getHash(), chainHeadHeight + 15, "b54")); + // Block with valid timestamp - Block b55 = createNextBlock(b53, chainHeadHeight + 16, out15, null); - b55.setTime(b35.getTimeSeconds()); + NewBlock b55 = createNextBlock(b53, chainHeadHeight + 16, out15, null); + b55.block.setTime(b35.block.getTimeSeconds()); b55.solve(); - blocks.add(new BlockAndValidity(blockToHeightMap, hashHeaderMap, b55, true, false, b55.getHash(), chainHeadHeight + 16, "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())); - + blocks.add(new BlockAndValidity(b55, true, false, b55.getHash(), chainHeadHeight + 16, "b55")); + spendableOutputs.offer(b55.getCoinbaseOutput()); + // 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 + 17, out16, null); + + NewBlock b57 = createNextBlock(b55, chainHeadHeight + 17, out16, null); Transaction b56txToDuplicate; { b56txToDuplicate = new Transaction(params); b56txToDuplicate.addOutput(new TransactionOutput(params, b56txToDuplicate, SATOSHI, new byte[] {})); - addOnlyInputToTransaction(b56txToDuplicate, new TransactionOutPointWithValue( - new TransactionOutPoint(params, 1, b57.getTransactions().get(1).getHash()), - SATOSHI, b57.getTransactions().get(1).getOutputs().get(1).getScriptPubKey())); + addOnlyInputToTransaction(b56txToDuplicate, b57); b57.addTransaction(b56txToDuplicate); } b57.solve(); - + Block b56; try { - b56 = new Block(params, b57.bitcoinSerialize()); + b56 = new Block(params, b57.block.bitcoinSerialize()); } catch (ProtocolException e) { throw new RuntimeException(e); // Cannot happen. } b56.addTransaction(b56txToDuplicate); checkState(b56.getHash().equals(b57.getHash())); - blocks.add(new BlockAndValidity(blockToHeightMap, hashHeaderMap, b56, false, true, b55.getHash(), chainHeadHeight + 16, "b56")); + blocks.add(new BlockAndValidity(b56, false, true, b55.getHash(), chainHeadHeight + 16, "b56")); - Block b57p2 = createNextBlock(b55, chainHeadHeight + 17, out16, null); + NewBlock b57p2 = createNextBlock(b55, chainHeadHeight + 17, out16, null); Transaction b56p2txToDuplicate1, b56p2txToDuplicate2; { Transaction tx1 = new Transaction(params); tx1.addOutput(new TransactionOutput(params, tx1, SATOSHI, new byte[] {OP_TRUE})); - addOnlyInputToTransaction(tx1, new TransactionOutPointWithValue( - new TransactionOutPoint(params, 1, b57p2.getTransactions().get(1).getHash()), - SATOSHI, b57p2.getTransactions().get(1).getOutputs().get(1).getScriptPubKey())); + addOnlyInputToTransaction(tx1, b57p2); b57p2.addTransaction(tx1); Transaction tx2 = new Transaction(params); @@ -1156,155 +1057,143 @@ public class FullBlockTestGenerator { Block b56p2; try { - b56p2 = new Block(params, b57p2.bitcoinSerialize()); + b56p2 = new Block(params, b57p2.block.bitcoinSerialize()); } catch (ProtocolException e) { throw new RuntimeException(e); // Cannot happen. } b56p2.addTransaction(b56p2txToDuplicate1); b56p2.addTransaction(b56p2txToDuplicate2); checkState(b56p2.getHash().equals(b57p2.getHash())); - blocks.add(new BlockAndValidity(blockToHeightMap, hashHeaderMap, b56p2, false, true, b55.getHash(), chainHeadHeight + 16, "b56p2")); - blocks.add(new BlockAndValidity(blockToHeightMap, hashHeaderMap, b57p2, true, false, b57p2.getHash(), chainHeadHeight + 17, "b57p2")); + blocks.add(new BlockAndValidity(b56p2, false, true, b55.getHash(), chainHeadHeight + 16, "b56p2")); + blocks.add(new BlockAndValidity(b57p2, true, false, b57p2.getHash(), chainHeadHeight + 17, "b57p2")); + + blocks.add(new BlockAndValidity(b57, true, false, b57p2.getHash(), chainHeadHeight + 17, "b57")); + spendableOutputs.offer(b57.getCoinbaseOutput()); - blocks.add(new BlockAndValidity(blockToHeightMap, hashHeaderMap, b57, true, false, b57p2.getHash(), chainHeadHeight + 17, "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 + 18, out17, null); + NewBlock b58 = createNextBlock(b57, chainHeadHeight + 18, out17, null); { Transaction tx = new Transaction(params); tx.addOutput(new TransactionOutput(params, tx, ZERO, new byte[] {})); - tx.addInput(new TransactionInput(params, tx, new byte[] {OP_1}, - new TransactionOutPoint(params, 3, b58.getTransactions().get(1).getHash()))); + b58.getSpendableOutput().outpoint.setIndex(42); + addOnlyInputToTransaction(tx, b58); b58.addTransaction(tx); } b58.solve(); - blocks.add(new BlockAndValidity(blockToHeightMap, hashHeaderMap, b58, false, true, b57p2.getHash(), chainHeadHeight + 17, "b58")); - + blocks.add(new BlockAndValidity(b58, false, true, b57p2.getHash(), chainHeadHeight + 17, "b58")); + // tx with output value > input value out of range - Block b59 = createNextBlock(b57, chainHeadHeight + 18, out17, null); + NewBlock b59 = createNextBlock(b57, chainHeadHeight + 18, out17, null); { Transaction tx = new Transaction(params); tx.addOutput(new TransactionOutput(params, tx, - b59.getTransactions().get(1).getOutputs().get(2).getValue().add(SATOSHI), new byte[] {})); - tx.addInput(new TransactionInput(params, tx, new byte[] {OP_1}, - new TransactionOutPoint(params, 2, b59.getTransactions().get(1).getHash()))); + b59.getSpendableOutput().value.add(SATOSHI), new byte[]{})); + addOnlyInputToTransaction(tx, b59); b59.addTransaction(tx); } b59.solve(); - blocks.add(new BlockAndValidity(blockToHeightMap, hashHeaderMap, b59, false, true, b57p2.getHash(), chainHeadHeight + 17, "b59")); - - Block b60 = createNextBlock(b57, chainHeadHeight + 18, out17, null); - blocks.add(new BlockAndValidity(blockToHeightMap, hashHeaderMap, b60, true, false, b60.getHash(), chainHeadHeight + 18, "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())); - + blocks.add(new BlockAndValidity(b59, false, true, b57p2.getHash(), chainHeadHeight + 17, "b59")); + + NewBlock b60 = createNextBlock(b57, chainHeadHeight + 18, out17, null); + blocks.add(new BlockAndValidity(b60, true, false, b60.getHash(), chainHeadHeight + 18, "b60")); + spendableOutputs.offer(b60.getCoinbaseOutput()); + // 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 + 19, out18, null); + + NewBlock b61 = createNextBlock(b60, chainHeadHeight + 19, out18, null); { - b61.getTransactions().get(0).getInput(0).setScriptBytes(b60.getTransactions().get(0).getInput(0).getScriptBytes()); - b61.unCache(); - checkState(b61.getTransactions().get(0).equals(b60.getTransactions().get(0))); + b61.block.getTransactions().get(0).getInput(0).setScriptBytes(b60.block.getTransactions().get(0).getInput(0).getScriptBytes()); + b61.block.unCache(); + checkState(b61.block.getTransactions().get(0).equals(b60.block.getTransactions().get(0))); } b61.solve(); - blocks.add(new BlockAndValidity(blockToHeightMap, hashHeaderMap, b61, false, true, b60.getHash(), chainHeadHeight + 18, "b61")); - + blocks.add(new BlockAndValidity(b61, false, true, b60.getHash(), chainHeadHeight + 18, "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 + 19, null, null); + NewBlock b62 = createNextBlock(b60, chainHeadHeight + 19, null, null); { Transaction tx = new Transaction(params); tx.setLockTime(0xffffffffL); tx.addOutput(ZERO, OP_TRUE_SCRIPT); addOnlyInputToTransaction(tx, out18, 0); b62.addTransaction(tx); - checkState(!tx.isFinal(chainHeadHeight + 17, b62.getTimeSeconds())); + checkState(!tx.isFinal(chainHeadHeight + 17, b62.block.getTimeSeconds())); } b62.solve(); - blocks.add(new BlockAndValidity(blockToHeightMap, hashHeaderMap, b62, false, true, b60.getHash(), chainHeadHeight + 18, "b62")); - + blocks.add(new BlockAndValidity(b62, false, true, b60.getHash(), chainHeadHeight + 18, "b62")); + // Test a non-final coinbase is also rejected // -> b39 (11) -> b42 (12) -> b43 (13) -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17) // \-> b63 (-) // - Block b63 = createNextBlock(b60, chainHeadHeight + 19, null, null); + NewBlock b63 = createNextBlock(b60, chainHeadHeight + 19, null, null); { - b63.getTransactions().get(0).setLockTime(0xffffffffL); - b63.getTransactions().get(0).getInputs().get(0).setSequenceNumber(0xDEADBEEF); - checkState(!b63.getTransactions().get(0).isFinal(chainHeadHeight + 17, b63.getTimeSeconds())); + b63.block.getTransactions().get(0).setLockTime(0xffffffffL); + b63.block.getTransactions().get(0).getInputs().get(0).setSequenceNumber(0xDEADBEEF); + checkState(!b63.block.getTransactions().get(0).isFinal(chainHeadHeight + 17, b63.block.getTimeSeconds())); } b63.solve(); - blocks.add(new BlockAndValidity(blockToHeightMap, hashHeaderMap, b63, false, true, b60.getHash(), chainHeadHeight + 18, "b63")); - + blocks.add(new BlockAndValidity(b63, false, true, b60.getHash(), chainHeadHeight + 18, "b63")); + // Check that a block which is (when properly encoded) <= MAX_BLOCK_SIZE is accepted // Even when it is encoded with varints that make its encoded size actually > MAX_BLOCK_SIZE // -> b39 (11) -> b42 (12) -> b43 (13) -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17) -> b64 (18) // - Block b64; + Block b64; NewBlock b64Original; { - Block b64Created = createNextBlock(b60, chainHeadHeight + 19, out18, null); + b64Original = createNextBlock(b60, chainHeadHeight + 19, out18, null); Transaction tx = new Transaction(params); - byte[] outputScript = new byte[Block.MAX_BLOCK_SIZE - b64Created.getMessageSize() - 65]; + byte[] outputScript = new byte[Block.MAX_BLOCK_SIZE - b64Original.block.getMessageSize() - 65]; Arrays.fill(outputScript, (byte) OP_FALSE); tx.addOutput(new TransactionOutput(params, tx, ZERO, outputScript)); - addOnlyInputToTransaction(tx, new TransactionOutPointWithValue( - new TransactionOutPoint(params, 2, b64Created.getTransactions().get(1).getHash()), - ZERO, b64Created.getTransactions().get(1).getOutputs().get(2).getScriptPubKey())); - b64Created.addTransaction(tx); - b64Created.solve(); - checkState(b64Created.getMessageSize() == Block.MAX_BLOCK_SIZE); - - UnsafeByteArrayOutputStream stream = new UnsafeByteArrayOutputStream(b64Created.getMessageSize() + 8); - b64Created.writeHeader(stream); - + addOnlyInputToTransaction(tx, b64Original); + b64Original.addTransaction(tx); + b64Original.solve(); + checkState(b64Original.block.getMessageSize() == Block.MAX_BLOCK_SIZE); + + UnsafeByteArrayOutputStream stream = new UnsafeByteArrayOutputStream(b64Original.block.getMessageSize() + 8); + b64Original.block.writeHeader(stream); + byte[] varIntBytes = new byte[9]; varIntBytes[0] = (byte) 255; - Utils.uint32ToByteArrayLE((long)b64Created.getTransactions().size(), varIntBytes, 1); - Utils.uint32ToByteArrayLE(((long)b64Created.getTransactions().size()) >>> 32, varIntBytes, 5); + Utils.uint32ToByteArrayLE((long)b64Original.block.getTransactions().size(), varIntBytes, 1); + Utils.uint32ToByteArrayLE(((long)b64Original.block.getTransactions().size()) >>> 32, varIntBytes, 5); stream.write(varIntBytes); - checkState(new VarInt(varIntBytes, 0).value == b64Created.getTransactions().size()); - - for (Transaction transaction : b64Created.getTransactions()) + checkState(new VarInt(varIntBytes, 0).value == b64Original.block.getTransactions().size()); + + for (Transaction transaction : b64Original.block.getTransactions()) transaction.bitcoinSerialize(stream); b64 = new Block(params, stream.toByteArray(), false, true, stream.size()); - + // The following checks are checking to ensure block serialization functions in the way needed for this test // If they fail, it is likely not an indication of error, but an indication that this test needs rewritten - checkState(stream.size() == b64Created.getMessageSize() + 8); + checkState(stream.size() == b64Original.block.getMessageSize() + 8); checkState(stream.size() == b64.getMessageSize()); checkState(Arrays.equals(stream.toByteArray(), b64.bitcoinSerialize())); - checkState(b64.getOptimalEncodingMessageSize() == b64Created.getMessageSize()); + checkState(b64.getOptimalEncodingMessageSize() == b64Original.block.getMessageSize()); } - blocks.add(new BlockAndValidity(blockToHeightMap, hashHeaderMap, b64, true, false, b64.getHash(), chainHeadHeight + 19, "b64")); - spendableOutputs.offer(new TransactionOutPointWithValue( - new TransactionOutPoint(params, 0, b64.getTransactions().get(0).getHash()), - b64.getTransactions().get(0).getOutputs().get(0).getValue(), - b64.getTransactions().get(0).getOutputs().get(0).getScriptPubKey())); - + blocks.add(new BlockAndValidity(b64, true, false, b64.getHash(), chainHeadHeight + 19, "b64")); + spendableOutputs.offer(b64Original.getCoinbaseOutput()); + // Spend an output created in the block itself // -> b42 (12) -> b43 (13) -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17) -> b64 (18) -> b65 (19) // TransactionOutPointWithValue out19 = spendableOutputs.poll(); checkState(out19 != null);//TODO preconditions all the way up - - Block b65 = createNextBlock(b64, chainHeadHeight + 20, null, null); + + NewBlock b65 = createNextBlock(b64, chainHeadHeight + 20, null, null); { Transaction tx1 = new Transaction(params); tx1.addOutput(out19.value, OP_TRUE_SCRIPT); @@ -1316,19 +1205,16 @@ public class FullBlockTestGenerator { b65.addTransaction(tx2); } b65.solve(); - blocks.add(new BlockAndValidity(blockToHeightMap, hashHeaderMap, b65, true, false, b65.getHash(), chainHeadHeight + 20, "b65")); - spendableOutputs.offer(new TransactionOutPointWithValue( - new TransactionOutPoint(params, 0, b65.getTransactions().get(0).getHash()), - b65.getTransactions().get(0).getOutputs().get(0).getValue(), - b65.getTransactions().get(0).getOutputs().get(0).getScriptPubKey())); - + blocks.add(new BlockAndValidity(b65, true, false, b65.getHash(), chainHeadHeight + 20, "b65")); + spendableOutputs.offer(b65.getCoinbaseOutput()); + // Attempt to spend an output created later in the same block // -> b43 (13) -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17) -> b64 (18) -> b65 (19) // \-> b66 (20) // TransactionOutPointWithValue out20 = spendableOutputs.poll(); checkState(out20 != null); - - Block b66 = createNextBlock(b65, chainHeadHeight + 21, null, null); + + NewBlock b66 = createNextBlock(b65, chainHeadHeight + 21, null, null); { Transaction tx1 = new Transaction(params); tx1.addOutput(out20.value, OP_TRUE_SCRIPT); @@ -1340,13 +1226,13 @@ public class FullBlockTestGenerator { b66.addTransaction(tx1); } b66.solve(); - blocks.add(new BlockAndValidity(blockToHeightMap, hashHeaderMap, b66, false, true, b65.getHash(), chainHeadHeight + 20, "b66")); - + blocks.add(new BlockAndValidity(b66, false, true, b65.getHash(), chainHeadHeight + 20, "b66")); + // Attempt to double-spend a transaction created in a block // -> b43 (13) -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17) -> b64 (18) -> b65 (19) // \-> b67 (20) // - Block b67 = createNextBlock(b65, chainHeadHeight + 21, null, null); + NewBlock b67 = createNextBlock(b65, chainHeadHeight + 21, null, null); { Transaction tx1 = new Transaction(params); tx1.addOutput(out20.value, OP_TRUE_SCRIPT); @@ -1362,13 +1248,13 @@ public class FullBlockTestGenerator { b67.addTransaction(tx3); } b67.solve(); - blocks.add(new BlockAndValidity(blockToHeightMap, hashHeaderMap, b67, false, true, b65.getHash(), chainHeadHeight + 20, "b67")); - + blocks.add(new BlockAndValidity(b67, false, true, b65.getHash(), chainHeadHeight + 20, "b67")); + // A few more tests of block subsidy // -> b43 (13) -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17) -> b64 (18) -> b65 (19) -> b69 (20) // \-> b68 (20) // - Block b68 = createNextBlock(b65, chainHeadHeight + 21, null, SATOSHI.multiply(10)); + NewBlock b68 = createNextBlock(b65, chainHeadHeight + 21, null, SATOSHI.multiply(10)); { Transaction tx = new Transaction(params); tx.addOutput(out20.value.subtract(Coin.valueOf(9)), OP_TRUE_SCRIPT); @@ -1376,9 +1262,9 @@ public class FullBlockTestGenerator { b68.addTransaction(tx); } b68.solve(); - blocks.add(new BlockAndValidity(blockToHeightMap, hashHeaderMap, b68, false, true, b65.getHash(), chainHeadHeight + 20, "b68")); - - Block b69 = createNextBlock(b65, chainHeadHeight + 21, null, SATOSHI.multiply(10)); + blocks.add(new BlockAndValidity(b68, false, true, b65.getHash(), chainHeadHeight + 20, "b68")); + + NewBlock b69 = createNextBlock(b65, chainHeadHeight + 21, null, SATOSHI.multiply(10)); { Transaction tx = new Transaction(params); tx.addOutput(out20.value.subtract(Coin.valueOf(10)), OP_TRUE_SCRIPT); @@ -1386,19 +1272,15 @@ public class FullBlockTestGenerator { b69.addTransaction(tx); } b69.solve(); - blocks.add(new BlockAndValidity(blockToHeightMap, hashHeaderMap, b69, true, false, b69.getHash(), chainHeadHeight + 21, "b69")); + blocks.add(new BlockAndValidity(b69, true, false, b69.getHash(), chainHeadHeight + 21, "b69")); + spendableOutputs.offer(b69.getCoinbaseOutput()); - spendableOutputs.offer(new TransactionOutPointWithValue( - new TransactionOutPoint(params, 0, b69.getTransactions().get(0).getHash()), - b69.getTransactions().get(0).getOutputs().get(0).getValue(), - b69.getTransactions().get(0).getOutputs().get(0).getScriptPubKey())); - // Test spending the outpoint of a non-existent transaction // -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17) -> b64 (18) -> b65 (19) -> b69 (20) // \-> b70 (21) // TransactionOutPointWithValue out21 = spendableOutputs.poll(); checkState(out21 != null); - Block b70 = createNextBlock(b69, chainHeadHeight + 22, out21, null); + NewBlock b70 = createNextBlock(b69, chainHeadHeight + 22, out21, null); { Transaction tx = new Transaction(params); tx.addOutput(ZERO, OP_TRUE_SCRIPT); @@ -1407,35 +1289,27 @@ public class FullBlockTestGenerator { b70.addTransaction(tx); } b70.solve(); - blocks.add(new BlockAndValidity(blockToHeightMap, hashHeaderMap, b70, false, true, b69.getHash(), chainHeadHeight + 21, "b70")); - + blocks.add(new BlockAndValidity(b70, false, true, b69.getHash(), chainHeadHeight + 21, "b70")); + // Test accepting an invalid block which has the same hash as a valid one (via merkle tree tricks) // -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17) -> b64 (18) -> b65 (19) -> b69 (20) -> b71 (21) // \-> b72 (21) // - Block b72 = createNextBlock(b69, chainHeadHeight + 22, out21, null); + NewBlock b72 = createNextBlock(b69, chainHeadHeight + 22, out21, null); { Transaction tx = new Transaction(params); tx.addOutput(ZERO, OP_TRUE_SCRIPT); - final Transaction tx2 = b72.getTransactions().get(1); - addOnlyInputToTransaction(tx, new TransactionOutPointWithValue( - new TransactionOutPoint(params, 1, tx2.getHash()), - SATOSHI, tx2.getOutput(1).getScriptPubKey())); + addOnlyInputToTransaction(tx, b72); b72.addTransaction(tx); } b72.solve(); - - Block b71 = new Block(params, b72.bitcoinSerialize()); - b71.addTransaction(b72.getTransactions().get(2)); - checkState(b71.getHash().equals(b72.getHash())); - blocks.add(new BlockAndValidity(blockToHeightMap, hashHeaderMap, b71, false, true, b69.getHash(), chainHeadHeight + 21, "b71")); - blocks.add(new BlockAndValidity(blockToHeightMap, hashHeaderMap, b72, true, false, b72.getHash(), chainHeadHeight + 22, "b72")); - final Transaction b72tx = b72.getTransactions().get(0); - spendableOutputs.offer(new TransactionOutPointWithValue( - new TransactionOutPoint(params, 0, b72tx.getHash()), - b72tx.getOutput(0).getValue(), - b72tx.getOutput(0).getScriptPubKey())); + Block b71 = new Block(params, b72.block.bitcoinSerialize()); + b71.addTransaction(b72.block.getTransactions().get(2)); + checkState(b71.getHash().equals(b72.getHash())); + blocks.add(new BlockAndValidity(b71, false, true, b69.getHash(), chainHeadHeight + 21, "b71")); + blocks.add(new BlockAndValidity(b72, true, false, b72.getHash(), chainHeadHeight + 22, "b72")); + spendableOutputs.offer(b72.getCoinbaseOutput()); // Have some fun with invalid scripts and MAX_BLOCK_SIGOPS // -> b55 (15) -> b57 (16) -> b60 (17) -> b64 (18) -> b65 (19) -> b69 (20) -> b72 (21) @@ -1443,10 +1317,10 @@ public class FullBlockTestGenerator { // TransactionOutPointWithValue out22 = spendableOutputs.poll(); checkState(out22 != null); - Block b73 = createNextBlock(b72, chainHeadHeight + 23, out22, null); + NewBlock b73 = createNextBlock(b72, chainHeadHeight + 23, out22, null); { int sigOps = 0; - for (Transaction tx : b73.transactions) { + for (Transaction tx : b73.block.getTransactions()) { sigOps += tx.getSigOpCount(); } Transaction tx = new Transaction(params); @@ -1456,18 +1330,16 @@ public class FullBlockTestGenerator { outputScript[Block.MAX_BLOCK_SIGOPS - sigOps] = OP_PUSHDATA4; Utils.uint32ToByteArrayLE(Script.MAX_SCRIPT_ELEMENT_SIZE + 1, outputScript, Block.MAX_BLOCK_SIGOPS - sigOps + 1); tx.addOutput(new TransactionOutput(params, tx, SATOSHI, outputScript)); - addOnlyInputToTransaction(tx, new TransactionOutPointWithValue( - new TransactionOutPoint(params, 1, b73.getTransactions().get(1).getHash()), - SATOSHI, b73.getTransactions().get(1).getOutputs().get(1).getScriptPubKey())); + addOnlyInputToTransaction(tx, b73); b73.addTransaction(tx); } b73.solve(); - blocks.add(new BlockAndValidity(blockToHeightMap, hashHeaderMap, b73, false, true, b72.getHash(), chainHeadHeight + 22, "b73")); + blocks.add(new BlockAndValidity(b73, false, true, b72.getHash(), chainHeadHeight + 22, "b73")); - Block b74 = createNextBlock(b72, chainHeadHeight + 23, out22, null); + NewBlock b74 = createNextBlock(b72, chainHeadHeight + 23, out22, null); { int sigOps = 0; - for (Transaction tx : b74.transactions) { + for (Transaction tx : b74.block.getTransactions()) { sigOps += tx.getSigOpCount(); } Transaction tx = new Transaction(params); @@ -1480,18 +1352,16 @@ public class FullBlockTestGenerator { outputScript[Block.MAX_BLOCK_SIGOPS - sigOps + 4] = (byte)0xff; outputScript[Block.MAX_BLOCK_SIGOPS - sigOps + 5] = (byte)0xff; tx.addOutput(new TransactionOutput(params, tx, SATOSHI, outputScript)); - addOnlyInputToTransaction(tx, new TransactionOutPointWithValue( - new TransactionOutPoint(params, 1, b74.getTransactions().get(1).getHash()), - SATOSHI, b74.getTransactions().get(1).getOutputs().get(1).getScriptPubKey())); + addOnlyInputToTransaction(tx, b74); b74.addTransaction(tx); } b74.solve(); - blocks.add(new BlockAndValidity(blockToHeightMap, hashHeaderMap, b74, false, true, b72.getHash(), chainHeadHeight + 22, "b74")); + blocks.add(new BlockAndValidity(b74, false, true, b72.getHash(), chainHeadHeight + 22, "b74")); - Block b75 = createNextBlock(b72, chainHeadHeight + 23, out22, null); + NewBlock b75 = createNextBlock(b72, chainHeadHeight + 23, out22, null); { int sigOps = 0; - for (Transaction tx : b75.transactions) { + for (Transaction tx : b75.block.getTransactions()) { sigOps += tx.getSigOpCount(); } Transaction tx = new Transaction(params); @@ -1504,25 +1374,19 @@ public class FullBlockTestGenerator { outputScript[Block.MAX_BLOCK_SIGOPS - sigOps + 3] = (byte)0xff; outputScript[Block.MAX_BLOCK_SIGOPS - sigOps + 4] = (byte)0xff; tx.addOutput(new TransactionOutput(params, tx, SATOSHI, outputScript)); - addOnlyInputToTransaction(tx, new TransactionOutPointWithValue( - new TransactionOutPoint(params, 1, b75.getTransactions().get(1).getHash()), - SATOSHI, b75.getTransactions().get(1).getOutputs().get(1).getScriptPubKey())); + addOnlyInputToTransaction(tx, b75); b75.addTransaction(tx); } b75.solve(); - blocks.add(new BlockAndValidity(blockToHeightMap, hashHeaderMap, b75, true, false, b75.getHash(), chainHeadHeight + 23, "b75")); - final Transaction b75tx = b75.getTransactions().get(0); - spendableOutputs.offer(new TransactionOutPointWithValue( - new TransactionOutPoint(params, 0, b75tx.getHash()), - b75tx.getOutput(0).getValue(), - b75tx.getOutput(0).getScriptPubKey())); + blocks.add(new BlockAndValidity(b75, true, false, b75.getHash(), chainHeadHeight + 23, "b75")); + spendableOutputs.offer(b75.getCoinbaseOutput()); TransactionOutPointWithValue out23 = spendableOutputs.poll(); checkState(out23 != null); - Block b76 = createNextBlock(b75, chainHeadHeight + 24, out23, null); + NewBlock b76 = createNextBlock(b75, chainHeadHeight + 24, out23, null); { int sigOps = 0; - for (Transaction tx : b76.transactions) { + for (Transaction tx : b76.block.getTransactions()) { sigOps += tx.getSigOpCount(); } Transaction tx = new Transaction(params); @@ -1532,18 +1396,12 @@ public class FullBlockTestGenerator { outputScript[Block.MAX_BLOCK_SIGOPS - sigOps] = OP_PUSHDATA4; Utils.uint32ToByteArrayLE(Block.MAX_BLOCK_SIGOPS, outputScript, Block.MAX_BLOCK_SIGOPS - sigOps + 1); tx.addOutput(new TransactionOutput(params, tx, SATOSHI, outputScript)); - addOnlyInputToTransaction(tx, new TransactionOutPointWithValue( - new TransactionOutPoint(params, 1, b76.getTransactions().get(1).getHash()), - SATOSHI, b76.getTransactions().get(1).getOutput(1).getScriptPubKey())); + addOnlyInputToTransaction(tx, b76); b76.addTransaction(tx); } b76.solve(); - blocks.add(new BlockAndValidity(blockToHeightMap, hashHeaderMap, b76, true, false, b76.getHash(), chainHeadHeight + 24, "b76")); - final Transaction b76tx = b76.getTransactions().get(0); - spendableOutputs.offer(new TransactionOutPointWithValue( - new TransactionOutPoint(params, 0, b76tx.getHash()), - b76tx.getOutput(0).getValue(), - b76tx.getOutput(0).getScriptPubKey())); + blocks.add(new BlockAndValidity(b76, true, false, b76.getHash(), chainHeadHeight + 24, "b76")); + spendableOutputs.offer(b76.getCoinbaseOutput()); // Test transaction resurrection // -> b77 (24) -> b78 (25) -> b79 (26) @@ -1555,26 +1413,21 @@ public class FullBlockTestGenerator { TransactionOutPointWithValue out26 = checkNotNull(spendableOutputs.poll()); TransactionOutPointWithValue out27 = checkNotNull(spendableOutputs.poll()); - Block b77 = createNextBlock(b76, chainHeadHeight + 25, out24, null); - blocks.add(new BlockAndValidity(blockToHeightMap, hashHeaderMap, b77, true, false, b77.getHash(), chainHeadHeight + 25, "b77")); - spendableOutputs.offer(new TransactionOutPointWithValue( - new TransactionOutPoint(params, 0, b77.getTransactions().get(0).getHash()), - b77.getTransactions().get(0).getOutputs().get(0).getValue(), - b77.getTransactions().get(0).getOutputs().get(0).getScriptPubKey())); + NewBlock b77 = createNextBlock(b76, chainHeadHeight + 25, out24, null); + blocks.add(new BlockAndValidity(b77, true, false, b77.getHash(), chainHeadHeight + 25, "b77")); + spendableOutputs.offer(b77.getCoinbaseOutput()); - Block b78 = createNextBlock(b77, chainHeadHeight + 26, out25, null); + NewBlock b78 = createNextBlock(b77, chainHeadHeight + 26, out25, null); Transaction b78tx = new Transaction(params); { b78tx.addOutput(ZERO, OP_TRUE_SCRIPT); - addOnlyInputToTransaction(b78tx, new TransactionOutPointWithValue( - new TransactionOutPoint(params, 1, b77.getTransactions().get(1).getHash()), - SATOSHI, b77.getTransactions().get(1).getOutputs().get(1).getScriptPubKey())); + addOnlyInputToTransaction(b78tx, b77); b78.addTransaction(b78tx); } b78.solve(); - blocks.add(new BlockAndValidity(blockToHeightMap, hashHeaderMap, b78, true, false, b78.getHash(), chainHeadHeight + 26, "b78")); + blocks.add(new BlockAndValidity(b78, true, false, b78.getHash(), chainHeadHeight + 26, "b78")); - Block b79 = createNextBlock(b78, chainHeadHeight + 27, out26, null); + NewBlock b79 = createNextBlock(b78, chainHeadHeight + 27, out26, null); Transaction b79tx = new Transaction(params); { @@ -1583,30 +1436,21 @@ public class FullBlockTestGenerator { b79.addTransaction(b79tx); } b79.solve(); - blocks.add(new BlockAndValidity(blockToHeightMap, hashHeaderMap, b79, true, false, b79.getHash(), chainHeadHeight + 27, "b79")); + blocks.add(new BlockAndValidity(b79, true, false, b79.getHash(), chainHeadHeight + 27, "b79")); blocks.add(new MemoryPoolState(new HashSet(), "post-b79 empty mempool")); - Block b80 = createNextBlock(b77, chainHeadHeight + 26, out25, null); - blocks.add(new BlockAndValidity(blockToHeightMap, hashHeaderMap, b80, true, false, b79.getHash(), chainHeadHeight + 27, "b80")); - spendableOutputs.offer(new TransactionOutPointWithValue( - new TransactionOutPoint(params, 0, b80.getTransactions().get(0).getHash()), - b80.getTransactions().get(0).getOutput(0).getValue(), - b80.getTransactions().get(0).getOutput(0).getScriptPubKey())); + NewBlock b80 = createNextBlock(b77, chainHeadHeight + 26, out25, null); + blocks.add(new BlockAndValidity(b80, true, false, b79.getHash(), chainHeadHeight + 27, "b80")); + spendableOutputs.offer(b80.getCoinbaseOutput()); - Block b81 = createNextBlock(b80, chainHeadHeight + 27, out26, null); - blocks.add(new BlockAndValidity(blockToHeightMap, hashHeaderMap, b81, true, false, b79.getHash(), chainHeadHeight + 27, "b81")); - spendableOutputs.offer(new TransactionOutPointWithValue( - new TransactionOutPoint(params, 0, b81.getTransactions().get(0).getHash()), - b81.getTransactions().get(0).getOutput(0).getValue(), - b81.getTransactions().get(0).getOutput(0).getScriptPubKey())); + NewBlock b81 = createNextBlock(b80, chainHeadHeight + 27, out26, null); + blocks.add(new BlockAndValidity(b81, true, false, b79.getHash(), chainHeadHeight + 27, "b81")); + spendableOutputs.offer(b81.getCoinbaseOutput()); - Block b82 = createNextBlock(b81, chainHeadHeight + 28, out27, null); - blocks.add(new BlockAndValidity(blockToHeightMap, hashHeaderMap, b82, true, false, b82.getHash(), chainHeadHeight + 28, "b82")); - spendableOutputs.offer(new TransactionOutPointWithValue( - new TransactionOutPoint(params, 0, b82.getTransactions().get(0).getHash()), - b82.getTransactions().get(0).getOutput(0).getValue(), - b82.getTransactions().get(0).getOutput(0).getScriptPubKey())); + NewBlock b82 = createNextBlock(b81, chainHeadHeight + 28, out27, null); + blocks.add(new BlockAndValidity(b82, true, false, b82.getHash(), chainHeadHeight + 28, "b82")); + spendableOutputs.offer(b82.getCoinbaseOutput()); HashSet post82Mempool = new HashSet(); post82Mempool.add(new InventoryItem(InventoryItem.Type.Transaction, b78tx.getHash())); @@ -1620,7 +1464,7 @@ public class FullBlockTestGenerator { // TransactionOutPointWithValue out28 = spendableOutputs.poll(); Preconditions.checkState(out28 != null); - Block b83 = createNextBlock(b82, chainHeadHeight + 29, null, null); + NewBlock b83 = createNextBlock(b82, chainHeadHeight + 29, null, null); { Transaction tx1 = new Transaction(params); tx1.addOutput(new TransactionOutput(params, tx1, out28.value, @@ -1634,11 +1478,8 @@ public class FullBlockTestGenerator { b83.addTransaction(tx2); } b83.solve(); - blocks.add(new BlockAndValidity(blockToHeightMap, hashHeaderMap, b83, true, false, b83.getHash(), chainHeadHeight + 29, "b83")); - spendableOutputs.offer(new TransactionOutPointWithValue( - new TransactionOutPoint(params, 0, b83.getTransactions().get(0).getHash()), - b83.getTransactions().get(0).getOutputs().get(0).getValue(), - b83.getTransactions().get(0).getOutputs().get(0).getScriptPubKey())); + blocks.add(new BlockAndValidity(b83, true, false, b83.getHash(), chainHeadHeight + 29, "b83")); + spendableOutputs.offer(b83.getCoinbaseOutput()); // Reorg on/off blocks that have OP_RETURN in them (and try to spend them) // -> b81 (26) -> b82 (27) -> b83 (28) -> b84 (29) -> b87 (30) -> b88 (31) @@ -1649,7 +1490,7 @@ public class FullBlockTestGenerator { TransactionOutPointWithValue out31 = spendableOutputs.poll(); Preconditions.checkState(out31 != null); TransactionOutPointWithValue out32 = spendableOutputs.poll(); Preconditions.checkState(out32 != null); - Block b84 = createNextBlock(b83, chainHeadHeight + 30, out29, null); + NewBlock b84 = createNextBlock(b83, chainHeadHeight + 30, out29, null); Transaction b84tx1 = new Transaction(params); { b84tx1.addOutput(new TransactionOutput(params, b84tx1, ZERO, new byte[]{OP_RETURN})); @@ -1657,9 +1498,7 @@ public class FullBlockTestGenerator { b84tx1.addOutput(new TransactionOutput(params, b84tx1, ZERO, new byte[]{OP_TRUE})); b84tx1.addOutput(new TransactionOutput(params, b84tx1, ZERO, new byte[]{OP_TRUE})); b84tx1.addOutput(new TransactionOutput(params, b84tx1, ZERO, new byte[]{OP_TRUE})); - addOnlyInputToTransaction(b84tx1, new TransactionOutPointWithValue( - new TransactionOutPoint(params, 1, b84.getTransactions().get(1).getHash()), - SATOSHI, b84.getTransactions().get(1).getOutputs().get(1).getScriptPubKey())); + addOnlyInputToTransaction(b84tx1, b84); b84.addTransaction(b84tx1); Transaction tx2 = new Transaction(params); @@ -1686,33 +1525,24 @@ public class FullBlockTestGenerator { b84.addTransaction(tx5); } b84.solve(); - blocks.add(new BlockAndValidity(blockToHeightMap, hashHeaderMap, b84, true, false, b84.getHash(), chainHeadHeight + 30, "b84")); - spendableOutputs.offer(new TransactionOutPointWithValue( - new TransactionOutPoint(params, 0, b84.getTransactions().get(0).getHash()), - b84.getTransactions().get(0).getOutputs().get(0).getValue(), - b84.getTransactions().get(0).getOutputs().get(0).getScriptPubKey())); + blocks.add(new BlockAndValidity(b84, true, false, b84.getHash(), chainHeadHeight + 30, "b84")); + spendableOutputs.offer(b84.getCoinbaseOutput()); - Block b85 = createNextBlock(b83, chainHeadHeight + 30, out29, null); - blocks.add(new BlockAndValidity(blockToHeightMap, hashHeaderMap, b85, true, false, b84.getHash(), chainHeadHeight + 30, "b85")); + NewBlock b85 = createNextBlock(b83, chainHeadHeight + 30, out29, null); + blocks.add(new BlockAndValidity(b85, true, false, b84.getHash(), chainHeadHeight + 30, "b85")); - Block b86 = createNextBlock(b85, chainHeadHeight + 31, out30, null); - blocks.add(new BlockAndValidity(blockToHeightMap, hashHeaderMap, b86, true, false, b86.getHash(), chainHeadHeight + 31, "b86")); + NewBlock b86 = createNextBlock(b85, chainHeadHeight + 31, out30, null); + blocks.add(new BlockAndValidity(b86, true, false, b86.getHash(), chainHeadHeight + 31, "b86")); - Block b87 = createNextBlock(b84, chainHeadHeight + 31, out30, null); - blocks.add(new BlockAndValidity(blockToHeightMap, hashHeaderMap, b87, true, false, b86.getHash(), chainHeadHeight + 31, "b87")); - spendableOutputs.offer(new TransactionOutPointWithValue( - new TransactionOutPoint(params, 0, b87.getTransactions().get(0).getHash()), - b87.getTransactions().get(0).getOutputs().get(0).getValue(), - b87.getTransactions().get(0).getOutputs().get(0).getScriptPubKey())); + NewBlock b87 = createNextBlock(b84, chainHeadHeight + 31, out30, null); + blocks.add(new BlockAndValidity(b87, true, false, b86.getHash(), chainHeadHeight + 31, "b87")); + spendableOutputs.offer(b87.getCoinbaseOutput()); - Block b88 = createNextBlock(b87, chainHeadHeight + 32, out31, null); - blocks.add(new BlockAndValidity(blockToHeightMap, hashHeaderMap, b88, true, false, b88.getHash(), chainHeadHeight + 32, "b88")); - spendableOutputs.offer(new TransactionOutPointWithValue( - new TransactionOutPoint(params, 0, b88.getTransactions().get(0).getHash()), - b88.getTransactions().get(0).getOutputs().get(0).getValue(), - b88.getTransactions().get(0).getOutputs().get(0).getScriptPubKey())); + NewBlock b88 = createNextBlock(b87, chainHeadHeight + 32, out31, null); + blocks.add(new BlockAndValidity(b88, true, false, b88.getHash(), chainHeadHeight + 32, "b88")); + spendableOutputs.offer(b88.getCoinbaseOutput()); - Block b89 = createNextBlock(b88, chainHeadHeight + 33, out32, null); + NewBlock b89 = createNextBlock(b88, chainHeadHeight + 33, out32, null); { Transaction tx = new Transaction(params); tx.addOutput(new TransactionOutput(params, tx, ZERO, new byte[] {OP_TRUE})); @@ -1720,39 +1550,87 @@ public class FullBlockTestGenerator { b89.addTransaction(tx); b89.solve(); } - blocks.add(new BlockAndValidity(blockToHeightMap, hashHeaderMap, b89, false, true, b88.getHash(), chainHeadHeight + 32, "b89")); + blocks.add(new BlockAndValidity(b89, false, true, b88.getHash(), chainHeadHeight + 32, "b89")); // The remaining tests arent designed to fit in the standard flow, and thus must always come last // Add new tests here. + //TODO: Explicitly address MoneyRange() checks + if (!runBarelyExpensiveTests) { + if (outStream != null) + outStream.close(); + + // (finally) return the created chain + return ret; + } + + // Test massive reorgs (in terms of block count/size) + // -> b81 (26) -> b82 (27) -> b83 (28) -> b84 (29) -> b87 (30) -> b88 (31) -> lots of blocks -> b1000 + // \-> b85 (29) -> b86 (30) \-> lots more blocks + // + NewBlock largeReorgFinal; + int LARGE_REORG_SIZE = 1008; // +/- a week of blocks + int largeReorgLastHeight = chainHeadHeight + 33 + LARGE_REORG_SIZE + 1; + { + NewBlock nextBlock = b88; + int nextHeight = chainHeadHeight + 33; + TransactionOutPointWithValue largeReorgOutput = out32; + for (int i = 0; i < LARGE_REORG_SIZE; i++) { + nextBlock = createNextBlock(nextBlock, nextHeight, largeReorgOutput, null); + Transaction tx = new Transaction(params); + byte[] outputScript = new byte[Block.MAX_BLOCK_SIZE - nextBlock.block.getMessageSize() - 65]; + Arrays.fill(outputScript, (byte) OP_FALSE); + tx.addOutput(new TransactionOutput(params, tx, ZERO, outputScript)); + addOnlyInputToTransaction(tx, nextBlock); + nextBlock.addTransaction(tx); + nextBlock.solve(); + blocks.add(new BlockAndValidity(nextBlock, true, false, nextBlock.getHash(), nextHeight++, "large reorg initial blocks " + i)); + spendableOutputs.offer(nextBlock.getCoinbaseOutput()); + largeReorgOutput = spendableOutputs.poll(); + } + NewBlock reorgBase = b88; + int reorgBaseHeight = chainHeadHeight + 33; + for (int i = 0; i < LARGE_REORG_SIZE; i++) { + reorgBase = createNextBlock(reorgBase, reorgBaseHeight++, null, null); + blocks.add(new BlockAndValidity(reorgBase, true, false, nextBlock.getHash(), nextHeight - 1, "large reorg reorg block " + i)); + } + reorgBase = createNextBlock(reorgBase, reorgBaseHeight, null, null); + blocks.add(new BlockAndValidity(reorgBase, true, false, reorgBase.getHash(), reorgBaseHeight, "large reorg reorging block")); + nextBlock = createNextBlock(nextBlock, nextHeight, null, null); + blocks.add(new BlockAndValidity(nextBlock, true, false, reorgBase.getHash(), nextHeight++, "large reorg second reorg initial")); + spendableOutputs.offer(nextBlock.getCoinbaseOutput()); + nextBlock = createNextBlock(nextBlock, nextHeight, null, null); spendableOutputs.poll(); + blocks.add(new BlockAndValidity(nextBlock, true, false, nextBlock.getHash(), nextHeight++, "large reorg second reorg")); + spendableOutputs.offer(nextBlock.getCoinbaseOutput()); + largeReorgFinal = nextBlock; + } + ret.maximumReorgBlockCount = Math.max(ret.maximumReorgBlockCount, LARGE_REORG_SIZE + 2); + // Test massive reorgs (in terms of tx count) // -> b60 (17) -> b64 (18) -> b65 (19) -> b69 (20) -> b72 (21) -> b1001 (22) -> lots of outputs -> lots of spends // Reorg back to: // -> b60 (17) -> b64 (18) -> b65 (19) -> b69 (20) -> b72 (21) -> b1001 (22) -> empty blocks // - Block b1001 = createNextBlock(b88, chainHeadHeight + 33, out32, null); - blocks.add(new BlockAndValidity(blockToHeightMap, hashHeaderMap, b1001, true, false, b1001.getHash(), chainHeadHeight + 33, "b1001")); - spendableOutputs.offer(new TransactionOutPointWithValue( - new TransactionOutPoint(params, 0, b1001.getTransactions().get(0).getHash()), - b1001.getTransactions().get(0).getOutputs().get(0).getValue(), - b1001.getTransactions().get(0).getOutputs().get(0).getScriptPubKey())); - int heightAfter1001 = chainHeadHeight + 34; - - if (runLargeReorgs) { + NewBlock b1001 = createNextBlock(largeReorgFinal, largeReorgLastHeight + 1, spendableOutputs.poll(), null); + blocks.add(new BlockAndValidity(b1001, true, false, b1001.getHash(), largeReorgLastHeight + 1, "b1001")); + spendableOutputs.offer(b1001.getCoinbaseOutput()); + int heightAfter1001 = largeReorgLastHeight + 2; + + if (runExpensiveTests) { // No way you can fit this test in memory Preconditions.checkArgument(blockStorageFile != null); - - Block lastBlock = b1001; - TransactionOutPoint lastOutput = new TransactionOutPoint(params, 2, b1001.getTransactions().get(1).getHash()); + + NewBlock lastBlock = b1001; + TransactionOutPoint lastOutput = new TransactionOutPoint(params, 1, b1001.block.getTransactions().get(1).getHash()); int blockCountAfter1001; int nextHeight = heightAfter1001; - + List hashesToSpend = new LinkedList(); // all index 0 final int TRANSACTION_CREATION_BLOCKS = 100; for (blockCountAfter1001 = 0; blockCountAfter1001 < TRANSACTION_CREATION_BLOCKS; blockCountAfter1001++) { - Block block = createNextBlock(lastBlock, nextHeight++, null, null); - while (block.getMessageSize() < Block.MAX_BLOCK_SIZE - 500) { + NewBlock block = createNextBlock(lastBlock, nextHeight++, null, null); + while (block.block.getMessageSize() < Block.MAX_BLOCK_SIZE - 500) { Transaction tx = new Transaction(params); tx.addInput(lastOutput.getHash(), lastOutput.getIndex(), OP_NOP_SCRIPT); tx.addOutput(ZERO, OP_TRUE_SCRIPT); @@ -1762,40 +1640,40 @@ public class FullBlockTestGenerator { block.addTransaction(tx); } block.solve(); - blocks.add(new BlockAndValidity(blockToHeightMap, hashHeaderMap, block, true, false, block.getHash(), nextHeight-1, - "post-b1001 repeated transaction generator " + blockCountAfter1001 + "/" + TRANSACTION_CREATION_BLOCKS).setSendOnce(true)); + blocks.add(new BlockAndValidity(block, true, false, block.getHash(), nextHeight-1, + "post-b1001 repeated transaction generator " + blockCountAfter1001 + "/" + TRANSACTION_CREATION_BLOCKS).setSendOnce(true)); lastBlock = block; } Iterator hashes = hashesToSpend.iterator(); for (int i = 0; hashes.hasNext(); i++) { - Block block = createNextBlock(lastBlock, nextHeight++, null, null); - while (block.getMessageSize() < Block.MAX_BLOCK_SIZE - 500 && hashes.hasNext()) { + NewBlock block = createNextBlock(lastBlock, nextHeight++, null, null); + while (block.block.getMessageSize() < Block.MAX_BLOCK_SIZE - 500 && hashes.hasNext()) { Transaction tx = new Transaction(params); tx.addInput(hashes.next(), 0, OP_NOP_SCRIPT); tx.addOutput(ZERO, OP_TRUE_SCRIPT); block.addTransaction(tx); } block.solve(); - blocks.add(new BlockAndValidity(blockToHeightMap, hashHeaderMap, block, true, false, block.getHash(), nextHeight-1, - "post-b1001 repeated transaction spender " + i).setSendOnce(true)); + blocks.add(new BlockAndValidity(block, true, false, block.getHash(), nextHeight-1, + "post-b1001 repeated transaction spender " + i).setSendOnce(true)); lastBlock = block; blockCountAfter1001++; } - + // Reorg back to b1001 + empty blocks Sha256Hash firstHash = lastBlock.getHash(); int height = nextHeight-1; nextHeight = heightAfter1001; lastBlock = b1001; for (int i = 0; i < blockCountAfter1001; i++) { - Block block = createNextBlock(lastBlock, nextHeight++, null, null); - blocks.add(new BlockAndValidity(blockToHeightMap, hashHeaderMap, block, true, false, firstHash, height, "post-b1001 empty reorg block " + i + "/" + blockCountAfter1001)); + NewBlock block = createNextBlock(lastBlock, nextHeight++, null, null); + blocks.add(new BlockAndValidity(block, true, false, firstHash, height, "post-b1001 empty reorg block " + i + "/" + blockCountAfter1001)); lastBlock = block; } - + // Try to spend from the other chain - Block b1002 = createNextBlock(lastBlock, nextHeight, null, null); + NewBlock b1002 = createNextBlock(lastBlock, nextHeight, null, null); { Transaction tx = new Transaction(params); tx.addInput(hashesToSpend.get(0), 0, OP_NOP_SCRIPT); @@ -1803,14 +1681,14 @@ public class FullBlockTestGenerator { b1002.addTransaction(tx); } b1002.solve(); - blocks.add(new BlockAndValidity(blockToHeightMap, hashHeaderMap, b1002, false, true, firstHash, height, "b1002")); - + blocks.add(new BlockAndValidity(b1002, false, true, firstHash, height, "b1002")); + // Now actually reorg - Block b1003 = createNextBlock(lastBlock, nextHeight, null, null); - blocks.add(new BlockAndValidity(blockToHeightMap, hashHeaderMap, b1003, true, false, b1003.getHash(), nextHeight, "b1003")); - + NewBlock b1003 = createNextBlock(lastBlock, nextHeight, null, null); + blocks.add(new BlockAndValidity(b1003, true, false, b1003.getHash(), nextHeight, "b1003")); + // Now try to spend again - Block b1004 = createNextBlock(b1003, nextHeight+1, null, null); + NewBlock b1004 = createNextBlock(b1003, nextHeight + 1, null, null); { Transaction tx = new Transaction(params); tx.addInput(hashesToSpend.get(0), 0, OP_NOP_SCRIPT); @@ -1818,21 +1696,21 @@ public class FullBlockTestGenerator { b1004.addTransaction(tx); } b1004.solve(); - blocks.add(new BlockAndValidity(blockToHeightMap, hashHeaderMap, b1004, false, true, b1003.getHash(), nextHeight, "b1004")); - + blocks.add(new BlockAndValidity(b1004, false, true, b1003.getHash(), nextHeight, "b1004")); + ret.maximumReorgBlockCount = Math.max(ret.maximumReorgBlockCount, blockCountAfter1001); } - + if (outStream != null) outStream.close(); - + // (finally) return the created chain return ret; } private byte uniquenessCounter = 0; - private Block createNextBlock(Block baseBlock, int nextBlockHeight, @Nullable TransactionOutPointWithValue prevOut, - Coin additionalCoinbaseValue) throws ScriptException { + private NewBlock createNextBlock(Block baseBlock, int nextBlockHeight, @Nullable TransactionOutPointWithValue prevOut, + Coin additionalCoinbaseValue) throws ScriptException { Integer height = blockToHeightMap.get(baseBlock.getHash()); if (height != null) checkState(height == nextBlockHeight - 1); @@ -1840,25 +1718,31 @@ public class FullBlockTestGenerator { .add((prevOut != null ? prevOut.value.subtract(SATOSHI) : ZERO)) .add(additionalCoinbaseValue == null ? ZERO : additionalCoinbaseValue); Block block = baseBlock.createNextBlockWithCoinbase(coinbaseOutKeyPubKey, coinbaseValue); + Transaction t = new Transaction(params); 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, ZERO, new byte[] {OP_PUSHDATA1 - 1, uniquenessCounter++})); - t.addOutput(new TransactionOutput(params, t, SATOSHI, - ScriptBuilder.createOutputScript(ECKey.fromPublicOnly(coinbaseOutKeyPubKey)).getProgram())); + t.addOutput(new TransactionOutput(params, t, ZERO, new byte[] {(byte)(new Random().nextInt() & 0xff), uniquenessCounter++})); // Spendable output - t.addOutput(new TransactionOutput(params, t, ZERO, new byte[] {OP_1})); + t.addOutput(new TransactionOutput(params, t, SATOSHI, new byte[] {OP_1})); addOnlyInputToTransaction(t, prevOut); block.addTransaction(t); block.solve(); } - return block; + return new NewBlock(block, prevOut == null ? null : new TransactionOutPointWithValue(t, 1)); } - + private NewBlock createNextBlock(NewBlock baseBlock, int nextBlockHeight, @Nullable TransactionOutPointWithValue prevOut, + Coin additionalCoinbaseValue) throws ScriptException { + return createNextBlock(baseBlock.block, nextBlockHeight, prevOut, additionalCoinbaseValue); + } + + private void addOnlyInputToTransaction(Transaction t, NewBlock block) throws ScriptException { + addOnlyInputToTransaction(t, block.getSpendableOutput(), TransactionInput.NO_SEQUENCE); + } + private void addOnlyInputToTransaction(Transaction t, TransactionOutPointWithValue prevOut) throws ScriptException { addOnlyInputToTransaction(t, prevOut, TransactionInput.NO_SEQUENCE); } - + private void addOnlyInputToTransaction(Transaction t, TransactionOutPointWithValue prevOut, long sequence) throws ScriptException { TransactionInput input = new TransactionInput(params, t, new byte[]{}, prevOut.outpoint); input.setSequenceNumber(sequence); @@ -1871,8 +1755,64 @@ public class FullBlockTestGenerator { checkState(prevOut.scriptPubKey.isSentToRawPubKey()); Sha256Hash hash = t.hashForSignature(0, prevOut.scriptPubKey, SigHash.ALL, false); input.setScriptSig(ScriptBuilder.createInputScript( - new TransactionSignature(coinbaseOutKey.sign(hash), SigHash.ALL, false)) + new TransactionSignature(coinbaseOutKey.sign(hash), SigHash.ALL, false)) ); } } + + /** + * Represents a block which is sent to the tested application and which the application must either reject or accept, + * depending on the flags in the rule + */ + class BlockAndValidity extends Rule { + Block block; + Sha256Hash blockHash; + boolean connects; + boolean throwsException; + boolean sendOnce; // We can throw away the memory for this block once we send it the first time (if bitcoind asks again, its broken) + Sha256Hash hashChainTipAfterBlock; + int heightAfterBlock; + + public BlockAndValidity(Block block, boolean connects, boolean throwsException, Sha256Hash hashChainTipAfterBlock, int heightAfterBlock, String blockName) { + super(blockName); + if (connects && throwsException) + throw new RuntimeException("A block cannot connect if an exception was thrown while adding it."); + this.block = block; + this.blockHash = block.getHash(); + this.connects = connects; + this.throwsException = throwsException; + this.hashChainTipAfterBlock = hashChainTipAfterBlock; + this.heightAfterBlock = heightAfterBlock; + + // Keep track of the set of blocks indexed by hash + hashHeaderMap.put(block.getHash(), block.cloneAsHeader()); + + // Double-check that we are always marking any given block at the same height + Integer height = blockToHeightMap.get(hashChainTipAfterBlock); + if (height != null) + checkState(height == heightAfterBlock); + else + blockToHeightMap.put(hashChainTipAfterBlock, heightAfterBlock); + } + + public BlockAndValidity(NewBlock block, boolean connects, boolean throwsException, Sha256Hash hashChainTipAfterBlock, int heightAfterBlock, String blockName) { + this(block.block, connects, throwsException, hashChainTipAfterBlock, heightAfterBlock, blockName); + coinbaseBlockMap.put(block.getCoinbaseOutput().outpoint.getHash(), block.getHash()); + Integer blockHeight = blockToHeightMap.get(block.block.getPrevBlockHash()); + if (blockHeight != null) { + blockHeight++; + for (Transaction t : block.block.getTransactions()) + for (TransactionInput in : t.getInputs()) { + Sha256Hash blockSpendingHash = coinbaseBlockMap.get(in.getOutpoint().getHash()); + checkState(blockSpendingHash == null || blockToHeightMap.get(blockSpendingHash) == null || + blockToHeightMap.get(blockSpendingHash) == blockHeight - params.getSpendableCoinbaseDepth()); + } + } + } + + public BlockAndValidity setSendOnce(boolean sendOnce) { + this.sendOnce = sendOnce; + return this; + } + } }