Line data Source code
1 : #include "fd_vote_states.h"
2 : #include "../types/fd_types.h"
3 : #include "../runtime/program/fd_vote_program.h"
4 :
5 : #define POOL_NAME fd_vote_state_pool
6 3264 : #define POOL_T fd_vote_state_ele_t
7 41607039 : #define POOL_NEXT next_
8 : #include "../../util/tmpl/fd_pool.c"
9 :
10 : #define MAP_NAME fd_vote_state_map
11 : #define MAP_KEY_T fd_pubkey_t
12 : #define MAP_ELE_T fd_vote_state_ele_t
13 6 : #define MAP_KEY vote_account
14 15 : #define MAP_KEY_EQ(k0,k1) (fd_pubkey_eq( k0, k1 ))
15 30 : #define MAP_KEY_HASH(key,seed) (fd_hash( seed, key, sizeof(fd_pubkey_t) ))
16 15 : #define MAP_NEXT next_
17 : #include "../../util/tmpl/fd_map_chain.c"
18 :
19 : static fd_vote_state_ele_t *
20 150 : fd_vote_states_get_pool( fd_vote_states_t const * vote_states ) {
21 150 : return fd_vote_state_pool_join( (uchar *)vote_states + vote_states->pool_offset_ );
22 150 : }
23 :
24 : static fd_vote_state_map_t *
25 102 : fd_vote_states_get_map( fd_vote_states_t const * vote_states ) {
26 102 : return fd_vote_state_map_join( (uchar *)vote_states + vote_states->map_offset_ );
27 102 : }
28 :
29 : ulong
30 16644 : fd_vote_states_align( void ) {
31 : /* The align of the struct should be the max of the align of the data
32 : structures that it contains. In this case, this is the map, the
33 : pool, and the struct itself. */
34 16644 : return fd_ulong_max( fd_ulong_max( fd_vote_state_map_align(),
35 16644 : fd_vote_state_pool_align() ), alignof(fd_vote_states_t) );
36 16644 : }
37 :
38 : ulong
39 2082 : fd_vote_states_footprint( ulong max_vote_accounts ) {
40 :
41 2082 : ulong map_chain_cnt = fd_vote_state_map_chain_cnt_est( max_vote_accounts );
42 :
43 2082 : ulong l = FD_LAYOUT_INIT;
44 2082 : l = FD_LAYOUT_APPEND( l, fd_vote_states_align(), sizeof(fd_vote_states_t) );
45 2082 : l = FD_LAYOUT_APPEND( l, fd_vote_state_pool_align(), fd_vote_state_pool_footprint( max_vote_accounts ) );
46 2082 : l = FD_LAYOUT_APPEND( l, fd_vote_state_map_align(), fd_vote_state_map_footprint( map_chain_cnt ) );
47 2082 : return FD_LAYOUT_FINI( l, fd_vote_states_align() );
48 2082 : }
49 :
50 : void *
51 : fd_vote_states_new( void * mem,
52 : ulong max_vote_accounts,
53 1044 : ulong seed ) {
54 1044 : if( FD_UNLIKELY( !mem ) ) {
55 3 : FD_LOG_WARNING(( "NULL mem" ));
56 3 : return NULL;
57 3 : }
58 :
59 1041 : if( FD_UNLIKELY( !max_vote_accounts ) ) {
60 3 : FD_LOG_WARNING(( "max_vote_accounts is 0" ));
61 3 : return NULL;
62 3 : }
63 :
64 1038 : if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)mem, fd_vote_states_align() ) ) ) {
65 0 : FD_LOG_WARNING(( "misaligned mem" ));
66 0 : return NULL;
67 0 : }
68 :
69 1038 : ulong map_chain_cnt = fd_vote_state_map_chain_cnt_est( max_vote_accounts );
70 :
71 1038 : FD_SCRATCH_ALLOC_INIT( l, mem );
72 1038 : fd_vote_states_t * vote_states = FD_SCRATCH_ALLOC_APPEND( l, fd_vote_states_align(), sizeof(fd_vote_states_t) );
73 1038 : void * pool_mem = FD_SCRATCH_ALLOC_APPEND( l, fd_vote_state_pool_align(), fd_vote_state_pool_footprint( max_vote_accounts ) );
74 1038 : void * map_mem = FD_SCRATCH_ALLOC_APPEND( l, fd_vote_state_map_align(), fd_vote_state_map_footprint( map_chain_cnt ) );
75 :
76 1038 : if( FD_UNLIKELY( FD_SCRATCH_ALLOC_FINI( l, fd_vote_states_align() )!=(ulong)mem+fd_vote_states_footprint( max_vote_accounts ) ) ) {
77 0 : FD_LOG_WARNING(( "fd_vote_states_new: bad layout" ));
78 0 : return NULL;
79 0 : }
80 :
81 1038 : vote_states->max_vote_accounts_ = max_vote_accounts;
82 1038 : vote_states->pool_offset_ = (ulong)pool_mem - (ulong)mem;
83 1038 : vote_states->map_offset_ = (ulong)map_mem - (ulong)mem;
84 :
85 1038 : fd_vote_state_ele_t * vote_states_pool = fd_vote_state_pool_join( fd_vote_state_pool_new( pool_mem, max_vote_accounts ) );
86 1038 : if( FD_UNLIKELY( !vote_states_pool ) ) {
87 0 : FD_LOG_WARNING(( "Failed to create vote states pool" ));
88 0 : return NULL;
89 0 : }
90 :
91 41608068 : for( ulong i=0UL; i<max_vote_accounts; i++ ) {
92 41607030 : fd_vote_state_ele_t * vote_state = fd_vote_state_pool_ele( vote_states_pool, i );
93 41607030 : vote_state->idx = i;
94 41607030 : }
95 :
96 1038 : if( FD_UNLIKELY( !fd_vote_state_map_join( fd_vote_state_map_new( map_mem, map_chain_cnt, seed ) ) ) ) {
97 0 : FD_LOG_WARNING(( "Failed to create vote states map" ));
98 0 : return NULL;
99 0 : }
100 :
101 1038 : FD_COMPILER_MFENCE();
102 1038 : FD_VOLATILE( vote_states->magic ) = FD_VOTE_STATES_MAGIC;
103 1038 : FD_COMPILER_MFENCE();
104 :
105 1038 : return mem;
106 1038 : }
107 :
108 : fd_vote_states_t *
109 1044 : fd_vote_states_join( void * mem ) {
110 1044 : if( FD_UNLIKELY( !mem ) ) {
111 3 : FD_LOG_WARNING(( "NULL mem" ));
112 3 : return NULL;
113 3 : }
114 :
115 1041 : if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)mem, fd_vote_states_align() ) ) ) {
116 0 : FD_LOG_WARNING(( "misaligned mem" ));
117 0 : return NULL;
118 0 : }
119 :
120 1041 : fd_vote_states_t * vote_states = (fd_vote_states_t *)mem;
121 :
122 1041 : if( FD_UNLIKELY( vote_states->magic != FD_VOTE_STATES_MAGIC ) ) {
123 3 : FD_LOG_WARNING(( "Invalid vote states magic" ));
124 3 : return NULL;
125 3 : }
126 :
127 1038 : ulong map_chain_cnt = fd_vote_state_map_chain_cnt_est( vote_states->max_vote_accounts_ );
128 1038 : FD_SCRATCH_ALLOC_INIT( l, vote_states );
129 1038 : vote_states = FD_SCRATCH_ALLOC_APPEND( l, fd_vote_states_align(), sizeof(fd_vote_states_t) );
130 1038 : void * pool_mem = FD_SCRATCH_ALLOC_APPEND( l, fd_vote_state_pool_align(), fd_vote_state_pool_footprint( vote_states->max_vote_accounts_ ) );
131 1038 : void * map_mem = FD_SCRATCH_ALLOC_APPEND( l, fd_vote_state_map_align(), fd_vote_state_map_footprint( map_chain_cnt ) );
132 :
133 1038 : if( FD_UNLIKELY( FD_SCRATCH_ALLOC_FINI( l, fd_vote_states_align() )!=(ulong)mem+fd_vote_states_footprint( vote_states->max_vote_accounts_ ) ) ) {
134 0 : FD_LOG_WARNING(( "fd_vote_states_join: bad layout" ));
135 0 : return NULL;
136 0 : }
137 :
138 1038 : if( FD_UNLIKELY( !fd_vote_state_pool_join( pool_mem ) ) ) {
139 0 : FD_LOG_WARNING(( "Failed to join vote states pool" ));
140 0 : return NULL;
141 0 : }
142 :
143 1038 : if( FD_UNLIKELY( !fd_vote_state_map_join( map_mem ) ) ) {
144 0 : FD_LOG_WARNING(( "Failed to join vote states map" ));
145 0 : return NULL;
146 0 : }
147 :
148 1038 : return vote_states;
149 1038 : }
150 :
151 : void
152 0 : fd_vote_states_init( fd_vote_states_t * vote_states ) {
153 0 : fd_vote_state_map_t * vote_state_map = fd_vote_states_get_map( vote_states );
154 0 : fd_vote_state_map_reset( vote_state_map );
155 0 : fd_vote_state_ele_t * vote_state_pool = fd_vote_states_get_pool( vote_states );
156 0 : fd_vote_state_pool_reset( vote_state_pool );
157 0 : }
158 :
159 : fd_vote_state_ele_t *
160 : fd_vote_states_update( fd_vote_states_t * vote_states,
161 6 : fd_pubkey_t const * vote_account ) {
162 :
163 6 : fd_vote_state_ele_t * vote_state_pool = fd_vote_states_get_pool( vote_states );
164 6 : fd_vote_state_map_t * vote_state_map = fd_vote_states_get_map( vote_states );
165 :
166 6 : if( FD_UNLIKELY( !vote_state_pool ) ) {
167 0 : FD_LOG_CRIT(( "unable to retrieve join to vote state pool" ));
168 0 : }
169 6 : if( FD_UNLIKELY( !vote_state_map ) ) {
170 0 : FD_LOG_CRIT(( "unable to retrieve join to vote state map" ));
171 0 : }
172 :
173 : /* First, handle the case where the vote state already exists
174 : and we just need to update the entry. The reason we do a const idx
175 : query is to allow fd_vote_states_update to be called while
176 : iterating over the map. It is unsafe to call
177 : fd_vote_state_map_ele_query() during iteration, but we only
178 : need to change fields which are not used for pool/map management. */
179 :
180 6 : ulong idx = fd_vote_state_map_idx_query_const(
181 6 : vote_state_map,
182 6 : vote_account,
183 6 : ULONG_MAX,
184 6 : vote_state_pool );
185 :
186 6 : if( idx!=ULONG_MAX ) {
187 0 : fd_vote_state_ele_t * vote_state = fd_vote_state_pool_ele( vote_state_pool, idx );
188 0 : if( FD_UNLIKELY( !vote_state ) ) {
189 0 : FD_LOG_CRIT(( "unable to retrieve vote state" ));
190 0 : }
191 :
192 : /* TODO: can do something smarter where we only update the
193 : comission and the credits coresponding to the new epoch. */
194 0 : return vote_state;
195 0 : }
196 :
197 : /* If the vote state does not exist, we need to create a new entry. */
198 : /* Otherwise, try to acquire a new node and populate it. */
199 6 : if( FD_UNLIKELY( !fd_vote_state_pool_free( vote_state_pool ) ) ) {
200 0 : FD_LOG_CRIT(( "no free vote states in pool" ));
201 0 : }
202 :
203 6 : fd_vote_state_ele_t * vote_state = fd_vote_state_pool_ele_acquire( vote_state_pool );
204 :
205 6 : vote_state->vote_account = *vote_account;
206 6 : vote_state->stake = 0UL;
207 6 : vote_state->stake_t_1 = 0UL;
208 6 : vote_state->stake_t_2 = 0UL;
209 :
210 6 : if( FD_UNLIKELY( !fd_vote_state_map_ele_insert(
211 6 : vote_state_map,
212 6 : vote_state,
213 6 : vote_state_pool ) ) ) {
214 0 : FD_LOG_CRIT(( "unable to insert stake delegation into map" ));
215 0 : }
216 6 : return vote_state;
217 6 : }
218 :
219 : void
220 : fd_vote_states_remove( fd_vote_states_t * vote_states,
221 3 : fd_pubkey_t const * vote_account ) {
222 3 : fd_vote_state_ele_t * vote_state_pool = fd_vote_states_get_pool( vote_states );
223 3 : fd_vote_state_map_t * vote_state_map = fd_vote_states_get_map( vote_states );
224 3 : if( FD_UNLIKELY( !vote_state_pool ) ) {
225 0 : FD_LOG_CRIT(( "unable to retrieve join to stake delegation pool" ));
226 0 : }
227 3 : if( FD_UNLIKELY( !vote_state_map ) ) {
228 0 : FD_LOG_CRIT(( "unable to retrieve join to stake delegation map" ));
229 0 : }
230 :
231 3 : ulong vote_state_idx = fd_vote_state_map_idx_query_const(
232 3 : vote_state_map,
233 3 : vote_account,
234 3 : ULONG_MAX,
235 3 : vote_state_pool );
236 3 : if( FD_UNLIKELY( vote_state_idx == ULONG_MAX ) ) {
237 : /* The vote state was not found, nothing to do. */
238 0 : return;
239 0 : }
240 :
241 3 : fd_vote_state_ele_t * vote_state = fd_vote_state_pool_ele( vote_state_pool, vote_state_idx );
242 3 : if( FD_UNLIKELY( !vote_state ) ) {
243 0 : FD_LOG_CRIT(( "unable to retrieve vote state" ));
244 0 : }
245 :
246 3 : ulong idx = fd_vote_state_map_idx_remove( vote_state_map, vote_account, ULONG_MAX, vote_state_pool );
247 3 : if( FD_UNLIKELY( idx==ULONG_MAX ) ) {
248 0 : FD_LOG_CRIT(( "unable to remove vote state" ));
249 0 : }
250 :
251 : /* Set vote state's next_ pointer to the null idx. */
252 3 : vote_state->next_ = fd_vote_state_pool_idx_null( vote_state_pool );
253 :
254 3 : fd_vote_state_pool_idx_release( vote_state_pool, vote_state_idx );
255 3 : }
256 :
257 : fd_vote_state_ele_t *
258 : fd_vote_states_update_from_account( fd_vote_states_t * vote_states,
259 : fd_pubkey_t const * vote_account,
260 : uchar const * account_data,
261 0 : ulong account_data_len ) {
262 :
263 : /* TODO: Instead of doing this messy + unbounded decode, it should be
264 : replaced with a more efficient decode that just reads the fields
265 : we need directly. */
266 :
267 0 : fd_bincode_decode_ctx_t ctx = {
268 0 : .data = account_data,
269 0 : .dataend = account_data + account_data_len,
270 0 : };
271 :
272 0 : uchar __attribute__((aligned(FD_VOTE_STATE_VERSIONED_ALIGN))) vote_state_versioned[ FD_VOTE_STATE_VERSIONED_FOOTPRINT ];
273 :
274 0 : fd_vote_state_versioned_t * vsv = fd_vote_state_versioned_decode( vote_state_versioned, &ctx );
275 0 : if( FD_UNLIKELY( vsv==NULL ) ) {
276 0 : FD_LOG_CRIT(( "unable to decode vote state versioned" ));
277 0 : }
278 :
279 0 : fd_pubkey_t node_account;
280 0 : uchar commission;
281 0 : long last_vote_timestamp;
282 0 : ulong last_vote_slot;
283 :
284 0 : switch( vsv->discriminant ) {
285 0 : case fd_vote_state_versioned_enum_v0_23_5:
286 0 : node_account = vsv->inner.v0_23_5.node_pubkey;
287 0 : commission = vsv->inner.v0_23_5.commission;
288 0 : last_vote_timestamp = vsv->inner.v0_23_5.last_timestamp.timestamp;
289 0 : last_vote_slot = vsv->inner.v0_23_5.last_timestamp.slot;
290 0 : break;
291 0 : case fd_vote_state_versioned_enum_v1_14_11:
292 0 : node_account = vsv->inner.v1_14_11.node_pubkey;
293 0 : commission = vsv->inner.v1_14_11.commission;
294 0 : last_vote_timestamp = vsv->inner.v1_14_11.last_timestamp.timestamp;
295 0 : last_vote_slot = vsv->inner.v1_14_11.last_timestamp.slot;
296 0 : break;
297 0 : case fd_vote_state_versioned_enum_v3:
298 0 : node_account = vsv->inner.v3.node_pubkey;
299 0 : commission = vsv->inner.v3.commission;
300 0 : last_vote_timestamp = vsv->inner.v3.last_timestamp.timestamp;
301 0 : last_vote_slot = vsv->inner.v3.last_timestamp.slot;
302 0 : break;
303 0 : case fd_vote_state_versioned_enum_v4:
304 : /* Commission calculation is deliberate according to this:
305 : https://github.com/anza-xyz/agave/blob/v3.1.1/vote/src/vote_state_view/field_frames.rs#L353 */
306 0 : node_account = vsv->inner.v4.node_pubkey;
307 0 : commission = (uchar)fd_ushort_min( vsv->inner.v4.inflation_rewards_commission_bps/100, UCHAR_MAX );
308 0 : last_vote_timestamp = vsv->inner.v4.last_timestamp.timestamp;
309 0 : last_vote_slot = vsv->inner.v4.last_timestamp.slot;
310 0 : break;
311 0 : default:
312 0 : __builtin_unreachable();
313 0 : }
314 :
315 0 : fd_vote_state_ele_t * vote_state = fd_vote_states_update( vote_states, vote_account );
316 :
317 0 : vote_state->node_account = node_account;
318 0 : vote_state->commission = commission;
319 0 : vote_state->last_vote_timestamp = last_vote_timestamp;
320 0 : vote_state->last_vote_slot = last_vote_slot;
321 :
322 0 : return vote_state;
323 0 : }
324 :
325 : void
326 15 : fd_vote_states_reset_stakes( fd_vote_states_t * vote_states ) {
327 15 : fd_vote_state_ele_t * vote_state_pool = fd_vote_states_get_pool( vote_states );
328 15 : fd_vote_state_map_t * vote_state_map = fd_vote_states_get_map( vote_states );
329 15 : if( FD_UNLIKELY( !vote_state_pool ) ) {
330 0 : FD_LOG_CRIT(( "unable to retrieve join to vote state pool" ));
331 0 : }
332 15 : if( FD_UNLIKELY( !vote_state_map ) ) {
333 0 : FD_LOG_CRIT(( "unable to retrieve join to vote state map" ));
334 0 : }
335 :
336 15 : for( fd_vote_state_map_iter_t iter = fd_vote_state_map_iter_init( vote_state_map, vote_state_pool );
337 21 : !fd_vote_state_map_iter_done( iter, vote_state_map, vote_state_pool );
338 15 : iter = fd_vote_state_map_iter_next( iter, vote_state_map, vote_state_pool ) ) {
339 6 : ulong idx = fd_vote_state_map_iter_idx( iter, vote_state_map, vote_state_pool );
340 :
341 6 : fd_vote_state_ele_t * vote_state = fd_vote_state_pool_ele( vote_state_pool, idx );
342 6 : if( FD_UNLIKELY( !vote_state ) ) {
343 0 : FD_LOG_CRIT(( "unable to retrieve vote state" ));
344 0 : }
345 :
346 6 : vote_state->stake = 0UL;
347 6 : }
348 15 : }
349 :
350 : fd_vote_state_ele_t *
351 : fd_vote_states_query( fd_vote_states_t const * vote_states,
352 12 : fd_pubkey_t const * vote_account ) {
353 :
354 : /* map_chain's _ele_query function isn't safe for concurrent access.
355 : The solution is to use the idx_query_const function, which is safe
356 : for concurrent access. The caller is still responsible for
357 : synchronizing concurrent writers to the fd_vote_state_ele_t. */
358 12 : ulong idx = fd_vote_state_map_idx_query_const(
359 12 : fd_vote_states_get_map( vote_states ),
360 12 : vote_account,
361 12 : ULONG_MAX,
362 12 : fd_vote_states_get_pool( vote_states ) );
363 12 : if( FD_UNLIKELY( idx==ULONG_MAX ) ) {
364 3 : return NULL;
365 3 : }
366 :
367 9 : fd_vote_state_ele_t * vote_state = fd_vote_state_pool_ele( fd_vote_states_get_pool( vote_states ), idx );
368 9 : if( FD_UNLIKELY( !vote_state ) ) {
369 0 : FD_LOG_CRIT(( "unable to retrieve vote state" ));
370 0 : }
371 :
372 9 : return vote_state;
373 9 : }
374 :
375 : /* fd_vote_states_query_const is the same as fd_vote_states but instead
376 : returns a const pointer. */
377 :
378 : fd_vote_state_ele_t const *
379 : fd_vote_states_query_const( fd_vote_states_t const * vote_states,
380 0 : fd_pubkey_t const * vote_account ) {
381 0 : return fd_vote_state_map_ele_query_const(
382 0 : fd_vote_states_get_map( vote_states ),
383 0 : vote_account,
384 0 : NULL,
385 0 : fd_vote_states_get_pool( vote_states ) );
386 0 : }
387 :
388 : ulong
389 0 : fd_vote_states_max( fd_vote_states_t const * vote_states ) {
390 0 : return vote_states->max_vote_accounts_;
391 0 : }
392 :
393 : ulong
394 39 : fd_vote_states_cnt( fd_vote_states_t const * vote_states ) {
395 39 : return fd_vote_state_pool_used( fd_vote_states_get_pool( vote_states ) );
396 39 : }
397 :
398 : fd_vote_state_ele_t *
399 0 : fd_vote_states_iter_ele( fd_vote_states_iter_t * iter ) {
400 0 : ulong idx = fd_vote_state_map_iter_idx( iter->iter, iter->map, iter->pool );
401 0 : return fd_vote_state_pool_ele( iter->pool, idx );
402 0 : }
403 :
404 : fd_vote_states_iter_t *
405 : fd_vote_states_iter_init( fd_vote_states_iter_t * iter,
406 66 : fd_vote_states_t const * vote_states ) {
407 66 : if( FD_UNLIKELY( !iter ) ) {
408 0 : FD_LOG_CRIT(( "NULL iter_mem" ));
409 0 : }
410 66 : if( FD_UNLIKELY( !vote_states ) ) {
411 0 : FD_LOG_CRIT(( "NULL vote_states" ));
412 0 : }
413 :
414 66 : iter->map = fd_vote_states_get_map( vote_states );
415 66 : iter->pool = fd_vote_states_get_pool( vote_states );
416 66 : iter->iter = fd_vote_state_map_iter_init( iter->map, iter->pool );
417 :
418 66 : return iter;
419 66 : }
420 :
421 : int
422 66 : fd_vote_states_iter_done( fd_vote_states_iter_t * iter ) {
423 66 : return fd_vote_state_map_iter_done( iter->iter, iter->map, iter->pool );
424 66 : }
425 :
426 : void
427 0 : fd_vote_states_iter_next( fd_vote_states_iter_t * iter ) {
428 0 : iter->iter = fd_vote_state_map_iter_next( iter->iter, iter->map, iter->pool );
429 0 : }
|