diff --git a/src/main.cpp b/src/main.cpp index 9b5a69865..84edf5bb9 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1004,8 +1004,7 @@ bool CheckSequenceLocks(const CTransaction &tx, int flags, LockPoints* lp, bool return EvaluateSequenceLocks(index, lockPair); } - -unsigned int GetLegacySigOpCount(const CTransaction& tx) +unsigned int GetSigOpCountWithoutP2SH(const CTransaction& tx) { unsigned int nSigOps = 0; BOOST_FOREACH(const CTxIn& txin, tx.vin) @@ -1034,12 +1033,19 @@ unsigned int GetP2SHSigOpCount(const CTransaction& tx, const CCoinsViewCache& in return nSigOps; } +uint64_t GetTransactionSigOpCount(const CTransaction &tx, const CCoinsViewCache &inputs, int flags) +{ + uint64_t nSigOps = GetSigOpCountWithoutP2SH(tx); + if (tx.IsCoinBase()) { + return nSigOps; + } + if (flags & SCRIPT_VERIFY_P2SH) { + nSigOps += GetP2SHSigOpCount(tx, inputs); + } - - - - + return nSigOps; +} bool CheckTransaction(const CTransaction& tx, CValidationState &state) { @@ -1230,7 +1236,7 @@ bool AcceptToMemoryPoolWorker(CTxMemPool& pool, CValidationState& state, const C if (fRequireStandard && !AreInputsStandard(tx, view)) return state.Invalid(false, REJECT_NONSTANDARD, "bad-txns-nonstandard-inputs"); - unsigned int nSigOps = GetLegacySigOpCount(tx); + unsigned int nSigOps = GetSigOpCountWithoutP2SH(tx); nSigOps += GetP2SHSigOpCount(tx, view); CAmount nValueOut = tx.GetValueOut(); @@ -2453,7 +2459,7 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin const CTransaction &tx = block.vtx[i]; nInputs += tx.vin.size(); - nSigOps += GetLegacySigOpCount(tx); + nSigOps += GetSigOpCountWithoutP2SH(tx); if (nSigOps > MAX_BLOCK_SIGOPS) return state.DoS(100, error("ConnectBlock(): too many sigops"), REJECT_INVALID, "bad-blk-sigops"); @@ -3516,7 +3522,7 @@ bool CheckBlock(const CBlock& block, CValidationState& state, const Consensus::P unsigned int nSigOps = 0; BOOST_FOREACH(const CTransaction& tx, block.vtx) { - nSigOps += GetLegacySigOpCount(tx); + nSigOps += GetSigOpCountWithoutP2SH(tx); } if (nSigOps > MAX_BLOCK_SIGOPS) return state.DoS(100, error("CheckBlock(): out-of-bounds SigOpCount"), diff --git a/src/main.h b/src/main.h index 782d5b42a..894ba11c9 100644 --- a/src/main.h +++ b/src/main.h @@ -330,7 +330,7 @@ struct CNodeStateStats { * @return number of sigops this transaction's outputs will produce when spent * @see CTransaction::FetchInputs */ -unsigned int GetLegacySigOpCount(const CTransaction& tx); +unsigned int GetSigOpCountWithoutP2SH(const CTransaction& tx); /** * Count ECDSA signature operations in pay-to-script-hash inputs. @@ -341,6 +341,14 @@ unsigned int GetLegacySigOpCount(const CTransaction& tx); */ unsigned int GetP2SHSigOpCount(const CTransaction& tx, const CCoinsViewCache& mapInputs); +/** + * Compute total signature operation cost of a transaction. + * @param[in] tx Transaction for which we are computing the cost + * @param[in] inputs Map of previous transactions that have outputs we're spending + * @param[out] flags Script verification flags + * @return Total signature operation cost of tx + */ +int64_t GetTransactionSigOpCount(const CTransaction& tx, const CCoinsViewCache& inputs, int flags); /** * Check whether all inputs of this transaction are valid (no double spends, scripts & sigs, amounts) diff --git a/src/miner.cpp b/src/miner.cpp index 96ea1b429..93f6994d9 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -185,7 +185,7 @@ CBlockTemplate* BlockAssembler::CreateNewBlock(const CScript& scriptPubKeyIn, in UpdateTime(pblock, chainparams.GetConsensus(), pindexPrev); pblock->nBits = GetNextTargetRequired(pindexPrev, pblock, chainparams.GetConsensus(), fProofOfStake); pblock->nNonce = 0; - pblocktemplate->vTxSigOpsCost[0] = GetLegacySigOpCount(pblock->vtx[0]); + pblocktemplate->vTxSigOpsCost[0] = GetSigOpCountWithoutP2SH(pblock->vtx[0]); CValidationState state; if (!fProofOfStake && !TestBlockValidity(state, chainparams, *pblock, pindexPrev, false, false, true)) { diff --git a/src/test/sigopcount_tests.cpp b/src/test/sigopcount_tests.cpp index a207fd921..5cbe7e354 100644 --- a/src/test/sigopcount_tests.cpp +++ b/src/test/sigopcount_tests.cpp @@ -2,6 +2,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#include "main.h" #include "pubkey.h" #include "key.h" #include "script/script.h" @@ -64,4 +65,93 @@ BOOST_AUTO_TEST_CASE(GetSigOpCount) BOOST_CHECK_EQUAL(p2sh.GetSigOpCount(scriptSig2), 3U); } +/** + * Verifies script execution of the zeroth scriptPubKey of tx output and + * zeroth scriptSig and witness of tx input. + */ +ScriptError VerifyWithFlag(const CTransaction& output, const CMutableTransaction& input, int flags) +{ + ScriptError error; + CTransaction inputi(input); + bool ret = VerifyScript(inputi.vin[0].scriptSig, output.vout[0].scriptPubKey, flags, TransactionSignatureChecker(&inputi, 0, output.vout[0].nValue), &error); + BOOST_CHECK((ret == true) == (error == SCRIPT_ERR_OK)); + + return error; +} + +/** + * Builds a creationTx from scriptPubKey and a spendingTx from scriptSig + * such that spendingTx spends output zero of creationTx. + * Also inserts creationTx's output into the coins view. + */ +void BuildTxs(CMutableTransaction& spendingTx, CCoinsViewCache& coins, CMutableTransaction& creationTx, const CScript& scriptPubKey, const CScript& scriptSig) +{ + creationTx.nVersion = 1; + creationTx.vin.resize(1); + creationTx.vin[0].prevout.SetNull(); + creationTx.vin[0].scriptSig = CScript(); + creationTx.vout.resize(1); + creationTx.vout[0].nValue = 1; + creationTx.vout[0].scriptPubKey = scriptPubKey; + + spendingTx.nVersion = 1; + spendingTx.vin.resize(1); + spendingTx.vin[0].prevout.hash = creationTx.GetHash(); + spendingTx.vin[0].prevout.n = 0; + spendingTx.vin[0].scriptSig = scriptSig; + spendingTx.vout.resize(1); + spendingTx.vout[0].nValue = 1; + spendingTx.vout[0].scriptPubKey = CScript(); + + coins.ModifyCoins(creationTx.GetHash())->FromTx(creationTx, 0); +} + +BOOST_AUTO_TEST_CASE(GetTxSigOpCost) +{ + // Transaction creates outputs + CMutableTransaction creationTx; + // Transaction that spends outputs and whose + // sig op cost is going to be tested + CMutableTransaction spendingTx; + + // Create utxo set + CCoinsView coinsDummy; + CCoinsViewCache coins(&coinsDummy); + // Create key + CKey key; + key.MakeNewKey(true); + CPubKey pubkey = key.GetPubKey(); + // Default flags + int flags = SCRIPT_VERIFY_P2SH; + + // Multisig script + { + CScript scriptPubKey = CScript() << 1 << ToByteVector(pubkey) << ToByteVector(pubkey) << 2 << OP_CHECKMULTISIGVERIFY; + // Do not use a valid signature to avoid using wallet operations. + CScript scriptSig = CScript() << OP_0 << OP_0; + + BuildTxs(spendingTx, coins, creationTx, scriptPubKey, scriptSig); + // Legacy counting only includes signature operations in scriptSigs and scriptPubKeys + // of a transaction and does not take the actual executed sig operations into account. + // spendingTx in itself does not contain a signature operation. + assert(GetTransactionSigOpCount(CTransaction(spendingTx), coins, flags) == 0); + // creationTx contains two signature operations in its scriptPubKey, but legacy counting + // is not accurate. + assert(GetTransactionSigOpCount(CTransaction(creationTx), coins, flags) == MAX_PUBKEYS_PER_MULTISIG); + // Sanity check: script verification fails because of an invalid signature. + assert(VerifyWithFlag(creationTx, spendingTx, flags) == SCRIPT_ERR_CHECKMULTISIGVERIFY); + } + + // Multisig nested in P2SH + { + CScript redeemScript = CScript() << 1 << ToByteVector(pubkey) << ToByteVector(pubkey) << 2 << OP_CHECKMULTISIGVERIFY; + CScript scriptPubKey = GetScriptForDestination(CScriptID(redeemScript)); + CScript scriptSig = CScript() << OP_0 << OP_0 << ToByteVector(redeemScript); + + BuildTxs(spendingTx, coins, creationTx, scriptPubKey, scriptSig)); + assert(GetTransactionSigOpCount(CTransaction(spendingTx), coins, flags) == 2); + assert(VerifyWithFlag(creationTx, spendingTx, flags) == SCRIPT_ERR_CHECKMULTISIGVERIFY); + } +} + BOOST_AUTO_TEST_SUITE_END()