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_hashes.h"
6 : #include "fd_runtime_err.h"
7 : #include "fd_runtime_init.h"
8 : #include "fd_pubkey_utils.h"
9 :
10 : #include "fd_executor.h"
11 : #include "sysvar/fd_sysvar_cache.h"
12 : #include "sysvar/fd_sysvar_clock.h"
13 : #include "sysvar/fd_sysvar_epoch_schedule.h"
14 : #include "sysvar/fd_sysvar_recent_hashes.h"
15 : #include "sysvar/fd_sysvar_stake_history.h"
16 :
17 : #include "../stakes/fd_stakes.h"
18 : #include "../rewards/fd_rewards.h"
19 :
20 : #include "context/fd_exec_txn_ctx.h"
21 :
22 : #include "program/fd_stake_program.h"
23 : #include "program/fd_builtin_programs.h"
24 : #include "program/fd_vote_program.h"
25 : #include "program/fd_program_cache.h"
26 : #include "program/fd_bpf_loader_program.h"
27 : #include "program/fd_address_lookup_table_program.h"
28 :
29 : #include "sysvar/fd_sysvar_clock.h"
30 : #include "sysvar/fd_sysvar_last_restart_slot.h"
31 : #include "sysvar/fd_sysvar_recent_hashes.h"
32 : #include "sysvar/fd_sysvar_rent.h"
33 : #include "sysvar/fd_sysvar_slot_hashes.h"
34 : #include "sysvar/fd_sysvar_slot_history.h"
35 :
36 : #include "tests/fd_dump_pb.h"
37 :
38 : #include "fd_system_ids.h"
39 : #include "../../disco/pack/fd_pack.h"
40 : #include "../../discof/replay/fd_exec.h"
41 :
42 : #include <unistd.h>
43 : #include <sys/stat.h>
44 : #include <sys/types.h>
45 : #include <errno.h>
46 : #include <fcntl.h>
47 :
48 : /******************************************************************************/
49 : /* Public Runtime Helpers */
50 : /******************************************************************************/
51 :
52 : int
53 0 : fd_runtime_should_use_vote_keyed_leader_schedule( fd_bank_t * bank ) {
54 : /* Agave uses an option type for their effective_epoch value. We
55 : represent None as ULONG_MAX and Some(value) as the value.
56 : https://github.com/anza-xyz/agave/blob/v2.3.1/runtime/src/bank.rs#L6149-L6165 */
57 0 : if( FD_FEATURE_ACTIVE_BANK( bank, enable_vote_address_leader_schedule ) ) {
58 : /* Return the first epoch if activated at genesis
59 : https://github.com/anza-xyz/agave/blob/v2.3.1/runtime/src/bank.rs#L6153-L6157 */
60 0 : ulong activation_slot = fd_bank_features_query( bank )->enable_vote_address_leader_schedule;
61 0 : if( activation_slot==0UL ) return 0;
62 :
63 : /* Calculate the epoch that the feature became activated in
64 : https://github.com/anza-xyz/agave/blob/v2.3.1/runtime/src/bank.rs#L6159-L6160 */
65 0 : fd_epoch_schedule_t const * epoch_schedule = fd_bank_epoch_schedule_query( bank );
66 0 : ulong activation_epoch = fd_slot_to_epoch( epoch_schedule, activation_slot, NULL );
67 :
68 : /* The effective epoch is the epoch immediately after the activation
69 : epoch.
70 : https://github.com/anza-xyz/agave/blob/v2.3.1/runtime/src/bank.rs#L6162-L6164 */
71 0 : ulong effective_epoch = activation_epoch + 1UL;
72 0 : ulong current_epoch = fd_bank_epoch_get( bank );
73 :
74 : /* https://github.com/anza-xyz/agave/blob/v2.3.1/runtime/src/bank.rs#L6167-L6170 */
75 0 : return !!( current_epoch >= effective_epoch );
76 0 : }
77 :
78 : /* ...The rest of the logic in this function either returns None or
79 : Some(false) so we will just return 0 by default. */
80 0 : return 0;
81 0 : }
82 :
83 : /*
84 : https://github.com/anza-xyz/agave/blob/v2.1.1/runtime/src/bank.rs#L1254-L1258
85 : https://github.com/anza-xyz/agave/blob/v2.1.1/runtime/src/bank.rs#L1749
86 : */
87 : int
88 : fd_runtime_compute_max_tick_height( ulong ticks_per_slot,
89 : ulong slot,
90 0 : ulong * out_max_tick_height /* out */ ) {
91 0 : ulong max_tick_height = 0UL;
92 0 : if( FD_LIKELY( ticks_per_slot > 0UL ) ) {
93 0 : ulong next_slot = fd_ulong_sat_add( slot, 1UL );
94 0 : if( FD_UNLIKELY( next_slot == slot ) ) {
95 0 : FD_LOG_WARNING(( "max tick height addition overflowed slot %lu ticks_per_slot %lu", slot, ticks_per_slot ));
96 0 : return -1;
97 0 : }
98 0 : if( FD_UNLIKELY( ULONG_MAX / ticks_per_slot < next_slot ) ) {
99 0 : FD_LOG_WARNING(( "max tick height multiplication overflowed slot %lu ticks_per_slot %lu", slot, ticks_per_slot ));
100 0 : return -1;
101 0 : }
102 0 : max_tick_height = fd_ulong_sat_mul( next_slot, ticks_per_slot );
103 0 : }
104 0 : *out_max_tick_height = max_tick_height;
105 0 : return FD_RUNTIME_EXECUTE_SUCCESS;
106 0 : }
107 :
108 : void
109 : fd_runtime_update_slots_per_epoch( fd_bank_t * bank,
110 0 : ulong slots_per_epoch ) {
111 0 : if( FD_LIKELY( slots_per_epoch == fd_bank_slots_per_epoch_get( bank ) ) ) {
112 0 : return;
113 0 : }
114 :
115 0 : fd_bank_slots_per_epoch_set( bank, slots_per_epoch );
116 0 : }
117 :
118 : void
119 : fd_runtime_update_leaders( fd_bank_t * bank,
120 : ulong slot,
121 0 : fd_spad_t * runtime_spad ) {
122 :
123 0 : FD_SPAD_FRAME_BEGIN( runtime_spad ) {
124 :
125 0 : fd_epoch_schedule_t const * epoch_schedule = fd_bank_epoch_schedule_query( bank );
126 :
127 0 : ulong epoch = fd_slot_to_epoch ( epoch_schedule, slot, NULL );
128 0 : ulong slot0 = fd_epoch_slot0 ( epoch_schedule, epoch );
129 0 : ulong slot_cnt = fd_epoch_slot_cnt( epoch_schedule, epoch );
130 :
131 0 : fd_vote_states_t const * vote_states_prev_prev = fd_bank_vote_states_prev_prev_locking_query( bank );
132 0 : ulong vote_acc_cnt = fd_vote_states_cnt( vote_states_prev_prev ) ;
133 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) );
134 0 : ulong stake_weight_cnt = fd_stake_weights_by_node( vote_states_prev_prev, epoch_weights );
135 0 : fd_bank_vote_states_prev_prev_end_locking_query( bank );
136 :
137 : /* Derive leader schedule */
138 :
139 0 : FD_LOG_INFO(( "stake_weight_cnt=%lu slot_cnt=%lu", stake_weight_cnt, slot_cnt ));
140 0 : ulong epoch_leaders_footprint = fd_epoch_leaders_footprint( stake_weight_cnt, slot_cnt );
141 0 : FD_LOG_INFO(( "epoch_leaders_footprint=%lu", epoch_leaders_footprint ));
142 0 : if( FD_LIKELY( epoch_leaders_footprint ) ) {
143 0 : if( FD_UNLIKELY( stake_weight_cnt>MAX_PUB_CNT ) ) {
144 0 : FD_LOG_ERR(( "Stake weight count exceeded max" ));
145 0 : }
146 0 : if( FD_UNLIKELY( slot_cnt>MAX_SLOTS_PER_EPOCH ) ) {
147 0 : FD_LOG_ERR(( "Slot count exceeeded max" ));
148 0 : }
149 :
150 0 : ulong vote_keyed_lsched = (ulong)fd_runtime_should_use_vote_keyed_leader_schedule( bank );
151 0 : void * epoch_leaders_mem = fd_bank_epoch_leaders_locking_modify( bank );
152 0 : fd_epoch_leaders_t * leaders = fd_epoch_leaders_join( fd_epoch_leaders_new(
153 0 : epoch_leaders_mem,
154 0 : epoch,
155 0 : slot0,
156 0 : slot_cnt,
157 0 : stake_weight_cnt,
158 0 : epoch_weights,
159 0 : 0UL,
160 0 : vote_keyed_lsched ) );
161 0 : if( FD_UNLIKELY( !leaders ) ) {
162 0 : FD_LOG_ERR(( "Unable to init and join fd_epoch_leaders" ));
163 0 : }
164 0 : fd_bank_epoch_leaders_end_locking_modify( bank );
165 0 : }
166 0 : } FD_SPAD_FRAME_END;
167 0 : }
168 :
169 : fd_funk_txn_t *
170 : fd_runtime_funk_txn_get( fd_funk_t * funk,
171 0 : ulong slot ) {
172 : /* Query the funk transaction for the given slot. */
173 0 : fd_funk_txn_map_t * txn_map = fd_funk_txn_map( funk );
174 0 : if( FD_UNLIKELY( !txn_map->map ) ) {
175 0 : FD_LOG_ERR(( "Could not find valid funk transaction map" ));
176 0 : }
177 0 : fd_funk_txn_xid_t xid = { .ul = { slot, slot } };
178 0 : fd_funk_txn_start_read( funk );
179 0 : fd_funk_txn_t * funk_txn = fd_funk_txn_query( &xid, txn_map );
180 0 : if( FD_UNLIKELY( !funk_txn ) ) {
181 0 : FD_LOG_ERR(( "Could not find valid funk transaction for slot %lu", slot ));
182 0 : }
183 0 : fd_funk_txn_end_read( funk );
184 0 : return funk_txn;
185 0 : }
186 :
187 : /******************************************************************************/
188 : /* Various Private Runtime Helpers */
189 : /******************************************************************************/
190 :
191 : /* fee to be deposited should be > 0
192 : Returns 0 if validation succeeds
193 : Returns the amount to burn(==fee) on failure */
194 : static ulong
195 : fd_runtime_validate_fee_collector( fd_bank_t * bank,
196 : fd_txn_account_t const * collector,
197 0 : ulong fee ) {
198 0 : if( FD_UNLIKELY( fee<=0UL ) ) {
199 0 : FD_LOG_ERR(( "expected fee(%lu) to be >0UL", fee ));
200 0 : }
201 :
202 0 : if( FD_UNLIKELY( memcmp( fd_txn_account_get_owner( collector ), fd_solana_system_program_id.key, sizeof(fd_pubkey_t) ) ) ) {
203 0 : FD_BASE58_ENCODE_32_BYTES( collector->pubkey->key, _out_key );
204 0 : FD_LOG_WARNING(( "cannot pay a non-system-program owned account (%s)", _out_key ));
205 0 : return fee;
206 0 : }
207 :
208 : /* https://github.com/anza-xyz/agave/blob/v1.18.23/runtime/src/bank/fee_distribution.rs#L111
209 : https://github.com/anza-xyz/agave/blob/v1.18.23/runtime/src/accounts/account_rent_state.rs#L39
210 : In agave's fee deposit code, rent state transition check logic is as follows:
211 : The transition is NOT allowed iff
212 : === BEGIN
213 : the post deposit account is rent paying AND the pre deposit account is not rent paying
214 : OR
215 : 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)
216 : === END
217 : post_data_size == pre_data_size is always true during fee deposit.
218 : However, post_lamports > pre_lamports because we are paying a >0 amount.
219 : So, the above reduces down to
220 : === BEGIN
221 : the post deposit account is rent paying AND the pre deposit account is not rent paying
222 : OR
223 : the post deposit account is rent paying AND the pre deposit account is rent paying AND TRUE
224 : === END
225 : This is equivalent to checking that the post deposit account is rent paying.
226 : An account is rent paying if the post deposit balance is >0 AND it's not rent exempt.
227 : We already know that the post deposit balance is >0 because we are paying a >0 amount.
228 : So TLDR we just check if the account is rent exempt.
229 : */
230 0 : fd_rent_t const * rent = fd_bank_rent_query( bank );
231 0 : ulong minbal = fd_rent_exempt_minimum_balance( rent, fd_txn_account_get_data_len( collector ) );
232 0 : if( FD_UNLIKELY( fd_txn_account_get_lamports( collector )+fee<minbal ) ) {
233 0 : FD_BASE58_ENCODE_32_BYTES( collector->pubkey->key, _out_key );
234 0 : FD_LOG_WARNING(("cannot pay a rent paying account (%s)", _out_key ));
235 0 : return fee;
236 0 : }
237 :
238 0 : return 0UL;
239 0 : }
240 :
241 : static int
242 : fd_runtime_run_incinerator( fd_bank_t * bank,
243 : fd_funk_t * funk,
244 : fd_funk_txn_t * funk_txn,
245 0 : fd_capture_ctx_t * capture_ctx ) {
246 0 : FD_TXN_ACCOUNT_DECL( rec );
247 0 : fd_funk_rec_prepare_t prepare = {0};
248 :
249 0 : int err = fd_txn_account_init_from_funk_mutable(
250 0 : rec,
251 0 : &fd_sysvar_incinerator_id,
252 0 : funk,
253 0 : funk_txn,
254 0 : 0,
255 0 : 0UL,
256 0 : &prepare );
257 0 : if( FD_UNLIKELY( err!=FD_ACC_MGR_SUCCESS ) ) {
258 : // TODO: not really an error! This is fine!
259 0 : return -1;
260 0 : }
261 :
262 0 : fd_lthash_value_t prev_hash[1];
263 0 : fd_hashes_account_lthash( rec->pubkey, fd_txn_account_get_meta( rec ), fd_txn_account_get_data( rec ), prev_hash );
264 :
265 0 : ulong new_capitalization = fd_ulong_sat_sub( fd_bank_capitalization_get( bank ), fd_txn_account_get_lamports( rec ) );
266 0 : fd_bank_capitalization_set( bank, new_capitalization );
267 :
268 0 : fd_txn_account_set_lamports( rec, 0UL );
269 0 : fd_hashes_update_lthash( rec, prev_hash, bank, capture_ctx );
270 0 : fd_txn_account_mutable_fini( rec, funk, funk_txn, &prepare );
271 :
272 0 : return 0;
273 0 : }
274 :
275 : static void
276 0 : fd_runtime_freeze( fd_exec_slot_ctx_t * slot_ctx ) {
277 :
278 0 : if( FD_LIKELY( fd_bank_slot_get( slot_ctx->bank ) != 0UL ) ) {
279 0 : fd_sysvar_recent_hashes_update( slot_ctx );
280 0 : }
281 :
282 0 : fd_sysvar_slot_history_update( slot_ctx );
283 :
284 0 : ulong execution_fees = fd_bank_execution_fees_get( slot_ctx->bank );
285 0 : ulong priority_fees = fd_bank_priority_fees_get( slot_ctx->bank );
286 :
287 0 : ulong burn = execution_fees / 2;
288 0 : ulong fees = fd_ulong_sat_add( priority_fees, execution_fees - burn );
289 :
290 0 : if( FD_LIKELY( fees ) ) {
291 : // Look at collect_fees... I think this was where I saw the fee payout..
292 0 : FD_TXN_ACCOUNT_DECL( rec );
293 :
294 0 : do {
295 : /* do_create=1 because we might wanna pay fees to a leader
296 : account that we've purged due to 0 balance. */
297 :
298 0 : fd_epoch_leaders_t const * leaders = fd_bank_epoch_leaders_locking_query( slot_ctx->bank );
299 0 : if( FD_UNLIKELY( !leaders ) ) {
300 0 : FD_LOG_CRIT(( "fd_runtime_freeze: leaders not found" ));
301 0 : fd_bank_epoch_leaders_end_locking_query( slot_ctx->bank );
302 0 : break;
303 0 : }
304 :
305 0 : fd_pubkey_t const * leader = fd_epoch_leaders_get( leaders, fd_bank_slot_get( slot_ctx->bank ) );
306 0 : if( FD_UNLIKELY( !leader ) ) {
307 0 : FD_LOG_CRIT(( "fd_runtime_freeze: leader not found" ));
308 0 : fd_bank_epoch_leaders_end_locking_query( slot_ctx->bank );
309 0 : break;
310 0 : }
311 :
312 0 : fd_funk_rec_prepare_t prepare = {0};
313 0 : int err = fd_txn_account_init_from_funk_mutable(
314 0 : rec,
315 0 : leader,
316 0 : slot_ctx->funk,
317 0 : slot_ctx->funk_txn,
318 0 : 1,
319 0 : 0UL,
320 0 : &prepare );
321 0 : if( FD_UNLIKELY( err ) ) {
322 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));
323 0 : burn = fd_ulong_sat_add( burn, fees );
324 0 : fd_bank_epoch_leaders_end_locking_query( slot_ctx->bank );
325 0 : break;
326 0 : }
327 :
328 0 : fd_lthash_value_t prev_hash[1];
329 0 : fd_hashes_account_lthash( leader, fd_txn_account_get_meta( rec ), fd_txn_account_get_data( rec ), prev_hash );
330 :
331 0 : fd_bank_epoch_leaders_end_locking_query( slot_ctx->bank );
332 :
333 0 : if ( FD_LIKELY( FD_FEATURE_ACTIVE_BANK( slot_ctx->bank, validate_fee_collector_account ) ) ) {
334 0 : ulong _burn;
335 0 : if( FD_UNLIKELY( _burn=fd_runtime_validate_fee_collector( slot_ctx->bank, rec, fees ) ) ) {
336 0 : if( FD_UNLIKELY( _burn!=fees ) ) {
337 0 : FD_LOG_ERR(( "expected _burn(%lu)==fees(%lu)", _burn, fees ));
338 0 : }
339 0 : burn = fd_ulong_sat_add( burn, fees );
340 0 : FD_LOG_WARNING(("fd_runtime_freeze: burned %lu", fees ));
341 0 : break;
342 0 : }
343 0 : }
344 :
345 : /* TODO: is it ok to not check the overflow error here? */
346 0 : fd_txn_account_checked_add_lamports( rec, fees );
347 0 : fd_txn_account_set_slot( rec, fd_bank_slot_get( slot_ctx->bank ) );
348 :
349 0 : fd_hashes_update_lthash( rec, prev_hash, slot_ctx->bank, slot_ctx->capture_ctx );
350 0 : fd_txn_account_mutable_fini( rec, slot_ctx->funk, slot_ctx->funk_txn, &prepare );
351 :
352 0 : } while(0);
353 :
354 0 : ulong old = fd_bank_capitalization_get( slot_ctx->bank );
355 0 : fd_bank_capitalization_set( slot_ctx->bank, fd_ulong_sat_sub( old, burn ) );
356 0 : FD_LOG_DEBUG(( "fd_runtime_freeze: burn %lu, capitalization %lu->%lu ", burn, old, fd_bank_capitalization_get( slot_ctx->bank ) ));
357 :
358 0 : fd_bank_execution_fees_set( slot_ctx->bank, 0UL );
359 :
360 0 : fd_bank_priority_fees_set( slot_ctx->bank, 0UL );
361 0 : }
362 :
363 0 : fd_runtime_run_incinerator( slot_ctx->bank, slot_ctx->funk, slot_ctx->funk_txn, slot_ctx->capture_ctx );
364 :
365 0 : }
366 :
367 : /* fd_runtime_collect_rent_from_account performs rent collection duties.
368 : Although the Solana runtime prevents the creation of new accounts
369 : that are subject to rent, some older accounts are still undergo the
370 : rent collection process. Updates the account's 'rent_epoch' if
371 : needed. Returns the amount of rent collected. */
372 : /* https://github.com/anza-xyz/agave/blob/v2.0.10/svm/src/account_loader.rs#L71-96 */
373 : ulong
374 : fd_runtime_collect_rent_from_account( fd_epoch_schedule_t const * schedule,
375 : fd_rent_t const * rent,
376 : double slots_per_year,
377 : fd_txn_account_t * acc,
378 0 : ulong epoch ) {
379 0 : (void)schedule; (void)rent; (void)slots_per_year; (void)acc; (void)epoch;
380 0 : return 0UL;
381 0 : }
382 :
383 : /******************************************************************************/
384 : /* Block-Level Execution Preparation/Finalization */
385 : /******************************************************************************/
386 :
387 : /*
388 : https://github.com/firedancer-io/solana/blob/dab3da8e7b667d7527565bddbdbecf7ec1fb868e/sdk/program/src/fee_calculator.rs#L105-L165
389 : */
390 : static void
391 : fd_runtime_new_fee_rate_governor_derived( fd_bank_t * bank,
392 0 : ulong latest_signatures_per_slot ) {
393 :
394 0 : fd_fee_rate_governor_t const * base_fee_rate_governor = fd_bank_fee_rate_governor_query( bank );
395 :
396 0 : ulong old_lamports_per_signature = fd_bank_lamports_per_signature_get( bank );
397 :
398 0 : fd_fee_rate_governor_t me = {
399 0 : .target_signatures_per_slot = base_fee_rate_governor->target_signatures_per_slot,
400 0 : .target_lamports_per_signature = base_fee_rate_governor->target_lamports_per_signature,
401 0 : .max_lamports_per_signature = base_fee_rate_governor->max_lamports_per_signature,
402 0 : .min_lamports_per_signature = base_fee_rate_governor->min_lamports_per_signature,
403 0 : .burn_percent = base_fee_rate_governor->burn_percent
404 0 : };
405 :
406 0 : ulong new_lamports_per_signature = 0;
407 0 : if( me.target_signatures_per_slot > 0 ) {
408 0 : me.min_lamports_per_signature = fd_ulong_max( 1UL, (ulong)(me.target_lamports_per_signature / 2) );
409 0 : me.max_lamports_per_signature = me.target_lamports_per_signature * 10;
410 0 : ulong desired_lamports_per_signature = fd_ulong_min(
411 0 : me.max_lamports_per_signature,
412 0 : fd_ulong_max(
413 0 : me.min_lamports_per_signature,
414 0 : me.target_lamports_per_signature
415 0 : * fd_ulong_min(latest_signatures_per_slot, (ulong)UINT_MAX)
416 0 : / me.target_signatures_per_slot
417 0 : )
418 0 : );
419 0 : long gap = (long)desired_lamports_per_signature - (long)old_lamports_per_signature;
420 0 : if ( gap == 0 ) {
421 0 : new_lamports_per_signature = desired_lamports_per_signature;
422 0 : } else {
423 0 : long gap_adjust = (long)(fd_ulong_max( 1UL, (ulong)(me.target_lamports_per_signature / 20) ))
424 0 : * (gap != 0)
425 0 : * (gap > 0 ? 1 : -1);
426 0 : new_lamports_per_signature = fd_ulong_min(
427 0 : me.max_lamports_per_signature,
428 0 : fd_ulong_max(
429 0 : me.min_lamports_per_signature,
430 0 : (ulong)((long)old_lamports_per_signature + gap_adjust)
431 0 : )
432 0 : );
433 0 : }
434 0 : } else {
435 0 : new_lamports_per_signature = base_fee_rate_governor->target_lamports_per_signature;
436 0 : me.min_lamports_per_signature = me.target_lamports_per_signature;
437 0 : me.max_lamports_per_signature = me.target_lamports_per_signature;
438 0 : }
439 :
440 0 : if( FD_UNLIKELY( old_lamports_per_signature==0UL ) ) {
441 0 : fd_bank_prev_lamports_per_signature_set( bank, new_lamports_per_signature );
442 0 : } else {
443 0 : fd_bank_prev_lamports_per_signature_set( bank, old_lamports_per_signature );
444 0 : }
445 :
446 0 : fd_bank_fee_rate_governor_set( bank, me );
447 :
448 0 : fd_bank_lamports_per_signature_set( bank, new_lamports_per_signature );
449 0 : }
450 :
451 : static int
452 : fd_runtime_block_sysvar_update_pre_execute( fd_exec_slot_ctx_t * slot_ctx,
453 0 : fd_spad_t * runtime_spad ) {
454 : // let (fee_rate_governor, fee_components_time_us) = measure_us!(
455 : // FeeRateGovernor::new_derived(&parent.fee_rate_governor, parent.signature_count())
456 : // );
457 : /* https://github.com/firedancer-io/solana/blob/dab3da8e7b667d7527565bddbdbecf7ec1fb868e/runtime/src/bank.rs#L1312-L1314 */
458 :
459 0 : FD_SPAD_FRAME_BEGIN( runtime_spad ) {
460 :
461 0 : fd_runtime_new_fee_rate_governor_derived( slot_ctx->bank, fd_bank_parent_signature_cnt_get( slot_ctx->bank ) );
462 :
463 0 : fd_epoch_schedule_t const * epoch_schedule = fd_bank_epoch_schedule_query( slot_ctx->bank );
464 0 : ulong parent_epoch = fd_slot_to_epoch( epoch_schedule, fd_bank_parent_slot_get( slot_ctx->bank ), NULL );
465 0 : fd_sysvar_clock_update( slot_ctx, runtime_spad, &parent_epoch );
466 :
467 : // It has to go into the current txn previous info but is not in slot 0
468 0 : if( fd_bank_slot_get( slot_ctx->bank ) != 0 ) {
469 0 : fd_sysvar_slot_hashes_update( slot_ctx, runtime_spad );
470 0 : }
471 0 : fd_sysvar_last_restart_slot_update( slot_ctx, fd_bank_last_restart_slot_get( slot_ctx->bank ).slot );
472 :
473 0 : } FD_SPAD_FRAME_END;
474 :
475 0 : return 0;
476 0 : }
477 :
478 : int
479 : fd_runtime_load_txn_address_lookup_tables(
480 : fd_txn_t const * txn,
481 : uchar const * payload,
482 : fd_funk_t * funk,
483 : fd_funk_txn_t * funk_txn,
484 : ulong slot,
485 : fd_slot_hash_t const * hashes, /* deque */
486 : fd_acct_addr_t * out_accts_alt
487 0 : ) {
488 :
489 0 : if( FD_LIKELY( txn->transaction_version!=FD_TXN_V0 ) ) return FD_RUNTIME_EXECUTE_SUCCESS;
490 :
491 0 : ulong readonly_lut_accs_cnt = 0UL;
492 0 : ulong writable_lut_accs_cnt = 0UL;
493 0 : fd_acct_addr_t * readonly_lut_accs = out_accts_alt+txn->addr_table_adtl_writable_cnt;
494 0 : fd_txn_acct_addr_lut_t const * addr_luts = fd_txn_get_address_tables_const( txn );
495 0 : for( ulong i = 0UL; i < txn->addr_table_lookup_cnt; i++ ) {
496 0 : fd_txn_acct_addr_lut_t const * addr_lut = &addr_luts[i];
497 0 : fd_pubkey_t const * addr_lut_acc = (fd_pubkey_t *)(payload + addr_lut->addr_off);
498 :
499 : /* https://github.com/anza-xyz/agave/blob/368ea563c423b0a85cc317891187e15c9a321521/accounts-db/src/accounts.rs#L90-L94 */
500 0 : FD_TXN_ACCOUNT_DECL( addr_lut_rec );
501 0 : int err = fd_txn_account_init_from_funk_readonly( addr_lut_rec,
502 0 : addr_lut_acc,
503 0 : funk,
504 0 : funk_txn );
505 0 : if( FD_UNLIKELY( err != FD_ACC_MGR_SUCCESS ) ) {
506 0 : return FD_RUNTIME_TXN_ERR_ADDRESS_LOOKUP_TABLE_NOT_FOUND;
507 0 : }
508 :
509 : /* https://github.com/anza-xyz/agave/blob/368ea563c423b0a85cc317891187e15c9a321521/accounts-db/src/accounts.rs#L96-L114 */
510 0 : if( FD_UNLIKELY( memcmp( fd_txn_account_get_owner( addr_lut_rec ), fd_solana_address_lookup_table_program_id.key, sizeof(fd_pubkey_t) ) ) ) {
511 0 : return FD_RUNTIME_TXN_ERR_INVALID_ADDRESS_LOOKUP_TABLE_OWNER;
512 0 : }
513 :
514 : /* Realistically impossible case, but need to make sure we don't cause an OOB data access
515 : https://github.com/anza-xyz/agave/blob/368ea563c423b0a85cc317891187e15c9a321521/sdk/program/src/address_lookup_table/state.rs#L205-L209 */
516 0 : if( FD_UNLIKELY( fd_txn_account_get_data_len( addr_lut_rec ) < FD_LOOKUP_TABLE_META_SIZE ) ) {
517 0 : return FD_RUNTIME_TXN_ERR_INVALID_ADDRESS_LOOKUP_TABLE_DATA;
518 0 : }
519 :
520 : /* https://github.com/anza-xyz/agave/blob/574bae8fefc0ed256b55340b9d87b7689bcdf222/accounts-db/src/accounts.rs#L141-L142 */
521 0 : fd_bincode_decode_ctx_t decode_ctx = {
522 0 : .data = fd_txn_account_get_data( addr_lut_rec ),
523 0 : .dataend = &fd_txn_account_get_data( addr_lut_rec )[FD_LOOKUP_TABLE_META_SIZE]
524 0 : };
525 :
526 0 : ulong total_sz = 0UL;
527 0 : err = fd_address_lookup_table_state_decode_footprint( &decode_ctx, &total_sz );
528 0 : if( FD_UNLIKELY( err ) ) {
529 0 : return FD_RUNTIME_TXN_ERR_INVALID_ADDRESS_LOOKUP_TABLE_DATA;
530 0 : }
531 :
532 : /* https://github.com/anza-xyz/agave/blob/368ea563c423b0a85cc317891187e15c9a321521/sdk/program/src/address_lookup_table/state.rs#L197-L214 */
533 0 : fd_address_lookup_table_state_t table[1];
534 0 : fd_address_lookup_table_state_t * addr_lookup_table_state = fd_address_lookup_table_state_decode( table, &decode_ctx );
535 :
536 : /* https://github.com/anza-xyz/agave/blob/368ea563c423b0a85cc317891187e15c9a321521/sdk/program/src/address_lookup_table/state.rs#L200-L203 */
537 0 : if( FD_UNLIKELY( addr_lookup_table_state->discriminant != fd_address_lookup_table_state_enum_lookup_table ) ) {
538 0 : return FD_RUNTIME_TXN_ERR_INVALID_ADDRESS_LOOKUP_TABLE_DATA;
539 0 : }
540 :
541 : /* Again probably an impossible case, but the ALUT data needs to be 32-byte aligned
542 : https://github.com/anza-xyz/agave/blob/368ea563c423b0a85cc317891187e15c9a321521/sdk/program/src/address_lookup_table/state.rs#L210-L214 */
543 0 : if( FD_UNLIKELY( (fd_txn_account_get_data_len( addr_lut_rec ) - FD_LOOKUP_TABLE_META_SIZE) & 0x1fUL ) ) {
544 0 : return FD_RUNTIME_TXN_ERR_INVALID_ADDRESS_LOOKUP_TABLE_DATA;
545 0 : }
546 :
547 : /* https://github.com/anza-xyz/agave/blob/368ea563c423b0a85cc317891187e15c9a321521/accounts-db/src/accounts.rs#L101-L112 */
548 0 : fd_acct_addr_t * lookup_addrs = (fd_acct_addr_t *)&fd_txn_account_get_data( addr_lut_rec )[FD_LOOKUP_TABLE_META_SIZE];
549 0 : ulong lookup_addrs_cnt = (fd_txn_account_get_data_len( addr_lut_rec ) - FD_LOOKUP_TABLE_META_SIZE) >> 5UL; // = (dlen - 56) / 32
550 :
551 : /* https://github.com/anza-xyz/agave/blob/368ea563c423b0a85cc317891187e15c9a321521/sdk/program/src/address_lookup_table/state.rs#L175-L176 */
552 0 : ulong active_addresses_len;
553 0 : err = fd_get_active_addresses_len( &addr_lookup_table_state->inner.lookup_table,
554 0 : slot,
555 0 : hashes,
556 0 : lookup_addrs_cnt,
557 0 : &active_addresses_len );
558 0 : if( FD_UNLIKELY( err ) ) {
559 0 : return err;
560 0 : }
561 :
562 : /* https://github.com/anza-xyz/agave/blob/368ea563c423b0a85cc317891187e15c9a321521/sdk/program/src/address_lookup_table/state.rs#L169-L182 */
563 0 : uchar * writable_lut_idxs = (uchar *)payload + addr_lut->writable_off;
564 0 : for( ulong j=0; j<addr_lut->writable_cnt; j++ ) {
565 : /* https://github.com/anza-xyz/agave/blob/368ea563c423b0a85cc317891187e15c9a321521/sdk/program/src/address_lookup_table/state.rs#L177-L181 */
566 0 : if( writable_lut_idxs[j] >= active_addresses_len ) {
567 0 : return FD_RUNTIME_TXN_ERR_INVALID_ADDRESS_LOOKUP_TABLE_INDEX;
568 0 : }
569 0 : out_accts_alt[writable_lut_accs_cnt++] = lookup_addrs[writable_lut_idxs[j]];
570 0 : }
571 :
572 0 : uchar * readonly_lut_idxs = (uchar *)payload + addr_lut->readonly_off;
573 0 : for( ulong j = 0; j < addr_lut->readonly_cnt; j++ ) {
574 : /* https://github.com/anza-xyz/agave/blob/368ea563c423b0a85cc317891187e15c9a321521/sdk/program/src/address_lookup_table/state.rs#L177-L181 */
575 0 : if( readonly_lut_idxs[j] >= active_addresses_len ) {
576 0 : return FD_RUNTIME_TXN_ERR_INVALID_ADDRESS_LOOKUP_TABLE_INDEX;
577 0 : }
578 0 : readonly_lut_accs[readonly_lut_accs_cnt++] = lookup_addrs[readonly_lut_idxs[j]];
579 0 : }
580 0 : }
581 :
582 0 : return FD_RUNTIME_EXECUTE_SUCCESS;
583 0 : }
584 :
585 : int
586 : fd_runtime_microblock_verify_read_write_conflicts( fd_txn_p_t * txns,
587 : ulong txn_cnt,
588 : fd_conflict_detect_ele_t * acct_map,
589 : fd_acct_addr_t * acct_arr,
590 : fd_funk_t * funk,
591 : fd_funk_txn_t * funk_txn,
592 : ulong slot,
593 : fd_slot_hash_t * slot_hashes,
594 : fd_features_t * features,
595 : int * out_conflict_detected,
596 0 : fd_acct_addr_t * out_conflict_addr_opt ) {
597 0 : *out_conflict_detected=FD_RUNTIME_NO_CONFLICT_DETECTED;
598 0 : #define NO_CONFLICT ( *out_conflict_detected==FD_RUNTIME_NO_CONFLICT_DETECTED )
599 :
600 0 : #define UPDATE_CONFLICT(cond1, cond2, acct) \
601 0 : if( FD_UNLIKELY( cond1 ) ) { \
602 0 : if( FD_LIKELY( out_conflict_addr_opt ) ) *out_conflict_addr_opt = acct; \
603 0 : *out_conflict_detected=FD_RUNTIME_WRITE_WRITE_CONFLICT_DETECTED; \
604 0 : } else if( FD_UNLIKELY( cond2 ) ) { \
605 0 : if( FD_LIKELY( out_conflict_addr_opt ) ) *out_conflict_addr_opt = acct; \
606 0 : *out_conflict_detected=FD_RUNTIME_READ_WRITE_CONFLICT_DETECTED; \
607 0 : }
608 :
609 0 : ulong curr_idx = 0;
610 0 : ulong sentinel_is_read = 0;
611 0 : ulong sentinel_is_written = 0;
612 0 : int runtime_err = FD_RUNTIME_EXECUTE_SUCCESS;
613 0 : for( ulong i=0; i<txn_cnt && NO_CONFLICT; i++ ) {
614 0 : fd_txn_p_t * txn = txns+i;
615 0 : fd_acct_addr_t * txn_accts = acct_arr+curr_idx;
616 :
617 : /* Put the immediate & ALT accounts at txn_accts */
618 0 : const fd_acct_addr_t * accts_imm = fd_txn_get_acct_addrs( TXN(txn), txn->payload );
619 0 : ulong accts_imm_cnt = fd_txn_account_cnt( TXN(txn), FD_TXN_ACCT_CAT_IMM );
620 0 : fd_memcpy( txn_accts, accts_imm, accts_imm_cnt*sizeof(fd_acct_addr_t) );
621 0 : runtime_err = fd_runtime_load_txn_address_lookup_tables( TXN(txn),
622 0 : txn->payload,
623 0 : funk,
624 0 : funk_txn,
625 0 : slot,
626 0 : slot_hashes,
627 0 : txn_accts+accts_imm_cnt );
628 0 : if( FD_UNLIKELY( runtime_err!=FD_RUNTIME_EXECUTE_SUCCESS ) ) break;
629 :
630 0 : ulong accounts_cnt = fd_txn_account_cnt( TXN(txn), FD_TXN_ACCT_CAT_ALL );
631 0 : curr_idx +=accounts_cnt;
632 0 : uint bpf_upgradeable = fd_txn_account_has_bpf_loader_upgradeable( fd_type_pun( txn_accts ), accounts_cnt );
633 :
634 : /* Iterate all writable accounts and detect W-W/R-W conflicts */
635 0 : for( fd_txn_acct_iter_t iter=fd_txn_acct_iter_init( TXN(txn), FD_TXN_ACCT_CAT_WRITABLE );
636 0 : iter!=fd_txn_acct_iter_end() && NO_CONFLICT;
637 0 : iter=fd_txn_acct_iter_next( iter ) ) {
638 0 : ushort idx = (ushort)fd_txn_acct_iter_idx( iter );
639 0 : fd_acct_addr_t writable_acc = txn_accts[ idx ];
640 :
641 : /* Check whether writable_acc is demoted to a read-only account */
642 0 : if( FD_UNLIKELY( !fd_exec_txn_account_is_writable_idx_flat( slot,
643 0 : idx,
644 0 : fd_type_pun( &txn_accts[ idx ] ),
645 0 : TXN(txn),
646 0 : features,
647 0 : bpf_upgradeable ) ) ) {
648 0 : continue;
649 0 : }
650 :
651 : /* writable_acc is the sentinel (fd_acct_addr_null) */
652 0 : if( FD_UNLIKELY( fd_conflict_detect_map_key_inval( writable_acc ) ) ) {
653 0 : UPDATE_CONFLICT( sentinel_is_written, sentinel_is_read, writable_acc );
654 0 : sentinel_is_written = 1;
655 0 : continue;
656 0 : }
657 :
658 : /* writable_acc is not the sentinel (fd_acct_addr_null) */
659 0 : fd_conflict_detect_ele_t * found = fd_conflict_detect_map_query( acct_map, writable_acc, NULL );
660 0 : if( FD_UNLIKELY( found ) ) {
661 0 : UPDATE_CONFLICT( found->writable, !found->writable, writable_acc );
662 0 : } else {
663 0 : fd_conflict_detect_ele_t * entry = fd_conflict_detect_map_insert( acct_map, writable_acc );
664 0 : entry->writable = 1;
665 0 : }
666 0 : }
667 :
668 : /* Iterate all readonly accounts and detect R-W conflicts */
669 0 : for( fd_txn_acct_iter_t iter=fd_txn_acct_iter_init( TXN(txn), FD_TXN_ACCT_CAT_READONLY );
670 0 : iter!=fd_txn_acct_iter_end() && NO_CONFLICT;
671 0 : iter=fd_txn_acct_iter_next( iter ) ) {
672 0 : fd_acct_addr_t readonly_acc = txn_accts[ fd_txn_acct_iter_idx( iter ) ];
673 :
674 : /* readonly_acc is the sentinel (fd_acct_addr_null) */
675 0 : if( FD_UNLIKELY( fd_conflict_detect_map_key_inval( readonly_acc ) ) ) {
676 0 : UPDATE_CONFLICT( 0, sentinel_is_written, readonly_acc );
677 0 : sentinel_is_read = 1;
678 0 : continue;
679 0 : }
680 :
681 : /* readonly_acc is not the sentinel (fd_acct_addr_null) */
682 0 : fd_conflict_detect_ele_t * found = fd_conflict_detect_map_query( acct_map, readonly_acc, NULL );
683 0 : if( FD_UNLIKELY( found ) ) {
684 0 : UPDATE_CONFLICT( 0, found->writable, readonly_acc );
685 0 : } else {
686 0 : fd_conflict_detect_ele_t * entry = fd_conflict_detect_map_insert( acct_map, readonly_acc );
687 0 : entry->writable = 0;
688 0 : }
689 0 : }
690 0 : }
691 :
692 : /* Clear all the entries inserted into acct_map */
693 0 : for( ulong i=0; i<curr_idx; i++ ) {
694 0 : if( FD_UNLIKELY( fd_conflict_detect_map_key_inval( acct_arr[i] ) ) ) continue;
695 0 : fd_conflict_detect_ele_t * found = fd_conflict_detect_map_query( acct_map, acct_arr[i], NULL );
696 0 : if( FD_LIKELY( found ) ) fd_conflict_detect_map_remove( acct_map, found );
697 0 : }
698 :
699 0 : if( FD_UNLIKELY( runtime_err!=FD_RUNTIME_EXECUTE_SUCCESS ) ) {
700 0 : return runtime_err;
701 0 : } else {
702 : /* https://github.com/anza-xyz/agave/blob/v2.2.3/accounts-db/src/account_locks.rs#L31 */
703 : /* https://github.com/anza-xyz/agave/blob/v2.2.3/accounts-db/src/account_locks.rs#L34 */
704 0 : return NO_CONFLICT? FD_RUNTIME_EXECUTE_SUCCESS : FD_RUNTIME_TXN_ERR_ACCOUNT_IN_USE;
705 0 : }
706 0 : }
707 :
708 : int
709 : fd_runtime_block_execute_prepare( fd_exec_slot_ctx_t * slot_ctx,
710 0 : fd_spad_t * runtime_spad ) {
711 0 : fd_bank_execution_fees_set( slot_ctx->bank, 0UL );
712 :
713 0 : fd_bank_priority_fees_set( slot_ctx->bank, 0UL );
714 :
715 0 : fd_bank_signature_count_set( slot_ctx->bank, 0UL );
716 :
717 0 : fd_bank_txn_count_set( slot_ctx->bank, 0UL );
718 :
719 0 : fd_bank_nonvote_txn_count_set( slot_ctx->bank, 0UL );
720 :
721 0 : fd_bank_failed_txn_count_set( slot_ctx->bank, 0UL );
722 :
723 0 : fd_bank_nonvote_failed_txn_count_set( slot_ctx->bank, 0UL );
724 :
725 0 : fd_bank_total_compute_units_used_set( slot_ctx->bank, 0UL );
726 :
727 : /* Setup cost tracker */
728 0 : fd_cost_tracker_t * cost_tracker = fd_cost_tracker_join( fd_cost_tracker_new(
729 0 : fd_bank_cost_tracker_locking_modify( slot_ctx->bank ),
730 0 : fd_bank_features_query( slot_ctx->bank ),
731 0 : fd_bank_slot_get( slot_ctx->bank ),
732 0 : 999UL ) );
733 0 : if( FD_UNLIKELY( !cost_tracker ) ) {
734 0 : FD_LOG_CRIT(("Unable to allocate memory for cost tracker" ));
735 0 : }
736 0 : fd_bank_cost_tracker_end_locking_modify( slot_ctx->bank );
737 :
738 0 : int result = fd_runtime_block_sysvar_update_pre_execute( slot_ctx, runtime_spad );
739 0 : if( FD_UNLIKELY( result != 0 ) ) {
740 0 : FD_LOG_WARNING(("updating sysvars failed"));
741 0 : return result;
742 0 : }
743 :
744 0 : if( FD_UNLIKELY( !fd_sysvar_cache_restore( slot_ctx ) ) ) {
745 0 : FD_LOG_ERR(( "Failed to restore sysvar cache" ));
746 0 : }
747 :
748 0 : return FD_RUNTIME_EXECUTE_SUCCESS;
749 0 : }
750 :
751 : static void
752 0 : fd_runtime_update_bank_hash( fd_exec_slot_ctx_t * slot_ctx ) {
753 : /* Save the previous bank hash, and the parents signature count */
754 0 : fd_hash_t const * prev_bank_hash = NULL;
755 0 : if( FD_LIKELY( fd_bank_slot_get( slot_ctx->bank )!=0UL ) ) {
756 0 : prev_bank_hash = fd_bank_bank_hash_query( slot_ctx->bank );
757 0 : fd_bank_prev_bank_hash_set( slot_ctx->bank, *prev_bank_hash );
758 0 : } else {
759 0 : prev_bank_hash = fd_bank_prev_bank_hash_query( slot_ctx->bank );
760 0 : }
761 :
762 0 : fd_bank_parent_signature_cnt_set( slot_ctx->bank, fd_bank_signature_count_get( slot_ctx->bank ) );
763 :
764 : /* Compute the new bank hash */
765 0 : fd_lthash_value_t const * lthash = fd_bank_lthash_locking_query( slot_ctx->bank );
766 0 : fd_hash_t new_bank_hash[1] = { 0 };
767 0 : fd_hashes_hash_bank(
768 0 : lthash,
769 0 : prev_bank_hash,
770 0 : (fd_hash_t *)fd_bank_poh_query( slot_ctx->bank )->hash,
771 0 : fd_bank_signature_count_get( slot_ctx->bank ),
772 0 : new_bank_hash );
773 :
774 : /* Update the bank hash */
775 0 : fd_bank_bank_hash_set( slot_ctx->bank, *new_bank_hash );
776 :
777 0 : if( !slot_ctx->silent ) {
778 0 : FD_LOG_NOTICE(( "\n\n[Runtime]\n"
779 0 : "slot: %lu\n"
780 0 : "bank hash: %s\n"
781 0 : "parent bank hash: %s\n"
782 0 : "lthash: %s\n"
783 0 : "signature_count: %lu\n"
784 0 : "last_blockhash: %s\n",
785 0 : fd_bank_slot_get( slot_ctx->bank ),
786 0 : FD_BASE58_ENC_32_ALLOCA( new_bank_hash->hash ),
787 0 : FD_BASE58_ENC_32_ALLOCA( fd_bank_prev_bank_hash_query( slot_ctx->bank ) ),
788 0 : FD_LTHASH_ENC_32_ALLOCA( lthash->bytes ),
789 0 : fd_bank_signature_count_get( slot_ctx->bank ),
790 0 : FD_BASE58_ENC_32_ALLOCA( fd_bank_poh_query( slot_ctx->bank )->hash ) ));
791 0 : }
792 :
793 0 : if( slot_ctx->capture_ctx != NULL && slot_ctx->capture_ctx->capture != NULL &&
794 0 : fd_bank_slot_get( slot_ctx->bank )>=slot_ctx->capture_ctx->solcap_start_slot ) {
795 :
796 0 : uchar lthash_hash[FD_HASH_FOOTPRINT];
797 0 : fd_blake3_hash(lthash->bytes, FD_LTHASH_LEN_BYTES, lthash_hash );
798 :
799 0 : fd_solcap_write_bank_preimage(
800 0 : slot_ctx->capture_ctx->capture,
801 0 : new_bank_hash->hash,
802 0 : fd_bank_prev_bank_hash_query( slot_ctx->bank ),
803 0 : NULL,
804 0 : lthash_hash,
805 0 : fd_bank_poh_query( slot_ctx->bank )->hash,
806 0 : fd_bank_signature_count_get( slot_ctx->bank ) );
807 0 : }
808 :
809 0 : fd_bank_lthash_end_locking_query( slot_ctx->bank );
810 0 : }
811 :
812 : /******************************************************************************/
813 : /* Transaction Level Execution Management */
814 : /******************************************************************************/
815 :
816 : /* fd_runtime_pre_execute_check is responsible for conducting many of the
817 : transaction sanitization checks. */
818 :
819 : int
820 0 : fd_runtime_pre_execute_check( fd_exec_txn_ctx_t * txn_ctx ) {
821 :
822 0 : int err;
823 :
824 : /* https://github.com/anza-xyz/agave/blob/16de8b75ebcd57022409b422de557dd37b1de8db/sdk/src/transaction/sanitized.rs#L263-L275
825 : TODO: Agave's precompile verification is done at the slot level, before batching and executing transactions. This logic should probably
826 : be moved in the future. The Agave call heirarchy looks something like this:
827 : process_single_slot
828 : v
829 : confirm_full_slot
830 : v
831 : confirm_slot_entries --------------------------------------------------->
832 : v v v
833 : verify_transaction ComputeBudget::process_instruction process_entries
834 : v v
835 : verify_precompiles process_batches
836 : v
837 : ...
838 : v
839 : load_and_execute_transactions
840 : v
841 : ...
842 : v
843 : load_accounts --> load_transaction_accounts
844 : v
845 : general transaction execution
846 :
847 : */
848 :
849 0 : uchar dump_txn = !!( txn_ctx->capture_ctx &&
850 0 : txn_ctx->slot >= txn_ctx->capture_ctx->dump_proto_start_slot &&
851 0 : txn_ctx->capture_ctx->dump_txn_to_pb );
852 0 : if( FD_UNLIKELY( dump_txn ) ) {
853 0 : fd_dump_txn_to_protobuf( txn_ctx, txn_ctx->spad );
854 0 : }
855 :
856 : /* Verify the transaction. For now, this step only involves processing
857 : the compute budget instructions. */
858 0 : err = fd_executor_verify_transaction( txn_ctx );
859 0 : if( FD_UNLIKELY( err!=FD_RUNTIME_EXECUTE_SUCCESS ) ) {
860 0 : txn_ctx->flags = 0U;
861 0 : return err;
862 0 : }
863 :
864 : /* Resolve and verify ALUT-referenced account keys, if applicable */
865 0 : err = fd_executor_setup_txn_alut_account_keys( txn_ctx );
866 0 : if( FD_UNLIKELY( err!=FD_RUNTIME_EXECUTE_SUCCESS ) ) {
867 0 : txn_ctx->flags = 0U;
868 0 : return err;
869 0 : }
870 :
871 : /* Set up the transaction accounts and other txn ctx metadata */
872 0 : fd_executor_setup_accounts_for_txn( txn_ctx );
873 :
874 : /* Post-sanitization checks. Called from prepare_sanitized_batch()
875 : which, for now, only is used to lock the accounts and perform a
876 : couple basic validations.
877 : https://github.com/anza-xyz/agave/blob/838c1952595809a31520ff1603a13f2c9123aa51/accounts-db/src/account_locks.rs#L118 */
878 0 : err = fd_executor_validate_account_locks( txn_ctx );
879 0 : if( FD_UNLIKELY( err!=FD_RUNTIME_EXECUTE_SUCCESS ) ) {
880 0 : txn_ctx->flags = 0U;
881 0 : return err;
882 0 : }
883 :
884 : /* load_and_execute_transactions() -> check_transactions()
885 : https://github.com/anza-xyz/agave/blob/ced98f1ebe73f7e9691308afa757323003ff744f/runtime/src/bank.rs#L3667-L3672 */
886 0 : err = fd_executor_check_transactions( txn_ctx );
887 0 : if( FD_UNLIKELY( err!=FD_RUNTIME_EXECUTE_SUCCESS ) ) {
888 0 : txn_ctx->flags = 0U;
889 0 : return err;
890 0 : }
891 :
892 : /* load_and_execute_sanitized_transactions() -> validate_fees() ->
893 : validate_transaction_fee_payer()
894 : https://github.com/anza-xyz/agave/blob/ced98f1ebe73f7e9691308afa757323003ff744f/svm/src/transaction_processor.rs#L236-L249 */
895 0 : err = fd_executor_validate_transaction_fee_payer( txn_ctx );
896 0 : if( FD_UNLIKELY( err!=FD_RUNTIME_EXECUTE_SUCCESS ) ) {
897 0 : txn_ctx->flags = 0U;
898 0 : return err;
899 0 : }
900 :
901 : /* https://github.com/anza-xyz/agave/blob/ced98f1ebe73f7e9691308afa757323003ff744f/svm/src/transaction_processor.rs#L284-L296 */
902 0 : err = fd_executor_load_transaction_accounts( txn_ctx );
903 0 : if( FD_UNLIKELY( err!=FD_RUNTIME_EXECUTE_SUCCESS ) ) {
904 : /* Regardless of whether transaction accounts were loaded successfully, the transaction is
905 : included in the block and transaction fees are collected.
906 : https://github.com/anza-xyz/agave/blob/v2.1.6/svm/src/transaction_processor.rs#L341-L357 */
907 0 : txn_ctx->flags |= FD_TXN_P_FLAGS_FEES_ONLY;
908 :
909 : /* If the transaction fails to load, the "rollback" accounts will include one of the following:
910 : 1. Nonce account only
911 : 2. Fee payer only
912 : 3. Nonce account + fee payer
913 :
914 : Because the cost tracker uses the loaded account data size in block cost calculations, we need to
915 : make sure our calculated loaded accounts data size is conformant with Agave's.
916 : https://github.com/anza-xyz/agave/blob/v2.1.14/runtime/src/bank.rs#L4116
917 :
918 : In any case, we should always add the dlen of the fee payer. */
919 0 : txn_ctx->loaded_accounts_data_size = fd_txn_account_get_data_len( &txn_ctx->accounts[FD_FEE_PAYER_TXN_IDX] );
920 :
921 : /* Special case handling for if a nonce account is present in the transaction. */
922 0 : if( txn_ctx->nonce_account_idx_in_txn!=ULONG_MAX ) {
923 : /* If the nonce account is not the fee payer, then we separately add the dlen of the nonce account. Otherwise, we would
924 : be double counting the dlen of the fee payer. */
925 0 : if( txn_ctx->nonce_account_idx_in_txn!=FD_FEE_PAYER_TXN_IDX ) {
926 0 : txn_ctx->loaded_accounts_data_size += fd_txn_account_get_data_len( txn_ctx->rollback_nonce_account );
927 0 : }
928 0 : }
929 0 : }
930 :
931 : /*
932 : The fee payer and the nonce account will be stored and hashed so
933 : long as the transaction landed on chain, or, in Agave terminology,
934 : the transaction was processed.
935 : https://github.com/anza-xyz/agave/blob/v2.1.1/runtime/src/account_saver.rs#L72
936 :
937 : A transaction lands on chain in one of two ways:
938 : (1) Passed fee validation and loaded accounts.
939 : (2) Passed fee validation and failed to load accounts and the enable_transaction_loading_failure_fees feature is enabled as per
940 : SIMD-0082 https://github.com/anza-xyz/feature-gate-tracker/issues/52
941 :
942 : So, at this point, the transaction is committable.
943 : */
944 :
945 0 : return err;
946 0 : }
947 :
948 : /* fd_runtime_finalize_account is a helper used to commit the data from
949 : a writable transaction account back into the accountsdb. */
950 :
951 : static void
952 : fd_runtime_finalize_account( fd_funk_t * funk,
953 : fd_funk_txn_t * funk_txn,
954 0 : fd_txn_account_t * acc ) {
955 0 : if( FD_UNLIKELY( !fd_txn_account_is_mutable( acc ) ) ) {
956 0 : FD_LOG_CRIT(( "fd_runtime_finalize_account: account is not mutable" ));
957 0 : }
958 :
959 0 : fd_pubkey_t const * key = acc->pubkey;
960 0 : uchar const * record_data = (uchar *)fd_txn_account_get_meta( acc );
961 0 : ulong record_sz = fd_account_meta_get_record_sz( acc->meta );
962 :
963 0 : int err = FD_FUNK_SUCCESS;
964 :
965 0 : fd_funk_rec_key_t funk_key = fd_funk_acc_key( key );
966 0 : fd_funk_rec_prepare_t prepare[1];
967 0 : fd_funk_rec_t * rec = fd_funk_rec_prepare( funk, funk_txn, &funk_key, prepare, &err );
968 0 : if( FD_UNLIKELY( !rec || err!=FD_FUNK_SUCCESS ) ) {
969 0 : FD_LOG_ERR(( "fd_runtime_finalize_account: failed to prepare record (%i-%s)", err, fd_funk_strerror( err ) ));
970 0 : }
971 :
972 0 : if( FD_UNLIKELY( !fd_funk_val_truncate(
973 0 : rec,
974 0 : fd_funk_alloc( funk ),
975 0 : fd_funk_wksp( funk ),
976 0 : 0UL,
977 0 : record_sz,
978 0 : &err ) ) ) {
979 0 : FD_LOG_ERR(( "fd_funk_val_truncate(sz=%lu) for account failed (%i-%s)", record_sz, err, fd_funk_strerror( err ) ));
980 0 : }
981 :
982 0 : fd_memcpy( fd_funk_val( rec, fd_funk_wksp( funk ) ), record_data, record_sz );
983 :
984 0 : fd_funk_rec_publish( funk, prepare );
985 0 : }
986 :
987 : /* fd_runtime_buffer_solcap_account_update buffers an account
988 : update event message in the capture context, which will be
989 : sent to the replay tile via the writer_replay link.
990 : This buffering is done to avoid passing stem down into the runtime.
991 :
992 : TODO: remove this when solcap v2 is here. */
993 : static void
994 : fd_runtime_buffer_solcap_account_update( fd_txn_account_t * account,
995 : fd_bank_t * bank,
996 0 : fd_capture_ctx_t * capture_ctx ) {
997 :
998 : /* Check if we should publish the update */
999 0 : if( FD_UNLIKELY( !capture_ctx || fd_bank_slot_get( bank )<capture_ctx->solcap_start_slot ) ) {
1000 0 : return;
1001 0 : }
1002 :
1003 : /* Get account data */
1004 0 : fd_account_meta_t const * meta = fd_txn_account_get_meta( account );
1005 0 : void const * data = fd_txn_account_get_data( account );
1006 :
1007 : /* Calculate account hash using lthash */
1008 0 : fd_lthash_value_t lthash[1];
1009 0 : fd_hashes_account_lthash( account->pubkey, meta, data, lthash );
1010 :
1011 : /* Calculate message size */
1012 0 : if( FD_UNLIKELY( capture_ctx->account_updates_len > FD_CAPTURE_CTX_MAX_ACCOUNT_UPDATES ) ) {
1013 0 : FD_LOG_CRIT(( "cannot buffer solcap account update. this should never happen" ));
1014 0 : return;
1015 0 : }
1016 :
1017 : /* Write the message to the buffer */
1018 0 : fd_capture_ctx_account_update_msg_t * account_update_msg = (fd_capture_ctx_account_update_msg_t *)(capture_ctx->account_updates_buffer_ptr);
1019 0 : account_update_msg->pubkey = *account->pubkey;
1020 0 : account_update_msg->info = fd_txn_account_get_solana_meta( account );
1021 0 : account_update_msg->data_sz = meta->dlen;
1022 0 : memcpy( account_update_msg->hash.uc, lthash->bytes, sizeof(fd_hash_t) );
1023 0 : capture_ctx->account_updates_buffer_ptr += sizeof(fd_capture_ctx_account_update_msg_t);
1024 :
1025 : /* Write the account data to the buffer */
1026 0 : memcpy( capture_ctx->account_updates_buffer_ptr, data, meta->dlen );
1027 0 : capture_ctx->account_updates_buffer_ptr += meta->dlen;
1028 :
1029 0 : capture_ctx->account_updates_len++;
1030 0 : }
1031 :
1032 : /* fd_runtime_save_account is a convenience wrapper that looks
1033 : up the previous account state from funk before updating the lthash
1034 : and saving the new version of the account to funk.
1035 :
1036 : TODO: We have to make a read request to the DB, so that we can calculate
1037 : the previous version of the accounts hash, to mix-out from the accumulated
1038 : lthash. In future we should likely cache the previous version of the account
1039 : in transaction setup, so that we don't have to issue a read request here.
1040 :
1041 : funk is the funk database handle. funk_txn is the transaction
1042 : context to query (NULL for root context). account is the modified
1043 : account. bank and capture_ctx are passed to fd_hashes_update_lthash.
1044 :
1045 : This function:
1046 : - Queries funk for the previous account version
1047 : - Computes the hash of the previous version (or uses zero for new)
1048 : - Calls fd_hashes_update_lthash with the computed previous hash
1049 : - Saves the new version of the account to Funk
1050 : - Notifies the replay tile that an account update has occurred, so it
1051 : can write the account to the solcap file.
1052 :
1053 : The function handles FD_ACC_MGR_ERR_UNKNOWN_ACCOUNT gracefully (uses
1054 : zero hash). On other funk errors, the function will FD_LOG_ERR.
1055 : All non-optional pointers must be valid. */
1056 :
1057 : static void
1058 : fd_runtime_save_account( fd_funk_t * funk,
1059 : fd_funk_txn_t * funk_txn,
1060 : fd_txn_account_t * account,
1061 : fd_bank_t * bank,
1062 : fd_wksp_t * acc_data_wksp,
1063 0 : fd_capture_ctx_t * capture_ctx ) {
1064 :
1065 : /* Join the transaction account */
1066 0 : if( FD_UNLIKELY( !fd_txn_account_join( account, acc_data_wksp ) ) ) {
1067 0 : FD_LOG_CRIT(( "fd_runtime_save_account: failed to join account" ));
1068 0 : }
1069 :
1070 : /* Look up the previous version of the account from Funk */
1071 0 : FD_TXN_ACCOUNT_DECL( previous_account_version );
1072 0 : int err = fd_txn_account_init_from_funk_readonly( previous_account_version, account->pubkey, funk, funk_txn );
1073 0 : if( FD_UNLIKELY( err!=FD_ACC_MGR_SUCCESS && err!=FD_ACC_MGR_ERR_UNKNOWN_ACCOUNT ) ) {
1074 0 : FD_LOG_CRIT(( "Failed to read old account version from Funk" ));
1075 0 : return;
1076 0 : }
1077 :
1078 : /* Hash the old version of the account */
1079 0 : fd_lthash_value_t prev_hash[1];
1080 0 : fd_lthash_zero( prev_hash );
1081 0 : if( err != FD_ACC_MGR_ERR_UNKNOWN_ACCOUNT ) {
1082 0 : fd_hashes_account_lthash(
1083 0 : account->pubkey,
1084 0 : fd_txn_account_get_meta( previous_account_version ),
1085 0 : fd_txn_account_get_data( previous_account_version ),
1086 0 : prev_hash );
1087 0 : }
1088 :
1089 : /* Mix in the account hash into the bank hash */
1090 0 : fd_hashes_update_lthash( account, prev_hash, bank, NULL );
1091 :
1092 : /* Publish account update to replay tile for solcap writing
1093 : TODO: write in the writer tile with solcap v2 */
1094 0 : fd_runtime_buffer_solcap_account_update( account, bank, capture_ctx );
1095 :
1096 : /* Save the new version of the account to Funk */
1097 0 : fd_runtime_finalize_account( funk, funk_txn, account );
1098 0 : }
1099 :
1100 : /* fd_runtime_finalize_txn is a helper used by the non-tpool transaction
1101 : executor to finalize borrowed account changes back into funk. It also
1102 : handles txncache insertion and updates to the vote/stake cache.
1103 : TODO: This function should probably be moved to fd_executor.c. */
1104 :
1105 : void
1106 : fd_runtime_finalize_txn( fd_funk_t * funk,
1107 : fd_funk_txn_t * funk_txn,
1108 : fd_exec_txn_ctx_t * txn_ctx,
1109 : fd_bank_t * bank,
1110 0 : fd_capture_ctx_t * capture_ctx ) {
1111 :
1112 : /* Collect fees */
1113 :
1114 0 : FD_ATOMIC_FETCH_AND_ADD( fd_bank_txn_count_modify( bank ), 1UL );
1115 0 : FD_ATOMIC_FETCH_AND_ADD( fd_bank_execution_fees_modify( bank ), txn_ctx->execution_fee );
1116 0 : FD_ATOMIC_FETCH_AND_ADD( fd_bank_priority_fees_modify( bank ), txn_ctx->priority_fee );
1117 :
1118 0 : FD_ATOMIC_FETCH_AND_ADD( fd_bank_signature_count_modify( bank ), TXN( &txn_ctx->txn )->signature_cnt );
1119 :
1120 0 : if( FD_UNLIKELY( txn_ctx->exec_err ) ) {
1121 :
1122 : /* Save the fee_payer. Everything but the fee balance should be reset.
1123 : TODO: an optimization here could be to use a dirty flag in the
1124 : borrowed account. If the borrowed account data has been changed in
1125 : any way, then the full account can be rolled back as it is done now.
1126 : However, most of the time the account data is not changed, and only
1127 : the lamport balance has to change. */
1128 :
1129 : /* With nonce account rollbacks, there are three cases:
1130 : 1. No nonce account in the transaction
1131 : 2. Nonce account is the fee payer
1132 : 3. Nonce account is not the fee payer
1133 :
1134 : We should always rollback the nonce account first. Note that the nonce account may be the fee payer (case 2). */
1135 0 : if( txn_ctx->nonce_account_idx_in_txn!=ULONG_MAX ) {
1136 0 : fd_runtime_save_account( funk, funk_txn, txn_ctx->rollback_nonce_account, bank, txn_ctx->spad_wksp, capture_ctx );
1137 0 : }
1138 :
1139 : /* Now, we must only save the fee payer if the nonce account was not the fee payer (because that was already saved above) */
1140 0 : if( FD_LIKELY( txn_ctx->nonce_account_idx_in_txn!=FD_FEE_PAYER_TXN_IDX ) ) {
1141 0 : fd_runtime_save_account( funk, funk_txn, txn_ctx->rollback_fee_payer_account, bank, txn_ctx->spad_wksp, capture_ctx );
1142 0 : }
1143 0 : } else {
1144 :
1145 0 : int dirty_vote_acc = txn_ctx->dirty_vote_acc;
1146 :
1147 0 : for( ushort i=0; i<txn_ctx->accounts_cnt; i++ ) {
1148 : /* We are only interested in saving writable accounts and the fee
1149 : payer account. */
1150 0 : if( !fd_exec_txn_ctx_account_is_writable_idx( txn_ctx, i ) && i!=FD_FEE_PAYER_TXN_IDX ) {
1151 0 : continue;
1152 0 : }
1153 :
1154 0 : fd_txn_account_t * acc_rec = fd_txn_account_join( &txn_ctx->accounts[i], txn_ctx->spad_wksp );
1155 0 : if( FD_UNLIKELY( !acc_rec ) ) {
1156 0 : FD_LOG_CRIT(( "fd_runtime_finalize_txn: failed to join account at idx %u", i ));
1157 0 : }
1158 :
1159 0 : if( dirty_vote_acc && 0==memcmp( fd_txn_account_get_owner( acc_rec ), &fd_solana_vote_program_id, sizeof(fd_pubkey_t) ) ) {
1160 0 : fd_vote_store_account( acc_rec, bank );
1161 0 : }
1162 :
1163 0 : if( 0==memcmp( fd_txn_account_get_owner( acc_rec ), &fd_solana_stake_program_id, sizeof(fd_pubkey_t) ) ) {
1164 0 : fd_update_stake_delegation( acc_rec, bank );
1165 0 : }
1166 :
1167 : /* Reclaim any accounts that have 0-lamports, now that any related
1168 : cache updates have been applied. */
1169 0 : fd_executor_reclaim_account( txn_ctx, &txn_ctx->accounts[i] );
1170 :
1171 0 : fd_runtime_save_account( funk, funk_txn, &txn_ctx->accounts[i], bank, txn_ctx->spad_wksp, capture_ctx );
1172 0 : }
1173 :
1174 : /* We need to queue any existing program accounts that may have
1175 : been deployed / upgraded for reverification in the program
1176 : cache since their programdata may have changed. ELF / sBPF
1177 : metadata will need to be updated. */
1178 0 : ulong current_slot = fd_bank_slot_get( bank );
1179 0 : for( uchar i=0; i<txn_ctx->programs_to_reverify_cnt; i++ ) {
1180 0 : fd_pubkey_t const * program_key = &txn_ctx->programs_to_reverify[i];
1181 0 : fd_program_cache_queue_program_for_reverification( funk, funk_txn, program_key, current_slot );
1182 0 : }
1183 0 : }
1184 :
1185 0 : int is_vote = fd_txn_is_simple_vote_transaction( TXN( &txn_ctx->txn ), txn_ctx->txn.payload );
1186 0 : if( !is_vote ){
1187 0 : ulong * nonvote_txn_count = fd_bank_nonvote_txn_count_modify( bank );
1188 0 : FD_ATOMIC_FETCH_AND_ADD(nonvote_txn_count, 1);
1189 :
1190 0 : if( FD_UNLIKELY( txn_ctx->exec_err ) ){
1191 0 : ulong * nonvote_failed_txn_count = fd_bank_nonvote_failed_txn_count_modify( bank );
1192 0 : FD_ATOMIC_FETCH_AND_ADD( nonvote_failed_txn_count, 1 );
1193 0 : }
1194 0 : } else {
1195 0 : if( FD_UNLIKELY( txn_ctx->exec_err ) ){
1196 0 : ulong * failed_txn_count = fd_bank_failed_txn_count_modify( bank );
1197 0 : FD_ATOMIC_FETCH_AND_ADD( failed_txn_count, 1 );
1198 0 : }
1199 0 : }
1200 :
1201 0 : ulong * total_compute_units_used = fd_bank_total_compute_units_used_modify( bank );
1202 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 );
1203 :
1204 : /* Update the cost tracker */
1205 0 : fd_cost_tracker_t * cost_tracker = fd_cost_tracker_join( fd_bank_cost_tracker_locking_modify( bank ) );
1206 0 : int res = fd_cost_tracker_calculate_cost_and_add( cost_tracker, txn_ctx );
1207 0 : if( FD_UNLIKELY( res!=FD_COST_TRACKER_SUCCESS ) ) {
1208 0 : txn_ctx->flags = 0U;
1209 0 : }
1210 0 : fd_bank_cost_tracker_end_locking_modify( bank );
1211 :
1212 0 : }
1213 :
1214 : int
1215 : fd_runtime_prepare_and_execute_txn( fd_banks_t * banks,
1216 : ulong bank_idx,
1217 : fd_exec_txn_ctx_t * txn_ctx,
1218 : fd_txn_p_t * txn,
1219 : fd_spad_t * exec_spad,
1220 : fd_capture_ctx_t * capture_ctx,
1221 0 : uchar do_sigverify ) {
1222 0 : FD_SPAD_FRAME_BEGIN( exec_spad ) {
1223 0 : int exec_res = 0;
1224 :
1225 0 : fd_bank_t * bank = fd_banks_get_bank_idx( banks, bank_idx );
1226 0 : if( FD_UNLIKELY( !bank ) ) {
1227 0 : FD_LOG_CRIT(( "Could not get bank at pool idx %lu", bank_idx ));
1228 0 : }
1229 :
1230 0 : ulong slot = fd_bank_slot_get( bank );
1231 :
1232 0 : fd_funk_txn_t * funk_txn = fd_runtime_funk_txn_get( txn_ctx->funk, slot );
1233 0 : if( FD_UNLIKELY( !funk_txn ) ) {
1234 0 : FD_LOG_CRIT(( "Could not get funk transaction for slot %lu", slot ));
1235 0 : }
1236 :
1237 : /* Setup and execute the transaction. */
1238 0 : txn_ctx->bank = bank;
1239 0 : txn_ctx->slot = fd_bank_slot_get( bank );
1240 0 : txn_ctx->bank_idx = bank_idx;
1241 0 : txn_ctx->features = fd_bank_features_get( bank );
1242 0 : txn_ctx->status_cache = NULL; // TODO: Make non-null once implemented
1243 0 : txn_ctx->enable_exec_recording = !!( bank->flags & FD_BANK_FLAGS_EXEC_RECORDING );
1244 0 : txn_ctx->funk_txn = funk_txn;
1245 0 : txn_ctx->capture_ctx = capture_ctx;
1246 0 : txn_ctx->txn = *txn;
1247 :
1248 0 : txn_ctx->flags = FD_TXN_P_FLAGS_SANITIZE_SUCCESS;
1249 0 : fd_exec_txn_ctx_setup_basic( txn_ctx );
1250 :
1251 : /* Set up the core account keys. These are the account keys directly
1252 : passed in via the serialized transaction, represented as an array.
1253 : Note that this does not include additional keys referenced in
1254 : address lookup tables. */
1255 0 : fd_executor_setup_txn_account_keys( txn_ctx );
1256 :
1257 0 : if( FD_LIKELY( do_sigverify ) ) {
1258 0 : exec_res = fd_executor_txn_verify( txn_ctx );
1259 0 : if( FD_UNLIKELY( exec_res!=FD_RUNTIME_EXECUTE_SUCCESS ) ) {
1260 0 : txn_ctx->flags = 0U;
1261 0 : return exec_res;
1262 0 : }
1263 0 : }
1264 :
1265 : /* Pre-execution checks */
1266 0 : exec_res = fd_runtime_pre_execute_check( txn_ctx );
1267 0 : if( FD_UNLIKELY( !( txn_ctx->flags & FD_TXN_P_FLAGS_SANITIZE_SUCCESS ) ) ) {
1268 0 : return exec_res;
1269 0 : }
1270 :
1271 : /* Execute the transaction. Note that fees-only transactions are still
1272 : marked as "executed". */
1273 0 : txn_ctx->flags |= FD_TXN_P_FLAGS_EXECUTE_SUCCESS;
1274 0 : if( FD_LIKELY( !( txn_ctx->flags & FD_TXN_P_FLAGS_FEES_ONLY ) ) ) {
1275 0 : exec_res = fd_execute_txn( txn_ctx );
1276 0 : }
1277 :
1278 0 : return exec_res;
1279 :
1280 0 : } FD_SPAD_FRAME_END;
1281 0 : }
1282 :
1283 : /* fd_executor_txn_verify and fd_runtime_pre_execute_check are responisble
1284 : for the bulk of the pre-transaction execution checks in the runtime.
1285 : They aim to preserve the ordering present in the Agave client to match
1286 : parity in terms of error codes. Sigverify is kept separate from the rest
1287 : of the transaction checks for fuzzing convenience.
1288 :
1289 : For reference this is the general code path which contains all relevant
1290 : pre-transactions checks in the v2.0.x Agave client from upstream
1291 : to downstream is as follows:
1292 :
1293 : confirm_slot_entries() which calls verify_ticks() and
1294 : verify_transaction(). verify_transaction() calls verify_and_hash_message()
1295 : and verify_precompiles() which parallels fd_executor_txn_verify() and
1296 : fd_executor_verify_transaction().
1297 :
1298 : process_entries() contains a duplicate account check which is part of
1299 : agave account lock acquiring. This is checked inline in
1300 : fd_runtime_pre_execute_check().
1301 :
1302 : load_and_execute_transactions() contains the function check_transactions().
1303 : This contains check_age() and check_status_cache() which is paralleled by
1304 : fd_executor_check_transaction_age_and_compute_budget_limits() and
1305 : fd_executor_check_status_cache() respectively.
1306 :
1307 : load_and_execute_sanitized_transactions() contains validate_fees()
1308 : which is responsible for executing the compute budget instructions,
1309 : validating the fee payer and collecting the fee. This is mirrored in
1310 : firedancer with fd_executor_compute_budget_program_execute_instructions()
1311 : and fd_executor_collect_fees(). load_and_execute_sanitized_transactions()
1312 : also checks the total data size of the accounts in load_accounts() and
1313 : validates the program accounts in load_transaction_accounts(). This
1314 : is paralled by fd_executor_load_transaction_accounts(). */
1315 :
1316 : /******************************************************************************/
1317 : /* Epoch Boundary */
1318 : /******************************************************************************/
1319 :
1320 : /* Replace the vote states for T-2 (vote_states_prev_prev) with the vote
1321 : states for T-1 (vote_states_prev) */
1322 :
1323 : static void
1324 0 : fd_update_vote_states_prev_prev( fd_exec_slot_ctx_t * slot_ctx ) {
1325 :
1326 0 : fd_vote_states_t * vote_states_prev_prev = fd_bank_vote_states_prev_prev_locking_modify( slot_ctx->bank );
1327 0 : fd_vote_states_t const * vote_states_prev = fd_bank_vote_states_prev_locking_query( slot_ctx->bank );
1328 0 : fd_memcpy( vote_states_prev_prev, vote_states_prev, fd_bank_vote_states_footprint );
1329 0 : fd_bank_vote_states_prev_prev_end_locking_modify( slot_ctx->bank );
1330 0 : fd_bank_vote_states_prev_end_locking_query( slot_ctx->bank );
1331 0 : }
1332 :
1333 : /* Replace the vote states for T-1 (vote_states_prev) with the vote
1334 : states for T-1 (vote_states) */
1335 :
1336 : static void
1337 0 : fd_update_vote_states_prev( fd_exec_slot_ctx_t * slot_ctx ) {
1338 0 : fd_vote_states_t * vote_states_prev = fd_bank_vote_states_prev_locking_modify( slot_ctx->bank );
1339 0 : fd_vote_states_t const * vote_states = fd_bank_vote_states_locking_query( slot_ctx->bank );
1340 0 : fd_memcpy( vote_states_prev, vote_states, fd_bank_vote_states_footprint );
1341 0 : fd_bank_vote_states_prev_end_locking_modify( slot_ctx->bank );
1342 0 : fd_bank_vote_states_end_locking_query( slot_ctx->bank );
1343 0 : }
1344 :
1345 : void
1346 : fd_migrate_builtin_to_core_bpf( fd_exec_slot_ctx_t * slot_ctx,
1347 : fd_core_bpf_migration_config_t const * config,
1348 : fd_spad_t * runtime_spad );
1349 :
1350 : /* https://github.com/anza-xyz/agave/blob/v2.1.0/runtime/src/bank.rs#L6704 */
1351 : static void
1352 : fd_apply_builtin_program_feature_transitions( fd_exec_slot_ctx_t * slot_ctx,
1353 0 : fd_spad_t * runtime_spad ) {
1354 : /* TODO: Set the upgrade authority properly from the core bpf migration config. Right now it's set to None.
1355 :
1356 : Migrate any necessary stateless builtins to core BPF. So far,
1357 : the only "stateless" builtin is the Feature program. Beginning
1358 : checks in the migrate_builtin_to_core_bpf function will fail if the
1359 : program has already been migrated to BPF. */
1360 :
1361 0 : FD_SPAD_FRAME_BEGIN( runtime_spad ) {
1362 :
1363 0 : fd_builtin_program_t const * builtins = fd_builtins();
1364 0 : for( ulong i=0UL; i<fd_num_builtins(); i++ ) {
1365 : /* https://github.com/anza-xyz/agave/blob/v2.1.0/runtime/src/bank.rs#L6732-L6751 */
1366 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 ) ) {
1367 0 : FD_LOG_DEBUG(( "Migrating builtin program %s to core BPF", FD_BASE58_ENC_32_ALLOCA( builtins[i].pubkey->key ) ));
1368 0 : fd_migrate_builtin_to_core_bpf( slot_ctx, builtins[i].core_bpf_migration_config, runtime_spad );
1369 0 : }
1370 : /* https://github.com/anza-xyz/agave/blob/v2.1.0/runtime/src/bank.rs#L6753-L6774 */
1371 0 : if( builtins[i].enable_feature_offset!=NO_ENABLE_FEATURE_ID && FD_FEATURE_JUST_ACTIVATED_OFFSET( slot_ctx, builtins[i].enable_feature_offset ) ) {
1372 0 : FD_LOG_DEBUG(( "Enabling builtin program %s", FD_BASE58_ENC_32_ALLOCA( builtins[i].pubkey->key ) ));
1373 0 : fd_write_builtin_account( slot_ctx, *builtins[i].pubkey, builtins[i].data,strlen(builtins[i].data) );
1374 0 : }
1375 0 : }
1376 :
1377 : /* https://github.com/anza-xyz/agave/blob/v2.1.0/runtime/src/bank.rs#L6776-L6793 */
1378 0 : fd_stateless_builtin_program_t const * stateless_builtins = fd_stateless_builtins();
1379 0 : for( ulong i=0UL; i<fd_num_stateless_builtins(); i++ ) {
1380 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 ) ) {
1381 0 : FD_LOG_DEBUG(( "Migrating stateless builtin program %s to core BPF", FD_BASE58_ENC_32_ALLOCA( stateless_builtins[i].pubkey->key ) ));
1382 0 : fd_migrate_builtin_to_core_bpf( slot_ctx, stateless_builtins[i].core_bpf_migration_config, runtime_spad );
1383 0 : }
1384 0 : }
1385 :
1386 : /* https://github.com/anza-xyz/agave/blob/c1080de464cfb578c301e975f498964b5d5313db/runtime/src/bank.rs#L6795-L6805 */
1387 0 : fd_precompile_program_t const * precompiles = fd_precompiles();
1388 0 : for( ulong i=0UL; i<fd_num_precompiles(); i++ ) {
1389 0 : if( precompiles[i].feature_offset != NO_ENABLE_FEATURE_ID && FD_FEATURE_JUST_ACTIVATED_OFFSET( slot_ctx, precompiles[i].feature_offset ) ) {
1390 0 : fd_write_builtin_account( slot_ctx, *precompiles[i].pubkey, "", 0 );
1391 0 : }
1392 0 : }
1393 :
1394 0 : } FD_SPAD_FRAME_END;
1395 0 : }
1396 :
1397 : static void
1398 : fd_feature_activate( fd_features_t * features,
1399 : fd_exec_slot_ctx_t * slot_ctx,
1400 : fd_feature_id_t const * id,
1401 : uchar const acct[ static 32 ],
1402 0 : fd_spad_t * runtime_spad ) {
1403 :
1404 : // Skip reverted features from being activated
1405 0 : if( id->reverted==1 ) {
1406 0 : return;
1407 0 : }
1408 :
1409 0 : FD_TXN_ACCOUNT_DECL( acct_rec );
1410 0 : int err = fd_txn_account_init_from_funk_readonly( acct_rec, (fd_pubkey_t*)acct, slot_ctx->funk, slot_ctx->funk_txn );
1411 0 : if( FD_UNLIKELY( err != FD_ACC_MGR_SUCCESS ) ) {
1412 0 : return;
1413 0 : }
1414 :
1415 0 : FD_SPAD_FRAME_BEGIN( runtime_spad ) {
1416 :
1417 0 : int decode_err = 0;
1418 0 : fd_feature_t * feature = fd_bincode_decode_spad(
1419 0 : feature, runtime_spad,
1420 0 : fd_txn_account_get_data( acct_rec ),
1421 0 : fd_txn_account_get_data_len( acct_rec ),
1422 0 : &decode_err );
1423 0 : if( FD_UNLIKELY( decode_err ) ) {
1424 0 : FD_LOG_WARNING(( "Failed to decode feature account %s (%d)", FD_BASE58_ENC_32_ALLOCA( acct ), decode_err ));
1425 0 : return;
1426 0 : }
1427 :
1428 0 : if( feature->has_activated_at ) {
1429 0 : FD_LOG_DEBUG(( "feature already activated - acc: %s, slot: %lu", FD_BASE58_ENC_32_ALLOCA( acct ), feature->activated_at ));
1430 0 : fd_features_set( features, id, feature->activated_at);
1431 0 : } else {
1432 0 : FD_LOG_DEBUG(( "Feature %s not activated at %lu, activating", FD_BASE58_ENC_32_ALLOCA( acct ), feature->activated_at ));
1433 :
1434 0 : FD_TXN_ACCOUNT_DECL( modify_acct_rec );
1435 0 : fd_funk_rec_prepare_t modify_acct_prepare = {0};
1436 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, &modify_acct_prepare );
1437 0 : if( FD_UNLIKELY( err != FD_ACC_MGR_SUCCESS ) ) {
1438 0 : return;
1439 0 : }
1440 :
1441 0 : fd_lthash_value_t prev_hash[1];
1442 0 : fd_hashes_account_lthash(
1443 0 : fd_type_pun_const( acct ),
1444 0 : fd_txn_account_get_meta( modify_acct_rec ),
1445 0 : fd_txn_account_get_data( modify_acct_rec ),
1446 0 : prev_hash );
1447 :
1448 0 : feature->has_activated_at = 1;
1449 0 : feature->activated_at = fd_bank_slot_get( slot_ctx->bank );
1450 0 : fd_bincode_encode_ctx_t encode_ctx = {
1451 0 : .data = fd_txn_account_get_data_mut( modify_acct_rec ),
1452 0 : .dataend = fd_txn_account_get_data_mut( modify_acct_rec ) + fd_txn_account_get_data_len( modify_acct_rec ),
1453 0 : };
1454 0 : int encode_err = fd_feature_encode( feature, &encode_ctx );
1455 0 : if( FD_UNLIKELY( encode_err != FD_BINCODE_SUCCESS ) ) {
1456 0 : FD_LOG_ERR(( "Failed to encode feature account %s (%d)", FD_BASE58_ENC_32_ALLOCA( acct ), decode_err ));
1457 0 : }
1458 :
1459 0 : fd_hashes_update_lthash( modify_acct_rec, prev_hash, slot_ctx->bank, slot_ctx->capture_ctx );
1460 0 : fd_txn_account_mutable_fini( modify_acct_rec, slot_ctx->funk, slot_ctx->funk_txn, &modify_acct_prepare );
1461 0 : }
1462 :
1463 0 : } FD_SPAD_FRAME_END;
1464 0 : }
1465 :
1466 : static void
1467 : fd_features_activate( fd_exec_slot_ctx_t * slot_ctx,
1468 0 : fd_spad_t * runtime_spad ) {
1469 0 : fd_features_t * features = fd_bank_features_modify( slot_ctx->bank );
1470 0 : for( fd_feature_id_t const * id = fd_feature_iter_init();
1471 0 : !fd_feature_iter_done( id );
1472 0 : id = fd_feature_iter_next( id ) ) {
1473 0 : fd_feature_activate( features, slot_ctx, id, id->id.key, runtime_spad );
1474 0 : }
1475 0 : }
1476 :
1477 : uint
1478 : fd_runtime_is_epoch_boundary( fd_exec_slot_ctx_t * slot_ctx,
1479 : ulong curr_slot,
1480 0 : ulong prev_slot ) {
1481 0 : ulong slot_idx;
1482 0 : fd_epoch_schedule_t const * schedule = fd_bank_epoch_schedule_query( slot_ctx->bank );
1483 0 : ulong prev_epoch = fd_slot_to_epoch( schedule, prev_slot, &slot_idx );
1484 0 : ulong new_epoch = fd_slot_to_epoch( schedule, curr_slot, &slot_idx );
1485 :
1486 0 : return ( prev_epoch < new_epoch || slot_idx == 0 );
1487 0 : }
1488 :
1489 : /* Starting a new epoch.
1490 : New epoch: T
1491 : Just ended epoch: T-1
1492 : Epoch before: T-2
1493 :
1494 : In this function:
1495 : - stakes in T-2 (vote_states_prev_prev) should be replaced by T-1 (vote_states_prev)
1496 : - stakes at T-1 (vote_states_prev) should be replaced by updated stakes at T (vote_states)
1497 : - leader schedule should be calculated using new T-2 stakes (vote_states_prev_prev)
1498 :
1499 : Invariant during an epoch T:
1500 : vote_states_prev holds the stakes at T-1
1501 : vote_states_prev_prev holds the stakes at T-2
1502 : */
1503 : /* process for the start of a new epoch */
1504 : static void
1505 : fd_runtime_process_new_epoch( fd_exec_slot_ctx_t * slot_ctx,
1506 : ulong parent_epoch,
1507 0 : fd_spad_t * runtime_spad ) {
1508 0 : FD_LOG_NOTICE(( "fd_process_new_epoch start, epoch: %lu, slot: %lu", fd_bank_epoch_get( slot_ctx->bank ), fd_bank_slot_get( slot_ctx->bank ) ));
1509 :
1510 0 : FD_SPAD_FRAME_BEGIN( runtime_spad ) {
1511 :
1512 0 : fd_stake_delegations_t const * stake_delegations = fd_bank_stake_delegations_frontier_query( slot_ctx->banks, slot_ctx->bank );
1513 0 : if( FD_UNLIKELY( !stake_delegations ) ) {
1514 0 : FD_LOG_CRIT(( "stake_delegations is NULL" ));
1515 0 : }
1516 :
1517 0 : long start = fd_log_wallclock();
1518 :
1519 0 : ulong const slot = fd_bank_slot_get ( slot_ctx->bank );
1520 :
1521 : /* Activate new features
1522 : https://github.com/anza-xyz/agave/blob/v2.1.0/runtime/src/bank.rs#L6587-L6598 */
1523 0 : fd_features_activate( slot_ctx, runtime_spad );
1524 0 : fd_features_restore( slot_ctx, runtime_spad );
1525 :
1526 : /* Apply builtin program feature transitions
1527 : https://github.com/anza-xyz/agave/blob/v2.1.0/runtime/src/bank.rs#L6621-L6624 */
1528 0 : fd_apply_builtin_program_feature_transitions( slot_ctx, runtime_spad );
1529 :
1530 : /* Get the new rate activation epoch */
1531 0 : int _err[1];
1532 0 : ulong new_rate_activation_epoch_val = 0UL;
1533 0 : ulong * new_rate_activation_epoch = &new_rate_activation_epoch_val;
1534 0 : int is_some = fd_new_warmup_cooldown_rate_epoch(
1535 0 : fd_bank_epoch_schedule_query( slot_ctx->bank ),
1536 0 : fd_bank_features_query( slot_ctx->bank ),
1537 0 : slot,
1538 0 : new_rate_activation_epoch,
1539 0 : _err );
1540 0 : if( FD_UNLIKELY( !is_some ) ) {
1541 0 : new_rate_activation_epoch = NULL;
1542 0 : }
1543 :
1544 : /* Updates stake history sysvar accumulated values. */
1545 0 : fd_stakes_activate_epoch( slot_ctx, stake_delegations, new_rate_activation_epoch, runtime_spad );
1546 :
1547 : /* Refresh vote accounts in stakes cache using updated stake weights, and merges slot bank vote accounts with the epoch bank vote accounts.
1548 : https://github.com/anza-xyz/agave/blob/v2.1.6/runtime/src/stakes.rs#L363-L370 */
1549 0 : fd_stake_history_t const * history = fd_sysvar_stake_history_read( slot_ctx->funk, slot_ctx->funk_txn, runtime_spad );
1550 0 : if( FD_UNLIKELY( !history ) ) {
1551 0 : FD_LOG_ERR(( "StakeHistory sysvar could not be read and decoded" ));
1552 0 : }
1553 :
1554 : /* Now increment the epoch */
1555 :
1556 0 : fd_bank_epoch_set( slot_ctx->bank, fd_bank_epoch_get( slot_ctx->bank ) + 1UL );
1557 :
1558 0 : fd_refresh_vote_accounts( slot_ctx,
1559 0 : stake_delegations,
1560 0 : history,
1561 0 : new_rate_activation_epoch );
1562 :
1563 : /* Distribute rewards */
1564 :
1565 0 : fd_hash_t parent_blockhash = {0};
1566 0 : {
1567 0 : fd_blockhashes_t const * bhq = fd_bank_block_hash_queue_query( slot_ctx->bank );
1568 0 : fd_hash_t const * bhq_last = fd_blockhashes_peek_last( bhq );
1569 0 : FD_TEST( bhq_last );
1570 0 : parent_blockhash = *bhq_last;
1571 0 : }
1572 :
1573 0 : fd_begin_partitioned_rewards( slot_ctx,
1574 0 : stake_delegations,
1575 0 : slot_ctx->capture_ctx,
1576 0 : &parent_blockhash,
1577 0 : parent_epoch,
1578 0 : runtime_spad );
1579 :
1580 :
1581 : /* Update vote_states_prev_prev with vote_states_prev */
1582 :
1583 0 : fd_update_vote_states_prev_prev( slot_ctx );
1584 :
1585 : /* Update vote_states_prev with vote_states */
1586 :
1587 0 : fd_update_vote_states_prev( slot_ctx );
1588 :
1589 : /* Update current leaders using epoch_stakes (new T-2 stakes) */
1590 :
1591 0 : fd_runtime_update_leaders( slot_ctx->bank, fd_bank_slot_get( slot_ctx->bank ), runtime_spad );
1592 :
1593 0 : FD_LOG_NOTICE(( "fd_process_new_epoch end" ));
1594 :
1595 0 : long end = fd_log_wallclock();
1596 0 : FD_LOG_NOTICE(("fd_process_new_epoch took %ld ns", end - start));
1597 :
1598 0 : } FD_SPAD_FRAME_END;
1599 0 : }
1600 :
1601 : /******************************************************************************/
1602 : /* Block Parsing */
1603 : /******************************************************************************/
1604 :
1605 : /* Block iteration and parsing */
1606 :
1607 : /* As a note, all of the logic in this section is used by the full firedancer
1608 : client. The store tile uses these APIs to help parse raw (micro)blocks
1609 : received from the network. */
1610 :
1611 : /* Helpers */
1612 :
1613 : void
1614 : fd_runtime_update_program_cache( fd_exec_slot_ctx_t * slot_ctx,
1615 : fd_txn_p_t const * txn_p,
1616 0 : fd_spad_t * runtime_spad ) {
1617 0 : fd_txn_t const * txn_descriptor = TXN( txn_p );
1618 :
1619 0 : FD_SPAD_FRAME_BEGIN( runtime_spad ) {
1620 :
1621 : /* Iterate over account keys referenced directly in the transaction first */
1622 0 : fd_acct_addr_t const * acc_addrs = fd_txn_get_acct_addrs( txn_descriptor, txn_p );
1623 0 : for( ushort acc_idx=0; acc_idx<txn_descriptor->acct_addr_cnt; acc_idx++ ) {
1624 0 : fd_pubkey_t const * account = fd_type_pun_const( &acc_addrs[acc_idx] );
1625 0 : fd_program_cache_update_program( slot_ctx, account, runtime_spad );
1626 0 : }
1627 :
1628 0 : if( txn_descriptor->transaction_version==FD_TXN_V0 ) {
1629 :
1630 : /* Iterate over account keys referenced in ALUTs */
1631 0 : fd_acct_addr_t alut_accounts[256];
1632 0 : fd_slot_hashes_global_t const * slot_hashes_global = fd_sysvar_slot_hashes_read( slot_ctx->funk, slot_ctx->funk_txn, runtime_spad );
1633 0 : if( FD_UNLIKELY( !slot_hashes_global ) ) {
1634 0 : return;
1635 0 : }
1636 :
1637 0 : fd_slot_hash_t * slot_hash = deq_fd_slot_hash_t_join( (uchar *)slot_hashes_global + slot_hashes_global->hashes_offset );
1638 :
1639 : /* TODO: This is done twice, once in the replay tile and once in the
1640 : exec tile. We should consolidate the account resolution into a
1641 : single place, but also keep in mind from a conformance
1642 : perspective that these ALUT resolution checks happen after some
1643 : things like compute budget instruction parsing */
1644 0 : if( FD_UNLIKELY( fd_runtime_load_txn_address_lookup_tables(
1645 0 : txn_descriptor,
1646 0 : txn_p->payload,
1647 0 : slot_ctx->funk,
1648 0 : slot_ctx->funk_txn,
1649 0 : fd_bank_slot_get( slot_ctx->bank ),
1650 0 : slot_hash,
1651 0 : alut_accounts ) ) ) {
1652 0 : return;
1653 0 : }
1654 :
1655 0 : for( ushort alut_idx=0; alut_idx<txn_descriptor->addr_table_adtl_cnt; alut_idx++ ) {
1656 0 : fd_pubkey_t const * account = fd_type_pun_const( &alut_accounts[alut_idx] );
1657 0 : fd_program_cache_update_program( slot_ctx, account, runtime_spad );
1658 0 : }
1659 0 : }
1660 :
1661 0 : } FD_SPAD_FRAME_END;
1662 0 : }
1663 :
1664 : /******************************************************************************/
1665 : /* Genesis */
1666 : /*******************************************************************************/
1667 :
1668 : static void
1669 : fd_runtime_genesis_init_program( fd_exec_slot_ctx_t * slot_ctx,
1670 0 : fd_spad_t * runtime_spad ) {
1671 :
1672 0 : fd_sysvar_clock_init( slot_ctx );
1673 0 : fd_sysvar_rent_init( slot_ctx );
1674 :
1675 0 : fd_sysvar_slot_history_init( slot_ctx, runtime_spad );
1676 0 : fd_sysvar_epoch_schedule_init( slot_ctx );
1677 0 : fd_sysvar_recent_hashes_init( slot_ctx );
1678 0 : fd_sysvar_stake_history_init( slot_ctx );
1679 0 : fd_sysvar_last_restart_slot_init( slot_ctx );
1680 :
1681 0 : fd_builtin_programs_init( slot_ctx );
1682 0 : fd_stake_program_config_init( slot_ctx );
1683 0 : }
1684 :
1685 : static void
1686 : fd_runtime_init_bank_from_genesis( fd_exec_slot_ctx_t * slot_ctx,
1687 : fd_genesis_solana_global_t const * genesis_block,
1688 : fd_hash_t const * genesis_hash,
1689 0 : fd_spad_t * runtime_spad ) {
1690 :
1691 0 : fd_bank_poh_set( slot_ctx->bank, *genesis_hash );
1692 :
1693 0 : fd_hash_t * bank_hash = fd_bank_bank_hash_modify( slot_ctx->bank );
1694 0 : memset( bank_hash->hash, 0, FD_SHA256_HASH_SZ );
1695 :
1696 0 : fd_poh_config_global_t const * poh = &genesis_block->poh_config;
1697 0 : uint128 target_tick_duration = ((uint128)poh->target_tick_duration.seconds * 1000000000UL + (uint128)poh->target_tick_duration.nanoseconds);
1698 :
1699 0 : fd_bank_epoch_schedule_set( slot_ctx->bank, genesis_block->epoch_schedule );
1700 :
1701 0 : fd_bank_rent_set( slot_ctx->bank, genesis_block->rent );
1702 :
1703 0 : fd_bank_block_height_set( slot_ctx->bank, 0UL );
1704 :
1705 0 : fd_bank_inflation_set( slot_ctx->bank, genesis_block->inflation );
1706 :
1707 0 : {
1708 : /* FIXME Why is there a previous blockhash at genesis? Why is the
1709 : last_hash field an option type in Agave, if even the first
1710 : real block has a previous blockhash? */
1711 0 : ulong seed; FD_TEST( fd_rng_secure( &seed, sizeof(ulong) ) );
1712 0 : fd_blockhashes_t * bhq = fd_blockhashes_init( fd_bank_block_hash_queue_modify( slot_ctx->bank ), seed );
1713 0 : fd_blockhash_info_t * info = fd_blockhashes_push_new( bhq, genesis_hash );
1714 0 : info->fee_calculator.lamports_per_signature = 0UL;
1715 0 : }
1716 :
1717 0 : fd_bank_fee_rate_governor_set( slot_ctx->bank, genesis_block->fee_rate_governor );
1718 :
1719 0 : fd_bank_lamports_per_signature_set( slot_ctx->bank, 0UL );
1720 :
1721 0 : fd_bank_prev_lamports_per_signature_set( slot_ctx->bank, 0UL );
1722 :
1723 0 : fd_bank_max_tick_height_set( slot_ctx->bank, genesis_block->ticks_per_slot * (fd_bank_slot_get( slot_ctx->bank ) + 1) );
1724 :
1725 0 : fd_bank_hashes_per_tick_set( slot_ctx->bank, !!poh->hashes_per_tick ? poh->hashes_per_tick : 0UL );
1726 :
1727 0 : fd_bank_ns_per_slot_set( slot_ctx->bank, target_tick_duration * genesis_block->ticks_per_slot );
1728 :
1729 0 : fd_bank_ticks_per_slot_set( slot_ctx->bank, genesis_block->ticks_per_slot );
1730 :
1731 0 : fd_bank_genesis_creation_time_set( slot_ctx->bank, genesis_block->creation_time );
1732 :
1733 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 );
1734 :
1735 0 : fd_bank_signature_count_set( slot_ctx->bank, 0UL );
1736 :
1737 : /* Derive epoch stakes */
1738 :
1739 0 : fd_stake_delegations_t * stake_delegations = fd_banks_stake_delegations_root_query( slot_ctx->banks );
1740 0 : if( FD_UNLIKELY( !stake_delegations ) ) {
1741 0 : FD_LOG_CRIT(( "Failed to join and new a stake delegations" ));
1742 0 : }
1743 :
1744 0 : fd_vote_states_t * vote_states = fd_vote_states_join( fd_vote_states_new( fd_bank_vote_states_locking_modify( slot_ctx->bank ), FD_RUNTIME_MAX_VOTE_ACCOUNTS, 999UL ) );
1745 0 : if( FD_UNLIKELY( !vote_states ) ) {
1746 0 : FD_LOG_CRIT(( "Failed to join and new a vote states" ));
1747 0 : }
1748 :
1749 0 : ulong capitalization = 0UL;
1750 :
1751 0 : fd_pubkey_account_pair_global_t const * accounts = fd_genesis_solana_accounts_join( genesis_block );
1752 :
1753 0 : for( ulong i=0UL; i<genesis_block->accounts_len; i++ ) {
1754 0 : fd_pubkey_account_pair_global_t const * acc = &accounts[ i ];
1755 0 : capitalization = fd_ulong_sat_add( capitalization, acc->account.lamports );
1756 :
1757 0 : uchar const * acc_data = fd_solana_account_data_join( &acc->account );
1758 :
1759 0 : if( !memcmp(acc->account.owner.key, fd_solana_vote_program_id.key, sizeof(fd_pubkey_t)) ) {
1760 : /* This means that there is a vote account which should be
1761 : inserted into the vote states. Even after the vote account is
1762 : inserted, we still don't know the total amount of stake that is
1763 : delegated to the vote account. This must be calculated later. */
1764 0 : fd_vote_states_update_from_account( vote_states, &acc->key, acc_data, acc->account.data_len );
1765 0 : } else if( !memcmp( acc->account.owner.key, fd_solana_stake_program_id.key, sizeof(fd_pubkey_t) ) ) {
1766 : /* If an account is a stake account, then it must be added to the
1767 : stake delegations cache. We should only add stake accounts that
1768 : have a valid non-zero stake. */
1769 0 : fd_stake_state_v2_t stake_state = {0};
1770 0 : if( FD_UNLIKELY( !fd_bincode_decode_static(
1771 0 : stake_state_v2, &stake_state,
1772 0 : acc_data, acc->account.data_len,
1773 0 : NULL ) ) ) {
1774 0 : FD_BASE58_ENCODE_32_BYTES( acc->key.key, stake_b58 );
1775 0 : FD_LOG_ERR(( "Failed to deserialize genesis stake account %s", stake_b58 ));
1776 0 : }
1777 0 : if( !fd_stake_state_v2_is_stake( &stake_state ) ) continue;
1778 0 : if( !stake_state.inner.stake.stake.delegation.stake ) continue;
1779 :
1780 0 : fd_stake_delegations_update(
1781 0 : stake_delegations,
1782 0 : (fd_pubkey_t *)acc->key.key,
1783 0 : &stake_state.inner.stake.stake.delegation.voter_pubkey,
1784 0 : stake_state.inner.stake.stake.delegation.stake,
1785 0 : stake_state.inner.stake.stake.delegation.activation_epoch,
1786 0 : stake_state.inner.stake.stake.delegation.deactivation_epoch,
1787 0 : stake_state.inner.stake.stake.credits_observed,
1788 0 : stake_state.inner.stake.stake.delegation.warmup_cooldown_rate );
1789 :
1790 0 : } else if( !memcmp(acc->account.owner.key, fd_solana_feature_program_id.key, sizeof(fd_pubkey_t)) ) {
1791 : /* Feature Account */
1792 :
1793 : /* Scan list of feature IDs to resolve address=>feature offset */
1794 0 : fd_feature_id_t const *found = NULL;
1795 0 : for( fd_feature_id_t const * id = fd_feature_iter_init();
1796 0 : !fd_feature_iter_done( id );
1797 0 : id = fd_feature_iter_next( id ) ) {
1798 0 : if( !memcmp( acc->key.key, id->id.key, sizeof(fd_pubkey_t) ) ) {
1799 0 : found = id;
1800 0 : break;
1801 0 : }
1802 0 : }
1803 :
1804 0 : if( found ) {
1805 : /* Load feature activation */
1806 0 : FD_SPAD_FRAME_BEGIN( runtime_spad ) {
1807 0 : int err;
1808 0 : fd_feature_t * feature = fd_bincode_decode_spad(
1809 0 : feature, runtime_spad,
1810 0 : acc_data,
1811 0 : acc->account.data_len,
1812 0 : &err );
1813 0 : FD_TEST( err==FD_BINCODE_SUCCESS );
1814 :
1815 0 : fd_features_t * features = fd_bank_features_modify( slot_ctx->bank );
1816 0 : if( feature->has_activated_at ) {
1817 0 : FD_LOG_DEBUG(( "Feature %s activated at %lu (genesis)", FD_BASE58_ENC_32_ALLOCA( acc->key.key ), feature->activated_at ));
1818 0 : fd_features_set( features, found, feature->activated_at );
1819 0 : } else {
1820 0 : FD_LOG_DEBUG(( "Feature %s not activated (genesis)", FD_BASE58_ENC_32_ALLOCA( acc->key.key ) ));
1821 0 : fd_features_set( features, found, ULONG_MAX );
1822 0 : }
1823 0 : } FD_SPAD_FRAME_END;
1824 0 : }
1825 0 : }
1826 0 : }
1827 0 : fd_bank_vote_states_end_locking_modify( slot_ctx->bank );
1828 :
1829 : /* fd_refresh_vote_accounts is responsible for updating the vote
1830 : states with the total amount of active delegated stake. It does
1831 : this by iterating over all active stake delegations and summing up
1832 : the amount of stake that is delegated to each vote account. */
1833 :
1834 0 : ulong new_rate_activation_epoch = 0UL;
1835 0 : fd_stake_history_t * stake_history = fd_sysvar_stake_history_read( slot_ctx->funk, slot_ctx->funk_txn, runtime_spad );
1836 0 : fd_refresh_vote_accounts(
1837 0 : slot_ctx,
1838 0 : stake_delegations,
1839 0 : stake_history,
1840 0 : &new_rate_activation_epoch );
1841 :
1842 : /* Now that the stake and vote delegations are updated correctly, we
1843 : will propagate the vote states to the vote states for the previous
1844 : epoch and the epoch before that.
1845 :
1846 : This is despite the fact we are booting off of genesis which means
1847 : that there is no previous or previous-previous epoch. This is done
1848 : to simplify edge cases around leader schedule and rewards
1849 : calculation.
1850 :
1851 : TODO: Each of the edge cases around this needs to be documented
1852 : much better where each case is clearly enumerated and explained. */
1853 :
1854 0 : fd_vote_states_t const * vote_states_curr = fd_bank_vote_states_locking_query( slot_ctx->bank );
1855 :
1856 0 : fd_vote_states_t * vote_states_prev_prev = fd_vote_states_join( fd_vote_states_new( fd_bank_vote_states_prev_prev_locking_modify( slot_ctx->bank ), FD_RUNTIME_MAX_VOTE_ACCOUNTS, 999UL ) );
1857 0 : fd_vote_states_t * vote_states_prev = fd_vote_states_join( fd_vote_states_new( fd_bank_vote_states_prev_locking_modify( slot_ctx->bank ), FD_RUNTIME_MAX_VOTE_ACCOUNTS, 999UL ) );
1858 :
1859 0 : for( ulong i=0UL; i<genesis_block->accounts_len; i++ ) {
1860 0 : fd_pubkey_account_pair_global_t const * acc = &accounts[ i ];
1861 :
1862 0 : uchar const * acc_data = fd_solana_account_data_join( &acc->account );
1863 :
1864 0 : if( !memcmp( acc->account.owner.key, fd_solana_vote_program_id.key, sizeof(fd_pubkey_t) ) ) {
1865 0 : fd_vote_state_ele_t * vote_state = fd_vote_states_query( vote_states_curr, &acc->key );
1866 0 : fd_vote_states_update_from_account( vote_states_prev_prev, &acc->key, acc_data, acc->account.data_len );
1867 0 : fd_vote_states_update_from_account( vote_states_prev, &acc->key, acc_data, acc->account.data_len );
1868 0 : fd_vote_states_update_stake( vote_states_prev, &acc->key, vote_state->stake );
1869 0 : fd_vote_states_update_stake( vote_states_prev_prev, &acc->key, vote_state->stake );
1870 0 : }
1871 0 : }
1872 :
1873 0 : fd_bank_vote_states_prev_prev_end_locking_modify( slot_ctx->bank );
1874 0 : fd_bank_vote_states_prev_end_locking_modify( slot_ctx->bank );
1875 0 : fd_bank_vote_states_end_locking_query( slot_ctx->bank );
1876 :
1877 0 : fd_bank_epoch_set( slot_ctx->bank, 0UL );
1878 :
1879 0 : fd_bank_capitalization_set( slot_ctx->bank, capitalization );
1880 0 : }
1881 :
1882 : static int
1883 : fd_runtime_process_genesis_block( fd_exec_slot_ctx_t * slot_ctx,
1884 0 : fd_spad_t * runtime_spad ) {
1885 :
1886 0 : fd_hash_t * poh = fd_bank_poh_modify( slot_ctx->bank );
1887 0 : ulong hashcnt_per_slot = fd_bank_hashes_per_tick_get( slot_ctx->bank ) * fd_bank_ticks_per_slot_get( slot_ctx->bank );
1888 0 : while( hashcnt_per_slot-- ) {
1889 0 : fd_sha256_hash( poh->hash, sizeof(fd_hash_t), poh->hash );
1890 0 : }
1891 :
1892 0 : fd_bank_execution_fees_set( slot_ctx->bank, 0UL );
1893 :
1894 0 : fd_bank_priority_fees_set( slot_ctx->bank, 0UL );
1895 :
1896 0 : fd_bank_signature_count_set( slot_ctx->bank, 0UL );
1897 :
1898 0 : fd_bank_txn_count_set( slot_ctx->bank, 0UL );
1899 :
1900 0 : fd_bank_failed_txn_count_set( slot_ctx->bank, 0UL );
1901 :
1902 0 : fd_bank_nonvote_failed_txn_count_set( slot_ctx->bank, 0UL );
1903 :
1904 0 : fd_bank_total_compute_units_used_set( slot_ctx->bank, 0UL );
1905 :
1906 0 : fd_runtime_genesis_init_program( slot_ctx, runtime_spad );
1907 :
1908 0 : fd_sysvar_slot_history_update( slot_ctx );
1909 :
1910 0 : fd_runtime_update_leaders( slot_ctx->bank, 0, runtime_spad );
1911 :
1912 0 : fd_runtime_freeze( slot_ctx );
1913 :
1914 0 : fd_lthash_value_t const * lthash = fd_bank_lthash_locking_query( slot_ctx->bank );
1915 :
1916 0 : fd_hash_t const * prev_bank_hash = fd_bank_bank_hash_query( slot_ctx->bank );
1917 :
1918 0 : fd_hash_t * bank_hash = fd_bank_bank_hash_modify( slot_ctx->bank );
1919 0 : fd_hashes_hash_bank(
1920 0 : lthash,
1921 0 : prev_bank_hash,
1922 0 : (fd_hash_t *)fd_bank_poh_query( slot_ctx->bank )->hash,
1923 0 : 0UL,
1924 0 : bank_hash );
1925 :
1926 0 : fd_bank_lthash_end_locking_query( slot_ctx->bank );
1927 :
1928 0 : return FD_RUNTIME_EXECUTE_SUCCESS;
1929 0 : }
1930 :
1931 : void
1932 : fd_runtime_read_genesis( fd_exec_slot_ctx_t * slot_ctx,
1933 : fd_hash_t const * genesis_hash,
1934 : fd_lthash_value_t const * genesis_lthash,
1935 : fd_genesis_solana_global_t const * genesis_block,
1936 0 : fd_spad_t * runtime_spad ) {
1937 0 : FD_SPAD_FRAME_BEGIN( runtime_spad ) {
1938 :
1939 0 : fd_lthash_value_t * lthash = fd_bank_lthash_locking_modify( slot_ctx->bank );
1940 0 : fd_memcpy( lthash, genesis_lthash, sizeof(fd_lthash_value_t) );
1941 0 : fd_bank_lthash_end_locking_modify( slot_ctx->bank );
1942 :
1943 : /* Once the accounts have been loaded from the genesis config into
1944 : the accounts db, we can initialize the bank state. This involves
1945 : setting some fields, and notably setting up the vote and stake
1946 : caches which are used for leader scheduling/rewards. */
1947 :
1948 0 : fd_runtime_init_bank_from_genesis( slot_ctx, genesis_block, genesis_hash, runtime_spad );
1949 :
1950 : /* Write the native programs to the accounts db. */
1951 :
1952 0 : fd_string_pubkey_pair_global_t * nips = fd_genesis_solana_native_instruction_processors_join( genesis_block );
1953 :
1954 0 : for( ulong i=0UL; i<genesis_block->native_instruction_processors_len; i++ ) {
1955 0 : fd_string_pubkey_pair_global_t const * a = &nips[ i ];
1956 :
1957 0 : uchar const * string = fd_string_pubkey_pair_string_join( a );
1958 0 : fd_write_builtin_account( slot_ctx, a->pubkey, (const char *)string, a->string_len );
1959 0 : }
1960 :
1961 0 : fd_features_restore( slot_ctx, runtime_spad );
1962 :
1963 : /* At this point, state related to the bank and the accounts db
1964 : have been initialized and we are free to finish executing the
1965 : block. In practice, this updates some bank fields (notably the
1966 : poh and bank hash). */
1967 :
1968 0 : int err = fd_runtime_process_genesis_block( slot_ctx, runtime_spad );
1969 0 : if( FD_UNLIKELY( err ) ) FD_LOG_CRIT(( "genesis slot 0 execute failed with error %d", err ));
1970 :
1971 0 : } FD_SPAD_FRAME_END;
1972 0 : }
1973 :
1974 : /******************************************************************************/
1975 : /* Offline Replay */
1976 : /******************************************************************************/
1977 :
1978 : /* As a note, currently offline and live replay of transactions has differences
1979 : with regards to how the execution environment is setup. These are helpers
1980 : used to emulate this behavior */
1981 :
1982 : void
1983 : fd_runtime_block_pre_execute_process_new_epoch( fd_exec_slot_ctx_t * slot_ctx,
1984 : fd_capture_ctx_t * capture_ctx,
1985 : fd_spad_t * runtime_spad,
1986 0 : int * is_epoch_boundary ) {
1987 :
1988 0 : ulong const slot = fd_bank_slot_get( slot_ctx->bank );
1989 0 : if( slot != 0UL ) {
1990 0 : fd_epoch_schedule_t const * epoch_schedule = fd_bank_epoch_schedule_query( slot_ctx->bank );
1991 :
1992 0 : ulong prev_epoch = fd_slot_to_epoch( epoch_schedule, fd_bank_parent_slot_get( slot_ctx->bank ), NULL );
1993 0 : ulong slot_idx;
1994 0 : ulong new_epoch = fd_slot_to_epoch( epoch_schedule, slot, &slot_idx );
1995 0 : if( FD_UNLIKELY( slot_idx==1UL && new_epoch==0UL ) ) {
1996 : /* The block after genesis has a height of 1. */
1997 0 : fd_bank_block_height_set( slot_ctx->bank, 1UL );
1998 0 : }
1999 :
2000 0 : if( FD_UNLIKELY( prev_epoch<new_epoch || !slot_idx ) ) {
2001 0 : FD_LOG_DEBUG(( "Epoch boundary" ));
2002 : /* Epoch boundary! */
2003 0 : fd_runtime_process_new_epoch( slot_ctx, prev_epoch, runtime_spad );
2004 0 : *is_epoch_boundary = 1;
2005 0 : }
2006 0 : } else {
2007 0 : *is_epoch_boundary = 0;
2008 0 : }
2009 :
2010 0 : if( FD_LIKELY( fd_bank_slot_get( slot_ctx->bank )!=0UL ) ) {
2011 0 : fd_distribute_partitioned_epoch_rewards( slot_ctx, capture_ctx );
2012 0 : }
2013 0 : }
2014 :
2015 : /******************************************************************************/
2016 : /* Debugging Tools */
2017 : /******************************************************************************/
2018 :
2019 : void
2020 : fd_runtime_checkpt( fd_capture_ctx_t * capture_ctx,
2021 : fd_exec_slot_ctx_t * slot_ctx,
2022 0 : ulong slot ) {
2023 0 : int is_checkpt_freq = capture_ctx != NULL && slot % capture_ctx->checkpt_freq == 0;
2024 0 : int is_abort_slot = slot == ULONG_MAX;
2025 0 : if( !is_checkpt_freq && !is_abort_slot ) {
2026 0 : return;
2027 0 : }
2028 :
2029 0 : if( capture_ctx->checkpt_path != NULL ) {
2030 0 : if( !is_abort_slot ) {
2031 0 : FD_LOG_NOTICE(( "checkpointing at slot=%lu to file=%s", slot, capture_ctx->checkpt_path ));
2032 0 : } else {
2033 0 : FD_LOG_NOTICE(( "checkpointing after mismatch to file=%s", capture_ctx->checkpt_path ));
2034 0 : }
2035 :
2036 0 : unlink( capture_ctx->checkpt_path );
2037 0 : int err = fd_wksp_checkpt( fd_funk_wksp( slot_ctx->funk ), capture_ctx->checkpt_path, 0666, 0, NULL );
2038 0 : if ( err ) {
2039 0 : FD_LOG_ERR(( "backup failed: error %d", err ));
2040 0 : }
2041 0 : }
2042 0 : }
2043 :
2044 : void
2045 0 : fd_runtime_block_execute_finalize( fd_exec_slot_ctx_t * slot_ctx ) {
2046 :
2047 : /* This slot is now "frozen" and can't be changed anymore. */
2048 0 : fd_runtime_freeze( slot_ctx );
2049 :
2050 0 : fd_runtime_update_bank_hash( slot_ctx );
2051 0 : }
|