From d5b71487563d08ff9cf19030c31a438842bb5aa9 Mon Sep 17 00:00:00 2001 From: nipatel-jump Date: Mon, 27 Oct 2025 16:57:32 +0000 Subject: [PATCH] solcap: v2 --- contrib/test/run_solcap_tests.sh | 3 - src/app/firedancer-dev/commands/backtest.c | 32 +- src/app/firedancer-dev/main.c | 2 + src/app/firedancer/topology.c | 22 + src/app/ledger/main.c | 2 +- src/app/shared/fd_config.h | 4 + src/disco/stem/fd_stem.c | 34 +- src/disco/topo/fd_topo.h | 6 + src/disco/topo/fd_topob.c | 1 + src/discof/capture/Local.mk | 6 + src/discof/capture/fd_capture_ctx.c | 308 ++++ src/discof/capture/fd_capture_ctx.h | 250 ++++ src/discof/capture/fd_capture_tile.c | 318 +++++ .../capture/fd_capture_tile.seccomppolicy | 33 + .../generated/fd_capture_tile_seccomp.h | 78 + src/discof/exec/fd_exec_tile.c | 155 +- src/discof/replay/fd_exec.h | 2 + src/discof/replay/fd_replay_tile.c | 81 +- src/flamenco/capture/Local.mk | 9 +- src/flamenco/capture/fd_solcap_diff.c | 1260 ----------------- src/flamenco/capture/fd_solcap_import.c | 346 ----- src/flamenco/capture/fd_solcap_proto.h | 365 ++--- src/flamenco/capture/fd_solcap_reader.c | 191 --- src/flamenco/capture/fd_solcap_reader.h | 147 -- src/flamenco/capture/fd_solcap_writer.c | 723 ++-------- src/flamenco/capture/fd_solcap_writer.h | 192 +-- src/flamenco/capture/fd_solcap_writer_stub.c | 84 -- src/flamenco/capture/fd_solcap_yaml.c | 559 -------- src/flamenco/rewards/fd_rewards.c | 44 +- src/flamenco/runtime/context/Local.mk | 3 - src/flamenco/runtime/context/fd_capture_ctx.c | 118 -- src/flamenco/runtime/context/fd_capture_ctx.h | 121 -- src/flamenco/runtime/fd_hashes.c | 15 +- src/flamenco/runtime/fd_runtime.c | 71 +- src/flamenco/runtime/fd_runtime.h | 2 +- src/flamenco/runtime/fd_txn_account.c | 1 - src/flamenco/runtime/tests/Local.mk | 6 +- src/flamenco/runtime/tests/fd_block_harness.c | 26 +- src/flamenco/runtime/tests/fd_sol_compat.c | 1 - src/flamenco/runtime/tests/test_dump_block.c | 2 +- src/flamenco/types/fd_fuzz_types.h | 1 - src/flamenco/types/fd_types.c | 6 - src/flamenco/types/fd_types.h | 5 +- src/flamenco/types/fd_types.json | 1 - 44 files changed, 1582 insertions(+), 4054 deletions(-) create mode 100644 src/discof/capture/Local.mk create mode 100644 src/discof/capture/fd_capture_ctx.c create mode 100644 src/discof/capture/fd_capture_ctx.h create mode 100644 src/discof/capture/fd_capture_tile.c create mode 100644 src/discof/capture/fd_capture_tile.seccomppolicy create mode 100644 src/discof/capture/generated/fd_capture_tile_seccomp.h delete mode 100644 src/flamenco/capture/fd_solcap_diff.c delete mode 100644 src/flamenco/capture/fd_solcap_import.c delete mode 100644 src/flamenco/capture/fd_solcap_reader.c delete mode 100644 src/flamenco/capture/fd_solcap_reader.h delete mode 100644 src/flamenco/capture/fd_solcap_writer_stub.c delete mode 100644 src/flamenco/capture/fd_solcap_yaml.c delete mode 100644 src/flamenco/runtime/context/fd_capture_ctx.c delete mode 100644 src/flamenco/runtime/context/fd_capture_ctx.h diff --git a/contrib/test/run_solcap_tests.sh b/contrib/test/run_solcap_tests.sh index afd5af68328..f6cff629900 100755 --- a/contrib/test/run_solcap_tests.sh +++ b/contrib/test/run_solcap_tests.sh @@ -76,8 +76,5 @@ $OBJDIR/bin/firedancer-dev configure init all --config $DUMP/$LEDGER/devnet-3987 $OBJDIR/bin/firedancer-dev backtest --config $DUMP/$LEDGER/devnet-398736132_current.toml $OBJDIR/bin/firedancer-dev configure fini all --config $DUMP/$LEDGER/devnet-398736132_current.toml &> /dev/null -$OBJDIR/bin/fd_solcap_import $DUMP/$LEDGER/bank_hash_details/ $DUMP/$LEDGER/solana.solcap -$OBJDIR/bin/fd_solcap_diff $DUMP/$LEDGER/solana.solcap $DUMP/$LEDGER/fd.solcap -v 4 - # check that the ledger is not corrupted after a run check_ledger_checksum diff --git a/src/app/firedancer-dev/commands/backtest.c b/src/app/firedancer-dev/commands/backtest.c index e8689bce4ce..2cc2aa9c8c6 100644 --- a/src/app/firedancer-dev/commands/backtest.c +++ b/src/app/firedancer-dev/commands/backtest.c @@ -27,7 +27,7 @@ #include "../../../discof/tower/fd_tower_tile.h" #include "../../../discof/replay/fd_exec.h" #include "../../../ballet/lthash/fd_lthash.h" -#include "../../../flamenco/runtime/context/fd_capture_ctx.h" +#include "../../../discof/capture/fd_capture_ctx.h" #include "../../../disco/pack/fd_pack_cost.h" #include "../../../flamenco/progcache/fd_progcache_admin.h" @@ -100,6 +100,14 @@ backtest_topo( config_t * config ) { #define FOR(cnt) for( ulong i=0UL; ifiredancer.store.max_completed_shred_sets, 1 ); fd_topob_tile_uses( topo, backt_tile, store_obj, FD_SHMEM_JOIN_MODE_READ_WRITE ); diff --git a/src/app/firedancer-dev/main.c b/src/app/firedancer-dev/main.c index db9d6607248..de10ed4d996 100644 --- a/src/app/firedancer-dev/main.c +++ b/src/app/firedancer-dev/main.c @@ -106,6 +106,7 @@ extern fd_topo_run_tile_t fd_tile_archiver_feeder; extern fd_topo_run_tile_t fd_tile_archiver_writer; extern fd_topo_run_tile_t fd_tile_archiver_playback; extern fd_topo_run_tile_t fd_tile_shredcap; +extern fd_topo_run_tile_t fd_tile_capture; extern fd_topo_run_tile_t fd_tile_snapct; extern fd_topo_run_tile_t fd_tile_snapld; @@ -161,6 +162,7 @@ fd_topo_run_tile_t * TILES[] = { &fd_tile_snapwr, &fd_tile_genesi, &fd_tile_ipecho, + &fd_tile_capture, NULL, }; diff --git a/src/app/firedancer/topology.c b/src/app/firedancer/topology.c index 3269cfdece3..d24c9630236 100644 --- a/src/app/firedancer/topology.c +++ b/src/app/firedancer/topology.c @@ -20,6 +20,7 @@ #include "../../util/tile/fd_tile_private.h" #include "../../discof/restore/utils/fd_ssctrl.h" #include "../../discof/restore/utils/fd_ssmsg.h" +#include "../../flamenco/capture/fd_solcap_writer.h" #include "../../flamenco/progcache/fd_progcache_admin.h" #include "../../vinyl/meta/fd_vinyl_meta.h" @@ -271,6 +272,8 @@ fd_topo_initialize( config_t * config ) { topo->max_page_size = fd_cstr_to_shmem_page_sz( config->hugetlbfs.max_page_size ); topo->gigantic_page_threshold = config->hugetlbfs.gigantic_page_threshold_mib << 20; + int solcap_enabled = strlen( config->capture.solcap_capture ) > 0; + /* topo, name */ fd_topob_wksp( topo, "metric" ); fd_topob_wksp( topo, "genesi" ); @@ -512,6 +515,11 @@ fd_topo_initialize( config_t * config ) { /**/ fd_topob_tile( topo, "poh", "poh", "metric_in", tile_to_cpu[ topo->tile_cnt ], 0, 1 ); FOR(sign_tile_cnt) fd_topob_tile( topo, "sign", "sign", "metric_in", tile_to_cpu[ topo->tile_cnt ], 0, 1 ); + if( FD_UNLIKELY( solcap_enabled ) ) { + fd_topob_wksp( topo, "captur" ); + fd_topob_tile( topo, "captur", "captur", "metric_in", tile_to_cpu[ topo->tile_cnt ], 0, 0 ); + } + /* topo, tile_name, tile_kind_id, fseq_wksp, link_name, link_kind_id, reliable, polled */ FOR(gossvf_tile_cnt) for( ulong j=0UL; jtile_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. " @@ -1252,6 +1269,11 @@ fd_topo_configure_tile( fd_topo_tile_t * tile, tile->shredcap.enable_publish_stake_weights = 0; /* this is not part of the config */ strncpy( tile->shredcap.manifest_path, "", PATH_MAX ); /* this is not part of the config */ + } else if( FD_UNLIKELY( !strcmp( tile->name, "captur" ) ) ) { + + tile->capctx.capture_start_slot = config->capture.capture_start_slot; + strncpy( tile->capctx.solcap_capture, config->capture.solcap_capture, sizeof(tile->capctx.solcap_capture) ); + } else { FD_LOG_ERR(( "unknown tile name `%s`", tile->name )); } diff --git a/src/app/ledger/main.c b/src/app/ledger/main.c index ba5547637ae..4beab697ca1 100644 --- a/src/app/ledger/main.c +++ b/src/app/ledger/main.c @@ -1,6 +1,6 @@ #include "../../flamenco/types/fd_types.h" #include "../../flamenco/runtime/fd_rocksdb.h" -#include "../../flamenco/runtime/context/fd_capture_ctx.h" +#include "../../discof/capture/fd_capture_ctx.h" #include #include diff --git a/src/app/shared/fd_config.h b/src/app/shared/fd_config.h index fafed1dea8e..71c4adabc27 100644 --- a/src/app/shared/fd_config.h +++ b/src/app/shared/fd_config.h @@ -156,6 +156,10 @@ struct fd_configf { struct { ulong max_completed_shred_sets; } store; + + struct { + char path[ PATH_MAX ]; + } capctx; }; typedef struct fd_configf fd_configf_t; diff --git a/src/disco/stem/fd_stem.c b/src/disco/stem/fd_stem.c index 7a46139747a..6d0713f401a 100644 --- a/src/disco/stem/fd_stem.c +++ b/src/disco/stem/fd_stem.c @@ -37,6 +37,14 @@ or monitoring tools. The ctx is a user-provided context object from when the stem tile was initialized. + CUSTOM_INPUT_SELECTION + Is called to determine if the stem should shuffle the input + selection. The ctx is a user-provided context object from when the + stem tile was initialized. The stem should return 1 if the input + selection should be shuffled, 0 otherwise. Used alongside + STEM_CUSTOM_INPUT_ADVANCE_FLAG to determine if the input selection + should be advanced. + BEFORE_CREDIT Is called every iteration of the stem run loop, whether there is a new frag ready to receive or not. This callback is also still @@ -484,14 +492,19 @@ STEM_(run1)( ulong in_cnt, /* We also do the same with the ins to prevent there being a correlated order frag origins from different inputs downstream at extreme fan in and extreme in load. */ - - if( FD_LIKELY( in_cnt>1UL ) ) { +#ifdef STEM_CUSTOM_INPUT_SELECTION + int shuffle_flag = STEM_CUSTOM_INPUT_ADVANCE_FLAG(ctx); +#else + int shuffle_flag = 1; +#endif + if( FD_LIKELY( in_cnt>1UL && shuffle_flag ) ) { swap_idx = (ulong)fd_rng_uint_roll( rng, (uint)in_cnt ); fd_stem_tile_in_t in_tmp; in_tmp = in[ swap_idx ]; in[ swap_idx ] = in[ 0 ]; in[ 0 ] = in_tmp; } + } /* Reload housekeeping timer */ @@ -579,10 +592,12 @@ STEM_(run1)( ulong in_cnt, } #endif - fd_stem_tile_in_t * this_in = &in[ in_seq ]; +fd_stem_tile_in_t * this_in = &in[ in_seq ]; +#ifdef STEM_CUSTOM_INPUT_SELECTION +#else in_seq++; if( in_seq>=in_cnt ) in_seq = 0UL; /* cmov */ - +#endif /* Check if this in has any new fragments to mux */ ulong this_in_seq = this_in->seq; @@ -714,6 +729,15 @@ STEM_(run1)( ulong in_cnt, this_in->accum[ FD_METRICS_COUNTER_LINK_CONSUMED_COUNT_OFF ]++; this_in->accum[ FD_METRICS_COUNTER_LINK_CONSUMED_SIZE_BYTES_OFF ] += (uint)sz; + /* Custom input selection: advance to next input based on flag */ +#ifdef STEM_CUSTOM_INPUT_SELECTION + int should_advance = STEM_CUSTOM_INPUT_ADVANCE_FLAG(ctx); + if( FD_LIKELY( should_advance ) ) { + in_seq++; + if( in_seq>=in_cnt ) in_seq = 0UL; /* cmov */ + } +#endif + metric_regime_ticks[1] += housekeeping_ticks; metric_regime_ticks[4] += prefrag_ticks; long next = fd_tickcount(); @@ -815,3 +839,5 @@ STEM_(run)( fd_topo_t * topo, #undef STEM_CALLBACK_RETURNABLE_FRAG #undef STEM_CALLBACK_AFTER_FRAG #undef STEM_CALLBACK_AFTER_POLL_OVERRUN +#undef STEM_CUSTOM_INPUT_SELECTION +#undef STEM_CUSTOM_INPUT_ADVANCE_FLAG diff --git a/src/disco/topo/fd_topo.h b/src/disco/topo/fd_topo.h index edfe65dde69..db7e1068289 100644 --- a/src/disco/topo/fd_topo.h +++ b/src/disco/topo/fd_topo.h @@ -584,6 +584,12 @@ struct fd_topo_tile { uint target_gid; uint target_uid; } genesi; + + struct { + ulong capture_start_slot; + char solcap_capture[ PATH_MAX ]; + int solcap_fd; + } capctx; }; }; diff --git a/src/disco/topo/fd_topob.c b/src/disco/topo/fd_topob.c index 37007b5817d..4c7fffa427a 100644 --- a/src/disco/topo/fd_topob.c +++ b/src/disco/topo/fd_topob.c @@ -385,6 +385,7 @@ fd_topob_auto_layout( fd_topo_t * topo, "snapld", /* FIREDANCER only */ "snapdc", /* FIREDANCER only */ "snapin", /* FIREDANCER only */ + "captur", /* FIREDANCER only */ "arch_f", /* FIREDANCER only */ "arch_w", /* FIREDANCER only */ }; diff --git a/src/discof/capture/Local.mk b/src/discof/capture/Local.mk new file mode 100644 index 00000000000..3c36bf29721 --- /dev/null +++ b/src/discof/capture/Local.mk @@ -0,0 +1,6 @@ +ifdef FD_HAS_INT128 +ifdef FD_HAS_ALLOCA +$(call add-hdrs,fd_capture_ctx.h) +$(call add-objs,fd_capture_ctx fd_capture_tile,fd_discof) +endif +endif diff --git a/src/discof/capture/fd_capture_ctx.c b/src/discof/capture/fd_capture_ctx.c new file mode 100644 index 00000000000..030cae3612b --- /dev/null +++ b/src/discof/capture/fd_capture_ctx.c @@ -0,0 +1,308 @@ +#include "fd_capture_ctx.h" +#include "../../flamenco/capture/fd_solcap_writer.h" +#include "../../tango/mcache/fd_mcache.h" +#include "../../tango/dcache/fd_dcache.h" +#include "../../tango/fd_tango_base.h" +#include "../../tango/fseq/fd_fseq.h" + +#include + +void * +fd_capture_ctx_new( void * mem ) { + if( FD_UNLIKELY( !mem ) ) { + FD_LOG_WARNING(( "NULL mem" )); + return NULL; + } + + if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)mem, fd_capture_ctx_align() ) ) ) { + FD_LOG_WARNING(( "misaligned mem" )); + return NULL; + } + + FD_SCRATCH_ALLOC_INIT( l, mem ); + fd_capture_ctx_t * capture_ctx = FD_SCRATCH_ALLOC_APPEND( l, fd_capture_ctx_align(), sizeof(fd_capture_ctx_t) ); + fd_solcap_writer_t * capture = FD_SCRATCH_ALLOC_APPEND( l, fd_solcap_writer_align(), fd_solcap_writer_footprint() ); + FD_TEST( FD_SCRATCH_ALLOC_FINI( l, fd_capture_ctx_align() ) == (ulong)mem + fd_capture_ctx_footprint() ); + + fd_memset( capture_ctx, 0, sizeof(fd_capture_ctx_t) ); + + capture_ctx->capture = fd_solcap_writer_new( capture ); + if( FD_UNLIKELY( !capture_ctx->capture ) ) { + FD_LOG_WARNING(( "failed to create solcap writer" )); + return NULL; + } + + FD_COMPILER_MFENCE(); + FD_VOLATILE( capture_ctx->magic ) = FD_CAPTURE_CTX_MAGIC; + FD_COMPILER_MFENCE(); + + return mem; +} + +fd_capture_ctx_t * +fd_capture_ctx_join( void * mem ) { + if( FD_UNLIKELY( !mem ) ) { + FD_LOG_WARNING(( "NULL block" )); + return NULL; + } + + fd_capture_ctx_t * ctx = (fd_capture_ctx_t *) mem; + + if( FD_UNLIKELY( ctx->magic!=FD_CAPTURE_CTX_MAGIC ) ) { + FD_LOG_WARNING(( "bad magic" )); + return NULL; + } + + return ctx; +} + +void * +fd_capture_ctx_leave( fd_capture_ctx_t * ctx) { + if( FD_UNLIKELY( !ctx ) ) { + FD_LOG_WARNING(( "NULL block" )); + return NULL; + } + + if( FD_UNLIKELY( ctx->magic!=FD_CAPTURE_CTX_MAGIC ) ) { + FD_LOG_WARNING(( "bad magic" )); + return NULL; + } + + return (void *) ctx; +} + +void * +fd_capture_ctx_delete( void * mem ) { + if( FD_UNLIKELY( !mem ) ) { + FD_LOG_WARNING(( "NULL mem" )); + return NULL; + } + + if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)mem, fd_capture_ctx_align() ) ) ) { + FD_LOG_WARNING(( "misaligned mem" )); + return NULL; + } + + fd_capture_ctx_t * hdr = (fd_capture_ctx_t *)mem; + if( FD_UNLIKELY( hdr->magic!=FD_CAPTURE_CTX_MAGIC ) ) { + FD_LOG_WARNING(( "bad magic" )); + return NULL; + } + + if( FD_UNLIKELY( fd_solcap_writer_delete( hdr->capture ) == NULL ) ) { + FD_LOG_WARNING(( "failed deleting capture" )); + return NULL; + } + + FD_COMPILER_MFENCE(); + FD_VOLATILE( hdr->magic ) = 0UL; + FD_COMPILER_MFENCE(); + + return mem; +} + + +static void +_wait_to_write_solcap_msg(fd_capture_link_buf_t * buf) { + if( FD_LIKELY( buf->fseq ) ) { + while( FD_UNLIKELY( fd_seq_diff( buf->seq, fd_fseq_query( buf->fseq ) ) > 2L ) ) { + FD_SPIN_PAUSE(); + } + } +} + +static uint +_valid_slot_range(fd_capture_ctx_t * ctx, ulong slot) { + /* When solcap_start_slot is 0 (not set), capture all slots */ + if( FD_LIKELY( ctx->solcap_start_slot == 0UL ) ) { + return 1; + } + if( FD_UNLIKELY( slot < ctx->solcap_start_slot ) ) { + return 0; + } + return 1; +} + +void +fd_cap_link_translate_account_update_buf( fd_capture_ctx_t * ctx, + ulong txn_idx, + fd_pubkey_t const * key, + fd_solana_account_meta_t const * info, + ulong slot, + uchar const * data, + ulong data_sz) { + + if( FD_UNLIKELY( !ctx || !ctx->capctx_buf.buf ) ) return; + if ( FD_UNLIKELY( !_valid_slot_range( ctx, slot ) ) ) return; + + fd_capture_link_buf_t * buf = ctx->capctx_buf.buf; + + _wait_to_write_solcap_msg(buf); + + ulong msg_sz = sizeof(fd_solcap_buf_msg_t) + sizeof(fd_solcap_account_update_hdr_t); + + uchar * dst = (uchar *)fd_chunk_to_laddr( buf->mem, buf->chunk ); + char * ptr = (char *)dst; + + fd_solcap_buf_msg_t msg = { + .sig = SOLCAP_WRITE_ACCOUNT_HDR, + .slot = slot, + .txn_idx = txn_idx, + }; + fd_memcpy(ptr, &msg, sizeof(fd_solcap_buf_msg_t)); + ptr += sizeof(fd_solcap_buf_msg_t); + + fd_solcap_account_update_hdr_t account_hdr = { + .key = *key, + .info = *info, + .data_sz = data_sz, + }; + + fd_memcpy(ptr, &account_hdr, sizeof(fd_solcap_account_update_hdr_t)); + + ulong write_cnt = (data_sz + SOLCAP_WRITE_ACCOUNT_DATA_MTU - 1) / SOLCAP_WRITE_ACCOUNT_DATA_MTU; + if( data_sz == 0 ) write_cnt = 0; + + int has_data = (write_cnt > 0); + ulong ctl = fd_frag_meta_ctl( 0UL, 1UL, has_data ? 0UL : 1UL, 0UL ); + fd_mcache_publish( buf->mcache, buf->depth, buf->seq, 0UL, buf->chunk, msg_sz, ctl, 0UL, 0UL ); + buf->chunk = fd_dcache_compact_next( buf->chunk, msg_sz, buf->chunk0, buf->wmark ); + buf->seq++; + + if( !has_data ) return; + + for ( ulong i = 0; i < write_cnt; i++ ) { + _wait_to_write_solcap_msg(buf); + + dst = (uchar *)fd_chunk_to_laddr( buf->mem, buf->chunk ); + ptr = (char *)dst; + + ulong fragment_data_sz = SOLCAP_WRITE_ACCOUNT_DATA_MTU; + int is_last = (i == write_cnt - 1); + + if( is_last ) { + fragment_data_sz = data_sz - i * SOLCAP_WRITE_ACCOUNT_DATA_MTU; + } + + fd_memcpy(ptr, &fragment_data_sz, sizeof(ulong)); + ptr += sizeof(ulong); + fd_memcpy(ptr, data + i * SOLCAP_WRITE_ACCOUNT_DATA_MTU, fragment_data_sz); + + msg_sz = sizeof(ulong) + fragment_data_sz; + + ctl = fd_frag_meta_ctl( 0UL, 0UL, is_last ? 1UL : 0UL, 0UL ); + + fd_mcache_publish( buf->mcache, buf->depth, buf->seq, 0UL, buf->chunk, msg_sz, ctl, 0UL, 0UL ); + buf->chunk = fd_dcache_compact_next( buf->chunk, msg_sz, buf->chunk0, buf->wmark ); + buf->seq++; + } +} + +void +fd_cap_link_translate_account_update_file(fd_capture_ctx_t * ctx, + ulong txn_idx, + fd_pubkey_t const * key, + fd_solana_account_meta_t const * info, + ulong slot, + uchar const * data, + ulong data_sz) { + if( FD_UNLIKELY( !ctx || !ctx->capture ) ) return; + if ( FD_UNLIKELY( !_valid_slot_range( ctx, slot ) ) ) return; + + fd_solcap_writer_t * writer = ctx->capture; + + /* Prepare message header */ + fd_solcap_buf_msg_t msg_hdr = { + .sig = SOLCAP_WRITE_ACCOUNT_HDR, + .slot = slot, + .txn_idx = txn_idx, + }; + + /* Prepare account update header */ + fd_solcap_account_update_hdr_t account_update = { + .key = *key, + .info = *info, + .data_sz = data_sz, + }; + + /* Write the header (EPB + internal header + account metadata) */ + uint32_t block_len = fd_solcap_write_account_hdr( writer, &msg_hdr, &account_update ); + + /* Write the account data */ + fd_solcap_write_account_data( writer, data, data_sz ); + + /* Write the footer */ + fd_solcap_write_ftr( writer, block_len ); +} + +void +fd_cap_link_write_bank_preimage_buf(fd_capture_ctx_t * ctx, + ulong slot, + fd_hash_t const * bank_hash, + fd_hash_t const * prev_bank_hash, + fd_hash_t const * accounts_lt_hash_checksum, + fd_hash_t const * poh_hash, + ulong signature_cnt) { + if( FD_UNLIKELY( !ctx || !ctx->capctx_buf.buf ) ) return; + if ( FD_UNLIKELY( !_valid_slot_range( ctx, slot ) ) ) return; + + fd_capture_link_buf_t * buf = ctx->capctx_buf.buf; + + _wait_to_write_solcap_msg(buf); + + uchar * dst = (uchar *)fd_chunk_to_laddr( buf->mem, buf->chunk ); + char * ptr = (char *)dst; + + fd_solcap_buf_msg_t msg = { + .sig = SOLCAP_WRITE_BANK_PREIMAGE, + .slot = slot, + .txn_idx = 0 + }; + fd_memcpy(ptr, &msg, sizeof(fd_solcap_buf_msg_t)); + ptr += sizeof(fd_solcap_buf_msg_t); + + fd_solcap_bank_preimage_t bank_preimage = { + .bank_hash = *bank_hash, + .prev_bank_hash = *prev_bank_hash, + .accounts_lt_hash_checksum = *accounts_lt_hash_checksum, + .poh_hash = *poh_hash, + .signature_cnt = signature_cnt + }; + fd_memcpy(ptr, &bank_preimage, sizeof(fd_solcap_bank_preimage_t)); + ulong ctl = fd_frag_meta_ctl( 0UL, 1UL, 1UL, 0UL ); + fd_mcache_publish( buf->mcache, buf->depth, buf->seq, 0UL, buf->chunk, sizeof(fd_solcap_buf_msg_t) + sizeof(fd_solcap_bank_preimage_t), ctl, 0UL, 0UL ); + buf->chunk = fd_dcache_compact_next( buf->chunk, sizeof(fd_solcap_buf_msg_t) + sizeof(fd_solcap_bank_preimage_t), buf->chunk0, buf->wmark ); + buf->seq++; +} + +void +fd_cap_link_write_bank_preimage_file(fd_capture_ctx_t * ctx, + ulong slot, + fd_hash_t const * bank_hash, + fd_hash_t const * prev_bank_hash, + fd_hash_t const * accounts_lt_hash_checksum, + fd_hash_t const * poh_hash, + ulong signature_cnt) { + if( FD_UNLIKELY( !ctx || !ctx->capture ) ) return; + if ( FD_UNLIKELY( !_valid_slot_range( ctx, slot ) ) ) return; + + fd_solcap_writer_t * writer = ctx->capture; + + fd_solcap_buf_msg_t msg_hdr = { + .sig = SOLCAP_WRITE_BANK_PREIMAGE, + .slot = slot, + .txn_idx = 0 + }; + + fd_solcap_bank_preimage_t bank_preimage = { + .bank_hash = *bank_hash, + .prev_bank_hash = *prev_bank_hash, + .accounts_lt_hash_checksum = *accounts_lt_hash_checksum, + .poh_hash = *poh_hash, + .signature_cnt = signature_cnt + }; + + uint32_t block_len = fd_solcap_write_bank_preimage( writer, &msg_hdr, &bank_preimage ); + + fd_solcap_write_ftr( writer, block_len ); +} diff --git a/src/discof/capture/fd_capture_ctx.h b/src/discof/capture/fd_capture_ctx.h new file mode 100644 index 00000000000..4b66e08bf5d --- /dev/null +++ b/src/discof/capture/fd_capture_ctx.h @@ -0,0 +1,250 @@ +#ifndef HEADER_fd_src_discof_capture_fd_capture_ctx_h +#define HEADER_fd_src_discof_capture_fd_capture_ctx_h + +#include "../../flamenco/capture/fd_solcap_writer.h" +#include "../../flamenco/runtime/fd_runtime_const.h" +#include "../../flamenco/fd_rwlock.h" +#include "../../util/fd_util_base.h" +#include "../../util/log/fd_log.h" +#include +#include "../../flamenco/capture/fd_solcap_proto.h" +#include "../../tango/mcache/fd_mcache.h" +#include "../../tango/dcache/fd_dcache.h" +#include "../../tango/fd_tango_base.h" + +/* + +Nishk (TODO): Write docs for capture context + +*/ + + +/* fd_capture_ctx_account_update_msg_t is the message sent from + exec tile to replay tile that notifies the solcap writer that an + account update has occurred. */ + +struct __attribute__((packed)) fd_capture_ctx_account_update_msg { + fd_pubkey_t pubkey; + fd_solana_account_meta_t info; + ulong data_sz; + fd_hash_t hash; + ulong bank_idx; + /* Account data follows immediately after this struct */ +}; +typedef struct fd_capture_ctx_account_update_msg fd_capture_ctx_account_update_msg_t; + +typedef struct fd_capture_link_vt fd_capture_link_vt_t; + +struct fd_capture_link { + const fd_capture_link_vt_t * vt; +}; +typedef struct fd_capture_link fd_capture_link_t; + +struct fd_capture_link_buf { + fd_capture_link_t base; + ulong idx; + fd_wksp_t * mem; + ulong chunk0; + ulong wmark; + ulong chunk; + fd_frag_meta_t * mcache; + ulong depth; + ulong seq; + ulong * fseq; +}; +typedef struct fd_capture_link_buf fd_capture_link_buf_t; + +struct fd_capture_link_file { + fd_capture_link_t base; + FILE * file; +}; +typedef struct fd_capture_link_file fd_capture_link_file_t; + +struct fd_capture_link_vt { + void (* write_account_update)( fd_capture_ctx_t * ctx, + ulong txn_idx, + fd_pubkey_t const * key, + fd_solana_account_meta_t const * info, + ulong slot, + uchar const * data, + ulong data_sz); + + void (* write_bank_preimage)( fd_capture_ctx_t * ctx, + ulong slot, + fd_hash_t const * bank_hash, + fd_hash_t const * prev_bank_hash, + fd_hash_t const * accounts_lt_hash_checksum, + fd_hash_t const * poh_hash, + ulong signature_cnt); +}; + + +/* Context needed to do solcap capture during execution of transactions */ + +struct fd_capture_ctx { + ulong magic; /* ==FD_CAPTURE_CTX_MAGIC */ + + fd_capture_link_t * capture_link; + union { + fd_capture_link_buf_t * buf; + fd_capture_link_file_t * file; + } capctx_buf; + + /* Solcap */ + ulong solcap_start_slot; + fd_solcap_writer_t * capture; + + ulong current_txn_idx; + + /*======== PROTOBUF ========*/ + char const * dump_proto_output_dir; + char const * dump_proto_sig_filter; + ulong dump_proto_start_slot; + + /* Instruction Capture */ + int dump_instr_to_pb; + + /* Transaction Capture */ + int dump_txn_to_pb; + + /* Block Capture */ + int dump_block_to_pb; + + /* Syscall Capture */ + int dump_syscall_to_pb; + + /* ELF Capture */ + int dump_elf_to_pb; + +}; +typedef struct fd_capture_ctx fd_capture_ctx_t; + +static inline ulong +fd_capture_ctx_align( void ) { + return fd_ulong_max( alignof(fd_capture_ctx_t), fd_solcap_writer_align() ); +} + +static inline ulong +fd_capture_ctx_footprint( void ) { + ulong l = FD_LAYOUT_INIT; + l = FD_LAYOUT_APPEND ( l, fd_capture_ctx_align(), sizeof(fd_capture_ctx_t) ); + l = FD_LAYOUT_APPEND ( l, fd_solcap_writer_align(), fd_solcap_writer_footprint() ); + return FD_LAYOUT_FINI ( l, fd_capture_ctx_align() ); +} + +#define FD_CAPTURE_CTX_MAGIC (0x193ECD2A6C395195UL) /* random */ + +FD_PROTOTYPES_BEGIN + +void * +fd_capture_ctx_new( void * mem ); + +fd_capture_ctx_t * +fd_capture_ctx_join( void * mem ); + +void * +fd_capture_ctx_leave( fd_capture_ctx_t * ctx ); + +void * +fd_capture_ctx_delete( void * mem ); + +FD_PROTOTYPES_END + +/* + Solcap Buffer Writables + + All the following functions are helpers to be used by subscribers of + the shared capture context buffer to write solcap messages out to the + buffer. +*/ + +struct __attribute__((packed)) fd_solcap_buf_msg { + ushort sig; + ulong slot; + ulong txn_idx; + /* Data follows immediately after this struct in memory */ +}; +typedef struct fd_solcap_buf_msg fd_solcap_buf_msg_t; + +/* The following capctx_buf_translate functions are wrappers used by + the funtime to drop solcap buffer messages into the capctx buffer. + + Each is mapped directly to a specific solcap writer function. +*/ + +void +fd_cap_link_translate_account_update_buf(fd_capture_ctx_t * ctx, + ulong txn_idx, + fd_pubkey_t const * key, + fd_solana_account_meta_t const * info, + ulong slot, + uchar const * data, + ulong data_sz); + +void +fd_cap_link_translate_account_update_file( fd_capture_ctx_t * ctx, + ulong txn_idx, + fd_pubkey_t const * key, + fd_solana_account_meta_t const * info, + ulong slot, + uchar const * data, + ulong data_sz); + +static inline void +fd_capture_link_write_account_update( fd_capture_ctx_t * ctx, + ulong txn_idx, + fd_pubkey_t const * key, + fd_solana_account_meta_t const * info, + ulong slot, + uchar const * data, + ulong data_sz) { + ctx->capture_link->vt->write_account_update(ctx, txn_idx, key, info, slot, data, data_sz); +} + +void +fd_cap_link_write_bank_preimage_buf( fd_capture_ctx_t * ctx, + ulong slot, + fd_hash_t const * bank_hash, + fd_hash_t const * prev_bank_hash, + fd_hash_t const * accounts_lt_hash_checksum, + fd_hash_t const * poh_hash, + ulong signature_cnt); + +void +fd_cap_link_write_bank_preimage_file( fd_capture_ctx_t * ctx, + ulong slot, + fd_hash_t const * bank_hash, + fd_hash_t const * prev_bank_hash, + fd_hash_t const * accounts_lt_hash_checksum, + fd_hash_t const * poh_hash, + ulong signature_cnt); + +static inline void +fd_capture_link_write_bank_preimage( fd_capture_ctx_t * ctx, + ulong slot, + fd_hash_t const * bank_hash, + fd_hash_t const * prev_bank_hash, + fd_hash_t const * accounts_lt_hash_checksum, + fd_hash_t const * poh_hash, + ulong signature_cnt) { + ctx->capture_link->vt->write_bank_preimage(ctx, slot, bank_hash, prev_bank_hash, accounts_lt_hash_checksum, poh_hash, signature_cnt); +} + +static const +fd_capture_link_vt_t fd_capture_link_buf_vt = { + .write_account_update = fd_cap_link_translate_account_update_buf, + .write_bank_preimage = fd_cap_link_write_bank_preimage_buf, +}; + +static const +fd_capture_link_vt_t fd_capture_link_file_vt = { + .write_account_update = fd_cap_link_translate_account_update_file, + .write_bank_preimage = fd_cap_link_write_bank_preimage_file, +}; + +uint32_t +fd_capctx_buf_process_msg( fd_capture_ctx_t * capture_ctx, + fd_solcap_buf_msg_t * msg_hdr, + char * actual_data ); + +#endif /* HEADER_fd_src_discof_capture_fd_capture_ctx_h */ diff --git a/src/discof/capture/fd_capture_tile.c b/src/discof/capture/fd_capture_tile.c new file mode 100644 index 00000000000..559313cb94f --- /dev/null +++ b/src/discof/capture/fd_capture_tile.c @@ -0,0 +1,318 @@ +#include "../../disco/topo/fd_topo.h" +#include "../../util/pod/fd_pod.h" +#include "../../util/log/fd_log.h" +#include "../../tango/dcache/fd_dcache.h" +#include "../../tango/fd_tango_base.h" + +#include +#include +#include +#include +#include +#include + +#include "fd_capture_ctx.h" +#include "../../flamenco/capture/fd_solcap_writer.h" +#include "generated/fd_capture_tile_seccomp.h" + + +/* The capture context tile is responsible for managing capture context + for debugging runtime execution. + + The tile is enabled when capture context is enabled in the config. + + ``` + [capture] + solcap_capture = "/path/to/filename.solcap.pcapng" + ``` + + When enabled, the each tile that writes to solcap will initalize + a mcache/dcache pair to use as a shared buffer to communicate with + the capture tile. Each tile that requires solcap writes will declare + their own capture context to pass into runtime execution or post + execution to write to the buffer via API's provided in the capture + context and notify the capture tile. The capture tile will then + process the messages from the link and write out to the file. + + More information about capture context in fd_capture_ctx.h + + Capture Tile: + + The capture tile is intialized with the incoming links from the + topology, for each tile that requires solcap writes. The handling for + messages is slightly altered from that of the normal stem run loop. + + The messages sent to the capture tile are bounded by the size of the + 10mb (size of account data) + (much smaller) header information. In + order to handle the larger messages in an efficient manner, + the tile providing the data will send the data in chunks of at most + 128kb, on a link ~4mb. This is in order to avoid cache trashing. This + is done by using a custom input selection control to shuffle the + incoming frags and origin in-link only if the current message has + been read completely using the SOM/EOM flags. +*/ + +struct __attribute__((packed)) fd_capture_tile_ctx { + ulong tile_idx; + + ulong msg_idx; + ushort msg_set_sig; + ulong msg_set_slot; + uint32_t block_len; + + /* Capture context management */ + fd_capture_ctx_t * capture_ctx; + fd_capture_link_t * capctx_buf; + + FILE * file; + + /* Incoming links for mcache/dcache processing */ + struct { + fd_wksp_t * mem; + ulong chunk0; + ulong wmark; + ulong mtu; + } in[32]; + + ulong in_cnt; + + /* Custom input selection control */ + int advance_link; + +}; + +typedef struct fd_capture_tile_ctx fd_capture_tile_ctx_t; + + +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 ) { + (void)tile; + ulong l = FD_LAYOUT_INIT; + l = FD_LAYOUT_APPEND ( l, alignof(fd_capture_tile_ctx_t), sizeof(fd_capture_tile_ctx_t) ); + l = FD_LAYOUT_APPEND ( l, fd_capture_ctx_align(), fd_capture_ctx_footprint() ); + return FD_LAYOUT_FINI( l, scratch_align() ); +} + +static ulong +populate_allowed_seccomp( fd_topo_t const * topo FD_PARAM_UNUSED, + fd_topo_tile_t const * tile, + ulong out_cnt, + struct sock_filter * out ) { + populate_sock_filter_policy_fd_capture_tile( out_cnt, + out, + (uint)fd_log_private_logfile_fd(), + (uint)tile->capctx.solcap_fd ); + return sock_filter_policy_fd_capture_tile_instr_cnt; +} + +static ulong +populate_allowed_fds( fd_topo_t const * topo FD_PARAM_UNUSED, + fd_topo_tile_t const * tile, + ulong out_fds_cnt FD_PARAM_UNUSED, + int * out_fds ) { + 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(); + if( FD_LIKELY( -1!=tile->capctx.solcap_fd ) ) + out_fds[ out_cnt++ ] = tile->capctx.solcap_fd; + + return out_cnt; +} + +/* + fd_capctx_buf_process_msg is responsible for processing the message + from the shared buffer. It writes the message to the solcap file using + the solcap writer API's. +*/ +uint32_t +fd_capctx_buf_process_msg(fd_capture_ctx_t * capture_ctx, + fd_solcap_buf_msg_t * msg_hdr, + char * actual_data ) { + uint32_t block_len = 0; + switch ( msg_hdr->sig ) { + case SOLCAP_WRITE_ACCOUNT_HDR: + { + fd_solcap_account_update_hdr_t * account_update = (fd_solcap_account_update_hdr_t *)actual_data; + block_len = fd_solcap_write_account_hdr( capture_ctx->capture, msg_hdr, account_update ); + break; + } + case SOLCAP_WRITE_ACCOUNT_DATA: + { + ulong msg_sz = *(ulong *)actual_data; + actual_data += sizeof(ulong); + block_len = fd_solcap_write_account_data( capture_ctx->capture, actual_data, msg_sz ); + break; + } + case SOLCAP_WRITE_BANK_PREIMAGE: + { + fd_solcap_bank_preimage_t * bank_preimage = (fd_solcap_bank_preimage_t *)actual_data; + block_len = fd_solcap_write_bank_preimage( capture_ctx->capture, msg_hdr, bank_preimage ); + break; + } + default: + FD_LOG_ERR(( "Unknown signal: %d", msg_hdr->sig )); + break; + } + return block_len; +} + +static inline int +returnable_frag( fd_capture_tile_ctx_t * ctx, + ulong in_idx, + ulong seq FD_PARAM_UNUSED, + ulong sig FD_PARAM_UNUSED, + ulong chunk, + ulong sz, + ulong ctl, + ulong tsorig FD_PARAM_UNUSED, + ulong tspub FD_PARAM_UNUSED, + fd_stem_context_t * stem FD_PARAM_UNUSED ) { + + if( FD_UNLIKELY( in_idx >= ctx->in_cnt ) ) return 0; + + if( FD_UNLIKELY( chunk < ctx->in[in_idx].chunk0 || chunk > ctx->in[in_idx].wmark || sz > ctx->in[in_idx].mtu ) ) return 0; + + uchar const * data = fd_chunk_to_laddr_const( ctx->in[in_idx].mem, chunk ); + + int som = fd_frag_meta_ctl_som(ctl); + int eom = fd_frag_meta_ctl_eom(ctl); + + fd_solcap_buf_msg_t msg_hdr_storage; + fd_solcap_buf_msg_t * msg_hdr = NULL; + char * actual_data; + if( som ) { + msg_hdr = (fd_solcap_buf_msg_t *)data; + actual_data = (char *)(data + sizeof(fd_solcap_buf_msg_t)); + ctx->msg_set_slot = msg_hdr->slot; + ctx->msg_set_sig = SOLCAP_SIG_MAP(msg_hdr->sig); + } else { + msg_hdr_storage.sig = ctx->msg_set_sig; + msg_hdr_storage.slot = ctx->msg_set_slot; + msg_hdr_storage.txn_idx = 0; /* Not used for continuation fragments */ + msg_hdr = &msg_hdr_storage; + actual_data = (char *)data; + } + + uint32_t block_len = fd_capctx_buf_process_msg( ctx->capture_ctx, msg_hdr, actual_data ); + + if (som) { + ctx->block_len = block_len; + } + + /* If message you receive has the eom flag, write footer */ + if (eom) { + fd_solcap_write_ftr( ctx->capture_ctx->capture, ctx->block_len ); + + if (ctx->msg_set_sig == SOLCAP_WRITE_BANK_PREIMAGE) { + fflush(ctx->file); + } + + ctx->msg_idx = 0; + ctx->block_len = 0; + ctx->msg_set_sig = 0; + ctx->msg_set_slot = 0; + /* ONLY the tile turns ON advance_link (when EOM is received) */ + ctx->advance_link = 1; + } else { + ctx->msg_idx++; + ctx->advance_link = 0; + /* advance_link stays OFF */ + } + + return 0; +} + +static void +privileged_init( fd_topo_t * topo, + fd_topo_tile_t * tile ) { + + void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id ); + FD_SCRATCH_ALLOC_INIT( l, scratch ); + fd_capture_tile_ctx_t * ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_capture_tile_ctx_t), sizeof(fd_capture_tile_ctx_t) ); + void * _capture_ctx = FD_SCRATCH_ALLOC_APPEND( l, fd_capture_ctx_align(), fd_capture_ctx_footprint() ); + + ctx->tile_idx = tile->kind_id; + + ctx->capture_ctx = fd_capture_ctx_join( fd_capture_ctx_new( _capture_ctx ) ); + FD_TEST( ctx->capture_ctx ); + + tile->capctx.solcap_fd = open( tile->capctx.solcap_capture, O_RDWR | O_CREAT | O_TRUNC, 0644 ); + if( FD_UNLIKELY( tile->capctx.solcap_fd == -1 ) ) { + FD_LOG_ERR(( "failed to open or create solcap capture file %s (%i-%s)", + tile->capctx.solcap_capture, errno, strerror(errno) )); + } + + ctx->file = fdopen( tile->capctx.solcap_fd, "w+" ); + if( FD_UNLIKELY( !ctx->file ) ) { + FD_LOG_ERR(( "failed to fdopen solcap capture file descriptor %d (%i-%s)", + tile->capctx.solcap_fd, errno, strerror(errno) )); + } + FD_TEST( ctx->capture_ctx->capture ); + + ctx->capture_ctx->solcap_start_slot = tile->capctx.capture_start_slot; + fd_solcap_writer_init( ctx->capture_ctx->capture, ctx->file ); + + ctx->advance_link = 1; /* Start open will chose to advance or not after processing first EOM */ + ctx->msg_idx = 0UL; + ctx->msg_set_sig = 0U; + ctx->msg_set_slot = 0UL; + ctx->block_len = 0U; + + ulong scratch_top = FD_SCRATCH_ALLOC_FINI( l, scratch_align() ); + + if( FD_UNLIKELY( scratch_top > (ulong)scratch + scratch_footprint( tile ) ) ) + FD_LOG_ERR(( "scratch overflow %lu %lu %lu", scratch_top - (ulong)scratch - scratch_footprint( tile ), scratch_top, (ulong)scratch + scratch_footprint( tile ) )); + +} + +static void +unprivileged_init( fd_topo_t * topo, + fd_topo_tile_t * tile ) { + + void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id ); + fd_capture_tile_ctx_t * ctx = (fd_capture_tile_ctx_t *)scratch; + + ctx->in_cnt = 0UL; + FD_TEST( tile->in_cnt <= 32UL ); + for( ulong i = 0UL; i < tile->in_cnt; i++ ) { + fd_topo_link_t * link = &topo->links[ tile->in_link_id[ i ] ]; + fd_topo_wksp_t * wksp = &topo->workspaces[ topo->objs[ link->dcache_obj_id ].wksp_id ]; + + ctx->in[ctx->in_cnt].mem = wksp->wksp; + ctx->in[ctx->in_cnt].chunk0 = fd_dcache_compact_chunk0( wksp->wksp, link->dcache ); + ctx->in[ctx->in_cnt].wmark = fd_dcache_compact_wmark( wksp->wksp, link->dcache, link->mtu ); + ctx->in[ctx->in_cnt].mtu = link->mtu; + ctx->in_cnt++; + } +} + +#define STEM_BURST (1UL) +#define STEM_LAZY (50UL) + +#define STEM_CUSTOM_INPUT_SELECTION 1 +#define STEM_CUSTOM_INPUT_ADVANCE_FLAG(ctx) ((ctx)->advance_link) + +#define STEM_CALLBACK_CONTEXT_TYPE fd_capture_tile_ctx_t +#define STEM_CALLBACK_CONTEXT_ALIGN alignof(fd_capture_tile_ctx_t) + +#define STEM_CALLBACK_RETURNABLE_FRAG returnable_frag + +#include "../../disco/stem/fd_stem.c" + +fd_topo_run_tile_t fd_tile_capture = { + .name = "captur", + .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/discof/capture/fd_capture_tile.seccomppolicy b/src/discof/capture/fd_capture_tile.seccomppolicy new file mode 100644 index 00000000000..178ce49cf95 --- /dev/null +++ b/src/discof/capture/fd_capture_tile.seccomppolicy @@ -0,0 +1,33 @@ +# logfile_fd: It can be disabled by configuration, but typically tiles +# will open a log file on boot and write all messages there. +# +# solcap_fd: The file that the solcap capture will be written to +unsigned int logfile_fd, uint solcap_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. +# +# The capture tile writes to the solcap 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) solcap_fd)) + +# fflush() requires fstat to check file state before flushing +# +# arg 0 is the file descriptor to fstat. +fstat: (eq (arg 0) solcap_fd) + +# fflush() may use lseek to manage file position +# +# arg 0 is the file descriptor to lseek. +lseek: (eq (arg 0) solcap_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) diff --git a/src/discof/capture/generated/fd_capture_tile_seccomp.h b/src/discof/capture/generated/fd_capture_tile_seccomp.h new file mode 100644 index 00000000000..8445eaf6a50 --- /dev/null +++ b/src/discof/capture/generated/fd_capture_tile_seccomp.h @@ -0,0 +1,78 @@ +/* THIS FILE WAS GENERATED BY generate_filters.py. DO NOT EDIT BY HAND! */ +#ifndef HEADER_fd_src_discof_capture_generated_fd_capture_tile_seccomp_h +#define HEADER_fd_src_discof_capture_generated_fd_capture_tile_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_fd_capture_tile_instr_cnt = 22; + +static void populate_sock_filter_policy_fd_capture_tile( ulong out_cnt, struct sock_filter * out, unsigned int logfile_fd, uint solcap_fd ) { + FD_TEST( out_cnt >= 22 ); + struct sock_filter filter[22] = { + /* 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 */ 18 ), + /* 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 */ 4, 0 ), + /* allow fstat based on expression */ + BPF_JUMP( BPF_JMP | BPF_JEQ | BPF_K, SYS_fstat, /* check_fstat */ 9, 0 ), + /* allow lseek based on expression */ + BPF_JUMP( BPF_JMP | BPF_JEQ | BPF_K, SYS_lseek, /* check_lseek */ 10, 0 ), + /* allow fsync based on expression */ + BPF_JUMP( BPF_JMP | BPF_JEQ | BPF_K, SYS_fsync, /* check_fsync */ 11, 0 ), + /* none of the syscalls matched */ + { BPF_JMP | BPF_JA, 0, 0, /* RET_KILL_PROCESS */ 12 }, +// 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 */ 11, /* 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 */ 9, /* 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, solcap_fd, /* RET_ALLOW */ 7, /* RET_KILL_PROCESS */ 6 ), +// check_fstat: + /* 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, solcap_fd, /* RET_ALLOW */ 5, /* RET_KILL_PROCESS */ 4 ), +// 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, solcap_fd, /* RET_ALLOW */ 3, /* RET_KILL_PROCESS */ 2 ), +// 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 */ 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/discof/exec/fd_exec_tile.c b/src/discof/exec/fd_exec_tile.c index b772c8535b0..eab96d77652 100644 --- a/src/discof/exec/fd_exec_tile.c +++ b/src/discof/exec/fd_exec_tile.c @@ -3,7 +3,7 @@ #include "../../util/pod/fd_pod_format.h" #include "../../discof/replay/fd_exec.h" -#include "../../flamenco/runtime/context/fd_capture_ctx.h" +#include "../../discof/capture/fd_capture_ctx.h" #include "../../flamenco/runtime/fd_bank.h" #include "../../flamenco/runtime/fd_exec_stack.h" #include "../../flamenco/runtime/fd_runtime.h" @@ -51,8 +51,7 @@ typedef struct fd_exec_tile_ctx { /* Capture context for debugging runtime execution. */ fd_capture_ctx_t * capture_ctx; - uchar * solcap_publish_buffer_ptr; - ulong account_updates_flushed; + fd_capture_link_buf_t cap_exec_out[1]; /* A transaction can be executed as long as there is a valid handle to a funk_txn and a bank. These are queried from fd_banks_t and @@ -102,6 +101,25 @@ metrics_write( fd_exec_tile_ctx_t * ctx ) { FD_MCNT_SET( EXEC, PROGCACHE_DUP_INSERTS, progcache->metrics->dup_insert_cnt ); } +/* Publish the txn finalized message to the replay tile */ +static void +publish_txn_finalized_msg( fd_exec_tile_ctx_t * ctx, + fd_stem_context_t * stem ) { + fd_exec_task_done_msg_t * msg = fd_chunk_to_laddr( ctx->exec_replay_out->mem, ctx->exec_replay_out->chunk ); + msg->bank_idx = ctx->txn_ctx->bank_idx; + msg->txn_exec->txn_idx = ctx->txn_idx; + msg->txn_exec->err = !(ctx->txn_ctx->flags&FD_TXN_P_FLAGS_EXECUTE_SUCCESS); + if( FD_UNLIKELY( msg->txn_exec->err ) ) { + FD_LOG_WARNING(( "txn failed to execute, bad block detected err=%d", ctx->txn_ctx->exec_err )); + } + + fd_stem_publish( stem, ctx->exec_replay_out->idx, (FD_EXEC_TT_TXN_EXEC<<32)|ctx->tile_idx, ctx->exec_replay_out->chunk, sizeof(*msg), 0UL, 0UL, 0UL ); + + ctx->exec_replay_out->chunk = fd_dcache_compact_next( ctx->exec_replay_out->chunk, sizeof(*msg), ctx->exec_replay_out->chunk0, ctx->exec_replay_out->wmark ); + + ctx->pending_txn_finalized_msg = 0; +} + static inline int returnable_frag( fd_exec_tile_ctx_t * ctx, ulong in_idx, @@ -124,6 +142,9 @@ returnable_frag( fd_exec_tile_ctx_t * ctx, case FD_EXEC_TT_TXN_EXEC: { /* Execute. */ fd_exec_txn_exec_msg_t * msg = fd_chunk_to_laddr( ctx->replay_in->mem, chunk ); + if ( FD_UNLIKELY( ctx->capture_ctx ) ) { + ctx->capture_ctx->current_txn_idx = msg->capture_txn_idx; + } ctx->txn_ctx->exec_err = fd_runtime_prepare_and_execute_txn( ctx->banks, msg->bank_idx, ctx->txn_ctx, @@ -170,6 +191,10 @@ returnable_frag( fd_exec_tile_ctx_t * ctx, } } else FD_LOG_CRIT(( "invalid in_idx %lu", in_idx )); + if( ctx->pending_txn_finalized_msg ) { + publish_txn_finalized_msg( ctx, stem ); + } + return 0; } @@ -185,6 +210,8 @@ unprivileged_init( fd_topo_t * topo, FD_SCRATCH_ALLOC_INIT( l, scratch ); fd_exec_tile_ctx_t * ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_exec_tile_ctx_t), sizeof(fd_exec_tile_ctx_t) ); + + fd_memset( ctx, 0, sizeof(fd_exec_tile_ctx_t) ); void * capture_ctx_mem = FD_SCRATCH_ALLOC_APPEND( l, fd_capture_ctx_align(), fd_capture_ctx_footprint() ); void * _txncache = FD_SCRATCH_ALLOC_APPEND( l, fd_txncache_align(), fd_txncache_footprint( tile->exec.max_live_slots ) ); uchar * pc_scratch = FD_SCRATCH_ALLOC_APPEND( l, FD_PROGCACHE_SCRATCH_ALIGN, FD_PROGCACHE_SCRATCH_FOOTPRINT ); @@ -296,15 +323,42 @@ unprivileged_init( fd_topo_t * topo, ctx->txn_ctx->status_cache = ctx->txncache; ctx->txn_ctx->bank_hash_cmp = ctx->bank_hash_cmp; + /********************************************************************/ /* Capture context */ /********************************************************************/ ctx->capture_ctx = NULL; - ctx->solcap_publish_buffer_ptr = NULL; - ctx->account_updates_flushed = 0UL; if( FD_UNLIKELY( strlen( tile->exec.solcap_capture ) || strlen( tile->exec.dump_proto_dir ) ) ) { + + ulong tile_idx = tile->kind_id; + ulong idx = fd_topo_find_tile_out_link( topo, tile, "cap_exec", tile_idx ); + FD_TEST( idx!=ULONG_MAX ); + fd_topo_link_t * link = &topo->links[ tile->out_link_id[ idx ] ]; + fd_capture_link_buf_t * cap_exec_out = ctx->cap_exec_out; + cap_exec_out->base.vt = &fd_capture_link_buf_vt; + cap_exec_out->idx = idx; + cap_exec_out->mem = topo->workspaces[ topo->objs[ link->dcache_obj_id ].wksp_id ].wksp; + cap_exec_out->chunk0 = fd_dcache_compact_chunk0( cap_exec_out->mem, link->dcache ); + cap_exec_out->wmark = fd_dcache_compact_wmark( cap_exec_out->mem, link->dcache, link->mtu ); + cap_exec_out->chunk = cap_exec_out->chunk0; + cap_exec_out->mcache = link->mcache; + cap_exec_out->depth = fd_mcache_depth( link->mcache ); + cap_exec_out->seq = 0UL; + + ulong consumer_tile_idx = fd_topo_find_tile(topo, "captur", 0UL); + fd_topo_tile_t * consumer_tile = &topo->tiles[ consumer_tile_idx ]; + cap_exec_out->fseq = NULL; + for (ulong j = 0UL; j < consumer_tile->in_cnt; j++ ) { + if( FD_UNLIKELY( consumer_tile->in_link_id[ j ] == link->id ) ) { + cap_exec_out->fseq = fd_fseq_join( fd_topo_obj_laddr( topo, consumer_tile->in_link_fseq_obj_id[ j ] ) ); + FD_TEST( cap_exec_out->fseq ); + break; + } + } + ctx->capture_ctx = fd_capture_ctx_join( fd_capture_ctx_new( capture_ctx_mem ) ); + ctx->capture_ctx->solcap_start_slot = tile->exec.capture_start_slot; if( strlen( tile->exec.dump_proto_dir ) ) { ctx->capture_ctx->dump_proto_output_dir = tile->exec.dump_proto_dir; @@ -315,98 +369,12 @@ unprivileged_init( fd_topo_t * topo, ctx->capture_ctx->dump_elf_to_pb = tile->exec.dump_elf_to_pb; } - if( strlen( tile->exec.solcap_capture ) ) { - ctx->capture_ctx->capture_txns = 0; - ctx->capture_ctx->solcap_start_slot = tile->exec.capture_start_slot; - ctx->account_updates_flushed = 0; - ctx->solcap_publish_buffer_ptr = ctx->capture_ctx->account_updates_buffer; - } + ctx->capture_ctx->capctx_buf.buf = cap_exec_out; + ctx->capture_ctx->capture_link = &cap_exec_out->base; } ctx->pending_txn_finalized_msg = 0; -} - -/* Publish the next account update event buffered in the capture tile to the replay tile - - TODO: remove this when solcap v2 is here. */ -static void -publish_next_capture_ctx_account_update( fd_exec_tile_ctx_t * ctx, - fd_stem_context_t * stem ) { - if( FD_UNLIKELY( !ctx->capture_ctx ) ) { - return; - } - - /* Copy the account update event to the buffer */ - ulong chunk = ctx->exec_replay_out->chunk; - uchar * out_ptr = fd_chunk_to_laddr( ctx->exec_replay_out->mem, chunk ); - fd_capture_ctx_account_update_msg_t * msg = (fd_capture_ctx_account_update_msg_t *)ctx->solcap_publish_buffer_ptr; - memcpy( out_ptr, msg, sizeof(fd_capture_ctx_account_update_msg_t) ); - ctx->solcap_publish_buffer_ptr += sizeof(fd_capture_ctx_account_update_msg_t); - out_ptr += sizeof(fd_capture_ctx_account_update_msg_t); - - /* Copy the data to the buffer */ - ulong data_sz = msg->data_sz; - memcpy( out_ptr, ctx->solcap_publish_buffer_ptr, data_sz ); - ctx->solcap_publish_buffer_ptr += data_sz; - out_ptr += data_sz; - - /* Stem publish the account update event */ - ulong msg_sz = sizeof(fd_capture_ctx_account_update_msg_t) + msg->data_sz; - fd_stem_publish( stem, ctx->exec_replay_out->idx, 0UL, chunk, msg_sz, 0UL, 0UL, 0UL ); - ctx->exec_replay_out->chunk = fd_dcache_compact_next( - chunk, - msg_sz, - ctx->exec_replay_out->chunk0, - ctx->exec_replay_out->wmark ); - - /* Advance the number of account updates flushed */ - ctx->account_updates_flushed++; - - /* If we have published all the account updates, reset the buffer pointer and length */ - if( ctx->account_updates_flushed == ctx->capture_ctx->account_updates_len ) { - ctx->capture_ctx->account_updates_buffer_ptr = ctx->capture_ctx->account_updates_buffer; - ctx->solcap_publish_buffer_ptr = ctx->capture_ctx->account_updates_buffer; - ctx->capture_ctx->account_updates_len = 0UL; - ctx->account_updates_flushed = 0UL; - } -} - -/* Publish the txn finalized message to the replay tile */ -static void -publish_txn_finalized_msg( fd_exec_tile_ctx_t * ctx, - fd_stem_context_t * stem ) { - fd_exec_task_done_msg_t * msg = fd_chunk_to_laddr( ctx->exec_replay_out->mem, ctx->exec_replay_out->chunk ); - msg->bank_idx = ctx->txn_ctx->bank_idx; - msg->txn_exec->txn_idx = ctx->txn_idx; - msg->txn_exec->err = !(ctx->txn_ctx->flags&FD_TXN_P_FLAGS_EXECUTE_SUCCESS); - if( FD_UNLIKELY( msg->txn_exec->err ) ) { - FD_LOG_WARNING(( "txn failed to execute, bad block detected err=%d", ctx->txn_ctx->exec_err )); - } - - fd_stem_publish( stem, ctx->exec_replay_out->idx, (FD_EXEC_TT_TXN_EXEC<<32)|ctx->tile_idx, ctx->exec_replay_out->chunk, sizeof(*msg), 0UL, 0UL, 0UL ); - ctx->exec_replay_out->chunk = fd_dcache_compact_next( ctx->exec_replay_out->chunk, sizeof(*msg), ctx->exec_replay_out->chunk0, ctx->exec_replay_out->wmark ); - - ctx->pending_txn_finalized_msg = 0; -} - -static void -after_credit( fd_exec_tile_ctx_t * ctx, - fd_stem_context_t * stem, - int * opt_poll_in, - int * charge_busy FD_PARAM_UNUSED ) { - /* If we have outstanding account updates to send to solcap, send - them. Note that we set opt_poll_in to 0 here because we must not - consume any more fragments from the exec tiles before publishing - our messages, so that solcap updates are not interleaved between - slots. */ - if( FD_UNLIKELY( ctx->capture_ctx && ctx->account_updates_flushed < ctx->capture_ctx->account_updates_len ) ) { - publish_next_capture_ctx_account_update( ctx, stem ); - *opt_poll_in = 0; - } else if( ctx->pending_txn_finalized_msg ) { - publish_txn_finalized_msg( ctx, stem ); - *opt_poll_in = 0; - } } static ulong @@ -442,7 +410,6 @@ populate_allowed_fds( fd_topo_t const * topo FD_PARAM_UNUSED, #define STEM_CALLBACK_CONTEXT_TYPE fd_exec_tile_ctx_t #define STEM_CALLBACK_CONTEXT_ALIGN alignof(fd_exec_tile_ctx_t) -#define STEM_CALLBACK_AFTER_CREDIT after_credit #define STEM_CALLBACK_RETURNABLE_FRAG returnable_frag #define STEM_CALLBACK_METRICS_WRITE metrics_write diff --git a/src/discof/replay/fd_exec.h b/src/discof/replay/fd_exec.h index 856c860a393..fa79d65cb5e 100644 --- a/src/discof/replay/fd_exec.h +++ b/src/discof/replay/fd_exec.h @@ -21,6 +21,8 @@ struct fd_exec_txn_exec_msg { ulong bank_idx; ulong txn_idx; fd_txn_p_t txn; + + ulong capture_txn_idx; /* Used currently by solcap to maintain ordering of messages */ }; typedef struct fd_exec_txn_exec_msg fd_exec_txn_exec_msg_t; diff --git a/src/discof/replay/fd_replay_tile.c b/src/discof/replay/fd_replay_tile.c index 8d9ebd819ae..fc9e2dc3eb8 100644 --- a/src/discof/replay/fd_replay_tile.c +++ b/src/discof/replay/fd_replay_tile.c @@ -325,8 +325,9 @@ struct fd_replay_tile { fd_block_id_map_t * block_id_map; /* Capture-related configs */ - fd_capture_ctx_t * capture_ctx; - FILE * capture_file; + fd_capture_ctx_t * capture_ctx; + FILE * capture_file; + fd_capture_link_buf_t cap_repl_out[1]; /* Whether the runtime has been booted either from snapshot loading or from genesis. */ @@ -689,10 +690,6 @@ replay_block_start( fd_replay_tile_t * ctx, /* Update any required runtime state and handle any potential epoch boundary change. */ - if( ctx->capture_ctx ) { - fd_solcap_writer_set_slot( ctx->capture_ctx->capture, slot ); - } - fd_bank_shred_cnt_set( bank, 0UL ); fd_bank_execution_fees_set( bank, 0UL ); fd_bank_priority_fees_set( bank, 0UL ); @@ -797,11 +794,8 @@ static void replay_block_finalize( fd_replay_tile_t * ctx, fd_stem_context_t * stem, fd_bank_t * bank ) { - bank->last_transaction_finished_nanos = fd_log_wallclock(); - if( FD_UNLIKELY( ctx->capture_ctx ) ) fd_solcap_writer_flush( ctx->capture_ctx->capture ); - FD_TEST( !(bank->flags&FD_BANK_FLAGS_FROZEN) ); ulong slot = fd_bank_slot_get( bank ); @@ -1166,8 +1160,6 @@ init_after_snapshot( fd_replay_tile_t * ctx ) { bank_hash_cmp->watermark = snapshot_slot; fd_bank_vote_states_end_locking_query( bank ); - - if( FD_UNLIKELY( ctx->capture_ctx ) ) fd_solcap_writer_flush( ctx->capture_ctx->capture ); } static inline int @@ -1565,6 +1557,9 @@ dispatch_task( fd_replay_tile_t * ctx, memcpy( &exec_msg->txn, txn_p, sizeof(fd_txn_p_t) ); exec_msg->bank_idx = task->txn_exec->bank_idx; exec_msg->txn_idx = task->txn_exec->txn_idx; + if ( FD_UNLIKELY( ctx->capture_ctx ) ) { + exec_msg->capture_txn_idx = ctx->capture_ctx->current_txn_idx++; + } fd_stem_publish( stem, exec_out->idx, (FD_EXEC_TT_TXN_EXEC<<32) | task->txn_exec->exec_idx, exec_out->chunk, sizeof(*exec_msg), 0UL, 0UL, 0UL ); exec_out->chunk = fd_dcache_compact_next( exec_out->chunk, sizeof(*exec_msg), exec_out->chunk0, exec_out->wmark ); break; @@ -1935,31 +1930,10 @@ before_frag( fd_replay_tile_t * ctx, return 0; } -static void -process_solcap_account_update( fd_replay_tile_t * ctx, - fd_capture_ctx_account_update_msg_t const * msg ) { - - fd_bank_t * bank = fd_banks_bank_query( ctx->banks, msg->bank_idx ); - if( FD_UNLIKELY( !bank ) ) { - FD_LOG_CRIT(( "invariant violation: bank is NULL for bank index %lu", msg->bank_idx )); - } - - if( FD_UNLIKELY( !ctx->capture_ctx || !ctx->capture_ctx->capture ) ) return; - if( FD_UNLIKELY( fd_bank_slot_get( bank )capture_ctx->solcap_start_slot ) ) return; - - uchar const * account_data = (uchar const *)fd_type_pun_const( msg )+sizeof(fd_capture_ctx_account_update_msg_t); - fd_solcap_write_account( ctx->capture_ctx->capture, &msg->pubkey, &msg->info, account_data, msg->data_sz ); -} - static void process_exec_task_done( fd_replay_tile_t * ctx, fd_exec_task_done_msg_t * msg, ulong sig ) { - if( FD_UNLIKELY( sig==0UL ) ) { - // FIXME remove this branch with new solcap - process_solcap_account_update( ctx, fd_type_pun( msg ) ); - return; - } ulong exec_tile_idx = sig&0xFFFFFFFFUL; @@ -2405,16 +2379,7 @@ unprivileged_init( fd_topo_t * topo, ctx->capture_ctx = NULL; if( FD_UNLIKELY( strcmp( "", tile->replay.solcap_capture ) || strcmp( "", tile->replay.dump_proto_dir ) ) ) { ctx->capture_ctx = fd_capture_ctx_join( fd_capture_ctx_new( _capture_ctx ) ); - } - - if( FD_UNLIKELY( strcmp( "", tile->replay.solcap_capture ) ) ) { - ctx->capture_ctx->checkpt_freq = ULONG_MAX; - ctx->capture_file = fopen( tile->replay.solcap_capture, "w+" ); - if( FD_UNLIKELY( !ctx->capture_file ) ) FD_LOG_ERR(( "fopen(%s) failed (%d-%s)", tile->replay.solcap_capture, errno, fd_io_strerror( errno ) )); - - ctx->capture_ctx->capture_txns = 0; ctx->capture_ctx->solcap_start_slot = tile->replay.capture_start_slot; - fd_solcap_writer_init( ctx->capture_ctx->capture, ctx->capture_file ); } if( FD_UNLIKELY( strcmp( "", tile->replay.dump_proto_dir ) ) ) { @@ -2518,6 +2483,40 @@ unprivileged_init( fd_topo_t * topo, ctx->gui_enabled = fd_topo_find_tile( topo, "gui", 0UL )!=ULONG_MAX; + if( FD_UNLIKELY( strcmp( "", tile->replay.solcap_capture ) ) ) { + idx = fd_topo_find_tile_out_link( topo, tile, "cap_repl", 0UL ); + FD_TEST( idx!=ULONG_MAX ); + link = &topo->links[ tile->out_link_id[ idx ] ]; + + + fd_capture_link_buf_t * cap_repl_out = ctx->cap_repl_out; + cap_repl_out->base.vt = &fd_capture_link_buf_vt; + cap_repl_out->idx = idx; + cap_repl_out->mem = topo->workspaces[ topo->objs[ link->dcache_obj_id ].wksp_id ].wksp; + cap_repl_out->chunk0 = fd_dcache_compact_chunk0( cap_repl_out->mem, link->dcache ); + cap_repl_out->wmark = fd_dcache_compact_wmark( cap_repl_out->mem, link->dcache, link->mtu ); + cap_repl_out->chunk = cap_repl_out->chunk0; + cap_repl_out->mcache = link->mcache; + cap_repl_out->depth = fd_mcache_depth( link->mcache ); + cap_repl_out->seq = 0UL; + + ctx->capture_ctx->capctx_buf.buf = cap_repl_out; + ctx->capture_ctx->capture_link = &cap_repl_out->base; + ctx->capture_ctx->current_txn_idx = 0UL; + + + ulong consumer_tile_idx = fd_topo_find_tile( topo, "captur", 0UL ); + fd_topo_tile_t * consumer_tile = &topo->tiles[ consumer_tile_idx ]; + cap_repl_out->fseq = NULL; + for (ulong j = 0UL; j < consumer_tile->in_cnt; j++ ) { + if( FD_UNLIKELY( consumer_tile->in_link_id[ j ] == link->id ) ) { + cap_repl_out->fseq = fd_fseq_join( fd_topo_obj_laddr( topo, consumer_tile->in_link_fseq_obj_id[ j ] ) ); + FD_TEST( cap_repl_out->fseq ); + break; + } + } + } + fd_memset( &ctx->metrics, 0, sizeof(ctx->metrics) ); fd_histf_join( fd_histf_new( ctx->metrics.store_link_wait, FD_MHIST_SECONDS_MIN( REPLAY, STORE_LINK_WAIT ), diff --git a/src/flamenco/capture/Local.mk b/src/flamenco/capture/Local.mk index 57310279c98..4cbcbd80ebc 100644 --- a/src/flamenco/capture/Local.mk +++ b/src/flamenco/capture/Local.mk @@ -1,11 +1,6 @@ -$(call add-hdrs,fd_solcap_proto.h fd_solcap_writer.h fd_solcap_reader.h) ifdef FD_HAS_INT128 ifdef FD_HAS_HOSTED -$(call add-objs,fd_solcap_writer fd_solcap_reader fd_solcap.pb,fd_flamenco) -$(call make-bin,fd_solcap_diff,fd_solcap_diff,fd_flamenco fd_ballet fd_util) -$(call make-bin,fd_solcap_import,fd_solcap_import,fd_flamenco fd_ballet fd_util) -$(call make-bin,fd_solcap_yaml,fd_solcap_yaml,fd_flamenco fd_ballet fd_util) -else -$(call add-objs,fd_solcap_writer_stub,fd_flamenco) +$(call add-objs,fd_solcap_writer fd_solcap.pb,fd_flamenco) +$(call add-hdrs,fd_solcap_proto.h fd_solcap_writer.h) endif endif diff --git a/src/flamenco/capture/fd_solcap_diff.c b/src/flamenco/capture/fd_solcap_diff.c deleted file mode 100644 index da7a3252028..00000000000 --- a/src/flamenco/capture/fd_solcap_diff.c +++ /dev/null @@ -1,1260 +0,0 @@ -#include "../fd_flamenco.h" -#include "fd_solcap_proto.h" -#include "fd_solcap_reader.h" -#include "fd_solcap.pb.h" -#include "../../ballet/base58/fd_base58.h" -#include "../types/fd_types.h" -#include "../types/fd_types_yaml.h" -#include "../../ballet/nanopb/pb_decode.h" - -#include -#include -#include /* mkdir(2) */ -#include /* open(2) */ -#include /* close(2) */ - -/* TODO: Ugly -- These should not be hard coded! */ -#define SOLCAP_FILE_NAME_LEN (13UL) -#define SOLCAP_SUFFIX_LEN (7UL) /* .solcap */ - -#if FD_USING_GCC && __GNUC__ >= 15 -#pragma GCC diagnostic ignored "-Wunterminated-string-initialization" -#endif - -static const uchar -_vote_program_address[ 32 ] = - "\x07\x61\x48\x1d\x35\x74\x74\xbb\x7c\x4d\x76\x24\xeb\xd3\xbd\xb3" - "\xd8\x35\x5e\x73\xd1\x10\x43\xfc\x0d\xa3\x53\x80\x00\x00\x00\x00"; - -static const uchar -_stake_program_address[ 32 ] = - "\x06\xa1\xd8\x17\x91\x37\x54\x2a\x98\x34\x37\xbd\xfe\x2a\x7a\xb2" - "\x55\x7f\x53\x5c\x8a\x78\x72\x2b\x68\xa4\x9d\xc0\x00\x00\x00\x00"; - -static void -normalize_filename( const char * original_str, char * file_name, char prefix ) { - /* We either need to truncate if too long or pad if too short (16 chars) */ - - file_name[0] = prefix; - ulong original_str_len = strlen( original_str ) - SOLCAP_SUFFIX_LEN + 1; - if ( original_str_len <= SOLCAP_FILE_NAME_LEN ) { - fd_memcpy( file_name + 1, original_str, original_str_len ); - for ( ulong i = original_str_len; i < SOLCAP_FILE_NAME_LEN; i++ ) { - file_name[ i ] = ' '; - } - } - else { - ulong start_idx = original_str_len - SOLCAP_FILE_NAME_LEN; - fd_memcpy( file_name + 1, original_str + start_idx, SOLCAP_FILE_NAME_LEN ); - - } - file_name[ SOLCAP_FILE_NAME_LEN ] = '\0'; -} - -/* Define routines for sorting the bank hash account delta accounts. - The solcap format does not mandate accounts to be sorted. */ - -static inline int -fd_solcap_account_tbl_lt( fd_solcap_account_tbl_t const * a, - fd_solcap_account_tbl_t const * b ) { - return memcmp( a->key, b->key, 32UL ) < 0; -} -#define SORT_NAME sort_account_tbl -#define SORT_KEY_T fd_solcap_account_tbl_t -#define SORT_BEFORE(a,b) fd_solcap_account_tbl_lt( &(a), &(b) ) -#include "../../util/tmpl/fd_sort.c" -struct fd_solcap_differ { - fd_solcap_chunk_iter_t iter [2]; - fd_solcap_BankPreimage preimage[2]; - - int verbose; - int dump_dir_fd; - char const * dump_dir; - char const * file_paths[2]; -}; - -typedef struct fd_solcap_differ fd_solcap_differ_t; - -struct fd_solcap_txn_differ { - FILE * file[2]; - fd_solcap_chunk_iter_t iter[2]; - long chunk_gaddr[2]; - fd_solcap_Transaction transaction[2]; - uchar meta_buf[128][2]; -}; - -typedef struct fd_solcap_txn_differ fd_solcap_txn_differ_t; - -static fd_solcap_differ_t * -fd_solcap_differ_new( fd_solcap_differ_t * diff, - FILE * streams[2], - const char * cap_path[2] ) { - - /* Attach to capture files */ - - for( ulong i=0UL; i<2UL; i++ ) { - FILE * stream = streams[i]; - - /* Set file names */ - diff->file_paths[i] = cap_path[i]; - - /* Read file header */ - fd_solcap_fhdr_t hdr[1]; - if( FD_UNLIKELY( 1UL!=fread( hdr, sizeof(fd_solcap_fhdr_t), 1UL, stream ) ) ) { - FD_LOG_WARNING(( "Failed to read file=%s header (%d-%s)", - diff->file_paths[i], errno, strerror( errno ) )); - return NULL; - } - - /* Seek to first chunk */ - long skip = ( (long)hdr->chunk0_foff - (long)sizeof(fd_solcap_fhdr_t) ); - if( FD_UNLIKELY( 0!=fseek( stream, skip, SEEK_CUR ) ) ) { - FD_LOG_WARNING(( "Failed to seek to first chunk (%d-%s)", errno, strerror( errno ) )); - return NULL; - } - - if( FD_UNLIKELY( !fd_solcap_chunk_iter_new( &diff->iter[i], stream ) ) ) - FD_LOG_CRIT(( "fd_solcap_chunk_iter_new() failed" )); - } - - return diff; -} - -/* fd_solcap_differ_advance seeks an iterator to the next bank hash. - idx identifies the iterator. Returns 1 on success, 0 if end-of-file - reached, and negated errno-like on failure. */ - -static int -fd_solcap_differ_advance( fd_solcap_differ_t * diff, - ulong idx ) { /* [0,2) */ - - fd_solcap_chunk_iter_t * iter = &diff->iter [ idx ]; - fd_solcap_BankPreimage * preimage = &diff->preimage[ idx ]; - - long off = fd_solcap_chunk_iter_find( iter, FD_SOLCAP_V1_BANK_MAGIC ); - if( FD_UNLIKELY( off<0L ) ) - return fd_solcap_chunk_iter_err( iter ); - - int err = fd_solcap_read_bank_preimage( iter->stream, iter->chunk_off, preimage, &iter->chunk ); - if( FD_UNLIKELY( err!=0 ) ) return -err; - return 1; -} - -/* fd_solcap_differ_sync synchronizes the given two iterators such that - both point to the lowest common slot number. Returns 1 on success - and 0 if no common slot was found. Negative values are negated - errno-like. */ - -static int -fd_solcap_differ_sync( fd_solcap_differ_t * diff, ulong start_slot, ulong end_slot ) { - - /* Seek to first bank preimage object */ - - for( ulong i=0UL; i<2UL; i++ ) { - int res = fd_solcap_differ_advance( diff, i ); - if( FD_UNLIKELY( res!=1 ) ) return res; - } - - ulong prev_slot0 = diff->preimage[ 0 ].slot; - ulong prev_slot1 = diff->preimage[ 1 ].slot; - - for(;;) { - ulong slot0 = diff->preimage[ 0 ].slot; - ulong slot1 = diff->preimage[ 1 ].slot; - - /* Handle cases where slot is skipped in one or the other */ - if ( FD_UNLIKELY( prev_slot0 < slot1 && slot0 > slot1 ) ) { - FD_LOG_WARNING(("Slot range (%lu,%lu) skipped in file=%s\n", - prev_slot0, slot0, diff->file_paths[0])); - } - else if ( FD_UNLIKELY( prev_slot1 < slot0 && slot1 > slot0 ) ) { - FD_LOG_WARNING(("Slot range (%lu,%lu) skipped in file=%s\n", - prev_slot1, slot1, diff->file_paths[1])); - } - - if( slot0 == slot1 ) { - if ( slot0 < start_slot ) { - int res; - res = fd_solcap_differ_advance( diff, 0 ); - if( FD_UNLIKELY( res <= 0 ) ) return res; - res = fd_solcap_differ_advance( diff, 1 ); - if( FD_UNLIKELY( res <= 0 ) ) return res; - } - else if ( slot0 > end_slot ) { - return 0; - } - else { - return 1; - } - } - else { - ulong idx = slot0>slot1; - int res = fd_solcap_differ_advance( diff, idx ); - if( FD_UNLIKELY( res<=0 ) ) return res; - } - prev_slot0 = slot0; - prev_slot1 = slot1; - } - - return 0; -} - -static int -fd_solcap_can_pretty_print( uchar const owner [ static 32 ], - uchar const pubkey[ static 32 ] ) { - - /* TODO clean up */ - uchar _sysvar_clock[ 32 ]; - fd_base58_decode_32( "SysvarC1ock11111111111111111111111111111111", _sysvar_clock ); - uchar _sysvar_rent[ 32 ]; - fd_base58_decode_32( "SysvarRent111111111111111111111111111111111", _sysvar_rent ); - uchar _sysvar_epoch_rewards[ 32 ]; - fd_base58_decode_32( "SysvarEpochRewards1111111111111111111111111", _sysvar_epoch_rewards ); - uchar _sysvar_stake_history[ 32 ]; - fd_base58_decode_32( "SysvarStakeHistory1111111111111111111111111", _sysvar_stake_history ); - - if( 0==memcmp( owner, _vote_program_address, 32UL ) ) - return 1; - if( 0==memcmp( owner, _stake_program_address, 32UL ) ) - return 1; - - if( 0==memcmp( pubkey, _sysvar_clock, 32UL ) ) - return 1; - if( 0==memcmp( pubkey, _sysvar_rent, 32UL ) ) - return 1; - if( 0==memcmp( pubkey, _sysvar_epoch_rewards, 32UL ) ) - return 1; - if( 0==memcmp( pubkey, _sysvar_stake_history, 32UL ) ) - return 1; - return 0; -} - -static int -fd_solcap_account_pretty_print( uchar const pubkey[ static 32 ], - uchar const owner[ static 32 ], - uchar const * data, - ulong data_sz, - FILE * file ) { - - FD_SCRATCH_SCOPE_BEGIN { - - fd_flamenco_yaml_t * yaml = - fd_flamenco_yaml_init( fd_flamenco_yaml_new( - fd_scratch_alloc( fd_flamenco_yaml_align(), fd_flamenco_yaml_footprint() ) ), - file ); - FD_TEST( yaml ); - - /* TODO clean up */ - uchar _sysvar_clock[ 32 ]; - fd_base58_decode_32( "SysvarC1ock11111111111111111111111111111111", _sysvar_clock ); - uchar _sysvar_rent[ 32 ]; - fd_base58_decode_32( "SysvarRent111111111111111111111111111111111", _sysvar_rent ); - uchar _sysvar_epoch_rewards[ 32 ]; - fd_base58_decode_32( "SysvarEpochRewards1111111111111111111111111", _sysvar_epoch_rewards ); - uchar _sysvar_stake_history[ 32 ]; - fd_base58_decode_32( "SysvarStakeHistory1111111111111111111111111", _sysvar_stake_history ); - - if( 0==memcmp( owner, _vote_program_address, 32UL ) ) { - fd_vote_state_versioned_t * vote_state = fd_bincode_decode_scratch( vote_state_versioned, data, data_sz, NULL ); - if( FD_UNLIKELY( !vote_state ) ) return -ENOMEM; - fd_vote_state_versioned_walk( yaml, vote_state, fd_flamenco_yaml_walk, NULL, 0U, 0U ); - } else if( 0==memcmp( owner, _stake_program_address, 32UL ) ) { - fd_stake_state_v2_t * stake_state = fd_bincode_decode_scratch( stake_state_v2, data, data_sz, NULL ); - if( FD_UNLIKELY( !stake_state ) ) return -ENOMEM; - fd_stake_state_v2_walk( yaml, stake_state, fd_flamenco_yaml_walk, NULL, 0U, 0U ); - } else if( 0==memcmp( pubkey, _sysvar_clock, 32UL ) ) { - fd_sol_sysvar_clock_t * clock = fd_bincode_decode_scratch( sol_sysvar_clock, data, data_sz, NULL ); - if( FD_UNLIKELY( !clock ) ) return -ENOMEM; - fd_sol_sysvar_clock_walk( yaml, clock, fd_flamenco_yaml_walk, NULL, 0U, 0U ); - } else if( 0==memcmp( pubkey, _sysvar_rent, 32UL ) ) { - fd_rent_t * rent = fd_bincode_decode_scratch( rent, data, data_sz, NULL ); - if( FD_UNLIKELY( !rent ) ) return -ENOMEM; - fd_rent_walk( yaml, rent, fd_flamenco_yaml_walk, NULL, 0U, 0U ); - } else if( 0==memcmp( pubkey, _sysvar_epoch_rewards, 32UL ) ) { - fd_sysvar_epoch_rewards_t * epoch_rewards = fd_bincode_decode_scratch( sysvar_epoch_rewards, data, data_sz, NULL ); - if( FD_UNLIKELY( !epoch_rewards ) ) return -ENOMEM; - fd_sysvar_epoch_rewards_walk( yaml, epoch_rewards, fd_flamenco_yaml_walk, NULL, 0U, 0U ); - } else if( 0==memcmp( pubkey, _sysvar_stake_history, 32UL ) ) { - fd_stake_history_t * stake_history = fd_bincode_decode_scratch( stake_history, data, data_sz, NULL ); - if( FD_UNLIKELY( !stake_history ) ) return -ENOMEM; - fd_stake_history_walk( yaml, stake_history, fd_flamenco_yaml_walk, NULL, 0U, 0U ); - } - - int err = ferror( file ); - if( FD_UNLIKELY( err!=0 ) ) return err; - - /* No need to destroy structures, using fd_scratch allocator */ - - fd_flamenco_yaml_delete( yaml ); - return 0; - } FD_SCRATCH_SCOPE_END; -} - -/* fd_solcap_dump_account_data writes a binary file containing exactly - the given account's content. */ - -static void -fd_solcap_dump_account_data( fd_solcap_differ_t * diff, - fd_solcap_AccountMeta const * meta, - char const * filename, - void const * acc_data ) { - /* Create dump file */ - int fd = openat( diff->dump_dir_fd, filename, O_CREAT|O_WRONLY|O_TRUNC, 0666 ); - if( FD_UNLIKELY( fd<0 ) ) - FD_LOG_ERR(( "openat(%d,%s) failed (%d-%s)", - diff->dump_dir_fd, filename, errno, strerror( errno ) )); - - /* Write dump file */ - FILE * file = fdopen( fd, "wb" ); - FD_TEST( meta->data_sz == fwrite( acc_data, 1UL, meta->data_sz, file ) ); - fclose( file ); /* Closes fd */ -} - -static void -fd_solcap_diff_account_data( fd_solcap_differ_t * diff, - fd_solcap_AccountMeta const meta [ static 2 ], - fd_solcap_account_tbl_t const * const entry [ static 2 ], - ulong const data_goff[ static 2 ] ) {\ - - /* Streaming diff */ - int data_eq = meta[0].data_sz == meta[1].data_sz; - if( data_eq ) { - for( ulong i=0UL; i<2UL; i++ ) { - if ( data_goff[i] == ULONG_MAX ) { - continue; - } - FD_TEST( 0==fseek( diff->iter[ i ].stream, (long)data_goff[i], SEEK_SET ) ); - } - - for( ulong off=0UL; offiter[i].stream ) ); - - /* Compare chunks */ - data_eq = 0==memcmp( buf[0], buf[1], sz ); - if( !data_eq ) break; - - off += sz; -# undef BUFSZ - } - } - if( data_eq ) return; - - /* Dump account data to file */ - if( diff->verbose >= 4 ) { - - /* TODO: Remove hardcoded account size check */ - FD_TEST( meta[0].data_sz <= 1048576 ); - FD_TEST( meta[1].data_sz <= 1048576 ); - - FD_SCRATCH_SCOPE_BEGIN { - void * acc_data[2]; - acc_data[0] = fd_scratch_alloc( 1UL, meta[0].data_sz ); - acc_data[1] = fd_scratch_alloc( 1UL, meta[1].data_sz ); - fd_memset( acc_data[0], 0, meta[0].data_sz ); - fd_memset( acc_data[1], 0, meta[1].data_sz ); - - for( ulong i=0UL; i<2UL; i++ ) { - if ( data_goff[i] == ULONG_MAX ) { - continue; - } - - /* Rewind capture stream */ - FD_TEST( 0==fseek( diff->iter[ i ].stream, (long)data_goff[i], SEEK_SET ) ); - - /* Copy data */ - FD_TEST( meta[i].data_sz == fread( acc_data[i], 1UL, meta[i].data_sz, diff->iter[i].stream ) ); - } - - for( ulong i=0; i<2; i++ ) { - char filename[ FD_BASE58_ENCODED_32_LEN+2+4+1 ]; - int res = snprintf( filename, sizeof(filename), "%s.%lu.bin", - FD_BASE58_ENC_32_ALLOCA( entry[i]->key ), i ); - FD_TEST( (res>0) & (res<(int)sizeof(filename)) ); - fd_solcap_dump_account_data( diff, meta+i, filename, acc_data[i] ); - } - - /* Inform user */ - printf( " (%s) data: %s/%s.0.bin\n" - " (%s) data: %s/%s.1.bin\n" - " vimdiff <(xxd '%s/%s.bin') <(xxd '%s/%s.bin')\n", - diff->file_paths[0], diff->dump_dir, FD_BASE58_ENC_32_ALLOCA( entry[0]->key ), - diff->file_paths[1], diff->dump_dir, FD_BASE58_ENC_32_ALLOCA( entry[1]->key ), - diff->dump_dir, FD_BASE58_ENC_32_ALLOCA( entry[0]->key ), - diff->dump_dir, FD_BASE58_ENC_32_ALLOCA( entry[1]->key ) ); - - if( fd_solcap_can_pretty_print( meta[0].owner, entry[0]->key ) - & fd_solcap_can_pretty_print( meta[1].owner, entry[1]->key ) ) { - - for( ulong i=0UL; i<2UL; i++ ) { - /* Create YAML file */ - char path[ FD_BASE58_ENCODED_32_LEN+2+4+1 ]; - int res = snprintf( path, sizeof(path), "%s.%lu.yml", FD_BASE58_ENC_32_ALLOCA( entry[i]->key ), i ); - FD_TEST( (res>0) & (res<(int)sizeof(path)) ); - int fd = openat( diff->dump_dir_fd, path, O_CREAT|O_WRONLY|O_TRUNC, 0666 ); - if( FD_UNLIKELY( fd<0 ) ) - FD_LOG_ERR(( "openat(%d,%s) failed (%d-%s)", - diff->dump_dir_fd, path, errno, strerror( errno ) )); - - /* Write YAML file */ - FILE * file = fdopen( fd, "wb" ); - fd_solcap_account_pretty_print( entry[i]->key, meta[i].owner, acc_data[i], meta[i].data_sz, file ); - fclose( file ); /* closes fd */ - } - - - /* Inform user */ - printf( " vimdiff '%s/%s.0.yml' '%s/%s.1.yml'\n", - diff->dump_dir, FD_BASE58_ENC_32_ALLOCA( entry[0]->key ), - diff->dump_dir, FD_BASE58_ENC_32_ALLOCA( entry[1]->key ) ); - - } - } FD_SCRATCH_SCOPE_END; - } -} - -/* fd_solcap_diff_account prints further details about a mismatch - between two accounts. Preserves stream cursors. */ - -static void -fd_solcap_diff_account( fd_solcap_differ_t * diff, - fd_solcap_account_tbl_t const * const entry [ static 2 ], - ulong const acc_tbl_goff[ static 2 ] ) { - - /* Remember current file offsets (should probably just use readat) */ - long orig_off[ 2 ]; - for( ulong i=0UL; i<2UL; i++ ) { - orig_off[ i ] = ftell( diff->iter[ i ].stream ); - if( FD_UNLIKELY( orig_off[ i ]<0L ) ) - FD_LOG_ERR(( "ftell failed (%d-%s)", errno, strerror( errno ) )); - } - - /* Read account meta */ - fd_solcap_AccountMeta meta[2]; - ulong data_goff[2] = {ULONG_MAX, ULONG_MAX}; - for( ulong i=0UL; i<2UL; i++ ) { - FILE * stream = diff->iter[ i ].stream; - int err = fd_solcap_find_account( stream, meta+i, &data_goff[i], entry[i], acc_tbl_goff[i] ); - FD_TEST( err==0 ); - } - - if( 0!=memcmp( meta[0].owner, meta[1].owner, 32UL ) || - meta[0].lamports!=meta[1].lamports || - meta[0].data_sz!=meta[1].data_sz || - meta[0].slot!=meta[1].slot || - meta[0].executable!=meta[1].executable ) { - printf( "account: %s\n", FD_BASE58_ENC_32_ALLOCA( entry[0]->key ) ); - } - - if( 0!=memcmp( meta[0].owner, meta[1].owner, 32UL ) ) - printf( "%s owner: %s\n" - "%s owner: %s\n", - diff->file_paths[0], FD_BASE58_ENC_32_ALLOCA( meta[0].owner ), - diff->file_paths[1], FD_BASE58_ENC_32_ALLOCA( meta[1].owner ) ); - if( meta[0].lamports != meta[1].lamports ) - printf( " (%s) lamports: %lu\n" - " (%s) lamports: %lu\n", - diff->file_paths[0], meta[0].lamports, - diff->file_paths[1], meta[1].lamports ); - if( meta[0].data_sz != meta[1].data_sz ) - printf( " (%s) data_sz: %lu\n" - " (%s) data_sz: %lu\n", - diff->file_paths[0], meta[0].data_sz, - diff->file_paths[1], meta[1].data_sz ); - if( meta[0].slot != meta[1].slot ) - printf( " (%s) slot: %lu\n" - " (%s) slot: %lu\n", - diff->file_paths[0], meta[0].slot, - diff->file_paths[1], meta[1].slot ); - if( meta[0].executable != meta[1].executable ) - printf( " (%s) executable: %d\n" - " (%s) executable: %d\n", - diff->file_paths[0], meta[0].executable, - diff->file_paths[1], meta[1].executable ); - if( ( (meta[0].data_sz != 0UL) | fd_solcap_includes_account_data( &meta[0] ) ) - | ( (meta[1].data_sz != 0UL) | fd_solcap_includes_account_data( &meta[1] ) ) ) - fd_solcap_diff_account_data( diff, meta, entry, data_goff ); - - /* Restore file offsets */ - for( ulong i=0UL; i<2UL; i++ ) { - if( FD_UNLIKELY( 0!=fseek( diff->iter[ i ].stream, orig_off[ i ], SEEK_SET ) ) ) - FD_LOG_ERR(( "fseek failed (%d-%s)", errno, strerror( errno ) )); - } -} - -/* fd_solcap_diff_missing_account is like fd_solcap_diff_account but in - the case that either side of the account is missing entirely. */ - -static void -fd_solcap_diff_missing_account( fd_solcap_differ_t * diff, - fd_solcap_account_tbl_t const * const entry, - ulong const acc_tbl_goff, - FILE * stream ) { - - /* Remember current file offset */ - long orig_off = ftell( stream ); - if( FD_UNLIKELY( orig_off<0L ) ) - FD_LOG_ERR(( "ftell failed (%d-%s)", errno, strerror( errno ) )); - - /* Read account meta */ - fd_solcap_AccountMeta meta[1]; - ulong data_goff[1]; - int err = fd_solcap_find_account( stream, meta, data_goff, entry, acc_tbl_goff ); - FD_TEST( err==0 ); - - printf( - " lamports: %lu\n" - " data_sz: %lu\n" - " owner: %s\n" - " slot: %lu\n" - " executable: %d\n", - meta->lamports, - meta->data_sz, - FD_BASE58_ENC_32_ALLOCA( meta->owner ), - meta->slot, - meta->executable -); - - /* Dump account data to file */ - if( diff->verbose >= 4 ) { - - /* TODO: Remove hardcoded account size check */ - FD_TEST( meta->data_sz <= 8388608 ); - - FD_SCRATCH_SCOPE_BEGIN { - void * acc_data = fd_scratch_alloc( 1UL, meta->data_sz ); - - /* Rewind capture stream */ - FD_TEST( 0==fseek(stream, (long)*data_goff, SEEK_SET ) ); - - /* Copy data */ - FD_TEST( meta->data_sz == fread( acc_data, 1UL, meta->data_sz, stream ) ); - - char filename[ FD_BASE58_ENCODED_32_LEN+4+1 ]; - int res = snprintf( filename, sizeof(filename), "%s.bin", FD_BASE58_ENC_32_ALLOCA( entry->key ) ); - FD_TEST( (res>0) & (res<(int)sizeof(filename)) ); - fd_solcap_dump_account_data( diff, meta, filename, acc_data ); - - /* Inform user */ - printf( " data: %s/%s.bin\n" - " xxd '%s/%s.bin'\n" - " explorer: 'https://explorer.solana.com/block/%lu?accountFilter=%s&filter=all'", - diff->dump_dir, FD_BASE58_ENC_32_ALLOCA( entry->key ), - diff->dump_dir, FD_BASE58_ENC_32_ALLOCA( entry->key ), - meta->slot, FD_BASE58_ENC_32_ALLOCA( entry->key ) ); - -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wnonnull" - if( fd_solcap_can_pretty_print( meta->owner, entry->key ) ) { -#pragma GCC diagnostic pop - /* Create YAML file */ - char path[ FD_BASE58_ENCODED_32_LEN+4+1 ]; - int res = snprintf( path, sizeof(path), "%s.yml", FD_BASE58_ENC_32_ALLOCA( entry->key ) ); - FD_TEST( (res>0) & (res<(int)sizeof(path)) ); - int fd = openat( diff->dump_dir_fd, path, O_CREAT|O_WRONLY|O_TRUNC, 0666 ); - if( FD_UNLIKELY( fd<0 ) ) - FD_LOG_ERR(( "openat(%d,%s) failed (%d-%s)", - diff->dump_dir_fd, path, errno, strerror( errno ) )); - - /* Write YAML file */ - FILE * file = fdopen( fd, "wb" ); - fd_solcap_account_pretty_print( entry->key, meta->owner, acc_data, meta->data_sz, file ); - fclose( file ); /* closes fd */ - - /* Inform user */ - printf( " cat '%s/%s.yml'\n", - diff->dump_dir, FD_BASE58_ENC_32_ALLOCA( entry->key ) ); - } - } FD_SCRATCH_SCOPE_END; - } -} - -/* fd_solcap_diff_account_tbl detects and prints differences in the - accounts that were hashed into the account delta hash. */ - -static int -fd_solcap_diff_account_tbl( fd_solcap_differ_t * diff ) { - int has_mismatch = 0; - - /* Read and sort tables */ - - fd_solcap_account_tbl_t * tbl [2]; - fd_solcap_account_tbl_t * tbl_end[2]; - ulong chunk_goff[2]; - for( ulong i=0UL; i<2UL; i++ ) { - if( diff->preimage[i].account_table_coff == 0L ) { - FD_LOG_WARNING(( "Missing accounts table in capture" )); - return 1; - } - chunk_goff[i] = (ulong)( (long)diff->iter[i].chunk_off + diff->preimage[i].account_table_coff ); - - /* Read table meta and seek to table */ - FILE * stream = diff->iter[i].stream; - fd_solcap_AccountTableMeta meta[1]; - int err = fd_solcap_find_account_table( stream, meta, chunk_goff[i] ); - FD_TEST( err==0 ); - - if( FD_UNLIKELY( meta->account_table_cnt > INT_MAX ) ) { - FD_LOG_WARNING(( "Too many accounts in capture" )); - return 1; - } - - /* Allocate table */ - ulong tbl_cnt = meta->account_table_cnt; - ulong tbl_align = alignof(fd_solcap_account_tbl_t); - ulong tbl_sz = tbl_cnt * sizeof(fd_solcap_account_tbl_t); - FD_TEST( fd_scratch_alloc_is_safe( tbl_align, tbl_sz ) ); - tbl [i] = fd_scratch_alloc( tbl_align, tbl_sz ); - tbl_end[i] = tbl[i] + tbl_cnt; - - /* Read table */ - FD_TEST( tbl_cnt==fread( tbl[i], sizeof(fd_solcap_account_tbl_t), tbl_cnt, stream ) ); - } - - typedef struct { - fd_solcap_account_tbl_t const * entry; - int is_last_occurrence; - } account_entry_info_t; - - account_entry_info_t * entries[2]; - ulong tbl_cnt[2]; - for( ulong i=0UL; i<2UL; i++ ) { - tbl_cnt[i] = (ulong)(tbl_end[i] - tbl[i]); - entries[i] = fd_scratch_alloc( alignof(account_entry_info_t), tbl_cnt[i] * sizeof(account_entry_info_t) ); - - for( ulong j=0UL; j < tbl_cnt[i]; j++ ) { - entries[i][j].entry = &tbl[i][j]; - entries[i][j].is_last_occurrence = 1; - } - - for( ulong j=0UL; j < tbl_cnt[i]; j++ ) { - for( ulong k=j+1UL; k < tbl_cnt[i]; k++ ) { - if( memcmp(entries[i][j].entry->key, entries[i][k].entry->key, sizeof(entries[i][j].entry->key)) == 0 ) { - entries[i][j].is_last_occurrence = 0; - break; - } - } - } - } - - for( ulong i=0UL; i<2UL; i++ ) { - for( ulong j=0UL; j < tbl_cnt[i]-1UL; j++ ) { - for( ulong k=j+1UL; k < tbl_cnt[i]; k++ ) { - if( memcmp(entries[i][j].entry->key, entries[i][k].entry->key, sizeof(entries[i][j].entry->key)) > 0 ) { - account_entry_info_t temp = entries[i][j]; - entries[i][j] = entries[i][k]; - entries[i][k] = temp; - } - } - } - } - - /* Walk tables in parallel */ - ulong idx[2] = {0UL, 0UL}; - - for(;;) { - while( idx[0] < tbl_cnt[0] && !entries[0][idx[0]].is_last_occurrence ) idx[0]++; - while( idx[1] < tbl_cnt[1] && !entries[1][idx[1]].is_last_occurrence ) idx[1]++; - - if( idx[0] >= tbl_cnt[0] ) break; - if( idx[1] >= tbl_cnt[1] ) break; - - fd_solcap_account_tbl_t const * a = entries[0][idx[0]].entry; - fd_solcap_account_tbl_t const * b = entries[1][idx[1]].entry; - - int key_cmp = memcmp( a->key, b->key, 32UL ); - if( key_cmp==0 ) { - if( diff->verbose >= 3 ) { - fd_solcap_account_tbl_t const * const entry_pair[2] = {a, b}; - fd_solcap_diff_account( diff, entry_pair, chunk_goff ); - } - idx[0]++; - idx[1]++; - continue; - } - - if( key_cmp<0 ) { - has_mismatch = 1; - printf( "\n (%s) account: %s\n", diff->file_paths[0], FD_BASE58_ENC_32_ALLOCA( a->key ) ); - if( diff->verbose >= 3 ) - fd_solcap_diff_missing_account( diff, a, chunk_goff[0], diff->iter[0].stream ); - idx[0]++; - continue; - } - - if( key_cmp>0 ) { - has_mismatch = 1; - printf( "\n (%s) account: %s\n", diff->file_paths[1], FD_BASE58_ENC_32_ALLOCA( b->key ) ); - if( diff->verbose >= 3 ) - fd_solcap_diff_missing_account( diff, b, chunk_goff[1], diff->iter[1].stream ); - idx[1]++; - continue; - } - } - - while( idx[0] < tbl_cnt[0] ) { - has_mismatch = 1; - if( entries[0][idx[0]].is_last_occurrence ) { - printf( "\n (%s) account: %s\n", diff->file_paths[0], FD_BASE58_ENC_32_ALLOCA( entries[0][idx[0]].entry->key ) ); - if( diff->verbose >= 3 ) - fd_solcap_diff_missing_account( diff, entries[0][idx[0]].entry, chunk_goff[0], diff->iter[0].stream ); - } - idx[0]++; - } - while( idx[1] < tbl_cnt[1] ) { - has_mismatch = 1; - if( entries[1][idx[1]].is_last_occurrence ) { - printf( "\n (%s) account: %s\n", diff->file_paths[1], FD_BASE58_ENC_32_ALLOCA( entries[1][idx[1]].entry->key ) ); - if( diff->verbose >= 3 ) - fd_solcap_diff_missing_account( diff, entries[1][idx[1]].entry, chunk_goff[1], diff->iter[1].stream ); - } - idx[1]++; - } - - return has_mismatch; -} - -/* fd_solcap_diff_bank detects bank hash mismatches and prints a - human-readable description of the root cause to stdout. Returns 0 - if bank hashes match, 1 if a mismatch was detected. */ - -static int -fd_solcap_diff_bank( fd_solcap_differ_t * diff ) { - - fd_solcap_BankPreimage const * pre = diff->preimage; - - FD_TEST( pre[0].slot == pre[1].slot ); - - int has_bank_hash_mismatch = 0!=memcmp( pre[0].bank_hash, pre[1].bank_hash, 32UL ); - - int has_any_mismatch = has_bank_hash_mismatch; - if( 0!=memcmp( pre[0].prev_bank_hash, pre[1].prev_bank_hash, 32UL ) ) has_any_mismatch = 1; - if( 0!=memcmp( pre[0].account_delta_hash, pre[1].account_delta_hash, 32UL ) ) has_any_mismatch = 1; - if( 0!=memcmp( pre[0].accounts_lt_hash_checksum, pre[1].accounts_lt_hash_checksum, 32UL ) ) has_any_mismatch = 1; - if( 0!=memcmp( pre[0].poh_hash, pre[1].poh_hash, 32UL ) ) has_any_mismatch = 1; - if( pre[0].signature_cnt != pre[1].signature_cnt ) has_any_mismatch = 1; - if( pre[0].account_cnt != pre[1].account_cnt ) has_any_mismatch = 1; - - - if( diff->verbose < 2 ) { - if( has_bank_hash_mismatch ) { - printf( "\nbank hash mismatch at slot=%lu\n", pre[0].slot ); - printf( "(%s) bank_hash: %s\n" - "(%s) bank_hash: %s\n", - diff->file_paths[0], FD_BASE58_ENC_32_ALLOCA( pre[0].bank_hash ), - diff->file_paths[1], FD_BASE58_ENC_32_ALLOCA( pre[1].bank_hash ) ); - } - return has_bank_hash_mismatch; - } - - if( has_any_mismatch ) - printf( "\nbank hash mismatch at slot=%lu\n", pre[0].slot ); - - if( 0!=memcmp( pre[0].bank_hash, pre[1].bank_hash, 32UL ) ) { - printf( "(%s) bank_hash: %s\n" - "(%s) bank_hash: %s\n", - diff->file_paths[0], FD_BASE58_ENC_32_ALLOCA( pre[0].bank_hash ), - diff->file_paths[1], FD_BASE58_ENC_32_ALLOCA( pre[1].bank_hash ) ); - } - - if( 0!=memcmp( pre[0].account_delta_hash, pre[1].account_delta_hash, 32UL ) ) { - printf( "(%s) account_delta_hash: %s\n" - "(%s) account_delta_hash: %s\n", - diff->file_paths[0], FD_BASE58_ENC_32_ALLOCA( pre[0].account_delta_hash ), - diff->file_paths[1], FD_BASE58_ENC_32_ALLOCA( pre[1].account_delta_hash ) ); - } - if( 0!=memcmp( pre[0].accounts_lt_hash_checksum, pre[1].accounts_lt_hash_checksum, 32UL ) ) { - printf( "(%s) accounts_lt_hash_checksum: %s\n" - "(%s) accounts_lt_hash_checksum: %s\n", - diff->file_paths[0], FD_BASE58_ENC_32_ALLOCA( pre[0].accounts_lt_hash_checksum ), - diff->file_paths[1], FD_BASE58_ENC_32_ALLOCA( pre[1].accounts_lt_hash_checksum ) ); - } - if( 0!=memcmp( pre[0].prev_bank_hash, pre[1].prev_bank_hash, 32UL ) ) { - printf( "(%s) prev_bank_hash: %s\n" - "(%s) prev_bank_hash: %s\n", - diff->file_paths[0], FD_BASE58_ENC_32_ALLOCA( pre[0].prev_bank_hash ), - diff->file_paths[1], FD_BASE58_ENC_32_ALLOCA( pre[1].prev_bank_hash ) ); - } - if( 0!=memcmp( pre[0].poh_hash, pre[1].poh_hash, 32UL ) ) { - printf( "(%s) poh_hash: %s\n" - "(%s) poh_hash: %s\n", - diff->file_paths[0], FD_BASE58_ENC_32_ALLOCA( pre[0].poh_hash ), - diff->file_paths[1], FD_BASE58_ENC_32_ALLOCA( pre[1].poh_hash ) ); - } - if( pre[0].signature_cnt != pre[1].signature_cnt ) { - printf( "(%s) signature_cnt: %lu\n" - "(%s) signature_cnt: %lu\n", - diff->file_paths[0], pre[0].signature_cnt, - diff->file_paths[1], pre[1].signature_cnt ); - } - if( pre[0].account_cnt != pre[1].account_cnt ) { - printf( "(%s) account_cnt: %lu\n" - "(%s) account_cnt: %lu\n", - diff->file_paths[0], pre[0].account_cnt, - diff->file_paths[1], pre[1].account_cnt ); - } - printf( "\n" ); - - int has_account_tbl_mismatch = 0; - if( diff->verbose >= 3 ) { - fd_scratch_push(); - has_account_tbl_mismatch = fd_solcap_diff_account_tbl( diff ); - fd_scratch_pop(); - } - - return has_any_mismatch || has_account_tbl_mismatch; -} - -/* Diffs two transaction results with each other. */ -static void -fd_solcap_transaction_fd_diff( fd_solcap_txn_differ_t * txn_differ ) { - if ( FD_UNLIKELY( memcmp( txn_differ->transaction[0].txn_sig, - txn_differ->transaction[1].txn_sig, 32UL ) != 0 ) ) { - /* Transactions don't line up. */ - FD_LOG_WARNING(( "Transaction signatures are different for slot=%lu, signature=(%s != %s)." - "It is possible that either the transactions are out of order or some transactions are missing.", - txn_differ->transaction[0].slot, - FD_BASE58_ENC_64_ALLOCA( txn_differ->transaction[0].txn_sig ), - FD_BASE58_ENC_64_ALLOCA( txn_differ->transaction[1].txn_sig ) )); - } - else { - bool diff_txns = txn_differ->transaction[0].fd_txn_err != txn_differ->transaction[1].fd_txn_err; - bool diff_cus = txn_differ->transaction[0].fd_cus_used != txn_differ->transaction[1].fd_cus_used; - bool diff_instr_err_idx = txn_differ->transaction[0].instr_err_idx != txn_differ->transaction[1].instr_err_idx; - if ( diff_txns || diff_cus ) { - printf( - "\nslot: %lu\n" - "txn_sig: '%s'\n", - txn_differ->transaction[0].slot, - FD_BASE58_ENC_64_ALLOCA( txn_differ->transaction[0].txn_sig) ); - } - if ( diff_txns ) { - printf( - " (+) txn_err: %d\n" - " (-) txn_err: %d\n", - txn_differ->transaction[0].fd_txn_err, - txn_differ->transaction[1].fd_txn_err ); - } - if ( diff_cus ) { - printf( - " (+) cus_used: %lu\n" - " (-) cus_used: %lu\n", - txn_differ->transaction[0].fd_cus_used, - txn_differ->transaction[1].fd_cus_used ); - } - if ( diff_instr_err_idx ) { - printf( - " (+) instr_err_idx: %d\n" - " (-) instr_err_idx: %d\n", - txn_differ->transaction[0].instr_err_idx, - txn_differ->transaction[1].instr_err_idx ); - } - } -} - -/* Diffs firedancer transaction result with solana's result iff it is included - in the solcap. The solana result comes from rocksdb. This diff is generated - from just one fd_solcap_Transaction object */ -static void -fd_solcap_transaction_solana_diff( fd_solcap_Transaction * transaction, - ulong start_slot, - ulong end_slot ) { - if ( transaction->slot < start_slot || transaction->slot > end_slot ) { - return; - } - - if ( transaction->solana_txn_err == ULONG_MAX && transaction->solana_cus_used == ULONG_MAX ) { - /* If solana_txn_err and solana_cus_used are both not populated, don't print diff */ - return; - } else if ( transaction->solana_txn_err == ULONG_MAX ) { - /* Print diff if the solana_txn_err is not set (txn executed successfully) */ - transaction->solana_txn_err = 0; - } - - /* Only print a diff if cus or transaction result is different */ - if( !!( transaction->fd_txn_err ) != !!( transaction->solana_txn_err ) || - transaction->fd_cus_used != transaction->solana_cus_used ) { - printf( - "slot: %lu\n" - "txn_sig: '%s'\n" - " (+) txn_err: %d\n" - " (-) solana_txn_err: %lu\n" - " (+) cus_used: %lu\n" - " (-) solana_cus_used: %lu\n" - " instr_err_idx: %d\n" - " explorer: 'https://explorer.solana.com/tx/%s'\n" - " solscan: 'https://solscan.io/tx/%s'\n" - " solanafm: 'https://solana.fm/tx/%s'\n", - transaction->slot, - FD_BASE58_ENC_64_ALLOCA( transaction->txn_sig ), - transaction->fd_txn_err, - transaction->solana_txn_err, - transaction->fd_cus_used, - transaction->solana_cus_used, - transaction->instr_err_idx, - FD_BASE58_ENC_64_ALLOCA( transaction->txn_sig ), - FD_BASE58_ENC_64_ALLOCA( transaction->txn_sig ), - FD_BASE58_ENC_64_ALLOCA( transaction->txn_sig ) ); - } -} - -static void -fd_solcap_get_transaction_from_iter( fd_solcap_txn_differ_t * differ, ulong idx ) { - if ( fd_solcap_chunk_iter_done( &differ->iter[idx] ) ) - return; - - fd_solcap_chunk_t const * chunk = fd_solcap_chunk_iter_item( &differ->iter[idx] ); - - if( FD_UNLIKELY( 0!=fseek( differ->file[idx], differ->chunk_gaddr[idx] + (long)chunk->meta_coff, SEEK_SET ) ) ) { - FD_LOG_ERR(( "fseek transaction meta failed (%d-%s)", errno, strerror( errno ) )); - } - if( FD_UNLIKELY( chunk->meta_sz != fread( &differ->meta_buf[idx], 1UL, chunk->meta_sz, differ->file[idx] ) ) ) { - FD_LOG_ERR(( "fread transaction meta failed (%d-%s)", errno, strerror( errno ) )); - } - - pb_istream_t stream = pb_istream_from_buffer( differ->meta_buf[idx], chunk->meta_sz ); - if( FD_UNLIKELY( !pb_decode( &stream, fd_solcap_Transaction_fields, &differ->transaction[idx] ) ) ) { - FD_LOG_HEXDUMP_DEBUG(( "transaction meta", differ->meta_buf[idx], chunk->meta_sz )); - FD_LOG_ERR(( "pb_decode transaction meta failed (%s)", PB_GET_ERROR(&stream) )); - } -} - -static void -fd_solcap_transaction_iter( fd_solcap_txn_differ_t * txn_differ, ulong idx ) { - while ( !fd_solcap_chunk_iter_done( &txn_differ->iter[idx] ) ) { - txn_differ->chunk_gaddr[idx] = fd_solcap_chunk_iter_find( &txn_differ->iter[idx], FD_SOLCAP_V1_TRXN_MAGIC ); - fd_solcap_get_transaction_from_iter( txn_differ, idx ); - fd_solcap_transaction_solana_diff( &txn_differ->transaction[idx], 0, ULONG_MAX ); - } -} - -static void -fd_solcap_txn_differ_advance( fd_solcap_txn_differ_t * txn_differ ) { - while ( !fd_solcap_chunk_iter_done( &txn_differ->iter[0] ) && - !fd_solcap_chunk_iter_done( &txn_differ->iter[1] ) ) { - /* Diff transactions against both solana result (rocksdb) and against each other */ - fd_solcap_transaction_fd_diff( txn_differ ); - fd_solcap_transaction_solana_diff( &txn_differ->transaction[0], 0, ULONG_MAX ); - txn_differ->chunk_gaddr[0] = fd_solcap_chunk_iter_find( &txn_differ->iter[0], FD_SOLCAP_V1_TRXN_MAGIC ); - txn_differ->chunk_gaddr[1] = fd_solcap_chunk_iter_find( &txn_differ->iter[1], FD_SOLCAP_V1_TRXN_MAGIC ); - fd_solcap_get_transaction_from_iter( txn_differ, 0 ); - fd_solcap_get_transaction_from_iter( txn_differ, 1 ); - } -} - -static void fd_solcap_txn_differ_sync( fd_solcap_txn_differ_t * txn_differ ) { - /* Find first transaction for both files */ - for( int i=0; i<2; i++ ) { - txn_differ->chunk_gaddr[i] = fd_solcap_chunk_iter_find( &txn_differ->iter[i], FD_SOLCAP_V1_TRXN_MAGIC ); - if( FD_UNLIKELY( txn_differ->chunk_gaddr[i] < 0L ) ) { - int err = fd_solcap_chunk_iter_err( &txn_differ->iter[i] ); - if( err == 0 ) break; - FD_LOG_ERR(( "fd_solcap_chunk_iter_next() failed (%d-%s)", err, strerror( err ) )); - } - } - - /* Get first transaction on both */ - fd_solcap_get_transaction_from_iter( txn_differ, 0 ); - fd_solcap_get_transaction_from_iter( txn_differ, 1 ); - - for(;;) { - /* If one is done but not the other, iterate through the rest of the - transactions in order to generate a diff against solana's transactions */ - if( fd_solcap_chunk_iter_done( &txn_differ->iter[0] ) ) { - fd_solcap_transaction_iter( txn_differ, 1 ); - break; - } else if( fd_solcap_chunk_iter_done( &txn_differ->iter[1] ) ) { - fd_solcap_transaction_iter( txn_differ, 0 ); - break; - } - - /* Otherwise, try to sync up the two files, printing any solana diffs along the way */ - if( txn_differ->transaction[0].slot == txn_differ->transaction[1].slot ) { - fd_solcap_txn_differ_advance( txn_differ ); - } else if( txn_differ->transaction[0].slot < txn_differ->transaction[1].slot ) { - /* Advance index 0 only */ - fd_solcap_transaction_solana_diff( &txn_differ->transaction[0], 0, ULONG_MAX ); - txn_differ->chunk_gaddr[0] = fd_solcap_chunk_iter_find( &txn_differ->iter[0], FD_SOLCAP_V1_TRXN_MAGIC ); - fd_solcap_get_transaction_from_iter( txn_differ, 0 ); - } else if( txn_differ->transaction[1].slot < txn_differ->transaction[0].slot ) { - /* Advance index 1 only */ - fd_solcap_transaction_solana_diff( &txn_differ->transaction[1], 0, ULONG_MAX ); - txn_differ->chunk_gaddr[1] = fd_solcap_chunk_iter_find( &txn_differ->iter[1], FD_SOLCAP_V1_TRXN_MAGIC ); - fd_solcap_get_transaction_from_iter( txn_differ, 1 ); - } - } -} - -static void fd_solcap_transaction_diff( FILE * file_zero, FILE * file_one ) { - - if ( FD_UNLIKELY( fseek( file_zero, 0, SEEK_SET ) != 0 ) ) { - FD_LOG_ERR(( "fseek to start of file failed (%d-%s)", errno, strerror( errno ) )); - } - if ( FD_UNLIKELY( fseek( file_one, 0, SEEK_SET ) != 0 ) ) { - FD_LOG_ERR(( "fseek to start of file failed (%d-%s)", errno, strerror( errno ) )); - } - - /* Read file header and seek to first chunk to begin interation */ - fd_solcap_fhdr_t fhdr_zero[1]; - fd_solcap_fhdr_t fhdr_one[1]; - ulong n_zero = fread( fhdr_zero, sizeof(fd_solcap_fhdr_t), 1UL, file_zero ); - ulong n_one = fread( fhdr_one, sizeof(fd_solcap_fhdr_t), 1UL, file_one ); - - if ( FD_UNLIKELY( n_zero != 1UL ) ) { - FD_LOG_ERR(( "fread file header failed (%d-%s)", errno, strerror( errno ) )); - } - if ( FD_UNLIKELY( n_one != 1UL ) ) { - FD_LOG_ERR(( "fread file header failed (%d-%s)", errno, strerror( errno ) )); - } - int err; - err = fseek( file_zero, (long)fhdr_zero->chunk0_foff - (long)sizeof(fd_solcap_fhdr_t), SEEK_CUR ); - if( FD_UNLIKELY( err < 0L ) ) { - FD_LOG_ERR(( "fseek chunk0 failed (%d-%s)", errno, strerror( errno ) )); - } - err = fseek( file_one, (long)fhdr_one->chunk0_foff - (long)sizeof(fd_solcap_fhdr_t), SEEK_CUR ); - if( FD_UNLIKELY( err < 0L ) ) { - FD_LOG_ERR(( "fseek chunk0 failed (%d-%s)", errno, strerror( errno ) )); - } - - /* Setup txn_differ */ - fd_solcap_txn_differ_t txn_differ; - txn_differ.file[0] = file_zero; - txn_differ.file[1] = file_one; - fd_solcap_chunk_iter_new( &txn_differ.iter[0], file_zero ); - fd_solcap_chunk_iter_new( &txn_differ.iter[1], file_one ); - - /* Iterate and diff throught the transactions */ - fd_solcap_txn_differ_sync( &txn_differ ); -} - -void -fd_solcap_one_file_transaction_diff( FILE * file, ulong start_slot, ulong end_slot ) { - /* Open and read the header */ - fd_solcap_fhdr_t fhdr[1]; - ulong n = fread( fhdr, sizeof(fd_solcap_fhdr_t), 1UL, file ); - if( FD_UNLIKELY( n!=1UL ) ) { - FD_LOG_ERR(( "fread file header failed (%d-%s)", errno, strerror( errno ) )); - } - - /* Seek to the first chunk */ - int err = fseek( file, (long)fhdr->chunk0_foff - (long)sizeof(fd_solcap_fhdr_t), SEEK_CUR ); - if( FD_UNLIKELY( err<0L ) ) { - FD_LOG_ERR(( "fseek chunk0 failed (%d-%s)", errno, strerror( errno ) )); - } - - /* Iterate through the chunks diffing the trnasactions */ - fd_solcap_chunk_iter_t iter[1]; - fd_solcap_chunk_iter_new( iter, file ); - while( !fd_solcap_chunk_iter_done( iter ) ) { - long chunk_gaddr = fd_solcap_chunk_iter_find( iter, FD_SOLCAP_V1_TRXN_MAGIC ); - if( FD_UNLIKELY( chunk_gaddr<0L ) ) { - int err = fd_solcap_chunk_iter_err( iter ); - if( err==0 ) break; - FD_LOG_ERR(( "fd_solcap_chunk_iter_next() failed (%d-%s)", err, strerror( err ) )); - } - - fd_solcap_chunk_t const * chunk = fd_solcap_chunk_iter_item( iter ); - if( FD_UNLIKELY( !chunk ) ) FD_LOG_ERR(( "fd_solcap_chunk_item() failed" )); - - /* Read transaction meta */ - - uchar meta_buf[ 128UL ]; - if( FD_UNLIKELY( 0!=fseek( file, chunk_gaddr + (long)chunk->meta_coff, SEEK_SET ) ) ) { - FD_LOG_ERR(( "fseek transaction meta failed (%d-%s)", errno, strerror( errno ) )); - } - if( FD_UNLIKELY( chunk->meta_sz != fread( meta_buf, 1UL, chunk->meta_sz, file ) ) ) { - FD_LOG_ERR(( "fread transaction meta failed (%d-%s)", errno, strerror( errno ) )); - } - - /* Deserialize transaction meta */ - - pb_istream_t stream = pb_istream_from_buffer( meta_buf, chunk->meta_sz ); - - fd_solcap_Transaction meta; - if( FD_UNLIKELY( !pb_decode( &stream, fd_solcap_Transaction_fields, &meta ) ) ) { - FD_LOG_HEXDUMP_DEBUG(( "transaction meta", meta_buf, chunk->meta_sz )); - FD_LOG_ERR(( "pb_decode transaction meta failed (%s)", PB_GET_ERROR(&stream) )); - } - - if( meta.slot < start_slot || meta.slot > end_slot ) { - continue; - } - - fd_solcap_transaction_solana_diff( &meta, start_slot, end_slot ); - } -} - -static void -usage( void ) { - fprintf( stderr, - "Usage: fd_solcap_diff [options] {FILE1} {FILE2}\n" - "\n" - "Imports a runtime capture file from JSON.\n" - "\n" - "Options:\n" - " --page-sz {gigantic|huge|normal} Page size\n" - " --page-cnt {count} Page count\n" - " --scratch-mb 1024 Scratch mem MiB\n" - " -v 1 Diff verbosity\n" - " --dump-dir {dir} Dump directory\n" - " --start-slot {slot} Start slot\n" - " --end-slot {slot} End slot\n" - "\n" ); -} - -int -main( int argc, - char ** argv ) { - fd_boot( &argc, &argv ); - - /* Command line handling */ - - for( int i=1; i=2 ) { usage(); return 1; } - cap_path[ caps_found++ ] = argv[i]; - } - - if( caps_found==1 ) { /* Support one file being passed in to see transaction diff */ - cap_path[ caps_found++ ] = cap_path[ 0 ]; - } - else if( caps_found!=2 ) { - fprintf( stderr, "ERROR: expected 2 arguments, got %d\n", argc-1 ); - usage(); - return 1; - } - - /* Acquire workspace */ - - fd_wksp_t * wksp = fd_wksp_new_anonymous( page_sz, page_cnt, fd_log_cpu_id(), "wksp", 0UL ); - if( FD_UNLIKELY( !wksp ) ) FD_LOG_ERR(( "fd_wksp_new_anonymous() failed" )); - - /* Create scratch allocator */ - - ulong smax = scratch_mb << 20; - void * smem = fd_wksp_alloc_laddr( wksp, fd_scratch_smem_align(), smax, 1UL ); - if( FD_UNLIKELY( !smem ) ) FD_LOG_ERR(( "Failed to alloc scratch mem" )); - -# define SCRATCH_DEPTH (4UL) - ulong fmem[ SCRATCH_DEPTH ] __attribute((aligned(FD_SCRATCH_FMEM_ALIGN))); - - fd_scratch_attach( smem, fmem, smax, SCRATCH_DEPTH ); - - /* Open capture files for reading */ - - FILE * cap_file[2] = {0}; - cap_file[0] = fopen( cap_path[0], "rb" ); - cap_file[1] = strcmp( cap_path[0], cap_path[1] ) ? fopen( cap_path[1], "rb" ) : cap_file[0]; - - if ( FD_UNLIKELY( !cap_file[0] ) ) - FD_LOG_ERR(( "fopen failed (%d-%s) on file=%s", errno, strerror( errno ), cap_path[0] )); - if ( FD_UNLIKELY( !cap_file[1] ) ) - FD_LOG_ERR(( "fopen failed (%d-%s) on file=%s", errno, strerror( errno ), cap_path[1] )); - - /* Create dump dir */ - - if( mkdir( dump_dir, 0777 )<0 && errno!=EEXIST ) - FD_LOG_ERR(( "mkdir failed (%d-%s)", errno, strerror( errno ) )); - int dump_dir_fd = open( dump_dir, O_DIRECTORY ); - if( FD_UNLIKELY( dump_dir_fd<0 ) ) - FD_LOG_ERR(( "open(%s) failed (%d-%s)", dump_dir, errno, strerror( errno ) )); - - /* Handle the one file case before diffing for accounts/hashes */ - if( cap_file[0] == cap_file[1] ) { - FD_LOG_NOTICE(( "Only one file was passed in. Will only print transaction diffs." )); - fd_solcap_one_file_transaction_diff( cap_file[0], start_slot, end_slot ); - - /* Cleanup*/ - close( dump_dir_fd ); - fclose( cap_file[0] ); - FD_TEST( fd_scratch_frame_used()==0UL ); - fd_wksp_free_laddr( fd_scratch_detach( NULL ) ); - fd_halt(); - return 0; - } - - /* Create differ */ - - fd_solcap_differ_t diff[1]; - - /* Copy over up to last 16 chars */ - char file_name_zero[SOLCAP_FILE_NAME_LEN + 1]; - char file_name_one[SOLCAP_FILE_NAME_LEN + 1]; - char * normalized_file_paths[2] = {file_name_zero, file_name_one}; - normalize_filename( cap_path[0], normalized_file_paths[0], '+' ); - normalize_filename( cap_path[1], normalized_file_paths[1], '-' ); - - printf( "++%s\n", normalized_file_paths[0] ); - printf( "--%s\n\n", normalized_file_paths[1] ); - - if( FD_UNLIKELY( !fd_solcap_differ_new( diff, cap_file, (const char **)normalized_file_paths ) ) ) - return 1; - diff->verbose = verbose; - diff->dump_dir = dump_dir; - diff->dump_dir_fd = dump_dir_fd; - int res = fd_solcap_differ_sync( diff, start_slot, end_slot ); - if( res <0 ) FD_LOG_ERR(( "fd_solcap_differ_sync failed (%d-%s)", - -res, strerror( -res ) )); - if( res==0 ) FD_LOG_ERR(( "Captures don't share any slots" )); - - /* Diff each block for accounts and hashes */ - int found_mismatch = 0; - for(;;) { - if( FD_UNLIKELY( fd_solcap_diff_bank( diff ) ) ) { - found_mismatch = 1; - break; - } - printf( "Slot %10lu: OK\n", diff->preimage[0].slot ); - /* Advance to next slot. */ - int res = fd_solcap_differ_sync( diff, start_slot, end_slot ); - if( FD_UNLIKELY( res<0 ) ) - FD_LOG_ERR(( "fd_solcap_differ_sync failed (%d-%s)", - -res, strerror( -res ) )); - if( res==0 ) break; - } - - /* Check both files for transaction and produce a diff if possible. If both - files contain transaction info, this will produce a diff between the two - files. If one of the files contains the solana transaction info, then we - will also print the diffs between the solana and firedancer execution. */ - if ( verbose >= 4 ) { - printf( "\nTransaction diffs:\n" ); - fd_solcap_transaction_diff( cap_file[0], cap_file[1] ); - } - - /* Cleanup */ - - close( dump_dir_fd ); - fclose( cap_file[1] ); - fclose( cap_file[0] ); - FD_TEST( fd_scratch_frame_used()==0UL ); - fd_wksp_free_laddr( fd_scratch_detach( NULL ) ); - fd_halt(); - return found_mismatch; -} diff --git a/src/flamenco/capture/fd_solcap_import.c b/src/flamenco/capture/fd_solcap_import.c deleted file mode 100644 index e5b9aad3595..00000000000 --- a/src/flamenco/capture/fd_solcap_import.c +++ /dev/null @@ -1,346 +0,0 @@ -#include "../fd_flamenco.h" -#include "fd_solcap.pb.h" -#include "fd_solcap_proto.h" -#include "fd_solcap_writer.h" -#include "../../ballet/base58/fd_base58.h" -#include "../../ballet/base64/fd_base64.h" -#include "../../ballet/json/cJSON.h" - -#include -#include -#include -#include -#include -#include -#include - -#ifndef DT_REG -#define DT_REG (8UL) -#endif - -static int -usage( void ) { - fprintf( stderr, - "Usage: fd_solcap_import [options] {IN_DIR} {OUT_FILE}\n" - "\n" - "Imports a runtime capture directory from JSON.\n" - "\n" - "Options:\n" - " --page-sz {gigantic|huge|normal} Page size\n" - " --page-cnt {count} Page count\n" - " --scratch-mb 1024 Scratch mem MiB\n" - "\n" ); - return 0; -} - -/* fd_alloc wrapper */ - -static fd_alloc_t * current_alloc; -static void * my_malloc( ulong sz ) { return fd_alloc_malloc( current_alloc, 1UL, sz ); } -static void my_free ( void * p ) { fd_alloc_free ( current_alloc, p ); } - -static cJSON * -read_json_file( fd_wksp_t * wksp, - fd_alloc_t * alloc, - char const * path ) { - - /* Wire up fd_alloc with cJSON */ - - current_alloc = alloc; - cJSON_Hooks hooks = { - .malloc_fn = my_malloc, - .free_fn = my_free - }; - cJSON_InitHooks( &hooks ); - - /* Open file */ - - int fd; - fd = open( path, O_RDONLY ); - if( FD_UNLIKELY( fd<0 ) ) { - FD_LOG_WARNING(( "open(%s) failed (%d-%s)", path, errno, strerror( errno ) )); - return NULL; - } - - /* Figure out file size */ - - struct stat stat; - if( FD_UNLIKELY( fstat( fd, &stat )<0 ) ) { - FD_LOG_WARNING(( "fstat(%s) failed (%d-%s)", path, errno, strerror( errno ) )); - close( fd ); - return NULL; - } - - /* Allocate buffer to store file content */ - - char * buf = fd_wksp_alloc_laddr( wksp, 1UL, (ulong)stat.st_size, 1UL ); - if( FD_UNLIKELY( !buf ) ) { - FD_LOG_WARNING(( "Failed to alloc memory region to fit file content" )); - close( fd ); - return NULL; - } - - /* Copy file content to memory */ - - char * cursor = buf; - ulong rem = (ulong)stat.st_size; - while( rem>0UL ) { - long n = read( fd, cursor, rem ); - if( FD_UNLIKELY( n<0L ) ) { - FD_LOG_WARNING(( "read(%s) failed (%d-%s)", path, errno, strerror( errno ) )); - close( fd ); - return NULL; - } - if( FD_UNLIKELY( n==0L ) ) { - FD_LOG_WARNING(( "read(%s) failed: unexpected EOF", path )); - close( fd ); - return NULL; - } - - cursor += (ulong)n; - rem -= (ulong)n; - } - - /* Call parser */ - - cJSON * json = cJSON_ParseWithLength( buf, (ulong)stat.st_size ); - - /* Clean up */ - - fd_wksp_free_laddr( buf ); - close( fd ); - return json; -} - -/* unmarshal_hash interprets given JSON node as a string containing - the Base58 encoding of 32 bytes. Copies the bytes out to out_buf. - Returns NULL if json is NULL, json is not string, or is not a valid - 32-byte Base58 encoding. Returns out_buf on success. */ - -static uchar * -unmarshal_hash( cJSON const * json, - uchar out_buf[static 32] ) { - - char const * str = cJSON_GetStringValue( json ); - if( FD_UNLIKELY( !str ) ) return NULL; - - return fd_base58_decode_32( str, out_buf ); -} - -/* unmarshal_bank_preimage reads top-level bank preimage information - from given JSON dictionary. Copies values into given out struct. - Aborts application via error log on failure. */ - -static void -unmarshal_bank_preimage( cJSON const * json, - fd_solcap_BankPreimage * out ) { - - cJSON * head = (cJSON *)json; - - cJSON * slot = cJSON_GetObjectItem( head, "slot" ); - out->slot = slot ? slot->valueulong : 0UL; - - FD_TEST( unmarshal_hash( cJSON_GetObjectItem( head, "bank_hash" ), out->bank_hash ) ); - FD_TEST( unmarshal_hash( cJSON_GetObjectItem( head, "parent_bank_hash" ), out->prev_bank_hash ) ); - if ( !unmarshal_hash( cJSON_GetObjectItem( head, "accounts_delta_hash" ), out->account_delta_hash ) ) - fd_memset(out->account_delta_hash, 0, sizeof(out->account_delta_hash)); - if ( !unmarshal_hash( cJSON_GetObjectItem( head, "accounts_lt_hash_checksum" ), out->accounts_lt_hash_checksum ) ) - fd_memset(out->account_delta_hash, 0, sizeof(out->accounts_lt_hash_checksum)); - FD_TEST( unmarshal_hash( cJSON_GetObjectItem( head, "last_blockhash" ), out->poh_hash ) ); - - if ( cJSON_GetObjectItem( head, "signature_count" ) != NULL ) - out->signature_cnt = cJSON_GetObjectItem( head, "signature_count" )->valueulong; - else - out->signature_cnt = 0; - - cJSON * accs = cJSON_GetObjectItem( head, "accounts" ); - FD_TEST( accs ); - out->account_cnt = (ulong)cJSON_GetArraySize( accs ); -} - -/* unmarshal_account reads account meta/data from given JSON object. - Object should be a dictionary and is found as an element of the - "accounts" array. On success, returns pointer to account data - (allocated in current scratch frame), and copies metadata to given - out structs. On failure, aborts application via error log. */ - -static void * -unmarshal_account( cJSON const * json, - fd_solcap_account_tbl_t * rec, - fd_solcap_AccountMeta * meta ) { - - /* TODO !!! THIS IS OBVIOUSLY UNSAFE - Representing lamports as double causes precision-loss for values - exceeding 2^53-1. This appears to be a limitation of the cJSON - library. */ - meta->lamports = cJSON_GetObjectItem( json, "lamports" )->valueulong; - - cJSON * executable_o = cJSON_GetObjectItem( json, "executable" ); - FD_TEST( executable_o ); - meta->executable = cJSON_IsBool( executable_o ) & cJSON_IsTrue( executable_o ); - - FD_TEST( unmarshal_hash( cJSON_GetObjectItem( json, "pubkey" ), rec->key ) ); - FD_TEST( unmarshal_hash( cJSON_GetObjectItem( json, "owner" ), meta->owner ) ); - - /* Data handling ... Base64 decode */ - - char const * data_b64 = cJSON_GetStringValue( cJSON_GetObjectItem( json, "data" ) ); - FD_TEST( data_b64 ); - - /* sigh ... cJSON doesn't remember string length, although it - obviously had this information while parsing. */ - ulong data_b64_len = strlen( data_b64 ); - - /* Very rough upper bound for decoded data sz. - Could do better here, but better to be on the safe side. */ - ulong approx_data_sz = 3UL + data_b64_len/2UL; - - /* Grab scratch memory suitable for storing account data */ - void * data = fd_scratch_alloc( /* align */ 1UL, /* sz */ approx_data_sz ); - FD_TEST( data ); - - /* Base64 decode */ - long data_sz = fd_base64_decode( data, data_b64, data_b64_len ); - FD_TEST( data_sz>=0L ); /* check for corruption */ - meta->data_sz = (ulong)data_sz; - - return data; -} - -void write_slots( const char * in_path, - fd_solcap_writer_t * writer, - fd_wksp_t * wksp, - fd_alloc_t * alloc ) { - /* Iterate through the directory to get all of the bank hash details file */ - struct dirent * ent; - DIR * dir = opendir( in_path ); - - if ( dir == NULL ) { - FD_LOG_ERR(( "unable to open the directory=%s", in_path )); - } - /* TODO: sort the files that are read in. The API makes no guarantee that the - files are alphabetically sorted, but in practice they are. */ - for ( ent = readdir( dir ); ent != NULL; ent = readdir( dir ) ) { - if ( ent->d_type != DT_REG ) { - continue; - } - - char path_buf[ 256UL ]; - char * path_buf_ptr = path_buf; - fd_memset( path_buf_ptr, '\0', sizeof( path_buf ) ); - fd_memcpy( path_buf_ptr, in_path, strlen( in_path ) ); - fd_memcpy( path_buf_ptr + strlen( in_path ), ent->d_name, strlen( ent->d_name ) ); - FD_LOG_NOTICE(( "Reading input file=%s", path_buf_ptr )); - - cJSON * json = read_json_file( wksp, alloc, path_buf_ptr ); - if( FD_UNLIKELY( !json ) ) { - FD_LOG_ERR(( "Failed to read input file=%s", path_buf_ptr )); - } - - // The structure of 1.18 is different to 1.17, and includes bank_hash_details - cJSON * bank_hash_details = cJSON_GetObjectItem( json, "bank_hash_details" ); - if ( bank_hash_details != NULL ) { - json = cJSON_GetArrayItem( bank_hash_details, 0 ); - } - - fd_solcap_BankPreimage preimg[1] = {{0}}; - unmarshal_bank_preimage( json, preimg ); - - fd_solcap_writer_set_slot( writer, preimg->slot ); - - cJSON * json_acc = cJSON_GetObjectItem( json, "accounts" ); - cJSON * acc = cJSON_GetArrayItem( json_acc, 0 ); - int n = cJSON_GetArraySize( json_acc ); - - for( int i=0; idata_sz ) ); - - fd_scratch_pop(); - - acc = acc->next; - } - - FD_TEST( 0==fd_solcap_write_bank_preimage2( writer, preimg ) ); - cJSON_free( json ); - } - closedir( dir ); -} - -int -main( int argc, - char ** argv ) { - fd_boot( &argc, &argv ); - - /* Command line handling */ - - for( int i=1; i +#include +#include + +#include /* fd_solcap_proto defines the capture data format "solcap". - solcap is a portable file format for capturing Solana runtime data - suitable for replay and debugging. It is laid out as follows: - - File Header - File Protobuf Object - Chunk 0 - Chunk Header - Chunk Protobuf Object - Data ... - Chunk 1 - Chunk Header - Chunk Protobuf Object - Data ... - Chunk N ... - - The file header briefly describes the file's content type and points - to the first chunk. Additional metadata, such as slot bounds, are - stored in a Protobuf blob following the header. Currently, the - following content types are implemented: - - SOLCAP_V1_BANK: Bank pre-image, version 0 - (assumed to only contain SOLCAP_V1_BANK chunks) - - Capture content is divided into variable-length chunks. Each chunk - contains a fixed-size binary header containing type and length - information. Following the header is a serialized Protobuf object - with chunk-specific information. - - Typically, readers sequentially read in chunks, loading one chunk - into memory at a time. Within a chunk, data structures are laid out - in arbitrary order which requires random access. Random access out- - side of chunks is rarely required. Readers should ignore chunks of - unknown content type. - - Furthermore: - - Byte order is little endian - - There should be no gaps between slots - - Suffix `_foff` ("file offset") refers to an offset from the - beginning of a stream - - Suffix `_coff` ("chunk offset") refers to an offset from the first - byte of the header of the current chunk - - Why a mix of C structs and Protobufs? We prefer the use of Protobuf - to easily support additions to the schema. Fixed-size/fixed-offset - structures are only used to support navigating the file. */ - -/* TODO Pending features: - - Fork support - - Compression - - Chunk Table */ - -/* FD_SOLCAP_V1_FILE_MAGIC identifies a solcap version 0 file. */ - -#define FD_SOLCAP_V1_FILE_MAGIC (0x806fe7581b1da4b7UL) - -/* fd_solcap_fhdr_t is the file header of a capture file. */ - -struct fd_solcap_fhdr { - /* 0x00 */ ulong magic; /* ==FD_SOLCAP_V1_NULL_MAGIC */ - /* 0x08 */ ulong chunk0_foff; /* Offset of first chunk from begin of stream */ - - /* Metadata; Protobuf fd_solcap_FileMeta */ - /* 0x10 */ uint meta_sz; - /* 0x14 */ uint _pad14[3]; + .solcap is a portable file format for capturing Solana runtime data + suitable for replay and debugging. It is laid out below: + + + [Section Header Block (file header) ] + [Interface Description Block (IDB, linktype=147, snaplen=0) ] + [Enhanced Packet Block #1 (interface_id=0)] + -- payload start: fd_solcap_chunk_int_hdr + -- payload rest: packet data (solcap custom format) + [Enhanced Packet Block #2] + -- ... + + The solcap format is compatible with the pcapng format, allowing for + easy interoperability with existing tools that support pcapng. The + format of the chunk headers is determined by the pcapng packet + blocks. See https://pcapng.com/ for more information. + + Section Header Block (SHB) - The file header. + This is the first block in the file and contains the file header. + None of this information is used by solcap analysis tools. + + Interface Description Block (IDB) - The header of the interface. + This is a required block for pcapng files and comes before the first + Enhanced Packet Block (EPB). + + Enhanced Packet Block (EPB) - A single solcap message. + The internal chunk header contains additional metadata about the + message, used for identifying the message and its position in the + stream. + + There can be a variety of messages within the EPB blocks, each + differentiated via an internal chunk header. This internal chunk + header allows for the reader to process the message in the correct + encoding scheme. Currently the list of messages is: + - Account Updates + - Bank Preimages + + The dumping of the exectuion can be done in multiple ways: + 1. To a 'capture link' which is read by a capture tile and then + subsequently written to a file. This is the default when running + firedancer live or a subcommand that uses a topo (backtest). + + 2. To a file directly. This is currently used for block harnesses. + The neccessity of this path is for the single threaded execution + mode of the harness. +*/ + +#define SOLCAP_WRITE_ACCOUNT_HDR (1UL) +#define SOLCAP_WRITE_ACCOUNT_DATA (2UL) +#define SOLCAP_STAKE_ACCOUNT_PAYOUT (4UL) +#define SOLCAP_STAKE_REWARDS_BEGIN (5UL) +#define SOLCAP_WRITE_BANK_PREIMAGE (6UL) +#define SOLCAP_WRITE_STAKE_REWARD_EVENT (7UL) +#define SOLCAP_WRITE_VOTE_ACCOUNT_PAYOUT (8UL) + +#define SOLCAP_SIG_MAP(x) (((const ushort[]){ \ + [SOLCAP_WRITE_ACCOUNT_HDR] = SOLCAP_WRITE_ACCOUNT_DATA, \ +})[x]) + + +/* FD_SOLCAP_V2_FILE_MAGIC identifies a solcap version 2 file. */ + +#define FD_SOLCAP_V1_FILE_MAGIC (0x806fe7581b1da4b7UL) /* deprecated */ +#define FD_SOLCAP_V2_FILE_MAGIC (0x0A0D0D0AUL) /* used in pcapng */ +#define FD_SOLCAP_V2_BYTE_ORDER_MAGIC (0x1A2B3C4DUL) /* used in pcapng */ + +/* fd_solcap_file_hdr_t is the file header of a capture file. This + format follows that of the pcapng file format - matching the pcapng + Section Header Block. +*/ + +struct __attribute__((packed)) fd_solcap_file_hdr { + /* 0x00 */ uint32_t block_type; /* 0x0A0D0D0A */ + /* 0x04 */ uint32_t block_len; /* length of the block */ + /* 0x08 */ uint32_t byte_order_magic; /* 0x1a2b3c4d */ + /* 0x0C */ uint32_t major_version; /* 0x00000001 */ + /* 0x10 */ uint32_t minor_version; /* 0x00000001 */ + /* 0x14 */ uint64_t section_len; /* (-1) length of the section */ + /* 0x1C */ /* Optional Section Data - kept as 0 in solcaps */ + /* 0x1C */ uint32_t block_len_redundant; /* length of the block */ + + }; +typedef struct fd_solcap_file_hdr fd_solcap_file_hdr_t; + +/* The fd_solcap_chunk_idb_hdr is the header of the Interface + Description Block (IDB) used by the pcapng file format, but basically + unused in solcap, only included for pcapng compatibility. +*/ +#define SOLCAP_PCAPNG_BLOCK_TYPE_IDB 1 +#define SOLCAP_PCAPNG_BLOCK_TYPE_EPB 6 +#define SOLCAP_IDB_HDR_LINK_TYPE 147 /* DLT_USER(0) */ +#define SOLCAP_IDB_HDR_SNAP_LEN 0 /* unlimited */ + +struct __attribute__((packed)) fd_solcap_chunk_idb_hdr { + /* 0x00 */ uint32_t block_type; /* pcap block type (1) */ + /* 0x04 */ uint32_t block_len; /* total block length */ + /* 0x08 */ uint16_t link_type; /* DLT_USER(0) = 147 */ + /* 0x0a */ uint16_t reserved; /* 0x0000 */ + /* 0x0c */ uint32_t snap_len; /* 0 = unlimited */ + /* options - blank for solcap */ + /* 0x10 */ uint32_t block_len_redundant; /* length of the block */ }; - -typedef struct fd_solcap_fhdr fd_solcap_fhdr_t; - -/* FD_SOLCAP_V1_{...}_MAGIC identifies a chunk type. - - NULL: ignored chunk -- can be used to patch out existing chunks - ACCT: account chunk - ACTB: account table chunk - BANK: bank hash preimage capture - Metadata Protobuf type fd_solcap_BankChunk */ - -#define FD_SOLCAP_V1_MAGIC_MASK (0xfffffffffffff000UL) -#define FD_SOLCAP_V1_NULL_MAGIC (0x805fe7580b1da000UL) -#define FD_SOLCAP_V1_ACCT_MAGIC (0x805fe7580b1da4bAUL) -#define FD_SOLCAP_V1_ACTB_MAGIC (0x805fe7580b1da4bBUL) -#define FD_SOLCAP_V1_BANK_MAGIC (0x805fe7580b1da4b8UL) -#define FD_SOLCAP_V1_TRXN_MAGIC (0x805fe7580b1da4bCUL) - -#define FD_SOLCAP_V1_REWARD_BEGIN_MAGIC (0x805fe7580b1da050UL) -#define FD_SOLCAP_V1_REWARD_CALC_MAGIC (0x805fe7580b1da051UL) -#define FD_SOLCAP_V1_REWARD_VOTE_MAGIC (0x805fe7580b1da052UL) -#define FD_SOLCAP_V1_REWARD_STAKE_MAGIC (0x805fe7580b1da053UL) - -FD_PROTOTYPES_BEGIN - -static inline int -fd_solcap_is_chunk_magic( ulong magic ) { - return (magic & FD_SOLCAP_V1_MAGIC_MASK) == FD_SOLCAP_V1_NULL_MAGIC; -} - -FD_PROTOTYPES_END - -/* fd_solcap_chunk_t is the fixed size header of a chunk. A "chunk - offset" points to the first byte of this structure. Immediately - following this structure is a serialized Protobuf blob, the type of - which is decided by the chunk's magic. meta_sz indicates the size - of such blob. */ - -struct fd_solcap_chunk { - /* 0x00 */ ulong magic; - /* 0x08 */ ulong total_sz; - /* 0x10 */ uint meta_coff; - /* 0x14 */ uint meta_sz; - /* 0x18 */ ulong _pad18; - /* 0x20 */ +typedef struct fd_solcap_chunk_idb_hdr fd_solcap_chunk_idb_hdr_t; + + +/* fd_solcap_chunk_epb_hdr is the fixed size header of a chunk. + It is the header of each solcap message - matching the pcapng + Enhanced Packet Block (EPB). + + Immediately following this structure is an internal chunk header, + an encoded solcap message, and a footer. + + fd_solcap_chunk_ftr_t is the footer of the chunk. It is a 4 octet + length field that is used as a redundant packet size, and for + backwards navigation on the file. +*/ + +struct __attribute__((packed)) fd_solcap_chunk_epb_hdr { + /* 0x00 */ uint32_t block_type; /* pcap block type (6) */ + /* 0x04 */ uint32_t block_len; /* total block length including footer */ + /* 0x08 */ uint32_t interface_id; /* 0 */ + /* 0x0c */ uint32_t timestamp_upper; /* upper 32 bits of timestamp */ + /* 0x10 */ uint32_t timestamp_lower; /* lower 32 bits of timestamp */ + /* 0x14 */ uint32_t captured_packet_len; /* captured packet length */ + /* 0x18 */ uint32_t original_packet_len; /* original packet length */ + /* 0x1c */ /* packet data follows immediately after this structure */ }; +typedef struct fd_solcap_chunk_epb_hdr fd_solcap_chunk_epb_hdr_t; -typedef struct fd_solcap_chunk fd_solcap_chunk_t; - -/* fd_solcap_account_tbl_t is an entry of the table of accounts that - were changed in a block. meta_coff points to the chunk offset of a - Protobuf-serialized fd_solcap_AccountMeta object, with serialized - size meta_sz. key is the account address. data_coff points to the - chunk offset of the account's data, with size data_sz. - - The table of accounts should ideally be sorted to match the order of - accounts in the accounts delta vector. */ - -struct fd_solcap_account_tbl { - /* 0x00 */ uchar key [ 32 ]; - /* 0x20 */ long acc_coff; /* chunk offset to account chunk */ - /* 0x28 */ ulong _pad28[5]; - /* 0x50 */ +struct __attribute__((packed)) fd_solcap_chunk_int_hdr { + /* 0x00 */ uint32_t block_type; /* SOLCAP_BLOCK_TYPE_CHUNK */ + /* 0x04 */ uint32_t slot; /* reference slot for the chunk */ + /* 0x08 */ uint64_t txn_idx; /* transaction index */ }; +typedef struct fd_solcap_chunk_int_hdr fd_solcap_chunk_int_hdr_t; -typedef struct fd_solcap_account_tbl fd_solcap_account_tbl_t; - -/* Hardcoded limits ***************************************************/ - -/* FD_SOLCAP_FHDR_SZ is the number of bytes occupied by the file header. - Immediately after the file header is the first chunk. */ - -#define FD_SOLCAP_FHDR_SZ (256UL) - -/* FD_SOLCAP_ACC_TBL_CNT is the number of entries that fit in the in- - memory buffer for the account table. - - N.b: to support epoch boundaries increase this number to 2097152 */ - -#define FD_SOLCAP_ACC_TBL_CNT (8192U) -/* FD_SOLCAP_FILE_META_FOOTPRINT is the max size of the FileMeta - Protobuf struct. */ - -#define FD_SOLCAP_FILE_META_FOOTPRINT (1024U) - -/* FD_SOLCAP_ACTB_META_FOOTPRINT is the max size of the - AccountChunkMeta Protobuf struct. */ - -#define FD_SOLCAP_ACTB_META_FOOTPRINT (128UL) - -/* FD_SOLCAP_ACCOUNT_META_FOOTPRINT is the max size of the AccountMeta - Protobuf struct. */ - -#define FD_SOLCAP_ACCOUNT_META_FOOTPRINT (1024UL) - -/* FD_SOLCAP_BANK_PREIMAGE_FOOTPRINT is the max size of the BankPreimage - Protobuf struct. */ - -#define FD_SOLCAP_BANK_PREIMAGE_FOOTPRINT (512UL) - -/* FD_SOLCAP_TRANSACTION_FOOTPRINT is the max size of the Transaction - Protobuf struct. */ +struct __attribute__((packed)) fd_solcap_chunk_ftr { + /* int_hdr + packet_len */ uint32_t block_len_redundant; /* length of the block */ +}; +typedef struct fd_solcap_chunk_ftr fd_solcap_chunk_ftr_t; + +/* + The following structures are the solcap messages that can be encoded. + They are used by the runtime to write messages to the shared buffer + and written to the file. +*/ +struct __attribute__((packed)) fd_solcap_account_update_hdr { + fd_pubkey_t key; + fd_solana_account_meta_t info; + ulong data_sz; +}; +typedef struct fd_solcap_account_update_hdr fd_solcap_account_update_hdr_t; + +struct __attribute__((packed))fd_solcap_bank_preimage { + fd_hash_t bank_hash; + fd_hash_t prev_bank_hash; + fd_hash_t accounts_lt_hash_checksum; + fd_hash_t poh_hash; + ulong signature_cnt; +}; +typedef struct fd_solcap_bank_preimage fd_solcap_bank_preimage_t; + +struct fd_solcap_buf_msg_stake_rewards_begin { + ulong payout_epoch; + ulong reward_epoch; + ulong inflation_lamports; + uint128 total_points; + }; + typedef struct fd_solcap_buf_msg_stake_rewards_begin fd_solcap_buf_msg_stake_rewards_begin_t; + +struct fd_solcap_buf_msg_stake_reward_event { + fd_pubkey_t stake_acc_addr; + fd_pubkey_t vote_acc_addr; + uint commission; + long vote_rewards; + long stake_rewards; + long new_credits_observed; + }; + typedef struct fd_solcap_buf_msg_stake_reward_event fd_solcap_buf_msg_stake_reward_event_t; + + +struct fd_solcap_buf_msg_vote_account_payout { + fd_pubkey_t vote_acc_addr; + ulong update_slot; + ulong lamports; + long lamports_delta; + }; + typedef struct fd_solcap_buf_msg_vote_account_payout fd_solcap_buf_msg_vote_account_payout_t; + + +struct fd_solcap_buf_msg_stake_account_payout { + fd_pubkey_t stake_acc_addr; + ulong update_slot; + ulong lamports; + long lamports_delta; + ulong credits_observed; + long credits_observed_delta; + ulong delegation_stake; + long delegation_stake_delta; + }; + typedef struct fd_solcap_buf_msg_stake_account_payout fd_solcap_buf_msg_stake_account_payout_t; -#define FD_SOLCAP_TRANSACTION_FOOTPRINT (128UL) #endif /* HEADER_fd_src_flamenco_capture_fd_solcap_proto_h */ diff --git a/src/flamenco/capture/fd_solcap_reader.c b/src/flamenco/capture/fd_solcap_reader.c deleted file mode 100644 index 7f19c425d37..00000000000 --- a/src/flamenco/capture/fd_solcap_reader.c +++ /dev/null @@ -1,191 +0,0 @@ -#include "fd_solcap_reader.h" -#include "fd_solcap_proto.h" -#include "../../ballet/nanopb/pb_decode.h" - -#if !FD_HAS_HOSTED -#error "fd_solcap_reader requires FD_HAS_HOSTED" -#endif - -#include -#include - -fd_solcap_chunk_iter_t * -fd_solcap_chunk_iter_new( fd_solcap_chunk_iter_t * iter, - void * _stream ) { - - FILE * stream = (FILE *)_stream; - - long pos = ftell( stream ); - if( FD_UNLIKELY( pos<0L ) ) { - iter->err = errno; - return iter; - } - - *iter = (fd_solcap_chunk_iter_t) { - .stream = stream, - .chunk = {0}, - .chunk_off = 0UL, - .chunk_end = (ulong)pos, - }; - return iter; -} - -long -fd_solcap_chunk_iter_next( fd_solcap_chunk_iter_t * iter ) { - - FILE * stream = (FILE *)iter->stream; - - long chunk_gaddr = (long)iter->chunk_end; - if( FD_UNLIKELY( 0!=fseek( iter->stream, chunk_gaddr, SEEK_SET ) ) ) { - FD_LOG_WARNING(( "fseek failed (%d-%s)", errno, strerror( errno ) )); - iter->err = errno; - return -1L; - } - iter->chunk_off = (ulong)chunk_gaddr; - - ulong n = fread( &iter->chunk, sizeof(fd_solcap_chunk_t), 1UL, stream ); - if( FD_UNLIKELY( n!=1UL ) ) { - int err = ferror( stream ); - if( FD_UNLIKELY( err ) ) { - FD_LOG_WARNING(( "fread failed (%d-%s)", errno, strerror( errno ) )); - iter->err = err; - } - iter->err = 0; - return -1L; - } - - if( FD_UNLIKELY( ( !fd_solcap_is_chunk_magic( iter->chunk.magic ) ) - | ( iter->chunk.total_sz < sizeof(fd_solcap_chunk_t) ) ) ) { - FD_LOG_WARNING(( "invalid chunk (offset=%#lx magic=0x%016lx total_sz=%lu)", - (ulong)chunk_gaddr, iter->chunk.magic, iter->chunk.total_sz )); - iter->err = EPROTO; - return -1L; - } - - iter->chunk_end = (ulong)chunk_gaddr + iter->chunk.total_sz; - - return chunk_gaddr; -} - -int -fd_solcap_chunk_iter_done( fd_solcap_chunk_iter_t const * iter ) { - return feof( (FILE *)iter->stream ) || fd_solcap_chunk_iter_err( iter ); -} - - -int -fd_solcap_read_bank_preimage( void * _file, - ulong chunk_goff, - fd_solcap_BankPreimage * preimage, - fd_solcap_chunk_t const * hdr ) { - - if( FD_UNLIKELY( hdr->magic != FD_SOLCAP_V1_BANK_MAGIC ) ) - return EPROTO; - - /* Seek to Protobuf */ - FILE * file = (FILE *)_file; - if( FD_UNLIKELY( 0!=fseek( file, (long)chunk_goff + hdr->meta_coff, SEEK_SET ) ) ) - return errno; - - /* Read into stack buffer */ - uchar buf[ FD_SOLCAP_BANK_PREIMAGE_FOOTPRINT ]; - if( FD_UNLIKELY( hdr->meta_sz > FD_SOLCAP_BANK_PREIMAGE_FOOTPRINT ) ) - return ENOMEM; - if( FD_UNLIKELY( hdr->meta_sz != fread( buf, 1UL, hdr->meta_sz, file ) ) ) - return ferror( file ); - - /* Decode */ - pb_istream_t stream = pb_istream_from_buffer( buf, hdr->meta_sz ); - if( FD_UNLIKELY( !pb_decode( &stream, fd_solcap_BankPreimage_fields, preimage ) ) ) { - FD_LOG_WARNING(( "pb_decode failed (%s)", PB_GET_ERROR(&stream) )); - return EPROTO; - } - - return 0; -} - -int -fd_solcap_find_account_table( void * _file, - fd_solcap_AccountTableMeta * meta, - ulong _chunk_goff ) { - - /* Read account table chunk header */ - long chunk_goff = (long)_chunk_goff; - fd_solcap_chunk_t hdr[1]; - FILE * file = (FILE *)_file; - if( FD_UNLIKELY( 0!=fseek( file, chunk_goff, SEEK_SET ) ) ) - return errno; - if( FD_UNLIKELY( 1UL != fread( hdr, sizeof(fd_solcap_chunk_t), 1UL, file ) ) ) - return ferror( file ); - if( FD_UNLIKELY( hdr->magic != FD_SOLCAP_V1_ACTB_MAGIC ) ) - return EPROTO; - - /* Seek to Protobuf */ - if( FD_UNLIKELY( 0!=fseek( file, chunk_goff + hdr->meta_coff, SEEK_SET ) ) ) - return errno; - - /* Read into stack buffer */ - uchar buf[ FD_SOLCAP_ACTB_META_FOOTPRINT ]; - if( FD_UNLIKELY( hdr->meta_sz > FD_SOLCAP_ACTB_META_FOOTPRINT ) ) - return ENOMEM; - if( FD_UNLIKELY( hdr->meta_sz != fread( buf, 1UL, hdr->meta_sz, file ) ) ) - return ferror( file ); - - /* Decode */ - pb_istream_t stream = pb_istream_from_buffer( buf, hdr->meta_sz ); - if( FD_UNLIKELY( !pb_decode( &stream, fd_solcap_AccountTableMeta_fields, meta ) ) ) { - FD_LOG_WARNING(( "pb_decode failed (%s)", PB_GET_ERROR(&stream) )); - return EPROTO; - } - - /* Seek to table */ - if( meta->account_table_coff ) { - if( FD_UNLIKELY( 0!=fseek( file, chunk_goff + (long)meta->account_table_coff, SEEK_SET ) ) ) - return errno; - } - - return 0; -} - -int -fd_solcap_find_account( void * _file, - fd_solcap_AccountMeta * meta, - ulong * opt_data_off, - fd_solcap_account_tbl_t const * rec, - ulong acc_tbl_goff ) { - - /* Read account chunk header */ - long chunk_goff = (long)acc_tbl_goff + rec->acc_coff; - fd_solcap_chunk_t hdr[1]; - FILE * file = (FILE *)_file; - if( FD_UNLIKELY( 0!=fseek( file, chunk_goff, SEEK_SET ) ) ) - return errno; - if( FD_UNLIKELY( 1UL != fread( hdr, sizeof(fd_solcap_chunk_t), 1UL, file ) ) ) - return ferror( file ); - if( FD_UNLIKELY( hdr->magic != FD_SOLCAP_V1_ACCT_MAGIC ) ) - return EPROTO; - - /* Seek to Protobuf */ - if( FD_UNLIKELY( 0!=fseek( file, chunk_goff + hdr->meta_coff, SEEK_SET ) ) ) - return errno; - - /* Read into stack buffer */ - uchar buf[ FD_SOLCAP_ACCOUNT_META_FOOTPRINT ]; - if( FD_UNLIKELY( hdr->meta_sz > FD_SOLCAP_ACCOUNT_META_FOOTPRINT ) ) - return ENOMEM; - if( FD_UNLIKELY( hdr->meta_sz != fread( buf, 1UL, hdr->meta_sz, file ) ) ) - return ferror( file ); - - /* Decode */ - pb_istream_t stream = pb_istream_from_buffer( buf, hdr->meta_sz ); - if( FD_UNLIKELY( !pb_decode( &stream, fd_solcap_AccountMeta_fields, meta ) ) ) { - FD_LOG_WARNING(( "pb_decode failed (%s)", PB_GET_ERROR(&stream) )); - return EPROTO; - } - - /* Seek to account data */ - if( fd_solcap_includes_account_data( meta ) && opt_data_off ) - *opt_data_off = (ulong)( chunk_goff + (long)meta->data_coff ); - - return 0; -} diff --git a/src/flamenco/capture/fd_solcap_reader.h b/src/flamenco/capture/fd_solcap_reader.h deleted file mode 100644 index f1aca37f338..00000000000 --- a/src/flamenco/capture/fd_solcap_reader.h +++ /dev/null @@ -1,147 +0,0 @@ -#ifndef HEADER_fd_src_flamenco_capture_fd_solcap_reader_h -#define HEADER_fd_src_flamenco_capture_fd_solcap_reader_h - -#include "fd_solcap.pb.h" -#include "fd_solcap_proto.h" - -#if FD_HAS_HOSTED - -/* fd_solcap_chunk_iter_t helps with iterating through the chunks of a - solcap file */ - -struct fd_solcap_chunk_iter { - void * stream; - fd_solcap_chunk_t chunk; - int err; - ulong chunk_off; /* Absolute file offset of current chunk */ - ulong chunk_end; /* Absolute file offset of next chunk */ -}; -typedef struct fd_solcap_chunk_iter fd_solcap_chunk_iter_t; - -FD_PROTOTYPES_BEGIN - -/* fd_solcap_chunk_iter_new initializes the given iter. stream is a - file handle (FILE * or platform equivalent). The stream cursor must - point to the first chunk (not the file header). To read the first - and subsequent chunks, use fd_solcap_chunk_iter_next. It is U.B. to - call any other reader methods than "next" after "new". */ - -fd_solcap_chunk_iter_t * -fd_solcap_chunk_iter_new( fd_solcap_chunk_iter_t * iter, - void * stream ); - -/* fd_solcap_chunk_iter_next reads the next chunk header. Safe to call - even if stream cursor was modified by user. On success, returns the - file offset pointing to the first byte of the chunk. The cursor of - iter->stream is undefined, so user should use fseek() with SEEK_SET - to find data of interest. Typically use as follows: - - long chunk_goff = fd_solcap_chunk_iter_next( iter ); - if( FD_UNLIKELY( chunk_goff<0L ) ) { ... } - fseek( iter->stream, chunk_goff + my_offset, SEEK_SET ); - - On failure, returns -1L. Reasons for failure are end-of-file, I/O - error, or parse error. errno-like code can be read via - fd_solcap_chunk_iter_err, which returns 0 on end-of-file. Reasons - for error (other than EOF) are written to warning log. */ - -long -fd_solcap_chunk_iter_next( fd_solcap_chunk_iter_t * iter ); - -/* fd_solcap_chunk_iter_err returns errno of last failure. Returns 0 - if last failure was EOF, and non-zero otherwise. Return value is - undefined if no failure occurred yet. */ - -static inline int -fd_solcap_chunk_iter_err( fd_solcap_chunk_iter_t const * iter ) { - return iter->err; -} - -/* fd_solcap_chunk_iter_done returns 0 if there might be more chunks. - Returns 1 if end-of-file was reached or read failure was encountered - at last chunk. */ - -int -fd_solcap_chunk_iter_done( fd_solcap_chunk_iter_t const * iter ); - -/* fd_solcap_chunk_iter_item returns pointer to last successful chunk - header read (using fd_solcap_chunk_iter_next()). Lifetime of pointer - is until next call to "next" or until lifetime of iter ends. If no - successful call to fd_solcap_chunk_iter_next was made yet, "chunk" - field of return value is zero. */ - -static inline fd_solcap_chunk_t const * -fd_solcap_chunk_iter_item( fd_solcap_chunk_iter_t const * iter ) { - return &iter->chunk; -} - -/* fd_solcap_chunk_iter_find iterates through chunks until a chunk with - the given magic is found. Returns absolute file offset of chunk if - chunk was found, and -1L if chunk was not found. */ - -static inline long -fd_solcap_chunk_iter_find( fd_solcap_chunk_iter_t * iter, - ulong magic ) { - for(;;) { - long chunk_gaddr = fd_solcap_chunk_iter_next( iter ); - if( FD_UNLIKELY( chunk_gaddr<0L ) ) - return -1L; - if( FD_UNLIKELY( fd_solcap_chunk_iter_done( iter ) ) ) - return -1L; - if( fd_solcap_chunk_iter_item( iter )->magic == magic ) - return chunk_gaddr; - } -} - -/* fd_solcap_read_bank_preimage reads and parses the bank preimage - metadata blob. chunk_goff is the file offset of the chunk containing - the bank preimage. hdr points to a copy of the corresponding chunk - header. */ - -int -fd_solcap_read_bank_preimage( void * stream, - ulong chunk_goff, - fd_solcap_BankPreimage * preimage, - fd_solcap_chunk_t const * hdr ); - -/* fd_solcap_find_account_table reads an account table meta and seeks - the given (FILE *) like stream to the first account table row. - acc_tbl_goff is the file offset of the chunk containing the account - table. On success, writes the account meta to *meta and returns 0. - On failure, returns errno-like error code. */ - -int -fd_solcap_find_account_table( void * _file, - fd_solcap_AccountTableMeta * meta, - ulong acc_tbl_goff ); - -/* fd_solcap_includes_account_data returns 1 if a capture account chunk - includes account data, and 0 otherwise. Reasons for missing account - data are that capture program deliberately excluded them, or that - account data size is zero. */ - -static inline int -fd_solcap_includes_account_data( fd_solcap_AccountMeta const * meta ) { - return (!!meta->data_coff) & (!!meta->data_sz); -} - -/* fd_solcap_find_account reads an account meta. If opt_data_off, sets - *opt_data_off to the file offset to account data. If account data - size is zero or the capture does not include account data, - *opt_data_off is undefined. (Use fd_solcap_includes_account_data to - check). Returns 0 on success or errno-like on failure. */ - -int -fd_solcap_find_account( void * _file, - fd_solcap_AccountMeta * meta, - ulong * opt_data_off, - fd_solcap_account_tbl_t const * rec, - ulong acc_tbl_goff ); - - -FD_PROTOTYPES_END - -#endif /* FD_HAS_HOSTED */ - -#endif /* HEADER_fd_src_flamenco_capture_fd_solcap_reader_h */ - diff --git a/src/flamenco/capture/fd_solcap_writer.c b/src/flamenco/capture/fd_solcap_writer.c index 18f9675b0f8..67f56716862 100644 --- a/src/flamenco/capture/fd_solcap_writer.c +++ b/src/flamenco/capture/fd_solcap_writer.c @@ -1,139 +1,10 @@ #include "fd_solcap_writer.h" -#include "fd_solcap.pb.h" #include "fd_solcap_proto.h" -#include "../../ballet/nanopb/pb_encode.h" -#include "../../ballet/blake3/fd_blake3.h" - -#if !FD_HAS_HOSTED -#error "fd_solcap_writer requires FD_HAS_HOSTED" -#endif +#include "../../discof/capture/fd_capture_ctx.h" #include #include - -/* Note on suffixes: - - goff: file offset (as returned by fseek) - foff: file offset from beginning of solcap stream - coff: file offset from beginning of current chunk */ - -/* fd_solcap_writer is the state of a capture writer. Currently, it - is only able to capture the bank hash pre-image and chagned accounts. - - The writer progresses with each API call to the writer functions. - - Typically, the order is the following: - - - fd_solcap_writer_set_slot advances to the next slot. If there was - a previous slot in progress but not finished, discards buffers. - - fd_solcap_write_account writes an account chunk and buffers an - entry for the accounts table. - - fd_solcap_write_bank_preimage flushes the buffered accounts table - and writes the preimage chunk. Slot is finished and ready for - next iteration. */ - -struct fd_solcap_writer { - FILE * file; - - /* Number of bytes between start of file and start of stream. - Usually 0. Non-zero if the bank capture is contained in some - other file format. */ - ulong stream_goff; - - /* In-flight write of accounts table. - account_idx==0UL implies no chunk header has been written yet. - account_idx>=0UL implies AccountTable chunk write is pending. - account_idx>=FD_SOLCAP_ACC_TBL_CNT implies that AccountTable is - unable to fit records. Table record will be skipped. */ - - ulong slot; - fd_solcap_account_tbl_t accounts[ FD_SOLCAP_ACC_TBL_CNT ]; - uint account_idx; - ulong account_table_goff; - - ulong first_slot; -}; - -/* FTELL_BAIL calls ftell on the given file, and bails the current - function with return code EIO if it fails. */ - -#define FTELL_BAIL( file ) \ - (__extension__({ \ - long n = ftell( (file) ); \ - if( FD_UNLIKELY( n<0L ) ) { \ - FD_LOG_WARNING(( "ftell failed (%d-%s)", \ - errno, strerror( errno ) )); \ - return EIO; \ - } \ - (ulong)n; \ - })) - -/* FSEEK_BAIL calls fseek on the given file, and bails the current - function with return code EIO if it fails. */ - -#define FSEEK_BAIL( file, off, whence ) \ - (__extension__({ \ - int err = fseek( (file), (off), (whence) ); \ - if( FD_UNLIKELY( err<0L ) ) { \ - FD_LOG_WARNING(( "fseek failed (%d-%s)", \ - errno, strerror( errno ) )); \ - return EIO; \ - } \ - 0; \ - })) - -/* FWRITE_BAIL calls fwrite on the given file, and bails the current - function with return code EIO if it fails. */ - -#define FWRITE_BAIL( ptr, sz, cnt, file ) \ - (__extension__({ \ - ulong _cnt = (cnt); \ - ulong n = fwrite( (ptr), (sz), _cnt, (file) ); \ - if( FD_UNLIKELY( n!=_cnt ) ) { \ - FD_LOG_WARNING(( "fwrite failed (%d-%s)", \ - errno, strerror( errno ) )); \ - return EIO; \ - } \ - 0; \ - })) - -/* _skip_file writes zeros to the file */ - -static int -_skip_file( FILE * file, - ulong skip ) { - if (skip == 0) return 0; - - uchar zero[ skip ]; - fd_memset( zero, 0, skip ); - - FWRITE_BAIL( zero, 1UL, skip, file ); - return 0; -} - -#define FSKIP_BAIL( file, skip ) \ - do { \ - int err = _skip_file( (file), (skip) ); \ - if( FD_UNLIKELY( err!=0 ) ) return err; \ - } while(0) - -/* _align_file pads file with zero up to meet given align requirement. - align is a positive power of two. */ - -static int -_align_file( FILE * file, - ulong align ) { - ulong pos = FTELL_BAIL( file ); - ulong skip = fd_ulong_align_up( pos, align ) - pos; - return _skip_file( file, skip ); -} - -#define FALIGN_BAIL( file, align ) \ - do { \ - int err = _align_file( (file), (align) ); \ - if( FD_UNLIKELY( err!=0 ) ) return err; \ - } while(0) - +#include ulong fd_solcap_writer_align( void ) { @@ -147,29 +18,18 @@ fd_solcap_writer_footprint( void ) { fd_solcap_writer_t * fd_solcap_writer_new( void * mem ) { - - if( FD_UNLIKELY( !mem ) ) { - FD_LOG_WARNING(( "NULL mem" )); - return NULL; - } - - memset( mem, 0, sizeof(fd_solcap_writer_t) ); - return (fd_solcap_writer_t *)mem; + return mem; } void * fd_solcap_writer_delete( fd_solcap_writer_t * writer ) { - - if( FD_UNLIKELY( !writer ) ) return NULL; - - writer->file = NULL; - return writer; + (void)writer; + return NULL; } - fd_solcap_writer_t * fd_solcap_writer_init( fd_solcap_writer_t * writer, - void * file ) { + FILE * file ) { if( FD_UNLIKELY( !writer ) ) { FD_LOG_WARNING(( "NULL writer" )); @@ -180,516 +40,153 @@ fd_solcap_writer_init( fd_solcap_writer_t * writer, return NULL; } - /* Leave space for file headers */ + writer->file = file; long pos = ftell( file ); - if( FD_UNLIKELY( pos<0L ) ) { - FD_LOG_WARNING(( "ftell failed (%d-%s)", errno, strerror( errno ) )); - return NULL; - } - ulong stream_goff = (ulong)pos; - - uchar zero[ FD_SOLCAP_FHDR_SZ ] = {0}; - ulong n = fwrite( zero, FD_SOLCAP_FHDR_SZ, 1UL, file ); - if( FD_UNLIKELY( n!=1UL ) ) { - FD_LOG_WARNING(( "fwrite failed (%d-%s)", errno, strerror( errno ) )); - return NULL; - } - - /* Init writer */ - writer->file = file; - writer->stream_goff = stream_goff; - - return writer; -} - -/* fd_solcap_writer_flush writes the file header. */ - -fd_solcap_writer_t * -fd_solcap_writer_flush( fd_solcap_writer_t * writer ) { - - if( FD_LIKELY( !writer ) ) return NULL; - - /* Flush stream */ - fflush( writer->file ); - - /* Remember stream cursor */ - - long cursor = ftell( writer->file ); - if( FD_UNLIKELY( cursor<0L ) ) { + if ( FD_UNLIKELY( pos<0L ) ) { FD_LOG_WARNING(( "ftell failed (%d-%s)", errno, strerror( errno ) )); return NULL; } + writer->stream_goff = (ulong)pos; - /* Construct file header */ - - fd_solcap_FileMeta fmeta = { - .first_slot = writer->first_slot, - .slot_cnt = (ulong)fd_long_max( 0L, (long)writer->slot - (long)writer->first_slot ), - .main_block_magic = FD_SOLCAP_V1_BANK_MAGIC, + fd_solcap_file_hdr_t file_hdr = { + .block_type = FD_SOLCAP_V2_FILE_MAGIC, /* pcap section header magic */ + .block_len = sizeof(fd_solcap_file_hdr_t), + .byte_order_magic = FD_SOLCAP_V2_BYTE_ORDER_MAGIC, + .major_version = 0x00000001, + .minor_version = 0x00000000, + .section_len = -1UL, + .block_len_redundant = sizeof(fd_solcap_file_hdr_t) }; - - uchar meta[ 128UL ]; - pb_ostream_t stream = pb_ostream_from_buffer( meta, sizeof(meta) ); - if( FD_UNLIKELY( !pb_encode( &stream, fd_solcap_FileMeta_fields, &fmeta ) ) ) { - FD_LOG_WARNING(( "pb_encode failed (%s)", PB_GET_ERROR(&stream) )); - return NULL; - } - - fd_solcap_fhdr_t fhdr = { - .magic = FD_SOLCAP_V1_FILE_MAGIC, - .chunk0_foff = FD_SOLCAP_FHDR_SZ, - .meta_sz = (uint)stream.bytes_written, + fwrite( &file_hdr, sizeof(fd_solcap_file_hdr_t), 1UL, file ); + + fd_solcap_chunk_idb_hdr_t idb_hdr = { + .block_type = SOLCAP_PCAPNG_BLOCK_TYPE_IDB, + .block_len = sizeof(fd_solcap_chunk_idb_hdr_t), + .link_type = SOLCAP_IDB_HDR_LINK_TYPE, + .reserved = 0, + .snap_len = SOLCAP_IDB_HDR_SNAP_LEN, + .block_len_redundant = sizeof(fd_solcap_chunk_idb_hdr_t) }; - - /* Write out file headers */ - - if( FD_UNLIKELY( 0!=fseek( writer->file, (long)writer->stream_goff, SEEK_SET ) ) ) { - FD_LOG_WARNING(( "fseek failed (%d-%s)", errno, strerror( errno ) )); - return NULL; - } - - if( FD_UNLIKELY( 1UL!=fwrite( &fhdr, sizeof(fd_solcap_fhdr_t), 1UL, writer->file ) ) ) { - FD_LOG_WARNING(( "fwrite file header failed (%d-%s)", errno, strerror( errno ) )); - return NULL; - } - - if( FD_UNLIKELY( stream.bytes_written != fwrite( meta, 1UL, stream.bytes_written, writer->file ) ) ) { - FD_LOG_WARNING(( "fwrite file meta failed (%d-%s)", ferror( writer->file ), strerror( ferror( writer->file ) ) )); - return NULL; - } - - /* Restore stream cursor */ - - if( FD_UNLIKELY( 0!=fseek( writer->file, cursor, SEEK_SET ) ) ) { - FD_LOG_WARNING(( "fseek failed (%d-%s)", errno, strerror( errno ) )); - return NULL; - } + fwrite( &idb_hdr, sizeof(fd_solcap_chunk_idb_hdr_t), 1UL, file ); return writer; } -/* fd_solcap_flush_account_table writes the buffered account table out - to the stream. */ - -static int -fd_solcap_flush_account_table( fd_solcap_writer_t * writer ) { - - /* Only flush if at least one account present. */ - - if( writer->account_idx == 0UL ) return 0; - - /* Skip if table was overflowed. */ - - /* FIXME: This breaks account recording for epoch boundaries and needs to be fixed */ - if( writer->account_idx >= FD_SOLCAP_ACC_TBL_CNT ) { - FD_LOG_WARNING(( "too many records in solcap accounts table - try increasing FD_SOLCAP_ACC_TBL_CNT" )); - writer->account_idx = 0UL; - return 0; +FILE* +fd_solcap_file_verify( fd_solcap_writer_t * writer ) { + FILE * file = writer->file; + if ( FD_UNLIKELY( !file ) ) { + FD_LOG_WARNING(( "NULL file" )); + return NULL; } + return file; +} - /* Leave space for header */ - - ulong chunk_goff = FTELL_BAIL( writer->file ); - FSKIP_BAIL( writer->file, sizeof(fd_solcap_chunk_t) ); - - /* Translate account table to chunk-relative addressing */ - - for( uint i=0U; iaccount_idx; i++ ) - writer->accounts[i].acc_coff -= (long)chunk_goff; +uint32_t +fd_solcap_write_account_hdr( fd_solcap_writer_t * writer, + fd_solcap_buf_msg_t * msg_hdr, + fd_solcap_account_update_hdr_t * account_update ) { + FILE * file = fd_solcap_file_verify( writer ); - /* Write account table (at beginning of chunk) */ + ulong data_sz = account_update->data_sz; - ulong account_table_coff = sizeof(fd_solcap_chunk_t); - ulong account_table_cnt = writer->account_idx; + uint32_t packet_len = (uint32_t)(sizeof(fd_solcap_chunk_int_hdr_t) + + sizeof(fd_solcap_account_update_hdr_t) + + data_sz); - FWRITE_BAIL( writer->accounts, - sizeof(fd_solcap_account_tbl_t), - account_table_cnt, - writer->file ); + uint32_t unaligned_block_len = (uint32_t)(sizeof(fd_solcap_chunk_epb_hdr_t) + + packet_len + + sizeof(fd_solcap_chunk_ftr_t)); - /* Serialize account chunk metadata */ + uint32_t block_len = (uint32_t)((unaligned_block_len + 3UL) & ~3UL); - ulong meta_goff = FTELL_BAIL( writer->file ); - fd_solcap_AccountTableMeta meta = { - .slot = writer->slot, - .account_table_coff = account_table_coff, - .account_table_cnt = account_table_cnt + fd_solcap_chunk_epb_hdr_t epb_hdr = { + .block_type = SOLCAP_PCAPNG_BLOCK_TYPE_EPB, + .block_len = block_len, + .interface_id = 0, + .timestamp_upper = 0, + .timestamp_lower = 0, + .captured_packet_len = packet_len, + .original_packet_len = packet_len }; + fwrite( &epb_hdr, sizeof(fd_solcap_chunk_epb_hdr_t), 1UL, file ); - uchar encoded[ FD_SOLCAP_ACTB_META_FOOTPRINT ]; - pb_ostream_t stream = pb_ostream_from_buffer( encoded, sizeof(encoded) ); - if( FD_UNLIKELY( !pb_encode( &stream, fd_solcap_AccountTableMeta_fields, &meta ) ) ) { - FD_LOG_WARNING(( "pb_encode failed (%s)", PB_GET_ERROR(&stream) )); - return EPROTO; - } - - FWRITE_BAIL( encoded, 1UL, stream.bytes_written, writer->file ); - FALIGN_BAIL( writer->file, 8UL ); - - /* Serialize chunk header */ - - ulong chunk_end_goff = FTELL_BAIL( writer->file ); - - fd_solcap_chunk_t chunk = { - .magic = FD_SOLCAP_V1_ACTB_MAGIC, - .meta_coff = (uint)( meta_goff - chunk_goff ), - .meta_sz = (uint)stream.bytes_written, - .total_sz = chunk_end_goff - chunk_goff + fd_solcap_chunk_int_hdr_t int_hdr = { + .block_type = SOLCAP_WRITE_ACCOUNT_HDR, + .slot = (uint32_t)msg_hdr->slot, + .txn_idx = msg_hdr->txn_idx }; + fwrite( &int_hdr, sizeof(fd_solcap_chunk_int_hdr_t), 1UL, file ); - /* Write out chunk */ - - FSEEK_BAIL( writer->file, (long)chunk_goff, SEEK_SET ); - FWRITE_BAIL( &chunk, sizeof(fd_solcap_chunk_t), 1UL, writer->file ); - - /* Restore stream cursor */ - - FSEEK_BAIL( writer->file, (long)chunk_end_goff, SEEK_SET ); + fwrite( account_update, sizeof(fd_solcap_account_update_hdr_t), 1UL, file ); - /* Wind up for next iteration */ - - writer->account_table_goff = chunk_goff; - writer->account_idx = 0U; - - return 0; + return block_len; } -int -fd_solcap_write_account( fd_solcap_writer_t * writer, - void const * key, - fd_solana_account_meta_t const * meta, - void const * data, - ulong data_sz ) { - - if( FD_LIKELY( !writer ) ) return 0; - - fd_solcap_account_tbl_t rec[1]; - memset( rec, 0, sizeof(fd_solcap_account_tbl_t) ); - memcpy( rec->key, key, 32UL ); - - fd_solcap_AccountMeta meta_pb[1] = {{ - .lamports = meta->lamports, - .executable = meta->executable, - .data_sz = data_sz, - }}; - memcpy( meta_pb->owner, meta->owner, 32UL ); - - return fd_solcap_write_account2( writer, rec, meta_pb, data, data_sz ); -} - -int -fd_solcap_write_account2( fd_solcap_writer_t * writer, - fd_solcap_account_tbl_t const * tbl, - fd_solcap_AccountMeta * meta_pb, - void const * data, - ulong data_sz ) { - - if( FD_LIKELY( !writer ) ) return 0; - - /* Locate chunk */ - - ulong chunk_goff = FTELL_BAIL( writer->file ); - - /* Write data */ - - ulong data_coff = sizeof(fd_solcap_chunk_t); - FSKIP_BAIL ( writer->file, data_coff ); - FWRITE_BAIL( data, 1UL, data_sz, writer->file ); - FALIGN_BAIL( writer->file, 8UL ); - - /* Serialize account meta */ - - ulong meta_goff = FTELL_BAIL( writer->file ); - - meta_pb->slot = writer->slot; - meta_pb->data_coff = (long)data_coff; - meta_pb->data_sz = data_sz; - - uchar meta_pb_enc[ FD_SOLCAP_ACCOUNT_META_FOOTPRINT ]; - pb_ostream_t stream = pb_ostream_from_buffer( meta_pb_enc, sizeof(meta_pb_enc) ); - FD_TEST( pb_encode( &stream, fd_solcap_AccountMeta_fields, meta_pb ) ); - - /* Write account meta */ - - ulong meta_coff = meta_goff - chunk_goff; - FWRITE_BAIL( meta_pb_enc, 1UL, stream.bytes_written, writer->file ); - FALIGN_BAIL( writer->file, 8UL ); - - /* Remember account table entry */ - - if( writer->account_idx < FD_SOLCAP_ACC_TBL_CNT ) { - fd_solcap_account_tbl_t * account = &writer->accounts[ writer->account_idx ]; - *account = *tbl; - - /* Since we don't yet know the final position of the account table, - we temporarily store a global offset. This will later get - converted into a chunk offset. */ - account->acc_coff = (long)chunk_goff; - } - - /* Serialize chunk header */ - - ulong chunk_end_goff = FTELL_BAIL( writer->file ); - - fd_solcap_chunk_t chunk = { - .magic = FD_SOLCAP_V1_ACCT_MAGIC, - .meta_coff = (uint)meta_coff, - .meta_sz = (uint)stream.bytes_written, - .total_sz = chunk_end_goff - chunk_goff - }; - - /* Write out chunk */ - - FSEEK_BAIL( writer->file, (long)chunk_goff, SEEK_SET ); - FWRITE_BAIL( &chunk, sizeof(fd_solcap_chunk_t), 1UL, writer->file ); - - /* Restore stream cursor */ - - FSEEK_BAIL( writer->file, (long)chunk_end_goff, SEEK_SET ); - - /* Wind up for next iteration */ - - writer->account_idx += 1U; - +uint32_t +fd_solcap_write_account_data( fd_solcap_writer_t * writer, + void const * data, + ulong data_sz ) { + FILE * file = fd_solcap_file_verify( writer ); + fwrite( data, data_sz, 1UL, file ); return 0; } -void -fd_solcap_writer_set_slot( fd_solcap_writer_t * writer, - ulong slot ) { - if( FD_LIKELY( !writer ) ) return; - - /* Discard account table buffer */ - writer->account_table_goff = 0UL; - writer->account_idx = 0UL; - writer->slot = slot; -} - -int +uint32_t fd_solcap_write_bank_preimage( fd_solcap_writer_t * writer, - void const * bank_hash, - void const * prev_bank_hash, - void const * account_delta_hash, - void const * accounts_lt_hash_checksum, - void const * poh_hash, - ulong signature_cnt ) { - - if( FD_LIKELY( !writer ) ) return 0; - - fd_solcap_BankPreimage preimage_pb[1] = {{0}}; - preimage_pb->signature_cnt = signature_cnt; - preimage_pb->account_cnt = writer->account_idx; - memcpy( preimage_pb->bank_hash, bank_hash, 32UL ); - memcpy( preimage_pb->prev_bank_hash, prev_bank_hash, 32UL ); - if (NULL != account_delta_hash ) - memcpy( preimage_pb->account_delta_hash, account_delta_hash, 32UL ); - else - fd_memset( preimage_pb->account_delta_hash, 0, 32UL ); - if( NULL != accounts_lt_hash_checksum ) - memcpy( preimage_pb->accounts_lt_hash_checksum, accounts_lt_hash_checksum, 32UL ); - else - fd_memset(preimage_pb->accounts_lt_hash_checksum, 0, 32UL ); - memcpy( preimage_pb->poh_hash, poh_hash, 32UL ); - - return fd_solcap_write_bank_preimage2( writer, preimage_pb ); -} - - -int -fd_solcap_write_bank_preimage2( fd_solcap_writer_t * writer, - fd_solcap_BankPreimage * preimage_pb ) { + fd_solcap_buf_msg_t * msg_hdr, + fd_solcap_bank_preimage_t * bank_preimage ) { + FILE * file = fd_solcap_file_verify( writer ); - if( FD_LIKELY( !writer ) ) return 0; + uint32_t packet_len = (uint32_t)(sizeof(fd_solcap_chunk_int_hdr_t) + + sizeof(fd_solcap_bank_preimage_t)); - int err = fd_solcap_flush_account_table( writer ); - if( FD_UNLIKELY( err!=0 ) ) return err; + uint32_t unaligned_block_len = (uint32_t)(sizeof(fd_solcap_chunk_epb_hdr_t) + + packet_len + + sizeof(fd_solcap_chunk_ftr_t)); - /* Leave space for header */ + uint32_t block_len = (uint32_t)((unaligned_block_len + 3UL) & ~3UL); - ulong chunk_goff = FTELL_BAIL( writer->file ); - FSKIP_BAIL( writer->file, sizeof(fd_solcap_chunk_t) ); + fd_solcap_chunk_epb_hdr_t epb_hdr = { + .block_type = SOLCAP_PCAPNG_BLOCK_TYPE_EPB, + .block_len = block_len, + .interface_id = 0, + .timestamp_upper = 0, + .timestamp_lower = 0, + .captured_packet_len = packet_len, + .original_packet_len = packet_len + }; + fwrite( &epb_hdr, sizeof(fd_solcap_chunk_epb_hdr_t), 1UL, file ); - /* Fixup predefined entries */ + fd_solcap_chunk_int_hdr_t int_hdr = { + .block_type = SOLCAP_WRITE_BANK_PREIMAGE, + .slot = (uint32_t)msg_hdr->slot, + .txn_idx = msg_hdr->txn_idx + }; + fwrite( &int_hdr, sizeof(fd_solcap_chunk_int_hdr_t), 1UL, file ); - preimage_pb->slot = writer->slot; - if( writer->account_table_goff ) { - preimage_pb->account_cnt = writer->account_idx; - preimage_pb->account_table_coff = (long)writer->account_table_goff - (long)chunk_goff; - } - - /* Serialize bank preimage */ - - uchar preimage_pb_enc[ FD_SOLCAP_BANK_PREIMAGE_FOOTPRINT ] = {0}; - pb_ostream_t stream = pb_ostream_from_buffer( preimage_pb_enc, sizeof(preimage_pb_enc) ); - FD_TEST( pb_encode( &stream, fd_solcap_BankPreimage_fields, preimage_pb ) ); - ulong meta_sz = stream.bytes_written; - - FWRITE_BAIL( preimage_pb_enc, 1UL, meta_sz, writer->file ); - FALIGN_BAIL( writer->file, 8UL ); - ulong chunk_end_goff = FTELL_BAIL( writer->file ); - - /* Serialize chunk header */ - - fd_solcap_chunk_t chunk = { - .magic = FD_SOLCAP_V1_BANK_MAGIC, - .meta_coff = (uint)sizeof(fd_solcap_chunk_t), - .meta_sz = (uint)meta_sz, - .total_sz = chunk_end_goff - chunk_goff - }; - - /* Write out chunk */ - - FSEEK_BAIL( writer->file, (long)chunk_goff, SEEK_SET ); - FWRITE_BAIL( &chunk, sizeof(fd_solcap_chunk_t), 1UL, writer->file ); - - /* Restore stream cursor */ + fwrite( bank_preimage, sizeof(fd_solcap_bank_preimage_t), 1UL, file ); - FSEEK_BAIL( writer->file, (long)chunk_end_goff, SEEK_SET ); - - return 0; + return block_len; } -int fd_solcap_write_transaction2( fd_solcap_writer_t * writer, - fd_solcap_Transaction * txn ) { - - if( FD_LIKELY( !writer ) ) return 0; - - /* Locate chunk */ - ulong chunk_goff = FTELL_BAIL( writer->file ); - FSKIP_BAIL( writer->file, sizeof(fd_solcap_chunk_t) ); - - /* Serialize and write transaction */ - uchar txn_pb_enc[ FD_SOLCAP_TRANSACTION_FOOTPRINT ]; - pb_ostream_t stream = pb_ostream_from_buffer( txn_pb_enc, sizeof(txn_pb_enc) ); - FD_TEST( pb_encode( &stream, fd_solcap_Transaction_fields, txn ) ); - - FWRITE_BAIL( txn_pb_enc, 1UL, stream.bytes_written, writer->file ); - FALIGN_BAIL( writer->file, 8UL ); - ulong chunk_end_goff = FTELL_BAIL( writer->file ); - - /* Serialize chunk header */ - fd_solcap_chunk_t chunk = { - .magic = FD_SOLCAP_V1_TRXN_MAGIC, - .meta_coff = (uint)sizeof(fd_solcap_chunk_t), - .meta_sz = (uint)stream.bytes_written, - .total_sz = chunk_end_goff - chunk_goff - }; - - /* Write out chunk */ - FSEEK_BAIL( writer->file, (long)chunk_goff, SEEK_SET ); - FWRITE_BAIL( &chunk, sizeof(fd_solcap_chunk_t), 1UL, writer->file ); +uint32_t +fd_solcap_write_ftr( fd_solcap_writer_t * writer, + uint32_t block_len_redundant ) { + FILE * file = fd_solcap_file_verify( writer ); + long current_pos = ftell( file ); + uint32_t padding_needed = (-(current_pos) & 3); - /* Restore stream cursor */ - - FSEEK_BAIL( writer->file, (long)chunk_end_goff, SEEK_SET ); - - return 0; -} - -int -fd_solcap_writer_stake_rewards_begin( - fd_solcap_writer_t * writer, - ulong payout_epoch, - ulong reward_epoch, - ulong inflation_lamports, - uint128 total_points -) { - fd_solcap_StakeRewardEpoch epoch_pb = { - .payout_epoch = payout_epoch, - .reward_epoch = reward_epoch, - .inflation_lamports = inflation_lamports, - }; - FD_STORE( uint128, epoch_pb.points, total_points ); - return fd_solcap_write_protobuf( writer, &epoch_pb, fd_solcap_StakeRewardEpoch_fields, FD_SOLCAP_V1_REWARD_BEGIN_MAGIC ); -} - -int -fd_solcap_write_stake_reward_event( - fd_solcap_writer_t * writer, - fd_pubkey_t const * stake_acc_addr, - fd_pubkey_t const * vote_acc_addr, - uint commission, - long vote_rewards, - long stake_rewards, - long new_credits_observed -) { - fd_solcap_StakeRewardEvent event = { - .commission = commission, - .vote_rewards = vote_rewards, - .stake_rewards = stake_rewards, - .new_credits_observed = new_credits_observed - }; - memcpy( event.stake_account_address, stake_acc_addr, 32UL ); - memcpy( event.vote_account_address, vote_acc_addr, 32UL ); - return fd_solcap_write_protobuf( writer, &event, fd_solcap_StakeRewardEvent_fields, FD_SOLCAP_V1_REWARD_CALC_MAGIC ); -} - -int -fd_solcap_write_vote_account_payout( - fd_solcap_writer_t * writer, - fd_pubkey_t const * vote_acc_addr, - ulong update_slot, - ulong lamports, - long lamports_delta -) { - fd_solcap_VoteAccountPayout payout = { - .update_slot = update_slot, - .lamports = lamports, - .lamports_delta = lamports_delta - }; - memcpy( payout.address, vote_acc_addr, 32UL ); - return fd_solcap_write_protobuf( writer, &payout, fd_solcap_VoteAccountPayout_fields, FD_SOLCAP_V1_REWARD_VOTE_MAGIC ); -} - -int -fd_solcap_write_stake_account_payout( - fd_solcap_writer_t * writer, - fd_pubkey_t const * stake_acc_addr, - ulong update_slot, - ulong lamports, - long lamports_delta, - ulong credits_observed, - long credits_observed_delta, - ulong delegation_stake, - long delegation_stake_delta -) { - fd_solcap_StakeAccountPayout payout = { - .update_slot = update_slot, - .lamports = lamports, - .lamports_delta = lamports_delta, - .credits_observed = credits_observed, - .credits_observed_delta = credits_observed_delta, - .delegation_stake = delegation_stake, - .delegation_stake_delta = delegation_stake_delta - }; - memcpy( payout.address, stake_acc_addr, 32UL ); - return fd_solcap_write_protobuf( writer, &payout, fd_solcap_StakeAccountPayout_fields, FD_SOLCAP_V1_REWARD_STAKE_MAGIC ); -} - -int -fd_solcap_write_protobuf( fd_solcap_writer_t * writer, - void const * msg, - struct pb_msgdesc_s const * desc, - ulong magic ) { - if( FD_UNLIKELY( !writer ) ) return 0; - - uchar buf[ 1UL<<20 ]; - pb_ostream_t stream = pb_ostream_from_buffer( buf, sizeof(buf) ); - if( FD_UNLIKELY( !pb_encode( &stream, desc, msg ) ) ) { - FD_LOG_WARNING(( "pb_encode failed (%s)", PB_GET_ERROR(&stream) )); - return EPROTO; + if (padding_needed > 0) { + static const char zeros[4] = {0}; + fwrite(zeros, 1, padding_needed, file); } - fd_solcap_chunk_t chunk = { - .magic = magic, - .meta_coff = (uint)sizeof(fd_solcap_chunk_t), - .meta_sz = (uint)stream.bytes_written, - .total_sz = stream.bytes_written + sizeof(fd_solcap_chunk_t) + fd_solcap_chunk_ftr_t ftr = { + .block_len_redundant = block_len_redundant }; - - FWRITE_BAIL( &chunk, sizeof(fd_solcap_chunk_t), 1UL, writer->file ); - FWRITE_BAIL( buf, 1UL, stream.bytes_written, writer->file ); + fwrite( &ftr, sizeof(fd_solcap_chunk_ftr_t), 1UL, file ); return 0; } diff --git a/src/flamenco/capture/fd_solcap_writer.h b/src/flamenco/capture/fd_solcap_writer.h index 312e133bbb9..c6c66e5c80d 100644 --- a/src/flamenco/capture/fd_solcap_writer.h +++ b/src/flamenco/capture/fd_solcap_writer.h @@ -1,25 +1,32 @@ #ifndef HEADER_fd_src_flamenco_capture_fd_solcap_writer_h #define HEADER_fd_src_flamenco_capture_fd_solcap_writer_h +#include #include "fd_solcap_proto.h" -#include "fd_solcap.pb.h" -#include "../types/fd_types.h" -/* fd_solcap_writer_t is an opaque handle to a capture writer object. - Currently, it implements writing SOLCAP_V1_BANK files. See below - on how to create and use this class. */ +struct fd_solcap_buf_msg; +typedef struct fd_solcap_buf_msg fd_solcap_buf_msg_t; -struct fd_solcap_writer; -typedef struct fd_solcap_writer fd_solcap_writer_t; + +/* fd_solcap_writer_t is a writer utility for solcap files. + + Each soclap write function is responsible for encoding and writing + out a specific type of chunk. They provide both a header, which + contains information about type of chunk, size, and slot number, + and the chunk data. + + Note: The functionality is limited to the writing of solcap v2 files + Nishk (TODO): Write docs for solcap writer +*/ FD_PROTOTYPES_BEGIN -/* fd_solcap_writer_t object lifecycle API ****************************/ +#define SOLCAP_WRITE_ACCOUNT_DATA_MTU (131072UL) -/* fd_solcap_writer_{align,footprint} return align and footprint - requirements for the memory region backing the fd_solcap_writer_t - object. fd_solcap_writer_align returns a power of two. - fd_solcap_writer_footprint returns a non-zero byte count. */ +typedef struct fd_solcap_writer { + FILE *file; + ulong stream_goff; +} fd_solcap_writer_t; ulong fd_solcap_writer_align( void ); @@ -27,166 +34,45 @@ fd_solcap_writer_align( void ); ulong fd_solcap_writer_footprint( void ); -/* fd_solcap_writer_new creates a new fd_solcap_writer_t object using - the given memory region. mem points to a memory region with matching - align and footprint. Returns a pointer to the writer object within - memory region, assigns ownership of mem to the object, and assigs - ownership of the object to the caller. Returned pointer should not - assumed to be a simple cast of mem. On failure, logs error and - returns NULL. Reasons for failure include mem==NULL or invalid - alignment. */ - fd_solcap_writer_t * fd_solcap_writer_new( void * mem ); -/* fd_solcap_writer_delete destroys the given fd_solcap_writer_t object - and transfers ownership of the backing memory region to the caller. - If mem is NULL, behaves like a noop and returns NULL. */ - void * -fd_solcap_writer_delete( fd_solcap_writer_t * mem ); - -/* fd_solcap_writer_init initializes writer to write to a new stream. - stream is (FILE *) or the platform-specific equivalent. The stream - offset should be positioned to where the capture header is expected - (usually file offset 0). stream access mode should be write and - should support random seeking, read is currently not required. - Returns writer, transfers ownership of stream to writer, and - writes the capture file header to stream on success. On failure, - logs reason and returns NULL and returns stream to the user. - Reasons for failure are stream I/O error. On failure, writer is left - in uninitialized state (safe to retry init), and stream is left in - unspecified state (caller should discard any writes made to stream). s*/ - -fd_solcap_writer_t * -fd_solcap_writer_init( fd_solcap_writer_t * writer, - void * stream ); - -/* fd_solcap_writer_flush finishes any outstanding writes and yields - ownership of the stream handle back to the caller of init. Always returns - writer for convenience. If an error occurs, writes reason to log. */ +fd_solcap_writer_delete( fd_solcap_writer_t * writer ); fd_solcap_writer_t * -fd_solcap_writer_flush( fd_solcap_writer_t * writer ); - -/* fd_solcap_writer_t user API ***************************************** - - Before calling below functions, the object must have been initialized - successfully. Currently, only supports SOLCAP_V1_BANK files. For - every slot, order of operations should be as follows: - - set_slot - - write_account (repeatedly) - - write_bank_preimage - - write_bank_hash */ - -/* fd_solcap_writer_set_slot starts a new slot record. Finishes any - previous slot record. slot numbers must be monotonically increasing. */ - -void -fd_solcap_writer_set_slot( fd_solcap_writer_t * writer, - ulong slot ); - -/* fd_solcap_write_account appends a copy of the given account (key, - meta, data) tuple to the stream. Must only be called for accounts - that are part of the current slot's account delta hash. Order of - accounts is arbitrary. */ +fd_solcap_writer_init( fd_solcap_writer_t * writer, + FILE * file ); int fd_solcap_write_account( fd_solcap_writer_t * writer, + ulong txn_idx, + ulong slot, void const * key, fd_solana_account_meta_t const * meta, void const * data, ulong data_sz ); -int -fd_solcap_write_account2( fd_solcap_writer_t * writer, - fd_solcap_account_tbl_t const * tbl, - fd_solcap_AccountMeta * meta_pb, - void const * data, - ulong data_sz ); - -/* fd_solcap_write_bank_preimage sets additional fields that are part - of the current slot's bank hash preimage. prev_bank_hash is the - bank hash of the previous block. account_delta_hash is the Merkle - root of the changed accounts (these accounts should match the ones - passed to fd_solcap_write_account). poh_hash is the PoH hash of the - current block. TODO what is signature_cnt? */ +uint32_t +fd_solcap_write_account_hdr( fd_solcap_writer_t * writer, + fd_solcap_buf_msg_t * msg_hdr, + fd_solcap_account_update_hdr_t * account_update ); -int -fd_solcap_write_bank_preimage( fd_solcap_writer_t * writer, - void const * bank_hash, - void const * prev_bank_hash, - void const * account_delta_hash, - void const * accounts_lt_hash_checksum, - void const * poh_hash, - ulong signature_cnt ); - -int -fd_solcap_write_bank_preimage2( fd_solcap_writer_t * writer, - fd_solcap_BankPreimage * preimg ); - -/* fd_solcap_write_transaction writes the given transaction to the - stream. Must only be called for transactions that are part of the - current slot's transaction hash. */ - -int -fd_solcap_write_transaction2( fd_solcap_writer_t * writer, - fd_solcap_Transaction * txn ); - -/* Stake Reward related methods */ +uint32_t +fd_solcap_write_account_data( fd_solcap_writer_t * writer, + void const * data, + ulong data_sz ); -int -fd_solcap_writer_stake_rewards_begin( - fd_solcap_writer_t * writer, - ulong payout_epoch, - ulong reward_epoch, - ulong inflation_lamports, - uint128 total_points -); - -int -fd_solcap_write_stake_reward_event( - fd_solcap_writer_t * writer, - fd_pubkey_t const * stake_acc_addr, - fd_pubkey_t const * vote_acc_addr, - uint commission, - long vote_rewards, - long stake_rewards, - long new_credits_observed -); -int -fd_solcap_write_vote_account_payout( - fd_solcap_writer_t * writer, - fd_pubkey_t const * vote_acc_addr, - ulong update_slot, - ulong lamports, - long lamports_delta -); +uint32_t +fd_solcap_write_bank_preimage( fd_solcap_writer_t * writer, + fd_solcap_buf_msg_t * msg_hdr, + fd_solcap_bank_preimage_t * bank_preimage ); -int -fd_solcap_write_stake_account_payout( - fd_solcap_writer_t * writer, - fd_pubkey_t const * stake_acc_addr, - ulong update_slot, - ulong lamports, - long lamports_delta, - ulong credits_observed, - long credits_observed_delta, - ulong delegation_stake, - long delegation_stake_delta -); - -/* fd_solcap_write_protobuf writes out an arbitrary protobuf blob. - Uses a 1 MiB large stack buffer. */ - -struct pb_msgdesc_s; -int -fd_solcap_write_protobuf( fd_solcap_writer_t * writer, - void const * msg, - struct pb_msgdesc_s const * desc, - ulong magic ); +uint32_t +fd_solcap_write_ftr( fd_solcap_writer_t * writer, + uint32_t block_len_redundant ); FD_PROTOTYPES_END diff --git a/src/flamenco/capture/fd_solcap_writer_stub.c b/src/flamenco/capture/fd_solcap_writer_stub.c deleted file mode 100644 index 71e39dcf013..00000000000 --- a/src/flamenco/capture/fd_solcap_writer_stub.c +++ /dev/null @@ -1,84 +0,0 @@ -#include "fd_solcap_writer.h" - -/* This file provides a stub implementation of fd_solcap_writer for - non-hosted targets. */ - -struct fd_solcap_writer { - uchar dummy; -}; - -FD_FN_CONST ulong -fd_solcap_writer_align( void ) { - return alignof(fd_solcap_writer_t); -} - -FD_FN_CONST ulong -fd_solcap_writer_footprint( void ) { - return sizeof(fd_solcap_writer_t); -} - -fd_solcap_writer_t * -fd_solcap_writer_new( void * mem ) { - return mem; -} - -void * -fd_solcap_writer_delete( fd_solcap_writer_t * mem ) { - return mem; -} - -fd_solcap_writer_t * -fd_solcap_writer_init( fd_solcap_writer_t * writer, - void * stream FD_PARAM_UNUSED ) { - return writer; -} - -fd_solcap_writer_t * -fd_solcap_writer_flush( fd_solcap_writer_t * writer ) { - return writer; -} - -void -fd_solcap_writer_set_slot( fd_solcap_writer_t * writer FD_PARAM_UNUSED, - ulong slot FD_PARAM_UNUSED ) {} - -int -fd_solcap_write_account( fd_solcap_writer_t * writer FD_PARAM_UNUSED, - void const * key FD_PARAM_UNUSED, - fd_solana_account_meta_t const * meta FD_PARAM_UNUSED, - void const * data FD_PARAM_UNUSED, - ulong data_sz FD_PARAM_UNUSED ) { - return 0; -} - -int -fd_solcap_write_account2( fd_solcap_writer_t * writer FD_PARAM_UNUSED, - fd_solcap_account_tbl_t const * tbl FD_PARAM_UNUSED, - fd_solcap_AccountMeta * meta_pb FD_PARAM_UNUSED, - void const * data FD_PARAM_UNUSED, - ulong data_sz FD_PARAM_UNUSED ) { - return 0; -} - -int -fd_solcap_write_bank_preimage( fd_solcap_writer_t * writer FD_PARAM_UNUSED, - void const * bank_hash FD_PARAM_UNUSED, - void const * prev_bank_hash FD_PARAM_UNUSED, - void const * account_delta_hash FD_PARAM_UNUSED, - void const * accounts_lt_hash_checksum FD_PARAM_UNUSED, - void const * poh_hash FD_PARAM_UNUSED, - ulong signature_cnt FD_PARAM_UNUSED ) { - return 0; -} - -int -fd_solcap_write_bank_preimage2( fd_solcap_writer_t * writer FD_PARAM_UNUSED, - fd_solcap_BankPreimage * preimg FD_PARAM_UNUSED ) { - return 0; -} - -int -fd_solcap_write_transaction2( fd_solcap_writer_t * writer FD_PARAM_UNUSED, - fd_solcap_Transaction * txn FD_PARAM_UNUSED ) { - return 0; -} diff --git a/src/flamenco/capture/fd_solcap_yaml.c b/src/flamenco/capture/fd_solcap_yaml.c deleted file mode 100644 index bba080a1163..00000000000 --- a/src/flamenco/capture/fd_solcap_yaml.c +++ /dev/null @@ -1,559 +0,0 @@ -#include "fd_solcap_proto.h" -#include "fd_solcap_reader.h" -#include "fd_solcap.pb.h" -#include "../runtime/fd_executor_err.h" -#include "../../ballet/base64/fd_base64.h" -#include "../../ballet/nanopb/pb_decode.h" -#include -#include - -static int -usage( void ) { - fprintf( stderr, - "Usage: fd_solcap_yaml [options] {FILE}\n" - "\n" - "Print a runtime capture file as YAML.\n" - "\n" - "Options:\n" - " --page-sz {gigantic|huge|normal} Page size\n" - " --page-cnt {count} Page count\n" - " --scratch-mb 1024 Scratch mem MiB\n" - " -v {level} YAML verbosity\n" - " --start-slot {slot} Start slot\n" - " --end-slot {slot} End slot\n" - "\n" ); - return 0; -} - -/* process_account reads and dumps a single account. Always shows - account metadata. If verbose>=4, includes a base64 dump of account content. */ - -static int -process_account( FILE * file, - long goff, - int verbose ) { - - /* Remember stream cursor */ - - long pos = ftell( file ); - if( FD_UNLIKELY( pos<0L ) ) { - FD_LOG_ERR(( "ftell failed (%d-%s)", errno, strerror( errno ) )); - return 0; - } - - /* Seek to chunk */ - - if( FD_UNLIKELY( 0!=fseek( file, goff, SEEK_SET ) ) ) { - FD_LOG_ERR(( "fseek failed (%d-%s)", errno, strerror( errno ) )); - return 0; - } - - /* Read chunk */ - - fd_solcap_chunk_t chunk[1]; - ulong n = fread( chunk, sizeof(fd_solcap_chunk_t), 1UL, file ); - if( FD_UNLIKELY( n!=1UL ) ) { - FD_LOG_ERR(( "fread chunk failed (%d-%s)", errno, strerror( errno ) )); - return 0; - } - if( FD_UNLIKELY( chunk->magic != FD_SOLCAP_V1_ACCT_MAGIC ) ) { - FD_LOG_ERR(( "expected account table chunk at %#lx, got magic=0x%016lx", (ulong)goff, chunk->magic )); - return 0; - } - - /* Read metadata */ - - fd_solcap_AccountMeta meta[1]; - do { - - uchar meta_buf[ 512UL ]; - ulong meta_sz = chunk->meta_sz; - if( FD_UNLIKELY( meta_sz > sizeof(meta_buf ) ) ) - FD_LOG_ERR(( "invalid account meta size (%lu)", meta_sz )); - - if( FD_UNLIKELY( 0!=fseek( file, (long)chunk->meta_coff - (long)sizeof(fd_solcap_chunk_t), SEEK_CUR ) ) ) - FD_LOG_ERR(( "fseek to account meta failed (%d-%s)", errno, strerror( errno ) )); - - if( FD_UNLIKELY( meta_sz != fread( meta_buf, 1UL, meta_sz, file ) ) ) - FD_LOG_ERR(( "fread account meta failed (%d-%s)", errno, strerror( errno ) )); - - pb_istream_t stream = pb_istream_from_buffer( meta_buf, meta_sz ); - if( FD_UNLIKELY( !pb_decode( &stream, fd_solcap_AccountMeta_fields, meta ) ) ) { - FD_LOG_HEXDUMP_DEBUG(( "account meta", meta_buf, meta_sz )); - FD_LOG_ERR(( "pb_decode account meta failed (%s)", PB_GET_ERROR(&stream) )); - } - - long rewind = (long)chunk->meta_coff + (long)meta_sz; - if( FD_UNLIKELY( 0!=fseek( file, -rewind, SEEK_CUR ) ) ) - FD_LOG_ERR(( "fseek failed (%d-%s)", errno, strerror( errno ) )); - - } while(0); - - printf( - " owner: '%s'\n" - " lamports: %lu\n" - " slot: %lu\n" - " executable: %s\n" - " data_sz: %lu\n", - FD_BASE58_ENC_32_ALLOCA( meta->owner ), - meta->lamports, - meta->slot, - meta->executable ? "true" : "false", - meta->data_sz ); - - /* Optionally print account data */ - - if( verbose>=4 ) { - printf( " data: '" ); - - /* Seek to account data */ - if( FD_UNLIKELY( 0!=fseek( file, goff + meta->data_coff, SEEK_SET ) ) ) - FD_LOG_ERR(( "fseek to account data failed (%d-%s)", errno, strerror( errno ) )); - - /* Streaming Base64 encode. - - Process inputs in "parts" with length divided by 3, 4 such that - no padding is in the middle of the encoding. Technically Base64 - allows padding in the middle, but it's cleaner to only have - padding at the end of the message. */ -# define PART_RAW_SZ (720UL) - ulong data_sz = meta->data_sz; - while( data_sz>0UL ) { - ulong n = fd_ulong_min( data_sz, PART_RAW_SZ ); - - /* Read chunk */ - uchar buf[ PART_RAW_SZ ]; - if( FD_UNLIKELY( 1UL!=fread( buf, n, 1UL, file ) ) ) - FD_LOG_ERR(( "fread account data failed (%d-%s)", errno, strerror( errno ) )); - - /* Encode chunk */ - char chunk[ FD_BASE64_ENC_SZ( PART_RAW_SZ ) ]; - ulong chunk_sz = fd_base64_encode( chunk, buf, n ); - /* Print encoded chunk */ - FD_TEST( 1UL==fwrite( chunk, chunk_sz, 1UL, stdout ) ); - - /* Wind up for next iteration */ - data_sz -= n; - } -# undef PART_RAW_SZ -# undef PART_BLK_SZ - - /* Finish YAML entry */ - printf( "'\n" ); - } - - /* Restore cursor */ - - if( FD_UNLIKELY( 0!=fseek( file, pos, SEEK_SET ) ) ) { - FD_LOG_ERR(( "fseek failed (%d-%s)", errno, strerror( errno ) )); - return 0; - } - - return 1; -} - -/* process_account_table reads and dumps an account table chunk. - If verbose>=3, prints account metadata. If verbose>=4, also prints - account data content. Returns 1 on success and 0 on failure. On - success, restores stream cursor to position on function entry. */ - -static int -process_account_table( FILE * file, - ulong slot, - int verbose, - int show_duplicate_accounts ) { - - /* Remember stream cursor */ - - long pos = ftell( file ); - if( FD_UNLIKELY( pos<0L ) ) { - FD_LOG_ERR(( "ftell failed (%d-%s)", errno, strerror( errno ) )); - return 0; - } - - /* Read chunk */ - - fd_solcap_chunk_t chunk[1]; - ulong n = fread( chunk, sizeof(fd_solcap_chunk_t), 1UL, file ); - if( FD_UNLIKELY( n!=1UL ) ) { - FD_LOG_ERR(( "fread chunk failed (%d-%s)", errno, strerror( errno ) )); - return 0; - } - if( FD_UNLIKELY( chunk->magic != FD_SOLCAP_V1_ACTB_MAGIC ) ) { - FD_LOG_ERR(( "expected account table chunk, got 0x%016lx", chunk->magic )); - return 0; - } - - /* Read metadata */ - - fd_solcap_AccountTableMeta meta[1]; - do { - - uchar meta_buf[ 512UL ]; - ulong meta_sz = chunk->meta_sz; - if( FD_UNLIKELY( meta_sz > sizeof(meta_buf ) ) ) { - FD_LOG_ERR(( "invalid accounts table meta size (%lu)", meta_sz )); - return 0; - } - - if( FD_UNLIKELY( 0!=fseek( file, (long)chunk->meta_coff - (long)sizeof(fd_solcap_chunk_t), SEEK_CUR ) ) ) { - FD_LOG_ERR(( "fseek to accounts table meta failed (%d-%s)", errno, strerror( errno ) )); - return 0; - } - - if( FD_UNLIKELY( meta_sz != fread( meta_buf, 1UL, meta_sz, file ) ) ) { - FD_LOG_ERR(( "fread accounts table meta failed (%d-%s)", errno, strerror( errno ) )); - return 0; - } - - pb_istream_t stream = pb_istream_from_buffer( meta_buf, meta_sz ); - if( FD_UNLIKELY( !pb_decode( &stream, fd_solcap_AccountTableMeta_fields, meta ) ) ) { - FD_LOG_HEXDUMP_DEBUG(( "accounts table meta", meta_buf, meta_sz )); - FD_LOG_ERR(( "pb_decode accounts table meta failed (%s)", PB_GET_ERROR(&stream) )); - return 0; - } - - long rewind = (long)chunk->meta_coff + (long)meta_sz; - if( FD_UNLIKELY( 0!=fseek( file, -rewind, SEEK_CUR ) ) ) { - FD_LOG_ERR(( "fseek failed (%d-%s)", errno, strerror( errno ) )); - return 0; - } - - } while(0); - - /* TODO verify meta.slot */ - - /* Seek to accounts table */ - - if( FD_UNLIKELY( 0!=fseek( file, (long)meta->account_table_coff, SEEK_CUR ) ) ) { - FD_LOG_ERR(( "fseek to accounts table failed (%d-%s)", errno, strerror( errno ) )); - return 0; - } - - typedef struct { - fd_solcap_account_tbl_t entry; - long acc_coff; - int is_last_occurrence; - } account_entry_info_t; - - account_entry_info_t * entries = malloc(meta->account_table_cnt * sizeof(account_entry_info_t)); - if( FD_UNLIKELY( !entries ) ) { - FD_LOG_ERR(( "malloc failed for account entries" )); - return 0; - } - - for( ulong i=0UL; i < meta->account_table_cnt; i++ ) { - if( FD_UNLIKELY( 1UL!=fread( &entries[i].entry, sizeof(fd_solcap_account_tbl_t), 1UL, file ) ) ) { - FD_LOG_ERR(( "fread accounts table entry failed (%d-%s)", errno, strerror( errno ) )); - free(entries); - return 0; - } - entries[i].acc_coff = entries[i].entry.acc_coff; - entries[i].is_last_occurrence = 1; - } - - for( ulong i=0UL; i < meta->account_table_cnt; i++ ) { - for( ulong j=i+1UL; j < meta->account_table_cnt; j++ ) { - if( memcmp(entries[i].entry.key, entries[j].entry.key, sizeof(entries[i].entry.key)) == 0 ) { - entries[i].is_last_occurrence = 0; - break; - } - } - } - - for( ulong i=0UL; i < meta->account_table_cnt; i++ ) { - if( !entries[i].is_last_occurrence && !show_duplicate_accounts ) { - continue; - } - - /* Write to YAML */ - - printf( - " - pubkey: '%s'\n" - " explorer: 'https://explorer.solana.com/block/%lu?accountFilter=%s&filter=all'\n", - FD_BASE58_ENC_32_ALLOCA( entries[i].entry.key ), - slot, - FD_BASE58_ENC_32_ALLOCA( entries[i].entry.key ) ); - - /* Fetch account details */ - - if( verbose >= 3 ) { - long acc_goff = (long)pos + entries[i].acc_coff; - if( FD_UNLIKELY( !process_account( file, acc_goff, verbose ) ) ) { - FD_LOG_ERR(( "process_account() failed" )); - free(entries); - return 0; - } - } - - } /* end for */ - - free(entries); - - /* Restore cursor */ - - if( FD_UNLIKELY( 0!=fseek( file, pos, SEEK_SET ) ) ) { - FD_LOG_ERR(( "fseek failed (%d-%s)", errno, strerror( errno ) )); - return 0; - } - - return 0; -} - -/* process_bank reads and dumps a bank chunk. If verbose>=3, also - processes account table. If verbose>=4, includes detailed content. - Returns errno (0 on success). Stream cursor is undefined on return. */ - -static int -process_bank( fd_solcap_chunk_t const * chunk, - FILE * file, - int verbose, - int show_duplicate_accounts, - long chunk_gaddr, - ulong start_slot, - ulong end_slot, - int has_txns ) { - -# define FD_SOLCAP_BANK_PREIMAGE_FOOTPRINT (512UL) - if( FD_UNLIKELY( chunk->meta_sz > FD_SOLCAP_BANK_PREIMAGE_FOOTPRINT ) ) { - FD_LOG_ERR(( "invalid bank preimage meta size (%u)", chunk->meta_sz )); - return ENOMEM; - } - - /* Read bank preimage meta */ - - uchar meta_buf[ 512UL ]; - if( FD_UNLIKELY( 0!=fseek( file, chunk_gaddr + (long)chunk->meta_coff, SEEK_SET ) ) ) { - FD_LOG_ERR(( "fseek bank preimage meta failed (%d-%s)", errno, strerror( errno ) )); - return errno; - } - if( FD_UNLIKELY( chunk->meta_sz != fread( meta_buf, 1UL, chunk->meta_sz, file ) ) ) { - FD_LOG_ERR(( "fread bank preimage meta failed (%d-%s)", errno, strerror( errno ) )); - return errno; - } - - /* Deserialize bank preimage meta */ - - pb_istream_t stream = pb_istream_from_buffer( meta_buf, chunk->meta_sz ); - - fd_solcap_BankPreimage meta; - if( FD_UNLIKELY( !pb_decode( &stream, fd_solcap_BankPreimage_fields, &meta ) ) ) { - FD_LOG_HEXDUMP_DEBUG(( "bank preimage meta", meta_buf, chunk->meta_sz )); - FD_LOG_ERR(( "pb_decode bank preimage meta failed (%s)", PB_GET_ERROR(&stream) )); - return EPROTO; - } - - if ( meta.slot < start_slot || meta.slot > end_slot ) { - return 0; - } - - /* Write YAML */ - if ( verbose < 4 || !has_txns ) - printf( "- slot: %lu\n", meta.slot ); - - printf( - " - bank_hash: '%s'\n", - FD_BASE58_ENC_32_ALLOCA( meta.bank_hash ) ); - - if( verbose>=2 ) { - printf( - " - prev_bank_hash: '%s'\n" - " - account_delta_hash: '%s'\n" - " - accounts_lt_hash_checksum: '%s'\n" - " - poh_hash: '%s'\n" - " - signature_cnt: %lu\n", - FD_BASE58_ENC_32_ALLOCA( meta.prev_bank_hash ), - FD_BASE58_ENC_32_ALLOCA( meta.account_delta_hash ), - FD_BASE58_ENC_32_ALLOCA( meta.accounts_lt_hash_checksum ), - FD_BASE58_ENC_32_ALLOCA( meta.poh_hash ), - meta.signature_cnt ); - } - - /* Accounts */ - - if( verbose >= 3 ) { - if( meta.account_table_coff==0L ) { - if( meta.account_cnt > 0UL ) - FD_LOG_WARNING(( "Capture does not include account info for slot=%lu", meta.slot )); - return 0; - } - - if( FD_UNLIKELY( 0!=fseek( file, chunk_gaddr + (long)meta.account_table_coff, SEEK_SET ) ) ) { - FD_LOG_ERR(( "fseek to account table failed (%d-%s)", errno, strerror( errno ) )); - return errno; - } - - printf( " - accounts_delta:\n" ); - if( FD_UNLIKELY( 0!=process_account_table( file, meta.slot, verbose, show_duplicate_accounts ) ) ) - return errno; - } - - return 0; -} - -static ulong -process_txn( fd_solcap_chunk_t const * chunk, - FILE * file, - int verbose, - long chunk_gaddr, - ulong prev_slot, - ulong start_slot, - ulong end_slot ) { - -if ( verbose < 4 ) - return 0; - -# define FD_SOLCAP_TRANSACTION_FOOTPRINT (128UL) - if( FD_UNLIKELY( chunk->meta_sz > FD_SOLCAP_TRANSACTION_FOOTPRINT ) ) { - FD_LOG_ERR(( "invalid transaction meta size (%u)", chunk->meta_sz )); - } - - /* Read transaction meta */ - - uchar meta_buf[ 128UL ]; - if( FD_UNLIKELY( 0!=fseek( file, chunk_gaddr + (long)chunk->meta_coff, SEEK_SET ) ) ) { - FD_LOG_ERR(( "fseek transaction meta failed (%d-%s)", errno, strerror( errno ) )); - } - if( FD_UNLIKELY( chunk->meta_sz != fread( meta_buf, 1UL, chunk->meta_sz, file ) ) ) { - FD_LOG_ERR(( "fread transaction meta failed (%d-%s)", errno, strerror( errno ) )); - } - - /* Deserialize transaction meta */ - - pb_istream_t stream = pb_istream_from_buffer( meta_buf, chunk->meta_sz ); - - fd_solcap_Transaction meta; - if( FD_UNLIKELY( !pb_decode( &stream, fd_solcap_Transaction_fields, &meta ) ) ) { - FD_LOG_HEXDUMP_DEBUG(( "transaction meta", meta_buf, chunk->meta_sz )); - FD_LOG_ERR(( "pb_decode transaction meta failed (%s)", PB_GET_ERROR(&stream) )); - } - - if ( meta.slot < start_slot || meta.slot > end_slot ) { - return meta.slot; - } - - /* Write YAML */ - if ( prev_slot == 0 || prev_slot != meta.slot ) { - printf( - "- slot: %lu\n" - " - txns:\n", meta.slot - ); - } - - printf( - " - txn_sig: '%s'\n" - " txn_err: %d\n" - " cus_used: %lu\n" - " instr_err_idx: %d\n", - FD_BASE58_ENC_64_ALLOCA( meta.txn_sig ), - meta.fd_txn_err, - meta.fd_cus_used, - meta.instr_err_idx); - - /* Only print custom error if it has been set*/ - if ( meta.fd_txn_err == FD_EXECUTOR_INSTR_ERR_CUSTOM_ERR) { - printf( " custom_err: %u\n", meta.fd_custom_err ); - } - - if ( verbose < 4 ) - return meta.slot; - - if ( meta.solana_txn_err != ULONG_MAX || meta.solana_cus_used != ULONG_MAX ) { - printf( - " solana_txn_err: %lu\n" - " solana_cus_used: %lu\n", - meta.solana_txn_err, - meta.solana_cus_used ); - } - - printf( - " explorer: 'https://explorer.solana.com/tx/%s'\n" - " solscan: 'https://solscan.io/tx/%s'\n" - " solanafm: 'https://solana.fm/tx/%s'\n", - FD_BASE58_ENC_64_ALLOCA( meta.txn_sig ), - FD_BASE58_ENC_64_ALLOCA( meta.txn_sig ), - FD_BASE58_ENC_64_ALLOCA( meta.txn_sig ) ); - - return meta.slot; -} - -int -main( int argc, - char ** argv ) { - fd_boot( &argc, &argv ); - - /* Command line handling */ - - for( int i=1; ichunk0_foff - (long)sizeof(fd_solcap_fhdr_t), SEEK_CUR ); - if( FD_UNLIKELY( err<0L ) ) { - FD_LOG_ERR(( "fseek chunk0 failed (%d-%s)", errno, strerror( errno ) )); - return errno; - } - - /* Read chunks */ - - fd_solcap_chunk_iter_t iter[1]; - fd_solcap_chunk_iter_new( iter, file ); - ulong previous_slot = 0; - /* TODO replace this with fd_solcap_chunk_iter_find */ - for(;;) { - long chunk_gaddr = fd_solcap_chunk_iter_next( iter ); - if( FD_UNLIKELY( chunk_gaddr<0L ) ) { - int err = fd_solcap_chunk_iter_err( iter ); - if( err==0 ) break; - FD_LOG_ERR(( "fd_solcap_chunk_iter_next() failed (%d-%s)", err, strerror( err ) )); - } - - if( fd_solcap_chunk_iter_done( iter ) ) break; - - fd_solcap_chunk_t const * chunk = fd_solcap_chunk_iter_item( iter ); - if( FD_UNLIKELY( !chunk ) ) FD_LOG_ERR(( "fd_solcap_chunk_item() failed" )); - - /* TODO: figure out how to make solana.solcap yamls print slot */ - if( chunk->magic == FD_SOLCAP_V1_BANK_MAGIC ) - process_bank( chunk, file, verbose, show_duplicate_accounts, chunk_gaddr, start_slot, end_slot, previous_slot != 0 ); - else if ( chunk->magic == FD_SOLCAP_V1_TRXN_MAGIC ) - previous_slot = process_txn( chunk, file, verbose, chunk_gaddr, previous_slot, start_slot, end_slot ); - } - - /* Cleanup */ - - FD_LOG_NOTICE(( "Done" )); - fclose( file ); - fd_halt(); - return 0; -} diff --git a/src/flamenco/rewards/fd_rewards.c b/src/flamenco/rewards/fd_rewards.c index 25df41ad722..95e1a220569 100644 --- a/src/flamenco/rewards/fd_rewards.c +++ b/src/flamenco/rewards/fd_rewards.c @@ -9,7 +9,7 @@ #include "../stakes/fd_stakes.h" #include "../runtime/program/fd_stake_program.h" #include "../runtime/sysvar/fd_sysvar_stake_history.h" -#include "../runtime/context/fd_capture_ctx.h" +#include "../../discof/capture/fd_capture_ctx.h" #include "../runtime/fd_runtime_stack.h" #include "../runtime/fd_runtime.h" #include "fd_epoch_rewards.h" @@ -489,7 +489,7 @@ calculate_stake_vote_rewards( fd_bank_t * bank, fd_funk_t * funk, fd_funk_txn_xid_t const * xid, fd_stake_delegations_t const * stake_delegations, - fd_capture_ctx_t * capture_ctx, + fd_capture_ctx_t * capture_ctx FD_PARAM_UNUSED, fd_stake_history_t const * stake_history, ulong rewarded_epoch, ulong total_rewards, @@ -563,16 +563,6 @@ calculate_stake_vote_rewards( fd_bank_t * bank, continue; } - if( capture_ctx ) { - fd_solcap_write_stake_reward_event( capture_ctx->capture, - &stake_delegation->stake_account, - voter_acc, - vote_state_ele->commission, - (long)calculated_stake_rewards->voter_rewards, - (long)calculated_stake_rewards->staker_rewards, - (long)calculated_stake_rewards->new_credits_observed ); - } - runtime_stack->stakes.vote_rewards[ vote_state_ele->idx ] += calculated_stake_rewards->voter_rewards; fd_epoch_rewards_insert( epoch_rewards, &stake_delegation->stake_account, calculated_stake_rewards->new_credits_observed, calculated_stake_rewards->staker_rewards ); @@ -612,15 +602,6 @@ calculate_validator_rewards( fd_bank_t * bank, /* If there are no points, then we set the rewards to 0. */ *rewards_out = points>0UL ? *rewards_out: 0UL; - if( capture_ctx ) { - ulong const epoch = fd_bank_epoch_get( bank ); - fd_solcap_writer_stake_rewards_begin( capture_ctx->capture, - epoch, - epoch-1UL, /* FIXME: this is not strictly correct */ - *rewards_out, - points ); - } - /* Calculate the stake and vote rewards for each account. We want to use the vote states from the end of the current_epoch. */ calculate_stake_vote_rewards( @@ -775,14 +756,6 @@ calculate_rewards_and_distribute_vote_rewards( fd_bank_t * ba fd_txn_account_mutable_fini( vote_rec, accdb, &prepare ); distributed_rewards = fd_ulong_sat_add( distributed_rewards, rewards ); - - if( capture_ctx ) { - fd_solcap_write_vote_account_payout( capture_ctx->capture, - vote_pubkey, - fd_bank_slot_get( bank ), - fd_txn_account_get_lamports( vote_rec ), - (long)rewards ); - } } fd_bank_vote_states_end_locking_query( bank ); @@ -849,7 +822,6 @@ distribute_epoch_reward_to_stake_acc( fd_bank_t * bank, return 1; } - ulong old_credits_observed = stake_state->inner.stake.stake.credits_observed; stake_state->inner.stake.stake.credits_observed = new_credits_observed; stake_state->inner.stake.stake.delegation.stake = fd_ulong_sat_add( stake_state->inner.stake.stake.delegation.stake, reward_lamports ); @@ -868,18 +840,6 @@ distribute_epoch_reward_to_stake_acc( fd_bank_t * bank, stake_state->inner.stake.stake.delegation.warmup_cooldown_rate ); fd_bank_stake_delegations_delta_end_locking_modify( bank ); - if( capture_ctx ) { - fd_solcap_write_stake_account_payout( capture_ctx->capture, - stake_pubkey, - fd_bank_slot_get( bank ), - fd_txn_account_get_lamports( stake_acc_rec ), - (long)reward_lamports, - new_credits_observed, - (long)( new_credits_observed-old_credits_observed ), - stake_state->inner.stake.stake.delegation.stake, - (long)reward_lamports ); - } - if( FD_UNLIKELY( write_stake_state( stake_acc_rec, stake_state ) != 0 ) ) { FD_LOG_ERR(( "write_stake_state failed" )); } diff --git a/src/flamenco/runtime/context/Local.mk b/src/flamenco/runtime/context/Local.mk index 06c07d05061..1f431569bda 100644 --- a/src/flamenco/runtime/context/Local.mk +++ b/src/flamenco/runtime/context/Local.mk @@ -4,7 +4,4 @@ $(call add-objs,fd_exec_txn_ctx,fd_flamenco) $(call add-hdrs,fd_exec_instr_ctx.h) $(call add-objs,fd_exec_instr_ctx,fd_flamenco) - -$(call add-hdrs,fd_capture_ctx.h) -$(call add-objs,fd_capture_ctx,fd_flamenco) endif diff --git a/src/flamenco/runtime/context/fd_capture_ctx.c b/src/flamenco/runtime/context/fd_capture_ctx.c deleted file mode 100644 index afe8d451900..00000000000 --- a/src/flamenco/runtime/context/fd_capture_ctx.c +++ /dev/null @@ -1,118 +0,0 @@ -#include "fd_capture_ctx.h" - -#include - -void * -fd_capture_ctx_new( void * mem ) { - if( FD_UNLIKELY( !mem ) ) { - FD_LOG_WARNING(( "NULL mem" )); - return NULL; - } - - if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)mem, fd_capture_ctx_align() ) ) ) { - FD_LOG_WARNING(( "misaligned mem" )); - return NULL; - } - - fd_memset( mem, 0, fd_capture_ctx_footprint() ); - - /* TODO: use layout macros */ - fd_capture_ctx_t * self = (fd_capture_ctx_t *) mem; - self->capture = (fd_solcap_writer_t *)((uchar *)mem + sizeof(fd_capture_ctx_t)); - fd_solcap_writer_new( self->capture ); - - self->account_updates_buffer = (uchar *)mem + sizeof(fd_capture_ctx_t) + fd_solcap_writer_footprint(); - self->account_updates_buffer_ptr = self->account_updates_buffer; - self->account_updates_len = 0UL; - - FD_COMPILER_MFENCE(); - self->magic = FD_CAPTURE_CTX_MAGIC; - FD_COMPILER_MFENCE(); - - return mem; -} - -fd_capture_ctx_t * -fd_capture_ctx_join( void * mem ) { - if( FD_UNLIKELY( !mem ) ) { - FD_LOG_WARNING(( "NULL block" )); - return NULL; - } - - fd_capture_ctx_t * ctx = (fd_capture_ctx_t *) mem; - - if( FD_UNLIKELY( ctx->magic!=FD_CAPTURE_CTX_MAGIC ) ) { - FD_LOG_WARNING(( "bad magic" )); - return NULL; - } - - return ctx; -} - -void * -fd_capture_ctx_leave( fd_capture_ctx_t * ctx) { - if( FD_UNLIKELY( !ctx ) ) { - FD_LOG_WARNING(( "NULL block" )); - return NULL; - } - - if( FD_UNLIKELY( ctx->magic!=FD_CAPTURE_CTX_MAGIC ) ) { - FD_LOG_WARNING(( "bad magic" )); - return NULL; - } - - return (void *) ctx; -} - -void * -fd_capture_ctx_delete( void * mem ) { - if( FD_UNLIKELY( !mem ) ) { - FD_LOG_WARNING(( "NULL mem" )); - return NULL; - } - - if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)mem, fd_capture_ctx_align() ) ) ) { - FD_LOG_WARNING(( "misaligned mem" )); - return NULL; - } - - fd_capture_ctx_t * hdr = (fd_capture_ctx_t *)mem; - if( FD_UNLIKELY( hdr->magic!=FD_CAPTURE_CTX_MAGIC ) ) { - FD_LOG_WARNING(( "bad magic" )); - return NULL; - } - - if( FD_UNLIKELY( fd_solcap_writer_delete( hdr->capture ) == NULL ) ) { - FD_LOG_WARNING(( "failed deleting capture" )); - return NULL; - } - - FD_COMPILER_MFENCE(); - FD_VOLATILE( hdr->magic ) = 0UL; - FD_COMPILER_MFENCE(); - - return mem; -} - -#include "../../fd_rwlock.h" -static fd_rwlock_t txn_status_lock[ 1 ] = {0}; - -void -fd_capture_ctx_txn_status_start_read( void ) { - fd_rwlock_read( txn_status_lock ); -} - -void -fd_capture_ctx_txn_status_end_read( void ) { - fd_rwlock_unread( txn_status_lock ); -} - -void -fd_capture_ctx_txn_status_start_write( void ) { - fd_rwlock_write( txn_status_lock ); -} - -void -fd_capture_ctx_txn_status_end_write( void ) { - fd_rwlock_unwrite( txn_status_lock ); -} diff --git a/src/flamenco/runtime/context/fd_capture_ctx.h b/src/flamenco/runtime/context/fd_capture_ctx.h deleted file mode 100644 index 9f60144fc63..00000000000 --- a/src/flamenco/runtime/context/fd_capture_ctx.h +++ /dev/null @@ -1,121 +0,0 @@ -#ifndef HEADER_fd_src_flamenco_runtime_context_fd_capture_ctx_h -#define HEADER_fd_src_flamenco_runtime_context_fd_capture_ctx_h - -#include "../../capture/fd_solcap_writer.h" -#include "../fd_runtime_const.h" - -/* fd_capture_ctx_account_update_msg_t is the message sent from - exec tile to replay tile that notifies the solcap writer that an - account update has occurred. */ - -struct __attribute__((packed)) fd_capture_ctx_account_update_msg { - fd_pubkey_t pubkey; - fd_solana_account_meta_t info; - ulong data_sz; - fd_hash_t hash; - ulong bank_idx; - /* Account data follows immediately after this struct */ -}; -typedef struct fd_capture_ctx_account_update_msg fd_capture_ctx_account_update_msg_t; -#define FD_CAPTURE_CTX_ACCOUNT_UPDATE_MSG_FOOTPRINT (FD_RUNTIME_ACC_SZ_MAX + sizeof(fd_capture_ctx_account_update_msg_t)) - -/* Maximum number of accounts that can be updated in a single transaction */ -#define FD_CAPTURE_CTX_MAX_ACCOUNT_UPDATES (128UL) -#define FD_CAPTURE_CTX_ACCOUNT_UPDATE_BUFFER_SZ (FD_CAPTURE_CTX_MAX_ACCOUNT_UPDATES * FD_CAPTURE_CTX_ACCOUNT_UPDATE_MSG_FOOTPRINT) -#define FD_CAPTURE_CTX_ACCOUNT_UPDATE_BUFFER_ALIGN (8UL) - -/* Context needed to do solcap capture during execution of transactions */ -struct fd_capture_ctx { - ulong magic; /* ==FD_CAPTURE_CTX_MAGIC */ - - /* Solcap */ - ulong solcap_start_slot; - int trace_dirfd; - int trace_mode; - fd_solcap_writer_t * capture; - int capture_txns; /* Capturing txns can add significant time */ - - /* Checkpointing */ - ulong checkpt_freq; /* Must be a rooted slot */ - char const * checkpt_path; /* Wksp checkpoint format */ - char const * checkpt_archive; /* Funk archive format */ - - /*======== PROTOBUF ========*/ - char const * dump_proto_output_dir; - char const * dump_proto_sig_filter; - ulong dump_proto_start_slot; - - /* Instruction Capture */ - int dump_instr_to_pb; - - /* Transaction Capture */ - int dump_txn_to_pb; - - /* Block Capture */ - int dump_block_to_pb; - - /* Syscall Capture */ - int dump_syscall_to_pb; - - /* ELF Capture */ - int dump_elf_to_pb; - - /* Account update buffer, account updates to be sent over the exec_replay link are buffered here - to avoid passing stem down into the runtime. - - FIXME: write directly into the dcache to avoid the memory copy and allocation - TODO: remove this when solcap v2 is here. */ - uchar * account_updates_buffer; - uchar * account_updates_buffer_ptr; - ulong account_updates_len; -}; -typedef struct fd_capture_ctx fd_capture_ctx_t; - -static inline ulong -fd_capture_ctx_align( void ) { - return fd_ulong_max( alignof(fd_capture_ctx_t), - fd_ulong_max( fd_solcap_writer_align(), FD_CAPTURE_CTX_ACCOUNT_UPDATE_BUFFER_ALIGN )); -} - -static inline ulong -fd_capture_ctx_footprint( void ) { - ulong l = FD_LAYOUT_INIT; - l = FD_LAYOUT_APPEND( l, fd_capture_ctx_align(), sizeof(fd_capture_ctx_t) ); - l = FD_LAYOUT_APPEND( l, fd_solcap_writer_align(), fd_solcap_writer_footprint() ); - l = FD_LAYOUT_APPEND( l, 8UL, FD_CAPTURE_CTX_ACCOUNT_UPDATE_BUFFER_SZ ); - return FD_LAYOUT_FINI( l, fd_capture_ctx_align() ); -} - -#define FD_CAPTURE_CTX_MAGIC (0x193ECD2A6C395195UL) /* random */ - -FD_PROTOTYPES_BEGIN - -void * -fd_capture_ctx_new( void * mem ); - -fd_capture_ctx_t * -fd_capture_ctx_join( void * mem ); - -void * -fd_capture_ctx_leave( fd_capture_ctx_t * ctx ); - -void * -fd_capture_ctx_delete( void * mem ); - -/* Temporary locks to protect the blockstore txn_map. See comment in - fd_runtime_write_transaction_status. */ -void -fd_capture_ctx_txn_status_start_read( void ); - -void -fd_capture_ctx_txn_status_end_read( void ); - -void -fd_capture_ctx_txn_status_start_write( void ); - -void -fd_capture_ctx_txn_status_end_write( void ); - -FD_PROTOTYPES_END - -#endif /* HEADER_fd_src_flamenco_runtime_context_fd_capture_ctx_h */ diff --git a/src/flamenco/runtime/fd_hashes.c b/src/flamenco/runtime/fd_hashes.c index dd67683744d..e624207134b 100644 --- a/src/flamenco/runtime/fd_hashes.c +++ b/src/flamenco/runtime/fd_hashes.c @@ -1,7 +1,7 @@ #include "fd_hashes.h" #include "fd_acc_mgr.h" #include "fd_bank.h" -#include "context/fd_capture_ctx.h" +#include "../../discof/capture/fd_capture_ctx.h" #include "../capture/fd_solcap_writer.h" #include "../../ballet/blake3/fd_blake3.h" #include "../../ballet/lthash/fd_lthash.h" @@ -78,19 +78,16 @@ fd_hashes_update_lthash( fd_txn_account_t const * account, fd_bank_lthash_end_locking_modify( bank ); - /* Write the new account state to the capture file */ if( capture_ctx && capture_ctx->capture && - fd_bank_slot_get( bank )>=capture_ctx->solcap_start_slot && - memcmp( prev_account_hash->bytes, new_hash->bytes, sizeof(fd_lthash_value_t))!=0 ) { + fd_bank_slot_get( bank )>=capture_ctx->solcap_start_slot ) { fd_solana_account_meta_t meta = fd_txn_account_get_solana_meta( account ); - int err = fd_solcap_write_account( - capture_ctx->capture, + fd_capture_link_write_account_update( + capture_ctx, + capture_ctx->current_txn_idx, account->pubkey, &meta, + fd_bank_slot_get( bank ), fd_txn_account_get_data( account ), fd_txn_account_get_data_len( account ) ); - if( FD_UNLIKELY( err ) ) { - FD_LOG_ERR(( "Failed to write account to capture file" )); - } } } diff --git a/src/flamenco/runtime/fd_runtime.c b/src/flamenco/runtime/fd_runtime.c index 7de54bf782b..f01546daa59 100644 --- a/src/flamenco/runtime/fd_runtime.c +++ b/src/flamenco/runtime/fd_runtime.c @@ -1,5 +1,5 @@ #include "fd_runtime.h" -#include "context/fd_capture_ctx.h" +#include "../../discof/capture/fd_capture_ctx.h" #include "fd_acc_mgr.h" #include "fd_alut_interp.h" #include "fd_bank.h" @@ -550,15 +550,14 @@ fd_runtime_update_bank_hash( fd_bank_t * bank, uchar lthash_hash[FD_HASH_FOOTPRINT]; fd_blake3_hash(lthash->bytes, FD_LTHASH_LEN_BYTES, lthash_hash ); - - fd_solcap_write_bank_preimage( - capture_ctx->capture, - new_bank_hash->hash, - fd_bank_prev_bank_hash_query( bank ), - NULL, - lthash_hash, - fd_bank_poh_query( bank )->hash, - fd_bank_signature_count_get( bank ) ); + fd_capture_link_write_bank_preimage( + capture_ctx, + fd_bank_slot_get( bank ), + (fd_hash_t *)new_bank_hash->hash, + (fd_hash_t *)fd_bank_prev_bank_hash_query( bank ), + (fd_hash_t *)lthash_hash, + (fd_hash_t *)fd_bank_poh_query( bank )->hash, + fd_bank_signature_count_get( bank ) ); } fd_bank_lthash_end_locking_query( bank ); @@ -759,52 +758,6 @@ fd_runtime_finalize_account( fd_funk_t * funk, } -/* fd_runtime_buffer_solcap_account_update buffers an account - update event message in the capture context, which will be - sent to the replay tile via the exec_replay link. - This buffering is done to avoid passing stem down into the runtime. - - TODO: remove this when solcap v2 is here. */ -static void -fd_runtime_buffer_solcap_account_update( fd_txn_account_t * account, - fd_bank_t * bank, - fd_capture_ctx_t * capture_ctx ) { - - /* Check if we should publish the update */ - if( FD_UNLIKELY( !capture_ctx || fd_bank_slot_get( bank )solcap_start_slot ) ) { - return; - } - - /* Get account data */ - fd_account_meta_t const * meta = fd_txn_account_get_meta( account ); - void const * data = fd_txn_account_get_data( account ); - - /* Calculate account hash using lthash */ - fd_lthash_value_t lthash[1]; - fd_hashes_account_lthash( account->pubkey, meta, data, lthash ); - - /* Calculate message size */ - if( FD_UNLIKELY( capture_ctx->account_updates_len >= FD_CAPTURE_CTX_MAX_ACCOUNT_UPDATES ) ) { - FD_LOG_CRIT(( "cannot buffer solcap account update. this should never happen" )); - return; - } - - /* Write the message to the buffer */ - fd_capture_ctx_account_update_msg_t * account_update_msg = (fd_capture_ctx_account_update_msg_t *)(capture_ctx->account_updates_buffer_ptr); - account_update_msg->pubkey = *account->pubkey; - account_update_msg->info = fd_txn_account_get_solana_meta( account ); - account_update_msg->data_sz = meta->dlen; - account_update_msg->bank_idx = bank->idx; - memcpy( account_update_msg->hash.uc, lthash->bytes, sizeof(fd_hash_t) ); - capture_ctx->account_updates_buffer_ptr += sizeof(fd_capture_ctx_account_update_msg_t); - - /* Write the account data to the buffer */ - memcpy( capture_ctx->account_updates_buffer_ptr, data, meta->dlen ); - capture_ctx->account_updates_buffer_ptr += meta->dlen; - - capture_ctx->account_updates_len++; -} - /* fd_runtime_save_account is a convenience wrapper that looks up the previous account state from funk before updating the lthash and saving the new version of the account to funk. @@ -865,11 +818,7 @@ fd_runtime_save_account( fd_funk_t * funk, } /* Mix in the account hash into the bank hash */ - fd_hashes_update_lthash( account, prev_hash, bank, NULL ); - - /* Publish account update to replay tile for solcap writing - TODO: write in the exec tile with solcap v2 */ - fd_runtime_buffer_solcap_account_update( account, bank, capture_ctx ); + fd_hashes_update_lthash( account, prev_hash, bank, capture_ctx ); /* Save the new version of the account to Funk */ fd_runtime_finalize_account( funk, xid, account, funk_prev_rec ); diff --git a/src/flamenco/runtime/fd_runtime.h b/src/flamenco/runtime/fd_runtime.h index b4f6940fc2c..852f8bb860a 100644 --- a/src/flamenco/runtime/fd_runtime.h +++ b/src/flamenco/runtime/fd_runtime.h @@ -9,7 +9,7 @@ #include "fd_acc_mgr.h" #include "fd_hashes.h" #include "../features/fd_features.h" -#include "context/fd_capture_ctx.h" +#include "../../discof/capture/fd_capture_ctx.h" #include "context/fd_exec_txn_ctx.h" #include "info/fd_instr_info.h" #include "../../disco/pack/fd_microblock.h" diff --git a/src/flamenco/runtime/fd_txn_account.c b/src/flamenco/runtime/fd_txn_account.c index c275ebb64d5..6743de892de 100644 --- a/src/flamenco/runtime/fd_txn_account.c +++ b/src/flamenco/runtime/fd_txn_account.c @@ -488,7 +488,6 @@ fd_solana_account_meta_t fd_txn_account_get_solana_meta( fd_txn_account_t const * acct ) { fd_solana_account_meta_t meta = { .lamports = acct->meta->lamports, - .rent_epoch = ULONG_MAX, .executable = acct->meta->executable, }; memcpy( meta.owner, acct->meta->owner, sizeof(fd_pubkey_t) ); diff --git a/src/flamenco/runtime/tests/Local.mk b/src/flamenco/runtime/tests/Local.mk index 108d3fcf032..7aa22bcfd84 100644 --- a/src/flamenco/runtime/tests/Local.mk +++ b/src/flamenco/runtime/tests/Local.mk @@ -12,11 +12,11 @@ $(call add-hdrs,generated/context.pb.h,generated/elf.pb.h,generated/invoke.pb.h, $(call add-objs,generated/context.pb generated/elf.pb generated/invoke.pb generated/txn.pb generated/block.pb generated/vm.pb generated/type.pb generated/shred.pb generated/metadata.pb,fd_flamenco) SOL_COMPAT_FLAGS:=-Wl,--undefined=fd_types_vt_by_name -Wl,--version-script=src/flamenco/runtime/tests/libfd_exec_sol_compat.map -$(call make-unit-test,test_sol_compat,test_sol_compat,fd_flamenco_test fd_flamenco fd_tango fd_funk fd_ballet fd_util fd_disco,$(SECP256K1_LIBS)) -$(call make-shared,libfd_exec_sol_compat.so,fd_sol_compat,fd_flamenco_test fd_flamenco fd_funk fd_ballet fd_util fd_disco,$(SECP256K1_LIBS) $(SOL_COMPAT_FLAGS)) +$(call make-unit-test,test_sol_compat,test_sol_compat,fd_flamenco_test fd_flamenco fd_tango fd_funk fd_ballet fd_util fd_disco fd_discof,$(SECP256K1_LIBS)) +$(call make-shared,libfd_exec_sol_compat.so,fd_sol_compat,fd_flamenco_test fd_flamenco fd_funk fd_ballet fd_util fd_disco fd_discof,$(SECP256K1_LIBS) $(SOL_COMPAT_FLAGS)) $(call make-unit-test,test_sol_compat_so,test_sol_compat_so,fd_util) -$(call make-unit-test,test_dump_block,test_dump_block,fd_flamenco_test fd_flamenco fd_funk fd_ballet fd_util fd_disco,$(SECP256K1_LIBS)) +$(call make-unit-test,test_dump_block,test_dump_block,fd_flamenco_test fd_flamenco fd_funk fd_ballet fd_util fd_disco fd_discof,$(SECP256K1_LIBS)) run-runtime-backtest: $(OBJDIR)/bin/fd_ledger $(OBJDIR)/bin/firedancer-dev OBJDIR=$(OBJDIR) src/flamenco/runtime/tests/run_backtest_ci.sh $(BACKTEST_ARGS) diff --git a/src/flamenco/runtime/tests/fd_block_harness.c b/src/flamenco/runtime/tests/fd_block_harness.c index 670c28b2693..51d3f3d3fab 100644 --- a/src/flamenco/runtime/tests/fd_block_harness.c +++ b/src/flamenco/runtime/tests/fd_block_harness.c @@ -15,6 +15,8 @@ #include "../../types/fd_types.h" #include "../../../disco/pack/fd_pack.h" #include "generated/block.pb.h" +#include "../../../discof/capture/fd_capture_ctx.h" +#include "../../capture/fd_solcap_writer.h" /* Templatized leader schedule sort helper functions */ typedef struct { @@ -441,15 +443,28 @@ fd_solfuzz_block_ctx_exec( fd_solfuzz_runner_t * runner, // Prepare. Execute. Finalize. FD_SPAD_FRAME_BEGIN( runner->spad ) { fd_capture_ctx_t * capture_ctx = NULL; + if( runner->solcap ) { void * capture_ctx_mem = fd_spad_alloc( runner->spad, fd_capture_ctx_align(), fd_capture_ctx_footprint() ); - capture_ctx = fd_capture_ctx_new( capture_ctx_mem ); - if( FD_UNLIKELY( capture_ctx==NULL ) ) { - FD_LOG_ERR(("capture_ctx_mem is NULL, cannot write solcap")); + capture_ctx = fd_capture_ctx_join( fd_capture_ctx_new( capture_ctx_mem ) ); + if( FD_UNLIKELY( !capture_ctx ) ) { + FD_LOG_ERR(( "Failed to initialize capture_ctx" )); + } + + fd_capture_link_file_t * capture_link_file = + fd_spad_alloc( runner->spad, alignof(fd_capture_link_file_t), sizeof(fd_capture_link_file_t) ); + if( FD_UNLIKELY( !capture_link_file ) ) { + FD_LOG_ERR(( "Failed to allocate capture_link_file" )); } - capture_ctx->capture = runner->solcap; + + capture_link_file->base.vt = &fd_capture_link_file_vt; + + capture_link_file->file = (FILE *)runner->solcap_file; + capture_ctx->capture_link = &capture_link_file->base; + capture_ctx->capctx_buf.file = capture_link_file; capture_ctx->solcap_start_slot = fd_bank_slot_get( runner->bank ); - fd_solcap_writer_set_slot( capture_ctx->capture, fd_bank_slot_get( runner->bank ) ); + + fd_solcap_writer_init( capture_ctx->capture, (FILE *)runner->solcap_file ); } fd_rewards_recalculate_partitioned_rewards( runner->banks, runner->bank, runner->accdb->funk, xid, runner->runtime_stack, capture_ctx ); @@ -687,7 +702,6 @@ fd_solfuzz_pb_block_run( fd_solfuzz_runner_t * runner, fd_exec_test_block_effects_t ** output = fd_type_pun( output_ ); FD_SPAD_FRAME_BEGIN( runner->spad ) { - /* Set up the block execution context */ ulong txn_cnt; fd_txn_p_t * txn_ptrs = fd_solfuzz_pb_block_ctx_create( runner, input, &txn_cnt ); if( txn_ptrs==NULL ) { diff --git a/src/flamenco/runtime/tests/fd_sol_compat.c b/src/flamenco/runtime/tests/fd_sol_compat.c index 7e3f092af80..0eb2d027407 100644 --- a/src/flamenco/runtime/tests/fd_sol_compat.c +++ b/src/flamenco/runtime/tests/fd_sol_compat.c @@ -51,7 +51,6 @@ static void sol_compat_cleanup_runner( fd_solfuzz_runner_t * runner ) { /* Cleanup test runner */ if( runner->solcap ) { - fd_solcap_writer_flush( runner->solcap ); fd_wksp_free_laddr( fd_solcap_writer_delete( runner->solcap ) ); runner->solcap = NULL; fclose( runner->solcap_file ); diff --git a/src/flamenco/runtime/tests/test_dump_block.c b/src/flamenco/runtime/tests/test_dump_block.c index 6a46f379a7f..706f14a6438 100644 --- a/src/flamenco/runtime/tests/test_dump_block.c +++ b/src/flamenco/runtime/tests/test_dump_block.c @@ -3,7 +3,7 @@ #include "fd_dump_pb.h" #include "fd_txn_harness.h" #include "../../../util/fd_util.h" -#include "../context/fd_capture_ctx.h" +#include "../../../discof/capture/fd_capture_ctx.h" #include "../fd_bank.h" #include "../fd_blockhashes.h" #include "../fd_system_ids.h" diff --git a/src/flamenco/types/fd_fuzz_types.h b/src/flamenco/types/fd_fuzz_types.h index 50a2634c464..77a06fd6927 100644 --- a/src/flamenco/types/fd_fuzz_types.h +++ b/src/flamenco/types/fd_fuzz_types.h @@ -203,7 +203,6 @@ void *fd_solana_account_meta_generate( void *mem, void **alloc_mem, fd_rng_t * r *alloc_mem = (uchar *) *alloc_mem + sizeof(fd_solana_account_meta_t); fd_solana_account_meta_new(mem); self->lamports = fd_rng_ulong( rng ); - self->rent_epoch = fd_rng_ulong( rng ); LLVMFuzzerMutate( &self->owner[0], sizeof(self->owner), sizeof(self->owner) ); self->executable = fd_rng_uchar( rng ); LLVMFuzzerMutate( self->padding, 3, 3 ); diff --git a/src/flamenco/types/fd_types.c b/src/flamenco/types/fd_types.c index d1ff7ca2fb0..db0babcf782 100644 --- a/src/flamenco/types/fd_types.c +++ b/src/flamenco/types/fd_types.c @@ -922,8 +922,6 @@ int fd_solana_account_meta_encode( fd_solana_account_meta_t const * self, fd_bin int err; err = fd_bincode_uint64_encode( self->lamports, ctx ); if( FD_UNLIKELY( err ) ) return err; - err = fd_bincode_uint64_encode( self->rent_epoch, ctx ); - if( FD_UNLIKELY( err ) ) return err; err = fd_bincode_bytes_encode( self->owner, sizeof(self->owner), ctx ); if( FD_UNLIKELY( err ) ) return err; err = fd_bincode_bool_encode( (uchar)(self->executable), ctx ); @@ -937,8 +935,6 @@ static int fd_solana_account_meta_decode_footprint_inner( fd_bincode_decode_ctx_ int err = 0; err = fd_bincode_uint64_decode_footprint( ctx ); if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; - err = fd_bincode_uint64_decode_footprint( ctx ); - if( FD_UNLIKELY( err!=FD_BINCODE_SUCCESS ) ) return err; err = fd_bincode_bytes_decode_footprint( 32, ctx ); if( FD_UNLIKELY( err ) ) return err; err = fd_bincode_bool_decode_footprint( ctx ); @@ -958,7 +954,6 @@ int fd_solana_account_meta_decode_footprint( fd_bincode_decode_ctx_t * ctx, ulon static void fd_solana_account_meta_decode_inner( void * struct_mem, void * * alloc_mem, fd_bincode_decode_ctx_t * ctx ) { fd_solana_account_meta_t * self = (fd_solana_account_meta_t *)struct_mem; fd_bincode_uint64_decode_unsafe( &self->lamports, ctx ); - fd_bincode_uint64_decode_unsafe( &self->rent_epoch, ctx ); fd_bincode_bytes_decode_unsafe( &self->owner[0], sizeof(self->owner), ctx ); fd_bincode_bool_decode_unsafe( &self->executable, ctx ); fd_bincode_bytes_decode_unsafe( self->padding, 3, ctx ); @@ -978,7 +973,6 @@ void fd_solana_account_meta_walk( void * w, fd_solana_account_meta_t const * sel (void) varint; fun( w, self, name, FD_FLAMENCO_TYPE_MAP, "fd_solana_account_meta", level++, 0 ); fun( w, &self->lamports, "lamports", FD_FLAMENCO_TYPE_ULONG, "ulong", level, 0 ); - fun( w, &self->rent_epoch, "rent_epoch", FD_FLAMENCO_TYPE_ULONG, "ulong", level, 0 ); fun( w, self->owner, "owner", FD_FLAMENCO_TYPE_HASH256, "uchar[32]", level, 0 ); fun( w, &self->executable, "executable", FD_FLAMENCO_TYPE_BOOL, "bool", level, 0 ); fun(w, self->padding, "padding", FD_FLAMENCO_TYPE_UCHAR, "uchar", level, 0 ); diff --git a/src/flamenco/types/fd_types.h b/src/flamenco/types/fd_types.h index 8fda01f8de3..69f982815e5 100644 --- a/src/flamenco/types/fd_types.h +++ b/src/flamenco/types/fd_types.h @@ -176,10 +176,9 @@ struct __attribute__((packed)) fd_solana_account_stored_meta { typedef struct fd_solana_account_stored_meta fd_solana_account_stored_meta_t; #define FD_SOLANA_ACCOUNT_STORED_META_ALIGN (8UL) -/* Encoded Size: Fixed (52 bytes) */ +/* Encoded Size: Fixed (44 bytes) */ struct __attribute__((packed)) fd_solana_account_meta { ulong lamports; - ulong rent_epoch; uchar owner[32]; uchar executable; uchar padding[3]; @@ -2071,7 +2070,7 @@ void * fd_solana_account_stored_meta_decode( void * mem, fd_bincode_decode_ctx_t void fd_solana_account_meta_new( fd_solana_account_meta_t * self ); int fd_solana_account_meta_encode( fd_solana_account_meta_t const * self, fd_bincode_encode_ctx_t * ctx ); void fd_solana_account_meta_walk( void * w, fd_solana_account_meta_t const * self, fd_types_walk_fn_t fun, const char *name, uint level, uint varint ); -static inline ulong fd_solana_account_meta_size( fd_solana_account_meta_t const * self ) { (void)self; return 52UL; } +static inline ulong fd_solana_account_meta_size( fd_solana_account_meta_t const * self ) { (void)self; return 44UL; } static inline ulong fd_solana_account_meta_align( void ) { return FD_SOLANA_ACCOUNT_META_ALIGN; } int fd_solana_account_meta_decode_footprint( fd_bincode_decode_ctx_t * ctx, ulong * total_sz ); void * fd_solana_account_meta_decode( void * mem, fd_bincode_decode_ctx_t * ctx ); diff --git a/src/flamenco/types/fd_types.json b/src/flamenco/types/fd_types.json index 6674983fa35..c32f7c25bcb 100644 --- a/src/flamenco/types/fd_types.json +++ b/src/flamenco/types/fd_types.json @@ -173,7 +173,6 @@ "type": "struct", "fields": [ { "name": "lamports", "type": "ulong" }, - { "name": "rent_epoch", "type": "ulong" }, { "name": "owner", "type": "uchar[32]" }, { "name": "executable", "type": "bool" }, { "name": "padding", "type": "array", "element": "uchar", "length": 3 }