package com.google.digitalcoin.core;
import com.google.digitalcoin.core.Transaction.SigHash;
import com.google.common.base.Preconditions;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.util.*;
class BlockAndValidity {
Block block;
boolean connects;
boolean throwsException;
Sha256Hash hashChainTipAfterBlock;
String blockName;
public BlockAndValidity(Block block, boolean connects, boolean throwsException, Sha256Hash hashChainTipAfterBlock, String blockName) {
if (connects && throwsException)
throw new RuntimeException("A block cannot connect if an exception was thrown while adding it.");
this.block = block;
this.connects = connects;
this.throwsException = throwsException;
this.hashChainTipAfterBlock = hashChainTipAfterBlock;
this.blockName = blockName;
}
}
class TransactionOutPointWithValue {
public TransactionOutPoint outpoint;
public BigInteger value;
Script scriptPubKey;
public TransactionOutPointWithValue(TransactionOutPoint outpoint, BigInteger value, Script scriptPubKey) {
this.outpoint = outpoint;
this.value = value;
this.scriptPubKey = scriptPubKey;
}
}
public class FullBlockTestGenerator {
// Used by DigitalcoindComparisonTool and FullPrunedBlockChainTest to create test cases
private NetworkParameters params;
private ECKey coinbaseOutKey;
private byte[] coinbaseOutKeyPubKey;
public FullBlockTestGenerator(NetworkParameters params) {
this.params = params;
coinbaseOutKey = new ECKey();
coinbaseOutKeyPubKey = coinbaseOutKey.getPubKey();
Utils.rollMockClock(0); // Set a mock clock for timestamp tests
}
public List<BlockAndValidity> getBlocksToTest(boolean addExpensiveBlocks) throws ScriptException, ProtocolException, IOException {
List<BlockAndValidity> blocks = new LinkedList<BlockAndValidity>();
Queue<TransactionOutPointWithValue> spendableOutputs = new LinkedList<TransactionOutPointWithValue>();
int chainHeadHeight = 1;
Block chainHead = params.genesisBlock.createNextBlockWithCoinbase(coinbaseOutKeyPubKey);
blocks.add(new BlockAndValidity(chainHead, true, false, chainHead.getHash(), "Initial Block"));
spendableOutputs.offer(new TransactionOutPointWithValue(
new TransactionOutPoint(params, 0, chainHead.getTransactions().get(0).getHash()),
Utils.toNanoCoins(50, 0), chainHead.getTransactions().get(0).getOutputs().get(0).getScriptPubKey()));
for (int i = 1; i < params.getSpendableCoinbaseDepth(); i++) {
chainHead = chainHead.createNextBlockWithCoinbase(coinbaseOutKeyPubKey);
chainHeadHeight++;
blocks.add(new BlockAndValidity(chainHead, true, false, chainHead.getHash(), "Initial Block chain output generation"));
spendableOutputs.offer(new TransactionOutPointWithValue(
new TransactionOutPoint(params, 0, chainHead.getTransactions().get(0).getHash()),
Utils.toNanoCoins(50, 0), chainHead.getTransactions().get(0).getOutputs().get(0).getScriptPubKey()));
}
// Start by building a couple of blocks on top of the genesis block.
Block b1 = createNextBlock(chainHead, chainHeadHeight + 1, spendableOutputs.poll(), null);
blocks.add(new BlockAndValidity(b1, true, false, b1.getHash(), "b1"));
spendableOutputs.offer(new TransactionOutPointWithValue(
new TransactionOutPoint(params, 0, b1.getTransactions().get(0).getHash()),
b1.getTransactions().get(0).getOutputs().get(0).getValue(),
b1.getTransactions().get(0).getOutputs().get(0).getScriptPubKey()));
TransactionOutPointWithValue out1 = spendableOutputs.poll();
Block b2 = createNextBlock(b1, chainHeadHeight + 2, out1, null);
blocks.add(new BlockAndValidity(b2, true, false, b2.getHash(), "b2"));
// Make sure nothing funky happens if we try to re-add b2
blocks.add(new BlockAndValidity(b2, true, false, b2.getHash(), "b2"));
spendableOutputs.offer(new TransactionOutPointWithValue(
new TransactionOutPoint(params, 0, b2.getTransactions().get(0).getHash()),
b2.getTransactions().get(0).getOutputs().get(0).getValue(),
b2.getTransactions().get(0).getOutputs().get(0).getScriptPubKey()));
// We now have the following chain (which output is spent is in parentheses):
// genesis -> b1 (0) -> b2 (1)
//
// so fork like this:
//
// genesis -> b1 (0) -> b2 (1)
// \-> b3 (1)
//
// Nothing should happen at this point. We saw b2 first so it takes priority.
Block b3 = createNextBlock(b1, chainHeadHeight + 2, out1, null);
blocks.add(new BlockAndValidity(b3, true, false, b2.getHash(), "b3"));
// Make sure nothing breaks if we add b3 twice
blocks.add(new BlockAndValidity(b3, true, false, b2.getHash(), "b3"));
// Now we add another block to make the alternative chain longer.
TransactionOutPointWithValue out2 = spendableOutputs.poll();
Block b4 = createNextBlock(b3, chainHeadHeight + 3, out2, null);
blocks.add(new BlockAndValidity(b4, true, false, b4.getHash(), "b4"));
//
// genesis -> b1 (0) -> b2 (1)
// \-> b3 (1) -> b4 (2)
//
// ... and back to the first chain.
Block b5 = createNextBlock(b2, chainHeadHeight + 3, out2, null);
blocks.add(new BlockAndValidity(b5, true, false, b4.getHash(), "b5"));
spendableOutputs.offer(new TransactionOutPointWithValue(
new TransactionOutPoint(params, 0, b5.getTransactions().get(0).getHash()),
b5.getTransactions().get(0).getOutputs().get(0).getValue(),
b5.getTransactions().get(0).getOutputs().get(0).getScriptPubKey()));
TransactionOutPointWithValue out3 = spendableOutputs.poll();
Block b6 = createNextBlock(b5, chainHeadHeight + 4, out3, null);
blocks.add(new BlockAndValidity(b6, true, false, b6.getHash(), "b6"));
//
// genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3)
// \-> b3 (1) -> b4 (2)
//
// Try to create a fork that double-spends
// genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3)
// \-> b7 (2) -> b8 (4)
// \-> b3 (1) -> b4 (2)
//
Block b7 = createNextBlock(b5, chainHeadHeight + 5, out2, null);
blocks.add(new BlockAndValidity(b7, true, false, b6.getHash(), "b7"));
TransactionOutPointWithValue out4 = spendableOutputs.poll();
Block b8 = createNextBlock(b7, chainHeadHeight + 6, out4, null);
blocks.add(new BlockAndValidity(b8, false, true, b6.getHash(), "b8"));
// Try to create a block that has too much fee
// genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3)
// \-> b9 (4)
// \-> b3 (1) -> b4 (2)
//
Block b9 = createNextBlock(b6, chainHeadHeight + 5, out4, BigInteger.valueOf(1));
blocks.add(new BlockAndValidity(b9, false, true, b6.getHash(), "b9"));
// Create a fork that ends in a block with too much fee (the one that causes the reorg)
// genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3)
// \-> b10 (3) -> b11 (4)
// \-> b3 (1) -> b4 (2)
//
Block b10 = createNextBlock(b5, chainHeadHeight + 4, out3, null);
blocks.add(new BlockAndValidity(b10, true, false, b6.getHash(), "b10"));
Block b11 = createNextBlock(b10, chainHeadHeight + 5, out4, BigInteger.valueOf(1));
blocks.add(new BlockAndValidity(b11, false, true, b6.getHash(), "b11"));
// Try again, but with a valid fork first
// genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3)
// \-> b12 (3) -> b13 (4) -> b14 (5)
// (b12 added last)
// \-> b3 (1) -> b4 (2)
//
Block b12 = createNextBlock(b5, chainHeadHeight + 4, out3, null);
spendableOutputs.offer(new TransactionOutPointWithValue(
new TransactionOutPoint(params, 0, b12.getTransactions().get(0).getHash()),
b12.getTransactions().get(0).getOutputs().get(0).getValue(),
b12.getTransactions().get(0).getOutputs().get(0).getScriptPubKey()));
Block b13 = createNextBlock(b12, chainHeadHeight + 5, out4, null);
blocks.add(new BlockAndValidity(b13, false, false, b6.getHash(), "b13"));
// Make sure we dont die if an orphan gets added twice
blocks.add(new BlockAndValidity(b13, false, false, b6.getHash(), "b13"));
spendableOutputs.offer(new TransactionOutPointWithValue(
new TransactionOutPoint(params, 0, b13.getTransactions().get(0).getHash()),
b13.getTransactions().get(0).getOutputs().get(0).getValue(),
b13.getTransactions().get(0).getOutputs().get(0).getScriptPubKey()));
TransactionOutPointWithValue out5 = spendableOutputs.poll();
Block b14 = createNextBlock(b13, chainHeadHeight + 6, out5, BigInteger.valueOf(1));
// This will be "validly" added, though its actually invalid, it will just be marked orphan
// and will be discarded when an attempt is made to reorg to it.
// TODO: Use a WeakReference to check that it is freed properly after the reorg
blocks.add(new BlockAndValidity(b14, false, false, b6.getHash(), "b14"));
// Make sure we dont die if an orphan gets added twice
blocks.add(new BlockAndValidity(b14, false, false, b6.getHash(), "b14"));
blocks.add(new BlockAndValidity(b12, false, true, b13.getHash(), "b12"));
// Add a block with MAX_BLOCK_SIGOPS and one with one more sigop
// genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3)
// \-> b12 (3) -> b13 (4) -> b15 (5) -> b16 (6)
// \-> b3 (1) -> b4 (2)
//
Block b15 = createNextBlock(b13, chainHeadHeight + 6, out5, null);
{
int sigOps = 0;
for (Transaction tx : b15.transactions) {
sigOps += tx.getSigOpCount();
}
Transaction tx = new Transaction(params);
byte[] outputScript = new byte[Block.MAX_BLOCK_SIGOPS - sigOps];
Arrays.fill(outputScript, (byte)Script.OP_CHECKSIG);
tx.addOutput(new TransactionOutput(params, tx, BigInteger.valueOf(1), outputScript));
addOnlyInputToTransaction(tx, new TransactionOutPointWithValue(
new TransactionOutPoint(params, 1, b15.getTransactions().get(1).getHash()),
BigInteger.valueOf(1), b15.getTransactions().get(1).getOutputs().get(1).getScriptPubKey()));
b15.addTransaction(tx);
}
b15.solve();
blocks.add(new BlockAndValidity(b15, true, false, b15.getHash(), "b15"));
spendableOutputs.offer(new TransactionOutPointWithValue(
new TransactionOutPoint(params, 0, b15.getTransactions().get(0).getHash()),
b15.getTransactions().get(0).getOutputs().get(0).getValue(),
b15.getTransactions().get(0).getOutputs().get(0).getScriptPubKey()));
TransactionOutPointWithValue out6 = spendableOutputs.poll();
Block b16 = createNextBlock(b15, chainHeadHeight + 7, out6, null);
{
int sigOps = 0;
for (Transaction tx : b16.transactions) {
sigOps += tx.getSigOpCount();
}
Transaction tx = new Transaction(params);
byte[] outputScript = new byte[Block.MAX_BLOCK_SIGOPS - sigOps + 1];
Arrays.fill(outputScript, (byte)Script.OP_CHECKSIG);
tx.addOutput(new TransactionOutput(params, tx, BigInteger.valueOf(1), outputScript));
addOnlyInputToTransaction(tx, new TransactionOutPointWithValue(
new TransactionOutPoint(params, 1, b16.getTransactions().get(1).getHash()),
BigInteger.valueOf(1), b16.getTransactions().get(1).getOutputs().get(1).getScriptPubKey()));
b16.addTransaction(tx);
}
b16.solve();
blocks.add(new BlockAndValidity(b16, false, true, b15.getHash(), "b16"));
// Attempt to spend a transaction created on a different fork
// genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3)
// \-> b12 (3) -> b13 (4) -> b15 (5) -> b17 (6)
// \-> b3 (1) -> b4 (2)
//
Block b17 = createNextBlock(b15, chainHeadHeight + 7, out6, null);
{
Transaction tx = new Transaction(params);
tx.addOutput(new TransactionOutput(params, tx, BigInteger.valueOf(1), new byte[] {}));
addOnlyInputToTransaction(tx, new TransactionOutPointWithValue(
new TransactionOutPoint(params, 1, b3.getTransactions().get(1).getHash()),
BigInteger.valueOf(1), b3.getTransactions().get(1).getOutputs().get(1).getScriptPubKey()));
b17.addTransaction(tx);
}
b17.solve();
blocks.add(new BlockAndValidity(b17, false, true, b15.getHash(), "b17"));
// Attempt to spend a transaction created on a different fork (on a fork this time)
// genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3)
// \-> b12 (3) -> b13 (4) -> b15 (5)
// \-> b18 (5) -> b19 (6)
// \-> b3 (1) -> b4 (2)
//
Block b18 = createNextBlock(b13, chainHeadHeight + 6, out5, null);
{
Transaction tx = new Transaction(params);
tx.addOutput(new TransactionOutput(params, tx, BigInteger.valueOf(1), new byte[] {}));
addOnlyInputToTransaction(tx, new TransactionOutPointWithValue(
new TransactionOutPoint(params, 1, b3.getTransactions().get(1).getHash()),
BigInteger.valueOf(1), b3.getTransactions().get(1).getOutputs().get(1).getScriptPubKey()));
b18.addTransaction(tx);
}
b18.solve();
blocks.add(new BlockAndValidity(b18, true, false, b15.getHash(), "b17"));
Block b19 = createNextBlock(b18, chainHeadHeight + 7, out6, null);
blocks.add(new BlockAndValidity(b19, false, true, b15.getHash(), "b19"));
// Attempt to spend a coinbase at depth too low
// genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3)
// \-> b12 (3) -> b13 (4) -> b15 (5) -> b20 (7)
// \-> b3 (1) -> b4 (2)
//
TransactionOutPointWithValue out7 = spendableOutputs.poll();
Block b20 = createNextBlock(b15, chainHeadHeight + 7, out7, null);
blocks.add(new BlockAndValidity(b20, false, true, b15.getHash(), "b20"));
// Attempt to spend a coinbase at depth too low (on a fork this time)
// genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3)
// \-> b12 (3) -> b13 (4) -> b15 (5)
// \-> b21 (6) -> b22 (5)
// \-> b3 (1) -> b4 (2)
//
Block b21 = createNextBlock(b13, chainHeadHeight + 6, out6, null);
blocks.add(new BlockAndValidity(b21, true, false, b15.getHash(), "b21"));
Block b22 = createNextBlock(b21, chainHeadHeight + 7, out5, null);
blocks.add(new BlockAndValidity(b22, false, true, b15.getHash(), "b22"));
// Create a block on either side of MAX_BLOCK_SIZE and make sure its accepted/rejected
// genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3)
// \-> b12 (3) -> b13 (4) -> b15 (5) -> b23 (6)
// \-> b24 (6) -> b25 (7)
// \-> b3 (1) -> b4 (2)
//
Block b23 = createNextBlock(b15, chainHeadHeight + 7, out6, null);
{
Transaction tx = new Transaction(params);
// Signature size is non-deterministic, so it may take several runs before finding any off-by-one errors
byte[] outputScript = new byte[Block.MAX_BLOCK_SIZE - b23.getMessageSize() - 138];
Arrays.fill(outputScript, (byte)Script.OP_FALSE);
tx.addOutput(new TransactionOutput(params, tx, BigInteger.valueOf(1), outputScript));
addOnlyInputToTransaction(tx, new TransactionOutPointWithValue(
new TransactionOutPoint(params, 1, b23.getTransactions().get(1).getHash()),
BigInteger.valueOf(1), b23.getTransactions().get(1).getOutputs().get(1).getScriptPubKey()));
b23.addTransaction(tx);
}
b23.solve();
blocks.add(new BlockAndValidity(b23, true, false, b23.getHash(), "b23"));
spendableOutputs.offer(new TransactionOutPointWithValue(
new TransactionOutPoint(params, 0, b23.getTransactions().get(0).getHash()),
b23.getTransactions().get(0).getOutputs().get(0).getValue(),
b23.getTransactions().get(0).getOutputs().get(0).getScriptPubKey()));
Block b24 = createNextBlock(b15, chainHeadHeight + 7, out6, null);
{
Transaction tx = new Transaction(params);
// Signature size is non-deterministic, so it may take several runs before finding any off-by-one errors
byte[] outputScript = new byte[Block.MAX_BLOCK_SIZE - b24.getMessageSize() - 135];
Arrays.fill(outputScript, (byte)Script.OP_FALSE);
tx.addOutput(new TransactionOutput(params, tx, BigInteger.valueOf(1), outputScript));
addOnlyInputToTransaction(tx, new TransactionOutPointWithValue(
new TransactionOutPoint(params, 1, b24.getTransactions().get(1).getHash()),
BigInteger.valueOf(1), b24.getTransactions().get(1).getOutputs().get(1).getScriptPubKey()));
b24.addTransaction(tx);
}
b24.solve();
blocks.add(new BlockAndValidity(b24, false, true, b23.getHash(), "b24"));
// Extend the b24 chain to make sure digitalcoind isn't accepting b24
Block b25 = createNextBlock(b24, chainHeadHeight + 8, out7, null);
blocks.add(new BlockAndValidity(b25, false, false, b23.getHash(), "b25"));
// Create blocks with a coinbase input script size out of range
// genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3)
// \-> b12 (3) -> b13 (4) -> b15 (5) -> b23 (6) -> b30 (7)
// \-> ... (6) -> ... (7)
// \-> b3 (1) -> b4 (2)
//
Block b26 = createNextBlock(b15, chainHeadHeight + 7, out6, null);
// 1 is too small, but we already generate every other block with 2, so that is tested
b26.getTransactions().get(0).getInputs().get(0).setScriptBytes(new byte[] {0});
b26.setMerkleRoot(null);
b26.solve();
blocks.add(new BlockAndValidity(b26, false, true, b23.getHash(), "b26"));
// Extend the b26 chain to make sure digitalcoind isn't accepting b26
Block b27 = createNextBlock(b26, chainHeadHeight + 8, out7, null);
blocks.add(new BlockAndValidity(b27, false, false, b23.getHash(), "b27"));
Block b28 = createNextBlock(b15, chainHeadHeight + 7, out6, null);
{
byte[] coinbase = new byte[101];
Arrays.fill(coinbase, (byte)0);
b28.getTransactions().get(0).getInputs().get(0).setScriptBytes(coinbase);
}
b28.setMerkleRoot(null);
b28.solve();
blocks.add(new BlockAndValidity(b28, false, true, b23.getHash(), "b28"));
// Extend the b28 chain to make sure digitalcoind isn't accepting b28
Block b29 = createNextBlock(b28, chainHeadHeight + 8, out7, null);
blocks.add(new BlockAndValidity(b29, false, false, b23.getHash(), "b29"));
Block b30 = createNextBlock(b23, chainHeadHeight + 8, out7, null);
{
byte[] coinbase = new byte[100];
Arrays.fill(coinbase, (byte)0);
b30.getTransactions().get(0).getInputs().get(0).setScriptBytes(coinbase);
}
b30.setMerkleRoot(null);
b30.solve();
blocks.add(new BlockAndValidity(b30, true, false, b30.getHash(), "b30"));
spendableOutputs.offer(new TransactionOutPointWithValue(
new TransactionOutPoint(params, 0, b30.getTransactions().get(0).getHash()),
b30.getTransactions().get(0).getOutputs().get(0).getValue(),
b30.getTransactions().get(0).getOutputs().get(0).getScriptPubKey()));
// Check sigops of OP_CHECKMULTISIG/OP_CHECKMULTISIGVERIFY/OP_CHECKSIGVERIFY
// 6 (3)
// 12 (3) -> b13 (4) -> b15 (5) -> b23 (6) -> b30 (7) -> b31 (8) -> b33 (9) -> b35 (10)
// \-> b36 (11)
// \-> b34 (10)
// \-> b32 (9)
//
TransactionOutPointWithValue out8 = spendableOutputs.poll();
Block b31 = createNextBlock(b30, chainHeadHeight + 9, out8, null);
{
int sigOps = 0;
for (Transaction tx : b31.transactions) {
sigOps += tx.getSigOpCount();
}
Transaction tx = new Transaction(params);
byte[] outputScript = new byte[(Block.MAX_BLOCK_SIGOPS - sigOps)/20];
Arrays.fill(outputScript, (byte)Script.OP_CHECKMULTISIG);
tx.addOutput(new TransactionOutput(params, tx, BigInteger.valueOf(1), outputScript));
addOnlyInputToTransaction(tx, new TransactionOutPointWithValue(
new TransactionOutPoint(params, 1, b31.getTransactions().get(1).getHash()),
BigInteger.valueOf(1), b31.getTransactions().get(1).getOutputs().get(1).getScriptPubKey()));
b31.addTransaction(tx);
}
b31.solve();
blocks.add(new BlockAndValidity(b31, true, false, b31.getHash(), "b31"));
spendableOutputs.offer(new TransactionOutPointWithValue(
new TransactionOutPoint(params, 0, b31.getTransactions().get(0).getHash()),
b31.getTransactions().get(0).getOutputs().get(0).getValue(),
b31.getTransactions().get(0).getOutputs().get(0).getScriptPubKey()));
TransactionOutPointWithValue out9 = spendableOutputs.poll();
Block b32 = createNextBlock(b31, chainHeadHeight + 10, out9, null);
{
int sigOps = 0;
for (Transaction tx : b32.transactions) {
sigOps += tx.getSigOpCount();
}
Transaction tx = new Transaction(params);
byte[] outputScript = new byte[(Block.MAX_BLOCK_SIGOPS - sigOps)/20 + (Block.MAX_BLOCK_SIGOPS - sigOps)%20 + 1];
Arrays.fill(outputScript, (byte)Script.OP_CHECKMULTISIG);
for (int i = 0; i < (Block.MAX_BLOCK_SIGOPS - sigOps)%20; i++)
outputScript[i] = (byte)Script.OP_CHECKSIG;
tx.addOutput(new TransactionOutput(params, tx, BigInteger.valueOf(1), outputScript));
addOnlyInputToTransaction(tx, new TransactionOutPointWithValue(
new TransactionOutPoint(params, 1, b32.getTransactions().get(1).getHash()),
BigInteger.valueOf(1), b32.getTransactions().get(1).getOutputs().get(1).getScriptPubKey()));
b32.addTransaction(tx);
}
b32.solve();
blocks.add(new BlockAndValidity(b32, false, true, b31.getHash(), "b32"));
Block b33 = createNextBlock(b31, chainHeadHeight + 10, out9, null);
{
int sigOps = 0;
for (Transaction tx : b33.transactions) {
sigOps += tx.getSigOpCount();
}
Transaction tx = new Transaction(params);
byte[] outputScript = new byte[(Block.MAX_BLOCK_SIGOPS - sigOps)/20];
Arrays.fill(outputScript, (byte)Script.OP_CHECKMULTISIGVERIFY);
tx.addOutput(new TransactionOutput(params, tx, BigInteger.valueOf(1), outputScript));
addOnlyInputToTransaction(tx, new TransactionOutPointWithValue(
new TransactionOutPoint(params, 1, b33.getTransactions().get(1).getHash()),
BigInteger.valueOf(1), b33.getTransactions().get(1).getOutputs().get(1).getScriptPubKey()));
b33.addTransaction(tx);
}
b33.solve();
blocks.add(new BlockAndValidity(b33, true, false, b33.getHash(), "b33"));
spendableOutputs.offer(new TransactionOutPointWithValue(
new TransactionOutPoint(params, 0, b33.getTransactions().get(0).getHash()),
b33.getTransactions().get(0).getOutputs().get(0).getValue(),
b33.getTransactions().get(0).getOutputs().get(0).getScriptPubKey()));
TransactionOutPointWithValue out10 = spendableOutputs.poll();
Block b34 = createNextBlock(b33, chainHeadHeight + 11, out10, null);
{
int sigOps = 0;
for (Transaction tx : b34.transactions) {
sigOps += tx.getSigOpCount();
}
Transaction tx = new Transaction(params);
byte[] outputScript = new byte[(Block.MAX_BLOCK_SIGOPS - sigOps)/20 + (Block.MAX_BLOCK_SIGOPS - sigOps)%20 + 1];
Arrays.fill(outputScript, (byte)Script.OP_CHECKMULTISIGVERIFY);
for (int i = 0; i < (Block.MAX_BLOCK_SIGOPS - sigOps)%20; i++)
outputScript[i] = (byte)Script.OP_CHECKSIG;
tx.addOutput(new TransactionOutput(params, tx, BigInteger.valueOf(1), outputScript));
addOnlyInputToTransaction(tx, new TransactionOutPointWithValue(
new TransactionOutPoint(params, 1, b34.getTransactions().get(1).getHash()),
BigInteger.valueOf(1), b34.getTransactions().get(1).getOutputs().get(1).getScriptPubKey()));
b34.addTransaction(tx);
}
b34.solve();
blocks.add(new BlockAndValidity(b34, false, true, b33.getHash(), "b34"));
Block b35 = createNextBlock(b33, chainHeadHeight + 11, out10, null);
{
int sigOps = 0;
for (Transaction tx : b35.transactions) {
sigOps += tx.getSigOpCount();
}
Transaction tx = new Transaction(params);
byte[] outputScript = new byte[Block.MAX_BLOCK_SIGOPS - sigOps];
Arrays.fill(outputScript, (byte)Script.OP_CHECKSIGVERIFY);
tx.addOutput(new TransactionOutput(params, tx, BigInteger.valueOf(1), outputScript));
addOnlyInputToTransaction(tx, new TransactionOutPointWithValue(
new TransactionOutPoint(params, 1, b35.getTransactions().get(1).getHash()),
BigInteger.valueOf(1), b35.getTransactions().get(1).getOutputs().get(1).getScriptPubKey()));
b35.addTransaction(tx);
}
b35.solve();
blocks.add(new BlockAndValidity(b35, true, false, b35.getHash(), "b35"));
spendableOutputs.offer(new TransactionOutPointWithValue(
new TransactionOutPoint(params, 0, b35.getTransactions().get(0).getHash()),
b35.getTransactions().get(0).getOutputs().get(0).getValue(),
b35.getTransactions().get(0).getOutputs().get(0).getScriptPubKey()));
TransactionOutPointWithValue out11 = spendableOutputs.poll();
Block b36 = createNextBlock(b35, chainHeadHeight + 12, out11, null);
{
int sigOps = 0;
for (Transaction tx : b36.transactions) {
sigOps += tx.getSigOpCount();
}
Transaction tx = new Transaction(params);
byte[] outputScript = new byte[Block.MAX_BLOCK_SIGOPS - sigOps + 1];
Arrays.fill(outputScript, (byte)Script.OP_CHECKSIGVERIFY);
tx.addOutput(new TransactionOutput(params, tx, BigInteger.valueOf(1), outputScript));
addOnlyInputToTransaction(tx, new TransactionOutPointWithValue(
new TransactionOutPoint(params, 1, b36.getTransactions().get(1).getHash()),
BigInteger.valueOf(1), b36.getTransactions().get(1).getOutputs().get(1).getScriptPubKey()));
b36.addTransaction(tx);
}
b36.solve();
blocks.add(new BlockAndValidity(b36, false, true, b35.getHash(), "b36"));
// Check spending of a transaction in a block which failed to connect
// (test block store transaction abort handling, not that it should get this far if that's broken...)
// 6 (3)
// 12 (3) -> b13 (4) -> b15 (5) -> b23 (6) -> b30 (7) -> b31 (8) -> b33 (9) -> b35 (10)
// \-> b37 (11)
// \-> b38 (11)
//
Block b37 = createNextBlock(b35, chainHeadHeight + 10, out11, null);
{
Transaction tx = new Transaction(params);
tx.addOutput(new TransactionOutput(params, tx, BigInteger.valueOf(1), new byte[] {}));
addOnlyInputToTransaction(tx, out11); // double spend out11
b37.addTransaction(tx);
}
b37.solve();
blocks.add(new BlockAndValidity(b37, false, true, b35.getHash(), "b37"));
Block b38 = createNextBlock(b35, chainHeadHeight + 10, out11, null);
{
Transaction tx = new Transaction(params);
tx.addOutput(new TransactionOutput(params, tx, BigInteger.valueOf(1), new byte[] {}));
// Attempt to spend b37's first non-coinbase tx, at which point b37 was still considered valid
addOnlyInputToTransaction(tx, new TransactionOutPointWithValue(
new TransactionOutPoint(params, 1, b37.getTransactions().get(1).getHash()),
BigInteger.valueOf(1), b37.getTransactions().get(1).getOutputs().get(1).getScriptPubKey()));
b38.addTransaction(tx);
}
b38.solve();
blocks.add(new BlockAndValidity(b38, false, true, b35.getHash(), "b38"));
// Check P2SH SigOp counting
// 13 (4) -> b15 (5) -> b23 (6) -> b30 (7) -> b31 (8) -> b33 (9) -> b35 (10) -> b39 (11) -> b41 (12)
// \-> b40 (12)
//
// Create some P2SH outputs that will require 6 sigops to spend
byte[] b39p2shScriptPubKey;
int b39numP2SHOutputs = 0, b39sigOpsPerOutput = 6;
Block b39 = createNextBlock(b35, chainHeadHeight + 10, null, null);
{
ByteArrayOutputStream p2shScriptPubKey = new UnsafeByteArrayOutputStream();
try {
Script.writeBytes(p2shScriptPubKey, coinbaseOutKeyPubKey);
p2shScriptPubKey.write(Script.OP_2DUP);
p2shScriptPubKey.write(Script.OP_CHECKSIGVERIFY);
p2shScriptPubKey.write(Script.OP_2DUP);
p2shScriptPubKey.write(Script.OP_CHECKSIGVERIFY);
p2shScriptPubKey.write(Script.OP_2DUP);
p2shScriptPubKey.write(Script.OP_CHECKSIGVERIFY);
p2shScriptPubKey.write(Script.OP_2DUP);
p2shScriptPubKey.write(Script.OP_CHECKSIGVERIFY);
p2shScriptPubKey.write(Script.OP_2DUP);
p2shScriptPubKey.write(Script.OP_CHECKSIGVERIFY);
p2shScriptPubKey.write(Script.OP_CHECKSIG);
} catch (IOException e) {
throw new RuntimeException(e); // Cannot happen.
}
b39p2shScriptPubKey = p2shScriptPubKey.toByteArray();
byte[] scriptHash = Utils.sha256hash160(b39p2shScriptPubKey);
UnsafeByteArrayOutputStream scriptPubKey = new UnsafeByteArrayOutputStream(scriptHash.length + 3);
scriptPubKey.write(Script.OP_HASH160);
try {
Script.writeBytes(scriptPubKey, scriptHash);
} catch (IOException e) {
throw new RuntimeException(e); // Cannot happen.
}
scriptPubKey.write(Script.OP_EQUAL);
BigInteger lastOutputValue = out11.value.subtract(BigInteger.valueOf(1));
TransactionOutPoint lastOutPoint;
{
Transaction tx = new Transaction(params);
tx.addOutput(new TransactionOutput(params, tx, BigInteger.valueOf(1), scriptPubKey.toByteArray()));
tx.addOutput(new TransactionOutput(params, tx, lastOutputValue, new byte[]{Script.OP_1}));
addOnlyInputToTransaction(tx, out11);
lastOutPoint = new TransactionOutPoint(params, 1, tx.getHash());
b39.addTransaction(tx);
}
b39numP2SHOutputs++;
while (b39.getMessageSize() < Block.MAX_BLOCK_SIZE)
{
Transaction tx = new Transaction(params);
lastOutputValue = lastOutputValue.subtract(BigInteger.valueOf(1));
tx.addOutput(new TransactionOutput(params, tx, BigInteger.valueOf(1), scriptPubKey.toByteArray()));
tx.addOutput(new TransactionOutput(params, tx, lastOutputValue, new byte[]{Script.OP_1}));
tx.addInput(new TransactionInput(params, tx, new byte[]{Script.OP_1}, lastOutPoint));
lastOutPoint = new TransactionOutPoint(params, 1, tx.getHash());
if (b39.getMessageSize() + tx.getMessageSize() < Block.MAX_BLOCK_SIZE) {
b39.addTransaction(tx);
b39numP2SHOutputs++;
} else
break;
}
}
b39.solve();
blocks.add(new BlockAndValidity(b39, true, false, b39.getHash(), "b39"));
spendableOutputs.offer(new TransactionOutPointWithValue(
new TransactionOutPoint(params, 0, b39.getTransactions().get(0).getHash()),
b39.getTransactions().get(0).getOutputs().get(0).getValue(),
b39.getTransactions().get(0).getOutputs().get(0).getScriptPubKey()));
TransactionOutPointWithValue out12 = spendableOutputs.poll();
Block b40 = createNextBlock(b39, chainHeadHeight + 11, out12, null);
{
int sigOps = 0;
for (Transaction tx : b40.transactions) {
sigOps += tx.getSigOpCount();
}
int numTxes = (Block.MAX_BLOCK_SIGOPS - sigOps) / b39sigOpsPerOutput;
Preconditions.checkState(numTxes <= b39numP2SHOutputs);
TransactionOutPoint lastOutPoint = new TransactionOutPoint(params, 2, b40.getTransactions().get(1).getHash());
byte[] scriptSig = null;
for (int i = 1; i <= numTxes; i++) {
Transaction tx = new Transaction(params);
tx.addOutput(new TransactionOutput(params, tx, BigInteger.valueOf(1), new byte[] {Script.OP_1}));
tx.addInput(new TransactionInput(params, tx, new byte[]{Script.OP_1}, lastOutPoint));
TransactionInput input = new TransactionInput(params, tx, new byte[]{},
new TransactionOutPoint(params, 0, b39.getTransactions().get(i).getHash()));
tx.addInput(input);
if (scriptSig == null) {
// Exploit the SigHash.SINGLE bug to avoid having to make more than one signature
Sha256Hash hash = tx.hashTransactionForSignature(1, b39p2shScriptPubKey, SigHash.SINGLE, false);
// Sign input
try {
ByteArrayOutputStream bos = new UnsafeByteArrayOutputStream(73);
bos.write(coinbaseOutKey.sign(hash).encodeToDER());
bos.write(SigHash.SINGLE.ordinal() + 1);
byte[] signature = bos.toByteArray();
ByteArrayOutputStream scriptSigBos = new UnsafeByteArrayOutputStream(signature.length + b39p2shScriptPubKey.length + 3);
Script.writeBytes(scriptSigBos, new byte[] {(byte) Script.OP_CHECKSIG});
scriptSigBos.write(Script.createInputScript(signature));
Script.writeBytes(scriptSigBos, b39p2shScriptPubKey);
scriptSig = scriptSigBos.toByteArray();
} catch (IOException e) {
throw new RuntimeException(e); // Cannot happen.
}
}
input.setScriptBytes(scriptSig);
lastOutPoint = new TransactionOutPoint(params, 0, tx.getHash());
b40.addTransaction(tx);
}
sigOps += numTxes * b39sigOpsPerOutput;
Transaction tx = new Transaction(params);
tx.addInput(new TransactionInput(params, tx, new byte[]{Script.OP_1}, lastOutPoint));
byte[] scriptPubKey = new byte[Block.MAX_BLOCK_SIGOPS - sigOps + 1];
Arrays.fill(scriptPubKey, (byte)Script.OP_CHECKSIG);
tx.addOutput(new TransactionOutput(params, tx, BigInteger.ZERO, scriptPubKey));
b40.addTransaction(tx);
}
b40.solve();
blocks.add(new BlockAndValidity(b40, false, true, b39.getHash(), "b40"));
Block b41 = null;
if (addExpensiveBlocks) {
b41 = createNextBlock(b39, chainHeadHeight + 11, out12, null);
{
int sigOps = 0;
for (Transaction tx : b41.transactions) {
sigOps += tx.getSigOpCount();
}
int numTxes = (Block.MAX_BLOCK_SIGOPS - sigOps)
/ b39sigOpsPerOutput;
Preconditions.checkState(numTxes <= b39numP2SHOutputs);
TransactionOutPoint lastOutPoint = new TransactionOutPoint(
params, 2, b41.getTransactions().get(1).getHash());
byte[] scriptSig = null;
for (int i = 1; i <= numTxes; i++) {
Transaction tx = new Transaction(params);
tx.addOutput(new TransactionOutput(params, tx, BigInteger
.valueOf(1), new byte[] { Script.OP_1 }));
tx.addInput(new TransactionInput(params, tx,
new byte[] { Script.OP_1 }, lastOutPoint));
TransactionInput input = new TransactionInput(params, tx,
new byte[] {}, new TransactionOutPoint(params, 0,
b39.getTransactions().get(i).getHash()));
tx.addInput(input);
if (scriptSig == null) {
// Exploit the SigHash.SINGLE bug to avoid having to make more than one signature
Sha256Hash hash = tx.hashTransactionForSignature(1,
b39p2shScriptPubKey, SigHash.SINGLE, false);
// Sign input
try {
ByteArrayOutputStream bos = new UnsafeByteArrayOutputStream(
73);
bos.write(coinbaseOutKey.sign(hash).encodeToDER());
bos.write(SigHash.SINGLE.ordinal() + 1);
byte[] signature = bos.toByteArray();
ByteArrayOutputStream scriptSigBos = new UnsafeByteArrayOutputStream(
signature.length
+ b39p2shScriptPubKey.length + 3);
Script.writeBytes(scriptSigBos,
new byte[] { (byte) Script.OP_CHECKSIG });
scriptSigBos.write(Script
.createInputScript(signature));
Script.writeBytes(scriptSigBos, b39p2shScriptPubKey);
scriptSig = scriptSigBos.toByteArray();
} catch (IOException e) {
throw new RuntimeException(e); // Cannot happen.
}
}
input.setScriptBytes(scriptSig);
lastOutPoint = new TransactionOutPoint(params, 0,
tx.getHash());
b41.addTransaction(tx);
}
sigOps += numTxes * b39sigOpsPerOutput;
Transaction tx = new Transaction(params);
tx.addInput(new TransactionInput(params, tx,
new byte[] { Script.OP_1 }, lastOutPoint));
byte[] scriptPubKey = new byte[Block.MAX_BLOCK_SIGOPS - sigOps];
Arrays.fill(scriptPubKey, (byte) Script.OP_CHECKSIG);
tx.addOutput(new TransactionOutput(params, tx, BigInteger.ZERO, scriptPubKey));
b41.addTransaction(tx);
}
b41.solve();
blocks.add(new BlockAndValidity(b41, true, false, b41.getHash(), "b41"));
}
// Fork off of b39 to create a constant base again
// b23 (6) -> b30 (7) -> b31 (8) -> b33 (9) -> b35 (10) -> b39 (11) -> b42 (12) -> b43 (13)
// \-> b41 (12)
//
Block b42 = createNextBlock(b39, chainHeadHeight + 11, out12, null);
blocks.add(new BlockAndValidity(b42, true, false, b41 == null ? b42.getHash() : b41.getHash(), "b42"));
spendableOutputs.offer(new TransactionOutPointWithValue(
new TransactionOutPoint(params, 0, b42.getTransactions().get(0).getHash()),
b42.getTransactions().get(0).getOutputs().get(0).getValue(),
b42.getTransactions().get(0).getOutputs().get(0).getScriptPubKey()));
TransactionOutPointWithValue out13 = spendableOutputs.poll();
Block b43 = createNextBlock(b42, chainHeadHeight + 12, out13, null);
blocks.add(new BlockAndValidity(b43, true, false, b43.getHash(), "b43"));
spendableOutputs.offer(new TransactionOutPointWithValue(
new TransactionOutPoint(params, 0, b43.getTransactions().get(0).getHash()),
b43.getTransactions().get(0).getOutputs().get(0).getValue(),
b43.getTransactions().get(0).getOutputs().get(0).getScriptPubKey()));
// Test a number of really invalid scenarios
// -> b31 (8) -> b33 (9) -> b35 (10) -> b39 (11) -> b42 (12) -> b43 (13) -> b44 (14)
// \-> ??? (15)
//
TransactionOutPointWithValue out14 = spendableOutputs.poll();
// A valid block created exactly like b44 to make sure the creation itself works
Block b44 = new Block(params);
{
b44.setDifficultyTarget(b43.getDifficultyTarget());
b44.addCoinbaseTransaction(coinbaseOutKeyPubKey, BigInteger.ZERO);
Transaction t = new Transaction(params);
// Entirely invalid scriptPubKey to ensure we aren't pre-verifying too much
t.addOutput(new TransactionOutput(params, t, BigInteger.valueOf(0), new byte[] { Script.OP_PUSHDATA1 - 1 }));
t.addOutput(new TransactionOutput(params, t, BigInteger.valueOf(1), Script.createOutputScript(coinbaseOutKeyPubKey)));
// Spendable output
t.addOutput(new TransactionOutput(params, t, BigInteger.ZERO, new byte[] {Script.OP_1}));
addOnlyInputToTransaction(t, out14);
b44.addTransaction(t);
b44.setPrevBlockHash(b43.getHash());
b44.setTime(b43.getTimeSeconds() + 1);
}
b44.solve();
blocks.add(new BlockAndValidity(b44, true, false, b44.getHash(), "b44"));
TransactionOutPointWithValue out15 = spendableOutputs.poll();
// A block with a non-coinbase as the first tx
Block b45 = new Block(params);
{
b45.setDifficultyTarget(b44.getDifficultyTarget());
//b45.addCoinbaseTransaction(pubKey, coinbaseValue);
Transaction t = new Transaction(params);
// Entirely invalid scriptPubKey to ensure we aren't pre-verifying too much
t.addOutput(new TransactionOutput(params, t, BigInteger.valueOf(0), new byte[] { Script.OP_PUSHDATA1 - 1 }));
t.addOutput(new TransactionOutput(params, t, BigInteger.valueOf(1), Script.createOutputScript(coinbaseOutKeyPubKey)));
// Spendable output
t.addOutput(new TransactionOutput(params, t, BigInteger.ZERO, new byte[] {Script.OP_1}));
addOnlyInputToTransaction(t, out15);
try {
b45.addTransaction(t);
} catch (RuntimeException e) { } // Should happen
if (b45.getTransactions().size() > 0)
throw new RuntimeException("addTransaction doesn't properly check for adding a non-coinbase as first tx");
b45.addTransaction(t, false);
b45.setPrevBlockHash(b44.getHash());
b45.setTime(b44.getTimeSeconds() + 1);
}
b45.solve();
blocks.add(new BlockAndValidity(b45, false, true, b44.getHash(), "b45"));
// A block with no txn
Block b46 = new Block(params);
{
b46.transactions = new ArrayList<Transaction>();
b46.setDifficultyTarget(b44.getDifficultyTarget());
b46.setMerkleRoot(Sha256Hash.ZERO_HASH);
b46.setPrevBlockHash(b44.getHash());
b46.setTime(b44.getTimeSeconds() + 1);
}
b46.solve();
blocks.add(new BlockAndValidity(b46, false, true, b44.getHash(), "b46"));
// A block with invalid work
Block b47 = createNextBlock(b44, chainHeadHeight + 14, out15, null);
{
try {
// Inverse solve
BigInteger target = b47.getDifficultyTargetAsInteger();
while (true) {
BigInteger h = b47.getHash().toBigInteger();
if (h.compareTo(target) > 0) // if invalid
break;
// increment the nonce and try again.
b47.setNonce(b47.getNonce() + 1);
}
} catch (VerificationException e) {
throw new RuntimeException(e); // Cannot happen.
}
}
blocks.add(new BlockAndValidity(b47, false, true, b44.getHash(), "b47"));
// Block with timestamp > 2h in the future
Block b48 = createNextBlock(b44, chainHeadHeight + 14, out15, null);
b48.setTime(Utils.now().getTime() / 1000 + 60*60*3);
b48.solve();
blocks.add(new BlockAndValidity(b48, false, true, b44.getHash(), "b48"));
// Block with invalid merkle hash
Block b49 = createNextBlock(b44, chainHeadHeight + 14, out15, null);
b49.setMerkleRoot(Sha256Hash.ZERO_HASH);
b49.solve();
blocks.add(new BlockAndValidity(b49, false, true, b44.getHash(), "b49"));
// Block with incorrect POW limit
Block b50 = createNextBlock(b44, chainHeadHeight + 14, out15, null);
{
long diffTarget = b44.getDifficultyTarget();
diffTarget &= 0xFFBFFFFF; // Make difficulty one bit harder
b50.setDifficultyTarget(diffTarget);
}
b50.solve();
blocks.add(new BlockAndValidity(b50, false, true, b44.getHash(), "b50"));
// A block with two coinbase txn
Block b51 = createNextBlock(b44, chainHeadHeight + 14, out15, null);
{
Transaction coinbase = new Transaction(params);
coinbase.addInput(new TransactionInput(params, coinbase, new byte[]{(byte) 0xff, 110, 1}));
coinbase.addOutput(new TransactionOutput(params, coinbase, BigInteger.ONE, Script.createOutputScript(coinbaseOutKeyPubKey)));
b51.addTransaction(coinbase, false);
}
b51.solve();
blocks.add(new BlockAndValidity(b51, false, true, b44.getHash(), "b51"));
// A block with duplicate txn
Block b52 = createNextBlock(b44, chainHeadHeight + 14, out15, null);
{
Transaction tx = new Transaction(params);
tx.addOutput(new TransactionOutput(params, tx, BigInteger.valueOf(1), new byte[] {}));
addOnlyInputToTransaction(tx, new TransactionOutPointWithValue(
new TransactionOutPoint(params, 1, b52.getTransactions().get(1).getHash()),
BigInteger.valueOf(1), b52.getTransactions().get(1).getOutputs().get(1).getScriptPubKey()));
b52.addTransaction(tx);
b52.addTransaction(tx);
}
b52.solve();
blocks.add(new BlockAndValidity(b52, false, true, b44.getHash(), "b52"));
// Test block timestamp
// -> b31 (8) -> b33 (9) -> b35 (10) -> b39 (11) -> b42 (12) -> b43 (13) -> b53 (14) -> b55 (15)
// \-> b54 (15)
// \-> b44 (14)
//
Block b53 = createNextBlock(b43, chainHeadHeight + 13, out14, null);
blocks.add(new BlockAndValidity(b53, true, false, b44.getHash(), "b53"));
spendableOutputs.offer(new TransactionOutPointWithValue(
new TransactionOutPoint(params, 0, b53.getTransactions().get(0).getHash()),
b53.getTransactions().get(0).getOutputs().get(0).getValue(),
b53.getTransactions().get(0).getOutputs().get(0).getScriptPubKey()));
// Block with invalid timestamp
Block b54 = createNextBlock(b53, chainHeadHeight + 14, out15, null);
b54.setTime(b35.getTimeSeconds() - 1);
b54.solve();
blocks.add(new BlockAndValidity(b54, false, true, b44.getHash(), "b54"));
// Block with valid timestamp
Block b55 = createNextBlock(b53, chainHeadHeight + 14, out15, null);
b55.setTime(b35.getTimeSeconds());
b55.solve();
blocks.add(new BlockAndValidity(b55, true, false, b55.getHash(), "b55"));
spendableOutputs.offer(new TransactionOutPointWithValue(
new TransactionOutPoint(params, 0, b55.getTransactions().get(0).getHash()),
b55.getTransactions().get(0).getOutputs().get(0).getValue(),
b55.getTransactions().get(0).getOutputs().get(0).getScriptPubKey()));
// Test CVE-2012-2459
// -> b33 (9) -> b35 (10) -> b39 (11) -> b42 (12) -> b43 (13) -> b53 (14) -> b55 (15) -> b57 (16)
// \-> b56 (16)
//
TransactionOutPointWithValue out16 = spendableOutputs.poll();
Block b57 = createNextBlock(b55, chainHeadHeight + 15, out16, null);
Transaction b56txToDuplicate;
{
b56txToDuplicate = new Transaction(params);
b56txToDuplicate.addOutput(new TransactionOutput(params, b56txToDuplicate, BigInteger.valueOf(1), new byte[] {}));
addOnlyInputToTransaction(b56txToDuplicate, new TransactionOutPointWithValue(
new TransactionOutPoint(params, 1, b57.getTransactions().get(1).getHash()),
BigInteger.valueOf(1), b57.getTransactions().get(1).getOutputs().get(1).getScriptPubKey()));
b57.addTransaction(b56txToDuplicate);
}
b57.solve();
Block b56;
try {
b56 = new Block(params, b57.digitalcoinSerialize());
} catch (ProtocolException e) {
throw new RuntimeException(e); // Cannot happen.
}
b56.addTransaction(b56txToDuplicate);
Preconditions.checkState(b56.getHash().equals(b57.getHash()));
blocks.add(new BlockAndValidity(b56, false, true, b55.getHash(), "b56"));
blocks.add(new BlockAndValidity(b57, true, false, b57.getHash(), "b57"));
spendableOutputs.offer(new TransactionOutPointWithValue(
new TransactionOutPoint(params, 0, b57.getTransactions().get(0).getHash()),
b57.getTransactions().get(0).getOutputs().get(0).getValue(),
b57.getTransactions().get(0).getOutputs().get(0).getScriptPubKey()));
// Test a few invalid tx types
// -> b35 (10) -> b39 (11) -> b42 (12) -> b43 (13) -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17)
// \-> ??? (17)
//
TransactionOutPointWithValue out17 = spendableOutputs.poll();
// tx with prevout.n out of range
Block b58 = createNextBlock(b57, chainHeadHeight + 16, out17, null);
{
Transaction tx = new Transaction(params);
tx.addOutput(new TransactionOutput(params, tx, BigInteger.ZERO, new byte[] {}));
tx.addInput(new TransactionInput(params, tx, new byte[] { Script.OP_1 },
new TransactionOutPoint(params, 3, b58.getTransactions().get(1).getHash())));
b58.addTransaction(tx);
}
b58.solve();
blocks.add(new BlockAndValidity(b58, false, true, b57.getHash(), "b58"));
// tx with output value > input value out of range
Block b59 = createNextBlock(b57, chainHeadHeight + 16, out17, null);
{
Transaction tx = new Transaction(params);
tx.addOutput(new TransactionOutput(params, tx,
b59.getTransactions().get(1).getOutputs().get(2).getValue().add(BigInteger.ONE), new byte[] {}));
tx.addInput(new TransactionInput(params, tx, new byte[] { Script.OP_1 },
new TransactionOutPoint(params, 2, b59.getTransactions().get(1).getHash())));
b59.addTransaction(tx);
}
b59.solve();
blocks.add(new BlockAndValidity(b59, false, true, b57.getHash(), "b59"));
Block b60 = createNextBlock(b57, chainHeadHeight + 16, out17, null);
blocks.add(new BlockAndValidity(b60, true, false, b60.getHash(), "b60"));
spendableOutputs.offer(new TransactionOutPointWithValue(
new TransactionOutPoint(params, 0, b60.getTransactions().get(0).getHash()),
b60.getTransactions().get(0).getOutputs().get(0).getValue(),
b60.getTransactions().get(0).getOutputs().get(0).getScriptPubKey()));
// Test BIP30
// -> b39 (11) -> b42 (12) -> b43 (13) -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17)
// \-> b61 (18)
//
TransactionOutPointWithValue out18 = spendableOutputs.poll();
Block b61 = createNextBlock(b60, chainHeadHeight + 17, out18, null);
{
byte[] scriptBytes = b61.getTransactions().get(0).getInputs().get(0).getScriptBytes();
scriptBytes[0]--; // createNextBlock will increment the first script byte on each new block
b61.getTransactions().get(0).getInputs().get(0).setScriptBytes(scriptBytes);
b61.unCache();
}
b61.solve();
blocks.add(new BlockAndValidity(b61, false, true, b60.getHash(), "b61"));
// Test tx.isFinal is properly rejected (not an exhaustive tx.isFinal test, that should be in data-driven transaction tests)
// -> b39 (11) -> b42 (12) -> b43 (13) -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17)
// \-> b62 (18)
//
Block b62 = createNextBlock(b60, chainHeadHeight + 17, null, null);
{
Transaction tx = new Transaction(params);
tx.setLockTime(0xffffffffL);
tx.addOutput(new TransactionOutput(params, tx, BigInteger.ZERO, new byte[] { Script.OP_TRUE }));
addOnlyInputToTransaction(tx, out18, 0);
b62.addTransaction(tx);
Preconditions.checkState(!tx.isFinal(chainHeadHeight + 17, b62.getTimeSeconds()));
}
b62.solve();
blocks.add(new BlockAndValidity(b62, false, true, b60.getHash(), "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 + 17, null, null);
{
b63.getTransactions().get(0).setLockTime(0xffffffffL);
b63.getTransactions().get(0).getInputs().get(0).setSequenceNumber(0xDEADBEEF);
Preconditions.checkState(!b63.getTransactions().get(0).isFinal(chainHeadHeight + 17, b63.getTimeSeconds()));
}
b63.solve();
blocks.add(new BlockAndValidity(b63, false, true, b60.getHash(), "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 b64Created = createNextBlock(b60, chainHeadHeight + 17, out18, null);
Transaction tx = new Transaction(params);
// Signature size is non-deterministic, so it may take several runs before finding any off-by-one errors
byte[] outputScript = new byte[Block.MAX_BLOCK_SIZE - b64Created.getMessageSize() - 138];
Arrays.fill(outputScript, (byte)Script.OP_FALSE);
tx.addOutput(new TransactionOutput(params, tx, BigInteger.valueOf(1), outputScript));
addOnlyInputToTransaction(tx, new TransactionOutPointWithValue(
new TransactionOutPoint(params, 1, b64Created.getTransactions().get(1).getHash()),
BigInteger.valueOf(1), b64Created.getTransactions().get(1).getOutputs().get(1).getScriptPubKey()));
b64Created.addTransaction(tx);
b64Created.solve();
UnsafeByteArrayOutputStream stream = new UnsafeByteArrayOutputStream(b64Created.getMessageSize() + 8);
b64Created.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);
stream.write(varIntBytes);
Preconditions.checkState(new VarInt(varIntBytes, 0).value == b64Created.getTransactions().size());
for (Transaction transaction : b64Created.getTransactions())
transaction.digitalcoinSerialize(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
Preconditions.checkState(stream.size() == b64Created.getMessageSize() + 8);
Preconditions.checkState(stream.size() == b64.getMessageSize());
Preconditions.checkState(Arrays.equals(stream.toByteArray(), b64.digitalcoinSerialize()));
Preconditions.checkState(b64.getOptimalEncodingMessageSize() == b64Created.getMessageSize());
}
blocks.add(new BlockAndValidity(b64, true, false, b64.getHash(), "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()));
// 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();
Block b65 = createNextBlock(b64, chainHeadHeight + 18, null, null);
{
Transaction tx1 = new Transaction(params);
tx1.addOutput(new TransactionOutput(params, tx1, out19.value, new byte[]{ Script.OP_TRUE }));
addOnlyInputToTransaction(tx1, out19, 0);
b65.addTransaction(tx1);
Transaction tx2 = new Transaction(params);
tx2.addOutput(new TransactionOutput(params, tx2, BigInteger.ZERO, new byte[]{ Script.OP_TRUE }));
tx2.addInput(new TransactionInput(params, tx2, new byte[]{ Script.OP_TRUE },
new TransactionOutPoint(params, 0, tx1.getHash())));
b65.addTransaction(tx2);
}
b65.solve();
blocks.add(new BlockAndValidity(b65, true, false, b65.getHash(), "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()));
// 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();
Block b66 = createNextBlock(b65, chainHeadHeight + 19, null, null);
{
Transaction tx1 = new Transaction(params);
tx1.addOutput(new TransactionOutput(params, tx1, out20.value, new byte[]{ Script.OP_TRUE }));
addOnlyInputToTransaction(tx1, out20, 0);
Transaction tx2 = new Transaction(params);
tx2.addOutput(new TransactionOutput(params, tx2, BigInteger.ZERO, new byte[]{ Script.OP_TRUE }));
tx2.addInput(new TransactionInput(params, tx2, new byte[]{ Script.OP_TRUE },
new TransactionOutPoint(params, 0, tx1.getHash())));
b66.addTransaction(tx2);
b66.addTransaction(tx1);
}
b66.solve();
blocks.add(new BlockAndValidity(b66, false, true, b65.getHash(), "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 + 19, null, null);
{
Transaction tx1 = new Transaction(params);
tx1.addOutput(new TransactionOutput(params, tx1, out20.value, new byte[]{ Script.OP_TRUE }));
addOnlyInputToTransaction(tx1, out20, 0);
b67.addTransaction(tx1);
Transaction tx2 = new Transaction(params);
tx2.addOutput(new TransactionOutput(params, tx2, BigInteger.ZERO, new byte[]{ Script.OP_TRUE }));
tx2.addInput(new TransactionInput(params, tx2, new byte[]{ Script.OP_TRUE },
new TransactionOutPoint(params, 0, tx1.getHash())));
b67.addTransaction(tx2);
Transaction tx3 = new Transaction(params);
tx3.addOutput(new TransactionOutput(params, tx3, out20.value, new byte[]{ Script.OP_TRUE }));
tx3.addInput(new TransactionInput(params, tx3, new byte[]{ Script.OP_TRUE },
new TransactionOutPoint(params, 0, tx1.getHash())));
b67.addTransaction(tx3);
}
b67.solve();
blocks.add(new BlockAndValidity(b67, false, true, b65.getHash(), "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 + 19, null, BigInteger.TEN);
{
Transaction tx = new Transaction(params);
tx.addOutput(new TransactionOutput(params, tx, out20.value.subtract(BigInteger.valueOf(9)), new byte[]{ Script.OP_TRUE }));
addOnlyInputToTransaction(tx, out20, 0);
b68.addTransaction(tx);
}
b68.solve();
blocks.add(new BlockAndValidity(b68, false, true, b65.getHash(), "b68"));
Block b69 = createNextBlock(b65, chainHeadHeight + 19, null, BigInteger.TEN);
{
Transaction tx = new Transaction(params);
tx.addOutput(new TransactionOutput(params, tx, out20.value.subtract(BigInteger.TEN), new byte[]{ Script.OP_TRUE }));
addOnlyInputToTransaction(tx, out20, 0);
b69.addTransaction(tx);
}
b69.solve();
blocks.add(new BlockAndValidity(b69, true, false, b69.getHash(), "b69"));
// 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();
Block b70 = createNextBlock(b69, chainHeadHeight + 20, out21, null);
{
Transaction tx = new Transaction(params);
tx.addOutput(new TransactionOutput(params, tx, BigInteger.ZERO, new byte[]{ Script.OP_TRUE }));
tx.addInput(new TransactionInput(params, tx, new byte[]{ Script.OP_TRUE },
new TransactionOutPoint(params, 0, new Sha256Hash("23c70ed7c0506e9178fc1a987f40a33946d4ad4c962b5ae3a52546da53af0c5c"))));
b70.addTransaction(tx);
}
b70.solve();
blocks.add(new BlockAndValidity(b70, false, true, b69.getHash(), "b70"));
//TODO: Explicitly address MoneyRange() checks
// (finally) return the created chain
return blocks;
}
private Block createNextBlock(Block baseBlock, int nextBlockHeight, TransactionOutPointWithValue prevOut,
BigInteger additionalCoinbaseValue) throws ScriptException {
BigInteger coinbaseValue = Utils.toNanoCoins(50, 0).shiftRight(nextBlockHeight / params.getSubsidyDecreaseBlockCount())
.add((prevOut != null ? prevOut.value.subtract(BigInteger.ONE) : BigInteger.valueOf(0)))
.add(additionalCoinbaseValue == null ? BigInteger.valueOf(0) : additionalCoinbaseValue);
Block block = baseBlock.createNextBlockWithCoinbase(coinbaseOutKeyPubKey, coinbaseValue);
if (prevOut != null) {
Transaction t = new Transaction(params);
// Entirely invalid scriptPubKey to ensure we aren't pre-verifying too much
t.addOutput(new TransactionOutput(params, t, BigInteger.valueOf(0), new byte[] { Script.OP_PUSHDATA1 - 1 }));
t.addOutput(new TransactionOutput(params, t, BigInteger.valueOf(1), Script.createOutputScript(coinbaseOutKeyPubKey)));
// Spendable output
t.addOutput(new TransactionOutput(params, t, BigInteger.ZERO, new byte[] {Script.OP_1}));
addOnlyInputToTransaction(t, prevOut);
block.addTransaction(t);
block.solve();
}
return block;
}
private void addOnlyInputToTransaction(Transaction t, TransactionOutPointWithValue prevOut) 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);
t.addInput(input);
byte[] connectedPubKeyScript = prevOut.scriptPubKey.program;
Sha256Hash hash = t.hashTransactionForSignature(0, connectedPubKeyScript, SigHash.ALL, false);
// Sign input
try {
ByteArrayOutputStream bos = new UnsafeByteArrayOutputStream(73);
bos.write(coinbaseOutKey.sign(hash).encodeToDER());
bos.write(SigHash.ALL.ordinal() + 1);
byte[] signature = bos.toByteArray();
Preconditions.checkState(prevOut.scriptPubKey.isSentToRawPubKey());
input.setScriptBytes(Script.createInputScript(signature));
} catch (IOException e) {
throw new RuntimeException(e); // Cannot happen.
}
}
}