diff --git a/src/init.cpp b/src/init.cpp index 09b69b23ab..cbbf6ed08d 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -680,9 +680,9 @@ void SetupServerArgs(ArgsManager& argsman) argsman.AddArg("-bytespersigop", strprintf("Equivalent bytes per sigop in transactions for relay and mining (default: %u)", DEFAULT_BYTES_PER_SIGOP), ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY); argsman.AddArg("-bytespersigopstrict", strprintf("Minimum bytes per sigop in transactions we relay and mine (default: %u)", DEFAULT_BYTES_PER_SIGOP_STRICT), ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY); argsman.AddArg("-datacarrier", strprintf("Relay and mine data carrier transactions (default: %u)", DEFAULT_ACCEPT_DATACARRIER), ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY); + argsman.AddArg("-datacarrierfullcount", strprintf("Apply datacarriersize limit to all known datacarrier methods (default: %u)", DEFAULT_DATACARRIER_FULLCOUNT), ArgsManager::ALLOW_ANY | (DEFAULT_DATACARRIER_FULLCOUNT ? uint32_t{ArgsManager::DEBUG_ONLY} : 0), OptionsCategory::NODE_RELAY); argsman.AddArg("-datacarriersize", - strprintf("Relay and mine transactions whose data-carrying raw scriptPubKey " - "is of this size or less (default: %u)", + strprintf("Maximum size of data in data carrier transactions we relay and mine, in bytes (default: %u)", MAX_OP_RETURN_RELAY), ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY); argsman.AddArg("-mempoolfullrbf", strprintf("Accept transaction replace-by-fee without requiring replaceability signaling (default: %u)", (DEFAULT_MEMPOOL_RBF_POLICY == RBFPolicy::Always)), ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY); diff --git a/src/kernel/mempool_options.h b/src/kernel/mempool_options.h index 8d92c7d952..09412c6c8d 100644 --- a/src/kernel/mempool_options.h +++ b/src/kernel/mempool_options.h @@ -68,6 +68,7 @@ struct MemPoolOptions { * If nullopt, any size is nonstandard. */ std::optional max_datacarrier_bytes{DEFAULT_ACCEPT_DATACARRIER ? std::optional{MAX_OP_RETURN_RELAY} : std::nullopt}; + bool datacarrier_fullcount{DEFAULT_DATACARRIER_FULLCOUNT}; bool permit_bare_pubkey{DEFAULT_PERMIT_BAREPUBKEY}; bool permit_bare_multisig{DEFAULT_PERMIT_BAREMULTISIG}; bool require_standard{true}; diff --git a/src/node/mempool_args.cpp b/src/node/mempool_args.cpp index 644d98340a..a28f65bb42 100644 --- a/src/node/mempool_args.cpp +++ b/src/node/mempool_args.cpp @@ -151,6 +151,7 @@ util::Result ApplyArgsManOptions(const ArgsManager& argsman, const CChainP } else { mempool_opts.max_datacarrier_bytes = std::nullopt; } + mempool_opts.datacarrier_fullcount = argsman.GetBoolArg("-datacarrierfullcount", DEFAULT_DATACARRIER_FULLCOUNT); mempool_opts.require_standard = !argsman.GetBoolArg("-acceptnonstdtxn", DEFAULT_ACCEPT_NON_STD_TXN); diff --git a/src/policy/policy.cpp b/src/policy/policy.cpp index 9b3bf21380..8ef3a98830 100644 --- a/src/policy/policy.cpp +++ b/src/policy/policy.cpp @@ -372,3 +372,66 @@ int64_t GetVirtualTransactionInputSize(const CTxIn& txin, int64_t nSigOpCost, un { return GetVirtualTransactionSize(GetTransactionInputWeight(txin), nSigOpCost, bytes_per_sigop); } + +std::pair GetScriptForTransactionInput(CScript prevScript, const CTxIn& txin) +{ + bool p2sh = false; + if (prevScript.IsPayToScriptHash()) { + std::vector > stack; + if (!EvalScript(stack, txin.scriptSig, SCRIPT_VERIFY_NONE, BaseSignatureChecker(), SigVersion::BASE)) { + return std::make_pair(CScript(), 0); + } + if (stack.empty()) { + return std::make_pair(CScript(), 0); + } + prevScript = CScript(stack.back().begin(), stack.back().end()); + p2sh = true; + } + + int witnessversion = 0; + std::vector witnessprogram; + + if (!prevScript.IsWitnessProgram(witnessversion, witnessprogram)) { + // For P2SH, scriptSig is always push-only, so the actual script is only the last stack item + // For non-P2SH, prevScript is likely the real script, but not part of this transaction, and scriptSig could very well be executable, so return the latter instead + return std::make_pair(p2sh ? prevScript : txin.scriptSig, WITNESS_SCALE_FACTOR); + } + + Span stack{txin.scriptWitness.stack}; + + if (witnessversion == 0 && witnessprogram.size() == WITNESS_V0_SCRIPTHASH_SIZE) { + if (stack.empty()) return std::make_pair(CScript(), 0); // invalid + auto& script_data = stack.back(); + prevScript = CScript(script_data.begin(), script_data.end()); + return std::make_pair(prevScript, 1); + } + + if (witnessversion == 1 && witnessprogram.size() == WITNESS_V1_TAPROOT_SIZE && !p2sh) { + if (stack.size() >= 2 && !stack.back().empty() && stack.back()[0] == ANNEX_TAG) { + SpanPopBack(stack); + } + if (stack.size() >= 2) { + SpanPopBack(stack); // Ignore control block + prevScript = CScript(stack.back().begin(), stack.back().end()); + return std::make_pair(prevScript, 1); + } + } + + return std::make_pair(CScript(), 0); +} + +size_t DatacarrierBytes(const CTransaction& tx, const CCoinsViewCache& view) +{ + size_t ret{0}; + + for (const CTxIn& txin : tx.vin) { + const CTxOut &utxo = view.AccessCoin(txin.prevout).out; + auto[script, consensus_weight_per_byte] = GetScriptForTransactionInput(utxo.scriptPubKey, txin); + ret += script.DatacarrierBytes(); + } + for (const CTxOut& txout : tx.vout) { + ret += txout.scriptPubKey.DatacarrierBytes(); + } + + return ret; +} diff --git a/src/policy/policy.h b/src/policy/policy.h index 2334478963..dd563d8bda 100644 --- a/src/policy/policy.h +++ b/src/policy/policy.h @@ -15,6 +15,7 @@ #include #include #include +#include class CCoinsViewCache; class CFeeRate; @@ -87,6 +88,8 @@ static const bool DEFAULT_ACCEPT_DATACARRIER = true; * +2 for the pushdata opcodes. */ static const unsigned int MAX_OP_RETURN_RELAY = 83; +/** Default for -datacarrierfullcount */ +static constexpr bool DEFAULT_DATACARRIER_FULLCOUNT{false}; /** * An extra transaction can be added to a package, as long as it only has one * ancestor and is no larger than this. Not really any reason to make this @@ -194,4 +197,8 @@ static inline int64_t GetVirtualTransactionInputSize(const CTxIn& tx) return GetVirtualTransactionInputSize(tx, 0, 0); } +std::pair GetScriptForTransactionInput(CScript prevScript, const CTxIn&); + +size_t DatacarrierBytes(const CTransaction& tx, const CCoinsViewCache& view); + #endif // BITCOIN_POLICY_POLICY_H diff --git a/src/script/script.cpp b/src/script/script.cpp index d650db9a0d..ead62cebc0 100644 --- a/src/script/script.cpp +++ b/src/script/script.cpp @@ -303,6 +303,56 @@ bool CScript::HasValidOps() const return true; } +size_t CScript::DatacarrierBytes() const +{ + size_t counted{0}; + opcodetype opcode, last_opcode{OP_INVALIDOPCODE}; + std::vector push_data; + unsigned int inside_noop{0}, inside_conditional{0}; + CScript::const_iterator opcode_it = begin(), data_began = begin(); + for (CScript::const_iterator it = begin(); it < end(); last_opcode = opcode) { + opcode_it = it; + if (!GetOp(it, opcode, push_data)) { + // Invalid scripts are necessarily all data + return size(); + } + + if (opcode == OP_IF || opcode == OP_NOTIF) { + ++inside_conditional; + } else if (opcode == OP_ENDIF) { + if (!inside_conditional) return size(); // invalid + --inside_conditional; + } else if (opcode == OP_RETURN && !inside_conditional) { + // unconditional OP_RETURN is unspendable + return size(); + } + + // Match OP_FALSE OP_IF + if (inside_noop) { + switch (opcode) { + case OP_IF: case OP_NOTIF: + ++inside_noop; + break; + case OP_ENDIF: + if (0 == --inside_noop) { + counted += it - data_began + 1; + } + break; + default: /* do nothing */; + } + } else if (opcode == OP_IF && last_opcode == OP_FALSE) { + inside_noop = 1; + data_began = opcode_it; + // Match OP_DROP + } else if (opcode <= OP_PUSHDATA4) { + data_began = opcode_it; + } else if (opcode == OP_DROP && last_opcode <= OP_PUSHDATA4) { + counted += it - data_began; + } + } + return counted; +} + bool GetScriptOp(CScriptBase::const_iterator& pc, CScriptBase::const_iterator end, opcodetype& opcodeRet, std::vector* pvchRet) { opcodeRet = OP_INVALIDOPCODE; diff --git a/src/script/script.h b/src/script/script.h index 323411251c..2282aa94f5 100644 --- a/src/script/script.h +++ b/src/script/script.h @@ -562,6 +562,8 @@ public: return (size() > 0 && *begin() == OP_RETURN) || (size() > MAX_SCRIPT_SIZE); } + size_t DatacarrierBytes() const; + void clear() { // The default prevector::clear() does not release memory diff --git a/src/test/script_tests.cpp b/src/test/script_tests.cpp index 6fa0e90a6f..7ec857cf0c 100644 --- a/src/test/script_tests.cpp +++ b/src/test/script_tests.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include