Line data Source code
1 : #include "fd_program_cache.h"
2 : #include "fd_bpf_loader_program.h"
3 : #include "fd_loader_v4_program.h"
4 : #include "../../vm/fd_vm.h"
5 : #include "../sysvar/fd_sysvar_epoch_schedule.h"
6 : #include "../../../funk/fd_funk_rec.h"
7 :
8 : #include <assert.h>
9 :
10 : fd_program_cache_entry_t *
11 : fd_program_cache_entry_new( void * mem,
12 : fd_sbpf_elf_info_t const * elf_info,
13 : ulong last_slot_modified,
14 36 : ulong last_slot_verified ) {
15 36 : fd_program_cache_entry_t * cache_entry = (fd_program_cache_entry_t *)mem;
16 36 : cache_entry->magic = FD_PROGRAM_CACHE_ENTRY_MAGIC;
17 :
18 : /* Failed verification flag */
19 36 : cache_entry->failed_verification = 0;
20 :
21 : /* Last slot the program was modified */
22 36 : cache_entry->last_slot_modified = last_slot_modified;
23 :
24 : /* Last slot verification checks were ran for this program */
25 36 : cache_entry->last_slot_verified = last_slot_verified;
26 :
27 36 : ulong l = FD_LAYOUT_INIT;
28 :
29 : /* calldests backing memory */
30 36 : l = FD_LAYOUT_APPEND( l, alignof(fd_program_cache_entry_t), sizeof(fd_program_cache_entry_t) );
31 36 : cache_entry->calldests_shmem_off = l;
32 :
33 : /* rodata backing memory */
34 36 : l = FD_LAYOUT_APPEND( l, fd_sbpf_calldests_align(), fd_sbpf_calldests_footprint( elf_info->text_cnt ) );
35 36 : cache_entry->rodata_off = l;
36 :
37 : /* SBPF version */
38 36 : cache_entry->sbpf_version = elf_info->sbpf_version;
39 :
40 36 : return cache_entry;
41 36 : }
42 :
43 : /* Sets up defined fields for a record that failed verification. */
44 : static void
45 : fd_program_cache_entry_set_failed_verification( void * mem,
46 12 : ulong last_slot_verified ) {
47 12 : fd_program_cache_entry_t * cache_entry = (fd_program_cache_entry_t *)mem;
48 12 : cache_entry->magic = FD_PROGRAM_CACHE_ENTRY_MAGIC;
49 :
50 : /* Failed verification flag */
51 12 : cache_entry->failed_verification = 1;
52 :
53 : /* Last slot the program was modified */
54 12 : cache_entry->last_slot_modified = 0UL;
55 :
56 : /* Last slot verification checks were ran for this program */
57 12 : cache_entry->last_slot_verified = last_slot_verified;
58 :
59 : /* All other fields are undefined. */
60 12 : }
61 :
62 : ulong
63 36 : fd_program_cache_entry_footprint( fd_sbpf_elf_info_t const * elf_info ) {
64 36 : ulong l = FD_LAYOUT_INIT;
65 36 : l = FD_LAYOUT_APPEND( l, alignof(fd_program_cache_entry_t), sizeof(fd_program_cache_entry_t) );
66 36 : l = FD_LAYOUT_APPEND( l, fd_sbpf_calldests_align(), fd_sbpf_calldests_footprint( elf_info->text_cnt ) );
67 36 : l = FD_LAYOUT_APPEND( l, 8UL, elf_info->bin_sz );
68 36 : l = FD_LAYOUT_FINI( l, alignof(fd_program_cache_entry_t) );
69 36 : return l;
70 36 : }
71 :
72 : /* Returns the footprint of a record that failed verification. Note that
73 : all other fields are undefined besides the `failed_verification`,
74 : `last_slot_verified`, and `last_slot_modified` fields, so we can
75 : just return the core struct's size. */
76 : static inline FD_FN_PURE ulong
77 12 : fd_program_cache_failed_verification_entry_footprint( void ) {
78 12 : ulong l = FD_LAYOUT_INIT;
79 12 : l = FD_LAYOUT_APPEND( l, alignof(fd_program_cache_entry_t), sizeof(fd_program_cache_entry_t) );
80 12 : l = FD_LAYOUT_FINI( l, alignof(fd_program_cache_entry_t) );
81 12 : return l;
82 12 : }
83 :
84 : fd_funk_rec_key_t
85 210 : fd_program_cache_key( fd_pubkey_t const * pubkey ) {
86 210 : fd_funk_rec_key_t id;
87 210 : memcpy( id.uc, pubkey, sizeof(fd_pubkey_t) );
88 210 : memset( id.uc + sizeof(fd_pubkey_t), 0, sizeof(fd_funk_rec_key_t) - sizeof(fd_pubkey_t) );
89 :
90 210 : id.uc[ FD_FUNK_REC_KEY_FOOTPRINT - 1 ] = FD_FUNK_KEY_TYPE_ELF_CACHE;
91 :
92 210 : return id;
93 210 : }
94 :
95 : /* Parse ELF info from programdata. */
96 : static int
97 : fd_program_cache_parse_elf_info( fd_bank_t * bank,
98 : fd_sbpf_elf_info_t * elf_info,
99 : uchar const * program_data,
100 45 : ulong program_data_len ) {
101 45 : uint min_sbpf_version, max_sbpf_version;
102 45 : fd_bpf_get_sbpf_versions( &min_sbpf_version,
103 45 : &max_sbpf_version,
104 45 : fd_bank_slot_get( bank ),
105 45 : fd_bank_features_query( bank ) );
106 :
107 45 : fd_sbpf_loader_config_t config = { 0 };
108 45 : config.elf_deploy_checks = 0;
109 45 : config.sbpf_min_version = min_sbpf_version;
110 45 : config.sbpf_max_version = max_sbpf_version;
111 :
112 45 : if( FD_UNLIKELY( fd_sbpf_elf_peek( elf_info, program_data, program_data_len, &config )<0 ) ) {
113 9 : FD_LOG_DEBUG(( "fd_sbpf_elf_peek() failed" ));
114 9 : return -1;
115 9 : }
116 36 : return 0;
117 45 : }
118 :
119 : /* Similar to the below function, but gets the executable program content for the v4 loader.
120 : Unlike the v3 loader, the programdata is stored in a single program account. The program must
121 : NOT be retracted to be added to the cache. Returns a pointer to the programdata on success,
122 : and NULL on failure.
123 :
124 : Reasons for failure include:
125 : - The program state cannot be read from the account data or is in the `retracted` state. */
126 : static uchar const *
127 : fd_get_executable_program_content_for_v4_loader( fd_txn_account_t const * program_acc,
128 0 : ulong * program_data_len ) {
129 0 : int err;
130 :
131 : /* Get the current loader v4 state. This implicitly also checks the dlen. */
132 0 : fd_loader_v4_state_t const * state = fd_loader_v4_get_state( program_acc, &err );
133 0 : if( FD_UNLIKELY( err ) ) {
134 0 : return NULL;
135 0 : }
136 :
137 : /* The program must be deployed or finalized. */
138 0 : if( FD_UNLIKELY( fd_loader_v4_status_is_retracted( state ) ) ) {
139 0 : return NULL;
140 0 : }
141 :
142 : /* This subtraction is safe because get_state() implicitly checks the
143 : dlen. */
144 0 : *program_data_len = fd_txn_account_get_data_len( program_acc )-LOADER_V4_PROGRAM_DATA_OFFSET;
145 0 : return fd_txn_account_get_data( program_acc )+LOADER_V4_PROGRAM_DATA_OFFSET;
146 0 : }
147 :
148 : /* Gets the programdata for a v3 loader-owned account by decoding the account data
149 : as well as the programdata account. Returns a pointer to the programdata on success,
150 : and NULL on failure.
151 :
152 : Reasons for failure include:
153 : - The program account data cannot be decoded or is not in the `program` state.
154 : - The programdata account is not large enough to hold at least `PROGRAMDATA_METADATA_SIZE` bytes. */
155 : static uchar const *
156 : fd_get_executable_program_content_for_upgradeable_loader( fd_funk_t const * funk,
157 : fd_funk_txn_xid_t const * xid,
158 : fd_txn_account_t const * program_acc,
159 0 : ulong * program_data_len ) {
160 0 : fd_bpf_upgradeable_loader_state_t program_account_state[1];
161 0 : if( FD_UNLIKELY( !fd_bincode_decode_static(
162 0 : bpf_upgradeable_loader_state,
163 0 : program_account_state,
164 0 : fd_txn_account_get_data( program_acc ),
165 0 : fd_txn_account_get_data_len( program_acc ),
166 0 : NULL ) ) ) {
167 0 : return NULL;
168 0 : }
169 0 : if( !fd_bpf_upgradeable_loader_state_is_program( program_account_state ) ) {
170 0 : return NULL;
171 0 : }
172 :
173 0 : fd_pubkey_t * programdata_address = &program_account_state->inner.program.programdata_address;
174 0 : FD_TXN_ACCOUNT_DECL( programdata_acc );
175 0 : if( fd_txn_account_init_from_funk_readonly( programdata_acc, programdata_address, funk, xid )!=FD_ACC_MGR_SUCCESS ) {
176 0 : return NULL;
177 0 : }
178 :
179 : /* We don't actually need to decode here, just make sure that the account
180 : can be decoded successfully. */
181 0 : fd_bincode_decode_ctx_t ctx_programdata = {
182 0 : .data = fd_txn_account_get_data( programdata_acc ),
183 0 : .dataend = fd_txn_account_get_data( programdata_acc ) + fd_txn_account_get_data_len( programdata_acc ),
184 0 : };
185 :
186 0 : ulong total_sz = 0UL;
187 0 : if( FD_UNLIKELY( fd_bpf_upgradeable_loader_state_decode_footprint( &ctx_programdata, &total_sz ) ) ) {
188 0 : return NULL;
189 0 : }
190 :
191 0 : if( FD_UNLIKELY( fd_txn_account_get_data_len( programdata_acc )<PROGRAMDATA_METADATA_SIZE ) ) {
192 0 : return NULL;
193 0 : }
194 :
195 0 : *program_data_len = fd_txn_account_get_data_len( programdata_acc ) - PROGRAMDATA_METADATA_SIZE;
196 0 : return fd_txn_account_get_data( programdata_acc ) + PROGRAMDATA_METADATA_SIZE;
197 0 : }
198 :
199 : /* Gets the programdata for a v1/v2 loader-owned account by returning a
200 : pointer to the account data. Returns a pointer to the programdata on
201 : success. Given the txn account API always returns a handle to the
202 : account data, this function should NEVER return NULL (since the
203 : programdata of v1 and v2 loader) accounts start at the beginning of
204 : the data. */
205 : static uchar const *
206 : fd_get_executable_program_content_for_v1_v2_loaders( fd_txn_account_t const * program_acc,
207 45 : ulong * program_data_len ) {
208 45 : *program_data_len = fd_txn_account_get_data_len( program_acc );
209 45 : return fd_txn_account_get_data( program_acc );
210 45 : }
211 :
212 : uchar const *
213 : fd_program_cache_get_account_programdata( fd_funk_t const * funk,
214 : fd_funk_txn_xid_t const * xid,
215 : fd_txn_account_t const * program_acc,
216 45 : ulong * out_program_data_len ) {
217 : /* v1/v2 loaders: Programdata is just the account data.
218 : v3 loader: Programdata lives in a separate account. Deserialize the
219 : program account and lookup the programdata account.
220 : Deserialize the programdata account.
221 : v4 loader: Programdata lives in the program account, offset by
222 : LOADER_V4_PROGRAM_DATA_OFFSET. */
223 45 : if( !memcmp( fd_txn_account_get_owner( program_acc ), fd_solana_bpf_loader_upgradeable_program_id.key, sizeof(fd_pubkey_t) ) ) {
224 0 : return fd_get_executable_program_content_for_upgradeable_loader( funk, xid, program_acc, out_program_data_len );
225 45 : } else if( !memcmp( fd_txn_account_get_owner( program_acc ), fd_solana_bpf_loader_v4_program_id.key, sizeof(fd_pubkey_t) ) ) {
226 0 : return fd_get_executable_program_content_for_v4_loader( program_acc, out_program_data_len );
227 45 : } else if( !memcmp( fd_txn_account_get_owner( program_acc ), fd_solana_bpf_loader_program_id.key, sizeof(fd_pubkey_t) ) ||
228 45 : !memcmp( fd_txn_account_get_owner( program_acc ), fd_solana_bpf_loader_deprecated_program_id.key, sizeof(fd_pubkey_t) ) ) {
229 45 : return fd_get_executable_program_content_for_v1_v2_loaders( program_acc, out_program_data_len );
230 45 : }
231 0 : return NULL;
232 45 : }
233 :
234 : /* This function is used to validate an sBPF program and set the
235 : program's flags accordingly. The return code only signifies whether
236 : the program was successfully validated or not. Regardless of the
237 : return code, the program should still be added to the cache.
238 : `cache_entry` is expected to be a pre-allocated struct with enough
239 : space to hold its field members + calldests info.
240 :
241 : Reasons for failure include:
242 : - Insufficient memory in the spad to allocate memory for local
243 : objects.
244 : - The sBPF program fails to be loaded or validated validated.
245 :
246 : On a failure that doesn't kill the client, the `failed_verification`
247 : flag for the record is set to 1, and the `last_slot_verified` field
248 : is set to the current slot.
249 :
250 : On success, `cache_entry` is updated with the loaded sBPF program
251 : metadata, as well as the `last_slot_modified`, `last_slot_verified`,
252 : and `failed_verification` flags. */
253 : static int
254 : fd_program_cache_validate_sbpf_program( fd_bank_t * bank,
255 : fd_sbpf_elf_info_t const * elf_info,
256 : uchar const * program_data,
257 : ulong program_data_len,
258 : fd_spad_t * runtime_spad,
259 36 : fd_program_cache_entry_t * cache_entry /* out */ ) {
260 36 : ulong prog_align = fd_sbpf_program_align();
261 36 : ulong prog_footprint = fd_sbpf_program_footprint( elf_info );
262 36 : void * prog_mem = fd_spad_alloc_check( runtime_spad, prog_align, prog_footprint );
263 36 : fd_sbpf_program_t * prog = fd_sbpf_program_new( prog_mem , elf_info, fd_program_cache_get_rodata( cache_entry ) );
264 36 : if( FD_UNLIKELY( !prog ) ) {
265 0 : FD_LOG_DEBUG(( "fd_sbpf_program_new() failed" ));
266 0 : cache_entry->failed_verification = 1;
267 0 : return -1;
268 0 : }
269 :
270 : /* Allocate syscalls */
271 :
272 36 : void * syscalls_mem = fd_spad_alloc_check( runtime_spad, fd_sbpf_syscalls_align(), fd_sbpf_syscalls_footprint() );
273 36 : fd_sbpf_syscalls_t * syscalls = fd_sbpf_syscalls_join( fd_sbpf_syscalls_new( syscalls_mem ) );
274 36 : if( FD_UNLIKELY( !syscalls ) ) {
275 0 : FD_LOG_CRIT(( "Call to fd_sbpf_syscalls_new() failed" ));
276 0 : }
277 :
278 36 : fd_vm_syscall_register_slot( syscalls,
279 36 : fd_bank_slot_get( bank ),
280 36 : fd_bank_features_query( bank ),
281 36 : 0 );
282 :
283 : /* Load program. */
284 :
285 36 : fd_sbpf_loader_config_t config = { 0 };
286 36 : if( FD_UNLIKELY( 0!=fd_sbpf_program_load( prog, program_data, program_data_len, syscalls, &config ) ) ) {
287 0 : FD_LOG_DEBUG(( "fd_sbpf_program_load() failed" ));
288 0 : cache_entry->failed_verification = 1;
289 0 : fd_sbpf_syscalls_leave( syscalls );
290 0 : return -1;
291 0 : }
292 :
293 : /* Validate the program. */
294 :
295 36 : fd_vm_t _vm[ 1UL ];
296 36 : fd_vm_t * vm = fd_vm_join( fd_vm_new( _vm ) );
297 36 : if( FD_UNLIKELY( !vm ) ) {
298 0 : FD_LOG_CRIT(( "fd_vm_new() or fd_vm_join() failed" ));
299 0 : }
300 :
301 36 : int direct_mapping = FD_FEATURE_ACTIVE( fd_bank_slot_get( bank ), fd_bank_features_query( bank ), bpf_account_data_direct_mapping );
302 :
303 36 : vm = fd_vm_init( vm,
304 36 : NULL, /* OK since unused in `fd_vm_validate()` */
305 36 : 0UL,
306 36 : 0UL,
307 36 : prog->rodata,
308 36 : prog->rodata_sz,
309 36 : prog->text,
310 36 : prog->info.text_cnt,
311 36 : prog->info.text_off,
312 36 : prog->info.text_sz,
313 36 : prog->entry_pc,
314 36 : prog->calldests,
315 36 : elf_info->sbpf_version,
316 36 : syscalls,
317 36 : NULL,
318 36 : NULL,
319 36 : NULL,
320 36 : 0U,
321 36 : NULL,
322 36 : 0,
323 36 : direct_mapping,
324 36 : 0 );
325 :
326 36 : if( FD_UNLIKELY( !vm ) ) {
327 0 : FD_LOG_CRIT(( "fd_vm_init() failed" ));
328 0 : }
329 :
330 36 : int res = fd_vm_validate( vm );
331 36 : fd_sbpf_syscalls_leave( syscalls );
332 36 : if( FD_UNLIKELY( res ) ) {
333 3 : FD_LOG_DEBUG(( "fd_vm_validate() failed" ));
334 3 : cache_entry->failed_verification = 1;
335 3 : return -1;
336 3 : }
337 :
338 : /* FIXME: Super expensive memcpy. */
339 33 : if( FD_LIKELY( prog->calldests_shmem ) ) {
340 33 : fd_memcpy( fd_program_cache_get_calldests_shmem( cache_entry ), prog->calldests_shmem, fd_sbpf_calldests_footprint( prog->info.text_cnt ) );
341 33 : } else {
342 0 : fd_memset( fd_program_cache_get_calldests_shmem( cache_entry ), 0, fd_sbpf_calldests_footprint( prog->info.text_cnt ) );
343 0 : }
344 :
345 33 : cache_entry->entry_pc = prog->entry_pc;
346 33 : cache_entry->text_off = prog->info.text_off;
347 33 : cache_entry->text_cnt = prog->info.text_cnt;
348 33 : cache_entry->text_sz = prog->info.text_sz;
349 33 : cache_entry->rodata_sz = prog->rodata_sz;
350 33 : cache_entry->failed_verification = 0;
351 :
352 33 : return 0;
353 36 : }
354 :
355 : /* Publishes an in-prepare funk record for a program that failed
356 : verification. Creates a default sBPF validated program with the
357 : `failed_verification` flag set to 1 and `last_slot_verified` set to
358 : the current slot. The passed-in funk record is expected to be in a
359 : prepare. */
360 : static void
361 : fd_program_cache_publish_failed_verification_rec( fd_funk_t * funk,
362 : fd_funk_rec_prepare_t * prepare,
363 : fd_funk_rec_t * rec,
364 9 : ulong current_slot ) {
365 : /* Truncate the record to have a minimal footprint */
366 9 : ulong record_sz = fd_program_cache_failed_verification_entry_footprint();
367 9 : void * data = fd_funk_val_truncate( rec, fd_funk_alloc( funk ), fd_funk_wksp( funk ), 0UL, record_sz, NULL );
368 9 : if( FD_UNLIKELY( data==NULL ) ) {
369 0 : FD_LOG_ERR(( "fd_funk_val_truncate() failed to truncate record to size %lu", record_sz ));
370 0 : }
371 :
372 9 : fd_program_cache_entry_set_failed_verification( data, current_slot );
373 9 : fd_funk_rec_publish( funk, prepare );
374 9 : }
375 :
376 : /* Validates an SBPF program and adds it to the program cache.
377 : Reasons for verification failure include:
378 : - The ELF info cannot be parsed or validated from the programdata.
379 : - The sBPF program fails to be validated.
380 :
381 : The program will still be added to the cache even if verifications
382 : fail. This is to prevent a DOS vector where an attacker could spam
383 : invocations to programs that failed verification. */
384 : static void
385 : fd_program_cache_create_cache_entry( fd_bank_t * bank,
386 : fd_funk_t * funk,
387 : fd_funk_txn_xid_t const * xid,
388 : fd_txn_account_t const * program_acc,
389 27 : fd_spad_t * runtime_spad ) {
390 27 : FD_SPAD_FRAME_BEGIN( runtime_spad ) {
391 27 : ulong current_slot = fd_bank_slot_get( bank );
392 :
393 : /* Prepare the funk record for the program cache. */
394 27 : fd_pubkey_t const * program_pubkey = program_acc->pubkey;
395 27 : fd_funk_rec_key_t id = fd_program_cache_key( program_pubkey );
396 :
397 : /* Try to get the programdata for the account. If it doesn't exist,
398 : simply return without publishing anything. The program could have
399 : been closed, but we do not want to touch the cache in this case. */
400 27 : ulong program_data_len = 0UL;
401 27 : uchar const * program_data = fd_program_cache_get_account_programdata( funk, xid, program_acc, &program_data_len );
402 :
403 : /* This prepare should never fail. */
404 27 : int funk_err = FD_FUNK_SUCCESS;
405 27 : fd_funk_rec_prepare_t prepare[1];
406 27 : fd_funk_rec_t * rec = fd_funk_rec_prepare( funk, xid, &id, prepare, &funk_err );
407 27 : if( rec == NULL || funk_err != FD_FUNK_SUCCESS ) {
408 0 : FD_LOG_CRIT(( "fd_funk_rec_prepare() failed: %i-%s", funk_err, fd_funk_strerror( funk_err ) ));
409 0 : }
410 :
411 : /* In Agave's load_program_with_pubkey(), if program data cannot be
412 : obtained, a tombstone cache entry of type Closed or
413 : FailedVerification is created. For correctness, we could just
414 : not insert a cache entry when there is no valid program data.
415 : Nonetheless, for purely conformance on instruction error log
416 : messages reasons, specifically "Program is not deployed" vs
417 : "Program is not cached", we would like to have a cache entry
418 : precisely when Agave does, such that we match Agave exactly on
419 : this error log. So, we insert a cache entry here. */
420 27 : if( FD_UNLIKELY( program_data==NULL ) ) {
421 0 : fd_program_cache_publish_failed_verification_rec( funk, prepare, rec, current_slot );
422 0 : return;
423 0 : }
424 :
425 27 : fd_sbpf_elf_info_t elf_info = {0};
426 27 : if( FD_UNLIKELY( fd_program_cache_parse_elf_info( bank, &elf_info, program_data, program_data_len ) ) ) {
427 6 : fd_program_cache_publish_failed_verification_rec( funk, prepare, rec, current_slot );
428 6 : return;
429 6 : }
430 :
431 21 : ulong val_sz = fd_program_cache_entry_footprint( &elf_info );
432 21 : void * val = fd_funk_val_truncate(
433 21 : rec,
434 21 : fd_funk_alloc( funk ),
435 21 : fd_funk_wksp( funk ),
436 21 : 0UL,
437 21 : val_sz,
438 21 : &funk_err );
439 21 : if( FD_UNLIKELY( funk_err ) ) {
440 0 : FD_LOG_ERR(( "fd_funk_val_truncate(sz=%lu) for account failed (%i-%s)", val_sz, funk_err, fd_funk_strerror( funk_err ) ));
441 0 : }
442 :
443 : /* Note that the cache entry points to the funk record data
444 : and writes into the record directly to avoid an expensive memcpy.
445 : Since this record is fresh, we should set the last slot modified
446 : to 0. */
447 21 : fd_program_cache_entry_t * cache_entry = fd_program_cache_entry_new( val, &elf_info, 0UL, current_slot );
448 21 : int res = fd_program_cache_validate_sbpf_program( bank, &elf_info, program_data, program_data_len, runtime_spad, cache_entry );
449 21 : if( FD_UNLIKELY( res ) ) {
450 3 : fd_program_cache_publish_failed_verification_rec( funk, prepare, rec, current_slot );
451 3 : return;
452 3 : }
453 :
454 18 : fd_funk_rec_publish( funk, prepare );
455 27 : } FD_SPAD_FRAME_END;
456 27 : }
457 :
458 : int
459 : fd_program_cache_load_entry( fd_funk_t const * funk,
460 : fd_funk_txn_xid_t const * xid,
461 : fd_pubkey_t const * program_pubkey,
462 120 : fd_program_cache_entry_t const ** cache_entry ) {
463 120 : fd_funk_rec_key_t id = fd_program_cache_key( program_pubkey );
464 :
465 120 : for(;;) {
466 120 : fd_funk_rec_query_t query[1];
467 120 : fd_funk_rec_t const * rec = fd_funk_rec_query_try_global(funk, xid, &id, NULL, query);
468 :
469 120 : if( FD_UNLIKELY( !rec ) ) {
470 : /* If rec is NULL, we shouldn't inspect query below because it
471 : would contain uninitialized fields. */
472 39 : return -1;
473 39 : }
474 :
475 81 : void const * data = fd_funk_val_const( rec, fd_funk_wksp(funk) );
476 :
477 81 : *cache_entry = (fd_program_cache_entry_t const *)data;
478 :
479 : /* This test is actually too early. It should happen after the
480 : data is actually consumed.
481 :
482 : TODO: this is likely fine because nothing else is modifying the
483 : program cache records at the same time. */
484 81 : if( FD_LIKELY( fd_funk_rec_query_test( query ) == FD_FUNK_SUCCESS ) ) {
485 81 : if( FD_UNLIKELY( (*cache_entry)->magic != FD_PROGRAM_CACHE_ENTRY_MAGIC ) ) FD_LOG_ERR(( "invalid magic" ));
486 81 : return 0;
487 81 : }
488 :
489 : /* Try again */
490 81 : }
491 120 : }
492 :
493 : void
494 : fd_program_cache_update_program( fd_bank_t * bank,
495 : fd_funk_t * funk,
496 : fd_funk_txn_xid_t const * xid,
497 : fd_pubkey_t const * program_key,
498 51 : fd_spad_t * runtime_spad ) {
499 51 : FD_SPAD_FRAME_BEGIN( runtime_spad ) {
500 51 : FD_TXN_ACCOUNT_DECL( exec_rec );
501 51 : fd_funk_rec_key_t id = fd_program_cache_key( program_key );
502 :
503 : /* No need to touch the cache if the account no longer exists. */
504 51 : if( FD_UNLIKELY( fd_txn_account_init_from_funk_readonly( exec_rec,
505 51 : program_key,
506 51 : funk,
507 51 : xid ) ) ) {
508 3 : return;
509 3 : }
510 :
511 : /* The account owner must be a BPF loader to even be considered. */
512 48 : if( FD_UNLIKELY( !fd_executor_pubkey_is_bpf_loader( fd_txn_account_get_owner( exec_rec ) ) ) ) {
513 3 : return;
514 3 : }
515 :
516 : /* If the program is not present in the cache yet, then we should run
517 : verifications and add it to the cache.
518 : `fd_program_cache_create_cache_entry()` will insert the program
519 : into the cache and update the entry's flags accordingly if it fails
520 : verification. */
521 45 : fd_program_cache_entry_t const * existing_entry = NULL;
522 45 : int err = fd_program_cache_load_entry( funk, xid, program_key, &existing_entry );
523 45 : if( FD_UNLIKELY( err ) ) {
524 27 : fd_program_cache_create_cache_entry( bank, funk, xid, exec_rec, runtime_spad );
525 27 : return;
526 27 : }
527 :
528 : /* At this point, the program exists in the cache but we may need to
529 : reverify it and update the program cache. A program does NOT
530 : require reverification if both of the following conditions are met:
531 : - The program was already reverified in the current epoch
532 : - EITHER of the following are met:
533 : - The program was not modified in a recent slot (i.e. the program
534 : was already reverified since the last time it was modified)
535 : - The program was already verified in the current slot
536 :
537 : We must reverify the program for the current epoch if it has not
538 : been reverified yet. Additionally, we cannot break the invariant
539 : that a program cache entry will get reverified more than once
540 : per slot, otherwise the replay and exec tiles may race. */
541 18 : fd_epoch_schedule_t const * epoch_schedule = fd_bank_epoch_schedule_query( bank );
542 18 : ulong current_slot = fd_bank_slot_get( bank );
543 18 : ulong current_epoch = fd_bank_epoch_get( bank );
544 18 : ulong last_epoch_verified = fd_slot_to_epoch( epoch_schedule, existing_entry->last_slot_verified, NULL );
545 18 : ulong last_slot_modified = existing_entry->last_slot_modified;
546 18 : if( FD_LIKELY( last_epoch_verified==current_epoch &&
547 18 : ( last_slot_modified<existing_entry->last_slot_verified ||
548 18 : current_slot==existing_entry->last_slot_verified ) ) ) {
549 0 : return;
550 0 : }
551 :
552 : /* Get the program data from the account. If the programdata does not
553 : exist, there is no need to touch the cache - if someone tries to
554 : invoke this program, account loading checks will fail. */
555 18 : ulong program_data_len = 0UL;
556 18 : uchar const * program_data = fd_program_cache_get_account_programdata( funk,
557 18 : xid,
558 18 : exec_rec,
559 18 : &program_data_len );
560 18 : if( FD_UNLIKELY( program_data==NULL ) ) {
561 : /* Unlike in fd_program_cache_create_cache_entry(), where we need to
562 : insert an entry into the cache when we cannot obtain valid
563 : program data, here we could simply return because we know, at
564 : this point, that the program is already in the cache. So we
565 : don't need to do anything extra for matching Agave on cache entry
566 : presence. */
567 0 : return;
568 0 : }
569 :
570 : /* From here on out, we need to reverify the program. */
571 :
572 : /* Parse out the ELF info so we can determine the record footprint.
573 : If the parsing fails, then we need to make sure to later publish
574 : a failed verification record. */
575 18 : uchar failed_elf_parsing = 0;
576 18 : ulong record_sz = 0UL;
577 18 : fd_sbpf_elf_info_t elf_info = {0};
578 18 : if( FD_UNLIKELY( fd_program_cache_parse_elf_info( bank, &elf_info, program_data, program_data_len ) ) ) {
579 3 : failed_elf_parsing = 1;
580 3 : record_sz = fd_program_cache_failed_verification_entry_footprint();
581 15 : } else {
582 15 : failed_elf_parsing = 0;
583 15 : record_sz = fd_program_cache_entry_footprint( &elf_info );
584 15 : }
585 :
586 : /* Resize the record to the new footprint if needed */
587 18 : uchar data[ record_sz ];
588 18 : fd_program_cache_entry_t * writable_entry = fd_type_pun( data );
589 :
590 : /* If the ELF header parsing failed, publish a failed verification
591 : record. */
592 18 : if( FD_UNLIKELY( failed_elf_parsing ) ) {
593 3 : fd_program_cache_entry_set_failed_verification( writable_entry, current_slot );
594 15 : } else {
595 : /* Validate the sBPF program. This will set the program's flags
596 : accordingly. We publish the funk record regardless of the return
597 : code. */
598 15 : writable_entry = fd_program_cache_entry_new( data, &elf_info, last_slot_modified, current_slot );
599 15 : int res = fd_program_cache_validate_sbpf_program( bank, &elf_info, program_data, program_data_len, runtime_spad, writable_entry );
600 15 : if( FD_UNLIKELY( res ) ) {
601 0 : FD_LOG_DEBUG(( "fd_program_cache_validate_sbpf_program() failed" ));
602 0 : }
603 15 : }
604 :
605 18 : fd_funk_rec_insert_para( funk, xid, &id, alignof(fd_program_cache_entry_t), record_sz, writable_entry );
606 :
607 51 : } FD_SPAD_FRAME_END;
608 51 : }
609 :
610 : void
611 : fd_program_cache_queue_program_for_reverification( fd_funk_t * funk,
612 : fd_funk_txn_xid_t const * xid,
613 : fd_pubkey_t const * program_key,
614 12 : ulong current_slot ) {
615 :
616 : /* We want to access the program cache entry for this pubkey and
617 : queue it for reverification. If it exists, clone it down to the
618 : current funk txn and update the entry's `last_slot_modified`
619 : field.
620 :
621 : This read is thread-safe because if you have transaction A that
622 : modifies the program and transaction B that references the program,
623 : the dispatcher will not attempt to execute transaction B until
624 : transaction A is finalized. */
625 12 : fd_program_cache_entry_t const * existing_entry = NULL;
626 12 : int err = fd_program_cache_load_entry( funk, xid, program_key, &existing_entry );
627 12 : if( FD_UNLIKELY( err ) ) {
628 3 : return;
629 3 : }
630 :
631 : /* Ensure the record is in the current funk transaction */
632 9 : fd_funk_rec_key_t id = fd_program_cache_key( program_key );
633 9 : fd_program_cache_entry_t entry = *existing_entry;
634 9 : entry.last_slot_modified = current_slot;
635 9 : fd_funk_rec_insert_para( funk, xid, &id, alignof(fd_program_cache_entry_t), sizeof(fd_program_cache_entry_t), &entry );
636 9 : }
637 :
638 : int
639 : fd_funk_rec_insert_para( fd_funk_t * funk,
640 : fd_funk_txn_xid_t const * xid,
641 : fd_funk_rec_key_t const * key,
642 : ulong val_align,
643 : ulong val_sz,
644 27 : void * val ) {
645 27 : if( FD_UNLIKELY( !funk ) ) FD_LOG_ERR(( "NULL funk" ));
646 27 : if( FD_UNLIKELY( !xid ) ) FD_LOG_ERR(( "NULL xid" ));
647 27 : if( FD_UNLIKELY( !key ) ) FD_LOG_ERR(( "NULL key" ));
648 27 : if( FD_UNLIKELY( !val && val_sz ) ) FD_LOG_ERR(( "NULL val" ));
649 :
650 27 : fd_funk_xid_key_pair_t pair[1];
651 27 : fd_funk_txn_xid_copy( pair->xid, xid );
652 27 : fd_funk_rec_key_copy( pair->key, key );
653 :
654 27 : for(;;) {
655 : /* See if the record already exists. */
656 27 : fd_funk_rec_query_t query[1];
657 27 : int err = fd_funk_rec_map_query_try( funk->rec_map, pair, NULL, query, 0 );
658 27 : if( err == FD_MAP_SUCCESS ) {
659 27 : fd_funk_rec_t * rec = fd_funk_rec_map_query_ele( query );
660 : /* Set the value of the record */
661 27 : if( !fd_funk_val_truncate( rec, fd_funk_alloc( funk ), fd_funk_wksp( funk ), val_align, val_sz, &err ) ) {
662 0 : FD_LOG_ERR(( "fd_funk_val_truncate() failed (out of memory?)" ));
663 0 : return err;
664 0 : }
665 27 : memcpy( fd_funk_val( rec, fd_funk_wksp( funk ) ), val, val_sz );
666 27 : return FD_FUNK_SUCCESS;
667 27 : }
668 :
669 : /* The record doesn't exist. Create it. */
670 0 : fd_funk_rec_prepare_t prepare[1];
671 0 : fd_funk_rec_t * rec = fd_funk_rec_prepare( funk, xid, key, prepare, &err );
672 0 : if( FD_UNLIKELY( !rec ) ) {
673 0 : FD_LOG_CRIT(( "fd_funk_rec_prepare returned err=%d", err ));
674 0 : return err;
675 0 : }
676 : /* Set the value of the record */
677 0 : if( !fd_funk_val_truncate( rec, fd_funk_alloc( funk ), fd_funk_wksp( funk ), val_align, val_sz, &err ) ) {
678 0 : FD_LOG_ERR(( "fd_funk_val_truncate() failed (out of memory?)" ));
679 0 : return err;
680 0 : }
681 0 : memcpy( fd_funk_val( rec, fd_funk_wksp( funk ) ), val, val_sz );
682 0 : fd_funk_rec_publish( funk, prepare );
683 0 : return FD_FUNK_SUCCESS;
684 0 : }
685 27 : }
|