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