/*
* Copyright by the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.bitcoinj.core;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import org.bitcoinj.core.Transaction.SigHash;
import org.bitcoinj.crypto.TransactionSignature;
import org.bitcoinj.script.Script;
import org.bitcoinj.script.ScriptBuilder;
import com.google.common.base.Preconditions;
import javax.annotation.Nullable;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.util.*;
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;
/**
* YOU ARE READING THIS CODE BECAUSE EITHER...
*
* a) You are testing an alternative implementation with full validation rules. If you are doing this, you should go
* rethink your life. Seriously, why are you reimplementing Bitcoin consensus rules? Instead, go work on making
* Bitcoin Core consensus rules a shared library and use that. Seriously, you wont get it right, and starting with
* this tester as a way to try to do so will simply end in pain and lost coins. SERIOUSLY, JUST STOP!
*
* b) Bitcoin Core is failing some test in here and you're wondering what test is causing failure. Just stop. There is no
* hope trying to read this file and decipher it. Give up and ping BlueMatt. Seriously, this stuff is a huge mess.
*
* c) You are trying to add a new test. STOP! WHY THE HELL WOULD YOU EVEN DO THAT? GO REWRITE THIS TESTER!
*
* d) You are BlueMatt and you're trying to hack more crap onto this multi-headed lopsided Proof Of Stake. Why are you
* doing this? Seriously, why have you not rewritten this thing yet? WTF man...
*
* 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;
public Script scriptPubKey;
public TransactionOutPointWithValue(TransactionOutPoint outpoint, Coin value, Script scriptPubKey) {
this.outpoint = outpoint;
this.value = value;
this.scriptPubKey = scriptPubKey;
}
public TransactionOutPointWithValue(Transaction tx, int output) {
this(new TransactionOutPoint(tx.getParams(), output, tx.getHash()),
tx.getOutput(output).getValue(), tx.getOutput(output).getScriptPubKey());
}
}
/** An arbitrary rule which the testing client must match */
class Rule {
String ruleName;
Rule(String ruleName) {
this.ruleName = ruleName;
}
}
/**
* A test which checks the mempool state (ie defined which transactions should be in memory pool
*/
class MemoryPoolState extends Rule {
Set<InventoryItem> mempool;
public MemoryPoolState(Set<InventoryItem> mempool, String ruleName) {
super(ruleName);
this.mempool = mempool;
}
}
class UTXORule extends Rule {
List<TransactionOutPoint> query;
UTXOsMessage result;
public UTXORule(String ruleName, TransactionOutPoint query, UTXOsMessage result) {
super(ruleName);
this.query = Collections.singletonList(query);
this.result = result;
}
public UTXORule(String ruleName, List<TransactionOutPoint> query, UTXOsMessage result) {
super(ruleName);
this.query = query;
this.result = result;
}
}
class RuleList {
public List<Rule> list;
public int maximumReorgBlockCount;
Map<Sha256Hash, Block> hashHeaderMap;
public RuleList(List<Rule> list, Map<Sha256Hash, Block> hashHeaderMap, int maximumReorgBlockCount) {
this.list = list;
this.hashHeaderMap = hashHeaderMap;
this.maximumReorgBlockCount = maximumReorgBlockCount;
}
}
public class FullBlockTestGenerator {
// Used by BitcoindComparisonTool and AbstractFullPrunedBlockChainTest to create test cases
private NetworkParameters params;
private ECKey coinbaseOutKey;
private byte[] coinbaseOutKeyPubKey;
// Used to double-check that we are always using the right next-height
private Map<Sha256Hash, Integer> blockToHeightMap = new HashMap<Sha256Hash, Integer>();
private Map<Sha256Hash, Block> hashHeaderMap = new HashMap<Sha256Hash, Block>();
private Map<Sha256Hash, Sha256Hash> coinbaseBlockMap = new HashMap<Sha256Hash, Sha256Hash>();
public FullBlockTestGenerator(NetworkParameters params) {
this.params = params;
coinbaseOutKey = new ECKey();
coinbaseOutKeyPubKey = coinbaseOutKey.getPubKey();
Utils.setMockClock();
}
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();
final Script OP_NOP_SCRIPT = new ScriptBuilder().op(OP_NOP).build();
// TODO: Rename this variable.
List<Rule> blocks = new LinkedList<Rule>() {
@Override
public boolean add(Rule element) {
if (outStream != null && element instanceof BlockAndValidity) {
try {
outStream.write((int) (params.getPacketMagic() >>> 24));
outStream.write((int) (params.getPacketMagic() >>> 16));
outStream.write((int) (params.getPacketMagic() >>> 8));
outStream.write((int) params.getPacketMagic());
byte[] block = ((BlockAndValidity)element).block.bitcoinSerialize();
byte[] length = new byte[4];
Utils.uint32ToByteArrayBE(block.length, length, 0);
outStream.write(Utils.reverseBytes(length));
outStream.write(block);
((BlockAndValidity)element).block = null;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
return super.add(element);
}
};
RuleList ret = new RuleList(blocks, hashHeaderMap, 10);
Queue<TransactionOutPointWithValue> spendableOutputs = new LinkedList<TransactionOutPointWithValue>();
int chainHeadHeight = 1;
Block chainHead = params.getGenesisBlock().createNextBlockWithCoinbase(Block.BLOCK_VERSION_GENESIS, coinbaseOutKeyPubKey, chainHeadHeight);
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(Block.BLOCK_VERSION_GENESIS, coinbaseOutKeyPubKey, chainHeadHeight);
chainHeadHeight++;
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.
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);
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(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)
//
// 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.
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(b3, true, false, b2.getHash(), chainHeadHeight + 2, "b3"));
// Do a simple UTXO query.
UTXORule utxo1;
{
Transaction coinbase = b2.block.getTransactions().get(0);
TransactionOutPoint outpoint = new TransactionOutPoint(params, 0, coinbase.getHash());
long[] heights = {chainHeadHeight + 2};
UTXOsMessage result = new UTXOsMessage(params, ImmutableList.of(coinbase.getOutput(0)), heights, b2.getHash(), chainHeadHeight + 2);
utxo1 = new UTXORule("utxo1", outpoint, result);
blocks.add(utxo1);
}
// Now we add another block to make the alternative chain longer.
//
// genesis -> b1 (0) -> b2 (1)
// \-> b3 (1) -> b4 (2)
//
TransactionOutPointWithValue out2 = checkNotNull(spendableOutputs.poll());
NewBlock b4 = createNextBlock(b3, chainHeadHeight + 3, out2, null);
blocks.add(new BlockAndValidity(b4, true, false, b4.getHash(), chainHeadHeight + 3, "b4"));
// Check that the old coinbase is no longer in the UTXO set and the new one is.
{
Transaction coinbase = b4.block.getTransactions().get(0);
TransactionOutPoint outpoint = new TransactionOutPoint(params, 0, coinbase.getHash());
List<TransactionOutPoint> queries = ImmutableList.of(utxo1.query.get(0), outpoint);
List<TransactionOutput> results = Lists.asList(null, coinbase.getOutput(0), new TransactionOutput[]{});
long[] heights = {chainHeadHeight + 3};
UTXOsMessage result = new UTXOsMessage(params, results, heights, b4.getHash(), chainHeadHeight + 3);
UTXORule utxo2 = new UTXORule("utxo2", queries, result);
blocks.add(utxo2);
}
// ... and back to the first chain.
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();
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)
//
// 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)
//
NewBlock b7 = createNextBlock(b5, chainHeadHeight + 5, out2, null);
blocks.add(new BlockAndValidity(b7, true, false, b6.getHash(), chainHeadHeight + 4, "b7"));
TransactionOutPointWithValue out4 = spendableOutputs.poll();
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)
//
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)
//
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)
// \-> b12 (3) -> b13 (4) -> b14 (5)
// (b12 added last)
// \-> b3 (1) -> b4 (2)
//
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(b13, false, false, b6.getHash(), chainHeadHeight + 4, "b13"));
spendableOutputs.offer(b13.getCoinbaseOutput());
TransactionOutPointWithValue out5 = spendableOutputs.poll();
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(b14, false, false, b6.getHash(), chainHeadHeight + 4, "b14"));
// Make sure we dont die if an orphan gets added twice
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)
//
NewBlock b15 = createNextBlock(b13, chainHeadHeight + 6, out5, null);
{
int sigOps = 0;
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, 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(b15, true, false, b15.getHash(), chainHeadHeight + 6, "b15"));
spendableOutputs.offer(b15.getCoinbaseOutput());
TransactionOutPointWithValue out6 = spendableOutputs.poll();
NewBlock b16 = createNextBlock(b15, chainHeadHeight + 7, out6, null);
{
int sigOps = 0;
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, 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(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)
//
NewBlock b17 = createNextBlock(b15, chainHeadHeight + 7, out6, null);
{
Transaction tx = new Transaction(params);
tx.addOutput(new TransactionOutput(params, tx, SATOSHI, new byte[] {}));
addOnlyInputToTransaction(tx, b3);
b17.addTransaction(tx);
}
b17.solve();
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)
//
NewBlock b18 = createNextBlock(b13, chainHeadHeight + 6, out5, null);
{
Transaction tx = new Transaction(params);
tx.addOutput(new TransactionOutput(params, tx, SATOSHI, new byte[] {}));
addOnlyInputToTransaction(tx, b3);
b18.addTransaction(tx);
}
b18.solve();
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)
// \-> b3 (1) -> b4 (2)
//
TransactionOutPointWithValue out7 = spendableOutputs.poll();
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)
//
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)
//
NewBlock b23 = createNextBlock(b15, chainHeadHeight + 7, out6, null);
{
Transaction tx = new Transaction(params);
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, b23);
b23.addTransaction(tx);
}
b23.solve();
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.block.getMessageSize() - 64];
Arrays.fill(outputScript, (byte) OP_FALSE);
tx.addOutput(new TransactionOutput(params, tx, ZERO, outputScript));
addOnlyInputToTransaction(tx, b24);
b24.addTransaction(tx);
}
b24.solve();
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
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)
//
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.block.getTransactions().get(0).getInputs().get(0).clearScriptBytes();
b26.block.setMerkleRoot(null);
b26.solve();
blocks.add(new BlockAndValidity(b26, false, true, b23.getHash(), chainHeadHeight + 7, "b26"));
// Extend the b26 chain to make sure bitcoind isn't accepting b26
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.block.getTransactions().get(0).getInputs().get(0).setScriptBytes(coinbase);
}
b28.block.setMerkleRoot(null);
b28.solve();
blocks.add(new BlockAndValidity(b28, false, true, b23.getHash(), chainHeadHeight + 7, "b28"));
// Extend the b28 chain to make sure bitcoind isn't accepting b28
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.block.getTransactions().get(0).getInputs().get(0).setScriptBytes(coinbase);
}
b30.block.setMerkleRoot(null);
b30.solve();
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)
// \-> b36 (11)
// \-> b34 (10)
// \-> b32 (9)
//
TransactionOutPointWithValue out8 = spendableOutputs.poll();
NewBlock b31 = createNextBlock(b30, chainHeadHeight + 9, out8, null);
{
int sigOps = 0;
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, b31);
b31.addTransaction(tx);
}
b31.solve();
blocks.add(new BlockAndValidity(b31, true, false, b31.getHash(), chainHeadHeight + 9, "b31"));
spendableOutputs.offer(b31.getCoinbaseOutput());
TransactionOutPointWithValue out9 = spendableOutputs.poll();
NewBlock b32 = createNextBlock(b31, chainHeadHeight + 10, out9, null);
{
int sigOps = 0;
for (Transaction tx : b32.block.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) OP_CHECKMULTISIG);
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, b32);
b32.addTransaction(tx);
}
b32.solve();
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.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, b33);
b33.addTransaction(tx);
}
b33.solve();
blocks.add(new BlockAndValidity(b33, true, false, b33.getHash(), chainHeadHeight + 10, "b33"));
spendableOutputs.offer(b33.getCoinbaseOutput());
TransactionOutPointWithValue out10 = spendableOutputs.poll();
NewBlock b34 = createNextBlock(b33, chainHeadHeight + 11, out10, null);
{
int sigOps = 0;
for (Transaction tx : b34.block.getTransactions()) {
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) OP_CHECKMULTISIGVERIFY);
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, b34);
b34.addTransaction(tx);
}
b34.solve();
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.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, b35);
b35.addTransaction(tx);
}
b35.solve();
blocks.add(new BlockAndValidity(b35, true, false, b35.getHash(), chainHeadHeight + 11, "b35"));
spendableOutputs.offer(b35.getCoinbaseOutput());
TransactionOutPointWithValue out11 = spendableOutputs.poll();
NewBlock b36 = createNextBlock(b35, chainHeadHeight + 12, out11, null);
{
int sigOps = 0;
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, b36);
b36.addTransaction(tx);
}
b36.solve();
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)
// 12 (3) -> b13 (4) -> b15 (5) -> b23 (6) -> b30 (7) -> b31 (8) -> b33 (9) -> b35 (10)
// \-> b37 (11)
// \-> b38 (11)
//
NewBlock b37 = createNextBlock(b35, chainHeadHeight + 12, out11, null);
{
Transaction tx = new Transaction(params);
tx.addOutput(new TransactionOutput(params, tx, SATOSHI, new byte[] {}));
addOnlyInputToTransaction(tx, out11); // double spend out11
b37.addTransaction(tx);
}
b37.solve();
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, b37);
b38.addTransaction(tx);
}
b38.solve();
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)
//
// Create some P2SH outputs that will require 6 sigops to spend
byte[] b39p2shScriptPubKey;
int b39numP2SHOutputs = 0, b39sigOpsPerOutput = 6;
NewBlock b39 = createNextBlock(b35, chainHeadHeight + 12, null, null);
{
ByteArrayOutputStream p2shScriptPubKey = new UnsafeByteArrayOutputStream();
try {
Script.writeBytes(p2shScriptPubKey, coinbaseOutKeyPubKey);
p2shScriptPubKey.write(OP_2DUP);
p2shScriptPubKey.write(OP_CHECKSIGVERIFY);
p2shScriptPubKey.write(OP_2DUP);
p2shScriptPubKey.write(OP_CHECKSIGVERIFY);
p2shScriptPubKey.write(OP_2DUP);
p2shScriptPubKey.write(OP_CHECKSIGVERIFY);
p2shScriptPubKey.write(OP_2DUP);
p2shScriptPubKey.write(OP_CHECKSIGVERIFY);
p2shScriptPubKey.write(OP_2DUP);
p2shScriptPubKey.write(OP_CHECKSIGVERIFY);
p2shScriptPubKey.write(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(OP_HASH160);
try {
Script.writeBytes(scriptPubKey, scriptHash);
} catch (IOException e) {
throw new RuntimeException(e); // Cannot happen.
}
scriptPubKey.write(OP_EQUAL);
Coin lastOutputValue = out11.value.subtract(SATOSHI);
TransactionOutPoint lastOutPoint;
{
Transaction tx = new Transaction(params);
tx.addOutput(new TransactionOutput(params, tx, SATOSHI, scriptPubKey.toByteArray()));
tx.addOutput(new TransactionOutput(params, tx, lastOutputValue, new byte[]{OP_1}));
addOnlyInputToTransaction(tx, out11);
lastOutPoint = new TransactionOutPoint(params, 1, tx.getHash());
b39.addTransaction(tx);
}
b39numP2SHOutputs++;
while (b39.block.getMessageSize() < Block.MAX_BLOCK_SIZE)
{
Transaction tx = new Transaction(params);
lastOutputValue = lastOutputValue.subtract(SATOSHI);
tx.addOutput(new TransactionOutput(params, tx, SATOSHI, scriptPubKey.toByteArray()));
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.block.getMessageSize() + tx.getMessageSize() < Block.MAX_BLOCK_SIZE) {
b39.addTransaction(tx);
b39numP2SHOutputs++;
} else
break;
}
}
b39.solve();
blocks.add(new BlockAndValidity(b39, true, false, b39.getHash(), chainHeadHeight + 12, "b39"));
spendableOutputs.offer(b39.getCoinbaseOutput());
TransactionOutPointWithValue out12 = spendableOutputs.poll();
NewBlock b40 = createNextBlock(b39, chainHeadHeight + 13, out12, null);
{
int sigOps = 0;
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, 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.block.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.hashForSignature(1, b39p2shScriptPubKey, SigHash.SINGLE, false);
// Sign input
try {
ByteArrayOutputStream bos = new UnsafeByteArrayOutputStream(73);
bos.write(coinbaseOutKey.sign(hash).encodeToDER());
bos.write(SigHash.SINGLE.value);
byte[] signature = bos.toByteArray();
ByteArrayOutputStream scriptSigBos = new UnsafeByteArrayOutputStream(signature.length + b39p2shScriptPubKey.length + 3);
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);
}
sigOps += numTxes * b39sigOpsPerOutput;
Transaction tx = new Transaction(params);
tx.addInput(new TransactionInput(params, tx, new byte[]{OP_1}, lastOutPoint));
byte[] scriptPubKey = new byte[Block.MAX_BLOCK_SIGOPS - sigOps + 1];
Arrays.fill(scriptPubKey, (byte) OP_CHECKSIG);
tx.addOutput(new TransactionOutput(params, tx, ZERO, scriptPubKey));
b40.addTransaction(tx);
}
b40.solve();
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.block.getTransactions()) {
sigOps += tx.getSigOpCount();
}
int numTxes = (Block.MAX_BLOCK_SIGOPS - sigOps)
/ b39sigOpsPerOutput;
checkState(numTxes <= b39numP2SHOutputs);
TransactionOutPoint lastOutPoint = new TransactionOutPoint(
params, 1, b41.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, Coin
.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.block.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.hashForSignature(1,
b39p2shScriptPubKey, SigHash.SINGLE, false);
// Sign input
try {
ByteArrayOutputStream bos = new UnsafeByteArrayOutputStream(
73);
bos.write(coinbaseOutKey.sign(hash).encodeToDER());
bos.write(SigHash.SINGLE.value);
byte[] signature = bos.toByteArray();
ByteArrayOutputStream scriptSigBos = new UnsafeByteArrayOutputStream(
signature.length
+ b39p2shScriptPubKey.length + 3);
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());
b41.addTransaction(tx);
}
sigOps += numTxes * b39sigOpsPerOutput;
Transaction tx = new Transaction(params);
tx.addInput(new TransactionInput(params, tx,
new byte[] {OP_1}, lastOutPoint));
byte[] scriptPubKey = new byte[Block.MAX_BLOCK_SIGOPS - sigOps];
Arrays.fill(scriptPubKey, (byte) OP_CHECKSIG);
tx.addOutput(new TransactionOutput(params, tx, ZERO, scriptPubKey));
b41.addTransaction(tx);
}
b41.solve();
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)
//
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();
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, Block.BLOCK_VERSION_GENESIS);
byte[] outScriptBytes = ScriptBuilder.createOutputScript(ECKey.fromPublicOnly(coinbaseOutKeyPubKey)).getProgram();
{
b44.setDifficultyTarget(b43.block.getDifficultyTarget());
b44.addCoinbaseTransaction(coinbaseOutKeyPubKey, ZERO, chainHeadHeight + 15);
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 }));
t.addOutput(new TransactionOutput(params, t, SATOSHI, outScriptBytes));
// Spendable output
t.addOutput(new TransactionOutput(params, t, ZERO, new byte[] {OP_1}));
addOnlyInputToTransaction(t, out14);
b44.addTransaction(t);
b44.setPrevBlockHash(b43.getHash());
b44.setTime(b43.block.getTimeSeconds() + 1);
}
b44.solve();
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, Block.BLOCK_VERSION_GENESIS);
{
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 }));
t.addOutput(new TransactionOutput(params, t, SATOSHI, outScriptBytes));
// Spendable output
t.addOutput(new TransactionOutput(params, t, ZERO, new byte[] {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(), chainHeadHeight + 15, "b45"));
// A block with no txn
Block b46 = new Block(params, Block.BLOCK_VERSION_GENESIS);
{
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(), chainHeadHeight + 15, "b46"));
// A block with invalid work
NewBlock b47 = createNextBlock(b44, chainHeadHeight + 16, out15, null);
{
try {
// Inverse solve
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.block.setNonce(b47.block.getNonce() + 1);
}
} catch (VerificationException e) {
throw new RuntimeException(e); // Cannot happen.
}
}
blocks.add(new BlockAndValidity(b47, false, true, b44.getHash(), chainHeadHeight + 15, "b47"));
// Block with timestamp > 2h in the future
NewBlock b48 = createNextBlock(b44, chainHeadHeight + 16, out15, null);
b48.block.setTime(Utils.currentTimeSeconds() + 60 * 60 * 3);
b48.solve();
blocks.add(new BlockAndValidity(b48, false, true, b44.getHash(), chainHeadHeight + 15, "b48"));
// Block with invalid merkle hash
NewBlock b49 = createNextBlock(b44, chainHeadHeight + 16, out15, null);
byte[] b49MerkleHash = Sha256Hash.ZERO_HASH.getBytes().clone();
b49MerkleHash[1] = (byte) 0xDE;
b49.block.setMerkleRoot(Sha256Hash.of(b49MerkleHash));
b49.solve();
blocks.add(new BlockAndValidity(b49, false, true, b44.getHash(), chainHeadHeight + 15, "b49"));
// Block with incorrect POW limit
NewBlock b50 = createNextBlock(b44, chainHeadHeight + 16, out15, null);
{
long diffTarget = b44.getDifficultyTarget();
diffTarget &= 0xFFBFFFFF; // Make difficulty one bit harder
b50.block.setDifficultyTarget(diffTarget);
}
b50.solve();
blocks.add(new BlockAndValidity(b50, false, true, b44.getHash(), chainHeadHeight + 15, "b50"));
// A block with two coinbase txn
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.block.addTransaction(coinbase, false);
}
b51.solve();
blocks.add(new BlockAndValidity(b51, false, true, b44.getHash(), chainHeadHeight + 15, "b51"));
// A block with duplicate txn
NewBlock b52 = createNextBlock(b44, chainHeadHeight + 16, out15, null);
{
Transaction tx = new Transaction(params);
tx.addOutput(new TransactionOutput(params, tx, SATOSHI, new byte[] {}));
addOnlyInputToTransaction(tx, b52);
b52.addTransaction(tx);
b52.addTransaction(tx);
}
b52.solve();
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)
//
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
NewBlock b54 = createNextBlock(b53, chainHeadHeight + 16, out15, null);
b54.block.setTime(b35.block.getTimeSeconds() - 1);
b54.solve();
blocks.add(new BlockAndValidity(b54, false, true, b44.getHash(), chainHeadHeight + 15, "b54"));
// Block with valid timestamp
NewBlock b55 = createNextBlock(b53, chainHeadHeight + 16, out15, null);
b55.block.setTime(b35.block.getTimeSeconds());
b55.solve();
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();
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, b57);
b57.addTransaction(b56txToDuplicate);
}
b57.solve();
Block b56;
try {
b56 = params.getDefaultSerializer().makeBlock(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(b56, false, true, b55.getHash(), chainHeadHeight + 16, "b56"));
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, b57p2);
b57p2.addTransaction(tx1);
Transaction tx2 = new Transaction(params);
tx2.addOutput(new TransactionOutput(params, tx2, SATOSHI, new byte[] {OP_TRUE}));
addOnlyInputToTransaction(tx2, new TransactionOutPointWithValue(
new TransactionOutPoint(params, 0, tx1.getHash()),
SATOSHI, tx1.getOutputs().get(0).getScriptPubKey()));
b57p2.addTransaction(tx2);
b56p2txToDuplicate1 = new Transaction(params);
b56p2txToDuplicate1.addOutput(new TransactionOutput(params, b56p2txToDuplicate1, SATOSHI, new byte[]{OP_TRUE}));
addOnlyInputToTransaction(b56p2txToDuplicate1, new TransactionOutPointWithValue(
new TransactionOutPoint(params, 0, tx2.getHash()),
SATOSHI, tx2.getOutputs().get(0).getScriptPubKey()));
b57p2.addTransaction(b56p2txToDuplicate1);
b56p2txToDuplicate2 = new Transaction(params);
b56p2txToDuplicate2.addOutput(new TransactionOutput(params, b56p2txToDuplicate2, SATOSHI, new byte[]{}));
addOnlyInputToTransaction(b56p2txToDuplicate2, new TransactionOutPointWithValue(
new TransactionOutPoint(params, 0, b56p2txToDuplicate1.getHash()),
SATOSHI, b56p2txToDuplicate1.getOutputs().get(0).getScriptPubKey()));
b57p2.addTransaction(b56p2txToDuplicate2);
}
b57p2.solve();
Block b56p2;
try {
b56p2 = params.getDefaultSerializer().makeBlock(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(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());
// 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
NewBlock b58 = createNextBlock(b57, chainHeadHeight + 18, out17, null);
{
Transaction tx = new Transaction(params);
tx.addOutput(new TransactionOutput(params, tx, ZERO, new byte[] {}));
b58.getSpendableOutput().outpoint.setIndex(42);
addOnlyInputToTransaction(tx, b58);
b58.addTransaction(tx);
}
b58.solve();
blocks.add(new BlockAndValidity(b58, false, true, b57p2.getHash(), chainHeadHeight + 17, "b58"));
// tx with output value > input value out of range
NewBlock b59 = createNextBlock(b57, chainHeadHeight + 18, out17, null);
{
Transaction tx = new Transaction(params);
tx.addOutput(new TransactionOutput(params, tx,
b59.getSpendableOutput().value.add(SATOSHI), new byte[]{}));
addOnlyInputToTransaction(tx, b59);
b59.addTransaction(tx);
}
b59.solve();
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();
NewBlock b61 = createNextBlock(b60, chainHeadHeight + 19, out18, null);
{
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(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)
//
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.block.getTimeSeconds()));
}
b62.solve();
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 (-)
//
NewBlock b63 = createNextBlock(b60, chainHeadHeight + 19, null, null);
{
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(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; NewBlock b64Original;
{
b64Original = createNextBlock(b60, chainHeadHeight + 19, out18, null);
Transaction tx = new Transaction(params);
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, 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)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 == b64Original.block.getTransactions().size());
for (Transaction transaction : b64Original.block.getTransactions())
transaction.bitcoinSerialize(stream);
b64 = params.getSerializer(true).makeBlock(stream.toByteArray(), 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() == b64Original.block.getMessageSize() + 8);
checkState(stream.size() == b64.getMessageSize());
checkState(Arrays.equals(stream.toByteArray(), b64.bitcoinSerialize()));
checkState(b64.getOptimalEncodingMessageSize() == b64Original.block.getMessageSize());
}
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
NewBlock b65 = createNextBlock(b64, chainHeadHeight + 20, null, null);
{
Transaction tx1 = new Transaction(params);
tx1.addOutput(out19.value, OP_TRUE_SCRIPT);
addOnlyInputToTransaction(tx1, out19, 0);
b65.addTransaction(tx1);
Transaction tx2 = new Transaction(params);
tx2.addOutput(ZERO, OP_TRUE_SCRIPT);
tx2.addInput(tx1.getHash(), 0, OP_TRUE_SCRIPT);
b65.addTransaction(tx2);
}
b65.solve();
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);
NewBlock b66 = createNextBlock(b65, chainHeadHeight + 21, null, null);
{
Transaction tx1 = new Transaction(params);
tx1.addOutput(out20.value, OP_TRUE_SCRIPT);
addOnlyInputToTransaction(tx1, out20, 0);
Transaction tx2 = new Transaction(params);
tx2.addOutput(ZERO, OP_TRUE_SCRIPT);
tx2.addInput(tx1.getHash(), 0, OP_NOP_SCRIPT);
b66.addTransaction(tx2);
b66.addTransaction(tx1);
}
b66.solve();
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)
//
NewBlock b67 = createNextBlock(b65, chainHeadHeight + 21, null, null);
{
Transaction tx1 = new Transaction(params);
tx1.addOutput(out20.value, OP_TRUE_SCRIPT);
addOnlyInputToTransaction(tx1, out20, 0);
b67.addTransaction(tx1);
Transaction tx2 = new Transaction(params);
tx2.addOutput(ZERO, OP_TRUE_SCRIPT);
tx2.addInput(tx1.getHash(), 0, OP_NOP_SCRIPT);
b67.addTransaction(tx2);
Transaction tx3 = new Transaction(params);
tx3.addOutput(out20.value, OP_TRUE_SCRIPT);
tx3.addInput(tx1.getHash(), 0, OP_NOP_SCRIPT);
b67.addTransaction(tx3);
}
b67.solve();
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)
//
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);
addOnlyInputToTransaction(tx, out20, 0);
b68.addTransaction(tx);
}
b68.solve();
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);
addOnlyInputToTransaction(tx, out20, 0);
b69.addTransaction(tx);
}
b69.solve();
blocks.add(new BlockAndValidity(b69, true, false, b69.getHash(), chainHeadHeight + 21, "b69"));
spendableOutputs.offer(b69.getCoinbaseOutput());
// 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);
NewBlock b70 = createNextBlock(b69, chainHeadHeight + 22, out21, null);
{
Transaction tx = new Transaction(params);
tx.addOutput(ZERO, OP_TRUE_SCRIPT);
tx.addInput(Sha256Hash.wrap("23c70ed7c0506e9178fc1a987f40a33946d4ad4c962b5ae3a52546da53af0c5c"), 0,
OP_NOP_SCRIPT);
b70.addTransaction(tx);
}
b70.solve();
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)
//
NewBlock b72 = createNextBlock(b69, chainHeadHeight + 22, out21, null);
{
Transaction tx = new Transaction(params);
tx.addOutput(ZERO, OP_TRUE_SCRIPT);
addOnlyInputToTransaction(tx, b72);
b72.addTransaction(tx);
}
b72.solve();
Block b71 = params.getDefaultSerializer().makeBlock(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)
// \-> b** (22)
//
TransactionOutPointWithValue out22 = spendableOutputs.poll(); checkState(out22 != null);
NewBlock b73 = createNextBlock(b72, chainHeadHeight + 23, out22, null);
{
int sigOps = 0;
for (Transaction tx : b73.block.getTransactions()) {
sigOps += tx.getSigOpCount();
}
Transaction tx = new Transaction(params);
byte[] outputScript = new byte[Block.MAX_BLOCK_SIGOPS - sigOps + (int)Script.MAX_SCRIPT_ELEMENT_SIZE + 1 + 5 + 1];
Arrays.fill(outputScript, (byte) OP_CHECKSIG);
// If we push an element that is too large, the CHECKSIGs after that push are still counted
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, b73);
b73.addTransaction(tx);
}
b73.solve();
blocks.add(new BlockAndValidity(b73, false, true, b72.getHash(), chainHeadHeight + 22, "b73"));
NewBlock b74 = createNextBlock(b72, chainHeadHeight + 23, out22, null);
{
int sigOps = 0;
for (Transaction tx : b74.block.getTransactions()) {
sigOps += tx.getSigOpCount();
}
Transaction tx = new Transaction(params);
byte[] outputScript = new byte[Block.MAX_BLOCK_SIGOPS - sigOps + (int)Script.MAX_SCRIPT_ELEMENT_SIZE + 42];
Arrays.fill(outputScript, (byte) OP_CHECKSIG);
// If we push an invalid element, all previous CHECKSIGs are counted
outputScript[Block.MAX_BLOCK_SIGOPS - sigOps + 1] = OP_PUSHDATA4;
outputScript[Block.MAX_BLOCK_SIGOPS - sigOps + 2] = (byte)0xfe;
outputScript[Block.MAX_BLOCK_SIGOPS - sigOps + 3] = (byte)0xff;
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, b74);
b74.addTransaction(tx);
}
b74.solve();
blocks.add(new BlockAndValidity(b74, false, true, b72.getHash(), chainHeadHeight + 22, "b74"));
NewBlock b75 = createNextBlock(b72, chainHeadHeight + 23, out22, null);
{
int sigOps = 0;
for (Transaction tx : b75.block.getTransactions()) {
sigOps += tx.getSigOpCount();
}
Transaction tx = new Transaction(params);
byte[] outputScript = new byte[Block.MAX_BLOCK_SIGOPS - sigOps + (int)Script.MAX_SCRIPT_ELEMENT_SIZE + 42];
Arrays.fill(outputScript, (byte) OP_CHECKSIG);
// If we push an invalid element, all subsequent CHECKSIGs are not counted
outputScript[Block.MAX_BLOCK_SIGOPS - sigOps] = OP_PUSHDATA4;
outputScript[Block.MAX_BLOCK_SIGOPS - sigOps + 1] = (byte)0xff;
outputScript[Block.MAX_BLOCK_SIGOPS - sigOps + 2] = (byte)0xff;
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, b75);
b75.addTransaction(tx);
}
b75.solve();
blocks.add(new BlockAndValidity(b75, true, false, b75.getHash(), chainHeadHeight + 23, "b75"));
spendableOutputs.offer(b75.getCoinbaseOutput());
TransactionOutPointWithValue out23 = spendableOutputs.poll(); checkState(out23 != null);
NewBlock b76 = createNextBlock(b75, chainHeadHeight + 24, out23, null);
{
int sigOps = 0;
for (Transaction tx : b76.block.getTransactions()) {
sigOps += tx.getSigOpCount();
}
Transaction tx = new Transaction(params);
byte[] outputScript = new byte[Block.MAX_BLOCK_SIGOPS - sigOps + (int)Script.MAX_SCRIPT_ELEMENT_SIZE + 1 + 5];
Arrays.fill(outputScript, (byte) OP_CHECKSIG);
// If we push an element that is filled with CHECKSIGs, they (obviously) arent counted
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, b76);
b76.addTransaction(tx);
}
b76.solve();
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)
// \-> b80 (25) -> b81 (26) -> b82 (27)
// b78 creates a tx, which is spent in b79. after b82, both should be in mempool
//
TransactionOutPointWithValue out24 = checkNotNull(spendableOutputs.poll());
TransactionOutPointWithValue out25 = checkNotNull(spendableOutputs.poll());
TransactionOutPointWithValue out26 = checkNotNull(spendableOutputs.poll());
TransactionOutPointWithValue out27 = checkNotNull(spendableOutputs.poll());
NewBlock b77 = createNextBlock(b76, chainHeadHeight + 25, out24, null);
blocks.add(new BlockAndValidity(b77, true, false, b77.getHash(), chainHeadHeight + 25, "b77"));
spendableOutputs.offer(b77.getCoinbaseOutput());
NewBlock b78 = createNextBlock(b77, chainHeadHeight + 26, out25, null);
Transaction b78tx = new Transaction(params);
{
b78tx.addOutput(ZERO, OP_TRUE_SCRIPT);
addOnlyInputToTransaction(b78tx, b77);
b78.addTransaction(b78tx);
}
b78.solve();
blocks.add(new BlockAndValidity(b78, true, false, b78.getHash(), chainHeadHeight + 26, "b78"));
NewBlock b79 = createNextBlock(b78, chainHeadHeight + 27, out26, null);
Transaction b79tx = new Transaction(params);
{
b79tx.addOutput(ZERO, OP_TRUE_SCRIPT);
b79tx.addInput(b78tx.getHash(), 0, OP_NOP_SCRIPT);
b79.addTransaction(b79tx);
}
b79.solve();
blocks.add(new BlockAndValidity(b79, true, false, b79.getHash(), chainHeadHeight + 27, "b79"));
blocks.add(new MemoryPoolState(new HashSet<InventoryItem>(), "post-b79 empty mempool"));
NewBlock b80 = createNextBlock(b77, chainHeadHeight + 26, out25, null);
blocks.add(new BlockAndValidity(b80, true, false, b79.getHash(), chainHeadHeight + 27, "b80"));
spendableOutputs.offer(b80.getCoinbaseOutput());
NewBlock b81 = createNextBlock(b80, chainHeadHeight + 27, out26, null);
blocks.add(new BlockAndValidity(b81, true, false, b79.getHash(), chainHeadHeight + 27, "b81"));
spendableOutputs.offer(b81.getCoinbaseOutput());
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<InventoryItem> post82Mempool = new HashSet<InventoryItem>();
post82Mempool.add(new InventoryItem(InventoryItem.Type.Transaction, b78tx.getHash()));
post82Mempool.add(new InventoryItem(InventoryItem.Type.Transaction, b79tx.getHash()));
blocks.add(new MemoryPoolState(post82Mempool, "post-b82 tx resurrection"));
// Check the UTXO query takes mempool into account.
{
TransactionOutPoint outpoint = new TransactionOutPoint(params, 0, b79tx.getHash());
long[] heights = { UTXOsMessage.MEMPOOL_HEIGHT };
UTXOsMessage result = new UTXOsMessage(params, ImmutableList.of(b79tx.getOutput(0)), heights, b82.getHash(), chainHeadHeight + 28);
UTXORule utxo3 = new UTXORule("utxo3", outpoint, result);
blocks.add(utxo3);
}
// Test invalid opcodes in dead execution paths.
// -> b81 (26) -> b82 (27) -> b83 (28)
// b83 creates a tx which contains a transaction script with an invalid opcode in a dead execution path:
// OP_FALSE OP_IF OP_INVALIDOPCODE OP_ELSE OP_TRUE OP_ENDIF
//
TransactionOutPointWithValue out28 = spendableOutputs.poll(); Preconditions.checkState(out28 != null);
NewBlock b83 = createNextBlock(b82, chainHeadHeight + 29, null, null);
{
Transaction tx1 = new Transaction(params);
tx1.addOutput(new TransactionOutput(params, tx1, out28.value,
new byte[]{OP_IF, (byte) OP_INVALIDOPCODE, OP_ELSE, OP_TRUE, OP_ENDIF}));
addOnlyInputToTransaction(tx1, out28, 0);
b83.addTransaction(tx1);
Transaction tx2 = new Transaction(params);
tx2.addOutput(new TransactionOutput(params, tx2, ZERO, new byte[]{OP_TRUE}));
tx2.addInput(new TransactionInput(params, tx2, new byte[]{OP_FALSE},
new TransactionOutPoint(params, 0, tx1.getHash())));
b83.addTransaction(tx2);
}
b83.solve();
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)
// \-> b85 (29) -> b86 (30) \-> b89 (32)
//
TransactionOutPointWithValue out29 = spendableOutputs.poll(); Preconditions.checkState(out29 != null);
TransactionOutPointWithValue out30 = spendableOutputs.poll(); Preconditions.checkState(out30 != null);
TransactionOutPointWithValue out31 = spendableOutputs.poll(); Preconditions.checkState(out31 != null);
TransactionOutPointWithValue out32 = spendableOutputs.poll(); Preconditions.checkState(out32 != 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}));
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}));
b84tx1.addOutput(new TransactionOutput(params, b84tx1, ZERO, new byte[]{OP_TRUE}));
addOnlyInputToTransaction(b84tx1, b84);
b84.addTransaction(b84tx1);
Transaction tx2 = new Transaction(params);
tx2.addOutput(new TransactionOutput(params, tx2, ZERO, new byte[]{OP_RETURN}));
tx2.addOutput(new TransactionOutput(params, tx2, ZERO, new byte[]{OP_RETURN}));
tx2.addInput(new TransactionInput(params, tx2, new byte[]{OP_TRUE}, new TransactionOutPoint(params, 1, b84tx1)));
b84.addTransaction(tx2);
Transaction tx3 = new Transaction(params);
tx3.addOutput(new TransactionOutput(params, tx3, ZERO, new byte[]{OP_RETURN}));
tx3.addOutput(new TransactionOutput(params, tx3, ZERO, new byte[]{OP_TRUE}));
tx3.addInput(new TransactionInput(params, tx3, new byte[]{OP_TRUE}, new TransactionOutPoint(params, 2, b84tx1)));
b84.addTransaction(tx3);
Transaction tx4 = new Transaction(params);
tx4.addOutput(new TransactionOutput(params, tx4, ZERO, new byte[]{OP_TRUE}));
tx4.addOutput(new TransactionOutput(params, tx4, ZERO, new byte[]{OP_RETURN}));
tx4.addInput(new TransactionInput(params, tx4, new byte[]{OP_TRUE}, new TransactionOutPoint(params, 3, b84tx1)));
b84.addTransaction(tx4);
Transaction tx5 = new Transaction(params);
tx5.addOutput(new TransactionOutput(params, tx5, ZERO, new byte[]{OP_RETURN}));
tx5.addInput(new TransactionInput(params, tx5, new byte[]{OP_TRUE}, new TransactionOutPoint(params, 4, b84tx1)));
b84.addTransaction(tx5);
}
b84.solve();
blocks.add(new BlockAndValidity(b84, true, false, b84.getHash(), chainHeadHeight + 30, "b84"));
spendableOutputs.offer(b84.getCoinbaseOutput());
NewBlock b85 = createNextBlock(b83, chainHeadHeight + 30, out29, null);
blocks.add(new BlockAndValidity(b85, true, false, b84.getHash(), chainHeadHeight + 30, "b85"));
NewBlock b86 = createNextBlock(b85, chainHeadHeight + 31, out30, null);
blocks.add(new BlockAndValidity(b86, true, false, b86.getHash(), chainHeadHeight + 31, "b86"));
NewBlock b87 = createNextBlock(b84, chainHeadHeight + 31, out30, null);
blocks.add(new BlockAndValidity(b87, true, false, b86.getHash(), chainHeadHeight + 31, "b87"));
spendableOutputs.offer(b87.getCoinbaseOutput());
NewBlock b88 = createNextBlock(b87, chainHeadHeight + 32, out31, null);
blocks.add(new BlockAndValidity(b88, true, false, b88.getHash(), chainHeadHeight + 32, "b88"));
spendableOutputs.offer(b88.getCoinbaseOutput());
NewBlock b89 = createNextBlock(b88, chainHeadHeight + 33, out32, null);
{
Transaction tx = new Transaction(params);
tx.addOutput(new TransactionOutput(params, tx, ZERO, new byte[] {OP_TRUE}));
tx.addInput(new TransactionInput(params, tx, new byte[]{OP_TRUE}, new TransactionOutPoint(params, 0, b84tx1)));
b89.addTransaction(tx);
b89.solve();
}
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
//
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);
NewBlock lastBlock = b1001;
TransactionOutPoint lastOutput = new TransactionOutPoint(params, 1, b1001.block.getTransactions().get(1).getHash());
int blockCountAfter1001;
int nextHeight = heightAfter1001;
List<Sha256Hash> hashesToSpend = new LinkedList<Sha256Hash>(); // all index 0
final int TRANSACTION_CREATION_BLOCKS = 100;
for (blockCountAfter1001 = 0; blockCountAfter1001 < TRANSACTION_CREATION_BLOCKS; blockCountAfter1001++) {
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);
tx.addOutput(ZERO, OP_TRUE_SCRIPT);
lastOutput = new TransactionOutPoint(params, 1, tx.getHash());
hashesToSpend.add(tx.getHash());
block.addTransaction(tx);
}
block.solve();
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<Sha256Hash> hashes = hashesToSpend.iterator();
for (int i = 0; hashes.hasNext(); i++) {
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(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++) {
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
NewBlock b1002 = createNextBlock(lastBlock, nextHeight, null, null);
{
Transaction tx = new Transaction(params);
tx.addInput(hashesToSpend.get(0), 0, OP_NOP_SCRIPT);
tx.addOutput(ZERO, OP_TRUE_SCRIPT);
b1002.addTransaction(tx);
}
b1002.solve();
blocks.add(new BlockAndValidity(b1002, false, true, firstHash, height, "b1002"));
// Now actually reorg
NewBlock b1003 = createNextBlock(lastBlock, nextHeight, null, null);
blocks.add(new BlockAndValidity(b1003, true, false, b1003.getHash(), nextHeight, "b1003"));
// Now try to spend again
NewBlock b1004 = createNextBlock(b1003, nextHeight + 1, null, null);
{
Transaction tx = new Transaction(params);
tx.addInput(hashesToSpend.get(0), 0, OP_NOP_SCRIPT);
tx.addOutput(ZERO, OP_TRUE_SCRIPT);
b1004.addTransaction(tx);
}
b1004.solve();
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 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);
Coin coinbaseValue = FIFTY_COINS.shiftRight(nextBlockHeight / params.getSubsidyDecreaseBlockCount())
.add((prevOut != null ? prevOut.value.subtract(SATOSHI) : ZERO))
.add(additionalCoinbaseValue == null ? ZERO : additionalCoinbaseValue);
Block block = baseBlock.createNextBlockWithCoinbase(Block.BLOCK_VERSION_GENESIS, coinbaseOutKeyPubKey, coinbaseValue, nextBlockHeight);
Transaction t = new Transaction(params);
if (prevOut != null) {
// Entirely invalid scriptPubKey to ensure we aren't pre-verifying too much
t.addOutput(new TransactionOutput(params, t, ZERO, new byte[] {(byte)(new Random().nextInt() & 0xff), uniquenessCounter++}));
// Spendable output
t.addOutput(new TransactionOutput(params, t, SATOSHI, new byte[] {OP_1}));
addOnlyInputToTransaction(t, prevOut);
block.addTransaction(t);
block.solve();
}
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);
t.addInput(input);
if (prevOut.scriptPubKey.getChunks().get(0).equalsOpCode(OP_TRUE)) {
input.setScriptSig(new ScriptBuilder().op(OP_1).build());
} else {
// Sign input
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))
);
}
}
/**
* 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;
}
}
}