Line data Source code
1 : #include "fd_forks.h"
2 :
3 : #include "../../flamenco/runtime/context/fd_exec_slot_ctx.h"
4 : #include "../../flamenco/runtime/fd_acc_mgr.h"
5 : #include "../../flamenco/runtime/fd_borrowed_account.h"
6 : #include "../../flamenco/runtime/fd_runtime.h"
7 : #include "../../flamenco/runtime/program/fd_program_util.h"
8 : #include "../../flamenco/runtime/program/fd_vote_program.h"
9 :
10 : void *
11 0 : fd_forks_new( void * shmem, ulong max, ulong seed ) {
12 :
13 0 : if( FD_UNLIKELY( !shmem ) ) {
14 0 : FD_LOG_WARNING( ( "NULL mem" ) );
15 0 : return NULL;
16 0 : }
17 :
18 0 : if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)shmem, fd_forks_align() ) ) ) {
19 0 : FD_LOG_WARNING( ( "misaligned mem" ) );
20 0 : return NULL;
21 0 : }
22 :
23 0 : ulong footprint = fd_forks_footprint( max );
24 0 : if( FD_UNLIKELY( !footprint ) ) {
25 0 : FD_LOG_WARNING( ( "bad mem" ) );
26 0 : return NULL;
27 0 : }
28 :
29 0 : fd_memset( shmem, 0, footprint );
30 0 : ulong laddr = (ulong)shmem;
31 :
32 0 : laddr = fd_ulong_align_up( laddr, alignof( fd_forks_t ) );
33 0 : laddr += sizeof( fd_forks_t );
34 :
35 0 : laddr = fd_ulong_align_up( laddr, fd_fork_pool_align() );
36 0 : fd_fork_pool_new( (void *)laddr, max );
37 0 : laddr += fd_fork_pool_footprint( max );
38 :
39 0 : laddr = fd_ulong_align_up( laddr, fd_fork_frontier_align() );
40 0 : fd_fork_frontier_new( (void *)laddr, max, seed );
41 0 : laddr += fd_fork_frontier_footprint( max );
42 :
43 0 : return shmem;
44 0 : }
45 :
46 : fd_forks_t *
47 0 : fd_forks_join( void * shforks ) {
48 :
49 0 : if( FD_UNLIKELY( !shforks ) ) {
50 0 : FD_LOG_WARNING( ( "NULL forks" ) );
51 0 : return NULL;
52 0 : }
53 :
54 0 : if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)shforks, fd_forks_align() ) ) ) {
55 0 : FD_LOG_WARNING( ( "misaligned forks" ) );
56 0 : return NULL;
57 0 : }
58 :
59 0 : ulong laddr = (ulong)shforks;
60 0 : fd_forks_t * forks = (void *)laddr;
61 :
62 0 : laddr = fd_ulong_align_up( laddr, alignof( fd_forks_t ) );
63 0 : laddr += sizeof( fd_forks_t );
64 :
65 0 : laddr = fd_ulong_align_up( laddr, fd_fork_pool_align() );
66 0 : forks->pool = fd_fork_pool_join( (void *)laddr );
67 0 : ulong max = fd_fork_pool_max( forks->pool );
68 0 : laddr += fd_fork_pool_footprint( max );
69 :
70 0 : laddr = fd_ulong_align_up( laddr, fd_fork_frontier_align() );
71 0 : forks->frontier = fd_fork_frontier_join( (void *)laddr );
72 0 : laddr += fd_fork_frontier_footprint( max );
73 :
74 0 : return (fd_forks_t *)shforks;
75 0 : }
76 :
77 : void *
78 0 : fd_forks_leave( fd_forks_t const * forks ) {
79 :
80 0 : if( FD_UNLIKELY( !forks ) ) {
81 0 : FD_LOG_WARNING( ( "NULL forks" ) );
82 0 : return NULL;
83 0 : }
84 :
85 0 : return (void *)forks;
86 0 : }
87 :
88 : void *
89 0 : fd_forks_delete( void * forks ) {
90 :
91 0 : if( FD_UNLIKELY( !forks ) ) {
92 0 : FD_LOG_WARNING( ( "NULL forks" ) );
93 0 : return NULL;
94 0 : }
95 :
96 0 : if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)forks, fd_forks_align() ) ) ) {
97 0 : FD_LOG_WARNING( ( "misaligned forks" ) );
98 0 : return NULL;
99 0 : }
100 :
101 0 : return forks;
102 0 : }
103 :
104 : fd_fork_t *
105 0 : fd_forks_init( fd_forks_t * forks, fd_exec_slot_ctx_t const * slot_ctx ) {
106 :
107 0 : if( FD_UNLIKELY( !forks ) ) {
108 0 : FD_LOG_WARNING( ( "NULL forks" ) );
109 0 : return NULL;
110 0 : }
111 :
112 0 : if( FD_UNLIKELY( !slot_ctx ) ) {
113 0 : FD_LOG_WARNING( ( "NULL slot_ctx" ) );
114 0 : return NULL;
115 0 : }
116 :
117 0 : fd_fork_t * fork = fd_fork_pool_ele_acquire( forks->pool );
118 0 : fork->slot = slot_ctx->slot_bank.slot;
119 0 : fork->prev = fd_fork_pool_idx_null( forks->pool );
120 0 : fork->lock = 0;
121 0 : fork->slot_ctx = *slot_ctx; /* this shallow copy is only safe if
122 : the lifetimes of slot_ctx's pointers
123 : are as long as fork */
124 0 : if( FD_UNLIKELY( !fd_fork_frontier_ele_insert( forks->frontier, fork, forks->pool ) ) ) {
125 0 : FD_LOG_WARNING( ( "Failed to insert fork into frontier" ) );
126 0 : }
127 :
128 0 : return fork;
129 0 : }
130 :
131 : fd_fork_t *
132 0 : fd_forks_query( fd_forks_t * forks, ulong slot ) {
133 0 : return fd_fork_frontier_ele_query( forks->frontier, &slot, NULL, forks->pool );
134 0 : }
135 :
136 : fd_fork_t const *
137 0 : fd_forks_query_const( fd_forks_t const * forks, ulong slot ) {
138 0 : return fd_fork_frontier_ele_query_const( forks->frontier, &slot, NULL, forks->pool );
139 0 : }
140 :
141 : // fd_fork_t *
142 : // fd_forks_advance( fd_forks_t * forks,
143 : // fd_fork_t * fork,
144 : // ulong slot,
145 : // fd_acc_mgr_t * acc_mgr,
146 : // fd_blockstore_t * blockstore,
147 : // fd_exec_epoch_ctx_t * epoch_ctx,
148 : // fd_funk_t * funk,
149 : // fd_valloc_t valloc ) {
150 : // // Remove slot ctx from frontier
151 : // fd_fork_t * child = fd_fork_frontier_ele_remove( forks->frontier,
152 : // &fork->slot,
153 : // NULL,
154 : // forks->pool );
155 : // child->slot = curr_slot;
156 : // if( FD_UNLIKELY( fd_fork_frontier_ele_query( forks->frontier,
157 : // &curr_slot,
158 : // NULL,
159 : // forks->pool ) ) ) {
160 : // FD_LOG_ERR( ( "invariant violation: child slot %lu was already in the
161 : // frontier", curr_slot ) );
162 : // }
163 : // fd_fork_frontier_ele_insert( forks->frontier, child, forks->pool );
164 : // FD_TEST( fork == child );
165 :
166 : // // fork is advancing
167 : // FD_LOG_DEBUG(( "new block execution - slot: %lu, parent_slot: %lu", curr_slot, parent_slot ));
168 :
169 : // fork->slot_ctx.slot_bank.prev_slot = fork->slot_ctx.slot_bank.slot;
170 : // fork->slot_ctx.slot_bank.slot = curr_slot;
171 :
172 : // fork->slot_ctx.status_cache = status_cache;
173 : // fd_funk_txn_xid_t xid;
174 :
175 : // fd_memcpy( xid.uc, blockhash.uc, sizeof( fd_funk_txn_xid_t));
176 : // xid.ul[0] = fork->slot_ctx.slot_bank.slot;
177 : // /* push a new transaction on the stack */
178 : // fd_funk_start_write( funk );
179 : // fork->slot_ctx.funk_txn = fd_funk_txn_prepare( funk, fork->slot_ctx.funk_txn, &xid, 1 );
180 : // fd_funk_end_write( funk );
181 :
182 : // int res = fd_runtime_publish_old_txns( &fork->slot_ctx, capture_ctx );
183 : // if( res != FD_RUNTIME_EXECUTE_SUCCESS ) {
184 : // FD_LOG_ERR(( "txn publishing failed" ));
185 : // }
186 : // }
187 :
188 : static void
189 : slot_ctx_restore( ulong slot,
190 : fd_acc_mgr_t * acc_mgr,
191 : fd_blockstore_t * blockstore,
192 : fd_exec_epoch_ctx_t * epoch_ctx,
193 : fd_funk_t * funk,
194 : fd_spad_t * runtime_spad,
195 0 : fd_exec_slot_ctx_t * slot_ctx_out ) {
196 0 : fd_funk_txn_t * txn_map = fd_funk_txn_map( funk, fd_funk_wksp( funk ) );
197 0 : bool block_exists = fd_blockstore_shreds_complete( blockstore, slot );
198 :
199 0 : FD_LOG_DEBUG( ( "Current slot %lu", slot ) );
200 0 : if( !block_exists )
201 0 : FD_LOG_ERR( ( "missing block at slot we're trying to restore" ) );
202 :
203 0 : fd_funk_txn_xid_t xid = { .ul = { slot, slot } };
204 0 : fd_funk_rec_key_t id = fd_runtime_slot_bank_key();
205 0 : fd_funk_txn_t * txn = fd_funk_txn_query( &xid, txn_map );
206 0 : if( !txn ) {
207 0 : memset( xid.uc, 0, sizeof( fd_funk_txn_xid_t ) );
208 0 : xid.ul[0] = slot;
209 0 : txn = fd_funk_txn_query( &xid, txn_map );
210 0 : if( !txn ) {
211 0 : FD_LOG_ERR( ( "missing txn, parent slot %lu", slot ) );
212 0 : }
213 0 : }
214 0 : fd_funk_rec_t const * rec = fd_funk_rec_query_global( funk, txn, &id, NULL );
215 0 : if( rec == NULL ) FD_LOG_ERR( ( "failed to read banks record" ) );
216 0 : void * val = fd_funk_val( rec, fd_funk_wksp( funk ) );
217 :
218 0 : uint magic = *(uint *)val;
219 :
220 0 : fd_bincode_decode_ctx_t decode_ctx = {
221 0 : .data = (uchar *)val + sizeof(uint),
222 0 : .dataend = (uchar *)val + fd_funk_val_sz( rec )
223 0 : };
224 :
225 0 : FD_TEST( slot_ctx_out->magic == FD_EXEC_SLOT_CTX_MAGIC );
226 :
227 0 : slot_ctx_out->funk_txn = txn;
228 0 : slot_ctx_out->acc_mgr = acc_mgr;
229 0 : slot_ctx_out->blockstore = blockstore;
230 0 : slot_ctx_out->epoch_ctx = epoch_ctx;
231 :
232 0 : if( FD_LIKELY( magic==FD_RUNTIME_ENC_BINCODE ) ) {
233 0 : ulong total_sz = 0UL;
234 0 : int err = fd_slot_bank_decode_footprint( &decode_ctx, &total_sz );
235 0 : if( FD_UNLIKELY( err != FD_BINCODE_SUCCESS ) ) {
236 0 : FD_LOG_ERR( ( "failed to decode banks record" ) );
237 0 : }
238 :
239 0 : uchar * mem = fd_spad_alloc( runtime_spad, fd_slot_bank_align(), total_sz );
240 0 : if( FD_UNLIKELY( !mem ) ) {
241 0 : FD_LOG_ERR( ( "failed to allocate memory for slot bank" ) );
242 0 : }
243 :
244 0 : fd_slot_bank_t * slot_bank = fd_slot_bank_decode( mem, &decode_ctx );
245 :
246 0 : fd_memcpy( &slot_ctx_out->slot_bank, slot_bank, sizeof(fd_slot_bank_t) );
247 0 : } else {
248 0 : FD_LOG_ERR( ( "failed to read banks record: invalid magic number" ) );
249 0 : }
250 0 : FD_TEST( !fd_runtime_sysvar_cache_load( slot_ctx_out, runtime_spad ) );
251 :
252 : // TODO how do i get this info, ignoring rewards for now
253 : // slot_ctx_out->epoch_reward_status = ???
254 :
255 : // signature_cnt, account_delta_hash, prev_banks_hash are used for the banks
256 : // hash calculation and not needed when restoring parent
257 :
258 0 : FD_LOG_NOTICE( ( "recovered slot_bank for slot=%lu banks_hash=%s poh_hash %s",
259 0 : slot_ctx_out->slot_bank.slot,
260 0 : FD_BASE58_ENC_32_ALLOCA( slot_ctx_out->slot_bank.banks_hash.hash ),
261 0 : FD_BASE58_ENC_32_ALLOCA( slot_ctx_out->slot_bank.poh.hash ) ) );
262 :
263 : /* Prepare bank for next slot */
264 0 : slot_ctx_out->slot_bank.slot = slot;
265 0 : slot_ctx_out->slot_bank.collected_execution_fees = 0;
266 0 : slot_ctx_out->slot_bank.collected_priority_fees = 0;
267 0 : slot_ctx_out->slot_bank.collected_rent = 0;
268 :
269 : /* FIXME epoch boundary stuff when replaying */
270 : // fd_features_restore( slot_ctx );
271 : // fd_runtime_update_leaders( slot_ctx, slot_ctx->slot_bank.slot );
272 : // fd_calculate_epoch_accounts_hash_values( slot_ctx );
273 0 : }
274 :
275 : fd_fork_t *
276 : fd_forks_prepare( fd_forks_t const * forks,
277 : ulong parent_slot,
278 : fd_acc_mgr_t * acc_mgr,
279 : fd_blockstore_t * blockstore,
280 : fd_exec_epoch_ctx_t * epoch_ctx,
281 : fd_funk_t * funk,
282 0 : fd_spad_t * runtime_spad ) {
283 :
284 : /* Check the parent block is present in the blockstore and executed. */
285 :
286 0 : if( FD_UNLIKELY( !fd_blockstore_shreds_complete( blockstore, parent_slot ) ) ) {
287 0 : FD_LOG_WARNING( ( "fd_forks_prepare missing parent_slot %lu", parent_slot ) );
288 0 : }
289 :
290 : /* Query for parent_slot in the frontier. */
291 :
292 0 : fd_fork_t * fork = fd_fork_frontier_ele_query( forks->frontier, &parent_slot, NULL, forks->pool );
293 :
294 : /* If the parent block is both present and executed, but isn't in the
295 : frontier, that means this block is starting a new fork and needs to
296 : be added to the frontier. This requires recovering the slot_ctx
297 : as of that parent_slot by executing a funky rollback. */
298 :
299 0 : if( FD_UNLIKELY( !fork ) ) {
300 :
301 : /* Alloc a new slot_ctx */
302 :
303 0 : fork = fd_fork_pool_ele_acquire( forks->pool );
304 0 : fork->prev = fd_fork_pool_idx_null( forks->pool );
305 0 : fork->slot = parent_slot;
306 0 : fork->lock = 1;
307 :
308 : /* Format and join the slot_ctx */
309 :
310 0 : fd_exec_slot_ctx_t * slot_ctx = fd_exec_slot_ctx_join( fd_exec_slot_ctx_new( &fork->slot_ctx, runtime_spad ) );
311 0 : if( FD_UNLIKELY( !slot_ctx ) ) {
312 0 : FD_LOG_ERR( ( "failed to new and join slot_ctx" ) );
313 0 : }
314 :
315 : /* Restore and decode w/ funk */
316 :
317 0 : slot_ctx_restore( fork->slot, acc_mgr, blockstore, epoch_ctx, funk, runtime_spad, slot_ctx );
318 :
319 : /* Add to frontier */
320 :
321 0 : fd_fork_frontier_ele_insert( forks->frontier, fork, forks->pool );
322 0 : }
323 :
324 0 : return fork;
325 0 : }
326 :
327 : void
328 : fd_forks_update( fd_forks_t * forks,
329 : fd_epoch_t * epoch,
330 : fd_funk_t * funk,
331 : fd_ghost_t * ghost,
332 0 : ulong slot ) {
333 0 : fd_fork_t * fork = fd_fork_frontier_ele_query( forks->frontier, &slot, NULL, forks->pool );
334 0 : fd_funk_txn_t * txn = fork->slot_ctx.funk_txn;
335 :
336 0 : fd_voter_t * epoch_voters = fd_epoch_voters( epoch );
337 0 : for( ulong i = 0; i < fd_epoch_voters_slot_cnt( epoch_voters ); i++ ) {
338 0 : if( FD_LIKELY( fd_epoch_voters_key_inval( epoch_voters[i].key ) ) ) continue /* most slots are empty */;
339 :
340 : /* TODO we can optimize this funk query to only check through the
341 : last slot on this fork this function was called on. currently
342 : rec_query_global traverses all the way back to the root. */
343 :
344 0 : fd_voter_t * voter = &epoch_voters[i];
345 0 : fd_voter_state_t const * state = fd_voter_state( funk, txn, &voter->rec );
346 :
347 : /* Only process votes for slots >= root. Ghost requires vote slot
348 : to already exist in the ghost tree. */
349 :
350 0 : ulong vote = fd_voter_state_vote( state );
351 0 : if( FD_LIKELY( vote != FD_SLOT_NULL && vote >= fd_ghost_root( ghost )->slot ) ) {
352 0 : fd_ghost_replay_vote( ghost, voter, vote );
353 :
354 : /* Check if it has crossed the equivocation safety and optimistic confirmation thresholds. */
355 :
356 0 : fd_ghost_node_t const * node = fd_ghost_query( ghost, vote );
357 :
358 : /* Error if the node's vote slot is not in ghost. This is an
359 : invariant violation, because we know their tower must be on the
360 : same fork as this current one that we're processing, and so by
361 : definition their vote slot must be in our ghost (ie. we can't
362 : have rooted past it or be on a different fork). */
363 :
364 0 : if( FD_UNLIKELY( !node ) ) FD_LOG_ERR(( "[%s] voter %s's vote slot %lu was not in ghost", __func__, FD_BASE58_ENC_32_ALLOCA(&voter->key), vote ));
365 :
366 0 : fd_ghost_replay_vote( ghost, voter, vote );
367 0 : double pct = (double)node->replay_stake / (double)epoch->total_stake;
368 0 : if( FD_UNLIKELY( pct > FD_CONFIRMED_PCT ) ) forks->confirmed = fd_ulong_max( forks->confirmed, node->slot );
369 0 : }
370 :
371 : /* Check if this voter's root >= ghost root. We can't process
372 : other voters' roots that precede the ghost root. */
373 :
374 0 : ulong root = fd_voter_state_root( state );
375 0 : if( FD_LIKELY( root != FD_SLOT_NULL && root >= fd_ghost_root( ghost )->slot ) ) {
376 0 : fd_ghost_node_t const * node = fd_ghost_query( ghost, root );
377 :
378 : /* Error if the node's root slot is not in ghost. This is an
379 : invariant violation, because we know their tower must be on the
380 : same fork as this current one that we're processing, and so by
381 : definition their root slot must be in our ghost (ie. we can't
382 : have rooted past it or be on a different fork). */
383 :
384 0 : if( FD_UNLIKELY( !node ) ) FD_LOG_ERR(( "[%s] voter %s's root slot %lu was not in ghost", __func__, FD_BASE58_ENC_32_ALLOCA(&voter->key), root ));
385 :
386 0 : fd_ghost_rooted_vote( ghost, voter, root );
387 0 : double pct = (double)node->rooted_stake / (double)epoch->total_stake;
388 0 : if( FD_UNLIKELY( pct > FD_FINALIZED_PCT ) ) forks->finalized = fd_ulong_max( forks->finalized, node->slot );
389 0 : }
390 0 : }
391 0 : }
392 :
393 : void
394 0 : fd_forks_publish( fd_forks_t * forks, ulong slot, fd_ghost_t const * ghost ) {
395 0 : fd_fork_t * tail = NULL;
396 0 : fd_fork_t * curr = NULL;
397 :
398 0 : for( fd_fork_frontier_iter_t iter = fd_fork_frontier_iter_init( forks->frontier, forks->pool );
399 0 : !fd_fork_frontier_iter_done( iter, forks->frontier, forks->pool );
400 0 : iter = fd_fork_frontier_iter_next( iter, forks->frontier, forks->pool ) ) {
401 0 : fd_fork_t * fork = fd_fork_frontier_iter_ele( iter, forks->frontier, forks->pool );
402 :
403 : /* Prune any forks not in the ancestry from root.
404 :
405 : Optimize for unlikely because there is usually just one fork. */
406 :
407 0 : int stale = fork->slot < slot || !fd_ghost_is_ancestor( ghost, slot, fork->slot );
408 0 : if( FD_UNLIKELY( !fork->lock && stale ) ) {
409 0 : FD_LOG_NOTICE( ( "adding %lu to prune. root %lu", fork->slot, slot ) );
410 0 : if( FD_LIKELY( !curr ) ) {
411 0 : tail = fork;
412 0 : curr = fork;
413 0 : } else {
414 0 : curr->prev = fd_fork_pool_idx( forks->pool, fork );
415 0 : curr = fd_fork_pool_ele( forks->pool, curr->prev );
416 0 : }
417 0 : }
418 0 : }
419 :
420 0 : while( FD_UNLIKELY( tail ) ) {
421 0 : fd_fork_t * fork = fd_fork_frontier_ele_query( forks->frontier,
422 0 : &tail->slot,
423 0 : NULL,
424 0 : forks->pool );
425 0 : if( FD_UNLIKELY( !fd_exec_slot_ctx_delete( fd_exec_slot_ctx_leave( &fork->slot_ctx ) ) ) ) {
426 0 : FD_LOG_ERR( ( "could not delete fork slot ctx" ) );
427 0 : }
428 0 : ulong remove = fd_fork_frontier_idx_remove( forks->frontier,
429 0 : &tail->slot,
430 0 : fd_fork_pool_idx_null( forks->pool ),
431 0 : forks->pool );
432 0 : #if FD_FORKS_USE_HANDHOLDING
433 0 : if( FD_UNLIKELY( remove == fd_fork_pool_idx_null( forks->pool ) ) ) {
434 0 : FD_LOG_ERR( ( "failed to remove fork we added to prune." ) );
435 0 : }
436 0 : #endif
437 :
438 : /* pool_idx_release cannot fail given we just removed this from the
439 : frontier directly above. */
440 0 : fd_fork_pool_idx_release( forks->pool, remove );
441 0 : tail = fd_ptr_if( tail->prev != fd_fork_pool_idx_null( forks->pool ),
442 0 : fd_fork_pool_ele( forks->pool, tail->prev ),
443 0 : NULL );
444 0 : }
445 0 : }
446 :
447 : #include <stdio.h>
448 :
449 : void
450 0 : fd_forks_print( fd_forks_t const * forks ) {
451 0 : FD_LOG_NOTICE( ( "\n\n[Forks]" ) );
452 0 : for( fd_fork_frontier_iter_t iter = fd_fork_frontier_iter_init( forks->frontier, forks->pool );
453 0 : !fd_fork_frontier_iter_done( iter, forks->frontier, forks->pool );
454 0 : iter = fd_fork_frontier_iter_next( iter, forks->frontier, forks->pool ) ) {
455 0 : printf( "%lu\n", fd_fork_frontier_iter_ele_const( iter, forks->frontier, forks->pool )->slot );
456 0 : }
457 0 : printf( "\n" );
458 0 : }
|