LCOV - code coverage report
Current view: top level - choreo/notar - fd_notar.h (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 0 38 0.0 %
Date: 2026-03-19 18:19:27 Functions: 0 48 0.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.
       6             : 
       7             :    Unlike duplicate and optimistic confirmation, propagation is counted
       8             :    at the slot-level rather than block id-level.  So two votes for
       9             :    different block ids would count towards the same slot.  This is
      10             :    intentionally done to mirror Agave behavior.
      11             : 
      12             :    Solana reaches consensus via replay, but can "forward confirm" slots
      13             :    ahead of the replay tip by listening to vote txns from gossip or TPU.
      14             :    The larger max_live_slots (specified in configuration toml), the
      15             :    further ahead slots can be cluster confirmed before they are
      16             :    replayed.
      17             : 
      18             :    On the similarities and differences between fd_ghost / fd_tower vs.
      19             :    fd_notar:
      20             : 
      21             :    The reason both fd_ghost / fd_tower and fd_notar exist even though
      22             :    they do seemingly similar things (tracking vote stake on blocks) is
      23             :    because Solana implements the rules quite differently.
      24             : 
      25             :    At a high-level, fd_ghost / fd_tower is based on vote accounts vs.
      26             :    fd_notar which is based on vote _transactions_.  Everything in
      27             :    fd_ghost / fd_tower is dependent on the vote account's state after
      28             :    the vote txns have been replayed.  So we only have stake information
      29             :    in fd_ghost / fd_tower for a block, if that block has been replayed.
      30             : 
      31             :    On the other hand, fd_notar processes vote transactions as they come
      32             :    in from gossip and TPU, so it does not have this same requirement
      33             :    that the block has been replayed.  This is important, because block
      34             :    transmission is unreliable, and notar provides a fallback mechanism
      35             :    for detecting votes for blocks we don't have.  fd_notar still ingests
      36             :    replay votes as well, so it is guaranteed to be a superset of the
      37             :    votes tracked by fd_ghost / fd_tower, though note this assumption is
      38             :    contingent on feature "Deprecate legacy vote instructions" because
      39             :    notar only counts TowerSync ixs and ignores any other deprecated vote
      40             :    instructions.
      41             : 
      42             :    There are also differences in how votes are counted between the two.
      43             :    In fd_ghost, we use the GHOST rule to recursively sum the stake of
      44             :    the subtree (a slot and all its descendants).  The LMD rule counts a
      45             :    validator's stake to at most one fork.  When the validator switches
      46             :    forks, their stake is subtracted from the old fork and added to the
      47             :    new fork.  The tree is then traversed as part of fork choice to find
      48             :    the best leaf ("head").  ghost bases fork choice purely on replay
      49             :    votes, but marks forks valid or invalid with gossip votes.
      50             : 
      51             :    In fd_notar, we count votes towards only the block itself, and not
      52             :    its ancestors.  Also a validator's stake can be counted towards
      53             :    multiple forks at the same time if they vote on a fork then switch to
      54             :    a different one, unlike ghost.  notar uses both replay and gossip
      55             :    votes when counting stake.
      56             : 
      57             :    A note on slots and block ids: vote transactions only contain the
      58             :    block_id of the last vote slot (and do not specify what block_ids
      59             :    previous vote slots correspond to.  Agave assumes if the hash of the
      60             :    last vote slot matches, all the previous slots in the tower match as
      61             :    well.  Agave uses bank hashes instead of block_ids (the relevant code
      62             :    predates block_ids) and maps slots to bank hashes during replay.
      63             : 
      64             :    As a result, there can be multiple block ids for a given slot.  notar
      65             :    tracks the block_id for each slot using fd_tower_block, and also
      66             :    "duplicate confirmation".  If notar observes a duplicate confirmation
      67             :    for a different block_id than the one it has for a given slot, it
      68             :    updates the block_id for that slot to the duplicate confirmed one. */
      69             : 
      70             : /* FD_NOTAR_PARANOID:  Define this to non-zero at compile time to turn
      71             :    on additional runtime integrity checks. */
      72             : 
      73             : #include "../fd_choreo_base.h"
      74             : #include "../tower/fd_tower_voters.h"
      75             : 
      76             : #ifndef FD_NOTAR_PARANOID
      77             : #define FD_NOTAR_PARANOID 1
      78             : #endif
      79             : 
      80             : #define FD_NOTAR_FLAG_CONFIRMED_PROPAGATED (0)
      81             : #define FD_NOTAR_FLAG_CONFIRMED_DUPLICATE  (1)
      82             : #define FD_NOTAR_FLAG_CONFIRMED_OPTIMISTIC (2)
      83             : 
      84             : #define SET_NAME fd_notar_slot_vtrs
      85           0 : #define SET_MAX  FD_VOTER_MAX
      86             : #include "../../util/tmpl/fd_set.c"
      87             : 
      88             : struct fd_notar_slot {
      89             :   ulong slot;             /* map key, vote slot */
      90             :   ulong parent_slot;      /* parent slot */
      91             :   ulong prev_leader_slot; /* previous slot in which we were leader */
      92             :   ulong stake;            /* amount of stake that has voted for this slot */
      93             :   int   is_leader;        /* whether this slot was our own leader slot */
      94             :   int   is_propagated;    /* whether this slot has reached 1/3 of stake */
      95             : 
      96             :   fd_hash_t block_ids[FD_VOTER_MAX]; /* one block id per voter per slot */
      97             :   ulong     block_ids_cnt;           /* count of block ids */
      98             : 
      99             :   fd_notar_slot_vtrs_t vtrs[fd_notar_slot_vtrs_word_cnt]; /* who has voted for this slot, curr epoch */
     100             : };
     101             : typedef struct fd_notar_slot fd_notar_slot_t;
     102             : 
     103             : struct fd_notar_blk {
     104             :   fd_hash_t block_id;  /* map key */
     105             :   uint      hash;      /* reserved for fd_map_dynamic */
     106             :   ulong     slot;      /* slot associated with this block */
     107             :   ulong     stake;     /* sum of stake that has voted for this block_id */
     108             :   int       level;     /* confirmation level, set by caller */
     109             :   int       fwd_level; /* forward confirmation level, set by caller */
     110             : };
     111             : typedef struct fd_notar_blk fd_notar_blk_t;
     112             : 
     113             : #define MAP_NAME              fd_notar_blk
     114           0 : #define MAP_T                 fd_notar_blk_t
     115           0 : #define MAP_KEY               block_id
     116           0 : #define MAP_KEY_T             fd_hash_t
     117           0 : #define MAP_KEY_NULL          hash_null
     118             : #define MAP_KEY_EQUAL_IS_SLOW 1
     119           0 : #define MAP_KEY_INVAL(k)      MAP_KEY_EQUAL((k),MAP_KEY_NULL)
     120           0 : #define MAP_KEY_EQUAL(k0,k1)  (!memcmp( (k0).key, (k1).key, 32UL ))
     121           0 : #define MAP_KEY_HASH(key,s)   ((MAP_HASH_T)( (key).ul[1] ))
     122             : #include "../../util/tmpl/fd_map_dynamic.c"
     123             : 
     124             : /* TODO map key DOS */
     125             : 
     126             : #define MAP_NAME           fd_notar_slot
     127           0 : #define MAP_T              fd_notar_slot_t
     128           0 : #define MAP_KEY            slot
     129           0 : #define MAP_KEY_NULL       ULONG_MAX
     130           0 : #define MAP_KEY_INVAL(key) ((key)==ULONG_MAX)
     131             : #define MAP_MEMOIZE        0
     132             : #include "../../util/tmpl/fd_map_dynamic.c"
     133             : 
     134             : struct fd_notar_vtr {
     135             :   fd_pubkey_t addr;  /* map key, vote account address */
     136             :   uint        hash;  /* reserved for fd_map_dynamic */
     137             :   ulong       bit;   /* bit position in fd_notar_slot_vtrs in epoch (ULONG_MAX if not set) */
     138             :   ulong       stake; /* amount of stake this voter has in epoch */
     139             : };
     140             : typedef struct fd_notar_vtr fd_notar_vtr_t;
     141             : 
     142             : #define MAP_NAME              fd_notar_vtr
     143           0 : #define MAP_T                 fd_notar_vtr_t
     144           0 : #define MAP_KEY               addr
     145           0 : #define MAP_KEY_T             fd_pubkey_t
     146           0 : #define MAP_KEY_NULL          pubkey_null
     147             : #define MAP_KEY_EQUAL_IS_SLOW 1
     148           0 : #define MAP_KEY_INVAL(k)      MAP_KEY_EQUAL((k),MAP_KEY_NULL)
     149           0 : #define MAP_KEY_EQUAL(k0,k1)  (!memcmp( (k0).key, (k1).key, 32UL ))
     150           0 : #define MAP_KEY_HASH(key,s)   ((MAP_HASH_T)( (key).ul[1] ))
     151             : #include "../../util/tmpl/fd_map_dynamic.c"
     152             : 
     153             : struct __attribute__((aligned(128UL))) fd_notar {
     154             :   ulong             root;     /* current root slot */
     155             :   ulong             slot_max; /* maximum number of slots notar can track */
     156             :   fd_notar_slot_t * slot_map; /* tracks who has voted for a given slot */
     157             :   fd_notar_blk_t *  blk_map;  /* tracks amount of stake for a given block (keyed by block id) */
     158             :   fd_notar_vtr_t *  vtr_map;  /* tracks each voter's stake and prev vote */
     159             : };
     160             : typedef struct fd_notar fd_notar_t;
     161             : 
     162             : /* fd_notar_{align,footprint} return the required alignment and
     163             :    footprint of a memory region suitable for use as a notar.  align
     164             :    returns fd_notar_ALIGN.  footprint returns fd_notar_FOOTPRINT. */
     165             : 
     166             : FD_FN_CONST static inline ulong
     167           0 : fd_notar_align( void ) {
     168           0 :   return alignof(fd_notar_t);
     169           0 : }
     170             : 
     171             : FD_FN_CONST static inline ulong
     172           0 : fd_notar_footprint( ulong slot_max ) {
     173           0 :   int lg_slot_max = fd_ulong_find_msb( fd_ulong_pow2_up( slot_max ) ) + 1;
     174           0 :   int lg_blk_max  = fd_ulong_find_msb( fd_ulong_pow2_up( slot_max * FD_VOTER_MAX ) ) + 1;
     175           0 :   int lg_vtr_max  = fd_ulong_find_msb( fd_ulong_pow2_up( FD_VOTER_MAX ) ) + 1;
     176           0 :   return FD_LAYOUT_FINI(
     177           0 :     FD_LAYOUT_APPEND(
     178           0 :     FD_LAYOUT_APPEND(
     179           0 :     FD_LAYOUT_APPEND(
     180           0 :     FD_LAYOUT_APPEND(
     181           0 :     FD_LAYOUT_INIT,
     182           0 :       alignof(fd_notar_t),   sizeof(fd_notar_t)                     ),
     183           0 :       fd_notar_slot_align(), fd_notar_slot_footprint( lg_slot_max ) ),
     184           0 :       fd_notar_blk_align(),  fd_notar_blk_footprint( lg_blk_max )   ),
     185           0 :       fd_notar_vtr_align(),  fd_notar_vtr_footprint( lg_vtr_max )   ),
     186           0 :     fd_notar_align() );
     187           0 : }
     188             : 
     189             : /* fd_notar_new formats an unused memory region for use as a notar.  mem
     190             :    is a non-NULL pointer to this region in the local address space with
     191             :    the required footprint and alignment. */
     192             : 
     193             : void *
     194             : fd_notar_new( void * shmem,
     195             :               ulong  slot_max );
     196             : 
     197             : /* fd_notar_join joins the caller to the notar.  notar points to the
     198             :    first byte of the memory region backing the notar in the caller's
     199             :    address space.
     200             : 
     201             :    Returns a pointer in the local address space to notar on success. */
     202             : 
     203             : fd_notar_t *
     204             : fd_notar_join( void * notar );
     205             : 
     206             : /* fd_notar_leave leaves a current local join.  Returns a pointer to the
     207             :    underlying shared memory region on success and NULL on failure (logs
     208             :    details).  Reasons for failure include notar is NULL. */
     209             : 
     210             : void *
     211             : fd_notar_leave( fd_notar_t const * notar );
     212             : 
     213             : /* fd_notar_delete unformats a memory region used as a notar.  Assumes
     214             :    only the local process is joined to the region.  Returns a pointer to
     215             :    the underlying shared memory region or NULL if used obviously in
     216             :    error (e.g. notar is obviously not a notar ...  logs details).  The
     217             :    ownership of the memory region is transferred to the caller. */
     218             : 
     219             : void *
     220             : fd_notar_delete( void * notar );
     221             : 
     222             : /* fd_notar_count_vote counts addr's stake towards the voted slots in
     223             :    their tower.  Returns 1 if block_id is duplicate confirmed by this
     224             :    vote, otherwise 0 (useful for the downstream tower tile to implement
     225             :    duplicate confirmation notifications).  addr is the vote account
     226             :    address, stake is the amount of stake associated with the vote
     227             :    account in the current epoch, slot is slot being voted for, block_id
     228             :    is the voter's proposed block id for this vote slot. */
     229             : 
     230             : fd_notar_blk_t *
     231             : fd_notar_count_vote( fd_notar_t *        notar,
     232             :                      fd_pubkey_t const * addr,
     233             :                      ulong               slot,
     234             :                      fd_hash_t const *   block_id );
     235             : 
     236             : /* fd_notar_publish publishes root as the new notar root slot, removing
     237             :    all blocks with slot numbers < the old notar root slot.  Some slots
     238             :    on minority forks that were pruned but > than the new root may remain
     239             :    but they will eventually be pruned as well as the root advances. */
     240             : 
     241             : void
     242             : fd_notar_publish( fd_notar_t * notar,
     243             :                   ulong        root );
     244             : 
     245             : #endif /* HEADER_fd_src_choreo_notar_fd_notar_h */

Generated by: LCOV version 1.14