cluster mempool: introduce TxGraph #31363

pull sipa wants to merge 15 commits into bitcoin:master from sipa:202411_txgraph changing 8 files +2855 −14
  1. sipa commented at 3:59 pm on November 24, 2024: member

    Part of cluster mempool: #30289.

    1. Overview

    This introduces the TxGraph class, which encapsulates knowledge about the fees, sizes, and dependencies between all mempool transactions, but nothing else. In particular, it lacks knowledge about CTransaction, inputs, outputs, txids, wtxids, prioritization, validatity, policy rules, and a lot more. Being restricted to just those aspects of the mempool makes the behavior very easy to fully specify (ignoring the actual linearizations produced), and write simulation-based tests for (which are included in this PR).

    2. Interface

    The interface can be largely categorized into:

    • Mutation functions:
      • AddTransaction (add a new transaction with specified feerate, and get a Ref object back to identify it).
      • RemoveTransaction (given a Ref object, remove the transaction).
      • AddDependency (given two Ref objects, add a dependency between them).
      • SetTransactionFeerate (modify the feerate associated with a Ref object).
    • Inspector functions:
      • GetAncestors (get the ancestor set in the form of Ref* pointers)
      • GetDescendants (get the descendant set in the form of Ref* pointers)
      • GetCluster (get the connected component set in the form of Ref* pointers, in the order they would be mined).
      • GetIndividualFeerate (get the feerate of a tranaction)
      • GetChunkFeerate (get the mining score of a transaction)
      • CountDistinctClusters (count the number of distinct clusters a list of Refs belong to)
    • Staging functions:
      • StartStaging (make all future mutations operate on a proposed transaction graph)
      • CommitStaging (apply all the changes that are staged)
      • AbortStaging (discard all the changes that are staged)
    • Miscellaneous functions:
      • DoWork (do queued-up computations now, so that future operations are fast)

    This TxGraph::Ref type used as a “handle” on transactions in the graph can be inherited from, and the idea is that in the full cluster mempool implementation (#28676, after it is rebased on this), CTxMempoolEntry will inherit from it, and all actually used Ref objects will be CTxMempoolEntrys. With that, the mempool code can just cast any Ref* returned by txgraph to CTxMempoolEntry*.

    3. Implementation

    Internally the graph data is kept in clustered form (partitioned into connected components), for which linearizations are maintained and updated as needed using the cluster_linearize.h algorithms under the hood, but this is hidden from the users of this class. Implementation-wise, mutations are generally applied lazily, appending to queues of to-be-removed transactions and to-be-added dependencies, so they can be batched for higher performance. Inspectors will generally only evaluate as much as is needed to answer queries, with roughly 5 levels of processing to go to fully instantiated and acceptable cluster linearizations, in order:

    1. ApplyRemovals (take batches of to-be-removed transactions and translate them to “holes” in the corresponding Clusters/DepGraphs).
    2. SplitAll (creating holes in Clusters may cause them to break apart into smaller connected components, so make turn them into separate Clusters/linearizations).
    3. GroupClusters (figure out which Clusters will need to be combined in order to add requested to-be-added dependencies, as these may span clusters).
    4. ApplyDependencies (actually merge Clusters as precomputed by GroupClusters, and add the dependencies between them).
    5. MakeAcceptable (perform the LIMO linearization algorithm on Clusters to make sure their linearizations are acceptable).

    4. Future work

    This is only an initial version of TxGraph, and some functionality is missing before #28676 can be rebased on top of it:

    • The ability to get comparative feerate diagrams before/after for the set of staged changes (to evaluate RBF incentive-compatibility).
    • Mining interface (ability to iterate transactions quickly in mining score order) (see #31444).
    • Eviction interface (reverse of mining order, plus memory usage accounting) (see #31444).
    • Ability to inspect the would-be-constructed clusters (so they can be “fixed” if they violate policy rules, as opposed to just accepted/rejected, before applying - this is needed for reorgs which are not optional).
    • Interface for controlling how much effort is spent on LIMO. In this PR it is hardcoded.

    Then there are further improvements possible which would not block other work:

    • Making Cluster a virtual class with different implementations based on transaction count (which could dramatically reduce memory usage, as most Clusters are just a single transaction, for which the current implementation is overkill).
    • The ability to have background thread(s) for improving cluster linearizations.
  2. DrahtBot commented at 3:59 pm on November 24, 2024: contributor

    The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

    Code Coverage & Benchmarks

    For details see: https://corecheck.dev/bitcoin/bitcoin/pulls/31363.

    Reviews

    See the guideline for information on the review process.

    Type Reviewers
    Concept ACK jonatack, ismaelsadeeq

    If your review is incorrectly listed, please react with 👎 to this comment and the bot will ignore it on the next update.

    Conflicts

    No conflicts as of last run.

  3. sipa force-pushed on Nov 24, 2024
  4. DrahtBot commented at 4:20 pm on November 24, 2024: contributor

    🚧 At least one of the CI tasks failed. Debug: https://github.com/bitcoin/bitcoin/runs/33443336095

    Try to run the tests locally, according to the documentation. However, a CI failure may still happen due to a number of reasons, for example:

    • Possibly due to a silent merge conflict (the changes in this pull request being incompatible with the current code in the target branch). If so, make sure to rebase on the latest commit of the target branch.

    • A sanitizer issue, which can only be found by compiling with the sanitizer and running the affected test.

    • An intermittent issue.

    Leave a comment here, if you need help tracking down a confusing failure.

  5. DrahtBot added the label CI failed on Nov 24, 2024
  6. sipa force-pushed on Nov 24, 2024
  7. sipa force-pushed on Nov 24, 2024
  8. DrahtBot removed the label CI failed on Nov 24, 2024
  9. sipa force-pushed on Nov 25, 2024
  10. sipa force-pushed on Nov 25, 2024
  11. sipa force-pushed on Nov 25, 2024
  12. glozow added the label Mempool on Nov 28, 2024
  13. sipa force-pushed on Dec 2, 2024
  14. sipa force-pushed on Dec 4, 2024
  15. jonatack commented at 2:22 pm on December 5, 2024: member
    Concept ACK
  16. sipa force-pushed on Dec 20, 2024
  17. sipa commented at 4:33 pm on December 20, 2024: member

    I’m aware this is a big chunk of code, but perhaps this can aid review a bit.

    The biggest commits is txgraph: (feature) add initial version. To get an idea of what is going on, I think it’s useful to look (in order) at:

    • The src/txgraph.h file added in that commit, as it defines the public interface.
    • The src/test/fuzz/txgraph.cpp test added in the next commit (txgraph: (tests) add simulation fuzz test), as it does to an extent show how the interface is used, and what is expected of it.
    • The data structures (TxGraphImpl and Cluster) in src/txgraph.cpp, and the internal functions corresponding to the 5 processing steps that happen:
      • Batch removal of queued transaction removals: TxGraphImpl::ApplyRemovals and Cluster::ApplyRemovals.
      • Splitting of clusters into components after removing transactions: TxGraphImpl::SplitAll, TxGraphImpl::Split, and Cluster::Split
      • Computing which clusters will need to be combined due to queued dependencies being added between them: TxGraphImpl::GroupClusters.
      • Merging of clusters and applying queued dependencies to them: TxGraphImpl::ApplyDependencies, TxGraphImpl::Merge, Cluster::Merge, Cluster::ApplyDependencies.
      • Making the linearization of clusters acceptable: TxGraphImpl::MakeAcceptable and Cluster::Relinearize.
    • The interface implementation functions that are built on top in TxGraphImpl: AddTransaction, RemoveTransaction, AddDependency, SetTransactionFee, Cleanup, GetAncestors, GetDescendants, GetCluster, GetChunkFeerate, etc.
  18. sipa force-pushed on Dec 22, 2024
  19. sipa commented at 5:23 pm on December 22, 2024: member
    I have pushed a substantial change to the TxGraphImpl::GroupClusters function (in commit “txgraph: (feature) add initial version”). In a benchmark with 64000 transactions being combined into 1000 clusters, this makes it go from ~160ms to ~16ms. I will add the benchmark to this PR when I clean it up.
  20. ismaelsadeeq commented at 7:33 pm on December 24, 2024: member

    Concept ACK

    GetChunkFeerate (get the mining score of a transaction)

    From reading the description and skimming through the code, IIUC TxGraph encapsulates knowledge about fees and sizes but lacks knowledge about prioritization. This implies that the fee it uses is the effective fee, not the modified fee that includes prioritization.

    How are we accounting for the real mining score of a Ref without that prioritization knowledge? The current mining algorithm uses the modified ancestor fee to calculate the mining score.

    Could you clarify how prioritization will be handled post-cluster mempool?

  21. sipa commented at 7:41 pm on December 24, 2024: member

    @ismaelsadeeq TxGraph (and DepGraph, and FeeFrac) just treat “fee” and “size” as numbers whose ratio is to be maximized. These classes don’t care what meaning they correspond to.

    In practice, I expect that the mempool code will provide the modified fee as “fee”, and vsize or weight as “size”.

  22. ismaelsadeeq commented at 8:37 pm on December 24, 2024: member
    Thank you for clarifying. Reading ’lacks knowledge about prioritization’ led me to infer that. But I see now you are talking about mapDeltas
  23. sipa force-pushed on Jan 8, 2025
  24. in src/txgraph.h:124 in 972df15af2 outdated
    134-    virtual bool IsOversized() noexcept = 0;
    135-    /** Get the feerate of the chunk which transaction arg is in. Returns the empty FeeFrac if arg
    136-     *  does not exist. The graph must not be oversized. */
    137-    virtual FeeFrac GetChunkFeerate(const Ref& arg) noexcept = 0;
    138+    virtual bool IsOversized(bool main_only = false) noexcept = 0;
    139+    /** Get the feerate of the chunk which transaction arg is in in the main graph. Returns the
    


    ismaelsadeeq commented at 6:28 pm on January 9, 2025:

    nit when retouching

    In " txgraph: (feature) add staging support" 972df15af28ba6787f096162f776b2973342e674

    0    /** Get the feerate of the chunk which transaction arg is in the main graph. Returns the
    

    sipa commented at 9:49 pm on January 9, 2025:
    Done.
  25. in src/txgraph.h:139 in 0c8dc2323e outdated
    104+    virtual FeeFrac GetChunkFeerate(const Ref& arg) noexcept = 0;
    105+    /** Get the individual transaction feerate of transaction arg. Returns the empty FeeFrac if
    106+     *  arg does not exist. */
    107+    virtual FeeFrac GetIndividualFeerate(const Ref& arg) noexcept = 0;
    108+    /** Get pointers to all transactions in the connected component ("cluster") which arg is in.
    109+     *  The transactions will be returned in a topologically-valid order of acceptable quality.
    


    ismaelsadeeq commented at 6:29 pm on January 9, 2025:

    optional nit when their is need to retouch

    In “txgraph: (feature) add initial version” 0c8dc2323eb1ec34357a807f0860cf0a08a63a75 you mean QualityLevel::ACCEPTABLE right we could just reference the enum like you were doing txgraph.cpp?

    0     *  The transactions will be returned in a topologically-valid order of acceptable quality (QualityLevel::ACCEPTABLE).
    

    sipa commented at 8:07 pm on January 9, 2025:

    I consider the QualityLevel enum to be an implementation detail of the txgraph module that should not leak into its interface.

    However, I guess it would be helpful to specify more clearly what acceptable mean. I’ll think about that; it’s hard to define.

  26. in src/cluster_linearize.h:1372 in 1c5f1c4601 outdated
    1353+        // Figure out which elements elem needs to be placed before.
    1354+        SetType place_before = done & depgraph.Ancestors(elem);
    1355+        // Find which position to place elem in (updating j), continuously moving the elements
    1356+        // in between forward.
    1357+        while (place_before.Any()) {
    1358+            auto to_swap = linearization[len - 1 - (j - 1)];
    


    ismaelsadeeq commented at 6:35 pm on January 9, 2025:
    In “clusterlin: add FixLinearization function + fuzz test” 1c5f1c4601e0a895258cfe073125361f7a6ea012 Is it safe to get the ClusterIndex when j is 0, we will subtract 1 from uint32_t which will result to a large positive number?

    sipa commented at 8:13 pm on January 9, 2025:

    j cannot be 0. If it were, there is necessarily nothing elem needs to be moved before anymore, and place_before would be empty, and the loop would have terminated.

    Will add an Assume(j > 0); on the next push.

  27. sipa force-pushed on Jan 9, 2025
  28. sipa commented at 9:49 pm on January 9, 2025: member

    A few changes:

    • Added a TxGraph::DoWork() functions which performs queued-up computations now, so that future operations are fast.
    • Make Ref& arguments to TxGraph::AddDependency, TxGraph::RemoveTransaction, and TxGraph::SetTransactionFee const. They do not modify the Ref itself; they only modify what the Ref points to.
    • Address review comments by @ismaelsadeeq here, and @theuni on #31553.
  29. clusterlin: add FixLinearization function + fuzz test
    This function takes an existing ordering for transactions in a DepGraph, and
    makes it a valid linearization for it (i.e., topological). Any topological
    prefix of the input remains untouched.
    30beeff0ae
  30. clusterlin: make IsAcyclic() a DepGraph member function
    ... instead of being a separate test-only function.
    91fcc73089
  31. txgraph: (feature) add initial version
    This adds an initial version of the txgraph module, with the TxGraph class.
    It encapsulates knowledge about the fees, sizes, and dependencies between all
    mempool transactions, but nothing else.
    
    In particular, it lacks knowledge about txids, inputs, outputs, CTransactions,
    ... and so for. Instead, it exposes a generic TxGraph::Ref type to reference
    nodes in the TxGraph, which can be passed around and stored by layers on top.
    0eb039bcf3
  32. txgraph: (tests) add simulation fuzz test
    This adds a simulation fuzz test for txgraph, by comparing with a naive
    reimplementation that models the entire graph as a single DepGraph, and
    clusters in TxGraph as connected components within that DepGraph.
    18c8eafcd7
  33. txgraph: (tests) add internal sanity check function
    To make testing more powerful, expose a function to perform an internal sanity
    check on the state of a TxGraph. This is especially important as TxGraphImpl
    contains many redundantly represented pieces of information:
    
    * graph contains clusters, which refer to entries, but the entries refer back
    * graph maintains pointers to Ref objects, which point back to the graph.
    
    This lets us make sure they are always in sync.
    ca9e539c9e
  34. txgraph: (feature) make max cluster count configurable and "oversize" state
    Instead of leaving the responsibility on higher layers to guarantee that
    no connected component within TxGraph (a barely exposed concept, except through
    GetCluster()) exceeds the cluster count limit, move this responsibility to
    TxGraph itself:
    * TxGraph retains a cluster count limit, but it becomes configurable at construction
      time (this primarily helps with testing that it is properly enforced).
    * It is always allowed to perform mutators on TxGraph, even if they would cause the
      cluster count limit to be exceeded. Instead, TxGraph exposes an IsOversized()
      function, which queries whether it is in a special "oversize" state.
    * During oversize state, many inspectors are unavailable, but mutators remain valid,
      so the higher layer can "fix" the oversize state before continuing.
    652d5aad6e
  35. txgraph: (optimization) delay chunking while sub-acceptable
    Chunk-based information (primarily, chunk feerates) are never accessed without
    first bringing the relevant Clusters to an "acceptable" quality level. Thus,
    while operations are ongoing and Clusters are not acceptable, we can omit
    computing the chunkings and chunk feerates for Clusters.
    2734dcb344
  36. txgraph: (optimization) special-case removal of tail of cluster
    When transactions are removed from the tail of a cluster, we know the existing
    linearization remains acceptable/optimal (if it already was), but may just need
    splitting, so special case these into separate quality levels.
    a5a41bbd61
  37. txgraph: (refactor) group per-graph data in ClusterSet
    This is a preparation for a next commit where a TxGraph will start representing
    potentially two distinct graphs (a main one, and a staging one with proposed
    changes).
    b2b17e7a10
  38. txgraph: (refactor) abstract out ClearLocator
    Move a number of related modifications to TxGraphImpl into a separate
    function for removal of transactions. This is preparation for a later
    commit where this will be useful in more than one place.
    eb05507849
  39. txgraph: (feature) add staging support
    In order to make it easy to evaluate proposed changes to a TxGraph, introduce a
    "staging" mode, where mutators (AddTransaction, AddDependency, RemoveTransaction)
    do not modify the actual graph, but just a staging version of it. That staging
    graph can then be commited (replacing the main one with it), or aborted (discarding
    the staging).
    7b106d7ac0
  40. txgraph: (feature) destroying Ref means removing transaction
    Before this commit, if a TxGraph::Ref object is destroyed, it becomes impossible
    to refer to, but the actual corresponding transaction node in the TxGraph remains,
    and remains indefinitely as there is no way to remove it.
    
    Fix this by making the destruction of TxGraph::Ref trigger immediate removal of
    the corresponding transaction in TxGraph, both in main and staging if it exists.
    05b35a0f4f
  41. txgraph: (feature) expose ability to compare transactions
    In order to make it possible for higher layers to compare transaction quality
    (ordering within the implicit total ordering on the mempool), expose a comparison
    function and test it.
    4005a901af
  42. txgraph: (feature) Add DoWork function
    This can be called when the caller has time to spend now, and wants future operations
    to be fast.
    114cd31eb0
  43. txgraph: (feature) Add CountDistinctClusters function 0da43c2217
  44. sipa force-pushed on Jan 16, 2025
  45. sipa commented at 9:14 pm on January 16, 2025: member

    Added an additional inspector function:

    • CountDistinctClusters, which efficiently counts how many distinct clusters a list of specified Refs belong to, for helping with enforcing RBF cluster policy limits.

github-metadata-mirror

This is a metadata mirror of the GitHub repository bitcoin/bitcoin. This site is not affiliated with GitHub. Content is generated from a GitHub metadata backup.
generated: 2025-01-21 06:12 UTC

This site is hosted by @0xB10C
More mirrored repositories can be found on mirror.b10c.me