Skip to content

Commit 8f6465a

Browse files
committed
flamenco, program-cache: allocate correct size for modified funk records
1 parent f97aaae commit 8f6465a

File tree

5 files changed

+195
-36
lines changed

5 files changed

+195
-36
lines changed
43.1 KB
Binary file not shown.

src/flamenco/runtime/program/fd_program_cache.c

Lines changed: 81 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ fd_program_cache_entry_new( void * mem,
1111
ulong last_slot_modified,
1212
ulong last_slot_verified ) {
1313
fd_program_cache_entry_t * cache_entry = (fd_program_cache_entry_t *)mem;
14+
cache_entry->magic = FD_PROGRAM_CACHE_ENTRY_MAGIC;
1415

1516
/* Failed verification flag */
1617
cache_entry->failed_verification = 0;
@@ -26,7 +27,6 @@ fd_program_cache_entry_new( void * mem,
2627
/* calldests backing memory */
2728
l = FD_LAYOUT_APPEND( l, alignof(fd_program_cache_entry_t), sizeof(fd_program_cache_entry_t) );
2829
cache_entry->calldests_shmem = (uchar *)mem + l;
29-
cache_entry->magic = FD_PROGRAM_CACHE_ENTRY_MAGIC;
3030

3131
/* rodata backing memory */
3232
l = FD_LAYOUT_APPEND( l, fd_sbpf_calldests_align(), fd_sbpf_calldests_footprint(elf_info->rodata_sz/8UL) );
@@ -35,7 +35,26 @@ fd_program_cache_entry_new( void * mem,
3535
/* SBPF version */
3636
cache_entry->sbpf_version = elf_info->sbpf_version;
3737

38-
return (fd_program_cache_entry_t *)mem;
38+
return cache_entry;
39+
}
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+
ulong last_slot_verified ) {
45+
fd_program_cache_entry_t * cache_entry = (fd_program_cache_entry_t *)mem;
46+
cache_entry->magic = FD_PROGRAM_CACHE_ENTRY_MAGIC;
47+
48+
/* Failed verification flag */
49+
cache_entry->failed_verification = 1;
50+
51+
/* Last slot the program was modified */
52+
cache_entry->last_slot_modified = 0UL;
53+
54+
/* Last slot verification checks were ran for this program */
55+
cache_entry->last_slot_verified = last_slot_verified;
56+
57+
/* All other fields are undefined. */
3958
}
4059

4160
ulong
@@ -44,12 +63,23 @@ fd_program_cache_entry_footprint( fd_sbpf_elf_info_t const * elf_info ) {
4463
l = FD_LAYOUT_APPEND( l, alignof(fd_program_cache_entry_t), sizeof(fd_program_cache_entry_t) );
4564
l = FD_LAYOUT_APPEND( l, fd_sbpf_calldests_align(), fd_sbpf_calldests_footprint(elf_info->rodata_sz/8UL) );
4665
l = FD_LAYOUT_APPEND( l, 8UL, elf_info->rodata_footprint );
47-
l = FD_LAYOUT_FINI( l, 128UL );
66+
l = FD_LAYOUT_FINI( l, alignof(fd_program_cache_entry_t) );
4867
return l;
4968
}
5069

51-
/* Gets the program cache funk record key for a given program pubkey. */
52-
static inline fd_funk_rec_key_t
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+
fd_program_cache_failed_verification_entry_footprint( void ) {
76+
ulong l = FD_LAYOUT_INIT;
77+
l = FD_LAYOUT_APPEND( l, alignof(fd_program_cache_entry_t), sizeof(fd_program_cache_entry_t) );
78+
l = FD_LAYOUT_FINI( l, alignof(fd_program_cache_entry_t) );
79+
return l;
80+
}
81+
82+
fd_funk_rec_key_t
5383
fd_program_cache_key( fd_pubkey_t const * pubkey ) {
5484
fd_funk_rec_key_t id;
5585
memcpy( id.uc, pubkey, sizeof(fd_pubkey_t) );
@@ -161,10 +191,12 @@ fd_get_executable_program_content_for_upgradeable_loader( fd_funk_t const *
161191
return fd_txn_account_get_data( programdata_acc ) + PROGRAMDATA_METADATA_SIZE;
162192
}
163193

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. */
194+
/* Gets the programdata for a v1/v2 loader-owned account by returning a
195+
pointer to the account data. Returns a pointer to the programdata on
196+
success. Given the txn account API always returns a handle to the
197+
account data, this function should NEVER return NULL (since the
198+
programdata of v1 and v2 loader) accounts start at the beginning of
199+
the data. */
168200
static uchar const *
169201
fd_get_executable_program_content_for_v1_v2_loaders( fd_txn_account_t const * program_acc,
170202
ulong * program_data_len ) {
@@ -179,9 +211,11 @@ fd_program_cache_get_account_programdata( fd_funk_t const * funk,
179211
ulong * out_program_data_len,
180212
fd_spad_t * runtime_spad ) {
181213
/* 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. */
214+
v3 loader: Programdata lives in a separate account. Deserialize the
215+
program account and lookup the programdata account.
216+
Deserialize the programdata account.
217+
v4 loader: Programdata lives in the program account, offset by
218+
LOADER_V4_PROGRAM_DATA_OFFSET. */
185219
if( !memcmp( fd_txn_account_get_owner( program_acc ), fd_solana_bpf_loader_upgradeable_program_id.key, sizeof(fd_pubkey_t) ) ) {
186220
return fd_get_executable_program_content_for_upgradeable_loader( funk, funk_txn, program_acc, out_program_data_len, runtime_spad );
187221
} else if( !memcmp( fd_txn_account_get_owner( program_acc ), fd_solana_bpf_loader_v4_program_id.key, sizeof(fd_pubkey_t) ) ) {
@@ -317,19 +351,13 @@ fd_program_cache_publish_failed_verification_rec( fd_funk_t * funk,
317351
fd_funk_rec_t * rec,
318352
ulong current_slot ) {
319353
/* Truncate the record to have a minimal footprint */
320-
fd_sbpf_elf_info_t elf_info = {0};
321-
ulong record_sz = fd_program_cache_entry_footprint( &elf_info );
354+
ulong record_sz = fd_program_cache_failed_verification_entry_footprint();
322355
void * data = fd_funk_val_truncate( rec, fd_funk_alloc( funk ), fd_funk_wksp( funk ), 0UL, record_sz, NULL );
323356
if( FD_UNLIKELY( data==NULL ) ) {
324357
FD_LOG_ERR(( "fd_funk_val_truncate() failed to truncate record to size %lu", record_sz ));
325358
}
326359

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-
fd_program_cache_entry_t * failed_entry = fd_program_cache_entry_new( data, &elf_info, 0UL, current_slot );
331-
failed_entry->failed_verification = 1;
332-
360+
fd_program_cache_entry_set_failed_verification( data, current_slot );
333361
fd_funk_rec_publish( funk, prepare );
334362
}
335363

@@ -540,10 +568,26 @@ FD_SPAD_FRAME_BEGIN( runtime_spad ) {
540568

541569
/* From here on out, we need to reverify the program. */
542570

571+
/* Parse out the ELF info so we can determine the record footprint.
572+
If the parsing fails, then we need to make sure to later publish
573+
a failed verification record. */
574+
uchar failed_elf_parsing = 0;
575+
ulong record_sz = 0UL;
576+
fd_sbpf_elf_info_t elf_info = {0};
577+
if( FD_UNLIKELY( fd_program_cache_parse_elf_info( &elf_info, program_data, program_data_len, slot_ctx ) ) ) {
578+
failed_elf_parsing = 1;
579+
record_sz = fd_program_cache_failed_verification_entry_footprint();
580+
} else {
581+
failed_elf_parsing = 0;
582+
record_sz = fd_program_cache_entry_footprint( &elf_info );
583+
}
584+
543585
/* 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 */
586+
of its ancestors.
587+
588+
TODO: We pass in a `min_sz` of 0 because this API does not resize
589+
the record if it already exists in the current funk transaction.
590+
This maybe needs to change. */
547591
fd_funk_rec_try_clone_safe( slot_ctx->funk, slot_ctx->funk_txn, &id, 0UL, 0UL );
548592

549593
/* Modify the record within the current funk txn */
@@ -552,19 +596,25 @@ FD_SPAD_FRAME_BEGIN( runtime_spad ) {
552596

553597
if( FD_UNLIKELY( !rec ) ) {
554598
/* The record does not exist (somehow). Ideally this should never
555-
happen since this function is called in a single-threaded context. */
599+
happen as this function is called in a single-threaded context. */
556600
FD_LOG_CRIT(( "Failed to modify the BPF program cache record. Perhaps there is a race condition?" ));
557601
}
558602

559-
void * data = fd_funk_val( rec, fd_funk_wksp( slot_ctx->funk ) );
560-
fd_sbpf_elf_info_t elf_info = {0};
603+
/* Resize the record to the new footprint if needed */
604+
void * data = fd_funk_val_truncate(
605+
rec,
606+
fd_funk_alloc( slot_ctx->funk ),
607+
fd_funk_wksp( slot_ctx->funk ),
608+
0UL,
609+
record_sz,
610+
NULL );
611+
561612
fd_program_cache_entry_t * writable_entry = fd_type_pun( data );
562613

563-
/* Parse the ELF info */
564-
if( FD_UNLIKELY( fd_program_cache_parse_elf_info( &elf_info, program_data, program_data_len, slot_ctx ) ) ) {
565-
writable_entry->failed_verification = 1;
566-
writable_entry->last_slot_modified = 0UL;
567-
writable_entry->last_slot_verified = current_slot;
614+
/* If the ELF header parsing failed, publish a failed verification
615+
record. */
616+
if( FD_UNLIKELY( failed_elf_parsing ) ) {
617+
fd_program_cache_entry_set_failed_verification( writable_entry, current_slot );
568618
fd_funk_rec_modify_publish( query );
569619
return;
570620
}

src/flamenco/runtime/program/fd_program_cache.h

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,9 @@
5656
If a program fails verification due to invalid ELF headers / sBPF
5757
loading failures, then we update the `failed_verification` tag,
5858
set `last_slot_verified` to the current slot, and set
59-
`last_slot_modified` to 0. A future program upgrade / network feature
60-
set change could make this program valid again.
59+
`last_slot_modified` to 0. All other fields have undefined value. A
60+
future program upgrade / network feature set change could make this
61+
program valid again.
6162
6263
A key invariant with the program cache design is that it does not
6364
evict any entries - it only grows through the lifetime of the
@@ -211,6 +212,10 @@ fd_program_cache_entry_new( void * mem,
211212
ulong
212213
fd_program_cache_entry_footprint( fd_sbpf_elf_info_t const * elf_info );
213214

215+
/* Gets the program cache funk record key for a given program pubkey. */
216+
fd_funk_rec_key_t
217+
fd_program_cache_key( fd_pubkey_t const * pubkey );
218+
214219
/* Loads a single program cache entry for a given pubkey. Returns 0 on
215220
success and -1 on failure. On success, `*cache_entry` holds a pointer
216221
to the program cache entry. */

src/flamenco/runtime/program/test_program_cache.c

Lines changed: 103 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
/* Load in programdata for tests */
99
FD_IMPORT_BINARY( valid_program_data, "src/ballet/sbpf/fixtures/hello_solana_program.so" );
10+
FD_IMPORT_BINARY( bigger_valid_program_data, "src/ballet/sbpf/fixtures/clock_sysvar_program.so" );
1011

1112
static uchar const invalid_program_data[] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07};
1213

@@ -87,9 +88,26 @@ create_test_account( fd_pubkey_t const * pubkey,
8788
fd_txn_account_set_rent_epoch( acc, ULONG_MAX );
8889
fd_txn_account_set_owner( acc, owner );
8990

90-
/* make the account read-only by default */
91-
fd_txn_account_set_readonly( acc );
91+
fd_txn_account_mutable_fini( acc, test_funk, test_slot_ctx->funk_txn, &prepare );
92+
}
93+
94+
static void
95+
update_account_data( fd_pubkey_t const * pubkey,
96+
uchar const * data,
97+
ulong data_len ) {
98+
FD_TXN_ACCOUNT_DECL( acc );
99+
fd_funk_rec_prepare_t prepare = {0};
100+
int err = fd_txn_account_init_from_funk_mutable( /* acc */ acc,
101+
/* pubkey */ pubkey,
102+
/* funk */ test_funk,
103+
/* txn */ test_slot_ctx->funk_txn,
104+
/* do_create */ 0,
105+
/* min_data_sz */ data_len,
106+
/* prepare */ &prepare );
107+
FD_TEST( !err );
108+
FD_TEST( data );
92109

110+
fd_txn_account_set_data( acc, data, data_len );
93111
fd_txn_account_mutable_fini( acc, test_funk, test_slot_ctx->funk_txn, &prepare );
94112
}
95113

@@ -527,6 +545,88 @@ test_valid_genesis_program_reverified_after_genesis( void ) {
527545
fd_funk_txn_cancel( test_funk, funk_txn, 0 );
528546
}
529547

548+
/* Test 11: Program gets upgraded with a larger programdata size */
549+
static void
550+
test_program_upgraded_with_larger_programdata( void ) {
551+
FD_LOG_NOTICE(( "Testing: Program gets upgraded with a larger programdata size" ));
552+
553+
fd_funk_txn_t * funk_txn = create_test_funk_txn();
554+
test_slot_ctx->funk_txn = funk_txn;
555+
test_slot_ctx->bank->slot_ = 0UL;
556+
557+
/* Create a BPF loader account */
558+
create_test_account( &test_program_pubkey,
559+
&fd_solana_bpf_loader_program_id,
560+
valid_program_data,
561+
valid_program_data_sz,
562+
1 );
563+
564+
/* First call to create cache entry */
565+
fd_program_cache_update_program( test_slot_ctx, &test_program_pubkey, test_spad );
566+
567+
/* Verify cache entry was created */
568+
fd_program_cache_entry_t const * valid_prog = NULL;
569+
int err = fd_program_cache_load_entry( test_funk, funk_txn, &test_program_pubkey, &valid_prog );
570+
FD_TEST( !err );
571+
FD_TEST( valid_prog );
572+
FD_TEST( valid_prog->magic==FD_PROGRAM_CACHE_ENTRY_MAGIC );
573+
FD_TEST( !valid_prog->failed_verification );
574+
FD_TEST( valid_prog->last_slot_modified==0UL );
575+
FD_TEST( valid_prog->last_slot_verified==0UL );
576+
577+
/* Fast forward to a future slot */
578+
ulong original_slot = test_slot_ctx->bank->slot_;
579+
test_slot_ctx->bank->slot_ += 11000UL; /* Move to future slot */
580+
ulong future_slot = test_slot_ctx->bank->slot_;
581+
FD_TEST( future_slot>original_slot );
582+
583+
/* "Upgrade" the program by modifying the programdata */
584+
update_account_data( &test_program_pubkey, bigger_valid_program_data, bigger_valid_program_data_sz );
585+
586+
/* Queue the program for reverification */
587+
fd_program_cache_queue_program_for_reverification( test_funk, funk_txn, &test_program_pubkey, future_slot );
588+
589+
/* Verify the cache entry was updated with the future slot as last_slot_modified */
590+
err = fd_program_cache_load_entry( test_funk, funk_txn, &test_program_pubkey, &valid_prog );
591+
FD_TEST( !err );
592+
FD_TEST( valid_prog );
593+
FD_TEST( valid_prog->magic==FD_PROGRAM_CACHE_ENTRY_MAGIC );
594+
FD_TEST( !valid_prog->failed_verification );
595+
FD_TEST( valid_prog->last_slot_modified==future_slot );
596+
FD_TEST( valid_prog->last_slot_verified==original_slot );
597+
FD_TEST( valid_prog->last_slot_modified>original_slot );
598+
599+
/* Store the old program cache funk record size */
600+
fd_funk_rec_key_t id = fd_program_cache_key( &test_program_pubkey );
601+
fd_funk_rec_query_t query[1];
602+
fd_funk_rec_t const * prev_rec = fd_funk_rec_query_try_global( test_funk, funk_txn, &id, NULL, query );
603+
FD_TEST( prev_rec );
604+
ulong prev_rec_sz = prev_rec->val_sz;
605+
FD_TEST( !fd_funk_rec_query_test( query ) );
606+
607+
/* Program invoked, update cache entry */
608+
fd_program_cache_update_program( test_slot_ctx, &test_program_pubkey, test_spad );
609+
610+
/* Get the new program cache funk record size, and make sure it's
611+
larger */
612+
fd_funk_rec_t const * new_rec = fd_funk_rec_query_try_global( test_funk, funk_txn, &id, NULL, query );
613+
FD_TEST( new_rec );
614+
ulong new_rec_sz = new_rec->val_sz;
615+
FD_TEST( new_rec_sz>prev_rec_sz );
616+
FD_TEST( !fd_funk_rec_query_test( query ) );
617+
618+
/* Verify the cache entry was updated */
619+
err = fd_program_cache_load_entry( test_funk, funk_txn, &test_program_pubkey, &valid_prog );
620+
FD_TEST( !err );
621+
FD_TEST( valid_prog );
622+
FD_TEST( valid_prog->magic==FD_PROGRAM_CACHE_ENTRY_MAGIC );
623+
FD_TEST( !valid_prog->failed_verification );
624+
FD_TEST( valid_prog->last_slot_verified==future_slot );
625+
FD_TEST( valid_prog->last_slot_modified==future_slot );
626+
627+
fd_funk_txn_cancel( test_funk, funk_txn, 0 );
628+
}
629+
530630
int
531631
main( int argc,
532632
char ** argv ) {
@@ -604,6 +704,7 @@ main( int argc,
604704
test_program_in_cache_queued_for_reverification_and_processed();
605705
test_invalid_genesis_program_reverified_after_genesis();
606706
test_valid_genesis_program_reverified_after_genesis();
707+
test_program_upgraded_with_larger_programdata();
607708
} FD_SPAD_FRAME_END;
608709

609710
test_teardown();

src/funk/fd_funk_rec.c

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -352,7 +352,10 @@ fd_funk_rec_try_clone_safe( fd_funk_t * funk,
352352
funk, txn,key, &txn_glob, query_glob );
353353

354354
/* If the record exists and already exists in the specified funk
355-
txn, we can return successfully. */
355+
txn, we can return successfully.
356+
357+
TODO: This should probably also check that the record has a large
358+
enough size, i.e. rec_glob >= min_sz. */
356359
if( rec_glob && txn==txn_glob ) {
357360
return;
358361
}

0 commit comments

Comments
 (0)