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