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:
Suhas Daftuar
2023-09-21 13:19:32 -04:00
parent ff8f115dec
commit 216e693729
11 changed files with 182 additions and 758 deletions

View File

@@ -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;
}