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