Line data Source code
1 : #include "fd_runtime.h"
2 : #include "fd_acc_mgr.h"
3 : #include "fd_runtime_err.h"
4 : #include "fd_runtime_init.h"
5 : #include "fd_pubkey_utils.h"
6 :
7 : #include "fd_executor.h"
8 : #include "fd_account.h"
9 : #include "fd_hashes.h"
10 : #include "fd_txncache.h"
11 : #include "sysvar/fd_sysvar_cache.h"
12 : #include "sysvar/fd_sysvar_clock.h"
13 : #include "sysvar/fd_sysvar_epoch_schedule.h"
14 : #include "sysvar/fd_sysvar_recent_hashes.h"
15 : #include "sysvar/fd_sysvar_stake_history.h"
16 : #include "sysvar/fd_sysvar.h"
17 : #include "../../ballet/base58/fd_base58.h"
18 : #include "../../ballet/txn/fd_txn.h"
19 : #include "../../ballet/bmtree/fd_bmtree.h"
20 : #include "../../ballet/bmtree/fd_wbmtree.h"
21 :
22 : #include "../stakes/fd_stakes.h"
23 : #include "../rewards/fd_rewards.h"
24 :
25 : #include "context/fd_exec_txn_ctx.h"
26 : #include "context/fd_exec_instr_ctx.h"
27 : #include "info/fd_microblock_batch_info.h"
28 : #include "info/fd_microblock_info.h"
29 :
30 : #include "program/fd_stake_program.h"
31 : #include "program/fd_builtin_programs.h"
32 : #include "program/fd_system_program.h"
33 : #include "program/fd_vote_program.h"
34 : #include "program/fd_bpf_program_util.h"
35 : #include "program/fd_bpf_loader_program.h"
36 : #include "program/fd_compute_budget_program.h"
37 :
38 : #include "sysvar/fd_sysvar_clock.h"
39 : #include "sysvar/fd_sysvar_fees.h"
40 : #include "sysvar/fd_sysvar_last_restart_slot.h"
41 : #include "sysvar/fd_sysvar_recent_hashes.h"
42 : #include "sysvar/fd_sysvar_rent.h"
43 : #include "sysvar/fd_sysvar_slot_hashes.h"
44 : #include "sysvar/fd_sysvar_slot_history.h"
45 :
46 : #include "../nanopb/pb_decode.h"
47 : #include "../nanopb/pb_encode.h"
48 : #include "../types/fd_solana_block.pb.h"
49 :
50 : #include "fd_system_ids.h"
51 : #include "../vm/fd_vm.h"
52 : #include "fd_blockstore.h"
53 : #include "../../ballet/pack/fd_pack.h"
54 : #include "../fd_rwlock.h"
55 :
56 : #include <stdio.h>
57 : #include <ctype.h>
58 : #include <unistd.h>
59 : #include <sys/stat.h>
60 : #include <sys/types.h>
61 : #include <errno.h>
62 : #include <fcntl.h>
63 :
64 : /******************************************************************************/
65 : /* Public Runtime Helpers */
66 : /******************************************************************************/
67 :
68 : void
69 0 : fd_runtime_update_leaders( fd_exec_slot_ctx_t * slot_ctx, ulong slot ) {
70 0 : FD_SCRATCH_SCOPE_BEGIN {
71 0 : fd_epoch_schedule_t schedule = slot_ctx->epoch_ctx->epoch_bank.epoch_schedule;
72 :
73 0 : FD_LOG_INFO(( "schedule->slots_per_epoch = %lu", schedule.slots_per_epoch ));
74 0 : FD_LOG_INFO(( "schedule->leader_schedule_slot_offset = %lu", schedule.leader_schedule_slot_offset ));
75 0 : FD_LOG_INFO(( "schedule->warmup = %d", schedule.warmup ));
76 0 : FD_LOG_INFO(( "schedule->first_normal_epoch = %lu", schedule.first_normal_epoch ));
77 0 : FD_LOG_INFO(( "schedule->first_normal_slot = %lu", schedule.first_normal_slot ));
78 :
79 0 : fd_vote_accounts_t const * epoch_vaccs = &slot_ctx->slot_bank.epoch_stakes;
80 :
81 0 : ulong epoch = fd_slot_to_epoch( &schedule, slot, NULL );
82 0 : ulong slot0 = fd_epoch_slot0( &schedule, epoch );
83 0 : ulong slot_cnt = fd_epoch_slot_cnt( &schedule, epoch );
84 :
85 0 : FD_LOG_INFO(( "starting rent list init" ));
86 :
87 0 : fd_acc_mgr_set_slots_per_epoch( slot_ctx, fd_epoch_slot_cnt(&schedule, epoch) );
88 0 : FD_LOG_INFO(( "rent list init done" ));
89 :
90 0 : ulong vote_acc_cnt = fd_vote_accounts_pair_t_map_size( epoch_vaccs->vote_accounts_pool, epoch_vaccs->vote_accounts_root );
91 0 : fd_stake_weight_t * epoch_weights = fd_scratch_alloc( alignof(fd_stake_weight_t), vote_acc_cnt * sizeof(fd_stake_weight_t) );
92 0 : if( FD_UNLIKELY( !epoch_weights ) ) {
93 0 : FD_LOG_ERR(("fd_scratch_alloc() failed"));
94 0 : }
95 :
96 0 : ulong stake_weight_cnt = fd_stake_weights_by_node(epoch_vaccs, epoch_weights);
97 :
98 0 : if( FD_UNLIKELY( stake_weight_cnt == ULONG_MAX ) ) {
99 0 : FD_LOG_ERR(("fd_stake_weights_by_node() failed"));
100 0 : }
101 :
102 : /* Derive leader schedule */
103 :
104 0 : FD_LOG_INFO(( "stake_weight_cnt=%lu slot_cnt=%lu", stake_weight_cnt, slot_cnt ));
105 0 : ulong epoch_leaders_footprint = fd_epoch_leaders_footprint( stake_weight_cnt, slot_cnt );
106 0 : FD_LOG_INFO(( "epoch_leaders_footprint=%lu", epoch_leaders_footprint ));
107 0 : if( FD_LIKELY( epoch_leaders_footprint ) ) {
108 0 : FD_TEST( stake_weight_cnt <= MAX_PUB_CNT );
109 0 : FD_TEST( slot_cnt <= MAX_SLOTS_CNT );
110 0 : void * epoch_leaders_mem = fd_exec_epoch_ctx_leaders( slot_ctx->epoch_ctx );
111 0 : fd_epoch_leaders_t * leaders = fd_epoch_leaders_join( fd_epoch_leaders_new( epoch_leaders_mem,
112 0 : epoch,
113 0 : slot0,
114 0 : slot_cnt,
115 0 : stake_weight_cnt,
116 0 : epoch_weights,
117 0 : 0UL ) );
118 0 : FD_TEST( leaders );
119 0 : }
120 0 : } FD_SCRATCH_SCOPE_END;
121 0 : }
122 :
123 : /* Loads the sysvar cache. Expects acc_mgr, funk_txn, valloc to be non-NULL and valid. */
124 : int
125 0 : fd_runtime_sysvar_cache_load( fd_exec_slot_ctx_t * slot_ctx ) {
126 0 : if( FD_UNLIKELY( !slot_ctx->acc_mgr ) ) {
127 0 : return -1;
128 0 : }
129 :
130 0 : fd_sysvar_cache_restore( slot_ctx->sysvar_cache, slot_ctx->acc_mgr, slot_ctx->funk_txn );
131 :
132 0 : return FD_RUNTIME_EXECUTE_SUCCESS;
133 0 : }
134 :
135 : /******************************************************************************/
136 : /* Various Private Runtime Helpers */
137 : /******************************************************************************/
138 :
139 : /* NOTE: Rent functions are not being cleaned up due to the fact that they will
140 : be entirely torn out of the codebase very soon. */
141 :
142 : static void
143 0 : fd_runtime_collect_rent_for_slot( fd_exec_slot_ctx_t * slot_ctx, ulong off, ulong epoch ) {
144 0 : fd_funk_txn_t * txn = slot_ctx->funk_txn;
145 0 : fd_acc_mgr_t * acc_mgr = slot_ctx->acc_mgr;
146 0 : fd_funk_t * funk = slot_ctx->acc_mgr->funk;
147 0 : fd_wksp_t * wksp = fd_funk_wksp( funk );
148 :
149 0 : fd_funk_partvec_t * partvec = fd_funk_get_partvec( funk, wksp );
150 :
151 0 : fd_funk_rec_t * rec_map = fd_funk_rec_map( funk, wksp );
152 :
153 0 : for( fd_funk_rec_t const *rec_ro = fd_funk_part_head( partvec, (uint)off, rec_map );
154 0 : rec_ro != NULL;
155 0 : rec_ro = fd_funk_part_next( rec_ro, rec_map ) ) {
156 :
157 0 : if ( FD_UNLIKELY( !fd_funk_key_is_acc( rec_ro->pair.key ) ) ) {
158 0 : continue;
159 0 : }
160 :
161 0 : fd_pubkey_t const *key = fd_type_pun_const( rec_ro->pair.key[0].uc );
162 0 : FD_BORROWED_ACCOUNT_DECL( rec );
163 0 : int err = fd_acc_mgr_view( acc_mgr, txn, key, rec );
164 :
165 : /* Account might not exist anymore in the current world */
166 0 : if( err==FD_ACC_MGR_ERR_UNKNOWN_ACCOUNT ) {
167 0 : continue;
168 0 : }
169 0 : if( FD_UNLIKELY( err != FD_ACC_MGR_SUCCESS ) ) {
170 0 : FD_LOG_WARNING(( "fd_runtime_collect_rent: fd_acc_mgr_view failed (%d)", err ));
171 0 : continue;
172 0 : }
173 :
174 : /* Check if latest version in this transaction */
175 0 : if( rec_ro!=rec->const_rec ) {
176 0 : continue;
177 0 : }
178 :
179 : /* Upgrade read-only handle to writable */
180 0 : err = fd_acc_mgr_modify(
181 0 : acc_mgr, txn, key,
182 0 : /* do_create */ 0,
183 0 : /* min_data_sz */ 0UL,
184 0 : rec);
185 0 : if( FD_UNLIKELY( err!=FD_ACC_MGR_SUCCESS ) ) {
186 0 : FD_LOG_WARNING(( "fd_runtime_collect_rent_range: fd_acc_mgr_modify failed (%d)", err ));
187 0 : continue;
188 0 : }
189 :
190 : /* Actually invoke rent collection */
191 0 : slot_ctx->slot_bank.collected_rent += fd_runtime_collect_rent_from_account( slot_ctx, rec->meta, key, epoch );
192 0 : }
193 0 : }
194 :
195 : /* Yes, this is a real function that exists in Solana. Yes, I am ashamed I have had to replicate it. */
196 : // https://github.com/firedancer-io/solana/blob/d8292b427adf8367d87068a3a88f6fd3ed8916a5/runtime/src/bank.rs#L5618
197 : static ulong
198 0 : fd_runtime_slot_count_in_two_day( ulong ticks_per_slot ) {
199 0 : return 2UL * FD_SYSVAR_CLOCK_DEFAULT_TICKS_PER_SECOND * 86400UL /* seconds per day */ / ticks_per_slot;
200 0 : }
201 :
202 : // https://github.com/firedancer-io/solana/blob/d8292b427adf8367d87068a3a88f6fd3ed8916a5/runtime/src/bank.rs#L5594
203 : static int
204 0 : fd_runtime_use_multi_epoch_collection( fd_exec_slot_ctx_t const * slot_ctx, ulong slot ) {
205 0 : fd_epoch_bank_t const * epoch_bank = fd_exec_epoch_ctx_epoch_bank( slot_ctx->epoch_ctx );
206 0 : fd_epoch_schedule_t const * schedule = &epoch_bank->epoch_schedule;
207 :
208 0 : ulong off;
209 0 : ulong epoch = fd_slot_to_epoch( schedule, slot, &off );
210 0 : ulong slots_per_normal_epoch = fd_epoch_slot_cnt( schedule, schedule->first_normal_epoch );
211 :
212 0 : ulong slot_count_in_two_day = fd_runtime_slot_count_in_two_day( epoch_bank->ticks_per_slot );
213 :
214 0 : int use_multi_epoch_collection = ( epoch >= schedule->first_normal_epoch )
215 0 : && ( slots_per_normal_epoch < slot_count_in_two_day );
216 :
217 0 : return use_multi_epoch_collection;
218 0 : }
219 :
220 : static ulong
221 0 : fd_runtime_num_rent_partitions( fd_exec_slot_ctx_t const * slot_ctx, ulong slot ) {
222 0 : fd_epoch_bank_t const * epoch_bank = fd_exec_epoch_ctx_epoch_bank( slot_ctx->epoch_ctx );
223 0 : fd_epoch_schedule_t const * schedule = &epoch_bank->epoch_schedule;
224 :
225 0 : ulong off;
226 0 : ulong epoch = fd_slot_to_epoch( schedule, slot, &off );
227 0 : ulong slots_per_epoch = fd_epoch_slot_cnt( schedule, epoch );
228 :
229 0 : ulong slot_count_in_two_day = fd_runtime_slot_count_in_two_day( epoch_bank->ticks_per_slot );
230 :
231 0 : int use_multi_epoch_collection = fd_runtime_use_multi_epoch_collection( slot_ctx, slot );
232 :
233 0 : if( use_multi_epoch_collection ) {
234 0 : ulong epochs_in_cycle = slot_count_in_two_day / slots_per_epoch;
235 0 : return slots_per_epoch * epochs_in_cycle;
236 0 : } else {
237 0 : return slots_per_epoch;
238 0 : }
239 0 : }
240 :
241 : // https://github.com/anza-xyz/agave/blob/2bdcc838c18d262637524274cbb2275824eb97b8/accounts-db/src/accounts_partition.rs#L30
242 : static ulong
243 0 : fd_runtime_get_rent_partition( fd_exec_slot_ctx_t const * slot_ctx, ulong slot ) {
244 0 : int use_multi_epoch_collection = fd_runtime_use_multi_epoch_collection( slot_ctx, slot );
245 :
246 0 : fd_epoch_bank_t const * epoch_bank = fd_exec_epoch_ctx_epoch_bank( slot_ctx->epoch_ctx );
247 0 : fd_epoch_schedule_t const * schedule = &epoch_bank->epoch_schedule;
248 :
249 0 : ulong off;
250 0 : ulong epoch = fd_slot_to_epoch( schedule, slot, &off );
251 0 : ulong slot_count_per_epoch = fd_epoch_slot_cnt( schedule, epoch );
252 0 : ulong slot_count_in_two_day = fd_runtime_slot_count_in_two_day( epoch_bank->ticks_per_slot );
253 :
254 0 : ulong base_epoch;
255 0 : ulong epoch_count_in_cycle;
256 0 : if( use_multi_epoch_collection ) {
257 0 : base_epoch = schedule->first_normal_epoch;
258 0 : epoch_count_in_cycle = slot_count_in_two_day / slot_count_per_epoch;
259 0 : } else {
260 0 : base_epoch = 0;
261 0 : epoch_count_in_cycle = 1;
262 0 : }
263 :
264 0 : ulong epoch_offset = epoch - base_epoch;
265 0 : ulong epoch_index_in_cycle = epoch_offset % epoch_count_in_cycle;
266 0 : return off + ( epoch_index_in_cycle * slot_count_per_epoch );
267 0 : }
268 :
269 : static ulong
270 : fd_runtime_calculate_rent_burn( ulong rent_collected,
271 0 : fd_rent_t const * rent ) {
272 0 : return (rent_collected * rent->burn_percent) / 100UL;
273 0 : }
274 :
275 : static void
276 0 : fd_runtime_collect_rent( fd_exec_slot_ctx_t * slot_ctx ) {
277 : // Bank::collect_rent_eagerly (enter)
278 :
279 0 : fd_epoch_bank_t const * epoch_bank = fd_exec_epoch_ctx_epoch_bank( slot_ctx->epoch_ctx );
280 0 : fd_epoch_schedule_t const * schedule = &epoch_bank->epoch_schedule;
281 :
282 : // Bank::rent_collection_partitions (enter)
283 : // Bank::variable_cycle_partitions (enter)
284 : // Bank::variable_cycle_partitions_between_slots (enter)
285 :
286 0 : ulong slot0 = slot_ctx->slot_bank.prev_slot;
287 0 : ulong slot1 = slot_ctx->slot_bank.slot;
288 :
289 : /* For genesis, we collect rent for slot 0. */
290 0 : if (slot1 == 0) {
291 0 : ulong s = slot1;
292 0 : ulong off;
293 0 : ulong epoch = fd_slot_to_epoch(schedule, s, &off);
294 :
295 : /* FIXME: This will not necessarily support warmup_epochs */
296 0 : ulong num_partitions = fd_runtime_num_rent_partitions( slot_ctx, s );
297 : /* Reconstruct rent lists if the number of slots per epoch changes */
298 0 : fd_acc_mgr_set_slots_per_epoch( slot_ctx, num_partitions );
299 0 : fd_runtime_collect_rent_for_slot( slot_ctx, fd_runtime_get_rent_partition( slot_ctx, s ), epoch );
300 0 : return;
301 0 : }
302 :
303 0 : FD_TEST(slot0 <= slot1);
304 :
305 0 : for( ulong s = slot0 + 1; s <= slot1; ++s ) {
306 0 : ulong off;
307 0 : ulong epoch = fd_slot_to_epoch(schedule, s, &off);
308 :
309 : /* FIXME: This will not necessarily support warmup_epochs */
310 0 : ulong num_partitions = fd_runtime_num_rent_partitions( slot_ctx, s );
311 : /* Reconstruct rent lists if the number of slots per epoch changes */
312 0 : fd_acc_mgr_set_slots_per_epoch( slot_ctx, num_partitions );
313 0 : fd_runtime_collect_rent_for_slot( slot_ctx, fd_runtime_get_rent_partition( slot_ctx, s ), epoch );
314 0 : }
315 :
316 : // FD_LOG_DEBUG(("rent collected - lamports: %lu", slot_ctx->slot_bank.collected_rent));
317 0 : }
318 :
319 :
320 : /* fee to be deposited should be > 0
321 : Returns 0 if validation succeeds
322 : Returns the amount to burn(==fee) on failure */
323 : FD_FN_PURE static ulong
324 : fd_runtime_validate_fee_collector( fd_exec_slot_ctx_t const * slot_ctx,
325 : fd_borrowed_account_t const * collector,
326 0 : ulong fee ) {
327 0 : if( FD_UNLIKELY( fee<=0UL ) ) {
328 0 : FD_LOG_ERR(( "expected fee(%lu) to be >0UL", fee ));
329 0 : }
330 :
331 0 : if( FD_UNLIKELY( memcmp( collector->const_meta->info.owner, fd_solana_system_program_id.key, sizeof(collector->const_meta->info.owner) ) ) ) {
332 0 : FD_BASE58_ENCODE_32_BYTES( collector->pubkey->key, _out_key );
333 0 : FD_LOG_WARNING(( "cannot pay a non-system-program owned account (%s)", _out_key ));
334 0 : return fee;
335 0 : }
336 :
337 : /* https://github.com/anza-xyz/agave/blob/v1.18.23/runtime/src/bank/fee_distribution.rs#L111
338 : https://github.com/anza-xyz/agave/blob/v1.18.23/runtime/src/accounts/account_rent_state.rs#L39
339 : In agave's fee deposit code, rent state transition check logic is as follows:
340 : The transition is NOT allowed iff
341 : === BEGIN
342 : the post deposit account is rent paying AND the pre deposit account is not rent paying
343 : OR
344 : the post deposit account is rent paying AND the pre deposit account is rent paying AND !(post_data_size == pre_data_size && post_lamports <= pre_lamports)
345 : === END
346 : post_data_size == pre_data_size is always true during fee deposit.
347 : However, post_lamports > pre_lamports because we are paying a >0 amount.
348 : So, the above reduces down to
349 : === BEGIN
350 : the post deposit account is rent paying AND the pre deposit account is not rent paying
351 : OR
352 : the post deposit account is rent paying AND the pre deposit account is rent paying AND TRUE
353 : === END
354 : This is equivalent to checking that the post deposit account is rent paying.
355 : An account is rent paying if the post deposit balance is >0 AND it's not rent exempt.
356 : We already know that the post deposit balance is >0 because we are paying a >0 amount.
357 : So TLDR we just check if the account is rent exempt.
358 : */
359 0 : ulong minbal = fd_rent_exempt_minimum_balance( fd_sysvar_cache_rent( slot_ctx->sysvar_cache ), collector->const_meta->dlen );
360 0 : if( FD_UNLIKELY( collector->const_meta->info.lamports + fee < minbal ) ) {
361 0 : FD_BASE58_ENCODE_32_BYTES( collector->pubkey->key, _out_key );
362 0 : FD_LOG_WARNING(("cannot pay a rent paying account (%s)", _out_key ));
363 0 : return fee;
364 0 : }
365 :
366 0 : return 0UL;
367 0 : }
368 :
369 : struct fd_validator_stake_pair {
370 : fd_pubkey_t pubkey;
371 : ulong stake;
372 : };
373 : typedef struct fd_validator_stake_pair fd_validator_stake_pair_t;
374 :
375 : static int
376 : fd_validator_stake_pair_compare_before( fd_validator_stake_pair_t const * a,
377 0 : fd_validator_stake_pair_t const * b ) {
378 0 : if( a->stake > b->stake ) {
379 0 : return 1;
380 0 : } else if (a->stake == b->stake) {
381 0 : return memcmp(&a->pubkey, &b->pubkey, sizeof(fd_pubkey_t)) > 0;
382 0 : }
383 0 : else
384 0 : { // a->stake < b->stake
385 0 : return 0;
386 0 : }
387 0 : }
388 :
389 : #define SORT_NAME sort_validator_stake_pair
390 0 : #define SORT_KEY_T fd_validator_stake_pair_t
391 0 : #define SORT_BEFORE(a, b) (fd_validator_stake_pair_compare_before((fd_validator_stake_pair_t const *)&a, (fd_validator_stake_pair_t const *)&b))
392 : #include "../../util/tmpl/fd_sort.c"
393 : #undef SORT_NAME
394 : #undef SORT_KEY_T
395 : #undef SORT_BERFORE
396 :
397 : static void
398 : fd_runtime_distribute_rent_to_validators( fd_exec_slot_ctx_t * slot_ctx,
399 0 : ulong rent_to_be_distributed ) {
400 :
401 0 : FD_SCRATCH_SCOPE_BEGIN {
402 0 : ulong total_staked = 0;
403 :
404 0 : fd_epoch_bank_t * epoch_bank = fd_exec_epoch_ctx_epoch_bank( slot_ctx->epoch_ctx );
405 0 : fd_vote_accounts_pair_t_mapnode_t *vote_accounts_pool = epoch_bank->stakes.vote_accounts.vote_accounts_pool;
406 0 : fd_vote_accounts_pair_t_mapnode_t *vote_accounts_root = epoch_bank->stakes.vote_accounts.vote_accounts_root;
407 :
408 0 : ulong num_validator_stakes = fd_vote_accounts_pair_t_map_size( vote_accounts_pool, vote_accounts_root );
409 0 : fd_validator_stake_pair_t * validator_stakes = fd_scratch_alloc( 8UL, sizeof(fd_validator_stake_pair_t) * num_validator_stakes );
410 0 : ulong i = 0;
411 :
412 0 : for( fd_vote_accounts_pair_t_mapnode_t *n = fd_vote_accounts_pair_t_map_minimum( vote_accounts_pool, vote_accounts_root );
413 0 : n;
414 0 : n = fd_vote_accounts_pair_t_map_successor( vote_accounts_pool, n ), i++) {
415 :
416 0 : validator_stakes[i].pubkey = n->elem.value.node_pubkey;
417 0 : validator_stakes[i].stake = n->elem.stake;
418 :
419 0 : total_staked += n->elem.stake;
420 :
421 0 : }
422 :
423 0 : sort_validator_stake_pair_inplace(validator_stakes, num_validator_stakes);
424 :
425 0 : ulong validate_fee_collector_account = FD_FEATURE_ACTIVE(slot_ctx, validate_fee_collector_account);
426 :
427 0 : ulong rent_distributed_in_initial_round = 0;
428 :
429 : // We now do distribution, reusing the validator stakes array for the rent stares
430 0 : for( i = 0; i < num_validator_stakes; i++ ) {
431 0 : ulong staked = validator_stakes[i].stake;
432 0 : ulong rent_share = (ulong)(((uint128)staked * (uint128)rent_to_be_distributed) / (uint128)total_staked);
433 :
434 0 : validator_stakes[i].stake = rent_share;
435 0 : rent_distributed_in_initial_round += rent_share;
436 0 : }
437 :
438 0 : ulong leftover_lamports = rent_to_be_distributed - rent_distributed_in_initial_round;
439 :
440 0 : for( i = 0; i < num_validator_stakes; i++ ) {
441 0 : if (leftover_lamports == 0) {
442 0 : break;
443 0 : }
444 :
445 : /* Not using saturating sub because Agave doesn't.
446 : https://github.com/anza-xyz/agave/blob/c88e6df566c5c17d71e9574785755683a8fb033a/runtime/src/bank/fee_distribution.rs#L207
447 : */
448 0 : leftover_lamports--;
449 0 : validator_stakes[i].stake++;
450 0 : }
451 :
452 0 : for( i = 0; i < num_validator_stakes; i++ ) {
453 0 : ulong rent_to_be_paid = validator_stakes[i].stake;
454 :
455 0 : if( rent_to_be_paid > 0 ) {
456 0 : fd_pubkey_t pubkey = validator_stakes[i].pubkey;
457 :
458 0 : FD_BORROWED_ACCOUNT_DECL(rec);
459 :
460 0 : int err = fd_acc_mgr_view( slot_ctx->acc_mgr, slot_ctx->funk_txn, &pubkey, rec );
461 0 : if( FD_UNLIKELY(err) ) {
462 0 : FD_LOG_WARNING(( "cannot view pubkey %s. fd_acc_mgr_view failed (%d)", FD_BASE58_ENC_32_ALLOCA( &pubkey ), err ));
463 0 : leftover_lamports = fd_ulong_sat_add( leftover_lamports, rent_to_be_paid );
464 0 : continue;
465 0 : }
466 :
467 0 : if( FD_LIKELY( validate_fee_collector_account ) ) {
468 0 : ulong burn;
469 0 : if( FD_UNLIKELY( burn=fd_runtime_validate_fee_collector( slot_ctx, rec, rent_to_be_paid ) ) ) {
470 0 : if( FD_UNLIKELY( burn!=rent_to_be_paid ) ) {
471 0 : FD_LOG_ERR(( "expected burn(%lu)==rent_to_be_paid(%lu)", burn, rent_to_be_paid ));
472 0 : }
473 0 : leftover_lamports = fd_ulong_sat_add( leftover_lamports, rent_to_be_paid );
474 0 : continue;
475 0 : }
476 0 : }
477 :
478 0 : err = fd_acc_mgr_modify( slot_ctx->acc_mgr, slot_ctx->funk_txn, &pubkey, 0, 0UL, rec );
479 0 : if( FD_UNLIKELY(err) ) {
480 0 : FD_LOG_WARNING(( "cannot modify pubkey %s. fd_acc_mgr_modify failed (%d)", FD_BASE58_ENC_32_ALLOCA( &pubkey ), err ));
481 0 : leftover_lamports = fd_ulong_sat_add( leftover_lamports, rent_to_be_paid );
482 0 : continue;
483 0 : }
484 0 : rec->meta->info.lamports += rent_to_be_paid;
485 0 : }
486 0 : } // end of iteration over validator_stakes
487 :
488 0 : ulong old = slot_ctx->slot_bank.capitalization;
489 0 : slot_ctx->slot_bank.capitalization = fd_ulong_sat_sub(slot_ctx->slot_bank.capitalization, leftover_lamports);
490 0 : FD_LOG_DEBUG(( "fd_runtime_distribute_rent_to_validators: burn %lu, capitalization %lu->%lu ", leftover_lamports, old, slot_ctx->slot_bank.capitalization ));
491 :
492 0 : } FD_SCRATCH_SCOPE_END;
493 0 : }
494 :
495 :
496 : static void
497 0 : fd_runtime_distribute_rent( fd_exec_slot_ctx_t * slot_ctx ) {
498 0 : ulong total_rent_collected = slot_ctx->slot_bank.collected_rent;
499 0 : fd_epoch_bank_t * epoch_bank = fd_exec_epoch_ctx_epoch_bank( slot_ctx->epoch_ctx );
500 0 : ulong burned_portion = fd_runtime_calculate_rent_burn( total_rent_collected, &epoch_bank->rent );
501 0 : slot_ctx->slot_bank.capitalization = fd_ulong_sat_sub( slot_ctx->slot_bank.capitalization, burned_portion );
502 0 : ulong rent_to_be_distributed = total_rent_collected - burned_portion;
503 :
504 0 : FD_LOG_DEBUG(( "rent distribution - slot: %lu, burned_lamports: %lu, distributed_lamports: %lu, total_rent_collected: %lu", slot_ctx->slot_bank.slot, burned_portion, rent_to_be_distributed, total_rent_collected ));
505 0 : if( rent_to_be_distributed == 0 ) {
506 0 : return;
507 0 : }
508 :
509 0 : fd_runtime_distribute_rent_to_validators( slot_ctx, rent_to_be_distributed );
510 0 : }
511 :
512 : static int
513 0 : fd_runtime_run_incinerator( fd_exec_slot_ctx_t * slot_ctx ) {
514 0 : FD_BORROWED_ACCOUNT_DECL( rec );
515 :
516 0 : int err = fd_acc_mgr_modify( slot_ctx->acc_mgr, slot_ctx->funk_txn, &fd_sysvar_incinerator_id, 0, 0UL, rec );
517 0 : if( FD_UNLIKELY( err!=FD_ACC_MGR_SUCCESS ) ) {
518 : // TODO: not really an error! This is fine!
519 0 : return -1;
520 0 : }
521 :
522 0 : slot_ctx->slot_bank.capitalization = fd_ulong_sat_sub( slot_ctx->slot_bank.capitalization, rec->const_meta->info.lamports );
523 0 : rec->meta->info.lamports = 0UL;
524 :
525 0 : return 0;
526 0 : }
527 :
528 : static void
529 0 : fd_runtime_freeze( fd_exec_slot_ctx_t * slot_ctx ) {
530 :
531 : /* https://github.com/anza-xyz/agave/blob/ced98f1ebe73f7e9691308afa757323003ff744f/runtime/src/bank.rs#L2820-L2821 */
532 0 : fd_runtime_collect_rent( slot_ctx );
533 : // self.collect_fees();
534 :
535 0 : fd_sysvar_recent_hashes_update( slot_ctx );
536 :
537 0 : if( !FD_FEATURE_ACTIVE(slot_ctx, disable_fees_sysvar) )
538 0 : fd_sysvar_fees_update(slot_ctx);
539 :
540 0 : ulong fees = 0UL;
541 0 : ulong burn = 0UL;
542 0 : if ( FD_FEATURE_ACTIVE( slot_ctx, reward_full_priority_fee ) ) {
543 0 : ulong half_fee = slot_ctx->slot_bank.collected_execution_fees / 2;
544 0 : fees = fd_ulong_sat_add( slot_ctx->slot_bank.collected_priority_fees, slot_ctx->slot_bank.collected_execution_fees - half_fee );
545 0 : burn = half_fee;
546 0 : } else {
547 0 : ulong total_fees = fd_ulong_sat_add( slot_ctx->slot_bank.collected_execution_fees, slot_ctx->slot_bank.collected_priority_fees );
548 0 : ulong half_fee = total_fees / 2;
549 0 : fees = total_fees - half_fee;
550 0 : burn = half_fee;
551 0 : }
552 0 : if( FD_LIKELY( fees ) ) {
553 : // Look at collect_fees... I think this was where I saw the fee payout..
554 0 : FD_BORROWED_ACCOUNT_DECL(rec);
555 :
556 0 : do {
557 : /* do_create=1 because we might wanna pay fees to a leader
558 : account that we've purged due to 0 balance. */
559 0 : fd_pubkey_t const * leader = fd_epoch_leaders_get( fd_exec_epoch_ctx_leaders( slot_ctx->epoch_ctx ), slot_ctx->slot_bank.slot );
560 0 : int err = fd_acc_mgr_modify( slot_ctx->acc_mgr, slot_ctx->funk_txn, leader, 1, 0UL, rec );
561 0 : if( FD_UNLIKELY(err) ) {
562 0 : FD_LOG_WARNING(("fd_runtime_freeze: fd_acc_mgr_modify for leader (%s) failed (%d)", FD_BASE58_ENC_32_ALLOCA( leader ), err));
563 0 : burn = fd_ulong_sat_add( burn, fees );
564 0 : break;
565 0 : }
566 :
567 0 : if ( FD_LIKELY( FD_FEATURE_ACTIVE( slot_ctx, validate_fee_collector_account ) ) ) {
568 0 : ulong _burn;
569 0 : if( FD_UNLIKELY( _burn=fd_runtime_validate_fee_collector( slot_ctx, rec, fees ) ) ) {
570 0 : if( FD_UNLIKELY( _burn!=fees ) ) {
571 0 : FD_LOG_ERR(( "expected _burn(%lu)==fees(%lu)", _burn, fees ));
572 0 : }
573 0 : burn = fd_ulong_sat_add( burn, fees );
574 0 : FD_LOG_WARNING(("fd_runtime_freeze: burned %lu", fees ));
575 0 : break;
576 0 : }
577 0 : }
578 :
579 0 : rec->meta->info.lamports += fees;
580 0 : rec->meta->slot = slot_ctx->slot_bank.slot;
581 :
582 0 : fd_blockstore_start_write( slot_ctx->blockstore );
583 0 : fd_block_t * blk = slot_ctx->block;
584 0 : blk->rewards.collected_fees = fees;
585 0 : blk->rewards.post_balance = rec->meta->info.lamports;
586 0 : memcpy( blk->rewards.leader.uc, leader->uc, sizeof(fd_hash_t) );
587 0 : fd_blockstore_end_write( slot_ctx->blockstore );
588 0 : } while(0);
589 :
590 0 : ulong old = slot_ctx->slot_bank.capitalization;
591 0 : slot_ctx->slot_bank.capitalization = fd_ulong_sat_sub( slot_ctx->slot_bank.capitalization, burn);
592 0 : FD_LOG_DEBUG(( "fd_runtime_freeze: burn %lu, capitalization %lu->%lu ", burn, old, slot_ctx->slot_bank.capitalization));
593 :
594 0 : slot_ctx->slot_bank.collected_execution_fees = 0;
595 0 : slot_ctx->slot_bank.collected_priority_fees = 0;
596 0 : }
597 :
598 0 : fd_runtime_distribute_rent( slot_ctx );
599 0 : fd_runtime_run_incinerator( slot_ctx );
600 :
601 0 : FD_LOG_DEBUG(( "fd_runtime_freeze: capitalization %lu ", slot_ctx->slot_bank.capitalization));
602 0 : slot_ctx->slot_bank.collected_rent = 0;
603 0 : }
604 :
605 2877 : #define FD_RENT_EXEMPT (-1L)
606 :
607 : static long
608 : fd_runtime_get_rent_due( fd_exec_slot_ctx_t const * slot_ctx,
609 : fd_account_meta_t * acc,
610 2139 : ulong epoch ) {
611 :
612 2139 : fd_epoch_bank_t * epoch_bank = fd_exec_epoch_ctx_epoch_bank( slot_ctx->epoch_ctx );
613 2139 : fd_epoch_schedule_t * schedule = &epoch_bank->rent_epoch_schedule;
614 2139 : fd_rent_t * rent = &epoch_bank->rent;
615 2139 : double slots_per_year = epoch_bank->slots_per_year;
616 :
617 2139 : fd_solana_account_meta_t *info = &acc->info;
618 :
619 : /* Nothing due if account is rent-exempt
620 : https://github.com/anza-xyz/agave/blob/v2.0.10/sdk/src/rent_collector.rs#L90 */
621 2139 : ulong min_balance = fd_rent_exempt_minimum_balance( rent, acc->dlen );
622 2139 : if( info->lamports>=min_balance ) {
623 2043 : return FD_RENT_EXEMPT;
624 2043 : }
625 :
626 : /* Count the number of slots that have passed since last collection. This
627 : inlines the agave function get_slots_in_peohc
628 : https://github.com/anza-xyz/agave/blob/v2.0.10/sdk/src/rent_collector.rs#L93-L98 */
629 96 : ulong slots_elapsed = 0UL;
630 96 : if( FD_UNLIKELY( info->rent_epoch<schedule->first_normal_epoch ) ) {
631 : /* Count the slots before the first normal epoch separately */
632 378 : for( ulong i=info->rent_epoch; i<schedule->first_normal_epoch && i<=epoch; i++ ) {
633 351 : slots_elapsed += fd_epoch_slot_cnt( schedule, i+1UL );
634 351 : }
635 27 : slots_elapsed += fd_ulong_sat_sub( epoch+1UL, schedule->first_normal_epoch ) * schedule->slots_per_epoch;
636 27 : }
637 : // slots_elapsed should remain 0 if rent_epoch is greater than epoch
638 69 : else if( info->rent_epoch<=epoch ) {
639 0 : slots_elapsed = (epoch - info->rent_epoch + 1UL) * schedule->slots_per_epoch;
640 0 : }
641 : /* Consensus-critical use of doubles :( */
642 :
643 96 : double years_elapsed;
644 96 : if( FD_LIKELY( slots_per_year!=0.0 ) ) {
645 96 : years_elapsed = (double)slots_elapsed / slots_per_year;
646 96 : } else {
647 0 : years_elapsed = 0.0;
648 0 : }
649 :
650 96 : ulong lamports_per_year = rent->lamports_per_uint8_year * (acc->dlen + 128UL);
651 : /* https://github.com/anza-xyz/agave/blob/d2124a995f89e33c54f41da76bfd5b0bd5820898/sdk/src/rent_collector.rs#L108 */
652 : /* https://github.com/anza-xyz/agave/blob/d2124a995f89e33c54f41da76bfd5b0bd5820898/sdk/program/src/rent.rs#L95 */
653 96 : return (long)fd_rust_cast_double_to_ulong(years_elapsed * (double)lamports_per_year);
654 2139 : }
655 :
656 : /* https://github.com/anza-xyz/agave/blob/v2.0.10/sdk/src/rent_collector.rs#L117-149 */
657 : /* Collect rent from an account. Returns the amount of rent collected. */
658 : static ulong
659 : fd_runtime_collect_from_existing_account( fd_exec_slot_ctx_t const * slot_ctx,
660 : fd_account_meta_t * acc,
661 : fd_pubkey_t const * pubkey,
662 14247 : ulong epoch ) {
663 14247 : ulong collected_rent = 0UL;
664 26820 : #define NO_RENT_COLLECTION_NOW (-1)
665 14247 : #define EXEMPT (-2)
666 14265 : #define COLLECT_RENT (-3)
667 :
668 : /* An account must be hashed regardless of if rent is collected from it. */
669 14247 : acc->slot = slot_ctx->slot_bank.slot;
670 :
671 : /* Inlining calculate_rent_result
672 : https://github.com/anza-xyz/agave/blob/v2.0.10/sdk/src/rent_collector.rs#L153-184 */
673 14247 : int calculate_rent_result = COLLECT_RENT;
674 :
675 : /* RentResult::NoRentCollectionNow */
676 14247 : if( FD_LIKELY( acc->info.rent_epoch==FD_RENT_EXEMPT_RENT_EPOCH || acc->info.rent_epoch>epoch ) ) {
677 13410 : calculate_rent_result = NO_RENT_COLLECTION_NOW;
678 13410 : goto rent_calculation;
679 13410 : }
680 : /* RentResult::Exempt */
681 : /* Inlining should_collect_rent() */
682 837 : int should_collect_rent = !( acc->info.executable ||
683 837 : !memcmp( pubkey, &fd_sysvar_incinerator_id, sizeof(fd_pubkey_t) ) );
684 837 : if( !should_collect_rent ) {
685 3 : calculate_rent_result = EXEMPT;
686 3 : goto rent_calculation;
687 3 : }
688 :
689 : /* https://github.com/anza-xyz/agave/blob/v2.0.10/sdk/src/rent_collector.rs#L167-180 */
690 834 : long rent_due = fd_runtime_get_rent_due( slot_ctx, acc, epoch );
691 834 : if( rent_due==FD_RENT_EXEMPT ) {
692 825 : calculate_rent_result = EXEMPT;
693 825 : } else if( rent_due==0L ) {
694 0 : calculate_rent_result = NO_RENT_COLLECTION_NOW;
695 9 : } else {
696 9 : calculate_rent_result = COLLECT_RENT;
697 9 : }
698 :
699 14247 : rent_calculation:
700 14247 : switch( calculate_rent_result ) {
701 828 : case EXEMPT:
702 828 : acc->info.rent_epoch = FD_RENT_EXEMPT_RENT_EPOCH;
703 828 : break;
704 13410 : case NO_RENT_COLLECTION_NOW:
705 13410 : break;
706 9 : case COLLECT_RENT:
707 9 : if( FD_UNLIKELY( (ulong)rent_due>=acc->info.lamports ) ) {
708 : /* Reclaim account */
709 9 : collected_rent += (ulong)acc->info.lamports;
710 9 : acc->info.lamports = 0UL;
711 9 : acc->dlen = 0UL;
712 9 : fd_memset( acc->info.owner, 0, sizeof(acc->info.owner) );
713 9 : } else {
714 0 : collected_rent += (ulong)rent_due;
715 0 : acc->info.lamports -= (ulong)rent_due;
716 0 : acc->info.rent_epoch = epoch+1UL;
717 0 : }
718 14247 : }
719 :
720 14247 : return collected_rent;
721 :
722 14247 : #undef NO_RENT_COLLECTION_NOW
723 14247 : #undef EXEMPT
724 14247 : #undef COLLECT_RENT
725 14247 : }
726 :
727 :
728 : /* fd_runtime_collect_rent_from_account performs rent collection duties.
729 : Although the Solana runtime prevents the creation of new accounts
730 : that are subject to rent, some older accounts are still undergo the
731 : rent collection process. Updates the account's 'rent_epoch' if
732 : needed. Returns the amount of rent collected. */
733 : /* https://github.com/anza-xyz/agave/blob/v2.0.10/svm/src/account_loader.rs#L71-96 */
734 : ulong
735 : fd_runtime_collect_rent_from_account( fd_exec_slot_ctx_t const * slot_ctx,
736 : fd_account_meta_t * acc,
737 : fd_pubkey_t const * key,
738 18111 : ulong epoch ) {
739 :
740 18111 : if( !FD_FEATURE_ACTIVE( slot_ctx, disable_rent_fees_collection ) ) {
741 14247 : return fd_runtime_collect_from_existing_account( slot_ctx, acc, key, epoch );
742 14247 : } else {
743 3864 : if( FD_UNLIKELY( acc->info.rent_epoch!=FD_RENT_EXEMPT_RENT_EPOCH &&
744 3864 : fd_runtime_get_rent_due( slot_ctx, acc, epoch )==FD_RENT_EXEMPT ) ) {
745 1218 : acc->info.rent_epoch = ULONG_MAX;
746 1218 : }
747 3864 : }
748 3864 : return 0UL;
749 18111 : }
750 :
751 : #undef FD_RENT_EXEMPT
752 :
753 : void
754 : fd_runtime_write_transaction_status( fd_capture_ctx_t * capture_ctx,
755 : fd_exec_slot_ctx_t * slot_ctx,
756 : fd_exec_txn_ctx_t * txn_ctx,
757 0 : int exec_txn_err) {
758 : /* Look up solana-side transaction status details */
759 0 : fd_blockstore_t * blockstore = txn_ctx->slot_ctx->blockstore;
760 0 : uchar * sig = (uchar *)txn_ctx->_txn_raw->raw + txn_ctx->txn_descriptor->signature_off;
761 0 : fd_blockstore_start_read( blockstore );
762 0 : fd_txn_map_t * txn_map_entry = fd_blockstore_txn_query( blockstore, sig );
763 0 : if( FD_LIKELY( txn_map_entry != NULL ) ) {
764 0 : void * meta = fd_wksp_laddr_fast( fd_blockstore_wksp( blockstore ), txn_map_entry->meta_gaddr );
765 :
766 0 : fd_solblock_TransactionStatusMeta txn_status = {0};
767 : /* Need to handle case for ledgers where transaction status is not available.
768 : This case will be handled in fd_solcap_diff. */
769 0 : ulong fd_cus_consumed = txn_ctx->compute_unit_limit - txn_ctx->compute_meter;
770 0 : ulong solana_cus_consumed = ULONG_MAX;
771 0 : ulong solana_txn_err = ULONG_MAX;
772 0 : if( FD_LIKELY( meta != NULL ) ) {
773 0 : pb_istream_t stream = pb_istream_from_buffer( meta, txn_map_entry->meta_sz );
774 0 : fd_blockstore_end_read( blockstore );
775 0 : if ( pb_decode( &stream, fd_solblock_TransactionStatusMeta_fields, &txn_status ) == false ) {
776 0 : FD_LOG_WARNING(("no txn_status decoding found sig=%s (%s)", FD_BASE58_ENC_64_ALLOCA( sig ), PB_GET_ERROR(&stream)));
777 0 : }
778 0 : if ( txn_status.has_compute_units_consumed ) {
779 0 : solana_cus_consumed = txn_status.compute_units_consumed;
780 0 : }
781 0 : if ( txn_status.has_err ) {
782 0 : solana_txn_err = txn_status.err.err->bytes[0];
783 0 : }
784 :
785 0 : fd_solcap_Transaction txn = {
786 0 : .slot = slot_ctx->slot_bank.slot,
787 0 : .fd_txn_err = exec_txn_err,
788 0 : .fd_custom_err = txn_ctx->custom_err,
789 0 : .solana_txn_err = solana_txn_err,
790 0 : .fd_cus_used = fd_cus_consumed,
791 0 : .solana_cus_used = solana_cus_consumed,
792 0 : .instr_err_idx = txn_ctx->instr_err_idx == INT_MAX ? -1 : txn_ctx->instr_err_idx,
793 0 : };
794 0 : memcpy( txn.txn_sig, sig, sizeof(fd_signature_t) );
795 :
796 0 : fd_exec_instr_ctx_t const * failed_instr = txn_ctx->failed_instr;
797 0 : if( failed_instr ) {
798 0 : assert( failed_instr->depth < 4 );
799 0 : txn.instr_err = failed_instr->instr_err;
800 0 : txn.failed_instr_path_count = failed_instr->depth + 1;
801 0 : for( long j = failed_instr->depth; j>=0L; j-- ) {
802 0 : txn.failed_instr_path[j] = failed_instr->index;
803 0 : failed_instr = failed_instr->parent;
804 0 : }
805 0 : }
806 :
807 0 : fd_solcap_write_transaction2( capture_ctx->capture, &txn );
808 0 : }
809 0 : } else {
810 0 : fd_blockstore_end_read( blockstore );
811 0 : }
812 0 : }
813 :
814 : static bool
815 0 : encode_return_data( pb_ostream_t *stream, const pb_field_t *field, void * const *arg ) {
816 0 : fd_exec_txn_ctx_t * txn_ctx = (fd_exec_txn_ctx_t *)(*arg);
817 0 : pb_encode_tag_for_field(stream, field);
818 0 : pb_encode_string(stream, txn_ctx->return_data.data, txn_ctx->return_data.len );
819 0 : return 1;
820 0 : }
821 :
822 : static ulong
823 0 : fd_txn_copy_meta( fd_exec_txn_ctx_t * txn_ctx, uchar * dest, ulong dest_sz ) {
824 0 : fd_solblock_TransactionStatusMeta txn_status = {0};
825 :
826 0 : txn_status.has_fee = 1;
827 0 : txn_status.fee = txn_ctx->execution_fee + txn_ctx->priority_fee;
828 :
829 0 : txn_status.has_compute_units_consumed = 1;
830 0 : txn_status.compute_units_consumed = txn_ctx->compute_unit_limit - txn_ctx->compute_meter;
831 :
832 0 : ulong readonly_cnt = 0;
833 0 : ulong writable_cnt = 0;
834 0 : if( txn_ctx->txn_descriptor->transaction_version == FD_TXN_V0 ) {
835 0 : fd_txn_acct_addr_lut_t const * addr_luts = fd_txn_get_address_tables_const( txn_ctx->txn_descriptor );
836 0 : for( ulong i = 0; i < txn_ctx->txn_descriptor->addr_table_lookup_cnt; i++ ) {
837 0 : fd_txn_acct_addr_lut_t const * addr_lut = &addr_luts[i];
838 0 : readonly_cnt += addr_lut->readonly_cnt;
839 0 : writable_cnt += addr_lut->writable_cnt;
840 0 : }
841 0 : }
842 :
843 0 : typedef PB_BYTES_ARRAY_T(32) my_ba_t;
844 0 : typedef union { my_ba_t my; pb_bytes_array_t normal; } union_ba_t;
845 0 : union_ba_t writable_ba[writable_cnt];
846 0 : pb_bytes_array_t * writable_baptr[writable_cnt];
847 0 : txn_status.loaded_writable_addresses_count = (uint)writable_cnt;
848 0 : txn_status.loaded_writable_addresses = writable_baptr;
849 0 : ulong idx2 = txn_ctx->txn_descriptor->acct_addr_cnt;
850 0 : for (ulong idx = 0; idx < writable_cnt; idx++) {
851 0 : pb_bytes_array_t * ba = writable_baptr[ idx ] = &writable_ba[ idx ].normal;
852 0 : ba->size = 32;
853 0 : fd_memcpy(ba->bytes, &txn_ctx->accounts[idx2++], 32);
854 0 : }
855 :
856 0 : union_ba_t readonly_ba[readonly_cnt];
857 0 : pb_bytes_array_t * readonly_baptr[readonly_cnt];
858 0 : txn_status.loaded_readonly_addresses_count = (uint)readonly_cnt;
859 0 : txn_status.loaded_readonly_addresses = readonly_baptr;
860 0 : for (ulong idx = 0; idx < readonly_cnt; idx++) {
861 0 : pb_bytes_array_t * ba = readonly_baptr[ idx ] = &readonly_ba[ idx ].normal;
862 0 : ba->size = 32;
863 0 : fd_memcpy(ba->bytes, &txn_ctx->accounts[idx2++], 32);
864 0 : }
865 0 : ulong acct_cnt = txn_ctx->accounts_cnt;
866 0 : FD_TEST(acct_cnt == idx2);
867 :
868 0 : txn_status.pre_balances_count = txn_status.post_balances_count = (pb_size_t)acct_cnt;
869 0 : uint64_t pre_balances[acct_cnt];
870 0 : txn_status.pre_balances = pre_balances;
871 0 : uint64_t post_balances[acct_cnt];
872 0 : txn_status.post_balances = post_balances;
873 :
874 0 : for (ulong idx = 0; idx < acct_cnt; idx++) {
875 0 : fd_borrowed_account_t const * acct = &txn_ctx->borrowed_accounts[idx];
876 0 : ulong pre = ( acct->starting_lamports == ULONG_MAX ? 0UL : acct->starting_lamports );
877 0 : pre_balances[idx] = pre;
878 0 : post_balances[idx] = ( acct->meta ? acct->meta->info.lamports :
879 0 : ( acct->orig_meta ? acct->orig_meta->info.lamports : pre ) );
880 0 : }
881 :
882 0 : if( txn_ctx->return_data.len ) {
883 0 : txn_status.has_return_data = 1;
884 0 : txn_status.return_data.has_program_id = 1;
885 0 : fd_memcpy( txn_status.return_data.program_id, txn_ctx->return_data.program_id.uc, 32U );
886 0 : pb_callback_t data = { .funcs.encode = encode_return_data, .arg = txn_ctx };
887 0 : txn_status.return_data.data = data;
888 0 : }
889 :
890 0 : union {
891 0 : pb_bytes_array_t arr;
892 0 : uchar space[64];
893 0 : } errarr;
894 0 : pb_byte_t * errptr = errarr.arr.bytes;
895 0 : if( txn_ctx->custom_err != UINT_MAX ) {
896 0 : *(uint*)errptr = 8 /* Instruction error */;
897 0 : errptr += sizeof(uint);
898 0 : *errptr = (uchar)txn_ctx->instr_err_idx;
899 0 : errptr += 1;
900 0 : *(int*)errptr = FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR;
901 0 : errptr += sizeof(int);
902 0 : *(uint*)errptr = txn_ctx->custom_err;
903 0 : errptr += sizeof(uint);
904 0 : errarr.arr.size = (uint)(errptr - errarr.arr.bytes);
905 0 : txn_status.has_err = 1;
906 0 : txn_status.err.err = &errarr.arr;
907 0 : } else if( txn_ctx->exec_err ) {
908 0 : switch( txn_ctx->exec_err_kind ) {
909 0 : case FD_EXECUTOR_ERR_KIND_SYSCALL:
910 0 : break;
911 0 : case FD_EXECUTOR_ERR_KIND_INSTR:
912 0 : *(uint*)errptr = 8 /* Instruction error */;
913 0 : errptr += sizeof(uint);
914 0 : *errptr = (uchar)txn_ctx->instr_err_idx;
915 0 : errptr += 1;
916 0 : *(int*)errptr = txn_ctx->exec_err;
917 0 : errptr += sizeof(int);
918 0 : errarr.arr.size = (uint)(errptr - errarr.arr.bytes);
919 0 : txn_status.has_err = 1;
920 0 : txn_status.err.err = &errarr.arr;
921 0 : break;
922 0 : case FD_EXECUTOR_ERR_KIND_EBPF:
923 0 : break;
924 0 : }
925 0 : }
926 :
927 0 : if( dest == NULL ) {
928 0 : size_t sz = 0;
929 0 : bool r = pb_get_encoded_size( &sz, fd_solblock_TransactionStatusMeta_fields, &txn_status );
930 0 : if( !r ) {
931 0 : FD_LOG_WARNING(( "pb_get_encoded_size failed" ));
932 0 : return 0;
933 0 : }
934 0 : return sz + txn_ctx->log_collector.buf_sz;
935 0 : }
936 :
937 0 : pb_ostream_t stream = pb_ostream_from_buffer( dest, dest_sz );
938 0 : bool r = pb_encode( &stream, fd_solblock_TransactionStatusMeta_fields, &txn_status );
939 0 : if( !r ) {
940 0 : FD_LOG_WARNING(( "pb_encode failed" ));
941 0 : return 0;
942 0 : }
943 0 : pb_write( &stream, txn_ctx->log_collector.buf, txn_ctx->log_collector.buf_sz );
944 0 : return stream.bytes_written;
945 0 : }
946 :
947 : /* fd_runtime_finalize_txns_update_blockstore_meta() updates transaction metadata
948 : after execution.
949 :
950 : Execution recording is controlled by slot_ctx->enable_exec_recording, and this
951 : function does nothing if execution recording is off. The following comments
952 : only apply when execution recording is on.
953 :
954 : Transaction metadata includes execution result (success/error), balance changes,
955 : transaction logs, ... All this info is not part of consensus but can be retrieved,
956 : for instace, via RPC getTransaction. Firedancer stores txn meta in the blockstore,
957 : in the same binary format as Agave, protobuf TransactionStatusMeta. */
958 : static void
959 : fd_runtime_finalize_txns_update_blockstore_meta( fd_exec_slot_ctx_t * slot_ctx,
960 : fd_execute_txn_task_info_t * task_info,
961 0 : ulong txn_cnt ) {
962 : /* Nothing to do if execution recording is off */
963 0 : if( !slot_ctx->enable_exec_recording ) {
964 0 : return;
965 0 : }
966 :
967 0 : fd_blockstore_t * blockstore = slot_ctx->blockstore;
968 0 : fd_wksp_t * blockstore_wksp = fd_blockstore_wksp( blockstore );
969 0 : fd_alloc_t * blockstore_alloc = fd_blockstore_alloc( blockstore );
970 0 : fd_txn_map_t * txn_map = fd_blockstore_txn_map( blockstore );
971 :
972 : /* Get the total size of all logs */
973 0 : ulong tot_meta_sz = 2*sizeof(ulong);
974 0 : for( ulong txn_idx = 0; txn_idx < txn_cnt; txn_idx++ ) {
975 : /* Prebalance compensation */
976 0 : fd_exec_txn_ctx_t * txn_ctx = task_info[txn_idx].txn_ctx;
977 0 : txn_ctx->borrowed_accounts[0].starting_lamports += (txn_ctx->execution_fee + txn_ctx->priority_fee);
978 : /* Get the size without the copy */
979 0 : tot_meta_sz += fd_txn_copy_meta( txn_ctx, NULL, 0 );
980 0 : }
981 0 : uchar * cur_laddr = fd_alloc_malloc( blockstore_alloc, 1, tot_meta_sz );
982 0 : if( cur_laddr == NULL ) {
983 0 : return;
984 0 : }
985 0 : uchar * const end_laddr = cur_laddr + tot_meta_sz;
986 :
987 0 : fd_blockstore_start_write( blockstore );
988 0 : fd_block_t * blk = slot_ctx->block;
989 : /* Link to previous allocation */
990 0 : ((ulong*)cur_laddr)[0] = blk->txns_meta_gaddr;
991 0 : ((ulong*)cur_laddr)[1] = blk->txns_meta_sz;
992 0 : blk->txns_meta_gaddr = fd_wksp_gaddr_fast( blockstore_wksp, cur_laddr );
993 0 : blk->txns_meta_sz = tot_meta_sz;
994 0 : cur_laddr += 2*sizeof(ulong);
995 :
996 0 : for( ulong txn_idx = 0; txn_idx < txn_cnt; txn_idx++ ) {
997 0 : fd_exec_txn_ctx_t * txn_ctx = task_info[txn_idx].txn_ctx;
998 0 : ulong meta_sz = fd_txn_copy_meta( txn_ctx, cur_laddr, (size_t)(end_laddr - cur_laddr) );
999 0 : if( meta_sz ) {
1000 0 : ulong meta_gaddr = fd_wksp_gaddr_fast( blockstore_wksp, cur_laddr );
1001 :
1002 : /* Update all the signatures */
1003 0 : char const * sig_p = (char const *)txn_ctx->_txn_raw->raw + txn_ctx->txn_descriptor->signature_off;
1004 0 : fd_txn_key_t sig;
1005 0 : for( uchar i=0U; i<txn_ctx->txn_descriptor->signature_cnt; i++ ) {
1006 0 : fd_memcpy( &sig, sig_p, sizeof(fd_txn_key_t) );
1007 0 : fd_txn_map_t * txn_map_entry = fd_txn_map_query( txn_map, &sig, NULL );
1008 0 : if( FD_LIKELY( txn_map_entry ) ) {
1009 0 : txn_map_entry->meta_gaddr = meta_gaddr;
1010 0 : txn_map_entry->meta_sz = meta_sz;
1011 0 : }
1012 0 : sig_p += FD_ED25519_SIG_SZ;
1013 0 : }
1014 :
1015 0 : cur_laddr += meta_sz;
1016 0 : }
1017 0 : fd_log_collector_delete( &txn_ctx->log_collector );
1018 0 : }
1019 :
1020 0 : FD_TEST( cur_laddr == end_laddr );
1021 :
1022 0 : fd_blockstore_end_write( blockstore );
1023 0 : }
1024 :
1025 : /******************************************************************************/
1026 : /* Block-Level Execution Preparation/Finalization */
1027 : /******************************************************************************/
1028 :
1029 : static int
1030 0 : fd_runtime_block_sysvar_update_pre_execute( fd_exec_slot_ctx_t * slot_ctx ) {
1031 : // let (fee_rate_governor, fee_components_time_us) = measure_us!(
1032 : // FeeRateGovernor::new_derived(&parent.fee_rate_governor, parent.signature_count())
1033 : // );
1034 : /* https://github.com/firedancer-io/solana/blob/dab3da8e7b667d7527565bddbdbecf7ec1fb868e/runtime/src/bank.rs#L1312-L1314 */
1035 0 : fd_sysvar_fees_new_derived( slot_ctx,
1036 0 : slot_ctx->slot_bank.fee_rate_governor,
1037 0 : slot_ctx->slot_bank.parent_signature_cnt );
1038 :
1039 : // TODO: move all these out to a fd_sysvar_update() call...
1040 0 : long clock_update_time = -fd_log_wallclock();
1041 0 : fd_sysvar_clock_update( slot_ctx );
1042 0 : clock_update_time += fd_log_wallclock();
1043 0 : double clock_update_time_ms = (double)clock_update_time * 1e-6;
1044 0 : FD_LOG_INFO(( "clock updated - slot: %lu, elapsed: %6.6f ms", slot_ctx->slot_bank.slot, clock_update_time_ms ));
1045 0 : if( !FD_FEATURE_ACTIVE(slot_ctx, disable_fees_sysvar ) ) {
1046 0 : fd_sysvar_fees_update(slot_ctx);
1047 0 : }
1048 : // It has to go into the current txn previous info but is not in slot 0
1049 0 : if( slot_ctx->slot_bank.slot != 0 ) {
1050 0 : fd_sysvar_slot_hashes_update( slot_ctx );
1051 0 : }
1052 0 : fd_sysvar_last_restart_slot_update( slot_ctx );
1053 :
1054 0 : return 0;
1055 0 : }
1056 :
1057 : int
1058 0 : fd_runtime_block_execute_prepare( fd_exec_slot_ctx_t * slot_ctx ) {
1059 : /* Update block height */
1060 0 : slot_ctx->slot_bank.block_height += 1UL;
1061 0 : fd_blockstore_start_write( slot_ctx->blockstore );
1062 0 : fd_blockstore_block_height_update( slot_ctx->blockstore,
1063 0 : slot_ctx->slot_bank.slot,
1064 0 : slot_ctx->slot_bank.block_height );
1065 :
1066 : // TODO: this is not part of block execution, move it.
1067 0 : if( slot_ctx->slot_bank.slot != 0UL ) {
1068 0 : slot_ctx->block = fd_blockstore_block_query( slot_ctx->blockstore, slot_ctx->slot_bank.slot );
1069 0 : fd_blockstore_end_write( slot_ctx->blockstore );
1070 :
1071 0 : ulong slot_idx;
1072 0 : fd_epoch_bank_t * epoch_bank = fd_exec_epoch_ctx_epoch_bank( slot_ctx->epoch_ctx );
1073 0 : ulong prev_epoch = fd_slot_to_epoch( &epoch_bank->epoch_schedule, slot_ctx->slot_bank.prev_slot, &slot_idx );
1074 0 : ulong new_epoch = fd_slot_to_epoch( &epoch_bank->epoch_schedule, slot_ctx->slot_bank.slot, &slot_idx );
1075 0 : if( FD_UNLIKELY( slot_idx==1UL && new_epoch==0UL ) ) {
1076 : /* the block after genesis has a height of 1*/
1077 0 : slot_ctx->slot_bank.block_height = 1UL;
1078 0 : }
1079 :
1080 0 : if( FD_UNLIKELY( prev_epoch<new_epoch || !slot_idx ) ) {
1081 0 : FD_LOG_DEBUG(("Epoch boundary"));
1082 : /* Epoch boundary! */
1083 0 : fd_funk_start_write( slot_ctx->acc_mgr->funk );
1084 0 : fd_process_new_epoch( slot_ctx, new_epoch - 1UL );
1085 0 : fd_funk_end_write( slot_ctx->acc_mgr->funk );
1086 0 : }
1087 0 : } else {
1088 0 : fd_blockstore_end_write( slot_ctx->blockstore );
1089 0 : }
1090 :
1091 0 : slot_ctx->slot_bank.collected_execution_fees = 0UL;
1092 0 : slot_ctx->slot_bank.collected_priority_fees = 0UL;
1093 0 : slot_ctx->slot_bank.collected_rent = 0UL;
1094 0 : slot_ctx->signature_cnt = 0UL;
1095 0 : slot_ctx->txn_count = 0UL;
1096 0 : slot_ctx->nonvote_txn_count = 0UL;
1097 0 : slot_ctx->failed_txn_count = 0UL;
1098 0 : slot_ctx->nonvote_failed_txn_count = 0UL;
1099 0 : slot_ctx->total_compute_units_used = 0UL;
1100 :
1101 0 : if( slot_ctx->slot_bank.slot != 0UL && (
1102 0 : FD_FEATURE_ACTIVE( slot_ctx, enable_partitioned_epoch_reward ) ||
1103 0 : FD_FEATURE_ACTIVE( slot_ctx, partitioned_epoch_rewards_superfeature ) ) ) {
1104 0 : fd_funk_start_write( slot_ctx->acc_mgr->funk );
1105 0 : fd_distribute_partitioned_epoch_rewards( slot_ctx );
1106 0 : fd_funk_end_write( slot_ctx->acc_mgr->funk );
1107 0 : }
1108 :
1109 0 : fd_funk_start_write( slot_ctx->acc_mgr->funk );
1110 0 : int result = fd_runtime_block_sysvar_update_pre_execute( slot_ctx );
1111 0 : fd_funk_end_write( slot_ctx->acc_mgr->funk );
1112 0 : if( FD_UNLIKELY( result != 0 ) ) {
1113 0 : FD_LOG_WARNING(("updating sysvars failed"));
1114 0 : return result;
1115 0 : }
1116 :
1117 : /* Load sysvars into cache */
1118 0 : if( FD_UNLIKELY( result = fd_runtime_sysvar_cache_load( slot_ctx ) ) ) {
1119 : /* non-zero error */
1120 0 : return result;
1121 0 : }
1122 :
1123 0 : return FD_RUNTIME_EXECUTE_SUCCESS;
1124 0 : }
1125 :
1126 : int
1127 : fd_runtime_block_execute_finalize_tpool( fd_exec_slot_ctx_t * slot_ctx,
1128 : fd_capture_ctx_t * capture_ctx,
1129 : fd_block_info_t const * block_info,
1130 0 : fd_tpool_t * tpool ) {
1131 0 : fd_funk_start_write( slot_ctx->acc_mgr->funk );
1132 :
1133 0 : fd_sysvar_slot_history_update( slot_ctx );
1134 :
1135 : /* This slot is now "frozen" and can't be changed anymore. */
1136 0 : fd_runtime_freeze( slot_ctx );
1137 :
1138 0 : int result = fd_bpf_scan_and_create_bpf_program_cache_entry_tpool( slot_ctx, slot_ctx->funk_txn, tpool );
1139 0 : if( FD_UNLIKELY( result ) ) {
1140 0 : FD_LOG_WARNING(( "update bpf program cache failed" ));
1141 0 : fd_funk_end_write( slot_ctx->acc_mgr->funk );
1142 0 : return result;
1143 0 : }
1144 :
1145 0 : result = fd_update_hash_bank_tpool( slot_ctx, capture_ctx, &slot_ctx->slot_bank.banks_hash, block_info->signature_cnt, tpool );
1146 0 : if( FD_UNLIKELY( result!=FD_EXECUTOR_INSTR_SUCCESS ) ) {
1147 0 : FD_LOG_WARNING(( "hashing bank failed" ));
1148 0 : fd_funk_end_write( slot_ctx->acc_mgr->funk );
1149 0 : return result;
1150 0 : }
1151 :
1152 : /* We don't want to save the epoch bank at the end of every slot because it
1153 : should only be changing at the epoch boundary. */
1154 :
1155 0 : result = fd_runtime_save_slot_bank( slot_ctx );
1156 0 : if( FD_UNLIKELY( result!=FD_RUNTIME_EXECUTE_SUCCESS ) ) {
1157 0 : FD_LOG_WARNING(( "failed to save slot bank" ));
1158 0 : fd_funk_end_write( slot_ctx->acc_mgr->funk );
1159 0 : return result;
1160 0 : }
1161 :
1162 0 : fd_funk_end_write( slot_ctx->acc_mgr->funk );
1163 :
1164 0 : slot_ctx->total_compute_units_requested = 0UL;
1165 0 : for ( fd_account_compute_table_iter_t iter = fd_account_compute_table_iter_init( slot_ctx->account_compute_table );
1166 0 : !fd_account_compute_table_iter_done( slot_ctx->account_compute_table, iter );
1167 0 : iter = fd_account_compute_table_iter_next( slot_ctx->account_compute_table, iter ) ) {
1168 0 : fd_account_compute_elem_t * e = fd_account_compute_table_iter_ele( slot_ctx->account_compute_table, iter );
1169 0 : fd_account_compute_table_remove( slot_ctx->account_compute_table, &e->key );
1170 0 : }
1171 :
1172 0 : return FD_RUNTIME_EXECUTE_SUCCESS;
1173 0 : }
1174 :
1175 : /******************************************************************************/
1176 : /* Transaction Level Execution Management */
1177 : /******************************************************************************/
1178 :
1179 : /* fd_runtime_prepare_txns_start is responsible for setting up the task infos,
1180 : the slot_ctx, and for setting up the accessed accounts. */
1181 :
1182 : int
1183 : fd_runtime_prepare_txns_start( fd_exec_slot_ctx_t * slot_ctx,
1184 : fd_execute_txn_task_info_t * task_info,
1185 : fd_txn_p_t * txns,
1186 12741 : ulong txn_cnt ) {
1187 12741 : int res = 0;
1188 : /* Loop across transactions */
1189 25482 : for (ulong txn_idx = 0; txn_idx < txn_cnt; txn_idx++) {
1190 12741 : fd_txn_p_t * txn = &txns[txn_idx];
1191 :
1192 : /* Allocate/setup transaction context and task infos */
1193 12741 : task_info[txn_idx].txn_ctx = fd_valloc_malloc( fd_scratch_virtual(), FD_EXEC_TXN_CTX_ALIGN, FD_EXEC_TXN_CTX_FOOTPRINT );
1194 12741 : fd_exec_txn_ctx_t * txn_ctx = task_info[txn_idx].txn_ctx;
1195 12741 : task_info[txn_idx].exec_res = 0;
1196 12741 : task_info[txn_idx].txn = txn;
1197 12741 : fd_txn_t const * txn_descriptor = (fd_txn_t const *) txn->_;
1198 :
1199 12741 : fd_rawtxn_b_t raw_txn = { .raw = txn->payload, .txn_sz = (ushort)txn->payload_sz };
1200 :
1201 12741 : int err = fd_execute_txn_prepare_start( slot_ctx, txn_ctx, txn_descriptor, &raw_txn );
1202 12741 : if( FD_UNLIKELY( err ) ) {
1203 264 : task_info[txn_idx].exec_res = err;
1204 264 : txn->flags = 0U;
1205 264 : res |= err;
1206 264 : }
1207 12741 : }
1208 :
1209 12741 : return res;
1210 12741 : }
1211 :
1212 : /* fd_runtime_pre_execute_check is responsible for conducting many of the
1213 : transaction sanitization checks. */
1214 :
1215 : void
1216 12741 : fd_runtime_pre_execute_check( fd_execute_txn_task_info_t * task_info ) {
1217 12741 : if( FD_UNLIKELY( !( task_info->txn->flags & FD_TXN_P_FLAGS_SANITIZE_SUCCESS ) ) ) {
1218 264 : return;
1219 264 : }
1220 :
1221 12477 : fd_exec_txn_ctx_t * txn_ctx = task_info->txn_ctx;
1222 :
1223 12477 : fd_funk_txn_t * parent_txn = txn_ctx->slot_ctx->funk_txn;
1224 12477 : txn_ctx->funk_txn = parent_txn;
1225 12477 : fd_executor_setup_borrowed_accounts_for_txn( txn_ctx );
1226 :
1227 12477 : int err;
1228 :
1229 : /* https://github.com/anza-xyz/agave/blob/16de8b75ebcd57022409b422de557dd37b1de8db/sdk/src/transaction/sanitized.rs#L263-L275
1230 : TODO: Agave's precompile verification is done at the slot level, before batching and executing transactions. This logic should probably
1231 : be moved in the future. The Agave call heirarchy looks something like this:
1232 : process_single_slot
1233 : v
1234 : confirm_full_slot
1235 : v
1236 : confirm_slot_entries --------->
1237 : v v
1238 : verify_transaction process_entries
1239 : v v
1240 : verify_precompiles process_batches
1241 : v
1242 : ...
1243 : v
1244 : load_and_execute_transactions
1245 : v
1246 : ...
1247 : v
1248 : load_accounts --> load_transaction_accounts
1249 : v
1250 : general transaction execution
1251 :
1252 : */
1253 12477 : err = fd_executor_verify_precompiles( txn_ctx );
1254 12477 : if( FD_UNLIKELY( err!=FD_RUNTIME_EXECUTE_SUCCESS ) ) {
1255 4113 : task_info->txn->flags = 0U;
1256 4113 : task_info->exec_res = err;
1257 4113 : return;
1258 4113 : }
1259 :
1260 : /* Post-sanitization checks. Called from `prepare_sanitized_batch()` which, for now, only is used
1261 : to lock the accounts and perform a couple basic validations.
1262 : https://github.com/anza-xyz/agave/blob/838c1952595809a31520ff1603a13f2c9123aa51/accounts-db/src/account_locks.rs#L118 */
1263 8364 : err = fd_executor_validate_account_locks( txn_ctx );
1264 8364 : if( FD_UNLIKELY( err!=FD_RUNTIME_EXECUTE_SUCCESS ) ) {
1265 393 : task_info->txn->flags = 0U;
1266 393 : task_info->exec_res = err;
1267 393 : return;
1268 393 : }
1269 :
1270 : /* `load_and_execute_transactions()` -> `check_transactions()`
1271 : https://github.com/anza-xyz/agave/blob/ced98f1ebe73f7e9691308afa757323003ff744f/runtime/src/bank.rs#L3667-L3672 */
1272 7971 : err = fd_executor_check_transactions( txn_ctx );
1273 7971 : if( FD_UNLIKELY( err!=FD_RUNTIME_EXECUTE_SUCCESS ) ) {
1274 1041 : task_info->txn->flags = 0U;
1275 1041 : task_info->exec_res = err;
1276 1041 : return;
1277 1041 : }
1278 :
1279 : /* `load_and_execute_sanitized_transactions()` -> `validate_fees()` -> `validate_transaction_fee_payer()`
1280 : https://github.com/anza-xyz/agave/blob/ced98f1ebe73f7e9691308afa757323003ff744f/svm/src/transaction_processor.rs#L236-L249 */
1281 6930 : err = fd_executor_validate_transaction_fee_payer( txn_ctx );
1282 6930 : if( FD_UNLIKELY( err!=FD_RUNTIME_EXECUTE_SUCCESS ) ) {
1283 204 : task_info->txn->flags = 0U;
1284 204 : task_info->exec_res = err;
1285 204 : return;
1286 204 : }
1287 :
1288 : /* https://github.com/anza-xyz/agave/blob/ced98f1ebe73f7e9691308afa757323003ff744f/svm/src/transaction_processor.rs#L284-L296 */
1289 6726 : err = fd_executor_load_transaction_accounts( txn_ctx );
1290 6726 : if( FD_UNLIKELY( err!=FD_RUNTIME_EXECUTE_SUCCESS ) ) {
1291 1536 : task_info->txn->flags = 0U;
1292 1536 : task_info->exec_res = err;
1293 1536 : return;
1294 1536 : }
1295 6726 : }
1296 :
1297 : /* fd_runtime_finalize_txn is a helper used by the non-tpool transaction
1298 : executor to finalize borrowed account changes back into funk. It also
1299 : handles txncache insertion and updates to the vote/stake cache. */
1300 :
1301 : static int
1302 : fd_runtime_finalize_txn( fd_exec_slot_ctx_t * slot_ctx,
1303 : fd_capture_ctx_t * capture_ctx,
1304 0 : fd_execute_txn_task_info_t * task_info ) {
1305 :
1306 0 : fd_exec_txn_ctx_t * txn_ctx = task_info->txn_ctx;
1307 0 : int exec_txn_err = task_info->exec_res;
1308 :
1309 : /* Store transaction info including logs */
1310 0 : fd_runtime_finalize_txns_update_blockstore_meta( slot_ctx, task_info, 1UL );
1311 :
1312 : /* For ledgers that contain txn status, decode and write out for solcap */
1313 0 : if( capture_ctx != NULL && capture_ctx->capture && capture_ctx->capture_txns ) {
1314 : // TODO: probably need to get rid of this lock or special case it to not use funk's lock.
1315 0 : fd_funk_start_write( slot_ctx->acc_mgr->funk );
1316 0 : fd_runtime_write_transaction_status( capture_ctx, slot_ctx, txn_ctx, exec_txn_err );
1317 0 : fd_funk_end_write( slot_ctx->acc_mgr->funk );
1318 0 : }
1319 :
1320 0 : if( slot_ctx->status_cache ) {
1321 0 : fd_txncache_insert_t * status_insert = fd_scratch_alloc( alignof(fd_txncache_insert_t), sizeof(fd_txncache_insert_t) );
1322 0 : uchar * results = fd_scratch_alloc( alignof(uchar), sizeof(uchar) );
1323 :
1324 0 : results[0] = exec_txn_err == 0 ? 1 : 0;
1325 0 : fd_txncache_insert_t * curr_insert = &status_insert[0];
1326 0 : curr_insert->blockhash = ((uchar *)txn_ctx->_txn_raw->raw + txn_ctx->txn_descriptor->recent_blockhash_off);
1327 0 : curr_insert->slot = slot_ctx->slot_bank.slot;
1328 0 : fd_hash_t * hash = &txn_ctx->blake_txn_msg_hash;
1329 0 : curr_insert->txnhash = hash->uc;
1330 0 : curr_insert->result = &results[0];
1331 0 : if( !fd_txncache_insert_batch( slot_ctx->status_cache, status_insert, 1UL ) ) {
1332 0 : FD_LOG_DEBUG(("Status cache is full, this should not be possible"));
1333 0 : }
1334 0 : }
1335 :
1336 0 : if( FD_UNLIKELY( exec_txn_err ) ) {
1337 :
1338 : /* Save the fee_payer. Everything but the fee balance should be reset.
1339 : TODO: an optimization here could be to use a dirty flag in the
1340 : borrowed account. If the borrowed account data has been changed in
1341 : any way, then the full account can be rolled back as it is done now.
1342 : However, most of the time the account data is not changed, and only
1343 : the lamport balance has to change. */
1344 0 : fd_borrowed_account_t * borrowed_account = fd_borrowed_account_init( &txn_ctx->borrowed_accounts[0] );
1345 :
1346 0 : fd_acc_mgr_view( txn_ctx->acc_mgr, txn_ctx->funk_txn, &txn_ctx->accounts[0], borrowed_account );
1347 0 : memcpy( borrowed_account->pubkey->key, &txn_ctx->accounts[0], sizeof(fd_pubkey_t) );
1348 :
1349 0 : void * borrowed_account_data = fd_spad_alloc( txn_ctx->spad, FD_ACCOUNT_REC_ALIGN, FD_ACC_TOT_SZ_MAX );
1350 0 : fd_borrowed_account_make_modifiable( borrowed_account, borrowed_account_data );
1351 0 : borrowed_account->meta->info.lamports -= (txn_ctx->execution_fee + txn_ctx->priority_fee);
1352 :
1353 0 : fd_acc_mgr_save_non_tpool( slot_ctx->acc_mgr, slot_ctx->funk_txn, &txn_ctx->borrowed_accounts[0] );
1354 :
1355 0 : for( ulong i=1UL; i<txn_ctx->accounts_cnt; i++ ) {
1356 0 : if( txn_ctx->nonce_accounts[i] ) {
1357 0 : ushort recent_blockhash_off = txn_ctx->txn_descriptor->recent_blockhash_off;
1358 0 : fd_hash_t * recent_blockhash = (fd_hash_t *)((uchar *)txn_ctx->_txn_raw->raw + recent_blockhash_off);
1359 0 : fd_block_hash_queue_t queue = slot_ctx->slot_bank.block_hash_queue;
1360 0 : ulong queue_sz = fd_hash_hash_age_pair_t_map_size( queue.ages_pool, queue.ages_root );
1361 0 : if( FD_UNLIKELY( !queue_sz ) ) {
1362 0 : FD_LOG_ERR(( "Blockhash queue is empty" ));
1363 0 : }
1364 :
1365 0 : if( !fd_executor_is_blockhash_valid_for_age( &queue, recent_blockhash, FD_RECENT_BLOCKHASHES_MAX_ENTRIES ) ) {
1366 0 : fd_acc_mgr_save_non_tpool( slot_ctx->acc_mgr, slot_ctx->funk_txn, &txn_ctx->borrowed_accounts[i] );
1367 0 : }
1368 0 : }
1369 0 : }
1370 0 : } else {
1371 :
1372 0 : int dirty_vote_acc = txn_ctx->dirty_vote_acc;
1373 0 : int dirty_stake_acc = txn_ctx->dirty_stake_acc;
1374 :
1375 0 : for( ulong i=0UL; i<txn_ctx->accounts_cnt; i++ ) {
1376 : /* We are only interested in saving writable accounts and the fee
1377 : payer account. */
1378 0 : if( !fd_txn_account_is_writable_idx( txn_ctx, (int)i ) || i!=FD_FEE_PAYER_TXN_IDX ) {
1379 0 : continue;
1380 0 : }
1381 :
1382 0 : fd_borrowed_account_t * acc_rec = &txn_ctx->borrowed_accounts[i];
1383 :
1384 0 : if( dirty_vote_acc && 0==memcmp( acc_rec->const_meta->info.owner, &fd_solana_vote_program_id, sizeof(fd_pubkey_t) ) ) {
1385 : /* lock for inserting/modifying vote accounts in slot ctx. */
1386 0 : fd_funk_start_write( slot_ctx->acc_mgr->funk );
1387 0 : fd_vote_store_account( slot_ctx, acc_rec, txn_ctx->spad );
1388 0 : FD_SPAD_FRAME_BEGIN( txn_ctx->spad ) {
1389 0 : fd_vote_state_versioned_t vsv[1];
1390 0 : fd_bincode_decode_ctx_t decode_vsv =
1391 0 : { .data = acc_rec->const_data,
1392 0 : .dataend = acc_rec->const_data + acc_rec->const_meta->dlen,
1393 0 : .valloc = fd_spad_virtual( txn_ctx->spad ) };
1394 :
1395 0 : int err = fd_vote_state_versioned_decode( vsv, &decode_vsv );
1396 0 : if( err ) break; /* out of scratch scope */
1397 :
1398 0 : fd_vote_block_timestamp_t const * ts = NULL;
1399 0 : switch( vsv->discriminant ) {
1400 0 : case fd_vote_state_versioned_enum_v0_23_5:
1401 0 : ts = &vsv->inner.v0_23_5.last_timestamp;
1402 0 : break;
1403 0 : case fd_vote_state_versioned_enum_v1_14_11:
1404 0 : ts = &vsv->inner.v1_14_11.last_timestamp;
1405 0 : break;
1406 0 : case fd_vote_state_versioned_enum_current:
1407 0 : ts = &vsv->inner.current.last_timestamp;
1408 0 : break;
1409 0 : default:
1410 0 : __builtin_unreachable();
1411 0 : }
1412 :
1413 0 : fd_valloc_t valloc = fd_spad_virtual( txn_ctx->spad );
1414 0 : fd_vote_record_timestamp_vote_with_slot( slot_ctx, acc_rec->pubkey, ts->timestamp, ts->slot, valloc );
1415 0 : } FD_SPAD_FRAME_END;
1416 0 : fd_funk_end_write( slot_ctx->acc_mgr->funk );
1417 0 : }
1418 :
1419 0 : if( dirty_stake_acc && 0==memcmp( acc_rec->const_meta->info.owner, &fd_solana_stake_program_id, sizeof(fd_pubkey_t) ) ) {
1420 : // TODO: does this correctly handle stake account close?
1421 0 : fd_funk_start_write( slot_ctx->acc_mgr->funk );
1422 0 : fd_store_stake_delegation( slot_ctx, acc_rec );
1423 0 : fd_funk_end_write( slot_ctx->acc_mgr->funk );
1424 0 : }
1425 :
1426 0 : fd_acc_mgr_save_non_tpool( slot_ctx->acc_mgr, slot_ctx->funk_txn, &txn_ctx->borrowed_accounts[i] );
1427 0 : }
1428 0 : }
1429 :
1430 0 : FD_ATOMIC_FETCH_AND_ADD( &slot_ctx->signature_cnt, txn_ctx->txn_descriptor->signature_cnt );
1431 :
1432 0 : return 0;
1433 0 : }
1434 :
1435 : /* fd_runtime_prepare_execute_finalize_txn is responsible for processing the
1436 : entire transaction end-to-end. */
1437 :
1438 : static int
1439 : fd_runtime_prepare_execute_finalize_txn( fd_exec_slot_ctx_t * slot_ctx,
1440 : fd_spad_t * spad,
1441 : fd_capture_ctx_t * capture_ctx,
1442 : fd_txn_p_t * txn,
1443 0 : fd_execute_txn_task_info_t * task_info ) {
1444 0 : FD_SPAD_FRAME_BEGIN( spad ) {
1445 0 : FD_SCRATCH_SCOPE_BEGIN {
1446 :
1447 0 : int res = 0;
1448 :
1449 0 : task_info->txn_ctx = fd_valloc_malloc( fd_scratch_virtual(), FD_EXEC_TXN_CTX_ALIGN, FD_EXEC_TXN_CTX_FOOTPRINT );
1450 0 : fd_exec_txn_ctx_t * txn_ctx = task_info->txn_ctx;
1451 0 : task_info->exec_res = -1;
1452 0 : task_info->txn = txn;
1453 0 : fd_txn_t const * txn_descriptor = (fd_txn_t const *) txn->_;
1454 :
1455 0 : task_info->txn_ctx->spad = spad;
1456 :
1457 0 : fd_rawtxn_b_t raw_txn = { .raw = txn->payload, .txn_sz = (ushort)txn->payload_sz };
1458 :
1459 0 : res = fd_execute_txn_prepare_start( slot_ctx, txn_ctx, txn_descriptor, &raw_txn );
1460 0 : if( FD_UNLIKELY( res ) ) {
1461 0 : txn->flags = 0U;
1462 0 : return -1;
1463 0 : }
1464 :
1465 0 : if( FD_UNLIKELY( fd_executor_txn_verify( txn_ctx )!=0 ) ) {
1466 0 : FD_LOG_WARNING(( "sigverify failed: %s", FD_BASE58_ENC_64_ALLOCA( (uchar *)txn_ctx->_txn_raw->raw+txn_ctx->txn_descriptor->signature_off ) ));
1467 0 : task_info->txn->flags = 0U;
1468 0 : task_info->exec_res = FD_RUNTIME_TXN_ERR_SIGNATURE_FAILURE;
1469 0 : }
1470 :
1471 0 : fd_runtime_pre_execute_check( task_info );
1472 0 : if( FD_UNLIKELY( !( task_info->txn->flags & FD_TXN_P_FLAGS_SANITIZE_SUCCESS ) ) ) {
1473 0 : res = task_info->exec_res;
1474 0 : return -1;
1475 0 : }
1476 :
1477 : /* Execute */
1478 0 : task_info->txn->flags |= FD_TXN_P_FLAGS_EXECUTE_SUCCESS;
1479 0 : task_info->exec_res = fd_execute_txn( task_info->txn_ctx );
1480 :
1481 0 : if( task_info->exec_res==0 ) {
1482 0 : fd_txn_reclaim_accounts( task_info->txn_ctx );
1483 0 : }
1484 :
1485 0 : FD_ATOMIC_FETCH_AND_ADD( &slot_ctx->slot_bank.collected_execution_fees, task_info->txn_ctx->execution_fee );
1486 0 : FD_ATOMIC_FETCH_AND_ADD( &slot_ctx->slot_bank.collected_priority_fees, task_info->txn_ctx->priority_fee );
1487 0 : FD_ATOMIC_FETCH_AND_ADD( &slot_ctx->slot_bank.collected_rent, task_info->txn_ctx->collected_rent );
1488 :
1489 0 : fd_runtime_finalize_txn( slot_ctx, capture_ctx, task_info );
1490 :
1491 0 : return res;
1492 :
1493 0 : } FD_SCRATCH_SCOPE_END;
1494 0 : } FD_SPAD_FRAME_END;
1495 0 : }
1496 :
1497 : /* fd_runtime_process_txns is the entrypoint for processing a batch of txns. */
1498 :
1499 : int
1500 : fd_runtime_process_txns( fd_exec_slot_ctx_t * slot_ctx,
1501 : fd_spad_t * spad,
1502 : fd_capture_ctx_t * capture_ctx,
1503 : fd_txn_p_t * txns,
1504 0 : ulong txn_cnt ) {
1505 :
1506 0 : FD_SCRATCH_SCOPE_BEGIN {
1507 :
1508 0 : fd_execute_txn_task_info_t * task_infos = fd_scratch_alloc( 8, txn_cnt * sizeof(fd_execute_txn_task_info_t));
1509 :
1510 0 : for( ulong i=0UL; i<txn_cnt; i++ ) {
1511 0 : txns[i].flags = FD_TXN_P_FLAGS_SANITIZE_SUCCESS;
1512 0 : }
1513 :
1514 0 : for( ulong i=0UL; i<txn_cnt; i++ ) {
1515 0 : fd_runtime_prepare_execute_finalize_txn( slot_ctx, spad, capture_ctx, &txns[i], &task_infos[i] );
1516 0 : }
1517 :
1518 0 : FD_ATOMIC_FETCH_AND_ADD( &slot_ctx->slot_bank.transaction_count, txn_cnt );
1519 :
1520 0 : return 0;
1521 0 : } FD_SCRATCH_SCOPE_END;
1522 :
1523 0 : }
1524 :
1525 : /* fd_runtime_execute_txn_task is a tpool wrapper around transaction execution. */
1526 :
1527 : static void FD_FN_UNUSED
1528 : fd_runtime_execute_txn_task( void * tpool,
1529 : ulong t0 FD_PARAM_UNUSED,
1530 : ulong t1 FD_PARAM_UNUSED,
1531 : void * args FD_PARAM_UNUSED,
1532 : void * reduce FD_PARAM_UNUSED,
1533 : ulong stride FD_PARAM_UNUSED,
1534 : ulong l0 FD_PARAM_UNUSED,
1535 : ulong l1 FD_PARAM_UNUSED,
1536 : ulong m0,
1537 : ulong m1 FD_PARAM_UNUSED,
1538 : ulong n0 FD_PARAM_UNUSED,
1539 0 : ulong n1 FD_PARAM_UNUSED ) {
1540 0 :
1541 0 : fd_execute_txn_task_info_t * task_info = (fd_execute_txn_task_info_t *)tpool + m0;
1542 0 :
1543 0 : if( !( task_info->txn->flags & FD_TXN_P_FLAGS_SANITIZE_SUCCESS ) ) {
1544 0 : task_info->exec_res = -1;
1545 0 : return;
1546 0 : }
1547 0 :
1548 0 : task_info->txn->flags |= FD_TXN_P_FLAGS_EXECUTE_SUCCESS;
1549 0 : task_info->exec_res = fd_execute_txn( task_info->txn_ctx );
1550 0 :
1551 0 : if( task_info->exec_res != 0 ) {
1552 0 : return;
1553 0 : }
1554 0 : fd_txn_reclaim_accounts( task_info->txn_ctx );
1555 0 : }
1556 :
1557 : /* fd_txn_sigverify_task and fd_txn_pre_execute_checks_task are responisble
1558 : for the bulk of the pre-transaction execution checks in the runtime.
1559 : They aim to preserve the ordering present in the Agave client to match
1560 : parity in terms of error codes. Sigverify is kept seperate from the rest
1561 : of the transaction checks for fuzzing convenience.
1562 :
1563 : For reference this is the general code path which contains all relevant
1564 : pre-transactions checks in the v2.0.x Agave client from upstream
1565 : to downstream is as follows:
1566 :
1567 : confirm_slot_entries() which calls verify_ticks()
1568 : (which is currently unimplemented in firedancer) and
1569 : verify_transaction(). verify_transaction() calls verify_and_hash_message()
1570 : and verify_precompiles() which parallels fd_executor_txn_verify() and
1571 : fd_executor_verify_precompiles().
1572 :
1573 : process_entries() contains a duplicate account check which is part of
1574 : agave account lock acquiring. This is checked inline in
1575 : fd_txn_pre_execute_checks_task().
1576 :
1577 : load_and_execute_transactions() contains the function check_transactions().
1578 : This contains check_age() and check_status_cache() which is paralleled by
1579 : fd_check_transaction_age() and fd_executor_check_status_cache()
1580 : respectively.
1581 :
1582 : load_and_execute_sanitized_transactions() contains validate_fees()
1583 : which is responsible for executing the compute budget instructions,
1584 : validating the fee payer and collecting the fee. This is mirrored in
1585 : firedancer with fd_executor_compute_budget_program_execute_instructions()
1586 : and fd_executor_collect_fees(). load_and_execute_sanitized_transactions()
1587 : also checks the total data size of the accounts in load_accounts() and
1588 : validates the program accounts in load_transaction_accounts(). This
1589 : is paralled by fd_executor_load_transaction_accounts(). */
1590 :
1591 : static void FD_FN_UNUSED
1592 : fd_txn_sigverify_task( void *tpool,
1593 : ulong t0 FD_PARAM_UNUSED, ulong t1 FD_PARAM_UNUSED,
1594 : void *args FD_PARAM_UNUSED,
1595 : void *reduce FD_PARAM_UNUSED, ulong stride FD_PARAM_UNUSED,
1596 : ulong l0 FD_PARAM_UNUSED, ulong l1 FD_PARAM_UNUSED,
1597 : ulong m0, ulong m1 FD_PARAM_UNUSED,
1598 0 : ulong n0 FD_PARAM_UNUSED, ulong n1 FD_PARAM_UNUSED ) {
1599 0 : fd_execute_txn_task_info_t * task_info = (fd_execute_txn_task_info_t *)tpool + m0;
1600 :
1601 : /* the txn failed sanitize sometime earlier */
1602 0 : if( FD_UNLIKELY( !( task_info->txn->flags & FD_TXN_P_FLAGS_SANITIZE_SUCCESS ) ) ) {
1603 0 : return;
1604 0 : }
1605 :
1606 0 : fd_exec_txn_ctx_t * txn_ctx = task_info->txn_ctx;
1607 0 : if( FD_UNLIKELY( fd_executor_txn_verify( txn_ctx )!=0 ) ) {
1608 0 : FD_LOG_WARNING(("sigverify failed: %s", FD_BASE58_ENC_64_ALLOCA( (uchar *)txn_ctx->_txn_raw->raw+txn_ctx->txn_descriptor->signature_off ) ));
1609 0 : task_info->txn->flags = 0U;
1610 0 : task_info->exec_res = FD_RUNTIME_TXN_ERR_SIGNATURE_FAILURE;
1611 0 : }
1612 :
1613 0 : }
1614 :
1615 : static void FD_FN_UNUSED
1616 : fd_txn_prep_and_exec_task( void *tpool,
1617 : ulong t0 FD_PARAM_UNUSED, ulong t1 FD_PARAM_UNUSED,
1618 : void *args FD_PARAM_UNUSED,
1619 : void *reduce FD_PARAM_UNUSED, ulong stride FD_PARAM_UNUSED,
1620 : ulong l0 FD_PARAM_UNUSED, ulong l1 FD_PARAM_UNUSED,
1621 : ulong m0, ulong m1 FD_PARAM_UNUSED,
1622 0 : ulong n0 FD_PARAM_UNUSED, ulong n1 FD_PARAM_UNUSED ) {
1623 :
1624 0 : fd_execute_txn_task_info_t * task_info = (fd_execute_txn_task_info_t *)tpool + m0;
1625 0 : fd_exec_slot_ctx_t * slot_ctx = (fd_exec_slot_ctx_t *)args;
1626 : // fd_capture_ctx_t * capture_ctx = (fd_capture_ctx_t *)reduce;
1627 :
1628 : /* It is important to note that there is currently a 1-1 mapping between the
1629 : tiles and tpool threads at the time of this comment. Eventually, this will
1630 : change and the transaction context's spad will not be queried by tile
1631 : index as every tile will correspond to one CPU core. */
1632 0 : ulong tile_idx = fd_tile_idx();
1633 0 : task_info->txn_ctx->spad = task_info->spads[ tile_idx ];
1634 0 : if( FD_UNLIKELY( !task_info->txn_ctx->spad ) ) {
1635 0 : FD_LOG_ERR(("spad is NULL"));
1636 0 : }
1637 :
1638 0 : fd_runtime_pre_execute_check( task_info );
1639 :
1640 : /* Transaction sanitization is complete at this point. Now finish account
1641 : setup, execute the transaction, and reclaim dead accounts. */
1642 0 : if( FD_LIKELY( !task_info->exec_res ) ) {
1643 0 : task_info->txn->flags |= FD_TXN_P_FLAGS_EXECUTE_SUCCESS;
1644 0 : task_info->exec_res = fd_execute_txn( task_info->txn_ctx );
1645 0 : fd_txn_reclaim_accounts( task_info->txn_ctx );
1646 0 : }
1647 :
1648 0 : FD_ATOMIC_FETCH_AND_ADD( &slot_ctx->slot_bank.collected_execution_fees, task_info->txn_ctx->execution_fee );
1649 0 : FD_ATOMIC_FETCH_AND_ADD( &slot_ctx->slot_bank.collected_priority_fees, task_info->txn_ctx->priority_fee );
1650 0 : FD_ATOMIC_FETCH_AND_ADD( &slot_ctx->slot_bank.collected_rent, task_info->txn_ctx->collected_rent );
1651 :
1652 : // fd_runtime_finalize_txn( slot_ctx, capture_ctx, task_info );
1653 :
1654 0 : }
1655 :
1656 : /* This task could be combined with the rest of the transaction checks that
1657 : exist in fd_runtime_prepare_txns_phase2_tpool, but creates a lot more
1658 : complexity to make the transaction fuzzer work. */
1659 : static int
1660 : fd_runtime_verify_txn_signatures_tpool( fd_execute_txn_task_info_t * task_info,
1661 : ulong txn_cnt,
1662 0 : fd_tpool_t * tpool ) {
1663 0 : int res = 0;
1664 0 : fd_tpool_exec_all_rrobin( tpool, 0, fd_tpool_worker_cnt( tpool ), fd_txn_sigverify_task, task_info, NULL, NULL, 1, 0, txn_cnt );
1665 0 : for( ulong txn_idx = 0; txn_idx < txn_cnt; txn_idx++ ) {
1666 0 : if( FD_UNLIKELY( !( task_info[txn_idx].txn->flags & FD_TXN_P_FLAGS_SANITIZE_SUCCESS ) ) ) {
1667 0 : task_info->exec_res = FD_RUNTIME_TXN_ERR_SIGNATURE_FAILURE;
1668 0 : res |= FD_RUNTIME_TXN_ERR_SIGNATURE_FAILURE;
1669 0 : break;
1670 0 : }
1671 0 : }
1672 0 : return res;
1673 0 : }
1674 :
1675 : /* This setup phase sets up the borrowed accounts in each transaction and
1676 : performs a series of checks on each of the transactions. */
1677 : int
1678 : fd_runtime_prep_and_exec_txns_tpool( fd_exec_slot_ctx_t * slot_ctx,
1679 : fd_execute_txn_task_info_t * task_info,
1680 : ulong txn_cnt,
1681 0 : fd_tpool_t * tpool ) {
1682 0 : int res = 0;
1683 0 : FD_SCRATCH_SCOPE_BEGIN {
1684 :
1685 0 : fd_tpool_exec_all_rrobin( tpool, 0, fd_tpool_worker_cnt( tpool ), fd_txn_prep_and_exec_task, task_info, slot_ctx, task_info->txn_ctx->capture_ctx, 1, 0, txn_cnt );
1686 :
1687 0 : for( ulong txn_idx=0UL; txn_idx<txn_cnt; txn_idx++ ) {
1688 0 : if( FD_UNLIKELY( !( task_info[txn_idx].txn->flags & FD_TXN_P_FLAGS_SANITIZE_SUCCESS ) ) ) {
1689 0 : res |= task_info[txn_idx].exec_res;
1690 0 : continue;
1691 0 : }
1692 0 : }
1693 :
1694 0 : } FD_SCRATCH_SCOPE_END;
1695 0 : return res;
1696 0 : }
1697 :
1698 : /* https://github.com/anza-xyz/agave/blob/16de8b75ebcd57022409b422de557dd37b1de8db/accounts-db/src/accounts.rs#L700 */
1699 : /* fd_runtime_finalize_txns_tpool is the tpool version of fd_runtime_finalize_txn. */
1700 :
1701 : static int
1702 : fd_runtime_finalize_txns_tpool( fd_exec_slot_ctx_t * slot_ctx,
1703 : fd_capture_ctx_t * capture_ctx,
1704 : fd_execute_txn_task_info_t * task_info,
1705 : ulong txn_cnt,
1706 0 : fd_tpool_t * tpool ) {
1707 0 : FD_SCRATCH_SCOPE_BEGIN {
1708 :
1709 : /* Store transaction metadata, including logs */
1710 0 : fd_runtime_finalize_txns_update_blockstore_meta( slot_ctx, task_info, txn_cnt );
1711 :
1712 0 : fd_txncache_insert_t * status_insert = NULL;
1713 0 : uchar * results = NULL;
1714 0 : ulong num_cache_txns = 0UL;
1715 :
1716 :
1717 0 : if( FD_LIKELY( slot_ctx->status_cache ) ) {
1718 0 : status_insert = fd_scratch_alloc( alignof(fd_txncache_insert_t), txn_cnt * sizeof(fd_txncache_insert_t) );
1719 0 : results = fd_scratch_alloc( alignof(uchar), txn_cnt * sizeof(uchar) );
1720 0 : }
1721 :
1722 0 : fd_borrowed_account_t * * accounts_to_save = fd_scratch_alloc( 8UL, MAX_TX_ACCOUNT_LOCKS * txn_cnt * sizeof(fd_borrowed_account_t *) );
1723 0 : ulong acc_idx = 0UL;
1724 0 : ulong nonvote_txn_count = 0UL;
1725 0 : ulong failed_txn_count = 0UL;
1726 0 : ulong nonvote_failed_txn_count = 0UL;
1727 0 : ulong compute_units_used = 0UL;
1728 :
1729 0 : for( ulong txn_idx=0UL; txn_idx<txn_cnt; txn_idx++ ) {
1730 : /* Transaction was skipped due to preparation failure. */
1731 0 : if( FD_UNLIKELY( !( task_info[txn_idx].txn->flags & FD_TXN_P_FLAGS_EXECUTE_SUCCESS ) ) ) {
1732 0 : continue;
1733 0 : }
1734 0 : fd_exec_txn_ctx_t * txn_ctx = task_info[txn_idx].txn_ctx;
1735 0 : int exec_txn_err = task_info[txn_idx].exec_res;
1736 :
1737 : /* For ledgers that contain txn status, decode and write out for solcap */
1738 0 : if( FD_UNLIKELY( capture_ctx != NULL && capture_ctx->capture && capture_ctx->capture_txns ) ) {
1739 0 : fd_runtime_write_transaction_status( capture_ctx, slot_ctx, txn_ctx, exec_txn_err );
1740 0 : }
1741 :
1742 0 : slot_ctx->signature_cnt += txn_ctx->txn_descriptor->signature_cnt;
1743 :
1744 0 : int is_vote = fd_txn_is_simple_vote_transaction( txn_ctx->txn_descriptor,
1745 0 : txn_ctx->_txn_raw->raw,
1746 0 : fd_solana_vote_program_id.key );
1747 0 : if( is_vote ) {
1748 0 : if( FD_UNLIKELY( exec_txn_err ) ) {
1749 0 : failed_txn_count++;
1750 0 : }
1751 0 : } else {
1752 0 : nonvote_txn_count++;
1753 0 : if( FD_UNLIKELY( exec_txn_err ) ) {
1754 0 : nonvote_failed_txn_count++;
1755 0 : failed_txn_count++;
1756 0 : }
1757 0 : }
1758 0 : compute_units_used += txn_ctx->compute_unit_limit - txn_ctx->compute_meter;
1759 :
1760 0 : if( FD_LIKELY( slot_ctx->status_cache ) ) {
1761 0 : results[num_cache_txns] = exec_txn_err == 0 ? 1 : 0;
1762 0 : fd_txncache_insert_t * curr_insert = &status_insert[num_cache_txns];
1763 0 : curr_insert->blockhash = ((uchar *)txn_ctx->_txn_raw->raw + txn_ctx->txn_descriptor->recent_blockhash_off);
1764 0 : curr_insert->slot = slot_ctx->slot_bank.slot;
1765 0 : fd_hash_t * hash = &txn_ctx->blake_txn_msg_hash;
1766 0 : curr_insert->txnhash = hash->uc;
1767 0 : curr_insert->result = &results[num_cache_txns];
1768 0 : num_cache_txns++;
1769 0 : }
1770 :
1771 0 : if( FD_UNLIKELY( exec_txn_err ) ) {
1772 : /* Save the fee_payer. Everything but the fee balance should be reset.
1773 : TODO: an optimization here could be to use a dirty flag in the
1774 : borrowed account. If the borrowed account data has been changed in
1775 : any way, then the full account can be rolled back as it is done now.
1776 : However, most of the time the account data is not changed, and only
1777 : the lamport balance has to change. */
1778 0 : fd_borrowed_account_t * borrowed_account = fd_borrowed_account_init( &txn_ctx->borrowed_accounts[ FD_FEE_PAYER_TXN_IDX ] );
1779 :
1780 0 : fd_acc_mgr_view( txn_ctx->acc_mgr, txn_ctx->funk_txn, &txn_ctx->accounts[ FD_FEE_PAYER_TXN_IDX ], borrowed_account );
1781 0 : memcpy( borrowed_account->pubkey->key, &txn_ctx->accounts[ FD_FEE_PAYER_TXN_IDX ], sizeof(fd_pubkey_t) );
1782 :
1783 0 : void * borrowed_account_data = fd_spad_alloc( txn_ctx->spad, FD_ACCOUNT_REC_ALIGN, FD_ACC_TOT_SZ_MAX );
1784 0 : fd_borrowed_account_make_modifiable( borrowed_account, borrowed_account_data );
1785 0 : borrowed_account->meta->info.lamports -= (txn_ctx->execution_fee + txn_ctx->priority_fee);
1786 :
1787 0 : accounts_to_save[acc_idx++] = &txn_ctx->borrowed_accounts[ FD_FEE_PAYER_TXN_IDX ];
1788 0 : for( ulong i=1UL; i<txn_ctx->accounts_cnt; i++ ) {
1789 0 : if( txn_ctx->nonce_accounts[i] ) {
1790 0 : ushort recent_blockhash_off = txn_ctx->txn_descriptor->recent_blockhash_off;
1791 0 : fd_hash_t * recent_blockhash = (fd_hash_t *)((uchar *)txn_ctx->_txn_raw->raw + recent_blockhash_off);
1792 0 : fd_block_hash_queue_t queue = slot_ctx->slot_bank.block_hash_queue;
1793 0 : ulong queue_sz = fd_hash_hash_age_pair_t_map_size( queue.ages_pool, queue.ages_root );
1794 0 : if( FD_UNLIKELY( !queue_sz ) ) {
1795 0 : FD_LOG_ERR(( "Blockhash queue is empty" ));
1796 0 : }
1797 :
1798 0 : if( !fd_executor_is_blockhash_valid_for_age( &queue, recent_blockhash, FD_RECENT_BLOCKHASHES_MAX_ENTRIES ) ) {
1799 0 : accounts_to_save[acc_idx++] = &txn_ctx->borrowed_accounts[i];
1800 0 : }
1801 0 : break;
1802 0 : }
1803 0 : }
1804 0 : } else {
1805 0 : int dirty_vote_acc = txn_ctx->dirty_vote_acc;
1806 0 : int dirty_stake_acc = txn_ctx->dirty_stake_acc;
1807 :
1808 0 : for( ulong i=0UL; i<txn_ctx->accounts_cnt; i++ ) {
1809 : /* We are only interested in saving writable accounts and the fee
1810 : payer account. */
1811 0 : if( !fd_txn_account_is_writable_idx( txn_ctx, (int)i ) && i!=FD_FEE_PAYER_TXN_IDX ) {
1812 0 : continue;
1813 0 : }
1814 :
1815 0 : fd_borrowed_account_t * acc_rec = &txn_ctx->borrowed_accounts[i];
1816 :
1817 0 : if( dirty_vote_acc && !memcmp( acc_rec->const_meta->info.owner, &fd_solana_vote_program_id, sizeof(fd_pubkey_t) ) ) {
1818 0 : fd_vote_store_account( slot_ctx, acc_rec, txn_ctx->spad );
1819 0 : FD_SPAD_FRAME_BEGIN( txn_ctx->spad ) {
1820 0 : fd_vote_state_versioned_t vsv[1];
1821 0 : fd_bincode_decode_ctx_t decode_vsv =
1822 0 : { .data = acc_rec->const_data,
1823 0 : .dataend = acc_rec->const_data + acc_rec->const_meta->dlen,
1824 0 : .valloc = fd_spad_virtual( txn_ctx->spad ) };
1825 :
1826 0 : int err = fd_vote_state_versioned_decode( vsv, &decode_vsv );
1827 0 : if( err ) break; /* out of scratch scope */
1828 :
1829 0 : fd_vote_block_timestamp_t const * ts = NULL;
1830 0 : switch( vsv->discriminant ) {
1831 0 : case fd_vote_state_versioned_enum_v0_23_5:
1832 0 : ts = &vsv->inner.v0_23_5.last_timestamp;
1833 0 : break;
1834 0 : case fd_vote_state_versioned_enum_v1_14_11:
1835 0 : ts = &vsv->inner.v1_14_11.last_timestamp;
1836 0 : break;
1837 0 : case fd_vote_state_versioned_enum_current:
1838 0 : ts = &vsv->inner.current.last_timestamp;
1839 0 : break;
1840 0 : default:
1841 0 : __builtin_unreachable();
1842 0 : }
1843 :
1844 0 : fd_valloc_t valloc = fd_spad_virtual( txn_ctx->spad );
1845 0 : fd_vote_record_timestamp_vote_with_slot( slot_ctx, acc_rec->pubkey, ts->timestamp, ts->slot, valloc );
1846 0 : } FD_SPAD_FRAME_END;
1847 0 : }
1848 :
1849 0 : if( dirty_stake_acc && !memcmp( acc_rec->const_meta->info.owner, &fd_solana_stake_program_id, sizeof(fd_pubkey_t) ) ) {
1850 : // TODO: does this correctly handle stake account close?
1851 0 : fd_store_stake_delegation( slot_ctx, acc_rec );
1852 0 : }
1853 :
1854 0 : accounts_to_save[acc_idx++] = acc_rec;
1855 0 : }
1856 0 : }
1857 0 : }
1858 :
1859 : /* Accumulate transaction counters */
1860 :
1861 0 : FD_ATOMIC_FETCH_AND_ADD( &slot_ctx->nonvote_txn_count, nonvote_txn_count );
1862 0 : FD_ATOMIC_FETCH_AND_ADD( &slot_ctx->failed_txn_count, failed_txn_count );
1863 0 : FD_ATOMIC_FETCH_AND_ADD( &slot_ctx->nonvote_failed_txn_count, nonvote_failed_txn_count );
1864 0 : FD_ATOMIC_FETCH_AND_ADD( &slot_ctx->total_compute_units_used, compute_units_used );
1865 :
1866 : /* All the accounts have been accumulated and can be saved */
1867 :
1868 : // TODO: we need to use the txn ctx funk_txn, valloc, etc.
1869 0 : int err = fd_acc_mgr_save_many_tpool( slot_ctx->acc_mgr, slot_ctx->funk_txn, accounts_to_save, acc_idx, tpool );
1870 0 : if( FD_UNLIKELY( err!=FD_ACC_MGR_SUCCESS ) ) {
1871 0 : FD_LOG_ERR(( "failed to save edits to accounts" ));
1872 0 : return -1;
1873 0 : }
1874 :
1875 0 : if( FD_LIKELY( slot_ctx->status_cache ) ) {
1876 0 : if( FD_UNLIKELY( !fd_txncache_insert_batch( slot_ctx->status_cache, status_insert, num_cache_txns ) ) ) {
1877 0 : FD_LOG_WARNING(("Status cache is full, this should not be possible"));
1878 0 : }
1879 0 : }
1880 :
1881 0 : return 0;
1882 0 : } FD_SCRATCH_SCOPE_END;
1883 0 : }
1884 :
1885 : struct fd_pubkey_map_node {
1886 : ulong pubkey;
1887 : uint hash;
1888 : };
1889 : typedef struct fd_pubkey_map_node fd_pubkey_map_node_t;
1890 :
1891 : #define MAP_NAME fd_pubkey_map
1892 0 : #define MAP_T fd_pubkey_map_node_t
1893 0 : #define MAP_KEY pubkey
1894 0 : #define MAP_KEY_T ulong
1895 0 : #define MAP_KEY_NULL 0
1896 0 : #define MAP_KEY_INVAL( k ) k==0
1897 0 : #define MAP_KEY_EQUAL( k0, k1 ) k0==k1
1898 : #define MAP_KEY_EQUAL_IS_SLOW 1
1899 0 : #define MAP_KEY_HASH( key ) ( (uint)key )
1900 : #define MAP_MEMOIZE 1
1901 : #include "../../util/tmpl/fd_map_dynamic.c"
1902 :
1903 : /* return 0 on failure, 1 if exists, 2 if inserted */
1904 : static uint
1905 : fd_pubkey_map_insert_if_not_in( fd_pubkey_map_node_t * map,
1906 0 : fd_pubkey_t pubkey ) {
1907 : /* Check if entry already exists */
1908 0 : ulong h = fd_hash( 0UL, &pubkey, sizeof( fd_pubkey_t ) );
1909 0 : fd_pubkey_map_node_t * entry = fd_pubkey_map_query( map, h, NULL );
1910 0 : if( entry )
1911 0 : return 1;
1912 :
1913 : /* Insert new */
1914 0 : entry = fd_pubkey_map_insert( map, h );
1915 0 : if( FD_UNLIKELY( !entry ) ) return 0; /* check for internal map collision */
1916 :
1917 0 : return 2;
1918 0 : }
1919 :
1920 : /* fd_runtime_generate_wave is responsible for scheduling parallel transactions */
1921 :
1922 : static void
1923 : fd_runtime_generate_wave( fd_execute_txn_task_info_t * task_infos,
1924 : ulong * prev_incomplete_txn_idxs,
1925 : ulong prev_incomplete_txn_idxs_cnt,
1926 : ulong prev_accounts_cnt,
1927 : ulong * incomplete_txn_idxs,
1928 : ulong * _incomplete_txn_idxs_cnt,
1929 : ulong * _incomplete_accounts_cnt,
1930 : fd_execute_txn_task_info_t * wave_task_infos,
1931 0 : ulong * _wave_task_infos_cnt ) {
1932 0 : FD_SCRATCH_SCOPE_BEGIN {
1933 0 : int lg_slot_cnt = fd_ulong_find_msb( prev_accounts_cnt ) + 1;
1934 0 : void * read_map_mem = fd_scratch_alloc( fd_pubkey_map_align(), fd_pubkey_map_footprint( lg_slot_cnt ) );
1935 0 : fd_pubkey_map_node_t * read_map = fd_pubkey_map_join( fd_pubkey_map_new( read_map_mem, lg_slot_cnt ) );
1936 :
1937 0 : void * write_map_mem = fd_scratch_alloc( fd_pubkey_map_align(), fd_pubkey_map_footprint( lg_slot_cnt ) );
1938 0 : fd_pubkey_map_node_t * write_map = fd_pubkey_map_join( fd_pubkey_map_new( write_map_mem, lg_slot_cnt ) );
1939 :
1940 0 : ulong incomplete_txn_idxs_cnt = 0UL;
1941 0 : ulong wave_task_infos_cnt = 0UL;
1942 0 : ulong accounts_in_wave = 0UL;
1943 0 : for( ulong i=0UL; i<prev_incomplete_txn_idxs_cnt; i++ ) {
1944 0 : ulong txn_idx = prev_incomplete_txn_idxs[i];
1945 0 : uint is_executable_now = 1;
1946 0 : fd_execute_txn_task_info_t * task_info = &task_infos[txn_idx];
1947 :
1948 0 : for( ulong j=0UL; j<task_info->txn_ctx->accounts_cnt; j++ ) {
1949 0 : ulong h = fd_hash( 0UL, &task_info->txn_ctx->accounts[j], sizeof( fd_pubkey_t ) );
1950 0 : if( fd_pubkey_map_query( write_map, h, NULL ) != NULL ) {
1951 0 : is_executable_now = 0;
1952 0 : break;
1953 0 : }
1954 0 : if( fd_txn_account_is_writable_idx( task_info->txn_ctx, (int)j ) ) {
1955 0 : if( fd_pubkey_map_query( read_map, h, NULL ) != NULL ) {
1956 0 : is_executable_now = 0;
1957 0 : break;
1958 0 : }
1959 0 : }
1960 0 : }
1961 :
1962 0 : if( !is_executable_now ) {
1963 0 : incomplete_txn_idxs[incomplete_txn_idxs_cnt++] = txn_idx;
1964 0 : } else {
1965 0 : wave_task_infos[wave_task_infos_cnt++] = *task_info;
1966 0 : }
1967 :
1968 : /* Include txn in wave */
1969 0 : for( ulong j=0UL; j<task_info->txn_ctx->accounts_cnt; j++ ) {
1970 0 : if( fd_txn_account_is_writable_idx( task_info->txn_ctx, (int)j ) ) {
1971 0 : uint ins_res = fd_pubkey_map_insert_if_not_in( write_map, task_info->txn_ctx->accounts[j] );
1972 0 : if( ins_res==2U ) {
1973 0 : accounts_in_wave++;
1974 0 : }
1975 0 : } else {
1976 0 : uint ins_res = fd_pubkey_map_insert_if_not_in( read_map, task_info->txn_ctx->accounts[j] );
1977 0 : if( ins_res==2UL ) {
1978 0 : accounts_in_wave++;
1979 0 : }
1980 0 : }
1981 0 : }
1982 :
1983 0 : }
1984 :
1985 0 : *_incomplete_txn_idxs_cnt = incomplete_txn_idxs_cnt;
1986 0 : *_incomplete_accounts_cnt = prev_accounts_cnt - accounts_in_wave;
1987 0 : *_wave_task_infos_cnt = wave_task_infos_cnt;
1988 0 : } FD_SCRATCH_SCOPE_END;
1989 0 : }
1990 :
1991 : /* NOTE: Don't mess with this call without updating the transaction fuzzing harness appropriately!
1992 : fd_exec_instr_test.c:_txn_context_create_and_exec.
1993 :
1994 : This function is the tpool version of fd_runtime_process_txn. */
1995 : int
1996 : fd_runtime_process_txns_in_waves_tpool( fd_exec_slot_ctx_t * slot_ctx,
1997 : fd_capture_ctx_t * capture_ctx,
1998 : fd_txn_p_t * all_txns,
1999 : ulong total_txn_cnt,
2000 : fd_tpool_t * tpool,
2001 : fd_spad_t * * spads,
2002 0 : ulong spad_cnt ) {
2003 0 : int dump_txn = capture_ctx && slot_ctx->slot_bank.slot >= capture_ctx->dump_proto_start_slot && capture_ctx->dump_txn_to_pb;
2004 :
2005 : /* As a note, the batch size of 128 is a relatively arbitrary number. The
2006 : notion of batching here will change as the transaction execution model
2007 : changes with respect to transaction execution. */
2008 0 : #define BATCH_SIZE (128UL)
2009 0 : ulong batch_size = fd_ulong_min( fd_tile_cnt(), BATCH_SIZE );
2010 :
2011 0 : for( ulong i=0UL; i<total_txn_cnt; i++ ) {
2012 0 : all_txns[i].flags = FD_TXN_P_FLAGS_SANITIZE_SUCCESS;
2013 0 : }
2014 :
2015 0 : ulong num_batches = total_txn_cnt/batch_size;
2016 0 : ulong rem = total_txn_cnt%batch_size;
2017 0 : num_batches += rem ? 1UL : 0UL;
2018 :
2019 0 : int res = 0;
2020 0 : for( ulong i=0UL; i<num_batches; i++ ) {
2021 0 : FD_SCRATCH_SCOPE_BEGIN {
2022 :
2023 0 : fd_txn_p_t * txns = all_txns + (batch_size * i);
2024 0 : ulong txn_cnt = ((i+1UL==num_batches) && rem) ? rem : batch_size;
2025 :
2026 0 : fd_execute_txn_task_info_t * task_infos = fd_scratch_alloc( 8UL, txn_cnt * sizeof(fd_execute_txn_task_info_t) );
2027 0 : fd_execute_txn_task_info_t * wave_task_infos = fd_scratch_alloc( 8UL, txn_cnt * sizeof(fd_execute_txn_task_info_t) );
2028 0 : ulong wave_task_infos_cnt = 0UL;
2029 :
2030 0 : res = fd_runtime_prepare_txns_start( slot_ctx, task_infos, txns, txn_cnt );
2031 0 : if( res != 0 ) {
2032 0 : FD_LOG_DEBUG(("Fail prep 1"));
2033 0 : }
2034 :
2035 0 : ulong * incomplete_txn_idxs = fd_scratch_alloc( 8UL, txn_cnt * sizeof(ulong) );
2036 0 : ulong incomplete_txn_idxs_cnt = 0UL;
2037 0 : ulong incomplete_accounts_cnt = 0UL;
2038 :
2039 : /* Setup sanitized txns as incomplete and set the capture context */
2040 0 : for( ulong i=0UL; i<txn_cnt; i++ ) {
2041 0 : if( FD_UNLIKELY( !( task_infos[i].txn->flags & FD_TXN_P_FLAGS_SANITIZE_SUCCESS ) ) ) {
2042 0 : continue;
2043 0 : }
2044 0 : incomplete_txn_idxs[incomplete_txn_idxs_cnt++] = i;
2045 0 : incomplete_accounts_cnt += task_infos[i].txn_ctx->accounts_cnt;
2046 0 : task_infos[i].txn_ctx->capture_ctx = capture_ctx;
2047 0 : }
2048 :
2049 0 : ulong * next_incomplete_txn_idxs = fd_scratch_alloc( 8UL, txn_cnt * sizeof(ulong) );
2050 0 : ulong next_incomplete_txn_idxs_cnt = 0UL;
2051 0 : ulong next_incomplete_accounts_cnt = 0UL;
2052 :
2053 0 : while( incomplete_txn_idxs_cnt > 0 ) {
2054 0 : fd_runtime_generate_wave( task_infos, incomplete_txn_idxs, incomplete_txn_idxs_cnt, incomplete_accounts_cnt,
2055 0 : next_incomplete_txn_idxs, &next_incomplete_txn_idxs_cnt, &next_incomplete_accounts_cnt,
2056 0 : wave_task_infos, &wave_task_infos_cnt );
2057 0 : ulong * temp_incomplete_txn_idxs = incomplete_txn_idxs;
2058 0 : incomplete_txn_idxs = next_incomplete_txn_idxs;
2059 0 : next_incomplete_txn_idxs = temp_incomplete_txn_idxs;
2060 0 : incomplete_txn_idxs_cnt = next_incomplete_txn_idxs_cnt;
2061 :
2062 0 : for( ulong i=0UL; i<spad_cnt; i++ ) {
2063 : /* Borrowed accounts are allocated during prep and need to
2064 : persist till the end of finalize. This initial frame will
2065 : be holding that. */
2066 0 : fd_spad_push( spads[ i ] );
2067 0 : }
2068 :
2069 : /* Assign out spads to the transaction contexts */
2070 0 : for( ulong i=0UL; i<wave_task_infos_cnt; i++ ) {
2071 0 : wave_task_infos[i].spads = spads;
2072 0 : }
2073 :
2074 : // Dump txns in waves
2075 0 : if( dump_txn ) {
2076 0 : for( ulong i = 0; i < wave_task_infos_cnt; ++i ) {
2077 : /* Manual push/pop on the spad within the callee. */
2078 0 : fd_dump_txn_to_protobuf( wave_task_infos[i].txn_ctx, spads[0] );
2079 0 : }
2080 0 : }
2081 :
2082 0 : res |= fd_runtime_verify_txn_signatures_tpool( wave_task_infos, wave_task_infos_cnt, tpool );
2083 0 : if( FD_UNLIKELY( res ) ) {
2084 0 : FD_LOG_WARNING(( "Fail signature verification" ));
2085 0 : }
2086 :
2087 0 : res |= fd_runtime_prep_and_exec_txns_tpool( slot_ctx, wave_task_infos, wave_task_infos_cnt, tpool );
2088 0 : if( res != 0 ) {
2089 0 : FD_LOG_DEBUG(( "Fail prep and exec" ));
2090 0 : }
2091 :
2092 0 : int finalize_res = fd_runtime_finalize_txns_tpool( slot_ctx, capture_ctx, wave_task_infos, wave_task_infos_cnt, tpool );
2093 0 : if( finalize_res != 0 ) {
2094 0 : FD_LOG_ERR(( "Fail finalize" ));
2095 0 : }
2096 :
2097 0 : for( ulong i=0UL; i<spad_cnt; i++ ) {
2098 : /* The first frame, which holds borrowed accounts, can be
2099 : pretty big, and there are additional dynamic allocations
2100 : during finalize. */
2101 0 : if( FD_UNLIKELY( fd_spad_verify( spads[ i ] ) ) ) {
2102 0 : FD_LOG_ERR(( "spad corrupted or overflown" ));
2103 0 : }
2104 : /* We indiscriminately pushed a frame to every spad.
2105 : So it should be safe to indiscriminately pop here. */
2106 0 : fd_spad_pop( spads[ i ] );
2107 0 : if( FD_UNLIKELY( fd_spad_frame_used( spads[ i ] )!=0 ) ) {
2108 0 : FD_LOG_ERR(( "stray spad frame frame_used=%lu", fd_spad_frame_used( spads[ i ] ) ));
2109 0 : }
2110 0 : }
2111 :
2112 0 : }
2113 0 : } FD_SCRATCH_SCOPE_END;
2114 0 : }
2115 0 : slot_ctx->slot_bank.transaction_count += total_txn_cnt;
2116 :
2117 0 : #undef BATCH_SIZE
2118 :
2119 0 : return res;
2120 0 : }
2121 :
2122 : /******************************************************************************/
2123 : /* Epoch Boundary */
2124 : /******************************************************************************/
2125 :
2126 : /* Update the epoch bank stakes cache with the delegated stake values from the slot bank cache.
2127 : The slot bank cache will have been accumulating this epoch, and now we are at an epoch boundary
2128 : we can safely update the epoch stakes cache with the latest values.
2129 :
2130 : In Solana, the stakes cache is updated after every transaction
2131 : (https://github.com/solana-labs/solana/blob/c091fd3da8014c0ef83b626318018f238f506435/runtime/src/bank.rs#L7587).
2132 : As delegations have to warm up, the contents of the cache will not change inter-epoch. We can therefore update
2133 : the cache only at epoch boundaries.
2134 :
2135 : https://github.com/solana-labs/solana/blob/c091fd3da8014c0ef83b626318018f238f506435/runtime/src/stakes.rs#L65 */
2136 : static void
2137 0 : fd_update_stake_delegations( fd_exec_slot_ctx_t * slot_ctx, fd_epoch_info_t * temp_info ) {
2138 0 : FD_SCRATCH_SCOPE_BEGIN {
2139 0 : fd_epoch_bank_t * epoch_bank = fd_exec_epoch_ctx_epoch_bank( slot_ctx->epoch_ctx );
2140 0 : fd_stakes_t * stakes = &epoch_bank->stakes;
2141 :
2142 : // TODO: is this size correct if the same stake account is in both the slot and epoch cache? Is this possible?
2143 0 : ulong stake_delegations_size = fd_delegation_pair_t_map_size(
2144 0 : stakes->stake_delegations_pool, stakes->stake_delegations_root );
2145 0 : stake_delegations_size += fd_stake_accounts_pair_t_map_size(
2146 0 : slot_ctx->slot_bank.stake_account_keys.stake_accounts_pool, slot_ctx->slot_bank.stake_account_keys.stake_accounts_root );
2147 :
2148 : // Create a new epoch stake delegations cache, which will hold the union of the slot and epoch caches.
2149 0 : fd_delegation_pair_t_mapnode_t * new_stake_root = NULL;
2150 0 : fd_delegation_pair_t_mapnode_t * new_stake_pool = fd_delegation_pair_t_map_alloc( fd_scratch_virtual(), stake_delegations_size );
2151 :
2152 0 : for ( ulong idx = 0; idx < temp_info->infos_len; idx++ ) {
2153 : // Fetch the delegation associated with this stake account
2154 0 : fd_delegation_pair_t_mapnode_t * entry = fd_delegation_pair_t_map_acquire( new_stake_pool );
2155 0 : fd_memcpy(&entry->elem.account, &temp_info->infos[idx].account, sizeof(fd_pubkey_t));
2156 0 : fd_memcpy(&entry->elem.delegation, &temp_info->infos[idx].stake.delegation, sizeof(fd_delegation_t));
2157 0 : fd_delegation_pair_t_map_insert( new_stake_pool, &new_stake_root, entry );
2158 0 : }
2159 :
2160 : // Update the epoch bank vote_accounts with the latest values from the slot bank
2161 : // FIXME: resize the vote_accounts_pool if necessary
2162 0 : ulong total_epoch_stake = 0UL;
2163 0 : for ( fd_vote_accounts_pair_t_mapnode_t * n = fd_vote_accounts_pair_t_map_minimum(
2164 0 : slot_ctx->slot_bank.vote_account_keys.vote_accounts_pool,
2165 0 : slot_ctx->slot_bank.vote_account_keys.vote_accounts_root );
2166 0 : n;
2167 0 : n = fd_vote_accounts_pair_t_map_successor( slot_ctx->slot_bank.vote_account_keys.vote_accounts_pool, n ) ) {
2168 :
2169 : // If the vote account is not in the epoch stakes cache, insert it
2170 0 : fd_vote_accounts_pair_t_mapnode_t key;
2171 0 : fd_memcpy( &key.elem.key, &n->elem.key, FD_PUBKEY_FOOTPRINT );
2172 0 : fd_vote_accounts_pair_t_mapnode_t * epoch_cache_node = fd_vote_accounts_pair_t_map_find( stakes->vote_accounts.vote_accounts_pool, stakes->vote_accounts.vote_accounts_root, &key );
2173 0 : if( epoch_cache_node == NULL ) {
2174 0 : epoch_cache_node = fd_vote_accounts_pair_t_map_acquire( stakes->vote_accounts.vote_accounts_pool );
2175 :
2176 0 : fd_memcpy(&epoch_cache_node->elem.key, &n->elem.key, sizeof(fd_pubkey_t));
2177 0 : fd_memcpy(&epoch_cache_node->elem.stake, &n->elem.stake, sizeof(ulong));
2178 0 : fd_memcpy(&epoch_cache_node->elem.value, &n->elem.value, sizeof(fd_solana_account_t));
2179 :
2180 0 : fd_vote_accounts_pair_t_map_insert( stakes->vote_accounts.vote_accounts_pool, &stakes->vote_accounts.vote_accounts_root, epoch_cache_node );
2181 0 : } else {
2182 0 : epoch_cache_node->elem.stake = n->elem.stake;
2183 0 : }
2184 0 : total_epoch_stake += epoch_cache_node->elem.stake;
2185 0 : }
2186 0 : slot_ctx->epoch_ctx->total_epoch_stake = total_epoch_stake;
2187 :
2188 0 : fd_bincode_destroy_ctx_t destroy_slot = { .valloc = slot_ctx->valloc };
2189 0 : fd_vote_accounts_destroy( &slot_ctx->slot_bank.vote_account_keys, &destroy_slot );
2190 0 : fd_stake_accounts_destroy(&slot_ctx->slot_bank.stake_account_keys, &destroy_slot );
2191 :
2192 : /* Release all nodes in tree.
2193 : FIXME sweep pool and ignore tree nodes might is probably faster
2194 : than recursive descent */
2195 0 : fd_delegation_pair_t_map_release_tree( stakes->stake_delegations_pool, stakes->stake_delegations_root );
2196 0 : stakes->stake_delegations_root = NULL;
2197 :
2198 0 : for( fd_delegation_pair_t_mapnode_t * n = fd_delegation_pair_t_map_minimum( new_stake_pool, new_stake_root ); n; n = fd_delegation_pair_t_map_successor( new_stake_pool, n ) ) {
2199 0 : fd_delegation_pair_t_mapnode_t * e = fd_delegation_pair_t_map_acquire( stakes->stake_delegations_pool );
2200 0 : if( FD_UNLIKELY( !e ) ) {
2201 0 : FD_LOG_CRIT(( "Stake delegation map overflowed! (capacity=%lu)", fd_delegation_pair_t_map_max( stakes->stake_delegations_pool ) ));
2202 0 : }
2203 0 : fd_memcpy( &e->elem.account, &n->elem.account, sizeof(fd_pubkey_t));
2204 0 : fd_memcpy( &e->elem.delegation, &n->elem.delegation, sizeof(fd_delegation_t));
2205 0 : fd_delegation_pair_t_map_insert( stakes->stake_delegations_pool, &stakes->stake_delegations_root, e );
2206 0 : }
2207 :
2208 0 : slot_ctx->slot_bank.stake_account_keys.stake_accounts_root = NULL;
2209 0 : slot_ctx->slot_bank.stake_account_keys.stake_accounts_pool = fd_stake_accounts_pair_t_map_alloc( slot_ctx->valloc, 100000 );
2210 :
2211 0 : slot_ctx->slot_bank.vote_account_keys.vote_accounts_root = NULL;
2212 0 : slot_ctx->slot_bank.vote_account_keys.vote_accounts_pool = fd_vote_accounts_pair_t_map_alloc( slot_ctx->valloc, 100000 );
2213 0 : } FD_SCRATCH_SCOPE_END;
2214 0 : }
2215 :
2216 : /* Replace the stakes in T-2 (slot_ctx->slot_bank.epoch_stakes) by the stakes at T-1 (epoch_bank->next_epoch_stakes) */
2217 : static void
2218 0 : fd_update_epoch_stakes( fd_exec_slot_ctx_t * slot_ctx ) {
2219 0 : FD_SCRATCH_SCOPE_BEGIN {
2220 0 : fd_epoch_bank_t * epoch_bank = &slot_ctx->epoch_ctx->epoch_bank;
2221 :
2222 : /* Copy epoch_bank->next_epoch_stakes into slot_ctx->slot_bank.epoch_stakes */
2223 0 : fd_vote_accounts_pair_t_map_release_tree(
2224 0 : slot_ctx->slot_bank.epoch_stakes.vote_accounts_pool,
2225 0 : slot_ctx->slot_bank.epoch_stakes.vote_accounts_root );
2226 0 : slot_ctx->slot_bank.epoch_stakes.vote_accounts_root = NULL;
2227 :
2228 0 : for ( fd_vote_accounts_pair_t_mapnode_t * n = fd_vote_accounts_pair_t_map_minimum(
2229 0 : epoch_bank->next_epoch_stakes.vote_accounts_pool,
2230 0 : epoch_bank->next_epoch_stakes.vote_accounts_root );
2231 0 : n;
2232 0 : n = fd_vote_accounts_pair_t_map_successor( epoch_bank->next_epoch_stakes.vote_accounts_pool, n ) ) {
2233 :
2234 0 : const fd_pubkey_t null_pubkey = {{ 0 }};
2235 0 : if ( memcmp( &n->elem.key, &null_pubkey, FD_PUBKEY_FOOTPRINT ) == 0 ) {
2236 0 : continue;
2237 0 : }
2238 :
2239 0 : fd_vote_accounts_pair_t_mapnode_t * elem = fd_vote_accounts_pair_t_map_acquire(
2240 0 : slot_ctx->slot_bank.epoch_stakes.vote_accounts_pool );
2241 0 : if ( FD_UNLIKELY(
2242 0 : fd_vote_accounts_pair_t_map_free( slot_ctx->slot_bank.epoch_stakes.vote_accounts_pool ) == 0 ) ) {
2243 0 : FD_LOG_ERR(( "slot_ctx->slot_bank.epoch_stakes.vote_accounts_pool full" ));
2244 0 : }
2245 :
2246 0 : fd_memcpy( &elem->elem, &n->elem, sizeof(fd_vote_accounts_pair_t));
2247 0 : fd_vote_accounts_pair_t_map_insert(
2248 0 : slot_ctx->slot_bank.epoch_stakes.vote_accounts_pool,
2249 0 : &slot_ctx->slot_bank.epoch_stakes.vote_accounts_root,
2250 0 : elem );
2251 0 : }
2252 0 : } FD_SCRATCH_SCOPE_END;
2253 0 : }
2254 :
2255 : /* Copy epoch_bank->stakes.vote_accounts into epoch_bank->next_epoch_stakes. */
2256 0 : static void fd_update_next_epoch_stakes( fd_exec_slot_ctx_t * slot_ctx ) {
2257 0 : FD_SCRATCH_SCOPE_BEGIN {
2258 0 : fd_epoch_bank_t * epoch_bank = &slot_ctx->epoch_ctx->epoch_bank;
2259 :
2260 : /* Copy epoch_ctx->epoch_bank->stakes.vote_accounts into epoch_bank->next_epoch_stakes */
2261 0 : fd_vote_accounts_pair_t_map_release_tree(
2262 0 : epoch_bank->next_epoch_stakes.vote_accounts_pool,
2263 0 : epoch_bank->next_epoch_stakes.vote_accounts_root );
2264 :
2265 0 : epoch_bank->next_epoch_stakes.vote_accounts_pool = fd_exec_epoch_ctx_next_epoch_stakes_join( slot_ctx->epoch_ctx );
2266 0 : epoch_bank->next_epoch_stakes.vote_accounts_root = NULL;
2267 :
2268 0 : for ( fd_vote_accounts_pair_t_mapnode_t * n = fd_vote_accounts_pair_t_map_minimum(
2269 0 : epoch_bank->stakes.vote_accounts.vote_accounts_pool,
2270 0 : epoch_bank->stakes.vote_accounts.vote_accounts_root );
2271 0 : n;
2272 0 : n = fd_vote_accounts_pair_t_map_successor( epoch_bank->stakes.vote_accounts.vote_accounts_pool, n ) ) {
2273 0 : fd_vote_accounts_pair_t_mapnode_t * elem = fd_vote_accounts_pair_t_map_acquire( epoch_bank->next_epoch_stakes.vote_accounts_pool );
2274 0 : fd_memcpy( &elem->elem, &n->elem, sizeof(fd_vote_accounts_pair_t));
2275 0 : fd_vote_accounts_pair_t_map_insert( epoch_bank->next_epoch_stakes.vote_accounts_pool, &epoch_bank->next_epoch_stakes.vote_accounts_root, elem );
2276 0 : }
2277 0 : } FD_SCRATCH_SCOPE_END;
2278 0 : }
2279 :
2280 : /* Mimics `bank.new_target_program_account()`. Assumes `out_rec` is a modifiable record.
2281 :
2282 : From the calling context, `out_rec` points to a native program record (e.g. Config, ALUT native programs).
2283 : There should be enough space in `out_rec->data` to hold at least 36 bytes (the size of a BPF upgradeable
2284 : program account) when calling this function. The native program account's owner is set to the BPF loader
2285 : upgradeable program ID, and lamports are increased / deducted to contain the rent exempt minimum balance.
2286 :
2287 : https://github.com/anza-xyz/agave/blob/v2.1.0/runtime/src/bank/builtins/core_bpf_migration/mod.rs#L79-L95 */
2288 : static int
2289 : fd_new_target_program_account( fd_exec_slot_ctx_t * slot_ctx,
2290 : const fd_pubkey_t * target_program_data_address,
2291 0 : fd_borrowed_account_t * out_rec ) {
2292 : /* https://github.com/anza-xyz/agave/blob/v2.1.0/sdk/account/src/lib.rs#L471 */
2293 0 : out_rec->meta->info.rent_epoch = 0UL;
2294 :
2295 : /* https://github.com/anza-xyz/agave/blob/v2.1.0/runtime/src/bank/builtins/core_bpf_migration/mod.rs#L86-L88 */
2296 0 : fd_bpf_upgradeable_loader_state_t state = {
2297 0 : .discriminant = fd_bpf_upgradeable_loader_state_enum_program,
2298 0 : .inner = {
2299 0 : .program = {
2300 0 : .programdata_address = *target_program_data_address,
2301 0 : }
2302 0 : }
2303 0 : };
2304 :
2305 : /* https://github.com/anza-xyz/agave/blob/v2.1.0/runtime/src/bank/builtins/core_bpf_migration/mod.rs#L89-L90 */
2306 0 : const fd_rent_t * rent = fd_sysvar_cache_rent( slot_ctx->sysvar_cache );
2307 0 : if( FD_UNLIKELY( rent==NULL ) ) {
2308 0 : return -1;
2309 0 : }
2310 :
2311 0 : out_rec->meta->info.lamports = fd_rent_exempt_minimum_balance( rent, SIZE_OF_PROGRAM );
2312 0 : fd_bincode_encode_ctx_t ctx = {
2313 0 : .data = out_rec->data,
2314 0 : .dataend = out_rec->data + SIZE_OF_PROGRAM,
2315 0 : };
2316 :
2317 : /* https://github.com/anza-xyz/agave/blob/v2.1.0/runtime/src/bank/builtins/core_bpf_migration/mod.rs#L91-L9 */
2318 0 : int err = fd_bpf_upgradeable_loader_state_encode( &state, &ctx );
2319 0 : if( FD_UNLIKELY( err ) ) {
2320 0 : return err;
2321 0 : }
2322 0 : fd_memcpy( out_rec->meta->info.owner, fd_solana_bpf_loader_upgradeable_program_id.uc, sizeof(fd_pubkey_t) );
2323 :
2324 : /* https://github.com/anza-xyz/agave/blob/v2.1.0/runtime/src/bank/builtins/core_bpf_migration/mod.rs#L93-L94 */
2325 0 : out_rec->meta->info.executable = 1;
2326 0 : return FD_RUNTIME_EXECUTE_SUCCESS;
2327 0 : }
2328 :
2329 : /* Mimics `bank.new_target_program_data_account()`. Assumes `new_target_program_data_account` is a modifiable record.
2330 : `config_upgrade_authority_address` may be NULL.
2331 :
2332 : This function uses an existing buffer account `buffer_acc_rec` to set the program data account data for a core
2333 : program BPF migration. Sets the lamports and data fields of `new_target_program_data_account` based on the
2334 : ELF data length, and sets the owner to the BPF loader upgradeable program ID.
2335 :
2336 : https://github.com/anza-xyz/agave/blob/v2.1.0/runtime/src/bank/builtins/core_bpf_migration/mod.rs#L97-L153 */
2337 : static int
2338 : fd_new_target_program_data_account( fd_exec_slot_ctx_t * slot_ctx,
2339 : fd_pubkey_t * config_upgrade_authority_address,
2340 : fd_borrowed_account_t * buffer_acc_rec,
2341 0 : fd_borrowed_account_t * new_target_program_data_account ) {
2342 : /* https://github.com/anza-xyz/agave/blob/v2.1.0/runtime/src/bank/builtins/core_bpf_migration/mod.rs#L113-L116 */
2343 0 : fd_bpf_upgradeable_loader_state_t state;
2344 0 : fd_bincode_decode_ctx_t decode_ctx = {
2345 0 : .data = buffer_acc_rec->const_data,
2346 0 : .dataend = buffer_acc_rec->const_data + buffer_acc_rec->const_meta->dlen,
2347 0 : .valloc = fd_scratch_virtual()
2348 0 : };
2349 0 : int err = fd_bpf_upgradeable_loader_state_decode( &state, &decode_ctx );
2350 0 : if( FD_UNLIKELY( err ) ) {
2351 0 : return err;
2352 0 : }
2353 0 : if( FD_UNLIKELY( !fd_bpf_upgradeable_loader_state_is_buffer( &state ) ) ) {
2354 0 : return -1;
2355 0 : }
2356 :
2357 : /* https://github.com/anza-xyz/agave/blob/v2.1.0/runtime/src/bank/builtins/core_bpf_migration/mod.rs#L118-L125 */
2358 0 : if( config_upgrade_authority_address!=NULL ) {
2359 0 : if( FD_UNLIKELY( state.inner.buffer.authority_address==NULL ||
2360 0 : memcmp( config_upgrade_authority_address, state.inner.buffer.authority_address, sizeof(fd_pubkey_t) ) ) ) {
2361 0 : return -1;
2362 0 : }
2363 0 : }
2364 :
2365 : /* https://github.com/anza-xyz/agave/blob/v2.1.0/runtime/src/bank/builtins/core_bpf_migration/mod.rs#L127-L132 */
2366 0 : const fd_rent_t * rent = fd_sysvar_cache_rent( slot_ctx->sysvar_cache );
2367 0 : if( FD_UNLIKELY( rent==NULL ) ) {
2368 0 : return -1;
2369 0 : }
2370 :
2371 0 : const uchar * elf = buffer_acc_rec->const_data + BUFFER_METADATA_SIZE;
2372 0 : ulong space = PROGRAMDATA_METADATA_SIZE - BUFFER_METADATA_SIZE + buffer_acc_rec->const_meta->dlen;
2373 0 : ulong lamports = fd_rent_exempt_minimum_balance( rent, space );
2374 :
2375 : /* https://github.com/anza-xyz/agave/blob/v2.1.0/runtime/src/bank/builtins/core_bpf_migration/mod.rs#L134-L137 */
2376 0 : fd_bpf_upgradeable_loader_state_t programdata_metadata = {
2377 0 : .discriminant = fd_bpf_upgradeable_loader_state_enum_program_data,
2378 0 : .inner = {
2379 0 : .program_data = {
2380 0 : .slot = slot_ctx->slot_bank.slot,
2381 0 : .upgrade_authority_address = config_upgrade_authority_address
2382 0 : }
2383 0 : }
2384 0 : };
2385 :
2386 : /* https://github.com/anza-xyz/agave/blob/v2.1.0/runtime/src/bank/builtins/core_bpf_migration/mod.rs#L139-L144 */
2387 0 : new_target_program_data_account->meta->info.lamports = lamports;
2388 0 : fd_bincode_encode_ctx_t encode_ctx = {
2389 0 : .data = new_target_program_data_account->data,
2390 0 : .dataend = new_target_program_data_account->data + PROGRAMDATA_METADATA_SIZE,
2391 0 : };
2392 0 : err = fd_bpf_upgradeable_loader_state_encode( &programdata_metadata, &encode_ctx );
2393 0 : if( FD_UNLIKELY( err ) ) {
2394 0 : return err;
2395 0 : }
2396 0 : fd_memcpy( new_target_program_data_account->meta->info.owner, fd_solana_bpf_loader_upgradeable_program_id.uc, sizeof(fd_pubkey_t) );
2397 :
2398 : /* Copy the ELF data over
2399 : https://github.com/anza-xyz/agave/blob/v2.1.0/runtime/src/bank/builtins/core_bpf_migration/mod.rs#L145 */
2400 0 : fd_memcpy( new_target_program_data_account->data + PROGRAMDATA_METADATA_SIZE, elf, buffer_acc_rec->const_meta->dlen - BUFFER_METADATA_SIZE );
2401 :
2402 0 : return FD_RUNTIME_EXECUTE_SUCCESS;
2403 0 : }
2404 :
2405 : /* Mimics `migrate_builtin_to_core_bpf()`. The arguments map as follows:
2406 : - builtin_program_id: builtin_program_id
2407 : - config
2408 : - source_buffer_address: source_buffer_address
2409 : - migration_target
2410 : - Builtin: !stateless
2411 : - Stateless: stateless
2412 : - upgrade_authority_address: upgrade_authority_address
2413 : https://github.com/anza-xyz/agave/blob/v2.1.0/runtime/src/bank/builtins/core_bpf_migration/mod.rs#L235-L318 */
2414 : static void
2415 : fd_migrate_builtin_to_core_bpf( fd_exec_slot_ctx_t * slot_ctx,
2416 : fd_pubkey_t * upgrade_authority_address,
2417 : const fd_pubkey_t * builtin_program_id,
2418 : const fd_pubkey_t * source_buffer_address,
2419 0 : uchar stateless ) {
2420 0 : int err;
2421 :
2422 : /* https://github.com/anza-xyz/agave/blob/v2.1.0/runtime/src/bank/builtins/core_bpf_migration/mod.rs#L242-L243
2423 :
2424 : The below logic is used to obtain a `TargetBuiltin` account. There are three fields of `TargetBuiltin` returned:
2425 : - target.program_address: builtin_program_id
2426 : - target.program_account:
2427 : - if stateless: an AccountSharedData::default() (i.e. system program id, 0 lamports, 0 data, non-executable, system program owner)
2428 : - if NOT stateless: the existing account (for us its called `target_program_account`)
2429 : - target.program_data_address: `target_program_data_address` for us, derived below. */
2430 :
2431 : /* These checks will fail if the core program has already been migrated to BPF, since the account will exist + the program owner
2432 : will no longer be the native loader.
2433 : https://github.com/anza-xyz/agave/blob/v2.1.0/runtime/src/bank/builtins/core_bpf_migration/target_builtin.rs#L23-L50 */
2434 0 : FD_BORROWED_ACCOUNT_DECL( target_program_account );
2435 0 : uchar program_exists = ( fd_acc_mgr_view( slot_ctx->acc_mgr, slot_ctx->funk_txn, builtin_program_id, target_program_account )==FD_ACC_MGR_SUCCESS );
2436 0 : if( !stateless ) {
2437 : /* The program account should exist.
2438 : https://github.com/anza-xyz/agave/blob/v2.1.0/runtime/src/bank/builtins/core_bpf_migration/target_builtin.rs#L30-L33 */
2439 0 : if( FD_UNLIKELY( !program_exists ) ) {
2440 0 : FD_LOG_WARNING(( "Builtin program %s does not exist, skipping migration...", FD_BASE58_ENC_32_ALLOCA( builtin_program_id ) ));
2441 0 : return;
2442 0 : }
2443 :
2444 : /* The program account should be owned by the native loader.
2445 : https://github.com/anza-xyz/agave/blob/v2.1.0/runtime/src/bank/builtins/core_bpf_migration/target_builtin.rs#L35-L38 */
2446 0 : if( FD_UNLIKELY( memcmp( target_program_account->const_meta->info.owner, fd_solana_native_loader_id.uc, sizeof(fd_pubkey_t) ) ) ) {
2447 0 : FD_LOG_WARNING(( "Builtin program %s is not owned by the native loader, skipping migration...", FD_BASE58_ENC_32_ALLOCA( builtin_program_id ) ));
2448 0 : return;
2449 0 : }
2450 0 : } else {
2451 : /* The program account should _not_ exist.
2452 : https://github.com/anza-xyz/agave/blob/v2.1.0/runtime/src/bank/builtins/core_bpf_migration/target_builtin.rs#L42-L46 */
2453 0 : if( FD_UNLIKELY( program_exists ) ) {
2454 0 : FD_LOG_WARNING(( "Stateless program %s already exists, skipping migration...", FD_BASE58_ENC_32_ALLOCA( builtin_program_id ) ));
2455 0 : return;
2456 0 : }
2457 0 : }
2458 :
2459 : /* The program data account should not exist.
2460 : https://github.com/anza-xyz/agave/blob/v2.1.0/runtime/src/bank/builtins/core_bpf_migration/target_builtin.rs#L52-L62 */
2461 0 : uint custom_err = UINT_MAX;
2462 0 : fd_pubkey_t target_program_data_address[ 1UL ];
2463 0 : uchar * seeds[ 1UL ];
2464 0 : seeds[ 0UL ] = (uchar *)builtin_program_id;
2465 0 : ulong seed_sz = sizeof(fd_pubkey_t);
2466 0 : uchar bump_seed = 0;
2467 0 : err = fd_pubkey_find_program_address( &fd_solana_bpf_loader_upgradeable_program_id, 1UL, seeds, &seed_sz, target_program_data_address, &bump_seed, &custom_err );
2468 0 : if( FD_UNLIKELY( err ) ) {
2469 : /* TODO: We should handle these errors more gracefully instead of just killing the client. */
2470 0 : FD_LOG_ERR(( "Unable to find a viable program address bump seed" )); // Solana panics, error code is undefined
2471 0 : return;
2472 0 : }
2473 0 : FD_BORROWED_ACCOUNT_DECL( program_data_account );
2474 0 : if( FD_UNLIKELY( fd_acc_mgr_view( slot_ctx->acc_mgr, slot_ctx->funk_txn, target_program_data_address, program_data_account )==FD_ACC_MGR_SUCCESS ) ) {
2475 0 : FD_LOG_WARNING(( "Program data account %s already exists, skipping migration...", FD_BASE58_ENC_32_ALLOCA( target_program_data_address ) ));
2476 0 : return;
2477 0 : }
2478 :
2479 : /* https://github.com/anza-xyz/agave/blob/v2.1.0/runtime/src/bank/builtins/core_bpf_migration/mod.rs#L244
2480 :
2481 : Obtains a `SourceBuffer` account. There are two fields returned:
2482 : - source.buffer_address: source_buffer_address
2483 : - source.buffer_account: the existing buffer account */
2484 :
2485 : /* The buffer account should exist.
2486 : https://github.com/anza-xyz/agave/blob/v2.1.0/runtime/src/bank/builtins/core_bpf_migration/source_buffer.rs#L26-L29 */
2487 0 : FD_BORROWED_ACCOUNT_DECL( source_buffer_account );
2488 0 : if( FD_UNLIKELY( fd_acc_mgr_modify( slot_ctx->acc_mgr, slot_ctx->funk_txn, source_buffer_address, 0, 0UL, source_buffer_account )!=FD_ACC_MGR_SUCCESS ) ) {
2489 0 : FD_LOG_WARNING(( "Buffer account %s does not exist, skipping migration...", FD_BASE58_ENC_32_ALLOCA( source_buffer_address ) ));
2490 0 : return;
2491 0 : }
2492 :
2493 : /* The buffer account should be owned by the upgradeable loader.
2494 : https://github.com/anza-xyz/agave/blob/v2.1.0/runtime/src/bank/builtins/core_bpf_migration/source_buffer.rs#L31-L34 */
2495 0 : if( FD_UNLIKELY( memcmp( source_buffer_account->const_meta->info.owner, fd_solana_bpf_loader_upgradeable_program_id.uc, sizeof(fd_pubkey_t) ) ) ) {
2496 0 : FD_LOG_WARNING(( "Buffer account %s is not owned by the upgradeable loader, skipping migration...", FD_BASE58_ENC_32_ALLOCA( source_buffer_address ) ));
2497 0 : return;
2498 0 : }
2499 :
2500 : /* The buffer account should have the correct state. We already check the buffer account state in `fd_new_target_program_data_account`,
2501 : so we can skip the checks here.
2502 : https://github.com/anza-xyz/agave/blob/v2.1.0/runtime/src/bank/builtins/core_bpf_migration/source_buffer.rs#L37-L47 */
2503 :
2504 : /* This check is done a bit prematurely because we calculate the previous account state's lamports. We use 0 for starting lamports
2505 : for stateless accounts because they don't yet exist.
2506 :
2507 : https://github.com/anza-xyz/agave/blob/v2.1.0/runtime/src/bank/builtins/core_bpf_migration/mod.rs#L277-L280 */
2508 0 : ulong lamports_to_burn = ( stateless ? 0UL : target_program_account->const_meta->info.lamports ) + source_buffer_account->const_meta->info.lamports;
2509 :
2510 : /* Start a funk write txn */
2511 0 : fd_funk_txn_t * parent_txn = slot_ctx->funk_txn;
2512 0 : fd_funk_txn_xid_t migration_xid = fd_funk_generate_xid();
2513 0 : slot_ctx->funk_txn = fd_funk_txn_prepare( slot_ctx->acc_mgr->funk, slot_ctx->funk_txn, &migration_xid, 0UL );
2514 :
2515 : /* Attempt serialization of program account. If the program is stateless, we want to create the account. Otherwise,
2516 : we want a writable handle to modify the existing account.
2517 : https://github.com/anza-xyz/agave/blob/v2.1.0/runtime/src/bank/builtins/core_bpf_migration/mod.rs#L246-L249 */
2518 0 : FD_BORROWED_ACCOUNT_DECL( new_target_program_account );
2519 0 : err = fd_acc_mgr_modify( slot_ctx->acc_mgr, slot_ctx->funk_txn, builtin_program_id, stateless, SIZE_OF_PROGRAM, new_target_program_account );
2520 0 : if( FD_UNLIKELY( err ) ) {
2521 0 : FD_LOG_WARNING(( "Builtin program ID %s does not exist", FD_BASE58_ENC_32_ALLOCA( builtin_program_id ) ));
2522 0 : goto fail;
2523 0 : }
2524 0 : new_target_program_account->meta->dlen = SIZE_OF_PROGRAM;
2525 0 : new_target_program_account->meta->slot = slot_ctx->slot_bank.slot;
2526 :
2527 : /* Create a new target program account. This modifies the existing record. */
2528 0 : err = fd_new_target_program_account( slot_ctx, target_program_data_address, new_target_program_account );
2529 0 : if( FD_UNLIKELY( err ) ) {
2530 0 : FD_LOG_WARNING(( "Failed to write new program state to %s", FD_BASE58_ENC_32_ALLOCA( builtin_program_id ) ));
2531 0 : goto fail;
2532 0 : }
2533 :
2534 : /* Create a new target program data account. */
2535 0 : ulong new_target_program_data_account_sz = PROGRAMDATA_METADATA_SIZE - BUFFER_METADATA_SIZE + source_buffer_account->const_meta->dlen;
2536 0 : FD_BORROWED_ACCOUNT_DECL( new_target_program_data_account );
2537 0 : err = fd_acc_mgr_modify( slot_ctx->acc_mgr,
2538 0 : slot_ctx->funk_txn,
2539 0 : target_program_data_address,
2540 0 : 1,
2541 0 : new_target_program_data_account_sz,
2542 0 : new_target_program_data_account );
2543 0 : if( FD_UNLIKELY( err ) ) {
2544 0 : FD_LOG_WARNING(( "Failed to create new program data account to %s", FD_BASE58_ENC_32_ALLOCA( target_program_data_address ) ));
2545 0 : goto fail;
2546 0 : }
2547 0 : new_target_program_data_account->meta->dlen = new_target_program_data_account_sz;
2548 0 : new_target_program_data_account->meta->slot = slot_ctx->slot_bank.slot;
2549 :
2550 0 : err = fd_new_target_program_data_account( slot_ctx, upgrade_authority_address, source_buffer_account, new_target_program_data_account );
2551 0 : if( FD_UNLIKELY( err ) ) {
2552 0 : FD_LOG_WARNING(( "Failed to write new program data state to %s", FD_BASE58_ENC_32_ALLOCA( target_program_data_address ) ));
2553 0 : goto fail;
2554 0 : }
2555 :
2556 : /* Deploy the new target Core BPF program.
2557 : https://github.com/anza-xyz/agave/blob/v2.1.0/runtime/src/bank/builtins/core_bpf_migration/mod.rs#L268-L271 */
2558 0 : err = fd_directly_invoke_loader_v3_deploy( slot_ctx,
2559 0 : new_target_program_data_account->const_data + PROGRAMDATA_METADATA_SIZE,
2560 0 : new_target_program_data_account->const_meta->dlen - PROGRAMDATA_METADATA_SIZE );
2561 0 : if( FD_UNLIKELY( err ) ) {
2562 0 : FD_LOG_WARNING(( "Failed to deploy program %s", FD_BASE58_ENC_32_ALLOCA( builtin_program_id ) ));
2563 0 : goto fail;
2564 0 : }
2565 :
2566 : /* https://github.com/anza-xyz/agave/blob/v2.1.0/runtime/src/bank/builtins/core_bpf_migration/mod.rs#L281-L284 */
2567 0 : ulong lamports_to_fund = new_target_program_account->const_meta->info.lamports + new_target_program_data_account->const_meta->info.lamports;
2568 :
2569 : /* Update capitalization.
2570 : https://github.com/anza-xyz/agave/blob/v2.1.0/runtime/src/bank/builtins/core_bpf_migration/mod.rs#L286-L297 */
2571 0 : if( lamports_to_burn>lamports_to_fund ) {
2572 0 : slot_ctx->slot_bank.capitalization -= lamports_to_burn - lamports_to_fund;
2573 0 : } else {
2574 0 : slot_ctx->slot_bank.capitalization += lamports_to_fund - lamports_to_burn;
2575 0 : }
2576 :
2577 : /* Reclaim the source buffer account
2578 : https://github.com/anza-xyz/agave/blob/v2.1.0/runtime/src/bank/builtins/core_bpf_migration/mod.rs#L305 */
2579 0 : source_buffer_account->meta->info.lamports = 0;
2580 0 : source_buffer_account->meta->dlen = 0;
2581 0 : fd_memset( source_buffer_account->meta->info.owner, 0, sizeof(fd_pubkey_t) );
2582 :
2583 : /* Publish the in-preparation transaction into the parent. We should not have to create
2584 : a BPF cache entry here because the program is technically "delayed visibility", so the program
2585 : should not be invokable until the next slot. The cache entry will be created at the end of the
2586 : block as a part of the finalize routine. */
2587 0 : fd_funk_txn_publish_into_parent( slot_ctx->acc_mgr->funk, slot_ctx->funk_txn, 1 );
2588 0 : slot_ctx->funk_txn = parent_txn;
2589 0 : return;
2590 :
2591 0 : fail:
2592 : /* Cancel the in-preparation transaction and discard any in-progress changes. */
2593 0 : fd_funk_txn_cancel( slot_ctx->acc_mgr->funk, slot_ctx->funk_txn, 0UL );
2594 0 : slot_ctx->funk_txn = parent_txn;
2595 0 : }
2596 :
2597 : static void
2598 0 : fd_apply_builtin_program_feature_transitions( fd_exec_slot_ctx_t * slot_ctx ) {
2599 0 : FD_SCRATCH_SCOPE_BEGIN {
2600 : /* TODO: Set the upgrade authority properly from the core bpf migration config. Right now it's set to None.
2601 :
2602 : Migrate any necessary stateless builtins to core BPF. So far, the only "stateless" builtin
2603 : is the Feature program. Beginning checks in the `migrate_builtin_to_core_bpf` function will
2604 : fail if the program has already been migrated to BPF.
2605 : https://github.com/anza-xyz/agave/blob/v2.1.0/runtime/src/bank.rs#L6776-L6793 */
2606 0 : if( FD_FEATURE_ACTIVE( slot_ctx, migrate_feature_gate_program_to_core_bpf ) ) {
2607 0 : fd_migrate_builtin_to_core_bpf( slot_ctx, NULL, &fd_solana_feature_program_id, &fd_solana_feature_program_buffer_address, 1 );
2608 0 : }
2609 :
2610 0 : if( FD_FEATURE_ACTIVE( slot_ctx, migrate_config_program_to_core_bpf ) ) {
2611 0 : fd_migrate_builtin_to_core_bpf( slot_ctx, NULL, &fd_solana_config_program_id, &fd_solana_config_program_buffer_address, 0 );
2612 0 : }
2613 :
2614 0 : if( FD_FEATURE_ACTIVE( slot_ctx, migrate_address_lookup_table_program_to_core_bpf ) ) {
2615 0 : fd_migrate_builtin_to_core_bpf( slot_ctx, NULL, &fd_solana_address_lookup_table_program_id, &fd_solana_address_lookup_table_program_buffer_address, 0 );
2616 0 : }
2617 :
2618 0 : if( FD_FEATURE_ACTIVE( slot_ctx, migrate_stake_program_to_core_bpf ) ) {
2619 0 : fd_migrate_builtin_to_core_bpf( slot_ctx, NULL, &fd_solana_stake_program_id, &fd_solana_stake_program_buffer_address, 0 );
2620 0 : }
2621 0 : } FD_SCRATCH_SCOPE_END;
2622 0 : }
2623 :
2624 : static void
2625 : fd_feature_activate( fd_exec_slot_ctx_t * slot_ctx,
2626 : fd_feature_id_t const * id,
2627 0 : uchar const acct[ static 32 ] ) {
2628 :
2629 : // Skip reverted features from being activated
2630 0 : if ( id->reverted==1 ) {
2631 0 : return;
2632 0 : }
2633 :
2634 0 : FD_BORROWED_ACCOUNT_DECL(acct_rec);
2635 0 : int err = fd_acc_mgr_view(slot_ctx->acc_mgr, slot_ctx->funk_txn, (fd_pubkey_t *)acct, acct_rec);
2636 0 : if (FD_UNLIKELY(err != FD_ACC_MGR_SUCCESS))
2637 0 : return;
2638 :
2639 0 : fd_feature_t feature[1];
2640 :
2641 0 : FD_SCRATCH_SCOPE_BEGIN
2642 0 : {
2643 :
2644 0 : fd_bincode_decode_ctx_t ctx = {
2645 0 : .data = acct_rec->const_data,
2646 0 : .dataend = acct_rec->const_data + acct_rec->const_meta->dlen,
2647 0 : .valloc = fd_scratch_virtual(),
2648 0 : };
2649 0 : int decode_err = fd_feature_decode(feature, &ctx);
2650 0 : if (FD_UNLIKELY(decode_err != FD_BINCODE_SUCCESS)) {
2651 0 : FD_LOG_ERR(( "Failed to decode feature account %s (%d)", FD_BASE58_ENC_32_ALLOCA( acct ), decode_err ));
2652 0 : }
2653 :
2654 0 : if( feature->has_activated_at ) {
2655 0 : FD_LOG_INFO(( "feature already activated - acc: %s, slot: %lu", FD_BASE58_ENC_32_ALLOCA( acct ), feature->activated_at ));
2656 0 : fd_features_set(&slot_ctx->epoch_ctx->features, id, feature->activated_at);
2657 0 : } else {
2658 0 : FD_LOG_INFO(( "Feature %s not activated at %lu, activating", FD_BASE58_ENC_32_ALLOCA( acct ), feature->activated_at ));
2659 :
2660 0 : FD_BORROWED_ACCOUNT_DECL(modify_acct_rec);
2661 0 : err = fd_acc_mgr_modify(slot_ctx->acc_mgr, slot_ctx->funk_txn, (fd_pubkey_t *)acct, 0, 0UL, modify_acct_rec);
2662 0 : if (FD_UNLIKELY(err != FD_ACC_MGR_SUCCESS)) {
2663 0 : return;
2664 0 : }
2665 :
2666 0 : feature->has_activated_at = 1;
2667 0 : feature->activated_at = slot_ctx->slot_bank.slot;
2668 0 : fd_bincode_encode_ctx_t encode_ctx = {
2669 0 : .data = modify_acct_rec->data,
2670 0 : .dataend = modify_acct_rec->data + modify_acct_rec->meta->dlen,
2671 0 : };
2672 0 : int encode_err = fd_feature_encode(feature, &encode_ctx);
2673 0 : if (FD_UNLIKELY(encode_err != FD_BINCODE_SUCCESS)) {
2674 0 : FD_LOG_ERR(( "Failed to encode feature account %s (%d)", FD_BASE58_ENC_32_ALLOCA( acct ), decode_err ));
2675 0 : }
2676 0 : }
2677 : /* No need to call destroy, since we are using fd_scratch allocator. */
2678 0 : } FD_SCRATCH_SCOPE_END;
2679 0 : }
2680 :
2681 : static void
2682 0 : fd_features_activate( fd_exec_slot_ctx_t * slot_ctx ) {
2683 0 : for( fd_feature_id_t const * id = fd_feature_iter_init();
2684 0 : !fd_feature_iter_done( id );
2685 0 : id = fd_feature_iter_next( id ) ) {
2686 0 : fd_feature_activate( slot_ctx, id, id->id.key );
2687 0 : }
2688 0 : }
2689 :
2690 : uint
2691 0 : fd_runtime_is_epoch_boundary( fd_epoch_bank_t * epoch_bank, ulong curr_slot, ulong prev_slot ) {
2692 0 : ulong slot_idx;
2693 0 : ulong prev_epoch = fd_slot_to_epoch( &epoch_bank->epoch_schedule, prev_slot, &slot_idx );
2694 0 : ulong new_epoch = fd_slot_to_epoch( &epoch_bank->epoch_schedule, curr_slot, &slot_idx );
2695 :
2696 0 : return ( prev_epoch < new_epoch || slot_idx == 0 );
2697 0 : }
2698 :
2699 : /* Starting a new epoch.
2700 : New epoch: T
2701 : Just ended epoch: T-1
2702 : Epoch before: T-2
2703 :
2704 : In this function:
2705 : - stakes in T-2 (slot_ctx->slot_bank.epoch_stakes) should be replaced by T-1 (epoch_bank->next_epoch_stakes)
2706 : - stakes at T-1 (epoch_bank->next_epoch_stakes) should be replaced by updated stakes at T (stakes->vote_accounts)
2707 : - leader schedule should be calculated using new T-2 stakes (slot_ctx->slot_bank.epoch_stakes)
2708 :
2709 : Invariant during an epoch T:
2710 : epoch_bank->next_epoch_stakes holds the stakes at T-1
2711 : slot_ctx->slot_bank.epoch_stakes holds the stakes at T-2
2712 : */
2713 : /* process for the start of a new epoch */
2714 :
2715 : void fd_process_new_epoch( fd_exec_slot_ctx_t * slot_ctx,
2716 0 : ulong parent_epoch ) {
2717 0 : FD_LOG_NOTICE(( "fd_process_new_epoch start" ));
2718 :
2719 0 : ulong slot;
2720 0 : fd_epoch_bank_t * epoch_bank = fd_exec_epoch_ctx_epoch_bank( slot_ctx->epoch_ctx );
2721 0 : ulong epoch = fd_slot_to_epoch( &epoch_bank->epoch_schedule, slot_ctx->slot_bank.slot, &slot );
2722 :
2723 : /* Activate new features
2724 : https://github.com/anza-xyz/agave/blob/v2.1.0/runtime/src/bank.rs#L6587-L6598 */
2725 0 : fd_features_activate( slot_ctx );
2726 0 : fd_features_restore( slot_ctx );
2727 :
2728 : /* Apply builtin program feature transitions
2729 : https://github.com/anza-xyz/agave/blob/v2.1.0/runtime/src/bank.rs#L6621-L6624 */
2730 0 : fd_apply_builtin_program_feature_transitions( slot_ctx );
2731 :
2732 : /* Change the speed of the poh clock
2733 : https://github.com/anza-xyz/agave/blob/v2.1.0/runtime/src/bank.rs#L6627-L6649 */
2734 0 : if( FD_FEATURE_JUST_ACTIVATED( slot_ctx, update_hashes_per_tick6 ) ) {
2735 0 : epoch_bank->hashes_per_tick = UPDATED_HASHES_PER_TICK6;
2736 0 : } else if( FD_FEATURE_JUST_ACTIVATED(slot_ctx, update_hashes_per_tick5 ) ) {
2737 0 : epoch_bank->hashes_per_tick = UPDATED_HASHES_PER_TICK5;
2738 0 : } else if( FD_FEATURE_JUST_ACTIVATED( slot_ctx, update_hashes_per_tick4 ) ) {
2739 0 : epoch_bank->hashes_per_tick = UPDATED_HASHES_PER_TICK4;
2740 0 : } else if( FD_FEATURE_JUST_ACTIVATED(slot_ctx, update_hashes_per_tick3 ) ) {
2741 0 : epoch_bank->hashes_per_tick = UPDATED_HASHES_PER_TICK3;
2742 0 : } else if( FD_FEATURE_JUST_ACTIVATED(slot_ctx, update_hashes_per_tick2 ) ) {
2743 0 : epoch_bank->hashes_per_tick = UPDATED_HASHES_PER_TICK2;
2744 0 : }
2745 :
2746 : /* Get the new rate activation epoch */
2747 0 : int _err[1];
2748 0 : ulong * new_rate_activation_epoch = fd_scratch_alloc( alignof(ulong), sizeof(ulong) );
2749 0 : int is_some = fd_new_warmup_cooldown_rate_epoch( slot_ctx, new_rate_activation_epoch, _err );
2750 0 : if( FD_UNLIKELY( !is_some ) ) {
2751 0 : new_rate_activation_epoch = NULL;
2752 0 : }
2753 :
2754 0 : FD_SCRATCH_SCOPE_BEGIN {
2755 :
2756 0 : fd_epoch_info_t temp_info = {0};
2757 0 : fd_epoch_info_new( &temp_info );
2758 :
2759 : /* Updates stake history sysvar accumulated values. */
2760 0 : fd_stakes_activate_epoch( slot_ctx, new_rate_activation_epoch, &temp_info );
2761 :
2762 : /* Update the stakes epoch value to the new epoch */
2763 0 : epoch_bank->stakes.epoch = epoch;
2764 :
2765 : /* If appropiate, use the stakes at T-1 to generate the leader schedule instead of T-2.
2766 : This is due to a subtlety in how Agave's stake caches interact when loading from snapshots.
2767 : See the comment in fd_exec_slot_ctx_recover_. */
2768 0 : if( slot_ctx->slot_bank.has_use_preceeding_epoch_stakes && slot_ctx->slot_bank.use_preceeding_epoch_stakes == epoch ) {
2769 0 : fd_update_epoch_stakes( slot_ctx );
2770 0 : }
2771 :
2772 : /* Distribute rewards */
2773 0 : fd_hash_t const * parent_blockhash = slot_ctx->slot_bank.block_hash_queue.last_hash;
2774 0 : if( ( FD_FEATURE_ACTIVE( slot_ctx, enable_partitioned_epoch_reward ) ||
2775 0 : FD_FEATURE_ACTIVE( slot_ctx, partitioned_epoch_rewards_superfeature ) ) ) {
2776 0 : FD_LOG_NOTICE(( "fd_begin_partitioned_rewards" ));
2777 0 : fd_begin_partitioned_rewards( slot_ctx, parent_blockhash, parent_epoch, &temp_info );
2778 0 : } else {
2779 0 : fd_update_rewards( slot_ctx, parent_blockhash, parent_epoch, &temp_info );
2780 0 : }
2781 :
2782 : /* Updates stakes at time T */
2783 0 : fd_stake_history_t const * history = fd_sysvar_cache_stake_history( slot_ctx->sysvar_cache );
2784 0 : if( FD_UNLIKELY( !history ) ) {
2785 0 : FD_LOG_ERR(( "StakeHistory sysvar is missing from sysvar cache" ));
2786 0 : }
2787 :
2788 0 : FD_LOG_NOTICE(( "refresh_vote_accounts" ));
2789 :
2790 0 : refresh_vote_accounts( slot_ctx, history, new_rate_activation_epoch, &temp_info );
2791 0 : fd_update_stake_delegations( slot_ctx, &temp_info );
2792 :
2793 : /* Replace stakes at T-2 (slot_ctx->slot_bank.epoch_stakes) by stakes at T-1 (epoch_bank->next_epoch_stakes) */
2794 0 : fd_update_epoch_stakes( slot_ctx );
2795 :
2796 : /* Replace stakes at T-1 (epoch_bank->next_epoch_stakes) by updated stakes at T (stakes->vote_accounts) */
2797 0 : fd_update_next_epoch_stakes( slot_ctx );
2798 :
2799 : /* Update current leaders using slot_ctx->slot_bank.epoch_stakes (new T-2 stakes) */
2800 0 : fd_runtime_update_leaders( slot_ctx, slot_ctx->slot_bank.slot );
2801 :
2802 0 : fd_calculate_epoch_accounts_hash_values( slot_ctx );
2803 0 : } FD_SCRATCH_SCOPE_END;
2804 :
2805 0 : FD_LOG_NOTICE(( "fd_process_new_epoch end" ));
2806 0 : }
2807 :
2808 : /******************************************************************************/
2809 : /* Block Parsing */
2810 : /******************************************************************************/
2811 :
2812 : /* Block iteration and parsing */
2813 :
2814 : /* As a note, all of the logic in this section is used by the full firedancer
2815 : client. The store tile uses these APIs to help parse raw (micro)blocks
2816 : received from the network. */
2817 :
2818 : /* Helpers */
2819 :
2820 : static int
2821 : fd_runtime_parse_microblock_hdr( void const * buf,
2822 : ulong buf_sz,
2823 : fd_microblock_hdr_t * opt_microblock_hdr,
2824 0 : ulong * opt_microblock_hdr_size ) {
2825 :
2826 0 : if( FD_UNLIKELY( buf_sz<sizeof(fd_microblock_hdr_t) ) ) {
2827 0 : return -1;
2828 0 : }
2829 :
2830 0 : if( !!opt_microblock_hdr ) {
2831 0 : *opt_microblock_hdr = *(fd_microblock_hdr_t *)buf;
2832 0 : }
2833 :
2834 0 : if( !!opt_microblock_hdr_size ) {
2835 0 : *opt_microblock_hdr_size = sizeof(fd_microblock_hdr_t);
2836 0 : }
2837 :
2838 0 : return 0;
2839 0 : }
2840 :
2841 : /* if we are currently in the middle of a batch, batch_cnt will include the current batch.
2842 : if we are at the start of a batch, batch_cnt will include the current batch. */
2843 : static fd_raw_block_txn_iter_t
2844 : find_next_txn_in_raw_block( uchar const * orig_data,
2845 : fd_block_entry_batch_t const * batches, /* The batch we are currently consuming. */
2846 : ulong batch_cnt, /* Includes batch we are currently consuming. */
2847 : ulong curr_offset,
2848 0 : ulong num_microblocks ) {
2849 :
2850 : /* At this point, all the transactions in the current microblock have been consumed
2851 : by fd_raw_block_txn_iter_next */
2852 :
2853 : /* Case 1: there are microblocks remaining in the current batch */
2854 0 : for( ulong i=0UL; i<num_microblocks; i++ ) {
2855 0 : ulong microblock_hdr_size = 0UL;
2856 0 : fd_microblock_info_t microblock_info = {0};
2857 0 : if( FD_UNLIKELY( fd_runtime_parse_microblock_hdr( orig_data + curr_offset,
2858 0 : batches->end_off - curr_offset,
2859 0 : µblock_info.microblock_hdr,
2860 0 : µblock_hdr_size ) ) ) {
2861 : /* TODO: improve error handling */
2862 0 : FD_LOG_ERR(( "premature end of batch" ));
2863 0 : }
2864 0 : curr_offset += microblock_hdr_size;
2865 :
2866 : /* If we have found a microblock with transactions in the current batch, return that */
2867 0 : if( FD_LIKELY( microblock_info.microblock_hdr.txn_cnt ) ) {
2868 0 : return (fd_raw_block_txn_iter_t){
2869 0 : .curr_batch = batches,
2870 0 : .orig_data = orig_data,
2871 0 : .remaining_batches = batch_cnt,
2872 0 : .remaining_microblocks = fd_ulong_sat_sub( fd_ulong_sat_sub(num_microblocks, i), 1UL),
2873 0 : .remaining_txns = microblock_info.microblock_hdr.txn_cnt,
2874 0 : .curr_offset = curr_offset,
2875 0 : .curr_txn_sz = ULONG_MAX
2876 0 : };
2877 0 : }
2878 0 : }
2879 :
2880 : /* If we have consumed the current batch, but did not find any txns, we need to move on to the next one */
2881 0 : curr_offset = batches->end_off;
2882 0 : batch_cnt = fd_ulong_sat_sub( batch_cnt, 1UL );
2883 0 : batches++;
2884 :
2885 : /* Case 2: need to find the next batch with a microblock in that has a non-zero number of txns */
2886 0 : for( ulong i=0UL; i<batch_cnt; i++ ) {
2887 : /* Sanity-check that we have not over-shot the end of the batch */
2888 0 : ulong const batch_end_off = batches[i].end_off;
2889 0 : if( FD_UNLIKELY( curr_offset+sizeof(ulong)>batch_end_off ) ) {
2890 0 : FD_LOG_ERR(( "premature end of batch" ));
2891 0 : }
2892 :
2893 : /* Consume the ulong describing how many microblocks there are */
2894 0 : num_microblocks = FD_LOAD( ulong, orig_data + curr_offset );
2895 0 : curr_offset += sizeof(ulong);
2896 :
2897 : /* Iterate over each microblock until we find one with a non-zero txn cnt */
2898 0 : for( ulong j=0UL; j<num_microblocks; j++ ) {
2899 0 : ulong microblock_hdr_size = 0UL;
2900 0 : fd_microblock_info_t microblock_info = {0};
2901 0 : if( FD_UNLIKELY( fd_runtime_parse_microblock_hdr( orig_data + curr_offset,
2902 0 : batch_end_off - curr_offset,
2903 0 : µblock_info.microblock_hdr,
2904 0 : µblock_hdr_size ) ) ) {
2905 : /* TODO: improve error handling */
2906 0 : FD_LOG_ERR(( "premature end of batch" ));
2907 0 : }
2908 0 : curr_offset += microblock_hdr_size;
2909 :
2910 : /* If we have found a microblock with a non-zero number of transactions in, return that */
2911 0 : if( FD_LIKELY( microblock_info.microblock_hdr.txn_cnt ) ) {
2912 0 : return (fd_raw_block_txn_iter_t){
2913 0 : .curr_batch = &batches[i],
2914 0 : .orig_data = orig_data,
2915 0 : .remaining_batches = fd_ulong_sat_sub( batch_cnt, i ),
2916 0 : .remaining_microblocks = fd_ulong_sat_sub( fd_ulong_sat_sub( num_microblocks, j ), 1UL ),
2917 0 : .remaining_txns = microblock_info.microblock_hdr.txn_cnt,
2918 0 : .curr_offset = curr_offset,
2919 0 : .curr_txn_sz = ULONG_MAX
2920 0 : };
2921 0 : }
2922 0 : }
2923 :
2924 : /* Skip to the start of the next batch */
2925 0 : curr_offset = batch_end_off;
2926 0 : }
2927 :
2928 : /* Case 3: we didn't manage to find any microblocks with non-zero transaction counts in */
2929 0 : return (fd_raw_block_txn_iter_t) {
2930 0 : .curr_batch = batches,
2931 0 : .orig_data = orig_data,
2932 0 : .remaining_batches = 0UL,
2933 0 : .remaining_microblocks = 0UL,
2934 0 : .remaining_txns = 0UL,
2935 0 : .curr_offset = curr_offset,
2936 0 : .curr_txn_sz = ULONG_MAX
2937 0 : };
2938 0 : }
2939 :
2940 : /* Public API */
2941 :
2942 : fd_raw_block_txn_iter_t
2943 : fd_raw_block_txn_iter_init( uchar const * orig_data,
2944 : fd_block_entry_batch_t const * batches,
2945 0 : ulong batch_cnt ) {
2946 : /* In general, every read of a lower level count should lead to a
2947 : decrement of a higher level count. For example, reading a count
2948 : of microblocks should lead to a decrement of the number of
2949 : remaining batches. In some sense, the batch count is drained into
2950 : the microblock count. */
2951 :
2952 0 : ulong num_microblocks = FD_LOAD( ulong, orig_data );
2953 0 : return find_next_txn_in_raw_block( orig_data, batches, batch_cnt, sizeof(ulong), num_microblocks );
2954 0 : }
2955 :
2956 : ulong
2957 0 : fd_raw_block_txn_iter_done( fd_raw_block_txn_iter_t iter ) {
2958 0 : return iter.remaining_batches==0UL && iter.remaining_microblocks==0UL && iter.remaining_txns==0UL;
2959 0 : }
2960 :
2961 : fd_raw_block_txn_iter_t
2962 0 : fd_raw_block_txn_iter_next( fd_raw_block_txn_iter_t iter ) {
2963 0 : ulong const batch_end_off = iter.curr_batch->end_off;
2964 0 : fd_txn_p_t out_txn;
2965 0 : if( iter.curr_txn_sz == ULONG_MAX ) {
2966 0 : ulong payload_sz = 0;
2967 0 : ulong txn_sz = fd_txn_parse_core( iter.orig_data + iter.curr_offset, fd_ulong_min( batch_end_off - iter.curr_offset, FD_TXN_MTU), TXN(&out_txn), NULL, &payload_sz );
2968 0 : if( FD_UNLIKELY( !txn_sz || txn_sz>FD_TXN_MTU ) ) {
2969 0 : FD_LOG_ERR(("Invalid txn parse"));
2970 0 : }
2971 0 : iter.curr_offset += payload_sz;
2972 0 : } else {
2973 0 : iter.curr_offset += iter.curr_txn_sz;
2974 0 : iter.curr_txn_sz = ULONG_MAX;
2975 0 : }
2976 :
2977 0 : if( --iter.remaining_txns ) {
2978 0 : return iter;
2979 0 : }
2980 :
2981 0 : return find_next_txn_in_raw_block( iter.orig_data,
2982 0 : iter.curr_batch,
2983 0 : iter.remaining_batches,
2984 0 : iter.curr_offset,
2985 0 : iter.remaining_microblocks );
2986 0 : }
2987 :
2988 : void
2989 0 : fd_raw_block_txn_iter_ele( fd_raw_block_txn_iter_t iter, fd_txn_p_t * out_txn ) {
2990 0 : ulong const batch_end_off = iter.curr_batch->end_off;
2991 0 : ulong payload_sz = 0UL;
2992 0 : ulong txn_sz = fd_txn_parse_core( iter.orig_data + iter.curr_offset,
2993 0 : fd_ulong_min( batch_end_off - iter.curr_offset, FD_TXN_MTU ),
2994 0 : TXN( out_txn ), NULL, &payload_sz );
2995 :
2996 0 : if( FD_UNLIKELY( !txn_sz || txn_sz>FD_TXN_MTU ) ) {
2997 0 : FD_LOG_ERR(( "Invalid txn parse %lu", txn_sz ));
2998 0 : }
2999 0 : fd_memcpy( out_txn->payload, iter.orig_data + iter.curr_offset, payload_sz );
3000 0 : out_txn->payload_sz = (ushort)payload_sz;
3001 0 : iter.curr_txn_sz = payload_sz;
3002 0 : }
3003 :
3004 : /******************************************************************************/
3005 : /* Block Parsing logic (Only for offline replay) */
3006 : /******************************************************************************/
3007 :
3008 : /* The below runtime block parsing and block destroying logic is ONLY used in
3009 : offline replay to simulate the block parsing/freeing that would occur in
3010 : the full, live firedancer client. This is done via two APIs:
3011 : fd_runtime_block_prepare and fd_runtime_block_destroy. */
3012 :
3013 : /* Helpers for fd_runtime_block_prepare */
3014 :
3015 : static int
3016 : fd_runtime_parse_microblock_txns( void const * buf,
3017 : ulong buf_sz,
3018 : fd_microblock_hdr_t const * microblock_hdr,
3019 : fd_txn_p_t * out_txns,
3020 : ulong * out_signature_cnt,
3021 : ulong * out_account_cnt,
3022 0 : ulong * out_microblock_txns_sz ) {
3023 :
3024 0 : ulong buf_off = 0UL;
3025 0 : ulong signature_cnt = 0UL;
3026 0 : ulong account_cnt = 0UL;
3027 :
3028 0 : for( ulong i=0UL; i<microblock_hdr->txn_cnt; i++ ) {
3029 0 : ulong payload_sz = 0UL;
3030 0 : ulong txn_sz = fd_txn_parse_core( (uchar const *)buf + buf_off,
3031 0 : fd_ulong_min( buf_sz-buf_off, FD_TXN_MTU ),
3032 0 : TXN( &out_txns[i] ),
3033 0 : NULL,
3034 0 : &payload_sz );
3035 0 : if( FD_UNLIKELY( !txn_sz || txn_sz>FD_TXN_MTU ) ) {
3036 0 : return -1;
3037 0 : }
3038 :
3039 0 : fd_memcpy( out_txns[i].payload, (uchar *)buf + buf_off, payload_sz );
3040 0 : out_txns[i].payload_sz = (ushort)payload_sz;
3041 :
3042 0 : signature_cnt += TXN( &out_txns[i] )->signature_cnt;
3043 0 : account_cnt += fd_txn_account_cnt( TXN(&out_txns[i]), FD_TXN_ACCT_CAT_ALL );
3044 0 : buf_off += payload_sz;
3045 0 : }
3046 :
3047 0 : *out_signature_cnt = signature_cnt;
3048 0 : *out_account_cnt = account_cnt;
3049 0 : *out_microblock_txns_sz = buf_off;
3050 :
3051 0 : return 0;
3052 0 : }
3053 :
3054 : static int
3055 : fd_runtime_microblock_prepare( void const * buf,
3056 : ulong buf_sz,
3057 : fd_valloc_t valloc,
3058 0 : fd_microblock_info_t * out_microblock_info ) {
3059 :
3060 0 : fd_microblock_info_t microblock_info = {
3061 0 : .raw_microblock = buf,
3062 0 : .signature_cnt = 0UL,
3063 0 : };
3064 0 : ulong buf_off = 0UL;
3065 0 : ulong hdr_sz = 0UL;
3066 0 : if( FD_UNLIKELY( fd_runtime_parse_microblock_hdr( buf, buf_sz, µblock_info.microblock_hdr, &hdr_sz ) ) ) {
3067 0 : return -1;
3068 0 : }
3069 0 : buf_off += hdr_sz;
3070 :
3071 0 : ulong txn_cnt = microblock_info.microblock_hdr.txn_cnt;
3072 0 : microblock_info.txns = fd_valloc_malloc( valloc, alignof(fd_txn_p_t), txn_cnt * sizeof(fd_txn_p_t) );
3073 0 : ulong txns_sz = 0UL;
3074 0 : if( FD_UNLIKELY( fd_runtime_parse_microblock_txns( (uchar *)buf + buf_off,
3075 0 : buf_sz - buf_off,
3076 0 : µblock_info.microblock_hdr,
3077 0 : microblock_info.txns,
3078 0 : µblock_info.signature_cnt,
3079 0 : µblock_info.account_cnt,
3080 0 : &txns_sz ) ) ) {
3081 0 : fd_valloc_free( valloc, microblock_info.txns );
3082 0 : return -1;
3083 0 : }
3084 :
3085 0 : buf_off += txns_sz;
3086 0 : microblock_info.raw_microblock_sz = buf_off;
3087 0 : *out_microblock_info = microblock_info;
3088 :
3089 0 : return 0;
3090 0 : }
3091 :
3092 : static int
3093 : fd_runtime_microblock_batch_prepare( void const * buf,
3094 : ulong buf_sz,
3095 : fd_valloc_t valloc,
3096 0 : fd_microblock_batch_info_t * out_microblock_batch_info ) {
3097 :
3098 0 : fd_microblock_batch_info_t microblock_batch_info = {
3099 0 : .raw_microblock_batch = buf,
3100 0 : .signature_cnt = 0UL,
3101 0 : .txn_cnt = 0UL,
3102 0 : .account_cnt = 0UL,
3103 0 : };
3104 0 : ulong buf_off = 0UL;
3105 :
3106 0 : if( FD_UNLIKELY( buf_sz<sizeof(ulong) ) ) {
3107 0 : FD_LOG_WARNING(( "microblock batch buffer too small" ));
3108 0 : return -1;
3109 0 : }
3110 0 : ulong microblock_cnt = FD_LOAD( ulong, buf );
3111 0 : buf_off += sizeof(ulong);
3112 :
3113 0 : microblock_batch_info.microblock_cnt = microblock_cnt;
3114 0 : microblock_batch_info.microblock_infos = fd_valloc_malloc( valloc, alignof(fd_microblock_info_t), microblock_cnt * sizeof(fd_microblock_info_t) );
3115 :
3116 0 : ulong signature_cnt = 0UL;
3117 0 : ulong txn_cnt = 0UL;
3118 0 : ulong account_cnt = 0UL;
3119 0 : for( ulong i=0UL; i<microblock_cnt; i++ ) {
3120 0 : fd_microblock_info_t * microblock_info = µblock_batch_info.microblock_infos[i];
3121 0 : if( FD_UNLIKELY( fd_runtime_microblock_prepare( (uchar const *)buf + buf_off, buf_sz - buf_off, valloc, microblock_info ) ) ) {
3122 0 : fd_valloc_free( valloc, microblock_batch_info.microblock_infos );
3123 0 : return -1;
3124 0 : }
3125 :
3126 0 : signature_cnt += microblock_info->signature_cnt;
3127 0 : txn_cnt += microblock_info->microblock_hdr.txn_cnt;
3128 0 : account_cnt += microblock_info->account_cnt;
3129 0 : buf_off += microblock_info->raw_microblock_sz;
3130 0 : }
3131 :
3132 0 : microblock_batch_info.signature_cnt = signature_cnt;
3133 0 : microblock_batch_info.txn_cnt = txn_cnt;
3134 0 : microblock_batch_info.account_cnt = account_cnt;
3135 0 : microblock_batch_info.raw_microblock_batch_sz = buf_off;
3136 :
3137 0 : *out_microblock_batch_info = microblock_batch_info;
3138 :
3139 0 : return 0;
3140 0 : }
3141 :
3142 : /* This function is used for parsing/preparing blocks during offline runtime replay. */
3143 : static int
3144 : fd_runtime_block_prepare( fd_blockstore_t * blockstore,
3145 : fd_block_t * block,
3146 : ulong slot,
3147 : fd_valloc_t valloc,
3148 0 : fd_block_info_t * out_block_info ) {
3149 0 : uchar const * buf = fd_blockstore_block_data_laddr( blockstore, block );
3150 0 : ulong const buf_sz = block->data_sz;
3151 0 : fd_block_entry_batch_t const * batch_laddr = fd_blockstore_block_batch_laddr( blockstore, block );
3152 0 : ulong const batch_cnt = block->batch_cnt;
3153 :
3154 0 : fd_block_info_t block_info = {
3155 0 : .raw_block = buf,
3156 0 : .raw_block_sz = buf_sz,
3157 0 : };
3158 :
3159 0 : ulong microblock_batch_cnt = 0UL;
3160 0 : ulong microblock_cnt = 0UL;
3161 0 : ulong signature_cnt = 0UL;
3162 0 : ulong txn_cnt = 0UL;
3163 0 : ulong account_cnt = 0UL;
3164 0 : block_info.microblock_batch_infos = fd_valloc_malloc( valloc, alignof(fd_microblock_batch_info_t), block->batch_cnt * sizeof(fd_microblock_batch_info_t) );
3165 :
3166 0 : ulong buf_off = 0UL;
3167 0 : for( microblock_batch_cnt=0UL; microblock_batch_cnt < batch_cnt; microblock_batch_cnt++ ) {
3168 0 : ulong const batch_end_off = batch_laddr[ microblock_batch_cnt ].end_off;
3169 0 : fd_microblock_batch_info_t * microblock_batch_info = block_info.microblock_batch_infos + microblock_batch_cnt;
3170 0 : if( FD_UNLIKELY( fd_runtime_microblock_batch_prepare( buf + buf_off, batch_end_off - buf_off, valloc, microblock_batch_info ) ) ) {
3171 0 : return -1;
3172 0 : }
3173 :
3174 0 : signature_cnt += microblock_batch_info->signature_cnt;
3175 0 : txn_cnt += microblock_batch_info->txn_cnt;
3176 0 : account_cnt += microblock_batch_info->account_cnt;
3177 0 : microblock_cnt += microblock_batch_info->microblock_cnt;
3178 :
3179 0 : uchar allow_trailing = 1UL;
3180 0 : buf_off += microblock_batch_info->raw_microblock_batch_sz;
3181 0 : if( FD_UNLIKELY( buf_off > batch_end_off ) ) {
3182 0 : FD_LOG_ERR(( "parser error: shouldn't have been allowed to read past batch boundary" ));
3183 0 : }
3184 0 : if( FD_UNLIKELY( buf_off < batch_end_off ) ) {
3185 0 : if( FD_LIKELY( allow_trailing ) ) {
3186 0 : FD_LOG_NOTICE(( "ignoring %lu trailing bytes in slot %lu batch %lu", batch_end_off-buf_off, slot, microblock_batch_cnt ));
3187 0 : }
3188 0 : if( FD_UNLIKELY( !allow_trailing ) ) {
3189 0 : FD_LOG_WARNING(( "%lu trailing bytes in slot %lu batch %lu", batch_end_off-buf_off, slot, microblock_batch_cnt ));
3190 0 : return -1;
3191 0 : }
3192 0 : }
3193 0 : buf_off = batch_end_off;
3194 0 : }
3195 :
3196 0 : block_info.microblock_batch_cnt = microblock_batch_cnt;
3197 0 : block_info.microblock_cnt = microblock_cnt;
3198 0 : block_info.signature_cnt = signature_cnt;
3199 0 : block_info.txn_cnt = txn_cnt;
3200 0 : block_info.account_cnt = account_cnt;
3201 :
3202 0 : *out_block_info = block_info;
3203 :
3204 0 : return 0;
3205 0 : }
3206 :
3207 : /* Block collecting (Only for offline replay) */
3208 :
3209 : static ulong
3210 : fd_runtime_microblock_collect_txns( fd_microblock_info_t const * microblock_info,
3211 0 : fd_txn_p_t * out_txns ) {
3212 0 : ulong txn_cnt = microblock_info->microblock_hdr.txn_cnt;
3213 0 : fd_memcpy( out_txns, microblock_info->txns, txn_cnt * sizeof(fd_txn_p_t) );
3214 :
3215 0 : return txn_cnt;
3216 0 : }
3217 :
3218 : static ulong
3219 : fd_runtime_microblock_batch_collect_txns( fd_microblock_batch_info_t const * microblock_batch_info,
3220 0 : fd_txn_p_t * out_txns ) {
3221 0 : for( ulong i=0UL; i<microblock_batch_info->microblock_cnt; i++ ) {
3222 0 : ulong txns_collected = fd_runtime_microblock_collect_txns( µblock_batch_info->microblock_infos[i], out_txns );
3223 0 : out_txns += txns_collected;
3224 0 : }
3225 :
3226 0 : return microblock_batch_info->txn_cnt;
3227 0 : }
3228 :
3229 : static ulong
3230 : fd_runtime_block_collect_txns( fd_block_info_t const * block_info,
3231 0 : fd_txn_p_t * out_txns ) {
3232 0 : for( ulong i=0UL; i<block_info->microblock_batch_cnt; i++ ) {
3233 0 : ulong txns_collected = fd_runtime_microblock_batch_collect_txns( &block_info->microblock_batch_infos[i], out_txns );
3234 0 : out_txns += txns_collected;
3235 0 : }
3236 :
3237 0 : return block_info->txn_cnt;
3238 0 : }
3239 :
3240 : /* TODO: Move this around once the changes to it are updated. */
3241 :
3242 : int
3243 : fd_runtime_block_verify_ticks( fd_block_info_t const * block_info,
3244 : ulong tick_height,
3245 0 : ulong max_tick_height ) {
3246 0 : (void)tick_height; (void)max_tick_height;
3247 0 : ulong tick_count = 0UL;
3248 0 : for( ulong i = 0UL; i < block_info->microblock_batch_cnt; i++ ) {
3249 0 : fd_microblock_batch_info_t const * microblock_batch_info = &block_info->microblock_batch_infos[ i ];
3250 0 : for( ulong j = 0UL; j < microblock_batch_info->microblock_cnt; j++ ) {
3251 0 : fd_microblock_info_t const * microblock_info = µblock_batch_info->microblock_infos[ i ];
3252 0 : if( microblock_info->microblock_hdr.txn_cnt == 0UL ) {
3253 : /* if this mblk is a tick */
3254 0 : tick_count++;
3255 0 : }
3256 0 : }
3257 0 : }
3258 0 : (void)tick_count;
3259 0 : return 0;
3260 0 : }
3261 :
3262 : /******************************************************************************/
3263 : /* Genesis */
3264 : /*******************************************************************************/
3265 :
3266 : static void
3267 : fd_runtime_init_program( fd_exec_slot_ctx_t * slot_ctx )
3268 0 : {
3269 0 : fd_sysvar_recent_hashes_init( slot_ctx );
3270 0 : fd_sysvar_clock_init( slot_ctx );
3271 0 : fd_sysvar_slot_history_init( slot_ctx );
3272 0 : fd_sysvar_epoch_schedule_init( slot_ctx );
3273 0 : if( !FD_FEATURE_ACTIVE( slot_ctx, disable_fees_sysvar ) ) {
3274 0 : fd_sysvar_fees_init( slot_ctx );
3275 0 : }
3276 0 : fd_sysvar_rent_init( slot_ctx );
3277 0 : fd_sysvar_stake_history_init( slot_ctx );
3278 0 : fd_sysvar_last_restart_slot_init( slot_ctx );
3279 :
3280 0 : fd_builtin_programs_init( slot_ctx );
3281 0 : fd_stake_program_config_init( slot_ctx );
3282 0 : }
3283 :
3284 : static void
3285 : fd_runtime_init_bank_from_genesis( fd_exec_slot_ctx_t * slot_ctx,
3286 : fd_genesis_solana_t * genesis_block,
3287 0 : fd_hash_t const * genesis_hash ) {
3288 0 : slot_ctx->slot_bank.slot = 0UL;
3289 :
3290 0 : memcpy( &slot_ctx->slot_bank.poh, genesis_hash->hash, FD_SHA256_HASH_SZ );
3291 0 : memset( slot_ctx->slot_bank.banks_hash.hash, 0, FD_SHA256_HASH_SZ );
3292 :
3293 0 : slot_ctx->slot_bank.fee_rate_governor = genesis_block->fee_rate_governor;
3294 0 : slot_ctx->slot_bank.lamports_per_signature = 0UL;
3295 0 : slot_ctx->prev_lamports_per_signature = 0UL;
3296 :
3297 0 : fd_poh_config_t * poh = &genesis_block->poh_config;
3298 0 : fd_exec_epoch_ctx_t * epoch_ctx = slot_ctx->epoch_ctx;
3299 0 : fd_epoch_bank_t * epoch_bank = fd_exec_epoch_ctx_epoch_bank( epoch_ctx );
3300 0 : if( poh->has_hashes_per_tick ) {
3301 0 : epoch_bank->hashes_per_tick = poh->hashes_per_tick;
3302 0 : } else {
3303 0 : epoch_bank->hashes_per_tick = 0UL;
3304 0 : }
3305 0 : epoch_bank->ticks_per_slot = genesis_block->ticks_per_slot;
3306 0 : epoch_bank->genesis_creation_time = genesis_block->creation_time;
3307 0 : uint128 target_tick_duration = ((uint128)poh->target_tick_duration.seconds * 1000000000UL + (uint128)poh->target_tick_duration.nanoseconds);
3308 0 : epoch_bank->ns_per_slot = target_tick_duration * epoch_bank->ticks_per_slot;
3309 :
3310 0 : epoch_bank->slots_per_year = SECONDS_PER_YEAR * (1000000000.0 / (double)target_tick_duration) / (double)epoch_bank->ticks_per_slot;
3311 0 : epoch_bank->genesis_creation_time = genesis_block->creation_time;
3312 0 : slot_ctx->slot_bank.max_tick_height = epoch_bank->ticks_per_slot * (slot_ctx->slot_bank.slot + 1);
3313 0 : epoch_bank->epoch_schedule = genesis_block->epoch_schedule;
3314 0 : epoch_bank->inflation = genesis_block->inflation;
3315 0 : epoch_bank->rent = genesis_block->rent;
3316 0 : slot_ctx->slot_bank.block_height = 0UL;
3317 :
3318 0 : fd_block_block_hash_entry_t * hashes = slot_ctx->slot_bank.recent_block_hashes.hashes =
3319 0 : deq_fd_block_block_hash_entry_t_alloc( slot_ctx->valloc, FD_SYSVAR_RECENT_HASHES_CAP );
3320 0 : fd_block_block_hash_entry_t * elem = deq_fd_block_block_hash_entry_t_push_head_nocopy( hashes );
3321 0 : fd_block_block_hash_entry_new( elem );
3322 0 : fd_memcpy( elem->blockhash.hash, genesis_hash, FD_SHA256_HASH_SZ );
3323 0 : elem->fee_calculator.lamports_per_signature = 0UL;
3324 :
3325 0 : slot_ctx->slot_bank.block_hash_queue.ages_root = NULL;
3326 0 : slot_ctx->slot_bank.block_hash_queue.ages_pool = fd_hash_hash_age_pair_t_map_alloc( slot_ctx->valloc, 400 );
3327 0 : fd_hash_hash_age_pair_t_mapnode_t * node = fd_hash_hash_age_pair_t_map_acquire( slot_ctx->slot_bank.block_hash_queue.ages_pool );
3328 0 : node->elem = (fd_hash_hash_age_pair_t){
3329 0 : .key = *genesis_hash,
3330 0 : .val = (fd_hash_age_t){ .hash_index = 0UL, .fee_calculator = (fd_fee_calculator_t){.lamports_per_signature = 0UL}, .timestamp = (ulong)fd_log_wallclock() }
3331 0 : };
3332 0 : fd_hash_hash_age_pair_t_map_insert( slot_ctx->slot_bank.block_hash_queue.ages_pool, &slot_ctx->slot_bank.block_hash_queue.ages_root, node );
3333 0 : slot_ctx->slot_bank.block_hash_queue.last_hash_index = 0UL;
3334 0 : slot_ctx->slot_bank.block_hash_queue.last_hash = fd_valloc_malloc( slot_ctx->valloc, FD_HASH_ALIGN, FD_HASH_FOOTPRINT );
3335 0 : fd_memcpy( slot_ctx->slot_bank.block_hash_queue.last_hash, genesis_hash, FD_SHA256_HASH_SZ );
3336 0 : slot_ctx->slot_bank.block_hash_queue.max_age = FD_BLOCKHASH_QUEUE_MAX_ENTRIES;
3337 :
3338 0 : slot_ctx->signature_cnt = 0UL;
3339 :
3340 : /* Derive epoch stakes */
3341 :
3342 0 : fd_vote_accounts_pair_t_mapnode_t * vacc_pool = fd_exec_epoch_ctx_stake_votes_join( epoch_ctx );
3343 0 : fd_vote_accounts_pair_t_mapnode_t * vacc_root = NULL;
3344 0 : FD_TEST( vacc_pool );
3345 :
3346 0 : fd_delegation_pair_t_mapnode_t * sacc_pool = fd_exec_epoch_ctx_stake_delegations_join( epoch_ctx );
3347 0 : fd_delegation_pair_t_mapnode_t * sacc_root = NULL;
3348 :
3349 0 : fd_acc_lamports_t capitalization = 0UL;
3350 :
3351 0 : for( ulong i=0UL; i<genesis_block->accounts_len; i++ ) {
3352 0 : fd_pubkey_account_pair_t const * acc = &genesis_block->accounts[i];
3353 0 : capitalization = fd_ulong_sat_add( capitalization, acc->account.lamports );
3354 :
3355 0 : if( !memcmp(acc->account.owner.key, fd_solana_vote_program_id.key, sizeof(fd_pubkey_t)) ) {
3356 : /* Vote Program Account */
3357 0 : fd_vote_accounts_pair_t_mapnode_t *node = fd_vote_accounts_pair_t_map_acquire(vacc_pool);
3358 0 : FD_TEST( node );
3359 :
3360 0 : fd_vote_block_timestamp_t last_timestamp = {0};
3361 0 : fd_pubkey_t node_pubkey = {0};
3362 0 : FD_SCRATCH_SCOPE_BEGIN {
3363 : /* Deserialize content */
3364 0 : fd_vote_state_versioned_t vs[1];
3365 0 : fd_bincode_decode_ctx_t decode =
3366 0 : { .data = acc->account.data,
3367 0 : .dataend = acc->account.data + acc->account.data_len,
3368 0 : .valloc = fd_scratch_virtual() };
3369 0 : int decode_err = fd_vote_state_versioned_decode( vs, &decode );
3370 0 : if( FD_UNLIKELY( decode_err!=FD_BINCODE_SUCCESS ) ) {
3371 0 : FD_LOG_WARNING(( "fd_vote_state_versioned_decode failed (%d)", decode_err ));
3372 0 : return;
3373 0 : }
3374 :
3375 0 : switch( vs->discriminant )
3376 0 : {
3377 0 : case fd_vote_state_versioned_enum_current:
3378 0 : last_timestamp = vs->inner.current.last_timestamp;
3379 0 : node_pubkey = vs->inner.current.node_pubkey;
3380 0 : break;
3381 0 : case fd_vote_state_versioned_enum_v0_23_5:
3382 0 : last_timestamp = vs->inner.v0_23_5.last_timestamp;
3383 0 : node_pubkey = vs->inner.v0_23_5.node_pubkey;
3384 0 : break;
3385 0 : case fd_vote_state_versioned_enum_v1_14_11:
3386 0 : last_timestamp = vs->inner.v1_14_11.last_timestamp;
3387 0 : node_pubkey = vs->inner.v1_14_11.node_pubkey;
3388 0 : break;
3389 0 : default:
3390 0 : __builtin_unreachable();
3391 0 : }
3392 :
3393 0 : } FD_SCRATCH_SCOPE_END;
3394 :
3395 0 : fd_memcpy(node->elem.key.key, acc->key.key, sizeof(fd_pubkey_t));
3396 0 : node->elem.stake = acc->account.lamports;
3397 0 : node->elem.value = (fd_solana_vote_account_t){
3398 0 : .lamports = acc->account.lamports,
3399 0 : .node_pubkey = node_pubkey,
3400 0 : .last_timestamp_ts = last_timestamp.timestamp,
3401 0 : .last_timestamp_slot = last_timestamp.slot,
3402 0 : .owner = acc->account.owner,
3403 0 : .executable = acc->account.executable,
3404 0 : .rent_epoch = acc->account.rent_epoch
3405 0 : };
3406 :
3407 0 : fd_vote_accounts_pair_t_map_insert( vacc_pool, &vacc_root, node );
3408 :
3409 0 : FD_LOG_INFO(( "Adding genesis vote account: key=%s stake=%lu",
3410 0 : FD_BASE58_ENC_32_ALLOCA( node->elem.key.key ),
3411 0 : node->elem.stake ));
3412 0 : } else if( !memcmp( acc->account.owner.key, fd_solana_stake_program_id.key, sizeof(fd_pubkey_t) ) ) {
3413 : /* stake program account */
3414 0 : fd_stake_state_v2_t stake_state = {0};
3415 0 : fd_account_meta_t meta = { .dlen = acc->account.data_len };
3416 0 : fd_borrowed_account_t stake_account = {
3417 0 : .const_data = acc->account.data,
3418 0 : .const_meta = &meta,
3419 0 : .data = acc->account.data,
3420 0 : .meta = &meta
3421 0 : };
3422 0 : FD_TEST( fd_stake_get_state(&stake_account, &slot_ctx->valloc, &stake_state) == 0 );
3423 0 : if( !stake_state.inner.stake.stake.delegation.stake ) {
3424 0 : continue;
3425 0 : }
3426 0 : fd_delegation_pair_t_mapnode_t query_node = {0};
3427 0 : fd_memcpy(&query_node.elem.account, acc->key.key, sizeof(fd_pubkey_t));
3428 0 : fd_delegation_pair_t_mapnode_t * node = fd_delegation_pair_t_map_find( sacc_pool, sacc_root, &query_node );
3429 :
3430 0 : if( !node ) {
3431 0 : node = fd_delegation_pair_t_map_acquire( sacc_pool );
3432 0 : fd_memcpy( &node->elem.account, acc->key.key, sizeof(fd_pubkey_t) );
3433 0 : fd_memcpy( &node->elem.delegation, &stake_state.inner.stake.stake.delegation, sizeof(fd_delegation_t) );
3434 0 : fd_delegation_pair_t_map_insert( sacc_pool, &sacc_root, node );
3435 0 : } else {
3436 0 : fd_memcpy( &node->elem.account, acc->key.key, sizeof(fd_pubkey_t) );
3437 0 : fd_memcpy( &node->elem.delegation, &stake_state.inner.stake.stake.delegation, sizeof(fd_delegation_t) );
3438 0 : }
3439 0 : } else if( !memcmp(acc->account.owner.key, fd_solana_feature_program_id.key, sizeof(fd_pubkey_t)) ) {
3440 : /* Feature Account */
3441 :
3442 : /* Scan list of feature IDs to resolve address => feature offset */
3443 0 : fd_feature_id_t const *found = NULL;
3444 0 : for( fd_feature_id_t const * id = fd_feature_iter_init();
3445 0 : !fd_feature_iter_done( id );
3446 0 : id = fd_feature_iter_next( id ) ) {
3447 0 : if( !memcmp( acc->key.key, id->id.key, sizeof(fd_pubkey_t) ) ) {
3448 0 : found = id;
3449 0 : break;
3450 0 : }
3451 0 : }
3452 :
3453 0 : if( found ) {
3454 : /* Load feature activation */
3455 0 : FD_SCRATCH_SCOPE_BEGIN {
3456 0 : fd_bincode_decode_ctx_t decode = {
3457 0 : .data = acc->account.data,
3458 0 : .dataend = acc->account.data + acc->account.data_len,
3459 0 : .valloc = fd_scratch_virtual()
3460 0 : };
3461 0 : fd_feature_t feature = {0};
3462 0 : int err = fd_feature_decode( &feature, &decode );
3463 0 : FD_TEST( err==FD_BINCODE_SUCCESS );
3464 0 : if( feature.has_activated_at ) {
3465 0 : FD_LOG_DEBUG(( "Feature %s activated at %lu (genesis)", FD_BASE58_ENC_32_ALLOCA( acc->key.key ), feature.activated_at ));
3466 0 : fd_features_set( &slot_ctx->epoch_ctx->features, found, feature.activated_at );
3467 0 : } else {
3468 0 : FD_LOG_DEBUG(( "Feature %s not activated (genesis)", FD_BASE58_ENC_32_ALLOCA( acc->key.key ) ));
3469 0 : fd_features_set( &slot_ctx->epoch_ctx->features, found, ULONG_MAX );
3470 0 : }
3471 0 : } FD_SCRATCH_SCOPE_END;
3472 0 : }
3473 0 : }
3474 0 : }
3475 :
3476 0 : slot_ctx->slot_bank.epoch_stakes.vote_accounts_pool = fd_vote_accounts_pair_t_map_alloc( slot_ctx->valloc, 100000 ); /* FIXME remove magic constant */
3477 0 : slot_ctx->slot_bank.epoch_stakes.vote_accounts_root = NULL;
3478 :
3479 0 : fd_vote_accounts_pair_t_mapnode_t * next_pool = fd_exec_epoch_ctx_next_epoch_stakes_join( slot_ctx->epoch_ctx );
3480 0 : fd_vote_accounts_pair_t_mapnode_t * next_root = NULL;
3481 :
3482 0 : for( fd_vote_accounts_pair_t_mapnode_t *n = fd_vote_accounts_pair_t_map_minimum( vacc_pool, vacc_root );
3483 0 : n;
3484 0 : n = fd_vote_accounts_pair_t_map_successor( vacc_pool, n )) {
3485 0 : fd_vote_accounts_pair_t_mapnode_t * e = fd_vote_accounts_pair_t_map_acquire( slot_ctx->slot_bank.epoch_stakes.vote_accounts_pool );
3486 0 : fd_memcpy( &e->elem, &n->elem, sizeof(fd_vote_accounts_pair_t) );
3487 0 : fd_vote_accounts_pair_t_map_insert( slot_ctx->slot_bank.epoch_stakes.vote_accounts_pool, &slot_ctx->slot_bank.epoch_stakes.vote_accounts_root, e );
3488 :
3489 0 : fd_vote_accounts_pair_t_mapnode_t * next_e = fd_vote_accounts_pair_t_map_acquire( next_pool );
3490 0 : fd_memcpy( &next_e->elem, &n->elem, sizeof(fd_vote_accounts_pair_t) );
3491 0 : fd_vote_accounts_pair_t_map_insert( next_pool, &next_root, next_e );
3492 0 : }
3493 :
3494 0 : for( fd_delegation_pair_t_mapnode_t *n = fd_delegation_pair_t_map_minimum( sacc_pool, sacc_root );
3495 0 : n;
3496 0 : n = fd_delegation_pair_t_map_successor( sacc_pool, n )) {
3497 0 : fd_vote_accounts_pair_t_mapnode_t query_voter = {0};
3498 0 : fd_pubkey_t * voter_pubkey = &n->elem.delegation.voter_pubkey;
3499 0 : fd_memcpy( &query_voter.elem.key, voter_pubkey, sizeof(fd_pubkey_t) );
3500 :
3501 0 : fd_vote_accounts_pair_t_mapnode_t * voter = fd_vote_accounts_pair_t_map_find( vacc_pool, vacc_root, &query_voter );
3502 :
3503 0 : if( !!voter ) {
3504 0 : voter->elem.stake = fd_ulong_sat_add( voter->elem.stake, n->elem.delegation.stake );
3505 0 : }
3506 0 : }
3507 :
3508 0 : epoch_bank->next_epoch_stakes = (fd_vote_accounts_t){
3509 0 : .vote_accounts_pool = next_pool,
3510 0 : .vote_accounts_root = next_root,
3511 0 : };
3512 :
3513 : /* Initializes the stakes cache in the Bank structure. */
3514 0 : epoch_bank->stakes = (fd_stakes_t){
3515 0 : .stake_delegations_pool = sacc_pool,
3516 0 : .stake_delegations_root = sacc_root,
3517 0 : .epoch = 0UL,
3518 0 : .unused = 0UL,
3519 0 : .vote_accounts = (fd_vote_accounts_t){
3520 0 : .vote_accounts_pool = vacc_pool,
3521 0 : .vote_accounts_root = vacc_root
3522 0 : },
3523 0 : .stake_history = {0}
3524 0 : };
3525 :
3526 0 : slot_ctx->slot_bank.capitalization = capitalization;
3527 0 : slot_ctx->slot_bank.timestamp_votes.votes_pool = fd_clock_timestamp_vote_t_map_alloc( slot_ctx->valloc, 10000 ); /* FIXME: remove magic constant */
3528 0 : slot_ctx->slot_bank.timestamp_votes.votes_root = NULL;
3529 :
3530 0 : }
3531 :
3532 : static int
3533 0 : fd_runtime_process_genesis_block( fd_exec_slot_ctx_t * slot_ctx, fd_capture_ctx_t * capture_ctx, fd_tpool_t * tpool ) {
3534 0 : ulong hashcnt_per_slot = slot_ctx->epoch_ctx->epoch_bank.hashes_per_tick * slot_ctx->epoch_ctx->epoch_bank.ticks_per_slot;
3535 0 : while( hashcnt_per_slot-- ) {
3536 0 : fd_sha256_hash( slot_ctx->slot_bank.poh.uc, sizeof(fd_hash_t), slot_ctx->slot_bank.poh.uc );
3537 0 : }
3538 :
3539 0 : slot_ctx->slot_bank.collected_execution_fees = 0UL;
3540 0 : slot_ctx->slot_bank.collected_priority_fees = 0UL;
3541 0 : slot_ctx->slot_bank.collected_rent = 0UL;
3542 0 : slot_ctx->signature_cnt = 0UL;
3543 0 : slot_ctx->txn_count = 0UL;
3544 0 : slot_ctx->nonvote_txn_count = 0UL;
3545 0 : slot_ctx->failed_txn_count = 0UL;
3546 0 : slot_ctx->nonvote_failed_txn_count = 0UL;
3547 0 : slot_ctx->total_compute_units_used = 0UL;
3548 :
3549 0 : fd_sysvar_slot_history_update( slot_ctx );
3550 :
3551 0 : fd_runtime_freeze( slot_ctx );
3552 :
3553 : /* sort and update bank hash */
3554 0 : int result = fd_update_hash_bank_tpool( slot_ctx, capture_ctx, &slot_ctx->slot_bank.banks_hash, slot_ctx->signature_cnt, tpool );
3555 0 : if( FD_UNLIKELY( result != FD_EXECUTOR_INSTR_SUCCESS ) ) {
3556 0 : FD_LOG_ERR(( "Failed to update bank hash with error=%d", result ));
3557 0 : }
3558 :
3559 0 : FD_TEST( FD_RUNTIME_EXECUTE_SUCCESS==fd_runtime_save_epoch_bank( slot_ctx ) );
3560 :
3561 0 : FD_TEST( FD_RUNTIME_EXECUTE_SUCCESS==fd_runtime_save_slot_bank( slot_ctx ) );
3562 :
3563 0 : return FD_RUNTIME_EXECUTE_SUCCESS;
3564 0 : }
3565 :
3566 : void
3567 : fd_runtime_read_genesis( fd_exec_slot_ctx_t * slot_ctx,
3568 : char const * genesis_filepath,
3569 : uchar is_snapshot,
3570 : fd_capture_ctx_t * capture_ctx,
3571 0 : fd_tpool_t * tpool ) {
3572 0 : if( strlen( genesis_filepath ) == 0 ) {
3573 0 : return;
3574 0 : }
3575 :
3576 0 : struct stat sbuf;
3577 0 : if( FD_UNLIKELY( stat( genesis_filepath, &sbuf) < 0 ) ) {
3578 0 : FD_LOG_ERR(( "cannot open %s : %s", genesis_filepath, strerror(errno) ));
3579 0 : }
3580 0 : int fd = open( genesis_filepath, O_RDONLY );
3581 0 : if( FD_UNLIKELY( fd < 0 ) ) {
3582 0 : FD_LOG_ERR(("cannot open %s : %s", genesis_filepath, strerror(errno)));
3583 0 : }
3584 :
3585 0 : fd_genesis_solana_t genesis_block = {0};
3586 0 : fd_genesis_solana_new( &genesis_block );
3587 0 : fd_hash_t genesis_hash;
3588 :
3589 0 : fd_epoch_bank_t * epoch_bank = fd_exec_epoch_ctx_epoch_bank( slot_ctx->epoch_ctx );
3590 :
3591 0 : FD_SCRATCH_SCOPE_BEGIN {
3592 0 : uchar * buf = fd_scratch_alloc( alignof(ulong), (ulong)sbuf.st_size );
3593 0 : ssize_t n = read( fd, buf, (ulong)sbuf.st_size );
3594 0 : close( fd );
3595 :
3596 0 : fd_bincode_decode_ctx_t decode_ctx = {
3597 0 : .data = buf,
3598 0 : .dataend = buf + n,
3599 0 : .valloc = slot_ctx->valloc,
3600 0 : };
3601 0 : if( FD_UNLIKELY( fd_genesis_solana_decode( &genesis_block, &decode_ctx ) ) ) {
3602 0 : FD_LOG_ERR(("fd_genesis_solana_decode failed"));
3603 0 : }
3604 :
3605 : // The hash is generated from the raw data... don't mess with this..
3606 0 : fd_sha256_hash( buf, (ulong)n, genesis_hash.uc );
3607 :
3608 0 : } FD_SCRATCH_SCOPE_END;
3609 :
3610 0 : fd_memcpy( epoch_bank->genesis_hash.uc, genesis_hash.uc, sizeof(fd_hash_t) );
3611 0 : epoch_bank->cluster_type = genesis_block.cluster_type;
3612 :
3613 0 : fd_funk_start_write( slot_ctx->acc_mgr->funk );
3614 :
3615 0 : if( !is_snapshot ) {
3616 0 : fd_runtime_init_bank_from_genesis( slot_ctx, &genesis_block, &genesis_hash );
3617 :
3618 0 : fd_runtime_init_program( slot_ctx );
3619 :
3620 0 : FD_LOG_DEBUG(( "start genesis accounts - count: %lu", genesis_block.accounts_len ));
3621 :
3622 0 : for( ulong i=0; i<genesis_block.accounts_len; i++ ) {
3623 0 : fd_pubkey_account_pair_t * a = &genesis_block.accounts[i];
3624 :
3625 0 : FD_BORROWED_ACCOUNT_DECL( rec );
3626 :
3627 0 : int err = fd_acc_mgr_modify( slot_ctx->acc_mgr,
3628 0 : slot_ctx->funk_txn,
3629 0 : &a->key,
3630 0 : /* do_create */ 1,
3631 0 : a->account.data_len,
3632 0 : rec );
3633 :
3634 0 : if( FD_UNLIKELY( err ) ) {
3635 0 : FD_LOG_ERR(( "fd_acc_mgr_modify failed (%d)", err ));
3636 0 : }
3637 :
3638 0 : rec->meta->dlen = a->account.data_len;
3639 0 : rec->meta->info.lamports = a->account.lamports;
3640 0 : rec->meta->info.rent_epoch = a->account.rent_epoch;
3641 0 : rec->meta->info.executable = a->account.executable;
3642 0 : memcpy( rec->meta->info.owner, a->account.owner.key, sizeof(fd_hash_t));
3643 0 : if( a->account.data_len ) {
3644 0 : memcpy( rec->data, a->account.data, a->account.data_len );
3645 0 : }
3646 0 : }
3647 :
3648 0 : FD_LOG_DEBUG(( "end genesis accounts" ));
3649 :
3650 0 : FD_LOG_DEBUG(( "native instruction processors - count: %lu", genesis_block.native_instruction_processors_len ));
3651 :
3652 0 : for( ulong i=0UL; i < genesis_block.native_instruction_processors_len; i++ ) {
3653 0 : fd_string_pubkey_pair_t * a = &genesis_block.native_instruction_processors[i];
3654 0 : fd_write_builtin_bogus_account( slot_ctx, a->pubkey.uc, (const char *) a->string, a->string_len );
3655 0 : }
3656 :
3657 0 : fd_features_restore( slot_ctx );
3658 :
3659 0 : slot_ctx->slot_bank.slot = 0UL;
3660 :
3661 0 : int err = fd_runtime_process_genesis_block( slot_ctx, capture_ctx, tpool );
3662 0 : if( FD_UNLIKELY( err ) ) {
3663 0 : FD_LOG_ERR(( "Genesis slot 0 execute failed with error %d", err ));
3664 0 : }
3665 0 : }
3666 :
3667 0 : slot_ctx->slot_bank.stake_account_keys.stake_accounts_root = NULL;
3668 0 : slot_ctx->slot_bank.stake_account_keys.stake_accounts_pool = fd_stake_accounts_pair_t_map_alloc( slot_ctx->valloc, 100000UL );
3669 :
3670 0 : slot_ctx->slot_bank.vote_account_keys.vote_accounts_root = NULL;
3671 0 : slot_ctx->slot_bank.vote_account_keys.vote_accounts_pool = fd_vote_accounts_pair_t_map_alloc( slot_ctx->valloc, 100000UL );
3672 :
3673 0 : fd_funk_end_write( slot_ctx->acc_mgr->funk );
3674 :
3675 0 : fd_bincode_destroy_ctx_t ctx2 = { .valloc = slot_ctx->valloc };
3676 0 : fd_genesis_solana_destroy( &genesis_block, &ctx2 );
3677 0 : }
3678 :
3679 : /******************************************************************************/
3680 : /* Offline Replay */
3681 : /******************************************************************************/
3682 :
3683 : /* As a note, currently offline and live replay of transactions has differences
3684 : with regards to how the execution environment is setup. These are helpers
3685 : used to emulate this behavior */
3686 :
3687 : struct fd_poh_verification_info {
3688 : fd_microblock_info_t const *microblock_info;
3689 : fd_hash_t const *in_poh_hash;
3690 : int success;
3691 : };
3692 : typedef struct fd_poh_verification_info fd_poh_verification_info_t;
3693 :
3694 : static void
3695 : fd_runtime_microblock_verify_info_collect( fd_microblock_info_t const * microblock_info,
3696 : fd_hash_t const * in_poh_hash,
3697 0 : fd_poh_verification_info_t * poh_verification_info ) {
3698 0 : poh_verification_info->microblock_info = microblock_info;
3699 0 : poh_verification_info->in_poh_hash = in_poh_hash;
3700 0 : poh_verification_info->success = 0;
3701 0 : }
3702 :
3703 : static void
3704 : fd_runtime_microblock_batch_verify_info_collect( fd_microblock_batch_info_t const * microblock_batch_info,
3705 : fd_hash_t const * in_poh_hash,
3706 0 : fd_poh_verification_info_t * poh_verification_info ) {
3707 0 : for( ulong i=0UL; i<microblock_batch_info->microblock_cnt; i++ ) {
3708 0 : fd_microblock_info_t const * microblock_info = µblock_batch_info->microblock_infos[i];
3709 0 : fd_runtime_microblock_verify_info_collect( microblock_info, in_poh_hash, &poh_verification_info[i] );
3710 0 : in_poh_hash = (fd_hash_t const *)µblock_info->microblock_hdr.hash;
3711 0 : }
3712 0 : }
3713 :
3714 : static void
3715 : fd_runtime_block_verify_info_collect( fd_block_info_t const * block_info,
3716 : fd_hash_t const * in_poh_hash,
3717 : fd_poh_verification_info_t * poh_verification_info)
3718 0 : {
3719 0 : for( ulong i=0UL; i<block_info->microblock_batch_cnt; i++ ) {
3720 0 : fd_microblock_batch_info_t const * microblock_batch_info = &block_info->microblock_batch_infos[i];
3721 :
3722 0 : fd_runtime_microblock_batch_verify_info_collect( microblock_batch_info, in_poh_hash, poh_verification_info );
3723 0 : in_poh_hash = (fd_hash_t const *)poh_verification_info[microblock_batch_info->microblock_cnt - 1].microblock_info->microblock_hdr.hash;
3724 0 : poh_verification_info += microblock_batch_info->microblock_cnt;
3725 0 : }
3726 0 : }
3727 :
3728 : static void
3729 : fd_runtime_poh_verify_wide_task( void * tpool,
3730 : ulong t0 FD_PARAM_UNUSED,
3731 : ulong t1 FD_PARAM_UNUSED,
3732 : void * args FD_PARAM_UNUSED,
3733 : void * reduce FD_PARAM_UNUSED,
3734 : ulong stride FD_PARAM_UNUSED,
3735 : ulong l0 FD_PARAM_UNUSED,
3736 : ulong l1 FD_PARAM_UNUSED,
3737 : ulong m0,
3738 : ulong m1 FD_PARAM_UNUSED,
3739 : ulong n0 FD_PARAM_UNUSED,
3740 0 : ulong n1 FD_PARAM_UNUSED ) {
3741 0 : fd_poh_verification_info_t * poh_info = (fd_poh_verification_info_t *)tpool + m0;
3742 :
3743 0 : fd_hash_t out_poh_hash = *poh_info->in_poh_hash;
3744 :
3745 0 : fd_microblock_info_t const *microblock_info = poh_info->microblock_info;
3746 0 : ulong hash_cnt = microblock_info->microblock_hdr.hash_cnt;
3747 0 : ulong txn_cnt = microblock_info->microblock_hdr.txn_cnt;
3748 :
3749 0 : if( !txn_cnt ) {
3750 0 : fd_poh_append( &out_poh_hash, hash_cnt );
3751 0 : } else {
3752 0 : if( hash_cnt ) {
3753 0 : fd_poh_append(&out_poh_hash, hash_cnt - 1);
3754 0 : }
3755 :
3756 0 : ulong leaf_cnt = microblock_info->signature_cnt;
3757 0 : uchar * commit = fd_alloca_check( FD_WBMTREE32_ALIGN, fd_wbmtree32_footprint(leaf_cnt));
3758 0 : fd_wbmtree32_leaf_t * leafs = fd_alloca_check(alignof(fd_wbmtree32_leaf_t), sizeof(fd_wbmtree32_leaf_t) * leaf_cnt);
3759 0 : uchar * mbuf = fd_alloca_check( 1UL, leaf_cnt * (sizeof(fd_ed25519_sig_t) + 1) );
3760 :
3761 0 : fd_wbmtree32_t * tree = fd_wbmtree32_init(commit, leaf_cnt);
3762 0 : fd_wbmtree32_leaf_t * l = &leafs[0];
3763 :
3764 : /* Loop across transactions */
3765 0 : for( ulong txn_idx=0UL; txn_idx<txn_cnt; txn_idx++ ) {
3766 0 : fd_txn_p_t * txn_p = µblock_info->txns[txn_idx];
3767 0 : fd_txn_t const * txn = (fd_txn_t const *) txn_p->_;
3768 0 : fd_rawtxn_b_t const raw_txn[1] = {{ .raw = txn_p->payload, .txn_sz = (ushort)txn_p->payload_sz } };
3769 :
3770 : /* Loop across signatures */
3771 0 : fd_ed25519_sig_t const * sigs = (fd_ed25519_sig_t const *)((ulong)raw_txn->raw + (ulong)txn->signature_off);
3772 0 : for( ulong j=0UL; j<txn->signature_cnt; j++ ) {
3773 0 : l->data = (uchar *)&sigs[j];
3774 0 : l->data_len = sizeof(fd_ed25519_sig_t);
3775 0 : l++;
3776 0 : }
3777 0 : }
3778 :
3779 0 : fd_wbmtree32_append( tree, leafs, leaf_cnt, mbuf );
3780 0 : uchar * root = fd_wbmtree32_fini( tree );
3781 0 : fd_poh_mixin( &out_poh_hash, root );
3782 0 : }
3783 :
3784 0 : if( FD_UNLIKELY( memcmp(microblock_info->microblock_hdr.hash, out_poh_hash.hash, sizeof(fd_hash_t)) ) ) {
3785 0 : FD_LOG_WARNING(( "poh mismatch (bank: %s, entry: %s)", FD_BASE58_ENC_32_ALLOCA( out_poh_hash.hash ), FD_BASE58_ENC_32_ALLOCA( microblock_info->microblock_hdr.hash ) ));
3786 0 : poh_info->success = -1;
3787 0 : }
3788 0 : }
3789 :
3790 : static int
3791 : fd_runtime_poh_verify_tpool( fd_poh_verification_info_t * poh_verification_info,
3792 : ulong poh_verification_info_cnt,
3793 0 : fd_tpool_t * tpool ) {
3794 0 : fd_tpool_exec_all_rrobin( tpool,
3795 0 : 0,
3796 0 : fd_tpool_worker_cnt( tpool ),
3797 0 : fd_runtime_poh_verify_wide_task,
3798 0 : poh_verification_info,
3799 0 : NULL,
3800 0 : NULL,
3801 0 : 1,
3802 0 : 0,
3803 0 : poh_verification_info_cnt );
3804 :
3805 0 : for( ulong i=0UL; i<poh_verification_info_cnt; i++ ) {
3806 0 : if( poh_verification_info[i].success ) {
3807 0 : return -1;
3808 0 : }
3809 0 : }
3810 :
3811 0 : return 0;
3812 0 : }
3813 :
3814 : static int
3815 : fd_runtime_block_verify_tpool( fd_block_info_t const * block_info,
3816 : fd_hash_t const * in_poh_hash,
3817 : fd_hash_t * out_poh_hash,
3818 : fd_valloc_t valloc,
3819 0 : fd_tpool_t * tpool ) {
3820 0 : long block_verify_time = -fd_log_wallclock();
3821 :
3822 0 : fd_hash_t tmp_in_poh_hash = *in_poh_hash;
3823 0 : ulong poh_verification_info_cnt = block_info->microblock_cnt;
3824 0 : fd_poh_verification_info_t * poh_verification_info = fd_valloc_malloc( valloc,
3825 0 : alignof(fd_poh_verification_info_t),
3826 0 : poh_verification_info_cnt * sizeof(fd_poh_verification_info_t));
3827 0 : fd_runtime_block_verify_info_collect( block_info, &tmp_in_poh_hash, poh_verification_info );
3828 0 : int result = fd_runtime_poh_verify_tpool( poh_verification_info, poh_verification_info_cnt, tpool );
3829 0 : fd_memcpy( out_poh_hash->hash, poh_verification_info[poh_verification_info_cnt - 1].microblock_info->microblock_hdr.hash, sizeof(fd_hash_t) );
3830 0 : fd_valloc_free( valloc, poh_verification_info );
3831 :
3832 0 : block_verify_time += fd_log_wallclock();
3833 0 : double block_verify_time_ms = (double)block_verify_time * 1e-6;
3834 :
3835 0 : FD_LOG_INFO(( "verified block successfully - elapsed: %6.6f ms", block_verify_time_ms ));
3836 :
3837 0 : return result;
3838 0 : }
3839 :
3840 : static int
3841 : fd_runtime_publish_old_txns( fd_exec_slot_ctx_t * slot_ctx,
3842 : fd_capture_ctx_t * capture_ctx,
3843 0 : fd_tpool_t * tpool ) {
3844 : /* Publish any transaction older than 31 slots */
3845 0 : fd_funk_t * funk = slot_ctx->acc_mgr->funk;
3846 0 : fd_funk_txn_t * txnmap = fd_funk_txn_map( funk, fd_funk_wksp( funk ) );
3847 0 : fd_epoch_bank_t * epoch_bank = fd_exec_epoch_ctx_epoch_bank( slot_ctx->epoch_ctx );
3848 :
3849 0 : uint depth = 0;
3850 0 : for( fd_funk_txn_t * txn = slot_ctx->funk_txn; txn; txn = fd_funk_txn_parent(txn, txnmap) ) {
3851 0 : if( ++depth == (FD_RUNTIME_NUM_ROOT_BLOCKS - 1 ) ) {
3852 0 : FD_LOG_DEBUG(("publishing %s (slot %lu)", FD_BASE58_ENC_32_ALLOCA( &txn->xid ), txn->xid.ul[0]));
3853 :
3854 0 : if( slot_ctx->status_cache && !fd_txncache_get_is_constipated( slot_ctx->status_cache ) ) {
3855 0 : fd_txncache_register_root_slot( slot_ctx->status_cache, txn->xid.ul[0] );
3856 0 : } else if( slot_ctx->status_cache ) {
3857 0 : fd_txncache_register_constipated_slot( slot_ctx->status_cache, txn->xid.ul[0] );
3858 0 : }
3859 :
3860 0 : fd_funk_start_write( funk );
3861 0 : if( slot_ctx->epoch_ctx->constipate_root ) {
3862 0 : fd_funk_txn_t * parent = fd_funk_txn_parent( txn, txnmap );
3863 0 : if( parent != NULL ) {
3864 0 : slot_ctx->root_slot = txn->xid.ul[0];
3865 :
3866 0 : if( FD_UNLIKELY( fd_funk_txn_publish_into_parent( funk, txn, 1) != FD_FUNK_SUCCESS ) ) {
3867 0 : FD_LOG_ERR(( "Unable to publish into the parent transaction" ));
3868 0 : }
3869 0 : }
3870 0 : } else {
3871 0 : slot_ctx->root_slot = txn->xid.ul[0];
3872 : /* TODO: The epoch boundary check is not correct due to skipped slots. */
3873 0 : if( (!(slot_ctx->root_slot % slot_ctx->snapshot_freq) || (
3874 0 : !(slot_ctx->root_slot % slot_ctx->incremental_freq) && slot_ctx->last_snapshot_slot)) &&
3875 0 : !fd_runtime_is_epoch_boundary( epoch_bank, slot_ctx->root_slot, slot_ctx->root_slot - 1UL )) {
3876 :
3877 0 : slot_ctx->last_snapshot_slot = slot_ctx->root_slot;
3878 0 : slot_ctx->epoch_ctx->constipate_root = 1;
3879 0 : fd_txncache_set_is_constipated( slot_ctx->status_cache, 1 );
3880 0 : }
3881 :
3882 0 : if( FD_UNLIKELY( !fd_funk_txn_publish( funk, txn, 1 ) ) ) {
3883 0 : FD_LOG_ERR(( "No transactions were published" ));
3884 0 : }
3885 0 : }
3886 :
3887 0 : if( txn->xid.ul[0] >= epoch_bank->eah_start_slot ) {
3888 0 : fd_accounts_hash( slot_ctx->acc_mgr->funk, &slot_ctx->slot_bank, slot_ctx->valloc, tpool, &slot_ctx->slot_bank.epoch_account_hash );
3889 0 : epoch_bank->eah_start_slot = ULONG_MAX;
3890 0 : }
3891 :
3892 0 : if( capture_ctx != NULL ) {
3893 0 : fd_runtime_checkpt( capture_ctx, slot_ctx, txn->xid.ul[0] );
3894 0 : }
3895 :
3896 0 : fd_funk_end_write( funk );
3897 :
3898 0 : break;
3899 0 : }
3900 0 : }
3901 :
3902 0 : return 0;
3903 0 : }
3904 :
3905 : static int
3906 : fd_runtime_block_execute_tpool( fd_exec_slot_ctx_t * slot_ctx,
3907 : fd_capture_ctx_t * capture_ctx,
3908 : fd_block_info_t const * block_info,
3909 : fd_tpool_t * tpool,
3910 : fd_spad_t * * spads,
3911 0 : ulong spad_cnt ) {
3912 0 : FD_SCRATCH_SCOPE_BEGIN {
3913 0 : if ( capture_ctx != NULL && capture_ctx->capture ) {
3914 0 : fd_solcap_writer_set_slot( capture_ctx->capture, slot_ctx->slot_bank.slot );
3915 0 : }
3916 :
3917 0 : slot_ctx->tick_count = 0UL;
3918 :
3919 0 : long block_execute_time = -fd_log_wallclock();
3920 :
3921 0 : int res = fd_runtime_block_execute_prepare( slot_ctx );
3922 0 : if( res != FD_RUNTIME_EXECUTE_SUCCESS ) {
3923 0 : return res;
3924 0 : }
3925 :
3926 0 : ulong txn_cnt = block_info->txn_cnt;
3927 0 : fd_txn_p_t * txn_ptrs = fd_scratch_alloc( alignof(fd_txn_p_t), txn_cnt * sizeof(fd_txn_p_t) );
3928 :
3929 : /* This now collects the tick entries in a block. */
3930 0 : fd_runtime_block_collect_txns( block_info, txn_ptrs );
3931 :
3932 : /* TODO: Currently the tick height is manually updated for each executed
3933 : slot as a hack to support snapshot loading. This code should be removed
3934 : once correct tick calculation is implemented. */
3935 0 : slot_ctx->slot_bank.tick_height += 64UL;
3936 0 : slot_ctx->slot_bank.max_tick_height += 64UL;
3937 :
3938 0 : res = fd_runtime_process_txns_in_waves_tpool( slot_ctx, capture_ctx, txn_ptrs, txn_cnt, tpool, spads, spad_cnt );
3939 0 : if( res != FD_RUNTIME_EXECUTE_SUCCESS ) {
3940 0 : return res;
3941 0 : }
3942 :
3943 0 : long block_finalize_time = -fd_log_wallclock();
3944 0 : res = fd_runtime_block_execute_finalize_tpool( slot_ctx, capture_ctx, block_info, tpool );
3945 0 : if( res != FD_RUNTIME_EXECUTE_SUCCESS ) {
3946 0 : return res;
3947 0 : }
3948 :
3949 0 : slot_ctx->slot_bank.transaction_count += txn_cnt;
3950 :
3951 0 : block_finalize_time += fd_log_wallclock();
3952 0 : double block_finalize_time_ms = (double)block_finalize_time * 1e-6;
3953 0 : FD_LOG_INFO(( "finalized block successfully - slot: %lu, elapsed: %6.6f ms", slot_ctx->slot_bank.slot, block_finalize_time_ms ));
3954 :
3955 0 : block_execute_time += fd_log_wallclock();
3956 0 : double block_execute_time_ms = (double)block_execute_time * 1e-6;
3957 :
3958 0 : FD_LOG_INFO(( "executed block successfully - slot: %lu, elapsed: %6.6f ms", slot_ctx->slot_bank.slot, block_execute_time_ms ));
3959 :
3960 0 : return FD_RUNTIME_EXECUTE_SUCCESS;
3961 0 : } FD_SCRATCH_SCOPE_END;
3962 0 : }
3963 :
3964 : int
3965 : fd_runtime_block_eval_tpool( fd_exec_slot_ctx_t * slot_ctx,
3966 : fd_capture_ctx_t * capture_ctx,
3967 : fd_tpool_t * tpool,
3968 : ulong scheduler,
3969 : ulong * txn_cnt,
3970 : fd_spad_t * * spads,
3971 0 : ulong spad_cnt ) {
3972 0 : (void)scheduler;
3973 :
3974 0 : FD_SCRATCH_SCOPE_BEGIN {
3975 :
3976 0 : int err = fd_runtime_publish_old_txns( slot_ctx, capture_ctx, tpool );
3977 0 : if( err != 0 ) {
3978 0 : return err;
3979 0 : }
3980 :
3981 0 : fd_funk_t * funk = slot_ctx->acc_mgr->funk;
3982 :
3983 0 : ulong slot = slot_ctx->slot_bank.slot;
3984 :
3985 0 : long block_eval_time = -fd_log_wallclock();
3986 0 : fd_block_info_t block_info;
3987 0 : int ret = fd_runtime_block_prepare( slot_ctx->blockstore, slot_ctx->block, slot, fd_scratch_virtual(), &block_info );
3988 0 : *txn_cnt = block_info.txn_cnt;
3989 :
3990 : /* Use the blockhash as the funk xid */
3991 0 : fd_funk_txn_xid_t xid;
3992 :
3993 0 : fd_blockstore_start_read( slot_ctx->blockstore );
3994 0 : fd_hash_t const * hash = fd_blockstore_block_hash_query( slot_ctx->blockstore, slot );
3995 0 : if( FD_UNLIKELY( !hash ) ) {
3996 0 : ret = FD_RUNTIME_EXECUTE_GENERIC_ERR;
3997 0 : FD_LOG_WARNING(( "missing blockhash for %lu", slot ));
3998 0 : } else {
3999 0 : fd_memcpy( xid.uc, hash->uc, sizeof(fd_funk_txn_xid_t) );
4000 0 : xid.ul[0] = slot_ctx->slot_bank.slot;
4001 : /* push a new transaction on the stack */
4002 0 : fd_funk_start_write( funk );
4003 0 : slot_ctx->funk_txn = fd_funk_txn_prepare( funk, slot_ctx->funk_txn, &xid, 1 );
4004 0 : fd_funk_end_write( funk );
4005 0 : }
4006 0 : fd_blockstore_end_read( slot_ctx->blockstore );
4007 :
4008 0 : if( FD_RUNTIME_EXECUTE_SUCCESS == ret ) {
4009 0 : ret = fd_runtime_block_verify_tpool( &block_info, &slot_ctx->slot_bank.poh, &slot_ctx->slot_bank.poh, fd_scratch_virtual(), tpool );
4010 0 : }
4011 0 : if( FD_RUNTIME_EXECUTE_SUCCESS == ret ) {
4012 0 : ret = fd_runtime_block_execute_tpool( slot_ctx, capture_ctx, &block_info, tpool, spads, spad_cnt );
4013 0 : }
4014 :
4015 : // FIXME: better way of using starting slot
4016 0 : if( FD_UNLIKELY( FD_RUNTIME_EXECUTE_SUCCESS != ret ) ) {
4017 0 : FD_LOG_WARNING(( "execution failure, code %d", ret ));
4018 : /* Skip over slot next time */
4019 0 : slot_ctx->slot_bank.slot = slot+1UL;
4020 0 : return 0;
4021 0 : }
4022 :
4023 0 : block_eval_time += fd_log_wallclock();
4024 0 : double block_eval_time_ms = (double)block_eval_time * 1e-6;
4025 0 : double tps = (double) block_info.txn_cnt / ((double)block_eval_time * 1e-9);
4026 0 : FD_LOG_INFO(( "evaluated block successfully - slot: %lu, elapsed: %6.6f ms, signatures: %lu, txns: %lu, tps: %6.6f, bank_hash: %s, leader: %s",
4027 0 : slot_ctx->slot_bank.slot,
4028 0 : block_eval_time_ms,
4029 0 : block_info.signature_cnt,
4030 0 : block_info.txn_cnt,
4031 0 : tps,
4032 0 : FD_BASE58_ENC_32_ALLOCA( slot_ctx->slot_bank.banks_hash.hash ),
4033 0 : FD_BASE58_ENC_32_ALLOCA( fd_epoch_leaders_get( fd_exec_epoch_ctx_leaders( slot_ctx->epoch_ctx ), slot_ctx->slot_bank.slot ) ) ));
4034 :
4035 0 : slot_ctx->slot_bank.transaction_count += block_info.txn_cnt;
4036 :
4037 0 : fd_funk_start_write( slot_ctx->acc_mgr->funk );
4038 0 : fd_runtime_save_slot_bank( slot_ctx );
4039 0 : fd_funk_end_write( slot_ctx->acc_mgr->funk );
4040 :
4041 0 : slot_ctx->slot_bank.prev_slot = slot;
4042 : // FIXME: this shouldn't be doing this, it doesn't work with forking. punting changing it though
4043 0 : slot_ctx->slot_bank.slot = slot+1UL;
4044 :
4045 0 : } FD_SCRATCH_SCOPE_END;
4046 :
4047 0 : return 0;
4048 0 : }
4049 :
4050 : /******************************************************************************/
4051 : /* Debugging Tools */
4052 : /******************************************************************************/
4053 :
4054 : static void
4055 : fd_runtime_copy_program_data_acc_to_pruned_funk( fd_funk_t * pruned_funk,
4056 : fd_funk_txn_t * prune_txn,
4057 : fd_exec_slot_ctx_t * slot_ctx,
4058 0 : fd_pubkey_t const * program_pubkey ) {
4059 0 : /* If account corresponds to bpf_upgradeable, copy over the programdata as well.
4060 0 : This is necessary for executing any bpf upgradeable program. */
4061 0 :
4062 0 : fd_account_meta_t const * program_acc = fd_acc_mgr_view_raw( slot_ctx->acc_mgr, NULL,
4063 0 : program_pubkey, NULL, NULL, NULL );
4064 0 :
4065 0 : if( memcmp( program_acc->info.owner, fd_solana_bpf_loader_upgradeable_program_id.key, sizeof(fd_pubkey_t) ) ) {
4066 0 : return;
4067 0 : }
4068 0 :
4069 0 : fd_bincode_decode_ctx_t ctx = {
4070 0 : .data = (uchar *)program_acc + program_acc->hlen,
4071 0 : .dataend = (char *) ctx.data + program_acc->dlen,
4072 0 : .valloc = slot_ctx->valloc,
4073 0 : };
4074 0 :
4075 0 : fd_bpf_upgradeable_loader_state_t loader_state;
4076 0 : if ( fd_bpf_upgradeable_loader_state_decode( &loader_state, &ctx ) ) {
4077 0 : FD_LOG_ERR(( "fd_bpf_upgradeable_loader_state_decode failed" ));
4078 0 : }
4079 0 :
4080 0 : if( !fd_bpf_upgradeable_loader_state_is_program( &loader_state ) ) {
4081 0 : FD_LOG_ERR(( "fd_bpf_upgradeable_loader_state_is_program failed" ));
4082 0 : }
4083 0 :
4084 0 : fd_pubkey_t * programdata_pubkey = (fd_pubkey_t *)&loader_state.inner.program.programdata_address;
4085 0 : fd_funk_rec_key_t programdata_reckey = fd_acc_funk_key( programdata_pubkey );
4086 0 :
4087 0 : /* Copy over programdata record */
4088 0 : fd_funk_rec_t * new_rec_pd = fd_funk_rec_write_prepare( pruned_funk, prune_txn, &programdata_reckey,
4089 0 : 0, 1, NULL, NULL );
4090 0 : FD_TEST(( !!new_rec_pd ));
4091 0 : }
4092 :
4093 : static void FD_FN_UNUSED
4094 : fd_runtime_copy_accounts_to_pruned_funk( fd_funk_t * pruned_funk,
4095 : fd_funk_txn_t * prune_txn,
4096 : fd_exec_slot_ctx_t * slot_ctx,
4097 0 : fd_exec_txn_ctx_t * txn_ctx ) {
4098 0 : /* This function is only responsible for copying over the account ids that are
4099 0 : modified. The account data is copied over after execution is complete. */
4100 0 :
4101 0 : /* Copy over ALUTs */
4102 0 : fd_txn_acct_addr_lut_t * addr_luts = fd_txn_get_address_tables( (fd_txn_t *) txn_ctx->txn_descriptor );
4103 0 : for( ulong i = 0; i < txn_ctx->txn_descriptor->addr_table_lookup_cnt; i++ ) {
4104 0 : fd_txn_acct_addr_lut_t * addr_lut = &addr_luts[i];
4105 0 : fd_pubkey_t const * addr_lut_acc = (fd_pubkey_t *)((uchar *)txn_ctx->_txn_raw->raw + addr_lut->addr_off);
4106 0 : if ( addr_lut_acc ) {
4107 0 : fd_funk_rec_key_t acc_lut_rec_key = fd_acc_funk_key( addr_lut_acc );
4108 0 : fd_funk_rec_write_prepare( pruned_funk, prune_txn, &acc_lut_rec_key, 0, 1, NULL, NULL );
4109 0 : }
4110 0 : }
4111 0 :
4112 0 : /* Get program id from top level instructions and copy over programdata */
4113 0 : fd_instr_info_t instrs[txn_ctx->txn_descriptor->instr_cnt];
4114 0 : for ( ushort i = 0; i < txn_ctx->txn_descriptor->instr_cnt; i++ ) {
4115 0 : fd_txn_instr_t const * txn_instr = &txn_ctx->txn_descriptor->instr[i];
4116 0 : fd_convert_txn_instr_to_instr( txn_ctx, txn_instr, txn_ctx->borrowed_accounts, &instrs[i] );
4117 0 : fd_pubkey_t program_pubkey = instrs[i].program_id_pubkey;
4118 0 : fd_funk_rec_key_t program_rec_key = fd_acc_funk_key( &program_pubkey );
4119 0 : fd_funk_rec_t *new_rec = fd_funk_rec_write_prepare(pruned_funk, prune_txn, &program_rec_key, 0, 1, NULL, NULL);
4120 0 : if ( !new_rec ) {
4121 0 : FD_LOG_NOTICE(("fd_funk_rec_write_prepare failed %s", FD_BASE58_ENC_32_ALLOCA( &program_pubkey ) ));
4122 0 : continue;
4123 0 : }
4124 0 :
4125 0 : /* If account corresponds to bpf_upgradeable, copy over the programdata as well */
4126 0 : fd_runtime_copy_program_data_acc_to_pruned_funk( pruned_funk, prune_txn, slot_ctx, &program_pubkey );
4127 0 : }
4128 0 :
4129 0 : /* Write out all accounts touched during the transaction, copy over all program data accounts for
4130 0 : any BPF upgradeable accounts in case they are a CPI's program account. */
4131 0 : for( ulong i = 0; i < txn_ctx->accounts_cnt; i++ ) {
4132 0 : fd_pubkey_t * acc_pubkey = (fd_pubkey_t *)&txn_ctx->accounts[i].key;
4133 0 : fd_funk_rec_key_t rec_key = fd_acc_funk_key( acc_pubkey );
4134 0 : fd_funk_rec_t * rec = fd_funk_rec_write_prepare( pruned_funk, prune_txn, &rec_key, 0, 1, NULL, NULL );
4135 0 : FD_TEST(( !!rec ));
4136 0 : fd_runtime_copy_program_data_acc_to_pruned_funk( pruned_funk, prune_txn, slot_ctx, acc_pubkey );
4137 0 : }
4138 0 : }
4139 :
4140 : void
4141 : fd_runtime_checkpt( fd_capture_ctx_t * capture_ctx,
4142 : fd_exec_slot_ctx_t * slot_ctx,
4143 0 : ulong slot ) {
4144 0 : int is_checkpt_freq = capture_ctx != NULL && slot % capture_ctx->checkpt_freq == 0;
4145 0 : int is_abort_slot = slot == ULONG_MAX;
4146 0 : if( !is_checkpt_freq && !is_abort_slot ) {
4147 0 : return;
4148 0 : }
4149 :
4150 0 : if( capture_ctx->checkpt_path != NULL ) {
4151 0 : if( !is_abort_slot ) {
4152 0 : FD_LOG_NOTICE(( "checkpointing at slot=%lu to file=%s", slot, capture_ctx->checkpt_path ));
4153 0 : fd_funk_end_write( slot_ctx->acc_mgr->funk );
4154 0 : } else {
4155 0 : FD_LOG_NOTICE(( "checkpointing after mismatch to file=%s", capture_ctx->checkpt_path ));
4156 0 : }
4157 :
4158 0 : unlink( capture_ctx->checkpt_path );
4159 0 : int err = fd_wksp_checkpt( fd_funk_wksp( slot_ctx->acc_mgr->funk ), capture_ctx->checkpt_path, 0666, 0, NULL );
4160 0 : if ( err ) {
4161 0 : FD_LOG_ERR(( "backup failed: error %d", err ));
4162 0 : }
4163 :
4164 0 : if( !is_abort_slot ) {
4165 0 : fd_funk_start_write( slot_ctx->acc_mgr->funk );
4166 0 : }
4167 0 : }
4168 :
4169 0 : }
4170 :
4171 : void
4172 0 : fd_runtime_collect_rent_accounts_prune( ulong slot, fd_exec_slot_ctx_t * slot_ctx, fd_capture_ctx_t * capture_ctx ) {
4173 : /* TODO: Test if this works across epoch boundaries */
4174 :
4175 : /* As a note, the number of partitions are determined before execution begins.
4176 : The rent accounts for each slot are added to the pruned funk. The data in
4177 : the accounts is populated after execution is completed. */
4178 0 : fd_epoch_bank_t const * epoch_bank = fd_exec_epoch_ctx_epoch_bank_const( slot_ctx->epoch_ctx );
4179 0 : fd_epoch_schedule_t const * schedule = &epoch_bank->epoch_schedule;
4180 :
4181 0 : ulong off;
4182 :
4183 0 : fd_slot_to_epoch( schedule, slot, &off );
4184 :
4185 0 : fd_funk_t * funk = slot_ctx->acc_mgr->funk;
4186 0 : fd_wksp_t * wksp = fd_funk_wksp( funk );
4187 0 : fd_funk_partvec_t * partvec = fd_funk_get_partvec( funk, wksp );
4188 0 : fd_funk_rec_t * rec_map = fd_funk_rec_map( funk, wksp );
4189 :
4190 0 : for (fd_funk_rec_t const *rec_ro = fd_funk_part_head(partvec, (uint)off, rec_map);
4191 0 : rec_ro != NULL;
4192 0 : rec_ro = fd_funk_part_next(rec_ro, rec_map)) {
4193 :
4194 0 : fd_pubkey_t const * key = fd_type_pun_const(rec_ro->pair.key[0].uc);
4195 0 : fd_funk_rec_key_t rec_key = fd_acc_funk_key( key );
4196 :
4197 0 : fd_funk_txn_xid_t prune_xid;
4198 0 : fd_memset( &prune_xid, 0x42, sizeof(fd_funk_txn_xid_t));
4199 :
4200 0 : fd_funk_txn_t * txn_map = fd_funk_txn_map( capture_ctx->pruned_funk, fd_funk_wksp( capture_ctx->pruned_funk ) );
4201 0 : fd_funk_txn_t * prune_txn = fd_funk_txn_query( &prune_xid, txn_map );
4202 :
4203 0 : fd_funk_rec_t * rec = fd_funk_rec_write_prepare( capture_ctx->pruned_funk, prune_txn, &rec_key, 0, 1, NULL, NULL );
4204 0 : FD_TEST(( !!rec ));
4205 :
4206 0 : int res = fd_funk_part_set( capture_ctx->pruned_funk, rec, (uint)off );
4207 0 : FD_TEST(( res == 0 ));
4208 0 : }
4209 0 : }
4210 :
4211 : // TODO: add tracking account_state hashes so that we can verify our
4212 : // banks hash... this has interesting threading implications since we
4213 : // could execute the cryptography in another thread for tracking this
4214 : // but we don't actually have anything to compare it to until we hit
4215 : // another snapshot... Probably we should just store the results into
4216 : // the slot_ctx state (a slot/hash map)?
4217 : //
4218 : // What slots exactly do cache'd account_updates go into? how are
4219 : // they hashed (which slot?)?
|