Line data Source code
1 : #ifndef HEADER_fd_src_disco_shred_fd_stake_ci_h 2 : #define HEADER_fd_src_disco_shred_fd_stake_ci_h 3 : 4 : /* fd_stake_ci handles the thorny problem of keeping track of leader 5 : schedules and shred destinations, which are epoch specific. Around 6 : epoch boundaries, we may need to query information from the epoch on 7 : either side of the boundary. 8 : 9 : When you make a stake delegation change during epoch N, it becomes 10 : active at the start of the first slot of epoch N+1, but it doesn't 11 : affect the leader schedule or the shred destinations until epoch N+2. 12 : These methods take care all that complexity, so the caller does not 13 : need to do any adjustment. */ 14 : 15 : #include "fd_shred_dest.h" 16 : #include "../../flamenco/leaders/fd_leaders_base.h" 17 : 18 : /* staked+unstaked <= MAX_SHRED_DESTS implies 19 : MAX_SHRED_DEST_FOOTPRINT>=fd_shred_dest_footprint( staked, unstaked ) 20 : This is asserted in the tests. The size of fd_shred_dest_t, varies 21 : based on FD_SHA256_BATCH_FOOTPRINT, which depends on the compiler 22 : settings. */ 23 : #define MAX_SHRED_DEST_FOOTPRINT (8386688UL + sizeof(fd_shred_dest_t)) 24 : 25 : struct fd_per_epoch_info_private { 26 : /* Epoch, and [start_slot, start_slot+slot_cnt) refer to the time 27 : period for which lsched and sdest are valid. I.e. if you're 28 : interested in the leader or computing a shred destination for a 29 : slot s, this struct has the right data when s is in [start_slot, 30 : start_slot+slot_cnt). */ 31 : ulong epoch; 32 : ulong start_slot; 33 : ulong slot_cnt; 34 : 35 : /* Invariant: These are always joined and use the memory below for 36 : their footprint. */ 37 : fd_epoch_leaders_t * lsched; 38 : fd_shred_dest_t * sdest; 39 : 40 : uchar __attribute__((aligned(FD_EPOCH_LEADERS_ALIGN))) _lsched[ FD_EPOCH_LEADERS_FOOTPRINT(MAX_STAKE_WEIGHTS, MAX_SLOTS_PER_EPOCH) ]; 41 : uchar __attribute__((aligned(FD_SHRED_DEST_ALIGN ))) _sdest [ MAX_SHRED_DEST_FOOTPRINT ]; 42 : }; 43 : typedef struct fd_per_epoch_info_private fd_per_epoch_info_t; 44 : 45 : struct fd_stake_ci { 46 : fd_pubkey_t identity_key[ 1 ]; 47 : ulong seed; 48 : 49 : /* scratch and stake_weight are only relevant between stake_msg_init 50 : and stake_msg_fini. shred_dest is only relevant between 51 : dest_add_init and dest_add_fini. */ 52 : struct { 53 : ulong epoch; 54 : ulong start_slot; 55 : ulong slot_cnt; 56 : ulong staked_vote_cnt; 57 : ulong staked_id_cnt; 58 : } scratch[1]; 59 : 60 : fd_vote_stake_weight_t vote_stake_weight[ MAX_STAKE_WEIGHTS ]; 61 : fd_stake_weight_t stake_weight [ MAX_STAKE_WEIGHTS ]; 62 : fd_shred_dest_weighted_t shred_dest [ MAX_SHRED_DESTS ]; 63 : 64 : fd_shred_dest_weighted_t shred_dest_temp[ MAX_SHRED_DESTS ]; 65 : 66 : /* The information to be used for epoch i can be found at 67 : epoch_info[ i%2 ] if it is known. */ 68 : fd_per_epoch_info_t epoch_info[ 2 ]; 69 : }; 70 : typedef struct fd_stake_ci fd_stake_ci_t; 71 : 72 : /* fd_stake_ci_{footprint, align} return the footprint and alignment 73 : required of a region of memory to be used as an fd_stake_ci_t. 74 : fd_stake_ci_t is statically sized, so it can just be declared 75 : outright if needed, but it's pretty large (~30 MB!), so you probably 76 : don't want it on the stack. */ 77 : 78 0 : FD_FN_CONST static inline ulong fd_stake_ci_footprint( void ) { return sizeof (fd_stake_ci_t); } 79 0 : FD_FN_CONST static inline ulong fd_stake_ci_align ( void ) { return alignof(fd_stake_ci_t); } 80 : 81 : /* fd_stake_ci_new formats a piece of memory as a valid stake contact 82 : information store. `identity_key` is a pointer to the public key of 83 : the identity keypair of the local validator. This is used by 84 : fd_shred_dest to know where in the Turbine tree it belongs. 85 : `seed` is the hash seed for an internal pubkey lookup hash map; this 86 : value should be generated using a random number generator. 87 : Does NOT retain a read interest in identity_key after the function 88 : returns. */ 89 : void * fd_stake_ci_new ( void * mem, fd_pubkey_t const * identity_key, ulong seed ); 90 : fd_stake_ci_t * fd_stake_ci_join( void * mem ); 91 : 92 : void * fd_stake_ci_leave ( fd_stake_ci_t * info ); 93 : void * fd_stake_ci_delete( void * mem ); 94 : 95 : /* Frankendancer and Firedancer's Gossip impls follow different regimes 96 : for broadcasting Contact Infos. Firedancer employs an update-based 97 : regime where we receive update/remove messages for individual contact 98 : info entries. Frankendancer (and thusly Agave) performs a full table 99 : broadcast. fd_stake_ci offers two sets of APIs that cater to the 100 : different regimes. */ 101 : 102 : /* Frankendancer only: 103 : fd_stake_ci_stake_msg_{init, fini} are used to handle messages 104 : containing stake weight updates from the Rust side of the splice, and 105 : fd_stake_ci_dest_add_{init, fini} are used to handle messages 106 : containing contact info (potential shred destinations) updates from 107 : the Rust side of the splice. 108 : 109 : These are very specific to the current splices, but rather than parse 110 : the message in the pack and shred tiles, we parse it here. Since 111 : these messages arrive on a dcache and can get overrun, both expose a 112 : init/fini model. 113 : 114 : Upon returning from a call to fd_stake_ci_{stake_msg, dest_add}_init, 115 : the stake contact info object will be in a stake-msg-pending or 116 : dest-add-pending mode, respectively, regardless of what mode it was 117 : in before. In either of these modes, calls to the query functions 118 : (get_*_for slot) are okay and will return the same values they 119 : returned prior to the _init call. 120 : 121 : In order to call fd_stake_ci_{stake_msg, dest_add}_fini, the stake 122 : contact info must be in stake-msg-pending / dest-add-pending mode, 123 : respectively. This means, for example, you cannot call 124 : fd_stake_ci_stake_msg_init followed by fd_stake_ci_dest_add_fini 125 : without an intervening call to fd_stake_ci_dest_add_init. There's no 126 : need to cancel an operation that begun but didn't finish. Calling 127 : init multiple times without calling fini will not leak any resources. 128 : 129 : msg should be a pointer to the first byte of the dcache entry 130 : containing the stakes update. msg will be accessed msg->weights[i] 131 : for i in [0, msg->staked_cnt). msg must contain at least one 132 : staked pubkey, and the pubkeys must be sorted in the usual way (by 133 : stake descending, ties broken by pubkey ascending). 134 : 135 : fd_stake_ci_dest_add_init behaves slightly differently and returns a 136 : pointer to the first element of an array of size MAX_SHRED_DESTS-1 to 137 : be populated. This allows the caller to add augment the information 138 : in the message from Rust with additional information (i.e. mac 139 : addresses). The `cnt` argument to _dest_add_fini specifies the 140 : number of elements of the array returned by _init that were 141 : populated. 0<=cnt<MAX_SHRED_DESTS. _fini will only read the first 142 : `cnt` elements of the array. The stake_lamports field of the input 143 : is ignored. The identity pubkey provided at initialization must not 144 : be one of the cnt values in the array. The caller should not retain 145 : a read or write interest in the pointer returned by _init after fini 146 : has been called, or after the caller has determined that fini will 147 : not be called for that update, e.g. because the update was overrun. 148 : Calls to _fini may clobber the array. 149 : 150 : The list used for leader schedules is always just the staked nodes. 151 : The list used for shred destinations is the staked nodes along with 152 : any unstaked nodes for which we have contact info. If a stake 153 : message doesn't have contact info for a staked node, the previous 154 : contact info will be preserved. If a stake message doesn't have 155 : contact info for an unstaked node, on the other hand, that node will 156 : be deleted from the list. */ 157 : void fd_stake_ci_stake_msg_init( fd_stake_ci_t * info, fd_stake_weight_msg_t const * msg ); 158 : void fd_stake_ci_stake_msg_fini( fd_stake_ci_t * info ); 159 : fd_shred_dest_weighted_t * fd_stake_ci_dest_add_init ( fd_stake_ci_t * info ); 160 : void fd_stake_ci_dest_add_fini ( fd_stake_ci_t * info, ulong cnt ); 161 : 162 : /* Firedancer only: 163 : fd_stake_ci_epoch_msg_{init, fini} are the Firedancer equivalents of 164 : fd_stake_ci_stake_msg_{init, fini}. They take a different input message 165 : structure (fd_epoch_info_msg_t vs fd_stake_weight_msg_t). */ 166 : void fd_stake_ci_epoch_msg_init( fd_stake_ci_t * info, fd_epoch_info_msg_t const * msg ); 167 : void fd_stake_ci_epoch_msg_fini( fd_stake_ci_t * info ); 168 : 169 : /* Firedancer only: 170 : The full client's Gossip update model publishes individual contact 171 : info updates (update/insert or remove), which requires a different 172 : set of dest_ APIs. 173 : 174 : fd_stake_ci_dest_update updates (or adds, if necessary) a shred dest 175 : entry. ip4 is in net order, port is in host order and are both 176 : assumed to be non-zero. */ 177 : 178 : void fd_stake_ci_dest_update( fd_stake_ci_t * info, fd_pubkey_t const * pubkey, uint ip4, ushort port ); 179 : void fd_stake_ci_dest_remove( fd_stake_ci_t * info, fd_pubkey_t const * pubkey ); 180 : 181 : 182 : /* fd_stake_ci_set_identity changes the identity of the locally running 183 : validator at runtime. */ 184 : void fd_stake_ci_set_identity( fd_stake_ci_t * info, 185 : fd_pubkey_t const * identity_key ); 186 : 187 : /* fd_stake_ci_get_{sdest, lsched}_for_slot respectively return a 188 : pointer to the fd_shred_dest_t and fd_epoch_leaders_t containing 189 : information about the specified slot, if it is available. These 190 : functions are the primary query functions for fd_stake_ci. They 191 : return NULL if we don't have information for that slot. 192 : 193 : The fact these take a slot perhaps makes it more clear, but, it's 194 : worth mentioning again there's nothing like the adjustment performed 195 : by Solana's get_leader_schedule_epoch going on here. If you want to 196 : know the leader in slot X, just pass slot X. The returned leader 197 : schedule will not be based on the stake weights active during slot X, 198 : but rather the stake weights offset in time by an appropriate amount 199 : so they apply to slot X. */ 200 : fd_shred_dest_t * fd_stake_ci_get_sdest_for_slot ( fd_stake_ci_t const * info, ulong slot ); 201 : fd_epoch_leaders_t * fd_stake_ci_get_lsched_for_slot( fd_stake_ci_t const * info, ulong slot ); 202 : 203 : #endif /* HEADER_fd_src_disco_shred_fd_stake_ci_h */