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