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