aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/bench/block_assemble.cpp5
-rw-r--r--src/net_processing.cpp16
-rw-r--r--src/node/transaction.cpp18
-rw-r--r--src/rpc/rawtransaction.cpp14
-rw-r--r--src/test/txvalidation_tests.cpp16
-rw-r--r--src/test/txvalidationcache_tests.cpp6
-rw-r--r--src/test/validation_block_tests.cpp10
-rw-r--r--src/validation.cpp64
-rw-r--r--src/validation.h49
9 files changed, 106 insertions, 92 deletions
diff --git a/src/bench/block_assemble.cpp b/src/bench/block_assemble.cpp
index af5a82f69f..9f7d582e8a 100644
--- a/src/bench/block_assemble.cpp
+++ b/src/bench/block_assemble.cpp
@@ -48,9 +48,8 @@ static void AssembleBlock(benchmark::Bench& bench)
LOCK(::cs_main); // Required for ::AcceptToMemoryPool.
for (const auto& txr : txs) {
- TxValidationState state;
- bool ret{::AcceptToMemoryPool(*test_setup.m_node.mempool, state, txr, nullptr /* plTxnReplaced */, false /* bypass_limits */)};
- assert(ret);
+ const MempoolAcceptResult res = ::AcceptToMemoryPool(*test_setup.m_node.mempool, txr, false /* bypass_limits */);
+ assert(res.m_result_type == MempoolAcceptResult::ResultType::VALID);
}
}
diff --git a/src/net_processing.cpp b/src/net_processing.cpp
index b68453759a..ba80da287a 100644
--- a/src/net_processing.cpp
+++ b/src/net_processing.cpp
@@ -2178,10 +2178,10 @@ void PeerManagerImpl::ProcessOrphanTx(std::set<uint256>& orphan_work_set)
if (orphan_it == mapOrphanTransactions.end()) continue;
const CTransactionRef porphanTx = orphan_it->second.tx;
- TxValidationState state;
- std::list<CTransactionRef> removed_txn;
+ const MempoolAcceptResult result = AcceptToMemoryPool(m_mempool, porphanTx, false /* bypass_limits */);
+ const TxValidationState& state = result.m_state;
- if (AcceptToMemoryPool(m_mempool, state, porphanTx, &removed_txn, false /* bypass_limits */)) {
+ if (result.m_result_type == MempoolAcceptResult::ResultType::VALID) {
LogPrint(BCLog::MEMPOOL, " accepted orphan tx %s\n", orphanHash.ToString());
RelayTransaction(orphanHash, porphanTx->GetWitnessHash(), m_connman);
for (unsigned int i = 0; i < porphanTx->vout.size(); i++) {
@@ -2193,7 +2193,7 @@ void PeerManagerImpl::ProcessOrphanTx(std::set<uint256>& orphan_work_set)
}
}
EraseOrphanTx(orphanHash);
- for (const CTransactionRef& removedTx : removed_txn) {
+ for (const CTransactionRef& removedTx : result.m_replaced_transactions.value()) {
AddToCompactExtraTransactions(removedTx);
}
break;
@@ -3197,10 +3197,10 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
return;
}
- TxValidationState state;
- std::list<CTransactionRef> lRemovedTxn;
+ const MempoolAcceptResult result = AcceptToMemoryPool(m_mempool, ptx, false /* bypass_limits */);
+ const TxValidationState& state = result.m_state;
- if (AcceptToMemoryPool(m_mempool, state, ptx, &lRemovedTxn, false /* bypass_limits */)) {
+ if (result.m_result_type == MempoolAcceptResult::ResultType::VALID) {
m_mempool.check(&::ChainstateActive().CoinsTip());
// As this version of the transaction was acceptable, we can forget about any
// requests for it.
@@ -3223,7 +3223,7 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
tx.GetHash().ToString(),
m_mempool.size(), m_mempool.DynamicMemoryUsage() / 1000);
- for (const CTransactionRef& removedTx : lRemovedTxn) {
+ for (const CTransactionRef& removedTx : result.m_replaced_transactions.value()) {
AddToCompactExtraTransactions(removedTx);
}
diff --git a/src/node/transaction.cpp b/src/node/transaction.cpp
index d3bb9687a8..6f5acf41e3 100644
--- a/src/node/transaction.cpp
+++ b/src/node/transaction.cpp
@@ -50,22 +50,22 @@ TransactionError BroadcastTransaction(NodeContext& node, const CTransactionRef t
}
if (!node.mempool->exists(hashTx)) {
// Transaction is not already in the mempool.
- TxValidationState state;
if (max_tx_fee > 0) {
// First, call ATMP with test_accept and check the fee. If ATMP
// fails here, return error immediately.
- CAmount fee{0};
- if (!AcceptToMemoryPool(*node.mempool, state, tx,
- nullptr /* plTxnReplaced */, false /* bypass_limits */, /* test_accept */ true, &fee)) {
- return HandleATMPError(state, err_string);
- } else if (fee > max_tx_fee) {
+ const MempoolAcceptResult result = AcceptToMemoryPool(*node.mempool, tx, false /* bypass_limits */,
+ true /* test_accept */);
+ if (result.m_result_type != MempoolAcceptResult::ResultType::VALID) {
+ return HandleATMPError(result.m_state, err_string);
+ } else if (result.m_base_fees.value() > max_tx_fee) {
return TransactionError::MAX_FEE_EXCEEDED;
}
}
// Try to submit the transaction to the mempool.
- if (!AcceptToMemoryPool(*node.mempool, state, tx,
- nullptr /* plTxnReplaced */, false /* bypass_limits */)) {
- return HandleATMPError(state, err_string);
+ const MempoolAcceptResult result = AcceptToMemoryPool(*node.mempool, tx, false /* bypass_limits */,
+ false /* test_accept */);
+ if (result.m_result_type != MempoolAcceptResult::ResultType::VALID) {
+ return HandleATMPError(result.m_state, err_string);
}
// Transaction was accepted to the mempool.
diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp
index 31190ef9fc..784a53e060 100644
--- a/src/rpc/rawtransaction.cpp
+++ b/src/rpc/rawtransaction.cpp
@@ -946,18 +946,13 @@ static RPCHelpMan testmempoolaccept()
result_0.pushKV("txid", tx->GetHash().GetHex());
result_0.pushKV("wtxid", tx->GetWitnessHash().GetHex());
- TxValidationState state;
- bool test_accept_res;
- CAmount fee{0};
- {
- LOCK(cs_main);
- test_accept_res = AcceptToMemoryPool(mempool, state, std::move(tx),
- nullptr /* plTxnReplaced */, false /* bypass_limits */, /* test_accept */ true, &fee);
- }
+ const MempoolAcceptResult accept_result = WITH_LOCK(cs_main, return AcceptToMemoryPool(mempool, std::move(tx),
+ false /* bypass_limits */, /* test_accept */ true));
// Only return the fee and vsize if the transaction would pass ATMP.
// These can be used to calculate the feerate.
- if (test_accept_res) {
+ if (accept_result.m_result_type == MempoolAcceptResult::ResultType::VALID) {
+ const CAmount fee = accept_result.m_base_fees.value();
// Check that fee does not exceed maximum fee
if (max_raw_tx_fee && fee > max_raw_tx_fee) {
result_0.pushKV("allowed", false);
@@ -972,6 +967,7 @@ static RPCHelpMan testmempoolaccept()
result.push_back(std::move(result_0));
} else {
result_0.pushKV("allowed", false);
+ const TxValidationState state = accept_result.m_state;
if (state.GetResult() == TxValidationResult::TX_MISSING_INPUTS) {
result_0.pushKV("reject-reason", "missing-inputs");
} else {
diff --git a/src/test/txvalidation_tests.cpp b/src/test/txvalidation_tests.cpp
index 7e6246d68f..9b0d2e3135 100644
--- a/src/test/txvalidation_tests.cpp
+++ b/src/test/txvalidation_tests.cpp
@@ -30,25 +30,21 @@ BOOST_FIXTURE_TEST_CASE(tx_mempool_reject_coinbase, TestChain100Setup)
BOOST_CHECK(CTransaction(coinbaseTx).IsCoinBase());
- TxValidationState state;
-
LOCK(cs_main);
unsigned int initialPoolSize = m_node.mempool->size();
+ const MempoolAcceptResult result = AcceptToMemoryPool(*m_node.mempool, MakeTransactionRef(coinbaseTx),
+ true /* bypass_limits */);
- BOOST_CHECK_EQUAL(
- false,
- AcceptToMemoryPool(*m_node.mempool, state, MakeTransactionRef(coinbaseTx),
- nullptr /* plTxnReplaced */,
- true /* bypass_limits */));
+ BOOST_CHECK(result.m_result_type == MempoolAcceptResult::ResultType::INVALID);
// Check that the transaction hasn't been added to mempool.
BOOST_CHECK_EQUAL(m_node.mempool->size(), initialPoolSize);
// Check that the validation state reflects the unsuccessful attempt.
- BOOST_CHECK(state.IsInvalid());
- BOOST_CHECK_EQUAL(state.GetRejectReason(), "coinbase");
- BOOST_CHECK(state.GetResult() == TxValidationResult::TX_CONSENSUS);
+ BOOST_CHECK(result.m_state.IsInvalid());
+ BOOST_CHECK_EQUAL(result.m_state.GetRejectReason(), "coinbase");
+ BOOST_CHECK(result.m_state.GetResult() == TxValidationResult::TX_CONSENSUS);
}
BOOST_AUTO_TEST_SUITE_END()
diff --git a/src/test/txvalidationcache_tests.cpp b/src/test/txvalidationcache_tests.cpp
index bed2ba3608..af0090cc10 100644
--- a/src/test/txvalidationcache_tests.cpp
+++ b/src/test/txvalidationcache_tests.cpp
@@ -28,9 +28,9 @@ BOOST_FIXTURE_TEST_CASE(tx_mempool_block_doublespend, TestChain100Setup)
const auto ToMemPool = [this](const CMutableTransaction& tx) {
LOCK(cs_main);
- TxValidationState state;
- return AcceptToMemoryPool(*m_node.mempool, state, MakeTransactionRef(tx),
- nullptr /* plTxnReplaced */, true /* bypass_limits */);
+ const MempoolAcceptResult result = AcceptToMemoryPool(*m_node.mempool, MakeTransactionRef(tx),
+ true /* bypass_limits */);
+ return result.m_result_type == MempoolAcceptResult::ResultType::VALID;
};
// Create a double-spend of mature coinbase txn:
diff --git a/src/test/validation_block_tests.cpp b/src/test/validation_block_tests.cpp
index ec45d9a434..6c18a9e1bb 100644
--- a/src/test/validation_block_tests.cpp
+++ b/src/test/validation_block_tests.cpp
@@ -283,15 +283,9 @@ BOOST_AUTO_TEST_CASE(mempool_locks_reorg)
// Add the txs to the tx pool
{
LOCK(cs_main);
- TxValidationState state;
- std::list<CTransactionRef> plTxnReplaced;
for (const auto& tx : txs) {
- BOOST_REQUIRE(AcceptToMemoryPool(
- *m_node.mempool,
- state,
- tx,
- &plTxnReplaced,
- /* bypass_limits */ false));
+ const MempoolAcceptResult result = AcceptToMemoryPool(*m_node.mempool, tx, false /* bypass_limits */);
+ BOOST_REQUIRE(result.m_result_type == MempoolAcceptResult::ResultType::VALID);
}
}
diff --git a/src/validation.cpp b/src/validation.cpp
index 38df71b994..a1e57752d3 100644
--- a/src/validation.cpp
+++ b/src/validation.cpp
@@ -380,10 +380,8 @@ static void UpdateMempoolForReorg(CTxMemPool& mempool, DisconnectedBlockTransact
auto it = disconnectpool.queuedTx.get<insertion_order>().rbegin();
while (it != disconnectpool.queuedTx.get<insertion_order>().rend()) {
// ignore validation errors in resurrected transactions
- TxValidationState stateDummy;
if (!fAddToMempool || (*it)->IsCoinBase() ||
- !AcceptToMemoryPool(mempool, stateDummy, *it,
- nullptr /* plTxnReplaced */, true /* bypass_limits */)) {
+ AcceptToMemoryPool(mempool, *it, true /* bypass_limits */).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).
mempool.removeRecursive(**it, MemPoolRemovalReason::REORG);
@@ -465,7 +463,7 @@ public:
const CChainParams& m_chainparams;
TxValidationState &m_state;
const int64_t m_accept_time;
- std::list<CTransactionRef>* m_replaced_transactions;
+ std::list<CTransactionRef> m_replaced_transactions;
const bool m_bypass_limits;
/*
* Return any outpoints which were not previously present in the coins
@@ -476,11 +474,11 @@ public:
*/
std::vector<COutPoint>& m_coins_to_uncache;
const bool m_test_accept;
- CAmount* m_fee_out;
+ CAmount m_fee_out;
};
// Single transaction acceptance
- bool AcceptSingleTransaction(const CTransactionRef& ptx, ATMPArgs& args) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
+ MempoolAcceptResult AcceptSingleTransaction(const CTransactionRef& ptx, ATMPArgs& args) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
private:
// All the intermediate state that gets passed between the various levels
@@ -688,10 +686,7 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
return false; // state filled in by CheckTxInputs
}
- // If fee_out is passed, return the fee to the caller
- if (args.m_fee_out) {
- *args.m_fee_out = nFees;
- }
+ args.m_fee_out = nFees;
// Check for non-standard pay-to-script-hash in inputs
const auto& params = args.m_chainparams.GetConsensus();
@@ -1007,8 +1002,7 @@ bool MemPoolAccept::Finalize(ATMPArgs& args, Workspace& ws)
hash.ToString(),
FormatMoney(nModifiedFees - nConflictingFees),
(int)entry->GetTxSize() - (int)nConflictingSize);
- if (args.m_replaced_transactions)
- args.m_replaced_transactions->push_back(it->GetSharedTx());
+ args.m_replaced_transactions.push_back(it->GetSharedTx());
}
m_pool.RemoveStaged(allConflicting, false, MemPoolRemovalReason::REPLACED);
@@ -1031,14 +1025,14 @@ bool MemPoolAccept::Finalize(ATMPArgs& args, Workspace& ws)
return true;
}
-bool MemPoolAccept::AcceptSingleTransaction(const CTransactionRef& ptx, ATMPArgs& args)
+MempoolAcceptResult MemPoolAccept::AcceptSingleTransaction(const CTransactionRef& ptx, ATMPArgs& args)
{
AssertLockHeld(cs_main);
LOCK(m_pool.cs); // mempool "read lock" (held through GetMainSignals().TransactionAddedToMempool())
Workspace workspace(ptx);
- if (!PreChecks(args, workspace)) return false;
+ if (!PreChecks(args, workspace)) return MempoolAcceptResult(args.m_state);
// Only compute the precomputed transaction data if we need to verify
// scripts (ie, other policy checks pass). We perform the inexpensive
@@ -1046,31 +1040,36 @@ bool MemPoolAccept::AcceptSingleTransaction(const CTransactionRef& ptx, ATMPArgs
// checks pass, to mitigate CPU exhaustion denial-of-service attacks.
PrecomputedTransactionData txdata;
- if (!PolicyScriptChecks(args, workspace, txdata)) return false;
+ if (!PolicyScriptChecks(args, workspace, txdata)) return MempoolAcceptResult(args.m_state);
- if (!ConsensusScriptChecks(args, workspace, txdata)) return false;
+ if (!ConsensusScriptChecks(args, workspace, txdata)) return MempoolAcceptResult(args.m_state);
// Tx was accepted, but not added
- if (args.m_test_accept) return true;
+ if (args.m_test_accept) {
+ return MempoolAcceptResult(std::move(args.m_replaced_transactions), args.m_fee_out);
+ }
- if (!Finalize(args, workspace)) return false;
+ if (!Finalize(args, workspace)) return MempoolAcceptResult(args.m_state);
GetMainSignals().TransactionAddedToMempool(ptx, m_pool.GetAndIncrementSequence());
- return true;
+ return MempoolAcceptResult(std::move(args.m_replaced_transactions), args.m_fee_out);
}
} // anon namespace
/** (try to) add transaction to memory pool with a specified acceptance time **/
-static bool AcceptToMemoryPoolWithTime(const CChainParams& chainparams, CTxMemPool& pool, TxValidationState &state, const CTransactionRef &tx,
- int64_t nAcceptTime, std::list<CTransactionRef>* plTxnReplaced,
- bool bypass_limits, bool test_accept, CAmount* fee_out=nullptr) EXCLUSIVE_LOCKS_REQUIRED(cs_main)
+static MempoolAcceptResult AcceptToMemoryPoolWithTime(const CChainParams& chainparams, CTxMemPool& pool,
+ const CTransactionRef &tx, int64_t nAcceptTime,
+ bool bypass_limits, bool test_accept)
+ EXCLUSIVE_LOCKS_REQUIRED(cs_main)
{
+ TxValidationState state;
std::vector<COutPoint> coins_to_uncache;
- MemPoolAccept::ATMPArgs args { chainparams, state, nAcceptTime, plTxnReplaced, bypass_limits, coins_to_uncache, test_accept, fee_out };
- bool res = MemPoolAccept(pool).AcceptSingleTransaction(tx, args);
- if (!res) {
+ MemPoolAccept::ATMPArgs args { chainparams, state, nAcceptTime, {}, bypass_limits, coins_to_uncache, test_accept, {} };
+
+ const MempoolAcceptResult result = MemPoolAccept(pool).AcceptSingleTransaction(tx, args);
+ if (result.m_result_type != MempoolAcceptResult::ResultType::VALID) {
// Remove coins that were not present in the coins cache before calling ATMPW;
// 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
@@ -1082,15 +1081,13 @@ static bool AcceptToMemoryPoolWithTime(const CChainParams& chainparams, CTxMemPo
// After we've (potentially) uncached entries, ensure our coins cache is still within its size limits
BlockValidationState state_dummy;
::ChainstateActive().FlushStateToDisk(chainparams, state_dummy, FlushStateMode::PERIODIC);
- return res;
+ return result;
}
-bool AcceptToMemoryPool(CTxMemPool& pool, TxValidationState &state, const CTransactionRef &tx,
- std::list<CTransactionRef>* plTxnReplaced,
- bool bypass_limits, bool test_accept, CAmount* fee_out)
+MempoolAcceptResult AcceptToMemoryPool(CTxMemPool& pool, const CTransactionRef &tx, bool bypass_limits, bool test_accept)
{
const CChainParams& chainparams = Params();
- return AcceptToMemoryPoolWithTime(chainparams, pool, state, tx, GetTime(), plTxnReplaced, bypass_limits, test_accept, fee_out);
+ return AcceptToMemoryPoolWithTime(chainparams, pool, tx, GetTime(), bypass_limits, test_accept);
}
CTransactionRef GetTransaction(const CBlockIndex* const block_index, const CTxMemPool* const mempool, const uint256& hash, const Consensus::Params& consensusParams, uint256& hashBlock)
@@ -5029,13 +5026,10 @@ bool LoadMempool(CTxMemPool& pool)
if (amountdelta) {
pool.PrioritiseTransaction(tx->GetHash(), amountdelta);
}
- TxValidationState state;
if (nTime > nNow - nExpiryTimeout) {
LOCK(cs_main);
- AcceptToMemoryPoolWithTime(chainparams, pool, state, tx, nTime,
- nullptr /* plTxnReplaced */, false /* bypass_limits */,
- false /* test_accept */);
- if (state.IsValid()) {
+ if (AcceptToMemoryPoolWithTime(chainparams, pool, tx, nTime, false /* bypass_limits */,
+ false /* test_accept */).m_result_type == MempoolAcceptResult::ResultType::VALID) {
++count;
} else {
// mempool may contain the transaction already, e.g. from
diff --git a/src/validation.h b/src/validation.h
index fc7add85b7..c737c113ad 100644
--- a/src/validation.h
+++ b/src/validation.h
@@ -12,6 +12,7 @@
#include <amount.h>
#include <coins.h>
+#include <consensus/validation.h>
#include <crypto/common.h> // for ReadLE64
#include <fs.h>
#include <optional.h>
@@ -23,6 +24,7 @@
#include <txdb.h>
#include <versionbits.h>
#include <serialize.h>
+#include <util/check.h>
#include <util/hasher.h>
#include <atomic>
@@ -46,7 +48,6 @@ class CConnman;
class CScriptCheck;
class CTxMemPool;
class ChainstateManager;
-class TxValidationState;
struct ChainTxData;
struct DisconnectedBlockTransactions;
@@ -181,12 +182,46 @@ void UnlinkPrunedFiles(const std::set<int>& setFilesToPrune);
/** Prune block files up to a given height */
void PruneBlockFilesManual(int nManualPruneHeight);
-/** (try to) add transaction to memory pool
- * plTxnReplaced will be appended to with all transactions replaced from mempool
- * @param[out] fee_out optional argument to return tx fee to the caller **/
-bool AcceptToMemoryPool(CTxMemPool& pool, TxValidationState &state, const CTransactionRef &tx,
- std::list<CTransactionRef>* plTxnReplaced,
- bool bypass_limits, bool test_accept=false, CAmount* fee_out=nullptr) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
+/**
+* Validation result for a single transaction mempool acceptance.
+*/
+struct MempoolAcceptResult {
+ /** Used to indicate the results of mempool validation,
+ * including the possibility of unfinished validation.
+ */
+ enum class ResultType {
+ VALID, //!> Fully validated, valid.
+ INVALID, //!> Invalid.
+ };
+ ResultType m_result_type;
+ TxValidationState m_state;
+
+ // The following fields are only present when m_result_type = ResultType::VALID
+ /** Mempool transactions replaced by the tx per BIP 125 rules. */
+ std::optional<std::list<CTransactionRef>> m_replaced_transactions;
+ /** Raw base fees. */
+ std::optional<CAmount> m_base_fees;
+
+ /** Constructor for failure case */
+ explicit MempoolAcceptResult(TxValidationState state)
+ : m_result_type(ResultType::INVALID),
+ m_state(state), m_replaced_transactions(nullopt), m_base_fees(nullopt) {
+ Assume(!state.IsValid()); // Can be invalid or error
+ }
+
+ /** Constructor for success case */
+ explicit MempoolAcceptResult(std::list<CTransactionRef>&& replaced_txns, CAmount fees)
+ : m_result_type(ResultType::VALID), m_state(TxValidationState{}),
+ m_replaced_transactions(std::move(replaced_txns)), m_base_fees(fees) {}
+};
+
+/**
+ * (Try to) add a transaction to the memory pool.
+ * @param[in] bypass_limits When true, don't enforce mempool fee limits.
+ * @param[in] test_accept When true, run validation checks but don't submit to mempool.
+ */
+MempoolAcceptResult AcceptToMemoryPool(CTxMemPool& pool, const CTransactionRef& tx,
+ bool bypass_limits, bool test_accept=false) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
/** Get the BIP9 state for a given deployment at the current tip. */
ThresholdState VersionBitsTipState(const Consensus::Params& params, Consensus::DeploymentPos pos);