diff --git a/src/app/fdctl/Local.mk b/src/app/fdctl/Local.mk index b22d28d5b1..bb7952bd5b 100644 --- a/src/app/fdctl/Local.mk +++ b/src/app/fdctl/Local.mk @@ -50,6 +50,8 @@ $(call add-objs,run/tiles/fd_poh_int,fd_fdctl) $(call add-objs,run/tiles/fd_sender,fd_fdctl) $(call add-objs,run/tiles/fd_eqvoc,fd_fdctl) $(call add-objs,run/tiles/fd_rpcserv,fd_fdctl) +$(call add-objs,run/tiles/fd_snapshot,fd_fdctl) +$(call add-objs,run/tiles/fd_snapshot_thread,fd_fdctl) endif # fdctl topologies @@ -104,6 +106,7 @@ $(OBJDIR)/obj/app/fdctl/run/tiles/fd_replay.o: src/app/fdctl/run/tiles/generated $(OBJDIR)/obj/app/fdctl/run/tiles/fd_sender.o: src/app/fdctl/run/tiles/generated/sender_seccomp.h $(OBJDIR)/obj/app/fdctl/run/tiles/fd_eqvoc.o: src/app/fdctl/run/tiles/generated/eqvoc_seccomp.h $(OBJDIR)/obj/app/fdctl/run/tiles/fd_rpcserv.o: src/app/fdctl/run/tiles/generated/rpcserv_seccomp.h +$(OBJDIR)/obj/app/fdctl/run/tiles/fd_snaps.o: src/app/fdctl/run/tiles/generated/snapshot_seccomp.h endif check-agave-hash: diff --git a/src/app/fdctl/config.c b/src/app/fdctl/config.c index 88fcfde061..08212be8d6 100644 --- a/src/app/fdctl/config.c +++ b/src/app/fdctl/config.c @@ -262,7 +262,7 @@ fdctl_obj_footprint( fd_topo_t const * topo, } else if( FD_UNLIKELY( !strcmp( obj->name, "funk" ) ) ) { return fd_funk_footprint(); } else if( FD_UNLIKELY( !strcmp( obj->name, "txncache" ) ) ) { - return fd_txncache_footprint( VAL("max_rooted_slots"), VAL("max_live_slots"), VAL("max_txn_per_slot") ); + return fd_txncache_footprint( VAL("max_rooted_slots"), VAL("max_live_slots"), VAL("max_txn_per_slot"), FD_TXNCACHE_DEFAULT_MAX_CONSTIPATED_SLOTS ); } else { FD_LOG_ERR(( "unknown object `%s`", obj->name )); return 0UL; diff --git a/src/app/fdctl/config.h b/src/app/fdctl/config.h index dd87de775b..ad51b0839e 100644 --- a/src/app/fdctl/config.h +++ b/src/app/fdctl/config.h @@ -305,6 +305,13 @@ typedef struct { char shred_cap_replay[ PATH_MAX ]; } store_int; + struct { + ulong full_interval; + ulong incremental_interval; + char out_dir[ PATH_MAX ]; + ulong hash_tpool_thread_count; + } snaps; + } tiles; } config_t; diff --git a/src/app/fdctl/config/default-firedancer.toml b/src/app/fdctl/config/default-firedancer.toml index 85e78844e4..b042cb8086 100644 --- a/src/app/fdctl/config/default-firedancer.toml +++ b/src/app/fdctl/config/default-firedancer.toml @@ -22,6 +22,8 @@ cluster_version = "1.18.0" [tiles.pack] use_consumed_cus = false + [tiles.snaps] + hash_tpool_thread_count = 2 [consensus] vote = true diff --git a/src/app/fdctl/config_parse.c b/src/app/fdctl/config_parse.c index c4cfdf66b4..b283ad1ab8 100644 --- a/src/app/fdctl/config_parse.c +++ b/src/app/fdctl/config_parse.c @@ -389,6 +389,11 @@ fdctl_pod_to_cfg( config_t * config, CFG_POP ( cstr, tiles.store_int.shred_cap_archive ); CFG_POP ( cstr, tiles.store_int.shred_cap_replay ); + CFG_POP ( ulong, tiles.snaps.full_interval ); + CFG_POP ( ulong, tiles.snaps.incremental_interval ); + CFG_POP ( cstr, tiles.snaps.out_dir ); + CFG_POP ( ulong, tiles.snaps.hash_tpool_thread_count ); + # undef CFG_POP # undef CFG_ARRAY diff --git a/src/app/fdctl/main.c b/src/app/fdctl/main.c index 508dbc9193..97438782fe 100644 --- a/src/app/fdctl/main.c +++ b/src/app/fdctl/main.c @@ -37,6 +37,7 @@ extern fd_topo_run_tile_t fd_tile_repair; extern fd_topo_run_tile_t fd_tile_store_int; extern fd_topo_run_tile_t fd_tile_replay; extern fd_topo_run_tile_t fd_tile_replay_thread; +extern fd_topo_run_tile_t fd_tile_snaps_thread; extern fd_topo_run_tile_t fd_tile_poh_int; extern fd_topo_run_tile_t fd_tile_sender; extern fd_topo_run_tile_t fd_tile_eqvoc; diff --git a/src/app/fdctl/ready.c b/src/app/fdctl/ready.c index 7ffb1e5ab3..bbf9035019 100644 --- a/src/app/fdctl/ready.c +++ b/src/app/fdctl/ready.c @@ -23,8 +23,9 @@ ready_cmd_fn( args_t * args, anyway. */ if( FD_UNLIKELY( tile->is_agave ) ) continue; - /* Don't wait for thread tiles, they will not report ready. */ - if( strncmp( tile->name, "thread", 7 )==0 ) continue; + /* Don't wait for rtpool/stpool tiles, they will not report ready. */ + if( strncmp( tile->name, "rtpool", 7 )==0 ) continue; + if( strncmp( tile->name, "stpool", 7 )==0 ) continue; long start = fd_log_wallclock(); int printed = 0; diff --git a/src/app/fdctl/run/run.c b/src/app/fdctl/run/run.c index 32988fbdb1..4b95543018 100644 --- a/src/app/fdctl/run/run.c +++ b/src/app/fdctl/run/run.c @@ -13,6 +13,7 @@ #include "../../../waltz/xdp/fd_xdp1.h" #include "../../../flamenco/runtime/fd_blockstore.h" #include "../../../flamenco/runtime/fd_txncache.h" +#include "../../../funk/fd_funk_filemap.h" #include "../../../funk/fd_funk.h" #include "../configure/configure.h" @@ -538,7 +539,7 @@ fdctl_obj_new( fd_topo_t const * topo, } else if( FD_UNLIKELY( !strcmp( obj->name, "funk" ) ) ) { FD_TEST( fd_funk_new( laddr, VAL("wksp_tag"), VAL("seed"), VAL("txn_max"), VAL("rec_max") ) ); } else if( FD_UNLIKELY( !strcmp( obj->name, "txncache" ) ) ) { - FD_TEST( fd_txncache_new( laddr, VAL("max_rooted_slots"), VAL("max_live_slots"), VAL("max_txn_per_slot") ) ); + FD_TEST( fd_txncache_new( laddr, VAL("max_rooted_slots"), VAL("max_live_slots"), VAL("max_txn_per_slot"), FD_TXNCACHE_DEFAULT_MAX_CONSTIPATED_SLOTS ) ); } else { FD_LOG_ERR(( "unknown object `%s`", obj->name )); } diff --git a/src/app/fdctl/run/tiles/fd_replay.c b/src/app/fdctl/run/tiles/fd_replay.c index df06783a27..bdf003cc0a 100644 --- a/src/app/fdctl/run/tiles/fd_replay.c +++ b/src/app/fdctl/run/tiles/fd_replay.c @@ -32,6 +32,7 @@ #include "../../../../choreo/fd_choreo.h" #include "../../../../disco/store/fd_epoch_forks.h" #include "../../../../funk/fd_funk_filemap.h" +#include "../../../../flamenco/snapshot/fd_snapshot_create.h" #include "../../../../disco/plugin/fd_plugin.h" #include @@ -261,6 +262,17 @@ struct fd_replay_tile_ctx { fd_spad_t * spads[ 128UL ]; ulong spad_cnt; + + /* TODO: refactor this all into fd_replay_tile_snapshot_ctx_t. */ + ulong snapshot_interval; /* User defined parameter */ + ulong incremental_interval; /* User defined parameter */ + ulong last_full_snap; /* If a full snapshot has been produced */ + ulong * is_funk_constipated; /* Shared fseq to determine if funk should be constipated */ + ulong prev_full_snapshot_dist; /* Tracking for snapshot creation */ + ulong prev_incr_snapshot_dist; /* Tracking for incremental snapshot creation */ + int first_constipation; + + int is_caught_up; }; typedef struct fd_replay_tile_ctx fd_replay_tile_ctx_t; @@ -562,37 +574,213 @@ blockstore_publish( fd_replay_tile_ctx_t * ctx, ulong smr ) { } static void -funk_publish( fd_replay_tile_ctx_t * ctx, ulong smr ) { +txncache_publish( fd_replay_tile_ctx_t * ctx, + fd_funk_txn_t * txn_map, + fd_funk_txn_t * root_txn, + uchar is_funk_constipated ) { + + /* For the status cache, we stop rooting until the status cache has been + written out to the current snapshot. We also need to iterate up the + funk transaction tree to figure out what slots should be constipated. + + As a note, when funk is constipated we don't want to iterate all the way + up to the root because then we will register previously registered slots + that are in the constipated root. This introduces an edge case where + we will never register the slots that in the original constipated txn. + This currently gets handled in a hacky way by first tracking the first + call to is_funk_constipated. This gets set to 1 as soon as funk gets + constipated. By setting this, we are able to register the slot that + corresponds to the constipated root. */ + + if( FD_UNLIKELY( !ctx->slot_ctx->status_cache ) ) { + return; + } + + if( ctx->first_constipation ) { + FD_LOG_NOTICE(( "Starting constipation at slot=%lu", root_txn->xid.ul[0] )); + fd_txncache_register_constipated_slot( ctx->slot_ctx->status_cache, root_txn->xid.ul[0] ); + ctx->first_constipation = 0; + } + + fd_funk_txn_t * txn = root_txn; + while( txn ) { + + if( !fd_funk_txn_parent( txn, txn_map ) && is_funk_constipated ) { + break; + } + + ulong slot = txn->xid.ul[0]; + if( FD_LIKELY( !fd_txncache_get_is_constipated( ctx->slot_ctx->status_cache ) ) ) { + FD_LOG_INFO(( "Registering slot %lu", slot )); + fd_txncache_register_root_slot( ctx->slot_ctx->status_cache, slot ); + } else { + FD_LOG_INFO(( "Registering constipated slot %lu", slot )); + fd_txncache_register_constipated_slot( ctx->slot_ctx->status_cache, slot ); + } + txn = fd_funk_txn_parent( txn, txn_map ); + } +} + +static void +snapshot_state_update( fd_replay_tile_ctx_t * ctx, ulong smr, uchar is_constipated ) { + + /* We are ready for a snapshot if either we are on or just passed a snapshot + interval and no snapshot is currently in progress. This is to handle the + case where the snapshot interval falls on a skipped slot. + + We are ready to create a snapshot if: + 1. The node is caught up to the network. + 2. There is currently no snapshot in progress + 3. The current slot is at the snapshot interval OR + The current slot has passed a snapshot interval + + If a snapshot is ready to be created we will constipate funk and the + status cache. This will also notify the status cache via the funk + constipation fseq. */ + + if( !ctx->is_caught_up ) { + return; + } + + if( is_constipated ) { + return; + } + + fd_epoch_bank_t * epoch_bank = fd_exec_epoch_ctx_epoch_bank( ctx->slot_ctx->epoch_ctx ); + + /* The distance from the last snapshot should only grow until we skip + past the last full snapshot. If it has shrunk that means we skipped + over the snapshot interval. */ + ulong curr_full_snapshot_dist = smr % ctx->snapshot_interval; + uchar is_full_snapshot_ready = curr_full_snapshot_dist < ctx->prev_full_snapshot_dist; + ctx->prev_full_snapshot_dist = curr_full_snapshot_dist; + + /* Do the same for incrementals, only try to create one if there has been + a full snapshot. */ + ulong curr_incr_snapshot_dist = smr % ctx->incremental_interval; + uchar is_inc_snapshot_ready = smr % ctx->incremental_interval < ctx->prev_incr_snapshot_dist && ctx->last_full_snap; + ctx->prev_incr_snapshot_dist = curr_incr_snapshot_dist; + + ulong updated_fseq = 0UL; + + /* TODO: We need a better check if the smr fell on an epoch boundary due to + skipped slots. We just don't want to make a snapshot on an epoch boundary */ + if( (is_full_snapshot_ready || is_inc_snapshot_ready) && + !fd_runtime_is_epoch_boundary( epoch_bank, smr, smr-1UL ) ) { + /* Constipate the status cache when a snapshot is ready to be created. */ + if( is_full_snapshot_ready ) { + ctx->last_full_snap = smr; + FD_LOG_NOTICE(( "Ready to create a full snapshot" )); + updated_fseq = fd_snapshot_create_pack_fseq( 0, smr ); + } else { + FD_LOG_NOTICE(( "Ready to create an incremental snapshot" )); + updated_fseq = fd_snapshot_create_pack_fseq( 1, smr ); + } + ctx->first_constipation = 1; /* TODO: I hate this hack. */ + fd_txncache_set_is_constipated( ctx->slot_ctx->status_cache, 1 ); + fd_fseq_update( ctx->is_funk_constipated, updated_fseq ); + } +} + +static void +funk_and_txncache_publish( fd_replay_tile_ctx_t * ctx, ulong smr ) { + + /* When we are trying to root for an smr that we want a snapshot for, we need + to constipate funk as well as the txncache. The snapshot tile will notify + the replay tile that funk is ready to be unconstipated via the + is_funk_constipated fseq. Txncache constipation will be handled differently. + All operations on the status cache are bounded by a rw lock making + operation atomic. The status cache will internally track if it is in a + constipated state. The snapshot tile will be directly responsible for + unconstipating the txncache. */ + fd_blockstore_start_read( ctx->blockstore ); fd_hash_t const * root_block_hash = fd_blockstore_block_hash_query( ctx->blockstore, smr ); fd_funk_txn_xid_t xid; memcpy( xid.uc, root_block_hash, sizeof( fd_funk_txn_xid_t ) ); fd_blockstore_end_read( ctx->blockstore ); + /* Generate a funk txn */ + xid.ul[0] = smr; fd_funk_txn_t * txn_map = fd_funk_txn_map( ctx->funk, fd_funk_wksp( ctx->funk ) ); fd_funk_txn_t * root_txn = fd_funk_txn_query( &xid, txn_map ); + /* Once all of the banking tiles have finished executing, grab a write + lock on funk and publish the transaction. + + The publishing mechanism for funk and the status cache will change + if constipation is enabled. If constipation is enabled, + constipate the current transaction into the constipated root. This means + we will treat the oldest ancestor as the new root of the transaction tree. + All slots that are "rooted" in the constipated state will be published + into the constipated root. When constipation is disabled, flush the backed + up transactions into the root. + + There is some unfortunate behavior we have to consider here with + publishing and constipation. When the status cache is in a constipated + state, we want to register all of the slots except for the slot that + corresponds to the constipated root. However, during the first pass when + we are constipated, we also need to register the constipated root into + the txn cache. If we don't then the constipated root slot will never be + included in the status cache. TODO: There is probably a better way to + hhandle this. + + Constipation can be activated for a variety of reasons including snapshot + creation and epoch account hash generation. + TODO: Currently epoch account hash generation is unimplemented but the + funk fseq should be repurposed to be more generalized. */ + for( ulong i = 0UL; ibank_cnt; i++ ) { fd_tpool_wait( ctx->tpool, i+1 ); } fd_funk_start_write( ctx->funk ); - ulong rc = fd_funk_txn_publish( ctx->funk, root_txn, 1 ); - if( FD_UNLIKELY( !rc ) ) { - FD_LOG_ERR(( "failed to funk publish slot %lu", smr )); - } - fd_funk_end_write( ctx->funk ); + + uchar is_funk_constipated = fd_fseq_query( ctx->is_funk_constipated ) != 0; + + txncache_publish( ctx, txn_map, root_txn, is_funk_constipated ); + + /* Now try to publish into funk, this is handled differently based on if + funk is constipated. */ + + if( !is_funk_constipated ) { + FD_LOG_NOTICE(( "Publishing slot=%lu", smr )); + + ulong rc = fd_funk_txn_publish( ctx->funk, root_txn, 1 ); + if( FD_UNLIKELY( !rc ) ) { + FD_LOG_ERR(( "failed to funk publish slot %lu", smr )); + } + + } else { + FD_LOG_WARNING(( "Publishing slot=%lu while constipated", smr )); + + /* At this point, first collapse the current transaction that should be + published into the oldest child transaction. */ - if( FD_LIKELY( ctx->slot_ctx->status_cache ) ) { - fd_txncache_register_root_slot( ctx->slot_ctx->status_cache, smr ); + fd_funk_txn_t * txn = root_txn; + fd_funk_txn_t * parent_txn = fd_funk_txn_parent( txn, txn_map ); + + while( parent_txn ) { + if( FD_UNLIKELY( fd_funk_txn_publish_into_parent( ctx->funk, txn, 0 ) ) ) { + FD_LOG_ERR(( "Can't publish funk transaction" )); + } + txn = parent_txn; + parent_txn = fd_funk_txn_parent( txn, txn_map ); + } } + fd_funk_end_write( ctx->funk ); + + /* TODO: This needs to get integrated into the snapshot tile. */ fd_epoch_bank_t * epoch_bank = fd_exec_epoch_ctx_epoch_bank( ctx->slot_ctx->epoch_ctx ); if( smr >= epoch_bank->eah_start_slot ) { - fd_accounts_hash( ctx->slot_ctx, ctx->tpool, &ctx->slot_ctx->slot_bank.epoch_account_hash ); + fd_accounts_hash( ctx->slot_ctx->acc_mgr->funk, &ctx->slot_ctx->slot_bank, + ctx->slot_ctx->valloc, ctx->tpool, &ctx->slot_ctx->slot_bank.epoch_account_hash ); epoch_bank->eah_start_slot = FD_SLOT_NULL; } + snapshot_state_update( ctx, smr, is_funk_constipated ); + if( FD_UNLIKELY( ctx->capture_ctx ) ) { fd_runtime_checkpt( ctx->capture_ctx, ctx->slot_ctx, smr ); } @@ -795,15 +983,6 @@ send_tower_sync( fd_replay_tile_ctx_t * ctx ) { if( FD_LIKELY( ctx->tower_checkpt_fileno > 0 ) ) fd_restart_tower_checkpt( vote_bank_hash, ctx->tower, ctx->tower_checkpt_fileno ); } -static uint -is_epoch_boundary( fd_epoch_bank_t * epoch_bank, ulong curr_slot, ulong prev_slot ) { - ulong slot_idx; - ulong prev_epoch = fd_slot_to_epoch( &epoch_bank->epoch_schedule, prev_slot, &slot_idx ); - ulong new_epoch = fd_slot_to_epoch( &epoch_bank->epoch_schedule, curr_slot, &slot_idx ); - - return ( prev_epoch < new_epoch || slot_idx == 0 ); -} - static fd_fork_t * prepare_new_block_execution( fd_replay_tile_ctx_t * ctx, fd_stem_context_t * stem, @@ -830,14 +1009,14 @@ prepare_new_block_execution( fd_replay_tile_ctx_t * ctx, /* if it is an epoch boundary, push out stake weights */ if( fork->slot_ctx.slot_bank.slot != 0 ) { - is_new_epoch_in_new_block = (int)is_epoch_boundary( epoch_bank, fork->slot_ctx.slot_bank.slot, fork->slot_ctx.slot_bank.prev_slot ); + is_new_epoch_in_new_block = (int)fd_runtime_is_epoch_boundary( epoch_bank, fork->slot_ctx.slot_bank.slot, fork->slot_ctx.slot_bank.prev_slot ); } fork->slot_ctx.slot_bank.prev_slot = fork->slot_ctx.slot_bank.slot; fork->slot_ctx.slot_bank.slot = curr_slot; fork->slot_ctx.enable_exec_recording = ctx->tx_metadata_storage; - if( is_epoch_boundary( epoch_bank, fork->slot_ctx.slot_bank.slot, fork->slot_ctx.slot_bank.prev_slot ) ) { + if( fd_runtime_is_epoch_boundary( epoch_bank, fork->slot_ctx.slot_bank.slot, fork->slot_ctx.slot_bank.prev_slot ) ) { FD_LOG_WARNING(("Epoch boundary")); fd_epoch_fork_elem_t * epoch_fork = NULL; @@ -1071,6 +1250,11 @@ after_frag( fd_replay_tile_ctx_t * ctx, fd_block_map_t * block_map_entry = fd_blockstore_block_map_query( ctx->blockstore, curr_slot ); fd_block_t * block_ = fd_blockstore_block_query( ctx->blockstore, curr_slot ); fork->slot_ctx.block = block_; + + /* TODO:FIXME: This needs to be unhacked. */ + fork->slot_ctx.slot_bank.max_tick_height += 64UL * (curr_slot - ctx->parent_slot); + fork->slot_ctx.slot_bank.tick_height += 64UL * (curr_slot - ctx->parent_slot); + int res = fd_runtime_block_execute_finalize_tpool( &fork->slot_ctx, ctx->capture_ctx, block_info, ctx->tpool ); if( res != FD_RUNTIME_EXECUTE_SUCCESS ) { @@ -1182,6 +1366,11 @@ after_frag( fd_replay_tile_ctx_t * ctx, FD_LOG_WARNING(( "still catching up. not voting." )); } else { + + if( FD_UNLIKELY( !ctx->is_caught_up ) ) { + ctx->is_caught_up = 1; + } + /* Proceed according to how local and cluster are synchronized. */ if( FD_LIKELY( vote_fork ) ) { @@ -1260,7 +1449,7 @@ after_frag( fd_replay_tile_ctx_t * ctx, funk_cancel( ctx, cmp_slot ); checkpt( ctx ); - FD_LOG_ERR( ( "Bank hash mismatch on slot: %lu. Halting.", cmp_slot ) ); + FD_LOG_ERR(( "Bank hash mismatch on slot: %lu. Halting.", cmp_slot )); break; @@ -1327,7 +1516,7 @@ tpool_boot( fd_topo_t * topo, ulong total_thread_count ) { ulong main_thread_seen = 0; for( ulong i=0UL; itile_cnt; i++ ) { - if( strcmp( topo->tiles[i].name, "thread" ) == 0 ) { + if( strcmp( topo->tiles[i].name, "rtpool" ) == 0 ) { tile_to_cpu[ 1+thread_count ] = (ushort)topo->tiles[i].cpu_idx; thread_count++; } @@ -1667,7 +1856,7 @@ during_housekeeping( void * _ctx ) { if( FD_LIKELY( ctx->blockstore->smr == smr ) ) return; if( FD_LIKELY( ctx->blockstore ) ) blockstore_publish( ctx, smr ); if( FD_LIKELY( ctx->forks ) ) fd_forks_publish( ctx->forks, smr, ctx->ghost ); - if( FD_LIKELY( ctx->funk && ctx->blockstore ) ) funk_publish( ctx, smr ); + if( FD_LIKELY( ctx->funk && ctx->blockstore ) ) funk_and_txncache_publish( ctx, smr ); if( FD_LIKELY( ctx->ghost ) ) { fd_epoch_forks_publish( ctx->epoch_forks, ctx->ghost, smr ); fd_ghost_publish( ctx->ghost, smr ); @@ -1760,10 +1949,22 @@ unprivileged_init( fd_topo_t * topo, FD_LOG_ERR(( "no status cache wksp" )); } + /**********************************************************************/ + /* snapshot */ + /**********************************************************************/ + + ctx->snapshot_interval = tile->replay.full_interval ? tile->replay.full_interval : ULONG_MAX; + ctx->incremental_interval = tile->replay.incremental_interval ? tile->replay.incremental_interval : ULONG_MAX; + ctx->last_full_snap = 0UL; + + FD_LOG_NOTICE(( "Snapshot intervals full=%lu incremental=%lu", ctx->snapshot_interval, ctx->incremental_interval )); + /**********************************************************************/ /* funk */ /**********************************************************************/ + /* TODO: This below code needs to be shared as a topology object. This + will involve adding support to create a funk-based file here. */ fd_funk_t * funk; const char * snapshot = tile->replay.snapshot; if( strcmp( snapshot, "funk" ) == 0 ) { @@ -1781,6 +1982,7 @@ unprivileged_init( fd_topo_t * topo, tile->replay.funk_file, 1, ctx->funk_seed, tile->replay.funk_txn_max, tile->replay.funk_rec_max, tile->replay.funk_sz_gb * (1UL<<30), FD_FUNK_OVERWRITE, NULL ); + FD_LOG_NOTICE(( "Opened funk file at %s", tile->replay.funk_file )); } if( FD_UNLIKELY( funk == NULL ) ) { FD_LOG_ERR(( "no funk loaded" )); @@ -1791,6 +1993,8 @@ unprivileged_init( fd_topo_t * topo, FD_LOG_ERR(( "no funk wksp" )); } + ctx->is_caught_up = 0; + /**********************************************************************/ /* root_slot fseq */ /**********************************************************************/ @@ -1801,6 +2005,19 @@ unprivileged_init( fd_topo_t * topo, if( FD_UNLIKELY( !ctx->smr ) ) FD_LOG_ERR(( "replay tile has no root_slot fseq" )); FD_TEST( ULONG_MAX==fd_fseq_query( ctx->smr ) ); + /**********************************************************************/ + /* constipated fseq */ + /**********************************************************************/ + + /* When the replay tile boots, funk should not be constipated */ + + ulong constipated_obj_id = fd_pod_queryf_ulong( topo->props, ULONG_MAX, "constipate" ); + FD_TEST( constipated_obj_id!=ULONG_MAX ); + ctx->is_funk_constipated = fd_fseq_join( fd_topo_obj_laddr( topo, constipated_obj_id ) ); + if( FD_UNLIKELY( !ctx->is_funk_constipated ) ) FD_LOG_ERR(( "replay tile has no root_slot fseq" )); + fd_fseq_update( ctx->is_funk_constipated, 0UL ); + FD_TEST( 0UL==fd_fseq_query( ctx->is_funk_constipated ) ); + /**********************************************************************/ /* poh_slot fseq */ /**********************************************************************/ @@ -1873,7 +2090,9 @@ unprivileged_init( fd_topo_t * topo, if (status_cache_mem == NULL) { FD_LOG_ERR(( "failed to allocate status cache" )); } - ctx->status_cache = fd_txncache_join( fd_txncache_new( status_cache_mem, FD_TXNCACHE_DEFAULT_MAX_ROOTED_SLOTS, FD_TXNCACHE_DEFAULT_MAX_LIVE_SLOTS, MAX_CACHE_TXNS_PER_SLOT ) ); + ctx->status_cache = fd_txncache_join( fd_txncache_new( status_cache_mem, FD_TXNCACHE_DEFAULT_MAX_ROOTED_SLOTS, + FD_TXNCACHE_DEFAULT_MAX_LIVE_SLOTS, MAX_CACHE_TXNS_PER_SLOT, + FD_TXNCACHE_DEFAULT_MAX_CONSTIPATED_SLOTS ) ); if (ctx->status_cache == NULL) { fd_wksp_free_laddr(status_cache_mem); FD_LOG_ERR(( "failed to join + new status cache" )); diff --git a/src/app/fdctl/run/tiles/fd_replay_thread.c b/src/app/fdctl/run/tiles/fd_replay_thread.c index 77c9f330ea..ae793f77e4 100644 --- a/src/app/fdctl/run/tiles/fd_replay_thread.c +++ b/src/app/fdctl/run/tiles/fd_replay_thread.c @@ -1,3 +1,3 @@ #include "../../../../disco/tiles.h" -fd_topo_run_tile_t fd_tile_replay_thread = { .name = "thread", .for_tpool = 1 }; +fd_topo_run_tile_t fd_tile_replay_thread = { .name = "rtpool", .for_tpool = 1 }; diff --git a/src/app/fdctl/run/tiles/fd_snapshot.c b/src/app/fdctl/run/tiles/fd_snapshot.c new file mode 100644 index 0000000000..a5c2b774c5 --- /dev/null +++ b/src/app/fdctl/run/tiles/fd_snapshot.c @@ -0,0 +1,430 @@ +#include "../../../../disco/tiles.h" + +#include "../../../../disco/topo/fd_pod_format.h" +#include "../../../../funk/fd_funk.h" +#include "../../../../flamenco/runtime/fd_txncache.h" +#include "../../../../flamenco/runtime/fd_runtime.h" +#include "../../../../flamenco/snapshot/fd_snapshot_create.h" +#include "../../../../funk/fd_funk_filemap.h" + +#include "generated/snapshot_seccomp.h" + +#define SCRATCH_MAX (1024UL << 24 ) /* 24 MiB */ +#define SCRATCH_DEPTH (256UL) /* 256 scratch frames */ +#define TPOOL_WORKER_MEM_SZ (1UL<<30UL) /* 256MB */ + +struct fd_snapshot_tile_ctx { + /* User defined parameters. */ + ulong full_interval; + ulong incremental_interval; + char const * out_dir; + char funk_file[ PATH_MAX ]; + + /* Shared data structures. */ + fd_txncache_t * status_cache; + ulong * is_constipated; + fd_funk_t * funk; + + /* File descriptors used for snapshot generation. */ + int tmp_fd; + int tmp_inc_fd; + int full_snapshot_fd; + int incremental_snapshot_fd; + + /* Thread pool used for account hash calculation. */ + uchar tpool_mem[ FD_TPOOL_FOOTPRINT( FD_TILE_MAX ) ] __attribute__( ( aligned( FD_TPOOL_ALIGN ) ) ); + fd_tpool_t * tpool; + + /* Only join funk after tiles start spinning. */ + int is_funk_active; + + /* Metadata from the full snapshot used for incremental snapshots. */ + ulong last_full_snap_slot; + fd_hash_t last_hash; + ulong last_capitalization; +}; +typedef struct fd_snapshot_tile_ctx fd_snapshot_tile_ctx_t; + +void FD_FN_UNUSED +tpool_snap_boot( fd_topo_t * topo, ulong total_thread_count ) { + ushort tile_to_cpu[ FD_TILE_MAX ] = { 0 }; + ulong thread_count = 0UL; + ulong main_thread_seen = 0UL; + + for( ulong i=0UL; itile_cnt; i++ ) { + if( !strcmp( topo->tiles[i].name, "stpool" ) ) { + tile_to_cpu[ thread_count++ ] = (ushort)topo->tiles[ i ].cpu_idx; + } + } + + if( FD_UNLIKELY( thread_count!=total_thread_count ) ) { + FD_LOG_ERR(( "thread count mismatch thread_count=%lu total_thread_count=%lu main_thread_seen=%lu", + thread_count, + total_thread_count, + main_thread_seen )); + } + + fd_tile_private_map_boot( tile_to_cpu, thread_count ); +} + +FD_FN_CONST static inline ulong +scratch_align( void ) { + return 128UL; +} + +FD_FN_PURE static inline ulong +scratch_footprint( fd_topo_tile_t const * tile FD_PARAM_UNUSED ) { + ulong l = FD_LAYOUT_INIT; + l = FD_LAYOUT_APPEND( l, alignof(fd_snapshot_tile_ctx_t), sizeof(fd_snapshot_tile_ctx_t) ); + l = FD_LAYOUT_APPEND( l, FD_SCRATCH_ALIGN_DEFAULT, tile->snaps.hash_tpool_thread_count * TPOOL_WORKER_MEM_SZ ); + l = FD_LAYOUT_APPEND( l, fd_scratch_smem_align(), fd_scratch_smem_footprint( SCRATCH_MAX ) ); + l = FD_LAYOUT_APPEND( l, fd_scratch_fmem_align(), fd_scratch_fmem_footprint( SCRATCH_DEPTH ) ); + return FD_LAYOUT_FINI( l, scratch_align() ); +} + +static void +privileged_init( fd_topo_t * topo FD_PARAM_UNUSED, + fd_topo_tile_t * tile FD_PARAM_UNUSED ) { + + /* First open the relevant files here. TODO: We eventually want to extend + this to support multiple files. */ + + char tmp_dir_buf[ FD_SNAPSHOT_DIR_MAX ]; + int err = snprintf( tmp_dir_buf, FD_SNAPSHOT_DIR_MAX, "%s/%s", tile->snaps.out_dir, FD_SNAPSHOT_TMP_ARCHIVE ); + if( FD_UNLIKELY( err<0 ) ) { + FD_LOG_ERR(( "Failed to format directory string" )); + } + + char tmp_inc_dir_buf[ FD_SNAPSHOT_DIR_MAX ]; + err = snprintf( tmp_inc_dir_buf, FD_SNAPSHOT_DIR_MAX, "%s/%s", tile->snaps.out_dir, FD_SNAPSHOT_TMP_INCR_ARCHIVE ); + if( FD_UNLIKELY( err<0 ) ) { + FD_LOG_ERR(( "Failed to format directory string" )); + } + + char zstd_dir_buf[ FD_SNAPSHOT_DIR_MAX ]; + err = snprintf( zstd_dir_buf, FD_SNAPSHOT_DIR_MAX, "%s/%s", tile->snaps.out_dir, FD_SNAPSHOT_TMP_FULL_ARCHIVE_ZSTD ); + if( FD_UNLIKELY( err<0 ) ) { + FD_LOG_ERR(( "Failed to format directory string" )); + } + + char zstd_inc_dir_buf[ FD_SNAPSHOT_DIR_MAX ]; + err = snprintf( zstd_inc_dir_buf, FD_SNAPSHOT_DIR_MAX, "%s/%s", tile->snaps.out_dir, FD_SNAPSHOT_TMP_INCR_ARCHIVE_ZSTD ); + if( FD_UNLIKELY( err<0 ) ) { + FD_LOG_ERR(( "Failed to format directory string" )); + } + + /* Create and open the relevant files for snapshots. */ + + tile->snaps.tmp_fd = open( tmp_dir_buf, O_CREAT | O_RDWR | O_TRUNC, 0644 ); + if( FD_UNLIKELY( tile->snaps.tmp_fd==-1 ) ) { + FD_LOG_ERR(( "Failed to open and create tarball for file=%s (%i-%s)", tmp_dir_buf, errno, fd_io_strerror( errno ) )); + } + + tile->snaps.tmp_inc_fd = open( tmp_inc_dir_buf, O_CREAT | O_RDWR | O_TRUNC, 0644 ); + if( FD_UNLIKELY( tile->snaps.tmp_inc_fd==-1 ) ) { + FD_LOG_ERR(( "Failed to open and create tarball for file=%s (%i-%s)", tmp_dir_buf, errno, fd_io_strerror( errno ) )); + } + + tile->snaps.full_snapshot_fd = open( zstd_dir_buf, O_RDWR | O_CREAT | O_TRUNC, 0644 ); + if( FD_UNLIKELY( tile->snaps.full_snapshot_fd==-1 ) ) { + FD_LOG_WARNING(( "Failed to open the snapshot file (%i-%s)", errno, fd_io_strerror( errno ) )); + } + + tile->snaps.incremental_snapshot_fd = open( zstd_inc_dir_buf, O_RDWR | O_CREAT | O_TRUNC, 0644 ); + if( FD_UNLIKELY( tile->snaps.incremental_snapshot_fd==-1 ) ) { + FD_LOG_WARNING(( "Failed to open the snapshot file (%i-%s)", errno, fd_io_strerror( errno ) )); + } +} + +static void +unprivileged_init( fd_topo_t * topo FD_PARAM_UNUSED, + fd_topo_tile_t * tile ) { + + void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id ); + + /**********************************************************************/ + /* scratch (bump)-allocate memory owned by the replay tile */ + /**********************************************************************/ + + /* Do not modify order! This is join-order in unprivileged_init. */ + + FD_SCRATCH_ALLOC_INIT( l, scratch ); + fd_snapshot_tile_ctx_t * ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_snapshot_tile_ctx_t), sizeof(fd_snapshot_tile_ctx_t) ); + memset( ctx, 0, sizeof(fd_snapshot_tile_ctx_t) ); + void * tpool_worker_mem = FD_SCRATCH_ALLOC_APPEND( l, FD_SCRATCH_ALIGN_DEFAULT, tile->snaps.hash_tpool_thread_count * TPOOL_WORKER_MEM_SZ ); + void * scratch_smem = FD_SCRATCH_ALLOC_APPEND( l, fd_scratch_smem_align(), fd_scratch_smem_footprint( SCRATCH_MAX ) ); + void * scratch_fmem = FD_SCRATCH_ALLOC_APPEND( l, fd_scratch_fmem_align(), fd_scratch_fmem_footprint( SCRATCH_DEPTH ) ); + ulong scratch_alloc_mem = FD_SCRATCH_ALLOC_FINI ( l, scratch_align() ); + + ctx->full_interval = tile->snaps.full_interval; + ctx->incremental_interval = tile->snaps.incremental_interval; + ctx->out_dir = tile->snaps.out_dir; + ctx->tmp_fd = tile->snaps.tmp_fd; + ctx->tmp_inc_fd = tile->snaps.tmp_inc_fd; + ctx->full_snapshot_fd = tile->snaps.full_snapshot_fd; + ctx->incremental_snapshot_fd = tile->snaps.incremental_snapshot_fd; + + /**********************************************************************/ + /* tpool */ + /**********************************************************************/ + + FD_LOG_NOTICE(( "Number of threads in hash tpool: %lu", tile->snaps.hash_tpool_thread_count )); + + if( FD_LIKELY( tile->snaps.hash_tpool_thread_count>1UL ) ) { + tpool_snap_boot( topo, tile->snaps.hash_tpool_thread_count ); + ctx->tpool = fd_tpool_init( ctx->tpool_mem, tile->snaps.hash_tpool_thread_count ); + } else { + ctx->tpool = NULL; + } + + if( FD_LIKELY( tile->snaps.hash_tpool_thread_count>1UL ) ) { + /* Start the tpool workers */ + for( ulong i=1UL; isnaps.hash_tpool_thread_count; i++ ) { + if( FD_UNLIKELY( !fd_tpool_worker_push( ctx->tpool, i, (uchar *)tpool_worker_mem + TPOOL_WORKER_MEM_SZ*(i - 1U), TPOOL_WORKER_MEM_SZ ) ) ) { + FD_LOG_ERR(( "failed to launch worker" )); + } + } + } + + /**********************************************************************/ + /* funk */ + /**********************************************************************/ + + /* We only want to join funk after it has been setup and joined in the + replay tile. + TODO: Eventually funk will be joined via a shared topology object. */ + ctx->is_funk_active = 0; + memcpy( ctx->funk_file, tile->replay.funk_file, sizeof(tile->replay.funk_file) ); + + /**********************************************************************/ + /* status cache */ + /**********************************************************************/ + + ulong status_cache_obj_id = fd_pod_queryf_ulong( topo->props, ULONG_MAX, "txncache" ); + if( FD_UNLIKELY( status_cache_obj_id==ULONG_MAX ) ) { + FD_LOG_ERR(( "no status cache object id" )); + } + + ctx->status_cache = fd_txncache_join( fd_topo_obj_laddr( topo, status_cache_obj_id ) ); + if( FD_UNLIKELY( !ctx->status_cache ) ) { + FD_LOG_ERR(( "no status cache" )); + } + + /**********************************************************************/ + /* scratch */ + /**********************************************************************/ + + fd_scratch_attach( scratch_smem, scratch_fmem, SCRATCH_MAX, SCRATCH_DEPTH ); + + if( FD_UNLIKELY( scratch_alloc_mem != ( (ulong)scratch + scratch_footprint( tile ) ) ) ) { + FD_LOG_ERR(( "scratch_alloc_mem did not match scratch_footprint diff: %lu alloc: %lu footprint: %lu", + scratch_alloc_mem - (ulong)scratch - scratch_footprint( tile ), + scratch_alloc_mem, + (ulong)scratch + scratch_footprint( tile ) )); + } + + /**********************************************************************/ + /* constipated fseq */ + /**********************************************************************/ + + ulong constipated_obj_id = fd_pod_queryf_ulong( topo->props, ULONG_MAX, "constipate" ); + if( FD_UNLIKELY( constipated_obj_id==ULONG_MAX ) ) { + FD_LOG_ERR(( "no constipated object id" )); + } + ctx->is_constipated = fd_fseq_join( fd_topo_obj_laddr( topo, constipated_obj_id ) ); + if( FD_UNLIKELY( !ctx->is_constipated ) ) { + FD_LOG_ERR(( "replay tile has no constipated fseq" )); + } + fd_fseq_update( ctx->is_constipated, 0UL ); + if( FD_UNLIKELY( fd_fseq_query( ctx->is_constipated ) ) ) { + FD_LOG_ERR(( "constipated fseq is not zero" )); + } + + /**********************************************************************/ + /* snapshot */ + /**********************************************************************/ + + /* Zero init all of the fields used for incremental snapshot generation + that must be persisted across snapshot creation. */ + + ctx->last_full_snap_slot = 0UL; + ctx->last_capitalization = 0UL; + fd_memset( &ctx->last_hash, 0, sizeof(fd_hash_t) ); +} + +static void +after_credit( fd_snapshot_tile_ctx_t * ctx FD_PARAM_UNUSED, + fd_stem_context_t * stem FD_PARAM_UNUSED, + int * opt_poll_in FD_PARAM_UNUSED, + int * charge_busy FD_PARAM_UNUSED ) { + + ulong is_constipated = fd_fseq_query( ctx->is_constipated ); + + if( !is_constipated ) { + return; + } + + if( FD_UNLIKELY( !ctx->is_funk_active ) ) { + ctx->funk = fd_funk_open_file( ctx->funk_file, + 1, + 0, + 0, + 0, + 0, + FD_FUNK_READ_WRITE, + NULL ); + if( FD_UNLIKELY( !ctx->funk ) ) { + FD_LOG_ERR(( "failed to join a funky" )); + } + ctx->is_funk_active = 1; + + FD_LOG_WARNING(( "Just joined funk at file=%s", ctx->funk_file )); + } + + ulong is_incremental = fd_snapshot_create_get_is_incremental( is_constipated ); + ulong snapshot_slot = fd_snapshot_create_get_slot( is_constipated ); + + if( !is_incremental ) { + ctx->last_full_snap_slot = snapshot_slot; + } + + FD_LOG_WARNING(( "Creating snapshot incremental=%lu slot=%lu", is_incremental, snapshot_slot )); + + fd_snapshot_ctx_t snapshot_ctx = { + .slot = snapshot_slot, + .out_dir = ctx->out_dir, + .is_incremental = (uchar)is_incremental, + .valloc = fd_scratch_virtual(), + .funk = ctx->funk, + .status_cache = ctx->status_cache, + .tmp_fd = is_incremental ? ctx->tmp_inc_fd : ctx->tmp_fd, + .snapshot_fd = is_incremental ? ctx->incremental_snapshot_fd : ctx->full_snapshot_fd, + .tpool = ctx->tpool, + /* These parameters are ignored if the snapshot is not incremental. */ + .last_snap_slot = ctx->last_full_snap_slot, + .last_snap_acc_hash = &ctx->last_hash, + .last_snap_capitalization = ctx->last_capitalization + }; + + if( !is_incremental ) { + ctx->last_full_snap_slot = snapshot_slot; + } + + /* If this isn't the first snapshot that this tile is creating, the + permissions should be made to not acessible by users and should be + renamed to the constant file that is expected. */ + + char proc_filename[ FD_SNAPSHOT_DIR_MAX ]; + char prev_filename[ FD_SNAPSHOT_DIR_MAX ]; + char new_filename [ FD_SNAPSHOT_DIR_MAX ]; + snprintf( proc_filename, FD_SNAPSHOT_DIR_MAX, "/proc/self/fd/%d", is_incremental ? ctx->incremental_snapshot_fd : ctx->full_snapshot_fd ); + long len = readlink( proc_filename, prev_filename, FD_SNAPSHOT_DIR_MAX ); + if( FD_UNLIKELY( len<=0L ) ) { + FD_LOG_ERR(( "Failed to readlink the snapshot file" )); + } + prev_filename[ len ] = '\0'; + + int err = snprintf( new_filename, FD_SNAPSHOT_DIR_MAX, "%s/%s", ctx->out_dir, is_incremental ? FD_SNAPSHOT_TMP_INCR_ARCHIVE_ZSTD : FD_SNAPSHOT_TMP_FULL_ARCHIVE_ZSTD ); + if( FD_UNLIKELY( err<0 ) ) { + FD_LOG_ERR(( "Can't format filename" )); + return; + } + + err = rename( prev_filename, new_filename ); + if( FD_UNLIKELY( err ) ) { + FD_LOG_ERR(( "Failed to rename file from %s to %s", prev_filename, new_filename )); + } + FD_LOG_NOTICE(( "Renaming file from %s to %s", prev_filename, new_filename )); + + err = ftruncate( snapshot_ctx.tmp_fd, 0UL ); + if( FD_UNLIKELY( err==-1 ) ) { + FD_LOG_ERR(( "Failed to truncate the temporary file (%i-%s)", errno, fd_io_strerror( errno ) )); + } + + err = ftruncate( snapshot_ctx.snapshot_fd, 0UL ); + if( FD_UNLIKELY( err==-1 ) ) { + FD_LOG_ERR(( "Failed to truncate the snapshot file (%i-%s)", errno, fd_io_strerror( errno ) )); + } + + long seek = lseek( snapshot_ctx.tmp_fd, 0UL, SEEK_SET ); + if( FD_UNLIKELY( seek!=0L ) ) { + FD_LOG_ERR(( "Failed to seek to the beginning of the file" )); + } + + /* Now that the files are in an expected state, create the snapshot. */ + + if( FD_UNLIKELY( fd_snapshot_create_new_snapshot( &snapshot_ctx, &ctx->last_hash, &ctx->last_capitalization ) ) ) { + FD_LOG_ERR(( "Failed to create a new snapshot" )); + } + + if( is_incremental ) { + //FD_LOG_ERR(( "Terminating out" )); + FD_LOG_NOTICE(( "Done creating a snapshot in %s", snapshot_ctx.out_dir )); + FD_LOG_ERR(("Successful exit" )); + } + + FD_LOG_NOTICE(( "Done creating a snapshot in %s", snapshot_ctx.out_dir )); + + fd_fseq_update( ctx->is_constipated, 0UL ); + +} + +static ulong +populate_allowed_seccomp( fd_topo_t const * topo, + fd_topo_tile_t const * tile, + ulong out_cnt, + struct sock_filter * out ) { + (void)topo; + + populate_sock_filter_policy_snapshot( out_cnt, + out, + (uint)fd_log_private_logfile_fd(), + (uint)tile->snaps.tmp_fd, + (uint)tile->snaps.tmp_inc_fd, + (uint)tile->snaps.full_snapshot_fd, + (uint)tile->snaps.incremental_snapshot_fd ); + return sock_filter_policy_snapshot_instr_cnt; +} + +static ulong +populate_allowed_fds( fd_topo_t const * topo, + fd_topo_tile_t const * tile, + ulong out_fds_cnt, + int * out_fds ) { + (void)topo; + + if( FD_UNLIKELY( out_fds_cnt<2UL ) ) { + FD_LOG_ERR(( "out_fds_cnt %lu", out_fds_cnt )); + } + + ulong out_cnt = 0UL; + out_fds[ out_cnt++ ] = 2; /* stderr */ + if( FD_LIKELY( -1!=fd_log_private_logfile_fd() ) ) + out_fds[ out_cnt++ ] = fd_log_private_logfile_fd(); /* logfile */ + + out_fds[ out_cnt++ ] = tile->snaps.tmp_fd; + out_fds[ out_cnt++ ] = tile->snaps.tmp_inc_fd; + out_fds[ out_cnt++ ] = tile->snaps.full_snapshot_fd; + out_fds[ out_cnt++ ] = tile->snaps.incremental_snapshot_fd; + return out_cnt; +} + +#define STEM_BURST (1UL) + +#define STEM_CALLBACK_CONTEXT_TYPE fd_snapshot_tile_ctx_t +#define STEM_CALLBACK_CONTEXT_ALIGN alignof(fd_snapshot_tile_ctx_t) + +#define STEM_CALLBACK_AFTER_CREDIT after_credit + +#include "../../../../disco/stem/fd_stem.c" + +fd_topo_run_tile_t fd_tile_snaps = { + .name = "snaps", + .populate_allowed_seccomp = populate_allowed_seccomp, + .populate_allowed_fds = populate_allowed_fds, + .scratch_align = scratch_align, + .scratch_footprint = scratch_footprint, + .privileged_init = privileged_init, + .unprivileged_init = unprivileged_init, + .run = stem_run, +}; diff --git a/src/app/fdctl/run/tiles/fd_snapshot_thread.c b/src/app/fdctl/run/tiles/fd_snapshot_thread.c new file mode 100644 index 0000000000..aabcd666ee --- /dev/null +++ b/src/app/fdctl/run/tiles/fd_snapshot_thread.c @@ -0,0 +1,3 @@ +#include "../../../../disco/tiles.h" + +fd_topo_run_tile_t fd_tile_snaps_thread = { .name = "stpool", .for_tpool = 1 }; diff --git a/src/app/fdctl/run/tiles/generated/snapshot_seccomp.h b/src/app/fdctl/run/tiles/generated/snapshot_seccomp.h new file mode 100644 index 0000000000..7b1baa6abe --- /dev/null +++ b/src/app/fdctl/run/tiles/generated/snapshot_seccomp.h @@ -0,0 +1,137 @@ +/* THIS FILE WAS GENERATED BY generate_filters.py. DO NOT EDIT BY HAND! */ +#ifndef HEADER_fd_src_app_fdctl_run_tiles_generated_snapshot_seccomp_h +#define HEADER_fd_src_app_fdctl_run_tiles_generated_snapshot_seccomp_h + +#include "../../../../../../src/util/fd_util_base.h" +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(__i386__) +# define ARCH_NR AUDIT_ARCH_I386 +#elif defined(__x86_64__) +# define ARCH_NR AUDIT_ARCH_X86_64 +#elif defined(__aarch64__) +# define ARCH_NR AUDIT_ARCH_AARCH64 +#else +# error "Target architecture is unsupported by seccomp." +#endif +static const unsigned int sock_filter_policy_snapshot_instr_cnt = 51; + +static void populate_sock_filter_policy_snapshot( ulong out_cnt, struct sock_filter * out, unsigned int logfile_fd, unsigned int tmp_fd, unsigned int tmp_inc_fd, unsigned int full_snapshot_fd, unsigned int incremental_snapshot_fd) { + FD_TEST( out_cnt >= 51 ); + struct sock_filter filter[51] = { + /* Check: Jump to RET_KILL_PROCESS if the script's arch != the runtime arch */ + BPF_STMT( BPF_LD | BPF_W | BPF_ABS, ( offsetof( struct seccomp_data, arch ) ) ), + BPF_JUMP( BPF_JMP | BPF_JEQ | BPF_K, ARCH_NR, 0, /* RET_KILL_PROCESS */ 47 ), + /* loading syscall number in accumulator */ + BPF_STMT( BPF_LD | BPF_W | BPF_ABS, ( offsetof( struct seccomp_data, nr ) ) ), + /* allow write based on expression */ + BPF_JUMP( BPF_JMP | BPF_JEQ | BPF_K, SYS_write, /* check_write */ 7, 0 ), + /* allow fsync based on expression */ + BPF_JUMP( BPF_JMP | BPF_JEQ | BPF_K, SYS_fsync, /* check_fsync */ 18, 0 ), + /* allow fchmod based on expression */ + BPF_JUMP( BPF_JMP | BPF_JEQ | BPF_K, SYS_fchmod, /* check_fchmod */ 19, 0 ), + /* allow ftruncate based on expression */ + BPF_JUMP( BPF_JMP | BPF_JEQ | BPF_K, SYS_ftruncate, /* check_ftruncate */ 20, 0 ), + /* allow lseek based on expression */ + BPF_JUMP( BPF_JMP | BPF_JEQ | BPF_K, SYS_lseek, /* check_lseek */ 29, 0 ), + /* allow read based on expression */ + BPF_JUMP( BPF_JMP | BPF_JEQ | BPF_K, SYS_read, /* check_read */ 36, 0 ), + /* allow readlink based on expression */ + BPF_JUMP( BPF_JMP | BPF_JEQ | BPF_K, SYS_readlink, /* check_readlink */ 39, 0 ), + /* none of the syscalls matched */ + { BPF_JMP | BPF_JA, 0, 0, /* RET_KILL_PROCESS */ 38 }, +// check_write: + /* load syscall argument 0 in accumulator */ + BPF_STMT( BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, args[0])), + BPF_JUMP( BPF_JMP | BPF_JEQ | BPF_K, 2, /* RET_ALLOW */ 37, /* lbl_1 */ 0 ), +// lbl_1: + /* load syscall argument 0 in accumulator */ + BPF_STMT( BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, args[0])), + BPF_JUMP( BPF_JMP | BPF_JEQ | BPF_K, logfile_fd, /* RET_ALLOW */ 35, /* lbl_2 */ 0 ), +// lbl_2: + /* load syscall argument 0 in accumulator */ + BPF_STMT( BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, args[0])), + BPF_JUMP( BPF_JMP | BPF_JEQ | BPF_K, tmp_fd, /* RET_ALLOW */ 33, /* lbl_3 */ 0 ), +// lbl_3: + /* load syscall argument 0 in accumulator */ + BPF_STMT( BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, args[0])), + BPF_JUMP( BPF_JMP | BPF_JEQ | BPF_K, tmp_inc_fd, /* RET_ALLOW */ 31, /* lbl_4 */ 0 ), +// lbl_4: + /* load syscall argument 0 in accumulator */ + BPF_STMT( BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, args[0])), + BPF_JUMP( BPF_JMP | BPF_JEQ | BPF_K, full_snapshot_fd, /* RET_ALLOW */ 29, /* lbl_5 */ 0 ), +// lbl_5: + /* load syscall argument 0 in accumulator */ + BPF_STMT( BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, args[0])), + BPF_JUMP( BPF_JMP | BPF_JEQ | BPF_K, incremental_snapshot_fd, /* RET_ALLOW */ 27, /* RET_KILL_PROCESS */ 26 ), +// check_fsync: + /* load syscall argument 0 in accumulator */ + BPF_STMT( BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, args[0])), + BPF_JUMP( BPF_JMP | BPF_JEQ | BPF_K, logfile_fd, /* RET_ALLOW */ 25, /* RET_KILL_PROCESS */ 24 ), +// check_fchmod: + /* load syscall argument 1 in accumulator */ + BPF_STMT( BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, args[1])), + BPF_JUMP( BPF_JMP | BPF_JEQ | BPF_K, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH, /* RET_ALLOW */ 23, /* RET_KILL_PROCESS */ 22 ), +// check_ftruncate: + /* load syscall argument 0 in accumulator */ + BPF_STMT( BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, args[0])), + BPF_JUMP( BPF_JMP | BPF_JEQ | BPF_K, tmp_fd, /* lbl_6 */ 6, /* lbl_7 */ 0 ), +// lbl_7: + /* load syscall argument 0 in accumulator */ + BPF_STMT( BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, args[0])), + BPF_JUMP( BPF_JMP | BPF_JEQ | BPF_K, tmp_inc_fd, /* lbl_6 */ 4, /* lbl_8 */ 0 ), +// lbl_8: + /* load syscall argument 0 in accumulator */ + BPF_STMT( BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, args[0])), + BPF_JUMP( BPF_JMP | BPF_JEQ | BPF_K, full_snapshot_fd, /* lbl_6 */ 2, /* lbl_9 */ 0 ), +// lbl_9: + /* load syscall argument 0 in accumulator */ + BPF_STMT( BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, args[0])), + BPF_JUMP( BPF_JMP | BPF_JEQ | BPF_K, incremental_snapshot_fd, /* lbl_6 */ 0, /* RET_KILL_PROCESS */ 14 ), +// lbl_6: + /* load syscall argument 1 in accumulator */ + BPF_STMT( BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, args[1])), + BPF_JUMP( BPF_JMP | BPF_JEQ | BPF_K, 0, /* RET_ALLOW */ 13, /* RET_KILL_PROCESS */ 12 ), +// check_lseek: + /* load syscall argument 0 in accumulator */ + BPF_STMT( BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, args[0])), + BPF_JUMP( BPF_JMP | BPF_JEQ | BPF_K, tmp_fd, /* RET_ALLOW */ 11, /* lbl_10 */ 0 ), +// lbl_10: + /* load syscall argument 0 in accumulator */ + BPF_STMT( BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, args[0])), + BPF_JUMP( BPF_JMP | BPF_JEQ | BPF_K, tmp_inc_fd, /* RET_ALLOW */ 9, /* lbl_11 */ 0 ), +// lbl_11: + /* load syscall argument 0 in accumulator */ + BPF_STMT( BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, args[0])), + BPF_JUMP( BPF_JMP | BPF_JEQ | BPF_K, full_snapshot_fd, /* RET_ALLOW */ 7, /* lbl_12 */ 0 ), +// lbl_12: + /* load syscall argument 0 in accumulator */ + BPF_STMT( BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, args[0])), + BPF_JUMP( BPF_JMP | BPF_JEQ | BPF_K, incremental_snapshot_fd, /* RET_ALLOW */ 5, /* RET_KILL_PROCESS */ 4 ), +// check_read: + /* load syscall argument 0 in accumulator */ + BPF_STMT( BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, args[0])), + BPF_JUMP( BPF_JMP | BPF_JEQ | BPF_K, tmp_fd, /* RET_ALLOW */ 3, /* lbl_13 */ 0 ), +// lbl_13: + /* load syscall argument 0 in accumulator */ + BPF_STMT( BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, args[0])), + BPF_JUMP( BPF_JMP | BPF_JEQ | BPF_K, tmp_inc_fd, /* RET_ALLOW */ 1, /* RET_KILL_PROCESS */ 0 ), +// check_readlink: +// RET_KILL_PROCESS: + /* KILL_PROCESS is placed before ALLOW since it's the fallthrough case. */ + BPF_STMT( BPF_RET | BPF_K, SECCOMP_RET_KILL_PROCESS ), +// RET_ALLOW: + /* ALLOW has to be reached by jumping */ + BPF_STMT( BPF_RET | BPF_K, SECCOMP_RET_ALLOW ), + }; + fd_memcpy( out, filter, sizeof( filter ) ); +} + +#endif diff --git a/src/app/fdctl/run/tiles/snapshot.seccomppolicy b/src/app/fdctl/run/tiles/snapshot.seccomppolicy new file mode 100644 index 0000000000..9fdb9eee4b --- /dev/null +++ b/src/app/fdctl/run/tiles/snapshot.seccomppolicy @@ -0,0 +1,75 @@ +# logfile_fd: It can be disabled by configuration, but typically tiles +# will open a log file on boot and write all messages there. +# tmp_fd: This is used to write out a tar archive file +# full_snapshot_fd: This is used to create a final snapshot by compressing +# the contents of tmp_fd +unsigned int logfile_fd, unsigned int tmp_fd, unsigned int tmp_inc_fd, unsigned int full_snapshot_fd, unsigned int incremental_snapshot_fd + +# logging: all log messages are written to a file and/or pipe +# +# 'WARNING' and above are written to the STDERR pipe, while all messages +# are always written to the log file. +# +# arg 0 is the file descriptor to write to. The boot process ensures +# that descriptor 2 is always STDERR. +write: (or (eq (arg 0) 2) + (eq (arg 0) logfile_fd) + (eq (arg 0) tmp_fd) + (eq (arg 0) tmp_inc_fd) + (eq (arg 0) full_snapshot_fd) + (eq (arg 0) incremental_snapshot_fd)) + +# logging: 'WARNING' and above fsync the logfile to disk immediately +# +# arg 0 is the file descriptor to fsync. +fsync: (eq (arg 0) logfile_fd) + +# snapshot: +# +# The only file descriptors that should have their permission changed are +# the two snapshot related files. The temporary file should always have its +# permissions set such that it can be read and written to by the owner, but +# it can't be accessed by anyone else. The snapshot file should be set to +# read/write by the owner at all times. When it is being written to, others +# should not have access to the file. Otherwise, anyone should be able to +# read the snapshot file. +# fchmod: (or (and (eq (arg 0) tmp_fd) +# (eq (arg 1) "S_IRUSR|S_IWUSR")) +# (and (eq (arg 0) full_snapshot_fd) +# (or (eq (arg 1) "S_IRUSR|S_IWUSR") +# (eq (arg 1) "S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH"))) +# (and (eq (arg 0) incremental_snapshot_fd) +# (or (eq (arg 1) "S_IRUSR|S_IWUSR") +# (eq (arg 1) "S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH")))) + +fchmod: (eq (arg 1) "S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH") + +# snapshot: +# +# We want to truncate the tmp file and the snapshot file everytime we try to +# create a new snapshot. If we do truncate it, we only want to be able to +# truncate to a length of zero. +ftruncate: (and (or (eq (arg 0) tmp_fd) + (eq (arg 0) tmp_inc_fd) + (eq (arg 0) full_snapshot_fd) + (eq (arg 0) incremental_snapshot_fd)) + (eq (arg 1) 0)) + +# snapshot: +# +# The tar writer that the snapshot creation logic uses requires seek access +# in the two snapshot related files. +lseek: (or (eq (arg 0) tmp_fd) + (eq (arg 0) tmp_inc_fd) + (eq (arg 0) full_snapshot_fd) + (eq (arg 0) incremental_snapshot_fd)) + +# snapshot +# +# The tar writer that the snapshot creation logic uses requires reads of the +# existing tar archive file. +read: (or (eq (arg 0) tmp_fd) + (eq (arg 0) tmp_inc_fd)) + +# snapshot +readlink: \ No newline at end of file diff --git a/src/app/fdctl/run/topos/fd_firedancer.c b/src/app/fdctl/run/topos/fd_firedancer.c index bc7307ee79..20ab45ebef 100644 --- a/src/app/fdctl/run/topos/fd_firedancer.c +++ b/src/app/fdctl/run/topos/fd_firedancer.c @@ -71,6 +71,7 @@ fd_topo_initialize( config_t * config ) { ulong bank_tile_cnt = config->layout.bank_tile_count; ulong replay_tpool_thread_count = config->tiles.replay.tpool_thread_count; + ulong snaps_tpool_thread_count = config->tiles.snaps.hash_tpool_thread_count; int enable_rpc = ( config->rpc.port != 0 ); @@ -140,7 +141,7 @@ fd_topo_initialize( config_t * config ) { fd_topob_wksp( topo, "gossip" ); fd_topob_wksp( topo, "metric" ); fd_topob_wksp( topo, "replay" ); - fd_topob_wksp( topo, "thread" ); + fd_topob_wksp( topo, "rtpool" ); fd_topob_wksp( topo, "bhole" ); fd_topob_wksp( topo, "bstore" ); fd_topob_wksp( topo, "tcache" ); @@ -148,6 +149,10 @@ fd_topo_initialize( config_t * config ) { fd_topob_wksp( topo, "voter" ); fd_topob_wksp( topo, "poh_slot" ); fd_topob_wksp( topo, "eqvoc" ); + fd_topob_wksp( topo, "snaps" ); + fd_topob_wksp( topo, "stpool" ); + fd_topob_wksp( topo, "constipate" ); + if( enable_rpc ) fd_topob_wksp( topo, "rpcsrv" ); @@ -249,12 +254,17 @@ fd_topo_initialize( config_t * config ) { /**/ fd_topob_tile( topo, "replay", "replay", "metric_in", tile_to_cpu[ topo->tile_cnt ], 0 ); /* These thread tiles must be defined immediately after the replay tile. We subtract one because the replay tile acts as a thread in the tpool as well. */ - FOR(replay_tpool_thread_count-1) fd_topob_tile( topo, "thread", "thread", "metric_in", tile_to_cpu[ topo->tile_cnt ], 0 ); + FOR(replay_tpool_thread_count-1) fd_topob_tile( topo, "rtpool", "rtpool", "metric_in", tile_to_cpu[ topo->tile_cnt ], 0 ); + /**/ fd_topob_tile( topo, "snaps", "snaps", "metric_in", tile_to_cpu[ topo->tile_cnt ], 0 ); + /* These thread tiles must be defined immediately after the snapshot tile. */ + FOR(snaps_tpool_thread_count) fd_topob_tile( topo, "stpool", "stpool", "metric_in", tile_to_cpu[ topo->tile_cnt ], 0 ); + if( enable_rpc ) fd_topob_tile( topo, "rpcsrv", "rpcsrv", "metric_in", tile_to_cpu[ topo->tile_cnt ], 0 ); fd_topo_tile_t * store_tile = &topo->tiles[ fd_topo_find_tile( topo, "storei", 0UL ) ]; fd_topo_tile_t * replay_tile = &topo->tiles[ fd_topo_find_tile( topo, "replay", 0UL ) ]; fd_topo_tile_t * repair_tile = &topo->tiles[ fd_topo_find_tile( topo, "repair", 0UL ) ]; + fd_topo_tile_t * snaps_tile = &topo->tiles[ fd_topo_find_tile( topo, "snaps", 0UL ) ]; /* Create a shared blockstore to be used by store and replay. */ fd_topo_obj_t * blockstore_obj = setup_topo_blockstore( topo, @@ -277,6 +287,7 @@ fd_topo_initialize( config_t * config ) { /* Create a txncache to be used by replay. */ fd_topo_obj_t * txncache_obj = setup_topo_txncache( topo, "tcache", FD_TXNCACHE_DEFAULT_MAX_ROOTED_SLOTS, FD_TXNCACHE_DEFAULT_MAX_LIVE_SLOTS, MAX_CACHE_TXNS_PER_SLOT ); fd_topob_tile_uses( topo, replay_tile, txncache_obj, FD_SHMEM_JOIN_MODE_READ_WRITE ); + fd_topob_tile_uses( topo, snaps_tile, txncache_obj, FD_SHMEM_JOIN_MODE_READ_WRITE ); FD_TEST( fd_pod_insertf_ulong( topo->props, txncache_obj->id, "txncache" ) ); @@ -295,7 +306,7 @@ fd_topo_initialize( config_t * config ) { fd_topob_tile_uses( topo, poh_tile, poh_shred_obj, FD_SHMEM_JOIN_MODE_READ_WRITE ); fd_topob_tile_uses( topo, store_tile, poh_shred_obj, FD_SHMEM_JOIN_MODE_READ_ONLY ); - /* This fseq maintains the node's currernt root slot for the purposes of + /* This fseq maintains the node's current root slot for the purposes of syncing across tiles and shared data structures. */ fd_topo_obj_t * root_slot_obj = fd_topob_obj( topo, "fseq", "root_slot" ); fd_topob_tile_uses( topo, replay_tile, root_slot_obj, FD_SHMEM_JOIN_MODE_READ_WRITE ); @@ -315,6 +326,11 @@ fd_topo_initialize( config_t * config ) { fd_topob_tile_uses( topo, replay_tile, poh_slot_obj, FD_SHMEM_JOIN_MODE_READ_ONLY ); FD_TEST( fd_pod_insertf_ulong( topo->props, poh_slot_obj->id, "poh_slot" ) ); + fd_topo_obj_t * constipated_obj = fd_topob_obj( topo, "fseq", "constipate" ); + fd_topob_tile_uses( topo, replay_tile, constipated_obj, FD_SHMEM_JOIN_MODE_READ_WRITE ); + fd_topob_tile_uses( topo, snaps_tile, constipated_obj, FD_SHMEM_JOIN_MODE_READ_WRITE ); + FD_TEST( fd_pod_insertf_ulong( topo->props, constipated_obj->id, "constipate" ) ); + if( FD_LIKELY( !is_auto_affinity ) ) { if( FD_UNLIKELY( affinity_tile_cnttile_cnt ) ) FD_LOG_ERR(( "The topology you are using has %lu tiles, but the CPU affinity specified in the config tile as [layout.affinity] only provides for %lu cores. " @@ -623,6 +639,8 @@ fd_topo_initialize( config_t * config ) { memcpy( tile->replay.src_mac_addr, config->tiles.net.mac_addr, 6UL ); tile->replay.vote = config->consensus.vote; strncpy( tile->replay.vote_account_path, config->consensus.vote_account_path, sizeof(tile->replay.vote_account_path) ); + tile->replay.full_interval = config->tiles.snaps.full_interval; + tile->replay.incremental_interval = config->tiles.snaps.incremental_interval; FD_LOG_NOTICE(("config->consensus.identity_path: %s", config->consensus.identity_path)); FD_LOG_NOTICE(("config->consensus.vote_account_path: %s", config->consensus.vote_account_path)); @@ -637,7 +655,7 @@ fd_topo_initialize( config_t * config ) { FD_LOG_ERR(( "failed to parse prometheus listen address `%s`", config->tiles.metric.prometheus_listen_address )); tile->metric.prometheus_listen_port = config->tiles.metric.prometheus_listen_port; - } else if( FD_UNLIKELY( !strcmp( tile->name, "thread" ) ) ) { + } else if( FD_UNLIKELY( !strcmp( tile->name, "rtpool" ) ) ) { /* Nothing for now */ } else if( FD_UNLIKELY( !strcmp( tile->name, "pack" ) ) ) { strncpy( tile->pack.identity_key_path, config->consensus.identity_path, sizeof(tile->pack.identity_key_path) ); @@ -670,6 +688,14 @@ fd_topo_initialize( config_t * config ) { tile->rpcserv.tpu_port = config->tiles.quic.regular_transaction_listen_port; tile->rpcserv.tpu_ip_addr = config->tiles.net.ip_addr; strncpy( tile->rpcserv.identity_key_path, config->consensus.identity_path, sizeof(tile->rpcserv.identity_key_path) ); + } else if( FD_UNLIKELY( !strcmp( tile->name, "snaps" ) ) ) { + tile->snaps.full_interval = config->tiles.snaps.full_interval; + tile->snaps.incremental_interval = config->tiles.snaps.incremental_interval; + strncpy( tile->snaps.out_dir, config->tiles.snaps.out_dir, sizeof(tile->snaps.out_dir) ); + tile->snaps.hash_tpool_thread_count = config->tiles.snaps.hash_tpool_thread_count; + strncpy( tile->replay.funk_file, config->tiles.replay.funk_file, sizeof(tile->replay.funk_file) ); + } else if( FD_UNLIKELY( !strcmp( tile->name, "stpool" ) ) ) { + /* Nothing for now */ } else if( FD_UNLIKELY( !strcmp( tile->name, "gui" ) ) ) { if( FD_UNLIKELY( !fd_cstr_to_ip4_addr( config->tiles.gui.gui_listen_address, &tile->gui.listen_addr ) ) ) FD_LOG_ERR(( "failed to parse gui listen address `%s`", config->tiles.gui.gui_listen_address )); diff --git a/src/app/fddev/main1.c b/src/app/fddev/main1.c index 75dd870e3b..cd170af60d 100644 --- a/src/app/fddev/main1.c +++ b/src/app/fddev/main1.c @@ -58,10 +58,12 @@ extern fd_topo_run_tile_t fd_tile_repair; extern fd_topo_run_tile_t fd_tile_store_int; extern fd_topo_run_tile_t fd_tile_replay; extern fd_topo_run_tile_t fd_tile_replay_thread; +extern fd_topo_run_tile_t fd_tile_snaps_thread; extern fd_topo_run_tile_t fd_tile_poh_int; extern fd_topo_run_tile_t fd_tile_sender; extern fd_topo_run_tile_t fd_tile_eqvoc; extern fd_topo_run_tile_t fd_tile_rpcserv; +extern fd_topo_run_tile_t fd_tile_snaps; #endif fd_topo_run_tile_t * TILES[] = { @@ -90,10 +92,12 @@ fd_topo_run_tile_t * TILES[] = { &fd_tile_store_int, &fd_tile_replay, &fd_tile_replay_thread, + &fd_tile_snaps_thread, &fd_tile_poh_int, &fd_tile_sender, &fd_tile_eqvoc, &fd_tile_rpcserv, + &fd_tile_snaps, #endif NULL, }; diff --git a/src/app/ledger/main.c b/src/app/ledger/main.c index ce0e5c11a4..8c9d040007 100644 --- a/src/app/ledger/main.c +++ b/src/app/ledger/main.c @@ -28,6 +28,7 @@ #include "../../flamenco/shredcap/fd_shredcap.h" #include "../../flamenco/runtime/program/fd_bpf_program_util.h" #include "../../flamenco/snapshot/fd_snapshot.h" +#include "../../flamenco/snapshot/fd_snapshot_create.h" extern void fd_write_builtin_bogus_account( fd_exec_slot_ctx_t * slot_ctx, uchar const pubkey[ static 32 ], char const * data, ulong sz ); @@ -88,30 +89,119 @@ struct fd_ledger_args { char const * rocksdb_list[32]; /* max number of rocksdb dirs that can be passed in */ ulong rocksdb_list_slot[32]; /* start slot for each rocksdb dir that's passed in assuming there are mulitple */ ulong rocksdb_list_cnt; /* number of rocksdb dirs passed in */ - uint cluster_version[3]; /* What version of solana is the genesis block? */ + uint cluster_version[3]; /* What version of solana is the genesis block? */ char const * one_off_features[32]; /* List of one off feature pubkeys to enable for execution agnostic of cluster version */ uint one_off_features_cnt; /* Number of one off features */ + ulong snapshot_freq; /* How often a snapshot should be produced */ + ulong incremental_freq; /* How often an incremental snapshot should be produced */ + char const * snapshot_dir; /* Directory to create a snapshot in */ + ulong snapshot_tcnt; /* Number of threads to use for snapshot creation */ - /* These values are setup before replay */ + /* These values are setup and maintained before replay */ fd_capture_ctx_t * capture_ctx; /* capture_ctx is used in runtime_replay for various debugging tasks */ fd_acc_mgr_t acc_mgr[ 1UL ]; /* funk wrapper*/ fd_exec_slot_ctx_t * slot_ctx; /* slot_ctx */ fd_exec_epoch_ctx_t * epoch_ctx; /* epoch_ctx */ fd_tpool_t * tpool; /* thread pool for execution */ uchar tpool_mem[FD_TPOOL_FOOTPRINT( FD_TILE_MAX )] __attribute__( ( aligned( FD_TPOOL_ALIGN ) ) ); + fd_spad_t * spads[ 128UL ]; /* scratchpad allocators that are eventually assigned to each txn_ctx */ ulong spad_cnt; /* number of scratchpads, bounded by number of threads */ + fd_tpool_t * snapshot_tpool; /* thread pool for snapshot creation */ + uchar tpool_mem_snapshot[FD_TPOOL_FOOTPRINT( FD_TILE_MAX )] __attribute__( ( aligned( FD_TPOOL_ALIGN ) ) ); + fd_tpool_t * snapshot_bg_tpool; /* thread pool for snapshot creation */ + uchar tpool_mem_snapshot_bg[FD_TPOOL_FOOTPRINT( FD_TILE_MAX )] __attribute__( ( aligned( FD_TPOOL_ALIGN ) ) ); + ulong last_snapshot_slot; /* last snapshot slot */ + fd_hash_t last_snapshot_hash; /* last snapshot hash */ + ulong last_snapshot_cap; /* last snapshot account capitalization */ + int is_snapshotting; /* determine if a snapshot is being created */ char const * lthash; }; typedef struct fd_ledger_args fd_ledger_args_t; +/* Snapshot *******************************************************************/ + +static void +fd_create_snapshot_task( void FD_PARAM_UNUSED *tpool, + ulong t0 FD_PARAM_UNUSED, ulong t1 FD_PARAM_UNUSED, + void *args FD_PARAM_UNUSED, + void *reduce FD_PARAM_UNUSED, ulong stride FD_PARAM_UNUSED, + ulong l0 FD_PARAM_UNUSED, ulong l1 FD_PARAM_UNUSED, + ulong m0 FD_PARAM_UNUSED, ulong m1 FD_PARAM_UNUSED, + ulong n0 FD_PARAM_UNUSED, ulong n1 FD_PARAM_UNUSED ) { + + fd_snapshot_ctx_t * snapshot_ctx = (fd_snapshot_ctx_t *)t0; + fd_ledger_args_t * ledger_args = (fd_ledger_args_t *)t1; + + char tmp_dir_buf[ FD_SNAPSHOT_DIR_MAX ]; + int err = snprintf( tmp_dir_buf, FD_SNAPSHOT_DIR_MAX, "%s/%s", + snapshot_ctx->out_dir, + snapshot_ctx->is_incremental ? FD_SNAPSHOT_TMP_INCR_ARCHIVE : FD_SNAPSHOT_TMP_ARCHIVE ); + if( FD_UNLIKELY( err<0 ) ) { + FD_LOG_WARNING(( "Failed to format directory string" )); + return; + } + + char zstd_dir_buf[ FD_SNAPSHOT_DIR_MAX ]; + err = snprintf( zstd_dir_buf, FD_SNAPSHOT_DIR_MAX, "%s/%s", + snapshot_ctx->out_dir, + snapshot_ctx->is_incremental ? FD_SNAPSHOT_TMP_INCR_ARCHIVE_ZSTD : FD_SNAPSHOT_TMP_FULL_ARCHIVE_ZSTD ); + if( FD_UNLIKELY( err<0 ) ) { + FD_LOG_WARNING(( "Failed to format directory string" )); + return; + } + + /* Create and open the relevant files for snapshots. */ + + snapshot_ctx->tmp_fd = open( tmp_dir_buf, O_CREAT | O_RDWR | O_TRUNC, 0644 ); + if( FD_UNLIKELY( snapshot_ctx->tmp_fd==-1 ) ) { + FD_LOG_WARNING(( "Failed to open and create tarball for file=%s (%i-%s)", tmp_dir_buf, errno, fd_io_strerror( errno ) )); + return; + } + + snapshot_ctx->snapshot_fd = open( zstd_dir_buf, O_RDWR | O_CREAT | O_TRUNC, 0644 ); + if( FD_UNLIKELY( snapshot_ctx->snapshot_fd==-1 ) ) { + FD_LOG_WARNING(( "Failed to open the snapshot file (%i-%s)", errno, fd_io_strerror( errno ) )); + return; + } + + FD_LOG_WARNING(( "Starting snapshot creation at slot=%lu", snapshot_ctx->slot )); + + err = fd_snapshot_create_new_snapshot( snapshot_ctx, + &ledger_args->last_snapshot_hash, + &ledger_args->last_snapshot_cap ); + if( FD_UNLIKELY( err ) ) { + FD_LOG_ERR(( "failed to create snapshot" )); + } + FD_LOG_NOTICE(( "Successfully produced a snapshot at directory=%s", ledger_args->snapshot_dir )); + + ledger_args->slot_ctx->epoch_ctx->constipate_root = 0; + ledger_args->is_snapshotting = 0; + + err = close( snapshot_ctx->tmp_fd ); + if( FD_UNLIKELY( err ) ) { + FD_LOG_ERR(( "failed to close tmp_fd" )); + } + + err = close( snapshot_ctx->snapshot_fd ); + if( FD_UNLIKELY( err ) ) { + FD_LOG_ERR(( "failed to close snapshot_fd" )); + } + +} + /* Runtime Replay *************************************************************/ static int init_tpool( fd_ledger_args_t * ledger_args ) { - ulong tcnt = fd_tile_cnt(); + + ulong snapshot_tcnt = ledger_args->snapshot_tcnt; + + ulong tcnt = fd_tile_cnt() - snapshot_tcnt; uchar * tpool_scr_mem = NULL; fd_tpool_t * tpool = NULL; + + ulong start_idx = 1UL; if( tcnt>=1UL ) { tpool = fd_tpool_init( ledger_args->tpool_mem, tcnt ); if( tpool == NULL ) { @@ -123,15 +213,59 @@ init_tpool( fd_ledger_args_t * ledger_args ) { FD_LOG_ERR( ( "failed to allocate thread pool scratch space" ) ); } for( ulong i=1UL; itpool = tpool; + + /* Setup a background thread for the snapshot service as well as a tpool + used for snapshot hashing. */ + + if( !snapshot_tcnt ) { + return 0; + } + + else if( snapshot_tcnt==1UL ) { + FD_LOG_ERR(( "This is an invalid value for the number of threads to use for snapshot creation" )); + } + + fd_tpool_t * snapshot_bg_tpool = fd_tpool_init( ledger_args->tpool_mem_snapshot_bg, snapshot_tcnt ); + ulong scratch_sz = fd_scratch_smem_footprint( 256UL<<20UL ); + tpool_scr_mem = fd_valloc_malloc( ledger_args->slot_ctx->valloc, FD_SCRATCH_SMEM_ALIGN, scratch_sz ); + if( FD_UNLIKELY( !fd_tpool_worker_push( snapshot_bg_tpool, start_idx++, tpool_scr_mem, scratch_sz ) ) ) { + FD_LOG_ERR(( "failed to launch worker" )); + } else { + FD_LOG_NOTICE(( "launched snapshot bg worker %lu", start_idx - 1UL )); + } + + ledger_args->snapshot_bg_tpool = snapshot_bg_tpool; + + + if( snapshot_tcnt==2UL ) { + return 0; + } + + /* If a snapshot is being created, setup its own tpool. */ + + fd_tpool_t * snapshot_tpool = fd_tpool_init( ledger_args->tpool_mem_snapshot, snapshot_tcnt - 1UL ); + scratch_sz = fd_scratch_smem_footprint( 256UL<<20UL ); + tpool_scr_mem = fd_valloc_malloc( ledger_args->slot_ctx->valloc, FD_SCRATCH_SMEM_ALIGN, scratch_sz ); + for( ulong i=1UL; isnapshot_tpool = snapshot_tpool; + return 0; } @@ -151,6 +285,8 @@ runtime_replay( fd_ledger_args_t * ledger_args ) { ulong prev_slot = ledger_args->slot_ctx->slot_bank.slot; ulong start_slot = ledger_args->slot_ctx->slot_bank.slot + 1; + ledger_args->slot_ctx->root_slot = prev_slot; + /* On demand rocksdb ingest */ fd_rocksdb_t rocks_db = {0}; fd_rocksdb_root_iter_t iter = {0}; @@ -190,6 +326,8 @@ runtime_replay( fd_ledger_args_t * ledger_args ) { uchar trash_hash_buf[32]; memset( trash_hash_buf, 0xFE, sizeof(trash_hash_buf) ); + ledger_args->is_snapshotting = 0; + ulong block_slot = start_slot; for( ulong slot = start_slot; slot <= ledger_args->end_slot; ++slot ) { ledger_args->slot_ctx->slot_bank.prev_slot = prev_slot; @@ -237,6 +375,12 @@ runtime_replay( fd_ledger_args_t * ledger_args ) { fd_block_t * blk = fd_blockstore_block_query( blockstore, slot ); if( blk == NULL ) { FD_LOG_WARNING(( "failed to read slot %lu", slot )); + /* TODO: This is currently a hack because ticks are not correctly + computed or handled in the runtime. It is neceesary to update ticks + for skipped slots for snapshot creation. */ + ledger_args->slot_ctx->slot_bank.tick_height += 64UL; + ledger_args->slot_ctx->slot_bank.max_tick_height += 64UL; + fd_blockstore_end_read( blockstore ); continue; } @@ -245,6 +389,48 @@ runtime_replay( fd_ledger_args_t * ledger_args ) { ulong sz = blk->data_sz; fd_blockstore_end_read( blockstore ); + if( ledger_args->slot_ctx->root_slot%ledger_args->snapshot_freq==0UL && !ledger_args->is_snapshotting ) { + + ledger_args->is_snapshotting = 1; + + ledger_args->last_snapshot_slot = ledger_args->slot_ctx->root_slot; + + fd_snapshot_ctx_t snapshot_ctx = { + .slot = ledger_args->slot_ctx->root_slot, + .out_dir = ledger_args->snapshot_dir, + .is_incremental = 0, + .valloc = ledger_args->slot_ctx->valloc, + .funk = ledger_args->slot_ctx->acc_mgr->funk, + .status_cache = ledger_args->slot_ctx->status_cache, + .tpool = ledger_args->snapshot_tpool + }; + + fd_tpool_exec( ledger_args->snapshot_bg_tpool, 1UL, fd_create_snapshot_task, NULL, + (ulong)&snapshot_ctx, (ulong)ledger_args, 0UL, NULL, + 0UL, 0UL, 0UL, 0UL, 0UL, 0UL, 0UL ); + + } else if( ledger_args->slot_ctx->root_slot%ledger_args->incremental_freq==0UL && !ledger_args->is_snapshotting && ledger_args->last_snapshot_slot ) { + + ledger_args->is_snapshotting = 1; + + fd_snapshot_ctx_t snapshot_ctx = { + .slot = ledger_args->slot_ctx->root_slot, + .out_dir = ledger_args->snapshot_dir, + .is_incremental = 1, + .valloc = ledger_args->slot_ctx->valloc, + .funk = ledger_args->slot_ctx->acc_mgr->funk, + .status_cache = ledger_args->slot_ctx->status_cache, + .last_snap_slot = ledger_args->last_snapshot_slot, + .tpool = ledger_args->snapshot_tpool, + .last_snap_acc_hash = &ledger_args->last_snapshot_hash, + .last_snap_capitalization = ledger_args->last_snapshot_cap + }; + + fd_tpool_exec( ledger_args->snapshot_bg_tpool, 1UL, fd_create_snapshot_task, NULL, + (ulong)&snapshot_ctx, (ulong)ledger_args, 0UL, NULL, + 0UL, 0UL, 0UL, 0UL, 0UL, 0UL, 0UL ); + } + ulong blk_txn_cnt = 0; FD_TEST( fd_runtime_block_eval_tpool( ledger_args->slot_ctx, ledger_args->capture_ctx, @@ -450,6 +636,11 @@ fd_ledger_main_setup( fd_ledger_args_t * args ) { fd_runtime_recover_banks( args->slot_ctx, 0, args->genesis==NULL ); + args->slot_ctx->snapshot_freq = args->snapshot_freq; + args->slot_ctx->incremental_freq = args->incremental_freq; + args->slot_ctx->last_snapshot_slot = 0UL; + args->last_snapshot_slot = 0UL; + /* Finish other runtime setup steps */ fd_features_restore( args->slot_ctx ); fd_runtime_update_leaders( args->slot_ctx, args->slot_ctx->slot_bank.slot ); @@ -869,9 +1060,19 @@ ingest( fd_ledger_args_t * args ) { slot_ctx->blockstore = args->blockstore; if( args->status_cache_wksp ) { - void * status_cache_mem = fd_wksp_alloc_laddr( args->status_cache_wksp, fd_txncache_align(), fd_txncache_footprint(FD_TXNCACHE_DEFAULT_MAX_ROOTED_SLOTS, FD_TXNCACHE_DEFAULT_MAX_LIVE_SLOTS, MAX_CACHE_TXNS_PER_SLOT), FD_TXNCACHE_MAGIC ); + void * status_cache_mem = fd_wksp_alloc_laddr( args->status_cache_wksp, + fd_txncache_align(), + fd_txncache_footprint( FD_TXNCACHE_DEFAULT_MAX_ROOTED_SLOTS, + FD_TXNCACHE_DEFAULT_MAX_LIVE_SLOTS, + MAX_CACHE_TXNS_PER_SLOT, + FD_TXNCACHE_DEFAULT_MAX_CONSTIPATED_SLOTS ), + FD_TXNCACHE_MAGIC ); FD_TEST( status_cache_mem ); - slot_ctx->status_cache = fd_txncache_join( fd_txncache_new( status_cache_mem, FD_TXNCACHE_DEFAULT_MAX_ROOTED_SLOTS, FD_TXNCACHE_DEFAULT_MAX_LIVE_SLOTS, MAX_CACHE_TXNS_PER_SLOT ) ); + slot_ctx->status_cache = fd_txncache_join( fd_txncache_new( status_cache_mem, + FD_TXNCACHE_DEFAULT_MAX_ROOTED_SLOTS, + FD_TXNCACHE_DEFAULT_MAX_LIVE_SLOTS, + MAX_CACHE_TXNS_PER_SLOT, + FD_TXNCACHE_DEFAULT_MAX_CONSTIPATED_SLOTS ) ); FD_TEST( slot_ctx->status_cache ); } @@ -1004,14 +1205,25 @@ replay( fd_ledger_args_t * args ) { args->slot_ctx->valloc = valloc; args->slot_ctx->acc_mgr = fd_acc_mgr_new( args->acc_mgr, funk ); args->slot_ctx->blockstore = args->blockstore; - void * status_cache_mem = fd_wksp_alloc_laddr( args->wksp, FD_TXNCACHE_ALIGN, fd_txncache_footprint( FD_TXNCACHE_DEFAULT_MAX_ROOTED_SLOTS, FD_TXNCACHE_DEFAULT_MAX_LIVE_SLOTS, MAX_CACHE_TXNS_PER_SLOT), FD_TXNCACHE_MAGIC ); - args->slot_ctx->status_cache = fd_txncache_join( fd_txncache_new( status_cache_mem, FD_TXNCACHE_DEFAULT_MAX_ROOTED_SLOTS, FD_TXNCACHE_DEFAULT_MAX_LIVE_SLOTS, MAX_CACHE_TXNS_PER_SLOT ) ); + void * status_cache_mem = fd_wksp_alloc_laddr( args->wksp, + FD_TXNCACHE_ALIGN, + fd_txncache_footprint( FD_TXNCACHE_DEFAULT_MAX_ROOTED_SLOTS, + FD_TXNCACHE_DEFAULT_MAX_LIVE_SLOTS, + MAX_CACHE_TXNS_PER_SLOT, + FD_TXNCACHE_DEFAULT_MAX_ROOTED_SLOTS), + FD_TXNCACHE_MAGIC ); + args->slot_ctx->status_cache = fd_txncache_join( fd_txncache_new( status_cache_mem, + FD_TXNCACHE_DEFAULT_MAX_ROOTED_SLOTS, + FD_TXNCACHE_DEFAULT_MAX_LIVE_SLOTS, + MAX_CACHE_TXNS_PER_SLOT, + FD_TXNCACHE_DEFAULT_MAX_CONSTIPATED_SLOTS ) ); FD_TEST( args->slot_ctx->status_cache ); init_tpool( args ); /* Check number of records in funk. If rec_cnt == 0, then it can be assumed that you need to load in snapshot(s). */ + ulong rec_cnt = fd_funk_rec_cnt( fd_funk_rec_map( funk, fd_funk_wksp( funk ) ) ); if( !rec_cnt ) { /* Load in snapshot(s) */ @@ -1373,6 +1585,10 @@ initial_setup( int argc, char ** argv, fd_ledger_args_t * args ) { char const * checkpt_status_cache = fd_env_strip_cmdline_cstr ( &argc, &argv, "--checkpt-status-cache", NULL, NULL ); char const * one_off_features = fd_env_strip_cmdline_cstr ( &argc, &argv, "--one-off-features", NULL, NULL ); char const * lthash = fd_env_strip_cmdline_cstr ( &argc, &argv, "--lthash", NULL, "false" ); + ulong snapshot_freq = fd_env_strip_cmdline_ulong( &argc, &argv, "--snapshot-freq", NULL, ULONG_MAX ); + ulong incremental_freq = fd_env_strip_cmdline_ulong( &argc, &argv, "--incremental-freq", NULL, ULONG_MAX ); + char const * snapshot_dir = fd_env_strip_cmdline_cstr ( &argc, &argv, "--snapshot-dir", NULL, NULL ); + ulong snapshot_tcnt = fd_env_strip_cmdline_ulong( &argc, &argv, "--snapshot-tcnt", NULL, 2UL ); if( FD_UNLIKELY( !verify_acc_hash ) ) { /* We've got full snapshots that contain all 0s for the account @@ -1474,6 +1690,10 @@ initial_setup( int argc, char ** argv, fd_ledger_args_t * args ) { args->rocksdb_list_cnt = 0UL; args->checkpt_status_cache = checkpt_status_cache; args->one_off_features_cnt = 0UL; + args->snapshot_freq = snapshot_freq; + args->incremental_freq = incremental_freq; + args->snapshot_dir = snapshot_dir; + args->snapshot_tcnt = snapshot_tcnt; parse_one_off_features( args, one_off_features ); parse_rocksdb_list( args, rocksdb_list, rocksdb_list_starts ); diff --git a/src/ballet/zstd/fd_zstd.h b/src/ballet/zstd/fd_zstd.h index 8f0edbbc29..8efb270b35 100644 --- a/src/ballet/zstd/fd_zstd.h +++ b/src/ballet/zstd/fd_zstd.h @@ -56,6 +56,8 @@ #define FD_ZSTD_MAX_HDR_SZ (18UL) +#define FD_ZSTD_CSTREAM_ALIGN (64UL) + /* Decompress API *****************************************************/ /* fd_zstd_dstream_t provides streaming decompression for Zstandard @@ -152,6 +154,8 @@ fd_zstd_dstream_read( fd_zstd_dstream_t * dstream, uchar * out_end, ulong * opt_errcode ); +/* TODO: Migrate compression logic from fd_snapshot_create. to fd_zstd.h */ + FD_PROTOTYPES_END #endif /* FD_HAS_ZSTD */ diff --git a/src/disco/topo/fd_topo.c b/src/disco/topo/fd_topo.c index 716d90aa98..2d2ee0c571 100644 --- a/src/disco/topo/fd_topo.c +++ b/src/disco/topo/fd_topo.c @@ -197,8 +197,14 @@ fd_topo_tile_extra_huge_pages( fd_topo_tile_t const * tile ) { extra threads which also require stack space. These huge pages need to be reserved as well. */ extra_pages += tile->replay.tpool_thread_count*((FD_TILE_PRIVATE_STACK_SZ/FD_SHMEM_HUGE_PAGE_SZ)+2UL); + } + else if( FD_UNLIKELY ( !strcmp( tile->name, "snaps" ) ) ) { + /* Snapshot tile spawns a bunch of extra threads which also require + stack space. These huge pages need to be reserved as well. */ + extra_pages += tile->snaps.hash_tpool_thread_count *((FD_TILE_PRIVATE_STACK_SZ/FD_SHMEM_HUGE_PAGE_SZ)+2UL); } + return extra_pages; } diff --git a/src/disco/topo/fd_topo.h b/src/disco/topo/fd_topo.h index e8d2e81f03..dd44c07404 100644 --- a/src/disco/topo/fd_topo.h +++ b/src/disco/topo/fd_topo.h @@ -246,6 +246,8 @@ typedef struct { int vote; char vote_account_path[ PATH_MAX ]; ulong bank_tile_count; + ulong full_interval; + ulong incremental_interval; char blockstore_file[ PATH_MAX ]; char blockstore_checkpt[ PATH_MAX ]; @@ -340,6 +342,17 @@ typedef struct { char identity_key_path[ PATH_MAX ]; } rpcserv; + struct { + ulong full_interval; + ulong incremental_interval; + char out_dir[ PATH_MAX ]; + int tmp_fd; + int tmp_inc_fd; + int full_snapshot_fd; + int incremental_snapshot_fd; + ulong hash_tpool_thread_count; + } snaps; + }; } fd_topo_tile_t; diff --git a/src/disco/topo/fd_topob.c b/src/disco/topo/fd_topob.c index 66b11319d3..fe713ea82f 100644 --- a/src/disco/topo/fd_topob.c +++ b/src/disco/topo/fd_topob.c @@ -118,6 +118,7 @@ fd_topob_tile( fd_topo_t * topo, char const * metrics_wksp, ulong cpu_idx, int is_agave ) { + if( FD_UNLIKELY( !topo || !tile_name || !tile_wksp || !metrics_wksp ) ) FD_LOG_ERR(( "NULL args" )); if( FD_UNLIKELY( strlen( tile_name )>=sizeof(topo->tiles[ topo->tile_cnt ].name ) ) ) FD_LOG_ERR(( "tile name too long: %s", tile_name )); if( FD_UNLIKELY( topo->tile_cnt>=FD_TOPO_MAX_TILES ) ) FD_LOG_ERR(( "too many tiles" )); @@ -353,10 +354,12 @@ fd_topob_auto_layout( fd_topo_t * topo ) { "gossip", /* FIREDANCER only */ "repair", /* FIREDANCER only */ "replay", /* FIREDANCER only */ - "thread", /* FIREDANCER only */ + "rtpool", /* FIREDANCER only */ "sender", /* FIREDANCER only */ "eqvoc", /* FIREDANCER only */ "rpcsrv", /* FIREDANCER only */ + "snaps", /* FIREDANCER only */ + "stpool", /* FIREDANCER only */ #endif }; diff --git a/src/flamenco/runtime/Local.mk b/src/flamenco/runtime/Local.mk index 753b6c08e9..df4c3418d4 100644 --- a/src/flamenco/runtime/Local.mk +++ b/src/flamenco/runtime/Local.mk @@ -23,8 +23,13 @@ $(call add-objs,fd_hashes,fd_flamenco) $(call add-hdrs,fd_pubkey_utils.h) $(call add-objs,fd_pubkey_utils,fd_flamenco) +$(call add-hdrs,fd_txncache.h) +$(call add-objs,fd_txncache,fd_flamenco) + $(call add-hdrs,fd_rent_lists.h) +$(call make-unit-test,test_txncache,test_txncache,fd_flamenco fd_util) + ifdef FD_HAS_ATOMIC $(call add-hdrs,fd_runtime.h fd_runtime_init.h fd_runtime_err.h) $(call add-objs,fd_runtime fd_runtime_init,fd_flamenco) @@ -42,10 +47,8 @@ $(call add-objs,fd_rocksdb,fd_flamenco) endif ifdef FD_HAS_ATOMIC -$(call add-hdrs,fd_txncache.h) -$(call add-objs,fd_txncache,fd_flamenco) + ifdef FD_HAS_HOSTED -$(call make-unit-test,test_txncache,test_txncache,fd_flamenco fd_util) $(call make-unit-test,test_archive_block,test_archive_block, fd_flamenco fd_util fd_ballet,$(SECP256K1_LIBS)) # TODO: Flakes # $(call run-unit-test,test_txncache,) diff --git a/src/flamenco/runtime/context/fd_exec_epoch_ctx.h b/src/flamenco/runtime/context/fd_exec_epoch_ctx.h index 448c7f5bd5..e0f858cdfd 100644 --- a/src/flamenco/runtime/context/fd_exec_epoch_ctx.h +++ b/src/flamenco/runtime/context/fd_exec_epoch_ctx.h @@ -31,6 +31,7 @@ struct __attribute__((aligned(64UL))) fd_exec_epoch_ctx { fd_bank_hash_cmp_t * bank_hash_cmp; + int constipate_root; /* Used for constipation in offline replay .*/ ulong total_epoch_stake; }; diff --git a/src/flamenco/runtime/context/fd_exec_slot_ctx.c b/src/flamenco/runtime/context/fd_exec_slot_ctx.c index 70b31320bc..c29d4bdaa5 100644 --- a/src/flamenco/runtime/context/fd_exec_slot_ctx.c +++ b/src/flamenco/runtime/context/fd_exec_slot_ctx.c @@ -118,7 +118,8 @@ recover_clock( fd_exec_slot_ctx_t * slot_ctx ) { for( fd_vote_accounts_pair_t_mapnode_t * n = fd_vote_accounts_pair_t_map_minimum(vote_accounts_pool, vote_accounts_root); n; n = fd_vote_accounts_pair_t_map_successor( vote_accounts_pool, n ) ) { - /* Extract vote timestamp of account */ + + /* Extract vote timestamp of account */ fd_vote_block_timestamp_t vote_state_timestamp = { .timestamp = n->elem.value.last_timestamp_ts, @@ -224,15 +225,19 @@ fd_exec_slot_ctx_recover_( fd_exec_slot_ctx_t * slot_ctx, /* Copy over fields */ + slot_ctx->slot_bank.parent_signature_cnt = oldbank->signature_count; + slot_ctx->slot_bank.tick_height = oldbank->tick_height; + if( oldbank->blockhash_queue.last_hash ) slot_bank->poh = *oldbank->blockhash_queue.last_hash; slot_bank->slot = oldbank->slot; slot_bank->prev_slot = oldbank->parent_slot; fd_memcpy(&slot_bank->banks_hash, &oldbank->hash, sizeof(oldbank->hash)); + fd_memcpy(&slot_ctx->slot_bank.prev_banks_hash, &oldbank->parent_hash, sizeof(oldbank->parent_hash)); fd_memcpy(&slot_bank->fee_rate_governor, &oldbank->fee_rate_governor, sizeof(oldbank->fee_rate_governor)); slot_bank->lamports_per_signature = manifest->lamports_per_signature; slot_ctx->prev_lamports_per_signature = manifest->lamports_per_signature; - slot_ctx->parent_signature_cnt = oldbank->signature_count; + slot_ctx->slot_bank.parent_signature_cnt = oldbank->signature_count; if( oldbank->hashes_per_tick ) epoch_bank->hashes_per_tick = *oldbank->hashes_per_tick; else @@ -277,6 +282,17 @@ fd_exec_slot_ctx_recover_( fd_exec_slot_ctx_t * slot_ctx, recover_clock( slot_ctx ); + /* Pass in the hard forks */ + + /* The hard forks should be deep copied over. + TODO:This should be in the epoch bank and not the slot bank. */ + slot_bank->hard_forks.hard_forks_len = oldbank->hard_forks.hard_forks_len; + slot_bank->hard_forks.hard_forks = fd_valloc_malloc( slot_ctx->valloc, + FD_SLOT_PAIR_ALIGN, + oldbank->hard_forks.hard_forks_len * FD_SLOT_PAIR_FOOTPRINT ); + memcpy( slot_bank->hard_forks.hard_forks, oldbank->hard_forks.hard_forks, + oldbank->hard_forks.hard_forks_len * FD_SLOT_PAIR_FOOTPRINT ); + /* Update last restart slot https://github.com/solana-labs/solana/blob/30531d7a5b74f914dde53bfbb0bc2144f2ac92bb/runtime/src/bank.rs#L2152 @@ -440,6 +456,7 @@ fd_exec_slot_ctx_recover( fd_exec_slot_ctx_t * slot_ctx, fd_exec_slot_ctx_t * fd_exec_slot_ctx_recover_status_cache( fd_exec_slot_ctx_t * ctx, fd_bank_slot_deltas_t * slot_deltas ) { + fd_txncache_t * status_cache = ctx->status_cache; if( !status_cache ) { FD_LOG_WARNING(("No status cache in slot ctx")); diff --git a/src/flamenco/runtime/context/fd_exec_slot_ctx.h b/src/flamenco/runtime/context/fd_exec_slot_ctx.h index bbf54320fe..290db7757d 100644 --- a/src/flamenco/runtime/context/fd_exec_slot_ctx.h +++ b/src/flamenco/runtime/context/fd_exec_slot_ctx.h @@ -67,9 +67,7 @@ struct __attribute__((aligned(8UL))) fd_exec_slot_ctx { /* TODO remove this stuff */ ulong signature_cnt; fd_hash_t account_delta_hash; - fd_hash_t prev_banks_hash; ulong prev_lamports_per_signature; - ulong parent_signature_cnt; ulong parent_transaction_count; ulong txn_count; ulong nonvote_txn_count; @@ -83,9 +81,16 @@ struct __attribute__((aligned(8UL))) fd_exec_slot_ctx { fd_txncache_t * status_cache; fd_slot_history_t slot_history[1]; + ulong tick_count; + int enable_exec_recording; /* Enable/disable execution metadata recording, e.g. txn logs. Analogue of Agave's ExecutionRecordingConfig. */ + + ulong root_slot; + ulong snapshot_freq; + ulong incremental_freq; + ulong last_snapshot_slot; }; #define FD_EXEC_SLOT_CTX_ALIGN (alignof(fd_exec_slot_ctx_t)) diff --git a/src/flamenco/runtime/fd_acc_mgr.h b/src/flamenco/runtime/fd_acc_mgr.h index 4e64493ffc..e7b6c9428e 100644 --- a/src/flamenco/runtime/fd_acc_mgr.h +++ b/src/flamenco/runtime/fd_acc_mgr.h @@ -112,7 +112,6 @@ fd_funk_key_to_acc( fd_funk_rec_key_t const * id ) { return (fd_pubkey_t const *)fd_type_pun_const( id->c ); } - /* Account Access API *************************************************/ static inline void diff --git a/src/flamenco/runtime/fd_hashes.c b/src/flamenco/runtime/fd_hashes.c index e61f10bf74..f538d52de2 100644 --- a/src/flamenco/runtime/fd_hashes.c +++ b/src/flamenco/runtime/fd_hashes.c @@ -38,7 +38,7 @@ struct fd_pubkey_hash_pair_list { typedef struct fd_pubkey_hash_pair_list fd_pubkey_hash_pair_list_t; static void -fd_hash_account_deltas( fd_pubkey_hash_pair_list_t * lists, ulong lists_len, fd_hash_t * hash, fd_exec_slot_ctx_t * slot_ctx FD_PARAM_UNUSED ) { +fd_hash_account_deltas( fd_pubkey_hash_pair_list_t * lists, ulong lists_len, fd_hash_t * hash ) { fd_sha256_t shas[FD_ACCOUNT_DELTAS_MAX_MERKLE_HEIGHT]; uchar num_hashes[FD_ACCOUNT_DELTAS_MAX_MERKLE_HEIGHT+1]; @@ -219,14 +219,15 @@ fd_hash_bank( fd_exec_slot_ctx_t * slot_ctx, fd_hash_t * hash, fd_pubkey_hash_pair_t * dirty_keys, ulong dirty_key_cnt ) { - slot_ctx->prev_banks_hash = slot_ctx->slot_bank.banks_hash; - slot_ctx->parent_signature_cnt = slot_ctx->signature_cnt; + slot_ctx->slot_bank.prev_banks_hash = slot_ctx->slot_bank.banks_hash; + slot_ctx->slot_bank.parent_signature_cnt = slot_ctx->signature_cnt; slot_ctx->prev_lamports_per_signature = slot_ctx->slot_bank.lamports_per_signature; slot_ctx->parent_transaction_count = slot_ctx->slot_bank.transaction_count; sort_pubkey_hash_pair_inplace( dirty_keys, dirty_key_cnt ); fd_pubkey_hash_pair_list_t list1 = { .pairs = dirty_keys, .pairs_len = dirty_key_cnt }; - fd_hash_account_deltas(&list1, 1, &slot_ctx->account_delta_hash, slot_ctx ); + + fd_hash_account_deltas(&list1, 1, &slot_ctx->account_delta_hash ); fd_sha256_t sha; fd_sha256_init( &sha ); @@ -250,7 +251,7 @@ fd_hash_bank( fd_exec_slot_ctx_t * slot_ctx, fd_solcap_write_bank_preimage( capture_ctx->capture, hash->hash, - slot_ctx->prev_banks_hash.hash, + slot_ctx->slot_bank.prev_banks_hash.hash, slot_ctx->account_delta_hash.hash, &slot_ctx->slot_bank.poh.hash, slot_ctx->signature_cnt ); @@ -266,7 +267,7 @@ fd_hash_bank( fd_exec_slot_ctx_t * slot_ctx, "last_blockhash: %s\n", slot_ctx->slot_bank.slot, FD_BASE58_ENC_32_ALLOCA( hash->hash ), - FD_BASE58_ENC_32_ALLOCA( slot_ctx->prev_banks_hash.hash ), + FD_BASE58_ENC_32_ALLOCA( slot_ctx->slot_bank.prev_banks_hash.hash ), FD_BASE58_ENC_32_ALLOCA( slot_ctx->account_delta_hash.hash ), FD_LTHASH_ENC_32_ALLOCA( (fd_lthash_value_t *) slot_ctx->slot_bank.lthash.lthash ), slot_ctx->signature_cnt, @@ -525,6 +526,7 @@ fd_update_hash_bank_tpool( fd_exec_slot_ctx_t * slot_ctx, continue; } + /* All removed recs should be stored with the slot from the funk txn. */ fd_funk_rec_remove( funk, fd_funk_rec_modify(funk, task_info->rec), task_info->rec->pair.xid->ul[0] ); } @@ -716,15 +718,21 @@ typedef struct accounts_hash accounts_hash_t; #include "../../util/tmpl/fd_map_dynamic.c" static fd_pubkey_hash_pair_t * -fd_accounts_sorted_subrange( fd_exec_slot_ctx_t * slot_ctx, uint range_idx, uint range_cnt, ulong * num_pairs_out, fd_lthash_value_t *lthash_values, ulong n0 +fd_accounts_sorted_subrange( fd_funk_t * funk, + uint range_idx, + uint range_cnt, + ulong * num_pairs_out, + fd_lthash_value_t * lthash_values, + ulong n0, + fd_valloc_t valloc ) { - fd_funk_t * funk = slot_ctx->acc_mgr->funk; + fd_wksp_t * wksp = fd_funk_wksp( funk ); fd_funk_rec_t * rec_map = fd_funk_rec_map( funk, wksp ); ulong num_iter_accounts = fd_funk_rec_map_key_max( rec_map ); ulong max_pairs = ( range_cnt == 1U ? num_iter_accounts : 2UL*num_iter_accounts/range_cnt ); /* Initial estimate */ ulong num_pairs = 0; - fd_pubkey_hash_pair_t * pairs = fd_valloc_malloc( slot_ctx->valloc, FD_PUBKEY_HASH_PAIR_ALIGN, max_pairs * sizeof(fd_pubkey_hash_pair_t) ); + fd_pubkey_hash_pair_t * pairs = fd_valloc_malloc( valloc, FD_PUBKEY_HASH_PAIR_ALIGN, max_pairs * sizeof(fd_pubkey_hash_pair_t) ); FD_TEST(NULL != pairs); ulong range_len = ULONG_MAX/range_cnt; ulong range_min = range_len*range_idx; @@ -773,9 +781,9 @@ fd_accounts_sorted_subrange( fd_exec_slot_ctx_t * slot_ctx, uint range_idx, uint if( num_pairs == max_pairs ) { /* Try again with a larger array */ - fd_valloc_free( slot_ctx->valloc, pairs ); + fd_valloc_free( valloc, pairs ); max_pairs *= 2; - pairs = fd_valloc_malloc( slot_ctx->valloc, FD_PUBKEY_HASH_PAIR_ALIGN, max_pairs * sizeof(fd_pubkey_hash_pair_t) ); + pairs = fd_valloc_malloc( valloc, FD_PUBKEY_HASH_PAIR_ALIGN, max_pairs * sizeof(fd_pubkey_hash_pair_t) ); FD_TEST(NULL != pairs); num_pairs = 0; fd_lthash_zero(&accum); @@ -798,10 +806,11 @@ fd_accounts_sorted_subrange( fd_exec_slot_ctx_t * slot_ctx, uint range_idx, uint } struct fd_subrange_task_info { - fd_exec_slot_ctx_t * slot_ctx; + fd_funk_t * funk; ulong num_lists; fd_pubkey_hash_pair_list_t * lists; fd_lthash_value_t *lthash_values; + fd_valloc_t valloc; }; typedef struct fd_subrange_task_info fd_subrange_task_info_t; @@ -815,60 +824,65 @@ fd_accounts_sorted_subrange_task( void *tpool, ulong n0, ulong n1 FD_PARAM_UNUSED) { fd_subrange_task_info_t * task_info = (fd_subrange_task_info_t *)tpool; fd_pubkey_hash_pair_list_t * list = task_info->lists + m0; - list->pairs = fd_accounts_sorted_subrange( task_info->slot_ctx, (uint)m0, (uint)task_info->num_lists, &list->pairs_len, task_info->lthash_values, n0 ); + list->pairs = fd_accounts_sorted_subrange( task_info->funk, (uint)m0, (uint)task_info->num_lists, &list->pairs_len, task_info->lthash_values, n0, task_info->valloc ); } int -fd_accounts_hash( fd_exec_slot_ctx_t * slot_ctx, fd_tpool_t * tpool, fd_hash_t * accounts_hash ) { +fd_accounts_hash( fd_funk_t * funk, + fd_slot_bank_t * slot_bank, + fd_valloc_t valloc, + fd_tpool_t * tpool, + fd_hash_t * accounts_hash ) { FD_LOG_NOTICE(("accounts_hash start")); if( tpool == NULL || fd_tpool_worker_cnt( tpool ) <= 1U ) { ulong num_pairs = 0; - fd_lthash_value_t *lthash_values = fd_valloc_malloc( slot_ctx->valloc, FD_LTHASH_VALUE_ALIGN, FD_LTHASH_VALUE_FOOTPRINT ); + fd_lthash_value_t *lthash_values = fd_valloc_malloc( valloc, FD_LTHASH_VALUE_ALIGN, FD_LTHASH_VALUE_FOOTPRINT ); fd_lthash_zero(<hash_values[0]); - fd_pubkey_hash_pair_t * pairs = fd_accounts_sorted_subrange( slot_ctx, 0, 1, &num_pairs, lthash_values, 0 ); + fd_pubkey_hash_pair_t * pairs = fd_accounts_sorted_subrange( funk, 0, 1, &num_pairs, lthash_values, 0, valloc ); FD_TEST(NULL != pairs); fd_pubkey_hash_pair_list_t list1 = { .pairs = pairs, .pairs_len = num_pairs }; - fd_hash_account_deltas( &list1, 1, accounts_hash, slot_ctx ); - fd_valloc_free( slot_ctx->valloc, pairs ); + fd_hash_account_deltas( &list1, 1, accounts_hash ); + fd_valloc_free( valloc, pairs ); - fd_lthash_value_t * acc = (fd_lthash_value_t *)fd_type_pun(slot_ctx->slot_bank.lthash.lthash); + fd_lthash_value_t * acc = (fd_lthash_value_t *)fd_type_pun(slot_bank->lthash.lthash); fd_lthash_add( acc, <hash_values[0] ); - fd_valloc_free( slot_ctx->valloc, lthash_values ); + fd_valloc_free( valloc, lthash_values ); } else { ulong num_lists = fd_tpool_worker_cnt( tpool ); FD_LOG_NOTICE(( "launching %lu hash tasks", num_lists )); fd_pubkey_hash_pair_list_t lists[num_lists]; - fd_lthash_value_t *lthash_values = fd_valloc_malloc( slot_ctx->valloc, FD_LTHASH_VALUE_ALIGN, num_lists * FD_LTHASH_VALUE_FOOTPRINT ); + fd_lthash_value_t *lthash_values = fd_valloc_malloc( valloc, FD_LTHASH_VALUE_ALIGN, num_lists * FD_LTHASH_VALUE_FOOTPRINT ); for( ulong i = 0; i < num_lists; i++ ) { fd_lthash_zero(<hash_values[i]); } fd_subrange_task_info_t task_info = { - .slot_ctx = slot_ctx, + .funk = funk, .num_lists = num_lists, .lists = lists, - .lthash_values = lthash_values}; - fd_tpool_exec_all_rrobin( tpool, 0, num_lists, fd_accounts_sorted_subrange_task, &task_info, NULL, NULL, 1, 0, num_lists ); - fd_hash_account_deltas( lists, num_lists, accounts_hash, slot_ctx ); + .lthash_values = lthash_values, + .valloc = valloc }; + fd_tpool_exec_all_rrobin( tpool, 0UL, num_lists, fd_accounts_sorted_subrange_task, &task_info, NULL, NULL, 1, 0, num_lists ); + fd_hash_account_deltas( lists, num_lists, accounts_hash ); for( ulong i = 0; i < num_lists; ++i ) { - fd_valloc_free( slot_ctx->valloc, lists[i].pairs ); + fd_valloc_free( valloc, lists[i].pairs ); } - fd_lthash_value_t * acc = (fd_lthash_value_t *)fd_type_pun(slot_ctx->slot_bank.lthash.lthash); + fd_lthash_value_t * acc = (fd_lthash_value_t *)fd_type_pun(slot_bank->lthash.lthash); for( ulong i = 0; i < num_lists; i++ ) { fd_lthash_add( acc, <hash_values[i] ); } - fd_valloc_free( slot_ctx->valloc, lthash_values ); + fd_valloc_free( valloc, lthash_values ); } - FD_LOG_NOTICE(("accounts_lthash %s", FD_LTHASH_ENC_32_ALLOCA( (fd_lthash_value_t *) slot_ctx->slot_bank.lthash.lthash ))); + FD_LOG_NOTICE(("accounts_lthash %s", FD_LTHASH_ENC_32_ALLOCA( (fd_lthash_value_t *) slot_bank->lthash.lthash ))); // fd_accounts_check_lthash( slot_ctx ); - FD_LOG_INFO(("accounts_hash %s", FD_BASE58_ENC_32_ALLOCA( accounts_hash->hash) )); + FD_LOG_NOTICE(("accounts_hash %s", FD_BASE58_ENC_32_ALLOCA( accounts_hash->hash ) )); return 0; } @@ -937,7 +951,7 @@ fd_accounts_hash_inc_only( fd_exec_slot_ctx_t * slot_ctx, fd_hash_t *accounts_ha sort_pubkey_hash_pair_inplace( pairs, num_pairs ); fd_pubkey_hash_pair_list_t list1 = { .pairs = pairs, .pairs_len = num_pairs }; - fd_hash_account_deltas( &list1, 1, accounts_hash, slot_ctx ); + fd_hash_account_deltas( &list1, 1, accounts_hash ); fd_valloc_free( slot_ctx->valloc, pairs ); fd_scratch_pop(); @@ -947,6 +961,90 @@ fd_accounts_hash_inc_only( fd_exec_slot_ctx_t * slot_ctx, fd_hash_t *accounts_ha return 0; } +int +fd_accounts_hash_inc_no_txn( fd_funk_t * funk, + fd_valloc_t valloc, + fd_hash_t * accounts_hash, + fd_funk_rec_key_t const * * pubkeys, + ulong pubkeys_len, + ulong do_hash_verify ) { + FD_LOG_NOTICE(( "accounts_hash_inc_no_txn" )); + + fd_wksp_t * wksp = fd_funk_wksp( funk ); + fd_funk_rec_t * rec_map = fd_funk_rec_map( funk, wksp ); + + /* Pre-allocate the number of pubkey pairs that we are iterating over. */ + + ulong num_iter_accounts = fd_funk_rec_map_key_cnt( rec_map ); + ulong num_pairs = 0UL; + fd_pubkey_hash_pair_t * pairs = fd_valloc_malloc( valloc, + FD_PUBKEY_HASH_PAIR_ALIGN, + num_iter_accounts * sizeof(fd_pubkey_hash_pair_t) ); + + if( FD_UNLIKELY( !pairs ) ) { + FD_LOG_ERR(( "failed to allocate memory for pairs" )); + } + + fd_blake3_t * b3 = NULL; + + FD_SCRATCH_SCOPE_BEGIN { + + for( ulong i=0UL; iinfo.lamports == 0); + + if( is_empty ) { + pairs[num_pairs].rec = rec; + + fd_hash_t * hash = fd_scratch_alloc( alignof(fd_hash_t), sizeof(fd_hash_t) ); + if( !b3 ) { + b3 = fd_scratch_alloc( alignof(fd_blake3_t), sizeof(fd_blake3_t) ); + } + fd_blake3_init ( b3 ); + fd_blake3_append( b3, rec->pair.key->uc, sizeof(fd_pubkey_t) ); + fd_blake3_fini ( b3, hash ); + + pairs[ num_pairs ].hash = hash; + num_pairs++; + continue; + } else { + fd_hash_t *h = (fd_hash_t*)metadata->hash; + if( !(h->ul[ 0 ] | h->ul[ 1 ] | h->ul[ 2 ] | h->ul[ 3 ]) ) { + // By the time we fall into this case, we can assume the ignore_slot feature is enabled... + fd_hash_account_current( (uchar*)metadata->hash, NULL, metadata, rec->pair.key->uc, fd_account_get_data( metadata ) ); + } else if( do_hash_verify ) { + uchar hash[ FD_HASH_FOOTPRINT ]; + fd_hash_account_current( (uchar*)&hash, NULL, metadata, rec->pair.key->uc, fd_account_get_data( metadata ) ); + if( fd_acc_exists( metadata ) && memcmp( metadata->hash, &hash, FD_HASH_FOOTPRINT ) ) { + FD_LOG_WARNING(( "snapshot hash (%s) doesn't match calculated hash (%s)", FD_BASE58_ENC_32_ALLOCA(metadata->hash), FD_BASE58_ENC_32_ALLOCA(&hash) )); + } + } + } + + if( (metadata->info.executable & ~1) ) { + continue; + } + + pairs[ num_pairs ].rec = rec; + pairs[ num_pairs ].hash = (fd_hash_t const *)metadata->hash; + num_pairs++; + } + + sort_pubkey_hash_pair_inplace( pairs, num_pairs ); + fd_pubkey_hash_pair_list_t list1 = { .pairs = pairs, .pairs_len = num_pairs }; + fd_hash_account_deltas( &list1, 1, accounts_hash ); + + fd_valloc_free( valloc, pairs ); + + } FD_SCRATCH_SCOPE_END; + + FD_LOG_INFO(( "accounts_hash %s", FD_BASE58_ENC_32_ALLOCA( accounts_hash->hash) )); + + return 0; +} + int fd_snapshot_hash( fd_exec_slot_ctx_t * slot_ctx, fd_tpool_t * tpool, fd_hash_t * accounts_hash, uint check_hash ) { (void) check_hash; @@ -955,7 +1053,7 @@ fd_snapshot_hash( fd_exec_slot_ctx_t * slot_ctx, fd_tpool_t * tpool, fd_hash_t * FD_LOG_NOTICE(( "snapshot is including epoch account hash" )); fd_sha256_t h; fd_hash_t hash; - fd_accounts_hash( slot_ctx, tpool, &hash ); + fd_accounts_hash( slot_ctx->acc_mgr->funk, &slot_ctx->slot_bank, slot_ctx->valloc, tpool, &hash ); fd_sha256_init( &h ); fd_sha256_append( &h, (uchar const *) hash.hash, sizeof( fd_hash_t ) ); @@ -964,20 +1062,78 @@ fd_snapshot_hash( fd_exec_slot_ctx_t * slot_ctx, fd_tpool_t * tpool, fd_hash_t * return 0; } - return fd_accounts_hash( slot_ctx, tpool, accounts_hash ); + return fd_accounts_hash( slot_ctx->acc_mgr->funk, &slot_ctx->slot_bank, slot_ctx->valloc, tpool, accounts_hash ); +} + +/* TODO: Combine with the above to get correct snapshot hash verification. */ + +int +fd_snapshot_service_hash( fd_hash_t * accounts_hash, + fd_hash_t * snapshot_hash, + fd_slot_bank_t * slot_bank, + fd_epoch_bank_t * epoch_bank, + fd_funk_t * funk, + fd_tpool_t * tpool, + fd_valloc_t valloc ) { + + fd_sha256_t h; + fd_accounts_hash( funk, slot_bank, valloc, tpool, accounts_hash ); + + int should_include_eah = epoch_bank->eah_stop_slot != ULONG_MAX && epoch_bank->eah_start_slot == ULONG_MAX; + + if( should_include_eah ) { + fd_sha256_init( &h ); + fd_sha256_append( &h, (uchar const *) accounts_hash, sizeof( fd_hash_t ) ); + fd_sha256_append( &h, (uchar const *) slot_bank->epoch_account_hash.hash, sizeof( fd_hash_t ) ); + fd_sha256_fini( &h, snapshot_hash ); + } else { + fd_memcpy( snapshot_hash, accounts_hash, sizeof(fd_hash_t) ); + } + + return 0; +} + +int +fd_snapshot_service_inc_hash( fd_hash_t * accounts_hash, + fd_hash_t * snapshot_hash, + fd_slot_bank_t * slot_bank, + fd_epoch_bank_t * epoch_bank, + fd_funk_t * funk, + fd_funk_rec_key_t const * * pubkeys, + ulong pubkeys_len, + fd_valloc_t valloc ) { + + fd_sha256_t h; + fd_accounts_hash_inc_no_txn( funk, valloc, accounts_hash, pubkeys, pubkeys_len, 0UL ); + + int should_include_eah = epoch_bank->eah_stop_slot != ULONG_MAX && epoch_bank->eah_start_slot == ULONG_MAX; + + if( should_include_eah ) { + fd_sha256_init( &h ); + fd_sha256_append( &h, (uchar const *) accounts_hash, sizeof( fd_hash_t ) ); + fd_sha256_append( &h, (uchar const *) slot_bank->epoch_account_hash.hash, sizeof( fd_hash_t ) ); + fd_sha256_fini( &h, snapshot_hash ); + } else { + fd_memcpy( snapshot_hash, accounts_hash, sizeof(fd_hash_t) ); + } + + return 0; } /* Re-computes the lthash from the current slot */ void -fd_accounts_check_lthash( fd_exec_slot_ctx_t * slot_ctx ) { - fd_funk_t * funk = slot_ctx->acc_mgr->funk; +fd_accounts_check_lthash( fd_funk_t * funk, + fd_funk_txn_t * funk_txn, + fd_slot_bank_t * slot_bank, + fd_valloc_t valloc ) { + fd_wksp_t * wksp = fd_funk_wksp( funk ); fd_funk_rec_t * rec_map = fd_funk_rec_map( funk, wksp ); fd_funk_txn_t * txn_map = fd_funk_txn_map( funk, wksp ); // How many txns are we dealing with? ulong txn_cnt = 1; - fd_funk_txn_t * txn = slot_ctx->funk_txn; + fd_funk_txn_t * txn = funk_txn; while (NULL != txn) { txn_cnt++; txn = fd_funk_txn_parent( txn, txn_map ); @@ -989,7 +1145,7 @@ fd_accounts_check_lthash( fd_exec_slot_ctx_t * slot_ctx ) { // Lay it flat to make it easier to walk backwards up the chain from // the root - txn = slot_ctx->funk_txn; + txn = funk_txn; ulong txn_idx = txn_cnt; while (1) { txns[--txn_idx] = txn; @@ -1004,7 +1160,7 @@ fd_accounts_check_lthash( fd_exec_slot_ctx_t * slot_ctx ) { int accounts_hash_slots = fd_ulong_find_msb(num_iter_accounts ) + 1; FD_LOG_WARNING(("allocating memory for hash. num_iter_accounts: %lu slots: %d", num_iter_accounts, accounts_hash_slots)); - void * hashmem = fd_valloc_malloc( slot_ctx->valloc, accounts_hash_align(), accounts_hash_footprint(accounts_hash_slots)); + void * hashmem = fd_valloc_malloc( valloc, accounts_hash_align(), accounts_hash_footprint(accounts_hash_slots)); FD_LOG_WARNING(("initializing memory for hash")); accounts_hash_t * hash_map = accounts_hash_join(accounts_hash_new(hashmem, accounts_hash_slots)); @@ -1054,7 +1210,7 @@ fd_accounts_check_lthash( fd_exec_slot_ctx_t * slot_ctx ) { } // Compare the accumulator to the slot - fd_lthash_value_t * acc = (fd_lthash_value_t *)fd_type_pun_const( slot_ctx->slot_bank.lthash.lthash ); + fd_lthash_value_t * acc = (fd_lthash_value_t *)fd_type_pun_const( slot_bank->lthash.lthash ); if ( memcmp( acc, &acc_lthash, sizeof( fd_lthash_value_t ) ) == 0 ) { FD_LOG_NOTICE(("accounts_lthash %s == %s", FD_LTHASH_ENC_32_ALLOCA (acc), FD_LTHASH_ENC_32_ALLOCA (&acc_lthash))); } else { diff --git a/src/flamenco/runtime/fd_hashes.h b/src/flamenco/runtime/fd_hashes.h index 2ebecb531e..3c699c1c5c 100644 --- a/src/flamenco/runtime/fd_hashes.h +++ b/src/flamenco/runtime/fd_hashes.h @@ -55,18 +55,34 @@ fd_hash_account_current( uchar hash [ static 32 ], uchar const * data ); /* Generate a complete accounts_hash of the entire account database. */ + int -fd_accounts_hash( fd_exec_slot_ctx_t * slot_ctx, - fd_tpool_t * tpool, - fd_hash_t * accounts_hash ); +fd_accounts_hash( fd_funk_t * funk, + fd_slot_bank_t * slot_bank, + fd_valloc_t valloc, + fd_tpool_t * tpool, + fd_hash_t * accounts_hash ); + +/* Special version for verifying incremental snapshot. */ -/* Special version for verifying incremental snapshot */ int fd_accounts_hash_inc_only( fd_exec_slot_ctx_t * slot_ctx, fd_hash_t * accounts_hash, fd_funk_txn_t * child_txn, ulong do_hash_verify ); +/* Same as fd_accounts_hash_inc_only but takes a list of pubkeys to hash. + Query the accounts from the root of funk. This is done as a read-only + way to generate an accounts hash from a subset of accounts from funk. */ + +int +fd_accounts_hash_inc_no_txn( fd_funk_t * funk, + fd_valloc_t valloc, + fd_hash_t * accounts_hash, + fd_funk_rec_key_t const * * pubkeys, + ulong pubkeys_len, + ulong do_hash_verify ); + /* Generate a non-incremental hash of the entire account database, including epoch bank hash. */ int fd_snapshot_hash( fd_exec_slot_ctx_t * slot_ctx, @@ -74,8 +90,39 @@ fd_snapshot_hash( fd_exec_slot_ctx_t * slot_ctx, fd_hash_t * accounts_hash, uint check_hash ); +/* Generate a non-incremental hash of the entire account database, including + the epoch account hash. It differs from fd_snapshot_hash in that this version + is used by the snapshot service which doesn't have access to a slot_ctx + handle. However, it retains a copy of funk, slot_bank, and epoch_bank. + Do the same for the incremental hash. These functions are also + responsible for conditionally including the epoch account hash into + the account hash. These hashes are used by the snapshot service. + TODO: These should be used to generate the hashes from snapshot loading. */ + +int +fd_snapshot_service_hash( fd_hash_t * accounts_hash, + fd_hash_t * snapshot_hash, + fd_slot_bank_t * slot_bank, + fd_epoch_bank_t * epoch_bank, + fd_funk_t * funk, + fd_tpool_t * tpool, + fd_valloc_t valloc ); + +int +fd_snapshot_service_inc_hash( fd_hash_t * accounts_hash, + fd_hash_t * snapshot_hash, + fd_slot_bank_t * slot_bank, + fd_epoch_bank_t * epoch_bank, + fd_funk_t * funk, + fd_funk_rec_key_t const * * pubkeys, + ulong pubkeys_len, + fd_valloc_t valloc ); + void -fd_accounts_check_lthash( fd_exec_slot_ctx_t * slot_ctx ); +fd_accounts_check_lthash( fd_funk_t * funk, + fd_funk_txn_t * funk_txn, + fd_slot_bank_t * slot_bank, + fd_valloc_t valloc ); void fd_calculate_epoch_accounts_hash_values(fd_exec_slot_ctx_t * slot_ctx); diff --git a/src/flamenco/runtime/fd_runtime.c b/src/flamenco/runtime/fd_runtime.c index 7e76f81d5d..2243d3c521 100644 --- a/src/flamenco/runtime/fd_runtime.c +++ b/src/flamenco/runtime/fd_runtime.c @@ -7,6 +7,7 @@ #include "fd_executor.h" #include "fd_account.h" #include "fd_hashes.h" +#include "fd_txncache.h" #include "sysvar/fd_sysvar_cache.h" #include "sysvar/fd_sysvar_clock.h" #include "sysvar/fd_sysvar_epoch_schedule.h" @@ -2179,7 +2180,7 @@ fd_runtime_block_sysvar_update_pre_execute( fd_exec_slot_ctx_t * slot_ctx ) { // FeeRateGovernor::new_derived(&parent.fee_rate_governor, parent.signature_count()) // ); /* https://github.com/firedancer-io/solana/blob/dab3da8e7b667d7527565bddbdbecf7ec1fb868e/runtime/src/bank.rs#L1312-L1314 */ - fd_sysvar_fees_new_derived(slot_ctx, slot_ctx->slot_bank.fee_rate_governor, slot_ctx->parent_signature_cnt); + fd_sysvar_fees_new_derived(slot_ctx, slot_ctx->slot_bank.fee_rate_governor, slot_ctx->slot_bank.parent_signature_cnt); // TODO: move all these out to a fd_sysvar_update() call... long clock_update_time = -fd_log_wallclock(); @@ -2326,6 +2327,8 @@ fd_runtime_block_execute_tpool_v2( fd_exec_slot_ctx_t * slot_ctx, fd_solcap_writer_set_slot( capture_ctx->capture, slot_ctx->slot_bank.slot ); } + slot_ctx->tick_count = 0UL; + long block_execute_time = -fd_log_wallclock(); int res = fd_runtime_block_execute_prepare( slot_ctx ); @@ -2336,8 +2339,15 @@ fd_runtime_block_execute_tpool_v2( fd_exec_slot_ctx_t * slot_ctx, ulong txn_cnt = block_info->txn_cnt; fd_txn_p_t * txn_ptrs = fd_scratch_alloc( alignof(fd_txn_p_t), txn_cnt * sizeof(fd_txn_p_t) ); + /* This now collects the tick entries in a block. */ fd_runtime_block_collect_txns( block_info, txn_ptrs ); + /* TODO: Currently the tick height is manually updated for each executed + slot as a hack to support snapshot loading. This code should be removed + once correct tick calculation is implemented. */ + slot_ctx->slot_bank.tick_height += 64UL; + slot_ctx->slot_bank.max_tick_height += 64UL; + res = fd_runtime_execute_txns_in_waves_tpool( slot_ctx, capture_ctx, txn_ptrs, txn_cnt, tpool, spads, spad_cnt ); if( res != FD_RUNTIME_EXECUTE_SUCCESS ) { return res; @@ -2759,32 +2769,64 @@ fd_runtime_checkpt( fd_capture_ctx_t * capture_ctx, } } +uint +fd_runtime_is_epoch_boundary( fd_epoch_bank_t * epoch_bank, ulong curr_slot, ulong prev_slot ) { + ulong slot_idx; + ulong prev_epoch = fd_slot_to_epoch( &epoch_bank->epoch_schedule, prev_slot, &slot_idx ); + ulong new_epoch = fd_slot_to_epoch( &epoch_bank->epoch_schedule, curr_slot, &slot_idx ); + + return ( prev_epoch < new_epoch || slot_idx == 0 ); +} + static int fd_runtime_publish_old_txns( fd_exec_slot_ctx_t * slot_ctx, fd_capture_ctx_t * capture_ctx, - fd_tpool_t * tpool ) { + fd_tpool_t * tpool ) { /* Publish any transaction older than 31 slots */ - fd_funk_t * funk = slot_ctx->acc_mgr->funk; - fd_funk_txn_t * txnmap = fd_funk_txn_map(funk, fd_funk_wksp(funk)); + fd_funk_t * funk = slot_ctx->acc_mgr->funk; + fd_funk_txn_t * txnmap = fd_funk_txn_map( funk, fd_funk_wksp( funk ) ); + fd_epoch_bank_t * epoch_bank = fd_exec_epoch_ctx_epoch_bank( slot_ctx->epoch_ctx ); + uint depth = 0; for( fd_funk_txn_t * txn = slot_ctx->funk_txn; txn; txn = fd_funk_txn_parent(txn, txnmap) ) { - /* TODO: tmp change */ - if (++depth == (FD_RUNTIME_NUM_ROOT_BLOCKS - 1) ) { + if( ++depth == (FD_RUNTIME_NUM_ROOT_BLOCKS - 1 ) ) { FD_LOG_DEBUG(("publishing %s (slot %lu)", FD_BASE58_ENC_32_ALLOCA( &txn->xid ), txn->xid.ul[0])); - fd_funk_start_write(funk); - ulong publish_err = fd_funk_txn_publish(funk, txn, 1); - if (publish_err == 0) { - FD_LOG_ERR(("publish err")); - return -1; - } - if( slot_ctx->status_cache ) { + if( slot_ctx->status_cache && !fd_txncache_get_is_constipated( slot_ctx->status_cache ) ) { fd_txncache_register_root_slot( slot_ctx->status_cache, txn->xid.ul[0] ); + } else if( slot_ctx->status_cache ) { + fd_txncache_register_constipated_slot( slot_ctx->status_cache, txn->xid.ul[0] ); + } + + fd_funk_start_write( funk ); + if( slot_ctx->epoch_ctx->constipate_root ) { + fd_funk_txn_t * parent = fd_funk_txn_parent( txn, txnmap ); + if( parent != NULL ) { + slot_ctx->root_slot = txn->xid.ul[0]; + + if( FD_UNLIKELY( fd_funk_txn_publish_into_parent( funk, txn, 1) != FD_FUNK_SUCCESS ) ) { + FD_LOG_ERR(( "Unable to publish into the parent transaction" )); + } + } + } else { + slot_ctx->root_slot = txn->xid.ul[0]; + /* TODO: The epoch boundary check is not correct due to skipped slots. */ + if( (!(slot_ctx->root_slot % slot_ctx->snapshot_freq) || ( + !(slot_ctx->root_slot % slot_ctx->incremental_freq) && slot_ctx->last_snapshot_slot)) && + !fd_runtime_is_epoch_boundary( epoch_bank, slot_ctx->root_slot, slot_ctx->root_slot - 1UL )) { + + slot_ctx->last_snapshot_slot = slot_ctx->root_slot; + slot_ctx->epoch_ctx->constipate_root = 1; + fd_txncache_set_is_constipated( slot_ctx->status_cache, 1 ); + } + + if( FD_UNLIKELY( !fd_funk_txn_publish( funk, txn, 1 ) ) ) { + FD_LOG_ERR(( "No transactions were published" )); + } } - fd_epoch_bank_t * epoch_bank = fd_exec_epoch_ctx_epoch_bank( slot_ctx->epoch_ctx ); if( txn->xid.ul[0] >= epoch_bank->eah_start_slot ) { - fd_accounts_hash( slot_ctx, tpool, &slot_ctx->slot_bank.epoch_account_hash ); + fd_accounts_hash( slot_ctx->acc_mgr->funk, &slot_ctx->slot_bank, slot_ctx->valloc, tpool, &slot_ctx->slot_bank.epoch_account_hash ); epoch_bank->eah_start_slot = ULONG_MAX; } diff --git a/src/flamenco/runtime/fd_runtime.h b/src/flamenco/runtime/fd_runtime.h index b70dfb8bfe..2909d5a18c 100644 --- a/src/flamenco/runtime/fd_runtime.h +++ b/src/flamenco/runtime/fd_runtime.h @@ -44,6 +44,12 @@ /* TODO: increase this to default once we have enough memory to support a 95G status cache. */ #define MAX_CACHE_TXNS_PER_SLOT (FD_TXNCACHE_DEFAULT_MAX_TRANSACTIONS_PER_SLOT / 8) +/* This is the reasonably tight upper bound for the number of writable + accounts in a slot. This is because a block has a limit of 48 million + compute units. Each writable account lock costs 300 CUs. That means there + can be up to 48M/300 writable accounts in a block. */ +#define FD_WRITABLE_ACCS_IN_SLOT (160000UL) + struct fd_execute_txn_task_info { fd_spad_t * * spads; fd_exec_txn_ctx_t * txn_ctx; @@ -287,6 +293,11 @@ fd_runtime_checkpt( fd_capture_ctx_t * capture_ctx, fd_exec_slot_ctx_t * slot_ctx, ulong slot ); +uint +fd_runtime_is_epoch_boundary( fd_epoch_bank_t * epoch_bank, + ulong curr_slot, + ulong prev_slot ); + fd_microblock_txn_iter_t fd_microblock_txn_iter_init( fd_microblock_info_t const * microblock_info ); diff --git a/src/flamenco/runtime/fd_txncache.c b/src/flamenco/runtime/fd_txncache.c index e24f057570..84cb8931db 100644 --- a/src/flamenco/runtime/fd_txncache.c +++ b/src/flamenco/runtime/fd_txncache.c @@ -6,6 +6,9 @@ #define SORT_BEFORE(a,b) (a)<(b) #include "../../util/tmpl/fd_sort.c" +/* TODO: This data structure needs a careful audit and testing. It may need + to be reworked to support better fork-aware behavior. */ + /* The number of transactions in each page. This needs to be high enough to amoritze the cost of caller code reserving pages from, and returning pages to the pool, but not so high that the memory @@ -37,6 +40,7 @@ #define FD_TXNCACHE_TOMBSTONE_ENTRY (ULONG_MAX-1UL) /* Placeholder value used for critical sections. */ + #define FD_TXNCACHE_TEMP_ENTRY (ULONG_MAX-2UL) struct fd_txncache_private_txn { @@ -111,6 +115,10 @@ struct __attribute__((aligned(FD_TXNCACHE_ALIGN))) fd_txncache_private { ulong root_slots_max; ulong live_slots_max; + ulong constipated_slots_max; /* The max number of constipated slots that the txncache will support + while in a constipated mode. If this gets exceeded, this means + that the txncache was in a constipated state for too long without + being flushed. */ ushort txnpages_per_blockhash_max; uint txnpages_max; @@ -149,6 +157,25 @@ struct __attribute__((aligned(FD_TXNCACHE_ALIGN))) fd_txncache_private { ulong probed_entries_off; /* The map of index to number of entries which oveflowed over this index. Overflow for index i is defined as every entry j > i where j should have been inserted at k < i. */ + + ulong constipated_slots_cnt; /* The number of constipated root slots that can be supported and + that are tracked in the below array. */ + ulong constipated_slots_off; /* The highest N slots that should be rooted will be in this + array, assuming that the latest slots were constipated + and not flushed. */ + + /* Constipation is used here in the same way Funk is constipated. The reason + this exists is to ensure that the set of rooted slots doesn't change while + the snapshot service is serializing the status cache into a snapshot. This + operation is usually very fast and only takes a few seconds. + TODO: Another implementation of this might be to continue rooting slots + to the same data structure but to temporarily stop evictions. Right now + this is implemented such that we maintain a seperate array of all slots + that should be rooted. Once the constipation is removed, all of the + constipated slots will be registered en masse. */ + int is_constipated; /* Is the status cache in a constipated state.*/ + ulong is_constipated_off; + ulong magic; /* ==FD_TXNCACHE_MAGIC */ }; @@ -157,6 +184,11 @@ fd_txncache_get_root_slots( fd_txncache_t * tc ) { return (ulong *)( (uchar *)tc + tc->root_slots_off ); } +FD_FN_PURE static ulong * +fd_txncache_get_constipated_slots( fd_txncache_t * tc ) { + return (ulong *)( (uchar *)tc + tc->constipated_slots_off ); +} + FD_FN_PURE static fd_txncache_private_blockcache_t * fd_txncache_get_blockcache( fd_txncache_t * tc ) { return (fd_txncache_private_blockcache_t *)( (uchar *)tc + tc->blockcache_off ); @@ -256,7 +288,8 @@ fd_txncache_align( void ) { FD_FN_CONST ulong fd_txncache_footprint( ulong max_rooted_slots, ulong max_live_slots, - ulong max_txn_per_slot ) { + ulong max_txn_per_slot, + ulong max_constipated_slots ) { if( FD_UNLIKELY( max_rooted_slots<1UL || max_live_slots<1UL ) ) return 0UL; if( FD_UNLIKELY( max_live_slotsroot_slots_off = (ulong)_root_slots - (ulong)txncache; - txncache->blockcache_off = (ulong)_blockcache - (ulong)txncache; - txncache->slotcache_off = (ulong)_slotcache - (ulong)txncache; - txncache->txnpages_free_off = (ulong)_txnpages_free - (ulong)txncache; - txncache->txnpages_off = (ulong)_txnpages - (ulong)txncache; - txncache->blockcache_pages_off = (ulong)_blockcache_pages - (ulong)txncache; - txncache->probed_entries_off = (ulong)_probed_entries - (ulong)txncache; - - tc->lock->value = 0; - tc->root_slots_cnt = 0UL; + txncache->root_slots_off = (ulong)_root_slots - (ulong)txncache; + txncache->blockcache_off = (ulong)_blockcache - (ulong)txncache; + txncache->slotcache_off = (ulong)_slotcache - (ulong)txncache; + txncache->txnpages_free_off = (ulong)_txnpages_free - (ulong)txncache; + txncache->txnpages_off = (ulong)_txnpages - (ulong)txncache; + txncache->blockcache_pages_off = (ulong)_blockcache_pages - (ulong)txncache; + txncache->probed_entries_off = (ulong)_probed_entries - (ulong)txncache; + txncache->constipated_slots_off = (ulong)_constipated_slots - (ulong)txncache; + + tc->lock->value = 0; + tc->root_slots_cnt = 0UL; + tc->constipated_slots_cnt = 0UL; tc->root_slots_max = max_rooted_slots; tc->live_slots_max = max_live_slots; + tc->constipated_slots_max = max_constipated_slots; tc->txnpages_per_blockhash_max = max_txnpages_per_blockhash; tc->txnpages_max = max_txnpages; ulong * root_slots = (ulong *)_root_slots; memset( root_slots, 0xFF, max_rooted_slots*sizeof(ulong) ); + ulong * constipated_slots = (ulong *)_constipated_slots; + memset( constipated_slots, 0xFF, max_constipated_slots*sizeof(ulong) ); + fd_txncache_private_blockcache_t * blockcache = (fd_txncache_private_blockcache_t *)_blockcache; fd_txncache_private_slotcache_t * slotcache = (fd_txncache_private_slotcache_t *)_slotcache; ulong * probed_entries = (ulong *)_probed_entries; @@ -503,15 +545,18 @@ fd_txncache_purge_slot( fd_txncache_t * tc, } } -void -fd_txncache_register_root_slot( fd_txncache_t * tc, - ulong slot ) { - fd_rwlock_write( tc->lock ); +/* fd_txncache_register_root_slot_private is a helper function that + actually registers the root. This function assumes that the + caller has already obtained a lock to the status cache. */ + +static void +fd_txncache_register_root_slot_private( fd_txncache_t * tc, + ulong slot ) { ulong * root_slots = fd_txncache_get_root_slots( tc ); ulong idx; for( idx=0UL; idxroot_slots_cnt; idx++ ) { - if( FD_UNLIKELY( root_slots[ idx ]==slot ) ) goto unlock; + if( FD_UNLIKELY( root_slots[ idx ]==slot ) ) return; if( FD_UNLIKELY( root_slots[ idx ]>slot ) ) break; } @@ -530,11 +575,56 @@ fd_txncache_register_root_slot( fd_txncache_t * tc, root_slots[ idx ] = slot; tc->root_slots_cnt++; } +} + +void +fd_txncache_register_root_slot( fd_txncache_t * tc, + ulong slot ) { + + fd_rwlock_write( tc->lock ); + + fd_txncache_register_root_slot_private( tc, slot ); -unlock: fd_rwlock_unwrite( tc->lock ); } +void +fd_txncache_register_constipated_slot( fd_txncache_t * tc, + ulong slot ) { + + fd_rwlock_write( tc->lock ); + + if( FD_UNLIKELY( tc->constipated_slots_cnt>=tc->constipated_slots_max ) ) { + FD_LOG_ERR(( "Status cache has exceeded constipated max slot count" )); + } + + ulong * constipated_slots = fd_txncache_get_constipated_slots( tc ); + constipated_slots[ tc->constipated_slots_cnt++ ] = slot; + + fd_rwlock_unwrite( tc->lock ); + +} + +void +fd_txncache_flush_constipated_slots( fd_txncache_t * tc ) { + + /* Register all previously constipated slots and unconstipate registration + into the status cache. */ + + fd_rwlock_write( tc->lock ); + + ulong * constipated_slots = fd_txncache_get_constipated_slots( tc ); + for( ulong i=0UL; iconstipated_slots_cnt; i++ ) { + fd_txncache_register_root_slot_private( tc, constipated_slots[ i ] ); + } + tc->constipated_slots_cnt = 0UL; + + tc->is_constipated = 0; + + fd_rwlock_unwrite( tc->lock ); + +} + void fd_txncache_root_slots( fd_txncache_t * tc, ulong * out_slots ) { @@ -996,3 +1086,96 @@ fd_txncache_is_rooted_slot( fd_txncache_t * tc, fd_rwlock_unread( tc->lock ); return 0; } + +int +fd_txncache_get_entries( fd_txncache_t * tc, + fd_bank_slot_deltas_t * slot_deltas ) { + + fd_rwlock_read( tc->lock ); + + slot_deltas->slot_deltas_len = tc->root_slots_cnt; + slot_deltas->slot_deltas = fd_scratch_alloc( FD_SLOT_DELTA_ALIGN, tc->root_slots_cnt * sizeof(fd_slot_delta_t) ); + + fd_txncache_private_txnpage_t * txnpages = fd_txncache_get_txnpages( tc ); + ulong * root_slots = fd_txncache_get_root_slots( tc ); + + for( ulong i=0UL; iroot_slots_cnt; i++ ) { + ulong slot = root_slots[ i ]; + + slot_deltas->slot_deltas[ i ].slot = slot; + slot_deltas->slot_deltas[ i ].is_root = 1; + slot_deltas->slot_deltas[ i ].slot_delta_vec = fd_scratch_alloc( FD_STATUS_PAIR_ALIGN, FD_TXNCACHE_DEFAULT_MAX_ROOTED_SLOTS * sizeof(fd_status_pair_t) ); + slot_deltas->slot_deltas[ i ].slot_delta_vec_len = 0UL; + ulong slot_delta_vec_len = 0UL; + + fd_txncache_private_slotcache_t * slotcache; + if( FD_UNLIKELY( FD_TXNCACHE_FIND_FOUND!=fd_txncache_find_slot( tc, slot, 0, &slotcache ) ) ) { + continue; + } + + for( ulong j=0UL; jblockcache[ j ]; + if( FD_UNLIKELY( slotblockcache->txnhash_offset>=ULONG_MAX-1UL ) ) { + continue; + } + fd_status_pair_t * status_pair = &slot_deltas->slot_deltas[ i ].slot_delta_vec[ slot_delta_vec_len++ ]; + fd_memcpy( &status_pair->hash, slotblockcache->blockhash, sizeof(fd_hash_t) ); + status_pair->value.txn_idx = slotblockcache->txnhash_offset; + + /* First count through the number of etnries you expect to encounter + and size out the data structure to store them. */ + + ulong num_statuses = 0UL; + for( ulong k=0UL; kheads[ k ]; + for( ; head!=UINT_MAX; head=txnpages[ head/FD_TXNCACHE_TXNS_PER_PAGE ].txns[ head%FD_TXNCACHE_TXNS_PER_PAGE ]->slotblockcache_next ) { + num_statuses++; + } + } + + status_pair->value.statuses_len = num_statuses; + status_pair->value.statuses = fd_scratch_alloc( FD_CACHE_STATUS_ALIGN, num_statuses * sizeof(fd_cache_status_t) ); + fd_memset( status_pair->value.statuses, 0, num_statuses * sizeof(fd_cache_status_t) ); + + /* Copy over every entry for the given slot into the slot deltas. */ + + num_statuses = 0UL; + for( ulong k=0UL; kheads[ k ]; + for( ; head!=UINT_MAX; head=txnpages[ head/FD_TXNCACHE_TXNS_PER_PAGE ].txns[ head%FD_TXNCACHE_TXNS_PER_PAGE ]->slotblockcache_next ) { + fd_txncache_private_txn_t * txn = txnpages[ head/FD_TXNCACHE_TXNS_PER_PAGE ].txns[ head%FD_TXNCACHE_TXNS_PER_PAGE ]; + fd_memcpy( status_pair->value.statuses[ num_statuses ].key_slice, txn->txnhash, 20 ); + status_pair->value.statuses[ num_statuses++ ].result.discriminant = txn->result; + } + } + } + slot_deltas->slot_deltas[ i ].slot_delta_vec_len = slot_delta_vec_len; + } + + fd_rwlock_unread( tc->lock ); + + return 0; + +} + +int +fd_txncache_get_is_constipated( fd_txncache_t * tc ) { + fd_rwlock_read( tc->lock ); + + int is_constipated = tc->is_constipated; + + fd_rwlock_unread( tc->lock ); + + return is_constipated; +} + +int +fd_txncache_set_is_constipated( fd_txncache_t * tc, int is_constipated ) { + fd_rwlock_read( tc->lock ); + + tc->is_constipated = is_constipated; + + fd_rwlock_unread( tc->lock ); + + return 0; +} diff --git a/src/flamenco/runtime/fd_txncache.h b/src/flamenco/runtime/fd_txncache.h index 2ed2aa4b60..50e1c6d307 100644 --- a/src/flamenco/runtime/fd_txncache.h +++ b/src/flamenco/runtime/fd_txncache.h @@ -1,6 +1,9 @@ #ifndef HEADER_fd_src_flamenco_runtime_txncache_h #define HEADER_fd_src_flamenco_runtime_txncache_h +#include "../fd_flamenco_base.h" +#include "../types/fd_types.h" + /* A txn cache is a concurrent map for saving the result (status) of transactions that have executed. In addition to supporting fast concurrent insertion and query of transaction results, the txn @@ -194,6 +197,19 @@ #define FD_TXNCACHE_DEFAULT_MAX_TRANSACTIONS_PER_SLOT (524288UL) +/* This number is not a strict bound but is a reasonable max allowed of + slots that can be constipated. As of the writing of this comment, the only + use case for constipating the status cache is to generate a snapshot. We + will use constipation here because we want the root to stay frozen while + we generate the full state of a node for a given rooted slot. This max + size gives us roughly 1024 slots * 0.4secs / 60 secs/min = ~6.8 minutes from + when we root a slot to when the status cache is done getting serialized into + the snapshot format. This SHOULD be enough time because serializing the + status cache into a Solana snapshot is done on the order of seconds and is + one of the first things that is done during snapshot creation. */ + +#define FD_TXNCACHE_DEFAULT_MAX_CONSTIPATED_SLOTS (1024UL) + struct fd_txncache_insert { uchar const * blockhash; uchar const * txnhash; @@ -262,13 +278,15 @@ fd_txncache_align( void ); FD_FN_CONST ulong fd_txncache_footprint( ulong max_rooted_slots, ulong max_live_slots, - ulong max_txn_per_slot ); + ulong max_txn_per_slot, + ulong max_constipated_slots ); void * fd_txncache_new( void * shmem, ulong max_rooted_slots, ulong max_live_slots, - ulong max_txn_per_slot ); + ulong max_txn_per_slot, + ulong max_constipated_slots ); fd_txncache_t * fd_txncache_join( void * shtc ); @@ -296,6 +314,19 @@ void fd_txncache_register_root_slot( fd_txncache_t * tc, ulong slot ); +/* fd_txncache_register_constipated_slot is the "constipated" version of + fd_txncache_register_root_slot. This means that older root slots will not + get purged nor will the newer root slots actually be rooted. All the slots + that are marked as constipated will be flushed down to the set of rooted + slots when fd_txncache_flush_constipated_slots is called. */ + +void +fd_txncache_register_constipated_slot( fd_txncache_t * tc, + ulong slot ); + +void +fd_txncache_flush_constipated_slots( fd_txncache_t * tc ); + /* fd_txncache_root_slots returns the list of live slots currently tracked by the txn cache. There will be at most max_root_slots slots, which will be written into the provided out_slots. It is @@ -394,6 +425,26 @@ int fd_txncache_is_rooted_slot( fd_txncache_t * tc, ulong slot ); +/* fd_txncache_get_entries is responsible for converting the rooted state of + the status cache back into fd_bank_slot_deltas_t, which is the decoded + format used by Agave. This is a helper method used to generate Agave- + compatible snapshots. + TODO: Currently all allocations are done via scratch. This should + probably be changed in the future. */ + +int +fd_txncache_get_entries( fd_txncache_t * tc, + fd_bank_slot_deltas_t * bank_slot_deltas ); + +/* fd_txncache_{is,set}_constipated is used to set and determine if the + status cache is currently in a constipated state. */ + +int +fd_txncache_get_is_constipated( fd_txncache_t * tc ); + +int +fd_txncache_set_is_constipated( fd_txncache_t * tc, int is_constipated ); + FD_PROTOTYPES_END #endif /* HEADER_fd_src_flamenco_runtime_txncache_h */ diff --git a/src/flamenco/runtime/program/fd_bpf_program_util.c b/src/flamenco/runtime/program/fd_bpf_program_util.c index d7a60a3c77..fec24854a1 100644 --- a/src/flamenco/runtime/program/fd_bpf_program_util.c +++ b/src/flamenco/runtime/program/fd_bpf_program_util.c @@ -53,7 +53,7 @@ fd_acc_mgr_cache_key( fd_pubkey_t const * pubkey ) { return id; } -/* Similar to the below function, but gets the executable program content for the v4 loader. +/* 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. */ static int diff --git a/src/flamenco/runtime/test_txncache.c b/src/flamenco/runtime/test_txncache.c index a2bda08d71..90f5f42531 100644 --- a/src/flamenco/runtime/test_txncache.c +++ b/src/flamenco/runtime/test_txncache.c @@ -20,14 +20,16 @@ init_all( ulong max_rooted_slots, ulong max_transactions_per_slot ) { ulong footprint = fd_txncache_footprint( max_rooted_slots, max_live_slots, - max_transactions_per_slot ); + max_transactions_per_slot, + 0UL ); FD_TEST( footprint ); if( FD_UNLIKELY( footprint>txncache_scratch_sz ) ) FD_LOG_ERR(( "Test required %lu bytes, but scratch was only %lu", footprint, txncache_scratch_sz )); fd_txncache_t * tc = fd_txncache_join( fd_txncache_new( txncache_scratch, max_rooted_slots, max_live_slots, - max_transactions_per_slot ) ); + max_transactions_per_slot, + 0UL ) ); FD_TEST( tc ); return tc; } @@ -134,27 +136,31 @@ void test_new_join_leave_delete( void ) { FD_LOG_NOTICE(( "TEST NEW JOIN LEAVE DELETE" )); - FD_TEST( fd_txncache_new( NULL, 1UL, 1UL, 1UL )==NULL ); /* null shmem */ - FD_TEST( fd_txncache_new( (void *)0x1UL, 1UL, 1UL, 1UL )==NULL ); /* misaligned shmem */ - FD_TEST( fd_txncache_new( txncache_scratch, 0UL, 1UL, 1UL )==NULL ); /* 0 max_rooted_slots */ - FD_TEST( fd_txncache_new( txncache_scratch, 2UL, 1UL, 1UL )==NULL ); /* 0 max_live_slotsuc, accounts_hash.uc, 32) != 0) - FD_LOG_ERR(( "snapshot accounts_hash %s != %s", FD_BASE58_ENC_32_ALLOCA( accounts_hash.hash ), FD_BASE58_ENC_32_ALLOCA( fhash->uc ) )); + FD_LOG_ERR(( "snapshot accounts_hash (calculated) %s != (expected) %s", FD_BASE58_ENC_32_ALLOCA( accounts_hash.hash ), FD_BASE58_ENC_32_ALLOCA( fhash->uc ) )); else FD_LOG_NOTICE(( "snapshot accounts_hash %s verified successfully", FD_BASE58_ENC_32_ALLOCA( accounts_hash.hash) )); } else if (snapshot_type == FD_SNAPSHOT_TYPE_INCREMENTAL) { diff --git a/src/flamenco/snapshot/fd_snapshot_create.c b/src/flamenco/snapshot/fd_snapshot_create.c new file mode 100644 index 0000000000..dd43f44ea4 --- /dev/null +++ b/src/flamenco/snapshot/fd_snapshot_create.c @@ -0,0 +1,1162 @@ +#include "fd_snapshot_create.h" +#include "../runtime/sysvar/fd_sysvar_epoch_schedule.h" +#include "../../ballet/zstd/fd_zstd.h" +#include "../runtime/fd_hashes.h" +#include "../runtime/fd_runtime.h" + +#include +#include +#include +#include + +static uchar padding[ FD_SNAPSHOT_ACC_ALIGN ] = {0}; +static fd_account_meta_t default_meta = { .magic = FD_ACCOUNT_META_MAGIC }; + +static inline fd_account_meta_t * +fd_snapshot_create_get_default_meta( ulong slot ) { + default_meta.slot = slot; + return &default_meta; +} + +static inline void +fd_snapshot_create_populate_acc_vecs( fd_snapshot_ctx_t * snapshot_ctx, + fd_solana_manifest_serializable_t * manifest, + fd_tar_writer_t * writer, + ulong * out_cap ) { + + /* The append vecs need to be described in an index in the manifest so a + reader knows what account files to look for. These files are technically + slot indexed, but the Firedancer implementation of the Solana snapshot + produces far fewer indices. These storages are for the accounts + that were modified and deleted in the most recent slot because that + information is used by the Agave client to calculate and verify the + bank hash for the given slot. This is done as an optimization to avoid + having to slot index the Firedancer accounts db which would incur a large + performance hit. + + To avoid iterating through the root twice to determine what accounts were + touched in the snapshot slot and what accounts were touched in the + other slots, we will create an array of pubkey pointers for all accounts + that were touched in the snapshot slot. This buffer can be safely sized to + the maximum amount of writable accounts that are possible in a non-epoch + boundary slot. The rationale for this bound is explained in fd_runtime.h. + We will not attempt to create a snapshot on an epoch boundary. + + TODO: We must add compaction here. */ + + fd_pubkey_t * * snapshot_slot_keys = fd_valloc_malloc( snapshot_ctx->valloc, alignof(fd_pubkey_t*), sizeof(fd_pubkey_t*) * FD_WRITABLE_ACCS_IN_SLOT ); + ulong snapshot_slot_key_cnt = 0UL; + + /* We will dynamically resize the number of incremental keys because the upper + bound will be roughly 8 bytes * writable accs in a slot * number of slots + since the last full snapshot which can quickly grow to be severalgigabytes + or more. In the normal case, this won't require dynamic resizing. */ + #define FD_INCREMENTAL_KEY_INIT_BOUND (100000UL) + ulong incremental_key_bound = FD_INCREMENTAL_KEY_INIT_BOUND; + ulong incremental_key_cnt = 0UL; + fd_funk_rec_key_t const * * incremental_keys = snapshot_ctx->is_incremental ? + fd_valloc_malloc( snapshot_ctx->valloc, alignof(fd_funk_rec_key_t*), sizeof(fd_funk_rec_key_t*) * incremental_key_bound ) : + NULL; + + #undef FD_INCREMENTAL_KEY_INIT_BOUND + + /* In order to size out the accounts DB index in the manifest, we must + iterate through funk and accumulate the size of all of the records + from all slots before the snapshot_slot. */ + + fd_funk_t * funk = snapshot_ctx->acc_mgr->funk; + ulong prev_sz = 0UL; + ulong tombstones_cnt = 0UL; + for( fd_funk_rec_t const * rec = fd_funk_txn_first_rec( funk, NULL ); NULL != rec; rec = fd_funk_txn_next_rec( funk, rec ) ) { + + if( !fd_funk_key_is_acc( rec->pair.key ) ) { + continue; + } + + tombstones_cnt++; + + int is_tombstone = rec->flags & FD_FUNK_REC_FLAG_ERASE; + uchar const * raw = fd_funk_val( rec, fd_funk_wksp( funk ) ); + fd_account_meta_t * metadata = is_tombstone ? fd_snapshot_create_get_default_meta( fd_funk_rec_get_erase_data( rec ) ) : + (fd_account_meta_t*)raw; + + if( !metadata ) { + continue; + } + + if( metadata->magic!=FD_ACCOUNT_META_MAGIC ) { + continue; + } + + if( snapshot_ctx->is_incremental ) { + /* We only care about accounts that were modified since the last + snapshot slot for incremental snapshots. + + We also need to keep track of the capitalization for all of the + accounts that are in the incremental as this is verified. */ + if( metadata->slot<=snapshot_ctx->last_snap_slot ) { + continue; + } + incremental_keys[ incremental_key_cnt++ ] = rec->pair.key; + *out_cap += metadata->info.lamports; + + if( FD_UNLIKELY( incremental_key_cnt==incremental_key_bound ) ) { + /* Dynamically resize if needed. */ + incremental_key_bound *= 2UL; + fd_funk_rec_key_t const * * new_incremental_keys = fd_valloc_malloc( snapshot_ctx->valloc, + alignof(fd_funk_rec_key_t*), + sizeof(fd_funk_rec_key_t*) * incremental_key_bound ); + fd_memcpy( new_incremental_keys, incremental_keys, sizeof(fd_funk_rec_key_t*) * incremental_key_cnt ); + fd_valloc_free( snapshot_ctx->valloc, incremental_keys ); + incremental_keys = new_incremental_keys; + } + } + + /* We know that all of the accounts from the snapshot slot can fit into + one append vec, so we ignore all accounts from the snapshot slot. */ + + if( metadata->slot==snapshot_ctx->slot ) { + continue; + } + + prev_sz += metadata->dlen + sizeof(fd_solana_account_hdr_t); + + } + + /* At this point we have sized out all of the relevant accounts that will + be included in the snapshot. Now we must populate each of the append vecs + and update the index as we go. + + When we account for the number of slots we need to consider one append vec + for the snapshot slot and try to maximally fill up the others: an append + vec has a protocol-defined maximum size in Agave. */ + + ulong num_slots = 1UL + prev_sz / FD_SNAPSHOT_APPEND_VEC_SZ_MAX + + (prev_sz % FD_SNAPSHOT_APPEND_VEC_SZ_MAX ? 1UL : 0UL); + + fd_solana_accounts_db_fields_t * accounts_db = &manifest->accounts_db; + + accounts_db->storages_len = num_slots; + accounts_db->storages = fd_valloc_malloc( snapshot_ctx->valloc, + FD_SNAPSHOT_SLOT_ACC_VECS_ALIGN, + sizeof(fd_snapshot_slot_acc_vecs_t) * accounts_db->storages_len ); + accounts_db->version = 1UL; + accounts_db->slot = snapshot_ctx->slot; + accounts_db->historical_roots_len = 0UL; + accounts_db->historical_roots = NULL; + accounts_db->historical_roots_with_hash_len = 0UL; + accounts_db->historical_roots_with_hash = NULL; + + for( ulong i=0UL; istorages[ i ].account_vecs_len = 1UL; + accounts_db->storages[ i ].account_vecs = fd_valloc_malloc( snapshot_ctx->valloc, + FD_SNAPSHOT_ACC_VEC_ALIGN, + sizeof(fd_snapshot_acc_vec_t) * accounts_db->storages[ i ].account_vecs_len ); + accounts_db->storages[ i ].account_vecs[ 0 ].file_sz = 0UL; + accounts_db->storages[ i ].account_vecs[ 0 ].id = i + 1UL; + accounts_db->storages[ i ].slot = snapshot_ctx->slot - i; + } + + /* At this point we have iterated through all of the accounts and created + the index. We are now ready to generate a snapshot hash. For both + snapshots we need to generate two hashes: + 1. The accounts hash. This is a simple hash of all of the accounts + included in the snapshot. + 2. The snapshot hash. This is a hash of the accounts hash and the epoch + account hash. If the EAH is not included, then the accounts hash == + snapshot hash. + + There is some nuance as to which hash goes where. For full snapshots, + the accounts hash in the bank hash info is the accounts hash. The hash in + the filename is the snapshot hash. + + For incremental snapshots, the account hash in the bank hash info field is + left zeroed out. The full snapshot's hash is in the incremental persistence + field. The incremental snapshot's accounts hash is included in the + incremental persistence field. The hash in the filename is the snapshot + hash. */ + + int err; + if( !snapshot_ctx->is_incremental ) { + err = fd_snapshot_service_hash( &snapshot_ctx->acc_hash, + &snapshot_ctx->snap_hash, + &snapshot_ctx->slot_bank, + &snapshot_ctx->epoch_bank, + snapshot_ctx->acc_mgr->funk, + snapshot_ctx->tpool, + snapshot_ctx->valloc ); + accounts_db->bank_hash_info.accounts_hash = snapshot_ctx->acc_hash; + } else { + err = fd_snapshot_service_inc_hash( &snapshot_ctx->acc_hash, + &snapshot_ctx->snap_hash, + &snapshot_ctx->slot_bank, + &snapshot_ctx->epoch_bank, + snapshot_ctx->acc_mgr->funk, + incremental_keys, + incremental_key_cnt, + snapshot_ctx->valloc ); + fd_valloc_free( snapshot_ctx->valloc, incremental_keys ); + + fd_memset( &accounts_db->bank_hash_info.accounts_hash, 0, sizeof(fd_hash_t) ); + } + + FD_LOG_NOTICE(( "Hashes calculated acc_hash=%s snapshot_hash=%s", + FD_BASE58_ENC_32_ALLOCA(&snapshot_ctx->acc_hash), + FD_BASE58_ENC_32_ALLOCA(&snapshot_ctx->snap_hash) )); + + if( FD_UNLIKELY( err ) ) { + FD_LOG_ERR(( "Unable to calculate snapshot hash" )); + } + + fd_memset( &accounts_db->bank_hash_info.stats, 0, sizeof(fd_bank_hash_stats_t) ); + + /* Now, we have calculated the relevant hashes for the accounts. + Because the files are serially written out for tar and we need to prepend + the manifest, we must reserve space in the archive for the solana manifest. */ + + if( snapshot_ctx->is_incremental ) { + manifest->bank_incremental_snapshot_persistence = fd_valloc_malloc( snapshot_ctx->valloc, + FD_BANK_INCREMENTAL_SNAPSHOT_PERSISTENCE_ALIGN, + sizeof(fd_bank_incremental_snapshot_persistence_t) ); + } + + ulong manifest_sz = fd_solana_manifest_serializable_size( manifest ); + + char buffer[ FD_SNAPSHOT_DIR_MAX ]; + err = snprintf( buffer, FD_SNAPSHOT_DIR_MAX, "snapshots/%lu/%lu", snapshot_ctx->slot, snapshot_ctx->slot ); + if( FD_UNLIKELY( err<0 ) ) { + FD_LOG_ERR(( "Unable to format manifest name string" )); + } + + err = fd_tar_writer_new_file( writer, buffer ); + if( FD_UNLIKELY( err ) ) { + FD_LOG_ERR(( "Unable to create snapshot manifest file" )); + } + + /* TODO: We want to eliminate having to write back into the tar file. This + will enable the snapshot service to only use one file per snapshot. + In order to do this, we must precompute the index in the manifest + completely. This will allow us to stream out a compressed snapshot. */ + + err = fd_tar_writer_make_space( writer, manifest_sz ); + if( FD_UNLIKELY( err ) ) { + FD_LOG_ERR(( "Unable to make space for snapshot manifest file" )); + } + + err = fd_tar_writer_fini_file( writer ); + if( FD_UNLIKELY( err ) ) { + FD_LOG_ERR(( "Unable to finalize snapshot manifest file" )); + } + + /* We have made space for the manifest and are ready to append the append + vec files directly into the tar archive. We will iterate through all of + the records in the funk root and create/populate an append vec for + previous slots. Just record the pubkeys for the latest slot to populate + the append vec after. If the append vec is full, write into the next one. */ + + ulong curr_slot = 1UL; + fd_snapshot_acc_vec_t * prev_accs = &accounts_db->storages[ curr_slot ].account_vecs[ 0UL ]; + + err = snprintf( buffer, FD_SNAPSHOT_DIR_MAX, "accounts/%lu.%lu", snapshot_ctx->slot - curr_slot, prev_accs->id ); + if( FD_UNLIKELY( err<0 ) ) { + FD_LOG_ERR(( "Unable to format previous accounts name string" )); + } + + err = fd_tar_writer_new_file( writer, buffer ); + if( FD_UNLIKELY( err ) ) { + FD_LOG_ERR(( "Unable to create previous accounts file" )); + } + + fd_funk_rec_t * * tombstones = snapshot_ctx->is_incremental ? NULL : + fd_valloc_malloc( snapshot_ctx->valloc, alignof(fd_funk_rec_t*), sizeof(fd_funk_rec_t*) * tombstones_cnt ); + tombstones_cnt = 0UL; + + for( fd_funk_rec_t const * rec = fd_funk_txn_first_rec( funk, NULL ); NULL != rec; rec = fd_funk_txn_next_rec( funk, rec ) ) { + + /* Get the account data. */ + + if( !fd_funk_key_is_acc( rec->pair.key ) ) { + continue; + } + + fd_pubkey_t const * pubkey = fd_type_pun_const( rec->pair.key[0].uc ); + int is_tombstone = rec->flags & FD_FUNK_REC_FLAG_ERASE; + uchar const * raw = fd_funk_val( rec, fd_funk_wksp( funk ) ); + fd_account_meta_t * metadata = is_tombstone ? fd_snapshot_create_get_default_meta( fd_funk_rec_get_erase_data( rec ) ) : + (fd_account_meta_t*)raw; + + if( !snapshot_ctx->is_incremental && is_tombstone ) { + /* If we are in a full snapshot, we need to gather all of the accounts + that we plan on deleting. */ + tombstones[ tombstones_cnt++ ] = (fd_funk_rec_t*)rec; + } + + if( !metadata ) { + continue; + } + + if( metadata->magic!=FD_ACCOUNT_META_MAGIC ) { + continue; + } + + /* Don't iterate through accounts that were touched before the last full + snapshot. */ + if( snapshot_ctx->is_incremental && metadata->slot<=snapshot_ctx->last_snap_slot ) { + continue; + } + + uchar const * acc_data = raw + metadata->hlen; + + /* All accounts that were touched in the snapshot slot should be in + a different append vec so that Agave can calculate the snapshot slot's + bank hash. We don't want to include them in an arbitrary append vec. */ + + if( metadata->slot==snapshot_ctx->slot ) { + snapshot_slot_keys[ snapshot_slot_key_cnt++ ] = (fd_pubkey_t*)pubkey; + continue; + } + + /* We don't want to iterate over tombstones if the snapshot is not + incremental */ + if( !snapshot_ctx->is_incremental && is_tombstone ) { + continue; + } + + ulong new_sz = prev_accs->file_sz + sizeof(fd_solana_account_hdr_t) + fd_ulong_align_up( metadata->dlen, FD_SNAPSHOT_ACC_ALIGN ); + + if( new_sz>FD_SNAPSHOT_APPEND_VEC_SZ_MAX ) { + + /* When the current append vec is full, finish writing it, start writing + into the next append vec. */ + + err = fd_tar_writer_fini_file( writer ); + if( FD_UNLIKELY( err ) ) { + FD_LOG_ERR(( "Unable to finalize previous accounts file" )); + } + + prev_accs = &accounts_db->storages[ ++curr_slot ].account_vecs[ 0UL ]; + + err = snprintf( buffer, FD_SNAPSHOT_DIR_MAX, "accounts/%lu.%lu", snapshot_ctx->slot - curr_slot, prev_accs->id ); + if( FD_UNLIKELY( err<0 ) ) { + FD_LOG_ERR(( "Unable to format previous accounts name string" )); + } + + err = fd_tar_writer_new_file( writer, buffer ); + if( FD_UNLIKELY( err ) ) { + FD_LOG_ERR(( "Unable to create previous accounts file" )); + } + } + + prev_accs->file_sz += sizeof(fd_solana_account_hdr_t) + fd_ulong_align_up( metadata->dlen, FD_SNAPSHOT_ACC_ALIGN ); + + + /* Write out the header. */ + + fd_solana_account_hdr_t header = {0}; + /* Stored meta */ + header.meta.write_version_obsolete = 0UL; + header.meta.data_len = metadata->dlen; + fd_memcpy( header.meta.pubkey, pubkey, sizeof(fd_pubkey_t) ); + /* Account Meta */ + header.info.lamports = metadata->info.lamports; + header.info.rent_epoch = header.info.lamports ? metadata->info.rent_epoch : 0UL; + fd_memcpy( header.info.owner, metadata->info.owner, sizeof(fd_pubkey_t) ); + header.info.executable = metadata->info.executable; + /* Hash */ + fd_memcpy( &header.hash, metadata->hash, sizeof(fd_hash_t) ); + + err = fd_tar_writer_write_file_data( writer, &header, sizeof(fd_solana_account_hdr_t) ); + if( FD_UNLIKELY( err ) ) { + FD_LOG_ERR(( "Unable to stream out account header to tar archive" )); + } + + /* Write out the file data. */ + + err = fd_tar_writer_write_file_data( writer, acc_data, metadata->dlen ); + if( FD_UNLIKELY( err ) ) { + FD_LOG_ERR(( "Unable to stream out account data to tar archive" )); + } + + ulong align_sz = fd_ulong_align_up( metadata->dlen, FD_SNAPSHOT_ACC_ALIGN ) - metadata->dlen; + err = fd_tar_writer_write_file_data( writer, padding, align_sz ); + if( FD_UNLIKELY( err ) ) { + FD_LOG_ERR( ("Unable to stream out account padding to tar archive" )); + } + } + + err = fd_tar_writer_fini_file( writer ); + if( FD_UNLIKELY( err ) ) { + FD_LOG_ERR(( "Unable to finalize previous accounts file" )); + } + + /* Now write out the append vec for the snapshot slot. Again, this is needed + because the snapshot slot's accounts must be in their append vec in order + to verify the bank hash for the snapshot slot in the Agave client. */ + + fd_snapshot_acc_vec_t * curr_accs = &accounts_db->storages[ 0UL ].account_vecs[ 0UL ]; + err = snprintf( buffer, FD_SNAPSHOT_DIR_MAX, "accounts/%lu.%lu", snapshot_ctx->slot, curr_accs->id ); + if( FD_UNLIKELY( err<0 ) ) { + FD_LOG_ERR(( "Unable to format current accounts name string" )); + } + + err = fd_tar_writer_new_file( writer, buffer ); + if( FD_UNLIKELY( err ) ) { + FD_LOG_ERR(( "Unable to create current accounts file" )); + } + + for( ulong i=0UL; iflags & FD_FUNK_REC_FLAG_ERASE; + uchar const * raw = fd_funk_val( rec, fd_funk_wksp( funk ) ); + fd_account_meta_t * metadata = is_tombstone ? fd_snapshot_create_get_default_meta( fd_funk_rec_get_erase_data( rec ) ) : + (fd_account_meta_t*)raw; + + if( FD_UNLIKELY( !metadata ) ) { + FD_LOG_ERR(( "Record should have non-NULL metadata" )); + } + + if( FD_UNLIKELY( metadata->magic!=FD_ACCOUNT_META_MAGIC ) ) { + FD_LOG_ERR(( "Record should have valid magic" )); + } + + uchar const * acc_data = raw + metadata->hlen; + + curr_accs->file_sz += sizeof(fd_solana_account_hdr_t) + fd_ulong_align_up( metadata->dlen, FD_SNAPSHOT_ACC_ALIGN ); + + /* Write out the header. */ + fd_solana_account_hdr_t header = {0}; + /* Stored meta */ + header.meta.write_version_obsolete = 0UL; + header.meta.data_len = metadata->dlen; + fd_memcpy( header.meta.pubkey, pubkey, sizeof(fd_pubkey_t) ); + /* Account Meta */ + header.info.lamports = metadata->info.lamports; + header.info.rent_epoch = header.info.lamports ? metadata->info.rent_epoch : 0UL; + fd_memcpy( header.info.owner, metadata->info.owner, sizeof(fd_pubkey_t) ); + header.info.executable = metadata->info.executable; + /* Hash */ + fd_memcpy( &header.hash, metadata->hash, sizeof(fd_hash_t) ); + + + err = fd_tar_writer_write_file_data( writer, &header, sizeof(fd_solana_account_hdr_t) ); + if( FD_UNLIKELY( err ) ) { + FD_LOG_ERR(( "Unable to stream out account header to tar archive" )); + } + err = fd_tar_writer_write_file_data( writer, acc_data, metadata->dlen ); + if( FD_UNLIKELY( err ) ) { + FD_LOG_ERR(( "Unable to stream out account data to tar archive" )); + } + ulong align_sz = fd_ulong_align_up( metadata->dlen, FD_SNAPSHOT_ACC_ALIGN ) - metadata->dlen; + err = fd_tar_writer_write_file_data( writer, padding, align_sz ); + if( FD_UNLIKELY( err ) ) { + FD_LOG_ERR(( "Unable to stream out account padding to tar archive" )); + } + } + + err = fd_tar_writer_fini_file( writer ); + if( FD_UNLIKELY( err ) ) { + FD_LOG_ERR(( "Unable to finish writing out file" )); + } + + /* TODO: At this point we must implement compaction to the snapshot service. + Without this, we are actually not cleaning up any tombstones from funk. */ + + if( snapshot_ctx->is_incremental ) { + fd_funk_start_write( funk ); + err = fd_funk_rec_forget( funk, tombstones, tombstones_cnt ); + if( FD_UNLIKELY( err!=FD_FUNK_SUCCESS ) ) { + FD_LOG_ERR(( "Unable to forget tombstones" )); + } + FD_LOG_NOTICE(( "Compacted %lu tombstone records", tombstones_cnt )); + fd_funk_end_write( funk ); + } + + fd_valloc_free( snapshot_ctx->valloc, snapshot_slot_keys ); + fd_valloc_free( snapshot_ctx->valloc, tombstones ); + +} + +static void +fd_snapshot_create_serialiable_stakes( fd_snapshot_ctx_t * snapshot_ctx, + fd_stakes_t * old_stakes, + fd_stakes_serializable_t * new_stakes ) { + +/* The deserialized stakes cache that is used by the runtime can't be + reserialized into the format that Agave uses. For every vote account + in the stakes struct, the Firedancer client holds a decoded copy of the + vote state. However, this vote state can't be reserialized back into the + full vote account data. + + This poses a problem in the Agave client client because upon boot, Agave + verifies that for all of the vote accounts in the stakes struct, the data + in the cache is the same as the data in the accounts db. + + The other problem is that the Firedancer stakes cache does not evict old + entries and doesn't update delegations within the cache. The cache will + just insert new pubkeys as stake accounts are created/delegated to. To + make the cache conformant for the snapshot, old accounts should be removed + from the snapshot and all of the delegations should be updated. */ + + /* First populate the vote accounts using the vote accounts/stakes cache. + We can populate over all of the fields except we can't reserialize the + vote account data. Instead we will copy over the raw contents of all of + the vote accounts. */ + + ulong vote_accounts_len = fd_vote_accounts_pair_t_map_size( old_stakes->vote_accounts.vote_accounts_pool, old_stakes->vote_accounts.vote_accounts_root ); + new_stakes->vote_accounts.vote_accounts_pool = fd_vote_accounts_pair_serializable_t_map_alloc( snapshot_ctx->valloc, fd_ulong_max(vote_accounts_len, 15000 ) ); + new_stakes->vote_accounts.vote_accounts_root = NULL; + + for( fd_vote_accounts_pair_t_mapnode_t * n = fd_vote_accounts_pair_t_map_minimum( + old_stakes->vote_accounts.vote_accounts_pool, + old_stakes->vote_accounts.vote_accounts_root ); + n; + n = fd_vote_accounts_pair_t_map_successor( old_stakes->vote_accounts.vote_accounts_pool, n ) ) { + + fd_vote_accounts_pair_serializable_t_mapnode_t * new_node = fd_vote_accounts_pair_serializable_t_map_acquire( new_stakes->vote_accounts.vote_accounts_pool ); + new_node->elem.key = n->elem.key; + new_node->elem.stake = n->elem.stake; + /* Now to populate the value, lookup the account using the acc mgr */ + FD_BORROWED_ACCOUNT_DECL( vote_acc ); + int err = fd_acc_mgr_view( snapshot_ctx->acc_mgr, NULL, &n->elem.key, vote_acc ); + if( FD_UNLIKELY( err ) ) { + FD_LOG_ERR(( "Failed to view vote account from stakes cache %s", FD_BASE58_ENC_32_ALLOCA(&n->elem.key) )); + } + + new_node->elem.value.lamports = vote_acc->const_meta->info.lamports; + new_node->elem.value.data_len = vote_acc->const_meta->dlen; + new_node->elem.value.data = fd_valloc_malloc( snapshot_ctx->valloc, 8UL, vote_acc->const_meta->dlen ); + fd_memcpy( new_node->elem.value.data, vote_acc->const_data, vote_acc->const_meta->dlen ); + fd_memcpy( &new_node->elem.value.owner, &vote_acc->const_meta->info.owner, sizeof(fd_pubkey_t) ); + new_node->elem.value.executable = vote_acc->const_meta->info.executable; + new_node->elem.value.rent_epoch = vote_acc->const_meta->info.rent_epoch; + fd_vote_accounts_pair_serializable_t_map_insert( new_stakes->vote_accounts.vote_accounts_pool, &new_stakes->vote_accounts.vote_accounts_root, new_node ); + + } + + /* Stale stake delegations should also be removed or updated in the cache. + TODO: This will likely be changed in the near future as the stake + program is migrated to a bpf program. It will likely be replaced by an + index of stake/vote accounts. */ + + FD_BORROWED_ACCOUNT_DECL( stake_acc ); + fd_delegation_pair_t_mapnode_t * nn = NULL; + for( fd_delegation_pair_t_mapnode_t * n = fd_delegation_pair_t_map_minimum( + old_stakes->stake_delegations_pool, old_stakes->stake_delegations_root ); n; n=nn ) { + + nn = fd_delegation_pair_t_map_successor( old_stakes->stake_delegations_pool, n ); + + int err = fd_acc_mgr_view( snapshot_ctx->acc_mgr, NULL, &n->elem.account, stake_acc ); + if( FD_UNLIKELY( err ) ) { + /* If the stake account doesn't exist, the cache is stale and the entry + just needs to be evicted. */ + fd_delegation_pair_t_map_remove( old_stakes->stake_delegations_pool, &old_stakes->stake_delegations_root, n ); + fd_delegation_pair_t_map_release( old_stakes->stake_delegations_pool, n ); + } else { + /* Otherwise, just update the delegation in case it is stale. */ + fd_bincode_decode_ctx_t ctx = { + .data = stake_acc->const_data, + .dataend = stake_acc->const_data + stake_acc->const_meta->dlen, + .valloc = snapshot_ctx->valloc + }; + fd_stake_state_v2_t stake_state = {0}; + err = fd_stake_state_v2_decode( &stake_state, &ctx ); + if( FD_UNLIKELY( err ) ) { + FD_LOG_ERR(( "Failed to decode stake state" )); + } + n->elem.delegation = stake_state.inner.stake.stake.delegation; + } + } + + /* Copy over the rest of the fields as they are the same. */ + + new_stakes->stake_delegations_pool = old_stakes->stake_delegations_pool; + new_stakes->stake_delegations_root = old_stakes->stake_delegations_root; + new_stakes->unused = old_stakes->unused; + new_stakes->epoch = old_stakes->epoch; + new_stakes->stake_history = old_stakes->stake_history; + +} + +static inline void +fd_snapshot_create_populate_bank( fd_snapshot_ctx_t * snapshot_ctx, + fd_serializable_versioned_bank_t * bank ) { + + fd_slot_bank_t * slot_bank = &snapshot_ctx->slot_bank; + fd_epoch_bank_t * epoch_bank = &snapshot_ctx->epoch_bank; + + /* The blockhash queue has to be copied over along with all of its entries. + As a note, the size is 300 but in fact is of size 301 due to a knwon bug + in the agave client that is emulated by the firedancer client. */ + + bank->blockhash_queue.last_hash_index = slot_bank->block_hash_queue.last_hash_index; + bank->blockhash_queue.last_hash = fd_valloc_malloc( snapshot_ctx->valloc, FD_HASH_ALIGN, FD_HASH_FOOTPRINT ); + fd_memcpy( bank->blockhash_queue.last_hash, slot_bank->block_hash_queue.last_hash, sizeof(fd_hash_t) ); + + bank->blockhash_queue.ages_len = fd_hash_hash_age_pair_t_map_size( slot_bank->block_hash_queue.ages_pool, slot_bank->block_hash_queue.ages_root); + bank->blockhash_queue.ages = fd_valloc_malloc( snapshot_ctx->valloc, FD_HASH_HASH_AGE_PAIR_ALIGN, bank->blockhash_queue.ages_len * sizeof(fd_hash_hash_age_pair_t) ); + bank->blockhash_queue.max_age = FD_BLOCKHASH_QUEUE_SIZE; + + fd_block_hash_queue_t * queue = &slot_bank->block_hash_queue; + fd_hash_hash_age_pair_t_mapnode_t * nn = NULL; + ulong blockhash_queue_idx = 0UL; + for( fd_hash_hash_age_pair_t_mapnode_t * n = fd_hash_hash_age_pair_t_map_minimum( queue->ages_pool, queue->ages_root ); n; n = nn ) { + nn = fd_hash_hash_age_pair_t_map_successor( queue->ages_pool, n ); + fd_memcpy( &bank->blockhash_queue.ages[ blockhash_queue_idx++ ], &n->elem, sizeof(fd_hash_hash_age_pair_t) ); + } + + + + /* Ancestor can be omitted to boot off of for both clients */ + + bank->ancestors_len = 0UL; + bank->ancestors = NULL; + + bank->hash = slot_bank->banks_hash; + bank->parent_hash = slot_bank->prev_banks_hash; + bank->parent_slot = slot_bank->prev_slot; + bank->hard_forks = slot_bank->hard_forks; + bank->transaction_count = slot_bank->transaction_count; + bank->signature_count = slot_bank->parent_signature_cnt; + bank->capitalization = slot_bank->capitalization; + bank->tick_height = slot_bank->tick_height; + bank->max_tick_height = slot_bank->max_tick_height; + + /* The hashes_per_tick needs to be copied over from the epoch bank because + the pointer could go out of bounds during an epoch boundary. */ + bank->hashes_per_tick = fd_valloc_malloc( snapshot_ctx->valloc, alignof(ulong), sizeof(ulong) ); + fd_memcpy( bank->hashes_per_tick, &epoch_bank->hashes_per_tick, sizeof(ulong) ); + + bank->ticks_per_slot = FD_TICKS_PER_SLOT; + bank->ns_per_slot = epoch_bank->ns_per_slot; + bank->genesis_creation_time = epoch_bank->genesis_creation_time; + bank->slots_per_year = epoch_bank->slots_per_year; + + /* This value can be set to 0 because the Agave client recomputes this value + and the firedancer client doesn't use it. */ + + bank->accounts_data_len = 0UL; + + bank->slot = snapshot_ctx->slot; + bank->epoch = fd_slot_to_epoch( &epoch_bank->epoch_schedule, bank->slot, NULL ); + bank->block_height = slot_bank->block_height; + + /* Collector id can be left as null for both clients */ + + fd_memset( &bank->collector_id, 0, sizeof(fd_pubkey_t) ); + + bank->collector_fees = slot_bank->collected_execution_fees + slot_bank->collected_priority_fees; + bank->fee_calculator.lamports_per_signature = slot_bank->lamports_per_signature; + bank->fee_rate_governor = slot_bank->fee_rate_governor; + bank->collected_rent = slot_bank->collected_rent; + + bank->rent_collector.epoch = bank->epoch; + bank->rent_collector.epoch_schedule = epoch_bank->rent_epoch_schedule; + bank->rent_collector.slots_per_year = epoch_bank->slots_per_year; + bank->rent_collector.rent = epoch_bank->rent; + + bank->epoch_schedule = epoch_bank->epoch_schedule; + bank->inflation = epoch_bank->inflation; + + /* Unused accounts can be left as NULL for both clients. */ + + fd_memset( &bank->unused_accounts, 0, sizeof(fd_unused_accounts_t) ); + + /* We need to copy over the stakes for two epochs despite the Agave client + providing the stakes for 6 epochs. These stakes need to be copied over + because of the fact that the leader schedule computation uses the two + previous epoch stakes. + + TODO: This field has been deprecated by agave and has instead been + replaced with the versioned epoch stakes field in the manifest. The + firedancer client will populate the deprecated field. */ + + fd_epoch_epoch_stakes_pair_t * relevant_epoch_stakes = fd_valloc_malloc( snapshot_ctx->valloc, FD_EPOCH_EPOCH_STAKES_PAIR_ALIGN, 2UL * sizeof(fd_epoch_epoch_stakes_pair_t) ); + fd_memset( &relevant_epoch_stakes[0], 0UL, sizeof(fd_epoch_epoch_stakes_pair_t) ); + fd_memset( &relevant_epoch_stakes[1], 0UL, sizeof(fd_epoch_epoch_stakes_pair_t) ); + relevant_epoch_stakes[0].key = bank->epoch; + relevant_epoch_stakes[0].value.stakes.vote_accounts = slot_bank->epoch_stakes; + relevant_epoch_stakes[1].key = bank->epoch+1UL; + relevant_epoch_stakes[1].value.stakes.vote_accounts = epoch_bank->next_epoch_stakes; + + bank->epoch_stakes_len = 2UL; + bank->epoch_stakes = relevant_epoch_stakes; + bank->is_delta = snapshot_ctx->is_incremental; + + /* The firedancer runtime currently maintains a version of the stakes which + can't be reserialized into a format that is compatible with the Solana + snapshot format. Therefore, we must recompute the data structure using + the pubkeys from the stakes cache that is currently in the epoch context. */ + + fd_snapshot_create_serialiable_stakes( snapshot_ctx, &epoch_bank->stakes, &bank->stakes ); + +} + +static inline void +fd_snapshot_create_setup_and_validate_ctx( fd_snapshot_ctx_t * snapshot_ctx ) { + + fd_funk_t * funk = snapshot_ctx->funk; + + /* Initialize the account manager. */ + + uchar * mem = fd_valloc_malloc( snapshot_ctx->valloc, FD_ACC_MGR_ALIGN, FD_ACC_MGR_FOOTPRINT ); + snapshot_ctx->acc_mgr = fd_acc_mgr_new( mem, funk ); + if( FD_UNLIKELY( !snapshot_ctx->acc_mgr ) ) { + FD_LOG_ERR(( "Failed to initialize account manager" )); + } + + /* First the epoch bank. */ + + fd_funk_rec_key_t epoch_id = fd_runtime_epoch_bank_key(); + fd_funk_rec_t const * epoch_rec = fd_funk_rec_query( funk, NULL, &epoch_id ); + if( FD_UNLIKELY( !epoch_rec ) ) { + FD_LOG_ERR(( "Failed to read epoch bank record: missing record" )); + } + void * epoch_val = fd_funk_val( epoch_rec, fd_funk_wksp( funk ) ); + + if( FD_UNLIKELY( fd_funk_val_sz( epoch_rec )valloc + }; + + if( FD_UNLIKELY( epoch_magic!=FD_RUNTIME_ENC_BINCODE ) ) { + FD_LOG_ERR(( "Epoch bank record has wrong magic" )); + } + + int err = fd_epoch_bank_decode( &snapshot_ctx->epoch_bank, &epoch_decode_ctx ); + if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) { + FD_LOG_ERR(( "Failed to decode epoch bank" )); + } + + /* Now the slot bank. */ + + fd_funk_rec_key_t slot_id = fd_runtime_slot_bank_key(); + fd_funk_rec_t const * slot_rec = fd_funk_rec_query( funk, NULL, &slot_id ); + if( FD_UNLIKELY( !slot_rec ) ) { + FD_LOG_ERR(( "Failed to read slot bank record: missing record" )); + } + void * slot_val = fd_funk_val( slot_rec, fd_funk_wksp( funk ) ); + + if( FD_UNLIKELY( fd_funk_val_sz( slot_rec )valloc + }; + + if( FD_UNLIKELY( slot_magic!=FD_RUNTIME_ENC_BINCODE ) ) { + FD_LOG_ERR(( "Slot bank record has wrong magic" )); + } + + err = fd_slot_bank_decode( &snapshot_ctx->slot_bank, &slot_decode_ctx ); + if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) { + FD_LOG_ERR(( "Failed to decode slot bank" )); + } + + /* Validate that the snapshot context is setup correctly */ + + if( FD_UNLIKELY( !snapshot_ctx->out_dir ) ) { + FD_LOG_ERR(( "Snapshot directory is not set" )); + } + + if( FD_UNLIKELY( snapshot_ctx->slot>snapshot_ctx->slot_bank.slot ) ) { + FD_LOG_ERR(( "Snapshot slot=%lu is greater than the current slot=%lu", + snapshot_ctx->slot, snapshot_ctx->slot_bank.slot )); + } + + /* Truncate the two files used for snapshot creation and seek to its start. */ + + long seek = lseek( snapshot_ctx->tmp_fd, 0, SEEK_SET ); + if( FD_UNLIKELY( seek ) ) { + FD_LOG_ERR(( "Failed to seek to the start of the file" )); + } + + if( FD_UNLIKELY( ftruncate( snapshot_ctx->tmp_fd, 0UL ) < 0 ) ) { + FD_LOG_ERR(( "Failed to truncate the temporary file" )); + } + + seek = lseek( snapshot_ctx->snapshot_fd, 0, SEEK_SET ); + if( FD_UNLIKELY( seek ) ) { + FD_LOG_ERR(( "Failed to seek to the start of the file" )); + } + + if( FD_UNLIKELY( ftruncate( snapshot_ctx->snapshot_fd, 0UL ) < 0 ) ) { + FD_LOG_ERR(( "Failed to truncate the snapshot file" )); + } + +} + +static inline void +fd_snapshot_create_setup_writer( fd_snapshot_ctx_t * snapshot_ctx ) { + + /* Setup a tar writer. */ + + uchar * writer_mem = fd_valloc_malloc( snapshot_ctx->valloc, fd_tar_writer_align(), fd_tar_writer_footprint() ); + snapshot_ctx->writer = fd_tar_writer_new( writer_mem, snapshot_ctx->tmp_fd ); + if( FD_UNLIKELY( !snapshot_ctx->writer ) ) { + FD_LOG_ERR(( "Unable to create a tar writer" )); + } +} + +static inline void +fd_snapshot_create_write_version( fd_snapshot_ctx_t * snapshot_ctx ) { + + /* The first file in the tar archive should be the version file.. */ + + int err = fd_tar_writer_new_file( snapshot_ctx->writer, FD_SNAPSHOT_VERSION_FILE ); + if( FD_UNLIKELY( err ) ) { + FD_LOG_ERR(( "Failed to create the version file" )); + } + + err = fd_tar_writer_write_file_data( snapshot_ctx->writer, FD_SNAPSHOT_VERSION, FD_SNAPSHOT_VERSION_LEN); + if( FD_UNLIKELY( err ) ) { + FD_LOG_ERR(( "Failed to create the version file" )); + } + + err = fd_tar_writer_fini_file( snapshot_ctx->writer ); + if( FD_UNLIKELY( err ) ) { + FD_LOG_ERR(( "Failed to create the version file" )); + } + +} + +static inline void +fd_snapshot_create_write_status_cache( fd_snapshot_ctx_t * snapshot_ctx ) { + + FD_SCRATCH_SCOPE_BEGIN { + + /* First convert the existing status cache into a snapshot-friendly format. */ + + fd_bank_slot_deltas_t slot_deltas_new = {0}; + int err = fd_txncache_get_entries( snapshot_ctx->status_cache, + &slot_deltas_new ); + if( FD_UNLIKELY( err ) ) { + FD_LOG_ERR(( "Failed to get entries from the status cache" )); + } + ulong bank_slot_deltas_sz = fd_bank_slot_deltas_size( &slot_deltas_new ); + uchar * out_status_cache = fd_valloc_malloc( snapshot_ctx->valloc, + FD_BANK_SLOT_DELTAS_ALIGN, + bank_slot_deltas_sz ); + fd_bincode_encode_ctx_t encode_status_cache = { + .data = out_status_cache, + .dataend = out_status_cache + bank_slot_deltas_sz, + }; + if( FD_UNLIKELY( fd_bank_slot_deltas_encode( &slot_deltas_new, &encode_status_cache ) ) ) { + FD_LOG_ERR(( "Failed to encode the status cache" )); + } + + /* Now write out the encoded buffer to the tar archive. */ + + err = fd_tar_writer_new_file( snapshot_ctx->writer, FD_SNAPSHOT_STATUS_CACHE_FILE ); + if( FD_UNLIKELY( err ) ) { + FD_LOG_ERR(( "Failed to create the status cache file" )); + } + err = fd_tar_writer_write_file_data( snapshot_ctx->writer, out_status_cache, bank_slot_deltas_sz ); + if( FD_UNLIKELY( err ) ) { + FD_LOG_ERR(( "Failed to create the status cache file" )); + } + err = fd_tar_writer_fini_file( snapshot_ctx->writer ); + if( FD_UNLIKELY( err ) ) { + FD_LOG_ERR(( "Failed to create the status cache file" )); + } + + /* Registers all roots and unconstipates the status cache. */ + + fd_txncache_flush_constipated_slots( snapshot_ctx->status_cache ); + + fd_valloc_free( snapshot_ctx->valloc, out_status_cache ); + + } FD_SCRATCH_SCOPE_END; + +} + +static inline void +fd_snapshot_create_write_manifest_and_acc_vecs( fd_snapshot_ctx_t * snapshot_ctx, + fd_hash_t * out_hash, + ulong * out_capitalization ) { + + + fd_solana_manifest_serializable_t manifest = {0}; + + /* Copy in all the fields of the bank. */ + + fd_snapshot_create_populate_bank( snapshot_ctx, &manifest.bank ); + + /* Populate the rest of the manifest, except for the append vec index. */ + + manifest.lamports_per_signature = snapshot_ctx->slot_bank.lamports_per_signature; + manifest.epoch_account_hash = &snapshot_ctx->slot_bank.epoch_account_hash; + + /* FIXME: The versioned epoch stakes needs to be implemented. Right now if + we try to create a snapshot on or near an epoch boundary, we will produce + an invalid snapshot. */ + + manifest.versioned_epoch_stakes_len = 0UL; + manifest.versioned_epoch_stakes = NULL; + + /* Populate the append vec index and write out the corresponding acc files. */ + + ulong incr_capitalization = 0UL; + fd_snapshot_create_populate_acc_vecs( snapshot_ctx, &manifest, snapshot_ctx->writer, &incr_capitalization ); + + /* Once the append vec index is populated and the hashes are calculated, + propogate the hashes to the correct fields. As a note, the last_snap_hash + is the full snapshot's account hash. */ + + if( snapshot_ctx->is_incremental ) { + manifest.bank_incremental_snapshot_persistence->full_slot = snapshot_ctx->last_snap_slot; + fd_memcpy( &manifest.bank_incremental_snapshot_persistence->full_hash, snapshot_ctx->last_snap_acc_hash, sizeof(fd_hash_t) ); + manifest.bank_incremental_snapshot_persistence->full_capitalization = snapshot_ctx->last_snap_capitalization; + manifest.bank_incremental_snapshot_persistence->incremental_hash = snapshot_ctx->acc_hash; + manifest.bank_incremental_snapshot_persistence->incremental_capitalization = incr_capitalization; + } else { + memcpy( out_hash, &manifest.accounts_db.bank_hash_info.accounts_hash, sizeof(fd_hash_t) ); + *out_capitalization = snapshot_ctx->slot_bank.capitalization; + } + + /* At this point, all of the account files are written out and the append + vec index is populated in the manifest. We have already reserved space + in the archive for the manifest. All we need to do now is encode the + manifest and write it in. */ + + ulong manifest_sz = fd_solana_manifest_serializable_size( &manifest ); + uchar * out_manifest = fd_valloc_malloc( snapshot_ctx->valloc, FD_SOLANA_MANIFEST_SERIALIZABLE_ALIGN, manifest_sz ); + + fd_bincode_encode_ctx_t encode = { + .data = out_manifest, + .dataend = out_manifest + manifest_sz + }; + + int err = fd_solana_manifest_serializable_encode( &manifest, &encode ); + if( FD_UNLIKELY( err ) ) { + FD_LOG_ERR(( "Failed to encode the manifest" )); + } + + err = fd_tar_writer_fill_space( snapshot_ctx->writer, out_manifest, manifest_sz ); + if( FD_UNLIKELY( err ) ) { + FD_LOG_ERR(( "Failed to write out the manifest" )); + } + + void * mem = fd_tar_writer_delete( snapshot_ctx->writer ); + if( FD_UNLIKELY( !mem ) ) { + FD_LOG_ERR(( "Unable to delete the tar writer" )); + } + + fd_bincode_destroy_ctx_t destroy = { + .valloc = snapshot_ctx->valloc + }; + + /* This is kind of a hack but we need to do this so we don't accidentally + corrupt memory when we try to double destory. Everything below is + things that aren't stack allocated from the manifest including the banks. */ + + fd_stakes_serializable_destroy( &manifest.bank.stakes, &destroy ); + fd_block_hash_vec_destroy( &manifest.bank.blockhash_queue, &destroy ); + fd_valloc_free( snapshot_ctx->valloc, manifest.bank.epoch_stakes ); + fd_epoch_bank_destroy( &snapshot_ctx->epoch_bank, &destroy ); + fd_slot_bank_destroy( &snapshot_ctx->slot_bank, &destroy ); + if( snapshot_ctx->is_incremental ) { + fd_valloc_free( snapshot_ctx->valloc, manifest.bank_incremental_snapshot_persistence ); + } + fd_valloc_free( snapshot_ctx->valloc, out_manifest ); + +} + +static inline void +fd_snapshot_create_compress( fd_snapshot_ctx_t * snapshot_ctx ) { + + /* Compress the file using zstd. First open the non-compressed file and + create a file for the compressed file. The reason why we can't do this + as we stream out the snapshot archive is that we write back into the + manifest buffer. + + TODO: A way to eliminate this and to just stream out + 1 compressed file would be to totally precompute the index such that + we don't have to write back into funk. + + TODO: Currently, the snapshot service interfaces directly with the zstd + library but a generalized cstream defined in fd_zstd should be used + instead. */ + + ulong in_buf_sz = ZSTD_CStreamInSize(); + ulong zstd_buf_sz = ZSTD_CStreamOutSize(); + ulong out_buf_sz = ZSTD_CStreamOutSize(); + + char * in_buf = fd_valloc_malloc( snapshot_ctx->valloc, FD_ZSTD_CSTREAM_ALIGN, in_buf_sz ); + char * zstd_buf = fd_valloc_malloc( snapshot_ctx->valloc, FD_ZSTD_CSTREAM_ALIGN, out_buf_sz ); + char * out_buf = fd_valloc_malloc( snapshot_ctx->valloc, FD_ZSTD_CSTREAM_ALIGN, out_buf_sz ); + + /* Reopen the tarball and open/overwrite the filename for the compressed, + finalized full snapshot. Setup the zstd compression stream. */ + + int err = 0; + + ZSTD_CStream * cstream = ZSTD_createCStream(); + if( FD_UNLIKELY( !cstream ) ) { + FD_LOG_ERR(( "Failed to create the zstd compression stream" )); + } + ZSTD_initCStream( cstream, ZSTD_CLEVEL_DEFAULT ); + + fd_io_buffered_ostream_t ostream[1]; + + if( FD_UNLIKELY( !fd_io_buffered_ostream_init( ostream, snapshot_ctx->snapshot_fd, out_buf, out_buf_sz ) ) ) { + FD_LOG_ERR(( "Failed to initialize the ostream" )); + } + + long seek = lseek( snapshot_ctx->snapshot_fd, 0, SEEK_SET ); + if( FD_UNLIKELY( seek!=0L ) ) { + FD_LOG_ERR(( "Failed to seek to the start of the file" )); + } + + /* At this point, the tar archive and the new zstd file is open. The zstd + streamer is still open. Now, we are ready to read in bytes and stream + compress them. We will keep going until we see an EOF in a tar archive. */ + + ulong in_sz = in_buf_sz; + + ulong off = (ulong)lseek( snapshot_ctx->tmp_fd, 0, SEEK_SET ); + if( FD_UNLIKELY( off ) ) { + FD_LOG_ERR(( "Failed to seek to the beginning of the file" )); + } + + while( in_sz==in_buf_sz ) { + + /* Read chunks from the file. There isn't really a need to use a streamed + reader here because we will read in the max size buffer for every single + file read except for the very last one. + + in_sz will only not equal in_buf_sz on the last read. */ + err = fd_io_read( snapshot_ctx->tmp_fd, in_buf, 0UL, in_buf_sz, &in_sz ); + if( FD_UNLIKELY( err ) ) { + FD_LOG_ERR(( "Failed to read in the file" )); + } + + /* Compress the in memory buffer and add it to the output stream. */ + + ZSTD_inBuffer input = { in_buf, in_sz, 0UL }; + while( input.pos0UL ) { + fd_io_buffered_ostream_write( ostream, zstd_buf, output.pos ); + } + + fd_valloc_free( snapshot_ctx->valloc, in_buf ); + fd_valloc_free( snapshot_ctx->valloc, zstd_buf ); + fd_valloc_free( snapshot_ctx->valloc, out_buf ); + + ZSTD_freeCStream( cstream ); /* Works even if cstream is null */ + err = fd_io_buffered_ostream_flush( ostream ); + if( FD_UNLIKELY( err ) ) { + FD_LOG_ERR(( "Failed to flush the ostream" )); + } + + /* Assuming that there was a successful write, make the compressed + snapshot file readable and servable. */ + + char tmp_directory_buf_zstd[ FD_SNAPSHOT_DIR_MAX ]; + err = snprintf( tmp_directory_buf_zstd, FD_SNAPSHOT_DIR_MAX, "%s/%s", snapshot_ctx->out_dir, snapshot_ctx->is_incremental ? FD_SNAPSHOT_TMP_INCR_ARCHIVE_ZSTD : FD_SNAPSHOT_TMP_FULL_ARCHIVE_ZSTD ); + if( FD_UNLIKELY( err<0 ) ) { + FD_LOG_ERR(( "Failed to format directory string" )); + } + + char directory_buf_zstd[ FD_SNAPSHOT_DIR_MAX ]; + if( !snapshot_ctx->is_incremental ) { + err = snprintf( directory_buf_zstd, FD_SNAPSHOT_DIR_MAX, "%s/snapshot-%lu-%s.tar.zst", + snapshot_ctx->out_dir, snapshot_ctx->slot, FD_BASE58_ENC_32_ALLOCA(&snapshot_ctx->snap_hash) ); + } else { + err = snprintf( directory_buf_zstd, FD_SNAPSHOT_DIR_MAX, "%s/incremental-snapshot-%lu-%lu-%s.tar.zst", + snapshot_ctx->out_dir, snapshot_ctx->last_snap_slot, snapshot_ctx->slot, FD_BASE58_ENC_32_ALLOCA(&snapshot_ctx->snap_hash) ); + } + + if( FD_UNLIKELY( err<0 ) ) { + FD_LOG_ERR(( "Failed to format directory string" )); + } + + err = rename( tmp_directory_buf_zstd, directory_buf_zstd ); + if( FD_UNLIKELY( err<0 ) ) { + FD_LOG_ERR(( "Failed to rename file from %s to %s (%i-%s)", tmp_directory_buf_zstd, directory_buf_zstd, errno, fd_io_strerror( errno ) )); + } + +} + +int +fd_snapshot_create_new_snapshot( fd_snapshot_ctx_t * snapshot_ctx, + fd_hash_t * out_hash, + ulong * out_capitalization ) { + + FD_SCRATCH_SCOPE_BEGIN { + + FD_LOG_NOTICE(( "Starting to produce a snapshot for slot=%lu in directory=%s", snapshot_ctx->slot, snapshot_ctx->out_dir )); + + /* Validate that the snapshot_ctx is setup correctly. */ + + fd_snapshot_create_setup_and_validate_ctx( snapshot_ctx ); + + /* Setup the tar archive writer. */ + + fd_snapshot_create_setup_writer( snapshot_ctx ); + + /* Write out the version file. */ + + fd_snapshot_create_write_version( snapshot_ctx ); + + /* Dump the status cache and append it to the tar archive. */ + + fd_snapshot_create_write_status_cache( snapshot_ctx ); + + /* Populate and write out the manifest and append vecs. */ + + fd_snapshot_create_write_manifest_and_acc_vecs( snapshot_ctx, out_hash, out_capitalization ); + + /* Compress the tar file and write it out to the specified directory. */ + + fd_snapshot_create_compress( snapshot_ctx ); + + } FD_SCRATCH_SCOPE_END; +} diff --git a/src/flamenco/snapshot/fd_snapshot_create.h b/src/flamenco/snapshot/fd_snapshot_create.h index 62cc106f8e..d8acd4a023 100644 --- a/src/flamenco/snapshot/fd_snapshot_create.h +++ b/src/flamenco/snapshot/fd_snapshot_create.h @@ -1,78 +1,141 @@ #ifndef HEADER_fd_src_flamenco_snapshot_fd_snapshot_create_h #define HEADER_fd_src_flamenco_snapshot_fd_snapshot_create_h -/* fd_snapshot_create.h provides APIs for creating a Labs-compatible +/* fd_snapshot_create.h provides APIs for creating a Agave-compatible snapshot from a slot execution context. */ -#include "../fd_flamenco_base.h" -#include "../runtime/context/fd_exec_slot_ctx.h" +#include "fd_snapshot_base.h" +#include "../runtime/fd_runtime_init.h" +#include "../runtime/fd_txncache.h" +#include "../../util/archive/fd_tar.h" +#include "../types/fd_types.h" -struct fd_snapshot_create_private; -typedef struct fd_snapshot_create_private fd_snapshot_create_t; +#include +#include +#include +#include + +#define FD_BLOCKHASH_QUEUE_SIZE (300UL) +#define FD_TICKS_PER_SLOT (64UL) + +#define FD_SNAPSHOT_DIR_MAX (256UL) +#define FD_SNAPSHOT_VERSION_FILE ("version") +#define FD_SNAPSHOT_VERSION ("1.2.0") +#define FD_SNAPSHOT_VERSION_LEN (5UL) +#define FD_SNAPSHOT_STATUS_CACHE_FILE ("snapshots/status_cache") + +#define FD_SNAPSHOT_TMP_ARCHIVE (".tmp.tar") +#define FD_SNAPSHOT_TMP_INCR_ARCHIVE (".tmp_inc.tar") +#define FD_SNAPSHOT_TMP_FULL_ARCHIVE_ZSTD (".tmp.tar.zst") +#define FD_SNAPSHOT_TMP_INCR_ARCHIVE_ZSTD (".tmp_inc.tar.zst") + +#define FD_SNAPSHOT_APPEND_VEC_SZ_MAX (16UL * 1024UL * 1024UL * 1024UL) /* 16 MiB */ FD_PROTOTYPES_BEGIN -/* fd_snapshot_create_{align,footprint} return required memory region - parameters for the fd_snapshot_create_t object. - - worker_cnt is the number of workers for parallel snapshot create - (treated as 1UL parallel mode not available). compress_lvl is the - Zstandard compression level. compress_bufsz is the in-memory buffer - for writes (larger buffers results in less frequent but larger write - ops). funk_rec_cnt is the number of slots in the funk rec hashmap. - batch_acc_cnt is the max number of accounts per account vec. - - Resulting footprint approximates - - O( funk_rec_cnt + (worker_cnt * (compress_lvl + compress_bufsz + batch_acc_cnt)) ) */ - -FD_FN_CONST ulong -fd_snapshot_create_align( void ); - -ulong -fd_snapshot_create_footprint( ulong worker_cnt, - int compress_lvl, - ulong compress_bufsz, - ulong funk_rec_cnt, - ulong batch_acc_cnt ); - -/* fd_snapshot_create_new creates a new snapshot create object in the - given mem region, which adheres to above alignment/footprint - requirements. Returns qualified handle to object given create object - on success. Serializes data from given slot context. snap_path is - the final snapshot path. May create temporary files adject to - snap_path. {worker_cnt,compress_lvl,compress_bufsz,funk_rec_cnt, - batch_acc_cnt} must match arguments to footprint when mem was - created. On failure, returns NULL. Reasons for failure include - invalid memory region or invalid file descriptor. Logs reasons for - failure. */ - -fd_snapshot_create_t * -fd_snapshot_create_new( void * mem, - fd_exec_slot_ctx_t * slot_ctx, - const char * snap_path, - ulong worker_cnt, - int compress_lvl, - ulong compress_bufsz, - ulong funk_rec_cnt, - ulong batch_acc_cnt, - ulong max_accv_sz, - fd_rng_t * rng ); - -/* fd_snapshot_create_delete destroys the given snapshot create object - and frees any resources. Returns memory region and fd back to caller. */ - -void * -fd_snapshot_create_delete( fd_snapshot_create_t * create ); - -/* fd_snapshot_create exports the 'snapshot manifest' and a copy of all - accounts from the slot ctx that the create object is attached to. - Writes a .tar.zst stream out to the fd. Returns 1 on success, and - 0 on failure. Reason for failure is logged. */ +/* fd_snapshot_ctx_t holds various data structures needed for snapshot + creation. It contains the snapshot slot, the snapshot directory, + whether the snapshot is incremental, the tarball writer, the allocator, + and holds the snapshot hash. + + FIXME: The snapshot service will currently not correctly free memory that is + allocated unless a bump allocator like fd_scratch or fd_spad are used. */ + +struct fd_snapshot_ctx { + + /* These parameters are setup by the caller of the snapshot service. */ + ulong slot; /* Slot for the snapshot. */ + char const * out_dir; /* Output directory. */ + fd_valloc_t valloc; /* Allocator */ + + /* The two data structures from the runtime referenced by the snapshot service. */ + fd_funk_t * funk; /* Funk handle. */ + fd_txncache_t * status_cache; /* Status cache handle. */ + + uchar is_incremental; /* If it is incremental, set the fields and pass in data from the previous full snapshot. */ + ulong last_snap_slot; /* Full snapshot slot. */ + ulong last_snap_capitalization; /* Full snapshot capitalization. */ + fd_hash_t * last_snap_acc_hash; /* Full snapshot account hash. */ + + fd_tpool_t * tpool; + + /* We need two files to represent the snapshot file because can not directly + stream out the compressed snapshot with the current implementation of the + snapshot service. This is because we write back into the tar archive. + So, we first write out a tar archive, then this is compressed into a + second file. The tmp_fd is the file used to write the tar archive and + the snapshot_fd is used to write out the compressed file. */ + int tmp_fd; + int snapshot_fd; + + /* This gets setup within the context and not by the user. */ + fd_tar_writer_t * writer; /* Tar writer. */ + fd_hash_t snap_hash; /* Snapshot hash. */ + fd_hash_t acc_hash; /* Account hash. */ + fd_slot_bank_t slot_bank; /* Obtained from funk. */ + fd_epoch_bank_t epoch_bank; /* Obtained from funk. */ + fd_acc_mgr_t * acc_mgr; /* Wrapper for funk. */ + +}; +typedef struct fd_snapshot_ctx fd_snapshot_ctx_t; + +/* fd_snapshot_create_populate_fseq, fd_snapshot_create_is_incremental, and + fd_snapshot_create_get_slot are helpers used to pack and unpack the fseq + used to indicate if a snapshot is ready to be created and if the snapshot + shoudl be incremental. The other bytes represent the slot that the snapshot + will be created for. The most significant 8 bits determine if the snapshot + is incremental and the least significant 56 bits are reserved for the slot. + + These functions are used for snapshot creation in the full client. */ + +static ulong FD_FN_UNUSED +fd_snapshot_create_pack_fseq( ulong is_incremental, ulong smr ) { + return (is_incremental << 56UL) | (smr & 0xFFFFFFFFFFFFFFUL); +} + +static ulong FD_FN_UNUSED +fd_snapshot_create_get_is_incremental( ulong fseq ) { + return (fseq >> 56UL) & 0xFF; +} + +static ulong FD_FN_UNUSED +fd_snapshot_create_get_slot( ulong fseq ) { + return fseq & 0xFFFFFFFFFFFFFFUL; +} + +/* fd_snapshot_create_new_snapshot is responsible for creating the different + structures used for snapshot generation and outputting them to a servable, + compressed tarball. The main components of a Solana snapshot are as follows: + + 1. Version - This is a file that contains the version of the snapshot. + 2. Manifest - The manifest contains data about the state of the network + as well as the index of the append vecs. + a. The bank. This is the equivalent of the firedancer slot/epoch context. + This contains almost all of the state of the network that is not + encapsulated in the accounts. + b. Append vec index. This is a list of all of the append vecs that are + used to store the accounts. This is a slot indexed file. + c. The manifest also contains other relevant metadata including the + account/snapshot hash. + 3. Status cache - the status cache holds the transaction statuses for the + last 300 rooted slots. This is a nested data structure which is indexed + by blockhash. See fd_txncache.h for more details on the status cache. + 4. Accounts directory - the accounts directory contains the state of all + of the accounts and is a set of files described by . These + are described by the append vec index in the manifest. + + The files are written out into a tar archive which is then zstd compressed. + + This can produce either a full snapshot or an incremental snapshot depending + on the value of is_incremental. An incremental snapshot will contain all of + the information described above, except it will only contain accounts that + have been modified or deleted since the creation of the last incremental + snapshot. */ int -fd_snapshot_create( fd_snapshot_create_t * create, - fd_exec_slot_ctx_t * slot_ctx ); +fd_snapshot_create_new_snapshot( fd_snapshot_ctx_t * snapshot_ctx, + fd_hash_t * out_hash, + ulong * out_capitalization ); FD_PROTOTYPES_END diff --git a/src/flamenco/snapshot/fd_snapshot_restore.c b/src/flamenco/snapshot/fd_snapshot_restore.c index 27151c80ed..45d4fcb0f7 100644 --- a/src/flamenco/snapshot/fd_snapshot_restore.c +++ b/src/flamenco/snapshot/fd_snapshot_restore.c @@ -270,6 +270,15 @@ fd_snapshot_restore_manifest( fd_snapshot_restore_t * restore ) { return EINVAL; } + if( manifest->bank_incremental_snapshot_persistence ) { + FD_LOG_NOTICE(( "Incremental snapshot has incremental snapshot persistence with full acc_hash=%s and incremental acc_hash=%s", + FD_BASE58_ENC_32_ALLOCA(&manifest->bank_incremental_snapshot_persistence->full_hash), + FD_BASE58_ENC_32_ALLOCA(&manifest->bank_incremental_snapshot_persistence->incremental_hash) )); + + } else { + FD_LOG_NOTICE(( "Full snapshot acc_hash=%s", FD_BASE58_ENC_32_ALLOCA(&manifest->accounts_db.bank_hash_info.accounts_hash) )); + } + /* Move over accounts DB fields */ fd_solana_accounts_db_fields_t accounts_db = manifest->accounts_db; diff --git a/src/flamenco/types/fd_type_names.c b/src/flamenco/types/fd_type_names.c index c58206d85f..3262cf10e8 100644 --- a/src/flamenco/types/fd_type_names.c +++ b/src/flamenco/types/fd_type_names.c @@ -1,5 +1,5 @@ // This is an auto-generated file. To add entries, edit fd_types.json -#define FD_TYPE_NAME_COUNT 232 +#define FD_TYPE_NAME_COUNT 237 static char const * fd_type_names[FD_TYPE_NAME_COUNT] = { "fd_hash", "fd_pubkey", @@ -23,6 +23,8 @@ static char const * fd_type_names[FD_TYPE_NAME_COUNT] = { "fd_stake_history", "fd_solana_account", "fd_vote_accounts_pair", + "fd_vote_accounts_pair_serializable", + "fd_vote_accounts_serializable", "fd_vote_accounts", "fd_stake_accounts_pair", "fd_stake_accounts", @@ -35,6 +37,7 @@ static char const * fd_type_names[FD_TYPE_NAME_COUNT] = { "fd_epoch_info", "fd_stake_pair", "fd_stakes", + "fd_stakes_serializable", "fd_stakes_stake", "fd_bank_incremental_snapshot_persistence", "fd_node_vote_accounts", @@ -45,6 +48,7 @@ static char const * fd_type_names[FD_TYPE_NAME_COUNT] = { "fd_pubkey_u64_pair", "fd_unused_accounts", "fd_deserializable_versioned_bank", + "fd_serializable_versioned_bank", "fd_bank_hash_stats", "fd_bank_hash_info", "fd_slot_map_pair", @@ -58,6 +62,7 @@ static char const * fd_type_names[FD_TYPE_NAME_COUNT] = { "fd_reward_info", "fd_slot_lthash", "fd_solana_manifest", + "fd_solana_manifest_serializable", "fd_rust_duration", "fd_poh_config", "fd_string_pubkey_pair", diff --git a/src/flamenco/types/fd_types.c b/src/flamenco/types/fd_types.c index f19eb7b3be..d49f58d145 100644 --- a/src/flamenco/types/fd_types.c +++ b/src/flamenco/types/fd_types.c @@ -1417,6 +1417,84 @@ int fd_slot_pair_encode( fd_slot_pair_t const * self, fd_bincode_encode_ctx_t * if( FD_UNLIKELY( err ) ) return err; return FD_BINCODE_SUCCESS; } +enum { + fd_slot_pair_slot_TAG = (0 << 6) | FD_ARCHIVE_META_ULONG, + fd_slot_pair_val_TAG = (1 << 6) | FD_ARCHIVE_META_ULONG, +}; +int fd_slot_pair_decode_archival( fd_slot_pair_t * self, fd_bincode_decode_ctx_t * ctx ) { + void const * data = ctx->data; + int err = fd_slot_pair_decode_archival_preflight( ctx ); + if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; + ctx->data = data; + if( !fd_is_null_alloc_virtual( ctx->valloc ) ) { + fd_slot_pair_new( self ); + } + fd_slot_pair_decode_archival_unsafe( self, ctx ); + return FD_BINCODE_SUCCESS; +} +int fd_slot_pair_decode_archival_preflight( fd_bincode_decode_ctx_t * ctx ) { + int err; + ushort tag = FD_ARCHIVE_META_SENTINAL; + void * offset = NULL; + for(;;) { + err = fd_bincode_uint16_decode( &tag, ctx ); + if( FD_UNLIKELY( err ) ) return err; + if( FD_UNLIKELY( tag == FD_ARCHIVE_META_SENTINAL ) ) break; + switch( tag ) { + case (ushort)fd_slot_pair_slot_TAG: { + err = fd_bincode_uint64_decode_preflight( ctx ); + if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; + break; + } + case (ushort)fd_slot_pair_val_TAG: { + err = fd_bincode_uint64_decode_preflight( ctx ); + if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; + break; + } + default: + err = fd_archive_decode_skip_field( ctx, tag ); + if( FD_UNLIKELY( err ) ) return err; + break; + } + } + return FD_BINCODE_SUCCESS; +} +void fd_slot_pair_decode_archival_unsafe( fd_slot_pair_t * self, fd_bincode_decode_ctx_t * ctx ) { + ushort tag = FD_ARCHIVE_META_SENTINAL; + void * offset = NULL; + for(;;) { + fd_bincode_uint16_decode( &tag, ctx ); + if( FD_UNLIKELY( tag == FD_ARCHIVE_META_SENTINAL ) ) break; + switch( tag ) { + case (ushort)fd_slot_pair_slot_TAG: { + fd_bincode_uint64_decode_unsafe( &self->slot, ctx ); + break; + } + case (ushort)fd_slot_pair_val_TAG: { + fd_bincode_uint64_decode_unsafe( &self->val, ctx ); + break; + } + default: + fd_archive_decode_skip_field( ctx, tag ); + break; + } + } +} +int fd_slot_pair_encode_archival( fd_slot_pair_t const * self, fd_bincode_encode_ctx_t * ctx ) { + int err; + void * offset = NULL; + err = fd_bincode_uint16_encode( (ushort)fd_slot_pair_slot_TAG, ctx ); + if( FD_UNLIKELY( err ) ) return err; + err = fd_bincode_uint64_encode( self->slot, ctx ); + if( FD_UNLIKELY( err ) ) return err; + err = fd_bincode_uint16_encode( (ushort)fd_slot_pair_val_TAG, ctx ); + if( FD_UNLIKELY( err ) ) return err; + err = fd_bincode_uint64_encode( self->val, ctx ); + if( FD_UNLIKELY( err ) ) return err; + err = fd_bincode_uint16_encode( FD_ARCHIVE_META_SENTINAL, ctx ); + if( FD_UNLIKELY( err ) ) return err; + return FD_BINCODE_SUCCESS; +} int fd_slot_pair_decode_offsets( fd_slot_pair_off_t * self, fd_bincode_decode_ctx_t * ctx ) { uchar const * data = ctx->data; int err; @@ -1497,6 +1575,100 @@ int fd_hard_forks_encode( fd_hard_forks_t const * self, fd_bincode_encode_ctx_t } return FD_BINCODE_SUCCESS; } +enum { + fd_hard_forks_hard_forks_TAG = (0 << 6) | FD_ARCHIVE_META_VECTOR, +}; +int fd_hard_forks_decode_archival( fd_hard_forks_t * self, fd_bincode_decode_ctx_t * ctx ) { + void const * data = ctx->data; + int err = fd_hard_forks_decode_archival_preflight( ctx ); + if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; + ctx->data = data; + if( !fd_is_null_alloc_virtual( ctx->valloc ) ) { + fd_hard_forks_new( self ); + } + fd_hard_forks_decode_archival_unsafe( self, ctx ); + return FD_BINCODE_SUCCESS; +} +int fd_hard_forks_decode_archival_preflight( fd_bincode_decode_ctx_t * ctx ) { + int err; + ushort tag = FD_ARCHIVE_META_SENTINAL; + void * offset = NULL; + for(;;) { + err = fd_bincode_uint16_decode( &tag, ctx ); + if( FD_UNLIKELY( err ) ) return err; + if( FD_UNLIKELY( tag == FD_ARCHIVE_META_SENTINAL ) ) break; + switch( tag ) { + case (ushort)fd_hard_forks_hard_forks_TAG: { + err = fd_archive_decode_setup_length( ctx, &offset ); + if( FD_UNLIKELY( err ) ) return err; + ulong hard_forks_len; + err = fd_bincode_uint64_decode( &hard_forks_len, ctx ); + if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; + if( hard_forks_len ) { + for( ulong i=0; i < hard_forks_len; i++ ) { + err = fd_slot_pair_decode_archival_preflight( ctx ); + if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; + } + } + err = fd_archive_decode_check_length( ctx, offset ); + if( FD_UNLIKELY( err ) ) return err; + break; + } + default: + err = fd_archive_decode_skip_field( ctx, tag ); + if( FD_UNLIKELY( err ) ) return err; + break; + } + } + return FD_BINCODE_SUCCESS; +} +void fd_hard_forks_decode_archival_unsafe( fd_hard_forks_t * self, fd_bincode_decode_ctx_t * ctx ) { + ushort tag = FD_ARCHIVE_META_SENTINAL; + void * offset = NULL; + for(;;) { + fd_bincode_uint16_decode( &tag, ctx ); + if( FD_UNLIKELY( tag == FD_ARCHIVE_META_SENTINAL ) ) break; + switch( tag ) { + case (ushort)fd_hard_forks_hard_forks_TAG: { + fd_archive_decode_setup_length( ctx, &offset ); + fd_bincode_uint64_decode_unsafe( &self->hard_forks_len, ctx ); + if( self->hard_forks_len ) { + self->hard_forks = (fd_slot_pair_t *)fd_valloc_malloc( ctx->valloc, FD_SLOT_PAIR_ALIGN, FD_SLOT_PAIR_FOOTPRINT*self->hard_forks_len ); + for( ulong i=0; i < self->hard_forks_len; i++ ) { + fd_slot_pair_new( self->hard_forks + i ); + fd_slot_pair_decode_archival_unsafe( self->hard_forks + i, ctx ); + } + } else + self->hard_forks = NULL; + break; + } + default: + fd_archive_decode_skip_field( ctx, tag ); + break; + } + } +} +int fd_hard_forks_encode_archival( fd_hard_forks_t const * self, fd_bincode_encode_ctx_t * ctx ) { + int err; + void * offset = NULL; + err = fd_bincode_uint16_encode( (ushort)fd_hard_forks_hard_forks_TAG, ctx ); + if( FD_UNLIKELY( err ) ) return err; + err = fd_archive_encode_setup_length( ctx, &offset ); + if( FD_UNLIKELY( err ) ) return err; + err = fd_bincode_uint64_encode( self->hard_forks_len, ctx ); + if( FD_UNLIKELY(err) ) return err; + if( self->hard_forks_len ) { + for( ulong i=0; i < self->hard_forks_len; i++ ) { + err = fd_slot_pair_encode_archival( self->hard_forks + i, ctx ); + if( FD_UNLIKELY( err ) ) return err; + } + } + err = fd_archive_encode_set_length( ctx, offset ); + if( FD_UNLIKELY( err ) ) return err; + err = fd_bincode_uint16_encode( FD_ARCHIVE_META_SENTINAL, ctx ); + if( FD_UNLIKELY( err ) ) return err; + return FD_BINCODE_SUCCESS; +} int fd_hard_forks_decode_offsets( fd_hard_forks_off_t * self, fd_bincode_decode_ctx_t * ctx ) { uchar const * data = ctx->data; int err; @@ -2935,50 +3107,128 @@ ulong fd_vote_accounts_pair_size( fd_vote_accounts_pair_t const * self ) { return size; } -int fd_vote_accounts_decode( fd_vote_accounts_t * self, fd_bincode_decode_ctx_t * ctx ) { +int fd_vote_accounts_pair_serializable_decode( fd_vote_accounts_pair_serializable_t * self, fd_bincode_decode_ctx_t * ctx ) { void const * data = ctx->data; - int err = fd_vote_accounts_decode_preflight( ctx ); + int err = fd_vote_accounts_pair_serializable_decode_preflight( ctx ); if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; ctx->data = data; if( !fd_is_null_alloc_virtual( ctx->valloc ) ) { - fd_vote_accounts_new( self ); + fd_vote_accounts_pair_serializable_new( self ); } - fd_vote_accounts_decode_unsafe( self, ctx ); + fd_vote_accounts_pair_serializable_decode_unsafe( self, ctx ); return FD_BINCODE_SUCCESS; } -int fd_vote_accounts_decode_preflight( fd_bincode_decode_ctx_t * ctx ) { +int fd_vote_accounts_pair_serializable_decode_preflight( fd_bincode_decode_ctx_t * ctx ) { + int err; + err = fd_bincode_bytes_decode_preflight( 32, ctx ); + if( FD_UNLIKELY( err ) ) return err; + err = fd_bincode_uint64_decode_preflight( ctx ); + if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; + err = fd_solana_account_decode_preflight( ctx ); + if( FD_UNLIKELY( err ) ) return err; + return FD_BINCODE_SUCCESS; +} +void fd_vote_accounts_pair_serializable_decode_unsafe( fd_vote_accounts_pair_serializable_t * self, fd_bincode_decode_ctx_t * ctx ) { + fd_pubkey_decode_unsafe( &self->key, ctx ); + fd_bincode_uint64_decode_unsafe( &self->stake, ctx ); + fd_solana_account_decode_unsafe( &self->value, ctx ); +} +int fd_vote_accounts_pair_serializable_encode( fd_vote_accounts_pair_serializable_t const * self, fd_bincode_encode_ctx_t * ctx ) { + int err; + err = fd_pubkey_encode( &self->key, ctx ); + if( FD_UNLIKELY( err ) ) return err; + err = fd_bincode_uint64_encode( self->stake, ctx ); + if( FD_UNLIKELY( err ) ) return err; + err = fd_solana_account_encode( &self->value, ctx ); + if( FD_UNLIKELY( err ) ) return err; + return FD_BINCODE_SUCCESS; +} +int fd_vote_accounts_pair_serializable_decode_offsets( fd_vote_accounts_pair_serializable_off_t * self, fd_bincode_decode_ctx_t * ctx ) { + uchar const * data = ctx->data; + int err; + self->key_off = (uint)( (ulong)ctx->data - (ulong)data ); + err = fd_bincode_bytes_decode_preflight( 32, ctx ); + if( FD_UNLIKELY( err ) ) return err; + self->stake_off = (uint)( (ulong)ctx->data - (ulong)data ); + err = fd_bincode_uint64_decode_preflight( ctx ); + if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; + self->value_off = (uint)( (ulong)ctx->data - (ulong)data ); + err = fd_solana_account_decode_preflight( ctx ); + if( FD_UNLIKELY( err ) ) return err; + return FD_BINCODE_SUCCESS; +} +void fd_vote_accounts_pair_serializable_new(fd_vote_accounts_pair_serializable_t * self) { + fd_memset( self, 0, sizeof(fd_vote_accounts_pair_serializable_t) ); + fd_pubkey_new( &self->key ); + fd_solana_account_new( &self->value ); +} +void fd_vote_accounts_pair_serializable_destroy( fd_vote_accounts_pair_serializable_t * self, fd_bincode_destroy_ctx_t * ctx ) { + fd_pubkey_destroy( &self->key, ctx ); + fd_solana_account_destroy( &self->value, ctx ); +} + +ulong fd_vote_accounts_pair_serializable_footprint( void ){ return FD_VOTE_ACCOUNTS_PAIR_SERIALIZABLE_FOOTPRINT; } +ulong fd_vote_accounts_pair_serializable_align( void ){ return FD_VOTE_ACCOUNTS_PAIR_SERIALIZABLE_ALIGN; } + +void fd_vote_accounts_pair_serializable_walk( void * w, fd_vote_accounts_pair_serializable_t const * self, fd_types_walk_fn_t fun, const char *name, uint level ) { + fun( w, self, name, FD_FLAMENCO_TYPE_MAP, "fd_vote_accounts_pair_serializable", level++ ); + fd_pubkey_walk( w, &self->key, fun, "key", level ); + fun( w, &self->stake, "stake", FD_FLAMENCO_TYPE_ULONG, "ulong", level ); + fd_solana_account_walk( w, &self->value, fun, "value", level ); + fun( w, self, name, FD_FLAMENCO_TYPE_MAP_END, "fd_vote_accounts_pair_serializable", level-- ); +} +ulong fd_vote_accounts_pair_serializable_size( fd_vote_accounts_pair_serializable_t const * self ) { + ulong size = 0; + size += fd_pubkey_size( &self->key ); + size += sizeof(ulong); + size += fd_solana_account_size( &self->value ); + return size; +} + +int fd_vote_accounts_serializable_decode( fd_vote_accounts_serializable_t * self, fd_bincode_decode_ctx_t * ctx ) { + void const * data = ctx->data; + int err = fd_vote_accounts_serializable_decode_preflight( ctx ); + if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; + ctx->data = data; + if( !fd_is_null_alloc_virtual( ctx->valloc ) ) { + fd_vote_accounts_serializable_new( self ); + } + fd_vote_accounts_serializable_decode_unsafe( self, ctx ); + return FD_BINCODE_SUCCESS; +} +int fd_vote_accounts_serializable_decode_preflight( fd_bincode_decode_ctx_t * ctx ) { int err; ulong vote_accounts_len; err = fd_bincode_uint64_decode( &vote_accounts_len, ctx ); if( FD_UNLIKELY( err ) ) return err; for( ulong i=0; i < vote_accounts_len; i++ ) { - err = fd_vote_accounts_pair_decode_preflight( ctx ); + err = fd_vote_accounts_pair_serializable_decode_preflight( ctx ); if( FD_UNLIKELY( err ) ) return err; } return FD_BINCODE_SUCCESS; } -void fd_vote_accounts_decode_unsafe( fd_vote_accounts_t * self, fd_bincode_decode_ctx_t * ctx ) { +void fd_vote_accounts_serializable_decode_unsafe( fd_vote_accounts_serializable_t * self, fd_bincode_decode_ctx_t * ctx ) { ulong vote_accounts_len; fd_bincode_uint64_decode_unsafe( &vote_accounts_len, ctx ); if( !fd_is_null_alloc_virtual( ctx->valloc ) ) { - self->vote_accounts_pool = fd_vote_accounts_pair_t_map_alloc( ctx->valloc, fd_ulong_max(vote_accounts_len, 15000 ) ); + self->vote_accounts_pool = fd_vote_accounts_pair_serializable_t_map_alloc( ctx->valloc, fd_ulong_max(vote_accounts_len, 15000 ) ); self->vote_accounts_root = NULL; } for( ulong i=0; i < vote_accounts_len; i++ ) { - fd_vote_accounts_pair_t_mapnode_t * node = fd_vote_accounts_pair_t_map_acquire( self->vote_accounts_pool ); - fd_vote_accounts_pair_new( &node->elem ); - fd_vote_accounts_pair_decode_unsafe( &node->elem, ctx ); - fd_vote_accounts_pair_t_map_insert( self->vote_accounts_pool, &self->vote_accounts_root, node ); + fd_vote_accounts_pair_serializable_t_mapnode_t * node = fd_vote_accounts_pair_serializable_t_map_acquire( self->vote_accounts_pool ); + fd_vote_accounts_pair_serializable_new( &node->elem ); + fd_vote_accounts_pair_serializable_decode_unsafe( &node->elem, ctx ); + fd_vote_accounts_pair_serializable_t_map_insert( self->vote_accounts_pool, &self->vote_accounts_root, node ); } } -int fd_vote_accounts_encode( fd_vote_accounts_t const * self, fd_bincode_encode_ctx_t * ctx ) { +int fd_vote_accounts_serializable_encode( fd_vote_accounts_serializable_t const * self, fd_bincode_encode_ctx_t * ctx ) { int err; if( self->vote_accounts_root ) { - ulong vote_accounts_len = fd_vote_accounts_pair_t_map_size( self->vote_accounts_pool, self->vote_accounts_root ); + ulong vote_accounts_len = fd_vote_accounts_pair_serializable_t_map_size( self->vote_accounts_pool, self->vote_accounts_root ); err = fd_bincode_uint64_encode( vote_accounts_len, ctx ); if( FD_UNLIKELY( err ) ) return err; - for( fd_vote_accounts_pair_t_mapnode_t * n = fd_vote_accounts_pair_t_map_minimum( self->vote_accounts_pool, self->vote_accounts_root ); n; n = fd_vote_accounts_pair_t_map_successor( self->vote_accounts_pool, n ) ) { - err = fd_vote_accounts_pair_encode( &n->elem, ctx ); + for( fd_vote_accounts_pair_serializable_t_mapnode_t * n = fd_vote_accounts_pair_serializable_t_map_minimum( self->vote_accounts_pool, self->vote_accounts_root ); n; n = fd_vote_accounts_pair_serializable_t_map_successor( self->vote_accounts_pool, n ) ) { + err = fd_vote_accounts_pair_serializable_encode( &n->elem, ctx ); if( FD_UNLIKELY( err ) ) return err; } } else { @@ -2988,53 +3238,156 @@ int fd_vote_accounts_encode( fd_vote_accounts_t const * self, fd_bincode_encode_ } return FD_BINCODE_SUCCESS; } -enum { - fd_vote_accounts_vote_accounts_TAG = (0 << 6) | FD_ARCHIVE_META_MAP, -}; -int fd_vote_accounts_decode_archival( fd_vote_accounts_t * self, fd_bincode_decode_ctx_t * ctx ) { - void const * data = ctx->data; - int err = fd_vote_accounts_decode_archival_preflight( ctx ); - if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; - ctx->data = data; - if( !fd_is_null_alloc_virtual( ctx->valloc ) ) { - fd_vote_accounts_new( self ); - } - fd_vote_accounts_decode_archival_unsafe( self, ctx ); - return FD_BINCODE_SUCCESS; -} -int fd_vote_accounts_decode_archival_preflight( fd_bincode_decode_ctx_t * ctx ) { +int fd_vote_accounts_serializable_decode_offsets( fd_vote_accounts_serializable_off_t * self, fd_bincode_decode_ctx_t * ctx ) { + uchar const * data = ctx->data; int err; - ushort tag = FD_ARCHIVE_META_SENTINAL; - void * offset = NULL; - for(;;) { - err = fd_bincode_uint16_decode( &tag, ctx ); - if( FD_UNLIKELY( err ) ) return err; - if( FD_UNLIKELY( tag == FD_ARCHIVE_META_SENTINAL ) ) break; - switch( tag ) { - case (ushort)fd_vote_accounts_vote_accounts_TAG: { - err = fd_archive_decode_setup_length( ctx, &offset ); - if( FD_UNLIKELY( err ) ) return err; + self->vote_accounts_off = (uint)( (ulong)ctx->data - (ulong)data ); ulong vote_accounts_len; err = fd_bincode_uint64_decode( &vote_accounts_len, ctx ); if( FD_UNLIKELY( err ) ) return err; for( ulong i=0; i < vote_accounts_len; i++ ) { - err = fd_vote_accounts_pair_decode_archival_preflight( ctx ); - if( FD_UNLIKELY( err ) ) return err; - } - err = fd_archive_decode_check_length( ctx, offset ); - if( FD_UNLIKELY( err ) ) return err; - break; - } - default: - err = fd_archive_decode_skip_field( ctx, tag ); + err = fd_vote_accounts_pair_serializable_decode_preflight( ctx ); if( FD_UNLIKELY( err ) ) return err; - break; - } } return FD_BINCODE_SUCCESS; } -void fd_vote_accounts_decode_archival_unsafe( fd_vote_accounts_t * self, fd_bincode_decode_ctx_t * ctx ) { - ushort tag = FD_ARCHIVE_META_SENTINAL; +void fd_vote_accounts_serializable_new(fd_vote_accounts_serializable_t * self) { + fd_memset( self, 0, sizeof(fd_vote_accounts_serializable_t) ); +} +void fd_vote_accounts_serializable_destroy( fd_vote_accounts_serializable_t * self, fd_bincode_destroy_ctx_t * ctx ) { + for( fd_vote_accounts_pair_serializable_t_mapnode_t * n = fd_vote_accounts_pair_serializable_t_map_minimum(self->vote_accounts_pool, self->vote_accounts_root ); n; n = fd_vote_accounts_pair_serializable_t_map_successor(self->vote_accounts_pool, n) ) { + fd_vote_accounts_pair_serializable_destroy( &n->elem, ctx ); + } + fd_valloc_free( ctx->valloc, fd_vote_accounts_pair_serializable_t_map_delete( fd_vote_accounts_pair_serializable_t_map_leave( self->vote_accounts_pool ) ) ); + self->vote_accounts_pool = NULL; + self->vote_accounts_root = NULL; +} + +ulong fd_vote_accounts_serializable_footprint( void ){ return FD_VOTE_ACCOUNTS_SERIALIZABLE_FOOTPRINT; } +ulong fd_vote_accounts_serializable_align( void ){ return FD_VOTE_ACCOUNTS_SERIALIZABLE_ALIGN; } + +void fd_vote_accounts_serializable_walk( void * w, fd_vote_accounts_serializable_t const * self, fd_types_walk_fn_t fun, const char *name, uint level ) { + fun( w, self, name, FD_FLAMENCO_TYPE_MAP, "fd_vote_accounts_serializable", level++ ); + if( self->vote_accounts_root ) { + for( fd_vote_accounts_pair_serializable_t_mapnode_t * n = fd_vote_accounts_pair_serializable_t_map_minimum(self->vote_accounts_pool, self->vote_accounts_root ); n; n = fd_vote_accounts_pair_serializable_t_map_successor( self->vote_accounts_pool, n ) ) { + fd_vote_accounts_pair_serializable_walk(w, &n->elem, fun, "vote_accounts", level ); + } + } + fun( w, self, name, FD_FLAMENCO_TYPE_MAP_END, "fd_vote_accounts_serializable", level-- ); +} +ulong fd_vote_accounts_serializable_size( fd_vote_accounts_serializable_t const * self ) { + ulong size = 0; + if( self->vote_accounts_root ) { + size += sizeof(ulong); + for( fd_vote_accounts_pair_serializable_t_mapnode_t * n = fd_vote_accounts_pair_serializable_t_map_minimum( self->vote_accounts_pool, self->vote_accounts_root ); n; n = fd_vote_accounts_pair_serializable_t_map_successor( self->vote_accounts_pool, n ) ) { + size += fd_vote_accounts_pair_serializable_size( &n->elem ); + } + } else { + size += sizeof(ulong); + } + return size; +} + +int fd_vote_accounts_decode( fd_vote_accounts_t * self, fd_bincode_decode_ctx_t * ctx ) { + void const * data = ctx->data; + int err = fd_vote_accounts_decode_preflight( ctx ); + if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; + ctx->data = data; + if( !fd_is_null_alloc_virtual( ctx->valloc ) ) { + fd_vote_accounts_new( self ); + } + fd_vote_accounts_decode_unsafe( self, ctx ); + return FD_BINCODE_SUCCESS; +} +int fd_vote_accounts_decode_preflight( fd_bincode_decode_ctx_t * ctx ) { + int err; + ulong vote_accounts_len; + err = fd_bincode_uint64_decode( &vote_accounts_len, ctx ); + if( FD_UNLIKELY( err ) ) return err; + for( ulong i=0; i < vote_accounts_len; i++ ) { + err = fd_vote_accounts_pair_decode_preflight( ctx ); + if( FD_UNLIKELY( err ) ) return err; + } + return FD_BINCODE_SUCCESS; +} +void fd_vote_accounts_decode_unsafe( fd_vote_accounts_t * self, fd_bincode_decode_ctx_t * ctx ) { + ulong vote_accounts_len; + fd_bincode_uint64_decode_unsafe( &vote_accounts_len, ctx ); + if( !fd_is_null_alloc_virtual( ctx->valloc ) ) { + self->vote_accounts_pool = fd_vote_accounts_pair_t_map_alloc( ctx->valloc, fd_ulong_max(vote_accounts_len, 15000 ) ); + self->vote_accounts_root = NULL; + } + for( ulong i=0; i < vote_accounts_len; i++ ) { + fd_vote_accounts_pair_t_mapnode_t * node = fd_vote_accounts_pair_t_map_acquire( self->vote_accounts_pool ); + fd_vote_accounts_pair_new( &node->elem ); + fd_vote_accounts_pair_decode_unsafe( &node->elem, ctx ); + fd_vote_accounts_pair_t_map_insert( self->vote_accounts_pool, &self->vote_accounts_root, node ); + } +} +int fd_vote_accounts_encode( fd_vote_accounts_t const * self, fd_bincode_encode_ctx_t * ctx ) { + int err; + if( self->vote_accounts_root ) { + ulong vote_accounts_len = fd_vote_accounts_pair_t_map_size( self->vote_accounts_pool, self->vote_accounts_root ); + err = fd_bincode_uint64_encode( vote_accounts_len, ctx ); + if( FD_UNLIKELY( err ) ) return err; + for( fd_vote_accounts_pair_t_mapnode_t * n = fd_vote_accounts_pair_t_map_minimum( self->vote_accounts_pool, self->vote_accounts_root ); n; n = fd_vote_accounts_pair_t_map_successor( self->vote_accounts_pool, n ) ) { + err = fd_vote_accounts_pair_encode( &n->elem, ctx ); + if( FD_UNLIKELY( err ) ) return err; + } + } else { + ulong vote_accounts_len = 0; + err = fd_bincode_uint64_encode( vote_accounts_len, ctx ); + if( FD_UNLIKELY( err ) ) return err; + } + return FD_BINCODE_SUCCESS; +} +enum { + fd_vote_accounts_vote_accounts_TAG = (0 << 6) | FD_ARCHIVE_META_MAP, +}; +int fd_vote_accounts_decode_archival( fd_vote_accounts_t * self, fd_bincode_decode_ctx_t * ctx ) { + void const * data = ctx->data; + int err = fd_vote_accounts_decode_archival_preflight( ctx ); + if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; + ctx->data = data; + if( !fd_is_null_alloc_virtual( ctx->valloc ) ) { + fd_vote_accounts_new( self ); + } + fd_vote_accounts_decode_archival_unsafe( self, ctx ); + return FD_BINCODE_SUCCESS; +} +int fd_vote_accounts_decode_archival_preflight( fd_bincode_decode_ctx_t * ctx ) { + int err; + ushort tag = FD_ARCHIVE_META_SENTINAL; + void * offset = NULL; + for(;;) { + err = fd_bincode_uint16_decode( &tag, ctx ); + if( FD_UNLIKELY( err ) ) return err; + if( FD_UNLIKELY( tag == FD_ARCHIVE_META_SENTINAL ) ) break; + switch( tag ) { + case (ushort)fd_vote_accounts_vote_accounts_TAG: { + err = fd_archive_decode_setup_length( ctx, &offset ); + if( FD_UNLIKELY( err ) ) return err; + ulong vote_accounts_len; + err = fd_bincode_uint64_decode( &vote_accounts_len, ctx ); + if( FD_UNLIKELY( err ) ) return err; + for( ulong i=0; i < vote_accounts_len; i++ ) { + err = fd_vote_accounts_pair_decode_archival_preflight( ctx ); + if( FD_UNLIKELY( err ) ) return err; + } + err = fd_archive_decode_check_length( ctx, offset ); + if( FD_UNLIKELY( err ) ) return err; + break; + } + default: + err = fd_archive_decode_skip_field( ctx, tag ); + if( FD_UNLIKELY( err ) ) return err; + break; + } + } + return FD_BINCODE_SUCCESS; +} +void fd_vote_accounts_decode_archival_unsafe( fd_vote_accounts_t * self, fd_bincode_decode_ctx_t * ctx ) { + ushort tag = FD_ARCHIVE_META_SENTINAL; void * offset = NULL; for(;;) { fd_bincode_uint16_decode( &tag, ctx ); @@ -4672,6 +5025,153 @@ ulong fd_stakes_size( fd_stakes_t const * self ) { return size; } +int fd_stakes_serializable_decode( fd_stakes_serializable_t * self, fd_bincode_decode_ctx_t * ctx ) { + void const * data = ctx->data; + int err = fd_stakes_serializable_decode_preflight( ctx ); + if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; + ctx->data = data; + if( !fd_is_null_alloc_virtual( ctx->valloc ) ) { + fd_stakes_serializable_new( self ); + } + fd_stakes_serializable_decode_unsafe( self, ctx ); + return FD_BINCODE_SUCCESS; +} +int fd_stakes_serializable_decode_preflight( fd_bincode_decode_ctx_t * ctx ) { + int err; + err = fd_vote_accounts_serializable_decode_preflight( ctx ); + if( FD_UNLIKELY( err ) ) return err; + ulong stake_delegations_len; + err = fd_bincode_uint64_decode( &stake_delegations_len, ctx ); + if( FD_UNLIKELY( err ) ) return err; + for( ulong i=0; i < stake_delegations_len; i++ ) { + err = fd_delegation_pair_decode_preflight( ctx ); + if( FD_UNLIKELY( err ) ) return err; + } + err = fd_bincode_uint64_decode_preflight( ctx ); + if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; + err = fd_bincode_uint64_decode_preflight( ctx ); + if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; + err = fd_stake_history_decode_preflight( ctx ); + if( FD_UNLIKELY( err ) ) return err; + return FD_BINCODE_SUCCESS; +} +void fd_stakes_serializable_decode_unsafe( fd_stakes_serializable_t * self, fd_bincode_decode_ctx_t * ctx ) { + fd_vote_accounts_serializable_decode_unsafe( &self->vote_accounts, ctx ); + ulong stake_delegations_len; + fd_bincode_uint64_decode_unsafe( &stake_delegations_len, ctx ); + if( !fd_is_null_alloc_virtual( ctx->valloc ) ) { + self->stake_delegations_pool = fd_delegation_pair_t_map_alloc( ctx->valloc, stake_delegations_len ); + self->stake_delegations_root = NULL; + } + for( ulong i=0; i < stake_delegations_len; i++ ) { + fd_delegation_pair_t_mapnode_t * node = fd_delegation_pair_t_map_acquire( self->stake_delegations_pool ); + fd_delegation_pair_new( &node->elem ); + fd_delegation_pair_decode_unsafe( &node->elem, ctx ); + fd_delegation_pair_t_map_insert( self->stake_delegations_pool, &self->stake_delegations_root, node ); + } + fd_bincode_uint64_decode_unsafe( &self->unused, ctx ); + fd_bincode_uint64_decode_unsafe( &self->epoch, ctx ); + fd_stake_history_decode_unsafe( &self->stake_history, ctx ); +} +int fd_stakes_serializable_encode( fd_stakes_serializable_t const * self, fd_bincode_encode_ctx_t * ctx ) { + int err; + err = fd_vote_accounts_serializable_encode( &self->vote_accounts, ctx ); + if( FD_UNLIKELY( err ) ) return err; + if( self->stake_delegations_root ) { + ulong stake_delegations_len = fd_delegation_pair_t_map_size( self->stake_delegations_pool, self->stake_delegations_root ); + err = fd_bincode_uint64_encode( stake_delegations_len, ctx ); + if( FD_UNLIKELY( err ) ) return err; + for( fd_delegation_pair_t_mapnode_t * n = fd_delegation_pair_t_map_minimum( self->stake_delegations_pool, self->stake_delegations_root ); n; n = fd_delegation_pair_t_map_successor( self->stake_delegations_pool, n ) ) { + err = fd_delegation_pair_encode( &n->elem, ctx ); + if( FD_UNLIKELY( err ) ) return err; + } + } else { + ulong stake_delegations_len = 0; + err = fd_bincode_uint64_encode( stake_delegations_len, ctx ); + if( FD_UNLIKELY( err ) ) return err; + } + err = fd_bincode_uint64_encode( self->unused, ctx ); + if( FD_UNLIKELY( err ) ) return err; + err = fd_bincode_uint64_encode( self->epoch, ctx ); + if( FD_UNLIKELY( err ) ) return err; + err = fd_stake_history_encode( &self->stake_history, ctx ); + if( FD_UNLIKELY( err ) ) return err; + return FD_BINCODE_SUCCESS; +} +int fd_stakes_serializable_decode_offsets( fd_stakes_serializable_off_t * self, fd_bincode_decode_ctx_t * ctx ) { + uchar const * data = ctx->data; + int err; + self->vote_accounts_off = (uint)( (ulong)ctx->data - (ulong)data ); + err = fd_vote_accounts_serializable_decode_preflight( ctx ); + if( FD_UNLIKELY( err ) ) return err; + self->stake_delegations_off = (uint)( (ulong)ctx->data - (ulong)data ); + ulong stake_delegations_len; + err = fd_bincode_uint64_decode( &stake_delegations_len, ctx ); + if( FD_UNLIKELY( err ) ) return err; + for( ulong i=0; i < stake_delegations_len; i++ ) { + err = fd_delegation_pair_decode_preflight( ctx ); + if( FD_UNLIKELY( err ) ) return err; + } + self->unused_off = (uint)( (ulong)ctx->data - (ulong)data ); + err = fd_bincode_uint64_decode_preflight( ctx ); + if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; + self->epoch_off = (uint)( (ulong)ctx->data - (ulong)data ); + err = fd_bincode_uint64_decode_preflight( ctx ); + if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; + self->stake_history_off = (uint)( (ulong)ctx->data - (ulong)data ); + err = fd_stake_history_decode_preflight( ctx ); + if( FD_UNLIKELY( err ) ) return err; + return FD_BINCODE_SUCCESS; +} +void fd_stakes_serializable_new(fd_stakes_serializable_t * self) { + fd_memset( self, 0, sizeof(fd_stakes_serializable_t) ); + fd_vote_accounts_serializable_new( &self->vote_accounts ); + fd_stake_history_new( &self->stake_history ); +} +void fd_stakes_serializable_destroy( fd_stakes_serializable_t * self, fd_bincode_destroy_ctx_t * ctx ) { + fd_vote_accounts_serializable_destroy( &self->vote_accounts, ctx ); + for( fd_delegation_pair_t_mapnode_t * n = fd_delegation_pair_t_map_minimum(self->stake_delegations_pool, self->stake_delegations_root ); n; n = fd_delegation_pair_t_map_successor(self->stake_delegations_pool, n) ) { + fd_delegation_pair_destroy( &n->elem, ctx ); + } + fd_valloc_free( ctx->valloc, fd_delegation_pair_t_map_delete( fd_delegation_pair_t_map_leave( self->stake_delegations_pool ) ) ); + self->stake_delegations_pool = NULL; + self->stake_delegations_root = NULL; + fd_stake_history_destroy( &self->stake_history, ctx ); +} + +ulong fd_stakes_serializable_footprint( void ){ return FD_STAKES_SERIALIZABLE_FOOTPRINT; } +ulong fd_stakes_serializable_align( void ){ return FD_STAKES_SERIALIZABLE_ALIGN; } + +void fd_stakes_serializable_walk( void * w, fd_stakes_serializable_t const * self, fd_types_walk_fn_t fun, const char *name, uint level ) { + fun( w, self, name, FD_FLAMENCO_TYPE_MAP, "fd_stakes_serializable", level++ ); + fd_vote_accounts_serializable_walk( w, &self->vote_accounts, fun, "vote_accounts", level ); + if( self->stake_delegations_root ) { + for( fd_delegation_pair_t_mapnode_t * n = fd_delegation_pair_t_map_minimum(self->stake_delegations_pool, self->stake_delegations_root ); n; n = fd_delegation_pair_t_map_successor( self->stake_delegations_pool, n ) ) { + fd_delegation_pair_walk(w, &n->elem, fun, "stake_delegations", level ); + } + } + fun( w, &self->unused, "unused", FD_FLAMENCO_TYPE_ULONG, "ulong", level ); + fun( w, &self->epoch, "epoch", FD_FLAMENCO_TYPE_ULONG, "ulong", level ); + fd_stake_history_walk( w, &self->stake_history, fun, "stake_history", level ); + fun( w, self, name, FD_FLAMENCO_TYPE_MAP_END, "fd_stakes_serializable", level-- ); +} +ulong fd_stakes_serializable_size( fd_stakes_serializable_t const * self ) { + ulong size = 0; + size += fd_vote_accounts_serializable_size( &self->vote_accounts ); + if( self->stake_delegations_root ) { + size += sizeof(ulong); + for( fd_delegation_pair_t_mapnode_t * n = fd_delegation_pair_t_map_minimum( self->stake_delegations_pool, self->stake_delegations_root ); n; n = fd_delegation_pair_t_map_successor( self->stake_delegations_pool, n ) ) { + size += fd_delegation_pair_size( &n->elem ); + } + } else { + size += sizeof(ulong); + } + size += sizeof(ulong); + size += sizeof(ulong); + size += fd_stake_history_size( &self->stake_history ); + return size; +} + int fd_stakes_stake_decode( fd_stakes_stake_t * self, fd_bincode_decode_ctx_t * ctx ) { void const * data = ctx->data; int err = fd_stakes_stake_decode_preflight( ctx ); @@ -6181,213 +6681,727 @@ ulong fd_deserializable_versioned_bank_size( fd_deserializable_versioned_bank_t return size; } -int fd_bank_hash_stats_decode( fd_bank_hash_stats_t * self, fd_bincode_decode_ctx_t * ctx ) { +int fd_serializable_versioned_bank_decode( fd_serializable_versioned_bank_t * self, fd_bincode_decode_ctx_t * ctx ) { void const * data = ctx->data; - int err = fd_bank_hash_stats_decode_preflight( ctx ); + int err = fd_serializable_versioned_bank_decode_preflight( ctx ); if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; ctx->data = data; if( !fd_is_null_alloc_virtual( ctx->valloc ) ) { - fd_bank_hash_stats_new( self ); + fd_serializable_versioned_bank_new( self ); } - fd_bank_hash_stats_decode_unsafe( self, ctx ); + fd_serializable_versioned_bank_decode_unsafe( self, ctx ); return FD_BINCODE_SUCCESS; } -int fd_bank_hash_stats_decode_preflight( fd_bincode_decode_ctx_t * ctx ) { +int fd_serializable_versioned_bank_decode_preflight( fd_bincode_decode_ctx_t * ctx ) { int err; - err = fd_bincode_uint64_decode_preflight( ctx ); - if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; - err = fd_bincode_uint64_decode_preflight( ctx ); - if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; - err = fd_bincode_uint64_decode_preflight( ctx ); + err = fd_block_hash_vec_decode_preflight( ctx ); + if( FD_UNLIKELY( err ) ) return err; + ulong ancestors_len; + err = fd_bincode_uint64_decode( &ancestors_len, ctx ); if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; + if( ancestors_len ) { + for( ulong i=0; i < ancestors_len; i++ ) { + err = fd_slot_pair_decode_preflight( ctx ); + if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; + } + } + err = fd_hash_decode_preflight( ctx ); + if( FD_UNLIKELY( err ) ) return err; + err = fd_hash_decode_preflight( ctx ); + if( FD_UNLIKELY( err ) ) return err; err = fd_bincode_uint64_decode_preflight( ctx ); if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; + err = fd_hard_forks_decode_preflight( ctx ); + if( FD_UNLIKELY( err ) ) return err; err = fd_bincode_uint64_decode_preflight( ctx ); if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; - return FD_BINCODE_SUCCESS; -} -void fd_bank_hash_stats_decode_unsafe( fd_bank_hash_stats_t * self, fd_bincode_decode_ctx_t * ctx ) { - fd_bincode_uint64_decode_unsafe( &self->num_updated_accounts, ctx ); - fd_bincode_uint64_decode_unsafe( &self->num_removed_accounts, ctx ); - fd_bincode_uint64_decode_unsafe( &self->num_lamports_stored, ctx ); - fd_bincode_uint64_decode_unsafe( &self->total_data_len, ctx ); - fd_bincode_uint64_decode_unsafe( &self->num_executable_accounts, ctx ); -} -int fd_bank_hash_stats_encode( fd_bank_hash_stats_t const * self, fd_bincode_encode_ctx_t * ctx ) { - int err; - err = fd_bincode_uint64_encode( self->num_updated_accounts, ctx ); - if( FD_UNLIKELY( err ) ) return err; - err = fd_bincode_uint64_encode( self->num_removed_accounts, ctx ); - if( FD_UNLIKELY( err ) ) return err; - err = fd_bincode_uint64_encode( self->num_lamports_stored, ctx ); - if( FD_UNLIKELY( err ) ) return err; - err = fd_bincode_uint64_encode( self->total_data_len, ctx ); - if( FD_UNLIKELY( err ) ) return err; - err = fd_bincode_uint64_encode( self->num_executable_accounts, ctx ); - if( FD_UNLIKELY( err ) ) return err; - return FD_BINCODE_SUCCESS; -} -int fd_bank_hash_stats_decode_offsets( fd_bank_hash_stats_off_t * self, fd_bincode_decode_ctx_t * ctx ) { - uchar const * data = ctx->data; - int err; - self->num_updated_accounts_off = (uint)( (ulong)ctx->data - (ulong)data ); err = fd_bincode_uint64_decode_preflight( ctx ); if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; - self->num_removed_accounts_off = (uint)( (ulong)ctx->data - (ulong)data ); err = fd_bincode_uint64_decode_preflight( ctx ); if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; - self->num_lamports_stored_off = (uint)( (ulong)ctx->data - (ulong)data ); err = fd_bincode_uint64_decode_preflight( ctx ); if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; - self->total_data_len_off = (uint)( (ulong)ctx->data - (ulong)data ); err = fd_bincode_uint64_decode_preflight( ctx ); if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; - self->num_executable_accounts_off = (uint)( (ulong)ctx->data - (ulong)data ); + { + uchar o; + err = fd_bincode_bool_decode( &o, ctx ); + if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; + if( o ) { + err = fd_bincode_uint64_decode_preflight( ctx ); + if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; + } + } err = fd_bincode_uint64_decode_preflight( ctx ); if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; - return FD_BINCODE_SUCCESS; -} -void fd_bank_hash_stats_new(fd_bank_hash_stats_t * self) { - fd_memset( self, 0, sizeof(fd_bank_hash_stats_t) ); -} -void fd_bank_hash_stats_destroy( fd_bank_hash_stats_t * self, fd_bincode_destroy_ctx_t * ctx ) { -} - -ulong fd_bank_hash_stats_footprint( void ){ return FD_BANK_HASH_STATS_FOOTPRINT; } -ulong fd_bank_hash_stats_align( void ){ return FD_BANK_HASH_STATS_ALIGN; } - -void fd_bank_hash_stats_walk( void * w, fd_bank_hash_stats_t const * self, fd_types_walk_fn_t fun, const char *name, uint level ) { - fun( w, self, name, FD_FLAMENCO_TYPE_MAP, "fd_bank_hash_stats", level++ ); - fun( w, &self->num_updated_accounts, "num_updated_accounts", FD_FLAMENCO_TYPE_ULONG, "ulong", level ); - fun( w, &self->num_removed_accounts, "num_removed_accounts", FD_FLAMENCO_TYPE_ULONG, "ulong", level ); - fun( w, &self->num_lamports_stored, "num_lamports_stored", FD_FLAMENCO_TYPE_ULONG, "ulong", level ); - fun( w, &self->total_data_len, "total_data_len", FD_FLAMENCO_TYPE_ULONG, "ulong", level ); - fun( w, &self->num_executable_accounts, "num_executable_accounts", FD_FLAMENCO_TYPE_ULONG, "ulong", level ); - fun( w, self, name, FD_FLAMENCO_TYPE_MAP_END, "fd_bank_hash_stats", level-- ); -} -ulong fd_bank_hash_stats_size( fd_bank_hash_stats_t const * self ) { - ulong size = 0; - size += sizeof(ulong); - size += sizeof(ulong); - size += sizeof(ulong); - size += sizeof(ulong); - size += sizeof(ulong); - return size; -} - -int fd_bank_hash_info_decode( fd_bank_hash_info_t * self, fd_bincode_decode_ctx_t * ctx ) { - void const * data = ctx->data; - int err = fd_bank_hash_info_decode_preflight( ctx ); + err = fd_bincode_uint128_decode_preflight( ctx ); + if( FD_UNLIKELY( err ) ) return err; + err = fd_bincode_uint64_decode_preflight( ctx ); if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; - ctx->data = data; - if( !fd_is_null_alloc_virtual( ctx->valloc ) ) { - fd_bank_hash_info_new( self ); - } - fd_bank_hash_info_decode_unsafe( self, ctx ); - return FD_BINCODE_SUCCESS; -} -int fd_bank_hash_info_decode_preflight( fd_bincode_decode_ctx_t * ctx ) { - int err; - err = fd_hash_decode_preflight( ctx ); + err = fd_bincode_double_decode_preflight( ctx ); if( FD_UNLIKELY( err ) ) return err; - err = fd_hash_decode_preflight( ctx ); + err = fd_bincode_uint64_decode_preflight( ctx ); + if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; + err = fd_bincode_uint64_decode_preflight( ctx ); + if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; + err = fd_bincode_uint64_decode_preflight( ctx ); + if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; + err = fd_bincode_uint64_decode_preflight( ctx ); + if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; + err = fd_bincode_bytes_decode_preflight( 32, ctx ); if( FD_UNLIKELY( err ) ) return err; - err = fd_bank_hash_stats_decode_preflight( ctx ); + err = fd_bincode_uint64_decode_preflight( ctx ); + if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; + err = fd_fee_calculator_decode_preflight( ctx ); if( FD_UNLIKELY( err ) ) return err; - return FD_BINCODE_SUCCESS; -} -void fd_bank_hash_info_decode_unsafe( fd_bank_hash_info_t * self, fd_bincode_decode_ctx_t * ctx ) { - fd_hash_decode_unsafe( &self->hash, ctx ); - fd_hash_decode_unsafe( &self->snapshot_hash, ctx ); - fd_bank_hash_stats_decode_unsafe( &self->stats, ctx ); -} -int fd_bank_hash_info_encode( fd_bank_hash_info_t const * self, fd_bincode_encode_ctx_t * ctx ) { - int err; - err = fd_hash_encode( &self->hash, ctx ); + err = fd_fee_rate_governor_decode_preflight( ctx ); if( FD_UNLIKELY( err ) ) return err; - err = fd_hash_encode( &self->snapshot_hash, ctx ); + err = fd_bincode_uint64_decode_preflight( ctx ); + if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; + err = fd_rent_collector_decode_preflight( ctx ); if( FD_UNLIKELY( err ) ) return err; - err = fd_bank_hash_stats_encode( &self->stats, ctx ); + err = fd_epoch_schedule_decode_preflight( ctx ); if( FD_UNLIKELY( err ) ) return err; - return FD_BINCODE_SUCCESS; -} -int fd_bank_hash_info_decode_offsets( fd_bank_hash_info_off_t * self, fd_bincode_decode_ctx_t * ctx ) { - uchar const * data = ctx->data; - int err; - self->hash_off = (uint)( (ulong)ctx->data - (ulong)data ); - err = fd_hash_decode_preflight( ctx ); + err = fd_inflation_decode_preflight( ctx ); if( FD_UNLIKELY( err ) ) return err; - self->snapshot_hash_off = (uint)( (ulong)ctx->data - (ulong)data ); - err = fd_hash_decode_preflight( ctx ); + err = fd_stakes_serializable_decode_preflight( ctx ); if( FD_UNLIKELY( err ) ) return err; - self->stats_off = (uint)( (ulong)ctx->data - (ulong)data ); - err = fd_bank_hash_stats_decode_preflight( ctx ); + err = fd_unused_accounts_decode_preflight( ctx ); if( FD_UNLIKELY( err ) ) return err; - return FD_BINCODE_SUCCESS; -} -void fd_bank_hash_info_new(fd_bank_hash_info_t * self) { - fd_memset( self, 0, sizeof(fd_bank_hash_info_t) ); - fd_hash_new( &self->hash ); - fd_hash_new( &self->snapshot_hash ); - fd_bank_hash_stats_new( &self->stats ); -} -void fd_bank_hash_info_destroy( fd_bank_hash_info_t * self, fd_bincode_destroy_ctx_t * ctx ) { - fd_hash_destroy( &self->hash, ctx ); - fd_hash_destroy( &self->snapshot_hash, ctx ); - fd_bank_hash_stats_destroy( &self->stats, ctx ); -} - -ulong fd_bank_hash_info_footprint( void ){ return FD_BANK_HASH_INFO_FOOTPRINT; } -ulong fd_bank_hash_info_align( void ){ return FD_BANK_HASH_INFO_ALIGN; } - -void fd_bank_hash_info_walk( void * w, fd_bank_hash_info_t const * self, fd_types_walk_fn_t fun, const char *name, uint level ) { - fun( w, self, name, FD_FLAMENCO_TYPE_MAP, "fd_bank_hash_info", level++ ); - fd_hash_walk( w, &self->hash, fun, "hash", level ); - fd_hash_walk( w, &self->snapshot_hash, fun, "snapshot_hash", level ); - fd_bank_hash_stats_walk( w, &self->stats, fun, "stats", level ); - fun( w, self, name, FD_FLAMENCO_TYPE_MAP_END, "fd_bank_hash_info", level-- ); -} -ulong fd_bank_hash_info_size( fd_bank_hash_info_t const * self ) { - ulong size = 0; - size += fd_hash_size( &self->hash ); - size += fd_hash_size( &self->snapshot_hash ); - size += fd_bank_hash_stats_size( &self->stats ); - return size; -} - -int fd_slot_map_pair_decode( fd_slot_map_pair_t * self, fd_bincode_decode_ctx_t * ctx ) { - void const * data = ctx->data; - int err = fd_slot_map_pair_decode_preflight( ctx ); + ulong epoch_stakes_len; + err = fd_bincode_uint64_decode( &epoch_stakes_len, ctx ); if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; - ctx->data = data; - if( !fd_is_null_alloc_virtual( ctx->valloc ) ) { - fd_slot_map_pair_new( self ); + if( epoch_stakes_len ) { + for( ulong i=0; i < epoch_stakes_len; i++ ) { + err = fd_epoch_epoch_stakes_pair_decode_preflight( ctx ); + if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; + } } - fd_slot_map_pair_decode_unsafe( self, ctx ); - return FD_BINCODE_SUCCESS; -} -int fd_slot_map_pair_decode_preflight( fd_bincode_decode_ctx_t * ctx ) { - int err; - err = fd_bincode_uint64_decode_preflight( ctx ); - if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; - err = fd_hash_decode_preflight( ctx ); + err = fd_bincode_bool_decode_preflight( ctx ); if( FD_UNLIKELY( err ) ) return err; return FD_BINCODE_SUCCESS; } -void fd_slot_map_pair_decode_unsafe( fd_slot_map_pair_t * self, fd_bincode_decode_ctx_t * ctx ) { - fd_bincode_uint64_decode_unsafe( &self->slot, ctx ); +void fd_serializable_versioned_bank_decode_unsafe( fd_serializable_versioned_bank_t * self, fd_bincode_decode_ctx_t * ctx ) { + fd_block_hash_vec_decode_unsafe( &self->blockhash_queue, ctx ); + fd_bincode_uint64_decode_unsafe( &self->ancestors_len, ctx ); + if( self->ancestors_len ) { + self->ancestors = (fd_slot_pair_t *)fd_valloc_malloc( ctx->valloc, FD_SLOT_PAIR_ALIGN, FD_SLOT_PAIR_FOOTPRINT*self->ancestors_len ); + for( ulong i=0; i < self->ancestors_len; i++ ) { + fd_slot_pair_new( self->ancestors + i ); + fd_slot_pair_decode_unsafe( self->ancestors + i, ctx ); + } + } else + self->ancestors = NULL; fd_hash_decode_unsafe( &self->hash, ctx ); + fd_hash_decode_unsafe( &self->parent_hash, ctx ); + fd_bincode_uint64_decode_unsafe( &self->parent_slot, ctx ); + fd_hard_forks_decode_unsafe( &self->hard_forks, ctx ); + fd_bincode_uint64_decode_unsafe( &self->transaction_count, ctx ); + fd_bincode_uint64_decode_unsafe( &self->tick_height, ctx ); + fd_bincode_uint64_decode_unsafe( &self->signature_count, ctx ); + fd_bincode_uint64_decode_unsafe( &self->capitalization, ctx ); + fd_bincode_uint64_decode_unsafe( &self->max_tick_height, ctx ); + { + uchar o; + fd_bincode_bool_decode_unsafe( &o, ctx ); + if( o ) { + self->hashes_per_tick = fd_valloc_malloc( ctx->valloc, 8, sizeof(ulong) ); + fd_bincode_uint64_decode_unsafe( self->hashes_per_tick, ctx ); + } else + self->hashes_per_tick = NULL; + } + fd_bincode_uint64_decode_unsafe( &self->ticks_per_slot, ctx ); + fd_bincode_uint128_decode_unsafe( &self->ns_per_slot, ctx ); + fd_bincode_uint64_decode_unsafe( &self->genesis_creation_time, ctx ); + fd_bincode_double_decode_unsafe( &self->slots_per_year, ctx ); + fd_bincode_uint64_decode_unsafe( &self->accounts_data_len, ctx ); + fd_bincode_uint64_decode_unsafe( &self->slot, ctx ); + fd_bincode_uint64_decode_unsafe( &self->epoch, ctx ); + fd_bincode_uint64_decode_unsafe( &self->block_height, ctx ); + fd_pubkey_decode_unsafe( &self->collector_id, ctx ); + fd_bincode_uint64_decode_unsafe( &self->collector_fees, ctx ); + fd_fee_calculator_decode_unsafe( &self->fee_calculator, ctx ); + fd_fee_rate_governor_decode_unsafe( &self->fee_rate_governor, ctx ); + fd_bincode_uint64_decode_unsafe( &self->collected_rent, ctx ); + fd_rent_collector_decode_unsafe( &self->rent_collector, ctx ); + fd_epoch_schedule_decode_unsafe( &self->epoch_schedule, ctx ); + fd_inflation_decode_unsafe( &self->inflation, ctx ); + fd_stakes_serializable_decode_unsafe( &self->stakes, ctx ); + fd_unused_accounts_decode_unsafe( &self->unused_accounts, ctx ); + fd_bincode_uint64_decode_unsafe( &self->epoch_stakes_len, ctx ); + if( self->epoch_stakes_len ) { + self->epoch_stakes = (fd_epoch_epoch_stakes_pair_t *)fd_valloc_malloc( ctx->valloc, FD_EPOCH_EPOCH_STAKES_PAIR_ALIGN, FD_EPOCH_EPOCH_STAKES_PAIR_FOOTPRINT*self->epoch_stakes_len ); + for( ulong i=0; i < self->epoch_stakes_len; i++ ) { + fd_epoch_epoch_stakes_pair_new( self->epoch_stakes + i ); + fd_epoch_epoch_stakes_pair_decode_unsafe( self->epoch_stakes + i, ctx ); + } + } else + self->epoch_stakes = NULL; + fd_bincode_bool_decode_unsafe( &self->is_delta, ctx ); } -int fd_slot_map_pair_encode( fd_slot_map_pair_t const * self, fd_bincode_encode_ctx_t * ctx ) { +int fd_serializable_versioned_bank_encode( fd_serializable_versioned_bank_t const * self, fd_bincode_encode_ctx_t * ctx ) { int err; - err = fd_bincode_uint64_encode( self->slot, ctx ); - if( FD_UNLIKELY( err ) ) return err; - err = fd_hash_encode( &self->hash, ctx ); + err = fd_block_hash_vec_encode( &self->blockhash_queue, ctx ); if( FD_UNLIKELY( err ) ) return err; - return FD_BINCODE_SUCCESS; -} -int fd_slot_map_pair_decode_offsets( fd_slot_map_pair_off_t * self, fd_bincode_decode_ctx_t * ctx ) { - uchar const * data = ctx->data; + err = fd_bincode_uint64_encode( self->ancestors_len, ctx ); + if( FD_UNLIKELY(err) ) return err; + if( self->ancestors_len ) { + for( ulong i=0; i < self->ancestors_len; i++ ) { + err = fd_slot_pair_encode( self->ancestors + i, ctx ); + if( FD_UNLIKELY( err ) ) return err; + } + } + err = fd_hash_encode( &self->hash, ctx ); + if( FD_UNLIKELY( err ) ) return err; + err = fd_hash_encode( &self->parent_hash, ctx ); + if( FD_UNLIKELY( err ) ) return err; + err = fd_bincode_uint64_encode( self->parent_slot, ctx ); + if( FD_UNLIKELY( err ) ) return err; + err = fd_hard_forks_encode( &self->hard_forks, ctx ); + if( FD_UNLIKELY( err ) ) return err; + err = fd_bincode_uint64_encode( self->transaction_count, ctx ); + if( FD_UNLIKELY( err ) ) return err; + err = fd_bincode_uint64_encode( self->tick_height, ctx ); + if( FD_UNLIKELY( err ) ) return err; + err = fd_bincode_uint64_encode( self->signature_count, ctx ); + if( FD_UNLIKELY( err ) ) return err; + err = fd_bincode_uint64_encode( self->capitalization, ctx ); + if( FD_UNLIKELY( err ) ) return err; + err = fd_bincode_uint64_encode( self->max_tick_height, ctx ); + if( FD_UNLIKELY( err ) ) return err; + if( self->hashes_per_tick != NULL ) { + err = fd_bincode_bool_encode( 1, ctx ); + if( FD_UNLIKELY( err ) ) return err; + err = fd_bincode_uint64_encode( self->hashes_per_tick[0], ctx ); + if( FD_UNLIKELY( err ) ) return err; + } else { + err = fd_bincode_bool_encode( 0, ctx ); + if( FD_UNLIKELY( err ) ) return err; + } + err = fd_bincode_uint64_encode( self->ticks_per_slot, ctx ); + if( FD_UNLIKELY( err ) ) return err; + err = fd_bincode_uint128_encode( self->ns_per_slot, ctx ); + if( FD_UNLIKELY( err ) ) return err; + err = fd_bincode_uint64_encode( self->genesis_creation_time, ctx ); + if( FD_UNLIKELY( err ) ) return err; + err = fd_bincode_double_encode( self->slots_per_year, ctx ); + if( FD_UNLIKELY( err ) ) return err; + err = fd_bincode_uint64_encode( self->accounts_data_len, ctx ); + if( FD_UNLIKELY( err ) ) return err; + err = fd_bincode_uint64_encode( self->slot, ctx ); + if( FD_UNLIKELY( err ) ) return err; + err = fd_bincode_uint64_encode( self->epoch, ctx ); + if( FD_UNLIKELY( err ) ) return err; + err = fd_bincode_uint64_encode( self->block_height, ctx ); + if( FD_UNLIKELY( err ) ) return err; + err = fd_pubkey_encode( &self->collector_id, ctx ); + if( FD_UNLIKELY( err ) ) return err; + err = fd_bincode_uint64_encode( self->collector_fees, ctx ); + if( FD_UNLIKELY( err ) ) return err; + err = fd_fee_calculator_encode( &self->fee_calculator, ctx ); + if( FD_UNLIKELY( err ) ) return err; + err = fd_fee_rate_governor_encode( &self->fee_rate_governor, ctx ); + if( FD_UNLIKELY( err ) ) return err; + err = fd_bincode_uint64_encode( self->collected_rent, ctx ); + if( FD_UNLIKELY( err ) ) return err; + err = fd_rent_collector_encode( &self->rent_collector, ctx ); + if( FD_UNLIKELY( err ) ) return err; + err = fd_epoch_schedule_encode( &self->epoch_schedule, ctx ); + if( FD_UNLIKELY( err ) ) return err; + err = fd_inflation_encode( &self->inflation, ctx ); + if( FD_UNLIKELY( err ) ) return err; + err = fd_stakes_serializable_encode( &self->stakes, ctx ); + if( FD_UNLIKELY( err ) ) return err; + err = fd_unused_accounts_encode( &self->unused_accounts, ctx ); + if( FD_UNLIKELY( err ) ) return err; + err = fd_bincode_uint64_encode( self->epoch_stakes_len, ctx ); + if( FD_UNLIKELY(err) ) return err; + if( self->epoch_stakes_len ) { + for( ulong i=0; i < self->epoch_stakes_len; i++ ) { + err = fd_epoch_epoch_stakes_pair_encode( self->epoch_stakes + i, ctx ); + if( FD_UNLIKELY( err ) ) return err; + } + } + err = fd_bincode_bool_encode( (uchar)(self->is_delta), ctx ); + if( FD_UNLIKELY( err ) ) return err; + return FD_BINCODE_SUCCESS; +} +int fd_serializable_versioned_bank_decode_offsets( fd_serializable_versioned_bank_off_t * self, fd_bincode_decode_ctx_t * ctx ) { + uchar const * data = ctx->data; + int err; + self->blockhash_queue_off = (uint)( (ulong)ctx->data - (ulong)data ); + err = fd_block_hash_vec_decode_preflight( ctx ); + if( FD_UNLIKELY( err ) ) return err; + self->ancestors_off = (uint)( (ulong)ctx->data - (ulong)data ); + ulong ancestors_len; + err = fd_bincode_uint64_decode( &ancestors_len, ctx ); + if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; + if( ancestors_len ) { + for( ulong i=0; i < ancestors_len; i++ ) { + err = fd_slot_pair_decode_preflight( ctx ); + if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; + } + } + self->hash_off = (uint)( (ulong)ctx->data - (ulong)data ); + err = fd_hash_decode_preflight( ctx ); + if( FD_UNLIKELY( err ) ) return err; + self->parent_hash_off = (uint)( (ulong)ctx->data - (ulong)data ); + err = fd_hash_decode_preflight( ctx ); + if( FD_UNLIKELY( err ) ) return err; + self->parent_slot_off = (uint)( (ulong)ctx->data - (ulong)data ); + err = fd_bincode_uint64_decode_preflight( ctx ); + if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; + self->hard_forks_off = (uint)( (ulong)ctx->data - (ulong)data ); + err = fd_hard_forks_decode_preflight( ctx ); + if( FD_UNLIKELY( err ) ) return err; + self->transaction_count_off = (uint)( (ulong)ctx->data - (ulong)data ); + err = fd_bincode_uint64_decode_preflight( ctx ); + if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; + self->tick_height_off = (uint)( (ulong)ctx->data - (ulong)data ); + err = fd_bincode_uint64_decode_preflight( ctx ); + if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; + self->signature_count_off = (uint)( (ulong)ctx->data - (ulong)data ); + err = fd_bincode_uint64_decode_preflight( ctx ); + if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; + self->capitalization_off = (uint)( (ulong)ctx->data - (ulong)data ); + err = fd_bincode_uint64_decode_preflight( ctx ); + if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; + self->max_tick_height_off = (uint)( (ulong)ctx->data - (ulong)data ); + err = fd_bincode_uint64_decode_preflight( ctx ); + if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; + self->hashes_per_tick_off = (uint)( (ulong)ctx->data - (ulong)data ); + { + uchar o; + err = fd_bincode_bool_decode( &o, ctx ); + if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; + if( o ) { + err = fd_bincode_uint64_decode_preflight( ctx ); + if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; + } + } + self->ticks_per_slot_off = (uint)( (ulong)ctx->data - (ulong)data ); + err = fd_bincode_uint64_decode_preflight( ctx ); + if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; + self->ns_per_slot_off = (uint)( (ulong)ctx->data - (ulong)data ); + err = fd_bincode_uint128_decode_preflight( ctx ); + if( FD_UNLIKELY( err ) ) return err; + self->genesis_creation_time_off = (uint)( (ulong)ctx->data - (ulong)data ); + err = fd_bincode_uint64_decode_preflight( ctx ); + if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; + self->slots_per_year_off = (uint)( (ulong)ctx->data - (ulong)data ); + err = fd_bincode_double_decode_preflight( ctx ); + if( FD_UNLIKELY( err ) ) return err; + self->accounts_data_len_off = (uint)( (ulong)ctx->data - (ulong)data ); + err = fd_bincode_uint64_decode_preflight( ctx ); + if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; + self->slot_off = (uint)( (ulong)ctx->data - (ulong)data ); + err = fd_bincode_uint64_decode_preflight( ctx ); + if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; + self->epoch_off = (uint)( (ulong)ctx->data - (ulong)data ); + err = fd_bincode_uint64_decode_preflight( ctx ); + if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; + self->block_height_off = (uint)( (ulong)ctx->data - (ulong)data ); + err = fd_bincode_uint64_decode_preflight( ctx ); + if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; + self->collector_id_off = (uint)( (ulong)ctx->data - (ulong)data ); + err = fd_bincode_bytes_decode_preflight( 32, ctx ); + if( FD_UNLIKELY( err ) ) return err; + self->collector_fees_off = (uint)( (ulong)ctx->data - (ulong)data ); + err = fd_bincode_uint64_decode_preflight( ctx ); + if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; + self->fee_calculator_off = (uint)( (ulong)ctx->data - (ulong)data ); + err = fd_fee_calculator_decode_preflight( ctx ); + if( FD_UNLIKELY( err ) ) return err; + self->fee_rate_governor_off = (uint)( (ulong)ctx->data - (ulong)data ); + err = fd_fee_rate_governor_decode_preflight( ctx ); + if( FD_UNLIKELY( err ) ) return err; + self->collected_rent_off = (uint)( (ulong)ctx->data - (ulong)data ); + err = fd_bincode_uint64_decode_preflight( ctx ); + if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; + self->rent_collector_off = (uint)( (ulong)ctx->data - (ulong)data ); + err = fd_rent_collector_decode_preflight( ctx ); + if( FD_UNLIKELY( err ) ) return err; + self->epoch_schedule_off = (uint)( (ulong)ctx->data - (ulong)data ); + err = fd_epoch_schedule_decode_preflight( ctx ); + if( FD_UNLIKELY( err ) ) return err; + self->inflation_off = (uint)( (ulong)ctx->data - (ulong)data ); + err = fd_inflation_decode_preflight( ctx ); + if( FD_UNLIKELY( err ) ) return err; + self->stakes_off = (uint)( (ulong)ctx->data - (ulong)data ); + err = fd_stakes_serializable_decode_preflight( ctx ); + if( FD_UNLIKELY( err ) ) return err; + self->unused_accounts_off = (uint)( (ulong)ctx->data - (ulong)data ); + err = fd_unused_accounts_decode_preflight( ctx ); + if( FD_UNLIKELY( err ) ) return err; + self->epoch_stakes_off = (uint)( (ulong)ctx->data - (ulong)data ); + ulong epoch_stakes_len; + err = fd_bincode_uint64_decode( &epoch_stakes_len, ctx ); + if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; + if( epoch_stakes_len ) { + for( ulong i=0; i < epoch_stakes_len; i++ ) { + err = fd_epoch_epoch_stakes_pair_decode_preflight( ctx ); + if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; + } + } + self->is_delta_off = (uint)( (ulong)ctx->data - (ulong)data ); + err = fd_bincode_bool_decode_preflight( ctx ); + if( FD_UNLIKELY( err ) ) return err; + return FD_BINCODE_SUCCESS; +} +void fd_serializable_versioned_bank_new(fd_serializable_versioned_bank_t * self) { + fd_memset( self, 0, sizeof(fd_serializable_versioned_bank_t) ); + fd_block_hash_vec_new( &self->blockhash_queue ); + fd_hash_new( &self->hash ); + fd_hash_new( &self->parent_hash ); + fd_hard_forks_new( &self->hard_forks ); + fd_pubkey_new( &self->collector_id ); + fd_fee_calculator_new( &self->fee_calculator ); + fd_fee_rate_governor_new( &self->fee_rate_governor ); + fd_rent_collector_new( &self->rent_collector ); + fd_epoch_schedule_new( &self->epoch_schedule ); + fd_inflation_new( &self->inflation ); + fd_stakes_serializable_new( &self->stakes ); + fd_unused_accounts_new( &self->unused_accounts ); +} +void fd_serializable_versioned_bank_destroy( fd_serializable_versioned_bank_t * self, fd_bincode_destroy_ctx_t * ctx ) { + fd_block_hash_vec_destroy( &self->blockhash_queue, ctx ); + if( self->ancestors ) { + for( ulong i=0; i < self->ancestors_len; i++ ) + fd_slot_pair_destroy( self->ancestors + i, ctx ); + fd_valloc_free( ctx->valloc, self->ancestors ); + self->ancestors = NULL; + } + fd_hash_destroy( &self->hash, ctx ); + fd_hash_destroy( &self->parent_hash, ctx ); + fd_hard_forks_destroy( &self->hard_forks, ctx ); + if( self->hashes_per_tick ) { + fd_valloc_free( ctx->valloc, self->hashes_per_tick ); + self->hashes_per_tick = NULL; + } + fd_pubkey_destroy( &self->collector_id, ctx ); + fd_fee_calculator_destroy( &self->fee_calculator, ctx ); + fd_fee_rate_governor_destroy( &self->fee_rate_governor, ctx ); + fd_rent_collector_destroy( &self->rent_collector, ctx ); + fd_epoch_schedule_destroy( &self->epoch_schedule, ctx ); + fd_inflation_destroy( &self->inflation, ctx ); + fd_stakes_serializable_destroy( &self->stakes, ctx ); + fd_unused_accounts_destroy( &self->unused_accounts, ctx ); + if( self->epoch_stakes ) { + for( ulong i=0; i < self->epoch_stakes_len; i++ ) + fd_epoch_epoch_stakes_pair_destroy( self->epoch_stakes + i, ctx ); + fd_valloc_free( ctx->valloc, self->epoch_stakes ); + self->epoch_stakes = NULL; + } +} + +ulong fd_serializable_versioned_bank_footprint( void ){ return FD_SERIALIZABLE_VERSIONED_BANK_FOOTPRINT; } +ulong fd_serializable_versioned_bank_align( void ){ return FD_SERIALIZABLE_VERSIONED_BANK_ALIGN; } + +void fd_serializable_versioned_bank_walk( void * w, fd_serializable_versioned_bank_t const * self, fd_types_walk_fn_t fun, const char *name, uint level ) { + fun( w, self, name, FD_FLAMENCO_TYPE_MAP, "fd_serializable_versioned_bank", level++ ); + fd_block_hash_vec_walk( w, &self->blockhash_queue, fun, "blockhash_queue", level ); + if( self->ancestors_len ) { + fun( w, NULL, "ancestors", FD_FLAMENCO_TYPE_ARR, "array", level++ ); + for( ulong i=0; i < self->ancestors_len; i++ ) + fd_slot_pair_walk(w, self->ancestors + i, fun, "slot_pair", level ); + fun( w, NULL, "ancestors", FD_FLAMENCO_TYPE_ARR_END, "array", level-- ); + } + fd_hash_walk( w, &self->hash, fun, "hash", level ); + fd_hash_walk( w, &self->parent_hash, fun, "parent_hash", level ); + fun( w, &self->parent_slot, "parent_slot", FD_FLAMENCO_TYPE_ULONG, "ulong", level ); + fd_hard_forks_walk( w, &self->hard_forks, fun, "hard_forks", level ); + fun( w, &self->transaction_count, "transaction_count", FD_FLAMENCO_TYPE_ULONG, "ulong", level ); + fun( w, &self->tick_height, "tick_height", FD_FLAMENCO_TYPE_ULONG, "ulong", level ); + fun( w, &self->signature_count, "signature_count", FD_FLAMENCO_TYPE_ULONG, "ulong", level ); + fun( w, &self->capitalization, "capitalization", FD_FLAMENCO_TYPE_ULONG, "ulong", level ); + fun( w, &self->max_tick_height, "max_tick_height", FD_FLAMENCO_TYPE_ULONG, "ulong", level ); + if( !self->hashes_per_tick ) { + fun( w, NULL, "hashes_per_tick", FD_FLAMENCO_TYPE_NULL, "ulong", level ); + } else { + fun( w, self->hashes_per_tick, "hashes_per_tick", FD_FLAMENCO_TYPE_ULONG, "ulong", level ); + } + fun( w, &self->ticks_per_slot, "ticks_per_slot", FD_FLAMENCO_TYPE_ULONG, "ulong", level ); + fun( w, &self->ns_per_slot, "ns_per_slot", FD_FLAMENCO_TYPE_UINT128, "uint128", level ); + fun( w, &self->genesis_creation_time, "genesis_creation_time", FD_FLAMENCO_TYPE_ULONG, "ulong", level ); + fun( w, &self->slots_per_year, "slots_per_year", FD_FLAMENCO_TYPE_DOUBLE, "double", level ); + fun( w, &self->accounts_data_len, "accounts_data_len", FD_FLAMENCO_TYPE_ULONG, "ulong", level ); + fun( w, &self->slot, "slot", FD_FLAMENCO_TYPE_ULONG, "ulong", level ); + fun( w, &self->epoch, "epoch", FD_FLAMENCO_TYPE_ULONG, "ulong", level ); + fun( w, &self->block_height, "block_height", FD_FLAMENCO_TYPE_ULONG, "ulong", level ); + fd_pubkey_walk( w, &self->collector_id, fun, "collector_id", level ); + fun( w, &self->collector_fees, "collector_fees", FD_FLAMENCO_TYPE_ULONG, "ulong", level ); + fd_fee_calculator_walk( w, &self->fee_calculator, fun, "fee_calculator", level ); + fd_fee_rate_governor_walk( w, &self->fee_rate_governor, fun, "fee_rate_governor", level ); + fun( w, &self->collected_rent, "collected_rent", FD_FLAMENCO_TYPE_ULONG, "ulong", level ); + fd_rent_collector_walk( w, &self->rent_collector, fun, "rent_collector", level ); + fd_epoch_schedule_walk( w, &self->epoch_schedule, fun, "epoch_schedule", level ); + fd_inflation_walk( w, &self->inflation, fun, "inflation", level ); + fd_stakes_serializable_walk( w, &self->stakes, fun, "stakes", level ); + fd_unused_accounts_walk( w, &self->unused_accounts, fun, "unused_accounts", level ); + if( self->epoch_stakes_len ) { + fun( w, NULL, "epoch_stakes", FD_FLAMENCO_TYPE_ARR, "array", level++ ); + for( ulong i=0; i < self->epoch_stakes_len; i++ ) + fd_epoch_epoch_stakes_pair_walk(w, self->epoch_stakes + i, fun, "epoch_epoch_stakes_pair", level ); + fun( w, NULL, "epoch_stakes", FD_FLAMENCO_TYPE_ARR_END, "array", level-- ); + } + fun( w, &self->is_delta, "is_delta", FD_FLAMENCO_TYPE_BOOL, "bool", level ); + fun( w, self, name, FD_FLAMENCO_TYPE_MAP_END, "fd_serializable_versioned_bank", level-- ); +} +ulong fd_serializable_versioned_bank_size( fd_serializable_versioned_bank_t const * self ) { + ulong size = 0; + size += fd_block_hash_vec_size( &self->blockhash_queue ); + do { + size += sizeof(ulong); + for( ulong i=0; i < self->ancestors_len; i++ ) + size += fd_slot_pair_size( self->ancestors + i ); + } while(0); + size += fd_hash_size( &self->hash ); + size += fd_hash_size( &self->parent_hash ); + size += sizeof(ulong); + size += fd_hard_forks_size( &self->hard_forks ); + size += sizeof(ulong); + size += sizeof(ulong); + size += sizeof(ulong); + size += sizeof(ulong); + size += sizeof(ulong); + size += sizeof(char); + if( NULL != self->hashes_per_tick ) { + size += sizeof(ulong); + } + size += sizeof(ulong); + size += sizeof(uint128); + size += sizeof(ulong); + size += sizeof(double); + size += sizeof(ulong); + size += sizeof(ulong); + size += sizeof(ulong); + size += sizeof(ulong); + size += fd_pubkey_size( &self->collector_id ); + size += sizeof(ulong); + size += fd_fee_calculator_size( &self->fee_calculator ); + size += fd_fee_rate_governor_size( &self->fee_rate_governor ); + size += sizeof(ulong); + size += fd_rent_collector_size( &self->rent_collector ); + size += fd_epoch_schedule_size( &self->epoch_schedule ); + size += fd_inflation_size( &self->inflation ); + size += fd_stakes_serializable_size( &self->stakes ); + size += fd_unused_accounts_size( &self->unused_accounts ); + do { + size += sizeof(ulong); + for( ulong i=0; i < self->epoch_stakes_len; i++ ) + size += fd_epoch_epoch_stakes_pair_size( self->epoch_stakes + i ); + } while(0); + size += sizeof(char); + return size; +} + +int fd_bank_hash_stats_decode( fd_bank_hash_stats_t * self, fd_bincode_decode_ctx_t * ctx ) { + void const * data = ctx->data; + int err = fd_bank_hash_stats_decode_preflight( ctx ); + if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; + ctx->data = data; + if( !fd_is_null_alloc_virtual( ctx->valloc ) ) { + fd_bank_hash_stats_new( self ); + } + fd_bank_hash_stats_decode_unsafe( self, ctx ); + return FD_BINCODE_SUCCESS; +} +int fd_bank_hash_stats_decode_preflight( fd_bincode_decode_ctx_t * ctx ) { + int err; + err = fd_bincode_uint64_decode_preflight( ctx ); + if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; + err = fd_bincode_uint64_decode_preflight( ctx ); + if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; + err = fd_bincode_uint64_decode_preflight( ctx ); + if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; + err = fd_bincode_uint64_decode_preflight( ctx ); + if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; + err = fd_bincode_uint64_decode_preflight( ctx ); + if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; + return FD_BINCODE_SUCCESS; +} +void fd_bank_hash_stats_decode_unsafe( fd_bank_hash_stats_t * self, fd_bincode_decode_ctx_t * ctx ) { + fd_bincode_uint64_decode_unsafe( &self->num_updated_accounts, ctx ); + fd_bincode_uint64_decode_unsafe( &self->num_removed_accounts, ctx ); + fd_bincode_uint64_decode_unsafe( &self->num_lamports_stored, ctx ); + fd_bincode_uint64_decode_unsafe( &self->total_data_len, ctx ); + fd_bincode_uint64_decode_unsafe( &self->num_executable_accounts, ctx ); +} +int fd_bank_hash_stats_encode( fd_bank_hash_stats_t const * self, fd_bincode_encode_ctx_t * ctx ) { + int err; + err = fd_bincode_uint64_encode( self->num_updated_accounts, ctx ); + if( FD_UNLIKELY( err ) ) return err; + err = fd_bincode_uint64_encode( self->num_removed_accounts, ctx ); + if( FD_UNLIKELY( err ) ) return err; + err = fd_bincode_uint64_encode( self->num_lamports_stored, ctx ); + if( FD_UNLIKELY( err ) ) return err; + err = fd_bincode_uint64_encode( self->total_data_len, ctx ); + if( FD_UNLIKELY( err ) ) return err; + err = fd_bincode_uint64_encode( self->num_executable_accounts, ctx ); + if( FD_UNLIKELY( err ) ) return err; + return FD_BINCODE_SUCCESS; +} +int fd_bank_hash_stats_decode_offsets( fd_bank_hash_stats_off_t * self, fd_bincode_decode_ctx_t * ctx ) { + uchar const * data = ctx->data; + int err; + self->num_updated_accounts_off = (uint)( (ulong)ctx->data - (ulong)data ); + err = fd_bincode_uint64_decode_preflight( ctx ); + if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; + self->num_removed_accounts_off = (uint)( (ulong)ctx->data - (ulong)data ); + err = fd_bincode_uint64_decode_preflight( ctx ); + if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; + self->num_lamports_stored_off = (uint)( (ulong)ctx->data - (ulong)data ); + err = fd_bincode_uint64_decode_preflight( ctx ); + if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; + self->total_data_len_off = (uint)( (ulong)ctx->data - (ulong)data ); + err = fd_bincode_uint64_decode_preflight( ctx ); + if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; + self->num_executable_accounts_off = (uint)( (ulong)ctx->data - (ulong)data ); + err = fd_bincode_uint64_decode_preflight( ctx ); + if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; + return FD_BINCODE_SUCCESS; +} +void fd_bank_hash_stats_new(fd_bank_hash_stats_t * self) { + fd_memset( self, 0, sizeof(fd_bank_hash_stats_t) ); +} +void fd_bank_hash_stats_destroy( fd_bank_hash_stats_t * self, fd_bincode_destroy_ctx_t * ctx ) { +} + +ulong fd_bank_hash_stats_footprint( void ){ return FD_BANK_HASH_STATS_FOOTPRINT; } +ulong fd_bank_hash_stats_align( void ){ return FD_BANK_HASH_STATS_ALIGN; } + +void fd_bank_hash_stats_walk( void * w, fd_bank_hash_stats_t const * self, fd_types_walk_fn_t fun, const char *name, uint level ) { + fun( w, self, name, FD_FLAMENCO_TYPE_MAP, "fd_bank_hash_stats", level++ ); + fun( w, &self->num_updated_accounts, "num_updated_accounts", FD_FLAMENCO_TYPE_ULONG, "ulong", level ); + fun( w, &self->num_removed_accounts, "num_removed_accounts", FD_FLAMENCO_TYPE_ULONG, "ulong", level ); + fun( w, &self->num_lamports_stored, "num_lamports_stored", FD_FLAMENCO_TYPE_ULONG, "ulong", level ); + fun( w, &self->total_data_len, "total_data_len", FD_FLAMENCO_TYPE_ULONG, "ulong", level ); + fun( w, &self->num_executable_accounts, "num_executable_accounts", FD_FLAMENCO_TYPE_ULONG, "ulong", level ); + fun( w, self, name, FD_FLAMENCO_TYPE_MAP_END, "fd_bank_hash_stats", level-- ); +} +ulong fd_bank_hash_stats_size( fd_bank_hash_stats_t const * self ) { + ulong size = 0; + size += sizeof(ulong); + size += sizeof(ulong); + size += sizeof(ulong); + size += sizeof(ulong); + size += sizeof(ulong); + return size; +} + +int fd_bank_hash_info_decode( fd_bank_hash_info_t * self, fd_bincode_decode_ctx_t * ctx ) { + void const * data = ctx->data; + int err = fd_bank_hash_info_decode_preflight( ctx ); + if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; + ctx->data = data; + if( !fd_is_null_alloc_virtual( ctx->valloc ) ) { + fd_bank_hash_info_new( self ); + } + fd_bank_hash_info_decode_unsafe( self, ctx ); + return FD_BINCODE_SUCCESS; +} +int fd_bank_hash_info_decode_preflight( fd_bincode_decode_ctx_t * ctx ) { + int err; + err = fd_hash_decode_preflight( ctx ); + if( FD_UNLIKELY( err ) ) return err; + err = fd_hash_decode_preflight( ctx ); + if( FD_UNLIKELY( err ) ) return err; + err = fd_bank_hash_stats_decode_preflight( ctx ); + if( FD_UNLIKELY( err ) ) return err; + return FD_BINCODE_SUCCESS; +} +void fd_bank_hash_info_decode_unsafe( fd_bank_hash_info_t * self, fd_bincode_decode_ctx_t * ctx ) { + fd_hash_decode_unsafe( &self->accounts_delta_hash, ctx ); + fd_hash_decode_unsafe( &self->accounts_hash, ctx ); + fd_bank_hash_stats_decode_unsafe( &self->stats, ctx ); +} +int fd_bank_hash_info_encode( fd_bank_hash_info_t const * self, fd_bincode_encode_ctx_t * ctx ) { + int err; + err = fd_hash_encode( &self->accounts_delta_hash, ctx ); + if( FD_UNLIKELY( err ) ) return err; + err = fd_hash_encode( &self->accounts_hash, ctx ); + if( FD_UNLIKELY( err ) ) return err; + err = fd_bank_hash_stats_encode( &self->stats, ctx ); + if( FD_UNLIKELY( err ) ) return err; + return FD_BINCODE_SUCCESS; +} +int fd_bank_hash_info_decode_offsets( fd_bank_hash_info_off_t * self, fd_bincode_decode_ctx_t * ctx ) { + uchar const * data = ctx->data; + int err; + self->accounts_delta_hash_off = (uint)( (ulong)ctx->data - (ulong)data ); + err = fd_hash_decode_preflight( ctx ); + if( FD_UNLIKELY( err ) ) return err; + self->accounts_hash_off = (uint)( (ulong)ctx->data - (ulong)data ); + err = fd_hash_decode_preflight( ctx ); + if( FD_UNLIKELY( err ) ) return err; + self->stats_off = (uint)( (ulong)ctx->data - (ulong)data ); + err = fd_bank_hash_stats_decode_preflight( ctx ); + if( FD_UNLIKELY( err ) ) return err; + return FD_BINCODE_SUCCESS; +} +void fd_bank_hash_info_new(fd_bank_hash_info_t * self) { + fd_memset( self, 0, sizeof(fd_bank_hash_info_t) ); + fd_hash_new( &self->accounts_delta_hash ); + fd_hash_new( &self->accounts_hash ); + fd_bank_hash_stats_new( &self->stats ); +} +void fd_bank_hash_info_destroy( fd_bank_hash_info_t * self, fd_bincode_destroy_ctx_t * ctx ) { + fd_hash_destroy( &self->accounts_delta_hash, ctx ); + fd_hash_destroy( &self->accounts_hash, ctx ); + fd_bank_hash_stats_destroy( &self->stats, ctx ); +} + +ulong fd_bank_hash_info_footprint( void ){ return FD_BANK_HASH_INFO_FOOTPRINT; } +ulong fd_bank_hash_info_align( void ){ return FD_BANK_HASH_INFO_ALIGN; } + +void fd_bank_hash_info_walk( void * w, fd_bank_hash_info_t const * self, fd_types_walk_fn_t fun, const char *name, uint level ) { + fun( w, self, name, FD_FLAMENCO_TYPE_MAP, "fd_bank_hash_info", level++ ); + fd_hash_walk( w, &self->accounts_delta_hash, fun, "accounts_delta_hash", level ); + fd_hash_walk( w, &self->accounts_hash, fun, "accounts_hash", level ); + fd_bank_hash_stats_walk( w, &self->stats, fun, "stats", level ); + fun( w, self, name, FD_FLAMENCO_TYPE_MAP_END, "fd_bank_hash_info", level-- ); +} +ulong fd_bank_hash_info_size( fd_bank_hash_info_t const * self ) { + ulong size = 0; + size += fd_hash_size( &self->accounts_delta_hash ); + size += fd_hash_size( &self->accounts_hash ); + size += fd_bank_hash_stats_size( &self->stats ); + return size; +} + +int fd_slot_map_pair_decode( fd_slot_map_pair_t * self, fd_bincode_decode_ctx_t * ctx ) { + void const * data = ctx->data; + int err = fd_slot_map_pair_decode_preflight( ctx ); + if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; + ctx->data = data; + if( !fd_is_null_alloc_virtual( ctx->valloc ) ) { + fd_slot_map_pair_new( self ); + } + fd_slot_map_pair_decode_unsafe( self, ctx ); + return FD_BINCODE_SUCCESS; +} +int fd_slot_map_pair_decode_preflight( fd_bincode_decode_ctx_t * ctx ) { + int err; + err = fd_bincode_uint64_decode_preflight( ctx ); + if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; + err = fd_hash_decode_preflight( ctx ); + if( FD_UNLIKELY( err ) ) return err; + return FD_BINCODE_SUCCESS; +} +void fd_slot_map_pair_decode_unsafe( fd_slot_map_pair_t * self, fd_bincode_decode_ctx_t * ctx ) { + fd_bincode_uint64_decode_unsafe( &self->slot, ctx ); + fd_hash_decode_unsafe( &self->hash, ctx ); +} +int fd_slot_map_pair_encode( fd_slot_map_pair_t const * self, fd_bincode_encode_ctx_t * ctx ) { + int err; + err = fd_bincode_uint64_encode( self->slot, ctx ); + if( FD_UNLIKELY( err ) ) return err; + err = fd_hash_encode( &self->hash, ctx ); + if( FD_UNLIKELY( err ) ) return err; + return FD_BINCODE_SUCCESS; +} +int fd_slot_map_pair_decode_offsets( fd_slot_map_pair_off_t * self, fd_bincode_decode_ctx_t * ctx ) { + uchar const * data = ctx->data; int err; self->slot_off = (uint)( (ulong)ctx->data - (ulong)data ); err = fd_bincode_uint64_decode_preflight( ctx ); @@ -7409,136 +8423,435 @@ ulong fd_reward_info_size( fd_reward_info_t const * self ) { int fd_slot_lthash_decode( fd_slot_lthash_t * self, fd_bincode_decode_ctx_t * ctx ) { void const * data = ctx->data; - int err = fd_slot_lthash_decode_preflight( ctx ); + int err = fd_slot_lthash_decode_preflight( ctx ); + if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; + ctx->data = data; + if( !fd_is_null_alloc_virtual( ctx->valloc ) ) { + fd_slot_lthash_new( self ); + } + fd_slot_lthash_decode_unsafe( self, ctx ); + return FD_BINCODE_SUCCESS; +} +int fd_slot_lthash_decode_preflight( fd_bincode_decode_ctx_t * ctx ) { + int err; + err = fd_bincode_bytes_decode_preflight( 2048, ctx ); + if( FD_UNLIKELY( err ) ) return err; + return FD_BINCODE_SUCCESS; +} +void fd_slot_lthash_decode_unsafe( fd_slot_lthash_t * self, fd_bincode_decode_ctx_t * ctx ) { + fd_bincode_bytes_decode_unsafe( &self->lthash[0], sizeof(self->lthash), ctx ); +} +int fd_slot_lthash_encode( fd_slot_lthash_t const * self, fd_bincode_encode_ctx_t * ctx ) { + int err; + err = fd_bincode_bytes_encode( self->lthash, sizeof(self->lthash), ctx ); + if( FD_UNLIKELY( err ) ) return err; + return FD_BINCODE_SUCCESS; +} +enum { + fd_slot_lthash_lthash_TAG = (0 << 6) | FD_ARCHIVE_META_UCHAR2048, +}; +int fd_slot_lthash_decode_archival( fd_slot_lthash_t * self, fd_bincode_decode_ctx_t * ctx ) { + void const * data = ctx->data; + int err = fd_slot_lthash_decode_archival_preflight( ctx ); + if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; + ctx->data = data; + if( !fd_is_null_alloc_virtual( ctx->valloc ) ) { + fd_slot_lthash_new( self ); + } + fd_slot_lthash_decode_archival_unsafe( self, ctx ); + return FD_BINCODE_SUCCESS; +} +int fd_slot_lthash_decode_archival_preflight( fd_bincode_decode_ctx_t * ctx ) { + int err; + ushort tag = FD_ARCHIVE_META_SENTINAL; + void * offset = NULL; + for(;;) { + err = fd_bincode_uint16_decode( &tag, ctx ); + if( FD_UNLIKELY( err ) ) return err; + if( FD_UNLIKELY( tag == FD_ARCHIVE_META_SENTINAL ) ) break; + switch( tag ) { + case (ushort)fd_slot_lthash_lthash_TAG: { + err = fd_bincode_bytes_decode_preflight( 2048, ctx ); + if( FD_UNLIKELY( err ) ) return err; + break; + } + default: + err = fd_archive_decode_skip_field( ctx, tag ); + if( FD_UNLIKELY( err ) ) return err; + break; + } + } + return FD_BINCODE_SUCCESS; +} +void fd_slot_lthash_decode_archival_unsafe( fd_slot_lthash_t * self, fd_bincode_decode_ctx_t * ctx ) { + ushort tag = FD_ARCHIVE_META_SENTINAL; + void * offset = NULL; + for(;;) { + fd_bincode_uint16_decode( &tag, ctx ); + if( FD_UNLIKELY( tag == FD_ARCHIVE_META_SENTINAL ) ) break; + switch( tag ) { + case (ushort)fd_slot_lthash_lthash_TAG: { + fd_bincode_bytes_decode_unsafe( &self->lthash[0], sizeof(self->lthash), ctx ); + break; + } + default: + fd_archive_decode_skip_field( ctx, tag ); + break; + } + } +} +int fd_slot_lthash_encode_archival( fd_slot_lthash_t const * self, fd_bincode_encode_ctx_t * ctx ) { + int err; + void * offset = NULL; + err = fd_bincode_uint16_encode( (ushort)fd_slot_lthash_lthash_TAG, ctx ); + if( FD_UNLIKELY( err ) ) return err; + err = fd_bincode_bytes_encode( self->lthash, sizeof(self->lthash), ctx ); + if( FD_UNLIKELY( err ) ) return err; + err = fd_bincode_uint16_encode( FD_ARCHIVE_META_SENTINAL, ctx ); + if( FD_UNLIKELY( err ) ) return err; + return FD_BINCODE_SUCCESS; +} +int fd_slot_lthash_decode_offsets( fd_slot_lthash_off_t * self, fd_bincode_decode_ctx_t * ctx ) { + uchar const * data = ctx->data; + int err; + self->lthash_off = (uint)( (ulong)ctx->data - (ulong)data ); + err = fd_bincode_bytes_decode_preflight( 2048, ctx ); + if( FD_UNLIKELY( err ) ) return err; + return FD_BINCODE_SUCCESS; +} +void fd_slot_lthash_new(fd_slot_lthash_t * self) { + fd_memset( self, 0, sizeof(fd_slot_lthash_t) ); +} +void fd_slot_lthash_destroy( fd_slot_lthash_t * self, fd_bincode_destroy_ctx_t * ctx ) { +} + +ulong fd_slot_lthash_footprint( void ){ return FD_SLOT_LTHASH_FOOTPRINT; } +ulong fd_slot_lthash_align( void ){ return FD_SLOT_LTHASH_ALIGN; } + +void fd_slot_lthash_walk( void * w, fd_slot_lthash_t const * self, fd_types_walk_fn_t fun, const char *name, uint level ) { + fun( w, self, name, FD_FLAMENCO_TYPE_MAP, "fd_slot_lthash", level++ ); + fun( w, self->lthash, "lthash", FD_FLAMENCO_TYPE_HASH16384, "uchar[2048]", level ); + fun( w, self, name, FD_FLAMENCO_TYPE_MAP_END, "fd_slot_lthash", level-- ); +} +ulong fd_slot_lthash_size( fd_slot_lthash_t const * self ) { + ulong size = 0; + size += sizeof(char) * 2048; + return size; +} + +int fd_solana_manifest_decode( fd_solana_manifest_t * self, fd_bincode_decode_ctx_t * ctx ) { + void const * data = ctx->data; + int err = fd_solana_manifest_decode_preflight( ctx ); if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; ctx->data = data; if( !fd_is_null_alloc_virtual( ctx->valloc ) ) { - fd_slot_lthash_new( self ); + fd_solana_manifest_new( self ); } - fd_slot_lthash_decode_unsafe( self, ctx ); + fd_solana_manifest_decode_unsafe( self, ctx ); return FD_BINCODE_SUCCESS; } -int fd_slot_lthash_decode_preflight( fd_bincode_decode_ctx_t * ctx ) { +int fd_solana_manifest_decode_preflight( fd_bincode_decode_ctx_t * ctx ) { int err; - err = fd_bincode_bytes_decode_preflight( 2048, ctx ); + err = fd_deserializable_versioned_bank_decode_preflight( ctx ); + if( FD_UNLIKELY( err ) ) return err; + err = fd_solana_accounts_db_fields_decode_preflight( ctx ); if( FD_UNLIKELY( err ) ) return err; + err = fd_bincode_uint64_decode_preflight( ctx ); + if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; + if( ctx->data == ctx->dataend ) return FD_BINCODE_SUCCESS; + { + uchar o; + err = fd_bincode_bool_decode( &o, ctx ); + if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; + if( o ) { + err = fd_bank_incremental_snapshot_persistence_decode_preflight( ctx ); + if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; + } + } + if( ctx->data == ctx->dataend ) return FD_BINCODE_SUCCESS; + { + uchar o; + err = fd_bincode_bool_decode( &o, ctx ); + if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; + if( o ) { + err = fd_hash_decode_preflight( ctx ); + if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; + } + } + if( ctx->data == ctx->dataend ) return FD_BINCODE_SUCCESS; + ulong versioned_epoch_stakes_len; + err = fd_bincode_uint64_decode( &versioned_epoch_stakes_len, ctx ); + if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; + if( versioned_epoch_stakes_len ) { + for( ulong i=0; i < versioned_epoch_stakes_len; i++ ) { + err = fd_versioned_epoch_stakes_pair_decode_preflight( ctx ); + if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; + } + } + if( ctx->data == ctx->dataend ) return FD_BINCODE_SUCCESS; + { + uchar o; + err = fd_bincode_bool_decode( &o, ctx ); + if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; + if( o ) { + err = fd_slot_lthash_decode_preflight( ctx ); + if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; + } + } return FD_BINCODE_SUCCESS; } -void fd_slot_lthash_decode_unsafe( fd_slot_lthash_t * self, fd_bincode_decode_ctx_t * ctx ) { - fd_bincode_bytes_decode_unsafe( &self->lthash[0], sizeof(self->lthash), ctx ); +void fd_solana_manifest_decode_unsafe( fd_solana_manifest_t * self, fd_bincode_decode_ctx_t * ctx ) { + fd_deserializable_versioned_bank_decode_unsafe( &self->bank, ctx ); + fd_solana_accounts_db_fields_decode_unsafe( &self->accounts_db, ctx ); + fd_bincode_uint64_decode_unsafe( &self->lamports_per_signature, ctx ); + if( ctx->data == ctx->dataend ) return; + { + uchar o; + fd_bincode_bool_decode_unsafe( &o, ctx ); + if( o ) { + self->bank_incremental_snapshot_persistence = (fd_bank_incremental_snapshot_persistence_t *)fd_valloc_malloc( ctx->valloc, FD_BANK_INCREMENTAL_SNAPSHOT_PERSISTENCE_ALIGN, FD_BANK_INCREMENTAL_SNAPSHOT_PERSISTENCE_FOOTPRINT ); + fd_bank_incremental_snapshot_persistence_new( self->bank_incremental_snapshot_persistence ); + fd_bank_incremental_snapshot_persistence_decode_unsafe( self->bank_incremental_snapshot_persistence, ctx ); + } else + self->bank_incremental_snapshot_persistence = NULL; + } + if( ctx->data == ctx->dataend ) return; + { + uchar o; + fd_bincode_bool_decode_unsafe( &o, ctx ); + if( o ) { + self->epoch_account_hash = (fd_hash_t *)fd_valloc_malloc( ctx->valloc, FD_HASH_ALIGN, FD_HASH_FOOTPRINT ); + fd_hash_new( self->epoch_account_hash ); + fd_hash_decode_unsafe( self->epoch_account_hash, ctx ); + } else + self->epoch_account_hash = NULL; + } + if( ctx->data == ctx->dataend ) return; + fd_bincode_uint64_decode_unsafe( &self->versioned_epoch_stakes_len, ctx ); + if( self->versioned_epoch_stakes_len ) { + self->versioned_epoch_stakes = (fd_versioned_epoch_stakes_pair_t *)fd_valloc_malloc( ctx->valloc, FD_VERSIONED_EPOCH_STAKES_PAIR_ALIGN, FD_VERSIONED_EPOCH_STAKES_PAIR_FOOTPRINT*self->versioned_epoch_stakes_len ); + for( ulong i=0; i < self->versioned_epoch_stakes_len; i++ ) { + fd_versioned_epoch_stakes_pair_new( self->versioned_epoch_stakes + i ); + fd_versioned_epoch_stakes_pair_decode_unsafe( self->versioned_epoch_stakes + i, ctx ); + } + } else + self->versioned_epoch_stakes = NULL; + if( ctx->data == ctx->dataend ) return; + { + uchar o; + fd_bincode_bool_decode_unsafe( &o, ctx ); + if( o ) { + self->lthash = (fd_slot_lthash_t *)fd_valloc_malloc( ctx->valloc, FD_SLOT_LTHASH_ALIGN, FD_SLOT_LTHASH_FOOTPRINT ); + fd_slot_lthash_new( self->lthash ); + fd_slot_lthash_decode_unsafe( self->lthash, ctx ); + } else + self->lthash = NULL; + } } -int fd_slot_lthash_encode( fd_slot_lthash_t const * self, fd_bincode_encode_ctx_t * ctx ) { +int fd_solana_manifest_encode( fd_solana_manifest_t const * self, fd_bincode_encode_ctx_t * ctx ) { int err; - err = fd_bincode_bytes_encode( self->lthash, sizeof(self->lthash), ctx ); + err = fd_deserializable_versioned_bank_encode( &self->bank, ctx ); if( FD_UNLIKELY( err ) ) return err; - return FD_BINCODE_SUCCESS; -} -enum { - fd_slot_lthash_lthash_TAG = (0 << 6) | FD_ARCHIVE_META_UCHAR2048, -}; -int fd_slot_lthash_decode_archival( fd_slot_lthash_t * self, fd_bincode_decode_ctx_t * ctx ) { - void const * data = ctx->data; - int err = fd_slot_lthash_decode_archival_preflight( ctx ); - if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; - ctx->data = data; - if( !fd_is_null_alloc_virtual( ctx->valloc ) ) { - fd_slot_lthash_new( self ); + err = fd_solana_accounts_db_fields_encode( &self->accounts_db, ctx ); + if( FD_UNLIKELY( err ) ) return err; + err = fd_bincode_uint64_encode( self->lamports_per_signature, ctx ); + if( FD_UNLIKELY( err ) ) return err; + if( self->bank_incremental_snapshot_persistence != NULL ) { + err = fd_bincode_bool_encode( 1, ctx ); + if( FD_UNLIKELY( err ) ) return err; + err = fd_bank_incremental_snapshot_persistence_encode( self->bank_incremental_snapshot_persistence, ctx ); + if( FD_UNLIKELY( err ) ) return err; + } else { + err = fd_bincode_bool_encode( 0, ctx ); + if( FD_UNLIKELY( err ) ) return err; + } + if( self->epoch_account_hash != NULL ) { + err = fd_bincode_bool_encode( 1, ctx ); + if( FD_UNLIKELY( err ) ) return err; + err = fd_hash_encode( self->epoch_account_hash, ctx ); + if( FD_UNLIKELY( err ) ) return err; + } else { + err = fd_bincode_bool_encode( 0, ctx ); + if( FD_UNLIKELY( err ) ) return err; + } + err = fd_bincode_uint64_encode( self->versioned_epoch_stakes_len, ctx ); + if( FD_UNLIKELY(err) ) return err; + if( self->versioned_epoch_stakes_len ) { + for( ulong i=0; i < self->versioned_epoch_stakes_len; i++ ) { + err = fd_versioned_epoch_stakes_pair_encode( self->versioned_epoch_stakes + i, ctx ); + if( FD_UNLIKELY( err ) ) return err; + } + } + if( self->lthash != NULL ) { + err = fd_bincode_bool_encode( 1, ctx ); + if( FD_UNLIKELY( err ) ) return err; + err = fd_slot_lthash_encode( self->lthash, ctx ); + if( FD_UNLIKELY( err ) ) return err; + } else { + err = fd_bincode_bool_encode( 0, ctx ); + if( FD_UNLIKELY( err ) ) return err; } - fd_slot_lthash_decode_archival_unsafe( self, ctx ); return FD_BINCODE_SUCCESS; } -int fd_slot_lthash_decode_archival_preflight( fd_bincode_decode_ctx_t * ctx ) { +int fd_solana_manifest_decode_offsets( fd_solana_manifest_off_t * self, fd_bincode_decode_ctx_t * ctx ) { + uchar const * data = ctx->data; int err; - ushort tag = FD_ARCHIVE_META_SENTINAL; - void * offset = NULL; - for(;;) { - err = fd_bincode_uint16_decode( &tag, ctx ); + self->bank_off = (uint)( (ulong)ctx->data - (ulong)data ); + err = fd_deserializable_versioned_bank_decode_preflight( ctx ); if( FD_UNLIKELY( err ) ) return err; - if( FD_UNLIKELY( tag == FD_ARCHIVE_META_SENTINAL ) ) break; - switch( tag ) { - case (ushort)fd_slot_lthash_lthash_TAG: { - err = fd_bincode_bytes_decode_preflight( 2048, ctx ); + self->accounts_db_off = (uint)( (ulong)ctx->data - (ulong)data ); + err = fd_solana_accounts_db_fields_decode_preflight( ctx ); if( FD_UNLIKELY( err ) ) return err; - break; + self->lamports_per_signature_off = (uint)( (ulong)ctx->data - (ulong)data ); + err = fd_bincode_uint64_decode_preflight( ctx ); + if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; + self->bank_incremental_snapshot_persistence_off = (uint)( (ulong)ctx->data - (ulong)data ); + if( ctx->data == ctx->dataend ) return FD_BINCODE_SUCCESS; + { + uchar o; + err = fd_bincode_bool_decode( &o, ctx ); + if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; + if( o ) { + err = fd_bank_incremental_snapshot_persistence_decode_preflight( ctx ); + if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; + } } - default: - err = fd_archive_decode_skip_field( ctx, tag ); - if( FD_UNLIKELY( err ) ) return err; - break; + self->epoch_account_hash_off = (uint)( (ulong)ctx->data - (ulong)data ); + if( ctx->data == ctx->dataend ) return FD_BINCODE_SUCCESS; + { + uchar o; + err = fd_bincode_bool_decode( &o, ctx ); + if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; + if( o ) { + err = fd_hash_decode_preflight( ctx ); + if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; + } + } + self->versioned_epoch_stakes_off = (uint)( (ulong)ctx->data - (ulong)data ); + if( ctx->data == ctx->dataend ) return FD_BINCODE_SUCCESS; + ulong versioned_epoch_stakes_len; + err = fd_bincode_uint64_decode( &versioned_epoch_stakes_len, ctx ); + if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; + if( versioned_epoch_stakes_len ) { + for( ulong i=0; i < versioned_epoch_stakes_len; i++ ) { + err = fd_versioned_epoch_stakes_pair_decode_preflight( ctx ); + if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; + } } + self->lthash_off = (uint)( (ulong)ctx->data - (ulong)data ); + if( ctx->data == ctx->dataend ) return FD_BINCODE_SUCCESS; + { + uchar o; + err = fd_bincode_bool_decode( &o, ctx ); + if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; + if( o ) { + err = fd_slot_lthash_decode_preflight( ctx ); + if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; + } } return FD_BINCODE_SUCCESS; } -void fd_slot_lthash_decode_archival_unsafe( fd_slot_lthash_t * self, fd_bincode_decode_ctx_t * ctx ) { - ushort tag = FD_ARCHIVE_META_SENTINAL; - void * offset = NULL; - for(;;) { - fd_bincode_uint16_decode( &tag, ctx ); - if( FD_UNLIKELY( tag == FD_ARCHIVE_META_SENTINAL ) ) break; - switch( tag ) { - case (ushort)fd_slot_lthash_lthash_TAG: { - fd_bincode_bytes_decode_unsafe( &self->lthash[0], sizeof(self->lthash), ctx ); - break; +void fd_solana_manifest_new(fd_solana_manifest_t * self) { + fd_memset( self, 0, sizeof(fd_solana_manifest_t) ); + fd_deserializable_versioned_bank_new( &self->bank ); + fd_solana_accounts_db_fields_new( &self->accounts_db ); +} +void fd_solana_manifest_destroy( fd_solana_manifest_t * self, fd_bincode_destroy_ctx_t * ctx ) { + fd_deserializable_versioned_bank_destroy( &self->bank, ctx ); + fd_solana_accounts_db_fields_destroy( &self->accounts_db, ctx ); + if( self->bank_incremental_snapshot_persistence ) { + fd_bank_incremental_snapshot_persistence_destroy( self->bank_incremental_snapshot_persistence, ctx ); + fd_valloc_free( ctx->valloc, self->bank_incremental_snapshot_persistence ); + self->bank_incremental_snapshot_persistence = NULL; } - default: - fd_archive_decode_skip_field( ctx, tag ); - break; + if( self->epoch_account_hash ) { + fd_hash_destroy( self->epoch_account_hash, ctx ); + fd_valloc_free( ctx->valloc, self->epoch_account_hash ); + self->epoch_account_hash = NULL; + } + if( self->versioned_epoch_stakes ) { + for( ulong i=0; i < self->versioned_epoch_stakes_len; i++ ) + fd_versioned_epoch_stakes_pair_destroy( self->versioned_epoch_stakes + i, ctx ); + fd_valloc_free( ctx->valloc, self->versioned_epoch_stakes ); + self->versioned_epoch_stakes = NULL; } + if( self->lthash ) { + fd_slot_lthash_destroy( self->lthash, ctx ); + fd_valloc_free( ctx->valloc, self->lthash ); + self->lthash = NULL; } } -int fd_slot_lthash_encode_archival( fd_slot_lthash_t const * self, fd_bincode_encode_ctx_t * ctx ) { - int err; - void * offset = NULL; - err = fd_bincode_uint16_encode( (ushort)fd_slot_lthash_lthash_TAG, ctx ); - if( FD_UNLIKELY( err ) ) return err; - err = fd_bincode_bytes_encode( self->lthash, sizeof(self->lthash), ctx ); - if( FD_UNLIKELY( err ) ) return err; - err = fd_bincode_uint16_encode( FD_ARCHIVE_META_SENTINAL, ctx ); - if( FD_UNLIKELY( err ) ) return err; - return FD_BINCODE_SUCCESS; -} -int fd_slot_lthash_decode_offsets( fd_slot_lthash_off_t * self, fd_bincode_decode_ctx_t * ctx ) { - uchar const * data = ctx->data; - int err; - self->lthash_off = (uint)( (ulong)ctx->data - (ulong)data ); - err = fd_bincode_bytes_decode_preflight( 2048, ctx ); - if( FD_UNLIKELY( err ) ) return err; - return FD_BINCODE_SUCCESS; -} -void fd_slot_lthash_new(fd_slot_lthash_t * self) { - fd_memset( self, 0, sizeof(fd_slot_lthash_t) ); -} -void fd_slot_lthash_destroy( fd_slot_lthash_t * self, fd_bincode_destroy_ctx_t * ctx ) { -} -ulong fd_slot_lthash_footprint( void ){ return FD_SLOT_LTHASH_FOOTPRINT; } -ulong fd_slot_lthash_align( void ){ return FD_SLOT_LTHASH_ALIGN; } +ulong fd_solana_manifest_footprint( void ){ return FD_SOLANA_MANIFEST_FOOTPRINT; } +ulong fd_solana_manifest_align( void ){ return FD_SOLANA_MANIFEST_ALIGN; } -void fd_slot_lthash_walk( void * w, fd_slot_lthash_t const * self, fd_types_walk_fn_t fun, const char *name, uint level ) { - fun( w, self, name, FD_FLAMENCO_TYPE_MAP, "fd_slot_lthash", level++ ); - fun( w, self->lthash, "lthash", FD_FLAMENCO_TYPE_HASH16384, "uchar[2048]", level ); - fun( w, self, name, FD_FLAMENCO_TYPE_MAP_END, "fd_slot_lthash", level-- ); +void fd_solana_manifest_walk( void * w, fd_solana_manifest_t const * self, fd_types_walk_fn_t fun, const char *name, uint level ) { + fun( w, self, name, FD_FLAMENCO_TYPE_MAP, "fd_solana_manifest", level++ ); + fd_deserializable_versioned_bank_walk( w, &self->bank, fun, "bank", level ); + fd_solana_accounts_db_fields_walk( w, &self->accounts_db, fun, "accounts_db", level ); + fun( w, &self->lamports_per_signature, "lamports_per_signature", FD_FLAMENCO_TYPE_ULONG, "ulong", level ); + if( !self->bank_incremental_snapshot_persistence ) { + fun( w, NULL, "bank_incremental_snapshot_persistence", FD_FLAMENCO_TYPE_NULL, "bank_incremental_snapshot_persistence", level ); + } else { + fd_bank_incremental_snapshot_persistence_walk( w, self->bank_incremental_snapshot_persistence, fun, "bank_incremental_snapshot_persistence", level ); + } + if( !self->epoch_account_hash ) { + fun( w, NULL, "epoch_account_hash", FD_FLAMENCO_TYPE_NULL, "hash", level ); + } else { + fd_hash_walk( w, self->epoch_account_hash, fun, "epoch_account_hash", level ); + } + if( self->versioned_epoch_stakes_len ) { + fun( w, NULL, "versioned_epoch_stakes", FD_FLAMENCO_TYPE_ARR, "array", level++ ); + for( ulong i=0; i < self->versioned_epoch_stakes_len; i++ ) + fd_versioned_epoch_stakes_pair_walk(w, self->versioned_epoch_stakes + i, fun, "versioned_epoch_stakes_pair", level ); + fun( w, NULL, "versioned_epoch_stakes", FD_FLAMENCO_TYPE_ARR_END, "array", level-- ); + } + if( !self->lthash ) { + fun( w, NULL, "lthash", FD_FLAMENCO_TYPE_NULL, "slot_lthash", level ); + } else { + fd_slot_lthash_walk( w, self->lthash, fun, "lthash", level ); + } + fun( w, self, name, FD_FLAMENCO_TYPE_MAP_END, "fd_solana_manifest", level-- ); } -ulong fd_slot_lthash_size( fd_slot_lthash_t const * self ) { +ulong fd_solana_manifest_size( fd_solana_manifest_t const * self ) { ulong size = 0; - size += sizeof(char) * 2048; + size += fd_deserializable_versioned_bank_size( &self->bank ); + size += fd_solana_accounts_db_fields_size( &self->accounts_db ); + size += sizeof(ulong); + size += sizeof(char); + if( NULL != self->bank_incremental_snapshot_persistence ) { + size += fd_bank_incremental_snapshot_persistence_size( self->bank_incremental_snapshot_persistence ); + } + size += sizeof(char); + if( NULL != self->epoch_account_hash ) { + size += fd_hash_size( self->epoch_account_hash ); + } + do { + size += sizeof(ulong); + for( ulong i=0; i < self->versioned_epoch_stakes_len; i++ ) + size += fd_versioned_epoch_stakes_pair_size( self->versioned_epoch_stakes + i ); + } while(0); + size += sizeof(char); + if( NULL != self->lthash ) { + size += fd_slot_lthash_size( self->lthash ); + } return size; } -int fd_solana_manifest_decode( fd_solana_manifest_t * self, fd_bincode_decode_ctx_t * ctx ) { +int fd_solana_manifest_serializable_decode( fd_solana_manifest_serializable_t * self, fd_bincode_decode_ctx_t * ctx ) { void const * data = ctx->data; - int err = fd_solana_manifest_decode_preflight( ctx ); + int err = fd_solana_manifest_serializable_decode_preflight( ctx ); if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; ctx->data = data; if( !fd_is_null_alloc_virtual( ctx->valloc ) ) { - fd_solana_manifest_new( self ); + fd_solana_manifest_serializable_new( self ); } - fd_solana_manifest_decode_unsafe( self, ctx ); + fd_solana_manifest_serializable_decode_unsafe( self, ctx ); return FD_BINCODE_SUCCESS; } -int fd_solana_manifest_decode_preflight( fd_bincode_decode_ctx_t * ctx ) { +int fd_solana_manifest_serializable_decode_preflight( fd_bincode_decode_ctx_t * ctx ) { int err; - err = fd_deserializable_versioned_bank_decode_preflight( ctx ); + err = fd_serializable_versioned_bank_decode_preflight( ctx ); if( FD_UNLIKELY( err ) ) return err; err = fd_solana_accounts_db_fields_decode_preflight( ctx ); if( FD_UNLIKELY( err ) ) return err; @@ -7574,20 +8887,10 @@ int fd_solana_manifest_decode_preflight( fd_bincode_decode_ctx_t * ctx ) { if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; } } - if( ctx->data == ctx->dataend ) return FD_BINCODE_SUCCESS; - { - uchar o; - err = fd_bincode_bool_decode( &o, ctx ); - if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; - if( o ) { - err = fd_slot_lthash_decode_preflight( ctx ); - if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; - } - } return FD_BINCODE_SUCCESS; } -void fd_solana_manifest_decode_unsafe( fd_solana_manifest_t * self, fd_bincode_decode_ctx_t * ctx ) { - fd_deserializable_versioned_bank_decode_unsafe( &self->bank, ctx ); +void fd_solana_manifest_serializable_decode_unsafe( fd_solana_manifest_serializable_t * self, fd_bincode_decode_ctx_t * ctx ) { + fd_serializable_versioned_bank_decode_unsafe( &self->bank, ctx ); fd_solana_accounts_db_fields_decode_unsafe( &self->accounts_db, ctx ); fd_bincode_uint64_decode_unsafe( &self->lamports_per_signature, ctx ); if( ctx->data == ctx->dataend ) return; @@ -7622,21 +8925,10 @@ void fd_solana_manifest_decode_unsafe( fd_solana_manifest_t * self, fd_bincode_d } } else self->versioned_epoch_stakes = NULL; - if( ctx->data == ctx->dataend ) return; - { - uchar o; - fd_bincode_bool_decode_unsafe( &o, ctx ); - if( o ) { - self->lthash = (fd_slot_lthash_t *)fd_valloc_malloc( ctx->valloc, FD_SLOT_LTHASH_ALIGN, FD_SLOT_LTHASH_FOOTPRINT ); - fd_slot_lthash_new( self->lthash ); - fd_slot_lthash_decode_unsafe( self->lthash, ctx ); - } else - self->lthash = NULL; - } } -int fd_solana_manifest_encode( fd_solana_manifest_t const * self, fd_bincode_encode_ctx_t * ctx ) { +int fd_solana_manifest_serializable_encode( fd_solana_manifest_serializable_t const * self, fd_bincode_encode_ctx_t * ctx ) { int err; - err = fd_deserializable_versioned_bank_encode( &self->bank, ctx ); + err = fd_serializable_versioned_bank_encode( &self->bank, ctx ); if( FD_UNLIKELY( err ) ) return err; err = fd_solana_accounts_db_fields_encode( &self->accounts_db, ctx ); if( FD_UNLIKELY( err ) ) return err; @@ -7668,22 +8960,13 @@ int fd_solana_manifest_encode( fd_solana_manifest_t const * self, fd_bincode_enc if( FD_UNLIKELY( err ) ) return err; } } - if( self->lthash != NULL ) { - err = fd_bincode_bool_encode( 1, ctx ); - if( FD_UNLIKELY( err ) ) return err; - err = fd_slot_lthash_encode( self->lthash, ctx ); - if( FD_UNLIKELY( err ) ) return err; - } else { - err = fd_bincode_bool_encode( 0, ctx ); - if( FD_UNLIKELY( err ) ) return err; - } return FD_BINCODE_SUCCESS; } -int fd_solana_manifest_decode_offsets( fd_solana_manifest_off_t * self, fd_bincode_decode_ctx_t * ctx ) { +int fd_solana_manifest_serializable_decode_offsets( fd_solana_manifest_serializable_off_t * self, fd_bincode_decode_ctx_t * ctx ) { uchar const * data = ctx->data; int err; self->bank_off = (uint)( (ulong)ctx->data - (ulong)data ); - err = fd_deserializable_versioned_bank_decode_preflight( ctx ); + err = fd_serializable_versioned_bank_decode_preflight( ctx ); if( FD_UNLIKELY( err ) ) return err; self->accounts_db_off = (uint)( (ulong)ctx->data - (ulong)data ); err = fd_solana_accounts_db_fields_decode_preflight( ctx ); @@ -7724,26 +9007,15 @@ int fd_solana_manifest_decode_offsets( fd_solana_manifest_off_t * self, fd_binco if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; } } - self->lthash_off = (uint)( (ulong)ctx->data - (ulong)data ); - if( ctx->data == ctx->dataend ) return FD_BINCODE_SUCCESS; - { - uchar o; - err = fd_bincode_bool_decode( &o, ctx ); - if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; - if( o ) { - err = fd_slot_lthash_decode_preflight( ctx ); - if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; - } - } return FD_BINCODE_SUCCESS; } -void fd_solana_manifest_new(fd_solana_manifest_t * self) { - fd_memset( self, 0, sizeof(fd_solana_manifest_t) ); - fd_deserializable_versioned_bank_new( &self->bank ); +void fd_solana_manifest_serializable_new(fd_solana_manifest_serializable_t * self) { + fd_memset( self, 0, sizeof(fd_solana_manifest_serializable_t) ); + fd_serializable_versioned_bank_new( &self->bank ); fd_solana_accounts_db_fields_new( &self->accounts_db ); } -void fd_solana_manifest_destroy( fd_solana_manifest_t * self, fd_bincode_destroy_ctx_t * ctx ) { - fd_deserializable_versioned_bank_destroy( &self->bank, ctx ); +void fd_solana_manifest_serializable_destroy( fd_solana_manifest_serializable_t * self, fd_bincode_destroy_ctx_t * ctx ) { + fd_serializable_versioned_bank_destroy( &self->bank, ctx ); fd_solana_accounts_db_fields_destroy( &self->accounts_db, ctx ); if( self->bank_incremental_snapshot_persistence ) { fd_bank_incremental_snapshot_persistence_destroy( self->bank_incremental_snapshot_persistence, ctx ); @@ -7761,19 +9033,14 @@ void fd_solana_manifest_destroy( fd_solana_manifest_t * self, fd_bincode_destroy fd_valloc_free( ctx->valloc, self->versioned_epoch_stakes ); self->versioned_epoch_stakes = NULL; } - if( self->lthash ) { - fd_slot_lthash_destroy( self->lthash, ctx ); - fd_valloc_free( ctx->valloc, self->lthash ); - self->lthash = NULL; - } } -ulong fd_solana_manifest_footprint( void ){ return FD_SOLANA_MANIFEST_FOOTPRINT; } -ulong fd_solana_manifest_align( void ){ return FD_SOLANA_MANIFEST_ALIGN; } +ulong fd_solana_manifest_serializable_footprint( void ){ return FD_SOLANA_MANIFEST_SERIALIZABLE_FOOTPRINT; } +ulong fd_solana_manifest_serializable_align( void ){ return FD_SOLANA_MANIFEST_SERIALIZABLE_ALIGN; } -void fd_solana_manifest_walk( void * w, fd_solana_manifest_t const * self, fd_types_walk_fn_t fun, const char *name, uint level ) { - fun( w, self, name, FD_FLAMENCO_TYPE_MAP, "fd_solana_manifest", level++ ); - fd_deserializable_versioned_bank_walk( w, &self->bank, fun, "bank", level ); +void fd_solana_manifest_serializable_walk( void * w, fd_solana_manifest_serializable_t const * self, fd_types_walk_fn_t fun, const char *name, uint level ) { + fun( w, self, name, FD_FLAMENCO_TYPE_MAP, "fd_solana_manifest_serializable", level++ ); + fd_serializable_versioned_bank_walk( w, &self->bank, fun, "bank", level ); fd_solana_accounts_db_fields_walk( w, &self->accounts_db, fun, "accounts_db", level ); fun( w, &self->lamports_per_signature, "lamports_per_signature", FD_FLAMENCO_TYPE_ULONG, "ulong", level ); if( !self->bank_incremental_snapshot_persistence ) { @@ -7792,16 +9059,11 @@ void fd_solana_manifest_walk( void * w, fd_solana_manifest_t const * self, fd_ty fd_versioned_epoch_stakes_pair_walk(w, self->versioned_epoch_stakes + i, fun, "versioned_epoch_stakes_pair", level ); fun( w, NULL, "versioned_epoch_stakes", FD_FLAMENCO_TYPE_ARR_END, "array", level-- ); } - if( !self->lthash ) { - fun( w, NULL, "lthash", FD_FLAMENCO_TYPE_NULL, "slot_lthash", level ); - } else { - fd_slot_lthash_walk( w, self->lthash, fun, "lthash", level ); - } - fun( w, self, name, FD_FLAMENCO_TYPE_MAP_END, "fd_solana_manifest", level-- ); + fun( w, self, name, FD_FLAMENCO_TYPE_MAP_END, "fd_solana_manifest_serializable", level-- ); } -ulong fd_solana_manifest_size( fd_solana_manifest_t const * self ) { +ulong fd_solana_manifest_serializable_size( fd_solana_manifest_serializable_t const * self ) { ulong size = 0; - size += fd_deserializable_versioned_bank_size( &self->bank ); + size += fd_serializable_versioned_bank_size( &self->bank ); size += fd_solana_accounts_db_fields_size( &self->accounts_db ); size += sizeof(ulong); size += sizeof(char); @@ -7817,10 +9079,6 @@ ulong fd_solana_manifest_size( fd_solana_manifest_t const * self ) { for( ulong i=0; i < self->versioned_epoch_stakes_len; i++ ) size += fd_versioned_epoch_stakes_pair_size( self->versioned_epoch_stakes + i ); } while(0); - size += sizeof(char); - if( NULL != self->lthash ) { - size += fd_slot_lthash_size( self->lthash ); - } return size; } @@ -14469,6 +15727,12 @@ int fd_slot_bank_decode_preflight( fd_bincode_decode_ctx_t * ctx ) { if( FD_UNLIKELY( err ) ) return err; err = fd_block_hash_queue_decode_preflight( ctx ); if( FD_UNLIKELY( err ) ) return err; + err = fd_hash_decode_preflight( ctx ); + if( FD_UNLIKELY( err ) ) return err; + err = fd_bincode_uint64_decode_preflight( ctx ); + if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; + err = fd_bincode_uint64_decode_preflight( ctx ); + if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; { uchar o; err = fd_bincode_bool_decode( &o, ctx ); @@ -14478,6 +15742,8 @@ int fd_slot_bank_decode_preflight( fd_bincode_decode_ctx_t * ctx ) { if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; } } + err = fd_hard_forks_decode_preflight( ctx ); + if( FD_UNLIKELY( err ) ) return err; return FD_BINCODE_SUCCESS; } void fd_slot_bank_decode_unsafe( fd_slot_bank_t * self, fd_bincode_decode_ctx_t * ctx ) { @@ -14503,6 +15769,9 @@ void fd_slot_bank_decode_unsafe( fd_slot_bank_t * self, fd_bincode_decode_ctx_t fd_bincode_uint64_decode_unsafe( &self->transaction_count, ctx ); fd_slot_lthash_decode_unsafe( &self->lthash, ctx ); fd_block_hash_queue_decode_unsafe( &self->block_hash_queue, ctx ); + fd_hash_decode_unsafe( &self->prev_banks_hash, ctx ); + fd_bincode_uint64_decode_unsafe( &self->parent_signature_cnt, ctx ); + fd_bincode_uint64_decode_unsafe( &self->tick_height, ctx ); { uchar o; fd_bincode_bool_decode_unsafe( &o, ctx ); @@ -14511,6 +15780,7 @@ void fd_slot_bank_decode_unsafe( fd_slot_bank_t * self, fd_bincode_decode_ctx_t fd_bincode_uint64_decode_unsafe( &self->use_preceeding_epoch_stakes, ctx ); } } + fd_hard_forks_decode_unsafe( &self->hard_forks, ctx ); } int fd_slot_bank_encode( fd_slot_bank_t const * self, fd_bincode_encode_ctx_t * ctx ) { int err; @@ -14558,12 +15828,20 @@ int fd_slot_bank_encode( fd_slot_bank_t const * self, fd_bincode_encode_ctx_t * if( FD_UNLIKELY( err ) ) return err; err = fd_block_hash_queue_encode( &self->block_hash_queue, ctx ); if( FD_UNLIKELY( err ) ) return err; + err = fd_hash_encode( &self->prev_banks_hash, ctx ); + if( FD_UNLIKELY( err ) ) return err; + err = fd_bincode_uint64_encode( self->parent_signature_cnt, ctx ); + if( FD_UNLIKELY( err ) ) return err; + err = fd_bincode_uint64_encode( self->tick_height, ctx ); + if( FD_UNLIKELY( err ) ) return err; err = fd_bincode_bool_encode( self->has_use_preceeding_epoch_stakes, ctx ); if( FD_UNLIKELY( err ) ) return err; if( self->has_use_preceeding_epoch_stakes ) { err = fd_bincode_uint64_encode( self->use_preceeding_epoch_stakes, ctx ); if( FD_UNLIKELY( err ) ) return err; } + err = fd_hard_forks_encode( &self->hard_forks, ctx ); + if( FD_UNLIKELY( err ) ) return err; return FD_BINCODE_SUCCESS; } enum { @@ -14589,7 +15867,11 @@ enum { fd_slot_bank_transaction_count_TAG = (19 << 6) | FD_ARCHIVE_META_ULONG, fd_slot_bank_lthash_TAG = (20 << 6) | FD_ARCHIVE_META_STRUCT, fd_slot_bank_block_hash_queue_TAG = (21 << 6) | FD_ARCHIVE_META_STRUCT, - fd_slot_bank_use_preceeding_epoch_stakes_TAG = (22 << 6) | FD_ARCHIVE_META_OPTION, + fd_slot_bank_prev_banks_hash_TAG = (22 << 6) | FD_ARCHIVE_META_STRUCT, + fd_slot_bank_parent_signature_cnt_TAG = (23 << 6) | FD_ARCHIVE_META_ULONG, + fd_slot_bank_tick_height_TAG = (24 << 6) | FD_ARCHIVE_META_ULONG, + fd_slot_bank_use_preceeding_epoch_stakes_TAG = (25 << 6) | FD_ARCHIVE_META_OPTION, + fd_slot_bank_hard_forks_TAG = (26 << 6) | FD_ARCHIVE_META_STRUCT, }; int fd_slot_bank_decode_archival( fd_slot_bank_t * self, fd_bincode_decode_ctx_t * ctx ) { void const * data = ctx->data; @@ -14769,6 +16051,25 @@ int fd_slot_bank_decode_archival_preflight( fd_bincode_decode_ctx_t * ctx ) { if( FD_UNLIKELY( err ) ) return err; break; } + case (ushort)fd_slot_bank_prev_banks_hash_TAG: { + err = fd_archive_decode_setup_length( ctx, &offset ); + if( FD_UNLIKELY( err ) ) return err; + err = fd_hash_decode_archival_preflight( ctx ); + if( FD_UNLIKELY( err ) ) return err; + err = fd_archive_decode_check_length( ctx, offset ); + if( FD_UNLIKELY( err ) ) return err; + break; + } + case (ushort)fd_slot_bank_parent_signature_cnt_TAG: { + err = fd_bincode_uint64_decode_preflight( ctx ); + if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; + break; + } + case (ushort)fd_slot_bank_tick_height_TAG: { + err = fd_bincode_uint64_decode_preflight( ctx ); + if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; + break; + } case (ushort)fd_slot_bank_use_preceeding_epoch_stakes_TAG: { err = fd_archive_decode_setup_length( ctx, &offset ); if( FD_UNLIKELY( err ) ) return err; @@ -14785,6 +16086,15 @@ int fd_slot_bank_decode_archival_preflight( fd_bincode_decode_ctx_t * ctx ) { if( FD_UNLIKELY( err ) ) return err; break; } + case (ushort)fd_slot_bank_hard_forks_TAG: { + err = fd_archive_decode_setup_length( ctx, &offset ); + if( FD_UNLIKELY( err ) ) return err; + err = fd_hard_forks_decode_archival_preflight( ctx ); + if( FD_UNLIKELY( err ) ) return err; + err = fd_archive_decode_check_length( ctx, offset ); + if( FD_UNLIKELY( err ) ) return err; + break; + } default: err = fd_archive_decode_skip_field( ctx, tag ); if( FD_UNLIKELY( err ) ) return err; @@ -14900,6 +16210,19 @@ void fd_slot_bank_decode_archival_unsafe( fd_slot_bank_t * self, fd_bincode_deco fd_block_hash_queue_decode_archival_unsafe( &self->block_hash_queue, ctx ); break; } + case (ushort)fd_slot_bank_prev_banks_hash_TAG: { + fd_archive_decode_setup_length( ctx, &offset ); + fd_hash_decode_archival_unsafe( &self->prev_banks_hash, ctx ); + break; + } + case (ushort)fd_slot_bank_parent_signature_cnt_TAG: { + fd_bincode_uint64_decode_unsafe( &self->parent_signature_cnt, ctx ); + break; + } + case (ushort)fd_slot_bank_tick_height_TAG: { + fd_bincode_uint64_decode_unsafe( &self->tick_height, ctx ); + break; + } case (ushort)fd_slot_bank_use_preceeding_epoch_stakes_TAG: { fd_archive_decode_setup_length( ctx, &offset ); { @@ -14912,6 +16235,11 @@ void fd_slot_bank_decode_archival_unsafe( fd_slot_bank_t * self, fd_bincode_deco } break; } + case (ushort)fd_slot_bank_hard_forks_TAG: { + fd_archive_decode_setup_length( ctx, &offset ); + fd_hard_forks_decode_archival_unsafe( &self->hard_forks, ctx ); + break; + } default: fd_archive_decode_skip_field( ctx, tag ); break; @@ -15057,6 +16385,22 @@ int fd_slot_bank_encode_archival( fd_slot_bank_t const * self, fd_bincode_encode if( FD_UNLIKELY( err ) ) return err; err = fd_archive_encode_set_length( ctx, offset ); if( FD_UNLIKELY( err ) ) return err; + err = fd_bincode_uint16_encode( (ushort)fd_slot_bank_prev_banks_hash_TAG, ctx ); + if( FD_UNLIKELY( err ) ) return err; + err = fd_archive_encode_setup_length( ctx, &offset ); + if( FD_UNLIKELY( err ) ) return err; + err = fd_hash_encode_archival( &self->prev_banks_hash, ctx ); + if( FD_UNLIKELY( err ) ) return err; + err = fd_archive_encode_set_length( ctx, offset ); + if( FD_UNLIKELY( err ) ) return err; + err = fd_bincode_uint16_encode( (ushort)fd_slot_bank_parent_signature_cnt_TAG, ctx ); + if( FD_UNLIKELY( err ) ) return err; + err = fd_bincode_uint64_encode( self->parent_signature_cnt, ctx ); + if( FD_UNLIKELY( err ) ) return err; + err = fd_bincode_uint16_encode( (ushort)fd_slot_bank_tick_height_TAG, ctx ); + if( FD_UNLIKELY( err ) ) return err; + err = fd_bincode_uint64_encode( self->tick_height, ctx ); + if( FD_UNLIKELY( err ) ) return err; err = fd_bincode_uint16_encode( (ushort)fd_slot_bank_use_preceeding_epoch_stakes_TAG, ctx ); if( FD_UNLIKELY( err ) ) return err; err = fd_archive_encode_setup_length( ctx, &offset ); @@ -15069,6 +16413,14 @@ int fd_slot_bank_encode_archival( fd_slot_bank_t const * self, fd_bincode_encode } err = fd_archive_encode_set_length( ctx, offset ); if( FD_UNLIKELY( err ) ) return err; + err = fd_bincode_uint16_encode( (ushort)fd_slot_bank_hard_forks_TAG, ctx ); + if( FD_UNLIKELY( err ) ) return err; + err = fd_archive_encode_setup_length( ctx, &offset ); + if( FD_UNLIKELY( err ) ) return err; + err = fd_hard_forks_encode_archival( &self->hard_forks, ctx ); + if( FD_UNLIKELY( err ) ) return err; + err = fd_archive_encode_set_length( ctx, offset ); + if( FD_UNLIKELY( err ) ) return err; err = fd_bincode_uint16_encode( FD_ARCHIVE_META_SENTINAL, ctx ); if( FD_UNLIKELY( err ) ) return err; return FD_BINCODE_SUCCESS; @@ -15142,6 +16494,15 @@ int fd_slot_bank_decode_offsets( fd_slot_bank_off_t * self, fd_bincode_decode_ct self->block_hash_queue_off = (uint)( (ulong)ctx->data - (ulong)data ); err = fd_block_hash_queue_decode_preflight( ctx ); if( FD_UNLIKELY( err ) ) return err; + self->prev_banks_hash_off = (uint)( (ulong)ctx->data - (ulong)data ); + err = fd_hash_decode_preflight( ctx ); + if( FD_UNLIKELY( err ) ) return err; + self->parent_signature_cnt_off = (uint)( (ulong)ctx->data - (ulong)data ); + err = fd_bincode_uint64_decode_preflight( ctx ); + if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; + self->tick_height_off = (uint)( (ulong)ctx->data - (ulong)data ); + err = fd_bincode_uint64_decode_preflight( ctx ); + if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; self->use_preceeding_epoch_stakes_off = (uint)( (ulong)ctx->data - (ulong)data ); { uchar o; @@ -15152,6 +16513,9 @@ int fd_slot_bank_decode_offsets( fd_slot_bank_off_t * self, fd_bincode_decode_ct if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; } } + self->hard_forks_off = (uint)( (ulong)ctx->data - (ulong)data ); + err = fd_hard_forks_decode_preflight( ctx ); + if( FD_UNLIKELY( err ) ) return err; return FD_BINCODE_SUCCESS; } void fd_slot_bank_new(fd_slot_bank_t * self) { @@ -15168,6 +16532,8 @@ void fd_slot_bank_new(fd_slot_bank_t * self) { fd_vote_accounts_new( &self->vote_account_keys ); fd_slot_lthash_new( &self->lthash ); fd_block_hash_queue_new( &self->block_hash_queue ); + fd_hash_new( &self->prev_banks_hash ); + fd_hard_forks_new( &self->hard_forks ); } void fd_slot_bank_destroy( fd_slot_bank_t * self, fd_bincode_destroy_ctx_t * ctx ) { fd_recent_block_hashes_destroy( &self->recent_block_hashes, ctx ); @@ -15182,9 +16548,11 @@ void fd_slot_bank_destroy( fd_slot_bank_t * self, fd_bincode_destroy_ctx_t * ctx fd_vote_accounts_destroy( &self->vote_account_keys, ctx ); fd_slot_lthash_destroy( &self->lthash, ctx ); fd_block_hash_queue_destroy( &self->block_hash_queue, ctx ); + fd_hash_destroy( &self->prev_banks_hash, ctx ); if( self->has_use_preceeding_epoch_stakes ) { self->has_use_preceeding_epoch_stakes = 0; } + fd_hard_forks_destroy( &self->hard_forks, ctx ); } ulong fd_slot_bank_footprint( void ){ return FD_SLOT_BANK_FOOTPRINT; } @@ -15214,11 +16582,15 @@ void fd_slot_bank_walk( void * w, fd_slot_bank_t const * self, fd_types_walk_fn_ fun( w, &self->transaction_count, "transaction_count", FD_FLAMENCO_TYPE_ULONG, "ulong", level ); fd_slot_lthash_walk( w, &self->lthash, fun, "lthash", level ); fd_block_hash_queue_walk( w, &self->block_hash_queue, fun, "block_hash_queue", level ); + fd_hash_walk( w, &self->prev_banks_hash, fun, "prev_banks_hash", level ); + fun( w, &self->parent_signature_cnt, "parent_signature_cnt", FD_FLAMENCO_TYPE_ULONG, "ulong", level ); + fun( w, &self->tick_height, "tick_height", FD_FLAMENCO_TYPE_ULONG, "ulong", level ); if( !self->has_use_preceeding_epoch_stakes ) { fun( w, NULL, "use_preceeding_epoch_stakes", FD_FLAMENCO_TYPE_NULL, "ulong", level ); } else { fun( w, &self->use_preceeding_epoch_stakes, "use_preceeding_epoch_stakes", FD_FLAMENCO_TYPE_ULONG, "ulong", level ); } + fd_hard_forks_walk( w, &self->hard_forks, fun, "hard_forks", level ); fun( w, self, name, FD_FLAMENCO_TYPE_MAP_END, "fd_slot_bank", level-- ); } ulong fd_slot_bank_size( fd_slot_bank_t const * self ) { @@ -15245,10 +16617,14 @@ ulong fd_slot_bank_size( fd_slot_bank_t const * self ) { size += sizeof(ulong); size += fd_slot_lthash_size( &self->lthash ); size += fd_block_hash_queue_size( &self->block_hash_queue ); + size += fd_hash_size( &self->prev_banks_hash ); + size += sizeof(ulong); + size += sizeof(ulong); size += sizeof(char); if( self->has_use_preceeding_epoch_stakes ) { size += sizeof(ulong); } + size += fd_hard_forks_size( &self->hard_forks ); return size; } @@ -32657,6 +34033,13 @@ ulong fd_duplicate_slot_proof_size( fd_duplicate_slot_proof_t const * self ) { long fd_hash_hash_age_pair_t_map_compare( fd_hash_hash_age_pair_t_mapnode_t * left, fd_hash_hash_age_pair_t_mapnode_t * right ) { return memcmp( left->elem.key.uc, right->elem.key.uc, sizeof(right->elem.key) ); } +#define REDBLK_T fd_vote_accounts_pair_serializable_t_mapnode_t +#define REDBLK_NAME fd_vote_accounts_pair_serializable_t_map +#define REDBLK_IMPL_STYLE 2 +#include "../../util/tmpl/fd_redblack.c" +long fd_vote_accounts_pair_serializable_t_map_compare( fd_vote_accounts_pair_serializable_t_mapnode_t * left, fd_vote_accounts_pair_serializable_t_mapnode_t * right ) { + return memcmp( left->elem.key.uc, right->elem.key.uc, sizeof(right->elem.key) ); +} #define REDBLK_T fd_vote_accounts_pair_t_mapnode_t #define REDBLK_NAME fd_vote_accounts_pair_t_map #define REDBLK_IMPL_STYLE 2 diff --git a/src/flamenco/types/fd_types.h b/src/flamenco/types/fd_types.h index 4e83f0ce6b..ebc7761a9a 100644 --- a/src/flamenco/types/fd_types.h +++ b/src/flamenco/types/fd_types.h @@ -456,6 +456,59 @@ typedef struct fd_vote_accounts_pair_off fd_vote_accounts_pair_off_t; #define FD_VOTE_ACCOUNTS_PAIR_OFF_FOOTPRINT sizeof(fd_vote_accounts_pair_off_t) #define FD_VOTE_ACCOUNTS_PAIR_OFF_ALIGN (8UL) +/* Encoded Size: Dynamic */ +struct __attribute__((aligned(8UL))) fd_vote_accounts_pair_serializable { + fd_pubkey_t key; + ulong stake; + fd_solana_account_t value; +}; +typedef struct fd_vote_accounts_pair_serializable fd_vote_accounts_pair_serializable_t; +#define FD_VOTE_ACCOUNTS_PAIR_SERIALIZABLE_FOOTPRINT sizeof(fd_vote_accounts_pair_serializable_t) +#define FD_VOTE_ACCOUNTS_PAIR_SERIALIZABLE_ALIGN (8UL) + +struct __attribute__((aligned(8UL))) fd_vote_accounts_pair_serializable_off { + uint key_off; + uint stake_off; + uint value_off; +}; +typedef struct fd_vote_accounts_pair_serializable_off fd_vote_accounts_pair_serializable_off_t; +#define FD_VOTE_ACCOUNTS_PAIR_SERIALIZABLE_OFF_FOOTPRINT sizeof(fd_vote_accounts_pair_serializable_off_t) +#define FD_VOTE_ACCOUNTS_PAIR_SERIALIZABLE_OFF_ALIGN (8UL) + +typedef struct fd_vote_accounts_pair_serializable_t_mapnode fd_vote_accounts_pair_serializable_t_mapnode_t; +#define REDBLK_T fd_vote_accounts_pair_serializable_t_mapnode_t +#define REDBLK_NAME fd_vote_accounts_pair_serializable_t_map +#define REDBLK_IMPL_STYLE 1 +#include "../../util/tmpl/fd_redblack.c" +struct fd_vote_accounts_pair_serializable_t_mapnode { + fd_vote_accounts_pair_serializable_t elem; + ulong redblack_parent; + ulong redblack_left; + ulong redblack_right; + int redblack_color; +}; +static inline fd_vote_accounts_pair_serializable_t_mapnode_t * +fd_vote_accounts_pair_serializable_t_map_alloc( fd_valloc_t valloc, ulong len ) { + if( FD_UNLIKELY( 0 == len ) ) len = 1; // prevent underflow + void * mem = fd_valloc_malloc( valloc, fd_vote_accounts_pair_serializable_t_map_align(), fd_vote_accounts_pair_serializable_t_map_footprint(len)); + return fd_vote_accounts_pair_serializable_t_map_join(fd_vote_accounts_pair_serializable_t_map_new(mem, len)); +} +/* Encoded Size: Dynamic */ +struct __attribute__((aligned(8UL))) fd_vote_accounts_serializable { + fd_vote_accounts_pair_serializable_t_mapnode_t * vote_accounts_pool; + fd_vote_accounts_pair_serializable_t_mapnode_t * vote_accounts_root; +}; +typedef struct fd_vote_accounts_serializable fd_vote_accounts_serializable_t; +#define FD_VOTE_ACCOUNTS_SERIALIZABLE_FOOTPRINT sizeof(fd_vote_accounts_serializable_t) +#define FD_VOTE_ACCOUNTS_SERIALIZABLE_ALIGN (8UL) + +struct __attribute__((aligned(8UL))) fd_vote_accounts_serializable_off { + uint vote_accounts_off; +}; +typedef struct fd_vote_accounts_serializable_off fd_vote_accounts_serializable_off_t; +#define FD_VOTE_ACCOUNTS_SERIALIZABLE_OFF_FOOTPRINT sizeof(fd_vote_accounts_serializable_off_t) +#define FD_VOTE_ACCOUNTS_SERIALIZABLE_OFF_ALIGN (8UL) + typedef struct fd_vote_accounts_pair_t_mapnode fd_vote_accounts_pair_t_mapnode_t; #define REDBLK_T fd_vote_accounts_pair_t_mapnode_t #define REDBLK_NAME fd_vote_accounts_pair_t_map @@ -745,6 +798,31 @@ typedef struct fd_stakes_off fd_stakes_off_t; #define FD_STAKES_OFF_FOOTPRINT sizeof(fd_stakes_off_t) #define FD_STAKES_OFF_ALIGN (8UL) +/* https://github.com/anza-xyz/agave/blob/beb3f582f784a96e59e06ef8f34e855258bcd98c/runtime/src/stakes.rs#L202 */ +/* Encoded Size: Dynamic */ +struct __attribute__((aligned(8UL))) fd_stakes_serializable { + fd_vote_accounts_serializable_t vote_accounts; + fd_delegation_pair_t_mapnode_t * stake_delegations_pool; + fd_delegation_pair_t_mapnode_t * stake_delegations_root; + ulong unused; + ulong epoch; + fd_stake_history_t stake_history; +}; +typedef struct fd_stakes_serializable fd_stakes_serializable_t; +#define FD_STAKES_SERIALIZABLE_FOOTPRINT sizeof(fd_stakes_serializable_t) +#define FD_STAKES_SERIALIZABLE_ALIGN (8UL) + +struct __attribute__((aligned(8UL))) fd_stakes_serializable_off { + uint vote_accounts_off; + uint stake_delegations_off; + uint unused_off; + uint epoch_off; + uint stake_history_off; +}; +typedef struct fd_stakes_serializable_off fd_stakes_serializable_off_t; +#define FD_STAKES_SERIALIZABLE_OFF_FOOTPRINT sizeof(fd_stakes_serializable_off_t) +#define FD_STAKES_SERIALIZABLE_OFF_ALIGN (8UL) + typedef struct fd_stake_pair_t_mapnode fd_stake_pair_t_mapnode_t; #define REDBLK_T fd_stake_pair_t_mapnode_t #define REDBLK_NAME fd_stake_pair_t_map @@ -1022,6 +1100,86 @@ typedef struct fd_deserializable_versioned_bank_off fd_deserializable_versioned_ #define FD_DESERIALIZABLE_VERSIONED_BANK_OFF_FOOTPRINT sizeof(fd_deserializable_versioned_bank_off_t) #define FD_DESERIALIZABLE_VERSIONED_BANK_OFF_ALIGN (16UL) +/* TODO: This should replace the deserializable_versioned_bank */ +/* Encoded Size: Dynamic */ +struct __attribute__((aligned(16UL))) fd_serializable_versioned_bank { + fd_block_hash_vec_t blockhash_queue; + ulong ancestors_len; + fd_slot_pair_t * ancestors; + fd_hash_t hash; + fd_hash_t parent_hash; + ulong parent_slot; + fd_hard_forks_t hard_forks; + ulong transaction_count; + ulong tick_height; + ulong signature_count; + ulong capitalization; + ulong max_tick_height; + ulong* hashes_per_tick; + ulong ticks_per_slot; + uint128 ns_per_slot; + ulong genesis_creation_time; + double slots_per_year; + ulong accounts_data_len; + ulong slot; + ulong epoch; + ulong block_height; + fd_pubkey_t collector_id; + ulong collector_fees; + fd_fee_calculator_t fee_calculator; + fd_fee_rate_governor_t fee_rate_governor; + ulong collected_rent; + fd_rent_collector_t rent_collector; + fd_epoch_schedule_t epoch_schedule; + fd_inflation_t inflation; + fd_stakes_serializable_t stakes; + fd_unused_accounts_t unused_accounts; + ulong epoch_stakes_len; + fd_epoch_epoch_stakes_pair_t * epoch_stakes; + uchar is_delta; +}; +typedef struct fd_serializable_versioned_bank fd_serializable_versioned_bank_t; +#define FD_SERIALIZABLE_VERSIONED_BANK_FOOTPRINT sizeof(fd_serializable_versioned_bank_t) +#define FD_SERIALIZABLE_VERSIONED_BANK_ALIGN (16UL) + +struct __attribute__((aligned(16UL))) fd_serializable_versioned_bank_off { + uint blockhash_queue_off; + uint ancestors_off; + uint hash_off; + uint parent_hash_off; + uint parent_slot_off; + uint hard_forks_off; + uint transaction_count_off; + uint tick_height_off; + uint signature_count_off; + uint capitalization_off; + uint max_tick_height_off; + uint hashes_per_tick_off; + uint ticks_per_slot_off; + uint ns_per_slot_off; + uint genesis_creation_time_off; + uint slots_per_year_off; + uint accounts_data_len_off; + uint slot_off; + uint epoch_off; + uint block_height_off; + uint collector_id_off; + uint collector_fees_off; + uint fee_calculator_off; + uint fee_rate_governor_off; + uint collected_rent_off; + uint rent_collector_off; + uint epoch_schedule_off; + uint inflation_off; + uint stakes_off; + uint unused_accounts_off; + uint epoch_stakes_off; + uint is_delta_off; +}; +typedef struct fd_serializable_versioned_bank_off fd_serializable_versioned_bank_off_t; +#define FD_SERIALIZABLE_VERSIONED_BANK_OFF_FOOTPRINT sizeof(fd_serializable_versioned_bank_off_t) +#define FD_SERIALIZABLE_VERSIONED_BANK_OFF_ALIGN (16UL) + /* Encoded Size: Fixed (40 bytes) */ struct __attribute__((aligned(8UL))) fd_bank_hash_stats { ulong num_updated_accounts; @@ -1047,8 +1205,8 @@ typedef struct fd_bank_hash_stats_off fd_bank_hash_stats_off_t; /* Encoded Size: Dynamic */ struct __attribute__((aligned(8UL))) fd_bank_hash_info { - fd_hash_t hash; - fd_hash_t snapshot_hash; + fd_hash_t accounts_delta_hash; + fd_hash_t accounts_hash; fd_bank_hash_stats_t stats; }; typedef struct fd_bank_hash_info fd_bank_hash_info_t; @@ -1056,8 +1214,8 @@ typedef struct fd_bank_hash_info fd_bank_hash_info_t; #define FD_BANK_HASH_INFO_ALIGN (8UL) struct __attribute__((aligned(8UL))) fd_bank_hash_info_off { - uint hash_off; - uint snapshot_hash_off; + uint accounts_delta_hash_off; + uint accounts_hash_off; uint stats_off; }; typedef struct fd_bank_hash_info_off fd_bank_hash_info_off_t; @@ -1278,6 +1436,32 @@ typedef struct fd_solana_manifest_off fd_solana_manifest_off_t; #define FD_SOLANA_MANIFEST_OFF_FOOTPRINT sizeof(fd_solana_manifest_off_t) #define FD_SOLANA_MANIFEST_OFF_ALIGN (16UL) +/* Encoded Size: Dynamic */ +struct __attribute__((aligned(16UL))) fd_solana_manifest_serializable { + fd_serializable_versioned_bank_t bank; + fd_solana_accounts_db_fields_t accounts_db; + ulong lamports_per_signature; + fd_bank_incremental_snapshot_persistence_t * bank_incremental_snapshot_persistence; + fd_hash_t * epoch_account_hash; + ulong versioned_epoch_stakes_len; + fd_versioned_epoch_stakes_pair_t * versioned_epoch_stakes; +}; +typedef struct fd_solana_manifest_serializable fd_solana_manifest_serializable_t; +#define FD_SOLANA_MANIFEST_SERIALIZABLE_FOOTPRINT sizeof(fd_solana_manifest_serializable_t) +#define FD_SOLANA_MANIFEST_SERIALIZABLE_ALIGN (16UL) + +struct __attribute__((aligned(16UL))) fd_solana_manifest_serializable_off { + uint bank_off; + uint accounts_db_off; + uint lamports_per_signature_off; + uint bank_incremental_snapshot_persistence_off; + uint epoch_account_hash_off; + uint versioned_epoch_stakes_off; +}; +typedef struct fd_solana_manifest_serializable_off fd_solana_manifest_serializable_off_t; +#define FD_SOLANA_MANIFEST_SERIALIZABLE_OFF_FOOTPRINT sizeof(fd_solana_manifest_serializable_off_t) +#define FD_SOLANA_MANIFEST_SERIALIZABLE_OFF_ALIGN (16UL) + /* Encoded Size: Fixed (12 bytes) */ struct __attribute__((aligned(8UL))) fd_rust_duration { ulong seconds; @@ -2465,8 +2649,12 @@ struct __attribute__((aligned(128UL))) fd_slot_bank { ulong transaction_count; fd_slot_lthash_t lthash; fd_block_hash_queue_t block_hash_queue; + fd_hash_t prev_banks_hash; + ulong parent_signature_cnt; + ulong tick_height; ulong use_preceeding_epoch_stakes; uchar has_use_preceeding_epoch_stakes; + fd_hard_forks_t hard_forks; }; typedef struct fd_slot_bank fd_slot_bank_t; #define FD_SLOT_BANK_FOOTPRINT sizeof(fd_slot_bank_t) @@ -2495,7 +2683,11 @@ struct __attribute__((aligned(128UL))) fd_slot_bank_off { uint transaction_count_off; uint lthash_off; uint block_hash_queue_off; + uint prev_banks_hash_off; + uint parent_signature_cnt_off; + uint tick_height_off; uint use_preceeding_epoch_stakes_off; + uint hard_forks_off; }; typedef struct fd_slot_bank_off fd_slot_bank_off_t; #define FD_SLOT_BANK_OFF_FOOTPRINT sizeof(fd_slot_bank_off_t) @@ -5105,6 +5297,10 @@ void fd_slot_pair_walk( void * w, fd_slot_pair_t const * self, fd_types_walk_fn_ ulong fd_slot_pair_size( fd_slot_pair_t const * self ); ulong fd_slot_pair_footprint( void ); ulong fd_slot_pair_align( void ); +int fd_slot_pair_decode_archival( fd_slot_pair_t * self, fd_bincode_decode_ctx_t * ctx ); +int fd_slot_pair_decode_archival_preflight( fd_bincode_decode_ctx_t * ctx ); +void fd_slot_pair_decode_archival_unsafe( fd_slot_pair_t * self, fd_bincode_decode_ctx_t * ctx ); +int fd_slot_pair_encode_archival( fd_slot_pair_t const * self, fd_bincode_encode_ctx_t * ctx ); void fd_hard_forks_new( fd_hard_forks_t * self ); int fd_hard_forks_decode( fd_hard_forks_t * self, fd_bincode_decode_ctx_t * ctx ); @@ -5117,6 +5313,10 @@ void fd_hard_forks_walk( void * w, fd_hard_forks_t const * self, fd_types_walk_f ulong fd_hard_forks_size( fd_hard_forks_t const * self ); ulong fd_hard_forks_footprint( void ); ulong fd_hard_forks_align( void ); +int fd_hard_forks_decode_archival( fd_hard_forks_t * self, fd_bincode_decode_ctx_t * ctx ); +int fd_hard_forks_decode_archival_preflight( fd_bincode_decode_ctx_t * ctx ); +void fd_hard_forks_decode_archival_unsafe( fd_hard_forks_t * self, fd_bincode_decode_ctx_t * ctx ); +int fd_hard_forks_encode_archival( fd_hard_forks_t const * self, fd_bincode_encode_ctx_t * ctx ); void fd_inflation_new( fd_inflation_t * self ); int fd_inflation_decode( fd_inflation_t * self, fd_bincode_decode_ctx_t * ctx ); @@ -5238,6 +5438,30 @@ int fd_vote_accounts_pair_decode_archival_preflight( fd_bincode_decode_ctx_t * c void fd_vote_accounts_pair_decode_archival_unsafe( fd_vote_accounts_pair_t * self, fd_bincode_decode_ctx_t * ctx ); int fd_vote_accounts_pair_encode_archival( fd_vote_accounts_pair_t const * self, fd_bincode_encode_ctx_t * ctx ); +void fd_vote_accounts_pair_serializable_new( fd_vote_accounts_pair_serializable_t * self ); +int fd_vote_accounts_pair_serializable_decode( fd_vote_accounts_pair_serializable_t * self, fd_bincode_decode_ctx_t * ctx ); +int fd_vote_accounts_pair_serializable_decode_preflight( fd_bincode_decode_ctx_t * ctx ); +void fd_vote_accounts_pair_serializable_decode_unsafe( fd_vote_accounts_pair_serializable_t * self, fd_bincode_decode_ctx_t * ctx ); +int fd_vote_accounts_pair_serializable_decode_offsets( fd_vote_accounts_pair_serializable_off_t * self, fd_bincode_decode_ctx_t * ctx ); +int fd_vote_accounts_pair_serializable_encode( fd_vote_accounts_pair_serializable_t const * self, fd_bincode_encode_ctx_t * ctx ); +void fd_vote_accounts_pair_serializable_destroy( fd_vote_accounts_pair_serializable_t * self, fd_bincode_destroy_ctx_t * ctx ); +void fd_vote_accounts_pair_serializable_walk( void * w, fd_vote_accounts_pair_serializable_t const * self, fd_types_walk_fn_t fun, const char *name, uint level ); +ulong fd_vote_accounts_pair_serializable_size( fd_vote_accounts_pair_serializable_t const * self ); +ulong fd_vote_accounts_pair_serializable_footprint( void ); +ulong fd_vote_accounts_pair_serializable_align( void ); + +void fd_vote_accounts_serializable_new( fd_vote_accounts_serializable_t * self ); +int fd_vote_accounts_serializable_decode( fd_vote_accounts_serializable_t * self, fd_bincode_decode_ctx_t * ctx ); +int fd_vote_accounts_serializable_decode_preflight( fd_bincode_decode_ctx_t * ctx ); +void fd_vote_accounts_serializable_decode_unsafe( fd_vote_accounts_serializable_t * self, fd_bincode_decode_ctx_t * ctx ); +int fd_vote_accounts_serializable_decode_offsets( fd_vote_accounts_serializable_off_t * self, fd_bincode_decode_ctx_t * ctx ); +int fd_vote_accounts_serializable_encode( fd_vote_accounts_serializable_t const * self, fd_bincode_encode_ctx_t * ctx ); +void fd_vote_accounts_serializable_destroy( fd_vote_accounts_serializable_t * self, fd_bincode_destroy_ctx_t * ctx ); +void fd_vote_accounts_serializable_walk( void * w, fd_vote_accounts_serializable_t const * self, fd_types_walk_fn_t fun, const char *name, uint level ); +ulong fd_vote_accounts_serializable_size( fd_vote_accounts_serializable_t const * self ); +ulong fd_vote_accounts_serializable_footprint( void ); +ulong fd_vote_accounts_serializable_align( void ); + void fd_vote_accounts_new( fd_vote_accounts_t * self ); int fd_vote_accounts_decode( fd_vote_accounts_t * self, fd_bincode_decode_ctx_t * ctx ); int fd_vote_accounts_decode_preflight( fd_bincode_decode_ctx_t * ctx ); @@ -5406,6 +5630,18 @@ int fd_stakes_decode_archival_preflight( fd_bincode_decode_ctx_t * ctx ); void fd_stakes_decode_archival_unsafe( fd_stakes_t * self, fd_bincode_decode_ctx_t * ctx ); int fd_stakes_encode_archival( fd_stakes_t const * self, fd_bincode_encode_ctx_t * ctx ); +void fd_stakes_serializable_new( fd_stakes_serializable_t * self ); +int fd_stakes_serializable_decode( fd_stakes_serializable_t * self, fd_bincode_decode_ctx_t * ctx ); +int fd_stakes_serializable_decode_preflight( fd_bincode_decode_ctx_t * ctx ); +void fd_stakes_serializable_decode_unsafe( fd_stakes_serializable_t * self, fd_bincode_decode_ctx_t * ctx ); +int fd_stakes_serializable_decode_offsets( fd_stakes_serializable_off_t * self, fd_bincode_decode_ctx_t * ctx ); +int fd_stakes_serializable_encode( fd_stakes_serializable_t const * self, fd_bincode_encode_ctx_t * ctx ); +void fd_stakes_serializable_destroy( fd_stakes_serializable_t * self, fd_bincode_destroy_ctx_t * ctx ); +void fd_stakes_serializable_walk( void * w, fd_stakes_serializable_t const * self, fd_types_walk_fn_t fun, const char *name, uint level ); +ulong fd_stakes_serializable_size( fd_stakes_serializable_t const * self ); +ulong fd_stakes_serializable_footprint( void ); +ulong fd_stakes_serializable_align( void ); + void fd_stakes_stake_new( fd_stakes_stake_t * self ); int fd_stakes_stake_decode( fd_stakes_stake_t * self, fd_bincode_decode_ctx_t * ctx ); int fd_stakes_stake_decode_preflight( fd_bincode_decode_ctx_t * ctx ); @@ -5526,6 +5762,18 @@ ulong fd_deserializable_versioned_bank_size( fd_deserializable_versioned_bank_t ulong fd_deserializable_versioned_bank_footprint( void ); ulong fd_deserializable_versioned_bank_align( void ); +void fd_serializable_versioned_bank_new( fd_serializable_versioned_bank_t * self ); +int fd_serializable_versioned_bank_decode( fd_serializable_versioned_bank_t * self, fd_bincode_decode_ctx_t * ctx ); +int fd_serializable_versioned_bank_decode_preflight( fd_bincode_decode_ctx_t * ctx ); +void fd_serializable_versioned_bank_decode_unsafe( fd_serializable_versioned_bank_t * self, fd_bincode_decode_ctx_t * ctx ); +int fd_serializable_versioned_bank_decode_offsets( fd_serializable_versioned_bank_off_t * self, fd_bincode_decode_ctx_t * ctx ); +int fd_serializable_versioned_bank_encode( fd_serializable_versioned_bank_t const * self, fd_bincode_encode_ctx_t * ctx ); +void fd_serializable_versioned_bank_destroy( fd_serializable_versioned_bank_t * self, fd_bincode_destroy_ctx_t * ctx ); +void fd_serializable_versioned_bank_walk( void * w, fd_serializable_versioned_bank_t const * self, fd_types_walk_fn_t fun, const char *name, uint level ); +ulong fd_serializable_versioned_bank_size( fd_serializable_versioned_bank_t const * self ); +ulong fd_serializable_versioned_bank_footprint( void ); +ulong fd_serializable_versioned_bank_align( void ); + void fd_bank_hash_stats_new( fd_bank_hash_stats_t * self ); int fd_bank_hash_stats_decode( fd_bank_hash_stats_t * self, fd_bincode_decode_ctx_t * ctx ); int fd_bank_hash_stats_decode_preflight( fd_bincode_decode_ctx_t * ctx ); @@ -5700,6 +5948,18 @@ ulong fd_solana_manifest_size( fd_solana_manifest_t const * self ); ulong fd_solana_manifest_footprint( void ); ulong fd_solana_manifest_align( void ); +void fd_solana_manifest_serializable_new( fd_solana_manifest_serializable_t * self ); +int fd_solana_manifest_serializable_decode( fd_solana_manifest_serializable_t * self, fd_bincode_decode_ctx_t * ctx ); +int fd_solana_manifest_serializable_decode_preflight( fd_bincode_decode_ctx_t * ctx ); +void fd_solana_manifest_serializable_decode_unsafe( fd_solana_manifest_serializable_t * self, fd_bincode_decode_ctx_t * ctx ); +int fd_solana_manifest_serializable_decode_offsets( fd_solana_manifest_serializable_off_t * self, fd_bincode_decode_ctx_t * ctx ); +int fd_solana_manifest_serializable_encode( fd_solana_manifest_serializable_t const * self, fd_bincode_encode_ctx_t * ctx ); +void fd_solana_manifest_serializable_destroy( fd_solana_manifest_serializable_t * self, fd_bincode_destroy_ctx_t * ctx ); +void fd_solana_manifest_serializable_walk( void * w, fd_solana_manifest_serializable_t const * self, fd_types_walk_fn_t fun, const char *name, uint level ); +ulong fd_solana_manifest_serializable_size( fd_solana_manifest_serializable_t const * self ); +ulong fd_solana_manifest_serializable_footprint( void ); +ulong fd_solana_manifest_serializable_align( void ); + void fd_rust_duration_new( fd_rust_duration_t * self ); int fd_rust_duration_decode( fd_rust_duration_t * self, fd_bincode_decode_ctx_t * ctx ); int fd_rust_duration_decode_preflight( fd_bincode_decode_ctx_t * ctx ); diff --git a/src/flamenco/types/fd_types.json b/src/flamenco/types/fd_types.json index 35c22e290d..467ce8ddb4 100644 --- a/src/flamenco/types/fd_types.json +++ b/src/flamenco/types/fd_types.json @@ -102,6 +102,7 @@ { "name": "slot_pair", "type": "struct", + "archival": "true", "fields": [ { "name": "slot", "type": "ulong" }, { "name": "val", "type": "ulong" } @@ -244,6 +245,22 @@ { "name": "value", "type": "solana_vote_account" } ] }, + { + "name": "vote_accounts_pair_serializable", + "type": "struct", + "fields": [ + { "name": "key", "type": "pubkey" }, + { "name": "stake", "type": "ulong" }, + { "name": "value", "type": "solana_account" } + ] + }, + { + "name": "vote_accounts_serializable", + "type": "struct", + "fields": [ + { "name": "vote_accounts", "type": "map", "element": "vote_accounts_pair_serializable", "key": "key", "minalloc":15000 } + ] + }, { "name": "vote_accounts", "type": "struct", @@ -346,6 +363,18 @@ ], "comment": "https://github.com/anza-xyz/agave/blob/beb3f582f784a96e59e06ef8f34e855258bcd98c/runtime/src/stakes.rs#L202" }, + { + "name": "stakes_serializable", + "type": "struct", + "fields": [ + { "name": "vote_accounts", "type": "vote_accounts_serializable" }, + { "name": "stake_delegations", "type": "map", "element": "delegation_pair", "key": "account" }, + { "name": "unused", "type": "ulong" }, + { "name": "epoch", "type": "ulong" }, + { "name": "stake_history", "type": "stake_history" } + ], + "comment": "https://github.com/anza-xyz/agave/blob/beb3f582f784a96e59e06ef8f34e855258bcd98c/runtime/src/stakes.rs#L202" + }, { "name": "stakes_stake", "type": "struct", @@ -468,6 +497,46 @@ ], "comment": "https://github.com/solana-labs/solana/blob/88aeaa82a856fc807234e7da0b31b89f2dc0e091/runtime/src/bank.rs#L967" }, + { + "name": "serializable_versioned_bank", + "type": "struct", + "alignment": "16", + "fields": [ + { "name": "blockhash_queue", "type": "block_hash_vec" }, + { "name": "ancestors", "type": "vector", "element": "slot_pair" }, + { "name": "hash", "type": "hash" }, + { "name": "parent_hash", "type": "hash" }, + { "name": "parent_slot", "type": "ulong" }, + { "name": "hard_forks", "type": "hard_forks" }, + { "name": "transaction_count", "type": "ulong" }, + { "name": "tick_height", "type": "ulong" }, + { "name": "signature_count", "type": "ulong" }, + { "name": "capitalization", "type": "ulong" }, + { "name": "max_tick_height", "type": "ulong" }, + { "name": "hashes_per_tick", "type": "option", "element": "ulong" }, + { "name": "ticks_per_slot", "type": "ulong" }, + { "name": "ns_per_slot", "type": "uint128" }, + { "name": "genesis_creation_time", "type": "ulong" }, + { "name": "slots_per_year", "type": "double" }, + { "name": "accounts_data_len", "type": "ulong" }, + { "name": "slot", "type": "ulong" }, + { "name": "epoch", "type": "ulong" }, + { "name": "block_height", "type": "ulong" }, + { "name": "collector_id", "type": "pubkey" }, + { "name": "collector_fees", "type": "ulong" }, + { "name": "fee_calculator", "type": "fee_calculator" }, + { "name": "fee_rate_governor", "type": "fee_rate_governor" }, + { "name": "collected_rent", "type": "ulong" }, + { "name": "rent_collector", "type": "rent_collector" }, + { "name": "epoch_schedule", "type": "epoch_schedule" }, + { "name": "inflation", "type": "inflation" }, + { "name": "stakes", "type": "stakes_serializable" }, + { "name": "unused_accounts", "type": "unused_accounts" }, + { "name": "epoch_stakes", "type": "vector", "element": "epoch_epoch_stakes_pair" }, + { "name": "is_delta", "type": "bool" } + ], + "comment": "TODO: This should replace the deserializable_versioned_bank" + }, { "name": "bank_hash_stats", "type": "struct", @@ -483,8 +552,8 @@ "name": "bank_hash_info", "type": "struct", "fields": [ - { "name": "hash", "type": "hash" }, - { "name": "snapshot_hash", "type": "hash" }, + { "name": "accounts_delta_hash", "type": "hash" }, + { "name": "accounts_hash", "type": "hash" }, { "name": "stats", "type": "bank_hash_stats" } ] }, @@ -596,6 +665,19 @@ { "name": "lthash", "type": "option", "element": "slot_lthash", "ignore_underflow": true } ] }, + { + "name": "solana_manifest_serializable", + "type": "struct", + "alignment": "16", + "fields": [ + { "name": "bank", "type": "serializable_versioned_bank" }, + { "name": "accounts_db", "type": "solana_accounts_db_fields" }, + { "name": "lamports_per_signature", "type": "ulong" }, + { "name": "bank_incremental_snapshot_persistence", "type": "option", "element": "bank_incremental_snapshot_persistence", "ignore_underflow": true }, + { "name": "epoch_account_hash", "type": "option", "element": "hash", "ignore_underflow": true }, + { "name": "versioned_epoch_stakes", "type": "vector", "element": "versioned_epoch_stakes_pair", "ignore_underflow": true } + ] + }, { "name": "rust_duration", "type": "struct", @@ -1128,7 +1210,11 @@ { "name": "transaction_count", "type": "ulong" }, { "name": "lthash", "type": "slot_lthash" }, { "name": "block_hash_queue", "type": "block_hash_queue" }, - { "name": "use_preceeding_epoch_stakes", "type": "option", "element": "ulong", "flat": true, "comment": "The epoch for which to use the immediately preceeding epoch's stakes for leader schedule calculation. This is necessary due to how Agave's stake caches interact when loading from snapshots." } + { "name": "prev_banks_hash", "type": "hash" }, + { "name": "parent_signature_cnt", "type": "ulong" }, + { "name": "tick_height", "type": "ulong" }, + { "name": "use_preceeding_epoch_stakes", "type": "option", "element": "ulong", "flat": true, "comment": "The epoch for which to use the immediately preceeding epoch's stakes for leader schedule calculation. This is necessary due to how Agave's stake caches interact when loading from snapshots." }, + { "name": "hard_forks", "type": "hard_forks" } ] }, { diff --git a/src/flamenco/types/gen_stubs.py b/src/flamenco/types/gen_stubs.py index e992d5cbf8..6b588911da 100644 --- a/src/flamenco/types/gen_stubs.py +++ b/src/flamenco/types/gen_stubs.py @@ -412,6 +412,12 @@ def __init__(self, container, json): self.compact = ("modifier" in json and json["modifier"] == "compact") self.ignore_underflow = (bool(json["ignore_underflow"]) if "ignore_underflow" in json else False) + + def propogateArchival(self, nametypes): + fulltype = f'{namespace}_{self.element}' + if fulltype in nametypes: + nametypes[fulltype].propogateArchival(nametypes) + def metaTag(self): return "FD_ARCHIVE_META_VECTOR" diff --git a/src/funk/fd_funk_rec.c b/src/funk/fd_funk_rec.c index 30927a042b..529258d326 100644 --- a/src/funk/fd_funk_rec.c +++ b/src/funk/fd_funk_rec.c @@ -470,6 +470,47 @@ fd_funk_rec_remove( fd_funk_t * funk, return FD_FUNK_SUCCESS; } +int +fd_funk_rec_forget( fd_funk_t * funk, + fd_funk_rec_t ** recs, + ulong recs_cnt ) { + if( FD_UNLIKELY( !funk ) ) return FD_FUNK_ERR_INVAL; + fd_funk_check_write( funk ); + + fd_wksp_t * wksp = fd_funk_wksp( funk ); + + fd_funk_rec_t * rec_map = fd_funk_rec_map( funk, wksp ); + + ulong rec_max = funk->rec_max; + + for( ulong i = 0; i < recs_cnt; ++i ) { + fd_funk_rec_t * rec = recs[i]; + ulong rec_idx = (ulong)(rec - rec_map); + + if( FD_UNLIKELY( (rec_idx>=rec_max) /* Out of map (incl NULL) */ | (rec!=(rec_map+rec_idx)) /* Bad alignment */ ) ) + return FD_FUNK_ERR_INVAL; + + ulong txn_idx = fd_funk_txn_idx( rec->txn_cidx ); + fd_funk_xid_key_pair_t const * key = fd_funk_rec_pair( rec ); + if( FD_UNLIKELY( !fd_funk_txn_idx_is_null( txn_idx ) || /* Must be published */ + !( rec->flags & FD_FUNK_REC_FLAG_ERASE ) || /* Must be removed */ + rec!=fd_funk_rec_map_query_const( rec_map, key, NULL ) ) ) { + return FD_FUNK_ERR_KEY; + } + + ulong prev_idx = rec->prev_idx; + ulong next_idx = rec->next_idx; + if( fd_funk_rec_idx_is_null( prev_idx ) ) funk->rec_head_idx = next_idx; + else rec_map[ prev_idx ].next_idx = next_idx; + if( fd_funk_rec_idx_is_null( next_idx ) ) funk->rec_tail_idx = prev_idx; + else rec_map[ next_idx ].prev_idx = prev_idx; + + fd_funk_rec_map_remove( rec_map, key ); + } + + return FD_FUNK_SUCCESS; +} + void fd_funk_rec_set_erase_data( fd_funk_rec_t * rec, ulong erase_data ) { rec->flags |= ((erase_data & 0xFFFFFFFFFFUL) << (sizeof(unsigned long) * 8 - 40)); diff --git a/src/funk/fd_funk_rec.h b/src/funk/fd_funk_rec.h index a128edb89c..ac529a5b2e 100644 --- a/src/funk/fd_funk_rec.h +++ b/src/funk/fd_funk_rec.h @@ -426,7 +426,8 @@ fd_funk_rec_insert( fd_funk_t * funk, transaction's subsequently created descendants (again, assuming no subsequent insert of key). This type of remove can be done on a published record (assuming the last published transaction is - unfrozen). + unfrozen). A tombstone is left in funk to track removals as they + are published or cancelled. Any information in an erased record is lost. @@ -461,6 +462,22 @@ fd_funk_rec_set_erase_data( fd_funk_rec_t * rec, ulong erase_data ); ulong fd_funk_rec_get_erase_data( fd_funk_rec_t const * rec ); +/* Remove a list of tombstones from funk, thereby freeing up space in + the main index. All the records must be removed and published + beforehand. Reasons for failure include: + + FD_FUNK_ERR_INVAL - bad inputs (NULL funk, NULL rec, rec is + obviously not from funk, etc) + + FD_FUNK_ERR_KEY - the record did not appear to be a removed record. + Specifically, a record query of funk for rec's (xid,key) pair did + not return rec. Also, the record was never published. +*/ +int +fd_funk_rec_forget( fd_funk_t * funk, + fd_funk_rec_t ** recs, + ulong recs_cnt ); + /* fd_funk_rec_write_prepare combines several operations into one convenient package. There are 3 basic cases: diff --git a/src/util/archive/Local.mk b/src/util/archive/Local.mk index 5e5756270d..a679df2aa9 100644 --- a/src/util/archive/Local.mk +++ b/src/util/archive/Local.mk @@ -1,5 +1,5 @@ $(call add-hdrs,fd_ar.h fd_tar.h) -$(call add-objs,fd_ar fd_tar,fd_util) +$(call add-objs,fd_ar fd_tar_writer fd_tar_reader,fd_util) $(call make-unit-test,test_ar,test_ar,fd_util) $(call run-unit-test,test_ar) $(call make-unit-test,test_tar,test_tar,fd_util) diff --git a/src/util/archive/fd_tar.h b/src/util/archive/fd_tar.h index d34abf6e3b..d084fea5cf 100644 --- a/src/util/archive/fd_tar.h +++ b/src/util/archive/fd_tar.h @@ -2,15 +2,25 @@ #define HEADER_fd_src_archive_fd_tar_h /* fd_tar implements the ustar and old-GNU versions of the TAR file - format. This is not a general-purpose TAR implementation. It is - currently only intended for loading Solana snapshots. */ + format. This is not a general-purpose TAR implementation. It is + currently only intended for loading and writing Solana snapshots. */ #include "../fd_util_base.h" +#include "../io/fd_io.h" /* File Format ********************************************************/ +/* The high level format of a tar archive/ball is a set of 512 byte blocks. + Each file will be described a tar header (fd_tar_meta_t) and will be + followed by the raw bytes of the file. The last block that is used for + the file will be padded to fit into a tar block. When the archive is + completed, it will be trailed by two EOF blocks which are populated with + zero bytes. */ + /* fd_tar_meta_t is the ustar/OLDGNU version of the TAR header. */ +#define FD_TAR_BLOCK_SZ (512UL) + struct __attribute__((packed)) fd_tar_meta { # define FD_TAR_NAME_SZ (100) /* 0x000 */ char name [ FD_TAR_NAME_SZ ]; @@ -74,12 +84,16 @@ fd_tar_set_octal( char buf[ static 12 ], ulong val ); /* fd_tar_meta_set_size sets the size field. Returns 1 on success, 0 - if sz is too large to be represented in TAR header. */ + if sz is too large to be represented in TAR header. Set size using the + OLDGNU size extension to allow for unlimited file sizes. The first byte + must be 0x80 followed by 0s and then the size in binary. */ static inline int fd_tar_meta_set_size( fd_tar_meta_t * meta, ulong sz ) { - return fd_tar_set_octal( meta->size, sz ); + meta->size[ 0 ] = (char)0x80; + FD_STORE( ulong, meta->size + 4UL, fd_ulong_bswap( sz ) ); + return 1; } /* fd_tar_meta_set_mtime sets the modification time field. Returns 1 @@ -211,6 +225,127 @@ fd_tar_read( void * reader, uchar const * data, ulong data_sz ); +/* Streaming writer ***************************************************/ + +/* TL;DR. I didn't read the code. How do I use this? + + Init with fd_tar_writer_new( mem, tarball_name ). + + For each file you want to add to the archive: + 1. Write out tar header with fd_tar_writer_new_file( writer, file_name ) + 2. Write out file data with fd_tar_writer_write_file_data( writer, data, data_sz ). + This can be done as many times as you want. + 3. Finish the current file with fd_tar_writer_fini_file( writer ). + + When you are done, call fd_tar_writer_delete( writer ) to write out the + tar archive trailer and close otu the file descriptor. + + If you want to reserve space for an existing file and write back to it + at some point in the future see the below comments for + fd_tar_writer_{make,fill}_space(). + + */ + +struct fd_tar_writer { + int fd; /* The file descriptor for the tar archive. */ + ulong header_pos; /* The position in the file for the current files header. + If there is no current file that is being streamed out, + the header_pos will be equal to ULONG_MAX. */ + ulong data_sz; /* The size of the current files data. If there is no + current file that is being streamed out, the data_sz + will be equal to ULONG_MAX. */ + ulong wb_pos; /* If this value is not equal to ULONG_MAX that means that + this is the position at which to write back to with a + call to fd_tar_writer_fill_space. */ + /* TODO: Right now, the stream to the tar writer just uses fd_io_write. + This can eventually be abstracted to use write callbacks that use + fd_io streaming under the hood. This adds some additional complexity + that's related to writing back into the header: if the header is still + in the ostream buf, modify the buffer. Otherwise, read the header + directly from the file. */ + +}; +typedef struct fd_tar_writer fd_tar_writer_t; + +FD_FN_CONST static inline ulong +fd_tar_writer_align( void ) { + return alignof(fd_tar_writer_t); +} + +FD_FN_CONST static inline ulong +fd_tar_writer_footprint( void ) { + return sizeof(fd_tar_writer_t); +} + +/* fd_tar_writer_new creates a new TAR writer. mem is the memory region + that will hold the fd_tar_writer_t (matches above align/footprint + requirements). Returns a qualified handle to the tar writer + object in mem on success. On failure, returns NULL and writes reason + to warning log. Reasons for failure include invalid memory region. + The writer will enable the user to write/stream out files of variable + size into a continual stream. The writer should persist for the span of + a single tar archive. The user is repsonsible for passing in an open, valid + file descriptor. */ + +fd_tar_writer_t * +fd_tar_writer_new( void * mem, int fd ); + +/* fd_tar_writer_delete destroys a tar writer and frees any allocated + resources. Returns the underlying memory region back to the caller. + This writer will also handle cleanup for the tar archive: it will write + out the tar archive trailer and will close the underlying file descriptor. */ + +void * +fd_tar_writer_delete( fd_tar_writer_t * writer ); + +/* fd_tar_write_new_file writes out a file header, it will leave certain + fields blank to allow for writing back of header metadata that is unknown + until the file done streaming out. The user must enforce the invariant that + this can only be called after fd_tar_fini_file() orfd_tar_writer_new() */ + +int +fd_tar_writer_new_file( fd_tar_writer_t * writer, + char const * file_name ); + +/* fd_tar_writer_write_file_data will write out a variable amount of bytes to the + writer's tarball. This can be called multiple times for a single file. + The user must enforce the invariant that this function succeeded a call + to fd_tar_new_file and should precede a call to fd_tar_fini_file. If this + invariant isn't enforced, then the tar writer will silently produce an + invalid file. */ + +int +fd_tar_writer_write_file_data( fd_tar_writer_t * writer, + void const * data, + ulong data_sz ); + +/* fd_tar_fini_file will write out any alignment bytes to the current file's + data. It will then write back to the file header with the file size and + the checksum. */ + +int +fd_tar_writer_fini_file( fd_tar_writer_t * writer ); + +/* fd_tar_writer_make_space and fd_tar_writer_fill_space, allow for writing + back to a specific place in the tar stream. This can be used by first + making a call to fd_tar_write_new_file, fd_tar_writer_make_space, and + fd_tar_writer_fini_file. This will populate the header and write out + random bytes. The start of this data file will be saved by the tar writer. + Up to n data files can be appended to the tar archive before a call to + fd_tar_writer_fill_space. fd_tar_writer_fill_space should only be called + after an unpaired call to fd_tar_writer_make_space and it requires a valid + fd_tar_writer_t handle. It allows the user to write back to the point at + which they made space. _make_space and _fill_space should be paired together. + There can only be one oustanding call to make_space at a time. + + TODO: This can be extended to support multiple write backs. */ + +int +fd_tar_writer_make_space( fd_tar_writer_t * writer, ulong sz ); + +int +fd_tar_writer_fill_space( fd_tar_writer_t * writer, void const * data, ulong sz ); + FD_PROTOTYPES_END #endif /* HEADER_fd_src_archive_fd_tar_h */ diff --git a/src/util/archive/fd_tar.c b/src/util/archive/fd_tar_reader.c similarity index 100% rename from src/util/archive/fd_tar.c rename to src/util/archive/fd_tar_reader.c diff --git a/src/util/archive/fd_tar_writer.c b/src/util/archive/fd_tar_writer.c new file mode 100644 index 0000000000..0c76db8871 --- /dev/null +++ b/src/util/archive/fd_tar_writer.c @@ -0,0 +1,349 @@ +#include "fd_tar.h" +#include "../fd_util.h" + +#include +#include +#include +#include + +static char null_tar_block[ FD_TAR_BLOCK_SZ ] = {0}; + +#define FD_TAR_PERM ("0000644\0") +#define FD_TAR_MAGIC_VERSION ("ustar \0") +#define FD_TAR_DEFAULT_CHKSUM (" " ) + +fd_tar_writer_t * +fd_tar_writer_new( void * mem, int fd ) { + + /* Allocate the relevant memory for the writer. */ + + if( FD_UNLIKELY( !mem ) ) { + FD_LOG_WARNING(( "NULL mem" )); + return NULL; + } + + if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)mem, fd_tar_writer_align() ) ) ) { + FD_LOG_WARNING(( "unaligned mem" )); + return NULL; + } + + fd_tar_writer_t * writer = (fd_tar_writer_t *)mem; + + /* Make sure that the file descriptor is valid. */ + + if( FD_UNLIKELY( fd<=0 ) ) { + FD_LOG_WARNING(( "Invalid file descriptor" )); + return NULL; + } + + /* If the file already exists, truncate it's length to zero. */ + + int err = ftruncate( fd, 0UL ); + if( FD_UNLIKELY( err==-1 ) ) { + FD_LOG_WARNING(( "Failed to truncate tarball (%i-%s)", errno, fd_io_strerror( errno ) )); + return NULL; + } + + writer->fd = fd; + writer->header_pos = ULONG_MAX; + writer->data_sz = ULONG_MAX; + writer->wb_pos = ULONG_MAX; + + return writer; +} + +void * +fd_tar_writer_delete( fd_tar_writer_t * writer ) { + + /* The end of a tar archive is marked with two EOF 512 byte blocks that are + filled with zeros. These must be written out. */ + + ulong out_sz = 0UL; + int err = fd_io_write( writer->fd, null_tar_block, FD_TAR_BLOCK_SZ, FD_TAR_BLOCK_SZ, &out_sz ); + if( FD_UNLIKELY( err ) ) { + FD_LOG_WARNING(( "Failed to write out the first tar trailer (%i-%s)", errno, fd_io_strerror( errno ) )); + return NULL; + } + err = fd_io_write( writer->fd, null_tar_block, FD_TAR_BLOCK_SZ, FD_TAR_BLOCK_SZ, &out_sz ); + if( FD_UNLIKELY( err ) ) { + FD_LOG_WARNING(( "Failed to write out the second tar trailer (%i-%s)", errno, fd_io_strerror( errno ) )); + return NULL; + } + + return (void*)writer; +} + +int +fd_tar_writer_new_file( fd_tar_writer_t * writer, + char const * file_name ) { + + /* TODO: This function currently fills in the bare minimum to get processed + by Agave, Firedancer, and most tar command line tools. To make this tool + more robust and generalizable, it may make sense to populate some of the + other fields in the tar header. */ + + /* Save position of the header in the file and do simple sanity checks. */ + + long header_pos = lseek( writer->fd, 0, SEEK_CUR ); + if( FD_UNLIKELY( header_pos==-1L ) ) { + FD_LOG_WARNING(( "Failed to get the current file position" )); + return -1; + } + + + writer->header_pos = (ulong)header_pos; + + if( FD_UNLIKELY( !fd_ulong_is_aligned( writer->header_pos, FD_TAR_BLOCK_SZ ) ) ) { + FD_LOG_WARNING(( "Unaligned header position %lu", writer->header_pos )); + return -1; + } + + /* Populate what fields you can in the header */ + + fd_tar_meta_t meta = {0}; + + /* Copy in file name */ + + fd_memcpy( &meta.name, file_name, strlen( file_name ) ); + + /* Copy in the mode: it will always be 0644 and will be left padded. + TODO: make this mode configurable in the future. */ + + fd_memcpy( &meta.mode, FD_TAR_PERM, sizeof(FD_TAR_PERM) ); + + /* Copy in the magic and version */ + + fd_memcpy( &meta.magic, FD_TAR_MAGIC_VERSION, sizeof(FD_TAR_MAGIC_VERSION) ); + + /* Write in the temporary value for the checksum. The tar format dictates + that the checksum bytes should be spaces when it is calculated. */ + + fd_memcpy( &meta.chksum, FD_TAR_DEFAULT_CHKSUM, sizeof(FD_TAR_DEFAULT_CHKSUM) ); + + ulong out_sz = 0UL; + int err = fd_io_write( writer->fd, &meta, FD_TAR_BLOCK_SZ, FD_TAR_BLOCK_SZ, &out_sz ); + if( FD_UNLIKELY( err ) ) { + FD_LOG_WARNING(( "Failed to write out the header (%i-%s)", errno, fd_io_strerror( errno ) )); + return -1; + } + + if( FD_UNLIKELY( out_sz!=FD_TAR_BLOCK_SZ ) ) { + FD_LOG_WARNING(( "Failed to write out correct size header (%lu)", out_sz )); + return -1; + } + + /* Now that the header is written out, reset the data size to prepare + for the file to be written out. */ + + writer->data_sz = 0UL; + + return 0; +} + +int +fd_tar_writer_write_file_data( fd_tar_writer_t * writer, + void const * data, + ulong data_sz ) { + + if( FD_UNLIKELY( writer->header_pos==ULONG_MAX ) ) { + FD_LOG_WARNING(( "There is no corresponding tar header for the tar write" )); + return -1; + } + + /* Simply write out the data and update the data_sz field. */ + + ulong out_sz = 0UL; + int err = fd_io_write( writer->fd, data, data_sz, data_sz, &out_sz ); + if( FD_UNLIKELY( err ) ) { + FD_LOG_WARNING(( "Failed to write out the data (%i-%s)", errno, fd_io_strerror( errno ) )); + return -1; + } + if( FD_UNLIKELY( out_sz!=data_sz ) ) { + FD_LOG_WARNING(( "Failed to write out the data (%lu)", out_sz )); + return -1; + } + + writer->data_sz += data_sz; + + return 0; +} + +int +fd_tar_writer_fini_file( fd_tar_writer_t * writer ) { + + /* If the current file that has been written out does not meet the tar + alignment requirements (512), pad out the rest of the file and update the + header with the file sz and checksum. */ + + ulong out_sz = 0UL; + ulong align_sz = fd_ulong_align_up( writer->data_sz, FD_TAR_BLOCK_SZ ) - writer->data_sz; + int err = fd_io_write( writer->fd, null_tar_block, align_sz, align_sz, &out_sz ); + if( FD_UNLIKELY( err ) ) { + FD_LOG_WARNING(( "Failed to write out the padding (%i-%s)", errno, fd_io_strerror( errno ) )); + return -1; + } + if( FD_UNLIKELY( out_sz!=align_sz ) ) { + FD_LOG_WARNING(( "Failed to write out the correct size padding (%lu)", out_sz )); + return -1; + } + + /* Now we need to write back to the header of the file. This involves + first setting the file pointer to where we expect the header to be. */ + + long eof_pos = lseek( writer->fd, 0L, SEEK_CUR ); + if( FD_UNLIKELY( eof_pos==-1L ) ) { + FD_LOG_WARNING(( "Failed to get the current file position" )); + return -1; + } + long seek = lseek( writer->fd, (long)writer->header_pos, SEEK_SET ); + if( FD_UNLIKELY( (ulong)seek!=writer->header_pos ) ) { + FD_LOG_WARNING(( "Failed to seek to the header position (%ld)", seek )); + return -1; + } + + fd_tar_meta_t meta = {0}; + err = fd_io_read( writer->fd, &meta, FD_TAR_BLOCK_SZ, FD_TAR_BLOCK_SZ, &out_sz ); + if( FD_UNLIKELY( err ) ) { + FD_LOG_WARNING(( "Failed to write out the header (%i-%s)", errno, fd_io_strerror( errno ) )); + return -1; + } + if( FD_UNLIKELY( out_sz!=FD_TAR_BLOCK_SZ ) ) { + FD_LOG_WARNING(( "Failed to write out the correct size header (%lu)", out_sz )); + return -1; + } + + /* The file pointer is now at the start of the file data and should be + moved back to the start of the file header. */ + + seek = lseek( writer->fd, (long)writer->header_pos, SEEK_SET ); + if( FD_UNLIKELY( (ulong)seek!=writer->header_pos ) ) { + FD_LOG_WARNING(( "Failed to seek to the header position (%ld)", seek )); + return -1; + } + + /* Now that the tar header is read in, update the size in the header. */ + + err = fd_tar_meta_set_size( &meta, writer->data_sz ); + if( FD_UNLIKELY( !err ) ) { + FD_LOG_WARNING(( "Failed to set the size in the header" )); + return -1; + } + + /* Write in the checksum which is left padded with zeros */ + + uint checksum = 0UL; + for( ulong i=0UL; ifd, &meta, FD_TAR_BLOCK_SZ, FD_TAR_BLOCK_SZ, &out_sz ); + if( FD_UNLIKELY( err ) ) { + FD_LOG_WARNING(( "Failed to write out the header (%i-%s)", errno, fd_io_strerror( errno ) )); + return -1; + } + if( FD_UNLIKELY( out_sz!=FD_TAR_BLOCK_SZ ) ) { + FD_LOG_WARNING(( "Failed to write out the correct size header (%lu)", out_sz )); + return -1; + } + + /* Reset the file pointer to the end of the file so that we can continue + writing out the next file. */ + + seek = lseek( writer->fd, 0L, SEEK_END ); + if( FD_UNLIKELY( seek!=eof_pos ) ) { + return -1; + } + + /* Reset the data_sz/header pointers as there is no outstanding write. */ + + writer->header_pos = ULONG_MAX; + writer->data_sz = ULONG_MAX; + + return 0; +} + +int +fd_tar_writer_make_space( fd_tar_writer_t * writer, ulong data_sz ) { + + if( FD_UNLIKELY( writer->wb_pos!=ULONG_MAX )) { + FD_LOG_WARNING(( "There is an outstanding write back position" )); + return -1; + } + + /* Extend the size of the file to make space that can be written back to. + TODO: In the future, this can be made into a hole to avoid preallocating + space. */ + + long file_sz = lseek( writer->fd, 0L, SEEK_END ); + if( FD_UNLIKELY( file_sz==-1L ) ) { + FD_LOG_WARNING(( "Failed to get the size of the tarball" )); + return -1; + } + + int err = ftruncate( writer->fd, file_sz + (long)data_sz ); + if( FD_UNLIKELY( err ) ) { + FD_LOG_WARNING(( "Failed to make space in the tarball (%i-%s)", errno, fd_io_strerror( errno ) )); + return -1; + } + + /* Seek to the new end of the file. */ + + long new_sz = lseek( writer->fd, 0, SEEK_END ); + if( FD_UNLIKELY( new_sz!=file_sz+(long)data_sz ) ) { + FD_LOG_WARNING(( "Failed to make space in the tarball" )); + return -1; + } + + writer->data_sz = data_sz; + writer->wb_pos = (ulong)file_sz; + + return 0; +} + +int +fd_tar_writer_fill_space( fd_tar_writer_t * writer, void const * data, ulong data_sz ) { + + if( FD_UNLIKELY( writer->wb_pos==ULONG_MAX ) ) { + FD_LOG_WARNING(( "There is no outstanding write back position" )); + return -1; + } + + long eof_pos = lseek( writer->fd, 0, SEEK_END ); + if( FD_UNLIKELY( eof_pos==-1L ) ) { + FD_LOG_WARNING(( "Failed to seek to the end of the file" )); + return -1; + } + + long seek = lseek( writer->fd, (long)writer->wb_pos, SEEK_SET ); + if( FD_UNLIKELY( (ulong)seek!=writer->wb_pos ) ) { + FD_LOG_WARNING(( "Failed to seek to the write back position (%ld %lu)", seek, writer->wb_pos )); + return -1; + } + + /* Write back to the specified location. Once again, this is unsafe and + you can override the rest of the tar archive making it invalid. */ + + ulong out_sz = 0UL; + int err = fd_io_write( writer->fd, data, data_sz, data_sz, &out_sz ); + if( FD_UNLIKELY( err ) ) { + FD_LOG_WARNING(( "Failed to write out the data (%i-%s)", errno, fd_io_strerror( errno ) )); + return -1; + } + if( FD_UNLIKELY( out_sz!=data_sz ) ) { + FD_LOG_WARNING(( "Failed to write out the data (%lu)", out_sz )); + return -1; + } + + writer->wb_pos = ULONG_MAX; + + seek = lseek( writer->fd, 0, SEEK_END ); + if( FD_UNLIKELY( seek!=eof_pos ) ) { + FD_LOG_WARNING(( "Failed to seek to the end of the file (%ld)", seek )); + return -1; + } + + return 0; +}