Skip to content

Commit

Permalink
Merge bitcoin#23157: txmempool -/-> validation 1/2: improve performan…
Browse files Browse the repository at this point in the history
…ce 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@082c5bf)
  rajarshimaitra:
    tACK bitcoin@082c5bf
  theStack:
    Code-review ACK 082c5bf

Tree-SHA512: 40ac622af1627b5c3e6abb4f0f035d833265a8c5e8dc88faf5354875dfb5137f137825e54bbd2a2668ed37b145c5d02285f776402629f58596e51853a9a79d29
  • Loading branch information
MarcoFalke authored and vijaydasmp committed Sep 8, 2024
1 parent 0c243ab commit 75797a6
Show file tree
Hide file tree
Showing 7 changed files with 61 additions and 62 deletions.
43 changes: 34 additions & 9 deletions src/bench/mempool_stress.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include <policy/policy.h>
#include <test/util/setup_common.h>
#include <txmempool.h>
#include <validation.h>

#include <vector>

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

static void ComplexMemPool(benchmark::Bench& bench)
static std::vector<CTransactionRef> CreateOrderedCoins(FastRandomContext& det_rand, int childTxs, int min_ancestors)
{
int childTxs = 800;
if (bench.complexityN() > 1) {
childTxs = static_cast<int>(bench.complexityN());
}

FastRandomContext det_rand{true};
std::vector<Available> available_coins;
std::vector<CTransactionRef> ordered_coins;
// Create some base transactions
Expand All @@ -57,8 +52,10 @@ static void ComplexMemPool(benchmark::Bench& bench)
size_t idx = det_rand.randrange(available_coins.size());
Available coin = available_coins[idx];
uint256 hash = coin.ref->GetHash();
// biased towards taking just one ancestor, but maybe more
size_t n_to_take = det_rand.randrange(2) == 0 ? 1 : 1+det_rand.randrange(coin.ref->vout.size() - coin.vin_left);
// biased towards taking min_ancestors parents, but maybe more
size_t n_to_take = det_rand.randrange(2) == 0 ?
min_ancestors :
min_ancestors + det_rand.randrange(coin.ref->vout.size() - coin.vin_left);
for (size_t i = 0; i < n_to_take; ++i) {
tx.vin.emplace_back();
tx.vin.back().prevout = COutPoint(hash, coin.vin_left++);
Expand All @@ -77,6 +74,17 @@ static void ComplexMemPool(benchmark::Bench& bench)
ordered_coins.emplace_back(MakeTransactionRef(tx));
available_coins.emplace_back(ordered_coins.back(), tx_counter++);
}
return ordered_coins;
}

static void ComplexMemPool(benchmark::Bench& bench)
{
FastRandomContext det_rand{true};
int childTxs = 800;
if (bench.complexityN() > 1) {
childTxs = static_cast<int>(bench.complexityN());
}
std::vector<CTransactionRef> ordered_coins = CreateOrderedCoins(det_rand, childTxs, /* min_ancestors */ 1);
const auto testing_setup = MakeNoLogFileContext<const TestingSetup>(CBaseChainParams::MAIN);
CTxMemPool pool;
LOCK2(cs_main, pool.cs);
Expand All @@ -89,4 +97,21 @@ static void ComplexMemPool(benchmark::Bench& bench)
});
}

static void MempoolCheck(benchmark::Bench& bench)
{
FastRandomContext det_rand{true};
const int childTxs = bench.complexityN() > 1 ? static_cast<int>(bench.complexityN()) : 2000;
const std::vector<CTransactionRef> ordered_coins = CreateOrderedCoins(det_rand, childTxs, /* min_ancestors */ 5);
const auto testing_setup = MakeNoLogFileContext<const TestingSetup>(CBaseChainParams::MAIN, {"-checkmempool=1"});
CTxMemPool pool;
LOCK2(cs_main, pool.cs);
const CCoinsViewCache& coins_tip = testing_setup.get()->m_node.chainman->ActiveChainstate().CoinsTip();
for (auto& tx : ordered_coins) AddTx(tx, pool);

bench.run([&]() NO_THREAD_SAFETY_ANALYSIS {
pool.check(coins_tip, /* spendheight */ 2);
});
}

BENCHMARK(ComplexMemPool);
BENCHMARK(MempoolCheck);
8 changes: 6 additions & 2 deletions src/net_processing.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2996,7 +2996,8 @@ void PeerManagerImpl::ProcessOrphanTx(std::set<uint256>& orphan_work_set)
break;
}
}
m_mempool.check(m_chainman.ActiveChainstate());
CChainState& active_chainstate = m_chainman.ActiveChainstate();
m_mempool.check(active_chainstate.CoinsTip(), active_chainstate.m_chain.Height() + 1);
}

bool PeerManagerImpl::PrepareBlockFilterRequest(CNode& node, Peer& peer,
Expand Down Expand Up @@ -4147,8 +4148,11 @@ void PeerManagerImpl::ProcessMessage(
m_cj_ctx->dstxman->AddDSTX(dstx);
}

m_mempool.check(m_chainman.ActiveChainstate());
CChainState& active_chainstate = m_chainman.ActiveChainstate();
m_mempool.check(active_chainstate.CoinsTip(), active_chainstate.m_chain.Height() + 1);

RelayTransaction(tx.GetHash());

m_orphanage.AddChildrenToWorkSet(tx, peer->m_orphan_work_set);

pfrom.m_last_tx_time = GetTime<std::chrono::seconds>();
Expand Down
4 changes: 2 additions & 2 deletions src/test/fuzz/tx_pool.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ void SetMempoolConstraints(ArgsManager& args, FuzzedDataProvider& fuzzed_data_pr

void Finish(FuzzedDataProvider& fuzzed_data_provider, MockedTxPool& tx_pool, const NodeContext& node, CChainState& chainstate)
{
WITH_LOCK(::cs_main, tx_pool.check(chainstate));
WITH_LOCK(::cs_main, tx_pool.check(chainstate.CoinsTip(), chainstate.m_chain.Height() + 1));
{
BlockAssembler::Options options;
options.nBlockMaxSize = fuzzed_data_provider.ConsumeIntegralInRange(0U, MaxBlockSize(true));
Expand All @@ -97,7 +97,7 @@ void Finish(FuzzedDataProvider& fuzzed_data_provider, MockedTxPool& tx_pool, con
std::vector<uint256> all_txids;
tx_pool.queryHashes(all_txids);
assert(all_txids.size() < info_all.size());
WITH_LOCK(::cs_main, tx_pool.check(chainstate));
WITH_LOCK(::cs_main, tx_pool.check(chainstate.CoinsTip(), chainstate.m_chain.Height() + 1));
}
SyncWithValidationInterfaceQueue();
}
Expand Down
55 changes: 17 additions & 38 deletions src/txmempool.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

#include <txmempool.h>

#include <coins.h>
#include <consensus/consensus.h>
#include <consensus/tx_verify.h>
#include <consensus/validation.h>
Expand Down Expand Up @@ -1101,16 +1102,7 @@ void CTxMemPool::clear()
_clear();
}

static void CheckInputsAndUpdateCoins(const CTransaction& tx, CCoinsViewCache& mempoolDuplicate, const int64_t spendheight)
{
TxValidationState dummy_state; // Not used. CheckTxInputs() should always pass
CAmount txfee = 0;
bool fCheckResult = tx.IsCoinBase() || Consensus::CheckTxInputs(tx, dummy_state, mempoolDuplicate, spendheight, txfee);
assert(fCheckResult);
UpdateCoins(tx, mempoolDuplicate, std::numeric_limits<int>::max());
}

void CTxMemPool::check(CChainState& active_chainstate) const
void CTxMemPool::check(const CCoinsViewCache& active_coins_tip, int64_t spendheight) const
{
if (m_check_ratio == 0) return;

Expand All @@ -1123,38 +1115,34 @@ void CTxMemPool::check(CChainState& active_chainstate) const
uint64_t checkTotal = 0;
CAmount check_total_fee{0};
uint64_t innerUsage = 0;
uint64_t prev_ancestor_count{0};

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

std::list<const CTxMemPoolEntry*> waitingOnDependants;
for (indexed_transaction_set::const_iterator it = mapTx.begin(); it != mapTx.end(); it++) {
unsigned int i = 0;
for (const auto& it : GetSortedDepthAndScore()) {
checkTotal += it->GetTxSize();
check_total_fee += it->GetFee();
innerUsage += it->DynamicMemoryUsage();
const CTransaction& tx = it->GetTx();
innerUsage += memusage::DynamicUsage(it->GetMemPoolParentsConst()) + memusage::DynamicUsage(it->GetMemPoolChildrenConst());
bool fDependsWait = false;
CTxMemPoolEntry::Parents setParentCheck;
for (const CTxIn &txin : tx.vin) {
// Check that every mempool transaction's inputs refer to available coins, or other mempool tx's.
indexed_transaction_set::const_iterator it2 = mapTx.find(txin.prevout.hash);
if (it2 != mapTx.end()) {
const CTransaction& tx2 = it2->GetTx();
assert(tx2.vout.size() > txin.prevout.n && !tx2.vout[txin.prevout.n].IsNull());
fDependsWait = true;
setParentCheck.insert(*it2);
} else {
assert(active_coins_tip.HaveCoin(txin.prevout));
}
// We are iterating through the mempool entries sorted in order by ancestor count.
// All parents must have been checked before their children and their coins added to
// the mempoolDuplicate coins cache.
assert(mempoolDuplicate.HaveCoin(txin.prevout));
// Check whether its inputs are marked in mapNextTx.
auto it3 = mapNextTx.find(txin.prevout);
assert(it3 != mapNextTx.end());
assert(it3->first == &txin.prevout);
assert(it3->second == &tx);
i++;
}
auto comp = [](const CTxMemPoolEntry& a, const CTxMemPoolEntry& b) -> bool {
return a.GetTx().GetHash() == b.GetTx().GetHash();
Expand All @@ -1181,6 +1169,9 @@ void CTxMemPool::check(CChainState& active_chainstate) const
assert(it->GetSizeWithAncestors() == nSizeCheck);
assert(it->GetSigOpCountWithAncestors() == nSigOpCheck);
assert(it->GetModFeesWithAncestors() == nFeesCheck);
// Sanity check: we are walking in ascending ancestor count order.
assert(prev_ancestor_count <= it->GetCountWithAncestors());
prev_ancestor_count = it->GetCountWithAncestors();

// Check children against mapNextTx
CTxMemPoolEntry::Children setChildrenCheck;
Expand All @@ -1199,24 +1190,12 @@ void CTxMemPool::check(CChainState& active_chainstate) const
// just a sanity check, not definitive that this calc is correct...
assert(it->GetSizeWithDescendants() >= child_sizes + it->GetTxSize());

if (fDependsWait)
waitingOnDependants.push_back(&(*it));
else {
CheckInputsAndUpdateCoins(tx, mempoolDuplicate, spendheight);
}
}
unsigned int stepsSinceLastRemove = 0;
while (!waitingOnDependants.empty()) {
const CTxMemPoolEntry* entry = waitingOnDependants.front();
waitingOnDependants.pop_front();
if (!mempoolDuplicate.HaveInputs(entry->GetTx())) {
waitingOnDependants.push_back(entry);
stepsSinceLastRemove++;
assert(stepsSinceLastRemove < waitingOnDependants.size());
} else {
CheckInputsAndUpdateCoins(entry->GetTx(), mempoolDuplicate, spendheight);
stepsSinceLastRemove = 0;
}
TxValidationState dummy_state; // Not used. CheckTxInputs() should always pass
CAmount txfee = 0;
assert(!tx.IsCoinBase());
assert(Consensus::CheckTxInputs(tx, dummy_state, mempoolDuplicate, spendheight, txfee));
for (const auto& input: tx.vin) mempoolDuplicate.SpendCoin(input.prevout);
AddCoins(mempoolDuplicate, tx, std::numeric_limits<int>::max());
}
for (auto it = mapNextTx.cbegin(); it != mapNextTx.cend(); it++) {
uint256 hash = it->second->GetHash();
Expand Down
2 changes: 1 addition & 1 deletion src/txmempool.h
Original file line number Diff line number Diff line change
Expand Up @@ -648,7 +648,7 @@ class CTxMemPool
* all inputs are in the mapNextTx array). If sanity-checking is turned off,
* check does nothing.
*/
void check(CChainState& active_chainstate) const EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
void check(const CCoinsViewCache& active_coins_tip, int64_t spendheight) const EXCLUSIVE_LOCKS_REQUIRED(::cs_main);

// addUnchecked must updated state for all ancestors of a given transaction,
// to track size/count of descendant transactions. First version of
Expand Down
8 changes: 1 addition & 7 deletions src/validation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1387,12 +1387,6 @@ void UpdateCoins(const CTransaction& tx, CCoinsViewCache& inputs, CTxUndo &txund
AddCoins(inputs, tx, nHeight);
}

void UpdateCoins(const CTransaction& tx, CCoinsViewCache& inputs, int nHeight)
{
CTxUndo txundo;
UpdateCoins(tx, inputs, txundo, nHeight);
}

bool CScriptCheck::operator()() {
const CScript &scriptSig = ptxTo->vin[nIn].scriptSig;
PrecomputedTransactionData txdata(*ptxTo);
Expand Down Expand Up @@ -2940,7 +2934,7 @@ bool CChainState::ActivateBestChainStep(BlockValidationState& state, CBlockIndex
// any disconnected transactions back to the mempool.
MaybeUpdateMempoolForReorg(disconnectpool, true);
}
if (m_mempool) m_mempool->check(*this);
if (m_mempool) m_mempool->check(this->CoinsTip(), this->m_chain.Height() + 1);

CheckForkWarningConditions();

Expand Down
3 changes: 0 additions & 3 deletions src/validation.h
Original file line number Diff line number Diff line change
Expand Up @@ -265,9 +265,6 @@ bool GetUTXOCoin(CChainState& active_chainstate, const COutPoint& outpoint, Coin
int GetUTXOHeight(CChainState& active_chainstate, const COutPoint& outpoint);
int GetUTXOConfirmations(CChainState& active_chainstate, const COutPoint& outpoint);

/** Apply the effects of this transaction on the UTXO set represented by view */
void UpdateCoins(const CTransaction& tx, CCoinsViewCache& inputs, int nHeight);

/** Transaction validation functions */

/**
Expand Down

0 comments on commit 75797a6

Please sign in to comment.