Line data Source code
1 : #include "fd_top_votes.h"
2 : #include "../accdb/fd_accdb_sync.h"
3 : #include "../runtime/program/vote/fd_vote_state_versioned.h"
4 : #include "../runtime/program/vote/fd_vote_codec.h"
5 :
6 1185 : #define FD_TOP_VOTES_MAGIC (0xF17EDA2CE7401E70UL) /* FIREDANCER TOP VOTES V0 */
7 :
8 : struct fd_top_votes {
9 : ulong magic;
10 : ulong pool_off;
11 : ulong heap_off;
12 : ulong map_off;
13 :
14 : ulong min_stake_wmark;
15 : };
16 : typedef struct fd_top_votes fd_top_votes_t;
17 :
18 : struct vote_ele {
19 : fd_pubkey_t pubkey;
20 : fd_pubkey_t node_account;
21 : ulong stake;
22 : ulong last_vote_slot;
23 : long last_vote_timestamp;
24 : uchar commission;
25 : uchar is_valid;
26 :
27 : ushort left;
28 : ushort right;
29 : ushort next;
30 : };
31 : typedef struct vote_ele vote_ele_t;
32 :
33 : #define HEAP_NAME heap
34 14013 : #define HEAP_IDX_T ushort
35 : #define HEAP_T vote_ele_t
36 117 : #define HEAP_LT(e0,e1) ( ((e0)->stake < (e1)->stake) | \
37 117 : (((e0)->stake==(e1)->stake) & \
38 117 : (memcmp( &(e0)->pubkey, &(e1)->pubkey, sizeof(fd_pubkey_t) )<0 ) ) )
39 : #include "../../util/tmpl/fd_heap.c"
40 :
41 : #define POOL_NAME pool
42 2370 : #define POOL_T vote_ele_t
43 : #define POOL_IDX_T ushort
44 : #define POOL_LAZY 1
45 : #include "../../util/tmpl/fd_pool.c"
46 :
47 : #define MAP_NAME map
48 : #define MAP_KEY_T fd_pubkey_t
49 : #define MAP_ELE_T vote_ele_t
50 7035 : #define MAP_KEY pubkey
51 3837 : #define MAP_KEY_EQ(k0,k1) (!memcmp( k0, k1, sizeof(fd_pubkey_t) ))
52 10836 : #define MAP_KEY_HASH(key,seed) (fd_hash( seed, key, sizeof(fd_pubkey_t) ))
53 19293 : #define MAP_IDX_T ushort
54 : #include "../../util/tmpl/fd_map_chain.c"
55 :
56 : static inline vote_ele_t *
57 18576 : get_pool( fd_top_votes_t const * top_votes ) {
58 18576 : return (vote_ele_t *)( (ulong)top_votes + top_votes->pool_off );
59 18576 : }
60 :
61 : static inline heap_t *
62 14040 : get_heap( fd_top_votes_t const * top_votes ) {
63 14040 : return (heap_t *)( (ulong)top_votes + top_votes->heap_off );
64 14040 : }
65 :
66 : static inline map_t *
67 18576 : get_map( fd_top_votes_t const * top_votes ) {
68 18576 : return (map_t *)( (ulong)top_votes + top_votes->map_off );
69 18576 : }
70 :
71 : ulong
72 14247 : fd_top_votes_align( void ) {
73 14247 : return FD_TOP_VOTES_ALIGN;
74 14247 : }
75 :
76 : ulong
77 2376 : fd_top_votes_footprint( ulong vote_accounts_max ) {
78 2376 : ulong map_chain_cnt = map_chain_cnt_est( vote_accounts_max );
79 :
80 2376 : ulong l = FD_LAYOUT_INIT;
81 2376 : l = FD_LAYOUT_APPEND( l, fd_top_votes_align(), sizeof(fd_top_votes_t) );
82 2376 : l = FD_LAYOUT_APPEND( l, pool_align(), pool_footprint( vote_accounts_max ) );
83 2376 : l = FD_LAYOUT_APPEND( l, heap_align(), heap_footprint( vote_accounts_max ) );
84 2376 : l = FD_LAYOUT_APPEND( l, map_align(), map_footprint( map_chain_cnt ) );
85 2376 : return FD_LAYOUT_FINI( l, fd_top_votes_align() );
86 2376 : }
87 :
88 : void *
89 : fd_top_votes_new( void * mem,
90 : ushort vote_accounts_max,
91 1188 : ulong seed ) {
92 1188 : if( FD_UNLIKELY( !mem ) ) {
93 3 : FD_LOG_WARNING(( "NULL mem" ));
94 3 : return NULL;
95 3 : }
96 :
97 1185 : if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)mem, fd_top_votes_align() ) ) ) {
98 0 : FD_LOG_WARNING(( "misaligned mem" ));
99 0 : return NULL;
100 0 : }
101 :
102 1185 : ulong map_chain_cnt = map_chain_cnt_est( vote_accounts_max );
103 :
104 1185 : FD_SCRATCH_ALLOC_INIT( l, mem );
105 1185 : fd_top_votes_t * top_votes = FD_SCRATCH_ALLOC_APPEND( l, fd_top_votes_align(), sizeof(fd_top_votes_t) );
106 1185 : void * pool_mem = FD_SCRATCH_ALLOC_APPEND( l, pool_align(), pool_footprint( vote_accounts_max ) );
107 1185 : void * heap_mem = FD_SCRATCH_ALLOC_APPEND( l, heap_align(), heap_footprint( vote_accounts_max ) );
108 1185 : void * map_mem = FD_SCRATCH_ALLOC_APPEND( l, map_align(), map_footprint( map_chain_cnt ) );
109 :
110 1185 : if( FD_UNLIKELY( FD_SCRATCH_ALLOC_FINI( l, fd_top_votes_align() ) != (ulong)top_votes + fd_top_votes_footprint( vote_accounts_max ) ) ) {
111 0 : FD_LOG_WARNING(( "fd_banks_new: bad layout" ));
112 0 : return NULL;
113 0 : }
114 :
115 1185 : if( FD_UNLIKELY( fd_top_votes_footprint( vote_accounts_max )>FD_TOP_VOTES_MAX_FOOTPRINT ) ) {
116 0 : FD_LOG_WARNING(( "fd_top_votes_new: bad footprint" ));
117 0 : return NULL;
118 0 : }
119 :
120 1185 : vote_ele_t * pool = pool_join( pool_new( pool_mem, vote_accounts_max ) );
121 1185 : if( FD_UNLIKELY( !pool ) ) {
122 0 : FD_LOG_WARNING(( "Failed to create top votes pool" ));
123 0 : return NULL;
124 0 : }
125 1185 : top_votes->pool_off = (ulong)pool - (ulong)mem;
126 :
127 1185 : heap_t * heap = heap_join( heap_new( heap_mem, vote_accounts_max ) );
128 1185 : if( FD_UNLIKELY( !heap ) ) {
129 0 : FD_LOG_WARNING(( "Failed to create top votes heap" ));
130 0 : return NULL;
131 0 : }
132 1185 : top_votes->heap_off = (ulong)heap - (ulong)mem;
133 :
134 1185 : map_t * map = map_join( map_new( map_mem, map_chain_cnt, seed ) );
135 1185 : if( FD_UNLIKELY( !map ) ) {
136 0 : FD_LOG_WARNING(( "Failed to create top votes map" ));
137 0 : return NULL;
138 0 : }
139 1185 : top_votes->map_off = (ulong)map - (ulong)mem;
140 :
141 1185 : FD_COMPILER_MFENCE();
142 1185 : FD_VOLATILE( top_votes->magic ) = FD_TOP_VOTES_MAGIC;
143 1185 : FD_COMPILER_MFENCE();
144 :
145 1185 : return top_votes;
146 1185 : }
147 :
148 : fd_top_votes_t *
149 1185 : fd_top_votes_join( void * mem ) {
150 1185 : fd_top_votes_t * top_votes = (fd_top_votes_t *)mem;
151 :
152 1185 : if( FD_UNLIKELY( !top_votes ) ) {
153 0 : FD_LOG_WARNING(( "NULL top votes" ));
154 0 : return NULL;
155 0 : }
156 :
157 1185 : if( FD_UNLIKELY( top_votes->magic!=FD_TOP_VOTES_MAGIC ) ) {
158 0 : FD_LOG_WARNING(( "Invalid top votes magic" ));
159 0 : return NULL;
160 0 : }
161 :
162 1185 : return top_votes;
163 1185 : }
164 :
165 : void
166 6981 : fd_top_votes_init( fd_top_votes_t * top_votes ) {
167 6981 : vote_ele_t * pool = get_pool( top_votes );
168 6981 : heap_t * heap = get_heap( top_votes );
169 6981 : map_t * map = get_map( top_votes );
170 :
171 6981 : top_votes->min_stake_wmark = 0UL;
172 :
173 : /* TODO: A smarter reset can probably be done here. */
174 13941 : while( heap_ele_cnt( heap ) ) heap_ele_remove_min( heap, pool );
175 :
176 6981 : map_reset( map );
177 6981 : pool_reset( pool );
178 6981 : }
179 :
180 : void
181 : fd_top_votes_insert( fd_top_votes_t * top_votes,
182 : fd_pubkey_t const * pubkey,
183 : fd_pubkey_t const * node_account,
184 : ulong stake,
185 7059 : uchar commission ) {
186 : /* If the heap is full, treat the current minimum stake as the cutoff
187 : stake. This matches Agave's retain(stake > floor_stake) behavior:
188 : 1. Reject candidates below the cutoff.
189 : 2. Evict all entries at the cutoff stake and watermark that stake so
190 : later inserts at or below the cutoff stay excluded.
191 : 3. Insert the candidate only if it is strictly above the cutoff. */
192 :
193 7059 : vote_ele_t * pool = get_pool( top_votes );
194 7059 : heap_t * heap = get_heap( top_votes );
195 7059 : map_t * map = get_map( top_votes );
196 :
197 7059 : if( FD_UNLIKELY( stake==0UL || stake<=top_votes->min_stake_wmark ) ) return;
198 :
199 7044 : if( FD_UNLIKELY( heap_ele_cnt( heap )==heap_ele_max( heap ) ) ) {
200 15 : vote_ele_t * ele = heap_ele_peek_min( heap, pool );
201 15 : ulong min_stake = ele->stake;
202 15 : if( stake<min_stake ) return;
203 :
204 12 : top_votes->min_stake_wmark = min_stake;
205 30 : while( (ele=heap_ele_peek_min( heap, pool )) && ele && min_stake==ele->stake ) {
206 18 : heap_ele_remove_min( heap, pool );
207 18 : map_ele_remove( map, &ele->pubkey, NULL, pool );
208 18 : pool_ele_release( pool, ele );
209 18 : }
210 12 : if( FD_UNLIKELY( stake==min_stake ) ) return;
211 12 : }
212 :
213 7035 : vote_ele_t * ele = pool_ele_acquire( pool );
214 7035 : ele->pubkey = *pubkey;
215 7035 : ele->node_account = *node_account;
216 7035 : ele->stake = stake;
217 7035 : ele->commission = commission;
218 7035 : ele->last_vote_slot = 0UL;
219 7035 : ele->last_vote_timestamp = 0L;
220 7035 : ele->is_valid = 1;
221 7035 : heap_ele_insert( heap, ele, pool );
222 7035 : map_ele_insert( map, ele, pool );
223 7035 : }
224 :
225 : void
226 : fd_top_votes_update( fd_top_votes_t * top_votes,
227 : fd_pubkey_t const * pubkey,
228 : ulong last_vote_slot,
229 132 : long last_vote_timestamp ) {
230 132 : vote_ele_t * pool = get_pool( top_votes );
231 132 : map_t * map = get_map( top_votes );
232 :
233 132 : ushort idx = (ushort)map_idx_query_const( map, pubkey, USHORT_MAX, pool );
234 132 : if( FD_UNLIKELY( idx==USHORT_MAX ) ) return;
235 132 : vote_ele_t * ele = pool_ele( pool, idx );
236 132 : ele->last_vote_slot = last_vote_slot;
237 132 : ele->last_vote_timestamp = last_vote_timestamp;
238 132 : ele->is_valid = 1;
239 132 : }
240 :
241 : void
242 : fd_top_votes_invalidate( fd_top_votes_t * top_votes,
243 3 : fd_pubkey_t const * pubkey ) {
244 3 : vote_ele_t * pool = get_pool( top_votes );
245 3 : map_t * map = get_map( top_votes );
246 :
247 3 : ushort idx = (ushort)map_idx_query_const( map, pubkey, USHORT_MAX, pool );
248 3 : if( FD_UNLIKELY( idx==USHORT_MAX ) ) return;
249 3 : pool_ele( pool, idx )->is_valid = 0;
250 3 : }
251 :
252 : int
253 : fd_top_votes_query( fd_top_votes_t const * top_votes,
254 : fd_pubkey_t const * pubkey,
255 : fd_pubkey_t * node_account_out_opt,
256 : ulong * stake_out_opt,
257 : ulong * last_vote_slot_out_opt,
258 : long * last_vote_timestamp_out_opt,
259 3648 : uchar * commission_out_opt ) {
260 3648 : vote_ele_t * pool = get_pool( top_votes );
261 3648 : map_t * map = get_map( top_votes );
262 :
263 3648 : vote_ele_t const * ele = map_ele_query_const( map, pubkey, NULL, pool );
264 3648 : if( FD_UNLIKELY( !ele ) ) return 0;
265 3591 : if( FD_UNLIKELY( !ele->is_valid ) ) return 0;
266 :
267 3588 : if( node_account_out_opt ) *node_account_out_opt = ele->node_account;
268 3588 : if( stake_out_opt ) *stake_out_opt = ele->stake;
269 3588 : if( last_vote_slot_out_opt ) *last_vote_slot_out_opt = ele->last_vote_slot;
270 3588 : if( last_vote_timestamp_out_opt ) *last_vote_timestamp_out_opt = ele->last_vote_timestamp;
271 3588 : if( commission_out_opt ) *commission_out_opt = ele->commission;
272 3588 : return 1;
273 3591 : }
274 :
275 : void
276 : fd_top_votes_refresh( fd_top_votes_t * top_votes,
277 : fd_accdb_user_t * accdb,
278 0 : fd_funk_txn_xid_t const * xid ) {
279 0 : uchar __attribute__((aligned(FD_TOP_VOTES_ITER_ALIGN))) top_votes_iter_mem[ FD_TOP_VOTES_ITER_FOOTPRINT ];
280 0 : for( fd_top_votes_iter_t * iter = fd_top_votes_iter_init( top_votes, top_votes_iter_mem );
281 0 : !fd_top_votes_iter_done( top_votes, iter );
282 0 : fd_top_votes_iter_next( top_votes, iter ) ) {
283 0 : fd_pubkey_t pubkey;
284 0 : fd_top_votes_iter_ele( top_votes, iter, &pubkey, NULL, NULL, NULL, NULL, NULL );
285 :
286 0 : int is_valid = 1;
287 0 : fd_accdb_ro_t acc[1];
288 0 : if( FD_UNLIKELY( !fd_accdb_open_ro( accdb, acc, xid, &pubkey ) ) ) {
289 0 : is_valid = 0;
290 0 : } else if( FD_UNLIKELY( !fd_vsv_is_correct_size_owner_and_init( acc->meta ) ) ) {
291 0 : fd_accdb_close_ro( accdb, acc );
292 0 : is_valid = 0;
293 0 : }
294 :
295 0 : if( FD_LIKELY( is_valid ) ) {
296 0 : fd_vote_block_timestamp_t last_vote;
297 0 : FD_TEST( !fd_vote_account_last_timestamp( fd_account_data( acc->meta ), acc->meta->dlen, &last_vote ) );
298 0 : fd_top_votes_update( top_votes, &pubkey, last_vote.slot, last_vote.timestamp );
299 0 : fd_accdb_close_ro( accdb, acc );
300 0 : } else {
301 0 : fd_top_votes_invalidate( top_votes, &pubkey );
302 0 : }
303 0 : }
304 0 : }
305 :
306 : FD_STATIC_ASSERT( FD_TOP_VOTES_ITER_FOOTPRINT == sizeof(map_iter_t), top_votes_iter );
307 : FD_STATIC_ASSERT( FD_TOP_VOTES_ITER_ALIGN == alignof(map_iter_t), top_votes_iter );
308 :
309 : fd_top_votes_iter_t *
310 : fd_top_votes_iter_init( fd_top_votes_t const * top_votes,
311 147 : uchar iter_mem[ static FD_TOP_VOTES_ITER_FOOTPRINT ] ) {
312 147 : map_iter_t iter = map_iter_init( get_map( top_votes ), get_pool( top_votes ) );
313 147 : memcpy( iter_mem, &iter, sizeof(map_iter_t) );
314 147 : return (fd_top_votes_iter_t *)iter_mem;
315 147 : }
316 :
317 : int
318 : fd_top_votes_iter_done( fd_top_votes_t const * top_votes,
319 300 : fd_top_votes_iter_t * iter ) {
320 300 : map_iter_t * map_iter = (map_iter_t *)iter;
321 300 : return map_iter_done( *map_iter, get_map( top_votes ), get_pool( top_votes ) );
322 300 : }
323 :
324 : void
325 : fd_top_votes_iter_next( fd_top_votes_t const * top_votes,
326 153 : fd_top_votes_iter_t * iter ) {
327 153 : map_iter_t * map_iter = (map_iter_t *)iter;
328 153 : *map_iter = map_iter_next( *map_iter, get_map( top_votes ), get_pool( top_votes ) );
329 153 : }
330 :
331 : int
332 : fd_top_votes_iter_ele( fd_top_votes_t const * top_votes,
333 : fd_top_votes_iter_t * iter,
334 : fd_pubkey_t * pubkey_out,
335 : fd_pubkey_t * node_account_out_opt,
336 : ulong * stake_out_opt,
337 : uchar * commission_out_opt,
338 : ulong * last_vote_slot_out_opt,
339 153 : long * last_vote_timestamp_out_opt ) {
340 153 : map_iter_t * map_iter = (map_iter_t *)iter;
341 153 : vote_ele_t * ele = map_iter_ele( *map_iter, get_map( top_votes ), get_pool( top_votes ) );
342 153 : *pubkey_out = ele->pubkey;
343 :
344 153 : if( node_account_out_opt ) *node_account_out_opt = ele->node_account;
345 153 : if( stake_out_opt ) *stake_out_opt = ele->stake;
346 153 : if( last_vote_slot_out_opt ) *last_vote_slot_out_opt = ele->last_vote_slot;
347 153 : if( last_vote_timestamp_out_opt ) *last_vote_timestamp_out_opt = ele->last_vote_timestamp;
348 153 : if( commission_out_opt ) *commission_out_opt = ele->commission;
349 :
350 153 : return ele->is_valid;
351 153 : }
|