From 357418409750e25cf873536435dd26a5f9b2a8e3 Mon Sep 17 00:00:00 2001 From: nipatel-jump Date: Mon, 27 Oct 2025 16:57:32 +0000 Subject: [PATCH 1/2] 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 | 24 + src/app/ledger/main.c | 2 +- src/app/shared/fd_config.h | 6 + src/app/shared/fd_config_parse.c | 2 + src/disco/topo/fd_topo.h | 7 + src/discof/capture/Local.mk | 6 + src/discof/capture/fd_capture_ctx.c | 296 +++++++ src/discof/capture/fd_capture_ctx.h | 238 ++++++ src/discof/capture/fd_capture_tile.c | 486 ++++++++++++ .../capture/fd_capture_tile.seccomppolicy | 43 + .../generated/fd_capture_tile_seccomp.h | 103 +++ src/discof/exec/fd_exec_tile.c | 161 ++-- src/discof/replay/fd_exec.h | 4 + src/discof/replay/fd_replay_tile.c | 80 +- src/flamenco/capture/Local.mk | 11 +- src/flamenco/capture/fd_solcap_import.c | 346 --------- src/flamenco/capture/fd_solcap_proto.h | 378 +++++---- src/flamenco/capture/fd_solcap_reader.c | 191 ----- src/flamenco/capture/fd_solcap_reader.h | 147 ---- src/flamenco/capture/fd_solcap_writer.c | 735 +++--------------- src/flamenco/capture/fd_solcap_writer.h | 207 +---- 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 | 27 +- src/flamenco/runtime/tests/fd_sol_compat.c | 18 +- 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 - 42 files changed, 1756 insertions(+), 2838 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_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 311b7577701..0698e779583 100755 --- a/contrib/test/run_solcap_tests.sh +++ b/contrib/test/run_solcap_tests.sh @@ -82,8 +82,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 -$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 559a5be68d0..813f5a45e45 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" @@ -102,6 +102,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 955e2a19fb8..5e4b8c2fe2b 100644 --- a/src/app/firedancer-dev/main.c +++ b/src/app/firedancer-dev/main.c @@ -110,6 +110,7 @@ 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_vinyl; +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; @@ -172,6 +173,7 @@ fd_topo_run_tile_t * TILES[] = { &fd_tile_genesi, &fd_tile_ipecho, &fd_tile_vinyl, + &fd_tile_capture, NULL, }; diff --git a/src/app/firedancer/topology.c b/src/app/firedancer/topology.c index 374cf67acdd..d010dc5ea61 100644 --- a/src/app/firedancer/topology.c +++ b/src/app/firedancer/topology.c @@ -22,6 +22,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" #include "../../vinyl/io/fd_vinyl_io.h" /* FD_VINYL_IO_TYPE_* */ @@ -285,6 +286,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" ); @@ -560,6 +563,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. " @@ -1394,6 +1411,13 @@ fd_topo_configure_tile( fd_topo_tile_t * tile, tile->bundle.tls_cert_verify = !!config->tiles.bundle.tls_cert_verify; } else if( FD_UNLIKELY( !strcmp( tile->name, "vinyl" ) ) ) { + + } 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) ); + tile->capctx.recent_only = config->capture.recent_only; + tile->capctx.recent_slots_per_file = config->capture.recent_slots_per_file; tile->vinyl.vinyl_meta_map_obj_id = fd_pod_query_ulong( config->topo.props, "vinyl.meta_map", ULONG_MAX ); tile->vinyl.vinyl_meta_pool_obj_id = fd_pod_query_ulong( config->topo.props, "vinyl.meta_pool", ULONG_MAX ); 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 fd9f0747e2b..070a706a000 100644 --- a/src/app/shared/fd_config.h +++ b/src/app/shared/fd_config.h @@ -168,6 +168,10 @@ struct fd_configf { struct { ulong max_completed_shred_sets; } store; + + struct { + char path[ PATH_MAX ]; + } capctx; }; typedef struct fd_configf fd_configf_t; @@ -499,6 +503,8 @@ struct fd_config { ulong capture_start_slot; char dump_proto_dir[ PATH_MAX ]; char solcap_capture[ PATH_MAX ]; + int recent_only; + ulong recent_slots_per_file; int dump_elf_to_pb; int dump_syscall_to_pb; int dump_instr_to_pb; diff --git a/src/app/shared/fd_config_parse.c b/src/app/shared/fd_config_parse.c index 5bd860659e2..e6716d63f67 100644 --- a/src/app/shared/fd_config_parse.c +++ b/src/app/shared/fd_config_parse.c @@ -249,6 +249,8 @@ fd_config_extract_pod( uchar * pod, CFG_POP ( ulong, capture.capture_start_slot ); CFG_POP ( cstr, capture.solcap_capture ); + CFG_POP ( bool, capture.recent_only ); + CFG_POP ( ulong, capture.recent_slots_per_file ); CFG_POP ( cstr, capture.dump_proto_dir ); CFG_POP ( bool, capture.dump_elf_to_pb ); CFG_POP ( bool, capture.dump_syscall_to_pb ); diff --git a/src/disco/topo/fd_topo.h b/src/disco/topo/fd_topo.h index 4d1f6bce059..49c4736aaf3 100644 --- a/src/disco/topo/fd_topo.h +++ b/src/disco/topo/fd_topo.h @@ -616,6 +616,13 @@ struct fd_topo_tile { int io_type; /* FD_VINYL_IO_TYPE_* */ uint uring_depth; } vinyl; + + struct { + ulong capture_start_slot; + char solcap_capture[ PATH_MAX ]; + int recent_only; + ulong recent_slots_per_file; + } capctx; }; }; 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..66a0f5ec8b5 --- /dev/null +++ b/src/discof/capture/fd_capture_ctx.c @@ -0,0 +1,296 @@ +#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) ); + fd_memset(capture, 0, sizeof(fd_solcap_writer_t)); + + 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; + } + + 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_type.buf ) ) FD_LOG_ERR(( "NULL ctx (%p) or buf (%p)", (void *)ctx, (void *)ctx->capctx_type.buf )); + if( FD_UNLIKELY( !valid_slot_range( ctx, slot ) ) ) return; + + fd_capture_link_buf_t * buf = ctx->capctx_type.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, data + i * SOLCAP_WRITE_ACCOUNT_DATA_MTU, fragment_data_sz ); + + msg_sz = 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) */ + uint 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_type.buf ) ) FD_LOG_ERR(( "NULL ctx (%p) or buf (%p)", (void *)ctx, (void *)ctx->capctx_type.buf )); + if ( FD_UNLIKELY( !valid_slot_range( ctx, slot ) ) ) return; + + fd_capture_link_buf_t * buf = ctx->capctx_type.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 + }; + + uint 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..b3b53045037 --- /dev/null +++ b/src/discof/capture/fd_capture_ctx.h @@ -0,0 +1,238 @@ +#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 "../../tango/mcache/fd_mcache.h" +#include "../../tango/dcache/fd_dcache.h" +#include "../../tango/fd_tango_base.h" + +/* + +Nishk (TODO): Write more docs for capture context + +*/ + + +/* fd_capture_ctx_account_update_msg_t is the message sent from + exec tile to capture 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; + int fd; +}; +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_type; + + /* 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. +*/ + +/* The following capctx_buf_translate functions are wrappers used by + the runtime 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 ) { + FD_TEST( ctx && ctx->capture_link ); + 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 ) { + FD_TEST( ctx && ctx->capture_link ); + 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, +}; + +#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..e45d05f3589 --- /dev/null +++ b/src/discof/capture/fd_capture_tile.c @@ -0,0 +1,486 @@ +#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 +#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. + + The recent-only mode is a mode that allows for the capture of the + last N slots of the execution. This is useful for debugging runtime + on a live network. The mode is enabled by setting the recent_only + configuration to 1. The number of slots per file is set by the + recent_slots_per_file configuration. The default is 128 slots per + file. The files are named recent_0.solcap, recent_1.solcap,. The + files are rotated when the current file reaches the number of slots + per file. +*/ + +struct fd_capture_tile_ctx { + ulong tile_idx; + + ulong msg_idx; + ushort msg_set_sig; + ulong msg_set_slot; + uint block_len; + + /* Capture context management */ + fd_capture_ctx_t * capture_ctx; + fd_capture_link_t * capctx_type; + + int fd; /* Current file descriptor */ + + /* Recent-only rotating capture state */ + int recent_only; /* 1 if using 2-file flip-flop, 0 for single file */ + int recent_fds[2]; /* File descriptors for flip-flop and system calls */ + ulong recent_current_idx; /* Current file index (0 or 1) */ + ulong recent_file_start_slot; /* Slot number when current file was started (ULONG_MAX = uninitialized) */ + ulong recent_slots_per_file; /* Number of slots per file */ + + /* Incoming links for mcache/dcache processing */ + struct { + fd_wksp_t * mem; + ulong chunk0; + ulong wmark; + ulong mtu; + } in[32]; + + ulong in_cnt; + + /* Track which input link we're currently processing */ + ulong current_in_idx; /* ULONG_MAX means no active message */ + +}; + +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_topo_tile_t const * tile, + ulong out_cnt, + struct sock_filter * out ) { + void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id ); + fd_capture_tile_ctx_t const * ctx = (fd_capture_tile_ctx_t const *)scratch; + + uint solcap_fd_0 = ctx->recent_only ? (uint)ctx->recent_fds[0] : (uint)ctx->fd; + uint solcap_fd_1 = ctx->recent_only ? (uint)ctx->recent_fds[1] : (uint)ctx->fd; + + populate_sock_filter_policy_fd_capture_tile( out_cnt, + out, + (uint)fd_log_private_logfile_fd(), + solcap_fd_0, + solcap_fd_1 ); + return sock_filter_policy_fd_capture_tile_instr_cnt; +} + +static ulong +populate_allowed_fds( fd_topo_t const * topo, + fd_topo_tile_t const * tile, + ulong out_fds_cnt FD_PARAM_UNUSED, + int * out_fds ) { + void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id ); + fd_capture_tile_ctx_t const * ctx = (fd_capture_tile_ctx_t const *)scratch; + + 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( ctx->recent_only ) { + /* In recent_only mode, allow both flip-flop file descriptors */ + if( FD_LIKELY( -1!=ctx->recent_fds[0] ) ) + out_fds[ out_cnt++ ] = ctx->recent_fds[0]; + if( FD_LIKELY( -1!=ctx->recent_fds[1] ) ) + out_fds[ out_cnt++ ] = ctx->recent_fds[1]; + } else { + /* Traditional single file mode */ + if( FD_LIKELY( -1!=ctx->fd ) ) + out_fds[ out_cnt++ ] = ctx->fd; + } + + return out_cnt; +} + +/* fd_capctx_buf_process_msg processes a message fragment from the +shared buffer and writes it to the solcap file using the solcap + writer API. + + Returns block_len, the total PCAPNG Enhanced Packet Block (EPB) + length in bytes. This value represents the complete size of the + PCAPNG block including: + - EPB header (28 bytes)gu + - Internal chunk header + - Message payload data + - Padding (to align to 4-byte boundary) + - Block footer (4 bytes) + + For messages that span multiple fragments: + - Only the first fragment (SOM) returns a non-zero block_len, + representing the total calculated block size + - Continuation fragments return 0 as they don't write EPB headers + - The block_len from SOM is saved and used when writing the EPB + footer on the final fragment (EOM) */ +uint +fd_capctx_buf_process_msg(fd_capture_tile_ctx_t * ctx, + fd_solcap_buf_msg_t * msg_hdr, + char * actual_data, + ulong msg_sz ) { + uint block_len = 0; + FD_TEST(ctx->capture_ctx->capture != NULL); + switch ( msg_hdr->sig ) { + case SOLCAP_WRITE_ACCOUNT_HDR: { + fd_solcap_account_update_hdr_t * account_update = fd_type_pun( actual_data ); + block_len = fd_solcap_write_account_hdr( ctx->capture_ctx->capture, msg_hdr, account_update ); + break; + } case SOLCAP_WRITE_ACCOUNT_DATA: { + block_len = fd_solcap_write_account_data( ctx->capture_ctx->capture, actual_data, msg_sz ); + break; + } case SOLCAP_WRITE_BANK_PREIMAGE: { + fd_solcap_bank_preimage_t * bank_preimage = fd_type_pun( actual_data ); + block_len = fd_solcap_write_bank_preimage( ctx->capture_ctx->capture, msg_hdr, bank_preimage ); + break; + } default: + /* Unknown signal received in message processing */ + FD_LOG_ERR(( "Unknown signal received in message processing: sig=%lu", (ulong)msg_hdr->sig )); + break; + } + return block_len; +} + +/* returnable_frag processes incoming message fragments and handles + fragmented solcap messages using SOM (Start of Message) and EOM (End + of Message) control flags. + + Message Fragmentation: + ---------------------- + Solcap messages can be very large (up to 10MB for account data). To + avoid cache thrashing and fit within the ~4MB link size, large + messages are split into smaller fragments of at most 128KB + (SOLCAP_WRITE_ACCOUNT_DATA_MTU). + + SOM/EOM Control Flags: + ---------------------- + Each fragment has two control flags set in the frag metadata: + - SOM (Start of Message): Set on the first fragment of a message + - EOM (End of Message): Set on the last fragment of a message + + For a single-fragment message: + Single Fragment: SOM=1, EOM=1 + For a multi-fragment message: + First fragment: SOM=1, EOM=0 + Middle fragments: SOM=0, EOM=0 + Last fragment: SOM=0, EOM=1 + + Fragment Processing: + -------------------- + When SOM is set: + - The fragment begins with a fd_solcap_buf_msg_t header containing + the message type (sig), slot, and transaction index + - This header is parsed and saved in the context (msg_set_sig, + msg_set_slot) + - The input link is locked (current_in_idx) to ensure all fragments + of this message are processed sequentially from the same link + - The actual data follows the header in the fragment + - The PCAPNG block_len is calculated and saved (ctx->block_len) for + use when writing the footer on EOM + + When SOM is not set (continuation fragment): + - The entire fragment is data (no header) + - The previously saved message state is used + - No block_len is calculated (continuation data is appended) + + When EOM is set: + - The PCAPNG block footer is written using the block_len from SOM + - For bank preimage messages, the file is synced to disk + - All message state is reset (msg_idx, block_len, msg_set_sig, etc.) + - The input link is unlocked (current_in_idx = ULONG_MAX) to allow + processing the next message + + This design allows the capture tile to handle arbitrarily large + messages while maintaining efficient memory usage and cache locality. +*/ + +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( sz!=0UL && (chunkin[ in_idx ].chunk0 || chunk>ctx->in[ in_idx ].wmark || sz>ctx->in[ in_idx ].mtu ) ) ) + FD_LOG_ERR(( "chunk %lu %lu from in %lu corrupt, not in range [%lu,%lu]", chunk, sz, in_idx, ctx->in[ in_idx ].chunk0, ctx->in[ in_idx ].wmark )); + + + /* If we're processing a message from a different input, skip this + fragment */ + if( FD_UNLIKELY( ctx->current_in_idx != ULONG_MAX && ctx->current_in_idx != in_idx ) ) return 1; + + 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; + ulong actual_data_sz; + if( som ) { + msg_hdr = fd_type_pun( (void *)data ); + actual_data = (char *)(data + sizeof(fd_solcap_buf_msg_t)); + actual_data_sz = sz - sizeof(fd_solcap_buf_msg_t); + ctx->msg_set_slot = msg_hdr->slot; + ctx->msg_set_sig = SOLCAP_SIG_MAP(msg_hdr->sig); + ctx->current_in_idx = in_idx; /* Start tracking this input */ + /* Handle file rotation for recent_only mode */ + if( ctx->recent_only ) { + if( ctx->recent_file_start_slot == ULONG_MAX ) { + ctx->recent_file_start_slot = msg_hdr->slot; + } + else if( msg_hdr->slot >= ctx->recent_file_start_slot + ctx->recent_slots_per_file ) { + /* Check if we need to rotate (>= slots_per_file slots from start) */ + /* Flip-flop to the other file */ + ulong next_idx = 1UL - ctx->recent_current_idx; + int next_fd = ctx->recent_fds[next_idx]; + + /* The following is a series of checks to ensure the file is + synced and truncated correctly. This occurs via: + 1. Syncing the current file + 2. Syncing the next file + 3. Truncating the next file + 4. Resetting the file descriptor position to 0 + 5. Reinitializing the solcap writer with the new file descriptor + */ + FD_TEST( fsync( ctx->fd ) == 0 ); + FD_TEST( fsync( next_fd ) == 0 ); + FD_TEST( ftruncate( next_fd, 0L ) == 0 ); + FD_TEST( lseek( next_fd, 0L, SEEK_SET ) == 0L ); + + fd_solcap_writer_init( ctx->capture_ctx->capture, next_fd ); + ctx->recent_current_idx = next_idx; + ctx->recent_file_start_slot = msg_hdr->slot; + ctx->fd = next_fd; + } + } + } 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; + actual_data_sz = sz; + } + + uint block_len = fd_capctx_buf_process_msg( ctx, msg_hdr, actual_data, actual_data_sz ); + + 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) { + FD_TEST( fsync(ctx->fd) == 0 ); + } + + ctx->msg_idx = 0; + ctx->block_len = 0; + ctx->msg_set_sig = 0; + ctx->msg_set_slot = 0; + ctx->current_in_idx = ULONG_MAX; /* Reset to sentinel - ready for next message */ + } else { + ctx->msg_idx++; + } + + 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 ); + + ctx->recent_only = tile->capctx.recent_only; + ctx->recent_slots_per_file = tile->capctx.recent_slots_per_file ? tile->capctx.recent_slots_per_file : 128UL; + + struct stat path_stat; + int stat_result = stat( tile->capctx.solcap_capture, &path_stat ); + + if( ctx->recent_only ) { + /* recent_only=1: Ensure path is a directory, create if not exists */ + if( stat_result != 0 ) { + if( FD_UNLIKELY( mkdir(tile->capctx.solcap_capture, 0755) != 0 ) ) { + FD_LOG_ERR(( "solcap_recent_only=1 but could not create directory: %s (%i-%s)", + tile->capctx.solcap_capture, errno, strerror(errno) )); + } + } else if( FD_UNLIKELY( !S_ISDIR(path_stat.st_mode) ) ) { + FD_LOG_ERR(( "solcap_recent_only=1 but path is not a directory: %s", tile->capctx.solcap_capture )); + } + + ctx->recent_current_idx = 0; + ctx->recent_file_start_slot = 0UL; /* Will be set on first fragment */ + + for( ulong i = 0; i < 2; i++ ) { + char filepath[PATH_MAX]; + int ret = snprintf( filepath, PATH_MAX, "%s/recent_%lu.solcap", tile->capctx.solcap_capture, i ); + if( FD_UNLIKELY( ret<0 || ret>=PATH_MAX ) ) { + FD_LOG_ERR(( "snprintf failed or path too long for recent file %lu", i )); + } + + ctx->recent_fds[i] = open( filepath, O_RDWR | O_CREAT | O_TRUNC, 0644 ); + if( FD_UNLIKELY( ctx->recent_fds[i] == -1 ) ) { + FD_LOG_ERR(( "failed to open or create solcap recent file %s (%i-%s)", + filepath, errno, strerror(errno) )); + } + } + + ctx->fd = ctx->recent_fds[0]; + + } else { + /* recent_only=0: Validate that path is a file*/ + if( FD_UNLIKELY( stat_result == 0 && S_ISDIR(path_stat.st_mode) ) ) { + FD_LOG_ERR(( "solcap_recent_only=0 but path is a directory: %s (should be a file path)", tile->capctx.solcap_capture )); + } + + ctx->fd = open( tile->capctx.solcap_capture, O_RDWR | O_CREAT | O_TRUNC, 0644 ); + if( FD_UNLIKELY( ctx->fd == -1 ) ) { + FD_LOG_ERR(( "failed to open or create solcap capture file %s (%i-%s)", + tile->capctx.solcap_capture, 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->fd ); + + ctx->current_in_idx = ULONG_MAX; /* No active message initially */ + 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_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..5c8c3a04fc0 --- /dev/null +++ b/src/discof/capture/fd_capture_tile.seccomppolicy @@ -0,0 +1,43 @@ +# 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_0: The first solcap capture file (single file mode or recent_0.solcap) +# solcap_fd_1: The second solcap capture file (recent_1.solcap in recent_only mode) +unsigned int logfile_fd, uint solcap_fd_0, uint solcap_fd_1 + +# 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(s). +# +# 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_0) + (eq (arg 0) solcap_fd_1)) + +# lseek: used to reset file position to 0 after truncating rotating capture files +# +# arg 0 is the file descriptor to lseek. +lseek: (or (eq (arg 0) solcap_fd_0) + (eq (arg 0) solcap_fd_1)) + +# ftruncate: used to clear rotating capture files to 0 bytes before reuse +# +# arg 0 is the file descriptor to truncate. +ftruncate: (or (eq (arg 0) solcap_fd_0) + (eq (arg 0) solcap_fd_1)) + +# fsync: used to synchronize file state to disk +# +# logging: 'WARNING' and above fsync the logfile to disk immediately +# capture: solcap files are fsync'd during file rotation (both old and new files) +# and after each bank preimage write for consistency +# +# arg 0 is the file descriptor to fsync. +fsync: (or (eq (arg 0) logfile_fd) + (eq (arg 0) solcap_fd_0) + (eq (arg 0) solcap_fd_1)) 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..c2ec8138161 --- /dev/null +++ b/src/discof/capture/generated/fd_capture_tile_seccomp.h @@ -0,0 +1,103 @@ +/* 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 + +#if defined(__linux__) + +#include "../../../../src/util/fd_util_base.h" +#include +#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 = 32; + +static void populate_sock_filter_policy_fd_capture_tile( ulong out_cnt, struct sock_filter * out, unsigned int logfile_fd, uint solcap_fd_0, uint solcap_fd_1 ) { + FD_TEST( out_cnt >= 32 ); + struct sock_filter filter[32] = { + /* 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 */ 28 ), + /* 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 lseek based on expression */ + BPF_JUMP( BPF_JMP | BPF_JEQ | BPF_K, SYS_lseek, /* check_lseek */ 11, 0 ), + /* allow ftruncate based on expression */ + BPF_JUMP( BPF_JMP | BPF_JEQ | BPF_K, SYS_ftruncate, /* check_ftruncate */ 14, 0 ), + /* allow fsync based on expression */ + BPF_JUMP( BPF_JMP | BPF_JEQ | BPF_K, SYS_fsync, /* check_fsync */ 17, 0 ), + /* none of the syscalls matched */ + { BPF_JMP | BPF_JA, 0, 0, /* RET_KILL_PROCESS */ 22 }, +// 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 */ 21, /* 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 */ 19, /* 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_0, /* RET_ALLOW */ 17, /* lbl_3 */ 0 ), +// lbl_3: + /* load syscall argument 0 in accumulator */ + BPF_STMT( BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, args[0])), + BPF_JUMP( BPF_JMP | BPF_JEQ | BPF_K, solcap_fd_1, /* RET_ALLOW */ 15, /* RET_KILL_PROCESS */ 14 ), +// 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_0, /* RET_ALLOW */ 13, /* lbl_4 */ 0 ), +// lbl_4: + /* load syscall argument 0 in accumulator */ + BPF_STMT( BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, args[0])), + BPF_JUMP( BPF_JMP | BPF_JEQ | BPF_K, solcap_fd_1, /* RET_ALLOW */ 11, /* RET_KILL_PROCESS */ 10 ), +// check_ftruncate: + /* load syscall argument 0 in accumulator */ + BPF_STMT( BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, args[0])), + BPF_JUMP( BPF_JMP | BPF_JEQ | BPF_K, solcap_fd_0, /* RET_ALLOW */ 9, /* lbl_5 */ 0 ), +// lbl_5: + /* load syscall argument 0 in accumulator */ + BPF_STMT( BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, args[0])), + BPF_JUMP( BPF_JMP | BPF_JEQ | BPF_K, solcap_fd_1, /* RET_ALLOW */ 7, /* RET_KILL_PROCESS */ 6 ), +// 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 */ 5, /* lbl_6 */ 0 ), +// lbl_6: + /* 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_0, /* RET_ALLOW */ 3, /* lbl_7 */ 0 ), +// lbl_7: + /* load syscall argument 0 in accumulator */ + BPF_STMT( BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, args[0])), + BPF_JUMP( BPF_JMP | BPF_JEQ | BPF_K, solcap_fd_1, /* 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 /* defined(__linux__) */ + +#endif /* HEADER_fd_src_discof_capture_generated_fd_capture_tile_seccomp_h */ diff --git a/src/discof/exec/fd_exec_tile.c b/src/discof/exec/fd_exec_tile.c index 167e9f0602d..65af4cc52bf 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" @@ -48,8 +48,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 @@ -60,9 +59,6 @@ typedef struct fd_exec_tile_ctx { fd_txncache_t * txncache; - /* We need to ensure that all solcap updates have been published - before this message. */ - int pending_txn_finalized_msg; ulong txn_idx; ulong slot; ulong dispatch_time_comp; @@ -105,6 +101,26 @@ 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->err.is_committable; + msg->txn_exec->slot = ctx->slot; + msg->txn_exec->start_shred_idx = ctx->txn_ctx->txn.start_shred_idx; + msg->txn_exec->end_shred_idx = ctx->txn_ctx->txn.end_shred_idx; + if( FD_UNLIKELY( msg->txn_exec->err ) ) { + FD_LOG_WARNING(( "txn failed to execute, bad block detected err=%d", ctx->txn_ctx->err.txn_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, ctx->dispatch_time_comp, fd_frag_meta_ts_comp( fd_tickcount() ) ); + + 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 ); +} + static inline int returnable_frag( fd_exec_tile_ctx_t * ctx, ulong in_idx, @@ -129,6 +145,9 @@ returnable_frag( fd_exec_tile_ctx_t * ctx, fd_exec_txn_exec_msg_t * msg = fd_chunk_to_laddr( ctx->replay_in->mem, chunk ); fd_bank_t * bank = fd_banks_bank_query( ctx->banks, msg->bank_idx ); FD_TEST( bank ); + if( FD_UNLIKELY( ctx->capture_ctx )) { + ctx->capture_ctx->current_txn_idx = msg->capture_txn_idx; + } ctx->txn_ctx->err.exec_err = fd_runtime_prepare_and_execute_txn( bank, ctx->txn_ctx, &msg->txn, @@ -158,7 +177,7 @@ returnable_frag( fd_exec_tile_ctx_t * ctx, ctx->txn_idx = msg->txn_idx; ctx->dispatch_time_comp = tspub; ctx->slot = fd_bank_slot_get( bank ); - ctx->pending_txn_finalized_msg = 1; + publish_txn_finalized_msg( ctx, stem ); break; } @@ -192,6 +211,7 @@ 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) ); + 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 ); @@ -290,15 +310,42 @@ unprivileged_init( fd_topo_t * topo, ctx->txn_ctx->status_cache = ctx->txncache; ctx->txn_ctx->bundle.is_bundle = 0; + /********************************************************************/ /* 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; @@ -309,101 +356,10 @@ 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_type.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->err.is_committable; - msg->txn_exec->slot = ctx->slot; - msg->txn_exec->start_shred_idx = ctx->txn_ctx->txn.start_shred_idx; - msg->txn_exec->end_shred_idx = ctx->txn_ctx->txn.end_shred_idx; - if( FD_UNLIKELY( msg->txn_exec->err ) ) { - FD_LOG_WARNING(( "txn failed to execute, bad block detected err=%d", ctx->txn_ctx->err.txn_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, ctx->dispatch_time_comp, fd_frag_meta_ts_comp( fd_tickcount() ) ); - - 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 @@ -439,7 +395,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 12c461ccab7..80eb0a03819 100644 --- a/src/discof/replay/fd_exec.h +++ b/src/discof/replay/fd_exec.h @@ -21,6 +21,10 @@ struct fd_exec_txn_exec_msg { ulong bank_idx; ulong txn_idx; fd_txn_p_t txn; + + /* Used currently by solcap to maintain ordering of messages + this is a hack for v2.0, will change to using txn sigs eventually */ + ulong capture_txn_idx; }; 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 7979232f043..ab99de9dc22 100644 --- a/src/discof/replay/fd_replay_tile.c +++ b/src/discof/replay/fd_replay_tile.c @@ -327,8 +327,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. */ @@ -591,10 +592,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 ); @@ -732,11 +729,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 ); @@ -1029,7 +1023,6 @@ init_after_snapshot( fd_replay_tile_t * ctx ) { snapshot_slot = 0UL; } - if( FD_UNLIKELY( ctx->capture_ctx ) ) fd_solcap_writer_flush( ctx->capture_ctx->capture ); } static inline int @@ -1492,6 +1485,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, fd_frag_meta_ts_comp( fd_tickcount() ) ); exec_out->chunk = fd_dcache_compact_next( exec_out->chunk, sizeof(*exec_msg), exec_out->chunk0, exec_out->wmark ); break; @@ -1863,31 +1859,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; @@ -2369,16 +2344,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 ) ) ) { @@ -2481,6 +2447,40 @@ unprivileged_init( fd_topo_t * topo, ctx->gui_enabled = fd_topo_find_tile( topo, "gui", 0UL )!=ULONG_MAX; ctx->rpc_enabled = fd_topo_find_tile( topo, "rpc", 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_type.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 374adca00ef..4cbcbd80ebc 100644 --- a/src/flamenco/capture/Local.mk +++ b/src/flamenco/capture/Local.mk @@ -1,9 +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_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, \ + [SOLCAP_WRITE_ACCOUNT_DATA] = SOLCAP_WRITE_ACCOUNT_DATA, \ + [SOLCAP_STAKE_ACCOUNT_PAYOUT] = SOLCAP_STAKE_ACCOUNT_PAYOUT, \ + [SOLCAP_STAKE_REWARDS_BEGIN] = SOLCAP_STAKE_REWARDS_BEGIN, \ + [SOLCAP_WRITE_BANK_PREIMAGE] = SOLCAP_WRITE_BANK_PREIMAGE, \ + [SOLCAP_WRITE_STAKE_REWARD_EVENT] = SOLCAP_WRITE_STAKE_REWARD_EVENT, \ + [SOLCAP_WRITE_VOTE_ACCOUNT_PAYOUT] = SOLCAP_WRITE_VOTE_ACCOUNT_PAYOUT, \ +})[x]) + +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_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_buf_msg fd_solcap_buf_msg_t; + +/* 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_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 */ +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_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. */ +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; -#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 f7ee6b22cc4..234fa878e37 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 -#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 +#include +#include ulong fd_solcap_writer_align( void ) { @@ -145,551 +16,157 @@ fd_solcap_writer_footprint( void ) { return sizeof(fd_solcap_writer_t); } -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; -} - -void * -fd_solcap_writer_delete( fd_solcap_writer_t * writer ) { - - if( FD_UNLIKELY( !writer ) ) return NULL; - - writer->file = NULL; - return writer; -} - - fd_solcap_writer_t * fd_solcap_writer_init( fd_solcap_writer_t * writer, - void * file ) { + int fd ) { if( FD_UNLIKELY( !writer ) ) { FD_LOG_WARNING(( "NULL writer" )); return NULL; } - if( FD_UNLIKELY( !file ) ) { - FD_LOG_WARNING(( "NULL file" )); + if( FD_UNLIKELY( fd < 0 ) ) { + FD_LOG_WARNING(( "invalid file descriptor" )); return NULL; } - /* Leave space for file headers */ + writer->fd = fd; - long pos = ftell( file ); - if( FD_UNLIKELY( pos<0L ) ) { - FD_LOG_WARNING(( "ftell failed (%d-%s)", errno, strerror( errno ) )); + off_t pos = lseek( fd, 0L, SEEK_CUR ); + if( FD_UNLIKELY( pos < 0L ) ) { + FD_LOG_WARNING(( "lseek 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; + writer->stream_goff = (ulong)pos; - 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 ) ) { - FD_LOG_WARNING(( "ftell failed (%d-%s)", errno, strerror( errno ) )); - return NULL; - } - - /* 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, + FD_TEST(sizeof(fd_solcap_file_hdr_t) == write(fd, &file_hdr, sizeof(fd_solcap_file_hdr_t))); + + 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; - } + FD_TEST(sizeof(fd_solcap_chunk_idb_hdr_t) == write(fd, &idb_hdr, sizeof(fd_solcap_chunk_idb_hdr_t))); 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. */ +uint +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 = writer->fd; - if( writer->account_idx == 0UL ) return 0; + ulong data_sz = account_update->data_sz; - /* Skip if table was overflowed. */ + uint packet_len = (uint)(sizeof(fd_solcap_chunk_int_hdr_t) + + sizeof(fd_solcap_account_update_hdr_t) + + data_sz); - /* 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; - } - - /* 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; + uint unaligned_block_len = (uint)(sizeof(fd_solcap_chunk_epb_hdr_t) + + packet_len + + sizeof(fd_solcap_chunk_ftr_t)); - /* Write account table (at beginning of chunk) */ + uint block_len = (uint)((unaligned_block_len + 3UL) & ~3UL); - ulong account_table_coff = sizeof(fd_solcap_chunk_t); - ulong account_table_cnt = writer->account_idx; - - FWRITE_BAIL( writer->accounts, - sizeof(fd_solcap_account_tbl_t), - account_table_cnt, - writer->file ); - - /* Serialize account chunk metadata */ - - 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 }; + FD_TEST(sizeof(fd_solcap_chunk_epb_hdr_t) == write(fd, &epb_hdr, sizeof(fd_solcap_chunk_epb_hdr_t))); - 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 = (uint)msg_hdr->slot, + .txn_idx = msg_hdr->txn_idx }; + FD_TEST(sizeof(fd_solcap_chunk_int_hdr_t) == write(fd, &int_hdr, sizeof(fd_solcap_chunk_int_hdr_t))); - /* 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 */ + FD_TEST(sizeof(fd_solcap_account_update_hdr_t) == write(fd, account_update, sizeof(fd_solcap_account_update_hdr_t))); - FSEEK_BAIL( writer->file, (long)chunk_end_goff, SEEK_SET ); - - /* 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; - +uint +fd_solcap_write_account_data( fd_solcap_writer_t * writer, + void const * data, + ulong data_sz ) { + int fd = writer->fd; + FD_TEST(data_sz == (ulong)write(fd, data, data_sz)); return 0; } -void -fd_solcap_writer_set_slot( fd_solcap_writer_t * writer, - ulong slot ) { - if( FD_LIKELY( !writer ) ) return; +uint +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 = writer->fd; - /* Discard account table buffer */ - writer->account_table_goff = 0UL; - writer->account_idx = 0UL; - writer->slot = slot; -} + uint packet_len = (uint)(sizeof(fd_solcap_chunk_int_hdr_t) + + sizeof(fd_solcap_bank_preimage_t)); -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 ) { - - 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 ); -} + uint unaligned_block_len = (uint)(sizeof(fd_solcap_chunk_epb_hdr_t) + + packet_len + + sizeof(fd_solcap_chunk_ftr_t)); + uint block_len = (uint)((unaligned_block_len + 3UL) & ~3UL); -int -fd_solcap_write_bank_preimage2( fd_solcap_writer_t * writer, - fd_solcap_BankPreimage * preimage_pb ) { + 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 + }; + FD_TEST(sizeof(fd_solcap_chunk_epb_hdr_t) == write(fd, &epb_hdr, sizeof(fd_solcap_chunk_epb_hdr_t))); - if( FD_LIKELY( !writer ) ) return 0; + fd_solcap_chunk_int_hdr_t int_hdr = { + .block_type = SOLCAP_WRITE_BANK_PREIMAGE, + .slot = (uint)msg_hdr->slot, + .txn_idx = msg_hdr->txn_idx + }; + FD_TEST(sizeof(fd_solcap_chunk_int_hdr_t) == write(fd, &int_hdr, sizeof(fd_solcap_chunk_int_hdr_t))); - int err = fd_solcap_flush_account_table( writer ); - if( FD_UNLIKELY( err!=0 ) ) return err; + FD_TEST(sizeof(fd_solcap_bank_preimage_t) == write(fd, bank_preimage, sizeof(fd_solcap_bank_preimage_t))); - /* Leave space for header */ - - ulong chunk_goff = FTELL_BAIL( writer->file ); - FSKIP_BAIL( writer->file, sizeof(fd_solcap_chunk_t) ); - - /* Fixup predefined entries */ - - 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 */ - - 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 ); +uint +fd_solcap_write_ftr( fd_solcap_writer_t * writer, + uint block_len_redundant ) { + int fd = writer->fd; + off_t current_pos = lseek( fd, 0L, SEEK_CUR ); + FD_TEST( current_pos >= 0L ); + uint padding_needed = (-(uint)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, - fd_w_u128_t total_points -) { - fd_solcap_StakeRewardEpoch epoch_pb = { - .payout_epoch = payout_epoch, - .reward_epoch = reward_epoch, - .inflation_lamports = inflation_lamports, - }; - FD_STORE( fd_w_u128_t, 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}; + FD_TEST(padding_needed == write(fd, zeros, padding_needed)); } - 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 ); + FD_TEST(sizeof(fd_solcap_chunk_ftr_t) == write(fd, &ftr, sizeof(fd_solcap_chunk_ftr_t))); return 0; } diff --git a/src/flamenco/capture/fd_solcap_writer.h b/src/flamenco/capture/fd_solcap_writer.h index e494676e001..33a8820cff3 100644 --- a/src/flamenco/capture/fd_solcap_writer.h +++ b/src/flamenco/capture/fd_solcap_writer.h @@ -2,24 +2,27 @@ #define HEADER_fd_src_flamenco_capture_fd_solcap_writer_h #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_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 { + int fd; + ulong stream_goff; +} fd_solcap_writer_t; ulong fd_solcap_writer_align( void ); @@ -27,166 +30,28 @@ 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_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. */ - -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 ); - -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? */ - -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 */ - -int -fd_solcap_writer_stake_rewards_begin( - fd_solcap_writer_t * writer, - ulong payout_epoch, - ulong reward_epoch, - ulong inflation_lamports, - fd_w_u128_t 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 -); - -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 ); +fd_solcap_writer_init( fd_solcap_writer_t * writer, + int fd ); + +uint +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 ); + +uint +fd_solcap_write_account_data( fd_solcap_writer_t * writer, + void const * data, + ulong data_sz ); + +uint +fd_solcap_write_bank_preimage( fd_solcap_writer_t * writer, + fd_solcap_buf_msg_t * msg_hdr, + fd_solcap_bank_preimage_t * bank_preimage ); + +uint +fd_solcap_write_ftr( fd_solcap_writer_t * writer, + uint 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 67377e10900..bf22d23e408 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" @@ -538,7 +538,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, @@ -611,16 +611,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 ); @@ -660,15 +650,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, - (fd_w_u128_t){ .ud=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( @@ -823,14 +804,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 ); @@ -897,7 +870,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 ); @@ -916,18 +888,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 fbe08c7ffd0..c8106aaa81e 100644 --- a/src/flamenco/runtime/context/Local.mk +++ b/src/flamenco/runtime/context/Local.mk @@ -3,6 +3,3 @@ $(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) 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 7217975aebf..bd2780ccfee 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" @@ -95,19 +95,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 5d9e283ffe3..37d5d73164f 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" @@ -542,15 +542,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 f91162e1d88..62f4a3d6b6b 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 a8e33304b11..199b29987e2 100644 --- a/src/flamenco/runtime/fd_txn_account.c +++ b/src/flamenco/runtime/fd_txn_account.c @@ -490,7 +490,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 1cc677211e3..a74b67435ee 100644 --- a/src/flamenco/runtime/tests/Local.mk +++ b/src/flamenco/runtime/tests/Local.mk @@ -17,11 +17,11 @@ $(call add-objs,generated/context.pb generated/elf.pb generated/invoke.pb genera $(call add-hdrs,flatbuffers/generated/elf_builder.h,flatbuffers/generated/elf_reader.h) 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) $(FLATCC_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) $(FLATCC_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) $(FLATCC_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) $(FLATCC_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) $(FLATCC_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) $(FLATCC_LIBS)) endif run-runtime-backtest: $(OBJDIR)/bin/fd_ledger $(OBJDIR)/bin/firedancer-dev diff --git a/src/flamenco/runtime/tests/fd_block_harness.c b/src/flamenco/runtime/tests/fd_block_harness.c index d371bb1a7bd..0735ec6062b 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 { @@ -495,15 +497,29 @@ 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; + + int solcap_fd = (int)(ulong)runner->solcap_file; + capture_link_file->fd = solcap_fd; + capture_ctx->capture_link = &capture_link_file->base; + capture_ctx->capctx_type.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, solcap_fd ); } fd_rewards_recalculate_partitioned_rewards( runner->banks, runner->bank, runner->accdb->funk, xid, runner->runtime_stack, capture_ctx ); @@ -671,7 +687,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_hash_t poh; fd_txn_p_t * txn_ptrs = fd_solfuzz_pb_block_ctx_create( runner, input, &txn_cnt, &poh ); diff --git a/src/flamenco/runtime/tests/fd_sol_compat.c b/src/flamenco/runtime/tests/fd_sol_compat.c index c8c3553dc4a..6ebfc38952f 100644 --- a/src/flamenco/runtime/tests/fd_sol_compat.c +++ b/src/flamenco/runtime/tests/fd_sol_compat.c @@ -20,7 +20,9 @@ #include #include +#include #include +#include static fd_wksp_t * wksp = NULL; static fd_solfuzz_runner_t * runner = NULL; @@ -35,16 +37,17 @@ sol_compat_setup_runner( fd_solfuzz_runner_options_t const * options ) { char const * solcap_path = getenv( "FD_SOLCAP" ); if( solcap_path ) { - runner->solcap_file = fopen( solcap_path, "w" ); - if( FD_UNLIKELY( !runner->solcap_file ) ) { - FD_LOG_ERR(( "fopen($FD_SOLCAP=%s) failed (%i-%s)", solcap_path, errno, fd_io_strerror( errno ) )); + int fd = open( solcap_path, O_WRONLY | O_CREAT | O_TRUNC, 0644 ); + if( FD_UNLIKELY( fd == -1 ) ) { + FD_LOG_ERR(( "open($FD_SOLCAP=%s) failed (%i-%s)", solcap_path, errno, fd_io_strerror( errno ) )); } + runner->solcap_file = (void *)(ulong)fd; FD_LOG_NOTICE(( "Logging to solcap file %s", solcap_path )); void * solcap_mem = fd_wksp_alloc_laddr( runner->wksp, fd_solcap_writer_align(), fd_solcap_writer_footprint(), 1UL ); runner->solcap = fd_solcap_writer_new( solcap_mem ); FD_TEST( runner->solcap ); - FD_TEST( fd_solcap_writer_init( solcap_mem, runner->solcap_file ) ); + FD_TEST( fd_solcap_writer_init( solcap_mem, fd ) ); } return runner; @@ -54,11 +57,12 @@ 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 ); - runner->solcap_file = NULL; + if( runner->solcap_file ) { + close( (int)(ulong)runner->solcap_file ); + runner->solcap_file = NULL; + } } fd_solfuzz_runner_delete( runner ); } diff --git a/src/flamenco/runtime/tests/test_dump_block.c b/src/flamenco/runtime/tests/test_dump_block.c index 31315251f7d..429ff90958c 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 5664e462e8b..481ba4fdbe3 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 d7cfa59659b..e56a5922912 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 } From 8065fae5f60f0857803f55b4259941255bf5b1b9 Mon Sep 17 00:00:00 2001 From: Kunal Bhargava Date: Thu, 20 Nov 2025 22:55:24 +0000 Subject: [PATCH 2/2] fix build --- .github/workflows/tests.yml | 7 -- config/everything.mk | 5 -- contrib/test/run_solcap_tests.sh | 86 ---------------------- src/app/firedancer/topology.c | 2 +- src/discof/capture/fd_capture_tile.c | 36 ++++----- src/discof/replay/fd_exec.h | 2 +- src/flamenco/capture/fd_solcap_proto.h | 2 +- src/flamenco/runtime/tests/fd_sol_compat.c | 6 +- 8 files changed, 24 insertions(+), 122 deletions(-) delete mode 100755 contrib/test/run_solcap_tests.sh diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 3de04b37d73..4b9076ecfad 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -141,13 +141,6 @@ jobs: source /opt/${{ matrix.compiler }}/${{ matrix.compiler }}-${{ matrix.compiler-version }}/activate make run-test-vectors - - name: run solcap tests - if: ${{ matrix.run-unit-tests && matrix.test-case == 'native' }} - run: | - sudo prlimit --pid $$ --memlock=-1:-1 - source /opt/${{ matrix.compiler }}/${{ matrix.compiler }}-${{ matrix.compiler-version }}/activate - DUMP=../dump make run-solcap-tests - - name: run integration tests if: ${{ matrix.run-integration-tests }} run: | diff --git a/config/everything.mk b/config/everything.mk index 024f439a4dd..4670ac2476b 100644 --- a/config/everything.mk +++ b/config/everything.mk @@ -463,11 +463,6 @@ LLVM_PROFILE_FILE="$(OBJDIR)/cov/raw/test_vectors-%p.profraw" \ LOG_PATH="$(OBJDIR)/log/fd-test-vectors-report" \ contrib/test/run_test_vectors.sh -run-solcap-tests: bin unit-test - OBJDIR=$(OBJDIR) \ - MACHINE=$(MACHINE) \ - contrib/test/run_solcap_tests.sh - seccomp-policies: $(FIND) . -name '*.seccomppolicy' -exec $(PYTHON) contrib/codegen/generate_filters.py {} \; diff --git a/contrib/test/run_solcap_tests.sh b/contrib/test/run_solcap_tests.sh deleted file mode 100755 index 0698e779583..00000000000 --- a/contrib/test/run_solcap_tests.sh +++ /dev/null @@ -1,86 +0,0 @@ -#!/bin/bash -set -xeou pipefail - -source contrib/test/ledger_common.sh - -DUMP=${DUMP:="./dump"} -OBJDIR=${OBJDIR:-build/native/gcc} -echo $OBJDIR - -LEDGER="devnet-398736132-solcap" -REDOWNLOAD=1 - -while [[ $# -gt 0 ]]; do -case $1 in - -nr|--no-redownload) - REDOWNLOAD=0 - shift - ;; - -*|--*) - echo "unknown option $1" - exit 1 - ;; - *) - POSITION_ARGS+=("$1") - shift - ;; - esac -done - -download_and_extract_ledger() { - echo "Downloading gs://firedancer-ci-resources/$LEDGER.tar.gz" - if [ "`gcloud auth list |& grep firedancer-scratch | wc -l`" == "0" ]; then - if [ "`gcloud auth list |& grep firedancer-ci | wc -l`" == "0" ]; then - if [ -f /etc/firedancer-scratch-bucket-key.json ]; then - gcloud auth activate-service-account --key-file /etc/firedancer-scratch-bucket-key.json - fi - if [ -f /etc/firedancer-ci-78fff3e07c8b.json ]; then - gcloud auth activate-service-account --key-file /etc/firedancer-ci-78fff3e07c8b.json - fi - fi - fi - gcloud storage cat gs://firedancer-ci-resources/$LEDGER.tar.gz | tee $DUMP/$LEDGER.tar.gz | tar zxf - -C $DUMP -} - -if [[ ! -e $DUMP/$LEDGER && SKIP_INGEST -eq 0 ]]; then - download_and_extract_ledger - create_checksum -else - check_ledger_checksum_and_redownload -fi - -rm -rf $DUMP/$LEDGER/devnet-398736132_current.toml -rm -rf $DUMP/$LEDGER/fd.solcap - -cp $DUMP/$LEDGER/devnet-398736132.toml $DUMP/$LEDGER/devnet-398736132_current.toml - -export ledger_dir=$(realpath $DUMP/$LEDGER) -sed -i "s#{ledger_dir}#${ledger_dir}#g" "$DUMP/$LEDGER/devnet-398736132_current.toml" -sed -i "s/max_total_banks = [0-9]*/max_total_banks = 32/g" "$DUMP/$LEDGER/devnet-398736132_current.toml" -sed -i -z "s/\[snapshots\].*\[layout\]/[layout]/" "$DUMP/$LEDGER/devnet-398736132_current.toml" -sed -i "/writer_tile_count/d" "$DUMP/$LEDGER/devnet-398736132_current.toml" -sed -i "/lock_pages/d" "$DUMP/$LEDGER/devnet-398736132_current.toml" -sed -i "/heap_size_gib/d" "$DUMP/$LEDGER/devnet-398736132_current.toml" -sed -i "/max_total_banks/d" "$DUMP/$LEDGER/devnet-398736132_current.toml" -sed -i "/max_fork_width/d" "$DUMP/$LEDGER/devnet-398736132_current.toml" -sed -i "/cluster_version/d" "$DUMP/$LEDGER/devnet-398736132_current.toml" - -echo " -[gossip] - entrypoints = [ \"0.0.0.0:1\" ]" >> "$DUMP/$LEDGER/devnet-398736132_current.toml" - -echo " -[snapshots] - incremental_snapshots = false - [snapshots.sources] - servers = [] - [snapshots.sources.gossip] - allow_any = false - allow_list = []" >> "$DUMP/$LEDGER/devnet-398736132_current.toml" - -$OBJDIR/bin/firedancer-dev configure init all --config $DUMP/$LEDGER/devnet-398736132_current.toml -$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 - -# check that the ledger is not corrupted after a run -check_ledger_checksum diff --git a/src/app/firedancer/topology.c b/src/app/firedancer/topology.c index d010dc5ea61..5048151c536 100644 --- a/src/app/firedancer/topology.c +++ b/src/app/firedancer/topology.c @@ -1411,7 +1411,7 @@ fd_topo_configure_tile( fd_topo_tile_t * tile, tile->bundle.tls_cert_verify = !!config->tiles.bundle.tls_cert_verify; } else if( FD_UNLIKELY( !strcmp( tile->name, "vinyl" ) ) ) { - + } else if( FD_UNLIKELY( !strcmp( tile->name, "captur" ) ) ) { tile->capctx.capture_start_slot = config->capture.capture_start_slot; diff --git a/src/discof/capture/fd_capture_tile.c b/src/discof/capture/fd_capture_tile.c index e45d05f3589..de88825f47a 100644 --- a/src/discof/capture/fd_capture_tile.c +++ b/src/discof/capture/fd_capture_tile.c @@ -60,7 +60,7 @@ recent_slots_per_file configuration. The default is 128 slots per file. The files are named recent_0.solcap, recent_1.solcap,. The files are rotated when the current file reaches the number of slots - per file. + per file. */ struct fd_capture_tile_ctx { @@ -122,10 +122,10 @@ populate_allowed_seccomp( fd_topo_t const * topo, struct sock_filter * out ) { void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id ); fd_capture_tile_ctx_t const * ctx = (fd_capture_tile_ctx_t const *)scratch; - + uint solcap_fd_0 = ctx->recent_only ? (uint)ctx->recent_fds[0] : (uint)ctx->fd; uint solcap_fd_1 = ctx->recent_only ? (uint)ctx->recent_fds[1] : (uint)ctx->fd; - + populate_sock_filter_policy_fd_capture_tile( out_cnt, out, (uint)fd_log_private_logfile_fd(), @@ -141,13 +141,13 @@ populate_allowed_fds( fd_topo_t const * topo, int * out_fds ) { void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id ); fd_capture_tile_ctx_t const * ctx = (fd_capture_tile_ctx_t const *)scratch; - + 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( ctx->recent_only ) { /* In recent_only mode, allow both flip-flop file descriptors */ if( FD_LIKELY( -1!=ctx->recent_fds[0] ) ) @@ -226,7 +226,7 @@ fd_capctx_buf_process_msg(fd_capture_tile_ctx_t * ctx, - SOM (Start of Message): Set on the first fragment of a message - EOM (End of Message): Set on the last fragment of a message - For a single-fragment message: + For a single-fragment message: Single Fragment: SOM=1, EOM=1 For a multi-fragment message: First fragment: SOM=1, EOM=0 @@ -310,8 +310,8 @@ returnable_frag( fd_capture_tile_ctx_t * ctx, int next_fd = ctx->recent_fds[next_idx]; /* The following is a series of checks to ensure the file is - synced and truncated correctly. This occurs via: - 1. Syncing the current file + synced and truncated correctly. This occurs via: + 1. Syncing the current file 2. Syncing the next file 3. Truncating the next file 4. Resetting the file descriptor position to 0 @@ -321,7 +321,7 @@ returnable_frag( fd_capture_tile_ctx_t * ctx, FD_TEST( fsync( next_fd ) == 0 ); FD_TEST( ftruncate( next_fd, 0L ) == 0 ); FD_TEST( lseek( next_fd, 0L, SEEK_SET ) == 0L ); - + fd_solcap_writer_init( ctx->capture_ctx->capture, next_fd ); ctx->recent_current_idx = next_idx; ctx->recent_file_start_slot = msg_hdr->slot; @@ -386,41 +386,41 @@ privileged_init( fd_topo_t * topo, /* recent_only=1: Ensure path is a directory, create if not exists */ if( stat_result != 0 ) { if( FD_UNLIKELY( mkdir(tile->capctx.solcap_capture, 0755) != 0 ) ) { - FD_LOG_ERR(( "solcap_recent_only=1 but could not create directory: %s (%i-%s)", + FD_LOG_ERR(( "solcap_recent_only=1 but could not create directory: %s (%i-%s)", tile->capctx.solcap_capture, errno, strerror(errno) )); } } else if( FD_UNLIKELY( !S_ISDIR(path_stat.st_mode) ) ) { FD_LOG_ERR(( "solcap_recent_only=1 but path is not a directory: %s", tile->capctx.solcap_capture )); } - + ctx->recent_current_idx = 0; ctx->recent_file_start_slot = 0UL; /* Will be set on first fragment */ - + for( ulong i = 0; i < 2; i++ ) { char filepath[PATH_MAX]; int ret = snprintf( filepath, PATH_MAX, "%s/recent_%lu.solcap", tile->capctx.solcap_capture, i ); if( FD_UNLIKELY( ret<0 || ret>=PATH_MAX ) ) { FD_LOG_ERR(( "snprintf failed or path too long for recent file %lu", i )); } - + ctx->recent_fds[i] = open( filepath, O_RDWR | O_CREAT | O_TRUNC, 0644 ); if( FD_UNLIKELY( ctx->recent_fds[i] == -1 ) ) { - FD_LOG_ERR(( "failed to open or create solcap recent file %s (%i-%s)", + FD_LOG_ERR(( "failed to open or create solcap recent file %s (%i-%s)", filepath, errno, strerror(errno) )); } } - + ctx->fd = ctx->recent_fds[0]; - + } else { /* recent_only=0: Validate that path is a file*/ if( FD_UNLIKELY( stat_result == 0 && S_ISDIR(path_stat.st_mode) ) ) { FD_LOG_ERR(( "solcap_recent_only=0 but path is a directory: %s (should be a file path)", tile->capctx.solcap_capture )); } - + ctx->fd = open( tile->capctx.solcap_capture, O_RDWR | O_CREAT | O_TRUNC, 0644 ); if( FD_UNLIKELY( ctx->fd == -1 ) ) { - FD_LOG_ERR(( "failed to open or create solcap capture file %s (%i-%s)", + FD_LOG_ERR(( "failed to open or create solcap capture file %s (%i-%s)", tile->capctx.solcap_capture, errno, strerror(errno) )); } } diff --git a/src/discof/replay/fd_exec.h b/src/discof/replay/fd_exec.h index 80eb0a03819..110aca0dac3 100644 --- a/src/discof/replay/fd_exec.h +++ b/src/discof/replay/fd_exec.h @@ -22,7 +22,7 @@ struct fd_exec_txn_exec_msg { ulong txn_idx; fd_txn_p_t txn; - /* Used currently by solcap to maintain ordering of messages + /* Used currently by solcap to maintain ordering of messages this is a hack for v2.0, will change to using txn sigs eventually */ ulong capture_txn_idx; }; diff --git a/src/flamenco/capture/fd_solcap_proto.h b/src/flamenco/capture/fd_solcap_proto.h index a9efee0bf7b..214f9dcbcd0 100644 --- a/src/flamenco/capture/fd_solcap_proto.h +++ b/src/flamenco/capture/fd_solcap_proto.h @@ -191,7 +191,7 @@ struct fd_solcap_buf_msg_stake_rewards_begin { ulong payout_epoch; ulong reward_epoch; ulong inflation_lamports; - uint128 total_points; + ulong total_points; }; typedef struct fd_solcap_buf_msg_stake_rewards_begin fd_solcap_buf_msg_stake_rewards_begin_t; diff --git a/src/flamenco/runtime/tests/fd_sol_compat.c b/src/flamenco/runtime/tests/fd_sol_compat.c index 6ebfc38952f..996ebabb576 100644 --- a/src/flamenco/runtime/tests/fd_sol_compat.c +++ b/src/flamenco/runtime/tests/fd_sol_compat.c @@ -45,9 +45,9 @@ sol_compat_setup_runner( fd_solfuzz_runner_options_t const * options ) { FD_LOG_NOTICE(( "Logging to solcap file %s", solcap_path )); void * solcap_mem = fd_wksp_alloc_laddr( runner->wksp, fd_solcap_writer_align(), fd_solcap_writer_footprint(), 1UL ); - runner->solcap = fd_solcap_writer_new( solcap_mem ); + runner->solcap = (fd_solcap_writer_t *)solcap_mem; FD_TEST( runner->solcap ); - FD_TEST( fd_solcap_writer_init( solcap_mem, fd ) ); + FD_TEST( fd_solcap_writer_init( runner->solcap, fd ) ); } return runner; @@ -57,7 +57,7 @@ static void sol_compat_cleanup_runner( fd_solfuzz_runner_t * runner ) { /* Cleanup test runner */ if( runner->solcap ) { - fd_wksp_free_laddr( fd_solcap_writer_delete( runner->solcap ) ); + fd_wksp_free_laddr( runner->solcap ); runner->solcap = NULL; if( runner->solcap_file ) { close( (int)(ulong)runner->solcap_file );