diff --git a/src/init.cpp b/src/init.cpp index cd767271ca..7d45444e93 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -669,6 +669,10 @@ void SetupServerArgs(ArgsManager& argsman) SetupChainParamsBaseOptions(argsman); + argsman.AddArg("-acceptnonstddatacarrier", + strprintf("Relay and mine non-OP_RETURN datacarrier injection (default: %u)", + DEFAULT_ACCEPT_NON_STD_DATACARRIER), + ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY); argsman.AddArg("-acceptnonstdtxn", strprintf("Relay and mine \"non-standard\" transactions (default: %u)", DEFAULT_ACCEPT_NON_STD_TXN), ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY); argsman.AddArg("-incrementalrelayfee=", strprintf("Fee rate (in %s/kvB) used to define cost of relay, used for mempool limiting and replacement policy. (default: %s)", CURRENCY_UNIT, FormatMoney(DEFAULT_INCREMENTAL_RELAY_FEE)), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::NODE_RELAY); argsman.AddArg("-dustrelayfee=", strprintf("Fee rate (in %s/kvB) used to define dust, the value of an output such that it will cost more than its value in fees at this fee rate to spend it. (default: %s)", CURRENCY_UNIT, FormatMoney(DUST_RELAY_TX_FEE)), ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY); diff --git a/src/kernel/mempool_options.h b/src/kernel/mempool_options.h index 09412c6c8d..230cc868c3 100644 --- a/src/kernel/mempool_options.h +++ b/src/kernel/mempool_options.h @@ -32,6 +32,8 @@ static constexpr RBFPolicy DEFAULT_MEMPOOL_RBF_POLICY{RBFPolicy::Always}; static constexpr TRUCPolicy DEFAULT_MEMPOOL_TRUC_POLICY{TRUCPolicy::Enforce}; /** Whether to fall back to legacy V1 serialization when writing mempool.dat */ static constexpr bool DEFAULT_PERSIST_V1_DAT{false}; +/** Default for -acceptnonstddatacarrier */ +static constexpr bool DEFAULT_ACCEPT_NON_STD_DATACARRIER{true}; /** Default for -acceptnonstdtxn */ static constexpr bool DEFAULT_ACCEPT_NON_STD_TXN{false}; @@ -71,6 +73,7 @@ struct MemPoolOptions { bool datacarrier_fullcount{DEFAULT_DATACARRIER_FULLCOUNT}; bool permit_bare_pubkey{DEFAULT_PERMIT_BAREPUBKEY}; bool permit_bare_multisig{DEFAULT_PERMIT_BAREMULTISIG}; + bool accept_non_std_datacarrier{DEFAULT_ACCEPT_NON_STD_DATACARRIER}; bool require_standard{true}; RBFPolicy rbf_policy{DEFAULT_MEMPOOL_RBF_POLICY}; TRUCPolicy truc_policy{DEFAULT_MEMPOOL_TRUC_POLICY}; diff --git a/src/node/mempool_args.cpp b/src/node/mempool_args.cpp index a28f65bb42..fc0167c30d 100644 --- a/src/node/mempool_args.cpp +++ b/src/node/mempool_args.cpp @@ -152,6 +152,7 @@ util::Result ApplyArgsManOptions(const ArgsManager& argsman, const CChainP mempool_opts.max_datacarrier_bytes = std::nullopt; } mempool_opts.datacarrier_fullcount = argsman.GetBoolArg("-datacarrierfullcount", DEFAULT_DATACARRIER_FULLCOUNT); + mempool_opts.accept_non_std_datacarrier = argsman.GetBoolArg("-acceptnonstddatacarrier", DEFAULT_ACCEPT_NON_STD_DATACARRIER); 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 74b5a76735..310e9808f9 100644 --- a/src/policy/policy.cpp +++ b/src/policy/policy.cpp @@ -22,6 +22,7 @@ #include #include +#include #include CAmount GetDustThreshold(const CTxOut& txout, const CFeeRate& dustRelayFeeIn) @@ -420,17 +421,21 @@ std::pair GetScriptForTransactionInput(CScript prevScript return std::make_pair(CScript(), 0); } -size_t DatacarrierBytes(const CTransaction& tx, const CCoinsViewCache& view) +std::pair DatacarrierBytes(const CTransaction& tx, const CCoinsViewCache& view) { - size_t ret{0}; + std::pair ret{0, 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(); + const auto dcb = script.DatacarrierBytes(); + ret.first += dcb.first; + ret.second += dcb.second; } for (const CTxOut& txout : tx.vout) { - ret += txout.scriptPubKey.DatacarrierBytes(); + const auto dcb = txout.scriptPubKey.DatacarrierBytes(); + ret.first += dcb.first; + ret.second += dcb.second; } return ret; @@ -446,12 +451,14 @@ int32_t CalculateExtraTxWeight(const CTransaction& tx, const CCoinsViewCache& vi const CTxOut &utxo = view.AccessCoin(txin.prevout).out; auto[script, consensus_weight_per_byte] = GetScriptForTransactionInput(utxo.scriptPubKey, txin); if (weight_per_data_byte > consensus_weight_per_byte) { - mod_weight += script.DatacarrierBytes() * (weight_per_data_byte - consensus_weight_per_byte); + const auto dcb = script.DatacarrierBytes(); + mod_weight += (dcb.first + dcb.second) * (weight_per_data_byte - consensus_weight_per_byte); } } if (weight_per_data_byte > WITNESS_SCALE_FACTOR) { for (const CTxOut& txout : tx.vout) { - mod_weight += txout.scriptPubKey.DatacarrierBytes() * (weight_per_data_byte - WITNESS_SCALE_FACTOR); + const auto dcb = txout.scriptPubKey.DatacarrierBytes(); + mod_weight += (dcb.first + dcb.second) * (weight_per_data_byte - WITNESS_SCALE_FACTOR); } } } diff --git a/src/policy/policy.h b/src/policy/policy.h index a00d09a42e..5d84e2818f 100644 --- a/src/policy/policy.h +++ b/src/policy/policy.h @@ -201,7 +201,7 @@ static inline int64_t GetVirtualTransactionInputSize(const CTxIn& tx) std::pair GetScriptForTransactionInput(CScript prevScript, const CTxIn&); -size_t DatacarrierBytes(const CTransaction& tx, const CCoinsViewCache& view); +std::pair DatacarrierBytes(const CTransaction& tx, const CCoinsViewCache& view); int32_t CalculateExtraTxWeight(const CTransaction& tx, const CCoinsViewCache& view, const unsigned int weight_per_data_byte); diff --git a/src/script/script.cpp b/src/script/script.cpp index ead62cebc0..368ca4b66f 100644 --- a/src/script/script.cpp +++ b/src/script/script.cpp @@ -303,7 +303,7 @@ bool CScript::HasValidOps() const return true; } -size_t CScript::DatacarrierBytes() const +std::pair CScript::DatacarrierBytes() const { size_t counted{0}; opcodetype opcode, last_opcode{OP_INVALIDOPCODE}; @@ -314,17 +314,17 @@ size_t CScript::DatacarrierBytes() const opcode_it = it; if (!GetOp(it, opcode, push_data)) { // Invalid scripts are necessarily all data - return size(); + return {0, size()}; } if (opcode == OP_IF || opcode == OP_NOTIF) { ++inside_conditional; } else if (opcode == OP_ENDIF) { - if (!inside_conditional) return size(); // invalid + if (!inside_conditional) return {0, size()}; // invalid --inside_conditional; } else if (opcode == OP_RETURN && !inside_conditional) { // unconditional OP_RETURN is unspendable - return size(); + return {size(), 0}; } // Match OP_FALSE OP_IF @@ -350,7 +350,7 @@ size_t CScript::DatacarrierBytes() const counted += it - data_began; } } - return counted; + return {0, counted}; } bool GetScriptOp(CScriptBase::const_iterator& pc, CScriptBase::const_iterator end, opcodetype& opcodeRet, std::vector* pvchRet) diff --git a/src/script/script.h b/src/script/script.h index 2282aa94f5..7362724e46 100644 --- a/src/script/script.h +++ b/src/script/script.h @@ -562,7 +562,7 @@ public: return (size() > 0 && *begin() == OP_RETURN) || (size() > MAX_SCRIPT_SIZE); } - size_t DatacarrierBytes() const; + std::pair DatacarrierBytes() const; void clear() { diff --git a/src/test/script_tests.cpp b/src/test/script_tests.cpp index 7ec857cf0c..22cccd776b 100644 --- a/src/test/script_tests.cpp +++ b/src/test/script_tests.cpp @@ -1505,28 +1505,33 @@ BOOST_AUTO_TEST_CASE(script_HasValidOps) BOOST_CHECK(!script.HasValidOps()); } +static std::string DatacarrierBytesStr(const CScript &script) { + auto dcb = script.DatacarrierBytes(); + return strprintf("%s+%s", dcb.first, dcb.second); +} + BOOST_AUTO_TEST_CASE(script_DataCarrierBytes) { using zeros = std::vector; // empty script - BOOST_CHECK_EQUAL(0, (CScript()).DatacarrierBytes()); + BOOST_CHECK_EQUAL("0+0", DatacarrierBytesStr(CScript())); // series of pushes are not data - BOOST_CHECK_EQUAL(0, (CScript() << OP_0 << OP_0 << OP_0).DatacarrierBytes()); + BOOST_CHECK_EQUAL("0+0", DatacarrierBytesStr(CScript() << OP_0 << OP_0 << OP_0)); // unspendable if first op is OP_RETURN, then length(1), zeros(11) - BOOST_CHECK_EQUAL(13, (CScript() << OP_RETURN << zeros(11)).DatacarrierBytes()); + BOOST_CHECK_EQUAL("13+0", DatacarrierBytesStr(CScript() << OP_RETURN << zeros(11))); // invalid script (no data following PUSHDATA) makes it all data - BOOST_CHECK_EQUAL(2, (CScript() << OP_0 << OP_PUSHDATA4).DatacarrierBytes()); + BOOST_CHECK_EQUAL("0+2", DatacarrierBytesStr(CScript() << OP_0 << OP_PUSHDATA4)); // no data here - BOOST_CHECK_EQUAL(0, (CScript() << OP_TRUE << OP_IF << OP_ENDIF).DatacarrierBytes()); + BOOST_CHECK_EQUAL("0+0", DatacarrierBytesStr(CScript() << OP_TRUE << OP_IF << OP_ENDIF)); // specific data pattern, entire script is data - BOOST_CHECK_EQUAL(4, (CScript() << OP_FALSE << OP_IF << OP_7 << OP_ENDIF).DatacarrierBytes()); + BOOST_CHECK_EQUAL("0+4", DatacarrierBytesStr(CScript() << OP_FALSE << OP_IF << OP_7 << OP_ENDIF)); // consecutive data - BOOST_CHECK_EQUAL(6, (CScript() << OP_FALSE << OP_IF << OP_ENDIF << OP_FALSE << OP_IF << OP_ENDIF).DatacarrierBytes()); + BOOST_CHECK_EQUAL("0+6", DatacarrierBytesStr(CScript() << OP_FALSE << OP_IF << OP_ENDIF << OP_FALSE << OP_IF << OP_ENDIF)); // nested data (all is data) - BOOST_CHECK_EQUAL(6, (CScript() << OP_FALSE << OP_IF << OP_TRUE << OP_IF << OP_ENDIF << OP_ENDIF).DatacarrierBytes()); + BOOST_CHECK_EQUAL("0+6", DatacarrierBytesStr(CScript() << OP_FALSE << OP_IF << OP_TRUE << OP_IF << OP_ENDIF << OP_ENDIF)); // pushing then immediately dropping is data: length(1), zero(11), OP_DROP - BOOST_CHECK_EQUAL(13, (CScript() << zeros(11) << OP_DROP).DatacarrierBytes()); + BOOST_CHECK_EQUAL("0+13", DatacarrierBytesStr(CScript() << zeros(11) << OP_DROP)); } BOOST_AUTO_TEST_CASE(script_GetScriptForTransactionInput) @@ -1541,7 +1546,7 @@ BOOST_AUTO_TEST_CASE(script_GetScriptForTransactionInput) auto [ret_script, scale] = GetScriptForTransactionInput(prev_script, tx_in); BOOST_CHECK(ret_script == tx_in.scriptSig); BOOST_CHECK_EQUAL(scale, WITNESS_SCALE_FACTOR); - BOOST_CHECK_EQUAL(ret_script.DatacarrierBytes(), 0); + BOOST_CHECK_EQUAL(DatacarrierBytesStr(ret_script), "0+0"); } { // P2PKH - no datacarrier bytes CScript prev_script; // scriptPubKey @@ -1552,7 +1557,7 @@ BOOST_AUTO_TEST_CASE(script_GetScriptForTransactionInput) auto [ret_script, scale] = GetScriptForTransactionInput(prev_script, tx_in); BOOST_CHECK(ret_script == tx_in.scriptSig); BOOST_CHECK_EQUAL(scale, WITNESS_SCALE_FACTOR); - BOOST_CHECK_EQUAL(ret_script.DatacarrierBytes(), 0); + BOOST_CHECK_EQUAL(DatacarrierBytesStr(ret_script), "0+0"); } { // P2SH - no datacarrier bytes CScript prev_script; // scriptPubKey @@ -1565,7 +1570,7 @@ BOOST_AUTO_TEST_CASE(script_GetScriptForTransactionInput) auto [ret_script, scale] = GetScriptForTransactionInput(prev_script, tx_in); BOOST_CHECK(ret_script == redeem_script); BOOST_CHECK_EQUAL(scale, WITNESS_SCALE_FACTOR); - BOOST_CHECK_EQUAL(ret_script.DatacarrierBytes(), 0); + BOOST_CHECK_EQUAL(DatacarrierBytesStr(ret_script), "0+0"); } { // P2SH - with datacarrier bytes CScript prev_script; // scriptPubKey @@ -1580,7 +1585,7 @@ BOOST_AUTO_TEST_CASE(script_GetScriptForTransactionInput) BOOST_CHECK(ret_script == redeem_script); BOOST_CHECK_EQUAL(scale, WITNESS_SCALE_FACTOR); // OP_RETURN(1), length(1), zeros(27) = 29 - BOOST_CHECK_EQUAL(ret_script.DatacarrierBytes(), 29); + BOOST_CHECK_EQUAL(DatacarrierBytesStr(ret_script), "29+0"); } { // P2WPKH - no datacarrier bytes CScript prev_script; // scriptPubKey @@ -1596,7 +1601,7 @@ BOOST_AUTO_TEST_CASE(script_GetScriptForTransactionInput) // should have no script at all since it's wrapped P2WPKH BOOST_CHECK(ret_script == CScript()); BOOST_CHECK_EQUAL(scale, 0); - BOOST_CHECK_EQUAL(ret_script.DatacarrierBytes(), 0); + BOOST_CHECK_EQUAL(DatacarrierBytesStr(ret_script), "0+0"); } { // P2WSH - no datacarrier bytes CScript prev_script; // scriptPubKey @@ -1612,7 +1617,7 @@ BOOST_AUTO_TEST_CASE(script_GetScriptForTransactionInput) auto [ret_script, scale] = GetScriptForTransactionInput(prev_script, tx_in); BOOST_CHECK(ret_script == redeem_script); BOOST_CHECK_EQUAL(scale, 1); - BOOST_CHECK_EQUAL(ret_script.DatacarrierBytes(), 0); + BOOST_CHECK_EQUAL(DatacarrierBytesStr(ret_script), "0+0"); } { // P2WSH - some datacarrier bytes CScript prev_script; // scriptPubKey @@ -1629,7 +1634,7 @@ BOOST_AUTO_TEST_CASE(script_GetScriptForTransactionInput) BOOST_CHECK(ret_script == redeem_script); BOOST_CHECK_EQUAL(scale, 1); // OP_FALSE(1), OP_IF(1), length(1), zeros(10), OP_ENDIF(1) - BOOST_CHECK_EQUAL(ret_script.DatacarrierBytes(), 14); + BOOST_CHECK_EQUAL(DatacarrierBytesStr(ret_script), "0+14"); } { // P2SH-P2WPKH - no datacarrier bytes CScript prev_script; // scriptPubKey @@ -1644,7 +1649,7 @@ BOOST_AUTO_TEST_CASE(script_GetScriptForTransactionInput) BOOST_CHECK(ret_script == CScript()); // data bytes in the witness get discounted (*1 instead of *4) BOOST_CHECK_EQUAL(scale, 0); - BOOST_CHECK_EQUAL(ret_script.DatacarrierBytes(), 0); + BOOST_CHECK_EQUAL(DatacarrierBytesStr(ret_script), "0+0"); } { // P2SH-P2WSH - no datacarrier bytes CScript prev_script; // scriptPubKey @@ -1668,7 +1673,7 @@ BOOST_AUTO_TEST_CASE(script_GetScriptForTransactionInput) BOOST_CHECK(ret_script == witness_redeem_script); // data bytes in the witness get discounted (*1 instead of *4) BOOST_CHECK_EQUAL(scale, 1); - BOOST_CHECK_EQUAL(ret_script.DatacarrierBytes(), 0); + BOOST_CHECK_EQUAL(DatacarrierBytesStr(ret_script), "0+0"); } { // P2SH-P2WSH - some datacarrier bytes CScript prev_script; // scriptPubKey @@ -1693,7 +1698,7 @@ BOOST_AUTO_TEST_CASE(script_GetScriptForTransactionInput) // data bytes in the witness get discounted (*1 instead of *4) BOOST_CHECK_EQUAL(scale, 1); // OP_FALSE(1), OP_IF(1), length(1), zeros(10), OP_ENDIF(1) = 14 - BOOST_CHECK_EQUAL(ret_script.DatacarrierBytes(), 14); + BOOST_CHECK_EQUAL(DatacarrierBytesStr(ret_script), "0+14"); } { // P2TR keypath - no datacarrier bytes CScript prev_script; // scriptPubKey @@ -1705,7 +1710,7 @@ BOOST_AUTO_TEST_CASE(script_GetScriptForTransactionInput) auto [ret_script, scale] = GetScriptForTransactionInput(prev_script, tx_in); BOOST_CHECK(ret_script == CScript()); BOOST_CHECK_EQUAL(scale, 0); - BOOST_CHECK_EQUAL(ret_script.DatacarrierBytes(), 0); + BOOST_CHECK_EQUAL(DatacarrierBytesStr(ret_script), "0+0"); } { // P2TR keypath - annex but no script - no datacarrier bytes CScript prev_script; // scriptPubKey @@ -1719,7 +1724,7 @@ BOOST_AUTO_TEST_CASE(script_GetScriptForTransactionInput) auto [ret_script, scale] = GetScriptForTransactionInput(prev_script, tx_in); BOOST_CHECK(ret_script == CScript()); BOOST_CHECK_EQUAL(scale, 0); - BOOST_CHECK_EQUAL(ret_script.DatacarrierBytes(), 0); + BOOST_CHECK_EQUAL(DatacarrierBytesStr(ret_script), "0+0"); } { // P2TR scriptpath - no datacarrier bytes CScript prev_script; // scriptPubKey @@ -1738,7 +1743,7 @@ BOOST_AUTO_TEST_CASE(script_GetScriptForTransactionInput) auto [ret_script, scale] = GetScriptForTransactionInput(prev_script, tx_in); BOOST_CHECK(ret_script == script); BOOST_CHECK_EQUAL(scale, 1); - BOOST_CHECK_EQUAL(ret_script.DatacarrierBytes(), 0); + BOOST_CHECK_EQUAL(DatacarrierBytesStr(ret_script), "0+0"); } { // P2TR scriptpath - some datacarrier bytes CScript prev_script; // scriptPubKey @@ -1756,7 +1761,7 @@ BOOST_AUTO_TEST_CASE(script_GetScriptForTransactionInput) auto [ret_script, scale] = GetScriptForTransactionInput(prev_script, tx_in); BOOST_CHECK(ret_script == script); BOOST_CHECK_EQUAL(scale, 1); - BOOST_CHECK_EQUAL(ret_script.DatacarrierBytes(), 3); + BOOST_CHECK_EQUAL(DatacarrierBytesStr(ret_script), "3+0"); } } diff --git a/src/validation.cpp b/src/validation.cpp index 8f4d526b62..3ea70bd33f 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -1014,8 +1014,14 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws) return state.Invalid(TxValidationResult::TX_INPUTS_NOT_STANDARD, reason); } - if (m_pool.m_opts.datacarrier_fullcount && (!ignore_rejects.count("txn-datacarrier-exceeded")) && DatacarrierBytes(tx, m_view) > m_pool.m_opts.max_datacarrier_bytes.value_or(0)) { - return state.Invalid(TxValidationResult::TX_INPUTS_NOT_STANDARD, "txn-datacarrier-exceeded"); + if (m_pool.m_opts.datacarrier_fullcount || !m_pool.m_opts.accept_non_std_datacarrier) { + const auto dcb = DatacarrierBytes(tx, m_view); + if (dcb.second > 0 && !(m_pool.m_opts.accept_non_std_datacarrier || ignore_rejects.count("txn-datacarrier-nonstandard"))) { + return state.Invalid(TxValidationResult::TX_INPUTS_NOT_STANDARD, "txn-datacarrier-nonstandard"); + } + if (m_pool.m_opts.datacarrier_fullcount && (!ignore_rejects.count("txn-datacarrier-exceeded")) && dcb.first + dcb.second > m_pool.m_opts.max_datacarrier_bytes.value_or(0)) { + return state.Invalid(TxValidationResult::TX_INPUTS_NOT_STANDARD, "txn-datacarrier-exceeded"); + } } // Check for non-standard witnesses.