Line data Source code
1 : #include "fd_hfork.h"
2 : #include "fd_hfork_private.h"
3 :
4 : static void
5 : check( fd_hfork_t * hfork,
6 : ulong total_stake,
7 : candidate_t * candidate,
8 : int dead,
9 0 : fd_hash_t * our_bank_hash ) {
10 :
11 0 : if( FD_LIKELY( candidate->checked ) ) return; /* already checked this bank hash against our own */
12 0 : double pct = (double)candidate->stake * 100.0 / (double)total_stake;
13 0 : if( FD_LIKELY( pct < 52.0 ) ) return; /* not enough stake to compare */
14 :
15 0 : if( FD_UNLIKELY( dead ) ) {
16 0 : char msg[ 4096UL ];
17 0 : FD_BASE58_ENCODE_32_BYTES( candidate->key.block_id.uc, _block_id );
18 0 : FD_TEST( fd_cstr_printf_check( msg, sizeof( msg ), NULL,
19 0 : "HARD FORK DETECTED: our validator has marked slot %lu with block ID `%s` dead, but %lu validators with %.1f of stake have voted on it",
20 0 : candidate->slot,
21 0 : _block_id,
22 0 : candidate->cnt,
23 0 : pct ) );
24 :
25 0 : if( FD_UNLIKELY( hfork->fatal ) ) FD_LOG_ERR (( "%s", msg ));
26 0 : else FD_LOG_WARNING(( "%s", msg ));
27 0 : } else if( FD_UNLIKELY( 0!=memcmp( our_bank_hash, &candidate->key.bank_hash, 32UL ) ) ) {
28 0 : char msg[ 4096UL ];
29 0 : FD_BASE58_ENCODE_32_BYTES( our_bank_hash->uc, _our_bank_hash );
30 0 : FD_BASE58_ENCODE_32_BYTES( candidate->key.block_id.uc, _block_id );
31 0 : FD_BASE58_ENCODE_32_BYTES( candidate->key.bank_hash.uc, _bank_hash );
32 0 : FD_TEST( fd_cstr_printf_check( msg, sizeof( msg ), NULL,
33 0 : "HARD FORK DETECTED: our validator has produced bank hash `%s` for slot %lu with block ID `%s`, but %lu validators with %.1f of stake have voted on a different bank hash `%s` for the same slot",
34 0 : _our_bank_hash,
35 0 : candidate->slot,
36 0 : _block_id,
37 0 : candidate->cnt,
38 0 : pct,
39 0 : _bank_hash ) );
40 :
41 0 : if( FD_UNLIKELY( hfork->fatal ) ) FD_LOG_ERR (( "%s", msg ));
42 0 : else FD_LOG_WARNING(( "%s", msg ));
43 0 : }
44 0 : candidate->checked = 1;
45 0 : }
46 :
47 : ulong
48 0 : fd_hfork_align( void ) {
49 0 : return 128UL;
50 0 : }
51 :
52 : ulong
53 : fd_hfork_footprint( ulong max_live_slots,
54 0 : ulong max_vote_accounts ) {
55 0 : ulong fork_max = max_live_slots * max_vote_accounts;
56 0 : int lg_blk_max = fd_ulong_find_msb( fd_ulong_pow2_up( fork_max ) ) + 1;
57 0 : int lg_vtr_max = fd_ulong_find_msb( fd_ulong_pow2_up( max_vote_accounts ) ) + 1;
58 :
59 0 : ulong l = FD_LAYOUT_INIT;
60 0 : l = FD_LAYOUT_APPEND( l, alignof(fd_hfork_t), sizeof(fd_hfork_t) );
61 0 : l = FD_LAYOUT_APPEND( l, blk_map_align(), blk_map_footprint( lg_blk_max ) );
62 0 : l = FD_LAYOUT_APPEND( l, vtr_map_align(), vtr_map_footprint( lg_vtr_max ) );
63 0 : l = FD_LAYOUT_APPEND( l, candidate_map_align(), candidate_map_footprint( lg_blk_max ) );
64 0 : l = FD_LAYOUT_APPEND( l, bank_hash_pool_align(), bank_hash_pool_footprint( fork_max ) );
65 0 : for( ulong i = 0UL; i < fd_ulong_pow2( lg_vtr_max ); i++ ) {
66 0 : l = FD_LAYOUT_APPEND( l, votes_align(), votes_footprint( max_live_slots ) );
67 0 : }
68 0 : return FD_LAYOUT_FINI( l, fd_hfork_align() );
69 0 : }
70 :
71 : void *
72 : fd_hfork_new( void * shmem,
73 : ulong max_live_slots,
74 : ulong max_vote_accounts,
75 : ulong seed,
76 0 : int fatal ) {
77 :
78 0 : if( FD_UNLIKELY( !shmem ) ) {
79 0 : FD_LOG_WARNING(( "NULL mem" ));
80 0 : return NULL;
81 0 : }
82 :
83 0 : if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)shmem, fd_hfork_align() ) ) ) {
84 0 : FD_LOG_WARNING(( "misaligned mem" ));
85 0 : return NULL;
86 0 : }
87 :
88 0 : ulong footprint = fd_hfork_footprint( max_live_slots, max_vote_accounts );
89 0 : if( FD_UNLIKELY( !footprint ) ) {
90 0 : FD_LOG_WARNING(( "bad max_live_slots (%lu) or max_vote_accounts (%lu)", max_live_slots, max_vote_accounts ));
91 0 : return NULL;
92 0 : }
93 :
94 0 : fd_memset( shmem, 0, footprint );
95 :
96 0 : ulong fork_max = max_live_slots * max_vote_accounts;
97 0 : int lg_blk_max = fd_ulong_find_msb( fd_ulong_pow2_up( fork_max ) ) + 1;
98 0 : int lg_vtr_max = fd_ulong_find_msb( fd_ulong_pow2_up( max_vote_accounts ) ) + 1;
99 :
100 0 : FD_SCRATCH_ALLOC_INIT( l, shmem );
101 0 : fd_hfork_t * hfork = FD_SCRATCH_ALLOC_APPEND( l, fd_hfork_align(), sizeof( fd_hfork_t ) );
102 0 : void * blk_map = FD_SCRATCH_ALLOC_APPEND( l, blk_map_align(), blk_map_footprint( lg_blk_max ) );
103 0 : void * vtr_map = FD_SCRATCH_ALLOC_APPEND( l, vtr_map_align(), vtr_map_footprint( lg_vtr_max ) );
104 0 : void * candidate_map = FD_SCRATCH_ALLOC_APPEND( l, candidate_map_align(), candidate_map_footprint( lg_blk_max ) );
105 0 : void * bank_hash_pool = FD_SCRATCH_ALLOC_APPEND( l, bank_hash_pool_align(), bank_hash_pool_footprint( fork_max ) );
106 :
107 0 : hfork->blk_map = blk_map_new( blk_map, lg_blk_max, seed );
108 0 : hfork->vtr_map = vtr_map_new( vtr_map, lg_vtr_max, seed );
109 0 : hfork->candidate_map = candidate_map_new( candidate_map, lg_blk_max, seed );
110 0 : hfork->bank_hash_pool = bank_hash_pool_new( bank_hash_pool, fork_max );
111 :
112 0 : vtr_t * vtr_map_ = vtr_map_join( hfork->vtr_map );
113 0 : for( ulong i = 0UL; i < fd_ulong_pow2( lg_vtr_max ); i++ ) {
114 0 : void * votes = FD_SCRATCH_ALLOC_APPEND( l, votes_align(), votes_footprint( max_live_slots ) );
115 0 : vtr_map_[i].votes = votes_new( votes, max_live_slots );
116 0 : }
117 0 : FD_TEST( FD_SCRATCH_ALLOC_FINI( l, fd_hfork_align() ) == (ulong)shmem + footprint );
118 0 : hfork->fatal = fatal;
119 0 : return shmem;
120 0 : }
121 :
122 : fd_hfork_t *
123 0 : fd_hfork_join( void * shhfork ) {
124 0 : fd_hfork_t * hfork = (fd_hfork_t *)shhfork;
125 :
126 0 : if( FD_UNLIKELY( !hfork ) ) {
127 0 : FD_LOG_WARNING(( "NULL hfork" ));
128 0 : return NULL;
129 0 : }
130 :
131 0 : if( FD_UNLIKELY( !fd_ulong_is_aligned((ulong)hfork, fd_hfork_align() ) ) ) {
132 0 : FD_LOG_WARNING(( "misaligned hfork" ));
133 0 : return NULL;
134 0 : }
135 :
136 0 : hfork->blk_map = blk_map_join( hfork->blk_map );
137 0 : hfork->vtr_map = vtr_map_join( hfork->vtr_map );
138 0 : hfork->candidate_map = candidate_map_join( hfork->candidate_map );
139 0 : hfork->bank_hash_pool = bank_hash_pool_join( hfork->bank_hash_pool );
140 0 : for( ulong i = 0UL; i < vtr_map_slot_cnt( hfork->vtr_map ); i++ ) {
141 0 : hfork->vtr_map[i].votes = votes_join( hfork->vtr_map[i].votes );
142 0 : }
143 :
144 0 : return hfork;
145 0 : }
146 :
147 : void *
148 0 : fd_hfork_leave( fd_hfork_t const * hfork ) {
149 :
150 0 : if( FD_UNLIKELY( !hfork ) ) {
151 0 : FD_LOG_WARNING(( "NULL hfork" ));
152 0 : return NULL;
153 0 : }
154 :
155 0 : return (void *)hfork;
156 0 : }
157 :
158 : void *
159 0 : fd_hfork_delete( void * hfork ) {
160 :
161 0 : if( FD_UNLIKELY( !hfork ) ) {
162 0 : FD_LOG_WARNING(( "NULL hfork" ));
163 0 : return NULL;
164 0 : }
165 :
166 0 : if( FD_UNLIKELY( !fd_ulong_is_aligned((ulong)hfork, fd_hfork_align() ) ) ) {
167 0 : FD_LOG_WARNING(( "misaligned hfork" ));
168 0 : return NULL;
169 0 : }
170 :
171 0 : return hfork;
172 0 : }
173 :
174 : void
175 0 : remove( blk_t * blk, fd_hash_t * bank_hash, bank_hash_t * pool ) {
176 0 : bank_hash_t * prev = NULL;
177 0 : bank_hash_t * curr = blk->bank_hashes;
178 0 : while( FD_LIKELY( curr ) ) {
179 0 : if( FD_LIKELY( 0==memcmp( &curr->bank_hash, bank_hash, 32UL ) ) ) break;
180 0 : prev = curr;
181 0 : curr = bank_hash_pool_ele( pool, curr->next );
182 0 : }
183 0 : FD_TEST( curr ); /* assumes bank_hash in blk->bank_hashes */
184 :
185 : /* In most cases, there is only one bank_hash per blk, so it will be
186 : the first element in blk->bank_hashes and prev will be NULL. */
187 :
188 0 : if( FD_LIKELY( !prev ) ) blk->bank_hashes = bank_hash_pool_ele( pool, curr->next );
189 0 : else prev->next = curr->next;
190 0 : bank_hash_pool_ele_release( pool, curr );
191 0 : }
192 :
193 : void
194 : fd_hfork_count_vote( fd_hfork_t * hfork,
195 : fd_hfork_metrics_t * metrics,
196 : fd_hash_t const * vote_acc,
197 : fd_hash_t const * block_id,
198 : fd_hash_t const * bank_hash,
199 : ulong slot,
200 : ulong stake,
201 0 : ulong total_stake ) {
202 :
203 : /* Get the vtr. */
204 :
205 0 : vtr_t * vtr = vtr_map_query( hfork->vtr_map, *vote_acc, NULL );
206 0 : if( FD_UNLIKELY( !vtr ) ) {
207 0 : FD_TEST( vtr_map_key_cnt( hfork->vtr_map ) < vtr_map_key_max( hfork->vtr_map ) );
208 0 : vtr = vtr_map_insert( hfork->vtr_map, *vote_acc );
209 0 : votes_remove_all( vtr->votes );
210 0 : }
211 :
212 : /* Only process newer votes (by vote slot) from a given voter. */
213 :
214 0 : if( FD_UNLIKELY( !votes_empty( vtr->votes ) && votes_peek_tail_const( vtr->votes )->slot >= slot ) ) return;
215 :
216 : /* Evict the candidate's oldest vote (by vote slot). */
217 :
218 0 : if( FD_UNLIKELY( votes_full( vtr->votes ) ) ) {
219 0 : vote_t vote = votes_pop_head( vtr->votes );
220 0 : candidate_key_t key = { .block_id = vote.block_id, .bank_hash = vote.bank_hash };
221 0 : candidate_t * candidate = candidate_map_query( hfork->candidate_map, key, NULL );
222 0 : candidate->stake -= vote.stake;
223 0 : candidate->cnt--;
224 0 : if( FD_UNLIKELY( candidate->cnt==0 ) ) {
225 0 : candidate_map_remove( hfork->candidate_map, candidate );
226 0 : blk_t * blk = blk_map_query( hfork->blk_map, vote.block_id, NULL );
227 0 : FD_TEST( blk ); /* asumes if this is in candidate_map, it must also be in blk_map */
228 0 : remove( blk, &vote.bank_hash, hfork->bank_hash_pool );
229 0 : if( FD_UNLIKELY( !blk->bank_hashes ) ) {
230 0 : blk_map_remove( hfork->blk_map, blk );
231 0 : if( FD_UNLIKELY( blk->forked ) ) {
232 0 : metrics->active--;
233 0 : metrics->pruned++;
234 0 : }
235 0 : }
236 0 : }
237 0 : }
238 :
239 : /* Push the vote onto the vtr. */
240 :
241 0 : vote_t vote = { .block_id = *block_id, .bank_hash = *bank_hash, .slot = slot, .stake = stake };
242 0 : vtr->votes = votes_push_tail( vtr->votes, vote );
243 :
244 : /* Update the hard fork candidate for this block id. */
245 :
246 0 : candidate_key_t key = { .block_id = *block_id, .bank_hash = *bank_hash };
247 0 : candidate_t * candidate = candidate_map_query( hfork->candidate_map, key, NULL );
248 0 : if( FD_UNLIKELY( !candidate ) ) {
249 0 : candidate = candidate_map_insert( hfork->candidate_map, key );
250 0 : candidate->slot = slot;
251 0 : candidate->stake = 0UL;
252 0 : candidate->cnt = 0UL;
253 0 : candidate->checked = 0;
254 0 : }
255 0 : candidate->cnt++;
256 0 : candidate->stake += stake;
257 :
258 : /* Update the list of bank hashes for this block_id. */
259 :
260 0 : blk_t * blk = blk_map_query( hfork->blk_map, *block_id, NULL );
261 0 : if( FD_UNLIKELY( !blk ) ) {
262 0 : FD_TEST( blk_map_key_cnt( hfork->blk_map ) < blk_map_key_max( hfork->blk_map ) ); /* invariant violation: blk_map full */
263 0 : blk = blk_map_insert( hfork->blk_map, *block_id );
264 0 : blk->dead = 0;
265 0 : blk->forked = 0;
266 0 : blk->replayed = 0;
267 0 : blk->bank_hashes = NULL;
268 0 : }
269 0 : int found = 0;
270 0 : ulong cnt = 0;
271 0 : bank_hash_t * prev = NULL;
272 0 : bank_hash_t * curr = blk->bank_hashes;
273 0 : while( FD_LIKELY( curr ) ) {
274 0 : if( FD_LIKELY( 0==memcmp( curr, bank_hash, 32UL ) ) ) found = 1;
275 0 : prev = curr;
276 0 : curr = bank_hash_pool_ele( hfork->bank_hash_pool, curr->next );
277 0 : cnt++;
278 0 : }
279 0 : if( FD_UNLIKELY( !found ) ) {
280 0 : FD_TEST( bank_hash_pool_free( hfork->bank_hash_pool ) );
281 0 : bank_hash_t * ele = bank_hash_pool_ele_acquire( hfork->bank_hash_pool );
282 0 : ele->bank_hash = *bank_hash;
283 0 : ele->next = bank_hash_pool_idx_null( hfork->bank_hash_pool );
284 0 : if( FD_LIKELY( !prev ) ) blk->bank_hashes = ele;
285 0 : else {
286 0 : prev->next = bank_hash_pool_idx( hfork->bank_hash_pool, ele );
287 0 : blk->forked = 1;
288 0 : metrics->seen++;
289 0 : metrics->active++;
290 0 : }
291 0 : cnt++;
292 0 : }
293 0 : metrics->max_width = fd_ulong_max( metrics->max_width, cnt );
294 :
295 : /* Check for hard forks. */
296 :
297 0 : if( FD_LIKELY( blk->replayed ) ) check( hfork, total_stake, candidate, blk->dead, &blk->our_bank_hash );
298 0 : }
299 :
300 : void
301 : fd_hfork_record_our_bank_hash( fd_hfork_t * hfork,
302 : fd_hash_t * block_id,
303 : fd_hash_t * bank_hash,
304 0 : ulong total_stake ) {
305 0 : blk_t * blk = blk_map_query( hfork->blk_map, *block_id, NULL );
306 0 : if( FD_UNLIKELY( !blk ) ) {
307 0 : blk = blk_map_insert( hfork->blk_map, *block_id );
308 : /* blk->dead set later */
309 0 : blk->forked = 0;
310 : /* blk->replayed set later */
311 : /* blk->our_bank_hash set later */
312 0 : blk->bank_hashes = NULL;
313 0 : }
314 0 : if( FD_LIKELY( bank_hash ) ) { blk->dead = 0; blk->replayed = 1; blk->our_bank_hash = *bank_hash; }
315 0 : else { blk->dead = 1; blk->replayed = 0; /* our_bank_hash undefined */ }
316 :
317 0 : bank_hash_t * curr = blk->bank_hashes;
318 0 : while( FD_LIKELY( curr ) ) {
319 0 : candidate_key_t key = { .block_id = *block_id, .bank_hash = curr->bank_hash };
320 0 : candidate_t * candidate = candidate_map_query( hfork->candidate_map, key, NULL );
321 0 : if( FD_LIKELY( candidate ) ) check( hfork, total_stake, candidate, blk->dead, &blk->our_bank_hash );
322 0 : curr = bank_hash_pool_ele( hfork->bank_hash_pool, curr->next );
323 0 : }
324 0 : }
|