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