Skip to content

Commit 1847ce2

Browse files
author
MarcoFalke
committed
Merge bitcoin/bitcoin#23157: txmempool -/-> validation 1/2: improve performance of check() and remove dependency on validation
082c5bf [refactor] pass coinsview and height to check() (glozow) ed6115f [mempool] simplify some check() logic (glozow) 9e8d7ad [validation/mempool] use Spend/AddCoin instead of UpdateCoins (glozow) 09d1891 MOVEONLY: remove single-use helper func CheckInputsAndUpdateCoins (glozow) e8639ec [mempool] remove now-unnecessary code (glozow) 54c6f3c [mempool] speed up check() by using coins cache and iterating in topo order (glozow) 30e240f [bench] Benchmark CTxMemPool::check() (glozow) cb14071 [refactor/bench] make mempool_stress bench reusable and parameterizable (glozow) Pull request description: Remove the txmempool <-> validation circular dependency by removing txmempool's dependency on validation. There are two functions in txmempool that need validation right now: `check()` and `removeForReorg()`. This PR removes the dependencies in `check()`. This PR also improves the performance of `CTxMemPool::check()` by walking through the entries exactly once, in ascending ancestorcount order, which guarantees that we see parents before children. ACKs for top commit: jnewbery: reACK 082c5bf GeneFerneau: tACK [082c5bf](bitcoin/bitcoin@082c5bf) rajarshimaitra: tACK bitcoin/bitcoin@082c5bf theStack: Code-review ACK 082c5bf Tree-SHA512: 40ac622af1627b5c3e6abb4f0f035d833265a8c5e8dc88faf5354875dfb5137f137825e54bbd2a2668ed37b145c5d02285f776402629f58596e51853a9a79d29
2 parents 49e40f5 + 082c5bf commit 1847ce2

File tree

7 files changed

+59
-62
lines changed

7 files changed

+59
-62
lines changed

src/bench/mempool_stress.cpp

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#include <policy/policy.h>
77
#include <test/util/setup_common.h>
88
#include <txmempool.h>
9+
#include <validation.h>
910

1011
#include <vector>
1112

@@ -26,14 +27,8 @@ struct Available {
2627
Available(CTransactionRef& ref, size_t tx_count) : ref(ref), tx_count(tx_count){}
2728
};
2829

29-
static void ComplexMemPool(benchmark::Bench& bench)
30+
static std::vector<CTransactionRef> CreateOrderedCoins(FastRandomContext& det_rand, int childTxs, int min_ancestors)
3031
{
31-
int childTxs = 800;
32-
if (bench.complexityN() > 1) {
33-
childTxs = static_cast<int>(bench.complexityN());
34-
}
35-
36-
FastRandomContext det_rand{true};
3732
std::vector<Available> available_coins;
3833
std::vector<CTransactionRef> ordered_coins;
3934
// Create some base transactions
@@ -58,8 +53,10 @@ static void ComplexMemPool(benchmark::Bench& bench)
5853
size_t idx = det_rand.randrange(available_coins.size());
5954
Available coin = available_coins[idx];
6055
uint256 hash = coin.ref->GetHash();
61-
// biased towards taking just one ancestor, but maybe more
62-
size_t n_to_take = det_rand.randrange(2) == 0 ? 1 : 1+det_rand.randrange(coin.ref->vout.size() - coin.vin_left);
56+
// biased towards taking min_ancestors parents, but maybe more
57+
size_t n_to_take = det_rand.randrange(2) == 0 ?
58+
min_ancestors :
59+
min_ancestors + det_rand.randrange(coin.ref->vout.size() - coin.vin_left);
6360
for (size_t i = 0; i < n_to_take; ++i) {
6461
tx.vin.emplace_back();
6562
tx.vin.back().prevout = COutPoint(hash, coin.vin_left++);
@@ -79,6 +76,17 @@ static void ComplexMemPool(benchmark::Bench& bench)
7976
ordered_coins.emplace_back(MakeTransactionRef(tx));
8077
available_coins.emplace_back(ordered_coins.back(), tx_counter++);
8178
}
79+
return ordered_coins;
80+
}
81+
82+
static void ComplexMemPool(benchmark::Bench& bench)
83+
{
84+
FastRandomContext det_rand{true};
85+
int childTxs = 800;
86+
if (bench.complexityN() > 1) {
87+
childTxs = static_cast<int>(bench.complexityN());
88+
}
89+
std::vector<CTransactionRef> ordered_coins = CreateOrderedCoins(det_rand, childTxs, /* min_ancestors */ 1);
8290
const auto testing_setup = MakeNoLogFileContext<const TestingSetup>(CBaseChainParams::MAIN);
8391
CTxMemPool pool;
8492
LOCK2(cs_main, pool.cs);
@@ -91,4 +99,21 @@ static void ComplexMemPool(benchmark::Bench& bench)
9199
});
92100
}
93101

102+
static void MempoolCheck(benchmark::Bench& bench)
103+
{
104+
FastRandomContext det_rand{true};
105+
const int childTxs = bench.complexityN() > 1 ? static_cast<int>(bench.complexityN()) : 2000;
106+
const std::vector<CTransactionRef> ordered_coins = CreateOrderedCoins(det_rand, childTxs, /* min_ancestors */ 5);
107+
const auto testing_setup = MakeNoLogFileContext<const TestingSetup>(CBaseChainParams::MAIN, {"-checkmempool=1"});
108+
CTxMemPool pool;
109+
LOCK2(cs_main, pool.cs);
110+
const CCoinsViewCache& coins_tip = testing_setup.get()->m_node.chainman->ActiveChainstate().CoinsTip();
111+
for (auto& tx : ordered_coins) AddTx(tx, pool);
112+
113+
bench.run([&]() NO_THREAD_SAFETY_ANALYSIS {
114+
pool.check(coins_tip, /* spendheight */ 2);
115+
});
116+
}
117+
94118
BENCHMARK(ComplexMemPool);
119+
BENCHMARK(MempoolCheck);

src/net_processing.cpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2299,7 +2299,8 @@ void PeerManagerImpl::ProcessOrphanTx(std::set<uint256>& orphan_work_set)
22992299
break;
23002300
}
23012301
}
2302-
m_mempool.check(m_chainman.ActiveChainstate());
2302+
CChainState& active_chainstate = m_chainman.ActiveChainstate();
2303+
m_mempool.check(active_chainstate.CoinsTip(), active_chainstate.m_chain.Height() + 1);
23032304
}
23042305

23052306
bool PeerManagerImpl::PrepareBlockFilterRequest(CNode& peer,
@@ -3262,7 +3263,8 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type,
32623263
const TxValidationState& state = result.m_state;
32633264

32643265
if (result.m_result_type == MempoolAcceptResult::ResultType::VALID) {
3265-
m_mempool.check(m_chainman.ActiveChainstate());
3266+
CChainState& active_chainstate = m_chainman.ActiveChainstate();
3267+
m_mempool.check(active_chainstate.CoinsTip(), active_chainstate.m_chain.Height() + 1);
32663268
// As this version of the transaction was acceptable, we can forget about any
32673269
// requests for it.
32683270
m_txrequest.ForgetTxHash(tx.GetHash());

src/test/fuzz/tx_pool.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ void SetMempoolConstraints(ArgsManager& args, FuzzedDataProvider& fuzzed_data_pr
8181

8282
void Finish(FuzzedDataProvider& fuzzed_data_provider, MockedTxPool& tx_pool, CChainState& chainstate)
8383
{
84-
WITH_LOCK(::cs_main, tx_pool.check(chainstate));
84+
WITH_LOCK(::cs_main, tx_pool.check(chainstate.CoinsTip(), chainstate.m_chain.Height() + 1));
8585
{
8686
BlockAssembler::Options options;
8787
options.nBlockMaxWeight = fuzzed_data_provider.ConsumeIntegralInRange(0U, MAX_BLOCK_WEIGHT);
@@ -97,7 +97,7 @@ void Finish(FuzzedDataProvider& fuzzed_data_provider, MockedTxPool& tx_pool, CCh
9797
std::vector<uint256> all_txids;
9898
tx_pool.queryHashes(all_txids);
9999
assert(all_txids.size() < info_all.size());
100-
WITH_LOCK(::cs_main, tx_pool.check(chainstate));
100+
WITH_LOCK(::cs_main, tx_pool.check(chainstate.CoinsTip(), chainstate.m_chain.Height() + 1));
101101
}
102102
SyncWithValidationInterfaceQueue();
103103
}

src/txmempool.cpp

Lines changed: 17 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
#include <txmempool.h>
77

8+
#include <coins.h>
89
#include <consensus/consensus.h>
910
#include <consensus/tx_verify.h>
1011
#include <consensus/validation.h>
@@ -671,16 +672,7 @@ void CTxMemPool::clear()
671672
_clear();
672673
}
673674

674-
static void CheckInputsAndUpdateCoins(const CTransaction& tx, CCoinsViewCache& mempoolDuplicate, const int64_t spendheight)
675-
{
676-
TxValidationState dummy_state; // Not used. CheckTxInputs() should always pass
677-
CAmount txfee = 0;
678-
bool fCheckResult = tx.IsCoinBase() || Consensus::CheckTxInputs(tx, dummy_state, mempoolDuplicate, spendheight, txfee);
679-
assert(fCheckResult);
680-
UpdateCoins(tx, mempoolDuplicate, std::numeric_limits<int>::max());
681-
}
682-
683-
void CTxMemPool::check(CChainState& active_chainstate) const
675+
void CTxMemPool::check(const CCoinsViewCache& active_coins_tip, int64_t spendheight) const
684676
{
685677
if (m_check_ratio == 0) return;
686678

@@ -693,38 +685,34 @@ void CTxMemPool::check(CChainState& active_chainstate) const
693685
uint64_t checkTotal = 0;
694686
CAmount check_total_fee{0};
695687
uint64_t innerUsage = 0;
688+
uint64_t prev_ancestor_count{0};
696689

697-
CCoinsViewCache& active_coins_tip = active_chainstate.CoinsTip();
698690
CCoinsViewCache mempoolDuplicate(const_cast<CCoinsViewCache*>(&active_coins_tip));
699-
const int64_t spendheight = active_chainstate.m_chain.Height() + 1;
700691

701-
std::list<const CTxMemPoolEntry*> waitingOnDependants;
702-
for (indexed_transaction_set::const_iterator it = mapTx.begin(); it != mapTx.end(); it++) {
703-
unsigned int i = 0;
692+
for (const auto& it : GetSortedDepthAndScore()) {
704693
checkTotal += it->GetTxSize();
705694
check_total_fee += it->GetFee();
706695
innerUsage += it->DynamicMemoryUsage();
707696
const CTransaction& tx = it->GetTx();
708697
innerUsage += memusage::DynamicUsage(it->GetMemPoolParentsConst()) + memusage::DynamicUsage(it->GetMemPoolChildrenConst());
709-
bool fDependsWait = false;
710698
CTxMemPoolEntry::Parents setParentCheck;
711699
for (const CTxIn &txin : tx.vin) {
712700
// Check that every mempool transaction's inputs refer to available coins, or other mempool tx's.
713701
indexed_transaction_set::const_iterator it2 = mapTx.find(txin.prevout.hash);
714702
if (it2 != mapTx.end()) {
715703
const CTransaction& tx2 = it2->GetTx();
716704
assert(tx2.vout.size() > txin.prevout.n && !tx2.vout[txin.prevout.n].IsNull());
717-
fDependsWait = true;
718705
setParentCheck.insert(*it2);
719-
} else {
720-
assert(active_coins_tip.HaveCoin(txin.prevout));
721706
}
707+
// We are iterating through the mempool entries sorted in order by ancestor count.
708+
// All parents must have been checked before their children and their coins added to
709+
// the mempoolDuplicate coins cache.
710+
assert(mempoolDuplicate.HaveCoin(txin.prevout));
722711
// Check whether its inputs are marked in mapNextTx.
723712
auto it3 = mapNextTx.find(txin.prevout);
724713
assert(it3 != mapNextTx.end());
725714
assert(it3->first == &txin.prevout);
726715
assert(it3->second == &tx);
727-
i++;
728716
}
729717
auto comp = [](const CTxMemPoolEntry& a, const CTxMemPoolEntry& b) -> bool {
730718
return a.GetTx().GetHash() == b.GetTx().GetHash();
@@ -751,6 +739,9 @@ void CTxMemPool::check(CChainState& active_chainstate) const
751739
assert(it->GetSizeWithAncestors() == nSizeCheck);
752740
assert(it->GetSigOpCostWithAncestors() == nSigOpCheck);
753741
assert(it->GetModFeesWithAncestors() == nFeesCheck);
742+
// Sanity check: we are walking in ascending ancestor count order.
743+
assert(prev_ancestor_count <= it->GetCountWithAncestors());
744+
prev_ancestor_count = it->GetCountWithAncestors();
754745

755746
// Check children against mapNextTx
756747
CTxMemPoolEntry::Children setChildrenCheck;
@@ -769,24 +760,12 @@ void CTxMemPool::check(CChainState& active_chainstate) const
769760
// just a sanity check, not definitive that this calc is correct...
770761
assert(it->GetSizeWithDescendants() >= child_sizes + it->GetTxSize());
771762

772-
if (fDependsWait)
773-
waitingOnDependants.push_back(&(*it));
774-
else {
775-
CheckInputsAndUpdateCoins(tx, mempoolDuplicate, spendheight);
776-
}
777-
}
778-
unsigned int stepsSinceLastRemove = 0;
779-
while (!waitingOnDependants.empty()) {
780-
const CTxMemPoolEntry* entry = waitingOnDependants.front();
781-
waitingOnDependants.pop_front();
782-
if (!mempoolDuplicate.HaveInputs(entry->GetTx())) {
783-
waitingOnDependants.push_back(entry);
784-
stepsSinceLastRemove++;
785-
assert(stepsSinceLastRemove < waitingOnDependants.size());
786-
} else {
787-
CheckInputsAndUpdateCoins(entry->GetTx(), mempoolDuplicate, spendheight);
788-
stepsSinceLastRemove = 0;
789-
}
763+
TxValidationState dummy_state; // Not used. CheckTxInputs() should always pass
764+
CAmount txfee = 0;
765+
assert(!tx.IsCoinBase());
766+
assert(Consensus::CheckTxInputs(tx, dummy_state, mempoolDuplicate, spendheight, txfee));
767+
for (const auto& input: tx.vin) mempoolDuplicate.SpendCoin(input.prevout);
768+
AddCoins(mempoolDuplicate, tx, std::numeric_limits<int>::max());
790769
}
791770
for (auto it = mapNextTx.cbegin(); it != mapNextTx.cend(); it++) {
792771
uint256 hash = it->second->GetHash();

src/txmempool.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -622,7 +622,7 @@ class CTxMemPool
622622
* all inputs are in the mapNextTx array). If sanity-checking is turned off,
623623
* check does nothing.
624624
*/
625-
void check(CChainState& active_chainstate) const EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
625+
void check(const CCoinsViewCache& active_coins_tip, int64_t spendheight) const EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
626626

627627
// addUnchecked must updated state for all ancestors of a given transaction,
628628
// to track size/count of descendant transactions. First version of

src/validation.cpp

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1236,12 +1236,6 @@ void UpdateCoins(const CTransaction& tx, CCoinsViewCache& inputs, CTxUndo &txund
12361236
AddCoins(inputs, tx, nHeight);
12371237
}
12381238

1239-
void UpdateCoins(const CTransaction& tx, CCoinsViewCache& inputs, int nHeight)
1240-
{
1241-
CTxUndo txundo;
1242-
UpdateCoins(tx, inputs, txundo, nHeight);
1243-
}
1244-
12451239
bool CScriptCheck::operator()() {
12461240
const CScript &scriptSig = ptxTo->vin[nIn].scriptSig;
12471241
const CScriptWitness *witness = &ptxTo->vin[nIn].scriptWitness;
@@ -2487,7 +2481,7 @@ bool CChainState::ActivateBestChainStep(BlockValidationState& state, CBlockIndex
24872481
// any disconnected transactions back to the mempool.
24882482
MaybeUpdateMempoolForReorg(disconnectpool, true);
24892483
}
2490-
if (m_mempool) m_mempool->check(*this);
2484+
if (m_mempool) m_mempool->check(this->CoinsTip(), this->m_chain.Height() + 1);
24912485

24922486
CheckForkWarningConditions();
24932487

src/validation.h

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -229,9 +229,6 @@ PackageMempoolAcceptResult ProcessNewPackage(CChainState& active_chainstate, CTx
229229
const Package& txns, bool test_accept)
230230
EXCLUSIVE_LOCKS_REQUIRED(cs_main);
231231

232-
/** Apply the effects of this transaction on the UTXO set represented by view */
233-
void UpdateCoins(const CTransaction& tx, CCoinsViewCache& inputs, int nHeight);
234-
235232
/** Transaction validation functions */
236233

237234
/**

0 commit comments

Comments
 (0)