aboutsummaryrefslogtreecommitdiff
path: root/src/txmempool.cpp
diff options
context:
space:
mode:
authorGreg Sanders <gsanders87@gmail.com>2024-01-19 15:20:33 -0500
committerGreg Sanders <gsanders87@gmail.com>2024-03-18 10:32:00 -0400
commit2079b80854e2595f6f696e7c13a56c7f2a7da9f4 (patch)
treeab42fae71ea1aa30bccdc9598aa02284a3f93d98 /src/txmempool.cpp
parent66d966dcfaad3638f84654e710f403cb0a0a2ac7 (diff)
downloadbitcoin-2079b80854e2595f6f696e7c13a56c7f2a7da9f4.tar.xz
Implement ImprovesFeerateDiagram
This new function takes the populated sets of direct and all conflicts computed in the current mempool, assuming the replacements are a single chunk, and computes a diagram check. The diagram check only works against cluster sizes of 2 or less, and fails if it encounters a different topology. Co-authored-by: Suhas Daftuar <sdaftuar@chaincode.com>
Diffstat (limited to 'src/txmempool.cpp')
-rw-r--r--src/txmempool.cpp129
1 files changed, 129 insertions, 0 deletions
diff --git a/src/txmempool.cpp b/src/txmempool.cpp
index 0bee27c2b2..4047ceda3c 100644
--- a/src/txmempool.cpp
+++ b/src/txmempool.cpp
@@ -17,6 +17,7 @@
#include <random.h>
#include <reverse_iterator.h>
#include <util/check.h>
+#include <util/feefrac.h>
#include <util/moneystr.h>
#include <util/overflow.h>
#include <util/result.h>
@@ -1238,3 +1239,131 @@ std::vector<CTxMemPool::txiter> CTxMemPool::GatherClusters(const std::vector<uin
}
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::CalculateFeerateDiagramsForRBF(CAmount replacement_fees, int64_t replacement_vsize, const setEntries& direct_conflicts, const setEntries& all_conflicts)
+{
+ Assume(replacement_vsize > 0);
+
+ auto err_string{CheckConflictTopology(direct_conflicts)};
+ 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 each element of all_conflicts either at
+ // its own feerate (followed by any descendant at its own feerate) or as a
+ // single chunk at its 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 : all_conflicts) {
+ // 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> old_diagram = BuildDiagramFromChunks(old_chunks);
+
+ 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 : direct_conflicts) {
+ // 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 (!all_conflicts.count(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_fees, int32_t(replacement_vsize));
+
+ // No topology restrictions post-chunking; sort
+ std::sort(new_chunks.begin(), new_chunks.end(), std::greater());
+ std::vector<FeeFrac> new_diagram = BuildDiagramFromChunks(new_chunks);
+ return std::make_pair(old_diagram, new_diagram);
+}