3
0
mirror of https://github.com/Qortal/altcoinj.git synced 2025-02-13 02:35:52 +00:00

Fix script parsing bug and test for it (partially reverts 7837a54)

This commit is contained in:
Matt Corallo 2013-05-19 16:30:23 +02:00 committed by Mike Hearn
parent e8ea5cea7c
commit 304bc705b8
2 changed files with 121 additions and 11 deletions

View File

@ -35,6 +35,7 @@ import java.util.*;
import static com.google.bitcoin.script.ScriptOpCodes.*;
import static com.google.bitcoin.core.Utils.bytesToHexString;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;
// TODO: Make this class a superclass with derived classes giving accessor methods for the various common templates.
@ -151,17 +152,16 @@ public class Script {
// Read a uint32, then read that many bytes of data.
// Though this is allowed, because its value cannot be > 520, it should never actually be used
if (bis.available() < 4) throw new ScriptException("Unexpected end of script");
dataToRead = bis.read() | (bis.read() << 8) | (bis.read() << 16) | (bis.read() << 24);
dataToRead = ((long)bis.read()) | (((long)bis.read()) << 8) | (((long)bis.read()) << 16) | (((long)bis.read()) << 24);
}
if (dataToRead == -1) {
chunks.add(new ScriptChunk(true, new byte[]{(byte) opcode}, startLocationInProgram));
} else {
if (dataToRead > MAX_SCRIPT_ELEMENT_SIZE)
throw new ScriptException("Push of data element that is larger than the max element size");
if (dataToRead > bis.available())
throw new ScriptException("Push of data element that is larger than remaining data");
byte[] data = new byte[(int)dataToRead];
if (dataToRead > 0 && bis.read(data, 0, (int)dataToRead) < dataToRead)
throw new ScriptException("Unexpected end of script");
checkState(dataToRead == 0 || bis.read(data, 0, (int)dataToRead) == dataToRead);
chunks.add(new ScriptChunk(false, data, startLocationInProgram));
}
}
@ -536,6 +536,9 @@ public class Script {
boolean shouldExecute = !ifStack.contains(false);
if (!chunk.isOpCode()) {
if (chunk.data.length > MAX_SCRIPT_ELEMENT_SIZE)
throw new ScriptException("Attempted to push a data string larger than 520 bytes");
if (!shouldExecute)
continue;

View File

@ -1313,6 +1313,112 @@ public class FullBlockTestGenerator {
b72.getTransactions().get(0).getOutputs().get(0).getValue(),
b72.getTransactions().get(0).getOutputs().get(0).getScriptPubKey()));
// 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(); Preconditions.checkState(out22 != null);
Block b73 = createNextBlock(b72, chainHeadHeight + 23, out22, null);
{
int sigOps = 0;
for (Transaction tx : b73.transactions) {
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, BigInteger.valueOf(1), outputScript));
addOnlyInputToTransaction(tx, new TransactionOutPointWithValue(
new TransactionOutPoint(params, 1, b73.getTransactions().get(1).getHash()),
BigInteger.valueOf(1), b73.getTransactions().get(1).getOutputs().get(1).getScriptPubKey()));
b73.addTransaction(tx);
}
b73.solve();
blocks.add(new BlockAndValidity(blockToHeightMap, b73, false, true, b72.getHash(), chainHeadHeight + 22, "b73"));
Block b74 = createNextBlock(b72, chainHeadHeight + 23, out22, null);
{
int sigOps = 0;
for (Transaction tx : b74.transactions) {
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, BigInteger.valueOf(1), outputScript));
addOnlyInputToTransaction(tx, new TransactionOutPointWithValue(
new TransactionOutPoint(params, 1, b74.getTransactions().get(1).getHash()),
BigInteger.valueOf(1), b74.getTransactions().get(1).getOutputs().get(1).getScriptPubKey()));
b74.addTransaction(tx);
}
b74.solve();
blocks.add(new BlockAndValidity(blockToHeightMap, b74, false, true, b72.getHash(), chainHeadHeight + 22, "b74"));
Block b75 = createNextBlock(b72, chainHeadHeight + 23, out22, null);
{
int sigOps = 0;
for (Transaction tx : b75.transactions) {
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, BigInteger.valueOf(1), outputScript));
addOnlyInputToTransaction(tx, new TransactionOutPointWithValue(
new TransactionOutPoint(params, 1, b75.getTransactions().get(1).getHash()),
BigInteger.valueOf(1), b75.getTransactions().get(1).getOutputs().get(1).getScriptPubKey()));
b75.addTransaction(tx);
}
b75.solve();
blocks.add(new BlockAndValidity(blockToHeightMap, b75, true, false, b75.getHash(), chainHeadHeight + 23, "b75"));
spendableOutputs.offer(new TransactionOutPointWithValue(
new TransactionOutPoint(params, 0, b75.getTransactions().get(0).getHash()),
b75.getTransactions().get(0).getOutputs().get(0).getValue(),
b75.getTransactions().get(0).getOutputs().get(0).getScriptPubKey()));
TransactionOutPointWithValue out23 = spendableOutputs.poll(); Preconditions.checkState(out23 != null);
Block b76 = createNextBlock(b75, chainHeadHeight + 24, out23, null);
{
int sigOps = 0;
for (Transaction tx : b76.transactions) {
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, BigInteger.valueOf(1), outputScript));
addOnlyInputToTransaction(tx, new TransactionOutPointWithValue(
new TransactionOutPoint(params, 1, b76.getTransactions().get(1).getHash()),
BigInteger.valueOf(1), b76.getTransactions().get(1).getOutputs().get(1).getScriptPubKey()));
b76.addTransaction(tx);
}
b76.solve();
blocks.add(new BlockAndValidity(blockToHeightMap, b76, true, false, b76.getHash(), chainHeadHeight + 24, "b76"));
spendableOutputs.offer(new TransactionOutPointWithValue(
new TransactionOutPoint(params, 0, b76.getTransactions().get(0).getHash()),
b76.getTransactions().get(0).getOutputs().get(0).getValue(),
b76.getTransactions().get(0).getOutputs().get(0).getScriptPubKey()));
// The remaining tests arent designed to fit in the standard flow, and thus must always come last
// Add new tests here.
@ -1321,9 +1427,10 @@ public class FullBlockTestGenerator {
// Reorg back to:
// -> b60 (17) -> b64 (18) -> b65 (19) -> b69 (20) -> b72 (21) -> b1001 (22) -> empty blocks
//
TransactionOutPointWithValue out22 = spendableOutputs.poll(); Preconditions.checkState(out22 != null);
Block b1001 = createNextBlock(b72, chainHeadHeight + 23, out22, null);
blocks.add(new BlockAndValidity(blockToHeightMap, b1001, true, false, b1001.getHash(), chainHeadHeight + 23, "b1001"));
TransactionOutPointWithValue out24 = spendableOutputs.poll(); Preconditions.checkState(out24 != null);
Block b1001 = createNextBlock(b76, chainHeadHeight + 25, out24, null);
blocks.add(new BlockAndValidity(blockToHeightMap, b1001, true, false, b1001.getHash(), chainHeadHeight + 25, "b1001"));
spendableOutputs.offer(new TransactionOutPointWithValue(
new TransactionOutPoint(params, 0, b1001.getTransactions().get(0).getHash()),
b1001.getTransactions().get(0).getOutputs().get(0).getValue(),
@ -1334,7 +1441,7 @@ public class FullBlockTestGenerator {
Preconditions.checkArgument(blockStorageFile != null);
Block lastBlock = b1001;
int nextHeight = chainHeadHeight + 24;
int nextHeight = chainHeadHeight + 26;
TransactionOutPoint lastOutput = new TransactionOutPoint(params, 2, b1001.getTransactions().get(1).getHash());
int blockCountAfter1001;
@ -1376,7 +1483,7 @@ public class FullBlockTestGenerator {
// Reorg back to b1001 + empty blocks
Sha256Hash firstHash = lastBlock.getHash();
int height = nextHeight-1;
nextHeight = chainHeadHeight + 24;
nextHeight = chainHeadHeight + 26;
lastBlock = b1001;
for (int i = 0; i < blockCountAfter1001; i++) {
Block block = createNextBlock(lastBlock, nextHeight++, null, null);