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 "sysvar/fd_sysvar_cache.h"
13 : #include "sysvar/fd_sysvar_clock.h"
14 : #include "sysvar/fd_sysvar_epoch_schedule.h"
15 : #include "sysvar/fd_sysvar_recent_hashes.h"
16 : #include "sysvar/fd_sysvar_stake_history.h"
17 :
18 : #include "../stakes/fd_stakes.h"
19 : #include "../rewards/fd_rewards.h"
20 :
21 : #include "context/fd_exec_txn_ctx.h"
22 : #include "info/fd_microblock_batch_info.h"
23 : #include "info/fd_microblock_info.h"
24 :
25 : #include "program/fd_stake_program.h"
26 : #include "program/fd_builtin_programs.h"
27 : #include "program/fd_vote_program.h"
28 : #include "program/fd_program_cache.h"
29 : #include "program/fd_bpf_loader_program.h"
30 : #include "program/fd_address_lookup_table_program.h"
31 :
32 : #include "sysvar/fd_sysvar_clock.h"
33 : #include "sysvar/fd_sysvar_last_restart_slot.h"
34 : #include "sysvar/fd_sysvar_recent_hashes.h"
35 : #include "sysvar/fd_sysvar_rent.h"
36 : #include "sysvar/fd_sysvar_slot_hashes.h"
37 : #include "sysvar/fd_sysvar_slot_history.h"
38 :
39 : #include "tests/fd_dump_pb.h"
40 :
41 : #include "fd_system_ids.h"
42 : #include "../vm/fd_vm.h"
43 : #include "../../disco/pack/fd_pack.h"
44 :
45 : #include <unistd.h>
46 : #include <sys/stat.h>
47 : #include <sys/types.h>
48 : #include <errno.h>
49 : #include <fcntl.h>
50 :
51 : /******************************************************************************/
52 : /* Public Runtime Helpers */
53 : /******************************************************************************/
54 :
55 : int
56 0 : fd_runtime_should_use_vote_keyed_leader_schedule( fd_bank_t * bank ) {
57 : /* Agave uses an option type for their `effective_epoch` value. We represent None
58 : as ULONG_MAX and Some(value) as the value.
59 : https://github.com/anza-xyz/agave/blob/v2.3.1/runtime/src/bank.rs#L6149-L6165 */
60 0 : if( FD_FEATURE_ACTIVE_BANK( bank, enable_vote_address_leader_schedule ) ) {
61 : /* Return the first epoch if activated at genesis
62 : https://github.com/anza-xyz/agave/blob/v2.3.1/runtime/src/bank.rs#L6153-L6157 */
63 0 : ulong activation_slot = fd_bank_features_query( bank )->enable_vote_address_leader_schedule;
64 0 : if( activation_slot==0UL ) return 0;
65 :
66 : /* Calculate the epoch that the feature became activated in
67 : https://github.com/anza-xyz/agave/blob/v2.3.1/runtime/src/bank.rs#L6159-L6160 */
68 0 : fd_epoch_schedule_t const * epoch_schedule = fd_bank_epoch_schedule_query( bank );
69 0 : ulong activation_epoch = fd_slot_to_epoch( epoch_schedule, activation_slot, NULL );
70 :
71 : /* The effective epoch is the epoch immediately after the activation epoch
72 : https://github.com/anza-xyz/agave/blob/v2.3.1/runtime/src/bank.rs#L6162-L6164 */
73 0 : ulong effective_epoch = activation_epoch + 1UL;
74 0 : ulong current_epoch = fd_bank_epoch_get( bank );
75 :
76 : /* https://github.com/anza-xyz/agave/blob/v2.3.1/runtime/src/bank.rs#L6167-L6170 */
77 0 : return !!( current_epoch >= effective_epoch );
78 0 : }
79 :
80 : /* ...The rest of the logic in this function either returns `None` or `Some(false)` so we
81 : will just return 0 by default. */
82 0 : return 0;
83 0 : }
84 :
85 : /*
86 : https://github.com/anza-xyz/agave/blob/v2.1.1/runtime/src/bank.rs#L1254-L1258
87 : https://github.com/anza-xyz/agave/blob/v2.1.1/runtime/src/bank.rs#L1749
88 : */
89 : int
90 : fd_runtime_compute_max_tick_height( ulong ticks_per_slot,
91 : ulong slot,
92 0 : ulong * out_max_tick_height /* out */ ) {
93 0 : ulong max_tick_height = 0UL;
94 0 : if( FD_LIKELY( ticks_per_slot > 0UL ) ) {
95 0 : ulong next_slot = fd_ulong_sat_add( slot, 1UL );
96 0 : if( FD_UNLIKELY( next_slot == slot ) ) {
97 0 : FD_LOG_WARNING(( "max tick height addition overflowed slot %lu ticks_per_slot %lu", slot, ticks_per_slot ));
98 0 : return -1;
99 0 : }
100 0 : if( FD_UNLIKELY( ULONG_MAX / ticks_per_slot < next_slot ) ) {
101 0 : FD_LOG_WARNING(( "max tick height multiplication overflowed slot %lu ticks_per_slot %lu", slot, ticks_per_slot ));
102 0 : return -1;
103 0 : }
104 0 : max_tick_height = fd_ulong_sat_mul( next_slot, ticks_per_slot );
105 0 : }
106 0 : *out_max_tick_height = max_tick_height;
107 0 : return FD_RUNTIME_EXECUTE_SUCCESS;
108 0 : }
109 :
110 : void
111 : fd_runtime_update_slots_per_epoch( fd_bank_t * bank,
112 0 : ulong slots_per_epoch ) {
113 0 : if( FD_LIKELY( slots_per_epoch == fd_bank_slots_per_epoch_get( bank ) ) ) {
114 0 : return;
115 0 : }
116 :
117 0 : fd_bank_slots_per_epoch_set( bank, slots_per_epoch );
118 :
119 0 : fd_bank_part_width_set( bank, fd_rent_partition_width( slots_per_epoch ) );
120 0 : }
121 :
122 : void
123 : fd_runtime_update_leaders( fd_bank_t * bank,
124 : ulong slot,
125 0 : fd_spad_t * runtime_spad ) {
126 :
127 0 : FD_SPAD_FRAME_BEGIN( runtime_spad ) {
128 :
129 0 : fd_epoch_schedule_t const * epoch_schedule = fd_bank_epoch_schedule_query( bank );
130 :
131 0 : fd_vote_accounts_global_t const * epoch_vaccs = fd_bank_epoch_stakes_locking_query( bank );
132 0 : fd_vote_accounts_pair_global_t_mapnode_t * vote_acc_pool = fd_vote_accounts_vote_accounts_pool_join( epoch_vaccs );
133 0 : fd_vote_accounts_pair_global_t_mapnode_t * vote_acc_root = fd_vote_accounts_vote_accounts_root_join( epoch_vaccs );
134 :
135 0 : ulong epoch = fd_slot_to_epoch ( epoch_schedule, slot, NULL );
136 0 : ulong slot0 = fd_epoch_slot0 ( epoch_schedule, epoch );
137 0 : ulong slot_cnt = fd_epoch_slot_cnt( epoch_schedule, epoch );
138 :
139 0 : fd_runtime_update_slots_per_epoch( bank, fd_epoch_slot_cnt( epoch_schedule, epoch ) );
140 :
141 0 : ulong vote_acc_cnt = fd_vote_accounts_pair_global_t_map_size( vote_acc_pool, vote_acc_root );
142 0 : fd_bank_epoch_stakes_end_locking_query( bank );
143 :
144 0 : fd_vote_stake_weight_t * epoch_weights = fd_spad_alloc_check( runtime_spad, alignof(fd_vote_stake_weight_t), vote_acc_cnt * sizeof(fd_vote_stake_weight_t) );
145 0 : ulong stake_weight_cnt = fd_stake_weights_by_node( epoch_vaccs, epoch_weights );
146 :
147 0 : if( FD_UNLIKELY( stake_weight_cnt == ULONG_MAX ) ) {
148 0 : FD_LOG_ERR(( "fd_stake_weights_by_node() failed" ));
149 0 : }
150 :
151 : /* Derive leader schedule */
152 :
153 0 : FD_LOG_INFO(( "stake_weight_cnt=%lu slot_cnt=%lu", stake_weight_cnt, slot_cnt ));
154 0 : ulong epoch_leaders_footprint = fd_epoch_leaders_footprint( stake_weight_cnt, slot_cnt );
155 0 : FD_LOG_INFO(( "epoch_leaders_footprint=%lu", epoch_leaders_footprint ));
156 0 : if( FD_LIKELY( epoch_leaders_footprint ) ) {
157 0 : if( FD_UNLIKELY( stake_weight_cnt>MAX_PUB_CNT ) ) {
158 0 : FD_LOG_ERR(( "Stake weight count exceeded max" ));
159 0 : }
160 0 : if( FD_UNLIKELY( slot_cnt>MAX_SLOTS_PER_EPOCH ) ) {
161 0 : FD_LOG_ERR(( "Slot count exceeeded max" ));
162 0 : }
163 :
164 0 : ulong vote_keyed_lsched = (ulong)fd_runtime_should_use_vote_keyed_leader_schedule( bank );
165 0 : void * epoch_leaders_mem = fd_bank_epoch_leaders_locking_modify( bank );
166 0 : fd_epoch_leaders_t * leaders = fd_epoch_leaders_join( fd_epoch_leaders_new( epoch_leaders_mem,
167 0 : epoch,
168 0 : slot0,
169 0 : slot_cnt,
170 0 : stake_weight_cnt,
171 0 : epoch_weights,
172 0 : 0UL,
173 0 : vote_keyed_lsched ) );
174 0 : fd_bank_epoch_leaders_end_locking_modify( bank );
175 0 : if( FD_UNLIKELY( !leaders ) ) {
176 0 : FD_LOG_ERR(( "Unable to init and join fd_epoch_leaders" ));
177 0 : }
178 0 : }
179 :
180 0 : } FD_SPAD_FRAME_END;
181 0 : }
182 :
183 : /******************************************************************************/
184 : /* Various Private Runtime Helpers */
185 : /******************************************************************************/
186 :
187 : /* fee to be deposited should be > 0
188 : Returns 0 if validation succeeds
189 : Returns the amount to burn(==fee) on failure */
190 : static ulong
191 : fd_runtime_validate_fee_collector( fd_bank_t * bank,
192 : fd_txn_account_t const * collector,
193 0 : ulong fee ) {
194 0 : if( FD_UNLIKELY( fee<=0UL ) ) {
195 0 : FD_LOG_ERR(( "expected fee(%lu) to be >0UL", fee ));
196 0 : }
197 :
198 0 : if( FD_UNLIKELY( memcmp( collector->vt->get_owner( collector ), fd_solana_system_program_id.key, sizeof(fd_pubkey_t) ) ) ) {
199 0 : FD_BASE58_ENCODE_32_BYTES( collector->pubkey->key, _out_key );
200 0 : FD_LOG_WARNING(( "cannot pay a non-system-program owned account (%s)", _out_key ));
201 0 : return fee;
202 0 : }
203 :
204 : /* https://github.com/anza-xyz/agave/blob/v1.18.23/runtime/src/bank/fee_distribution.rs#L111
205 : https://github.com/anza-xyz/agave/blob/v1.18.23/runtime/src/accounts/account_rent_state.rs#L39
206 : In agave's fee deposit code, rent state transition check logic is as follows:
207 : The transition is NOT allowed iff
208 : === BEGIN
209 : the post deposit account is rent paying AND the pre deposit account is not rent paying
210 : OR
211 : 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)
212 : === END
213 : post_data_size == pre_data_size is always true during fee deposit.
214 : However, post_lamports > pre_lamports because we are paying a >0 amount.
215 : So, the above reduces down to
216 : === BEGIN
217 : the post deposit account is rent paying AND the pre deposit account is not rent paying
218 : OR
219 : the post deposit account is rent paying AND the pre deposit account is rent paying AND TRUE
220 : === END
221 : This is equivalent to checking that the post deposit account is rent paying.
222 : An account is rent paying if the post deposit balance is >0 AND it's not rent exempt.
223 : We already know that the post deposit balance is >0 because we are paying a >0 amount.
224 : So TLDR we just check if the account is rent exempt.
225 : */
226 0 : fd_rent_t const * rent = fd_bank_rent_query( bank );
227 0 : ulong minbal = fd_rent_exempt_minimum_balance( rent, collector->vt->get_data_len( collector ) );
228 0 : if( FD_UNLIKELY( collector->vt->get_lamports( collector ) + fee < minbal ) ) {
229 0 : FD_BASE58_ENCODE_32_BYTES( collector->pubkey->key, _out_key );
230 0 : FD_LOG_WARNING(("cannot pay a rent paying account (%s)", _out_key ));
231 0 : return fee;
232 0 : }
233 :
234 0 : return 0UL;
235 0 : }
236 :
237 : static int
238 : fd_runtime_run_incinerator( fd_bank_t * bank,
239 : fd_funk_t * funk,
240 0 : fd_funk_txn_t * funk_txn ) {
241 0 : FD_TXN_ACCOUNT_DECL( rec );
242 :
243 0 : int err = fd_txn_account_init_from_funk_mutable( rec,
244 0 : &fd_sysvar_incinerator_id,
245 0 : funk,
246 0 : funk_txn,
247 0 : 0,
248 0 : 0UL );
249 0 : if( FD_UNLIKELY( err!=FD_ACC_MGR_SUCCESS ) ) {
250 : // TODO: not really an error! This is fine!
251 0 : return -1;
252 0 : }
253 :
254 0 : ulong new_capitalization = fd_ulong_sat_sub( fd_bank_capitalization_get( bank ), rec->vt->get_lamports( rec ) );
255 0 : fd_bank_capitalization_set( bank, new_capitalization );
256 :
257 0 : rec->vt->set_lamports( rec, 0UL );
258 0 : fd_txn_account_mutable_fini( rec, funk, funk_txn );
259 :
260 0 : return 0;
261 0 : }
262 :
263 : static void
264 0 : fd_runtime_freeze( fd_exec_slot_ctx_t * slot_ctx ) {
265 :
266 0 : fd_sysvar_recent_hashes_update( slot_ctx );
267 :
268 0 : ulong execution_fees = fd_bank_execution_fees_get( slot_ctx->bank );
269 0 : ulong priority_fees = fd_bank_priority_fees_get( slot_ctx->bank );
270 :
271 0 : ulong burn = execution_fees / 2;
272 0 : ulong fees = fd_ulong_sat_add( priority_fees, execution_fees - burn );
273 :
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 : fd_bank_epoch_leaders_end_locking_query( slot_ctx->bank );
286 0 : break;
287 0 : }
288 :
289 0 : fd_pubkey_t const * leader = fd_epoch_leaders_get( leaders, fd_bank_slot_get( slot_ctx->bank ) );
290 0 : if( FD_UNLIKELY( !leader ) ) {
291 0 : FD_LOG_WARNING(( "fd_runtime_freeze: leader not found" ));
292 0 : fd_bank_epoch_leaders_end_locking_query( slot_ctx->bank );
293 0 : break;
294 0 : }
295 :
296 0 : int err = fd_txn_account_init_from_funk_mutable( rec, leader, slot_ctx->funk, slot_ctx->funk_txn, 1, 0UL );
297 0 : if( FD_UNLIKELY( err ) ) {
298 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));
299 0 : burn = fd_ulong_sat_add( burn, fees );
300 0 : fd_bank_epoch_leaders_end_locking_query( slot_ctx->bank );
301 0 : break;
302 0 : }
303 :
304 0 : fd_bank_epoch_leaders_end_locking_query( slot_ctx->bank );
305 :
306 0 : if ( FD_LIKELY( FD_FEATURE_ACTIVE_BANK( slot_ctx->bank, validate_fee_collector_account ) ) ) {
307 0 : ulong _burn;
308 0 : if( FD_UNLIKELY( _burn=fd_runtime_validate_fee_collector( slot_ctx->bank, rec, fees ) ) ) {
309 0 : if( FD_UNLIKELY( _burn!=fees ) ) {
310 0 : FD_LOG_ERR(( "expected _burn(%lu)==fees(%lu)", _burn, fees ));
311 0 : }
312 0 : burn = fd_ulong_sat_add( burn, fees );
313 0 : FD_LOG_WARNING(("fd_runtime_freeze: burned %lu", fees ));
314 0 : break;
315 0 : }
316 0 : }
317 :
318 : /* TODO: is it ok to not check the overflow error here? */
319 0 : rec->vt->checked_add_lamports( rec, fees );
320 0 : rec->vt->set_slot( rec, fd_bank_slot_get( slot_ctx->bank ) );
321 :
322 0 : fd_txn_account_mutable_fini( rec, slot_ctx->funk, slot_ctx->funk_txn );
323 :
324 0 : } while(0);
325 :
326 0 : ulong old = fd_bank_capitalization_get( slot_ctx->bank );
327 0 : fd_bank_capitalization_set( slot_ctx->bank, fd_ulong_sat_sub( old, burn ) );
328 0 : FD_LOG_DEBUG(( "fd_runtime_freeze: burn %lu, capitalization %lu->%lu ", burn, old, fd_bank_capitalization_get( slot_ctx->bank ) ));
329 :
330 0 : fd_bank_execution_fees_set( slot_ctx->bank, 0UL );
331 :
332 0 : fd_bank_priority_fees_set( slot_ctx->bank, 0UL );
333 0 : }
334 :
335 0 : fd_runtime_run_incinerator( slot_ctx->bank, slot_ctx->funk, slot_ctx->funk_txn );
336 :
337 0 : }
338 :
339 0 : #define FD_RENT_EXEMPT (-1L)
340 :
341 : static long
342 : fd_runtime_get_rent_due( fd_epoch_schedule_t const * schedule,
343 : fd_rent_t const * rent,
344 : double slots_per_year,
345 : fd_txn_account_t * acc,
346 0 : ulong epoch ) {
347 : /* Nothing due if account is rent-exempt
348 : https://github.com/anza-xyz/agave/blob/v2.0.10/sdk/src/rent_collector.rs#L90 */
349 0 : ulong min_balance = fd_rent_exempt_minimum_balance( rent, acc->vt->get_data_len( acc ) );
350 0 : if( acc->vt->get_lamports( acc )>=min_balance ) {
351 0 : return FD_RENT_EXEMPT;
352 0 : }
353 :
354 : /* Count the number of slots that have passed since last collection. This
355 : inlines the agave function get_slots_in_peohc
356 : https://github.com/anza-xyz/agave/blob/v2.0.10/sdk/src/rent_collector.rs#L93-L98 */
357 0 : ulong slots_elapsed = 0UL;
358 0 : if( FD_UNLIKELY( acc->vt->get_rent_epoch( acc )<schedule->first_normal_epoch ) ) {
359 : /* Count the slots before the first normal epoch separately */
360 0 : for( ulong i=acc->vt->get_rent_epoch( acc ); i<schedule->first_normal_epoch && i<=epoch; i++ ) {
361 0 : slots_elapsed += fd_epoch_slot_cnt( schedule, i+1UL );
362 0 : }
363 0 : slots_elapsed += fd_ulong_sat_sub( epoch+1UL, schedule->first_normal_epoch ) * schedule->slots_per_epoch;
364 0 : }
365 : // slots_elapsed should remain 0 if rent_epoch is greater than epoch
366 0 : else if( acc->vt->get_rent_epoch( acc )<=epoch ) {
367 0 : slots_elapsed = (epoch - acc->vt->get_rent_epoch( acc ) + 1UL) * schedule->slots_per_epoch;
368 0 : }
369 : /* Consensus-critical use of doubles :( */
370 :
371 0 : double years_elapsed;
372 0 : if( FD_LIKELY( slots_per_year!=0.0 ) ) {
373 0 : years_elapsed = (double)slots_elapsed / slots_per_year;
374 0 : } else {
375 0 : years_elapsed = 0.0;
376 0 : }
377 :
378 0 : ulong lamports_per_year = rent->lamports_per_uint8_year * (acc->vt->get_data_len( acc ) + 128UL);
379 : /* https://github.com/anza-xyz/agave/blob/d2124a995f89e33c54f41da76bfd5b0bd5820898/sdk/src/rent_collector.rs#L108 */
380 : /* https://github.com/anza-xyz/agave/blob/d2124a995f89e33c54f41da76bfd5b0bd5820898/sdk/program/src/rent.rs#L95 */
381 0 : return (long)fd_rust_cast_double_to_ulong(years_elapsed * (double)lamports_per_year);
382 0 : }
383 :
384 : /* fd_runtime_collect_rent_from_account performs rent collection duties.
385 : Although the Solana runtime prevents the creation of new accounts
386 : that are subject to rent, some older accounts are still undergo the
387 : rent collection process. Updates the account's 'rent_epoch' if
388 : needed. Returns the amount of rent collected. */
389 : /* https://github.com/anza-xyz/agave/blob/v2.0.10/svm/src/account_loader.rs#L71-96 */
390 : ulong
391 : fd_runtime_collect_rent_from_account( fd_epoch_schedule_t const * schedule,
392 : fd_rent_t const * rent,
393 : double slots_per_year,
394 : fd_txn_account_t * acc,
395 0 : ulong epoch ) {
396 :
397 0 : if( FD_UNLIKELY( acc->vt->get_rent_epoch( acc )!=FD_RENT_EXEMPT_RENT_EPOCH &&
398 0 : fd_runtime_get_rent_due( schedule,
399 0 : rent,
400 0 : slots_per_year,
401 0 : acc,
402 0 : epoch )==FD_RENT_EXEMPT ) ) {
403 0 : acc->vt->set_rent_epoch( acc, FD_RENT_EXEMPT_RENT_EPOCH );
404 0 : }
405 0 : return 0UL;
406 0 : }
407 :
408 : #undef FD_RENT_EXEMPT
409 :
410 : /******************************************************************************/
411 : /* Block-Level Execution Preparation/Finalization */
412 : /******************************************************************************/
413 :
414 : /*
415 : https://github.com/firedancer-io/solana/blob/dab3da8e7b667d7527565bddbdbecf7ec1fb868e/sdk/program/src/fee_calculator.rs#L105-L165
416 : */
417 : static void
418 : fd_runtime_new_fee_rate_governor_derived( fd_bank_t * bank,
419 0 : ulong latest_signatures_per_slot ) {
420 :
421 0 : fd_fee_rate_governor_t const * base_fee_rate_governor = fd_bank_fee_rate_governor_query( bank );
422 :
423 0 : ulong old_lamports_per_signature = fd_bank_lamports_per_signature_get( bank );
424 :
425 0 : fd_fee_rate_governor_t me = {
426 0 : .target_signatures_per_slot = base_fee_rate_governor->target_signatures_per_slot,
427 0 : .target_lamports_per_signature = base_fee_rate_governor->target_lamports_per_signature,
428 0 : .max_lamports_per_signature = base_fee_rate_governor->max_lamports_per_signature,
429 0 : .min_lamports_per_signature = base_fee_rate_governor->min_lamports_per_signature,
430 0 : .burn_percent = base_fee_rate_governor->burn_percent
431 0 : };
432 :
433 0 : ulong new_lamports_per_signature = 0;
434 0 : if( me.target_signatures_per_slot > 0 ) {
435 0 : me.min_lamports_per_signature = fd_ulong_max( 1UL, (ulong)(me.target_lamports_per_signature / 2) );
436 0 : me.max_lamports_per_signature = me.target_lamports_per_signature * 10;
437 0 : ulong desired_lamports_per_signature = fd_ulong_min(
438 0 : me.max_lamports_per_signature,
439 0 : fd_ulong_max(
440 0 : me.min_lamports_per_signature,
441 0 : me.target_lamports_per_signature
442 0 : * fd_ulong_min(latest_signatures_per_slot, (ulong)UINT_MAX)
443 0 : / me.target_signatures_per_slot
444 0 : )
445 0 : );
446 0 : long gap = (long)desired_lamports_per_signature - (long)old_lamports_per_signature;
447 0 : if ( gap == 0 ) {
448 0 : new_lamports_per_signature = desired_lamports_per_signature;
449 0 : } else {
450 0 : long gap_adjust = (long)(fd_ulong_max( 1UL, (ulong)(me.target_lamports_per_signature / 20) ))
451 0 : * (gap != 0)
452 0 : * (gap > 0 ? 1 : -1);
453 0 : new_lamports_per_signature = fd_ulong_min(
454 0 : me.max_lamports_per_signature,
455 0 : fd_ulong_max(
456 0 : me.min_lamports_per_signature,
457 0 : (ulong)((long)old_lamports_per_signature + gap_adjust)
458 0 : )
459 0 : );
460 0 : }
461 0 : } else {
462 0 : new_lamports_per_signature = base_fee_rate_governor->target_lamports_per_signature;
463 0 : me.min_lamports_per_signature = me.target_lamports_per_signature;
464 0 : me.max_lamports_per_signature = me.target_lamports_per_signature;
465 0 : }
466 :
467 0 : if( FD_UNLIKELY( old_lamports_per_signature==0UL ) ) {
468 0 : fd_bank_prev_lamports_per_signature_set( bank, new_lamports_per_signature );
469 0 : } else {
470 0 : fd_bank_prev_lamports_per_signature_set( bank, old_lamports_per_signature );
471 0 : }
472 :
473 0 : fd_bank_fee_rate_governor_set( bank, me );
474 :
475 0 : fd_bank_lamports_per_signature_set( bank, new_lamports_per_signature );
476 0 : }
477 :
478 : static int
479 : fd_runtime_block_sysvar_update_pre_execute( fd_exec_slot_ctx_t * slot_ctx,
480 0 : fd_spad_t * runtime_spad ) {
481 : // let (fee_rate_governor, fee_components_time_us) = measure_us!(
482 : // FeeRateGovernor::new_derived(&parent.fee_rate_governor, parent.signature_count())
483 : // );
484 : /* https://github.com/firedancer-io/solana/blob/dab3da8e7b667d7527565bddbdbecf7ec1fb868e/runtime/src/bank.rs#L1312-L1314 */
485 :
486 0 : FD_SPAD_FRAME_BEGIN( runtime_spad ) {
487 :
488 0 : fd_runtime_new_fee_rate_governor_derived( slot_ctx->bank, fd_bank_parent_signature_cnt_get( slot_ctx->bank ) );
489 :
490 : // TODO: move all these out to a fd_sysvar_update() call...
491 0 : long clock_update_time = -fd_log_wallclock();
492 0 : fd_sysvar_clock_update( slot_ctx, runtime_spad );
493 0 : clock_update_time += fd_log_wallclock();
494 0 : double clock_update_time_ms = (double)clock_update_time * 1e-6;
495 0 : FD_LOG_INFO(( "clock updated - slot: %lu, elapsed: %6.6f ms", fd_bank_slot_get( slot_ctx->bank ), clock_update_time_ms ));
496 :
497 : // It has to go into the current txn previous info but is not in slot 0
498 0 : if( fd_bank_slot_get( slot_ctx->bank ) != 0 ) {
499 0 : fd_sysvar_slot_hashes_update( slot_ctx, runtime_spad );
500 0 : }
501 0 : fd_sysvar_last_restart_slot_update( slot_ctx, fd_bank_last_restart_slot_get( slot_ctx->bank ).slot );
502 :
503 0 : } FD_SPAD_FRAME_END;
504 :
505 0 : return 0;
506 0 : }
507 :
508 : // int
509 : // fd_runtime_microblock_verify_ticks( fd_blockstore_t * blockstore,
510 : // ulong slot,
511 : // fd_microblock_hdr_t const * hdr,
512 : // bool slot_complete,
513 : // ulong tick_height,
514 : // ulong max_tick_height,
515 : // ulong hashes_per_tick ) {
516 : // ulong invalid_tick_hash_count = 0UL;
517 : // ulong has_trailing_entry = 0UL;
518 :
519 : // /*
520 : // In order to mimic the order of checks in Agave,
521 : // we cache the results of some checks but do not immediately return
522 : // an error.
523 : // */
524 : // fd_block_map_query_t quer[1];
525 : // int err = fd_block_map_prepare( blockstore->block_map, &slot, NULL, quer, FD_MAP_FLAG_BLOCKING );
526 : // fd_block_info_t * query = fd_block_map_query_ele( quer );
527 : // if( FD_UNLIKELY( err || query->slot != slot ) ) {
528 : // FD_LOG_ERR(( "fd_runtime_microblock_verify_ticks: fd_block_map_prepare on %lu failed", slot ));
529 : // }
530 :
531 : // query->tick_hash_count_accum = fd_ulong_sat_add( query->tick_hash_count_accum, hdr->hash_cnt );
532 : // if( hdr->txn_cnt == 0UL ) {
533 : // query->ticks_consumed++;
534 : // if( FD_LIKELY( hashes_per_tick > 1UL ) ) {
535 : // if( FD_UNLIKELY( query->tick_hash_count_accum != hashes_per_tick ) ) {
536 : // 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 ));
537 : // invalid_tick_hash_count = 1U;
538 : // }
539 : // }
540 : // query->tick_hash_count_accum = 0UL;
541 : // } else {
542 : // /* This wasn't a tick entry, but it's the last entry. */
543 : // if( FD_UNLIKELY( slot_complete ) ) {
544 : // FD_LOG_WARNING(( "last has %lu transactions expects 0", hdr->txn_cnt ));
545 : // has_trailing_entry = 1U;
546 : // }
547 : // }
548 :
549 : // ulong next_tick_height = tick_height + query->ticks_consumed;
550 : // fd_block_map_publish( quer );
551 :
552 : // if( FD_UNLIKELY( next_tick_height > max_tick_height ) ) {
553 : // 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 ));
554 : // return FD_BLOCK_ERR_TOO_MANY_TICKS;
555 : // }
556 : // if( FD_UNLIKELY( slot_complete && next_tick_height < max_tick_height ) ) {
557 : // FD_LOG_WARNING(( "Too few ticks" ));
558 : // return FD_BLOCK_ERR_TOO_FEW_TICKS;
559 : // }
560 : // if( FD_UNLIKELY( slot_complete && has_trailing_entry ) ) {
561 : // FD_LOG_WARNING(( "Did not end with a tick" ));
562 : // return FD_BLOCK_ERR_TRAILING_ENTRY;
563 : // }
564 :
565 : // /* Not returning FD_BLOCK_ERR_INVALID_LAST_TICK because we assume the
566 : // slot is full. */
567 :
568 : // /* Don't care about low power hashing or no hashing. */
569 : // if( FD_LIKELY( hashes_per_tick > 1UL ) ) {
570 : // if( FD_UNLIKELY( invalid_tick_hash_count ) ) {
571 : // FD_LOG_WARNING(( "Tick with invalid number of hashes found" ));
572 : // return FD_BLOCK_ERR_INVALID_TICK_HASH_COUNT;
573 : // }
574 : // }
575 : // return FD_BLOCK_OK;
576 : // }
577 :
578 : // /* A streaming version of this by batch is implemented in batch_verify_ticks.
579 : // This block_verify_ticks should only used for offline replay. */
580 : // ulong
581 : // fd_runtime_block_verify_ticks( fd_blockstore_t * blockstore,
582 : // ulong slot,
583 : // uchar * block_data,
584 : // ulong block_data_sz,
585 : // ulong tick_height,
586 : // ulong max_tick_height,
587 : // ulong hashes_per_tick ) {
588 : // ulong tick_count = 0UL;
589 : // ulong tick_hash_count = 0UL;
590 : // ulong has_trailing_entry = 0UL;
591 : // uchar invalid_tick_hash_count = 0U;
592 : // /*
593 : // Iterate over microblocks/entries to
594 : // (1) count the number of ticks
595 : // (2) check whether the last entry is a tick
596 : // (3) check whether ticks align with hashes per tick
597 :
598 : // This precomputes everything we need in a single loop over the array.
599 : // In order to mimic the order of checks in Agave,
600 : // we cache the results of some checks but do not immediately return
601 : // an error.
602 : // */
603 : // ulong slot_complete_idx = FD_SHRED_IDX_NULL;
604 : // fd_block_set_t data_complete_idxs[FD_SHRED_BLK_MAX / sizeof(ulong)];
605 : // int err = FD_MAP_ERR_AGAIN;
606 : // while( err == FD_MAP_ERR_AGAIN ) {
607 : // fd_block_map_query_t quer[1] = {0};
608 : // err = fd_block_map_query_try( blockstore->block_map, &slot, NULL, quer, 0 );
609 : // fd_block_info_t * query = fd_block_map_query_ele( quer );
610 : // if( FD_UNLIKELY( err == FD_MAP_ERR_AGAIN ) )continue;
611 : // if( FD_UNLIKELY( err == FD_MAP_ERR_KEY ) ) FD_LOG_ERR(( "fd_runtime_block_verify_ticks: fd_block_map_query_try failed" ));
612 : // slot_complete_idx = query->slot_complete_idx;
613 : // fd_memcpy( data_complete_idxs, query->data_complete_idxs, sizeof(data_complete_idxs) );
614 : // err = fd_block_map_query_test( quer );
615 : // }
616 :
617 : // uint batch_cnt = 0;
618 : // ulong batch_idx = 0;
619 : // while ( batch_idx <= slot_complete_idx ) {
620 : // batch_cnt++;
621 : // ulong batch_sz = 0;
622 : // uint end_idx = (uint)fd_block_set_const_iter_next( data_complete_idxs, batch_idx - 1 );
623 : // FD_TEST( fd_blockstore_slice_query( blockstore, slot, (uint) batch_idx, end_idx, block_data_sz, block_data, &batch_sz ) == FD_BLOCKSTORE_SUCCESS );
624 : // ulong micro_cnt = FD_LOAD( ulong, block_data );
625 : // ulong off = sizeof(ulong);
626 : // for( ulong i = 0UL; i < micro_cnt; i++ ){
627 : // fd_microblock_hdr_t const * hdr = fd_type_pun_const( ( block_data + off ) );
628 : // off += sizeof(fd_microblock_hdr_t);
629 : // tick_hash_count = fd_ulong_sat_add( tick_hash_count, hdr->hash_cnt );
630 : // if( hdr->txn_cnt == 0UL ){
631 : // tick_count++;
632 : // if( FD_LIKELY( hashes_per_tick > 1UL ) ) {
633 : // if( FD_UNLIKELY( tick_hash_count != hashes_per_tick ) ) {
634 : // 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 ));
635 : // invalid_tick_hash_count = 1U;
636 : // }
637 : // }
638 : // tick_hash_count = 0UL;
639 : // continue;
640 : // }
641 : // /* This wasn't a tick entry, but it's the last entry. */
642 : // if( FD_UNLIKELY( i == micro_cnt - 1UL ) ) {
643 : // has_trailing_entry = batch_cnt;
644 : // }
645 :
646 : // /* seek past txns */
647 : // uchar txn[FD_TXN_MAX_SZ];
648 : // for( ulong j = 0; j < hdr->txn_cnt; j++ ) {
649 : // ulong pay_sz = 0;
650 : // ulong txn_sz = fd_txn_parse_core( block_data + off, fd_ulong_min( batch_sz - off, FD_TXN_MTU ), txn, NULL, &pay_sz );
651 : // if( FD_UNLIKELY( !pay_sz ) ) FD_LOG_ERR(( "failed to parse transaction %lu in microblock %lu in slot %lu", j, i, slot ) );
652 : // 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 ));
653 : // off += pay_sz;
654 : // }
655 : // }
656 : // /* advance batch iterator */
657 : // if( FD_UNLIKELY( batch_cnt == 1 ) ){ /* first batch */
658 : // batch_idx = fd_block_set_const_iter_init( data_complete_idxs ) + 1;
659 : // } else {
660 : // batch_idx = fd_block_set_const_iter_next( data_complete_idxs, batch_idx - 1 ) + 1;
661 : // }
662 : // }
663 :
664 : // ulong next_tick_height = tick_height + tick_count;
665 : // if( FD_UNLIKELY( next_tick_height > max_tick_height ) ) {
666 : // 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 ));
667 : // FD_LOG_WARNING(( "Too many ticks" ));
668 : // return FD_BLOCK_ERR_TOO_MANY_TICKS;
669 : // }
670 : // if( FD_UNLIKELY( next_tick_height < max_tick_height ) ) {
671 : // FD_LOG_WARNING(( "Too few ticks" ));
672 : // return FD_BLOCK_ERR_TOO_FEW_TICKS;
673 : // }
674 : // if( FD_UNLIKELY( has_trailing_entry == batch_cnt ) ) {
675 : // FD_LOG_WARNING(( "Did not end with a tick" ));
676 : // return FD_BLOCK_ERR_TRAILING_ENTRY;
677 : // }
678 :
679 : // /* Not returning FD_BLOCK_ERR_INVALID_LAST_TICK because we assume the
680 : // slot is full. */
681 :
682 : // /* Don't care about low power hashing or no hashing. */
683 : // if( FD_LIKELY( hashes_per_tick > 1UL ) ) {
684 : // if( FD_UNLIKELY( invalid_tick_hash_count ) ) {
685 : // FD_LOG_WARNING(( "Tick with invalid number of hashes found" ));
686 : // return FD_BLOCK_ERR_INVALID_TICK_HASH_COUNT;
687 : // }
688 : // }
689 :
690 : // return FD_BLOCK_OK;
691 : // }
692 :
693 : int
694 : fd_runtime_load_txn_address_lookup_tables(
695 : fd_txn_t const * txn,
696 : uchar const * payload,
697 : fd_funk_t * funk,
698 : fd_funk_txn_t * funk_txn,
699 : ulong slot,
700 : fd_slot_hash_t const * hashes, /* deque */
701 : fd_acct_addr_t * out_accts_alt
702 0 : ) {
703 :
704 0 : if( FD_LIKELY( txn->transaction_version!=FD_TXN_V0 ) ) return FD_RUNTIME_EXECUTE_SUCCESS;
705 :
706 0 : ulong readonly_lut_accs_cnt = 0UL;
707 0 : ulong writable_lut_accs_cnt = 0UL;
708 0 : fd_acct_addr_t * readonly_lut_accs = out_accts_alt+txn->addr_table_adtl_writable_cnt;
709 0 : fd_txn_acct_addr_lut_t const * addr_luts = fd_txn_get_address_tables_const( txn );
710 0 : for( ulong i = 0UL; i < txn->addr_table_lookup_cnt; i++ ) {
711 0 : fd_txn_acct_addr_lut_t const * addr_lut = &addr_luts[i];
712 0 : fd_pubkey_t const * addr_lut_acc = (fd_pubkey_t *)(payload + addr_lut->addr_off);
713 :
714 : /* https://github.com/anza-xyz/agave/blob/368ea563c423b0a85cc317891187e15c9a321521/accounts-db/src/accounts.rs#L90-L94 */
715 0 : FD_TXN_ACCOUNT_DECL( addr_lut_rec );
716 0 : int err = fd_txn_account_init_from_funk_readonly( addr_lut_rec,
717 0 : addr_lut_acc,
718 0 : funk,
719 0 : funk_txn );
720 0 : if( FD_UNLIKELY( err != FD_ACC_MGR_SUCCESS ) ) {
721 0 : return FD_RUNTIME_TXN_ERR_ADDRESS_LOOKUP_TABLE_NOT_FOUND;
722 0 : }
723 :
724 : /* https://github.com/anza-xyz/agave/blob/368ea563c423b0a85cc317891187e15c9a321521/accounts-db/src/accounts.rs#L96-L114 */
725 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) ) ) ) {
726 0 : return FD_RUNTIME_TXN_ERR_INVALID_ADDRESS_LOOKUP_TABLE_OWNER;
727 0 : }
728 :
729 : /* Realistically impossible case, but need to make sure we don't cause an OOB data access
730 : https://github.com/anza-xyz/agave/blob/368ea563c423b0a85cc317891187e15c9a321521/sdk/program/src/address_lookup_table/state.rs#L205-L209 */
731 0 : if( FD_UNLIKELY( addr_lut_rec->vt->get_data_len( addr_lut_rec ) < FD_LOOKUP_TABLE_META_SIZE ) ) {
732 0 : return FD_RUNTIME_TXN_ERR_INVALID_ADDRESS_LOOKUP_TABLE_DATA;
733 0 : }
734 :
735 : /* https://github.com/anza-xyz/agave/blob/574bae8fefc0ed256b55340b9d87b7689bcdf222/accounts-db/src/accounts.rs#L141-L142 */
736 0 : fd_bincode_decode_ctx_t decode_ctx = {
737 0 : .data = addr_lut_rec->vt->get_data( addr_lut_rec ),
738 0 : .dataend = &addr_lut_rec->vt->get_data( addr_lut_rec )[FD_LOOKUP_TABLE_META_SIZE]
739 0 : };
740 :
741 0 : ulong total_sz = 0UL;
742 0 : err = fd_address_lookup_table_state_decode_footprint( &decode_ctx, &total_sz );
743 0 : if( FD_UNLIKELY( err ) ) {
744 0 : return FD_RUNTIME_TXN_ERR_INVALID_ADDRESS_LOOKUP_TABLE_DATA;
745 0 : }
746 :
747 : /* https://github.com/anza-xyz/agave/blob/368ea563c423b0a85cc317891187e15c9a321521/sdk/program/src/address_lookup_table/state.rs#L197-L214 */
748 0 : fd_address_lookup_table_state_t table[1];
749 0 : fd_address_lookup_table_state_t * addr_lookup_table_state = fd_address_lookup_table_state_decode( table, &decode_ctx );
750 :
751 : /* https://github.com/anza-xyz/agave/blob/368ea563c423b0a85cc317891187e15c9a321521/sdk/program/src/address_lookup_table/state.rs#L200-L203 */
752 0 : if( FD_UNLIKELY( addr_lookup_table_state->discriminant != fd_address_lookup_table_state_enum_lookup_table ) ) {
753 0 : return FD_RUNTIME_TXN_ERR_INVALID_ADDRESS_LOOKUP_TABLE_DATA;
754 0 : }
755 :
756 : /* Again probably an impossible case, but the ALUT data needs to be 32-byte aligned
757 : https://github.com/anza-xyz/agave/blob/368ea563c423b0a85cc317891187e15c9a321521/sdk/program/src/address_lookup_table/state.rs#L210-L214 */
758 0 : if( FD_UNLIKELY( (addr_lut_rec->vt->get_data_len( addr_lut_rec ) - FD_LOOKUP_TABLE_META_SIZE) & 0x1fUL ) ) {
759 0 : return FD_RUNTIME_TXN_ERR_INVALID_ADDRESS_LOOKUP_TABLE_DATA;
760 0 : }
761 :
762 : /* https://github.com/anza-xyz/agave/blob/368ea563c423b0a85cc317891187e15c9a321521/accounts-db/src/accounts.rs#L101-L112 */
763 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];
764 0 : ulong lookup_addrs_cnt = (addr_lut_rec->vt->get_data_len( addr_lut_rec ) - FD_LOOKUP_TABLE_META_SIZE) >> 5UL; // = (dlen - 56) / 32
765 :
766 : /* https://github.com/anza-xyz/agave/blob/368ea563c423b0a85cc317891187e15c9a321521/sdk/program/src/address_lookup_table/state.rs#L175-L176 */
767 0 : ulong active_addresses_len;
768 0 : err = fd_get_active_addresses_len( &addr_lookup_table_state->inner.lookup_table,
769 0 : slot,
770 0 : hashes,
771 0 : lookup_addrs_cnt,
772 0 : &active_addresses_len );
773 0 : if( FD_UNLIKELY( err ) ) {
774 0 : return err;
775 0 : }
776 :
777 : /* https://github.com/anza-xyz/agave/blob/368ea563c423b0a85cc317891187e15c9a321521/sdk/program/src/address_lookup_table/state.rs#L169-L182 */
778 0 : uchar * writable_lut_idxs = (uchar *)payload + addr_lut->writable_off;
779 0 : for( ulong j=0; j<addr_lut->writable_cnt; j++ ) {
780 : /* https://github.com/anza-xyz/agave/blob/368ea563c423b0a85cc317891187e15c9a321521/sdk/program/src/address_lookup_table/state.rs#L177-L181 */
781 0 : if( writable_lut_idxs[j] >= active_addresses_len ) {
782 0 : return FD_RUNTIME_TXN_ERR_INVALID_ADDRESS_LOOKUP_TABLE_INDEX;
783 0 : }
784 0 : out_accts_alt[writable_lut_accs_cnt++] = lookup_addrs[writable_lut_idxs[j]];
785 0 : }
786 :
787 0 : uchar * readonly_lut_idxs = (uchar *)payload + addr_lut->readonly_off;
788 0 : for( ulong j = 0; j < addr_lut->readonly_cnt; j++ ) {
789 : /* https://github.com/anza-xyz/agave/blob/368ea563c423b0a85cc317891187e15c9a321521/sdk/program/src/address_lookup_table/state.rs#L177-L181 */
790 0 : if( readonly_lut_idxs[j] >= active_addresses_len ) {
791 0 : return FD_RUNTIME_TXN_ERR_INVALID_ADDRESS_LOOKUP_TABLE_INDEX;
792 0 : }
793 0 : readonly_lut_accs[readonly_lut_accs_cnt++] = lookup_addrs[readonly_lut_idxs[j]];
794 0 : }
795 0 : }
796 :
797 0 : return FD_RUNTIME_EXECUTE_SUCCESS;
798 0 : }
799 :
800 : int
801 : fd_runtime_microblock_verify_read_write_conflicts( fd_txn_p_t * txns,
802 : ulong txn_cnt,
803 : fd_conflict_detect_ele_t * acct_map,
804 : fd_acct_addr_t * acct_arr,
805 : fd_funk_t * funk,
806 : fd_funk_txn_t * funk_txn,
807 : ulong slot,
808 : fd_slot_hash_t * slot_hashes,
809 : fd_features_t * features,
810 : int * out_conflict_detected,
811 0 : fd_acct_addr_t * out_conflict_addr_opt ) {
812 0 : *out_conflict_detected=FD_RUNTIME_NO_CONFLICT_DETECTED;
813 0 : #define NO_CONFLICT ( *out_conflict_detected==FD_RUNTIME_NO_CONFLICT_DETECTED )
814 :
815 0 : #define UPDATE_CONFLICT(cond1, cond2, acct) \
816 0 : if( FD_UNLIKELY( cond1 ) ) { \
817 0 : if( FD_LIKELY( out_conflict_addr_opt ) ) *out_conflict_addr_opt = acct; \
818 0 : *out_conflict_detected=FD_RUNTIME_WRITE_WRITE_CONFLICT_DETECTED; \
819 0 : } else if( FD_UNLIKELY( cond2 ) ) { \
820 0 : if( FD_LIKELY( out_conflict_addr_opt ) ) *out_conflict_addr_opt = acct; \
821 0 : *out_conflict_detected=FD_RUNTIME_READ_WRITE_CONFLICT_DETECTED; \
822 0 : }
823 :
824 0 : ulong curr_idx = 0;
825 0 : ulong sentinel_is_read = 0;
826 0 : ulong sentinel_is_written = 0;
827 0 : int runtime_err = FD_RUNTIME_EXECUTE_SUCCESS;
828 0 : for( ulong i=0; i<txn_cnt && NO_CONFLICT; i++ ) {
829 0 : fd_txn_p_t * txn = txns+i;
830 0 : fd_acct_addr_t * txn_accts = acct_arr+curr_idx;
831 :
832 : /* Put the immediate & ALT accounts at txn_accts */
833 0 : const fd_acct_addr_t * accts_imm = fd_txn_get_acct_addrs( TXN(txn), txn->payload );
834 0 : ulong accts_imm_cnt = fd_txn_account_cnt( TXN(txn), FD_TXN_ACCT_CAT_IMM );
835 0 : fd_memcpy( txn_accts, accts_imm, accts_imm_cnt*sizeof(fd_acct_addr_t) );
836 0 : runtime_err = fd_runtime_load_txn_address_lookup_tables( TXN(txn),
837 0 : txn->payload,
838 0 : funk,
839 0 : funk_txn,
840 0 : slot,
841 0 : slot_hashes,
842 0 : txn_accts+accts_imm_cnt );
843 0 : if( FD_UNLIKELY( runtime_err!=FD_RUNTIME_EXECUTE_SUCCESS ) ) break;
844 :
845 0 : ulong accounts_cnt = fd_txn_account_cnt( TXN(txn), FD_TXN_ACCT_CAT_ALL );
846 0 : curr_idx +=accounts_cnt;
847 0 : uint bpf_upgradeable = fd_txn_account_has_bpf_loader_upgradeable( fd_type_pun( txn_accts ), accounts_cnt );
848 :
849 : /* Iterate all writable accounts and detect W-W/R-W conflicts */
850 0 : for( fd_txn_acct_iter_t iter=fd_txn_acct_iter_init( TXN(txn), FD_TXN_ACCT_CAT_WRITABLE );
851 0 : iter!=fd_txn_acct_iter_end() && NO_CONFLICT;
852 0 : iter=fd_txn_acct_iter_next( iter ) ) {
853 0 : ushort idx = (ushort)fd_txn_acct_iter_idx( iter );
854 0 : fd_acct_addr_t writable_acc = txn_accts[ idx ];
855 :
856 : /* Check whether writable_acc is demoted to a read-only account */
857 0 : if( FD_UNLIKELY( !fd_exec_txn_account_is_writable_idx_flat( slot,
858 0 : idx,
859 0 : fd_type_pun( &txn_accts[ idx ] ),
860 0 : TXN(txn),
861 0 : features,
862 0 : bpf_upgradeable ) ) ) {
863 0 : continue;
864 0 : }
865 :
866 : /* writable_acc is the sentinel (fd_acct_addr_null) */
867 0 : if( FD_UNLIKELY( fd_conflict_detect_map_key_inval( writable_acc ) ) ) {
868 0 : UPDATE_CONFLICT( sentinel_is_written, sentinel_is_read, writable_acc );
869 0 : sentinel_is_written = 1;
870 0 : continue;
871 0 : }
872 :
873 : /* writable_acc is not the sentinel (fd_acct_addr_null) */
874 0 : fd_conflict_detect_ele_t * found = fd_conflict_detect_map_query( acct_map, writable_acc, NULL );
875 0 : if( FD_UNLIKELY( found ) ) {
876 0 : UPDATE_CONFLICT( found->writable, !found->writable, writable_acc );
877 0 : } else {
878 0 : fd_conflict_detect_ele_t * entry = fd_conflict_detect_map_insert( acct_map, writable_acc );
879 0 : entry->writable = 1;
880 0 : }
881 0 : }
882 :
883 : /* Iterate all readonly accounts and detect R-W conflicts */
884 0 : for( fd_txn_acct_iter_t iter=fd_txn_acct_iter_init( TXN(txn), FD_TXN_ACCT_CAT_READONLY );
885 0 : iter!=fd_txn_acct_iter_end() && NO_CONFLICT;
886 0 : iter=fd_txn_acct_iter_next( iter ) ) {
887 0 : fd_acct_addr_t readonly_acc = txn_accts[ fd_txn_acct_iter_idx( iter ) ];
888 :
889 : /* readonly_acc is the sentinel (fd_acct_addr_null) */
890 0 : if( FD_UNLIKELY( fd_conflict_detect_map_key_inval( readonly_acc ) ) ) {
891 0 : UPDATE_CONFLICT( 0, sentinel_is_written, readonly_acc );
892 0 : sentinel_is_read = 1;
893 0 : continue;
894 0 : }
895 :
896 : /* readonly_acc is not the sentinel (fd_acct_addr_null) */
897 0 : fd_conflict_detect_ele_t * found = fd_conflict_detect_map_query( acct_map, readonly_acc, NULL );
898 0 : if( FD_UNLIKELY( found ) ) {
899 0 : UPDATE_CONFLICT( 0, found->writable, readonly_acc );
900 0 : } else {
901 0 : fd_conflict_detect_ele_t * entry = fd_conflict_detect_map_insert( acct_map, readonly_acc );
902 0 : entry->writable = 0;
903 0 : }
904 0 : }
905 0 : }
906 :
907 : /* Clear all the entries inserted into acct_map */
908 0 : for( ulong i=0; i<curr_idx; i++ ) {
909 0 : if( FD_UNLIKELY( fd_conflict_detect_map_key_inval( acct_arr[i] ) ) ) continue;
910 0 : fd_conflict_detect_ele_t * found = fd_conflict_detect_map_query( acct_map, acct_arr[i], NULL );
911 0 : if( FD_LIKELY( found ) ) fd_conflict_detect_map_remove( acct_map, found );
912 0 : }
913 :
914 0 : if( FD_UNLIKELY( runtime_err!=FD_RUNTIME_EXECUTE_SUCCESS ) ) {
915 0 : return runtime_err;
916 0 : } else {
917 : /* https://github.com/anza-xyz/agave/blob/v2.2.3/accounts-db/src/account_locks.rs#L31 */
918 : /* https://github.com/anza-xyz/agave/blob/v2.2.3/accounts-db/src/account_locks.rs#L34 */
919 0 : return NO_CONFLICT? FD_RUNTIME_EXECUTE_SUCCESS : FD_RUNTIME_TXN_ERR_ACCOUNT_IN_USE;
920 0 : }
921 0 : }
922 :
923 : void
924 0 : fd_runtime_poh_verify( fd_poh_verifier_t * poh_info ) {
925 :
926 0 : fd_hash_t working_hash = *(poh_info->in_poh_hash);
927 0 : fd_hash_t init_hash = working_hash;
928 :
929 0 : fd_microblock_hdr_t const * hdr = poh_info->microblock.hdr;
930 0 : ulong microblk_sz = poh_info->microblk_max_sz;
931 :
932 0 : if( !hdr->txn_cnt ){
933 0 : fd_poh_append( &working_hash, hdr->hash_cnt );
934 0 : } else { /* not a tick, regular microblock */
935 0 : if( hdr->hash_cnt ){
936 0 : fd_poh_append( &working_hash, hdr->hash_cnt - 1 );
937 0 : }
938 :
939 0 : ulong leaf_cnt_max = FD_TXN_ACTUAL_SIG_MAX * hdr->txn_cnt;
940 :
941 0 : FD_SPAD_FRAME_BEGIN( poh_info->spad ) {
942 0 : uchar * commit = fd_spad_alloc( poh_info->spad, FD_WBMTREE32_ALIGN, fd_wbmtree32_footprint(leaf_cnt_max) );
943 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 );
944 0 : fd_wbmtree32_t * tree = fd_wbmtree32_init( commit, leaf_cnt_max );
945 0 : fd_wbmtree32_leaf_t * l = &leafs[0];
946 :
947 : /* Loop across transactions */
948 0 : ulong leaf_cnt = 0UL;
949 0 : ulong off = sizeof(fd_microblock_hdr_t);
950 0 : for( ulong txn_idx=0UL; txn_idx<hdr->txn_cnt; txn_idx++ ) {
951 0 : fd_txn_p_t txn_p;
952 0 : ulong pay_sz = 0UL;
953 0 : ulong txn_sz = fd_txn_parse_core( poh_info->microblock.raw + off,
954 0 : fd_ulong_min( FD_TXN_MTU, microblk_sz - off ),
955 0 : TXN(&txn_p),
956 0 : NULL,
957 0 : &pay_sz );
958 0 : if( FD_UNLIKELY( !pay_sz || !txn_sz || txn_sz > FD_TXN_MTU ) ) {
959 0 : FD_LOG_ERR(( "failed to parse transaction %lu in replay", txn_idx ));
960 0 : }
961 :
962 : /* Loop across signatures */
963 0 : fd_txn_t const * txn = (fd_txn_t const *) txn_p._;
964 0 : fd_ed25519_sig_t const * sigs = (fd_ed25519_sig_t const *)fd_type_pun((poh_info->microblock.raw + off) + (ulong)txn->signature_off);
965 0 : for( ulong j=0UL; j<txn->signature_cnt; j++ ) {
966 0 : l->data = (uchar *)&sigs[j];
967 0 : l->data_len = sizeof(fd_ed25519_sig_t);
968 0 : l++;
969 0 : leaf_cnt++;
970 0 : }
971 0 : off += pay_sz;
972 0 : }
973 :
974 0 : uchar * mbuf = fd_spad_alloc( poh_info->spad, 1UL, leaf_cnt * (sizeof(fd_ed25519_sig_t) + 1) );
975 0 : fd_wbmtree32_append( tree, leafs, leaf_cnt, mbuf );
976 0 : uchar * root = fd_wbmtree32_fini( tree );
977 0 : fd_poh_mixin( &working_hash, root );
978 0 : } FD_SPAD_FRAME_END;
979 0 : }
980 :
981 0 : if( FD_UNLIKELY( memcmp(hdr->hash, working_hash.hash, sizeof(fd_hash_t)) ) ) {
982 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 ) ));
983 0 : poh_info->success = -1;
984 0 : }
985 0 : }
986 :
987 : int
988 : fd_runtime_block_execute_prepare( fd_exec_slot_ctx_t * slot_ctx,
989 0 : fd_spad_t * runtime_spad ) {
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 : if( FD_UNLIKELY( !fd_sysvar_cache_restore( slot_ctx ) ) ) {
1013 0 : FD_LOG_ERR(( "Failed to restore sysvar cache" ));
1014 0 : }
1015 :
1016 0 : return FD_RUNTIME_EXECUTE_SUCCESS;
1017 0 : }
1018 :
1019 : void
1020 : fd_runtime_block_execute_finalize_start( fd_exec_slot_ctx_t * slot_ctx,
1021 : fd_spad_t * runtime_spad,
1022 : fd_accounts_hash_task_data_t * * task_data,
1023 0 : ulong lt_hash_cnt ) {
1024 :
1025 0 : fd_sysvar_slot_history_update( slot_ctx, runtime_spad );
1026 :
1027 : /* This slot is now "frozen" and can't be changed anymore. */
1028 0 : fd_runtime_freeze( slot_ctx );
1029 :
1030 : /* Collect list of changed accounts to be added to bank hash */
1031 0 : *task_data = fd_spad_alloc( runtime_spad,
1032 0 : alignof(fd_accounts_hash_task_data_t),
1033 0 : sizeof(fd_accounts_hash_task_data_t) );
1034 0 : (*task_data)->lthash_values = fd_spad_alloc_check(
1035 0 : runtime_spad, alignof(fd_lthash_value_t), lt_hash_cnt * sizeof(fd_lthash_value_t) );
1036 :
1037 0 : for( ulong i=0UL; i<lt_hash_cnt; i++ ) {
1038 0 : fd_lthash_zero( &((*task_data)->lthash_values)[i] );
1039 0 : }
1040 :
1041 0 : fd_collect_modified_accounts( slot_ctx, *task_data, runtime_spad );
1042 0 : }
1043 :
1044 : int
1045 : fd_runtime_block_execute_finalize_finish( fd_exec_slot_ctx_t * slot_ctx,
1046 : fd_capture_ctx_t * capture_ctx,
1047 : fd_runtime_block_info_t const * block_info,
1048 : fd_spad_t * runtime_spad,
1049 : fd_accounts_hash_task_data_t * task_data,
1050 0 : ulong lt_hash_cnt ) {
1051 :
1052 0 : fd_hash_t * bank_hash = fd_bank_bank_hash_modify( slot_ctx->bank );
1053 0 : int err = fd_update_hash_bank_exec_hash( slot_ctx,
1054 0 : bank_hash,
1055 0 : capture_ctx,
1056 0 : task_data,
1057 0 : 1UL,
1058 0 : task_data->lthash_values,
1059 0 : lt_hash_cnt,
1060 0 : block_info->signature_cnt,
1061 0 : runtime_spad );
1062 :
1063 0 : if( FD_UNLIKELY( err ) ) {
1064 0 : FD_LOG_ERR(( "Unable to hash at end of slot" ));
1065 0 : }
1066 :
1067 0 : return FD_RUNTIME_EXECUTE_SUCCESS;
1068 :
1069 0 : }
1070 :
1071 : int
1072 : fd_runtime_block_execute_finalize_para( fd_exec_slot_ctx_t * slot_ctx,
1073 : fd_capture_ctx_t * capture_ctx,
1074 : fd_runtime_block_info_t const * block_info,
1075 : ulong worker_cnt,
1076 : fd_spad_t * runtime_spad,
1077 0 : fd_exec_para_cb_ctx_t * exec_para_ctx ) {
1078 :
1079 0 : FD_SPAD_FRAME_BEGIN( runtime_spad ) {
1080 :
1081 0 : fd_accounts_hash_task_data_t * task_data = NULL;
1082 :
1083 0 : fd_runtime_block_execute_finalize_start( slot_ctx, runtime_spad, &task_data, worker_cnt );
1084 :
1085 0 : exec_para_ctx->fn_arg_1 = (void*)task_data;
1086 0 : exec_para_ctx->fn_arg_2 = (void*)worker_cnt;
1087 0 : exec_para_ctx->fn_arg_3 = (void*)slot_ctx;
1088 0 : fd_exec_para_call_func( exec_para_ctx );
1089 :
1090 0 : fd_runtime_block_execute_finalize_finish( slot_ctx, capture_ctx, block_info, runtime_spad, task_data, worker_cnt );
1091 :
1092 0 : } FD_SPAD_FRAME_END;
1093 :
1094 0 : return 0;
1095 0 : }
1096 :
1097 : /******************************************************************************/
1098 : /* Transaction Level Execution Management */
1099 : /******************************************************************************/
1100 :
1101 : /* fd_runtime_prepare_txns_start is responsible for setting up the task infos,
1102 : the slot_ctx, and for setting up the accessed accounts. */
1103 :
1104 : int
1105 : fd_runtime_prepare_txns_start( fd_exec_slot_ctx_t * slot_ctx,
1106 : fd_execute_txn_task_info_t * task_info,
1107 : fd_txn_p_t * txns,
1108 : ulong txn_cnt,
1109 0 : fd_spad_t * runtime_spad ) {
1110 0 : int res = 0;
1111 : /* Loop across transactions */
1112 0 : for( ulong txn_idx = 0UL; txn_idx < txn_cnt; txn_idx++ ) {
1113 0 : fd_txn_p_t * txn = &txns[txn_idx];
1114 :
1115 : /* Allocate/setup transaction context and task infos */
1116 0 : task_info[txn_idx].txn_ctx = fd_spad_alloc( runtime_spad, FD_EXEC_TXN_CTX_ALIGN, FD_EXEC_TXN_CTX_FOOTPRINT );
1117 0 : fd_exec_txn_ctx_t * txn_ctx = task_info[txn_idx].txn_ctx;
1118 0 : task_info[txn_idx].exec_res = 0;
1119 0 : task_info[txn_idx].txn = txn;
1120 0 : fd_txn_t const * txn_descriptor = (fd_txn_t const *) txn->_;
1121 :
1122 0 : fd_rawtxn_b_t raw_txn = { .raw = txn->payload, .txn_sz = (ushort)txn->payload_sz };
1123 :
1124 0 : task_info[txn_idx].txn_ctx->spad = runtime_spad;
1125 0 : task_info[txn_idx].txn_ctx->spad_wksp = fd_wksp_containing( runtime_spad );
1126 0 : int err = fd_execute_txn_prepare_start( slot_ctx,
1127 0 : txn_ctx,
1128 0 : txn_descriptor,
1129 0 : &raw_txn );
1130 0 : if( FD_UNLIKELY( err ) ) {
1131 0 : task_info[txn_idx].exec_res = err;
1132 0 : txn->flags = 0U;
1133 0 : res |= err;
1134 0 : }
1135 0 : }
1136 :
1137 0 : return res;
1138 0 : }
1139 :
1140 : /* fd_runtime_pre_execute_check is responsible for conducting many of the
1141 : transaction sanitization checks. */
1142 :
1143 : void
1144 0 : fd_runtime_pre_execute_check( fd_execute_txn_task_info_t * task_info ) {
1145 0 : if( FD_UNLIKELY( !( task_info->txn->flags & FD_TXN_P_FLAGS_SANITIZE_SUCCESS ) ) ) {
1146 0 : return;
1147 0 : }
1148 :
1149 0 : int err;
1150 :
1151 : /* https://github.com/anza-xyz/agave/blob/16de8b75ebcd57022409b422de557dd37b1de8db/sdk/src/transaction/sanitized.rs#L263-L275
1152 : TODO: Agave's precompile verification is done at the slot level, before batching and executing transactions. This logic should probably
1153 : be moved in the future. The Agave call heirarchy looks something like this:
1154 : process_single_slot
1155 : v
1156 : confirm_full_slot
1157 : v
1158 : confirm_slot_entries --------------------------------------------------->
1159 : v v v
1160 : verify_transaction ComputeBudget::process_instruction process_entries
1161 : v v
1162 : verify_precompiles process_batches
1163 : v
1164 : ...
1165 : v
1166 : load_and_execute_transactions
1167 : v
1168 : ...
1169 : v
1170 : load_accounts --> load_transaction_accounts
1171 : v
1172 : general transaction execution
1173 :
1174 : */
1175 :
1176 0 : uchar dump_txn = !!( task_info->txn_ctx->capture_ctx &&
1177 0 : task_info->txn_ctx->slot >= task_info->txn_ctx->capture_ctx->dump_proto_start_slot &&
1178 0 : task_info->txn_ctx->capture_ctx->dump_txn_to_pb );
1179 0 : if( FD_UNLIKELY( dump_txn ) ) {
1180 0 : fd_dump_txn_to_protobuf( task_info->txn_ctx, task_info->txn_ctx->spad );
1181 0 : }
1182 :
1183 : /* Verify the transaction. For now, this step only involves processing
1184 : the compute budget instructions. */
1185 0 : err = fd_executor_verify_transaction( task_info->txn_ctx );
1186 0 : if( FD_UNLIKELY( err!=FD_RUNTIME_EXECUTE_SUCCESS ) ) {
1187 0 : task_info->txn->flags = 0U;
1188 0 : task_info->exec_res = err;
1189 0 : return;
1190 0 : }
1191 :
1192 : /* Resolve and verify ALUT-referenced account keys, if applicable */
1193 0 : err = fd_executor_setup_txn_alut_account_keys( task_info->txn_ctx );
1194 0 : if( FD_UNLIKELY( err!=FD_RUNTIME_EXECUTE_SUCCESS ) ) {
1195 0 : task_info->txn->flags = 0U;
1196 0 : task_info->exec_res = err;
1197 0 : return;
1198 0 : }
1199 :
1200 : /* Set up the transaction accounts and other txn ctx metadata */
1201 0 : fd_exec_txn_ctx_t * txn_ctx = task_info->txn_ctx;
1202 0 : fd_executor_setup_accounts_for_txn( txn_ctx );
1203 :
1204 : /* Post-sanitization checks. Called from `prepare_sanitized_batch()` which, for now, only is used
1205 : to lock the accounts and perform a couple basic validations.
1206 : https://github.com/anza-xyz/agave/blob/838c1952595809a31520ff1603a13f2c9123aa51/accounts-db/src/account_locks.rs#L118 */
1207 0 : err = fd_executor_validate_account_locks( txn_ctx );
1208 0 : if( FD_UNLIKELY( err!=FD_RUNTIME_EXECUTE_SUCCESS ) ) {
1209 0 : task_info->txn->flags = 0U;
1210 0 : task_info->exec_res = err;
1211 0 : return;
1212 0 : }
1213 :
1214 : /* `load_and_execute_transactions()` -> `check_transactions()`
1215 : https://github.com/anza-xyz/agave/blob/ced98f1ebe73f7e9691308afa757323003ff744f/runtime/src/bank.rs#L3667-L3672 */
1216 0 : err = fd_executor_check_transactions( txn_ctx );
1217 0 : if( FD_UNLIKELY( err!=FD_RUNTIME_EXECUTE_SUCCESS ) ) {
1218 0 : task_info->txn->flags = 0U;
1219 0 : task_info->exec_res = err;
1220 0 : return;
1221 0 : }
1222 :
1223 : /* `load_and_execute_sanitized_transactions()` -> `validate_fees()` -> `validate_transaction_fee_payer()`
1224 : https://github.com/anza-xyz/agave/blob/ced98f1ebe73f7e9691308afa757323003ff744f/svm/src/transaction_processor.rs#L236-L249 */
1225 0 : err = fd_executor_validate_transaction_fee_payer( txn_ctx );
1226 0 : if( FD_UNLIKELY( err!=FD_RUNTIME_EXECUTE_SUCCESS ) ) {
1227 0 : task_info->txn->flags = 0U;
1228 0 : task_info->exec_res = err;
1229 0 : return;
1230 0 : }
1231 :
1232 : /* https://github.com/anza-xyz/agave/blob/ced98f1ebe73f7e9691308afa757323003ff744f/svm/src/transaction_processor.rs#L284-L296 */
1233 0 : err = fd_executor_load_transaction_accounts( txn_ctx );
1234 0 : if( FD_UNLIKELY( err!=FD_RUNTIME_EXECUTE_SUCCESS ) ) {
1235 : /* Regardless of whether transaction accounts were loaded successfully, the transaction is
1236 : included in the block and transaction fees are collected.
1237 : https://github.com/anza-xyz/agave/blob/v2.1.6/svm/src/transaction_processor.rs#L341-L357 */
1238 0 : task_info->txn->flags |= FD_TXN_P_FLAGS_FEES_ONLY;
1239 0 : task_info->exec_res = err;
1240 :
1241 : /* If the transaction fails to load, the "rollback" accounts will include one of the following:
1242 : 1. Nonce account only
1243 : 2. Fee payer only
1244 : 3. Nonce account + fee payer
1245 :
1246 : Because the cost tracker uses the loaded account data size in block cost calculations, we need to
1247 : make sure our calculated loaded accounts data size is conformant with Agave's.
1248 : https://github.com/anza-xyz/agave/blob/v2.1.14/runtime/src/bank.rs#L4116
1249 :
1250 : In any case, we should always add the dlen of the fee payer. */
1251 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] );
1252 :
1253 : /* Special case handling for if a nonce account is present in the transaction. */
1254 0 : if( task_info->txn_ctx->nonce_account_idx_in_txn!=ULONG_MAX ) {
1255 : /* If the nonce account is not the fee payer, then we separately add the dlen of the nonce account. Otherwise, we would
1256 : be double counting the dlen of the fee payer. */
1257 0 : if( task_info->txn_ctx->nonce_account_idx_in_txn!=FD_FEE_PAYER_TXN_IDX ) {
1258 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 );
1259 0 : }
1260 0 : }
1261 0 : }
1262 :
1263 : /*
1264 : The fee payer and the nonce account will be stored and hashed so
1265 : long as the transaction landed on chain, or, in Agave terminology,
1266 : the transaction was processed.
1267 : https://github.com/anza-xyz/agave/blob/v2.1.1/runtime/src/account_saver.rs#L72
1268 :
1269 : A transaction lands on chain in one of two ways:
1270 : (1) Passed fee validation and loaded accounts.
1271 : (2) Passed fee validation and failed to load accounts and the enable_transaction_loading_failure_fees feature is enabled as per
1272 : SIMD-0082 https://github.com/anza-xyz/feature-gate-tracker/issues/52
1273 :
1274 : So, at this point, the transaction is committable.
1275 : */
1276 0 : }
1277 :
1278 : /* fd_runtime_finalize_txn is a helper used by the non-tpool transaction
1279 : executor to finalize borrowed account changes back into funk. It also
1280 : handles txncache insertion and updates to the vote/stake cache.
1281 : TODO: This function should probably be moved to fd_executor.c. */
1282 :
1283 : void
1284 : fd_runtime_finalize_txn( fd_funk_t * funk,
1285 : fd_funk_txn_t * funk_txn,
1286 : fd_execute_txn_task_info_t * task_info,
1287 : fd_spad_t * finalize_spad,
1288 0 : fd_bank_t * bank ) {
1289 :
1290 : /* for all accounts, if account->is_verified==true, propagate update
1291 : to cache entry. */
1292 :
1293 : /* Store transaction info including logs */
1294 : // fd_runtime_finalize_txns_update_blockstore_meta( slot_ctx, task_info, 1UL );
1295 :
1296 : /* Collect fees */
1297 :
1298 0 : FD_ATOMIC_FETCH_AND_ADD( fd_bank_txn_count_modify( bank ), 1UL );
1299 0 : FD_ATOMIC_FETCH_AND_ADD( fd_bank_execution_fees_modify( bank ), task_info->txn_ctx->execution_fee );
1300 0 : FD_ATOMIC_FETCH_AND_ADD( fd_bank_priority_fees_modify( bank ), task_info->txn_ctx->priority_fee );
1301 :
1302 0 : fd_exec_txn_ctx_t * txn_ctx = task_info->txn_ctx;
1303 0 : int exec_txn_err = task_info->exec_res;
1304 :
1305 0 : FD_ATOMIC_FETCH_AND_ADD( fd_bank_signature_count_modify( bank ), txn_ctx->txn_descriptor->signature_cnt );
1306 :
1307 0 : if( FD_UNLIKELY( exec_txn_err ) ) {
1308 :
1309 : /* Save the fee_payer. Everything but the fee balance should be reset.
1310 : TODO: an optimization here could be to use a dirty flag in the
1311 : borrowed account. If the borrowed account data has been changed in
1312 : any way, then the full account can be rolled back as it is done now.
1313 : However, most of the time the account data is not changed, and only
1314 : the lamport balance has to change. */
1315 :
1316 : /* With nonce account rollbacks, there are three cases:
1317 : 1. No nonce account in the transaction
1318 : 2. Nonce account is the fee payer
1319 : 3. Nonce account is not the fee payer
1320 :
1321 : We should always rollback the nonce account first. Note that the nonce account may be the fee payer (case 2). */
1322 0 : if( txn_ctx->nonce_account_idx_in_txn!=ULONG_MAX ) {
1323 0 : fd_txn_account_save( txn_ctx->rollback_nonce_account, funk, funk_txn, txn_ctx->spad_wksp );
1324 0 : }
1325 :
1326 : /* Now, we must only save the fee payer if the nonce account was not the fee payer (because that was already saved above) */
1327 0 : if( FD_LIKELY( txn_ctx->nonce_account_idx_in_txn!=FD_FEE_PAYER_TXN_IDX ) ) {
1328 0 : fd_txn_account_save( txn_ctx->rollback_fee_payer_account, funk, funk_txn, txn_ctx->spad_wksp );
1329 0 : }
1330 0 : } else {
1331 :
1332 0 : int dirty_vote_acc = txn_ctx->dirty_vote_acc;
1333 0 : int dirty_stake_acc = txn_ctx->dirty_stake_acc;
1334 :
1335 0 : for( ushort i=0; i<txn_ctx->accounts_cnt; i++ ) {
1336 : /* We are only interested in saving writable accounts and the fee
1337 : payer account. */
1338 0 : if( !fd_exec_txn_ctx_account_is_writable_idx( txn_ctx, i ) && i!=FD_FEE_PAYER_TXN_IDX ) {
1339 0 : continue;
1340 0 : }
1341 :
1342 0 : fd_txn_account_t * acc_rec = &txn_ctx->accounts[i];
1343 :
1344 0 : if( dirty_vote_acc && 0==memcmp( acc_rec->vt->get_owner( acc_rec ), &fd_solana_vote_program_id, sizeof(fd_pubkey_t) ) ) {
1345 0 : fd_vote_store_account( acc_rec, bank );
1346 0 : FD_SPAD_FRAME_BEGIN( finalize_spad ) {
1347 0 : int err;
1348 0 : fd_vote_state_versioned_t * vsv = fd_bincode_decode_spad(
1349 0 : vote_state_versioned, finalize_spad,
1350 0 : acc_rec->vt->get_data( acc_rec ),
1351 0 : acc_rec->vt->get_data_len( acc_rec ),
1352 0 : &err );
1353 0 : if( FD_UNLIKELY( err ) ) {
1354 0 : FD_LOG_WARNING(( "failed to decode vote state versioned" ));
1355 0 : continue;
1356 0 : }
1357 :
1358 0 : fd_vote_block_timestamp_t const * ts = NULL;
1359 0 : switch( vsv->discriminant ) {
1360 0 : case fd_vote_state_versioned_enum_v0_23_5:
1361 0 : ts = &vsv->inner.v0_23_5.last_timestamp;
1362 0 : break;
1363 0 : case fd_vote_state_versioned_enum_v1_14_11:
1364 0 : ts = &vsv->inner.v1_14_11.last_timestamp;
1365 0 : break;
1366 0 : case fd_vote_state_versioned_enum_current:
1367 0 : ts = &vsv->inner.current.last_timestamp;
1368 0 : break;
1369 0 : default:
1370 0 : __builtin_unreachable();
1371 0 : }
1372 :
1373 0 : fd_vote_record_timestamp_vote_with_slot( acc_rec->pubkey,
1374 0 : ts->timestamp,
1375 0 : ts->slot,
1376 0 : bank );
1377 0 : } FD_SPAD_FRAME_END;
1378 0 : }
1379 :
1380 0 : if( dirty_stake_acc && 0==memcmp( acc_rec->vt->get_owner( acc_rec ), &fd_solana_stake_program_id, sizeof(fd_pubkey_t) ) ) {
1381 : // TODO: does this correctly handle stake account close?
1382 0 : fd_store_stake_delegation( acc_rec, bank );
1383 0 : }
1384 :
1385 0 : fd_txn_account_save( &txn_ctx->accounts[i], funk, funk_txn, txn_ctx->spad_wksp );
1386 0 : }
1387 :
1388 : /* We need to queue any existing program accounts that may have
1389 : been deployed / upgraded for reverification in the program
1390 : cache since their programdata may have changed. ELF / sBPF
1391 : metadata will need to be updated. */
1392 0 : ulong current_slot = fd_bank_slot_get( bank );
1393 0 : for( uchar i=0; i<txn_ctx->programs_to_reverify_cnt; i++ ) {
1394 0 : fd_pubkey_t const * program_key = &txn_ctx->programs_to_reverify[i];
1395 0 : fd_program_cache_queue_program_for_reverification( funk, funk_txn, program_key, current_slot );
1396 0 : }
1397 0 : }
1398 :
1399 0 : int is_vote = fd_txn_is_simple_vote_transaction( txn_ctx->txn_descriptor, txn_ctx->_txn_raw->raw );
1400 0 : if( !is_vote ){
1401 0 : ulong * nonvote_txn_count = fd_bank_nonvote_txn_count_modify( bank );
1402 0 : FD_ATOMIC_FETCH_AND_ADD(nonvote_txn_count, 1);
1403 :
1404 0 : if( FD_UNLIKELY( exec_txn_err ) ){
1405 0 : ulong * nonvote_failed_txn_count = fd_bank_nonvote_failed_txn_count_modify( bank );
1406 0 : FD_ATOMIC_FETCH_AND_ADD( nonvote_failed_txn_count, 1 );
1407 0 : }
1408 0 : } else {
1409 0 : if( FD_UNLIKELY( exec_txn_err ) ){
1410 0 : ulong * failed_txn_count = fd_bank_failed_txn_count_modify( bank );
1411 0 : FD_ATOMIC_FETCH_AND_ADD( failed_txn_count, 1 );
1412 0 : }
1413 0 : }
1414 :
1415 0 : ulong * total_compute_units_used = fd_bank_total_compute_units_used_modify( bank );
1416 0 : FD_ATOMIC_FETCH_AND_ADD( total_compute_units_used, txn_ctx->compute_budget_details.compute_unit_limit - txn_ctx->compute_budget_details.compute_meter );
1417 :
1418 0 : }
1419 :
1420 : /* fd_runtime_prepare_and_execute_txn is the main entrypoint into the executor
1421 : tile. At this point, the slot and epoch context should NOT be changed.
1422 : NOTE: The executor tile doesn't exist yet. */
1423 :
1424 : static int
1425 : fd_runtime_prepare_and_execute_txn( fd_exec_slot_ctx_t const * slot_ctx,
1426 : fd_txn_p_t * txn,
1427 : fd_execute_txn_task_info_t * task_info,
1428 : fd_spad_t * exec_spad,
1429 0 : fd_capture_ctx_t * capture_ctx ) {
1430 :
1431 0 : int res = 0;
1432 :
1433 0 : fd_exec_txn_ctx_t * txn_ctx = task_info->txn_ctx;
1434 0 : task_info->exec_res = -1;
1435 0 : task_info->txn = txn;
1436 0 : fd_txn_t const * txn_descriptor = (fd_txn_t const *) txn->_;
1437 0 : task_info->txn_ctx->spad = exec_spad;
1438 0 : task_info->txn_ctx->spad_wksp = fd_wksp_containing( exec_spad );
1439 :
1440 0 : fd_rawtxn_b_t raw_txn = { .raw = txn->payload, .txn_sz = (ushort)txn->payload_sz };
1441 :
1442 0 : res = fd_execute_txn_prepare_start( slot_ctx, txn_ctx, txn_descriptor, &raw_txn );
1443 0 : if( FD_UNLIKELY( res ) ) {
1444 0 : txn->flags = 0U;
1445 0 : return -1;
1446 0 : }
1447 :
1448 0 : task_info->txn_ctx->capture_ctx = capture_ctx;
1449 :
1450 0 : if( FD_UNLIKELY( fd_executor_txn_verify( txn_ctx )!=0 ) ) {
1451 0 : FD_LOG_WARNING(( "sigverify failed: %s", FD_BASE58_ENC_64_ALLOCA( (uchar *)txn_ctx->_txn_raw->raw+txn_ctx->txn_descriptor->signature_off ) ));
1452 0 : task_info->txn->flags = 0U;
1453 0 : task_info->exec_res = FD_RUNTIME_TXN_ERR_SIGNATURE_FAILURE;
1454 0 : }
1455 :
1456 0 : fd_runtime_pre_execute_check( task_info ); /* TODO: check if this will be called from executor tile or replay tile */
1457 0 : if( FD_UNLIKELY( !( task_info->txn->flags & FD_TXN_P_FLAGS_SANITIZE_SUCCESS ) ) ) {
1458 0 : res = task_info->exec_res;
1459 0 : return -1;
1460 0 : }
1461 :
1462 : /* Execute */
1463 0 : task_info->txn->flags |= FD_TXN_P_FLAGS_EXECUTE_SUCCESS;
1464 0 : task_info->exec_res = fd_execute_txn( task_info );
1465 :
1466 0 : if( task_info->exec_res==0 ) {
1467 0 : fd_txn_reclaim_accounts( task_info->txn_ctx );
1468 0 : }
1469 :
1470 0 : return res;
1471 :
1472 0 : }
1473 :
1474 : /* fd_executor_txn_verify and fd_runtime_pre_execute_check are responisble
1475 : for the bulk of the pre-transaction execution checks in the runtime.
1476 : They aim to preserve the ordering present in the Agave client to match
1477 : parity in terms of error codes. Sigverify is kept separate from the rest
1478 : of the transaction checks for fuzzing convenience.
1479 :
1480 : For reference this is the general code path which contains all relevant
1481 : pre-transactions checks in the v2.0.x Agave client from upstream
1482 : to downstream is as follows:
1483 :
1484 : confirm_slot_entries() which calls verify_ticks() and
1485 : verify_transaction(). verify_transaction() calls verify_and_hash_message()
1486 : and verify_precompiles() which parallels fd_executor_txn_verify() and
1487 : fd_executor_verify_transaction().
1488 :
1489 : process_entries() contains a duplicate account check which is part of
1490 : agave account lock acquiring. This is checked inline in
1491 : fd_runtime_pre_execute_check().
1492 :
1493 : load_and_execute_transactions() contains the function check_transactions().
1494 : This contains check_age() and check_status_cache() which is paralleled by
1495 : fd_executor_check_transaction_age_and_compute_budget_limits() and
1496 : fd_executor_check_status_cache() respectively.
1497 :
1498 : load_and_execute_sanitized_transactions() contains validate_fees()
1499 : which is responsible for executing the compute budget instructions,
1500 : validating the fee payer and collecting the fee. This is mirrored in
1501 : firedancer with fd_executor_compute_budget_program_execute_instructions()
1502 : and fd_executor_collect_fees(). load_and_execute_sanitized_transactions()
1503 : also checks the total data size of the accounts in load_accounts() and
1504 : validates the program accounts in load_transaction_accounts(). This
1505 : is paralled by fd_executor_load_transaction_accounts(). */
1506 :
1507 : /******************************************************************************/
1508 : /* Epoch Boundary */
1509 : /******************************************************************************/
1510 :
1511 : /* Update the epoch bank stakes cache with the delegated stake values from the slot bank cache.
1512 : The slot bank cache will have been accumulating this epoch, and now we are at an epoch boundary
1513 : we can safely update the epoch stakes cache with the latest values.
1514 :
1515 : In Solana, the stakes cache is updated after every transaction
1516 : (https://github.com/solana-labs/solana/blob/c091fd3da8014c0ef83b626318018f238f506435/runtime/src/bank.rs#L7587).
1517 : As delegations have to warm up, the contents of the cache will not change inter-epoch. We can therefore update
1518 : the cache only at epoch boundaries.
1519 :
1520 : https://github.com/solana-labs/solana/blob/c091fd3da8014c0ef83b626318018f238f506435/runtime/src/stakes.rs#L65 */
1521 : static void
1522 : fd_update_stake_delegations( fd_exec_slot_ctx_t * slot_ctx,
1523 0 : fd_epoch_info_t * temp_info ) {
1524 :
1525 0 : fd_stakes_global_t * stakes = fd_bank_stakes_locking_modify( slot_ctx->bank );
1526 0 : fd_delegation_pair_t_mapnode_t * stake_delegations_pool = fd_stakes_stake_delegations_pool_join( stakes );
1527 0 : fd_delegation_pair_t_mapnode_t * stake_delegations_root = fd_stakes_stake_delegations_root_join( stakes );
1528 :
1529 : /* In one pass, iterate over all the new stake infos and insert the updated values into the epoch stakes cache
1530 : This assumes that there is enough memory pre-allocated for the stakes cache. */
1531 0 : for( ulong idx=temp_info->stake_infos_new_keys_start_idx; idx<temp_info->stake_infos_len; idx++ ) {
1532 : // Fetch and store the delegation associated with this stake account
1533 0 : fd_delegation_pair_t_mapnode_t key;
1534 0 : key.elem.account = temp_info->stake_infos[idx].account;
1535 0 : fd_delegation_pair_t_mapnode_t * entry = fd_delegation_pair_t_map_find( stake_delegations_pool, stake_delegations_root, &key );
1536 0 : if( FD_LIKELY( entry==NULL ) ) {
1537 0 : entry = fd_delegation_pair_t_map_acquire( stake_delegations_pool );
1538 0 : if( FD_UNLIKELY( !entry ) ) {
1539 0 : FD_TEST( 0 == fd_delegation_pair_t_map_verify( stake_delegations_pool, stake_delegations_root ) );
1540 0 : FD_LOG_CRIT(( "stake_delegations_pool full %lu", fd_delegation_pair_t_map_size( stake_delegations_pool, stake_delegations_root ) ));
1541 0 : }
1542 0 : entry->elem.account = temp_info->stake_infos[idx].account;
1543 0 : entry->elem.delegation = temp_info->stake_infos[idx].stake.delegation;
1544 0 : fd_delegation_pair_t_map_insert( stake_delegations_pool, &stake_delegations_root, entry );
1545 0 : }
1546 0 : }
1547 :
1548 0 : fd_stakes_stake_delegations_pool_update( stakes, stake_delegations_pool );
1549 0 : fd_stakes_stake_delegations_root_update( stakes, stake_delegations_root );
1550 0 : fd_bank_stakes_end_locking_modify( slot_ctx->bank );
1551 :
1552 : /* At the epoch boundary, release all of the stake account keys
1553 : because at this point all of the changes have been applied to the
1554 : stakes. */
1555 0 : fd_account_keys_global_t * stake_account_keys = fd_bank_stake_account_keys_locking_modify( slot_ctx->bank );
1556 0 : fd_account_keys_pair_t_mapnode_t * account_keys_pool = fd_account_keys_account_keys_pool_join( stake_account_keys );
1557 0 : fd_account_keys_pair_t_mapnode_t * account_keys_root = fd_account_keys_account_keys_root_join( stake_account_keys );
1558 :
1559 0 : fd_account_keys_pair_t_map_release_tree( account_keys_pool, account_keys_root );
1560 0 : account_keys_root = NULL;
1561 :
1562 0 : fd_account_keys_account_keys_pool_update( stake_account_keys, account_keys_pool );
1563 0 : fd_account_keys_account_keys_root_update( stake_account_keys, account_keys_root );
1564 0 : fd_bank_stake_account_keys_end_locking_modify( slot_ctx->bank );
1565 0 : }
1566 :
1567 : /* Replace the stakes in T-2 (epoch_stakes) by the stakes at T-1 (next_epoch_stakes) */
1568 : static void
1569 0 : fd_update_epoch_stakes( fd_exec_slot_ctx_t * slot_ctx ) {
1570 :
1571 : /* Copy epoch_bank->next_epoch_stakes into fd_bank_slot_get( slot_ctx->bank )_bank.epoch_stakes */
1572 0 : fd_vote_accounts_global_t const * next_epoch_stakes = fd_bank_next_epoch_stakes_locking_query( slot_ctx->bank );
1573 :
1574 0 : fd_vote_accounts_global_t * epoch_stakes = fd_bank_epoch_stakes_locking_modify( slot_ctx->bank );
1575 0 : fd_memcpy( epoch_stakes, next_epoch_stakes, fd_bank_epoch_stakes_footprint );
1576 0 : fd_bank_epoch_stakes_end_locking_modify( slot_ctx->bank );
1577 :
1578 0 : fd_bank_next_epoch_stakes_end_locking_query( slot_ctx->bank );
1579 :
1580 0 : }
1581 :
1582 : /* Copy stakes->vote_accounts into next_epoch_stakes. */
1583 : static void
1584 0 : fd_update_next_epoch_stakes( fd_exec_slot_ctx_t * slot_ctx ) {
1585 :
1586 : /* FIXME: This is technically not correct, since the vote accounts
1587 : could be laid out after the stake delegations from fd_stakes.
1588 : The correct solution is to split out the stake delgations from the
1589 : vote accounts in fd_stakes. */
1590 :
1591 : /* Copy stakes->vote_accounts into next_epoch_stakes */
1592 0 : fd_stakes_global_t const * stakes = fd_bank_stakes_locking_query( slot_ctx->bank );
1593 0 : fd_vote_accounts_global_t const * vote_stakes = &stakes->vote_accounts;
1594 :
1595 0 : fd_vote_accounts_global_t * next_epoch_stakes = fd_bank_next_epoch_stakes_locking_modify( slot_ctx->bank );
1596 0 : fd_memcpy( next_epoch_stakes, vote_stakes, fd_bank_next_epoch_stakes_footprint );
1597 0 : fd_bank_next_epoch_stakes_end_locking_modify( slot_ctx->bank );
1598 :
1599 0 : fd_bank_stakes_end_locking_query( slot_ctx->bank );
1600 0 : }
1601 :
1602 : /* Mimics `bank.new_target_program_account()`. Assumes `out_rec` is a modifiable record.
1603 :
1604 : From the calling context, `out_rec` points to a native program record (e.g. Config, ALUT native programs).
1605 : There should be enough space in `out_rec->data` to hold at least 36 bytes (the size of a BPF upgradeable
1606 : program account) when calling this function. The native program account's owner is set to the BPF loader
1607 : upgradeable program ID, and lamports are increased / deducted to contain the rent exempt minimum balance.
1608 :
1609 : https://github.com/anza-xyz/agave/blob/v2.1.0/runtime/src/bank/builtins/core_bpf_migration/mod.rs#L79-L95 */
1610 : static int
1611 : fd_new_target_program_account( fd_exec_slot_ctx_t * slot_ctx,
1612 : fd_pubkey_t const * target_program_data_address,
1613 0 : fd_txn_account_t * out_rec ) {
1614 : /* https://github.com/anza-xyz/agave/blob/v2.1.0/sdk/account/src/lib.rs#L471 */
1615 0 : out_rec->vt->set_rent_epoch( out_rec, 0UL );
1616 :
1617 : /* https://github.com/anza-xyz/agave/blob/v2.1.0/runtime/src/bank/builtins/core_bpf_migration/mod.rs#L86-L88 */
1618 0 : fd_bpf_upgradeable_loader_state_t state = {
1619 0 : .discriminant = fd_bpf_upgradeable_loader_state_enum_program,
1620 0 : .inner = {
1621 0 : .program = {
1622 0 : .programdata_address = *target_program_data_address,
1623 0 : }
1624 0 : }
1625 0 : };
1626 :
1627 : /* https://github.com/anza-xyz/agave/blob/v2.1.0/runtime/src/bank/builtins/core_bpf_migration/mod.rs#L89-L90 */
1628 0 : fd_rent_t const * rent = fd_bank_rent_query( slot_ctx->bank );
1629 0 : if( FD_UNLIKELY( rent==NULL ) ) {
1630 0 : return -1;
1631 0 : }
1632 :
1633 0 : out_rec->vt->set_lamports( out_rec, fd_rent_exempt_minimum_balance( rent, SIZE_OF_PROGRAM ) );
1634 0 : fd_bincode_encode_ctx_t ctx = {
1635 0 : .data = out_rec->vt->get_data_mut( out_rec ),
1636 0 : .dataend = out_rec->vt->get_data_mut( out_rec ) + SIZE_OF_PROGRAM,
1637 0 : };
1638 :
1639 : /* https://github.com/anza-xyz/agave/blob/v2.1.0/runtime/src/bank/builtins/core_bpf_migration/mod.rs#L91-L9 */
1640 0 : int err = fd_bpf_upgradeable_loader_state_encode( &state, &ctx );
1641 0 : if( FD_UNLIKELY( err ) ) {
1642 0 : return err;
1643 0 : }
1644 0 : out_rec->vt->set_owner( out_rec, &fd_solana_bpf_loader_upgradeable_program_id );
1645 :
1646 : /* https://github.com/anza-xyz/agave/blob/v2.1.0/runtime/src/bank/builtins/core_bpf_migration/mod.rs#L93-L94 */
1647 0 : out_rec->vt->set_executable( out_rec, 1 );
1648 0 : return FD_RUNTIME_EXECUTE_SUCCESS;
1649 0 : }
1650 :
1651 : /* Mimics `bank.new_target_program_data_account()`. Assumes `new_target_program_data_account` is a modifiable record.
1652 : `config_upgrade_authority_address` may be NULL.
1653 :
1654 : This function uses an existing buffer account `buffer_acc_rec` to set the program data account data for a core
1655 : program BPF migration. Sets the lamports and data fields of `new_target_program_data_account` based on the
1656 : ELF data length, and sets the owner to the BPF loader upgradeable program ID.
1657 :
1658 : https://github.com/anza-xyz/agave/blob/v2.1.0/runtime/src/bank/builtins/core_bpf_migration/mod.rs#L97-L153 */
1659 : static int
1660 : fd_new_target_program_data_account( fd_exec_slot_ctx_t * slot_ctx,
1661 : fd_pubkey_t * config_upgrade_authority_address,
1662 : fd_txn_account_t * buffer_acc_rec,
1663 : fd_txn_account_t * new_target_program_data_account,
1664 0 : fd_spad_t * runtime_spad ) {
1665 :
1666 0 : FD_SPAD_FRAME_BEGIN( runtime_spad ) {
1667 :
1668 : /* https://github.com/anza-xyz/agave/blob/v2.1.0/runtime/src/bank/builtins/core_bpf_migration/mod.rs#L113-L116 */
1669 0 : int err;
1670 0 : fd_bpf_upgradeable_loader_state_t * state = fd_bincode_decode_spad(
1671 0 : bpf_upgradeable_loader_state, runtime_spad,
1672 0 : buffer_acc_rec->vt->get_data( buffer_acc_rec ),
1673 0 : buffer_acc_rec->vt->get_data_len( buffer_acc_rec ),
1674 0 : &err );
1675 0 : if( FD_UNLIKELY( err ) ) return err;
1676 :
1677 0 : if( FD_UNLIKELY( !fd_bpf_upgradeable_loader_state_is_buffer( state ) ) ) {
1678 0 : return -1;
1679 0 : }
1680 :
1681 : /* https://github.com/anza-xyz/agave/blob/v2.1.0/runtime/src/bank/builtins/core_bpf_migration/mod.rs#L118-L125 */
1682 0 : if( config_upgrade_authority_address!=NULL ) {
1683 0 : if( FD_UNLIKELY( state->inner.buffer.authority_address==NULL ||
1684 0 : memcmp( config_upgrade_authority_address, state->inner.buffer.authority_address, sizeof(fd_pubkey_t) ) ) ) {
1685 0 : return -1;
1686 0 : }
1687 0 : }
1688 :
1689 : /* https://github.com/anza-xyz/agave/blob/v2.1.0/runtime/src/bank/builtins/core_bpf_migration/mod.rs#L127-L132 */
1690 0 : fd_rent_t const * rent = fd_bank_rent_query( slot_ctx->bank );
1691 0 : if( FD_UNLIKELY( rent==NULL ) ) {
1692 0 : return -1;
1693 0 : }
1694 :
1695 0 : const uchar * elf = buffer_acc_rec->vt->get_data( buffer_acc_rec ) + BUFFER_METADATA_SIZE;
1696 0 : ulong space = PROGRAMDATA_METADATA_SIZE - BUFFER_METADATA_SIZE + buffer_acc_rec->vt->get_data_len( buffer_acc_rec );
1697 0 : ulong lamports = fd_rent_exempt_minimum_balance( rent, space );
1698 :
1699 : /* https://github.com/anza-xyz/agave/blob/v2.1.0/runtime/src/bank/builtins/core_bpf_migration/mod.rs#L134-L137 */
1700 0 : fd_bpf_upgradeable_loader_state_t programdata_metadata = {
1701 0 : .discriminant = fd_bpf_upgradeable_loader_state_enum_program_data,
1702 0 : .inner = {
1703 0 : .program_data = {
1704 0 : .slot = fd_bank_slot_get( slot_ctx->bank ),
1705 0 : .upgrade_authority_address = config_upgrade_authority_address
1706 0 : }
1707 0 : }
1708 0 : };
1709 :
1710 : /* https://github.com/anza-xyz/agave/blob/v2.1.0/runtime/src/bank/builtins/core_bpf_migration/mod.rs#L139-L144 */
1711 0 : new_target_program_data_account->vt->set_lamports( new_target_program_data_account, lamports );
1712 0 : fd_bincode_encode_ctx_t encode_ctx = {
1713 0 : .data = new_target_program_data_account->vt->get_data_mut( new_target_program_data_account ),
1714 0 : .dataend = new_target_program_data_account->vt->get_data_mut( new_target_program_data_account ) + PROGRAMDATA_METADATA_SIZE,
1715 0 : };
1716 0 : err = fd_bpf_upgradeable_loader_state_encode( &programdata_metadata, &encode_ctx );
1717 0 : if( FD_UNLIKELY( err ) ) {
1718 0 : return err;
1719 0 : }
1720 0 : new_target_program_data_account->vt->set_owner( new_target_program_data_account, &fd_solana_bpf_loader_upgradeable_program_id );
1721 :
1722 : /* Copy the ELF data over
1723 : https://github.com/anza-xyz/agave/blob/v2.1.0/runtime/src/bank/builtins/core_bpf_migration/mod.rs#L145 */
1724 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 );
1725 :
1726 0 : return FD_RUNTIME_EXECUTE_SUCCESS;
1727 :
1728 0 : } FD_SPAD_FRAME_END;
1729 0 : }
1730 :
1731 : /* Mimics `migrate_builtin_to_core_bpf()`. The arguments map as follows:
1732 : - builtin_program_id: builtin_program_id
1733 : - config
1734 : - source_buffer_address: source_buffer_address
1735 : - migration_target
1736 : - Builtin: !stateless
1737 : - Stateless: stateless
1738 : - upgrade_authority_address: upgrade_authority_address
1739 : https://github.com/anza-xyz/agave/blob/v2.1.0/runtime/src/bank/builtins/core_bpf_migration/mod.rs#L235-L318 */
1740 : static void
1741 : fd_migrate_builtin_to_core_bpf( fd_exec_slot_ctx_t * slot_ctx,
1742 : fd_pubkey_t * upgrade_authority_address,
1743 : fd_pubkey_t const * builtin_program_id,
1744 : fd_pubkey_t const * source_buffer_address,
1745 : uchar stateless,
1746 0 : fd_spad_t * runtime_spad ) {
1747 0 : int err;
1748 :
1749 : /* https://github.com/anza-xyz/agave/blob/v2.1.0/runtime/src/bank/builtins/core_bpf_migration/mod.rs#L242-L243
1750 :
1751 : The below logic is used to obtain a `TargetBuiltin` account. There are three fields of `TargetBuiltin` returned:
1752 : - target.program_address: builtin_program_id
1753 : - target.program_account:
1754 : - if stateless: an AccountSharedData::default() (i.e. system program id, 0 lamports, 0 data, non-executable, system program owner)
1755 : - if NOT stateless: the existing account (for us its called `target_program_account`)
1756 : - target.program_data_address: `target_program_data_address` for us, derived below. */
1757 :
1758 : /* These checks will fail if the core program has already been migrated to BPF, since the account will exist + the program owner
1759 : will no longer be the native loader.
1760 : https://github.com/anza-xyz/agave/blob/v2.1.0/runtime/src/bank/builtins/core_bpf_migration/target_builtin.rs#L23-L50 */
1761 0 : FD_TXN_ACCOUNT_DECL( target_program_account );
1762 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 );
1763 0 : if( !stateless ) {
1764 : /* The program account should exist.
1765 : https://github.com/anza-xyz/agave/blob/v2.1.0/runtime/src/bank/builtins/core_bpf_migration/target_builtin.rs#L30-L33 */
1766 0 : if( FD_UNLIKELY( !program_exists ) ) {
1767 0 : FD_LOG_WARNING(( "Builtin program %s does not exist, skipping migration...", FD_BASE58_ENC_32_ALLOCA( builtin_program_id ) ));
1768 0 : return;
1769 0 : }
1770 :
1771 : /* The program account should be owned by the native loader.
1772 : https://github.com/anza-xyz/agave/blob/v2.1.0/runtime/src/bank/builtins/core_bpf_migration/target_builtin.rs#L35-L38 */
1773 0 : if( FD_UNLIKELY( memcmp( target_program_account->vt->get_owner( target_program_account ), fd_solana_native_loader_id.uc, sizeof(fd_pubkey_t) ) ) ) {
1774 0 : FD_LOG_WARNING(( "Builtin program %s is not owned by the native loader, skipping migration...", FD_BASE58_ENC_32_ALLOCA( builtin_program_id ) ));
1775 0 : return;
1776 0 : }
1777 0 : } else {
1778 : /* The program account should _not_ exist.
1779 : https://github.com/anza-xyz/agave/blob/v2.1.0/runtime/src/bank/builtins/core_bpf_migration/target_builtin.rs#L42-L46 */
1780 0 : if( FD_UNLIKELY( program_exists ) ) {
1781 0 : FD_LOG_WARNING(( "Stateless program %s already exists, skipping migration...", FD_BASE58_ENC_32_ALLOCA( builtin_program_id ) ));
1782 0 : return;
1783 0 : }
1784 0 : }
1785 :
1786 : /* The program data account should not exist.
1787 : https://github.com/anza-xyz/agave/blob/v2.1.0/runtime/src/bank/builtins/core_bpf_migration/target_builtin.rs#L52-L62 */
1788 0 : uint custom_err = UINT_MAX;
1789 0 : fd_pubkey_t target_program_data_address[ 1UL ];
1790 0 : uchar * seeds[ 1UL ];
1791 0 : seeds[ 0UL ] = (uchar *)builtin_program_id;
1792 0 : ulong seed_sz = sizeof(fd_pubkey_t);
1793 0 : uchar bump_seed = 0;
1794 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 );
1795 0 : if( FD_UNLIKELY( err ) ) {
1796 : /* TODO: We should handle these errors more gracefully instead of just killing the client. */
1797 0 : FD_LOG_ERR(( "Unable to find a viable program address bump seed" )); // Solana panics, error code is undefined
1798 0 : return;
1799 0 : }
1800 0 : FD_TXN_ACCOUNT_DECL( program_data_account );
1801 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 ) ) {
1802 0 : FD_LOG_WARNING(( "Program data account %s already exists, skipping migration...", FD_BASE58_ENC_32_ALLOCA( target_program_data_address ) ));
1803 0 : return;
1804 0 : }
1805 :
1806 : /* https://github.com/anza-xyz/agave/blob/v2.1.0/runtime/src/bank/builtins/core_bpf_migration/mod.rs#L244
1807 :
1808 : Obtains a `SourceBuffer` account. There are two fields returned:
1809 : - source.buffer_address: source_buffer_address
1810 : - source.buffer_account: the existing buffer account */
1811 :
1812 : /* The buffer account should exist.
1813 : https://github.com/anza-xyz/agave/blob/v2.1.0/runtime/src/bank/builtins/core_bpf_migration/source_buffer.rs#L26-L29 */
1814 0 : FD_TXN_ACCOUNT_DECL( source_buffer_account );
1815 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 ) ) {
1816 0 : FD_LOG_WARNING(( "Buffer account %s does not exist, skipping migration...", FD_BASE58_ENC_32_ALLOCA( source_buffer_address ) ));
1817 0 : return;
1818 0 : }
1819 :
1820 : /* The buffer account should be owned by the upgradeable loader.
1821 : https://github.com/anza-xyz/agave/blob/v2.1.0/runtime/src/bank/builtins/core_bpf_migration/source_buffer.rs#L31-L34 */
1822 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) ) ) ) {
1823 0 : FD_LOG_WARNING(( "Buffer account %s is not owned by the upgradeable loader, skipping migration...", FD_BASE58_ENC_32_ALLOCA( source_buffer_address ) ));
1824 0 : return;
1825 0 : }
1826 :
1827 : /* The buffer account should have the correct state. We already check the buffer account state in `fd_new_target_program_data_account`,
1828 : so we can skip the checks here.
1829 : https://github.com/anza-xyz/agave/blob/v2.1.0/runtime/src/bank/builtins/core_bpf_migration/source_buffer.rs#L37-L47 */
1830 :
1831 : /* This check is done a bit prematurely because we calculate the previous account state's lamports. We use 0 for starting lamports
1832 : for stateless accounts because they don't yet exist.
1833 :
1834 : https://github.com/anza-xyz/agave/blob/v2.1.0/runtime/src/bank/builtins/core_bpf_migration/mod.rs#L277-L280 */
1835 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 );
1836 :
1837 : /* Start a funk write txn */
1838 0 : fd_funk_txn_t * parent_txn = slot_ctx->funk_txn;
1839 0 : fd_funk_txn_xid_t migration_xid = fd_funk_generate_xid();
1840 0 : fd_funk_txn_start_write( slot_ctx->funk );
1841 0 : slot_ctx->funk_txn = fd_funk_txn_prepare( slot_ctx->funk, slot_ctx->funk_txn, &migration_xid, 0UL );
1842 0 : fd_funk_txn_end_write( slot_ctx->funk );
1843 :
1844 : /* Attempt serialization of program account. If the program is stateless, we want to create the account. Otherwise,
1845 : we want a writable handle to modify the existing account.
1846 : https://github.com/anza-xyz/agave/blob/v2.1.0/runtime/src/bank/builtins/core_bpf_migration/mod.rs#L246-L249 */
1847 0 : FD_TXN_ACCOUNT_DECL( new_target_program_account );
1848 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 );
1849 0 : if( FD_UNLIKELY( err ) ) {
1850 0 : FD_LOG_WARNING(( "Builtin program ID %s does not exist", FD_BASE58_ENC_32_ALLOCA( builtin_program_id ) ));
1851 0 : goto fail;
1852 0 : }
1853 0 : new_target_program_account->vt->set_data_len( new_target_program_account, SIZE_OF_PROGRAM );
1854 0 : new_target_program_account->vt->set_slot( new_target_program_account, fd_bank_slot_get( slot_ctx->bank ) );
1855 :
1856 : /* Create a new target program account. This modifies the existing record. */
1857 0 : err = fd_new_target_program_account( slot_ctx, target_program_data_address, new_target_program_account );
1858 0 : if( FD_UNLIKELY( err ) ) {
1859 0 : FD_LOG_WARNING(( "Failed to write new program state to %s", FD_BASE58_ENC_32_ALLOCA( builtin_program_id ) ));
1860 0 : goto fail;
1861 0 : }
1862 :
1863 0 : fd_txn_account_mutable_fini( new_target_program_account, slot_ctx->funk, slot_ctx->funk_txn );
1864 :
1865 : /* Create a new target program data account. */
1866 0 : ulong new_target_program_data_account_sz = PROGRAMDATA_METADATA_SIZE - BUFFER_METADATA_SIZE + source_buffer_account->vt->get_data_len( source_buffer_account );
1867 0 : FD_TXN_ACCOUNT_DECL( new_target_program_data_account );
1868 0 : err = fd_txn_account_init_from_funk_mutable( new_target_program_data_account,
1869 0 : target_program_data_address,
1870 0 : slot_ctx->funk,
1871 0 : slot_ctx->funk_txn,
1872 0 : 1,
1873 0 : new_target_program_data_account_sz );
1874 0 : if( FD_UNLIKELY( err ) ) {
1875 0 : FD_LOG_WARNING(( "Failed to create new program data account to %s", FD_BASE58_ENC_32_ALLOCA( target_program_data_address ) ));
1876 0 : goto fail;
1877 0 : }
1878 0 : new_target_program_data_account->vt->set_data_len( new_target_program_data_account, new_target_program_data_account_sz );
1879 0 : new_target_program_data_account->vt->set_slot( new_target_program_data_account, fd_bank_slot_get( slot_ctx->bank ) );
1880 :
1881 0 : err = fd_new_target_program_data_account( slot_ctx,
1882 0 : upgrade_authority_address,
1883 0 : source_buffer_account,
1884 0 : new_target_program_data_account,
1885 0 : runtime_spad );
1886 0 : if( FD_UNLIKELY( err ) ) {
1887 0 : FD_LOG_WARNING(( "Failed to write new program data state to %s", FD_BASE58_ENC_32_ALLOCA( target_program_data_address ) ));
1888 0 : goto fail;
1889 0 : }
1890 :
1891 0 : fd_txn_account_mutable_fini( new_target_program_data_account, slot_ctx->funk, slot_ctx->funk_txn );
1892 :
1893 : /* Deploy the new target Core BPF program.
1894 : https://github.com/anza-xyz/agave/blob/v2.1.0/runtime/src/bank/builtins/core_bpf_migration/mod.rs#L268-L271 */
1895 0 : err = fd_directly_invoke_loader_v3_deploy( slot_ctx,
1896 0 : builtin_program_id,
1897 0 : new_target_program_data_account->vt->get_data( new_target_program_data_account ) + PROGRAMDATA_METADATA_SIZE,
1898 0 : new_target_program_data_account->vt->get_data_len( new_target_program_data_account ) - PROGRAMDATA_METADATA_SIZE,
1899 0 : runtime_spad );
1900 0 : if( FD_UNLIKELY( err ) ) {
1901 0 : FD_LOG_WARNING(( "Failed to deploy program %s", FD_BASE58_ENC_32_ALLOCA( builtin_program_id ) ));
1902 0 : goto fail;
1903 0 : }
1904 :
1905 : /* https://github.com/anza-xyz/agave/blob/v2.1.0/runtime/src/bank/builtins/core_bpf_migration/mod.rs#L281-L284 */
1906 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 );
1907 :
1908 : /* Update capitalization.
1909 : https://github.com/anza-xyz/agave/blob/v2.1.0/runtime/src/bank/builtins/core_bpf_migration/mod.rs#L286-L297 */
1910 0 : if( lamports_to_burn>lamports_to_fund ) {
1911 0 : fd_bank_capitalization_set( slot_ctx->bank, fd_bank_capitalization_get( slot_ctx->bank ) - ( lamports_to_burn - lamports_to_fund ) );
1912 0 : } else {
1913 0 : fd_bank_capitalization_set( slot_ctx->bank, fd_bank_capitalization_get( slot_ctx->bank ) + ( lamports_to_fund - lamports_to_burn ) );
1914 0 : }
1915 :
1916 : /* Reclaim the source buffer account
1917 : https://github.com/anza-xyz/agave/blob/v2.1.0/runtime/src/bank/builtins/core_bpf_migration/mod.rs#L305 */
1918 0 : source_buffer_account->vt->set_lamports( source_buffer_account, 0 );
1919 0 : source_buffer_account->vt->set_data_len( source_buffer_account, 0 );
1920 0 : source_buffer_account->vt->clear_owner( source_buffer_account );
1921 :
1922 0 : fd_txn_account_mutable_fini( source_buffer_account, slot_ctx->funk, slot_ctx->funk_txn );
1923 :
1924 : /* Publish the in-preparation transaction into the parent. We should not have to create
1925 : a BPF cache entry here because the program is technically "delayed visibility", so the program
1926 : should not be invokable until the next slot. The cache entry will be created at the end of the
1927 : block as a part of the finalize routine. */
1928 0 : fd_funk_txn_start_write( slot_ctx->funk );
1929 0 : fd_funk_txn_publish_into_parent( slot_ctx->funk, slot_ctx->funk_txn, 1 );
1930 0 : fd_funk_txn_end_write( slot_ctx->funk );
1931 0 : slot_ctx->funk_txn = parent_txn;
1932 0 : return;
1933 :
1934 0 : fail:
1935 : /* Cancel the in-preparation transaction and discard any in-progress changes. */
1936 0 : fd_funk_txn_start_write( slot_ctx->funk );
1937 0 : fd_funk_txn_cancel( slot_ctx->funk, slot_ctx->funk_txn, 0UL );
1938 0 : fd_funk_txn_end_write( slot_ctx->funk );
1939 0 : slot_ctx->funk_txn = parent_txn;
1940 0 : }
1941 :
1942 : /* https://github.com/anza-xyz/agave/blob/v2.1.0/runtime/src/bank.rs#L6704 */
1943 : static void
1944 : fd_apply_builtin_program_feature_transitions( fd_exec_slot_ctx_t * slot_ctx,
1945 0 : fd_spad_t * runtime_spad ) {
1946 : /* TODO: Set the upgrade authority properly from the core bpf migration config. Right now it's set to None.
1947 :
1948 : Migrate any necessary stateless builtins to core BPF. So far, the only "stateless" builtin
1949 : is the Feature program. Beginning checks in the `migrate_builtin_to_core_bpf` function will
1950 : fail if the program has already been migrated to BPF. */
1951 :
1952 0 : FD_SPAD_FRAME_BEGIN( runtime_spad ) {
1953 :
1954 0 : fd_builtin_program_t const * builtins = fd_builtins();
1955 0 : for( ulong i=0UL; i<fd_num_builtins(); i++ ) {
1956 : /* https://github.com/anza-xyz/agave/blob/v2.1.0/runtime/src/bank.rs#L6732-L6751 */
1957 0 : if( builtins[i].core_bpf_migration_config && FD_FEATURE_ACTIVE_OFFSET( fd_bank_slot_get( slot_ctx->bank ), fd_bank_features_get( slot_ctx->bank ), builtins[i].core_bpf_migration_config->enable_feature_offset ) ) {
1958 0 : FD_LOG_NOTICE(( "Migrating builtin program %s to core BPF", FD_BASE58_ENC_32_ALLOCA( builtins[i].pubkey->key ) ));
1959 0 : fd_migrate_builtin_to_core_bpf( slot_ctx,
1960 0 : builtins[i].core_bpf_migration_config->upgrade_authority_address,
1961 0 : builtins[i].core_bpf_migration_config->builtin_program_id,
1962 0 : builtins[i].core_bpf_migration_config->source_buffer_address,
1963 0 : 0,
1964 0 : runtime_spad );
1965 0 : }
1966 : /* https://github.com/anza-xyz/agave/blob/v2.1.0/runtime/src/bank.rs#L6753-L6774 */
1967 0 : if( builtins[i].enable_feature_offset!=NO_ENABLE_FEATURE_ID && FD_FEATURE_JUST_ACTIVATED_OFFSET( slot_ctx, builtins[i].enable_feature_offset ) ) {
1968 0 : FD_LOG_NOTICE(( "Enabling builtin program %s", FD_BASE58_ENC_32_ALLOCA( builtins[i].pubkey->key ) ));
1969 0 : fd_write_builtin_account( slot_ctx, *builtins[i].pubkey, builtins[i].data,strlen(builtins[i].data) );
1970 0 : }
1971 0 : }
1972 :
1973 : /* https://github.com/anza-xyz/agave/blob/v2.1.0/runtime/src/bank.rs#L6776-L6793 */
1974 0 : fd_stateless_builtin_program_t const * stateless_builtins = fd_stateless_builtins();
1975 0 : for( ulong i=0UL; i<fd_num_stateless_builtins(); i++ ) {
1976 0 : if( stateless_builtins[i].core_bpf_migration_config && FD_FEATURE_ACTIVE_OFFSET( fd_bank_slot_get( slot_ctx->bank ), fd_bank_features_get( slot_ctx->bank ), stateless_builtins[i].core_bpf_migration_config->enable_feature_offset ) ) {
1977 0 : FD_LOG_NOTICE(( "Migrating stateless builtin program %s to core BPF", FD_BASE58_ENC_32_ALLOCA( stateless_builtins[i].pubkey->key ) ));
1978 0 : fd_migrate_builtin_to_core_bpf( slot_ctx,
1979 0 : stateless_builtins[i].core_bpf_migration_config->upgrade_authority_address,
1980 0 : stateless_builtins[i].core_bpf_migration_config->builtin_program_id,
1981 0 : stateless_builtins[i].core_bpf_migration_config->source_buffer_address,
1982 0 : 1,
1983 0 : runtime_spad );
1984 0 : }
1985 0 : }
1986 :
1987 : /* https://github.com/anza-xyz/agave/blob/c1080de464cfb578c301e975f498964b5d5313db/runtime/src/bank.rs#L6795-L6805 */
1988 0 : fd_precompile_program_t const * precompiles = fd_precompiles();
1989 0 : for( ulong i=0UL; i<fd_num_precompiles(); i++ ) {
1990 0 : if( precompiles[i].feature_offset != NO_ENABLE_FEATURE_ID && FD_FEATURE_JUST_ACTIVATED_OFFSET( slot_ctx, precompiles[i].feature_offset ) ) {
1991 0 : fd_write_builtin_account( slot_ctx, *precompiles[i].pubkey, "", 0 );
1992 0 : }
1993 0 : }
1994 :
1995 0 : } FD_SPAD_FRAME_END;
1996 0 : }
1997 :
1998 : static void
1999 : fd_feature_activate( fd_features_t * features,
2000 : fd_exec_slot_ctx_t * slot_ctx,
2001 : fd_feature_id_t const * id,
2002 : uchar const acct[ static 32 ],
2003 0 : fd_spad_t * runtime_spad ) {
2004 :
2005 : // Skip reverted features from being activated
2006 0 : if( id->reverted==1 ) {
2007 0 : return;
2008 0 : }
2009 :
2010 0 : FD_TXN_ACCOUNT_DECL( acct_rec );
2011 0 : int err = fd_txn_account_init_from_funk_readonly( acct_rec, (fd_pubkey_t*)acct, slot_ctx->funk, slot_ctx->funk_txn );
2012 0 : if( FD_UNLIKELY( err != FD_ACC_MGR_SUCCESS ) ) {
2013 0 : return;
2014 0 : }
2015 :
2016 0 : FD_SPAD_FRAME_BEGIN( runtime_spad ) {
2017 :
2018 0 : int decode_err = 0;
2019 0 : fd_feature_t * feature = fd_bincode_decode_spad(
2020 0 : feature, runtime_spad,
2021 0 : acct_rec->vt->get_data( acct_rec ),
2022 0 : acct_rec->vt->get_data_len( acct_rec ),
2023 0 : &decode_err );
2024 0 : if( FD_UNLIKELY( decode_err ) ) {
2025 0 : FD_LOG_WARNING(( "Failed to decode feature account %s (%d)", FD_BASE58_ENC_32_ALLOCA( acct ), decode_err ));
2026 0 : return;
2027 0 : }
2028 :
2029 0 : if( feature->has_activated_at ) {
2030 0 : FD_LOG_INFO(( "feature already activated - acc: %s, slot: %lu", FD_BASE58_ENC_32_ALLOCA( acct ), feature->activated_at ));
2031 0 : fd_features_set( features, id, feature->activated_at);
2032 0 : } else {
2033 0 : FD_LOG_INFO(( "Feature %s not activated at %lu, activating", FD_BASE58_ENC_32_ALLOCA( acct ), feature->activated_at ));
2034 :
2035 0 : FD_TXN_ACCOUNT_DECL( modify_acct_rec );
2036 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 );
2037 0 : if( FD_UNLIKELY( err != FD_ACC_MGR_SUCCESS ) ) {
2038 0 : return;
2039 0 : }
2040 :
2041 0 : feature->has_activated_at = 1;
2042 0 : feature->activated_at = fd_bank_slot_get( slot_ctx->bank );
2043 0 : fd_bincode_encode_ctx_t encode_ctx = {
2044 0 : .data = modify_acct_rec->vt->get_data_mut( modify_acct_rec ),
2045 0 : .dataend = modify_acct_rec->vt->get_data_mut( modify_acct_rec ) + modify_acct_rec->vt->get_data_len( modify_acct_rec ),
2046 0 : };
2047 0 : int encode_err = fd_feature_encode( feature, &encode_ctx );
2048 0 : if( FD_UNLIKELY( encode_err != FD_BINCODE_SUCCESS ) ) {
2049 0 : FD_LOG_ERR(( "Failed to encode feature account %s (%d)", FD_BASE58_ENC_32_ALLOCA( acct ), decode_err ));
2050 0 : }
2051 :
2052 0 : fd_txn_account_mutable_fini( modify_acct_rec, slot_ctx->funk, slot_ctx->funk_txn );
2053 0 : }
2054 :
2055 0 : } FD_SPAD_FRAME_END;
2056 0 : }
2057 :
2058 : static void
2059 0 : fd_features_activate( fd_exec_slot_ctx_t * slot_ctx, fd_spad_t * runtime_spad ) {
2060 0 : fd_features_t * features = fd_bank_features_modify( slot_ctx->bank );
2061 0 : for( fd_feature_id_t const * id = fd_feature_iter_init();
2062 0 : !fd_feature_iter_done( id );
2063 0 : id = fd_feature_iter_next( id ) ) {
2064 0 : fd_feature_activate( features, slot_ctx, id, id->id.key, runtime_spad );
2065 0 : }
2066 0 : }
2067 :
2068 : uint
2069 : fd_runtime_is_epoch_boundary( fd_exec_slot_ctx_t * slot_ctx,
2070 : ulong curr_slot,
2071 0 : ulong prev_slot ) {
2072 0 : ulong slot_idx;
2073 0 : fd_epoch_schedule_t const * schedule = fd_bank_epoch_schedule_query( slot_ctx->bank );
2074 0 : ulong prev_epoch = fd_slot_to_epoch( schedule, prev_slot, &slot_idx );
2075 0 : ulong new_epoch = fd_slot_to_epoch( schedule, curr_slot, &slot_idx );
2076 :
2077 0 : return ( prev_epoch < new_epoch || slot_idx == 0 );
2078 0 : }
2079 :
2080 : /* Starting a new epoch.
2081 : New epoch: T
2082 : Just ended epoch: T-1
2083 : Epoch before: T-2
2084 :
2085 : In this function:
2086 : - stakes in T-2 (epoch_stakes) should be replaced by T-1 (next_epoch_stakes)
2087 : - stakes at T-1 (next_epoch_stakes) should be replaced by updated stakes at T (stakes->vote_accounts)
2088 : - leader schedule should be calculated using new T-2 stakes (epoch_stakes)
2089 :
2090 : Invariant during an epoch T:
2091 : next_epoch_stakes holds the stakes at T-1
2092 : epoch_stakes holds the stakes at T-2
2093 : */
2094 : /* process for the start of a new epoch */
2095 : static void
2096 : fd_runtime_process_new_epoch( fd_exec_slot_ctx_t * slot_ctx,
2097 : fd_capture_ctx_t * capture_ctx,
2098 : ulong parent_epoch,
2099 0 : fd_spad_t * runtime_spad ) {
2100 0 : FD_LOG_NOTICE(( "fd_process_new_epoch start" ));
2101 :
2102 0 : FD_SPAD_FRAME_BEGIN( runtime_spad ) {
2103 :
2104 0 : long start = fd_log_wallclock();
2105 :
2106 0 : ulong const slot = fd_bank_slot_get ( slot_ctx->bank );
2107 0 : ulong const epoch = fd_bank_epoch_get( slot_ctx->bank );
2108 :
2109 : /* Activate new features
2110 : https://github.com/anza-xyz/agave/blob/v2.1.0/runtime/src/bank.rs#L6587-L6598 */
2111 0 : fd_features_activate( slot_ctx, runtime_spad );
2112 0 : fd_features_restore( slot_ctx, runtime_spad );
2113 :
2114 : /* Apply builtin program feature transitions
2115 : https://github.com/anza-xyz/agave/blob/v2.1.0/runtime/src/bank.rs#L6621-L6624 */
2116 0 : fd_apply_builtin_program_feature_transitions( slot_ctx, runtime_spad );
2117 :
2118 : /* Get the new rate activation epoch */
2119 0 : int _err[1];
2120 0 : ulong new_rate_activation_epoch_val = 0UL;
2121 0 : ulong * new_rate_activation_epoch = &new_rate_activation_epoch_val;
2122 0 : int is_some = fd_new_warmup_cooldown_rate_epoch(
2123 0 : fd_bank_epoch_schedule_query( slot_ctx->bank ),
2124 0 : fd_bank_features_query( slot_ctx->bank ),
2125 0 : slot,
2126 0 : new_rate_activation_epoch,
2127 0 : _err );
2128 0 : if( FD_UNLIKELY( !is_some ) ) {
2129 0 : new_rate_activation_epoch = NULL;
2130 0 : }
2131 :
2132 0 : fd_epoch_info_t temp_info = {0};
2133 0 : fd_epoch_info_new( &temp_info );
2134 :
2135 : /* If appropiate, use the stakes at T-1 to generate the leader schedule instead of T-2.
2136 : This is due to a subtlety in how Agave's stake caches interact when loading from snapshots.
2137 : See the comment in fd_exec_slot_ctx_recover_. */
2138 :
2139 0 : if( fd_bank_use_prev_epoch_stake_get( slot_ctx->bank ) == epoch ) {
2140 0 : fd_update_epoch_stakes( slot_ctx );
2141 0 : }
2142 :
2143 : /* Updates stake history sysvar accumulated values. */
2144 0 : fd_stakes_activate_epoch( slot_ctx,
2145 0 : new_rate_activation_epoch,
2146 0 : &temp_info,
2147 0 : runtime_spad );
2148 :
2149 : /* Update the stakes epoch value to the new epoch */
2150 0 : fd_stakes_global_t * stakes = fd_bank_stakes_locking_modify( slot_ctx->bank );
2151 0 : stakes->epoch = epoch;
2152 0 : fd_bank_stakes_end_locking_modify( slot_ctx->bank );
2153 :
2154 0 : fd_update_stake_delegations( slot_ctx, &temp_info );
2155 :
2156 : /* Refresh vote accounts in stakes cache using updated stake weights, and merges slot bank vote accounts with the epoch bank vote accounts.
2157 : https://github.com/anza-xyz/agave/blob/v2.1.6/runtime/src/stakes.rs#L363-L370 */
2158 0 : fd_stake_history_t const * history = fd_sysvar_stake_history_read( slot_ctx->funk, slot_ctx->funk_txn, runtime_spad );
2159 0 : if( FD_UNLIKELY( !history ) ) {
2160 0 : FD_LOG_ERR(( "StakeHistory sysvar could not be read and decoded" ));
2161 0 : }
2162 :
2163 : /* FIXME: There are allocations made in here that are persisted. */
2164 0 : fd_refresh_vote_accounts( slot_ctx,
2165 0 : history,
2166 0 : new_rate_activation_epoch,
2167 0 : &temp_info,
2168 0 : runtime_spad );
2169 :
2170 : /* Distribute rewards */
2171 :
2172 0 : fd_hash_t parent_blockhash = {0};
2173 0 : {
2174 0 : fd_blockhashes_t const * bhq = fd_bank_block_hash_queue_query( slot_ctx->bank );
2175 0 : fd_hash_t const * bhq_last = fd_blockhashes_peek_last( bhq );
2176 0 : FD_TEST( bhq_last );
2177 0 : parent_blockhash = *bhq_last;
2178 0 : }
2179 :
2180 0 : fd_begin_partitioned_rewards( slot_ctx,
2181 0 : capture_ctx,
2182 0 : &parent_blockhash,
2183 0 : parent_epoch,
2184 0 : &temp_info,
2185 0 : runtime_spad );
2186 :
2187 : /* Replace stakes at T-2 (epoch_stakes) by stakes at T-1 (next_epoch_stakes) */
2188 0 : fd_update_epoch_stakes( slot_ctx );
2189 :
2190 : /* Replace stakes at T-1 (next_epoch_stakes) by updated stakes at T (stakes->vote_accounts) */
2191 0 : fd_update_next_epoch_stakes( slot_ctx );
2192 :
2193 : /* Update current leaders using epoch_stakes (new T-2 stakes) */
2194 0 : fd_runtime_update_leaders( slot_ctx->bank, fd_bank_slot_get( slot_ctx->bank ), runtime_spad );
2195 :
2196 0 : fd_calculate_epoch_accounts_hash_values( slot_ctx );
2197 :
2198 0 : FD_LOG_NOTICE(( "fd_process_new_epoch end" ));
2199 :
2200 0 : long end = fd_log_wallclock();
2201 0 : FD_LOG_NOTICE(("fd_process_new_epoch took %ld ns", end - start));
2202 :
2203 0 : } FD_SPAD_FRAME_END;
2204 0 : }
2205 :
2206 : /******************************************************************************/
2207 : /* Block Parsing */
2208 : /******************************************************************************/
2209 :
2210 : /* Block iteration and parsing */
2211 :
2212 : /* As a note, all of the logic in this section is used by the full firedancer
2213 : client. The store tile uses these APIs to help parse raw (micro)blocks
2214 : received from the network. */
2215 :
2216 : /* Helpers */
2217 :
2218 : void
2219 : fd_runtime_update_program_cache( fd_exec_slot_ctx_t * slot_ctx,
2220 : fd_txn_p_t const * txn_p,
2221 0 : fd_spad_t * runtime_spad ) {
2222 0 : fd_txn_t const * txn_descriptor = TXN( txn_p );
2223 :
2224 0 : FD_SPAD_FRAME_BEGIN( runtime_spad ) {
2225 :
2226 : /* Iterate over account keys referenced directly in the transaction first */
2227 0 : fd_acct_addr_t const * acc_addrs = fd_txn_get_acct_addrs( txn_descriptor, txn_p );
2228 0 : for( ushort acc_idx=0; acc_idx<txn_descriptor->acct_addr_cnt; acc_idx++ ) {
2229 0 : fd_pubkey_t const * account = fd_type_pun_const( &acc_addrs[acc_idx] );
2230 0 : fd_program_cache_update_program( slot_ctx, account, runtime_spad );
2231 0 : }
2232 :
2233 0 : if( txn_descriptor->transaction_version==FD_TXN_V0 ) {
2234 :
2235 : /* Iterate over account keys referenced in ALUTs */
2236 0 : fd_acct_addr_t alut_accounts[256];
2237 0 : fd_slot_hashes_global_t const * slot_hashes_global = fd_sysvar_slot_hashes_read( slot_ctx->funk, slot_ctx->funk_txn, runtime_spad );
2238 0 : if( FD_UNLIKELY( !slot_hashes_global ) ) {
2239 0 : return;
2240 0 : }
2241 :
2242 0 : fd_slot_hash_t * slot_hash = deq_fd_slot_hash_t_join( (uchar *)slot_hashes_global + slot_hashes_global->hashes_offset );
2243 :
2244 : /* TODO: This is done twice, once in the replay tile and once in the
2245 : exec tile. We should consolidate the account resolution into a
2246 : single place, but also keep in mind from a conformance
2247 : perspective that these ALUT resolution checks happen after some
2248 : things like compute budget instruction parsing */
2249 0 : if( FD_UNLIKELY( fd_runtime_load_txn_address_lookup_tables(
2250 0 : txn_descriptor,
2251 0 : txn_p->payload,
2252 0 : slot_ctx->funk,
2253 0 : slot_ctx->funk_txn,
2254 0 : fd_bank_slot_get( slot_ctx->bank ),
2255 0 : slot_hash,
2256 0 : alut_accounts ) ) ) {
2257 0 : return;
2258 0 : }
2259 :
2260 0 : for( ushort alut_idx=0; alut_idx<txn_descriptor->addr_table_adtl_cnt; alut_idx++ ) {
2261 0 : fd_pubkey_t const * account = fd_type_pun_const( &alut_accounts[alut_idx] );
2262 0 : fd_program_cache_update_program( slot_ctx, account, runtime_spad );
2263 0 : }
2264 0 : }
2265 :
2266 0 : } FD_SPAD_FRAME_END;
2267 0 : }
2268 :
2269 : /* Public API */
2270 :
2271 : /* Block collecting (Only for offline replay) */
2272 :
2273 : static ulong
2274 : fd_runtime_microblock_collect_txns( fd_microblock_info_t const * microblock_info,
2275 0 : fd_txn_p_t * out_txns ) {
2276 0 : ulong txn_cnt = microblock_info->microblock.hdr->txn_cnt;
2277 0 : fd_memcpy( out_txns, microblock_info->txns, txn_cnt * sizeof(fd_txn_p_t) );
2278 0 : return txn_cnt;
2279 0 : }
2280 :
2281 : static ulong
2282 : fd_runtime_microblock_batch_collect_txns( fd_microblock_batch_info_t const * microblock_batch_info,
2283 0 : fd_txn_p_t * out_txns ) {
2284 0 : for( ulong i=0UL; i<microblock_batch_info->microblock_cnt; i++ ) {
2285 0 : ulong txns_collected = fd_runtime_microblock_collect_txns( µblock_batch_info->microblock_infos[i], out_txns );
2286 0 : out_txns += txns_collected;
2287 0 : }
2288 :
2289 0 : return microblock_batch_info->txn_cnt;
2290 0 : }
2291 :
2292 : static ulong
2293 : fd_runtime_block_collect_txns( fd_runtime_block_info_t const * block_info,
2294 0 : fd_txn_p_t * out_txns ) {
2295 0 : for( ulong i=0UL; i<block_info->microblock_batch_cnt; i++ ) {
2296 0 : ulong txns_collected = fd_runtime_microblock_batch_collect_txns( &block_info->microblock_batch_infos[i], out_txns );
2297 0 : out_txns += txns_collected;
2298 0 : }
2299 :
2300 0 : return block_info->txn_cnt;
2301 0 : }
2302 :
2303 : /******************************************************************************/
2304 : /* Genesis */
2305 : /*******************************************************************************/
2306 :
2307 : static void
2308 : fd_runtime_init_program( fd_exec_slot_ctx_t * slot_ctx,
2309 0 : fd_spad_t * runtime_spad ) {
2310 0 : fd_sysvar_recent_hashes_init( slot_ctx );
2311 0 : fd_sysvar_clock_init( slot_ctx );
2312 0 : fd_sysvar_slot_history_init( slot_ctx, runtime_spad );
2313 0 : fd_sysvar_slot_hashes_init( slot_ctx, runtime_spad );
2314 0 : fd_sysvar_epoch_schedule_init( slot_ctx );
2315 0 : fd_sysvar_rent_init( slot_ctx );
2316 0 : fd_sysvar_stake_history_init( slot_ctx );
2317 0 : fd_sysvar_last_restart_slot_init( slot_ctx );
2318 :
2319 0 : fd_builtin_programs_init( slot_ctx );
2320 0 : fd_stake_program_config_init( slot_ctx );
2321 0 : }
2322 :
2323 : static void
2324 : fd_runtime_init_bank_from_genesis( fd_exec_slot_ctx_t * slot_ctx,
2325 : fd_genesis_solana_t const * genesis_block,
2326 : fd_hash_t const * genesis_hash,
2327 0 : fd_spad_t * runtime_spad ) {
2328 :
2329 0 : fd_bank_poh_set( slot_ctx->bank, *genesis_hash );
2330 :
2331 0 : fd_hash_t * bank_hash = fd_bank_bank_hash_modify( slot_ctx->bank );
2332 0 : memset( bank_hash->hash, 0, FD_SHA256_HASH_SZ );
2333 :
2334 0 : fd_poh_config_t const * poh = &genesis_block->poh_config;
2335 0 : uint128 target_tick_duration = ((uint128)poh->target_tick_duration.seconds * 1000000000UL + (uint128)poh->target_tick_duration.nanoseconds);
2336 :
2337 0 : fd_bank_epoch_schedule_set( slot_ctx->bank, genesis_block->epoch_schedule );
2338 :
2339 0 : fd_bank_rent_set( slot_ctx->bank, genesis_block->rent );
2340 :
2341 0 : fd_bank_block_height_set( slot_ctx->bank, 0UL );
2342 :
2343 0 : fd_bank_inflation_set( slot_ctx->bank, genesis_block->inflation );
2344 :
2345 0 : {
2346 : /* FIXME Why is there a previous blockhash at genesis? Why is the
2347 : last_hash field an option type in Agave, if even the first
2348 : real block has a previous blockhash? */
2349 0 : ulong seed; FD_TEST( fd_rng_secure( &seed, sizeof(ulong) ) );
2350 0 : fd_blockhashes_t * bhq = fd_blockhashes_init( fd_bank_block_hash_queue_modify( slot_ctx->bank ), seed );
2351 0 : fd_blockhash_info_t * info = fd_blockhashes_push_new( bhq, genesis_hash );
2352 0 : info->fee_calculator.lamports_per_signature = 0UL;
2353 0 : }
2354 :
2355 0 : fd_bank_fee_rate_governor_set( slot_ctx->bank, genesis_block->fee_rate_governor );
2356 :
2357 0 : fd_bank_lamports_per_signature_set( slot_ctx->bank, 0UL );
2358 :
2359 0 : fd_bank_prev_lamports_per_signature_set( slot_ctx->bank, 0UL );
2360 :
2361 0 : fd_bank_max_tick_height_set( slot_ctx->bank, genesis_block->ticks_per_slot * (fd_bank_slot_get( slot_ctx->bank ) + 1) );
2362 :
2363 0 : fd_bank_hashes_per_tick_set( slot_ctx->bank, !!poh->hashes_per_tick ? poh->hashes_per_tick : 0UL );
2364 :
2365 0 : fd_bank_ns_per_slot_set( slot_ctx->bank, target_tick_duration * genesis_block->ticks_per_slot );
2366 :
2367 0 : fd_bank_ticks_per_slot_set( slot_ctx->bank, genesis_block->ticks_per_slot );
2368 :
2369 0 : fd_bank_genesis_creation_time_set( slot_ctx->bank, genesis_block->creation_time );
2370 :
2371 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 );
2372 :
2373 0 : fd_bank_signature_count_set( slot_ctx->bank, 0UL );
2374 :
2375 : /* Derive epoch stakes */
2376 :
2377 0 : fd_stakes_global_t * stakes_global = fd_bank_stakes_locking_modify( slot_ctx->bank );
2378 :
2379 0 : uchar * vacc_pool_mem = (uchar *)fd_ulong_align_up( (ulong)stakes_global + sizeof(fd_stakes_global_t), fd_vote_accounts_pair_global_t_map_align() );
2380 0 : fd_vote_accounts_pair_global_t_mapnode_t * vacc_pool = fd_vote_accounts_pair_global_t_map_join( fd_vote_accounts_pair_global_t_map_new( vacc_pool_mem, 5000UL ) );
2381 0 : fd_vote_accounts_pair_global_t_mapnode_t * vacc_root = NULL;
2382 :
2383 :
2384 0 : uchar * sacc_pool_mem = (uchar *)fd_ulong_align_up( (ulong)vacc_pool + fd_vote_accounts_pair_global_t_map_footprint( 5000UL ), fd_delegation_pair_t_map_align() );
2385 0 : fd_delegation_pair_t_mapnode_t * sacc_pool = fd_delegation_pair_t_map_join( fd_delegation_pair_t_map_new( sacc_pool_mem, 5000UL ) );
2386 0 : fd_delegation_pair_t_mapnode_t * sacc_root = NULL;
2387 :
2388 0 : fd_acc_lamports_t capitalization = 0UL;
2389 :
2390 0 : fd_features_t * features = fd_bank_features_modify( slot_ctx->bank );
2391 0 : FD_FEATURE_SET_ACTIVE(features, accounts_lt_hash, 0);
2392 0 : FD_FEATURE_SET_ACTIVE(features, remove_accounts_delta_hash, 0);
2393 :
2394 0 : for( ulong i=0UL; i<genesis_block->accounts_len; i++ ) {
2395 0 : fd_pubkey_account_pair_t const * acc = &genesis_block->accounts[i];
2396 0 : capitalization = fd_ulong_sat_add( capitalization, acc->account.lamports );
2397 :
2398 0 : if( !memcmp(acc->account.owner.key, fd_solana_vote_program_id.key, sizeof(fd_pubkey_t)) ) {
2399 : /* Vote Program Account */
2400 0 : fd_vote_accounts_pair_global_t_mapnode_t * node = fd_vote_accounts_pair_global_t_map_acquire(vacc_pool);
2401 0 : FD_TEST( node );
2402 :
2403 0 : fd_memcpy(node->elem.key.key, acc->key.key, sizeof(fd_pubkey_t));
2404 0 : node->elem.stake = acc->account.lamports;
2405 0 : node->elem.value = (fd_solana_account_global_t){
2406 0 : .lamports = acc->account.lamports,
2407 0 : .data_len = acc->account.data_len,
2408 0 : .data_offset = 0UL, /* FIXME: remove this field from the cache altogether. */
2409 0 : .owner = acc->account.owner,
2410 0 : .executable = acc->account.executable,
2411 0 : .rent_epoch = acc->account.rent_epoch
2412 0 : };
2413 0 : fd_solana_account_data_update( &node->elem.value, acc->account.data );
2414 :
2415 0 : fd_vote_accounts_pair_global_t_map_insert( vacc_pool, &vacc_root, node );
2416 :
2417 0 : FD_LOG_INFO(( "Adding genesis vote account: key=%s stake=%lu",
2418 0 : FD_BASE58_ENC_32_ALLOCA( node->elem.key.key ),
2419 0 : node->elem.stake ));
2420 0 : } else if( !memcmp( acc->account.owner.key, fd_solana_stake_program_id.key, sizeof(fd_pubkey_t) ) ) {
2421 : /* stake program account */
2422 0 : fd_stake_state_v2_t stake_state = {0};
2423 0 : fd_account_meta_t meta = { .dlen = acc->account.data_len };
2424 0 : FD_TXN_ACCOUNT_DECL( stake_account );
2425 0 : fd_txn_account_init_from_meta_and_data_mutable( stake_account, &meta, acc->account.data );
2426 0 : FD_TEST( fd_stake_get_state( stake_account, &stake_state ) == 0 );
2427 0 : if( !stake_state.inner.stake.stake.delegation.stake ) {
2428 0 : continue;
2429 0 : }
2430 0 : fd_delegation_pair_t_mapnode_t query_node = {0};
2431 0 : fd_memcpy(&query_node.elem.account, acc->key.key, sizeof(fd_pubkey_t));
2432 0 : fd_delegation_pair_t_mapnode_t * node = fd_delegation_pair_t_map_find( sacc_pool, sacc_root, &query_node );
2433 :
2434 0 : if( !node ) {
2435 0 : node = fd_delegation_pair_t_map_acquire( sacc_pool );
2436 0 : fd_memcpy( &node->elem.account, acc->key.key, sizeof(fd_pubkey_t) );
2437 0 : node->elem.delegation = stake_state.inner.stake.stake.delegation;
2438 0 : fd_delegation_pair_t_map_insert( sacc_pool, &sacc_root, node );
2439 0 : } else {
2440 0 : fd_memcpy( &node->elem.account, acc->key.key, sizeof(fd_pubkey_t) );
2441 0 : node->elem.delegation = stake_state.inner.stake.stake.delegation;
2442 0 : }
2443 0 : } else if( !memcmp(acc->account.owner.key, fd_solana_feature_program_id.key, sizeof(fd_pubkey_t)) ) {
2444 : /* Feature Account */
2445 :
2446 : /* Scan list of feature IDs to resolve address => feature offset */
2447 0 : fd_feature_id_t const *found = NULL;
2448 0 : for( fd_feature_id_t const * id = fd_feature_iter_init();
2449 0 : !fd_feature_iter_done( id );
2450 0 : id = fd_feature_iter_next( id ) ) {
2451 0 : if( !memcmp( acc->key.key, id->id.key, sizeof(fd_pubkey_t) ) ) {
2452 0 : found = id;
2453 0 : break;
2454 0 : }
2455 0 : }
2456 :
2457 0 : if( found ) {
2458 : /* Load feature activation */
2459 0 : FD_SPAD_FRAME_BEGIN( runtime_spad ) {
2460 0 : int err;
2461 0 : fd_feature_t * feature = fd_bincode_decode_spad(
2462 0 : feature, runtime_spad,
2463 0 : acc->account.data,
2464 0 : acc->account.data_len,
2465 0 : &err );
2466 0 : FD_TEST( err==FD_BINCODE_SUCCESS );
2467 :
2468 0 : fd_features_t * features = fd_bank_features_modify( slot_ctx->bank );
2469 0 : if( feature->has_activated_at ) {
2470 0 : FD_LOG_DEBUG(( "Feature %s activated at %lu (genesis)", FD_BASE58_ENC_32_ALLOCA( acc->key.key ), feature->activated_at ));
2471 0 : fd_features_set( features, found, feature->activated_at );
2472 0 : } else {
2473 0 : FD_LOG_DEBUG(( "Feature %s not activated (genesis)", FD_BASE58_ENC_32_ALLOCA( acc->key.key ) ));
2474 0 : fd_features_set( features, found, ULONG_MAX );
2475 0 : }
2476 0 : } FD_SPAD_FRAME_END;
2477 0 : }
2478 0 : }
2479 0 : }
2480 :
2481 0 : fd_vote_accounts_global_t * epoch_stakes = fd_bank_epoch_stakes_locking_modify( slot_ctx->bank );
2482 0 : uchar * pool_mem = (uchar *)fd_ulong_align_up( (ulong)epoch_stakes + sizeof(fd_vote_accounts_global_t), fd_vote_accounts_pair_t_map_align() );
2483 0 : fd_vote_accounts_pair_global_t_mapnode_t * vote_accounts_pool = fd_vote_accounts_pair_global_t_map_join( fd_vote_accounts_pair_global_t_map_new( pool_mem, 50000UL ) );
2484 0 : fd_vote_accounts_pair_global_t_mapnode_t * vote_accounts_root = NULL;
2485 :
2486 0 : uchar * epoch_stakes_vote_acc_region_curr = (uchar *)fd_ulong_align_up( (ulong)vote_accounts_pool + fd_vote_accounts_pair_global_t_map_footprint( 50000UL ), 8UL );
2487 :
2488 0 : fd_vote_accounts_global_t * next_epoch_stakes = fd_bank_next_epoch_stakes_locking_modify( slot_ctx->bank );
2489 0 : uchar * next_pool_mem = (uchar *)fd_ulong_align_up( (ulong)next_epoch_stakes + sizeof(fd_vote_accounts_global_t), fd_vote_accounts_pair_t_map_align() );
2490 0 : fd_vote_accounts_pair_global_t_mapnode_t * next_pool = fd_vote_accounts_pair_global_t_map_join( fd_vote_accounts_pair_global_t_map_new( next_pool_mem, 50000UL ) );
2491 0 : fd_vote_accounts_pair_global_t_mapnode_t * next_root = NULL;
2492 :
2493 0 : uchar * next_epoch_stakes_acc_region_curr = (uchar *)fd_ulong_align_up( (ulong)next_pool + fd_vote_accounts_pair_global_t_map_footprint( 50000UL ), 8UL );
2494 :
2495 0 : for( ulong i=0UL; i<genesis_block->accounts_len; i++ ) {
2496 0 : fd_pubkey_account_pair_t const * acc = &genesis_block->accounts[i];
2497 :
2498 0 : if( !memcmp( acc->account.owner.key, fd_solana_vote_program_id.key, sizeof(fd_pubkey_t) ) ) {
2499 :
2500 : /* Insert into the epoch_stakes vote accounts map */
2501 0 : fd_vote_accounts_pair_global_t_mapnode_t * e = fd_vote_accounts_pair_global_t_map_acquire( vote_accounts_pool );
2502 0 : FD_TEST( e );
2503 0 : e->elem.key = acc->key;
2504 0 : e->elem.stake = acc->account.lamports;
2505 0 : e->elem.value = (fd_solana_account_global_t){
2506 0 : .lamports = acc->account.lamports,
2507 0 : .data_len = acc->account.data_len,
2508 0 : .data_offset = 0UL, /* FIXME: remove this field from the cache altogether. */
2509 0 : .owner = acc->account.owner,
2510 0 : .executable = acc->account.executable,
2511 0 : .rent_epoch = acc->account.rent_epoch
2512 0 : };
2513 :
2514 0 : memcpy( epoch_stakes_vote_acc_region_curr, acc->account.data, acc->account.data_len );
2515 0 : e->elem.value.data_offset = (ulong)(epoch_stakes_vote_acc_region_curr - (uchar *)&e->elem.value);
2516 0 : epoch_stakes_vote_acc_region_curr += acc->account.data_len;
2517 :
2518 0 : fd_vote_accounts_pair_global_t_map_insert( vote_accounts_pool, &vote_accounts_root, e );
2519 :
2520 : /* Insert into the next_epoch_stakes vote accounts map */
2521 : /* FIXME: is this correct? */
2522 0 : fd_vote_accounts_pair_global_t_mapnode_t * next_e = fd_vote_accounts_pair_global_t_map_acquire( next_pool );
2523 0 : FD_TEST( next_e );
2524 0 : next_e->elem.key = acc->key;
2525 0 : next_e->elem.stake = acc->account.lamports;
2526 0 : next_e->elem.value = (fd_solana_account_global_t){
2527 0 : .lamports = acc->account.lamports,
2528 0 : .data_len = acc->account.data_len,
2529 0 : .data_offset = 0UL, /* FIXME: remove this field from the cache altogether. */
2530 0 : .owner = acc->account.owner,
2531 0 : .executable = acc->account.executable,
2532 0 : .rent_epoch = acc->account.rent_epoch
2533 0 : };
2534 :
2535 0 : memcpy( next_epoch_stakes_acc_region_curr, acc->account.data, acc->account.data_len );
2536 0 : next_e->elem.value.data_offset = (ulong)(next_epoch_stakes_acc_region_curr - (uchar *)&next_e->elem.value);
2537 0 : next_epoch_stakes_acc_region_curr += acc->account.data_len;
2538 :
2539 0 : fd_vote_accounts_pair_global_t_map_insert( next_pool, &next_root, next_e );
2540 0 : }
2541 :
2542 0 : }
2543 :
2544 0 : for( fd_delegation_pair_t_mapnode_t *n = fd_delegation_pair_t_map_minimum( sacc_pool, sacc_root );
2545 0 : n;
2546 0 : n = fd_delegation_pair_t_map_successor( sacc_pool, n )) {
2547 0 : fd_vote_accounts_pair_global_t_mapnode_t query_voter = {0};
2548 0 : query_voter.elem.key = n->elem.delegation.voter_pubkey;
2549 :
2550 0 : fd_vote_accounts_pair_global_t_mapnode_t * voter = fd_vote_accounts_pair_global_t_map_find( vacc_pool, vacc_root, &query_voter );
2551 :
2552 0 : if( !!voter ) {
2553 0 : voter->elem.stake = fd_ulong_sat_add( voter->elem.stake, n->elem.delegation.stake );
2554 0 : }
2555 0 : }
2556 :
2557 0 : fd_vote_accounts_vote_accounts_pool_update( epoch_stakes, vote_accounts_pool );
2558 0 : fd_vote_accounts_vote_accounts_root_update( epoch_stakes, vote_accounts_root );
2559 :
2560 :
2561 0 : fd_vote_accounts_vote_accounts_pool_update( next_epoch_stakes, next_pool );
2562 0 : fd_vote_accounts_vote_accounts_root_update( next_epoch_stakes, next_root );
2563 :
2564 0 : fd_bank_epoch_stakes_end_locking_modify( slot_ctx->bank );
2565 :
2566 0 : fd_bank_next_epoch_stakes_end_locking_modify( slot_ctx->bank );
2567 :
2568 :
2569 :
2570 0 : stakes_global->epoch = 0UL;
2571 0 : stakes_global->unused = 0UL;
2572 :
2573 0 : fd_vote_accounts_vote_accounts_pool_update( &stakes_global->vote_accounts, vacc_pool );
2574 0 : fd_vote_accounts_vote_accounts_root_update( &stakes_global->vote_accounts, vacc_root );
2575 0 : fd_stakes_stake_delegations_pool_update( stakes_global, sacc_pool );
2576 0 : fd_stakes_stake_delegations_root_update( stakes_global, sacc_root );
2577 0 : fd_bank_stakes_end_locking_modify( slot_ctx->bank );
2578 :
2579 0 : fd_bank_capitalization_set( slot_ctx->bank, capitalization );
2580 :
2581 0 : fd_clock_timestamp_votes_global_t * clock_timestamp_votes = fd_bank_clock_timestamp_votes_locking_modify( slot_ctx->bank );
2582 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() );
2583 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 ) );
2584 0 : clock_timestamp_votes->votes_pool_offset = (ulong)fd_clock_timestamp_vote_t_map_leave( clock_pool) - (ulong)clock_timestamp_votes;
2585 0 : clock_timestamp_votes->votes_root_offset = 0UL;
2586 0 : fd_bank_clock_timestamp_votes_end_locking_modify( slot_ctx->bank );
2587 0 : }
2588 :
2589 : static int
2590 : fd_runtime_process_genesis_block( fd_exec_slot_ctx_t * slot_ctx,
2591 : fd_capture_ctx_t * capture_ctx,
2592 0 : fd_spad_t * runtime_spad ) {
2593 :
2594 :
2595 0 : fd_hash_t * poh = fd_bank_poh_modify( slot_ctx->bank );
2596 0 : ulong hashcnt_per_slot = fd_bank_hashes_per_tick_get( slot_ctx->bank ) * fd_bank_ticks_per_slot_get( slot_ctx->bank );
2597 0 : while( hashcnt_per_slot-- ) {
2598 0 : fd_sha256_hash( poh->hash, sizeof(fd_hash_t), poh->hash );
2599 0 : }
2600 :
2601 0 : fd_bank_execution_fees_set( slot_ctx->bank, 0UL );
2602 :
2603 0 : fd_bank_priority_fees_set( slot_ctx->bank, 0UL );
2604 :
2605 0 : fd_bank_signature_count_set( slot_ctx->bank, 0UL );
2606 :
2607 0 : fd_bank_txn_count_set( slot_ctx->bank, 0UL );
2608 :
2609 0 : fd_bank_failed_txn_count_set( slot_ctx->bank, 0UL );
2610 :
2611 0 : fd_bank_nonvote_failed_txn_count_set( slot_ctx->bank, 0UL );
2612 :
2613 0 : fd_bank_total_compute_units_used_set( slot_ctx->bank, 0UL );
2614 :
2615 0 : fd_runtime_init_program( slot_ctx, runtime_spad );
2616 :
2617 0 : fd_sysvar_slot_history_update( slot_ctx, runtime_spad );
2618 :
2619 0 : fd_runtime_update_leaders( slot_ctx->bank, 0, runtime_spad );
2620 :
2621 0 : fd_runtime_freeze( slot_ctx );
2622 :
2623 : /* sort and update bank hash */
2624 0 : fd_hash_t * bank_hash = fd_bank_bank_hash_modify( slot_ctx->bank );
2625 0 : int result = fd_update_hash_bank_tpool( slot_ctx,
2626 0 : capture_ctx,
2627 0 : bank_hash,
2628 0 : 0UL,
2629 0 : NULL,
2630 0 : runtime_spad );
2631 :
2632 0 : if( FD_UNLIKELY( result != FD_EXECUTOR_INSTR_SUCCESS ) ) {
2633 0 : FD_LOG_ERR(( "Failed to update bank hash with error=%d", result ));
2634 0 : }
2635 :
2636 0 : return FD_RUNTIME_EXECUTE_SUCCESS;
2637 0 : }
2638 :
2639 : void
2640 : fd_runtime_read_genesis( fd_exec_slot_ctx_t * slot_ctx,
2641 : char const * genesis_filepath,
2642 : uchar is_snapshot,
2643 : fd_capture_ctx_t * capture_ctx,
2644 0 : fd_spad_t * runtime_spad ) {
2645 :
2646 0 : if( strlen( genesis_filepath ) == 0 ) {
2647 0 : return;
2648 0 : }
2649 :
2650 0 : struct stat sbuf;
2651 0 : if( FD_UNLIKELY( stat( genesis_filepath, &sbuf) < 0 ) ) {
2652 0 : FD_LOG_ERR(( "cannot open %s : %s", genesis_filepath, strerror(errno) ));
2653 0 : }
2654 0 : int fd = open( genesis_filepath, O_RDONLY );
2655 0 : if( FD_UNLIKELY( fd < 0 ) ) {
2656 0 : FD_LOG_ERR(("cannot open %s : %s", genesis_filepath, strerror(errno)));
2657 0 : }
2658 :
2659 0 : fd_genesis_solana_t * genesis_block;
2660 0 : fd_hash_t genesis_hash;
2661 :
2662 : /* NOTE: These genesis decode spad allocs persist through the lifetime of fd_runtime,
2663 : even though they aren't used outside of this function. This is because
2664 : fd_runtime_init_bank_from_genesis, which depends on the genesis_block, initializes
2665 : a bunch of structures on spad that need to persist throughout fd_runtime. Using a bump
2666 : allocator does not let us free memory lower in the stack without freeing everything
2667 : above it (in a meaningful way).
2668 :
2669 : FIXME: Use spad frames here once the fd_runtime structures initialized here are no
2670 : longer spad-backed. */
2671 :
2672 0 : uchar * buf = fd_spad_alloc( runtime_spad, alignof(ulong), (ulong)sbuf.st_size );
2673 0 : ulong sz = 0UL;
2674 0 : int res = fd_io_read( fd, buf, (ulong)sbuf.st_size, (ulong)sbuf.st_size, &sz );
2675 0 : FD_TEST( res==0 );
2676 0 : FD_TEST( sz==(ulong)sbuf.st_size );
2677 0 : close( fd );
2678 :
2679 0 : int err;
2680 0 : genesis_block = fd_bincode_decode_spad(
2681 0 : genesis_solana, runtime_spad, buf, sz, &err );
2682 0 : if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) {
2683 0 : FD_LOG_ERR(( "fd_genesis_solana_decode_footprint failed (%d)", err ));
2684 0 : }
2685 :
2686 : // The hash is generated from the raw data... don't mess with this..
2687 0 : fd_sha256_hash( buf, sz, genesis_hash.uc );
2688 :
2689 0 : fd_hash_t * genesis_hash_bm = fd_bank_genesis_hash_modify( slot_ctx->bank );
2690 0 : fd_memcpy( genesis_hash_bm, buf, sizeof(fd_hash_t) );
2691 :
2692 0 : if( !is_snapshot ) {
2693 : /* Create a new Funk transaction for slot 0 */
2694 0 : fd_funk_txn_start_write( slot_ctx->funk );
2695 0 : fd_funk_txn_xid_t xid = { 0 };
2696 0 : xid.ul[1] = 0UL;
2697 0 : xid.ul[0] = 0UL;
2698 0 : slot_ctx->funk_txn = fd_funk_txn_prepare( slot_ctx->funk, NULL, &xid, 1 );
2699 0 : fd_funk_txn_end_write( slot_ctx->funk );
2700 :
2701 0 : fd_runtime_init_bank_from_genesis( slot_ctx,
2702 0 : genesis_block,
2703 0 : &genesis_hash,
2704 0 : runtime_spad );
2705 :
2706 0 : FD_LOG_DEBUG(( "start genesis accounts - count: %lu", genesis_block->accounts_len ));
2707 :
2708 0 : for( ulong i=0; i<genesis_block->accounts_len; i++ ) {
2709 0 : fd_pubkey_account_pair_t * a = &genesis_block->accounts[i];
2710 :
2711 0 : FD_TXN_ACCOUNT_DECL( rec );
2712 :
2713 0 : int err = fd_txn_account_init_from_funk_mutable( rec,
2714 0 : &a->key,
2715 0 : slot_ctx->funk,
2716 0 : slot_ctx->funk_txn,
2717 0 : 1, /* do_create */
2718 0 : a->account.data_len );
2719 :
2720 0 : if( FD_UNLIKELY( err ) ) {
2721 0 : FD_LOG_ERR(( "fd_txn_account_init_from_funk_mutable failed (%d)", err ));
2722 0 : }
2723 :
2724 0 : rec->vt->set_data( rec, a->account.data, a->account.data_len );
2725 0 : rec->vt->set_lamports( rec, a->account.lamports );
2726 0 : rec->vt->set_rent_epoch( rec, a->account.rent_epoch );
2727 0 : rec->vt->set_executable( rec, a->account.executable );
2728 0 : rec->vt->set_owner( rec, &a->account.owner );
2729 :
2730 0 : fd_txn_account_mutable_fini( rec, slot_ctx->funk, slot_ctx->funk_txn );
2731 0 : }
2732 :
2733 0 : FD_LOG_DEBUG(( "end genesis accounts" ));
2734 :
2735 0 : FD_LOG_DEBUG(( "native instruction processors - count: %lu", genesis_block->native_instruction_processors_len ));
2736 :
2737 0 : for( ulong i=0UL; i < genesis_block->native_instruction_processors_len; i++ ) {
2738 0 : fd_string_pubkey_pair_t * a = &genesis_block->native_instruction_processors[i];
2739 0 : fd_write_builtin_account( slot_ctx, a->pubkey, (const char *) a->string, a->string_len );
2740 0 : }
2741 :
2742 0 : fd_features_restore( slot_ctx, runtime_spad );
2743 :
2744 0 : int err = fd_runtime_process_genesis_block( slot_ctx, capture_ctx, runtime_spad );
2745 0 : if( FD_UNLIKELY( err ) ) {
2746 0 : FD_LOG_ERR(( "Genesis slot 0 execute failed with error %d", err ));
2747 0 : }
2748 0 : }
2749 :
2750 :
2751 0 : fd_account_keys_global_t * stake_account_keys = fd_bank_stake_account_keys_locking_modify( slot_ctx->bank );
2752 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() );
2753 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 ) );
2754 0 : fd_account_keys_pair_t_mapnode_t * stake_account_keys_root = NULL;
2755 :
2756 0 : fd_account_keys_account_keys_pool_update( stake_account_keys, stake_account_keys_pool );
2757 0 : fd_account_keys_account_keys_root_update( stake_account_keys, stake_account_keys_root );
2758 0 : fd_bank_stake_account_keys_end_locking_modify( slot_ctx->bank );
2759 :
2760 0 : fd_account_keys_global_t * vote_account_keys = fd_bank_vote_account_keys_locking_modify( slot_ctx->bank );
2761 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() );
2762 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 ) );
2763 0 : fd_account_keys_pair_t_mapnode_t * vote_account_keys_root = NULL;
2764 :
2765 0 : fd_account_keys_account_keys_pool_update( vote_account_keys, vote_account_keys_pool );
2766 0 : fd_account_keys_account_keys_root_update( vote_account_keys, vote_account_keys_root );
2767 :
2768 0 : fd_bank_vote_account_keys_end_locking_modify( slot_ctx->bank );
2769 0 : }
2770 :
2771 : /******************************************************************************/
2772 : /* Offline Replay */
2773 : /******************************************************************************/
2774 :
2775 : /* As a note, currently offline and live replay of transactions has differences
2776 : with regards to how the execution environment is setup. These are helpers
2777 : used to emulate this behavior */
2778 :
2779 : struct fd_poh_verification_info {
2780 : fd_microblock_info_t const * microblock_info;
2781 : fd_hash_t const * in_poh_hash;
2782 : int success;
2783 : };
2784 : typedef struct fd_poh_verification_info fd_poh_verification_info_t;
2785 :
2786 : int
2787 : fd_runtime_block_execute( fd_exec_slot_ctx_t * slot_ctx,
2788 : fd_capture_ctx_t * capture_ctx,
2789 : fd_runtime_block_info_t const * block_info,
2790 0 : fd_spad_t * runtime_spad ) {
2791 :
2792 0 : if ( capture_ctx != NULL && capture_ctx->capture && fd_bank_slot_get( slot_ctx->bank )>=capture_ctx->solcap_start_slot ) {
2793 0 : fd_solcap_writer_set_slot( capture_ctx->capture, fd_bank_slot_get( slot_ctx->bank ) );
2794 0 : }
2795 :
2796 0 : long block_execute_time = -fd_log_wallclock();
2797 :
2798 0 : int res = fd_runtime_block_execute_prepare( slot_ctx, runtime_spad );
2799 0 : if( res != FD_RUNTIME_EXECUTE_SUCCESS ) {
2800 0 : return res;
2801 0 : }
2802 :
2803 0 : ulong txn_cnt = block_info->txn_cnt;
2804 0 : fd_txn_p_t * txn_ptrs = fd_spad_alloc( runtime_spad, alignof(fd_txn_p_t), txn_cnt * sizeof(fd_txn_p_t) );
2805 :
2806 0 : fd_runtime_block_collect_txns( block_info, txn_ptrs );
2807 :
2808 : /* Initialize the cost tracker when the feature is active */
2809 0 : fd_cost_tracker_t * cost_tracker = fd_spad_alloc( runtime_spad, FD_COST_TRACKER_ALIGN, sizeof(fd_cost_tracker_t) );
2810 0 : fd_cost_tracker_init( cost_tracker, runtime_spad );
2811 :
2812 : /* We want to emulate microblock-by-microblock execution */
2813 0 : ulong to_exec_idx = 0UL;
2814 0 : for( ulong i=0UL; i<block_info->microblock_batch_cnt; i++ ) {
2815 0 : for( ulong j=0UL; j<block_info->microblock_batch_infos[i].microblock_cnt; j++ ) {
2816 0 : ulong txn_cnt = block_info->microblock_batch_infos[i].microblock_infos[j].microblock.hdr->txn_cnt;
2817 0 : fd_txn_p_t * mblock_txn_ptrs = &txn_ptrs[ to_exec_idx ];
2818 0 : ulong mblock_txn_cnt = txn_cnt;
2819 0 : to_exec_idx += txn_cnt;
2820 :
2821 : /* UPDATE */
2822 :
2823 0 : if( !mblock_txn_cnt ) continue;
2824 :
2825 : /* Reverify programs for this epoch if needed */
2826 0 : for( ulong txn_idx=0UL; txn_idx<mblock_txn_cnt; txn_idx++ ) {
2827 0 : fd_runtime_update_program_cache( slot_ctx, &mblock_txn_ptrs[txn_idx], runtime_spad );
2828 0 : }
2829 :
2830 0 : res = fd_runtime_process_txns_in_microblock_stream_sequential( slot_ctx,
2831 0 : capture_ctx,
2832 0 : mblock_txn_ptrs,
2833 0 : mblock_txn_cnt,
2834 0 : runtime_spad,
2835 0 : cost_tracker );
2836 0 : if( FD_UNLIKELY( res!=FD_RUNTIME_EXECUTE_SUCCESS ) ) {
2837 0 : return res;
2838 0 : }
2839 0 : }
2840 0 : }
2841 :
2842 0 : long block_finalize_time = -fd_log_wallclock();
2843 :
2844 0 : res = fd_runtime_block_execute_finalize_sequential( slot_ctx,
2845 0 : capture_ctx,
2846 0 : block_info,
2847 0 : runtime_spad );
2848 0 : if( FD_UNLIKELY( res!=FD_RUNTIME_EXECUTE_SUCCESS ) ) {
2849 0 : return res;
2850 0 : }
2851 :
2852 0 : block_finalize_time += fd_log_wallclock();
2853 0 : double block_finalize_time_ms = (double)block_finalize_time * 1e-6;
2854 0 : FD_LOG_INFO(( "finalized block successfully - slot: %lu, elapsed: %6.6f ms", fd_bank_slot_get( slot_ctx->bank ), block_finalize_time_ms ));
2855 :
2856 0 : block_execute_time += fd_log_wallclock();
2857 0 : double block_execute_time_ms = (double)block_execute_time * 1e-6;
2858 :
2859 0 : FD_LOG_INFO(( "executed block successfully - slot: %lu, elapsed: %6.6f ms", fd_bank_slot_get( slot_ctx->bank ), block_execute_time_ms ));
2860 :
2861 0 : return FD_RUNTIME_EXECUTE_SUCCESS;
2862 0 : }
2863 :
2864 : void
2865 : fd_runtime_block_pre_execute_process_new_epoch( fd_exec_slot_ctx_t * slot_ctx,
2866 : fd_capture_ctx_t * capture_ctx,
2867 : fd_spad_t * runtime_spad,
2868 0 : int * is_epoch_boundary ) {
2869 :
2870 : /* Update block height. */
2871 0 : fd_bank_block_height_set( slot_ctx->bank, fd_bank_block_height_get( slot_ctx->bank ) + 1UL );
2872 :
2873 0 : ulong const slot = fd_bank_slot_get( slot_ctx->bank );
2874 0 : if( slot != 0UL ) {
2875 0 : fd_epoch_schedule_t const * epoch_schedule = fd_bank_epoch_schedule_query( slot_ctx->bank );
2876 :
2877 0 : ulong prev_epoch = fd_slot_to_epoch( epoch_schedule, fd_bank_parent_slot_get( slot_ctx->bank ), NULL );
2878 0 : ulong slot_idx;
2879 0 : ulong new_epoch = fd_slot_to_epoch( epoch_schedule, slot, &slot_idx );
2880 0 : if( FD_UNLIKELY( slot_idx==1UL && new_epoch==0UL ) ) {
2881 : /* The block after genesis has a height of 1. */
2882 0 : fd_bank_block_height_set( slot_ctx->bank, 1UL );
2883 0 : }
2884 :
2885 0 : if( FD_UNLIKELY( prev_epoch<new_epoch || !slot_idx ) ) {
2886 0 : FD_LOG_DEBUG(( "Epoch boundary" ));
2887 : /* Epoch boundary! */
2888 0 : fd_runtime_process_new_epoch( slot_ctx,
2889 0 : capture_ctx,
2890 0 : new_epoch - 1UL,
2891 0 : runtime_spad );
2892 0 : *is_epoch_boundary = 1;
2893 0 : }
2894 0 : } else {
2895 0 : *is_epoch_boundary = 0;
2896 0 : }
2897 :
2898 0 : if( FD_LIKELY( fd_bank_slot_get( slot_ctx->bank )!=0UL ) ) {
2899 0 : fd_distribute_partitioned_epoch_rewards( slot_ctx, capture_ctx );
2900 0 : }
2901 0 : }
2902 :
2903 : /******************************************************************************/
2904 : /* Debugging Tools */
2905 : /******************************************************************************/
2906 :
2907 : void
2908 : fd_runtime_checkpt( fd_capture_ctx_t * capture_ctx,
2909 : fd_exec_slot_ctx_t * slot_ctx,
2910 0 : ulong slot ) {
2911 0 : int is_checkpt_freq = capture_ctx != NULL && slot % capture_ctx->checkpt_freq == 0;
2912 0 : int is_abort_slot = slot == ULONG_MAX;
2913 0 : if( !is_checkpt_freq && !is_abort_slot ) {
2914 0 : return;
2915 0 : }
2916 :
2917 0 : if( capture_ctx->checkpt_path != NULL ) {
2918 0 : if( !is_abort_slot ) {
2919 0 : FD_LOG_NOTICE(( "checkpointing at slot=%lu to file=%s", slot, capture_ctx->checkpt_path ));
2920 0 : } else {
2921 0 : FD_LOG_NOTICE(( "checkpointing after mismatch to file=%s", capture_ctx->checkpt_path ));
2922 0 : }
2923 :
2924 0 : unlink( capture_ctx->checkpt_path );
2925 0 : int err = fd_wksp_checkpt( fd_funk_wksp( slot_ctx->funk ), capture_ctx->checkpt_path, 0666, 0, NULL );
2926 0 : if ( err ) {
2927 0 : FD_LOG_ERR(( "backup failed: error %d", err ));
2928 0 : }
2929 0 : }
2930 0 : }
2931 :
2932 : int
2933 : fd_runtime_process_txns_in_microblock_stream_sequential( fd_exec_slot_ctx_t * slot_ctx,
2934 : fd_capture_ctx_t * capture_ctx,
2935 : fd_txn_p_t * txns,
2936 : ulong txn_cnt,
2937 : fd_spad_t * runtime_spad,
2938 0 : fd_cost_tracker_t * cost_tracker_opt ) {
2939 :
2940 0 : int res = 0;
2941 :
2942 0 : for( ulong i=0UL; i<txn_cnt; i++ ) {
2943 0 : txns[i].flags = FD_TXN_P_FLAGS_SANITIZE_SUCCESS;
2944 0 : }
2945 :
2946 0 : fd_execute_txn_task_info_t * task_infos = fd_spad_alloc( runtime_spad,
2947 0 : alignof(fd_execute_txn_task_info_t),
2948 0 : txn_cnt * sizeof(fd_execute_txn_task_info_t) );
2949 :
2950 0 : for( ulong i=0UL; i<txn_cnt; i++ ) {
2951 0 : task_infos[ i ].spad = runtime_spad;
2952 0 : task_infos[ i ].txn = &txns[ i ];
2953 0 : task_infos[ i ].txn_ctx = fd_spad_alloc( task_infos[ i ].spad,
2954 0 : FD_EXEC_TXN_CTX_ALIGN,
2955 0 : FD_EXEC_TXN_CTX_FOOTPRINT );
2956 0 : if( FD_UNLIKELY( !task_infos[ i ].txn_ctx ) ) {
2957 0 : FD_LOG_ERR(( "failed to allocate txn ctx" ));
2958 0 : }
2959 :
2960 0 : fd_runtime_prepare_and_execute_txn( slot_ctx,
2961 0 : &txns[ i ],
2962 0 : &task_infos[ i ],
2963 0 : runtime_spad,
2964 0 : capture_ctx );
2965 :
2966 0 : if( FD_UNLIKELY( !( task_infos[ i ].txn->flags & FD_TXN_P_FLAGS_EXECUTE_SUCCESS ) ) ) {
2967 0 : continue;
2968 0 : }
2969 :
2970 0 : fd_runtime_finalize_txn( slot_ctx->funk, slot_ctx->funk_txn, &task_infos[ i ], task_infos[ i ].txn_ctx->spad, slot_ctx->bank );
2971 :
2972 0 : if( cost_tracker_opt!=NULL ) {
2973 0 : fd_execute_txn_task_info_t const * task_info = &task_infos[ i ];
2974 0 : if( FD_UNLIKELY( !( task_info->txn->flags & FD_TXN_P_FLAGS_EXECUTE_SUCCESS ) ) ) continue;
2975 :
2976 0 : fd_exec_txn_ctx_t const * txn_ctx = task_info->txn_ctx;
2977 0 : fd_transaction_cost_t transaction_cost = fd_calculate_cost_for_executed_transaction( task_info->txn_ctx,
2978 0 : runtime_spad );
2979 :
2980 0 : res = fd_cost_tracker_try_add( cost_tracker_opt, txn_ctx, &transaction_cost );
2981 0 : if( FD_UNLIKELY( res ) ) {
2982 0 : FD_LOG_WARNING(( "Block cost limits exceeded for slot %lu", fd_bank_slot_get( slot_ctx->bank ) ));
2983 0 : break;
2984 0 : }
2985 0 : }
2986 0 : }
2987 :
2988 0 : return res;
2989 0 : }
2990 :
2991 : int
2992 : fd_runtime_block_execute_finalize_sequential( fd_exec_slot_ctx_t * slot_ctx,
2993 : fd_capture_ctx_t * capture_ctx,
2994 : fd_runtime_block_info_t const * block_info,
2995 0 : fd_spad_t * runtime_spad ) {
2996 :
2997 0 : FD_SPAD_FRAME_BEGIN( runtime_spad ) {
2998 :
2999 0 : fd_accounts_hash_task_data_t * task_data = NULL;
3000 :
3001 0 : fd_runtime_block_execute_finalize_start( slot_ctx, runtime_spad, &task_data, 1UL );
3002 :
3003 0 : if( task_data && task_data->info_sz > 0UL ) {
3004 0 : for( ulong i=0UL; i<task_data->info_sz; i++ ) {
3005 0 : fd_account_hash_task( task_data, i, i, &task_data->lthash_values[0], slot_ctx, 0UL,
3006 0 : 0UL, 0UL, 0UL, 0UL, 0UL, 0UL );
3007 0 : }
3008 0 : }
3009 :
3010 0 : fd_runtime_block_execute_finalize_finish( slot_ctx, capture_ctx, block_info, runtime_spad, task_data, 1UL );
3011 :
3012 0 : } FD_SPAD_FRAME_END;
3013 :
3014 0 : return 0;
3015 0 : }
|