Commit 2aa462f8 authored by Tom Zander's avatar Tom Zander

Replace SigOps with SigChecks

This is part of the protocol upgrade for 2020-05-15, and in general it
seems to go the direction of "we did this before, lets do this again".

The spec is clear enough, but there is still a lack of questioning and
testing. The problem this attempts to fix has been neutered for years[1].

The spec states:
> The essential idea of SigChecks is to perform counting solely in the
> spending transaction, and count actual executed signature check
> operations.

This, however nobel and logical, ignores that the
check-for-being-too-costly just pulled in a UTXO lookup and the loading
of the output script from the historical chain.
The goal that we protect against CPU over-use may be reached, but the
price is a total system slowdown. You can have multiple CPUs, but the
bus to permanent storage has one, max 2 parallel pipes.

To ensure theHub stays the number one scalable node, I didn't blindly
follow the spec, while making sure that the Hub is correctly going to
follow/reject consensus violations of newly mined blocks.

As a result the implementation in Flowee the Hub:

* does not check sigcheck-counts on historical blocks (more than 1000
  blocks in the past).

  This may increase the risk of chain-splits ever so slightly, but the cost
  of disk-IO would be too high.

* No longer stores the value in the mempool, nor uses it for the
  CPU-miner.

* Ties the sigcheck-limits to the user-set block-size-accept-limit.

  This is contrary to the spec which mistakenly thinks that BCH has a
  max block-size in the consensus rules. The effect is the same, though.

* The per-intput standardness suggestion is not implemented because
  standardness checks don't currently fetch the previous outputs and
  that would be too expensive to add.

* Standardness rules for the whole transaction are moved to the
  mempool-acceptance logic instead. The cost would be too great
  otherwise, similar to the previous point.
  Again, the effect is the same as likely intented.

---
1) since the intro of the CachingTransactionSignatureChecker
parent 0386f38c
......@@ -378,7 +378,6 @@ static void addNodeRelayOptions(AllowedArgs& allowedArgs)
.addDebugArg("acceptnonstdtxn", optionalBool, strprintf("Relay and mine \"non-standard\" transactions (%sdefault: %u)", "testnet/regtest only; ", true))
.addArg("blocksizeacceptlimit=<n>", requiredAmount, strprintf("This node will not accept blocks larger than this limit. Unit is in MB (default: %.1f)", DefaultBlockAcceptSize / 1e6))
.addDebugArg("blocksizeacceptlimitbytes,excessiveblocksize=<n>", requiredInt, strprintf("This node will not accept blocks larger than this limit. Unit is in bytes. Superseded by -blocksizeacceptlimit (default: %u)", DefaultBlockAcceptSize))
.addArg("bytespersigop=<n>", requiredInt, strprintf(_("Minimum bytes per sigop in transactions we relay and mine (default: %u)"), DefaultBytesPerSigop))
.addArg("datacarrier", optionalBool, strprintf(_("Relay and mine data carrier transactions (default: %u)"), DefaultAcceptDataCarrier))
.addArg("datacarriersize=<n>", requiredInt, strprintf(_("Maximum size of data in data carrier transactions we relay and mine (default: %u)"), MaxOpReturnRelay))
.addArg("expeditedblock=<host>", requiredStr, _("Request expedited blocks from this host whenever we are connected to it"))
......
......@@ -24,8 +24,6 @@
static const unsigned int MAX_LEGACY_BLOCK_SIZE = 1000000;
/** The maximum allowed size for a serialized block, in bytes (network rule) */
static const unsigned int MAX_TX_SIZE = 1000000;
/** The maximum allowed number of signature check operations per megabyte in a block (network rule) */
static const unsigned int MAX_BLOCK_SIGOPS_PER_MB = 20000;
/** Coinbase transaction outputs can only be spent after this number of new blocks (network rule) */
static const int COINBASE_MATURITY = 100;
......
......@@ -634,9 +634,6 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler)
}
fRequireStandard = !GetBoolArg("-acceptnonstdtxn", !Params().RequireStandard());
if (Params().RequireStandard() && !fRequireStandard)
return InitError(strprintf("acceptnonstdtxn is not currently supported for %s chain", chainparams.NetworkIDString()));
nBytesPerSigOp = GetArg("-bytespersigop", nBytesPerSigOp);
#ifdef ENABLE_WALLET
if (mapArgs.count("-mintxfee"))
......
......@@ -65,7 +65,6 @@ CWaitableCriticalSection csBestBlock;
CConditionVariable cvBlockChange;
bool fIsBareMultisigStd = Settings::DefaultPermitBareMultisig;
bool fRequireStandard = true;
unsigned int nBytesPerSigOp = Settings::DefaultBytesPerSigop;
bool fCheckpointsEnabled = Settings::DefaultCheckpointsEnabled;
/** Fees smaller than this (in satoshi) are considered zero fee (for relaying, mining and transaction creation) */
......
......@@ -102,7 +102,6 @@ extern CWaitableCriticalSection csBestBlock;
extern CConditionVariable cvBlockChange;
extern bool fIsBareMultisigStd;
extern bool fRequireStandard;
extern unsigned int nBytesPerSigOp;
extern bool fCheckpointsEnabled;
extern CFeeRate minRelayTxFee;
......
......@@ -140,7 +140,6 @@ CBlockTemplate* Mining::CreateNewBlock(Validation::Engine &validationEngine) con
// Add dummy coinbase tx as first transaction
pblock->vtx.push_back(CTransaction());
pblocktemplate->vTxFees.push_back(-1); // updated at end
pblocktemplate->vTxSigOps.push_back(0); // updated at end
// Largest block you're willing to create (in bytes):
uint32_t nBlockMaxSize = std::max<uint32_t>(1000, GetArg("-blockmaxsize", Settings::DefaultBlockMAxSize));
......@@ -169,7 +168,6 @@ CBlockTemplate* Mining::CreateNewBlock(Validation::Engine &validationEngine) con
const uint32_t nCoinbaseReserveSize = 1000;
uint64_t nBlockSize = nCoinbaseReserveSize;
uint64_t nBlockTx = 0;
unsigned int nBlockSigOps = 100;
int lastFewTxs = 0;
CAmount nFees = 0;
......@@ -275,23 +273,12 @@ CBlockTemplate* Mining::CreateNewBlock(Validation::Engine &validationEngine) con
if (!IsFinalTx(tx, nHeight, nLockTimeCutoff))
continue;
const uint64_t maxSigOps = Policy::blockSigOpAcceptLimit(nBlockSize + nTxSize - nCoinbaseReserveSize);
unsigned int nTxSigOps = iter->GetSigOpCount();
if (nBlockSigOps + nTxSigOps >= maxSigOps) {
if (nBlockSigOps > maxSigOps - 2) {
break;
}
continue;
}
CAmount nTxFees = iter->GetFee();
// Added
pblock->vtx.push_back(tx);
pblocktemplate->vTxFees.push_back(nTxFees);
pblocktemplate->vTxSigOps.push_back(nTxSigOps);
nBlockSize += nTxSize;
++nBlockTx;
nBlockSigOps += nTxSigOps;
nFees += nTxFees;
if (fPrintPriority) {
......@@ -325,7 +312,7 @@ CBlockTemplate* Mining::CreateNewBlock(Validation::Engine &validationEngine) con
nLastBlockTx = nBlockTx;
nLastBlockSize = nBlockSize;
logInfo(Log::Mining) << "CreateNewBlock(): total size:" <<nBlockSize << "txs:" << nBlockTx
<< "fees:" << nFees << "sigops:"<< nBlockSigOps;
<< "fees:" << nFees;
// Compute final coinbase transaction.
txNew.vout[0].nValue = nFees + GetBlockSubsidy(nHeight, Params().GetConsensus());
......@@ -343,11 +330,6 @@ CBlockTemplate* Mining::CreateNewBlock(Validation::Engine &validationEngine) con
UpdateTime(pblock, Params().GetConsensus(), pindexPrev);
pblock->nBits = GetNextWorkRequired(pindexPrev, pblock, Params().GetConsensus());
pblock->nNonce = 0;
uint32_t sigops = 0;
for (auto out : pblock->vtx.at(0).vout) {
sigops += out.scriptPubKey.GetSigOpCount(false);
}
pblocktemplate->vTxSigOps[0] = sigops;
}
if (validationEngine.priv().lock()->tipFlags.hf201811Active) {
// sort the to-be-mined block using CTOR rules
......
......@@ -39,7 +39,6 @@ struct CBlockTemplate
{
CBlock block;
std::vector<CAmount> vTxFees;
std::vector<uint32_t> vTxSigOps;
};
......
......@@ -2,7 +2,7 @@
* This file is part of the Flowee project
* Copyright (C) 2009-2010 Satoshi Nakamoto
* Copyright (C) 2009-2015 The Bitcoin developers
* Copyright (C) 2019 Tom Zander <[email protected]>
* Copyright (C) 2019-2020 Tom Zander <[email protected]>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
......@@ -117,9 +117,9 @@ bool IsStandardTx(const CTransaction& tx, std::string& reason)
return false;
}
if (whichType == Script::TX_NULL_DATA)
if (whichType == Script::TX_NULL_DATA) {
nDataOut++;
else if ((whichType == Script::TX_MULTISIG) && (!fIsBareMultisigStd)) {
} else if ((whichType == Script::TX_MULTISIG) && (!fIsBareMultisigStd)) {
reason = "bare-multisig";
return false;
} else if (txout.IsDust(::minRelayTxFee)) {
......@@ -151,10 +151,6 @@ bool Policy::isInputStandard(const CScript &outputScript, const CScript &inputSc
return false;
if (stack.empty())
return false;
CScript subscript(stack.back().begin(), stack.back().end());
if (subscript.GetSigOpCount(true) > MAX_P2SH_SIGOPS) {
return false;
}
}
return true;
......@@ -186,14 +182,6 @@ int32_t Policy::blockSizeAcceptLimit()
return static_cast<int>(std::min(int64_t(INT_MAX), limit));
}
uint32_t Policy::blockSigOpAcceptLimit(int32_t nBlockSize)
{
if (nBlockSize < 2)
return MAX_BLOCK_SIGOPS_PER_MB;
uint32_t nBlockSizeMb = 1 + ((static_cast<uint32_t>(nBlockSize) - 1) / 1000000);
return nBlockSizeMb * MAX_BLOCK_SIGOPS_PER_MB;
}
bool Policy::areInputsStandard(const Tx &tx, const UnspentOutputDatabase *utxo)
{
Tx::Iterator iter(tx);
......@@ -217,3 +205,8 @@ bool Policy::areInputsStandard(const Tx &tx, const UnspentOutputDatabase *utxo)
}
return true;
}
uint32_t Policy::blockSigCheckAcceptLimit()
{
return (blockSizeAcceptLimit() + 70) / 141;
}
......@@ -2,6 +2,7 @@
* This file is part of the Flowee project
* Copyright (C) 2009-2010 Satoshi Nakamoto
* Copyright (C) 2009-2015 The Bitcoin developers
* Copyright (C) 2019-2020 Tom Zander <[email protected]>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
......@@ -31,10 +32,6 @@ class UnspentOutputDatabase;
/** The maximum size for transactions we're willing to relay/mine */
static const unsigned int MAX_STANDARD_TX_SIZE = 100000;
/** Maximum number of signature check operations in an IsStandard() P2SH script */
static const unsigned int MAX_P2SH_SIGOPS = 15;
/** The maximum number of sigops we're willing to relay/mine in a single tx */
static const unsigned int MAX_STANDARD_TX_SIGOPS = MAX_BLOCK_SIGOPS_PER_MB/5;
/**
* Standard script verification flags that standard transactions will comply
......@@ -68,8 +65,10 @@ bool IsStandard(const CScript &scriptPubKey, Script::TxnOutType &whichType);
bool IsStandardTx(const CTransaction& tx, std::string& reason);
namespace Policy {
constexpr unsigned int MAX_SIGCHEKCS_PER_TX = 3000;
std::int32_t blockSizeAcceptLimit();
uint32_t blockSigOpAcceptLimit(int32_t nBlockSize);
uint32_t blockSigCheckAcceptLimit();
/**
* Check for standard transaction types
* @return True if input use standard transaction forms
......
......@@ -560,7 +560,7 @@ UniValue getblocktemplate(const UniValue& params, bool fHelp)
assert(i > 0);
size_t index_in_template = i - 1;
entry.push_back(Pair("fee", pblocktemplate->vTxFees[index_in_template]));
entry.push_back(Pair("sigops", static_cast<int64_t>(pblocktemplate->vTxSigOps[index_in_template])));
entry.push_back(Pair("sigops", 0));
transactions.push_back(entry);
}
......@@ -587,7 +587,7 @@ UniValue getblocktemplate(const UniValue& params, bool fHelp)
result.push_back(Pair("mutable", aMutable));
result.push_back(Pair("noncerange", "00000000ffffffff"));
int64_t sizeLimit = 32E6; // lets have a nice big default, the goal is to remove this from the API
result.push_back(Pair("sigoplimit", static_cast<int64_t>(Policy::blockSigOpAcceptLimit(sizeLimit))));
result.push_back(Pair("sigoplimit", 100000)); // obsolete
result.push_back(Pair("sizelimit", sizeLimit));
result.push_back(Pair("curtime", pblock->GetBlockTime()));
result.push_back(Pair("bits", strprintf("%08x", pblock->nBits)));
......
......@@ -55,7 +55,7 @@ CTxMemPoolEntry::CTxMemPoolEntry(const Tx &tx)
CTxMemPoolEntry::CTxMemPoolEntry(const CTransaction &tx, const CAmount& _nFee,
int64_t _nTime, double _entryPriority, unsigned int _entryHeight,
bool poolHasNoInputsOf, CAmount _inChainInputValue,
bool _spendsCoinbase, unsigned int _sigOps, LockPoints lp)
bool _spendsCoinbase, LockPoints lp)
: CTxMemPoolEntry(Tx::fromOldTransaction(tx))
{
nFee = _nFee;
......@@ -66,7 +66,6 @@ CTxMemPoolEntry::CTxMemPoolEntry(const CTransaction &tx, const CAmount& _nFee,
hadNoDependencies = poolHasNoInputsOf;
inChainInputValue = _inChainInputValue;
spendsCoinbase = _spendsCoinbase;
sigOpCount = _sigOps;
lockPoints = lp;
assert(inChainInputValue <= oldTx.GetValueOut() + nFee);
......
......@@ -102,7 +102,6 @@ public:
bool hadNoDependencies; //! Not dependent on any other txs when it entered the mempool
CAmount inChainInputValue; //! Sum of all txin values that are already in blockchain
bool spendsCoinbase; //! keep track of transactions that spend a coinbase
unsigned int sigOpCount; //! Legacy sig ops plus P2SH sig op count
int64_t feeDelta; //! Used for determining the priority of the transaction for mining in a block
LockPoints lockPoints; //! Track the height and time at which tx was final
......@@ -121,7 +120,7 @@ public:
CTxMemPoolEntry(const CTransaction &tx, const CAmount& _nFee,
int64_t _nTime, double _entryPriority, unsigned int _entryHeight,
bool poolHasNoInputsOf, CAmount _inChainInputValue, bool spendsCoinbase,
unsigned int nSigOps, LockPoints lp);
LockPoints lp);
CTxMemPoolEntry(const CTxMemPoolEntry& other);
const CTransaction& GetTx() const { return this->oldTx; }
......@@ -135,7 +134,6 @@ public:
int64_t GetTime() const { return nTime; }
unsigned int GetHeight() const { return entryHeight; }
bool WasClearAtEntry() const { return hadNoDependencies; }
unsigned int GetSigOpCount() const { return sigOpCount; }
int64_t GetModifiedFee() const { return nFee + feeDelta; }
size_t DynamicMemoryUsage() const { return nUsageSize; }
const LockPoints& GetLockPoints() const { return lockPoints; }
......
......@@ -555,9 +555,9 @@ void ValidationEnginePrivate::processNewBlock(std::shared_ptr<BlockValidationSta
index->RaiseValidity(BLOCK_VALID_SCRIPTS); // done
state->signalChildren();
} else {
const uint64_t maxSigOps = Policy::blockSigOpAcceptLimit(state->m_block.size());
if (state->m_sigOpsCounted > maxSigOps)
throw Exception("bad-blk-sigops");
static const uint64_t maxSigChecks = Policy::blockSigCheckAcceptLimit();
if (state->m_sigChecksCounted > maxSigChecks)
throw Exception("bad-blk-sigcheck");
CBlock block = state->m_block.createOldBlock();
if (state->flags.enableValidation) {
......@@ -1050,7 +1050,7 @@ BlockValidationState::BlockValidationState(const std::weak_ptr<ValidationEngineP
m_txChunkLeftToFinish(-1),
m_validationStatus(BlockValidityUnknown),
m_blockFees(0),
m_sigOpsCounted(0),
m_sigChecksCounted(0),
m_parent(parent)
{
assert(onResultFlags < 0x100);
......@@ -1292,18 +1292,6 @@ void BlockValidationState::checks2HaveParentHeaders()
throw Exception("bad-cb-height");
}
// Sigops.
// Notice that we continue counting in validateTransactionInputs and do one last check in processNewBlock()
// TODO chunk this over all CPUs
uint32_t sigOpsCounted = 0;
for (const CTransaction &tx : block.vtx)
sigOpsCounted += Validation::countSigOps(tx);
const uint32_t maxSigOps = Policy::blockSigOpAcceptLimit(m_block.size());
if (sigOpsCounted > maxSigOps)
throw Exception("bad-blk-sigops");
assert(m_sigOpsCounted == 0);
m_sigOpsCounted = sigOpsCounted;
if (flags.hf201811Active) {
for (auto tx : m_block.transactions()) {
// Impose a minimum transaction size of 100 bytes after the Nov, 15 2018 HF
......@@ -1470,7 +1458,7 @@ void BlockValidationState::checkSignaturesChunk()
bool blockValid = (m_validationStatus.load() & BlockInvalid) == 0;
int txIndex = itemsPerChunk * chunkToStart;
const int txMax = std::min(txIndex + itemsPerChunk, totalTxCount);
uint32_t chunkSigops = 0;
uint32_t chunkSigChecks = 0;
CAmount chunkFees = 0;
std::unique_ptr<std::deque<FastUndoBlock::Item> >undoItems(new std::deque<FastUndoBlock::Item>());
......@@ -1592,10 +1580,10 @@ void BlockValidationState::checkSignaturesChunk()
throw Exception("bad-txns-nonfinal");
bool spendsCoinBase;
uint32_t sigops = 0;
uint32_t sigChecks = 0;
ValidationPrivate::validateTransactionInputs(old, unspents, m_blockIndex->nHeight, flags, fees,
sigops, spendsCoinBase, /* requireStandard */ false);
chunkSigops += sigops;
sigChecks, spendsCoinBase, /* requireStandard */ false);
chunkSigChecks += sigChecks;
chunkFees += fees;
}
......@@ -1626,7 +1614,7 @@ void BlockValidationState::checkSignaturesChunk()
blockValid = false;
}
m_blockFees.fetch_add(chunkFees);
m_sigOpsCounted.fetch_add(chunkSigops);
m_sigChecksCounted.fetch_add(chunkSigChecks );
m_undoItems[static_cast<size_t>(chunkToStart)] = undoItems.release();
#ifdef ENABLE_BENCHMARKS
......
......@@ -178,7 +178,7 @@ public:
mutable std::atomic<int> m_validationStatus;
mutable std::atomic<std::int64_t> m_blockFees;
mutable std::atomic<std::uint32_t> m_sigOpsCounted;
mutable std::atomic<std::uint32_t> m_sigChecksCounted;
std::vector<std::deque<FastUndoBlock::Item> *> m_undoItems;
......
......@@ -44,24 +44,17 @@
using Validation::Exception;
void ValidationPrivate::validateTransactionInputs(CTransaction &tx, const std::vector<UnspentOutput> &unspents, int blockHeight, ValidationFlags flags, int64_t &fees, uint32_t &txSigops, bool &spendsCoinbase, bool requireStandard)
void ValidationPrivate::validateTransactionInputs(CTransaction &tx, const std::vector<UnspentOutput> &unspents, int blockHeight, ValidationFlags flags, int64_t &fees, uint32_t &txSigChecks, bool &spendsCoinbase, bool requireStandard)
{
assert(unspents.size() == tx.vin.size());
txSigChecks = 0;
int64_t valueIn = 0;
for (size_t i = 0; i < tx.vin.size(); ++i) {
const ValidationPrivate::UnspentOutput &prevout = unspents.at(i);
assert(prevout.amount >= 0);
if (flags.strictPayToScriptHash && prevout.outputScript.IsPayToScriptHash()) {
// Add in sigops done by pay-to-script-hash inputs;
// this is to prevent a "rogue miner" from creating
// an incredibly-expensive-to-validate block.
txSigops += prevout.outputScript.GetSigOpCount(tx.vin[i].scriptSig);
}
valueIn += prevout.amount;
}
if (txSigops > MAX_BLOCK_SIGOPS_PER_MB)
throw Exception("bad-tx-sigops");
if (valueIn < tx.GetValueOut())
throw Exception("bad-txns-in-belowout");
......@@ -112,6 +105,7 @@ void ValidationPrivate::validateTransactionInputs(CTransaction &tx, const std::v
throw Exception(strprintf("mandatory-script-verify-flag-failed (%s)", strict.errorString()));
}
txSigChecks += strict.sigCheckCount;
}
}
......@@ -314,8 +308,11 @@ void TxValidationState::checkTransaction()
if (!CheckSequenceLocks(*parent->mempool, tx, STANDARD_LOCKTIME_VERIFY_FLAGS, &entry.lockPoints, false, tip))
throw Exception("non-BIP68-final", Validation::RejectNonstandard, 0);
entry.sigOpCount = Validation::countSigOps(tx);
ValidationPrivate::validateTransactionInputs(tx, unspents, static_cast<int>(entry.entryHeight) + 1, flags, entry.nFee, entry.sigOpCount, entry.spendsCoinbase, fRequireStandard);
uint32_t txSigChecks = 0;
ValidationPrivate::validateTransactionInputs(tx, unspents, static_cast<int>(entry.entryHeight) + 1, flags, entry.nFee, txSigChecks , entry.spendsCoinbase, fRequireStandard);
if (fRequireStandard && txSigChecks > Policy::MAX_SIGCHEKCS_PER_TX) {
throw Exception("bad-blk-sigcheck", Validation::RejectNonstandard, 0);
}
// nModifiedFees includes any fee deltas from PrioritiseTransaction
CAmount nModifiedFees = entry.nFee;
......@@ -326,10 +323,6 @@ void TxValidationState::checkTransaction()
const size_t nSize = entry.GetTxSize();
// Notice nBytesPerSigOp is a global!
if ((entry.sigOpCount > MAX_STANDARD_TX_SIGOPS) || (nBytesPerSigOp && entry.sigOpCount > nSize / nBytesPerSigOp))
throw Exception("bad-txns-too-many-sigops", Validation::RejectNonstandard);
CAmount mempoolRejectFee = parent->mempool->GetMinFee().GetFee(nSize);
if (mempoolRejectFee > 0 && nModifiedFees < mempoolRejectFee) {
logInfo(Log::Mempool) << "transaction rejected, low fee:" << nModifiedFees << "<" << mempoolRejectFee << "sat";
......@@ -525,16 +518,3 @@ void TxValidationState::notifyDoubleSpend()
ValidationNotifier().DoubleSpendFound(m_doubleSpendTx, m_tx);
}
uint32_t Validation::countSigOps(const CTransaction &tx)
{
uint32_t txSigops = 0;
for (auto out : tx.vout)
txSigops += out.scriptPubKey.GetSigOpCount(false);
for (auto in : tx.vin)
txSigops += in.scriptSig.GetSigOpCount(false);
if (txSigops > MAX_BLOCK_SIGOPS_PER_MB)
throw Exception("bad-tx-sigops");
return txSigops;
}
......@@ -36,10 +36,6 @@
class CTransaction;
namespace Validation {
uint32_t countSigOps(const CTransaction &tx);
}
class TxValidationState : public std::enable_shared_from_this<TxValidationState> {
public:
enum InternalFlags {
......
......@@ -106,8 +106,6 @@ static const unsigned int DefaultDescendantSizeLimit = 101;
static const unsigned int DefaultLimitFreeRelay = 15;
static const unsigned int DefaultBytesPerSigop = 20;
// ///////// Wallet
static const unsigned int DefaultKeypoolSize = 100;
......
......@@ -200,55 +200,6 @@ bool CheckMinimalPush(const std::vector<uint8_t> &data, opcodetype opcode)
return true;
}
unsigned int CScript::GetSigOpCount(bool fAccurate) const
{
unsigned int n = 0;
const_iterator pc = begin();
opcodetype lastOpcode = OP_INVALIDOPCODE;
while (pc < end()) {
opcodetype opcode;
if (!GetOp(pc, opcode))
break;
if (opcode == OP_CHECKSIG || opcode == OP_CHECKSIGVERIFY
|| opcode == OP_CHECKDATASIG || opcode == OP_CHECKDATASIGVERIFY) {
n++;
}
else if (opcode == OP_CHECKMULTISIG || opcode == OP_CHECKMULTISIGVERIFY) {
if (fAccurate && lastOpcode >= OP_1 && lastOpcode <= OP_16)
n += DecodeOP_N(lastOpcode);
else
n += MAX_PUBKEYS_PER_MULTISIG;
}
lastOpcode = opcode;
}
return n;
}
unsigned int CScript::GetSigOpCount(const CScript& scriptSig) const
{
if (!IsPayToScriptHash())
return GetSigOpCount(true);
// This is a pay-to-script-hash scriptPubKey;
// get the last item that the scriptSig
// pushes onto the stack:
const_iterator pc = scriptSig.begin();
std::vector<unsigned char> data;
while (pc < scriptSig.end())
{
opcodetype opcode;
if (!scriptSig.GetOp(pc, opcode, data))
return 0;
if (opcode > OP_16)
return 0;
}
/// ... and return its opcount:
CScript subscript(data.begin(), data.end());
return subscript.GetSigOpCount(true);
}
bool CScript::IsPayToScriptHash() const
{
// Extra-fast test for pay-to-script-hash CScripts:
......
......@@ -568,21 +568,6 @@ public:
return nFound;
}
/**
* Pre-version-0.6, Bitcoin always counted CHECKMULTISIGs
* as 20 sigops. With pay-to-script-hash, that changed:
* CHECKMULTISIGs serialized in scriptSigs are
* counted more accurately, assuming they are of the form
* ... OP_N CHECKMULTISIG ...
*/
unsigned int GetSigOpCount(bool fAccurate) const;
/**
* Accurately count sigOps, including sigOps in
* pay-to-script-hash transactions:
*/
unsigned int GetSigOpCount(const CScript& scriptSig) const;
bool IsPayToScriptHash() const;
// BTC SegWit detection code.
......
......@@ -278,7 +278,6 @@ void TestPaymentToScriptHash::switchover()
void TestPaymentToScriptHash::AreInputsStandard()
{
LOCK(cs_main);
UnspentOutputDatabase *utxo = UnspentOutputDatabase::createMemOnlyDB("unspent");
CBasicKeyStore keystore;
CKey key[6];
std::vector<CPubKey> keys;
......@@ -316,9 +315,9 @@ void TestPaymentToScriptHash::AreInputsStandard()
txFrom.vout[3].scriptPubKey = GetScriptForDestination(CScriptID(oneAndTwo));
txFrom.vout[3].nValue = 4000;
// vout[4] is max sigops:
// vout[4] is max sigchecks: Non-standard because its too long
CScript fifteenSigops; fifteenSigops << OP_1;
for (unsigned i = 0; i < MAX_P2SH_SIGOPS; i++)
for (unsigned i = 0; i < Policy::MAX_SIGCHEKCS_PER_TX; i++)
fifteenSigops << ToByteVector(key[i%3].GetPubKey());
fifteenSigops << OP_15 << OP_CHECKMULTISIG;
keystore.AddCScript(fifteenSigops);
......@@ -357,44 +356,7 @@ void TestPaymentToScriptHash::AreInputsStandard()
for (size_t i = 0; i < txTo.vin.size(); ++i) {
const auto in = txTo.vin.at(i);
const auto prevOut = txFrom.vout.at(i);
QVERIFY(Policy::isInputStandard(prevOut.scriptPubKey, in.scriptSig));
// 22 P2SH sigops for all inputs (1 for vin[0], 6 for vin[3], 15 for vin[4]
if (prevOut.scriptPubKey.IsPayToScriptHash()) {
int expected = 0;
switch (i) {
case 1: case 2: case 5: expected = 0; break;
case 0: expected = 1; break;
case 3: expected = 6; break;
case 4: expected = 15; break;
}
// an incredibly-expensive-to-validate block.
QCOMPARE(prevOut.scriptPubKey.GetSigOpCount(in.scriptSig), (uint) expected);
}
const bool ok = Policy::isInputStandard(prevOut.scriptPubKey, in.scriptSig);
QCOMPARE(ok, i < 4);
}
CMutableTransaction txToNonStd1;
txToNonStd1.vout.resize(1);
txToNonStd1.vout[0].scriptPubKey = GetScriptForDestination(key[1].GetPubKey().GetID());
txToNonStd1.vout[0].nValue = 1000;
txToNonStd1.vin.resize(1);
txToNonStd1.vin[0].prevout.n = 5;
txToNonStd1.vin[0].prevout.hash = txFrom.GetHash();
txToNonStd1.vin[0].scriptSig << std::vector<unsigned char>(sixteenSigops.begin(), sixteenSigops.end());
QVERIFY(!Policy::isInputStandard(txFrom.vout.at(0).scriptPubKey,
txToNonStd1.vin.at(0).scriptSig));
QCOMPARE(txFrom.vout.at(5).scriptPubKey.GetSigOpCount(txToNonStd1.vin.at(0).scriptSig), (uint) 16);
CMutableTransaction txToNonStd2;
txToNonStd2.vout.resize(1);
txToNonStd2.vout[0].scriptPubKey = GetScriptForDestination(key[1].GetPubKey().GetID());
txToNonStd2.vout[0].nValue = 1000;
txToNonStd2.vin.resize(1);
txToNonStd2.vin[0].prevout.n = 6;
txToNonStd2.vin[0].prevout.hash = txFrom.GetHash();
txToNonStd2.vin[0].scriptSig << std::vector<unsigned char>(twentySigops.begin(), twentySigops.end());
QVERIFY(!Policy::isInputStandard(txFrom.vout.at(0).scriptPubKey,
txToNonStd2.vin.at(0).scriptSig));
QCOMPARE(txFrom.vout.at(6).scriptPubKey.GetSigOpCount(txToNonStd2.vin.at(0).scriptSig), (uint) 20);
}