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