3
0
mirror of https://github.com/Qortal/altcoinj.git synced 2025-01-30 23:02:15 +00:00

Count P2SH SigOps the way the reference client does.

This commit is contained in:
Matt Corallo 2012-07-13 18:17:35 +02:00 committed by Mike Hearn
parent 7ca87c078c
commit c789b757f3
4 changed files with 86 additions and 3 deletions

View File

@ -100,6 +100,8 @@ public class FullPrunedBlockChain extends AbstractBlockChain {
LinkedList<StoredTransactionOutput> txOutsSpent = new LinkedList<StoredTransactionOutput>();
LinkedList<StoredTransactionOutput> txOutsCreated = new LinkedList<StoredTransactionOutput>();
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<StoredTransactionOutput> txOutsSpent = new LinkedList<StoredTransactionOutput>();
LinkedList<StoredTransactionOutput> txOutsCreated = new LinkedList<StoredTransactionOutput>();
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);

View File

@ -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
*/

View File

@ -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;
}
/**
* <p>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 <a href="https://en.bitcoin.it/wiki/BIP_0016">BIP 16</a>.</p>
*
* <p>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).</p>
*/
public boolean isPayToScriptHash() {
return program.length == 23 &&
(program[0] & 0xff) == OP_HASH160 &&
(program[1] & 0xff) == 0x14 &&
(program[22] & 0xff) == OP_EQUAL;
}
}

View File

@ -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
* <p>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 <a href="http://en.bitcoin.it/wiki/Contracts">contracts</a>, such as
* micropayment channels.</p>
*
* <p>Note that currently the replacement feature is disabled in the Satoshi client and will need to be
* re-activated before this functionality is useful.</p>
*/
public boolean isFinal(int height, long blockTimeSeconds) {
// Time based nLockTime implemented in 0.1.6