From ad1dcd668dd001da731bfaf23ff5aeca955eb6eb Mon Sep 17 00:00:00 2001 From: ibhatt-jumptrading Date: Tue, 17 Dec 2024 15:24:18 +0000 Subject: [PATCH] snapshot_service --- src/app/fdctl/Local.mk | 3 + src/app/fdctl/config.c | 2 +- src/app/fdctl/config.h | 7 + src/app/fdctl/config/default-firedancer.toml | 2 + src/app/fdctl/config_parse.c | 5 + src/app/fdctl/main.c | 1 + src/app/fdctl/ready.c | 5 +- src/app/fdctl/run/run.c | 3 +- src/app/fdctl/run/tiles/fd_eqvoc.c | 1 + src/app/fdctl/run/tiles/fd_replay.c | 242 +- src/app/fdctl/run/tiles/fd_replay_thread.c | 2 +- src/app/fdctl/run/tiles/fd_snaps_thread.c | 3 + src/app/fdctl/run/tiles/fd_snapshot.c | 435 ++++ .../fdctl/run/tiles/generated/snaps_seccomp.h | 134 + src/app/fdctl/run/tiles/snaps.seccomppolicy | 75 + src/app/fdctl/run/topos/fd_firedancer.c | 34 +- src/app/fddev/main1.c | 4 + src/app/ledger/main.c | 243 +- src/ballet/zstd/fd_zstd.h | 4 + src/disco/topo/fd_topo.c | 6 + src/disco/topo/fd_topo.h | 13 + src/disco/topo/fd_topob.c | 5 +- src/flamenco/fd_flamenco_base.h | 1 + .../runtime/context/fd_exec_epoch_ctx.h | 1 + .../runtime/context/fd_exec_slot_ctx.c | 21 +- .../runtime/context/fd_exec_slot_ctx.h | 9 +- src/flamenco/runtime/fd_hashes.c | 247 +- src/flamenco/runtime/fd_hashes.h | 48 +- src/flamenco/runtime/fd_runtime.c | 68 +- src/flamenco/runtime/fd_txncache.c | 214 +- src/flamenco/runtime/fd_txncache.h | 56 +- src/flamenco/runtime/test_txncache.c | 43 +- src/flamenco/snapshot/Local.mk | 3 + src/flamenco/snapshot/fd_snapshot.c | 3 +- src/flamenco/snapshot/fd_snapshot_create.c | 1224 ++++++++++ src/flamenco/snapshot/fd_snapshot_create.h | 192 +- src/flamenco/snapshot/fd_snapshot_restore.c | 9 + src/flamenco/types/fd_type_names.c | 7 +- src/flamenco/types/fd_types.c | 2147 ++++++++++++++--- src/flamenco/types/fd_types.h | 270 ++- src/flamenco/types/fd_types.json | 92 +- src/flamenco/types/gen_stubs.py | 6 + src/util/archive/Local.mk | 2 +- src/util/archive/fd_tar.h | 143 +- .../archive/{fd_tar.c => fd_tar_reader.c} | 0 src/util/archive/fd_tar_writer.c | 349 +++ 46 files changed, 5785 insertions(+), 599 deletions(-) create mode 100644 src/app/fdctl/run/tiles/fd_snaps_thread.c create mode 100644 src/app/fdctl/run/tiles/fd_snapshot.c create mode 100644 src/app/fdctl/run/tiles/generated/snaps_seccomp.h create mode 100644 src/app/fdctl/run/tiles/snaps.seccomppolicy create mode 100644 src/flamenco/snapshot/fd_snapshot_create.c rename src/util/archive/{fd_tar.c => fd_tar_reader.c} (100%) create mode 100644 src/util/archive/fd_tar_writer.c diff --git a/src/app/fdctl/Local.mk b/src/app/fdctl/Local.mk index 276efe61f0..42e326151a 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_snaps_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/snaps_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 7781e3a2c3..c3f0606e60 100644 --- a/src/app/fdctl/config.h +++ b/src/app/fdctl/config.h @@ -304,6 +304,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 5ae4c34633..cbcb4ad21b 100644 --- a/src/app/fdctl/config_parse.c +++ b/src/app/fdctl/config_parse.c @@ -388,6 +388,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_eqvoc.c b/src/app/fdctl/run/tiles/fd_eqvoc.c index 984dea93b8..71260860d3 100644 --- a/src/app/fdctl/run/tiles/fd_eqvoc.c +++ b/src/app/fdctl/run/tiles/fd_eqvoc.c @@ -178,6 +178,7 @@ after_frag( fd_eqvoc_tile_ctx_t * ctx, finalize_new_cluster_contact_info( ctx ); return; } else if ( FD_UNLIKELY( in_idx == ctx->gossip_in_idx ) ) { + return; fd_gossip_duplicate_shred_t * chunk = &ctx->duplicate_shred; ulong slot = ctx->duplicate_shred.slot; fd_pubkey_t const * from = &chunk->from; diff --git a/src/app/fdctl/run/tiles/fd_replay.c b/src/app/fdctl/run/tiles/fd_replay.c index d009a94243..149cb7b826 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 @@ -252,6 +253,19 @@ 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; /* TODO: tear out this hack somehow */ + + int is_caught_up; }; typedef struct fd_replay_tile_ctx fd_replay_tile_ctx_t; @@ -549,37 +563,197 @@ 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 ) { + + (void)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 in funk_and_txncache_publish. */ + + if( FD_UNLIKELY( !ctx->slot_ctx->status_cache ) ) { + return; + } + + 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_WARNING(("REGISTERING SLOT %lu", slot)); + fd_txncache_register_root_slot( ctx->slot_ctx->status_cache, slot ); + } else { + FD_LOG_WARNING(("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; + } + + /* 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; + if( is_full_snapshot_ready || is_inc_snapshot_ready ) { + /* 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. + + 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 ); + } + + if( ctx->first_constipation ) { + FD_LOG_NOTICE(("Starting constipation at slot=%lu", txn->xid.ul[0])); + fd_txncache_register_constipated_slot( ctx->slot_ctx->status_cache, txn->xid.ul[0] ); + ctx->first_constipation = 0; + } } + 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 ); } @@ -1055,6 +1229,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 ) { @@ -1166,6 +1345,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 ) ) { @@ -1244,7 +1428,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; @@ -1311,7 +1495,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++; } @@ -1326,7 +1510,7 @@ tpool_boot( fd_topo_t * topo, ulong total_thread_count ) { } if( 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_LOG_WARNING(( "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 ); } @@ -1624,7 +1808,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 ); @@ -1722,10 +1906,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 ) { @@ -1743,6 +1939,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" )); @@ -1753,6 +1950,8 @@ unprivileged_init( fd_topo_t * topo, FD_LOG_ERR(( "no funk wksp" )); } + ctx->is_caught_up = 0; + /**********************************************************************/ /* root_slot fseq */ /**********************************************************************/ @@ -1763,6 +1962,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 */ /**********************************************************************/ @@ -1835,7 +2047,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_snaps_thread.c b/src/app/fdctl/run/tiles/fd_snaps_thread.c new file mode 100644 index 0000000000..aabcd666ee --- /dev/null +++ b/src/app/fdctl/run/tiles/fd_snaps_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/fd_snapshot.c b/src/app/fdctl/run/tiles/fd_snapshot.c new file mode 100644 index 0000000000..fcf2154a94 --- /dev/null +++ b/src/app/fdctl/run/tiles/fd_snapshot.c @@ -0,0 +1,435 @@ +/* Gossip verify tile sits before the gossip (dedup?) tile to verify incoming + gossip packets */ +#include +#define _GNU_SOURCE + +#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/snaps_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_READONLY, + 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_snaps( 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_snaps_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/generated/snaps_seccomp.h b/src/app/fdctl/run/tiles/generated/snaps_seccomp.h new file mode 100644 index 0000000000..6e6c5e42f3 --- /dev/null +++ b/src/app/fdctl/run/tiles/generated/snaps_seccomp.h @@ -0,0 +1,134 @@ +/* THIS FILE WAS GENERATED BY generate_filters.py. DO NOT EDIT BY HAND! */ +#ifndef HEADER_fd_src_app_fdctl_run_tiles_generated_snaps_seccomp_h +#define HEADER_fd_src_app_fdctl_run_tiles_generated_snaps_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_snaps_instr_cnt = 50; + +static void populate_sock_filter_policy_snaps( 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 >= 50 ); + struct sock_filter filter[50] = { + /* 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 */ 46 ), + /* 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 */ 6, 0 ), + /* allow fsync based on expression */ + BPF_JUMP( BPF_JMP | BPF_JEQ | BPF_K, SYS_fsync, /* check_fsync */ 17, 0 ), + /* allow fchmod based on expression */ + BPF_JUMP( BPF_JMP | BPF_JEQ | BPF_K, SYS_fchmod, /* check_fchmod */ 18, 0 ), + /* allow ftruncate based on expression */ + BPF_JUMP( BPF_JMP | BPF_JEQ | BPF_K, SYS_ftruncate, /* check_ftruncate */ 19, 0 ), + /* allow lseek based on expression */ + BPF_JUMP( BPF_JMP | BPF_JEQ | BPF_K, SYS_lseek, /* check_lseek */ 28, 0 ), + /* allow read based on expression */ + BPF_JUMP( BPF_JMP | BPF_JEQ | BPF_K, SYS_read, /* check_read */ 35, 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 ), +// 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/snaps.seccomppolicy b/src/app/fdctl/run/tiles/snaps.seccomppolicy new file mode 100644 index 0000000000..d8a03ad8d5 --- /dev/null +++ b/src/app/fdctl/run/tiles/snaps.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 28f456dfca..44717d75e4 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. " @@ -619,6 +635,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)); @@ -633,7 +651,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) ); @@ -666,6 +684,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 e931cc2530..30d6c1991b 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 ) ) ); + uchar tpool_mem_two[FD_TPOOL_FOOTPRINT( FD_TILE_MAX )] __attribute__( ( aligned( FD_TPOOL_ALIGN ) ) ); + uchar tpool_mem_three[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 */ + fd_tpool_t * snapshot_bg_tpool; /* thread pool for snapshot creation */ + ulong last_snapshot_slot; /* last snapshot slot */ + fd_hash_t last_snapshot_hash; /* last snapshot hash */ + ulong last_snapshot_capitalization;/* last snapshot account hash */ + 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_capitalization ); + 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_two, 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 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_three, 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,53 @@ runtime_replay( fd_ledger_args_t * ledger_args ) { ulong sz = blk->data_sz; fd_blockstore_end_read( blockstore ); + /* TODO:FIXME: skipped slots handling */ + + + 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_capitalization + }; + + FD_LOG_WARNING(("STARTING INCREMENTAL SNPASHOTTTING")); + + 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 +641,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 +1065,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 +1210,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 +1590,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 ); // TODO: Add argument validation. Make sure that we aren't including any arguments that aren't parsed for @@ -1467,6 +1688,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 6d4ff4c5e6..d2b658e2aa 100644 --- a/src/disco/topo/fd_topo.h +++ b/src/disco/topo/fd_topo.h @@ -245,6 +245,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 ]; @@ -339,6 +341,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 cd774d8cb4..9f35619aeb 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/fd_flamenco_base.h b/src/flamenco/fd_flamenco_base.h index b9fa487346..b298845368 100644 --- a/src/flamenco/fd_flamenco_base.h +++ b/src/flamenco/fd_flamenco_base.h @@ -15,6 +15,7 @@ #define FD_FUNK_KEY_TYPE_ACC ((uchar)1) #define FD_FUNK_KEY_TYPE_ELF_CACHE ((uchar)2) +#define FD_FUNK_KEY_TYPE_TOMBSTONES ((uchar)3) /* CLUSTER_VERSION is the default value for the cluster version in the epoch context. This value will foll forward to the diff --git a/src/flamenco/runtime/context/fd_exec_epoch_ctx.h b/src/flamenco/runtime/context/fd_exec_epoch_ctx.h index 9a1133923d..380946752d 100644 --- a/src/flamenco/runtime/context/fd_exec_epoch_ctx.h +++ b/src/flamenco/runtime/context/fd_exec_epoch_ctx.h @@ -33,6 +33,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 df48b3443d..2d84a00f63 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, @@ -245,15 +246,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 @@ -298,6 +303,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 @@ -461,6 +477,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 84fd21915d..7c27ff3023 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; fd_sysvar_cache_t * sysvar_cache; @@ -78,9 +76,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_hashes.c b/src/flamenco/runtime/fd_hashes.c index a2bcdaa279..b9cfc4adf1 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, @@ -515,6 +516,16 @@ fd_update_hash_bank_tpool( fd_exec_slot_ctx_t * slot_ctx, slot_ctx->signature_cnt = signature_cnt; fd_hash_bank( slot_ctx, capture_ctx, hash, dirty_keys, dirty_key_cnt); + /* TODO: This should be factored out */ + fd_funk_rec_key_t key = {0}; + key.c[ FD_FUNK_REC_KEY_FOOTPRINT - 1 ] = FD_FUNK_KEY_TYPE_TOMBSTONES; + + fd_funk_rec_t * tombstones = fd_funk_rec_write_prepare( funk, txn, &key, 512008, 1, NULL, NULL ); + uchar * tombstone_rec = fd_funk_val( tombstones, fd_funk_wksp( funk ) ); + ulong * tombestone_cnt = (ulong *)tombstone_rec; + uchar * tombstone_data = tombstone_rec + sizeof(ulong); + *tombestone_cnt = 0; + for( ulong i = 0; i < task_data.info_sz; i++ ) { fd_accounts_hash_task_info_t * task_info = &task_data.info[i]; /* Upgrade to writable record */ @@ -522,7 +533,10 @@ fd_update_hash_bank_tpool( fd_exec_slot_ctx_t * slot_ctx, continue; } - fd_funk_rec_remove(funk, fd_funk_rec_modify(funk, task_info->rec), 1); + //fd_funk_rec_remove( funk, fd_funk_rec_modify( funk, task_info->rec ), 1 ); + + fd_memcpy( tombstone_data + *tombestone_cnt * sizeof(fd_pubkey_t), task_info->acc_pubkey, sizeof(fd_pubkey_t) ); + *tombestone_cnt += 1UL; } // Sanity-check LT Hash @@ -713,15 +727,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; @@ -769,9 +789,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); @@ -794,10 +814,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; @@ -811,60 +832,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; } @@ -933,7 +959,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(); @@ -943,6 +969,89 @@ 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 ); + + // How many total records are we dealing with? + 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; @@ -951,7 +1060,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 ) ); @@ -960,20 +1069,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 ); @@ -985,7 +1152,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; @@ -1000,7 +1167,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)); @@ -1050,7 +1217,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..5eeaf8939d 100644 --- a/src/flamenco/runtime/fd_hashes.h +++ b/src/flamenco/runtime/fd_hashes.h @@ -56,9 +56,11 @@ fd_hash_account_current( uchar hash [ static 32 ], /* 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 */ int @@ -67,6 +69,17 @@ fd_accounts_hash_inc_only( fd_exec_slot_ctx_t * slot_ctx, 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 +87,35 @@ 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. */ +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 3766cf7abf..188af33124 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" @@ -1107,7 +1108,7 @@ fd_runtime_verify_txn_signatures_tpool( fd_execute_txn_task_info_t * task_info, ulong txn_cnt, fd_tpool_t * tpool ) { int res = 0; - fd_tpool_exec_all_rrobin( tpool, 0, fd_tpool_worker_cnt( tpool ), fd_txn_sigverify_task, task_info, NULL, NULL, 1, 0, txn_cnt ); + fd_tpool_exec_all_rrobin( tpool, 0, fd_tpool_worker_cnt( tpool ) - 1, fd_txn_sigverify_task, task_info, NULL, NULL, 1, 0, txn_cnt ); for( ulong txn_idx = 0; txn_idx < txn_cnt; txn_idx++ ) { if( FD_UNLIKELY(!( task_info[txn_idx].txn->flags & FD_TXN_P_FLAGS_SANITIZE_SUCCESS )) ) { task_info->exec_res = FD_RUNTIME_TXN_ERR_SIGNATURE_FAILURE; @@ -2206,7 +2207,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(); @@ -2348,6 +2349,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 ); @@ -2358,8 +2361,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; @@ -2781,32 +2791,61 @@ fd_runtime_checkpt( fd_capture_ctx_t * capture_ctx, } } -static int +static int FD_FN_UNUSED 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)); 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( slot_ctx->status_cache && !fd_txncache_get_is_constipated( slot_ctx->status_cache ) ) { + FD_LOG_WARNING(("REGISTERING ROOT %lu", txn->xid.ul[0] )); + 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 ); + ulong publish_err = 0; + // todo: this publish_err thing is bs... + if( slot_ctx->epoch_ctx->constipate_root ) { + fd_funk_txn_t *p = fd_funk_txn_parent( txn, txnmap ); + if( p != NULL ) { + slot_ctx->root_slot = txn->xid.ul[0]; + FD_LOG_WARNING(("CONSTIPATED PUBLISH %lu into %lu", slot_ctx->root_slot, p->xid.ul[0])); + + if( fd_funk_txn_publish_into_parent(funk, txn, 1) == FD_FUNK_SUCCESS ) { + publish_err = 1; + } + } else { + publish_err = 1; + } + } else { + slot_ctx->root_slot = txn->xid.ul[0]; + if( slot_ctx->root_slot % slot_ctx->snapshot_freq == 0 || (slot_ctx->root_slot % slot_ctx->incremental_freq == 0 && slot_ctx->last_snapshot_slot) ) { + + slot_ctx->last_snapshot_slot = slot_ctx->root_slot; + FD_LOG_WARNING(("CONSTIPATING")); + slot_ctx->epoch_ctx->constipate_root = 1; + fd_txncache_set_is_constipated( slot_ctx->status_cache, 1 ); + } + FD_LOG_WARNING(("PUBLISH for slot %lu ", txn->xid.ul[0])); + 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 ) { - fd_txncache_register_root_slot( slot_ctx->status_cache, txn->xid.ul[0] ); - } 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; } @@ -3274,6 +3313,10 @@ fd_runtime_collect_rent_from_account( fd_exec_slot_ctx_t const * slot_ctx, fd_pubkey_t const * key, ulong epoch ) { + if( !memcmp(key, &fd_sysvar_last_restart_slot_id, sizeof(fd_pubkey_t)) ) { + FD_LOG_WARNING(("PURR")); + } + if( !FD_FEATURE_ACTIVE( slot_ctx, disable_rent_fees_collection ) ) { return fd_runtime_collect_from_existing_account( slot_ctx, acc, key, epoch ); } else { @@ -3711,8 +3754,9 @@ fd_runtime_cleanup_incinerator( fd_exec_slot_ctx_t * slot_ctx ) { fd_funk_rec_key_t id = fd_acc_funk_key( &fd_sysvar_incinerator_id ); fd_funk_t * funk = slot_ctx->acc_mgr->funk; fd_funk_rec_t const * rec = fd_funk_rec_query( funk, slot_ctx->funk_txn, &id ); - if( rec ) + if( rec ) { fd_funk_rec_remove( funk, fd_funk_rec_modify( funk, rec ), 1 ); + } } void diff --git a/src/flamenco/runtime/fd_txncache.c b/src/flamenco/runtime/fd_txncache.c index e24f057570..f37a669015 100644 --- a/src/flamenco/runtime/fd_txncache.c +++ b/src/flamenco/runtime/fd_txncache.c @@ -111,6 +111,7 @@ struct __attribute__((aligned(FD_TXNCACHE_ALIGN))) fd_txncache_private { ulong root_slots_max; ulong live_slots_max; + ulong constipated_slots_max; ushort txnpages_per_blockhash_max; uint txnpages_max; @@ -149,6 +150,16 @@ 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. */ + + int is_constipated; /* Is the status cache in a constipated*/ + ulong is_constipated_off; + ulong magic; /* ==FD_TXNCACHE_MAGIC */ }; @@ -157,6 +168,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 +272,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 +529,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,9 +559,54 @@ 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 ); + + 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; -unlock: 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 @@ -996,3 +1070,91 @@ 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; + + 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) ); + + 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..fc4cd57616 100644 --- a/src/flamenco/runtime/fd_txncache.h +++ b/src/flamenco/runtime/fd_txncache.h @@ -1,6 +1,8 @@ #ifndef HEADER_fd_src_flamenco_runtime_txncache_h #define HEADER_fd_src_flamenco_runtime_txncache_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 +196,20 @@ #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. + TODO:FIXME: flush right after we are done with the stuff here. */ + +#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,20 @@ 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 +426,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/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..6f1c64b743 --- /dev/null +++ b/src/flamenco/snapshot/fd_snapshot_create.c @@ -0,0 +1,1224 @@ +#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 +#include +#include +#include + +static uchar padding [ FD_SNAPSHOT_ACC_ALIGN ] = {0}; + +static inline int +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 pubkey 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_snapshot_create.h. */ + + 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. slot */ + + fd_funk_t * funk = snapshot_ctx->acc_mgr->funk; + ulong prev_sz = 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; + } + + uchar const * raw = fd_funk_val( rec, fd_funk_wksp( funk ) ); + fd_account_meta_t const * metadata = fd_type_pun_const( 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_WARNING(( "Unable to calculate snapshot hash" )); + return -1; + } + + 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_WARNING(( "Unable to format manifest name string" )); + return -1; + } + + err = fd_tar_writer_new_file( writer, buffer ); + if( FD_UNLIKELY( err ) ) { + FD_LOG_WARNING(( "Unable to create snapshot manifest file" )); + return -1; + } + + err = fd_tar_writer_make_space( writer, manifest_sz ); + if( FD_UNLIKELY( err ) ) { + FD_LOG_WARNING(( "Unable to make space for snapshot manifest file" )); + return -1; + } + + err = fd_tar_writer_fini_file( writer ); + if( FD_UNLIKELY( err ) ) { + FD_LOG_WARNING(( "Unable to finalize snapshot manifest file" )); + return -1; + } + + /* 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_WARNING(( "Unable to format previous accounts name string" )); + return -1; + } + + err = fd_tar_writer_new_file( writer, buffer ); + if( FD_UNLIKELY( err ) ) { + FD_LOG_WARNING(( "Unable to create previous accounts file" )); + return -1; + } + + 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 ); + uchar const * raw = fd_funk_val( rec, fd_funk_wksp( funk ) ); + fd_account_meta_t const * metadata = fd_type_pun_const( raw ); + + 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; + } + + 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. */ + + fd_tar_writer_fini_file( writer ); + + 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_WARNING(( "Unable to format previous accounts name string" )); + return -1; + } + + fd_tar_writer_new_file( writer, buffer ); + } + + 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_WARNING(( "Unable to stream out account header to tar archive" )); + return -1; + } + + /* Write out the file data. */ + + err = fd_tar_writer_write_file_data( writer, acc_data, metadata->dlen ); + if( FD_UNLIKELY( err ) ) { + FD_LOG_WARNING(( "Unable to stream out account data to tar archive" )); + return -1; + } + + 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_WARNING( ("Unable to stream out account padding to tar archive" )); + return -1; + } + } + + fd_tar_writer_fini_file( writer ); + + /* Now write out the append vec for the snapshot slot. */ + + 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_WARNING(( "Unable to format current accounts name string" )); + return -1; + } + fd_tar_writer_new_file( writer, buffer ); + + for( ulong i=0UL; imagic!=FD_ACCOUNT_META_MAGIC ) ) { + FD_LOG_ERR(( "Record should have valid magic" )); + } + + if( !metadata->info.lamports ) { + continue; + } + + 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_WARNING(( "Unable to stream out account header to tar archive" )); + return -1; + } + err = fd_tar_writer_write_file_data( writer, acc_data, metadata->dlen ); + if( FD_UNLIKELY( err ) ) { + FD_LOG_WARNING(( "Unable to stream out account data to tar archive" )); + return -1; + } + 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_WARNING(( "Unable to stream out account padding to tar archive" )); + return -1; + } + } + + fd_funk_rec_key_t key = {0}; + key.c[ FD_FUNK_REC_KEY_FOOTPRINT - 1 ] = FD_FUNK_KEY_TYPE_TOMBSTONES; + fd_funk_rec_t const * rec = fd_funk_rec_query( funk, NULL, &key ); + uchar const * rec_val = fd_funk_val_const( rec, fd_funk_wksp( funk ) ); + ulong tombstone_cnt = *(ulong*)rec_val; + + for( ulong i=0UL; ifile_sz += sizeof(fd_solana_account_hdr_t); + + fd_pubkey_t const * pubkey = (fd_pubkey_t*)(rec_val + sizeof(ulong) + i * sizeof(fd_pubkey_t)); + fd_solana_account_hdr_t header = {0}; + fd_memcpy( header.meta.pubkey, pubkey, sizeof(fd_pubkey_t) ); + err = fd_tar_writer_write_file_data( writer, &header, sizeof(fd_solana_account_hdr_t) ); + if( FD_UNLIKELY( err ) ) { + FD_LOG_WARNING(( "Unable to stream out account header to tar archive" )); + return -1; + } + FD_LOG_WARNING(("PUBKEY %s", FD_BASE58_ENC_32_ALLOCA(pubkey))); + } + + err = fd_tar_writer_fini_file( writer ); + if( FD_UNLIKELY( err ) ) { + FD_LOG_WARNING(( "Unable to finish writing out file" )); + } + + fd_valloc_free( snapshot_ctx->valloc, snapshot_slot_keys ); + + return 0; +} + +int +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_WARNING(( "Failed to view vote account from stakes cache %s", FD_BASE58_ENC_32_ALLOCA(&n->elem.key) )); + return -1; + } + + 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. */ + + 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_WARNING(( "Failed to decode stake state" )); + return -1; + } + 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; + + return 0; +} + +static inline int +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; + bank->hashes_per_tick = &epoch_bank->hashes_per_tick; + 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. */ + + int err = fd_snapshot_create_serialiable_stakes( snapshot_ctx, &epoch_bank->stakes, &bank->stakes ); + if( FD_UNLIKELY( err ) ) { + FD_LOG_WARNING(( "Failed to serialize stakes" )); + return -1; + } + + return 0; +} + +static inline int +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_WARNING(( "Failed to initialize account manager" )); + return -1; + } + + /* 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_WARNING(( "Failed to read epoch bank record: missing record" )); + return -1; + } + 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_WARNING(( "Epoch bank record has wrong magic" )); + return -1; + } + + int err = fd_epoch_bank_decode( &snapshot_ctx->epoch_bank, &epoch_decode_ctx ); + if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) { + FD_LOG_WARNING(( "Failed to decode epoch bank" )); + return -1; + } + + /* 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_WARNING(( "Failed to read slot bank record: missing record" )); + return -1; + } + 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_WARNING(( "Slot bank record has wrong magic" )); + return -1; + } + + err = fd_slot_bank_decode( &snapshot_ctx->slot_bank, &slot_decode_ctx ); + if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) { + FD_LOG_WARNING(( "Failed to decode slot bank" )); + return -1; + } + + /* Validate that the snapshot context is setup correctly */ + + if( FD_UNLIKELY( !snapshot_ctx->out_dir ) ) { + FD_LOG_WARNING(( "Snapshot directory is not set" )); + return -1; + } + + if( FD_UNLIKELY( snapshot_ctx->slot>snapshot_ctx->slot_bank.slot ) ) { + FD_LOG_WARNING(( "Snapshot slot=%lu is greater than the current slot=%lu", + snapshot_ctx->slot, snapshot_ctx->slot_bank.slot )); + return -1; + } + + /* 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_WARNING(( "Failed to seek to the start of the file" )); + return -1; + } + + if( FD_UNLIKELY( ftruncate( snapshot_ctx->tmp_fd, 0UL ) < 0 ) ) { + FD_LOG_WARNING(( "Failed to truncate the temporary file" )); + return -1; + } + + seek = lseek( snapshot_ctx->snapshot_fd, 0, SEEK_SET ); + if( FD_UNLIKELY( seek ) ) { + FD_LOG_WARNING(( "Failed to seek to the start of the file" )); + return -1; + } + + if( FD_UNLIKELY( ftruncate( snapshot_ctx->snapshot_fd, 0UL ) < 0 ) ) { + FD_LOG_WARNING(( "Failed to truncate the snapshot file" )); + return -1; + } + + return 0; +} + +static inline int +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 ) ) { + return -1; + } + + return 0; +} + +static inline int +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_WARNING(( "Failed to create the version file" )); + return -1; + } + + err = fd_tar_writer_write_file_data( snapshot_ctx->writer, FD_SNAPSHOT_VERSION, FD_SNAPSHOT_VERSION_LEN); + if( FD_UNLIKELY( err ) ) { + FD_LOG_WARNING(( "Failed to create the version file" )); + return -1; + } + + err = fd_tar_writer_fini_file( snapshot_ctx->writer ); + if( FD_UNLIKELY( err ) ) { + FD_LOG_WARNING(( "Failed to create the version file" )); + return -1; + } + + return 0; +} + +static inline int +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_WARNING(( "Failed to get entries from the status cache" )); + return -1; + } + 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_WARNING(( "Failed to encode the status cache" )); + return -1; + } + + /* 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_WARNING(( "Failed to create the status cache file" )); + return -1; + } + err = fd_tar_writer_write_file_data( snapshot_ctx->writer, out_status_cache, bank_slot_deltas_sz ); + if( FD_UNLIKELY( err ) ) { + FD_LOG_WARNING(( "Failed to create the status cache file" )); + return -1; + } + err = fd_tar_writer_fini_file( snapshot_ctx->writer ); + if( FD_UNLIKELY( err ) ) { + FD_LOG_WARNING(( "Failed to create the status cache file" )); + return -1; + } + + /* 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 ); + + return 0; + + } FD_SCRATCH_SCOPE_END; + +} + +static inline int +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. */ + + int err = fd_snapshot_create_populate_bank( snapshot_ctx, &manifest.bank ); + if( FD_UNLIKELY( err ) ) { + FD_LOG_WARNING(( "Failed to populate the bank" )); + return -1; + } + + /* 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; + + /* TODO: The versioned epoch stakes needs to be implemented. */ + + 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; + err = fd_snapshot_create_populate_acc_vecs( snapshot_ctx, &manifest, snapshot_ctx->writer, &incr_capitalization ); + if( FD_UNLIKELY( err ) ) { + FD_LOG_WARNING(( "Failed to populate the account vectors" )); + return -1; + } + + /* 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 + }; + + err = fd_solana_manifest_serializable_encode( &manifest, &encode ); + if( FD_UNLIKELY( err ) ) { + FD_LOG_WARNING(( "Failed to encode the manifest" )); + return -1; + } + + err = fd_tar_writer_fill_space( snapshot_ctx->writer, out_manifest, manifest_sz ); + if( FD_UNLIKELY( err ) ) { + FD_LOG_WARNING(( "Failed to write out the manifest" )); + return -1; + } + + void * mem = fd_tar_writer_delete( snapshot_ctx->writer ); + if( FD_UNLIKELY( !mem ) ) { + return -1; + } + + fd_bincode_destroy_ctx_t destroy = { + .valloc = snapshot_ctx->valloc + }; + + /* FIXME: A lot of the allocations can leak if the snaphshot loader + fails out. There also might be some other leaks and this should + be tracked down. This is mostly mitigated if you use scratch and + some other allocator for the accounts hash. The snapshot tile currently + uses scratch. The other option is to crash out if any step in snapshot + creation fails. */ + + /* 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 ); + + return 0; +} + +static int +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_WARNING(( "Failed to create the zstd compression stream" )); + return -1; + } + 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_WARNING(( "Failed to initialize the ostream" )); + err = -1; + goto cleanup; + } + + long seek = lseek( snapshot_ctx->snapshot_fd, 0, SEEK_SET ); + if( FD_UNLIKELY( seek!=0L ) ) { + FD_LOG_WARNING(( "Failed to seek to the start of the file" )); + err = -1; + goto cleanup; + } + + /* 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_WARNING(( "Failed to seek to the beginning of the file" )); + err = -1; + goto cleanup; + } + + 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_WARNING(( "Failed to read in the file" )); + goto cleanup; + } + + /* 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 ); + } + + cleanup: + + 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_WARNING(( "Failed to flush the ostream" )); + } + + if( FD_UNLIKELY( err ) ) { + return err; + } + + /* 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_WARNING(( "Failed to format directory string" )); + return -1; + } + + 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_WARNING(( "Failed to format directory string" )); + return -1; + } + + err = rename( tmp_directory_buf_zstd, directory_buf_zstd ); + if( FD_UNLIKELY( err<0 ) ) { + FD_LOG_WARNING(( "Failed to rename file from %s to %s (%i-%s)", tmp_directory_buf_zstd, directory_buf_zstd, errno, fd_io_strerror( errno ) )); + return -1; + } + + return 0; +} + +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 )); + + int err = 0; + + /* Validate that the snapshot_ctx is setup correctly. */ + + err = fd_snapshot_create_setup_and_validate_ctx( snapshot_ctx ); + if( FD_UNLIKELY( err ) ) { + return err; + } + + /* Setup the tar archive writer. */ + + err = fd_snapshot_create_setup_writer( snapshot_ctx ); + if( FD_UNLIKELY( err ) ) { + return err; + } + + /* Write out the version file. */ + + err = fd_snapshot_create_write_version( snapshot_ctx ); + if( FD_UNLIKELY( err ) ) { + return err; + } + + /* Dump the status cache and append it to the tar archive. */ + + err = fd_snapshot_create_write_status_cache( snapshot_ctx ); + if( FD_UNLIKELY( err ) ) { + return err; + } + + /* Populate and write out the manifest and append vecs. */ + + err = fd_snapshot_create_write_manifest_and_acc_vecs( snapshot_ctx, out_hash, out_capitalization ); + if( FD_UNLIKELY( err ) ) { + return err; + } + + /* Compress the tar file and write it out to the specified directory. */ + + err = fd_snapshot_create_compress( snapshot_ctx ); + if( FD_UNLIKELY( err ) ) { + return err; + } + + return err; + + } FD_SCRATCH_SCOPE_END; +} diff --git a/src/flamenco/snapshot/fd_snapshot_create.h b/src/flamenco/snapshot/fd_snapshot_create.h index 62cc106f8e..561fe9ab87 100644 --- a/src/flamenco/snapshot/fd_snapshot_create.h +++ b/src/flamenco/snapshot/fd_snapshot_create.h @@ -1,78 +1,140 @@ #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" - -struct fd_snapshot_create_private; -typedef struct fd_snapshot_create_private fd_snapshot_create_t; +#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" + +#include +#include +#include +#include + +/* 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) +#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; + + 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 c8bffbf335..f505d94dda 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 227 +#define FD_TYPE_NAME_COUNT 232 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", @@ -33,6 +35,7 @@ static char const * fd_type_names[FD_TYPE_NAME_COUNT] = { "fd_stake", "fd_stake_pair", "fd_stakes", + "fd_stakes_serializable", "fd_stakes_stake", "fd_bank_incremental_snapshot_persistence", "fd_node_vote_accounts", @@ -43,6 +46,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", @@ -56,6 +60,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 fda01d57dc..4cb34353bc 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; @@ -2981,50 +3153,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 { @@ -3034,53 +3284,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 ); @@ -4553,6 +4906,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 ); @@ -6062,213 +6562,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 ); @@ -7290,136 +8304,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; @@ -7455,20 +8768,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; @@ -7503,21 +8806,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; @@ -7549,22 +8841,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 ); @@ -7605,26 +8888,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 ); @@ -7642,19 +8914,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 ) { @@ -7673,16 +8940,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); @@ -7698,10 +8960,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; } @@ -14350,6 +15608,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 ); @@ -14359,6 +15623,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 ) { @@ -14384,6 +15650,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 ); @@ -14392,6 +15661,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; @@ -14439,12 +15709,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 { @@ -14470,7 +15748,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; @@ -14650,6 +15932,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; @@ -14666,6 +15967,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; @@ -14781,6 +16091,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 ); { @@ -14793,6 +16116,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; @@ -14938,6 +16266,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 ); @@ -14950,6 +16294,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; @@ -15023,6 +16375,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; @@ -15033,6 +16394,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) { @@ -15049,6 +16413,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 ); @@ -15063,9 +16429,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; } @@ -15095,11 +16463,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 ) { @@ -15126,10 +16498,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; } @@ -32294,6 +33670,15 @@ 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" +#undef REDBLK_T +#undef REDBLK_NAME +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 a26e998d68..afd63462e7 100644 --- a/src/flamenco/types/fd_types.h +++ b/src/flamenco/types/fd_types.h @@ -491,6 +491,61 @@ 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" +#undef REDBLK_T +#undef REDBLK_NAME +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 @@ -747,6 +802,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 @@ -1024,6 +1104,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) +/* This is the serializable version of 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; @@ -1049,8 +1209,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; @@ -1058,8 +1218,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; @@ -1280,6 +1440,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; @@ -2467,8 +2653,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) @@ -2497,7 +2687,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) @@ -5053,6 +5247,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 ); @@ -5065,6 +5263,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 ); @@ -5186,6 +5388,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 ); @@ -5330,6 +5556,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 ); @@ -5450,6 +5688,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 ); @@ -5624,6 +5874,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 b4011b399f..cbb6a5e6e0 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" } @@ -248,6 +249,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", @@ -335,6 +352,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", @@ -457,6 +486,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": "This is the serializable version of deserializable_versioned_bank." + }, { "name": "bank_hash_stats", "type": "struct", @@ -472,8 +541,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" } ] }, @@ -585,6 +654,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", @@ -1117,7 +1199,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 caffd5be59..84be715dae 100644 --- a/src/flamenco/types/gen_stubs.py +++ b/src/flamenco/types/gen_stubs.py @@ -413,6 +413,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/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..76d23cd163 --- /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 (%lu)", 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 (%lu)", 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; +}