LCOV - code coverage report
Current view: top level - discof/replay - fd_rdisp.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 0 1035 0.0 %
Date: 2026-10-22 18:10:43 Functions: 0 31 0.0 %

          Line data    Source code
       1             : #include "fd_rdisp.h"
       2             : #include "fd_rdisp_mq.h"
       3             : #include "../../util/fd_hash32.h"
       4             : #include <math.h> /* for the EMA */
       5             : 
       6             : /* The conflict graph that this file builds is not a general DAG, but
       7             :    the union of several special account-conflict graphs.  Each
       8             :    account-conflict graph has special structure:
       9             :                            ---> 3 --
      10             :                           /          \
      11             :                          /            v
      12             :                1  ---> 2 -----> 4 --> 6 ----> 7
      13             :                         \             ^
      14             :                          \           /
      15             :                           ----> 5 --
      16             : 
      17             :    That is, the graph is almost a line, but may have fan-out and fan-in
      18             :    regions.  The tricky part about representing a graph like this
      19             :    without dynamic memory allocation is that nodes may have arbitrary
      20             :    in-degree and out-degree.  Thus, we use a pretty standard trick and
      21             :    use sibling pointers, denoted below with dotted lines.  Each node
      22             :    maintains at most one successor pointer and at most one sibling
      23             :    pointer.  Although not shown below, the sibling pointers are
      24             :    circularly linked, so 5's sibling is 3.  Additionally, though not
      25             :    shown below, the child of the last node (7) stores information about
      26             :    what account address all this is for, which facilitates deleting the
      27             :    last node in the graph.
      28             : 
      29             : 
      30             :                               --> 3 --
      31             :                              /    ^   \
      32             :                             /     :    \
      33             :                            /      V     v
      34             :                  1  ---> 2        4 --> 6 ----> 7
      35             :                                   ^     ^
      36             :                                   :    /
      37             :                                   V   /
      38             :                                   5 --
      39             : 
      40             :    The normal edge 2->3 along with the sibling edge 3..>4 implies a
      41             :    normal edge 2->4.  That then transitively implies an edge 2->5.
      42             : 
      43             :    We want each node to maintain a count of its in-degree so that we
      44             :    know when it can be executed.  The implied edges also count for the
      45             :    in-degree.  In this example, node 1 has in-degree 0, node 6 has
      46             :    in-degree 3, and the rest have in-degree 1.
      47             : 
      48             :    Maintaining each account-conflict graph is relatively easy given the
      49             :    operations we want to support.  Only the details about sibling edges
      50             :    are worth mentioning.  For example, when deleting node 2, we
      51             :    decrement the in-degree count for its successor, and then follow the
      52             :    sibling pointers, decrementing all the in-degree counts as we go to
      53             :    mark the deletion of the implied edges.  Sibling edges also need to
      54             :    be doubly linked, so that e.g. nodes 3 and 5 can be re-linked in O(1)
      55             :    if node 4 is deleted.  They're also circularly linked as well for
      56             :    convenience.
      57             : 
      58             :    When building the graph, we maintain a map of account to the last
      59             :    node that references it, whether that was a read or write, and
      60             :    whether there are any writers to that account in the graph right now.
      61             :    If the new node reads from an account that was last read, the new
      62             :    node becomes a sibling of the last read, with in-degree increased if
      63             :    there are any writers.  Otherwise, it becomes a successor of the node
      64             :    that last referenced the account. */
      65             : 
      66             : /* For a task like this with lots of graph traversal and pointer
      67             :    chasing, performance is typically limited by memory latency.  That
      68             :    means that the more that can fit in cache, the better the
      69             :    performance.  This implementation uses a lot of bit-packing to
      70             :    improve cache footprint. */
      71             : 
      72             : /* The following structs are all very local to this compilation unit,
      73             :    and so they don't have globally acceptable type names (e.g.
      74             :    fd_rdisp_edge_t). */
      75             : 
      76             : #define MAX_ACCT_PER_TXN FD_TXN_ACCT_ADDR_MAX
      77             : FD_STATIC_ASSERT( MAX_ACCT_PER_TXN<=128UL, max_acct_per_txn );
      78             : 
      79             : /* edge_t: Fields typed edge_t represent an edge in one of the parallel
      80             :    account-conflict DAGs.  Each transaction stores a list of all its
      81             :    outgoing edges.  The type is actually a union of bitfield, but C
      82             :    bitfields are gross, so we just do it manually with macros.
      83             :    If the high bit is not set, then the next 23 bits store the
      84             :    destination of the edge, represented by its index in the pool of
      85             :    transactions.  Then the lowest 8 bits store the account index within
      86             :    that transaction of the edge that is part of the same
      87             :    account-specific DAG as this edge.  Because the max depth is 2^23-1,
      88             :    and each transaction can reference FD_TXN_ACCT_ADDR_MAX accounts,
      89             :    the max accounts that can be referenced fits in 30 bits.
      90             : 
      91             :    If the high bit is set, the second highest bit indicates whether it's
      92             :    a pseudo transaction or if the transaction storing this edge_t value
      93             :    is the last in this specific DAG.  Either way, the lower 30 bits of
      94             :    the value are the index.  This index is in the map_pool for the
      95             :    account pubkey for this DAG if it's a final transaction, and an index
      96             :    into the pseudo transaction pool if it's a pseudo-transaction.  See
      97             :    the comments about the hidden edge outgoing from node 7 in the DAG at
      98             :    the top of this file for an example.
      99             :    Because the max depth is 2^23-1, and each transaction can reference
     100             :    128 accounts, the max accounts that can be referenced fits in 30
     101             :    bits.
     102             :    The proper type would be something like
     103             :    typedef union {
     104             :      struct {
     105             :        uint is_special:1; // == 0
     106             :        uint txn_idx:23;
     107             :        uint acct_idx:8;
     108             :      } e;
     109             :      struct {
     110             :        uint is_special:1; // == 1
     111             :        uint is_last:1;    // == 1
     112             :        uint map_pool_idx:30;
     113             :      } last;
     114             :      struct {
     115             :        uint is_special:1; // == 1
     116             :        uint is_last:1;    // == 0
     117             :        uint pseudo_txn_idx:30;
     118             :      } pseudo;
     119             :    } edge_t;
     120             : 
     121             :    */
     122             : typedef uint edge_t;
     123             : 
     124           0 : #define EDGE_SPECIAL_PTXN     0x80000000U
     125           0 : #define EDGE_SPECIAL_LAST     0xC0000000U
     126           0 : #define EDGE_SPECIAL_MASK     0xC0000000U
     127             : 
     128             : #define EDGE_IS_LAST(x)        (((x)&EDGE_SPECIAL_MASK)==EDGE_SPECIAL_LAST)
     129           0 : #define EDGE_IS_PTXN(x)        (((x)&EDGE_SPECIAL_MASK)==EDGE_SPECIAL_PTXN)
     130           0 : #define EDGE_SPECIAL_IDX(x)    ((x)&(~EDGE_SPECIAL_MASK))
     131           0 : #define EDGE_MAKE_SPECIAL(k,x) ((x)|FD_CONCAT2(EDGE_SPECIAL_,k))
     132             : 
     133             : FD_STATIC_ASSERT( MAX_ACCT_PER_TXN<=128UL, max_acct_per_txn );
     134             : 
     135             : /* fd_rdisp_txn is the representation of a transaction as a node in the
     136             :    DAG. */
     137             : struct fd_rdisp_txn {
     138             :   /* in_degree: The total number of edges summed across all account DAGs
     139             :      with this node as their destination.  In the worst case, all the
     140             :      other transactions in the pool read from each of the max number of
     141             :      accounts that this transaction writes to,  so there are
     142             :      FD_RDISP_MAX_ACCT_PER_TXN*depth edges that come into this node,
     143             :      which fits in about 30 bits, so we have some room for special
     144             :      values.  If in_degree is one of the following values, then the
     145             :      transaction is: */
     146           0 : #define IN_DEGREE_FREE                (UINT_MAX   )
     147           0 : #define IN_DEGREE_UNSTAGED            (UINT_MAX-1U)/* unstaged, not dispatched */
     148           0 : #define IN_DEGREE_DISPATCHED          (UINT_MAX-2U)/* staged,   dispatched */
     149           0 : #define IN_DEGREE_UNSTAGED_DISPATCHED (UINT_MAX-3U)/* unstaged, dispatched */
     150           0 : #define IN_DEGREE_ZOMBIE              (UINT_MAX-4U)/* zombie */
     151             :   /* a transaction that is staged and dispatched is must have an
     152             :      in_degree of 0.  in_degree isn't a meaningful concept for unstaged
     153             :      transactions. */
     154             :   uint    in_degree;
     155             : 
     156             :   /* score: integer part stores how many transactions in the block must
     157             :      have completed before this transaction can be scheduled.  This is
     158             :      useful for transactions marked as serializing.  The fractional part
     159             :      gives some measure of how urgent the transaction is, where lower is
     160             :      more urgent.  This means we can't have more transactions in a block
     161             :      marked as serializing than the first integer that a float cannot
     162             :      represent.  That value is about 16M, which is much higher than the
     163             :      maximum number of transactions in a block, so this is not a
     164             :      problem.  If in the very rare case that there are more than just a
     165             :      few serializing transactions, we will lose a bit of precision for
     166             :      the fractional portion, which makes total sense; if there's not
     167             :      much room for parallel execution, having the optimal parallel
     168             :      execution is not very important. */
     169             :   float   score;
     170             : 
     171             :   /* edge_cnt_etc:
     172             :      0xFFFF0000 (16 bits) for linear block number,
     173             :      0x0000C000 (2 bits) for concurrency lane,
     174             :      0x00003F80 (7 bits) for r_cnt
     175             :      0x0000007F (7 bits) for w_cnt_1.  Transactions have at least one
     176             :      writable account and at most MAX_ACCT_PER_TXN total accounts, so
     177             :      storing w_cnt_1 = w_cnt - 1 fits in 7 bits. */
     178             :   union {
     179             :     uint edge_cnt_etc;
     180             :     /* edge_cnt_etc is only used when the transaction is STAGED and
     181             :        PENDING, READY, or DISPATCHED.  If UNSTAGED or FREE, the next
     182             :        pointer is also here.  It can't be UNSTAGED and FREE at the same
     183             :        time, so there's no conflict with storage there either. */
     184             :     uint unstaged_next;
     185             :     uint free_next;
     186             :     /* If ZOMBIE, pointer back to block is here.  No storage conflict
     187             :        because ZOMBIE is an exclusive state. */
     188             :     uint block_idx;
     189             :   };
     190             : 
     191             : 
     192             :   /* When a transaction writes to an account, it only creates one
     193             :      link.  When a transaction reads from an account, we need the full
     194             :      doubly linked list with its siblings, so it creates 3 edges
     195             :      (child, next, prev).  All edges from writable accounts come first,
     196             :      and we keep track of how many there are.  In the worst case, all
     197             :      accounts are reads, so we size it appropriately. */
     198             :   edge_t edges[3UL*FD_RDISP_MAX_ACCT_PER_TXN]; /* addressed [0, w_cnt_1+1+3*r_cnt) */
     199             : };
     200             : typedef struct fd_rdisp_txn fd_rdisp_txn_t;
     201             : 
     202             : /* To simplify the code, we make fd_rdisp_ptxn_t such that it can be cast
     203             :    as a fd_rdisp_txn_t * and mostly function.  This greatly simplifies
     204             :    the code but is technically UB.  The alias rules say that a write to
     205             :    a field of a pointer of type T1* and a write to a field of a pointer
     206             :    of type T2* should be independent.  To combat this, we need to be
     207             :    careful in each function to either use the ptxn_t * pointer or to
     208             :    immediately cast to txn_t * and only access it via that pointer. */
     209             : struct fd_rdisp_ptxn {
     210             :   /* These fields have the same meaning as in fd_rdisp_txn_t */
     211             :   uint  in_degree;
     212             :   float score;
     213             :   union {
     214             :     uint edge_cnt_etc; /* r_cnt == 1 and w_cnt_1 is 0x7F, but ignored */
     215             :     uint free_next;
     216             :   };
     217             :   /* The whole reason for the difference between a normal transaction
     218             :      and a pseudo-transaction is that this array can be a lot smaller.
     219             :      */
     220             :   edge_t edges[3];
     221             :   /* last maps back to the account for which this pseudo-transaction is an
     222             :      LtHash addition task.  It's normally the same as edges[0], but if a
     223             :      writer is added after this pseudo-transaction is added (for
     224             :      example, in the next block), then edges[0] may have a different
     225             :      value, but we still need to be able to map back to the account
     226             :      address. */
     227             :   edge_t last;
     228             : };
     229             : typedef struct fd_rdisp_ptxn fd_rdisp_ptxn_t;
     230             : 
     231             : #define POOL_NAME  ptxn_pool
     232           0 : #define POOL_NEXT  free_next
     233             : #define POOL_IDX_T uint
     234           0 : #define POOL_T     fd_rdisp_ptxn_t
     235             : #include "../../util/tmpl/fd_pool.c"
     236             : 
     237             : /* Two more definitions:
     238             :    An edge index is an array position within the edges array.  An
     239             :    account index is a position within a transaction's account addresses,
     240             :    reordered so that the writable ones come first.  Since writable
     241             :    accounts use one position in the edges array per account address,
     242             :    these often coincide. */
     243             : 
     244             : 
     245             : /* FOLLOW_EDGE and FOLLOW_EDGE_TXN are helper macros for dealing with
     246             :    edges.  Given an edge_t x, and rdisp disp, FOLLOW_EDGE_TXN
     247             :    returns a pointer to transaction that the edge points to; FOLLOW_EDGE
     248             :    returns a pointer to the (first) edge_t within that transaction that
     249             :    is part of the same DAG as this edge.  FOLLOW_EDGE and
     250             :    FOLLOW_EDGE_TXN must not be called if EDGE_IS_LAST is non-zero.
     251             : 
     252             :    Then the edge index, i.e. the position in the edges array of the
     253             :    (first) edge for an account index is:
     254             :           acct_idx                              if acct_idx<=w_cnt_1
     255             :           w_cnt_1+1 + 3*(acct_idx-w_cnt_1-1)    else.
     256             :   Simplifying gives
     257             :          acct_idx + 2*signed_max( 0, acct_idx-w_cnt_1-1 ).
     258             :   If the edge points to a pseudo-transaction, then __w_cnt_1 is a
     259             :   garbage value, but __idx is forced to 0, which means its value is not
     260             :   used.
     261             :   In doing this calculation, we also basically get for free whether the
     262             :   edge is for a writable account or a readonly account, so we return
     263             :   that as well via the w parameter.  If the child transaction only reads
     264             :   the account address for this DAG, then the next and prev pointers can
     265             :   be accessed using the returned value +1 and +2, respectively. */
     266           0 : #define FOLLOW_EDGE(disp, x, w) (__extension__({                                                                            \
     267           0 :         fd_rdisp_t const * __disp = (disp);                                                                                 \
     268           0 :         uint __e = (x);                                                                                                     \
     269           0 :         edge_t * __edges = EDGE_IS_PTXN(__e) ? __disp->ptxn_pool[EDGE_SPECIAL_IDX(__e)].edges : __disp->pool[__e>>8].edges; \
     270           0 :         uint __w_cnt_1   = EDGE_IS_PTXN(__e) ? 0 : __disp->pool[__e>>8].edge_cnt_etc & 0x7FU;                               \
     271           0 :         uint __idx  = fd_uint_if( EDGE_IS_PTXN(__e), 0U, (__e & 0xFFU) );                                                   \
     272           0 :         (w) = (!EDGE_IS_PTXN(__e)) & (__idx<=__w_cnt_1);                                                                    \
     273           0 :         (void)(w);  /* not robust... */                                                                                     \
     274           0 :         __edges + __idx + 2*fd_int_max( 0, (int)(__idx)-(int)(__w_cnt_1)-1 ); }))
     275             : 
     276           0 : #define FOLLOW_EDGE_TXN(disp, x) (__extension__({                                                                    \
     277           0 :         fd_rdisp_t const * __disp = (disp);                                                                          \
     278           0 :         uint __e = (x);                                                                                              \
     279           0 :         EDGE_IS_PTXN(__e) ? (fd_rdisp_txn_t *)((__disp)->ptxn_pool+EDGE_SPECIAL_IDX(__e)) :                          \
     280           0 :                             ((__disp)->pool+(__e>>8)); }))
     281             : 
     282             : 
     283             : 
     284             : /* The pool and slist are almost the same, but they are used
     285             :    differently, so keep them as different structures for now. */
     286             : 
     287             : #define POOL_NAME     pool
     288           0 : #define POOL_T        fd_rdisp_txn_t
     289             : #define POOL_IDX_T    uint
     290           0 : #define POOL_NEXT     free_next
     291             : #define POOL_SENTINEL 1
     292             : #include "../../util/tmpl/fd_pool.c"
     293             : 
     294             : #define SLIST_NAME unstaged_txn_ll
     295             : #define SLIST_ELE_T fd_rdisp_txn_t
     296           0 : #define SLIST_IDX_T uint
     297           0 : #define SLIST_NEXT  unstaged_next
     298             : #include "../../util/tmpl/fd_slist.c"
     299             : 
     300             : #define DLIST_IDX_T uint
     301           0 : #define DLIST_PREV  edges[0]
     302           0 : #define DLIST_NEXT  edges[1]
     303             : #define DLIST_NAME  zombie_dlist
     304             : #define DLIST_ELE_T fd_rdisp_txn_t
     305             : #include "../../util/tmpl/fd_dlist.c"
     306             : 
     307             : 
     308             : /* ACCT_INFO_STATE: We need to keep just a little metadata about writers
     309             :    in the DAG for an account.  In particular, for correctness, we need
     310             :    to be able to distinguish the case where there are only readers so
     311             :    that we don't increment in_degree when adding a new fd_rdisp_txn.  If
     312             :    we have any writers, the only way to transition into a state where
     313             :    there are only readers is to complete the last writer.  We know we
     314             :    are in this case when the completed node's child doesn't have a
     315             :    child, and the completed node's child is a reader, as indicated by
     316             :    the LAST_REF_WAS_WRITE bit.  LAST_REF_WAS_WRITE also has the
     317             :    advantage of being easy to maintain.  You'd think with those two
     318             :    binary states we'd have used up our 2 bits, but actually, there are
     319             :    only states there, which means we can almost cram in a little more
     320             :    information for free.  In particular, whether we need to create a
     321             :    LtHash addition hash is also very related to ANY_WRITERS and
     322             :    LAST_REF_WAS_WRITE, via the state machine below, which has only 5
     323             :    accessible states out of 8 possible states (we can add a reader or
     324             :    writer from any state. If there are any writers, we can complete the
     325             :    last writer. If it needs an LtHash add pseudo transaction, then we
     326             :    may add a pseudo transaction).
     327             : 
     328             :          add reader
     329             :          |--------|
     330             :          V        |
     331             :       0-----------------
     332             :       | No writers,    |<-------------------------------------------
     333             :       | which implies  |                                           |
     334             :       | LRWW==0 and    |<---------------------------------         |
     335             :       | NEEDS_LTADD==0 |     complete last writer        |         |
     336             :       ------------------                                 |         |
     337             :               |                                          |         |
     338             :           add |             complete last writer         |         |
     339             :        writer |       -----------------------------------+-------  |
     340             :               V       |                                  |      |  |
     341             :       1-----------------        add writer               |      |  |
     342             :       | ANY_WRITERS==1 |<---------------------------|    |      |  |
     343             :    |->| LRWW==1 and    |------------------------    |    |      |  |
     344             :    |  | NEEDS_LTADD==1 |<---|        add pseudo|    |    |      |  |
     345             :    |  ------------------    | add          txn |    |    |      |  |
     346             :    |         ^  |     |     | writer           V    |    |      |  |
     347             :    |         |  |     |-----|          2*--------------------   |  |
     348             :    |     add |  | add                  | Last is pseudo txn |   |  |
     349             :    |  writer |  | reader           |-->| ANY_WRITERS==1     |   |  |
     350             :    |         |  V              add |   | LRWW==0 and        |   |  |
     351             :    |  2-----------------    reader |---| NEEDS_LTADD==0     |   |  |
     352             :    |  | ANY_WRITERS==1 |               ----------------------   |  |
     353             :    |  | LRWW==0 and    |                        ^               |  |
     354             :    |  | NEEDS_LTADD==1 |------------------------|               |  |
     355             :    |  ------------------     add pseudo txn                     |  |
     356             :    |     |        ^  |                                          |  |
     357             :    |     |        |  |                                          |  |
     358             :    |     ----------  |                3------------------       |  |
     359             :    |     add reader  |                | ANY_WRITERS==0  |       |  |
     360             :    |                 ---------------->| LRWW==0 and     |<-------  |
     361             :    |                   complete last  | NEEDS_LTADD==1  |-----------
     362             :    |                      writer      ------------------- add pseudo
     363             :    |                                   |   |        ^       txn
     364             :    |       add writer                  |   |        |
     365             :    |------------------------------------   ----------
     366             :                                            add reader
     367             : 
     368             :    Sadly, we still only have 2 bits, so we collapse the top-left and
     369             :    bottom right states into one state.  The only difference between the
     370             :    two states is whether it is possible to add a pseudo-txn, and that's
     371             :    driven by the rdisp_mq anyway. */
     372             : /*                                                 AW  LRWW   NLT */
     373           0 : #define ACCT_INFO_STATE_NO_WRITERS             0U /*0    0      0  */
     374           0 : #define ACCT_INFO_STATE_LAST_REF_WAS_WRITE     1U /*1    1      1  */
     375           0 : #define ACCT_INFO_STATE_LAST_REF_WAS_READ      2U /*1    0      ?  */
     376           0 : #define ACCT_INFO_STATE_NEEDS_PTXN             3U /*0    0      1  */
     377             : 
     378           0 : #define ACCT_INFO_STATE_IS( val, lane, state) ( (((val)>>(2U*(lane)))&3) == FD_CONCAT2(ACCT_INFO_STATE_, state) )
     379           0 : #define ACCT_INFO_STATE_SET(val, lane, state) (((val) & (uchar)(~(3U<<(2U*(lane))))) | (uchar)(FD_CONCAT2(ACCT_INFO_STATE_, state)<<(2U*(lane))) )
     380             : 
     381             : 
     382             : /* acct_info_t is a node in a map_chain that contains the metadata for a
     383             :    single account address's conflict graph DAG.  In particular, it
     384             :    contains the information needed to know where to insert a node that
     385             :    reads from or writes to the account.  The objects of this type follow
     386             :    this state machine:
     387             : 
     388             :                  FREE  -----> ACTIVE ----> CACHED
     389             :                                 ^            |
     390             :                                 |-------------
     391             :    When FREE, it is in free_acct_dlist only.  When ACTIVE, it is in
     392             :    acct_map only.  When CACHED, it is in both free_acct_dlist and
     393             :    acct_map, and cached==ACCT_INFO_CACHED.
     394             : */
     395           0 : #define ACCT_INFO_CACHED UINT_MAX
     396             : FD_STATIC_ASSERT( ((FD_RDISP_MAX_DEPTH<<8)|0xFFUL)<(ulong)ACCT_INFO_CACHED, acct_info_cached ); /* no edge_t is the mark */
     397             : 
     398             : struct acct_info {
     399             :   /* key, next, and prev are the map_chain fields. Used in the ACTIVE
     400             :      and CACHED states.  next and prev set to 0 when in the FREE state.
     401             :      Element 0 is a sentinel and isn't inserted to the free_acct_dlist,
     402             :      so this is unambiguous. */
     403             :   fd_acct_addr_t key;
     404             :   uint next;
     405             :   uint prev;
     406             : 
     407             :   union {
     408             :     struct {
     409             :       /* This is effectively a pointer to the last node in the DAG for
     410             :          this pubkey, one for each staging lane.
     411             :          EDGE_IS_LAST(FOLLOW_EDGE(disp, last_reference[i])) is non-zero.
     412             :          */
     413             :       edge_t last_reference[4];
     414             : 
     415             :     }; /* When in the ACTIVE state */
     416             :     struct {
     417             :       uint free_ll_next;
     418             :       uint free_ll_prev;
     419             :       /* cached is ACCT_INFO_CACHED in the CACHED state, which tells a
     420             :          CACHED element in acct_map from an ACTIVE one: it aliases
     421             :          last_reference[2], an edge_t of a transaction index in the
     422             :          ACTIVE state, which has its top bit clear.  Not maintained in
     423             :          the FREE state (next and prev tell FREE from CACHED). */
     424             :       uint cached;
     425             :       /* 4 bytes of padding here */
     426             :     }; /* When not in the ACTIVE state, used by the free_acct_dlist */
     427             :   };
     428             :   /* states: a combination of ACCT_INFO_STATE fields above.  Used when
     429             :      ACTIVE. */
     430             :   uint states:8;
     431             : 
     432             : 
     433             :   /* We want to dispatch the READY transactions in an order that
     434             :      maximizes parallelism, but we also want to be able to start
     435             :      dispatching transactions decently well before we have the full
     436             :      conflict graph.  We can do that because we know that contentious
     437             :      accounts tend to stay contentious and uncontentious accounts tend
     438             :      to stay uncontentious.
     439             : 
     440             :      To accomplish this, we maintain a special EMA.  Let x_i be 1 if
     441             :      transaction i references it and 0 if not.  If we squint and assume
     442             :      the transactions are independent and all drawn from some
     443             :      distribution (which is not true, but that's why we squint), an EMA
     444             :      of x_i estimates the probability that the next transaction
     445             :      references this account.  How we use this value is detailed later.
     446             : 
     447             :      We can't update this value for every pubkey for each transaction,
     448             :      so we maintain it in a lazy way, by applying updates only when we
     449             :      need to read the value, which also happens to be every time we want
     450             :      to add a 1 value to the EMA.  We then just need to maintain the
     451             :      last index i at which x_i was updated and the current value.  We
     452             :      only have 24 bits for the index, which means that we can't maintain
     453             :      it in a fork-aware way, which doesn't seem like a problem.  Also,
     454             :      it's possible it can overflow, and that can result in an incorrect
     455             :      value, but that means the account is only referenced ~ 1/2^24
     456             :      transactions, which is also fine.
     457             : 
     458             :      last_ref is in the domain of global_inserted_txn_cnt.  ema_refs is
     459             :      in [0, 1].  Both fields are used in ACTIVE and CACHED.  Maintaining
     460             :      these fields is actually the main reason CACHED exists. */
     461             :   uint   last_ref:24;
     462             :   float  ema_refs;
     463             : };
     464             : typedef struct acct_info acct_info_t;
     465             : 
     466             : FD_STATIC_ASSERT( sizeof(acct_info_t)==64UL, acct_info_t );
     467             : 
     468             : #define MAP_NAME          acct_map
     469           0 : #define MAP_ELE_T         acct_info_t
     470           0 : #define MAP_IDX_T         uint
     471             : #define MAP_KEY_T         fd_acct_addr_t
     472           0 : #define MAP_KEY_HASH(k,s) fd_hash32( (k)->b, (s) )
     473           0 : #define MAP_KEY_EQ(k0,k1) (!memcmp( (k0)->b, (k1)->b, 32UL ))
     474             : #define MAP_OPTIMIZE_RANDOM_ACCESS_REMOVAL 1
     475             : #include "../../util/tmpl/fd_map_chain.c"
     476             : 
     477             : 
     478             : #define DLIST_IDX_T uint
     479           0 : #define DLIST_PREV  free_ll_prev
     480           0 : #define DLIST_NEXT  free_ll_next
     481             : #define DLIST_NAME  free_dlist
     482             : #define DLIST_ELE_T acct_info_t
     483             : #include "../../util/tmpl/fd_dlist.c"
     484             : 
     485             : 
     486             : struct pending_prq_ele {
     487             :   /* lower score means should be scheduled sooner.  Integer part has a
     488             :      special meaning as explained above. */
     489             :   float score;
     490             : 
     491             :   uint linear_block_number;
     492             :   uint txn_idx;
     493             : 
     494             :   /* It seems like we should be able to get this whole struct in 8
     495             :      bytes, but we're a few bits over.
     496             : 
     497             :      23 bits for txn_idx
     498             :      16 bit for linear_block_number
     499             :      Then the integer part of the score needs to be at least 18 or 19
     500             :      bits, but we can't use a custom floating point number. */
     501             : };
     502             : 
     503             : typedef struct pending_prq_ele pending_prq_ele_t;
     504             : 
     505             : 
     506             : #define PRQ_NAME pending_prq
     507           0 : #define PRQ_T    pending_prq_ele_t
     508             : #define PRQ_EXPLICIT_TIMEOUT 0
     509             : /* returns 1 if x is strictly after y */
     510           0 : #define PRQ_AFTER(x,y) (__extension__( {                                            \
     511           0 :             int cmp0 = (int)(x).linear_block_number - (int)(y).linear_block_number; \
     512           0 :             fd_int_if( cmp0!=0, cmp0>0, (x).score>(y).score );                      \
     513           0 :             }))
     514             : #include "../../util/tmpl/fd_prq.c"
     515             : 
     516             : /* We need yet another data structure to keep track of accounts that
     517             :    need LtHash addition tasks for unstaged transactions.  These are part
     518             :    of a map keyed by (block, acct), as stored in ustlt_key_t so that we
     519             :    can quickly query whether the account address is "new" (in the sense
     520             :    explained in the header).  In addition to possibly being part of the
     521             :    map, these are also part of one of several linked lists: FREE,
     522             :    NEEDED, CREATED.  When in the CREATED state and list, there's a
     523             :    pseudo-txn with a last field pointing to it.  When in the NEEDED
     524             :    state and list, it's part of the map_chain.  Technically FREE could
     525             :    be an slist/fd_pool, but it's easier to reason about correctness when
     526             :    each element is in exactly one of three lists at all times. */
     527             : typedef struct {
     528             :   uint           block_idx;
     529             :   fd_acct_addr_t acct;
     530             : } ustlt_key_t;
     531             : 
     532             : struct ustlt {
     533             :   ustlt_key_t key;
     534             :   uint map_prev;
     535             :   uint map_next;
     536             :   uint ll_prev;
     537             :   uint ll_next;
     538             :   uint ptxn_idx; /* when CREATED */
     539             : };
     540             : typedef struct ustlt ustlt_t;
     541             : 
     542             : #define MAP_NAME  ustlt_map
     543           0 : #define MAP_ELE_T ustlt_t
     544           0 : #define MAP_IDX_T uint
     545             : #define MAP_KEY_T ustlt_key_t
     546           0 : #define MAP_NEXT  map_next
     547           0 : #define MAP_PREV  map_prev
     548           0 : #define MAP_KEY_HASH(k,s) fd_hash32( (k)->acct.b, (s)+((k)->block_idx) )
     549           0 : #define MAP_KEY_EQ(a,b)   (0==memcmp( (a), (b), sizeof(ustlt_key_t) ))
     550             : #define MAP_OPTIMIZE_RANDOM_ACCESS_REMOVAL 1
     551             : #include "../../util/tmpl/fd_map_chain.c"
     552             : 
     553             : #define DLIST_NAME  ustlt_dlist
     554             : #define DLIST_ELE_T ustlt_t
     555             : #define DLIST_IDX_T uint
     556           0 : #define DLIST_PREV  ll_prev
     557           0 : #define DLIST_NEXT  ll_next
     558             : #include "../../util/tmpl/fd_dlist.c"
     559             : 
     560             : 
     561             : 
     562             : /* fd_rdisp_blockinfo_t maintains a little metadata about transactions for each
     563             :    slot.  */
     564             : struct fd_rdisp_blockinfo {
     565             :   FD_RDISP_BLOCK_TAG_T block;
     566             :   uint  linear_block_number;
     567             : 
     568             :   uint  insert_ready:1;
     569             :   uint  schedule_ready:1;
     570             :   uint  staged:1;
     571             :   uint  staging_lane:2; /* ignored if staged==0 */
     572             :   uint  last_insert_was_serializing:1;
     573             :   uint  inserted_extra_ptxn:1;
     574             : 
     575             :   uint inserted_cnt;
     576             :   uint dispatched_cnt;
     577             :   uint completed_cnt;
     578             :   uint last_serializing;
     579             : 
     580             :   uint map_chain_next;
     581             :   uint ll_next;
     582             :   zombie_dlist_t    zombie_list[ 1 ];
     583             :   /* When unstaged: */
     584             :   unstaged_txn_ll_t ll[ 1 ];
     585             :   ustlt_dlist_t     ustlt_needed[ 1 ];
     586             :   ustlt_dlist_t     ustlt_created[ 1 ];
     587             : };
     588             : typedef struct fd_rdisp_blockinfo fd_rdisp_blockinfo_t;
     589             : 
     590             : #define POOL_NAME     block_pool
     591           0 : #define POOL_T        fd_rdisp_blockinfo_t
     592             : #define POOL_IDX_T    uint
     593           0 : #define POOL_NEXT     ll_next
     594             : #define POOL_SENTINEL 1
     595             : #include "../../util/tmpl/fd_pool.c"
     596             : 
     597             : #define MAP_NAME  block_map
     598             : #define MAP_ELE_T fd_rdisp_blockinfo_t
     599             : #define MAP_KEY_T FD_RDISP_BLOCK_TAG_T
     600           0 : #define MAP_KEY   block
     601           0 : #define MAP_NEXT  map_chain_next
     602           0 : #define MAP_IDX_T uint
     603             : #include "../../util/tmpl/fd_map_chain.c"
     604             : 
     605             : #define SLIST_NAME  block_slist
     606             : #define SLIST_ELE_T fd_rdisp_blockinfo_t
     607           0 : #define SLIST_IDX_T uint
     608           0 : #define SLIST_NEXT  ll_next
     609             : #include "../../util/tmpl/fd_slist.c"
     610             : 
     611             : struct fd_rdisp_unstaged {
     612             :   uint block_idx;
     613             : 
     614             :   uint writable_cnt;
     615             :   uint readonly_cnt;
     616             :   fd_acct_addr_t keys[FD_RDISP_MAX_ACCT_PER_TXN];
     617             : };
     618             : typedef struct fd_rdisp_unstaged fd_rdisp_unstaged_t;
     619             : 
     620             : 
     621             : 
     622             : typedef struct {
     623             :   pending_prq_ele_t * pending;
     624             :   ulong               linear_block_number;
     625             :   block_slist_t       block_ll[1];
     626             :   ulong               inserted_cnt;
     627             :   ulong               dispatched_cnt;
     628             :   ulong               completed_cnt;
     629             : } per_lane_info_t;
     630             : 
     631             : /* We maintain one map from pubkeys to acct_info_t, acct_map.  The
     632             :    ACTIVE pubkeys in this map have >0 references in one of the staging
     633             :    lane DAGs.  When an account goes to 0 references it stays in the map
     634             :    as CACHED, which exists to maintain the reference count EMA
     635             :    information lazily.  Unless we need the acct_info_t for something in
     636             :    the DAG, we might as well maintain the EMA info.
     637             : 
     638             :    When we start up, all the acct_info_t structs are in the
     639             :    free_acct_dlist.  Whenever something becomes CACHED, it's also added
     640             :    to the tail of the free_acct_dlist.  When we need an acct_info_t for
     641             :    a pubkey that's not in the map, we pop the head of the
     642             :    free_acct_dlist, evicting it from the map if it was CACHED.  In
     643             :    general, the free_acct_dlist contains everything CACHED, potentially
     644             :    plus some elements that have never been used; all acct_info_t objects
     645             :    are in at least one of acct_map and the free_acct_dlist.  See
     646             :    acct_info_t for more information about this. */
     647             : 
     648             : struct fd_rdisp {
     649             :   ulong depth;
     650             :   ulong block_depth;
     651             : 
     652             :   ulong global_insert_cnt;
     653             :   ulong unstaged_lblk_num;
     654             : 
     655             :   /* pool: an fd_pool, indexed [0, depth+1), with 0 being a sentinel */
     656             :   fd_rdisp_txn_t       * pool;
     657             :   fd_rdisp_unstaged_t  * unstaged; /* parallel to pool with additional info */
     658             :   /* ptxn_pool: an fd_pool, also indexed [0, depth*128+1), with 0 as a
     659             :      sentinel. */
     660             :   fd_rdisp_ptxn_t      * ptxn_pool;
     661             :   fd_rdisp_mq_t        * mq;
     662             : 
     663             :   block_map_t          * blockmap; /* map chain */
     664             :   fd_rdisp_blockinfo_t * block_pool;
     665             : 
     666             :   int free_lanes; /* a bitmask */
     667             :   per_lane_info_t lanes[4];
     668             : 
     669             :   acct_map_t   * acct_map;
     670             :   /* acct_pool is not an fd_pool, but is just a flat array, since we
     671             :      don't need to acquire and release from it because of the dlist. */
     672             :   acct_info_t  * acct_pool;
     673             :   free_dlist_t   free_acct_dlist[1];
     674             : 
     675             :   /* Unfortunately, the LtHash info for unstaged transactions needs to
     676             :      be stored completely separately. */
     677             :   ustlt_t          * ustlt_pool; /* a flat array, not an fd_pool */
     678             :   ustlt_map_t      * ustlt_map;
     679             :   ustlt_dlist_t      ustlt_free[ 1 ];
     680             : };
     681             : 
     682             : typedef struct fd_rdisp fd_rdisp_t;
     683             : 
     684             : 
     685             :  #define ACCT_ITER_TO_PTR( iter ) (__extension__( {                                             \
     686             :        ulong __idx = fd_txn_acct_iter_idx( iter );                                              \
     687             :        fd_ptr_if( __idx<fd_txn_account_cnt( txn, FD_TXN_ACCT_CAT_IMM ), accts, alt_adj )+__idx; \
     688             :        }))
     689             : 
     690             : /* 62,500,000 CUs gets 3137 transactions with 64 writers and one with 34
     691             :    writers for a total of 200,802.  Then we add 8k for inflation
     692             :    rewards, 10 for sysvars, 4000 for vote accounts, and a bit of
     693             :    padding. */
     694           0 : #define MAX_WRITERS_PER_BLOCK 217354UL
     695             : 
     696           0 : ulong fd_rdisp_align( void ) { return 128UL; }
     697             : 
     698             : ulong
     699             : fd_rdisp_footprint( ulong depth,
     700           0 :                     ulong block_depth ) {
     701           0 :   if( FD_UNLIKELY( (depth>FD_RDISP_MAX_DEPTH)             | (depth<2UL) |
     702           0 :                    (block_depth>FD_RDISP_MAX_BLOCK_DEPTH) | (block_depth<4UL) ) ) return 0UL;
     703             : 
     704           0 :   ulong chain_cnt      = block_map_chain_cnt_est( block_depth );
     705           0 :   ulong acct_depth     = depth*FD_RDISP_MAX_ACCT_PER_TXN;
     706             :   /* The map holds at most acct_depth entries, keep load factor at most
     707             :      0.5 to speed up chain operations. */
     708           0 :   ulong acct_chain_cnt = acct_map_chain_cnt_est( 2UL*acct_depth );
     709           0 :   ulong mq_footprint   = fd_rdisp_mq_footprint( acct_depth, MAX_WRITERS_PER_BLOCK );
     710           0 :   ulong full_depth     = depth*(FD_RDISP_MAX_ACCT_PER_TXN+1UL); /* normal and pseudo transactions */
     711             : 
     712           0 :   ulong l = FD_LAYOUT_INIT;
     713           0 :   l = FD_LAYOUT_APPEND( l, fd_rdisp_align(),             sizeof(fd_rdisp_t)                              );
     714           0 :   l = FD_LAYOUT_APPEND( l, pool_align(),                 pool_footprint              ( depth+1UL       ) ); /* pool       */
     715           0 :   l = FD_LAYOUT_APPEND( l, alignof(fd_rdisp_unstaged_t), sizeof(fd_rdisp_unstaged_t)*( depth+1UL       ) ); /* unstaged   */
     716           0 :   l = FD_LAYOUT_APPEND( l, ptxn_pool_align(),            ptxn_pool_footprint         ( acct_depth+1UL  ) ); /* ptxn_pool  */
     717           0 :   l = FD_LAYOUT_APPEND( l, fd_rdisp_mq_align(),          mq_footprint                                    ); /* mq         */
     718           0 :   l = FD_LAYOUT_APPEND( l, block_map_align(),            block_map_footprint         ( chain_cnt       ) ); /* blockmap   */
     719           0 :   l = FD_LAYOUT_APPEND( l, block_pool_align(),           block_pool_footprint        ( block_depth+1UL ) ); /* block_pool */
     720           0 :   l = FD_LAYOUT_APPEND( l, pending_prq_align(),          4UL*pending_prq_footprint   ( full_depth      ) ); /* pending    */
     721           0 :   l = FD_LAYOUT_APPEND( l, acct_map_align(),             acct_map_footprint          ( acct_chain_cnt  ) ); /* acct_map   */
     722           0 :   l = FD_LAYOUT_APPEND( l, alignof(acct_info_t),         (acct_depth+1UL)*sizeof(acct_info_t)            ); /* acct_pool  */
     723           0 :   l = FD_LAYOUT_APPEND( l, alignof(ustlt_t),             (acct_depth+1UL)*sizeof(ustlt_t)                ); /* ustlt_pool */
     724           0 :   l = FD_LAYOUT_APPEND( l, ustlt_map_align(),            ustlt_map_footprint         ( acct_chain_cnt  ) ); /* ustlt_map  */
     725           0 :   return FD_LAYOUT_FINI( l, fd_rdisp_align() );
     726           0 : }
     727             : 
     728             : void *
     729             : fd_rdisp_new( void * mem,
     730             :               ulong  depth,
     731             :               ulong  block_depth,
     732           0 :               ulong  seed ) {
     733           0 :   if( FD_UNLIKELY( (depth>FD_RDISP_MAX_DEPTH)             | (depth<2UL) |
     734           0 :                    (block_depth>FD_RDISP_MAX_BLOCK_DEPTH) | (block_depth<4UL) ) ) return NULL;
     735             : 
     736           0 :   ulong chain_cnt      = block_map_chain_cnt_est( block_depth );
     737           0 :   ulong acct_depth     = depth*FD_RDISP_MAX_ACCT_PER_TXN;
     738           0 :   ulong acct_chain_cnt = acct_map_chain_cnt_est( 2UL*acct_depth ); /* see fd_rdisp_footprint */
     739           0 :   ulong mq_footprint   = fd_rdisp_mq_footprint( acct_depth, MAX_WRITERS_PER_BLOCK );
     740           0 :   ulong full_depth     = depth*(FD_RDISP_MAX_ACCT_PER_TXN+1UL);
     741             : 
     742           0 :   FD_SCRATCH_ALLOC_INIT( l, mem );
     743           0 :   fd_rdisp_t * disp   = FD_SCRATCH_ALLOC_APPEND( l, fd_rdisp_align(),             sizeof(fd_rdisp_t)                              );
     744           0 :   void  * _pool       = FD_SCRATCH_ALLOC_APPEND( l, pool_align(),                 pool_footprint              ( depth+1UL       ) );
     745           0 :   void  * _unstaged   = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_rdisp_unstaged_t), sizeof(fd_rdisp_unstaged_t)*( depth+1UL       ) );
     746           0 :   void  * _ptxn_pool  = FD_SCRATCH_ALLOC_APPEND( l, ptxn_pool_align(),            ptxn_pool_footprint         ( acct_depth+1UL  ) );
     747           0 :   void  * _mq         = FD_SCRATCH_ALLOC_APPEND( l, fd_rdisp_mq_align(),          mq_footprint                                    );
     748           0 :   void  * _bmap       = FD_SCRATCH_ALLOC_APPEND( l, block_map_align(),            block_map_footprint         ( chain_cnt       ) );
     749           0 :   void  * _bpool      = FD_SCRATCH_ALLOC_APPEND( l, block_pool_align(),           block_pool_footprint        ( block_depth+1UL ) );
     750           0 :   uchar * _pending    = FD_SCRATCH_ALLOC_APPEND( l, pending_prq_align(),          4UL*pending_prq_footprint   ( full_depth      ) );
     751           0 :   void  * _acct_map   = FD_SCRATCH_ALLOC_APPEND( l, acct_map_align(),             acct_map_footprint          ( acct_chain_cnt  ) );
     752           0 :   acct_info_t * apool = FD_SCRATCH_ALLOC_APPEND( l, alignof(acct_info_t),         (acct_depth+1UL)*sizeof(acct_info_t)            );
     753           0 :   ustlt_t     * upool = FD_SCRATCH_ALLOC_APPEND( l, alignof(ustlt_t),             (acct_depth+1UL)*sizeof(ustlt_t)                );
     754           0 :   void  * _ustlt_map  = FD_SCRATCH_ALLOC_APPEND( l, ustlt_map_align(),            ustlt_map_footprint         ( acct_chain_cnt  ) );
     755           0 :   FD_SCRATCH_ALLOC_FINI( l, fd_rdisp_align() );
     756             : 
     757           0 :   disp->depth             = depth;
     758           0 :   disp->block_depth       = block_depth;
     759           0 :   disp->global_insert_cnt = 0UL;
     760           0 :   disp->unstaged_lblk_num = 0UL;
     761             : 
     762           0 :   fd_rdisp_txn_t * temp_pool_join = pool_join( pool_new( _pool, depth+1UL ) );
     763           0 :   for( ulong i=0UL; i<depth+1UL; i++ ) temp_pool_join[ i ].in_degree = IN_DEGREE_FREE;
     764           0 :   pool_leave( temp_pool_join );
     765             : 
     766           0 :   memset( _unstaged, '\0', sizeof(fd_rdisp_unstaged_t)*(depth+1UL) );
     767             : 
     768           0 :   fd_rdisp_ptxn_t * temp_ppool_join = ptxn_pool_join( ptxn_pool_new( _ptxn_pool, acct_depth+1UL ) );
     769           0 :   for( ulong i=0UL; i<acct_depth+1UL; i++ ) temp_ppool_join[ i ].in_degree = IN_DEGREE_FREE;
     770           0 :   ptxn_pool_leave( temp_ppool_join );
     771             : 
     772           0 :   fd_rdisp_mq_new( _mq, acct_depth, MAX_WRITERS_PER_BLOCK );
     773             : 
     774           0 :   block_map_new ( _bmap,  chain_cnt, seed );
     775           0 :   block_pool_new( _bpool, block_depth+1UL );
     776             : 
     777           0 :   fd_rdisp_blockinfo_t * bpool_temp_join = block_pool_join( _bpool );
     778           0 :   for( ulong i=0UL; i<block_depth+1UL; i++ ) zombie_dlist_new( bpool_temp_join[ i ].zombie_list );
     779           0 :   block_pool_leave( bpool_temp_join );
     780             : 
     781           0 :   disp->free_lanes = 0xF;
     782           0 :   for( ulong i=0UL; i<4UL; i++ ) {
     783           0 :     pending_prq_new( _pending, full_depth );
     784           0 :     _pending += pending_prq_footprint( full_depth );
     785             : 
     786           0 :     disp->lanes[i].linear_block_number = 0UL;
     787           0 :     disp->lanes[i].inserted_cnt        = 0U;
     788           0 :     disp->lanes[i].dispatched_cnt      = 0U;
     789           0 :     disp->lanes[i].completed_cnt       = 0U;
     790           0 :   }
     791             : 
     792           0 :   acct_map_new( _acct_map,  acct_chain_cnt, fd_ulong_hash( seed+1UL ) );
     793             : 
     794           0 :   free_dlist_t * temp_join = free_dlist_join( free_dlist_new( disp->free_acct_dlist ) );
     795           0 :   for( ulong i=1UL; i<acct_depth+1UL; i++ ) {
     796           0 :     apool[ i ].next = apool[ i ].prev = 0U;
     797           0 :     free_dlist_idx_push_tail( disp->free_acct_dlist, i, apool );
     798           0 :   }
     799           0 :   free_dlist_leave( temp_join );
     800             : 
     801           0 :   ustlt_map_new( _ustlt_map, acct_chain_cnt, fd_ulong_hash( seed+3UL ) );
     802             : 
     803           0 :   ustlt_dlist_t * temp_join_ustlt = ustlt_dlist_join( ustlt_dlist_new( disp->ustlt_free ) );
     804           0 :   for( ulong i=1UL; i<acct_depth+1UL; i++ ) {
     805           0 :     upool[ i ].ll_next = upool[ i ].ll_prev = 0U;
     806           0 :     ustlt_dlist_idx_push_tail( disp->ustlt_free, i, upool );
     807           0 :   }
     808           0 :   ustlt_dlist_leave( temp_join_ustlt );
     809             : 
     810             : 
     811           0 :   return disp;
     812           0 : }
     813             : 
     814             : fd_rdisp_t *
     815           0 : fd_rdisp_join( void * mem ) {
     816           0 :   fd_rdisp_t * disp = (fd_rdisp_t *)mem;
     817             : 
     818           0 :   ulong depth          = disp->depth;
     819           0 :   ulong block_depth    = disp->block_depth;
     820           0 :   ulong chain_cnt      = block_map_chain_cnt_est( block_depth );
     821           0 :   ulong acct_depth     = depth*FD_RDISP_MAX_ACCT_PER_TXN;
     822           0 :   ulong acct_chain_cnt = acct_map_chain_cnt_est( 2UL*acct_depth ); /* see fd_rdisp_footprint */
     823           0 :   ulong mq_footprint   = fd_rdisp_mq_footprint( acct_depth, MAX_WRITERS_PER_BLOCK );
     824           0 :   ulong full_depth     = depth*(FD_RDISP_MAX_ACCT_PER_TXN+1UL);
     825             : 
     826           0 :   FD_SCRATCH_ALLOC_INIT( l, mem );
     827           0 :   /*                 */ FD_SCRATCH_ALLOC_APPEND( l, fd_rdisp_align(),             sizeof(fd_rdisp_t)                              );
     828           0 :   void  * _pool       = FD_SCRATCH_ALLOC_APPEND( l, pool_align(),                 pool_footprint              ( depth+1UL       ) );
     829           0 :   void  * _unstaged   = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_rdisp_unstaged_t), sizeof(fd_rdisp_unstaged_t)*( depth+1UL       ) );
     830           0 :   void  * _ptxn_pool  = FD_SCRATCH_ALLOC_APPEND( l, ptxn_pool_align(),            ptxn_pool_footprint         ( acct_depth+1UL  ) );
     831           0 :   void  * _mq         = FD_SCRATCH_ALLOC_APPEND( l, fd_rdisp_mq_align(),          mq_footprint                                    );
     832           0 :   void  * _bmap       = FD_SCRATCH_ALLOC_APPEND( l, block_map_align(),            block_map_footprint         ( chain_cnt       ) );
     833           0 :   void  * _bpool      = FD_SCRATCH_ALLOC_APPEND( l, block_pool_align(),           block_pool_footprint        ( block_depth+1UL ) );
     834           0 :   uchar * _pending    = FD_SCRATCH_ALLOC_APPEND( l, pending_prq_align(),          4UL*pending_prq_footprint   ( full_depth      ) );
     835           0 :   void  * _acct_map   = FD_SCRATCH_ALLOC_APPEND( l, acct_map_align(),             acct_map_footprint          ( acct_chain_cnt  ) );
     836           0 :   acct_info_t * apool = FD_SCRATCH_ALLOC_APPEND( l, alignof(acct_info_t),         (acct_depth+1UL)*sizeof(acct_info_t)            );
     837           0 :   ustlt_t     * upool = FD_SCRATCH_ALLOC_APPEND( l, alignof(ustlt_t),             (acct_depth+1UL)*sizeof(ustlt_t)                );
     838           0 :   void  * _ustlt_map  = FD_SCRATCH_ALLOC_APPEND( l, ustlt_map_align(),            ustlt_map_footprint         ( acct_chain_cnt  ) );
     839           0 :   FD_SCRATCH_ALLOC_FINI( l, fd_rdisp_align() );
     840             : 
     841           0 :   disp->pool       = pool_join( _pool );
     842           0 :   disp->unstaged   = (fd_rdisp_unstaged_t *)_unstaged;
     843           0 :   disp->ptxn_pool  = ptxn_pool_join( _ptxn_pool );
     844           0 :   disp->mq         = fd_rdisp_mq_join( _mq );
     845           0 :   disp->blockmap   = block_map_join( _bmap );
     846           0 :   disp->block_pool = block_pool_join( _bpool );
     847             : 
     848           0 :   for( ulong i=0UL; i<block_depth+1UL; i++ ) zombie_dlist_join( disp->block_pool[ i ].zombie_list );
     849             : 
     850           0 :   for( ulong i=0UL; i<4UL; i++ ) {
     851           0 :     disp->lanes[i].pending = pending_prq_join( _pending );
     852           0 :     _pending += pending_prq_footprint( full_depth );
     853           0 :   }
     854             : 
     855           0 :   disp->acct_map      = acct_map_join( _acct_map );
     856           0 :   disp->acct_pool     = apool;
     857           0 :   free_dlist_join( disp->free_acct_dlist );
     858             : 
     859           0 :   disp->ustlt_pool    = upool;
     860           0 :   disp->ustlt_map     = ustlt_map_join( _ustlt_map );
     861           0 :   ustlt_dlist_join( disp->ustlt_free );
     862             : 
     863           0 :   return disp;
     864           0 : }
     865             : 
     866             : static inline void
     867             : free_lane( fd_rdisp_t * disp,
     868           0 :            ulong        staging_lane ) {
     869           0 :   disp->free_lanes |= 1<<staging_lane;
     870           0 :   per_lane_info_t * l = disp->lanes+staging_lane;
     871           0 :   FD_TEST( pending_prq_cnt( l->pending )==0UL );
     872           0 :   l->linear_block_number = 0UL;
     873           0 :   l->inserted_cnt   = 0UL;
     874           0 :   l->dispatched_cnt = 0UL;
     875           0 :   l->completed_cnt  = 0UL;
     876           0 :   block_slist_delete( block_slist_leave( l->block_ll ) );
     877           0 : }
     878             : 
     879             : static inline void
     880             : alloc_lane( fd_rdisp_t * disp,
     881           0 :             ulong        staging_lane ) {
     882           0 :   disp->free_lanes &= ~(1<<staging_lane);
     883           0 :   block_slist_join( block_slist_new( disp->lanes[staging_lane].block_ll ) );
     884           0 : }
     885             : 
     886             : ulong
     887             : fd_rdisp_suggest_staging_lane( fd_rdisp_t const *   disp,
     888             :                                FD_RDISP_BLOCK_TAG_T parent_block,
     889           0 :                                int                  duplicate ) {
     890             : 
     891             :   /* 1. If it's a duplicate, suggest FD_RDISP_UNSTAGED */
     892           0 :   if( FD_UNLIKELY( duplicate ) ) return FD_RDISP_UNSTAGED;
     893             : 
     894             :   /* 2. If parent is the last block in any existing staging lane, suggest
     895             :         that lane */
     896           0 :   fd_rdisp_blockinfo_t const * block_pool = disp->block_pool;
     897           0 :   fd_rdisp_blockinfo_t const * block = block_map_ele_query_const( disp->blockmap, &parent_block, NULL, block_pool );
     898           0 :   if( FD_LIKELY( block && block->insert_ready && block->staged ) ) return block->staging_lane;
     899             : 
     900             :   /* 3. If there is at least one free lane, suggest a free lane */
     901           0 :   if( FD_LIKELY( disp->free_lanes!=0 ) ) return (ulong)fd_uint_find_lsb( (uint)disp->free_lanes );
     902             : 
     903             :   /* 4. Else, suggest FD_RDISP_UNSTAGED */
     904           0 :   return FD_RDISP_UNSTAGED;
     905           0 : }
     906             : 
     907             : int
     908             : fd_rdisp_add_block( fd_rdisp_t          * disp,
     909             :                    FD_RDISP_BLOCK_TAG_T   new_block,
     910           0 :                    ulong                  staging_lane ) {
     911           0 :   fd_rdisp_blockinfo_t * block_pool = disp->block_pool;
     912             : 
     913           0 :   if( FD_UNLIKELY( !block_pool_free( block_pool )                                                             ) ) return -1;
     914           0 :   if( FD_UNLIKELY(  ULONG_MAX!=block_map_idx_query_const( disp->blockmap, &new_block, ULONG_MAX, block_pool ) ) ) return -1;
     915           0 :   fd_rdisp_blockinfo_t * block = block_pool_ele_acquire( block_pool );
     916           0 :   block->block = new_block;
     917           0 :   block_map_ele_insert( disp->blockmap, block, block_pool );
     918             : 
     919           0 :   block->insert_ready = 1;
     920           0 :   block->staged       = staging_lane!=FD_RDISP_UNSTAGED;
     921           0 :   block->staging_lane = (uint)(staging_lane & 0x3UL);
     922           0 :   block->last_insert_was_serializing = 0U;
     923           0 :   block->inserted_extra_ptxn         = 0U;
     924             : 
     925           0 :   block->inserted_cnt     = 0U;
     926           0 :   block->dispatched_cnt   = 0U;
     927           0 :   block->completed_cnt    = 0U;
     928           0 :   block->last_serializing = 0U;
     929             : 
     930           0 :   if( FD_UNLIKELY( staging_lane==FD_RDISP_UNSTAGED ) ) {
     931           0 :     block->schedule_ready = 1;
     932           0 :     block->linear_block_number = (uint)disp->unstaged_lblk_num++;
     933           0 :     unstaged_txn_ll_join( unstaged_txn_ll_new( block->ll            ) );
     934           0 :     ustlt_dlist_join(     ustlt_dlist_new    ( block->ustlt_needed  ) );
     935           0 :     ustlt_dlist_join(     ustlt_dlist_new    ( block->ustlt_created ) );
     936           0 :   } else {
     937           0 :     block->linear_block_number = (uint)disp->lanes[staging_lane].linear_block_number++;
     938           0 :     block->schedule_ready      = (uint)(1 & (disp->free_lanes >> staging_lane));
     939             : 
     940           0 :     if( FD_LIKELY( disp->free_lanes & (1<<staging_lane) ) ) alloc_lane( disp, staging_lane );
     941             : 
     942           0 :     block_slist_t * sl = disp->lanes[staging_lane].block_ll;
     943           0 :     if( FD_LIKELY( !block_slist_is_empty( sl, block_pool ) ) )  block_slist_ele_peek_tail( sl, block_pool )->insert_ready = 0;
     944           0 :     block_slist_ele_push_tail( sl, block, block_pool );
     945           0 :   }
     946           0 :   return 0;
     947           0 : }
     948             : 
     949             : 
     950             : 
     951             : int
     952             : fd_rdisp_remove_block( fd_rdisp_t          * disp,
     953           0 :                        FD_RDISP_BLOCK_TAG_T  block_tag ) {
     954           0 :   fd_rdisp_blockinfo_t * block_pool = disp->block_pool;
     955             : 
     956           0 :   fd_rdisp_blockinfo_t * block   = block_map_ele_query( disp->blockmap, &block_tag, NULL, block_pool );
     957           0 :   if( FD_UNLIKELY( block==NULL ) ) return -1;
     958             : 
     959           0 :   FD_TEST( block->schedule_ready );
     960           0 :   FD_TEST( block->completed_cnt==block->inserted_cnt );
     961           0 :   FD_TEST( zombie_dlist_is_empty( block->zombie_list, disp->pool ) );
     962             : 
     963           0 :   if( FD_LIKELY( block->staged ) ) {
     964           0 :     ulong staging_lane = (ulong)block->staging_lane;
     965           0 :     block_slist_t * sl = disp->lanes[staging_lane].block_ll;
     966             : 
     967           0 :     FD_TEST( block==block_slist_ele_peek_head( sl, block_pool ) );
     968           0 :     block_slist_idx_pop_head( sl, block_pool );
     969           0 :     if( FD_LIKELY( !block_slist_is_empty( sl, block_pool ) ) ) block_slist_ele_peek_head( sl, block_pool )->schedule_ready = 1;
     970           0 :     else                                                       free_lane( disp, staging_lane );
     971           0 :   } else {
     972           0 :     unstaged_txn_ll_delete( unstaged_txn_ll_leave( block->ll            ) );
     973           0 :     ustlt_dlist_delete    ( ustlt_dlist_leave    ( block->ustlt_needed  ) );
     974           0 :     ustlt_dlist_delete    ( ustlt_dlist_leave    ( block->ustlt_created ) );
     975           0 :   }
     976           0 :   block_pool_idx_release( block_pool, block_map_idx_remove( disp->blockmap, &block_tag, ULONG_MAX, block_pool ) );
     977             : 
     978           0 :   return 0;
     979           0 : }
     980             : 
     981             : 
     982             : int
     983             : fd_rdisp_abandon_block( fd_rdisp_t          * disp,
     984           0 :                         FD_RDISP_BLOCK_TAG_T  block_tag ) {
     985           0 :   fd_rdisp_blockinfo_t * block_pool = disp->block_pool;
     986             : 
     987           0 :   fd_rdisp_blockinfo_t * block   = block_map_ele_query( disp->blockmap, &block_tag, NULL, disp->block_pool );
     988           0 :   if( FD_UNLIKELY( block==NULL ) ) return -1;
     989             : 
     990           0 :   FD_TEST( block->schedule_ready );
     991           0 :   FD_TEST( block->dispatched_cnt==block->completed_cnt ); /* TODO: remove this when it can call complete properly */
     992           0 :   while( block->completed_cnt<block->inserted_cnt ) {
     993             :     /* because there is nothing DISPATCHED, there has to be something
     994             :        READY */
     995           0 :     ulong txn = fd_rdisp_get_next_ready( disp, block_tag );
     996           0 :     FD_TEST( txn );
     997           0 :     fd_rdisp_complete_txn( disp, txn, 1 );
     998           0 :   }
     999           0 :   while( !zombie_dlist_is_empty( block->zombie_list, disp->pool ) ) {
    1000             :     /* rdisp_complete_txn removes the peeked head */
    1001           0 :     fd_rdisp_complete_txn( disp, zombie_dlist_idx_peek_head( block->zombie_list, disp->pool ), 1 );
    1002           0 :   }
    1003             : 
    1004           0 :   if( FD_LIKELY( block->staged ) ) {
    1005           0 :     ulong staging_lane = (ulong)block->staging_lane;
    1006             : 
    1007           0 :     block_slist_t * sl = disp->lanes[staging_lane].block_ll;
    1008             : 
    1009           0 :     FD_TEST( block==block_slist_ele_peek_head( sl, block_pool ) );
    1010           0 :     block_slist_idx_pop_head( sl, block_pool );
    1011           0 :     if( FD_LIKELY( !block_slist_is_empty( sl, block_pool ) ) ) block_slist_ele_peek_head( sl, block_pool )->schedule_ready = 1;
    1012           0 :     else {
    1013             :       /* It's schedule-ready, so it's possible there are some accounts
    1014             :          in the NEEDS_PTXN state (add writer, schedule, complete it).
    1015             :          However, if it's not insert-ready, then we know the caller
    1016             :          would already have created the pseudo-transactions if they were
    1017             :          needed, so we don't need to do anything in that case. */
    1018           0 :       while( fd_rdisp_mq_cnt( disp->mq, staging_lane ) ) {
    1019           0 :         ulong acct_idx = fd_rdisp_mq_pop( disp->mq, staging_lane );
    1020           0 :         acct_info_t * ai = disp->acct_pool + acct_idx;
    1021           0 :         uint s = ai->states;
    1022           0 :         if( FD_UNLIKELY( ACCT_INFO_STATE_IS(s, staging_lane, NEEDS_PTXN) ) ) {
    1023           0 :           ai->states = (uchar)ACCT_INFO_STATE_SET(s, staging_lane, NO_WRITERS);
    1024             : 
    1025             :           /* Potentially transition from ACTIVE -> CACHED */
    1026           0 :           if( FD_UNLIKELY( (ai->states==0)&
    1027           0 :                 (ai->last_reference[ 0 ]==0U)&(ai->last_reference[ 1 ]==0U)&
    1028           0 :                 (ai->last_reference[ 2 ]==0U)&(ai->last_reference[ 3 ]==0U) ) ) {
    1029           0 :             ai->cached = ACCT_INFO_CACHED;
    1030           0 :             free_dlist_idx_push_tail( disp->free_acct_dlist, acct_idx, disp->acct_pool );
    1031           0 :           }
    1032           0 :         }
    1033           0 :       }
    1034             : 
    1035           0 :       free_lane( disp, staging_lane );
    1036           0 :     }
    1037           0 :   } else {
    1038           0 :     while( !ustlt_dlist_is_empty( block->ustlt_created, disp->ustlt_pool ) ) {
    1039           0 :       ustlt_t * ele = ustlt_dlist_ele_peek_head( block->ustlt_created, disp->ustlt_pool );
    1040             :       /* Free the created ones */
    1041           0 :       fd_rdisp_ptxn_t * ptxn = disp->ptxn_pool + EDGE_SPECIAL_IDX( ele->ptxn_idx );
    1042           0 :       ptxn->in_degree        = IN_DEGREE_FREE;
    1043           0 :       ptxn_pool_ele_release( disp->ptxn_pool, ptxn );
    1044             : 
    1045           0 :       ustlt_dlist_ele_remove   ( block->ustlt_created, ele, disp->ustlt_pool );
    1046           0 :       ustlt_dlist_ele_push_tail( disp->ustlt_free,     ele, disp->ustlt_pool );
    1047           0 :     }
    1048             : 
    1049           0 :     while( !ustlt_dlist_is_empty( block->ustlt_needed, disp->ustlt_pool ) ) {
    1050           0 :       ustlt_t * ele = ustlt_dlist_ele_peek_head( block->ustlt_needed, disp->ustlt_pool );
    1051           0 :       ustlt_dlist_ele_remove   ( block->ustlt_needed, ele, disp->ustlt_pool );
    1052           0 :       ustlt_map_ele_remove_fast( disp->ustlt_map,     ele, disp->ustlt_pool );
    1053           0 :       ustlt_dlist_ele_push_tail( disp->ustlt_free,    ele, disp->ustlt_pool );
    1054           0 :     }
    1055             : 
    1056           0 :     unstaged_txn_ll_delete( unstaged_txn_ll_leave( block->ll ) );
    1057           0 :     ustlt_dlist_delete    ( ustlt_dlist_leave    ( block->ustlt_needed  ) );
    1058           0 :     ustlt_dlist_delete    ( ustlt_dlist_leave    ( block->ustlt_created ) );
    1059           0 :   }
    1060           0 :   block_pool_idx_release( disp->block_pool, block_map_idx_remove( disp->blockmap, &block_tag, ULONG_MAX, disp->block_pool ) );
    1061             : 
    1062           0 :   return 0;
    1063           0 : }
    1064             : 
    1065             : static void
    1066             : add_edges( fd_rdisp_t           * disp,
    1067             :            fd_rdisp_txn_t       * ele,
    1068             :            fd_acct_addr_t const * addr,
    1069             :            ulong                  addr_cnt,
    1070             :            uint                   staging_lane,
    1071             :            int                    writable,
    1072             :            int                    update_score,
    1073             :            fd_rdisp_neww_t *      new_wr,
    1074             :            ulong                  new_wr_offset );
    1075             : /* updates in_degree, edge_cnt_etc */
    1076             : 
    1077             : 
    1078             : static inline ulong
    1079             : lookup_pubkey( fd_rdisp_t * disp,
    1080             :                fd_acct_addr_t const * addr );
    1081             : 
    1082             : int
    1083             : fd_rdisp_promote_block( fd_rdisp_t *          disp,
    1084             :                         FD_RDISP_BLOCK_TAG_T  block_tag,
    1085           0 :                         ulong                 staging_lane ) {
    1086           0 :   fd_rdisp_blockinfo_t * block_pool = disp->block_pool;
    1087           0 :   per_lane_info_t * lane = disp->lanes + staging_lane;
    1088           0 :   block_slist_t * sl = lane->block_ll;
    1089             : 
    1090           0 :   fd_rdisp_blockinfo_t * block   = block_map_ele_query( disp->blockmap, &block_tag, NULL, block_pool );
    1091           0 :   if( FD_UNLIKELY( block==NULL   ) ) return -1;
    1092           0 :   if( FD_UNLIKELY( block->staged ) ) return -1;
    1093             : 
    1094           0 :   block->staged = 1;
    1095           0 :   block->staging_lane = (uint)(staging_lane & 0x3);
    1096           0 :   block->insert_ready = 1;
    1097           0 :   block->schedule_ready = (uint)(1 & (disp->free_lanes >> staging_lane));
    1098             : 
    1099           0 :   if( FD_LIKELY( disp->free_lanes & (1<<staging_lane) ) ) alloc_lane( disp, staging_lane );
    1100             : 
    1101           0 :   if( FD_LIKELY( !block_slist_is_empty( sl, block_pool ) ) )  block_slist_ele_peek_tail( sl, block_pool )->insert_ready = 0;
    1102           0 :   block_slist_ele_push_tail( sl, block, block_pool );
    1103           0 :   uint linear_block_number = (uint)lane->linear_block_number++;
    1104           0 :   block->linear_block_number = linear_block_number;
    1105             : 
    1106           0 :   unstaged_txn_ll_iter_t next;
    1107           0 :   for( unstaged_txn_ll_iter_t iter = unstaged_txn_ll_iter_init( block->ll, disp->pool );
    1108           0 :       !unstaged_txn_ll_iter_done( iter, block->ll, disp->pool );
    1109           0 :       iter = next ) {
    1110           0 :       next = unstaged_txn_ll_iter_next( iter, block->ll, disp->pool );
    1111             : 
    1112           0 :     fd_rdisp_txn_t      * ele = unstaged_txn_ll_iter_ele( iter, block->ll, disp->pool );
    1113           0 :     fd_rdisp_unstaged_t * uns = disp->unstaged + unstaged_txn_ll_iter_idx( iter, block->ll, disp->pool );
    1114           0 :     FD_TEST( ele->in_degree==IN_DEGREE_UNSTAGED );
    1115             : 
    1116           0 :     ele->in_degree    = 0U;
    1117           0 :     ele->edge_cnt_etc = (uint)(-1); /* set w_cnt_1 to -1 */
    1118             : 
    1119             :     /* Ignore LtHash accounts here since we have the precise list in
    1120             :        ustlt_needed. */
    1121           0 :     add_edges( disp, ele, uns->keys,                   uns->writable_cnt, (uint)staging_lane, 1, 0, NULL, 0UL );
    1122           0 :     add_edges( disp, ele, uns->keys+uns->writable_cnt, uns->readonly_cnt, (uint)staging_lane, 0, 0, NULL, 0UL );
    1123             : 
    1124           0 :     ele->edge_cnt_etc &= 0x3FFFU;
    1125           0 :     ele->edge_cnt_etc |= (uint)staging_lane<<14;
    1126           0 :     ele->edge_cnt_etc |= linear_block_number<<16;
    1127             : 
    1128           0 :     if( FD_UNLIKELY( ele->in_degree==0U ) ) {
    1129           0 :       pending_prq_ele_t temp[1] = {{ .score = ele->score, .linear_block_number = linear_block_number, .txn_idx = (uint)(ele-disp->pool)}};
    1130           0 :       pending_prq_insert( lane->pending, temp );
    1131           0 :     }
    1132           0 :   }
    1133           0 :   unstaged_txn_ll_delete( unstaged_txn_ll_leave( block->ll ) );
    1134             : 
    1135           0 :   while( !ustlt_dlist_is_empty( block->ustlt_created, disp->ustlt_pool ) ) {
    1136           0 :     ustlt_t * ele = ustlt_dlist_ele_peek_head( block->ustlt_created, disp->ustlt_pool );
    1137             : 
    1138             :     /* Free the created ones */
    1139           0 :     fd_rdisp_ptxn_t * ptxn = disp->ptxn_pool + EDGE_SPECIAL_IDX( ele->ptxn_idx );
    1140           0 :     ptxn->in_degree        = IN_DEGREE_FREE;
    1141           0 :     ptxn_pool_ele_release( disp->ptxn_pool, ptxn );
    1142             : 
    1143           0 :     ustlt_dlist_ele_remove   ( block->ustlt_created, ele, disp->ustlt_pool );
    1144           0 :     ustlt_dlist_ele_push_tail( disp->ustlt_free,     ele, disp->ustlt_pool );
    1145           0 :     block->inserted_cnt--;
    1146           0 :   }
    1147             : 
    1148           0 :   while( !ustlt_dlist_is_empty( block->ustlt_needed, disp->ustlt_pool ) ) {
    1149           0 :     ustlt_t * ele = ustlt_dlist_ele_peek_head( block->ustlt_needed, disp->ustlt_pool );
    1150             : 
    1151           0 :     ulong acct_idx = lookup_pubkey( disp, &(ele->key.acct) );
    1152           0 :     acct_info_t * ai = disp->acct_pool + acct_idx;
    1153             :     /* If it's NO_WRITERS, we need to transition to NEEDS_PTXN, but
    1154             :        otherwise it's fine. */
    1155           0 :     uint s = ai->states;
    1156           0 :     s = fd_uint_if( ACCT_INFO_STATE_IS(s, staging_lane, NO_WRITERS), ACCT_INFO_STATE_SET(s, staging_lane, NEEDS_PTXN), s );
    1157           0 :     ai->states = (uchar)s;
    1158             : 
    1159           0 :     fd_rdisp_mq_insert( disp->mq, acct_idx, staging_lane, disp->acct_pool[acct_idx].ema_refs );
    1160             : 
    1161           0 :     ustlt_dlist_ele_remove   ( block->ustlt_needed, ele, disp->ustlt_pool );
    1162           0 :     ustlt_map_ele_remove_fast( disp->ustlt_map,     ele, disp->ustlt_pool );
    1163           0 :     ustlt_dlist_ele_push_tail( disp->ustlt_free,    ele, disp->ustlt_pool );
    1164           0 :   }
    1165             : 
    1166           0 :   lane->inserted_cnt   += block->inserted_cnt;
    1167           0 :   lane->dispatched_cnt += block->dispatched_cnt;
    1168           0 :   lane->completed_cnt  += block->completed_cnt;
    1169             : 
    1170           0 :   return 0;
    1171           0 : }
    1172             : 
    1173             : int
    1174             : fd_rdisp_demote_block( fd_rdisp_t *          disp,
    1175           0 :                        FD_RDISP_BLOCK_TAG_T  block_tag ) {
    1176           0 :   fd_rdisp_blockinfo_t * block_pool = disp->block_pool;
    1177             : 
    1178           0 :   fd_rdisp_blockinfo_t * block   = block_map_ele_query( disp->blockmap, &block_tag, NULL, block_pool );
    1179           0 :   if( FD_UNLIKELY(  block==NULL           ) ) return -1;
    1180           0 :   if( FD_UNLIKELY( !block->staged         ) ) return -1;
    1181           0 :   if( FD_UNLIKELY( !block->schedule_ready ) ) return -1;
    1182           0 :   if( FD_UNLIKELY(  block->completed_cnt!=block->inserted_cnt ) ) FD_LOG_ERR(( "demote_block called with non-empty block" ));
    1183           0 :   ulong staging_lane = block->staging_lane;
    1184           0 :   block->staged = 0;
    1185             : 
    1186           0 :   per_lane_info_t * lane = disp->lanes + staging_lane;
    1187           0 :   block_slist_t * sl = lane->block_ll;
    1188             : 
    1189           0 :   lane->inserted_cnt   -= block->inserted_cnt;
    1190           0 :   lane->dispatched_cnt -= block->dispatched_cnt;
    1191           0 :   lane->completed_cnt  -= block->completed_cnt;
    1192             : 
    1193           0 :   block->linear_block_number = (uint)disp->unstaged_lblk_num++;
    1194             : 
    1195             :   /* staged and schedule_ready means it must be the head of the staging lane */
    1196           0 :   FD_TEST( block_slist_ele_peek_head( sl, block_pool )==block );
    1197           0 :   block_slist_idx_pop_head( sl, block_pool );
    1198             : 
    1199           0 :   unstaged_txn_ll_join( unstaged_txn_ll_new( block->ll            ) );
    1200           0 :   ustlt_dlist_join(     ustlt_dlist_new    ( block->ustlt_needed  ) );
    1201           0 :   ustlt_dlist_join(     ustlt_dlist_new    ( block->ustlt_created ) );
    1202             : 
    1203             : 
    1204           0 :   if( FD_LIKELY( !block_slist_is_empty( sl, block_pool ) ) ) block_slist_ele_peek_head( sl, block_pool )->schedule_ready = 1;
    1205           0 :   else {
    1206             :     /* If it was insert-ready, we need to carry over the LtHash addition
    1207             :        information.  If it wasn't, then that information should have
    1208             :        been encoded in the created LtHash addition tasks, but the block
    1209             :        can't contain any PENDING transactions by contract... */
    1210           0 :     while( fd_rdisp_mq_cnt( disp->mq, staging_lane ) ) {
    1211           0 :       ulong acct_idx = fd_rdisp_mq_pop( disp->mq, staging_lane );
    1212           0 :       acct_info_t * ai = disp->acct_pool + acct_idx;
    1213             : 
    1214           0 :       ulong ustlt_idx = ustlt_dlist_idx_pop_head( disp->ustlt_free, disp->ustlt_pool );
    1215           0 :       disp->ustlt_pool[ ustlt_idx ].key.acct      = ai->key;
    1216           0 :       disp->ustlt_pool[ ustlt_idx ].key.block_idx = (uint)(block-block_pool);
    1217           0 :       ustlt_map_idx_insert( disp->ustlt_map, ustlt_idx, disp->ustlt_pool );
    1218           0 :       ustlt_dlist_idx_push_tail( block->ustlt_needed, ustlt_idx, disp->ustlt_pool );
    1219             : 
    1220           0 :       uint s = ai->states;
    1221           0 :       if( FD_UNLIKELY( ACCT_INFO_STATE_IS(s, staging_lane, NEEDS_PTXN) ) ) {
    1222           0 :         ai->states = (uchar)ACCT_INFO_STATE_SET(s, staging_lane, NO_WRITERS);
    1223             : 
    1224             :         /* Potentially transition from ACTIVE -> CACHED */
    1225           0 :         if( FD_UNLIKELY( (ai->states==0)&
    1226           0 :               (ai->last_reference[ 0 ]==0U)&(ai->last_reference[ 1 ]==0U)&
    1227           0 :               (ai->last_reference[ 2 ]==0U)&(ai->last_reference[ 3 ]==0U) ) ) {
    1228           0 :           ai->cached = ACCT_INFO_CACHED;
    1229           0 :           free_dlist_idx_push_tail( disp->free_acct_dlist, acct_idx, disp->acct_pool );
    1230           0 :         }
    1231           0 :       }
    1232           0 :     }
    1233           0 :     free_lane( disp, staging_lane );
    1234           0 :   }
    1235           0 :   return 0;
    1236           0 : }
    1237             : 
    1238             : int
    1239             : fd_rdisp_rekey_block( fd_rdisp_t *           disp,
    1240             :                       FD_RDISP_BLOCK_TAG_T   new_tag,
    1241           0 :                       FD_RDISP_BLOCK_TAG_T   old_tag ) {
    1242           0 :   fd_rdisp_blockinfo_t * block_pool = disp->block_pool;
    1243             : 
    1244           0 :   if( FD_UNLIKELY(        NULL!= block_map_ele_query_const( disp->blockmap, &new_tag, NULL, block_pool ) ) ) return -1;
    1245           0 :   fd_rdisp_blockinfo_t * block = block_map_ele_query      ( disp->blockmap, &old_tag, NULL, block_pool );
    1246           0 :   if( FD_UNLIKELY(        NULL== block ) )                                                                   return -1;
    1247             : 
    1248           0 :   block_map_idx_remove( disp->blockmap, &old_tag, ULONG_MAX, block_pool );
    1249           0 :   block->block = new_tag;
    1250           0 :   block_map_ele_insert( disp->blockmap, block, block_pool );
    1251           0 :   return 0;
    1252           0 : }
    1253             : 
    1254             : 
    1255             : /* "Registers" a reference to the account in info at transaction
    1256             :    global_insert_cnt.  Returns the value of the EMA, which is an
    1257             :    estimate of the probability that the next transaction also references
    1258             :    the account.  This value does not matter for correctness, other than
    1259             :    that it is in [0, 1), which is why floating point arithmetic is
    1260             :    acceptable here. */
    1261             : static inline float
    1262             : update_ema( acct_info_t * info,
    1263           0 :             ulong         global_insert_cnt ) {
    1264             : #if FD_RDISP_DISABLE_EMA
    1265             :   (void)info;
    1266             :   (void)global_insert_cnt;
    1267             :   return 0.0f;
    1268             : #else
    1269           0 : #define ALPHA 0.005f
    1270             :   /* The normal EMA update equation is
    1271             :                 e_i = (alpha) * x_i + (1-alpha)*e_{i-1},
    1272             :      where alpha is a constant in (0,1).  Let L be the last reference of
    1273             :      the account, and G be the current global_insert_cnt value.  We know
    1274             :      that e_L = ema_refs, and that for i in [L+1, G), x_i = 0.
    1275             :      That means
    1276             :                e_{G-1} =         (1-alpha)^(G-L-1) * ema_refs
    1277             :                e_G     = alpha + (1-alpha)^(G-L)   * ema_refs
    1278             :    */
    1279             :   /* last_ref only captures the low 24 bits, so we guess its the highest
    1280             :      value for them that would still make it less than
    1281             :      global_insert_cnt.  Turns out, we can calculate that with just an
    1282             :      AND.  We need to make sure that in the rare case it is actually
    1283             :      2^24 away, we don't use a delta of 0.  We also want to make sure
    1284             :      that even with inexact float math, we never get to 1.0, so we bias
    1285             :      the exponent up slightly. */
    1286           0 :   ulong last_ref = (ulong)info->last_ref;
    1287           0 :   ulong delta = fd_ulong_max( (global_insert_cnt - last_ref) & 0xFFFFFFUL, 1UL );
    1288           0 :   float ema_refs = ALPHA + powf( 1.0f-ALPHA, 0.05f+(float)delta ) * info->ema_refs;
    1289             : 
    1290           0 :   info->ema_refs = ema_refs;
    1291           0 :   info->last_ref = (uint)(global_insert_cnt & 0xFFFFFFUL);
    1292           0 : #undef ALPHA
    1293             : 
    1294           0 :   return ema_refs;
    1295           0 : #endif
    1296           0 : }
    1297             : 
    1298             : /* Finds addr in acct_map, making it ACTIVE if it isn't already */
    1299             : static inline ulong
    1300             : lookup_pubkey( fd_rdisp_t * disp,
    1301           0 :                fd_acct_addr_t const * addr ) {
    1302             : 
    1303           0 :     ulong idx = acct_map_idx_query( disp->acct_map, addr, ULONG_MAX, disp->acct_pool );
    1304           0 :     acct_info_t * ai = NULL;
    1305           0 :     if( FD_LIKELY( idx!=ULONG_MAX ) ) {
    1306           0 :       ai = disp->acct_pool+idx;
    1307           0 :       if( FD_LIKELY( ai->cached!=ACCT_INFO_CACHED ) ) return idx;
    1308             : 
    1309             :       /* CACHED -> ACTIVE transition.  It has the right key. last_ref,
    1310             :          and ema_refs are valid. states was zeroed when it was cached. */
    1311           0 :       free_dlist_idx_remove( disp->free_acct_dlist, idx, disp->acct_pool );
    1312           0 :       memset( ai->last_reference, '\0', sizeof(ai->last_reference) );
    1313           0 :       return idx;
    1314           0 :     }
    1315             : 
    1316             :     /* The acct pool is sized so that the list cannot be empty at this
    1317             :        point.  However, the element at the head might be CACHED with
    1318             :        a different pubkey. */
    1319           0 :     idx = free_dlist_idx_peek_head( disp->free_acct_dlist, disp->acct_pool );
    1320           0 :     ai = disp->acct_pool+idx;
    1321             : 
    1322             :     /* CACHED -> FREE transition */
    1323           0 :     if( FD_LIKELY( ai->next!=0U ) ) {
    1324           0 :       acct_map_idx_remove_fast( disp->acct_map, idx, disp->acct_pool );
    1325           0 :     }
    1326             : 
    1327             :     /* FREE -> ACTIVE transition */
    1328           0 :     ai->key      = *addr;
    1329           0 :     ai->states   = 0U;
    1330           0 :     ai->last_ref = 0U;
    1331           0 :     ai->ema_refs = 0.0f;
    1332           0 :     free_dlist_idx_remove( disp->free_acct_dlist, idx, disp->acct_pool );
    1333           0 :     memset( ai->last_reference, '\0', sizeof(ai->last_reference) );
    1334           0 :     acct_map_idx_insert( disp->acct_map, idx, disp->acct_pool );
    1335             : 
    1336           0 :     return idx;
    1337           0 : }
    1338             : 
    1339             : static void
    1340             : add_edges( fd_rdisp_t           * disp,
    1341             :            fd_rdisp_txn_t       * ele,
    1342             :            fd_acct_addr_t const * addrs,
    1343             :            ulong                  addr_cnt,
    1344             :            uint                   lane,
    1345             :            int                    writable,
    1346             :            int                    update_score,
    1347             :            fd_rdisp_neww_t *      new_wr,
    1348           0 :            ulong                  new_wr_offset ) {
    1349             :   /* When we first start, the low 14 bits are 0x3FFF, so that gives us
    1350             :      w_cnt==0, r_cnt==0, as desired.  Then otherwise, the low 7 bits are
    1351             :      w_cnt_1, so adding 1 gives w_cnt.  If we've actually added
    1352             :      MAX_ACCT_PER_TXN writable accounts, then r_cnt will be incorrect,
    1353             :      but then we actually can't add any more readonly accounts, so the
    1354             :      function call will be a no-op. */
    1355             : 
    1356           0 :   ulong w_cnt =  (ele->edge_cnt_etc + 1U)     & 0x7FU;
    1357           0 :   ulong r_cnt = ((ele->edge_cnt_etc + 1U)>>7) & 0x7FU;
    1358           0 :   ulong acct_idx = w_cnt +     r_cnt;
    1359           0 :   ulong edge_idx = w_cnt + 3UL*r_cnt;
    1360             : 
    1361           0 :   for( ulong i=0UL; i<addr_cnt; i++ ) {
    1362           0 :     fd_acct_addr_t const * addr = addrs+i;
    1363           0 :     acct_info_t * ai = NULL;
    1364             : 
    1365             :     /* Step 1: lookup the pubkey */
    1366           0 :     ulong idx = lookup_pubkey( disp, addr );
    1367           0 :     ai = disp->acct_pool+idx;
    1368             :     /* At this point, in all cases, the acct_info is now in the ACTIVE
    1369             :        state.  It's in acct_map and not in free_acct_dlist. */
    1370             : 
    1371             :     /* Assume that transactions are drawn randomly from some large
    1372             :        distribution of potential transactions.  We want to estimate the
    1373             :        expected value of the probability that this transaction conflicts
    1374             :        with the next transaction that is sampled.  Two transactions
    1375             :        conflict if they conflict on any account, and in general, they
    1376             :        conflict if they both reference the same account, unless both
    1377             :        this transaction and the next one only read it.  We don't have
    1378             :        read/write info, so the best guess we have is that the next
    1379             :        transaction does the same thing to the account that this one
    1380             :        does.  That means we only really care about the accounts that
    1381             :        this transaction writes to.  Label those a_1, a_2, ..., a_i, and
    1382             :        suppose the probability that the next transaction references a_i
    1383             :        is p_i (determined using the EMA).  Now then, assuming accounts
    1384             :        are independent (which is false, but whatever), then the
    1385             :        probability that the next transaction does not conflict with this
    1386             :        account is:
    1387             :                      (1-p_1) * (1-p_2) * (1 - p_i).
    1388             : 
    1389             :        Since for the treap, a lower value means we'll schedule it
    1390             :        earlier, we'll use the probability of non-conflict as the
    1391             :        fractional part of the score. */
    1392           0 :     if( FD_LIKELY( update_score ) ) {
    1393           0 :       float score_change = 1.0f - update_ema( ai, disp->global_insert_cnt );
    1394           0 :       ele->score *= fd_float_if( writable, score_change, 1.0f );
    1395           0 :     }
    1396             : 
    1397             :     /* Step 2: add edge. There are 4 cases depending on whether this is
    1398             :        a writer or not and whether the previous reference was a writer
    1399             :        or not. */
    1400           0 :     int      _ignore;
    1401             : 
    1402           0 :     edge_t   ref_to_pa = ai->last_reference[ lane ];
    1403           0 :     ulong    displ0    = ((ulong)ele - (ulong)disp->pool     )/sizeof(fd_rdisp_txn_t);
    1404           0 :     ulong    displ1    = ((ulong)ele - (ulong)disp->ptxn_pool)/sizeof(fd_rdisp_ptxn_t);
    1405           0 :     edge_t   ref_to_me = (uint)fd_ulong_if( displ0>disp->depth, EDGE_MAKE_SPECIAL(PTXN, displ1), (displ0<<8)|acct_idx );
    1406           0 :     edge_t * pa        = FOLLOW_EDGE( disp, ai->last_reference[ lane ], _ignore );
    1407           0 :     edge_t * me        = ele->edges + edge_idx;
    1408             : 
    1409             :     /* In the case that this is the first txn in the DAG, pa will point
    1410             :        to edges[0] of the sentinel element, pool[0].  We don't care
    1411             :        about what is stored there, so just set it up as a dummy element
    1412             :        to make the rest of the code work properly in this case too.  If
    1413             :        this is a read, we want me->sibli to ref_to_me in case 4; if this
    1414             :        is a write, we want to set me->sibli to 0 in case 2, but we need
    1415             :        to make sure pb==pa. */
    1416           0 :     disp->pool->edges[0] = EDGE_MAKE_SPECIAL(LAST, (uint)idx);
    1417           0 :     disp->pool->edges[1] = fd_uint_if( writable, 0U, ref_to_me );
    1418           0 :     disp->pool->edges[2] = fd_uint_if( writable, 0U, ref_to_me );
    1419             : 
    1420           0 :     int states = ai->states;
    1421             : 
    1422           0 :     if( writable ) { /* also should be known at compile time */
    1423           0 :       if( ACCT_INFO_STATE_IS( states, lane, LAST_REF_WAS_WRITE ) ) { /* unclear prob */
    1424             :         /* Case 1: w-w. The parent is the special last pointer.  Point
    1425             :            the parent to me, and set me to the last pointer. */
    1426           0 :         *me = *pa;
    1427           0 :         *pa = ref_to_me;
    1428           0 :       } else {
    1429             :         /* Case 2: r-w. This is the tricky case because there could be
    1430             :            multiple readers.  We need to set all the last readers' child
    1431             :            pointers to me. */
    1432           0 :         *me = *pa;
    1433           0 :         *pa = ref_to_me;
    1434           0 :         edge_t * pb = FOLLOW_EDGE( disp, pa[1], _ignore );
    1435             :         /* Intentionally skip the first in_degree increment, because it
    1436             :            will be done later */
    1437           0 :         while( pb!=pa ) {
    1438           0 :           *pb = ref_to_me;
    1439           0 :           ele->in_degree++;
    1440           0 :           pb = FOLLOW_EDGE( disp, pb[1], _ignore );
    1441           0 :         }
    1442           0 :         states = ACCT_INFO_STATE_SET( states, lane, LAST_REF_WAS_WRITE );
    1443           0 :       }
    1444           0 :     } else {
    1445           0 :       if( ACCT_INFO_STATE_IS( states, lane, LAST_REF_WAS_WRITE ) ) { /* unclear prob */
    1446             :         /* Case 3: w-r. Similar to case 1, but need to initialize my
    1447             :            next and prev sibling pointers too. */
    1448           0 :         *me = *pa;
    1449           0 :         *pa = ref_to_me;
    1450           0 :         me[1] = ref_to_me; /* next */
    1451           0 :         me[2] = ref_to_me; /* prev */
    1452           0 :         states = ACCT_INFO_STATE_SET( states, lane, LAST_REF_WAS_READ );
    1453           0 :       } else {
    1454             :         /* Case 4: r-r. Add myself as a sibling instead of a child */
    1455           0 :         *me = *pa;
    1456           0 :         FOLLOW_EDGE( disp, pa[1], _ignore )[2] = ref_to_me;  /* prev->next->prev = me   */
    1457           0 :         me[1] = pa[1];                                       /* me->next   = prev->next */
    1458           0 :         me[2] = ref_to_pa;                                   /* me->prev   = prev       */
    1459           0 :         pa[1] = ref_to_me;                                   /* prev->next = me         */
    1460           0 :       }
    1461           0 :     }
    1462             : 
    1463             :     /* Step 3: Update new_wr */
    1464           0 :     if( ((!!writable) & FD_LIKELY( !!new_wr )) && !fd_rdisp_mq_test( disp->mq, idx, lane ) ) {
    1465           0 :       fd_rdisp_mq_insert( disp->mq, idx, lane, ai->ema_refs );
    1466           0 :       fd_rdisp_neww_insert( new_wr, new_wr_offset+i );
    1467           0 :     }
    1468             : 
    1469             :     /* Step 4: Update the final values */
    1470             :     /* In general, we want to increment the in_degree unless this
    1471             :        transaction is the first to reference this account.  The
    1472             :        exception is that if this account has only readers, including
    1473             :        this transaction, we don't want to increment the in_degree
    1474             :        either.  At this point, we can tell if that is the case based on
    1475             :        states.  */
    1476           0 :     int any_write = ACCT_INFO_STATE_IS( states, lane, LAST_REF_WAS_READ ) | ACCT_INFO_STATE_IS( states, lane, LAST_REF_WAS_WRITE );
    1477           0 :     ele->in_degree += (uint)((ai->last_reference[ lane ]!=0U) & any_write);
    1478           0 :     ai->last_reference[ lane ] = ref_to_me;
    1479           0 :     ai->states                 = (uchar)states;
    1480           0 :     edge_idx += fd_uint_if( writable, 1U, 3U );
    1481           0 :     acct_idx++;
    1482           0 :   }
    1483             :   /* Can't overflow by construction, except for the intentional overflow on the first time. */
    1484           0 :   ele->edge_cnt_etc += (uint)addr_cnt<<fd_int_if( writable, 0, 7 );
    1485           0 : }
    1486             : 
    1487             : 
    1488             : /* should be called with all writable accounts first */
    1489             : static void
    1490             : add_unstaged_edges( fd_rdisp_t * disp,
    1491             :                     fd_rdisp_txn_t       * ele,
    1492             :                     fd_rdisp_unstaged_t  * unstaged,
    1493             :                     fd_acct_addr_t const * addr,
    1494             :                     ulong                  addr_cnt,
    1495             :                     int                    writable,
    1496             :                     int                    update_score,
    1497             :                     fd_rdisp_neww_t      * new_wr,
    1498           0 :                     ulong                  new_wr_offset ) {
    1499           0 :   ulong base_idx = unstaged->writable_cnt+unstaged->readonly_cnt;
    1500           0 :   FD_TEST( !writable || unstaged->readonly_cnt==0U );
    1501           0 :   for( ulong i=0UL; i<addr_cnt; i++ ) {
    1502           0 :     unstaged->keys[ base_idx+i ] = addr[i];
    1503           0 :     if( FD_LIKELY( update_score ) ) {
    1504           0 :       ulong idx = acct_map_idx_query( disp->acct_map, addr+i, ULONG_MAX, disp->acct_pool );
    1505             :       /* since these are unstaged, we don't bother moving accounts
    1506             :          around */
    1507           0 :       float score_change = 1.0f;
    1508           0 :       if( FD_LIKELY( idx!=ULONG_MAX ) ) score_change = 1.0f - update_ema( disp->acct_pool+idx, disp->global_insert_cnt );
    1509           0 :       ele->score *= fd_float_if( writable & update_score, score_change, 1.0f );
    1510           0 :     }
    1511           0 :     if( ((!!writable) & FD_LIKELY( !!new_wr )) ) {
    1512           0 :       ustlt_key_t query[1] = {{
    1513           0 :         .block_idx = unstaged->block_idx,
    1514           0 :         .acct      = addr[i]
    1515           0 :       }};
    1516           0 :       int new = (ustlt_map_idx_query( disp->ustlt_map, query, ULONG_MAX, disp->ustlt_pool )==ULONG_MAX);
    1517           0 :       fd_rdisp_neww_insert_if( new_wr, new, new_wr_offset+i );
    1518           0 :       if( FD_UNLIKELY( new ) ) {
    1519           0 :         ulong ustlt_idx = ustlt_dlist_idx_pop_head( disp->ustlt_free, disp->ustlt_pool );
    1520           0 :         disp->ustlt_pool[ ustlt_idx ].key = *query;
    1521           0 :         ustlt_map_idx_insert( disp->ustlt_map, ustlt_idx, disp->ustlt_pool );
    1522           0 :         ustlt_dlist_idx_push_tail( disp->block_pool[ unstaged->block_idx ].ustlt_needed, ustlt_idx, disp->ustlt_pool );
    1523           0 :       }
    1524           0 :     }
    1525           0 :   }
    1526           0 :   *(fd_ptr_if( writable, &(unstaged->writable_cnt), &(unstaged->readonly_cnt) ) ) += (uint)addr_cnt;
    1527           0 : }
    1528             : 
    1529             : /* prefetch_accts warms what add_edges (or add_unstaged_edges) touches
    1530             :    first for each of the cnt addresses: the chain head of each account,
    1531             :    then the element at the head of each chain, which is where the key
    1532             :    compare of a hit usually lands (queries move the found element to the
    1533             :    front).  The map is large enough that these are otherwise serial
    1534             :    cache misses, one account at a time.  The free list head, which a
    1535             :    miss takes, is warmed by the caller.  Hints only: the queries below
    1536             :    are unchanged. */
    1537             : 
    1538             : static inline void
    1539             : prefetch_accts( fd_rdisp_t const *     disp,
    1540             :                 fd_acct_addr_t const * addrs,
    1541           0 :                 ulong                  cnt ) {
    1542           0 :   uint const * heads[ MAX_ACCT_PER_TXN ];
    1543           0 :   uint const * chain = acct_map_private_chain_const( disp->acct_map );
    1544           0 :   ulong seed         = acct_map_seed     ( disp->acct_map );
    1545           0 :   ulong chain_cnt    = acct_map_chain_cnt( disp->acct_map );
    1546           0 :   for( ulong i=0UL; i<cnt; i++ ) {
    1547           0 :     heads[ i ] = chain + acct_map_private_chain_idx( addrs+i, seed, chain_cnt );
    1548           0 :     __builtin_prefetch( heads[ i ], 0, 3 );
    1549           0 :   }
    1550           0 :   for( ulong i=0UL; i<cnt; i++ ) {
    1551           0 :     ulong head = (ulong)*heads[ i ];
    1552           0 :     __builtin_prefetch( disp->acct_pool + fd_ulong_if( head==UINT_MAX, 0UL, head ), 0, 3 );
    1553           0 :   }
    1554           0 : }
    1555             : 
    1556             : ulong
    1557           0 : fd_rdisp_peek_free_txn( fd_rdisp_t const * disp ) {
    1558           0 :   ulong idx = pool_private_meta_const( disp->pool )->free_top;
    1559           0 :   return fd_ulong_if( idx==pool_idx_null( disp->pool ), 0UL, idx );
    1560           0 : }
    1561             : 
    1562             : ulong
    1563             : fd_rdisp_add_txn( fd_rdisp_t          *  disp,
    1564             :                   FD_RDISP_BLOCK_TAG_T   insert_block,
    1565             :                   fd_txn_t const       * txn,
    1566             :                   uchar const          * payload,
    1567             :                   fd_acct_addr_t const * alts,
    1568             :                   int                    serializing,
    1569           0 :                   fd_rdisp_neww_t      * new_writable ) {
    1570           0 :   if( FD_LIKELY( new_writable ) ) fd_rdisp_neww_new( new_writable ); /* clear it */
    1571             : 
    1572           0 :   fd_rdisp_blockinfo_t * block   = block_map_ele_query( disp->blockmap, &insert_block, NULL, disp->block_pool );
    1573           0 :   if( FD_UNLIKELY( !block || !block->insert_ready ) ) return 0UL;
    1574           0 :   if( FD_UNLIKELY( !pool_free( disp->pool       ) ) ) return 0UL;
    1575             : 
    1576           0 :   fd_acct_addr_t const * imm_addrs = fd_txn_get_acct_addrs( txn, payload );
    1577             : 
    1578           0 :   /*                   */ prefetch_accts( disp, imm_addrs, fd_txn_account_cnt( txn, FD_TXN_ACCT_CAT_IMM ) );
    1579           0 :   if( FD_LIKELY( alts ) ) prefetch_accts( disp, alts, fd_txn_account_cnt( txn, FD_TXN_ACCT_CAT_ALT ) );
    1580           0 :   __builtin_prefetch( disp->acct_pool + free_dlist_idx_peek_head( disp->free_acct_dlist, disp->acct_pool ), 1, 3 );
    1581             : 
    1582           0 :   ulong idx = pool_idx_acquire( disp->pool );
    1583           0 :   fd_rdisp_txn_t * rtxn = disp->pool + idx;
    1584           0 :   if( FD_UNLIKELY( rtxn->in_degree!=IN_DEGREE_FREE ) ) FD_LOG_CRIT(( "pool[%lu].in_degree==%u but free", idx, rtxn->in_degree ));
    1585             : 
    1586             :   /* The element the next add_txn takes (unless a completion pushes a
    1587             :      recently touched one first) is the next cold line in this path. */
    1588           0 :   __builtin_prefetch( disp->pool + fd_rdisp_peek_free_txn( disp ), 1, 3 );
    1589             : 
    1590           0 :   ulong ws = fd_txn_account_cnt( txn, FD_TXN_ACCT_CAT_WRITABLE_SIGNER );
    1591           0 :   ulong rs = fd_txn_account_cnt( txn, FD_TXN_ACCT_CAT_READONLY_SIGNER );
    1592           0 :   ulong wn = fd_txn_account_cnt( txn, FD_TXN_ACCT_CAT_WRITABLE_NONSIGNER_IMM );
    1593           0 :   ulong rn = fd_txn_account_cnt( txn, FD_TXN_ACCT_CAT_READONLY_NONSIGNER_IMM );
    1594           0 :   ulong wa = fd_txn_account_cnt( txn, FD_TXN_ACCT_CAT_WRITABLE_ALT );
    1595           0 :   ulong ra = fd_txn_account_cnt( txn, FD_TXN_ACCT_CAT_READONLY_ALT );
    1596             : 
    1597           0 :   if( FD_UNLIKELY( !block->staged ) ) {
    1598           0 :     rtxn->in_degree = IN_DEGREE_UNSTAGED;
    1599           0 :     rtxn->score     = 1.0f;
    1600             : 
    1601           0 :     fd_rdisp_unstaged_t * unstaged = disp->unstaged + idx;
    1602           0 :     unstaged->block_idx    = (uint)(block-disp->block_pool);
    1603           0 :     unstaged->writable_cnt = 0U;
    1604           0 :     unstaged->readonly_cnt = 0U;
    1605             : 
    1606           0 :     add_unstaged_edges(   disp, rtxn, unstaged, imm_addrs,          ws, 1, 1, new_writable, 0UL            );
    1607           0 :     add_unstaged_edges(   disp, rtxn, unstaged, imm_addrs+ws+rs,    wn, 1, 1, new_writable, ws+rs          );
    1608           0 :     if( FD_LIKELY( alts ) )
    1609           0 :       add_unstaged_edges( disp, rtxn, unstaged, alts,               wa, 1, 1, new_writable, ws+rs+wn+rn    );
    1610           0 :     add_unstaged_edges(   disp, rtxn, unstaged, imm_addrs+ws,       rs, 0, 1, new_writable, ws             );
    1611           0 :     add_unstaged_edges(   disp, rtxn, unstaged, imm_addrs+ws+rs+wn, rn, 0, 1, new_writable, ws+rs+wn       );
    1612           0 :     if( FD_LIKELY( alts ) )
    1613           0 :       add_unstaged_edges( disp, rtxn, unstaged, alts+wa,            ra, 0, 1, new_writable, ws+rs+wn+rn+wa );
    1614             : 
    1615           0 :     unstaged_txn_ll_ele_push_tail( block->ll, rtxn, disp->pool );
    1616           0 :   } else {
    1617           0 :     uint lane = block->staging_lane;
    1618             : 
    1619           0 :     rtxn->in_degree    = 0U;
    1620           0 :     rtxn->score        = 1.0f;
    1621             :     /* There's not a good way to initialize w_cnt_1 to -1, which is what
    1622             :        it should be at this point, but this is close.  We must be sure
    1623             :        to add at least one writer (which we are assured of because we
    1624             :        know the transaction passed fd_txn_parse) to correct this value. */
    1625           0 :     rtxn->edge_cnt_etc = ((block->linear_block_number<<16) | (lane<<14)) - 1U;
    1626             : 
    1627           0 :     add_edges(   disp, rtxn, imm_addrs,          ws, lane, 1, 1, new_writable, 0UL            );
    1628           0 :     add_edges(   disp, rtxn, imm_addrs+ws+rs,    wn, lane, 1, 1, new_writable, ws+rs          );
    1629           0 :     if( FD_LIKELY( alts ) )
    1630           0 :       add_edges( disp, rtxn, alts,               wa, lane, 1, 1, new_writable, ws+rs+wn+rn    );
    1631           0 :     add_edges(   disp, rtxn, imm_addrs+ws,       rs, lane, 0, 1, new_writable, ws             );
    1632           0 :     add_edges(   disp, rtxn, imm_addrs+ws+rs+wn, rn, lane, 0, 1, new_writable, ws+rs+wn       );
    1633           0 :     if( FD_LIKELY( alts ) )
    1634           0 :       add_edges( disp, rtxn, alts+wa,            ra, lane, 0, 1, new_writable, ws+rs+wn+rn+wa );
    1635           0 :   }
    1636           0 :   if( FD_UNLIKELY( rtxn->score>FD_RDISP_MAX_SCORE ) ) rtxn->score=FD_RDISP_MAX_SCORE;
    1637             : 
    1638           0 :   if( FD_UNLIKELY( serializing | block->last_insert_was_serializing ) ) {
    1639           0 :     block->last_serializing = block->inserted_cnt;
    1640           0 :   }
    1641           0 :   block->last_insert_was_serializing = (uint)!!serializing;
    1642           0 :   rtxn->score += (float)block->last_serializing;
    1643             : 
    1644           0 :   block->inserted_cnt++;
    1645           0 :   disp->global_insert_cnt++;
    1646             : 
    1647           0 :   if( FD_LIKELY( (block->staged) & (rtxn->in_degree==0U) ) ) {
    1648           0 :     pending_prq_ele_t temp[1] = {{ .score = rtxn->score, .linear_block_number = block->linear_block_number, .txn_idx = (uint)idx }};
    1649           0 :     pending_prq_insert( disp->lanes[ block->staging_lane ].pending, temp );
    1650           0 :   }
    1651             : 
    1652           0 :   return idx;
    1653           0 : }
    1654             : 
    1655             : ulong
    1656             : fd_rdisp_get_next_ready( fd_rdisp_t           * disp,
    1657           0 :                          FD_RDISP_BLOCK_TAG_T   schedule_block ) {
    1658           0 :   fd_rdisp_blockinfo_t * block   = block_map_ele_query( disp->blockmap, &schedule_block, NULL, disp->block_pool );
    1659           0 :   if( FD_UNLIKELY( !block || !block->schedule_ready ) ) return 0UL;
    1660             : 
    1661           0 :   ulong idx;
    1662           0 :   if( FD_LIKELY( block->staged ) ) {
    1663           0 :     ulong staging_lane = block->staging_lane;
    1664           0 :     per_lane_info_t * l = disp->lanes + staging_lane;
    1665             : 
    1666           0 :     if( FD_UNLIKELY( !pending_prq_cnt( l->pending )                                ) ) return 0UL;
    1667           0 :     if( FD_UNLIKELY( l->pending->linear_block_number != block->linear_block_number ) ) return 0UL;
    1668             :     /* e.g. when completed_cnt==0, we can accept any score below 1.0 */
    1669           0 :     if( FD_UNLIKELY( l->pending->score>=(float)(block->completed_cnt+1U)           ) ) return 0UL;
    1670           0 :     idx = l->pending->txn_idx;
    1671           0 :     pending_prq_remove_min( l->pending );
    1672           0 :     if( FD_UNLIKELY( EDGE_IS_PTXN(idx) ) ) disp->ptxn_pool[ EDGE_SPECIAL_IDX( idx ) ].in_degree = IN_DEGREE_DISPATCHED;
    1673           0 :     else                                   disp->pool     [                   idx   ].in_degree = IN_DEGREE_DISPATCHED;
    1674           0 :   } else {
    1675           0 :     if( FD_UNLIKELY( block->dispatched_cnt!=block->completed_cnt       ) ) return 0UL;
    1676           0 :     if( FD_UNLIKELY( !unstaged_txn_ll_is_empty( block->ll, disp->pool ) ) ) {
    1677           0 :       idx = unstaged_txn_ll_idx_peek_head( block->ll, disp->pool );
    1678           0 :       disp->pool[ idx ].in_degree = IN_DEGREE_UNSTAGED_DISPATCHED;
    1679           0 :     } else if( FD_UNLIKELY( !ustlt_dlist_is_empty( block->ustlt_created, disp->ustlt_pool ) ) ) {
    1680           0 :       idx = ustlt_dlist_ele_peek_head( block->ustlt_created, disp->ustlt_pool )->ptxn_idx;
    1681           0 :       disp->ptxn_pool[ idx ].in_degree = IN_DEGREE_UNSTAGED_DISPATCHED;
    1682           0 :       idx = EDGE_MAKE_SPECIAL(PTXN, idx);
    1683           0 :     } else {
    1684           0 :       return 0UL;
    1685           0 :     }
    1686           0 :   }
    1687           0 :   block->dispatched_cnt++;
    1688             : 
    1689           0 :   return idx;
    1690           0 : }
    1691             : 
    1692             : void
    1693             : fd_rdisp_complete_txn( fd_rdisp_t * disp,
    1694             :                        ulong        txn_idx,
    1695           0 :                        int          reclaim ) {
    1696             : 
    1697           0 :   int ptxn = EDGE_IS_PTXN( txn_idx );
    1698           0 :   fd_rdisp_txn_t * rtxn = (!ptxn) ? disp->pool + txn_idx :
    1699           0 :                                     (fd_rdisp_txn_t *)fd_type_pun( (disp->ptxn_pool + EDGE_SPECIAL_IDX(txn_idx)) );
    1700           0 :   fd_rdisp_blockinfo_t * block = NULL;
    1701             : 
    1702           0 :   if( FD_UNLIKELY( rtxn->in_degree==IN_DEGREE_UNSTAGED_DISPATCHED ) ) {
    1703             :     /* Unstaged */
    1704           0 :     if( FD_UNLIKELY( ptxn ) ) {
    1705           0 :       fd_rdisp_ptxn_t  * ptxn_e    = disp->ptxn_pool + EDGE_SPECIAL_IDX(txn_idx); /* == rtxn */
    1706           0 :       ustlt_t          * ustlt_ele = disp->ustlt_pool + ptxn_e->last;
    1707           0 :       block = disp->block_pool + ustlt_ele->key.block_idx;
    1708           0 :       FD_TEST( ustlt_ele==ustlt_dlist_ele_pop_head( block->ustlt_created, disp->ustlt_pool ) );
    1709             :       /* ptxn_e gets freed below */
    1710           0 :       ustlt_dlist_ele_push_tail( disp->ustlt_free, ustlt_ele, disp->ustlt_pool );
    1711             : 
    1712           0 :     } else {
    1713           0 :       block = disp->block_pool + disp->unstaged[ txn_idx ].block_idx;
    1714           0 :       FD_TEST( rtxn==unstaged_txn_ll_ele_peek_head( block->ll, disp->pool ) );
    1715           0 :       unstaged_txn_ll_ele_pop_head( block->ll, disp->pool );
    1716           0 :     }
    1717           0 :     block->completed_cnt++;
    1718           0 :   } else if( FD_LIKELY( rtxn->in_degree==IN_DEGREE_DISPATCHED ) ) {
    1719             :     /* Staged */
    1720           0 :     ulong w_cnt = fd_ulong_if( ptxn, 0UL, 1UL + ((rtxn->edge_cnt_etc    ) & 0x7FU) );
    1721           0 :     ulong r_cnt = (rtxn->edge_cnt_etc>> 7) & 0x7FU;
    1722           0 :     ulong lane  = (rtxn->edge_cnt_etc>>14) & 0x3U;
    1723           0 :     uint  tail_linear_block_num = (uint)(disp->lanes[lane].linear_block_number);
    1724           0 :     ulong edge_idx = 0UL;
    1725           0 :     for( ulong i=0UL; i<w_cnt+r_cnt; i++ ) {
    1726           0 :       edge_t const * e = rtxn->edges+edge_idx;
    1727           0 :       edge_t const  e0 = *e;
    1728           0 :       edge_t ref_to_me = (uint)fd_ulong_if( ptxn, txn_idx, ((txn_idx<<8) | i) );
    1729             : 
    1730             :       /* To help with explanations, consider the following DAG:
    1731             : 
    1732             :            --> B --\   --> E --\
    1733             :           /         V /         V
    1734             :          A           D         (G)
    1735             :           \         ^ \         ^
    1736             :            --> C --/   --> F --/     */
    1737             : 
    1738           0 :       if( FD_UNLIKELY( EDGE_IS_LAST( e0 ) ) ) {
    1739           0 :         ulong acct_idx = EDGE_SPECIAL_IDX( e0 );
    1740           0 :         acct_info_t * ai = disp->acct_pool + acct_idx;
    1741             :         /* If this is a writer, e.g. node G above, we know it's the last
    1742             :            one, so we can clear last_reference.  We know it has to go to
    1743             :            the NEEDS_PTXN state unless LtHash tracking was disabled for it.
    1744             :            If this is a reader, e.g. node E and F above, if node G
    1745             :            didn't exist, we need to check if it's the last one
    1746             :            (me==me->next).  If so, we can clear last_reference.  If not,
    1747             :            we need to delete this node from the linked list.  In either
    1748             :            case, there are only readers or pseudo-transactions, so we're
    1749             :            either in NO_WRITERS or NEEDS_PTXN states, and we stay in
    1750             :            that state. */
    1751           0 :         if( edge_idx<w_cnt ) {
    1752           0 :           ai->last_reference[ lane ] = 0U;
    1753           0 :           ai->states = fd_uchar_if( fd_rdisp_mq_test( disp->mq, acct_idx, lane ), ACCT_INFO_STATE_SET(ai->states, lane, NEEDS_PTXN),
    1754           0 :                                                                                   ACCT_INFO_STATE_SET(ai->states, lane, NO_WRITERS));
    1755           0 :         } else if( e[1]==ref_to_me ) {
    1756           0 :           ai->last_reference[ lane ] = 0U;
    1757           0 :         } else {
    1758           0 :           int _ignore;
    1759           0 :           FOLLOW_EDGE( disp, e[1], _ignore )[2] = e[2];  /* me->next->prev = me->prev */
    1760           0 :           FOLLOW_EDGE( disp, e[2], _ignore )[1] = e[1];  /* me->prev->next = me->next */
    1761           0 :           ai->last_reference[ lane ]= fd_uint_if( ai->last_reference[ lane ]==ref_to_me, e[1], ai->last_reference[ lane ] );
    1762           0 :         }
    1763             : 
    1764             :         /* Potentially transition from ACTIVE -> CACHED */
    1765           0 :         if( FD_UNLIKELY( (ai->states==0)&
    1766           0 :                          (ai->last_reference[ 0 ]==0U)&(ai->last_reference[ 1 ]==0U)&
    1767           0 :                          (ai->last_reference[ 2 ]==0U)&(ai->last_reference[ 3 ]==0U) ) ) {
    1768           0 :           ai->cached = ACCT_INFO_CACHED;
    1769           0 :           free_dlist_idx_push_tail( disp->free_acct_dlist, acct_idx, disp->acct_pool );
    1770           0 :         }
    1771           0 :       } else {
    1772           0 :         int child_is_writer;
    1773             : 
    1774           0 :         edge_t next_e = e0;
    1775           0 :         edge_t const * child_edge;
    1776           0 :         while( 1 ) {
    1777             :           /* This loop first traverses the me->child link, and then
    1778             :              traverses any sibling links.  For example, in the case that
    1779             :              we're completing node D above, the first child_txn is E,
    1780             :              and the second child_txn is F.  child_txn might be a
    1781             :              pseudo-transaction. */
    1782           0 :           /*            */ child_edge = FOLLOW_EDGE(     disp, next_e, child_is_writer );
    1783           0 :           fd_rdisp_txn_t * child_txn  = FOLLOW_EDGE_TXN( disp, next_e                  );
    1784             : 
    1785             :           /* Sanity test */
    1786           0 :           FD_TEST( child_txn->in_degree>0U                   );
    1787           0 :           FD_TEST( child_txn->in_degree<IN_DEGREE_DISPATCHED );
    1788             : 
    1789           0 :           if( FD_UNLIKELY( 0U==(--(child_txn->in_degree)) ) ) {
    1790             :             /* We need an operation something like
    1791             :                fd_frag_meta_ts_decomp. child_txn has the low 16 bits,
    1792             :                and tail_linear_block_num has the full 32 bits, except
    1793             :                for tail_linear_block_num refers to a block < block_depth
    1794             :                later.  Since block_depth<2^16, that means we can resolve
    1795             :                this unambiguously.  Basically, we copy the high 16 bits
    1796             :                frorm tail_linear_block_num unless that would make
    1797             :                linear_block_num larger than tail_linear_block_num, in
    1798             :                which case, we subtract 2^16. */
    1799           0 :             uint low_16_bits = child_txn->edge_cnt_etc>>16;
    1800           0 :             uint linear_block_num = ((tail_linear_block_num & ~0xFFFFU) | low_16_bits) - (uint)((low_16_bits>(tail_linear_block_num&0xFFFFU))<<16);
    1801           0 :             pending_prq_ele_t temp[1] = {{ .score               = child_txn->score,
    1802           0 :                                            .linear_block_number = linear_block_num,
    1803           0 :                                            .txn_idx             = fd_uint_if( EDGE_IS_PTXN(next_e), next_e, next_e>>8 ) }};
    1804           0 :             pending_prq_insert( disp->lanes[ lane ].pending, temp );
    1805           0 :           }
    1806           0 :           if( child_is_writer || child_edge[1]==e0 ) break;
    1807           0 :           next_e = child_edge[1];
    1808           0 :         }
    1809             :         /* In the case that the completed transaction is a reader, say B
    1810             :            or C above, it seems like we should need to remove it from
    1811             :            the doubly linked list, but we actually don't.  The times
    1812             :            that we need to read the sibling pointers are:
    1813             :             1. Completing the writer before a reader (e.g. completing A)
    1814             :             2. Completing a reader that's the last in the DAG (e.g.
    1815             :                completing E/F if G didn't exist)
    1816             :             3. Adding another reader to the same set of readers (e.g. if
    1817             :                G were added as a reader instead of a writer)
    1818             :             4. Adding a writer after a set of readers (e.g. adding G).
    1819             : 
    1820             :            Supposing that the completed transaction is a reader, since
    1821             :            we checked EDGE_IS_LAST( e0 ), we know that there is at least
    1822             :            one writer that follows this reader, e.g. D above.
    1823             : 
    1824             :            And so none of these reason can apply to this group of readers:
    1825             :             1. Completing B or C implies that A has already completed,
    1826             :                so it can't complete again.
    1827             :             2. We know that we're not in that case because we checked
    1828             :                EDGE_IS_LAST( e0 ), and if we're not last now, we cannot
    1829             :                become last later, because the growth happens in the
    1830             :                other direction.
    1831             :             3. Similarly, because we checked EDGE_IS_LAST, any future
    1832             :                additions of readers won't be to this group of readers.
    1833             :             4. Similarly, we know that there already is a writer that
    1834             :                follows this group of readers, so a later writer would
    1835             :                not read this set of readers.
    1836             : 
    1837             :            So then, we don't need to deal with the sibling edges.  The
    1838             :            fact that we only need to do it in one case almost calls into
    1839             :            question whether we need to maintain the whole circular
    1840             :            system in the first place, and whether we could get away with
    1841             :            a reference count or something instead, but it's important in
    1842             :            one critical case: suppose we add a bunch of readers, then
    1843             :            some of them complete, and then we add a writer (case 4
    1844             :            above).  We need to be able to enumerate the nodes in the DAG
    1845             :            that have not yet completed, and we need to be able to remove
    1846             :            them from that set in O(1).  Those two requirements don't
    1847             :            leave us with many alternatives besides a doubly linked list. */
    1848             : 
    1849           0 :         if( FD_UNLIKELY( EDGE_IS_LAST( *child_edge ) ) ) {
    1850             :           /* For example, either:
    1851             :              1. completing D if G didn't exist
    1852             :              2. completing E or F with G in the DAG
    1853             : 
    1854             :              After completing this transaction, there's either 0 writers
    1855             :              left (case 1) or 1 writer left (case 2) in the DAG. and if
    1856             :              there is one, it is the last reference.  In case 1, we need
    1857             :              to know if we have a pending LtHash pseudo-transaction to
    1858             :              determine whether we transition to state NO_WRITERS or
    1859             :              NEEDS_PTXN.  In case 2, we must be in LAST_REF_WAS_WRITE
    1860             :              and we stay there. */
    1861             : 
    1862           0 :           acct_info_t * ai = disp->acct_pool + EDGE_SPECIAL_IDX(*child_edge);
    1863           0 :           uint s = ai->states;
    1864           0 :           if( !ACCT_INFO_STATE_IS( s, lane, LAST_REF_WAS_WRITE ) ) {
    1865           0 :             int needs_ptxn = fd_rdisp_mq_test( disp->mq, EDGE_SPECIAL_IDX( *child_edge ), lane );
    1866           0 :             s = fd_uint_if( needs_ptxn, ACCT_INFO_STATE_SET(s, lane, NEEDS_PTXN), ACCT_INFO_STATE_SET(s, lane, NO_WRITERS) );
    1867           0 :             ai->states = (uchar)s;
    1868           0 :           }
    1869           0 :         }
    1870           0 :       }
    1871           0 :       edge_idx += fd_ulong_if( i<w_cnt, 1UL, 3UL );
    1872           0 :     }
    1873           0 :     block = block_slist_ele_peek_head( disp->lanes[ lane ].block_ll, disp->block_pool );
    1874           0 :     block->completed_cnt++;
    1875           0 :   } else if( FD_LIKELY( rtxn->in_degree==IN_DEGREE_ZOMBIE ) ) {
    1876           0 :     FD_TEST( reclaim );
    1877           0 :     FD_TEST( !ptxn   );
    1878           0 :     block = block_pool_ele( disp->block_pool, rtxn->block_idx );
    1879           0 :     zombie_dlist_ele_remove( block->zombie_list, rtxn, disp->pool );
    1880             :     /* Fall through to the pool release branch below. */
    1881           0 :   } else {
    1882           0 :     FD_LOG_CRIT(( "completed un-dispatched transaction %lu", txn_idx ));
    1883           0 :   }
    1884           0 :   if( reclaim|ptxn ) {
    1885             :     /* For testing purposes, to make sure we don't read a completed
    1886             :        transaction, we can clobber the memory. */
    1887             :     /* memset( disp->pool+txn_idx, '\xCC', sizeof(fd_rdisp_txn_t) ); */
    1888           0 :     rtxn->in_degree = IN_DEGREE_FREE;
    1889           0 :     if( FD_LIKELY( !ptxn ) ) pool_idx_release     ( disp->pool,                        txn_idx   );
    1890           0 :     else                     ptxn_pool_idx_release( disp->ptxn_pool, EDGE_SPECIAL_IDX( txn_idx ) );
    1891           0 :   } else {
    1892           0 :     rtxn->in_degree = IN_DEGREE_ZOMBIE;
    1893           0 :     zombie_dlist_ele_push_tail( block->zombie_list, rtxn, disp->pool );
    1894           0 :     rtxn->block_idx = (uint)block_pool_idx( disp->block_pool, block );
    1895           0 :   }
    1896           0 : }
    1897             : 
    1898             : ulong
    1899             : fd_rdisp_staging_lane_info( fd_rdisp_t           const * disp,
    1900           0 :                             fd_rdisp_staging_lane_info_t out_sched[ static 4 ] ) {
    1901           0 :   for( ulong i=0UL; i<4UL; i++ ) {
    1902           0 :     if( !(disp->free_lanes & (1<<i) ) ) {
    1903           0 :       block_slist_t const * sl = disp->lanes[ i ].block_ll;
    1904           0 :       out_sched[ i ].insert_ready_block   = block_slist_ele_peek_tail( sl, disp->block_pool )->block;
    1905           0 :       out_sched[ i ].schedule_ready_block = block_slist_ele_peek_head( sl, disp->block_pool )->block;
    1906           0 :     }
    1907           0 :   }
    1908           0 :   return 0xFUL & ~(ulong)disp->free_lanes;
    1909           0 : }
    1910             : 
    1911             : void
    1912             : fd_rdisp_pseudo_txn_to_addr( fd_rdisp_t const * disp,
    1913             :                              ulong              txn_idx,
    1914           0 :                              fd_acct_addr_t   * out ) {
    1915           0 :   FD_TEST( txn_idx & FD_RDISP_LTHASH_PSEUDO_TXN );
    1916           0 :   txn_idx ^= FD_RDISP_LTHASH_PSEUDO_TXN;
    1917           0 :   fd_rdisp_ptxn_t const * ptxn = disp->ptxn_pool + txn_idx;
    1918             : 
    1919           0 :   if( FD_LIKELY( (ptxn->in_degree!=IN_DEGREE_UNSTAGED) & (ptxn->in_degree!=IN_DEGREE_UNSTAGED_DISPATCHED) ) ) {
    1920           0 :     *out = disp->acct_pool[ EDGE_SPECIAL_IDX(ptxn->last) ].key;
    1921           0 :   } else {
    1922           0 :     *out = disp->ustlt_pool[ EDGE_SPECIAL_IDX(ptxn->last) ].key.acct;
    1923           0 :   }
    1924           0 : }
    1925             : 
    1926             : void
    1927             : fd_rdisp_add_all_pseudo_txn( fd_rdisp_t          *  disp,
    1928           0 :                              FD_RDISP_BLOCK_TAG_T   insert_block ) {
    1929           0 :   while( fd_rdisp_add_pseudo_txn( disp, insert_block ) );
    1930           0 : }
    1931             : 
    1932             : ulong
    1933             : fd_rdisp_add_pseudo_txn( fd_rdisp_t          *  disp,
    1934           0 :                          FD_RDISP_BLOCK_TAG_T   insert_block ) {
    1935           0 :   fd_rdisp_blockinfo_t * block   = block_map_ele_query( disp->blockmap, &insert_block, NULL, disp->block_pool );
    1936           0 :   if( FD_UNLIKELY( !block || !block->insert_ready ) ) return 0UL;
    1937             : 
    1938           0 :   fd_rdisp_ptxn_t * ptxn;
    1939           0 :   uint ptxn_idx;
    1940             : 
    1941           0 :   if( FD_LIKELY( block->staged ) ) {
    1942           0 :     uint lane = block->staging_lane;
    1943           0 :     if( FD_UNLIKELY( !fd_rdisp_mq_cnt( disp->mq, lane ) ) ) return 0UL;
    1944             : 
    1945           0 :     ptxn = ptxn_pool_ele_acquire( disp->ptxn_pool );
    1946           0 :     ptxn_idx = EDGE_MAKE_SPECIAL(PTXN, (uint)(ptxn - disp->ptxn_pool));
    1947             : 
    1948           0 :     ulong acct_idx = fd_rdisp_mq_pop( disp->mq, lane );
    1949           0 :     acct_info_t * ai = disp->acct_pool + acct_idx;
    1950             : 
    1951           0 :     ptxn->in_degree = 0U;
    1952             :     /* lower txn score means higher priority, and we want pseudo-
    1953             :        transactions for less frequently accessed accounts, i.e. those
    1954             :        with lower EMA value to be higher priority, so we just use that
    1955             :        value directly, with the last_serializing addition. */
    1956           0 :     if( FD_UNLIKELY( block->last_insert_was_serializing ) ) {
    1957           0 :       block->last_serializing = block->inserted_cnt;
    1958           0 :     }
    1959           0 :     block->last_insert_was_serializing = 0U;
    1960           0 :     ptxn->score = (ai->ema_refs<FD_RDISP_MAX_SCORE ? ai->ema_refs : FD_RDISP_MAX_SCORE) + (float)block->last_serializing;
    1961             : 
    1962             :     /* see add_txn for this line */
    1963           0 :     ptxn->edge_cnt_etc = ((block->linear_block_number<<16) | (lane<<14)) - 1U;
    1964             : 
    1965             :     /* add_edges is more general than what we need, but it's better to
    1966             :        have all the logic in one place.  We don't need to pass new_wr
    1967             :        because it's only a reader, so it won't be accessed anyway. */
    1968           0 :     add_edges( disp, (fd_rdisp_txn_t *)fd_type_pun( ptxn ), &(ai->key), 1UL, lane, 0, 0, NULL, 0UL );
    1969             :     /* since we're kind of cheating with edge_cnt_etc, we need to fix it
    1970             :        here.  Additionally, type punning ptxn like that is a bit
    1971             :        fast-and-loose with the C spec, so add a compiler fence to make
    1972             :        sure these writes can't be reordered. */
    1973           0 :     FD_COMPILER_MFENCE();
    1974           0 :     ptxn->edge_cnt_etc += 1U<<7;
    1975           0 :     ptxn->last = EDGE_MAKE_SPECIAL(LAST, (uint)acct_idx);
    1976             : 
    1977             :     /* add_edge handles most of the state transitions, but it treats the
    1978             :        pseudo-transaction like a reader, so it needs a little fixing. */
    1979           0 :     uint states = ai->states;
    1980           0 :     states = fd_uint_if( ACCT_INFO_STATE_IS( states, lane, NEEDS_PTXN ), ACCT_INFO_STATE_SET( states, lane, NO_WRITERS ), states );
    1981           0 :     ai->states = (uchar)states;
    1982             : 
    1983           0 :     if( FD_LIKELY( ptxn->in_degree==0U ) ) {
    1984           0 :       pending_prq_ele_t temp[1] = {{ .score = ptxn->score, .linear_block_number = block->linear_block_number, .txn_idx = ptxn_idx }};
    1985           0 :       pending_prq_insert( disp->lanes[ block->staging_lane ].pending, temp );
    1986           0 :     }
    1987           0 :   } else {
    1988           0 :     if( FD_UNLIKELY( ustlt_dlist_is_empty( block->ustlt_needed, disp->ustlt_pool ) ) ) return 0UL;
    1989             : 
    1990           0 :     ptxn = ptxn_pool_ele_acquire( disp->ptxn_pool );
    1991           0 :     ptxn_idx = EDGE_MAKE_SPECIAL(PTXN, (uint)(ptxn - disp->ptxn_pool));
    1992             : 
    1993           0 :     ustlt_t * ustlt_ele = ustlt_dlist_ele_pop_head( block->ustlt_needed, disp->ustlt_pool );
    1994           0 :     ustlt_map_ele_remove_fast( disp->ustlt_map, ustlt_ele, disp->ustlt_pool );
    1995           0 :     ptxn->in_degree = IN_DEGREE_UNSTAGED;
    1996           0 :     ptxn->score = 0.0f;
    1997             : 
    1998           0 :     ptxn->edge_cnt_etc = 0U;
    1999             : 
    2000           0 :     ptxn->last          = (uint)(ustlt_ele - disp->ustlt_pool);
    2001           0 :     ustlt_ele->ptxn_idx = (uint)(ptxn      - disp->ptxn_pool );
    2002             : 
    2003           0 :     ustlt_dlist_ele_push_tail( block->ustlt_created, ustlt_ele, disp->ustlt_pool );
    2004           0 :   }
    2005           0 :   block->inserted_cnt++;
    2006           0 :   return ptxn_idx;
    2007           0 : }
    2008             : 
    2009             : int
    2010             : fd_rdisp_add_writable( fd_rdisp_t           * disp,
    2011             :                        FD_RDISP_BLOCK_TAG_T   insert_block,
    2012           0 :                        fd_acct_addr_t const * addr ) {
    2013           0 :   fd_rdisp_blockinfo_t * block   = block_map_ele_query( disp->blockmap, &insert_block, NULL, disp->block_pool );
    2014           0 :   if( FD_UNLIKELY( !block || !block->insert_ready ) ) return -1;
    2015             : 
    2016           0 :   if( FD_LIKELY( block->staged ) ) {
    2017           0 :     ulong idx  = lookup_pubkey( disp, addr );
    2018           0 :     ulong lane = block->staging_lane;
    2019           0 :     if( FD_LIKELY( fd_rdisp_mq_test( disp->mq, idx, block->staging_lane ) ) ) return 0;
    2020             : 
    2021           0 :     acct_info_t * ai = disp->acct_pool+idx;
    2022           0 :     uint states = ai->states;
    2023           0 :     states = fd_uint_if( ACCT_INFO_STATE_IS( states, lane, NO_WRITERS ), ACCT_INFO_STATE_SET( states, lane, NEEDS_PTXN ), states );
    2024           0 :     ai->states = (uchar)states;
    2025           0 :     fd_rdisp_mq_insert( disp->mq, idx, lane, 0.0f );
    2026           0 :     return 1;
    2027           0 :   } else {
    2028           0 :     ustlt_key_t query[1] = {{
    2029           0 :       .block_idx = (uint)(block-disp->block_pool),
    2030           0 :       .acct      = *addr
    2031           0 :     }};
    2032           0 :     int new = (ustlt_map_idx_query( disp->ustlt_map, query, ULONG_MAX, disp->ustlt_pool )==ULONG_MAX);
    2033           0 :     if( FD_LIKELY( !new ) ) return 0;
    2034             : 
    2035           0 :     ulong ustlt_idx = ustlt_dlist_idx_pop_head( disp->ustlt_free, disp->ustlt_pool );
    2036           0 :     disp->ustlt_pool[ ustlt_idx ].key = *query;
    2037           0 :     ustlt_map_idx_insert( disp->ustlt_map, ustlt_idx, disp->ustlt_pool );
    2038           0 :     ustlt_dlist_idx_push_tail( block->ustlt_needed, ustlt_idx, disp->ustlt_pool );
    2039           0 :     return 1;
    2040           0 :   }
    2041           0 : }
    2042             : 
    2043             : ulong
    2044             : fd_rdisp_add_extra_pseudo_txn( fd_rdisp_t           * disp,
    2045             :                                FD_RDISP_BLOCK_TAG_T   schedule_block,
    2046           0 :                                fd_acct_addr_t const * addr ) {
    2047           0 :   fd_rdisp_blockinfo_t * block   = block_map_ele_query( disp->blockmap, &schedule_block, NULL, disp->block_pool );
    2048           0 :   if( FD_UNLIKELY( !block || !block->schedule_ready ) ) return 0UL;
    2049             : 
    2050           0 :   uint ptxn_idx;
    2051             : 
    2052           0 :   if( FD_LIKELY( block->staged ) ) {
    2053           0 :     ulong idx  = lookup_pubkey( disp, addr );
    2054           0 :     uint  lane = block->staging_lane;
    2055             : 
    2056           0 :     acct_info_t * ai = disp->acct_pool+idx;
    2057           0 :     edge_t last = ai->last_reference[lane];
    2058           0 :     int insert_inline = 0;
    2059           0 :     if( FD_UNLIKELY( block->insert_ready ) ) {
    2060           0 :       insert_inline = 1;
    2061           0 :     } else if( FD_UNLIKELY( EDGE_IS_PTXN(last) ) ) {
    2062             :       /* If this is for the right block, we got lucky */
    2063           0 :       fd_rdisp_ptxn_t const * ptxn = disp->ptxn_pool + EDGE_SPECIAL_IDX(last);
    2064           0 :       if( FD_UNLIKELY( (ptxn->edge_cnt_etc>>16)==(0xFFFF&block->linear_block_number) ) ) return last;
    2065           0 :     } else if( FD_UNLIKELY( last==0U ) ) {
    2066           0 :       insert_inline=1;
    2067           0 :     } else {
    2068           0 :       fd_rdisp_txn_t const * txn = FOLLOW_EDGE_TXN( disp, last );
    2069           0 :       insert_inline = (txn->edge_cnt_etc>>16)==(0xFFFF&block->linear_block_number);
    2070           0 :     }
    2071             : 
    2072             :     /* If we haven't inserted an extra ptxn yet, and we're inserting
    2073             :        inline, treat this as a normal non-seralizing transaction.  ... */
    2074           0 :     if( FD_UNLIKELY( insert_inline && block->last_insert_was_serializing && !block->inserted_extra_ptxn ) ) {
    2075           0 :       block->last_serializing = block->inserted_cnt;
    2076           0 :       block->last_insert_was_serializing = 0U;
    2077           0 :     } else if( FD_UNLIKELY( !insert_inline && !block->inserted_extra_ptxn ) ) {
    2078             :       /* Otherwise, if this is the first non-inline one, bump
    2079             :          last_serializing once and don't touch it again.  The block
    2080             :          can't be insert-ready, so we won't insert any other
    2081             :          transactions that aren't from this function. */
    2082           0 :       block->last_serializing = block->inserted_cnt;
    2083           0 :       block->last_insert_was_serializing = 0U;
    2084           0 :       block->inserted_extra_ptxn = 1;
    2085           0 :     }
    2086             : 
    2087           0 :     fd_rdisp_ptxn_t * ptxn = ptxn_pool_ele_acquire( disp->ptxn_pool );
    2088           0 :     ptxn_idx = EDGE_MAKE_SPECIAL(PTXN, (uint)(ptxn - disp->ptxn_pool));
    2089           0 :     if( FD_LIKELY( insert_inline ) ) {
    2090             :       /* What follows below is essentially the same as the normal
    2091             :          add_pseudo_txn case */
    2092           0 :       ptxn->in_degree = 0U;
    2093           0 :       ptxn->score = (ai->ema_refs<FD_RDISP_MAX_SCORE ? ai->ema_refs : FD_RDISP_MAX_SCORE) + (float)block->last_serializing;
    2094           0 :       ptxn->edge_cnt_etc = ((block->linear_block_number<<16) | (lane<<14)) - 1U;
    2095           0 :       add_edges( disp, (fd_rdisp_txn_t *)fd_type_pun( ptxn ), &(ai->key), 1UL, lane, 0, 0, NULL, 0UL );
    2096           0 :       FD_COMPILER_MFENCE();
    2097           0 :       ptxn->edge_cnt_etc += 1U<<7;
    2098           0 :       ptxn->last = EDGE_MAKE_SPECIAL(LAST, (uint)idx);
    2099             : 
    2100           0 :       uint states = ai->states;
    2101           0 :       states = fd_uint_if( ACCT_INFO_STATE_IS( states, lane, NEEDS_PTXN ), ACCT_INFO_STATE_SET( states, lane, NO_WRITERS ), states );
    2102           0 :       ai->states = (uchar)states;
    2103             : 
    2104           0 :       if( FD_LIKELY( ptxn->in_degree==0U ) ) {
    2105           0 :         pending_prq_ele_t temp[1] = {{ .score = ptxn->score, .linear_block_number = block->linear_block_number, .txn_idx = ptxn_idx }};
    2106           0 :         pending_prq_insert( disp->lanes[ block->staging_lane ].pending, temp );
    2107           0 :       }
    2108           0 :     } else {
    2109             :       /* Otherwise there's already a transaction for a later block in
    2110             :          the DAG for this account, we insert a hacky 0-edge pseudo-txn
    2111             :          and make use of the serializing functionality to make sure it
    2112             :          executes after all the transactions.  Since this block is not
    2113             :          insert-ready, we don't need to worry about preventing later
    2114             :          transactions from happening before this one. */
    2115             : 
    2116           0 :       ptxn->in_degree = 0U;
    2117           0 :       ptxn->edge_cnt_etc = ((block->linear_block_number<<16) | (lane<<14)); /* r_cnt==0, w_cnt is ignored */
    2118           0 :       ptxn->score = (ai->ema_refs<FD_RDISP_MAX_SCORE ? ai->ema_refs : FD_RDISP_MAX_SCORE) + (float)block->last_serializing;
    2119           0 :       ptxn->last = EDGE_MAKE_SPECIAL(LAST, (uint)idx);
    2120             :       /* We know that there's a transaction that must execute after this
    2121             :          one in the DAG, so the account info can't get freed until after
    2122             :          this transaction has completed.  That means we don't need to
    2123             :          touch ai->states. */
    2124           0 :       pending_prq_ele_t temp[1] = {{ .score = ptxn->score, .linear_block_number = block->linear_block_number, .txn_idx = ptxn_idx }};
    2125           0 :       pending_prq_insert( disp->lanes[ block->staging_lane ].pending, temp );
    2126           0 :     }
    2127           0 :   } else {
    2128           0 :     ulong ustlt_idx = ustlt_dlist_idx_pop_head( disp->ustlt_free, disp->ustlt_pool );
    2129           0 :     disp->ustlt_pool[ ustlt_idx ].key.block_idx = (uint)(block-disp->block_pool);
    2130           0 :     disp->ustlt_pool[ ustlt_idx ].key.acct      = *addr;
    2131             : 
    2132           0 :     fd_rdisp_ptxn_t * ptxn = ptxn_pool_ele_acquire( disp->ptxn_pool );
    2133           0 :     ptxn_idx = EDGE_MAKE_SPECIAL(PTXN, (uint)(ptxn - disp->ptxn_pool));
    2134             : 
    2135           0 :     ptxn->in_degree = IN_DEGREE_UNSTAGED;
    2136           0 :     ptxn->score = 0.0f;
    2137           0 :     ptxn->edge_cnt_etc = 0U;
    2138           0 :     ptxn->last = (uint)ustlt_idx;
    2139             : 
    2140           0 :     disp->ustlt_pool[ ustlt_idx ].ptxn_idx = (uint)(ptxn - disp->ptxn_pool);
    2141           0 :     ustlt_dlist_idx_push_tail( block->ustlt_created, ustlt_idx, disp->ustlt_pool );
    2142           0 :   }
    2143           0 :   block->inserted_cnt++;
    2144           0 :   return ptxn_idx;
    2145           0 : }
    2146             : 
    2147             : void
    2148             : fd_rdisp_verify( fd_rdisp_t const * disp,
    2149           0 :                  uint             * scratch ) {
    2150           0 :   ulong acct_depth  = disp->depth*FD_RDISP_MAX_ACCT_PER_TXN;
    2151           0 :   ulong block_depth = disp->block_depth;
    2152           0 :   FD_TEST( 0==acct_map_verify ( disp->acct_map,  acct_depth +1UL, disp->acct_pool  ) );
    2153           0 :   FD_TEST( 0==block_map_verify( disp->blockmap,  block_depth+1UL, disp->block_pool ) );
    2154           0 :   FD_TEST( 0==ustlt_map_verify( disp->ustlt_map, acct_depth +1UL, disp->ustlt_pool ) );
    2155             : 
    2156             :   /* Every element of the free list that is in the map is CACHED under
    2157             :      its own key, every element in the map is either ACTIVE with a
    2158             :      reference in some lane or CACHED, and the CACHED ones are exactly
    2159             :      the mapped elements of the free list. */
    2160           0 :   ulong cached_cnt = 0UL;
    2161           0 :   for( free_dlist_iter_t it=free_dlist_iter_fwd_init( disp->free_acct_dlist, disp->acct_pool );
    2162           0 :        !free_dlist_iter_done( it, disp->free_acct_dlist, disp->acct_pool );
    2163           0 :        it=free_dlist_iter_fwd_next( it, disp->free_acct_dlist, disp->acct_pool ) ) {
    2164           0 :     ulong idx = free_dlist_iter_idx( it, disp->free_acct_dlist, disp->acct_pool );
    2165           0 :     acct_info_t const * ai = disp->acct_pool+idx;
    2166           0 :     if( !ai->next ) continue; /* FREE */
    2167           0 :     FD_TEST( ai->cached==ACCT_INFO_CACHED && !ai->states );
    2168           0 :     FD_TEST( acct_map_idx_query_const( disp->acct_map, &ai->key, ULONG_MAX, disp->acct_pool )==idx );
    2169           0 :     cached_cnt++;
    2170           0 :   }
    2171           0 :   for( acct_map_iter_t it=acct_map_iter_init( disp->acct_map, disp->acct_pool );
    2172           0 :        !acct_map_iter_done( it, disp->acct_map, disp->acct_pool );
    2173           0 :        it=acct_map_iter_next( it, disp->acct_map, disp->acct_pool ) ) {
    2174           0 :     acct_info_t const * ai = acct_map_iter_ele_const( it, disp->acct_map, disp->acct_pool );
    2175           0 :     if( ai->cached==ACCT_INFO_CACHED ) cached_cnt--;
    2176           0 :     else FD_TEST( ai->states | ai->last_reference[ 0 ] | ai->last_reference[ 1 ] | ai->last_reference[ 2 ] | ai->last_reference[ 3 ] );
    2177           0 :   }
    2178           0 :   FD_TEST( cached_cnt==0UL );
    2179             : 
    2180             :   /* Check all the in degree counts are right */
    2181           0 :   memset( scratch, '\0', sizeof(uint)*(disp->depth*(FD_RDISP_MAX_ACCT_PER_TXN+1UL)+2UL) );
    2182           0 :   scratch[ 0 ] = UINT_MAX;
    2183             : 
    2184           0 :   for( ulong j=0UL; j<disp->depth*(FD_RDISP_MAX_ACCT_PER_TXN+1UL)+2UL; j++ ) {
    2185           0 :     int ptxn = j>disp->depth;
    2186           0 :     fd_rdisp_txn_t const * rtxn = !ptxn ? disp->pool+j : (fd_rdisp_txn_t const *)(disp->ptxn_pool+(j-disp->depth-1UL));
    2187           0 :     if( rtxn->in_degree==IN_DEGREE_FREE ) { scratch[ j ]=UINT_MAX; continue; }
    2188             : 
    2189           0 :     if( (rtxn->in_degree==IN_DEGREE_UNSTAGED_DISPATCHED) |
    2190           0 :         (rtxn->in_degree==IN_DEGREE_UNSTAGED)            |
    2191           0 :         (rtxn->in_degree==IN_DEGREE_ZOMBIE) ) continue;
    2192             : 
    2193           0 :     ulong w_cnt = fd_ulong_if( ptxn, 0UL, 1UL + ((rtxn->edge_cnt_etc    ) & 0x7FU) );
    2194           0 :     ulong r_cnt = (rtxn->edge_cnt_etc>> 7) & 0x7FU;
    2195           0 :     ulong edge_idx = 0UL;
    2196             : 
    2197           0 :     for( ulong i=0UL; i<w_cnt+r_cnt; i++ ) {
    2198           0 :       edge_t const * e = rtxn->edges+edge_idx;
    2199           0 :       edge_t const  e0 = *e;
    2200             : 
    2201           0 :       edge_idx += fd_ulong_if( i<w_cnt, 1UL, 3UL );
    2202             : 
    2203           0 :       if( FD_UNLIKELY( EDGE_IS_LAST( e0 ) ) ) {
    2204             :         /* Make sure acct is ACTIVE */
    2205           0 :         acct_info_t * ai = disp->acct_pool + EDGE_SPECIAL_IDX(e0);
    2206           0 :         FD_TEST( !( (ai->states==0)&
    2207           0 :                     (ai->last_reference[ 0 ]==0U)&(ai->last_reference[ 1 ]==0U)&
    2208           0 :                     (ai->last_reference[ 2 ]==0U)&(ai->last_reference[ 3 ]==0U) ) );
    2209           0 :         FD_TEST( EDGE_SPECIAL_IDX(e0)==acct_map_idx_query_const( disp->acct_map, &(ai->key), ULONG_MAX, disp->acct_pool ) );
    2210           0 :         continue;
    2211           0 :       }
    2212             : 
    2213           0 :       edge_t next_e = e0;
    2214           0 :       edge_t const * child_edge;
    2215           0 :       edge_t last_e = 0U;
    2216           0 :       while( 1 ) {
    2217           0 :         int child_is_writer;
    2218             :         /* This loop first traverses the me->child link, and then
    2219             :            traverses any sibling links.  For example, in the case that
    2220             :            we're completing node D above, the first child_txn is E,
    2221             :            and the second child_txn is F. */
    2222           0 :         child_edge = FOLLOW_EDGE(     disp, next_e, child_is_writer );
    2223             : 
    2224           0 :         scratch[ fd_ulong_if( EDGE_IS_PTXN(next_e), EDGE_SPECIAL_IDX(next_e)+disp->depth+1UL, next_e>>8 ) ]++;
    2225             : 
    2226           0 :         if( child_is_writer || child_edge[1]==e0 ) break;
    2227           0 :         if( last_e!=0U ) FD_TEST( child_edge[2]==last_e );
    2228           0 :         last_e = next_e;
    2229           0 :         next_e = child_edge[1];
    2230           0 :         FD_TEST( next_e>=0x100U );
    2231           0 :       }
    2232           0 :     }
    2233           0 :   }
    2234           0 :   for( ulong i=0UL; i<disp->depth*(FD_RDISP_MAX_ACCT_PER_TXN+1UL)+2UL; i++ ) {
    2235           0 :     fd_rdisp_txn_t const * rtxn = i<=disp->depth ? disp->pool+i : (fd_rdisp_txn_t const *)(disp->ptxn_pool+(i-disp->depth-1UL));
    2236           0 :     FD_TEST( scratch[ i ]==UINT_MAX ||
    2237           0 :              rtxn->in_degree==IN_DEGREE_DISPATCHED ||
    2238           0 :              rtxn->in_degree==IN_DEGREE_UNSTAGED ||
    2239           0 :              rtxn->in_degree==IN_DEGREE_UNSTAGED_DISPATCHED ||
    2240           0 :              rtxn->in_degree==IN_DEGREE_ZOMBIE ||
    2241           0 :              rtxn->in_degree==scratch[ i ] );
    2242           0 :   }
    2243           0 : }
    2244             : 
    2245           0 : void * fd_rdisp_leave ( fd_rdisp_t * disp ) { return disp; }
    2246           0 : void * fd_rdisp_delete( void * mem        ) { return  mem; }

Generated by: LCOV version 1.14