LCOV - code coverage report
Current view: top level - choreo/notar - fd_notar.h (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 35 35 100.0 %
Date: 2025-12-07 04:58:33 Functions: 4 20 20.0 %

          Line data    Source code
       1             : #ifndef HEADER_fd_src_choreo_notar_fd_notar_h
       2             : #define HEADER_fd_src_choreo_notar_fd_notar_h
       3             : 
       4             : /* fd_notar ("notarized") processes vote transactions from both TPU and
       5             :    gossip and tracks when blocks become confirmed.  There are three key
       6             :    confirmation thresholds in Solana:
       7             : 
       8             :    - propagation confirmed: a block is propagated if it has received
       9             :      votes from at least 1/3 of stake in the cluster.  This threshold is
      10             :      important in two contexts:
      11             : 
      12             :      1. When becoming leader, we need to check that our "previous"
      13             :         leader block _as of_ the parent slot we're building on, has
      14             :         propagated.  If it's not propagated, we need to instead
      15             :         retransmit our last block that failed to propagate.  "Previous"
      16             :         is quoted, because there is a grace period of one leader
      17             :         rotation for leader blocks to propagate.
      18             : 
      19             :      2. When voting, we need to check our previous leader block _as of_
      20             :         the slot we're voting for has propagated (unless we're voting
      21             :         for one of our own leader blocks).  We cannot vote for slots in
      22             :         which our last leader block failed to propagate.
      23             : 
      24             :    - duplicate confirmed: a block is duplicate confirmed if it has
      25             :      received votes from at least 52% of stake in the cluster.  The
      26             :      "duplicate" adjective is a bit of a misnomer, and a more accurate
      27             :      technical term is equivocation: two (or more) different blocks for
      28             :      the same slot.  This threshold is important for consensus safety,
      29             :      because it ensures Solana eventually converges to the same block
      30             :      per slot.  Specifically fork choice allows choosing a fork if it is
      31             :      duplicate confirmed, even if there is equivocation.
      32             : 
      33             :    - optimistically confirmed: a block is optimistically confirmed if it
      34             :      has received votes from at least 2/3 of stake in the cluster.  This
      35             :      threshold is important for end-users, who rely on the "confirmed"
      36             :      commitment status of blocks (queryable via RPC) to determine that
      37             :      their transaction has landed on a block that will not rollback.
      38             :      This is unimplemented in Firedancer and only relevant for RPC.
      39             :      (TODO verify this?)
      40             : 
      41             :    Unlike duplicate and optimistic confirmation, propagation is at the
      42             :    slot-level rather than block-level.  So two votes for different block
      43             :    ids would count towards the same slot.  This mirrors Agave behavior.
      44             : 
      45             :    On the similarities and differences between fd_ghost vs fd_notar:
      46             : 
      47             :    The reason both fd_ghost and fd_notar exist even though they do
      48             :    seemingly similar things (tracking vote stake on blocks) is because
      49             :    Solana implements the rules quite differently.
      50             : 
      51             :    In fd_ghost, we use the GHOST rule to recursively sum the stake of
      52             :    the subtree (a slot and all its descendants).  The LMD rule counts a
      53             :    validator's stake to at most one fork.  When the validator switches
      54             :    forks, their stake is subtracted from the old fork and added to the
      55             :    new fork.  The tree is then traversed as part of fork choice to find
      56             :    the best leaf ("head").  ghost bases fork choice purely on replay
      57             :    votes, but marks forks valid or invalid with gossip votes.
      58             : 
      59             :    In fd_notar, we count votes towards only the block itself, and not
      60             :    its ancestors.  Also a validator's stake can be counted towards
      61             :    multiple forks at the same time if they vote on a fork then switch to
      62             :    a different one, unlike ghost.  notar uses both replay and gossip
      63             :    votes when counting stake.
      64             : 
      65             :    A note on slots and block ids: vote transactions only contain the
      66             :    block_id of the last vote slot (and do not specify what block_ids
      67             :    previous vote slots correspond to.  Agave assumes if the hash of the
      68             :    last vote slot matches, all the previous slots in the tower match as
      69             :    well.  Agave uses bank hashes instead of block_ids (the relevant code
      70             :    predates block_ids) and maps slots to bank hashes during replay.
      71             : 
      72             :    As a result, there can be multiple block ids for a given slot.  notar
      73             :    tracks the block_id for each slot using fd_tower_forks, and also
      74             :    "duplicate confirmation".  If notar observes a duplicate confirmation
      75             :    for a different block_id than the one it has for a given slot, it
      76             :    updates the block_id for that slot to the duplicate confirmed one. */
      77             : 
      78             : /* FD_NOTAR_PARANOID:  Define this to non-zero at compile time to turn
      79             :    on additional runtime integrity checks. */
      80             : 
      81             : #include "../fd_choreo_base.h"
      82             : #include "../tower/fd_tower_accts.h"
      83             : 
      84             : #ifndef FD_NOTAR_PARANOID
      85             : #define FD_NOTAR_PARANOID 1
      86             : #endif
      87             : 
      88             : #define FD_NOTAR_FLAG_CONFIRMED_PROPAGATED (0)
      89             : #define FD_NOTAR_FLAG_CONFIRMED_DUPLICATE  (1)
      90             : #define FD_NOTAR_FLAG_CONFIRMED_OPTIMISTIC (2)
      91             : 
      92             : #define SET_NAME fd_notar_slot_vtrs
      93             : #define SET_MAX  FD_VOTER_MAX
      94             : #include "../../util/tmpl/fd_set.c"
      95             : 
      96             : struct fd_notar_slot {
      97             :   ulong slot;             /* map key, vote slot */
      98             :   ulong parent_slot;      /* parent slot */
      99             :   ulong prev_leader_slot; /* previous slot in which we were leader */
     100             :   ulong stake;            /* amount of stake that has voted for this slot */
     101             :   int   is_leader;        /* whether this slot was our own leader slot */
     102             :   int   is_propagated;    /* whether this slot has reached 1/3 of stake */
     103             : 
     104             :   fd_hash_t block_ids[FD_VOTER_MAX]; /* one block id per voter per slot */
     105             :   ulong     block_ids_cnt;           /* count of block ids */
     106             : 
     107             :   fd_notar_slot_vtrs_t prev_vtrs[fd_notar_slot_vtrs_word_cnt]; /* who has voted for this slot, prev epoch */
     108             :   fd_notar_slot_vtrs_t vtrs     [fd_notar_slot_vtrs_word_cnt]; /* who has voted for this slot, curr epoch */
     109             : };
     110             : typedef struct fd_notar_slot fd_notar_slot_t;
     111             : 
     112             : struct fd_notar_blk {
     113             :   fd_hash_t block_id;  /* map key */
     114             :   uint      hash;      /* reserved for fd_map_dynamic */
     115             :   ulong     slot;      /* slot associated with this block */
     116             :   ulong     stake;     /* sum of stake that has voted for this block_id */
     117             :   int       dup_conf;  /* whether this block has reached 52% of stake */
     118             :   int       opt_conf;  /* whether this block has reached 2/3 of stake */
     119             :   int       dup_notif; /* set by caller, whether we've notified consumers this is duplicate confirmed */
     120             :   int       opt_notif; /* set by caller, whether we've notified consumers this is optimistically confirmed */
     121             : };
     122             : typedef struct fd_notar_blk fd_notar_blk_t;
     123             : 
     124             : #define MAP_NAME              fd_notar_blk
     125          12 : #define MAP_T                 fd_notar_blk_t
     126      196614 : #define MAP_KEY               block_id
     127           6 : #define MAP_KEY_T             fd_hash_t
     128      196608 : #define MAP_KEY_NULL          hash_null
     129             : #define MAP_KEY_EQUAL_IS_SLOW 1
     130           6 : #define MAP_KEY_INVAL(k)      MAP_KEY_EQUAL((k),MAP_KEY_NULL)
     131           9 : #define MAP_KEY_EQUAL(k0,k1)  (!memcmp( (k0).key, (k1).key, 32UL ))
     132           6 : #define MAP_KEY_HASH(key,s)   ((MAP_HASH_T)( (key).ul[1] ))
     133             : #include "../../util/tmpl/fd_map_dynamic.c"
     134             : 
     135             : /* TODO map key DOS */
     136             : 
     137             : #define MAP_NAME    fd_notar_slot
     138          18 : #define MAP_T       fd_notar_slot_t
     139          60 : #define MAP_KEY     slot
     140             : #define MAP_MEMOIZE 0
     141             : #include "../../util/tmpl/fd_map_dynamic.c"
     142             : 
     143             : struct fd_notar_vtr {
     144             :   fd_pubkey_t vote_acc;     /* map key, vote account address */
     145             :   uint        hash;         /* reserved for fd_map_dynamic */
     146             :   ulong       prev_stake;   /* amount of stake this voter has in epoch - 1 */
     147             :   ulong       stake;        /* amount of stake this voter has in epoch */
     148             :   ulong       prev_bit;     /* bit position in fd_notar_slot_vtrs in epoch - 1 (ULONG_MAX if not set) */
     149             :   ulong       bit;          /* bit position in fd_notar_slot_vtrs in epoch (ULONG_MAX if not set) */
     150             : };
     151             : typedef struct fd_notar_vtr fd_notar_vtr_t;
     152             : 
     153             : #define MAP_NAME              fd_notar_vtr
     154          21 : #define MAP_T                 fd_notar_vtr_t
     155       24591 : #define MAP_KEY               vote_acc
     156          15 : #define MAP_KEY_T             fd_pubkey_t
     157       24576 : #define MAP_KEY_NULL          pubkey_null
     158             : #define MAP_KEY_EQUAL_IS_SLOW 1
     159       49161 : #define MAP_KEY_INVAL(k)      MAP_KEY_EQUAL((k),MAP_KEY_NULL)
     160       49173 : #define MAP_KEY_EQUAL(k0,k1)  (!memcmp( (k0).key, (k1).key, 32UL ))
     161          15 : #define MAP_KEY_HASH(key,s)   ((MAP_HASH_T)( (key).ul[1] ))
     162             : #include "../../util/tmpl/fd_map_dynamic.c"
     163             : 
     164             : struct __attribute__((aligned(128UL))) fd_notar {
     165             :   ulong epoch;    /* highest replayed epoch */
     166             :   ulong lo_wmark; /* notar ignores votes < lo_wmark */
     167             :   ulong hi_wmark; /* notar ignores votes > hi_wmark */
     168             :   ulong slot_max; /* maximum number of slots notar can track */
     169             : 
     170             :   fd_notar_slot_t * slot_map; /* tracks who has voted for a given slot */
     171             :   fd_notar_blk_t *  blk_map;  /* tracks amount of stake for a given block (keyed by block id) */
     172             :   fd_notar_vtr_t *  vtr_map;  /* tracks each voter's stake and prev vote */
     173             : };
     174             : typedef struct fd_notar fd_notar_t;
     175             : 
     176             : /* fd_notar_{align,footprint} return the required alignment and
     177             :    footprint of a memory region suitable for use as a notar.  align
     178             :    returns fd_notar_ALIGN.  footprint returns fd_notar_FOOTPRINT. */
     179             : 
     180             : FD_FN_CONST static inline ulong
     181          33 : fd_notar_align( void ) {
     182          33 :   return alignof(fd_notar_t);
     183          33 : }
     184             : 
     185             : FD_FN_CONST static inline ulong
     186           6 : fd_notar_footprint( ulong slot_max ) {
     187           6 :   int lg_slot_max = fd_ulong_find_msb( fd_ulong_pow2_up( slot_max ) ) + 1;
     188           6 :   int lg_blk_max  = fd_ulong_find_msb( fd_ulong_pow2_up( slot_max * FD_VOTER_MAX ) ) + 1;
     189           6 :   int lg_vtr_max  = fd_ulong_find_msb( fd_ulong_pow2_up( FD_VOTER_MAX ) ) + 1;
     190           6 :   return FD_LAYOUT_FINI(
     191           6 :     FD_LAYOUT_APPEND(
     192           6 :     FD_LAYOUT_APPEND(
     193           6 :     FD_LAYOUT_APPEND(
     194           6 :     FD_LAYOUT_APPEND(
     195           6 :     FD_LAYOUT_INIT,
     196           6 :       alignof(fd_notar_t),   sizeof(fd_notar_t)                     ),
     197           6 :       fd_notar_slot_align(), fd_notar_slot_footprint( lg_slot_max ) ),
     198           6 :       fd_notar_blk_align(),  fd_notar_blk_footprint( lg_blk_max )   ),
     199           6 :       fd_notar_vtr_align(),  fd_notar_vtr_footprint( lg_vtr_max )   ),
     200           6 :     fd_notar_align() );
     201           6 : }
     202             : 
     203             : /* fd_notar_new formats an unused memory region for use as a notar.  mem
     204             :    is a non-NULL pointer to this region in the local address space with
     205             :    the required footprint and alignment. */
     206             : 
     207             : void *
     208             : fd_notar_new( void * shmem,
     209             :               ulong  slot_max );
     210             : 
     211             : /* fd_notar_join joins the caller to the notar.  notar points to the
     212             :    first byte of the memory region backing the notar in the caller's
     213             :    address space.
     214             : 
     215             :    Returns a pointer in the local address space to notar on success. */
     216             : 
     217             : fd_notar_t *
     218             : fd_notar_join( void * notar );
     219             : 
     220             : /* fd_notar_leave leaves a current local join.  Returns a pointer to the
     221             :    underlying shared memory region on success and NULL on failure (logs
     222             :    details).  Reasons for failure include notar is NULL. */
     223             : 
     224             : void *
     225             : fd_notar_leave( fd_notar_t const * notar );
     226             : 
     227             : /* fd_notar_delete unformats a memory region used as a notar.  Assumes
     228             :    only the local process is joined to the region.  Returns a pointer to
     229             :    the underlying shared memory region or NULL if used obviously in
     230             :    error (e.g. notar is obviously not a notar ...  logs details).  The
     231             :    ownership of the memory region is transferred to the caller. */
     232             : 
     233             : void *
     234             : fd_notar_delete( void * notar );
     235             : 
     236             : /* fd_notar_count_vote counts addr's stake towards the voted slots in
     237             :    their tower.  Returns 1 if block_id is duplicate confirmed by this
     238             :    vote, otherwise 0 (useful for the downstream tower tile to implement
     239             :    duplicate confirmation notifications).  addr is the vote account
     240             :    address, stake is the amount of stake associated with the vote
     241             :    account in the current epoch, slot is slot being voted for, block_id
     242             :    is the voter's proposed block id for this vote slot. */
     243             : 
     244             : fd_notar_blk_t *
     245             : fd_notar_count_vote( fd_notar_t *        notar,
     246             :                      ulong               total_stake,
     247             :                      fd_pubkey_t const * addr,
     248             :                      ulong               slot,
     249             :                      fd_hash_t const *   block_id );
     250             : 
     251             : void
     252             : fd_notar_advance_epoch( fd_notar_t *       notar,
     253             :                         fd_tower_accts_t * accts,
     254             :                         ulong              epoch );
     255             : 
     256             : /* fd_notar_publish publishes root as the new notar root slot, removing
     257             :    all blocks with slot numbers < the old notar root slot.  Some slots
     258             :    on minority forks that were pruned but > than the new root may remain
     259             :    but they will eventually be pruned as well as the root advances. */
     260             : 
     261             : void
     262             : fd_notar_advance_wmark( fd_notar_t * notar,
     263             :                         ulong        root );
     264             : 
     265             : #endif /* HEADER_fd_src_choreo_notar_fd_notar_h */

Generated by: LCOV version 1.14