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 : int geo_aware_lsched; 59 : } scratch[1]; 60 : 61 : fd_vote_stake_weight_t vote_stake_weight[ MAX_STAKE_WEIGHTS ]; 62 : fd_stake_weight_t stake_weight [ MAX_STAKE_WEIGHTS ]; 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 : `seed` is the hash seed for an internal pubkey lookup hash map; this 87 : value should be generated using a random number generator. 88 : Does NOT retain a read interest in identity_key after the function 89 : returns. */ 90 : void * fd_stake_ci_new ( void * mem, fd_pubkey_t const * identity_key, ulong seed ); 91 : fd_stake_ci_t * fd_stake_ci_join( void * mem ); 92 : 93 : void * fd_stake_ci_leave ( fd_stake_ci_t * info ); 94 : void * fd_stake_ci_delete( void * mem ); 95 : 96 : /* fd_stake_ci offers two sets of APIs for contact info: an 97 : update-based regime where we receive update/remove messages for 98 : individual contact info entries (used by the shred tile), and a full 99 : table broadcast. */ 100 : 101 : /* fd_stake_ci_stake_msg_{init, fini} are used to handle messages 102 : containing stake weight updates, and fd_stake_ci_dest_add_{init, fini} 103 : are used to handle messages containing full contact info (potential 104 : shred destinations) updates. Since these messages arrive on a dcache 105 : and can get overrun, both expose a init/fini model. 106 : 107 : Upon returning from a call to fd_stake_ci_{stake_msg, dest_add}_init, 108 : the stake contact info object will be in a stake-msg-pending or 109 : dest-add-pending mode, respectively, regardless of what mode it was 110 : in before. In either of these modes, calls to the query functions 111 : (get_*_for slot) are okay and will return the same values they 112 : returned prior to the _init call. 113 : 114 : In order to call fd_stake_ci_{stake_msg, dest_add}_fini, the stake 115 : contact info must be in stake-msg-pending / dest-add-pending mode, 116 : respectively. This means, for example, you cannot call 117 : fd_stake_ci_stake_msg_init followed by fd_stake_ci_dest_add_fini 118 : without an intervening call to fd_stake_ci_dest_add_init. There's no 119 : need to cancel an operation that begun but didn't finish. Calling 120 : init multiple times without calling fini will not leak any resources. 121 : 122 : msg should be a pointer to the first byte of the dcache entry 123 : containing the stakes update. msg will be accessed msg->weights[i] 124 : for i in [0, msg->staked_cnt). msg must contain at least one 125 : staked pubkey, and the pubkeys must be sorted in the usual way (by 126 : stake descending, ties broken by pubkey ascending). 127 : 128 : fd_stake_ci_dest_add_init behaves slightly differently and returns a 129 : pointer to the first element of an array of size MAX_SHRED_DESTS-1 to 130 : be populated. This allows the caller to add augment the information 131 : in the message with additional information (i.e. mac 132 : addresses). The `cnt` argument to _dest_add_fini specifies the 133 : number of elements of the array returned by _init that were 134 : populated. 0<=cnt<MAX_SHRED_DESTS. _fini will only read the first 135 : `cnt` elements of the array. The stake_lamports field of the input 136 : is ignored. The identity pubkey provided at initialization must not 137 : be one of the cnt values in the array. The caller should not retain 138 : a read or write interest in the pointer returned by _init after fini 139 : has been called, or after the caller has determined that fini will 140 : not be called for that update, e.g. because the update was overrun. 141 : Calls to _fini may clobber the array. 142 : 143 : The list used for leader schedules is always just the staked nodes. 144 : The list used for shred destinations is the staked nodes along with 145 : any unstaked nodes for which we have contact info. If a stake 146 : message doesn't have contact info for a staked node, the previous 147 : contact info will be preserved. If a stake message doesn't have 148 : contact info for an unstaked node, on the other hand, that node will 149 : be deleted from the list. */ 150 : void fd_stake_ci_stake_msg_init( fd_stake_ci_t * info, fd_stake_weight_msg_t const * msg ); 151 : void fd_stake_ci_stake_msg_fini( fd_stake_ci_t * info ); 152 : fd_shred_dest_weighted_t * fd_stake_ci_dest_add_init ( fd_stake_ci_t * info ); 153 : void fd_stake_ci_dest_add_fini ( fd_stake_ci_t * info, ulong cnt ); 154 : 155 : /* Firedancer only: 156 : fd_stake_ci_epoch_msg_{init, fini} are the Firedancer equivalents of 157 : fd_stake_ci_stake_msg_{init, fini}. They take a different input message 158 : structure (fd_epoch_info_msg_t vs fd_stake_weight_msg_t). */ 159 : void fd_stake_ci_epoch_msg_init( fd_stake_ci_t * info, fd_epoch_info_msg_t const * msg ); 160 : void fd_stake_ci_epoch_msg_fini( fd_stake_ci_t * info ); 161 : 162 : /* Firedancer only: 163 : The full client's Gossip update model publishes individual contact 164 : info updates (update/insert or remove), which requires a different 165 : set of dest_ APIs. 166 : 167 : fd_stake_ci_dest_update updates (or adds, if necessary) a shred dest 168 : entry. ip4 is in net order, port is in host order and are both 169 : assumed to be non-zero. */ 170 : 171 : void fd_stake_ci_dest_update( fd_stake_ci_t * info, fd_pubkey_t const * pubkey, uint ip4, ushort port ); 172 : void fd_stake_ci_dest_remove( fd_stake_ci_t * info, fd_pubkey_t const * pubkey ); 173 : 174 : 175 : /* fd_stake_ci_set_identity changes the identity of the locally running 176 : validator at runtime. */ 177 : void fd_stake_ci_set_identity( fd_stake_ci_t * info, 178 : fd_pubkey_t const * identity_key ); 179 : 180 : /* fd_stake_ci_get_{sdest, lsched}_for_slot respectively return a 181 : pointer to the fd_shred_dest_t and fd_epoch_leaders_t containing 182 : information about the specified slot, if it is available. These 183 : functions are the primary query functions for fd_stake_ci. They 184 : return NULL if we don't have information for that slot. 185 : 186 : The fact these take a slot perhaps makes it more clear, but, it's 187 : worth mentioning again there's nothing like the adjustment performed 188 : by Solana's get_leader_schedule_epoch going on here. If you want to 189 : know the leader in slot X, just pass slot X. The returned leader 190 : schedule will not be based on the stake weights active during slot X, 191 : but rather the stake weights offset in time by an appropriate amount 192 : so they apply to slot X. */ 193 : fd_shred_dest_t * fd_stake_ci_get_sdest_for_slot ( fd_stake_ci_t const * info, ulong slot ); 194 : fd_epoch_leaders_t * fd_stake_ci_get_lsched_for_slot( fd_stake_ci_t const * info, ulong slot ); 195 : 196 : #endif /* HEADER_fd_src_disco_shred_fd_stake_ci_h */