Line data Source code
1 : #ifndef HEADER_fd_src_choreo_voter_fd_voter_h 2 : #define HEADER_fd_src_choreo_voter_fd_voter_h 3 : 4 : /* fd_voter is an API for accessing voters' on-chain accounts, known as 5 : "vote states". The accounts are in bincode-serialized form and voter 6 : intentionally X-rays the accounts ie. interprets the bytes without 7 : deserializing. */ 8 : 9 : #include "../fd_choreo_base.h" 10 : #include "../../funk/fd_funk_rec.h" 11 : 12 : /* FD_VOTER_USE_HANDHOLDING: Define this to non-zero at compile time 13 : to turn on additional runtime checks and logging. */ 14 : 15 : #ifndef FD_VOTER_USE_HANDHOLDING 16 : #define FD_VOTER_USE_HANDHOLDING 1 17 : #endif 18 : 19 : #define FD_VOTER_STATE_V0_23_5 (0) 20 : #define FD_VOTER_STATE_V1_14_11 (1) 21 : #define FD_VOTER_STATE_CURRENT (2) 22 : FD_STATIC_ASSERT(FD_VOTER_STATE_V0_23_5 ==fd_vote_state_versioned_enum_v0_23_5, FD_VOTER_STATE_V0_23_5 ); 23 : FD_STATIC_ASSERT(FD_VOTER_STATE_V1_14_11==fd_vote_state_versioned_enum_v1_14_11, FD_VOTER_STATE_V1_14_11); 24 : FD_STATIC_ASSERT(FD_VOTER_STATE_CURRENT ==fd_vote_state_versioned_enum_current, FD_VOTER_STATE_CURRENT ); 25 : 26 : 27 : 28 : /* Agave VoteAccount https://github.com/anza-xyz/agave/blob/v2.3.7/vote/src/vote_state_view.rs#L182 */ 29 : /* fd_voter_v2_serde defines a serialization / deserialization schema 30 : for a bincode-encoded vote account v2. This corresponds exactly with 31 : the binary layout of a an Agave VoteState1_14_11. 32 : 33 : The serde is structured for zero-copy access ie. x-raying individual 34 : fields. */ 35 : 36 : struct fd_voter_v2_serde { 37 : fd_pubkey_t const * node_pubkey; 38 : fd_pubkey_t const * authorized_withdrawer; 39 : uchar const * commission; 40 : 41 : struct /* VecDeque<Lockout> */ { 42 : ulong const * votes_cnt; 43 : struct { 44 : ulong const * slot; 45 : uint const * confirmation_count; 46 : } votes[31]; /* idx >= votes_cnt are invalid */ 47 : }; 48 : 49 : struct /* Option<Slot> */ { 50 : uchar const * root_slot_option; 51 : ulong const * root_slot; 52 : }; 53 : 54 : struct /* AuthorizedVoters */ { 55 : ulong const * authorized_voters_cnt; 56 : struct { 57 : ulong const * epoch; 58 : fd_pubkey_t const * pubkey; 59 : } authorized_voters[32]; /* idx >= authorized_voters_cnt are invalid */ 60 : }; 61 : 62 : struct /* CircBuf<Pubkey, Epoch, Epoch> */ { 63 : struct { 64 : fd_pubkey_t const * pubkey; 65 : ulong const * start_epoch; 66 : ulong const * end_epoch; 67 : } buf[32]; 68 : ulong const * idx; 69 : uchar const * is_empty; 70 : } prior_voters; 71 : 72 : struct /* Vec<Epoch, u64, u64> */ { 73 : ulong const * epoch_credits_cnt; 74 : struct { 75 : ulong const * epoch; 76 : ulong const * credits; 77 : ulong const * prev_credits; 78 : } epoch_credits[32]; /* idx >= epoch_credits_cnt are invalid */ 79 : }; 80 : 81 : struct /* BlockTimestamp */ { 82 : ulong const * slot; 83 : long const * timestamp; 84 : } last_timestamp; 85 : }; 86 : typedef struct fd_voter_v2_serde fd_voter_v2_serde_t; 87 : 88 : /* fd_voter_v3_serde defines a serialization / deserialization schema 89 : for a bincode-encoded vote account v3. This corresponds exactly with 90 : the binary layout of a an Agave VoteState (also known as 91 : VoteStateVersioned::Current). 92 : 93 : The serde is structured for zero-copy access ie. x-raying individual 94 : fields. */ 95 : 96 : struct fd_voter_v3_serde { 97 : fd_pubkey_t const * node_pubkey; 98 : fd_pubkey_t const * authorized_withdrawer; 99 : uchar const * commission; 100 : 101 : struct /* VecDeque<LandedVote> */ { 102 : ulong const * votes_cnt; 103 : struct { 104 : uchar const * latency; 105 : ulong const * slot; 106 : uint const * confirmation_count; 107 : } votes[31]; /* idx >= votes_cnt are invalid */ 108 : }; 109 : 110 : struct /* Option<Slot> */ { 111 : uchar const * root_slot_option; 112 : ulong const * root_slot; 113 : }; 114 : 115 : struct /* AuthorizedVoters */ { 116 : ulong const * authorized_voters_cnt; 117 : struct { 118 : ulong const * epoch; 119 : fd_pubkey_t const * pubkey; 120 : } authorized_voters[32]; /* idx >= authorized_voters_cnt are invalid */ 121 : }; 122 : 123 : struct /* CircBuf<Pubkey, Epoch, Epoch> */ { 124 : struct { 125 : fd_pubkey_t const * pubkey; 126 : ulong const * start_epoch; 127 : ulong const * end_epoch; 128 : } buf[32]; 129 : ulong const * idx; 130 : uchar const * is_empty; 131 : } prior_voters; 132 : 133 : struct /* Vec<Epoch, u64, u64> */ { 134 : ulong const * epoch_credits_cnt; 135 : struct { 136 : ulong const * epoch; 137 : ulong const * credits; 138 : ulong const * prev_credits; 139 : } epoch_credits[32]; /* idx >= epoch_credits_cnt are invalid */ 140 : }; 141 : 142 : struct /* BlockTimestamp */ { 143 : ulong const * slot; 144 : long const * timestamp; 145 : } last_timestamp; 146 : }; 147 : typedef struct fd_voter_v3_serde fd_voter_v3_serde_t; 148 : 149 : /* Useful to keep both the block_id and slot in the vote record, 150 : for handling equivocation cases. Potentially re-evaluate removing the 151 : slot altogether.*/ 152 : 153 : struct fd_vote_record { 154 : ulong slot; 155 : fd_hash_t hash; 156 : }; 157 : typedef struct fd_vote_record fd_vote_record_t; 158 : 159 : /* A fd_voter_t describes a voter. The voter is generic to the context 160 : in which it is used, eg. it might be a voter in a slot-level context 161 : in which its stake value may be different from the same voter in an 162 : epoch-level context which in turn is different from the same voter in 163 : the prior epoch's context. 164 : 165 : The voter is used by various choreo APIs including fd_epoch which 166 : tracks all the voters in a given epoch, fd_forks which performs 167 : choreo-related fork updates after replaying a slot, and ghost and 168 : tower which both require bookkeeping the epoch voters. */ 169 : 170 : struct fd_voter { 171 : fd_pubkey_t key; /* vote account address */ 172 : uint hash; /* reserved for fd_map_dynamic.c */ 173 : 174 : /* IMPORTANT! The values below should only be modified by fd_epoch and 175 : fd_ghost. */ 176 : 177 : ulong stake; /* voter's stake */ 178 : fd_vote_record_t replay_vote; /* cached read of last tower vote via replay */ 179 : fd_vote_record_t gossip_vote; /* cached read of last tower vote via gossip */ 180 : fd_vote_record_t rooted_vote; /* cached read of last tower root via replay */ 181 : }; 182 : typedef struct fd_voter fd_voter_t; 183 : 184 : /* fd_voter_{vote_old, vote, meta, meta_old, state} are struct 185 : representations of the bincode-serialized layout of a voter's state 186 : stored in a vote account. These structs are used to support zero-copy 187 : reads of the vote account. 188 : 189 : The voter's state is versioned, and the serialized formats differ 190 : depending on this. Currently, the only version that differs from the 191 : others that is relevant here is v0.23.5. Thus v0.23.5 has its own 192 : dedicated struct definition with a different set of fields that 193 : precede the votes than the other versions. Furthermore, v0.23.5 194 : contains votes of type `fd_vote_lockout_t` vs. the other versions 195 : which are of type `fd_landed_vote_t`. The only difference between 196 : `fd_vote_lockout_t` and `fd_landed_vote_t` is there is an additional 197 : uchar field `latency`, so that is we include an offset of 0 or 1 198 : depending on which vote state type it is. 199 : 200 : The layout begins with a set of fields providing important metadata 201 : about the voter. Immediately following these fields is the tower 202 : itself. The tower layout begins with the number of votes currently in 203 : the tower ie. `cnt`. Then the votes themselves follow. The format of 204 : the votes varies depending on the version. Finally the layout 205 : concludes with the tower's root slot. 206 : 207 : -------- 208 : metadata <- sizeof(fd_voter_meta_t) or sizeof(fd_voter_meta_old_t) 209 : -------- 210 : votes <- {sizeof(vote) or sizeof(vote_old)} * cnt 211 : -------- 212 : root <- 5 or 1 byte(s). bincode-serialized Option<u64> 213 : -------- 214 : */ 215 : 216 : struct __attribute__((packed)) fd_voter_vote_old { 217 : ulong slot; 218 : uint conf; 219 : }; 220 : typedef struct fd_voter_vote_old fd_voter_vote_old_t; 221 : 222 : struct __attribute__((packed)) fd_voter_vote { 223 : uchar latency; 224 : ulong slot; 225 : uint conf; 226 : }; 227 : typedef struct fd_voter_vote fd_voter_vote_t; 228 : 229 : struct __attribute__((packed)) fd_voter_meta_old { 230 : fd_pubkey_t node_pubkey; 231 : fd_pubkey_t authorized_voter; 232 : ulong authorized_voter_epoch; 233 : uchar prior_voters[ (32*56+sizeof(ulong)) /* serialized bincode sz */ ]; 234 : fd_pubkey_t authorized_withdrawer; 235 : uchar commission; 236 : }; 237 : typedef struct fd_voter_meta_old fd_voter_meta_old_t; 238 : 239 : struct __attribute__((packed)) fd_voter_meta { 240 : fd_pubkey_t node_pubkey; 241 : fd_pubkey_t authorized_withdrawer; 242 : uchar commission; 243 : }; 244 : typedef struct fd_voter_meta fd_voter_meta_t; 245 : 246 : struct __attribute__((packed)) fd_voter_state { 247 : uint kind; 248 : union { 249 : struct __attribute__((packed)) { 250 : fd_voter_meta_old_t meta; 251 : ulong cnt; 252 : fd_voter_vote_old_t votes[31]; 253 : } v0_23_5; 254 : 255 : struct __attribute__((packed)) { 256 : fd_voter_meta_t meta; 257 : ulong cnt; 258 : fd_voter_vote_old_t votes[31]; 259 : } v1_14_11; 260 : 261 : struct __attribute__((packed)) { 262 : fd_voter_meta_t meta; 263 : ulong cnt; 264 : fd_voter_vote_t votes[31]; 265 : }; 266 : 267 : /* The voter's root (a bincode-serialized Option<u64>) follows 268 : votes. Because the preceding votes are variable-length in 269 : serialized form, we cannot encode the root directly inside the 270 : struct. */ 271 : }; 272 : }; 273 : typedef struct fd_voter_state fd_voter_state_t; 274 : 275 : struct __attribute__((packed)) fd_voter_tower { 276 : ulong cnt; 277 : fd_voter_vote_old_t votes[31]; 278 : }; 279 : typedef struct fd_voter_tower fd_voter_tower_t; 280 : 281 : struct __attribute__((packed)) fd_voter_footer { 282 : uchar some; /* 0 = None, 1 = Some */ 283 : ulong root; 284 : }; 285 : 286 : /* fd_voter_state_cnt returns the number of votes in the voter's tower. 287 : Assumes `state` is a valid fd_voter_state_t. */ 288 : 289 : FD_FN_PURE static inline ulong 290 0 : fd_voter_state_cnt( fd_voter_state_t const * state ) { 291 0 : if( FD_UNLIKELY( state->kind == FD_VOTER_STATE_V0_23_5 ) ) return state->v0_23_5.cnt; 292 0 : if( FD_UNLIKELY( state->kind == FD_VOTER_STATE_V1_14_11 ) ) return state->v1_14_11.cnt; 293 0 : return state->cnt; 294 0 : } 295 : 296 : /* fd_voter_root_laddr returns a pointer to the voter's root by x-raying 297 : the bincode-serialized vote state. */ 298 : 299 : static inline uchar * 300 0 : fd_voter_root_laddr( fd_voter_state_t const * state ) { 301 0 : ulong cnt = fd_voter_state_cnt( state ); 302 0 : if( FD_UNLIKELY( !cnt ) ) return NULL; 303 0 : uchar * root = NULL; 304 0 : if ( FD_UNLIKELY( state->kind == FD_VOTER_STATE_V0_23_5 ) ) root = (uchar *)&state->v0_23_5.votes[cnt]; 305 0 : else if( FD_UNLIKELY( state->kind == FD_VOTER_STATE_V1_14_11 ) ) root = (uchar *)&state->v1_14_11.votes[cnt]; 306 0 : else root = (uchar *)&state->votes[cnt]; 307 0 : FD_TEST( root ); 308 0 : return root; 309 0 : } 310 : 311 : /* fd_voter_state queries funk for the record in the provided `txn` and 312 : `key`. Returns a pointer to the start of the voter's state. Assumes 313 : `key` is a vote account address and the record is a voter's state 314 : (fd_voter_state_t). U.B. if `key` does not point to a valid vote 315 : account. 316 : 317 : It will update the given Funk query with the version at the point of 318 : querying. fd_funk_rec_query_test must be called after usage to check 319 : that the record has not been modified. */ 320 : 321 : fd_voter_state_t const * 322 : fd_voter_state( fd_funk_t const * funk, fd_funk_rec_t const * rec ); 323 : 324 : /* fd_voter_state_vote returns the voter's most recent vote (ie. the 325 : last vote of the tower in the voter's state). Assumes `state` is a 326 : valid fd_voter_state_t. */ 327 : 328 : FD_FN_PURE static inline ulong 329 0 : fd_voter_state_vote( fd_voter_state_t const * state ) { 330 0 : ulong cnt = fd_voter_state_cnt( state ); 331 0 : if( FD_UNLIKELY( !cnt ) ) return FD_SLOT_NULL; 332 : 333 0 : if( FD_UNLIKELY( state->kind == FD_VOTER_STATE_V0_23_5 ) ) return state->v0_23_5.votes[cnt - 1].slot; 334 0 : if( FD_UNLIKELY( state->kind == FD_VOTER_STATE_V1_14_11 ) ) return state->v1_14_11.votes[cnt - 1].slot; 335 0 : return state->votes[cnt - 1].slot; 336 0 : } 337 : 338 : /* fd_voter_root_slot returns the voter's root slot. Assumes `state` 339 : is a valid fd_voter_state_t. */ 340 : 341 : static inline ulong 342 0 : fd_voter_root_slot( fd_voter_state_t const * state ) { 343 0 : uchar * root = fd_voter_root_laddr( state ); 344 0 : return *(uchar *)root ? *(ulong *)(root+sizeof(uchar)) /* Some(root) */ : FD_SLOT_NULL /* None */; 345 0 : } 346 : 347 : #endif /* HEADER_fd_src_choreo_voter_fd_voter_h */