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