From e3028a0da5200ce9cacd977c53e210dcf58311e1 Mon Sep 17 00:00:00 2001 From: TheCharlatan Date: Sat, 1 Nov 2025 21:22:46 +0100 Subject: [PATCH 01/11] kernel: Add mempool interface As a first step add `removeRecursive` to the interface. --- src/CMakeLists.txt | 1 + src/init.cpp | 6 ++++ src/kernel/bitcoinkernel.cpp | 6 ++++ src/kernel/chainstatemanager_opts.h | 2 ++ src/kernel/mempool_interface.h | 26 +++++++++++++++++ src/node/context.cpp | 1 + src/node/context.h | 2 ++ src/node/kernel_mempool.cpp | 18 ++++++++++++ src/node/kernel_mempool.h | 29 +++++++++++++++++++ src/test/fuzz/cmpctblock.cpp | 5 +++- src/test/util/setup_common.cpp | 4 +++ .../validation_chainstatemanager_tests.cpp | 6 ++++ src/validation.cpp | 2 +- src/validation.h | 1 + 14 files changed, 107 insertions(+), 2 deletions(-) create mode 100644 src/kernel/mempool_interface.h create mode 100644 src/node/kernel_mempool.cpp create mode 100644 src/node/kernel_mempool.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 524c28165cca..c5144db70a2e 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -227,6 +227,7 @@ add_library(bitcoin_node STATIC EXCLUDE_FROM_ALL node/eviction.cpp node/interface_ui.cpp node/interfaces.cpp + node/kernel_mempool.cpp node/kernel_notifications.cpp node/mempool_args.cpp node/mempool_persist.cpp diff --git a/src/init.cpp b/src/init.cpp index da58efe74c42..4fb08eb2348b 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -60,6 +60,7 @@ #include #include #include +#include #include #include #include @@ -156,6 +157,7 @@ using node::DEFAULT_PRINT_MODIFIED_FEE; using node::DEFAULT_STOPATHEIGHT; using node::DumpMempool; using node::ImportBlocks; +using node::KernelMempool; using node::KernelNotifications; using node::LoadChainstate; using node::LoadMempool; @@ -1138,9 +1140,11 @@ bool AppInitParameterInteraction(const ArgsManager& args) // Also report errors from parsing before daemonization { kernel::Notifications notifications{}; + kernel::Mempool mempool_interface{}; ChainstateManager::Options chainman_opts_dummy{ .chainparams = chainparams, .datadir = args.GetDataDirNet(), + .mempool_interface = mempool_interface, .notifications = notifications, }; auto chainman_result{ApplyArgsManOptions(args, chainman_opts_dummy)}; @@ -1341,12 +1345,14 @@ static ChainstateLoadResult InitAndLoadChainstate( auto mining_args{node::ReadMiningArgs(args)}; Assert(mining_args); // no error can happen, already checked in AppInitParameterInteraction node.mining_args = std::move(*mining_args); + node.mempool_interface = std::make_unique(*node.mempool); LogInfo("* Using %.1f MiB for in-memory UTXO set (plus up to %.1f MiB of unused mempool space)", cache_sizes.coins / double(1_MiB), mempool_opts.max_size_bytes / double(1_MiB)); ChainstateManager::Options chainman_opts{ .chainparams = chainparams, .datadir = args.GetDataDirNet(), + .mempool_interface = *node.mempool_interface, .notifications = *node.notifications, .signals = node.validation_signals.get(), }; diff --git a/src/kernel/bitcoinkernel.cpp b/src/kernel/bitcoinkernel.cpp index ba3a57d44225..009223250304 100644 --- a/src/kernel/bitcoinkernel.cpp +++ b/src/kernel/bitcoinkernel.cpp @@ -15,6 +15,8 @@ #include #include #include +#include +#include #include #include #include @@ -397,6 +399,8 @@ class Context public: std::unique_ptr m_context; + std::shared_ptr m_mempool; + std::shared_ptr m_notifications; std::unique_ptr m_interrupt; @@ -409,6 +413,7 @@ class Context Context(const ContextOptions* options, bool& sane) : m_context{std::make_unique()}, + m_mempool{std::make_unique()}, m_interrupt{std::make_unique()} { if (options) { @@ -459,6 +464,7 @@ struct ChainstateManagerOptions { : m_chainman_options{ChainstateManager::Options{ .chainparams = *context->m_chainparams, .datadir = data_dir, + .mempool_interface = *context->m_mempool, .notifications = *context->m_notifications, .signals = context->m_signals.get()}}, m_blockman_options{node::BlockManager::Options{ diff --git a/src/kernel/chainstatemanager_opts.h b/src/kernel/chainstatemanager_opts.h index 134b93194bf4..f451e0cb751f 100644 --- a/src/kernel/chainstatemanager_opts.h +++ b/src/kernel/chainstatemanager_opts.h @@ -5,6 +5,7 @@ #ifndef BITCOIN_KERNEL_CHAINSTATEMANAGER_OPTS_H #define BITCOIN_KERNEL_CHAINSTATEMANAGER_OPTS_H +#include #include #include @@ -42,6 +43,7 @@ struct ChainstateManagerOpts { std::chrono::seconds max_tip_age{DEFAULT_MAX_TIP_AGE}; DBOptions coins_db{}; CoinsViewOptions coins_view{}; + Mempool& mempool_interface; Notifications& notifications; ValidationSignals* signals{nullptr}; //! Number of script check worker threads. Zero means no parallel verification. diff --git a/src/kernel/mempool_interface.h b/src/kernel/mempool_interface.h new file mode 100644 index 000000000000..1ad71d3832b7 --- /dev/null +++ b/src/kernel/mempool_interface.h @@ -0,0 +1,26 @@ +// Copyright (c) 2025 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_KERNEL_MEMPOOL_INTERFACE_H +#define BITCOIN_KERNEL_MEMPOOL_INTERFACE_H + +class CTransaction; + +namespace kernel { + +/** + * A base class defining functions for notifying about certain kernel + * events. + */ +class Mempool +{ +public: + virtual ~Mempool() = default; + + virtual void removeRecursive(const CTransaction& tx) {} +}; + +} // namespace kernel + +#endif // BITCOIN_KERNEL_MEMPOOL_INTERFACE_H diff --git a/src/node/context.cpp b/src/node/context.cpp index 164361601f9b..ccb462132288 100644 --- a/src/node/context.cpp +++ b/src/node/context.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include diff --git a/src/node/context.h b/src/node/context.h index b8b3274f090c..b0db78ca0ed9 100644 --- a/src/node/context.h +++ b/src/node/context.h @@ -44,6 +44,7 @@ class SignalInterrupt; namespace node { class KernelNotifications; +class KernelMempool; class Warnings; //! NodeContext struct containing references to chain state and connection @@ -93,6 +94,7 @@ struct NodeContext { std::function rpc_interruption_point = [] {}; //! Issues blocking calls about sync status, errors and warnings std::unique_ptr notifications; + std::unique_ptr mempool_interface; //! Issues calls about blocks and transactions std::unique_ptr validation_signals; std::atomic exit_status{EXIT_SUCCESS}; diff --git a/src/node/kernel_mempool.cpp b/src/node/kernel_mempool.cpp new file mode 100644 index 000000000000..fdfdfb68b32d --- /dev/null +++ b/src/node/kernel_mempool.cpp @@ -0,0 +1,18 @@ +// Copyright (c) 2025 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include + +#include +#include + +namespace node { + +void KernelMempool::removeRecursive(const CTransaction& tx) +{ + LOCK(m_mempool.cs); + m_mempool.removeRecursive(tx, MemPoolRemovalReason::REORG); +} + +} // namespace node diff --git a/src/node/kernel_mempool.h b/src/node/kernel_mempool.h new file mode 100644 index 000000000000..18aea728824e --- /dev/null +++ b/src/node/kernel_mempool.h @@ -0,0 +1,29 @@ +// Copyright (c) 2025 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_NODE_KERNEL_MEMPOOL_H +#define BITCOIN_NODE_KERNEL_MEMPOOL_H + +#include + +class CTransaction; +class CTxMemPool; + +namespace node { + +class KernelMempool: public kernel::Mempool +{ +public: + KernelMempool(CTxMemPool& mempool) + : m_mempool{mempool} {} + + void removeRecursive(const CTransaction& tx) override; + +private: + CTxMemPool& m_mempool; +}; + +} // namespace node + +#endif // BITCOIN_NODE_KERNEL_MEMPOOL_H diff --git a/src/test/fuzz/cmpctblock.cpp b/src/test/fuzz/cmpctblock.cpp index 882169c93a33..3b134bbbb53e 100644 --- a/src/test/fuzz/cmpctblock.cpp +++ b/src/test/fuzz/cmpctblock.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -111,12 +112,14 @@ void ResetChainmanAndMempool(TestingSetup& setup) { SetMockTime(Params().GenesisBlock().Time()); + setup.m_node.chainman.reset(); + bilingual_str error{}; setup.m_node.mempool.reset(); setup.m_node.mempool = std::make_unique(MemPoolOptionsForTest(setup.m_node), error); + setup.m_node.mempool_interface = std::make_unique(*setup.m_node.mempool); Assert(error.empty()); - setup.m_node.chainman.reset(); setup.m_make_chainman(); setup.LoadVerifyActivateChainstate(); diff --git a/src/test/util/setup_common.cpp b/src/test/util/setup_common.cpp index d7b7b29d5841..0c6df7534bd1 100644 --- a/src/test/util/setup_common.cpp +++ b/src/test/util/setup_common.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -98,6 +99,7 @@ using namespace util::hex_literals; using node::ApplyArgsManOptions; using node::BlockManager; +using node::KernelMempool; using node::KernelNotifications; using node::LoadChainstate; using node::RegenerateCommitments; @@ -299,6 +301,7 @@ ChainTestingSetup::ChainTestingSetup(const ChainType chainType, TestOpts opts) m_node.warnings = std::make_unique(); m_node.notifications = std::make_unique(Assert(m_node.shutdown_request), m_node.exit_status, *Assert(m_node.warnings)); + m_node.mempool_interface = std::make_unique(*Assert(m_node.mempool)); m_make_chainman = [this, &chainparams, opts] { Assert(!m_node.chainman); @@ -306,6 +309,7 @@ ChainTestingSetup::ChainTestingSetup(const ChainType chainType, TestOpts opts) .chainparams = chainparams, .datadir = m_args.GetDataDirNet(), .check_block_index = 1, + .mempool_interface = *m_node.mempool_interface, .notifications = *m_node.notifications, .signals = m_node.validation_signals.get(), // Use no worker threads while fuzzing to avoid non-determinism diff --git a/src/test/validation_chainstatemanager_tests.cpp b/src/test/validation_chainstatemanager_tests.cpp index 9818b51e51b9..09cbbd4c9aa6 100644 --- a/src/test/validation_chainstatemanager_tests.cpp +++ b/src/test/validation_chainstatemanager_tests.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -31,6 +32,7 @@ #include using node::BlockManager; +using node::KernelMempool; using node::KernelNotifications; using node::SnapshotMetadata; @@ -426,9 +428,11 @@ struct SnapshotTestSetup : TestChain100Setup { chainman.ResetChainstates(); BOOST_CHECK_EQUAL(chainman.m_chainstates.size(), 0); m_node.notifications = std::make_unique(Assert(m_node.shutdown_request), m_node.exit_status, *Assert(m_node.warnings)); + m_node.mempool_interface = std::make_unique(*Assert(m_node.mempool)); const ChainstateManager::Options chainman_opts{ .chainparams = ::Params(), .datadir = chainman.m_options.datadir, + .mempool_interface = *m_node.mempool_interface, .notifications = *m_node.notifications, .signals = m_node.validation_signals.get(), }; @@ -954,9 +958,11 @@ BOOST_FIXTURE_TEST_CASE(chainstatemanager_args, BasicTestingSetup) //! Try to apply the provided args to a ChainstateManager::Options auto get_opts = [&](const std::vector& args) { static kernel::Notifications notifications{}; + static kernel::Mempool mempool{}; static const ChainstateManager::Options options{ .chainparams = ::Params(), .datadir = {}, + .mempool_interface = mempool, .notifications = notifications}; return SetOptsFromArgs(*this->m_node.args, options, args); }; diff --git a/src/validation.cpp b/src/validation.cpp index 8a4986b49517..24aee8819c92 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -2973,7 +2973,7 @@ bool Chainstate::DisconnectTip(BlockValidationState& state, DisconnectedBlockTra // Save transactions to re-add to mempool at end of reorg. If any entries are evicted for // exceeding memory limits, remove them and their descendants from the mempool. for (auto&& evicted_tx : disconnectpool->AddTransactionsFromBlock(block.vtx)) { - m_mempool->removeRecursive(*evicted_tx, MemPoolRemovalReason::REORG); + m_chainman.GetMempool().removeRecursive(*evicted_tx); } } diff --git a/src/validation.h b/src/validation.h index 4cb5dc631e95..b8778e191f4e 100644 --- a/src/validation.h +++ b/src/validation.h @@ -1010,6 +1010,7 @@ class ChainstateManager const arith_uint256& MinimumChainWork() const { return *Assert(m_options.minimum_chain_work); } const uint256& AssumedValidBlock() const { return *Assert(m_options.assumed_valid_block); } kernel::Notifications& GetNotifications() const { return m_options.notifications; }; + kernel::Mempool& GetMempool() const { return m_options.mempool_interface; }; /** * Make various assertions about the state of the block index. From ceb15789feacf59c23809eb9b2988c37614ba13c Mon Sep 17 00:00:00 2001 From: TheCharlatan Date: Sat, 1 Nov 2025 22:18:09 +0100 Subject: [PATCH 02/11] kernel: Add removeForBlock to mempool interface --- src/kernel/mempool_interface.h | 2 ++ src/node/kernel_mempool.cpp | 6 ++++++ src/node/kernel_mempool.h | 2 ++ src/validation.cpp | 2 +- 4 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/kernel/mempool_interface.h b/src/kernel/mempool_interface.h index 1ad71d3832b7..26f9285dcac8 100644 --- a/src/kernel/mempool_interface.h +++ b/src/kernel/mempool_interface.h @@ -5,6 +5,7 @@ #ifndef BITCOIN_KERNEL_MEMPOOL_INTERFACE_H #define BITCOIN_KERNEL_MEMPOOL_INTERFACE_H +class CBlock; class CTransaction; namespace kernel { @@ -19,6 +20,7 @@ class Mempool virtual ~Mempool() = default; virtual void removeRecursive(const CTransaction& tx) {} + virtual void removeForBlock(const CBlock& block, unsigned int block_height) {} }; } // namespace kernel diff --git a/src/node/kernel_mempool.cpp b/src/node/kernel_mempool.cpp index fdfdfb68b32d..e11c9db3bbed 100644 --- a/src/node/kernel_mempool.cpp +++ b/src/node/kernel_mempool.cpp @@ -15,4 +15,10 @@ void KernelMempool::removeRecursive(const CTransaction& tx) m_mempool.removeRecursive(tx, MemPoolRemovalReason::REORG); } +void KernelMempool::removeForBlock(const CBlock& block, unsigned int block_height) +{ + LOCK(m_mempool.cs); + m_mempool.removeForBlock(block.vtx, block_height); +} + } // namespace node diff --git a/src/node/kernel_mempool.h b/src/node/kernel_mempool.h index 18aea728824e..e201f0996b8f 100644 --- a/src/node/kernel_mempool.h +++ b/src/node/kernel_mempool.h @@ -7,6 +7,7 @@ #include +class CBlock; class CTransaction; class CTxMemPool; @@ -19,6 +20,7 @@ class KernelMempool: public kernel::Mempool : m_mempool{mempool} {} void removeRecursive(const CTransaction& tx) override; + void removeForBlock(const CBlock& block, unsigned int nBlockHeight) override; private: CTxMemPool& m_mempool; diff --git a/src/validation.cpp b/src/validation.cpp index 24aee8819c92..efea32e92938 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -3069,7 +3069,7 @@ bool Chainstate::ConnectTip( Ticks(m_chainman.time_chainstate) / m_chainman.num_blocks_total); // Remove conflicting transactions from the mempool.; if (m_mempool) { - m_mempool->removeForBlock(block_to_connect->vtx, pindexNew->nHeight); + m_chainman.GetMempool().removeForBlock(*block_to_connect, pindexNew->nHeight); disconnectpool.removeForBlock(block_to_connect->vtx); } // Update m_chain & related variables. From b6faf853f9f103004f41800eacf5512314ef7815 Mon Sep 17 00:00:00 2001 From: TheCharlatan Date: Sat, 1 Nov 2025 22:22:13 +0100 Subject: [PATCH 03/11] kernel: Add dynamic memory usage to mempool interface --- src/kernel/mempool_interface.h | 3 +++ src/node/kernel_mempool.cpp | 5 +++++ src/node/kernel_mempool.h | 1 + src/validation.cpp | 2 +- 4 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/kernel/mempool_interface.h b/src/kernel/mempool_interface.h index 26f9285dcac8..7f41ebd5f1dd 100644 --- a/src/kernel/mempool_interface.h +++ b/src/kernel/mempool_interface.h @@ -5,6 +5,8 @@ #ifndef BITCOIN_KERNEL_MEMPOOL_INTERFACE_H #define BITCOIN_KERNEL_MEMPOOL_INTERFACE_H +#include + class CBlock; class CTransaction; @@ -21,6 +23,7 @@ class Mempool virtual void removeRecursive(const CTransaction& tx) {} virtual void removeForBlock(const CBlock& block, unsigned int block_height) {} + virtual size_t measureExternalDynamicMemoryUsage() { return 0; } }; } // namespace kernel diff --git a/src/node/kernel_mempool.cpp b/src/node/kernel_mempool.cpp index e11c9db3bbed..e94122f4bcb8 100644 --- a/src/node/kernel_mempool.cpp +++ b/src/node/kernel_mempool.cpp @@ -21,4 +21,9 @@ void KernelMempool::removeForBlock(const CBlock& block, unsigned int block_heigh m_mempool.removeForBlock(block.vtx, block_height); } +size_t KernelMempool::measureExternalDynamicMemoryUsage() +{ + return m_mempool.DynamicMemoryUsage(); +} + } // namespace node diff --git a/src/node/kernel_mempool.h b/src/node/kernel_mempool.h index e201f0996b8f..589226a5b2e0 100644 --- a/src/node/kernel_mempool.h +++ b/src/node/kernel_mempool.h @@ -21,6 +21,7 @@ class KernelMempool: public kernel::Mempool void removeRecursive(const CTransaction& tx) override; void removeForBlock(const CBlock& block, unsigned int nBlockHeight) override; + size_t measureExternalDynamicMemoryUsage() override; private: CTxMemPool& m_mempool; diff --git a/src/validation.cpp b/src/validation.cpp index efea32e92938..6f678813c844 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -2683,7 +2683,7 @@ CoinsCacheSizeState Chainstate::GetCoinsCacheSizeState( size_t max_mempool_size_bytes) { AssertLockHeld(::cs_main); - const int64_t nMempoolUsage = m_mempool ? m_mempool->DynamicMemoryUsage() : 0; + const int64_t nMempoolUsage = m_mempool ? m_chainman.GetMempool().measureExternalDynamicMemoryUsage() : 0; int64_t cacheSize = CoinsTip().DynamicMemoryUsage(); int64_t nTotalSpace = max_coins_cache_size_bytes + std::max(int64_t(max_mempool_size_bytes) - nMempoolUsage, 0); From ffc8febcf26e1a08eb3e85fbf759fdbc7b8dd5cc Mon Sep 17 00:00:00 2001 From: TheCharlatan Date: Sat, 1 Nov 2025 22:29:52 +0100 Subject: [PATCH 04/11] kernel: Add addTransactionUpdated to mempool interface --- src/kernel/mempool_interface.h | 2 ++ src/node/kernel_mempool.cpp | 8 ++++++++ src/node/kernel_mempool.h | 4 ++++ src/validation.cpp | 2 +- 4 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/kernel/mempool_interface.h b/src/kernel/mempool_interface.h index 7f41ebd5f1dd..5974b2e29e6f 100644 --- a/src/kernel/mempool_interface.h +++ b/src/kernel/mempool_interface.h @@ -6,6 +6,7 @@ #define BITCOIN_KERNEL_MEMPOOL_INTERFACE_H #include +#include class CBlock; class CTransaction; @@ -24,6 +25,7 @@ class Mempool virtual void removeRecursive(const CTransaction& tx) {} virtual void removeForBlock(const CBlock& block, unsigned int block_height) {} virtual size_t measureExternalDynamicMemoryUsage() { return 0; } + virtual void addTransactionsUpdated(uint32_t n) {} }; } // namespace kernel diff --git a/src/node/kernel_mempool.cpp b/src/node/kernel_mempool.cpp index e94122f4bcb8..c923b8c6cabb 100644 --- a/src/node/kernel_mempool.cpp +++ b/src/node/kernel_mempool.cpp @@ -7,6 +7,9 @@ #include #include +#include +#include + namespace node { void KernelMempool::removeRecursive(const CTransaction& tx) @@ -26,4 +29,9 @@ size_t KernelMempool::measureExternalDynamicMemoryUsage() return m_mempool.DynamicMemoryUsage(); } +void KernelMempool::addTransactionsUpdated(uint32_t n) +{ + m_mempool.AddTransactionsUpdated(n); +} + } // namespace node diff --git a/src/node/kernel_mempool.h b/src/node/kernel_mempool.h index 589226a5b2e0..644405e4bec0 100644 --- a/src/node/kernel_mempool.h +++ b/src/node/kernel_mempool.h @@ -7,6 +7,9 @@ #include +#include +#include + class CBlock; class CTransaction; class CTxMemPool; @@ -22,6 +25,7 @@ class KernelMempool: public kernel::Mempool void removeRecursive(const CTransaction& tx) override; void removeForBlock(const CBlock& block, unsigned int nBlockHeight) override; size_t measureExternalDynamicMemoryUsage() override; + void addTransactionsUpdated(uint32_t n) override; private: CTxMemPool& m_mempool; diff --git a/src/validation.cpp b/src/validation.cpp index 6f678813c844..a168c4490f9e 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -2895,7 +2895,7 @@ void Chainstate::UpdateTip(const CBlockIndex* pindexNew) // New best block if (m_mempool) { - m_mempool->AddTransactionsUpdated(1); + m_chainman.GetMempool().addTransactionsUpdated(1); } std::vector warning_messages; From 6eafd2887ea6e5709d578dad59e274265471eb45 Mon Sep 17 00:00:00 2001 From: TheCharlatan Date: Sat, 1 Nov 2025 23:06:59 +0100 Subject: [PATCH 05/11] kernel: Add check to mempool interface --- src/kernel/mempool_interface.h | 2 ++ src/node/kernel_mempool.cpp | 8 ++++++++ src/node/kernel_mempool.h | 2 ++ src/validation.cpp | 2 +- 4 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/kernel/mempool_interface.h b/src/kernel/mempool_interface.h index 5974b2e29e6f..9d6c8ec2f511 100644 --- a/src/kernel/mempool_interface.h +++ b/src/kernel/mempool_interface.h @@ -9,6 +9,7 @@ #include class CBlock; +class CCoinsViewCache; class CTransaction; namespace kernel { @@ -26,6 +27,7 @@ class Mempool virtual void removeForBlock(const CBlock& block, unsigned int block_height) {} virtual size_t measureExternalDynamicMemoryUsage() { return 0; } virtual void addTransactionsUpdated(uint32_t n) {} + virtual void check(const CCoinsViewCache& active_coins_tip, int64_t spendheight) {} }; } // namespace kernel diff --git a/src/node/kernel_mempool.cpp b/src/node/kernel_mempool.cpp index c923b8c6cabb..fbc23111ea1d 100644 --- a/src/node/kernel_mempool.cpp +++ b/src/node/kernel_mempool.cpp @@ -10,6 +10,8 @@ #include #include +class CCoinsViewCache; + namespace node { void KernelMempool::removeRecursive(const CTransaction& tx) @@ -34,4 +36,10 @@ void KernelMempool::addTransactionsUpdated(uint32_t n) m_mempool.AddTransactionsUpdated(n); } +void KernelMempool::check(const CCoinsViewCache& active_coins_tip, int64_t spendheight) +{ + LOCK(::cs_main); + m_mempool.check(active_coins_tip, spendheight); +} + } // namespace node diff --git a/src/node/kernel_mempool.h b/src/node/kernel_mempool.h index 644405e4bec0..96a315f9702d 100644 --- a/src/node/kernel_mempool.h +++ b/src/node/kernel_mempool.h @@ -11,6 +11,7 @@ #include class CBlock; +class CCoinsViewCache; class CTransaction; class CTxMemPool; @@ -26,6 +27,7 @@ class KernelMempool: public kernel::Mempool void removeForBlock(const CBlock& block, unsigned int nBlockHeight) override; size_t measureExternalDynamicMemoryUsage() override; void addTransactionsUpdated(uint32_t n) override; + void check(const CCoinsViewCache& active_coins_tip, int64_t spendheight) override; private: CTxMemPool& m_mempool; diff --git a/src/validation.cpp b/src/validation.cpp index a168c4490f9e..a1c8308bfd77 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -3257,7 +3257,7 @@ bool Chainstate::ActivateBestChainStep(BlockValidationState& state, CBlockIndex& // any disconnected transactions back to the mempool. MaybeUpdateMempoolForReorg(disconnectpool, true); } - if (m_mempool) m_mempool->check(this->CoinsTip(), this->m_chain.Height() + 1); + if (m_mempool) m_chainman.GetMempool().check(this->CoinsTip(), this->m_chain.Height() + 1); CheckForkWarningConditions(); From 872a30b0d7e297508fc712b2a5ce5b969c7102b3 Mon Sep 17 00:00:00 2001 From: sedited Date: Tue, 26 May 2026 22:50:40 +0200 Subject: [PATCH 06/11] Move mempool accept to txmempool And add MaybeUpdateMempoolForReorg to the mempool interface. --- src/kernel/mempool_interface.h | 3 + src/node/kernel_mempool.cpp | 8 + src/node/kernel_mempool.h | 5 + src/policy/mempool_accept_result.h | 165 +++ src/test/txvalidationcache_tests.cpp | 6 - src/txmempool.cpp | 1585 +++++++++++++++++++++++++ src/txmempool.h | 57 + src/validation.cpp | 1592 +------------------------- src/validation.h | 209 +--- 9 files changed, 1834 insertions(+), 1796 deletions(-) create mode 100644 src/policy/mempool_accept_result.h diff --git a/src/kernel/mempool_interface.h b/src/kernel/mempool_interface.h index 9d6c8ec2f511..de376778f683 100644 --- a/src/kernel/mempool_interface.h +++ b/src/kernel/mempool_interface.h @@ -9,8 +9,10 @@ #include class CBlock; +class Chainstate; class CCoinsViewCache; class CTransaction; +class DisconnectedBlockTransactions; namespace kernel { @@ -28,6 +30,7 @@ class Mempool virtual size_t measureExternalDynamicMemoryUsage() { return 0; } virtual void addTransactionsUpdated(uint32_t n) {} virtual void check(const CCoinsViewCache& active_coins_tip, int64_t spendheight) {} + virtual void MaybeUpdateMempoolForReorg(Chainstate& active_chainstate, DisconnectedBlockTransactions& disconnectpool, bool fAddToMempool) {} }; } // namespace kernel diff --git a/src/node/kernel_mempool.cpp b/src/node/kernel_mempool.cpp index fbc23111ea1d..5fe36cc6333c 100644 --- a/src/node/kernel_mempool.cpp +++ b/src/node/kernel_mempool.cpp @@ -42,4 +42,12 @@ void KernelMempool::check(const CCoinsViewCache& active_coins_tip, int64_t spend m_mempool.check(active_coins_tip, spendheight); } +void KernelMempool::MaybeUpdateMempoolForReorg(Chainstate& active_chainstate, DisconnectedBlockTransactions& disconnectpool, bool fAddToMempool) +{ + LOCK(::cs_main); + LOCK(m_mempool.cs); + m_mempool.MaybeUpdateMempoolForReorg(active_chainstate, disconnectpool, fAddToMempool); +} + + } // namespace node diff --git a/src/node/kernel_mempool.h b/src/node/kernel_mempool.h index 96a315f9702d..622c664d6f39 100644 --- a/src/node/kernel_mempool.h +++ b/src/node/kernel_mempool.h @@ -7,13 +7,17 @@ #include +#include + #include #include class CBlock; +class Chainstate; class CCoinsViewCache; class CTransaction; class CTxMemPool; +class DisconnectedBlockTransactions; namespace node { @@ -28,6 +32,7 @@ class KernelMempool: public kernel::Mempool size_t measureExternalDynamicMemoryUsage() override; void addTransactionsUpdated(uint32_t n) override; void check(const CCoinsViewCache& active_coins_tip, int64_t spendheight) override; + void MaybeUpdateMempoolForReorg(Chainstate& active_chainstate, DisconnectedBlockTransactions& disconnectpool, bool fAddToMempool) override; private: CTxMemPool& m_mempool; diff --git a/src/policy/mempool_accept_result.h b/src/policy/mempool_accept_result.h new file mode 100644 index 000000000000..1fae1d3cb944 --- /dev/null +++ b/src/policy/mempool_accept_result.h @@ -0,0 +1,165 @@ +// Copyright (c) The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_POLICY_MEMPOOL_ACCEPT_RESULT_H +#define BITCOIN_POLICY_MEMPOOL_ACCEPT_RESULT_H + +#include +#include +#include + +#include + +/** +* Validation result for a transaction evaluated by MemPoolAccept (single or package). +* Here are the expected fields and properties of a result depending on its ResultType, applicable to +* results returned from package evaluation: +*+---------------------------+----------------+-------------------+------------------+----------------+-------------------+ +*| Field or property | VALID | INVALID | MEMPOOL_ENTRY | DIFFERENT_WITNESS | +*| | |--------------------------------------| | | +*| | | TX_RECONSIDERABLE | Other | | | +*+---------------------------+----------------+-------------------+------------------+----------------+-------------------+ +*| txid in mempool? | yes | no | no* | yes | yes | +*| wtxid in mempool? | yes | no | no* | yes | no | +*| m_state | yes, IsValid() | yes, IsInvalid() | yes, IsInvalid() | yes, IsValid() | yes, IsValid() | +*| m_vsize | yes | no | no | yes | no | +*| m_base_fees | yes | no | no | yes | no | +*| m_effective_feerate | yes | yes | no | no | no | +*| m_wtxids_fee_calculations | yes | yes | no | no | no | +*| m_other_wtxid | no | no | no | no | yes | +*+---------------------------+----------------+-------------------+------------------+----------------+-------------------+ +* (*) Individual transaction acceptance doesn't return MEMPOOL_ENTRY and DIFFERENT_WITNESS. It returns +* INVALID, with the errors txn-already-in-mempool and txn-same-nonwitness-data-in-mempool +* respectively. In those cases, the txid or wtxid may be in the mempool for a TX_CONFLICT. +*/ +struct MempoolAcceptResult { + /** Used to indicate the results of mempool validation. */ + enum class ResultType { + VALID, //!> Fully validated, valid. + INVALID, //!> Invalid. + MEMPOOL_ENTRY, //!> Valid, transaction was already in the mempool. + DIFFERENT_WITNESS, //!> Not validated. A same-txid-different-witness tx (see m_other_wtxid) already exists in the mempool and was not replaced. + }; + /** Result type. Present in all MempoolAcceptResults. */ + const ResultType m_result_type; + + /** Contains information about why the transaction failed. */ + const TxValidationState m_state; + + /** Mempool transactions replaced by the tx. */ + const std::list m_replaced_transactions; + /** Virtual size as used by the mempool, calculated using serialized size and sigops. */ + const std::optional m_vsize; + /** Raw base fees in satoshis. */ + const std::optional m_base_fees; + /** The feerate at which this transaction was considered. This includes any fee delta added + * using prioritisetransaction (i.e. modified fees). If this transaction was submitted as a + * package, this is the package feerate, which may also include its descendants and/or + * ancestors (see m_wtxids_fee_calculations below). + */ + const std::optional m_effective_feerate; + /** Contains the wtxids of the transactions used for fee-related checks. Includes this + * transaction's wtxid and may include others if this transaction was validated as part of a + * package. This is not necessarily equivalent to the list of transactions passed to + * ProcessNewPackage(). + * Only present when m_result_type = ResultType::VALID. */ + const std::optional> m_wtxids_fee_calculations; + + /** The wtxid of the transaction in the mempool which has the same txid but different witness. */ + const std::optional m_other_wtxid; + + static MempoolAcceptResult Failure(TxValidationState state) { + return MempoolAcceptResult(state); + } + + static MempoolAcceptResult FeeFailure(TxValidationState state, + CFeeRate effective_feerate, + const std::vector& wtxids_fee_calculations) { + return MempoolAcceptResult(state, effective_feerate, wtxids_fee_calculations); + } + + static MempoolAcceptResult Success(std::list&& replaced_txns, + int64_t vsize, + CAmount fees, + CFeeRate effective_feerate, + const std::vector& wtxids_fee_calculations) { + return MempoolAcceptResult(std::move(replaced_txns), vsize, fees, + effective_feerate, wtxids_fee_calculations); + } + + static MempoolAcceptResult MempoolTx(int64_t vsize, CAmount fees) { + return MempoolAcceptResult(vsize, fees); + } + + static MempoolAcceptResult MempoolTxDifferentWitness(const Wtxid& other_wtxid) { + return MempoolAcceptResult(other_wtxid); + } + +// Private constructors. Use static methods MempoolAcceptResult::Success, etc. to construct. +private: + /** Constructor for failure case */ + explicit MempoolAcceptResult(TxValidationState state) + : m_result_type(ResultType::INVALID), m_state(state) { + Assume(!state.IsValid()); // Can be invalid or error + } + + /** Constructor for success case */ + explicit MempoolAcceptResult(std::list&& replaced_txns, + int64_t vsize, + CAmount fees, + CFeeRate effective_feerate, + const std::vector& wtxids_fee_calculations) + : m_result_type(ResultType::VALID), + m_replaced_transactions(std::move(replaced_txns)), + m_vsize{vsize}, + m_base_fees(fees), + m_effective_feerate(effective_feerate), + m_wtxids_fee_calculations(wtxids_fee_calculations) {} + + /** Constructor for fee-related failure case */ + explicit MempoolAcceptResult(TxValidationState state, + CFeeRate effective_feerate, + const std::vector& wtxids_fee_calculations) + : m_result_type(ResultType::INVALID), + m_state(state), + m_effective_feerate(effective_feerate), + m_wtxids_fee_calculations(wtxids_fee_calculations) {} + + /** Constructor for already-in-mempool case. It wouldn't replace any transactions. */ + explicit MempoolAcceptResult(int64_t vsize, CAmount fees) + : m_result_type(ResultType::MEMPOOL_ENTRY), m_vsize{vsize}, m_base_fees(fees) {} + + /** Constructor for witness-swapped case. */ + explicit MempoolAcceptResult(const Wtxid& other_wtxid) + : m_result_type(ResultType::DIFFERENT_WITNESS), m_other_wtxid(other_wtxid) {} +}; + +/** +* Validation result for package mempool acceptance. +*/ +struct PackageMempoolAcceptResult +{ + PackageValidationState m_state; + /** + * Map from wtxid to finished MempoolAcceptResults. The client is responsible + * for keeping track of the transaction objects themselves. If a result is not + * present, it means validation was unfinished for that transaction. If there + * was a package-wide error (see result in m_state), m_tx_results will be empty. + */ + std::map m_tx_results; + + explicit PackageMempoolAcceptResult(PackageValidationState state, + std::map&& results) + : m_state{state}, m_tx_results(std::move(results)) {} + + explicit PackageMempoolAcceptResult(PackageValidationState state, CFeeRate feerate, + std::map&& results) + : m_state{state}, m_tx_results(std::move(results)) {} + + /** Constructor to create a PackageMempoolAcceptResult from a single MempoolAcceptResult */ + explicit PackageMempoolAcceptResult(const Wtxid& wtxid, const MempoolAcceptResult& result) + : m_tx_results{ {wtxid, result} } {} +}; + +#endif // BITCOIN_POLICY_MEMPOOL_ACCEPT_RESULT_H diff --git a/src/test/txvalidationcache_tests.cpp b/src/test/txvalidationcache_tests.cpp index d695e08f5f50..56fe62af7fcc 100644 --- a/src/test/txvalidationcache_tests.cpp +++ b/src/test/txvalidationcache_tests.cpp @@ -20,12 +20,6 @@ struct Dersig100Setup : public TestChain100Setup { : TestChain100Setup{ChainType::REGTEST, {.extra_args = {"-testactivationheight=dersig@102"}}} {} }; -bool CheckInputScripts(const CTransaction& tx, TxValidationState& state, - const CCoinsViewCache& inputs, script_verify_flags flags, bool cacheSigStore, - bool cacheFullScriptStore, PrecomputedTransactionData& txdata, - ValidationCache& validation_cache, - std::vector* pvChecks) EXCLUSIVE_LOCKS_REQUIRED(cs_main); - BOOST_AUTO_TEST_SUITE(txvalidationcache_tests) BOOST_FIXTURE_TEST_CASE(tx_mempool_block_doublespend, Dersig100Setup) diff --git a/src/txmempool.cpp b/src/txmempool.cpp index a22cd2b199d7..d79b333d504d 100644 --- a/src/txmempool.cpp +++ b/src/txmempool.cpp @@ -9,10 +9,15 @@ #include #include #include +#include #include #include +#include +#include #include +#include #include +#include #include #include #include @@ -24,6 +29,7 @@ #include #include #include +#include #include #include @@ -36,6 +42,11 @@ TRACEPOINT_SEMAPHORE(mempool, added); TRACEPOINT_SEMAPHORE(mempool, removed); +TRACEPOINT_SEMAPHORE(mempool, replaced); +TRACEPOINT_SEMAPHORE(mempool, rejected); + +/** Maximum age of our tip for us to be considered current for fee estimation */ +static constexpr std::chrono::hours MAX_FEE_ESTIMATION_TIP_AGE{3}; bool TestLockPointValidity(CChain& active_chain, const LockPoints& lp) { @@ -1100,3 +1111,1577 @@ std::vector CTxMemPool::GetFeerateDiagram() const StopBlockBuilding(); return ret; } + +static void LimitMempoolSize(CTxMemPool& pool, CCoinsViewCache& coins_cache) + EXCLUSIVE_LOCKS_REQUIRED(::cs_main, pool.cs) +{ + AssertLockHeld(::cs_main); + AssertLockHeld(pool.cs); + int expired = pool.Expire(GetTime() - pool.m_opts.expiry); + if (expired != 0) { + LogDebug(BCLog::MEMPOOL, "Expired %i transactions from the memory pool\n", expired); + } + + std::vector vNoSpendsRemaining; + pool.TrimToSize(pool.m_opts.max_size_bytes, &vNoSpendsRemaining); + for (const COutPoint& removed : vNoSpendsRemaining) + coins_cache.Uncache(removed); +} + +static bool IsCurrentForFeeEstimation(Chainstate& active_chainstate) EXCLUSIVE_LOCKS_REQUIRED(cs_main) +{ + AssertLockHeld(cs_main); + if (active_chainstate.m_chainman.IsInitialBlockDownload()) { + return false; + } + if (active_chainstate.m_chain.Tip()->GetBlockTime() < count_seconds(GetTime() - MAX_FEE_ESTIMATION_TIP_AGE)) + return false; + if (active_chainstate.m_chain.Height() < active_chainstate.m_chainman.m_best_header->nHeight - 1) { + return false; + } + return true; +} + +void CTxMemPool::MaybeUpdateMempoolForReorg( + Chainstate& active_chainstate, + DisconnectedBlockTransactions& disconnectpool, + bool fAddToMempool) +{ + AssertLockHeld(cs_main); + AssertLockHeld(cs); + std::vector vHashUpdate; + { + // disconnectpool is ordered so that the front is the most recently-confirmed + // transaction (the last tx of the block at the tip) in the disconnected chain. + // Iterate disconnectpool in reverse, so that we add transactions + // back to the mempool starting with the earliest transaction that had + // been previously seen in a block. + const auto queuedTx = disconnectpool.take(); + auto it = queuedTx.rbegin(); + while (it != queuedTx.rend()) { + // ignore validation errors in resurrected transactions + if (!fAddToMempool || (*it)->IsCoinBase() || + AcceptToMemoryPool(active_chainstate, *it, GetTime(), + /*bypass_limits=*/true, /*test_accept=*/false).m_result_type != + MempoolAcceptResult::ResultType::VALID) { + // If the transaction doesn't make it in to the mempool, remove any + // transactions that depend on it (which would now be orphans). + removeRecursive(**it, MemPoolRemovalReason::REORG); + } else if (exists((*it)->GetHash())) { + vHashUpdate.push_back((*it)->GetHash()); + } + ++it; + } + } + + // AcceptToMemoryPool/addNewTransaction all assume that new mempool entries have + // no in-mempool children, which is generally not true when adding + // previously-confirmed transactions back to the mempool. + // UpdateTransactionsFromBlock finds descendants of any transactions in + // the disconnectpool that were added back and cleans up the mempool state. + UpdateTransactionsFromBlock(vHashUpdate); + + // Predicate to use for filtering transactions in removeForReorg. + // Checks whether the transaction is still final and, if it spends a coinbase output, mature. + // Also updates valid entries' cached LockPoints if needed. + // If false, the tx is still valid and its lockpoints are updated. + // If true, the tx would be invalid in the next block; remove this entry and all of its descendants. + // Note that TRUC rules are not applied here, so reorgs may cause violations of TRUC inheritance or + // topology restrictions. + const auto filter_final_and_mature = [&](CTxMemPool::txiter it) + EXCLUSIVE_LOCKS_REQUIRED(cs, ::cs_main) { + AssertLockHeld(cs); + AssertLockHeld(::cs_main); + const CTransaction& tx = it->GetTx(); + + // The transaction must be final. + if (!CheckFinalTxAtTip(*Assert(active_chainstate.m_chain.Tip()), tx)) return true; + + const LockPoints& lp = it->GetLockPoints(); + // CheckSequenceLocksAtTip checks if the transaction will be final in the next block to be + // created on top of the new chain. + if (TestLockPointValidity(active_chainstate.m_chain, lp)) { + if (!CheckSequenceLocksAtTip(active_chainstate.m_chain.Tip(), lp)) { + return true; + } + } else { + const CCoinsViewMemPool view_mempool{&active_chainstate.CoinsTip(), *this}; + const std::optional new_lock_points{CalculateLockPointsAtTip(active_chainstate.m_chain.Tip(), view_mempool, tx)}; + if (new_lock_points.has_value() && CheckSequenceLocksAtTip(active_chainstate.m_chain.Tip(), *new_lock_points)) { + // Now update the mempool entry lockpoints as well. + it->UpdateLockPoints(*new_lock_points); + } else { + return true; + } + } + + // If the transaction spends any coinbase outputs, it must be mature. + if (it->GetSpendsCoinbase()) { + for (const CTxIn& txin : tx.vin) { + if (exists(txin.prevout.hash)) continue; + const Coin& coin{active_chainstate.CoinsTip().AccessCoin(txin.prevout)}; + assert(!coin.IsSpent()); + const auto mempool_spend_height{active_chainstate.m_chain.Tip()->nHeight + 1}; + if (coin.IsCoinBase() && mempool_spend_height - coin.nHeight < COINBASE_MATURITY) { + return true; + } + } + } + // Transaction is still valid and cached LockPoints are updated. + return false; + }; + + // We also need to remove any now-immature transactions + removeForReorg(active_chainstate.m_chain, filter_final_and_mature); + // Re-limit mempool size, in case we added any transactions + LimitMempoolSize(*this, active_chainstate.CoinsTip()); +} + +/** +* Checks to avoid mempool polluting consensus critical paths since cached +* signature and script validity results will be reused if we validate this +* transaction again during block validation. +* */ +static bool CheckInputsFromMempoolAndCache(const CTransaction& tx, TxValidationState& state, + const CCoinsViewCache& view, const CTxMemPool& pool, + script_verify_flags flags, PrecomputedTransactionData& txdata, CCoinsViewCache& coins_tip, + ValidationCache& validation_cache) + EXCLUSIVE_LOCKS_REQUIRED(cs_main, pool.cs) +{ + AssertLockHeld(cs_main); + AssertLockHeld(pool.cs); + + assert(!tx.IsCoinBase()); + for (const CTxIn& txin : tx.vin) { + const Coin& coin = view.AccessCoin(txin.prevout); + + // This coin was checked in PreChecks and MemPoolAccept + // has been holding cs_main since then. + Assume(!coin.IsSpent()); + if (coin.IsSpent()) return false; + + // If the Coin is available, there are 2 possibilities: + // it is available in our current ChainstateActive UTXO set, + // or it's a UTXO provided by a transaction in our mempool. + // Ensure the scriptPubKeys in Coins from CoinsView are correct. + const CTransactionRef& txFrom = pool.get(txin.prevout.hash); + if (txFrom) { + assert(txFrom->GetHash() == txin.prevout.hash); + assert(txFrom->vout.size() > txin.prevout.n); + assert(txFrom->vout[txin.prevout.n] == coin.out); + } else { + const Coin& coinFromUTXOSet = coins_tip.AccessCoin(txin.prevout); + assert(!coinFromUTXOSet.IsSpent()); + assert(coinFromUTXOSet.out == coin.out); + } + } + + // Call CheckInputScripts() to cache signature and script validity against current tip consensus rules. + return CheckInputScripts(tx, state, view, flags, /* cacheSigStore= */ true, /* cacheFullScriptStore= */ true, txdata, validation_cache); +} + +namespace { + +class MemPoolAccept +{ +public: + explicit MemPoolAccept(CTxMemPool& mempool, Chainstate& active_chainstate) : + m_pool(mempool), + m_view(&CoinsViewEmpty::Get()), + m_viewmempool(&active_chainstate.CoinsTip(), m_pool), + m_active_chainstate(active_chainstate) + { + } + + // We put the arguments we're handed into a struct, so we can pass them + // around easier. + struct ATMPArgs { + const CChainParams& m_chainparams; + const int64_t m_accept_time; + const bool m_bypass_limits; + /* + * Return any outpoints which were not previously present in the coins + * cache, but were added as a result of validating the tx for mempool + * acceptance. This allows the caller to optionally remove the cache + * additions if the associated transaction ends up being rejected by + * the mempool. + */ + std::vector& m_coins_to_uncache; + /** When true, the transaction or package will not be submitted to the mempool. */ + const bool m_test_accept; + /** Whether we allow transactions to replace mempool transactions. If false, + * any transaction spending the same inputs as a transaction in the mempool is considered + * a conflict. */ + const bool m_allow_replacement; + /** When true, allow sibling eviction. This only occurs in single transaction package settings. */ + const bool m_allow_sibling_eviction; + /** Used to skip the LimitMempoolSize() call within AcceptSingleTransaction(). This should be used when multiple + * AcceptSubPackage calls are expected and the mempool will be trimmed at the end of AcceptPackage(). */ + const bool m_package_submission; + /** When true, use package feerates instead of individual transaction feerates for fee-based + * policies such as mempool min fee and min relay fee. + */ + const bool m_package_feerates; + /** Used for local submission of transactions to catch "absurd" fees + * due to fee miscalculation by wallets. std:nullopt implies unset, allowing any feerates. + * Any individual transaction failing this check causes immediate failure. + */ + const std::optional m_client_maxfeerate; + + /** Parameters for single transaction mempool validation. */ + static ATMPArgs SingleAccept(const CChainParams& chainparams, int64_t accept_time, + bool bypass_limits, std::vector& coins_to_uncache, + bool test_accept) { + return ATMPArgs{/*chainparams=*/ chainparams, + /*accept_time=*/ accept_time, + /*bypass_limits=*/ bypass_limits, + /*coins_to_uncache=*/ coins_to_uncache, + /*test_accept=*/ test_accept, + /*allow_replacement=*/ true, + /*allow_sibling_eviction=*/ true, + /*package_submission=*/ false, + /*package_feerates=*/ false, + /*client_maxfeerate=*/ {}, // checked by caller + }; + } + + /** Parameters for test package mempool validation through testmempoolaccept. */ + static ATMPArgs PackageTestAccept(const CChainParams& chainparams, int64_t accept_time, + std::vector& coins_to_uncache) { + return ATMPArgs{/*chainparams=*/ chainparams, + /*accept_time=*/ accept_time, + /*bypass_limits=*/ false, + /*coins_to_uncache=*/ coins_to_uncache, + /*test_accept=*/ true, + /*allow_replacement=*/ false, + /*allow_sibling_eviction=*/ false, + /*package_submission=*/ false, // not submitting to mempool + /*package_feerates=*/ false, + /*client_maxfeerate=*/ {}, // checked by caller + }; + } + + /** Parameters for child-with-parents package validation. */ + static ATMPArgs PackageChildWithParents(const CChainParams& chainparams, int64_t accept_time, + std::vector& coins_to_uncache, const std::optional& client_maxfeerate) { + return ATMPArgs{/*chainparams=*/ chainparams, + /*accept_time=*/ accept_time, + /*bypass_limits=*/ false, + /*coins_to_uncache=*/ coins_to_uncache, + /*test_accept=*/ false, + /*allow_replacement=*/ true, + /*allow_sibling_eviction=*/ false, + /*package_submission=*/ true, + /*package_feerates=*/ true, + /*client_maxfeerate=*/ client_maxfeerate, + }; + } + + /** Parameters for a single transaction within a package. */ + static ATMPArgs SingleInPackageAccept(const ATMPArgs& package_args) { + return ATMPArgs{/*chainparams=*/ package_args.m_chainparams, + /*accept_time=*/ package_args.m_accept_time, + /*bypass_limits=*/ false, + /*coins_to_uncache=*/ package_args.m_coins_to_uncache, + /*test_accept=*/ package_args.m_test_accept, + /*allow_replacement=*/ true, + /*allow_sibling_eviction=*/ true, + /*package_submission=*/ true, // trim at the end of AcceptPackage() + /*package_feerates=*/ false, // only 1 transaction + /*client_maxfeerate=*/ package_args.m_client_maxfeerate, + }; + } + + private: + // Private ctor to avoid exposing details to clients and allowing the possibility of + // mixing up the order of the arguments. Use static functions above instead. + ATMPArgs(const CChainParams& chainparams, + int64_t accept_time, + bool bypass_limits, + std::vector& coins_to_uncache, + bool test_accept, + bool allow_replacement, + bool allow_sibling_eviction, + bool package_submission, + bool package_feerates, + std::optional client_maxfeerate) + : m_chainparams{chainparams}, + m_accept_time{accept_time}, + m_bypass_limits{bypass_limits}, + m_coins_to_uncache{coins_to_uncache}, + m_test_accept{test_accept}, + m_allow_replacement{allow_replacement}, + m_allow_sibling_eviction{allow_sibling_eviction}, + m_package_submission{package_submission}, + m_package_feerates{package_feerates}, + m_client_maxfeerate{client_maxfeerate} + { + // If we are using package feerates, we must be doing package submission. + // It also means sibling eviction is not permitted. + if (m_package_feerates) { + Assume(m_package_submission); + Assume(!m_allow_sibling_eviction); + } + if (m_allow_sibling_eviction) Assume(m_allow_replacement); + } + }; + + /** Clean up all non-chainstate coins from m_view and m_viewmempool. */ + void CleanupTemporaryCoins() EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_pool.cs); + + // Single transaction acceptance + MempoolAcceptResult AcceptSingleTransactionAndCleanup(const CTransactionRef& ptx, ATMPArgs& args) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { + LOCK(m_pool.cs); + MempoolAcceptResult result = AcceptSingleTransactionInternal(ptx, args); + ClearSubPackageState(); + return result; + } + MempoolAcceptResult AcceptSingleTransactionInternal(const CTransactionRef& ptx, ATMPArgs& args) EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_pool.cs); + + /** + * Multiple transaction acceptance. Transactions may or may not be interdependent, but must not + * conflict with each other, and the transactions cannot already be in the mempool. Parents must + * come before children if any dependencies exist. + */ + PackageMempoolAcceptResult AcceptMultipleTransactionsAndCleanup(const std::vector& txns, ATMPArgs& args) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { + LOCK(m_pool.cs); + PackageMempoolAcceptResult result = AcceptMultipleTransactionsInternal(txns, args); + ClearSubPackageState(); + return result; + } + PackageMempoolAcceptResult AcceptMultipleTransactionsInternal(const std::vector& txns, ATMPArgs& args) EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_pool.cs); + + /** + * Submission of a subpackage. + * If subpackage size == 1, calls AcceptSingleTransaction() with adjusted ATMPArgs to + * enable sibling eviction and creates a PackageMempoolAcceptResult + * wrapping the result. + * + * If subpackage size > 1, calls AcceptMultipleTransactions() with the provided ATMPArgs. + * + * Also cleans up all non-chainstate coins from m_view at the end. + */ + PackageMempoolAcceptResult AcceptSubPackage(const std::vector& subpackage, ATMPArgs& args) + EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_pool.cs); + + /** + * Package (more specific than just multiple transactions) acceptance. Package must be a child + * with all of its unconfirmed parents, and topologically sorted. + */ + PackageMempoolAcceptResult AcceptPackage(const Package& package, ATMPArgs& args) EXCLUSIVE_LOCKS_REQUIRED(cs_main); + +private: + // All the intermediate state that gets passed between the various levels + // of checking a given transaction. + struct Workspace { + explicit Workspace(const CTransactionRef& ptx) : m_ptx(ptx), m_hash(ptx->GetHash()) {} + /** Txids of mempool transactions that this transaction directly conflicts with or may + * replace via sibling eviction. */ + std::set m_conflicts; + /** Iterators to mempool entries that this transaction directly conflicts with or may + * replace via sibling eviction. */ + CTxMemPool::setEntries m_iters_conflicting; + /** All mempool parents of this transaction. */ + std::vector m_parents; + /* Handle to the tx in the changeset */ + CTxMemPool::ChangeSet::TxHandle m_tx_handle; + /** Whether RBF-related data structures (m_conflicts, m_iters_conflicting, + * m_replaced_transactions) include a sibling in addition to txns with conflicting inputs. */ + bool m_sibling_eviction{false}; + + /** Virtual size of the transaction as used by the mempool, calculated using serialized size + * of the transaction and sigops. */ + int64_t m_vsize; + /** Fees paid by this transaction: total input amounts subtracted by total output amounts. */ + CAmount m_base_fees; + /** Base fees + any fee delta set by the user with prioritisetransaction. */ + CAmount m_modified_fees; + + /** If we're doing package validation (i.e. m_package_feerates=true), the "effective" + * package feerate of this transaction is the total fees divided by the total size of + * transactions (which may include its ancestors and/or descendants). */ + CFeeRate m_package_feerate{0}; + + const CTransactionRef& m_ptx; + /** Txid. */ + const Txid& m_hash; + TxValidationState m_state; + /** A temporary cache containing serialized transaction data for signature verification. + * Reused across PolicyScriptChecks and ConsensusScriptChecks. */ + PrecomputedTransactionData m_precomputed_txdata; + }; + + // Run the policy checks on a given transaction, excluding any script checks. + // Looks up inputs, calculates feerate, considers replacement, evaluates + // package limits, etc. As this function can be invoked for "free" by a peer, + // only tests that are fast should be done here (to avoid CPU DoS). + bool PreChecks(ATMPArgs& args, Workspace& ws) EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_pool.cs); + + // Run checks for mempool replace-by-fee, only used in AcceptSingleTransaction. + bool ReplacementChecks(Workspace& ws) EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_pool.cs); + + bool PackageRBFChecks(const std::vector& txns, + std::vector& workspaces, + int64_t total_vsize, + PackageValidationState& package_state) EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_pool.cs); + + // Run the script checks using our policy flags. As this can be slow, we should + // only invoke this on transactions that have otherwise passed policy checks. + bool PolicyScriptChecks(const ATMPArgs& args, Workspace& ws) EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_pool.cs); + + // Re-run the script checks, using consensus flags, and try to cache the + // result in the scriptcache. This should be done after + // PolicyScriptChecks(). This requires that all inputs either be in our + // utxo set or in the mempool. + bool ConsensusScriptChecks(const ATMPArgs& args, Workspace& ws) EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_pool.cs); + + // Try to add the transaction to the mempool, removing any conflicts first. + void FinalizeSubpackage(const ATMPArgs& args) EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_pool.cs); + + // Submit all transactions to the mempool and call ConsensusScriptChecks to add to the script + // cache - should only be called after successful validation of all transactions in the package. + // Does not call LimitMempoolSize(), so mempool max_size_bytes may be temporarily exceeded. + bool SubmitPackage(const ATMPArgs& args, std::vector& workspaces, PackageValidationState& package_state, + std::map& results) + EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_pool.cs); + + // Compare a package's feerate against minimum allowed. + bool CheckFeeRate(size_t package_size, CAmount package_fee, TxValidationState& state) EXCLUSIVE_LOCKS_REQUIRED(::cs_main, m_pool.cs) + { + AssertLockHeld(::cs_main); + AssertLockHeld(m_pool.cs); + CAmount mempoolRejectFee = m_pool.GetMinFee().GetFee(package_size); + if (mempoolRejectFee > 0 && package_fee < mempoolRejectFee) { + return state.Invalid(TxValidationResult::TX_RECONSIDERABLE, "mempool min fee not met", strprintf("%d < %d", package_fee, mempoolRejectFee)); + } + + if (package_fee < m_pool.m_opts.min_relay_feerate.GetFee(package_size)) { + return state.Invalid(TxValidationResult::TX_RECONSIDERABLE, "min relay fee not met", + strprintf("%d < %d", package_fee, m_pool.m_opts.min_relay_feerate.GetFee(package_size))); + } + return true; + } + + ValidationCache& GetValidationCache() + { + return m_active_chainstate.m_chainman.m_validation_cache; + } + +private: + CTxMemPool& m_pool; + + /** Holds a cached view of available coins from the UTXO set, mempool, and artificial temporary coins (to enable package validation). + * The view doesn't track whether a coin previously existed but has now been spent. We detect conflicts in other ways: + * - conflicts within a transaction are checked in CheckTransaction (bad-txns-inputs-duplicate) + * - conflicts within a package are checked in IsWellFormedPackage (conflict-in-package) + * - conflicts with an existing mempool transaction are found in CTxMemPool::GetConflictTx and replacements are allowed + * The temporary coins should persist between individual transaction checks so that package validation is possible, + * but must be cleaned up when we finish validating a subpackage, whether accepted or rejected. The cache must also + * be cleared when mempool contents change (when a changeset is applied or when the mempool trims itself) because it + * can return cached coins that no longer exist in the backend. Use CleanupTemporaryCoins() anytime you are finished + * with a SubPackageState or call LimitMempoolSize(). + */ + CCoinsViewCache m_view; + + // These are the two possible backends for m_view. + /** When m_view is connected to m_viewmempool as its backend, it can pull coins from the mempool and from the UTXO + * set. This is also where temporary coins are stored. */ + CCoinsViewMemPool m_viewmempool; + + Chainstate& m_active_chainstate; + + // Fields below are per *sub*package state and must be reset prior to subsequent + // AcceptSingleTransaction and AcceptMultipleTransactions invocations + struct SubPackageState { + /** Aggregated modified fees of all transactions, used to calculate package feerate. */ + CAmount m_total_modified_fees{0}; + /** Aggregated virtual size of all transactions, used to calculate package feerate. */ + int64_t m_total_vsize{0}; + + // RBF-related members + /** Whether the transaction(s) would replace any mempool transactions and/or evict any siblings. + * If so, RBF rules apply. */ + bool m_rbf{false}; + /** Mempool transactions that were replaced. */ + std::list m_replaced_transactions; + /* Changeset representing adding transactions and removing their conflicts. */ + std::unique_ptr m_changeset; + + /** Total modified fees of mempool transactions being replaced. */ + CAmount m_conflicting_fees{0}; + /** Total size (in virtual bytes) of mempool transactions being replaced. */ + size_t m_conflicting_size{0}; + }; + + struct SubPackageState m_subpackage; + + /** Re-set sub-package state to not leak between evaluations */ + void ClearSubPackageState() EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_pool.cs) + { + m_subpackage = SubPackageState{}; + + // And clean coins while at it + CleanupTemporaryCoins(); + } +}; + +bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws) +{ + AssertLockHeld(cs_main); + AssertLockHeld(m_pool.cs); + const CTransactionRef& ptx = ws.m_ptx; + const CTransaction& tx = *ws.m_ptx; + const Txid& hash = ws.m_hash; + + // Copy/alias what we need out of args + const int64_t nAcceptTime = args.m_accept_time; + const bool bypass_limits = args.m_bypass_limits; + std::vector& coins_to_uncache = args.m_coins_to_uncache; + + // Alias what we need out of ws + TxValidationState& state = ws.m_state; + + if (!CheckTransaction(tx, state)) { + return false; // state filled in by CheckTransaction + } + + // Coinbase is only valid in a block, not as a loose transaction + if (tx.IsCoinBase()) + return state.Invalid(TxValidationResult::TX_CONSENSUS, "coinbase"); + + // Rather not work on nonstandard transactions (unless -testnet/-regtest) + std::string reason; + if (m_pool.m_opts.require_standard && !IsStandardTx(tx, m_pool.m_opts.max_datacarrier_bytes, m_pool.m_opts.permit_bare_multisig, m_pool.m_opts.dust_relay_feerate, reason)) { + return state.Invalid(TxValidationResult::TX_NOT_STANDARD, reason); + } + + // Transactions smaller than 65 non-witness bytes are not relayed to mitigate CVE-2017-12842. + if (::GetSerializeSize(TX_NO_WITNESS(tx)) < MIN_STANDARD_TX_NONWITNESS_SIZE) + return state.Invalid(TxValidationResult::TX_NOT_STANDARD, "tx-size-small"); + + // Only accept nLockTime-using transactions that can be mined in the next + // block; we don't want our mempool filled up with transactions that can't + // be mined yet. + if (!CheckFinalTxAtTip(*Assert(m_active_chainstate.m_chain.Tip()), tx)) { + return state.Invalid(TxValidationResult::TX_PREMATURE_SPEND, "non-final"); + } + + if (m_pool.exists(tx.GetWitnessHash())) { + // Exact transaction already exists in the mempool. + return state.Invalid(TxValidationResult::TX_CONFLICT, "txn-already-in-mempool"); + } else if (m_pool.exists(tx.GetHash())) { + // Transaction with the same non-witness data but different witness (same txid, different + // wtxid) already exists in the mempool. + return state.Invalid(TxValidationResult::TX_CONFLICT, "txn-same-nonwitness-data-in-mempool"); + } + + // Check for conflicts with in-memory transactions + for (const CTxIn &txin : tx.vin) + { + const CTransaction* ptxConflicting = m_pool.GetConflictTx(txin.prevout); + if (ptxConflicting) { + if (!args.m_allow_replacement) { + // Transaction conflicts with a mempool tx, but we're not allowing replacements in this context. + return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "bip125-replacement-disallowed"); + } + ws.m_conflicts.insert(ptxConflicting->GetHash()); + } + } + + m_view.SetBackend(m_viewmempool); + + const CCoinsViewCache& coins_cache = m_active_chainstate.CoinsTip(); + // do all inputs exist? + for (const CTxIn& txin : tx.vin) { + if (!coins_cache.HaveCoinInCache(txin.prevout)) { + coins_to_uncache.push_back(txin.prevout); + } + + // Note: this call may add txin.prevout to the coins cache + // (coins_cache.cacheCoins) by way of FetchCoin(). It should be removed + // later (via coins_to_uncache) if this tx turns out to be invalid. + if (!m_view.HaveCoin(txin.prevout)) { + // Are inputs missing because we already have the tx? + for (size_t out = 0; out < tx.vout.size(); out++) { + // Optimistically just do efficient check of cache for outputs + if (coins_cache.HaveCoinInCache(COutPoint(hash, out))) { + return state.Invalid(TxValidationResult::TX_CONFLICT, "txn-already-known"); + } + } + // Otherwise assume this might be an orphan tx for which we just haven't seen parents yet + return state.Invalid(TxValidationResult::TX_MISSING_INPUTS, "bad-txns-inputs-missingorspent"); + } + } + + // This is const, but calls into `CCoinsViewCache::GetBestBlock()` to refresh + // the cached best block through `m_viewmempool` after caching inputs. + (void)m_view.GetBestBlock(); + + // All required inputs are cached now, so switch m_view to the empty backend. + // This keeps already-fetched cache entries for later checks and prevents new + // backend lookups (which would avoid coins_to_uncache tracking). + m_view.SetBackend(CoinsViewEmpty::Get()); + + assert(m_active_chainstate.m_blockman.LookupBlockIndex(m_view.GetBestBlock()) == m_active_chainstate.m_chain.Tip()); + + // Only accept BIP68 sequence locked transactions that can be mined in the next + // block; we don't want our mempool filled up with transactions that can't + // be mined yet. + // Pass in m_view which has all of the relevant inputs cached. Note that, since m_view's + // backend was removed, it no longer pulls coins from the mempool. + const std::optional lock_points{CalculateLockPointsAtTip(m_active_chainstate.m_chain.Tip(), m_view, tx)}; + if (!lock_points.has_value() || !CheckSequenceLocksAtTip(m_active_chainstate.m_chain.Tip(), *lock_points)) { + return state.Invalid(TxValidationResult::TX_PREMATURE_SPEND, "non-BIP68-final"); + } + + // The mempool holds txs for the next block, so pass height+1 to CheckTxInputs + if (!Consensus::CheckTxInputs(tx, state, m_view, m_active_chainstate.m_chain.Height() + 1, ws.m_base_fees)) { + return false; // state filled in by CheckTxInputs + } + + if (m_pool.m_opts.require_standard) { + state = ValidateInputsStandardness(tx, m_view); + if (state.IsInvalid()) { + return false; + } + } + + // Check for non-standard witnesses. + if (tx.HasWitness() && m_pool.m_opts.require_standard && !IsWitnessStandard(tx, m_view)) { + return state.Invalid(TxValidationResult::TX_WITNESS_MUTATED, "bad-witness-nonstandard"); + } + + int64_t nSigOpsCost = GetTransactionSigOpCost(tx, m_view, STANDARD_SCRIPT_VERIFY_FLAGS); + + // Keep track of transactions that spend a coinbase, which we re-scan + // during reorgs to ensure COINBASE_MATURITY is still met. + bool fSpendsCoinbase = false; + for (const CTxIn &txin : tx.vin) { + const Coin &coin = m_view.AccessCoin(txin.prevout); + if (coin.IsCoinBase()) { + fSpendsCoinbase = true; + break; + } + } + + // Set entry_sequence to 0 when bypass_limits 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 = bypass_limits ? 0 : m_pool.GetSequence(); + if (!m_subpackage.m_changeset) { + m_subpackage.m_changeset = m_pool.GetChangeSet(); + } + ws.m_tx_handle = m_subpackage.m_changeset->StageAddition(ptx, ws.m_base_fees, nAcceptTime, m_active_chainstate.m_chain.Height(), entry_sequence, fSpendsCoinbase, nSigOpsCost, lock_points.value()); + + // ws.m_modified_fees includes any fee deltas from PrioritiseTransaction + ws.m_modified_fees = ws.m_tx_handle->GetModifiedFee(); + + ws.m_vsize = ws.m_tx_handle->GetTxSize(); + + // Enforces 0-fee for dust transactions, no incentive to be mined alone + if (m_pool.m_opts.require_standard) { + if (!PreCheckEphemeralTx(*ptx, m_pool.m_opts.dust_relay_feerate, ws.m_base_fees, ws.m_modified_fees, state)) { + return false; // state filled in by PreCheckEphemeralTx + } + } + + if (nSigOpsCost > MAX_STANDARD_TX_SIGOPS_COST) + return state.Invalid(TxValidationResult::TX_NOT_STANDARD, "bad-txns-too-many-sigops", + strprintf("%d", nSigOpsCost)); + + // No individual transactions are allowed below the mempool min feerate except from disconnected + // blocks and transactions in a package. Package transactions will be checked using package + // feerate later. + if (!bypass_limits && !args.m_package_feerates && !CheckFeeRate(ws.m_vsize, ws.m_modified_fees, state)) return false; + + ws.m_iters_conflicting = m_pool.GetIterSet(ws.m_conflicts); + + ws.m_parents = m_pool.GetParents(*ws.m_tx_handle); + + if (!args.m_bypass_limits) { + // Perform the TRUC checks, using the in-mempool parents. + if (const auto err{SingleTRUCChecks(m_pool, ws.m_ptx, ws.m_parents, ws.m_conflicts, ws.m_vsize)}) { + // Single transaction contexts only. + if (args.m_allow_sibling_eviction && err->second != nullptr) { + // We should only be considering where replacement is considered valid as well. + Assume(args.m_allow_replacement); + // Potential sibling eviction. Add the sibling to our list of mempool conflicts to be + // included in RBF checks. + ws.m_conflicts.insert(err->second->GetHash()); + // Adding the sibling to m_iters_conflicting here means that it doesn't count towards + // RBF Carve Out above. This is correct, since removing to-be-replaced transactions from + // the descendant count is done separately in SingleTRUCChecks for TRUC transactions. + ws.m_iters_conflicting.insert(m_pool.GetIter(err->second->GetHash()).value()); + ws.m_sibling_eviction = true; + // The sibling will be treated as part of the to-be-replaced set in ReplacementChecks. + // Note that we are not checking whether it opts in to replaceability via BIP125 or TRUC + // (which is normally done in PreChecks). However, the only way a TRUC transaction can + // have a non-TRUC and non-BIP125 descendant is due to a reorg. + } else { + return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "TRUC-violation", err->first); + } + } + } + + // We want to detect conflicts in any tx in a package to trigger package RBF logic + m_subpackage.m_rbf |= !ws.m_conflicts.empty(); + return true; +} + +bool MemPoolAccept::ReplacementChecks(Workspace& ws) +{ + AssertLockHeld(cs_main); + AssertLockHeld(m_pool.cs); + + const CTransaction& tx = *ws.m_ptx; + const Txid& hash = ws.m_hash; + TxValidationState& state = ws.m_state; + + CFeeRate newFeeRate(ws.m_modified_fees, ws.m_vsize); + + CTxMemPool::setEntries all_conflicts; + + // Calculate all conflicting entries and enforce Rule #5. + if (const auto err_string{GetEntriesForConflicts(tx, m_pool, ws.m_iters_conflicting, all_conflicts)}) { + return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, + strprintf("too many potential replacements%s", ws.m_sibling_eviction ? " (including sibling eviction)" : ""), *err_string); + } + + // Check if it's economically rational to mine this transaction rather than the ones it + // replaces and pays for its own relay fees. Enforce Rules #3 and #4. + for (CTxMemPool::txiter it : all_conflicts) { + m_subpackage.m_conflicting_fees += it->GetModifiedFee(); + m_subpackage.m_conflicting_size += it->GetTxSize(); + } + + if (const auto err_string{PaysForRBF(m_subpackage.m_conflicting_fees, ws.m_modified_fees, ws.m_vsize, + m_pool.m_opts.incremental_relay_feerate, hash)}) { + // Result may change in a package context + return state.Invalid(TxValidationResult::TX_RECONSIDERABLE, + strprintf("insufficient fee%s", ws.m_sibling_eviction ? " (including sibling eviction)" : ""), *err_string); + } + + // Add all the to-be-removed transactions to the changeset. + for (auto it : all_conflicts) { + m_subpackage.m_changeset->StageRemoval(it); + } + + // Run cluster size limit checks and fail if we exceed them. + if (!m_subpackage.m_changeset->CheckMemPoolPolicyLimits()) { + return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "too-large-cluster", ""); + } + + if (const auto err_string{ImprovesFeerateDiagram(*m_subpackage.m_changeset)}) { + // We checked above for the cluster size limits being respected, so a + // failure here can only be due to an insufficient fee. + Assume(err_string->first == DiagramCheckError::FAILURE); + return state.Invalid(TxValidationResult::TX_RECONSIDERABLE, "replacement-failed", err_string->second); + } + + return true; +} + +bool MemPoolAccept::PackageRBFChecks(const std::vector& txns, + std::vector& workspaces, + const int64_t total_vsize, + PackageValidationState& package_state) +{ + AssertLockHeld(cs_main); + AssertLockHeld(m_pool.cs); + + assert(std::all_of(txns.cbegin(), txns.cend(), [this](const auto& tx) + { return !m_pool.exists(tx->GetHash());})); + + assert(txns.size() == workspaces.size()); + + // We're in package RBF context; replacement proposal must be size 2 + if (workspaces.size() != 2 || !Assume(IsChildWithParents(txns))) { + return package_state.Invalid(PackageValidationResult::PCKG_POLICY, "package RBF failed: package must be 1-parent-1-child"); + } + + // If the package has in-mempool parents, we won't consider a package RBF + // since it would result in a cluster larger than 2. + // N.B. To relax this constraint we will need to revisit how CCoinsViewMemPool::PackageAddTransaction + // is being used inside AcceptMultipleTransactions to track available inputs while processing a package. + // Specifically we would need to check that the ancestors of the new + // transactions don't intersect with the set of transactions to be removed + // due to RBF, which is not checked at all in the package acceptance + // context. + for (const auto& ws : workspaces) { + if (!ws.m_parents.empty()) { + return package_state.Invalid(PackageValidationResult::PCKG_POLICY, "package RBF failed: new transaction cannot have mempool ancestors"); + } + } + + // Aggregate all conflicts into one set. + CTxMemPool::setEntries direct_conflict_iters; + for (Workspace& ws : workspaces) { + // Aggregate all conflicts into one set. + direct_conflict_iters.merge(ws.m_iters_conflicting); + } + + const auto& parent_ws = workspaces[0]; + const auto& child_ws = workspaces[1]; + + // Don't consider replacements that would cause us to remove a large number of mempool entries. + // This limit is not increased in a package RBF. Use the aggregate number of transactions. + CTxMemPool::setEntries all_conflicts; + if (const auto err_string{GetEntriesForConflicts(*child_ws.m_ptx, m_pool, direct_conflict_iters, + all_conflicts)}) { + return package_state.Invalid(PackageValidationResult::PCKG_POLICY, + "package RBF failed: too many potential replacements", *err_string); + } + + for (CTxMemPool::txiter it : all_conflicts) { + m_subpackage.m_changeset->StageRemoval(it); + m_subpackage.m_conflicting_fees += it->GetModifiedFee(); + m_subpackage.m_conflicting_size += it->GetTxSize(); + } + + // Use the child as the transaction for attributing errors to. + const Txid& child_hash = child_ws.m_ptx->GetHash(); + if (const auto err_string{PaysForRBF(/*original_fees=*/m_subpackage.m_conflicting_fees, + /*replacement_fees=*/m_subpackage.m_total_modified_fees, + /*replacement_vsize=*/m_subpackage.m_total_vsize, + m_pool.m_opts.incremental_relay_feerate, child_hash)}) { + return package_state.Invalid(PackageValidationResult::PCKG_POLICY, + "package RBF failed: insufficient anti-DoS fees", *err_string); + } + + // Ensure this two transaction package is a "chunk" on its own; we don't want the child + // to be only paying anti-DoS fees + const CFeeRate parent_feerate(parent_ws.m_modified_fees, parent_ws.m_vsize); + const CFeeRate package_feerate(m_subpackage.m_total_modified_fees, m_subpackage.m_total_vsize); + if (package_feerate <= parent_feerate) { + return package_state.Invalid(PackageValidationResult::PCKG_POLICY, + "package RBF failed: package feerate is less than or equal to parent feerate", + strprintf("package feerate %s <= parent feerate is %s", package_feerate.ToString(), parent_feerate.ToString())); + } + + // Run cluster size limit checks and fail if we exceed them. + if (!m_subpackage.m_changeset->CheckMemPoolPolicyLimits()) { + return package_state.Invalid(PackageValidationResult::PCKG_POLICY, "too-large-cluster", ""); + } + + // Check if it's economically rational to mine this package rather than the ones it replaces. + if (const auto err_tup{ImprovesFeerateDiagram(*m_subpackage.m_changeset)}) { + Assume(err_tup->first == DiagramCheckError::FAILURE); + return package_state.Invalid(PackageValidationResult::PCKG_POLICY, + "package RBF failed: " + err_tup.value().second, ""); + } + + LogDebug(BCLog::TXPACKAGES, "package RBF checks passed: parent %s (wtxid=%s), child %s (wtxid=%s), package hash (%s)\n", + txns.front()->GetHash().ToString(), txns.front()->GetWitnessHash().ToString(), + txns.back()->GetHash().ToString(), txns.back()->GetWitnessHash().ToString(), + GetPackageHash(txns).ToString()); + + + return true; +} + +bool MemPoolAccept::PolicyScriptChecks(const ATMPArgs& args, Workspace& ws) +{ + AssertLockHeld(cs_main); + AssertLockHeld(m_pool.cs); + const CTransaction& tx = *ws.m_ptx; + TxValidationState& state = ws.m_state; + + constexpr script_verify_flags scriptVerifyFlags = STANDARD_SCRIPT_VERIFY_FLAGS; + + // Check input scripts and signatures. + // This is done last to help prevent CPU exhaustion denial-of-service attacks. + if (!CheckInputScripts(tx, state, m_view, scriptVerifyFlags, true, false, ws.m_precomputed_txdata, GetValidationCache())) { + // Detect a failure due to a missing witness so that p2p code can handle rejection caching appropriately. + if (!tx.HasWitness() && SpendsNonAnchorWitnessProg(tx, m_view)) { + state.Invalid(TxValidationResult::TX_WITNESS_STRIPPED, + state.GetRejectReason(), state.GetDebugMessage()); + } + return false; // state filled in by CheckInputScripts + } + + return true; +} + +bool MemPoolAccept::ConsensusScriptChecks(const ATMPArgs& args, Workspace& ws) +{ + AssertLockHeld(cs_main); + AssertLockHeld(m_pool.cs); + const CTransaction& tx = *ws.m_ptx; + const Txid& hash = ws.m_hash; + TxValidationState& state = ws.m_state; + + // Check again against the current block tip's script verification + // flags to cache our script execution flags. This is, of course, + // useless if the next block has different script flags from the + // previous one, but because the cache tracks script flags for us it + // will auto-invalidate and we'll just have a few blocks of extra + // misses on soft-fork activation. + // + // This is also useful in case of bugs in the standard flags that cause + // transactions to pass as valid when they're actually invalid. For + // instance the STRICTENC flag was incorrectly allowing certain + // CHECKSIG NOT scripts to pass, even though they were invalid. + // + // There is a similar check in CreateNewBlock() to prevent creating + // invalid blocks (using TestBlockValidity), however allowing such + // transactions into the mempool can be exploited as a DoS attack. + script_verify_flags currentBlockScriptVerifyFlags{GetBlockScriptFlags(*m_active_chainstate.m_chain.Tip(), m_active_chainstate.m_chainman)}; + if (!CheckInputsFromMempoolAndCache(tx, state, m_view, m_pool, currentBlockScriptVerifyFlags, + ws.m_precomputed_txdata, m_active_chainstate.CoinsTip(), GetValidationCache())) { + LogError("BUG! PLEASE REPORT THIS! CheckInputScripts failed against latest-block but not STANDARD flags %s, %s", hash.ToString(), state.ToString()); + return Assume(false); + } + + return true; +} + +void MemPoolAccept::FinalizeSubpackage(const ATMPArgs& args) +{ + AssertLockHeld(cs_main); + AssertLockHeld(m_pool.cs); + + if (!m_subpackage.m_changeset->GetRemovals().empty()) Assume(args.m_allow_replacement); + // Remove conflicting transactions from the mempool + for (CTxMemPool::txiter it : m_subpackage.m_changeset->GetRemovals()) + { + std::string log_string = strprintf("replacing mempool tx %s (wtxid=%s, fees=%s, vsize=%s). ", + it->GetTx().GetHash().ToString(), + it->GetTx().GetWitnessHash().ToString(), + it->GetFee(), + it->GetTxSize()); + FeeFrac feerate{m_subpackage.m_total_modified_fees, int32_t(m_subpackage.m_total_vsize)}; + uint256 tx_or_package_hash{}; + const bool replaced_with_tx{m_subpackage.m_changeset->GetTxCount() == 1}; + if (replaced_with_tx) { + const CTransaction& tx = m_subpackage.m_changeset->GetAddedTxn(0); + tx_or_package_hash = tx.GetHash().ToUint256(); + log_string += strprintf("New tx %s (wtxid=%s, fees=%s, vsize=%s)", + tx.GetHash().ToString(), + tx.GetWitnessHash().ToString(), + feerate.fee, + feerate.size); + } else { + tx_or_package_hash = GetPackageHash(m_subpackage.m_changeset->GetAddedTxns()); + log_string += strprintf("New package %s with %lu txs, fees=%s, vsize=%s", + tx_or_package_hash.ToString(), + m_subpackage.m_changeset->GetTxCount(), + feerate.fee, + feerate.size); + + } + LogDebug(BCLog::MEMPOOL, "%s\n", log_string); + TRACEPOINT(mempool, replaced, + it->GetTx().GetHash().data(), + it->GetTxSize(), + it->GetFee(), + std::chrono::duration_cast>(it->GetTime()).count(), + tx_or_package_hash.data(), + feerate.size, + feerate.fee, + replaced_with_tx + ); + m_subpackage.m_replaced_transactions.push_back(it->GetSharedTx()); + } + m_subpackage.m_changeset->Apply(); + m_subpackage.m_changeset.reset(); +} + +bool MemPoolAccept::SubmitPackage(const ATMPArgs& args, std::vector& workspaces, + PackageValidationState& package_state, + std::map& results) +{ + AssertLockHeld(cs_main); + AssertLockHeld(m_pool.cs); + // Sanity check: none of the transactions should be in the mempool, and none of the transactions + // should have a same-txid-different-witness equivalent in the mempool. + assert(std::all_of(workspaces.cbegin(), workspaces.cend(), [this](const auto& ws) { return !m_pool.exists(ws.m_ptx->GetHash()); })); + + bool all_submitted = true; + FinalizeSubpackage(args); + // ConsensusScriptChecks adds to the script cache and is therefore consensus-critical; + // CheckInputsFromMempoolAndCache asserts that transactions only spend coins available from the + // mempool or UTXO set. Submit each transaction to the mempool immediately after calling + // ConsensusScriptChecks to make the outputs available for subsequent transactions. + for (Workspace& ws : workspaces) { + if (!ConsensusScriptChecks(args, ws)) { + results.emplace(ws.m_ptx->GetWitnessHash(), MempoolAcceptResult::Failure(ws.m_state)); + // Since PolicyScriptChecks() passed, this should never fail. + Assume(false); + all_submitted = false; + package_state.Invalid(PackageValidationResult::PCKG_MEMPOOL_ERROR, + strprintf("BUG! PolicyScriptChecks succeeded but ConsensusScriptChecks failed: %s", + ws.m_ptx->GetHash().ToString())); + } + // Remove first failing tx and all subsequent in package + if (!all_submitted) { + if (!m_subpackage.m_changeset) m_subpackage.m_changeset = m_pool.GetChangeSet(); + m_subpackage.m_changeset->StageRemoval(m_pool.GetIter(ws.m_ptx->GetHash()).value()); + } + } + if (!all_submitted) { + Assume(m_subpackage.m_changeset); + // This code should be unreachable; it's here as belt-and-suspenders + // to try to ensure we have no consensus-invalid transactions in the + // mempool. + m_subpackage.m_changeset->Apply(); + m_subpackage.m_changeset.reset(); + return false; + } + + std::vector all_package_wtxids; + all_package_wtxids.reserve(workspaces.size()); + std::transform(workspaces.cbegin(), workspaces.cend(), std::back_inserter(all_package_wtxids), + [](const auto& ws) { return ws.m_ptx->GetWitnessHash(); }); + + if (!m_subpackage.m_replaced_transactions.empty()) { + LogDebug(BCLog::MEMPOOL, "replaced %u mempool transactions with %u new one(s) for %s additional fees, %d delta bytes\n", + m_subpackage.m_replaced_transactions.size(), workspaces.size(), + m_subpackage.m_total_modified_fees - m_subpackage.m_conflicting_fees, + m_subpackage.m_total_vsize - static_cast(m_subpackage.m_conflicting_size)); + } + + // Add successful results. The returned results may change later if LimitMempoolSize() evicts them. + for (Workspace& ws : workspaces) { + auto iter = m_pool.GetIter(ws.m_ptx->GetHash()); + Assume(iter.has_value()); + const auto effective_feerate = args.m_package_feerates ? ws.m_package_feerate : + CFeeRate{ws.m_modified_fees, static_cast(ws.m_vsize)}; + const auto effective_feerate_wtxids = args.m_package_feerates ? all_package_wtxids : + std::vector{ws.m_ptx->GetWitnessHash()}; + results.emplace(ws.m_ptx->GetWitnessHash(), + MempoolAcceptResult::Success(std::move(m_subpackage.m_replaced_transactions), ws.m_vsize, + ws.m_base_fees, effective_feerate, effective_feerate_wtxids)); + if (!m_pool.m_opts.signals) continue; + const CTransaction& tx = *ws.m_ptx; + const auto tx_info = NewMempoolTransactionInfo(ws.m_ptx, ws.m_base_fees, + ws.m_vsize, (*iter)->GetHeight(), + args.m_bypass_limits, args.m_package_submission, + IsCurrentForFeeEstimation(m_active_chainstate), + m_pool.HasNoInputsOf(tx)); + m_pool.m_opts.signals->TransactionAddedToMempool(tx_info, m_pool.GetAndIncrementSequence()); + } + return all_submitted; +} + +MempoolAcceptResult MemPoolAccept::AcceptSingleTransactionInternal(const CTransactionRef& ptx, ATMPArgs& args) +{ + AssertLockHeld(cs_main); + AssertLockHeld(m_pool.cs); + + Workspace ws(ptx); + const std::vector single_wtxid{ws.m_ptx->GetWitnessHash()}; + + if (!PreChecks(args, ws)) { + if (ws.m_state.GetResult() == TxValidationResult::TX_RECONSIDERABLE) { + // Failed for fee reasons. Provide the effective feerate and which tx was included. + return MempoolAcceptResult::FeeFailure(ws.m_state, CFeeRate(ws.m_modified_fees, ws.m_vsize), single_wtxid); + } + return MempoolAcceptResult::Failure(ws.m_state); + } + + if (m_subpackage.m_rbf && !ReplacementChecks(ws)) { + if (ws.m_state.GetResult() == TxValidationResult::TX_RECONSIDERABLE) { + // Failed for incentives-based fee reasons. Provide the effective feerate and which tx was included. + return MempoolAcceptResult::FeeFailure(ws.m_state, CFeeRate(ws.m_modified_fees, ws.m_vsize), single_wtxid); + } + return MempoolAcceptResult::Failure(ws.m_state); + } + + // Check if the transaction would exceed the cluster size limit. + if (!m_subpackage.m_changeset->CheckMemPoolPolicyLimits()) { + ws.m_state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "too-large-cluster", ""); + return MempoolAcceptResult::Failure(ws.m_state); + } + + // Now that we've verified the cluster limit is respected, we can perform + // calculations involving the full ancestors of the tx. + if (ws.m_conflicts.size()) { + auto ancestors = m_subpackage.m_changeset->CalculateMemPoolAncestors(ws.m_tx_handle); + + // A transaction that spends outputs that would be replaced by it is invalid. Now + // that we have the set of all ancestors we can detect this + // pathological case by making sure ws.m_conflicts and this tx's ancestors don't + // intersect. + if (const auto err_string{EntriesAndTxidsDisjoint(ancestors, ws.m_conflicts, ptx->GetHash())}) { + // We classify this as a consensus error because a transaction depending on something it + // conflicts with would be inconsistent. + ws.m_state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-txns-spends-conflicting-tx", *err_string); + return MempoolAcceptResult::Failure(ws.m_state); + } + } + + m_subpackage.m_total_vsize = ws.m_vsize; + m_subpackage.m_total_modified_fees = ws.m_modified_fees; + + // Individual modified feerate exceeded caller-defined max; abort + if (args.m_client_maxfeerate && CFeeRate(ws.m_modified_fees, ws.m_vsize) > args.m_client_maxfeerate.value()) { + ws.m_state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "max feerate exceeded", ""); + return MempoolAcceptResult::Failure(ws.m_state); + } + + if (!args.m_bypass_limits && m_pool.m_opts.require_standard) { + Wtxid dummy_wtxid; + if (!CheckEphemeralSpends(/*package=*/{ptx}, m_pool.m_opts.dust_relay_feerate, m_pool, ws.m_state, dummy_wtxid)) { + return MempoolAcceptResult::Failure(ws.m_state); + } + } + + // Perform the inexpensive checks first and avoid hashing and signature verification unless + // those checks pass, to mitigate CPU exhaustion denial-of-service attacks. + if (!PolicyScriptChecks(args, ws)) return MempoolAcceptResult::Failure(ws.m_state); + + if (!ConsensusScriptChecks(args, ws)) return MempoolAcceptResult::Failure(ws.m_state); + + const CFeeRate effective_feerate{ws.m_modified_fees, static_cast(ws.m_vsize)}; + // Tx was accepted, but not added + if (args.m_test_accept) { + return MempoolAcceptResult::Success(std::move(m_subpackage.m_replaced_transactions), ws.m_vsize, + ws.m_base_fees, effective_feerate, single_wtxid); + } + + FinalizeSubpackage(args); + + // Limit the mempool, if appropriate. + if (!args.m_package_submission && !args.m_bypass_limits) { + LimitMempoolSize(m_pool, m_active_chainstate.CoinsTip()); + // If mempool contents change, then the m_view cache is dirty. Given this isn't a package + // submission, we won't be using the cache anymore, but clear it anyway for clarity. + CleanupTemporaryCoins(); + + if (!m_pool.exists(ws.m_hash)) { + // The tx no longer meets our (new) mempool minimum feerate but could be reconsidered in a package. + ws.m_state.Invalid(TxValidationResult::TX_RECONSIDERABLE, "mempool full"); + return MempoolAcceptResult::FeeFailure(ws.m_state, CFeeRate(ws.m_modified_fees, ws.m_vsize), {ws.m_ptx->GetWitnessHash()}); + } + } + + if (m_pool.m_opts.signals) { + const CTransaction& tx = *ws.m_ptx; + auto iter = m_pool.GetIter(tx.GetHash()); + Assume(iter.has_value()); + const auto tx_info = NewMempoolTransactionInfo(ws.m_ptx, ws.m_base_fees, + ws.m_vsize, (*iter)->GetHeight(), + args.m_bypass_limits, args.m_package_submission, + IsCurrentForFeeEstimation(m_active_chainstate), + m_pool.HasNoInputsOf(tx)); + m_pool.m_opts.signals->TransactionAddedToMempool(tx_info, m_pool.GetAndIncrementSequence()); + } + + if (!m_subpackage.m_replaced_transactions.empty()) { + LogDebug(BCLog::MEMPOOL, "replaced %u mempool transactions with 1 new transaction for %s additional fees, %d delta bytes\n", + m_subpackage.m_replaced_transactions.size(), + ws.m_modified_fees - m_subpackage.m_conflicting_fees, + ws.m_vsize - static_cast(m_subpackage.m_conflicting_size)); + } + + return MempoolAcceptResult::Success(std::move(m_subpackage.m_replaced_transactions), ws.m_vsize, ws.m_base_fees, + effective_feerate, single_wtxid); +} + +PackageMempoolAcceptResult MemPoolAccept::AcceptMultipleTransactionsInternal(const std::vector& txns, ATMPArgs& args) +{ + AssertLockHeld(cs_main); + AssertLockHeld(m_pool.cs); + + // These context-free package limits can be done before taking the mempool lock. + PackageValidationState package_state; + if (!IsWellFormedPackage(txns, package_state)) return PackageMempoolAcceptResult(package_state, {}); + + std::vector workspaces{}; + workspaces.reserve(txns.size()); + std::transform(txns.cbegin(), txns.cend(), std::back_inserter(workspaces), + [](const auto& tx) { return Workspace(tx); }); + std::map results; + + // Do all PreChecks first and fail fast to avoid running expensive script checks when unnecessary. + for (Workspace& ws : workspaces) { + if (!PreChecks(args, ws)) { + package_state.Invalid(PackageValidationResult::PCKG_TX, "transaction failed"); + // Exit early to avoid doing pointless work. Update the failed tx result; the rest are unfinished. + results.emplace(ws.m_ptx->GetWitnessHash(), MempoolAcceptResult::Failure(ws.m_state)); + return PackageMempoolAcceptResult(package_state, std::move(results)); + } + + // Individual modified feerate exceeded caller-defined max; abort + // N.B. this doesn't take into account CPFPs. Chunk-aware validation may be more robust. + if (args.m_client_maxfeerate && CFeeRate(ws.m_modified_fees, ws.m_vsize) > args.m_client_maxfeerate.value()) { + // Need to set failure here both individually and at package level + ws.m_state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "max feerate exceeded", ""); + package_state.Invalid(PackageValidationResult::PCKG_TX, "transaction failed"); + // Exit early to avoid doing pointless work. Update the failed tx result; the rest are unfinished. + results.emplace(ws.m_ptx->GetWitnessHash(), MempoolAcceptResult::Failure(ws.m_state)); + return PackageMempoolAcceptResult(package_state, std::move(results)); + } + + // Make the coins created by this transaction available for subsequent transactions in the + // package to spend. If there are no conflicts within the package, no transaction can spend a coin + // needed by another transaction in the package. We also need to make sure that no package + // tx replaces (or replaces the ancestor of) the parent of another package tx. As long as we + // check these two things, we don't need to track the coins spent. + // If a package tx conflicts with a mempool tx, PackageRBFChecks() ensures later that any package RBF attempt + // has *no* in-mempool ancestors, so we don't have to worry about subsequent transactions in + // same package spending the same in-mempool outpoints. This needs to be revisited for general + // package RBF. + m_viewmempool.PackageAddTransaction(ws.m_ptx); + } + + // At this point we have all in-mempool parents, and we know every transaction's vsize. + // Run the TRUC checks on the package. + for (Workspace& ws : workspaces) { + if (auto err{PackageTRUCChecks(m_pool, ws.m_ptx, ws.m_vsize, txns, ws.m_parents)}) { + package_state.Invalid(PackageValidationResult::PCKG_POLICY, "TRUC-violation", err.value()); + return PackageMempoolAcceptResult(package_state, {}); + } + } + + // Transactions must meet two minimum feerates: the mempool minimum fee and min relay fee. + // For transactions consisting of exactly one child and its parents, it suffices to use the + // package feerate (total modified fees / total virtual size) to check this requirement. + // Note that this is an aggregate feerate; this function has not checked that there are transactions + // too low feerate to pay for themselves, or that the child transactions are higher feerate than + // their parents. Using aggregate feerate may allow "parents pay for child" behavior and permit + // a child that is below mempool minimum feerate. To avoid these behaviors, callers of + // AcceptMultipleTransactions need to restrict txns topology (e.g. to ancestor sets) and check + // the feerates of individuals and subsets. + m_subpackage.m_total_vsize = std::accumulate(workspaces.cbegin(), workspaces.cend(), int64_t{0}, + [](int64_t sum, auto& ws) { return sum + ws.m_vsize; }); + m_subpackage.m_total_modified_fees = std::accumulate(workspaces.cbegin(), workspaces.cend(), CAmount{0}, + [](CAmount sum, auto& ws) { return sum + ws.m_modified_fees; }); + const CFeeRate package_feerate(m_subpackage.m_total_modified_fees, m_subpackage.m_total_vsize); + std::vector all_package_wtxids; + all_package_wtxids.reserve(workspaces.size()); + std::transform(workspaces.cbegin(), workspaces.cend(), std::back_inserter(all_package_wtxids), + [](const auto& ws) { return ws.m_ptx->GetWitnessHash(); }); + TxValidationState placeholder_state; + if (args.m_package_feerates && + !CheckFeeRate(m_subpackage.m_total_vsize, m_subpackage.m_total_modified_fees, placeholder_state)) { + package_state.Invalid(PackageValidationResult::PCKG_TX, "transaction failed"); + return PackageMempoolAcceptResult(package_state, {{workspaces.back().m_ptx->GetWitnessHash(), + MempoolAcceptResult::FeeFailure(placeholder_state, CFeeRate(m_subpackage.m_total_modified_fees, m_subpackage.m_total_vsize), all_package_wtxids)}}); + } + + // Apply package mempool RBF checks. + if (m_subpackage.m_rbf && !PackageRBFChecks(txns, workspaces, m_subpackage.m_total_vsize, package_state)) { + return PackageMempoolAcceptResult(package_state, std::move(results)); + } + + // Check if the transactions would exceed the cluster size limit. + if (!m_subpackage.m_changeset->CheckMemPoolPolicyLimits()) { + package_state.Invalid(PackageValidationResult::PCKG_POLICY, "too-large-cluster", ""); + return PackageMempoolAcceptResult(package_state, std::move(results)); + } + + // Now that we've bounded the resulting possible ancestry count, check package for dust spends + if (m_pool.m_opts.require_standard) { + TxValidationState child_state; + Wtxid child_wtxid; + if (!CheckEphemeralSpends(txns, m_pool.m_opts.dust_relay_feerate, m_pool, child_state, child_wtxid)) { + package_state.Invalid(PackageValidationResult::PCKG_TX, "unspent-dust"); + results.emplace(child_wtxid, MempoolAcceptResult::Failure(child_state)); + return PackageMempoolAcceptResult(package_state, std::move(results)); + } + } + + for (Workspace& ws : workspaces) { + ws.m_package_feerate = package_feerate; + if (!PolicyScriptChecks(args, ws)) { + // Exit early to avoid doing pointless work. Update the failed tx result; the rest are unfinished. + package_state.Invalid(PackageValidationResult::PCKG_TX, "transaction failed"); + results.emplace(ws.m_ptx->GetWitnessHash(), MempoolAcceptResult::Failure(ws.m_state)); + return PackageMempoolAcceptResult(package_state, std::move(results)); + } + if (args.m_test_accept) { + const auto effective_feerate = args.m_package_feerates ? ws.m_package_feerate : + CFeeRate{ws.m_modified_fees, static_cast(ws.m_vsize)}; + const auto effective_feerate_wtxids = args.m_package_feerates ? all_package_wtxids : + std::vector{ws.m_ptx->GetWitnessHash()}; + results.emplace(ws.m_ptx->GetWitnessHash(), + MempoolAcceptResult::Success(std::move(m_subpackage.m_replaced_transactions), + ws.m_vsize, ws.m_base_fees, effective_feerate, + effective_feerate_wtxids)); + } + } + + if (args.m_test_accept) return PackageMempoolAcceptResult(package_state, std::move(results)); + + if (!SubmitPackage(args, workspaces, package_state, results)) { + // PackageValidationState filled in by SubmitPackage(). + return PackageMempoolAcceptResult(package_state, std::move(results)); + } + + return PackageMempoolAcceptResult(package_state, std::move(results)); +} + +void MemPoolAccept::CleanupTemporaryCoins() +{ + // There are 3 kinds of coins in m_view: + // (1) Temporary coins from the transactions in subpackage, constructed by m_viewmempool. + // (2) Mempool coins from transactions in the mempool, constructed by m_viewmempool. + // (3) Confirmed coins fetched from our current UTXO set. + // + // (1) Temporary coins need to be removed, regardless of whether the transaction was submitted. + // If the transaction was submitted to the mempool, m_viewmempool will be able to fetch them from + // there. If it wasn't submitted to mempool, it is incorrect to keep them - future calls may try + // to spend those coins that don't actually exist. + // (2) Mempool coins also need to be removed. If the mempool contents have changed as a result + // of submitting or replacing transactions, coins previously fetched from mempool may now be + // spent or nonexistent. Those coins need to be deleted from m_view. + // (3) Confirmed coins don't need to be removed. The chainstate has not changed (we are + // holding cs_main and no blocks have been processed) so the confirmed tx cannot disappear like + // a mempool tx can. The coin may now be spent after we submitted a tx to mempool, but + // we have already checked that the package does not have 2 transactions spending the same coin + // and we check whether a mempool transaction spends conflicting coins (CTxMemPool::GetConflictTx). + // Keeping them in m_view is an optimization to not re-fetch confirmed coins if we later look up + // inputs for this transaction again. + for (const auto& outpoint : m_viewmempool.GetNonBaseCoins()) { + // In addition to resetting m_viewmempool, we also need to manually delete these coins from + // m_view because it caches copies of the coins it fetched from m_viewmempool previously. + m_view.Uncache(outpoint); + } + // This deletes the temporary and mempool coins. + m_viewmempool.Reset(); +} + +PackageMempoolAcceptResult MemPoolAccept::AcceptSubPackage(const std::vector& subpackage, ATMPArgs& args) +{ + AssertLockHeld(::cs_main); + AssertLockHeld(m_pool.cs); + auto result = [&]() EXCLUSIVE_LOCKS_REQUIRED(::cs_main, m_pool.cs) { + if (subpackage.size() > 1) { + return AcceptMultipleTransactionsInternal(subpackage, args); + } + const auto& tx = subpackage.front(); + ATMPArgs single_args = ATMPArgs::SingleInPackageAccept(args); + const auto single_res = AcceptSingleTransactionInternal(tx, single_args); + PackageValidationState package_state_wrapped; + if (single_res.m_result_type != MempoolAcceptResult::ResultType::VALID) { + package_state_wrapped.Invalid(PackageValidationResult::PCKG_TX, "transaction failed"); + } + return PackageMempoolAcceptResult(package_state_wrapped, {{tx->GetWitnessHash(), single_res}}); + }(); + + // Clean up m_view and m_viewmempool so that other subpackage evaluations don't have access to + // coins they shouldn't. Keep some coins in order to minimize re-fetching coins from the UTXO set. + // Clean up package feerate and rbf calculations + ClearSubPackageState(); + + return result; +} + +PackageMempoolAcceptResult MemPoolAccept::AcceptPackage(const Package& package, ATMPArgs& args) +{ + Assert(!package.empty()); + AssertLockHeld(cs_main); + // Used if returning a PackageMempoolAcceptResult directly from this function. + PackageValidationState package_state_quit_early; + + // There are two topologies we are able to handle through this function: + // (1) A single transaction + // (2) A child-with-parents package. + // Check that the package is well-formed. If it isn't, we won't try to validate any of the + // transactions and thus won't return any MempoolAcceptResults, just a package-wide error. + + // Context-free package checks. + if (!IsWellFormedPackage(package, package_state_quit_early)) { + return PackageMempoolAcceptResult(package_state_quit_early, {}); + } + + if (package.size() > 1 && !IsChildWithParents(package)) { + // All transactions in the package must be a parent of the last transaction. This is just an + // opportunity for us to fail fast on a context-free check without taking the mempool lock. + package_state_quit_early.Invalid(PackageValidationResult::PCKG_POLICY, "package-not-child-with-parents"); + return PackageMempoolAcceptResult(package_state_quit_early, {}); + } + + LOCK(m_pool.cs); + // Stores results from which we will create the returned PackageMempoolAcceptResult. + // A result may be changed if a mempool transaction is evicted later due to LimitMempoolSize(). + std::map results_final; + // Results from individual validation which will be returned if no other result is available for + // this transaction. "Nonfinal" because if a transaction fails by itself but succeeds later + // (i.e. when evaluated with a fee-bumping child), the result in this map may be discarded. + std::map individual_results_nonfinal; + // Tracks whether we think package submission could result in successful entry to the mempool + bool quit_early{false}; + std::vector txns_package_eval; + for (const auto& tx : package) { + const auto& wtxid = tx->GetWitnessHash(); + const auto& txid = tx->GetHash(); + // There are 3 possibilities: already in mempool, same-txid-diff-wtxid already in mempool, + // or not in mempool. An already confirmed tx is treated as one not in mempool, because all + // we know is that the inputs aren't available. + if (m_pool.exists(wtxid)) { + // Exact transaction already exists in the mempool. + // Node operators are free to set their mempool policies however they please, nodes may receive + // transactions in different orders, and malicious counterparties may try to take advantage of + // policy differences to pin or delay propagation of transactions. As such, it's possible for + // some package transaction(s) to already be in the mempool, and we don't want to reject the + // entire package in that case (as that could be a censorship vector). De-duplicate the + // transactions that are already in the mempool, and only call AcceptMultipleTransactions() with + // the new transactions. This ensures we don't double-count transaction counts and sizes when + // checking ancestor/descendant limits, or double-count transaction fees for fee-related policy. + const auto& entry{*Assert(m_pool.GetEntry(txid))}; + results_final.emplace(wtxid, MempoolAcceptResult::MempoolTx(entry.GetTxSize(), entry.GetFee())); + } else if (m_pool.exists(txid)) { + // Transaction with the same non-witness data but different witness (same txid, + // different wtxid) already exists in the mempool. + // + // We don't allow replacement transactions right now, so just swap the package + // transaction for the mempool one. Note that we are ignoring the validity of the + // package transaction passed in. + // TODO: allow witness replacement in packages. + const auto& entry{*Assert(m_pool.GetEntry(txid))}; + // Provide the wtxid of the mempool tx so that the caller can look it up in the mempool. + results_final.emplace(wtxid, MempoolAcceptResult::MempoolTxDifferentWitness(entry.GetTx().GetWitnessHash())); + } else { + // Transaction does not already exist in the mempool. + // Try submitting the transaction on its own. + const auto single_package_res = AcceptSubPackage({tx}, args); + const auto& single_res = single_package_res.m_tx_results.at(wtxid); + if (single_res.m_result_type == MempoolAcceptResult::ResultType::VALID) { + // The transaction succeeded on its own and is now in the mempool. Don't include it + // in package validation, because its fees should only be "used" once. + assert(m_pool.exists(wtxid)); + results_final.emplace(wtxid, single_res); + } else if (package.size() == 1 || // If there is only one transaction, no need to retry it "as a package" + (single_res.m_state.GetResult() != TxValidationResult::TX_RECONSIDERABLE && + single_res.m_state.GetResult() != TxValidationResult::TX_MISSING_INPUTS)) { + // Package validation policy only differs from individual policy in its evaluation + // of feerate. For example, if a transaction fails here due to violation of a + // consensus rule, the result will not change when it is submitted as part of a + // package. To minimize the amount of repeated work, unless the transaction fails + // due to feerate or missing inputs (its parent is a previous transaction in the + // package that failed due to feerate), don't run package validation. Note that this + // decision might not make sense if different types of packages are allowed in the + // future. Continue individually validating the rest of the transactions, because + // some of them may still be valid. + quit_early = true; + package_state_quit_early.Invalid(PackageValidationResult::PCKG_TX, "transaction failed"); + individual_results_nonfinal.emplace(wtxid, single_res); + } else { + individual_results_nonfinal.emplace(wtxid, single_res); + txns_package_eval.push_back(tx); + } + } + } + + auto multi_submission_result = quit_early || txns_package_eval.empty() ? PackageMempoolAcceptResult(package_state_quit_early, {}) : + AcceptSubPackage(txns_package_eval, args); + PackageValidationState& package_state_final = multi_submission_result.m_state; + + // This is invoked by AcceptSubPackage() already, so this is just here for + // clarity (since it's not permitted to invoke LimitMempoolSize() while a + // changeset is outstanding). + ClearSubPackageState(); + + // Make sure we haven't exceeded max mempool size. + // Package transactions that were submitted to mempool or already in mempool may be evicted. + // If mempool contents change, then the m_view cache is dirty. It has already been cleared above. + LimitMempoolSize(m_pool, m_active_chainstate.CoinsTip()); + + for (const auto& tx : package) { + const auto& wtxid = tx->GetWitnessHash(); + if (multi_submission_result.m_tx_results.contains(wtxid)) { + // We shouldn't have re-submitted if the tx result was already in results_final. + Assume(!results_final.contains(wtxid)); + // If it was submitted, check to see if the tx is still in the mempool. It could have + // been evicted due to LimitMempoolSize() above. + const auto& txresult = multi_submission_result.m_tx_results.at(wtxid); + if (txresult.m_result_type == MempoolAcceptResult::ResultType::VALID && !m_pool.exists(wtxid)) { + package_state_final.Invalid(PackageValidationResult::PCKG_TX, "transaction failed"); + TxValidationState mempool_full_state; + mempool_full_state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "mempool full"); + results_final.emplace(wtxid, MempoolAcceptResult::Failure(mempool_full_state)); + } else { + results_final.emplace(wtxid, txresult); + } + } else if (const auto it{results_final.find(wtxid)}; it != results_final.end()) { + // Already-in-mempool transaction. Check to see if it's still there, as it could have + // been evicted when LimitMempoolSize() was called. + Assume(it->second.m_result_type != MempoolAcceptResult::ResultType::INVALID); + Assume(!individual_results_nonfinal.contains(wtxid)); + // Query by txid to include the same-txid-different-witness ones. + if (!m_pool.exists(tx->GetHash())) { + package_state_final.Invalid(PackageValidationResult::PCKG_TX, "transaction failed"); + TxValidationState mempool_full_state; + mempool_full_state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "mempool full"); + // Replace the previous result. + results_final.erase(wtxid); + results_final.emplace(wtxid, MempoolAcceptResult::Failure(mempool_full_state)); + } + } else if (const auto it{individual_results_nonfinal.find(wtxid)}; it != individual_results_nonfinal.end()) { + Assume(it->second.m_result_type == MempoolAcceptResult::ResultType::INVALID); + // Interesting result from previous processing. + results_final.emplace(wtxid, it->second); + } + } + Assume(results_final.size() == package.size()); + return PackageMempoolAcceptResult(package_state_final, std::move(results_final)); +} + +} // anon namespace + +MempoolAcceptResult AcceptToMemoryPool(Chainstate& active_chainstate, const CTransactionRef& tx, + int64_t accept_time, bool bypass_limits, bool test_accept) +{ + AssertLockHeld(::cs_main); + const CChainParams& chainparams{active_chainstate.m_chainman.GetParams()}; + assert(active_chainstate.GetMempool() != nullptr); + CTxMemPool& pool{*active_chainstate.GetMempool()}; + + std::vector coins_to_uncache; + + auto args = MemPoolAccept::ATMPArgs::SingleAccept(chainparams, accept_time, bypass_limits, coins_to_uncache, test_accept); + MempoolAcceptResult result = MemPoolAccept(pool, active_chainstate).AcceptSingleTransactionAndCleanup(tx, args); + + if (result.m_result_type != MempoolAcceptResult::ResultType::VALID) { + // Remove coins that were not present in the coins cache before calling + // AcceptSingleTransaction(); this is to prevent memory DoS in case we receive a large + // number of invalid transactions that attempt to overrun the in-memory coins cache + // (`CCoinsViewCache::cacheCoins`). + + for (const COutPoint& hashTx : coins_to_uncache) + active_chainstate.CoinsTip().Uncache(hashTx); + TRACEPOINT(mempool, rejected, + tx->GetHash().data(), + result.m_state.GetRejectReason().c_str() + ); + } + // After we've (potentially) uncached entries, ensure our coins cache is still within its size limits + BlockValidationState state_dummy; + active_chainstate.FlushStateToDisk(state_dummy, FlushStateMode::PERIODIC); + return result; +} + +PackageMempoolAcceptResult ProcessNewPackage(Chainstate& active_chainstate, CTxMemPool& pool, + const Package& package, bool test_accept, const std::optional& client_maxfeerate) +{ + AssertLockHeld(cs_main); + assert(!package.empty()); + assert(std::all_of(package.cbegin(), package.cend(), [](const auto& tx){return tx != nullptr;})); + + std::vector coins_to_uncache; + const CChainParams& chainparams = active_chainstate.m_chainman.GetParams(); + auto result = [&]() EXCLUSIVE_LOCKS_REQUIRED(cs_main) { + AssertLockHeld(cs_main); + if (test_accept) { + auto args = MemPoolAccept::ATMPArgs::PackageTestAccept(chainparams, GetTime(), coins_to_uncache); + return MemPoolAccept(pool, active_chainstate).AcceptMultipleTransactionsAndCleanup(package, args); + } else { + auto args = MemPoolAccept::ATMPArgs::PackageChildWithParents(chainparams, GetTime(), coins_to_uncache, client_maxfeerate); + return MemPoolAccept(pool, active_chainstate).AcceptPackage(package, args); + } + }(); + + // Uncache coins pertaining to transactions that were not submitted to the mempool. + if (test_accept || result.m_state.IsInvalid()) { + for (const COutPoint& hashTx : coins_to_uncache) { + active_chainstate.CoinsTip().Uncache(hashTx); + } + } + // Ensure the coins cache is still within limits. + BlockValidationState state_dummy; + active_chainstate.FlushStateToDisk(state_dummy, FlushStateMode::PERIODIC); + return result; +} + + diff --git a/src/txmempool.h b/src/txmempool.h index ae59057ca62b..4a27dc089229 100644 --- a/src/txmempool.h +++ b/src/txmempool.h @@ -15,6 +15,7 @@ #include // IWYU pragma: export #include // IWYU pragma: export #include +#include #include #include #include @@ -42,7 +43,9 @@ #include class CChain; +class Chainstate; class ValidationSignals; +class DisconnectedBlockTransactions; struct bilingual_str; @@ -315,6 +318,24 @@ class CTxMemPool */ void check(const CCoinsViewCache& active_coins_tip, int64_t spendheight) const EXCLUSIVE_LOCKS_REQUIRED(::cs_main); + /** + * Make mempool consistent after a reorg, by re-adding or recursively erasing + * disconnected block transactions from the mempool, and also removing any + * other transactions from the mempool that are no longer valid given the new + * tip/height. + * + * Note: we assume that disconnectpool only contains transactions that are NOT + * confirmed in the current chain nor already in the mempool (otherwise, + * in-mempool descendants of such transactions would be removed). + * + * Passing fAddToMempool=false will skip trying to add the transactions back, + * and instead just erase from the mempool as needed. + */ + void MaybeUpdateMempoolForReorg( + Chainstate& active_chainstate, + DisconnectedBlockTransactions& disconnectpool, + bool fAddToMempool) EXCLUSIVE_LOCKS_REQUIRED(cs_main, cs); + /** * Remove a transaction from the mempool along with any descendants. * If the transaction is not already in the mempool, find any descendants @@ -776,4 +797,40 @@ class CCoinsViewMemPool : public CCoinsViewBacked /** Clear m_temp_added and m_non_base_coins. */ void Reset(); }; + +/** + * Try to add a transaction to the mempool. This is an internal function and is exposed only for testing. + * Client code should use ChainstateManager::ProcessTransaction() + * + * @param[in] active_chainstate Reference to the active chainstate. + * @param[in] tx The transaction to submit for mempool acceptance. + * @param[in] accept_time The timestamp for adding the transaction to the mempool. + * It is also used to determine when the entry expires. + * @param[in] bypass_limits When true, don't enforce mempool fee and capacity limits, + * and set entry_sequence to zero. + * @param[in] test_accept When true, run validation checks but don't submit to mempool. + * + * @returns a MempoolAcceptResult indicating whether the transaction was accepted/rejected with reason. + */ +MempoolAcceptResult AcceptToMemoryPool(Chainstate& active_chainstate, const CTransactionRef& tx, + int64_t accept_time, bool bypass_limits, bool test_accept) + EXCLUSIVE_LOCKS_REQUIRED(cs_main); + +/** +* Validate (and maybe submit) a package to the mempool. See doc/policy/packages.md for full details +* on package validation rules. +* @param[in] test_accept When true, run validation checks but don't submit to mempool. +* @param[in] client_maxfeerate If exceeded by an individual transaction, rest of (sub)package evaluation is aborted. +* Only for sanity checks against local submission of transactions. +* @returns a PackageMempoolAcceptResult which includes a MempoolAcceptResult for each transaction. +* If a transaction fails, validation will exit early and some results may be missing. It is also +* possible for the package to be partially submitted. +*/ +PackageMempoolAcceptResult ProcessNewPackage(Chainstate& active_chainstate, CTxMemPool& pool, + const Package& txns, bool test_accept, const std::optional& client_maxfeerate) + EXCLUSIVE_LOCKS_REQUIRED(cs_main); + +/* Mempool validation helper functions */ + + #endif // BITCOIN_TXMEMPOOL_H diff --git a/src/validation.cpp b/src/validation.cpp index a1c8308bfd77..029d9ee0fec7 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -96,8 +96,6 @@ using node::SnapshotMetadata; */ static constexpr auto DATABASE_WRITE_INTERVAL_MIN{50min}; static constexpr auto DATABASE_WRITE_INTERVAL_MAX{70min}; -/** Maximum age of our tip for us to be considered current for fee estimation */ -static constexpr std::chrono::hours MAX_FEE_ESTIMATION_TIP_AGE{3}; const std::vector CHECKLEVEL_DOC { "level 0 reads the blocks from disk", "level 1 verifies block validity", @@ -115,8 +113,6 @@ static constexpr int PRUNE_LOCK_BUFFER{10}; TRACEPOINT_SEMAPHORE(validation, block_connected); TRACEPOINT_SEMAPHORE(utxocache, flush); -TRACEPOINT_SEMAPHORE(mempool, replaced); -TRACEPOINT_SEMAPHORE(mempool, rejected); const CBlockIndex* Chainstate::FindForkInGlobalIndex(const CBlockLocator& locator) const { @@ -138,13 +134,6 @@ const CBlockIndex* Chainstate::FindForkInGlobalIndex(const CBlockLocator& locato return m_chain.Genesis(); } -bool CheckInputScripts(const CTransaction& tx, TxValidationState& state, - const CCoinsViewCache& inputs, script_verify_flags flags, bool cacheSigStore, - bool cacheFullScriptStore, PrecomputedTransactionData& txdata, - ValidationCache& validation_cache, - std::vector* pvChecks = nullptr) - EXCLUSIVE_LOCKS_REQUIRED(cs_main); - bool CheckFinalTxAtTip(const CBlockIndex& active_chain_tip, const CTransaction& tx) { AssertLockHeld(cs_main); @@ -262,1579 +251,6 @@ bool CheckSequenceLocksAtTip(CBlockIndex* tip, return EvaluateSequenceLocks(index, {lock_points.height, lock_points.time}); } -static void LimitMempoolSize(CTxMemPool& pool, CCoinsViewCache& coins_cache) - EXCLUSIVE_LOCKS_REQUIRED(::cs_main, pool.cs) -{ - AssertLockHeld(::cs_main); - AssertLockHeld(pool.cs); - int expired = pool.Expire(GetTime() - pool.m_opts.expiry); - if (expired != 0) { - LogDebug(BCLog::MEMPOOL, "Expired %i transactions from the memory pool\n", expired); - } - - std::vector vNoSpendsRemaining; - pool.TrimToSize(pool.m_opts.max_size_bytes, &vNoSpendsRemaining); - for (const COutPoint& removed : vNoSpendsRemaining) - coins_cache.Uncache(removed); -} - -static bool IsCurrentForFeeEstimation(Chainstate& active_chainstate) EXCLUSIVE_LOCKS_REQUIRED(cs_main) -{ - AssertLockHeld(cs_main); - if (active_chainstate.m_chainman.IsInitialBlockDownload()) { - return false; - } - if (active_chainstate.m_chain.Tip()->GetBlockTime() < count_seconds(GetTime() - MAX_FEE_ESTIMATION_TIP_AGE)) - return false; - if (active_chainstate.m_chain.Height() < active_chainstate.m_chainman.m_best_header->nHeight - 1) { - return false; - } - return true; -} - -void Chainstate::MaybeUpdateMempoolForReorg( - DisconnectedBlockTransactions& disconnectpool, - bool fAddToMempool) -{ - if (!m_mempool) return; - - AssertLockHeld(cs_main); - AssertLockHeld(m_mempool->cs); - std::vector vHashUpdate; - { - // disconnectpool is ordered so that the front is the most recently-confirmed - // transaction (the last tx of the block at the tip) in the disconnected chain. - // Iterate disconnectpool in reverse, so that we add transactions - // back to the mempool starting with the earliest transaction that had - // been previously seen in a block. - const auto queuedTx = disconnectpool.take(); - auto it = queuedTx.rbegin(); - while (it != queuedTx.rend()) { - // ignore validation errors in resurrected transactions - if (!fAddToMempool || (*it)->IsCoinBase() || - AcceptToMemoryPool(*this, *it, GetTime(), - /*bypass_limits=*/true, /*test_accept=*/false).m_result_type != - MempoolAcceptResult::ResultType::VALID) { - // If the transaction doesn't make it in to the mempool, remove any - // transactions that depend on it (which would now be orphans). - m_mempool->removeRecursive(**it, MemPoolRemovalReason::REORG); - } else if (m_mempool->exists((*it)->GetHash())) { - vHashUpdate.push_back((*it)->GetHash()); - } - ++it; - } - } - - // AcceptToMemoryPool/addNewTransaction all assume that new mempool entries have - // no in-mempool children, which is generally not true when adding - // previously-confirmed transactions back to the mempool. - // UpdateTransactionsFromBlock finds descendants of any transactions in - // the disconnectpool that were added back and cleans up the mempool state. - m_mempool->UpdateTransactionsFromBlock(vHashUpdate); - - // Predicate to use for filtering transactions in removeForReorg. - // Checks whether the transaction is still final and, if it spends a coinbase output, mature. - // Also updates valid entries' cached LockPoints if needed. - // If false, the tx is still valid and its lockpoints are updated. - // If true, the tx would be invalid in the next block; remove this entry and all of its descendants. - // Note that TRUC rules are not applied here, so reorgs may cause violations of TRUC inheritance or - // topology restrictions. - const auto filter_final_and_mature = [&](CTxMemPool::txiter it) - EXCLUSIVE_LOCKS_REQUIRED(m_mempool->cs, ::cs_main) { - AssertLockHeld(m_mempool->cs); - AssertLockHeld(::cs_main); - const CTransaction& tx = it->GetTx(); - - // The transaction must be final. - if (!CheckFinalTxAtTip(*Assert(m_chain.Tip()), tx)) return true; - - const LockPoints& lp = it->GetLockPoints(); - // CheckSequenceLocksAtTip checks if the transaction will be final in the next block to be - // created on top of the new chain. - if (TestLockPointValidity(m_chain, lp)) { - if (!CheckSequenceLocksAtTip(m_chain.Tip(), lp)) { - return true; - } - } else { - const CCoinsViewMemPool view_mempool{&CoinsTip(), *m_mempool}; - const std::optional new_lock_points{CalculateLockPointsAtTip(m_chain.Tip(), view_mempool, tx)}; - if (new_lock_points.has_value() && CheckSequenceLocksAtTip(m_chain.Tip(), *new_lock_points)) { - // Now update the mempool entry lockpoints as well. - it->UpdateLockPoints(*new_lock_points); - } else { - return true; - } - } - - // If the transaction spends any coinbase outputs, it must be mature. - if (it->GetSpendsCoinbase()) { - for (const CTxIn& txin : tx.vin) { - if (m_mempool->exists(txin.prevout.hash)) continue; - const Coin& coin{CoinsTip().AccessCoin(txin.prevout)}; - assert(!coin.IsSpent()); - const auto mempool_spend_height{m_chain.Tip()->nHeight + 1}; - if (coin.IsCoinBase() && mempool_spend_height - coin.nHeight < COINBASE_MATURITY) { - return true; - } - } - } - // Transaction is still valid and cached LockPoints are updated. - return false; - }; - - // We also need to remove any now-immature transactions - m_mempool->removeForReorg(m_chain, filter_final_and_mature); - // Re-limit mempool size, in case we added any transactions - LimitMempoolSize(*m_mempool, this->CoinsTip()); -} - -/** -* Checks to avoid mempool polluting consensus critical paths since cached -* signature and script validity results will be reused if we validate this -* transaction again during block validation. -* */ -static bool CheckInputsFromMempoolAndCache(const CTransaction& tx, TxValidationState& state, - const CCoinsViewCache& view, const CTxMemPool& pool, - script_verify_flags flags, PrecomputedTransactionData& txdata, CCoinsViewCache& coins_tip, - ValidationCache& validation_cache) - EXCLUSIVE_LOCKS_REQUIRED(cs_main, pool.cs) -{ - AssertLockHeld(cs_main); - AssertLockHeld(pool.cs); - - assert(!tx.IsCoinBase()); - for (const CTxIn& txin : tx.vin) { - const Coin& coin = view.AccessCoin(txin.prevout); - - // This coin was checked in PreChecks and MemPoolAccept - // has been holding cs_main since then. - Assume(!coin.IsSpent()); - if (coin.IsSpent()) return false; - - // If the Coin is available, there are 2 possibilities: - // it is available in our current ChainstateActive UTXO set, - // or it's a UTXO provided by a transaction in our mempool. - // Ensure the scriptPubKeys in Coins from CoinsView are correct. - const CTransactionRef& txFrom = pool.get(txin.prevout.hash); - if (txFrom) { - assert(txFrom->GetHash() == txin.prevout.hash); - assert(txFrom->vout.size() > txin.prevout.n); - assert(txFrom->vout[txin.prevout.n] == coin.out); - } else { - const Coin& coinFromUTXOSet = coins_tip.AccessCoin(txin.prevout); - assert(!coinFromUTXOSet.IsSpent()); - assert(coinFromUTXOSet.out == coin.out); - } - } - - // Call CheckInputScripts() to cache signature and script validity against current tip consensus rules. - return CheckInputScripts(tx, state, view, flags, /* cacheSigStore= */ true, /* cacheFullScriptStore= */ true, txdata, validation_cache); -} - -namespace { - -class MemPoolAccept -{ -public: - explicit MemPoolAccept(CTxMemPool& mempool, Chainstate& active_chainstate) : - m_pool(mempool), - m_view(&CoinsViewEmpty::Get()), - m_viewmempool(&active_chainstate.CoinsTip(), m_pool), - m_active_chainstate(active_chainstate) - { - } - - // We put the arguments we're handed into a struct, so we can pass them - // around easier. - struct ATMPArgs { - const CChainParams& m_chainparams; - const int64_t m_accept_time; - const bool m_bypass_limits; - /* - * Return any outpoints which were not previously present in the coins - * cache, but were added as a result of validating the tx for mempool - * acceptance. This allows the caller to optionally remove the cache - * additions if the associated transaction ends up being rejected by - * the mempool. - */ - std::vector& m_coins_to_uncache; - /** When true, the transaction or package will not be submitted to the mempool. */ - const bool m_test_accept; - /** Whether we allow transactions to replace mempool transactions. If false, - * any transaction spending the same inputs as a transaction in the mempool is considered - * a conflict. */ - const bool m_allow_replacement; - /** When true, allow sibling eviction. This only occurs in single transaction package settings. */ - const bool m_allow_sibling_eviction; - /** Used to skip the LimitMempoolSize() call within AcceptSingleTransaction(). This should be used when multiple - * AcceptSubPackage calls are expected and the mempool will be trimmed at the end of AcceptPackage(). */ - const bool m_package_submission; - /** When true, use package feerates instead of individual transaction feerates for fee-based - * policies such as mempool min fee and min relay fee. - */ - const bool m_package_feerates; - /** Used for local submission of transactions to catch "absurd" fees - * due to fee miscalculation by wallets. std:nullopt implies unset, allowing any feerates. - * Any individual transaction failing this check causes immediate failure. - */ - const std::optional m_client_maxfeerate; - - /** Parameters for single transaction mempool validation. */ - static ATMPArgs SingleAccept(const CChainParams& chainparams, int64_t accept_time, - bool bypass_limits, std::vector& coins_to_uncache, - bool test_accept) { - return ATMPArgs{/*chainparams=*/ chainparams, - /*accept_time=*/ accept_time, - /*bypass_limits=*/ bypass_limits, - /*coins_to_uncache=*/ coins_to_uncache, - /*test_accept=*/ test_accept, - /*allow_replacement=*/ true, - /*allow_sibling_eviction=*/ true, - /*package_submission=*/ false, - /*package_feerates=*/ false, - /*client_maxfeerate=*/ {}, // checked by caller - }; - } - - /** Parameters for test package mempool validation through testmempoolaccept. */ - static ATMPArgs PackageTestAccept(const CChainParams& chainparams, int64_t accept_time, - std::vector& coins_to_uncache) { - return ATMPArgs{/*chainparams=*/ chainparams, - /*accept_time=*/ accept_time, - /*bypass_limits=*/ false, - /*coins_to_uncache=*/ coins_to_uncache, - /*test_accept=*/ true, - /*allow_replacement=*/ false, - /*allow_sibling_eviction=*/ false, - /*package_submission=*/ false, // not submitting to mempool - /*package_feerates=*/ false, - /*client_maxfeerate=*/ {}, // checked by caller - }; - } - - /** Parameters for child-with-parents package validation. */ - static ATMPArgs PackageChildWithParents(const CChainParams& chainparams, int64_t accept_time, - std::vector& coins_to_uncache, const std::optional& client_maxfeerate) { - return ATMPArgs{/*chainparams=*/ chainparams, - /*accept_time=*/ accept_time, - /*bypass_limits=*/ false, - /*coins_to_uncache=*/ coins_to_uncache, - /*test_accept=*/ false, - /*allow_replacement=*/ true, - /*allow_sibling_eviction=*/ false, - /*package_submission=*/ true, - /*package_feerates=*/ true, - /*client_maxfeerate=*/ client_maxfeerate, - }; - } - - /** Parameters for a single transaction within a package. */ - static ATMPArgs SingleInPackageAccept(const ATMPArgs& package_args) { - return ATMPArgs{/*chainparams=*/ package_args.m_chainparams, - /*accept_time=*/ package_args.m_accept_time, - /*bypass_limits=*/ false, - /*coins_to_uncache=*/ package_args.m_coins_to_uncache, - /*test_accept=*/ package_args.m_test_accept, - /*allow_replacement=*/ true, - /*allow_sibling_eviction=*/ true, - /*package_submission=*/ true, // trim at the end of AcceptPackage() - /*package_feerates=*/ false, // only 1 transaction - /*client_maxfeerate=*/ package_args.m_client_maxfeerate, - }; - } - - private: - // Private ctor to avoid exposing details to clients and allowing the possibility of - // mixing up the order of the arguments. Use static functions above instead. - ATMPArgs(const CChainParams& chainparams, - int64_t accept_time, - bool bypass_limits, - std::vector& coins_to_uncache, - bool test_accept, - bool allow_replacement, - bool allow_sibling_eviction, - bool package_submission, - bool package_feerates, - std::optional client_maxfeerate) - : m_chainparams{chainparams}, - m_accept_time{accept_time}, - m_bypass_limits{bypass_limits}, - m_coins_to_uncache{coins_to_uncache}, - m_test_accept{test_accept}, - m_allow_replacement{allow_replacement}, - m_allow_sibling_eviction{allow_sibling_eviction}, - m_package_submission{package_submission}, - m_package_feerates{package_feerates}, - m_client_maxfeerate{client_maxfeerate} - { - // If we are using package feerates, we must be doing package submission. - // It also means sibling eviction is not permitted. - if (m_package_feerates) { - Assume(m_package_submission); - Assume(!m_allow_sibling_eviction); - } - if (m_allow_sibling_eviction) Assume(m_allow_replacement); - } - }; - - /** Clean up all non-chainstate coins from m_view and m_viewmempool. */ - void CleanupTemporaryCoins() EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_pool.cs); - - // Single transaction acceptance - MempoolAcceptResult AcceptSingleTransactionAndCleanup(const CTransactionRef& ptx, ATMPArgs& args) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { - LOCK(m_pool.cs); - MempoolAcceptResult result = AcceptSingleTransactionInternal(ptx, args); - ClearSubPackageState(); - return result; - } - MempoolAcceptResult AcceptSingleTransactionInternal(const CTransactionRef& ptx, ATMPArgs& args) EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_pool.cs); - - /** - * Multiple transaction acceptance. Transactions may or may not be interdependent, but must not - * conflict with each other, and the transactions cannot already be in the mempool. Parents must - * come before children if any dependencies exist. - */ - PackageMempoolAcceptResult AcceptMultipleTransactionsAndCleanup(const std::vector& txns, ATMPArgs& args) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { - LOCK(m_pool.cs); - PackageMempoolAcceptResult result = AcceptMultipleTransactionsInternal(txns, args); - ClearSubPackageState(); - return result; - } - PackageMempoolAcceptResult AcceptMultipleTransactionsInternal(const std::vector& txns, ATMPArgs& args) EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_pool.cs); - - /** - * Submission of a subpackage. - * If subpackage size == 1, calls AcceptSingleTransaction() with adjusted ATMPArgs to - * enable sibling eviction and creates a PackageMempoolAcceptResult - * wrapping the result. - * - * If subpackage size > 1, calls AcceptMultipleTransactions() with the provided ATMPArgs. - * - * Also cleans up all non-chainstate coins from m_view at the end. - */ - PackageMempoolAcceptResult AcceptSubPackage(const std::vector& subpackage, ATMPArgs& args) - EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_pool.cs); - - /** - * Package (more specific than just multiple transactions) acceptance. Package must be a child - * with all of its unconfirmed parents, and topologically sorted. - */ - PackageMempoolAcceptResult AcceptPackage(const Package& package, ATMPArgs& args) EXCLUSIVE_LOCKS_REQUIRED(cs_main); - -private: - // All the intermediate state that gets passed between the various levels - // of checking a given transaction. - struct Workspace { - explicit Workspace(const CTransactionRef& ptx) : m_ptx(ptx), m_hash(ptx->GetHash()) {} - /** Txids of mempool transactions that this transaction directly conflicts with or may - * replace via sibling eviction. */ - std::set m_conflicts; - /** Iterators to mempool entries that this transaction directly conflicts with or may - * replace via sibling eviction. */ - CTxMemPool::setEntries m_iters_conflicting; - /** All mempool parents of this transaction. */ - std::vector m_parents; - /* Handle to the tx in the changeset */ - CTxMemPool::ChangeSet::TxHandle m_tx_handle; - /** Whether RBF-related data structures (m_conflicts, m_iters_conflicting, - * m_replaced_transactions) include a sibling in addition to txns with conflicting inputs. */ - bool m_sibling_eviction{false}; - - /** Virtual size of the transaction as used by the mempool, calculated using serialized size - * of the transaction and sigops. */ - int64_t m_vsize; - /** Fees paid by this transaction: total input amounts subtracted by total output amounts. */ - CAmount m_base_fees; - /** Base fees + any fee delta set by the user with prioritisetransaction. */ - CAmount m_modified_fees; - - /** If we're doing package validation (i.e. m_package_feerates=true), the "effective" - * package feerate of this transaction is the total fees divided by the total size of - * transactions (which may include its ancestors and/or descendants). */ - CFeeRate m_package_feerate{0}; - - const CTransactionRef& m_ptx; - /** Txid. */ - const Txid& m_hash; - TxValidationState m_state; - /** A temporary cache containing serialized transaction data for signature verification. - * Reused across PolicyScriptChecks and ConsensusScriptChecks. */ - PrecomputedTransactionData m_precomputed_txdata; - }; - - // Run the policy checks on a given transaction, excluding any script checks. - // Looks up inputs, calculates feerate, considers replacement, evaluates - // package limits, etc. As this function can be invoked for "free" by a peer, - // only tests that are fast should be done here (to avoid CPU DoS). - bool PreChecks(ATMPArgs& args, Workspace& ws) EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_pool.cs); - - // Run checks for mempool replace-by-fee, only used in AcceptSingleTransaction. - bool ReplacementChecks(Workspace& ws) EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_pool.cs); - - bool PackageRBFChecks(const std::vector& txns, - std::vector& workspaces, - int64_t total_vsize, - PackageValidationState& package_state) EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_pool.cs); - - // Run the script checks using our policy flags. As this can be slow, we should - // only invoke this on transactions that have otherwise passed policy checks. - bool PolicyScriptChecks(const ATMPArgs& args, Workspace& ws) EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_pool.cs); - - // Re-run the script checks, using consensus flags, and try to cache the - // result in the scriptcache. This should be done after - // PolicyScriptChecks(). This requires that all inputs either be in our - // utxo set or in the mempool. - bool ConsensusScriptChecks(const ATMPArgs& args, Workspace& ws) EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_pool.cs); - - // Try to add the transaction to the mempool, removing any conflicts first. - void FinalizeSubpackage(const ATMPArgs& args) EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_pool.cs); - - // Submit all transactions to the mempool and call ConsensusScriptChecks to add to the script - // cache - should only be called after successful validation of all transactions in the package. - // Does not call LimitMempoolSize(), so mempool max_size_bytes may be temporarily exceeded. - bool SubmitPackage(const ATMPArgs& args, std::vector& workspaces, PackageValidationState& package_state, - std::map& results) - EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_pool.cs); - - // Compare a package's feerate against minimum allowed. - bool CheckFeeRate(size_t package_size, CAmount package_fee, TxValidationState& state) EXCLUSIVE_LOCKS_REQUIRED(::cs_main, m_pool.cs) - { - AssertLockHeld(::cs_main); - AssertLockHeld(m_pool.cs); - CAmount mempoolRejectFee = m_pool.GetMinFee().GetFee(package_size); - if (mempoolRejectFee > 0 && package_fee < mempoolRejectFee) { - return state.Invalid(TxValidationResult::TX_RECONSIDERABLE, "mempool min fee not met", strprintf("%d < %d", package_fee, mempoolRejectFee)); - } - - if (package_fee < m_pool.m_opts.min_relay_feerate.GetFee(package_size)) { - return state.Invalid(TxValidationResult::TX_RECONSIDERABLE, "min relay fee not met", - strprintf("%d < %d", package_fee, m_pool.m_opts.min_relay_feerate.GetFee(package_size))); - } - return true; - } - - ValidationCache& GetValidationCache() - { - return m_active_chainstate.m_chainman.m_validation_cache; - } - -private: - CTxMemPool& m_pool; - - /** Holds a cached view of available coins from the UTXO set, mempool, and artificial temporary coins (to enable package validation). - * The view doesn't track whether a coin previously existed but has now been spent. We detect conflicts in other ways: - * - conflicts within a transaction are checked in CheckTransaction (bad-txns-inputs-duplicate) - * - conflicts within a package are checked in IsWellFormedPackage (conflict-in-package) - * - conflicts with an existing mempool transaction are found in CTxMemPool::GetConflictTx and replacements are allowed - * The temporary coins should persist between individual transaction checks so that package validation is possible, - * but must be cleaned up when we finish validating a subpackage, whether accepted or rejected. The cache must also - * be cleared when mempool contents change (when a changeset is applied or when the mempool trims itself) because it - * can return cached coins that no longer exist in the backend. Use CleanupTemporaryCoins() anytime you are finished - * with a SubPackageState or call LimitMempoolSize(). - */ - CCoinsViewCache m_view; - - // These are the two possible backends for m_view. - /** When m_view is connected to m_viewmempool as its backend, it can pull coins from the mempool and from the UTXO - * set. This is also where temporary coins are stored. */ - CCoinsViewMemPool m_viewmempool; - - Chainstate& m_active_chainstate; - - // Fields below are per *sub*package state and must be reset prior to subsequent - // AcceptSingleTransaction and AcceptMultipleTransactions invocations - struct SubPackageState { - /** Aggregated modified fees of all transactions, used to calculate package feerate. */ - CAmount m_total_modified_fees{0}; - /** Aggregated virtual size of all transactions, used to calculate package feerate. */ - int64_t m_total_vsize{0}; - - // RBF-related members - /** Whether the transaction(s) would replace any mempool transactions and/or evict any siblings. - * If so, RBF rules apply. */ - bool m_rbf{false}; - /** Mempool transactions that were replaced. */ - std::list m_replaced_transactions; - /* Changeset representing adding transactions and removing their conflicts. */ - std::unique_ptr m_changeset; - - /** Total modified fees of mempool transactions being replaced. */ - CAmount m_conflicting_fees{0}; - /** Total size (in virtual bytes) of mempool transactions being replaced. */ - size_t m_conflicting_size{0}; - }; - - struct SubPackageState m_subpackage; - - /** Re-set sub-package state to not leak between evaluations */ - void ClearSubPackageState() EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_pool.cs) - { - m_subpackage = SubPackageState{}; - - // And clean coins while at it - CleanupTemporaryCoins(); - } -}; - -bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws) -{ - AssertLockHeld(cs_main); - AssertLockHeld(m_pool.cs); - const CTransactionRef& ptx = ws.m_ptx; - const CTransaction& tx = *ws.m_ptx; - const Txid& hash = ws.m_hash; - - // Copy/alias what we need out of args - const int64_t nAcceptTime = args.m_accept_time; - const bool bypass_limits = args.m_bypass_limits; - std::vector& coins_to_uncache = args.m_coins_to_uncache; - - // Alias what we need out of ws - TxValidationState& state = ws.m_state; - - if (!CheckTransaction(tx, state)) { - return false; // state filled in by CheckTransaction - } - - // Coinbase is only valid in a block, not as a loose transaction - if (tx.IsCoinBase()) - return state.Invalid(TxValidationResult::TX_CONSENSUS, "coinbase"); - - // Rather not work on nonstandard transactions (unless -testnet/-regtest) - std::string reason; - if (m_pool.m_opts.require_standard && !IsStandardTx(tx, m_pool.m_opts.max_datacarrier_bytes, m_pool.m_opts.permit_bare_multisig, m_pool.m_opts.dust_relay_feerate, reason)) { - return state.Invalid(TxValidationResult::TX_NOT_STANDARD, reason); - } - - // Transactions smaller than 65 non-witness bytes are not relayed to mitigate CVE-2017-12842. - if (::GetSerializeSize(TX_NO_WITNESS(tx)) < MIN_STANDARD_TX_NONWITNESS_SIZE) - return state.Invalid(TxValidationResult::TX_NOT_STANDARD, "tx-size-small"); - - // Only accept nLockTime-using transactions that can be mined in the next - // block; we don't want our mempool filled up with transactions that can't - // be mined yet. - if (!CheckFinalTxAtTip(*Assert(m_active_chainstate.m_chain.Tip()), tx)) { - return state.Invalid(TxValidationResult::TX_PREMATURE_SPEND, "non-final"); - } - - if (m_pool.exists(tx.GetWitnessHash())) { - // Exact transaction already exists in the mempool. - return state.Invalid(TxValidationResult::TX_CONFLICT, "txn-already-in-mempool"); - } else if (m_pool.exists(tx.GetHash())) { - // Transaction with the same non-witness data but different witness (same txid, different - // wtxid) already exists in the mempool. - return state.Invalid(TxValidationResult::TX_CONFLICT, "txn-same-nonwitness-data-in-mempool"); - } - - // Check for conflicts with in-memory transactions - for (const CTxIn &txin : tx.vin) - { - const CTransaction* ptxConflicting = m_pool.GetConflictTx(txin.prevout); - if (ptxConflicting) { - if (!args.m_allow_replacement) { - // Transaction conflicts with a mempool tx, but we're not allowing replacements in this context. - return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "bip125-replacement-disallowed"); - } - ws.m_conflicts.insert(ptxConflicting->GetHash()); - } - } - - m_view.SetBackend(m_viewmempool); - - const CCoinsViewCache& coins_cache = m_active_chainstate.CoinsTip(); - // do all inputs exist? - for (const CTxIn& txin : tx.vin) { - if (!coins_cache.HaveCoinInCache(txin.prevout)) { - coins_to_uncache.push_back(txin.prevout); - } - - // Note: this call may add txin.prevout to the coins cache - // (coins_cache.cacheCoins) by way of FetchCoin(). It should be removed - // later (via coins_to_uncache) if this tx turns out to be invalid. - if (!m_view.HaveCoin(txin.prevout)) { - // Are inputs missing because we already have the tx? - for (size_t out = 0; out < tx.vout.size(); out++) { - // Optimistically just do efficient check of cache for outputs - if (coins_cache.HaveCoinInCache(COutPoint(hash, out))) { - return state.Invalid(TxValidationResult::TX_CONFLICT, "txn-already-known"); - } - } - // Otherwise assume this might be an orphan tx for which we just haven't seen parents yet - return state.Invalid(TxValidationResult::TX_MISSING_INPUTS, "bad-txns-inputs-missingorspent"); - } - } - - // This is const, but calls into `CCoinsViewCache::GetBestBlock()` to refresh - // the cached best block through `m_viewmempool` after caching inputs. - (void)m_view.GetBestBlock(); - - // All required inputs are cached now, so switch m_view to the empty backend. - // This keeps already-fetched cache entries for later checks and prevents new - // backend lookups (which would avoid coins_to_uncache tracking). - m_view.SetBackend(CoinsViewEmpty::Get()); - - assert(m_active_chainstate.m_blockman.LookupBlockIndex(m_view.GetBestBlock()) == m_active_chainstate.m_chain.Tip()); - - // Only accept BIP68 sequence locked transactions that can be mined in the next - // block; we don't want our mempool filled up with transactions that can't - // be mined yet. - // Pass in m_view which has all of the relevant inputs cached. Note that, since m_view's - // backend was removed, it no longer pulls coins from the mempool. - const std::optional lock_points{CalculateLockPointsAtTip(m_active_chainstate.m_chain.Tip(), m_view, tx)}; - if (!lock_points.has_value() || !CheckSequenceLocksAtTip(m_active_chainstate.m_chain.Tip(), *lock_points)) { - return state.Invalid(TxValidationResult::TX_PREMATURE_SPEND, "non-BIP68-final"); - } - - // The mempool holds txs for the next block, so pass height+1 to CheckTxInputs - if (!Consensus::CheckTxInputs(tx, state, m_view, m_active_chainstate.m_chain.Height() + 1, ws.m_base_fees)) { - return false; // state filled in by CheckTxInputs - } - - if (m_pool.m_opts.require_standard) { - state = ValidateInputsStandardness(tx, m_view); - if (state.IsInvalid()) { - return false; - } - } - - // Check for non-standard witnesses. - if (tx.HasWitness() && m_pool.m_opts.require_standard && !IsWitnessStandard(tx, m_view)) { - return state.Invalid(TxValidationResult::TX_WITNESS_MUTATED, "bad-witness-nonstandard"); - } - - int64_t nSigOpsCost = GetTransactionSigOpCost(tx, m_view, STANDARD_SCRIPT_VERIFY_FLAGS); - - // Keep track of transactions that spend a coinbase, which we re-scan - // during reorgs to ensure COINBASE_MATURITY is still met. - bool fSpendsCoinbase = false; - for (const CTxIn &txin : tx.vin) { - const Coin &coin = m_view.AccessCoin(txin.prevout); - if (coin.IsCoinBase()) { - fSpendsCoinbase = true; - break; - } - } - - // Set entry_sequence to 0 when bypass_limits 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 = bypass_limits ? 0 : m_pool.GetSequence(); - if (!m_subpackage.m_changeset) { - m_subpackage.m_changeset = m_pool.GetChangeSet(); - } - ws.m_tx_handle = m_subpackage.m_changeset->StageAddition(ptx, ws.m_base_fees, nAcceptTime, m_active_chainstate.m_chain.Height(), entry_sequence, fSpendsCoinbase, nSigOpsCost, lock_points.value()); - - // ws.m_modified_fees includes any fee deltas from PrioritiseTransaction - ws.m_modified_fees = ws.m_tx_handle->GetModifiedFee(); - - ws.m_vsize = ws.m_tx_handle->GetTxSize(); - - // Enforces 0-fee for dust transactions, no incentive to be mined alone - if (m_pool.m_opts.require_standard) { - if (!PreCheckEphemeralTx(*ptx, m_pool.m_opts.dust_relay_feerate, ws.m_base_fees, ws.m_modified_fees, state)) { - return false; // state filled in by PreCheckEphemeralTx - } - } - - if (nSigOpsCost > MAX_STANDARD_TX_SIGOPS_COST) - return state.Invalid(TxValidationResult::TX_NOT_STANDARD, "bad-txns-too-many-sigops", - strprintf("%d", nSigOpsCost)); - - // No individual transactions are allowed below the mempool min feerate except from disconnected - // blocks and transactions in a package. Package transactions will be checked using package - // feerate later. - if (!bypass_limits && !args.m_package_feerates && !CheckFeeRate(ws.m_vsize, ws.m_modified_fees, state)) return false; - - ws.m_iters_conflicting = m_pool.GetIterSet(ws.m_conflicts); - - ws.m_parents = m_pool.GetParents(*ws.m_tx_handle); - - if (!args.m_bypass_limits) { - // Perform the TRUC checks, using the in-mempool parents. - if (const auto err{SingleTRUCChecks(m_pool, ws.m_ptx, ws.m_parents, ws.m_conflicts, ws.m_vsize)}) { - // Single transaction contexts only. - if (args.m_allow_sibling_eviction && err->second != nullptr) { - // We should only be considering where replacement is considered valid as well. - Assume(args.m_allow_replacement); - // Potential sibling eviction. Add the sibling to our list of mempool conflicts to be - // included in RBF checks. - ws.m_conflicts.insert(err->second->GetHash()); - // Adding the sibling to m_iters_conflicting here means that it doesn't count towards - // RBF Carve Out above. This is correct, since removing to-be-replaced transactions from - // the descendant count is done separately in SingleTRUCChecks for TRUC transactions. - ws.m_iters_conflicting.insert(m_pool.GetIter(err->second->GetHash()).value()); - ws.m_sibling_eviction = true; - // The sibling will be treated as part of the to-be-replaced set in ReplacementChecks. - // Note that we are not checking whether it opts in to replaceability via BIP125 or TRUC - // (which is normally done in PreChecks). However, the only way a TRUC transaction can - // have a non-TRUC and non-BIP125 descendant is due to a reorg. - } else { - return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "TRUC-violation", err->first); - } - } - } - - // We want to detect conflicts in any tx in a package to trigger package RBF logic - m_subpackage.m_rbf |= !ws.m_conflicts.empty(); - return true; -} - -bool MemPoolAccept::ReplacementChecks(Workspace& ws) -{ - AssertLockHeld(cs_main); - AssertLockHeld(m_pool.cs); - - const CTransaction& tx = *ws.m_ptx; - const Txid& hash = ws.m_hash; - TxValidationState& state = ws.m_state; - - CFeeRate newFeeRate(ws.m_modified_fees, ws.m_vsize); - - CTxMemPool::setEntries all_conflicts; - - // Calculate all conflicting entries and enforce Rule #5. - if (const auto err_string{GetEntriesForConflicts(tx, m_pool, ws.m_iters_conflicting, all_conflicts)}) { - return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, - strprintf("too many potential replacements%s", ws.m_sibling_eviction ? " (including sibling eviction)" : ""), *err_string); - } - - // Check if it's economically rational to mine this transaction rather than the ones it - // replaces and pays for its own relay fees. Enforce Rules #3 and #4. - for (CTxMemPool::txiter it : all_conflicts) { - m_subpackage.m_conflicting_fees += it->GetModifiedFee(); - m_subpackage.m_conflicting_size += it->GetTxSize(); - } - - if (const auto err_string{PaysForRBF(m_subpackage.m_conflicting_fees, ws.m_modified_fees, ws.m_vsize, - m_pool.m_opts.incremental_relay_feerate, hash)}) { - // Result may change in a package context - return state.Invalid(TxValidationResult::TX_RECONSIDERABLE, - strprintf("insufficient fee%s", ws.m_sibling_eviction ? " (including sibling eviction)" : ""), *err_string); - } - - // Add all the to-be-removed transactions to the changeset. - for (auto it : all_conflicts) { - m_subpackage.m_changeset->StageRemoval(it); - } - - // Run cluster size limit checks and fail if we exceed them. - if (!m_subpackage.m_changeset->CheckMemPoolPolicyLimits()) { - return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "too-large-cluster", ""); - } - - if (const auto err_string{ImprovesFeerateDiagram(*m_subpackage.m_changeset)}) { - // We checked above for the cluster size limits being respected, so a - // failure here can only be due to an insufficient fee. - Assume(err_string->first == DiagramCheckError::FAILURE); - return state.Invalid(TxValidationResult::TX_RECONSIDERABLE, "replacement-failed", err_string->second); - } - - return true; -} - -bool MemPoolAccept::PackageRBFChecks(const std::vector& txns, - std::vector& workspaces, - const int64_t total_vsize, - PackageValidationState& package_state) -{ - AssertLockHeld(cs_main); - AssertLockHeld(m_pool.cs); - - assert(std::all_of(txns.cbegin(), txns.cend(), [this](const auto& tx) - { return !m_pool.exists(tx->GetHash());})); - - assert(txns.size() == workspaces.size()); - - // We're in package RBF context; replacement proposal must be size 2 - if (workspaces.size() != 2 || !Assume(IsChildWithParents(txns))) { - return package_state.Invalid(PackageValidationResult::PCKG_POLICY, "package RBF failed: package must be 1-parent-1-child"); - } - - // If the package has in-mempool parents, we won't consider a package RBF - // since it would result in a cluster larger than 2. - // N.B. To relax this constraint we will need to revisit how CCoinsViewMemPool::PackageAddTransaction - // is being used inside AcceptMultipleTransactions to track available inputs while processing a package. - // Specifically we would need to check that the ancestors of the new - // transactions don't intersect with the set of transactions to be removed - // due to RBF, which is not checked at all in the package acceptance - // context. - for (const auto& ws : workspaces) { - if (!ws.m_parents.empty()) { - return package_state.Invalid(PackageValidationResult::PCKG_POLICY, "package RBF failed: new transaction cannot have mempool ancestors"); - } - } - - // Aggregate all conflicts into one set. - CTxMemPool::setEntries direct_conflict_iters; - for (Workspace& ws : workspaces) { - // Aggregate all conflicts into one set. - direct_conflict_iters.merge(ws.m_iters_conflicting); - } - - const auto& parent_ws = workspaces[0]; - const auto& child_ws = workspaces[1]; - - // Don't consider replacements that would cause us to remove a large number of mempool entries. - // This limit is not increased in a package RBF. Use the aggregate number of transactions. - CTxMemPool::setEntries all_conflicts; - if (const auto err_string{GetEntriesForConflicts(*child_ws.m_ptx, m_pool, direct_conflict_iters, - all_conflicts)}) { - return package_state.Invalid(PackageValidationResult::PCKG_POLICY, - "package RBF failed: too many potential replacements", *err_string); - } - - for (CTxMemPool::txiter it : all_conflicts) { - m_subpackage.m_changeset->StageRemoval(it); - m_subpackage.m_conflicting_fees += it->GetModifiedFee(); - m_subpackage.m_conflicting_size += it->GetTxSize(); - } - - // Use the child as the transaction for attributing errors to. - const Txid& child_hash = child_ws.m_ptx->GetHash(); - if (const auto err_string{PaysForRBF(/*original_fees=*/m_subpackage.m_conflicting_fees, - /*replacement_fees=*/m_subpackage.m_total_modified_fees, - /*replacement_vsize=*/m_subpackage.m_total_vsize, - m_pool.m_opts.incremental_relay_feerate, child_hash)}) { - return package_state.Invalid(PackageValidationResult::PCKG_POLICY, - "package RBF failed: insufficient anti-DoS fees", *err_string); - } - - // Ensure this two transaction package is a "chunk" on its own; we don't want the child - // to be only paying anti-DoS fees - const CFeeRate parent_feerate(parent_ws.m_modified_fees, parent_ws.m_vsize); - const CFeeRate package_feerate(m_subpackage.m_total_modified_fees, m_subpackage.m_total_vsize); - if (package_feerate <= parent_feerate) { - return package_state.Invalid(PackageValidationResult::PCKG_POLICY, - "package RBF failed: package feerate is less than or equal to parent feerate", - strprintf("package feerate %s <= parent feerate is %s", package_feerate.ToString(), parent_feerate.ToString())); - } - - // Run cluster size limit checks and fail if we exceed them. - if (!m_subpackage.m_changeset->CheckMemPoolPolicyLimits()) { - return package_state.Invalid(PackageValidationResult::PCKG_POLICY, "too-large-cluster", ""); - } - - // Check if it's economically rational to mine this package rather than the ones it replaces. - if (const auto err_tup{ImprovesFeerateDiagram(*m_subpackage.m_changeset)}) { - Assume(err_tup->first == DiagramCheckError::FAILURE); - return package_state.Invalid(PackageValidationResult::PCKG_POLICY, - "package RBF failed: " + err_tup.value().second, ""); - } - - LogDebug(BCLog::TXPACKAGES, "package RBF checks passed: parent %s (wtxid=%s), child %s (wtxid=%s), package hash (%s)\n", - txns.front()->GetHash().ToString(), txns.front()->GetWitnessHash().ToString(), - txns.back()->GetHash().ToString(), txns.back()->GetWitnessHash().ToString(), - GetPackageHash(txns).ToString()); - - - return true; -} - -bool MemPoolAccept::PolicyScriptChecks(const ATMPArgs& args, Workspace& ws) -{ - AssertLockHeld(cs_main); - AssertLockHeld(m_pool.cs); - const CTransaction& tx = *ws.m_ptx; - TxValidationState& state = ws.m_state; - - constexpr script_verify_flags scriptVerifyFlags = STANDARD_SCRIPT_VERIFY_FLAGS; - - // Check input scripts and signatures. - // This is done last to help prevent CPU exhaustion denial-of-service attacks. - if (!CheckInputScripts(tx, state, m_view, scriptVerifyFlags, true, false, ws.m_precomputed_txdata, GetValidationCache())) { - // Detect a failure due to a missing witness so that p2p code can handle rejection caching appropriately. - if (!tx.HasWitness() && SpendsNonAnchorWitnessProg(tx, m_view)) { - state.Invalid(TxValidationResult::TX_WITNESS_STRIPPED, - state.GetRejectReason(), state.GetDebugMessage()); - } - return false; // state filled in by CheckInputScripts - } - - return true; -} - -bool MemPoolAccept::ConsensusScriptChecks(const ATMPArgs& args, Workspace& ws) -{ - AssertLockHeld(cs_main); - AssertLockHeld(m_pool.cs); - const CTransaction& tx = *ws.m_ptx; - const Txid& hash = ws.m_hash; - TxValidationState& state = ws.m_state; - - // Check again against the current block tip's script verification - // flags to cache our script execution flags. This is, of course, - // useless if the next block has different script flags from the - // previous one, but because the cache tracks script flags for us it - // will auto-invalidate and we'll just have a few blocks of extra - // misses on soft-fork activation. - // - // This is also useful in case of bugs in the standard flags that cause - // transactions to pass as valid when they're actually invalid. For - // instance the STRICTENC flag was incorrectly allowing certain - // CHECKSIG NOT scripts to pass, even though they were invalid. - // - // There is a similar check in CreateNewBlock() to prevent creating - // invalid blocks (using TestBlockValidity), however allowing such - // transactions into the mempool can be exploited as a DoS attack. - script_verify_flags currentBlockScriptVerifyFlags{GetBlockScriptFlags(*m_active_chainstate.m_chain.Tip(), m_active_chainstate.m_chainman)}; - if (!CheckInputsFromMempoolAndCache(tx, state, m_view, m_pool, currentBlockScriptVerifyFlags, - ws.m_precomputed_txdata, m_active_chainstate.CoinsTip(), GetValidationCache())) { - LogError("BUG! PLEASE REPORT THIS! CheckInputScripts failed against latest-block but not STANDARD flags %s, %s", hash.ToString(), state.ToString()); - return Assume(false); - } - - return true; -} - -void MemPoolAccept::FinalizeSubpackage(const ATMPArgs& args) -{ - AssertLockHeld(cs_main); - AssertLockHeld(m_pool.cs); - - if (!m_subpackage.m_changeset->GetRemovals().empty()) Assume(args.m_allow_replacement); - // Remove conflicting transactions from the mempool - for (CTxMemPool::txiter it : m_subpackage.m_changeset->GetRemovals()) - { - std::string log_string = strprintf("replacing mempool tx %s (wtxid=%s, fees=%s, vsize=%s). ", - it->GetTx().GetHash().ToString(), - it->GetTx().GetWitnessHash().ToString(), - it->GetFee(), - it->GetTxSize()); - FeeFrac feerate{m_subpackage.m_total_modified_fees, int32_t(m_subpackage.m_total_vsize)}; - uint256 tx_or_package_hash{}; - const bool replaced_with_tx{m_subpackage.m_changeset->GetTxCount() == 1}; - if (replaced_with_tx) { - const CTransaction& tx = m_subpackage.m_changeset->GetAddedTxn(0); - tx_or_package_hash = tx.GetHash().ToUint256(); - log_string += strprintf("New tx %s (wtxid=%s, fees=%s, vsize=%s)", - tx.GetHash().ToString(), - tx.GetWitnessHash().ToString(), - feerate.fee, - feerate.size); - } else { - tx_or_package_hash = GetPackageHash(m_subpackage.m_changeset->GetAddedTxns()); - log_string += strprintf("New package %s with %lu txs, fees=%s, vsize=%s", - tx_or_package_hash.ToString(), - m_subpackage.m_changeset->GetTxCount(), - feerate.fee, - feerate.size); - - } - LogDebug(BCLog::MEMPOOL, "%s\n", log_string); - TRACEPOINT(mempool, replaced, - it->GetTx().GetHash().data(), - it->GetTxSize(), - it->GetFee(), - std::chrono::duration_cast>(it->GetTime()).count(), - tx_or_package_hash.data(), - feerate.size, - feerate.fee, - replaced_with_tx - ); - m_subpackage.m_replaced_transactions.push_back(it->GetSharedTx()); - } - m_subpackage.m_changeset->Apply(); - m_subpackage.m_changeset.reset(); -} - -bool MemPoolAccept::SubmitPackage(const ATMPArgs& args, std::vector& workspaces, - PackageValidationState& package_state, - std::map& results) -{ - AssertLockHeld(cs_main); - AssertLockHeld(m_pool.cs); - // Sanity check: none of the transactions should be in the mempool, and none of the transactions - // should have a same-txid-different-witness equivalent in the mempool. - assert(std::all_of(workspaces.cbegin(), workspaces.cend(), [this](const auto& ws) { return !m_pool.exists(ws.m_ptx->GetHash()); })); - - bool all_submitted = true; - FinalizeSubpackage(args); - // ConsensusScriptChecks adds to the script cache and is therefore consensus-critical; - // CheckInputsFromMempoolAndCache asserts that transactions only spend coins available from the - // mempool or UTXO set. Submit each transaction to the mempool immediately after calling - // ConsensusScriptChecks to make the outputs available for subsequent transactions. - for (Workspace& ws : workspaces) { - if (!ConsensusScriptChecks(args, ws)) { - results.emplace(ws.m_ptx->GetWitnessHash(), MempoolAcceptResult::Failure(ws.m_state)); - // Since PolicyScriptChecks() passed, this should never fail. - Assume(false); - all_submitted = false; - package_state.Invalid(PackageValidationResult::PCKG_MEMPOOL_ERROR, - strprintf("BUG! PolicyScriptChecks succeeded but ConsensusScriptChecks failed: %s", - ws.m_ptx->GetHash().ToString())); - } - // Remove first failing tx and all subsequent in package - if (!all_submitted) { - if (!m_subpackage.m_changeset) m_subpackage.m_changeset = m_pool.GetChangeSet(); - m_subpackage.m_changeset->StageRemoval(m_pool.GetIter(ws.m_ptx->GetHash()).value()); - } - } - if (!all_submitted) { - Assume(m_subpackage.m_changeset); - // This code should be unreachable; it's here as belt-and-suspenders - // to try to ensure we have no consensus-invalid transactions in the - // mempool. - m_subpackage.m_changeset->Apply(); - m_subpackage.m_changeset.reset(); - return false; - } - - std::vector all_package_wtxids; - all_package_wtxids.reserve(workspaces.size()); - std::transform(workspaces.cbegin(), workspaces.cend(), std::back_inserter(all_package_wtxids), - [](const auto& ws) { return ws.m_ptx->GetWitnessHash(); }); - - if (!m_subpackage.m_replaced_transactions.empty()) { - LogDebug(BCLog::MEMPOOL, "replaced %u mempool transactions with %u new one(s) for %s additional fees, %d delta bytes\n", - m_subpackage.m_replaced_transactions.size(), workspaces.size(), - m_subpackage.m_total_modified_fees - m_subpackage.m_conflicting_fees, - m_subpackage.m_total_vsize - static_cast(m_subpackage.m_conflicting_size)); - } - - // Add successful results. The returned results may change later if LimitMempoolSize() evicts them. - for (Workspace& ws : workspaces) { - auto iter = m_pool.GetIter(ws.m_ptx->GetHash()); - Assume(iter.has_value()); - const auto effective_feerate = args.m_package_feerates ? ws.m_package_feerate : - CFeeRate{ws.m_modified_fees, static_cast(ws.m_vsize)}; - const auto effective_feerate_wtxids = args.m_package_feerates ? all_package_wtxids : - std::vector{ws.m_ptx->GetWitnessHash()}; - results.emplace(ws.m_ptx->GetWitnessHash(), - MempoolAcceptResult::Success(std::move(m_subpackage.m_replaced_transactions), ws.m_vsize, - ws.m_base_fees, effective_feerate, effective_feerate_wtxids)); - if (!m_pool.m_opts.signals) continue; - const CTransaction& tx = *ws.m_ptx; - const auto tx_info = NewMempoolTransactionInfo(ws.m_ptx, ws.m_base_fees, - ws.m_vsize, (*iter)->GetHeight(), - args.m_bypass_limits, args.m_package_submission, - IsCurrentForFeeEstimation(m_active_chainstate), - m_pool.HasNoInputsOf(tx)); - m_pool.m_opts.signals->TransactionAddedToMempool(tx_info, m_pool.GetAndIncrementSequence()); - } - return all_submitted; -} - -MempoolAcceptResult MemPoolAccept::AcceptSingleTransactionInternal(const CTransactionRef& ptx, ATMPArgs& args) -{ - AssertLockHeld(cs_main); - AssertLockHeld(m_pool.cs); - - Workspace ws(ptx); - const std::vector single_wtxid{ws.m_ptx->GetWitnessHash()}; - - if (!PreChecks(args, ws)) { - if (ws.m_state.GetResult() == TxValidationResult::TX_RECONSIDERABLE) { - // Failed for fee reasons. Provide the effective feerate and which tx was included. - return MempoolAcceptResult::FeeFailure(ws.m_state, CFeeRate(ws.m_modified_fees, ws.m_vsize), single_wtxid); - } - return MempoolAcceptResult::Failure(ws.m_state); - } - - if (m_subpackage.m_rbf && !ReplacementChecks(ws)) { - if (ws.m_state.GetResult() == TxValidationResult::TX_RECONSIDERABLE) { - // Failed for incentives-based fee reasons. Provide the effective feerate and which tx was included. - return MempoolAcceptResult::FeeFailure(ws.m_state, CFeeRate(ws.m_modified_fees, ws.m_vsize), single_wtxid); - } - return MempoolAcceptResult::Failure(ws.m_state); - } - - // Check if the transaction would exceed the cluster size limit. - if (!m_subpackage.m_changeset->CheckMemPoolPolicyLimits()) { - ws.m_state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "too-large-cluster", ""); - return MempoolAcceptResult::Failure(ws.m_state); - } - - // Now that we've verified the cluster limit is respected, we can perform - // calculations involving the full ancestors of the tx. - if (ws.m_conflicts.size()) { - auto ancestors = m_subpackage.m_changeset->CalculateMemPoolAncestors(ws.m_tx_handle); - - // A transaction that spends outputs that would be replaced by it is invalid. Now - // that we have the set of all ancestors we can detect this - // pathological case by making sure ws.m_conflicts and this tx's ancestors don't - // intersect. - if (const auto err_string{EntriesAndTxidsDisjoint(ancestors, ws.m_conflicts, ptx->GetHash())}) { - // We classify this as a consensus error because a transaction depending on something it - // conflicts with would be inconsistent. - ws.m_state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-txns-spends-conflicting-tx", *err_string); - return MempoolAcceptResult::Failure(ws.m_state); - } - } - - m_subpackage.m_total_vsize = ws.m_vsize; - m_subpackage.m_total_modified_fees = ws.m_modified_fees; - - // Individual modified feerate exceeded caller-defined max; abort - if (args.m_client_maxfeerate && CFeeRate(ws.m_modified_fees, ws.m_vsize) > args.m_client_maxfeerate.value()) { - ws.m_state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "max feerate exceeded", ""); - return MempoolAcceptResult::Failure(ws.m_state); - } - - if (!args.m_bypass_limits && m_pool.m_opts.require_standard) { - Wtxid dummy_wtxid; - if (!CheckEphemeralSpends(/*package=*/{ptx}, m_pool.m_opts.dust_relay_feerate, m_pool, ws.m_state, dummy_wtxid)) { - return MempoolAcceptResult::Failure(ws.m_state); - } - } - - // Perform the inexpensive checks first and avoid hashing and signature verification unless - // those checks pass, to mitigate CPU exhaustion denial-of-service attacks. - if (!PolicyScriptChecks(args, ws)) return MempoolAcceptResult::Failure(ws.m_state); - - if (!ConsensusScriptChecks(args, ws)) return MempoolAcceptResult::Failure(ws.m_state); - - const CFeeRate effective_feerate{ws.m_modified_fees, static_cast(ws.m_vsize)}; - // Tx was accepted, but not added - if (args.m_test_accept) { - return MempoolAcceptResult::Success(std::move(m_subpackage.m_replaced_transactions), ws.m_vsize, - ws.m_base_fees, effective_feerate, single_wtxid); - } - - FinalizeSubpackage(args); - - // Limit the mempool, if appropriate. - if (!args.m_package_submission && !args.m_bypass_limits) { - LimitMempoolSize(m_pool, m_active_chainstate.CoinsTip()); - // If mempool contents change, then the m_view cache is dirty. Given this isn't a package - // submission, we won't be using the cache anymore, but clear it anyway for clarity. - CleanupTemporaryCoins(); - - if (!m_pool.exists(ws.m_hash)) { - // The tx no longer meets our (new) mempool minimum feerate but could be reconsidered in a package. - ws.m_state.Invalid(TxValidationResult::TX_RECONSIDERABLE, "mempool full"); - return MempoolAcceptResult::FeeFailure(ws.m_state, CFeeRate(ws.m_modified_fees, ws.m_vsize), {ws.m_ptx->GetWitnessHash()}); - } - } - - if (m_pool.m_opts.signals) { - const CTransaction& tx = *ws.m_ptx; - auto iter = m_pool.GetIter(tx.GetHash()); - Assume(iter.has_value()); - const auto tx_info = NewMempoolTransactionInfo(ws.m_ptx, ws.m_base_fees, - ws.m_vsize, (*iter)->GetHeight(), - args.m_bypass_limits, args.m_package_submission, - IsCurrentForFeeEstimation(m_active_chainstate), - m_pool.HasNoInputsOf(tx)); - m_pool.m_opts.signals->TransactionAddedToMempool(tx_info, m_pool.GetAndIncrementSequence()); - } - - if (!m_subpackage.m_replaced_transactions.empty()) { - LogDebug(BCLog::MEMPOOL, "replaced %u mempool transactions with 1 new transaction for %s additional fees, %d delta bytes\n", - m_subpackage.m_replaced_transactions.size(), - ws.m_modified_fees - m_subpackage.m_conflicting_fees, - ws.m_vsize - static_cast(m_subpackage.m_conflicting_size)); - } - - return MempoolAcceptResult::Success(std::move(m_subpackage.m_replaced_transactions), ws.m_vsize, ws.m_base_fees, - effective_feerate, single_wtxid); -} - -PackageMempoolAcceptResult MemPoolAccept::AcceptMultipleTransactionsInternal(const std::vector& txns, ATMPArgs& args) -{ - AssertLockHeld(cs_main); - AssertLockHeld(m_pool.cs); - - // These context-free package limits can be done before taking the mempool lock. - PackageValidationState package_state; - if (!IsWellFormedPackage(txns, package_state)) return PackageMempoolAcceptResult(package_state, {}); - - std::vector workspaces{}; - workspaces.reserve(txns.size()); - std::transform(txns.cbegin(), txns.cend(), std::back_inserter(workspaces), - [](const auto& tx) { return Workspace(tx); }); - std::map results; - - // Do all PreChecks first and fail fast to avoid running expensive script checks when unnecessary. - for (Workspace& ws : workspaces) { - if (!PreChecks(args, ws)) { - package_state.Invalid(PackageValidationResult::PCKG_TX, "transaction failed"); - // Exit early to avoid doing pointless work. Update the failed tx result; the rest are unfinished. - results.emplace(ws.m_ptx->GetWitnessHash(), MempoolAcceptResult::Failure(ws.m_state)); - return PackageMempoolAcceptResult(package_state, std::move(results)); - } - - // Individual modified feerate exceeded caller-defined max; abort - // N.B. this doesn't take into account CPFPs. Chunk-aware validation may be more robust. - if (args.m_client_maxfeerate && CFeeRate(ws.m_modified_fees, ws.m_vsize) > args.m_client_maxfeerate.value()) { - // Need to set failure here both individually and at package level - ws.m_state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "max feerate exceeded", ""); - package_state.Invalid(PackageValidationResult::PCKG_TX, "transaction failed"); - // Exit early to avoid doing pointless work. Update the failed tx result; the rest are unfinished. - results.emplace(ws.m_ptx->GetWitnessHash(), MempoolAcceptResult::Failure(ws.m_state)); - return PackageMempoolAcceptResult(package_state, std::move(results)); - } - - // Make the coins created by this transaction available for subsequent transactions in the - // package to spend. If there are no conflicts within the package, no transaction can spend a coin - // needed by another transaction in the package. We also need to make sure that no package - // tx replaces (or replaces the ancestor of) the parent of another package tx. As long as we - // check these two things, we don't need to track the coins spent. - // If a package tx conflicts with a mempool tx, PackageRBFChecks() ensures later that any package RBF attempt - // has *no* in-mempool ancestors, so we don't have to worry about subsequent transactions in - // same package spending the same in-mempool outpoints. This needs to be revisited for general - // package RBF. - m_viewmempool.PackageAddTransaction(ws.m_ptx); - } - - // At this point we have all in-mempool parents, and we know every transaction's vsize. - // Run the TRUC checks on the package. - for (Workspace& ws : workspaces) { - if (auto err{PackageTRUCChecks(m_pool, ws.m_ptx, ws.m_vsize, txns, ws.m_parents)}) { - package_state.Invalid(PackageValidationResult::PCKG_POLICY, "TRUC-violation", err.value()); - return PackageMempoolAcceptResult(package_state, {}); - } - } - - // Transactions must meet two minimum feerates: the mempool minimum fee and min relay fee. - // For transactions consisting of exactly one child and its parents, it suffices to use the - // package feerate (total modified fees / total virtual size) to check this requirement. - // Note that this is an aggregate feerate; this function has not checked that there are transactions - // too low feerate to pay for themselves, or that the child transactions are higher feerate than - // their parents. Using aggregate feerate may allow "parents pay for child" behavior and permit - // a child that is below mempool minimum feerate. To avoid these behaviors, callers of - // AcceptMultipleTransactions need to restrict txns topology (e.g. to ancestor sets) and check - // the feerates of individuals and subsets. - m_subpackage.m_total_vsize = std::accumulate(workspaces.cbegin(), workspaces.cend(), int64_t{0}, - [](int64_t sum, auto& ws) { return sum + ws.m_vsize; }); - m_subpackage.m_total_modified_fees = std::accumulate(workspaces.cbegin(), workspaces.cend(), CAmount{0}, - [](CAmount sum, auto& ws) { return sum + ws.m_modified_fees; }); - const CFeeRate package_feerate(m_subpackage.m_total_modified_fees, m_subpackage.m_total_vsize); - std::vector all_package_wtxids; - all_package_wtxids.reserve(workspaces.size()); - std::transform(workspaces.cbegin(), workspaces.cend(), std::back_inserter(all_package_wtxids), - [](const auto& ws) { return ws.m_ptx->GetWitnessHash(); }); - TxValidationState placeholder_state; - if (args.m_package_feerates && - !CheckFeeRate(m_subpackage.m_total_vsize, m_subpackage.m_total_modified_fees, placeholder_state)) { - package_state.Invalid(PackageValidationResult::PCKG_TX, "transaction failed"); - return PackageMempoolAcceptResult(package_state, {{workspaces.back().m_ptx->GetWitnessHash(), - MempoolAcceptResult::FeeFailure(placeholder_state, CFeeRate(m_subpackage.m_total_modified_fees, m_subpackage.m_total_vsize), all_package_wtxids)}}); - } - - // Apply package mempool RBF checks. - if (m_subpackage.m_rbf && !PackageRBFChecks(txns, workspaces, m_subpackage.m_total_vsize, package_state)) { - return PackageMempoolAcceptResult(package_state, std::move(results)); - } - - // Check if the transactions would exceed the cluster size limit. - if (!m_subpackage.m_changeset->CheckMemPoolPolicyLimits()) { - package_state.Invalid(PackageValidationResult::PCKG_POLICY, "too-large-cluster", ""); - return PackageMempoolAcceptResult(package_state, std::move(results)); - } - - // Now that we've bounded the resulting possible ancestry count, check package for dust spends - if (m_pool.m_opts.require_standard) { - TxValidationState child_state; - Wtxid child_wtxid; - if (!CheckEphemeralSpends(txns, m_pool.m_opts.dust_relay_feerate, m_pool, child_state, child_wtxid)) { - package_state.Invalid(PackageValidationResult::PCKG_TX, "unspent-dust"); - results.emplace(child_wtxid, MempoolAcceptResult::Failure(child_state)); - return PackageMempoolAcceptResult(package_state, std::move(results)); - } - } - - for (Workspace& ws : workspaces) { - ws.m_package_feerate = package_feerate; - if (!PolicyScriptChecks(args, ws)) { - // Exit early to avoid doing pointless work. Update the failed tx result; the rest are unfinished. - package_state.Invalid(PackageValidationResult::PCKG_TX, "transaction failed"); - results.emplace(ws.m_ptx->GetWitnessHash(), MempoolAcceptResult::Failure(ws.m_state)); - return PackageMempoolAcceptResult(package_state, std::move(results)); - } - if (args.m_test_accept) { - const auto effective_feerate = args.m_package_feerates ? ws.m_package_feerate : - CFeeRate{ws.m_modified_fees, static_cast(ws.m_vsize)}; - const auto effective_feerate_wtxids = args.m_package_feerates ? all_package_wtxids : - std::vector{ws.m_ptx->GetWitnessHash()}; - results.emplace(ws.m_ptx->GetWitnessHash(), - MempoolAcceptResult::Success(std::move(m_subpackage.m_replaced_transactions), - ws.m_vsize, ws.m_base_fees, effective_feerate, - effective_feerate_wtxids)); - } - } - - if (args.m_test_accept) return PackageMempoolAcceptResult(package_state, std::move(results)); - - if (!SubmitPackage(args, workspaces, package_state, results)) { - // PackageValidationState filled in by SubmitPackage(). - return PackageMempoolAcceptResult(package_state, std::move(results)); - } - - return PackageMempoolAcceptResult(package_state, std::move(results)); -} - -void MemPoolAccept::CleanupTemporaryCoins() -{ - // There are 3 kinds of coins in m_view: - // (1) Temporary coins from the transactions in subpackage, constructed by m_viewmempool. - // (2) Mempool coins from transactions in the mempool, constructed by m_viewmempool. - // (3) Confirmed coins fetched from our current UTXO set. - // - // (1) Temporary coins need to be removed, regardless of whether the transaction was submitted. - // If the transaction was submitted to the mempool, m_viewmempool will be able to fetch them from - // there. If it wasn't submitted to mempool, it is incorrect to keep them - future calls may try - // to spend those coins that don't actually exist. - // (2) Mempool coins also need to be removed. If the mempool contents have changed as a result - // of submitting or replacing transactions, coins previously fetched from mempool may now be - // spent or nonexistent. Those coins need to be deleted from m_view. - // (3) Confirmed coins don't need to be removed. The chainstate has not changed (we are - // holding cs_main and no blocks have been processed) so the confirmed tx cannot disappear like - // a mempool tx can. The coin may now be spent after we submitted a tx to mempool, but - // we have already checked that the package does not have 2 transactions spending the same coin - // and we check whether a mempool transaction spends conflicting coins (CTxMemPool::GetConflictTx). - // Keeping them in m_view is an optimization to not re-fetch confirmed coins if we later look up - // inputs for this transaction again. - for (const auto& outpoint : m_viewmempool.GetNonBaseCoins()) { - // In addition to resetting m_viewmempool, we also need to manually delete these coins from - // m_view because it caches copies of the coins it fetched from m_viewmempool previously. - m_view.Uncache(outpoint); - } - // This deletes the temporary and mempool coins. - m_viewmempool.Reset(); -} - -PackageMempoolAcceptResult MemPoolAccept::AcceptSubPackage(const std::vector& subpackage, ATMPArgs& args) -{ - AssertLockHeld(::cs_main); - AssertLockHeld(m_pool.cs); - auto result = [&]() EXCLUSIVE_LOCKS_REQUIRED(::cs_main, m_pool.cs) { - if (subpackage.size() > 1) { - return AcceptMultipleTransactionsInternal(subpackage, args); - } - const auto& tx = subpackage.front(); - ATMPArgs single_args = ATMPArgs::SingleInPackageAccept(args); - const auto single_res = AcceptSingleTransactionInternal(tx, single_args); - PackageValidationState package_state_wrapped; - if (single_res.m_result_type != MempoolAcceptResult::ResultType::VALID) { - package_state_wrapped.Invalid(PackageValidationResult::PCKG_TX, "transaction failed"); - } - return PackageMempoolAcceptResult(package_state_wrapped, {{tx->GetWitnessHash(), single_res}}); - }(); - - // Clean up m_view and m_viewmempool so that other subpackage evaluations don't have access to - // coins they shouldn't. Keep some coins in order to minimize re-fetching coins from the UTXO set. - // Clean up package feerate and rbf calculations - ClearSubPackageState(); - - return result; -} - -PackageMempoolAcceptResult MemPoolAccept::AcceptPackage(const Package& package, ATMPArgs& args) -{ - Assert(!package.empty()); - AssertLockHeld(cs_main); - // Used if returning a PackageMempoolAcceptResult directly from this function. - PackageValidationState package_state_quit_early; - - // There are two topologies we are able to handle through this function: - // (1) A single transaction - // (2) A child-with-parents package. - // Check that the package is well-formed. If it isn't, we won't try to validate any of the - // transactions and thus won't return any MempoolAcceptResults, just a package-wide error. - - // Context-free package checks. - if (!IsWellFormedPackage(package, package_state_quit_early)) { - return PackageMempoolAcceptResult(package_state_quit_early, {}); - } - - if (package.size() > 1 && !IsChildWithParents(package)) { - // All transactions in the package must be a parent of the last transaction. This is just an - // opportunity for us to fail fast on a context-free check without taking the mempool lock. - package_state_quit_early.Invalid(PackageValidationResult::PCKG_POLICY, "package-not-child-with-parents"); - return PackageMempoolAcceptResult(package_state_quit_early, {}); - } - - LOCK(m_pool.cs); - // Stores results from which we will create the returned PackageMempoolAcceptResult. - // A result may be changed if a mempool transaction is evicted later due to LimitMempoolSize(). - std::map results_final; - // Results from individual validation which will be returned if no other result is available for - // this transaction. "Nonfinal" because if a transaction fails by itself but succeeds later - // (i.e. when evaluated with a fee-bumping child), the result in this map may be discarded. - std::map individual_results_nonfinal; - // Tracks whether we think package submission could result in successful entry to the mempool - bool quit_early{false}; - std::vector txns_package_eval; - for (const auto& tx : package) { - const auto& wtxid = tx->GetWitnessHash(); - const auto& txid = tx->GetHash(); - // There are 3 possibilities: already in mempool, same-txid-diff-wtxid already in mempool, - // or not in mempool. An already confirmed tx is treated as one not in mempool, because all - // we know is that the inputs aren't available. - if (m_pool.exists(wtxid)) { - // Exact transaction already exists in the mempool. - // Node operators are free to set their mempool policies however they please, nodes may receive - // transactions in different orders, and malicious counterparties may try to take advantage of - // policy differences to pin or delay propagation of transactions. As such, it's possible for - // some package transaction(s) to already be in the mempool, and we don't want to reject the - // entire package in that case (as that could be a censorship vector). De-duplicate the - // transactions that are already in the mempool, and only call AcceptMultipleTransactions() with - // the new transactions. This ensures we don't double-count transaction counts and sizes when - // checking ancestor/descendant limits, or double-count transaction fees for fee-related policy. - const auto& entry{*Assert(m_pool.GetEntry(txid))}; - results_final.emplace(wtxid, MempoolAcceptResult::MempoolTx(entry.GetTxSize(), entry.GetFee())); - } else if (m_pool.exists(txid)) { - // Transaction with the same non-witness data but different witness (same txid, - // different wtxid) already exists in the mempool. - // - // We don't allow replacement transactions right now, so just swap the package - // transaction for the mempool one. Note that we are ignoring the validity of the - // package transaction passed in. - // TODO: allow witness replacement in packages. - const auto& entry{*Assert(m_pool.GetEntry(txid))}; - // Provide the wtxid of the mempool tx so that the caller can look it up in the mempool. - results_final.emplace(wtxid, MempoolAcceptResult::MempoolTxDifferentWitness(entry.GetTx().GetWitnessHash())); - } else { - // Transaction does not already exist in the mempool. - // Try submitting the transaction on its own. - const auto single_package_res = AcceptSubPackage({tx}, args); - const auto& single_res = single_package_res.m_tx_results.at(wtxid); - if (single_res.m_result_type == MempoolAcceptResult::ResultType::VALID) { - // The transaction succeeded on its own and is now in the mempool. Don't include it - // in package validation, because its fees should only be "used" once. - assert(m_pool.exists(wtxid)); - results_final.emplace(wtxid, single_res); - } else if (package.size() == 1 || // If there is only one transaction, no need to retry it "as a package" - (single_res.m_state.GetResult() != TxValidationResult::TX_RECONSIDERABLE && - single_res.m_state.GetResult() != TxValidationResult::TX_MISSING_INPUTS)) { - // Package validation policy only differs from individual policy in its evaluation - // of feerate. For example, if a transaction fails here due to violation of a - // consensus rule, the result will not change when it is submitted as part of a - // package. To minimize the amount of repeated work, unless the transaction fails - // due to feerate or missing inputs (its parent is a previous transaction in the - // package that failed due to feerate), don't run package validation. Note that this - // decision might not make sense if different types of packages are allowed in the - // future. Continue individually validating the rest of the transactions, because - // some of them may still be valid. - quit_early = true; - package_state_quit_early.Invalid(PackageValidationResult::PCKG_TX, "transaction failed"); - individual_results_nonfinal.emplace(wtxid, single_res); - } else { - individual_results_nonfinal.emplace(wtxid, single_res); - txns_package_eval.push_back(tx); - } - } - } - - auto multi_submission_result = quit_early || txns_package_eval.empty() ? PackageMempoolAcceptResult(package_state_quit_early, {}) : - AcceptSubPackage(txns_package_eval, args); - PackageValidationState& package_state_final = multi_submission_result.m_state; - - // This is invoked by AcceptSubPackage() already, so this is just here for - // clarity (since it's not permitted to invoke LimitMempoolSize() while a - // changeset is outstanding). - ClearSubPackageState(); - - // Make sure we haven't exceeded max mempool size. - // Package transactions that were submitted to mempool or already in mempool may be evicted. - // If mempool contents change, then the m_view cache is dirty. It has already been cleared above. - LimitMempoolSize(m_pool, m_active_chainstate.CoinsTip()); - - for (const auto& tx : package) { - const auto& wtxid = tx->GetWitnessHash(); - if (multi_submission_result.m_tx_results.contains(wtxid)) { - // We shouldn't have re-submitted if the tx result was already in results_final. - Assume(!results_final.contains(wtxid)); - // If it was submitted, check to see if the tx is still in the mempool. It could have - // been evicted due to LimitMempoolSize() above. - const auto& txresult = multi_submission_result.m_tx_results.at(wtxid); - if (txresult.m_result_type == MempoolAcceptResult::ResultType::VALID && !m_pool.exists(wtxid)) { - package_state_final.Invalid(PackageValidationResult::PCKG_TX, "transaction failed"); - TxValidationState mempool_full_state; - mempool_full_state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "mempool full"); - results_final.emplace(wtxid, MempoolAcceptResult::Failure(mempool_full_state)); - } else { - results_final.emplace(wtxid, txresult); - } - } else if (const auto it{results_final.find(wtxid)}; it != results_final.end()) { - // Already-in-mempool transaction. Check to see if it's still there, as it could have - // been evicted when LimitMempoolSize() was called. - Assume(it->second.m_result_type != MempoolAcceptResult::ResultType::INVALID); - Assume(!individual_results_nonfinal.contains(wtxid)); - // Query by txid to include the same-txid-different-witness ones. - if (!m_pool.exists(tx->GetHash())) { - package_state_final.Invalid(PackageValidationResult::PCKG_TX, "transaction failed"); - TxValidationState mempool_full_state; - mempool_full_state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "mempool full"); - // Replace the previous result. - results_final.erase(wtxid); - results_final.emplace(wtxid, MempoolAcceptResult::Failure(mempool_full_state)); - } - } else if (const auto it{individual_results_nonfinal.find(wtxid)}; it != individual_results_nonfinal.end()) { - Assume(it->second.m_result_type == MempoolAcceptResult::ResultType::INVALID); - // Interesting result from previous processing. - results_final.emplace(wtxid, it->second); - } - } - Assume(results_final.size() == package.size()); - return PackageMempoolAcceptResult(package_state_final, std::move(results_final)); -} - -} // anon namespace - -MempoolAcceptResult AcceptToMemoryPool(Chainstate& active_chainstate, const CTransactionRef& tx, - int64_t accept_time, bool bypass_limits, bool test_accept) -{ - AssertLockHeld(::cs_main); - const CChainParams& chainparams{active_chainstate.m_chainman.GetParams()}; - assert(active_chainstate.GetMempool() != nullptr); - CTxMemPool& pool{*active_chainstate.GetMempool()}; - - std::vector coins_to_uncache; - - auto args = MemPoolAccept::ATMPArgs::SingleAccept(chainparams, accept_time, bypass_limits, coins_to_uncache, test_accept); - MempoolAcceptResult result = MemPoolAccept(pool, active_chainstate).AcceptSingleTransactionAndCleanup(tx, args); - - if (result.m_result_type != MempoolAcceptResult::ResultType::VALID) { - // Remove coins that were not present in the coins cache before calling - // AcceptSingleTransaction(); this is to prevent memory DoS in case we receive a large - // number of invalid transactions that attempt to overrun the in-memory coins cache - // (`CCoinsViewCache::cacheCoins`). - - for (const COutPoint& hashTx : coins_to_uncache) - active_chainstate.CoinsTip().Uncache(hashTx); - TRACEPOINT(mempool, rejected, - tx->GetHash().data(), - result.m_state.GetRejectReason().c_str() - ); - } - // After we've (potentially) uncached entries, ensure our coins cache is still within its size limits - BlockValidationState state_dummy; - active_chainstate.FlushStateToDisk(state_dummy, FlushStateMode::PERIODIC); - return result; -} - -PackageMempoolAcceptResult ProcessNewPackage(Chainstate& active_chainstate, CTxMemPool& pool, - const Package& package, bool test_accept, const std::optional& client_maxfeerate) -{ - AssertLockHeld(cs_main); - assert(!package.empty()); - assert(std::all_of(package.cbegin(), package.cend(), [](const auto& tx){return tx != nullptr;})); - - std::vector coins_to_uncache; - const CChainParams& chainparams = active_chainstate.m_chainman.GetParams(); - auto result = [&]() EXCLUSIVE_LOCKS_REQUIRED(cs_main) { - AssertLockHeld(cs_main); - if (test_accept) { - auto args = MemPoolAccept::ATMPArgs::PackageTestAccept(chainparams, GetTime(), coins_to_uncache); - return MemPoolAccept(pool, active_chainstate).AcceptMultipleTransactionsAndCleanup(package, args); - } else { - auto args = MemPoolAccept::ATMPArgs::PackageChildWithParents(chainparams, GetTime(), coins_to_uncache, client_maxfeerate); - return MemPoolAccept(pool, active_chainstate).AcceptPackage(package, args); - } - }(); - - // Uncache coins pertaining to transactions that were not submitted to the mempool. - if (test_accept || result.m_state.IsInvalid()) { - for (const COutPoint& hashTx : coins_to_uncache) { - active_chainstate.CoinsTip().Uncache(hashTx); - } - } - // Ensure the coins cache is still within limits. - BlockValidationState state_dummy; - active_chainstate.FlushStateToDisk(state_dummy, FlushStateMode::PERIODIC); - return result; -} - CAmount GetBlockSubsidy(int nHeight, const Consensus::Params& consensusParams) { int halvings = nHeight / consensusParams.nSubsidyHalvingInterval; @@ -3194,7 +1610,7 @@ bool Chainstate::ActivateBestChainStep(BlockValidationState& state, CBlockIndex& if (!DisconnectTip(state, &disconnectpool)) { // This is likely a fatal error, but keep the mempool consistent, // just in case. Only remove from the mempool in this case. - MaybeUpdateMempoolForReorg(disconnectpool, false); + if (m_mempool) m_chainman.GetMempool().MaybeUpdateMempoolForReorg(*this, disconnectpool, false); // If we're unable to disconnect a block during normal operation, // then that is a failure of our local system -- we should abort @@ -3238,7 +1654,7 @@ bool Chainstate::ActivateBestChainStep(BlockValidationState& state, CBlockIndex& // A system error occurred (disk space, database error, ...). // Make the mempool consistent with the current tip, just in case // any observers try to use it before shutdown. - MaybeUpdateMempoolForReorg(disconnectpool, false); + if (m_mempool) m_chainman.GetMempool().MaybeUpdateMempoolForReorg(*this, disconnectpool, false); return false; } } else { @@ -3255,7 +1671,7 @@ bool Chainstate::ActivateBestChainStep(BlockValidationState& state, CBlockIndex& if (fBlocksDisconnected) { // If any blocks were disconnected, disconnectpool may be non empty. Add // any disconnected transactions back to the mempool. - MaybeUpdateMempoolForReorg(disconnectpool, true); + if (m_mempool) m_chainman.GetMempool().MaybeUpdateMempoolForReorg(*this, disconnectpool, true); } if (m_mempool) m_chainman.GetMempool().check(this->CoinsTip(), this->m_chain.Height() + 1); @@ -3577,7 +1993,7 @@ bool Chainstate::InvalidateBlock(BlockValidationState& state, CBlockIndex* const // transactions back to the mempool if disconnecting was successful, // and we're not doing a very deep invalidation (in which case // keeping the mempool up to date is probably futile anyway). - MaybeUpdateMempoolForReorg(disconnectpool, /* fAddToMempool = */ (++disconnected <= 10) && ret); + if (m_mempool) m_chainman.GetMempool().MaybeUpdateMempoolForReorg(*this, disconnectpool, /* fAddToMempool = */ (++disconnected <= 10) && ret); if (!ret) return false; CBlockIndex* new_tip{m_chain.Tip()}; assert(disconnected_tip->pprev == new_tip); diff --git a/src/validation.h b/src/validation.h index b8778e191f4e..10effc3d7635 100644 --- a/src/validation.h +++ b/src/validation.h @@ -106,191 +106,6 @@ bool FatalError(kernel::Notifications& notifications, BlockValidationState& stat /** Prune block files up to a given height */ void PruneBlockFilesManual(Chainstate& active_chainstate, int nManualPruneHeight); -/** -* Validation result for a transaction evaluated by MemPoolAccept (single or package). -* Here are the expected fields and properties of a result depending on its ResultType, applicable to -* results returned from package evaluation: -*+---------------------------+----------------+-------------------+------------------+----------------+-------------------+ -*| Field or property | VALID | INVALID | MEMPOOL_ENTRY | DIFFERENT_WITNESS | -*| | |--------------------------------------| | | -*| | | TX_RECONSIDERABLE | Other | | | -*+---------------------------+----------------+-------------------+------------------+----------------+-------------------+ -*| txid in mempool? | yes | no | no* | yes | yes | -*| wtxid in mempool? | yes | no | no* | yes | no | -*| m_state | yes, IsValid() | yes, IsInvalid() | yes, IsInvalid() | yes, IsValid() | yes, IsValid() | -*| m_vsize | yes | no | no | yes | no | -*| m_base_fees | yes | no | no | yes | no | -*| m_effective_feerate | yes | yes | no | no | no | -*| m_wtxids_fee_calculations | yes | yes | no | no | no | -*| m_other_wtxid | no | no | no | no | yes | -*+---------------------------+----------------+-------------------+------------------+----------------+-------------------+ -* (*) Individual transaction acceptance doesn't return MEMPOOL_ENTRY and DIFFERENT_WITNESS. It returns -* INVALID, with the errors txn-already-in-mempool and txn-same-nonwitness-data-in-mempool -* respectively. In those cases, the txid or wtxid may be in the mempool for a TX_CONFLICT. -*/ -struct MempoolAcceptResult { - /** Used to indicate the results of mempool validation. */ - enum class ResultType { - VALID, //!> Fully validated, valid. - INVALID, //!> Invalid. - MEMPOOL_ENTRY, //!> Valid, transaction was already in the mempool. - DIFFERENT_WITNESS, //!> Not validated. A same-txid-different-witness tx (see m_other_wtxid) already exists in the mempool and was not replaced. - }; - /** Result type. Present in all MempoolAcceptResults. */ - const ResultType m_result_type; - - /** Contains information about why the transaction failed. */ - const TxValidationState m_state; - - /** Mempool transactions replaced by the tx. */ - const std::list m_replaced_transactions; - /** Virtual size as used by the mempool, calculated using serialized size and sigops. */ - const std::optional m_vsize; - /** Raw base fees in satoshis. */ - const std::optional m_base_fees; - /** The feerate at which this transaction was considered. This includes any fee delta added - * using prioritisetransaction (i.e. modified fees). If this transaction was submitted as a - * package, this is the package feerate, which may also include its descendants and/or - * ancestors (see m_wtxids_fee_calculations below). - */ - const std::optional m_effective_feerate; - /** Contains the wtxids of the transactions used for fee-related checks. Includes this - * transaction's wtxid and may include others if this transaction was validated as part of a - * package. This is not necessarily equivalent to the list of transactions passed to - * ProcessNewPackage(). - * Only present when m_result_type = ResultType::VALID. */ - const std::optional> m_wtxids_fee_calculations; - - /** The wtxid of the transaction in the mempool which has the same txid but different witness. */ - const std::optional m_other_wtxid; - - static MempoolAcceptResult Failure(TxValidationState state) { - return MempoolAcceptResult(state); - } - - static MempoolAcceptResult FeeFailure(TxValidationState state, - CFeeRate effective_feerate, - const std::vector& wtxids_fee_calculations) { - return MempoolAcceptResult(state, effective_feerate, wtxids_fee_calculations); - } - - static MempoolAcceptResult Success(std::list&& replaced_txns, - int64_t vsize, - CAmount fees, - CFeeRate effective_feerate, - const std::vector& wtxids_fee_calculations) { - return MempoolAcceptResult(std::move(replaced_txns), vsize, fees, - effective_feerate, wtxids_fee_calculations); - } - - static MempoolAcceptResult MempoolTx(int64_t vsize, CAmount fees) { - return MempoolAcceptResult(vsize, fees); - } - - static MempoolAcceptResult MempoolTxDifferentWitness(const Wtxid& other_wtxid) { - return MempoolAcceptResult(other_wtxid); - } - -// Private constructors. Use static methods MempoolAcceptResult::Success, etc. to construct. -private: - /** Constructor for failure case */ - explicit MempoolAcceptResult(TxValidationState state) - : m_result_type(ResultType::INVALID), m_state(state) { - Assume(!state.IsValid()); // Can be invalid or error - } - - /** Constructor for success case */ - explicit MempoolAcceptResult(std::list&& replaced_txns, - int64_t vsize, - CAmount fees, - CFeeRate effective_feerate, - const std::vector& wtxids_fee_calculations) - : m_result_type(ResultType::VALID), - m_replaced_transactions(std::move(replaced_txns)), - m_vsize{vsize}, - m_base_fees(fees), - m_effective_feerate(effective_feerate), - m_wtxids_fee_calculations(wtxids_fee_calculations) {} - - /** Constructor for fee-related failure case */ - explicit MempoolAcceptResult(TxValidationState state, - CFeeRate effective_feerate, - const std::vector& wtxids_fee_calculations) - : m_result_type(ResultType::INVALID), - m_state(state), - m_effective_feerate(effective_feerate), - m_wtxids_fee_calculations(wtxids_fee_calculations) {} - - /** Constructor for already-in-mempool case. It wouldn't replace any transactions. */ - explicit MempoolAcceptResult(int64_t vsize, CAmount fees) - : m_result_type(ResultType::MEMPOOL_ENTRY), m_vsize{vsize}, m_base_fees(fees) {} - - /** Constructor for witness-swapped case. */ - explicit MempoolAcceptResult(const Wtxid& other_wtxid) - : m_result_type(ResultType::DIFFERENT_WITNESS), m_other_wtxid(other_wtxid) {} -}; - -/** -* Validation result for package mempool acceptance. -*/ -struct PackageMempoolAcceptResult -{ - PackageValidationState m_state; - /** - * Map from wtxid to finished MempoolAcceptResults. The client is responsible - * for keeping track of the transaction objects themselves. If a result is not - * present, it means validation was unfinished for that transaction. If there - * was a package-wide error (see result in m_state), m_tx_results will be empty. - */ - std::map m_tx_results; - - explicit PackageMempoolAcceptResult(PackageValidationState state, - std::map&& results) - : m_state{state}, m_tx_results(std::move(results)) {} - - explicit PackageMempoolAcceptResult(PackageValidationState state, CFeeRate feerate, - std::map&& results) - : m_state{state}, m_tx_results(std::move(results)) {} - - /** Constructor to create a PackageMempoolAcceptResult from a single MempoolAcceptResult */ - explicit PackageMempoolAcceptResult(const Wtxid& wtxid, const MempoolAcceptResult& result) - : m_tx_results{ {wtxid, result} } {} -}; - -/** - * Try to add a transaction to the mempool. This is an internal function and is exposed only for testing. - * Client code should use ChainstateManager::ProcessTransaction() - * - * @param[in] active_chainstate Reference to the active chainstate. - * @param[in] tx The transaction to submit for mempool acceptance. - * @param[in] accept_time The timestamp for adding the transaction to the mempool. - * It is also used to determine when the entry expires. - * @param[in] bypass_limits When true, don't enforce mempool fee and capacity limits, - * and set entry_sequence to zero. - * @param[in] test_accept When true, run validation checks but don't submit to mempool. - * - * @returns a MempoolAcceptResult indicating whether the transaction was accepted/rejected with reason. - */ -MempoolAcceptResult AcceptToMemoryPool(Chainstate& active_chainstate, const CTransactionRef& tx, - int64_t accept_time, bool bypass_limits, bool test_accept) - EXCLUSIVE_LOCKS_REQUIRED(cs_main); - -/** -* Validate (and maybe submit) a package to the mempool. See doc/policy/packages.md for full details -* on package validation rules. -* @param[in] test_accept When true, run validation checks but don't submit to mempool. -* @param[in] client_maxfeerate If exceeded by an individual transaction, rest of (sub)package evaluation is aborted. -* Only for sanity checks against local submission of transactions. -* @returns a PackageMempoolAcceptResult which includes a MempoolAcceptResult for each transaction. -* If a transaction fails, validation will exit early and some results may be missing. It is also -* possible for the package to be partially submitted. -*/ -PackageMempoolAcceptResult ProcessNewPackage(Chainstate& active_chainstate, CTxMemPool& pool, - const Package& txns, bool test_accept, const std::optional& client_maxfeerate) - EXCLUSIVE_LOCKS_REQUIRED(cs_main); - -/* Mempool validation helper functions */ - /** * Check if transaction will be final in the next block to be created. */ @@ -533,6 +348,13 @@ enum class Assumeutxo { INVALID, }; +bool CheckInputScripts(const CTransaction& tx, TxValidationState& state, + const CCoinsViewCache& inputs, script_verify_flags flags, bool cacheSigStore, + bool cacheFullScriptStore, PrecomputedTransactionData& txdata, + ValidationCache& validation_cache, + std::vector* pvChecks = nullptr) + EXCLUSIVE_LOCKS_REQUIRED(cs_main); + /** * Chainstate stores and provides an API to update our local knowledge of the * current best chain. @@ -867,23 +689,6 @@ class Chainstate void CheckForkWarningConditions() EXCLUSIVE_LOCKS_REQUIRED(cs_main); void InvalidChainFound(CBlockIndex* pindexNew) EXCLUSIVE_LOCKS_REQUIRED(cs_main); - /** - * Make mempool consistent after a reorg, by re-adding or recursively erasing - * disconnected block transactions from the mempool, and also removing any - * other transactions from the mempool that are no longer valid given the new - * tip/height. - * - * Note: we assume that disconnectpool only contains transactions that are NOT - * confirmed in the current chain nor already in the mempool (otherwise, - * in-mempool descendants of such transactions would be removed). - * - * Passing fAddToMempool=false will skip trying to add the transactions back, - * and instead just erase from the mempool as needed. - */ - void MaybeUpdateMempoolForReorg( - DisconnectedBlockTransactions& disconnectpool, - bool fAddToMempool) EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_mempool->cs); - /** Check warning conditions and do some notifications on new chain tip set. */ void UpdateTip(const CBlockIndex* pindexNew) EXCLUSIVE_LOCKS_REQUIRED(::cs_main); From 22829b0a2455aafc34d314a341f4143743f79476 Mon Sep 17 00:00:00 2001 From: TheCharlatan Date: Tue, 20 May 2025 21:46:05 +0200 Subject: [PATCH 07/11] Move ProcessTransaction --- src/bench/block_assemble.cpp | 7 ++++--- src/net_processing.cpp | 7 ++++--- src/node/transaction.cpp | 26 +++++++++++++++++++++++--- src/node/transaction.h | 19 +++++++++++++++++++ src/rpc/mempool.cpp | 2 +- src/test/txdownload_tests.cpp | 3 ++- src/test/txpackage_tests.cpp | 3 ++- src/test/txvalidation_tests.cpp | 3 ++- src/test/txvalidationcache_tests.cpp | 3 ++- src/test/util/setup_common.cpp | 2 +- src/test/validation_block_tests.cpp | 4 +++- src/validation.cpp | 15 --------------- src/validation.h | 9 --------- 13 files changed, 63 insertions(+), 40 deletions(-) diff --git a/src/bench/block_assemble.cpp b/src/bench/block_assemble.cpp index be03917417e6..ebd8890005aa 100644 --- a/src/bench/block_assemble.cpp +++ b/src/bench/block_assemble.cpp @@ -4,7 +4,8 @@ #include #include -#include +#include +#include #include #include #include