mirror of
https://github.com/bitcoin/bitcoin.git
synced 2026-04-17 19:18:00 +02:00
Implement new RBF logic for cluster mempool
With a total ordering on mempool transactions, we are now able to calculate a transaction's mining score at all times. Use this to improve the RBF logic: - we no longer enforce a "no new unconfirmed parents" rule - we now require that the mempool's feerate diagram must improve in order to accept a replacement - the topology restrictions for conflicts in the package rbf setting have been eliminated Revert the temporary change to mempool_ephemeral_dust.py that were previously made due to RBF validation checks being reordered. Co-authored-by: Gregory Sanders <gsanders87@gmail.com>, glozow <gloriajzhao@gmail.com>
This commit is contained in:
@@ -1287,135 +1287,15 @@ std::vector<CTxMemPool::txiter> CTxMemPool::GatherClusters(const std::vector<Txi
|
||||
return clustered_txs;
|
||||
}
|
||||
|
||||
std::optional<std::string> CTxMemPool::CheckConflictTopology(const setEntries& direct_conflicts)
|
||||
{
|
||||
for (const auto& direct_conflict : direct_conflicts) {
|
||||
// Ancestor and descendant counts are inclusive of the tx itself.
|
||||
const auto ancestor_count{direct_conflict->GetCountWithAncestors()};
|
||||
const auto descendant_count{direct_conflict->GetCountWithDescendants()};
|
||||
const bool has_ancestor{ancestor_count > 1};
|
||||
const bool has_descendant{descendant_count > 1};
|
||||
const auto& txid_string{direct_conflict->GetSharedTx()->GetHash().ToString()};
|
||||
// The only allowed configurations are:
|
||||
// 1 ancestor and 0 descendant
|
||||
// 0 ancestor and 1 descendant
|
||||
// 0 ancestor and 0 descendant
|
||||
if (ancestor_count > 2) {
|
||||
return strprintf("%s has %u ancestors, max 1 allowed", txid_string, ancestor_count - 1);
|
||||
} else if (descendant_count > 2) {
|
||||
return strprintf("%s has %u descendants, max 1 allowed", txid_string, descendant_count - 1);
|
||||
} else if (has_ancestor && has_descendant) {
|
||||
return strprintf("%s has both ancestor and descendant, exceeding cluster limit of 2", txid_string);
|
||||
}
|
||||
// Additionally enforce that:
|
||||
// If we have a child, we are its only parent.
|
||||
// If we have a parent, we are its only child.
|
||||
if (has_descendant) {
|
||||
const auto& our_child = direct_conflict->GetMemPoolChildrenConst().begin();
|
||||
if (our_child->get().GetCountWithAncestors() > 2) {
|
||||
return strprintf("%s is not the only parent of child %s",
|
||||
txid_string, our_child->get().GetSharedTx()->GetHash().ToString());
|
||||
}
|
||||
} else if (has_ancestor) {
|
||||
const auto& our_parent = direct_conflict->GetMemPoolParentsConst().begin();
|
||||
if (our_parent->get().GetCountWithDescendants() > 2) {
|
||||
return strprintf("%s is not the only child of parent %s",
|
||||
txid_string, our_parent->get().GetSharedTx()->GetHash().ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
util::Result<std::pair<std::vector<FeeFrac>, std::vector<FeeFrac>>> CTxMemPool::ChangeSet::CalculateChunksForRBF()
|
||||
{
|
||||
LOCK(m_pool->cs);
|
||||
FeeFrac replacement_feerate{0, 0};
|
||||
for (auto it : m_entry_vec) {
|
||||
replacement_feerate += {it->GetModifiedFee(), it->GetTxSize()};
|
||||
|
||||
if (!CheckMemPoolPolicyLimits()) {
|
||||
return util::Error{Untranslated("cluster size limit exceeded")};
|
||||
}
|
||||
|
||||
auto err_string{m_pool->CheckConflictTopology(m_to_remove)};
|
||||
if (err_string.has_value()) {
|
||||
// Unsupported topology for calculating a feerate diagram
|
||||
return util::Error{Untranslated(err_string.value())};
|
||||
}
|
||||
|
||||
// new diagram will have chunks that consist of each ancestor of
|
||||
// direct_conflicts that is at its own fee/size, along with the replacement
|
||||
// tx/package at its own fee/size
|
||||
|
||||
// old diagram will consist of the ancestors and descendants of each element of
|
||||
// all_conflicts. every such transaction will either be at its own feerate (followed
|
||||
// by any descendant at its own feerate), or as a single chunk at the descendant's
|
||||
// ancestor feerate.
|
||||
|
||||
std::vector<FeeFrac> old_chunks;
|
||||
// Step 1: build the old diagram.
|
||||
|
||||
// The above clusters are all trivially linearized;
|
||||
// they have a strict topology of 1 or two connected transactions.
|
||||
|
||||
// OLD: Compute existing chunks from all affected clusters
|
||||
for (auto txiter : m_to_remove) {
|
||||
// Does this transaction have descendants?
|
||||
if (txiter->GetCountWithDescendants() > 1) {
|
||||
// Consider this tx when we consider the descendant.
|
||||
continue;
|
||||
}
|
||||
// Does this transaction have ancestors?
|
||||
FeeFrac individual{txiter->GetModifiedFee(), txiter->GetTxSize()};
|
||||
if (txiter->GetCountWithAncestors() > 1) {
|
||||
// We'll add chunks for either the ancestor by itself and this tx
|
||||
// by itself, or for a combined package.
|
||||
FeeFrac package{txiter->GetModFeesWithAncestors(), static_cast<int32_t>(txiter->GetSizeWithAncestors())};
|
||||
if (individual >> package) {
|
||||
// The individual feerate is higher than the package, and
|
||||
// therefore higher than the parent's fee. Chunk these
|
||||
// together.
|
||||
old_chunks.emplace_back(package);
|
||||
} else {
|
||||
// Add two points, one for the parent and one for this child.
|
||||
old_chunks.emplace_back(package - individual);
|
||||
old_chunks.emplace_back(individual);
|
||||
}
|
||||
} else {
|
||||
old_chunks.emplace_back(individual);
|
||||
}
|
||||
}
|
||||
|
||||
// No topology restrictions post-chunking; sort
|
||||
std::sort(old_chunks.begin(), old_chunks.end(), std::greater());
|
||||
|
||||
std::vector<FeeFrac> new_chunks;
|
||||
|
||||
/* Step 2: build the NEW diagram
|
||||
* CON = Conflicts of proposed chunk
|
||||
* CNK = Proposed chunk
|
||||
* NEW = OLD - CON + CNK: New diagram includes all chunks in OLD, minus
|
||||
* the conflicts, plus the proposed chunk
|
||||
*/
|
||||
|
||||
// OLD - CON: Add any parents of direct conflicts that are not conflicted themselves
|
||||
for (auto direct_conflict : m_to_remove) {
|
||||
// If a direct conflict has an ancestor that is not in all_conflicts,
|
||||
// it can be affected by the replacement of the child.
|
||||
if (direct_conflict->GetMemPoolParentsConst().size() > 0) {
|
||||
// Grab the parent.
|
||||
const CTxMemPoolEntry& parent = direct_conflict->GetMemPoolParentsConst().begin()->get();
|
||||
if (!m_to_remove.contains(m_pool->mapTx.iterator_to(parent))) {
|
||||
// This transaction would be left over, so add to the NEW
|
||||
// diagram.
|
||||
new_chunks.emplace_back(parent.GetModifiedFee(), parent.GetTxSize());
|
||||
}
|
||||
}
|
||||
}
|
||||
// + CNK: Add the proposed chunk itself
|
||||
new_chunks.emplace_back(replacement_feerate);
|
||||
|
||||
// No topology restrictions post-chunking; sort
|
||||
std::sort(new_chunks.begin(), new_chunks.end(), std::greater());
|
||||
return std::make_pair(old_chunks, new_chunks);
|
||||
return m_pool->m_txgraph->GetMainStagingDiagrams();
|
||||
}
|
||||
|
||||
CTxMemPool::ChangeSet::TxHandle CTxMemPool::ChangeSet::StageAddition(const CTransactionRef& tx, const CAmount fee, int64_t time, unsigned int entry_height, uint64_t entry_sequence, bool spends_coinbase, int64_t sigops_cost, LockPoints lp)
|
||||
@@ -1438,6 +1318,7 @@ CTxMemPool::ChangeSet::TxHandle CTxMemPool::ChangeSet::StageAddition(const CTran
|
||||
}
|
||||
|
||||
m_entry_vec.push_back(newit);
|
||||
|
||||
return newit;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user