LCOV - code coverage report
Current view: top level - discof/replay - fd_rdisp.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 638 652 97.9 %
Date: 2026-09-17 04:28:31 Functions: 22 23 95.7 %

          Line data    Source code
       1             : #include "fd_rdisp.h"
       2             : #include "../../util/fd_hash32.h"
       3             : #include <math.h> /* for the EMA */
       4             : 
       5             : /* The conflict graph that this file builds is not a general DAG, but
       6             :    the union of several special account-conflict graphs.  Each
       7             :    account-conflict graph has special structure:
       8             :                            ---> 3 --
       9             :                           /          \
      10             :                          /            v
      11             :                1  ---> 2 -----> 4 --> 6 ----> 7
      12             :                         \             ^
      13             :                          \           /
      14             :                           ----> 5 --
      15             : 
      16             :    That is, the graph is almost a line, but may have fan-out and fan-in
      17             :    regions.  The tricky part about representing a graph like this
      18             :    without dynamic memory allocation is that nodes may have arbitrary
      19             :    in-degree and out-degree.  Thus, we use a pretty standard trick and
      20             :    use sibling pointers, denoted below with dotted lines.  Each node
      21             :    maintains at most one successor pointer and at most one sibling
      22             :    pointer.  Although not shown below, the sibling pointers are
      23             :    circularly linked, so 5's sibling is 3.  Additionally, though not
      24             :    shown below, the child of the last node (7) stores information about
      25             :    what account address all this is for, which facilitates deleting the
      26             :    last node in the graph.
      27             : 
      28             : 
      29             :                               --> 3 --
      30             :                              /    ^   \
      31             :                             /     :    \
      32             :                            /      V     v
      33             :                  1  ---> 2        4 --> 6 ----> 7
      34             :                                   ^     ^
      35             :                                   :    /
      36             :                                   V   /
      37             :                                   5 --
      38             : 
      39             :    The normal edge 2->3 along with the sibling edge 3..>4 implies a
      40             :    normal edge 2->4.  That then transitively implies an edge 2->5.
      41             : 
      42             :    We want each node to maintain a count of its in-degree so that we
      43             :    know when it can be executed.  The implied edges also count for the
      44             :    in-degree.  In this example, node 1 has in-degree 0, node 6 has
      45             :    in-degree 3, and the rest have in-degree 1.
      46             : 
      47             :    Maintaining each account-conflict graph is relatively easy given the
      48             :    operations we want to support.  Only the details about sibling edges
      49             :    are worth mentioning.  For example, when deleting node 2, we
      50             :    decrement the in-degree count for its successor, and then follow the
      51             :    sibling pointers, decrementing all the in-degree counts as we go to
      52             :    mark the deletion of the implied edges.  Sibling edges also need to
      53             :    be doubly linked, so that e.g. nodes 3 and 5 can be re-linked in O(1)
      54             :    if node 4 is deleted.  They're also circularly linked as well for
      55             :    convenience.
      56             : 
      57             :    When building the graph, we maintain a map of account to the last
      58             :    node that references it, whether that was a read or write, and
      59             :    whether there are any writers to that account in the graph right now.
      60             :    If the new node reads from an account that was last read, the new
      61             :    node becomes a sibling of the last read, with in-degree increased if
      62             :    there are any writers.  Otherwise, it becomes a successor of the node
      63             :    that last referenced the account. */
      64             : 
      65             : /* For a task like this with lots of graph traversal and pointer
      66             :    chasing, performance is typically limited by memory latency.  That
      67             :    means that the more that can fit in cache, the better the
      68             :    performance.  This implementation uses a lot of bit-packing to
      69             :    improve cache footprint. */
      70             : 
      71             : /* The following structs are all very local to this compilation unit,
      72             :    and so they don't have globally acceptable type names (e.g.
      73             :    fd_rdisp_edge_t). */
      74             : 
      75      155841 : #define MAX_ACCT_PER_TXN FD_TXN_ACCT_ADDR_MAX
      76             : FD_STATIC_ASSERT( MAX_ACCT_PER_TXN<=128UL, max_acct_per_txn );
      77             : 
      78             : /* edge_t: Fields typed edge_t represent an edge in one of the parallel
      79             :    account-conflict DAGs.  Each transaction stores a list of all its
      80             :    outgoing edges.  The type is actually a union of bitfield, but C
      81             :    bitfields are gross, so we just do it manually with macros.  If the
      82             :    high bit is set, that means the transaction storing this edge_t value
      83             :    is the last in this specific DAG, and the lower 31 bits of the
      84             :    value are an index in the map_pool for the account pubkey for this
      85             :    DAG.  See the comments about the hidden edge outgoing from node 7 in
      86             :    the DAG at the top of this file for an example.
      87             :    If the high bit is not set, then the next 23 bits store the
      88             :    destination of the edge, represented by its index in the pool of
      89             :    transactions.  Then the lowest 8 bits store the account index within
      90             :    that transaction of the edge that is part of the same
      91             :    account-specific DAG as this edge.  Because the max depth is 2^23-1,
      92             :    and each transaction can reference FD_TXN_ACCT_ADDR_MAX accounts,
      93             :    the max accounts that can be referenced fits in 30 bits.
      94             :    The proper type would be something like
      95             :    typedef union {
      96             :      struct {
      97             :        uint is_last:1;
      98             :        uint map_pool_idx:31;
      99             :      } last;
     100             :      struct {
     101             :        uint is_last:1;
     102             :        uint txn_idx:23;
     103             :        uint acct_idx:8;
     104             :      } e;
     105             :    } edge_t;
     106             : 
     107             :    */
     108             : typedef uint edge_t;
     109             : 
     110             : 
     111             : /* fd_rdisp_txn is the representation of a transaction as a node in the
     112             :    DAG. */
     113             : struct fd_rdisp_txn {
     114             :   /* in_degree: The total number of edges summed across all account DAGs
     115             :      with this node as their destination.  In the worst case, all the
     116             :      other transactions in the pool read from each of the max number of
     117             :      accounts that this transaction writes to,  so there are
     118             :      MAX_ACCT_PER_TXN*depth edges that come into this node, which fits in
     119             :      about 30 bits, so we have some room for special values.  If
     120             :      in_degree is one of the following values, then
     121             :      the transaction is: */
     122    47394837 : #define IN_DEGREE_FREE                (UINT_MAX   )
     123    11532921 : #define IN_DEGREE_UNSTAGED            (UINT_MAX-1U)/* unstaged, not dispatched */
     124      552843 : #define IN_DEGREE_DISPATCHED          (UINT_MAX-2U)/* staged,   dispatched */
     125    11532603 : #define IN_DEGREE_UNSTAGED_DISPATCHED (UINT_MAX-3U)/* unstaged, dispatched */
     126    11532300 : #define IN_DEGREE_ZOMBIE              (UINT_MAX-4U)/* zombie */
     127             :   /* a transaction that is staged and dispatched is must have an
     128             :      in_degree of 0.  in_degree isn't a meaningful concept for unstaged
     129             :      transactions. */
     130             :   uint    in_degree;
     131             : 
     132             :   /* score: integer part stores how many transactions in the block must
     133             :      have completed before this transaction can be scheduled.  This is
     134             :      useful for transactions marked as serializing.  The fractional part
     135             :      gives some measure of how urgent the transaction is, where lower is
     136             :      more urgent.  This means we can't have more transactions in a block
     137             :      marked as serializing than the first integer that a float cannot
     138             :      represent.  That value is about 16M, which is much higher than the
     139             :      maximum number of transactions in a block, so this is not a
     140             :      problem.  If in the very rare case that there are more than just a
     141             :      few serializing transactions, we will lose a bit of precision for
     142             :      the fractional portion, which makes total sense; if there's not
     143             :      much room for parallel execution, having the optimal parallel
     144             :      execution is not very important. */
     145             :   float   score;
     146             : 
     147             :   /* edge_cnt_etc:
     148             :      0xFFFF0000 (16 bits) for linear block number,
     149             :      0x0000C000 (2 bits) for concurrency lane,
     150             :      0x00003F80 (7 bits) for r_cnt
     151             :      0x0000007F (7 bits) for w_cnt_1.  Transactions have at least one
     152             :      writable account and at most MAX_ACCT_PER_TXN total accounts, so
     153             :      storing w_cnt_1 = w_cnt - 1 fits in 7 bits. */
     154             :   union {
     155             :     uint edge_cnt_etc;
     156             :     /* edge_cnt_etc is only used when the transaction is STAGED and
     157             :        PENDING, READY, or DISPATCHED.  If UNSTAGED or FREE, the next
     158             :        pointer is also here.  It can't be UNSTAGED and FREE at the same
     159             :        time, so there's no conflict with storage there either. */
     160             :     uint unstaged_next;
     161             :     uint free_next;
     162             :     /* If ZOMBIE, pointer back to block is here.  No storage conflict
     163             :        because ZOMBIE is an exclusive state. */
     164             :     uint block_idx;
     165             :   };
     166             : 
     167             : 
     168             :   /* When a transaction writes to an account, it only creates one
     169             :      link.  When a transaction reads from an account, we need the full
     170             :      doubly linked list with its siblings, so it creates 3 edges
     171             :      (child, next, prev).  All edges from writable accounts come first,
     172             :      and we keep track of how many there are.  In the worst case, all
     173             :      accounts are reads, so we size it appropriately. */
     174             :   edge_t edges[3UL*MAX_ACCT_PER_TXN]; /* addressed [0, w_cnt_1+1+3*r_cnt) */
     175             : };
     176             : typedef struct fd_rdisp_txn fd_rdisp_txn_t;
     177             : 
     178             : #define EDGE_IS_LAST(x) ((x)&0x80000000U)
     179             : 
     180             : /* Two more definitions:
     181             :    An edge index is an array position within the edges array.  An
     182             :    account index is a position within a transaction's account addresses,
     183             :    reordered so that the writable ones come first.  Since writable
     184             :    accounts use one position in the edges array per account address,
     185             :    these often coincide. */
     186             : 
     187             : 
     188             : /* FOLLOW_EDGE and FOLLOW_EDGE_TXN are helper macros for dealing with
     189             :    edges.  Given an edge_t x, and transaction pool base, FOLLOW_EDGE_TXN
     190             :    returns a pointer to transaction that the edge points to; FOLLOW_EDGE
     191             :    returns a pointer to the (first) edge_t within that transaction that
     192             :    is part of the same DAG as this edge.  FOLLOW_EDGE and
     193             :    FOLLOW_EDGE_TXN must not be called if EDGE_IS_LAST is non-zero.
     194             : 
     195             :    Then the edge index, i.e. the position in the edges array of the
     196             :    (first) edge for an account index is:
     197             :           acct_idx                              if acct_idx<=w_cnt_1
     198             :           w_cnt_1+1 + 3*(acct_idx-w_cnt_1-1)    else.
     199             :   Simplifying gives
     200             :          acct_idx + 2*signed_max( 0, acct_idx-w_cnt_1-1 ).
     201             :   In doing this calculation, we also basically get for free whether the
     202             :   edge is for a writable account or a readonly account, so we return
     203             :   that as well via the w parameter.  If the child transaction only reads
     204             :   the account address for this DAG, then the next and prev pointers can
     205             :   be accessed using the returned value +1 and +2, respectively. */
     206    38507154 : #define FOLLOW_EDGE(base, x, w) (__extension__({                                      \
     207    38507154 :         uint __e = (x);                                                               \
     208    38507154 :         fd_rdisp_txn_t * __txn = ((base)+(__e>>8));                                   \
     209    38507154 :         uint __w_cnt_1 = __txn->edge_cnt_etc & 0x7FU;                                 \
     210    38507154 :         uint __idx  = (__e & 0xFFU);                                                  \
     211    38507154 :         (w) = __idx<=__w_cnt_1;                                                       \
     212    38507154 :         (void)(w);  /* not robust... */                                               \
     213    38507154 :         __txn->edges + __idx + 2*fd_int_max( 0, (int)(__idx)-(int)(__w_cnt_1)-1 ); }))
     214    31259313 : #define FOLLOW_EDGE_TXN(base, x) ( (base)+((x)>>8) )
     215             : 
     216             : /* The pool and slist are almost the same, but they are used
     217             :    differently, so keep them as different structures for now. */
     218             : 
     219             : #define POOL_NAME     pool
     220         297 : #define POOL_T        fd_rdisp_txn_t
     221             : #define POOL_IDX_T    uint
     222     1350198 : #define POOL_NEXT     free_next
     223             : #define POOL_SENTINEL 1
     224             : #include "../../util/tmpl/fd_pool.c"
     225             : 
     226             : #define SLIST_NAME unstaged_txn_ll
     227             : #define SLIST_ELE_T fd_rdisp_txn_t
     228         318 : #define SLIST_IDX_T uint
     229        1272 : #define SLIST_NEXT  unstaged_next
     230             : #include "../../util/tmpl/fd_slist.c"
     231             : 
     232             : #define DLIST_IDX_T uint
     233          27 : #define DLIST_PREV  edges[0]
     234          27 : #define DLIST_NEXT  edges[1]
     235             : #define DLIST_NAME  zombie_dlist
     236             : #define DLIST_ELE_T fd_rdisp_txn_t
     237             : #include "../../util/tmpl/fd_dlist.c"
     238             : 
     239             : 
     240             : /* ACCT_INFO_FLAG: It's a bit unfortunate that we have to maintain these
     241             :    flags, but basically we need to be able to distinguish the case where
     242             :    there are only readers so that we don't increment in_degree when
     243             :    adding a new fd_rdisp_txn.  If we have any writers, the only way to
     244             :    transition into a state where there are only readers is to complete
     245             :    the last writer.  We know we are in this case when the completed
     246             :    node's child doesn't have a child, and the completed node's child is
     247             :    a reader, as indicated by the LAST_REF_WAS_WRITE bit.
     248             :    LAST_REF_WAS_WRITE also has the advantage of being easy to
     249             :    maintain. */
     250     6087087 : #define ACCT_INFO_FLAG_LAST_REF_WAS_WRITE(lane) (((uchar)1)<<(2*(lane)))
     251     5994870 : #define ACCT_INFO_FLAG_ANY_WRITERS(       lane) (((uchar)2)<<(2*(lane)))
     252             : 
     253             : /* acct_info_t is a node in a map_chain that contains the metadata for a
     254             :    single account address's conflict graph DAG.  In particular, it
     255             :    contains the information needed to know where to insert a node that
     256             :    reads from or writes to the account.  The objects of this type follow
     257             :    this state machine:
     258             : 
     259             :                  FREE  -----> ACTIVE ----> CACHED
     260             :                                 ^            |
     261             :                                 |-------------
     262             :    When FREE, it is in free_acct_dlist only.  When ACTIVE, it is in
     263             :    acct_map only.  When CACHED, it is in both free_acct_dlist and
     264             :    cached_acct_map.
     265             : */
     266             : struct acct_info {
     267             :   /* key, next, and prev are the map_chain fields. Used in the ACTIVE
     268             :      and CACHED states.  next and prev set to 0 when in the FREE state.
     269             :      Element 0 is a sentinel and isn't inserted to the free_acct_dlist,
     270             :      so this is unambiguous. */
     271             :   fd_acct_addr_t key;
     272             :   uint next;
     273             :   uint prev;
     274             : 
     275             :   union {
     276             :     struct {
     277             :       /* This is effectively a pointer to the last node in the DAG for
     278             :          this pubkey, one for each staging lane.
     279             :          EDGE_IS_LAST(FOLLOW_EDGE(base, last_reference[i])) is non-zero.
     280             :          */
     281             :       edge_t last_reference[4];
     282             : 
     283             :     }; /* When in the ACTIVE state */
     284             :     struct {
     285             :       uint free_ll_next;
     286             :       uint free_ll_prev;
     287             :       /* 8 bytes of padding here */
     288             :     }; /* When not in the ACTIVE state, used by the free_acct_dlist */
     289             :   };
     290             :   /* flags: a combination of ACCT_INFO_FLAG_* bitfields above.  Used
     291             :      when ACTIVE. */
     292             :   uint flags:8;
     293             : 
     294             : 
     295             :   /* We want to dispatch the READY transactions in an order that
     296             :      maximizes parallelism, but we also want to be able to start
     297             :      dispatching transactions decently well before we have the full
     298             :      conflict graph.  We can do that because we know that contentious
     299             :      accounts tend to stay contentious and uncontentious accounts tend
     300             :      to stay uncontentious.
     301             : 
     302             :      To accomplish this, we maintain a special EMA.  Let x_i be 1 if
     303             :      transaction i references it and 0 if not.  If we squint and assume
     304             :      the transactions are independent and all drawn from some
     305             :      distribution (which is not true, but that's why we squint), an EMA
     306             :      of x_i estimates the probability that the next transaction
     307             :      references this account.  How we use this value is detailed later.
     308             : 
     309             :      We can't update this value for every pubkey for each transaction,
     310             :      so we maintain it in a lazy way, by applying updates only when we
     311             :      need to read the value, which also happens to be every time we want
     312             :      to add a 1 value to the EMA.  We then just need to maintain the
     313             :      last index i at which x_i was updated and the current value.  We
     314             :      only have 24 bits for the index, which means that we can't maintain
     315             :      it in a fork-aware way, which doesn't seem like a problem.  Also,
     316             :      it's possible it can overflow, and that can result in an incorrect
     317             :      value, but that means the account is only referenced ~ 1/2^24
     318             :      transactions, which is also fine.
     319             : 
     320             :      last_ref is in the domain of global_inserted_txn_cnt.  ema_refs is
     321             :      in [0, 1].  Both fields are used in ACTIVE and CACHED.  Maintaining
     322             :      these fields is actually the main reason CACHED exists. */
     323             :   uint   last_ref:24;
     324             :   float  ema_refs;
     325             : };
     326             : typedef struct acct_info acct_info_t;
     327             : 
     328             : FD_STATIC_ASSERT( sizeof(acct_info_t)==64UL, acct_info_t );
     329             : 
     330             : /* For the acct_map and the free_acct_map */
     331             : #define MAP_NAME          acct_map
     332     1759374 : #define MAP_ELE_T         acct_info_t
     333   119613348 : #define MAP_IDX_T         uint
     334             : #define MAP_KEY_T         fd_acct_addr_t
     335   225795531 : #define MAP_KEY_HASH(k,s) fd_hash32( (k)->b, (s) )
     336   112970685 : #define MAP_KEY_EQ(k0,k1) (!memcmp( (k0)->b, (k1)->b, 32UL ))
     337             : #define MAP_OPTIMIZE_RANDOM_ACCESS_REMOVAL 1
     338             : #include "../../util/tmpl/fd_map_chain.c"
     339             : 
     340             : 
     341             : #define DLIST_IDX_T uint
     342    17769498 : #define DLIST_PREV  free_ll_prev
     343    17769498 : #define DLIST_NEXT  free_ll_next
     344             : #define DLIST_NAME  free_dlist
     345             : #define DLIST_ELE_T acct_info_t
     346             : #include "../../util/tmpl/fd_dlist.c"
     347             : 
     348             : 
     349             : struct pending_prq_ele {
     350             :   /* lower score means should be scheduled sooner.  Integer part has a
     351             :      special meaning as explained above. */
     352             :   float score;
     353             : 
     354             :   uint linear_block_number;
     355             :   uint txn_idx;
     356             : 
     357             :   /* It seems like we should be able to get this whole struct in 8
     358             :      bytes, but we're a few bits over.
     359             : 
     360             :      23 bits for txn_idx
     361             :      16 bit for linear_block_number
     362             :      Then the integer part of the score needs to be at least 18 or 19
     363             :      bits, but we can't use a custom floating point number. */
     364             : };
     365             : 
     366             : typedef struct pending_prq_ele pending_prq_ele_t;
     367             : 
     368             : 
     369             : #define PRQ_NAME pending_prq
     370    13532595 : #define PRQ_T    pending_prq_ele_t
     371             : #define PRQ_EXPLICIT_TIMEOUT 0
     372             : /* returns 1 if x is strictly after y */
     373    12067920 : #define PRQ_AFTER(x,y) (__extension__( {                                            \
     374    12067920 :             int cmp0 = (int)(x).linear_block_number - (int)(y).linear_block_number; \
     375    12067920 :             fd_int_if( cmp0!=0, cmp0>0, (x).score>(y).score );                      \
     376    12067920 :             }))
     377             : #include "../../util/tmpl/fd_prq.c"
     378             : 
     379             : /* fd_rdisp_blockinfo_t maintains a little metadata about transactions for each
     380             :    slot.  */
     381             : struct fd_rdisp_blockinfo {
     382             :   FD_RDISP_BLOCK_TAG_T block;
     383             :   uint  linear_block_number;
     384             : 
     385             :   uint  insert_ready:1;
     386             :   uint  schedule_ready:1;
     387             :   uint  staged:1;
     388             :   uint  staging_lane:2; /* ignored if staged==0 */
     389             :   uint  last_insert_was_serializing:1;
     390             : 
     391             :   uint inserted_cnt;
     392             :   uint dispatched_cnt;
     393             :   uint completed_cnt;
     394             :   uint last_serializing;
     395             : 
     396             :   uint map_chain_next;
     397             :   uint ll_next;
     398             :   unstaged_txn_ll_t ll[ 1 ]; /* used only when unstaged */
     399             :   zombie_dlist_t    zombie_list[ 1 ];
     400             : };
     401             : typedef struct fd_rdisp_blockinfo fd_rdisp_blockinfo_t;
     402             : 
     403             : #define POOL_NAME     block_pool
     404         297 : #define POOL_T        fd_rdisp_blockinfo_t
     405             : #define POOL_IDX_T    uint
     406      988818 : #define POOL_NEXT     ll_next
     407             : #define POOL_SENTINEL 1
     408             : #include "../../util/tmpl/fd_pool.c"
     409             : 
     410             : #define MAP_NAME  block_map
     411             : #define MAP_ELE_T fd_rdisp_blockinfo_t
     412             : #define MAP_KEY_T FD_RDISP_BLOCK_TAG_T
     413      395787 : #define MAP_KEY   block
     414     4045788 : #define MAP_NEXT  map_chain_next
     415     5305476 : #define MAP_IDX_T uint
     416             : #include "../../util/tmpl/fd_map_chain.c"
     417             : 
     418             : #define SLIST_NAME  block_slist
     419             : #define SLIST_ELE_T fd_rdisp_blockinfo_t
     420      395760 : #define SLIST_IDX_T uint
     421      791535 : #define SLIST_NEXT  ll_next
     422             : #include "../../util/tmpl/fd_slist.c"
     423             : 
     424             : struct fd_rdisp_unstaged {
     425             :   FD_RDISP_BLOCK_TAG_T block;
     426             : 
     427             :   uint writable_cnt;
     428             :   uint readonly_cnt;
     429             :   fd_acct_addr_t keys[MAX_ACCT_PER_TXN];
     430             : };
     431             : typedef struct fd_rdisp_unstaged fd_rdisp_unstaged_t;
     432             : 
     433             : typedef struct {
     434             :   pending_prq_ele_t * pending;
     435             :   ulong               linear_block_number;
     436             :   block_slist_t       block_ll[1];
     437             :   ulong               inserted_cnt;
     438             :   ulong               dispatched_cnt;
     439             :   ulong               completed_cnt;
     440             : } per_lane_info_t;
     441             : 
     442             : /* We maintain two maps from pubkeys to acct_info_t.  The first one is
     443             :    the main acct_map, just called acct_map.  All pubkeys in this map
     444             :    have >0 references in the one of the staging lane DAGs.  When an
     445             :    account goes to 0 references, it gets removed from main map_chain and
     446             :    moved to free map_chain, called free_acct_map.  The free_acct_map
     447             :    exists to maintain the reference count EMA information lazily.
     448             :    Unless we need the acct_info_t for something in the DAG, we might as
     449             :    well maintain the EMA info.
     450             : 
     451             :    When we start up, all the acct_info_t structs are in the
     452             :    free_acct_dlist.  Whenever something is added to the free_acct_map,
     453             :    it's also added to the tail of the free_acct_dlist.  When we need an
     454             :    acct_info_t that's not in the free_acct_map, we pop the head of the
     455             :    free_acct_dlist.  In general, the free_acct_dlist contains everything
     456             :    in the free_acct_map, potentially plus some elements that have never
     457             :    been used; all acct_info_t objects are in exactly one of the main
     458             :    acct_map and the free_acct_dlist (not free_acct_map).  See
     459             :    acct_info_t for more information about this. */
     460             : 
     461             : struct fd_rdisp {
     462             :   ulong depth;
     463             :   ulong block_depth;
     464             : 
     465             :   ulong global_insert_cnt;
     466             :   ulong unstaged_lblk_num;
     467             : 
     468             :   /* pool: an fd_pool, indexed [0, depth+1), with 0 being a sentinel */
     469             :   fd_rdisp_txn_t       * pool;
     470             :   fd_rdisp_unstaged_t  * unstaged; /* parallel to pool with additional info */
     471             : 
     472             :   block_map_t          * blockmap; /* map chain */
     473             :   fd_rdisp_blockinfo_t * block_pool;
     474             : 
     475             :   int free_lanes; /* a bitmask */
     476             :   per_lane_info_t lanes[4];
     477             : 
     478             :   acct_map_t   * acct_map;
     479             :   acct_map_t   * free_acct_map;
     480             :   /* acct_pool is not an fd_pool, but is just a flat array, since we
     481             :      don't need to acquire and release from it because of the dlist. */
     482             :   acct_info_t  * acct_pool;
     483             :   free_dlist_t   free_acct_dlist[1];
     484             : };
     485             : 
     486             : typedef struct fd_rdisp fd_rdisp_t;
     487             : 
     488             : 
     489             :  #define ACCT_ITER_TO_PTR( iter ) (__extension__( {                                             \
     490             :        ulong __idx = fd_txn_acct_iter_idx( iter );                                              \
     491             :        fd_ptr_if( __idx<fd_txn_account_cnt( txn, FD_TXN_ACCT_CAT_IMM ), accts, alt_adj )+__idx; \
     492             :        }))
     493             : 
     494             : 
     495        3261 : ulong fd_rdisp_align( void ) { return 128UL; }
     496             : 
     497             : ulong
     498             : fd_rdisp_footprint( ulong depth,
     499         291 :                     ulong block_depth ) {
     500         291 :   if( FD_UNLIKELY( (depth>FD_RDISP_MAX_DEPTH)             | (depth<2UL) |
     501         291 :                    (block_depth>FD_RDISP_MAX_BLOCK_DEPTH) | (block_depth<4UL) ) ) return 0UL;
     502             : 
     503         291 :   ulong chain_cnt      = block_map_chain_cnt_est( block_depth );
     504         291 :   ulong acct_depth     = depth*MAX_ACCT_PER_TXN;
     505         291 :   ulong acct_chain_cnt = acct_map_chain_cnt_est( acct_depth );
     506             : 
     507         291 :   ulong l = FD_LAYOUT_INIT;
     508         291 :   l = FD_LAYOUT_APPEND( l, fd_rdisp_align(),             sizeof(fd_rdisp_t)                              );
     509         291 :   l = FD_LAYOUT_APPEND( l, pool_align(),                 pool_footprint              ( depth+1UL       ) ); /* pool       */
     510         291 :   l = FD_LAYOUT_APPEND( l, alignof(fd_rdisp_unstaged_t), sizeof(fd_rdisp_unstaged_t)*( depth+1UL       ) ); /* unstaged   */
     511         291 :   l = FD_LAYOUT_APPEND( l, block_map_align(),            block_map_footprint         ( chain_cnt       ) ); /* blockmap   */
     512         291 :   l = FD_LAYOUT_APPEND( l, block_pool_align(),           block_pool_footprint        ( block_depth+1UL ) ); /* block_pool */
     513         291 :   l = FD_LAYOUT_APPEND( l, pending_prq_align(),          4UL*pending_prq_footprint   ( depth           ) ); /* pending    */
     514         291 :   l = FD_LAYOUT_APPEND( l, acct_map_align(),             acct_map_footprint          ( acct_chain_cnt  ) ); /* acct_map   */
     515         291 :   l = FD_LAYOUT_APPEND( l, acct_map_align(),             acct_map_footprint          ( acct_chain_cnt  ) ); /* free_acct_map */
     516         291 :   l = FD_LAYOUT_APPEND( l, alignof(acct_info_t),         (acct_depth+1UL)*sizeof(acct_info_t)            ); /* acct_pool  */
     517         291 :   return FD_LAYOUT_FINI( l, fd_rdisp_align() );
     518         291 : }
     519             : 
     520             : void *
     521             : fd_rdisp_new( void * mem,
     522             :               ulong  depth,
     523             :               ulong  block_depth,
     524          99 :               ulong  seed ) {
     525          99 :   if( FD_UNLIKELY( (depth>FD_RDISP_MAX_DEPTH)             | (depth<2UL) |
     526          99 :                    (block_depth>FD_RDISP_MAX_BLOCK_DEPTH) | (block_depth<4UL) ) ) return NULL;
     527             : 
     528          99 :   ulong chain_cnt      = block_map_chain_cnt_est( block_depth );
     529          99 :   ulong acct_depth     = depth*MAX_ACCT_PER_TXN;
     530          99 :   ulong acct_chain_cnt = acct_map_chain_cnt_est( acct_depth );
     531             : 
     532          99 :   FD_SCRATCH_ALLOC_INIT( l, mem );
     533          99 :   fd_rdisp_t * disp   = FD_SCRATCH_ALLOC_APPEND( l, fd_rdisp_align(),             sizeof(fd_rdisp_t)                              );
     534          99 :   void  * _pool       = FD_SCRATCH_ALLOC_APPEND( l, pool_align(),                 pool_footprint              ( depth+1UL       ) );
     535          99 :   void  * _unstaged   = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_rdisp_unstaged_t), sizeof(fd_rdisp_unstaged_t)*( depth+1UL       ) );
     536          99 :   void  * _bmap       = FD_SCRATCH_ALLOC_APPEND( l, block_map_align(),            block_map_footprint         ( chain_cnt       ) );
     537          99 :   void  * _bpool      = FD_SCRATCH_ALLOC_APPEND( l, block_pool_align(),           block_pool_footprint        ( block_depth+1UL ) );
     538          99 :   uchar * _pending    = FD_SCRATCH_ALLOC_APPEND( l, pending_prq_align(),          4UL*pending_prq_footprint   ( depth           ) );
     539          99 :   void  * _acct_map   = FD_SCRATCH_ALLOC_APPEND( l, acct_map_align(),             acct_map_footprint          ( acct_chain_cnt  ) );
     540          99 :   void  * _freea_map  = FD_SCRATCH_ALLOC_APPEND( l, acct_map_align(),             acct_map_footprint          ( acct_chain_cnt  ) );
     541          99 :   acct_info_t * apool = FD_SCRATCH_ALLOC_APPEND( l, alignof(acct_info_t),         (acct_depth+1UL)*sizeof(acct_info_t)            );
     542          99 :   FD_SCRATCH_ALLOC_FINI( l, fd_rdisp_align() );
     543             : 
     544          99 :   disp->depth             = depth;
     545          99 :   disp->block_depth       = block_depth;
     546          99 :   disp->global_insert_cnt = 0UL;
     547          99 :   disp->unstaged_lblk_num = 0UL;
     548             : 
     549          99 :   fd_rdisp_txn_t * temp_pool_join = pool_join( pool_new( _pool, depth+1UL ) );
     550      243879 :   for( ulong i=0UL; i<depth+1UL; i++ ) temp_pool_join[ i ].in_degree = IN_DEGREE_FREE;
     551          99 :   pool_leave( temp_pool_join );
     552             : 
     553          99 :   memset( _unstaged, '\0', sizeof(fd_rdisp_unstaged_t)*(depth+1UL) );
     554             : 
     555          99 :   block_map_new ( _bmap,  chain_cnt, seed );
     556          99 :   block_pool_new( _bpool, block_depth+1UL );
     557             : 
     558          99 :   fd_rdisp_blockinfo_t * bpool_temp_join = block_pool_join( _bpool );
     559      197283 :   for( ulong i=0UL; i<block_depth+1UL; i++ ) zombie_dlist_new( bpool_temp_join[ i ].zombie_list );
     560          99 :   block_pool_leave( bpool_temp_join );
     561             : 
     562          99 :   disp->free_lanes = 0xF;
     563         495 :   for( ulong i=0UL; i<4UL; i++ ) {
     564         396 :     pending_prq_new( _pending, depth );
     565         396 :     _pending += pending_prq_footprint( depth );
     566             : 
     567         396 :     disp->lanes[i].linear_block_number = 0UL;
     568         396 :     disp->lanes[i].inserted_cnt        = 0U;
     569         396 :     disp->lanes[i].dispatched_cnt      = 0U;
     570         396 :     disp->lanes[i].completed_cnt       = 0U;
     571         396 :   }
     572             : 
     573          99 :   acct_map_new( _acct_map,  acct_chain_cnt, fd_ulong_hash( seed+1UL ) );
     574          99 :   acct_map_new( _freea_map, acct_chain_cnt, fd_ulong_hash( seed+2UL ) );
     575             : 
     576          99 :   free_dlist_t * temp_join = free_dlist_join( free_dlist_new( disp->free_acct_dlist ) );
     577    15595683 :   for( ulong i=1UL; i<acct_depth+1UL; i++ ) {
     578    15595584 :     apool[ i ].next = apool[ i ].prev = 0U;
     579    15595584 :     free_dlist_idx_push_tail( disp->free_acct_dlist, i, apool );
     580    15595584 :   }
     581          99 :   free_dlist_leave( temp_join );
     582             : 
     583          99 :   return disp;
     584          99 : }
     585             : 
     586             : fd_rdisp_t *
     587          99 : fd_rdisp_join( void * mem ) {
     588          99 :   fd_rdisp_t * disp = (fd_rdisp_t *)mem;
     589             : 
     590          99 :   ulong depth          = disp->depth;
     591          99 :   ulong block_depth    = disp->block_depth;
     592          99 :   ulong chain_cnt      = block_map_chain_cnt_est( block_depth );
     593          99 :   ulong acct_depth     = depth*MAX_ACCT_PER_TXN;
     594          99 :   ulong acct_chain_cnt = acct_map_chain_cnt_est( acct_depth );
     595             : 
     596          99 :   FD_SCRATCH_ALLOC_INIT( l, mem );
     597          99 :   /*                 */ FD_SCRATCH_ALLOC_APPEND( l, fd_rdisp_align(),             sizeof(fd_rdisp_t)                              );
     598          99 :   void  * _pool       = FD_SCRATCH_ALLOC_APPEND( l, pool_align(),                 pool_footprint              ( depth+1UL       ) );
     599          99 :   void  * _unstaged   = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_rdisp_unstaged_t), sizeof(fd_rdisp_unstaged_t)*( depth+1UL       ) );
     600          99 :   void  * _bmap       = FD_SCRATCH_ALLOC_APPEND( l, block_map_align(),            block_map_footprint         ( chain_cnt       ) );
     601          99 :   void  * _bpool      = FD_SCRATCH_ALLOC_APPEND( l, block_pool_align(),           block_pool_footprint        ( block_depth+1UL ) );
     602          99 :   uchar * _pending    = FD_SCRATCH_ALLOC_APPEND( l, pending_prq_align(),          4UL*pending_prq_footprint   ( depth           ) );
     603          99 :   void  * _acct_map   = FD_SCRATCH_ALLOC_APPEND( l, acct_map_align(),             acct_map_footprint          ( acct_chain_cnt  ) );
     604          99 :   void  * _freea_map  = FD_SCRATCH_ALLOC_APPEND( l, acct_map_align(),             acct_map_footprint          ( acct_chain_cnt  ) );
     605          99 :   acct_info_t * apool = FD_SCRATCH_ALLOC_APPEND( l, alignof(acct_info_t),         (acct_depth+1UL)*sizeof(acct_info_t)            );
     606          99 :   FD_SCRATCH_ALLOC_FINI( l, fd_rdisp_align() );
     607             : 
     608          99 :   disp->pool       = pool_join( _pool );
     609          99 :   disp->unstaged   = (fd_rdisp_unstaged_t *)_unstaged;
     610          99 :   disp->blockmap   = block_map_join( _bmap );
     611          99 :   disp->block_pool = block_pool_join( _bpool );
     612             : 
     613      197283 :   for( ulong i=0UL; i<block_depth+1UL; i++ ) zombie_dlist_join( disp->block_pool[ i ].zombie_list );
     614             : 
     615         495 :   for( ulong i=0UL; i<4UL; i++ ) {
     616         396 :     disp->lanes[i].pending = pending_prq_join( _pending );
     617         396 :     _pending += pending_prq_footprint( depth );
     618         396 :   }
     619             : 
     620          99 :   disp->acct_map      = acct_map_join( _acct_map );
     621          99 :   disp->free_acct_map = acct_map_join( _freea_map );
     622          99 :   disp->acct_pool     = apool;
     623          99 :   free_dlist_join( disp->free_acct_dlist );
     624             : 
     625          99 :   return disp;
     626          99 : }
     627             : 
     628             : static inline void
     629             : free_lane( fd_rdisp_t * disp,
     630        2535 :            ulong        staging_lane ) {
     631        2535 :   disp->free_lanes |= 1<<staging_lane;
     632        2535 :   per_lane_info_t * l = disp->lanes+staging_lane;
     633        2535 :   FD_TEST( pending_prq_cnt( l->pending )==0UL );
     634        2535 :   l->linear_block_number = 0UL;
     635        2535 :   l->inserted_cnt   = 0UL;
     636        2535 :   l->dispatched_cnt = 0UL;
     637        2535 :   l->completed_cnt  = 0UL;
     638        2535 :   block_slist_delete( block_slist_leave( l->block_ll ) );
     639        2535 : }
     640             : 
     641             : static inline void
     642             : alloc_lane( fd_rdisp_t * disp,
     643        2550 :             ulong        staging_lane ) {
     644        2550 :   disp->free_lanes &= ~(1<<staging_lane);
     645        2550 :   block_slist_join( block_slist_new( disp->lanes[staging_lane].block_ll ) );
     646        2550 : }
     647             : 
     648             : ulong
     649             : fd_rdisp_suggest_staging_lane( fd_rdisp_t const *   disp,
     650             :                                FD_RDISP_BLOCK_TAG_T parent_block,
     651           0 :                                int                  duplicate ) {
     652             : 
     653             :   /* 1. If it's a duplicate, suggest FD_RDISP_UNSTAGED */
     654           0 :   if( FD_UNLIKELY( duplicate ) ) return FD_RDISP_UNSTAGED;
     655             : 
     656             :   /* 2. If parent is the last block in any existing staging lane, suggest
     657             :         that lane */
     658           0 :   fd_rdisp_blockinfo_t const * block_pool = disp->block_pool;
     659           0 :   fd_rdisp_blockinfo_t const * block = block_map_ele_query_const( disp->blockmap, &parent_block, NULL, block_pool );
     660           0 :   if( FD_LIKELY( block && block->insert_ready && block->staged ) ) return block->staging_lane;
     661             : 
     662             :   /* 3. If there is at least one free lane, suggest a free lane */
     663           0 :   if( FD_LIKELY( disp->free_lanes!=0 ) ) return (ulong)fd_uint_find_lsb( (uint)disp->free_lanes );
     664             : 
     665             :   /* 4. Else, suggest FD_RDISP_UNSTAGED */
     666           0 :   return FD_RDISP_UNSTAGED;
     667           0 : }
     668             : 
     669             : int
     670             : fd_rdisp_add_block( fd_rdisp_t          * disp,
     671             :                    FD_RDISP_BLOCK_TAG_T   new_block,
     672      395787 :                    ulong                  staging_lane ) {
     673      395787 :   fd_rdisp_blockinfo_t * block_pool = disp->block_pool;
     674             : 
     675      395787 :   if( FD_UNLIKELY( !block_pool_free( block_pool )                                                             ) ) return -1;
     676      395787 :   if( FD_UNLIKELY(  ULONG_MAX!=block_map_idx_query_const( disp->blockmap, &new_block, ULONG_MAX, block_pool ) ) ) return -1;
     677      395781 :   fd_rdisp_blockinfo_t * block = block_pool_ele_acquire( block_pool );
     678      395781 :   block->block = new_block;
     679      395781 :   block_map_ele_insert( disp->blockmap, block, block_pool );
     680             : 
     681      395781 :   block->insert_ready = 1;
     682      395781 :   block->staged       = staging_lane!=FD_RDISP_UNSTAGED;
     683      395781 :   block->staging_lane = (uint)(staging_lane & 0x3UL);
     684      395781 :   block->last_insert_was_serializing = 0U;
     685             : 
     686      395781 :   block->inserted_cnt     = 0U;
     687      395781 :   block->dispatched_cnt   = 0U;
     688      395781 :   block->completed_cnt    = 0U;
     689      395781 :   block->last_serializing = 0U;
     690             : 
     691      395781 :   if( FD_UNLIKELY( staging_lane==FD_RDISP_UNSTAGED ) ) {
     692          18 :     block->schedule_ready = 1;
     693          18 :     block->linear_block_number = (uint)disp->unstaged_lblk_num++;
     694          18 :     unstaged_txn_ll_join( unstaged_txn_ll_new( block->ll ) );
     695      395763 :   } else {
     696      395763 :     block->linear_block_number = (uint)disp->lanes[staging_lane].linear_block_number++;
     697      395763 :     block->schedule_ready      = (uint)(1 & (disp->free_lanes >> staging_lane));
     698             : 
     699      395763 :     if( FD_LIKELY( disp->free_lanes & (1<<staging_lane) ) ) alloc_lane( disp, staging_lane );
     700             : 
     701      395763 :     block_slist_t * sl = disp->lanes[staging_lane].block_ll;
     702      395763 :     if( FD_LIKELY( !block_slist_is_empty( sl, block_pool ) ) )  block_slist_ele_peek_tail( sl, block_pool )->insert_ready = 0;
     703      395763 :     block_slist_ele_push_tail( sl, block, block_pool );
     704      395763 :   }
     705      395781 :   return 0;
     706      395787 : }
     707             : 
     708             : 
     709             : 
     710             : int
     711             : fd_rdisp_remove_block( fd_rdisp_t          * disp,
     712      395694 :                        FD_RDISP_BLOCK_TAG_T  block_tag ) {
     713      395694 :   fd_rdisp_blockinfo_t * block_pool = disp->block_pool;
     714             : 
     715      395694 :   fd_rdisp_blockinfo_t * block   = block_map_ele_query( disp->blockmap, &block_tag, NULL, block_pool );
     716      395694 :   if( FD_UNLIKELY( block==NULL ) ) return -1;
     717             : 
     718      395685 :   FD_TEST( block->schedule_ready );
     719      395685 :   FD_TEST( block->completed_cnt==block->inserted_cnt );
     720      395685 :   FD_TEST( zombie_dlist_is_empty( block->zombie_list, disp->pool ) );
     721             : 
     722      395685 :   if( FD_LIKELY( block->staged ) ) {
     723      395676 :     ulong staging_lane = (ulong)block->staging_lane;
     724      395676 :     block_slist_t * sl = disp->lanes[staging_lane].block_ll;
     725             : 
     726      395676 :     FD_TEST( block==block_slist_ele_peek_head( sl, block_pool ) );
     727      395676 :     block_slist_idx_pop_head( sl, block_pool );
     728      395676 :     if( FD_LIKELY( !block_slist_is_empty( sl, block_pool ) ) ) block_slist_ele_peek_head( sl, block_pool )->schedule_ready = 1;
     729        2460 :     else                                                       free_lane( disp, staging_lane );
     730      395676 :   } else {
     731           9 :     unstaged_txn_ll_delete( unstaged_txn_ll_leave( block->ll ) );
     732           9 :   }
     733      395685 :   block_pool_idx_release( block_pool, block_map_idx_remove( disp->blockmap, &block_tag, ULONG_MAX, block_pool ) );
     734             : 
     735      395685 :   return 0;
     736      395685 : }
     737             : 
     738             : 
     739             : int
     740             : fd_rdisp_abandon_block( fd_rdisp_t          * disp,
     741          72 :                         FD_RDISP_BLOCK_TAG_T  block_tag ) {
     742          72 :   fd_rdisp_blockinfo_t * block_pool = disp->block_pool;
     743             : 
     744          72 :   fd_rdisp_blockinfo_t * block   = block_map_ele_query( disp->blockmap, &block_tag, NULL, disp->block_pool );
     745          72 :   if( FD_UNLIKELY( block==NULL ) ) return -1;
     746             : 
     747          69 :   FD_TEST( block->schedule_ready );
     748          69 :   FD_TEST( block->dispatched_cnt==block->completed_cnt ); /* TODO: remove this when it can call complete properly */
     749          78 :   while( block->completed_cnt<block->inserted_cnt ) {
     750             :     /* because there is nothing DISPATCHED, there has to be something
     751             :        READY */
     752           9 :     ulong txn = fd_rdisp_get_next_ready( disp, block_tag );
     753           9 :     FD_TEST( txn );
     754           9 :     fd_rdisp_complete_txn( disp, txn, 1 );
     755           9 :   }
     756          69 :   while( !zombie_dlist_is_empty( block->zombie_list, disp->pool ) ) {
     757             :     /* rdisp_complete_txn removes the peeked head */
     758           0 :     fd_rdisp_complete_txn( disp, zombie_dlist_idx_peek_head( block->zombie_list, disp->pool ), 1 );
     759           0 :   }
     760             : 
     761          69 :   if( FD_LIKELY( block->staged ) ) {
     762          69 :     ulong staging_lane = (ulong)block->staging_lane;
     763          69 :     block_slist_t * sl = disp->lanes[staging_lane].block_ll;
     764             : 
     765          69 :     FD_TEST( block==block_slist_ele_peek_head( sl, block_pool ) );
     766          69 :     block_slist_idx_pop_head( sl, block_pool );
     767          69 :     if( FD_LIKELY( !block_slist_is_empty( sl, block_pool ) ) ) block_slist_ele_peek_head( sl, block_pool )->schedule_ready = 1;
     768          60 :     else                                                       free_lane( disp, staging_lane );
     769          69 :   } else {
     770           0 :     unstaged_txn_ll_delete( unstaged_txn_ll_leave( block->ll ) );
     771           0 :   }
     772          69 :   block_pool_idx_release( disp->block_pool, block_map_idx_remove( disp->blockmap, &block_tag, ULONG_MAX, disp->block_pool ) );
     773             : 
     774          69 :   return 0;
     775          69 : }
     776             : 
     777             : static void
     778             : add_edges( fd_rdisp_t           * disp,
     779             :            fd_rdisp_txn_t       * ele,
     780             :            fd_acct_addr_t const * addr,
     781             :            ulong                  addr_cnt,
     782             :            uint                   staging_lane,
     783             :            int                    writable,
     784             :            int                    update_score );
     785             : /* updates in_degree, edge_cnt_etc */
     786             : 
     787             : int
     788             : fd_rdisp_promote_block( fd_rdisp_t *          disp,
     789             :                         FD_RDISP_BLOCK_TAG_T  block_tag,
     790          12 :                         ulong                 staging_lane ) {
     791          12 :   fd_rdisp_blockinfo_t * block_pool = disp->block_pool;
     792          12 :   per_lane_info_t * lane = disp->lanes + staging_lane;
     793          12 :   block_slist_t * sl = lane->block_ll;
     794             : 
     795          12 :   fd_rdisp_blockinfo_t * block   = block_map_ele_query( disp->blockmap, &block_tag, NULL, block_pool );
     796          12 :   if( FD_UNLIKELY( block==NULL   ) ) return -1;
     797          12 :   if( FD_UNLIKELY( block->staged ) ) return -1;
     798             : 
     799          12 :   block->staged = 1;
     800          12 :   block->staging_lane = (uint)(staging_lane & 0x3);
     801          12 :   block->insert_ready = 1;
     802          12 :   block->schedule_ready = (uint)(1 & (disp->free_lanes >> staging_lane));
     803             : 
     804          12 :   if( FD_LIKELY( disp->free_lanes & (1<<staging_lane) ) ) alloc_lane( disp, staging_lane );
     805             : 
     806          12 :   if( FD_LIKELY( !block_slist_is_empty( sl, block_pool ) ) )  block_slist_ele_peek_tail( sl, block_pool )->insert_ready = 0;
     807          12 :   block_slist_ele_push_tail( sl, block, block_pool );
     808          12 :   uint linear_block_number = (uint)lane->linear_block_number++;
     809          12 :   block->linear_block_number = linear_block_number;
     810             : 
     811          12 :   unstaged_txn_ll_iter_t next;
     812          12 :   for( unstaged_txn_ll_iter_t iter = unstaged_txn_ll_iter_init( block->ll, disp->pool );
     813         330 :       !unstaged_txn_ll_iter_done( iter, block->ll, disp->pool );
     814         318 :       iter = next ) {
     815         318 :       next = unstaged_txn_ll_iter_next( iter, block->ll, disp->pool );
     816             : 
     817         318 :     fd_rdisp_txn_t      * ele = unstaged_txn_ll_iter_ele( iter, block->ll, disp->pool );
     818         318 :     fd_rdisp_unstaged_t * uns = disp->unstaged + unstaged_txn_ll_iter_idx( iter, block->ll, disp->pool );
     819         318 :     FD_TEST( ele->in_degree==IN_DEGREE_UNSTAGED );
     820             : 
     821         318 :     ele->in_degree    = 0U;
     822         318 :     ele->edge_cnt_etc = (uint)(-1); /* set w_cnt_1 to -1 */
     823             : 
     824         318 :     add_edges( disp, ele, uns->keys,                   uns->writable_cnt, (uint)staging_lane, 1, 0 );
     825         318 :     add_edges( disp, ele, uns->keys+uns->writable_cnt, uns->readonly_cnt, (uint)staging_lane, 0, 0 );
     826             : 
     827         318 :     ele->edge_cnt_etc &= 0x3FFFU;
     828         318 :     ele->edge_cnt_etc |= (uint)staging_lane<<14;
     829         318 :     ele->edge_cnt_etc |= linear_block_number<<16;
     830             : 
     831         318 :     if( FD_UNLIKELY( ele->in_degree==0U ) ) {
     832           6 :       pending_prq_ele_t temp[1] = {{ .score = ele->score, .linear_block_number = linear_block_number, .txn_idx = (uint)(ele-disp->pool)}};
     833           6 :       pending_prq_insert( lane->pending, temp );
     834           6 :     }
     835         318 :   }
     836          12 :   unstaged_txn_ll_delete( unstaged_txn_ll_leave( block->ll ) );
     837             : 
     838          12 :   lane->inserted_cnt   += block->inserted_cnt;
     839          12 :   lane->dispatched_cnt += block->dispatched_cnt;
     840          12 :   lane->completed_cnt  += block->completed_cnt;
     841             : 
     842          12 :   return 0;
     843          12 : }
     844             : 
     845             : int
     846             : fd_rdisp_demote_block( fd_rdisp_t *          disp,
     847          15 :                        FD_RDISP_BLOCK_TAG_T  block_tag ) {
     848          15 :   fd_rdisp_blockinfo_t * block_pool = disp->block_pool;
     849             : 
     850          15 :   fd_rdisp_blockinfo_t * block   = block_map_ele_query( disp->blockmap, &block_tag, NULL, block_pool );
     851          15 :   if( FD_UNLIKELY(  block==NULL           ) ) return -1;
     852          15 :   if( FD_UNLIKELY( !block->staged         ) ) return -1;
     853          15 :   if( FD_UNLIKELY( !block->schedule_ready ) ) return -1;
     854          15 :   if( FD_UNLIKELY(  block->completed_cnt!=block->inserted_cnt ) ) FD_LOG_ERR(( "demote_block called with non-empty block" ));
     855          15 :   ulong staging_lane = block->staging_lane;
     856          15 :   block->staged = 0;
     857             : 
     858          15 :   per_lane_info_t * lane = disp->lanes + staging_lane;
     859          15 :   block_slist_t * sl = lane->block_ll;
     860             : 
     861          15 :   lane->inserted_cnt   -= block->inserted_cnt;
     862          15 :   lane->dispatched_cnt -= block->dispatched_cnt;
     863          15 :   lane->completed_cnt  -= block->completed_cnt;
     864             : 
     865          15 :   block->linear_block_number = (uint)disp->unstaged_lblk_num++;
     866             : 
     867             :   /* staged and schedule_ready means it must be the head of the staging lane */
     868          15 :   FD_TEST( block_slist_ele_peek_head( sl, block_pool )==block );
     869          15 :   block_slist_idx_pop_head( sl, block_pool );
     870             : 
     871          15 :   unstaged_txn_ll_join( unstaged_txn_ll_new( block->ll ) );
     872             : 
     873          15 :   if( FD_LIKELY( !block_slist_is_empty( sl, block_pool ) ) ) block_slist_ele_peek_head( sl, block_pool )->schedule_ready = 1;
     874          15 :   else                                                       free_lane( disp, staging_lane );
     875          15 :   return 0;
     876          15 : }
     877             : 
     878             : int
     879             : fd_rdisp_rekey_block( fd_rdisp_t *           disp,
     880             :                       FD_RDISP_BLOCK_TAG_T   new_tag,
     881           6 :                       FD_RDISP_BLOCK_TAG_T   old_tag ) {
     882           6 :   fd_rdisp_blockinfo_t * block_pool = disp->block_pool;
     883             : 
     884           6 :   if( FD_UNLIKELY(        NULL!= block_map_ele_query_const( disp->blockmap, &new_tag, NULL, block_pool ) ) ) return -1;
     885           6 :   fd_rdisp_blockinfo_t * block = block_map_ele_query      ( disp->blockmap, &old_tag, NULL, block_pool );
     886           6 :   if( FD_UNLIKELY(        NULL== block ) )                                                                   return -1;
     887             : 
     888           6 :   block_map_idx_remove( disp->blockmap, &old_tag, ULONG_MAX, block_pool );
     889           6 :   block->block = new_tag;
     890           6 :   block_map_ele_insert( disp->blockmap, block, block_pool );
     891           6 :   return 0;
     892           6 : }
     893             : 
     894             : 
     895             : /* "Registers" a reference to the account in info at transaction
     896             :    global_insert_cnt.  Returns the value of the EMA, which is an
     897             :    estimate of the probability that the next transaction also references
     898             :    the account.  This value does not matter for correctness, other than
     899             :    that it is in [0, 1), which is why floating point arithmetic is
     900             :    acceptable here. */
     901             : static inline float
     902             : update_ema( acct_info_t * info,
     903     2940699 :             ulong         global_insert_cnt ) {
     904             : #if FD_RDISP_DISABLE_EMA
     905             :   (void)info;
     906             :   (void)global_insert_cnt;
     907             :   return 0.0f;
     908             : #else
     909     5881398 : #define ALPHA 0.005f
     910             :   /* The normal EMA update equation is
     911             :                 e_i = (alpha) * x_i + (1-alpha)*e_{i-1},
     912             :      where alpha is a constant in (0,1).  Let L be the last reference of
     913             :      the account, and G be the current global_insert_cnt value.  We know
     914             :      that e_L = ema_refs, and that for i in [L+1, G), x_i = 0.
     915             :      That means
     916             :                e_{G-1} =         (1-alpha)^(G-L-1) * ema_refs
     917             :                e_G     = alpha + (1-alpha)^(G-L)   * ema_refs
     918             :    */
     919             :   /* last_ref only captures the low 24 bits, so we guess its the highest
     920             :      value for them that would still make it less than
     921             :      global_insert_cnt.  Turns out, we can calculate that with just an
     922             :      AND.  We need to make sure that in the rare case it is actually
     923             :      2^24 away, we don't use a delta of 0.  We also want to make sure
     924             :      that even with inexact float math, we never get to 1.0, so we bias
     925             :      the exponent up slightly. */
     926     2940699 :   ulong last_ref = (ulong)info->last_ref;
     927     2940699 :   ulong delta = fd_ulong_max( (global_insert_cnt - last_ref) & 0xFFFFFFUL, 1UL );
     928     2940699 :   float ema_refs = ALPHA + powf( 1.0f-ALPHA, 0.05f+(float)delta ) * info->ema_refs;
     929             : 
     930     2940699 :   info->ema_refs = ema_refs;
     931     2940699 :   info->last_ref = (uint)(global_insert_cnt & 0xFFFFFFUL);
     932     2940699 : #undef ALPHA
     933             : 
     934     2940699 :   return ema_refs;
     935     2940699 : #endif
     936     2940699 : }
     937             : 
     938             : static void
     939             : add_edges( fd_rdisp_t           * disp,
     940             :            fd_rdisp_txn_t       * ele,
     941             :            fd_acct_addr_t const * addrs,
     942             :            ulong                  addr_cnt,
     943             :            uint                   lane,
     944             :            int                    writable,
     945     3315756 :            int                    update_score ) {
     946             :   /* When we first start, the low 14 bits are 0x3FFF, so adding the first
     947             :      non-empty writable batch wraps them to w_cnt-1 with r_cnt==0.
     948             :      Thereafter, the low 7 bits store w_cnt_1 and the next 7 bits store
     949             :      r_cnt.  Both counts fit without overflow because their sum is at
     950             :      most MAX_ACCT_PER_TXN. */
     951             : 
     952     3315756 :   ulong w_cnt =  (ele->edge_cnt_etc + 1U)     & 0x7FU;
     953     3315756 :   ulong r_cnt = ((ele->edge_cnt_etc + 1U)>>7) & 0x7FU;
     954     3315756 :   ulong acct_idx = w_cnt +     r_cnt;
     955     3315756 :   ulong edge_idx = w_cnt + 3UL*r_cnt;
     956             : 
     957     6237885 :   for( ulong i=0UL; i<addr_cnt; i++ ) {
     958     2922129 :     fd_acct_addr_t const * addr = addrs+i;
     959     2922129 :     acct_info_t * ai = NULL;
     960             : 
     961             :     /* Step 1: lookup the pubkey */
     962     2922129 :     ulong idx = acct_map_idx_query( disp->acct_map, addr, ULONG_MAX, disp->acct_pool );
     963     2922129 :     if( FD_UNLIKELY( idx==ULONG_MAX ) ) {
     964     1086957 :       idx = acct_map_idx_query( disp->free_acct_map, addr, ULONG_MAX, disp->acct_pool );
     965     1086957 :       if( FD_UNLIKELY( idx==ULONG_MAX ) ) {
     966             :         /* The acct pool is sized so that the list cannot be empty at this
     967             :            point.  However, the element at the head might be the free
     968             :            map with a different pubkey. */
     969      597963 :         idx = free_dlist_idx_peek_head( disp->free_acct_dlist, disp->acct_pool );
     970      597963 :         ai = disp->acct_pool+idx;
     971             : 
     972             :         /* CACHED -> FREE transition */
     973      597963 :         if( FD_LIKELY( ai->next!=0U ) ) {
     974      183423 :           acct_map_idx_remove_fast( disp->free_acct_map, idx, disp->acct_pool );
     975      183423 :         }
     976             : 
     977             :         /* FREE -> ACTIVE transition */
     978      597963 :         ai->key      = *addr;
     979      597963 :         ai->flags    = 0U;
     980      597963 :         ai->last_ref = 0U;
     981      597963 :         ai->ema_refs = 0.0f;
     982      597963 :       } else {
     983             :         /* CACHED -> ACTIVE transition */
     984      488994 :         ai = disp->acct_pool+idx;
     985      488994 :         ai->flags    = 0U; /* FIXME: unnecessary */
     986      488994 :         acct_map_idx_remove_fast( disp->free_acct_map, idx, disp->acct_pool );
     987      488994 :       }
     988             :       /* In either case, at this point, the element is not in any map
     989             :          but is in free_acct_dlist.  It has the right key. last_ref, and
     990             :          ema_refs are valid. flags is 0. */
     991     1086957 :       free_dlist_idx_remove( disp->free_acct_dlist, idx, disp->acct_pool );
     992     1086957 :       memset( ai->last_reference, '\0', sizeof(ai->last_reference) );
     993     1086957 :       acct_map_idx_insert( disp->acct_map, idx, disp->acct_pool );
     994     1086957 :     }
     995     2922129 :     ai = disp->acct_pool+idx;
     996             :     /* At this point, in all cases, the acct_info is now in the ACTIVE
     997             :        state.  It's in acct_map, not in free_acct_map, and not in
     998             :        free_acct_dlist. */
     999             : 
    1000             :     /* Assume that transactions are drawn randomly from some large
    1001             :        distribution of potential transactions.  We want to estimate the
    1002             :        expected value of the probability that this transaction conflicts
    1003             :        with the next transaction that is sampled.  Two transactions
    1004             :        conflict if they conflict on any account, and in general, they
    1005             :        conflict if they both reference the same account, unless both
    1006             :        this transaction and the next one only read it.  We don't have
    1007             :        read/write info, so the best guess we have is that the next
    1008             :        transaction does the same thing to the account that this one
    1009             :        does.  That means we only really care about the accounts that
    1010             :        this transaction writes to.  Label those a_1, a_2, ..., a_i, and
    1011             :        suppose the probability that the next transaction references a_i
    1012             :        is p_i (determined using the EMA).  Now then, assuming accounts
    1013             :        are independent (which is false, but whatever), then the
    1014             :        probability that the next transaction does not conflict with this
    1015             :        account is:
    1016             :                      (1-p_1) * (1-p_2) * (1 - p_i).
    1017             : 
    1018             :        Since for the treap, a lower value means we'll schedule it
    1019             :        earlier, we'll use the probability of non-conflict as the
    1020             :        fractional part of the score. */
    1021     2922129 :     if( FD_LIKELY( update_score ) ) {
    1022     2902836 :       float score_change = 1.0f - update_ema( ai, disp->global_insert_cnt );
    1023     2902836 :       ele->score *= fd_float_if( writable, score_change, 1.0f );
    1024     2902836 :     }
    1025             : 
    1026             :     /* Step 2: add edge. There are 4 cases depending on whether this is
    1027             :        a writer or not and whether the previous reference was a writer
    1028             :        or not. */
    1029     2922129 :     int      _ignore;
    1030             : 
    1031     2922129 :     edge_t   ref_to_pa = ai->last_reference[ lane ];
    1032     2922129 :     edge_t   ref_to_me = (uint)((ulong)((ele - disp->pool)<<8) | acct_idx);
    1033     2922129 :     edge_t * pa        = FOLLOW_EDGE( disp->pool, ai->last_reference[ lane ], _ignore );
    1034     2922129 :     edge_t * me        = ele->edges + edge_idx;
    1035             : 
    1036             :     /* In the case that this is the first txn in the DAG, pa will point
    1037             :        to edges[0] of the sentinel element, pool[0].  We don't care
    1038             :        about what is stored there, so just set it up as a dummy element
    1039             :        to make the rest of the code work properly in this case too.  If
    1040             :        this is a read, we want me->sibli to ref_to_me in case 4; if this
    1041             :        is a write, we want to set me->sibli to 0 in case 2, but we need
    1042             :        to make sure pb==pa. */
    1043     2922129 :     disp->pool->edges[0] = (1U<<31) | (uint)idx;
    1044     2922129 :     disp->pool->edges[1] = fd_uint_if( writable, 0U, ref_to_me );
    1045     2922129 :     disp->pool->edges[2] = fd_uint_if( writable, 0U, ref_to_me );
    1046             : 
    1047     2922129 :     int flags = ai->flags;
    1048             : 
    1049     2922129 :     if( writable ) { /* also should be known at compile time */
    1050     1386039 :       if( flags & ACCT_INFO_FLAG_LAST_REF_WAS_WRITE( lane ) ) { /* unclear prob */
    1051             :         /* Case 1: w-w. The parent is the special last pointer.  Point
    1052             :            the parent to me, and set me to the last pointer. */
    1053      256695 :         *me = *pa;
    1054      256695 :         *pa = ref_to_me;
    1055     1129344 :       } else {
    1056             :         /* Case 2: r-w. This is the tricky case because there could be
    1057             :            multiple readers.  We need to set all the last readers' child
    1058             :            pointers to me. */
    1059     1129344 :         *me = *pa;
    1060     1129344 :         *pa = ref_to_me;
    1061     1129344 :         edge_t * pb = FOLLOW_EDGE( disp->pool, pa[1], _ignore );
    1062             :         /* Intentionally skip the first in_degree increment, because it
    1063             :            will be done later */
    1064     1212099 :         while( pb!=pa ) {
    1065       82755 :           *pb = ref_to_me;
    1066       82755 :           ele->in_degree++;
    1067       82755 :           pb = FOLLOW_EDGE( disp->pool, pb[1], _ignore );
    1068       82755 :         }
    1069     1129344 :         flags |= ACCT_INFO_FLAG_LAST_REF_WAS_WRITE( lane ) | ACCT_INFO_FLAG_ANY_WRITERS( lane );
    1070     1129344 :       }
    1071     1536090 :     } else {
    1072     1536090 :       if( flags & ACCT_INFO_FLAG_LAST_REF_WAS_WRITE( lane ) ) { /* unclear prob */
    1073             :         /* Case 3: w-r. Similar to case 1, but need to initialize my
    1074             :            next and prev sibling pointers too. */
    1075       92217 :         *me = *pa;
    1076       92217 :         *pa = ref_to_me;
    1077       92217 :         me[1] = ref_to_me; /* next */
    1078       92217 :         me[2] = ref_to_me; /* prev */
    1079       92217 :         flags &= ~(int)ACCT_INFO_FLAG_LAST_REF_WAS_WRITE( lane ); /* clear bit */
    1080     1443873 :       } else {
    1081             :         /* Case 4: r-r. Add myself as a sibling instead of a child */
    1082     1443873 :         *me = *pa;
    1083     1443873 :         FOLLOW_EDGE( disp->pool, pa[1], _ignore )[2] = ref_to_me;  /* prev->next->prev = me   */
    1084     1443873 :         me[1] = pa[1];                                             /* me->next   = prev->next */
    1085     1443873 :         me[2] = ref_to_pa;                                         /* me->prev   = prev       */
    1086     1443873 :         pa[1] = ref_to_me;                                         /* prev->next = me         */
    1087     1443873 :       }
    1088     1536090 :     }
    1089             : 
    1090             :     /* Step 3: Update the final values */
    1091             :     /* In general, we want to increment the in_degree unless this
    1092             :        transaction is the first to reference this account.  The
    1093             :        exception is that if this account has only readers, including
    1094             :        this transaction, we don't want to increment the in_degree
    1095             :        either.  At this point, we can tell if that is the case based on
    1096             :        ANY_WRITERS.  */
    1097     2922129 :     ele->in_degree += (uint)((ai->last_reference[ lane ]!=0U) & !!(flags & ACCT_INFO_FLAG_ANY_WRITERS( lane )));
    1098     2922129 :     ai->last_reference[ lane ] = ref_to_me;
    1099     2922129 :     ai->flags                  = (uchar)flags;
    1100     2922129 :     edge_idx += fd_uint_if( writable, 1U, 3U );
    1101     2922129 :     acct_idx++;
    1102     2922129 :   }
    1103             :   /* Can't overflow by construction, except for the intentional overflow on the first time. */
    1104     3315756 :   ele->edge_cnt_etc += (uint)addr_cnt<<fd_int_if( writable, 0, 7 );
    1105     3315756 : }
    1106             : 
    1107             : 
    1108             : /* should be called with all writable accounts first */
    1109             : static void
    1110             : add_unstaged_edges( fd_rdisp_t * disp,
    1111             :                     fd_rdisp_txn_t       * ele,
    1112             :                     fd_rdisp_unstaged_t  * unstaged,
    1113             :                     fd_acct_addr_t const * addr,
    1114             :                     ulong                  addr_cnt,
    1115             :                     int                    writable,
    1116        3816 :                     int                    update_score ) {
    1117        3816 :   ulong base_idx = unstaged->writable_cnt+unstaged->readonly_cnt;
    1118        3816 :   FD_TEST( !writable || unstaged->readonly_cnt==0U );
    1119       42402 :   for( ulong i=0UL; i<addr_cnt; i++ ) {
    1120       38586 :     unstaged->keys[ base_idx+i ] = addr[i];
    1121       38586 :     if( FD_LIKELY( update_score ) ) {
    1122       38586 :       ulong idx = acct_map_idx_query( disp->acct_map, addr+i, ULONG_MAX, disp->acct_pool );
    1123       38586 :       if( FD_UNLIKELY( idx==ULONG_MAX ) ) idx = acct_map_idx_query( disp->free_acct_map, addr+i, ULONG_MAX, disp->acct_pool );
    1124             :       /* since these are unstaged, we don't bother moving accounts
    1125             :          around */
    1126       38586 :       float score_change = 1.0f;
    1127       38586 :       if( FD_LIKELY( idx!=ULONG_MAX ) ) score_change = 1.0f - update_ema( disp->acct_pool+idx, disp->global_insert_cnt );
    1128       38586 :       ele->score *= fd_float_if( writable & update_score, score_change, 1.0f );
    1129       38586 :     }
    1130       38586 :   }
    1131        3816 :   *(fd_ptr_if( writable, &(unstaged->writable_cnt), &(unstaged->readonly_cnt) ) ) += (uint)addr_cnt;
    1132        3816 : }
    1133             : 
    1134             : ulong
    1135             : fd_rdisp_add_txn( fd_rdisp_t          *  disp,
    1136             :                   FD_RDISP_BLOCK_TAG_T   insert_block,
    1137             :                   fd_txn_t const       * txn,
    1138             :                   uchar const          * payload,
    1139             :                   fd_acct_addr_t const * alts,
    1140      553164 :                   int                    serializing ) {
    1141             : 
    1142      553164 :   fd_rdisp_blockinfo_t * block   = block_map_ele_query( disp->blockmap, &insert_block, NULL, disp->block_pool );
    1143      553164 :   if( FD_UNLIKELY( !block || !block->insert_ready ) ) return 0UL;
    1144      553161 :   if( FD_UNLIKELY( !pool_free( disp->pool       ) ) ) return 0UL;
    1145             : 
    1146      553161 :   ulong idx = pool_idx_acquire( disp->pool );
    1147      553161 :   fd_rdisp_txn_t * rtxn = disp->pool + idx;
    1148      553161 :   if( FD_UNLIKELY( rtxn->in_degree!=IN_DEGREE_FREE ) ) FD_LOG_CRIT(( "pool[%lu].in_degree==%u but free", idx, rtxn->in_degree ));
    1149             : 
    1150      553161 :   fd_acct_addr_t const * imm_addrs = fd_txn_get_acct_addrs( txn, payload );
    1151             : 
    1152      553161 :   if( FD_UNLIKELY( !block->staged ) ) {
    1153         636 :     rtxn->in_degree = IN_DEGREE_UNSTAGED;
    1154         636 :     rtxn->score     = 1.0f;
    1155             : 
    1156         636 :     fd_rdisp_unstaged_t * unstaged = disp->unstaged + idx;
    1157         636 :     unstaged->block = insert_block;
    1158         636 :     unstaged->writable_cnt = 0U;
    1159         636 :     unstaged->readonly_cnt = 0U;
    1160             : 
    1161         636 :     add_unstaged_edges( disp, rtxn, unstaged, imm_addrs,
    1162         636 :                                                         fd_txn_account_cnt( txn, FD_TXN_ACCT_CAT_WRITABLE_SIGNER        ), 1, 1 );
    1163         636 :     add_unstaged_edges( disp, rtxn, unstaged, imm_addrs+fd_txn_account_cnt( txn, FD_TXN_ACCT_CAT_SIGNER ),
    1164         636 :                                                         fd_txn_account_cnt( txn, FD_TXN_ACCT_CAT_WRITABLE_NONSIGNER_IMM ), 1, 1 );
    1165         636 :     if( FD_LIKELY( alts ) )
    1166         636 :       add_unstaged_edges( disp, rtxn, unstaged, alts,
    1167         636 :                                                         fd_txn_account_cnt( txn, FD_TXN_ACCT_CAT_WRITABLE_ALT ),           1, 1 );
    1168         636 :     add_unstaged_edges( disp, rtxn, unstaged, imm_addrs+fd_txn_account_cnt( txn, FD_TXN_ACCT_CAT_WRITABLE_SIGNER ),
    1169         636 :                                                         fd_txn_account_cnt( txn, FD_TXN_ACCT_CAT_READONLY_SIGNER        ), 0, 1 );
    1170         636 :     add_unstaged_edges( disp, rtxn, unstaged, imm_addrs+fd_txn_account_cnt( txn, FD_TXN_ACCT_CAT_SIGNER | FD_TXN_ACCT_CAT_WRITABLE_NONSIGNER_IMM ),
    1171         636 :                                                         fd_txn_account_cnt( txn, FD_TXN_ACCT_CAT_READONLY_NONSIGNER_IMM ), 0, 1 );
    1172         636 :     if( FD_LIKELY( alts ) )
    1173         636 :       add_unstaged_edges( disp, rtxn, unstaged, alts   +fd_txn_account_cnt( txn, FD_TXN_ACCT_CAT_WRITABLE_ALT ),
    1174         636 :                                                         fd_txn_account_cnt( txn, FD_TXN_ACCT_CAT_READONLY_ALT ),           0, 1 );
    1175             : 
    1176         636 :     unstaged_txn_ll_ele_push_tail( block->ll, rtxn, disp->pool );
    1177      552525 :   } else {
    1178      552525 :     uint lane = block->staging_lane;
    1179             : 
    1180      552525 :     rtxn->in_degree    = 0U;
    1181      552525 :     rtxn->score        = 1.0f;
    1182             :     /* There's not a good way to initialize w_cnt_1 to -1, which is what
    1183             :        it should be at this point, but this is close.  We must be sure
    1184             :        to add at least one writer (which we are assured of because we
    1185             :        know the transaction passed fd_txn_parse) to correct this value. */
    1186      552525 :     rtxn->edge_cnt_etc = ((block->linear_block_number<<16) | (lane<<14)) - 1U;
    1187             : 
    1188      552525 :     add_edges( disp, rtxn, imm_addrs,
    1189      552525 :                                      fd_txn_account_cnt( txn, FD_TXN_ACCT_CAT_WRITABLE_SIGNER        ), lane, 1, 1 );
    1190      552525 :     add_edges( disp, rtxn, imm_addrs+fd_txn_account_cnt( txn, FD_TXN_ACCT_CAT_SIGNER ),
    1191      552525 :                                      fd_txn_account_cnt( txn, FD_TXN_ACCT_CAT_WRITABLE_NONSIGNER_IMM ), lane, 1, 1 );
    1192      552525 :     if( FD_LIKELY( alts ) )
    1193      552510 :       add_edges( disp, rtxn, alts,
    1194      552510 :                                      fd_txn_account_cnt( txn, FD_TXN_ACCT_CAT_WRITABLE_ALT ),           lane, 1, 1 );
    1195      552525 :     add_edges( disp, rtxn, imm_addrs+fd_txn_account_cnt( txn, FD_TXN_ACCT_CAT_WRITABLE_SIGNER ),
    1196      552525 :                                      fd_txn_account_cnt( txn, FD_TXN_ACCT_CAT_READONLY_SIGNER        ), lane, 0, 1 );
    1197      552525 :     add_edges( disp, rtxn, imm_addrs+fd_txn_account_cnt( txn, FD_TXN_ACCT_CAT_SIGNER | FD_TXN_ACCT_CAT_WRITABLE_NONSIGNER_IMM ),
    1198      552525 :                                      fd_txn_account_cnt( txn, FD_TXN_ACCT_CAT_READONLY_NONSIGNER_IMM ), lane, 0, 1 );
    1199      552525 :     if( FD_LIKELY( alts ) )
    1200      552510 :       add_edges( disp, rtxn, alts   +fd_txn_account_cnt( txn, FD_TXN_ACCT_CAT_WRITABLE_ALT ),
    1201      552510 :                                      fd_txn_account_cnt( txn, FD_TXN_ACCT_CAT_READONLY_ALT ),           lane, 0, 1 );
    1202      552525 :   }
    1203      553161 :   if( FD_UNLIKELY( rtxn->score>FD_RDISP_MAX_SCORE ) ) rtxn->score=FD_RDISP_MAX_SCORE;
    1204             : 
    1205      553161 :   if( FD_UNLIKELY( serializing | block->last_insert_was_serializing ) ) {
    1206           6 :     block->last_serializing = block->inserted_cnt;
    1207           6 :   }
    1208      553161 :   block->last_insert_was_serializing = (uint)!!serializing;
    1209      553161 :   rtxn->score += (float)block->last_serializing;
    1210             : 
    1211      553161 :   block->inserted_cnt++;
    1212      553161 :   disp->global_insert_cnt++;
    1213             : 
    1214      553161 :   if( FD_LIKELY( (block->staged) & (rtxn->in_degree==0U) ) ) {
    1215      433488 :     pending_prq_ele_t temp[1] = {{ .score = rtxn->score, .linear_block_number = block->linear_block_number, .txn_idx = (uint)idx }};
    1216      433488 :     pending_prq_insert( disp->lanes[ block->staging_lane ].pending, temp );
    1217      433488 :   }
    1218             : 
    1219      553161 :   return idx;
    1220      553161 : }
    1221             : 
    1222             : ulong
    1223             : fd_rdisp_get_next_ready( fd_rdisp_t           * disp,
    1224      724386 :                          FD_RDISP_BLOCK_TAG_T   schedule_block ) {
    1225      724386 :   fd_rdisp_blockinfo_t * block   = block_map_ele_query( disp->blockmap, &schedule_block, NULL, disp->block_pool );
    1226      724386 :   if( FD_UNLIKELY( !block || !block->schedule_ready ) ) return 0UL;
    1227             : 
    1228      724371 :   ulong idx;
    1229      724371 :   if( FD_LIKELY( block->staged ) ) {
    1230      723744 :     ulong staging_lane = block->staging_lane;
    1231      723744 :     per_lane_info_t * l = disp->lanes + staging_lane;
    1232             : 
    1233      723744 :     if( FD_UNLIKELY( !pending_prq_cnt( l->pending )                                ) ) return 0UL;
    1234      552849 :     if( FD_UNLIKELY( l->pending->linear_block_number != block->linear_block_number ) ) return 0UL;
    1235             :     /* e.g. when completed_cnt==0, we can accept any score below 1.0 */
    1236      552843 :     if( FD_UNLIKELY( l->pending->score>=(float)(block->completed_cnt+1U)           ) ) return 0UL;
    1237      552843 :     idx = l->pending->txn_idx;
    1238      552843 :     pending_prq_remove_min( l->pending );
    1239      552843 :     disp->pool[ idx ].in_degree = IN_DEGREE_DISPATCHED;
    1240      552843 :   } else {
    1241         627 :     if( FD_UNLIKELY( block->dispatched_cnt!=block->completed_cnt       ) ) return 0UL;
    1242         324 :     if( FD_UNLIKELY( unstaged_txn_ll_is_empty( block->ll, disp->pool ) ) ) return 0UL;
    1243         318 :     idx = unstaged_txn_ll_idx_peek_head( block->ll, disp->pool );
    1244         318 :     disp->pool[ idx ].in_degree = IN_DEGREE_UNSTAGED_DISPATCHED;
    1245         318 :   }
    1246      553161 :   block->dispatched_cnt++;
    1247             : 
    1248      553161 :   return idx;
    1249      724371 : }
    1250             : 
    1251             : void
    1252             : fd_rdisp_complete_txn( fd_rdisp_t * disp,
    1253             :                        ulong        txn_idx,
    1254      553173 :                        int          reclaim ) {
    1255             : 
    1256      553173 :   fd_rdisp_txn_t * rtxn = disp->pool + txn_idx;
    1257      553173 :   fd_rdisp_blockinfo_t * block = NULL;
    1258             : 
    1259      553173 :   if( FD_UNLIKELY( rtxn->in_degree==IN_DEGREE_UNSTAGED_DISPATCHED ) ) {
    1260             :     /* Unstaged */
    1261         318 :     block = block_map_ele_query( disp->blockmap, &disp->unstaged[ txn_idx ].block, NULL, disp->block_pool );
    1262         318 :     FD_TEST( rtxn==unstaged_txn_ll_ele_peek_head( block->ll, disp->pool ) );
    1263         318 :     unstaged_txn_ll_ele_pop_head( block->ll, disp->pool );
    1264         318 :     block->completed_cnt++;
    1265      552855 :   } else if( FD_LIKELY( rtxn->in_degree==IN_DEGREE_DISPATCHED ) ) {
    1266             :     /* Staged */
    1267      552843 :     ulong w_cnt_1 = (rtxn->edge_cnt_etc    ) & 0x7FU;
    1268      552843 :     ulong r_cnt   = (rtxn->edge_cnt_etc>> 7) & 0x7FU;
    1269      552843 :     ulong lane    = (rtxn->edge_cnt_etc>>14) & 0x3U;
    1270      552843 :     uint  tail_linear_block_num = (uint)(disp->lanes[lane].linear_block_number);
    1271      552843 :     ulong edge_idx = 0UL;
    1272     3474972 :     for( ulong i=0UL; i<=w_cnt_1+r_cnt; i++ ) {
    1273     2922129 :       edge_t const * e = rtxn->edges+edge_idx;
    1274     2922129 :       edge_t const  e0 = *e;
    1275     2922129 :       edge_t ref_to_me = (uint)((txn_idx<<8) | i);
    1276             : 
    1277             :       /* To help with explanations, consider the following DAG:
    1278             : 
    1279             :            --> B --\   --> E --\
    1280             :           /         V /         V
    1281             :          A           D         (G)
    1282             :           \         ^ \         ^
    1283             :            --> C --/   --> F --/     */
    1284             : 
    1285     2922129 :       if( FD_UNLIKELY( EDGE_IS_LAST( e0 ) ) ) {
    1286     2378718 :         ulong acct_idx = e0 & 0x7FFFFFFFU;
    1287     2378718 :         acct_info_t * ai = disp->acct_pool + acct_idx;
    1288             :         /* If this is a writer, e.g. node G above, we know it's the last
    1289             :            one, so we can clear last_reference.
    1290             :            If this is a reader, e.g. node E and F above, if node G
    1291             :            didn't exist, we need to check if it's the last one
    1292             :            (me==me->next).  If so, we can clear last_reference.  If not,
    1293             :            we need to delete this node from the linked list */
    1294     2378718 :         if( edge_idx<=w_cnt_1 || e[1]==ref_to_me ) {
    1295     1543848 :           ai->last_reference[ lane ] = 0U;
    1296     1543848 :           ai->flags = (uchar)(ai->flags & (~(ACCT_INFO_FLAG_ANY_WRITERS( lane ) | ACCT_INFO_FLAG_LAST_REF_WAS_WRITE( lane ))));
    1297     1543848 :         } else {
    1298      834870 :           int _ignore;
    1299      834870 :           FOLLOW_EDGE( disp->pool, e[1], _ignore )[2] = e[2];  /* me->next->prev = me->prev */
    1300      834870 :           FOLLOW_EDGE( disp->pool, e[2], _ignore )[1] = e[1];  /* me->prev->next = me->next */
    1301      834870 :           ai->last_reference[ lane ]= fd_uint_if( ai->last_reference[ lane ]==ref_to_me, e[1], ai->last_reference[ lane ] );
    1302      834870 :         }
    1303             : 
    1304             :         /* Potentially transition from ACTIVE -> CACHED */
    1305     2378718 :         if( FD_UNLIKELY( (ai->last_reference[ 0 ]==0U)&(ai->last_reference[ 1 ]==0U)&
    1306     2378718 :                          (ai->last_reference[ 2 ]==0U)&(ai->last_reference[ 3 ]==0U) ) ) {
    1307     1086957 :           ai->flags = 0;
    1308     1086957 :           acct_map_idx_remove_fast( disp->acct_map,        acct_idx, disp->acct_pool );
    1309     1086957 :           acct_map_idx_insert     ( disp->free_acct_map,   acct_idx, disp->acct_pool );
    1310     1086957 :           free_dlist_idx_push_tail( disp->free_acct_dlist, acct_idx, disp->acct_pool );
    1311     1086957 :         }
    1312     2378718 :       } else {
    1313      543411 :         int child_is_writer;
    1314             : 
    1315      543411 :         edge_t next_e = e0;
    1316      543411 :         edge_t const * child_edge;
    1317      587814 :         while( 1 ) {
    1318             :           /* This loop first traverses the me->child link, and then
    1319             :              traverses any sibling links.  For example, in the case that
    1320             :              we're completing node D above, the first child_txn is E,
    1321             :              and the second child_txn is F. */
    1322      587814 :           /*            */ child_edge = FOLLOW_EDGE(     disp->pool, next_e, child_is_writer );
    1323      587814 :           fd_rdisp_txn_t * child_txn  = FOLLOW_EDGE_TXN( disp->pool, next_e                  );
    1324             : 
    1325             :           /* Sanity test */
    1326      587814 :           FD_TEST( child_txn->in_degree>0U                   );
    1327      587814 :           FD_TEST( child_txn->in_degree<IN_DEGREE_DISPATCHED );
    1328             : 
    1329      587814 :           if( FD_UNLIKELY( 0U==(--(child_txn->in_degree)) ) ) {
    1330             :             /* We need an operation something like
    1331             :                fd_frag_meta_ts_decomp. child_txn has the low 16 bits,
    1332             :                and tail_linear_block_num has the full 32 bits, except
    1333             :                for tail_linear_block_num refers to a block < block_depth
    1334             :                later.  Since block_depth<2^16, that means we can resolve
    1335             :                this unambiguously.  Basically, we copy the high 16 bits
    1336             :                frorm tail_linear_block_num unless that would make
    1337             :                linear_block_num larger than tail_linear_block_num, in
    1338             :                which case, we subtract 2^16. */
    1339      119349 :             uint low_16_bits = child_txn->edge_cnt_etc>>16;
    1340      119349 :             uint linear_block_num = ((tail_linear_block_num & ~0xFFFFU) | low_16_bits) - (uint)((low_16_bits>(tail_linear_block_num&0xFFFFU))<<16);
    1341      119349 :             pending_prq_ele_t temp[1] = {{ .score               = child_txn->score,
    1342      119349 :                                            .linear_block_number = linear_block_num,
    1343      119349 :                                            .txn_idx             = (uint)(child_txn-disp->pool) }};
    1344      119349 :             pending_prq_insert( disp->lanes[ lane ].pending, temp );
    1345      119349 :           }
    1346      587814 :           if( child_is_writer || child_edge[1]==e0 ) break;
    1347       44403 :           next_e = child_edge[1];
    1348       44403 :         }
    1349             :         /* In the case that the completed transaction is a reader, say B
    1350             :            or C above, it seems like we should need to remove it from
    1351             :            the doubly linked list, but we actually don't.  The times
    1352             :            that we need to read the sibling pointers are:
    1353             :             1. Completing the writer before a reader (e.g. completing A)
    1354             :             2. Completing a reader that's the last in the DAG (e.g.
    1355             :                completing E/F if G didn't exist)
    1356             :             3. Adding another reader to the same set of readers (e.g. if
    1357             :                G were added as a reader instead of a writer)
    1358             :             4. Adding a writer after a set of readers (e.g. adding G).
    1359             : 
    1360             :            Supposing that the completed transaction is a reader, since
    1361             :            we checked EDGE_IS_LAST( e0 ), we know that there is at least
    1362             :            one writer that follows this reader, e.g. D above.
    1363             : 
    1364             :            And so none of these reason can apply to this group of readers:
    1365             :             1. Completing B or C implies that A has already completed,
    1366             :                so it can't complete again.
    1367             :             2. We know that we're not in that case because we checked
    1368             :                EDGE_IS_LAST( e0 ), and if we're not last now, we cannot
    1369             :                become last later, because the growth happens in the
    1370             :                other direction.
    1371             :             3. Similarly, because we checked EDGE_IS_LAST, any future
    1372             :                additions of readers won't be to this group of readers.
    1373             :             4. Similarly, we know that there already is a writer that
    1374             :                follows this group of readers, so a later writer would
    1375             :                not read this set of readers.
    1376             : 
    1377             :            So then, we don't need to deal with the sibling edges.  The
    1378             :            fact that we only need to do it in one case almost calls into
    1379             :            question whether we need to maintain the whole circular
    1380             :            system in the first place, and whether we could get away with
    1381             :            a reference count or something instead, but it's important in
    1382             :            one critical case: suppose we add a bunch of readers, then
    1383             :            some of them complete, and then we add a writer (case 4
    1384             :            above).  We need to be able to enumerate the nodes in the DAG
    1385             :            that have not yet completed, and we need to be able to remove
    1386             :            them from that set in O(1).  Those two requirements don't
    1387             :            leave us with many alternatives besides a doubly linked list. */
    1388             : 
    1389      543411 :         if( FD_UNLIKELY( EDGE_IS_LAST( *child_edge ) ) ) {
    1390             :           /* For example, either:
    1391             :              1. completing D when if G didn't exist
    1392             :              2. completing E or F with G in the DAG
    1393             : 
    1394             :              After completing this transaction, there's either 0 writers
    1395             :              left (case 1) or 1 writer left (case 2) in the DAG. and if
    1396             :              there is one, it is the last reference.
    1397             : 
    1398             :              Either way, we want to set ANY_WRITERS to
    1399             :              LAST_REF_WAS_WRITE. */
    1400      399549 :           acct_info_t * ai = disp->acct_pool + (*child_edge & 0x7FFFFFFFU);
    1401      399549 :           ulong flags = ai->flags;
    1402      399549 :           flags &= ~(ulong)ACCT_INFO_FLAG_ANY_WRITERS( lane );
    1403      399549 :           flags |= (flags & (ulong)ACCT_INFO_FLAG_LAST_REF_WAS_WRITE( lane ))<<1;
    1404      399549 :           ai->flags = (uchar)flags;
    1405      399549 :         }
    1406      543411 :       }
    1407     2922129 :       edge_idx += fd_ulong_if( i<=w_cnt_1, 1UL, 3UL );
    1408     2922129 :     }
    1409      552843 :     block = block_slist_ele_peek_head( disp->lanes[ lane ].block_ll, disp->block_pool );
    1410      552843 :     block->completed_cnt++;
    1411      552843 :   } else if( FD_LIKELY( rtxn->in_degree==IN_DEGREE_ZOMBIE ) ) {
    1412          12 :     FD_TEST( reclaim );
    1413          12 :     block = block_pool_ele( disp->block_pool, rtxn->block_idx );
    1414          12 :     zombie_dlist_ele_remove( block->zombie_list, rtxn, disp->pool );
    1415             :     /* Fall through to the pool release branch below. */
    1416          12 :   } else {
    1417           0 :     FD_LOG_CRIT(( "completed un-dispatched transaction %lu", txn_idx ));
    1418           0 :   }
    1419      553173 :   if( reclaim ) {
    1420             :     /* For testing purposes, to make sure we don't read a completed
    1421             :        transaction, we can clobber the memory. */
    1422             :     /* memset( disp->pool+txn_idx, '\xCC', sizeof(fd_rdisp_txn_t) ); */
    1423      553158 :     rtxn->in_degree = IN_DEGREE_FREE;
    1424      553158 :     pool_idx_release( disp->pool, txn_idx );
    1425      553158 :   } else {
    1426          15 :     rtxn->in_degree = IN_DEGREE_ZOMBIE;
    1427          15 :     zombie_dlist_ele_push_tail( block->zombie_list, rtxn, disp->pool );
    1428          15 :     rtxn->block_idx = (uint)block_pool_idx( disp->block_pool, block );
    1429          15 :   }
    1430      553173 : }
    1431             : 
    1432             : 
    1433             : ulong
    1434             : fd_rdisp_staging_lane_info( fd_rdisp_t           const * disp,
    1435          45 :                             fd_rdisp_staging_lane_info_t out_sched[ static 4 ] ) {
    1436         225 :   for( ulong i=0UL; i<4UL; i++ ) {
    1437         180 :     if( !(disp->free_lanes & (1<<i) ) ) {
    1438          66 :       block_slist_t const * sl = disp->lanes[ i ].block_ll;
    1439          66 :       out_sched[ i ].insert_ready_block   = block_slist_ele_peek_tail( sl, disp->block_pool )->block;
    1440          66 :       out_sched[ i ].schedule_ready_block = block_slist_ele_peek_head( sl, disp->block_pool )->block;
    1441          66 :     }
    1442         180 :   }
    1443          45 :   return 0xFUL & ~(ulong)disp->free_lanes;
    1444          45 : }
    1445             : 
    1446             : void
    1447             : fd_rdisp_verify( fd_rdisp_t const * disp,
    1448      155352 :                  uint             * scratch ) {
    1449      155352 :   ulong acct_depth  = disp->depth*MAX_ACCT_PER_TXN;
    1450      155352 :   ulong block_depth = disp->block_depth;
    1451      155352 :   FD_TEST( 0==acct_map_verify ( disp->acct_map,      acct_depth+1UL,  disp->acct_pool ) );
    1452      155352 :   FD_TEST( 0==acct_map_verify ( disp->free_acct_map, acct_depth+1UL,  disp->acct_pool ) );
    1453      155352 :   FD_TEST( 0==block_map_verify( disp->blockmap,     block_depth+1UL, disp->block_pool ) );
    1454             : 
    1455             :   /* Check all the in degree counts are right */
    1456      155352 :   memset( scratch, '\0', sizeof(uint)*(disp->depth+1UL) );
    1457      155352 :   scratch[ 0 ] = UINT_MAX;
    1458    46753152 :   for( ulong j=1UL; j<disp->depth+1UL; j++ ) {
    1459    46597800 :     fd_rdisp_txn_t const * rtxn = disp->pool+j;
    1460    46597800 :     if( rtxn->in_degree==IN_DEGREE_FREE ) { scratch[ j ]=UINT_MAX; continue; }
    1461             : 
    1462    11532285 :     if( (rtxn->in_degree==IN_DEGREE_UNSTAGED_DISPATCHED) |
    1463    11532285 :         (rtxn->in_degree==IN_DEGREE_UNSTAGED)            |
    1464    11532285 :         (rtxn->in_degree==IN_DEGREE_ZOMBIE) ) continue;
    1465             : 
    1466    11531658 :     ulong w_cnt_1 = (rtxn->edge_cnt_etc    ) & 0x7FU;
    1467    11531658 :     ulong r_cnt   = (rtxn->edge_cnt_etc>> 7) & 0x7FU;
    1468    11531658 :     ulong edge_idx = 0UL;
    1469             : 
    1470   157731339 :     for( ulong i=0UL; i<=w_cnt_1+r_cnt; i++ ) {
    1471   146199681 :       edge_t const * e = rtxn->edges+edge_idx;
    1472   146199681 :       edge_t const  e0 = *e;
    1473             : 
    1474   146199681 :       edge_idx += fd_ulong_if( i<=w_cnt_1, 1UL, 3UL );
    1475             : 
    1476   146199681 :       if( FD_UNLIKELY( EDGE_IS_LAST( e0 ) ) ) continue;
    1477             : 
    1478    29429448 :       edge_t next_e = e0;
    1479    29429448 :       edge_t const * child_edge;
    1480    29429448 :       edge_t last_e = 0U;
    1481    30671499 :       while( 1 ) {
    1482    30671499 :         int child_is_writer;
    1483             :         /* This loop first traverses the me->child link, and then
    1484             :            traverses any sibling links.  For example, in the case that
    1485             :            we're completing node D above, the first child_txn is E,
    1486             :            and the second child_txn is F. */
    1487    30671499 :         /*            */ child_edge = FOLLOW_EDGE(     disp->pool, next_e, child_is_writer );
    1488    30671499 :         fd_rdisp_txn_t * child_txn  = FOLLOW_EDGE_TXN( disp->pool, next_e                  );
    1489    30671499 :         scratch[ child_txn - disp->pool ]++;
    1490    30671499 :         if( child_is_writer || child_edge[1]==e0 ) break;
    1491     1242051 :         if( last_e!=0U ) FD_TEST( child_edge[2]==last_e );
    1492     1242051 :         last_e = next_e;
    1493     1242051 :         next_e = child_edge[1];
    1494     1242051 :         FD_TEST( next_e>=0x100U );
    1495     1242051 :       }
    1496    29429448 :     }
    1497    11531658 :   }
    1498    46753152 :   for( ulong i=1UL; i<disp->depth+1UL; i++ ) {
    1499    46597800 :     FD_TEST( scratch[ i ]==UINT_MAX ||
    1500    46597800 :              disp->pool[ i ].in_degree==IN_DEGREE_DISPATCHED ||
    1501    46597800 :              disp->pool[ i ].in_degree==IN_DEGREE_UNSTAGED ||
    1502    46597800 :              disp->pool[ i ].in_degree==IN_DEGREE_UNSTAGED_DISPATCHED ||
    1503    46597800 :              disp->pool[ i ].in_degree==IN_DEGREE_ZOMBIE ||
    1504    46597800 :              disp->pool[ i ].in_degree==scratch[ i ] );
    1505    46597800 :   }
    1506      155352 : }
    1507             : 
    1508           9 : void * fd_rdisp_leave ( fd_rdisp_t * disp ) { return disp; }
    1509           9 : void * fd_rdisp_delete( void * mem        ) { return  mem; }

Generated by: LCOV version 1.14