LCOV - code coverage report
Current view: top level - src - txgraph.h Coverage Total Hit
Test: fuzz_coverage.info Lines: 100.0 % 8 8
Test Date: 2025-05-19 04:07:08 Functions: - 0 0
Branches: 100.0 % 30 30

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

Generated by: LCOV version 2.0-1