diff --git a/src/bench/mempool_eviction.cpp b/src/bench/mempool_eviction.cpp index e118fca314..22e82b4ad2 100644 --- a/src/bench/mempool_eviction.cpp +++ b/src/bench/mempool_eviction.cpp @@ -21,7 +21,7 @@ static void AddTx(const CTransactionRef& tx, const CAmount& nFee, CTxMemPool& po pool.addUnchecked(CTxMemPoolEntry( tx, nFee, nTime, dPriority, nHeight, sequence, tx->GetValueOut(), - spendsCoinbase, sigOpCost, lp)); + spendsCoinbase, /*extra_weight=*/0, sigOpCost, lp)); } // Right now this is only testing eviction performance in an extremely small diff --git a/src/bench/mempool_stress.cpp b/src/bench/mempool_stress.cpp index 814c3032ef..c1b89f1fe2 100644 --- a/src/bench/mempool_stress.cpp +++ b/src/bench/mempool_stress.cpp @@ -22,7 +22,7 @@ static void AddTx(const CTransactionRef& tx, CTxMemPool& pool) EXCLUSIVE_LOCKS_R bool spendsCoinbase = false; unsigned int sigOpCost = 4; LockPoints lp; - pool.addUnchecked(CTxMemPoolEntry(tx, 1000, nTime, nHeight, sequence, /*entry_tx_inputs_coin_age=*/coin_age, tx->GetValueOut(), spendsCoinbase, sigOpCost, lp)); + pool.addUnchecked(CTxMemPoolEntry(tx, 1000, nTime, nHeight, sequence, /*entry_tx_inputs_coin_age=*/coin_age, tx->GetValueOut(), spendsCoinbase, /*extra_weight=*/0, sigOpCost, lp)); } struct Available { diff --git a/src/bench/rpc_mempool.cpp b/src/bench/rpc_mempool.cpp index 8eb83c4853..5cd86fa848 100644 --- a/src/bench/rpc_mempool.cpp +++ b/src/bench/rpc_mempool.cpp @@ -17,7 +17,7 @@ static void AddTx(const CTransactionRef& tx, const CAmount& fee, CTxMemPool& pool) EXCLUSIVE_LOCKS_REQUIRED(cs_main, pool.cs) { LockPoints lp; - pool.addUnchecked(CTxMemPoolEntry(tx, fee, /*time=*/0, /*entry_height=*/0, /*entry_sequence=*/0, /*entry_tx_inputs_coin_age=*/0.0, /*in_chain_input_value=*/0, /*spends_coinbase=*/false, /*sigops_cost=*/4, lp)); + pool.addUnchecked(CTxMemPoolEntry(tx, fee, /*time=*/0, /*entry_height=*/0, /*entry_sequence=*/0, /*entry_tx_inputs_coin_age=*/0.0, /*in_chain_input_value=*/0, /*spends_coinbase=*/false, /*extra_weight=*/0, /*sigops_cost=*/4, lp)); } static void RpcMempool(benchmark::Bench& bench) diff --git a/src/common/args.cpp b/src/common/args.cpp index 86a71fece1..2bb3f141d7 100644 --- a/src/common/args.cpp +++ b/src/common/args.cpp @@ -507,6 +507,25 @@ int64_t SettingToInt(const common::SettingsValue& value, int64_t nDefault) return SettingToInt(value).value_or(nDefault); } +std::optional ArgsManager::GetFixedPointArg(const std::string& arg, int decimals) const +{ + const common::SettingsValue value = GetSetting(arg); + return SettingToFixedPoint(value, decimals); +} + +std::optional SettingToFixedPoint(const common::SettingsValue& value, int decimals) +{ + if (value.isNull()) return std::nullopt; + if (value.isFalse()) return 0; + if (value.isTrue()) return 1; + if (!value.isNum()) value.get_str(); // throws an exception if type is wrong + int64_t v; + if (!ParseFixedPoint(value.getValStr(), decimals, &v)) { + throw std::runtime_error(strprintf("Parse error ('%s')", value.getValStr())); + } + return v; +} + bool ArgsManager::GetBoolArg(const std::string& strArg, bool fDefault) const { return GetBoolArg(strArg).value_or(fDefault); diff --git a/src/common/args.h b/src/common/args.h index 1495e20f09..66e2c39460 100644 --- a/src/common/args.h +++ b/src/common/args.h @@ -92,6 +92,8 @@ std::optional SettingToString(const common::SettingsValue&); int64_t SettingToInt(const common::SettingsValue&, int64_t); std::optional SettingToInt(const common::SettingsValue&); +std::optional SettingToFixedPoint(const common::SettingsValue&, int decimals); + bool SettingToBool(const common::SettingsValue&, bool); std::optional SettingToBool(const common::SettingsValue&); @@ -305,6 +307,15 @@ protected: int64_t GetIntArg(const std::string& strArg, int64_t nDefault) const; std::optional GetIntArg(const std::string& strArg) const; + /** + * Return fixed-point argument + * + * @param arg Argument to get (e.g. "-foo") + * @param decimals Number of fractional decimal digits to accept + * @return Command-line argument (0 if invalid number) multiplied by 10**decimals + */ + std::optional GetFixedPointArg(const std::string& arg, int decimals) const; + /** * Return boolean argument or default value * diff --git a/src/init.cpp b/src/init.cpp index cbbf6ed08d..cd767271ca 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -680,6 +680,7 @@ 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("-datacarriercost", strprintf("Treat extra data in transactions as at least N vbytes per actual byte (default: %s)", DEFAULT_WEIGHT_PER_DATA_BYTE / 4.0), 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("Maximum size of data in data carrier transactions we relay and mine, in bytes (default: %u)", @@ -1087,6 +1088,10 @@ bool AppInitParameterInteraction(const ArgsManager& args) } } + if (auto parsed = args.GetFixedPointArg("-datacarriercost", 2)) { + g_weight_per_data_byte = ((*parsed * WITNESS_SCALE_FACTOR) + 99) / 100; + } + nBytesPerSigOp = args.GetIntArg("-bytespersigop", nBytesPerSigOp); nBytesPerSigOpStrict = args.GetIntArg("-bytespersigopstrict", nBytesPerSigOpStrict); diff --git a/src/kernel/mempool_entry.h b/src/kernel/mempool_entry.h index ff32f52b5c..19030235b1 100644 --- a/src/kernel/mempool_entry.h +++ b/src/kernel/mempool_entry.h @@ -96,6 +96,7 @@ private: const int64_t nTime; //!< Local time when entering the mempool const uint64_t entry_sequence; //!< Sequence number used to determine whether this transaction is too recent for relay const int64_t sigOpCost; //!< Total sigop cost + const int32_t m_extra_weight; //!< Policy-only additional transaction weight beyond nTxWeight const size_t nModSize; //!< Cached modified size for priority const double entryPriority; //!< Priority when entering the mempool const unsigned int entryHeight; //!< Chain height when entering the mempool @@ -127,6 +128,7 @@ public: double entry_tx_inputs_coin_age, CAmount in_chain_input_value, bool spends_coinbase, + int32_t extra_weight, int64_t sigops_cost, LockPoints lp) : tx{tx}, nFee{fee}, @@ -135,6 +137,7 @@ public: nTime{time}, entry_sequence{entry_sequence}, sigOpCost{sigops_cost}, + m_extra_weight{extra_weight}, nModSize{CalculateModifiedSize(*tx, GetTxSize())}, entryPriority{ComputePriority2(entry_tx_inputs_coin_age, nModSize)}, entryHeight{entry_height}, @@ -177,7 +180,7 @@ public: const CAmount& GetFee() const { return nFee; } int32_t GetTxSize() const { - return GetVirtualTransactionSize(nTxWeight, sigOpCost, ::nBytesPerSigOp); + return GetVirtualTransactionSize(nTxWeight + m_extra_weight, sigOpCost, ::nBytesPerSigOp); } int32_t GetTxWeight() const { return nTxWeight; } std::chrono::seconds GetTime() const { return std::chrono::seconds{nTime}; } diff --git a/src/node/interfaces.cpp b/src/node/interfaces.cpp index 6fc3962d9f..c7bafa2617 100644 --- a/src/node/interfaces.cpp +++ b/src/node/interfaces.cpp @@ -749,7 +749,7 @@ public: { if (!m_node.mempool) return {}; LockPoints lp; - CTxMemPoolEntry entry(tx, 0, 0, 0, 0, 0, 0, false, 0, lp); + CTxMemPoolEntry entry(tx, 0, 0, 0, 0, 0, 0, false, /*extra_weight=*/0, 0, lp); LOCK(m_node.mempool->cs); return m_node.mempool->CheckPackageLimits({tx}, entry.GetTxSize()); } diff --git a/src/node/psbt.cpp b/src/node/psbt.cpp index 51e252bffc..37b8bd02cc 100644 --- a/src/node/psbt.cpp +++ b/src/node/psbt.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -137,7 +138,7 @@ PSBTAnalysis AnalyzePSBT(PartiallySignedTransaction psbtx) if (success) { CTransaction ctx = CTransaction(mtx); - size_t size(GetVirtualTransactionSize(ctx, GetTransactionSigOpCost(ctx, view, STANDARD_SCRIPT_VERIFY_FLAGS), ::nBytesPerSigOp)); + size_t size(GetVirtualTransactionSize(GetTransactionWeight(ctx) + CalculateExtraTxWeight(ctx, view, ::g_weight_per_data_byte), GetTransactionSigOpCost(ctx, view, STANDARD_SCRIPT_VERIFY_FLAGS), ::nBytesPerSigOp)); result.estimated_vsize = size; // Estimate fee rate CFeeRate feerate(fee, size); diff --git a/src/policy/policy.cpp b/src/policy/policy.cpp index 8ef3a98830..74b5a76735 100644 --- a/src/policy/policy.cpp +++ b/src/policy/policy.cpp @@ -435,3 +435,26 @@ size_t DatacarrierBytes(const CTransaction& tx, const CCoinsViewCache& view) return ret; } + +int32_t CalculateExtraTxWeight(const CTransaction& tx, const CCoinsViewCache& view, const unsigned int weight_per_data_byte) +{ + int32_t mod_weight{0}; + + // Add in any extra weight for data bytes + if (weight_per_data_byte > 1) { + for (const CTxIn& txin : tx.vin) { + 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); + } + } + 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); + } + } + } + + return mod_weight; +} diff --git a/src/policy/policy.h b/src/policy/policy.h index dd563d8bda..a00d09a42e 100644 --- a/src/policy/policy.h +++ b/src/policy/policy.h @@ -48,6 +48,8 @@ static constexpr unsigned int DEFAULT_INCREMENTAL_RELAY_FEE{1000}; static constexpr unsigned int DEFAULT_BYTES_PER_SIGOP{20}; /** Default for -bytespersigopstrict */ static constexpr unsigned int DEFAULT_BYTES_PER_SIGOP_STRICT{20}; +/** Default for -datacarriercost (multiplied by WITNESS_SCALE_FACTOR) */ +static constexpr unsigned int DEFAULT_WEIGHT_PER_DATA_BYTE{1}; /** Default for -permitbarepubkey */ static constexpr bool DEFAULT_PERMIT_BAREPUBKEY{true}; /** Default for -permitbaremultisig */ @@ -201,4 +203,6 @@ std::pair GetScriptForTransactionInput(CScript prevScript size_t DatacarrierBytes(const CTransaction& tx, const CCoinsViewCache& view); +int32_t CalculateExtraTxWeight(const CTransaction& tx, const CCoinsViewCache& view, const unsigned int weight_per_data_byte); + #endif // BITCOIN_POLICY_POLICY_H diff --git a/src/policy/settings.cpp b/src/policy/settings.cpp index 6200b4bd30..a202a9495c 100644 --- a/src/policy/settings.cpp +++ b/src/policy/settings.cpp @@ -9,3 +9,4 @@ unsigned int nBytesPerSigOp = DEFAULT_BYTES_PER_SIGOP; unsigned int nBytesPerSigOpStrict = DEFAULT_BYTES_PER_SIGOP_STRICT; +unsigned int g_weight_per_data_byte = DEFAULT_WEIGHT_PER_DATA_BYTE; diff --git a/src/policy/settings.h b/src/policy/settings.h index 14fb9b769b..47ea346283 100644 --- a/src/policy/settings.h +++ b/src/policy/settings.h @@ -8,5 +8,6 @@ extern unsigned int nBytesPerSigOp; extern unsigned int nBytesPerSigOpStrict; +extern unsigned int g_weight_per_data_byte; #endif // BITCOIN_POLICY_SETTINGS_H diff --git a/src/test/fuzz/util/mempool.cpp b/src/test/fuzz/util/mempool.cpp index a648da19f2..3dfa3d6e94 100644 --- a/src/test/fuzz/util/mempool.cpp +++ b/src/test/fuzz/util/mempool.cpp @@ -38,6 +38,7 @@ CTxMemPoolEntry ConsumeTxMemPoolEntry(FuzzedDataProvider& fuzzed_data_provider, const double coin_age = fuzzed_data_provider.ConsumeFloatingPoint(); const unsigned int entry_height = fuzzed_data_provider.ConsumeIntegralInRange(0, std::numeric_limits::max() - 1); const bool spends_coinbase = fuzzed_data_provider.ConsumeBool(); + const int32_t extra_weight = fuzzed_data_provider.ConsumeIntegralInRange(0, GetTransactionWeight(tx) * 3); const unsigned int sig_op_cost = fuzzed_data_provider.ConsumeIntegralInRange(0, MAX_BLOCK_SIGOPS_COST); - return CTxMemPoolEntry{MakeTransactionRef(tx), fee, time, entry_height, entry_sequence, /*entry_tx_inputs_coin_age=*/coin_age, tx.GetValueOut(), spends_coinbase, sig_op_cost, {}}; + return CTxMemPoolEntry{MakeTransactionRef(tx), fee, time, entry_height, entry_sequence, /*entry_tx_inputs_coin_age=*/coin_age, tx.GetValueOut(), spends_coinbase, extra_weight, sig_op_cost, {}}; } diff --git a/src/test/util/setup_common.cpp b/src/test/util/setup_common.cpp index 053540098d..29d84059f8 100644 --- a/src/test/util/setup_common.cpp +++ b/src/test/util/setup_common.cpp @@ -566,7 +566,7 @@ std::vector TestChain100Setup::PopulateMempool(FastRandomContex /*time=*/0, /*entry_height=*/ height, /*entry_sequence=*/0, /*entry_tx_inputs_coin_age=*/coin_age, in_chain_input_value, - /*spends_coinbase=*/false, /*sigops_cost=*/4, lp)); + /*spends_coinbase=*/false, /*extra_weight=*/0, /*sigops_cost=*/4, lp)); } --num_transactions; } @@ -598,7 +598,7 @@ void TestChain100Setup::MockMempoolMinFee(const CFeeRate& target_feerate) /*time=*/0, /*entry_height=*/1, /*entry_sequence=*/0, /*entry_tx_inputs_coin_age=*/0.0, /*in_chain_input_value=*/0, - /*spends_coinbase=*/true, /*sigops_cost=*/1, lp)); + /*spends_coinbase=*/true, /*extra_weight=*/0, /*sigops_cost=*/1, lp)); m_node.mempool->TrimToSize(0); assert(m_node.mempool->GetMinFee() == target_feerate); } diff --git a/src/test/util/txmempool.cpp b/src/test/util/txmempool.cpp index d018b53ae5..9ee5fd1ae0 100644 --- a/src/test/util/txmempool.cpp +++ b/src/test/util/txmempool.cpp @@ -39,7 +39,7 @@ CTxMemPoolEntry TestMemPoolEntryHelper::FromTx(const CTransactionRef& tx) const { constexpr double coin_age{0}; const CAmount inChainValue = 0; - return CTxMemPoolEntry{tx, nFee, TicksSinceEpoch(time), nHeight, m_sequence, /*entry_tx_inputs_coin_age=*/coin_age, inChainValue, spendsCoinbase, sigOpCost, lp}; + return CTxMemPoolEntry{tx, nFee, TicksSinceEpoch(time), nHeight, m_sequence, /*entry_tx_inputs_coin_age=*/coin_age, inChainValue, spendsCoinbase, /*extra_weight=*/0, sigOpCost, lp}; } std::optional CheckPackageMempoolAcceptResult(const Package& txns, diff --git a/src/validation.cpp b/src/validation.cpp index 6f253e32d4..8f4d526b62 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -1048,10 +1048,11 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws) // Set entry_sequence to 0 when rejectmsg_zero_mempool_entry_seq is used; this allows txs from a block // reorg to be marked earlier than any child txs that were already in the mempool. const uint64_t entry_sequence = args.m_ignore_rejects.count(rejectmsg_zero_mempool_entry_seq) ? 0 : m_pool.GetSequence(); + int32_t extra_weight = CalculateExtraTxWeight(*ptx, m_view, ::g_weight_per_data_byte); entry.reset(new CTxMemPoolEntry(ptx, ws.m_base_fees, nAcceptTime, m_active_chainstate.m_chain.Height(), entry_sequence, /*entry_tx_inputs_coin_age=*/coin_age, inChainInputValue, - fSpendsCoinbase, nSigOpsCost, lock_points.value())); + fSpendsCoinbase, extra_weight, nSigOpsCost, lock_points.value())); ws.m_vsize = entry->GetTxSize(); entry->mapSPK = mapSPK; diff --git a/test/lint/check-doc.py b/test/lint/check-doc.py index f55d0f8cb7..8696ca8e0e 100755 --- a/test/lint/check-doc.py +++ b/test/lint/check-doc.py @@ -15,7 +15,7 @@ import re FOLDER_GREP = 'src' FOLDER_TEST = 'src/test/' -REGEX_ARG = r'\b(?:GetArg|GetArgs|GetBoolArg|GetIntArg|GetPathArg|IsArgSet|get_net)\("(-[^"]+)"' +REGEX_ARG = r'\b(?:GetArg|GetArgs|GetBoolArg|GetIntArg|GetFixedPointArg|GetPathArg|IsArgSet|get_net)\("(-[^"]+)"' REGEX_DOC = r'AddArg\("(-[^"=]+?)(?:=|")' CMD_ROOT_DIR = '$(git rev-parse --show-toplevel)/{}'.format(FOLDER_GREP) CMD_GREP_ARGS = r"git grep --perl-regexp '{}' -- {} ':(exclude){}'".format(REGEX_ARG, CMD_ROOT_DIR, FOLDER_TEST)