From c789b757f3dcf2865cb7c21e61544434a159592a Mon Sep 17 00:00:00 2001 From: Matt Corallo Date: Fri, 13 Jul 2012 18:17:35 +0200 Subject: [PATCH] Count P2SH SigOps the way the reference client does. --- .../bitcoin/core/FullPrunedBlockChain.java | 35 ++++++++++++++++- .../bitcoin/core/NetworkParameters.java | 7 ++++ .../java/com/google/bitcoin/core/Script.java | 39 +++++++++++++++++++ .../com/google/bitcoin/core/Transaction.java | 8 +++- 4 files changed, 86 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/com/google/bitcoin/core/FullPrunedBlockChain.java b/core/src/main/java/com/google/bitcoin/core/FullPrunedBlockChain.java index 58297909..d2744cb4 100644 --- a/core/src/main/java/com/google/bitcoin/core/FullPrunedBlockChain.java +++ b/core/src/main/java/com/google/bitcoin/core/FullPrunedBlockChain.java @@ -99,7 +99,9 @@ public class FullPrunedBlockChain extends AbstractBlockChain { blockStore.beginDatabaseBatchWrite(); LinkedList txOutsSpent = new LinkedList(); - LinkedList txOutsCreated = new LinkedList(); + LinkedList txOutsCreated = new LinkedList(); + long sigOps = 0; + boolean enforceBIP16 = block.getTimeSeconds() >= params.BIP16_ENFORCE_TIME; try { if (!params.isCheckpoint(height)) { // BIP30 violator blocks are ones that contain a duplicated transaction. They are all in the @@ -111,6 +113,13 @@ public class FullPrunedBlockChain extends AbstractBlockChain { // being added twice (bug) or the block is a BIP30 violator. if (blockStore.hasUnspentOutputs(hash, tx.getOutputs().size())) throw new VerificationException("Block failed BIP30 test!"); + if (enforceBIP16) { // We already check non-BIP16 sigops in Block.verifyTransactions(true) + try { + sigOps += tx.getSigOpCount(); + } catch (ScriptException e) { + throw new VerificationException("Invalid script in transaction"); + } + } } } for (Transaction tx : block.transactions) { @@ -124,6 +133,16 @@ public class FullPrunedBlockChain extends AbstractBlockChain { if (prevOut == null) throw new VerificationException("Attempted to spend a non-existent or already spent output!"); // TODO: Check we're not spending the genesis transaction here. Satoshis code won't allow it. + if (enforceBIP16) { + try { + if (new Script(params, prevOut.getScriptBytes(), 0, prevOut.getScriptBytes().length).isPayToScriptHash()) + sigOps += Script.getP2SHSigOpCount(in.getScriptBytes()); + } catch (ScriptException e) { + throw new VerificationException("Error reading script in transaction"); + } + if (sigOps > Block.MAX_BLOCK_SIGOPS) + throw new VerificationException("Too many P2SH SigOps in block"); + } //TODO: check script here blockStore.removeUnspentTransactionOutput(prevOut); txOutsSpent.add(prevOut); @@ -170,8 +189,9 @@ public class FullPrunedBlockChain extends AbstractBlockChain { if (transactions != null) { LinkedList txOutsSpent = new LinkedList(); LinkedList txOutsCreated = new LinkedList(); + long sigOps = 0; + boolean enforcePayToScriptHash = newBlock.getHeader().getTimeSeconds() >= params.BIP16_ENFORCE_TIME; if (!params.isCheckpoint(newBlock.getHeight())) { - // See explanation above. for(StoredTransaction tx : transactions) { Sha256Hash hash = tx.getHash(); if (blockStore.hasUnspentOutputs(hash, tx.getOutputs().size())) @@ -186,6 +206,17 @@ public class FullPrunedBlockChain extends AbstractBlockChain { in.getOutpoint().getIndex()); if (prevOut == null) throw new VerificationException("Attempted spend of a non-existent or already spent output!"); + if (enforcePayToScriptHash) { + try { + Script script = new Script(params, prevOut.getScriptBytes(), 0, prevOut.getScriptBytes().length); + if (script.isPayToScriptHash()) + sigOps += Script.getP2SHSigOpCount(in.getScriptBytes()); + } catch (ScriptException e) { + throw new VerificationException("Error reading script in transaction"); + } + if (sigOps > Block.MAX_BLOCK_SIGOPS) + throw new VerificationException("Too many P2SH SigOps in block"); + } //TODO: check script here blockStore.removeUnspentTransactionOutput(prevOut); txOutsSpent.add(prevOut); diff --git a/core/src/main/java/com/google/bitcoin/core/NetworkParameters.java b/core/src/main/java/com/google/bitcoin/core/NetworkParameters.java index d1159d1e..b167d269 100644 --- a/core/src/main/java/com/google/bitcoin/core/NetworkParameters.java +++ b/core/src/main/java/com/google/bitcoin/core/NetworkParameters.java @@ -153,6 +153,13 @@ public class NetworkParameters implements Serializable { public static final int TARGET_SPACING = 10 * 60; // 10 minutes per block. public static final int INTERVAL = TARGET_TIMESPAN / TARGET_SPACING; + /** + * Blocks with a timestamp after this should enforce BIP 16, aka "Pay to script hash". This BIP changed the + * network rules in a soft-forking manner, that is, blocks that don't follow the rules are accepted but not + * mined upon and thus will be quickly re-orged out as long as the majority are enforcing the rule. + */ + public final int BIP16_ENFORCE_TIME = 1333238400; + /** * The maximum money to be generated */ diff --git a/core/src/main/java/com/google/bitcoin/core/Script.java b/core/src/main/java/com/google/bitcoin/core/Script.java index 94dad397..54a9f91f 100644 --- a/core/src/main/java/com/google/bitcoin/core/Script.java +++ b/core/src/main/java/com/google/bitcoin/core/Script.java @@ -751,4 +751,43 @@ public class Script { } return getSigOpCount(script.chunks, false); } + + /** + * Gets the count of P2SH Sig Ops in the Script scriptSig + */ + public static long getP2SHSigOpCount(byte[] scriptSig) throws ScriptException { + Script script = new Script(); + try { + script.parse(scriptSig, 0, scriptSig.length); + } catch (ScriptException e) { + // Ignore errors and count up to the parse-able length + } + for (int i = script.chunks.size() - 1; i >= 0; i--) + if (!script.chunks.get(i).isOpCode) { + Script subScript = new Script(); + subScript.parse(script.chunks.get(i).data, 0, script.chunks.get(i).data.length); + return getSigOpCount(subScript.chunks, true); + } + return 0; + } + + /** + *

Whether or not this is a scriptPubKey representing a pay-to-script-hash output. In such outputs, the logic that + * controls reclamation is not actually in the output at all. Instead there's just a hash, and it's up to the + * spending input to provide a program matching that hash. This rule is "soft enforced" by the network as it does + * not exist in Satoshis original implementation. It means blocks containing P2SH transactions that don't match + * correctly are considered valid, but won't be mined upon, so they'll be rapidly re-orgd out of the chain. This + * logic is defined by BIP 16.

+ * + *

bitcoinj does not support creation of P2SH transactions today. The goal of P2SH is to allow short addresses + * even for complex scripts (eg, multi-sig outputs) so they are convenient to work with in things like QRcodes or + * with copy/paste, and also to minimize the size of the unspent output set (which improves performance of the + * Bitcoin system).

+ */ + public boolean isPayToScriptHash() { + return program.length == 23 && + (program[0] & 0xff) == OP_HASH160 && + (program[1] & 0xff) == 0x14 && + (program[22] & 0xff) == OP_EQUAL; + } } diff --git a/core/src/main/java/com/google/bitcoin/core/Transaction.java b/core/src/main/java/com/google/bitcoin/core/Transaction.java index 7f348e8c..912c5a86 100644 --- a/core/src/main/java/com/google/bitcoin/core/Transaction.java +++ b/core/src/main/java/com/google/bitcoin/core/Transaction.java @@ -919,7 +919,13 @@ public class Transaction extends ChildMessage implements Serializable { } /** - * Returns true if this transaction is considered finalized and can be placed in a block + *

Returns true if this transaction is considered finalized and can be placed in a block. Non-finalized + * transactions won't be included by miners and can be replaced with newer versions using sequence numbers. + * This is useful in certain types of contracts, such as + * micropayment channels.

+ * + *

Note that currently the replacement feature is disabled in the Satoshi client and will need to be + * re-activated before this functionality is useful.

*/ public boolean isFinal(int height, long blockTimeSeconds) { // Time based nLockTime implemented in 0.1.6