/ src / txgraph.h
txgraph.h
  1  // Copyright (c) The Bitcoin Core developers
  2  // Distributed under the MIT software license, see the accompanying
  3  // file COPYING or http://www.opensource.org/licenses/mit-license.php.
  4  
  5  #include <compare>
  6  #include <stdint.h>
  7  #include <memory>
  8  #include <vector>
  9  
 10  #include <util/feefrac.h>
 11  
 12  #ifndef BITCOIN_TXGRAPH_H
 13  #define BITCOIN_TXGRAPH_H
 14  
 15  static constexpr unsigned MAX_CLUSTER_COUNT_LIMIT{64};
 16  
 17  /** Data structure to encapsulate fees, sizes, and dependencies for a set of transactions.
 18   *
 19   * Each TxGraph represents one or two such graphs ("main", and optionally "staging"), to allow for
 20   * working with batches of changes that may still be discarded.
 21   *
 22   * The connected components within each transaction graph are called clusters: whenever one
 23   * transaction is reachable from another, through any sequence of is-parent-of or is-child-of
 24   * relations, they belong to the same cluster (so clusters include parents, children, but also
 25   * grandparents, siblings, cousins twice removed, ...).
 26   *
 27   * For each graph, TxGraph implicitly defines an associated total ordering on its transactions
 28   * (its linearization) that respects topology (parents go before their children), aiming for it to
 29   * be close to the optimal order those transactions should be mined in if the goal is fee
 30   * maximization, though this is a best effort only, not a strong guarantee.
 31   *
 32   * For more explanation, see https://delvingbitcoin.org/t/introduction-to-cluster-linearization/1032
 33   *
 34   * This linearization is partitioned into chunks: groups of transactions that according to this
 35   * order would be mined together. Each chunk consists of the highest-feerate prefix of what remains
 36   * of the linearization after removing previous chunks. TxGraph guarantees that the maintained
 37   * linearization always results in chunks consisting of transactions that are connected. A chunk's
 38   * transactions always belong to the same cluster.
 39   *
 40   * The interface is designed to accommodate an implementation that only stores the transitive
 41   * closure of dependencies, so if B spends C, it does not distinguish between "A spending B" and
 42   * "A spending both B and C".
 43   */
 44  class TxGraph
 45  {
 46  public:
 47      /** Internal identifier for a transaction within a TxGraph. */
 48      using GraphIndex = uint32_t;
 49  
 50      /** Data type used to reference transactions within a TxGraph.
 51       *
 52       * Every transaction within a TxGraph has exactly one corresponding TxGraph::Ref, held by users
 53       * of the class. Destroying the TxGraph::Ref removes the corresponding transaction (in both the
 54       * main and staging graphs).
 55       *
 56       * Users of the class can inherit from TxGraph::Ref. If all Refs are inherited this way, the
 57       * Ref* pointers returned by TxGraph functions can be cast to, and used as, this inherited type.
 58       */
 59      class Ref;
 60  
 61      /** Virtual destructor, so inheriting is safe. */
 62      virtual ~TxGraph() = default;
 63      /** Construct a new transaction with the specified feerate, and return a Ref to it.
 64       *  If a staging graph exists, the new transaction is only created there. In all
 65       *  further calls, only Refs created by AddTransaction() are allowed to be passed to this
 66       *  TxGraph object (or empty Ref objects). Ref objects may outlive the TxGraph they were
 67       *  created for. */
 68      [[nodiscard]] virtual Ref AddTransaction(const FeePerWeight& feerate) noexcept = 0;
 69      /** Remove the specified transaction. If a staging graph exists, the removal only happens
 70       *  there. This is a no-op if the transaction was already removed.
 71       *
 72       * TxGraph may internally reorder transaction removals with dependency additions for
 73       * performance reasons. If together with any transaction removal all its descendants, or all
 74       * its ancestors, are removed as well (which is what always happens in realistic scenarios),
 75       * this reordering will not affect the behavior of TxGraph.
 76       *
 77       * As an example, imagine 3 transactions A,B,C where B depends on A. If a dependency of C on B
 78       * is added, and then B is deleted, C will still depend on A. If the deletion of B is reordered
 79       * before the C->B dependency is added, the dependency adding has no effect. If, together with
 80       * the deletion of B also either A or C is deleted, there is no distinction between the
 81       * original order case and the reordered case.
 82       */
 83      virtual void RemoveTransaction(const Ref& arg) noexcept = 0;
 84      /** Add a dependency between two specified transactions. If a staging graph exists, the
 85       *  dependency is only added there. Parent may not be a descendant of child already (but may
 86       *  be an ancestor of it already, in which case this is a no-op). If either transaction is
 87       *  already removed, this is a no-op. */
 88      virtual void AddDependency(const Ref& parent, const Ref& child) noexcept = 0;
 89      /** Modify the fee of the specified transaction, in both the main graph and the staging
 90       *  graph if it exists. Wherever the transaction does not exist (or was removed), this has no
 91       *  effect. */
 92      virtual void SetTransactionFee(const Ref& arg, int64_t fee) noexcept = 0;
 93  
 94      /** TxGraph is internally lazy, and will not compute many things until they are needed.
 95       *  Calling DoWork will compute everything now, so that future operations are fast. This can be
 96       *  invoked while oversized. */
 97      virtual void DoWork() noexcept = 0;
 98  
 99      /** Create a staging graph (which cannot exist already). This acts as if a full copy of
100       *  the transaction graph is made, upon which further modifications are made. This copy can
101       *  be inspected, and then either discarded, or the main graph can be replaced by it by
102       *  committing it. */
103      virtual void StartStaging() noexcept = 0;
104      /** Discard the existing active staging graph (which must exist). */
105      virtual void AbortStaging() noexcept = 0;
106      /** Replace the main graph with the staging graph (which must exist). */
107      virtual void CommitStaging() noexcept = 0;
108      /** Check whether a staging graph exists. */
109      virtual bool HaveStaging() const noexcept = 0;
110  
111      /** Determine whether the graph is oversized (contains a connected component of more than the
112       *  configured maximum cluster count). If main_only is false and a staging graph exists, it is
113       *  queried; otherwise the main graph is queried. Some of the functions below are not available
114       *  for oversized graphs. The mutators above are always available. Removing a transaction by
115       *  destroying its Ref while staging exists will not clear main's oversizedness until staging
116       *  is aborted or committed. */
117      virtual bool IsOversized(bool main_only = false) noexcept = 0;
118      /** Determine whether arg exists in the graph (i.e., was not removed). If main_only is false
119       *  and a staging graph exists, it is queried; otherwise the main graph is queried. This is
120       *  available even for oversized graphs. */
121      virtual bool Exists(const Ref& arg, bool main_only = false) noexcept = 0;
122      /** Get the individual transaction feerate of transaction arg. Returns the empty FeePerWeight
123       *  if arg does not exist in either main or staging. This is available even for oversized
124       *  graphs. */
125      virtual FeePerWeight GetIndividualFeerate(const Ref& arg) noexcept = 0;
126      /** Get the feerate of the chunk which transaction arg is in, in the main graph. Returns the
127       *  empty FeePerWeight if arg does not exist in the main graph. The main graph must not be
128       *  oversized. */
129      virtual FeePerWeight GetMainChunkFeerate(const Ref& arg) noexcept = 0;
130      /** Get pointers to all transactions in the cluster which arg is in. The transactions are
131       *  returned in graph order. If main_only is false and a staging graph exists, it is queried;
132       *  otherwise the main graph is queried. The queried graph must not be oversized. Returns {} if
133       *  arg does not exist in the queried graph. */
134      virtual std::vector<Ref*> GetCluster(const Ref& arg, bool main_only = false) noexcept = 0;
135      /** Get pointers to all ancestors of the specified transaction (including the transaction
136       *  itself), in unspecified order. If main_only is false and a staging graph exists, it is
137       *  queried; otherwise the main graph is queried. The queried graph must not be oversized.
138       *  Returns {} if arg does not exist in the graph. */
139      virtual std::vector<Ref*> GetAncestors(const Ref& arg, bool main_only = false) noexcept = 0;
140      /** Get pointers to all descendants of the specified transaction (including the transaction
141       *  itself), in unspecified order. If main_only is false and a staging graph exists, it is
142       *  queried; otherwise the main graph is queried. The queried graph must not be oversized.
143       *  Returns {} if arg does not exist in the graph. */
144      virtual std::vector<Ref*> GetDescendants(const Ref& arg, bool main_only = false) noexcept = 0;
145      /** Like GetAncestors, but return the Refs for all transactions in the union of the provided
146       *  arguments' ancestors (each transaction is only reported once). Refs that do not exist in
147       *  the queried graph are ignored. Null refs are not allowed. */
148      virtual std::vector<Ref*> GetAncestorsUnion(std::span<const Ref* const> args, bool main_only = false) noexcept = 0;
149      /** Like GetDescendants, but return the Refs for all transactions in the union of the provided
150       *  arguments' descendants (each transaction is only reported once). Refs that do not exist in
151       *  the queried graph are ignored. Null refs are not allowed. */
152      virtual std::vector<Ref*> GetDescendantsUnion(std::span<const Ref* const> args, bool main_only = false) noexcept = 0;
153      /** Get the total number of transactions in the graph. If main_only is false and a staging
154       *  graph exists, it is queried; otherwise the main graph is queried. This is available even
155       *  for oversized graphs. */
156      virtual GraphIndex GetTransactionCount(bool main_only = false) noexcept = 0;
157      /** Compare two transactions according to their order in the main graph. Both transactions must
158       *  be in the main graph. The main graph must not be oversized. */
159      virtual std::strong_ordering CompareMainOrder(const Ref& a, const Ref& b) noexcept = 0;
160      /** Count the number of distinct clusters that the specified transactions belong to. If
161       *  main_only is false and a staging graph exists, staging clusters are counted. Otherwise,
162       *  main clusters are counted. Refs that do not exist in the queried graph are ignored. Refs
163       *  can not be null. The queried graph must not be oversized. */
164      virtual GraphIndex CountDistinctClusters(std::span<const Ref* const>, bool main_only = false) noexcept = 0;
165  
166      /** Perform an internal consistency check on this object. */
167      virtual void SanityCheck() const = 0;
168  
169  protected:
170      // Allow TxGraph::Ref to call UpdateRef and UnlinkRef.
171      friend class TxGraph::Ref;
172      /** Inform the TxGraph implementation that a TxGraph::Ref has moved. */
173      virtual void UpdateRef(GraphIndex index, Ref& new_location) noexcept = 0;
174      /** Inform the TxGraph implementation that a TxGraph::Ref was destroyed. */
175      virtual void UnlinkRef(GraphIndex index) noexcept = 0;
176      // Allow TxGraph implementations (inheriting from it) to access Ref internals.
177      static TxGraph*& GetRefGraph(Ref& arg) noexcept { return arg.m_graph; }
178      static TxGraph* GetRefGraph(const Ref& arg) noexcept { return arg.m_graph; }
179      static GraphIndex& GetRefIndex(Ref& arg) noexcept { return arg.m_index; }
180      static GraphIndex GetRefIndex(const Ref& arg) noexcept { return arg.m_index; }
181  
182  public:
183      class Ref
184      {
185          // Allow TxGraph's GetRefGraph and GetRefIndex to access internals.
186          friend class TxGraph;
187          /** Which Graph the Entry lives in. nullptr if this Ref is empty. */
188          TxGraph* m_graph = nullptr;
189          /** Index into the Graph's m_entries. Only used if m_graph != nullptr. */
190          GraphIndex m_index = GraphIndex(-1);
191      public:
192          /** Construct an empty Ref. Non-empty Refs can only be created using
193           *  TxGraph::AddTransaction. */
194          Ref() noexcept = default;
195          /** Destroy this Ref. If it is not empty, the corresponding transaction is removed (in both
196           *  main and staging, if it exists). */
197          virtual ~Ref();
198          // Support moving a Ref.
199          Ref& operator=(Ref&& other) noexcept;
200          Ref(Ref&& other) noexcept;
201          // Do not permit copy constructing or copy assignment. A TxGraph entry can have at most one
202          // Ref pointing to it.
203          Ref& operator=(const Ref&) = delete;
204          Ref(const Ref&) = delete;
205      };
206  };
207  
208  /** Construct a new TxGraph with the specified limit on transactions within a cluster. That
209   *  number cannot exceed MAX_CLUSTER_COUNT_LIMIT. */
210  std::unique_ptr<TxGraph> MakeTxGraph(unsigned max_cluster_count) noexcept;
211  
212  #endif // BITCOIN_TXGRAPH_H