Line data Source code
1 : #ifndef HEADER_fd_src_app_fdctl_run_tiles_fd_stake_ci_h 2 : #define HEADER_fd_src_app_fdctl_run_tiles_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 241449 : #define MAX_SHRED_DESTS 40200UL 19 : #define MAX_SLOTS_PER_EPOCH 432000UL 20 : /* staked+unstaked <= MAX_SHRED_DESTS implies 21 : MAX_SHRED_DEST_FOOTPRINT>=fd_shred_dest_footprint( staked, unstaked ) 22 : This is asserted in the tests. The size of fd_shred_dest_t, varies 23 : based on FD_SHA256_BATCH_FOOTPRINT, which depends on the compiler 24 : settings. */ 25 : #define MAX_SHRED_DEST_FOOTPRINT (8708224UL + sizeof(fd_shred_dest_t)) 26 : 27 : #define FD_STAKE_CI_STAKE_MSG_SZ (40UL + MAX_SHRED_DESTS * 40UL) 28 : 29 : struct fd_per_epoch_info_private { 30 : /* Epoch, and [start_slot, start_slot+slot_cnt) refer to the time 31 : period for which lsched and sdest are valid. I.e. if you're 32 : interested in the leader or computing a shred destination for a 33 : slot s, this struct has the right data when s is in [start_slot, 34 : start_slot+slot_cnt). */ 35 : ulong epoch; 36 : ulong start_slot; 37 : ulong slot_cnt; 38 : ulong excluded_stake; 39 : 40 : /* Invariant: These are always joined and use the memory below for 41 : their footprint. */ 42 : fd_epoch_leaders_t * lsched; 43 : fd_shred_dest_t * sdest; 44 : 45 : uchar __attribute__((aligned(FD_EPOCH_LEADERS_ALIGN))) _lsched[ FD_EPOCH_LEADERS_FOOTPRINT(MAX_SHRED_DESTS, MAX_SLOTS_PER_EPOCH) ]; 46 : uchar __attribute__((aligned(FD_SHRED_DEST_ALIGN ))) _sdest [ MAX_SHRED_DEST_FOOTPRINT ]; 47 : }; 48 : typedef struct fd_per_epoch_info_private fd_per_epoch_info_t; 49 : 50 : struct fd_stake_ci { 51 : fd_pubkey_t identity_key[ 1 ]; 52 : 53 : /* scratch and stake_weight are only relevant between stake_msg_init 54 : and stake_msg_fini. shred_dest is only relevant between 55 : dest_add_init and dest_add_fini. */ 56 : struct { 57 : ulong epoch; 58 : ulong start_slot; 59 : ulong slot_cnt; 60 : ulong staked_cnt; 61 : ulong excluded_stake; 62 : } scratch[1]; 63 : 64 : fd_stake_weight_t stake_weight [ MAX_SHRED_DESTS ]; 65 : fd_shred_dest_weighted_t shred_dest [ MAX_SHRED_DESTS ]; 66 : 67 : fd_shred_dest_weighted_t shred_dest_temp[ MAX_SHRED_DESTS ]; 68 : 69 : /* The information to be used for epoch i can be found at 70 : epoch_info[ i%2 ] if it is known. */ 71 : fd_per_epoch_info_t epoch_info[ 2 ]; 72 : }; 73 : typedef struct fd_stake_ci fd_stake_ci_t; 74 : 75 : /* fd_stake_ci_{footprint, align} return the footprint and alignment 76 : required of a region of memory to be used as an fd_stake_ci_t. 77 : fd_stake_ci_t is statically sized, so it can just be declared 78 : outright if needed, but it's pretty large (~30 MB!), so you probably 79 : don't want it on the stack. */ 80 : 81 6 : static inline ulong fd_stake_ci_footprint( void ) { return sizeof (fd_stake_ci_t); } 82 12 : static inline ulong fd_stake_ci_align ( void ) { return alignof(fd_stake_ci_t); } 83 : 84 : /* fd_stake_ci_new formats a piece of memory as a valid stake contact 85 : information store. `identity_key` is a pointer to the public key of 86 : the identity keypair of the local validator. This is used by 87 : fd_shred_dest to know where in the Turbine tree it belongs. 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 ); 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_stake_msg_{init, fini} are used to handle messages 97 : containing stake weight updates from the Rust side of the splice, and 98 : fd_stake_ci_dest_add_{init, fini} are used to handle messages 99 : containing contact info (potential shred destinations) updates from 100 : the Rust side of the splice. 101 : 102 : These are very specific to the current splices, but rather than parse 103 : the message in the pack and shred tiles, we parse it here. Since 104 : these messages arrive on a dcache and can get overrun, both expose a 105 : 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 : new_message should be a pointer to the first byte of the dcache entry 123 : containing the stakes update. new_message will be accessed 124 : new_message[i] for i in [0, FD_STAKE_CI_STAKE_MSG_SZ). new_message 125 : must contain at least one staked pubkey, and the pubkeys must be 126 : sorted in the usual way (by stake descending, ties broken by pubkey 127 : ascending). 128 : 129 : fd_stake_ci_dest_add_init behaves slightly differently and returns a 130 : pointer to the first element of an array of size MAX_SHRED_DESTS-1 to 131 : be populated. This allows the caller to add augment the information 132 : in the message from Rust with additional information (i.e. mac 133 : addresses). The `cnt` argument to _dest_add_fini specifies the 134 : number of elements of the array returned by _init that were 135 : populated. 0<=cnt<MAX_SHRED_DESTS. _fini will only read the first 136 : `cnt` elements of the array. The stake_lamports field of the input 137 : is ignored. The identity pubkey provided at initialization must not 138 : be one of the cnt values in the array. The caller should not retain 139 : a read or write interest in the pointer returned by _init after fini 140 : has been called, or after the caller has determined that fini will 141 : not be called for that update, e.g. because the update was overrun. 142 : Calls to _fini may clobber the array. 143 : 144 : The list used for leader schedules is always just the staked nodes. 145 : The list used for shred destinations is the staked nodes along with 146 : any unstaked nodes for which we have contact info. If a stake 147 : message doesn't have contact info for a staked node, the previous 148 : contact info will be preserved. If a stake message doesn't have 149 : contact info for an unstaked node, on the other hand, that node will 150 : be deleted from the list. */ 151 : void fd_stake_ci_stake_msg_init( fd_stake_ci_t * info, uchar const * new_message ); 152 : void fd_stake_ci_stake_msg_fini( fd_stake_ci_t * info ); 153 : fd_shred_dest_weighted_t * fd_stake_ci_dest_add_init ( fd_stake_ci_t * info ); 154 : void fd_stake_ci_dest_add_fini ( fd_stake_ci_t * info, ulong cnt ); 155 : 156 : 157 : /* fd_stake_ci_get_{sdest, lsched}_for_slot respectively return a 158 : pointer to the fd_shred_dest_t and fd_epoch_leaders_t containing 159 : information about the specified slot, if it is available. These 160 : functions are the primary query functions for fd_stake_ci. They 161 : return NULL if we don't have information for that slot. 162 : 163 : The fact these take a slot perhaps makes it more clear, but, it's 164 : worth mentioning again there's nothing like the adjustment performed 165 : by Solana's get_leader_schedule_epoch going on here. If you want to 166 : know the leader in slot X, just pass slot X. The returned leader 167 : schedule will not be based on the stake weights active during slot X, 168 : but rather the stake weights offset in time by an appropriate amount 169 : so they apply to slot X. */ 170 : fd_shred_dest_t * fd_stake_ci_get_sdest_for_slot ( fd_stake_ci_t const * info, ulong slot ); 171 : fd_epoch_leaders_t * fd_stake_ci_get_lsched_for_slot( fd_stake_ci_t const * info, ulong slot ); 172 : 173 : #endif /* HEADER_fd_src_app_fdctl_run_tiles_fd_stake_ci_h */