diff --git a/src/app/firedancer-dev/commands/backtest.c b/src/app/firedancer-dev/commands/backtest.c index 21238fff3e5..fb39da07916 100644 --- a/src/app/firedancer-dev/commands/backtest.c +++ b/src/app/firedancer-dev/commands/backtest.c @@ -74,9 +74,16 @@ backtest_topo( config_t * config ) { config->firedancer.funk.max_database_transactions, config->firedancer.funk.heap_size_gib, config->firedancer.funk.lock_pages ); - fd_topob_tile_uses( topo, replay_tile, funk_obj, FD_SHMEM_JOIN_MODE_READ_WRITE ); + fd_topob_wksp( topo, "progcache" ); + fd_topo_obj_t * progcache_obj = setup_topo_progcache( topo, "progcache", + fd_progcache_est_rec_max( config->firedancer.runtime.program_cache.heap_size_mib<<20, + config->firedancer.runtime.program_cache.mean_cache_entry_size_kib<<10 ), + config->firedancer.funk.max_database_transactions, + config->firedancer.runtime.program_cache.heap_size_mib<<20 ); + fd_topob_tile_uses( topo, replay_tile, progcache_obj, FD_SHMEM_JOIN_MODE_READ_WRITE ); + /**********************************************************************/ /* Add the executor tiles to topo */ /**********************************************************************/ diff --git a/src/app/firedancer/config/default.toml b/src/app/firedancer/config/default.toml index a526bb50218..a924acceb89 100644 --- a/src/app/firedancer/config/default.toml +++ b/src/app/firedancer/config/default.toml @@ -533,6 +533,17 @@ user = "" # max_fork_width, the client will crash. max_fork_width = 32 + # The program cache pre-loads frequently executed programs for + # faster transaction execution. + [runtime.program_cache] + # The size of the loaded program cache in MiB. + heap_size_mib = 1024 + + # The mean expected heap utilization of a cache entry. Controls + # the size of metadata structures (e.g. cache entry table). It + # is not recommended to change this setting. + mean_cache_entry_size_kib = 131072 + # This section configures the "groove" persistent account database. # [groove] # ... diff --git a/src/app/firedancer/topology.c b/src/app/firedancer/topology.c index 6c3b8864de9..466e91f4032 100644 --- a/src/app/firedancer/topology.c +++ b/src/app/firedancer/topology.c @@ -97,6 +97,35 @@ setup_topo_funk( fd_topo_t * topo, return obj; } +fd_topo_obj_t * +setup_topo_progcache( fd_topo_t * topo, + char const * wksp_name, + ulong max_cache_entries, + ulong max_database_transactions, + ulong heap_size ) { + fd_topo_obj_t * obj = fd_topob_obj( topo, "funk", wksp_name ); + FD_TEST( fd_pod_insert_ulong( topo->props, "progcache", obj->id ) ); + FD_TEST( fd_pod_insertf_ulong( topo->props, max_cache_entries, "obj.%lu.rec_max", obj->id ) ); + FD_TEST( fd_pod_insertf_ulong( topo->props, max_database_transactions, "obj.%lu.txn_max", obj->id ) ); + FD_TEST( fd_pod_insertf_ulong( topo->props, heap_size, "obj.%lu.heap_max", obj->id ) ); + ulong funk_footprint = fd_funk_footprint( max_database_transactions, max_cache_entries ); + if( FD_UNLIKELY( !funk_footprint ) ) FD_LOG_ERR(( "Invalid [runtime.program_cache] parameters" )); + if( FD_UNLIKELY( heap_size<(2*funk_footprint) ) ) { + FD_LOG_ERR(( "Invalid [runtime.program_cache] parameters: heap_size_mib should be at least %lu", + ( 4*funk_footprint )>>20 )); + } + + /* Increase workspace partition count */ + ulong wksp_idx = fd_topo_find_wksp( topo, wksp_name ); + FD_TEST( wksp_idx!=ULONG_MAX ); + fd_topo_wksp_t * wksp = &topo->workspaces[ wksp_idx ]; + ulong part_max = fd_wksp_part_max_est( heap_size, 1U<<14U ); + if( FD_UNLIKELY( !part_max ) ) FD_LOG_ERR(( "fd_wksp_part_max_est(%lu,16KiB) failed", funk_footprint )); + wksp->part_max += part_max; + + return obj; +} + fd_topo_obj_t * setup_topo_store( fd_topo_t * topo, char const * wksp_name, @@ -275,8 +304,8 @@ fd_topo_initialize( config_t * config ) { fd_topob_wksp( topo, "poh_shred" ); fd_topob_wksp( topo, "poh_replay" ); - /* TODO: WTF are these for? */ fd_topob_wksp( topo, "funk" ); + fd_topob_wksp( topo, "progcache" ); fd_topob_wksp( topo, "bh_cmp" ); fd_topob_wksp( topo, "fec_sets" ); fd_topob_wksp( topo, "txncache" ); @@ -720,6 +749,15 @@ fd_topo_initialize( config_t * config ) { FOR(resolv_tile_cnt) fd_topob_tile_uses( topo, &topo->tiles[ fd_topo_find_tile( topo, "resolv", i ) ], banks_obj, FD_SHMEM_JOIN_MODE_READ_ONLY ); FD_TEST( fd_pod_insertf_ulong( topo->props, banks_obj->id, "banks" ) ); + fd_topo_obj_t * progcache_obj = setup_topo_progcache( topo, "progcache", + fd_progcache_est_rec_max( config->firedancer.runtime.program_cache.heap_size_mib<<20, + config->firedancer.runtime.program_cache.mean_cache_entry_size_kib<<10 ), + config->firedancer.funk.max_database_transactions, + config->firedancer.runtime.program_cache.heap_size_mib<<20 ); + /**/ fd_topob_tile_uses( topo, &topo->tiles[ fd_topo_find_tile( topo, "replay", 0UL ) ], progcache_obj, FD_SHMEM_JOIN_MODE_READ_WRITE ); + FOR(exec_tile_cnt) fd_topob_tile_uses( topo, &topo->tiles[ fd_topo_find_tile( topo, "exec", i ) ], progcache_obj, FD_SHMEM_JOIN_MODE_READ_WRITE ); + FOR(bank_tile_cnt) fd_topob_tile_uses( topo, &topo->tiles[ fd_topo_find_tile( topo, "bank", i ) ], progcache_obj, FD_SHMEM_JOIN_MODE_READ_ONLY ); + /* TODO: This should not exist in production */ fd_topo_obj_t * bank_hash_cmp_obj = setup_topo_bank_hash_cmp( topo, "bh_cmp" ); /**/ fd_topob_tile_uses( topo, &topo->tiles[ fd_topo_find_tile( topo, "replay", 0UL ) ], bank_hash_cmp_obj, FD_SHMEM_JOIN_MODE_READ_WRITE ); @@ -913,8 +951,9 @@ fd_topo_configure_tile( fd_topo_tile_t * tile, tile->replay.tx_metadata_storage = config->rpc.extended_tx_metadata_storage; - tile->replay.txncache_obj_id = fd_pod_query_ulong( config->topo.props, "txncache", ULONG_MAX ); - tile->replay.funk_obj_id = fd_pod_query_ulong( config->topo.props, "funk", ULONG_MAX ); + tile->replay.txncache_obj_id = fd_pod_query_ulong( config->topo.props, "txncache", ULONG_MAX ); FD_TEST( tile->replay.txncache_obj_id !=ULONG_MAX ); + tile->replay.funk_obj_id = fd_pod_query_ulong( config->topo.props, "funk", ULONG_MAX ); FD_TEST( tile->replay.funk_obj_id !=ULONG_MAX ); + tile->replay.progcache_obj_id = fd_pod_query_ulong( config->topo.props, "progcache", ULONG_MAX ); FD_TEST( tile->replay.progcache_obj_id!=ULONG_MAX ); strncpy( tile->replay.cluster_version, config->tiles.replay.cluster_version, sizeof(tile->replay.cluster_version) ); @@ -937,8 +976,9 @@ fd_topo_configure_tile( fd_topo_tile_t * tile, } else if( FD_UNLIKELY( !strcmp( tile->name, "exec" ) ) ) { - tile->exec.funk_obj_id = fd_pod_query_ulong( config->topo.props, "funk", ULONG_MAX ); - tile->exec.txncache_obj_id = fd_pod_query_ulong( config->topo.props, "txncache", ULONG_MAX ); + tile->exec.funk_obj_id = fd_pod_query_ulong( config->topo.props, "funk", ULONG_MAX ); FD_TEST( tile->exec.funk_obj_id !=ULONG_MAX ); + tile->exec.txncache_obj_id = fd_pod_query_ulong( config->topo.props, "txncache", ULONG_MAX ); FD_TEST( tile->exec.txncache_obj_id !=ULONG_MAX ); + tile->exec.progcache_obj_id = fd_pod_query_ulong( config->topo.props, "progcache", ULONG_MAX ); FD_TEST( tile->exec.progcache_obj_id!=ULONG_MAX ); tile->exec.max_live_slots = config->firedancer.runtime.max_live_slots; @@ -1011,8 +1051,9 @@ fd_topo_configure_tile( fd_topo_tile_t * tile, } } else if( FD_UNLIKELY( !strcmp( tile->name, "bank" ) ) ) { - tile->bank.txncache_obj_id = fd_pod_query_ulong( config->topo.props, "txncache", ULONG_MAX ); - tile->bank.funk_obj_id = fd_pod_query_ulong( config->topo.props, "funk", ULONG_MAX ); + tile->bank.txncache_obj_id = fd_pod_query_ulong( config->topo.props, "txncache", ULONG_MAX ); + tile->bank.funk_obj_id = fd_pod_query_ulong( config->topo.props, "funk", ULONG_MAX ); + tile->bank.progcache_obj_id = fd_pod_query_ulong( config->topo.props, "progcache", ULONG_MAX ); tile->bank.max_live_slots = config->firedancer.runtime.max_live_slots; diff --git a/src/app/firedancer/topology.h b/src/app/firedancer/topology.h index e108973f0a5..7b912dd5ec1 100644 --- a/src/app/firedancer/topology.h +++ b/src/app/firedancer/topology.h @@ -30,6 +30,13 @@ setup_topo_funk( fd_topo_t * topo, ulong heap_size_gib, int lock_pages ); +fd_topo_obj_t * +setup_topo_progcache( fd_topo_t * topo, + char const * wksp_name, + ulong max_cache_entries, + ulong max_database_transactions, + ulong heap_size_gib ); + fd_topo_obj_t * setup_topo_runtime_pub( fd_topo_t * topo, char const * wksp_name, diff --git a/src/app/shared/fd_config.h b/src/app/shared/fd_config.h index f5011c9fd74..2a9f1b38ec7 100644 --- a/src/app/shared/fd_config.h +++ b/src/app/shared/fd_config.h @@ -112,6 +112,11 @@ struct fd_configf { ulong max_live_slots; ulong max_vote_accounts; ulong max_fork_width; + + struct { + ulong heap_size_mib; + ulong mean_cache_entry_size_kib; + } program_cache; } runtime; struct { diff --git a/src/app/shared/fd_config_parse.c b/src/app/shared/fd_config_parse.c index 04c94388f70..9ae3bbf99a4 100644 --- a/src/app/shared/fd_config_parse.c +++ b/src/app/shared/fd_config_parse.c @@ -91,6 +91,9 @@ fd_config_extract_podf( uchar * pod, CFG_POP ( ulong, runtime.max_vote_accounts ); CFG_POP ( ulong, runtime.max_fork_width ); + CFG_POP ( ulong, runtime.program_cache.heap_size_mib ); + CFG_POP ( ulong, runtime.program_cache.mean_cache_entry_size_kib ); + CFG_POP ( ulong, store.max_completed_shred_sets ); CFG_POP ( bool, snapshots.incremental_snapshots ); diff --git a/src/ballet/sbpf/fixtures/malformed_bytecode.so b/src/ballet/sbpf/fixtures/malformed_bytecode.so new file mode 100644 index 00000000000..c359c0887bc Binary files /dev/null and b/src/ballet/sbpf/fixtures/malformed_bytecode.so differ diff --git a/src/disco/topo/fd_topo.c b/src/disco/topo/fd_topo.c index b5705257cd7..20164a19856 100644 --- a/src/disco/topo/fd_topo.c +++ b/src/disco/topo/fd_topo.c @@ -10,6 +10,17 @@ #include #include +void * +fd_topo_obj_laddr( fd_topo_t const * topo, + ulong obj_id ) { + fd_topo_obj_t const * obj = &topo->objs[ obj_id ]; + if( FD_UNLIKELY( obj_id==ULONG_MAX ) ) FD_LOG_CRIT(( "invalid obj_id ULONG_MAX" )); + if( FD_UNLIKELY( obj_id>=FD_TOPO_MAX_OBJS ) ) FD_LOG_CRIT(( "invalid obj_id %lu", obj_id )); + FD_TEST( obj->id == obj_id ); + FD_TEST( obj->offset ); + return (void *)((ulong)topo->workspaces[ obj->wksp_id ].wksp + obj->offset); +} + void fd_topo_join_workspace( fd_topo_t * topo, fd_topo_wksp_t * wksp, diff --git a/src/disco/topo/fd_topo.h b/src/disco/topo/fd_topo.h index 052598fd46e..dbe733af13e 100644 --- a/src/disco/topo/fd_topo.h +++ b/src/disco/topo/fd_topo.h @@ -353,6 +353,7 @@ struct fd_topo_tile { int tx_metadata_storage; ulong funk_obj_id; ulong txncache_obj_id; + ulong progcache_obj_id; char shred_cap[ PATH_MAX ]; char cluster_version[ 32 ]; @@ -381,6 +382,7 @@ struct fd_topo_tile { struct { ulong funk_obj_id; ulong txncache_obj_id; + ulong progcache_obj_id; ulong max_live_slots; @@ -543,6 +545,7 @@ struct fd_topo_tile { ulong txncache_obj_id; ulong funk_obj_id; + ulong progcache_obj_id; } bank; struct { @@ -643,15 +646,9 @@ fd_topo_workspace_align( void ) { return 4096UL; } -static inline void * +void * fd_topo_obj_laddr( fd_topo_t const * topo, - ulong obj_id ) { - fd_topo_obj_t const * obj = &topo->objs[ obj_id ]; - FD_TEST( obj_idid == obj_id ); - FD_TEST( obj->offset ); - return (void *)((ulong)topo->workspaces[ obj->wksp_id ].wksp + obj->offset); -} + ulong obj_id ); /* Returns a pointer in the local address space to the base address of the workspace out of which the given object was allocated. */ diff --git a/src/discof/bank/fd_bank_tile.c b/src/discof/bank/fd_bank_tile.c index edf5336d5a7..1bebc6588b3 100644 --- a/src/discof/bank/fd_bank_tile.c +++ b/src/discof/bank/fd_bank_tile.c @@ -242,7 +242,7 @@ handle_microblock( fd_bank_ctx_t * ctx, if that happens. We cannot reject the transaction here as there would be no way to undo the partially applied changes to the bank in finalize anyway. */ - fd_runtime_finalize_txn( ctx->txn_ctx->funk, txn_ctx->status_cache, txn_ctx->xid, txn_ctx, bank, NULL ); + fd_runtime_finalize_txn( ctx->txn_ctx->funk, ctx->txn_ctx->progcache, txn_ctx->status_cache, txn_ctx->xid, txn_ctx, bank, NULL ); FD_TEST( txn->flags ); } diff --git a/src/discof/exec/fd_exec_tile.c b/src/discof/exec/fd_exec_tile.c index fd5efb02e32..83658792472 100644 --- a/src/discof/exec/fd_exec_tile.c +++ b/src/discof/exec/fd_exec_tile.c @@ -61,7 +61,10 @@ typedef struct fd_exec_tile_ctx { fd_funk_t. TODO: These should probably be made read-only handles. */ fd_banks_t * banks; - fd_funk_t funk[ 1 ]; + void * shfunk; + void * shprogcache; + fd_funk_t funk[1]; + fd_progcache_t progcache[1]; fd_txncache_t * txncache; @@ -174,6 +177,7 @@ after_frag( fd_exec_tile_ctx_t * ctx, if( FD_LIKELY( ctx->txn_ctx->flags & FD_TXN_P_FLAGS_EXECUTE_SUCCESS ) ) { fd_runtime_finalize_txn( ctx->funk, + ctx->progcache, ctx->txncache, &xid, ctx->txn_ctx, @@ -309,15 +313,15 @@ unprivileged_init( fd_topo_t * topo, FD_LOG_ERR(( "Failed to join bank hash cmp" )); } - /********************************************************************/ - /* funk-specific setup */ - /********************************************************************/ - - FD_TEST( fd_funk_join( ctx->funk, fd_topo_obj_laddr( topo, tile->exec.funk_obj_id ) ) ); + void * shfunk = fd_topo_obj_laddr( topo, tile->exec.funk_obj_id ); + if( FD_UNLIKELY( !fd_funk_join( ctx->funk, shfunk ) ) ) { + FD_LOG_CRIT(( "fd_funk_join(accdb) failed" )); + } - /********************************************************************/ - /* setup txncache */ - /********************************************************************/ + void * shprogcache = fd_topo_obj_laddr( topo, tile->exec.progcache_obj_id ); + if( FD_UNLIKELY( !fd_progcache_join( ctx->progcache, shprogcache ) ) ) { + FD_LOG_CRIT(( "fd_progcache_join() failed" )); + } void * _txncache_shmem = fd_topo_obj_laddr( topo, tile->exec.txncache_obj_id ); fd_txncache_shmem_t * txncache_shmem = fd_txncache_shmem_join( _txncache_shmem ); @@ -332,7 +336,13 @@ unprivileged_init( fd_topo_t * topo, fd_spad_push( ctx->exec_spad ); uchar * txn_ctx_mem = fd_spad_alloc_check( ctx->exec_spad, FD_EXEC_TXN_CTX_ALIGN, FD_EXEC_TXN_CTX_FOOTPRINT ); ctx->txn_ctx = fd_exec_txn_ctx_join( fd_exec_txn_ctx_new( txn_ctx_mem ), ctx->exec_spad, ctx->exec_spad_wksp ); - ctx->txn_ctx->funk[0] = *ctx->funk; + if( FD_UNLIKELY( !fd_funk_join( ctx->txn_ctx->funk, shfunk ) ) ) { + FD_LOG_CRIT(( "fd_funk_join(accdb) failed" )); + } + ctx->txn_ctx->progcache = fd_progcache_join( ctx->txn_ctx->_progcache, shprogcache ); + if( FD_UNLIKELY( !ctx->txn_ctx->progcache ) ) { + FD_LOG_CRIT(( "fd_progcache_join() failed" )); + } ctx->txn_ctx->status_cache = ctx->txncache; ctx->txn_ctx->bank_hash_cmp = ctx->bank_hash_cmp; ctx->txn_ctx->spad = ctx->exec_spad; diff --git a/src/discof/replay/fd_replay_tile.c b/src/discof/replay/fd_replay_tile.c index d6d8cfb00ea..1506399ce2c 100644 --- a/src/discof/replay/fd_replay_tile.c +++ b/src/discof/replay/fd_replay_tile.c @@ -127,6 +127,7 @@ struct fd_replay_tile { int tx_metadata_storage; fd_funk_t funk[1]; + fd_progcache_t progcache[1]; fd_txncache_t * txncache; fd_store_t * store; @@ -612,7 +613,8 @@ replay_block_start( fd_replay_tile_t * ctx, fd_funk_txn_xid_t xid = { .ul = { slot, slot } }; fd_funk_txn_xid_t parent_xid = { .ul = { parent_slot, parent_slot } }; - fd_funk_txn_prepare( ctx->funk, &parent_xid, &xid ); + fd_funk_txn_prepare( ctx->funk, &parent_xid, &xid ); + fd_funk_txn_prepare( ctx->progcache->funk, &parent_xid, &xid ); /* Update any required runtime state and handle any potential epoch boundary change. */ @@ -821,7 +823,8 @@ prepare_leader_bank( fd_replay_tile_t * ctx, /* prepare the funk transaction for the leader bank */ fd_funk_txn_xid_t xid = { .ul = { slot, slot } }; fd_funk_txn_xid_t parent_xid = { .ul = { parent_slot, parent_slot } }; - fd_funk_txn_prepare( ctx->funk, &parent_xid, &xid ); + fd_funk_txn_prepare( ctx->funk, &parent_xid, &xid ); + fd_funk_txn_prepare( ctx->progcache->funk, &parent_xid, &xid ); fd_bank_execution_fees_set( ctx->leader_bank, 0UL ); fd_bank_priority_fees_set( ctx->leader_bank, 0UL ); @@ -945,6 +948,43 @@ publish_root_advanced( fd_replay_tile_t * ctx, ctx->replay_out->chunk = fd_dcache_compact_next( ctx->replay_out->chunk, sizeof(fd_replay_root_advanced_t), ctx->replay_out->chunk0, ctx->replay_out->wmark ); } +/* init_funk performs pre-flight checks for the account database and + program cache. Ensures that the account database was set up + correctly by bootstrap components (e.g. genesis or snapshot loader). + Mirrors the account database's fork tree down to the program cache. */ + +static void +init_funk( fd_replay_tile_t * ctx, + ulong bank_slot ) { + /* Ensure that the loaded bank root corresponds to the account + database's root. */ + if( FD_UNLIKELY( !ctx->funk->shmem ) ) { + FD_LOG_CRIT(( "failed to initialize account database: replay tile is not joined to database shared memory objects" )); + } + fd_funk_txn_xid_t const * accdb_pub = fd_funk_last_publish( ctx->funk ); + if( FD_UNLIKELY( accdb_pub->ul[0]!=bank_slot ) ) { + FD_LOG_CRIT(( "failed to initialize account database: accdb is at slot %lu, but chain state is at slot %lu\n" + "This is a bug in startup components.", + accdb_pub->ul[0], bank_slot )); + } + if( FD_UNLIKELY( fd_funk_last_publish_is_frozen( ctx->funk ) ) ) { + FD_LOG_CRIT(( "failed to initialize account database: accdb fork graph is not clean.\n" + "The account database should only contain state for the root slot at this point,\n" + "but there are incomplete database transactions leftover.\n" + "This is a bug in startup components." )); + } + + /* The program cache tracks the account database's fork graph at all + times. Perform initial synchronization: pivot from funk 'root' (a + sentinel XID) to 'last publish' (the bootstrap root slot). */ + if( FD_UNLIKELY( !ctx->progcache->funk->shmem ) ) { + FD_LOG_CRIT(( "failed to initialize account database: replay tile is not joined to program cache" )); + } + fd_progcache_clear( ctx->progcache ); + fd_funk_txn_prepare( ctx->progcache->funk, fd_funk_root( ctx->progcache->funk ), fd_funk_last_publish( ctx->funk ) ); + fd_funk_txn_publish( ctx->progcache->funk, fd_funk_last_publish( ctx->funk ) ); +} + static void init_after_snapshot( fd_replay_tile_t * ctx ) { /* Now that the snapshot has been loaded in, we have to refresh the @@ -957,9 +997,10 @@ init_after_snapshot( fd_replay_tile_t * ctx ) { FD_LOG_CRIT(( "invariant violation: replay bank is NULL at bank index %lu", FD_REPLAY_BOOT_BANK_IDX )); } - fd_stake_delegations_t * root_delegations = fd_banks_stake_delegations_root_query( ctx->banks ); - fd_funk_txn_xid_t xid = { .ul = { fd_bank_slot_get( bank ), fd_bank_slot_get( bank ) } }; + init_funk( ctx, fd_bank_slot_get( bank ) ); + + fd_stake_delegations_t * root_delegations = fd_banks_stake_delegations_root_query( ctx->banks ); fd_stake_delegations_refresh( root_delegations, ctx->funk, &xid ); @@ -1409,7 +1450,7 @@ replay( fd_replay_tile_t * ctx, fd_funk_txn_xid_t xid = { .ul = { ready_txn->slot, ready_txn->slot } }; - fd_runtime_update_program_cache( bank, ctx->funk, &xid, txn_p, ctx->runtime_spad ); + fd_runtime_update_program_cache( bank, ctx->progcache, ctx->funk, &xid, txn_p, ctx->runtime_spad ); /* At this point, we are going to send the txn down the execution pipeline. Increment the refcnt so we don't prematurely prune a @@ -1561,7 +1602,8 @@ funk_publish( fd_replay_tile_t * ctx, /* This is the standard case. Publish all transactions up to and including the watermark. This will publish any in-prep ancestors of root_txn as well. */ - if( FD_UNLIKELY( !fd_funk_txn_publish( ctx->funk, &xid ) ) ) FD_LOG_CRIT(( "failed to funk publish slot %lu", slot )); + if( FD_UNLIKELY( !fd_funk_txn_publish( ctx->funk, &xid ) ) ) FD_LOG_CRIT(( "failed to root slot %lu: fd_funk_txn_publish(accdb) failed", slot )); + if( FD_UNLIKELY( !fd_funk_txn_publish( ctx->progcache->funk, &xid ) ) ) FD_LOG_CRIT(( "failed to root slot %lu: fd_funk_txn_publish(progcache) failed", slot )); } static int @@ -2010,6 +2052,7 @@ unprivileged_init( fd_topo_t * topo, fd_features_enable_one_offs( features, one_off_features, (uint)tile->replay.enable_features_cnt, 0UL ); FD_TEST( fd_funk_join( ctx->funk, fd_topo_obj_laddr( topo, tile->replay.funk_obj_id ) ) ); + FD_TEST( fd_progcache_join( ctx->progcache, fd_topo_obj_laddr( topo, tile->replay.progcache_obj_id ) ) ); void * _txncache_shmem = fd_topo_obj_laddr( topo, tile->replay.txncache_obj_id ); fd_txncache_shmem_t * txncache_shmem = fd_txncache_shmem_join( _txncache_shmem ); diff --git a/src/flamenco/fd_flamenco_base.h b/src/flamenco/fd_flamenco_base.h index 1b27f18474e..2af1f054358 100644 --- a/src/flamenco/fd_flamenco_base.h +++ b/src/flamenco/fd_flamenco_base.h @@ -9,9 +9,6 @@ #define FD_SLOT_NULL ( ULONG_MAX ) #define FD_SHRED_IDX_NULL ( UINT_MAX ) -#define FD_FUNK_KEY_TYPE_ACC ((uchar)1) -#define FD_FUNK_KEY_TYPE_ELF_CACHE ((uchar)2) - /* CLUSTER_VERSION is the default value for the cluster version in the epoch context. This value will foll forward to the latest version. @@ -96,6 +93,12 @@ typedef struct fd_borrowed_account fd_borrowed_account_t; struct fd_txn_account; typedef struct fd_txn_account fd_txn_account_t; +union fd_features; +typedef union fd_features fd_features_t; + +struct fd_progcache; +typedef struct fd_progcache fd_progcache_t; + struct fd_account_meta { uchar owner[32]; ulong lamports; diff --git a/src/flamenco/progcache/Local.mk b/src/flamenco/progcache/Local.mk new file mode 100644 index 00000000000..7d04a09db27 --- /dev/null +++ b/src/flamenco/progcache/Local.mk @@ -0,0 +1,8 @@ +ifdef FD_HAS_INT128 +$(call add-hdrs,fd_prog_load.h) +$(call add-objs,fd_prog_load,fd_flamenco) +$(call add-hdrs,fd_progcache.h fd_progcache_rec.h) +$(call add-objs,fd_progcache fd_progcache_rec fd_progcache_evict,fd_flamenco) +$(call make-unit-test,test_progcache,test_progcache,fd_flamenco fd_funk fd_ballet fd_util) +$(call run-unit-test,test_progcache) +endif diff --git a/src/flamenco/progcache/fd_prog_load.c b/src/flamenco/progcache/fd_prog_load.c new file mode 100644 index 00000000000..43ba21f6bf1 --- /dev/null +++ b/src/flamenco/progcache/fd_prog_load.c @@ -0,0 +1,169 @@ +#include "fd_prog_load.h" +#include "../runtime/program/fd_bpf_loader_program.h" +#include "../runtime/program/fd_loader_v4_program.h" + +/* Similar to the below function, but gets the executable program content for the v4 loader. + Unlike the v3 loader, the programdata is stored in a single program account. The program must + NOT be retracted to be added to the cache. Returns a pointer to the programdata on success, + and NULL on failure. + + Reasons for failure include: + - The program state cannot be read from the account data or is in the `retracted` state. */ +static uchar const * +fd_get_executable_program_content_for_v4_loader( fd_txn_account_t const * program_acc, + ulong * program_data_len ) { + int err; + + /* Get the current loader v4 state. This implicitly also checks the dlen. */ + fd_loader_v4_state_t const * state = fd_loader_v4_get_state( program_acc, &err ); + if( FD_UNLIKELY( err ) ) { + return NULL; + } + + /* The program must be deployed or finalized. */ + if( FD_UNLIKELY( fd_loader_v4_status_is_retracted( state ) ) ) { + return NULL; + } + + /* This subtraction is safe because get_state() implicitly checks the + dlen. */ + *program_data_len = fd_txn_account_get_data_len( program_acc )-LOADER_V4_PROGRAM_DATA_OFFSET; + return fd_txn_account_get_data( program_acc )+LOADER_V4_PROGRAM_DATA_OFFSET; +} + +/* Gets the programdata for a v3 loader-owned account by decoding the account data + as well as the programdata account. Returns a pointer to the programdata on success, + and NULL on failure. + + Reasons for failure include: + - The program account data cannot be decoded or is not in the `program` state. + - The programdata account is not large enough to hold at least `PROGRAMDATA_METADATA_SIZE` bytes. */ +static uchar const * +fd_get_executable_program_content_for_upgradeable_loader( fd_funk_t const * funk, + fd_funk_txn_xid_t const * xid, + fd_txn_account_t const * program_acc, + ulong * program_data_len, + fd_funk_txn_xid_t * out_xid ) { + fd_bpf_upgradeable_loader_state_t program_account_state[1]; + if( FD_UNLIKELY( !fd_bincode_decode_static( + bpf_upgradeable_loader_state, + program_account_state, + fd_txn_account_get_data( program_acc ), + fd_txn_account_get_data_len( program_acc ), + NULL ) ) ) { + return NULL; + } + if( !fd_bpf_upgradeable_loader_state_is_program( program_account_state ) ) { + return NULL; + } + + fd_pubkey_t * programdata_address = &program_account_state->inner.program.programdata_address; + + fd_account_meta_t const * meta = fd_funk_get_acc_meta_readonly( + funk, xid, programdata_address, NULL, NULL, out_xid ); + if( FD_UNLIKELY( !meta ) ) return NULL; + fd_txn_account_t _rec[1]; + fd_txn_account_t * programdata_acc = fd_txn_account_join( fd_txn_account_new( _rec, programdata_address, (void *)meta, 0 ), funk->wksp ); + if( FD_UNLIKELY( !programdata_acc ) ) FD_LOG_CRIT(( "fd_txn_account_new failed" )); + + /* We don't actually need to decode here, just make sure that the account + can be decoded successfully. */ + fd_bincode_decode_ctx_t ctx_programdata = { + .data = fd_txn_account_get_data( programdata_acc ), + .dataend = fd_txn_account_get_data( programdata_acc ) + fd_txn_account_get_data_len( programdata_acc ), + }; + + ulong total_sz = 0UL; + if( FD_UNLIKELY( fd_bpf_upgradeable_loader_state_decode_footprint( &ctx_programdata, &total_sz ) ) ) { + return NULL; + } + + if( FD_UNLIKELY( fd_txn_account_get_data_len( programdata_acc )wksp ); + if( FD_UNLIKELY( !rec ) ) FD_LOG_CRIT(( "fd_txn_account_new failed" )); + + /* v1/v2 loaders: Programdata is just the account data. + v3 loader: Programdata lives in a separate account. Deserialize the + program account and lookup the programdata account. + Deserialize the programdata account. + v4 loader: Programdata lives in the program account, offset by + LOADER_V4_PROGRAM_DATA_OFFSET. */ + fd_pubkey_t const * owner = fd_txn_account_get_owner( rec ); + uchar const * elf = NULL; + fd_funk_txn_xid_t sub_xid = { .ul={ 0UL, 0UL } }; + if( !memcmp( owner, fd_solana_bpf_loader_upgradeable_program_id.key, sizeof(fd_pubkey_t) ) ) { + elf = fd_get_executable_program_content_for_upgradeable_loader( accdb, xid, rec, out_sz, &sub_xid ); + } else if( !memcmp( owner, fd_solana_bpf_loader_v4_program_id.key, sizeof(fd_pubkey_t) ) ) { + elf = fd_get_executable_program_content_for_v4_loader( rec, out_sz ); + } else if( !memcmp( owner, fd_solana_bpf_loader_program_id.key, sizeof(fd_pubkey_t) ) || + !memcmp( owner, fd_solana_bpf_loader_deprecated_program_id.key, sizeof(fd_pubkey_t) ) ) { + elf = fd_get_executable_program_content_for_v1_v2_loaders( rec, out_sz ); + } + + if( FD_LIKELY( elf ) ) { + /* Return the XID with the last modification slow (unclear if they can differ) */ + *out_xid = sub_xid.ul[0]>out_xid->ul[0] ? sub_xid : *out_xid; + } else { + fd_funk_txn_xid_set_root( out_xid ); + } + + return elf; +} + +fd_prog_versions_t +fd_prog_versions( fd_features_t const * features, + ulong slot ) { + int disable_v0 = FD_FEATURE_ACTIVE( slot, features, disable_sbpf_v0_execution ); + int reenable_v0 = FD_FEATURE_ACTIVE( slot, features, reenable_sbpf_v0_execution ); + int enable_v0 = !disable_v0 || reenable_v0; + int enable_v1 = FD_FEATURE_ACTIVE( slot, features, enable_sbpf_v1_deployment_and_execution ); + int enable_v2 = FD_FEATURE_ACTIVE( slot, features, enable_sbpf_v2_deployment_and_execution ); + int enable_v3 = FD_FEATURE_ACTIVE( slot, features, enable_sbpf_v3_deployment_and_execution ); + + fd_prog_versions_t v = {0}; + v.min_sbpf_version = enable_v0 ? FD_SBPF_V0 : FD_SBPF_V3; + if( enable_v3 ) { + v.max_sbpf_version = FD_SBPF_V3; + } else if( enable_v2 ) { + v.max_sbpf_version = FD_SBPF_V2; + } else if( enable_v1 ) { + v.max_sbpf_version = FD_SBPF_V1; + } else { + v.max_sbpf_version = FD_SBPF_V0; + } + return v; +} diff --git a/src/flamenco/progcache/fd_prog_load.h b/src/flamenco/progcache/fd_prog_load.h new file mode 100644 index 00000000000..3a2c7533863 --- /dev/null +++ b/src/flamenco/progcache/fd_prog_load.h @@ -0,0 +1,45 @@ +#ifndef HEADER_fd_src_flamenco_fd_progcache_fd_prog_load_h +#define HEADER_fd_src_flamenco_fd_progcache_fd_prog_load_h + +/* fd_prog_load.h provides high-level APIs for loading Solana programs + from the account database. */ + +#include "../../funk/fd_funk_rec.h" +#include "../fd_flamenco_base.h" + +FD_PROTOTYPES_BEGIN + +/* fd_prog_load_elf loads a reference to program data from a funk-backed + account database. *prog_addr gives the account address of the + program account (NOT the program data account). Returns a pointer to + the first byte of the ELF binary on success. *out_sz is set to the + size of the program data account. *out_xid is set to the the txn + XID the program data account was written to. On failure, returns + NULL. Reasons for failure include: program account not found, + program not deployed, program data account not found, etc */ + +uchar const * +fd_prog_load_elf( fd_funk_t const * accdb, + fd_funk_txn_xid_t const * xid, + void const * prog_addr, + ulong * out_sz, + fd_funk_txn_xid_t * out_xid ); +/* FIXME provide an API to detect data race */ +/* FIXME clarify edge case where program account and program data + account were modified in different funk txns */ + +/* fd_prog_versions derives sBPF versions from the current feature set. */ + +struct fd_prog_versions { + uint min_sbpf_version; + uint max_sbpf_version; +}; +typedef struct fd_prog_versions fd_prog_versions_t; + +fd_prog_versions_t +fd_prog_versions( fd_features_t const * features, + ulong slot ); + +FD_PROTOTYPES_END + +#endif /* HEADER_fd_src_flamenco_fd_progcache_fd_prog_load_h */ diff --git a/src/flamenco/progcache/fd_progcache.c b/src/flamenco/progcache/fd_progcache.c new file mode 100644 index 00000000000..515e471c4ba --- /dev/null +++ b/src/flamenco/progcache/fd_progcache.c @@ -0,0 +1,314 @@ +#include "fd_progcache.h" +#include "fd_progcache_evict.h" +#include "fd_prog_load.h" +#include "../runtime/fd_bank.h" +#include "../runtime/sysvar/fd_sysvar_epoch_schedule.h" +#include "fd_progcache_rec.h" + +FD_TL fd_progcache_metrics_t fd_progcache_metrics_default; +FD_TL fd_progcache_metrics_t * fd_progcache_metrics_cur; + +__attribute__((constructor)) static void +init_progcache( void ) { + fd_progcache_metrics_cur = &fd_progcache_metrics_default; +} + +/* Algorithm to estimate size of cache metadata structures (rec_pool + object pool and rec_map hashchain table). + + FIXME Carefully balance this */ + +static ulong +fd_progcache_est_rec_max1( ulong wksp_footprint, + ulong mean_cache_entry_size ) { + return wksp_footprint / mean_cache_entry_size; +} + +ulong +fd_progcache_est_rec_max( ulong wksp_footprint, + ulong mean_cache_entry_size ) { + ulong est = fd_progcache_est_rec_max1( wksp_footprint, mean_cache_entry_size ); + if( FD_UNLIKELY( est>(1UL<<31) ) ) FD_LOG_ERR(( "fd_progcache_est_rec_max(wksp_footprint=%lu,mean_cache_entry_size=%lu) failed: invalid parameters", wksp_footprint, mean_cache_entry_size )); + return fd_ulong_max( est, 2048UL ); +} + +fd_progcache_t * +fd_progcache_join( fd_progcache_t * ljoin, + void * shfunk ) { + if( FD_UNLIKELY( !ljoin ) ) { + FD_LOG_WARNING(( "NULL ljoin" )); + return NULL; + } + if( FD_UNLIKELY( !shfunk ) ) { + FD_LOG_WARNING(( "NULL shfunk" )); + return NULL; + } + + memset( ljoin, 0, sizeof(fd_progcache_t) ); + if( FD_UNLIKELY( !fd_funk_join( ljoin->funk, shfunk ) ) ) return NULL; + return ljoin; +} + +void * +fd_progcache_leave( fd_progcache_t * cache, + void ** opt_shfunk ) { + if( FD_UNLIKELY( !cache ) ) { + FD_LOG_WARNING(( "NULL cache" )); + return NULL; + } + if( FD_UNLIKELY( !fd_funk_leave( cache->funk, opt_shfunk ) ) ) return NULL; + return cache; +} + +fd_progcache_rec_t const * +fd_progcache_peek( fd_progcache_t const * cache, + fd_funk_txn_xid_t const * xid, + void const * prog_addr ) { + if( FD_UNLIKELY( !cache || !cache->funk->shmem ) ) FD_LOG_CRIT(( "NULL progcache" )); + fd_funk_rec_key_t key[1]; memcpy( key->uc, prog_addr, 32UL ); + fd_funk_rec_query_t query[1]; + fd_funk_rec_t const * rec = fd_funk_rec_query_try_global( cache->funk, xid, key, NULL, query ); + if( FD_UNLIKELY( !rec ) ) return NULL; + return fd_funk_val_const( rec, fd_funk_wksp( cache->funk ) ); +} + +static void +fd_progcache_publish_prepare( fd_funk_rec_prepare_t * prepare, + fd_funk_rec_t * rec, + fd_funk_txn_map_query_t * txn_query, + fd_funk_t const * funk, + fd_funk_txn_xid_t const * xid ) { + prepare->rec = rec; + int query_err = fd_funk_txn_map_query_try( funk->txn_map, xid, NULL, txn_query, 0 ); + if( query_err==FD_MAP_ERR_KEY ) { + if( FD_UNLIKELY( !fd_funk_txn_xid_eq( xid, fd_funk_last_publish( funk ) ) ) ) { + FD_LOG_CRIT(( "fd_funk_txn_map_query_try failed: xid %lu:%lu not found", xid->ul[0], xid->ul[1] )); + } + } else if( query_err==FD_MAP_SUCCESS ) { + fd_funk_txn_t * txn = fd_funk_txn_map_query_ele( txn_query ); + prepare->rec_head_idx = &txn->rec_head_idx; + prepare->rec_tail_idx = &txn->rec_tail_idx; + } else { + FD_LOG_CRIT(( "fd_funk_txn_map_query_try failed (%i-%s)", query_err, fd_map_strerror( query_err ) )); + } +} + +static void +fd_progcache_publish( fd_progcache_t * cache, + fd_funk_txn_xid_t const * xid, + fd_funk_rec_t * rec, + void const * prog_addr ) { + fd_funk_t * funk = cache->funk; + + fd_funk_txn_map_query_t query[1]; + fd_funk_rec_prepare_t prepare[1] = {{0}}; + fd_progcache_publish_prepare( prepare, rec, query, funk, xid ); + + int is_rooted = !prepare->rec_head_idx; + if( is_rooted ) fd_funk_txn_xid_set_root( rec->pair.xid ); + else fd_funk_txn_xid_copy( rec->pair.xid, xid ); + memcpy( rec->pair.key, prog_addr, 32UL ); + + rec->tag = 0; + rec->prev_idx = FD_FUNK_REC_IDX_NULL; + rec->next_idx = FD_FUNK_REC_IDX_NULL; + + fd_funk_rec_publish( funk, prepare ); + + if( !is_rooted ) { + int query_err = fd_funk_txn_map_query_test( query ); + if( FD_UNLIKELY( query_err!=FD_MAP_SUCCESS ) ) FD_LOG_CRIT(( "fd_funk_txn_map_query_test failed (%i-%s)", query_err, fd_map_strerror( query_err ) )); + } +} + +static fd_progcache_rec_t const * +fd_progcache_insert( fd_progcache_t * cache, + fd_funk_t * accdb, + fd_funk_txn_xid_t const * xid, + void const * prog_addr, + ulong gen, + fd_bank_t const * bank, + ulong const last_slot_modified ) { + + /* Acquire reference to ELF binary data */ + + fd_funk_txn_xid_t modify_xid; + ulong progdata_sz; + uchar const * progdata = fd_prog_load_elf( accdb, xid, prog_addr, &progdata_sz, &modify_xid ); + if( FD_UNLIKELY( !progdata ) ) return NULL; + + /* Prevent cache entry from crossing epoch boundary */ + + fd_epoch_schedule_t const * epoch_schedule = fd_bank_epoch_schedule_query( bank ); + ulong load_epoch = fd_bank_epoch_get( bank ); + ulong modify_epoch = fd_slot_to_epoch( epoch_schedule, modify_xid.ul[0], NULL ); + if( FD_UNLIKELY( modify_epoch= target_slot */ + fd_funk_txn_map_query_t query[1]; + FD_TEST( fd_funk_txn_map_query_try( cache->funk->txn_map, xid, NULL, query, 0 ) != FD_MAP_ERR_KEY ); + fd_funk_txn_t const * candidate = fd_funk_txn_map_query_ele_const( query ); + for(;;) { + ulong parent_idx = fd_funk_txn_idx( candidate->parent_cidx ); + if( fd_funk_txn_idx_is_null( parent_idx ) ) break; + fd_funk_txn_t const * parent = &cache->funk->txn_pool->ele[ parent_idx ]; + if( parent->xid.ul[0]xid.ul[0]>=candidate->xid.ul[0] ) ) { + /* prevent cycles and other forms of corruption */ + FD_LOG_CRIT(( "fd_progcache_insert failed: funk txn tree is malformed (txn %lu:%lu has parent %lu:%lu)", + candidate->xid.ul[0], candidate->xid.ul[1], + parent ->xid.ul[0], parent ->xid.ul[1] )); + } + candidate = parent; + } + modify_xid = candidate->xid; + } + + /* Peek ELF header to determine allocation size */ + + fd_features_t const * features = fd_bank_features_query( bank ); + ulong const load_slot = fd_bank_slot_get( bank ); + fd_prog_versions_t versions = fd_prog_versions( features, load_slot ); + fd_sbpf_loader_config_t config = { + .sbpf_min_version = versions.min_sbpf_version, + .sbpf_max_version = versions.max_sbpf_version, + }; + fd_sbpf_elf_info_t elf_info[1]; + if( FD_UNLIKELY( fd_sbpf_elf_peek( elf_info, progdata, progdata_sz, &config ) ) ) return NULL; + + /* Allocate cache entry */ + + fd_funk_t * funk = cache->funk; + ulong rec_footprint = fd_progcache_rec_footprint( elf_info ); + fd_funk_rec_t * funk_rec = fd_progcache_rec_acquire( cache, rec_footprint, gen ); + void * rec_mem = fd_wksp_laddr_fast( funk->wksp, funk_rec->val_gaddr ); + + /* Load cache entry */ + + fd_progcache_rec_t * rec = fd_progcache_rec_new( rec_mem, elf_info, load_slot, features, progdata, progdata_sz ); + if( FD_UNLIKELY( !rec ) ) { /* load failed? */ + fd_progcache_rec_tombstone( cache, funk_rec ); + rec_mem = fd_wksp_laddr_fast( funk->wksp, funk_rec->val_gaddr ); + rec = fd_progcache_rec_new_nx( rec_mem, load_slot, last_slot_modified ); + } + rec->last_slot_modified = last_slot_modified; + + /* Publish cache entry to funk index */ + + fd_progcache_publish( cache, &modify_xid, funk_rec, prog_addr ); + return rec; +} + +fd_progcache_rec_t const * +fd_progcache_pull( fd_progcache_t * cache, + fd_funk_t * accdb, + fd_funk_txn_xid_t const * xid, + void const * prog_addr, + ulong gen, + fd_bank_t const * bank ) { + if( FD_UNLIKELY( !cache || !cache->funk->shmem ) ) FD_LOG_CRIT(( "NULL progcache" )); + + fd_progcache_rec_t const * found_rec = fd_progcache_peek( cache, xid, prog_addr ); + ulong last_slot_modified = 0UL; + if( found_rec ) { + + fd_epoch_schedule_t const * epoch_schedule = fd_bank_epoch_schedule_query( bank ); + ulong current_slot = fd_bank_slot_get( bank ); + ulong current_epoch = fd_bank_epoch_get( bank ); + ulong last_epoch_verified = fd_slot_to_epoch( epoch_schedule, found_rec->last_slot_verified, NULL ); + /* */ last_slot_modified = found_rec->last_slot_modified; + if( FD_LIKELY( last_epoch_verified==current_epoch && + ( last_slot_modifiedlast_slot_verified || + current_slot==found_rec->last_slot_verified ) ) ) { + return found_rec; + } + + } + + return fd_progcache_insert( cache, accdb, xid, prog_addr, gen, bank, last_slot_modified ); +} + +fd_progcache_rec_t const * +fd_progcache_invalidate( fd_progcache_t * cache, + fd_funk_txn_xid_t const * xid, + void const * prog_addr, + ulong slot, + ulong gen ) { + if( FD_UNLIKELY( !cache || !cache->funk->shmem ) ) FD_LOG_CRIT(( "NULL progcache" )); + fd_funk_rec_key_t key[1]; memcpy( key->uc, prog_addr, 32UL ); + fd_funk_rec_query_t query[1]; + fd_funk_rec_t const * prev_rec = fd_funk_rec_query_try_global( cache->funk, xid, prog_addr, NULL, query ); + if( FD_UNLIKELY( !prev_rec ) ) return NULL; /* nothing to invalidate */ + + ulong rec_footprint = fd_progcache_rec_footprint( NULL ); + fd_funk_rec_t * funk_rec = fd_progcache_rec_acquire( cache, rec_footprint, gen ); + void * rec_mem = fd_wksp_laddr_fast( cache->funk->wksp, funk_rec->val_gaddr ); + fd_progcache_rec_t * rec = fd_progcache_rec_new_nx( rec_mem, slot, slot ); + fd_progcache_publish( cache, xid, funk_rec, prog_addr ); + return rec; +} + +/* reset_txn_list does a depth-first traversal of the txn tree. + Detaches all recs from txns by emptying rec linked lists. */ + +static void +reset_txn_list( fd_funk_t * funk, + ulong txn_head_idx ) { + fd_funk_txn_pool_t * txn_pool = funk->txn_pool; + for( ulong idx = txn_head_idx; + !fd_funk_txn_idx_is_null( idx ); + ) { + fd_funk_txn_t * txn = &txn_pool->ele[ idx ]; + fd_funk_txn_state_assert( txn, FD_FUNK_TXN_STATE_ACTIVE ); + txn->rec_head_idx = FD_FUNK_REC_IDX_NULL; + txn->rec_tail_idx = FD_FUNK_REC_IDX_NULL; + reset_txn_list( funk, txn->child_head_cidx ); + idx = fd_funk_txn_idx( txn->sibling_next_cidx ); + } +} + +/* reset_rec_map frees all records in a funk instance. */ + +static void +reset_rec_map( fd_funk_t * funk ) { + fd_wksp_t * wksp = funk->wksp; + fd_alloc_t * alloc = funk->alloc; + fd_funk_rec_map_t * rec_map = funk->rec_map; + fd_funk_rec_pool_t * rec_pool = funk->rec_pool; + + ulong chain_cnt = fd_funk_rec_map_chain_cnt( rec_map ); + for( ulong chain_idx=0UL; chain_idxmap_next );; + + /* Remove rec object from map */ + fd_funk_rec_map_query_t rec_query[1]; + int err = fd_funk_rec_map_remove( rec_map, fd_funk_rec_pair( rec ), NULL, rec_query, FD_MAP_FLAG_BLOCKING ); + fd_funk_rec_key_t key; fd_funk_rec_key_copy( &key, rec->pair.key ); + if( FD_UNLIKELY( err!=FD_MAP_SUCCESS ) ) FD_LOG_CRIT(( "fd_funk_rec_map_remove failed (%i-%s)", err, fd_map_strerror( err ) )); + + /* Free rec resources */ + fd_funk_val_flush( rec, alloc, wksp ); + fd_funk_rec_pool_release( rec_pool, rec, 1 ); + iter.ele_idx = next; + } + } +} + +void +fd_progcache_reset( fd_progcache_t * cache ) { + fd_funk_t * funk = cache->funk; + reset_txn_list( funk, fd_funk_txn_idx( funk->shmem->child_head_cidx ) ); + reset_rec_map( funk ); +} + +void +fd_progcache_clear( fd_progcache_t * cache ) { + /* FIXME this descends the progcache txn tree multiple times */ + fd_progcache_reset( cache ); + fd_funk_txn_cancel_all( cache->funk ); +} diff --git a/src/flamenco/progcache/fd_progcache.h b/src/flamenco/progcache/fd_progcache.h new file mode 100644 index 00000000000..fff8df31c87 --- /dev/null +++ b/src/flamenco/progcache/fd_progcache.h @@ -0,0 +1,149 @@ +#ifndef HEADER_fd_src_flamenco_fd_progcache_h +#define HEADER_fd_src_flamenco_fd_progcache_h + +/* fd_progcache.h provides an API for managing a cache of loaded + Solana on-chain program. + + ### Background + + Solana on-chain programs are rarely updated but frequently executed. + Before a program can be executed, it must be loaded and verified, + which is costly. + + ### Fork management + + The program cache is fork-aware (using funk transactions). Txn-level + operations take an exclusive lock over the cache (record ops are + stalled indefinitely until the txn completes). + + ### Cache entry + + Each Solana program can have a number of program cache entries + (typically only zero or one, in rare cases where the program content + differs across forks multiple). + + A cache entry consists of a funk_rec object (from a preallocated + object pool), and a variable-sized fd_progcache_entry struct + (from an fd_alloc heap). + + ### Cache fill policy + + fd_progcache is lazily filled on reads, and eagerly invalidated + if underlying programs are written to. + + ### Cache evict policy + + Cache eviction (i.e. force removal of potentially useful records) + happens on fill. Specifically, cache eviction is triggered when a + cache fill fails to allocate from the wksp (fd_alloc) heap. + + fd_progcache further has a concept of "generations" (gen). Each + cache fill operation specifies a 'gen' number. Only entries with a + lower 'gen' number may get evicted. + + ### Garbage collect policy + + fd_progcache cleans up unused entries eagerly when: + + 1. a database fork is cancelled (e.g. slot is rooted and competing + history dies, or consensus layer prunes a fork) + 2. a cache entry is orphaned (updated or invalidated by an epoch + boundary) + + ### Concurrency + + fd_progcache does not support concurrent access. This may change in + the future. */ + +#include "../fd_flamenco_base.h" +#include "fd_progcache_rec.h" +#include "../../funk/fd_funk.h" + +struct fd_progcache { + fd_funk_t funk[1]; +}; + +typedef struct fd_progcache fd_progcache_t; + +struct fd_progcache_metrics { + ulong rec_oom; + ulong val_oom; + ulong evict_scan; + ulong evict; +}; + +typedef struct fd_progcache_metrics fd_progcache_metrics_t; + +FD_PROTOTYPES_BEGIN + +extern FD_TL fd_progcache_metrics_t fd_progcache_metrics_default; +extern FD_TL fd_progcache_metrics_t * fd_progcache_metrics_cur; /* = &fd_progcache_metrics_default; */ + +/* fd_progcache_est_rec_max estimates the fd_funk rec_max parameter + given the cache's wksp footprint and mean cache entry heap + utilization. */ + +ulong +fd_progcache_est_rec_max( ulong wksp_footprint, + ulong mean_cache_entry_size ); + +/* fd_progcache_join joins the caller to a program cache funk instance. */ + +fd_progcache_t * +fd_progcache_join( fd_progcache_t * ljoin, + void * shfunk ); + +/* fd_progcache_leave detaches the caller from a program cache. */ + +void * +fd_progcache_leave( fd_progcache_t * cache, + void ** opt_shfunk ); + +/* fd_progcache_peek queries the program cache for an existing cache + entry. Does not fill the cache. Returns a pointer to the entry on + cache hit (invalidated by the next non-const API call). Returns NULL + on cache miss. */ + +fd_progcache_rec_t const * +fd_progcache_peek( fd_progcache_t const * cache, + fd_funk_txn_xid_t const * xid, + void const * prog_addr ); + +/* fd_progcache_pull does a cache fill (on cache miss) or returns an + existing cache entry (on cache hit). Returns a pointer to the cache + entry (invalidated by an API call with a higher gen number). */ + +fd_progcache_rec_t const * +fd_progcache_pull( fd_progcache_t * cache, + fd_funk_t * accdb, + fd_funk_txn_xid_t const * xid, + void const * prog_addr, + ulong gen, + fd_bank_t const * bank ); + +/* fd_progcache_invalidate marks the program at the given address as + invalidated (typically due to a change of program content). This + creates a non-executable cache entry at the given xid. */ + +fd_progcache_rec_t const * +fd_progcache_invalidate( fd_progcache_t * cache, + fd_funk_txn_xid_t const * xid, + void const * prog_addr, + ulong slot, + ulong gen ); + +/* fd_progcache_flush removes all cache entries while leaving the txn + graph intact. */ + +void +fd_progcache_reset( fd_progcache_t * cache ); + +/* fd_progcache_clear removes all cache entries and destroys the txn + graph. */ + +void +fd_progcache_clear( fd_progcache_t * cache ); + +FD_PROTOTYPES_END + +#endif /* HEADER_fd_src_flamenco_fd_progcache_h */ diff --git a/src/flamenco/progcache/fd_progcache_evict.c b/src/flamenco/progcache/fd_progcache_evict.c new file mode 100644 index 00000000000..c2d40090730 --- /dev/null +++ b/src/flamenco/progcache/fd_progcache_evict.c @@ -0,0 +1,53 @@ +#include "fd_progcache_evict.h" + +fd_funk_rec_t * +fd_progcache_rec_acquire( fd_progcache_t * cache, + ulong rec_footprint, + ulong gen ) { + /* FIXME CACHE EVICT ALGO */ + (void)gen; + + fd_funk_t * funk = cache->funk; + fd_funk_rec_t * rec = fd_funk_rec_pool_acquire( funk->rec_pool, NULL, 0, NULL ); + if( FD_UNLIKELY( !rec ) ) { + FD_LOG_ERR(( "Program cache is out of memory: fd_funk_rec_pool_acquire failed (rec_max=%lu)", + fd_funk_rec_pool_ele_max( funk->rec_pool ) )); + } + memset( rec, 0, sizeof(fd_funk_rec_t) ); + + ulong rec_align = fd_progcache_rec_align(); + void * val = fd_alloc_malloc( funk->alloc, rec_align, rec_footprint ); + if( FD_UNLIKELY( !val ) ) { + FD_LOG_ERR(( "Program cache is out of memory: fd_alloc_malloc failed (requested align=%lu sz=%lu)", + rec_align, rec_footprint )); + } + + rec->val_gaddr = fd_wksp_gaddr_fast( funk->wksp, val ); + rec->val_max = (uint)( rec_footprint & FD_FUNK_REC_VAL_MAX ); + rec->val_sz = (uint)( rec_footprint & FD_FUNK_REC_VAL_MAX ); + return rec; +} + +fd_funk_rec_t * +fd_progcache_rec_tombstone( fd_progcache_t * cache, + fd_funk_rec_t * rec ) { + fd_funk_t * funk = cache->funk; + void * val = fd_wksp_laddr_fast( funk->wksp, rec->val_gaddr ); + fd_alloc_free( funk->alloc, val ); + fd_funk_val_init( rec ); + + /* FIXME CACHE EVICT ALGO */ + + ulong rec_align = fd_progcache_rec_align(); + ulong rec_footprint = fd_progcache_rec_footprint( NULL ); /* non-executable */ + val = fd_alloc_malloc( funk->alloc, rec_align, rec_footprint ); + if( FD_UNLIKELY( !val ) ) { + FD_LOG_ERR(( "Program cache is out of memory: fd_alloc_malloc failed (requested align=%lu sz=%lu)", + rec_align, rec_footprint )); + } + + rec->val_gaddr = fd_wksp_gaddr_fast( funk->wksp, val ); + rec->val_max = (uint)( rec_footprint & FD_FUNK_REC_VAL_MAX ); + rec->val_sz = (uint)( rec_footprint & FD_FUNK_REC_VAL_MAX ); + return rec; +} diff --git a/src/flamenco/progcache/fd_progcache_evict.h b/src/flamenco/progcache/fd_progcache_evict.h new file mode 100644 index 00000000000..ba046a842e7 --- /dev/null +++ b/src/flamenco/progcache/fd_progcache_evict.h @@ -0,0 +1,33 @@ +#ifndef HEADER_fd_src_flamenco_progcache_fd_progcache_evict_h +#define HEADER_fd_src_flamenco_progcache_fd_progcache_evict_h + +/* fd_progcache_evict.h provides internal cache eviction APIs for the + on-chain program cache. */ + +#include "fd_progcache.h" +#include "../../funk/fd_funk_rec.h" + +FD_PROTOTYPES_BEGIN + +/* fd_progcache_rec_acquire acquires funk_rec and progcache_rec objects, + evicting records from progcache as necessary. rec_footprint is the + size of the progcache_rec object. Returns a pointer to the newly + created funk_rec object (with uninitialized progcache_rec attached as + val). Terminates the app with FD_LOG_ERR if allocation failed after + exhausting all possible eviction possibilities. */ + +fd_funk_rec_t * +fd_progcache_rec_acquire( fd_progcache_t * cache, + ulong rec_footprint, + ulong gen ); + +/* fd_progcache_rec_tombstone swaps out a prior allocation (from + rec_acquire) for a tombstone. */ + +fd_funk_rec_t * +fd_progcache_rec_tombstone( fd_progcache_t * cache, + fd_funk_rec_t * rec ); + +FD_PROTOTYPES_END + +#endif /* HEADER_fd_src_flamenco_progcache_fd_progcache_evict_h */ diff --git a/src/flamenco/progcache/fd_progcache_rec.c b/src/flamenco/progcache/fd_progcache_rec.c new file mode 100644 index 00000000000..74758e9f606 --- /dev/null +++ b/src/flamenco/progcache/fd_progcache_rec.c @@ -0,0 +1,110 @@ +#include "fd_progcache_rec.h" +#include "../vm/fd_vm.h" /* fd_vm_syscall_register_slot, fd_vm_validate */ + +fd_progcache_rec_t * +fd_progcache_rec_new( void * mem, + fd_sbpf_elf_info_t const * elf_info, + ulong load_slot, + fd_features_t const * features, + void const * progdata, + ulong progdata_sz ) { + + /* Format object */ + + int has_calldests = !fd_sbpf_enable_stricter_elf_headers_enabled( elf_info->sbpf_version ); + ulong pc_max = fd_ulong_max( 1UL, elf_info->text_cnt ); + + FD_SCRATCH_ALLOC_INIT( l, mem ); + fd_progcache_rec_t * rec = FD_SCRATCH_ALLOC_APPEND( l, fd_progcache_rec_align(), sizeof(fd_progcache_rec_t) ); + void * calldests_mem = NULL; + if( has_calldests ) + /* */calldests_mem = FD_SCRATCH_ALLOC_APPEND( l, fd_sbpf_calldests_align(), fd_sbpf_calldests_footprint( pc_max ) ); + void * rodata_mem = FD_SCRATCH_ALLOC_APPEND( l, 8UL, elf_info->bin_sz ); + FD_SCRATCH_ALLOC_FINI( l, fd_progcache_rec_align() ); + memset( rec, 0, sizeof(fd_progcache_rec_t) ); + rec->calldests_off = has_calldests ? (uint)( (ulong)calldests_mem - (ulong)mem ) : 0U; + rec->rodata_off = (uint)( (ulong)rodata_mem - (ulong)mem ); + + rec->text_cnt = elf_info->text_cnt; + rec->text_off = elf_info->text_off; + rec->text_sz = (uint)elf_info->text_sz; + rec->sbpf_version = (uchar)elf_info->sbpf_version; + + /* Set up sbpf_loader (redirect writes into progcache_rec object) */ + + fd_sbpf_program_t prog[1] = {{ + .info = *elf_info, + .rodata = rodata_mem, + .text = (ulong *)((ulong)rodata_mem + elf_info->text_off), /* FIXME: WHAT IF MISALIGNED */ + .entry_pc = ULONG_MAX + }}; + if( has_calldests ) { + prog->calldests_shmem = calldests_mem; + prog->calldests = fd_sbpf_calldests_join( fd_sbpf_calldests_new( calldests_mem, pc_max ) ); + } + + /* Loader requires syscall table */ + + fd_sbpf_syscalls_t _syscalls[ FD_SBPF_SYSCALLS_SLOT_CNT ]; + fd_sbpf_syscalls_t * syscalls = fd_sbpf_syscalls_join( fd_sbpf_syscalls_new( _syscalls ) ); + int syscalls_err = fd_vm_syscall_register_slot( syscalls, load_slot, features, /* is_deploy */ 0 ); + if( FD_UNLIKELY( syscalls_err!=FD_VM_SUCCESS ) ) FD_LOG_CRIT(( "fd_vm_syscall_register_slot failed" )); + + /* Run ELF loader */ + + fd_sbpf_loader_config_t config = {0}; + if( FD_UNLIKELY( 0!=fd_sbpf_program_load( prog, progdata, progdata_sz, syscalls, &config ) ) ) {\ + return NULL; + } + + rec->entry_pc = (uint)prog->entry_pc; + rec->rodata_sz = (uint)prog->rodata_sz; + + /* Run bytecode validator */ + + fd_vm_t _vm[1]; + fd_vm_t * vm = fd_vm_join( fd_vm_new( _vm ) ); + if( FD_UNLIKELY( !vm ) ) FD_LOG_CRIT(( "fd_vm_new failed" )); + vm = fd_vm_init( vm, + NULL, /* OK since unused in `fd_vm_validate()` */ + 0UL, + 0UL, + prog->rodata, + prog->rodata_sz, + prog->text, + prog->info.text_cnt, + prog->info.text_off, + prog->info.text_sz, + prog->entry_pc, + prog->calldests, + elf_info->sbpf_version, + syscalls, + NULL, + NULL, + NULL, + 0U, + NULL, + 0, + FD_FEATURE_ACTIVE( load_slot, features, bpf_account_data_direct_mapping ), + 0 ); + if( FD_UNLIKELY( !vm ) ) FD_LOG_CRIT(( "fd_vm_init failed" )); + + if( FD_UNLIKELY( fd_vm_validate( vm )!=FD_VM_SUCCESS ) ) return NULL; + + rec->last_slot_verified = load_slot; + rec->last_slot_modified = 0UL; + rec->executable = 1; + return rec; +} + +fd_progcache_rec_t * +fd_progcache_rec_new_nx( void * mem, + ulong load_slot, + ulong modify_slot ) { + fd_progcache_rec_t * rec = mem; + memset( rec, 0, sizeof(fd_progcache_rec_t) ); + rec->last_slot_verified = load_slot; + rec->last_slot_modified = modify_slot; + rec->executable = 0; + return rec; +} diff --git a/src/flamenco/progcache/fd_progcache_rec.h b/src/flamenco/progcache/fd_progcache_rec.h new file mode 100644 index 00000000000..6227eeea1d7 --- /dev/null +++ b/src/flamenco/progcache/fd_progcache_rec.h @@ -0,0 +1,126 @@ +#ifndef HEADER_fd_src_flamenco_progcache_fd_progcache_rec_h +#define HEADER_fd_src_flamenco_progcache_fd_progcache_rec_h + +#include "../fd_flamenco_base.h" +#include "../../ballet/sbpf/fd_sbpf_loader.h" + +/* fd_progcache_rec_t is the fixed size header of a program cache entry + object. Entries are either non-executable (e.g. programs that failed + verification) or executable. Non-executable entry objects consist + only of this header struct. Executable entry objects are variable- + sized and contain additional structures past this header (rodata/ROM + segment, control flow metadata, ...). */ + +struct fd_progcache_rec { + /* Stores the last slot the program was modified. This field is + updated at the end of a transaction for a program account that was + referenced as a writable account within an instruction. For any + programs that are freshly added to the cache, this field is set to + 0. */ + ulong last_slot_modified; + + /* Stores the last slot verification checks were ran for a program. + Programs are reverified if they are mentioned in the current + transaction, and if one of the following are true: + - It is the first time they are referenced in a transaction in the + current epoch + - The program was a writable account in a transaction + + We reverify referenced programs at least once every epoch + regardless of whether they were modified or not because changes + in the active feature set may cause existing deployed programs + to be invalided (e.g. stricter ELF / VM / sBPF checks). */ + ulong last_slot_verified; + + uint entry_pc; + uint text_cnt; + uint text_off; + uint text_sz; + + uint rodata_sz; + + uint calldests_off; /* offset to sbpf_calldests map */ + uint rodata_off; /* offset to rodata segment */ + + /* SBPF version, SIMD-0161 */ + uchar sbpf_version; + + uint executable : 1; +}; + +typedef struct fd_progcache_rec fd_progcache_rec_t; + +FD_PROTOTYPES_BEGIN + +/* Accessors */ + +static inline uchar const * +fd_progcache_rec_rodata( fd_progcache_rec_t const * rec ) { + return (uchar const *)rec + rec->rodata_off; +} + +static inline fd_sbpf_calldests_t const * +fd_progcache_rec_calldests( fd_progcache_rec_t const * rec ) { + return fd_sbpf_calldests_join( (void *)( (ulong)rec + rec->calldests_off ) ); +} + +/* Private APIs */ + +/* fd_progcache_rec_{align,footprint} give the params of backing memory + of a progcache_rec object for the given ELF info. If elf_info is + NULL, implies a non-executable cache entry (sizeof(fd_progcache_rec_t)). */ + +FD_FN_CONST static inline ulong +fd_progcache_rec_align( void ) { + return alignof(fd_progcache_rec_t); +} + +FD_FN_PURE FD_FN_UNUSED static ulong +fd_progcache_rec_footprint( fd_sbpf_elf_info_t const * elf_info ) { + if( !elf_info ) return sizeof(fd_progcache_rec_t); /* non-executable */ + + int has_calldests = !fd_sbpf_enable_stricter_elf_headers_enabled( elf_info->sbpf_version ); + ulong pc_max = fd_ulong_max( 1UL, elf_info->text_cnt ); + + ulong l = FD_LAYOUT_INIT; + l = FD_LAYOUT_APPEND( l, alignof(fd_progcache_rec_t), sizeof(fd_progcache_rec_t) ); + if( has_calldests ) { + l = FD_LAYOUT_APPEND( l, fd_sbpf_calldests_align(), fd_sbpf_calldests_footprint( pc_max ) ); + } + l = FD_LAYOUT_APPEND( l, 8UL, elf_info->bin_sz ); + return FD_LAYOUT_FINI( l, fd_progcache_rec_align() ); +} + +/* fd_progcache_rec_new creates a new excutable progcache_rec object. + mem points to a memory region matching fd_progcache_rec_{align, + footprint}. Loads and verifies the given program data and returns + the newly created executable object on success. On failure, returns + NULL (the caller may call fd_progcache_rec_new_nx instead). */ + +fd_progcache_rec_t * +fd_progcache_rec_new( void * mem, + fd_sbpf_elf_info_t const * elf_info, + ulong load_slot, + fd_features_t const * features, + void const * progdata, + ulong progdata_sz ); + +/* fd_progcache_rec_new_nx creates a non-executable program_cache + object. fd_progcache_rec_t[1] is suitable for mem. */ + +fd_progcache_rec_t * +fd_progcache_rec_new_nx( void * mem, + ulong load_slot, + ulong modify_slot ); + +/* fd_progcache_rec_delete destroys a progcache_rec object and returns + the backing memory region to the caller. */ + +static inline void * +fd_progcache_rec_delete( fd_progcache_rec_t * rec ) { + return rec; +} + +FD_PROTOTYPES_END + +#endif /* HEADER_fd_src_flamenco_progcache_fd_progcache_rec_h */ diff --git a/src/flamenco/progcache/test_progcache.c b/src/flamenco/progcache/test_progcache.c new file mode 100644 index 00000000000..9a8bf356ef6 --- /dev/null +++ b/src/flamenco/progcache/test_progcache.c @@ -0,0 +1,827 @@ +#include "fd_progcache.h" +#include "../runtime/fd_bank.h" + +/* FIXME verify that progcache_rec >= align of all sub objects */ + +#if FD_HAS_HOSTED + +#define TEST_WKSP_TAG 1234UL + +/* Load in programdata for tests */ +FD_IMPORT_BINARY( valid_program_data, "src/ballet/sbpf/fixtures/hello_solana_program.so" ); +FD_IMPORT_BINARY( bigger_valid_program_data, "src/ballet/sbpf/fixtures/clock_sysvar_program.so" ); +FD_IMPORT_BINARY( invalid_program_data, "src/ballet/sbpf/fixtures/malformed_bytecode.so" ); + +/* Test program pubkeys */ +static fd_pubkey_t const test_program_pubkey = { + .uc = { 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, + 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99 } +}; + +/* Test setup and teardown helpers */ +static fd_wksp_t * test_wksp = NULL; +static fd_progcache_t * test_cache = NULL; +static fd_funk_t * test_accdb = NULL; +static fd_bank_t * test_bank = NULL; +static fd_banks_t * test_banks = NULL; +static fd_funk_txn_xid_t test_xid = {0}; + +static void +test_teardown( void ) { + if( test_accdb ) { + fd_funk_leave( test_accdb, NULL ); + test_accdb = NULL; + } + + if( test_wksp ) { + fd_wksp_delete_anonymous( test_wksp ); + test_wksp = NULL; + } +} + +/* Helper to create a funk transaction */ +static fd_funk_txn_xid_t +test_funk_txn_create( void ) { + fd_funk_txn_xid_t xid = { .ul={ 433000UL, 123UL } }; + fd_funk_txn_xid_t root; fd_funk_txn_xid_set_root( &root ); + fd_funk_txn_prepare( test_accdb, &root, &xid ); + fd_funk_txn_prepare( test_cache->funk, &root, &xid ); + return xid; +} + +static void +test_funk_txn_cancel( void ) { + fd_funk_txn_cancel( test_cache->funk, &test_xid ); + fd_funk_txn_cancel( test_accdb, &test_xid ); +} + +/* Helper to create a test account */ +static void +create_test_account( fd_funk_txn_xid_t const * xid, + fd_pubkey_t const * pubkey, + fd_pubkey_t const * owner, + uchar const * data, + ulong data_len, + uchar executable ) { + FD_TXN_ACCOUNT_DECL( acc ); + fd_funk_rec_prepare_t prepare = {0}; + int err = fd_txn_account_init_from_funk_mutable( /* acc */ acc, + /* pubkey */ pubkey, + /* funk */ test_accdb, + /* xid */ xid, + /* do_create */ 1, + /* min_data_sz */ data_len, + /* prepare */ &prepare ); + FD_TEST( !err ); + + if( data ) { + fd_txn_account_set_data( acc, data, data_len ); + } + + acc->starting_lamports = 1UL; + acc->starting_dlen = data_len; + fd_txn_account_set_lamports( acc, 1UL ); + fd_txn_account_set_executable( acc, executable ); + fd_txn_account_set_owner( acc, owner ); + + fd_txn_account_mutable_fini( acc, test_accdb, &prepare ); +} + +static void +update_account_data( fd_pubkey_t const * pubkey, + uchar const * data, + ulong data_len ) { + FD_TXN_ACCOUNT_DECL( acc ); + fd_funk_rec_prepare_t prepare = {0}; + int err = fd_txn_account_init_from_funk_mutable( /* acc */ acc, + /* pubkey */ pubkey, + /* funk */ test_accdb, + /* xid */ &test_xid, + /* do_create */ 0, + /* min_data_sz */ data_len, + /* prepare */ &prepare ); + FD_TEST( !err ); + FD_TEST( data ); + + fd_txn_account_set_data( acc, data, data_len ); + fd_txn_account_mutable_fini( acc, test_accdb, &prepare ); +} + +/* Test 1: Account doesn't exist */ +static void +test_account_does_not_exist( void ) { + FD_LOG_INFO(( "Testing: Account doesn't exist" )); + + test_xid = test_funk_txn_create(); + + /* Call with a non-existent pubkey */ + fd_pubkey_t const non_existent_pubkey = {0}; + + /* This should return early without doing anything */ + FD_TEST( fd_progcache_pull( test_cache, test_accdb, &test_xid, &non_existent_pubkey, 1UL, test_bank )==NULL ); + + /* Verify no cache entry was created */ + FD_TEST( fd_progcache_peek( test_cache, &test_xid, &non_existent_pubkey )==NULL ); + + test_funk_txn_cancel(); +} + +/* Test 2: Account exists but is not owned by a BPF loader */ +static void +test_account_not_bpf_loader_owner( void ) { + FD_LOG_INFO(( "Testing: Account exists but is not owned by a BPF loader" )); + + test_xid = test_funk_txn_create(); + + /* Create an account owned by a non-BPF loader */ + create_test_account( &test_xid, + &test_program_pubkey, + &fd_solana_system_program_id, + invalid_program_data, + invalid_program_data_sz, + 1 ); + + /* This should return early without doing anything */ + FD_TEST( fd_progcache_pull( test_cache, test_accdb, &test_xid, &test_program_pubkey, 1UL, test_bank )==NULL ); + + /* Verify no cache entry was created */ + FD_TEST( fd_progcache_peek( test_cache, &test_xid, &test_program_pubkey )==NULL ); + + test_funk_txn_cancel(); +} + +/* Test 3: Program is not in cache yet (first time), but program fails validations */ +static void +test_invalid_program_not_in_cache_first_time( void ) { + FD_LOG_INFO(( "Testing: Program is not in cache yet (first time), but program fails validations" )); + + test_xid = test_funk_txn_create(); + + /* Create a BPF loader account */ + create_test_account( &test_xid, + &test_program_pubkey, + &fd_solana_bpf_loader_program_id, + invalid_program_data, + invalid_program_data_sz, + 1 ); + + /* This should create a cache entry */ + FD_TEST( fd_progcache_pull( test_cache, test_accdb, &test_xid, &test_program_pubkey, 1UL, test_bank ) ); + + /* Verify cache entry was created */ + fd_progcache_rec_t const * rec = fd_progcache_peek( test_cache, &test_xid, &test_program_pubkey ); + FD_TEST( rec ); + FD_TEST( rec->executable==0 ); + FD_TEST( rec->last_slot_verified==fd_bank_slot_get( test_bank ) ); + + test_funk_txn_cancel(); +} + +/* Test 4: Program is not in cache yet (first time), but program passes validations */ +static void +test_valid_program_not_in_cache_first_time( void ) { + FD_LOG_INFO(( "Testing: Program is not in cache yet (first time), but program passes validations" )); + + test_xid = test_funk_txn_create(); + + /* Create a BPF loader account */ + create_test_account( &test_xid, + &test_program_pubkey, + &fd_solana_bpf_loader_program_id, + valid_program_data, + valid_program_data_sz, + 1 ); + + /* This should create a cache entry */ + FD_TEST( fd_progcache_pull( test_cache, test_accdb, &test_xid, &test_program_pubkey, 1UL, test_bank ) ); + + /* Verify cache entry was created */ + fd_progcache_rec_t const * rec = fd_progcache_peek( test_cache, &test_xid, &test_program_pubkey ); + FD_TEST( rec ); + FD_TEST( rec->executable==1 ); + FD_TEST( rec->last_slot_verified==fd_bank_slot_get( test_bank ) ); + + test_funk_txn_cancel(); +} + +/* Test 5: Program is in cache but needs reverification + (different epoch) */ +static void +test_program_in_cache_needs_reverification( void ) { + FD_LOG_INFO(( "Testing: Program is in cache but needs reverification (different epoch)" )); + + test_xid = test_funk_txn_create(); + + /* Create a BPF loader account */ + create_test_account( &test_xid, + &test_program_pubkey, + &fd_solana_bpf_loader_program_id, + valid_program_data, + valid_program_data_sz, + 1 ); + + /* First call to create cache entry */ + FD_TEST( fd_progcache_pull( test_cache, test_accdb, &test_xid, &test_program_pubkey, 1UL, test_bank ) ); + + /* Verify cache entry was created */ + fd_progcache_rec_t const * valid_prog = fd_progcache_peek( test_cache, &test_xid, &test_program_pubkey ); + FD_TEST( valid_prog ); + FD_TEST( valid_prog->executable==1 ); + FD_TEST( valid_prog->last_slot_verified==fd_bank_slot_get( test_bank ) ); + FD_TEST( valid_prog->last_slot_modified==0UL ); + + /* Fast forward to next epoch */ + fd_bank_slot_set( test_bank, fd_bank_slot_get( test_bank ) + 432000UL ); + + /* This should trigger reverification */ + FD_TEST( fd_progcache_pull( test_cache, test_accdb, &test_xid, &test_program_pubkey, 1UL, test_bank ) ); + + /* Verify the cache entry was updated */ + valid_prog = fd_progcache_peek( test_cache, &test_xid, &test_program_pubkey ); + FD_TEST( valid_prog ); + FD_TEST( valid_prog->executable==1 ); + FD_TEST( valid_prog->last_slot_verified==fd_bank_slot_get( test_bank ) ); + FD_TEST( valid_prog->last_slot_modified==0UL ); + + test_funk_txn_cancel(); +} + +/* Test 6: Program is in cache and was just modified, so it should be + queued for reverification */ +static void +test_program_in_cache_queued_for_reverification( void ) { + FD_LOG_INFO(( "Testing: Program is in cache and was just modified, so it should be queued for reverification" )); + + test_xid = test_funk_txn_create(); + + /* Create a BPF loader account */ + create_test_account( &test_xid, + &test_program_pubkey, + &fd_solana_bpf_loader_program_id, + valid_program_data, + valid_program_data_sz, + 1 ); + + /* First call to create cache entry */ + FD_TEST( fd_progcache_pull( test_cache, test_accdb, &test_xid, &test_program_pubkey, 1UL, test_bank ) ); + + /* Verify cache entry was created */ + fd_progcache_rec_t const * valid_prog = fd_progcache_peek( test_cache, &test_xid, &test_program_pubkey ); + FD_TEST( valid_prog ); + FD_TEST( valid_prog->executable==1 ); + FD_TEST( valid_prog->last_slot_verified==fd_bank_slot_get( test_bank ) ); + + /* Fast forward to a future slot */ + ulong original_slot = fd_bank_slot_get( test_bank ); + fd_bank_slot_set( test_bank, fd_bank_slot_get( test_bank ) + 1000UL ); + ulong future_slot = fd_bank_slot_get( test_bank ); + FD_TEST( future_slot>original_slot ); + + /* Get the program account to pass to the queue function */ + FD_TXN_ACCOUNT_DECL( program_acc ); + int err = fd_txn_account_init_from_funk_readonly( program_acc, &test_program_pubkey, test_accdb, &test_xid ); + FD_TEST( !err ); + + /* Queue the program for reverification */ + fd_progcache_invalidate( test_cache, &test_xid, &test_program_pubkey, future_slot, 1UL ); + + /* Verify the cache entry was updated with the future slot as last_slot_modified */ + valid_prog = fd_progcache_peek( test_cache, &test_xid, &test_program_pubkey ); + FD_TEST( valid_prog ); + FD_TEST( valid_prog->executable==0 ); + FD_TEST( valid_prog->last_slot_modified==future_slot ); + FD_TEST( valid_prog->last_slot_verified==future_slot ); + FD_TEST( valid_prog->last_slot_modified>original_slot ); + + /* Reverify the cache entry at the future slot */ + fd_progcache_pull( test_cache, test_accdb, &test_xid, &test_program_pubkey, 1UL, test_bank ); + + /* Verify the cache entry was updated */ + valid_prog = fd_progcache_peek( test_cache, &test_xid, &test_program_pubkey ); + FD_TEST( valid_prog ); + FD_TEST( valid_prog->executable==1 ); + FD_TEST( valid_prog->last_slot_modified==future_slot ); + FD_TEST( valid_prog->last_slot_verified==future_slot ); + + test_funk_txn_cancel(); +} + +/* Test 7: Program queued for reverification but program doesn't exist + in the cache yet */ +static void +test_program_queued_for_reverification_account_does_not_exist( void ) { + FD_LOG_INFO(( "Testing: Program queued for reverification but account doesn't exist" )); + + test_xid = test_funk_txn_create(); + + /* Create a BPF loader account but don't add it to the cache */ + create_test_account( &test_xid, + &test_program_pubkey, + &fd_solana_bpf_loader_program_id, + valid_program_data, + valid_program_data_sz, + 1 ); + + /* Fast forward to a future slot */ + ulong original_slot = fd_bank_slot_get( test_bank ); + fd_bank_slot_set( test_bank, fd_bank_slot_get( test_bank ) + 1000UL ); + ulong future_slot = fd_bank_slot_get( test_bank ); + FD_TEST( future_slot>original_slot ); + + /* Get the program account to pass to the queue function */ + FD_TXN_ACCOUNT_DECL( program_acc ); + int err = fd_txn_account_init_from_funk_readonly( program_acc, &test_program_pubkey, test_accdb, &test_xid ); + FD_TEST( !err ); + + /* Try to queue the program for reverification - this should return early since it's not in cache */ + fd_progcache_invalidate( test_cache, &test_xid, &test_program_pubkey, future_slot, 1UL ); + + /* Verify no cache entry was created since the program wasn't in the cache */ + FD_TEST( !fd_progcache_peek( test_cache, &test_xid, &test_program_pubkey ) ); + + test_funk_txn_cancel(); +} + +/* Test 8: Program is in cache and was just modified and queued for + reverification, so when it is next reverified the + `last_slot_verified` should be set to the current slot */ +static void +test_program_in_cache_queued_for_reverification_and_processed( void ) { + FD_LOG_INFO(( "Testing: Program is in cache and was just modified and queued for reverification, so the last slot reverification ran should be set to the current slot" )); + + test_xid = test_funk_txn_create(); + + /* Create a BPF loader account */ + create_test_account( &test_xid, + &test_program_pubkey, + &fd_solana_bpf_loader_program_id, + valid_program_data, + valid_program_data_sz, + 1 ); + + /* First call to create cache entry */ + fd_progcache_pull( test_cache, test_accdb, &test_xid, &test_program_pubkey, 1UL, test_bank ); + + /* Verify cache entry was created */ + fd_progcache_rec_t const * valid_prog = fd_progcache_peek( test_cache, &test_xid, &test_program_pubkey ); + FD_TEST( valid_prog ); + FD_TEST( valid_prog->executable==1 ); + FD_TEST( valid_prog->last_slot_verified==fd_bank_slot_get( test_bank ) ); + + /* Fast forward to a future slot */ + ulong original_slot = fd_bank_slot_get( test_bank ); + fd_bank_slot_set( test_bank, fd_bank_slot_get( test_bank ) + 11000UL ); + ulong future_slot = fd_bank_slot_get( test_bank ); + FD_TEST( future_slot>original_slot ); + + /* Get the program account to pass to the queue function */ + FD_TXN_ACCOUNT_DECL( program_acc ); + int err = fd_txn_account_init_from_funk_readonly( program_acc, &test_program_pubkey, test_accdb, &test_xid ); + FD_TEST( !err ); + + /* Queue the program for reverification */ + fd_progcache_invalidate( test_cache, &test_xid, &test_program_pubkey, future_slot, 1UL ); + + /* Verify the cache entry was updated with the future slot as last_slot_modified */ + valid_prog = fd_progcache_peek( test_cache, &test_xid, &test_program_pubkey ); + FD_TEST( valid_prog ); + FD_TEST( valid_prog->executable==0 ); + FD_TEST( valid_prog->last_slot_modified==future_slot ); + FD_TEST( valid_prog->last_slot_verified==future_slot ); + FD_TEST( valid_prog->last_slot_modified>original_slot ); + + /* Fast forward to a future slot */ + fd_bank_slot_set( test_bank, fd_bank_slot_get( test_bank ) + 11000UL ); + ulong future_update_slot = fd_bank_slot_get( test_bank ); + FD_TEST( future_update_slot>future_slot ); + + /* Now update the cache entry at the future slot */ + fd_progcache_pull( test_cache, test_accdb, &test_xid, &test_program_pubkey, 1UL, test_bank ); + + /* Verify the cache entry was updated */ + valid_prog = fd_progcache_peek( test_cache, &test_xid, &test_program_pubkey ); + FD_TEST( valid_prog ); + FD_TEST( valid_prog->executable==1 ); + FD_TEST( valid_prog->last_slot_verified==future_update_slot ); + FD_TEST( valid_prog->last_slot_modified==future_slot ); + + test_funk_txn_cancel(); +} + +/* Test 9: Genesis program fails verification, and is reverified later */ +static void +test_invalid_genesis_program_reverified_after_genesis( void ) { + FD_LOG_INFO(( "Testing: Program fails verification in genesis, and is reverified later" )); + + test_xid = test_funk_txn_create(); + fd_bank_slot_set( test_bank, 0UL ); + + /* Create a BPF loader account */ + create_test_account( &test_xid, + &test_program_pubkey, + &fd_solana_bpf_loader_program_id, + invalid_program_data, + invalid_program_data_sz, + 1 ); + + /* First call to create cache entry */ + fd_progcache_pull( test_cache, test_accdb, &test_xid, &test_program_pubkey, 1UL, test_bank ); + + /* Verify cache entry was created */ + fd_progcache_rec_t const * valid_prog = fd_progcache_peek( test_cache, &test_xid, &test_program_pubkey ); + FD_TEST( valid_prog ); + FD_TEST( valid_prog->executable==0 ); + FD_TEST( valid_prog->last_slot_modified==0UL ); + FD_TEST( valid_prog->last_slot_verified==0UL ); + + /* Fast forward to a future slot */ + ulong original_slot = fd_bank_slot_get( test_bank ); + fd_bank_slot_set( test_bank, fd_bank_slot_get( test_bank ) + 11000UL ); + ulong future_slot = fd_bank_slot_get( test_bank ); + FD_TEST( future_slot>original_slot ); + + /* Program invoked, update cache entry */ + fd_progcache_pull( test_cache, test_accdb, &test_xid, &test_program_pubkey, 1UL, test_bank ); + + /* Verify the cache entry was updated */ + valid_prog = fd_progcache_peek( test_cache, &test_xid, &test_program_pubkey ); + FD_TEST( valid_prog ); + FD_TEST( valid_prog->executable==0 ); + FD_TEST( valid_prog->last_slot_verified==future_slot ); + FD_TEST( valid_prog->last_slot_modified==0UL ); + + test_funk_txn_cancel(); +} + +/* Test 10: Genesis program passes verification, and is reverified + later */ +static void +test_valid_genesis_program_reverified_after_genesis( void ) { + FD_LOG_INFO(( "Testing: Program passes verification in genesis, and is reverified later" )); + + test_xid = test_funk_txn_create(); + fd_bank_slot_set( test_bank, 0UL ); + + /* Create a BPF loader account */ + create_test_account( &test_xid, + &test_program_pubkey, + &fd_solana_bpf_loader_program_id, + valid_program_data, + valid_program_data_sz, + 1 ); + + /* First call to create cache entry */ + fd_progcache_pull( test_cache, test_accdb, &test_xid, &test_program_pubkey, 1UL, test_bank ); + + /* Verify cache entry was created */ + fd_progcache_rec_t const * valid_prog = fd_progcache_peek( test_cache, &test_xid, &test_program_pubkey ); + FD_TEST( valid_prog ); + FD_TEST( valid_prog->executable==1 ); + FD_TEST( valid_prog->last_slot_modified==0UL ); + FD_TEST( valid_prog->last_slot_verified==0UL ); + + /* Fast forward to a future slot */ + ulong original_slot = fd_bank_slot_get( test_bank ); + fd_bank_slot_set( test_bank, fd_bank_slot_get( test_bank ) + 11000UL ); + ulong future_slot = fd_bank_slot_get( test_bank ); + FD_TEST( future_slot>original_slot ); + + /* Program invoked, update cache entry */ + fd_progcache_pull( test_cache, test_accdb, &test_xid, &test_program_pubkey, 1UL, test_bank ); + + /* Verify the cache entry was updated */ + valid_prog = fd_progcache_peek( test_cache, &test_xid, &test_program_pubkey ); + FD_TEST( valid_prog ); + FD_TEST( valid_prog->executable==1 ); + FD_TEST( valid_prog->last_slot_verified==future_slot ); + FD_TEST( valid_prog->last_slot_modified==0UL ); + + test_funk_txn_cancel(); +} + +/* Test 11: Program gets upgraded with a larger programdata size */ +static void +test_program_upgraded_with_larger_programdata( void ) { + FD_LOG_INFO(( "Testing: Program gets upgraded with a larger programdata size" )); + + test_xid = test_funk_txn_create(); + fd_bank_slot_set( test_bank, 0UL ); + + /* Create a BPF loader account */ + create_test_account( &test_xid, + &test_program_pubkey, + &fd_solana_bpf_loader_program_id, + valid_program_data, + valid_program_data_sz, + 1 ); + + /* First call to create cache entry */ + FD_TEST( fd_progcache_pull( test_cache, test_accdb, &test_xid, &test_program_pubkey, 1UL, test_bank ) ); + + /* Verify cache entry was created */ + fd_progcache_rec_t const * valid_prog = fd_progcache_peek( test_cache, &test_xid, &test_program_pubkey ); + FD_TEST( valid_prog ); + FD_TEST( valid_prog->executable==1 ); + FD_TEST( valid_prog->last_slot_modified==0UL ); + FD_TEST( valid_prog->last_slot_verified==0UL ); + + /* "Upgrade" the program by modifying the programdata */ + update_account_data( &test_program_pubkey, bigger_valid_program_data, bigger_valid_program_data_sz ); + + /* Queue the program for reverification */ + ulong original_slot = fd_bank_slot_get( test_bank ); + fd_progcache_invalidate( test_cache, &test_xid, &test_program_pubkey, original_slot, 1UL ); + + /* Advance slot number */ + fd_bank_slot_set( test_bank, fd_bank_slot_get( test_bank ) + 1UL ); + ulong future_slot = fd_bank_slot_get( test_bank ); + FD_TEST( future_slot>original_slot ); + + /* Verify the cache entry was updated with the future slot as last_slot_modified */ + valid_prog = fd_progcache_peek( test_cache, &test_xid, &test_program_pubkey ); + FD_TEST( valid_prog ); + FD_TEST( valid_prog->executable==0 ); + FD_TEST( valid_prog->last_slot_modified==original_slot ); + FD_TEST( valid_prog->last_slot_verified==original_slot ); + + /* Store the old program cache funk record size */ + fd_funk_rec_key_t id; memcpy( &id, &test_program_pubkey, 32UL ); + fd_funk_rec_query_t query[1]; + fd_funk_rec_t const * prev_rec = fd_funk_rec_query_try_global( test_cache->funk, &test_xid, &id, NULL, query ); + FD_TEST( prev_rec ); + ulong prev_rec_sz = prev_rec->val_sz; + FD_TEST( !fd_funk_rec_query_test( query ) ); + + /* Program invoked, update cache entry */ + fd_progcache_pull( test_cache, test_accdb, &test_xid, &test_program_pubkey, 1UL, test_bank ); + + /* Get the new program cache funk record size, and make sure it's + larger */ + fd_funk_rec_t const * new_rec = fd_funk_rec_query_try_global( test_cache->funk, &test_xid, &id, NULL, query ); + FD_TEST( new_rec ); + ulong new_rec_sz = new_rec->val_sz; + FD_TEST( new_rec_sz>prev_rec_sz ); + FD_TEST( !fd_funk_rec_query_test( query ) ); + + /* Verify the cache entry was updated */ + valid_prog = fd_progcache_peek( test_cache, &test_xid, &test_program_pubkey ); + FD_TEST( valid_prog ); + FD_TEST( valid_prog->executable==1 ); + FD_TEST( valid_prog->last_slot_verified==future_slot ); + FD_TEST( valid_prog->last_slot_modified==original_slot ); + + test_funk_txn_cancel(); +} + +/* Test 12: Rooted program */ + +static void +test_program_rooted( void ) { + FD_LOG_INFO(( "Testing: Rooted program" )); + + test_xid = test_funk_txn_create(); + fd_bank_slot_set( test_bank, 0UL ); + + /* Create a BPF loader account */ + create_test_account( &test_xid, + &test_program_pubkey, + &fd_solana_bpf_loader_program_id, + valid_program_data, + valid_program_data_sz, + 1 ); + + fd_funk_txn_publish( test_accdb, &test_xid ); + fd_funk_txn_publish( test_cache->funk, &test_xid ); + + /* Fill cache on rooted fork */ + FD_TEST( fd_progcache_pull( test_cache, test_accdb, &test_xid, &test_program_pubkey, 1UL, test_bank ) ); + FD_TEST( fd_funk_txn_xid_eq( fd_funk_last_publish( test_cache->funk ), &test_xid ) ); + FD_TEST( fd_funk_txn_xid_eq( fd_funk_last_publish( test_accdb ), &test_xid ) ); + + /* Verify cache entry was created */ + fd_progcache_rec_t const * valid_prog = fd_progcache_peek( test_cache, &test_xid, &test_program_pubkey ); + FD_TEST( valid_prog ); + FD_TEST( valid_prog->executable==1 ); + FD_TEST( valid_prog->last_slot_modified==0UL ); + FD_TEST( valid_prog->last_slot_verified==0UL ); + + fd_funk_txn_cancel_all( test_accdb ); + fd_funk_txn_remove_published( test_accdb ); + + /* Verify that cache entry is removed by reset */ + fd_progcache_reset( test_cache ); + FD_TEST( fd_progcache_peek( test_cache, &test_xid, &test_program_pubkey )==NULL ); + fd_progcache_clear( test_cache ); + FD_TEST( fd_progcache_peek( test_cache, fd_funk_last_publish( test_cache->funk ), &test_program_pubkey )==NULL ); +} + +/* Test 13: Epoch boundary */ + +static int +find_rec( fd_progcache_t * cache, + fd_funk_txn_xid_t const * xid, + fd_pubkey_t const * pubkey, + fd_progcache_rec_t const * expected ) { + fd_funk_rec_key_t id; memcpy( &id, pubkey, 32UL ); + fd_funk_rec_query_t query[1]; + fd_funk_rec_t const * rec = fd_funk_rec_query_try_global( cache->funk, xid, &id, NULL, query ); + if( !rec ) return 0; + if( fd_funk_rec_query_test( query ) ) return 0; + return fd_funk_txn_xid_eq( rec->pair.xid, xid ) && fd_wksp_laddr_fast( cache->funk->wksp, rec->val_gaddr )==expected; +} + +static void +bank_slot_set( fd_bank_t * bank, ulong slot ) { + fd_bank_slot_set( bank, slot ); + fd_bank_epoch_set( bank, slot / 432000UL ); +} + +static void +test_epoch_boundary( void ) { + FD_LOG_INFO(( "Testing: Epoch boundary" )); + + /* Fork graph: + A -> B -> C # -> D -> G + # -> H + -> E # -> F + + '#' marks an epoch boundary + 'A' is the last published (root) + Program is created at 'B' + Feature activations at 'C' and 'E' respectively */ + + fd_funk_txn_xid_t const fork_a = { .ul={ 863997UL, 1UL } }; + fd_funk_txn_xid_t const fork_b = { .ul={ 863998UL, 2UL } }; + fd_funk_txn_xid_t const fork_c = { .ul={ 863999UL, 3UL } }; + fd_funk_txn_xid_t const fork_d = { .ul={ 864000UL, 4UL } }; + fd_funk_txn_xid_t const fork_e = { .ul={ 863999UL, 5UL } }; + fd_funk_txn_xid_t const fork_f = { .ul={ 864001UL, 6UL } }; + fd_funk_txn_xid_t const fork_g = { .ul={ 864002UL, 7UL } }; + fd_funk_txn_xid_t const fork_h = { .ul={ 864003UL, 8UL } }; + + fd_funk_txn_prepare( test_accdb, fd_funk_last_publish( test_accdb ), &fork_a ); + fd_funk_txn_prepare( test_cache->funk, fd_funk_last_publish( test_cache->funk ), &fork_a ); + fd_funk_txn_publish( test_accdb, &fork_a ); + fd_funk_txn_publish( test_cache->funk, &fork_a ); + + fd_funk_txn_prepare( test_accdb, &fork_a, &fork_b ); + fd_funk_txn_prepare( test_cache->funk, &fork_a, &fork_b ); + create_test_account( &fork_b, + &test_program_pubkey, + &fd_solana_bpf_loader_program_id, + valid_program_data, + valid_program_data_sz, + 1 ); + + fd_funk_txn_prepare( test_accdb, &fork_b, &fork_c ); + fd_funk_txn_prepare( test_cache->funk, &fork_b, &fork_c ); + fd_funk_txn_prepare( test_accdb, &fork_b, &fork_e ); + fd_funk_txn_prepare( test_cache->funk, &fork_b, &fork_e ); + fd_funk_txn_prepare( test_accdb, &fork_c, &fork_d ); + fd_funk_txn_prepare( test_cache->funk, &fork_c, &fork_d ); + fd_funk_txn_prepare( test_accdb, &fork_e, &fork_f ); + fd_funk_txn_prepare( test_cache->funk, &fork_e, &fork_f ); + fd_funk_txn_prepare( test_accdb, &fork_d, &fork_g ); + fd_funk_txn_prepare( test_cache->funk, &fork_d, &fork_g ); + fd_funk_txn_prepare( test_accdb, &fork_d, &fork_h ); + fd_funk_txn_prepare( test_cache->funk, &fork_d, &fork_h ); + + /* Pull program from fork G, should create cache entry at D */ + FD_TEST( !fd_progcache_peek( test_cache, &fork_d, &test_program_pubkey ) ); + bank_slot_set( test_bank, fork_g.ul[0] ); + FD_TEST( fd_bank_epoch_get( test_bank )==2UL ); + fd_progcache_rec_t const * rec_d = fd_progcache_pull( test_cache, test_accdb, &fork_g, &test_program_pubkey, 1UL, test_bank ); + FD_TEST( find_rec( test_cache, &fork_d, &test_program_pubkey, rec_d ) ); + FD_TEST( fd_progcache_peek( test_cache, &fork_d, &test_program_pubkey )==rec_d ); + + /* Pull program from fork H, should reuse existing cache entry */ + FD_TEST( fd_progcache_peek( test_cache, &fork_h, &test_program_pubkey )==rec_d ); + + /* Pull program from fork F, should create cache entry at F */ + FD_TEST( !fd_progcache_peek( test_cache, &fork_f, &test_program_pubkey ) ); + bank_slot_set( test_bank, fork_f.ul[0] ); + FD_TEST( fd_bank_epoch_get( test_bank )==2UL ); + fd_progcache_rec_t const * rec_f = fd_progcache_pull( test_cache, test_accdb, &fork_f, &test_program_pubkey, 1UL, test_bank ); + FD_TEST( find_rec( test_cache, &fork_f, &test_program_pubkey, rec_f ) ); + FD_TEST( fd_progcache_peek( test_cache, &fork_f, &test_program_pubkey )==rec_f ); + + /* Pull program from fork D, should reuse existing cache entry */ + FD_TEST( fd_progcache_peek( test_cache, &fork_d, &test_program_pubkey )==rec_d ); + + /* Pull program from fork E, should create cache entry at B */ + FD_TEST( !fd_progcache_peek( test_cache, &fork_e, &test_program_pubkey ) ); + bank_slot_set( test_bank, fork_e.ul[0] ); + FD_TEST( fd_bank_epoch_get( test_bank )==1UL ); + fd_progcache_rec_t const * rec_b = fd_progcache_pull( test_cache, test_accdb, &fork_e, &test_program_pubkey, 1UL, test_bank ); + FD_TEST( find_rec( test_cache, &fork_b, &test_program_pubkey, rec_b ) ); + FD_TEST( fd_progcache_peek( test_cache, &fork_e, &test_program_pubkey )==rec_b ); + + /* Root fork B */ + fd_funk_txn_publish( test_accdb, &fork_b ); + fd_funk_txn_publish( test_cache->funk, &fork_b ); + + /* Pull program from fork C, should reuse existing cache entry */ + FD_TEST( fd_progcache_peek( test_cache, &fork_c, &test_program_pubkey )==rec_b ); + + /* Root fork D */ + fd_funk_txn_publish( test_accdb, &fork_d ); + fd_funk_txn_publish( test_cache->funk, &fork_d ); + + /* Pull program from fork G, should reuse existing cache entry */ + FD_TEST( fd_progcache_peek( test_cache, &fork_g, &test_program_pubkey )==rec_d ); +} + +int +main( int argc, + char ** argv ) { + fd_boot( &argc, &argv ); + + /* Create workspace */ + test_wksp = fd_wksp_new_anonymous( FD_SHMEM_GIGANTIC_PAGE_SZ, 2UL, fd_log_cpu_id(), "test_wksp", 0UL ); + FD_TEST( test_wksp ); + + ulong accdb_rec_max = 128UL; + ulong accdb_txn_max = 16UL; + + ulong progcache_rec_max = 64UL; + ulong progcache_txn_max = 16UL; + + /* Create funk */ + void * accdb_mem = fd_wksp_alloc_laddr( test_wksp, fd_funk_align(), fd_funk_footprint( accdb_txn_max, accdb_rec_max ), TEST_WKSP_TAG ); + FD_TEST( accdb_mem ); + + void * progcache_mem = fd_wksp_alloc_laddr( test_wksp, fd_funk_align(), fd_funk_footprint( progcache_txn_max, progcache_rec_max ), TEST_WKSP_TAG ); + FD_TEST( progcache_mem ); + + void * shaccdb = fd_funk_new( accdb_mem, 1234UL, 5678UL, accdb_txn_max, accdb_rec_max ); + FD_TEST( shaccdb ); + + void * shprogcache = fd_funk_new( progcache_mem, 1235UL, 5679UL, progcache_txn_max, progcache_rec_max ); + FD_TEST( shprogcache ); + + fd_funk_t funk_[1]; + test_accdb = fd_funk_join( funk_, shaccdb ); + FD_TEST( test_accdb ); + + fd_progcache_t progcache_[1]; + test_cache = fd_progcache_join( progcache_, shprogcache ); + FD_TEST( test_cache ); + + /* Set up bank */ + ulong banks_footprint = fd_banks_footprint( 1UL, 1UL ); + uchar * banks_mem = fd_wksp_alloc_laddr( test_wksp, fd_banks_align(), banks_footprint, TEST_WKSP_TAG ); + FD_TEST( banks_mem ); + + fd_banks_t * banks = fd_banks_join( fd_banks_new( banks_mem, 1UL, 1UL ) ); + FD_TEST( banks ); + fd_bank_t * bank = fd_banks_init_bank( banks ); + fd_bank_slot_set( bank, 433000UL ); + FD_TEST( bank ); + + test_bank = bank; + test_banks = banks; + + fd_epoch_schedule_t epoch_schedule = { + .slots_per_epoch = 432000UL, + .leader_schedule_slot_offset = 432000UL, + .warmup = 0, + .first_normal_epoch = 0UL, + .first_normal_slot = 0UL + }; + fd_bank_epoch_schedule_set( bank, epoch_schedule ); + + test_account_does_not_exist(); + test_account_not_bpf_loader_owner(); + test_invalid_program_not_in_cache_first_time(); + test_valid_program_not_in_cache_first_time(); + test_program_in_cache_needs_reverification(); + test_program_in_cache_queued_for_reverification(); + test_program_queued_for_reverification_account_does_not_exist(); + test_program_in_cache_queued_for_reverification_and_processed(); + test_invalid_genesis_program_reverified_after_genesis(); + test_valid_genesis_program_reverified_after_genesis(); + test_program_upgraded_with_larger_programdata(); + test_program_rooted(); + test_epoch_boundary(); + + test_teardown(); + + FD_LOG_NOTICE(( "pass" )); + fd_halt(); + return 0; +} + +#undef TEST_WKSP_TAG + +#else + +int +main( int argc, + char ** argv ) { + fd_boot( &argc, &argv ); + FD_LOG_WARNING(( "skip: unit test requires FD_HAS_HOSTED capabilities" )); + fd_halt(); + return 0; +} + +#endif diff --git a/src/flamenco/runtime/context/fd_exec_txn_ctx.h b/src/flamenco/runtime/context/fd_exec_txn_ctx.h index a37182a3403..23d58c0f7af 100644 --- a/src/flamenco/runtime/context/fd_exec_txn_ctx.h +++ b/src/flamenco/runtime/context/fd_exec_txn_ctx.h @@ -8,6 +8,7 @@ #include "../fd_txncache.h" #include "../fd_bank_hash_cmp.h" #include "../../../funk/fd_funk.h" +#include "../../progcache/fd_progcache.h" #include "../fd_compute_budget_details.h" #include "../../../disco/pack/fd_microblock.h" @@ -55,6 +56,8 @@ struct fd_exec_txn_ctx { int enable_exec_recording; fd_bank_hash_cmp_t * bank_hash_cmp; fd_funk_t funk[1]; + fd_progcache_t * progcache; + fd_progcache_t _progcache[1]; fd_funk_txn_xid_t xid[1]; ulong slot; ulong bank_idx; diff --git a/src/flamenco/runtime/fd_acc_mgr.h b/src/flamenco/runtime/fd_acc_mgr.h index a427b6b4d34..b719186a6a9 100644 --- a/src/flamenco/runtime/fd_acc_mgr.h +++ b/src/flamenco/runtime/fd_acc_mgr.h @@ -92,18 +92,9 @@ FD_FN_PURE static inline fd_funk_rec_key_t fd_funk_acc_key( fd_pubkey_t const * pubkey ) { fd_funk_rec_key_t key = {0}; memcpy( key.uc, pubkey, sizeof(fd_pubkey_t) ); - key.uc[ FD_FUNK_REC_KEY_FOOTPRINT - 1 ] = FD_FUNK_KEY_TYPE_ACC; return key; } -/* fd_funk_key_is_acc returns 1 if given fd_funk key is an account - and 0 otherwise. */ - -FD_FN_PURE static inline int -fd_funk_key_is_acc( fd_funk_rec_key_t const * id ) { - return id->uc[ FD_FUNK_REC_KEY_FOOTPRINT - 1 ] == FD_FUNK_KEY_TYPE_ACC; -} - /* Account Access from Funk APIs *************************************************/ /* The following fd_funk_acc_mgr APIs translate between the runtime account DB abstraction diff --git a/src/flamenco/runtime/fd_core_bpf_migration.c b/src/flamenco/runtime/fd_core_bpf_migration.c index 24a41f9733b..7fbdf5cdf85 100644 --- a/src/flamenco/runtime/fd_core_bpf_migration.c +++ b/src/flamenco/runtime/fd_core_bpf_migration.c @@ -424,7 +424,7 @@ fd_migrate_builtin_to_core_bpf( fd_bank_t * bank, /* Deploy the new target Core BPF program. https://github.com/anza-xyz/agave/blob/v2.1.0/runtime/src/bank/builtins/core_bpf_migration/mod.rs#L268-L271 */ err = fd_directly_invoke_loader_v3_deploy( bank, - funk, + funk->shmem, &migration_xid, builtin_program_id, fd_txn_account_get_data( new_target_program_data_account ) + PROGRAMDATA_METADATA_SIZE, diff --git a/src/flamenco/runtime/fd_executor.c b/src/flamenco/runtime/fd_executor.c index 477826d9971..bcf60c3e5d8 100644 --- a/src/flamenco/runtime/fd_executor.c +++ b/src/flamenco/runtime/fd_executor.c @@ -20,7 +20,6 @@ #include "program/fd_vote_program.h" #include "program/fd_zk_elgamal_proof_program.h" #include "sysvar/fd_sysvar_cache.h" -#include "program/fd_program_cache.h" #include "sysvar/fd_sysvar_epoch_schedule.h" #include "sysvar/fd_sysvar_instructions.h" #include "sysvar/fd_sysvar_rent.h" @@ -29,8 +28,6 @@ #include "tests/fd_dump_pb.h" #include "../../ballet/base58/fd_base58.h" -#include "../../disco/pack/fd_pack.h" -#include "../../disco/pack/fd_pack_cost.h" #include "../../util/bits/fd_uwide.h" @@ -1369,12 +1366,22 @@ fd_executor_reclaim_account( fd_exec_txn_ctx_t * txn_ctx, void fd_exec_txn_ctx_setup( fd_bank_t * bank, - fd_funk_t * funk, + void * accdb_shfunk, + void * progcache_shfunk, fd_funk_txn_xid_t const * xid, fd_txncache_t * status_cache, fd_exec_txn_ctx_t * ctx, fd_bank_hash_cmp_t * bank_hash_cmp ) { - ctx->funk[0] = *funk; + if( FD_UNLIKELY( !fd_funk_join( ctx->funk, accdb_shfunk ) ) ) { + FD_LOG_CRIT(( "fd_funk_join(accdb) failed" )); + } + + if( progcache_shfunk ) { + ctx->progcache = fd_progcache_join( ctx->_progcache, progcache_shfunk ); + if( FD_UNLIKELY( !ctx->progcache ) ) { + FD_LOG_CRIT(( "fd_progcache_join() failed" )); + } + } ctx->xid[0] = *xid; diff --git a/src/flamenco/runtime/fd_executor.h b/src/flamenco/runtime/fd_executor.h index b2866c8a257..f6dfc711f51 100644 --- a/src/flamenco/runtime/fd_executor.h +++ b/src/flamenco/runtime/fd_executor.h @@ -134,7 +134,8 @@ fd_instr_stack_pop( fd_exec_txn_ctx_t * txn_ctx, void fd_exec_txn_ctx_setup( fd_bank_t * bank, - fd_funk_t * funk, + void * accdb_shfunk, + void * progcache_shfunk, fd_funk_txn_xid_t const * xid, fd_txncache_t * status_cache, fd_exec_txn_ctx_t * ctx, diff --git a/src/flamenco/runtime/fd_runtime.c b/src/flamenco/runtime/fd_runtime.c index f96fbd5d499..85b1e429a45 100644 --- a/src/flamenco/runtime/fd_runtime.c +++ b/src/flamenco/runtime/fd_runtime.c @@ -15,13 +15,13 @@ #include "../stakes/fd_stakes.h" #include "../rewards/fd_rewards.h" +#include "../progcache/fd_progcache.h" #include "context/fd_exec_txn_ctx.h" #include "program/fd_stake_program.h" #include "program/fd_builtin_programs.h" #include "program/fd_vote_program.h" -#include "program/fd_program_cache.h" #include "program/fd_bpf_loader_program.h" #include "program/fd_address_lookup_table_program.h" @@ -1087,6 +1087,7 @@ fd_runtime_save_account( fd_funk_t * funk, void fd_runtime_finalize_txn( fd_funk_t * funk, + fd_progcache_t * progcache, fd_txncache_t * txncache, fd_funk_txn_xid_t const * xid, fd_exec_txn_ctx_t * txn_ctx, @@ -1161,7 +1162,7 @@ fd_runtime_finalize_txn( fd_funk_t * funk, ulong current_slot = fd_bank_slot_get( bank ); for( uchar i=0; iprograms_to_reverify_cnt; i++ ) { fd_pubkey_t const * program_key = &txn_ctx->programs_to_reverify[i]; - fd_program_cache_queue_program_for_reverification( funk, xid, program_key, current_slot ); + fd_progcache_invalidate( progcache, xid, program_key, current_slot, 1UL ); } } @@ -1597,6 +1598,7 @@ fd_runtime_process_new_epoch( fd_banks_t * banks, void fd_runtime_update_program_cache( fd_bank_t * bank, + fd_progcache_t * progcache, fd_funk_t * funk, fd_funk_txn_xid_t const * xid, fd_txn_p_t const * txn_p, @@ -1609,7 +1611,7 @@ fd_runtime_update_program_cache( fd_bank_t * bank, fd_acct_addr_t const * acc_addrs = fd_txn_get_acct_addrs( txn_descriptor, txn_p ); for( ushort acc_idx=0; acc_idxacct_addr_cnt; acc_idx++ ) { fd_pubkey_t const * account = fd_type_pun_const( &acc_addrs[acc_idx] ); - fd_program_cache_update_program( bank, funk, xid, account, runtime_spad ); + fd_progcache_pull( progcache, funk, xid, account, 1UL, bank ); } if( txn_descriptor->transaction_version==FD_TXN_V0 ) { @@ -1641,7 +1643,7 @@ fd_runtime_update_program_cache( fd_bank_t * bank, for( ushort alut_idx=0; alut_idxaddr_table_adtl_cnt; alut_idx++ ) { fd_pubkey_t const * account = fd_type_pun_const( &alut_accounts[alut_idx] ); - fd_program_cache_update_program( bank, funk, xid, account, runtime_spad ); + fd_progcache_pull( progcache, funk, xid, account, 1UL, bank ); } } diff --git a/src/flamenco/runtime/fd_runtime.h b/src/flamenco/runtime/fd_runtime.h index 5661c9a3807..f78c78fd2a7 100644 --- a/src/flamenco/runtime/fd_runtime.h +++ b/src/flamenco/runtime/fd_runtime.h @@ -467,6 +467,7 @@ fd_runtime_prepare_and_execute_txn( fd_banks_t * banks, void fd_runtime_finalize_txn( fd_funk_t * funk, + fd_progcache_t * progcache, fd_txncache_t * txncache, fd_funk_txn_xid_t const * xid, fd_exec_txn_ctx_t * txn_ctx, @@ -494,7 +495,7 @@ fd_runtime_block_pre_execute_process_new_epoch( fd_banks_t * banks, /* `fd_runtime_update_program_cache()` is responsible for updating the program cache with any programs referenced in the current - transaction. See fd_program_cache.h for more details. + transaction. Note that ALUTs must be resolved because programs referenced in ALUTs can be invoked via CPI. @@ -503,6 +504,7 @@ fd_runtime_block_pre_execute_process_new_epoch( fd_banks_t * banks, because it is redundant (ALUTs get resolved again in the exec tile). */ void fd_runtime_update_program_cache( fd_bank_t * bank, + fd_progcache_t * progcache, fd_funk_t * funk, fd_funk_txn_xid_t const * xid, fd_txn_p_t const * txn_p, diff --git a/src/flamenco/runtime/program/Local.mk b/src/flamenco/runtime/program/Local.mk index cc9d5c05295..a9ff544f868 100644 --- a/src/flamenco/runtime/program/Local.mk +++ b/src/flamenco/runtime/program/Local.mk @@ -8,9 +8,6 @@ $(call add-objs,fd_builtin_programs,fd_flamenco) $(call add-hdrs,fd_bpf_loader_serialization.h) $(call add-objs,fd_bpf_loader_serialization,fd_flamenco) -$(call add-hdrs,fd_program_cache.h) -$(call add-objs,fd_program_cache,fd_flamenco) - ### Precompiles $(call add-hdrs,fd_precompiles.h) @@ -48,13 +45,4 @@ $(call add-objs,fd_zk_elgamal_proof_program,fd_flamenco) $(call add-hdrs,fd_native_cpi.h) $(call add-objs,fd_native_cpi,fd_flamenco) -### Tests -ifdef FD_HAS_HOSTED -ifdef FD_HAS_SECP256K1 -$(call make-unit-test,test_program_cache,test_program_cache,fd_flamenco fd_ballet fd_funk fd_util) -$(call run-unit-test,test_program_cache) -$(call make-unit-test,test_program_cache_concur,test_program_cache_concur,fd_flamenco fd_vm fd_funk fd_ballet fd_util) -endif -endif - endif diff --git a/src/flamenco/runtime/program/fd_bpf_loader_program.c b/src/flamenco/runtime/program/fd_bpf_loader_program.c index 6af66b42365..9716e0217d2 100644 --- a/src/flamenco/runtime/program/fd_bpf_loader_program.c +++ b/src/flamenco/runtime/program/fd_bpf_loader_program.c @@ -2,6 +2,7 @@ /* For additional context see https://solana.com/docs/programs/deploying#state-accounts */ +#include "../../progcache/fd_prog_load.h" #include "../fd_pubkey_utils.h" #include "../../../ballet/sbpf/fd_sbpf_loader.h" #include "../sysvar/fd_sysvar.h" @@ -92,30 +93,6 @@ calculate_heap_cost( ulong heap_size, ulong heap_cost ) { #undef KIBIBYTE_MUL_PAGES_SUB_1 } -void -fd_bpf_get_sbpf_versions( uint * sbpf_min_version, - uint * sbpf_max_version, - ulong slot, - fd_features_t const * features ) { - int disable_v0 = FD_FEATURE_ACTIVE( slot, features, disable_sbpf_v0_execution ); - int reenable_v0 = FD_FEATURE_ACTIVE( slot, features, reenable_sbpf_v0_execution ); - int enable_v0 = !disable_v0 || reenable_v0; - int enable_v1 = FD_FEATURE_ACTIVE( slot, features, enable_sbpf_v1_deployment_and_execution ); - int enable_v2 = FD_FEATURE_ACTIVE( slot, features, enable_sbpf_v2_deployment_and_execution ); - int enable_v3 = FD_FEATURE_ACTIVE( slot, features, enable_sbpf_v3_deployment_and_execution ); - - *sbpf_min_version = enable_v0 ? FD_SBPF_V0 : FD_SBPF_V3; - if( enable_v3 ) { - *sbpf_max_version = FD_SBPF_V3; - } else if( enable_v2 ) { - *sbpf_max_version = FD_SBPF_V2; - } else if( enable_v1 ) { - *sbpf_max_version = FD_SBPF_V1; - } else { - *sbpf_max_version = FD_SBPF_V0; - } -} - /* https://github.com/anza-xyz/agave/blob/574bae8fefc0ed256b55340b9d87b7689bcdf222/programs/bpf_loader/src/lib.rs#L105-L171 Our arguments to deploy_program are different from the Agave version because @@ -160,16 +137,12 @@ fd_deploy_program( fd_exec_instr_ctx_t * instr_ctx, /* Load executable */ fd_sbpf_elf_info_t elf_info[ 1UL ]; - uint min_sbpf_version, max_sbpf_version; - fd_bpf_get_sbpf_versions( &min_sbpf_version, - &max_sbpf_version, - instr_ctx->txn_ctx->slot, - &instr_ctx->txn_ctx->features ); + fd_prog_versions_t versions = fd_prog_versions( &instr_ctx->txn_ctx->features, instr_ctx->txn_ctx->slot ); fd_sbpf_loader_config_t config = { 0 }; config.elf_deploy_checks = deploy_mode; - config.sbpf_min_version = min_sbpf_version; - config.sbpf_max_version = max_sbpf_version; + config.sbpf_min_version = versions.min_sbpf_version; + config.sbpf_max_version = versions.max_sbpf_version; if( FD_UNLIKELY( fd_sbpf_elf_peek( elf_info, programdata, programdata_size, &config )<0 ) ) { //TODO: actual log, this is a custom Firedancer msg @@ -392,9 +365,9 @@ common_close_account( fd_pubkey_t * authority_address, https://github.com/anza-xyz/agave/blob/574bae8fefc0ed256b55340b9d87b7689bcdf222/programs/bpf_loader/src/lib.rs#L1332-L1501 */ int -fd_bpf_execute( fd_exec_instr_ctx_t * instr_ctx, - fd_program_cache_entry_t const * cache_entry, - uchar is_deprecated ) { +fd_bpf_execute( fd_exec_instr_ctx_t * instr_ctx, + fd_progcache_rec_t const * cache_entry, + uchar is_deprecated ) { int err = FD_EXECUTOR_INSTR_SUCCESS; fd_sbpf_syscalls_t * syscalls = fd_sbpf_syscalls_new( fd_spad_alloc( instr_ctx->txn_ctx->spad, @@ -411,12 +384,12 @@ fd_bpf_execute( fd_exec_instr_ctx_t * instr_ctx, 0 ); /* https://github.com/anza-xyz/agave/blob/574bae8fefc0ed256b55340b9d87b7689bcdf222/programs/bpf_loader/src/lib.rs#L1362-L1368 */ - ulong input_sz = 0UL; - ulong pre_lens[256] = {0}; - fd_vm_input_region_t input_mem_regions[1000] = {0}; /* We can have a max of (3 * num accounts + 1) regions */ - fd_vm_acc_region_meta_t acc_region_metas[256] = {0}; /* instr acc idx to idx */ - uint input_mem_regions_cnt = 0U; - int direct_mapping = FD_FEATURE_ACTIVE_BANK( instr_ctx->txn_ctx->bank, bpf_account_data_direct_mapping ); + ulong input_sz = 0UL; + ulong pre_lens[256] = {0}; + fd_vm_input_region_t input_mem_regions[1000] = {0}; /* We can have a max of (3 * num accounts + 1) regions */ + fd_vm_acc_region_meta_t acc_region_metas[256] = {0}; /* instr acc idx to idx */ + uint input_mem_regions_cnt = 0U; + int direct_mapping = FD_FEATURE_ACTIVE_BANK( instr_ctx->txn_ctx->bank, bpf_account_data_direct_mapping ); uchar * input = NULL; err = fd_bpf_loader_input_serialize_parameters( instr_ctx, &input_sz, pre_lens, @@ -458,14 +431,14 @@ fd_bpf_execute( fd_exec_instr_ctx_t * instr_ctx, /* instr_ctx */ instr_ctx, /* heap_max */ heap_size, /* entry_cu */ instr_ctx->txn_ctx->compute_budget_details.compute_meter, - /* rodata */ fd_program_cache_get_rodata( cache_entry ), + /* rodata */ fd_progcache_rec_rodata( cache_entry ), /* rodata_sz */ cache_entry->rodata_sz, - /* text */ (ulong *)((ulong)fd_program_cache_get_rodata( cache_entry ) + (ulong)cache_entry->text_off), /* Note: text_off is byte offset */ + /* text */ (ulong *)((ulong)fd_progcache_rec_rodata( cache_entry ) + (ulong)cache_entry->text_off), /* Note: text_off is byte offset */ /* text_cnt */ cache_entry->text_cnt, /* text_off */ cache_entry->text_off, /* text_sz */ cache_entry->text_sz, /* entry_pc */ cache_entry->entry_pc, - /* calldests */ fd_program_cache_get_calldests( cache_entry ), + /* calldests */ fd_progcache_rec_calldests( cache_entry ), /* sbpf_version */ cache_entry->sbpf_version, /* syscalls */ syscalls, /* trace */ NULL, @@ -2567,11 +2540,9 @@ fd_bpf_loader_program_execute( fd_exec_instr_ctx_t * ctx ) { could also theoretically cause some currently-deployed programs to fail in the future if ELF / VM checks are eventually made stricter. */ - fd_program_cache_entry_t const * cache_entry = NULL; - if( FD_UNLIKELY( fd_program_cache_load_entry( ctx->txn_ctx->funk, - ctx->txn_ctx->xid, - program_id, - &cache_entry )!=0 ) ) { + fd_progcache_rec_t const * cache_entry = + fd_progcache_peek( ctx->txn_ctx->progcache, ctx->txn_ctx->xid, program_id ); + if( FD_UNLIKELY( !cache_entry ) ) { fd_log_collector_msg_literal( ctx, "Program is not cached" ); /* https://github.com/anza-xyz/agave/blob/89872fdb074e6658646b2b57a299984f0059cc84/programs/bpf_loader/src/lib.rs#L460-L467 */ @@ -2582,7 +2553,7 @@ fd_bpf_loader_program_execute( fd_exec_instr_ctx_t * ctx ) { } /* The program may be in the cache but could have failed verification in the current epoch. */ - if( FD_UNLIKELY( cache_entry->failed_verification ) ) { + if( FD_UNLIKELY( cache_entry->executable==0 ) ) { fd_log_collector_msg_literal( ctx, "Program is not deployed" ); if( FD_FEATURE_ACTIVE_BANK( ctx->txn_ctx->bank, remove_accounts_executable_flag_checks ) ) { return FD_EXECUTOR_INSTR_ERR_UNSUPPORTED_PROGRAM_ID; @@ -2602,7 +2573,7 @@ fd_bpf_loader_program_execute( fd_exec_instr_ctx_t * ctx ) { int fd_directly_invoke_loader_v3_deploy( fd_bank_t * bank, - fd_funk_t * funk, + void * accdb_shfunk, fd_funk_txn_xid_t const * xid, fd_pubkey_t const * program_key, uchar const * elf, @@ -2612,7 +2583,8 @@ fd_directly_invoke_loader_v3_deploy( fd_bank_t * bank, fd_exec_txn_ctx_t * txn_ctx = fd_exec_txn_ctx_join( fd_exec_txn_ctx_new( fd_spad_alloc( runtime_spad, FD_EXEC_TXN_CTX_ALIGN, FD_EXEC_TXN_CTX_FOOTPRINT ) ), runtime_spad, fd_wksp_containing( runtime_spad ) ); fd_exec_txn_ctx_setup( bank, - funk, + accdb_shfunk, + NULL, xid, NULL, txn_ctx, diff --git a/src/flamenco/runtime/program/fd_bpf_loader_program.h b/src/flamenco/runtime/program/fd_bpf_loader_program.h index c79ea328cfb..547921df0de 100644 --- a/src/flamenco/runtime/program/fd_bpf_loader_program.h +++ b/src/flamenco/runtime/program/fd_bpf_loader_program.h @@ -6,9 +6,10 @@ Address: BPFLoaderUpgradeab1e11111111111111111111111 */ -#include "fd_program_cache.h" +#include "../../progcache/fd_progcache_rec.h" #include "../../features/fd_features.h" #include "../../types/fd_types.h" +#include "../../../funk/fd_funk_base.h" /* https://github.com/anza-xyz/agave/blob/77daab497df191ef485a7ad36ed291c1874596e5/programs/bpf_loader/src/lib.rs#L67-L69 */ #define DEFAULT_LOADER_COMPUTE_UNITS (570UL ) @@ -65,12 +66,6 @@ fd_bpf_loader_program_get_state( fd_txn_account_t const * acct, fd_spad_t * spad, int * opt_err ); -void -fd_bpf_get_sbpf_versions( uint * sbpf_min_version, - uint * sbpf_max_version, - ulong slot, - fd_features_t const * features ); - int fd_deploy_program( fd_exec_instr_ctx_t * instr_ctx, fd_pubkey_t const * program_key, @@ -79,9 +74,9 @@ fd_deploy_program( fd_exec_instr_ctx_t * instr_ctx, fd_spad_t * spad ); int -fd_bpf_execute( fd_exec_instr_ctx_t * instr_ctx, - fd_program_cache_entry_t const * cache_entry, - uchar is_deprecated ); +fd_bpf_execute( fd_exec_instr_ctx_t * instr_ctx, + fd_progcache_rec_t const * program, + uchar is_deprecated ); int fd_bpf_loader_program_execute( fd_exec_instr_ctx_t * instr_ctx ); @@ -98,7 +93,7 @@ fd_bpf_loader_program_execute( fd_exec_instr_ctx_t * instr_ctx ); https://github.com/anza-xyz/agave/blob/v2.1.0/runtime/src/bank/builtins/core_bpf_migration/mod.rs#L155-L233 */ int fd_directly_invoke_loader_v3_deploy( fd_bank_t * bank, - fd_funk_t * funk, + void * accdb_shfunk, fd_funk_txn_xid_t const * xid, fd_pubkey_t const * program_key, uchar const * elf, diff --git a/src/flamenco/runtime/program/fd_loader_v4_program.c b/src/flamenco/runtime/program/fd_loader_v4_program.c index a3dab6a094d..03df106b28f 100644 --- a/src/flamenco/runtime/program/fd_loader_v4_program.c +++ b/src/flamenco/runtime/program/fd_loader_v4_program.c @@ -853,19 +853,18 @@ fd_loader_v4_program_execute( fd_exec_instr_ctx_t * instr_ctx ) { return rc; } - /* See note in `fd_bpf_loader_program_execute()` as to why we must tie the cache into consensus :( - https://github.com/anza-xyz/agave/blob/v2.2.6/programs/loader-v4/src/lib.rs#L522-L528 */ - fd_program_cache_entry_t const * cache_entry = NULL; - if( FD_UNLIKELY( fd_program_cache_load_entry( instr_ctx->txn_ctx->funk, - instr_ctx->txn_ctx->xid, - program_id, - &cache_entry )!=0 ) ) { + /* https://github.com/anza-xyz/agave/blob/v2.2.6/programs/loader-v4/src/lib.rs#L522-L528 */ + fd_progcache_rec_t const * cache_entry = + fd_progcache_peek( instr_ctx->txn_ctx->progcache, + instr_ctx->txn_ctx->xid, + program_id ); + if( FD_UNLIKELY( !cache_entry ) ) { fd_log_collector_msg_literal( instr_ctx, "Program is not cached" ); return FD_EXECUTOR_INSTR_ERR_UNSUPPORTED_PROGRAM_ID; } /* The program may be in the cache but could have failed verification in the current epoch. */ - if( FD_UNLIKELY( cache_entry->failed_verification ) ) { + if( FD_UNLIKELY( cache_entry->executable==0 ) ) { fd_log_collector_msg_literal( instr_ctx, "Program is not deployed" ); return FD_EXECUTOR_INSTR_ERR_UNSUPPORTED_PROGRAM_ID; } diff --git a/src/flamenco/runtime/program/fd_program_cache.c b/src/flamenco/runtime/program/fd_program_cache.c deleted file mode 100644 index 4f3a4f5091b..00000000000 --- a/src/flamenco/runtime/program/fd_program_cache.c +++ /dev/null @@ -1,681 +0,0 @@ -#include "fd_program_cache.h" -#include "fd_bpf_loader_program.h" -#include "fd_loader_v4_program.h" -#include "../../vm/fd_vm.h" -#include "../sysvar/fd_sysvar_epoch_schedule.h" -#include "../../../funk/fd_funk_rec.h" - -#include - -fd_program_cache_entry_t * -fd_program_cache_entry_new( void * mem, - fd_sbpf_elf_info_t const * elf_info, - ulong last_slot_modified, - ulong last_slot_verified ) { - fd_program_cache_entry_t * cache_entry = (fd_program_cache_entry_t *)mem; - cache_entry->magic = FD_PROGRAM_CACHE_ENTRY_MAGIC; - - /* Failed verification flag */ - cache_entry->failed_verification = 0; - - /* Last slot the program was modified */ - cache_entry->last_slot_modified = last_slot_modified; - - /* Last slot verification checks were ran for this program */ - cache_entry->last_slot_verified = last_slot_verified; - - ulong l = FD_LAYOUT_INIT; - - /* calldests backing memory */ - l = FD_LAYOUT_APPEND( l, alignof(fd_program_cache_entry_t), sizeof(fd_program_cache_entry_t) ); - cache_entry->calldests_shmem_off = l; - - /* rodata backing memory */ - l = FD_LAYOUT_APPEND( l, fd_sbpf_calldests_align(), fd_sbpf_calldests_footprint(elf_info->text_cnt) ); - cache_entry->rodata_off = l; - - /* SBPF version */ - cache_entry->sbpf_version = elf_info->sbpf_version; - - return cache_entry; -} - -/* Sets up defined fields for a record that failed verification. */ -static void -fd_program_cache_entry_set_failed_verification( void * mem, - ulong last_slot_verified ) { - fd_program_cache_entry_t * cache_entry = (fd_program_cache_entry_t *)mem; - cache_entry->magic = FD_PROGRAM_CACHE_ENTRY_MAGIC; - - /* Failed verification flag */ - cache_entry->failed_verification = 1; - - /* Last slot the program was modified */ - cache_entry->last_slot_modified = 0UL; - - /* Last slot verification checks were ran for this program */ - cache_entry->last_slot_verified = last_slot_verified; - - /* All other fields are undefined. */ -} - -ulong -fd_program_cache_entry_footprint( fd_sbpf_elf_info_t const * elf_info ) { - ulong l = FD_LAYOUT_INIT; - l = FD_LAYOUT_APPEND( l, alignof(fd_program_cache_entry_t), sizeof(fd_program_cache_entry_t) ); - l = FD_LAYOUT_APPEND( l, fd_sbpf_calldests_align(), fd_sbpf_calldests_footprint( elf_info->text_cnt ) ); - l = FD_LAYOUT_APPEND( l, 8UL, elf_info->bin_sz ); - l = FD_LAYOUT_FINI( l, alignof(fd_program_cache_entry_t) ); - return l; -} - -/* Returns the footprint of a record that failed verification. Note that - all other fields are undefined besides the `failed_verification`, - `last_slot_verified`, and `last_slot_modified` fields, so we can - just return the core struct's size. */ -static inline FD_FN_PURE ulong -fd_program_cache_failed_verification_entry_footprint( void ) { - ulong l = FD_LAYOUT_INIT; - l = FD_LAYOUT_APPEND( l, alignof(fd_program_cache_entry_t), sizeof(fd_program_cache_entry_t) ); - l = FD_LAYOUT_FINI( l, alignof(fd_program_cache_entry_t) ); - return l; -} - -fd_funk_rec_key_t -fd_program_cache_key( fd_pubkey_t const * pubkey ) { - fd_funk_rec_key_t id; - memcpy( id.uc, pubkey, sizeof(fd_pubkey_t) ); - memset( id.uc + sizeof(fd_pubkey_t), 0, sizeof(fd_funk_rec_key_t) - sizeof(fd_pubkey_t) ); - - id.uc[ FD_FUNK_REC_KEY_FOOTPRINT - 1 ] = FD_FUNK_KEY_TYPE_ELF_CACHE; - - return id; -} - -/* Parse ELF info from programdata. */ -static int -fd_program_cache_parse_elf_info( fd_bank_t * bank, - fd_sbpf_elf_info_t * elf_info, - uchar const * program_data, - ulong program_data_len ) { - uint min_sbpf_version, max_sbpf_version; - fd_bpf_get_sbpf_versions( &min_sbpf_version, - &max_sbpf_version, - fd_bank_slot_get( bank ), - fd_bank_features_query( bank ) ); - - fd_sbpf_loader_config_t config = { 0 }; - config.elf_deploy_checks = 0; - config.sbpf_min_version = min_sbpf_version; - config.sbpf_max_version = max_sbpf_version; - - if( FD_UNLIKELY( fd_sbpf_elf_peek( elf_info, program_data, program_data_len, &config )<0 ) ) { - FD_LOG_DEBUG(( "fd_sbpf_elf_peek() failed" )); - return -1; - } - return 0; -} - -/* Similar to the below function, but gets the executable program content for the v4 loader. - Unlike the v3 loader, the programdata is stored in a single program account. The program must - NOT be retracted to be added to the cache. Returns a pointer to the programdata on success, - and NULL on failure. - - Reasons for failure include: - - The program state cannot be read from the account data or is in the `retracted` state. */ -static uchar const * -fd_get_executable_program_content_for_v4_loader( fd_txn_account_t const * program_acc, - ulong * program_data_len ) { - int err; - - /* Get the current loader v4 state. This implicitly also checks the dlen. */ - fd_loader_v4_state_t const * state = fd_loader_v4_get_state( program_acc, &err ); - if( FD_UNLIKELY( err ) ) { - return NULL; - } - - /* The program must be deployed or finalized. */ - if( FD_UNLIKELY( fd_loader_v4_status_is_retracted( state ) ) ) { - return NULL; - } - - /* This subtraction is safe because get_state() implicitly checks the - dlen. */ - *program_data_len = fd_txn_account_get_data_len( program_acc )-LOADER_V4_PROGRAM_DATA_OFFSET; - return fd_txn_account_get_data( program_acc )+LOADER_V4_PROGRAM_DATA_OFFSET; -} - -/* Gets the programdata for a v3 loader-owned account by decoding the account data - as well as the programdata account. Returns a pointer to the programdata on success, - and NULL on failure. - - Reasons for failure include: - - The program account data cannot be decoded or is not in the `program` state. - - The programdata account is not large enough to hold at least `PROGRAMDATA_METADATA_SIZE` bytes. */ -static uchar const * -fd_get_executable_program_content_for_upgradeable_loader( fd_funk_t const * funk, - fd_funk_txn_xid_t const * xid, - fd_txn_account_t const * program_acc, - ulong * program_data_len ) { - fd_bpf_upgradeable_loader_state_t program_account_state[1]; - if( FD_UNLIKELY( !fd_bincode_decode_static( - bpf_upgradeable_loader_state, - program_account_state, - fd_txn_account_get_data( program_acc ), - fd_txn_account_get_data_len( program_acc ), - NULL ) ) ) { - return NULL; - } - if( !fd_bpf_upgradeable_loader_state_is_program( program_account_state ) ) { - return NULL; - } - - fd_pubkey_t * programdata_address = &program_account_state->inner.program.programdata_address; - FD_TXN_ACCOUNT_DECL( programdata_acc ); - if( fd_txn_account_init_from_funk_readonly( programdata_acc, programdata_address, funk, xid )!=FD_ACC_MGR_SUCCESS ) { - return NULL; - } - - /* We don't actually need to decode here, just make sure that the account - can be decoded successfully. */ - fd_bincode_decode_ctx_t ctx_programdata = { - .data = fd_txn_account_get_data( programdata_acc ), - .dataend = fd_txn_account_get_data( programdata_acc ) + fd_txn_account_get_data_len( programdata_acc ), - }; - - ulong total_sz = 0UL; - if( FD_UNLIKELY( fd_bpf_upgradeable_loader_state_decode_footprint( &ctx_programdata, &total_sz ) ) ) { - return NULL; - } - - if( FD_UNLIKELY( fd_txn_account_get_data_len( programdata_acc )failed_verification = 1; - return -1; - } - - /* Allocate syscalls */ - - void * syscalls_mem = fd_spad_alloc_check( runtime_spad, fd_sbpf_syscalls_align(), fd_sbpf_syscalls_footprint() ); - fd_sbpf_syscalls_t * syscalls = fd_sbpf_syscalls_join( fd_sbpf_syscalls_new( syscalls_mem ) ); - if( FD_UNLIKELY( !syscalls ) ) { - FD_LOG_CRIT(( "Call to fd_sbpf_syscalls_new() failed" )); - } - - fd_vm_syscall_register_slot( syscalls, - fd_bank_slot_get( bank ), - fd_bank_features_query( bank ), - 0 ); - - /* Load program. */ - - fd_sbpf_loader_config_t config = { 0 }; - if( FD_UNLIKELY( 0!=fd_sbpf_program_load( prog, program_data, program_data_len, syscalls, &config ) ) ) { - FD_LOG_DEBUG(( "fd_sbpf_program_load() failed" )); - cache_entry->failed_verification = 1; - fd_sbpf_syscalls_leave( syscalls ); - return -1; - } - - /* Validate the program. */ - - fd_vm_t _vm[ 1UL ]; - fd_vm_t * vm = fd_vm_join( fd_vm_new( _vm ) ); - if( FD_UNLIKELY( !vm ) ) { - FD_LOG_CRIT(( "fd_vm_new() or fd_vm_join() failed" )); - } - - int direct_mapping = FD_FEATURE_ACTIVE( fd_bank_slot_get( bank ), fd_bank_features_query( bank ), bpf_account_data_direct_mapping ); - - vm = fd_vm_init( vm, - NULL, /* OK since unused in `fd_vm_validate()` */ - 0UL, - 0UL, - prog->rodata, - prog->rodata_sz, - prog->text, - prog->info.text_cnt, - prog->info.text_off, - prog->info.text_sz, - prog->entry_pc, - prog->calldests, - elf_info->sbpf_version, - syscalls, - NULL, - NULL, - NULL, - 0U, - NULL, - 0, - direct_mapping, - 0 ); - - if( FD_UNLIKELY( !vm ) ) { - FD_LOG_CRIT(( "fd_vm_init() failed" )); - } - - int res = fd_vm_validate( vm ); - fd_sbpf_syscalls_leave( syscalls ); - if( FD_UNLIKELY( res ) ) { - FD_LOG_DEBUG(( "fd_vm_validate() failed" )); - cache_entry->failed_verification = 1; - return -1; - } - - /* FIXME: Super expensive memcpy. */ - fd_memcpy( fd_program_cache_get_calldests_shmem( cache_entry ), prog->calldests_shmem, fd_sbpf_calldests_footprint( prog->info.text_cnt ) ); - - cache_entry->entry_pc = prog->entry_pc; - cache_entry->text_off = prog->info.text_off; - cache_entry->text_cnt = prog->info.text_cnt; - cache_entry->text_sz = prog->info.text_sz; - cache_entry->rodata_sz = prog->rodata_sz; - cache_entry->failed_verification = 0; - - return 0; -} - -/* Publishes an in-prepare funk record for a program that failed - verification. Creates a default sBPF validated program with the - `failed_verification` flag set to 1 and `last_slot_verified` set to - the current slot. The passed-in funk record is expected to be in a - prepare. */ -static void -fd_program_cache_publish_failed_verification_rec( fd_funk_t * funk, - fd_funk_rec_prepare_t * prepare, - fd_funk_rec_t * rec, - ulong current_slot ) { - /* Truncate the record to have a minimal footprint */ - ulong record_sz = fd_program_cache_failed_verification_entry_footprint(); - void * data = fd_funk_val_truncate( rec, fd_funk_alloc( funk ), fd_funk_wksp( funk ), 0UL, record_sz, NULL ); - if( FD_UNLIKELY( data==NULL ) ) { - FD_LOG_ERR(( "fd_funk_val_truncate() failed to truncate record to size %lu", record_sz )); - } - - fd_program_cache_entry_set_failed_verification( data, current_slot ); - fd_funk_rec_publish( funk, prepare ); -} - -/* Validates an SBPF program and adds it to the program cache. - Reasons for verification failure include: - - The ELF info cannot be parsed or validated from the programdata. - - The sBPF program fails to be validated. - - The program will still be added to the cache even if verifications - fail. This is to prevent a DOS vector where an attacker could spam - invocations to programs that failed verification. */ -static void -fd_program_cache_create_cache_entry( fd_bank_t * bank, - fd_funk_t * funk, - fd_funk_txn_xid_t const * xid, - fd_txn_account_t const * program_acc, - fd_spad_t * runtime_spad ) { - FD_SPAD_FRAME_BEGIN( runtime_spad ) { - ulong current_slot = fd_bank_slot_get( bank ); - - /* Prepare the funk record for the program cache. */ - fd_pubkey_t const * program_pubkey = program_acc->pubkey; - fd_funk_rec_key_t id = fd_program_cache_key( program_pubkey ); - - /* Try to get the programdata for the account. If it doesn't exist, - simply return without publishing anything. The program could have - been closed, but we do not want to touch the cache in this case. */ - ulong program_data_len = 0UL; - uchar const * program_data = fd_program_cache_get_account_programdata( funk, xid, program_acc, &program_data_len ); - - /* This prepare should never fail. */ - int funk_err = FD_FUNK_SUCCESS; - fd_funk_rec_prepare_t prepare[1]; - fd_funk_rec_t * rec = fd_funk_rec_prepare( funk, xid, &id, prepare, &funk_err ); - if( rec == NULL || funk_err != FD_FUNK_SUCCESS ) { - FD_LOG_CRIT(( "fd_funk_rec_prepare() failed: %i-%s", funk_err, fd_funk_strerror( funk_err ) )); - } - - /* In Agave's load_program_with_pubkey(), if program data cannot be - obtained, a tombstone cache entry of type Closed or - FailedVerification is created. For correctness, we could just - not insert a cache entry when there is no valid program data. - Nonetheless, for purely conformance on instruction error log - messages reasons, specifically "Program is not deployed" vs - "Program is not cached", we would like to have a cache entry - precisely when Agave does, such that we match Agave exactly on - this error log. So, we insert a cache entry here. */ - if( FD_UNLIKELY( program_data==NULL ) ) { - fd_program_cache_publish_failed_verification_rec( funk, prepare, rec, current_slot ); - return; - } - - fd_sbpf_elf_info_t elf_info = {0}; - if( FD_UNLIKELY( fd_program_cache_parse_elf_info( bank, &elf_info, program_data, program_data_len ) ) ) { - fd_program_cache_publish_failed_verification_rec( funk, prepare, rec, current_slot ); - return; - } - - ulong val_sz = fd_program_cache_entry_footprint( &elf_info ); - void * val = fd_funk_val_truncate( - rec, - fd_funk_alloc( funk ), - fd_funk_wksp( funk ), - 0UL, - val_sz, - &funk_err ); - if( FD_UNLIKELY( funk_err ) ) { - FD_LOG_ERR(( "fd_funk_val_truncate(sz=%lu) for account failed (%i-%s)", val_sz, funk_err, fd_funk_strerror( funk_err ) )); - } - - /* Note that the cache entry points to the funk record data - and writes into the record directly to avoid an expensive memcpy. - Since this record is fresh, we should set the last slot modified - to 0. */ - fd_program_cache_entry_t * cache_entry = fd_program_cache_entry_new( val, &elf_info, 0UL, current_slot ); - int res = fd_program_cache_validate_sbpf_program( bank, &elf_info, program_data, program_data_len, runtime_spad, cache_entry ); - if( FD_UNLIKELY( res ) ) { - fd_program_cache_publish_failed_verification_rec( funk, prepare, rec, current_slot ); - return; - } - - fd_funk_rec_publish( funk, prepare ); - } FD_SPAD_FRAME_END; -} - -int -fd_program_cache_load_entry( fd_funk_t const * funk, - fd_funk_txn_xid_t const * xid, - fd_pubkey_t const * program_pubkey, - fd_program_cache_entry_t const ** cache_entry ) { - fd_funk_rec_key_t id = fd_program_cache_key( program_pubkey ); - - for(;;) { - fd_funk_rec_query_t query[1]; - fd_funk_rec_t const * rec = fd_funk_rec_query_try_global(funk, xid, &id, NULL, query); - - if( FD_UNLIKELY( !rec ) ) { - /* If rec is NULL, we shouldn't inspect query below because it - would contain uninitialized fields. */ - return -1; - } - - void const * data = fd_funk_val_const( rec, fd_funk_wksp(funk) ); - - *cache_entry = (fd_program_cache_entry_t const *)data; - - /* This test is actually too early. It should happen after the - data is actually consumed. - - TODO: this is likely fine because nothing else is modifying the - program cache records at the same time. */ - if( FD_LIKELY( fd_funk_rec_query_test( query ) == FD_FUNK_SUCCESS ) ) { - if( FD_UNLIKELY( (*cache_entry)->magic != FD_PROGRAM_CACHE_ENTRY_MAGIC ) ) FD_LOG_ERR(( "invalid magic" )); - return 0; - } - - /* Try again */ - } -} - -void -fd_program_cache_update_program( fd_bank_t * bank, - fd_funk_t * funk, - fd_funk_txn_xid_t const * xid, - fd_pubkey_t const * program_key, - fd_spad_t * runtime_spad ) { -FD_SPAD_FRAME_BEGIN( runtime_spad ) { - FD_TXN_ACCOUNT_DECL( exec_rec ); - fd_funk_rec_key_t id = fd_program_cache_key( program_key ); - - /* No need to touch the cache if the account no longer exists. */ - if( FD_UNLIKELY( fd_txn_account_init_from_funk_readonly( exec_rec, - program_key, - funk, - xid ) ) ) { - return; - } - - /* The account owner must be a BPF loader to even be considered. */ - if( FD_UNLIKELY( !fd_executor_pubkey_is_bpf_loader( fd_txn_account_get_owner( exec_rec ) ) ) ) { - return; - } - - /* If the program is not present in the cache yet, then we should run - verifications and add it to the cache. - `fd_program_cache_create_cache_entry()` will insert the program - into the cache and update the entry's flags accordingly if it fails - verification. */ - fd_program_cache_entry_t const * existing_entry = NULL; - int err = fd_program_cache_load_entry( funk, xid, program_key, &existing_entry ); - if( FD_UNLIKELY( err ) ) { - fd_program_cache_create_cache_entry( bank, funk, xid, exec_rec, runtime_spad ); - return; - } - - /* At this point, the program exists in the cache but we may need to - reverify it and update the program cache. A program does NOT - require reverification if both of the following conditions are met: - - The program was already reverified in the current epoch - - EITHER of the following are met: - - The program was not modified in a recent slot (i.e. the program - was already reverified since the last time it was modified) - - The program was already verified in the current slot - - We must reverify the program for the current epoch if it has not - been reverified yet. Additionally, we cannot break the invariant - that a program cache entry will get reverified more than once - per slot, otherwise the replay and exec tiles may race. */ - fd_epoch_schedule_t const * epoch_schedule = fd_bank_epoch_schedule_query( bank ); - ulong current_slot = fd_bank_slot_get( bank ); - ulong current_epoch = fd_bank_epoch_get( bank ); - ulong last_epoch_verified = fd_slot_to_epoch( epoch_schedule, existing_entry->last_slot_verified, NULL ); - ulong last_slot_modified = existing_entry->last_slot_modified; - if( FD_LIKELY( last_epoch_verified==current_epoch && - ( last_slot_modifiedlast_slot_verified || - current_slot==existing_entry->last_slot_verified ) ) ) { - return; - } - - /* Get the program data from the account. If the programdata does not - exist, there is no need to touch the cache - if someone tries to - invoke this program, account loading checks will fail. */ - ulong program_data_len = 0UL; - uchar const * program_data = fd_program_cache_get_account_programdata( funk, - xid, - exec_rec, - &program_data_len ); - if( FD_UNLIKELY( program_data==NULL ) ) { - /* Unlike in fd_program_cache_create_cache_entry(), where we need to - insert an entry into the cache when we cannot obtain valid - program data, here we could simply return because we know, at - this point, that the program is already in the cache. So we - don't need to do anything extra for matching Agave on cache entry - presence. */ - return; - } - - /* From here on out, we need to reverify the program. */ - - /* Parse out the ELF info so we can determine the record footprint. - If the parsing fails, then we need to make sure to later publish - a failed verification record. */ - uchar failed_elf_parsing = 0; - ulong record_sz = 0UL; - fd_sbpf_elf_info_t elf_info = {0}; - if( FD_UNLIKELY( fd_program_cache_parse_elf_info( bank, &elf_info, program_data, program_data_len ) ) ) { - failed_elf_parsing = 1; - record_sz = fd_program_cache_failed_verification_entry_footprint(); - } else { - failed_elf_parsing = 0; - record_sz = fd_program_cache_entry_footprint( &elf_info ); - } - - /* Resize the record to the new footprint if needed */ - uchar data[ record_sz ]; - fd_program_cache_entry_t * writable_entry = fd_type_pun( data ); - - /* If the ELF header parsing failed, publish a failed verification - record. */ - if( FD_UNLIKELY( failed_elf_parsing ) ) { - fd_program_cache_entry_set_failed_verification( writable_entry, current_slot ); - } else { - /* Validate the sBPF program. This will set the program's flags - accordingly. We publish the funk record regardless of the return - code. */ - writable_entry = fd_program_cache_entry_new( data, &elf_info, last_slot_modified, current_slot ); - int res = fd_program_cache_validate_sbpf_program( bank, &elf_info, program_data, program_data_len, runtime_spad, writable_entry ); - if( FD_UNLIKELY( res ) ) { - FD_LOG_DEBUG(( "fd_program_cache_validate_sbpf_program() failed" )); - } - } - - fd_funk_rec_insert_para( funk, xid, &id, alignof(fd_program_cache_entry_t), record_sz, writable_entry ); - -} FD_SPAD_FRAME_END; -} - -void -fd_program_cache_queue_program_for_reverification( fd_funk_t * funk, - fd_funk_txn_xid_t const * xid, - fd_pubkey_t const * program_key, - ulong current_slot ) { - - /* We want to access the program cache entry for this pubkey and - queue it for reverification. If it exists, clone it down to the - current funk txn and update the entry's `last_slot_modified` - field. - - This read is thread-safe because if you have transaction A that - modifies the program and transaction B that references the program, - the dispatcher will not attempt to execute transaction B until - transaction A is finalized. */ - fd_program_cache_entry_t const * existing_entry = NULL; - int err = fd_program_cache_load_entry( funk, xid, program_key, &existing_entry ); - if( FD_UNLIKELY( err ) ) { - return; - } - - /* Ensure the record is in the current funk transaction */ - fd_funk_rec_key_t id = fd_program_cache_key( program_key ); - fd_program_cache_entry_t entry = *existing_entry; - entry.last_slot_modified = current_slot; - fd_funk_rec_insert_para( funk, xid, &id, alignof(fd_program_cache_entry_t), sizeof(fd_program_cache_entry_t), &entry ); -} - -int -fd_funk_rec_insert_para( fd_funk_t * funk, - fd_funk_txn_xid_t const * xid, - fd_funk_rec_key_t const * key, - ulong val_align, - ulong val_sz, - void * val ) { - if( FD_UNLIKELY( !funk ) ) FD_LOG_ERR(( "NULL funk" )); - if( FD_UNLIKELY( !xid ) ) FD_LOG_ERR(( "NULL xid" )); - if( FD_UNLIKELY( !key ) ) FD_LOG_ERR(( "NULL key" )); - if( FD_UNLIKELY( !val && val_sz ) ) FD_LOG_ERR(( "NULL val" )); - - fd_funk_xid_key_pair_t pair[1]; - fd_funk_txn_xid_copy( pair->xid, xid ); - fd_funk_rec_key_copy( pair->key, key ); - - for(;;) { - /* See if the record already exists. */ - fd_funk_rec_query_t query[1]; - int err = fd_funk_rec_map_query_try( funk->rec_map, pair, NULL, query, 0 ); - if( err == FD_MAP_SUCCESS ) { - fd_funk_rec_t * rec = fd_funk_rec_map_query_ele( query ); - /* Set the value of the record */ - if( !fd_funk_val_truncate( rec, fd_funk_alloc( funk ), fd_funk_wksp( funk ), val_align, val_sz, &err ) ) { - FD_LOG_ERR(( "fd_funk_val_truncate() failed (out of memory?)" )); - return err; - } - memcpy( fd_funk_val( rec, fd_funk_wksp( funk ) ), val, val_sz ); - return FD_FUNK_SUCCESS; - } - - /* The record doesn't exist. Create it. */ - fd_funk_rec_prepare_t prepare[1]; - fd_funk_rec_t * rec = fd_funk_rec_prepare( funk, xid, key, prepare, &err ); - if( FD_UNLIKELY( !rec ) ) { - FD_LOG_CRIT(( "fd_funk_rec_prepare returned err=%d", err )); - return err; - } - /* Set the value of the record */ - if( !fd_funk_val_truncate( rec, fd_funk_alloc( funk ), fd_funk_wksp( funk ), val_align, val_sz, &err ) ) { - FD_LOG_ERR(( "fd_funk_val_truncate() failed (out of memory?)" )); - return err; - } - memcpy( fd_funk_val( rec, fd_funk_wksp( funk ) ), val, val_sz ); - fd_funk_rec_publish( funk, prepare ); - return FD_FUNK_SUCCESS; - } -} diff --git a/src/flamenco/runtime/program/fd_program_cache.h b/src/flamenco/runtime/program/fd_program_cache.h deleted file mode 100644 index 3e4b63bd59e..00000000000 --- a/src/flamenco/runtime/program/fd_program_cache.h +++ /dev/null @@ -1,325 +0,0 @@ -#ifndef HEADER_fd_src_flamenco_runtime_program_fd_program_cache_h -#define HEADER_fd_src_flamenco_runtime_program_fd_program_cache_h - -#include "../../types/fd_types_custom.h" -#include "../../../funk/fd_funk_base.h" -#include "../../../ballet/sbpf/fd_sbpf_loader.h" - -/* fd_program_cache contains the core logic for the program cache's - behavior, including accesses, insertions, updates, and verifies. - Specifically, the program cache operates in a lazy manner where - programs are only inserted / reverified when they are referenced - in a transaction. - - The program cache lives in Funk and is fork-aware. The size of the - program cache is bounded by the size of the Funk instance. - - On bootup, the program cache starts out empty. As programs (defined - as accounts owned by one of the BPF loaders that can be invoked - by the user) are referenced as account keys within transactions - (either statically or through address lookup tables), - `fd_program_cache_update_program()` accepts a pubkey as input and - will attempt to either insert the program into the cache (performing - sBPF + ELF validations) if missing, or reverify the program if - needed. - - During transaction execution, we accumulate any program pubkeys that - are deployed, extended, or upgraded (basically, any calls to - `fd_deploy_program()` in the BPF loaders) since those instructions - may have changed the program's executable data (closed / retracted - programs also do, but see comments below on how we handle that). - After a transaction is executed successfully, we iterate through - these accumulated program keys and queue them for reverification. - To mark a program for reverification, we simply update the - `last_slot_modified` field to the current slot. When the program is - next referenced in a transaction, `fd_program_cache_update_program()` - will check if the program was modified since the last time it was - reverified, and reverify the program accordingly to update the cache - entry's sBPF info, calldests, etc. Once the program has been - reverified, the entry's `last_slot_verified` field will be updated - to the current slot. - - When a program is reverified and updated in the cache, we clone the - record down to the current funk transaction from an ancestor, and - then modify the record within the current funk transaction. - - A program cache entry may also be reverified after crossing an - epoch boundary, even if it was not modified recently. This is because - the network's active feature set may have changed, and any previously - {valid,invalid} programs may now be {valid,invalid}. Therefore, - if a program is referenced in a transaction that has not been - reverified since the last epoch (regardless of it having been - modified or not), it will be reverified and updated. - - If a program fails verification due to invalid ELF headers / sBPF - loading failures, then we update the `failed_verification` tag, - set `last_slot_verified` to the current slot, and set - `last_slot_modified` to 0. All other fields have undefined value. A - future program upgrade / network feature set change could make this - program valid again. - - A key invariant with the program cache design is that it does not - evict any entries - it only grows through the lifetime of the - client's execution. Because of this invariant, we do not touch cache - entries for programs which are closed, and instead let - transaction-level account loading and BPF loader program checks - catch cases where a user tries to invoke a closed program. - - Another key invariant is that for any given program, we will insert / - update its cache entry at most ONCE per slot, and this will happen - the FIRST time the program is referenced in a transaction before it - is dispatched to the exec tiles. This is important because it ensures - that the replay tile does not race with the exec tiles by updating - the cache entry for a program while an exec tile is already executing - it. Even if a program is upgraded in the same slot after it has been - reverified, the network forbids invoking programs in the same slot as - they are deployed / upgraded, so we can wait for a future slot to - update the program cache entry when it is invoked next. - - EDGE CASES (and how we handle them): - - Deploying programs - - Deploying a program - - `fd_program_cache_queue_program_for_reverification()` will - not do anything because the program does not exist in the - cache yet, and so there is nothing to update. The next time - the program is referenced in a transaction, - `fd_program_cache_update_program()` will see that the program - is missing in the cache and will perform ELF / sBPF - verifications and insert the entry into the cache. - - Deploying and invoking in the same slot - - BPF loader checks will fail because the program account's - slot value will be equal to the current slot, which - gets caught by the `DelayedVisibility` checks. - - Upgrading programs - - Upgrading a program - - The program account will have been referenced as writable - in the transaction, and thus - `fd_program_cache_queue_program_for_reverification()` will - update the `last_slot_modified` to queue the program - for reverification the next time it is referenced in a - transaction. - - Upgrading and invoking in the same transaction - - Same as "deploy" case - - Closing programs - - Closing a program - - We do not touch the program cache here. We let account - loading / BPF loader checks handle cases where a user may try - to invoke a closed program. - - Closing + invoking a program in the same transaction - - The program account's state will be set to uninitialized / - retracted, so any future instructions that invoke the program - will fail when the BPF loader checks the account state. - - Closing + invoking a program in separate transactions - - The program account's owner will be set to the system program - and thus fail account loading checks. - - TL;DR: Deploys and upgrades are treated the same way - if the cache - entry exists, it's queued for reverification. If it doesn't, the - program cache will verify and add it to the cache the next time it's - invoked. Cases where the program is closed / retracted are not - explicitly handled. - - Q: Is the program cache concurrency-safe? - A: Yes. In any given slot, the replay tile first iterates through - each transaction in a single-threaded manner. For each - transaction, it will read the accounts and will call - `fd_program_cache_update_program()` for each program, and then - dispatch the transaction to the exec tiles. This means - that the first time any program is referenced in any transaction - in the slot, the replay tile will update the associated program - cache entry before dispatching the transaction to any of the exec - tiles. Furthermore, we are not allowed to insert / reverify a - program cache entry more than once within a single slot. This - guarantees that any read / write accesses for a particular program - in the program cache by the exec tiles will occur after the cache - entries have been processed by the replay tile in any given slot. - Furthermore, if the program was upgraded, the exec tile simply - updates a single header in the existing program cache entry - `last_slot_modified`, which is behind a blocking write lock. - Note that if there is read-write or write-write-contention - between two transactions for any accounts, the scheduler will - ensure that those two transactions are scheduled and finalized - in a serial manner. */ - -/* `fd_program_cache_entry` defines the structure for a single - program cache entry. */ -struct fd_program_cache_entry { - ulong magic; - - /* For any programs that fail verification, we retain this flag for - to prevent any reverification attempts for the remainder of the - epoch / until the program is modified again (instead of removing - them from the cache). When `failed_verification` is set, - the values of all other fields except for - `last_slot_verified` are undefined. Any invocations of a - program that fails verification will continue to fail until the - program is queued for reverification by an eligible BPF loader - instruction (e.g. the program is upgraded). */ - uchar failed_verification; - - /* Stores the last slot the program was modified. This field is - updated at the end of a transaction for a program account that was - referenced as a writable account within an instruction. For any - programs that are freshly added to the cache, this field is set to - 0. */ - ulong last_slot_modified; - - /* Stores the last slot verification checks were ran for a program. - Programs are reverified if they are mentioned in the current - transaction, and if one of the following are true: - - It is the first time they are referenced in a transaction in the - current epoch - - The program was a writable account in a transaction - - We reverify referenced programs at least once every epoch - regardless of whether they were modified or not because changes - in the active feature set may cause existing deployed programs - to be invalided (e.g. stricter ELF / VM / sBPF checks). */ - ulong last_slot_verified; - - ulong entry_pc; - ulong text_cnt; - ulong text_off; - ulong text_sz; - - ulong rodata_sz; - - /* We use offsets to store the calldests and rodata in order to keep - the cache entry gaddr-aware so it can be passed around different - tiles. */ - ulong calldests_shmem_off; - ulong rodata_off; - - /* SBPF version, SIMD-0161 */ - ulong sbpf_version; -}; -typedef struct fd_program_cache_entry fd_program_cache_entry_t; - -static inline uchar * -fd_program_cache_get_rodata( fd_program_cache_entry_t const * cache_entry ) { - return (uchar *)cache_entry + cache_entry->rodata_off; -} - -static inline fd_sbpf_calldests_t * -fd_program_cache_get_calldests_shmem( fd_program_cache_entry_t const * cache_entry ) { - return (fd_sbpf_calldests_t *)((uchar *)cache_entry + cache_entry->calldests_shmem_off); -} - -static inline fd_sbpf_calldests_t * -fd_program_cache_get_calldests( fd_program_cache_entry_t const * cache_entry ) { - return fd_sbpf_calldests_join( fd_program_cache_get_calldests_shmem( cache_entry ) ); -} - -/* arbitrary unique value, in this case - echo -n "fd_program_cache_entry" | sha512sum | head -c 16 */ -#define FD_PROGRAM_CACHE_ENTRY_MAGIC 0xb45640baf006ddf6 - -FD_PROTOTYPES_BEGIN - -fd_program_cache_entry_t * -fd_program_cache_entry_new( void * mem, - fd_sbpf_elf_info_t const * elf_info, - ulong last_slot_modified, - ulong last_slot_verified ); - -ulong -fd_program_cache_entry_footprint( fd_sbpf_elf_info_t const * elf_info ); - -/* Gets the program cache funk record key for a given program pubkey. */ -fd_funk_rec_key_t -fd_program_cache_key( fd_pubkey_t const * pubkey ); - -/* Loads a single program cache entry for a given pubkey. Returns 0 on - success and -1 on failure. On success, `*cache_entry` holds a pointer - to the program cache entry. */ -int -fd_program_cache_load_entry( fd_funk_t const * funk, - fd_funk_txn_xid_t const * xid, - fd_pubkey_t const * program_pubkey, - fd_program_cache_entry_t const ** cache_entry ); - -/* Parses the programdata from a program account. Returns a pointer to - the program data and sets `out_program_data_len` on success. Returns - NULL on failure or if the program account is not owned by a BPF - loader program ID, and leaves `out_program_data_len` in an undefined - state. Reasons for failure vary on the loader version. See the - respective functions in this file for more details. - - This is essentially load_program_with_pubkey(), sans the ELF parsing - and whatnot which is done in load_program_from_bytes(). */ -uchar const * -fd_program_cache_get_account_programdata( fd_funk_t const * funk, - fd_funk_txn_xid_t const * xid, - fd_txn_account_t const * program_acc, - ulong * out_program_data_len ); - -/* Updates the program cache for a single program. This function is - called for every program that is referenced in a transaction, plus - every single account in a lookup table referenced in the transaction. - This function... - - Accepts a pubkey and reads the programdata from the account - - Creates a program cache entry for the program if it doesn't exist - in the cache already - - Reverifies programs if either... - - The program was recently modified in a STRICTLY PRIOR SLOT - - The program has not been verified yet for the current epoch - - Invalidated programs that fail ELF / sBPF verification - - Updates the program cache entry for the program after - reverification (syscalls, calldests, etc) - - With this design, the program cache is designed to only grow as new - programs are deployed / invoked. If a program fails verification, it - stays in the cache so that repeated calls won't DOS the validator by - forcing reverifications (since we won't be able to distinguish failed - verifications from new deployments). - - When a program is reverified (and the cache entry already exists in - some ancestor funk transaction), we clone the record down to the - current funk transaction and acquire a blocking write lock on the - cloned funk record. */ -void -fd_program_cache_update_program( fd_bank_t * bank, - fd_funk_t * funk, - fd_funk_txn_xid_t const * xid, - fd_pubkey_t const * program_key, - fd_spad_t * runtime_spad ); - -/* Queues a single program account for reverification. This function - queries the cache for an existing entry and queues it for - reverification by setting the `last_slot_modified` field in the - program cache entry to the current slot. If the cache entry - for the program does not exist yet (e.g. newly deployed programs), - this function does nothing and instead, - `fd_program_cache_publish_failed_verification_rec()` will insert - the program into the cache. - - If the record exists in the program cache, this function will - acquire a write-lock on the program cache entry. */ -void -fd_program_cache_queue_program_for_reverification( fd_funk_t * funk, - fd_funk_txn_xid_t const * xid, - fd_pubkey_t const * program_key, - ulong current_slot ); - -/* fd_funk_rec_insert_para does thread-safe insertion of a funk record. - - Detailed Behavior: - - More specifically, first this function will query the transaction - stack to identify what the youngest transaction with the key is. - If a record is found in some ancestor txn or if the - record doesn't exist, we will allocate a new account record and add - this into the transaction. In either case, the record is set to the - given value. */ - -int -fd_funk_rec_insert_para( fd_funk_t * funk, - fd_funk_txn_xid_t const * xid, - fd_funk_rec_key_t const * key, - ulong val_align, - ulong val_sz, - void * val ); - -FD_PROTOTYPES_END - -#endif /* HEADER_fd_src_flamenco_runtime_program_fd_program_cache_h */ diff --git a/src/flamenco/runtime/program/test_program_cache.c b/src/flamenco/runtime/program/test_program_cache.c deleted file mode 100644 index 069f895efdb..00000000000 --- a/src/flamenco/runtime/program/test_program_cache.c +++ /dev/null @@ -1,705 +0,0 @@ -#include "fd_program_cache.h" -#include "../fd_bank.h" - -#if FD_HAS_HOSTED - -#define TEST_WKSP_TAG 1234UL - -/* Load in programdata for tests */ -FD_IMPORT_BINARY( valid_program_data, "src/ballet/sbpf/fixtures/hello_solana_program.so" ); -FD_IMPORT_BINARY( bigger_valid_program_data, "src/ballet/sbpf/fixtures/clock_sysvar_program.so" ); - -static uchar const invalid_program_data[] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07}; - -/* Test program pubkeys */ -static fd_pubkey_t const test_program_pubkey = { - .uc = { 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, - 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99 } -}; - -static ulong const SPAD_MEM_MAX = 100UL << 20; /* 100MB */ - -/* Test setup and teardown helpers */ -static fd_wksp_t * test_wksp = NULL; -static fd_funk_t * test_funk = NULL; -static fd_spad_t * test_spad = NULL; -static fd_bank_t * test_bank = NULL; -static fd_banks_t * test_banks = NULL; -static fd_funk_txn_xid_t test_xid = {0}; - -static void -test_teardown( void ) { - - if( test_spad ) { - fd_spad_leave( test_spad ); - test_spad = NULL; - } - - if( test_funk ) { - fd_funk_leave( test_funk, NULL ); - test_funk = NULL; - } - - if( test_wksp ) { - fd_wksp_delete_anonymous( test_wksp ); - test_wksp = NULL; - } -} - -/* Helper to create a funk transaction */ -static fd_funk_txn_xid_t -create_test_funk_txn( void ) { - fd_funk_txn_xid_t xid = fd_funk_generate_xid(); - fd_funk_txn_xid_t root; fd_funk_txn_xid_set_root( &root ); - fd_funk_txn_prepare( test_funk, &root, &xid ); - return xid; -} - -/* Helper to create a test account */ -static void -create_test_account( fd_pubkey_t const * pubkey, - fd_pubkey_t const * owner, - uchar const * data, - ulong data_len, - uchar executable ) { - FD_TXN_ACCOUNT_DECL( acc ); - fd_funk_rec_prepare_t prepare = {0}; - int err = fd_txn_account_init_from_funk_mutable( /* acc */ acc, - /* pubkey */ pubkey, - /* funk */ test_funk, - /* xid */ &test_xid, - /* do_create */ 1, - /* min_data_sz */ data_len, - /* prepare */ &prepare ); - FD_TEST( !err ); - - if( data ) { - fd_txn_account_set_data( acc, data, data_len ); - } - - acc->starting_lamports = 1UL; - acc->starting_dlen = data_len; - fd_txn_account_set_lamports( acc, 1UL ); - fd_txn_account_set_executable( acc, executable ); - fd_txn_account_set_owner( acc, owner ); - - fd_txn_account_mutable_fini( acc, test_funk, &prepare ); -} - -static void -update_account_data( fd_pubkey_t const * pubkey, - uchar const * data, - ulong data_len ) { - FD_TXN_ACCOUNT_DECL( acc ); - fd_funk_rec_prepare_t prepare = {0}; - int err = fd_txn_account_init_from_funk_mutable( /* acc */ acc, - /* pubkey */ pubkey, - /* funk */ test_funk, - /* xid */ &test_xid, - /* do_create */ 0, - /* min_data_sz */ data_len, - /* prepare */ &prepare ); - FD_TEST( !err ); - FD_TEST( data ); - - fd_txn_account_set_data( acc, data, data_len ); - fd_txn_account_mutable_fini( acc, test_funk, &prepare ); -} - -/* Test 1: Account doesn't exist */ -static void -test_account_does_not_exist( void ) { - FD_LOG_NOTICE(( "Testing: Account doesn't exist" )); - - test_xid = create_test_funk_txn(); - - /* Call with a non-existent pubkey */ - fd_pubkey_t const non_existent_pubkey = {0}; - - /* This should return early without doing anything */ - fd_program_cache_update_program( test_bank, test_funk, &test_xid, &non_existent_pubkey, test_spad ); - - /* Verify no cache entry was created */ - fd_program_cache_entry_t const * valid_prog = NULL; - int err = fd_program_cache_load_entry( test_funk, &test_xid, &non_existent_pubkey, &valid_prog ); - FD_TEST( err==-1 ); /* Should not exist */ - - fd_funk_txn_cancel( test_funk, &test_xid ); -} - -/* Test 2: Account exists but is not owned by a BPF loader */ -static void -test_account_not_bpf_loader_owner( void ) { - FD_LOG_NOTICE(( "Testing: Account exists but is not owned by a BPF loader" )); - - test_xid = create_test_funk_txn(); - - /* Create an account owned by a non-BPF loader */ - create_test_account( &test_program_pubkey, - &fd_solana_system_program_id, - invalid_program_data, - sizeof(invalid_program_data), - 1 ); - - /* This should return early without doing anything */ - fd_program_cache_update_program( test_bank, test_funk, &test_xid, &test_program_pubkey, test_spad ); - - /* Verify no cache entry was created */ - fd_program_cache_entry_t const * valid_prog = NULL; - int err = fd_program_cache_load_entry( test_funk, &test_xid, &test_program_pubkey, &valid_prog ); - FD_TEST( err==-1 ); /* Should not exist */ - - fd_funk_txn_cancel( test_funk, &test_xid ); -} - -/* Test 3: Program is not in cache yet (first time), but program fails validations */ -static void -test_invalid_program_not_in_cache_first_time( void ) { - FD_LOG_NOTICE(( "Testing: Program is not in cache yet (first time), but program fails validations" )); - - test_xid = create_test_funk_txn(); - - /* Create a BPF loader account */ - create_test_account( &test_program_pubkey, - &fd_solana_bpf_loader_program_id, - invalid_program_data, - sizeof(invalid_program_data), - 1 ); - - /* This should create a cache entry */ - fd_program_cache_update_program( test_bank, test_funk, &test_xid, &test_program_pubkey, test_spad ); - - /* Verify cache entry was created */ - fd_program_cache_entry_t const * valid_prog = NULL; - int err = fd_program_cache_load_entry( test_funk, &test_xid, &test_program_pubkey, &valid_prog ); - FD_TEST( !err ); /* Should exist */ - FD_TEST( valid_prog ); - FD_TEST( valid_prog->magic==FD_PROGRAM_CACHE_ENTRY_MAGIC ); - FD_TEST( valid_prog->failed_verification ); - FD_TEST( valid_prog->last_slot_verified==fd_bank_slot_get( test_bank ) ); - - fd_funk_txn_cancel( test_funk, &test_xid ); -} - -/* Test 4: Program is not in cache yet (first time), but program passes validations */ -static void -test_valid_program_not_in_cache_first_time( void ) { - FD_LOG_NOTICE(( "Testing: Program is not in cache yet (first time), but program passes validations" )); - - test_xid = create_test_funk_txn(); - - /* Create a BPF loader account */ - create_test_account( &test_program_pubkey, - &fd_solana_bpf_loader_program_id, - valid_program_data, - valid_program_data_sz, - 1 ); - - /* This should create a cache entry */ - fd_program_cache_update_program( test_bank, test_funk, &test_xid, &test_program_pubkey, test_spad ); - - /* Verify cache entry was created */ - fd_program_cache_entry_t const * valid_prog = NULL; - int err = fd_program_cache_load_entry( test_funk, &test_xid, &test_program_pubkey, &valid_prog ); - FD_TEST( !err ); /* Should exist */ - FD_TEST( valid_prog ); - FD_TEST( valid_prog->magic==FD_PROGRAM_CACHE_ENTRY_MAGIC ); - FD_TEST( !valid_prog->failed_verification ); - FD_TEST( valid_prog->last_slot_verified==fd_bank_slot_get( test_bank ) ); - - fd_funk_txn_cancel( test_funk, &test_xid ); -} - -/* Test 5: Program is in cache but needs reverification - (different epoch) */ -static void -test_program_in_cache_needs_reverification( void ) { - FD_LOG_NOTICE(( "Testing: Program is in cache but needs reverification (different epoch)" )); - - test_xid = create_test_funk_txn(); - - /* Create a BPF loader account */ - create_test_account( &test_program_pubkey, - &fd_solana_bpf_loader_program_id, - valid_program_data, - valid_program_data_sz, - 1 ); - - /* First call to create cache entry */ - fd_program_cache_update_program( test_bank, test_funk, &test_xid, &test_program_pubkey, test_spad ); - - /* Verify cache entry was created */ - fd_program_cache_entry_t const * valid_prog = NULL; - int err = fd_program_cache_load_entry( test_funk, &test_xid, &test_program_pubkey, &valid_prog ); - FD_TEST( !err ); - FD_TEST( valid_prog ); - FD_TEST( valid_prog->magic==FD_PROGRAM_CACHE_ENTRY_MAGIC ); - FD_TEST( !valid_prog->failed_verification ); - FD_TEST( valid_prog->last_slot_verified==fd_bank_slot_get( test_bank ) ); - FD_TEST( valid_prog->last_slot_modified==0UL ); - - /* Fast forward to next epoch */ - fd_bank_slot_set( test_bank, fd_bank_slot_get( test_bank ) + 432000UL ); - - /* This should trigger reverification */ - fd_program_cache_update_program( test_bank, test_funk, &test_xid, &test_program_pubkey, test_spad ); - - /* Verify the cache entry was updated */ - err = fd_program_cache_load_entry( test_funk, &test_xid, &test_program_pubkey, &valid_prog ); - FD_TEST( !err ); - FD_TEST( valid_prog ); - FD_TEST( valid_prog->magic==FD_PROGRAM_CACHE_ENTRY_MAGIC ); - FD_TEST( !valid_prog->failed_verification ); - FD_TEST( valid_prog->last_slot_verified==fd_bank_slot_get( test_bank ) ); - FD_TEST( valid_prog->last_slot_modified==0UL ); - - fd_funk_txn_cancel( test_funk, &test_xid ); -} - -/* Test 6: Program is in cache and was just modified, so it should be - queued for reverification */ -static void -test_program_in_cache_queued_for_reverification( void ) { - FD_LOG_NOTICE(( "Testing: Program is in cache and was just modified, so it should be queued for reverification" )); - - test_xid = create_test_funk_txn(); - - /* Create a BPF loader account */ - create_test_account( &test_program_pubkey, - &fd_solana_bpf_loader_program_id, - valid_program_data, - valid_program_data_sz, - 1 ); - - /* First call to create cache entry */ - fd_program_cache_update_program( test_bank, test_funk, &test_xid, &test_program_pubkey, test_spad ); - - /* Verify cache entry was created */ - fd_program_cache_entry_t const * valid_prog = NULL; - int err = fd_program_cache_load_entry( test_funk, &test_xid, &test_program_pubkey, &valid_prog ); - FD_TEST( !err ); - FD_TEST( valid_prog ); - FD_TEST( valid_prog->magic==FD_PROGRAM_CACHE_ENTRY_MAGIC ); - FD_TEST( !valid_prog->failed_verification ); - FD_TEST( valid_prog->last_slot_verified==fd_bank_slot_get( test_bank ) ); - - /* Fast forward to a future slot */ - ulong original_slot = fd_bank_slot_get( test_bank ); - fd_bank_slot_set( test_bank, fd_bank_slot_get( test_bank ) + 1000UL ); - ulong future_slot = fd_bank_slot_get( test_bank ); - FD_TEST( future_slot>original_slot ); - - /* Get the program account to pass to the queue function */ - FD_TXN_ACCOUNT_DECL( program_acc ); - err = fd_txn_account_init_from_funk_readonly( program_acc, &test_program_pubkey, test_funk, &test_xid ); - FD_TEST( !err ); - - /* Queue the program for reverification */ - fd_program_cache_queue_program_for_reverification( test_funk, &test_xid, &test_program_pubkey, future_slot ); - - /* Verify the cache entry was updated with the future slot as last_slot_modified */ - err = fd_program_cache_load_entry( test_funk, &test_xid, &test_program_pubkey, &valid_prog ); - FD_TEST( !err ); - FD_TEST( valid_prog ); - FD_TEST( valid_prog->magic==FD_PROGRAM_CACHE_ENTRY_MAGIC ); - FD_TEST( !valid_prog->failed_verification ); - FD_TEST( valid_prog->last_slot_modified==future_slot ); - FD_TEST( valid_prog->last_slot_verified==original_slot ); - FD_TEST( valid_prog->last_slot_modified>original_slot ); - - /* Reverify the cache entry at the future slot */ - fd_program_cache_update_program( test_bank, test_funk, &test_xid, &test_program_pubkey, test_spad ); - - /* Verify the cache entry was updated */ - err = fd_program_cache_load_entry( test_funk, &test_xid, &test_program_pubkey, &valid_prog ); - FD_TEST( !err ); - FD_TEST( valid_prog ); - FD_TEST( valid_prog->magic==FD_PROGRAM_CACHE_ENTRY_MAGIC ); - FD_TEST( !valid_prog->failed_verification ); - FD_TEST( valid_prog->last_slot_modified==future_slot ); - FD_TEST( valid_prog->last_slot_verified==future_slot ); - - fd_funk_txn_cancel( test_funk, &test_xid ); -} - -/* Test 7: Program queued for reverification but program doesn't exist - in the cache yet */ -static void -test_program_queued_for_reverification_account_does_not_exist( void ) { - FD_LOG_NOTICE(( "Testing: Program queued for reverification but account doesn't exist" )); - - test_xid = create_test_funk_txn(); - - /* Create a BPF loader account but don't add it to the cache */ - create_test_account( &test_program_pubkey, - &fd_solana_bpf_loader_program_id, - valid_program_data, - valid_program_data_sz, - 1 ); - - /* Fast forward to a future slot */ - ulong original_slot = fd_bank_slot_get( test_bank ); - fd_bank_slot_set( test_bank, fd_bank_slot_get( test_bank ) + 1000UL ); - ulong future_slot = fd_bank_slot_get( test_bank ); - FD_TEST( future_slot>original_slot ); - - /* Get the program account to pass to the queue function */ - FD_TXN_ACCOUNT_DECL( program_acc ); - int err = fd_txn_account_init_from_funk_readonly( program_acc, &test_program_pubkey, test_funk, &test_xid ); - FD_TEST( !err ); - - /* Try to queue the program for reverification - this should return early since it's not in cache */ - fd_program_cache_queue_program_for_reverification( test_funk, &test_xid, &test_program_pubkey, future_slot ); - - /* Verify no cache entry was created since the program wasn't in the cache */ - fd_program_cache_entry_t const * valid_prog = NULL; - err = fd_program_cache_load_entry( test_funk, &test_xid, &test_program_pubkey, &valid_prog ); - FD_TEST( err ); /* Should not exist */ - - fd_funk_txn_cancel( test_funk, &test_xid ); -} - -/* Test 8: Program is in cache and was just modified and queued for - reverification, so when it is next reverified the - `last_slot_verified` should be set to the current slot */ -static void -test_program_in_cache_queued_for_reverification_and_processed( void ) { - FD_LOG_NOTICE(( "Testing: Program is in cache and was just modified and queued for reverification, so the last slot reverification ran should be set to the current slot" )); - - test_xid = create_test_funk_txn(); - - /* Create a BPF loader account */ - create_test_account( &test_program_pubkey, - &fd_solana_bpf_loader_program_id, - valid_program_data, - valid_program_data_sz, - 1 ); - - /* First call to create cache entry */ - fd_program_cache_update_program( test_bank, test_funk, &test_xid, &test_program_pubkey, test_spad ); - - /* Verify cache entry was created */ - fd_program_cache_entry_t const * valid_prog = NULL; - int err = fd_program_cache_load_entry( test_funk, &test_xid, &test_program_pubkey, &valid_prog ); - FD_TEST( !err ); - FD_TEST( valid_prog ); - FD_TEST( valid_prog->magic==FD_PROGRAM_CACHE_ENTRY_MAGIC ); - FD_TEST( !valid_prog->failed_verification ); - FD_TEST( valid_prog->last_slot_verified==fd_bank_slot_get( test_bank ) ); - - /* Fast forward to a future slot */ - ulong original_slot = fd_bank_slot_get( test_bank ); - fd_bank_slot_set( test_bank, fd_bank_slot_get( test_bank ) + 11000UL ); - ulong future_slot = fd_bank_slot_get( test_bank ); - FD_TEST( future_slot>original_slot ); - - /* Get the program account to pass to the queue function */ - FD_TXN_ACCOUNT_DECL( program_acc ); - err = fd_txn_account_init_from_funk_readonly( program_acc, &test_program_pubkey, test_funk, &test_xid ); - FD_TEST( !err ); - - /* Queue the program for reverification */ - fd_program_cache_queue_program_for_reverification( test_funk, &test_xid, &test_program_pubkey, future_slot ); - - /* Verify the cache entry was updated with the future slot as last_slot_modified */ - err = fd_program_cache_load_entry( test_funk, &test_xid, &test_program_pubkey, &valid_prog ); - FD_TEST( !err ); - FD_TEST( valid_prog ); - FD_TEST( valid_prog->magic==FD_PROGRAM_CACHE_ENTRY_MAGIC ); - FD_TEST( !valid_prog->failed_verification ); - FD_TEST( valid_prog->last_slot_modified==future_slot ); - FD_TEST( valid_prog->last_slot_verified==original_slot ); - FD_TEST( valid_prog->last_slot_modified>original_slot ); - - /* Fast forward to a future slot */ - fd_bank_slot_set( test_bank, fd_bank_slot_get( test_bank ) + 11000UL ); - ulong future_update_slot = fd_bank_slot_get( test_bank ); - FD_TEST( future_update_slot>future_slot ); - - /* Now update the cache entry at the future slot */ - fd_program_cache_update_program( test_bank, test_funk, &test_xid, &test_program_pubkey, test_spad ); - - /* Verify the cache entry was updated */ - err = fd_program_cache_load_entry( test_funk, &test_xid, &test_program_pubkey, &valid_prog ); - FD_TEST( !err ); - FD_TEST( valid_prog ); - FD_TEST( valid_prog->magic==FD_PROGRAM_CACHE_ENTRY_MAGIC ); - FD_TEST( !valid_prog->failed_verification ); - FD_TEST( valid_prog->last_slot_verified==future_update_slot ); - FD_TEST( valid_prog->last_slot_modified==future_slot ); - - fd_funk_txn_cancel( test_funk, &test_xid ); -} - -/* Test 9: Genesis program fails verification, and is reverified later */ -static void -test_invalid_genesis_program_reverified_after_genesis( void ) { - FD_LOG_NOTICE(( "Testing: Program fails verification in genesis, and is reverified later" )); - - test_xid = create_test_funk_txn(); - fd_bank_slot_set( test_bank, 0UL ); - - /* Create a BPF loader account */ - create_test_account( &test_program_pubkey, - &fd_solana_bpf_loader_program_id, - invalid_program_data, - sizeof(invalid_program_data), - 1 ); - - /* First call to create cache entry */ - fd_program_cache_update_program( test_bank, test_funk, &test_xid, &test_program_pubkey, test_spad ); - - /* Verify cache entry was created */ - fd_program_cache_entry_t const * valid_prog = NULL; - int err = fd_program_cache_load_entry( test_funk, &test_xid, &test_program_pubkey, &valid_prog ); - FD_TEST( !err ); - FD_TEST( valid_prog ); - FD_TEST( valid_prog->magic==FD_PROGRAM_CACHE_ENTRY_MAGIC ); - FD_TEST( valid_prog->failed_verification ); - FD_TEST( valid_prog->last_slot_modified==0UL ); - FD_TEST( valid_prog->last_slot_verified==0UL ); - - /* Fast forward to a future slot */ - ulong original_slot = fd_bank_slot_get( test_bank ); - fd_bank_slot_set( test_bank, fd_bank_slot_get( test_bank ) + 11000UL ); - ulong future_slot = fd_bank_slot_get( test_bank ); - FD_TEST( future_slot>original_slot ); - - /* Program invoked, update cache entry */ - fd_program_cache_update_program( test_bank, test_funk, &test_xid, &test_program_pubkey, test_spad ); - - /* Verify the cache entry was updated */ - err = fd_program_cache_load_entry( test_funk, &test_xid, &test_program_pubkey, &valid_prog ); - FD_TEST( !err ); - FD_TEST( valid_prog ); - FD_TEST( valid_prog->magic==FD_PROGRAM_CACHE_ENTRY_MAGIC ); - FD_TEST( valid_prog->failed_verification ); - FD_TEST( valid_prog->last_slot_verified==future_slot ); - FD_TEST( valid_prog->last_slot_modified==0UL ); - - fd_funk_txn_cancel( test_funk, &test_xid ); -} - -/* Test 10: Genesis program passes verification, and is reverified - later */ -static void -test_valid_genesis_program_reverified_after_genesis( void ) { - FD_LOG_NOTICE(( "Testing: Program passes verification in genesis, and is reverified later" )); - - test_xid = create_test_funk_txn(); - fd_bank_slot_set( test_bank, 0UL ); - - /* Create a BPF loader account */ - create_test_account( &test_program_pubkey, - &fd_solana_bpf_loader_program_id, - valid_program_data, - valid_program_data_sz, - 1 ); - - /* First call to create cache entry */ - fd_program_cache_update_program( test_bank, test_funk, &test_xid, &test_program_pubkey, test_spad ); - - /* Verify cache entry was created */ - fd_program_cache_entry_t const * valid_prog = NULL; - int err = fd_program_cache_load_entry( test_funk, &test_xid, &test_program_pubkey, &valid_prog ); - FD_TEST( !err ); - FD_TEST( valid_prog ); - FD_TEST( valid_prog->magic==FD_PROGRAM_CACHE_ENTRY_MAGIC ); - FD_TEST( !valid_prog->failed_verification ); - FD_TEST( valid_prog->last_slot_modified==0UL ); - FD_TEST( valid_prog->last_slot_verified==0UL ); - - /* Fast forward to a future slot */ - ulong original_slot = fd_bank_slot_get( test_bank ); - fd_bank_slot_set( test_bank, fd_bank_slot_get( test_bank ) + 11000UL ); - ulong future_slot = fd_bank_slot_get( test_bank ); - FD_TEST( future_slot>original_slot ); - - /* Program invoked, update cache entry */ - fd_program_cache_update_program( test_bank, test_funk, &test_xid, &test_program_pubkey, test_spad ); - - /* Verify the cache entry was updated */ - err = fd_program_cache_load_entry( test_funk, &test_xid, &test_program_pubkey, &valid_prog ); - FD_TEST( !err ); - FD_TEST( valid_prog ); - FD_TEST( valid_prog->magic==FD_PROGRAM_CACHE_ENTRY_MAGIC ); - FD_TEST( !valid_prog->failed_verification ); - FD_TEST( valid_prog->last_slot_verified==future_slot ); - FD_TEST( valid_prog->last_slot_modified==0UL ); - - fd_funk_txn_cancel( test_funk, &test_xid ); -} - -/* Test 11: Program gets upgraded with a larger programdata size */ -static void -test_program_upgraded_with_larger_programdata( void ) { - FD_LOG_NOTICE(( "Testing: Program gets upgraded with a larger programdata size" )); - - test_xid = create_test_funk_txn(); - fd_bank_slot_set( test_bank, 0UL ); - - /* Create a BPF loader account */ - create_test_account( &test_program_pubkey, - &fd_solana_bpf_loader_program_id, - valid_program_data, - valid_program_data_sz, - 1 ); - - /* First call to create cache entry */ - fd_program_cache_update_program( test_bank, test_funk, &test_xid, &test_program_pubkey, test_spad ); - - /* Verify cache entry was created */ - fd_program_cache_entry_t const * valid_prog = NULL; - int err = fd_program_cache_load_entry( test_funk, &test_xid, &test_program_pubkey, &valid_prog ); - FD_TEST( !err ); - FD_TEST( valid_prog ); - FD_TEST( valid_prog->magic==FD_PROGRAM_CACHE_ENTRY_MAGIC ); - FD_TEST( !valid_prog->failed_verification ); - FD_TEST( valid_prog->last_slot_modified==0UL ); - FD_TEST( valid_prog->last_slot_verified==0UL ); - - /* Fast forward to a future slot */ - ulong original_slot = fd_bank_slot_get( test_bank ); - fd_bank_slot_set( test_bank, fd_bank_slot_get( test_bank ) + 11000UL ); - ulong future_slot = fd_bank_slot_get( test_bank ); - FD_TEST( future_slot>original_slot ); - - /* "Upgrade" the program by modifying the programdata */ - update_account_data( &test_program_pubkey, bigger_valid_program_data, bigger_valid_program_data_sz ); - - /* Queue the program for reverification */ - fd_program_cache_queue_program_for_reverification( test_funk, &test_xid, &test_program_pubkey, future_slot ); - - /* Verify the cache entry was updated with the future slot as last_slot_modified */ - err = fd_program_cache_load_entry( test_funk, &test_xid, &test_program_pubkey, &valid_prog ); - FD_TEST( !err ); - FD_TEST( valid_prog ); - FD_TEST( valid_prog->magic==FD_PROGRAM_CACHE_ENTRY_MAGIC ); - FD_TEST( !valid_prog->failed_verification ); - FD_TEST( valid_prog->last_slot_modified==future_slot ); - FD_TEST( valid_prog->last_slot_verified==original_slot ); - FD_TEST( valid_prog->last_slot_modified>original_slot ); - - /* Store the old program cache funk record size */ - fd_funk_rec_key_t id = fd_program_cache_key( &test_program_pubkey ); - fd_funk_rec_query_t query[1]; - fd_funk_rec_t const * prev_rec = fd_funk_rec_query_try_global( test_funk, &test_xid, &id, NULL, query ); - FD_TEST( prev_rec ); - ulong prev_rec_sz = prev_rec->val_sz; - FD_TEST( !fd_funk_rec_query_test( query ) ); - - /* Program invoked, update cache entry */ - fd_program_cache_update_program( test_bank, test_funk, &test_xid, &test_program_pubkey, test_spad ); - - /* Get the new program cache funk record size, and make sure it's - larger */ - fd_funk_rec_t const * new_rec = fd_funk_rec_query_try_global( test_funk, &test_xid, &id, NULL, query ); - FD_TEST( new_rec ); - ulong new_rec_sz = new_rec->val_sz; - FD_TEST( new_rec_sz>prev_rec_sz ); - FD_TEST( !fd_funk_rec_query_test( query ) ); - - /* Verify the cache entry was updated */ - err = fd_program_cache_load_entry( test_funk, &test_xid, &test_program_pubkey, &valid_prog ); - FD_TEST( !err ); - FD_TEST( valid_prog ); - FD_TEST( valid_prog->magic==FD_PROGRAM_CACHE_ENTRY_MAGIC ); - FD_TEST( !valid_prog->failed_verification ); - FD_TEST( valid_prog->last_slot_verified==future_slot ); - FD_TEST( valid_prog->last_slot_modified==future_slot ); - - fd_funk_txn_cancel( test_funk, &test_xid ); -} - -int -main( int argc, - char ** argv ) { - fd_boot( &argc, &argv ); - - FD_LOG_NOTICE(( "Starting BPF program cache tests" )); - - /* Create workspace */ - test_wksp = fd_wksp_new_anonymous( FD_SHMEM_GIGANTIC_PAGE_SZ, 3UL, fd_log_cpu_id(), "test_wksp", 0UL ); - FD_TEST( test_wksp ); - - /* Create funk */ - ulong funk_align = fd_funk_align(); - ulong funk_footprint = fd_funk_footprint( 1024UL, 1024UL ); - void * funk_mem = fd_wksp_alloc_laddr( test_wksp, funk_align, funk_footprint, TEST_WKSP_TAG ); - FD_TEST( funk_mem ); - - void * shfunk = fd_funk_new( funk_mem, 1234UL, 5678UL, 1024UL, 1024UL ); - FD_TEST( shfunk ); - - fd_funk_t funk_[1]; - test_funk = fd_funk_join( funk_, shfunk ); - FD_TEST( test_funk ); - - /* Create spad */ - ulong spad_align = fd_spad_align(); - ulong spad_footprint = fd_spad_footprint( SPAD_MEM_MAX ); - void * spad_mem = fd_wksp_alloc_laddr( test_wksp, spad_align, spad_footprint, TEST_WKSP_TAG ); - FD_TEST( spad_mem ); - - test_spad = fd_spad_join( fd_spad_new( spad_mem, SPAD_MEM_MAX ) ); - FD_TEST( test_spad ); - - FD_SPAD_FRAME_BEGIN( test_spad ) { - - /* Set up bank */ - ulong banks_footprint = fd_banks_footprint( 1UL, 1UL ); - uchar * banks_mem = fd_wksp_alloc_laddr( test_wksp, fd_banks_align(), banks_footprint, TEST_WKSP_TAG ); - FD_TEST( banks_mem ); - - fd_banks_t * banks = fd_banks_join( fd_banks_new( banks_mem, 1UL, 1UL ) ); - FD_TEST( banks ); - fd_bank_t * bank = fd_banks_init_bank( banks ); - fd_bank_slot_set( bank, 433000UL ); - FD_TEST( bank ); - - test_bank = bank; - test_banks = banks; - - fd_epoch_schedule_t epoch_schedule = { - .slots_per_epoch = 432000UL, - .leader_schedule_slot_offset = 432000UL, - .warmup = 0, - .first_normal_epoch = 0UL, - .first_normal_slot = 0UL - }; - fd_bank_epoch_schedule_set( bank, epoch_schedule ); - - test_account_does_not_exist(); - test_account_not_bpf_loader_owner(); - test_invalid_program_not_in_cache_first_time(); - test_valid_program_not_in_cache_first_time(); - test_program_in_cache_needs_reverification(); - test_program_in_cache_queued_for_reverification(); - test_program_queued_for_reverification_account_does_not_exist(); - test_program_in_cache_queued_for_reverification_and_processed(); - test_invalid_genesis_program_reverified_after_genesis(); - test_valid_genesis_program_reverified_after_genesis(); - test_program_upgraded_with_larger_programdata(); - } FD_SPAD_FRAME_END; - - test_teardown(); - - FD_LOG_NOTICE(( "pass" )); - fd_halt(); - return 0; -} - -#undef TEST_WKSP_TAG - -#else - -int -main( int argc, - char ** argv ) { - fd_boot( &argc, &argv ); - FD_LOG_WARNING(( "skip: unit test requires FD_HAS_HOSTED capabilities" )); - fd_halt(); - return 0; -} - -#endif diff --git a/src/flamenco/runtime/program/test_program_cache_concur.cxx b/src/flamenco/runtime/program/test_program_cache_concur.cxx deleted file mode 100644 index 057779323fd..00000000000 --- a/src/flamenco/runtime/program/test_program_cache_concur.cxx +++ /dev/null @@ -1,138 +0,0 @@ -#include "../../../funk/fd_funk_rec.h" -#include "../../../funk/fd_funk_txn.h" -#include "../../../funk/test_funk_common.hxx" -#include "fd_program_cache.h" -#include -#include - -#define NUM_THREADS 4 -#define MAX_TXN_CNT 4096 -#define NUM_KEYS 64 - -static volatile uint exp_val[NUM_KEYS] = {0}; - -struct test_funk_thread_arg { - fd_funk_t * funk; - fd_funk_txn_xid_t xid; - uint start_idx; - uint end_idx; -}; -typedef struct test_funk_thread_arg test_funk_thread_arg_t; - -static void * work_thread( void * _arg ) { - test_funk_thread_arg_t * arg = (test_funk_thread_arg_t *)_arg; - fd_funk_t * funk = arg->funk; - fd_funk_txn_xid_t const * xid = &arg->xid; - - for( ulong i=0UL; i<1024UL; i++ ) { - uint key_idx = ((uint)lrand48() % (arg->end_idx - arg->start_idx)) + arg->start_idx; - - /* Increment the value. */ - FD_ATOMIC_FETCH_AND_ADD( &exp_val[key_idx], 1 ); - - /* Update the record. */ - fd_funk_rec_key_t key = {}; - key.ul[0] = key_idx; - fd_funk_rec_insert_para( funk, xid, &key, alignof(uint), sizeof(uint), (void*)&exp_val[key_idx] ); - } - return NULL; - -} - -int main( int argc, char ** argv ) { - srand(1234); - - fd_boot( &argc, &argv ); - - char const * _page_sz = fd_env_strip_cmdline_cstr ( &argc, &argv, "--page-sz", NULL, "gigantic" ); - ulong page_cnt = fd_env_strip_cmdline_ulong( &argc, &argv, "--page-cnt", NULL, 1UL ); - ulong near_cpu = fd_env_strip_cmdline_ulong( &argc, &argv, "--near-cpu", NULL, fd_log_cpu_id() ); - - ulong page_sz = fd_cstr_to_shmem_page_sz( _page_sz ); - - ulong txn_max = MAX_TXN_CNT; - uint rec_max = 1<<20; - fd_wksp_t * wksp = fd_wksp_new_anonymous( page_sz, page_cnt, near_cpu, "wksp", 0UL ); - FD_TEST( wksp ); - void * mem = fd_wksp_alloc_laddr( - wksp, - fd_funk_align(), - fd_funk_footprint( txn_max, rec_max ), - FD_FUNK_MAGIC ); - FD_TEST( mem ); - fd_funk_t funk_[1]; - fd_funk_t * funk = fd_funk_join( funk_, fd_funk_new( mem, 1, 1234U, txn_max, rec_max ) ); - FD_TEST( funk ); - - /* Insert the records with their initial values. (0) */ - for( uint i = 0; i < NUM_KEYS; ++i ) { - fd_funk_rec_key_t key = {}; - key.ul[0] = i; - fd_funk_rec_prepare_t prepare[1]; - fd_funk_rec_t * rec = fd_funk_rec_prepare( funk, fd_funk_last_publish( funk ), &key, prepare, NULL ); - FD_TEST( rec ); - - void * val = fd_funk_val_truncate( - rec, - fd_funk_alloc( funk ), - fd_funk_wksp( funk ), - alignof(uint), - sizeof(uint), - NULL - ); - FD_TEST( val ); - - /* Set the value to 0. */ - uint * val_ul = (uint *)val; - *val_ul = 0U; - fd_funk_rec_publish( funk, prepare ); - } - - fd_funk_txn_xid_t parent_xid; fd_funk_txn_xid_set_root( &parent_xid ); - fd_funk_txn_xid_t xid = {{0}}; - - /* Number of iterations to run. */ - for( ulong i=0UL; ifunk ); + fd_progcache_clear( runner->progcache ); } /* Sets up block execution context from an input test case to execute against the runtime. @@ -181,7 +182,8 @@ fd_runtime_fuzz_block_ctx_create( fd_solfuzz_runner_t * runner, /* Create temporary funk transaction and slot / epoch contexts */ fd_funk_txn_xid_t parent_xid; fd_funk_txn_xid_set_root( &parent_xid ); - fd_funk_txn_prepare( funk, &parent_xid, xid ); + fd_funk_txn_prepare( funk, &parent_xid, xid ); + fd_funk_txn_prepare( runner->progcache->funk, &parent_xid, xid ); /* Restore feature flags */ fd_features_t features = {0}; @@ -301,7 +303,7 @@ fd_runtime_fuzz_block_ctx_create( fd_solfuzz_runner_t * runner, /* Refresh the program cache */ - fd_runtime_fuzz_refresh_program_cache( bank, funk, xid, test_ctx->acct_states, test_ctx->acct_states_count, runner->spad ); + fd_runtime_fuzz_refresh_program_cache( bank, runner->progcache, funk, xid, test_ctx->acct_states, test_ctx->acct_states_count ); /* Update vote cache for epoch T-1 */ vote_states_prev = fd_bank_vote_states_prev_locking_modify( bank ); @@ -347,7 +349,8 @@ fd_runtime_fuzz_block_ctx_create( fd_solfuzz_runner_t * runner, /* Make a new funk transaction since we're done loading in accounts for context */ fd_funk_txn_xid_t fork_xid = { .ul = { slot, slot } }; - fd_funk_txn_prepare( funk, xid, &fork_xid ); + fd_funk_txn_prepare( funk, xid, &fork_xid ); + fd_funk_txn_prepare( runner->progcache->funk, xid, &fork_xid ); xid[0] = fork_xid; /* Reset the lthash to zero, because we are in a new Funk transaction now */ @@ -476,7 +479,7 @@ fd_runtime_fuzz_block_ctx_exec( fd_solfuzz_runner_t * runner, fd_txn_p_t * txn = &txn_ptrs[i]; /* Update the program cache */ - fd_runtime_update_program_cache( runner->bank, runner->funk, xid, txn, runner->spad ); + fd_runtime_update_program_cache( runner->bank, runner->progcache, runner->funk, xid, txn, runner->spad ); /* Execute the transaction against the runtime */ res = FD_RUNTIME_EXECUTE_SUCCESS; @@ -490,6 +493,7 @@ fd_runtime_fuzz_block_ctx_exec( fd_solfuzz_runner_t * runner, /* Finalize the transaction */ fd_runtime_finalize_txn( runner->funk, + runner->progcache, NULL, xid, txn_ctx, diff --git a/src/flamenco/runtime/tests/fd_dump_pb.c b/src/flamenco/runtime/tests/fd_dump_pb.c index 3b6d2bca8b3..6b1b1cd13e0 100644 --- a/src/flamenco/runtime/tests/fd_dump_pb.c +++ b/src/flamenco/runtime/tests/fd_dump_pb.c @@ -7,7 +7,7 @@ #include "../fd_bank.h" #include "../program/fd_address_lookup_table_program.h" #include "../../../ballet/nanopb/pb_encode.h" -#include "../program/fd_program_cache.h" +#include "../../progcache/fd_prog_load.h" #include /* fopen */ @@ -1123,10 +1123,8 @@ FD_SPAD_FRAME_BEGIN( txn_ctx->spad ) { /* Get the programdata for the account */ ulong program_data_len = 0UL; - uchar const * program_data = fd_program_cache_get_account_programdata( txn_ctx->funk, - txn_ctx->xid, - program_acc, - &program_data_len ); + uchar const * program_data = + fd_prog_load_elf( txn_ctx->funk, txn_ctx->xid, program_acc, &program_data_len, NULL ); if( program_data==NULL ) { return; } diff --git a/src/flamenco/runtime/tests/fd_elf_harness.c b/src/flamenco/runtime/tests/fd_elf_harness.c index ef291837d6c..86a0f366df0 100644 --- a/src/flamenco/runtime/tests/fd_elf_harness.c +++ b/src/flamenco/runtime/tests/fd_elf_harness.c @@ -4,6 +4,7 @@ #include "../../../ballet/sbpf/fd_sbpf_loader.h" #include "../program/fd_bpf_loader_program.h" #include "../../vm/fd_vm_base.h" +#include "../../progcache/fd_prog_load.h" #define SORT_NAME sort_ulong #define SORT_KEY_T ulong @@ -53,11 +54,11 @@ fd_solfuzz_elf_loader_run( fd_solfuzz_runner_t * runner, fd_sbpf_loader_config_t config = { .elf_deploy_checks = input->deploy_checks, }; - fd_bpf_get_sbpf_versions( - &config.sbpf_min_version, - &config.sbpf_max_version, - UINT_MAX, - &feature_set ); + + fd_prog_versions_t versions[1]; + fd_prog_versions( &feature_set, UINT_MAX ); + config.sbpf_min_version = versions->min_sbpf_version; + config.sbpf_max_version = versions->max_sbpf_version; int err = fd_sbpf_elf_peek( &info, elf_bin, elf_sz, &config ); diff --git a/src/flamenco/runtime/tests/fd_harness_common.c b/src/flamenco/runtime/tests/fd_harness_common.c index bc65069ba36..1248c0b8c02 100644 --- a/src/flamenco/runtime/tests/fd_harness_common.c +++ b/src/flamenco/runtime/tests/fd_harness_common.c @@ -1,4 +1,4 @@ -#include "../program/fd_program_cache.h" +#include "../../progcache/fd_progcache.h" #include "generated/context.pb.h" #include "../fd_acc_mgr.h" #include "../../features/fd_features.h" @@ -73,14 +73,14 @@ fd_runtime_fuzz_restore_features( fd_features_t * features, void fd_runtime_fuzz_refresh_program_cache( fd_bank_t * bank, - fd_funk_t * funk, + fd_progcache_t * progcache, + fd_funk_t * accdb, fd_funk_txn_xid_t const * xid, fd_exec_test_acct_state_t const * acct_states, - ulong acct_states_count, - fd_spad_t * runtime_spad ) { + ulong acct_states_count ) { for( ushort i=0; iprogcache->funk, &parent_xid, xid ); /* Allocate contexts */ uchar * txn_ctx_mem = fd_spad_alloc( runner->spad,FD_EXEC_TXN_CTX_ALIGN, FD_EXEC_TXN_CTX_FOOTPRINT ); @@ -76,7 +77,8 @@ fd_runtime_fuzz_instr_ctx_create( fd_solfuzz_runner_t * runner, txn_descriptor->acct_addr_cnt = (ushort)test_ctx->accounts_count; fd_exec_txn_ctx_setup( runner->bank, - runner->funk, + runner->funk->shmem, + runner->progcache->funk->shmem, xid, NULL, txn_ctx, @@ -250,7 +252,7 @@ fd_runtime_fuzz_instr_ctx_create( fd_solfuzz_runner_t * runner, } /* Refresh the program cache */ - fd_runtime_fuzz_refresh_program_cache( runner->bank, funk, xid, test_ctx->accounts, test_ctx->accounts_count, runner->spad ); + fd_runtime_fuzz_refresh_program_cache( runner->bank, runner->progcache, funk, xid, test_ctx->accounts, test_ctx->accounts_count ); /* Load instruction accounts */ @@ -308,7 +310,8 @@ fd_runtime_fuzz_instr_ctx_create( fd_solfuzz_runner_t * runner, /* Refresh the setup from the updated slot and epoch ctx. */ fd_exec_txn_ctx_setup( runner->bank, - runner->funk, + runner->funk->shmem, + runner->progcache->funk->shmem, xid, NULL, txn_ctx, @@ -327,6 +330,7 @@ fd_runtime_fuzz_instr_ctx_destroy( fd_solfuzz_runner_t * runner, fd_exec_instr_ctx_t * ctx ) { if( !ctx ) return; fd_funk_txn_cancel_all( runner->funk ); + fd_progcache_clear( runner->progcache ); } diff --git a/src/flamenco/runtime/tests/fd_solfuzz.c b/src/flamenco/runtime/tests/fd_solfuzz.c index bf409032b44..39f9f0b5da7 100644 --- a/src/flamenco/runtime/tests/fd_solfuzz.c +++ b/src/flamenco/runtime/tests/fd_solfuzz.c @@ -77,26 +77,34 @@ fd_solfuzz_runner_new( fd_wksp_t * wksp, fd_solfuzz_runner_options_t const * options ) { /* Allocate objects */ - ulong const txn_max = 64UL; - ulong const rec_max = 1024UL; + ulong const txn_max = 16UL; + ulong const rec_max = 128UL; ulong const spad_max = FD_RUNTIME_TRANSACTION_EXECUTION_FOOTPRINT_FUZZ; ulong const bank_max = 1UL; ulong const fork_max = 1UL; - fd_solfuzz_runner_t * runner = fd_wksp_alloc_laddr( wksp, alignof(fd_solfuzz_runner_t), sizeof(fd_solfuzz_runner_t), wksp_tag ); - void * funk_mem = fd_wksp_alloc_laddr( wksp, fd_funk_align(), fd_funk_footprint( txn_max, rec_max ), wksp_tag ); - void * spad_mem = fd_wksp_alloc_laddr( wksp, fd_spad_align(), fd_spad_footprint( spad_max ), wksp_tag ); - void * banks_mem = fd_wksp_alloc_laddr( wksp, fd_banks_align(), fd_banks_footprint( bank_max, fork_max ), wksp_tag ); - if( FD_UNLIKELY( !runner ) ) { FD_LOG_WARNING(( "fd_wksp_alloc(solfuzz_runner) failed" )); goto bail1; } - if( FD_UNLIKELY( !funk_mem ) ) { FD_LOG_WARNING(( "fd_wksp_alloc(funk) failed" )); goto bail1; } - if( FD_UNLIKELY( !spad_mem ) ) { FD_LOG_WARNING(( "fd_wksp_alloc(spad) failed (spad_max=%g)", (double)spad_max )); goto bail1; } - if( FD_UNLIKELY( !banks_mem ) ) { FD_LOG_WARNING(( "fd_wksp_alloc(banks) failed (bank_max=%lu fork_max=%lu)", bank_max, fork_max )); goto bail1; } + fd_solfuzz_runner_t * runner = fd_wksp_alloc_laddr( wksp, alignof(fd_solfuzz_runner_t), sizeof(fd_solfuzz_runner_t), wksp_tag ); + void * funk_mem = fd_wksp_alloc_laddr( wksp, fd_funk_align(), fd_funk_footprint( txn_max, rec_max ), wksp_tag ); + void * pcache_mem = fd_wksp_alloc_laddr( wksp, fd_funk_align(), fd_funk_footprint( txn_max, rec_max ), wksp_tag ); + void * spad_mem = fd_wksp_alloc_laddr( wksp, fd_spad_align(), fd_spad_footprint( spad_max ), wksp_tag ); + void * banks_mem = fd_wksp_alloc_laddr( wksp, fd_banks_align(), fd_banks_footprint( bank_max, fork_max ), wksp_tag ); + if( FD_UNLIKELY( !runner ) ) { FD_LOG_WARNING(( "fd_wksp_alloc(solfuzz_runner) failed" )); goto bail1; } + if( FD_UNLIKELY( !funk_mem ) ) { FD_LOG_WARNING(( "fd_wksp_alloc(funk) failed" )); goto bail1; } + if( FD_UNLIKELY( !pcache_mem ) ) { FD_LOG_WARNING(( "fd_wksp_alloc(funk) failed" )); goto bail1; } + if( FD_UNLIKELY( !spad_mem ) ) { FD_LOG_WARNING(( "fd_wksp_alloc(spad) failed (spad_max=%g)", (double)spad_max )); goto bail1; } + if( FD_UNLIKELY( !banks_mem ) ) { FD_LOG_WARNING(( "fd_wksp_alloc(banks) failed (bank_max=%lu fork_max=%lu)", bank_max, fork_max )); goto bail1; } /* Create objects */ fd_memset( runner, 0, sizeof(fd_solfuzz_runner_t) ); runner->wksp = wksp; - void * shfunk = fd_funk_new( funk_mem, wksp_tag, 1UL, txn_max, rec_max ); - if( FD_UNLIKELY( !shfunk ) ) goto bail1; - if( FD_UNLIKELY( !fd_funk_join( runner->funk, funk_mem ) ) ) goto bail2; + + void * shfunk = fd_funk_new( funk_mem, wksp_tag, 1UL, txn_max, rec_max ); + void * shpcache = fd_funk_new( pcache_mem, wksp_tag, 1UL, txn_max, rec_max ); + if( FD_UNLIKELY( !shfunk ) ) goto bail1; + if( FD_UNLIKELY( !shpcache ) ) goto bail1; + + if( FD_UNLIKELY( !fd_funk_join ( runner->funk, funk_mem ) ) ) goto bail2; + if( FD_UNLIKELY( !fd_progcache_join( runner->progcache, pcache_mem ) ) ) goto bail2; + runner->spad = fd_spad_join( fd_spad_new( spad_mem, spad_max ) ); if( FD_UNLIKELY( !runner->spad ) ) goto bail2; runner->banks = fd_banks_join( fd_banks_new( banks_mem, bank_max, fork_max ) ); @@ -109,17 +117,20 @@ fd_solfuzz_runner_new( fd_wksp_t * wksp, fd_bank_slot_set( runner->bank, 0UL ); runner->enable_vm_tracing = options->enable_vm_tracing; + FD_TEST( runner->progcache->funk->shmem ); return runner; bail2: if( runner->spad ) fd_spad_delete( fd_spad_leave( runner->spad ) ); - if( shfunk ) fd_funk_delete( funk_mem ); /* free underlying fd_alloc instance */ + if( shfunk ) fd_funk_delete( shfunk ); /* free underlying fd_alloc instance */ + if( shpcache ) fd_funk_delete( shpcache ); if( runner->banks ) fd_banks_delete( fd_banks_leave( runner->banks ) ); bail1: - fd_wksp_free_laddr( funk_mem ); - fd_wksp_free_laddr( spad_mem ); - fd_wksp_free_laddr( banks_mem ); - fd_wksp_free_laddr( runner ); + fd_wksp_free_laddr( pcache_mem ); + fd_wksp_free_laddr( funk_mem ); + fd_wksp_free_laddr( spad_mem ); + fd_wksp_free_laddr( banks_mem ); + fd_wksp_free_laddr( runner ); FD_LOG_WARNING(( "fd_solfuzz_runner_new failed" )); return NULL; } diff --git a/src/flamenco/runtime/tests/fd_solfuzz.h b/src/flamenco/runtime/tests/fd_solfuzz.h index b429508bdcd..a32f04469a1 100644 --- a/src/flamenco/runtime/tests/fd_solfuzz.h +++ b/src/flamenco/runtime/tests/fd_solfuzz.h @@ -12,6 +12,7 @@ #include "../../capture/fd_solcap_writer.h" #include "../../../funk/fd_funk.h" +#include "../../progcache/fd_progcache.h" /* A fd_solfuzz_runner_t object processes solfuzz inputs. Can be reused for different inputs, even of different types. Single-thread per @@ -22,6 +23,7 @@ struct fd_solfuzz_runner { fd_funk_t funk[1]; + fd_progcache_t progcache[1]; fd_wksp_t * wksp; fd_spad_t * spad; fd_banks_t * banks; diff --git a/src/flamenco/runtime/tests/fd_solfuzz_private.h b/src/flamenco/runtime/tests/fd_solfuzz_private.h index 9a41cfd97e1..d5cd8081370 100644 --- a/src/flamenco/runtime/tests/fd_solfuzz_private.h +++ b/src/flamenco/runtime/tests/fd_solfuzz_private.h @@ -30,11 +30,11 @@ fd_runtime_fuzz_restore_features( fd_features_t * features, void fd_runtime_fuzz_refresh_program_cache( fd_bank_t * bank, - fd_funk_t * funk, + fd_progcache_t * progcache, + fd_funk_t * accdb, fd_funk_txn_xid_t const * xid, fd_exec_test_acct_state_t const * acct_states, - ulong acct_states_count, - fd_spad_t * runtime_spad ); + ulong acct_states_count ); typedef ulong( exec_test_run_fn_t )( fd_solfuzz_runner_t *, void const *, diff --git a/src/flamenco/runtime/tests/fd_txn_harness.c b/src/flamenco/runtime/tests/fd_txn_harness.c index 152ab59323c..446917be4ff 100644 --- a/src/flamenco/runtime/tests/fd_txn_harness.c +++ b/src/flamenco/runtime/tests/fd_txn_harness.c @@ -40,6 +40,7 @@ fd_runtime_fuzz_xid_cancel( fd_solfuzz_runner_t * runner, fd_funk_txn_xid_t * xid ) { if( FD_UNLIKELY( !xid ) ) return; // This shouldn't be false either fd_funk_txn_cancel( runner->funk, xid ); + fd_progcache_clear( runner->progcache ); } /* Creates transaction execution context for a single test case. Returns a @@ -55,7 +56,8 @@ fd_runtime_fuzz_txn_ctx_create( fd_solfuzz_runner_t * runner, /* Set up the funk transaction */ fd_funk_txn_xid_t xid = { .ul = { slot, slot } }; fd_funk_txn_xid_t parent_xid; fd_funk_txn_xid_set_root( &parent_xid ); - fd_funk_txn_prepare( funk, &parent_xid, &xid ); + fd_funk_txn_prepare( funk, &parent_xid, &xid ); + fd_funk_txn_prepare( runner->progcache->funk, &parent_xid, &xid ); /* Set up slot context */ fd_banks_clear_bank( runner->banks, runner->bank ); @@ -203,7 +205,7 @@ fd_runtime_fuzz_txn_ctx_create( fd_solfuzz_runner_t * runner, fd_sysvar_cache_restore_fuzz( runner->bank, runner->funk, &xid ); /* Refresh the program cache */ - fd_runtime_fuzz_refresh_program_cache( runner->bank, runner->funk, &xid, test_ctx->account_shared_data, test_ctx->account_shared_data_count, runner->spad ); + fd_runtime_fuzz_refresh_program_cache( runner->bank, runner->progcache, runner->funk, &xid, test_ctx->account_shared_data, test_ctx->account_shared_data_count ); /* Create the raw txn (https://solana.com/docs/core/transactions#transaction-size) */ fd_txn_p_t * txn = fd_spad_alloc( runner->spad, alignof(fd_txn_p_t), sizeof(fd_txn_p_t) ); @@ -343,7 +345,13 @@ fd_runtime_fuzz_txn_ctx_exec( fd_solfuzz_runner_t * runner, uchar * txn_ctx_mem = fd_spad_alloc( runner->spad, FD_EXEC_TXN_CTX_ALIGN, FD_EXEC_TXN_CTX_FOOTPRINT ); fd_exec_txn_ctx_t * txn_ctx = fd_exec_txn_ctx_join( fd_exec_txn_ctx_new( txn_ctx_mem ), runner->spad, fd_wksp_containing( runner->spad ) ); txn_ctx->flags = FD_TXN_P_FLAGS_SANITIZE_SUCCESS; - txn_ctx->funk[0] = *runner->funk; + if( FD_UNLIKELY( !fd_funk_join( txn_ctx->funk, runner->funk->shmem ) ) ) { + FD_LOG_CRIT(( "fd_funk_join failed" )); + } + txn_ctx->progcache = fd_progcache_join( txn_ctx->_progcache, runner->progcache->funk->shmem ); + if( FD_UNLIKELY( !txn_ctx->progcache ) ) { + FD_LOG_CRIT(( "fd_progcache_join failed" )); + } txn_ctx->bank_hash_cmp = NULL; txn_ctx->fuzz_config.enable_vm_tracing = runner->enable_vm_tracing; txn_ctx->xid[0] = *xid; diff --git a/src/flamenco/vm/fd_vm.c b/src/flamenco/vm/fd_vm.c index 6e413b52375..e2ef98f7fb6 100644 --- a/src/flamenco/vm/fd_vm.c +++ b/src/flamenco/vm/fd_vm.c @@ -585,7 +585,7 @@ fd_vm_init( ulong text_off, ulong text_sz, ulong entry_pc, - ulong * calldests, + ulong const * calldests, ulong sbpf_version, fd_sbpf_syscalls_t * syscalls, fd_vm_trace_t * trace, diff --git a/src/flamenco/vm/fd_vm.h b/src/flamenco/vm/fd_vm.h index f9c029cd8f0..a5e01eb2775 100644 --- a/src/flamenco/vm/fd_vm.h +++ b/src/flamenco/vm/fd_vm.h @@ -292,7 +292,7 @@ fd_vm_init( ulong text_off, ulong text_sz, ulong entry_pc, - ulong * calldests, + ulong const * calldests, ulong sbpf_version, fd_sbpf_syscalls_t * syscalls, fd_vm_trace_t * trace, diff --git a/src/funk/fd_funk_base.h b/src/funk/fd_funk_base.h index f1caa79f8b0..fed6201c6b9 100644 --- a/src/funk/fd_funk_base.h +++ b/src/funk/fd_funk_base.h @@ -66,7 +66,7 @@ facilitate compile time declarations. */ #define FD_FUNK_REC_KEY_ALIGN (8UL) -#define FD_FUNK_REC_KEY_FOOTPRINT (40UL) /* 32 byte hash + 8 byte meta */ +#define FD_FUNK_REC_KEY_FOOTPRINT (32UL) /* A fd_funk_rec_key_t identifies a funk record. Compact binary keys are encouraged but a cstr can be used so long as it has @@ -77,8 +77,8 @@ union __attribute__((aligned(FD_FUNK_REC_KEY_ALIGN))) fd_funk_rec_key { uchar uc[ FD_FUNK_REC_KEY_FOOTPRINT ]; - uint ui[ 10 ]; - ulong ul[ 5 ]; + uint ui[ 8 ]; + ulong ul[ 4 ]; }; typedef union fd_funk_rec_key fd_funk_rec_key_t; @@ -113,7 +113,7 @@ typedef union fd_funk_txn_xid fd_funk_txn_xid_t; facilitate compile time declarations. */ #define FD_FUNK_XID_KEY_PAIR_ALIGN (8UL) -#define FD_FUNK_XID_KEY_PAIR_FOOTPRINT (56UL) +#define FD_FUNK_XID_KEY_PAIR_FOOTPRINT (48UL) /* A fd_funk_xid_key_pair_t identifies a funk record. It is just xid and key packed into the same structure. */ @@ -183,7 +183,6 @@ fd_funk_rec_key_hash1( uchar const key[ 32 ], FD_FN_PURE static inline ulong fd_funk_rec_key_hash( fd_funk_rec_key_t const * k, ulong seed ) { - seed ^= k->ul[4]; /* tons of ILP */ return (fd_ulong_hash( seed ^ (1UL<<0) ^ k->ul[0] ) ^ fd_ulong_hash( seed ^ (1UL<<1) ^ k->ul[1] ) ) ^ (fd_ulong_hash( seed ^ (1UL<<2) ^ k->ul[2] ) ^ fd_ulong_hash( seed ^ (1UL<<3) ^ k->ul[3] ) ); @@ -211,7 +210,7 @@ fd_funk_rec_key_hash1( uchar const key[ 32 ], FD_FN_PURE static inline ulong fd_funk_rec_key_hash( fd_funk_rec_key_t const * k, ulong seed ) { - return fd_funk_rec_key_hash1( k->uc, k->ul[4], seed ); + return fd_funk_rec_key_hash1( k->uc, 0UL, seed ); } #endif /* FD_HAS_INT128 */ @@ -222,10 +221,10 @@ fd_funk_rec_key_hash( fd_funk_rec_key_t const * k, FD_FN_UNUSED FD_FN_PURE static int /* Workaround -Winline */ fd_funk_rec_key_eq( fd_funk_rec_key_t const * ka, - fd_funk_rec_key_t const * kb ) { + fd_funk_rec_key_t const * kb ) { ulong const * a = ka->ul; ulong const * b = kb->ul; - return !( ((a[0]^b[0]) | (a[1]^b[1])) | ((a[2]^b[2]) | (a[3]^b[3])) | (a[4]^b[4]) ) ; + return !( ((a[0]^b[0]) | (a[1]^b[1])) | ((a[2]^b[2]) | (a[3]^b[3])) ) ; } /* fd_funk_rec_key_copy copies the key pointed to by ks into the key @@ -234,10 +233,10 @@ fd_funk_rec_key_eq( fd_funk_rec_key_t const * ka, static inline fd_funk_rec_key_t * fd_funk_rec_key_copy( fd_funk_rec_key_t * kd, - fd_funk_rec_key_t const * ks ) { + fd_funk_rec_key_t const * ks ) { ulong * d = kd->ul; ulong const * s = ks->ul; - d[0] = s[0]; d[1] = s[1]; d[2] = s[2]; d[3] = s[3]; d[4] = s[4]; + d[0] = s[0]; d[1] = s[1]; d[2] = s[2]; d[3] = s[3]; return kd; } @@ -250,7 +249,7 @@ fd_funk_rec_key_copy( fd_funk_rec_key_t * kd, FD_FN_UNUSED FD_FN_PURE static ulong /* Work around -Winline */ fd_funk_txn_xid_hash( fd_funk_txn_xid_t const * x, - ulong seed ) { + ulong seed ) { return ( fd_ulong_hash( seed ^ (1UL<<0) ^ x->ul[0] ) ^ fd_ulong_hash( seed ^ (1UL<<1) ^ x->ul[1] ) ); /* tons of ILP */ } @@ -260,7 +259,7 @@ fd_funk_txn_xid_hash( fd_funk_txn_xid_t const * x, FD_FN_PURE static inline int fd_funk_txn_xid_eq( fd_funk_txn_xid_t const * xa, - fd_funk_txn_xid_t const * xb ) { + fd_funk_txn_xid_t const * xb ) { ulong const * a = xa->ul; ulong const * b = xb->ul; return !( (a[0]^b[0]) | (a[1]^b[1]) ); @@ -272,7 +271,7 @@ fd_funk_txn_xid_eq( fd_funk_txn_xid_t const * xa, static inline fd_funk_txn_xid_t * fd_funk_txn_xid_copy( fd_funk_txn_xid_t * xd, - fd_funk_txn_xid_t const * xs ) { + fd_funk_txn_xid_t const * xs ) { ulong * d = xd->ul; ulong const * s = xs->ul; d[0] = s[0]; d[1] = s[1]; diff --git a/src/funk/test_funk_base.c b/src/funk/test_funk_base.c index c9254fc0380..a3ae88baa8b 100644 --- a/src/funk/test_funk_base.c +++ b/src/funk/test_funk_base.c @@ -10,7 +10,7 @@ FD_STATIC_ASSERT( FD_FUNK_ERR_REC ==-6, FD_STATIC_ASSERT( FD_FUNK_ERR_MEM ==-7, unit_test ); FD_STATIC_ASSERT( FD_FUNK_REC_KEY_ALIGN ==8UL, unit_test ); -FD_STATIC_ASSERT( FD_FUNK_REC_KEY_FOOTPRINT ==40UL, unit_test ); +FD_STATIC_ASSERT( FD_FUNK_REC_KEY_FOOTPRINT ==32UL, unit_test ); FD_STATIC_ASSERT( FD_FUNK_REC_KEY_ALIGN ==alignof(fd_funk_rec_key_t), unit_test ); FD_STATIC_ASSERT( FD_FUNK_REC_KEY_FOOTPRINT ==sizeof (fd_funk_rec_key_t), unit_test ); @@ -22,7 +22,7 @@ FD_STATIC_ASSERT( FD_FUNK_TXN_XID_ALIGN ==alignof(fd_funk_txn_xid_t), FD_STATIC_ASSERT( FD_FUNK_TXN_XID_FOOTPRINT ==sizeof (fd_funk_txn_xid_t), unit_test ); FD_STATIC_ASSERT( FD_FUNK_XID_KEY_PAIR_ALIGN ==8UL, unit_test ); -FD_STATIC_ASSERT( FD_FUNK_XID_KEY_PAIR_FOOTPRINT==56UL, unit_test ); +FD_STATIC_ASSERT( FD_FUNK_XID_KEY_PAIR_FOOTPRINT==48UL, unit_test ); FD_STATIC_ASSERT( FD_FUNK_XID_KEY_PAIR_ALIGN ==alignof(fd_funk_xid_key_pair_t), unit_test ); FD_STATIC_ASSERT( FD_FUNK_XID_KEY_PAIR_FOOTPRINT==sizeof (fd_funk_xid_key_pair_t), unit_test ); @@ -39,7 +39,6 @@ fd_funk_rec_key_set_unique( fd_funk_rec_key_t * key ) { # else key->ul[3] = 0UL; # endif - key->ul[4] = ~key->ul[0]; return key; } diff --git a/src/funk/test_funk_common.h b/src/funk/test_funk_common.h index 19261682fb9..3c554fe9a58 100644 --- a/src/funk/test_funk_common.h +++ b/src/funk/test_funk_common.h @@ -178,7 +178,6 @@ __attribute__((noinline)) static FD_FN_UNUSED fd_funk_rec_key_t * key_set( fd_funk_rec_key_t * key, ulong _key ) { key->ul[0] = _key; key->ul[1] = _key+_key; key->ul[2] = _key*_key; key->ul[3] = -_key; - key->ul[4] = _key*3U; return key; }