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