3
0
mirror of https://github.com/Qortal/altcoinj.git synced 2025-02-07 06:44:16 +00:00

Add a few more tests to FullBlockTestGenerator.

...including one which (somewhat) tests the optimally encoded size
stuff.
This commit is contained in:
Matt Corallo 2012-12-30 13:53:48 -05:00 committed by Mike Hearn
parent 810b03dd0a
commit 0208b426f5
2 changed files with 173 additions and 5 deletions

View File

@ -333,7 +333,8 @@ public class Block extends Message {
} }
} }
private void writeHeader(OutputStream stream) throws IOException { // default for testing
void writeHeader(OutputStream stream) throws IOException {
// try for cached write first // try for cached write first
if (headerBytesValid && bytes != null && bytes.length >= offset + HEADER_SIZE) { if (headerBytesValid && bytes != null && bytes.length >= offset + HEADER_SIZE) {
stream.write(bytes, offset, HEADER_SIZE); stream.write(bytes, offset, HEADER_SIZE);

View File

@ -49,7 +49,7 @@ public class FullBlockTestGenerator {
Utils.rollMockClock(0); // Set a mock clock for timestamp tests Utils.rollMockClock(0); // Set a mock clock for timestamp tests
} }
public List<BlockAndValidity> getBlocksToTest(boolean addExpensiveBlocks) throws ScriptException { public List<BlockAndValidity> getBlocksToTest(boolean addExpensiveBlocks) throws ScriptException, ProtocolException, IOException {
List<BlockAndValidity> blocks = new LinkedList<BlockAndValidity>(); List<BlockAndValidity> blocks = new LinkedList<BlockAndValidity>();
Queue<TransactionOutPointWithValue> spendableOutputs = new LinkedList<TransactionOutPointWithValue>(); Queue<TransactionOutPointWithValue> spendableOutputs = new LinkedList<TransactionOutPointWithValue>();
@ -701,7 +701,6 @@ public class FullBlockTestGenerator {
} }
b40.solve(); b40.solve();
blocks.add(new BlockAndValidity(b40, false, true, b39.getHash(), "b40")); blocks.add(new BlockAndValidity(b40, false, true, b39.getHash(), "b40"));
Block b41 = null; Block b41 = null;
if (addExpensiveBlocks) { if (addExpensiveBlocks) {
@ -1054,7 +1053,7 @@ public class FullBlockTestGenerator {
// Test tx.isFinal is properly rejected (not an exhaustive tx.isFinal test, that should be in data-driven transaction tests) // 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) // -> b39 (11) -> b42 (12) -> b43 (13) -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17)
// \-> b62 (18) // \-> b62 (18)
// //
Block b62 = createNextBlock(b60, chainHeadHeight + 17, null, null); Block b62 = createNextBlock(b60, chainHeadHeight + 17, null, null);
{ {
Transaction tx = new Transaction(params); Transaction tx = new Transaction(params);
@ -1062,10 +1061,178 @@ public class FullBlockTestGenerator {
tx.addOutput(new TransactionOutput(params, tx, BigInteger.ZERO, new byte[] { Script.OP_TRUE })); tx.addOutput(new TransactionOutput(params, tx, BigInteger.ZERO, new byte[] { Script.OP_TRUE }));
addOnlyInputToTransaction(tx, out18, 0); addOnlyInputToTransaction(tx, out18, 0);
b62.addTransaction(tx); b62.addTransaction(tx);
Preconditions.checkState(!tx.isFinal(chainHeadHeight + 17, b62.getTimeSeconds()));
} }
b62.solve(); b62.solve();
blocks.add(new BlockAndValidity(b62, false, true, b60.getHash(), "b62")); 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.bitcoinSerialize(stream);
b64 = new Block(params, stream.toByteArray(), false, true, stream.size());
// The following checks are checking to ensure block serialization functions in the way needed for this test
// If they fail, it is likely not an indication of error, but an indication that this test needs rewritten
Preconditions.checkState(stream.size() == b64Created.getMessageSize() + 8);
Preconditions.checkState(stream.size() == b64.getMessageSize());
Preconditions.checkState(Arrays.equals(stream.toByteArray(), b64.bitcoinSerialize()));
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 //TODO: Explicitly address MoneyRange() checks
// (finally) return the created chain // (finally) return the created chain
@ -1075,7 +1242,7 @@ public class FullBlockTestGenerator {
private Block createNextBlock(Block baseBlock, int nextBlockHeight, TransactionOutPointWithValue prevOut, private Block createNextBlock(Block baseBlock, int nextBlockHeight, TransactionOutPointWithValue prevOut,
BigInteger additionalCoinbaseValue) throws ScriptException { BigInteger additionalCoinbaseValue) throws ScriptException {
BigInteger coinbaseValue = Utils.toNanoCoins(50, 0).shiftRight(nextBlockHeight / params.getSubsidyDecreaseBlockCount()) BigInteger coinbaseValue = Utils.toNanoCoins(50, 0).shiftRight(nextBlockHeight / params.getSubsidyDecreaseBlockCount())
.add((prevOut != null ? prevOut.value : BigInteger.valueOf(0))).subtract(BigInteger.valueOf(1)) .add((prevOut != null ? prevOut.value.subtract(BigInteger.ONE) : BigInteger.valueOf(0)))
.add(additionalCoinbaseValue == null ? BigInteger.valueOf(0) : additionalCoinbaseValue); .add(additionalCoinbaseValue == null ? BigInteger.valueOf(0) : additionalCoinbaseValue);
Block block = baseBlock.createNextBlockWithCoinbase(coinbaseOutKeyPubKey, coinbaseValue); Block block = baseBlock.createNextBlockWithCoinbase(coinbaseOutKeyPubKey, coinbaseValue);
if (prevOut != null) { if (prevOut != null) {