LCOV - code coverage report
Current view: top level - choreo/ghost - fd_ghost.h (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 0 30 0.0 %
Date: 2024-11-13 11:58:15 Functions: 0 52 0.0 %

          Line data    Source code
       1             : #ifndef HEADER_fd_src_choreo_ghost_fd_ghost_h
       2             : #define HEADER_fd_src_choreo_ghost_fd_ghost_h
       3             : 
       4             : /* fd_ghost implements Solana's LMD-GHOST ("latest message-driven greedy
       5             :    heaviest-observed subtree") fork choice rule.
       6             : 
       7             :    Protocol details:
       8             : 
       9             :    - LMD is an acronym for "latest message-driven". It denotes the
      10             :      specific flavor of GHOST implementation, ie. only a
      11             :      validator's latest vote counts.
      12             : 
      13             :    - GHOST is an acronym for "greedy heaviest-observed subtree":
      14             :      - greedy:   pick the locally optimal subtree / fork based on our
      15             :                  current view (which may not be globally optimal).
      16             :      - heaviest: pick based on the highest stake weight.
      17             :      - observed: this is the validator's local view, and other
      18             :                  validators may have differing views.
      19             :      - subtree:  pick a subtree, not an individual node.
      20             : 
      21             :    In-memory representation:
      22             : 
      23             :    - Each tree node is keyed by slot number.
      24             : 
      25             :    - Each tree node tracks the amount of stake (`stake`) that has voted
      26             :      for its slot, as well as the recursive sum of stake for the subtree
      27             :      rooted at that node (`weight`).
      28             : 
      29             :    Link to original GHOST paper: https://eprint.iacr.org/2013/881.pdf.
      30             :    This is simply a reference for those curious about the etymology, and
      31             :    not prerequisite reading for understanding this implementation. */
      32             : 
      33             : #include "../fd_choreo_base.h"
      34             : 
      35             : /* FD_GHOST_USE_HANDHOLDING:  Define this to non-zero at compile time
      36             :    to turn on additional runtime checks and logging. */
      37             : 
      38             : #ifndef FD_GHOST_USE_HANDHOLDING
      39             : #define FD_GHOST_USE_HANDHOLDING 1
      40             : #endif
      41             : 
      42             : /* clang-format off */
      43             : 
      44             : /* fd_ghost_node_t implements a left-child, right-sibling n-ary tree.
      45             :    Each node maintains pointers to its left-most child, its
      46             :    immediate-right sibling, and its parent. */
      47             : 
      48             : typedef struct fd_ghost_node fd_ghost_node_t;
      49             : struct __attribute__((aligned(128UL))) fd_ghost_node {
      50             :   ulong             slot;         /* slot this node is tracking, also the map key */
      51             :   ulong             next;         /* reserved for internal use by fd_pool and fd_map_chain */
      52             :   ulong             weight;       /* amount of stake (in lamports) that has voted for this slot or any of its descendants */
      53             :   ulong             stake;        /* amount of stake (in lamports) that has voted for this slot */
      54             :   ulong             gossip_stake; /* amount of stake (in lamports) that has voted for this slot via gossip (sans replay overlap) */
      55             :   ulong             rooted_stake; /* amount of stake (in lamports) that has rooted this slot */
      56             :   int               eqv;          /* flag for equivocation (multiple blocks) in this slot */
      57             :   fd_ghost_node_t * parent;       /* pointer to the parent */
      58             :   fd_ghost_node_t * child;        /* pointer to the left-most child */
      59             :   fd_ghost_node_t * sibling;      /* pointer to next sibling */
      60             : };
      61             : 
      62             : #define FD_GHOST_EQV_SAFE ( 0.52 )
      63             : #define FD_GHOST_OPT_CONF ( 2.0 / 3.0 )
      64             : 
      65             : #define POOL_NAME fd_ghost_node_pool
      66           0 : #define POOL_T    fd_ghost_node_t
      67             : #include "../../util/tmpl/fd_pool.c"
      68             : 
      69             : #define MAP_NAME               fd_ghost_node_map
      70             : #define MAP_ELE_T              fd_ghost_node_t
      71           0 : #define MAP_KEY                slot
      72             : #include "../../util/tmpl/fd_map_chain.c"
      73             : 
      74             : /* fd_ghost_vote_t represents a validator's vote.  This includes the
      75             :    slot being voted for, the validator's pubkey identity, and the
      76             :    validator's stake. */
      77             : 
      78             : struct fd_ghost_vote {
      79             :   fd_pubkey_t    pubkey; /* validator identity, also the map key */
      80             :   ulong          next;   /* reserved for internal use by fd_pool and fd_map_chain */
      81             :   ulong          slot;   /* latest vote slot */
      82             :   ulong          root;   /* latest root slot */
      83             :   ulong          stake;  /* validator's stake */
      84             : };
      85             : typedef struct fd_ghost_vote fd_ghost_vote_t;
      86             : 
      87             : #define POOL_NAME fd_ghost_vote_pool
      88           0 : #define POOL_T    fd_ghost_vote_t
      89             : #include "../../util/tmpl/fd_pool.c"
      90             : 
      91             : #define MAP_NAME               fd_ghost_vote_map
      92             : #define MAP_ELE_T              fd_ghost_vote_t
      93           0 : #define MAP_KEY                pubkey
      94             : #define MAP_KEY_T              fd_pubkey_t
      95           0 : #define MAP_KEY_EQ(k0,k1)      (!(memcmp((k0)->hash,(k1)->hash,sizeof(fd_hash_t))))
      96           0 : #define MAP_KEY_HASH(key,seed) (key->ui[0]^seed)
      97             : #include "../../util/tmpl/fd_map_chain.c"
      98             : 
      99             : /* fd_ghost_t is the top-level structure that holds the root of the
     100             :    tree, as well as the memory pools and map structures for tracking
     101             :    ghost nodes and votes.
     102             : 
     103             :    These structures are bump-allocated and laid out contiguously in
     104             :    memory from the fd_ghost_t * pointer which points to the beginning of
     105             :    the memory region.
     106             : 
     107             :    ---------------------- <--- fd_ghost_t *
     108             :    | root | total_stake |
     109             :    ----------------------
     110             :    | node_pool          |
     111             :    ----------------------
     112             :    | node_map           |
     113             :    ----------------------
     114             :    | vote_map           |
     115             :    ----------------------
     116             : */
     117             : 
     118             : struct __attribute__((aligned(128UL))) fd_ghost {
     119             : 
     120             :   /* Metadata */
     121             : 
     122             :   fd_ghost_node_t *     root;
     123             :   ulong                 total_stake;
     124             : 
     125             :   /* Inline data structures */
     126             : 
     127             :   fd_ghost_node_t *     node_pool; /* memory pool of ghost nodes */
     128             :   fd_ghost_node_map_t * node_map;  /* map of slot_hash->fd_ghost_node_t */
     129             :   fd_ghost_vote_t *     vote_pool; /* memory pool of ghost votes */
     130             :   fd_ghost_vote_map_t * vote_map;  /* each node's latest vote. map of pubkey->fd_ghost_vote_t */
     131             : };
     132             : typedef struct fd_ghost fd_ghost_t;
     133             : 
     134             : FD_PROTOTYPES_BEGIN
     135             : 
     136             : /* Constructors */
     137             : 
     138             : /* fd_ghost_{align,footprint} return the required alignment and
     139             :    footprint of a memory region suitable for use as ghost with up to
     140             :    node_max nodes and vote_max votes. */
     141             : 
     142             : FD_FN_CONST static inline ulong
     143           0 : fd_ghost_align( void ) {
     144           0 :   return alignof(fd_ghost_t);
     145           0 : }
     146             : 
     147             : FD_FN_CONST static inline ulong
     148           0 : fd_ghost_footprint( ulong node_max, ulong vote_max ) {
     149           0 :   return FD_LAYOUT_FINI(
     150           0 :     FD_LAYOUT_APPEND(
     151           0 :     FD_LAYOUT_APPEND(
     152           0 :     FD_LAYOUT_APPEND(
     153           0 :     FD_LAYOUT_APPEND(
     154           0 :     FD_LAYOUT_APPEND(
     155           0 :     FD_LAYOUT_INIT,
     156           0 :       alignof(fd_ghost_t),        sizeof(fd_ghost_t) ),
     157           0 :       fd_ghost_node_pool_align(), fd_ghost_node_pool_footprint( node_max ) ),
     158           0 :       fd_ghost_node_map_align(),  fd_ghost_node_map_footprint( node_max ) ),
     159           0 :       fd_ghost_vote_pool_align(), fd_ghost_vote_pool_footprint( vote_max ) ),
     160           0 :       fd_ghost_vote_map_align(),  fd_ghost_vote_map_footprint( vote_max ) ),
     161           0 :     fd_ghost_align() );
     162           0 : }
     163             : /* clang-format on */
     164             : 
     165             : /* fd_ghost_new formats an unused memory region for use as a ghost.
     166             :    mem is a non-NULL pointer to this region in the local address space
     167             :    with the required footprint and alignment. */
     168             : 
     169             : void *
     170             : fd_ghost_new( void * mem, ulong node_max, ulong vote_max, ulong seed );
     171             : 
     172             : /* fd_ghost_join joins the caller to the ghost.  ghost points to the
     173             :    first byte of the memory region backing the ghost in the caller's
     174             :    address space.
     175             : 
     176             :    Returns a pointer in the local address space to ghost on success. */
     177             : 
     178             : fd_ghost_t *
     179             : fd_ghost_join( void * ghost );
     180             : 
     181             : /* fd_ghost_leave leaves a current local join.  Returns a pointer to the
     182             :    underlying shared memory region on success and NULL on failure (logs
     183             :    details).  Reasons for failure include ghost is NULL. */
     184             : 
     185             : void *
     186             : fd_ghost_leave( fd_ghost_t const * ghost );
     187             : 
     188             : /* fd_ghost_delete unformats a memory region used as a ghost.
     189             :    Assumes only the nobody is joined to the region.  Returns a
     190             :    pointer to the underlying shared memory region or NULL if used
     191             :    obviously in error (e.g. ghost is obviously not a ghost ... logs
     192             :    details).  The ownership of the memory region is transferred to the
     193             :    caller. */
     194             : 
     195             : void *
     196             : fd_ghost_delete( void * ghost );
     197             : 
     198             : /* fd_ghost_init initializes a ghost.  Assumes ghost is a valid local
     199             :    join and no one else is joined.  root is the initial root ghost will
     200             :    use.  This is the snapshot slot if booting from a snapshot, 0 if the
     201             :    genesis slot.
     202             : 
     203             :    In general, this should be called by the same process that formatted
     204             :    ghost's memory, ie. the caller of fd_ghost_new. */
     205             : 
     206             : void
     207             : fd_ghost_init( fd_ghost_t * ghost, ulong root, ulong total_stake );
     208             : 
     209             : /* Accessors */
     210             : 
     211             : FD_FN_PURE static inline fd_ghost_node_t const *
     212           0 : fd_ghost_query( fd_ghost_t const * ghost, ulong slot ) {
     213           0 :   return fd_ghost_node_map_ele_query_const( ghost->node_map, &slot, NULL, ghost->node_pool );
     214           0 : }
     215             : 
     216             : /* Operations */
     217             : 
     218             : /* fd_ghost_node_insert inserts a new node with slot as the key into the
     219             :    ghost.  Assumes slot >= ghost->smr, slot is not already in ghost,
     220             :    parent_slot is already in ghost, and the node pool has a free element
     221             :    (if handholding is enabled, explicitly checks and errors).  Returns
     222             :    the inserted ghost node. */
     223             : 
     224             : fd_ghost_node_t *
     225             : fd_ghost_insert( fd_ghost_t * ghost, ulong slot, ulong parent_slot );
     226             : 
     227             : /* fd_ghost_replay_vote votes for slot, adding pubkey's stake to the
     228             :    `replay_stake` field for slot and to the `weight` field for both slot
     229             :    and slot's ancestors.  If pubkey has previously voted, pubkey's stake
     230             :    is also subtracted from `weight` for its previous vote slot and its
     231             :    ancestors.
     232             : 
     233             :    Assumes slot is present in ghost (if handholding is enabled,
     234             :    explicitly checks and errors).  Returns the ghost node keyed by slot.
     235             : 
     236             :    TODO the implementation can be made more efficient by
     237             :    short-circuiting and doing fewer traversals.  Currently this is
     238             :    bounded to O(h), where h is the height of ghost. */
     239             : 
     240             : fd_ghost_node_t const *
     241             : fd_ghost_replay_vote( fd_ghost_t * ghost, ulong slot, fd_pubkey_t const * pubkey, ulong stake );
     242             : 
     243             : /* fd_ghost_gossip_vote adds stake amount to the gossip_stake field of
     244             :    slot.
     245             : 
     246             :    Assumes slot is present in ghost (if handholding is enabled,
     247             :    explicitly checks and errors).  Returns the ghost node keyed by slot.
     248             : 
     249             :    Unlike fd_ghost_replay_vote, this stake is not propagated to
     250             :    the weight field for slot and slot's ancestors.  It is only counted
     251             :    towards slot itself, as gossip votes are only used for optimistic
     252             :    confirmation and not fork choice. */
     253             : 
     254             : fd_ghost_node_t const *
     255             : fd_ghost_gossip_vote( fd_ghost_t * ghost, ulong slot, fd_pubkey_t const * pubkey, ulong stake );
     256             : 
     257             : /* fd_ghost_rooted_vote adds stake amount to the rooted_stake field of
     258             :    slot.
     259             : 
     260             :    Assumes slot is present in ghost (if handholding is enabled,
     261             :    explicitly checks and errors).  Returns the ghost node keyed by slot.
     262             : 
     263             :    Note rooting a slot implies rooting its ancestor, but ghost does not
     264             :    explicitly track this. */
     265             : 
     266             : fd_ghost_node_t const *
     267             : fd_ghost_rooted_vote( fd_ghost_t * ghost, ulong slot, fd_pubkey_t const * pubkey, ulong stake );
     268             : 
     269             : /* fd_ghost_publish publishes slot as the new ghost root, setting the
     270             :    subtree beginning from slot as the new ghost tree (ie. slot and all
     271             :    its descendants).  Prunes all nodes not in slot's ancestry.  Assumes
     272             :    slot is present in ghost.  Returns the new root. */
     273             : 
     274             : fd_ghost_node_t const *
     275             : fd_ghost_publish( fd_ghost_t * ghost, ulong slot );
     276             : 
     277             : /* Traversals */
     278             : 
     279             : /* fd_ghost_gca returns the greatest common ancestor of slot1, slot2 in
     280             :    ghost.  Assumes slot1 or slot2 are present in ghost (warns and
     281             :    returns NULL with handholding enabled).  This is guaranteed to be
     282             :    non-NULL if slot1 and slot2 are both present. */
     283             : 
     284             : FD_FN_PURE fd_ghost_node_t const *
     285             : fd_ghost_gca( fd_ghost_t const * ghost, ulong slot1, ulong slot2 );
     286             : 
     287             : /* fd_ghost_head returns ghost's head.  Assumes caller has called
     288             : fd_ghost_init and that the ghost is non-empty, ie. has a root. */
     289             : 
     290             : FD_FN_PURE fd_ghost_node_t const *
     291             : fd_ghost_head( fd_ghost_t const * ghost );
     292             : 
     293             : /* fd_ghost_is_descendant returns 1 if slot descends from ancestor_slot,
     294             :    0 otherwise.  Assumes slot is present in ghost (warns and returns 0
     295             :    early if handholding is on).  Does not assume the same of
     296             :    ancestor_slot. */
     297             : 
     298             : FD_FN_PURE int
     299             : fd_ghost_is_descendant( fd_ghost_t const * ghost, ulong slot, ulong ancestor_slot );
     300             : 
     301             : /* Misc */
     302             : 
     303             : /* fd_ghost_slot_print pretty-prints a formatted ghost tree.  slot
     304             :    controls which slot to begin printing from (will appear as the root
     305             :    in the print output).  depth allows caller to specify additional
     306             :    ancestors to walk back from slot to set as the root.
     307             : 
     308             :    ghost->root->slot and 0 are valid defaults for the above,
     309             :    respectively.  In that case, this would print ghost beginning from
     310             :    the root.  See fd_ghost_print.
     311             : 
     312             :    Typical usage is to pass in the most recently executed slot, in which
     313             :    that slot in a leaf in ghost, and pick an appropriate depth for
     314             :    visualization (20 is a reasonable default). */
     315             : 
     316             : void
     317             : fd_ghost_slot_print( fd_ghost_t * ghost, ulong slot, ulong depth );
     318             : 
     319             : /* fd_ghost_print pretty-prints a formatted ghost tree starting from the
     320             :    root using fd_ghost_slot_print. */
     321             : 
     322             : static inline void
     323           0 : fd_ghost_print( fd_ghost_t * ghost ) {
     324           0 :   fd_ghost_slot_print( ghost, ghost->root->slot, 0 );
     325           0 : }
     326             : 
     327             : FD_PROTOTYPES_END
     328             : 
     329             : #endif /* HEADER_fd_src_choreo_ghost_fd_ghost_h */

Generated by: LCOV version 1.14