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