Verify SigOp counts when verifying Blocks.

This commit is contained in:
Matt Corallo 2012-07-11 01:18:15 +02:00 committed by Mike Hearn
parent 1cbd5c07b4
commit 626ff2fa2c
3 changed files with 99 additions and 0 deletions

View File

@ -52,6 +52,19 @@ public class Block extends Message {
static final long ALLOWED_TIME_DRIFT = 2 * 60 * 60; // Same value as official client.
/**
* A constant shared by the entire network: how large in bytes a block is allowed to be. One day we may have to
* upgrade everyone to change this, so Bitcoin can continue to grow. For now it exists as an anti-DoS measure to
* avoid somebody creating a titanically huge but valid block and forcing everyone to download/store it forever.
*/
public static final int MAX_BLOCK_SIZE = 1 * 1000 * 1000;
/**
* A "sigop" is a signature verification operation. Because they're expensive we also impose a separate limit on
* the number in a block to prevent somebody mining a huge block that has way more sigops than normal, so is very
* expensive/slow to verify.
*/
public static final int MAX_BLOCK_SIGOPS = MAX_BLOCK_SIZE / 50;
/** A value for difficultyTarget (nBits) that allows half of all possible hash solutions. Used in unit testing. */
static final long EASIEST_DIFFICULTY_TARGET = 0x207fFFFFL;
@ -569,6 +582,21 @@ public class Block extends Message {
if (time > currentTime + ALLOWED_TIME_DRIFT)
throw new VerificationException("Block too far in future");
}
private void checkSigOps() throws VerificationException {
// Check there aren't too many signature verifications in the block. This is an anti-DoS measure, see the
// comments for MAX_BLOCK_SIGOPS.
int sigOps = 0;
for (Transaction tx : transactions) {
try {
sigOps += tx.getSigOpCount();
} catch (ScriptException e) {
throw new VerificationException("Unreadable script in transaction");
}
}
if (sigOps > MAX_BLOCK_SIGOPS)
throw new VerificationException("Block had too many Signature Operations");
}
private void checkMerkleRoot() throws VerificationException {
Sha256Hash calculatedRoot = calculateMerkleRoot();
@ -683,6 +711,7 @@ public class Block extends Message {
maybeParseTransactions();
checkTransactions();
checkMerkleRoot();
checkSigOps();
}
/**

View File

@ -195,6 +195,11 @@ public class Script {
// The program is a set of byte[]s where each element is either [opcode] or [data, data, data ...]
List<ScriptChunk> chunks;
private final NetworkParameters params;
// Only for internal use
private Script() {
params = null;
}
/**
* Construct a Script using the given network parameters and a range of the programBytes array.
@ -694,4 +699,56 @@ public class Script {
throw new RuntimeException(e);
}
}
////////////////////// Interface used during verification of transactions/blocks ////////////////////////////////
private static int getSigOpCount(List<ScriptChunk> chunks, boolean accurate) throws ScriptException {
int sigOps = 0;
int lastOpCode = OP_INVALIDOPCODE;
for (ScriptChunk chunk : chunks) {
if (chunk.isOpCode) {
int opcode = 0xFF & chunk.data[0];
switch (opcode) {
case OP_CHECKSIG:
case OP_CHECKSIGVERIFY:
sigOps++;
break;
case OP_CHECKMULTISIG:
case OP_CHECKMULTISIGVERIFY:
if (accurate && lastOpCode >= OP_1 && lastOpCode <= OP_16)
sigOps += getOpNValue(lastOpCode);
else
sigOps += 20;
default:
break;
}
lastOpCode = opcode;
}
}
return sigOps;
}
/**
* Convince method to get the int value of OP_N
*/
private static int getOpNValue(int opcode) throws ScriptException {
if (opcode == OP_0)
return 0;
if (opcode < OP_1 || opcode > OP_16) // This should absolutely never happen
throw new ScriptException("getOpNValue called on non OP_N opcode");
return opcode + 1 - OP_1;
}
/**
* Gets the count of regular SigOps in the script program (counting multisig ops as 20)
*/
public static int getSigOpCount(byte[] program) throws ScriptException {
Script script = new Script();
try {
script.parse(program, 0, program.length);
} catch (ScriptException e) {
// Ignore errors and count up to the parse-able length
}
return getSigOpCount(script.chunks, false);
}
}

View File

@ -869,4 +869,17 @@ public class Transaction extends ChildMessage implements Serializable {
maybeParse();
out.defaultWriteObject();
}
/**
* Gets the count of regular SigOps in this transactions
*/
public int getSigOpCount() throws ScriptException {
maybeParse();
int sigOps = 0;
for (TransactionInput input : inputs)
sigOps += Script.getSigOpCount(input.getScriptBytes());
for (TransactionOutput output : outputs)
sigOps += Script.getSigOpCount(output.getScriptBytes());
return sigOps;
}
}