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