Line data Source code
1 : #include "fd_system_program.h"
2 : #include "../fd_borrowed_account.h"
3 : #include "../fd_acc_mgr.h"
4 : #include "../fd_system_ids.h"
5 : #include "../sysvar/fd_sysvar_rent.h"
6 : #include "../sysvar/fd_sysvar_recent_hashes.h"
7 : #include "../../log_collector/fd_log_collector.h"
8 :
9 : static int
10 : require_acct( fd_exec_instr_ctx_t * ctx,
11 : ushort idx,
12 0 : fd_pubkey_t const * pubkey ) {
13 :
14 : /* https://github.com/anza-xyz/agave/blob/v2.1.14/program-runtime/src/sysvar_cache.rs#L290-L294 */
15 0 : fd_pubkey_t const * acc_key = NULL;
16 0 : int err = fd_exec_instr_ctx_get_key_of_account_at_index( ctx, idx, &acc_key );
17 0 : if( FD_UNLIKELY( err ) ) return err;
18 :
19 0 : if( FD_UNLIKELY( 0!=memcmp( acc_key, pubkey->uc, sizeof(fd_pubkey_t) ) ) )
20 0 : return FD_EXECUTOR_INSTR_ERR_INVALID_ARG;
21 :
22 0 : return FD_EXECUTOR_INSTR_SUCCESS;
23 0 : }
24 :
25 : static int
26 : require_acct_rent( fd_exec_instr_ctx_t * ctx,
27 : ushort idx,
28 0 : fd_rent_t * rent ) {
29 :
30 0 : do {
31 0 : int err = require_acct( ctx, idx, &fd_sysvar_rent_id );
32 0 : if( FD_UNLIKELY( err ) ) return err;
33 0 : } while(0);
34 :
35 0 : if( FD_UNLIKELY( !fd_sysvar_cache_rent_read( ctx->sysvar_cache, rent ) ) )
36 0 : return FD_EXECUTOR_INSTR_ERR_UNSUPPORTED_SYSVAR;
37 :
38 0 : return FD_EXECUTOR_INSTR_SUCCESS;
39 0 : }
40 :
41 : static int
42 : require_acct_recent_blockhashes( fd_exec_instr_ctx_t * ctx,
43 0 : ushort idx ) {
44 0 : int err = require_acct( ctx, idx, &fd_sysvar_recent_block_hashes_id );
45 0 : if( FD_UNLIKELY( err ) ) return err;
46 :
47 0 : if( FD_UNLIKELY( !fd_sysvar_cache_recent_hashes_is_valid( ctx->sysvar_cache ) ) ) {
48 0 : return FD_EXECUTOR_INSTR_ERR_UNSUPPORTED_SYSVAR;
49 0 : }
50 :
51 0 : return FD_EXECUTOR_INSTR_SUCCESS;
52 0 : }
53 :
54 : /* most_recent_block_hash mirrors
55 : solana_runtime::bank::Bank::last_blockhash_and_lamports_per_signature
56 :
57 : https://github.com/solana-labs/solana/blob/v1.17.23/runtime/src/bank.rs#L4033-L4040 */
58 :
59 : static int
60 : most_recent_block_hash( fd_exec_instr_ctx_t * ctx,
61 0 : fd_blockhash_info_t * out ) {
62 : /* The environment config blockhash comes from `bank.last_blockhash_and_lamports_per_signature()`,
63 : which takes the top element from the blockhash queue.
64 : https://github.com/anza-xyz/agave/blob/v2.1.6/programs/system/src/system_instruction.rs#L47 */
65 0 : fd_blockhashes_t const * blockhashes = fd_bank_block_hash_queue_query( ctx->bank );
66 0 : fd_blockhash_info_t const * last_bhash_info = fd_blockhashes_peek_last( blockhashes );
67 0 : if( FD_UNLIKELY( last_bhash_info==NULL ) ) {
68 : // Agave panics if this blockhash was never set at the start of the txn batch
69 0 : ctx->txn_out->err.custom_err = FD_SYSTEM_PROGRAM_ERR_NONCE_NO_RECENT_BLOCKHASHES;
70 0 : return FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR;
71 0 : }
72 :
73 0 : *out = *last_bhash_info;
74 0 : return FD_EXECUTOR_INSTR_SUCCESS;
75 0 : }
76 :
77 : static void
78 : fd_durable_nonce_from_blockhash( fd_hash_t * out,
79 0 : fd_hash_t const * blockhash ) {
80 0 : uchar buf[45];
81 0 : memcpy( buf, "DURABLE_NONCE", 13UL );
82 0 : memcpy( buf+13, blockhash, sizeof(fd_hash_t) );
83 0 : fd_sha256_hash( buf, sizeof(buf), out );
84 0 : }
85 :
86 : /* fd_system_program_set_nonce_state is a helper for updating the
87 : contents of a nonce account.
88 :
89 : Matches solana_sdk::transaction_context::BorrowedAccount::set_state
90 : https://github.com/solana-labs/solana/blob/v1.17.23/sdk/src/transaction_context.rs#L1020-L1029 */
91 :
92 : static int
93 : fd_system_program_set_nonce_state( fd_borrowed_account_t * account,
94 0 : fd_nonce_state_versions_t const * new_state ) {
95 :
96 : /* https://github.com/solana-labs/solana/blob/v1.17.23/sdk/src/transaction_context.rs#L1021
97 : => https://github.com/solana-labs/solana/blob/v1.17.23/sdk/src/transaction_context.rs#L868 */
98 :
99 0 : uchar * data = NULL;
100 0 : ulong dlen = 0UL;
101 0 : int err = fd_borrowed_account_get_data_mut( account, &data, &dlen );
102 0 : if( FD_UNLIKELY( err ) ) return err;
103 :
104 : /* https://github.com/solana-labs/solana/blob/v1.17.23/sdk/src/transaction_context.rs#L1024-L1026 */
105 :
106 0 : if( FD_UNLIKELY( fd_nonce_state_versions_size( new_state ) > fd_borrowed_account_get_data_len( account ) ) )
107 0 : return FD_EXECUTOR_INSTR_ERR_ACC_DATA_TOO_SMALL;
108 :
109 : /* https://github.com/solana-labs/solana/blob/v1.17.23/sdk/src/transaction_context.rs#L1027 */
110 :
111 0 : do {
112 0 : fd_bincode_encode_ctx_t encode =
113 0 : { .data = data,
114 0 : .dataend = data + dlen };
115 0 : int err = fd_nonce_state_versions_encode( new_state, &encode );
116 0 : if( FD_UNLIKELY( err ) ) {
117 0 : return FD_EXECUTOR_INSTR_ERR_GENERIC_ERR;
118 0 : }
119 0 : } while(0);
120 :
121 0 : return FD_EXECUTOR_INSTR_SUCCESS;
122 0 : }
123 :
124 : /* https://github.com/solana-labs/solana/blob/v1.17.23/programs/system/src/system_instruction.rs#L20-L70
125 :
126 : Matches Solana Labs system_instruction::advance_nonce_account */
127 :
128 : static int
129 : fd_system_program_advance_nonce_account( fd_exec_instr_ctx_t * ctx,
130 : fd_borrowed_account_t * account,
131 0 : ushort instr_acc_idx ) {
132 :
133 : /* https://github.com/solana-labs/solana/blob/v1.17.23/programs/system/src/system_instruction.rs#L25-L32 */
134 :
135 0 : if( FD_UNLIKELY( !fd_instr_acc_is_writable_idx( ctx->instr, instr_acc_idx ) ) ) {
136 : /* Max msg_sz: 50 - 2 + 45 = 93 < 127 => we can use printf */
137 0 : FD_BASE58_ENCODE_32_BYTES( account->pubkey->key, pubkey_b58 );
138 0 : fd_log_collector_printf_dangerous_max_127( ctx,
139 0 : "Advance nonce account: Account %s must be writeable", pubkey_b58 );
140 0 : return FD_EXECUTOR_INSTR_ERR_INVALID_ARG;
141 0 : }
142 :
143 : /* https://github.com/solana-labs/solana/blob/v1.17.23/programs/system/src/system_instruction.rs#L34 */
144 :
145 0 : fd_nonce_state_versions_t versions[1];
146 0 : if( FD_UNLIKELY( !fd_bincode_decode_static(
147 0 : nonce_state_versions, versions,
148 0 : fd_borrowed_account_get_data( account ),
149 0 : fd_borrowed_account_get_data_len( account ),
150 0 : NULL ) ) ) {
151 0 : return FD_EXECUTOR_INSTR_ERR_INVALID_ACC_DATA;
152 0 : }
153 :
154 : /* https://github.com/solana-labs/solana/blob/v1.17.23/programs/system/src/system_instruction.rs#L35 */
155 :
156 0 : fd_nonce_state_t * state = NULL;
157 0 : switch( versions->discriminant ) {
158 0 : case fd_nonce_state_versions_enum_legacy:
159 0 : state = &versions->inner.legacy;
160 0 : break;
161 0 : case fd_nonce_state_versions_enum_current:
162 0 : state = &versions->inner.current;
163 0 : break;
164 0 : default:
165 0 : __builtin_unreachable();
166 0 : }
167 :
168 0 : switch( state->discriminant ) {
169 :
170 0 : case fd_nonce_state_enum_initialized: {
171 0 : fd_nonce_data_t * data = &state->inner.initialized;
172 :
173 : /* https://github.com/solana-labs/solana/blob/v1.17.23/programs/system/src/system_instruction.rs#L37-L44 */
174 :
175 0 : if( FD_UNLIKELY( !fd_exec_instr_ctx_any_signed( ctx, &data->authority ) ) ) {
176 : /* Max msg_sz: 50 - 2 + 45 = 93 < 127 => we can use printf */
177 0 : FD_BASE58_ENCODE_32_BYTES( data->authority.key, authority_b58 );
178 0 : fd_log_collector_printf_dangerous_max_127( ctx,
179 0 : "Advance nonce account: Account %s must be a signer", authority_b58 );
180 0 : return FD_EXECUTOR_INSTR_ERR_MISSING_REQUIRED_SIGNATURE;
181 0 : }
182 :
183 : /* https://github.com/solana-labs/solana/blob/v1.17.23/programs/system/src/system_instruction.rs#L45 */
184 :
185 0 : fd_blockhash_info_t blockhash[1];
186 0 : do {
187 0 : int err = most_recent_block_hash( ctx, blockhash );
188 0 : if( FD_UNLIKELY( err ) ) return err;
189 0 : } while(0);
190 :
191 0 : fd_hash_t next_durable_nonce;
192 0 : fd_durable_nonce_from_blockhash( &next_durable_nonce, &blockhash->hash );
193 :
194 : /* https://github.com/solana-labs/solana/blob/v1.17.23/programs/system/src/system_instruction.rs#L46-L52 */
195 :
196 0 : if( FD_UNLIKELY( 0==memcmp( data->durable_nonce.hash, next_durable_nonce.hash, sizeof(fd_hash_t) ) ) ) {
197 0 : fd_log_collector_msg_literal( ctx, "Advance nonce account: nonce can only advance once per slot" );
198 0 : ctx->txn_out->err.custom_err = FD_SYSTEM_PROGRAM_ERR_NONCE_BLOCKHASH_NOT_EXPIRED;
199 0 : return FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR;
200 0 : }
201 :
202 : /* https://github.com/anza-xyz/agave/blob/v3.0.3/programs/system/src/system_instruction.rs#L57-L63 */
203 :
204 0 : fd_nonce_state_versions_t new_state = {
205 0 : .discriminant = fd_nonce_state_versions_enum_current,
206 0 : .inner = { .current = {
207 0 : .discriminant = fd_nonce_state_enum_initialized,
208 0 : .inner = { .initialized = {
209 0 : .authority = data->authority,
210 0 : .durable_nonce = next_durable_nonce,
211 0 : .fee_calculator = {
212 0 : .lamports_per_signature = blockhash->fee_calculator.lamports_per_signature
213 0 : }
214 0 : } }
215 0 : } }
216 0 : };
217 :
218 : /* https://github.com/solana-labs/solana/blob/v1.17.23/programs/system/src/system_instruction.rs#L59 */
219 :
220 0 : do {
221 0 : int err = fd_system_program_set_nonce_state( account, &new_state );
222 0 : if( FD_UNLIKELY( err ) ) return err;
223 0 : } while(0);
224 :
225 0 : break;
226 0 : }
227 :
228 0 : case fd_nonce_state_enum_uninitialized: {
229 : /* Max msg_sz: 50 - 2 + 45 = 93 < 127 => we can use printf */
230 0 : FD_BASE58_ENCODE_32_BYTES( account->pubkey->key, pubkey_b58 );
231 0 : fd_log_collector_printf_dangerous_max_127( ctx,
232 0 : "Advance nonce account: Account %s state is invalid", pubkey_b58 );
233 0 : return FD_EXECUTOR_INSTR_ERR_INVALID_ACC_DATA;
234 0 : }
235 :
236 0 : } /* switch */
237 :
238 0 : return FD_EXECUTOR_INSTR_SUCCESS;
239 0 : }
240 :
241 : /* https://github.com/solana-labs/solana/blob/v1.17.23/programs/system/src/system_processor.rs#L423-L441
242 :
243 : Matches Solana Labs system_processor SystemInstruction::AdvanceNonceAccount => { ... } */
244 :
245 : int
246 0 : fd_system_program_exec_advance_nonce_account( fd_exec_instr_ctx_t * ctx ) {
247 0 : int err;
248 : /* https://github.com/solana-labs/solana/blob/v1.17.23/programs/system/src/system_processor.rs#L423-L441 */
249 :
250 0 : if( FD_UNLIKELY( ctx->instr->acct_cnt < 1 ) )
251 0 : return FD_EXECUTOR_INSTR_ERR_MISSING_ACC;
252 :
253 : /* https://github.com/solana-labs/solana/blob/v1.17.23/programs/system/src/system_processor.rs#L425-L426 */
254 :
255 0 : uchar const instr_acc_idx = 0;
256 :
257 : /* https://github.com/anza-xyz/agave/blob/v2.1.14/programs/system/src/system_processor.rs#L409-L410 */
258 :
259 0 : fd_guarded_borrowed_account_t account = {0};
260 0 : FD_TRY_BORROW_INSTR_ACCOUNT_DEFAULT_ERR_CHECK( ctx, instr_acc_idx, &account );
261 :
262 : /* https://github.com/solana-labs/solana/blob/v1.17.23/programs/system/src/system_processor.rs#L427-L432 */
263 :
264 0 : err = require_acct_recent_blockhashes( ctx, 1UL );
265 0 : if( FD_UNLIKELY( err ) ) return err;
266 :
267 : /* https://github.com/solana-labs/solana/blob/v1.17.23/programs/system/src/system_processor.rs#L433-L439 */
268 :
269 0 : int bhq_empty;
270 0 : do {
271 0 : fd_block_block_hash_entry_t const * hashes = fd_sysvar_cache_recent_hashes_join_const( ctx->sysvar_cache );
272 0 : if( FD_UNLIKELY( !hashes ) ) __builtin_unreachable(); /* validated above */
273 0 : bhq_empty = deq_fd_block_block_hash_entry_t_empty( hashes );
274 0 : fd_sysvar_cache_recent_hashes_leave_const( ctx->sysvar_cache, hashes );
275 0 : } while(0);
276 0 : if( FD_UNLIKELY( bhq_empty ) ) {
277 0 : fd_log_collector_msg_literal( ctx, "Advance nonce account: recent blockhash list is empty" );
278 0 : ctx->txn_out->err.custom_err = FD_SYSTEM_PROGRAM_ERR_NONCE_NO_RECENT_BLOCKHASHES;
279 0 : return FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR;
280 0 : }
281 :
282 0 : err = fd_system_program_advance_nonce_account( ctx, &account, instr_acc_idx );
283 :
284 : /* Implicit drop */
285 :
286 0 : return err;
287 0 : }
288 :
289 : /* https://github.com/solana-labs/solana/blob/v1.17.23/programs/system/src/system_instruction.rs#L72-L151
290 :
291 : Matches Solana Labs system_instruction::withdraw_nonce_account */
292 :
293 : static int
294 : fd_system_program_withdraw_nonce_account( fd_exec_instr_ctx_t * ctx,
295 : ulong requested_lamports,
296 0 : fd_rent_t const * rent ) {
297 0 : int err;
298 0 : ushort const from_acct_idx = 0UL;
299 0 : ushort const to_acct_idx = 1UL;
300 :
301 : /* https://github.com/solana-labs/solana/blob/v1.17.23/programs/system/src/system_instruction.rs#L82-L83 */
302 :
303 0 : fd_guarded_borrowed_account_t from = {0};
304 0 : FD_TRY_BORROW_INSTR_ACCOUNT_DEFAULT_ERR_CHECK( ctx, from_acct_idx, &from );
305 :
306 : /* https://github.com/solana-labs/solana/blob/v1.17.23/programs/system/src/system_instruction.rs#L84-L91 */
307 :
308 0 : if( FD_UNLIKELY( !fd_instr_acc_is_writable_idx( ctx->instr, from_acct_idx ) ) ) {
309 : /* Max msg_sz: 51 - 2 + 45 = 94 < 127 => we can use printf */
310 0 : FD_BASE58_ENCODE_32_BYTES( from.pubkey->key, pubkey_b58 );
311 0 : fd_log_collector_printf_dangerous_max_127( ctx,
312 0 : "Withdraw nonce account: Account %s must be writeable", pubkey_b58 );
313 0 : return FD_EXECUTOR_INSTR_ERR_INVALID_ARG;
314 0 : }
315 :
316 : /* https://github.com/solana-labs/solana/blob/v1.17.23/programs/system/src/system_instruction.rs#L93 */
317 :
318 0 : fd_nonce_state_versions_t versions[1];
319 0 : if( FD_UNLIKELY( !fd_bincode_decode_static(
320 0 : nonce_state_versions, versions,
321 0 : fd_borrowed_account_get_data( &from ),
322 0 : fd_borrowed_account_get_data_len( &from ),
323 0 : NULL ) ) ) {
324 0 : return FD_EXECUTOR_INSTR_ERR_INVALID_ACC_DATA;
325 0 : }
326 :
327 : /* https://github.com/solana-labs/solana/blob/v1.17.23/programs/system/src/system_instruction.rs#L94 */
328 :
329 0 : fd_nonce_state_t * state = NULL;
330 0 : switch( versions->discriminant ) {
331 0 : case fd_nonce_state_versions_enum_legacy:
332 0 : state = &versions->inner.legacy;
333 0 : break;
334 0 : case fd_nonce_state_versions_enum_current:
335 0 : state = &versions->inner.current;
336 0 : break;
337 0 : default:
338 0 : __builtin_unreachable();
339 0 : }
340 :
341 0 : fd_pubkey_t signer[1] = {0};
342 0 : switch( state->discriminant ) {
343 :
344 0 : case fd_nonce_state_enum_uninitialized: {
345 : /* https://github.com/solana-labs/solana/blob/v1.17.23/programs/system/src/system_instruction.rs#L95-L106 */
346 :
347 0 : if( FD_UNLIKELY( requested_lamports > fd_borrowed_account_get_lamports( &from ) ) ) {
348 : /* Max msg_sz: 59 - 6 + 20 + 20 = 93 < 127 => we can use printf */
349 0 : fd_log_collector_printf_dangerous_max_127( ctx,
350 0 : "Withdraw nonce account: insufficient lamports %lu, need %lu", fd_borrowed_account_get_lamports( &from ), requested_lamports );
351 0 : return FD_EXECUTOR_INSTR_ERR_INSUFFICIENT_FUNDS;
352 0 : }
353 :
354 : /* https://github.com/solana-labs/solana/blob/v1.17.23/programs/system/src/system_instruction.rs#L105 */
355 :
356 0 : *signer = *from.pubkey;
357 :
358 0 : break;
359 0 : }
360 :
361 0 : case fd_nonce_state_enum_initialized: {
362 : /* https://github.com/solana-labs/solana/blob/v1.17.23/programs/system/src/system_instruction.rs#L107-L132 */
363 0 : fd_nonce_data_t * data = &state->inner.initialized;
364 :
365 0 : if( requested_lamports == fd_borrowed_account_get_lamports( &from ) ) {
366 : /* https://github.com/solana-labs/solana/blob/v1.17.23/programs/system/src/system_instruction.rs#L108-L117 */
367 :
368 : /* https://github.com/solana-labs/solana/blob/v1.17.23/programs/system/src/system_instruction.rs#L109 */
369 :
370 0 : fd_blockhash_info_t blockhash[1];
371 0 : do {
372 0 : int err = most_recent_block_hash( ctx, blockhash );
373 0 : if( FD_UNLIKELY( err ) ) return err;
374 0 : } while(0);
375 :
376 0 : fd_hash_t next_durable_nonce;
377 0 : fd_durable_nonce_from_blockhash( &next_durable_nonce, &blockhash->hash );
378 :
379 : /* https://github.com/solana-labs/solana/blob/v1.17.23/programs/system/src/system_instruction.rs#L110-L116 */
380 :
381 0 : if( FD_UNLIKELY( 0==memcmp( data->durable_nonce.hash, next_durable_nonce.hash, sizeof(fd_hash_t) ) ) ) {
382 0 : fd_log_collector_msg_literal( ctx, "Withdraw nonce account: nonce can only advance once per slot" );
383 0 : ctx->txn_out->err.custom_err = FD_SYSTEM_PROGRAM_ERR_NONCE_BLOCKHASH_NOT_EXPIRED;
384 0 : return FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR;
385 0 : }
386 :
387 : /* https://github.com/solana-labs/solana/blob/v1.17.23/programs/system/src/system_instruction.rs#L117 */
388 :
389 0 : fd_nonce_state_versions_t new_state[1] = {{
390 0 : .discriminant = fd_nonce_state_versions_enum_current,
391 0 : .inner = { .current = {
392 0 : .discriminant = fd_nonce_state_enum_uninitialized
393 0 : } }
394 0 : }};
395 :
396 0 : do {
397 0 : int err = fd_system_program_set_nonce_state( &from, new_state );
398 0 : if( FD_UNLIKELY( err ) ) return err;
399 0 : } while(0);
400 :
401 0 : } else {
402 : /* https://github.com/solana-labs/solana/blob/v1.17.23/programs/system/src/system_instruction.rs#L118-L130 */
403 :
404 : /* https://github.com/solana-labs/solana/blob/v1.17.23/programs/system/src/system_instruction.rs#L120 */
405 :
406 0 : ulong min_balance = fd_rent_exempt_minimum_balance( rent, fd_borrowed_account_get_data_len( &from ) );
407 :
408 0 : ulong amount;
409 0 : if( FD_UNLIKELY( __builtin_uaddl_overflow( requested_lamports, min_balance, &amount ) ) )
410 0 : return FD_EXECUTOR_INSTR_ERR_INSUFFICIENT_FUNDS;
411 :
412 : /* https://github.com/solana-labs/solana/blob/v1.17.23/programs/system/src/system_instruction.rs#L121-L129 */
413 :
414 0 : if( FD_UNLIKELY( amount > fd_borrowed_account_get_lamports( &from ) ) ) {
415 : /* Max msg_sz: 59 - 6 + 20 + 20 = 93 < 127 => we can use printf */
416 0 : fd_log_collector_printf_dangerous_max_127( ctx,
417 0 : "Withdraw nonce account: insufficient lamports %lu, need %lu", fd_borrowed_account_get_lamports( &from ), amount );
418 0 : return FD_EXECUTOR_INSTR_ERR_INSUFFICIENT_FUNDS;
419 0 : }
420 :
421 0 : }
422 :
423 : /* https://github.com/solana-labs/solana/blob/v1.17.23/programs/system/src/system_instruction.rs#L131 */
424 :
425 0 : *signer = data->authority;
426 :
427 0 : break;
428 0 : }
429 :
430 0 : } /* switch */
431 :
432 : /* https://github.com/solana-labs/solana/blob/v1.17.23/programs/system/src/system_instruction.rs#L135-L142 */
433 :
434 0 : if( FD_UNLIKELY( !fd_exec_instr_ctx_any_signed( ctx, signer ) ) ) {
435 : /* Max msg_sz: 44 - 2 + 45 = 87 < 127 => we can use printf */
436 0 : FD_BASE58_ENCODE_32_BYTES( signer->key, signer_b58 );
437 0 : fd_log_collector_printf_dangerous_max_127( ctx,
438 0 : "Withdraw nonce account: Account %s must sign", signer_b58 );
439 0 : return FD_EXECUTOR_INSTR_ERR_MISSING_REQUIRED_SIGNATURE;
440 0 : }
441 :
442 : /* https://github.com/solana-labs/solana/blob/v1.17.23/programs/system/src/system_instruction.rs#L144 */
443 :
444 0 : err = fd_borrowed_account_checked_sub_lamports( &from, requested_lamports );
445 0 : if( FD_UNLIKELY( err ) ) return err;
446 :
447 : /* https://github.com/solana-labs/solana/blob/v1.17.23/programs/system/src/system_instruction.rs#L145 */
448 :
449 0 : fd_borrowed_account_drop( &from );
450 :
451 : /* https://github.com/solana-labs/solana/blob/v1.17.23/programs/system/src/system_instruction.rs#L146-L147 */
452 :
453 0 : fd_guarded_borrowed_account_t to = {0};
454 0 : FD_TRY_BORROW_INSTR_ACCOUNT_DEFAULT_ERR_CHECK( ctx, to_acct_idx, &to );
455 :
456 : /* https://github.com/solana-labs/solana/blob/v1.17.23/programs/system/src/system_instruction.rs#L148 */
457 :
458 0 : err = fd_borrowed_account_checked_add_lamports( &to, requested_lamports );
459 0 : if( FD_UNLIKELY( err ) ) return err;
460 :
461 : /* Implicit drop */
462 :
463 0 : return FD_EXECUTOR_INSTR_SUCCESS;
464 0 : }
465 :
466 : /* https://github.com/solana-labs/solana/blob/v1.17.23/programs/system/src/system_processor.rs#L442-L461
467 :
468 : Matches Solana Labs system_processor SystemInstruction::WithdrawNonceAccount { ... } => { ... } */
469 :
470 : int
471 : fd_system_program_exec_withdraw_nonce_account( fd_exec_instr_ctx_t * ctx,
472 0 : ulong requested_lamports ) {
473 :
474 : /* https://github.com/solana-labs/solana/blob/v1.17.23/programs/system/src/system_processor.rs#L443 */
475 :
476 0 : if( FD_UNLIKELY( ctx->instr->acct_cnt < 2 ) )
477 0 : return FD_EXECUTOR_INSTR_ERR_MISSING_ACC;
478 :
479 : /* https://github.com/solana-labs/solana/blob/v1.17.23/programs/system/src/system_processor.rs#L445-L449 */
480 :
481 0 : do {
482 0 : int err = require_acct_recent_blockhashes( ctx, 2UL );
483 0 : if( FD_UNLIKELY( err ) ) return err;
484 0 : } while(0);
485 :
486 : /* https://github.com/solana-labs/solana/blob/v1.17.23/programs/system/src/system_processor.rs#L450 */
487 :
488 0 : fd_rent_t rent[1];
489 0 : do {
490 0 : int err = require_acct_rent( ctx, 3UL, rent );
491 0 : if( FD_UNLIKELY( err ) ) return err;
492 0 : } while(0);
493 :
494 : /* https://github.com/solana-labs/solana/blob/v1.17.23/programs/system/src/system_processor.rs#L451-L460 */
495 :
496 0 : return fd_system_program_withdraw_nonce_account( ctx, requested_lamports, rent );
497 0 : }
498 :
499 : /* https://github.com/solana-labs/solana/blob/v1.17.23/programs/system/src/system_instruction.rs#L153-L198
500 :
501 : Matches Solana Labs system_instruction::initialize_nonce_account */
502 :
503 : static int
504 : fd_system_program_initialize_nonce_account( fd_exec_instr_ctx_t * ctx,
505 : fd_borrowed_account_t * account,
506 : fd_pubkey_t const * authorized,
507 0 : fd_rent_t const * rent ) {
508 :
509 : /* https://github.com/anza-xyz/agave/blob/v2.2.0/programs/system/src/system_instruction.rs#L167-L174 */
510 :
511 0 : if( FD_UNLIKELY( !fd_borrowed_account_is_writable( account ) ) ) {
512 : /* Max msg_sz: 53 - 2 + 45 = 96 < 127 => we can use printf */
513 0 : FD_BASE58_ENCODE_32_BYTES( account->pubkey->key, pubkey_b58 );
514 0 : fd_log_collector_printf_dangerous_max_127( ctx,
515 0 : "Initialize nonce account: Account %s must be writeable", pubkey_b58 );
516 0 : return FD_EXECUTOR_INSTR_ERR_INVALID_ARG;
517 0 : }
518 :
519 : /* https://github.com/solana-labs/solana/blob/v1.17.23/programs/system/src/system_instruction.rs#L168 */
520 :
521 0 : fd_nonce_state_versions_t versions[1];
522 0 : if( FD_UNLIKELY( !fd_bincode_decode_static(
523 0 : nonce_state_versions, versions,
524 0 : fd_borrowed_account_get_data( account ),
525 0 : fd_borrowed_account_get_data_len( account ),
526 0 : NULL ) ) ) {
527 0 : return FD_EXECUTOR_INSTR_ERR_INVALID_ACC_DATA;
528 0 : }
529 :
530 0 : fd_nonce_state_t * state = NULL;
531 0 : switch( versions->discriminant ) {
532 0 : case fd_nonce_state_versions_enum_legacy:
533 0 : state = &versions->inner.legacy;
534 0 : break;
535 0 : case fd_nonce_state_versions_enum_current:
536 0 : state = &versions->inner.current;
537 0 : break;
538 0 : default:
539 0 : __builtin_unreachable();
540 0 : }
541 :
542 0 : switch( state->discriminant ) {
543 :
544 0 : case fd_nonce_state_enum_uninitialized: {
545 : /* https://github.com/solana-labs/solana/blob/v1.17.23/programs/system/src/system_instruction.rs#L169-L188 */
546 :
547 : /* https://github.com/solana-labs/solana/blob/v1.17.23/programs/system/src/system_instruction.rs#L170 */
548 :
549 0 : ulong min_balance = fd_rent_exempt_minimum_balance( rent, fd_borrowed_account_get_data_len( account ) );
550 :
551 : /* https://github.com/solana-labs/solana/blob/v1.17.23/programs/system/src/system_instruction.rs#L171-L179 */
552 :
553 0 : if( FD_UNLIKELY( fd_borrowed_account_get_lamports( account ) < min_balance ) ) {
554 : /* Max msg_sz: 61 - 6 + 20 + 20 = 95 < 127 => we can use printf */
555 0 : fd_log_collector_printf_dangerous_max_127( ctx,
556 0 : "Initialize nonce account: insufficient lamports %lu, need %lu", fd_borrowed_account_get_lamports( account ), min_balance );
557 0 : return FD_EXECUTOR_INSTR_ERR_INSUFFICIENT_FUNDS;
558 0 : }
559 :
560 : /* https://github.com/solana-labs/solana/blob/v1.17.23/programs/system/src/system_instruction.rs#L180 */
561 :
562 0 : fd_blockhash_info_t blockhash[1];
563 0 : do {
564 0 : int err = most_recent_block_hash( ctx, blockhash );
565 0 : if( FD_UNLIKELY( err ) ) return err;
566 0 : } while(0);
567 :
568 0 : fd_hash_t durable_nonce;
569 0 : fd_durable_nonce_from_blockhash( &durable_nonce, &blockhash->hash );
570 :
571 : /* https://github.com/anza-xyz/agave/blob/v3.0.3/programs/system/src/system_instruction.rs#L185-L191 */
572 :
573 0 : fd_nonce_state_versions_t new_state = {
574 0 : .discriminant = fd_nonce_state_versions_enum_current,
575 0 : .inner = { .current = {
576 0 : .discriminant = fd_nonce_state_enum_initialized,
577 0 : .inner = { .initialized = {
578 0 : .authority = *authorized,
579 0 : .durable_nonce = durable_nonce,
580 0 : .fee_calculator = {
581 0 : .lamports_per_signature = blockhash->fee_calculator.lamports_per_signature
582 0 : }
583 0 : } }
584 0 : } }
585 0 : };
586 :
587 : /* https://github.com/solana-labs/solana/blob/v1.17.23/programs/system/src/system_instruction.rs#L187 */
588 :
589 0 : do {
590 0 : int err = fd_system_program_set_nonce_state( account, &new_state );
591 0 : if( FD_UNLIKELY( err ) ) return err;
592 0 : } while(0);
593 :
594 0 : break;
595 0 : }
596 :
597 0 : case fd_nonce_state_enum_initialized: {
598 : /* https://github.com/solana-labs/solana/blob/v1.17.23/programs/system/src/system_instruction.rs#L189-L196 */
599 :
600 : /* Max msg_sz: 53 - 2 + 45 = 96 < 127 => we can use printf */
601 0 : FD_BASE58_ENCODE_32_BYTES( account->pubkey->key, pubkey_b58 );
602 0 : fd_log_collector_printf_dangerous_max_127( ctx,
603 0 : "Initialize nonce account: Account %s state is invalid", pubkey_b58 );
604 :
605 0 : return FD_EXECUTOR_INSTR_ERR_INVALID_ACC_DATA;
606 0 : }
607 :
608 0 : } /* switch */
609 :
610 0 : return FD_EXECUTOR_INSTR_SUCCESS;
611 0 : }
612 :
613 : /* https://github.com/solana-labs/solana/blob/v1.17.23/programs/system/src/system_processor.rs#L462-L481
614 :
615 : Matches Solana Labs system_processor SystemInstruction::InitializeNonceAccount { ... } => { ... } */
616 :
617 : int
618 : fd_system_program_exec_initialize_nonce_account( fd_exec_instr_ctx_t * ctx,
619 0 : fd_pubkey_t const * authorized ) {
620 0 : int err;
621 : /* https://github.com/solana-labs/solana/blob/v1.17.23/programs/system/src/system_processor.rs#L463 */
622 :
623 0 : if( FD_UNLIKELY( ctx->instr->acct_cnt < 1 ) )
624 0 : return FD_EXECUTOR_INSTR_ERR_MISSING_ACC;
625 :
626 : /* https://github.com/solana-labs/solana/blob/v1.17.23/programs/system/src/system_processor.rs#L464-L465 */
627 :
628 0 : uchar const instr_acc_idx = 0;
629 : /* https://github.com/anza-xyz/agave/blob/v2.1.14/programs/system/src/system_processor.rs#L448-L449 */
630 0 : fd_guarded_borrowed_account_t account = {0};
631 0 : FD_TRY_BORROW_INSTR_ACCOUNT_DEFAULT_ERR_CHECK( ctx, instr_acc_idx, &account );
632 :
633 : /* https://github.com/solana-labs/solana/blob/v1.17.23/programs/system/src/system_processor.rs#L466-L471 */
634 :
635 0 : do {
636 0 : err = require_acct_recent_blockhashes( ctx, 1UL );
637 0 : if( FD_UNLIKELY( err ) ) return err;
638 0 : } while(0);
639 :
640 : /* https://github.com/solana-labs/solana/blob/v1.17.23/programs/system/src/system_processor.rs#L472-L478 */
641 :
642 0 : int bhq_empty;
643 0 : do {
644 0 : fd_block_block_hash_entry_t const * hashes = fd_sysvar_cache_recent_hashes_join_const( ctx->sysvar_cache );
645 0 : if( FD_UNLIKELY( !hashes ) ) __builtin_unreachable(); /* validated above */
646 0 : bhq_empty = deq_fd_block_block_hash_entry_t_empty( hashes );
647 0 : fd_sysvar_cache_recent_hashes_leave_const( ctx->sysvar_cache, hashes );
648 0 : } while(0);
649 0 : if( FD_UNLIKELY( bhq_empty ) ) {
650 0 : fd_log_collector_msg_literal( ctx, "Initialize nonce account: recent blockhash list is empty" );
651 0 : ctx->txn_out->err.custom_err = FD_SYSTEM_PROGRAM_ERR_NONCE_NO_RECENT_BLOCKHASHES;
652 0 : return FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR;
653 0 : }
654 :
655 : /* https://github.com/solana-labs/solana/blob/v1.17.23/programs/system/src/system_processor.rs#L479 */
656 :
657 0 : fd_rent_t rent[1];
658 0 : do {
659 0 : err = require_acct_rent( ctx, 2UL, rent );
660 0 : if( FD_UNLIKELY( err ) ) return err;
661 0 : } while(0);
662 :
663 : /* https://github.com/solana-labs/solana/blob/v1.17.23/programs/system/src/system_processor.rs#L480 */
664 :
665 0 : err = fd_system_program_initialize_nonce_account( ctx, &account, authorized, rent );
666 :
667 : /* Implicit drop */
668 :
669 0 : return err;
670 0 : }
671 :
672 : /* https://github.com/solana-labs/solana/blob/v1.17.23/programs/system/src/system_instruction.rs#L200-L236
673 :
674 : Matches Solana Labs system_instruction::authorize_nonce_account */
675 :
676 : static int
677 : fd_system_program_authorize_nonce_account( fd_exec_instr_ctx_t * ctx,
678 : fd_borrowed_account_t * account,
679 : ushort instr_acc_idx,
680 0 : fd_pubkey_t const * nonce_authority ) {
681 :
682 : /* https://github.com/solana-labs/solana/blob/v1.17.23/programs/system/src/system_instruction.rs#L206-L213 */
683 :
684 0 : if( FD_UNLIKELY( !fd_instr_acc_is_writable_idx( ctx->instr, instr_acc_idx ) ) ) {
685 : /* Max msg_sz: 52 - 2 + 45 = 95 < 127 => we can use printf */
686 0 : FD_BASE58_ENCODE_32_BYTES( account->pubkey->key, pubkey_b58 );
687 0 : fd_log_collector_printf_dangerous_max_127( ctx,
688 0 : "Authorize nonce account: Account %s must be writeable", pubkey_b58 );
689 0 : return FD_EXECUTOR_INSTR_ERR_INVALID_ARG;
690 0 : }
691 :
692 : /* https://github.com/solana-labs/solana/blob/v1.17.23/programs/system/src/system_instruction.rs#L214-L215 */
693 :
694 0 : fd_nonce_state_versions_t versions[1];
695 0 : if( FD_UNLIKELY( !fd_bincode_decode_static(
696 0 : nonce_state_versions, versions,
697 0 : fd_borrowed_account_get_data( account ),
698 0 : fd_borrowed_account_get_data_len( account ),
699 0 : NULL ) ) ) {
700 0 : return FD_EXECUTOR_INSTR_ERR_INVALID_ACC_DATA;
701 0 : }
702 :
703 : /* Inlining solana_program::nonce::state::Versions::authorize
704 : https://github.com/solana-labs/solana/blob/v1.17.23/sdk/program/src/nonce/state/mod.rs#L76-L102 */
705 :
706 : /* https://github.com/solana-labs/solana/blob/v1.17.23/sdk/program/src/nonce/state/mod.rs#L81 */
707 :
708 0 : fd_nonce_state_t * state = NULL;
709 0 : switch( versions->discriminant ) {
710 0 : case fd_nonce_state_versions_enum_legacy:
711 0 : state = &versions->inner.legacy;
712 0 : break;
713 0 : case fd_nonce_state_versions_enum_current:
714 0 : state = &versions->inner.current;
715 0 : break;
716 0 : default:
717 0 : __builtin_unreachable();
718 0 : }
719 :
720 : /* https://github.com/solana-labs/solana/blob/v1.17.23/sdk/program/src/nonce/state/mod.rs#L81-L84 */
721 :
722 0 : if( FD_UNLIKELY( state->discriminant != fd_nonce_state_enum_initialized ) ) {
723 : /* https://github.com/solana-labs/solana/blob/v1.17.23/programs/system/src/system_instruction.rs#L219-L226 */
724 :
725 : /* Max msg_sz: 52 - 2 + 45 = 95 < 127 => we can use printf */
726 0 : FD_BASE58_ENCODE_32_BYTES( account->pubkey->key, pubkey_b58 );
727 0 : fd_log_collector_printf_dangerous_max_127( ctx,
728 0 : "Authorize nonce account: Account %s state is invalid", pubkey_b58 );
729 :
730 0 : return FD_EXECUTOR_INSTR_ERR_INVALID_ACC_DATA;
731 0 : }
732 :
733 0 : fd_nonce_data_t * data = &state->inner.initialized;
734 :
735 : /* https://github.com/solana-labs/solana/blob/v1.17.23/sdk/program/src/nonce/state/mod.rs#L85-L89 */
736 :
737 0 : if( FD_UNLIKELY( !fd_exec_instr_ctx_any_signed( ctx, &data->authority ) ) ) {
738 : /* https://github.com/solana-labs/solana/blob/v1.17.23/programs/system/src/system_instruction.rs#L227-L234 */
739 : /* Max msg_sz: 45 - 2 + 45 = 88 < 127 => we can use printf */
740 0 : FD_BASE58_ENCODE_32_BYTES( data->authority.key, authority_b58 );
741 0 : fd_log_collector_printf_dangerous_max_127( ctx,
742 0 : "Authorize nonce account: Account %s must sign", authority_b58 );
743 0 : return FD_EXECUTOR_INSTR_ERR_MISSING_REQUIRED_SIGNATURE;
744 0 : }
745 :
746 : /* https://github.com/solana-labs/solana/blob/v1.17.23/sdk/program/src/nonce/state/mod.rs#L90-L95 */
747 :
748 0 : fd_nonce_state_t new_state[1] = {{
749 0 : .discriminant = fd_nonce_state_enum_initialized,
750 0 : .inner = { .initialized = {
751 0 : .authority = *nonce_authority,
752 0 : .durable_nonce = data->durable_nonce,
753 0 : .fee_calculator = data->fee_calculator
754 0 : } }
755 0 : }};
756 :
757 : /* https://github.com/solana-labs/solana/blob/v1.17.23/sdk/program/src/nonce/state/mod.rs#L96-L101 */
758 :
759 0 : fd_nonce_state_versions_t new_versioned[1] = {{0}};
760 0 : new_versioned->discriminant = versions->discriminant;
761 0 : switch( versions->discriminant ) {
762 0 : case fd_nonce_state_versions_enum_legacy:
763 0 : new_versioned->inner.legacy = *new_state;
764 0 : break;
765 0 : case fd_nonce_state_versions_enum_current:
766 0 : new_versioned->inner.current = *new_state;
767 0 : break;
768 0 : default:
769 0 : __builtin_unreachable();
770 0 : }
771 :
772 : /* https://github.com/solana-labs/solana/blob/v1.17.23/programs/system/src/system_instruction.rs#L218 */
773 :
774 0 : do {
775 0 : int err = fd_system_program_set_nonce_state( account, new_versioned );
776 0 : if( FD_UNLIKELY( err ) ) return err;
777 0 : } while(0);
778 :
779 0 : return FD_EXECUTOR_INSTR_SUCCESS;
780 0 : }
781 :
782 : /* https://github.com/solana-labs/solana/blob/v1.17.23/programs/system/src/system_processor.rs#L482-L487
783 :
784 : Matches Solana Labs system_processor SystemInstruction::AuthorizeNonceAccount { ... } => { ... } */
785 :
786 : int
787 : fd_system_program_exec_authorize_nonce_account( fd_exec_instr_ctx_t * ctx,
788 0 : fd_pubkey_t const * nonce_authority ) {
789 0 : int err;
790 : /* https://github.com/solana-labs/solana/blob/v1.17.23/programs/system/src/system_processor.rs#L483 */
791 :
792 0 : if( FD_UNLIKELY( ctx->instr->acct_cnt < 1 ) )
793 0 : return FD_EXECUTOR_INSTR_ERR_MISSING_ACC;
794 :
795 : /* https://github.com/solana-labs/solana/blob/v1.17.23/programs/system/src/system_processor.rs#L484-L485 */
796 :
797 0 : fd_guarded_borrowed_account_t account = {0};
798 0 : err = fd_exec_instr_ctx_try_borrow_instr_account( ctx, 0, &account );
799 0 : if( FD_UNLIKELY( err ) ) {
800 0 : return err;
801 0 : }
802 :
803 : /* https://github.com/solana-labs/solana/blob/v1.17.23/programs/system/src/system_processor.rs#L486 */
804 :
805 0 : err = fd_system_program_authorize_nonce_account( ctx, &account, 0UL, nonce_authority );
806 :
807 : /* Implicit drop */
808 :
809 0 : return err;
810 0 : }
811 :
812 : /* https://github.com/solana-labs/solana/blob/v1.17.23/programs/system/src/system_processor.rs#L488-L503
813 :
814 : Matches Solana Labs system_processor SystemInstruction::UpgradeNonceAccount { ... } => { ... } */
815 :
816 : int
817 0 : fd_system_program_exec_upgrade_nonce_account( fd_exec_instr_ctx_t * ctx ) {
818 0 : int err;
819 0 : ushort const nonce_acct_idx = 0UL;
820 :
821 : /* https://github.com/solana-labs/solana/blob/v1.17.23/programs/system/src/system_processor.rs#L489 */
822 :
823 0 : if( FD_UNLIKELY( ctx->instr->acct_cnt < 1 ) )
824 0 : return FD_EXECUTOR_INSTR_ERR_MISSING_ACC;
825 :
826 : /* https://github.com/anza-xyz/agave/blob/v2.1.14/programs/system/src/system_processor.rs#L474-475 */
827 :
828 0 : fd_guarded_borrowed_account_t account = {0};
829 0 : FD_TRY_BORROW_INSTR_ACCOUNT_DEFAULT_ERR_CHECK( ctx, nonce_acct_idx, &account );
830 :
831 : /* https://github.com/solana-labs/solana/blob/v1.17.23/programs/system/src/system_processor.rs#L492-L494 */
832 :
833 0 : if( FD_UNLIKELY( 0!=memcmp( fd_borrowed_account_get_owner( &account ), fd_solana_system_program_id.key, sizeof(fd_pubkey_t) ) ) )
834 0 : return FD_EXECUTOR_INSTR_ERR_INVALID_ACC_OWNER;
835 :
836 : /* https://github.com/solana-labs/solana/blob/v1.17.23/programs/system/src/system_processor.rs#L495-L497 */
837 :
838 0 : if( FD_UNLIKELY( !fd_instr_acc_is_writable_idx( ctx->instr, 0 ) ) )
839 0 : return FD_EXECUTOR_INSTR_ERR_INVALID_ARG;
840 :
841 : /* https://github.com/solana-labs/solana/blob/v1.17.23/programs/system/src/system_processor.rs#L498 */
842 :
843 0 : fd_nonce_state_versions_t versions[1];
844 0 : if( FD_UNLIKELY( !fd_bincode_decode_static(
845 0 : nonce_state_versions, versions,
846 0 : fd_borrowed_account_get_data( &account ),
847 0 : fd_borrowed_account_get_data_len( &account ),
848 0 : NULL ) ) ) {
849 0 : return FD_EXECUTOR_INSTR_ERR_INVALID_ACC_DATA;
850 0 : }
851 :
852 : /* Inlining solana_program::nonce::state::Versions::upgrade
853 : https://github.com/solana-labs/solana/blob/v1.17.23/sdk/program/src/nonce/state/mod.rs#L55-L73 */
854 :
855 0 : if( FD_UNLIKELY( versions->discriminant != fd_nonce_state_versions_enum_legacy ) )
856 0 : return FD_EXECUTOR_INSTR_ERR_INVALID_ARG;
857 :
858 0 : fd_nonce_state_t * state = &versions->inner.legacy;
859 0 : if( FD_UNLIKELY( state->discriminant != fd_nonce_state_enum_initialized ) )
860 0 : return FD_EXECUTOR_INSTR_ERR_INVALID_ARG;
861 :
862 0 : fd_durable_nonce_from_blockhash( &state->inner.initialized.durable_nonce, &state->inner.initialized.durable_nonce );
863 :
864 : /* https://github.com/solana-labs/solana/blob/v1.17.23/programs/system/src/system_processor.rs#L501 */
865 :
866 0 : fd_nonce_state_versions_t new_state[1] = {{
867 0 : .discriminant = fd_nonce_state_versions_enum_current,
868 0 : .inner = { .current = *state }
869 0 : }};
870 :
871 0 : err = fd_system_program_set_nonce_state( &account, new_state );
872 0 : if( FD_UNLIKELY( err ) ) return err;
873 :
874 : /* Implicit drop */
875 :
876 0 : return FD_EXECUTOR_INSTR_SUCCESS;
877 0 : }
878 :
879 : /* https://github.com/anza-xyz/agave/blob/v3.0.3/runtime/src/bank/check_transactions.rs#L166-L200 */
880 : /* The age of a transaction is valid under two conditions. The first is that
881 : the transactions blockhash is a recent blockhash (within 151) in the block
882 : hash queue. The other condition is that the transaction contains a valid
883 : nonce account. This is the case under several conditions. If neither
884 : condition is met then the transaction is invalid.
885 : Note: We check 151 and not 150 due to a known bug in agave. */
886 : int
887 : fd_check_transaction_age( fd_runtime_t * runtime,
888 : fd_bank_t * bank,
889 : fd_txn_in_t const * txn_in,
890 0 : fd_txn_out_t * txn_out ) {
891 0 : fd_blockhashes_t const * block_hash_queue = fd_bank_block_hash_queue_query( bank );
892 0 : fd_hash_t const * last_blockhash = fd_blockhashes_peek_last_hash( block_hash_queue );
893 0 : if( FD_UNLIKELY( !last_blockhash ) ) {
894 0 : FD_LOG_CRIT(( "blockhash queue is empty" ));
895 0 : }
896 :
897 : /* check_transaction_age */
898 0 : fd_hash_t next_durable_nonce = {0};
899 0 : fd_durable_nonce_from_blockhash( &next_durable_nonce, last_blockhash );
900 0 : ushort recent_blockhash_off = TXN( txn_in->txn )->recent_blockhash_off;
901 0 : fd_hash_t * recent_blockhash = (fd_hash_t *)((uchar *)txn_in->txn->payload + recent_blockhash_off);
902 :
903 : /* https://github.com/anza-xyz/agave/blob/16de8b75ebcd57022409b422de557dd37b1de8db/runtime/src/bank.rs#L3538-L3542 */
904 : /* get_hash_info_if_valid. Check 151 hashes from the block hash queue and its
905 : age to see if it is valid. */
906 :
907 0 : if( fd_blockhashes_check_age( block_hash_queue, recent_blockhash, FD_SYSVAR_RECENT_HASHES_CAP ) ) {
908 0 : return FD_RUNTIME_EXECUTE_SUCCESS;
909 0 : }
910 :
911 : /* https://github.com/anza-xyz/agave/blob/16de8b75ebcd57022409b422de557dd37b1de8db/runtime/src/bank.rs#L3622-L3633 */
912 : /* check_and_load_message_nonce_account */
913 0 : if( FD_UNLIKELY( !memcmp( &next_durable_nonce, recent_blockhash, sizeof(fd_hash_t) ) ) ) { /* nonce_is_advanceable == false */
914 0 : return FD_RUNTIME_TXN_ERR_BLOCKHASH_NONCE_ALREADY_ADVANCED;
915 0 : }
916 :
917 : /* https://github.com/anza-xyz/agave/blob/16de8b75ebcd57022409b422de557dd37b1de8db/runtime/src/bank.rs#L3603-L3620*/
918 : /* load_message_nonce_account */
919 :
920 : /* https://github.com/anza-xyz/agave/blob/v2.3.1/svm-transaction/src/svm_message.rs#L87-L119 */
921 : /* get_durable_nonce */
922 0 : if( FD_UNLIKELY( !TXN( txn_in->txn )->instr_cnt ) ) {
923 0 : return FD_RUNTIME_TXN_ERR_BLOCKHASH_NOT_FOUND;
924 0 : }
925 : /* Check the first instruction (nonce instruction) to see if the
926 : program id is the system program. Also make sure that it is an
927 : advance nonce account instruction. Finally make sure that the
928 : first instruction account is writable; if it is, then that account
929 : is a durable nonce account. */
930 0 : fd_txn_instr_t const * txn_instr = &TXN( txn_in->txn )->instr[0];
931 0 : fd_acct_addr_t const * tx_accs = fd_txn_get_acct_addrs( TXN( txn_in->txn ), txn_in->txn->payload );
932 0 : fd_acct_addr_t const * prog_id = tx_accs + txn_instr->program_id;
933 0 : if( FD_UNLIKELY( memcmp( prog_id->b, fd_solana_system_program_id.key, sizeof( fd_pubkey_t ) ) ) ) {
934 0 : return FD_RUNTIME_TXN_ERR_BLOCKHASH_NOT_FOUND;
935 0 : }
936 0 : uchar const * instr_data = fd_txn_get_instr_data( txn_instr, txn_in->txn->payload );
937 0 : uchar const * instr_accts = fd_txn_get_instr_accts( txn_instr, txn_in->txn->payload );
938 0 : uchar nonce_idx = instr_accts[0];
939 :
940 : /* https://github.com/anza-xyz/agave/blob/v2.3.1/svm-transaction/src/svm_message.rs#L99-L105 */
941 0 : if( FD_UNLIKELY( txn_instr->data_sz<4UL || FD_LOAD( uint, instr_data ) !=
942 0 : (uint)fd_system_program_instruction_enum_advance_nonce_account ) ) {
943 0 : return FD_RUNTIME_TXN_ERR_BLOCKHASH_NOT_FOUND;
944 0 : }
945 :
946 : /* Nonce account must be...
947 : - writable
948 : - statically included in the transaction account keys (if SIMD-242
949 : is active)
950 : https://github.com/anza-xyz/agave/blob/v2.3.1/svm-transaction/src/svm_message.rs#L110-L111 */
951 0 : if( FD_UNLIKELY( !fd_runtime_account_is_writable_idx( txn_in, txn_out, bank, nonce_idx ) ) ) {
952 0 : return FD_RUNTIME_TXN_ERR_BLOCKHASH_FAIL_ADVANCE_NONCE_INSTR;
953 0 : }
954 0 : if( FD_UNLIKELY( FD_FEATURE_ACTIVE_BANK( bank, require_static_nonce_account ) &&
955 0 : nonce_idx>=TXN( txn_in->txn )->acct_addr_cnt ) ) {
956 0 : return FD_RUNTIME_TXN_ERR_BLOCKHASH_FAIL_ADVANCE_NONCE_INSTR;
957 0 : }
958 :
959 0 : fd_txn_account_t durable_nonce_rec[1];
960 0 : fd_funk_txn_xid_t xid = { .ul = { fd_bank_slot_get( bank ), bank->idx } };
961 0 : int err = fd_txn_account_init_from_funk_readonly( durable_nonce_rec,
962 0 : &txn_out->accounts.keys[ nonce_idx ],
963 0 : runtime->funk,
964 0 : &xid );
965 0 : if( FD_UNLIKELY( err!=FD_ACC_MGR_SUCCESS ) ) {
966 0 : return FD_RUNTIME_TXN_ERR_BLOCKHASH_FAIL_ADVANCE_NONCE_INSTR;
967 0 : }
968 :
969 : /* https://github.com/anza-xyz/agave/blob/16de8b75ebcd57022409b422de557dd37b1de8db/sdk/src/nonce_account.rs#L28-L42 */
970 : /* verify_nonce_account */
971 0 : fd_pubkey_t const * owner_pubkey = fd_txn_account_get_owner( durable_nonce_rec );
972 0 : if( FD_UNLIKELY( memcmp( owner_pubkey, fd_solana_system_program_id.key, sizeof( fd_pubkey_t ) ) ) ) {
973 0 : return FD_RUNTIME_TXN_ERR_BLOCKHASH_FAIL_ADVANCE_NONCE_INSTR;
974 0 : }
975 :
976 0 : fd_nonce_state_versions_t state[1];
977 0 : if( FD_UNLIKELY( !fd_bincode_decode_static(
978 0 : nonce_state_versions, state,
979 0 : fd_txn_account_get_data( durable_nonce_rec ),
980 0 : fd_txn_account_get_data_len( durable_nonce_rec ),
981 0 : NULL ) ) ) {
982 0 : return FD_RUNTIME_TXN_ERR_BLOCKHASH_FAIL_ADVANCE_NONCE_INSTR;
983 0 : }
984 :
985 : /* https://github.com/anza-xyz/agave/blob/16de8b75ebcd57022409b422de557dd37b1de8db/sdk/program/src/nonce/state/mod.rs#L36-L53 */
986 : /* verify_recent_blockhash. This checks that the decoded nonce record is
987 : not a legacy nonce nor uninitialized. If this is the case, then we can
988 : verify by comparing the decoded durable nonce to the recent blockhash */
989 0 : if( FD_UNLIKELY( fd_nonce_state_versions_is_legacy( state ) ) ) {
990 0 : return FD_RUNTIME_TXN_ERR_BLOCKHASH_FAIL_ADVANCE_NONCE_INSTR;
991 0 : }
992 :
993 0 : fd_nonce_state_t nonce_state = state->inner.current;
994 0 : if( FD_UNLIKELY( fd_nonce_state_is_uninitialized( &nonce_state ) ) ) {
995 0 : return FD_RUNTIME_TXN_ERR_BLOCKHASH_FAIL_ADVANCE_NONCE_INSTR;
996 0 : }
997 :
998 0 : if( FD_UNLIKELY( memcmp( &nonce_state.inner.initialized.durable_nonce, recent_blockhash, sizeof(fd_hash_t) ) ) ) {
999 0 : return FD_RUNTIME_TXN_ERR_BLOCKHASH_FAIL_WRONG_NONCE;
1000 0 : }
1001 :
1002 : /* Finally check that the nonce is authorized by seeing if any accounts in
1003 : the nonce instruction are signers. This is a successful exit case. */
1004 0 : for( ushort i=0; i<txn_instr->acct_cnt; ++i ) {
1005 0 : if( fd_txn_is_signer( TXN( txn_in->txn ), (int)instr_accts[i] ) ) {
1006 0 : if( !memcmp( &txn_out->accounts.keys[ instr_accts[i] ], &state->inner.current.inner.initialized.authority, sizeof(fd_pubkey_t) ) ) {
1007 : /*
1008 : Mark nonce account to make sure that we modify and hash the
1009 : account even if the transaction failed to execute
1010 : successfully.
1011 : */
1012 0 : txn_out->accounts.nonce_idx_in_txn = instr_accts[ 0 ];
1013 : /*
1014 : Now figure out the state that the nonce account should
1015 : advance to.
1016 : */
1017 0 : fd_account_meta_t const * meta = fd_funk_get_acc_meta_readonly(
1018 0 : runtime->funk,
1019 0 : &xid,
1020 0 : &txn_out->accounts.keys[ instr_accts[ 0UL ] ],
1021 0 : NULL,
1022 0 : &err,
1023 0 : NULL );
1024 0 : ulong acc_data_len = meta->dlen;
1025 :
1026 0 : if( FD_UNLIKELY( err!=FD_ACC_MGR_SUCCESS ) ) {
1027 0 : return FD_RUNTIME_TXN_ERR_BLOCKHASH_FAIL_ADVANCE_NONCE_INSTR;
1028 0 : }
1029 :
1030 0 : fd_blockhashes_t const * blockhashes = fd_bank_block_hash_queue_query( bank );
1031 0 : fd_blockhash_info_t const * last_bhash_info = fd_blockhashes_peek_last( blockhashes );
1032 0 : FD_TEST( last_bhash_info ); /* Agave panics here if the blockhash queue is empty */
1033 :
1034 : /* https://github.com/anza-xyz/agave/blob/v3.0.3/runtime/src/bank/check_transactions.rs#L217-L221*/
1035 0 : fd_nonce_state_versions_t new_state = {
1036 0 : .discriminant = fd_nonce_state_versions_enum_current,
1037 0 : .inner = { .current = {
1038 0 : .discriminant = fd_nonce_state_enum_initialized,
1039 0 : .inner = { .initialized = {
1040 0 : .authority = state->inner.current.inner.initialized.authority,
1041 0 : .durable_nonce = next_durable_nonce,
1042 0 : .fee_calculator = {
1043 : /* https://github.com/anza-xyz/agave/blob/v3.0.3/runtime/src/bank/check_transactions.rs#L88-L90 */
1044 0 : .lamports_per_signature = last_bhash_info->fee_calculator.lamports_per_signature
1045 0 : }
1046 0 : } }
1047 0 : } }
1048 0 : };
1049 0 : if( FD_UNLIKELY( fd_nonce_state_versions_size( &new_state ) > FD_ACC_NONCE_SZ_MAX ) ) {
1050 0 : FD_LOG_ERR(( "fd_nonce_state_versions_size( &new_state ) %lu > FD_ACC_NONCE_SZ_MAX %lu", fd_nonce_state_versions_size( &new_state ), FD_ACC_NONCE_SZ_MAX ));
1051 0 : }
1052 : /* make_modifiable uses the old length for the data copy */
1053 0 : void * borrowed_account_data = txn_in->exec_accounts->rollback_nonce_account_mem;
1054 0 : if( FD_UNLIKELY( !borrowed_account_data ) ) {
1055 0 : FD_LOG_CRIT(( "Failed to allocate memory for nonce account" ));
1056 0 : }
1057 0 : if( FD_UNLIKELY( !meta ) ) {
1058 0 : FD_LOG_CRIT(( "Failed to get meta for nonce account" ));
1059 0 : }
1060 0 : fd_memcpy( borrowed_account_data, meta, sizeof(fd_account_meta_t)+acc_data_len );
1061 :
1062 0 : txn_out->accounts.rollback_nonce = (fd_account_meta_t *)borrowed_account_data;
1063 :
1064 0 : if( FD_UNLIKELY( fd_nonce_state_versions_size( &new_state )>txn_out->accounts.rollback_nonce->dlen ) ) {
1065 0 : return FD_RUNTIME_TXN_ERR_BLOCKHASH_FAIL_ADVANCE_NONCE_INSTR;
1066 0 : }
1067 0 : do {
1068 0 : fd_bincode_encode_ctx_t encode_ctx =
1069 0 : { .data = fd_account_data( txn_out->accounts.rollback_nonce ),
1070 0 : .dataend = fd_account_data( txn_out->accounts.rollback_nonce ) + txn_out->accounts.rollback_nonce->dlen };
1071 0 : int err = fd_nonce_state_versions_encode( &new_state, &encode_ctx );
1072 0 : if( FD_UNLIKELY( err ) ) {
1073 0 : return FD_RUNTIME_TXN_ERR_BLOCKHASH_FAIL_ADVANCE_NONCE_INSTR;
1074 0 : }
1075 0 : } while(0);
1076 0 : return FD_RUNTIME_EXECUTE_SUCCESS;
1077 0 : }
1078 0 : }
1079 0 : }
1080 : /* This means that the blockhash was not found */
1081 0 : return FD_RUNTIME_TXN_ERR_BLOCKHASH_FAIL_ADVANCE_NONCE_INSTR;
1082 :
1083 0 : }
|