From 9efaf6493d2b6331c49a727d95fc1bb309fb26d7 Mon Sep 17 00:00:00 2001 From: Richard Patel Date: Wed, 21 Oct 2026 12:21:46 +0000 Subject: [PATCH 01/13] backtest: remove Tower BFT support --- .github/workflows/backtest.yml | 2 +- .github/workflows/benchmark.yml | 4 +- .github/workflows/on_pull_request.yml | 2 +- contrib/ci/bench/bench.sh | 7 +- contrib/ci/bench/benchmark_comment.py | 3 +- src/app/firedancer-dev/commands/backtest.c | 32 +-- src/discof/backtest/fd_backtest_tile.c | 172 +++------------ .../runtime/tests/run_backtest_all.sh | 201 ++---------------- src/flamenco/runtime/tests/run_backtest_ci.sh | 72 ++----- .../runtime/tests/run_ledger_backtest.sh | 7 - 10 files changed, 86 insertions(+), 416 deletions(-) diff --git a/.github/workflows/backtest.yml b/.github/workflows/backtest.yml index cd9066e7a02..5d9141c2a6e 100644 --- a/.github/workflows/backtest.yml +++ b/.github/workflows/backtest.yml @@ -79,4 +79,4 @@ jobs: if: always() run: | sudo killall firedancer-dev || true - sudo $OBJDIR/bin/firedancer-dev configure fini all --config ../dump/mainnet-308392063-v2.3.0_backtest.toml || true + sudo $OBJDIR/bin/firedancer-dev configure fini all --config ../dump/genesis-vat-alpenglow-agave-v4.4-968b24ce_backtest.toml || true diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 443a24c003d..b8de07da9e0 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -11,13 +11,13 @@ on: description: 'GitHub token for posting comments' snapshot-ledger: type: string - default: testnet-438272759-snapload + default: testnet-455087014-snapload description: 'load-only testnet ledger (genesis.bin + full snapshot) for the snapshot row' workflow_dispatch: inputs: snapshot-ledger: type: string - default: testnet-438272759-snapload + default: testnet-455087014-snapload permissions: contents: read diff --git a/.github/workflows/on_pull_request.yml b/.github/workflows/on_pull_request.yml index ce136bc6825..af53da7b4ac 100644 --- a/.github/workflows/on_pull_request.yml +++ b/.github/workflows/on_pull_request.yml @@ -26,7 +26,7 @@ jobs: uses: ./.github/workflows/benchmark.yml with: github-token: ${{ github.token }} - snapshot-ledger: testnet-438272759-snapload + snapshot-ledger: testnet-455087014-snapload secrets: inherit build_checks_1: if: github.event.pull_request.draft == false diff --git a/contrib/ci/bench/bench.sh b/contrib/ci/bench/bench.sh index 665f8abe5dd..69ee155bb8a 100755 --- a/contrib/ci/bench/bench.sh +++ b/contrib/ci/bench/bench.sh @@ -59,11 +59,10 @@ case $what in cp contrib/ci/bench/bench.toml "$BENCH_DIR/$side/" # each side runs the config its checkout knows size -A -d "$bin/firedancer" > "$out.size" "$bin/firedancer-dev" mem --mainnet --json > "$out.mem.mainnet.json" - "$bin/firedancer-dev" mem --mainnet --alpenglow --json > "$out.mem.ag.mainnet.json" "$bin/firedancer-dev" mem --testnet --json > "$out.mem.testnet.json" ;; - replay) backtest "${BENCH_LEDGER:-mainnet-424669000-perf-ledger-v4.2.0-beta.1-vat}" \ - -e "${BENCH_END_SLOT:-424669200}" -m 4000000 --cache-size-gib 12 --partition-size-gib 4 ;; + replay) backtest "${BENCH_LEDGER:-mainnet-459662777-perf-ledger-v4.5.0-alpha.2-alpenglow}" \ + -e "${BENCH_END_SLOT:-459662997}" -m 4000000 --cache-size-gib 12 --partition-size-gib 4 ;; snapshot) # load-only ledger: no shreds if ! mountpoint -q $RAMDB; then # brd allocates on first write: one untimed load touches the pages both sides reuse { findmnt -rn -o TARGET -S /dev/ram0 || true; } | xargs -r sudo umount # findmnt exits 1 when nothing is mounted @@ -82,7 +81,7 @@ case $what in quiesce inplace rm -f "$out.log" - sudo $BINDIR/bin/firedancer-dev bench --alpenglow --no-watch --duration 10 --config "$out.toml" \ + sudo $BINDIR/bin/firedancer-dev bench --no-watch --duration 10 --config "$out.toml" \ --log-path "$out.log" > /dev/null 2>&1 ;; *) echo "bench.sh: unknown measurement $what" >&2; exit 1 ;; diff --git a/contrib/ci/bench/benchmark_comment.py b/contrib/ci/bench/benchmark_comment.py index 9e78908693c..6e697fd437e 100755 --- a/contrib/ci/bench/benchmark_comment.py +++ b/contrib/ci/bench/benchmark_comment.py @@ -38,11 +38,10 @@ def mem(d, s, cluster): ("snapshot_load", "snapshot load, testnet", "snapshot", snapshot_load, ".2f s", 1.5, 3.0, False), ("mem_mainnet", "mem total, mainnet", "replay", lambda d, s: mem(d, s, "mainnet"), ".2f GiB", 0.0, 1.0, False), ("mem_testnet", "mem total, testnet", "replay", lambda d, s: mem(d, s, "testnet"), ".2f GiB", 0.0, 1.0, False), - ("mem_ag_mainnet","mem total, ag mainnet", "replay", lambda d, s: mem(d, s, "ag.mainnet"), ".2f GiB", 0.0, 1.0, False), ("compile", "clean compile, firedancer", "*", lambda d, s: float(read(d, s, "build.time").split()[0]), ".2f s", 1.5, 3.0, False), ("binsize", "binary size, firedancer", "replay", lambda d, s: os.path.getsize(f"{d}/{s}/bin/firedancer") / 1e6, ".2f MB", 0.5, 2.0, False), ) -HIST_HDR = ("TPS", "BENCH", "SNAP", "MEM·M", "MEM·T", "AG·M", "COMPILE", "BINARY") +HIST_HDR = ("TPS", "BENCH", "SNAP", "MEM·M", "MEM·T", "COMPILE", "BINARY") def tier(row, d): _, _, _, _, _, warn, red, up = row diff --git a/src/app/firedancer-dev/commands/backtest.c b/src/app/firedancer-dev/commands/backtest.c index 30656e843ee..18b5bb8f126 100644 --- a/src/app/firedancer-dev/commands/backtest.c +++ b/src/app/firedancer-dev/commands/backtest.c @@ -28,11 +28,10 @@ #include "../../../discof/restore/fd_snapct_tile.h" #include "../../../discof/restore/utils/fd_ssctrl.h" #include "../../../discof/restore/utils/fd_ssmsg.h" -#include "../../../discof/tower/fd_tower_tile.h" +#include "../../../discof/rotor/fd_rotor_tile.h" #include "../../../discof/votor/fd_votor_tile.h" #include "../../../discof/replay/fd_execrp.h" #include "../../../disco/shred/fd_shred_tile.h" -#include "../../../discof/repair/fd_repair_tile.h" #include "../../../flamenco/capture/fd_capture_ctx.h" #include "../../../disco/pack/fd_pack_cost.h" #include "../../../flamenco/runtime/fd_cost_tracker.h" @@ -224,7 +223,7 @@ backtest_topo( config_t * config ) { batches from the CLI-specified source (eg. pcap/pcapng file). */ fd_topob_wksp( topo, "repair_out" ); - fd_topob_link( topo, "repair_out", "repair_out", 65536UL, sizeof(fd_repair_fec_complete_t), 1UL ); + fd_topob_link( topo, "repair_out", "repair_out", 65536UL, sizeof(fd_rotor_replay_fec_t), 1UL ); fd_topob_tile_in( topo, "replay", 0UL, "metric_in", "repair_out", 0UL, FD_TOPOB_RELIABLE, FD_TOPOB_POLLED ); fd_topob_tile_out( topo, "backt", 0UL, "repair_out", 0UL ); @@ -271,29 +270,15 @@ backtest_topo( config_t * config ) { /* More backtest->replay links in topo */ /**********************************************************************/ - /* The tower tile is replaced by the backtest tile for the tower to - replay link. The backtest tile simply sends monotonically - increasing rooted slot numbers to the replay tile, once after each - "replayed a full slot" notification received from the replay tile. - This allows the replay tile to advance its watermark, and publish - various data structures. This is an oversimplified barebones mock - of the tower tile. */ - fd_topob_wksp( topo, "tower_out" ); - fd_topob_link( topo, "tower_out", "tower_out", 1024UL, sizeof(fd_tower_slot_done_t), 1UL ); - fd_topob_tile_in( topo, "replay", 0UL, "metric_in", "tower_out", 0UL, FD_TOPOB_RELIABLE, FD_TOPOB_POLLED ); - fd_topob_tile_out( topo, "backt", 0UL, "tower_out", 0UL ); - /* Under Alpenglow the votor tile is likewise replaced by the backtest tile, which certifies each rooted slot as fast finalized so replay roots it. Replay keys blocks by the synthetic ids the backtest tile feeds it, so the finalization certs in the ledger's footers can not root anything here. */ - if( FD_UNLIKELY( config->firedancer.consensus.alpenglow ) ) { - fd_topob_wksp( topo, "votor_out" ); - fd_topob_link( topo, "votor_out", "votor_out", 1024UL, sizeof(fd_votor_msg_t), 1UL ); - fd_topob_tile_in( topo, "replay", 0UL, "metric_in", "votor_out", 0UL, FD_TOPOB_RELIABLE, FD_TOPOB_POLLED ); - fd_topob_tile_out( topo, "backt", 0UL, "votor_out", 0UL ); - } + fd_topob_wksp( topo, "votor_out" ); + fd_topob_link( topo, "votor_out", "votor_out", 1024UL, sizeof(fd_votor_msg_t), 1UL ); + fd_topob_tile_in( topo, "replay", 0UL, "metric_in", "votor_out", 0UL, FD_TOPOB_RELIABLE, FD_TOPOB_POLLED ); + fd_topob_tile_out( topo, "backt", 0UL, "votor_out", 0UL ); /**********************************************************************/ /* Setup replay->stake/send/poh links in topo w/o consumers */ @@ -367,7 +352,6 @@ backtest_topo( config_t * config ) { fd_topob_tile_out( topo, "snapct", 0UL, "snapct_gui", 0UL ); FOR(snapin_tile_cnt) fd_topob_tile_out( topo, "snapin", i, "snapin_gui", i ); - /**/ fd_topob_tile_in( topo, "gui", 0UL, "metric_in", "tower_out", 0UL, FD_TOPOB_RELIABLE, FD_TOPOB_POLLED ); /**/ fd_topob_tile_in( topo, "gui", 0UL, "metric_in", "replay_out", 0UL, FD_TOPOB_RELIABLE, FD_TOPOB_POLLED ); /**/ fd_topob_tile_in( topo, "gui", 0UL, "metric_in", "replay_epoch", 0UL, FD_TOPOB_RELIABLE, FD_TOPOB_POLLED ); /**/ fd_topob_tile_in( topo, "gui", 0UL, "metric_in", "genesi_out", 0UL, FD_TOPOB_RELIABLE, FD_TOPOB_POLLED ); @@ -401,7 +385,7 @@ backtest_topo( config_t * config ) { config->limits.max_shreds_per_block/FD_FEC_SHRED_CNT, config->limits.max_shreds_per_block ); ulong store_fec_max = config->firedancer.runtime.max_live_slots * fec_sets_per_slot + repair_out_link->depth + 1UL; ulong store_fec_data_max = fd_ulong_if( config->firedancer.development.fixed_fec_sets, 31840UL, 63985UL ); - fd_topo_obj_t * store_obj = setup_topo_store( topo, "store", store_fec_max, store_fec_data_max, 0UL, config->tiles.shred.shred_cache_size_mib, 0UL, config->limits.max_shreds_per_block, config->paths.shredb, config->firedancer.consensus.alpenglow, 1UL ); + fd_topo_obj_t * store_obj = setup_topo_store( topo, "store", store_fec_max, store_fec_data_max, 0UL, config->tiles.shred.shred_cache_size_mib, 0UL, config->limits.max_shreds_per_block, config->paths.shredb, 1, 1UL ); fd_topob_tile_uses( topo, backt_tile, store_obj, FD_SHMEM_JOIN_MODE_READ_WRITE ); fd_topob_tile_uses( topo, replay_tile, store_obj, FD_SHMEM_JOIN_MODE_READ_WRITE ); FD_TEST( fd_pod_insertf_ulong( topo->props, store_obj->id, "store" ) ); @@ -416,7 +400,7 @@ backtest_topo( config_t * config ) { fd_topo_obj_t * banks_obj = setup_topo_banks( topo, "banks", config->firedancer.runtime.max_live_slots, config->firedancer.runtime.bank_cache_cnt, config->firedancer.runtime.stake_delegation_cache_cnt, config->development.bench.max_cost_per_block ); fd_topob_tile_uses( topo, replay_tile, banks_obj, FD_SHMEM_JOIN_MODE_READ_WRITE ); FOR(execrp_tile_cnt) fd_topob_tile_uses( topo, &topo->tiles[ fd_topo_find_tile( topo, "execrp", i ) ], banks_obj, FD_SHMEM_JOIN_MODE_READ_WRITE ); - if( config->firedancer.consensus.alpenglow ) setup_topo_footer_cache( topo, "banks" ); + setup_topo_footer_cache( topo, "banks" ); if( FD_LIKELY( !disable_snap_loader ) ) { FOR(snapin_tile_cnt) fd_topob_tile_uses( topo, &topo->tiles[ fd_topo_find_tile( topo, "snapin", i ) ], banks_obj, FD_SHMEM_JOIN_MODE_READ_WRITE ); } diff --git a/src/discof/backtest/fd_backtest_tile.c b/src/discof/backtest/fd_backtest_tile.c index 1ae45c54b72..ed653db3805 100644 --- a/src/discof/backtest/fd_backtest_tile.c +++ b/src/discof/backtest/fd_backtest_tile.c @@ -4,10 +4,8 @@ #include "../../disco/events/fd_event_client.h" #include "../../discof/replay/fd_replay_tile.h" #include "../../disco/shred/fd_shred_tile.h" -#include "../../discof/repair/fd_repair_tile.h" #include "../../discof/rotor/fd_rotor_tile.h" #include "../../discof/restore/utils/fd_ssmsg.h" -#include "../../discof/tower/fd_tower_tile.h" #include "../../discof/votor/fd_votor_tile.h" #include "../../choreo/votor/ag_cert.h" #include "../../discof/genesis/fd_genesi_tile.h" @@ -60,10 +58,8 @@ struct fd_backt_tile { int initialized; int genesis; int snapshot_done; - int alpenglow; - uint first_fec_complete : 1; - uint reasm_ready : 1; /* reasm root is set, so we can start publishing FECs to replay */ - uint source_exhausted : 1; + uint reasm_ready : 1; /* reasm root is set, so we can start publishing FECs to replay */ + uint source_exhausted : 1; fd_backt_src_t * src; @@ -108,7 +104,6 @@ struct fd_backt_tile { fd_backt_in_t in[ 16UL ]; fd_backt_out_t repair_out[ 1 ]; - fd_backt_out_t tower_out[ 1 ]; fd_backt_out_t votor_out[ 1 ]; ulong ipecho_out_idx; /* ULONG if no event tile */ @@ -276,70 +271,32 @@ after_credit( fd_backt_tile_t * ctx, if( FD_LIKELY( !completes_fec_set ) ) return; - /* If Alpenglow is enabled, simulate the rotor tile instead of the - repair tile. */ - if( FD_UNLIKELY( ctx->alpenglow ) ) { - ulong parent_slot = shred->slot - shred->data.parent_off; - - fd_rotor_replay_fec_t * rotor_fec = fd_chunk_to_laddr( ctx->repair_out->mem, ctx->repair_out->chunk ); - memset( rotor_fec, 0, sizeof(fd_rotor_replay_fec_t) ); - rotor_fec->slot = shred->slot; - rotor_fec->fec_set_idx = (uint)ctx->out_fec_set_idx; - rotor_fec->mr = mr; - rotor_fec->parent_slot = parent_slot; - rotor_fec->data_complete = !!(shred->data.flags & FD_SHRED_DATA_FLAG_DATA_COMPLETE); - rotor_fec->slot_complete = completes_slot; - - rotor_fec->metrics.highest_fec_complete_slot = shred->slot; - if( FD_UNLIKELY( ctx->out_fec_set_idx==0UL ) ) { - if( FD_UNLIKELY( parent_slot==ctx->start_slot ) ) { - rotor_fec->parent_block_id = ctx->rooted_slots_block_id[ parent_slot % BANK_HASH_BUFFER_LEN ]; - } else { - rotor_fec->parent_block_id.ul[ 0 ] = parent_slot; - rotor_fec->parent_block_id.ul[ 1 ] = ctx->fec_set_idxs[ parent_slot % OUT_FECS_BUFFER_LEN ]; - } + ulong parent_slot = shred->slot - shred->data.parent_off; + + fd_rotor_replay_fec_t * rotor_fec = fd_chunk_to_laddr( ctx->repair_out->mem, ctx->repair_out->chunk ); + memset( rotor_fec, 0, sizeof(fd_rotor_replay_fec_t) ); + rotor_fec->slot = shred->slot; + rotor_fec->fec_set_idx = (uint)ctx->out_fec_set_idx; + rotor_fec->mr = mr; + rotor_fec->parent_slot = parent_slot; + rotor_fec->data_complete = !!(shred->data.flags & FD_SHRED_DATA_FLAG_DATA_COMPLETE); + rotor_fec->slot_complete = completes_slot; + + rotor_fec->metrics.highest_fec_complete_slot = shred->slot; + if( FD_UNLIKELY( ctx->out_fec_set_idx==0UL ) ) { + if( FD_UNLIKELY( parent_slot==ctx->start_slot ) ) { + rotor_fec->parent_block_id = ctx->rooted_slots_block_id[ parent_slot % BANK_HASH_BUFFER_LEN ]; + } else { + rotor_fec->parent_block_id.ul[ 0 ] = parent_slot; + rotor_fec->parent_block_id.ul[ 1 ] = ctx->fec_set_idxs[ parent_slot % OUT_FECS_BUFFER_LEN ]; } - if( FD_UNLIKELY( completes_slot ) ) rotor_fec->block_id = mr; - - ctx->fec_set_idxs[ shred->slot % OUT_FECS_BUFFER_LEN ] = ctx->out_fec_set_idx; - - fd_stem_publish( stem, ctx->repair_out->idx, ROTOR_SIG_FEC_REPLAY, ctx->repair_out->chunk, sizeof(fd_rotor_replay_fec_t), 0, 0UL, fd_frag_meta_ts_comp( fd_tickcount() ) ); - ctx->repair_out->chunk = fd_dcache_compact_next( ctx->repair_out->chunk, sizeof(fd_rotor_replay_fec_t), ctx->repair_out->chunk0, ctx->repair_out->wmark ); - - if( FD_UNLIKELY( ctx->source_exhausted && !ctx->shreds_cnt ) ) ctx->publish_time += fd_log_wallclock(); - return; - } - - fd_hash_t cmr = {0}; - if( FD_UNLIKELY( !ctx->first_fec_complete ) ) { - cmr.ul[ 0 ] = 0xbaC27e57b1d; /* any initial value works */ - ctx->first_fec_complete = 1; - } else { - ulong chained_slot = ctx->out_fec_set_idx==0 ? shred->slot - shred->data.parent_off : shred->slot; - cmr.ul[ 0 ] = chained_slot; - cmr.ul[ 1 ] = ctx->fec_set_idxs[ chained_slot % OUT_FECS_BUFFER_LEN ]; } + if( FD_UNLIKELY( completes_slot ) ) rotor_fec->block_id = mr; ctx->fec_set_idxs[ shred->slot % OUT_FECS_BUFFER_LEN ] = ctx->out_fec_set_idx; - /* We need to simulate the FEC set completion message that is sent out - of the shred tile. This involves copying the data shred header and - appending the merkle root and chained merkle root. */ - - fd_repair_fec_complete_t * complete_msg = (fd_repair_fec_complete_t *)fd_type_pun( fd_chunk_to_laddr( ctx->repair_out->mem, ctx->repair_out->chunk ) ); - complete_msg->fec.last_shred_hdr = *shred; - complete_msg->fec.last_shred_hdr.idx = (uint)out_shred_idx; - complete_msg->fec.last_shred_hdr.fec_set_idx = (uint)ctx->out_fec_set_idx; - memcpy( &complete_msg->fec.merkle_root, &mr, sizeof(fd_hash_t) ); - memcpy( &complete_msg->fec.chained_merkle_root, &cmr, sizeof(fd_hash_t) ); - complete_msg->fec.turbine_shred_cnt = 0U; - complete_msg->fec.repair_shred_cnt = 0U; - complete_msg->fec.reconstructed_shred_cnt = 0U; - - memset( &complete_msg->metrics, 0, sizeof(fd_fec_complete_metrics_t) ); - - fd_stem_publish( stem, ctx->repair_out->idx, REPAIR_SIG_FEC, ctx->repair_out->chunk, sizeof(fd_repair_fec_complete_t), 0, 0UL, fd_frag_meta_ts_comp( fd_tickcount() ) ); - ctx->repair_out->chunk = fd_dcache_compact_next( ctx->repair_out->chunk, sizeof(fd_repair_fec_complete_t), ctx->repair_out->chunk0, ctx->repair_out->wmark ); + fd_stem_publish( stem, ctx->repair_out->idx, ROTOR_SIG_FEC_REPLAY, ctx->repair_out->chunk, sizeof(fd_rotor_replay_fec_t), 0, 0UL, fd_frag_meta_ts_comp( fd_tickcount() ) ); + ctx->repair_out->chunk = fd_dcache_compact_next( ctx->repair_out->chunk, sizeof(fd_rotor_replay_fec_t), ctx->repair_out->chunk0, ctx->repair_out->wmark ); if( FD_UNLIKELY( ctx->source_exhausted && !ctx->shreds_cnt ) ) ctx->publish_time += fd_log_wallclock(); } @@ -486,7 +443,7 @@ returnable_frag( fd_backt_tile_t * ctx, /* In Alpenglow, dead blocks should never be in the ledger, so if Firedancer's replay marked a block as dead this should be fatal. */ - if( FD_UNLIKELY( ctx->alpenglow && memcmp( msg->block_id.uc, ((fd_hash_t){0}).uc, sizeof(fd_hash_t) ) ) ) { + if( FD_UNLIKELY( memcmp( msg->block_id.uc, ((fd_hash_t){0}).uc, sizeof(fd_hash_t) ) ) ) { FD_LOG_ERR(( "replay marked slot=%lu as dead", msg->slot )); } FD_LOG_NOTICE(( "replay marked slot=%lu as dead", msg->slot )); @@ -502,39 +459,9 @@ returnable_frag( fd_backt_tile_t * ctx, ctx->replay_ready_time = fd_log_wallclock(); ctx->prev_root = msg->slot; if( FD_UNLIKELY( !ctx->src ) ) finish( ctx, msg->slot, msg->completion_time_nanos ); /* load-only: no ledger */ - if( FD_UNLIKELY( ctx->alpenglow ) ) return 0; - /* Even though this is the first slot, we need to simulate tower - publishing the slot done message to replay so replay can - release the bank reference count on it. */ - fd_tower_slot_done_t * dst = fd_chunk_to_laddr( ctx->tower_out->mem, ctx->tower_out->chunk ); - /* Zero the fields this mock does not explicitly populate below - (eg. has_vote_txn, tower_cnt): consumers such as the gui tile - read the whole message. */ - memset( dst, 0, sizeof(fd_tower_slot_done_t) ); - dst->vote_slot = msg->slot; - dst->reset_slot = msg->slot; - dst->reset_block_id = msg->block_id; - dst->reset_bank_seq = msg->bank_seq; - dst->root_slot = msg->slot; - dst->root_block_id = msg->block_id; - dst->replay_slot = msg->slot; - dst->replay_bank_idx = msg->bank_idx; - dst->replay_bank_seq = msg->bank_seq; - dst->active_fork_cnt = 1UL; - dst->authority_idx = ULONG_MAX; - dst->vote_acct_bal = ULONG_MAX; - dst->vote_acct_com = USHORT_MAX; - dst->is_voting = 1; - fd_stem_publish( stem, ctx->tower_out->idx, FD_TOWER_SIG_SLOT_DONE, ctx->tower_out->chunk, sizeof(fd_tower_slot_done_t), 0UL, tspub, fd_frag_meta_ts_comp( fd_tickcount() ) ); - ctx->tower_out->chunk = fd_dcache_compact_next( ctx->tower_out->chunk, sizeof(fd_tower_slot_done_t), ctx->tower_out->chunk0, ctx->tower_out->wmark ); return 0; } - for( ulong idx=0UL; !ctx->alpenglow && idx<=msg->block_id.ul[ 1 ]; idx+=FD_FEC_SHRED_CNT ) { /* replay publishes Alpenglow slots */ - fd_hash_t mr = { .ul[ 0 ] = msg->slot, .ul[ 1 ] = idx }; - fd_store_remove( ctx->store, ctx->map_join, &mr ); - } - long prior_completion_timestamp = ctx->prior_completion_timestamp ? ctx->prior_completion_timestamp : msg->preparation_begin_nanos; fd_backt_slot_info_t slot_info; @@ -579,43 +506,17 @@ returnable_frag( fd_backt_tile_t * ctx, int root_advanced = root_slot!=ctx->prev_root; ctx->prev_root = root_slot; - /* Under Alpenglow the backtest tile stands in for votor and - certifies the root slot fast finalized so replay roots it. */ - if( ctx->alpenglow ) { - if( FD_LIKELY( root_advanced ) ) { - fd_votor_certed_t * certed = fd_chunk_to_laddr( ctx->votor_out->mem, ctx->votor_out->chunk ); - memset( certed, 0, sizeof(fd_votor_certed_t) ); - certed->kind = AG_CERT_KIND_FAST_FINAL; - certed->slot = root_slot; - certed->block_id = ctx->rooted_slots_block_id[ root_slot%BANK_HASH_BUFFER_LEN ]; - fd_stem_publish( stem, ctx->votor_out->idx, FD_VOTOR_SIG_CERTED, ctx->votor_out->chunk, sizeof(fd_votor_msg_t), 0UL, tspub, fd_frag_meta_ts_comp( fd_tickcount() ) ); - ctx->votor_out->chunk = fd_dcache_compact_next( ctx->votor_out->chunk, sizeof(fd_votor_msg_t), ctx->votor_out->chunk0, ctx->votor_out->wmark ); - } - break; + /* The backtest tile stands in for votor and certifies the root + slot fast finalized so replay roots it. */ + if( FD_LIKELY( root_advanced ) ) { + fd_votor_certed_t * certed = fd_chunk_to_laddr( ctx->votor_out->mem, ctx->votor_out->chunk ); + memset( certed, 0, sizeof(fd_votor_certed_t) ); + certed->kind = AG_CERT_KIND_FAST_FINAL; + certed->slot = root_slot; + certed->block_id = ctx->rooted_slots_block_id[ root_slot%BANK_HASH_BUFFER_LEN ]; + fd_stem_publish( stem, ctx->votor_out->idx, FD_VOTOR_SIG_CERTED, ctx->votor_out->chunk, sizeof(fd_votor_msg_t), 0UL, tspub, fd_frag_meta_ts_comp( fd_tickcount() ) ); + ctx->votor_out->chunk = fd_dcache_compact_next( ctx->votor_out->chunk, sizeof(fd_votor_msg_t), ctx->votor_out->chunk0, ctx->votor_out->wmark ); } - - fd_tower_slot_done_t * dst = fd_chunk_to_laddr( ctx->tower_out->mem, ctx->tower_out->chunk ); - /* Zero the fields this mock does not explicitly populate below - (eg. has_vote_txn, tower_cnt): consumers such as the gui tile - read the whole message. */ - memset( dst, 0, sizeof(fd_tower_slot_done_t) ); - dst->replay_slot = msg->slot; - dst->replay_bank_idx = msg->bank_idx; - dst->replay_bank_seq = msg->bank_seq; - dst->vote_slot = msg->slot; - dst->reset_slot = msg->slot; - dst->reset_block_id = msg->block_id; - dst->reset_bank_seq = msg->bank_seq; - dst->root_slot = root_slot; - dst->root_block_id = ctx->rooted_slots_block_id[ root_slot%BANK_HASH_BUFFER_LEN ]; - dst->active_fork_cnt = 1UL; - dst->authority_idx = ULONG_MAX; - dst->vote_acct_bal = ULONG_MAX; - dst->vote_acct_com = USHORT_MAX; - dst->is_voting = 1; - - fd_stem_publish( stem, ctx->tower_out->idx, FD_TOWER_SIG_SLOT_DONE, ctx->tower_out->chunk, sizeof(fd_tower_slot_done_t), 0UL, tspub, fd_frag_meta_ts_comp( fd_tickcount() ) ); - ctx->tower_out->chunk = fd_dcache_compact_next( ctx->tower_out->chunk, sizeof(fd_tower_slot_done_t), ctx->tower_out->chunk0, ctx->tower_out->wmark ); break; } default: FD_LOG_ERR(( "unhandled in_kind: %d in_idx: %lu", ctx->in_kind[ in_idx ], in_idx )); @@ -678,7 +579,6 @@ unprivileged_init( fd_topo_t const * topo, ctx->snapshot_done = 0; ctx->initialized = 0; - ctx->first_fec_complete = 0; ctx->reasm_ready = 0; ctx->genesis = fd_topo_find_tile( topo, "snapct", 0UL )==ULONG_MAX; ctx->idle_cnt = 0UL; @@ -721,7 +621,6 @@ unprivileged_init( fd_topo_t const * topo, memset( ctx->fec_set_idxs, 0UL, sizeof(ctx->fec_set_idxs) ); ctx->root_distance = tile->backtest.root_distance; - ctx->alpenglow = tile->backtest.alpenglow; ctx->boot_time = tile->backtest.boot_timestamp_nanos; ctx->src = NULL; @@ -763,8 +662,7 @@ unprivileged_init( fd_topo_t const * topo, } *ctx->repair_out = out1( topo, tile, "repair_out" ); - *ctx->tower_out = out1( topo, tile, "tower_out" ); - if( ctx->alpenglow ) *ctx->votor_out = out1( topo, tile, "votor_out" ); + *ctx->votor_out = out1( topo, tile, "votor_out" ); ctx->ipecho_out_idx = fd_topo_find_tile_out_link( topo, tile, "ipecho_out", 0UL ); ctx->store = fd_store_join( fd_topo_obj_laddr( topo, fd_pod_query_ulong( topo->props, "store", ULONG_MAX ) ) ); diff --git a/src/flamenco/runtime/tests/run_backtest_all.sh b/src/flamenco/runtime/tests/run_backtest_all.sh index fd18d8e3255..fc07ac7e55c 100755 --- a/src/flamenco/runtime/tests/run_backtest_all.sh +++ b/src/flamenco/runtime/tests/run_backtest_all.sh @@ -1,186 +1,21 @@ #!/bin/bash set -e -src/flamenco/runtime/tests/run_ledger_backtest.sh -l testnet-519-v4.2.0-beta.1-vat -m 2000000 -e 255312007 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l mainnet-257066033-v4.2.0-beta.1-vat -m 2000000 -e 257066038 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l mainnet-257066844-v4.2.0-beta.1-vat -m 2000000 -e 257066849 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l mainnet-257067457-v4.2.0-beta.1-vat -m 2000000 -e 257067461 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l mainnet-257068890-v4.2.0-beta.1-vat -m 2000000 -e 257068895 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l mainnet-257181622-v4.2.0-beta.1-vat -m 2000000 -e 257181624 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l mainnet-254462437-v4.2.0-beta.1-vat -m 10000000 -e 254462598 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l mainnet-257037451-v4.2.0-beta.1-vat -m 2000000 -e 257037454 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l mainnet-257035225-v4.2.0-beta.1-vat -m 2000000 -e 257035233 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l mainnet-257465453-v4.2.0-beta.1-vat -m 10000000 -e 257465454 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l mainnet-257058865-v4.2.0-beta.1-vat -m 2000000 -e 257058870 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l mainnet-257059815-v4.2.0-beta.1-vat -m 2000000 -e 257059818 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l mainnet-257061172-v4.2.0-beta.1-vat -m 2000000 -e 257061175 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l mainnet-257222682-v4.2.0-beta.1-vat -m 2000000 -e 257222688 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l mainnet-264890264-v4.2.0-beta.1-vat -m 2000000 -e 264890265 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l mainnet-257229353-v4.2.0-beta.1-vat -m 2000000 -e 257229357 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l mainnet-257257983-v4.2.0-beta.1-vat -m 2000000 -e 257257986 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l mainnet-267728520-v4.2.0-beta.1-vat -m 2000000 -e 267728522 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l mainnet-267651942-v4.2.0-beta.1-vat -m 2000000 -e 267651943 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l mainnet-267081197-v4.2.0-beta.1-vat -m 2000000 -e 267081198 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l mainnet-267085604-v4.2.0-beta.1-vat -m 2000000 -e 267085605 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l mainnet-265688706-v4.2.0-beta.1-vat -m 2000000 -e 265688707 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l mainnet-265330432-v4.2.0-beta.1-vat -m 2000000 -e 265330433 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l mainnet-268575190-v4.2.0-beta.1-vat -m 2000000 -e 268575191 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l mainnet-268129380-v4.2.0-beta.1-vat -m 2000000 -e 268129380 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l mainnet-268163043-v4.2.0-beta.1-vat -m 2000000 -e 268163043 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l mainnet-269511381-v4.2.0-beta.1-vat -m 2000000 -e 269511381 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l mainnet-269567236-v4.2.0-beta.1-vat -m 2000000 -e 269567236 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l mainnet-266134813-v4.2.0-beta.1-vat -m 2000000 -e 266134814 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l mainnet-266545736-v4.2.0-beta.1-vat -m 2000000 -e 266545737 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l mainnet-267059180-v4.2.0-beta.1-vat -m 2000000 -e 267059181 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l mainnet-267580466-v4.2.0-beta.1-vat -m 2000000 -e 267580467 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l mainnet-268196194-v4.2.0-beta.1-vat -m 2000000 -e 268196195 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l mainnet-267766641-v4.2.0-beta.1-vat -m 2000000 -e 267766642 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l mainnet-269648145-v4.2.0-beta.1-vat -m 2000000 -e 269648146 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l testnet-281688085-v4.2.0-beta.1-vat -m 2000000 -e 281688086 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l mainnet-277660422-v4.2.0-beta.1-vat -m 2000000 -e 277660423 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l mainnet-277876060-v4.2.0-beta.1-vat -m 2000000 -e 277876061 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l mainnet-277927063-v4.2.0-beta.1-vat -m 2000000 -e 277927065 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l mainnet-281375356-v4.2.0-beta.1-vat -m 2000000 -e 281375359 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l mainnet-251418170-v4.2.0-beta.1-vat -m 2000000 -e 251418233 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l mainnet-282232100-v4.2.0-beta.1-vat -m 2000000 -e 282232101 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l mainnet-282151715-v4.2.0-beta.1-vat -m 2000000 -e 282151717 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l mainnet-286450148-v4.2.0-beta.1-vat -m 2000000 -e 286450151 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l multi-epoch-per-200-v4.2.0-beta.1-vat -m 2000000 -e 984 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l multi-epoch-per-300-v4.2.0-beta.1-vat -m 2000000 -e 984 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l multi-epoch-per-500-v4.2.0-beta.1-vat -m 2000000 -e 984 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l testnet-297489336-v4.2.0-beta.1-vat -m 2000000 -e 297489363 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l mainnet-300377724-v4.2.0-beta.1-vat -m 2000000 -e 300377724 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l mainnet-300645644-v4.3.0-beta.0 -m 2000000 -e 300645644 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l mainnet-300648964-v4.3.0-beta.0 -m 2000000 -e 300648964 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l mainnet-301359740-v4.2.0-beta.1-vat -m 2000000 -e 301359740 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l mainnet-257181032-v4.2.0-beta.1-vat -m 2000000 -e 257181035 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l mainnet-257047660-v4.2.0-beta.1-vat -m 2000000 -e 257047662 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l mainnet-257047659-v4.2.0-beta.1-vat -m 2000000 -e 257047660 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l mainnet-308445707-v4.2.0-beta.1-vat -m 2000000 -e 308445711 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l testnet-307395181-v4.2.0-beta.1-vat -m 2000000 -e 307395190 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l mainnet-308392063-v4.2.0-beta.1-vat -m 2000000 -e 308392063 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l devnet-350814254-v4.2.0-beta.1-vat -m 2000000 -e 350814284 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l testnet-311586340-v4.2.0-beta.1-vat -m 2000000 -e 311586380 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l testnet-281546597-v4.2.0-beta.1-vat -m 2000000 -e 281546597 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l mainnet-324823213-v4.2.0-beta.1-vat -m 2000000 -e 324823214 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l mainnet-325467935-v4.2.0-beta.1-vat -m 2000000 -e 325467935 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l testnet-283927487-v4.2.0-beta.1-vat -m 2000000 -e 283927497 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l testnet-321168308-v4.2.0-beta.1-vat -m 2000000 -e 321168308 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l mainnet-327324660-v4.2.0-beta.1-vat -m 2000000 -e 327324660 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l devnet-370199634-v4.2.0-beta.1-vat -m 200000 -e 370199634 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l mainnet-330219081-v4.3.0-beta.0 -m 2000000 -e 330219082 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l devnet-372721907-v4.3.0-beta.0 -m 2000000 -e 372721910 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l mainnet-331691646-v4.2.0-beta.1-vat -m 2000000 -e 331691647 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l devnet-378683870-v4.2.0-beta.1-vat -m 2000000 -e 378683872 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l devnet-380592002-v4.2.0-beta.1-vat -m 2000000 -e 380592006 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l testnet-336218682-v4.2.0-beta.1-vat -m 2000000 -e 336218683 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l testnet-340269866-v4.2.0-beta.1-vat -m 2000000 -e 340269872 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l testnet-340272018-v4.2.0-beta.1-vat -m 2000000 -e 340272023 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l testnet-346556000-v4.2.0-beta.1-vat -m 2000000 -e 346556337 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l testnet-346179946-v4.2.0-beta.1-vat -m 90000000 -e 346179950 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l multi-bpf-loader-v4.2.0-beta.1-vat -m 1000 -e 108 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l local-multi-boundary-v4.2.0-beta.1-vat -m 1000 -e 2325 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l genesis-vat-alpenglow-agave-v4.4-968b24ce --alpenglow -g -m 3000 -e 1360 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l localnet-stake-v4.2.0-beta.1-vat -m 3000 -e 541 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l mainnet-376969880-v4.2.0-beta.1-vat -m 2000000 -e 376969880 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l testnet-384169347-v4.2.0-beta.1-vat -m 2000000 -e 384169377 --root-distance 32 --max-live-slots 64 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l breakpoint-385786458-v4.2.0-beta.1-vat -m 2000000 -e 385786452 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l testnet-386300256-v4.2.0-beta.1-vat -m 2000000 -e 386300289 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l testnet-387596258-v4.2.0-beta.1-vat -m 2000000 -e 387596373 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l deployment-before-boundary-sha512-2-v4.2.0-beta.1-vat -m 1000 -e 75 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l vote-stake-scenarios-v4.2.0-beta.1-vat -m 10000 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l mainnet-391824000-boundary-v4.2.0-beta.1-vat -m 2000000 -e 391824016 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l progcache-stale-entry-v4.2.0-beta.1-vat -m 10000 -e 135 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l snapshot_block_id_some-v4.2.0-beta.1-vat -m 1000 -e 110 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l snapshot_block_id_none-v4.2.0-beta.1-vat -m 1000 -e 110 - -# Local cluster ledgers testing specific feature gates -src/flamenco/runtime/tests/run_ledger_backtest.sh -l localnet-deprecate-rent-exemption-threshold-v4.2.0-beta.1-vat -m 1000 -e 260 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l relax-intrabatch-account-locks-v4.2.0-beta.1-vat -m 1000 -e 240 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l vote-states-v4-local-v4.2.0-beta.1-vat -m 3000 -e 1000 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l create_account_allow_prefund-v4.2.0-beta.1-vat -m 1000 -e 520 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l relax_programdata_account_check_migration-v4.2.0-beta.1-vat -m 1000 -e 260 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l replace_spl_token_with_p_token-v4.2.0-beta.1-vat -m 1000 -e 720 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l syscall-parameter-address-restrictions-v4.2.0-beta.1-vat -m 1000 -e 312 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l virtual-address-space-adjustments-v4.2.0-beta.1-vat -m 1000 -e 819 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l account-data-direct-mapping-v4.2.0-beta.1-vat -m 1000 -e 1395 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l enable_sbpf_v3_deployment_and_execution-v4.2.0-beta.1-vat -m 1000 -e 961 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l upgrade_bpf_stake_program_to_v5-v4.2.0-beta.1-vat -m 2000 -e 586 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l upgrade_bpf_stake_program_to_v5_revoke-v4.2.0-beta.1-vat -m 1000 -e 290 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l delay-comission-updates-7-v4.2.0-beta.1-vat -m 20000 -e 1596 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l delay-comission-updates-8-v4.2.0-beta.1-vat -m 20000 -e 1596 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l vat-activation-v4.2.0-beta.1-vat -m 20000 -e 540 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l enable_bls12_381_syscall-v4.2.0-beta.1-vat -m 1000 -e 379 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l bls_pubkey_management_in_vote_account_rekey-v4.2.0-beta.1-vat -m 1000 -e 329 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l raise_block_limits_to_100m-v4.2.0-beta.1-vat -m 10000 -e 603 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l direct_account_pointers_in_program_input-v4.2.0-beta.1-vat -m 10000 -e 386 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l commission_rate_in_basis_points_boundary-v4.2.0-beta.1-vat -m 10000 -e 950 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l commission_rate_in_basis_points_snapshot-v4.2.0-beta.1-vat -m 10000 -e 950 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l disable_sbpf_v0_v1_v2_deployment-v4.2.0-beta.1-vat -m 10000 -e 625 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l define_ltds_fee_only_semantics-v4.2.0-beta.1-vat -m 1000 -e 330 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l loader_v3_minimum_extend_program_size-v4.2.0-beta.1-vat -m 1000 -e 590 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l enable_sha512_syscall-v4.2.0-beta.1-vat -m 1000 -e 611 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l loader_v3_set_program_data_to_elf_length -m 1000 -e 363 - -# Delay-commission epoch-boundary scenarios (local cluster, agave-cluster generated). -# delay_commission_updates + validator_admission_ticket (VAT) + bls_pubkey_management ALL ON. -src/flamenco/runtime/tests/run_ledger_backtest.sh -l vat-dc-stake-and-commission-v4.2.0-beta.1-vat -m 1000000 -e 1295 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l vat-dc-unstake-recommission-restake-v4.2.0-beta.1-vat -m 1000000 -e 1540 -# vat-dc-deleted-vote-per: boots mid-PER @ slot 1025, 10k stake accounts (3-partition PER), vote acct -# voted epochs 1-6 w/ commission 50->40->30->20 (present in top_votes t_1/t_2/t_3), deleted ep8 + recreated ep9 (#9840). -src/flamenco/runtime/tests/run_ledger_backtest.sh -l vat-dc-deleted-vote-per-v4.2.0-beta.1-vat -m 2000000 -e 2555 -# 10k stake accounts, 2nd vote acct voting across the transition w/ commission 100 (epoch0) -> 0 (epoch1+, max split delta). -src/flamenco/runtime/tests/run_ledger_backtest.sh -l vat-activate-10k-v4.2.0-beta.1-vat -m 2000000 -e 1020 -# SIMD-525 shorter slot times combined with raise_block_limits_to_100m. -# There are 2 sets of ledgers: one with the expected order of activation, -# and one with an out-of-order activation. There is a ledger for each -# feature gate in each run. -src/flamenco/runtime/tests/run_ledger_backtest.sh -l reduce_slot_time_to_350ms-v4.2.0-beta.1-vat --zst 2 -m 50000 -e 585 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l reduce_slot_time_to_300ms-v4.2.0-beta.1-vat --zst 2 -m 50000 -e 1099 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l reduce_slot_time_to_250ms-v4.2.0-beta.1-vat --zst 2 -m 50000 -e 1612 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l reduce_slot_time_to_200ms-v4.2.0-beta.1-vat --zst 2 -m 50000 -e 2123 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l raise_block_limits_to_100m-2-v4.2.0-beta.1-vat --zst 2 -m 50000 -e 2364 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l reduce_slot_time_to_200ms-out-of-order-v4.2.0-beta.1-vat --zst 2 -m 50000 -e 580 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l reduce_slot_time_to_350ms-out-of-order-v4.2.0-beta.1-vat --zst 2 -m 50000 -e 1102 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l raise_block_limits_to_100m-out-of-order-v4.2.0-beta.1-vat --zst 2 -m 50000 -e 1357 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l reduce_slot_time_to_250ms-out-of-order-v4.2.0-beta.1-vat --zst 2 -m 50000 -e 1865 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l reduce_slot_time_to_300ms-out-of-order-v4.2.0-beta.1-vat --zst 2 -m 50000 -e 2364 -# SIMD-0437/0438 (rent lamports_per_byte) + SIMD-0194 (deprecate threshold) + SIMD-0392 (relax_post_exec_min_balance_check); -# localnet, features activate @ slot 256; boot @ snapshot-200 to replay it OFF+ON across the boundary. -src/flamenco/runtime/tests/run_ledger_backtest.sh -l rent_relax_only-v4.2.0-beta.1-vat_rekey -m 2000000 -e 325 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l rent_lamports_per_byte_6333-v4.2.0-beta.1-vat_rekey -m 2000000 -e 325 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l rent_lamports_per_byte_5080-v4.2.0-beta.1-vat_rekey -m 2000000 -e 325 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l rent_lamports_per_byte_2575-v4.3.0-rc.1_rekey -m 2000000 -e 325 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l rent_lamports_per_byte_1322-v4.3.0-rc.1_rekey -m 2000000 -e 325 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l rent_lamports_per_byte_696-v4.3.0-rc.1_rekey -m 2000000 -e 325 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l rent_lamports_per_byte_6960-v4.3.0-rc.1_rekey -m 2000000 -e 325 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l enable_tx_v1 -m 2000000 -e 637 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l programdata-closeslot -m 10000 -e 330 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l programdata-poison -m 10000 -e 562 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l double_disinflation_rate -m 2000000 -e 840 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l double_disinflation_rate_snapshot -m 2000000 -e 840 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l relax_fee_payer_constraint -m 2000000 -e 400 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l snapshot-hard-fork -m 2000000 -e 162 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l alpenglow-block-revenue-sharing-agave-v4.4-57a35916-pr15795 --alpenglow -m 2000000 -e 1400 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l alpenglow-remove-inactive-stakes-agave-v4.4-6afe9919 --alpenglow -m 2000000 -e 1100 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l alpenglow-block-revenue-sharing-remove-inactive-stakes-agave-v4.4-57a35916-pr15795 --alpenglow -m 2000000 -e 1400 - -# Alpenglow -src/flamenco/runtime/tests/run_ledger_backtest.sh -l alpenglow-legacy-vote-ixs --alpenglow -m 2000000 -e 126 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l alpenglow-deactivated-stake --alpenglow -m 2000000 -e 810 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l alpenglow-leader-credits --alpenglow -m 2000000 -e 3915 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l alpenglow-alpenclock-lamports --alpenglow -m 2000000 -e 280 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l alpenglow-migration-rewards --alpenglow -m 2000000 -e 1086 --zst -src/flamenco/runtime/tests/run_ledger_backtest.sh -l alpenglow-migration-epoch-end --alpenglow -m 2000000 -e 1087 --zst -src/flamenco/runtime/tests/run_ledger_backtest.sh -l alpenglow-migration-no-ag-credits --alpenglow -m 2000000 -e 1087 --zst -src/flamenco/runtime/tests/run_ledger_backtest.sh -l alpenglow-migration-epoch-start --alpenglow -m 2000000 -e 1599 --zst -src/flamenco/runtime/tests/run_ledger_backtest.sh -l alpenglow-migration-ag-only-voter --alpenglow -m 2000000 -e 1087 --zst -src/flamenco/runtime/tests/run_ledger_backtest.sh -l alpenglow-migration-no-tower-points --alpenglow -m 2000000 -e 1125 --zst -src/flamenco/runtime/tests/run_ledger_backtest.sh -l alpenglow-migration-vote-recreate --alpenglow -m 2000000 -e 1637 --zst -src/flamenco/runtime/tests/run_ledger_backtest.sh -l alpenglow-migration-validators --alpenglow -m 2000000 -e 2112 --zst -src/flamenco/runtime/tests/run_ledger_backtest.sh -l alpenglow-migration-validators-boundary-snap --alpenglow -m 2000000 -e 1600 --zst -src/flamenco/runtime/tests/run_ledger_backtest.sh -l alpenglow-migration-validators-mid-payout --alpenglow -m 2000000 -e 1600 --zst - -src/flamenco/runtime/tests/run_ledger_backtest.sh -l sd-disk-spill -m 10000000 -e 17400 --genesis-max-file-size-mib 1024 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l sd-disk-spill-post -m 10000000 -e 17400 --genesis-max-file-size-mib 1024 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l sd-disk-spill-midwin -m 10000000 -e 17400 --genesis-max-file-size-mib 1024 +src/flamenco/runtime/tests/run_ledger_backtest.sh -l genesis-vat-alpenglow-agave-v4.4-968b24ce -g -m 3000 -e 1360 +src/flamenco/runtime/tests/run_ledger_backtest.sh -l alpenglow-block-revenue-sharing-agave-v4.4-57a35916-pr15795 -m 2000000 -e 1400 +src/flamenco/runtime/tests/run_ledger_backtest.sh -l alpenglow-remove-inactive-stakes-agave-v4.4-6afe9919 -m 2000000 -e 1100 +src/flamenco/runtime/tests/run_ledger_backtest.sh -l alpenglow-block-revenue-sharing-remove-inactive-stakes-agave-v4.4-57a35916-pr15795 -m 2000000 -e 1400 +src/flamenco/runtime/tests/run_ledger_backtest.sh -l alpenglow-legacy-vote-ixs -m 2000000 -e 126 +src/flamenco/runtime/tests/run_ledger_backtest.sh -l alpenglow-deactivated-stake -m 2000000 -e 810 +src/flamenco/runtime/tests/run_ledger_backtest.sh -l alpenglow-leader-credits -m 2000000 -e 3915 +src/flamenco/runtime/tests/run_ledger_backtest.sh -l alpenglow-alpenclock-lamports -m 2000000 -e 280 +src/flamenco/runtime/tests/run_ledger_backtest.sh -l alpenglow-migration-rewards -m 2000000 -e 1086 --zst +src/flamenco/runtime/tests/run_ledger_backtest.sh -l alpenglow-migration-epoch-end -m 2000000 -e 1087 --zst +src/flamenco/runtime/tests/run_ledger_backtest.sh -l alpenglow-migration-no-ag-credits -m 2000000 -e 1087 --zst +src/flamenco/runtime/tests/run_ledger_backtest.sh -l alpenglow-migration-epoch-start -m 2000000 -e 1599 --zst +src/flamenco/runtime/tests/run_ledger_backtest.sh -l alpenglow-migration-ag-only-voter -m 2000000 -e 1087 --zst +src/flamenco/runtime/tests/run_ledger_backtest.sh -l alpenglow-migration-no-tower-points -m 2000000 -e 1125 --zst +src/flamenco/runtime/tests/run_ledger_backtest.sh -l alpenglow-migration-vote-recreate -m 2000000 -e 1637 --zst +src/flamenco/runtime/tests/run_ledger_backtest.sh -l alpenglow-migration-validators -m 2000000 -e 2112 --zst +src/flamenco/runtime/tests/run_ledger_backtest.sh -l alpenglow-migration-validators-boundary-snap -m 2000000 -e 1600 --zst +src/flamenco/runtime/tests/run_ledger_backtest.sh -l alpenglow-migration-validators-mid-payout -m 2000000 -e 1600 --zst diff --git a/src/flamenco/runtime/tests/run_backtest_ci.sh b/src/flamenco/runtime/tests/run_backtest_ci.sh index 334fc0c0de2..a738dc67665 100755 --- a/src/flamenco/runtime/tests/run_backtest_ci.sh +++ b/src/flamenco/runtime/tests/run_backtest_ci.sh @@ -1,58 +1,20 @@ #!/bin/bash set -e -src/flamenco/runtime/tests/run_ledger_backtest.sh -l mainnet-308392063-v4.2.0-beta.1-vat -m 2000000 -e 308392063 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l devnet-350814254-v4.2.0-beta.1-vat -m 2000000 -e 350814284 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l testnet-281546597-v4.2.0-beta.1-vat -m 2000000 -e 281546597 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l mainnet-324823213-v4.2.0-beta.1-vat -m 2000000 -e 324823214 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l mainnet-325467935-v4.2.0-beta.1-vat -m 2000000 -e 325467935 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l testnet-283927487-v4.2.0-beta.1-vat -m 2000000 -e 283927497 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l testnet-281688085-v4.2.0-beta.1-vat -m 2000000 -e 281688086 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l testnet-321168308-v4.2.0-beta.1-vat -m 2000000 -e 321168308 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l mainnet-327324660-v4.2.0-beta.1-vat -m 2000000 -e 327324660 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l devnet-370199634-v4.2.0-beta.1-vat -m 200000 -e 370199634 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l devnet-378683870-v4.2.0-beta.1-vat -m 2000000 -e 378683872 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l mainnet-330219081-v4.3.0-beta.0 -m 2000000 -e 330219082 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l devnet-372721907-v4.3.0-beta.0 -m 2000000 -e 372721910 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l mainnet-331691646-v4.2.0-beta.1-vat -m 2000000 -e 331691647 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l testnet-336218682-v4.2.0-beta.1-vat -m 2000000 -e 336218683 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l testnet-340269866-v4.2.0-beta.1-vat -m 2000000 -e 340269872 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l mainnet-254462437-v4.2.0-beta.1-vat -m 10000000 -e 254462598 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l testnet-346556000-v4.2.0-beta.1-vat -m 2000000 -e 346556337 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l devnet-380592002-v4.2.0-beta.1-vat -m 2000000 -e 380592006 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l genesis-vat-alpenglow-agave-v4.4-968b24ce --alpenglow -g -m 3000 -e 1360 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l breakpoint-385786458-v4.2.0-beta.1-vat -m 2000000 -e 385786452 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l vote-states-v4-local-v4.2.0-beta.1-vat -m 3000 -e 1000 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l testnet-384169347-v4.2.0-beta.1-vat -m 2000000 -e 384169377 --root-distance 32 --max-live-slots 64 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l testnet-387596258-v4.2.0-beta.1-vat -m 2000000 -e 387596373 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l deployment-before-boundary-sha512-2-v4.2.0-beta.1-vat -m 1000 -e 75 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l mainnet-391824000-boundary-v4.2.0-beta.1-vat -m 2000000 -e 391824016 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l vote-stake-scenarios-v4.2.0-beta.1-vat -m 10000 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l vat-activation-v4.2.0-beta.1-vat -m 20000 -e 540 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l progcache-stale-entry-v4.2.0-beta.1-vat -m 10000 -e 135 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l commission_rate_in_basis_points_boundary-v4.2.0-beta.1-vat -m 10000 -e 950 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l commission_rate_in_basis_points_snapshot-v4.2.0-beta.1-vat -m 10000 -e 950 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l rent_relax_only-v4.2.0-beta.1-vat_rekey -m 2000000 -e 325 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l rent_lamports_per_byte_6333-v4.2.0-beta.1-vat_rekey -m 2000000 -e 325 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l rent_lamports_per_byte_696-v4.3.0-rc.1_rekey -m 2000000 -e 325 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l rent_lamports_per_byte_6960-v4.3.0-rc.1_rekey -m 2000000 -e 325 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l programdata-closeslot -m 10000 -e 330 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l programdata-poison -m 10000 -e 562 - -# Alpenglow -src/flamenco/runtime/tests/run_ledger_backtest.sh -l alpenglow-legacy-vote-ixs --alpenglow -m 2000000 -e 126 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l alpenglow-deactivated-stake --alpenglow -m 2000000 -e 810 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l alpenglow-leader-credits --alpenglow -m 2000000 -e 3915 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l alpenglow-alpenclock-lamports --alpenglow -m 2000000 -e 280 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l alpenglow-migration-epoch-end --alpenglow -m 2000000 -e 1087 --zst -src/flamenco/runtime/tests/run_ledger_backtest.sh -l alpenglow-migration-no-ag-credits --alpenglow -m 2000000 -e 1087 --zst -src/flamenco/runtime/tests/run_ledger_backtest.sh -l alpenglow-migration-epoch-start --alpenglow -m 2000000 -e 1599 --zst -src/flamenco/runtime/tests/run_ledger_backtest.sh -l alpenglow-migration-ag-only-voter --alpenglow -m 2000000 -e 1087 --zst -src/flamenco/runtime/tests/run_ledger_backtest.sh -l alpenglow-migration-no-tower-points --alpenglow -m 2000000 -e 1125 --zst -src/flamenco/runtime/tests/run_ledger_backtest.sh -l alpenglow-migration-vote-recreate --alpenglow -m 2000000 -e 1637 --zst -src/flamenco/runtime/tests/run_ledger_backtest.sh -l alpenglow-migration-validators --alpenglow -m 2000000 -e 2112 --zst -src/flamenco/runtime/tests/run_ledger_backtest.sh -l alpenglow-migration-validators-boundary-snap --alpenglow -m 2000000 -e 1600 --zst -src/flamenco/runtime/tests/run_ledger_backtest.sh -l alpenglow-migration-validators-mid-payout --alpenglow -m 2000000 -e 1600 --zst -src/flamenco/runtime/tests/run_ledger_backtest.sh -l alpenglow-block-revenue-sharing-agave-v4.4-57a35916-pr15795 --alpenglow -m 2000000 -e 1400 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l alpenglow-remove-inactive-stakes-agave-v4.4-6afe9919 --alpenglow -m 2000000 -e 1100 -src/flamenco/runtime/tests/run_ledger_backtest.sh -l alpenglow-block-revenue-sharing-remove-inactive-stakes-agave-v4.4-57a35916-pr15795 --alpenglow -m 2000000 -e 1400 +src/flamenco/runtime/tests/run_ledger_backtest.sh -l genesis-vat-alpenglow-agave-v4.4-968b24ce -g -m 3000 -e 1360 +src/flamenco/runtime/tests/run_ledger_backtest.sh -l alpenglow-legacy-vote-ixs -m 2000000 -e 126 +src/flamenco/runtime/tests/run_ledger_backtest.sh -l alpenglow-deactivated-stake -m 2000000 -e 810 +src/flamenco/runtime/tests/run_ledger_backtest.sh -l alpenglow-leader-credits -m 2000000 -e 3915 +src/flamenco/runtime/tests/run_ledger_backtest.sh -l alpenglow-alpenclock-lamports -m 2000000 -e 280 +src/flamenco/runtime/tests/run_ledger_backtest.sh -l alpenglow-migration-epoch-end -m 2000000 -e 1087 --zst +src/flamenco/runtime/tests/run_ledger_backtest.sh -l alpenglow-migration-no-ag-credits -m 2000000 -e 1087 --zst +src/flamenco/runtime/tests/run_ledger_backtest.sh -l alpenglow-migration-epoch-start -m 2000000 -e 1599 --zst +src/flamenco/runtime/tests/run_ledger_backtest.sh -l alpenglow-migration-ag-only-voter -m 2000000 -e 1087 --zst +src/flamenco/runtime/tests/run_ledger_backtest.sh -l alpenglow-migration-no-tower-points -m 2000000 -e 1125 --zst +src/flamenco/runtime/tests/run_ledger_backtest.sh -l alpenglow-migration-vote-recreate -m 2000000 -e 1637 --zst +src/flamenco/runtime/tests/run_ledger_backtest.sh -l alpenglow-migration-validators -m 2000000 -e 2112 --zst +src/flamenco/runtime/tests/run_ledger_backtest.sh -l alpenglow-migration-validators-boundary-snap -m 2000000 -e 1600 --zst +src/flamenco/runtime/tests/run_ledger_backtest.sh -l alpenglow-migration-validators-mid-payout -m 2000000 -e 1600 --zst +src/flamenco/runtime/tests/run_ledger_backtest.sh -l alpenglow-block-revenue-sharing-agave-v4.4-57a35916-pr15795 -m 2000000 -e 1400 +src/flamenco/runtime/tests/run_ledger_backtest.sh -l alpenglow-remove-inactive-stakes-agave-v4.4-6afe9919 -m 2000000 -e 1100 +src/flamenco/runtime/tests/run_ledger_backtest.sh -l alpenglow-block-revenue-sharing-remove-inactive-stakes-agave-v4.4-57a35916-pr15795 -m 2000000 -e 1400 diff --git a/src/flamenco/runtime/tests/run_ledger_backtest.sh b/src/flamenco/runtime/tests/run_ledger_backtest.sh index fa616886f91..ba5c0b99595 100755 --- a/src/flamenco/runtime/tests/run_ledger_backtest.sh +++ b/src/flamenco/runtime/tests/run_ledger_backtest.sh @@ -22,7 +22,6 @@ EXECRP_TILE_COUNT="10" SNAPDC_TILE_COUNT="" ROOT_DISTANCE="2" MAX_LIVE_SLOTS="32" -ALPENGLOW="false" CACHE_SIZE_GIB="3" PARTITION_SIZE_GIB="" GENESIS_MAX_FILE_SIZE_MIB="" @@ -111,10 +110,6 @@ while [[ $# -gt 0 ]]; do shift shift ;; - --alpenglow) - ALPENGLOW="true" - shift - ;; --cache-size-gib) CACHE_SIZE_GIB="$2" shift @@ -244,8 +239,6 @@ ${SNAPDC_TILE_COUNT:+ snapdc_tile_count = $SNAPDC_TILE_COUNT} snapshots = "$DUMP/$LEDGER" accounts = "${ACCOUNTS_DB:-$DUMP/accounts.db}" genesis = "$DUMP/$LEDGER/genesis.bin" -[consensus] - alpenglow = $ALPENGLOW [development] fixed_fec_sets = false [development.genesis] From 54be20cd7d08f6f9d3a92b962f62a2dda6e42575 Mon Sep 17 00:00:00 2001 From: Richard Patel Date: Wed, 21 Oct 2026 12:23:02 +0000 Subject: [PATCH 02/13] topology: remove Tower BFT support --- src/app/firedancer-dev/Local.mk | 3 - src/app/firedancer-dev/commands/backtest.c | 2 +- src/app/firedancer-dev/commands/gossip.c | 1 - src/app/firedancer-dev/commands/reasm.c | 103 -- src/app/firedancer-dev/commands/repair.c | 1426 ----------------- src/app/firedancer-dev/commands/rotor.c | 2 +- .../commands/send_test/Local.mk | 3 - .../commands/send_test/send_test.c | 308 ---- .../commands/send_test/send_test_helpers.c | 230 --- src/app/firedancer-dev/commands/tower.c | 174 -- src/app/firedancer-dev/main.h | 16 - .../tests/test_firedancer_dev.c | 2 - src/app/firedancer/callbacks.c | 4 +- src/app/firedancer/commands/wait.c | 9 +- src/app/firedancer/config/default.toml | 42 +- src/app/firedancer/config/devnet.toml | 1 - src/app/firedancer/config/testnet-jito.toml | 1 - src/app/firedancer/config/testnet.toml | 1 - src/app/firedancer/main.c | 8 - src/app/firedancer/topology.c | 374 +---- src/app/firedancer/topology.h | 1 - src/app/shared/boot/fd_boot.c | 4 - src/app/shared/commands/configure/configure.c | 1 - .../commands/configure/ethtool-channels.c | 7 +- .../shared/commands/configure/hyperthreads.c | 2 +- src/app/shared/commands/watch/watch.c | 58 +- src/app/shared/fd_action.h | 25 +- src/app/shared/fd_config.h | 9 - src/app/shared/fd_config_json.c | 9 +- src/app/shared/fd_config_parse.c | 8 +- src/app/shared/test_config_parse.c | 6 +- src/app/shared_dev/boot/fd_dev_boot.c | 3 - .../shared_dev/commands/configure/genesis.c | 5 - src/disco/topo/fd_topo.h | 1 - 34 files changed, 129 insertions(+), 2720 deletions(-) delete mode 100644 src/app/firedancer-dev/commands/reasm.c delete mode 100644 src/app/firedancer-dev/commands/repair.c delete mode 100644 src/app/firedancer-dev/commands/send_test/Local.mk delete mode 100644 src/app/firedancer-dev/commands/send_test/send_test.c delete mode 100644 src/app/firedancer-dev/commands/send_test/send_test_helpers.c delete mode 100644 src/app/firedancer-dev/commands/tower.c diff --git a/src/app/firedancer-dev/Local.mk b/src/app/firedancer-dev/Local.mk index 78cdc6c5f45..cb7619b204c 100644 --- a/src/app/firedancer-dev/Local.mk +++ b/src/app/firedancer-dev/Local.mk @@ -10,12 +10,9 @@ $(call add-objs,commands/bench,fd_firedancer_dev) $(call add-objs,commands/dev,fd_firedancer_dev) $(call add-objs,commands/backtest,fd_firedancer_dev) $(call add-objs,commands/snapshot_load,fd_firedancer_dev) -$(call add-objs,commands/repair,fd_firedancer_dev) $(call add-objs,commands/rotor,fd_firedancer_dev) -$(call add-objs,commands/tower,fd_firedancer_dev) $(call add-objs,commands/ipecho_server,fd_firedancer_dev) $(call add-objs,commands/gossip_dump,fd_firedancer_dev) -$(call add-objs,commands/reasm,fd_firedancer_dev) ifdef FD_ARCH_SUPPORTS_SANDBOX $(call make-bin,firedancer-dev,main,fd_firedancer_dev fd_firedancer fddev_shared fdctl_shared fdctl_platform fd_discof fd_disco fd_choreo fd_flamenco fd_waltz_test fd_quic fd_tls fd_reedsol fd_waltz fd_tango fd_ballet fd_util_extra fd_util) diff --git a/src/app/firedancer-dev/commands/backtest.c b/src/app/firedancer-dev/commands/backtest.c index 18b5bb8f126..556564448dd 100644 --- a/src/app/firedancer-dev/commands/backtest.c +++ b/src/app/firedancer-dev/commands/backtest.c @@ -385,7 +385,7 @@ backtest_topo( config_t * config ) { config->limits.max_shreds_per_block/FD_FEC_SHRED_CNT, config->limits.max_shreds_per_block ); ulong store_fec_max = config->firedancer.runtime.max_live_slots * fec_sets_per_slot + repair_out_link->depth + 1UL; ulong store_fec_data_max = fd_ulong_if( config->firedancer.development.fixed_fec_sets, 31840UL, 63985UL ); - fd_topo_obj_t * store_obj = setup_topo_store( topo, "store", store_fec_max, store_fec_data_max, 0UL, config->tiles.shred.shred_cache_size_mib, 0UL, config->limits.max_shreds_per_block, config->paths.shredb, 1, 1UL ); + fd_topo_obj_t * store_obj = setup_topo_store( topo, "store", store_fec_max, store_fec_data_max, 0UL, config->tiles.shred.shred_cache_size_mib, 0UL, config->limits.max_shreds_per_block, config->paths.shredb, 1UL ); fd_topob_tile_uses( topo, backt_tile, store_obj, FD_SHMEM_JOIN_MODE_READ_WRITE ); fd_topob_tile_uses( topo, replay_tile, store_obj, FD_SHMEM_JOIN_MODE_READ_WRITE ); FD_TEST( fd_pod_insertf_ulong( topo->props, store_obj->id, "store" ) ); diff --git a/src/app/firedancer-dev/commands/gossip.c b/src/app/firedancer-dev/commands/gossip.c index 4611d19a8a0..fcdfa0db276 100644 --- a/src/app/firedancer-dev/commands/gossip.c +++ b/src/app/firedancer-dev/commands/gossip.c @@ -29,7 +29,6 @@ gossip_cmd_topo( config_t * config ) { config->tiles.quic.regular_transaction_listen_port = 0U; config->tiles.repair.repair_client_listen_port = 0U; config->tiles.rserve.repair_serve_listen_port = 0U; - config->tiles.txsend.txsend_src_port = 0U; static ulong tile_to_cpu[ FD_TILE_MAX ] = {0}; /* TODO */ diff --git a/src/app/firedancer-dev/commands/reasm.c b/src/app/firedancer-dev/commands/reasm.c deleted file mode 100644 index e2955b78083..00000000000 --- a/src/app/firedancer-dev/commands/reasm.c +++ /dev/null @@ -1,103 +0,0 @@ -/* The reasm command joins the replay tile and prints the reasm tree. - This is a standalone development application that can be run to - inspect the replay tile memory. */ - -#include "../../shared/fd_config.h" /* config_t */ -#include "../../shared_dev/commands/dev.h" -#include "../../../discof/replay/fd_replay_tile_private.h" - -#include -#include - -fd_topo_run_tile_t -fdctl_tile_run( fd_topo_tile_t const * tile ); - -static void -replay_ctx_wksp( args_t * args, - config_t * config, - fd_replay_tile_t ** replay_ctx, - fd_topo_wksp_t ** replay_wksp ) { - (void)args; - - fd_topo_t * topo = &config->topo; - - ulong tile_id = fd_topo_find_tile( topo, "replay", 0UL ); - if( FD_UNLIKELY( tile_id==ULONG_MAX ) ) FD_LOG_ERR(( "replay tile not found" )); - - fd_topo_tile_t * tile = &topo->tiles[ tile_id ]; - - /* Get the workspace that contains the tile's scratch memory */ - ulong scratch_wksp_id = topo->objs[ tile->tile_obj_id ].wksp_id; - if( FD_UNLIKELY( scratch_wksp_id>=topo->wksp_cnt ) ) FD_LOG_ERR(( "invalid workspace id %lu for tile scratch", scratch_wksp_id )); - - fd_topo_wksp_t * _replay_wksp = &topo->workspaces[ scratch_wksp_id ]; - fd_topo_join_workspace( topo, _replay_wksp, FD_SHMEM_JOIN_MODE_READ_ONLY, FD_TOPO_CORE_DUMP_LEVEL_DISABLED ); - - /* Access the replay tile scratch memory where replay_tile_ctx is stored */ - void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id ); - if( FD_UNLIKELY( !scratch ) ) FD_LOG_ERR(( "Failed to access replay tile scratch memory" )); - - FD_SCRATCH_ALLOC_INIT( l, scratch ); - fd_replay_tile_t * _replay_ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_replay_tile_t), sizeof(fd_replay_tile_t) ); - - *replay_ctx = _replay_ctx; - *replay_wksp = _replay_wksp; -} - -static void -reasm_cmd_fn( args_t * args, - config_t * config ) { - (void)args; - fd_replay_tile_t * replay_ctx; - fd_topo_wksp_t * replay_wksp; - replay_ctx_wksp( args, config, &replay_ctx, &replay_wksp ); - - ulong reasm_gaddr = fd_wksp_gaddr_fast( replay_ctx->wksp, replay_ctx->reasm ); - fd_reasm_t * reasm = (fd_reasm_t *)fd_wksp_laddr( replay_wksp->wksp, reasm_gaddr ); - - for( ;; ) { - ulong root_slot = replay_ctx->consensus_root_slot; - if( root_slot == ULONG_MAX ) { - printf( "root_slot: ULONG_MAX (not set)\n" ); - } else { - printf( "root_slot: %lu\n", root_slot ); - } - - fd_reasm_print( reasm ); - - fflush( stdout ); - sleep( 1 ); - } -} - -static const char * HELP = - "\n\n" - "usage: reasm [-h]\n" - "\n" - "optional arguments:\n" - " -h, --help show this help message and exit\n"; - -void -reasm_cmd_help( char const * arg ) { - (void)arg; - FD_LOG_NOTICE(( "%s", HELP )); -} - -void -reasm_cmd_args( int * pargc, - char *** pargv, - args_t * args ) { - (void)pargc; - (void)pargv; - (void)args; - /* no args yet */ -} - -action_t fd_action_reasm = { - .name = "reasm", - .args = reasm_cmd_args, - .fn = reasm_cmd_fn, - .perm = dev_cmd_perm, - .description = "Join a running validator's replay tile and print its reassembly tree", -}; - diff --git a/src/app/firedancer-dev/commands/repair.c b/src/app/firedancer-dev/commands/repair.c deleted file mode 100644 index c2d0f48d642..00000000000 --- a/src/app/firedancer-dev/commands/repair.c +++ /dev/null @@ -1,1426 +0,0 @@ -/* The repair command spawns a smaller topology for profiling the repair - tile. This is a standalone application, and it can be run in mainnet, - testnet and/or a private cluster. */ - -#include "../../../discof/repair/fd_repair_tile.h" -#include "../../../disco/shred/fd_shred_tile.h" -#include "../../../disco/net/fd_net_tile.h" -#include "../../../disco/tiles.h" -#include "../../../disco/topo/fd_topob.h" -#include "../../../disco/topo/fd_cpu_topo.h" -#include "../../../util/pod/fd_pod_format.h" - -#include "../../firedancer/topology.h" -#include "../../shared/commands/configure/configure.h" -#include "../../shared/commands/run/run.h" /* initialize_workspaces */ -#include "../../shared/fd_config.h" /* config_t */ -#include "../../shared_dev/commands/dev.h" -#include "../../../disco/topo/fd_topob.h" -#include "../../../util/pod/fd_pod_format.h" -#include "../../../waltz/resolv/fd_io_readline.h" -#include "../../platform/fd_sys_util.h" -#include "../../shared/commands/monitor/helper.h" -#include "../../../disco/metrics/fd_metrics.h" -#include "../../../discof/restore/utils/fd_ssmanifest_parser.h" -#include "../../../discof/genesis/fd_genesi_tile.h" -#include "../../../flamenco/runtime/sysvar/fd_sysvar_epoch_schedule.h" -#include "../../../flamenco/stakes/fd_stake_weight_sort.h" -#include "../../../flamenco/leaders/fd_leaders_base.h" -#include "../../../discof/repair/fd_repair_tile_private.h" -#include "../../../discof/tower/fd_tower_tile.h" - -#include "gossip.h" -#include "core_subtopo.h" - -#include /* pause */ -#include -#include -#include -#include -#include - -extern action_t fd_action_repair; - -struct fd_location_info { - ulong ip4_addr; /* for map key convenience */ - char location[ 128 ]; -}; -typedef struct fd_location_info fd_location_info_t; - -#define MAP_NAME fd_location_table -#define MAP_T fd_location_info_t -#define MAP_KEY ip4_addr -#define MAP_LG_SLOT_CNT 16 -#define MAP_MEMOIZE 0 -#include "../../../util/tmpl/fd_map.c" - -uchar __attribute__((aligned(alignof(fd_location_info_t)))) location_table_mem[ sizeof(fd_location_info_t) * (1 << 16 ) ]; - -static struct termios termios_backup; - -static void -restore_terminal( void ) { - (void)tcsetattr( STDIN_FILENO, TCSANOW, &termios_backup ); -} - -extern fd_topo_obj_callbacks_t * CALLBACKS[]; - -fd_topo_run_tile_t -fdctl_tile_run( fd_topo_tile_t const * tile ); - -#define MANIFEST_LOAD_MAX_SZ (2UL * FD_SHMEM_GIGANTIC_PAGE_SZ) - -/* https://github.com/anza-xyz/agave/blob/v3.1.8/runtime/src/snapshot_bank_utils.rs#L632 */ -static int -repair_verify_epoch_stakes( fd_snapshot_manifest_t const * manifest ) { - fd_epoch_schedule_t epoch_schedule = (fd_epoch_schedule_t){ - .slots_per_epoch = manifest->epoch_schedule_params.slots_per_epoch, - .leader_schedule_slot_offset = manifest->epoch_schedule_params.leader_schedule_slot_offset, - .warmup = manifest->epoch_schedule_params.warmup, - .first_normal_epoch = manifest->epoch_schedule_params.first_normal_epoch, - .first_normal_slot = manifest->epoch_schedule_params.first_normal_slot, - }; - - ulong min_required_epoch = fd_slot_to_epoch( &epoch_schedule, manifest->slot, NULL ); - ulong max_required_epoch = fd_slot_to_leader_schedule_epoch( &epoch_schedule, manifest->slot ); - - for( ulong i=min_required_epoch; i<=max_required_epoch; i++ ) { - int found = 0; - for( ulong j=0UL; jepoch_stakes[j].epoch==i ) { - found = 1; - break; - } - } - if( FD_UNLIKELY( !found ) ) { - FD_LOG_WARNING(( "stakes not found for epoch %lu in manifest", i )); - return -1; - } - } - return 0; -} - -static inline ulong -repair_generate_epoch_info_msg( ulong epoch, - fd_epoch_schedule_t const * epoch_schedule, - fd_snapshot_manifest_epoch_stakes_t const * epoch_stakes, - ulong * epoch_info_msg_out ) { - fd_epoch_info_msg_t * epoch_info_msg = (fd_epoch_info_msg_t *)fd_type_pun( epoch_info_msg_out ); - fd_vote_stake_weight_t * stake_weights = fd_epoch_info_msg_stake_weights( epoch_info_msg ); - - epoch_info_msg->epoch = epoch; - epoch_info_msg->start_slot = fd_epoch_slot0( epoch_schedule, epoch ); - epoch_info_msg->slot_cnt = fd_epoch_slot_cnt( epoch_schedule, epoch ); - epoch_info_msg->ns_per_slot = FD_SLOT_PARAMS_400MS.ns_per_slot; - - fd_memset( &epoch_info_msg->features, 0xFF, sizeof(fd_features_t) ); - - ulong idx = 0UL; - for( ulong i=0UL; ivote_stakes_len; i++ ) { - ulong stake = epoch_stakes->vote_stakes[ i ].stake; - if( FD_UNLIKELY( !stake ) ) continue; - stake_weights[ idx ].stake = stake; - memcpy( stake_weights[ idx ].id_key.uc, epoch_stakes->vote_stakes[ i ].identity, sizeof(fd_pubkey_t) ); - memcpy( stake_weights[ idx ].vote_key.uc, epoch_stakes->vote_stakes[ i ].vote, sizeof(fd_pubkey_t) ); - idx++; - } - epoch_info_msg->staked_vote_cnt = idx; - sort_vote_weights_by_stake_vote_inplace( stake_weights, idx ); - - fd_stake_weight_t * id_weights = fd_epoch_info_msg_id_weights( epoch_info_msg ); - epoch_info_msg->staked_id_cnt = compute_id_weights_from_vote_weights( id_weights, stake_weights, epoch_info_msg->staked_vote_cnt ); - FD_TEST( idx<=MAX_SHRED_DESTS ); - - epoch_info_msg->epoch_schedule = *epoch_schedule; - return fd_epoch_info_msg_sz( epoch_info_msg->staked_vote_cnt, epoch_info_msg->staked_id_cnt ); -} - -/* repair_load_manifest loads the snapshot manifest from disk and - pre-populates the snapin_manif and replay_epoch dcache links so - that consumer tiles see the data on their first poll cycle. */ -static void -repair_load_manifest( fd_topo_t * topo, - char const * manifest_path ) { - if( FD_UNLIKELY( !manifest_path || !manifest_path[0] ) ) return; - - /* Parse manifest */ - - int fd = open( manifest_path, O_RDONLY ); - if( FD_UNLIKELY( fd<0 ) ) FD_LOG_ERR(( "open(%s) failed (%d-%s)", manifest_path, errno, fd_io_strerror( errno ) )); - - fd_snapshot_manifest_t * manifest = aligned_alloc( alignof(fd_snapshot_manifest_t), sizeof(fd_snapshot_manifest_t) ); - FD_TEST( manifest ); - for( ulong i=0UL; iepoch_stakes[i].epoch = ULONG_MAX; - - uchar * buf = aligned_alloc( 128UL, MANIFEST_LOAD_MAX_SZ ); - FD_TEST( buf ); - ulong buf_sz = 0; - FD_TEST( !fd_io_read( fd, buf, 0UL, MANIFEST_LOAD_MAX_SZ-1UL, &buf_sz ) ); - close( fd ); - - fd_ssmanifest_parser_t * parser = fd_ssmanifest_parser_join( fd_ssmanifest_parser_new( - aligned_alloc( fd_ssmanifest_parser_align(), fd_ssmanifest_parser_footprint() ) ) ); - FD_TEST( parser ); - fd_ssmanifest_parser_init( parser, manifest ); - int parser_err = fd_ssmanifest_parser_consume( parser, buf, buf_sz ); - FD_TEST( parser_err!=FD_SSMANIFEST_PARSER_ADVANCE_ERROR ); - FD_TEST( fd_ssmanifest_parser_fini( parser )==FD_SSMANIFEST_PARSER_ADVANCE_DONE ); - free( parser ); - free( buf ); - - FD_LOG_NOTICE(( "manifest bank slot %lu", manifest->slot )); - FD_TEST( !repair_verify_epoch_stakes( manifest ) ); - - /* Update root_slot fseq */ - - ulong root_slot_obj_id = fd_pod_queryf_ulong( topo->props, ULONG_MAX, "root_slot" ); - if( FD_LIKELY( root_slot_obj_id!=ULONG_MAX ) ) { - ulong * root_fseq = fd_fseq_join( fd_topo_obj_laddr( topo, root_slot_obj_id ) ); - FD_TEST( root_fseq ); - fd_fseq_update( root_fseq, manifest->slot ); - } - - /* Publish manifest to snapin_manif dcache */ - - ulong snap_link_idx = fd_topo_find_link( topo, "snapin_manif", 0UL ); - FD_TEST( snap_link_idx!=ULONG_MAX ); - fd_topo_link_t * snap_link = &topo->links[ snap_link_idx ]; - fd_wksp_t * snap_mem = topo->workspaces[ topo->objs[ snap_link->dcache_obj_id ].wksp_id ].wksp; - ulong snap_chunk0 = fd_dcache_compact_chunk0( snap_mem, snap_link->dcache ); - ulong snap_wmark = fd_dcache_compact_wmark ( snap_mem, snap_link->dcache, snap_link->mtu ); - ulong snap_chunk = snap_chunk0; - - uchar * snap_dst = fd_chunk_to_laddr( snap_mem, snap_chunk ); - memcpy( snap_dst, manifest, sizeof(fd_snapshot_manifest_t) ); - fd_mcache_publish( snap_link->mcache, snap_link->depth, 0UL, - fd_ssmsg_sig( FD_SSMSG_MANIFEST_INCREMENTAL ), - snap_chunk, sizeof(fd_snapshot_manifest_t), 0UL, 0UL, 0UL ); - snap_chunk = fd_dcache_compact_next( snap_chunk, sizeof(fd_snapshot_manifest_t), snap_chunk0, snap_wmark ); - - fd_mcache_publish( snap_link->mcache, snap_link->depth, 1UL, - fd_ssmsg_sig( FD_SSMSG_DONE ), 0UL, 0UL, 0UL, 0UL, 0UL ); - - /* Publish epoch stake weights to replay_epoch dcache */ - - ulong epoch_link_idx = fd_topo_find_link( topo, "replay_epoch", 0UL ); - FD_TEST( epoch_link_idx!=ULONG_MAX ); - fd_topo_link_t * epoch_link = &topo->links[ epoch_link_idx ]; - fd_wksp_t * epoch_mem = topo->workspaces[ topo->objs[ epoch_link->dcache_obj_id ].wksp_id ].wksp; - ulong epoch_chunk0 = fd_dcache_compact_chunk0( epoch_mem, epoch_link->dcache ); - ulong epoch_wmark = fd_dcache_compact_wmark ( epoch_mem, epoch_link->dcache, epoch_link->mtu ); - ulong epoch_chunk = epoch_chunk0; - ulong epoch_seq = 0UL; - - /* Construct fd_epoch_schedule_t field-by-field rather than type-punning - from the unpacked manifest struct (fd_epoch_schedule_t is packed). */ - fd_epoch_schedule_t schedule_local; - schedule_local.slots_per_epoch = manifest->epoch_schedule_params.slots_per_epoch; - schedule_local.leader_schedule_slot_offset = manifest->epoch_schedule_params.leader_schedule_slot_offset; - schedule_local.warmup = manifest->epoch_schedule_params.warmup; - schedule_local.first_normal_epoch = manifest->epoch_schedule_params.first_normal_epoch; - schedule_local.first_normal_slot = manifest->epoch_schedule_params.first_normal_slot; - fd_epoch_schedule_t const * schedule = &schedule_local; - ulong epoch = fd_slot_to_epoch( schedule, manifest->slot, NULL ); - - ulong epoch_stakes_base = epoch > 3UL ? epoch - 3UL : 0UL; - ulong leader_schedule_epoch = fd_slot_to_leader_schedule_epoch( schedule, manifest->slot ); - ulong cur_idx = epoch - epoch_stakes_base; - FD_TEST( cur_idx < FD_RUNTIME_MANIFEST_EPOCH_STAKES_LEN ); - - ulong * epoch_dst = fd_chunk_to_laddr( epoch_mem, epoch_chunk ); - ulong epoch_sz = repair_generate_epoch_info_msg( epoch, schedule, &manifest->epoch_stakes[cur_idx], epoch_dst ); - fd_mcache_publish( epoch_link->mcache, epoch_link->depth, epoch_seq, - 4UL, epoch_chunk, epoch_sz, 0UL, 0UL, fd_frag_meta_ts_comp( fd_tickcount() ) ); - epoch_chunk = fd_dcache_compact_next( epoch_chunk, epoch_sz, epoch_chunk0, epoch_wmark ); - epoch_seq++; - FD_LOG_NOTICE(( "sending current epoch stake weights - epoch: %lu", epoch )); - - if( leader_schedule_epoch >= epoch + 1UL ) { - ulong next_idx = epoch + 1UL - epoch_stakes_base; - FD_TEST( next_idx < FD_RUNTIME_MANIFEST_EPOCH_STAKES_LEN ); - - epoch_dst = fd_chunk_to_laddr( epoch_mem, epoch_chunk ); - epoch_sz = repair_generate_epoch_info_msg( epoch + 1UL, schedule, &manifest->epoch_stakes[next_idx], epoch_dst ); - fd_mcache_publish( epoch_link->mcache, epoch_link->depth, epoch_seq, - 4UL, epoch_chunk, epoch_sz, 0UL, 0UL, fd_frag_meta_ts_comp( fd_tickcount() ) ); - epoch_chunk = fd_dcache_compact_next( epoch_chunk, epoch_sz, epoch_chunk0, epoch_wmark ); - epoch_seq++; - FD_LOG_NOTICE(( "sending next epoch stake weights - epoch: %lu", epoch + 1UL )); - } - (void)epoch_chunk; - - free( manifest ); -} - -/* repair_topo is a subset of "src/app/firedancer/topology.c" at commit - 0d8386f4f305bb15329813cfe4a40c3594249e96, slightly modified to work - as a repair catchup. TODO ideally, one should invoke the firedancer - topology first, and exclude the parts that are not needed, instead of - manually generating new topologies for every command. This would - also guarantee that the catchup is replicating (as close as possible) - the full topology. */ -static void -repair_topo( config_t * config ) { - ulong net_tile_cnt = config->layout.net_tile_count; - ulong shred_tile_cnt = config->layout.shred_tile_count; - ulong quic_tile_cnt = config->layout.quic_tile_count; - ulong sign_tile_cnt = config->firedancer.layout.sign_tile_count; - ulong gossvf_tile_cnt = config->firedancer.layout.gossvf_tile_count; - - fd_topo_t * topo = { fd_topob_new( &config->topo, config->name ) }; - 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; - - ulong tile_to_cpu[ FD_TILE_MAX ] = {0}; - ushort parsed_tile_to_cpu[ FD_TILE_MAX ]; - /* Unassigned tiles will be floating, unless auto topology is enabled. */ - for( ulong i=0UL; ilayout.affinity, "auto" ); - int is_bench_auto_affinity = !strcmp( config->development.bench.affinity, "auto" ); - - if( FD_UNLIKELY( is_auto_affinity != is_bench_auto_affinity ) ) { - FD_LOG_ERR(( "The CPU affinity string in the configuration file under [layout.affinity] and [development.bench.affinity] must all be set to 'auto' or all be set to a specific CPU affinity string." )); - } - - fd_topo_cpus_t cpus[1]; - fd_topo_cpus_init( cpus ); - - ulong affinity_tile_cnt = 0UL; - if( FD_LIKELY( !is_auto_affinity ) ) affinity_tile_cnt = fd_topob_parse_affinity_cstr( config->layout.affinity, parsed_tile_to_cpu, 0, 1 ); - - for( ulong i=0UL; i=cpus->cpu_cnt ) ) - FD_LOG_ERR(( "The CPU affinity string in the configuration file under [layout.affinity] specifies a CPU index of %hu, but the system " - "only has %lu CPUs. You should either change the CPU allocations in the affinity string, or increase the number of CPUs " - "in the system.", - cpu_idx, cpus->cpu_cnt )); - tile_to_cpu[ i ] = fd_ulong_if( parsed_tile_to_cpu[ i ]==USHORT_MAX, ULONG_MAX, (ulong)parsed_tile_to_cpu[ i ] ); - } - - fd_core_subtopo( config, tile_to_cpu ); - fd_gossip_subtopo( config, tile_to_cpu ); - - /* topo, name */ - fd_topob_wksp( topo, "net_shred" ); - fd_topob_wksp( topo, "net_repair" ); - fd_topob_wksp( topo, "net_quic" ); - - fd_topob_wksp( topo, "shred_out" ); - fd_topob_wksp( topo, "replay_epoch" ); - - fd_topob_wksp( topo, "poh_shred" ); - - fd_topob_wksp( topo, "shred_sign" ); - fd_topob_wksp( topo, "sign_shred" ); - - fd_topob_wksp( topo, "repair_sign" ); - fd_topob_wksp( topo, "sign_repair" ); - fd_topob_wksp( topo, "rnonce" ); - fd_topob_wksp( topo, "repair_out" ); - - fd_topob_wksp( topo, "txsend_out" ); - - fd_topob_wksp( topo, "shred" ); - fd_topob_wksp( topo, "repair" ); - fd_topob_wksp( topo, "fec_sets" ); - fd_topob_wksp( topo, "snapin_manif" ); - - fd_topob_wksp( topo, "genesi_out" ); /* mock genesi_out for ipecho */ - - fd_topob_wksp( topo, "tower_out" ); /* mock tower_out for confirmation msgs. Not needed for any topo except eqvoc. */ - - #define FOR(cnt) for( ulong i=0UL; itiles.shred.max_pending_shred_sets * FD_REEDSOL_DATA_SHREDS_MAX, - FD_SHRED_STEM_BURST ) ), - USHORT_MAX + 1UL /* dcache max */ ); - - /* topo, link_name, wksp_name, depth, mtu, burst */ - FOR(quic_tile_cnt) fd_topob_link( topo, "quic_net", "net_quic", config->net.ingress_buffer_size, FD_NET_MTU, 1UL ); - FOR(shred_tile_cnt) fd_topob_link( topo, "shred_net", "net_shred", config->net.ingress_buffer_size, FD_NET_MTU, 1UL ); - - /**/ fd_topob_link( topo, "replay_epoch", "replay_epoch", 16UL, FD_EPOCH_OUT_MTU, 1UL ); - - FOR(shred_tile_cnt) fd_topob_link( topo, "shred_sign", "shred_sign", 128UL, 32UL, 1UL ); - FOR(shred_tile_cnt) fd_topob_link( topo, "sign_shred", "sign_shred", 128UL, 64UL, 1UL ); - - /**/ fd_topob_link( topo, "repair_net", "net_repair", config->net.ingress_buffer_size, FD_NET_MTU, 1UL ); - - FOR(shred_tile_cnt) fd_topob_link( topo, "shred_out", "shred_out", pending_fec_shreds_depth, sizeof(fd_shred_message_t), FD_SHRED_STEM_BURST ); - FOR(sign_tile_cnt-1) fd_topob_link( topo, "repair_sign", "repair_sign", 256UL, FD_REPAIR_MAX_PREIMAGE_SZ, 1UL ); - FOR(sign_tile_cnt-1) fd_topob_link( topo, "sign_repair", "sign_repair", 128UL, sizeof(fd_ed25519_sig_t), 1UL ); - - /**/ fd_topob_link( topo, "repair_out", "repair_out", 128UL, sizeof(fd_repair_fec_complete_t), 1UL ); - - /**/ fd_topob_link( topo, "poh_shred", "poh_shred", 16384UL, FD_POH_SHRED_MTU, 1UL ); - - /**/ fd_topob_link( topo, "txsend_out", "txsend_out", 128UL, FD_TXN_MTU, 1UL ); - - /**/ fd_topob_link( topo, "snapin_manif", "snapin_manif", 2UL, sizeof(fd_snapshot_manifest_t),1UL ); - - /**/ fd_topob_link( topo, "genesi_out", "genesi_out", 1UL, fd_genesi_tile_mtu( config->firedancer.development.genesis.max_file_size_mib<<20 ), 1UL ); - /**/ fd_topob_link( topo, "tower_out", "tower_out", 1024UL, sizeof(fd_tower_msg_t), 1UL ); - - FOR(net_tile_cnt) fd_topos_net_rx_link( topo, "net_repair", i, config->net.ingress_buffer_size ); - FOR(net_tile_cnt) fd_topos_net_rx_link( topo, "net_quic", i, config->net.ingress_buffer_size ); - FOR(net_tile_cnt) fd_topos_net_rx_link( topo, "net_shred", i, config->net.ingress_buffer_size ); - - /* topo, tile_name, tile_wksp, metrics_wksp, cpu_idx, uses_id_keyswitch, uses_av_keyswitch */ - FOR(shred_tile_cnt) fd_topob_tile( topo, "shred", "shred", "metric_in", tile_to_cpu[ topo->tile_cnt ], 1, 0, 0 ); - /**/ fd_topob_tile( topo, "repair", "repair", "metric_in", tile_to_cpu[ topo->tile_cnt ], 1, 0, 0 ); - - /* Setup a shared wksp object for fec sets. */ - - ulong fec_set_cnt = fd_shred_tile_fec_set_cnt( FD_SHRED_FIREDANCER_FEC_EXPOSURE, - config->tiles.shred.max_pending_shred_sets ); - ulong fec_sets_sz = fec_set_cnt*sizeof(fd_fec_set_t); - fd_topo_obj_t * fec_sets_obj = setup_topo_fec_sets( topo, "fec_sets", shred_tile_cnt*fec_sets_sz ); - for( ulong i=0UL; itiles[ fd_topo_find_tile( topo, "shred", i ) ]; - fd_topob_tile_uses( topo, shred_tile, fec_sets_obj, FD_SHMEM_JOIN_MODE_READ_WRITE ); - } - FD_TEST( fd_pod_insertf_ulong( topo->props, fec_sets_obj->id, "fec_sets" ) ); - - if( FD_LIKELY( !is_auto_affinity ) ) { - if( FD_UNLIKELY( affinity_tile_cnttile_cnt ) ) - FD_LOG_ERR(( "The topology you are using has %lu tiles, but the CPU affinity specified in the config tile as [layout.affinity] only provides for %lu cores. " - "You should either increase the number of cores dedicated to Firedancer in the affinity string, or decrease the number of cores needed by reducing " - "the total tile count. You can reduce the tile count by decreasing individual tile counts in the [layout] section of the configuration file.", - topo->tile_cnt, affinity_tile_cnt )); - if( FD_UNLIKELY( affinity_tile_cnt>topo->tile_cnt ) ) - FD_LOG_WARNING(( "The topology you are using has %lu tiles, but the CPU affinity specified in the config tile as [layout.affinity] provides for %lu cores. " - "Not all cores in the affinity will be used by Firedancer. You may wish to increase the number of tiles in the system by increasing " - "individual tile counts in the [layout] section of the configuration file.", - topo->tile_cnt, affinity_tile_cnt )); - } - - /* topo, tile_name, tile_kind_id, fseq_wksp, link_name, link_kind_id, reliable, polled */ - for( ulong j=0UL; jtiles[ fd_topo_find_tile( topo, "repair", 0UL ) ]; - fd_topob_tile_uses( topo, repair_tile, rnonce_ss_obj, FD_SHMEM_JOIN_MODE_READ_ONLY ); - for( ulong i=0UL; itiles[ fd_topo_find_tile( topo, "shred", i ) ]; - fd_topob_tile_uses( topo, shred_tile, rnonce_ss_obj, FD_SHMEM_JOIN_MODE_READ_ONLY ); - } - FD_TEST( fd_pod_insertf_ulong( topo->props, rnonce_ss_obj->id, "rnonce_ss" ) ); - } - - FD_TEST( fd_link_permit_no_producers( topo, "quic_net" ) == quic_tile_cnt ); - FD_TEST( fd_link_permit_no_producers( topo, "poh_shred" ) == 1UL ); - FD_TEST( fd_link_permit_no_producers( topo, "txsend_out" ) == 1UL ); - FD_TEST( fd_link_permit_no_producers( topo, "genesi_out" ) == 1UL ); - FD_TEST( fd_link_permit_no_producers( topo, "tower_out" ) == 1UL ); - FD_TEST( fd_link_permit_no_producers( topo, "replay_epoch" ) == 1UL ); - FD_TEST( fd_link_permit_no_producers( topo, "snapin_manif" ) == 1UL ); - FD_TEST( fd_link_permit_no_consumers( topo, "net_quic" ) == net_tile_cnt ); - FD_TEST( fd_link_permit_no_consumers( topo, "repair_out" ) == 1UL ); - - config->tiles.txsend.txsend_src_port = 0; /* disable txsend */ - - FOR(net_tile_cnt) fd_topos_net_tile_finish( topo, i ); - - for( ulong i=0UL; itile_cnt; i++ ) { - fd_topo_tile_t * tile = &topo->tiles[ i ]; - fd_topo_configure_tile( tile, config ); - } - - if( FD_UNLIKELY( is_auto_affinity ) ) fd_topob_auto_layout( topo ); - - fd_topob_waker( topo ); - fd_topob_finish( topo, CALLBACKS ); - - config->topo = *topo; -} - -static char * -fmt_count( char buf[ static 64 ], ulong count ) { - char tmp[ 64 ]; - if( FD_LIKELY( count<1000UL ) ) FD_TEST( fd_cstr_printf_check( tmp, 64UL, NULL, "%lu", count ) ); - else if( FD_LIKELY( count<1000000UL ) ) FD_TEST( fd_cstr_printf_check( tmp, 64UL, NULL, "%.1f K", (double)count/1000.0 ) ); - else if( FD_LIKELY( count<1000000000UL ) ) FD_TEST( fd_cstr_printf_check( tmp, 64UL, NULL, "%.1f M", (double)count/1000000.0 ) ); - - FD_TEST( fd_cstr_printf_check( buf, 64UL, NULL, "%12s", tmp ) ); - return buf; -} - -static void -print_histogram_buckets( volatile ulong * metrics, - ulong offset, - int converter, - ulong const * edges_ns, - ulong histmin, - ulong histmax, - char * title ) { - fd_histf_t hist[1]; - - /* Create histogram structure only to get bucket edges for display */ - if( FD_LIKELY( converter == FD_METRICS_CONVERTER_NANOSECONDS ) ) { - FD_TEST( fd_histf_new_edges( hist, edges_ns ) ); - } else if( FD_LIKELY( converter == FD_METRICS_CONVERTER_NONE ) ) { - FD_TEST( fd_histf_new( hist, histmin, histmax ) ); - } else { - FD_LOG_ERR(( "unknown converter %i", converter )); - } - - printf( " +---------------------+--------------------+--------------+\n" ); - printf( " | %-19s | | Count |\n", title ); - printf( " +---------------------+--------------------+--------------+\n" ); - - ulong total_count = 0; - for( ulong k = 0; k < FD_HISTF_BUCKET_CNT; k++ ) { - ulong bucket_count = metrics[ offset + k ]; - total_count += bucket_count; - } - - for( ulong k = 0; k < FD_HISTF_BUCKET_CNT; k++ ) { - /* Get individual bucket count directly from metrics array */ - ulong bucket_count = metrics[ offset + k ]; - - char * le_str; - char le_buf[ 64 ]; - if( FD_UNLIKELY( k == FD_HISTF_BUCKET_CNT - 1UL ) ) { - le_str = "+Inf"; - } else { - ulong edge = fd_histf_right( hist, k ); - if( FD_LIKELY( converter == FD_METRICS_CONVERTER_NANOSECONDS ) ) { - double edgef = (double)(edge - 1) / 1e9; - FD_TEST( fd_cstr_printf_check( le_buf, sizeof( le_buf ), NULL, "%.3f", edgef ) ); - } else { - FD_TEST( fd_cstr_printf_check( le_buf, sizeof( le_buf ), NULL, "%.3f", (double)(edge - 1) / 1000000.0 ) ); - } - le_str = le_buf; - } - - char count_buf[ 64 ]; - fmt_count( count_buf, bucket_count ); - - /* Match visual bar length to the %-18s display column width. */ - char bar_buf[ 19 ]; - ulong bar_max = sizeof( bar_buf ) - 1UL; - if( bucket_count > 0 && total_count > 0 ) { - ulong bar_length = (bucket_count * bar_max) / total_count; - if( bar_length == 0 ) bar_length = 1; - if( bar_length > bar_max ) bar_length = bar_max; - for( ulong i = 0; i < bar_length; i++ ) { bar_buf[ i ] = '|'; } - bar_buf[ bar_length ] = '\0'; - } else { - bar_buf[ 0 ] = '\0'; - } - - printf( " | %-19s | %-18s | %s |\n", le_str, bar_buf, count_buf ); - } - - /* Print sum and total count */ - char sum_buf[ 64 ]; - char avg_buf[ 64 ]; - if( FD_LIKELY( converter == FD_METRICS_CONVERTER_NANOSECONDS ) ) { - double sumf = (double)fd_metrics_convert_ticks_to_nanoseconds( metrics[ offset + FD_HISTF_BUCKET_CNT ] ) / 1e9; - FD_TEST( fd_cstr_printf_check( sum_buf, sizeof( sum_buf ), NULL, "%.6f", sumf ) ); - double avg = sumf / (double)total_count; - FD_TEST( fd_cstr_printf_check( avg_buf, sizeof( avg_buf ), NULL, "%.6f", avg ) ); - } else { - FD_TEST( fd_cstr_printf_check( sum_buf, sizeof( sum_buf ), NULL, "%lu", metrics[ offset + FD_HISTF_BUCKET_CNT ] )); - } - - printf( " +---------------------+--------------------+---------------+\n" ); - printf( " | Sum: %-14s | Count: %-11lu | Avg: %-8s |\n", sum_buf, total_count, avg_buf ); - printf( " +---------------------+--------------------+---------------+\n" ); -} - -static fd_slot_metrics_t temp_slots[ FD_CATCHUP_METRICS_MAX ]; - -static void -print_catchup_slots( fd_wksp_t * repair_tile_wksp, ctx_t * repair_ctx, int verbose, int sort_by_slot ) { - fd_repair_metrics_t * catchup = repair_ctx->slot_metrics; - ulong catchup_gaddr = fd_wksp_gaddr_fast( repair_ctx->wksp, catchup ); - fd_repair_metrics_t * catchup_table = (fd_repair_metrics_t *)fd_wksp_laddr( repair_tile_wksp, catchup_gaddr ); - if( FD_LIKELY( sort_by_slot ) ) { - fd_repair_metrics_print_sorted( catchup_table, verbose, temp_slots ); - } else { - fd_repair_metrics_print( catchup_table, verbose ); - } -} - -static fd_location_info_t * location_table; -static fd_pubkey_t peers_copy[ FD_REPAIR_PEER_MAX]; - -static ulong -sort_peers_by_latency( fd_policy_peer_map_t * active_table, fd_policy_peer_dlist_t * peers_dlist, fd_policy_peer_dlist_t * peers_wlist, fd_policy_peer_t * peers_arr ) { - ulong i = 0; - fd_policy_peer_dlist_iter_t iter = fd_policy_peer_dlist_iter_fwd_init( peers_dlist, peers_arr ); - while( !fd_policy_peer_dlist_iter_done( iter, peers_dlist, peers_arr ) ) { - fd_policy_peer_t * peer = fd_policy_peer_dlist_iter_ele( iter, peers_dlist, peers_arr ); - if( FD_UNLIKELY( !peer ) ) break; - peers_copy[ i++ ] = peer->key; - if( FD_UNLIKELY( i >= FD_REPAIR_PEER_MAX ) ) break; - iter = fd_policy_peer_dlist_iter_fwd_next( iter, peers_dlist, peers_arr ); - } - ulong fast_cnt = i; - iter = fd_policy_peer_dlist_iter_fwd_init( peers_wlist, peers_arr ); - while( !fd_policy_peer_dlist_iter_done( iter, peers_wlist, peers_arr ) ) { - fd_policy_peer_t * peer = fd_policy_peer_dlist_iter_ele( iter, peers_wlist, peers_arr ); - if( FD_UNLIKELY( !peer ) ) break; - peers_copy[ i++ ] = peer->key; - if( FD_UNLIKELY( i >= FD_REPAIR_PEER_MAX ) ) break; - iter = fd_policy_peer_dlist_iter_fwd_next( iter, peers_wlist, peers_arr ); - } - FD_LOG_NOTICE(( "Fast peers cnt: %lu. Slow peers cnt: %lu.", fast_cnt, i - fast_cnt )); - - ulong peer_cnt = i; - for( uint i = 0; i < peer_cnt - 1; i++ ) { - int swapped = 0; - for( uint j = 0; j < peer_cnt - 1 - i; j++ ) { - fd_policy_peer_t const * active_j = fd_policy_peer_map_ele_query( active_table, &peers_copy[ j ], NULL, peers_arr ); - fd_policy_peer_t const * active_j1 = fd_policy_peer_map_ele_query( active_table, &peers_copy[ j + 1 ], NULL, peers_arr ); - - /* Skip peers with no responses */ - double latency_j = 10e9; - double latency_j1 = 10e9; - if( FD_LIKELY( active_j && active_j->res_cnt > 0 ) ) latency_j = ((double)active_j->total_lat / (double)active_j->res_cnt); - if( FD_LIKELY( active_j1 && active_j1->res_cnt > 0 ) ) latency_j1 = ((double)active_j1->total_lat / (double)active_j1->res_cnt); - - /* Swap if j has higher latency than j+1 */ - if( latency_j > latency_j1 ) { - fd_pubkey_t temp = peers_copy[ j ]; - peers_copy[ j ] = peers_copy[ j + 1 ]; - peers_copy[ j + 1 ] = temp; - swapped = 1; - } - } - if( !swapped ) break; - } - return peer_cnt; -} - -static void -print_peer_location_latency( fd_wksp_t * repair_tile_wksp, ctx_t * tile_ctx ) { - ulong policy_gaddr = fd_wksp_gaddr_fast( tile_ctx->wksp, tile_ctx->policy ); - fd_policy_t * policy = fd_wksp_laddr ( repair_tile_wksp, policy_gaddr ); - ulong peermap_gaddr = fd_wksp_gaddr_fast( tile_ctx->wksp, policy->peers.map ); - ulong peerarr_gaddr = fd_wksp_gaddr_fast( tile_ctx->wksp, policy->peers.pool ); - ulong peerlst_gaddr = fd_wksp_gaddr_fast( tile_ctx->wksp, policy->peers.fast ); - ulong peerwst_gaddr = fd_wksp_gaddr_fast( tile_ctx->wksp, policy->peers.slow ); - fd_policy_peer_map_t * peers_map = (fd_policy_peer_map_t *) fd_wksp_laddr( repair_tile_wksp, peermap_gaddr ); - fd_policy_peer_dlist_t * peers_dlist = (fd_policy_peer_dlist_t *)fd_wksp_laddr( repair_tile_wksp, peerlst_gaddr ); - fd_policy_peer_dlist_t * peers_wlist = (fd_policy_peer_dlist_t *)fd_wksp_laddr( repair_tile_wksp, peerwst_gaddr ); - fd_policy_peer_t * peers_arr = (fd_policy_peer_t *) fd_wksp_laddr( repair_tile_wksp, peerarr_gaddr ); - - ulong peer_cnt = sort_peers_by_latency( peers_map, peers_dlist, peers_wlist, peers_arr ); - printf("\nPeer Location/Latency Information\n"); - printf( " | %-46s | %-7s | %-8s | %-8s | %-7s | %-7s | %-12s | %s\n", "Pubkey", "Req Cnt", "Req B/s", "Rx B/s", "Rx Rate", "Avg Latency", "Ewma Latency", "Location Info" ); - for( uint i = 0; i < peer_cnt; i++ ) { - fd_policy_peer_t const * active = fd_policy_peer_map_ele_query( peers_map, &peers_copy[ i ], NULL, peers_arr ); - if( FD_LIKELY( active && active->res_cnt > 0 ) ) { - fd_location_info_t * info = fd_location_table_query( location_table, active->ip4, NULL ); - char * geolocation = info ? info->location : ""; - double peer_bps = (double)(active->res_cnt * FD_SHRED_MIN_SZ) / ((double)(active->last_resp_ts - active->first_resp_ts) / 1e9); - double req_bps = (double)active->req_cnt * 202 / ((double)(active->last_req_ts - active->first_req_ts) / 1e9); - FD_BASE58_ENCODE_32_BYTES( active->key.key, key_b58 ); - printf( "%-5u | %-46s | %-7lu | %-8.2f | %-8.2f | %-7.2f | %10.3fms | %10.3fms | %s\n", i, key_b58, active->req_cnt, req_bps, peer_bps, (double)active->res_cnt / (double)active->req_cnt, ((double)active->total_lat / (double)active->res_cnt) / 1e6, (double)active->ewma_lat / 1e6, geolocation ); - } - } - printf("\n"); - fflush( stdout ); -} - -static void -read_iptable( char * iptable_path, fd_location_info_t * location_table ) { - int iptable_fd = open( iptable_path, O_RDONLY ); - if( FD_UNLIKELY( iptable_fd<0 ) ) return; - - /* read iptable line by line */ - if( FD_LIKELY( iptable_fd>=0 ) ) { - char line[ 256 ]; - uchar istream_buf[256]; - fd_io_buffered_istream_t istream[1]; - fd_io_buffered_istream_init( istream, iptable_fd, istream_buf, sizeof(istream_buf) ); - for(;;) { - int err; - if( !fd_io_fgets( line, sizeof(line), istream, &err ) ) break; - fd_location_info_t location_info; - sscanf( line, "%lu %[^\n]", &location_info.ip4_addr, location_info.location ); - fd_location_info_t * info = fd_location_table_insert( location_table, location_info.ip4_addr ); - if( FD_UNLIKELY( info==NULL ) ) break; - memcpy( info->location, location_info.location, sizeof(info->location) ); - } - } -} - -static void -print_tile_metrics( volatile ulong * shred_metrics, - volatile ulong * repair_metrics, - volatile ulong * repair_metrics_prev, /* for diffing metrics */ - volatile ulong ** repair_net_links, - volatile ulong ** net_shred_links, - ulong net_tile_cnt, - ulong * last_sent_cnt, - long last_print_ts, - long now ) { - char buf2[ 64 ]; - ulong rcvd = shred_metrics [ MIDX( COUNTER, SHRED, REPAIR_RX ) ]; - ulong sent = repair_metrics[ MIDX( COUNTER, REPAIR, REQUEST_TX_WINDOW_INDEX ) ] + - repair_metrics[ MIDX( COUNTER, REPAIR, REQUEST_TX_HIGHEST_WINDOW_INDEX ) ] + - repair_metrics[ MIDX( COUNTER, REPAIR, REQUEST_TX_ORPHAN ) ]; - printf(" Requests received: (%lu/%lu) %.1f%% \n", rcvd, sent, (double)rcvd / (double)sent * 100.0 ); - printf( " +---------------+--------------+\n" ); - printf( " | Request Type | Count |\n" ); - printf( " +---------------+--------------+\n" ); - printf( " | Orphan | %s |\n", fmt_count( buf2, repair_metrics[ MIDX( COUNTER, REPAIR, REQUEST_TX_ORPHAN ) ] ) ); - printf( " | HighestWindow | %s |\n", fmt_count( buf2, repair_metrics[ MIDX( COUNTER, REPAIR, REQUEST_TX_HIGHEST_WINDOW_INDEX ) ] ) ); - printf( " | Index | %s |\n", fmt_count( buf2, repair_metrics[ MIDX( COUNTER, REPAIR, REQUEST_TX_WINDOW_INDEX ) ] ) ); - printf( " +---------------+--------------+\n" ); - printf( " Send Pkt Rate: %s pps\n", fmt_count( buf2, (ulong)((sent - *last_sent_cnt)*1e9L / (now - last_print_ts) ) ) ); - *last_sent_cnt = sent; - - /* Sum overrun across all net tiles connected to repair_net */ - ulong total_overrun = repair_net_links[0][ MIDX( COUNTER, LINK, FRAG_POLLING_OVERRUN ) ]; /* coarse double counting prevention */ - ulong total_consumed = 0UL; - for( ulong i = 0UL; i < net_tile_cnt; i++ ) { - volatile ulong * ovar_net_metrics = repair_net_links[i]; - total_overrun += ovar_net_metrics[ MIDX( COUNTER, LINK, FRAG_READING_OVERRUN ) ]; - total_consumed += ovar_net_metrics[ MIDX( COUNTER, LINK, FRAG_CONSUMED ) ]; /* consumed is incremented after after_frag is called */ - } - printf( " Outgoing requests overrun: %s\n", fmt_count( buf2, total_overrun ) ); - printf( " Outgoing requests consumed: %s\n", fmt_count( buf2, total_consumed ) ); - - total_overrun = net_shred_links[0][ MIDX( COUNTER, LINK, FRAG_READING_OVERRUN ) ]; - total_consumed = 0UL; - for( ulong i = 0UL; i < net_tile_cnt; i++ ) { - volatile ulong * ovar_net_metrics = net_shred_links[i]; - total_overrun += ovar_net_metrics[ MIDX( COUNTER, LINK, FRAG_READING_OVERRUN ) ]; - total_consumed += ovar_net_metrics[ MIDX( COUNTER, LINK, FRAG_CONSUMED ) ]; /* shred frag filtering happens manually in after_frag, so no need to index every shred_tile. */ - } - - printf( " Incoming shreds overrun: %s\n", fmt_count( buf2, total_overrun ) ); - printf( " Incoming shreds consumed: %s\n", fmt_count( buf2, total_consumed ) ); - - print_histogram_buckets( repair_metrics, - MIDX( HISTOGRAM, REPAIR, RESPONSE_LATENCY_NANOS ), - FD_METRICS_CONVERTER_NONE, - NULL, - FD_METRICS_HISTOGRAM_REPAIR_RESPONSE_LATENCY_NANOS_MIN, - FD_METRICS_HISTOGRAM_REPAIR_RESPONSE_LATENCY_NANOS_MAX, - "Response Latency" ); - - printf(" Repair Peers: %lu\n", repair_metrics[ MIDX( GAUGE, REPAIR, PEER_COUNT ) ] ); - printf(" Shreds rejected (no stakes): %lu\n", shred_metrics[ MIDX( COUNTER, SHRED, PROCESSED ) ] ); - /* Print histogram buckets similar to Prometheus format */ - print_histogram_buckets( repair_metrics, - MIDX( HISTOGRAM, REPAIR, SLOT_COMPLETE_DURATION_NANOS ), - FD_METRICS_CONVERTER_NANOSECONDS, - FD_MHIST_EDGES_NS( REPAIR, SLOT_COMPLETE_DURATION_NANOS ), - 0UL, /* unused with edges */ - 0UL, - "Slot Complete Time" ); - -#define DIFFG(GROUP) ( fd_metrics_enum_group_sum( repair_metrics+MIDX( COUNTER, TILE, REGIME_DURATION_NANOS ), FD_METRICS_ENUM_TILE_REGIME_G_##GROUP##_MASK ) - \ - fd_metrics_enum_group_sum( repair_metrics_prev+MIDX( COUNTER, TILE, REGIME_DURATION_NANOS ), FD_METRICS_ENUM_TILE_REGIME_G_##GROUP##_MASK ) ) - ulong hkeep_ticks = DIFFG(HOUSEKEEPING); - ulong busy_ticks = DIFFG(BUSY); - ulong caught_up_ticks = DIFFG(IDLE); - ulong backpressure_ticks = DIFFG(BACKPRESSURE); - ulong total_ticks = hkeep_ticks + busy_ticks + caught_up_ticks + backpressure_ticks; - - printf( " Repair Hkeep: %.1f %% Busy: %.1f %% Idle: %.1f %% Backp: %0.1f %%\n", - (double)hkeep_ticks/(double)total_ticks*100.0, - (double)busy_ticks/(double)total_ticks*100.0, - (double)caught_up_ticks/(double)total_ticks*100.0, - (double)backpressure_ticks/(double)total_ticks*100.0 ); -#undef DIFFG - fflush( stdout ); - - printf( " Block failed insert: %lu\n", repair_metrics[ MIDX( COUNTER, REPAIR, BLOCK_INSERT_FAILED ) ] ); - printf( " Block evicted: %lu\n", repair_metrics[ MIDX( COUNTER, REPAIR, BLOCK_EVICTED ) ] ); - printf( " slot evicted: %lu\n", repair_metrics[ MIDX( GAUGE, REPAIR, LAST_EVICTED_SLOT ) ] ); - printf( " slot evicted by: %lu\n", repair_metrics[ MIDX( GAUGE, REPAIR, LAST_EVICTION_CAUSE_SLOT ) ] ); - printf( " slot failed insert: %lu\n", repair_metrics[ MIDX( GAUGE, REPAIR, LAST_INSERT_FAILED_SLOT ) ] ); - for( ulong i=0UL; itopo; - ulong wksp_id = fd_topo_find_wksp( topo, "repair" ); - if( FD_UNLIKELY( wksp_id==ULONG_MAX ) ) FD_LOG_ERR(( "repair workspace not found" )); - - fd_topo_wksp_t * _repair_wksp = &topo->workspaces[ wksp_id ]; - - ulong tile_id = fd_topo_find_tile( topo, "repair", 0UL ); - if( FD_UNLIKELY( tile_id==ULONG_MAX ) ) FD_LOG_ERR(( "repair tile not found" )); - - fd_topo_join_workspace( topo, _repair_wksp, FD_SHMEM_JOIN_MODE_READ_ONLY, FD_TOPO_CORE_DUMP_LEVEL_DISABLED ); - - /* Access the repair tile scratch memory where repair_tile_ctx is stored */ - fd_topo_tile_t * tile = &topo->tiles[ tile_id ]; - void * scratch = fd_topo_obj_laddr( &config->topo, tile->tile_obj_id ); - if( FD_UNLIKELY( !scratch ) ) FD_LOG_ERR(( "Failed to access repair tile scratch memory" )); - - FD_SCRATCH_ALLOC_INIT( l, scratch ); - ctx_t * _repair_ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof(ctx_t), sizeof(ctx_t) ); - - *repair_ctx = _repair_ctx; - *repair_wksp = _repair_wksp; -} - -static void -repair_cmd_fn_catchup( args_t * args, - config_t * config ) { - - memset( &config->topo, 0, sizeof(config->topo) ); - repair_topo( config ); - - fd_topo_print_log( 1, &config->topo ); - - args_t configure_args = { - .configure.command = CONFIGURE_CMD_INIT, - }; - for( ulong i=0UL; STAGES[ i ]; i++ ) { - configure_args.configure.stages[ i ] = STAGES[ i ]; - } - configure_cmd_fn( &configure_args, config ); - if( 0==strcmp( config->net.provider, "xdp" ) ) { - fd_topo_install_xdp_simple( &config->topo, config->net.bind_address_parsed ); - } - run_firedancer_init( config, 1, 0 ); - - fd_topo_join_workspaces( &config->topo, FD_SHMEM_JOIN_MODE_READ_WRITE, FD_TOPO_CORE_DUMP_LEVEL_DISABLED ); - if( 0==strcmp( config->net.provider, "mlx5" ) ) { - fd_topo_install_mlx5( &config->topo, NULL ); - } - - fd_topo_fill( &config->topo ); - - repair_load_manifest( &config->topo, args->repair.manifest_path ); - - /* Access repair workspace memory and metrics */ - - ulong repair_tile_idx = fd_topo_find_tile( &config->topo, "repair", 0UL ); - ulong shred_tile_idx = fd_topo_find_tile( &config->topo, "shred", 0UL ); - FD_TEST( repair_tile_idx!=ULONG_MAX ); - FD_TEST( shred_tile_idx !=ULONG_MAX ); - fd_topo_tile_t * repair_tile = &config->topo.tiles[ repair_tile_idx ]; - fd_topo_tile_t * shred_tile = &config->topo.tiles[ shred_tile_idx ]; - - fd_topo_wksp_t * repair_wksp; - ctx_t * repair_ctx; - repair_ctx_wksp( args, config, &repair_ctx, &repair_wksp ); - - volatile ulong * shred_metrics = fd_metrics_tile( shred_tile->metrics ); - volatile ulong * repair_metrics = fd_metrics_tile( repair_tile->metrics ); - FD_TEST( repair_metrics ); - ulong * repair_metrics_prev = aligned_alloc( 8UL, sizeof(ulong) * FD_METRICS_TOTAL_SZ ); - FD_TEST( repair_metrics_prev ); - memset( repair_metrics_prev, 0, sizeof(ulong) * FD_METRICS_TOTAL_SZ ); - - /* Collect link metrics */ - - /* Collect all net tiles and their repair_net link metrics */ - ulong net_cnt = config->layout.net_tile_count; - volatile ulong ** repair_net_links = aligned_alloc( 8UL, net_cnt * sizeof(volatile ulong*) ); - volatile ulong ** net_shred_links = aligned_alloc( 8UL, net_cnt * sizeof(volatile ulong*) ); - FD_TEST( repair_net_links ); - FD_TEST( net_shred_links ); - - char const * net_tile_name = fd_net_tile_name( config->net.provider ); - for( ulong i = 0UL; i < net_cnt; i++ ) { - ulong tile_idx = fd_topo_find_tile( &config->topo, net_tile_name, i ); - if( FD_UNLIKELY( tile_idx == ULONG_MAX ) ) FD_LOG_ERR(( "net tile %lu not found", i )); - fd_topo_tile_t * tile = &config->topo.tiles[ tile_idx ]; - - ulong repair_net_in_idx = fd_topo_find_tile_in_link( &config->topo, tile, "repair_net", 0UL ); - if( FD_UNLIKELY( repair_net_in_idx == ULONG_MAX ) ) FD_LOG_ERR(( "repair_net link not found for net tile %lu", i )); - FD_TEST( tile->metrics ); - repair_net_links[i] = fd_metrics_link_in( tile->metrics, repair_net_in_idx ); - FD_TEST( repair_net_links[i] ); - - /* process all net_shred links */ - ulong shred_tile_idx = fd_topo_find_tile( &config->topo, "shred", 0 ); - if( FD_UNLIKELY( shred_tile_idx == ULONG_MAX ) ) FD_LOG_ERR(( "shred tile 0 not found" )); - fd_topo_tile_t * shred_tile = &config->topo.tiles[ shred_tile_idx ]; - - ulong shred_out_in_idx = fd_topo_find_tile_in_link( &config->topo, shred_tile, "net_shred", i ); - if( FD_UNLIKELY( shred_out_in_idx == ULONG_MAX ) ) FD_LOG_ERR(( "net_shred link not found for shred tile 0" )); - FD_TEST( shred_tile->metrics ); - net_shred_links[i] = fd_metrics_link_in( shred_tile->metrics, shred_out_in_idx ); - FD_TEST( net_shred_links[i] ); - } - - FD_LOG_NOTICE(( "Repair catchup run" )); - - ulong shred_out_link_idx = fd_topo_find_link( &config->topo, "shred_out", 0UL ); - FD_TEST( shred_out_link_idx!=ULONG_MAX ); - fd_topo_link_t * shred_out_link = &config->topo.links[ shred_out_link_idx ]; - fd_frag_meta_t * shred_out_mcache = shred_out_link->mcache; - void * shred_out_dcache = config->topo.workspaces[ config->topo.objs[ shred_out_link->dcache_obj_id ].wksp_id ].wksp; - - ulong turbine_slot0 = 0; - long last_print = fd_log_wallclock(); - ulong last_sent = 0UL; - - fd_topo_run_single_process( &config->topo, config->uid, config->gid, fdctl_tile_run ); - for(;;) { - - if( FD_UNLIKELY( !turbine_slot0 ) ) { - fd_frag_meta_t * frag = &shred_out_mcache[0]; /* hack to get first frag */ - if ( frag->sz > 0 ) { - uchar * shred_out_chunk = fd_chunk_to_laddr( shred_out_dcache, frag->chunk ); - fd_shred_base_t * shred_out_shred = (fd_shred_base_t *)fd_type_pun( shred_out_chunk ); - turbine_slot0 = shred_out_shred->shred.slot; - FD_LOG_NOTICE(("turbine_slot0: %lu", turbine_slot0)); - } - } - - /* print metrics */ - - long now = fd_log_wallclock(); - int catchup_finished = 0; - if( FD_UNLIKELY( now - last_print > 1e9L ) ) { - print_tile_metrics( shred_metrics, repair_metrics, repair_metrics_prev, repair_net_links, net_shred_links, net_cnt, &last_sent, last_print, now ); - ulong slots_behind = turbine_slot0 > repair_metrics[ MIDX( GAUGE, REPAIR, HIGHEST_REPAIRED_SLOT ) ] ? turbine_slot0 - repair_metrics[ MIDX( GAUGE, REPAIR, HIGHEST_REPAIRED_SLOT ) ] : 0; - printf(" Repaired slots: %lu/%lu (slots behind: %lu)\n", repair_metrics[ MIDX( GAUGE, REPAIR, HIGHEST_REPAIRED_SLOT ) ], turbine_slot0, slots_behind ); - if( turbine_slot0 && !slots_behind ) { - catchup_finished = 1; - } - printf("\n"); - fflush( stdout ); - last_print = now; - } - - if( FD_UNLIKELY( catchup_finished ) ) { - /* repair cmd owned memory */ - location_table = fd_location_table_join( fd_location_table_new( location_table_mem ) ); - read_iptable( args->repair.iptable_path, location_table ); - print_peer_location_latency( repair_wksp->wksp, repair_ctx ); - print_catchup_slots( repair_wksp->wksp, repair_ctx, 0, 1 ); - FD_LOG_NOTICE(("Catchup to slot %lu completed successfully", turbine_slot0)); - fd_sys_util_exit_group( 0 ); - } - } -} - -/* Tests equivocation detection & repair path. */ -static void -repair_cmd_fn_eqvoc( args_t * args, - config_t * config ) { - (void)args; - memset( &config->topo, 0, sizeof(config->topo) ); - repair_topo( config ); - - FD_LOG_NOTICE(( "Repair eqvoc testing init" )); - fd_topo_print_log( 1, &config->topo ); - - args_t configure_args = { .configure.command = CONFIGURE_CMD_INIT, }; - for( ulong i=0UL; STAGES[ i ]; i++ ) configure_args.configure.stages[ i ] = STAGES[ i ]; - configure_cmd_fn( &configure_args, config ); - if( 0==strcmp( config->net.provider, "xdp" ) ) fd_topo_install_xdp_simple( &config->topo, config->net.bind_address_parsed ); - - run_firedancer_init( config, 1, 0 ); - fd_topo_join_workspaces( &config->topo, FD_SHMEM_JOIN_MODE_READ_WRITE, FD_TOPO_CORE_DUMP_LEVEL_DISABLED ); - if( 0==strcmp( config->net.provider, "mlx5" ) ) { - fd_topo_install_mlx5( &config->topo, NULL ); - } - fd_topo_fill( &config->topo ); - - repair_load_manifest( &config->topo, args->repair.manifest_path ); - - ulong repair_tile_idx = fd_topo_find_tile( &config->topo, "repair", 0UL ); - fd_topo_tile_t * repair_tile = &config->topo.tiles[ repair_tile_idx ]; - volatile ulong * repair_metrics = fd_metrics_tile( repair_tile->metrics ); - - void * scratch = fd_topo_obj_laddr( &config->topo, repair_tile->tile_obj_id ); - if( FD_UNLIKELY( !scratch ) ) FD_LOG_ERR(( "Failed to access repair tile scratch memory" )); - FD_SCRATCH_ALLOC_INIT( l, scratch ); - ctx_t * repair_ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof(ctx_t), sizeof(ctx_t) ); - (void)repair_ctx; - - /* read tower_out mcache dcache */ - ulong tower_out_link_idx = fd_topo_find_link( &config->topo, "tower_out", 0UL ); - FD_TEST( tower_out_link_idx!=ULONG_MAX ); - fd_topo_link_t * tower_out_link = &config->topo.links[ tower_out_link_idx ]; - fd_frag_meta_t * tower_out_mcache = tower_out_link->mcache; - fd_wksp_t * tower_out_mem = config->topo.workspaces[ config->topo.objs[ tower_out_link->dcache_obj_id ].wksp_id ].wksp; - ulong tower_out_chunk0 = fd_dcache_compact_chunk0( tower_out_mem, tower_out_link->dcache ); - ulong tower_out_wmark = fd_dcache_compact_wmark( tower_out_mem, tower_out_link->dcache, tower_out_link->mtu ); - ulong tower_out_chunk = tower_out_chunk0; - - fd_topo_run_single_process( &config->topo, config->uid, config->gid, fdctl_tile_run ); - int confirmed = 0; - for(;;) { - /* publish a confirmation on tower_out */ - if( FD_UNLIKELY( !confirmed && repair_metrics[ MIDX( GAUGE, REPAIR, HIGHEST_REPAIRED_SLOT ) ] != 0 ) ) { - fd_tower_slot_confirmed_t * msg = fd_chunk_to_laddr( tower_out_mem, tower_out_chunk ); - FD_LOG_NOTICE(( "publishing confirmation for slot %lu", msg->slot )); - fd_mcache_publish( tower_out_mcache, tower_out_link->depth, 0, FD_TOWER_SIG_SLOT_CONFIRMED, tower_out_chunk, sizeof(fd_tower_slot_confirmed_t), 0, 0, 0 ); - tower_out_chunk = fd_dcache_compact_next( tower_out_chunk, sizeof(fd_tower_slot_confirmed_t), tower_out_chunk0, tower_out_wmark ); - confirmed = 1; - } - sleep( 1 ); - } -} - -static void -repair_cmd_fn_metrics( args_t * args, - config_t * config ) { - //memset( &config->topo, 0, sizeof(config->topo) ); - - fd_topo_join_workspaces( &config->topo, FD_SHMEM_JOIN_MODE_READ_ONLY, FD_TOPO_CORE_DUMP_LEVEL_DISABLED ); - fd_topo_fill( &config->topo ); - - ctx_t * repair_ctx; - fd_topo_wksp_t * repair_wksp; - repair_ctx_wksp( args, config, &repair_ctx, &repair_wksp ); - - ulong shred_tile_idx = fd_topo_find_tile( &config->topo, "shred", 0UL ); - ulong repair_tile_idx = fd_topo_find_tile( &config->topo, "repair", 0UL ); - FD_TEST( shred_tile_idx != ULONG_MAX ); - FD_TEST( repair_tile_idx!= ULONG_MAX ); - fd_topo_tile_t * shred_tile = &config->topo.tiles[ shred_tile_idx ]; - fd_topo_tile_t * repair_tile = &config->topo.tiles[ repair_tile_idx ]; - - volatile ulong * shred_metrics = fd_metrics_tile( shred_tile->metrics ); - FD_TEST( shred_metrics ); - - volatile ulong * repair_metrics = fd_metrics_tile( repair_tile->metrics ); - FD_TEST( repair_metrics ); - ulong * repair_metrics_prev = aligned_alloc( 8UL, sizeof(ulong) * FD_METRICS_TOTAL_SZ ); - FD_TEST( repair_metrics_prev ); - memset( repair_metrics_prev, 0, sizeof(ulong) * FD_METRICS_TOTAL_SZ ); - - - ulong net_tile_cnt = config->layout.net_tile_count; - volatile ulong ** repair_net_links = aligned_alloc( 8UL, net_tile_cnt * sizeof(volatile ulong*) ); - volatile ulong ** net_shred_links = aligned_alloc( 8UL, net_tile_cnt * sizeof(volatile ulong*) ); - FD_TEST( repair_net_links ); - FD_TEST( net_shred_links ); - - char const * net_tile_name = fd_net_tile_name( config->net.provider ); - for( ulong i = 0UL; i < net_tile_cnt; i++ ) { - /* process all repair_net links */ - ulong tile_idx = fd_topo_find_tile( &config->topo, net_tile_name, i ); - if( FD_UNLIKELY( tile_idx == ULONG_MAX ) ) FD_LOG_ERR(( "net tile %lu not found", i )); - fd_topo_tile_t * tile = &config->topo.tiles[ tile_idx ]; - - ulong repair_net_in_idx = fd_topo_find_tile_in_link( &config->topo, tile, "repair_net", 0UL ); - if( FD_UNLIKELY( repair_net_in_idx == ULONG_MAX ) ) FD_LOG_ERR(( "repair_net link not found for net tile %lu", i )); - repair_net_links[i] = fd_metrics_link_in( tile->metrics, repair_net_in_idx ); - FD_TEST( repair_net_links[i] ); - - /* process all net_shred links */ - tile_idx = fd_topo_find_tile( &config->topo, "shred", 0 ); - if( FD_UNLIKELY( tile_idx == ULONG_MAX ) ) FD_LOG_ERR(( "shred tile 0 not found" )); - fd_topo_tile_t * shred_tile = &config->topo.tiles[ tile_idx ]; - - ulong shred_out_in_idx = fd_topo_find_tile_in_link( &config->topo, shred_tile, "net_shred", i ); - if( FD_UNLIKELY( shred_out_in_idx == ULONG_MAX ) ) FD_LOG_ERR(( "net_shred link not found for shred tile 0" )); - net_shred_links[i] = fd_metrics_link_in( shred_tile->metrics, shred_out_in_idx ); - FD_TEST( net_shred_links[i] ); - } - - long last_print_ts = fd_log_wallclock(); - ulong last_sent = 0UL; - for(;;) { - long now = fd_log_wallclock(); - if( FD_UNLIKELY( now - last_print_ts > 1e9L ) ) { - print_tile_metrics( shred_metrics, repair_metrics, repair_metrics_prev, repair_net_links, net_shred_links, net_tile_cnt, &last_sent, last_print_ts, now ); - last_print_ts = now; - } - } -} - -static void -repair_cmd_fn_forest( args_t * args, - config_t * config ) { - ctx_t * repair_ctx; - fd_topo_wksp_t * repair_wksp; - repair_ctx_wksp( args, config, &repair_ctx, &repair_wksp ); - - ulong forest_gaddr = fd_wksp_gaddr_fast( repair_ctx->wksp, repair_ctx->forest ); - fd_forest_t * forest = (fd_forest_t *)fd_wksp_laddr( repair_wksp->wksp, forest_gaddr ); - - for( ;; ) { - fd_forest_print( forest ); - sleep( 1 ); - } -} - -static void -repair_cmd_fn_inflight( args_t * args, - config_t * config ) { - ctx_t * repair_ctx; - fd_topo_wksp_t * repair_wksp; - repair_ctx_wksp( args, config, &repair_ctx, &repair_wksp ); - - ulong inflights_gaddr = fd_wksp_gaddr_fast( repair_ctx->wksp, repair_ctx->inflights ); - fd_inflights_t * inflights = (fd_inflights_t *)fd_wksp_laddr( repair_wksp->wksp, inflights_gaddr ); - - ulong inflight_pool_off = (ulong)inflights->pool - (ulong)repair_ctx->inflights; - fd_inflight_t * inflight_pool = (fd_inflight_t *)fd_wksp_laddr( repair_wksp->wksp, inflights_gaddr + inflight_pool_off ); - - for( ;; ) { - fd_inflights_print( inflights->outstanding_dl, inflight_pool ); - printf("popped count: %lu\n", inflights->popped_cnt); - fd_inflights_print( inflights->popped_dl, inflight_pool ); - sleep( 1 ); - } -} - -static void -repair_cmd_fn_requests( args_t * args, - config_t * config ) { - ctx_t * repair_ctx; - fd_topo_wksp_t * repair_wksp; - repair_ctx_wksp( args, config, &repair_ctx, &repair_wksp ); - - fd_forest_t * forest = fd_forest_join( fd_wksp_laddr( repair_wksp->wksp, fd_wksp_gaddr_fast( repair_ctx->wksp, repair_ctx->forest ) ) ); - fd_forest_reqslist_t * dlist = fd_forest_reqslist( forest ); - fd_forest_ref_t * pool = fd_forest_reqspool( forest ); - - fd_forest_reqslist_t * orphlist = fd_forest_orphlist( forest ); - - for( ;; ) { - printf("%-15s %-12s %-12s %-12s %-20s %-12s\n", - "Slot", "Buffered Idx", "Complete Idx", "First Shred ts", "Turbine Cnt", "Repair Cnt"); - printf("%-15s %-12s %-12s %-12s %-20s %-12s\n", - "---------------", "------------", "------------", "------------", - "--------------------", "------------"); - for( fd_forest_reqslist_iter_t iter = fd_forest_reqslist_iter_fwd_init( dlist, pool ); - !fd_forest_reqslist_iter_done( iter, dlist, pool ); - iter = fd_forest_reqslist_iter_fwd_next( iter, dlist, pool ) ) { - fd_forest_ref_t * req = fd_forest_reqslist_iter_ele( iter, dlist, pool ); - fd_forest_blk_t * blk = fd_forest_pool_ele( fd_forest_pool( forest ), req->idx ); - - printf("%-15lu %-12u %-12u %-20ld %-12u %-10u\n", - blk->slot, - blk->buffered_idx, - blk->complete_idx, - blk->first_shred_ts, - blk->turbine_cnt, - blk->repair_cnt); - } - printf("\n"); - - /* now lets print the orphreqs */ - - printf("Orphan Requests:\n"); - printf("%-15s %-12s %-12s %-12s %-20s %-12s %-10s\n", - "Slot", "Consumed Idx", "Buffered Idx", "Complete Idx", - "First Shred Timestamp", "Turbine Cnt", "Repair Cnt"); -printf("%-15s %-12s %-12s %-12s %-20s %-12s %-10s\n", - "---------------", "------------", "------------", "------------", - "--------------------", "------------", "----------"); - - for( fd_forest_reqslist_iter_t iter = fd_forest_reqslist_iter_fwd_init( orphlist, pool ); - !fd_forest_reqslist_iter_done( iter, orphlist, pool ); - iter = fd_forest_reqslist_iter_fwd_next( iter, orphlist, pool ) ) { - fd_forest_ref_t * req = fd_forest_reqslist_iter_ele( iter, orphlist, pool ); - fd_forest_blk_t * blk = fd_forest_pool_ele( fd_forest_pool( forest ), req->idx ); - printf("%-15lu %-12u %-12u %-20ld %-12u %-10u\n", - blk->slot, - blk->buffered_idx, - blk->complete_idx, - blk->first_shred_ts, - blk->turbine_cnt, - blk->repair_cnt); - } - sleep( 1 ); - } -} - -static void -repair_cmd_fn_waterfall( args_t * args, - config_t * config ) { - - fd_topo_t * topo = &config->topo; - ulong wksp_id = fd_topo_find_wksp( topo, "repair" ); - if( FD_UNLIKELY( wksp_id==ULONG_MAX ) ) FD_LOG_ERR(( "repair workspace not found" )); - fd_topo_wksp_t * repair_wksp = &topo->workspaces[ wksp_id ]; - fd_topo_join_workspace( topo, repair_wksp, FD_SHMEM_JOIN_MODE_READ_ONLY, FD_TOPO_CORE_DUMP_LEVEL_DISABLED ); - - /* Access the repair tile scratch memory where repair_tile_ctx is stored */ - ulong tile_id = fd_topo_find_tile( topo, "repair", 0UL ); - if( FD_UNLIKELY( tile_id==ULONG_MAX ) ) FD_LOG_ERR(( "repair tile not found" )); - fd_topo_tile_t * tile = &topo->tiles[ tile_id ]; - void * scratch = fd_topo_obj_laddr( &config->topo, tile->tile_obj_id ); - if( FD_UNLIKELY( !scratch ) ) FD_LOG_ERR(( "Failed to access repair tile scratch memory" )); - - FD_SCRATCH_ALLOC_INIT( l, scratch ); - ctx_t * repair_ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof(ctx_t), sizeof(ctx_t) ); - - /* catchup cmd owned memory */ - location_table = fd_location_table_join( fd_location_table_new( location_table_mem ) ); - read_iptable( args->repair.iptable_path, location_table ); - - // Add terminal setup here - same as monitor.c - atexit( restore_terminal ); - if( FD_UNLIKELY( 0!=tcgetattr( STDIN_FILENO, &termios_backup ) ) ) { - FD_LOG_ERR(( "tcgetattr(STDIN_FILENO) failed (%i-%s)", errno, fd_io_strerror( errno ) )); - } - - /* Disable character echo and line buffering */ - struct termios term = termios_backup; - term.c_lflag &= (tcflag_t)~(ICANON | ECHO); - if( FD_UNLIKELY( 0!=tcsetattr( STDIN_FILENO, TCSANOW, &term ) ) ) { - FD_LOG_WARNING(( "tcsetattr(STDIN_FILENO) failed (%i-%s)", errno, fd_io_strerror( errno ) )); - } - - int catchup_verbose = 0; - long last_print = 0; - for( ;; ) { - int c = fd_getchar(); - if( FD_UNLIKELY( c=='i' ) ) catchup_verbose = !catchup_verbose; - if( FD_UNLIKELY( c=='\x04' ) ) break; /* Ctrl-D */ - - long now = fd_log_wallclock(); - if( FD_UNLIKELY( now - last_print > 1e9L ) ) { - last_print = now; - print_catchup_slots( repair_wksp->wksp, repair_ctx, catchup_verbose, args->repair.sort_by_slot ); - printf( "catchup slots | Use 'i' to toggle extra slot information" TEXT_NEWLINE ); - fflush( stdout ); - - /* Peer location latency is not that useful post catchup, and also - requires some concurrent dlist iteration, so only print it when - in catchup mode. */ - } - } -} - -#define PEERS_DISPLAY_MAX 20 - -static void -print_peer_dlist( fd_policy_peer_dlist_t * dlist, - fd_policy_peer_t * pool, - fd_policy_peer_dlist_iter_t cursor, - char const * label ) { - ulong cnt = 0; - for( fd_policy_peer_dlist_iter_t it = fd_policy_peer_dlist_iter_fwd_init( dlist, pool ); - !fd_policy_peer_dlist_iter_done( it, dlist, pool ); - it = fd_policy_peer_dlist_iter_fwd_next( it, dlist, pool ) ) cnt++; - - printf( "%s (%lu peers)\n", label, cnt ); - if( !cnt || fd_policy_peer_dlist_iter_done( cursor, dlist, pool ) ) { - printf( " (empty or iterator not initialized)\n\n" ); - return; - } - - printf( " | %-8s | %-12s | %-12s | %-8s | %-8s\n", - "Idx", "Pubkey", "Ewma Lat", "Avg Lat", "Req/Res" ); - printf( "-----+----------+--------------+--------------+----------+---------\n" ); - - fd_policy_peer_dlist_iter_t it = cursor; - for( ulong i = 0; i < PEERS_DISPLAY_MAX && i < cnt; i++ ) { - fd_policy_peer_t * peer = fd_policy_peer_dlist_iter_ele( it, dlist, pool ); - - FD_BASE58_ENCODE_32_BYTES( peer->key.key, b58 ); - char pubkey_short[13]; - fd_cstr_fini( fd_cstr_append_text( fd_cstr_init( pubkey_short ), b58, 12 ) ); - - double avg_lat_ms = peer->res_cnt ? ((double)peer->total_lat / (double)peer->res_cnt) / 1e6 : 0.0; - double ewma_lat_ms = (double)peer->ewma_lat / 1e6; - - printf( " %s%c%s | %-8lu | %-12s | %9.3fms | %9.3fms | %lu/%lu\n", - i == 0 ? "\033[1;33m" : "", - i == 0 ? '>' : ' ', - i == 0 ? "\033[0m" : "", - fd_policy_peer_pool_idx( pool, peer ), - pubkey_short, - ewma_lat_ms, - avg_lat_ms, - peer->req_cnt, - peer->res_cnt ); - - it = fd_policy_peer_dlist_iter_fwd_next( it, dlist, pool ); - if( fd_policy_peer_dlist_iter_done( it, dlist, pool ) ) { - it = fd_policy_peer_dlist_iter_fwd_init( dlist, pool ); - } - } - if( cnt > PEERS_DISPLAY_MAX ) printf( " ... (%lu more)\n", cnt - PEERS_DISPLAY_MAX ); - printf( "\n" ); -} - -static void -repair_cmd_fn_peers( args_t * args, - config_t * config ) { - ctx_t * repair_ctx; - fd_topo_wksp_t * repair_wksp; - repair_ctx_wksp( args, config, &repair_ctx, &repair_wksp ); - - fd_policy_t * policy = fd_wksp_laddr( repair_wksp->wksp, fd_wksp_gaddr_fast( repair_ctx->wksp, repair_ctx->policy ) ); - - fd_policy_peer_dlist_t * fast_dlist = fd_wksp_laddr( repair_wksp->wksp, fd_wksp_gaddr_fast( repair_ctx->wksp, policy->peers.fast ) ); - fd_policy_peer_dlist_t * slow_dlist = fd_wksp_laddr( repair_wksp->wksp, fd_wksp_gaddr_fast( repair_ctx->wksp, policy->peers.slow ) ); - fd_policy_peer_t * pool = fd_wksp_laddr( repair_wksp->wksp, fd_wksp_gaddr_fast( repair_ctx->wksp, policy->peers.pool ) ); - - long last_print = 0; - for( ;; ) { - long now = fd_log_wallclock(); - if( FD_UNLIKELY( now - last_print > 1e9L ) ) { - last_print = now; - printf( "\033[2J\033[H" ); - - char fast_label[64]; - char slow_label[64]; - snprintf( fast_label, sizeof(fast_label), "FAST PEERS (ewma < %ldms)", (long)(FD_POLICY_LATENCY_THRESH / 1e6L) ); - snprintf( slow_label, sizeof(slow_label), "SLOW PEERS (ewma >= %ldms or no responses)", (long)(FD_POLICY_LATENCY_THRESH / 1e6L) ); - print_peer_dlist( fast_dlist, pool, policy->peers.select.fast_iter, fast_label ); - print_peer_dlist( slow_dlist, pool, policy->peers.select.slow_iter, slow_label ); - - printf( "select cnt: %u / %u (fast per slow)\n", policy->peers.select.cnt, FD_POLICY_FAST_PER_SLOW ); - printf( "pool used: %lu / %lu\n", fd_policy_peer_pool_used( pool ), fd_policy_peer_pool_max( pool ) ); - - fflush( stdout ); - } - - } -} - - - - -void -repair_cmd_args( int * pargc, - char *** pargv, - args_t * args ) { - - /* positional arg */ - - args->repair.pos_arg = (*pargv)[0]; - if( FD_UNLIKELY( !args->repair.pos_arg ) ) { - args->repair.help = 1; - return; - } - (*pargc)--; - (*pargv)++; - - /* required args */ - - char const * manifest_path = fd_env_strip_cmdline_cstr ( pargc, pargv, "--manifest-path", NULL, NULL ); - - /* optional args */ - - char const * iptable_path = fd_env_strip_cmdline_cstr ( pargc, pargv, "--iptable", NULL, NULL ); - ulong slot = fd_env_strip_cmdline_ulong ( pargc, pargv, "--slot", NULL, ULONG_MAX ); - int sort_by_slot = fd_env_strip_cmdline_contains( pargc, pargv, "--sort-by-slot" ); - - if( FD_UNLIKELY( !strcmp( args->repair.pos_arg, "catchup" ) && !manifest_path ) ) { - args->repair.help = 1; - return; - } - - fd_cstr_fini( fd_cstr_append_cstr_safe( fd_cstr_init( args->repair.manifest_path ), manifest_path, sizeof(args->repair.manifest_path)-1UL ) ); - fd_cstr_fini( fd_cstr_append_cstr_safe( fd_cstr_init( args->repair.iptable_path ), iptable_path, sizeof(args->repair.iptable_path )-1UL ) ); - args->repair.slot = slot; - args->repair.sort_by_slot = sort_by_slot; -} - -static void -repair_cmd_fn( args_t * args, - config_t * config ) { - - if( args->repair.help ) { - fd_action_help_print( &fd_action_repair ); - return; - } - - if ( !strcmp( args->repair.pos_arg, "catchup" ) ) repair_cmd_fn_catchup ( args, config ); - else if( !strcmp( args->repair.pos_arg, "eqvoc" ) ) repair_cmd_fn_eqvoc ( args, config ); - else if( !strcmp( args->repair.pos_arg, "forest" ) ) repair_cmd_fn_forest ( args, config ); - else if( !strcmp( args->repair.pos_arg, "inflight" ) ) repair_cmd_fn_inflight ( args, config ); - else if( !strcmp( args->repair.pos_arg, "requests" ) ) repair_cmd_fn_requests ( args, config ); - else if( !strcmp( args->repair.pos_arg, "waterfall" ) ) repair_cmd_fn_waterfall( args, config ); - else if( !strcmp( args->repair.pos_arg, "peers" ) ) repair_cmd_fn_peers ( args, config ); - else if( !strcmp( args->repair.pos_arg, "metrics" ) ) repair_cmd_fn_metrics ( args, config ); - else fd_action_help_print( &fd_action_repair ); -} - -static void -repair_args_help( fd_action_help_t * help ) { - fd_action_help_arg( help, "catchup", NULL, "Run a reduced topology that only repairs slots until catchup.\n" - "Requires --manifest-path; accepts --iptable and --sort-by-slot" ); - fd_action_help_arg( help, "eqvoc", NULL, "Test equivocation detection and the repair path" ); - fd_action_help_arg( help, "forest", NULL, "Print the repair forest. Accepts --slot to drill into a slot" ); - fd_action_help_arg( help, "inflight", NULL, "Print the inflight repairs" ); - fd_action_help_arg( help, "requests", NULL, "Print the queued repair requests" ); - fd_action_help_arg( help, "waterfall", NULL, "Print a waterfall diagram of recent slot completion times and\n" - "response latencies. Accepts --iptable and --sort-by-slot" ); - fd_action_help_arg( help, "peers", NULL, "Print the list of slow and fast repair peers" ); - fd_action_help_arg( help, "metrics", NULL, "Print repair tile metrics in a digestible format" ); - fd_action_help_arg( help, "--manifest-path", "", "Path to manifest file (required by catchup)" ); - fd_action_help_arg( help, "--iptable", "", "Path to iptable file (catchup, waterfall)" ); - fd_action_help_arg( help, "--slot", "", "Specific forest slot to drill into (forest)" ); - fd_action_help_arg( help, "--sort-by-slot", NULL, "Sort results by slot (catchup, waterfall)" ); -} - -action_t fd_action_repair = { - .name = "repair", - .args = repair_cmd_args, - .fn = repair_cmd_fn, - .perm = dev_cmd_perm, - .description = "Spawn a reduced topology for inspecting and profiling the repair tile", - .detail = "Boots a smaller Firedancer topology focused on the repair tile and runs the\n" - "requested subcommand to drive or inspect repair behavior. Pick one of the\n" - "subcommands below.", - .usage = "repair [OPTIONS]", - .args_help = repair_args_help, -}; diff --git a/src/app/firedancer-dev/commands/rotor.c b/src/app/firedancer-dev/commands/rotor.c index a3e1c34350c..b719049470a 100644 --- a/src/app/firedancer-dev/commands/rotor.c +++ b/src/app/firedancer-dev/commands/rotor.c @@ -105,7 +105,7 @@ metrics_src_init( metrics_src_t * src, fd_topo_workspace_fill( topo, &topo->workspaces[ wksp_id ] ); ulong rotor_id = fd_topo_find_tile( topo, "rotor", 0UL ); - if( FD_UNLIKELY( rotor_id==ULONG_MAX ) ) FD_LOG_ERR(( "rotor tile not found (is the validator running with --alpenglow?)" )); + if( FD_UNLIKELY( rotor_id==ULONG_MAX ) ) FD_LOG_ERR(( "rotor tile not found" )); fd_topo_tile_t * rotor_tile = &topo->tiles[ rotor_id ]; src->rotor = fd_metrics_tile( rotor_tile->metrics ); diff --git a/src/app/firedancer-dev/commands/send_test/Local.mk b/src/app/firedancer-dev/commands/send_test/Local.mk deleted file mode 100644 index 5ca4f183f6c..00000000000 --- a/src/app/firedancer-dev/commands/send_test/Local.mk +++ /dev/null @@ -1,3 +0,0 @@ -ifdef FD_HAS_HOSTED -$(call add-objs,send_test,fd_firedancer_dev) -endif diff --git a/src/app/firedancer-dev/commands/send_test/send_test.c b/src/app/firedancer-dev/commands/send_test/send_test.c deleted file mode 100644 index eda602890cd..00000000000 --- a/src/app/firedancer-dev/commands/send_test/send_test.c +++ /dev/null @@ -1,308 +0,0 @@ -/* -send_test is a firedancer-dev command that tests the send tile. -It uses the net, send, metrics, and sign tiles, just like in prod. -The main test function writes contact info to the gossip_ciaddr link, -stake info to the stake_out link, and triggers mock votes on the -tower_out link. - -It takes two required arguments: ---gossip-file: the path to the gossip file ---stake-file: the path to the stake file -These two files should include lines from the 'solana gossip' and -'solana validators' commands, respectively. It is recommended to run -with a known good subset of nodes while tuning the send tile. - -send_test can also run with live gossip, if --gossip-file is set to "live". -This will populate contact info from the live cluster, and requires the -config's gossip section populated appropriately. 'live' mode will spin -up the entire gossip subtopo. -*/ -#include "../../../shared/commands/configure/configure.h" -#include "../../../shared/commands/run/run.h" /* initialize_workspaces */ - -#include /* RLIMIT_NOFILE */ -#include "../../../shared/fd_config.h" /* config_t */ -#include "../../../../disco/topo/fd_topob.h" -#include "../../../../disco/topo/fd_cpu_topo.h" /* fd_topo_cpus_t */ -#include "../../../../disco/net/fd_net_tile.h" /* fd_topos_net_tiles */ -#include "../../../../discof/tower/fd_tower_tile.h" -#include "../../../../flamenco/leaders/fd_leaders_base.h" /* FD_STAKE_OUT_MTU */ -#include "../../../../app/firedancer/topology.h" /* fd_topo_configure_tile */ -#include "../../../../disco/keyguard/fd_keyload.h" - -#include "../core_subtopo.h" -#include "../gossip.h" -#include "send_test_helpers.c" - -extern fd_topo_obj_callbacks_t * CALLBACKS[]; - -fd_topo_run_tile_t -fdctl_tile_run( fd_topo_tile_t const * tile ); - -struct { - char gossip_file[256]; - char stake_file[256]; -} send_test_args = {0}; - -static void -send_test_topo( config_t * config ) { - - ulong const net_tile_cnt = config->layout.net_tile_count; - ulong const ingress_buf_sz = config->net.ingress_buffer_size; - - /* Setup topology */ - fd_topo_t * topo = fd_topob_new( &config->topo, config->name ); - topo->max_page_size = fd_cstr_to_shmem_page_sz( config->hugetlbfs.max_page_size ); - - ulong tile_to_cpu[ FD_TILE_MAX ] = {0}; - ushort parsed_tile_to_cpu[ FD_TILE_MAX ]; - for( ulong i=0UL; ilayout.affinity, "auto" ) ) ) affinity_tile_cnt = fd_topob_parse_affinity_cstr( config->layout.affinity, parsed_tile_to_cpu, 0, 1 ); - - for( ulong i=0UL; i=cpus->cpu_cnt ) ) - FD_LOG_ERR(( "The CPU affinity string in the configuration file under [layout.affinity] specifies a CPU index of %hu, but the system " - "only has %lu CPUs. You should either change the CPU allocations in the affinity string, or increase the number of CPUs " - "in the system.", - cpu_idx, cpus->cpu_cnt )); - tile_to_cpu[ i ] = fd_ulong_if( parsed_tile_to_cpu[ i ]==USHORT_MAX, ULONG_MAX, (ulong)parsed_tile_to_cpu[ i ] ); - } - - /* Check if we should use live gossip or mock gossip */ - int use_live_gossip = !strcmp( send_test_args.gossip_file, "live" ); - - fd_core_subtopo( config, tile_to_cpu ); - if( use_live_gossip ) { - fd_gossip_subtopo( config, tile_to_cpu ); - } - - #define FOR(cnt) for( ulong i=0UL; itile_cnt ], 0, 0, 0 ); - - /* wksps for send links */ - fd_topob_wksp( topo, "txsend_net" ); - fd_topob_wksp( topo, "sign_txsend" ); - fd_topob_wksp( topo, "txsend_sign" ); - - /* real links for send */ - FOR(net_tile_cnt) fd_topos_net_rx_link( topo, "net_txsend", i, ingress_buf_sz ); - - FOR(net_tile_cnt) fd_topob_link( topo, "txsend_net", "txsend_net", ingress_buf_sz, FD_NET_MTU, 1UL ); - /**/ fd_topob_link( topo, "txsend_sign", "txsend_sign", 128UL, FD_TXN_MTU, 1UL ); - /**/ fd_topob_link( topo, "sign_txsend", "sign_txsend", 128UL, 64UL, 1UL ); - - /* mock links */ - /* braces shut up clang's 'misleading identation' warning */ - if( !use_live_gossip ) {fd_topob_wksp( topo, "gossip_ciaddr" ); } - /**/ fd_topob_wksp( topo, "replay_epoch" ); - /**/ fd_topob_wksp( topo, "tower_out" ); - /**/ fd_topob_wksp( topo, "txsend_out" ); - - if( !use_live_gossip ) {fd_topob_link( topo, "gossip_ciaddr", "gossip_ciaddr", 65536UL*2UL, sizeof(fd_gossip_update_message_t), 1UL ); } - /**/ fd_topob_link( topo, "replay_epoch", "replay_epoch", 128UL, FD_STAKE_OUT_MTU, 1UL ); - /**/ fd_topob_link( topo, "tower_out", "tower_out", 1024UL, sizeof(fd_tower_slot_done_t), 1UL ); - /**/ fd_topob_link( topo, "txsend_out", "txsend_out", 128UL, 40200UL * 38UL, 1UL ); - - if( !use_live_gossip ) {fd_link_permit_no_producers( topo, "gossip_ciaddr" ); } - if( !use_live_gossip ) {fd_link_permit_no_consumers( topo, "txsend_out" ); } - /**/ fd_link_permit_no_producers( topo, "replay_epoch" ); - /**/ fd_link_permit_no_producers( topo, "tower_out" ); - - if( use_live_gossip ) { - /* finish off gossip in_links */ - fd_topob_tile_in( topo, "gossip", 0UL, "metric_in", "txsend_out", 0UL, FD_TOPOB_RELIABLE, FD_TOPOB_POLLED ); - fd_topob_tile_in( topo, "gossip", 0UL, "metric_in", "sign_gossip", 0UL, FD_TOPOB_UNRELIABLE, FD_TOPOB_POLLED ); - } - - /* attach txsend in links */ - fd_topos_tile_in_net( topo, /* ***** */ "metric_in", "txsend_net", 0UL, FD_TOPOB_UNRELIABLE, FD_TOPOB_POLLED ); - fd_topob_tile_in ( topo, "txsend", 0UL, "metric_in", "net_txsend", 0UL, FD_TOPOB_UNRELIABLE, FD_TOPOB_POLLED ); - - fd_topob_tile_in( topo, "txsend", 0UL, "metric_in", "gossip_ciaddr", 0UL, FD_TOPOB_RELIABLE, FD_TOPOB_POLLED ); - fd_topob_tile_in( topo, "txsend", 0UL, "metric_in", "replay_epoch", 0UL, FD_TOPOB_RELIABLE, FD_TOPOB_POLLED ); - fd_topob_tile_in( topo, "txsend", 0UL, "metric_in", "tower_out", 0UL, FD_TOPOB_RELIABLE, FD_TOPOB_POLLED ); - /* attach out links */ - fd_topob_tile_out( topo, "txsend", 0UL, "txsend_net", 0UL ); - fd_topob_tile_out( topo, "txsend", 0UL, "txsend_out", 0UL ); - - /* unpolled links have to be last! */ - fd_topob_tile_in ( topo, "sign", 0UL, "metric_in", "txsend_sign", 0UL, FD_TOPOB_RELIABLE, FD_TOPOB_POLLED ); - fd_topob_tile_in ( topo, "txsend", 0UL, "metric_in", "sign_txsend", 0UL, FD_TOPOB_UNRELIABLE, FD_TOPOB_UNPOLLED ); - fd_topob_tile_out( topo, "txsend", 0UL, "txsend_sign", 0UL ); - fd_topob_tile_out( topo, "sign", 0UL, "sign_txsend", 0UL ); - - FOR(net_tile_cnt) fd_topos_net_tile_finish( topo, i ); - - for( ulong i=0UL; itile_cnt; i++ ) { - fd_topo_tile_t * tile = &topo->tiles[ i ]; - fd_topo_configure_tile( tile, config ); - } - - /* Finish topology setup */ - if( FD_UNLIKELY( !strcmp( config->layout.affinity, "auto" ) ) ) fd_topob_auto_layout( topo ); - fd_topob_waker( topo ); - fd_topob_finish( topo, CALLBACKS ); -} - -static void -send_test_cmd_args( int * pargc, - char *** pargv, - args_t * args FD_PARAM_UNUSED ) { - char ** _pargv = *pargv; - int _pargc = *pargc; - int found_gossip = 0; - int found_stake = 0; - - /* Extract our arguments */ - for( int i = 0; i < _pargc - 1; i++ ) { - if( !strcmp( _pargv[i], "--gossip-file" ) ) { - strncpy( send_test_args.gossip_file, _pargv[i+1], sizeof(send_test_args.gossip_file) - 1 ); - found_gossip = 1; - } else if( !strcmp( _pargv[i], "--stake-file" ) ) { - strncpy( send_test_args.stake_file, _pargv[i+1], sizeof(send_test_args.stake_file) - 1 ); - found_stake = 1; - } - } - - /* Remove our arguments from argv */ - int write_idx = 0; - for( int read_idx = 0; read_idx < _pargc; read_idx++ ) { - if( read_idx < _pargc - 1 && - (!strcmp( _pargv[read_idx], "--gossip-file" ) || !strcmp( _pargv[read_idx], "--stake-file" )) ) { - read_idx++; /* Skip the argument value too */ - } else { - _pargv[write_idx++] = _pargv[read_idx]; - } - } - *pargc = write_idx; - - if( !found_gossip ) FD_LOG_ERR(( "--gossip-file is required" )); - if( !found_stake ) FD_LOG_ERR(( "--stake-file is required" )); -} - - -static void -init( send_test_ctx_t * ctx, config_t * config ) { - fd_topo_t * topo = &config->topo; - ctx->topo = topo; - ctx->config = config; - - /* Copy file paths from send_test_args */ - fd_memcpy( ctx->gossip_file, send_test_args.gossip_file, sizeof(ctx->gossip_file) ); - fd_memcpy( ctx->stake_file, send_test_args.stake_file, sizeof(ctx->stake_file ) ); - - int live_gossip = !strcmp( send_test_args.gossip_file, "live" ); - - ctx->identity_key [ 0 ] = *(fd_pubkey_t const *)(fd_keyload_load( config->paths.identity_key, /* pubkey only: */ 1 ) ); - ctx->vote_acct_addr[ 0 ] = *(fd_pubkey_t const *)(fd_keyload_load( config->paths.vote_account, /* pubkey only: */ 1 ) ); - - ctx->out_links[ MOCK_CI_IDX ] = setup_test_out_link( topo, "gossip_ciaddr" ); - ctx->out_links[ MOCK_STAKE_IDX ] = setup_test_out_link( topo, "replay_epoch" ); - ctx->out_links[ MOCK_TRIGGER_IDX ] = setup_test_out_link( topo, "tower_out" ); - - ctx->out_fns [ MOCK_CI_IDX ] = send_test_ci; - ctx->out_fns [ MOCK_STAKE_IDX ] = send_test_stake; - ctx->out_fns [ MOCK_TRIGGER_IDX ] = send_test_trigger; - - ctx->last_evt [ MOCK_CI_IDX ] = 0; - ctx->last_evt [ MOCK_STAKE_IDX ] = 0; - ctx->last_evt [ MOCK_TRIGGER_IDX ] = 0; - - double tick_per_ns = fd_tempo_tick_per_ns( NULL ); - ctx->delay [ MOCK_CI_IDX ] = (long)(tick_per_ns * 5e9); - ctx->delay [ MOCK_STAKE_IDX ] = (long)(tick_per_ns * 400e6 * MAX_SLOTS_PER_EPOCH); - ctx->delay [ MOCK_TRIGGER_IDX ] = (long)(tick_per_ns * 400e6); /* 400ms */ - if( live_gossip ) { - ctx->delay[ MOCK_CI_IDX ] = LONG_MAX; - } - - encode_vote( ctx, ctx->twr_buf ); - - /* send first epoch of stake info */ - send_test_stake( ctx, &ctx->out_links[ MOCK_STAKE_IDX ] ); -} -static void -send_test_main_loop( send_test_ctx_t * ctx ) { - for(;;) { - long now = fd_tickcount(); - for( ulong i=0UL; ilast_evt[ i ] + ctx->delay[ i ] <= now ) { - send_test_out_t * out = &ctx->out_links[ i ]; - ctx->out_fns [ i ]( ctx, out ); - ctx->last_evt[ i ] = now; - } - } - } -} - -static void -send_test_cmd_fn( args_t * args , - config_t * config ) { - send_test_topo( config ); - - configure_stage( &fd_cfg_stage_mlx5, CONFIGURE_CMD_INIT, config ); - configure_stage( &fd_cfg_stage_sysctl, CONFIGURE_CMD_INIT, config ); - configure_stage( &fd_cfg_stage_hugetlbfs, CONFIGURE_CMD_INIT, config ); - configure_stage( &fd_cfg_stage_bonding, CONFIGURE_CMD_INIT, config ); - configure_stage( &fd_cfg_stage_ethtool_channels, CONFIGURE_CMD_INIT, config ); - configure_stage( &fd_cfg_stage_ethtool_offloads, CONFIGURE_CMD_INIT, config ); - configure_stage( &fd_cfg_stage_ethtool_loopback, CONFIGURE_CMD_INIT, config ); - - fd_topo_print_log( 0, &config->topo ); - - run_firedancer_init( config, !args->dev.no_init_workspaces, 1 ); - - if( 0==strcmp( config->net.provider, "xdp" ) ) { - fd_topo_install_xdp_simple( &config->topo, config->net.bind_address_parsed ); - } - - fd_topo_join_workspaces( &config->topo, FD_SHMEM_JOIN_MODE_READ_WRITE, FD_TOPO_CORE_DUMP_LEVEL_DISABLED ); - if( 0==strcmp( config->net.provider, "mlx5" ) ) { - fd_topo_install_mlx5( &config->topo, NULL ); - } - fd_topo_run_single_process( &config->topo, config->uid, config->gid, fdctl_tile_run ); - - send_test_ctx_t ctx = {0}; - init( &ctx, config ); - send_test_main_loop( &ctx ); -} - -static void -configure_stage_perm( configure_stage_t const * stage, - fd_cap_chk_t * chk, - config_t const * config ) { - int enabled = !stage->enabled || stage->enabled( config ); - if( enabled && stage->check( config, FD_CONFIGURE_CHECK_TYPE_INIT_PERM ).result != CONFIGURE_OK ) - if( stage->init_perm ) stage->init_perm( chk, config ); -} - -static void -send_test_cmd_perm( args_t * args FD_PARAM_UNUSED, - fd_cap_chk_t * chk, - config_t const * config ) { - configure_stage_perm( &fd_cfg_stage_mlx5, chk, config ); - configure_stage_perm( &fd_cfg_stage_sysctl, chk, config ); - configure_stage_perm( &fd_cfg_stage_hugetlbfs, chk, config ); - configure_stage_perm( &fd_cfg_stage_bonding, chk, config ); - configure_stage_perm( &fd_cfg_stage_ethtool_channels, chk, config ); - configure_stage_perm( &fd_cfg_stage_ethtool_offloads, chk, config ); - configure_stage_perm( &fd_cfg_stage_ethtool_loopback, chk, config ); - fd_cap_chk_raise_rlimit( chk, "send_test", RLIMIT_NOFILE, CONFIGURE_NR_OPEN_FILES, "call `rlimit(2)` to increase `RLIMIT_NOFILE` for the fixed waker fd range" ); -} - -action_t fd_action_send_test = { - .name = "send_test", - .args = send_test_cmd_args, - .fn = send_test_cmd_fn, - .perm = send_test_cmd_perm, - .description = "Exercise the send tile in isolation using gossip/stake fixtures or live gossip", -}; diff --git a/src/app/firedancer-dev/commands/send_test/send_test_helpers.c b/src/app/firedancer-dev/commands/send_test/send_test_helpers.c deleted file mode 100644 index 770dbb27835..00000000000 --- a/src/app/firedancer-dev/commands/send_test/send_test_helpers.c +++ /dev/null @@ -1,230 +0,0 @@ -#ifndef FD_SRC_APP_FIREDANCER_DEV_COMMANDS_SEND_TEST_HELPERS_C -#define FD_SRC_APP_FIREDANCER_DEV_COMMANDS_SEND_TEST_HELPERS_C - -#include "../../../../choreo/tower/fd_tower.h" -#include "../../../../flamenco/leaders/fd_leaders_base.h" -#include "../../../../discof/tower/fd_tower_tile.h" -#include "../../../../flamenco/gossip/fd_gossip_message.h" -#include "../../../../util/net/fd_ip4.h" - -#include -#include -#include - -#define MOCK_CI_IDX 0UL -#define MOCK_STAKE_IDX 1UL -#define MOCK_TRIGGER_IDX 2UL -#define MOCK_CNT 3UL - -/* Forward declarations for send_test types */ -struct send_test_ctx; -typedef struct send_test_ctx send_test_ctx_t; - -typedef struct { - fd_frag_meta_t * mcache; - ulong * sync; - ulong depth; - ulong seq; - - fd_wksp_t * mem; - ulong chunk0; - ulong wmark; - ulong chunk; -} send_test_out_t; - -typedef void -(* out_fn_t)( send_test_ctx_t * ctx, send_test_out_t * out ); - -struct send_test_ctx { - fd_topo_t * topo; - fd_config_t * config; - - send_test_out_t out_links[ MOCK_CNT ]; - out_fn_t out_fns [ MOCK_CNT ]; - - fd_pubkey_t identity_key [ 1 ]; - fd_pubkey_t vote_acct_addr[ 1 ]; - - fd_tower_slot_done_t twr_buf[ 1 ]; - - long last_evt[ MOCK_CNT ]; - long delay [ MOCK_CNT ]; - - ulong epoch; - ulong slot; - - char gossip_file[256]; - char stake_file[256]; -}; - -/* File paths are now part of the ctx struct */ - -/* Event handler function implementations */ - -static inline fd_gossip_update_message_t -parse_gossip_line( char * line ) { - fd_gossip_update_message_t msg = {0}; - FD_LOG_DEBUG(( "Parsing gossip line: %s", line )); - - /* Parse tokens */ - char * ip_token = strtok( line, " |\t" ); FD_TEST( ip_token ); - char * pubkey_token = strtok( NULL, " |\t" ); FD_TEST( pubkey_token ); - /* ************* */ strtok( NULL, " |\t" ); /* Skip gossip port */ - char * tpu_udp = strtok( NULL, " |\t" ); FD_TEST( tpu_udp ); - char * tpu_quic = strtok( NULL, " |\t" ); FD_TEST( tpu_quic ); - - /* solana gossip output does not contain all 4 we care about - for now, use these two */ - fd_gossip_socket_t * udp_tpu = &msg.contact_info->value->sockets[ FD_GOSSIP_CONTACT_INFO_SOCKET_TPU ]; - fd_gossip_socket_t * quic_tpu = &msg.contact_info->value->sockets[ FD_GOSSIP_CONTACT_INFO_SOCKET_TPU_QUIC ]; - fd_gossip_socket_t * udp_vote = &msg.contact_info->value->sockets[ FD_GOSSIP_CONTACT_INFO_SOCKET_TPU_VOTE ]; - fd_gossip_socket_t * quic_vote = &msg.contact_info->value->sockets[ FD_GOSSIP_CONTACT_INFO_SOCKET_TPU_VOTE_QUIC ]; - - /* Set pubkey, IP, ports - 'gossip' should send all in net order */ - FD_TEST( fd_base58_decode_32( pubkey_token, msg.origin ) ); - - uint ip_addr; - FD_TEST( fd_cstr_to_ip4_addr( ip_token, &ip_addr ) ); - *udp_tpu = (fd_gossip_socket_t){ .is_ipv6 = 0, .ip4 = ip_addr, .port = fd_ushort_bswap( fd_cstr_to_ushort( tpu_udp ) ) }; - *quic_tpu = (fd_gossip_socket_t){ .is_ipv6 = 0, .ip4 = ip_addr, .port = fd_ushort_bswap( fd_cstr_to_ushort( tpu_quic ) ) }; - fd_memset( udp_vote, 0, sizeof(fd_gossip_socket_t) ); - fd_memset( quic_vote, 0, sizeof(fd_gossip_socket_t) ); - - return msg; -} - -static inline void -send_test_ci( send_test_ctx_t * ctx, send_test_out_t * out ) { - FILE * file = fopen( ctx->gossip_file, "r" ); - if( !file ) FD_LOG_ERR(( "Failed to open gossip file: %s", ctx->gossip_file )); - - char line[1024]; - while( fgets( line, sizeof(line), file ) ) { - fd_gossip_update_message_t * msg = fd_chunk_to_laddr( out->mem, out->chunk ); - *msg = parse_gossip_line( line ); - ulong const sz = sizeof(fd_gossip_update_message_t); - - fd_mcache_publish( out->mcache, out->depth, out->seq, 0UL, out->chunk, sz, 0UL, 0UL, 0UL ); - out->seq = fd_seq_inc( out->seq, 1UL ); - out->chunk = fd_dcache_compact_next( out->chunk, sz, out->chunk0, out->wmark ); - } - fclose( file ); -} - -static inline fd_vote_stake_weight_t -parse_stake_weight( char * line ) { - fd_vote_stake_weight_t weight; - FD_LOG_DEBUG(( "Parsing stake line: %s", line )); - - /* Set pubkeys */ - char * id_token = strtok( line+3, " \t" ); FD_TEST( id_token ); - char * vote_token = strtok( NULL, " \t" ); FD_TEST( vote_token ); - FD_TEST( fd_base58_decode_32( id_token, weight.id_key.key ) ); - FD_TEST( fd_base58_decode_32( vote_token, weight.vote_key.key ) ); - - /* Find staked amount in rest of string */ - char * sol_pos = strstr( strtok( NULL, "" ), " SOL " ); FD_TEST( sol_pos ); - char * sol_start = sol_pos - 2; - *sol_pos = '\0'; - /* Scan backwards from " SOL " to find the start of the number */ - while( sol_start > line && (sol_start[-1] == '.' || (sol_start[-1] >= '0' && sol_start[-1] <= '9')) ) { - sol_start--; - } - - /* Set staked amount */ - double sol_amount = atof( sol_start ); FD_TEST( sol_amount > 0.0 ); - weight.stake = (ulong)(sol_amount * 1000000000UL); - return weight; -} - -static inline void -send_test_stake( send_test_ctx_t * ctx, send_test_out_t * out ) { - - fd_stake_weight_msg_t * msg = fd_chunk_to_laddr( out->mem, out->chunk ); - - msg->epoch = ctx->epoch; - msg->start_slot = ctx->epoch*MAX_SLOTS_PER_EPOCH; - msg->slot_cnt = MAX_SLOTS_PER_EPOCH; - - fd_vote_stake_weight_t * vote_stake_weights = fd_stake_weight_msg_stake_weights( msg ); - ulong stake_count = 0; - - FILE * file = fopen( ctx->stake_file, "r" ); - if( !file ) FD_LOG_ERR(( "Failed to open stake file: %s", ctx->stake_file )); - - char line[1024]; - while( fgets( line, sizeof(line), file ) ) { - vote_stake_weights[stake_count++] = parse_stake_weight( line ); - } - fclose( file ); - - if( stake_count == 0 ) FD_LOG_ERR(( "No valid stake entries found in %s", ctx->stake_file )); - - msg->staked_vote_cnt = stake_count; - ulong const sz = sizeof(fd_stake_weight_msg_t) + stake_count * sizeof(fd_vote_stake_weight_t); - FD_TEST( sz <= USHORT_MAX ); - - fd_mcache_publish( out->mcache, out->depth, out->seq, 0UL, out->chunk, sz, 0UL, 0UL, 0UL ); - out->seq = fd_seq_inc( out->seq, 1UL ); - out->chunk = fd_dcache_compact_next( out->chunk, sz, out->chunk0, out->wmark ); - - ctx->epoch++; -} - -static inline void -send_test_trigger( send_test_ctx_t * ctx, send_test_out_t * out ) { - fd_tower_slot_done_t * slot_done = fd_chunk_to_laddr( out->mem, out->chunk ); - fd_memcpy( slot_done, ctx->twr_buf, sizeof(fd_tower_slot_done_t) ); - - fd_mcache_publish( out->mcache, out->depth, out->seq, 0UL, out->chunk, sizeof(fd_tower_slot_done_t), 0UL, 0UL, 0UL ); - out->seq = fd_seq_inc( out->seq, 1UL ); - out->chunk = fd_dcache_compact_next( out->chunk, sizeof(fd_tower_slot_done_t), out->chunk0, out->wmark ); -} - -static inline send_test_out_t -setup_test_out_link( fd_topo_t const * topo, char const * name ) { - ulong idx = fd_topo_find_link( topo, name, 0UL ); - FD_TEST( idx != ULONG_MAX ); - fd_topo_link_t const * link = &topo->links[ idx ]; - send_test_out_t out = { 0 }; - out.mcache = link->mcache; - out.sync = fd_mcache_seq_laddr( out.mcache ); - out.depth = fd_mcache_depth( out.mcache ); - out.seq = fd_mcache_seq_query( out.sync ); - out.mem = topo->workspaces[ topo->objs[ link->dcache_obj_id ].wksp_id ].wksp; - out.chunk0 = fd_dcache_compact_chunk0( out.mem, link->dcache ); - out.wmark = fd_dcache_compact_wmark( out.mem, link->dcache, link->mtu ); - out.chunk = out.chunk0; - return out; -} - -static inline void -encode_vote( send_test_ctx_t * ctx, fd_tower_slot_done_t * slot_done ) { - ulong const root = 350284672UL; - ulong const vote_slot = root+30; - - /* Create minimal mock tower with one vote */ - uchar votes_mem[ FD_TOWER_VOTE_FOOTPRINT ] __attribute__((aligned(FD_TOWER_VOTE_ALIGN))); - fd_tower_vote_t * votes = fd_tower_vote_join( fd_tower_vote_new( votes_mem ) ); - fd_tower_vote_push_tail( votes, (fd_tower_vote_t){ .slot = vote_slot, .conf = 1 } ); - fd_tower_t tower[1] = {{ .root = root, .votes = votes }}; - - /* Mock values */ - fd_hash_t test_hash = {0}; - fd_txn_p_t txn[1]; - - /* Use fd_tower_to_vote_txn to generate the transaction */ - fd_tower_to_vote_txn( tower, &test_hash, &test_hash, - &test_hash, ctx->identity_key, - ctx->identity_key, ctx->vote_acct_addr, txn ); - FD_TEST( txn->payload_sz && txn->payload_sz<=FD_TPU_MTU ); - fd_memcpy( slot_done->vote_txn, txn->payload, txn->payload_sz ); - slot_done->vote_txn_sz = txn->payload_sz; - slot_done->is_voting = 1; - slot_done->has_vote_txn = 1; - slot_done->authority_idx = ULONG_MAX; - - uchar txn_mem[ FD_TXN_MAX_SZ ] __attribute__((aligned(alignof(fd_txn_t)))); - FD_TEST( fd_txn_parse( slot_done->vote_txn, slot_done->vote_txn_sz, txn_mem, NULL ) ); -} - -#endif /* FD_SRC_APP_FIREDANCER_DEV_COMMANDS_SEND_TEST_HELPERS_C */ diff --git a/src/app/firedancer-dev/commands/tower.c b/src/app/firedancer-dev/commands/tower.c deleted file mode 100644 index ebdf32460ba..00000000000 --- a/src/app/firedancer-dev/commands/tower.c +++ /dev/null @@ -1,174 +0,0 @@ -/* The tower command prints the tower forks tree structure and leaves. - This is a standalone application that can be run to inspect the tower - tile's fork structure. */ - -#include "../../shared/fd_config.h" /* config_t */ -#include "../../shared_dev/commands/dev.h" -#include "../../../discof/tower/fd_tower_tile_private.h" - -#include -#include - -fd_topo_run_tile_t -fdctl_tile_run( fd_topo_tile_t const * tile ); - -extern action_t fd_action_tower; - -static void -tower_ctx_wksp( args_t * args, - config_t * config, - fd_tower_tile_t ** tower_ctx, - fd_topo_wksp_t ** tower_wksp ) { - (void)args; - - fd_topo_t * topo = &config->topo; - - ulong tile_id = fd_topo_find_tile( topo, "tower", 0UL ); - if( FD_UNLIKELY( tile_id==ULONG_MAX ) ) FD_LOG_ERR(( "tower tile not found" )); - - fd_topo_tile_t * tile = &topo->tiles[ tile_id ]; - - /* Get the workspace that contains the tile's scratch memory */ - ulong scratch_wksp_id = topo->objs[ tile->tile_obj_id ].wksp_id; - if( FD_UNLIKELY( scratch_wksp_id>=topo->wksp_cnt ) ) FD_LOG_ERR(( "invalid workspace id %lu for tile scratch", scratch_wksp_id )); - - fd_topo_wksp_t * _tower_wksp = &topo->workspaces[ scratch_wksp_id ]; - fd_topo_join_workspace( topo, _tower_wksp, FD_SHMEM_JOIN_MODE_READ_ONLY, FD_TOPO_CORE_DUMP_LEVEL_DISABLED ); - - /* Access the tower tile scratch memory where tower_tile_ctx is stored */ - void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id ); - if( FD_UNLIKELY( !scratch ) ) FD_LOG_ERR(( "Failed to access tower tile scratch memory" )); - - FD_SCRATCH_ALLOC_INIT( l, scratch ); - fd_tower_tile_t * _tower_ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_tower_tile_t), sizeof(fd_tower_tile_t) ); - - *tower_ctx = _tower_ctx; - *tower_wksp = _tower_wksp; -} - -static void -print_all_forks( fd_tower_t * tower ) { - printf( "\n[Tower Forks]\n" ); - printf( "=============\n" ); - printf( "%-15s | %-15s | %-10s | %-10s\n", "Slot", "Parent Slot", "Voted", "Confirmed" ); - printf( "%-15s-+-%-15s-+-%-10s-+-%-10s\n", "---------------", "---------------", "----------", "----------" ); - - ulong slot_count = 0; - for( ulong i = 0; i < tower->blk_max; i++ ) { - fd_tower_blk_t * blk = tower->blk_pool + i; - if( !blk->slot ) continue; /* unused pool element */ - fd_tower_blk_t * found = fd_tower_blocks_query( tower, blk->slot ); - if( !found ) continue; - printf( "%-15lu | ", found->slot ); - if( found->parent_slot == ULONG_MAX ) { - printf( "%-15s | ", "NULL" ); - } else { - printf( "%-15lu | ", found->parent_slot ); - } - printf( "%-10s | ", found->voted ? "Yes" : "No" ); - printf( "%-10s\n", found->confirmed ? "Yes" : "No" ); - slot_count++; - } - - printf( "Total slots: %lu\n", slot_count ); - printf( "\n" ); -} - -static void -tower_cmd_fn_forks( args_t * args, - config_t * config ) { - fd_tower_tile_t * tower_ctx; - fd_topo_wksp_t * tower_wksp; - tower_ctx_wksp( args, config, &tower_ctx, &tower_wksp ); - - for( ;; ) { - print_all_forks( tower_ctx->tower ); - sleep( 1 ); - } -} - -static void -tower_cmd_fn_ghost( args_t * args, - config_t * config ) { - fd_tower_tile_t * tower_ctx; - fd_topo_wksp_t * tower_wksp; - tower_ctx_wksp( args, config, &tower_ctx, &tower_wksp ); - - ulong ghost_gaddr = fd_wksp_gaddr_fast( tower_ctx->wksp, tower_ctx->ghost ); - fd_ghost_t * ghost = (fd_ghost_t *)fd_wksp_laddr( tower_wksp->wksp, ghost_gaddr ); - fd_ghost_root( ghost ); - FD_LOG_NOTICE(( "root slot %lu", fd_ghost_root( ghost )->slot )); - - for( ;; ) { - char cstr[4096]; cstr[4095] = '\0'; ulong sz; - FD_LOG_NOTICE(( "\n\n%s", fd_ghost_to_cstr( ghost, fd_ghost_root( ghost ), cstr, sizeof(cstr), &sz ) )); - sleep( 1 ); - } -} - -static void -tower_cmd_fn_tower( args_t * args, - config_t * config ) { - fd_tower_tile_t * tower_ctx; - fd_topo_wksp_t * tower_wksp; - tower_ctx_wksp( args, config, &tower_ctx, &tower_wksp ); - - ulong tower_laddr = fd_wksp_gaddr_fast( tower_ctx->wksp, tower_ctx->tower ); - fd_tower_t * tower = (fd_tower_t *)fd_wksp_laddr( tower_wksp->wksp, tower_laddr ); - - for( ;; ) { - char cstr[4096]; cstr[4095] = '\0'; - FD_LOG_DEBUG(( "\n\n%s", fd_tower_to_cstr( tower, cstr ) )); - sleep( 1 ); - } -} - -void -tower_cmd_args( int * pargc, - char *** pargv, - args_t * args ) { - - /* positional arg */ - args->tower.pos_arg = (*pargv)[0]; - if( FD_UNLIKELY( !args->tower.pos_arg ) ) { - args->tower.help = 1; - return; - } - - (*pargc)--; - (*pargv)++; -} - -static void -tower_cmd_fn( args_t * args, - config_t * config ) { - - if( args->tower.help ) { - fd_action_help_print( &fd_action_tower ); - return; - } - - if ( !strcmp( args->tower.pos_arg, "forks" ) ) tower_cmd_fn_forks( args, config ); - else if( !strcmp( args->tower.pos_arg, "ghost" ) ) tower_cmd_fn_ghost( args, config ); - else if( !strcmp( args->tower.pos_arg, "tower" ) ) tower_cmd_fn_tower( args, config ); - else fd_action_help_print( &fd_action_tower ); -} - -static void -tower_args_help( fd_action_help_t * help ) { - fd_action_help_arg( help, "forks", NULL, "Print the tower forks tree structure and leaves" ); - fd_action_help_arg( help, "ghost", NULL, "Print the ghost fork choice structure" ); - fd_action_help_arg( help, "tower", NULL, "Print the local tower" ); -} - -action_t fd_action_tower = { - .name = "tower", - .args = tower_cmd_args, - .fn = tower_cmd_fn, - .perm = dev_cmd_perm, - .description = "Inspect a running validator's tower, ghost, and forks state", - .detail = "Attaches to a running validator's tower tile and continuously prints the\n" - "requested data structure. Pick one of the subcommands below.", - .usage = "tower ", - .args_help = tower_args_help, -}; diff --git a/src/app/firedancer-dev/main.h b/src/app/firedancer-dev/main.h index 5fe9d53ce2e..cd0bffd2b64 100644 --- a/src/app/firedancer-dev/main.h +++ b/src/app/firedancer-dev/main.h @@ -98,7 +98,6 @@ extern fd_topo_run_tile_t fd_tile_dedup; extern fd_topo_run_tile_t fd_tile_resolv; extern fd_topo_run_tile_t fd_tile_pack; extern fd_topo_run_tile_t fd_tile_execle; -extern fd_topo_run_tile_t fd_tile_poh; extern fd_topo_run_tile_t fd_tile_motor; extern fd_topo_run_tile_t fd_tile_shred; extern fd_topo_run_tile_t fd_tile_sign; @@ -121,13 +120,10 @@ extern fd_topo_run_tile_t fd_tile_admin; extern fd_topo_run_tile_t fd_tile_gossvf; extern fd_topo_run_tile_t fd_tile_gossip; -extern fd_topo_run_tile_t fd_tile_repair; extern fd_topo_run_tile_t fd_tile_rotor; extern fd_topo_run_tile_t fd_tile_rserve; extern fd_topo_run_tile_t fd_tile_replay; extern fd_topo_run_tile_t fd_tile_execrp; -extern fd_topo_run_tile_t fd_tile_txsend; -extern fd_topo_run_tile_t fd_tile_tower; extern fd_topo_run_tile_t fd_tile_votor; extern fd_topo_run_tile_t fd_tile_accdb; extern fd_topo_run_tile_t fd_tile_backtest; @@ -169,15 +165,11 @@ fd_topo_run_tile_t * TILES[] = { &fd_tile_bundle, &fd_tile_gossvf, &fd_tile_gossip, - &fd_tile_repair, &fd_tile_rotor, &fd_tile_rserve, &fd_tile_replay, &fd_tile_execrp, - &fd_tile_poh, &fd_tile_motor, - &fd_tile_txsend, - &fd_tile_tower, &fd_tile_votor, &fd_tile_accdb, &fd_tile_backtest, @@ -230,13 +222,9 @@ extern action_t fd_action_wksp; extern action_t fd_action_gossip; extern action_t fd_action_backtest; extern action_t fd_action_snapshot_load; -extern action_t fd_action_repair; extern action_t fd_action_rotor; -extern action_t fd_action_reasm; -extern action_t fd_action_tower; extern action_t fd_action_shred_version; extern action_t fd_action_ipecho_server; -extern action_t fd_action_send_test; extern action_t fd_action_gossip_dump; extern action_t fd_action_monitor_gossip; extern action_t fd_action_watch; @@ -275,13 +263,9 @@ action_t * ACTIONS[] = { &fd_action_gossip, &fd_action_backtest, &fd_action_snapshot_load, - &fd_action_repair, &fd_action_rotor, - &fd_action_reasm, - &fd_action_tower, &fd_action_shred_version, &fd_action_ipecho_server, - &fd_action_send_test, &fd_action_gossip_dump, &fd_action_monitor_gossip, &fd_action_watch, diff --git a/src/app/firedancer-dev/tests/test_firedancer_dev.c b/src/app/firedancer-dev/tests/test_firedancer_dev.c index 2e091a627da..9b16d1f5a46 100644 --- a/src/app/firedancer-dev/tests/test_firedancer_dev.c +++ b/src/app/firedancer-dev/tests/test_firedancer_dev.c @@ -242,7 +242,6 @@ firedancer_dev_test_run( int argc, static config_t config[1]; fd_config_load( 1, (char const *)firedancer_default_config, firedancer_default_config_sz, NULL, NULL, 0UL, NULL, 0UL, NULL, config, 1 /* dev */ ); - config->firedancer.consensus.alpenglow = 1; FD_TEST( config->firedancer.development.genesis.max_file_size_mib==16UL ); @@ -399,7 +398,6 @@ main( int argc, fd_boot( &argc, &argv ); static config_t config[ 1 ]; fd_config_load( 1, (char const *)firedancer_default_config, firedancer_default_config_sz, NULL, NULL, 0UL, NULL, 0UL, NULL, config, 1 /* dev */ ); - config->firedancer.consensus.alpenglow = 1; fd_topo_initialize( config ); test_efficient_layout( config ); fd_halt(); diff --git a/src/app/firedancer/callbacks.c b/src/app/firedancer/callbacks.c index acd392c36a5..1f60d74f7a0 100644 --- a/src/app/firedancer/callbacks.c +++ b/src/app/firedancer/callbacks.c @@ -127,7 +127,7 @@ fd_topo_obj_callbacks_t fd_obj_cb_fec_sets = { static ulong store_footprint( fd_topo_t const * topo, fd_topo_obj_t const * obj ) { - return fd_store_footprint( VAL("fec_max"), VAL("fec_data_max"), VAL("shred_storage_gib"), VAL("shred_cache_bytes"), VAL("fec_set_cnt"), !!VAL("alpenglow"), VAL("shred_tile_cnt") ); + return fd_store_footprint( VAL("fec_max"), VAL("fec_data_max"), VAL("shred_storage_gib"), VAL("shred_cache_bytes"), VAL("fec_set_cnt"), 1, VAL("shred_tile_cnt") ); } static ulong @@ -144,7 +144,7 @@ store_new( fd_topo_t const * topo, VAL("fec_max"), VAL("fec_data_max"), VAL("shred_storage_gib"), VAL("shred_cache_bytes"), VAL("fec_set_cnt"), VAL("max_shreds_per_block"), - disk_seed, !!VAL("alpenglow"), VAL("shred_tile_cnt") ) ); + disk_seed, 1, VAL("shred_tile_cnt") ) ); } fd_topo_obj_callbacks_t fd_obj_cb_store = { diff --git a/src/app/firedancer/commands/wait.c b/src/app/firedancer/commands/wait.c index e706a980257..b6af05109b4 100644 --- a/src/app/firedancer/commands/wait.c +++ b/src/app/firedancer/commands/wait.c @@ -657,22 +657,17 @@ wait_cmd_fn( args_t * args, ulong min_idle_ns = args->wait.min_idle_ns; ulong max_delinquent_stake_pct = args->wait.max_delinquent_stake_pct; - /* Consensus mode decides where delinquent stake comes from. */ - char const * consensus = fd_topo_find_tile( &config->topo, "votor", 0UL )!=ULONG_MAX ? "alpenglow" : "tower"; - if( FD_UNLIKELY( min_idle_ns ) ) { ulong min_idle_s = min_idle_ns / 1000000000UL; FD_LOG_NOTICE(( "waiting for a safe window " - "(consensus=%s, min-idle-seconds=%lus, max-delinquent-stake=%lu%%, skip-health=%s, skip-snapshot=%s)", - consensus, + "(min-idle-seconds=%lus, max-delinquent-stake=%lu%%, skip-health=%s, skip-snapshot=%s)", min_idle_s, max_delinquent_stake_pct, skip_health ? "yes" : "no", skip_snapshot ? "yes" : "no" )); } else { FD_LOG_NOTICE(( "waiting for a safe window " - "(consensus=%s, min-idle-slots=%lu, max-delinquent-stake=%lu%%, skip-health=%s, skip-snapshot=%s)", - consensus, + "(min-idle-slots=%lu, max-delinquent-stake=%lu%%, skip-health=%s, skip-snapshot=%s)", min_idle_slots, max_delinquent_stake_pct, skip_health ? "yes" : "no", diff --git a/src/app/firedancer/config/default.toml b/src/app/firedancer/config/default.toml index 603d8289db8..780b0d12050 100644 --- a/src/app/firedancer/config/default.toml +++ b/src/app/firedancer/config/default.toml @@ -282,18 +282,13 @@ telemetry = true # base directory above. The directory must exist, be writable by # the Firedancer user. - # In Tower, the tower file is only created if - # `[tiles.tower.write_vote_history_file]` is set to true. The - # validator saves its last vote to `tower-1_9-.bin` in - # this directory after each vote, which is the file name Agave uses. - - # In Alpenglow, the validator writes `vote_history-.bin` - # before each vote and each block it produces. This is an - # Agave-compatible vote history file and it protects against - # equivocation (both double-voting and double-producing blocks) on - # set-identity or restart. See the book for a sample runbook on how - # to safely set-identity. - + # The validator writes `vote_history-.bin` before each + # vote and each block it produces. This is an Agave-compatible vote + # history file and it protects against equivocation (both + # double-voting and double-producing blocks) on set-identity or + # restart. See the book for a sample runbook on how to safely + # use set-identity. + # # Two substitutions will be performed on this string. If "{user}" # is present it will be replaced with the user running Firedancer, # as above, and "{name}" will be replaced with the name of the @@ -720,13 +715,6 @@ telemetry = true wait_for_supermajority_at_slot = 0 wait_for_supermajority_with_bank_hash = "" - # Whether to run Alpenglow consensus. Firedancer runs exactly one - # consensus algorithm, so when this is enabled any Tower-related - # components will be replaced with Alpenglow. - # - # You can also enable Alpenglow with the `--alpenglow` argument. - alpenglow = false - # The primary state a validator is required to maintain is the accounts # database, a current listing of all "public key" identifiers on the # chain, and their associated balance (lamports), owner, and data. @@ -1865,22 +1853,6 @@ telemetry = true # FEC payload spills can consume additional space. shred_storage_limit_gib = 50 - # The tower tile runs pre-Alpenglow consensus and decides what - # blocks to vote for. The tower file is optional, but used for safer - # identity switches that avoid consensus violations like double - # voting and vote lockout violations. If Alpenglow is enabled, this - # option will be ignored. - [tiles.tower] - write_vote_history_file = true - - # The txsend tile is responsible for sending transactions out to - # the network over QUIC. In the default configuration, this is just - # used to send votes that our validator produces. - [tiles.txsend] - # The port the send tile uses to make outgoing QUIC connections, - # and also set as the src port on non-QUIC UDP packets. - txsend_src_port = 9006 - # The metric tile receives metrics updates published from the rest # of the tiles and serves them via. a Prometheus compatible HTTP # endpoint. diff --git a/src/app/firedancer/config/devnet.toml b/src/app/firedancer/config/devnet.toml index 89e69940ab0..30462dcf633 100644 --- a/src/app/firedancer/config/devnet.toml +++ b/src/app/firedancer/config/devnet.toml @@ -8,4 +8,3 @@ max_accounts = 500_000_000 [consensus] expected_genesis_hash = "EtWTRABZaYq6iMfeYKouRu166VU2xqa1wcaWoxPkrZBG" - alpenglow = true diff --git a/src/app/firedancer/config/testnet-jito.toml b/src/app/firedancer/config/testnet-jito.toml index 9fc5169b106..7daad7b66ce 100644 --- a/src/app/firedancer/config/testnet-jito.toml +++ b/src/app/firedancer/config/testnet-jito.toml @@ -5,7 +5,6 @@ ] [consensus] expected_genesis_hash = "4uhcVJyU9pJkvQyS88uRDiswHXSCkY3zQawwpjk2NsNY" - alpenglow = true [accounts] max_accounts = 200_000_000 [snapshots] diff --git a/src/app/firedancer/config/testnet.toml b/src/app/firedancer/config/testnet.toml index 6d5b8d49a4d..0d8c7dd598e 100644 --- a/src/app/firedancer/config/testnet.toml +++ b/src/app/firedancer/config/testnet.toml @@ -5,7 +5,6 @@ ] [consensus] expected_genesis_hash = "4uhcVJyU9pJkvQyS88uRDiswHXSCkY3zQawwpjk2NsNY" - alpenglow = true [accounts] max_accounts = 200_000_000 [snapshots] diff --git a/src/app/firedancer/main.c b/src/app/firedancer/main.c index 835757ad8f5..d99ad4482b5 100644 --- a/src/app/firedancer/main.c +++ b/src/app/firedancer/main.c @@ -92,7 +92,6 @@ extern fd_topo_run_tile_t fd_tile_dedup; extern fd_topo_run_tile_t fd_tile_resolv; extern fd_topo_run_tile_t fd_tile_pack; extern fd_topo_run_tile_t fd_tile_execle; -extern fd_topo_run_tile_t fd_tile_poh; extern fd_topo_run_tile_t fd_tile_motor; extern fd_topo_run_tile_t fd_tile_shred; extern fd_topo_run_tile_t fd_tile_sign; @@ -110,13 +109,10 @@ extern fd_topo_run_tile_t fd_tile_admin; extern fd_topo_run_tile_t fd_tile_gossvf; extern fd_topo_run_tile_t fd_tile_gossip; -extern fd_topo_run_tile_t fd_tile_repair; extern fd_topo_run_tile_t fd_tile_rotor; extern fd_topo_run_tile_t fd_tile_rserve; extern fd_topo_run_tile_t fd_tile_replay; extern fd_topo_run_tile_t fd_tile_execrp; -extern fd_topo_run_tile_t fd_tile_txsend; -extern fd_topo_run_tile_t fd_tile_tower; extern fd_topo_run_tile_t fd_tile_votor; extern fd_topo_run_tile_t fd_tile_accdb; extern fd_topo_run_tile_t fd_tile_solcap; @@ -153,15 +149,11 @@ fd_topo_run_tile_t * TILES[] = { &fd_tile_bundle, &fd_tile_gossvf, &fd_tile_gossip, - &fd_tile_repair, &fd_tile_rotor, &fd_tile_rserve, &fd_tile_replay, &fd_tile_execrp, - &fd_tile_poh, &fd_tile_motor, - &fd_tile_txsend, - &fd_tile_tower, &fd_tile_votor, &fd_tile_accdb, &fd_tile_snapct, diff --git a/src/app/firedancer/topology.c b/src/app/firedancer/topology.c index e24701c83fd..106accec862 100644 --- a/src/app/firedancer/topology.c +++ b/src/app/firedancer/topology.c @@ -5,7 +5,6 @@ #include "../../discof/replay/fd_execrp.h" #include "../../discof/genesis/fd_genesi_tile.h" #include "../../discof/gossip/fd_gossip_tile.h" -#include "../../discof/tower/fd_tower_tile.h" #include "../../discof/resolv/fd_resolv_tile.h" #include "../../discof/repair/fd_repair.h" #include "../../discof/replay/fd_replay_tile.h" @@ -17,7 +16,6 @@ #include "../../discof/backup/fd_snapsv_tile.h" #include "../../disco/shred/fd_shred_tile.h" #include "../../disco/store/fd_store.h" -#include "../../discof/repair/fd_repair_tile.h" #include "../../discof/rotor/fd_rotor_tile.h" #include "../../choreo/rotor/fd_rotor.h" #include "../../disco/net/fd_net_tile.h" @@ -178,7 +176,6 @@ setup_topo_store( fd_topo_t * topo, ulong fec_set_cnt, ulong max_shreds_per_block, char const * db_path, - int alpenglow, ulong shred_tile_cnt ) { if( FD_UNLIKELY( !shred_cache_mib ) ) FD_LOG_ERR(( "tiles.shred.shred_cache_size_mib must be non-zero; the FEC payload cache cannot be disabled" )); @@ -200,7 +197,6 @@ setup_topo_store( fd_topo_t * topo, FD_TEST( fd_pod_insertf_ulong( topo->props, fec_set_cnt, "obj.%lu.fec_set_cnt", obj->id ) ); FD_TEST( fd_pod_insertf_ulong( topo->props, max_shreds_per_block, "obj.%lu.max_shreds_per_block", obj->id ) ); FD_TEST( fd_pod_insertf_ulong( topo->props, seed, "obj.%lu.seed", obj->id ) ); - FD_TEST( fd_pod_insertf_ulong( topo->props, (ulong)!!alpenglow, "obj.%lu.alpenglow", obj->id ) ); FD_TEST( fd_pod_insertf_ulong( topo->props, shred_tile_cnt, "obj.%lu.shred_tile_cnt", obj->id ) ); if( db_path ) { FD_TEST( fd_pod_insertf_cstr( topo->props, db_path, "obj.%lu.disk_path", obj->id ) ); @@ -286,13 +282,8 @@ fd_topo_initialize( config_t * config ) { int telemetry_enabled = config->telemetry && strcmp( config->tiles.event.url, "" ); int leader_enabled = !!config->firedancer.layout.enable_block_production; int rserve_enabled = config->tiles.rserve.enabled; - int alpenglow_enabled = config->firedancer.consensus.alpenglow; - int tower_file_enabled = !alpenglow_enabled && config->tiles.tower.write_vote_history_file; int efficient_mode = !strcmp( config->firedancer.layout.mode, "efficient" ); - int gossip_vote_enabled = leader_enabled || ( rpc_enabled && !alpenglow_enabled ); - - char const * repair = alpenglow_enabled ? "rotor" : "repair"; - char const * poh = alpenglow_enabled ? "motor" : "poh"; + int gossip_vote_enabled = leader_enabled; if( FD_UNLIKELY( snapmk_enabled ) ) { FD_CHECK_ERR( config->firedancer.snapshots.max_full_snapshots_to_keep, @@ -317,14 +308,12 @@ fd_topo_initialize( config_t * config ) { fd_topob_wksp( topo, "gossvf" ); fd_topob_wksp( topo, "gossip" ); fd_topob_wksp( topo, "shred" ); - fd_topob_wksp( topo, repair ); + fd_topob_wksp( topo, "rotor" ); if( rserve_enabled ) fd_topob_wksp( topo, "rserve" ); fd_topob_wksp( topo, "replay" ); fd_topob_wksp( topo, "accdb" ); fd_topob_wksp( topo, "execrp" ); - if( alpenglow_enabled ) fd_topob_wksp( topo, "votor" ); - else fd_topob_wksp( topo, "tower" ); - if( !alpenglow_enabled ) fd_topob_wksp( topo, "txsend" ); + fd_topob_wksp( topo, "votor" ); fd_topob_wksp( topo, "sign" )->core_dump_level = FD_TOPO_CORE_DUMP_LEVEL_NEVER; fd_topob_wksp( topo, "admin" )->core_dump_level = FD_TOPO_CORE_DUMP_LEVEL_NEVER; @@ -335,7 +324,7 @@ fd_topo_initialize( config_t * config ) { fd_topob_wksp( topo, "resolv" ); fd_topob_wksp( topo, "pack" ); fd_topob_wksp( topo, "execle" ); - fd_topob_wksp( topo, poh ); + fd_topob_wksp( topo, "motor" ); } else { execle_tile_cnt = 0UL; resolv_tile_cnt = 0UL; @@ -351,8 +340,7 @@ fd_topo_initialize( config_t * config ) { fd_topob_wksp( topo, "net_shred" ); fd_topob_wksp( topo, "net_repair" ); if( rserve_enabled ) fd_topob_wksp( topo, "net_rserve" ); - if( alpenglow_enabled ) fd_topob_wksp( topo, "net_votor" ); - if( !alpenglow_enabled ) fd_topob_wksp( topo, "net_txsend" ); + fd_topob_wksp( topo, "net_votor" ); if( leader_enabled ) fd_topob_wksp( topo, "net_quic" ); fd_topob_wksp( topo, "genesi_out" ); @@ -370,9 +358,7 @@ fd_topo_initialize( config_t * config ) { fd_topob_wksp( topo, "replay_execrp" ); fd_topob_wksp( topo, "replay_out" ); fd_topob_wksp( topo, "replay_slot" ); - if( alpenglow_enabled ) fd_topob_wksp( topo, "votor_out" ); - else fd_topob_wksp( topo, "tower_out" ); - if( !alpenglow_enabled ) fd_topob_wksp( topo, "txsend_out" ); + fd_topob_wksp( topo, "votor_out" ); if( leader_enabled ) { fd_topob_wksp( topo, "quic_verify" ); @@ -412,23 +398,11 @@ fd_topo_initialize( config_t * config ) { if( rserve_enabled ) { fd_topob_wksp( topo, "rserve_sign" ); fd_topob_wksp( topo, "sign_rserve" ); - if( alpenglow_enabled ) fd_topob_wksp( topo, "rotor_rserve" ); - } - - if( !alpenglow_enabled ) { - fd_topob_wksp( topo, "txsend_sign" ); - fd_topob_wksp( topo, "sign_txsend" ); - } - - if( tower_file_enabled ) { - fd_topob_wksp( topo, "tower_sign" ); - fd_topob_wksp( topo, "sign_tower" ); + fd_topob_wksp( topo, "rotor_rserve" ); } - if( alpenglow_enabled ) { - fd_topob_wksp( topo, "votor_sign" ); - fd_topob_wksp( topo, "sign_votor" ); - } + fd_topob_wksp( topo, "votor_sign" ); + fd_topob_wksp( topo, "sign_votor" ); fd_topob_wksp( topo, "execrp_replay" ); fd_topob_wksp( topo, "admin_replay" ); @@ -471,26 +445,19 @@ fd_topo_initialize( config_t * config ) { replay loses a parent bank, and polls nothing until that is published, so repair_out holds all of it. */ - ulong repair_out_depth = fd_ulong_if( alpenglow_enabled, - fd_ulong_pow2_up( config->firedancer.runtime.max_live_slots * (config->limits.max_shreds_per_block/FD_FEC_SHRED_CNT) ), - shred_depth ); + ulong repair_out_depth = fd_ulong_pow2_up( config->firedancer.runtime.max_live_slots * (config->limits.max_shreds_per_block/FD_FEC_SHRED_CNT) ); /* topo, link_name, wksp_name, depth, mtu, burst */ /**/ fd_topob_link( topo, "gossip_net", "net_gossip", 32768UL, FD_NET_MTU, 1UL ); FOR(shred_tile_cnt) fd_topob_link( topo, "shred_net", "net_shred", 32768UL, FD_NET_MTU, 1UL ); /**/ fd_topob_link( topo, "repair_net", "net_repair", config->net.ingress_buffer_size, FD_NET_MTU, 1UL ); - if( !alpenglow_enabled ) { - /**/ fd_topob_link( topo, "txsend_net", "net_txsend", config->net.ingress_buffer_size, FD_NET_MTU, 1UL ); - } FOR(quic_tile_cnt) fd_topob_link( topo, "quic_net", "net_quic", config->net.ingress_buffer_size, FD_NET_MTU, 1UL ); if( FD_LIKELY( rserve_enabled ) ) { /**/ fd_topob_link( topo, "rserve_net", "net_rserve", config->net.ingress_buffer_size, FD_NET_MTU, 1UL ); /**/ fd_topob_link( topo, "rserve_sign", "rserve_sign", 128UL, 32UL, 1UL ); /**/ fd_topob_link( topo, "sign_rserve", "sign_rserve", 128UL, sizeof(fd_ed25519_sig_t), 1UL ); - if( alpenglow_enabled ) { - /**/ fd_topob_link( topo, "rotor_rserve", "rotor_rserve", 2048UL, sizeof(fd_rotor_block_t), 1UL ); - } + /**/ fd_topob_link( topo, "rotor_rserve", "rotor_rserve", 2048UL, sizeof(fd_rotor_block_t), 1UL ); } if( FD_LIKELY( snapshots_enabled ) ) { @@ -540,7 +507,7 @@ fd_topo_initialize( config_t * config ) { /**/ fd_topob_link( topo, "replay_epoch", "replay_epoch", 32UL, FD_EPOCH_OUT_MTU, 1UL ); /* replay publishes two epochs at boot and must keep STEM_BURST (4) credits after */ /**/ fd_topob_link( topo, "replay_out", "replay_out", 65536UL, sizeof(fd_replay_message_t), 1UL )->permit_no_consumers = 1; /**/ fd_topob_link( topo, "replay_slot", "replay_slot", 4096UL, sizeof(fd_replay_message_t), 1UL ); - if( alpenglow_enabled ) fd_topob_link( topo, "replay_rotor", "replay_slot", 16UL, sizeof(fd_replay_message_t), 1UL ); /* REPLAY_SIG_MISSING_FEC; >= replay's STEM_BURST (4) */ + /**/ fd_topob_link( topo, "replay_rotor", "replay_slot", 16UL, sizeof(fd_replay_message_t), 1UL ); /* REPLAY_SIG_MISSING_FEC; >= replay's STEM_BURST (4) */ FOR(execrp_tile_cnt) fd_topob_link( topo, "replay_execrp", "replay_execrp", 256UL, sizeof(fd_execrp_task_msg_t), 1UL ); /**/ fd_topob_link( topo, "admin_replay", "admin_replay", 32UL, 0UL, 1UL ); /**/ fd_topob_link( topo, "replay_admin", "admin_replay", 32UL, 0UL, 1UL ); @@ -568,22 +535,8 @@ fd_topo_initialize( config_t * config ) { FOR(sign_tile_cnt-1) fd_topob_link( topo, "repair_sign", "repair_sign", 256UL, FD_REPAIR_MAX_PREIMAGE_SZ, 1UL ); /* See repair_tile.c for explanation */ FOR(sign_tile_cnt-1) fd_topob_link( topo, "sign_repair", "sign_repair", 256UL, sizeof(fd_ed25519_sig_t), 1UL ); - if( !alpenglow_enabled ) { - /**/ fd_topob_link( topo, "txsend_sign", "txsend_sign", 128UL, FD_TXN_MTU_V0, 1UL ); /* TODO: Depth probably doesn't need to be 128 */ - /**/ fd_topob_link( topo, "sign_txsend", "sign_txsend", 128UL, sizeof(fd_ed25519_sig_t)*2UL, 1UL ); /* TODO: Depth probably doesn't need to be 128 */ - } - - if( tower_file_enabled ) { - /**/ fd_topob_link( topo, "tower_sign", "tower_sign", 128UL, 4096UL, 1UL ); - /**/ fd_topob_link( topo, "sign_tower", "sign_tower", 128UL, sizeof(fd_ed25519_sig_t), 1UL ); - } - FOR(shred_tile_cnt) fd_topob_link( topo, "shred_out", "shred_out", shred_depth, sizeof(fd_shred_message_t), FD_SHRED_STEM_BURST ); - /**/ fd_topob_link( topo, "repair_out", "repair_out", repair_out_depth, sizeof(fd_repair_fec_complete_t), 1UL ); - if( !alpenglow_enabled ) { - /**/ fd_topob_link( topo, "tower_out", "tower_out", 16384UL, sizeof(fd_tower_msg_t), 2UL ); /* conf + slot_done. see explanation in fd_tower_tile.h for link_depth */ - /**/ fd_topob_link( topo, "txsend_out", "txsend_out", 128UL, FD_TPU_RAW_MTU, 1UL ); - } + /**/ fd_topob_link( topo, "repair_out", "repair_out", repair_out_depth, sizeof(fd_rotor_replay_fec_t), 1UL ); FOR(execrp_tile_cnt) fd_topob_link( topo, "execrp_replay", "execrp_replay", 16384UL, sizeof(fd_execrp_task_done_msg_t), 1UL ); if( FD_LIKELY( config->tiles.gui.enabled ) ) { @@ -626,7 +579,6 @@ fd_topo_initialize( config_t * config ) { FOR(net_tile_cnt) fd_topos_net_rx_link( topo, "net_shred", i, config->net.ingress_buffer_size ); FOR(net_tile_cnt) fd_topos_net_rx_link( topo, "net_repair", i, config->net.ingress_buffer_size ); if( rserve_enabled ) FOR(net_tile_cnt) fd_topos_net_rx_link( topo, "net_rserve", i, config->net.ingress_buffer_size ); - if( !alpenglow_enabled ) FOR(net_tile_cnt) fd_topos_net_rx_link( topo, "net_txsend", i, config->net.ingress_buffer_size ); if( leader_enabled ) FOR(net_tile_cnt) fd_topos_net_rx_link( topo, "net_quic", i, config->net.ingress_buffer_size ); /* topo, tile_name, tile_wksp, metrics_wksp, cpu_idx, uses_id_keyswitch, uses_av_keyswitch, is_waker_client */ @@ -654,19 +606,14 @@ fd_topo_initialize( config_t * config ) { /**/ fd_topob_tile( topo, "gossip", "gossip", "metric_in", tile_to_cpu[ topo->tile_cnt ], 1, 0, 0 ); FOR(shred_tile_cnt) fd_topob_tile( topo, "shred", "shred", "metric_in", tile_to_cpu[ topo->tile_cnt ], 1, 0, 0 ); - /**/ fd_topob_tile( topo, repair, repair, "metric_in", tile_to_cpu[ topo->tile_cnt ], 1, 0, 0 ); + /**/ fd_topob_tile( topo, "rotor", "rotor", "metric_in", tile_to_cpu[ topo->tile_cnt ], 1, 0, 0 ); if( rserve_enabled) { /**/ fd_topob_tile( topo, "rserve", "rserve", "metric_in", tile_to_cpu[ topo->tile_cnt ], 1, 0, 0 ); } /**/ fd_topob_tile( topo, "replay", "replay", "metric_in", tile_to_cpu[ topo->tile_cnt ], 1, 0, 0 ); /**/ fd_topob_tile( topo, "accdb", "accdb", "metric_in", tile_to_cpu[ topo->tile_cnt ], 0, 0, 0 ); FOR(execrp_tile_cnt) fd_topob_tile( topo, "execrp", "execrp", "metric_in", tile_to_cpu[ topo->tile_cnt ], 0, 0, 0 ); - if( alpenglow_enabled ) { - /**/ fd_topob_tile( topo, "votor", "votor", "metric_in", tile_to_cpu[ topo->tile_cnt ], 1, 1, 0 ); - } else { - /**/ fd_topob_tile( topo, "tower", "tower", "metric_in", tile_to_cpu[ topo->tile_cnt ], 1, 1, 0 ); - /**/ fd_topob_tile( topo, "txsend", "txsend", "metric_in", tile_to_cpu[ topo->tile_cnt ], 1, 1, 0 ); - } + /**/ fd_topob_tile( topo, "votor", "votor", "metric_in", tile_to_cpu[ topo->tile_cnt ], 1, 1, 0 ); if( leader_enabled ) { FOR(quic_tile_cnt) fd_topob_tile( topo, "quic", "quic", "metric_in", tile_to_cpu[ topo->tile_cnt ], 0, 0, 0 ); @@ -675,7 +622,7 @@ fd_topo_initialize( config_t * config ) { FOR(resolv_tile_cnt) fd_topob_tile( topo, "resolv", "resolv", "metric_in", tile_to_cpu[ topo->tile_cnt ], 0, 0, 0 ); /**/ fd_topob_tile( topo, "pack", "pack", "metric_in", tile_to_cpu[ topo->tile_cnt ], config->tiles.bundle.enabled, 0, 0 ); FOR(execle_tile_cnt) fd_topob_tile( topo, "execle", "execle", "metric_in", tile_to_cpu[ topo->tile_cnt ], 0, 0, 0 ); - /**/ fd_topob_tile( topo, poh, poh, "metric_in", tile_to_cpu[ topo->tile_cnt ], 0, 0, 0 ); + /**/ fd_topob_tile( topo, "motor", "motor", "metric_in", tile_to_cpu[ topo->tile_cnt ], 0, 0, 0 ); } FOR(sign_tile_cnt) fd_topob_tile( topo, "sign", "sign", "metric_in", tile_to_cpu[ topo->tile_cnt ], 1, 1, 0 ); @@ -697,19 +644,13 @@ fd_topo_initialize( config_t * config ) { fd_topob_tile_in( topo, "gossvf", i, "metric_in", "net_gossvf", j, FD_TOPOB_UNRELIABLE, FD_TOPOB_POLLED ); /* No reliable consumers of networking fragments, may be dropped or overrun */ FOR(shred_tile_cnt) for( ulong j=0UL; jtiles.pack.use_consumed_cus ) ) { FOR(execle_tile_cnt)fd_topob_tile_out(topo, "execle", i, "execle_pack", i ); } - FOR(execle_tile_cnt) fd_topob_tile_in ( topo, poh, 0UL, "metric_in", "execle_poh", i, FD_TOPOB_RELIABLE, FD_TOPOB_POLLED ); - /**/ fd_topob_tile_in ( topo, poh, 0UL, "metric_in", "pack_poh", 0UL, FD_TOPOB_RELIABLE, FD_TOPOB_POLLED ); - /**/ fd_topob_tile_in ( topo, poh, 0UL, "metric_in", "replay_slot", 0UL, FD_TOPOB_RELIABLE, FD_TOPOB_POLLED ); - /**/ fd_topob_tile_out( topo, poh, 0UL, "poh_shred", 0UL ); - /**/ fd_topob_tile_out( topo, poh, 0UL, "poh_replay", 0UL ); + FOR(execle_tile_cnt) fd_topob_tile_in ( topo, "motor", 0UL, "metric_in", "execle_poh", i, FD_TOPOB_RELIABLE, FD_TOPOB_POLLED ); + /**/ fd_topob_tile_in ( topo, "motor", 0UL, "metric_in", "pack_poh", 0UL, FD_TOPOB_RELIABLE, FD_TOPOB_POLLED ); + /**/ fd_topob_tile_in ( topo, "motor", 0UL, "metric_in", "replay_slot", 0UL, FD_TOPOB_RELIABLE, FD_TOPOB_POLLED ); + /**/ fd_topob_tile_out( topo, "motor", 0UL, "poh_shred", 0UL ); + /**/ fd_topob_tile_out( topo, "motor", 0UL, "poh_replay", 0UL ); FOR(shred_tile_cnt) fd_topob_tile_in ( topo, "shred", i, "metric_in", "poh_shred", 0UL, FD_TOPOB_RELIABLE, FD_TOPOB_POLLED ); } @@ -895,11 +795,7 @@ fd_topo_initialize( config_t * config ) { FOR(shred_tile_cnt) fd_topob_tile_in ( topo, "shred", i, "metric_in", "gossip_ciaddr", 0UL, FD_TOPOB_RELIABLE, FD_TOPOB_POLLED ); FOR(shred_tile_cnt) fd_topob_tile_out( topo, "shred", i, "shred_out", i ); FOR(shred_tile_cnt) fd_topob_tile_in ( topo, "shred", i, "metric_in", "ipecho_out", 0UL, FD_TOPOB_RELIABLE, FD_TOPOB_POLLED ); - if( !alpenglow_enabled ) { - FOR(shred_tile_cnt) fd_topob_tile_in ( topo, "shred", i, "metric_in", "tower_out", 0UL, FD_TOPOB_UNRELIABLE, FD_TOPOB_POLLED ); - } else { - FOR(shred_tile_cnt) fd_topob_tile_in ( topo, "shred", i, "metric_in", "replay_slot", 0UL, FD_TOPOB_UNRELIABLE, FD_TOPOB_POLLED ); - } + FOR(shred_tile_cnt) fd_topob_tile_in ( topo, "shred", i, "metric_in", "replay_slot", 0UL, FD_TOPOB_UNRELIABLE, FD_TOPOB_POLLED ); FOR(shred_tile_cnt) fd_topob_tile_out( topo, "shred", i, "shred_net", i ); if( FD_LIKELY( telemetry_enabled ) ) { @@ -993,10 +889,10 @@ fd_topo_initialize( config_t * config ) { /**/ fd_topob_tile_out( topo, "sign", 0UL, "sign_shred", i ); } - FOR(sign_tile_cnt-1UL) fd_topob_tile_out( topo, repair, 0UL, "repair_sign", i ); + FOR(sign_tile_cnt-1UL) fd_topob_tile_out( topo, "rotor", 0UL, "repair_sign", i ); FOR(sign_tile_cnt-1UL) fd_topob_tile_in ( topo, "sign", i+1UL, "metric_in", "repair_sign", i, FD_TOPOB_UNRELIABLE, FD_TOPOB_POLLED ); FOR(sign_tile_cnt-1UL) fd_topob_tile_out( topo, "sign", i+1UL, "sign_repair", i ); - FOR(sign_tile_cnt-1UL) fd_topob_tile_in ( topo, repair, 0UL, "metric_in", "sign_repair", i, FD_TOPOB_UNRELIABLE, FD_TOPOB_POLLED ); /* This link is polled because the signing requests are asynchronous */ + FOR(sign_tile_cnt-1UL) fd_topob_tile_in ( topo, "rotor", 0UL, "metric_in", "sign_repair", i, FD_TOPOB_UNRELIABLE, FD_TOPOB_POLLED ); /* This link is polled because the signing requests are asynchronous */ if( rserve_enabled ) { /**/ fd_topob_tile_in ( topo, "sign", 0UL, "metric_in", "rserve_sign", 0UL, FD_TOPOB_RELIABLE, FD_TOPOB_POLLED ); @@ -1005,13 +901,6 @@ fd_topo_initialize( config_t * config ) { /**/ fd_topob_tile_out( topo, "sign", 0UL, "sign_rserve", 0UL ); } - if( !alpenglow_enabled ) { - /**/ fd_topob_tile_in ( topo, "sign", 0UL, "metric_in", "txsend_sign", 0UL, FD_TOPOB_RELIABLE, FD_TOPOB_POLLED ); - /**/ fd_topob_tile_out( topo, "txsend", 0UL, "txsend_sign", 0UL ); - /**/ fd_topob_tile_in ( topo, "txsend", 0UL, "metric_in", "sign_txsend", 0UL, FD_TOPOB_UNRELIABLE, FD_TOPOB_UNPOLLED ); - /**/ fd_topob_tile_out( topo, "sign", 0UL, "sign_txsend", 0UL ); - } - if( FD_UNLIKELY( rpc_enabled ) ) { fd_topob_link( topo, "rpc_replay", "rpc_replay", 8UL, 0UL, 1UL ); fd_topob_tile_out( topo, "rpc", 0UL, "rpc_replay", 0UL ); @@ -1020,10 +909,6 @@ fd_topo_initialize( config_t * config ) { fd_topob_tile_in( topo, "rpc", 0UL, "metric_in", "genesi_out", 0UL, FD_TOPOB_RELIABLE, FD_TOPOB_POLLED ); fd_topob_tile_in( topo, "replay", 0UL, "metric_in", "rpc_replay", 0UL, FD_TOPOB_RELIABLE, FD_TOPOB_POLLED ); fd_topob_tile_in( topo, "rpc", 0UL, "metric_in", "gossip_ciaddr", 0UL, FD_TOPOB_RELIABLE, FD_TOPOB_POLLED ); - if( !alpenglow_enabled ) { - fd_topob_tile_in( topo, "rpc", 0UL, "metric_in", "gossip_vote", 0UL, FD_TOPOB_RELIABLE, FD_TOPOB_POLLED ); - fd_topob_tile_in( topo, "rpc", 0UL, "metric_in", "tower_out", 0UL, FD_TOPOB_RELIABLE, FD_TOPOB_POLLED ); - } fd_topob_tile_in( topo, "rpc", 0UL, "metric_in", "replay_epoch", 0UL, FD_TOPOB_RELIABLE, FD_TOPOB_POLLED ); } @@ -1070,7 +955,7 @@ fd_topo_initialize( config_t * config ) { an object. */ if( 1 /* just restrict the scope for these variables in this big function */ ) { fd_topo_obj_t * rnonce_ss_obj = fd_topob_obj( topo, "rnonce_ss", "rnonce" ); - fd_topo_tile_t * repair_tile = &topo->tiles[ fd_topo_find_tile( topo, repair, 0UL ) ]; + fd_topo_tile_t * repair_tile = &topo->tiles[ fd_topo_find_tile( topo, "rotor", 0UL ) ]; fd_topob_tile_uses( topo, repair_tile, rnonce_ss_obj, FD_SHMEM_JOIN_MODE_READ_WRITE ); for( ulong i=0UL; itiles[ fd_topo_find_tile( topo, "shred", i ) ]; @@ -1083,9 +968,6 @@ fd_topo_initialize( config_t * config ) { represented as simple scalar metrics */ fd_topo_obj_t * node_info_obj = fd_topob_obj( topo, "node_info", "replay" ); fd_topob_tile_uses( topo, &topo->tiles[ fd_topo_find_tile( topo, "replay", 0UL ) ], node_info_obj, FD_SHMEM_JOIN_MODE_READ_WRITE ); - if( !alpenglow_enabled ) { - fd_topob_tile_uses( topo, &topo->tiles[ fd_topo_find_tile( topo, "tower", 0UL ) ], node_info_obj, FD_SHMEM_JOIN_MODE_READ_WRITE ); - } FD_TEST( fd_pod_insertf_ulong( topo->props, node_info_obj->id, "node_info" ) ); /* wait_info holds data for the wait command's safe-restart-window checks */ @@ -1094,9 +976,9 @@ fd_topo_initialize( config_t * config ) { FD_TEST( fd_pod_insertf_ulong( topo->props, wait_info_obj->id, "wait_info" ) ); if( leader_enabled ) { - fd_topo_obj_t * ldr_tt_obj = fd_topob_obj( topo, "ldr_tt", poh ); + fd_topo_obj_t * ldr_tt_obj = fd_topob_obj( topo, "ldr_tt", "motor" ); FD_TEST( fd_pod_insertf_ulong( topo->props, config->limits.max_txn_per_slot, "obj.%lu.max_txn_per_slot", ldr_tt_obj->id ) ); - fd_topob_tile_uses( topo, &topo->tiles[ fd_topo_find_tile( topo, poh, 0UL ) ], ldr_tt_obj, FD_SHMEM_JOIN_MODE_READ_WRITE ); + fd_topob_tile_uses( topo, &topo->tiles[ fd_topo_find_tile( topo, "motor", 0UL ) ], ldr_tt_obj, FD_SHMEM_JOIN_MODE_READ_WRITE ); fd_topob_tile_uses( topo, &topo->tiles[ fd_topo_find_tile( topo, "replay", 0UL ) ], ldr_tt_obj, FD_SHMEM_JOIN_MODE_READ_ONLY ); FD_TEST( fd_pod_insertf_ulong( topo->props, ldr_tt_obj->id, "ldr_tt" ) ); } @@ -1109,11 +991,8 @@ fd_topo_initialize( config_t * config ) { } fd_topo_obj_t * banks_obj = setup_topo_banks( topo, "banks", config->firedancer.runtime.max_live_slots, config->firedancer.runtime.bank_cache_cnt, config->firedancer.runtime.stake_delegation_cache_cnt, config->development.bench.max_cost_per_block ); /**/ fd_topob_tile_uses( topo, &topo->tiles[ fd_topo_find_tile( topo, "replay", 0UL ) ], banks_obj, FD_SHMEM_JOIN_MODE_READ_WRITE ); - if( !alpenglow_enabled ) { - /**/ fd_topob_tile_uses( topo, &topo->tiles[ fd_topo_find_tile( topo, "tower", 0UL ) ], banks_obj, FD_SHMEM_JOIN_MODE_READ_WRITE ); - } FOR(execrp_tile_cnt) fd_topob_tile_uses( topo, &topo->tiles[ fd_topo_find_tile( topo, "execrp", i ) ], banks_obj, FD_SHMEM_JOIN_MODE_READ_WRITE ); - if( alpenglow_enabled ) setup_topo_footer_cache( topo, "banks" ); + setup_topo_footer_cache( topo, "banks" ); FOR(execle_tile_cnt) fd_topob_tile_uses( topo, &topo->tiles[ fd_topo_find_tile( topo, "execle", i ) ], banks_obj, FD_SHMEM_JOIN_MODE_READ_WRITE ); FOR(resolv_tile_cnt) fd_topob_tile_uses( topo, &topo->tiles[ fd_topo_find_tile( topo, "resolv", i ) ], banks_obj, FD_SHMEM_JOIN_MODE_READ_ONLY ); if( snapmk_enabled ) { @@ -1124,7 +1003,7 @@ fd_topo_initialize( config_t * config ) { } FD_TEST( fd_pod_insertf_ulong( topo->props, banks_obj->id, "banks" ) ); - if( FD_UNLIKELY( config->firedancer.runtime.max_live_slots<32UL ) ) FD_LOG_ERR(( "max_live_slots must be >= 32 in order to support tower rooting" )); + if( FD_UNLIKELY( config->firedancer.runtime.max_live_slots<32UL ) ) FD_LOG_ERR(( "max_live_slots must be >= 32" )); setup_topo_progcache( topo, "progcache", config->firedancer.runtime.max_live_slots, @@ -1151,9 +1030,6 @@ fd_topo_initialize( config_t * config ) { FOR(shred_tile_cnt) fd_topob_tile_in( topo, "gui", 0UL, "metric_in", "shred_out", i, FD_TOPOB_RELIABLE, FD_TOPOB_POLLED ); /**/ fd_topob_tile_in( topo, "gui", 0UL, "metric_in", "gossip_gui", 0UL, FD_TOPOB_UNRELIABLE, FD_TOPOB_POLLED ); /**/ fd_topob_tile_in( topo, "gui", 0UL, "metric_in", "gossip_ciseen", 0UL, FD_TOPOB_RELIABLE, FD_TOPOB_POLLED ); - if( !alpenglow_enabled ) { - /**/ fd_topob_tile_in( topo, "gui", 0UL, "metric_in", "tower_out", 0UL, FD_TOPOB_RELIABLE, FD_TOPOB_POLLED ); - } /**/ fd_topob_tile_in( topo, "gui", 0UL, "metric_in", "replay_out", 0UL, FD_TOPOB_RELIABLE, FD_TOPOB_POLLED ); /**/ fd_topob_tile_in( topo, "gui", 0UL, "metric_in", "replay_epoch", 0UL, FD_TOPOB_RELIABLE, FD_TOPOB_POLLED ); /**/ fd_topob_tile_in( topo, "gui", 0UL, "metric_in", "genesi_out", 0UL, FD_TOPOB_RELIABLE, FD_TOPOB_POLLED ); @@ -1209,39 +1085,37 @@ fd_topo_initialize( config_t * config ) { /* Alpenglow links and tile connections. The optional GUI tile has been created by this point. */ - if( alpenglow_enabled ) { - /**/ fd_topob_link( topo, "votor_out", "votor_out", 8192UL, sizeof(fd_votor_msg_t), FD_VOTOR_OUT_BURST ); - /**/ fd_topob_link( topo, "votor_net", "net_votor", 32768UL, FD_NET_MTU, FD_VOTOR_NET_BURST ); /* as shred_net: fan-out publishes as it goes and rides depth, burst covers only the deferred aio flush */ - /**/ fd_topob_link( topo, "votor_sign", "votor_sign", 128UL, FD_KEYGUARD_SIGN_REQ_MTU, 1UL ); /* TLS 1.3 CertificateVerify payload or vote history file body */ - /**/ fd_topob_link( topo, "sign_votor", "sign_votor", 128UL, FD_BLS_SIG_SZ, 1UL ); /* ed25519 sig (TLS) or BLS sig (vote) */ - FOR(net_tile_cnt) fd_topos_net_rx_link( topo, "net_votor", i, config->net.ingress_buffer_size ); - - /**/ fd_topob_tile_in ( topo, "votor", 0UL, "metric_in", "replay_slot", 0UL, FD_TOPOB_RELIABLE, FD_TOPOB_POLLED ); - /**/ fd_topob_tile_in ( topo, "votor", 0UL, "metric_in", "replay_epoch", 0UL, FD_TOPOB_RELIABLE, FD_TOPOB_POLLED ); - /**/ fd_topob_tile_in ( topo, "votor", 0UL, "metric_in", "gossip_ciaddr", 0UL, FD_TOPOB_RELIABLE, FD_TOPOB_POLLED ); - /**/ fd_topob_tile_in ( topo, "votor", 0UL, "metric_in", "ipecho_out", 0UL, FD_TOPOB_UNRELIABLE, FD_TOPOB_POLLED ); - FOR(net_tile_cnt) fd_topob_tile_in ( topo, "votor", 0UL, "metric_in", "net_votor", i, FD_TOPOB_UNRELIABLE, FD_TOPOB_POLLED ); /* No reliable consumers of networking fragments, may be dropped or overrun */ - - /* Votor's first two outputs must be votor_out and votor_net. */ - /**/ fd_topob_tile_out( topo, "votor", 0UL, "votor_out", 0UL ); - /**/ fd_topob_tile_out( topo, "votor", 0UL, "votor_net", 0UL ); - /**/ fd_topos_tile_in_net( topo, "metric_in", "votor_net", 0UL, FD_TOPOB_UNRELIABLE, FD_TOPOB_POLLED ); /* No reliable consumers of networking fragments, may be dropped or overrun */ - - /**/ fd_topob_tile_in ( topo, "rotor", 0UL, "metric_in", "votor_out", 0UL, FD_TOPOB_RELIABLE, FD_TOPOB_POLLED ); /* certified blocks to repair */ - /**/ fd_topob_tile_in ( topo, "replay", 0UL, "metric_in", "votor_out", 0UL, FD_TOPOB_RELIABLE, FD_TOPOB_POLLED ); - if( config->tiles.gui.enabled ) { - /**/ fd_topob_tile_in ( topo, "gui", 0UL, "metric_in", "votor_out", 0UL, FD_TOPOB_RELIABLE, FD_TOPOB_POLLED ); - } - if( rpc_enabled ) { - /**/ fd_topob_tile_in ( topo, "rpc", 0UL, "metric_in", "votor_out", 0UL, FD_TOPOB_RELIABLE, FD_TOPOB_POLLED ); - } + /**/ fd_topob_link( topo, "votor_out", "votor_out", 8192UL, sizeof(fd_votor_msg_t), FD_VOTOR_OUT_BURST ); + /**/ fd_topob_link( topo, "votor_net", "net_votor", 32768UL, FD_NET_MTU, FD_VOTOR_NET_BURST ); /* as shred_net: fan-out publishes as it goes and rides depth, burst covers only the deferred aio flush */ + /**/ fd_topob_link( topo, "votor_sign", "votor_sign", 128UL, FD_KEYGUARD_SIGN_REQ_MTU, 1UL ); /* TLS 1.3 CertificateVerify payload or vote history file body */ + /**/ fd_topob_link( topo, "sign_votor", "sign_votor", 128UL, FD_BLS_SIG_SZ, 1UL ); /* ed25519 sig (TLS) or BLS sig (vote) */ + FOR(net_tile_cnt) fd_topos_net_rx_link( topo, "net_votor", i, config->net.ingress_buffer_size ); + + /**/ fd_topob_tile_in ( topo, "votor", 0UL, "metric_in", "replay_slot", 0UL, FD_TOPOB_RELIABLE, FD_TOPOB_POLLED ); + /**/ fd_topob_tile_in ( topo, "votor", 0UL, "metric_in", "replay_epoch", 0UL, FD_TOPOB_RELIABLE, FD_TOPOB_POLLED ); + /**/ fd_topob_tile_in ( topo, "votor", 0UL, "metric_in", "gossip_ciaddr", 0UL, FD_TOPOB_RELIABLE, FD_TOPOB_POLLED ); + /**/ fd_topob_tile_in ( topo, "votor", 0UL, "metric_in", "ipecho_out", 0UL, FD_TOPOB_UNRELIABLE, FD_TOPOB_POLLED ); + FOR(net_tile_cnt) fd_topob_tile_in ( topo, "votor", 0UL, "metric_in", "net_votor", i, FD_TOPOB_UNRELIABLE, FD_TOPOB_POLLED ); /* No reliable consumers of networking fragments, may be dropped or overrun */ - /**/ fd_topob_tile_in ( topo, "sign", 0UL, "metric_in", "votor_sign", 0UL, FD_TOPOB_RELIABLE, FD_TOPOB_POLLED ); - /**/ fd_topob_tile_out( topo, "votor", 0UL, "votor_sign", 0UL ); - /**/ fd_topob_tile_in ( topo, "votor", 0UL, "metric_in", "sign_votor", 0UL, FD_TOPOB_UNRELIABLE, FD_TOPOB_UNPOLLED ); - /**/ fd_topob_tile_out( topo, "sign", 0UL, "sign_votor", 0UL ); + /* Votor's first two outputs must be votor_out and votor_net. */ + /**/ fd_topob_tile_out( topo, "votor", 0UL, "votor_out", 0UL ); + /**/ fd_topob_tile_out( topo, "votor", 0UL, "votor_net", 0UL ); + /**/ fd_topos_tile_in_net( topo, "metric_in", "votor_net", 0UL, FD_TOPOB_UNRELIABLE, FD_TOPOB_POLLED ); /* No reliable consumers of networking fragments, may be dropped or overrun */ + + /**/ fd_topob_tile_in ( topo, "rotor", 0UL, "metric_in", "votor_out", 0UL, FD_TOPOB_RELIABLE, FD_TOPOB_POLLED ); /* certified blocks to repair */ + /**/ fd_topob_tile_in ( topo, "replay", 0UL, "metric_in", "votor_out", 0UL, FD_TOPOB_RELIABLE, FD_TOPOB_POLLED ); + if( config->tiles.gui.enabled ) { + /**/ fd_topob_tile_in ( topo, "gui", 0UL, "metric_in", "votor_out", 0UL, FD_TOPOB_RELIABLE, FD_TOPOB_POLLED ); + } + if( rpc_enabled ) { + /**/ fd_topob_tile_in ( topo, "rpc", 0UL, "metric_in", "votor_out", 0UL, FD_TOPOB_RELIABLE, FD_TOPOB_POLLED ); } + /**/ fd_topob_tile_in ( topo, "sign", 0UL, "metric_in", "votor_sign", 0UL, FD_TOPOB_RELIABLE, FD_TOPOB_POLLED ); + /**/ fd_topob_tile_out( topo, "votor", 0UL, "votor_sign", 0UL ); + /**/ fd_topob_tile_in ( topo, "votor", 0UL, "metric_in", "sign_votor", 0UL, FD_TOPOB_UNRELIABLE, FD_TOPOB_UNPOLLED ); + /**/ fd_topob_tile_out( topo, "sign", 0UL, "sign_votor", 0UL ); + /* Grant the admin tile access to every keyswitch object in the topology for add authorized voters and set identity. */ for( ulong i=0UL; itile_cnt; i++ ) { @@ -1289,39 +1163,12 @@ fd_topo_initialize( config_t * config ) { known an epoch in advance, so an adversary can target the densest window in the epoch, which is somewhat worse. - Without Alpenglow, this value is derived by first multiplying - max_live_slots by the maximum number of FEC sets per slot. Fixed - 32-data-shred sets need 512. - - However, the downstream structure reasm also has the same bound. - Replay relies on the guarantee that if a FEC set is present in - reasm, then it is present in store. Therefore, store needs - additional room for FEC sets that have been inserted into the - store but not yet consumed by reasm. - - This exactly equals the sum of link depths between shred (producer - into store), repair (intermediate tile) and replay (consumer into - reasm). This is depth(shred_out) + depth(repair_out). - - Shred inserts FECs into store (before publishing to shred_out) and - Replay inserts FECs into reasm (after consuming from repair_out), - so store can be up to depth(shred_out) + depth(repair_out) ahead - of reasm. We also add one to avoid a race when replay tile has - consumed from the link but not yet read from store. - - If store contains this sum of depths more FEC sets than reasm, - then the links must be full and shred is backpressured via - repair_out -> shred_out. This guarantees both 1. no more FEC sets - will be inserted into the store, which at this point is full and - 2. no more FEC sets (transitively) will be inserted into reasm, - which is also full. */ - - fd_topo_link_t * repair_out_link = &topo->links[ fd_topo_find_link( topo, "repair_out", 0UL ) ]; + Fixed 32-data-shred sets need 1024 FEC sets per slot; legacy + variable sets can contain one data shred and need up to 32768. */ + ulong fec_sets_per_slot = fd_ulong_if( config->firedancer.development.fixed_fec_sets, config->limits.max_shreds_per_block/FD_FEC_SHRED_CNT, config->limits.max_shreds_per_block ); - ulong store_fec_max = alpenglow_enabled - ? fd_rotor_blk_max( config->firedancer.runtime.max_live_slots ) * fec_sets_per_slot /* every FEC set of every blk rotor holds */ - : config->firedancer.runtime.max_live_slots * fec_sets_per_slot + (shred_depth * shred_tile_cnt) + repair_out_link->depth + 1; + ulong store_fec_max = fd_rotor_blk_max( config->firedancer.runtime.max_live_slots ) * fec_sets_per_slot; /* every FEC set of every blk rotor holds */ /* 32 shreds * 995 payload bytes = 31840 bytes with fixed_fec_sets = true 67 shreds * 955 payload bytes = 63985 bytes with fixed_fec_sets = false */ @@ -1333,11 +1180,10 @@ fd_topo_initialize( config_t * config ) { store_fec_set_cnt, config->limits.max_shreds_per_block, config->paths.shredb, - alpenglow_enabled, shred_tile_cnt ); FOR(shred_tile_cnt) fd_topob_tile_uses( topo, &topo->tiles[ fd_topo_find_tile( topo, "shred", i ) ], store_obj, FD_SHMEM_JOIN_MODE_READ_WRITE ); fd_topob_tile_uses( topo, &topo->tiles[ fd_topo_find_tile( topo, "replay", 0UL ) ], store_obj, FD_SHMEM_JOIN_MODE_READ_WRITE ); - if( alpenglow_enabled ) fd_topob_tile_uses( topo, &topo->tiles[ fd_topo_find_tile( topo, repair, 0UL ) ], store_obj, FD_SHMEM_JOIN_MODE_READ_WRITE ); /* rotor */ + fd_topob_tile_uses( topo, &topo->tiles[ fd_topo_find_tile( topo, "rotor", 0UL ) ], store_obj, FD_SHMEM_JOIN_MODE_READ_WRITE ); /* rotor */ if( rserve_enabled ) fd_topob_tile_uses( topo, &topo->tiles[ fd_topo_find_tile( topo, "rserve", 0UL ) ], store_obj, FD_SHMEM_JOIN_MODE_READ_WRITE ); FD_TEST( fd_pod_insertf_ulong( topo->props, store_obj->id, "store" ) ); @@ -1369,9 +1215,6 @@ fd_topo_initialize( config_t * config ) { snapmk_enabled ? config->firedancer.snapshots.max_incremental_snapshot_accounts : 0UL ); fd_topob_tile_uses( topo, &topo->tiles[ fd_topo_find_tile( topo, "accdb", 0UL ) ], accdb_obj, FD_SHMEM_JOIN_MODE_READ_WRITE ); fd_topob_tile_uses( topo, &topo->tiles[ fd_topo_find_tile( topo, "replay", 0UL ) ], accdb_obj, FD_SHMEM_JOIN_MODE_READ_WRITE ); - if( !alpenglow_enabled ) { - fd_topob_tile_uses( topo, &topo->tiles[ fd_topo_find_tile( topo, "tower", 0UL ) ], accdb_obj, FD_SHMEM_JOIN_MODE_READ_WRITE ); - } if( FD_UNLIKELY( !snapshots_enabled ) ) { fd_topob_tile_uses( topo, &topo->tiles[ fd_topo_find_tile( topo, "genesi", 0UL ) ], accdb_obj, FD_SHMEM_JOIN_MODE_READ_WRITE ); } @@ -1480,7 +1323,7 @@ fd_topo_initialize( config_t * config ) { for( ulong i=0UL; itile_cnt; i++ ) { fd_topo_configure_tile( &topo->tiles[ i ], config ); if( FD_UNLIKELY( !strcmp( topo->tiles[ i ].name, "gui" ) ) ) topo->tiles[ i ].gui.tile_cnt = topo->tile_cnt; - if( FD_UNLIKELY( alpenglow_enabled && ( !strcmp( topo->tiles[ i ].name, "net" ) || !strcmp( topo->tiles[ i ].name, "sock" ) || !strcmp( topo->tiles[ i ].name, "mlx5" ) ) ) ) { + if( FD_UNLIKELY( ( !strcmp( topo->tiles[ i ].name, "net" ) || !strcmp( topo->tiles[ i ].name, "sock" ) || !strcmp( topo->tiles[ i ].name, "mlx5" ) ) ) ) { topo->tiles[ i ].net.votor_quic_client_listen_port = config->firedancer.development.votor.quic_client_listen_port; topo->tiles[ i ].net.votor_quic_server_listen_port = config->firedancer.development.votor.quic_server_listen_port; } @@ -1567,7 +1410,6 @@ fd_topo_configure_tile( fd_topo_tile_t * tile, tile->net.gossip_listen_port = config->gossip.port; tile->net.repair_client_listen_port = config->tiles.repair.repair_client_listen_port; tile->net.repair_serve_listen_port = fd_ushort_if( config->tiles.rserve.enabled, config->tiles.rserve.repair_serve_listen_port, 0 ); - tile->net.txsend_src_port = fd_ushort_if( !config->firedancer.consensus.alpenglow, config->tiles.txsend.txsend_src_port, 0 ); } else if( FD_UNLIKELY( !strcmp( tile->name, "netlnk" ) ) ) { @@ -1648,7 +1490,7 @@ fd_topo_configure_tile( fd_topo_tile_t * tile, tile->gossip.ports.tpu_quic = config->tiles.quic.quic_transaction_listen_port; tile->gossip.ports.repair = config->tiles.repair.repair_client_listen_port; tile->gossip.ports.rserve = fd_ushort_if( config->tiles.rserve.enabled, config->tiles.rserve.repair_serve_listen_port, 0 ); - tile->gossip.ports.votor = fd_ushort_if( config->firedancer.consensus.alpenglow, config->firedancer.development.votor.quic_server_listen_port, (ushort)0 ); + tile->gossip.ports.votor = config->firedancer.development.votor.quic_server_listen_port; tile->gossip.entrypoints_cnt = config->gossip.entrypoints_cnt; for( ulong i=0UL; igossip.entrypoints_cnt; i++ ) { @@ -1710,27 +1552,12 @@ fd_topo_configure_tile( fd_topo_tile_t * tile, tile->snapin.expected_shred_version = config->consensus.expected_shred_version; tile->snapin.max_live_slots = config->firedancer.runtime.max_live_slots; tile->snapin.max_txn_per_slot = config->limits.max_txn_per_slot; - tile->snapin.alpenglow = config->firedancer.consensus.alpenglow; + tile->snapin.alpenglow = 1; tile->snapin.accdb_obj_id = fd_pod_query_ulong( config->topo.props, "accdb", ULONG_MAX ); tile->snapin.txncache_obj_id = fd_pod_query_ulong( config->topo.props, "txncache", ULONG_MAX ); tile->snapin.banks_obj_id = fd_pod_query_ulong( config->topo.props, "banks", ULONG_MAX ); tile->snapin.shmem_obj_id = fd_pod_query_ulong( config->topo.props, "snapin_shmem", ULONG_MAX ); - } else if( FD_UNLIKELY( !strcmp( tile->name, "repair" ) ) ) { - tile->repair.max_pending_shred_sets = config->tiles.shred.max_pending_shred_sets; - tile->repair.max_shreds_per_block = config->limits.max_shreds_per_block; - tile->repair.repair_client_listen_port = config->tiles.repair.repair_client_listen_port; - tile->repair.slot_max = config->tiles.repair.slot_max; - tile->repair.repair_sign_cnt = config->firedancer.layout.sign_tile_count - 1; /* -1 because this excludes the keyguard client */ - - for( ulong i=0; iin_cnt; i++ ) { - if( !strcmp( config->topo.links[ tile->in_link_id[ i ] ].name, "sign_repair" ) ) { - tile->repair.repair_sign_depth = config->topo.links[ tile->in_link_id[ i ] ].depth; - break; - } - } - fd_cstr_ncpy( tile->repair.identity_key_path, config->paths.identity_key, sizeof(tile->repair.identity_key_path) ); - } else if( FD_UNLIKELY( !strcmp( tile->name, "rotor" ) ) ) { tile->rotor.slot_max = config->firedancer.runtime.max_live_slots; tile->rotor.fec_max = fd_pod_queryf_ulong( config->topo.props, ULONG_MAX, "obj.%lu.fec_max", fd_pod_query_ulong( config->topo.props, "store", ULONG_MAX ) ); /* as many FEC sets as Store */ @@ -1745,12 +1572,11 @@ fd_topo_configure_tile( fd_topo_tile_t * tile, } } tile->rotor.repair_sign_cnt = config->firedancer.layout.sign_tile_count - 1; /* -1 because this excludes the keyguard client */ - fd_cstr_ncpy( tile->rotor.identity_key_path, config->paths.identity_key, sizeof(tile->repair.identity_key_path) ); + fd_cstr_ncpy( tile->rotor.identity_key_path, config->paths.identity_key, sizeof(tile->rotor.identity_key_path) ); } else if( FD_UNLIKELY( !strcmp( tile->name, "rserve" ) ) ) { tile->rserve.repair_serve_listen_port = config->tiles.rserve.repair_serve_listen_port; - tile->rserve.max_shreds_per_block = config->limits.max_shreds_per_block; tile->rserve.ping_cache_entries = 1UL<<16; /* TODO: Configure this from some global metric? */ - tile->rserve.blockdb_max = config->firedancer.consensus.alpenglow ? 1UL<<15 : 0UL; /* ~20.6 KiB each. TODO: make configurable */ + tile->rserve.blockdb_max = 1UL<<15; /* ~20.6 KiB each. TODO: make configurable */ fd_cstr_ncpy( tile->rserve.identity_key_path, config->paths.identity_key, sizeof(tile->rserve.identity_key_path) ); } else if( FD_UNLIKELY( !strcmp( tile->name, "replay" ) )) { @@ -1773,7 +1599,7 @@ fd_topo_configure_tile( fd_topo_tile_t * tile, tile->replay.expected_shred_version = config->consensus.expected_shred_version; tile->replay.wait_for_vote_to_start_leader = config->consensus.wait_for_vote_to_start_leader; - tile->replay.alpenglow = config->firedancer.consensus.alpenglow; + tile->replay.alpenglow = 1; tile->replay.sched_depth = config->tiles.replay.max_transaction_lookahead_buffer_size; wfs_decode_bank_hash( config, &tile->replay.wait_for_supermajority_with_bank_hash ); @@ -1842,25 +1668,6 @@ fd_topo_configure_tile( fd_topo_tile_t * tile, tile->votor.wait_for_supermajority_hash_is_zero = wfs_hash_is_zero( config ); tile->votor.expected_shred_version = config->consensus.expected_shred_version; - } else if( FD_UNLIKELY( !strcmp( tile->name, "tower" ) ) ) { - tile->tower.authorized_voter_paths_cnt = config->firedancer.paths.authorized_voter_paths_cnt; - tile->tower.max_shreds_per_block = config->limits.max_shreds_per_block; - for( ulong i=0UL; itower.authorized_voter_paths_cnt; i++ ) { - fd_cstr_ncpy( tile->tower.authorized_voter_paths[ i ], config->firedancer.paths.authorized_voter_paths[ i ], sizeof(tile->tower.authorized_voter_paths[ i ]) ); - } - - tile->tower.accdb_obj_id = fd_pod_query_ulong( config->topo.props, "accdb", ULONG_MAX ); - tile->tower.hard_fork_fatal = config->firedancer.development.hard_fork_fatal; - tile->tower.wait_for_supermajority_at_slot = config->firedancer.consensus.wait_for_supermajority_at_slot; - tile->tower.wait_for_supermajority_hash_is_zero = wfs_hash_is_zero( config ); - tile->tower.expected_shred_version = config->consensus.expected_shred_version; - tile->tower.max_live_slots = config->firedancer.runtime.max_live_slots; - fd_cstr_ncpy( tile->tower.identity_key, config->paths.identity_key, sizeof(tile->tower.identity_key) ); - fd_cstr_ncpy( tile->tower.vote_account, config->paths.vote_account, sizeof(tile->tower.vote_account) ); - fd_cstr_ncpy( tile->tower.tower_path, config->tiles.tower.write_vote_history_file ? config->paths.vote_history : "", sizeof(tile->tower.tower_path) ); - tile->tower.target_gid = config->gid; - tile->tower.target_uid = config->uid; - } else if( FD_UNLIKELY( !strcmp( tile->name, "accdb" ) ) ) { tile->accdb.accdb_obj_id = fd_pod_query_ulong( config->topo.props, "accdb", ULONG_MAX ); @@ -1885,12 +1692,6 @@ fd_topo_configure_tile( fd_topo_tile_t * tile, /* might not exist for topos without snapshot creation */ } - } else if( FD_UNLIKELY( !strcmp( tile->name, "txsend" ) ) ) { - - tile->txsend.txsend_src_port = config->tiles.txsend.txsend_src_port; - tile->txsend.ip_addr = config->net.ip_addr; - fd_cstr_ncpy( tile->txsend.identity_key_path, config->paths.identity_key, sizeof(tile->txsend.identity_key_path) ); - } else if( FD_UNLIKELY( !strcmp( tile->name, "quic" ) ) ) { tile->quic.reasm_cnt = config->tiles.quic.txn_reassembly_count; @@ -1957,12 +1758,6 @@ fd_topo_configure_tile( fd_topo_tile_t * tile, tile->execle.max_live_slots = config->firedancer.runtime.max_live_slots; tile->execle.report_runtime_diffs = config->development.event.report_runtime_diffs; - } else if( FD_UNLIKELY( !strcmp( tile->name, "poh" ) ) ) { - fd_cstr_ncpy( tile->poh.identity_key_path, config->paths.identity_key, sizeof(tile->poh.identity_key_path) ); - - tile->poh.execle_cnt = config->firedancer.layout.execle_tile_count; - tile->poh.max_txn_per_slot = config->limits.max_txn_per_slot; - } else if( FD_UNLIKELY( !strcmp( tile->name, "motor" ) ) ) { fd_cstr_ncpy( tile->motor.identity_key_path, config->paths.identity_key, sizeof(tile->motor.identity_key_path) ); @@ -1979,7 +1774,7 @@ fd_topo_configure_tile( fd_topo_tile_t * tile, tile->shred.expected_shred_version = config->consensus.expected_shred_version; tile->shred.shred_listen_port = config->tiles.shred.shred_listen_port; tile->shred.max_shreds_per_block = config->limits.max_shreds_per_block; - tile->shred.alpenglow = config->firedancer.consensus.alpenglow; + tile->shred.alpenglow = 1; tile->shred.bench_max_shreds_per_block = config->development.bench.max_shreds_per_block; tile->shred.adtl_dests_retransmit_cnt = config->tiles.shred.additional_shred_destinations_retransmit_cnt; tile->shred.adtl_dests_leader_cnt = config->tiles.shred.additional_shred_destinations_leader_cnt; @@ -2026,7 +1821,7 @@ fd_topo_configure_tile( fd_topo_tile_t * tile, tile->gui.listen_port = config->tiles.gui.gui_listen_port; tile->gui.max_txn_per_slot = config->limits.max_txn_per_slot; tile->gui.is_voting = strcmp( config->paths.vote_account, "" ); - tile->gui.is_alpenglow = config->firedancer.consensus.alpenglow; + tile->gui.is_alpenglow = 1; fd_cstr_ncpy( tile->gui.cluster, config->cluster, sizeof(tile->gui.cluster) ); fd_cstr_ncpy( tile->gui.identity_key_path, config->paths.identity_key, sizeof(tile->gui.identity_key_path) ); fd_cstr_ncpy( tile->gui.vote_key_path, config->paths.vote_account, sizeof(tile->gui.vote_key_path) ); @@ -2051,7 +1846,7 @@ fd_topo_configure_tile( fd_topo_tile_t * tile, parse_listen_addr( config->tiles.rpc.rpc_listen_address, "tiles.rpc.rpc_listen_address", &tile->rpc.listen_addr ); tile->rpc.listen_port = config->tiles.rpc.rpc_listen_port; tile->rpc.delay_startup = config->tiles.rpc.delay_startup; - tile->rpc.alpenglow = config->firedancer.consensus.alpenglow; + tile->rpc.alpenglow = 1; tile->rpc.max_http_connections = config->tiles.rpc.max_http_connections; tile->rpc.max_websocket_connections = config->tiles.rpc.max_websocket_connections; tile->rpc.send_buffer_size_mb = config->tiles.rpc.send_buffer_size_mb; @@ -2079,7 +1874,6 @@ fd_topo_configure_tile( fd_topo_tile_t * tile, } else if( FD_UNLIKELY( !strcmp( tile->name, "backt" ) ) ) { - tile->backtest.alpenglow = config->firedancer.consensus.alpenglow; tile->backtest.root_distance = config->firedancer.development.backtest.root_distance; fd_cstr_ncpy( tile->backtest.ledger_format, config->firedancer.development.ledger_input.format, sizeof(tile->backtest.ledger_format) ); fd_cstr_ncpy( tile->backtest.ledger_path, config->firedancer.development.ledger_input.path, PATH_MAX ); diff --git a/src/app/firedancer/topology.h b/src/app/firedancer/topology.h index 61353f36a50..e806cdf74ca 100644 --- a/src/app/firedancer/topology.h +++ b/src/app/firedancer/topology.h @@ -46,7 +46,6 @@ setup_topo_store( fd_topo_t * topo, ulong fec_set_cnt, ulong max_shreds_per_block, char const * db_path, - int alpenglow, ulong shred_tile_cnt ); fd_topo_obj_t * diff --git a/src/app/shared/boot/fd_boot.c b/src/app/shared/boot/fd_boot.c index 77bf0efefe3..7e2fe6f3f06 100644 --- a/src/app/shared/boot/fd_boot.c +++ b/src/app/shared/boot/fd_boot.c @@ -121,7 +121,6 @@ fd_global_options_help( fd_action_help_t * help ) { fd_action_help_arg( help, "--devnet", NULL, "Use Solana devnet defaults" ); fd_action_help_arg( help, "--mainnet-jito", NULL, "Use Solana mainnet defaults with the Jito relayer/bundles" ); fd_action_help_arg( help, "--testnet-jito", NULL, "Use Solana testnet defaults with the Jito relayer/bundles" ); - fd_action_help_arg( help, "--alpenglow", NULL, "Run Alpenglow consensus in lieu of Tower consensus" ); fd_action_help_arg( help, "--version", NULL, "Show the current software version" ); fd_action_help_arg( help, "--help/-h", NULL, "Print this help message" ); } @@ -262,8 +261,6 @@ fd_main( int argc, char ** argv = _argv; argc--; argv++; - int alpenglow = fd_env_strip_cmdline_contains( &argc, &argv, "--alpenglow" ); - /* Short circuit evaluating help and version commands so that we don't need to load and evaluate the entire config file to run them. This is useful for some operators in CI environments where, for @@ -319,7 +316,6 @@ fd_main( int argc, int is_local_cluster = action ? action->is_local_cluster : 0; int load_topo = fd_main_init( &argc, &argv, &config, opt_user_config_path, is_local_cluster, NULL, configs, 0 /* dev */ ); if( FD_LIKELY( load_topo && action ) ) fd_cstr_ncpy( config.action, action->name, sizeof( config.action ) ); - if( FD_UNLIKELY( load_topo && alpenglow ) ) config.firedancer.consensus.alpenglow = 1; if( FD_LIKELY( load_topo ) ) topo_init( &config ); if( FD_UNLIKELY( !action ) ) { diff --git a/src/app/shared/commands/configure/configure.c b/src/app/shared/commands/configure/configure.c index 24897409c9a..34efd6fe6ef 100644 --- a/src/app/shared/commands/configure/configure.c +++ b/src/app/shared/commands/configure/configure.c @@ -236,7 +236,6 @@ configure_stage_help( char const * name ) { if( !strcmp( name, "kill" ) ) return "kill any running validator"; if( !strcmp( name, "keys" ) ) return "generate dev identity/vote keypairs"; if( !strcmp( name, "genesis" ) ) return "generate a local cluster genesis"; - if( !strcmp( name, "blockstore" ) ) return "create the genesis block in the ledger blockstore"; return NULL; } diff --git a/src/app/shared/commands/configure/ethtool-channels.c b/src/app/shared/commands/configure/ethtool-channels.c index d733431ed96..ad8a6fb9d68 100644 --- a/src/app/shared/commands/configure/ethtool-channels.c +++ b/src/app/shared/commands/configure/ethtool-channels.c @@ -52,11 +52,8 @@ get_ports( fd_config_t const * config, ADD_PORT( config->gossip.port ); ADD_PORT( config->tiles.repair.repair_client_listen_port ); ADD_PORT( config->tiles.rserve.repair_serve_listen_port ); - ADD_PORT( config->tiles.txsend.txsend_src_port ); - if( config->firedancer.consensus.alpenglow ) { - ADD_PORT( config->firedancer.development.votor.quic_client_listen_port ); - ADD_PORT( config->firedancer.development.votor.quic_server_listen_port ); - } + ADD_PORT( config->firedancer.development.votor.quic_client_listen_port ); + ADD_PORT( config->firedancer.development.votor.quic_server_listen_port ); #undef ADD_PORT return port_cnt; diff --git a/src/app/shared/commands/configure/hyperthreads.c b/src/app/shared/commands/configure/hyperthreads.c index b11ddceb182..e78ca66ff2c 100644 --- a/src/app/shared/commands/configure/hyperthreads.c +++ b/src/app/shared/commands/configure/hyperthreads.c @@ -48,7 +48,7 @@ check( config_t const * config, fd_topo_t const * topo = &config->topo; for( ulong i=0UL; itile_cnt; i++ ) { fd_topo_tile_t const * tile = &topo->tiles[ i ]; - if( FD_LIKELY( tile->kind_id || !( !strcmp( tile->name, "pack" ) || !strcmp( tile->name, "poh" ) ) ) ) continue; + if( FD_LIKELY( tile->kind_id || strcmp( tile->name, "pack" ) ) ) continue; /* A floating tile (efficient layout mode) shares its cores with the other floaters, so an idle sibling buys it nothing */ diff --git a/src/app/shared/commands/watch/watch.c b/src/app/shared/commands/watch/watch.c index 2663d99c9d8..6f6ebaf933e 100644 --- a/src/app/shared/commands/watch/watch.c +++ b/src/app/shared/commands/watch/watch.c @@ -772,13 +772,6 @@ accdb_per_tile_offsets( char const * name, offs[6] =MIDX(COUNTER,REPLAY,ACCDB_COPIED_BYTES ); offs[7]=MIDX(COUNTER,REPLAY,ACCDB_ACCOUNT_EVICTED ); offs[8] =ULONG_MAX ; offs[9]=MIDX(COUNTER,REPLAY,ACCDB_ACCOUNT_NEW_COMMITTED ); offs[10]=MIDX(COUNTER,REPLAY,ACCDB_ACCOUNT_OVERWRITE_COMMITTED); - } else if( !strcmp( name, "tower" ) ) { - offs[0] =MIDX(COUNTER,TOWER,ACCDB_ACCOUNT_READONLY_ACQUIRED ); offs[1]=MIDX(COUNTER,TOWER,ACCDB_ACCOUNT_WRITABLE_ACQUIRED ); - offs[2] =MIDX(COUNTER,TOWER,ACCDB_ACCOUNT_CACHE_MISSED ); offs[3]=MIDX(COUNTER,TOWER,ACCDB_ACCOUNT_WAITED ); - offs[4] =MIDX(COUNTER,TOWER,ACCDB_DISK_READ_BYTES ); offs[5]=MIDX(COUNTER,TOWER,ACCDB_DISK_WRITTEN_BYTES ); - offs[6] =MIDX(COUNTER,TOWER,ACCDB_COPIED_BYTES ); offs[7]=MIDX(COUNTER,TOWER,ACCDB_ACCOUNT_EVICTED ); - offs[8] =ULONG_MAX ; offs[9]=MIDX(COUNTER,TOWER,ACCDB_ACCOUNT_NEW_COMMITTED ); - offs[10]=MIDX(COUNTER,TOWER,ACCDB_ACCOUNT_OVERWRITE_COMMITTED); } else if( !strcmp( name, "accdb" ) ) { /* The accdb tile only runs background work (compact, preevict, advance_root, purge); it never acquires/releases. Sentinel @@ -1143,20 +1136,14 @@ write_repair( config_t const * config, ulong const * cur_tile, ulong const * cur_link, ulong const * prev_link ) { - ulong repair_tile_idx = fd_topo_find_tile( &config->topo, "repair", 0UL ); - char const * repair_label = "repair "; - int is_rotor = 0; - if( repair_tile_idx==ULONG_MAX ) { - repair_tile_idx = fd_topo_find_tile( &config->topo, "rotor", 0UL ); - repair_label = "rotor "; - is_rotor = 1; - } + ulong repair_tile_idx = fd_topo_find_tile( &config->topo, "rotor", 0UL ); + char const * repair_label = "rotor "; if( repair_tile_idx==ULONG_MAX ) return 0U; - ulong highest_off = is_rotor ? MIDX( GAUGE, ROTOR, HIGHEST_DELIVERED_SLOT ) : MIDX( GAUGE, REPAIR, HIGHEST_REPAIRED_SLOT ); - ulong current_off = is_rotor ? MIDX( GAUGE, ROTOR, HIGHEST_RECEIVED_SLOT ) : MIDX( GAUGE, REPAIR, HIGHEST_TURBINE_SLOT ); + ulong highest_off = MIDX( GAUGE, ROTOR, HIGHEST_DELIVERED_SLOT ); + ulong current_off = MIDX( GAUGE, ROTOR, HIGHEST_RECEIVED_SLOT ); ulong repair_slot = cur_tile[ repair_tile_idx*FD_METRICS_TOTAL_SZ+highest_off ]; ulong turbine_slot = cur_tile[ repair_tile_idx*FD_METRICS_TOTAL_SZ+current_off ]; - if( FD_LIKELY( is_rotor ) ) turbine_slot = fd_ulong_max( turbine_slot, cur_tile[ repair_tile_idx*FD_METRICS_TOTAL_SZ+MIDX( GAUGE, ROTOR, CATCHUP_SLOT ) ] ); + turbine_slot = fd_ulong_max( turbine_slot, cur_tile[ repair_tile_idx*FD_METRICS_TOTAL_SZ+MIDX( GAUGE, ROTOR, CATCHUP_SLOT ) ] ); long repair_lag = (long)repair_slot-(long)turbine_slot; PRINT( ROWH( "▲", RED, "%s" ) K( "rx" ) "%s" @@ -1256,12 +1243,7 @@ write_rserve( config_t const * config, static uint write_replay( config_t const * config, ulong const * cur_tile ) { - int repair_is_rotor = 0; - ulong repair_tile_idx = fd_topo_find_tile( &config->topo, "repair", 0UL ); - if( repair_tile_idx==ULONG_MAX ) { // alpenglow - repair_tile_idx = fd_topo_find_tile( &config->topo, "rotor", 0UL ); - repair_is_rotor = 1; - } + ulong repair_tile_idx = fd_topo_find_tile( &config->topo, "rotor", 0UL ); ulong replay_tile_idx = fd_topo_find_tile( &config->topo, "replay", 0UL ); if( replay_tile_idx==ULONG_MAX ) return 0U; @@ -1272,8 +1254,8 @@ write_replay( config_t const * config, ulong turbine_slot; if( repair_tile_idx!=ULONG_MAX ) { - turbine_slot = cur_tile[ repair_tile_idx*FD_METRICS_TOTAL_SZ+( repair_is_rotor ? MIDX( GAUGE, ROTOR, HIGHEST_RECEIVED_SLOT ) : MIDX( GAUGE, REPAIR, HIGHEST_TURBINE_SLOT ) ) ]; - if( FD_LIKELY( repair_is_rotor ) ) turbine_slot = fd_ulong_max( turbine_slot, cur_tile[ repair_tile_idx*FD_METRICS_TOTAL_SZ+MIDX( GAUGE, ROTOR, CATCHUP_SLOT ) ] ); + turbine_slot = cur_tile[ repair_tile_idx*FD_METRICS_TOTAL_SZ+MIDX( GAUGE, ROTOR, HIGHEST_RECEIVED_SLOT ) ]; + turbine_slot = fd_ulong_max( turbine_slot, cur_tile[ repair_tile_idx*FD_METRICS_TOTAL_SZ+MIDX( GAUGE, ROTOR, CATCHUP_SLOT ) ] ); } else { turbine_slot = reset_slot; } @@ -1378,21 +1360,18 @@ write_gui( config_t const * config, ulong const * prev_tile ) { char const * gui_name = "gui"; ulong gui_tile_idx = fd_topo_find_tile( &config->topo, gui_name, 0UL ); + if( FD_UNLIKELY( gui_tile_idx==ULONG_MAX ) ) return 0U; ulong off_conn_active, off_websocket_conn_active, off_websocket_frame_tx, off_websocket_frame_rx; char * bytes_read_s; char * bytes_written_s; - if( FD_LIKELY( gui_tile_idx!=ULONG_MAX ) ) { - off_conn_active = MIDX( GAUGE, GUI, CONN_OPEN ); - off_websocket_conn_active = MIDX( GAUGE, GUI, WEBSOCKET_CONN_OPEN ); - off_websocket_frame_tx = MIDX( COUNTER, GUI, WEBSOCKET_FRAME_TX ); - off_websocket_frame_rx = MIDX( COUNTER, GUI, WEBSOCKET_FRAME_RX ); - - bytes_read_s = DIFF_BYTES( gui_name, COUNTER, GUI, CONN_RX_BYTES ); - bytes_written_s = DIFF_BYTES( gui_name, COUNTER, GUI, CONN_TX_BYTES ); - } else { - return 0U; - } + off_conn_active = MIDX( GAUGE, GUI, CONN_OPEN ); + off_websocket_conn_active = MIDX( GAUGE, GUI, WEBSOCKET_CONN_OPEN ); + off_websocket_frame_tx = MIDX( COUNTER, GUI, WEBSOCKET_FRAME_TX ); + off_websocket_frame_rx = MIDX( COUNTER, GUI, WEBSOCKET_FRAME_RX ); + + bytes_read_s = DIFF_BYTES( gui_name, COUNTER, GUI, CONN_RX_BYTES ); + bytes_written_s = DIFF_BYTES( gui_name, COUNTER, GUI, CONN_TX_BYTES ); ulong connection_count = cur_tile[ gui_tile_idx*FD_METRICS_TOTAL_SZ+off_conn_active ]+cur_tile[ gui_tile_idx*FD_METRICS_TOTAL_SZ+off_websocket_conn_active ]; ulong gui_total_ticks = total_regime( &cur_tile[ gui_tile_idx*FD_METRICS_TOTAL_SZ ] )-total_regime( &prev_tile[ gui_tile_idx*FD_METRICS_TOTAL_SZ ] ); @@ -1940,7 +1919,10 @@ run( config_t const * config, diff_tile( config, "rserve", tiles+(1UL-last_snap)*tile_cnt*FD_METRICS_TOTAL_SZ, tiles+last_snap*tile_cnt*FD_METRICS_TOTAL_SZ, MIDX( COUNTER, RSERVE, RESPONSE_TX_PING ) ) + diff_tile( config, "rserve", tiles+(1UL-last_snap)*tile_cnt*FD_METRICS_TOTAL_SZ, tiles+last_snap*tile_cnt*FD_METRICS_TOTAL_SZ, MIDX( COUNTER, RSERVE, RESPONSE_TX_WINDOW_INDEX ) ) + diff_tile( config, "rserve", tiles+(1UL-last_snap)*tile_cnt*FD_METRICS_TOTAL_SZ, tiles+last_snap*tile_cnt*FD_METRICS_TOTAL_SZ, MIDX( COUNTER, RSERVE, RESPONSE_TX_HIGHEST_WINDOW_INDEX ) ) + - diff_tile( config, "rserve", tiles+(1UL-last_snap)*tile_cnt*FD_METRICS_TOTAL_SZ, tiles+last_snap*tile_cnt*FD_METRICS_TOTAL_SZ, MIDX( COUNTER, RSERVE, RESPONSE_TX_ORPHAN ) ); + diff_tile( config, "rserve", tiles+(1UL-last_snap)*tile_cnt*FD_METRICS_TOTAL_SZ, tiles+last_snap*tile_cnt*FD_METRICS_TOTAL_SZ, MIDX( COUNTER, RSERVE, RESPONSE_TX_ORPHAN ) ) + + diff_tile( config, "rserve", tiles+(1UL-last_snap)*tile_cnt*FD_METRICS_TOTAL_SZ, tiles+last_snap*tile_cnt*FD_METRICS_TOTAL_SZ, MIDX( COUNTER, RSERVE, RESPONSE_TX_PARENT_FEC_SET_COUNT ) ) + + diff_tile( config, "rserve", tiles+(1UL-last_snap)*tile_cnt*FD_METRICS_TOTAL_SZ, tiles+last_snap*tile_cnt*FD_METRICS_TOTAL_SZ, MIDX( COUNTER, RSERVE, RESPONSE_TX_FEC_SET_ROOT ) ) + + diff_tile( config, "rserve", tiles+(1UL-last_snap)*tile_cnt*FD_METRICS_TOTAL_SZ, tiles+last_snap*tile_cnt*FD_METRICS_TOTAL_SZ, MIDX( COUNTER, RSERVE, RESPONSE_TX_WINDOW_INDEX_FOR_BLOCK_ID ) ); rserve_rps_valid_samples_idx++; rserve_rps_invalid_samples[ rserve_rps_invalid_samples_idx%(sizeof(rserve_rps_invalid_samples)/sizeof(rserve_rps_invalid_samples[0])) ] = 0UL; diff --git a/src/app/shared/fd_action.h b/src/app/shared/fd_action.h index 9689c72f348..78d6e91a5cb 100644 --- a/src/app/shared/fd_action.h +++ b/src/app/shared/fd_action.h @@ -5,10 +5,10 @@ #include "../../choreo/tower/fd_tower_file.h" /* FD_APP_NAME and FD_BINARY_NAME identify the control binary at run time - (e.g. "Firedancer"/"firedancer"). They are - defined once per binary in its main translation unit. Commands - shared across binaries should reference these instead of hard-coding - a binary name in help text or usage messages. */ + (e.g. "Firedancer"/"firedancer"). They are defined once per binary + in its main translation unit. Commands shared across binaries should + reference these instead of hard-coding a binary name in help text or + usage messages. */ extern char const * FD_APP_NAME; extern char const * FD_BINARY_NAME; @@ -109,18 +109,6 @@ union fdctl_args { uint freq; } flame; - struct { - char const * pos_arg; - int help; - - /* options */ - - char manifest_path[256UL]; - char iptable_path[256UL]; - ulong slot; - int sort_by_slot; - } repair; - struct { char affinity[ AFFINITY_SZ ]; uint tpu_ip; @@ -196,11 +184,6 @@ union fdctl_args { int compact_mode; } monitor_gossip; - struct { - char const * pos_arg; - int help; - } tower; - struct { int once; } rotor; diff --git a/src/app/shared/fd_config.h b/src/app/shared/fd_config.h index d863f6372a2..86a4126188f 100644 --- a/src/app/shared/fd_config.h +++ b/src/app/shared/fd_config.h @@ -50,7 +50,6 @@ struct fd_configf { struct { ulong wait_for_supermajority_at_slot; char wait_for_supermajority_with_bank_hash[ FD_BASE58_ENCODED_32_SZ ]; - int alpenglow; } consensus; struct { @@ -379,10 +378,6 @@ struct fd_config { char ssl_key_log_file[ PATH_MAX ]; } quic; - struct { - ushort txsend_src_port; - } txsend; - struct { uint signature_cache_size; uint receive_buffer_size; @@ -464,10 +459,6 @@ struct fd_config { ulong shred_storage_limit_gib; } rserve; - struct { - int write_vote_history_file; - } tower; - struct { ulong max_transaction_lookahead_buffer_size; ulong enable_features_cnt; diff --git a/src/app/shared/fd_config_json.c b/src/app/shared/fd_config_json.c index a1f165b59f7..7f8d2e45302 100644 --- a/src/app/shared/fd_config_json.c +++ b/src/app/shared/fd_config_json.c @@ -12,7 +12,7 @@ or knowingly skipped) before the constant is bumped. String keys of the user's own file are separately forced through the classification lists below. */ -FD_STATIC_ASSERT( sizeof(fd_config_t)==26553488UL, update_fd_config_to_json_for_the_layout_change ); +FD_STATIC_ASSERT( sizeof(fd_config_t)==26553472UL, update_fd_config_to_json_for_the_layout_change ); #define REDACTED "[redacted]" @@ -347,7 +347,6 @@ fd_config_to_json( fd_config_t const * config, jw_boolau( &w, "wait_for_vote_to_start_leader", config->consensus.wait_for_vote_to_start_leader ); jw_ulong ( &w, "wait_for_supermajority_at_slot", f->consensus.wait_for_supermajority_at_slot ); jw_str ( &w, "wait_for_supermajority_with_bank_hash", f->consensus.wait_for_supermajority_with_bank_hash ); - jw_bool ( &w, "alpenglow", f->consensus.alpenglow ); jw_obj_close( &w ); jw_obj_open( &w, "gossip" ); @@ -549,9 +548,6 @@ fd_config_to_json( fd_config_t const * config, jw_bool ( &w, "retry", config->tiles.quic.retry ); jw_path ( &w, "ssl_key_log_file", config->tiles.quic.ssl_key_log_file ); jw_obj_close( &w ); - jw_obj_open( &w, "txsend" ); - jw_ulong( &w, "txsend_src_port", config->tiles.txsend.txsend_src_port ); - jw_obj_close( &w ); jw_obj_open( &w, "verify" ); jw_ulong( &w, "signature_cache_size", config->tiles.verify.signature_cache_size ); jw_ulong( &w, "receive_buffer_size", config->tiles.verify.receive_buffer_size ); @@ -618,9 +614,6 @@ fd_config_to_json( fd_config_t const * config, jw_ulong( &w, "repair_serve_listen_port", config->tiles.rserve.repair_serve_listen_port ); jw_ulong( &w, "shred_storage_limit_gib", config->tiles.rserve.shred_storage_limit_gib ); jw_obj_close( &w ); - jw_obj_open( &w, "tower" ); - jw_bool ( &w, "write_vote_history_file", config->tiles.tower.write_vote_history_file ); - jw_obj_close( &w ); jw_obj_open( &w, "replay" ); jw_ulong( &w, "max_transaction_lookahead_buffer_size", config->tiles.replay.max_transaction_lookahead_buffer_size ); jw_str_arr( &w, "enable_features", config->tiles.replay.enable_features[ 0 ], sizeof(config->tiles.replay.enable_features[ 0 ]), config->tiles.replay.enable_features_cnt ); diff --git a/src/app/shared/fd_config_parse.c b/src/app/shared/fd_config_parse.c index 23b384305eb..ad342d3494a 100644 --- a/src/app/shared/fd_config_parse.c +++ b/src/app/shared/fd_config_parse.c @@ -51,7 +51,6 @@ fd_config_extract_podf( uchar * pod, CFG_POP ( ulong, consensus.wait_for_supermajority_at_slot ); CFG_POP ( cstr, consensus.wait_for_supermajority_with_bank_hash ); - CFG_POP ( bool, consensus.alpenglow ); CFG_POP ( uint, snapshots.sources.max_local_full_effective_age ); CFG_POP ( uint, snapshots.sources.max_local_incremental_age ); @@ -236,8 +235,6 @@ fd_config_extract_pod( uchar * pod, CFG_POP ( ushort, tiles.rserve.repair_serve_listen_port ); CFG_POP ( ulong, tiles.rserve.shred_storage_limit_gib ); - CFG_POP ( bool, tiles.tower.write_vote_history_file ); - CFG_POP ( ulong, capture.capture_start_slot ); CFG_POP ( cstr, capture.solcap_capture ); CFG_POP ( bool, capture.recent_only ); @@ -251,8 +248,6 @@ fd_config_extract_pod( uchar * pod, CFG_POP ( bool, capture.dump_txn_as_fixture ); CFG_POP ( bool, capture.dump_block_to_pb ); - CFG_POP ( ushort, tiles.txsend.txsend_src_port ); - CFG_POP ( bool, development.sandbox ); CFG_POP ( bool, development.no_clone ); CFG_POP ( cstr, development.core_dump ); @@ -340,6 +335,9 @@ fd_config_extract_pod( uchar * pod, } while(0) CFG_DEPRECATED( development.bundle.ssl_heap_size_mib ); + CFG_DEPRECATED( tiles.tower.write_vote_history_file ); + CFG_DEPRECATED( tiles.txsend.txsend_src_port ); + CFG_DEPRECATED( consensus.alpenglow ); # undef CFG_DEPRECATED diff --git a/src/app/shared/test_config_parse.c b/src/app/shared/test_config_parse.c index d2b68b30ab4..ccbf91fa08a 100644 --- a/src/app/shared/test_config_parse.c +++ b/src/app/shared/test_config_parse.c @@ -30,9 +30,7 @@ static char const cfg_str_5[] = " max_file_size_mib = 33"; static char const cfg_str_vote_history[] = "[paths]\n" - " vote_history = \"/data/{name}/vote_history\"\n" - "[tiles.tower]\n" - " write_vote_history_file = false"; + " vote_history = \"/data/{name}/vote_history\"\n"; extern uchar const firedancer_default_config[]; extern ulong const firedancer_default_config_sz; @@ -243,12 +241,10 @@ main( int argc, /* Parse the vote history file options */ memset( config, 0, sizeof(config_t) ); - config->tiles.tower.write_vote_history_file = 1; pod = fd_pod_join( fd_pod_new( pod_mem, sizeof(pod_mem) ) ); FD_TEST( fd_toml_parse( cfg_str_vote_history, sizeof(cfg_str_vote_history)-1, pod, scratch, sizeof(scratch), NULL ) == FD_TOML_SUCCESS ); FD_TEST( fd_config_extract_pod( pod, config ) == config ); FD_TEST( !strcmp( config->paths.vote_history, "/data/{name}/vote_history" ) ); - FD_TEST( !config->tiles.tower.write_vote_history_file ); FD_TEST( vote_history_path_is_valid( "" ) ); /* default */ FD_TEST( vote_history_path_is_valid( "/data/vote_history" ) ); diff --git a/src/app/shared_dev/boot/fd_dev_boot.c b/src/app/shared_dev/boot/fd_dev_boot.c index 9dcff41624c..1b4a0cf6d72 100644 --- a/src/app/shared_dev/boot/fd_dev_boot.c +++ b/src/app/shared_dev/boot/fd_dev_boot.c @@ -71,7 +71,6 @@ fd_global_options_help( fd_action_help_t * help ) { fd_action_help_arg( help, "--no-sandbox", NULL, "Disable the security sandbox (development only)" ); fd_action_help_arg( help, "--no-clone", NULL, "Run all tiles in a single process instead of one per tile (development only)" ); fd_action_help_arg( help, "--max-live-slots", "", "Override the [runtime.max_live_slots] configuration (development only)" ); - fd_action_help_arg( help, "--alpenglow", NULL, "Run Alpenglow consensus in lieu of Tower consensus" ); fd_action_help_arg( help, "--efficient", NULL, "Override the layout mode to \"efficient\" so tiles sleep when idle" ); fd_action_help_arg( help, "--version", NULL, "Show the current software version" ); fd_action_help_arg( help, "--help/-h", NULL, "Print this help message" ); @@ -99,7 +98,6 @@ fd_dev_main( int argc, int no_sandbox = fd_env_strip_cmdline_contains( &argc, &argv, "--no-sandbox" ); int no_clone = fd_env_strip_cmdline_contains( &argc, &argv, "--no-clone" ); - int alpenglow = fd_env_strip_cmdline_contains( &argc, &argv, "--alpenglow" ); int efficient = fd_env_strip_cmdline_contains( &argc, &argv, "--efficient" ); ulong max_live_slots = fd_env_strip_cmdline_ulong( &argc, &argv, "--max-live-slots", NULL, 0UL ); @@ -161,7 +159,6 @@ fd_dev_main( int argc, if( FD_LIKELY( load_topo ) ) { if( FD_UNLIKELY( max_live_slots ) ) config.firedancer.runtime.max_live_slots = max_live_slots; - if( FD_UNLIKELY( alpenglow ) ) config.firedancer.consensus.alpenglow = 1; if( FD_UNLIKELY( efficient ) ) fd_cstr_ncpy( config.firedancer.layout.mode, "efficient", sizeof(config.firedancer.layout.mode) ); if( FD_LIKELY( action->topo ) ) action->topo( &config ); else topo_init( &config ); diff --git a/src/app/shared_dev/commands/configure/genesis.c b/src/app/shared_dev/commands/configure/genesis.c index 561888180ca..1ccc973c1f8 100644 --- a/src/app/shared_dev/commands/configure/genesis.c +++ b/src/app/shared_dev/commands/configure/genesis.c @@ -195,8 +195,6 @@ create_genesis( config_t const * config, } /* Firedancer only bootstraps Alpenglow clusters */ - if( FD_UNLIKELY( !config->firedancer.consensus.alpenglow ) ) - FD_LOG_ERR(( "creating a genesis requires [consensus.alpenglow]" )); options->alpenglow = 1; options->hashes_per_tick = 0UL; /* PoH is in low power mode */ @@ -347,9 +345,6 @@ check( config_t const * config, if( FD_UNLIKELY( !fd_genesis_parse( _genesis, disk_bin, (ulong)st.st_size ) ) ) FD_LOG_ERR(( "malformed genesis file at `%s`", genesis_path )); - if( FD_UNLIKELY( !config->firedancer.consensus.alpenglow ) ) - PARTIALLY_CONFIGURED( "`%s` exists but [consensus.alpenglow] is disabled", genesis_path ); - static uchar fresh_bin[ GENESIS_BUF_MAX ]; ulong fresh_bin_sz = create_genesis( config, fresh_bin, sizeof(fresh_bin) ); diff --git a/src/disco/topo/fd_topo.h b/src/disco/topo/fd_topo.h index 8640f8cb5b2..8ab381bf82a 100644 --- a/src/disco/topo/fd_topo.h +++ b/src/disco/topo/fd_topo.h @@ -624,7 +624,6 @@ struct fd_topo_tile { char ledger_path[ PATH_MAX ]; ulong end_slot; ulong root_distance; - int alpenglow; long boot_timestamp_nanos; } backtest; From 06fb7e3f5a95ea8574bc652d455bdc256720b6e6 Mon Sep 17 00:00:00 2001 From: Richard Patel Date: Wed, 14 Oct 2026 22:34:31 +0000 Subject: [PATCH 03/13] admin: remove Tower BFT tile support --- src/app/firedancer/commands/set_identity.c | 36 +-- src/app/shared/fd_action.h | 3 - src/disco/keyguard/fd_sign_tile.c | 10 - src/discof/admin/fd_admin_tile.c | 259 +++++---------------- src/discof/admin/fd_adminctl.h | 9 +- src/discof/admin/test_admin_tile.c | 162 +++---------- 6 files changed, 97 insertions(+), 382 deletions(-) diff --git a/src/app/firedancer/commands/set_identity.c b/src/app/firedancer/commands/set_identity.c index ef23bb1e951..6887dd660fb 100644 --- a/src/app/firedancer/commands/set_identity.c +++ b/src/app/firedancer/commands/set_identity.c @@ -54,10 +54,9 @@ set_identity_cmd_args( int * pargc, } /* Scan a vote history file for the slot to wait to vote past, which - is all the validator needs, or copy in a tower file. */ + is all the validator needs. */ args->set_identity.vote_history_slot = 0UL; - args->set_identity.tower_file_sz = 0UL; if( vote_history_file ) { int fd = open( vote_history_file, O_RDONLY ); if( FD_UNLIKELY( -1==fd ) ) FD_LOG_ERR(( "open(%s) failed (%i-%s)", vote_history_file, errno, fd_io_strerror( errno ) )); @@ -72,19 +71,10 @@ set_identity_cmd_args( int * pargc, if( FD_UNLIKELY( file==MAP_FAILED ) ) FD_LOG_ERR(( "mmap(%s) failed (%i-%s)", vote_history_file, errno, fd_io_strerror( errno ) )); if( FD_UNLIKELY( -1==close( fd ) ) ) FD_LOG_ERR(( "close() failed (%i-%s)", errno, fd_io_strerror( errno ) )); - if( FD_LIKELY( sz>=sizeof(uint) && FD_LOAD( uint, file )==AG_VOTE_HISTORY_SERDE_KIND ) ) { - int err = ag_vote_history_de( file, sz, args->set_identity.keypair+32UL, &args->set_identity.vote_history_slot ); - if( FD_UNLIKELY( err ) ) { - FD_LOG_ERR(( "vote history file %s is invalid (%i) or does not belong to the new identity. If you believe the " - "vote history file is valid, please contact the Firedancer team for additional assistance.", vote_history_file, err )); - } - } else { - if( FD_UNLIKELY( sz>sizeof(args->set_identity.tower_file) ) ) { - FD_LOG_ERR(( "tower file %s exceeds %lu bytes. The firedancer validator will not be able to process this file. " - "Retry without the --vote-history-file argument.", vote_history_file, sizeof(args->set_identity.tower_file) )); - } - fd_memcpy( args->set_identity.tower_file, file, sz ); - args->set_identity.tower_file_sz = sz; + int err = ag_vote_history_de( file, sz, args->set_identity.keypair+32UL, &args->set_identity.vote_history_slot ); + if( FD_UNLIKELY( err ) ) { + FD_LOG_ERR(( "vote history file %s is invalid (%i) or does not belong to the new identity. If you believe the " + "vote history file is valid, please contact the Firedancer team for additional assistance.", vote_history_file, err )); } if( FD_UNLIKELY( munmap( (void *)file, sz ) ) ) FD_LOG_ERR(( "munmap(%s) failed (%i-%s)", vote_history_file, errno, fd_io_strerror( errno ) )); } @@ -124,9 +114,7 @@ set_identity( args_t * args, fd_adminctl_set_identity_t * req = (fd_adminctl_set_identity_t *)payload; req->version = FD_ADMINCTL_SET_IDENTITY_PAYLOAD_VERSION; req->vote_history_slot = args->set_identity.vote_history_slot; - req->tower_file_sz = args->set_identity.tower_file_sz; memcpy( req->keypair, args->set_identity.keypair, 64UL ); - memcpy( req->tower_file, args->set_identity.tower_file, args->set_identity.tower_file_sz ); uchar * keypair_wr = fd_keyload_mprotect_wr( args->set_identity.keypair, 0 ); fd_memzero_explicit( keypair_wr, 64UL ); @@ -171,7 +159,7 @@ set_identity_args_help( fd_action_help_t * help ) { "array from stdin instead of from a file" ); fd_action_help_arg( help, "--name", "", "Name of the validator instance to attach to, if more than one is\n" "running on this host" ); - fd_action_help_arg( help, "--vote-history-file", "", "Path to the vote history or tower file for the new identity" ); + fd_action_help_arg( help, "--vote-history-file", "", "Path to the vote history file for the new identity" ); } action_t fd_action_set_identity = { @@ -188,16 +176,8 @@ action_t fd_action_set_identity = { "identity. On success it prints `Validator identity key switched to `\n" "and exits 0; a rejected request exits non-zero.\n" "\n" - "With --vote-history-file, the saved tower is verified before switching. The\n" - "validator takes its votes on blocks it has replayed, and does not vote until\n" - "its votes on blocks it has not replayed, or on forks outside its rooted\n" - "history, stop locking it out. Agave instead stops if the tower file\n" - "conflicts with the running validator's rooted history. Without a file, or if\n" - "the tower is behind the validator's root, voting history is reconstructed\n" - "from on-chain state.\n" - "\n" - "With Alpenglow, pass the vote history file instead. The validator does not\n" - "vote until the leader window after the highest slot the file voted in.\n" + "With --vote-history-file, the validator does not vote until the leader window\n" + "after the highest slot the file voted in.\n" "\n" "This command does not start a validator; it attaches to one that is already\n" "running. With no arguments it discovers the running validator automatically.\n" diff --git a/src/app/shared/fd_action.h b/src/app/shared/fd_action.h index 78d6e91a5cb..6244fd53168 100644 --- a/src/app/shared/fd_action.h +++ b/src/app/shared/fd_action.h @@ -2,7 +2,6 @@ #define HEADER_fd_src_app_shared_fd_action_h #include "../platform/fd_cap_chk.h" -#include "../../choreo/tower/fd_tower_file.h" /* FD_APP_NAME and FD_BINARY_NAME identify the control binary at run time (e.g. "Firedancer"/"firedancer"). They are defined once per binary @@ -48,8 +47,6 @@ union fdctl_args { uchar const * keypair; char name[ 64UL ]; ulong vote_history_slot; - uchar tower_file[ FD_TOWER_FILE_MAX ]; - ulong tower_file_sz; } set_identity; struct { diff --git a/src/disco/keyguard/fd_sign_tile.c b/src/disco/keyguard/fd_sign_tile.c index 32c8cbdefa5..d8228f5e5c7 100644 --- a/src/disco/keyguard/fd_sign_tile.c +++ b/src/disco/keyguard/fd_sign_tile.c @@ -447,11 +447,6 @@ unprivileged_init_sensitive( fd_topo_t const * topo, FD_TEST( !strcmp( out_link->name, "sign_repair" ) ); FD_TEST( in_link->mtu==124UL ); // FD_REPAIR_MAX_PREIMAGE_SZ FD_TEST( out_link->mtu==64UL ); - } else if ( !strcmp(in_link->name, "txsend_sign" ) ) { - ctx->in[ i ].role = FD_KEYGUARD_ROLE_TXSEND; - FD_TEST( !strcmp( out_link->name, "sign_txsend" ) ); - FD_TEST( in_link->mtu==FD_TXN_MTU_V0 ); - FD_TEST( out_link->mtu==64UL*2UL ); } else if( !strcmp(in_link->name, "bundle_sign" ) ) { ctx->in[ i ].role = FD_KEYGUARD_ROLE_BUNDLE; FD_TEST( !strcmp( out_link->name, "sign_bundle" ) ); @@ -477,11 +472,6 @@ unprivileged_init_sensitive( fd_topo_t const * topo, FD_TEST( !strcmp( out_link->name, "sign_votor" ) ); FD_TEST( in_link->mtu==FD_KEYGUARD_SIGN_REQ_MTU ); FD_TEST( out_link->mtu==FD_BLS_SIG_SZ ); - } else if( !strcmp(in_link->name, "tower_sign" ) ) { - ctx->in[ i ].role = FD_KEYGUARD_ROLE_TOWER; - FD_TEST( !strcmp( out_link->name, "sign_tower" ) ); - FD_TEST( in_link->mtu==4096UL ); - FD_TEST( out_link->mtu==64UL ); } else { FD_LOG_CRIT(( "unexpected link %s", in_link->name )); } diff --git a/src/discof/admin/fd_admin_tile.c b/src/discof/admin/fd_admin_tile.c index 746a8018c69..6fefacc7a53 100644 --- a/src/discof/admin/fd_admin_tile.c +++ b/src/discof/admin/fd_admin_tile.c @@ -11,10 +11,7 @@ struct fd_admin_tile_ctx { fd_topo_t const * topo; fd_adminctl_t * adminctl; uchar identity_pubkey[ 32UL ]; - int alpenglow; - char const * voter_name; /* tile that produces votes: tower, or votor under Alpenglow */ fd_keyswitch_t * voter_av_keyswitch; - fd_keyswitch_t * txsend_av_keyswitch; fd_keyswitch_t * sign_av_keyswitch[ FD_TOPO_MAX_TILES ]; ulong sign_av_keyswitch_cnt; fd_sha512_t sha512[ 1 ]; @@ -120,24 +117,12 @@ unprivileged_init( fd_topo_t const * topo, } } - ulong voter_idx = fd_topo_find_tile( topo, "tower", 0UL ); - ctx->alpenglow = voter_idx==ULONG_MAX; - if( FD_UNLIKELY( ctx->alpenglow ) ) voter_idx = fd_topo_find_tile( topo, "votor", 0UL ); + ulong voter_idx = fd_topo_find_tile( topo, "votor", 0UL ); FD_TEST( voter_idx!=ULONG_MAX ); FD_TEST( topo->tiles[ voter_idx ].av_keyswitch_obj_id!=ULONG_MAX ); - ctx->voter_name = topo->tiles[ voter_idx ].name; ctx->voter_av_keyswitch = fd_keyswitch_join( fd_topo_obj_laddr( topo, topo->tiles[ voter_idx ].av_keyswitch_obj_id ) ); FD_TEST( ctx->voter_av_keyswitch ); - ulong txsend_idx = fd_topo_find_tile( topo, "txsend", 0UL ); - if( FD_LIKELY( txsend_idx!=ULONG_MAX ) ) { - FD_TEST( topo->tiles[ txsend_idx ].av_keyswitch_obj_id!=ULONG_MAX ); - ctx->txsend_av_keyswitch = fd_keyswitch_join( fd_topo_obj_laddr( topo, topo->tiles[ txsend_idx ].av_keyswitch_obj_id ) ); - FD_TEST( ctx->txsend_av_keyswitch ); - } else { - ctx->txsend_av_keyswitch = NULL; - } - for( ulong i=0UL; itile_cnt; i++ ) { fd_topo_tile_t const * sign_tile = &topo->tiles[ i ]; if( FD_LIKELY( strcmp( sign_tile->name, "sign" ) ) ) continue; @@ -161,9 +146,8 @@ unprivileged_init( fd_topo_t const * topo, except direct forward steps, except in emergency recovery cases an operator can force the state past the initial lock. - If Alpenglow is active, Votor takes the place of Tower and Rotor - takes the place of repair. Votor/Tower will be referred to as the - Voter tile for the purposes of the identity switch. + Votor is referred to as the Voter tile and Rotor takes the place of + Repair for the purposes of the identity switch. The states follow, in order. */ @@ -187,8 +171,8 @@ unprivileged_init( fd_topo_t const * topo, In Firedancer, this halt request goes to the Replay tile, which causes the tile to switch the identity key it uses to determine the identity's balance as well as when the validator is the leader. - Replay reports the producer sequence of the link the voter reads - (replay_out for tower, replay_slot for votor) as it switches. */ + Replay reports the producer sequence of replay_slot, the link votor + reads, as it switches. */ #define FD_SET_IDENTITY_STATE_REPLAY_HALT_REQUESTED (2UL) /* State 3: REPLAY_HALTED @@ -211,28 +195,10 @@ unprivileged_init( fd_topo_t const * topo, /* State 5: VOTER_HALTED Voter has drained its pre-switch publish queue, switched identity, - and reported the sequence after the final drained message. If - Alpenglow is active, there is no TxSend tile, so VOTER_HALTED will - move straight to TXSEND_FLUSHED. */ + and reported the sequence after the final drained message. */ #define FD_SET_IDENTITY_STATE_VOTER_HALTED (5UL) -/* State 6: TXSEND_FLUSH_REQUESTED - TxSend has been told the Tower sequence through which it must - process messages before switching identity. Gossip remains active - in this state so it continues returning credits on tower_out and - txsend_out. */ -#define FD_SET_IDENTITY_STATE_TXSEND_FLUSH_REQUESTED (6UL) - -/* State 7: TXSEND_FLUSHED - TxSend has processed all Tower messages through the halt sequence, - switched its identity key, and stopped receiving Net fragments that - could invoke QUIC signing callbacks. TxSend also reported the - sequence after its final txsend_out vote, which carries the old - identity. This state can also be reached right after VOTER_HALTED - only if Alpenglow is active. */ -#define FD_SET_IDENTITY_STATE_TXSEND_FLUSHED (7UL) - -/* State 8: SIGNERS_HALT_REQUESTED +/* State 6: SIGNERS_HALT_REQUESTED Repair, Gossip, Bundle, and Rserve will stop sending requests downstream to the sign tile. This is done to avoid any mismatches with the identity key. Their identity keys will be switched during @@ -249,9 +215,7 @@ unprivileged_init( fd_topo_t const * topo, sign tile before halting any new signing requests. (b) Gossip. The gossip tile sends out ContactInfo messages with our identity key, and also uses the identity key to sign - outgoing gossip messages. Gossip first pushes the TxSend - votes through the sequence TxSend reported, since the sign - tile only signs them under the old key. + outgoing gossip messages. (c) Bundle. The bundle tile uses the identity key to sign an authentication challenge from the bundle server. (d) Rserve. The rserve tile uses the identity key to sign @@ -259,14 +223,14 @@ unprivileged_init( fd_topo_t const * topo, (e) Shred. The shred tile has the sign tile sign FEC sets asynchronously; it waits for its outstanding requests to be answered under the old key before switching. */ -#define FD_SET_IDENTITY_STATE_SIGNERS_HALT_REQUESTED (8UL) +#define FD_SET_IDENTITY_STATE_SIGNERS_HALT_REQUESTED (6UL) -/* State 9: SIGNERS_HALTED +/* State 7: SIGNERS_HALTED Repair, Gossip, Bundle, and Rserve are no longer sending requests - to the sign tile. Tower and TxSend remain halted. */ -#define FD_SET_IDENTITY_STATE_SIGNERS_HALTED (9UL) + to the sign tile. */ +#define FD_SET_IDENTITY_STATE_SIGNERS_HALTED (7UL) -/* State 10: ALL_SWITCH_REQUESTED +/* State 8: ALL_SWITCH_REQUESTED The client now requests that all other tiles which consume the identity key in some way switch to the new key. The leader pipeline is still halted, although it doesn't strictly need to be, @@ -288,33 +252,32 @@ unprivileged_init( fd_topo_t const * topo, outgoing shreds. (e) Event. Outgoing events to the event server are signed with the identity key to authenticate the sender. */ -#define FD_SET_IDENTITY_STATE_ALL_SWITCH_REQUESTED (10UL) +#define FD_SET_IDENTITY_STATE_ALL_SWITCH_REQUESTED (8UL) -/* State 11: ALL_SWITCHED +/* State 9: ALL_SWITCHED All remaining tiles that use the identity key have confirmed that they have switched to the new key. Gossip has not yet updated its - identity key. Repair, Gossip, Tower, TxSend, Bundle, and Rserve - remain halted. */ -#define FD_SET_IDENTITY_STATE_ALL_SWITCHED (11UL) + identity key. Repair, Gossip, Bundle, and Rserve remain halted. */ +#define FD_SET_IDENTITY_STATE_ALL_SWITCHED (9UL) -/* State 12: SIGNERS_UNHALT_REQUESTED +/* State 10: SIGNERS_UNHALT_REQUESTED Now that the sign tile is using the switched identity key, the halted signers can be unhalted. Gossip switches its identity key during this step. These are the same tiles from - SIGNERS_HALT_REQUESTED, along with Tower and TxSend. */ -#define FD_SET_IDENTITY_STATE_SIGNERS_UNHALT_REQUESTED (12UL) + SIGNERS_HALT_REQUESTED. */ +#define FD_SET_IDENTITY_STATE_SIGNERS_UNHALT_REQUESTED (10UL) -/* State 13: SIGNERS_UNHALTED +/* State 11: SIGNERS_UNHALTED All tiles that rely on the sign tile have been unhalted. Replay remains paused. */ -#define FD_SET_IDENTITY_STATE_SIGNERS_UNHALTED (13UL) +#define FD_SET_IDENTITY_STATE_SIGNERS_UNHALTED (11UL) -/* State 14: REPLAY_UNHALT_REQUESTED +/* State 12: REPLAY_UNHALT_REQUESTED The final state, now that all tiles have switched, Replay can be unpaused and the validator can resume processing and producing blocks. The next state once Replay confirms it is unpaused is UNLOCKED. */ -#define FD_SET_IDENTITY_STATE_REPLAY_UNHALT_REQUESTED (14UL) +#define FD_SET_IDENTITY_STATE_REPLAY_UNHALT_REQUESTED (12UL) static fd_keyswitch_t * find_identity_keyswitch( fd_admin_tile_ctx_t * ctx, @@ -329,15 +292,12 @@ find_identity_keyswitch( fd_admin_tile_ctx_t * ctx, return keyswitch; } -FD_STATIC_ASSERT( 40UL+sizeof(fd_tower_file_t)<=sizeof(((fd_keyswitch_t *)NULL)->bytes), tower_file ); - static int FD_FN_SENSITIVE poll_set_identity( fd_admin_tile_ctx_t * ctx, ulong * state, ulong identity_outset, - uchar * keypair, - fd_tower_file_t const * tower_file, - ulong vote_history_slot ) { + uchar * keypair, + ulong vote_history_slot ) { fd_topo_t const * topo = ctx->topo; switch( *state ) { @@ -378,74 +338,38 @@ poll_set_identity( fd_admin_tile_ctx_t * ctx, } case FD_SET_IDENTITY_STATE_REPLAY_HALTED: { fd_keyswitch_t * replay = find_identity_keyswitch( ctx, "replay" ); - fd_keyswitch_t * voter = find_identity_keyswitch( ctx, ctx->voter_name ); + fd_keyswitch_t * voter = find_identity_keyswitch( ctx, "votor" ); voter->param = replay->result; memcpy( voter->bytes, keypair+32UL, 32UL ); - /* Votor takes the slot to wait to vote past, tower its tower file - if one was given. */ - if( FD_UNLIKELY( ctx->alpenglow ) ) FD_STORE( ulong, voter->bytes+32UL, vote_history_slot ); - else FD_STORE( ulong, voter->bytes+32UL, fd_ulong_if( !!tower_file, sizeof(fd_tower_file_t), 0UL ) ); - if( FD_LIKELY( tower_file ) ) memcpy( voter->bytes+40UL, tower_file, sizeof(fd_tower_file_t) ); + /* Votor takes the slot to wait to vote past. */ + FD_STORE( ulong, voter->bytes+32UL, vote_history_slot ); FD_COMPILER_MFENCE(); voter->state = FD_KEYSWITCH_STATE_SWITCH_PENDING; FD_COMPILER_MFENCE(); *state = FD_SET_IDENTITY_STATE_VOTER_HALT_REQUESTED; - FD_LOG_INFO(( "Pausing %s and draining queued messages...", ctx->voter_name )); + FD_LOG_INFO(( "Pausing votor and draining queued messages..." )); break; } case FD_SET_IDENTITY_STATE_VOTER_HALT_REQUESTED: { - fd_keyswitch_t * voter = find_identity_keyswitch( ctx, ctx->voter_name ); + fd_keyswitch_t * voter = find_identity_keyswitch( ctx, "votor" ); if( FD_LIKELY( voter->state==FD_KEYSWITCH_STATE_COMPLETED ) ) { fd_memzero_explicit( voter->bytes, 64UL ); FD_COMPILER_MFENCE(); *state = FD_SET_IDENTITY_STATE_VOTER_HALTED; - FD_LOG_INFO(( "%s successfully paused...", ctx->voter_name )); + FD_LOG_INFO(( "votor successfully paused..." )); } else if( FD_UNLIKELY( voter->state==FD_KEYSWITCH_STATE_SWITCH_PENDING ) ) { FD_SPIN_PAUSE(); } else { - FD_LOG_ERR(( "Unexpected %s keyswitch state %lu", ctx->voter_name, voter->state )); + FD_LOG_ERR(( "Unexpected votor keyswitch state %lu", voter->state )); } break; } case FD_SET_IDENTITY_STATE_VOTER_HALTED: { - if( FD_UNLIKELY( ctx->alpenglow ) ) { - *state = FD_SET_IDENTITY_STATE_TXSEND_FLUSHED; - break; - } - ulong tower_halted_seq = find_identity_keyswitch( ctx, "tower" )->result; - fd_keyswitch_t * txsend = find_identity_keyswitch( ctx, "txsend" ); - txsend->param = tower_halted_seq; - memcpy( txsend->bytes, keypair+32UL, 32UL ); - FD_COMPILER_MFENCE(); - txsend->state = FD_KEYSWITCH_STATE_SWITCH_PENDING; - FD_COMPILER_MFENCE(); - - *state = FD_SET_IDENTITY_STATE_TXSEND_FLUSH_REQUESTED; - FD_LOG_INFO(( "Flushing old identity vote transactions from TxSend..." )); - break; - } - case FD_SET_IDENTITY_STATE_TXSEND_FLUSH_REQUESTED: { - fd_keyswitch_t * txsend = find_identity_keyswitch( ctx, "txsend" ); - if( FD_LIKELY( txsend->state==FD_KEYSWITCH_STATE_COMPLETED ) ) { - fd_memzero_explicit( txsend->bytes, 64UL ); - FD_COMPILER_MFENCE(); - *state = FD_SET_IDENTITY_STATE_TXSEND_FLUSHED; - FD_LOG_INFO(( "TxSend successfully flushed..." )); - } else if( FD_UNLIKELY( txsend->state==FD_KEYSWITCH_STATE_SWITCH_PENDING ) ) { - FD_SPIN_PAUSE(); - } else { - FD_LOG_ERR(( "Unexpected txsend keyswitch state %lu", txsend->state )); - } - break; - } - case FD_SET_IDENTITY_STATE_TXSEND_FLUSHED: { - ulong txsend_flushed_seq = ctx->alpenglow ? 0UL : find_identity_keyswitch( ctx, "txsend" )->result; for( ulong i=0UL; itile_cnt; i++ ) { fd_topo_tile_t const * tile = &topo->tiles[ i ]; if( FD_LIKELY( tile->id_keyswitch_obj_id==ULONG_MAX ) ) continue; - if( strcmp( tile->name, "repair" ) && - strcmp( tile->name, "rotor" ) && + if( strcmp( tile->name, "rotor" ) && strcmp( tile->name, "gossip" ) && strcmp( tile->name, "bundle" ) && strcmp( tile->name, "rserve" ) && @@ -454,8 +378,7 @@ poll_set_identity( fd_admin_tile_ctx_t * ctx, } fd_keyswitch_t * tile_ks = fd_topo_obj_laddr( topo, tile->id_keyswitch_obj_id ); - if( !strcmp( tile->name, "gossip" ) ) tile_ks->param = txsend_flushed_seq; - if( !strcmp( tile->name, "shred" ) ) tile_ks->param = 0UL; /* the leader pipeline is halted, nothing more to reach */ + tile_ks->param = 0UL; memcpy( tile_ks->bytes, keypair+32UL, 32UL ); FD_COMPILER_MFENCE(); tile_ks->state = FD_KEYSWITCH_STATE_SWITCH_PENDING; @@ -470,8 +393,7 @@ poll_set_identity( fd_admin_tile_ctx_t * ctx, for( ulong i=0UL; itile_cnt; i++ ) { fd_topo_tile_t const * tile = &topo->tiles[ i ]; if( FD_LIKELY( tile->id_keyswitch_obj_id==ULONG_MAX ) ) continue; - if( strcmp( tile->name, "repair" ) && - strcmp( tile->name, "rotor" ) && + if( strcmp( tile->name, "rotor" ) && strcmp( tile->name, "gossip" ) && strcmp( tile->name, "bundle" ) && strcmp( tile->name, "rserve" ) && @@ -511,11 +433,8 @@ poll_set_identity( fd_admin_tile_ctx_t * ctx, if( FD_LIKELY( tile->id_keyswitch_obj_id==ULONG_MAX ) ) continue; if( FD_LIKELY( !strcmp( tile->name, "sign" ) || !strcmp( tile->name, "replay" ) || - !strcmp( tile->name, "repair" ) || !strcmp( tile->name, "rotor" ) || !strcmp( tile->name, "gossip" ) || - !strcmp( tile->name, "txsend" ) || - !strcmp( tile->name, "tower" ) || !strcmp( tile->name, "votor" ) || !strcmp( tile->name, "bundle" ) || !strcmp( tile->name, "rserve" ) || @@ -539,11 +458,8 @@ poll_set_identity( fd_admin_tile_ctx_t * ctx, fd_topo_tile_t const * tile = &topo->tiles[ i ]; if( FD_LIKELY( tile->id_keyswitch_obj_id==ULONG_MAX ) ) continue; if( FD_LIKELY( !strcmp( tile->name, "replay" ) || - !strcmp( tile->name, "repair" ) || !strcmp( tile->name, "rotor" ) || !strcmp( tile->name, "gossip" ) || - !strcmp( tile->name, "txsend" ) || - !strcmp( tile->name, "tower" ) || !strcmp( tile->name, "votor" ) || !strcmp( tile->name, "bundle" ) || !strcmp( tile->name, "rserve" ) || @@ -577,12 +493,9 @@ poll_set_identity( fd_admin_tile_ctx_t * ctx, for( ulong i=0UL; itile_cnt; i++ ) { fd_topo_tile_t const * tile = &topo->tiles[ i ]; if( FD_LIKELY( tile->id_keyswitch_obj_id==ULONG_MAX ) ) continue; - if( strcmp( tile->name, "repair" ) && - strcmp( tile->name, "rotor" ) && + if( strcmp( tile->name, "rotor" ) && strcmp( tile->name, "gossip" ) && - strcmp( tile->name, "tower" ) && strcmp( tile->name, "votor" ) && - strcmp( tile->name, "txsend" ) && strcmp( tile->name, "bundle" ) && strcmp( tile->name, "rserve" ) ) { continue; @@ -604,12 +517,9 @@ poll_set_identity( fd_admin_tile_ctx_t * ctx, for( ulong i=0UL; itile_cnt; i++ ) { fd_topo_tile_t const * tile = &topo->tiles[ i ]; if( FD_LIKELY( tile->id_keyswitch_obj_id==ULONG_MAX ) ) continue; - if( strcmp( tile->name, "repair" ) && - strcmp( tile->name, "rotor" ) && + if( strcmp( tile->name, "rotor" ) && strcmp( tile->name, "gossip" ) && - strcmp( tile->name, "tower" ) && strcmp( tile->name, "votor" ) && - strcmp( tile->name, "txsend" ) && strcmp( tile->name, "bundle" ) && strcmp( tile->name, "rserve" ) ) { continue; @@ -690,12 +600,6 @@ set_identity( fd_admin_tile_ctx_t * ctx, } fd_adminctl_set_identity_t * req = fd_type_pun( data ); - if( FD_UNLIKELY( req->tower_file_sz>sizeof(req->tower_file) ) ) { - FD_LOG_WARNING(( "unexpected adminctl set-identity tower_file_sz %lu", req->tower_file_sz )); - report_admin_command( &event, FD_EVENT_ADMIN_COMMAND_RESULT_ABI_SIZE_MISMATCH ); - fd_adminctl_complete( adminctl, slot_idx, FD_ADMINCTL_RESULT_ABI_SIZE_MISMATCH ); - return; - } fd_pubkey_t public_key; fd_ed25519_public_from_private( public_key.uc, req->keypair, ctx->sha512 ); @@ -706,32 +610,17 @@ set_identity( fd_admin_tile_ctx_t * ctx, return; } - if( FD_UNLIKELY( ctx->alpenglow && !memcmp( public_key.uc, ctx->identity_pubkey, 32UL ) ) ) { + if( FD_UNLIKELY( !memcmp( public_key.uc, ctx->identity_pubkey, 32UL ) ) ) { FD_LOG_WARNING(( "set-identity failed: the validator is already running as the identity" )); report_admin_command_custom_result( &event, "same_identity" ); fd_adminctl_complete( adminctl, slot_idx, FD_SET_IDENTITY_RESULT_SAME_IDENTITY ); return; } - fd_tower_file_t tower_file_[1]; - fd_tower_file_t const * tower_file = NULL; - int err = 0; - if( FD_UNLIKELY( ctx->alpenglow ? !!req->tower_file_sz : !!req->vote_history_slot ) ) err = -1; /* a tower file for votor, or a vote history file for tower */ - if( FD_LIKELY( !err && !ctx->alpenglow && req->tower_file_sz ) ) { - err = fd_tower_file_de( req->tower_file, req->tower_file_sz, &public_key, tower_file_ ); - tower_file = tower_file_; - } - if( FD_UNLIKELY( err ) ) { - FD_LOG_WARNING(( "set-identity failed: invalid vote history file (%i)", err )); - report_admin_command_custom_result( &event, "invalid_vote_history" ); - fd_adminctl_complete( adminctl, slot_idx, FD_SET_IDENTITY_RESULT_INVALID_VOTE_HISTORY ); - return; - } - ulong state = FD_SET_IDENTITY_STATE_UNLOCKED; ulong identity_outset = (ulong)fd_log_wallclock(); for(;;) { - if( FD_UNLIKELY( poll_set_identity( ctx, &state, identity_outset, req->keypair, tower_file, req->vote_history_slot ) ) ) break; + if( FD_UNLIKELY( poll_set_identity( ctx, &state, identity_outset, req->keypair, req->vote_history_slot ) ) ) break; } memcpy( ctx->identity_pubkey, req->keypair+32UL, 32UL ); @@ -898,7 +787,7 @@ poll_add_authorized_voter( fd_admin_tile_ctx_t * ctx, voter->state = FD_KEYSWITCH_STATE_SWITCH_PENDING; FD_COMPILER_MFENCE(); *state = FD_ADD_AUTH_VOTER_STATE_VOTER_TILE_REQUESTED; - FD_LOG_INFO(( "Requesting %s tile to update authorized voter key set...", ctx->voter_name )); + FD_LOG_INFO(( "Requesting votor tile to update authorized voter key set..." )); break; } case FD_ADD_AUTH_VOTER_STATE_VOTER_TILE_REQUESTED: { @@ -911,7 +800,7 @@ poll_add_authorized_voter( fd_admin_tile_ctx_t * ctx, validator. */ if( FD_LIKELY( voter->state==FD_KEYSWITCH_STATE_COMPLETED ) ) { *state = FD_ADD_AUTH_VOTER_STATE_VOTER_TILE_UPDATED; - FD_LOG_INFO(( "%s tile key set successfully updated...", ctx->voter_name )); + FD_LOG_INFO(( "votor tile key set successfully updated..." )); } else { FD_SPIN_PAUSE(); } @@ -1105,21 +994,13 @@ snapshot_create_response( fd_admin_tile_ctx_t * ctx, authorized voter index before the sign tile drops the corresponding key. - Clearing the tower map prevents new vote transactions from - referencing a removed voter, but transactions already published to - TxSend may still do so. The tower therefore reports its final output - sequence after draining its local publish queue. TxSend processes - every tower message through that sequence and synchronously waits for - each signing response before acknowledging the drain. Only then is - it safe to clear the sign tiles. All transitions are linear and in - forward order. + All transitions are linear and in forward order. Unlike add-authorized-voter, removal cannot fail on the tile side: it is unconditional and idempotent (clearing an empty set succeeds). - Under Alpenglow the votor's votes are not sent through TxSend, and it - signs synchronously, so it has no signing requests in flight once it - confirms the clear. The TxSend drain is skipped. */ + The votor signs synchronously, so it has no signing requests in + flight once it confirms the clear. */ /* State 0: UNLOCKED The validator is not currently in the process of switching keys. */ @@ -1144,29 +1025,19 @@ snapshot_create_response( fd_admin_tile_ctx_t * ctx, identity key. */ #define FD_REMOVE_ALL_AUTH_VOTERS_STATE_VOTER_TILE_CLEARED (3UL) -/* State 4: TXSEND_FLUSH_REQUESTED - TxSend has been notified to process every tower message through the - sequence at which the tower stopped producing votes. */ -#define FD_REMOVE_ALL_AUTH_VOTERS_STATE_TXSEND_FLUSH_REQUESTED (4UL) - -/* State 5: TXSEND_FLUSHED - TxSend confirmed that all vote transactions which could reference an - authorized voter have finished signing. */ -#define FD_REMOVE_ALL_AUTH_VOTERS_STATE_TXSEND_FLUSHED (5UL) - -/* State 6: SIGN_TILE_REQUESTED +/* State 4: SIGN_TILE_REQUESTED All sign tiles have been notified to clear their authorized voter keys. */ -#define FD_REMOVE_ALL_AUTH_VOTERS_STATE_SIGN_TILE_REQUESTED (6UL) +#define FD_REMOVE_ALL_AUTH_VOTERS_STATE_SIGN_TILE_REQUESTED (4UL) -/* State 7: SIGN_TILE_CLEARED +/* State 5: SIGN_TILE_CLEARED All sign tiles confirmed they cleared (and securely zeroed) their authorized voter keys. */ -#define FD_REMOVE_ALL_AUTH_VOTERS_STATE_SIGN_TILE_CLEARED (7UL) +#define FD_REMOVE_ALL_AUTH_VOTERS_STATE_SIGN_TILE_CLEARED (5UL) -/* State 8: UNLOCK_REQUESTED +/* State 6: UNLOCK_REQUESTED The client requests that the voter tile release the lock. */ -#define FD_REMOVE_ALL_AUTH_VOTERS_STATE_UNLOCK_REQUESTED (8UL) +#define FD_REMOVE_ALL_AUTH_VOTERS_STATE_UNLOCK_REQUESTED (6UL) static void poll_remove_all_authorized_voters( fd_admin_tile_ctx_t * ctx, @@ -1192,43 +1063,19 @@ poll_remove_all_authorized_voters( fd_admin_tile_ctx_t * ctx, voter->state = FD_KEYSWITCH_STATE_SWITCH_PENDING; FD_COMPILER_MFENCE(); *state = FD_REMOVE_ALL_AUTH_VOTERS_STATE_VOTER_TILE_REQUESTED; - FD_LOG_INFO(( "Requesting %s tile to clear authorized voter key set...", ctx->voter_name )); + FD_LOG_INFO(( "Requesting votor tile to clear authorized voter key set..." )); break; } case FD_REMOVE_ALL_AUTH_VOTERS_STATE_VOTER_TILE_REQUESTED: { if( FD_LIKELY( voter->state==FD_KEYSWITCH_STATE_COMPLETED ) ) { *state = FD_REMOVE_ALL_AUTH_VOTERS_STATE_VOTER_TILE_CLEARED; - FD_LOG_INFO(( "%s tile authorized voter key set cleared...", ctx->voter_name )); + FD_LOG_INFO(( "votor tile authorized voter key set cleared..." )); } else { FD_SPIN_PAUSE(); } break; } case FD_REMOVE_ALL_AUTH_VOTERS_STATE_VOTER_TILE_CLEARED: { - if( FD_UNLIKELY( ctx->alpenglow ) ) { - *state = FD_REMOVE_ALL_AUTH_VOTERS_STATE_TXSEND_FLUSHED; - break; - } - fd_keyswitch_t * txsend = ctx->txsend_av_keyswitch; - FD_COMPILER_MFENCE(); - txsend->param = voter->result; - FD_COMPILER_MFENCE(); - txsend->state = FD_KEYSWITCH_STATE_SWITCH_PENDING; - FD_COMPILER_MFENCE(); - *state = FD_REMOVE_ALL_AUTH_VOTERS_STATE_TXSEND_FLUSH_REQUESTED; - FD_LOG_INFO(( "Requesting TxSend drain in-flight authorized voter signing requests..." )); - break; - } - case FD_REMOVE_ALL_AUTH_VOTERS_STATE_TXSEND_FLUSH_REQUESTED: { - if( FD_LIKELY( ctx->txsend_av_keyswitch->state==FD_KEYSWITCH_STATE_COMPLETED ) ) { - *state = FD_REMOVE_ALL_AUTH_VOTERS_STATE_TXSEND_FLUSHED; - FD_LOG_INFO(( "TxSend authorized voter signing requests drained..." )); - } else { - FD_SPIN_PAUSE(); - } - break; - } - case FD_REMOVE_ALL_AUTH_VOTERS_STATE_TXSEND_FLUSHED: { for( ulong i=0UL; isign_av_keyswitch_cnt; i++ ) { fd_keyswitch_t * sign = ctx->sign_av_keyswitch[ i ]; sign->param = FD_KEYSWITCH_PARAM_AV_CLEAR; diff --git a/src/discof/admin/fd_adminctl.h b/src/discof/admin/fd_adminctl.h index 55f17296924..f57c3e24ccc 100644 --- a/src/discof/admin/fd_adminctl.h +++ b/src/discof/admin/fd_adminctl.h @@ -2,7 +2,6 @@ #define HEADER_fd_src_discof_admin_fd_adminctl_h #include "../../util/fd_util_base.h" -#include "../../choreo/tower/fd_tower_file.h" /* fd_adminctl_t provides APIs for out-of-band command-and-control signals to the firedancer process via the admin tile. It provides a @@ -81,16 +80,14 @@ struct fd_adminctl_snap_create_v1 { typedef struct fd_adminctl_snap_create_v1 fd_adminctl_snap_create_t; #define FD_ADMINCTL_SNAP_CREATE_PAYLOAD_VERSION (1UL) -struct fd_adminctl_set_identity_v3 { +struct fd_adminctl_set_identity_v4 { ulong version; /* ==FD_ADMINCTL_SET_IDENTITY_PAYLOAD_VERSION */ uchar keypair[ 64UL ]; ulong vote_history_slot; /* scanned by the command from the vote history file, 0 if none was provided */ - ulong tower_file_sz; /* 0 if no tower file was provided */ - uchar tower_file[ FD_TOWER_FILE_MAX ]; }; -typedef struct fd_adminctl_set_identity_v3 fd_adminctl_set_identity_t; +typedef struct fd_adminctl_set_identity_v4 fd_adminctl_set_identity_t; FD_STATIC_ASSERT( sizeof(fd_adminctl_set_identity_t)<=FD_ADMINCTL_PAYLOAD_MAX, set_identity ); -#define FD_ADMINCTL_SET_IDENTITY_PAYLOAD_VERSION (3UL) +#define FD_ADMINCTL_SET_IDENTITY_PAYLOAD_VERSION (4UL) struct fd_adminctl_get_identity_req_v1 { ulong version; /* ==FD_ADMINCTL_GET_IDENTITY_PAYLOAD_VERSION */ diff --git a/src/discof/admin/test_admin_tile.c b/src/discof/admin/test_admin_tile.c index d1bf2b32291..f1a59788c37 100644 --- a/src/discof/admin/test_admin_tile.c +++ b/src/discof/admin/test_admin_tile.c @@ -5,17 +5,13 @@ static fd_admin_tile_ctx_t ctx; static fd_keyswitch_t voter [1]; -static fd_keyswitch_t txsend[1]; static fd_keyswitch_t sign [ TEST_SIGN_CNT ]; static void -setup( int alpenglow ) { +setup( void ) { memset( &ctx, 0, sizeof(ctx) ); - ctx.alpenglow = alpenglow; - ctx.voter_name = alpenglow ? "votor" : "tower"; ctx.voter_av_keyswitch = fd_keyswitch_join( fd_keyswitch_new( voter, FD_KEYSWITCH_STATE_UNLOCKED ) ); - ctx.txsend_av_keyswitch = fd_keyswitch_join( fd_keyswitch_new( txsend, FD_KEYSWITCH_STATE_UNLOCKED ) ); - FD_TEST( ctx.voter_av_keyswitch && ctx.txsend_av_keyswitch ); + FD_TEST( ctx.voter_av_keyswitch ); for( ulong i=0UL; istate==FD_KEYSWITCH_STATE_UNLOCKED ); } -/* The vote producing tile drops its authorized voters before the sign - tiles do. Only the tower's votes pass through TxSend, so only the - tower path drains it. */ +/* Votor drops its authorized voters before the sign tiles do. */ static void -test_remove_all_authorized_voters( int alpenglow ) { - setup( alpenglow ); +test_remove_all_authorized_voters( void ) { + setup(); ulong state = FD_REMOVE_ALL_AUTH_VOTERS_STATE_UNLOCKED; poll_remove_all_authorized_voters( &ctx, &state ); @@ -97,18 +89,6 @@ test_remove_all_authorized_voters( int alpenglow ) { fd_keyswitch_state( voter, FD_KEYSWITCH_STATE_COMPLETED ); poll_remove_all_authorized_voters( &ctx, &state ); FD_TEST( state==FD_REMOVE_ALL_AUTH_VOTERS_STATE_VOTER_TILE_CLEARED ); - - poll_remove_all_authorized_voters( &ctx, &state ); - if( alpenglow ) { - FD_TEST( state==FD_REMOVE_ALL_AUTH_VOTERS_STATE_TXSEND_FLUSHED ); - FD_TEST( txsend->state==FD_KEYSWITCH_STATE_UNLOCKED ); - } else { - FD_TEST( state==FD_REMOVE_ALL_AUTH_VOTERS_STATE_TXSEND_FLUSH_REQUESTED ); - FD_TEST( txsend->state==FD_KEYSWITCH_STATE_SWITCH_PENDING && txsend->param==42UL ); - fd_keyswitch_state( txsend, FD_KEYSWITCH_STATE_COMPLETED ); - poll_remove_all_authorized_voters( &ctx, &state ); - FD_TEST( state==FD_REMOVE_ALL_AUTH_VOTERS_STATE_TXSEND_FLUSHED ); - } sign_expect( FD_KEYSWITCH_STATE_UNLOCKED, 0UL ); poll_remove_all_authorized_voters( &ctx, &state ); @@ -137,166 +117,90 @@ mock_tile( char const * name ) { return ks; } -/* Votor and rotor stand in for tower and repair under Alpenglow: they - stop signing before the sign tiles switch keys, and resume after. - Alpenglow has no TxSend tile to drain. */ +/* Votor and rotor stop signing before the sign tiles switch keys, and + resume after. */ static void -test_set_identity( int alpenglow ) { - setup( alpenglow ); +test_set_identity( void ) { + setup(); FD_TEST( fd_topob_new( test_topo, "admin-test" ) ); fd_topob_wksp( test_topo, "wksp" )->wksp = NULL; fd_keyswitch_t * replay_ks = mock_tile( "replay" ); - fd_keyswitch_t * repair_ks = mock_tile( alpenglow ? "rotor" : "repair" ); - fd_keyswitch_t * voter_ks = mock_tile( ctx.voter_name ); - fd_keyswitch_t * txsend_ks = alpenglow ? NULL : mock_tile( "txsend" ); + fd_keyswitch_t * repair_ks = mock_tile( "rotor" ); + fd_keyswitch_t * voter_ks = mock_tile( "votor" ); fd_keyswitch_t * gossip_ks = mock_tile( "gossip" ); fd_keyswitch_t * sign_ks = mock_tile( "sign" ); fd_keyswitch_t * gui_ks = mock_tile( "gui" ); ctx.topo = test_topo; uchar keypair[ 64 ]; memset( keypair, 0x44, sizeof(keypair) ); - static fd_tower_file_t tower_file_[1]; - memset( tower_file_, 0x55, sizeof(fd_tower_file_t) ); - fd_tower_file_t const * tower_file = alpenglow ? NULL : tower_file_; - ulong vote_history_slot = alpenglow ? 8UL : 0UL; + ulong vote_history_slot = 8UL; ulong state = FD_SET_IDENTITY_STATE_UNLOCKED; - poll_set_identity( &ctx, &state, 0UL, keypair, tower_file, vote_history_slot ); + poll_set_identity( &ctx, &state, 0UL, keypair, vote_history_slot ); FD_TEST( state==FD_SET_IDENTITY_STATE_LOCKED && replay_ks->state==FD_KEYSWITCH_STATE_LOCKED ); - poll_set_identity( &ctx, &state, 0UL, keypair, tower_file, vote_history_slot ); + poll_set_identity( &ctx, &state, 0UL, keypair, vote_history_slot ); FD_TEST( state==FD_SET_IDENTITY_STATE_REPLAY_HALT_REQUESTED && replay_ks->state==FD_KEYSWITCH_STATE_SWITCH_PENDING ); replay_ks->result = 40UL; fd_keyswitch_state( replay_ks, FD_KEYSWITCH_STATE_COMPLETED ); - poll_set_identity( &ctx, &state, 0UL, keypair, tower_file, vote_history_slot ); + poll_set_identity( &ctx, &state, 0UL, keypair, vote_history_slot ); FD_TEST( state==FD_SET_IDENTITY_STATE_REPLAY_HALTED ); - poll_set_identity( &ctx, &state, 0UL, keypair, tower_file, vote_history_slot ); + poll_set_identity( &ctx, &state, 0UL, keypair, vote_history_slot ); FD_TEST( state==FD_SET_IDENTITY_STATE_VOTER_HALT_REQUESTED ); FD_TEST( voter_ks->state==FD_KEYSWITCH_STATE_SWITCH_PENDING && voter_ks->param==40UL ); - if( alpenglow ) FD_TEST( FD_LOAD( ulong, voter_ks->bytes+32UL )==8UL ); - if( !alpenglow ) FD_TEST( FD_LOAD( ulong, voter_ks->bytes+32UL )==sizeof(fd_tower_file_t) && !memcmp( voter_ks->bytes+40UL, tower_file_, sizeof(fd_tower_file_t) ) ); + FD_TEST( FD_LOAD( ulong, voter_ks->bytes+32UL )==8UL ); voter_ks->result = 42UL; fd_keyswitch_state( voter_ks, FD_KEYSWITCH_STATE_COMPLETED ); - poll_set_identity( &ctx, &state, 0UL, keypair, tower_file, vote_history_slot ); + poll_set_identity( &ctx, &state, 0UL, keypair, vote_history_slot ); FD_TEST( state==FD_SET_IDENTITY_STATE_VOTER_HALTED ); - poll_set_identity( &ctx, &state, 0UL, keypair, tower_file, vote_history_slot ); - if( !alpenglow ) { - FD_TEST( state==FD_SET_IDENTITY_STATE_TXSEND_FLUSH_REQUESTED ); - FD_TEST( txsend_ks->state==FD_KEYSWITCH_STATE_SWITCH_PENDING && txsend_ks->param==42UL ); - txsend_ks->result = 7UL; - fd_keyswitch_state( txsend_ks, FD_KEYSWITCH_STATE_COMPLETED ); - poll_set_identity( &ctx, &state, 0UL, keypair, tower_file, vote_history_slot ); - } - FD_TEST( state==FD_SET_IDENTITY_STATE_TXSEND_FLUSHED ); - - /* Gossip must push the old identity votes TxSend published before it - halts. */ - poll_set_identity( &ctx, &state, 0UL, keypair, tower_file, vote_history_slot ); + poll_set_identity( &ctx, &state, 0UL, keypair, vote_history_slot ); FD_TEST( state==FD_SET_IDENTITY_STATE_SIGNERS_HALT_REQUESTED ); FD_TEST( repair_ks->state==FD_KEYSWITCH_STATE_SWITCH_PENDING && voter_ks->state==FD_KEYSWITCH_STATE_COMPLETED ); - FD_TEST( gossip_ks->state==FD_KEYSWITCH_STATE_SWITCH_PENDING && gossip_ks->param==( alpenglow ? 0UL : 7UL ) ); + FD_TEST( gossip_ks->state==FD_KEYSWITCH_STATE_SWITCH_PENDING && gossip_ks->param==0UL ); FD_TEST( sign_ks->state==FD_KEYSWITCH_STATE_UNLOCKED && gui_ks->state==FD_KEYSWITCH_STATE_UNLOCKED ); fd_keyswitch_state( repair_ks, FD_KEYSWITCH_STATE_COMPLETED ); fd_keyswitch_state( gossip_ks, FD_KEYSWITCH_STATE_COMPLETED ); - poll_set_identity( &ctx, &state, 0UL, keypair, tower_file, vote_history_slot ); + poll_set_identity( &ctx, &state, 0UL, keypair, vote_history_slot ); FD_TEST( state==FD_SET_IDENTITY_STATE_SIGNERS_HALTED ); - poll_set_identity( &ctx, &state, 0UL, keypair, tower_file, vote_history_slot ); + poll_set_identity( &ctx, &state, 0UL, keypair, vote_history_slot ); FD_TEST( state==FD_SET_IDENTITY_STATE_ALL_SWITCH_REQUESTED ); FD_TEST( sign_ks->state==FD_KEYSWITCH_STATE_SWITCH_PENDING && gui_ks->state==FD_KEYSWITCH_STATE_SWITCH_PENDING ); FD_TEST( repair_ks->state==FD_KEYSWITCH_STATE_COMPLETED && voter_ks->state==FD_KEYSWITCH_STATE_COMPLETED ); fd_keyswitch_state( sign_ks, FD_KEYSWITCH_STATE_COMPLETED ); fd_keyswitch_state( gui_ks, FD_KEYSWITCH_STATE_COMPLETED ); - poll_set_identity( &ctx, &state, 0UL, keypair, tower_file, vote_history_slot ); + poll_set_identity( &ctx, &state, 0UL, keypair, vote_history_slot ); FD_TEST( state==FD_SET_IDENTITY_STATE_ALL_SWITCHED ); - poll_set_identity( &ctx, &state, 0UL, keypair, tower_file, vote_history_slot ); + poll_set_identity( &ctx, &state, 0UL, keypair, vote_history_slot ); FD_TEST( state==FD_SET_IDENTITY_STATE_SIGNERS_UNHALT_REQUESTED ); FD_TEST( repair_ks->state==FD_KEYSWITCH_STATE_UNHALT_PENDING && voter_ks->state==FD_KEYSWITCH_STATE_UNHALT_PENDING ); FD_TEST( gossip_ks->state==FD_KEYSWITCH_STATE_UNHALT_PENDING && gossip_ks->param==0UL /* identity_outset */ ); FD_TEST( gui_ks->state==FD_KEYSWITCH_STATE_COMPLETED ); - if( !alpenglow ) FD_TEST( txsend_ks->state==FD_KEYSWITCH_STATE_UNHALT_PENDING ); fd_keyswitch_state( repair_ks, FD_KEYSWITCH_STATE_COMPLETED ); fd_keyswitch_state( voter_ks, FD_KEYSWITCH_STATE_COMPLETED ); fd_keyswitch_state( gossip_ks, FD_KEYSWITCH_STATE_COMPLETED ); - if( !alpenglow ) fd_keyswitch_state( txsend_ks, FD_KEYSWITCH_STATE_COMPLETED ); - poll_set_identity( &ctx, &state, 0UL, keypair, tower_file, vote_history_slot ); + poll_set_identity( &ctx, &state, 0UL, keypair, vote_history_slot ); FD_TEST( state==FD_SET_IDENTITY_STATE_SIGNERS_UNHALTED ); - poll_set_identity( &ctx, &state, 0UL, keypair, tower_file, vote_history_slot ); + poll_set_identity( &ctx, &state, 0UL, keypair, vote_history_slot ); FD_TEST( state==FD_SET_IDENTITY_STATE_REPLAY_UNHALT_REQUESTED && replay_ks->state==FD_KEYSWITCH_STATE_UNHALT_PENDING ); fd_keyswitch_state( replay_ks, FD_KEYSWITCH_STATE_COMPLETED ); - FD_TEST( poll_set_identity( &ctx, &state, 0UL, keypair, tower_file, vote_history_slot ) ); + FD_TEST( poll_set_identity( &ctx, &state, 0UL, keypair, vote_history_slot ) ); FD_TEST( state==FD_SET_IDENTITY_STATE_UNLOCKED && replay_ks->state==FD_KEYSWITCH_STATE_UNLOCKED ); } -/* The admin tile decodes the tower file, or takes the slot the command - scanned from an Alpenglow vote history file, and rejects an invalid - tower file, or a file for the other voter, before switching. */ - -static uchar adminctl_mem[ 1UL<<18 ] __attribute__((aligned(FD_ADMINCTL_ALIGN))); - -static void -test_set_identity_invalid_vote_history( int alpenglow ) { - setup( alpenglow ); - FD_TEST( fd_sha512_join( fd_sha512_new( ctx.sha512 ) ) ); - FD_TEST( fd_adminctl_footprint()<=sizeof(adminctl_mem) ); - ctx.adminctl = fd_adminctl_join( fd_adminctl_new( adminctl_mem ) ); - FD_TEST( ctx.adminctl ); - - void * payload; - ulong payload_max; - ulong slot_idx = fd_adminctl_reserve( ctx.adminctl, &payload, &payload_max ); - FD_TEST( slot_idx!=ULONG_MAX && payload_max>=sizeof(fd_adminctl_set_identity_t) ); - fd_adminctl_set_identity_t * req = payload; - req->version = FD_ADMINCTL_SET_IDENTITY_PAYLOAD_VERSION; - req->tower_file_sz = 1UL; - memset( req->keypair, 0x44, 32UL ); - fd_ed25519_public_from_private( req->keypair+32UL, req->keypair, ctx.sha512 ); - fd_adminctl_publish( ctx.adminctl, slot_idx, FD_ADMINCTL_CMD_SET_IDENTITY, sizeof(fd_adminctl_set_identity_t) ); - - ulong poll_idx; - void * data; - ulong data_sz; - FD_TEST( fd_adminctl_poll( ctx.adminctl, &poll_idx, &data, &data_sz )==FD_ADMINCTL_CMD_SET_IDENTITY ); - set_identity( &ctx, poll_idx, data, data_sz ); - FD_TEST( fd_adminctl_wait( ctx.adminctl, slot_idx )==FD_SET_IDENTITY_RESULT_INVALID_VOTE_HISTORY ); - if( alpenglow ) return; - - /* tower rejects a slot scanned from a vote history file */ - - slot_idx = fd_adminctl_reserve( ctx.adminctl, &payload, &payload_max ); - FD_TEST( slot_idx!=ULONG_MAX ); - req = payload; - req->version = FD_ADMINCTL_SET_IDENTITY_PAYLOAD_VERSION; - req->vote_history_slot = 8UL; - req->tower_file_sz = 0UL; - memset( req->keypair, 0x44, 32UL ); - fd_ed25519_public_from_private( req->keypair+32UL, req->keypair, ctx.sha512 ); - fd_adminctl_publish( ctx.adminctl, slot_idx, FD_ADMINCTL_CMD_SET_IDENTITY, sizeof(fd_adminctl_set_identity_t) ); - ulong cmd = FD_ADMINCTL_CMD_IDLE; - for( ulong i=0UL; i Date: Wed, 21 Oct 2026 13:34:00 +0000 Subject: [PATCH 04/13] replay: remove Tower BFT support --- book/api/metrics-generated.md | 2 - src/disco/events/schema/events.proto | 2 +- src/disco/events/schema/metrics_replay.json | 6 +- src/disco/metrics/generated/fd_metrics_all.c | 2 - .../metrics/generated/fd_metrics_enums.h | 4 +- .../metrics/generated/fd_metrics_replay.h | 10 +- .../metrics/generated/fd_metrics_sample_gen.c | 6 +- src/disco/metrics/metrics.xml | 4 +- src/discof/replay/fd_replay_tile.c | 1496 +--------- src/discof/replay/fd_replay_tile_private.h | 56 +- src/discof/replay/test_replay_tile.c | 2444 ++--------------- 11 files changed, 346 insertions(+), 3686 deletions(-) diff --git a/book/api/metrics-generated.md b/book/api/metrics-generated.md index 4388e9b8d76..eefe3e14dda 100644 --- a/book/api/metrics-generated.md +++ b/book/api/metrics-generated.md @@ -1097,7 +1097,6 @@ | replay_​reset_​slot | gauge | Slot replay last reset to (Tower reset slot; under Alpenglow the newest replayed slot or parent-ready parent); can decrease | | replay_​bank_​capacity | gauge | Maximum number of banks we can have alive | | replay_​bank_​used | gauge | Banks we currently have alive | -| replay_​reassembly_​free | gauge | Free FEC sets in the reassembly queue, 0 under Alpenglow (no reassembly queue) | | replay_​last_​reassembly_​dequeued_​slot | gauge | Slot of the FEC set most recently dequeued from reassembly for replay, 0 if none | | replay_​last_​reassembly_​dequeued_​fec_​index | gauge | FEC set index of the FEC set most recently dequeued from reassembly for replay; read with LastReassemblyDequeuedSlot | | replay_​sched_​active_​bank_​index | gauge | Bank pool index the replay scheduler is dispatching work for, ULONG_MAX if none | @@ -1154,7 +1153,6 @@ | replay_​bank_​frozen | counter | Banks frozen, including our own leader blocks | | replay_​chain_​txn_​height | gauge | Transactions from genesis through the last replayed block, 0 if none; drops on a fork switch | | replay_​fec_​poll_​skipped
{fec_​poll_​skip_​reason="sched_​full"} | counter | Replay loop iterations that processed no FEC set, by reason (The scheduler was full) | -| replay_​fec_​poll_​skipped
{fec_​poll_​skip_​reason="reassembly_​empty"} | counter | Replay loop iterations that processed no FEC set, by reason (The reassembly queue was empty) | | replay_​fec_​poll_​skipped
{fec_​poll_​skip_​reason="leader_​freeze_​wait"} | counter | Replay loop iterations that processed no FEC set, by reason (Waiting for PoH to end our leader block) | | replay_​fec_​poll_​skipped
{fec_​poll_​skip_​reason="bank_​full"} | counter | Replay loop iterations that processed no FEC set, by reason (The bank pool was full) | | replay_​fec_​parent_​missing_​dropped | counter | FEC sets dropped because replay could not resolve the block's parent (Alpenglow), which was dropped on a dead lineage, evicted with its tracking slot reused, or rooted past | diff --git a/src/disco/events/schema/events.proto b/src/disco/events/schema/events.proto index dca154a3b3e..6b3d1beb990 100644 --- a/src/disco/events/schema/events.proto +++ b/src/disco/events/schema/events.proto @@ -9877,7 +9877,7 @@ message MetricsReplay { // Banks we currently have alive uint64 bank_used = 48; // Free FEC sets in the reassembly queue, 0 under Alpenglow (no reassembly queue) - uint64 reassembly_free = 49; + uint64 reassembly_free = 49 [deprecated = true]; // Slot of the FEC set most recently dequeued from reassembly for replay, 0 if none uint64 last_reassembly_dequeued_slot = 50; // FEC set index of the FEC set most recently dequeued from reassembly for replay; read with LastReassemblyDequeuedSlot diff --git a/src/disco/events/schema/metrics_replay.json b/src/disco/events/schema/metrics_replay.json index 7bb53f0334a..0b94b826744 100644 --- a/src/disco/events/schema/metrics_replay.json +++ b/src/disco/events/schema/metrics_replay.json @@ -131,7 +131,7 @@ "reset_slot": { "type": "UInt64", "compression": "T64, ZSTD", "description": "Slot replay last reset to (Tower reset slot; under Alpenglow the newest replayed slot or parent-ready parent); can decrease" }, "bank_capacity": { "type": "UInt64", "compression": "T64, ZSTD", "description": "Maximum number of banks we can have alive" }, "bank_used": { "type": "UInt64", "compression": "T64, ZSTD", "description": "Banks we currently have alive" }, - "reassembly_free": { "type": "UInt64", "compression": "T64, ZSTD", "description": "Free FEC sets in the reassembly queue, 0 under Alpenglow (no reassembly queue)" }, + "reassembly_free": { "type": "UInt64", "compression": "T64, ZSTD", "description": "Free FEC sets in the reassembly queue, 0 under Alpenglow (no reassembly queue)", "deprecated": true }, "last_reassembly_dequeued_slot": { "type": "UInt64", "compression": "T64, ZSTD", "description": "Slot of the FEC set most recently dequeued from reassembly for replay, 0 if none" }, "last_reassembly_dequeued_fec_index": { "type": "UInt64", "compression": "T64, ZSTD", "description": "FEC set index of the FEC set most recently dequeued from reassembly for replay; read with LastReassemblyDequeuedSlot" }, "sched_active_bank_index": { "type": "UInt64", "compression": "T64, ZSTD", "description": "Bank pool index the replay scheduler is dispatching work for, ULONG_MAX if none" }, @@ -224,11 +224,11 @@ "type": "Tuple", "description": "Replay loop iterations that processed no FEC set, by reason", "aliases": { - "all": ["sched_full", "reassembly_empty", "leader_freeze_wait", "bank_full"] + "all": ["sched_full", "leader_freeze_wait", "bank_full"] }, "fields": { "sched_full": { "type": "UInt64", "compression": "DoubleDelta, ZSTD", "description": "The scheduler was full" }, - "reassembly_empty": { "type": "UInt64", "compression": "DoubleDelta, ZSTD", "description": "The reassembly queue was empty" }, + "reassembly_empty": { "type": "UInt64", "compression": "DoubleDelta, ZSTD", "description": "The reassembly queue was empty", "deprecated": true }, "leader_freeze_wait": { "type": "UInt64", "compression": "DoubleDelta, ZSTD", "description": "Waiting for PoH to end our leader block" }, "bank_full": { "type": "UInt64", "compression": "DoubleDelta, ZSTD", "description": "The bank pool was full" } } diff --git a/src/disco/metrics/generated/fd_metrics_all.c b/src/disco/metrics/generated/fd_metrics_all.c index 703384c48d6..249d965da59 100644 --- a/src/disco/metrics/generated/fd_metrics_all.c +++ b/src/disco/metrics/generated/fd_metrics_all.c @@ -928,7 +928,6 @@ const fd_metrics_meta_t FD_METRICS_REPLAY[FD_METRICS_REPLAY_TOTAL] = { DECLARE_METRIC( REPLAY_RESET_SLOT, GAUGE ), DECLARE_METRIC( REPLAY_BANK_CAPACITY, GAUGE ), DECLARE_METRIC( REPLAY_BANK_USED, GAUGE ), - DECLARE_METRIC( REPLAY_REASSEMBLY_FREE, GAUGE ), DECLARE_METRIC( REPLAY_LAST_REASSEMBLY_DEQUEUED_SLOT, GAUGE ), DECLARE_METRIC( REPLAY_LAST_REASSEMBLY_DEQUEUED_FEC_INDEX, GAUGE ), DECLARE_METRIC( REPLAY_SCHED_ACTIVE_BANK_INDEX, GAUGE ), @@ -985,7 +984,6 @@ const fd_metrics_meta_t FD_METRICS_REPLAY[FD_METRICS_REPLAY_TOTAL] = { DECLARE_METRIC( REPLAY_BANK_FROZEN, COUNTER ), DECLARE_METRIC( REPLAY_CHAIN_TXN_HEIGHT, GAUGE ), DECLARE_METRIC_ENUM( REPLAY_FEC_POLL_SKIPPED, COUNTER, FEC_POLL_SKIP_REASON, SCHED_FULL ), - DECLARE_METRIC_ENUM( REPLAY_FEC_POLL_SKIPPED, COUNTER, FEC_POLL_SKIP_REASON, REASSEMBLY_EMPTY ), DECLARE_METRIC_ENUM( REPLAY_FEC_POLL_SKIPPED, COUNTER, FEC_POLL_SKIP_REASON, LEADER_FREEZE_WAIT ), DECLARE_METRIC_ENUM( REPLAY_FEC_POLL_SKIPPED, COUNTER, FEC_POLL_SKIP_REASON, BANK_FULL ), DECLARE_METRIC( REPLAY_FEC_PARENT_MISSING_DROPPED, COUNTER ), diff --git a/src/disco/metrics/generated/fd_metrics_enums.h b/src/disco/metrics/generated/fd_metrics_enums.h index 731514c35dd..59e1b0cd39c 100644 --- a/src/disco/metrics/generated/fd_metrics_enums.h +++ b/src/disco/metrics/generated/fd_metrics_enums.h @@ -445,13 +445,11 @@ #define FD_METRICS_ENUM_FEC_POLL_SKIP_REASON_CNT (4UL) #define FD_METRICS_ENUM_FEC_POLL_SKIP_REASON_V_SCHED_FULL_IDX 0 #define FD_METRICS_ENUM_FEC_POLL_SKIP_REASON_V_SCHED_FULL_NAME "sched_full" -#define FD_METRICS_ENUM_FEC_POLL_SKIP_REASON_V_REASSEMBLY_EMPTY_IDX 1 -#define FD_METRICS_ENUM_FEC_POLL_SKIP_REASON_V_REASSEMBLY_EMPTY_NAME "reassembly_empty" #define FD_METRICS_ENUM_FEC_POLL_SKIP_REASON_V_LEADER_FREEZE_WAIT_IDX 2 #define FD_METRICS_ENUM_FEC_POLL_SKIP_REASON_V_LEADER_FREEZE_WAIT_NAME "leader_freeze_wait" #define FD_METRICS_ENUM_FEC_POLL_SKIP_REASON_V_BANK_FULL_IDX 3 #define FD_METRICS_ENUM_FEC_POLL_SKIP_REASON_V_BANK_FULL_NAME "bank_full" -#define FD_METRICS_ENUM_FEC_POLL_SKIP_REASON_G_ALL_MASK (0xfUL) +#define FD_METRICS_ENUM_FEC_POLL_SKIP_REASON_G_ALL_MASK (0xdUL) #define FD_METRICS_ENUM_PACK_SCHEDULE_REGIME_NAME "pack_schedule_regime" #define FD_METRICS_ENUM_PACK_SCHEDULE_REGIME_CNT (16UL) diff --git a/src/disco/metrics/generated/fd_metrics_replay.h b/src/disco/metrics/generated/fd_metrics_replay.h index edc64aadb12..a9fc3100ad8 100644 --- a/src/disco/metrics/generated/fd_metrics_replay.h +++ b/src/disco/metrics/generated/fd_metrics_replay.h @@ -32,7 +32,6 @@ enum { FD_METRICS_GAUGE_REPLAY_RESET_SLOT_OFF, FD_METRICS_GAUGE_REPLAY_BANK_CAPACITY_OFF, FD_METRICS_GAUGE_REPLAY_BANK_USED_OFF, - FD_METRICS_GAUGE_REPLAY_REASSEMBLY_FREE_OFF, FD_METRICS_GAUGE_REPLAY_LAST_REASSEMBLY_DEQUEUED_SLOT_OFF, FD_METRICS_GAUGE_REPLAY_LAST_REASSEMBLY_DEQUEUED_FEC_INDEX_OFF, FD_METRICS_GAUGE_REPLAY_SCHED_ACTIVE_BANK_INDEX_OFF, @@ -94,7 +93,7 @@ enum { FD_METRICS_GAUGE_REPLAY_CHAIN_TXN_HEIGHT_OFF, FD_METRICS_COUNTER_REPLAY_FEC_POLL_SKIPPED_OFF, FD_METRICS_COUNTER_REPLAY_FEC_POLL_SKIPPED_SCHED_FULL_OFF = FD_METRICS_COUNTER_REPLAY_FEC_POLL_SKIPPED_OFF, - FD_METRICS_COUNTER_REPLAY_FEC_POLL_SKIPPED_REASSEMBLY_EMPTY_OFF, + FD_METRICS_COUNTER_REPLAY_FEC_POLL_SKIPPED_REASSEMBLY_EMPTY_DEPRECATED_OFF, FD_METRICS_COUNTER_REPLAY_FEC_POLL_SKIPPED_LEADER_FREEZE_WAIT_OFF, FD_METRICS_COUNTER_REPLAY_FEC_POLL_SKIPPED_BANK_FULL_OFF, FD_METRICS_COUNTER_REPLAY_FEC_PARENT_MISSING_DROPPED_OFF, @@ -303,11 +302,6 @@ enum { #define FD_METRICS_GAUGE_REPLAY_BANK_USED_DESC "Banks we currently have alive" #define FD_METRICS_GAUGE_REPLAY_BANK_USED_CVT (FD_METRICS_CONVERTER_NONE) -#define FD_METRICS_GAUGE_REPLAY_REASSEMBLY_FREE_NAME "replay_reassembly_free" -#define FD_METRICS_GAUGE_REPLAY_REASSEMBLY_FREE_TYPE (FD_METRICS_TYPE_GAUGE) -#define FD_METRICS_GAUGE_REPLAY_REASSEMBLY_FREE_DESC "Free FEC sets in the reassembly queue, 0 under Alpenglow (no reassembly queue)" -#define FD_METRICS_GAUGE_REPLAY_REASSEMBLY_FREE_CVT (FD_METRICS_CONVERTER_NONE) - #define FD_METRICS_GAUGE_REPLAY_LAST_REASSEMBLY_DEQUEUED_SLOT_NAME "replay_last_reassembly_dequeued_slot" #define FD_METRICS_GAUGE_REPLAY_LAST_REASSEMBLY_DEQUEUED_SLOT_TYPE (FD_METRICS_TYPE_GAUGE) #define FD_METRICS_GAUGE_REPLAY_LAST_REASSEMBLY_DEQUEUED_SLOT_DESC "Slot of the FEC set most recently dequeued from reassembly for replay, 0 if none" @@ -671,7 +665,7 @@ enum { #define FD_METRICS_GAUGE_REPLAY_IS_RUNTIME_LOADED_DESC "Whether the runtime has loaded a bank and can replay" #define FD_METRICS_GAUGE_REPLAY_IS_RUNTIME_LOADED_CVT (FD_METRICS_CONVERTER_NONE) -#define FD_METRICS_REPLAY_TOTAL (159UL) +#define FD_METRICS_REPLAY_TOTAL (157UL) extern const fd_metrics_meta_t FD_METRICS_REPLAY[FD_METRICS_REPLAY_TOTAL]; #endif /* HEADER_fd_src_disco_metrics_generated_fd_metrics_replay_h */ diff --git a/src/disco/metrics/generated/fd_metrics_sample_gen.c b/src/disco/metrics/generated/fd_metrics_sample_gen.c index e0ade630246..b7a13f43154 100644 --- a/src/disco/metrics/generated/fd_metrics_sample_gen.c +++ b/src/disco/metrics/generated/fd_metrics_sample_gen.c @@ -1555,7 +1555,7 @@ static fd_metrics_sample_field_t const fields_accdb[ 132 ] = { { 0, 0U, FD_METRICS_SAMPLE_CLOSE, NULL, 0UL }, }; -static fd_metrics_sample_field_t const fields_replay[ 241 ] = { +static fd_metrics_sample_field_t const fields_replay[ 239 ] = { { FD_METRICS_GAUGE_TILE_PID_OFF, 8U, FD_METRICS_SAMPLE_U64, NULL, 0UL }, { FD_METRICS_GAUGE_TILE_TID_OFF, 9U, FD_METRICS_SAMPLE_U64, NULL, 0UL }, { FD_METRICS_GAUGE_TILE_LAST_CPU_INDEX_OFF, 10U, FD_METRICS_SAMPLE_U64, NULL, 0UL }, @@ -1636,7 +1636,6 @@ static fd_metrics_sample_field_t const fields_replay[ 241 ] = { { FD_METRICS_GAUGE_REPLAY_RESET_SLOT_OFF, 46U, FD_METRICS_SAMPLE_U64, NULL, 0UL }, { FD_METRICS_GAUGE_REPLAY_BANK_CAPACITY_OFF, 47U, FD_METRICS_SAMPLE_U64, NULL, 0UL }, { FD_METRICS_GAUGE_REPLAY_BANK_USED_OFF, 48U, FD_METRICS_SAMPLE_U64, NULL, 0UL }, - { FD_METRICS_GAUGE_REPLAY_REASSEMBLY_FREE_OFF, 49U, FD_METRICS_SAMPLE_U64, NULL, 0UL }, { FD_METRICS_GAUGE_REPLAY_LAST_REASSEMBLY_DEQUEUED_SLOT_OFF, 50U, FD_METRICS_SAMPLE_U64, NULL, 0UL }, { FD_METRICS_GAUGE_REPLAY_LAST_REASSEMBLY_DEQUEUED_FEC_INDEX_OFF, 51U, FD_METRICS_SAMPLE_U64, NULL, 0UL }, { FD_METRICS_GAUGE_REPLAY_SCHED_ACTIVE_BANK_INDEX_OFF, 52U, FD_METRICS_SAMPLE_U64, NULL, 0UL }, @@ -1702,7 +1701,6 @@ static fd_metrics_sample_field_t const fields_replay[ 241 ] = { { FD_METRICS_GAUGE_REPLAY_CHAIN_TXN_HEIGHT_OFF, 95U, FD_METRICS_SAMPLE_U64, NULL, 0UL }, { FD_METRICS_COUNTER_REPLAY_FEC_POLL_SKIPPED_OFF, 96U, FD_METRICS_SAMPLE_OPEN, NULL, 0UL }, { FD_METRICS_COUNTER_REPLAY_FEC_POLL_SKIPPED_SCHED_FULL_OFF, 1U, FD_METRICS_SAMPLE_U64, NULL, 0UL }, - { FD_METRICS_COUNTER_REPLAY_FEC_POLL_SKIPPED_REASSEMBLY_EMPTY_OFF, 2U, FD_METRICS_SAMPLE_U64, NULL, 0UL }, { FD_METRICS_COUNTER_REPLAY_FEC_POLL_SKIPPED_LEADER_FREEZE_WAIT_OFF, 3U, FD_METRICS_SAMPLE_U64, NULL, 0UL }, { FD_METRICS_COUNTER_REPLAY_FEC_POLL_SKIPPED_BANK_FULL_OFF, 4U, FD_METRICS_SAMPLE_U64, NULL, 0UL }, { 0, 0U, FD_METRICS_SAMPLE_CLOSE, NULL, 0UL }, @@ -4382,7 +4380,7 @@ fd_metrics_sample_schema_t const fd_metrics_sample_tiles[] = { { "execle", 31U, fields_execle, 205UL, 2159UL }, { "shred", 32U, fields_shred, 177UL, 1962UL }, { "accdb", 33U, fields_accdb, 132UL, 1361UL }, - { "replay", 34U, fields_replay, 241UL, 2591UL }, + { "replay", 34U, fields_replay, 239UL, 2568UL }, { "rotor", 35U, fields_rotor, 144UL, 1624UL }, { "rserve", 36U, fields_rserve, 145UL, 1593UL }, { "gossip", 37U, fields_gossip, 184UL, 1952UL }, diff --git a/src/disco/metrics/metrics.xml b/src/disco/metrics/metrics.xml index e2899321942..00114bbf810 100644 --- a/src/disco/metrics/metrics.xml +++ b/src/disco/metrics/metrics.xml @@ -633,7 +633,7 @@ deliberate exception with lint-allow="") - + @@ -998,7 +998,7 @@ deliberate exception with lint-allow="") - + diff --git a/src/discof/replay/fd_replay_tile.c b/src/discof/replay/fd_replay_tile.c index 824077ac9b3..e308051837d 100644 --- a/src/discof/replay/fd_replay_tile.c +++ b/src/discof/replay/fd_replay_tile.c @@ -16,11 +16,11 @@ #include "../genesis/fd_genesi_tile.h" #include "../poh/fd_poh.h" #include "../poh/fd_poh_tile.h" -#include "../tower/fd_tower_tile.h" #include "../votor/fd_votor_tile.h" #include "../resolv/fd_resolv_tile.h" #include "../restore/utils/fd_ssload.h" #include "../restore/utils/fd_wfs.h" +#include "../../flamenco/gossip/fd_gossip_message.h" #include "../../disco/tiles.h" #include "../../disco/fd_txn_m.h" @@ -29,7 +29,6 @@ #include "../../disco/pack/fd_pack.h" #include "../../disco/pack/fd_pack_cost.h" #include "../backup/fd_snapmk_tile.h" -#include "../reasm/fd_reasm.h" #include "../../disco/keyguard/fd_keyload.h" #include "../../disco/genesis/fd_genesis_cluster.h" #include "../../discof/genesis/genesis_hash.h" @@ -43,7 +42,6 @@ #include "../../disco/metrics/fd_metrics.h" #include "../../flamenco/events/fd_event_runtime.h" #include "../../disco/events/generated/fd_event_gen.h" -#include "../repair/fd_repair_tile.h" #include "../rotor/fd_rotor_tile.h" #include "../../flamenco/runtime/fd_runtime.h" #include "../../flamenco/runtime/fd_runtime_stack_tmpl.h" @@ -107,7 +105,6 @@ #define IN_KIND_SNAP ( 0) #define IN_KIND_GENESIS ( 1) #define IN_KIND_IPECHO ( 2) -#define IN_KIND_TOWER ( 3) #define IN_KIND_RESOLV ( 4) #define IN_KIND_POH ( 5) #define IN_KIND_EXECRP ( 6) @@ -146,12 +143,8 @@ static inline fd_block_id_ele_t * fd_block_id_ele_query( fd_replay_tile_t * ctx, fd_hash_t const * block_id, ulong slot ) { - if( !ctx->alpenglow ) { - return fd_block_id_map_ele_query( ctx->block_id_map, block_id, NULL, ctx->block_id_arr ); - } else { - ag_block_id_t key = ag_block_id( slot, block_id->uc ); - return fd_ag_block_id_map_ele_query( ctx->ag_block_id_map, &key, NULL, ctx->block_id_arr ); - } + ag_block_id_t key = ag_block_id( slot, block_id->uc ); + return fd_ag_block_id_map_ele_query( ctx->ag_block_id_map, &key, NULL, ctx->block_id_arr ); } FD_FN_CONST static inline ulong @@ -160,7 +153,7 @@ scratch_align( void ) { } FD_FN_PURE static inline ulong scratch_footprint( fd_topo_tile_t const * tile ) { - ulong chain_cnt = fd_block_id_map_chain_cnt_est( tile->replay.max_live_slots ); + ulong chain_cnt = fd_ag_block_id_map_chain_cnt_est( tile->replay.max_live_slots ); ulong l = FD_LAYOUT_INIT; l = FD_LAYOUT_APPEND( l, alignof(fd_replay_tile_t), sizeof(fd_replay_tile_t) ); @@ -172,16 +165,9 @@ scratch_footprint( fd_topo_tile_t const * tile ) { l = FD_LAYOUT_APPEND( l, alignof(fd_hash_t), sizeof(fd_hash_t) * FD_FEC_BLK_MAX * tile->replay.max_live_slots ); l = FD_LAYOUT_APPEND( l, 8UL, FD_EVENT_RUNTIME_SLOT_DIFFS_FOOTPRINT * tile->replay.max_live_slots ); } - if( !tile->replay.alpenglow ) { - l = FD_LAYOUT_APPEND( l, fd_block_id_map_align(), fd_block_id_map_footprint( chain_cnt ) ); - } else { - l = FD_LAYOUT_APPEND( l, fd_ag_block_id_map_align(), fd_ag_block_id_map_footprint( chain_cnt ) ); - } + l = FD_LAYOUT_APPEND( l, fd_ag_block_id_map_align(), fd_ag_block_id_map_footprint( chain_cnt ) ); l = FD_LAYOUT_APPEND( l, fd_txncache_align(), fd_txncache_footprint( tile->replay.max_live_slots ) ); l = FD_LAYOUT_APPEND( l, fd_accdb_align(), fd_accdb_footprint( tile->replay.max_live_slots, 0 ) ); - if( !tile->replay.alpenglow ) { - l = FD_LAYOUT_APPEND( l, fd_reasm_align(), fd_reasm_footprint( tile->replay.fec_max ) ); - } l = FD_LAYOUT_APPEND( l, alignof(fd_reception_stats_t), sizeof(fd_reception_stats_t)*tile->replay.max_live_slots ); l = FD_LAYOUT_APPEND( l, fd_sched_align(), fd_sched_footprint( tile->replay.sched_depth, tile->replay.max_live_slots, tile->replay.max_shreds_per_block, tile->replay.max_txn_per_slot ) ); l = FD_LAYOUT_APPEND( l, fd_vote_tracker_align(), fd_vote_tracker_footprint() ); @@ -191,7 +177,6 @@ scratch_footprint( fd_topo_tile_t const * tile ) { l = FD_LAYOUT_APPEND( l, fd_timing_slot_pool_align(), fd_timing_slot_pool_footprint( FD_REPLAY_TXN_TIMING_SLOTS ) ); l = FD_LAYOUT_APPEND( l, alignof(fd_replay_txn_timing_t), FD_REPLAY_TXN_TIMING_SLOTS*tile->replay.max_txn_per_slot*sizeof(fd_replay_txn_timing_t) ); l = FD_LAYOUT_APPEND( l, alignof(ulong), tile->replay.max_live_slots*sizeof(ulong) ); - l = FD_LAYOUT_APPEND( l, alignof(fd_reasm_fec_t *), (tile->replay.max_shreds_per_block/FD_FEC_SHRED_CNT)*sizeof(fd_reasm_fec_t *) ); if( FD_UNLIKELY( tile->replay.dump_block_to_pb ) ) { l = FD_LAYOUT_APPEND( l, fd_block_dump_context_align(), fd_block_dump_context_footprint() ); @@ -330,16 +315,13 @@ metrics_write( fd_replay_tile_t * ctx ) { FD_MGAUGE_SET( REPLAY, LEADER_SLOT, 0UL ); } FD_MGAUGE_SET( REPLAY, RESET_SLOT, ctx->reset_slot==ULONG_MAX ? 0UL : ctx->reset_slot ); - if( FD_UNLIKELY( ctx->alpenglow && ctx->reset_slot!=ULONG_MAX && ctx->consensus_root_slot!=ULONG_MAX ) ) { + if( FD_UNLIKELY( ctx->reset_slot!=ULONG_MAX && ctx->consensus_root_slot!=ULONG_MAX ) ) { FD_MGAUGE_SET( REPLAY, ROOT_SLOT_DISTANCE, fd_ulong_sat_sub( ctx->reset_slot, ctx->consensus_root_slot ) ); /* no tower_slot_done */ } FD_MGAUGE_SET( REPLAY, LAST_REWARDED_VOTE_SLOT, fd_ulong_if( ctx->metrics.voted_slot==ULONG_MAX, 0UL, ctx->metrics.voted_slot ) ); FD_MGAUGE_SET( REPLAY, BANK_USED, fd_banks_pool_used_cnt( ctx->banks ) ); - ulong reasm_free = ctx->reasm ? fd_reasm_free( ctx->reasm ) : 0UL; - FD_MGAUGE_SET( REPLAY, REASSEMBLY_FREE, reasm_free ); - FD_MCNT_SET( REPLAY, BANK_FROZEN, ctx->metrics.slots_total ); FD_MGAUGE_SET( REPLAY, CHAIN_TXN_HEIGHT, ctx->metrics.transactions_total ); @@ -349,7 +331,6 @@ metrics_write( fd_replay_tile_t * ctx ) { fd_sched_metrics_write( ctx->sched, ctx->clock ); FD_MCNT_SET( REPLAY, FEC_POLL_SKIPPED_SCHED_FULL, ctx->metrics.sched_full ); - FD_MCNT_SET( REPLAY, FEC_POLL_SKIPPED_REASSEMBLY_EMPTY, ctx->metrics.reasm_empty ); FD_MCNT_SET( REPLAY, FEC_POLL_SKIPPED_LEADER_FREEZE_WAIT, ctx->metrics.leader_bid_wait ); FD_MCNT_SET( REPLAY, FEC_POLL_SKIPPED_BANK_FULL, ctx->metrics.banks_full ); FD_MCNT_SET( REPLAY, FEC_PARENT_MISSING_DROPPED, ctx->metrics.parent_unavailable ); @@ -397,8 +378,6 @@ static ushort replay_voter_rank( fd_replay_tile_t * ctx, fd_bank_t * bank, ulong epoch ) { - if( FD_LIKELY( !ctx->alpenglow ) ) return USHORT_MAX; - ulong fork_id = bank->vote_stakes_fork_id; ulong fork_epoch = fd_vote_stakes_fork_epoch( fork_id ); int iter_kind = FD_VOTE_STAKES_ITER_T_2; @@ -449,8 +428,7 @@ ag_delinquent_epoch_roll( fd_replay_tile_t * ctx, } /* ag_update_delinquent stamps the reward cert's signers into - ag_last_voted and recomputes delinquent stake. Alpenglow-only: - Tower keeps last_vote_slot current through vote transactions. */ + ag_last_voted and recomputes delinquent stake. */ static void ag_update_delinquent( fd_replay_tile_t * ctx, @@ -538,8 +516,6 @@ replay_reward_cert_voted( fd_replay_tile_t * ctx, ushort * count_out ) { *rank_out = USHORT_MAX; *count_out = USHORT_MAX; - if( FD_LIKELY( !ctx->alpenglow ) ) return 0; - if( FD_UNLIKELY( bank->f.slotf.slot-FD_NUM_SLOTS_FOR_REWARD; @@ -694,21 +670,19 @@ replay_block_start( fd_replay_tile_t * ctx, int is_epoch_boundary = 0; fd_runtime_block_execute_prepare( ctx->banks, bank, ctx->accdb, ctx->runtime_stack, ctx->capture_ctx, &is_epoch_boundary ); - FD_CHECK_CRIT( !is_epoch_boundary || FD_FEATURE_ACTIVE_BANK( bank, alpenglow )==!!ctx->alpenglow, "alpenglow feature activation does not match [consensus.alpenglow]" ); + FD_CHECK_CRIT( !is_epoch_boundary || FD_FEATURE_ACTIVE_BANK( bank, alpenglow ), "alpenglow feature is not active" ); ulong max_tick_height; if( FD_UNLIKELY( FD_RUNTIME_EXECUTE_SUCCESS!=fd_runtime_compute_max_tick_height( parent_bank->f.ticks_per_slot, slot, &max_tick_height ) ) ) { FD_LOG_CRIT(( "couldn't compute tick height/max tick height slot %lu ticks_per_slot %lu", slot, parent_bank->f.ticks_per_slot )); } bank->f.max_tick_height = max_tick_height; - if( FD_UNLIKELY( ctx->alpenglow ) ) { - /* in alpenglow, we expect only one tick per block. Instead of - adjusting max tick height, we match agave behavior by setting - tick height to max tick height - 1. These fields must stay in - line with agave behavior. */ - bank->f.tick_height = bank->f.max_tick_height - 1UL; - bank->f.slot_params.hashes_per_tick = 1UL; - } + /* in alpenglow, we expect only one tick per block. Instead of + adjusting max tick height, we match agave behavior by setting + tick height to max tick height - 1. These fields must stay in + line with agave behavior. */ + bank->f.tick_height = bank->f.max_tick_height - 1UL; + bank->f.slot_params.hashes_per_tick = 1UL; fd_sched_set_poh_params( ctx->sched, bank->idx, bank->f.tick_height, bank->f.max_tick_height, bank->f.slot_params.hashes_per_tick, &parent_bank->f.poh ); FD_LOG_DEBUG(( "replay_block_start: bank_idx=%lu slot=%lu parent_bank_idx=%lu", bank_idx, slot, parent_bank_idx )); @@ -775,22 +749,16 @@ sched_block_dead_reason_to_event( fd_replay_tile_t * ctx, ulong bank_idx ) { return sched_dead_reason_to_event( fd_sched_get_dead_reason( ctx->sched, bank_idx ) ); } -/* The stats array is keyed by slot under tower and by bank idx under - alpenglow. Pass ULONG_MAX for bank_idx under tower, where the walk - keys off mr/slot instead. */ +/* The stats array is keyed by bank idx. */ static void block_completed_event_fill_reception( fd_replay_tile_t * ctx, fd_event_block_completed_t * ev, - ulong bank_idx, - fd_hash_t const * mr, - ulong slot ) { - ev->lowest_verified_fec_index = ctx->alpenglow ? 0U : UINT_MAX; + ulong bank_idx ) { + ev->lowest_verified_fec_index = 0U; ev->last_completed_fec_set_index = UINT_MAX; - fd_reception_stats_t const * stats; fd_rotor_fec_metrics_t const * rm = NULL; - if( FD_UNLIKELY( ctx->alpenglow ) ) { fd_bank_t const * bank = bank_idx==ULONG_MAX ? NULL : fd_banks_bank_query( ctx->banks, bank_idx ); if( FD_UNLIKELY( !bank ) ) return; @@ -837,57 +805,6 @@ block_completed_event_fill_reception( fd_replay_tile_t * ctx, ev->lowest_verified_fec_index = 0; ev->repair_failed_chain_verify = 0; ev->chain_confirmed = 0; - - return; - } else { - - fd_reasm_fec_t * chain_tip = fd_reasm_query( ctx->reasm, mr ); - if( FD_UNLIKELY( !chain_tip ) ) return; - - ulong n = 0UL; - fd_reasm_fec_t * f = chain_tip; - for( ; f && f->slot==slot; f = fd_reasm_parent( ctx->reasm, f ) ) { - n++; - if( f->eqvoc ) ev->equivocation_detected_shred = 1; - } - ev->fec_set_count = n; - - /* The walk's terminating FEC is the parent block's final one: its - merkle root is the parent block id. Recover parent_slot from it - too, for rows whose bank never learned it. */ - if( f ) { - fd_memcpy( ev->parent_block_id, f->key.uc, sizeof(ev->parent_block_id) ); - if( !ev->parent_slot ) ev->parent_slot = f->slot; - } - - stats = &ctx->reception_stats[ slot % ctx->reception_stats_cnt ]; - /* If there's a mismatch in the slot's stats, we just ignore it and - return. This can only happen in the case where there is a large - jump in the slot number and it exactly matches the expected - slot's modulo with max_live_slots. */ - if( FD_UNLIKELY( stats->slot!=slot ) ) return; - fd_fec_complete_metrics_t const * m = &stats->metrics.repair; - ev->last_completed_fec_set_index = stats->fec_set_idx; - ev->turbine_shred_count = m->blk_turbine_cnt; - ev->repair_shred_count = m->blk_repair_cnt; - ev->recovered_shred_count = m->blk_recovered_cnt; - ev->data_shred_count = m->blk_data_cnt; - ev->parity_shred_count = m->blk_parity_cnt; - ev->chain_confirmed = !!m->blk_chain_confirmed; - ev->slot_complete_flag = !!m->blk_slot_complete; - ev->lowest_verified_fec_index = m->blk_lowest_verified_fec; - - ev->repair_request_window_count = m->blk_req_window_cnt; - ev->repair_request_highest_window_count = m->blk_req_highest_cnt; - ev->repair_request_orphan_count = m->blk_req_orphan_cnt; - ev->repair_responses_received = m->blk_repair_responses; - ev->repair_requests_retransmitted = m->blk_req_retransmit_cnt; - ev->repair_failed_chain_verify = !!m->blk_chain_verify_failed; - ev->first_shred_received_time = m->blk_first_shred_ts_nanos; - ev->last_shred_received_time = m->blk_last_shred_ts_nanos; - ev->first_repair_request_time = m->blk_first_req_ts_nanos; - ev->last_repair_received_time = m->blk_last_repair_resp_ts_nanos; - } } static void @@ -907,7 +824,7 @@ block_completed_event_fill_bank( fd_replay_tile_t * ctx, if( FD_LIKELY( prepared ) ) ev->epoch = bank->f.epoch; else if( FD_LIKELY( ctx->notified_root_bank ) ) ev->epoch = fd_slot_to_epoch( &ctx->notified_root_bank->f.epoch_schedule, slot, NULL ); fd_block_id_ele_t const * ele = &ctx->block_id_arr[ bank->idx ]; - fd_memcpy( ev->block_id, ctx->alpenglow && ele->block_id_seen ? ele->dmr.uc : ele->latest_mr.uc, sizeof(ev->block_id) ); + fd_memcpy( ev->block_id, ele->block_id_seen ? ele->dmr.uc : ele->latest_mr.uc, sizeof(ev->block_id) ); if( FD_LIKELY( bank->block_completed_nanos ) ) fd_memcpy( ev->bank_hash, bank->f.bank_hash.uc, sizeof(ev->bank_hash) ); ev->first_fec_set_received_time = bank->is_leader ? 0UL : (ulong)bank->first_fec_set_received_nanos; @@ -938,7 +855,7 @@ block_completed_event_fill_bank( fd_replay_tile_t * ctx, ev->cost_tracker_pool_idx = fd_ulong_if( bank->cost_tracker_fork_id==USHORT_MAX, ULONG_MAX, bank->cost_tracker_fork_id ); } - block_completed_event_fill_reception( ctx, ev, bank->idx, &ctx->block_id_arr[ bank->idx ].latest_mr, slot ); + block_completed_event_fill_reception( ctx, ev, bank->idx ); } static int @@ -1128,55 +1045,6 @@ update_metric_active_stake( fd_bank_t const * bank, FD_MGAUGE_SET( REPLAY, ACTIVE_STAKE_LAMPORTS, my_active_stake ); } -/* vote_account_is_current returns 1 if the validator is current (not - delinquent). */ - -static inline int -vote_account_is_current( ulong cur_slot, - ulong last_vote_slot ) { - return last_vote_slot!=ULONG_MAX && - (cur_slotwait_info ) ) return; /* not in this topology */ - - fd_vote_stakes_t * vote_stakes = fd_bank_vote_stakes( bank ); - ulong fork_id = bank->vote_stakes_fork_id; - ulong cur_slot = bank->f.slot; - ulong delinquent = 0UL; - ulong total = 0UL; - - if( FD_LIKELY( ctx->delinquent_known && - cur_slotdelinquent_sample_slot+DELINQUENT_SAMPLE_SLOTS ) ) return; - /* Slots arrive out of order across forks, so hold the watermark. */ - ctx->delinquent_sample_slot = fd_ulong_max( ctx->delinquent_sample_slot, cur_slot ); - - uchar __attribute__((aligned(FD_VOTE_STAKES_ITER_ALIGN))) iter_mem[ FD_VOTE_STAKES_ITER_FOOTPRINT ]; - for( fd_vote_stakes_iter_t * iter = fd_vote_stakes_iter_init( vote_stakes, fork_id, FD_VOTE_STAKES_ITER_T_2, iter_mem ); - !fd_vote_stakes_iter_done( vote_stakes, fork_id, FD_VOTE_STAKES_ITER_T_2, iter ); - fd_vote_stakes_iter_next( vote_stakes, fork_id, FD_VOTE_STAKES_ITER_T_2, iter ) ) { - fd_vote_stakes_ele_t ele[1]; - fd_vote_stakes_iter_ele( vote_stakes, fork_id, FD_VOTE_STAKES_ITER_T_2, iter, ele ); - if( FD_UNLIKELY( !ele->is_valid ) ) continue; - total += ele->stake; - if( FD_UNLIKELY( !vote_account_is_current( cur_slot, ele->last_vote_slot ) ) ) { - delinquent += ele->stake; - } - } - - ctx->delinquent_stake_lamports = delinquent; - ctx->cluster_active_stake_lamports = total; - ctx->delinquent_known = 1; -} - static void update_metric_balances( fd_replay_tile_t * ctx, fd_bank_t * bank ) { @@ -1191,10 +1059,6 @@ update_metric_balances( fd_replay_tile_t * ctx, update_metric_active_stake( bank, &node_info->vote_account ); } FD_MGAUGE_SET( REPLAY, CLUSTER_ACTIVE_STAKE_LAMPORTS, bank->f.total_epoch_stake ); - - /* last_vote_slot only advances on a landed vote transaction, so this - is meaningless under Alpenglow. */ - if( FD_LIKELY( !ctx->alpenglow ) ) update_delinquent_stake( ctx, bank ); } /* try_advance_root_ag attempts to advance root to finalized_block_id. For @@ -1264,7 +1128,7 @@ publish_slot_completed( fd_replay_tile_t * ctx, ulong slot = bank->f.slot; - if( FD_UNLIKELY( ctx->alpenglow && slot>=ctx->reset_slot ) ) { + if( FD_UNLIKELY( slot>=ctx->reset_slot ) ) { ctx->reset_slot = slot; /* Under Alpenglow the votor does not send SLOT_DONE, so refresh epoch info here. Do not touch next_leader_slot: try_fini_leader @@ -1278,11 +1142,11 @@ publish_slot_completed( fd_replay_tile_t * ctx, /* HACKY: hacky way of checking if we should send a null parent block id */ - fd_hash_t block_id = ctx->alpenglow ? block_id_ele->dmr : block_id_ele->latest_mr; + fd_hash_t block_id = block_id_ele->dmr; fd_hash_t parent_block_id = {0}; if( FD_LIKELY( !is_initial ) ) { fd_block_id_ele_t const * parent_ele = &ctx->block_id_arr[ bank->parent_idx ]; - parent_block_id = ctx->alpenglow ? parent_ele->dmr : parent_ele->latest_mr; + parent_block_id = parent_ele->dmr; } fd_hash_t const * bank_hash = &bank->f.bank_hash; @@ -1353,7 +1217,6 @@ publish_slot_completed( fd_replay_tile_t * ctx, /* refcnt should be incremented by 1 for each consumer that uses `bank_idx`. Each consumer should decrement the bank's refcnt once they are done using the bank. */ - if( FD_LIKELY( !ctx->alpenglow ) ) bank->refcnt++; /* tower_tile */ if( FD_LIKELY( ctx->rpc_enabled ) ) bank->refcnt++; /* rpc tile */ slot_info->bank_idx = bank->idx; slot_info->bank_seq = bank->bank_seq; @@ -1390,7 +1253,7 @@ publish_slot_completed( fd_replay_tile_t * ctx, slot_info->vote_balance = ULONG_MAX; slot_info->vote_commission = USHORT_MAX; - if( FD_UNLIKELY( ctx->alpenglow && ctx->has_vote_account && slot%512UL==0UL ) ) { + if( FD_UNLIKELY( ctx->has_vote_account && slot%512UL==0UL ) ) { fd_acc_t acc = fd_accdb_read_one( ctx->accdb, bank->accdb_fork_id, ctx->vote_account->uc ); if( FD_LIKELY( acc.lamports ) ) { slot_info->vote_balance = acc.lamports; @@ -1414,7 +1277,7 @@ publish_slot_completed( fd_replay_tile_t * ctx, if( FD_LIKELY( !is_initial ) ) report_block_completed( ctx, bank, is_leader, slot_info ); timing_slot_release( ctx, bank->idx ); - if( FD_UNLIKELY( ctx->alpenglow && slot==ctx->finalized_block_id_lo.slot && !memcmp( block_id.uc, ctx->finalized_block_id_lo.hash, sizeof(fd_hash_t) ) ) ) { /* finalized ahead of replay, replay caught up */ + if( FD_UNLIKELY( slot==ctx->finalized_block_id_lo.slot && !memcmp( block_id.uc, ctx->finalized_block_id_lo.hash, sizeof(fd_hash_t) ) ) ) { /* finalized ahead of replay, replay caught up */ try_advance_root_ag( ctx, ctx->finalized_block_id_lo ); ctx->finalized_block_id_lo = ctx->finalized_block_id_hi; /* hi becomes lo once lo is reached */ } @@ -1438,7 +1301,7 @@ report_block_incomplete( fd_replay_tile_t * ctx, ev->slot = slot; if( FD_LIKELY( ctx->notified_root_bank ) ) ev->epoch = fd_slot_to_epoch( &ctx->notified_root_bank->f.epoch_schedule, slot, NULL ); fd_memcpy( ev->block_id, block_id->uc, sizeof(ev->block_id) ); - block_completed_event_fill_reception( ctx, ev, ULONG_MAX, block_id, slot ); + block_completed_event_fill_reception( ctx, ev, ULONG_MAX ); } ev->root_slot = ctx->consensus_root_slot; ev->storage_slot = ctx->published_root_slot; @@ -1546,11 +1409,11 @@ replay_runtime_block_emit( fd_replay_tile_t * ctx, ulong priority_fees, ulong tips ) { fd_block_id_ele_t const * ele = &ctx->block_id_arr[ bank->idx ]; - fd_hash_t const * block_id = fd_ptr_if( ctx->alpenglow, &ele->dmr, &ele->latest_mr ); + fd_hash_t const * block_id = &ele->dmr; fd_hash_t parent_block_id = {0}; if( FD_LIKELY( bank->parent_idx!=ULONG_MAX ) ) { fd_block_id_ele_t const * parent_ele = &ctx->block_id_arr[ bank->parent_idx ]; - parent_block_id = fd_ptr_if( ctx->alpenglow, &parent_ele->dmr, &parent_ele->latest_mr )[0]; + parent_block_id = parent_ele->dmr; } fd_pubkey_t leader = {0}; fd_pubkey_t const * _leader = fd_multi_epoch_leaders_get_leader_for_slot( ctx->mleaders, bank->f.slot ); @@ -1606,9 +1469,7 @@ prepare_leader_bank( fd_replay_tile_t * ctx, /* Clear the previous occupant of this reused bank idx from whichever map it is currently keyed in. */ fd_block_id_ele_t * block_id_ele = &ctx->block_id_arr[ ctx->leader_bank->idx ]; - if( !ctx->alpenglow && FD_LIKELY( fd_block_id_map_ele_query( ctx->block_id_map, &block_id_ele->latest_mr, NULL, ctx->block_id_arr )==block_id_ele ) ) { - FD_TEST( fd_block_id_map_ele_remove( ctx->block_id_map, &block_id_ele->latest_mr, NULL, ctx->block_id_arr ) ); - } else if( ctx->alpenglow && FD_LIKELY( fd_ag_block_id_map_ele_query( ctx->ag_block_id_map, &block_id_ele->block_info, NULL, ctx->block_id_arr )==block_id_ele ) ) { + if( FD_LIKELY( fd_ag_block_id_map_ele_query( ctx->ag_block_id_map, &block_id_ele->block_info, NULL, ctx->block_id_arr )==block_id_ele ) ) { FD_TEST( fd_ag_block_id_map_ele_remove( ctx->ag_block_id_map, &block_id_ele->block_info, NULL, ctx->block_id_arr ) ); } @@ -1627,7 +1488,7 @@ prepare_leader_bank( fd_replay_tile_t * ctx, int is_epoch_boundary = 0; fd_runtime_block_execute_prepare( ctx->banks, ctx->leader_bank, ctx->accdb, ctx->runtime_stack, ctx->capture_ctx, &is_epoch_boundary ); - FD_CHECK_CRIT( !is_epoch_boundary || FD_FEATURE_ACTIVE_BANK( ctx->leader_bank, alpenglow )==!!ctx->alpenglow, "alpenglow feature activation does not match [consensus.alpenglow]" ); + FD_CHECK_CRIT( !is_epoch_boundary || FD_FEATURE_ACTIVE_BANK( ctx->leader_bank, alpenglow ), "alpenglow feature is not active" ); ulong max_tick_height; if( FD_UNLIKELY( FD_RUNTIME_EXECUTE_SUCCESS!=fd_runtime_compute_max_tick_height( parent_bank->f.ticks_per_slot, slot, &max_tick_height ) ) ) { @@ -1660,28 +1521,7 @@ maybe_switch_identity( fd_replay_tile_t * ctx ) { ctx->node_info->info.identity = *ctx->identity_pubkey; fd_node_info_write_end ( ctx->node_info ); - /* The next leader slot will be incorrect now that the identity has - switched. The next leader slot normally gets updated based on the - reset slot returned by tower. */ ctx->next_leader_query_start = ULONG_MAX; - if( FD_LIKELY( !ctx->alpenglow ) ) { - ulong min_leader_slot = fd_ulong_max( ctx->reset_slot+1UL, fd_ulong_if( ctx->highwater_leader_slot==ULONG_MAX, 0UL, ctx->highwater_leader_slot+1UL ) ); - ctx->next_leader_slot = fd_multi_epoch_leaders_get_next_slot( ctx->mleaders, min_leader_slot, ctx->identity_pubkey ); - ctx->next_leader_tickcount = LONG_MAX; - if( FD_LIKELY( ctx->next_leader_slot != ULONG_MAX && ctx->is_booted ) ) { - /* If we are booted, we have a reset_bank, so use it to set - next_leader_tickcount. If we are not booted, then we don't need - to set next_leader_tickcount as it will be set when we boot. */ - fd_block_id_ele_t * block_id_ele = fd_block_id_ele_query( ctx, &ctx->reset_cmr, ctx->reset_slot ); - if( FD_LIKELY( block_id_ele ) ) { - fd_bank_t * reset_bank = fd_banks_bank_query( ctx->banks, fd_block_id_ele_get_idx( ctx->block_id_arr, block_id_ele ) ); - if( FD_LIKELY( reset_bank && reset_bank->bank_seq==block_id_ele->bank_seq && reset_bank->state!=FD_BANK_STATE_PRUNABLE ) ) { - double slot_duration_ticks = (double)reset_bank->f.slot_params.ns_per_slot_adjusted*ctx->tick_per_ns; - ctx->next_leader_tickcount = (long)((double)(ctx->next_leader_slot-ctx->reset_slot-1UL)*slot_duration_ticks) + fd_tickcount(); - } - } - } - } ctx->identity_vote_rooted = 0; ctx->identity_idx++; @@ -1694,8 +1534,8 @@ maybe_switch_identity( fd_replay_tile_t * ctx ) { } /* Save the current sequence so the voter knows what sequence to - consume up to. Tower reads replay_out, votor reads replay_slot. */ - ctx->keyswitch->result = fd_mcache_seq_query( ctx->alpenglow ? ctx->slot_out_seq : ctx->replay_out_seq ); + consume up to. */ + ctx->keyswitch->result = fd_mcache_seq_query( ctx->slot_out_seq ); fd_keyswitch_state( ctx->keyswitch, FD_KEYSWITCH_STATE_COMPLETED ); } @@ -1820,7 +1660,6 @@ try_become_leader_ag( fd_replay_tile_t * ctx, definition been replayed. So we publish reset here instead, once we know we are certain we can build off the ParentReady bank. */ - ctx->reset_cmr = block_id_ele->latest_mr; ctx->reset_dmr = *parent_block_id; ctx->reset_slot = parent_slot; ctx->reset_timestamp_nanos = fd_clock_tile_now( ctx->clock ); @@ -1958,19 +1797,17 @@ replay_block_complete( fd_replay_tile_t * ctx, fd_block_footer_t const * footer, ulong execution_fees_pre_settle, ulong priority_fees_pre_settle ) { - if( FD_UNLIKELY( ctx->alpenglow ) ) { - fd_hash_t const * footer_bank_hash = &footer->bank_hash; - if( FD_UNLIKELY( memcmp( footer_bank_hash->uc, bank->f.bank_hash.uc, sizeof(fd_hash_t) ) ) ) { - FD_BASE58_ENCODE_32_BYTES( footer_bank_hash->uc, footer_bank_hash_b58 ); - FD_BASE58_ENCODE_32_BYTES( bank->f.bank_hash.uc, executed_bank_hash_b58 ); - FD_LOG_WARNING(( "slot %lu: bank hash mismatch, footer declares %s but executed %s. ", bank->f.slot, footer_bank_hash_b58, executed_bank_hash_b58 )); - mark_bank_dead( ctx, stem, bank->idx, FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_BAD_FOOTER, FD_EVENT_BLOCK_COMPLETED_ABANDONED_REASON_NOT_ABANDONED, footer ); - return 1; - } else { - FD_BASE58_ENCODE_32_BYTES( footer_bank_hash->uc, footer_bank_hash_b58 ); - FD_BASE58_ENCODE_32_BYTES( bank->f.bank_hash.uc, executed_bank_hash_b58 ); - FD_LOG_INFO(( "slot %lu: bank hash matches, footer declares %s, executed %s", bank->f.slot, footer_bank_hash_b58, executed_bank_hash_b58 )); - } + fd_hash_t const * footer_bank_hash = &footer->bank_hash; + if( FD_UNLIKELY( memcmp( footer_bank_hash->uc, bank->f.bank_hash.uc, sizeof(fd_hash_t) ) ) ) { + FD_BASE58_ENCODE_32_BYTES( footer_bank_hash->uc, footer_bank_hash_b58 ); + FD_BASE58_ENCODE_32_BYTES( bank->f.bank_hash.uc, executed_bank_hash_b58 ); + FD_LOG_WARNING(( "slot %lu: bank hash mismatch, footer declares %s but executed %s. ", bank->f.slot, footer_bank_hash_b58, executed_bank_hash_b58 )); + mark_bank_dead( ctx, stem, bank->idx, FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_BAD_FOOTER, FD_EVENT_BLOCK_COMPLETED_ABANDONED_REASON_NOT_ABANDONED, footer ); + return 1; + } else { + FD_BASE58_ENCODE_32_BYTES( footer_bank_hash->uc, footer_bank_hash_b58 ); + FD_BASE58_ENCODE_32_BYTES( bank->f.bank_hash.uc, executed_bank_hash_b58 ); + FD_LOG_INFO(( "slot %lu: bank hash matches, footer declares %s, executed %s", bank->f.slot, footer_bank_hash_b58, executed_bank_hash_b58 )); } /* Copy out cost tracker fields before freezing */ @@ -1986,7 +1823,7 @@ replay_block_complete( fd_replay_tile_t * ctx, /* Mark the bank as frozen. */ fd_block_id_ele_t const * block_id_ele = &ctx->block_id_arr[ bank->idx ]; - bank->f.block_id = ctx->alpenglow ? block_id_ele->dmr : block_id_ele->latest_mr; + bank->f.block_id = block_id_ele->dmr; fd_banks_mark_bank_frozen( bank ); bank->block_completed_nanos = fd_clock_tile_now( ctx->clock ); @@ -2000,10 +1837,8 @@ replay_block_complete( fd_replay_tile_t * ctx, publish_slot_completed( ctx, stem, bank, 0, 0 /* is_leader */, execution_fees_pre_settle, priority_fees_pre_settle, footer ); /* The footer's finalization cert can also finalize. */ - if( FD_UNLIKELY( ctx->alpenglow ) ) { - if( footer->has_fast_final_cert ) { try_advance_root_ag( ctx, ag_block_id( footer->fast_final_cert.slot, footer->fast_final_cert.block_id.uc ) ); } - else if( footer->has_final_cert ) { try_advance_root_ag( ctx, ag_block_id( footer->final_cert.slot, footer->notar_cert.block_id.uc ) ); } - } + if( footer->has_fast_final_cert ) { try_advance_root_ag( ctx, ag_block_id( footer->fast_final_cert.slot, footer->fast_final_cert.block_id.uc ) ); } + else if( footer->has_final_cert ) { try_advance_root_ag( ctx, ag_block_id( footer->final_cert.slot, footer->notar_cert.block_id.uc ) ); } /* If enabled, dump the block to a file and reset the dumping context state */ @@ -2032,7 +1867,7 @@ replay_block_finalize( fd_replay_tile_t * ctx, ulong tips_pre_settle = bank->f.tips; fd_block_footer_t const * footer = NULL; - if( FD_UNLIKELY( ctx->alpenglow ) ) footer = fd_sched_get_footer( ctx->sched, bank->idx ); + footer = fd_sched_get_footer( ctx->sched, bank->idx ); /* Footer certs go to idle exec tiles as cert verify tasks, the rest verify here, then finalize applies the footer while the tasks run. @@ -2116,8 +1951,7 @@ try_fini_leader( fd_replay_tile_t * ctx, block was retransmitted but replay hasn't processed the FEC yet (due to bespoke repair <=> replay concurrency). */ - if( FD_UNLIKELY( ctx->alpenglow && - !ctx->block_id_arr[ ctx->leader_bank->idx ].block_id_seen && + if( FD_UNLIKELY( !ctx->block_id_arr[ ctx->leader_bank->idx ].block_id_seen && ctx->consensus_root_slot!=ULONG_MAX && ctx->consensus_root_slot>=ctx->leader_bank->f.slot ) ) { ulong bank_idx = ctx->leader_bank->idx; @@ -2139,26 +1973,11 @@ try_fini_leader( fd_replay_tile_t * ctx, ulong priority_fees_pre_settle; ulong tips_pre_settle; - if( FD_UNLIKELY( ctx->alpenglow ) ) { - - /* Already finalized above, when the footer was published. */ - - execution_fees_pre_settle = ctx->leader_execution_fees; - priority_fees_pre_settle = ctx->leader_priority_fees; - tips_pre_settle = ctx->leader_tips; - - } else { - - ctx->leader_bank->last_transaction_finished_nanos = fd_clock_tile_now( ctx->clock ); - - fd_sched_block_add_done( ctx->sched, ctx->leader_bank->idx, ctx->leader_bank->parent_idx, curr_slot ); - - execution_fees_pre_settle = ctx->leader_bank->f.execution_fees; - priority_fees_pre_settle = ctx->leader_bank->f.priority_fees; - tips_pre_settle = ctx->leader_bank->f.tips; + /* Already finalized above, when the footer was published. */ - fd_runtime_block_execute_finalize( ctx->leader_bank, ctx->accdb, ctx->capture_ctx, NULL, ctx->shred_version, NULL ); - } + execution_fees_pre_settle = ctx->leader_execution_fees; + priority_fees_pre_settle = ctx->leader_priority_fees; + tips_pre_settle = ctx->leader_tips; ctx->leader_bank->f.shred_cnt = (ulong)ctx->block_id_arr[ ctx->leader_bank->idx ].fec_cnt*FD_FEC_SHRED_CNT; @@ -2172,7 +1991,7 @@ try_fini_leader( fd_replay_tile_t * ctx, slot_info->identity_balance = fd_accdb_lamports( ctx->accdb, ctx->leader_bank->accdb_fork_id, ctx->identity_pubkey->uc ); } - ctx->leader_bank->f.block_id = ctx->alpenglow ? ctx->block_id_arr[ ctx->leader_bank->idx ].dmr : ctx->block_id_arr[ ctx->leader_bank->idx ].latest_mr; + ctx->leader_bank->f.block_id = ctx->block_id_arr[ ctx->leader_bank->idx ].dmr; fd_banks_mark_bank_frozen( ctx->leader_bank ); ctx->leader_bank->block_completed_nanos = fd_clock_tile_now( ctx->clock ); @@ -2189,7 +2008,7 @@ try_fini_leader( fd_replay_tile_t * ctx, ctx->recv_poh = 0; ctx->is_leader = 0; - if( FD_UNLIKELY( ctx->alpenglow && ctx->next_leader_slot==ULONG_MAX && (curr_slot+1UL)%AG_SLOTS_PER_WINDOW ) ) { + if( FD_UNLIKELY( ctx->next_leader_slot==ULONG_MAX && (curr_slot+1UL)%AG_SLOTS_PER_WINDOW ) ) { *ctx->votor_leader = (fd_votor_leader_t){ .slot = curr_slot+1UL, .parent_slot = curr_slot, @@ -2224,10 +2043,8 @@ publish_root_advanced( fd_replay_tile_t * ctx, /* Under Alpenglow Agave treats a rooted bank as both confirmed and finalized. The rpc tile holds an independent reference for each alias. */ - if( FD_UNLIKELY( ctx->alpenglow ) ) { - bank->refcnt++; - FD_LOG_DEBUG(( "bank (idx=%lu, slot=%lu) refcnt incremented to %lu for rpc (confirmed alias)", bank->idx, bank->f.slot, bank->refcnt )); - } + bank->refcnt++; + FD_LOG_DEBUG(( "bank (idx=%lu, slot=%lu) refcnt incremented to %lu for rpc (confirmed alias)", bank->idx, bank->f.slot, bank->refcnt )); } /* Increment the reference count on the consensus root bank to account @@ -2248,7 +2065,7 @@ publish_root_advanced( fd_replay_tile_t * ctx, /* Under Alpenglow there is no reasm to prune the store, so drop every FEC set below the new root here. */ - if( FD_UNLIKELY( ctx->alpenglow ) ) fd_store_publish( ctx->store, ctx->map_join, bank->f.slot ); + fd_store_publish( ctx->store, ctx->map_join, bank->f.slot ); } /* publish_boot_slot announces the snapshot slot, which the validator @@ -2263,7 +2080,7 @@ publish_boot_slot( fd_replay_tile_t * ctx, slot_info->identity_balance = fd_accdb_lamports( ctx->accdb, bank->accdb_fork_id, ctx->identity_pubkey->uc ); fd_block_footer_t footer = { .bank_hash = bank->f.bank_hash }; /* dummy footer for the snapshot slot */ - publish_slot_completed( ctx, stem, bank, 1, 0 /* is_leader */, 0, 0, fd_ptr_if( ctx->alpenglow, &footer, NULL ) ); + publish_slot_completed( ctx, stem, bank, 1, 0 /* is_leader */, 0, 0, &footer ); publish_root_advanced( ctx, stem, bank ); } @@ -2335,7 +2152,7 @@ init_after_snapshot( fd_replay_tile_t * ctx, char const * one_offs[ 16UL ]; for( ulong i=0UL; ienable_features_cnt; i++ ) one_offs[ i ] = ctx->enable_features[ i ]; fd_features_enable_one_offs( &bank->f.features, one_offs, (uint)ctx->enable_features_cnt, 0UL ); - FD_CHECK_CRIT( FD_FEATURE_ACTIVE_BANK( bank, alpenglow )==!!ctx->alpenglow, "alpenglow feature activation does not match [consensus.alpenglow]" ); + FD_CHECK_CRIT( FD_FEATURE_ACTIVE_BANK( bank, alpenglow ), "alpenglow feature is not active" ); bank->f.alpenglow_migration_slot = fd_alpenglow_migration_slot( bank, ctx->accdb ); /* Set slot params based on the feature gates in the snapshot, @@ -2347,9 +2164,6 @@ init_after_snapshot( fd_replay_tile_t * ctx, bank->f.slot_params = fd_slot_params_at_slot( bank, bank->f.slot ); FD_TEST( bank->f.slot_params.ns_per_slot == manifest_params.ns_per_slot ); FD_TEST( bank->f.slot_params.slots_per_year == manifest_params.slots_per_year ); - if( FD_LIKELY( manifest_params.hashes_per_tick && !FD_FEATURE_ACTIVE_BANK( bank, alpenglow ) ) ) { - FD_TEST( bank->f.slot_params.hashes_per_tick==manifest_params.hashes_per_tick ); - } fd_runtime_update_next_leaders( bank, ctx->runtime_stack ); fd_runtime_update_leaders( bank, ctx->runtime_stack ); @@ -2422,186 +2236,6 @@ init_after_snapshot( fd_replay_tile_t * ctx, metrics_trigger( ctx, FD_METRICS_SAMPLE_REASON_REPLAY_READY, bank->f.slot ); } -static inline int -try_become_leader( fd_replay_tile_t * ctx, - fd_stem_context_t * stem ) { - - if( FD_LIKELY( ctx->next_leader_slot==ULONG_MAX || - ctx->is_leader || - (!ctx->identity_vote_rooted && ctx->wait_for_vote_to_start_leader!=FD_BOOLAU_FALSE) || - ctx->replay_out->idx==ULONG_MAX || - !ctx->wfs_complete ) ) { - return 0; - } - - /* If we have evicted the reset bank we can't become leader it may be - inactive or have been resused, we can't become leader. We may miss - our leader slot if we happen to evict our reset bank. The block id - maps to the re-replayed bank before it freezes, so we can only - become leader again once it is frozen. */ - fd_block_id_ele_t * block_id_ele = fd_block_id_map_ele_query( ctx->block_id_map, &ctx->reset_cmr, NULL, ctx->block_id_arr ); - if( FD_UNLIKELY( !block_id_ele ) ) return 0; - fd_bank_t * reset_bank = fd_banks_bank_query( ctx->banks, fd_block_id_ele_get_idx( ctx->block_id_arr, block_id_ele ) ); - if( FD_UNLIKELY( !reset_bank || reset_bank->bank_seq!=block_id_ele->bank_seq || reset_bank->state!=FD_BANK_STATE_FROZEN ) ) return 0; - - if( FD_UNLIKELY( !fd_banks_can_start_bank( ctx->banks ) ) ) return 0; - if( FD_UNLIKELY( ctx->halt_replay ) ) return 0; - if( !ctx->supports_leader ) return 0; - if( FD_UNLIKELY( fd_accdb_cmd_pending( ctx->accdb ) ) ) return 0; /* leader bank attaches an accdb fork */ - - FD_TEST( ctx->next_leader_slot>ctx->reset_slot ); - long now = fd_tickcount(); - if( FD_LIKELY( nownext_leader_tickcount ) ) return 0; - - /* If a prior leader is still in the process of publishing their slot, - delay ours to let them finish ... unless they are so delayed that - we risk getting skipped by the leader following us. 3*slot duration - is a reasonable default here, although any value within our leader - span could be considered reasonable. This is arbitrary and chosen - due to intuition. - - If we are becoming leader for a slot at an epoch boundary where a - slot time reduction is taking effect, we have a choice for the - grace period: give the previous leader maximal time to complete - their slot, or use a shorter grace period reflecting the new - shorter slot duration that the next leader after us will use to - time out our slot. We choose the latter to minimize the risk of - our slot getting skipped: a grace period based on the new shorter - slot duration at next_leader_slot. This only matters for epoch - boundaries where a slot time reduction is taking effect, so either - choice is defensible. */ - ulong ns_per_slot_adjusted = fd_slot_params_at_slot( reset_bank, ctx->next_leader_slot ).ns_per_slot_adjusted; - double slot_duration_ticks = (double)ns_per_slot_adjusted*ctx->tick_per_ns; - if( FD_UNLIKELY( nownext_leader_tickcount+(long)(3.0*slot_duration_ticks) ) ) { - /* TODO: Make the max_active_descendant calculation more efficient - by caching it in the bank structure and updating it as banks are - created and completed. */ - ulong max_active_descendant = 0UL; - ulong child_idx = reset_bank->child_idx; - while( child_idx!=ULONG_MAX ) { - fd_bank_t * child_bank = fd_banks_bank_query( ctx->banks, child_idx ); - max_active_descendant = fd_ulong_max( max_active_descendant, ctx->block_id_arr[ child_idx ].slot ); - child_idx = child_bank->sibling_idx; - } - - /* If the max_active_descendant is >= next_leader_slot, we waited - too long and a leader after us started publishing to try and skip - us. Just start our leader slot immediately, we might win ... */ - if( FD_LIKELY( max_active_descendant>=ctx->reset_slot && max_active_descendantnext_leader_slot ) ) { - /* If one of the leaders between the reset slot and our leader - slot is in the process of publishing (they have a descendant - bank that is in progress of being replayed), then keep waiting. - We probably wouldn't get a leader slot out before they - finished. - - Unless... we are past the deadline to start our slot by more - than 3*(slot duration), in which case we should probably start - it to avoid getting skipped by the leader behind us. */ - return 0; - } - } - - /* If we haven't started replaying the prior block, but we have - finished replaying the second to last slot of the prior - leader (and that leader is not us), we should give the prior leader - a little more time. */ - if( FD_UNLIKELY( ctx->next_leader_slot==ctx->reset_slot+2UL && nownext_leader_tickcount+(long)(1.0*slot_duration_ticks) ) ) { - - fd_pubkey_t const * reset_leader = fd_multi_epoch_leaders_get_leader_for_slot( ctx->mleaders, ctx->reset_slot ); - if( FD_UNLIKELY( reset_leader && !fd_memeq( reset_leader, ctx->identity_pubkey, 32UL ) ) ) return 0; - } - - long now_nanos = fd_clock_epoch_y( ctx->clock->epoch, now ); - - ctx->is_leader = 1; - ctx->recv_poh = 0; - - memset( &ctx->leader_stats, 0, sizeof(ctx->leader_stats) ); - ctx->leader_stats.slot = ctx->next_leader_slot; - ctx->leader_stats.timing_table_idx = ULONG_MAX; - ctx->leader_stats.became_leader_nanos = now_nanos; - ctx->leader_stats.leader_slot_start_nanos = fd_clock_epoch_y( ctx->clock->epoch, ctx->next_leader_tickcount ); - - FD_TEST( ctx->highwater_leader_slot==ULONG_MAX || ctx->highwater_leader_slotnext_leader_slot ); - ctx->highwater_leader_slot = ctx->next_leader_slot; - - FD_LOG_INFO(( "becoming leader for slot %lu, parent slot is %lu", ctx->next_leader_slot, ctx->reset_slot )); - - fd_bank_t * bank = prepare_leader_bank( ctx, reset_bank, ctx->next_leader_slot, now_nanos ); - - fd_bundle_crank_tip_payment_config_t config[1] = { 0 }; - fd_pubkey_t tip_receiver_owner = {0}; - - if( FD_UNLIKELY( ctx->bundle.enabled ) ) { - fd_acct_addr_t tip_payment_config[1]; - fd_acct_addr_t tip_receiver[1]; - fd_bundle_crank_get_addresses( ctx->bundle.gen, bank->f.epoch, tip_payment_config, tip_receiver ); - - fd_acc_t tip_config_acc = fd_accdb_read_one( ctx->accdb, bank->accdb_fork_id, tip_payment_config->b ); - if( FD_UNLIKELY( !tip_config_acc.lamports ) ) { - FD_BASE58_ENCODE_32_BYTES( tip_payment_config->b, tip_config_acc_b58 ); - FD_LOG_WARNING(( "tip payment config account %s does not exist", tip_config_acc_b58 )); - fd_accdb_unread_one( ctx->accdb, &tip_config_acc ); - } else if( FD_UNLIKELY( tip_config_acc.data_lenaccdb, &tip_config_acc ); - } else { - memcpy( config, tip_config_acc.data, sizeof(fd_bundle_crank_tip_payment_config_t) ); - fd_accdb_unread_one( ctx->accdb, &tip_config_acc ); - } - - /* It is possible that the tip receiver account does not exist yet - if it is the first time in an epoch. */ - fd_acc_t tip_receiver_acc = fd_accdb_read_one( ctx->accdb, bank->accdb_fork_id, tip_receiver->b ); - if( FD_LIKELY( tip_receiver_acc.lamports ) ) { - fd_memcpy( tip_receiver_owner.uc, tip_receiver_acc.owner, 32UL ); - } - fd_accdb_unread_one( ctx->accdb, &tip_receiver_acc ); - } - - - fd_became_leader_t * msg = fd_chunk_to_laddr( ctx->replay_out->mem, ctx->replay_out->chunk ); - msg->slot = ctx->next_leader_slot; - msg->block_height = bank->f.block_height; - msg->slot_start_ns = now_nanos; - msg->slot_end_ns = now_nanos+(long)bank->f.slot_params.ns_per_slot_adjusted; - msg->bank_idx = bank->idx; - msg->bank_seq = bank->bank_seq; - msg->ticks_per_slot = bank->f.ticks_per_slot; - msg->hashcnt_per_tick = bank->f.slot_params.hashes_per_tick; - msg->tick_duration_ns = bank->f.slot_params.ns_per_slot_adjusted/msg->ticks_per_slot; - msg->bundle->config[0] = config[0]; - memcpy( msg->bundle->last_blockhash, bank->f.poh.hash, sizeof(fd_hash_t) ); - memcpy( msg->bundle->tip_receiver_owner, tip_receiver_owner.uc, sizeof(fd_pubkey_t) ); - - msg->max_microblocks_in_slot = fd_poh_max_microblocks_per_slot( msg->ticks_per_slot, msg->hashcnt_per_tick ); - - msg->total_skipped_ticks = msg->ticks_per_slot*(ctx->next_leader_slot-ctx->reset_slot); - msg->epoch = fd_slot_to_epoch( &bank->f.epoch_schedule, ctx->next_leader_slot, NULL ); - - fd_cost_tracker_t const * cost_tracker = fd_cost_tracker_store_peek( fd_bank_cost_tracker( bank ), bank->cost_tracker_fork_id ); - - msg->limits.slot_max_cost = cost_tracker->block_cost_limit; - msg->limits.slot_max_vote_cost = FD_PACK_MAX_VOTE_COST_PER_BLOCK_UPPER_BOUND; - msg->limits.slot_max_write_cost_per_acct = cost_tracker->account_cost_limit; - msg->limits.slot_max_allocated_data_per_block = cost_tracker->data_size_limit; - msg->limits.slot_max_data_shreds = bank->f.slot_params.max_shred_idx; - - if( FD_UNLIKELY( msg->ticks_per_slot+msg->total_skipped_ticks>USHORT_MAX ) ) { - /* There can be at most USHORT_MAX skipped ticks, because the - parent_offset field in the shred data is only 2 bytes wide. */ - FD_LOG_ERR(( "too many skipped ticks %lu for slot %lu, chain must halt", msg->ticks_per_slot+msg->total_skipped_ticks, ctx->next_leader_slot )); - } - - publish_replay_out( ctx, stem, REPLAY_SIG_BECAME_LEADER, sizeof(fd_became_leader_t) ); - metrics_trigger( ctx, FD_METRICS_SAMPLE_REASON_LEADER_START, msg->slot ); - - ctx->next_leader_slot = ULONG_MAX; - ctx->next_leader_tickcount = LONG_MAX; - - return 1; -} - static ulong enforce_nanosecond_clock_bounds( fd_replay_tile_t * ctx, fd_bank_t * bank, @@ -2660,32 +2294,30 @@ process_poh_message( fd_replay_tile_t * ctx, ctx->recv_poh = 1; - if( FD_UNLIKELY( ctx->alpenglow ) ) { - ctx->leader_bank->last_transaction_finished_nanos = fd_clock_tile_now( ctx->clock ); - fd_sched_block_add_done( ctx->sched, ctx->leader_bank->idx, ctx->leader_bank->parent_idx, ctx->leader_bank->f.slot ); - - /* cache fees before runtime zeros */ + ctx->leader_bank->last_transaction_finished_nanos = fd_clock_tile_now( ctx->clock ); + fd_sched_block_add_done( ctx->sched, ctx->leader_bank->idx, ctx->leader_bank->parent_idx, ctx->leader_bank->f.slot ); - ctx->leader_execution_fees = ctx->leader_bank->f.execution_fees; - ctx->leader_priority_fees = ctx->leader_bank->f.priority_fees; - ctx->leader_tips = ctx->leader_bank->f.tips; + /* cache fees before runtime zeros */ - fd_block_footer_t * footer = ctx->leader_footer; - fd_memset( footer, 0, sizeof(fd_block_footer_t) ); - footer->block_producer_time_nanos = enforce_nanosecond_clock_bounds( ctx, ctx->leader_bank, (ulong)fd_clock_tile_now( ctx->clock ) ); + ctx->leader_execution_fees = ctx->leader_bank->f.execution_fees; + ctx->leader_priority_fees = ctx->leader_bank->f.priority_fees; + ctx->leader_tips = ctx->leader_bank->f.tips; - ulong migration_slot = fd_alpenglow_migration_slot( ctx->leader_bank, ctx->accdb ); - construct_footer_certs( ctx, ctx->leader_bank->f.slot, migration_slot, footer ); + fd_block_footer_t * footer = ctx->leader_footer; + fd_memset( footer, 0, sizeof(fd_block_footer_t) ); + footer->block_producer_time_nanos = enforce_nanosecond_clock_bounds( ctx, ctx->leader_bank, (ulong)fd_clock_tile_now( ctx->clock ) ); - /* The block goes out regardless: the certs are already committed to - the bank hash, so there is nothing left to fall back to. */ - if( FD_UNLIKELY( fd_runtime_block_execute_finalize( ctx->leader_bank, ctx->accdb, ctx->capture_ctx, footer, ctx->shred_version, ctx->footer_cache ) ) ) { - FD_LOG_WARNING(( "slot %lu: our own block footer certs did not apply; the block we produce will be dead to the cluster", ctx->leader_bank->f.slot )); - } - footer->bank_hash = ctx->leader_bank->f.bank_hash; + ulong migration_slot = fd_alpenglow_migration_slot( ctx->leader_bank, ctx->accdb ); + construct_footer_certs( ctx, ctx->leader_bank->f.slot, migration_slot, footer ); - publish_leader_footer( ctx, stem, ctx->leader_bank->f.slot, footer ); + /* The block goes out regardless: the certs are already committed to + the bank hash, so there is nothing left to fall back to. */ + if( FD_UNLIKELY( fd_runtime_block_execute_finalize( ctx->leader_bank, ctx->accdb, ctx->capture_ctx, footer, ctx->shred_version, ctx->footer_cache ) ) ) { + FD_LOG_WARNING(( "slot %lu: our own block footer certs did not apply; the block we produce will be dead to the cluster", ctx->leader_bank->f.slot )); } + footer->bank_hash = ctx->leader_bank->f.bank_hash; + + publish_leader_footer( ctx, stem, ctx->leader_bank->f.slot, footer ); } static void @@ -2701,7 +2333,6 @@ boot_genesis( fd_replay_tile_t * ctx, /* Firedancer only boots Alpenglow clusters from genesis. The genesis certificate certifies slot 0 with a zero block id. */ - FD_CHECK_ERR( ctx->alpenglow, "booting from genesis requires alpenglow" ); fd_memset( &ctx->initial_block_id, 0, sizeof(fd_hash_t) ); /* If we are bootstrapping, we can't wait to wait for our identity @@ -2764,7 +2395,6 @@ boot_genesis( fd_replay_tile_t * ctx, } ctx->reset_slot = 0UL; - ctx->reset_cmr = ctx->initial_block_id; ctx->reset_dmr = ctx->initial_block_id; ctx->epoch_end_slot = 0UL; ctx->slots_per_epoch = 0UL; @@ -2968,7 +2598,6 @@ on_snapshot_message( fd_replay_tile_t * ctx, } ctx->reset_slot = snapshot_slot; - ctx->reset_cmr = manifest_block_id; ctx->reset_dmr = manifest_block_id; refresh_epoch_info( ctx, bank, snapshot_slot ); ctx->reset_timestamp_nanos = fd_clock_tile_now( ctx->clock ); @@ -2985,33 +2614,13 @@ on_snapshot_message( fd_replay_tile_t * ctx, block_id_ele->latest_fec_idx = 0U; bank->f.block_id = manifest_block_id; - if( !ctx->alpenglow ) { - FD_TEST( fd_block_id_map_ele_insert( ctx->block_id_map, block_id_ele, ctx->block_id_arr ) ); - } else { - block_id_ele->block_info = ag_block_id( snapshot_slot, manifest_block_id.uc ); - FD_TEST( fd_ag_block_id_map_ele_insert( ctx->ag_block_id_map, block_id_ele, ctx->block_id_arr ) ); - } + block_id_ele->block_info = ag_block_id( snapshot_slot, manifest_block_id.uc ); + FD_TEST( fd_ag_block_id_map_ele_insert( ctx->ag_block_id_map, block_id_ele, ctx->block_id_arr ) ); /* We call this after fd_runtime_read_genesis, which sets up the slot_bank needed in blockstore_init. */ init_after_snapshot( ctx, stem ); - if( FD_LIKELY( !ctx->alpenglow ) ) { - ctx->next_leader_slot = fd_multi_epoch_leaders_get_next_slot( ctx->mleaders, snapshot_slot+1UL, ctx->identity_pubkey ); - if( FD_LIKELY( ctx->next_leader_slot != ULONG_MAX ) ) { - double slot_duration_ticks = (double)bank->f.slot_params.ns_per_slot_adjusted*ctx->tick_per_ns; - ctx->next_leader_tickcount = (long)((double)(ctx->next_leader_slot-ctx->reset_slot-1UL)*slot_duration_ticks) + fd_tickcount(); - } else { - ctx->next_leader_tickcount = LONG_MAX; - } - } - - if( ctx->reasm ) { - fd_reasm_fec_t * fec = fd_reasm_init( ctx->reasm, &manifest_block_id, snapshot_slot ); - fec->bank_idx = (uint)bank->idx; - fec->bank_seq = bank->bank_seq; - } - long now = fd_log_wallclock(); FD_LOG_INFO(( "replay ready at slot %lu (%.3f s after snapshot done, %.3f s since boot)", snapshot_slot, (double)(now-snapshot_done_nanos)/1e9, (double)(now-ctx->boot_timestamp_nanos)/1e9 )); @@ -3185,8 +2794,8 @@ mark_bank_dead( fd_replay_tile_t * ctx, fd_banks_mark_bank_dead( ctx->banks, bank_idx, dead_idxs, &dead_idxs_cnt ); fd_block_id_ele_t * block_id_ele = &ctx->block_id_arr[ bank_idx ]; - if( block_id_ele->block_id_seen ) publish_slot_dead( ctx, stem, block_id_ele->slot, ctx->alpenglow ? &block_id_ele->dmr : &block_id_ele->latest_mr, footer ); - else if( ctx->alpenglow && dead_reason!=FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_NOT_DEAD && !memcmp( block_id_ele->block_info.hash, ((fd_hash_t){0}).uc, sizeof(fd_hash_t) ) ) publish_slot_dead( ctx, stem, block_id_ele->slot, &(fd_hash_t){ 0 }, footer ); /* an incomplete turbine copy, keyed {slot,0}: rotor frees its eager blk */ + if( block_id_ele->block_id_seen ) publish_slot_dead( ctx, stem, block_id_ele->slot, &block_id_ele->dmr, footer ); + else if( dead_reason!=FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_NOT_DEAD && !memcmp( block_id_ele->block_info.hash, ((fd_hash_t){0}).uc, sizeof(fd_hash_t) ) ) publish_slot_dead( ctx, stem, block_id_ele->slot, &(fd_hash_t){ 0 }, footer ); /* an incomplete turbine copy, keyed {slot,0}: rotor frees its eager blk */ /* Report each newly dead bank now (dead_idxs excludes already-dead, already-reported subtrees): the failing bank with its real reason and @@ -3199,17 +2808,12 @@ mark_bank_dead( fd_replay_tile_t * ctx, int abandoned = abandoned_reason!=FD_EVENT_BLOCK_COMPLETED_ABANDONED_REASON_NOT_ABANDONED; for( ulong i=0UL; iblock_id_arr[ dead_idxs[ i ] ]; - fd_reasm_fec_t * fec = ctx->reasm ? fd_reasm_query( ctx->reasm, &ele->latest_mr ) : NULL; fd_bank_t * bank = fd_banks_bank_query( ctx->banks, dead_idxs[ i ] ); - if( FD_LIKELY( fec ) ) { - fec->bank_dead = (uchar)(abandoned ? 2U : 1U); - fec->dead_reported = 1; - } timing_slot_release( ctx, dead_idxs[ i ] ); int dr = abandoned ? FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_NOT_DEAD : dead_idxs[ i ]==bank_idx ? dead_reason : FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_PARENT_DEAD; - fd_hash_t * block_id = ctx->alpenglow && ele->block_id_seen ? &ele->dmr : &ele->latest_mr; + fd_hash_t * block_id = ele->block_id_seen ? &ele->dmr : &ele->latest_mr; report_block_incomplete( ctx, ele->slot, block_id, bank, dr, abandoned_reason ); } } @@ -3222,7 +2826,7 @@ try_replay( fd_replay_tile_t * ctx, /* Hold off executing until the computed shred version is known, so footer certs verify under it. */ - if( FD_UNLIKELY( ctx->alpenglow && !ctx->shred_version ) ) return 0; + if( FD_UNLIKELY( !ctx->shred_version ) ) return 0; /* Starting a block attaches an accdb fork, which would spin on a pending root. Nothing of the block dispatches before its start. */ @@ -3272,169 +2876,74 @@ try_replay( fd_replay_tile_t * ctx, return charge_busy; } +#define PROCESS_FEC_WAIT 0 /* Put back on dcache for later retry */ +#define PROCESS_FEC_OK 1 /* Process FEC set */ +#define PROCESS_FEC_DROP 2 /* Drop FEC set and notify rotor to start redelivery */ +#define PROCESS_FEC_SKIP 3 /* Good to skip this FEC set, drop from dcache */ + +/* live_block_id_ele returns ele if it is backed by a live bank (same + bank_seq, not PRUNABLE), else NULL. Map entries outlive their banks + through eviction. TODO if we remove block_id_eles with banks, we + can get rid of this */ +static fd_block_id_ele_t * +live_block_id_ele( fd_replay_tile_t * ctx, + fd_block_id_ele_t * ele ) { + if( FD_UNLIKELY( !ele ) ) return NULL; + fd_bank_t * bank = fd_banks_bank_query( ctx->banks, fd_block_id_ele_get_idx( ctx->block_id_arr, ele ) ); + if( FD_UNLIKELY( !bank || bank->bank_seq!=ele->bank_seq || bank->state==FD_BANK_STATE_PRUNABLE ) ) return NULL; + return ele; +} + static int -can_process_fec( fd_replay_tile_t * ctx, - int * evict_banks_out ) { +can_process_rotor_fec( fd_replay_tile_t * ctx, + fd_rotor_replay_fec_t * fec, + int * evict_banks_out ) { /* We can process a FEC set if a few conditions are met: - sched has capacity - - reasm has a FEC in its out queue ready to be processed - - banks has capacity. Evict if we don't (see below) */ + - banks has capacity. Evict if we don't (see below) + Our leader FECs bind to the existing leader bank and never enter + sched, so neither capacity gate applies to them. One that is not + for the current leader slot has no bank to bind to. */ + + if( FD_UNLIKELY( fec->is_leader && ( !ctx->leader_bank || ctx->leader_bank->f.slot!=fec->slot ) ) ) return PROCESS_FEC_SKIP; - if( FD_UNLIKELY( fd_sched_can_ingest_cnt( ctx->sched )==0UL ) ) { + if( FD_UNLIKELY( !fec->is_leader && fd_sched_can_ingest_cnt( ctx->sched )==0UL ) ) { FD_TEST( !fd_sched_is_drained( ctx->sched ) ); ctx->metrics.sched_full++; - return 0; + return PROCESS_FEC_WAIT; } - fd_reasm_fec_t * fec; - if( FD_UNLIKELY( (fec = fd_reasm_peek( ctx->reasm ))==NULL ) ) { - ctx->metrics.reasm_empty++; - return 0; + /* Recovery redelivery re-publishes the entire ancestry path from the + rotor root, but most of those blocks are already replayed. A + fully replayed block is always keyed by {slot, block_id_real}; a + turbine block still in flight is keyed by {slot, 0}. Rotor fills + in block_id on redelivered FECs whenever it knows it, so try the id + key first and fall back to the slot key for a turbine version. + + A turbine FEC only counts a {slot, block_id} hit if that block is + complete. An incomplete one is the repaired copy of the same + block, a different stream that may be ahead of or behind the + turbine copy: rotor merges the two copies into the turbine one + without changing its known_id, so the turbine copy keeps building + its own {slot, 0} bank, and the repaired one is abandoned once the + turbine bank completes and takes its {slot, block_id} key. */ + { + fd_block_id_ele_t * self = NULL; + if( FD_LIKELY( !fd_hash_check_zero( &fec->block_id ) ) ) { + ag_block_id_t self_key = ag_block_id( fec->slot, fec->block_id.uc ); + self = live_block_id_ele( ctx, fd_ag_block_id_map_ele_query( ctx->ag_block_id_map, &self_key, NULL, ctx->block_id_arr ) ); + if( FD_UNLIKELY( self && !self->block_id_seen && !fec->known_id ) ) self = NULL; + } + if( FD_LIKELY( !self && !fec->known_id ) ) { + ag_block_id_t self_key = { .slot = fec->slot }; + self = live_block_id_ele( ctx, fd_ag_block_id_map_ele_query( ctx->ag_block_id_map, &self_key, NULL, ctx->block_id_arr ) ); + } + if( FD_UNLIKELY( self && ( self->block_id_seen || self->latest_fec_idx>=fec->fec_set_idx ) ) ) return PROCESS_FEC_SKIP; } - fd_reasm_fec_t * parent = fd_reasm_parent( ctx->reasm, fec ); - FD_TEST( parent ); /* FEC must be connected */ - - ctx->metrics.reasm_latest_slot = fec->slot; - ctx->metrics.reasm_latest_fec_idx = fec->fec_set_idx; - - /* If the FEC we are building off of is for a prunable bank, we must - wait to process the FEC until the bank has been evicted. */ - fd_bank_t * parent_fec_bank = parent->bank_idx==UINT_MAX ? NULL : fd_banks_bank_query( ctx->banks, parent->bank_idx ); - if( FD_UNLIKELY( parent_fec_bank && parent_fec_bank->bank_seq==parent->bank_seq && parent_fec_bank->state==FD_BANK_STATE_PRUNABLE ) ) { - FD_LOG_DEBUG(( "waiting to process FEC set (slot=%lu, fec_set_idx=%u) because parent bank is being pruned", fec->slot, fec->fec_set_idx )); - return 0; - } - - if( FD_UNLIKELY( ctx->is_leader && - fec->fec_set_idx==0U && - parent->bank_idx==ctx->leader_bank->idx && - parent->bank_seq==ctx->leader_bank->bank_seq ) ) { - /* This guards against a rare race where we receive the FEC set for - the slot right after our leader rotation before we freeze the - bank for the last slot in our leader rotation. Leader slot - freezing happens only after if we've received the final PoH hash - from the poh tile as well as the final FEC set for the leader - slot. So the race happens when FEC sets are delivered and - processed sooner than the PoH hash, aka when the - poh=>shred=>replay path for the block id beats the poh=>replay - path for the poh hash. To mitigate this race, we must block on - ingesting the FEC set for the ensuing slot before the leader - bank freezes, because that would violate ordering invariants in - banks and sched. Both the bank index and sequence must match - because bank indices are recycled after eviction. */ - FD_TEST( ctx->block_id_arr[ ctx->leader_bank->idx ].block_id_seen ); - FD_TEST( !ctx->recv_poh ); - ctx->metrics.leader_bid_wait++; - return 0; - } - - /* Should we evict banks if there are no more free banks? The answer - is it depends. Eviction should only happen if we can make no - forward replay progress. This can only happen if: - 1. banks are full - 2. sched is drained: pending txns could complete a block and - eventually advance the root. - AND - 3. next reasm FEC needs a new bank. A fec that chains off of a - bank that is already allocated can be processed. A FEC can - require a new bank in three ways: - - fec_set_idx==0: we don't have any free banks to provision a - new bank for this FEC. - - equivocation: a FEC may be in the middle of a block, but if - it's the first equivocating FEC detected, we need to allocate - a new bank for the version of the block. - - backfill: the parent FEC's bank was never created or has been - evicted and must be reconstructed. */ - - int invalid_parent = !parent_fec_bank || parent_fec_bank->bank_seq!=parent->bank_seq; - if( FD_UNLIKELY( !fd_banks_can_start_bank( ctx->banks ) ) ) { - int is_new_block = fec->fec_set_idx==0U; - int is_eqvoc = fec->eqvoc && !parent->eqvoc; - if( FD_UNLIKELY( is_new_block || is_eqvoc || invalid_parent ) ) { - ctx->metrics.banks_full++; - /* While a snapshot freezes the published root nothing is pruned, - so evicting would only drop the tip and replay it again. Wait - for the snapshot to finish instead. */ - if( FD_UNLIKELY( fd_sched_is_drained( ctx->sched ) && !ctx->snapmk.active ) ) *evict_banks_out = 1; - return 0; - } - } - - /* Otherwise, banks may not be full, so we can always create a new - bank if needed. Or, if banks are full, the current fec set's - ancestor (idx 0) already created a bank for this slot. */ - return 1; -} - -#define PROCESS_FEC_WAIT 0 /* Put back on dcache for later retry */ -#define PROCESS_FEC_OK 1 /* Process FEC set */ -#define PROCESS_FEC_DROP 2 /* Drop FEC set and notify rotor to start redelivery */ -#define PROCESS_FEC_SKIP 3 /* Good to skip this FEC set, drop from dcache */ - -/* live_block_id_ele returns ele if it is backed by a live bank (same - bank_seq, not PRUNABLE), else NULL. Map entries outlive their banks - through eviction. TODO if we remove block_id_eles with banks, we - can get rid of this */ -static fd_block_id_ele_t * -live_block_id_ele( fd_replay_tile_t * ctx, - fd_block_id_ele_t * ele ) { - if( FD_UNLIKELY( !ele ) ) return NULL; - fd_bank_t * bank = fd_banks_bank_query( ctx->banks, fd_block_id_ele_get_idx( ctx->block_id_arr, ele ) ); - if( FD_UNLIKELY( !bank || bank->bank_seq!=ele->bank_seq || bank->state==FD_BANK_STATE_PRUNABLE ) ) return NULL; - return ele; -} - -static int -can_process_rotor_fec( fd_replay_tile_t * ctx, - fd_rotor_replay_fec_t * fec, - int * evict_banks_out ) { - /* We can process a FEC set if a few conditions are met: - - sched has capacity - - banks has capacity. Evict if we don't (see below) - Our leader FECs bind to the existing leader bank and never enter - sched, so neither capacity gate applies to them. One that is not - for the current leader slot has no bank to bind to. */ - - if( FD_UNLIKELY( fec->is_leader && ( !ctx->leader_bank || ctx->leader_bank->f.slot!=fec->slot ) ) ) return PROCESS_FEC_SKIP; - - if( FD_UNLIKELY( !fec->is_leader && fd_sched_can_ingest_cnt( ctx->sched )==0UL ) ) { - FD_TEST( !fd_sched_is_drained( ctx->sched ) ); - ctx->metrics.sched_full++; - return PROCESS_FEC_WAIT; - } - - /* Recovery redelivery re-publishes the entire ancestry path from the - rotor root, but most of those blocks are already replayed. A - fully replayed block is always keyed by {slot, block_id_real}; a - turbine block still in flight is keyed by {slot, 0}. Rotor fills - in block_id on redelivered FECs whenever it knows it, so try the id - key first and fall back to the slot key for a turbine version. - - A turbine FEC only counts a {slot, block_id} hit if that block is - complete. An incomplete one is the repaired copy of the same - block, a different stream that may be ahead of or behind the - turbine copy: rotor merges the two copies into the turbine one - without changing its known_id, so the turbine copy keeps building - its own {slot, 0} bank, and the repaired one is abandoned once the - turbine bank completes and takes its {slot, block_id} key. */ - { - fd_block_id_ele_t * self = NULL; - if( FD_LIKELY( !fd_hash_check_zero( &fec->block_id ) ) ) { - ag_block_id_t self_key = ag_block_id( fec->slot, fec->block_id.uc ); - self = live_block_id_ele( ctx, fd_ag_block_id_map_ele_query( ctx->ag_block_id_map, &self_key, NULL, ctx->block_id_arr ) ); - if( FD_UNLIKELY( self && !self->block_id_seen && !fec->known_id ) ) self = NULL; - } - if( FD_LIKELY( !self && !fec->known_id ) ) { - ag_block_id_t self_key = { .slot = fec->slot }; - self = live_block_id_ele( ctx, fd_ag_block_id_map_ele_query( ctx->ag_block_id_map, &self_key, NULL, ctx->block_id_arr ) ); - } - if( FD_UNLIKELY( self && ( self->block_id_seen || self->latest_fec_idx>=fec->fec_set_idx ) ) ) return PROCESS_FEC_SKIP; - } - - /* Rooted past: the block is rooted or on a dead fork, so no - redelivery can help. A DROP would ask for another, and during - catch-up that one also lands behind the backlog, rooted past. */ + /* Rooted past: the block is rooted or on a dead fork, so no + redelivery can help. A DROP would ask for another, and during + catch-up that one also lands behind the backlog, rooted past. */ if( FD_UNLIKELY( ctx->published_root_slot!=ULONG_MAX && ( fec->slot<=ctx->published_root_slot || @@ -3564,288 +3073,6 @@ can_process_rotor_fec( fd_replay_tile_t * ctx, return PROCESS_FEC_OK; } -/* Returns 0 on successful FEC ingestion, 1 if the block got marked - dead. insert_fec_set assumes that all FECs that are inserted are - directly connected to a parent FEC. Every block that is replayed - has initial fec set idx 0 up to and including a FEC with - slot_complete set. The caller is responsible for ensuring this. */ -static int -insert_fec_set( fd_replay_tile_t * ctx, - fd_stem_context_t * stem, - fd_reasm_fec_t * reasm_fec ) { - - /* First, read FEC set from the store. If it's not there that means - that the FEC is on a minority fork which has been pruned away. - This means we shouldn't have a bank for the corresponding block so - we should just ignore and discard the FEC set. */ - - long work = fd_tickcount(); - - fd_store_fec_t * store_fec = fd_store_query( ctx->map_join, &reasm_fec->key ); - ctx->metrics.store_query_cnt++; - if( FD_UNLIKELY( !store_fec && !reasm_fec->is_leader ) ) { - /* The only case in which a FEC is not found in the store is either - if the FEC is from our own leader block or after repair has - notified is if the FEC was on a minority fork that has already - been published away. In this case we abandon the entire slice - because it is no longer relevant. If the FEC is from our own - leader block, process the FEC so we can unbecome leader. */ - ctx->metrics.store_query_missing_cnt++; - FD_BASE58_ENCODE_32_BYTES( reasm_fec->key.key, key_b58 ); - FD_LOG_WARNING(( "store fec for slot: %lu is on minority fork already pruned by publish. abandoning slice. root: %lu. pruned merkle: %s", reasm_fec->slot, ctx->consensus_root_slot, key_b58 )); - fd_histf_sample( ctx->metrics.store_query_work, (ulong)( fd_tickcount() - work ) ); - return 1; - } - - long now = fd_clock_tile_now( ctx->clock ); - - /* A leader FEC arriving after its slot was aborted (or after a later - leadership began) has no bank to bind to; drop it. */ - if( FD_UNLIKELY( reasm_fec->is_leader && - ( !ctx->leader_bank || ctx->leader_bank->f.slot!=reasm_fec->slot ) ) ) { - return 0; - } - - /* Assign parent bank idx + seq no to the FEC */ - reasm_fec->parent_bank_idx = fd_reasm_parent( ctx->reasm, reasm_fec )->bank_idx; - fd_bank_t * parent_bank = fd_banks_bank_query( ctx->banks, reasm_fec->parent_bank_idx ); - - if( FD_UNLIKELY( reasm_fec->fec_set_idx==0U ) ) { - /* Provision new bank if not leader. Assign bank idx and seq no - to the FEC. Remove stale block id map entry if any and update - pool element. */ - fd_bank_t * bank = reasm_fec->is_leader ? ctx->leader_bank : fd_banks_new_bank( ctx->banks, reasm_fec->parent_bank_idx, now, 0 ); - - if( FD_UNLIKELY( reasm_fec->is_leader && ctx->leader_stats.slot==reasm_fec->slot ) ) { - ctx->leader_stats.first_fec_returned_nanos = now; - } - - reasm_fec->bank_idx = (uint)bank->idx; - reasm_fec->bank_seq = bank->bank_seq; - - fd_block_id_ele_t * block_id_ele = &ctx->block_id_arr[ reasm_fec->bank_idx ]; - if( FD_LIKELY( fd_block_id_map_ele_query( ctx->block_id_map, &block_id_ele->latest_mr, NULL, ctx->block_id_arr )==block_id_ele ) ) { - FD_TEST( fd_block_id_map_ele_remove( ctx->block_id_map, &block_id_ele->latest_mr, NULL, ctx->block_id_arr ) ); - } - block_id_ele->block_id_seen = 0; - block_id_ele->slot = reasm_fec->slot; - block_id_ele->bank_seq = bank->bank_seq; - block_id_ele->latest_fec_idx = 0U; - block_id_ele->latest_mr = reasm_fec->key; - block_id_ele->fec_cnt = 0U; - } else { /* FEC for the middle or end of a block */ - /* Assign bank idx + seqno to the FEC. Update block id pool ele. */ - reasm_fec->bank_idx = reasm_fec->parent_bank_idx; - reasm_fec->bank_seq = parent_bank->bank_seq; - - FD_TEST( reasm_fec->bank_idx!=UINT_MAX ); - - fd_block_id_ele_t * block_id_ele = &ctx->block_id_arr[ reasm_fec->bank_idx ]; - block_id_ele->latest_fec_idx = reasm_fec->fec_set_idx; - block_id_ele->latest_mr = reasm_fec->key; - } - - fd_block_id_ele_t * block_id_ele = &ctx->block_id_arr[ reasm_fec->bank_idx ]; - if( FD_UNLIKELY( ctx->report_runtime_diffs && block_id_ele->fec_cntfec_chain[ reasm_fec->bank_idx*FD_FEC_BLK_MAX + block_id_ele->fec_cnt ] = reasm_fec->key; - } - block_id_ele->fec_cnt++; - - /* If the FEC set is a slot complete, this means we have finally seen - the block id (block's last mr). */ - if( FD_UNLIKELY( reasm_fec->slot_complete ) ) { - block_id_ele->block_id_seen = 1; - block_id_ele->latest_mr = reasm_fec->key; - block_id_ele->latest_fec_idx = reasm_fec->fec_set_idx; - /* If we are re-replaying a block, we want to remove the first - version of the block that we have presumably evicted. */ - if( FD_UNLIKELY( fd_block_id_map_ele_remove( ctx->block_id_map, &block_id_ele->latest_mr, NULL, ctx->block_id_arr ) ) ) { - FD_LOG_DEBUG(( "finished re-replaying evicted bank (slot=%lu, bank_idx=%u)", reasm_fec->slot, reasm_fec->bank_idx )); - } - FD_TEST( fd_block_id_map_ele_insert( ctx->block_id_map, block_id_ele, ctx->block_id_arr ) ); - } - - /* For leader FECs, don't insert the FEC into the scheduler. */ - if( FD_UNLIKELY( reasm_fec->is_leader ) ) { - return 0; - } - - /* Forks form a partial ordering over FEC sets. The Repair tile - delivers FEC sets in-order per fork, but FEC set ordering across - forks is arbitrary */ - fd_sched_fec_t sched_fec[ 1 ]; - -# if DEBUG_LOGGING - FD_BASE58_ENCODE_32_BYTES( reasm_fec->key.key, key_b58 ); - FD_BASE58_ENCODE_32_BYTES( reasm_fec->cmr.key, cmr_b58 ); - FD_LOG_INFO(( "replay processing FEC set for slot %lu fec_set_idx %u, mr %s cmr %s", reasm_fec->slot, reasm_fec->fec_set_idx, key_b58, cmr_b58 )); -# endif - - sched_fec->shred_cnt = reasm_fec->data_cnt; - sched_fec->is_last_in_batch = !!reasm_fec->data_complete; - sched_fec->is_last_in_block = !!reasm_fec->slot_complete; - sched_fec->bank_idx = reasm_fec->bank_idx; - sched_fec->parent_bank_idx = reasm_fec->parent_bank_idx; - sched_fec->slot = reasm_fec->slot; - sched_fec->parent_slot = reasm_fec->slot - reasm_fec->parent_off; - sched_fec->is_first_in_block = reasm_fec->fec_set_idx==0U; - sched_fec->fec = store_fec; - fd_store_fec_data_view_t data_view[ 1 ]; - FD_TEST( !fd_store_fec_data_view( ctx->store, ctx->store_disk_fd, store_fec, data_view ) ); - - sched_fec->data = data_view->data; - sched_fec->completed_ns = (long)reasm_fec->fec_completed_ts_nanos; - sched_fec->alut_ctx->fork_id = fd_banks_bank_query( ctx->banks, ctx->published_root_bank_idx )->accdb_fork_id; - sched_fec->alut_ctx->accdb = ctx->accdb; - sched_fec->alut_ctx->els = ctx->published_root_slot; - - fd_bank_t * bank = fd_banks_bank_query( ctx->banks, sched_fec->bank_idx ); - if( sched_fec->is_first_in_block ) { - bank->refcnt++; - FD_LOG_DEBUG(( "bank (idx=%lu, slot=%lu) refcnt incremented to %lu for sched", bank->idx, sched_fec->slot, bank->refcnt )); - } - - int ingested = fd_sched_fec_ingest( ctx->sched, sched_fec ); - fd_store_fec_data_view_release( ctx->store, data_view ); - - if( FD_UNLIKELY( !ingested ) ) { - int dr = sched_block_dead_reason_to_event( ctx, sched_fec->bank_idx ); - int ar = dr==FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_NOT_DEAD ? FD_EVENT_BLOCK_COMPLETED_ABANDONED_REASON_PRUNED - : FD_EVENT_BLOCK_COMPLETED_ABANDONED_REASON_NOT_ABANDONED; - mark_bank_dead( ctx, stem, sched_fec->bank_idx, dr, ar, NULL ); - return 1; - } - - fd_histf_sample( ctx->metrics.store_query_work, (ulong)( fd_tickcount() - work ) ); - return 0; -} - -static void -backfill_fec_sets( fd_replay_tile_t * ctx, - fd_stem_context_t * stem, - fd_reasm_fec_t * reasm_fec ) { - fd_reasm_fec_t * parent = fd_reasm_parent( ctx->reasm, reasm_fec ); - FD_TEST( !!parent ); - - fd_reasm_fec_t ** path = ctx->backfill_path; - ulong path_max = ctx->max_shreds_per_block/FD_FEC_SHRED_CNT; - ulong path_cnt = 0UL; - ulong path_slot = reasm_fec->slot; - - /* Walk backward from the candidate FEC until we find one with an - associated bank that we consider 'valid'. A FEC is considered - valid to backfill off of if the bank matches the seq we expect and - if its latest mr matches. We must check the latest MR in the case - of equivocation. */ - fd_bank_t * base_bank = NULL; - fd_reasm_fec_t * base_fec = NULL; - for( fd_reasm_fec_t * curr = reasm_fec;; ) { - fd_bank_t * curr_bank = curr->bank_idx==UINT_MAX ? NULL : fd_banks_bank_query( ctx->banks, curr->bank_idx ); - fd_block_id_ele_t * block_id_ele = curr_bank ? &ctx->block_id_arr[ curr_bank->idx ] : NULL; - if( FD_LIKELY( curr_bank && - curr_bank->bank_seq==curr->bank_seq && - curr_bank->state!=FD_BANK_STATE_PRUNABLE && - block_id_ele->bank_seq==curr->bank_seq && - fd_hash_eq( &block_id_ele->latest_mr, &curr->key ) ) ) { base_bank = curr_bank; base_fec = curr; break; } - - if( FD_UNLIKELY( curr->slot!=path_slot ) ) { - path_cnt = 0UL; - path_slot = curr->slot; - } - - FD_TEST( path_cntreasm, curr ); - FD_TEST( curr ); - } - - if( FD_UNLIKELY( base_bank->state==FD_BANK_STATE_DEAD ) ) { - uchar bank_dead = fd_uchar_if( base_fec->bank_dead==2U, 2U, 1U ); - for( ulong i=0UL; ibank_dead = bank_dead; - path[ i ]->dead_reported = 0UL; /* new version: no row yet */ - } - reasm_fec->bank_dead = bank_dead; - if( FD_UNLIKELY( reasm_fec->slot_complete ) ) { - publish_slot_dead( ctx, stem, reasm_fec->slot, &reasm_fec->key, NULL ); - int abandoned = bank_dead==2U; - report_block_incomplete( ctx, reasm_fec->slot, &reasm_fec->key, NULL, - abandoned ? FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_NOT_DEAD : FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_PARENT_DEAD, - abandoned ? FD_EVENT_BLOCK_COMPLETED_ABANDONED_REASON_PRUNED : FD_EVENT_BLOCK_COMPLETED_ABANDONED_REASON_NOT_ABANDONED ); - reasm_fec->dead_reported = 1UL; - } - return; - } - - /* Now that we have queued up the potential path of FECs to backfill, - ingest as much as sched can allow. */ - fd_reasm_fec_t * first = path[ path_cnt-1UL ]; - fd_reasm_fec_t * last = path[ 0 ]; - FD_LOG_DEBUG(( "backfilling FEC sets for slot %lu from fec_set_idx %u to fec_set_idx %u", first->slot, first->fec_set_idx, last->fec_set_idx )); - - ulong sched_capacity = fd_sched_can_ingest_cnt( ctx->sched ); - ulong path_idx_min = path_cnt - fd_ulong_min( sched_capacity, path_cnt ); - for( ulong i=path_cnt; i>path_idx_min; i-- ) { - if( FD_UNLIKELY( insert_fec_set( ctx, stem, path[ i-1UL ] ) ) ) return; - } -} - -static void -process_fec_set( fd_replay_tile_t * ctx, - fd_stem_context_t * stem, - fd_reasm_fec_t * reasm_fec ) { - - fd_reasm_fec_t * parent = fd_reasm_parent( ctx->reasm, reasm_fec ); - if( FD_UNLIKELY( parent->bank_dead ) ) { - /* Inherit the dead flag from the parent (1: dead lineage, 2: - abandoned lineage). If a dead slot is completed, we publish the - slot as dead. Don't insert FECs for dead slots. */ - reasm_fec->bank_dead = parent->bank_dead; - reasm_fec->dead_reported = ( parent->slot==reasm_fec->slot && reasm_fec->xid_next==UINT_MAX ) - ? parent->dead_reported : 0UL; - if( FD_UNLIKELY( reasm_fec->slot_complete ) ) { - publish_slot_dead( ctx, stem, reasm_fec->slot, &reasm_fec->key, NULL ); - if( !reasm_fec->dead_reported ) { - int abandoned = reasm_fec->bank_dead==2UL; - report_block_incomplete( ctx, reasm_fec->slot, &reasm_fec->key, NULL, - abandoned ? FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_NOT_DEAD : FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_PARENT_DEAD, - abandoned ? FD_EVENT_BLOCK_COMPLETED_ABANDONED_REASON_PRUNED : FD_EVENT_BLOCK_COMPLETED_ABANDONED_REASON_NOT_ABANDONED ); - } - } - FD_LOG_DEBUG(( "dropping FEC set (slot=%lu, fec_set_idx=%u) because parent bank is marked dead", reasm_fec->slot, reasm_fec->fec_set_idx )); - return; - } - - /* An invariant from reasm is that if we receive a FEC set that is - both with eqvoc and confirmed set, we know that we must replay the - slot associated with this FEC. equivocation when fec_set_idx == 0 - gets handled cleanly. */ - int eqvoc_detected = reasm_fec->fec_set_idx!=0 && (reasm_fec->eqvoc && !parent->eqvoc); - if( FD_UNLIKELY( eqvoc_detected ) ) FD_TEST( reasm_fec->confirmed && parent->confirmed ); - - /* We can detect if a bank has not replayed if the bank index tagged - to the FEC set is no longer valid or the bank sequence number for - the same bank is different (the bank has been recycled). This is - either due to the parent bank being evicted, or in reasm, the - parent is marked eqvoc (and not replayed), but the child gets - confirmed and delivered. */ - fd_bank_t * parent_fec_bank = parent->bank_idx==UINT_MAX ? NULL : fd_banks_bank_query( ctx->banks, parent->bank_idx ); - int parent_bank_invalid = !parent_fec_bank || parent_fec_bank->bank_seq!=parent->bank_seq; - - /* If the upcoming FEC is either the start of an equivocating chain, - chains off of a bank that was evicted, OR is the child of an - equivocating chain whose parent was gated from getting replayed, we - must backfill any FECs into the scheduler. This backfill must - start from a FEC with fec_set_idx==0 with a parent FEC - corresponding to a valid bank. */ - if( FD_LIKELY( !parent_bank_invalid && !eqvoc_detected ) ) { - insert_fec_set( ctx, stem, reasm_fec ); - } else { - backfill_fec_sets( ctx, stem, reasm_fec ); - } -} - static int try_notify_consensus_root( fd_replay_tile_t * ctx, fd_stem_context_t * stem ) { @@ -3989,8 +3216,6 @@ try_advance_published_root( fd_replay_tile_t * ctx, if( FD_UNLIKELY( advanceable_root_idx>=ctx->block_id_len ) ) { FD_LOG_CRIT(( "invariant violation: advanceable root ele out of bounds [0, %lu) index %lu", ctx->block_id_len, advanceable_root_idx )); } - fd_block_id_ele_t * advanceable_root_ele = &ctx->block_id_arr[ advanceable_root_idx ]; - /* Telemetry: banks still mid-replay (started but never completed) on forks not descending from the new root are about to be cancelled by fd_banks_advance_root without ever emitting a block_completed row. @@ -4006,7 +3231,7 @@ try_advance_published_root( fd_replay_tile_t * ctx, if( FD_LIKELY( s!=advanceable_root_idx ) ) { if( FD_UNLIKELY( b->state==FD_BANK_STATE_INIT || b->state==FD_BANK_STATE_REPLAYABLE ) ) { fd_block_id_ele_t * ele = &ctx->block_id_arr[ s ]; - report_block_incomplete( ctx, ele->slot, ctx->alpenglow && ele->block_id_seen ? &ele->dmr : &ele->latest_mr, b, FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_NOT_DEAD, FD_EVENT_BLOCK_COMPLETED_ABANDONED_REASON_PRUNED ); + report_block_incomplete( ctx, ele->slot, ele->block_id_seen ? &ele->dmr : &ele->latest_mr, b, FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_NOT_DEAD, FD_EVENT_BLOCK_COMPLETED_ABANDONED_REASON_PRUNED ); } if( FD_LIKELY( b->child_idx!=ULONG_MAX ) ) { s = b->child_idx; b = fd_banks_bank_query( ctx->banks, s ); continue; } } @@ -4024,7 +3249,6 @@ try_advance_published_root( fd_replay_tile_t * ctx, fd_sched_advance_root( ctx->sched, advanceable_root_idx ); fd_banks_advance_root( ctx->banks, advanceable_root_idx ); if( FD_UNLIKELY( root_epoch_changed ) ) refresh_vote_account_staked( ctx, bank ); - if( ctx->reasm ) fd_reasm_publish( ctx->reasm, &advanceable_root_ele->latest_mr, ctx->store, ctx->map_join ); for( ulong b=0UL; bmax_live_slots; b++ ) { if( FD_UNLIKELY( ctx->timing_slot_of_bank[ b ]!=fd_timing_slot_pool_idx_null( ctx->timing_slot_pool ) && !fd_banks_bank_query( ctx->banks, b ) ) ) timing_slot_release( ctx, b ); @@ -4076,7 +3300,7 @@ try_prune_sched( fd_replay_tile_t * ctx ) { already reported a completed row. */ if( FD_UNLIKELY( bank->state==FD_BANK_STATE_PRUNABLE && !bank->block_completed_nanos ) ) { fd_block_id_ele_t * ele = &ctx->block_id_arr[ bank_idx ]; - report_block_incomplete( ctx, ele->slot, ctx->alpenglow && ele->block_id_seen ? &ele->dmr : &ele->latest_mr, bank, + report_block_incomplete( ctx, ele->slot, ele->block_id_seen ? &ele->dmr : &ele->latest_mr, bank, FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_NOT_DEAD, FD_EVENT_BLOCK_COMPLETED_ABANDONED_REASON_EVICTED ); } @@ -4122,149 +3346,6 @@ try_prune_bank( fd_replay_tile_t * ctx ) { } } -static int -try_evict_reasm( fd_replay_tile_t * ctx, - fd_stem_context_t * stem ) { - - /* if reasm_evicted is set, publish starting from reasm_evicted down - to the leaf node to repair so repair can re-request for it. - reasm_evicted gets set when reasm tries to insert a FEC and there - is no remaining capacity. */ - if( FD_LIKELY( !ctx->reasm_evicted ) ) return 0; - - /* Publish a notification to the repair tile that the Replay tile no - longer has the FEC that was evicted. This will make sure that the - repair tile will re-request the FEC if it eventually gets - confirmed so that Replay can still make forward progress. */ - fd_replay_fec_evicted_t evicted = (fd_replay_fec_evicted_t){ .mr = ctx->reasm_evicted->key, .slot = ctx->reasm_evicted->slot, .fec_set_idx = ctx->reasm_evicted->fec_set_idx, .bank_idx = ctx->reasm_evicted->bank_idx }; - fd_memcpy( fd_chunk_to_laddr( ctx->replay_out->mem, ctx->replay_out->chunk ), &evicted, sizeof(fd_replay_fec_evicted_t) ); - publish_replay_out( ctx, stem, REPLAY_SIG_REASM_EVICTED, sizeof(fd_replay_fec_evicted_t) ); - - /* eviction policy only evicts chains of nodes until there is a - fork, so guaranteed that the evict path is always the left-child - TODO: This should be abstracted away. */ - fd_reasm_pool_release( ctx->reasm, ctx->reasm_evicted ); - ctx->reasm_evicted = fd_reasm_child( ctx->reasm, ctx->reasm_evicted ); /* indexes into pool, safe to use */ - return 1; -} - -static int -try_process_leader_fec( fd_replay_tile_t * ctx, - fd_stem_context_t * stem ) { - if( FD_LIKELY( !ctx->is_leader ) ) return 0; - - fd_bank_t * leader_bank = ctx->leader_bank; - FD_TEST( leader_bank ); - fd_block_id_ele_t * leader_id = &ctx->block_id_arr[ leader_bank->idx ]; - FD_TEST( leader_id->bank_seq==leader_bank->bank_seq ); - if( FD_UNLIKELY( leader_id->block_id_seen ) ) return 0; - - /* Follow only the next link in our own chain. Searching the entire - delivery queue here would be expensive precisely when replay is - behind. Before FEC 0, the chain starts at the frozen parent bank. */ - int first = fd_hash_check_zero( &leader_id->latest_mr ); - ulong parent_idx = first ? leader_bank->parent_idx : leader_bank->idx; - fd_bank_t * parent_bank = fd_banks_bank_query( ctx->banks, parent_idx ); - FD_TEST( parent_bank ); - FD_TEST( parent_bank->state!=FD_BANK_STATE_PRUNABLE ); - FD_TEST( parent_bank->state!=FD_BANK_STATE_DEAD ); - - fd_block_id_ele_t * parent_id = &ctx->block_id_arr[ parent_idx ]; - FD_TEST( parent_id->bank_seq==parent_bank->bank_seq ); - fd_reasm_fec_t * parent = fd_reasm_query( ctx->reasm, &parent_id->latest_mr ); - FD_TEST( parent ); - FD_TEST( !parent->bank_dead ); - FD_TEST( parent->bank_idx==parent_idx ); - FD_TEST( parent->bank_seq==parent_bank->bank_seq ); - - for( fd_reasm_fec_t * fec=fd_reasm_child( ctx->reasm, parent ); fec; fec=fd_reasm_sibling( ctx->reasm, fec ) ) { - if( FD_LIKELY( !fec->is_leader || fec->slot!=leader_bank->f.slot ) ) continue; - FD_TEST( (fec->fec_set_idx==0U)==first ); - if( FD_UNLIKELY( !first && fec->eqvoc && !parent->eqvoc ) ) continue; - if( FD_UNLIKELY( !fec->in_out || fec->popped || (fec->eqvoc && !fec->confirmed) ) ) continue; - FD_TEST( fd_reasm_pop_fec( ctx->reasm, fec )==fec ); - - ctx->metrics.reasm_latest_slot = fec->slot; - ctx->metrics.reasm_latest_fec_idx = fec->fec_set_idx; - - /* The leader bank already exists and these FECs never enter sched. - Neither bank exhaustion nor scheduler capacity may delay the - block id needed to finish leadership and release that bank. */ - FD_TEST( !insert_fec_set( ctx, stem, fec ) ); - return 1; - } - return 0; -} - -static int -try_process_fec( fd_replay_tile_t * ctx, - fd_stem_context_t * stem ) { - - /* If the reassembler has a fec that is ready, we should process it - and pass it to the scheduler. - - We would also like to pace FEC ingestion such that we keep the exec - tiles busy. If there's a pending frag from one of the exec tiles, - we would like to know about that asap, because that could unblock - dispatching. So we ingest FEC sets only if we are sure that there - are no more exec tile notifications to process. This delays FEC - ingestion just enough so as to keep the exec tiles as busy as we - can, and prevents us from being stuck ingesting a backlog of FEC - sets, especially when there is a pending completion notification - about a single-transaction chokepoint in the replay dispatcher DAG. - Except that when we are leader or the reasm buffer is getting full, - we prioritize FEC processing. In the leader case, this is so we - can get to the leader FEC sets asap and freeze the leader bank on - time. In the reasm full case, this is so we don't prematurely - trigger eviction. */ - if( FD_UNLIKELY( try_process_leader_fec( ctx, stem ) ) ) { - ctx->execrp_idle_cnt = 0UL; - return 1; - } - - int evict_banks = 0; - if( FD_LIKELY( (ctx->execrp_idle_cnt>=2UL*ctx->in_cnt || ctx->is_leader || fd_reasm_free( ctx->reasm )<=1UL) && - can_process_fec( ctx, &evict_banks ) ) ) { - fd_reasm_fec_t * fec = fd_reasm_pop( ctx->reasm ); - process_fec_set( ctx, stem, fec ); - ctx->execrp_idle_cnt = 0UL; - return 1; - } - - /* If we need to evict banks, gather one evictable bank. The bank is - marked prunable by fd_banks_get_evictable_bank and pruned once refs - drain. */ - if( FD_UNLIKELY( evict_banks ) ) { - ulong evictable_bank_idx = fd_banks_get_evictable_bank( ctx->banks, ctx->notified_root_bank ); - if( FD_UNLIKELY( evictable_bank_idx==ULONG_MAX ) ) { - FD_LOG_DEBUG(( "replay has no banks to mark as prunable, it's possible that there is one bank already marked as prunable" )); - return 0; - } - - FD_LOG_WARNING(( "banks full, evicting bank (idx=%lu)", evictable_bank_idx )); - - timing_slot_release( ctx, evictable_bank_idx ); - - if( FD_UNLIKELY( fd_sched_block_is_discarded( ctx->sched, evictable_bank_idx ) ) ) { - fd_block_id_ele_t * ele = &ctx->block_id_arr[ evictable_bank_idx ]; - report_block_incomplete( ctx, ele->slot, ctx->alpenglow && ele->block_id_seen ? &ele->dmr : &ele->latest_mr, fd_banks_bank_query( ctx->banks, evictable_bank_idx ), - FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_NOT_DEAD, FD_EVENT_BLOCK_COMPLETED_ABANDONED_REASON_PRUNED ); - } - - /* Send a notification to other tiles to drop a reference to the - evictable bank. The RPC tile is the only tile which holds onto - non-rooted banks, non-transiently. */ - fd_replay_drop_bank_ref_t * msg = fd_chunk_to_laddr( ctx->replay_out->mem, ctx->replay_out->chunk ); - fd_sched_block_abandon( ctx->sched, evictable_bank_idx, FD_SCHED_ABANDON_DISCARDED ); - msg->bank_idx = evictable_bank_idx; - publish_replay_out( ctx, stem, REPLAY_SIG_DROP_BANK_REF, sizeof(fd_replay_drop_bank_ref_t) ); - - return 1; - } - - return 0; -} - static void after_credit( fd_replay_tile_t * ctx, fd_stem_context_t * stem, @@ -4288,12 +3369,6 @@ after_credit( fd_replay_tile_t * ctx, WARNING: The ordering here is VERY load bearing and it should not be changed without extreme caution. */ - if( FD_UNLIKELY( try_evict_reasm( ctx, stem ) ) ) { - *charge_busy = 1; - *opt_poll_in = 0; - return; - } - if( FD_UNLIKELY( try_prune_sched( ctx ) ) ) { *charge_busy = 1; *opt_poll_in = 0; @@ -4312,13 +3387,7 @@ after_credit( fd_replay_tile_t * ctx, return; } - if( FD_UNLIKELY( !ctx->alpenglow && try_become_leader( ctx, stem ) ) ) { - *charge_busy = 1; - *opt_poll_in = 0; - return; - } - - if( FD_UNLIKELY( ctx->alpenglow && try_become_leader_ag( ctx, stem, FD_METRICS_SAMPLE_REASON_LEADER_START, 0UL ) ) ) { + if( FD_UNLIKELY( try_become_leader_ag( ctx, stem, FD_METRICS_SAMPLE_REASON_LEADER_START, 0UL ) ) ) { *charge_busy = 1; *opt_poll_in = 0; return; @@ -4342,23 +3411,9 @@ after_credit( fd_replay_tile_t * ctx, return; } - if( FD_LIKELY( !ctx->alpenglow && try_process_fec( ctx, stem ) ) ) { - *charge_busy = 1; - *opt_poll_in = 0; - return; - } - ctx->execrp_idle_cnt++; } -/* A ready FEC inside the exec-notification pacing window is work the - stem cannot see, spin the window out rather than sleep through it */ - -static int -prevent_park( fd_replay_tile_t * ctx ) { - return !ctx->alpenglow && ctx->execrp_idle_cnt<2UL*ctx->in_cnt && !!fd_reasm_peek( ctx->reasm ); -} - static int before_frag( fd_replay_tile_t * ctx, ulong in_idx, @@ -4520,156 +3575,6 @@ process_exec_task_done( fd_replay_tile_t * ctx, } -static void -process_tower_slot_done( fd_replay_tile_t * ctx, - fd_stem_context_t * stem, - fd_tower_slot_done_t const * msg, - ulong seq ) { - - /* This frag from tower tells us to: - - Release an outstanding refernence on the replayed bank - - Advance the consensus root if one has been supplied - - Update the reset block */ - - fd_bank_t * replay_bank = fd_banks_bank_query( ctx->banks, msg->replay_bank_idx ); - if( FD_UNLIKELY( !replay_bank ) ) FD_LOG_CRIT(( "invariant violation: bank not found for bank index %lu", msg->replay_bank_idx )); - replay_bank->refcnt--; - FD_LOG_DEBUG(( "bank (idx=%lu, slot=%lu) refcnt decremented to %lu for tower", replay_bank->idx, msg->replay_slot, replay_bank->refcnt )); - - if( FD_LIKELY( msg->root_slot!=ULONG_MAX ) ) { - FD_TEST( msg->root_slot>=ctx->consensus_root_slot ); - ctx->consensus_root_slot = msg->root_slot; - ctx->consensus_root = msg->root_block_id; - } - - if( FD_UNLIKELY( fd_hash_eq( &msg->reset_block_id, &ctx->reset_cmr ) ) ) return; - - fd_block_id_ele_t * block_id_ele = fd_block_id_map_ele_query( ctx->block_id_map, &msg->reset_block_id, NULL, ctx->block_id_arr ); - if( FD_UNLIKELY( !block_id_ele ) ) { - FD_LOG_WARNING(( "ignoring reset block update from tower because block has been evicted (slot=%lu)", msg->reset_slot )); - return; - } - fd_bank_t * bank = fd_banks_bank_query( ctx->banks, fd_block_id_ele_get_idx( ctx->block_id_arr, block_id_ele ) ); - if( FD_UNLIKELY( !bank || bank->bank_seq!=block_id_ele->bank_seq || bank->state!=FD_BANK_STATE_FROZEN ) ) { - FD_LOG_WARNING(( "ignoring reset block update from tower because bank has been evicted (slot=%lu)", msg->reset_slot )); - return; - } - - ctx->reset_cmr = msg->reset_block_id; - ctx->reset_slot = msg->reset_slot; - refresh_epoch_info( ctx, bank, msg->reset_slot ); - ctx->reset_timestamp_nanos = fd_clock_tile_now( ctx->clock ); - if( FD_LIKELY( msg->root_slot!=ULONG_MAX ) ) FD_TEST( msg->root_slot<=msg->reset_slot ); - - ulong min_leader_slot = fd_ulong_max( msg->reset_slot+1UL, fd_ulong_if( ctx->highwater_leader_slot==ULONG_MAX, 0UL, ctx->highwater_leader_slot+1UL ) ); - ctx->next_leader_slot = query_next_leader_slot( ctx, min_leader_slot ); - if( FD_LIKELY( ctx->next_leader_slot != ULONG_MAX ) ) { - double slot_duration_ticks = (double)bank->f.slot_params.ns_per_slot_adjusted*ctx->tick_per_ns; - ctx->next_leader_tickcount = (long)((double)(ctx->next_leader_slot-ctx->reset_slot-1UL)*slot_duration_ticks) + fd_tickcount(); - } else { - ctx->next_leader_tickcount = LONG_MAX; - } - - if( FD_LIKELY( ctx->replay_out->idx!=ULONG_MAX ) ) { - fd_poh_reset_t * reset = fd_chunk_to_laddr( ctx->replay_out->mem, ctx->replay_out->chunk ); - - reset->bank_idx = bank->idx; - reset->timestamp = ctx->reset_timestamp_nanos; - reset->completed_slot = ctx->reset_slot; - reset->hashcnt_per_tick = bank->f.slot_params.hashes_per_tick; - reset->ticks_per_slot = bank->f.ticks_per_slot; - reset->tick_duration_ns = bank->f.slot_params.ns_per_slot_adjusted/reset->ticks_per_slot; - - fd_memcpy( reset->completed_cmr, &block_id_ele->latest_mr, sizeof(fd_hash_t) ); - - fd_blockhashes_t const * block_hash_queue = &bank->f.block_hash_queue; - fd_hash_t const * last_hash = fd_blockhashes_peek_last_hash( block_hash_queue ); - FD_TEST( last_hash ); - fd_memcpy( reset->completed_blockhash, last_hash->uc, sizeof(fd_hash_t) ); - - reset->max_microblocks_in_slot = fd_poh_max_microblocks_per_slot( bank->f.ticks_per_slot, reset->hashcnt_per_tick ); - reset->next_leader_slot = ctx->next_leader_slot; - reset->wfs_paused = !ctx->wfs_complete; - - publish_replay_out( ctx, stem, REPLAY_SIG_RESET, sizeof(fd_poh_reset_t) ); - } - - FD_LOG_INFO(( "tower_slot_done(reset_slot=%lu, next_leader_slot=%lu, vote_slot=%lu, replay_slot=%lu, root_slot=%lu, seqno=%lu)", msg->reset_slot, ctx->next_leader_slot, msg->vote_slot, msg->replay_slot, msg->root_slot, seq )); - try_become_leader( ctx, stem ); - - ulong distance = 0UL; - fd_bank_t * parent = bank; - while( parent ) { - if( FD_UNLIKELY( fd_hash_eq( &parent->f.block_id, &ctx->consensus_root ) ) ) break; - parent = fd_banks_get_parent( ctx->banks, parent ); - distance++; - } - - FD_MGAUGE_SET( REPLAY, ROOT_SLOT_DISTANCE, distance ); - -} - -static void -process_fec_complete( fd_replay_tile_t * ctx, - ulong sig, - fd_repair_fec_complete_t * complete_msg ) { - fd_shred_t const * shred = &complete_msg->fec.last_shred_hdr; - - fd_hash_t const * merkle_root = &complete_msg->fec.merkle_root; - fd_hash_t const * chained_merkle_root = &complete_msg->fec.chained_merkle_root; - int is_leader_fec = sig == REPAIR_SIG_FEC_LEADER; - int data_complete = !!( shred->data.flags & FD_SHRED_DATA_FLAG_DATA_COMPLETE ); - int slot_complete = !!( shred->data.flags & FD_SHRED_DATA_FLAG_SLOT_COMPLETE ); - - if( FD_UNLIKELY( sig==REPAIR_SIG_FEC_INVALID ) ) { - /* FEC set detected as invalid based on duplicate confirmations. - Nothing to do except remove from store. If the FEC set is not in - reasm, we can directly remove from store. If the FEC set is in - reasm, then we let reasm_publish handle it. */ - if( FD_LIKELY( !fd_reasm_query( ctx->reasm, merkle_root ) ) ) { - fd_store_remove( ctx->store, ctx->map_join, merkle_root ); - } - return; - } - - /* Track the cluster tip: the highest slot seen in FEC sets from the - network (leader FECs are our own blocks, not evidence of the tip). */ - if( FD_LIKELY( !is_leader_fec && ( ctx->catch_up_max_fec_slot==ULONG_MAX || shred->slot>ctx->catch_up_max_fec_slot ) ) ) { - ctx->catch_up_max_fec_slot = shred->slot; - ctx->catch_up_tip_advance_cnt++; - } - - if( FD_UNLIKELY( shred->slot - shred->data.parent_off == fd_reasm_slot0( ctx->reasm ) && shred->fec_set_idx == 0) ) { - chained_merkle_root = &fd_reasm_root( ctx->reasm )->key; - } - - if( FD_UNLIKELY( fd_reasm_query( ctx->reasm, merkle_root ) ) ) return; - fd_reasm_fec_t * fec = fd_reasm_insert( ctx->reasm, merkle_root, chained_merkle_root, shred->slot, shred->fec_set_idx, shred->data.parent_off, (ushort)(shred->idx - shred->fec_set_idx + 1), data_complete, slot_complete, is_leader_fec, ctx->store, ctx->map_join, &ctx->reasm_evicted ); - - if( FD_UNLIKELY( !fec ) ) { - /* reasm failed to insert. We don't want to just put this back on - the returnable_frag queue because it's unclear whether this FEC - is truly something we want to process. Therefore our best option - is to punt it and "go around." Either the FEC was invalid and - was rejected or reasm_insert populates its last pool element with - the data of the failed insert, so we make sure to publish the - failed insert data to repair in after_credit. */ - fd_store_remove( ctx->store, ctx->map_join, merkle_root ); - return; - } - - fec->fec_completed_ts_nanos = (ulong)complete_msg->metrics.fec_completed_ts_nanos; - if( FD_LIKELY( complete_msg->metrics.stats_valid ) ) { - /* Repair builds these cumulative snapshots from fd_forest_blk_t, - which is keyed by slot rather than block identity. Retain only - the newest valid snapshot for that slot. */ - fd_reception_stats_t * stats = &ctx->reception_stats[ fec->slot % ctx->reception_stats_cnt ]; - stats->slot = fec->slot; - stats->fec_set_idx = fec->fec_set_idx; - stats->metrics.repair = complete_msg->metrics; - } -} - /* Essentially an inlined version of try_process_fec that reads off the dcache directly, instead of the reasm out_queue. All FECs processed by this function must be safe to be forever @@ -4780,7 +3685,6 @@ process_rotor_fec( fd_replay_tile_t * ctx, fd_reception_stats_t * stats = &ctx->reception_stats[ bank->idx ]; stats->bank_seq = bank->bank_seq; - stats->slot = fec->slot; stats->fec_set_idx = fec->fec_set_idx; stats->metrics.rotor = fec->metrics; /* votor_repaired always, the counters only if stats_valid */ @@ -4925,44 +3829,6 @@ maybe_verify_genesis_timestamp( fd_replay_tile_t * ctx ) { ctx->genesis_path, ctx->genesis_timestamp, ctx->expected_genesis_timestamp )); } -static void -process_tower_optimistic_confirmed( fd_replay_tile_t * ctx, - fd_stem_context_t * stem, - fd_tower_slot_confirmed_t const * msg ) { - - fd_block_id_ele_t * block_id_ele = fd_block_id_map_ele_query( ctx->block_id_map, &msg->block_id, NULL, ctx->block_id_arr ); - if( FD_UNLIKELY( !block_id_ele ) ) { - FD_BASE58_ENCODE_32_BYTES( msg->block_id.key, block_id_b58 ); - FD_LOG_WARNING(( "missing bank for confirmed block_id: %s level %d", block_id_b58, msg->level )); - return; - } - - ulong bank_idx = fd_block_id_ele_get_idx( ctx->block_id_arr, block_id_ele ); - fd_bank_t * bank = fd_banks_bank_query( ctx->banks, bank_idx ); - - if( FD_UNLIKELY( !bank || - bank->bank_seq!=block_id_ele->bank_seq || - bank->state!=FD_BANK_STATE_FROZEN ) ) { - FD_BASE58_ENCODE_32_BYTES( msg->block_id.key, block_id_cstr ); - FD_LOG_WARNING(( "failed to query optimistically confirmed bank for block id %s", block_id_cstr )); - return; - } - - if( ctx->rpc_enabled ) { - bank->refcnt++; - FD_LOG_DEBUG(( "bank (idx=%lu, slot=%lu) refcnt incremented to %lu for rpc", bank->idx, bank->f.slot, bank->refcnt )); - } - - fd_replay_oc_advanced_t * replay_msg = fd_chunk_to_laddr( ctx->replay_out->mem, ctx->replay_out->chunk ); - replay_msg->bank_idx = bank_idx; - replay_msg->bank_seq = bank->bank_seq; - replay_msg->slot = msg->slot; - - publish_replay_out( ctx, stem, REPLAY_SIG_OC_ADVANCED, sizeof(fd_replay_oc_advanced_t) ); - - update_metric_balances( ctx, bank ); -} - /* snapmk_start instructs the snapmk tile to start producing a snapshot. */ static void @@ -5236,27 +4102,6 @@ returnable_frag( fd_replay_tile_t * ctx, process_resolv_slot_completed( ctx, exchanged_slot->bank_idx ); break; } - case IN_KIND_TOWER: { - if( FD_LIKELY( sig==FD_TOWER_SIG_SLOT_DONE ) ) { - process_tower_slot_done( ctx, stem, fd_chunk_to_laddr( ctx->in[ in_idx ].mem, chunk ), seq ); - } else if( FD_LIKELY( sig==FD_TOWER_SIG_SLOT_CONFIRMED ) ) { - fd_tower_slot_confirmed_t const * msg = fd_chunk_to_laddr( ctx->in[ in_idx ].mem, chunk ); - if( msg->level==FD_TOWER_SLOT_CONFIRMED_OPTIMISTIC && !msg->fwd ) process_tower_optimistic_confirmed( ctx, stem, msg ); - if( msg->level==FD_TOWER_SLOT_CONFIRMED_DUPLICATE ) fd_reasm_confirm( ctx->reasm, &msg->block_id ); - } else if( FD_LIKELY( sig==FD_TOWER_SIG_SLOT_IGNORED ) ) { - fd_tower_slot_ignored_t const * msg = fd_chunk_to_laddr( ctx->in[ in_idx ].mem, chunk ); - fd_tower_slot_done_t ignored = { - .replay_slot = msg->slot, - .replay_bank_idx = msg->bank_idx, - .vote_slot = ULONG_MAX, - .reset_slot = ctx->reset_slot, /* Use most recent reset slot */ - .reset_block_id = ctx->reset_cmr, - .root_slot = ULONG_MAX - }; - process_tower_slot_done( ctx, stem, &ignored, seq ); - } - break; - } case IN_KIND_VOTOR: { if( FD_UNLIKELY( sig==FD_VOTOR_SIG_LEADER ) ) { fd_votor_leader_t const * leader = fd_chunk_to_laddr( ctx->in[ in_idx ].mem, chunk ); @@ -5288,13 +4133,6 @@ returnable_frag( fd_replay_tile_t * ctx, break; } case IN_KIND_REPAIR: { - /* Store and reasm follow the invariant that any FEC in the - shred->out link, repair->out link, or reasm must be present in - store. If any FEC is rejected at this point, it must be - removed from store. See topology.c for more details. */ - if( FD_UNLIKELY( sig==REPAIR_SIG_FEC || sig==REPAIR_SIG_FEC_LEADER || sig==REPAIR_SIG_FEC_INVALID ) ) { - process_fec_complete( ctx, sig, fd_chunk_to_laddr( ctx->in[ in_idx ].mem, chunk ) ); - } if( FD_UNLIKELY( sig!=ROTOR_SIG_FEC_REPLAY ) ) break; /* process rotor incoming FECs. 1 to keep frag for retry, returning 0 consumes it. */ @@ -5349,7 +4187,7 @@ returnable_frag( fd_replay_tile_t * ctx, if( FD_UNLIKELY( fd_sched_block_is_discarded( ctx->sched, evictable_bank_idx ) ) ) { fd_block_id_ele_t * ele = &ctx->block_id_arr[ evictable_bank_idx ]; - report_block_incomplete( ctx, ele->slot, ctx->alpenglow && ele->block_id_seen ? &ele->dmr : &ele->latest_mr, fd_banks_bank_query( ctx->banks, evictable_bank_idx ), + report_block_incomplete( ctx, ele->slot, ele->block_id_seen ? &ele->dmr : &ele->latest_mr, fd_banks_bank_query( ctx->banks, evictable_bank_idx ), FD_EVENT_BLOCK_COMPLETED_DEAD_REASON_NOT_DEAD, FD_EVENT_BLOCK_COMPLETED_ABANDONED_REASON_PRUNED ); } @@ -5383,7 +4221,7 @@ returnable_frag( fd_replay_tile_t * ctx, on_snapshot_message). */ ctx->next_leader_tickcount = LONG_MAX; if( FD_LIKELY( ctx->next_leader_slot!=ULONG_MAX ) ) { - fd_block_id_ele_t * block_id_ele = fd_block_id_ele_query( ctx, ctx->alpenglow ? &ctx->reset_dmr : &ctx->reset_cmr, ctx->reset_slot ); + fd_block_id_ele_t * block_id_ele = fd_block_id_ele_query( ctx, &ctx->reset_dmr, ctx->reset_slot ); if( FD_LIKELY( block_id_ele ) ) { fd_bank_t * reset_bank = fd_banks_bank_query( ctx->banks, fd_block_id_ele_get_idx( ctx->block_id_arr, block_id_ele ) ); if( FD_LIKELY( reset_bank && reset_bank->bank_seq==block_id_ele->bank_seq && reset_bank->state!=FD_BANK_STATE_PRUNABLE ) ) { @@ -5467,7 +4305,7 @@ privileged_init( fd_topo_t const * topo, ctx->metrics.voted_slot = ULONG_MAX; - ctx->has_vote_account = tile->replay.alpenglow && !!tile->replay.vote_account_path[ 0 ]; + ctx->has_vote_account = !!tile->replay.vote_account_path[ 0 ]; if( FD_LIKELY( ctx->has_vote_account ) ) { if( FD_UNLIKELY( !fd_base58_decode_32( tile->replay.vote_account_path, ctx->vote_account->uc ) ) ) { uchar const * vote_key = fd_keyload_load( tile->replay.vote_account_path, /* pubkey only: */ 1 ); @@ -5489,9 +4327,7 @@ privileged_init( fd_topo_t const * topo, FD_TEST( fd_rng_secure( &ctx->rng_seed, sizeof(ctx->rng_seed) ) ); FD_TEST( fd_rng_secure( &ctx->blockhash_seed, sizeof(ulong) ) ); - FD_TEST( fd_rng_secure( &ctx->reasm_seed, sizeof(ulong) ) ); FD_TEST( fd_rng_secure( &ctx->vote_tracker_seed, sizeof(ulong) ) ); - FD_TEST( fd_rng_secure( &ctx->block_id_map_seed, sizeof(ulong) ) ); FD_TEST( fd_rng_secure( &ctx->ag_block_id_map_seed, sizeof(ulong) ) ); FD_TEST( fd_rng_secure( &ctx->initial_block_id, sizeof(fd_hash_t) ) ); FD_TEST( fd_rng_secure( &ctx->runtime_stack_seed, sizeof(ulong) ) ); @@ -5511,9 +4347,8 @@ static void unprivileged_init( fd_topo_t const * topo, fd_topo_tile_t const * tile ) { void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id ); - void * reasm_mem = NULL; void * block_id_map_mem = NULL; - ulong chain_cnt = fd_block_id_map_chain_cnt_est( tile->replay.max_live_slots ); + ulong chain_cnt = fd_ag_block_id_map_chain_cnt_est( tile->replay.max_live_slots ); FD_SCRATCH_ALLOC_INIT( l, scratch ); fd_replay_tile_t * ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_replay_tile_t), sizeof(fd_replay_tile_t) ); @@ -5525,16 +4360,9 @@ unprivileged_init( fd_topo_t const * topo, FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_hash_t), sizeof(fd_hash_t) * FD_FEC_BLK_MAX * tile->replay.max_live_slots ) : NULL; void * slot_diffs_mem = tile->replay.report_runtime_diffs ? FD_SCRATCH_ALLOC_APPEND( l, 8UL, FD_EVENT_RUNTIME_SLOT_DIFFS_FOOTPRINT * tile->replay.max_live_slots ) : NULL; - if( !tile->replay.alpenglow ) { - block_id_map_mem = FD_SCRATCH_ALLOC_APPEND( l, fd_block_id_map_align(), fd_block_id_map_footprint( chain_cnt ) ); - } else { block_id_map_mem = FD_SCRATCH_ALLOC_APPEND( l, fd_ag_block_id_map_align(), fd_ag_block_id_map_footprint( chain_cnt ) ); - } void * _txncache = FD_SCRATCH_ALLOC_APPEND( l, fd_txncache_align(), fd_txncache_footprint( tile->replay.max_live_slots ) ); void * _accdb = FD_SCRATCH_ALLOC_APPEND( l, fd_accdb_align(), fd_accdb_footprint( tile->replay.max_live_slots, 0 ) ); - if( !tile->replay.alpenglow ) { - reasm_mem = FD_SCRATCH_ALLOC_APPEND( l, fd_reasm_align(), fd_reasm_footprint( tile->replay.fec_max ) ); - } void * recp_stats_mem = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_reception_stats_t), sizeof(fd_reception_stats_t)*tile->replay.max_live_slots ); void * sched_mem = FD_SCRATCH_ALLOC_APPEND( l, fd_sched_align(), fd_sched_footprint( tile->replay.sched_depth, tile->replay.max_live_slots, tile->replay.max_shreds_per_block, tile->replay.max_txn_per_slot ) ); void * vote_tracker_mem = FD_SCRATCH_ALLOC_APPEND( l, fd_vote_tracker_align(), fd_vote_tracker_footprint() ); @@ -5544,7 +4372,6 @@ unprivileged_init( fd_topo_t const * topo, void * timing_pool_mem = FD_SCRATCH_ALLOC_APPEND( l, fd_timing_slot_pool_align(), fd_timing_slot_pool_footprint( FD_REPLAY_TXN_TIMING_SLOTS ) ); void * timing_rec_mem = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_replay_txn_timing_t), FD_REPLAY_TXN_TIMING_SLOTS*tile->replay.max_txn_per_slot*sizeof(fd_replay_txn_timing_t) ); void * timing_of_bank_mem = FD_SCRATCH_ALLOC_APPEND( l, alignof(ulong), tile->replay.max_live_slots*sizeof(ulong) ); - void * backfill_path_mem = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_reasm_fec_t *), (tile->replay.max_shreds_per_block/FD_FEC_SHRED_CNT)*sizeof(fd_reasm_fec_t *) ); void * block_dump_ctx = NULL; if( FD_UNLIKELY( tile->replay.dump_block_to_pb ) ) { block_dump_ctx = FD_SCRATCH_ALLOC_APPEND( l, fd_block_dump_context_align(), fd_block_dump_context_footprint() ); @@ -5674,7 +4501,6 @@ unprivileged_init( fd_topo_t const * topo, ctx->timing_rec = timing_rec_mem; ctx->timing_slot_of_bank = timing_of_bank_mem; for( ulong i=0UL; ireplay.max_live_slots; i++ ) ctx->timing_slot_of_bank[ i ] = fd_timing_slot_pool_idx_null( ctx->timing_slot_pool ); - ctx->backfill_path = backfill_path_mem; ctx->dump_proto_ctx = NULL; if( FD_UNLIKELY( strcmp( "", tile->replay.dump_proto_dir ) ) ) { @@ -5702,23 +4528,14 @@ unprivileged_init( fd_topo_t const * topo, FD_TEST( fd_rng_new( ctx->rng, ctx->rng_seed, 0UL ) ); - if( !tile->replay.alpenglow ) { - ctx->reasm = fd_reasm_join( fd_reasm_new( reasm_mem, tile->replay.fec_max, ctx->reasm_seed ) ); - } else { - ctx->reasm = NULL; - } ctx->reception_stats = recp_stats_mem; ctx->reception_stats_cnt = tile->replay.max_live_slots; FD_TEST( ctx->reception_stats_cnt ); memset( ctx->reception_stats, 0, sizeof(fd_reception_stats_t)*ctx->reception_stats_cnt ); - for( ulong i=0UL; ireception_stats_cnt; i++ ) ctx->reception_stats[ i ].slot = ULONG_MAX; - ctx->reasm_evicted = NULL; ctx->leader_stats.slot = ULONG_MAX; - ctx->alpenglow = tile->replay.alpenglow; - ctx->sched = fd_sched_join( fd_sched_new( sched_mem, ctx->rng, tile->replay.sched_depth, tile->replay.max_live_slots, ctx->max_shreds_per_block, ctx->max_txn_per_slot, fd_topo_tile_name_cnt( topo, "execrp" ), ctx->alpenglow ) ); + ctx->sched = fd_sched_join( fd_sched_new( sched_mem, ctx->rng, tile->replay.sched_depth, tile->replay.max_live_slots, ctx->max_shreds_per_block, ctx->max_txn_per_slot, fd_topo_tile_name_cnt( topo, "execrp" ), 1 ) ); FD_TEST( ctx->sched ); - FD_TEST( ctx->alpenglow || ctx->reasm ); ctx->in_cnt = tile->in_cnt; ctx->execrp_idle_cnt = 0UL; @@ -5766,7 +4583,6 @@ unprivileged_init( fd_topo_t const * topo, ctx->snapmk.incremental_interval_blocks = 0UL; } ctx->reset_slot = 0UL; - ctx->reset_cmr = ctx->initial_block_id; ctx->reset_dmr = ctx->initial_block_id; ctx->epoch_end_slot = 0UL; ctx->slots_per_epoch = 0UL; @@ -5794,15 +4610,8 @@ unprivileged_init( fd_topo_t const * topo, ctx->fec_chain = (fd_hash_t *)fec_chain_mem; if( FD_UNLIKELY( slot_diffs_mem ) ) fd_event_runtime_slot_diffs_init( slot_diffs_mem, tile->replay.max_live_slots ); - if( !tile->replay.alpenglow ) { - ctx->block_id_map = fd_block_id_map_join( fd_block_id_map_new( block_id_map_mem, chain_cnt, ctx->block_id_map_seed ) ); - FD_TEST( ctx->block_id_map ); - ctx->ag_block_id_map = NULL; - } else { - ctx->ag_block_id_map = fd_ag_block_id_map_join( fd_ag_block_id_map_new( block_id_map_mem, chain_cnt, ctx->ag_block_id_map_seed ) ); - FD_TEST( ctx->ag_block_id_map ); - ctx->block_id_map = NULL; - } + ctx->ag_block_id_map = fd_ag_block_id_map_join( fd_ag_block_id_map_new( block_id_map_mem, chain_cnt, ctx->ag_block_id_map_seed ) ); + FD_TEST( ctx->ag_block_id_map ); for( ulong i=0UL; ireplay.max_live_slots; i++ ) { ctx->block_id_arr[ i ].block_id_seen = 0; @@ -5841,7 +4650,6 @@ unprivileged_init( fd_topo_t const * topo, else if( !strcmp( link->name, "gossip_misc" ) ) ctx->in_kind[ i ] = IN_KIND_GOSSIP_OUT; else if( !strcmp( link->name, "snapmk_out" ) ) ctx->in_kind[ i ] = IN_KIND_SNAPMK; else if( !strcmp( link->name, "admin_replay" ) ) ctx->in_kind[ i ] = IN_KIND_ADMIN; - else if( !strcmp( link->name, "tower_out" ) ) ctx->in_kind[ i ] = IN_KIND_TOWER; else if( !strcmp( link->name, "votor_out" ) ) ctx->in_kind[ i ] = IN_KIND_VOTOR; else FD_LOG_ERR(( "unexpected input link name %s", link->name )); @@ -5853,7 +4661,7 @@ unprivileged_init( fd_topo_t const * topo, *ctx->epoch_out = out1( topo, tile, "replay_epoch" ); FD_TEST( ctx->epoch_out->idx!=ULONG_MAX ); *ctx->replay_out = out1( topo, tile, "replay_out" ); FD_TEST( ctx->replay_out->idx!=ULONG_MAX ); *ctx->slot_out = out1( topo, tile, "replay_slot" ); FD_TEST( ctx->slot_out->idx!=ULONG_MAX ); - *ctx->rotor_out = out1( topo, tile, "replay_rotor" ); FD_TEST( ctx->rotor_out->idx==ULONG_MAX || ctx->alpenglow ); /* backtest has no rotor */ + *ctx->rotor_out = out1( topo, tile, "replay_rotor" ); *ctx->snapmk_out = out1( topo, tile, "replay_snapmk" ); FD_TEST( ctx->snapmk.supported == (ctx->snapmk_out->idx!=ULONG_MAX) ); ctx->exec_cnt = 0UL; @@ -5876,7 +4684,6 @@ unprivileged_init( fd_topo_t const * topo, FD_TEST( ctx->exec_cnt && ctx->exec_cnt==fd_topo_tile_name_cnt( topo, "execrp" ) ); for( ulong i=0UL; iexec_cnt; i++ ) FD_TEST( ctx->exec_out[ i ].idx!=ULONG_MAX ); - ctx->replay_out_seq = fd_mcache_seq_laddr_const( topo->links[ tile->out_link_id[ ctx->replay_out->idx ] ].mcache ); ctx->slot_out_seq = fd_mcache_seq_laddr_const( topo->links[ tile->out_link_id[ ctx->slot_out->idx ] ].mcache ); ctx->rpc_enabled = fd_topo_find_tile( topo, "rpc", 0UL )!=ULONG_MAX; @@ -6011,7 +4818,6 @@ during_housekeeping( fd_replay_tile_t * ctx ) { #define STEM_CALLBACK_METRICS_WRITE metrics_write #define STEM_CALLBACK_AFTER_CREDIT after_credit -#define STEM_CALLBACK_PREVENT_PARK prevent_park #define STEM_CALLBACK_BEFORE_FRAG before_frag #define STEM_CALLBACK_RETURNABLE_FRAG returnable_frag #define STEM_CALLBACK_DURING_HOUSEKEEPING during_housekeeping diff --git a/src/discof/replay/fd_replay_tile_private.h b/src/discof/replay/fd_replay_tile_private.h index e47441c604c..d6e75480bf2 100644 --- a/src/discof/replay/fd_replay_tile_private.h +++ b/src/discof/replay/fd_replay_tile_private.h @@ -9,8 +9,6 @@ #include "../../disco/node_info/fd_node_info.h" #include "../../disco/wait_info/fd_wait_info.h" #include "../../discof/poh/fd_poh.h" -#include "../../discof/reasm/fd_reasm.h" -#include "../../discof/repair/fd_repair_tile.h" #include "../../discof/rotor/fd_rotor_tile.h" #include "../../discof/replay/fd_sched.h" #include "../../discof/votor/fd_votor_tile.h" @@ -46,43 +44,28 @@ struct fd_replay_out_link { typedef struct fd_replay_out_link fd_replay_out_link_t; -/* fd_block_id_map is a simple map of block-ids to bank indices. The - map sits on top of an array of fd_block_id_ele_t. This serves as a - translation layer between block ids to bank indices. The data - array is indexed by bank index and the latest observed merkle root - for the bank index is stored in the array. Once the block id has - been observed, the entry is keyed by the latest merkle root (aka the - block id). */ - struct fd_block_id_ele { - fd_hash_t latest_mr; /* merkle root of the latest observed FEC set. Under tower this is - the block id once the block is complete. Under alpenglow this is - NEVER the block id: on completion it holds the last FEC set's - merkle root, which the next block's shreds chain off (the cmr). */ - fd_hash_t dmr; /* ALPENGLOW-ONLY block id (double merkle root). Only valid once - block_id_seen is set. */ - ag_block_id_t block_info; /* unpopulated for non alpenglow, but key for the compound map under alpenglow */ + fd_hash_t latest_mr; /* merkle root of the latest observed FEC set. This is NEVER the + block id: on completion it holds the last FEC set's merkle root, + which the next block's shreds chain off (the cmr). */ + fd_hash_t dmr; /* block id (double merkle root). Only valid once block_id_seen is + set. */ + ag_block_id_t block_info; /* key for the compound map */ uint latest_fec_idx; int block_id_seen; ulong slot; ulong bank_seq; - ulong next_; ulong ag_next_; uint fec_cnt; }; typedef struct fd_block_id_ele fd_block_id_ele_t; -/* Tower keys by slot (slot % reception_stats_cnt). Alpenglow keys by - bank idx. There is no reasm; rotor stamps the snapshot onto every - FEC it delivers and replay records it in process_rotor_fec. */ struct fd_reception_stats { ulong bank_seq; /* alpenglow guard: valid iff ==bank->bank_seq */ - ulong slot; /* tower guard */ uint fec_set_idx; union { - fd_fec_complete_metrics_t repair; /* tower/repair */ - fd_rotor_fec_metrics_t rotor; /* ag */ + fd_rotor_fec_metrics_t rotor; } metrics; }; typedef struct fd_reception_stats fd_reception_stats_t; @@ -116,15 +99,6 @@ typedef struct fd_replay_txn_timing_slot fd_replay_txn_timing_slot_t; #define POOL_NEXT pool.next #include "../../util/tmpl/fd_pool.c" -#define MAP_NAME fd_block_id_map -#define MAP_ELE_T fd_block_id_ele_t -#define MAP_KEY_T fd_hash_t -#define MAP_KEY latest_mr -#define MAP_NEXT next_ -#define MAP_KEY_EQ(k0,k1) (!memcmp((k0),(k1), sizeof(fd_hash_t))) -#define MAP_KEY_HASH(key,seed) (fd_hash32( (key)->uc, (seed) )) -#include "../../util/tmpl/fd_map_chain.c" - /* fd_ag_block_id_map indexes the same fd_block_id_ele_t array by the compound {slot, block_id} key. A block that is still being received through turbing is keyed by {slot, 0}: the DMR is only known once the @@ -178,8 +152,6 @@ struct fd_replay_tile { int vote_account_staked; int vote_account_inadmissible; - int alpenglow; - /* wfs_enabled is 1 if the validator is booted in wait_for_supermajority mode. In this mode replay (and, by extension, downstream consumers) is not allowed to make progress until 80% of @@ -194,9 +166,6 @@ struct fd_replay_tile { int wfs_hash_is_zero; ulong blockhash_seed; - ulong reasm_seed; - fd_reasm_t * reasm; - fd_reasm_fec_t * reasm_evicted; /* evicted FEC by reasm_insert must be stored in returnable_frag, and then drained in after_credit */ fd_reception_stats_t * reception_stats; ulong reception_stats_cnt; @@ -412,8 +381,6 @@ struct fd_replay_tile { ulong * dead_idxs; /* max_live_slots scratch for mark_bank_dead */ fd_hash_t * fec_chain; - ulong block_id_map_seed; - fd_block_id_map_t * block_id_map; ulong ag_block_id_map_seed; fd_ag_block_id_map_t * ag_block_id_map; @@ -441,8 +408,6 @@ struct fd_replay_tile { fd_replay_txn_timing_t * timing_rec; /* [FD_REPLAY_TXN_TIMING_SLOTS*max_txn_per_slot], slot i at i*max_txn_per_slot */ ulong * timing_slot_of_bank; /* [max_live_slots] bank_idx -> pool idx or idx_null */ - fd_reasm_fec_t ** backfill_path; /* [max_shreds_per_block/FD_FEC_SHRED_CNT] scratch for backfill_fec_sets */ - /* Whether the runtime has been booted either from snapshot loading or from genesis. */ int is_booted; @@ -500,8 +465,7 @@ struct fd_replay_tile { ulong catch_up_tip_advance_cnt; long boot_timestamp_nanos; - fd_hash_t reset_cmr; /* chained merkle root of the reset block */ - fd_hash_t reset_dmr; /* ALPENGLOW-ONLY double merkle root of the reset block */ + fd_hash_t reset_dmr; /* double merkle root of the reset block */ long reset_timestamp_nanos; fd_bank_t * leader_bank; @@ -560,9 +524,8 @@ struct fd_replay_tile { fd_replay_out_link_t exec_out[ FD_SCHED_MAX_EXEC_TILE_CNT ]; fd_replay_out_link_t replay_out[1]; - ulong const * replay_out_seq; fd_replay_out_link_t slot_out[1]; - fd_replay_out_link_t rotor_out[1]; /* REPLAY_SIG_MISSING_FEC, alpenglow only */ + fd_replay_out_link_t rotor_out[1]; /* REPLAY_SIG_MISSING_FEC */ ulong const * slot_out_seq; fd_replay_out_link_t snapmk_out[1]; ulong admin_out_idx; @@ -615,7 +578,6 @@ struct fd_replay_tile { ulong reasm_latest_fec_idx; ulong sched_full; - ulong reasm_empty; ulong leader_bid_wait; ulong banks_full; ulong parent_unavailable; diff --git a/src/discof/replay/test_replay_tile.c b/src/discof/replay/test_replay_tile.c index e268accdfe8..d0c9bd7a614 100644 --- a/src/discof/replay/test_replay_tile.c +++ b/src/discof/replay/test_replay_tile.c @@ -1,8 +1,7 @@ -/* test_replay_tile.c — Tests the replay tile's FEC consumption path - through reasm, verifying that equivocating FEC sets are not delivered. +/* test_replay_tile.c — Tests the replay tile's Alpenglow paths: rotor + FEC consumption, votor rooting and leader slots. - Follows the pattern of test_repair_tile.c and test_tower_tile.c: - mock heavy dependencies via #define before including the tile .c. */ + Mocks heavy dependencies via #define before including the tile .c. */ #define _GNU_SOURCE #include "../../disco/store/fd_store.h" #include @@ -268,7 +267,7 @@ mock_runtime_block_execute_prepare_fn( fd_banks_t * banks FD_PARAM_UNUSE #define fd_rewards_recalculate_partitioned_rewards(k,a,s,c) do { if( !mock_snapshot_boot ) (fd_rewards_recalculate_partitioned_rewards)(k,a,s,c); } while(0) #define fd_accdb_lamports(a,i,p) (mock_snapshot_boot ? 0UL : (fd_accdb_lamports)(a,i,p)) static ulong mock_migration_slot = ULONG_MAX; -#define fd_alpenglow_migration_slot(b,a) (mock_snapshot_boot ? mock_migration_slot : (fd_alpenglow_migration_slot)(b,a)) +#define fd_alpenglow_migration_slot(b,a) (mock_snapshot_boot || mock_footer_finalize ? mock_migration_slot : (fd_alpenglow_migration_slot)(b,a)) #define fd_runtime_block_execute_prepare mock_runtime_block_execute_prepare_fn /* Isolate footer rooting from scheduler storage and runtime settlement. */ @@ -279,8 +278,9 @@ static fd_hash_t mock_footer_poh; #define fd_sched_get_poh(s,b) (mock_footer_finalize ? &mock_footer_poh : (fd_sched_get_poh)(s,b)) #define fd_sched_get_shred_cnt(s,b) (mock_footer_finalize ? 0U : (fd_sched_get_shred_cnt)(s,b)) -#define fd_sched_get_footer(s,b) (mock_footer_finalize ? mock_footer : (fd_sched_get_footer)(s,b)) +#define fd_sched_get_footer(s,b) (mock_footer_finalize ? mock_footer : mock_snapshot_boot ? NULL : (fd_sched_get_footer)(s,b)) static int mock_footer_verify; /* mocked finalize still verifies certs */ +#define fd_alpenglow_producer_time_bounds(b,a,l,h) do { if( mock_footer_finalize ) { *(l) = 0L; *(h) = LONG_MAX; } else (fd_alpenglow_producer_time_bounds)(b,a,l,h); } while(0) #define fd_runtime_block_execute_finalize(b,a,c,f,s,g) (mock_footer_finalize ? ( mock_footer_verify && (f) ? fd_alpenglow_footer_verify(b,f,s,g) : 0 ) : (fd_runtime_block_execute_finalize)(b,a,c,f,s,g)) #define fd_runtime_block_execute_finalize_unverified(b,a,f,g) (mock_footer_finalize ? 0 : (fd_runtime_block_execute_finalize_unverified)(b,a,f,g)) #define fd_txncache_finalize_fork(t,f,o,h) do { if( !mock_footer_finalize ) (fd_txncache_finalize_fork)(t,f,o,h); } while(0) @@ -313,7 +313,7 @@ static fd_stem_context_t test_stem[ 1 ]; static void setup_repair_input( fd_replay_tile_t * ctx, fd_wksp_t * wksp ) { ulong const depth = 128UL; - ulong const mtu = sizeof(fd_repair_fec_complete_t); + ulong const mtu = sizeof(fd_rotor_replay_fec_t); ulong dcache_data_sz = fd_dcache_req_data_sz( mtu, depth, 1UL, 1 ); void * dcache_mem = fd_wksp_alloc_laddr( wksp, fd_dcache_align(), fd_dcache_footprint( dcache_data_sz, 0UL ), 1UL ); @@ -393,8 +393,6 @@ setup_timing( fd_replay_tile_t * ctx, FD_TEST( ctx->timing_rec ); ctx->timing_slot_of_bank = test_timing_of_bank; for( ulong i=0UL; itiming_slot_of_bank[ i ] = fd_timing_slot_pool_idx_null( ctx->timing_slot_pool ); - ctx->backfill_path = fd_wksp_alloc_laddr( wksp, alignof(fd_reasm_fec_t *), (ctx->max_shreds_per_block/FD_FEC_SHRED_CNT)*sizeof(fd_reasm_fec_t *), 1UL ); - FD_TEST( ctx->backfill_path ); } /* node_info is a shared topology object present in every topology that @@ -450,18 +448,10 @@ setup_ctx( fd_replay_tile_t * ctx, memset( ctx, 0, sizeof(*ctx) ); setup_timing( ctx, wksp ); - /* Reasm */ - - ulong fec_max = 32UL; - void * reasm_mem = fd_wksp_alloc_laddr( wksp, fd_reasm_align(), fd_reasm_footprint( fec_max ), 1UL ); - FD_TEST( reasm_mem ); - ctx->reasm = fd_reasm_join( fd_reasm_new( reasm_mem, fec_max, 0UL ) ); - FD_TEST( ctx->reasm ); - /* Block-id map */ ulong bid_cnt = TEST_BANKS_MAX; - ulong chain_cnt = fd_block_id_map_chain_cnt_est( bid_cnt ); + ulong chain_cnt = fd_ag_block_id_map_chain_cnt_est( bid_cnt ); ctx->block_id_arr = fd_wksp_alloc_laddr( wksp, alignof(fd_block_id_ele_t), sizeof(fd_block_id_ele_t) * bid_cnt, 1UL ); FD_TEST( ctx->block_id_arr ); @@ -469,16 +459,15 @@ setup_ctx( fd_replay_tile_t * ctx, ctx->dead_idxs = fd_wksp_alloc_laddr( wksp, alignof(ulong), sizeof(ulong)*bid_cnt, 1UL ); FD_TEST( ctx->dead_idxs ); - void * bid_map_mem = fd_wksp_alloc_laddr( wksp, fd_block_id_map_align(), fd_block_id_map_footprint( chain_cnt ), 1UL ); + void * bid_map_mem = fd_wksp_alloc_laddr( wksp, fd_ag_block_id_map_align(), fd_ag_block_id_map_footprint( chain_cnt ), 1UL ); FD_TEST( bid_map_mem ); - ctx->block_id_map_seed = 42UL; - ctx->block_id_map = fd_block_id_map_join( fd_block_id_map_new( bid_map_mem, chain_cnt, ctx->block_id_map_seed ) ); - FD_TEST( ctx->block_id_map ); + ctx->ag_block_id_map_seed = 7UL; + ctx->ag_block_id_map = fd_ag_block_id_map_join( fd_ag_block_id_map_new( bid_map_mem, chain_cnt, ctx->ag_block_id_map_seed ) ); + FD_TEST( ctx->ag_block_id_map ); ctx->reception_stats_cnt = bid_cnt; ctx->reception_stats = fd_wksp_alloc_laddr( wksp, alignof(fd_reception_stats_t), sizeof(fd_reception_stats_t)*bid_cnt, 1UL ); FD_TEST( ctx->reception_stats ); - for( ulong i=0UL; ireception_stats_cnt; i++ ) ctx->reception_stats[ i ].slot = ULONG_MAX; static fd_store_t mock_store; memset( &mock_store, 0, sizeof(mock_store) ); @@ -546,16 +535,14 @@ setup_ctx( fd_replay_tile_t * ctx, setup_repair_input( ctx, wksp ); } -static fd_reasm_fec_t * -init_root_fec( fd_replay_tile_t * ctx, - fd_hash_t const * mr_root ) { - fd_reasm_fec_t * f_root = fd_reasm_init( ctx->reasm, mr_root, 0 ); - FD_TEST( f_root ); +/* init_root fills the root bank's block id element. Callers key it in + ag_block_id_map themselves. */ +static void +init_root( fd_replay_tile_t * ctx, + fd_hash_t const * mr_root ) { fd_bank_t * root_bank = fd_banks_root( ctx->banks ); FD_TEST( root_bank ); - f_root->bank_idx = (uint)root_bank->idx; - f_root->bank_seq = root_bank->bank_seq; fd_block_id_ele_t * block_id_ele = &ctx->block_id_arr[ root_bank->idx ]; block_id_ele->block_id_seen = 1; @@ -564,101 +551,6 @@ init_root_fec( fd_replay_tile_t * ctx, block_id_ele->latest_fec_idx = 0U; block_id_ele->latest_mr = *mr_root; block_id_ele->dmr = *mr_root; - FD_TEST( fd_block_id_map_ele_insert( ctx->block_id_map, block_id_ele, ctx->block_id_arr ) ); - - ctx->reset_cmr = *mr_root; - return f_root; -} - -static fd_reasm_fec_t * -ingest_fec_complete_with_signal( fd_replay_tile_t * ctx, - ulong sig, - fd_hash_t const * merkle_root, - fd_hash_t const * chained_merkle_root, - ulong slot, - uint fec_set_idx, - ushort parent_off, - ushort data_cnt, - int data_complete, - int slot_complete, - fd_fec_complete_metrics_t const * metrics ) { - ulong chunk = ctx->in[ TEST_REPAIR_IN_IDX ].chunk0; - fd_repair_fec_complete_t * complete_msg = fd_chunk_to_laddr( ctx->in[ TEST_REPAIR_IN_IDX ].mem, chunk ); - memset( complete_msg, 0, sizeof(fd_repair_fec_complete_t) ); - - complete_msg->fec.merkle_root = *merkle_root; - complete_msg->fec.chained_merkle_root = *chained_merkle_root; - complete_msg->fec.last_shred_hdr.slot = slot; - complete_msg->fec.last_shred_hdr.fec_set_idx = fec_set_idx; - complete_msg->fec.last_shred_hdr.idx = fec_set_idx + (uint)data_cnt - 1U; - complete_msg->fec.last_shred_hdr.data.parent_off = parent_off; - complete_msg->fec.last_shred_hdr.data.flags = - (uchar)( fd_uchar_if( data_complete, FD_SHRED_DATA_FLAG_DATA_COMPLETE, 0U ) | - fd_uchar_if( slot_complete, FD_SHRED_DATA_FLAG_SLOT_COMPLETE, 0U ) ); - if( metrics ) complete_msg->metrics = *metrics; - - FD_TEST( !returnable_frag( ctx, TEST_REPAIR_IN_IDX, 0UL, sig, chunk, - sizeof(fd_repair_fec_complete_t), 0UL, 0UL, - fd_frag_meta_ts_comp( fd_tickcount() ), test_stem ) ); - - fd_reasm_fec_t * fec = fd_reasm_query( ctx->reasm, merkle_root ); - FD_TEST( fec ); - return fec; -} - -static fd_reasm_fec_t * -ingest_fec_complete_with_metrics( fd_replay_tile_t * ctx, - fd_hash_t const * merkle_root, - fd_hash_t const * chained_merkle_root, - ulong slot, - uint fec_set_idx, - ushort parent_off, - ushort data_cnt, - int data_complete, - int slot_complete, - fd_fec_complete_metrics_t const * metrics ) { - return ingest_fec_complete_with_signal( ctx, REPAIR_SIG_FEC, merkle_root, chained_merkle_root, - slot, fec_set_idx, parent_off, data_cnt, - data_complete, slot_complete, metrics ); -} - -static fd_reasm_fec_t * -ingest_fec_complete( fd_replay_tile_t * ctx, - fd_hash_t const * merkle_root, - fd_hash_t const * chained_merkle_root, - ulong slot, - uint fec_set_idx, - ushort parent_off, - ushort data_cnt, - int data_complete, - int slot_complete ) { - return ingest_fec_complete_with_metrics( ctx, merkle_root, chained_merkle_root, slot, fec_set_idx, - parent_off, data_cnt, data_complete, slot_complete, NULL ); -} - -static void -assert_reception_event_matches( fd_event_block_completed_t const * ev, - fd_fec_complete_metrics_t const * metrics, - uint fec_set_idx ) { - FD_TEST( ev->last_completed_fec_set_index==fec_set_idx ); - FD_TEST( ev->turbine_shred_count==metrics->blk_turbine_cnt ); - FD_TEST( ev->repair_shred_count==metrics->blk_repair_cnt ); - FD_TEST( ev->recovered_shred_count==metrics->blk_recovered_cnt ); - FD_TEST( ev->data_shred_count==metrics->blk_data_cnt ); - FD_TEST( ev->parity_shred_count==metrics->blk_parity_cnt ); - FD_TEST( ev->chain_confirmed==!!metrics->blk_chain_confirmed ); - FD_TEST( ev->slot_complete_flag==!!metrics->blk_slot_complete ); - FD_TEST( ev->lowest_verified_fec_index==metrics->blk_lowest_verified_fec ); - FD_TEST( ev->repair_request_window_count==metrics->blk_req_window_cnt ); - FD_TEST( ev->repair_request_highest_window_count==metrics->blk_req_highest_cnt ); - FD_TEST( ev->repair_request_orphan_count==metrics->blk_req_orphan_cnt ); - FD_TEST( ev->repair_responses_received==metrics->blk_repair_responses ); - FD_TEST( ev->repair_requests_retransmitted==metrics->blk_req_retransmit_cnt ); - FD_TEST( ev->repair_failed_chain_verify==!!metrics->blk_chain_verify_failed ); - FD_TEST( ev->first_shred_received_time==metrics->blk_first_shred_ts_nanos ); - FD_TEST( ev->last_shred_received_time==metrics->blk_last_shred_ts_nanos ); - FD_TEST( ev->first_repair_request_time==metrics->blk_first_req_ts_nanos ); - FD_TEST( ev->last_repair_received_time==metrics->blk_last_repair_resp_ts_nanos ); } static void @@ -803,179 +695,10 @@ test_txn_completion_publish( fd_wksp_t * wksp ) { FD_LOG_NOTICE(( "pass: test_txn_completion_publish" )); } -static void -test_reception_metrics_sidecar( fd_wksp_t * wksp ) { - static fd_replay_tile_t ctx[1]; - setup_ctx( ctx, wksp ); - - fd_hash_t mr_root = { .ul = { 100UL } }; - fd_hash_t mr1_0 = { .ul = { 200UL } }; - fd_hash_t mr1_32 = { .ul = { 300UL } }; - fd_hash_t mr1_64 = { .ul = { 400UL } }; - fd_hash_t mr1_0_b = { .ul = { 500UL } }; - init_root_fec( ctx, &mr_root ); - - fd_fec_complete_metrics_t metrics_a = { - .stats_valid = 1U, - .blk_turbine_cnt = 1U, - .blk_repair_cnt = 2U, - .blk_recovered_cnt = 3U, - .blk_data_cnt = 4U, - .blk_parity_cnt = 5U, - .blk_lowest_verified_fec = 6U, - .blk_chain_confirmed = 1U, - .blk_slot_complete = 0U, - .blk_req_window_cnt = 7U, - .blk_req_highest_cnt = 8U, - .blk_req_orphan_cnt = 9U, - .blk_req_retransmit_cnt = 10U, - .blk_repair_responses = 11U, - .blk_chain_verify_failed = 0U, - .fec_completed_ts_nanos = 12UL, - .blk_first_shred_ts_nanos = 13UL, - .blk_last_shred_ts_nanos = 14UL, - .blk_first_req_ts_nanos = 15UL, - .blk_last_repair_resp_ts_nanos = 16UL, - }; - - fd_reasm_fec_t * f1_0 = ingest_fec_complete_with_metrics( ctx, &mr1_0, &mr_root, - 1UL, 0U, 1U, 32U, 1, 0, &metrics_a ); - FD_TEST( f1_0->fec_completed_ts_nanos==(ulong)metrics_a.fec_completed_ts_nanos ); - FD_TEST( ctx->reception_stats[ 1UL % ctx->reception_stats_cnt ].slot==1UL ); - - fd_fec_complete_metrics_t metrics_invalid = { - .stats_valid = 0U, - .fec_completed_ts_nanos = 22UL, - }; - fd_reasm_fec_t * f1_32 = ingest_fec_complete_with_metrics( ctx, &mr1_32, &mr1_0, - 1UL, 32U, 1U, 32U, 1, 0, &metrics_invalid ); - FD_TEST( f1_32->fec_completed_ts_nanos==(ulong)metrics_invalid.fec_completed_ts_nanos ); - - fd_event_block_completed_t ev = {0}; - block_completed_event_fill_reception( ctx, &ev, ULONG_MAX, &mr1_32, 1UL ); - FD_TEST( ev.fec_set_count==2UL ); - assert_reception_event_matches( &ev, &metrics_a, 0U ); - - fd_fec_complete_metrics_t metrics_b = metrics_a; - metrics_b.blk_turbine_cnt = 21U; - metrics_b.blk_slot_complete = 1U; - metrics_b.fec_completed_ts_nanos = 23UL; - fd_reasm_fec_t * f1_64 = ingest_fec_complete_with_metrics( ctx, &mr1_64, &mr1_32, - 1UL, 64U, 1U, 32U, 1, 1, &metrics_b ); - FD_TEST( f1_64->fec_completed_ts_nanos==(ulong)metrics_b.fec_completed_ts_nanos ); - FD_TEST( ctx->reception_stats[ 1UL % ctx->reception_stats_cnt ].slot==1UL ); - - memset( &ev, 0, sizeof(ev) ); - block_completed_event_fill_reception( ctx, &ev, ULONG_MAX, &mr1_64, 1UL ); - assert_reception_event_matches( &ev, &metrics_b, 64U ); - - fd_fec_complete_metrics_t metrics_c = metrics_a; - metrics_c.blk_turbine_cnt = 31U; - metrics_c.fec_completed_ts_nanos = 32UL; - fd_reasm_fec_t * f1_0_b = ingest_fec_complete_with_metrics( ctx, &mr1_0_b, &mr_root, - 1UL, 0U, 1U, 32U, 1, 1, &metrics_c ); - FD_TEST( f1_0_b->eqvoc ); - FD_TEST( ctx->reception_stats[ 1UL % ctx->reception_stats_cnt ].slot==1UL ); - - memset( &ev, 0, sizeof(ev) ); - block_completed_event_fill_reception( ctx, &ev, ULONG_MAX, &mr1_64, 1UL ); - assert_reception_event_matches( &ev, &metrics_c, 0U ); - - memset( &ev, 0, sizeof(ev) ); - block_completed_event_fill_reception( ctx, &ev, ULONG_MAX, &mr1_0_b, 1UL ); - assert_reception_event_matches( &ev, &metrics_c, 0U ); - - fd_fec_complete_metrics_t capacity_metrics = metrics_a; - for( ulong slot=1UL; slot<=TEST_BANKS_MAX+1UL; slot++ ) { - capacity_metrics.blk_turbine_cnt = (uint)slot; - fd_reception_stats_t * stats = &ctx->reception_stats[ slot % ctx->reception_stats_cnt ]; - stats->slot = slot; - stats->fec_set_idx = (uint)slot; - stats->metrics.repair = capacity_metrics; - } - FD_TEST( ctx->reception_stats[ 1UL % ctx->reception_stats_cnt ].slot!=1UL ); - fd_reception_stats_t * latest = &ctx->reception_stats[ (TEST_BANKS_MAX+1UL) % ctx->reception_stats_cnt ]; - FD_TEST( latest->slot==TEST_BANKS_MAX+1UL ); - FD_TEST( latest->metrics.repair.blk_turbine_cnt==TEST_BANKS_MAX+1UL ); - - FD_LOG_NOTICE(( "pass: test_reception_metrics_sidecar" )); -} - -static int -drive_after_credit_once( fd_replay_tile_t * ctx ) { - int opt_poll_in = 1; - int charge_busy = 0; - ctx->execrp_idle_cnt = 2UL*ctx->in_cnt; - after_credit( ctx, test_stem, &opt_poll_in, &charge_busy ); - return charge_busy; -} - -static fd_reasm_fec_t * -drive_one_fec( fd_replay_tile_t * ctx, - ulong slot, - uint fec_set_idx ) { - fd_reasm_fec_t * fec = fd_reasm_peek( ctx->reasm ); - FD_TEST( fec && fec->slot==slot && fec->fec_set_idx==fec_set_idx ); - - FD_TEST( drive_after_credit_once( ctx ) ); - FD_TEST( fec->popped ); - return fec; -} - -static fd_bank_t * -drive_become_leader( fd_replay_tile_t * ctx, - fd_hash_t const * reset_cmr, - ulong leader_slot ) { - fd_bank_t * reset_bank = fd_banks_root( ctx->banks ); - FD_TEST( reset_bank ); - - mock_next_leader_slot = leader_slot; - ctx->reset_slot = reset_bank->f.slot; - ctx->reset_cmr = *reset_cmr; - ctx->next_leader_slot = fd_multi_epoch_leaders_get_next_slot( ctx->mleaders, leader_slot, ctx->identity_pubkey ); - FD_TEST( ctx->next_leader_slot==leader_slot ); - ctx->next_leader_tickcount = 0L; - - FD_TEST( drive_after_credit_once( ctx ) ); - - FD_TEST( ctx->is_leader ); - FD_TEST( ctx->leader_bank ); - FD_TEST( ctx->leader_bank->is_leader ); - FD_TEST( ctx->leader_bank->f.slot==leader_slot ); - FD_TEST( ctx->leader_bank->parent_idx==reset_bank->idx ); - return ctx->leader_bank; -} - -static void -test_leader_fec_payload_retained( fd_wksp_t * wksp ) { - static fd_replay_tile_t ctx[ 1 ]; - setup_ctx( ctx, wksp ); - - fd_hash_t mr_root = { .ul = { 100UL } }; - fd_hash_t mr_leader = { .ul = { 200UL } }; - init_root_fec( ctx, &mr_root ); - drive_become_leader( ctx, &mr_root, 1UL ); - - fd_reasm_fec_t * leader_fec = ingest_fec_complete_with_signal( - ctx, REPAIR_SIG_FEC_LEADER, &mr_leader, &mr_root, 1UL, 0U, 1U, 32U, 1, 1, NULL ); - FD_TEST( leader_fec ); - - ulong sched_cnt = mock_sched_fec_ingest_cnt; - drive_one_fec( ctx, 1UL, 0U ); - FD_TEST( mock_store_view_call_cnt==0UL ); - FD_TEST( mock_sched_fec_ingest_cnt==sched_cnt ); - - fd_reasm_fec_t stale_fec = { .key = { .ul = { 300UL } }, .slot = 2UL, .is_leader = 1U }; - FD_TEST( !insert_fec_set( ctx, test_stem, &stale_fec ) ); - - FD_LOG_NOTICE(( "pass: test_leader_fec_payload_retained" )); -} - static void test_reward_cert_signer_count( void ) { static fd_replay_tile_t ctx[ 1 ]; static fd_bank_t bank[ 1 ]; - ctx->alpenglow = 1; bank->f.slot = FD_NUM_SLOTS_FOR_REWARD; fd_epoch_schedule_derive( &bank->f.epoch_schedule, 128UL, 128UL, 0 ); /* Count signers even when our voter rank is unavailable. */ @@ -1157,39 +880,6 @@ test_snapshot_found_deleted_state( fd_wksp_t * wksp ) { FD_LOG_NOTICE(( "pass: test_snapshot_found_deleted_state" )); } -/* Test vote_account_is_current (the production classifier used by - update_delinquent_stake). */ - -static void -test_delinquent_classifier( void ) { - /* last_vote_slot==ULONG_MAX is always delinquent. */ - FD_TEST( !vote_account_is_current( 0UL, ULONG_MAX ) ); - FD_TEST( !vote_account_is_current( 128UL, ULONG_MAX ) ); - FD_TEST( !vote_account_is_current( 500UL, ULONG_MAX ) ); - - /* cur_slot < 128: distance < 128, so all are current. */ - FD_TEST( vote_account_is_current( 0UL, 0UL ) ); /* distance 0 */ - FD_TEST( vote_account_is_current( 0UL, 1UL ) ); - FD_TEST( vote_account_is_current( 50UL, 0UL ) ); /* distance 50 */ - FD_TEST( vote_account_is_current( 50UL, 1UL ) ); - FD_TEST( vote_account_is_current( 127UL, 1UL ) ); - FD_TEST( vote_account_is_current( 127UL, 0UL ) ); /* distance 127 */ - - /* cur_slot >= 128: distance < 128 is current, >= 128 is delinquent. */ - FD_TEST( vote_account_is_current( 128UL, 128UL ) ); /* distance 0 */ - FD_TEST( vote_account_is_current( 128UL, 1UL ) ); /* distance 127 */ - FD_TEST( !vote_account_is_current( 128UL, 0UL ) ); /* distance 128 */ - FD_TEST( vote_account_is_current( 500UL, 373UL ) ); /* distance 127 */ - FD_TEST( !vote_account_is_current( 500UL, 372UL ) ); /* distance 128 */ - FD_TEST( !vote_account_is_current( 500UL, 100UL ) ); /* distance 400 */ - - /* Future vote slot (cur_slot < last_vote_slot): always current. */ - FD_TEST( vote_account_is_current( 200UL, 300UL ) ); - FD_TEST( vote_account_is_current( 128UL, 999UL ) ); - - FD_LOG_NOTICE(( "pass: test_delinquent_classifier" )); -} - /* Test wait_info_healthy (the production health classifier used by metrics_write). */ @@ -1217,28 +907,11 @@ test_wait_info_health_signal( void ) { FD_LOG_NOTICE(( "pass: test_wait_info_health_signal" )); } -/* publish_both_modes publishes under both consensus modes, Tower and - Alpenglow, and asserts the result does not depend on which: - wait_info derives the leader gap from the leader schedule, which is - mode independent. */ - static void -publish_both_modes( fd_replay_tile_t * ctx, - fd_wait_info_t * info ) { - fd_wait_info_t alpenglow_info; - ulong query_start = ctx->next_leader_query_start; - - ctx->alpenglow = 0; +publish( fd_replay_tile_t * ctx, + fd_wait_info_t * info ) { wait_info_publish( ctx ); FD_TEST( fd_wait_info_try_read( info, ctx->wait_info ) ); - - /* Restore the cache so the second publish rescans. */ - ctx->next_leader_query_start = query_start; - ctx->alpenglow = 1; - wait_info_publish( ctx ); - FD_TEST( fd_wait_info_try_read( &alpenglow_info, ctx->wait_info ) ); - - FD_TEST( !memcmp( info, &alpenglow_info, sizeof(fd_wait_info_t) ) ); } static void @@ -1263,7 +936,7 @@ test_wait_info_leader_gap( void ) { mock_next_leader_slot = 5000UL; ctx->next_leader_slot = 5000UL; ctx->next_leader_query_start = ULONG_MAX; - publish_both_modes( ctx, &info ); + publish( ctx, &info ); FD_TEST( info.next_leader_known ); FD_TEST( info.next_leader_slot==5000UL ); FD_TEST( !info.leader_slot ); @@ -1274,13 +947,13 @@ test_wait_info_leader_gap( void ) { replay trailing it, and read 0 rather than ULONG_MAX when unset. */ ctx->catch_up_max_fec_slot = 1012UL; ctx->next_leader_query_start = ULONG_MAX; - publish_both_modes( ctx, &info ); + publish( ctx, &info ); FD_TEST( info.tip_slot==1012UL ); FD_TEST( info.reset_slot==1000UL ); ctx->catch_up_max_fec_slot = ULONG_MAX; ctx->next_leader_query_start = ULONG_MAX; - publish_both_modes( ctx, &info ); + publish( ctx, &info ); FD_TEST( !info.tip_slot ); ctx->catch_up_max_fec_slot = 1000UL; @@ -1292,7 +965,7 @@ test_wait_info_leader_gap( void ) { ctx->highwater_leader_slot = 1001UL; ctx->leader_bank = NULL; ctx->next_leader_query_start = ULONG_MAX; - publish_both_modes( ctx, &info ); + publish( ctx, &info ); FD_TEST( !( info.next_leader_known && !info.next_leader_slot ) ); /* not "nothing upcoming" */ FD_TEST( info.next_leader_slot==5000UL ); FD_TEST( !info.leader_slot ); @@ -1303,7 +976,7 @@ test_wait_info_leader_gap( void ) { term must exclude it. */ mock_next_leader_slot = 1001UL; ctx->next_leader_query_start = ULONG_MAX; - publish_both_modes( ctx, &info ); + publish( ctx, &info ); FD_TEST( info.next_leader_known ); FD_TEST( !info.next_leader_slot ); @@ -1314,7 +987,7 @@ test_wait_info_leader_gap( void ) { mock_next_leader_slot = 0UL; /* mock yields ULONG_MAX for start_slot>0 */ ctx->next_leader_slot = ULONG_MAX; ctx->next_leader_query_start = ULONG_MAX; - publish_both_modes( ctx, &info ); + publish( ctx, &info ); FD_TEST( info.next_leader_known ); FD_TEST( !info.next_leader_slot ); @@ -1322,7 +995,7 @@ test_wait_info_leader_gap( void ) { "nothing upcoming". */ mock_leader_schedule_loaded = 0; ctx->next_leader_query_start = ULONG_MAX; - publish_both_modes( ctx, &info ); + publish( ctx, &info ); FD_TEST( !info.next_leader_known ); /* Actively leading outranks everything else. */ @@ -1333,7 +1006,7 @@ test_wait_info_leader_gap( void ) { leader_bank->f.slot = 1001UL; ctx->leader_bank = leader_bank; ctx->next_leader_query_start = ULONG_MAX; - publish_both_modes( ctx, &info ); + publish( ctx, &info ); FD_TEST( info.leader_slot ==1001UL ); FD_TEST( info.next_leader_slot==1001UL ); @@ -1435,7 +1108,7 @@ test_wait_info_snapshot_intervals( void ) { /* Both intervals reach the box verbatim. */ ctx->snapmk.full_interval_blocks = 25000UL; ctx->snapmk.incremental_interval_blocks = 500UL; - publish_both_modes( ctx, &info ); + publish( ctx, &info ); FD_TEST( info.snap_full_interval_blocks==25000UL ); FD_TEST( info.snap_incr_interval_blocks==500UL ); @@ -1444,7 +1117,7 @@ test_wait_info_snapshot_intervals( void ) { ctx->snapmk.full_interval_blocks = 0UL; ctx->snapmk.incremental_interval_blocks = 0UL; ctx->next_leader_query_start = ULONG_MAX; - publish_both_modes( ctx, &info ); + publish( ctx, &info ); FD_TEST( !info.snap_full_interval_blocks ); FD_TEST( !info.snap_incr_interval_blocks ); @@ -1452,7 +1125,7 @@ test_wait_info_snapshot_intervals( void ) { incremental, so the two intervals cannot collapse into one flag. */ ctx->snapmk.incremental_interval_blocks = 500UL; ctx->next_leader_query_start = ULONG_MAX; - publish_both_modes( ctx, &info ); + publish( ctx, &info ); FD_TEST( !info.snap_full_interval_blocks ); FD_TEST( info.snap_incr_interval_blocks==500UL ); @@ -1511,28 +1184,6 @@ test_next_leader_slot_cache( void ) { FD_LOG_NOTICE(( "pass: test_next_leader_slot_cache" )); } -static void -start_fec_with_epoch_boundary_mode( fd_replay_tile_t * ctx, - fd_reasm_fec_t * fec, - int freeze_bank, - int epoch_boundary_enabled ) { - int prev_epoch_boundary_enabled = mock_epoch_boundary_enabled; - mock_epoch_boundary_enabled = epoch_boundary_enabled; - replay_block_start( ctx, fec->bank_idx, fec->parent_bank_idx, fec->slot ); - mock_epoch_boundary_enabled = prev_epoch_boundary_enabled; - - fd_bank_t * bank = fd_banks_bank_query( ctx->banks, fec->bank_idx ); - FD_TEST( bank ); - if( freeze_bank ) fd_banks_mark_bank_frozen( bank ); -} - -static void -start_non_epoch_boundary_fec( fd_replay_tile_t * ctx, - fd_reasm_fec_t * fec, - int freeze_bank ) { - start_fec_with_epoch_boundary_mode( ctx, fec, freeze_bank, 0 ); -} - static void test_consensus_root_notification_handoff( fd_wksp_t * wksp ) { static fd_replay_tile_t ctx[ 1 ]; @@ -1556,7 +1207,7 @@ test_consensus_root_notification_handoff( fd_wksp_t * wksp ) { root->f.slot_params = FD_SLOT_PARAMS_400MS; root->f.slot_params.hashes_per_tick = 4UL; root->f.slot_params_default = FD_SLOT_PARAMS_400MS; - root->f.features.alpenglow = FD_FEATURE_DISABLED; + root->f.features.alpenglow = 0UL; fd_epoch_schedule_derive( &root->f.epoch_schedule, 128UL, 128UL, 0 ); fd_hash_t root_id = { .ul = { 100UL } }; @@ -1565,27 +1216,22 @@ test_consensus_root_notification_handoff( fd_wksp_t * wksp ) { fd_blockhashes_init( &root->f.block_hash_queue, 42UL ); FD_TEST( fd_blockhashes_push_new( &root->f.block_hash_queue, &root_id ) ); - ulong chain_cnt = fd_block_id_map_chain_cnt_est( bank_cnt ); + ulong chain_cnt = fd_ag_block_id_map_chain_cnt_est( bank_cnt ); ctx->block_id_arr = fd_wksp_alloc_laddr( wksp, alignof(fd_block_id_ele_t), sizeof(fd_block_id_ele_t)*bank_cnt, 1UL ); FD_TEST( ctx->block_id_arr ); memset( ctx->block_id_arr, 0, sizeof(fd_block_id_ele_t)*bank_cnt ); ctx->dead_idxs = fd_wksp_alloc_laddr( wksp, alignof(ulong), sizeof(ulong)*bank_cnt, 1UL ); FD_TEST( ctx->dead_idxs ); - void * map_mem = fd_wksp_alloc_laddr( wksp, fd_block_id_map_align(), fd_block_id_map_footprint( chain_cnt ), 1UL ); + void * map_mem = fd_wksp_alloc_laddr( wksp, fd_ag_block_id_map_align(), fd_ag_block_id_map_footprint( chain_cnt ), 1UL ); FD_TEST( map_mem ); - ctx->block_id_map = fd_block_id_map_join( fd_block_id_map_new( map_mem, chain_cnt, 44UL ) ); - FD_TEST( ctx->block_id_map ); + ctx->ag_block_id_map = fd_ag_block_id_map_join( fd_ag_block_id_map_new( map_mem, chain_cnt, 44UL ) ); + FD_TEST( ctx->ag_block_id_map ); ctx->block_id_len = bank_cnt; ctx->max_live_slots = bank_cnt; - void * reasm_mem = fd_wksp_alloc_laddr( wksp, fd_reasm_align(), fd_reasm_footprint( 2UL ), 1UL ); - FD_TEST( reasm_mem ); - ctx->reasm = fd_reasm_join( fd_reasm_new( reasm_mem, 2UL, 0UL ) ); - FD_TEST( ctx->reasm ); - - void * store_mem = fd_wksp_alloc_laddr( wksp, fd_store_align(), fd_store_footprint( 2UL, 1UL, 0UL, 0UL, 0UL, 0, 0UL ), 1UL ); + void * store_mem = fd_wksp_alloc_laddr( wksp, fd_store_align(), fd_store_footprint( 2UL, 1UL, 0UL, 0UL, 0UL, 1, 1UL ), 1UL ); FD_TEST( store_mem ); - ctx->store = fd_store_join( fd_store_new( store_mem, 2UL, 1UL, 0UL, 0UL, 0UL, FD_SHRED_BLK_MAX, 0UL, 0, 0UL ) ); + ctx->store = fd_store_join( fd_store_new( store_mem, 2UL, 1UL, 0UL, 0UL, 0UL, FD_SHRED_BLK_MAX, 0UL, 1, 1UL ) ); FD_TEST( ctx->store ); FD_TEST( fd_store_map_ljoin( ctx->store, ctx->map_join ) ); ctx->store_disk_fd = -1; @@ -1645,11 +1291,13 @@ test_consensus_root_notification_handoff( fd_wksp_t * wksp ) { fd_banks_mark_bank_frozen( child ); fd_block_id_ele_t * child_ele = &ctx->block_id_arr[ child->idx ]; - child_ele->latest_mr = child_id; - child_ele->slot = child->f.slot; - child_ele->bank_seq = child->bank_seq; + child_ele->latest_mr = child_id; + child_ele->dmr = child_id; + child_ele->block_info = ag_block_id( child->f.slot, child_id.uc ); + child_ele->slot = child->f.slot; + child_ele->bank_seq = child->bank_seq; child_ele->block_id_seen = 1; - FD_TEST( fd_block_id_map_ele_insert( ctx->block_id_map, child_ele, ctx->block_id_arr ) ); + FD_TEST( fd_ag_block_id_map_ele_insert( ctx->ag_block_id_map, child_ele, ctx->block_id_arr ) ); ctx->resolv_tile_cnt = 1UL; ctx->consensus_root = root_id; @@ -1736,11 +1384,10 @@ setup_rooting_ctx( fd_replay_tile_t * ctx, setup_node_info( ctx ); setup_stem( ctx, wksp ); setup_votor_input( ctx, wksp ); - ctx->alpenglow = 1; - void * store_mem = fd_wksp_alloc_laddr( wksp, fd_store_align(), fd_store_footprint( 2UL, 1UL, 0UL, 0UL, 0UL, 0, 0UL ), 1UL ); + void * store_mem = fd_wksp_alloc_laddr( wksp, fd_store_align(), fd_store_footprint( 2UL, 1UL, 0UL, 0UL, 0UL, 1, 1UL ), 1UL ); FD_TEST( store_mem ); - ctx->store = fd_store_join( fd_store_new( store_mem, 2UL, 1UL, 0UL, 0UL, 0UL, FD_SHRED_BLK_MAX, 0UL, 0, 0UL ) ); + ctx->store = fd_store_join( fd_store_new( store_mem, 2UL, 1UL, 0UL, 0UL, 0UL, FD_SHRED_BLK_MAX, 0UL, 1, 1UL ) ); FD_TEST( ctx->store ); FD_TEST( fd_store_map_ljoin( ctx->store, ctx->map_join ) ); @@ -2104,75 +1751,6 @@ test_ag_update_delinquent( fd_wksp_t * wksp ) { FD_LOG_NOTICE(( "pass: test_ag_update_delinquent" )); } -static void -test_update_delinquent_stake( fd_wksp_t * wksp ) { - static fd_replay_tile_t ctx[ 1 ]; - fd_hash_t root_id = { .ul = { 100UL } }; - fd_bank_t * bank = setup_rooting_ctx( ctx, wksp, &root_id ); - ctx->alpenglow = 0; - - static fd_wait_info_box_t box[ 1 ]; - ctx->wait_info = fd_wait_info_box_join( fd_wait_info_box_new( box ) ); - FD_TEST( ctx->wait_info ); - - fd_vote_stakes_t * vote_stakes = fd_bank_vote_stakes( bank ); - ulong fork_id = bank->vote_stakes_fork_id; - - /* 100+200+300+400 == 1000 lamports of valid stake. */ - ulong const stake[ TEST_AG_VOTERS ] = { 100UL, 200UL, 300UL, 400UL }; - fd_pubkey_t vote[ TEST_AG_VOTERS ]; - for( ulong i=0UL; if.slot = cur; - update_delinquent_stake( ctx, bank ); - FD_TEST( ctx->delinquent_known ); - FD_TEST( ctx->cluster_active_stake_lamports==1000UL ); - FD_TEST( ctx->delinquent_stake_lamports == 200UL ); - - /* Voter 1 catches up, but the figure only moves on the next sample. */ - VOTED_AT( 1UL, cur+1UL ); - bank->f.slot = cur+1UL; - update_delinquent_stake( ctx, bank ); - FD_TEST( ctx->delinquent_stake_lamports==200UL ); /* no sample yet */ - - bank->f.slot = cur+DELINQUENT_SAMPLE_SLOTS; - update_delinquent_stake( ctx, bank ); - FD_TEST( !ctx->delinquent_stake_lamports ); - - /* An account that stops being a valid voter leaves the total. */ - fd_vote_stakes_update_state( vote_stakes, fork_id, &vote[ 3UL ], 0UL, 0L, 0 ); - bank->f.slot = cur+2UL*DELINQUENT_SAMPLE_SLOTS; - update_delinquent_stake( ctx, bank ); - FD_TEST( ctx->cluster_active_stake_lamports==600UL ); - FD_TEST( !ctx->delinquent_stake_lamports ); - - /* No wait_info consumer (backtest): the walk is skipped - rather than run for nothing. */ - ctx->wait_info = NULL; - ctx->delinquent_stake_lamports = 123UL; - VOTED_AT( 0UL, 0UL ); - bank->f.slot = cur+3UL*DELINQUENT_SAMPLE_SLOTS; - update_delinquent_stake( ctx, bank ); - FD_TEST( ctx->delinquent_stake_lamports==123UL ); - -#undef VOTED_AT - FD_LOG_NOTICE(( "pass: test_update_delinquent_stake" )); -} - #undef TEST_AG_VOTERS static void @@ -2581,1713 +2159,121 @@ test_slot_completed_keeps_leader_sentinel( fd_wksp_t * wksp ) { FD_LOG_NOTICE(( "pass: test_slot_completed_keeps_leader_sentinel" )); } -static void -test_eqvoc_last_fec( fd_wksp_t * wksp ) { - - static fd_replay_tile_t ctx[ 1 ]; - setup_ctx( ctx, wksp ); - fd_reasm_t * reasm = ctx->reasm; - - /* Merkle roots — arbitrary unique hashes. */ - - fd_hash_t mr_root = { .ul = { 100 } }; - fd_hash_t mr1_0 = { .ul = { 200 } }; - fd_hash_t mr1_32 = { .ul = { 300 } }; - fd_hash_t mr1_32_eqvoc = { .ul = { 999 } }; - fd_hash_t mr2_0 = { .ul = { 400 } }; - - /* 1. Insert root FEC (slot 0). chained_merkle_root is NULL for the - very first FEC in a reasm instance. The root is automatically - marked popped=1, confirmed=1 by fd_reasm_insert. */ - - init_root_fec( ctx, &mr_root ); - - /* 2. Insert FEC 0 of slot 1 (chained off root). */ - - fd_reasm_fec_t * f1_0 = ingest_fec_complete( ctx, &mr1_0, &mr_root, - 1, 0, 1, 32, 1, 0 ); - FD_TEST( f1_0 ); - - /* 3. Insert FEC 32 of slot 1 (slot_complete, chained off FEC 0). */ - - fd_reasm_fec_t * f1_32 = ingest_fec_complete( ctx, &mr1_32, &mr1_0, - 1, 32, 1, 32, 1, 1 ); - FD_TEST( f1_32 ); - - /* 4. Pop FEC 0 and process it through the replay tile path. */ - - fd_reasm_fec_t * fec = drive_one_fec( ctx, 1UL, 0U ); - - /* 5. Pop FEC 32 and process it. */ - - fec = drive_one_fec( ctx, 1UL, 32U ); - ulong first_bank_idx = fec->bank_idx; - FD_TEST( first_bank_idx!=fd_banks_root( ctx->banks )->idx ); - - /* 6. Insert equivocating FEC 32 — same (slot, fec_set_idx) but - different merkle root. Reasm detects the equivocation. */ - - fd_reasm_fec_t * f1_32_eq = ingest_fec_complete( ctx, &mr1_32_eqvoc, &mr1_0, - 1, 32, 1, 32, 1, 1 ); - FD_TEST( f1_32_eq ); - - /* 7. Verify: the equivocating FEC is NOT delivered. The eqvoc flag - is set and confirmed==0, so the gate blocks delivery. */ - - FD_TEST( f1_32_eq->eqvoc==1 ); - FD_TEST( f1_32_eq->confirmed==0 ); - FD_TEST( fd_reasm_peek( reasm )==NULL ); - FD_TEST( fd_reasm_pop ( reasm )==NULL ); - - fd_reasm_confirm( reasm, &mr1_32_eqvoc ); - - fec = drive_one_fec( ctx, 1UL, 32U ); - ulong eqvoc_bank_idx = fec->bank_idx; - FD_TEST( eqvoc_bank_idx!=first_bank_idx ); /* new bank is allocated */ - FD_TEST( fd_reasm_query( reasm, &mr1_0 )->bank_idx==eqvoc_bank_idx ); /* bank idx is updated for fec 0 */ - - /* latest mr is updated */ - fd_block_id_ele_t * block_id_ele = &ctx->block_id_arr[ fec->bank_idx ]; - FD_TEST( memcmp( &block_id_ele->latest_mr, &mr1_32_eqvoc, sizeof(fd_hash_t) ) == 0 ); /* so bad lol */ - - fd_block_id_ele_t * block_id_ele_0 = &ctx->block_id_arr[ first_bank_idx ]; - FD_TEST( memcmp( &block_id_ele_0->latest_mr, &mr1_32, sizeof(fd_hash_t) ) == 0 ); +/* Deliver one fd_rotor_replay_fec_t through the ROTOR_SIG_FEC_REPLAY + handler (it lives under IN_KIND_REPAIR, dispatched by sig). Returns + returnable_frag's value: 0 = frag consumed, 1 = kept for retry. */ +static int +deliver_rotor_fec( fd_replay_tile_t * ctx, + ulong slot, + uint fec_set_idx, + ulong parent_slot, + fd_hash_t const * parent_block_id, + fd_hash_t const * mr, + int slot_complete, + int verified ) { + ulong chunk = ctx->in[ TEST_REPAIR_IN_IDX ].chunk0; + fd_rotor_replay_fec_t * fec = fd_chunk_to_laddr( ctx->in[ TEST_REPAIR_IN_IDX ].mem, chunk ); + memset( fec, 0, sizeof(*fec) ); + fec->slot = slot; + fec->fec_set_idx = fec_set_idx; + fec->mr = *mr; + fec->parent_slot = parent_slot; + fec->parent_block_id = *parent_block_id; + fec->slot_complete = slot_complete; + fec->known_id = verified; - fd_reasm_fec_t * f2_0 = ingest_fec_complete( ctx, &mr2_0, &mr1_32_eqvoc, - 2, 0, 1, 32, 1, 0 ); - FD_TEST( f2_0 ); - fec = drive_one_fec( ctx, 2UL, 0U ); - FD_TEST( fec->bank_idx!=first_bank_idx && fec->bank_idx!=eqvoc_bank_idx ); /* used to bank hash mismatch */ + /* Clear the pacing gate at the top of the handler + (execrp_idle_cnt >= 2*in_cnt || is_leader) so the FEC reaches the + drain gate rather than being kept for retry. */ + ctx->execrp_idle_cnt = 2UL*ctx->in_cnt + 4UL; - FD_LOG_NOTICE(( "pass: test_eqvoc_fec_gate" )); + return returnable_frag( ctx, TEST_REPAIR_IN_IDX, 0UL, ROTOR_SIG_FEC_REPLAY, chunk, + sizeof(fd_rotor_replay_fec_t), 0UL, 0UL, + fd_frag_meta_ts_comp( fd_tickcount() ), test_stem ); } -static void -test_eqvoc_first_fec( fd_wksp_t * wksp ) { - static fd_replay_tile_t ctx[ 1 ]; - setup_ctx( ctx, wksp ); - fd_reasm_t * reasm = ctx->reasm; - - fd_hash_t mr_root = { .ul = { 100 } }; - fd_hash_t mr1_0 = { .ul = { 200 } }; - fd_hash_t mr1_32 = { .ul = { 300 } }; - - init_root_fec( ctx, &mr_root ); - - fd_reasm_fec_t * f1_0 = ingest_fec_complete( ctx, &mr1_0, &mr_root, - 1, 0, 1, 32, 1, 0 ); - FD_TEST( f1_0 ); - - fd_reasm_fec_t * f1_32 = ingest_fec_complete( ctx, &mr1_32, &mr1_0, - 1, 32, 1, 32, 1, 0 ); - FD_TEST( f1_32 ); - - fd_reasm_fec_t * fec = drive_one_fec( ctx, 1UL, 0U ); - - fec = drive_one_fec( ctx, 1UL, 32U ); - ulong first_bank_idx = fec->bank_idx; - FD_TEST( first_bank_idx!=fd_banks_root( ctx->banks )->idx ); - - /* Insert equivocating version */ - fd_hash_t mr1_32_ = { .ul = { 32, 1 } }; - fd_hash_t mr1_0_ = { .ul = { 0, 1 } }; - fd_reasm_fec_t * f1_32_eq = ingest_fec_complete( ctx, &mr1_32_, &mr1_0_, - 1, 32, 1, 32, 1, 1 ); - FD_TEST( f1_32_eq->eqvoc ); - - fd_reasm_fec_t * fd_1_0_eq = ingest_fec_complete( ctx, &mr1_0_, &mr_root, - 1, 0, 1, 32, 1, 0 ); - FD_TEST( fd_1_0_eq->eqvoc ); - - /* no delivery of eqvoc */ - FD_TEST( fd_reasm_peek( reasm )==NULL ); - - /* confirm equivocating version */ - fd_reasm_confirm( reasm, &mr1_32_ ); - - /* delivery of confirmed version */ - fec = drive_one_fec( ctx, 1UL, 0U ); - ulong eqvoc_bank_idx = fec->bank_idx; - FD_TEST( eqvoc_bank_idx!=first_bank_idx ); - - fec = drive_one_fec( ctx, 1UL, 32U ); - FD_TEST( fec->bank_idx==eqvoc_bank_idx ); - - FD_TEST( fd_reasm_peek( reasm )==NULL ); - - FD_LOG_NOTICE(( "pass: test_eqvoc_first_fec" )); +/* Like deliver_rotor_fec, but also sets fec->block_id -- the field the + deliver_from_root recovery path always populates (non-zero), and that + replay dedups on to skip already-replayed blocks. */ +static int +deliver_rotor_fec_bid( fd_replay_tile_t * ctx, + ulong slot, + uint fec_set_idx, + ulong parent_slot, + fd_hash_t const * parent_block_id, + fd_hash_t const * block_id, + fd_hash_t const * mr, + int slot_complete, + int verified ) { + ulong chunk = ctx->in[ TEST_REPAIR_IN_IDX ].chunk0; + fd_rotor_replay_fec_t * fec = fd_chunk_to_laddr( ctx->in[ TEST_REPAIR_IN_IDX ].mem, chunk ); + memset( fec, 0, sizeof(*fec) ); + fec->slot = slot; + fec->fec_set_idx = fec_set_idx; + fec->mr = *mr; + fec->parent_slot = parent_slot; + fec->parent_block_id = *parent_block_id; + fec->block_id = *block_id; + fec->slot_complete = slot_complete; + fec->known_id = verified; + ctx->execrp_idle_cnt = 2UL*ctx->in_cnt + 4UL; + return returnable_frag( ctx, TEST_REPAIR_IN_IDX, 0UL, ROTOR_SIG_FEC_REPLAY, chunk, + sizeof(fd_rotor_replay_fec_t), 0UL, 0UL, + fd_frag_meta_ts_comp( fd_tickcount() ), test_stem ); } -/* Happy path: 4 FECs in slot 1, all popped and processed normally. - After fd_reasm_confirm, no FECs should be re-delivered. */ +/* test_process_rotor_fec_skip_replayed: on recovery rotor re-delivers + the entire ancestry path from the rotor root, but most of those + blocks are already replayed. Replay must SKIP a redelivered FEC + whose (slot, block_id) names an already-replayed block -- returning + before it touches the store or creates a duplicate bank -- while + still processing a FEC for a block it has NOT yet replayed. + Observability: the skip returns at the top of process_rotor_fec, + before the store lock, so store_query_cnt does NOT advance for a + skipped FEC. The control FEC forces payload pinning to fail after a + successful lookup and verifies replay abandons it before creating a + bank or changing the block-id map. */ static void -test_confirm( fd_wksp_t * wksp ) { - +test_process_rotor_fec_skip_replayed( fd_wksp_t * wksp ) { static fd_replay_tile_t ctx[ 1 ]; setup_ctx( ctx, wksp ); - fd_reasm_t * reasm = ctx->reasm; fd_hash_t mr_root = { .ul = { 100 } }; - fd_hash_t mr1_0 = { .ul = { 200 } }; - fd_hash_t mr1_32 = { .ul = { 300 } }; - fd_hash_t mr1_64 = { .ul = { 400 } }; - fd_hash_t mr1_96 = { .ul = { 500 } }; - - /* Root FEC (slot 0). */ + init_root( ctx, &mr_root ); - init_root_fec( ctx, &mr_root ); - - /* Slot 1: 4 FECs chained sequentially. */ - - fd_reasm_fec_t * f1_0 = ingest_fec_complete( ctx, &mr1_0, &mr_root, - 1, 0, 1, 32, 1, 0 ); - FD_TEST( f1_0 ); - - fd_reasm_fec_t * f1_32 = ingest_fec_complete( ctx, &mr1_32, &mr1_0, - 1, 32, 1, 32, 1, 0 ); - FD_TEST( f1_32 ); + fd_bank_t * root_bank = fd_banks_root( ctx->banks ); + FD_TEST( root_bank ); + root_bank->state = FD_BANK_STATE_FROZEN; - fd_reasm_fec_t * f1_64 = ingest_fec_complete( ctx, &mr1_64, &mr1_32, - 1, 64, 1, 32, 1, 0 ); - FD_TEST( f1_64 ); + /* Register the root as a resolvable parent (keyed {0, parent_bid}) so + a NOT-yet-replayed FEC 0 can resolve its parent and reach the store + query -- i.e. the control FEC is not dropped earlier for a missing + parent. */ + fd_hash_t parent_bid = { .ul = { 0xBEEFUL } }; + fd_block_id_ele_t * root_ele = &ctx->block_id_arr[ root_bank->idx ]; + root_ele->block_info = ag_block_id( 0UL, parent_bid.uc ); + root_ele->block_id_seen = 1; + FD_TEST( fd_ag_block_id_map_ele_insert( ctx->ag_block_id_map, root_ele, ctx->block_id_arr ) ); - fd_reasm_fec_t * f1_96 = ingest_fec_complete( ctx, &mr1_96, &mr1_64, - 1, 96, 1, 32, 1, 1 ); - FD_TEST( f1_96 ); + /* An ALREADY-REPLAYED complete block at slot 5 with block_id B: a live + bank plus an ele keyed by {5, B} with block_id_seen=1. */ + fd_hash_t B = { .ul = { 0x5150UL } }; + fd_bank_t * bank5 = fd_banks_new_bank( ctx->banks, root_bank->idx, 0L, 0 ); + FD_TEST( bank5 ); + bank5->state = FD_BANK_STATE_FROZEN; + fd_block_id_ele_t * ele5 = &ctx->block_id_arr[ bank5->idx ]; + ele5->slot = 5UL; + ele5->bank_seq = bank5->bank_seq; + ele5->latest_mr = B; + ele5->latest_fec_idx = 0U; + ele5->block_id_seen = 1; + ele5->block_info = ag_block_id( 5UL, B.uc ); + FD_TEST( fd_ag_block_id_map_ele_insert( ctx->ag_block_id_map, ele5, ctx->block_id_arr ) ); - /* Pop and process all 4 FECs. */ - - fd_reasm_fec_t * fec = drive_one_fec( ctx, 1UL, 0U ); - ulong bank_idx = fec->bank_idx; - - fec = drive_one_fec( ctx, 1UL, 32U ); - FD_TEST( fec->bank_idx==bank_idx ); - - fec = drive_one_fec( ctx, 1UL, 64U ); - FD_TEST( fec->bank_idx==bank_idx ); - - fec = drive_one_fec( ctx, 1UL, 96U ); - FD_TEST( fec->bank_idx==bank_idx ); - - /* Queue should be empty. */ - - FD_TEST( fd_reasm_peek( reasm )==NULL ); - - /* Confirm the slot. No equivocation occurred, so no FEC should be - re-delivered. */ - - fd_reasm_confirm( reasm, &mr1_96 ); - - FD_TEST( fd_reasm_peek( reasm )==NULL ); - FD_TEST( fd_reasm_pop ( reasm )==NULL ); - - FD_LOG_NOTICE(( "pass: test_confirm" )); -} - -/* Stale redeliver: insert, pop, process, evict, then reinsert the same - FEC sets. Verifies that the system handles re-receipt of evicted - FECs gracefully — a new bank is allocated and execution is - scheduled. */ - -static void -test_stale_redeliver( fd_wksp_t * wksp ) { - - static fd_replay_tile_t ctx[ 1 ]; - setup_ctx( ctx, wksp ); - fd_reasm_t * reasm = ctx->reasm; - - fd_hash_t mr_root = { .ul = { 100 } }; - fd_hash_t mr1_0 = { .ul = { 200 } }; - fd_hash_t mr1_32 = { .ul = { 300 } }; - fd_hash_t mr1_64 = { .ul = { 400 } }; - - /* 1. Insert root FEC (slot 0). */ - - init_root_fec( ctx, &mr_root ); - - /* 2. Insert 3 FEC sets for slot 1. */ - - fd_reasm_fec_t * f1_0 = ingest_fec_complete( ctx, &mr1_0, &mr_root, - 1, 0, 1, 32, 1, 0 ); - FD_TEST( f1_0 ); - - fd_reasm_fec_t * f1_32 = ingest_fec_complete( ctx, &mr1_32, &mr1_0, - 1, 32, 1, 32, 1, 0 ); - FD_TEST( f1_32 ); - - fd_reasm_fec_t * f1_64 = ingest_fec_complete( ctx, &mr1_64, &mr1_32, - 1, 64, 1, 32, 1, 1 ); - FD_TEST( f1_64 ); - - /* 3. Pop and process all 3. */ - - fd_reasm_fec_t * fec = drive_one_fec( ctx, 1UL, 0U ); - - fec = drive_one_fec( ctx, 1UL, 32U ); - - fec = drive_one_fec( ctx, 1UL, 64U ); - - /* After processing, bank 1 should have been allocated. */ - ulong first_bank_idx = fec->bank_idx; - FD_TEST( first_bank_idx!=fd_banks_root( ctx->banks )->idx ); - FD_TEST( fd_banks_pool_used_cnt( ctx->banks )==2UL ); - - /* Queue should be empty. */ - FD_TEST( fd_reasm_peek( reasm )==NULL ); - - /* 4. Evict the slot 1 chain by removing the leaf (FEC 64) from the - frontier. fd_reasm_remove walks up to fec_set_idx==0, so FECs 0, - 32, 64 are all evicted. */ - - fd_reasm_fec_t * leaf = fd_reasm_query( reasm, &mr1_64 ); - FD_TEST( leaf ); - fd_reasm_fec_t * evicted_head = fd_reasm_remove( reasm, leaf, NULL, NULL ); - FD_TEST( evicted_head ); - - /* Walk the evicted chain and release each element back to the pool. - The chain is linked via child pointers (linear, no branches). */ - fd_reasm_fec_t * curr = evicted_head; - while( curr ) { - fd_reasm_fec_t * next = fd_reasm_child( reasm, curr ); - fd_reasm_pool_release( reasm, curr ); - curr = next; - } - - /* All 3 FECs should be gone from reasm. */ - FD_TEST( !fd_reasm_query( reasm, &mr1_0 ) ); - FD_TEST( !fd_reasm_query( reasm, &mr1_32 ) ); - FD_TEST( !fd_reasm_query( reasm, &mr1_64 ) ); - - /* 5. Reinsert the same 3 FECs. */ - - f1_0 = ingest_fec_complete( ctx, &mr1_0, &mr_root, - 1, 0, 1, 32, 1, 0 ); - FD_TEST( f1_0 ); - - f1_32 = ingest_fec_complete( ctx, &mr1_32, &mr1_0, - 1, 32, 1, 32, 1, 0 ); - FD_TEST( f1_32 ); - - f1_64 = ingest_fec_complete( ctx, &mr1_64, &mr1_32, - 1, 64, 1, 32, 1, 1 ); - FD_TEST( f1_64 ); - - /* 6. Pop and process all 3 again. */ - - ulong sched_ingest_cnt_before = mock_sched_fec_ingest_cnt; - - fec = drive_one_fec( ctx, 1UL, 0U ); - - fec = drive_one_fec( ctx, 1UL, 32U ); - - fec = drive_one_fec( ctx, 1UL, 64U ); - - /* 7. A NEW bank should have been allocated (fec_set_idx==0 always - allocates a new bank in insert_fec_set). */ - FD_TEST( fec->bank_idx!=first_bank_idx ); - FD_TEST( fd_banks_pool_used_cnt( ctx->banks )==3UL ); - - /* 8. Execution was scheduled for the reinserted FECs. */ - FD_TEST( mock_sched_fec_ingest_cnt>sched_ingest_cnt_before ); - - /* Queue should be drained. */ - FD_TEST( fd_reasm_peek( reasm )==NULL ); - - FD_LOG_NOTICE(( "pass: test_stale_redeliver" )); -} - -/* Mid-slot equivocation with eviction: slot 1 equivocates mid-slot. - We deliver the full version A first (FEC 0, 32, 64), then the entire - slot 1 chain gets evicted from reasm. We reinsert FEC 0 with the - same merkle root (it is shared between both versions), which gets a - fresh bank. Then we receive version B (FEC 32' and 64' with - different merkle roots). Since version A's FECs were evicted, reasm - does NOT detect equivocation. The replay tile allocates a new bank - starting from FEC 0 for version B. */ - -static void -test_eqvoc_mid_slot_evicted( fd_wksp_t * wksp ) { - - static fd_replay_tile_t ctx[ 1 ]; - setup_ctx( ctx, wksp ); - fd_reasm_t * reasm = ctx->reasm; - - /* Merkle roots — arbitrary unique hashes. */ - - fd_hash_t mr_root = { .ul = { 100 } }; - fd_hash_t mr1_0 = { .ul = { 200 } }; - fd_hash_t mr1_32 = { .ul = { 300 } }; - fd_hash_t mr1_64 = { .ul = { 400 } }; - fd_hash_t mr1_32_prime = { .ul = { 500 } }; - fd_hash_t mr1_64_prime = { .ul = { 600 } }; - - /* 1. Insert root FEC (slot 0). */ - - init_root_fec( ctx, &mr_root ); - - /* 2. Insert 3 FEC sets for slot 1 version A. */ - - fd_reasm_fec_t * f1_0 = ingest_fec_complete( ctx, &mr1_0, &mr_root, - 1, 0, 1, 32, 1, 0 ); - FD_TEST( f1_0 ); - - fd_reasm_fec_t * f1_32 = ingest_fec_complete( ctx, &mr1_32, &mr1_0, - 1, 32, 1, 32, 1, 0 ); - FD_TEST( f1_32 ); - - fd_reasm_fec_t * f1_64 = ingest_fec_complete( ctx, &mr1_64, &mr1_32, - 1, 64, 1, 32, 1, 1 ); - FD_TEST( f1_64 ); - - /* 3. Pop and process all 3 FEC sets. After this, bank 1 is allocated - for slot 1. */ - - fd_reasm_fec_t * fec = drive_one_fec( ctx, 1UL, 0U ); - - fec = drive_one_fec( ctx, 1UL, 32U ); - - fec = drive_one_fec( ctx, 1UL, 64U ); - ulong version_a_bank_idx = fec->bank_idx; - FD_TEST( version_a_bank_idx!=fd_banks_root( ctx->banks )->idx ); - - FD_TEST( fd_reasm_peek( reasm )==NULL ); - - /* 4. Evict the entire slot 1 chain from reasm. fd_reasm_remove walks - UP from the leaf (FEC 64) and stops at fec_set_idx==0, so FEC 0, - 32, and 64 are all evicted. Walk the evicted chain (linked via - child pointers) and release each element back to the pool. */ - - fd_reasm_fec_t * evicted = fd_reasm_remove( reasm, f1_64, NULL, NULL ); - FD_TEST( evicted ); - while( evicted ) { - fd_reasm_fec_t * next = fd_reasm_child( reasm, evicted ); - fd_reasm_pool_release( reasm, evicted ); - evicted = next; - } - - /* All 3 FECs should be gone from reasm. */ - - FD_TEST( !fd_reasm_query( reasm, &mr1_0 ) ); - FD_TEST( !fd_reasm_query( reasm, &mr1_32 ) ); - FD_TEST( !fd_reasm_query( reasm, &mr1_64 ) ); - - /* 5. Reinsert FEC 0 with the SAME merkle root (mr1_0). Since the - original was evicted, reasm does not detect equivocation. Pop and - process it: allocates a new bank (bank_idx=2). */ - - fd_reasm_fec_t * f1_0_new = ingest_fec_complete( ctx, &mr1_0, &mr_root, - 1, 0, 1, 32, 1, 0 ); - FD_TEST( f1_0_new ); - FD_TEST( !f1_0_new->eqvoc ); - - fec = drive_one_fec( ctx, 1UL, 0U ); - ulong version_b_bank_idx = fec->bank_idx; - FD_TEST( version_b_bank_idx!=version_a_bank_idx ); - - /* 6. Insert version B's FEC 32' and 64' (different merkle roots from - version A) chaining off the reinserted FEC 0. Reasm does NOT - detect equivocation because version A was evicted. */ - - fd_reasm_fec_t * f1_32p = ingest_fec_complete( ctx, &mr1_32_prime, &mr1_0, - 1, 32, 1, 32, 1, 0 ); - FD_TEST( f1_32p ); - FD_TEST( !f1_32p->eqvoc ); - - fd_reasm_fec_t * f1_64p = ingest_fec_complete( ctx, &mr1_64_prime, &mr1_32_prime, - 1, 64, 1, 32, 1, 1 ); - FD_TEST( f1_64p ); - FD_TEST( !f1_64p->eqvoc ); - - /* 7. Pop and process version B's FEC sets. They should inherit the - fresh bank (bank_idx=2) from the reinserted FEC 0. */ - - fec = drive_one_fec( ctx, 1UL, 32U ); - FD_TEST( fec->bank_idx==version_b_bank_idx ); - - fec = drive_one_fec( ctx, 1UL, 64U ); - FD_TEST( fec->bank_idx==version_b_bank_idx ); - - FD_TEST( fd_reasm_peek( reasm )==NULL ); - - FD_LOG_NOTICE(( "pass: test_eqvoc_mid_slot_evicted" )); -} - -/* Banks eviction across two consecutive slots: slots 1 and 2 each have - 2 FECs. We pop and process all of slot 1 (bank 1) and the first FEC - of slot 2 (bank 2). Then banks evicts both banks (simulated by - bumping bank_seq so the replay tile sees a seq mismatch). Backfill - reconstructs at most one slot at a time: slot 2's second FEC rebuilds - slot 1, then a future slot 3 FEC rebuilds slot 2. */ - -static void -test_banks_evict_backfill( fd_wksp_t * wksp ) { - - static fd_replay_tile_t ctx[ 1 ]; - setup_ctx( ctx, wksp ); - - fd_hash_t mr_root = { .ul = { 100 } }; - fd_hash_t mr1_0 = { .ul = { 200 } }; - fd_hash_t mr1_32 = { .ul = { 300 } }; - fd_hash_t mr2_0 = { .ul = { 400 } }; - fd_hash_t mr2_32 = { .ul = { 500 } }; - fd_hash_t mr3_0 = { .ul = { 600 } }; - - /* 1. Root FEC (slot 0). */ - - init_root_fec( ctx, &mr_root ); - - /* 2. Insert slot 1: 2 FECs (0 and 32), slot_complete on FEC 32. */ - - fd_reasm_fec_t * f1_0 = ingest_fec_complete( ctx, &mr1_0, &mr_root, - 1, 0, 1, 32, 1, 0 ); - FD_TEST( f1_0 ); - - fd_reasm_fec_t * f1_32 = ingest_fec_complete( ctx, &mr1_32, &mr1_0, - 1, 32, 1, 32, 1, 1 ); - FD_TEST( f1_32 ); - - /* 3. Insert slot 2: 2 FECs (0 and 32), slot_complete on FEC 32. */ - - fd_reasm_fec_t * f2_0 = ingest_fec_complete( ctx, &mr2_0, &mr1_32, - 2, 0, 1, 32, 1, 0 ); - FD_TEST( f2_0 ); - - fd_reasm_fec_t * f2_32 = ingest_fec_complete( ctx, &mr2_32, &mr2_0, - 2, 32, 1, 32, 1, 1 ); - FD_TEST( f2_32 ); - - /* 4. Pop and process all of slot 1 → bank 1. */ - - fd_reasm_fec_t * fec = drive_one_fec( ctx, 1UL, 0U ); - - fec = drive_one_fec( ctx, 1UL, 32U ); - ulong slot1_bank_idx = fec->bank_idx; - FD_TEST( slot1_bank_idx!=fd_banks_root( ctx->banks )->idx ); - - /* 5. Pop and process first FEC of slot 2 → bank 2. */ - - fec = drive_one_fec( ctx, 2UL, 0U ); - ulong slot2_bank_idx = fec->bank_idx; - FD_TEST( slot2_bank_idx!=slot1_bank_idx ); - - /* 6. Simulate banks evicting both bank 1 and bank 2 by bumping their - bank_seq. Now the replay tile will see a seq mismatch and treat - them as evicted. */ - - fd_banks_bank_query( ctx->banks, slot1_bank_idx )->bank_seq = 999UL; - fd_banks_bank_query( ctx->banks, slot2_bank_idx )->bank_seq = 999UL; - - /* 7. Pop the second FEC of slot 2 and process it. Backfill walks - through invalid slots 2 and 1, but retains and reconstructs only - the oldest slot nearest the valid root. */ - - fec = drive_one_fec( ctx, 2UL, 32U ); - - /* 8. Verify: slot 1 was backfilled with a new bank (bank 3). */ - - ulong new_slot1_bank_idx = f1_32->bank_idx; - FD_TEST( new_slot1_bank_idx!=slot1_bank_idx ); - FD_TEST( f1_0->bank_idx==new_slot1_bank_idx ); - - /* 9. Verify: slot 2 has not been backfilled yet. */ - - FD_TEST( f2_0->bank_idx==slot2_bank_idx ); - FD_TEST( f2_32->bank_idx==UINT_MAX ); - FD_TEST( fd_banks_pool_used_cnt( ctx->banks )==4UL ); - - /* 10. A future FEC chaining off slot 2 drives the next round of - backfill. Slot 2 should now receive a replacement bank while - slot 3 remains deferred. */ - - fd_reasm_fec_t * f3_0 = ingest_fec_complete( ctx, &mr3_0, &mr2_32, - 3, 0, 1, 32, 1, 1 ); - FD_TEST( f3_0 ); - - fec = drive_one_fec( ctx, 3UL, 0U ); - - ulong new_slot2_bank_idx = f2_32->bank_idx; - FD_TEST( new_slot2_bank_idx!=slot2_bank_idx ); - FD_TEST( new_slot2_bank_idx!=new_slot1_bank_idx ); - FD_TEST( f2_0->bank_idx==new_slot2_bank_idx ); - FD_TEST( f3_0->bank_idx==UINT_MAX ); - FD_TEST( fd_banks_pool_used_cnt( ctx->banks )==5UL ); - - FD_LOG_NOTICE(( "pass: test_banks_evict_backfill" )); -} - -/* Backfill keys the replacement of an evicted reset bank under the - reset block id before the replacement freezes. Becoming leader must - wait for the freeze. */ - -static void -test_become_leader_waits_for_rereplayed_reset( fd_wksp_t * wksp ) { - static fd_replay_tile_t ctx[ 1 ]; - setup_ctx( ctx, wksp ); - - fd_hash_t mr_root = { .ul = { 100 } }; - fd_hash_t mr1_0 = { .ul = { 200 } }; - fd_hash_t mr1_32 = { .ul = { 300 } }; - fd_hash_t mr2_0 = { .ul = { 400 } }; - - init_root_fec( ctx, &mr_root ); - fd_reasm_fec_t * f1_0 = ingest_fec_complete( ctx, &mr1_0, &mr_root, 1, 0, 1, 32, 1, 0 ); - ingest_fec_complete( ctx, &mr1_32, &mr1_0, 1, 32, 1, 32, 1, 1 ); - drive_one_fec( ctx, 1UL, 0U ); - drive_one_fec( ctx, 1UL, 32U ); - - fd_bank_t * reset = fd_banks_bank_query( ctx->banks, f1_0->bank_idx ); - reset->state = FD_BANK_STATE_FROZEN; - reset->refcnt = 0UL; - - ctx->reset_cmr = mr1_32; - ctx->reset_slot = 1UL; - ctx->next_leader_slot = 4UL; - ctx->next_leader_tickcount = 0L; - - FD_TEST( fd_banks_get_evictable_bank( ctx->banks, NULL )==reset->idx ); - FD_TEST( !try_become_leader( ctx, test_stem ) ); - fd_banks_prune_cancel_info_t cancel[ 1 ]; - FD_TEST( fd_banks_prune_one_bank( ctx->banks, cancel ) ); - FD_TEST( !try_become_leader( ctx, test_stem ) ); - - ingest_fec_complete( ctx, &mr2_0, &mr1_32, 2, 0, 1, 32, 1, 1 ); - drive_one_fec( ctx, 2UL, 0U ); - - fd_block_id_ele_t * ele = fd_block_id_map_ele_query( ctx->block_id_map, &mr1_32, NULL, ctx->block_id_arr ); - FD_TEST( ele ); - fd_bank_t * replacement = fd_banks_bank_query( ctx->banks, fd_block_id_ele_get_idx( ctx->block_id_arr, ele ) ); - FD_TEST( replacement && replacement->bank_seq==ele->bank_seq ); - FD_TEST( replacement->state==FD_BANK_STATE_INIT ); - FD_TEST( !try_become_leader( ctx, test_stem ) ); - - start_non_epoch_boundary_fec( ctx, f1_0, 0 ); - FD_TEST( replacement->state==FD_BANK_STATE_REPLAYABLE ); - FD_TEST( !try_become_leader( ctx, test_stem ) ); - - fd_banks_mark_bank_frozen( replacement ); - FD_TEST( try_become_leader( ctx, test_stem ) ); - FD_TEST( ctx->leader_bank->parent_idx==replacement->idx ); - - FD_LOG_NOTICE(( "pass: test_become_leader_waits_for_rereplayed_reset" )); -} - -/* The leader-start grace check must see the slot of a reset bank child - that is still in FD_BANK_STATE_INIT, whose f is stale from the - previous pool occupant. */ - -static void -test_become_leader_grace_init_child( fd_wksp_t * wksp ) { - static fd_replay_tile_t ctx[ 1 ]; - setup_ctx( ctx, wksp ); - - fd_hash_t mr_root = { .ul = { 100 } }; - fd_hash_t mr1_0 = { .ul = { 200 } }; - fd_hash_t mr1_32 = { .ul = { 300 } }; - fd_hash_t mr2_0 = { .ul = { 400 } }; - - init_root_fec( ctx, &mr_root ); - fd_reasm_fec_t * f1_0 = ingest_fec_complete( ctx, &mr1_0, &mr_root, 1, 0, 1, 32, 1, 0 ); - ingest_fec_complete( ctx, &mr1_32, &mr1_0, 1, 32, 1, 32, 1, 1 ); - drive_one_fec( ctx, 1UL, 0U ); - drive_one_fec( ctx, 1UL, 32U ); - - fd_bank_t * reset = fd_banks_bank_query( ctx->banks, f1_0->bank_idx ); - start_non_epoch_boundary_fec( ctx, f1_0, 0 ); - fd_banks_mark_bank_frozen( reset ); - - fd_reasm_fec_t * f2_0 = ingest_fec_complete( ctx, &mr2_0, &mr1_32, 2, 0, 1, 32, 1, 1 ); - drive_one_fec( ctx, 2UL, 0U ); - fd_bank_t * child = fd_banks_bank_query( ctx->banks, f2_0->bank_idx ); - FD_TEST( child->parent_idx==reset->idx ); - FD_TEST( child->state==FD_BANK_STATE_INIT ); - - ctx->reset_cmr = mr1_32; - ctx->reset_slot = 1UL; - ctx->next_leader_slot = 4UL; - ctx->next_leader_tickcount = fd_tickcount(); - - FD_TEST( !try_become_leader( ctx, test_stem ) ); - - ctx->next_leader_tickcount = LONG_MIN/2L; - FD_TEST( try_become_leader( ctx, test_stem ) ); - - FD_LOG_NOTICE(( "pass: test_become_leader_grace_init_child" )); -} - -/* Tower can name a reset block whose replacement is still replaying. - The reset must wait for the tower update that follows the freeze. */ - -static void -test_tower_reset_waits_for_rereplayed_bank( fd_wksp_t * wksp ) { - static fd_replay_tile_t ctx[ 1 ]; - setup_ctx( ctx, wksp ); - - fd_hash_t mr_root = { .ul = { 100 } }; - fd_hash_t mr1_0 = { .ul = { 200 } }; - fd_hash_t mr1_32 = { .ul = { 300 } }; - fd_hash_t mr2_0 = { .ul = { 400 } }; - - init_root_fec( ctx, &mr_root ); - fd_reasm_fec_t * f1_0 = ingest_fec_complete( ctx, &mr1_0, &mr_root, 1, 0, 1, 32, 1, 0 ); - ingest_fec_complete( ctx, &mr1_32, &mr1_0, 1, 32, 1, 32, 1, 1 ); - drive_one_fec( ctx, 1UL, 0U ); - drive_one_fec( ctx, 1UL, 32U ); - - fd_bank_t * evicted = fd_banks_bank_query( ctx->banks, f1_0->bank_idx ); - evicted->state = FD_BANK_STATE_FROZEN; - evicted->refcnt = 0UL; - FD_TEST( fd_banks_get_evictable_bank( ctx->banks, NULL )==evicted->idx ); - fd_banks_prune_cancel_info_t cancel[ 1 ]; - FD_TEST( fd_banks_prune_one_bank( ctx->banks, cancel ) ); - - ingest_fec_complete( ctx, &mr2_0, &mr1_32, 2, 0, 1, 32, 1, 1 ); - drive_one_fec( ctx, 2UL, 0U ); - start_non_epoch_boundary_fec( ctx, f1_0, 0 ); - fd_bank_t * replacement = fd_banks_bank_query( ctx->banks, f1_0->bank_idx ); - FD_TEST( replacement && replacement->state==FD_BANK_STATE_REPLAYABLE ); - - fd_bank_t * root = fd_banks_root( ctx->banks ); - fd_tower_slot_done_t msg = { - .replay_slot = root->f.slot, - .replay_bank_idx = root->idx, - .vote_slot = ULONG_MAX, - .reset_slot = 1UL, - .reset_block_id = mr1_32, - .root_slot = ULONG_MAX, - }; - - static ulong test_metrics[ FD_METRICS_TOTAL_SZ/sizeof(ulong) ]; - volatile ulong * saved_metrics_tl = fd_metrics_tl; - fd_metrics_tl = test_metrics; - ulong out_idx = ctx->replay_out->idx; - ulong seq0 = test_stem_seqs[ out_idx ]; - - root->refcnt++; - process_tower_slot_done( ctx, test_stem, &msg, 0UL ); - FD_TEST( fd_hash_eq( &ctx->reset_cmr, &mr_root ) ); - FD_TEST( ctx->reset_slot==0UL ); - FD_TEST( test_stem_seqs[ out_idx ]==seq0 ); - - fd_banks_mark_bank_frozen( replacement ); - root->refcnt++; - process_tower_slot_done( ctx, test_stem, &msg, 1UL ); - fd_metrics_tl = saved_metrics_tl; - FD_TEST( fd_hash_eq( &ctx->reset_cmr, &mr1_32 ) ); - FD_TEST( ctx->reset_slot==1UL ); - FD_TEST( test_stem_seqs[ out_idx ]==seq0+1UL ); - fd_frag_meta_t const * meta = test_stem_mcaches[ out_idx ] + fd_mcache_line_idx( seq0, test_stem_depths[ out_idx ] ); - FD_TEST( meta->sig==REPLAY_SIG_RESET ); - fd_poh_reset_t const * reset = fd_chunk_to_laddr_const( ctx->replay_out->mem, meta->chunk ); - FD_TEST( reset->bank_idx==replacement->idx ); - - FD_LOG_NOTICE(( "pass: test_tower_reset_waits_for_rereplayed_bank" )); -} - -static void -test_backfill_partial_sched_capacity( fd_wksp_t * wksp ) { - static fd_replay_tile_t ctx[ 1 ]; - setup_ctx( ctx, wksp ); - - fd_hash_t mr_root = { .ul = { 100 } }; - fd_hash_t mr1_0 = { .ul = { 200 } }; - fd_hash_t mr1_32 = { .ul = { 300 } }; - fd_hash_t mr1_64 = { .ul = { 400 } }; - fd_hash_t mr1_96 = { .ul = { 500 } }; - - init_root_fec( ctx, &mr_root ); - - fd_reasm_fec_t * f1_0 = ingest_fec_complete( ctx, &mr1_0, &mr_root, - 1, 0, 1, 32, 1, 0 ); - FD_TEST( f1_0 ); - - fd_reasm_fec_t * f1_32 = ingest_fec_complete( ctx, &mr1_32, &mr1_0, - 1, 32, 1, 32, 1, 0 ); - FD_TEST( f1_32 ); - - fd_reasm_fec_t * f1_64 = ingest_fec_complete( ctx, &mr1_64, &mr1_32, - 1, 64, 1, 32, 1, 0 ); - FD_TEST( f1_64 ); - - fd_reasm_fec_t * f1_96 = ingest_fec_complete( ctx, &mr1_96, &mr1_64, - 1, 96, 1, 32, 1, 1 ); - FD_TEST( f1_96 ); - - mock_sched_capacity = 2UL; - ulong sched_ingest_cnt = mock_sched_fec_ingest_cnt; - backfill_fec_sets( ctx, test_stem, f1_96 ); - - FD_TEST( mock_sched_fec_ingest_cnt==sched_ingest_cnt+2UL ); - FD_TEST( f1_0->bank_idx!=UINT_MAX ); - FD_TEST( f1_32->bank_idx==f1_0->bank_idx ); - FD_TEST( f1_64->bank_idx==UINT_MAX ); - FD_TEST( f1_96->bank_idx==UINT_MAX ); - - FD_LOG_NOTICE(( "pass: test_backfill_partial_sched_capacity" )); -} - -/* Partial execution eviction: slot 1 has 4 FECs (0, 32, 64, 96) with 96 - NOT slot_complete. We pop and process only the first 2 (FEC 0 and - 32), which sets bank_idx=1 on those FECs. FECs 64 and 96 remain - unprocessed with bank_idx=UINT_MAX. Then we evict the frontier leaf - (FEC 96). fd_reasm_remove walks UP from the leaf checking that each - node shares the same bank_idx as the tail. When it reaches FEC 32 - (bank_idx=1) while tail has bank_idx=UINT_MAX, make sure fd_reasm.c - wouldn't crash. */ - -static void -test_partial_exec_evict( fd_wksp_t * wksp ) { - - static fd_replay_tile_t ctx[ 1 ]; - setup_ctx( ctx, wksp ); - fd_reasm_t * reasm = ctx->reasm; - - fd_hash_t mr_root = { .ul = { 100 } }; - fd_hash_t mr1_0 = { .ul = { 200 } }; - fd_hash_t mr1_32 = { .ul = { 300 } }; - fd_hash_t mr1_64 = { .ul = { 400 } }; - fd_hash_t mr1_96 = { .ul = { 500 } }; - - /* 1. Root FEC (slot 0). */ - - init_root_fec( ctx, &mr_root ); - - /* 2. Insert 4 FECs for slot 1. Crucially, FEC 96 is NOT - slot_complete — the slot is still incomplete, making FEC 96 a valid - eviction candidate via the unconfirmed evictable leaf path. */ - - fd_reasm_fec_t * f1_0 = ingest_fec_complete( ctx, &mr1_0, &mr_root, - 1, 0, 1, 32, 1, 0 ); - FD_TEST( f1_0 ); - - fd_reasm_fec_t * f1_32 = ingest_fec_complete( ctx, &mr1_32, &mr1_0, - 1, 32, 1, 32, 1, 0 ); - FD_TEST( f1_32 ); - - fd_reasm_fec_t * f1_64 = ingest_fec_complete( ctx, &mr1_64, &mr1_32, - 1, 64, 1, 32, 1, 0 ); - FD_TEST( f1_64 ); - - fd_reasm_fec_t * f1_96 = ingest_fec_complete( ctx, &mr1_96, &mr1_64, - 1, 96, 1, 32, 1, 0 ); - FD_TEST( f1_96 ); - - /* 3. Pop and process only the first 2 FECs. After this, FEC 0 and - 32 have bank_idx=1. FECs 64 and 96 remain in the delivery queue - with bank_idx=UINT_MAX. */ - - fd_reasm_fec_t * fec = drive_one_fec( ctx, 1UL, 0U ); - - fec = drive_one_fec( ctx, 1UL, 32U ); - ulong partial_bank_idx = fec->bank_idx; - FD_TEST( partial_bank_idx!=fd_banks_root( ctx->banks )->idx ); - - /* Verify FECs 64 and 96 have NOT been processed. */ - - FD_TEST( f1_64->bank_idx==UINT_MAX ); - FD_TEST( f1_96->bank_idx==UINT_MAX ); - - /* 4. Evict the frontier leaf (FEC 96). This is the node the eviction - policy would select: it is an unconfirmed, !slot_complete frontier - leaf. fd_reasm_remove walks up from FEC 96 toward fec_set_idx==0. - The walk hits FEC 32 (bank_idx=1) while tail (FEC 96) has - bank_idx=UINT_MAX. */ - - fd_reasm_fec_t * evicted = fd_reasm_remove( reasm, f1_96, NULL, NULL ); - FD_TEST( evicted ); - - /* Release evicted chain back to pool. */ - - uint evict_order[ 4 ] = { (uint)partial_bank_idx, (uint)partial_bank_idx, UINT_MAX, UINT_MAX }; - uint evict_idx = 0; - - while( evicted ) { - FD_LOG_NOTICE(( "evicting FEC slot %lu, fec idx %u, bank idx %u", evicted->slot, evicted->fec_set_idx, evicted->bank_idx )); - FD_TEST( evict_order[ evict_idx ] == evicted->bank_idx && evicted->fec_set_idx == 32*evict_idx ); - fd_reasm_fec_t * next = fd_reasm_child( reasm, evicted ); - fd_reasm_pool_release( reasm, evicted ); - evicted = next; - evict_idx++; - } - FD_LOG_NOTICE(( "pass: test_partial_exec_evict" )); -} - -/* Banks full eviction: fill the bank pool with sibling leaves, then ingest - another slot. Replay should evict one non-leader evictable leaf, prune it - once scheduler refs are drained, and use the freed bank for the pending - FEC. */ - -static void -test_banks_full_prune_leaf( fd_wksp_t * wksp ) { - - static fd_replay_tile_t ctx[ 1 ]; - setup_ctx( ctx, wksp ); - - fd_hash_t mr_root = { .ul = { 100 } }; - init_root_fec( ctx, &mr_root ); - - ulong leaf_bank_idxs[ TEST_BANKS_MAX ]; - ulong leaf_bank_seqs[ TEST_BANKS_MAX ]; - ulong leaf_cnt = 0UL; - - fd_bank_t * leader_bank = drive_become_leader( ctx, &mr_root, 1UL ); - ulong const leader_leaf_idx = leader_bank->idx; - - /* Fill the rest of the bank pool with sibling child slots. */ - - for( ulong slot=2UL; slotbanks, fec->bank_idx ); - FD_TEST( leaf_bank ); - leaf_bank_idxs[ leaf_cnt ] = fec->bank_idx; - leaf_bank_seqs[ leaf_cnt ] = leaf_bank->bank_seq; - leaf_cnt++; - - /* Simulate scheduler draining its ref on the block. */ - leaf_bank->refcnt = 0UL; - } - - FD_TEST( !fd_banks_can_start_bank( ctx->banks ) ); - FD_TEST( fd_banks_pool_used_cnt( ctx->banks )==TEST_BANKS_MAX ); - FD_TEST( leaf_cnt>1UL ); - - ulong const used_before = fd_banks_pool_used_cnt( ctx->banks ); - - /* Ingest a new slot that needs a fresh bank. */ - - fd_hash_t mr_next = { .ul = { 2000 } }; - ingest_fec_complete( ctx, &mr_next, &mr_root, TEST_BANKS_MAX, 0U, (ushort)TEST_BANKS_MAX, 32U, 1, 0 ); - - FD_TEST( drive_after_credit_once( ctx ) ); - FD_TEST( mock_sched_abandon_cnt==1UL ); - - ulong evicted_leaf_pos = ULONG_MAX; - for( ulong i=0UL; ibanks, evicted_leaf_idx ); - FD_TEST( evicted_bank ); - FD_TEST( evicted_bank->bank_seq==evicted_leaf_seq ); - FD_TEST( evicted_bank->state==FD_BANK_STATE_PRUNABLE ); - - FD_TEST( drive_after_credit_once( ctx ) ); - FD_TEST( !fd_banks_bank_query( ctx->banks, evicted_leaf_idx ) ); - FD_TEST( fd_banks_pool_used_cnt( ctx->banks )==used_before-1UL ); - - fd_reasm_fec_t * next = drive_one_fec( ctx, TEST_BANKS_MAX, 0U ); - fd_bank_t * next_bank = fd_banks_bank_query( ctx->banks, next->bank_idx ); - FD_TEST( next_bank ); - FD_TEST( next_bank->bank_seq!=evicted_leaf_seq ); - FD_TEST( fd_banks_pool_used_cnt( ctx->banks )==used_before ); - - for( ulong i=0UL; ibanks, leaf_bank_idxs[ i ] ); - if( i==evicted_leaf_pos ) { - FD_TEST( !leaf_bank || leaf_bank->bank_seq!=leaf_bank_seqs[ i ] ); - } else { - FD_TEST( leaf_bank ); - FD_TEST( leaf_bank->bank_seq==leaf_bank_seqs[ i ] ); - } - } - leader_bank = fd_banks_bank_query( ctx->banks, leader_leaf_idx ); - FD_TEST( leader_bank ); - FD_TEST( leader_bank->is_leader ); - FD_TEST( !fd_banks_can_start_bank( ctx->banks ) ); - - FD_LOG_NOTICE(( "pass: test_banks_full_prune_leaf" )); -} - -/* Ordinary replay cannot allocate a bank with this FIFO backlog. Leader - FECs already have a bank and must advance even when sched is full. */ -static void -test_leader_fec_bypasses_backpressure( fd_wksp_t * wksp, - int sched_blocked ) { - static fd_replay_tile_t ctx[ 1 ]; - setup_ctx( ctx, wksp ); - fd_hash_t mr_root = { .ul = { 100UL } }; - init_root_fec( ctx, &mr_root ); - fd_bank_t * leader = drive_become_leader( ctx, &mr_root, 1UL ); - - for( ulong slot=2UL; slotbanks, fec->bank_idx )->refcnt = 0UL; - } - FD_TEST( !fd_banks_can_start_bank( ctx->banks ) ); - FD_TEST( fd_banks_pool_used_cnt( ctx->banks )==TEST_BANKS_MAX ); - - fd_hash_t mr_backlog[ 2 ] = { { .ul = { 2000UL } }, { .ul = { 2001UL } } }; - fd_reasm_fec_t * backlog[ 2 ]; - for( ulong i=0UL; i<2UL; i++ ) { - ulong slot = TEST_BANKS_MAX+i; - backlog[ i ] = ingest_fec_complete( ctx, &mr_backlog[ i ], &mr_root, slot, 0U, (ushort)slot, 32U, 1, 1 ); - } - fd_hash_t mr[ 3 ] = { { .ul = { 3000UL } }, { .ul = { 3001UL } }, { .ul = { 3002UL } } }; - fd_reasm_fec_t * fec[ 3 ]; - for( ulong i=0UL; i<3UL; i++ ) - fec[ i ] = ingest_fec_complete_with_signal( ctx, REPAIR_SIG_FEC_LEADER, &mr[ i ], i ? &mr[ i-1UL ] : &mr_root, - 1UL, (uint)i*FD_FEC_SHRED_CNT, 1U, 32U, 1, i==2UL, NULL ); - - ulong bank_seq[ TEST_BANKS_MAX ]; - ulong bank_state[ TEST_BANKS_MAX ]; - for( ulong i=0UL; ibanks, i ); - FD_TEST( bank ); - bank_seq[ i ] = bank->bank_seq; - bank_state[ i ] = bank->state; - } - ulong sched_cnt = mock_sched_fec_ingest_cnt; - ulong view_cnt = mock_store_view_call_cnt; - ulong out_seq = test_stem_seqs[ ctx->replay_out->idx ]; - mock_sched_capacity = sched_blocked ? 0UL : ULONG_MAX; - mock_sched_drained = !sched_blocked; - int evict_banks = 0; - FD_TEST( !can_process_fec( ctx, &evict_banks ) ); - FD_TEST( evict_banks==!sched_blocked ); - - for( ulong i=0UL; i<3UL; i++ ) { - FD_TEST( fd_reasm_peek( ctx->reasm )==backlog[ 0 ] ); - FD_TEST( drive_after_credit_once( ctx ) ); - fd_block_id_ele_t * id = &ctx->block_id_arr[ leader->idx ]; - FD_TEST( fec[ i ]->popped && !fec[ i ]->in_out ); - FD_TEST( fec[ i ]->bank_idx==leader->idx && fec[ i ]->bank_seq==leader->bank_seq ); - FD_TEST( id->latest_fec_idx==i*FD_FEC_SHRED_CNT ); - FD_TEST( fd_hash_eq( &id->latest_mr, &mr[ i ] ) ); - FD_TEST( id->block_id_seen==(i==2UL) ); - for( ulong j=i+1UL; j<3UL; j++ ) FD_TEST( !fec[ j ]->popped ); - FD_TEST( !backlog[ 0 ]->popped && !backlog[ 1 ]->popped ); - FD_TEST( mock_sched_abandon_cnt==0UL ); - FD_TEST( mock_sched_fec_ingest_cnt==sched_cnt ); - FD_TEST( mock_store_view_call_cnt==view_cnt ); - FD_TEST( test_stem_seqs[ ctx->replay_out->idx ]==out_seq ); - FD_TEST( fd_banks_pool_used_cnt( ctx->banks )==TEST_BANKS_MAX ); - for( ulong j=0UL; jbanks, j ); - FD_TEST( bank && bank->bank_seq==bank_seq[ j ] && bank->state==bank_state[ j ] ); - } - } - FD_TEST( fd_block_id_map_ele_query( ctx->block_id_map, &mr[ 2 ], NULL, ctx->block_id_arr )==&ctx->block_id_arr[ leader->idx ] ); - FD_TEST( !try_process_leader_fec( ctx, test_stem ) ); - FD_TEST( fd_reasm_pop( ctx->reasm )==backlog[ 0 ] ); - FD_TEST( fd_reasm_pop( ctx->reasm )==backlog[ 1 ] ); - FD_TEST( !fd_reasm_pop( ctx->reasm ) ); - - /* Returning the block id does not permit replaying its child before - the final PoH notification freezes the leader bank. */ - fd_hash_t mr_child = { .ul = { 4000UL } }; - ulong child_slot = TEST_BANKS_MAX+2UL; - fd_reasm_fec_t * child = ingest_fec_complete( ctx, &mr_child, &mr[ 2 ], child_slot, 0U, (ushort)(child_slot-1UL), 32U, 1, 1 ); - mock_sched_capacity = ULONG_MAX; - ulong leader_bid_wait = ctx->metrics.leader_bid_wait; - evict_banks = 0; - FD_TEST( !ctx->recv_poh ); - FD_TEST( !can_process_fec( ctx, &evict_banks ) ); - FD_TEST( !evict_banks && !child->popped ); - FD_TEST( ctx->metrics.leader_bid_wait==leader_bid_wait+1UL ); - FD_LOG_NOTICE(( "pass: test_leader_fec_bypasses_backpressure (sched_blocked=%d)", sched_blocked )); -} - -static void -test_leader_fec_waits_for_chain( fd_wksp_t * wksp ) { - static fd_replay_tile_t ctx[ 1 ]; - setup_ctx( ctx, wksp ); - fd_hash_t mr_root = { .ul = { 100UL } }; - fd_hash_t mr[ 3 ] = { { .ul = { 200UL } }, { .ul = { 300UL } }, { .ul = { 400UL } } }; - fd_hash_t mr_stale = { .ul = { 500UL } }; - init_root_fec( ctx, &mr_root ); - fd_bank_t * leader = drive_become_leader( ctx, &mr_root, 2UL ); - fd_block_id_ele_t * id = &ctx->block_id_arr[ leader->idx ]; - ulong sched_cnt = mock_sched_fec_ingest_cnt; - mock_sched_capacity = 0UL; - mock_sched_drained = 0; - - /* A stale leadership FEC is connected but must not bind to the current - bank. The final FEC arrives before either of its predecessors. */ - fd_reasm_fec_t * stale = ingest_fec_complete_with_signal( ctx, REPAIR_SIG_FEC_LEADER, &mr_stale, &mr_root, - 1UL, 0U, 1U, 32U, 1, 1, NULL ); - fd_reasm_fec_t * final = ingest_fec_complete_with_signal( ctx, REPAIR_SIG_FEC_LEADER, &mr[ 2 ], &mr[ 1 ], - 2UL, 64U, 2U, 32U, 1, 1, NULL ); - FD_TEST( !try_process_fec( ctx, test_stem ) ); - FD_TEST( !stale->popped && !final->popped && !id->block_id_seen ); - - /* A competing FEC 0 makes our FEC equivocating. Confirmation permits - attaching our FEC 0 to the already allocated leader bank. */ - fd_hash_t mr_other = { .ul = { 600UL } }; - fd_reasm_fec_t * other = ingest_fec_complete( ctx, &mr_other, &mr_root, 2UL, 0U, 2U, 32U, 1, 0 ); - fd_reasm_fec_t * first = ingest_fec_complete_with_signal( ctx, REPAIR_SIG_FEC_LEADER, &mr[ 0 ], &mr_root, - 2UL, 0U, 2U, 32U, 1, 0, NULL ); - FD_TEST( first->eqvoc && !first->confirmed ); - FD_TEST( !try_process_fec( ctx, test_stem ) ); - FD_TEST( !first->popped ); - fd_reasm_confirm( ctx->reasm, &mr[ 0 ] ); - FD_TEST( try_process_fec( ctx, test_stem ) ); - FD_TEST( first->popped && !final->popped && !id->block_id_seen ); - FD_TEST( !try_process_fec( ctx, test_stem ) ); - FD_TEST( fd_hash_eq( &id->latest_mr, &mr[ 0 ] ) ); - - fd_reasm_fec_t * middle = ingest_fec_complete_with_signal( ctx, REPAIR_SIG_FEC_LEADER, &mr[ 1 ], &mr[ 0 ], - 2UL, 32U, 2U, 32U, 1, 0, NULL ); - FD_TEST( try_process_fec( ctx, test_stem ) ); - FD_TEST( middle->popped && !final->popped && !id->block_id_seen ); - FD_TEST( fd_hash_eq( &id->latest_mr, &mr[ 1 ] ) ); - FD_TEST( try_process_fec( ctx, test_stem ) ); - FD_TEST( final->popped && id->block_id_seen ); - FD_TEST( fd_hash_eq( &id->latest_mr, &mr[ 2 ] ) ); - FD_TEST( !stale->popped && stale->bank_idx==UINT_MAX ); - FD_TEST( !other->popped && other->bank_idx==UINT_MAX ); - FD_TEST( mock_sched_fec_ingest_cnt==sched_cnt ); - FD_TEST( fd_banks_pool_used_cnt( ctx->banks )==2UL ); - FD_LOG_NOTICE(( "pass: test_leader_fec_waits_for_chain" )); -} - -/* A confirmed equivocation in the middle of a slot needs ordinary - replay's separate bank; it cannot reuse the existing leader bank. */ -static void -test_leader_fec_eqvoc_guard( fd_wksp_t * wksp ) { - static fd_replay_tile_t ctx[ 1 ]; - setup_ctx( ctx, wksp ); - fd_hash_t mr_root = { .ul = { 100UL } }; - fd_hash_t mr0 = { .ul = { 200UL } }; - fd_hash_t mr32 = { .ul = { 300UL } }; - fd_hash_t mr32_alt = { .ul = { 400UL } }; - init_root_fec( ctx, &mr_root ); - fd_bank_t * leader = drive_become_leader( ctx, &mr_root, 1UL ); - fd_reasm_fec_t * first = ingest_fec_complete_with_signal( ctx, REPAIR_SIG_FEC_LEADER, &mr0, &mr_root, - 1UL, 0U, 1U, 32U, 1, 0, NULL ); - FD_TEST( try_process_leader_fec( ctx, test_stem ) ); - fd_reasm_fec_t * ordinary = ingest_fec_complete( ctx, &mr32, &mr0, 1UL, 32U, 1U, 32U, 1, 1 ); - fd_reasm_fec_t * alternative = ingest_fec_complete_with_signal( ctx, REPAIR_SIG_FEC_LEADER, &mr32_alt, &mr0, - 1UL, 32U, 1U, 32U, 1, 1, NULL ); - FD_TEST( alternative->eqvoc && !first->eqvoc ); - FD_TEST( !try_process_leader_fec( ctx, test_stem ) ); - fd_reasm_confirm( ctx->reasm, &mr32_alt ); - FD_TEST( alternative->confirmed ); - FD_TEST( !try_process_leader_fec( ctx, test_stem ) ); - FD_TEST( !ordinary->popped && !alternative->popped ); - FD_TEST( !ctx->block_id_arr[ leader->idx ].block_id_seen ); - FD_TEST( fd_hash_eq( &ctx->block_id_arr[ leader->idx ].latest_mr, &mr0 ) ); - FD_TEST( fd_banks_pool_used_cnt( ctx->banks )==2UL ); - FD_LOG_NOTICE(( "pass: test_leader_fec_eqvoc_guard" )); -} - -static void -expect_leader_fec_invariant_failure( fd_replay_tile_t * ctx ) { - pid_t pid = fork(); - FD_TEST( pid>=0 ); - if( !pid ) { - try_process_leader_fec( ctx, test_stem ); - _exit( 0 ); - } - int status; - FD_TEST( waitpid( pid, &status, 0 )==pid ); - FD_TEST( WIFEXITED( status ) && WEXITSTATUS( status )==1 ); -} - -/* An active leader pins its ancestry. Recycled parent metadata is an - invariant violation, not a reason to silently defer leader progress. */ -static void -test_leader_fec_recycled_parent( fd_wksp_t * wksp ) { - static fd_replay_tile_t ctx[ 1 ]; - setup_ctx( ctx, wksp ); - fd_hash_t mr_root = { .ul = { 100UL } }; - fd_hash_t mr_parent = { .ul = { 200UL } }; - fd_hash_t mr_leader = { .ul = { 300UL } }; - init_root_fec( ctx, &mr_root ); - ingest_fec_complete( ctx, &mr_parent, &mr_root, 1UL, 0U, 1U, 32U, 1, 1 ); - fd_reasm_fec_t * parent = drive_one_fec( ctx, 1UL, 0U ); - fd_bank_t * old = fd_banks_bank_query( ctx->banks, parent->bank_idx ); - old->refcnt = 0UL; - ulong old_idx = old->idx; - ulong old_seq = old->bank_seq; - FD_TEST( fd_banks_get_evictable_bank( ctx->banks, NULL )==old_idx ); - fd_banks_prune_cancel_info_t cancel[ 1 ]; - FD_TEST( fd_banks_prune_one_bank( ctx->banks, cancel ) ); - fd_bank_t * replacement = fd_banks_new_bank( ctx->banks, fd_banks_root( ctx->banks )->idx, 0L, 0 ); - FD_TEST( replacement && replacement->idx==old_idx && replacement->bank_seq!=old_seq ); - replacement->state = FD_BANK_STATE_FROZEN; - fd_bank_t * leader = fd_banks_new_bank( ctx->banks, replacement->idx, 0L, 1 ); - FD_TEST( leader ); - leader->f.slot = 2UL; - ctx->leader_bank = leader; - ctx->is_leader = 1; - ctx->block_id_arr[ leader->idx ].bank_seq = leader->bank_seq; - fd_reasm_fec_t * fec = ingest_fec_complete_with_signal( ctx, REPAIR_SIG_FEC_LEADER, &mr_leader, &mr_parent, - 2UL, 0U, 1U, 32U, 1, 1, NULL ); - FD_TEST( fd_reasm_parent( ctx->reasm, fec )==parent ); - expect_leader_fec_invariant_failure( ctx ); - FD_TEST( !fec->popped ); - - /* Check the two generation witnesses independently. */ - fd_block_id_ele_t * parent_id = &ctx->block_id_arr[ old_idx ]; - parent_id->bank_seq = replacement->bank_seq; - expect_leader_fec_invariant_failure( ctx ); - parent_id->bank_seq = old_seq; - parent->bank_seq = replacement->bank_seq; - expect_leader_fec_invariant_failure( ctx ); - FD_TEST( !fec->popped && fec->bank_idx==UINT_MAX ); - FD_TEST( !ctx->block_id_arr[ leader->idx ].block_id_seen ); - FD_LOG_NOTICE(( "pass: test_leader_fec_recycled_parent" )); -} - -static void -test_reused_parent_bank_idx_not_leader_bank( fd_wksp_t * wksp ) { - static fd_replay_tile_t ctx[ 1 ]; - setup_ctx( ctx, wksp ); - - fd_hash_t mr_root = { .ul = { 100UL } }; - fd_hash_t mr_parent = { .ul = { 200UL } }; - fd_hash_t mr_child = { .ul = { 300UL } }; - init_root_fec( ctx, &mr_root ); - - ingest_fec_complete( ctx, &mr_parent, &mr_root, 1UL, 0U, 1U, 32U, 1, 1 ); - fd_reasm_fec_t * parent = drive_one_fec( ctx, 1UL, 0U ); - fd_bank_t * bank = fd_banks_bank_query( ctx->banks, parent->bank_idx ); - FD_TEST( bank ); - bank->refcnt = 0UL; - - fd_reasm_fec_t * child = ingest_fec_complete( ctx, &mr_child, &mr_parent, 2UL, 0U, 1U, 32U, 1, 1 ); - FD_TEST( fd_reasm_parent( ctx->reasm, child )==parent ); - - uint old_bank_idx = parent->bank_idx; - ulong old_bank_seq = parent->bank_seq; - FD_TEST( fd_banks_get_evictable_bank( ctx->banks, NULL )==old_bank_idx ); - fd_banks_prune_cancel_info_t cancel[ 1 ]; - FD_TEST( fd_banks_prune_one_bank( ctx->banks, cancel ) ); - - fd_bank_t * leader_bank = fd_banks_new_bank( ctx->banks, fd_banks_root( ctx->banks )->idx, 0L, 1 ); - FD_TEST( leader_bank->idx==old_bank_idx ); - FD_TEST( leader_bank->bank_seq!=old_bank_seq ); - ctx->leader_bank = leader_bank; - ctx->is_leader = 1; - - ulong leader_bid_wait = ctx->metrics.leader_bid_wait; - int evict_banks = 0; - FD_TEST( can_process_fec( ctx, &evict_banks ) ); - FD_TEST( ctx->metrics.leader_bid_wait==leader_bid_wait ); - FD_TEST( !evict_banks ); - - /* The rotor path resolves the same stale parent through the Alpenglow - block-id map. It must detect the recycled generation and drop the - FEC instead of treating the current leader as its parent. */ - - ulong chain_cnt = fd_ag_block_id_map_chain_cnt_est( TEST_BANKS_MAX ); - void * ag_map_mem = fd_wksp_alloc_laddr( wksp, fd_ag_block_id_map_align(), fd_ag_block_id_map_footprint( chain_cnt ), 1UL ); - FD_TEST( ag_map_mem ); - ctx->ag_block_id_map_seed = 7UL; - ctx->ag_block_id_map = fd_ag_block_id_map_join( fd_ag_block_id_map_new( ag_map_mem, chain_cnt, ctx->ag_block_id_map_seed ) ); - FD_TEST( ctx->ag_block_id_map ); - - fd_block_id_ele_t * parent_ele = &ctx->block_id_arr[ old_bank_idx ]; - parent_ele->block_info = ag_block_id( 1UL, mr_parent.uc ); - FD_TEST( fd_ag_block_id_map_ele_insert( ctx->ag_block_id_map, parent_ele, ctx->block_id_arr ) ); - - ulong chunk = ctx->in[ TEST_REPAIR_IN_IDX ].chunk0; - fd_rotor_replay_fec_t * rotor_fec = fd_chunk_to_laddr( ctx->in[ TEST_REPAIR_IN_IDX ].mem, chunk ); - *rotor_fec = (fd_rotor_replay_fec_t) { - .slot = 2UL, - .fec_set_idx = 0U, - .parent_slot = 1UL, - .parent_block_id = mr_parent - }; - ulong out_idx = ctx->replay_out->idx; - ulong seq0 = test_stem_seqs[ out_idx ]; - FD_TEST( !returnable_frag( ctx, TEST_REPAIR_IN_IDX, 0UL, ROTOR_SIG_FEC_REPLAY, chunk, - sizeof(fd_rotor_replay_fec_t), 0UL, 0UL, - fd_frag_meta_ts_comp( fd_tickcount() ), test_stem ) ); - FD_TEST( ctx->drain_rotor_fecs ); - FD_TEST( test_stem_seqs[ out_idx ]==seq0+1UL ); - fd_frag_meta_t const * m = test_stem_mcaches[ out_idx ] + fd_mcache_line_idx( seq0, test_stem_depths[ out_idx ] ); - FD_TEST( m->sig==REPLAY_SIG_MISSING_FEC ); - FD_TEST( ctx->metrics.leader_bid_wait==leader_bid_wait ); - FD_TEST( !evict_banks ); - - FD_LOG_NOTICE(( "pass: test_reused_parent_bank_idx_not_leader_bank" )); -} - -static ulong -replay_out_sig( fd_replay_tile_t * ctx, - ulong seq ); - -/* Backfilling an evicted block installs its block id mapping when the - slot-complete FEC is inserted, which is before sched runs BLOCK_START - and clones the bank from its parent. A non-forward optimistic - confirmation arriving in that window resolves a mapping whose bank - holds no runtime state yet: vote_stakes_fork_id is still the - ULONG_MAX sentinel, and reading metrics off it indexes the t-1 vote - stakes sets at set idx 65535, way past the max_live_slots - allocation. The confirmation must be skipped until the bank has - completed replay, after SLOT_COMPLETED has been ordered ahead of it - on the replay output. */ - -static void -test_oc_skips_unfrozen_bank( fd_wksp_t * wksp ) { - static fd_replay_tile_t ctx[ 1 ]; - setup_ctx( ctx, wksp ); - - setup_node_info( ctx ); - - fd_hash_t mr_root = { .ul = { 100UL } }; - init_root_fec( ctx, &mr_root ); - - fd_bank_t * root_bank = fd_banks_root( ctx->banks ); - fd_bank_t * bank = fd_banks_new_bank( ctx->banks, root_bank->idx, 0L, 0 ); - FD_TEST( bank ); - FD_TEST( bank->state==FD_BANK_STATE_INIT ); - FD_TEST( bank->vote_stakes_fork_id==ULONG_MAX ); - - fd_hash_t block_id = { .ul = { 777UL } }; - fd_block_id_ele_t * ele = &ctx->block_id_arr[ bank->idx ]; - ele->block_id_seen = 1; - ele->slot = 1UL; - ele->bank_seq = bank->bank_seq; - ele->latest_fec_idx = 0U; - ele->latest_mr = block_id; - ele->dmr = block_id; - FD_TEST( fd_block_id_map_ele_insert( ctx->block_id_map, ele, ctx->block_id_arr ) ); - - ctx->rpc_enabled = 1; - - fd_tower_slot_confirmed_t msg = { - .level = FD_TOWER_SLOT_CONFIRMED_OPTIMISTIC, - .fwd = 0, - .slot = 1UL, - .block_id = block_id - }; - - ulong out_idx = ctx->replay_out->idx; - ulong seq0 = test_stem_seqs[ out_idx ]; - ulong refcnt0 = bank->refcnt; - - process_tower_optimistic_confirmed( ctx, test_stem, &msg ); - - FD_TEST( test_stem_seqs[ out_idx ]==seq0 ); - FD_TEST( bank->refcnt==refcnt0 ); - - /* REPLAYABLE is also too early: RPC does not populate its bank - metadata until replay publishes SLOT_COMPLETED. */ - - bank->vote_stakes_fork_id = root_bank->vote_stakes_fork_id; - bank->state = FD_BANK_STATE_REPLAYABLE; - - process_tower_optimistic_confirmed( ctx, test_stem, &msg ); - - FD_TEST( test_stem_seqs[ out_idx ]==seq0 ); - FD_TEST( bank->refcnt==refcnt0 ); - - /* Once the block is frozen the confirmation is published. */ - - bank->state = FD_BANK_STATE_FROZEN; - - process_tower_optimistic_confirmed( ctx, test_stem, &msg ); - - FD_TEST( test_stem_seqs[ out_idx ]==seq0+1UL ); - FD_TEST( replay_out_sig( ctx, seq0 )==REPLAY_SIG_OC_ADVANCED ); - FD_TEST( bank->refcnt==refcnt0+1UL ); - - FD_LOG_NOTICE(( "pass: test_oc_skips_unfrozen_bank" )); -} - -/* Out-queue misordering on eqvoc + confirm. - - Version A of slot 1 is fully replayed. Then version B FEC 0 arrives - and is marked eqvoc — pushed to the out queue but not deliverable. - Next, a non-equivocating slot 3 arrives and goes into the queue after - FEC 0_B. When we drive_one_fec for slot 3, fd_reasm_pop drains - FEC 0_B from the head (eqvoc+unconfirmed → rejected, in_out set to 0 - but popped stays 0), then delivers slot 3. - - After that, version B FEC 32 and slot 2's FECs arrive — eqvoc, - pushed to the out queue tail. FEC 0_B is dangling: not in the queue, - not popped. - - When we confirm slot 2, fd_reasm_confirm walks upward. FEC 32_B and - slot 2 FECs have in_out=1 so confirm just sets their confirmed flag. - But FEC 0_B has !popped && !in_out, so confirm re-inserts it at the - TAIL — after its own children. - - Queue: [FEC 32_B, slot2 FEC 0, slot2 FEC 32, FEC 0_B]. - - Pop now delivers FEC 32_B first. process_fec_set does - fd_banks_bank_query(parent->bank_idx) where parent is FEC 0_B whose - bank_idx is UINT_MAX → segfault. */ - -static void -test_eqvoc_child_confirm( fd_wksp_t * wksp ) { - - static fd_replay_tile_t ctx[ 1 ]; - setup_ctx( ctx, wksp ); - fd_reasm_t * reasm = ctx->reasm; - - fd_hash_t mr_root = { .ul = { 100 } }; - fd_hash_t mr1_0_a = { .ul = { 200 } }; /* slot 1 version A FEC 0 */ - fd_hash_t mr1_32_a = { .ul = { 300 } }; /* slot 1 version A FEC 32 */ - fd_hash_t mr1_0_b = { .ul = { 400 } }; /* slot 1 version B FEC 0 */ - fd_hash_t mr1_32_b = { .ul = { 500 } }; /* slot 1 version B FEC 32 */ - fd_hash_t mr2_0 = { .ul = { 600 } }; /* slot 2 FEC 0 */ - fd_hash_t mr2_32 = { .ul = { 700 } }; /* slot 2 FEC 32 */ - fd_hash_t mr3_0 = { .ul = { 800 } }; /* slot 3 FEC 0 (non-eqvoc) */ - - /* 1. Root FEC (slot 0). */ - - init_root_fec( ctx, &mr_root ); - - /* 2. Insert and replay version A of slot 1 (FEC 0 and 32). */ - - ingest_fec_complete( ctx, &mr1_0_a, &mr_root, - 1, 0, 1, 32, 1, 0 ); - - ingest_fec_complete( ctx, &mr1_32_a, &mr1_0_a, - 1, 32, 1, 32, 1, 1 ); - - drive_one_fec( ctx, 1UL, 0U ); - fd_reasm_fec_t * fec = drive_one_fec( ctx, 1UL, 32U ); - ulong version_a_bank_idx = fec->bank_idx; - FD_TEST( version_a_bank_idx!=fd_banks_root( ctx->banks )->idx ); - - /* 3. Version B FEC 0 arrives — eqvoc (same slot+fec_set_idx as - version A). Pushed to out queue but not deliverable. */ - - fd_reasm_fec_t * f1_0_b = ingest_fec_complete( ctx, &mr1_0_b, &mr_root, - 1, 0, 1, 32, 1, 0 ); - FD_TEST( f1_0_b->eqvoc ); - - /* 4. Non-equivocating slot 3 arrives (fork off root). Goes into the - out queue AFTER FEC 0_B. */ - - fd_reasm_fec_t * f3_0 = ingest_fec_complete( ctx, &mr3_0, &mr_root, - 3, 0, 3, 32, 1, 1 ); - FD_TEST( f3_0 ); - FD_TEST( !f3_0->eqvoc ); - - /* 5. Pop slot 3. fd_reasm_pop first pops FEC 0_B from the head — - eqvoc+unconfirmed so it is rejected (in_out=0 but popped stays 0). - Then pop delivers slot 3 FEC 0. */ - - fec = drive_one_fec( ctx, 3UL, 0U ); - FD_TEST( fec->bank_idx!=UINT_MAX ); - - /* FEC 0_B is now dangling: not in the queue, not marked popped. */ - FD_TEST( !f1_0_b->in_out ); - FD_TEST( !f1_0_b->popped ); - - /* 6. Version B FEC 32 and slot 2 chain arrive — eqvoc, pushed to - out queue tail. */ - - fd_reasm_fec_t * f1_32_b = ingest_fec_complete( ctx, &mr1_32_b, &mr1_0_b, - 1, 32, 1, 32, 1, 1 ); - FD_TEST( f1_32_b->eqvoc ); - - fd_reasm_fec_t * f2_0 = ingest_fec_complete( ctx, &mr2_0, &mr1_32_b, - 2, 0, 1, 32, 1, 0 ); - FD_TEST( f2_0->eqvoc ); - - fd_reasm_fec_t * f2_32 = ingest_fec_complete( ctx, &mr2_32, &mr2_0, - 2, 32, 1, 32, 1, 1 ); - FD_TEST( f2_32->eqvoc ); - - /* Out queue: [FEC 32_B, slot2 FEC 0, slot2 FEC 32]. - FEC 0_B is NOT in queue (in_out=0). */ - FD_TEST( f1_32_b->in_out ); - FD_TEST( f2_0->in_out ); - FD_TEST( f2_32->in_out ); - FD_TEST( !f1_0_b->in_out ); - - /* 7. Confirm slot 2 FEC 32. fd_reasm_confirm walks upward: - - slot2 FEC 32: in_out=1 → just set confirmed - - slot2 FEC 0: in_out=1 → just set confirmed - - FEC 32_B: in_out=1 → just set confirmed - - FEC 0_B: !popped && !in_out → re-inserted at TAIL - - root: confirmed → stop - Out queue: [FEC 32_B, slot2 FEC 0, slot2 FEC 32, FEC 0_B]. */ - - fd_reasm_confirm( reasm, &mr2_32 ); - FD_TEST( f2_32->confirmed ); - FD_TEST( f2_0->confirmed ); - FD_TEST( f1_32_b->confirmed ); - FD_TEST( f1_0_b->confirmed ); - - /* 8. Verify the misordering: peek should return FEC 32_B (the child), - not FEC 0_B (the parent that should come first). */ - - fec = fd_reasm_peek( reasm ); - FD_TEST( fec ); - FD_TEST( fec->slot==1 && fec->fec_set_idx==32 ); - - /* 9. FEC 32_B requires backfill because its parent FEC 0_B has no - bank. If banks cannot allocate, can_process_fec must leave FEC - 32_B queued instead of popping it and failing partway through - backfill. */ - - FD_TEST( f1_0_b->bank_idx==UINT_MAX ); - - ulong filler[ TEST_BANKS_MAX ]; - ulong filler_cnt = 0UL; - while( fd_banks_can_start_bank( ctx->banks ) ) { - filler[ filler_cnt++ ] = fd_banks_new_bank( ctx->banks, fd_banks_root( ctx->banks )->idx, 0L, 0 )->idx; - } - - int evict_banks = 0; - FD_TEST( !can_process_fec( ctx, &evict_banks ) ); - FD_TEST( evict_banks ); - FD_TEST( !f1_32_b->popped ); - FD_TEST( fd_reasm_peek( reasm )==f1_32_b ); - - for( ulong i=0UL; ibanks, filler[ i ], NULL, NULL ); - for( ulong i=0UL; ibanks, filler[ i ] ) ) FD_TEST( fd_banks_prune_one_bank( ctx->banks, NULL ) ); - } - fec = drive_one_fec( ctx, 1UL, 32U ); - - FD_LOG_NOTICE(( "pass: test_eqvoc_child_confirm" )); -} - -/* Avoid double scheduling on re-confirm of an already-backfilled FEC. - - When a descendant is confirmed before its ancestor, staged backfill - replays the oldest invalid slot. The ancestor FEC ends up with a - valid bank_idx but still has popped=0 and in_out=0 (only fd_reasm_pop - sets popped, and backfill doesn't touch the out queue). - - If the ancestor is later confirmed separately, fd_reasm_confirm must - not reinsert it into the out queue and schedule it a second time. - - The scenario: - root ← slot 1 version A (fully replayed) - slot 1 version B (eqvoc) ← slot 2 (eqvoc, inherited) - 1. Drain all eqvoc FECs from out queue (rejected: eqvoc+!confirmed) - 2. Confirm slot 2 (descendant) — backfill replays version B - 3. Confirm slot 1 version B (ancestor) — no redelivery */ - -static void -test_double_confirm_backfill( fd_wksp_t * wksp ) { - - static fd_replay_tile_t ctx[ 1 ]; - setup_ctx( ctx, wksp ); - fd_reasm_t * reasm = ctx->reasm; - - fd_hash_t mr_root = { .ul = { 100 } }; - fd_hash_t mr1_0_a = { .ul = { 200 } }; /* slot 1 version A FEC 0 */ - fd_hash_t mr1_0_b = { .ul = { 400 } }; /* slot 1 version B FEC 0 */ - fd_hash_t mr2_0 = { .ul = { 600 } }; /* slot 2 FEC 0 */ - fd_hash_t mr2_32 = { .ul = { 700 } }; /* slot 2 FEC 32 */ - - /* 1. Root FEC (slot 0). */ - - init_root_fec( ctx, &mr_root ); - - /* 2. Fully replay version A of slot 1 (FEC 0). */ - - ingest_fec_complete( ctx, &mr1_0_a, &mr_root, - 1, 0, 1, 32, 1, 1 ); - - drive_one_fec( ctx, 1UL, 0U ); - - /* 3. Version B of slot 1 arrives — eqvoc. It is pushed to the out - queue but gated by eqvoc+!confirmed. */ - - fd_reasm_fec_t * f1_0_b = ingest_fec_complete( ctx, &mr1_0_b, &mr_root, - 1, 0, 1, 32, 1, 1 ); - FD_TEST( f1_0_b->eqvoc ); - - /* 4. Slot 2 chains off version B — eqvoc inherited. */ - - fd_reasm_fec_t * f2_0 = ingest_fec_complete( ctx, &mr2_0, &mr1_0_b, - 2, 0, 1, 32, 1, 0 ); - FD_TEST( f2_0->eqvoc ); - - fd_reasm_fec_t * f2_32 = ingest_fec_complete( ctx, &mr2_32, &mr2_0, - 2, 32, 1, 32, 1, 1 ); - FD_TEST( f2_32->eqvoc ); - - /* 5. Drain the out queue. All 3 eqvoc FECs are rejected (eqvoc && - !confirmed). After draining, each has popped=0, in_out=0. */ - - FD_TEST( !fd_reasm_pop( reasm ) ); - FD_TEST( !f1_0_b->popped && !f1_0_b->in_out ); - FD_TEST( !f2_0->popped && !f2_0->in_out ); - FD_TEST( !f2_32->popped && !f2_32->in_out ); - - /* 6. Confirm the descendant (slot 2 FEC 32) first. - - fd_reasm_confirm walks upward setting confirmed=1. The confirmed - FEC (slot 2 FEC 32) has !popped && !in_out, so it gets pushed to - the out queue. */ - - fd_reasm_confirm( reasm, &mr2_32 ); - FD_TEST( f2_32->confirmed ); - FD_TEST( f2_0->confirmed ); - FD_TEST( f1_0_b->confirmed ); - - /* 7. Pop and process the confirmed FEC. Staged backfill replays - slot 1 version B and leaves slot 2 deferred. */ - - ulong sched_cnt_before = mock_sched_fec_ingest_cnt; - ulong banks_used_before = fd_banks_pool_used_cnt( ctx->banks ); - - fd_reasm_fec_t * fec = drive_one_fec( ctx, 2UL, 32U ); - FD_TEST( fec->bank_idx==UINT_MAX ); - - /* After backfill: version B FEC 0 has a valid bank but still - popped=0, in_out=0 — backfill doesn't touch those flags. */ - - FD_TEST( f1_0_b->bank_idx!=UINT_MAX ); - FD_TEST( !f1_0_b->popped ); - FD_TEST( !f1_0_b->in_out ); - FD_TEST( f2_0->bank_idx==UINT_MAX ); - FD_TEST( f2_32->bank_idx==UINT_MAX ); - - ulong sched_cnt_after_backfill = mock_sched_fec_ingest_cnt; - ulong banks_used_after_backfill = fd_banks_pool_used_cnt( ctx->banks ); - - /* Backfill should have ingested one slot and allocated one bank. */ - - FD_TEST( sched_cnt_after_backfill==sched_cnt_before+1UL ); - FD_TEST( banks_used_after_backfill==banks_used_before+1UL ); - - /* 8. Confirm the already-confirmed ancestor. It must not be - reinserted into the out queue. */ - - fd_reasm_confirm( reasm, &mr1_0_b ); - - FD_TEST( !fd_reasm_peek( reasm ) ); - FD_TEST( mock_sched_fec_ingest_cnt==sched_cnt_after_backfill ); - FD_TEST( fd_banks_pool_used_cnt( ctx->banks )==banks_used_after_backfill ); - - FD_LOG_NOTICE(( "pass: test_double_confirm_backfill" )); -} - -/* Deliver one fd_rotor_replay_fec_t through the ROTOR_SIG_FEC_REPLAY - handler (it lives under IN_KIND_REPAIR, dispatched by sig). Returns - returnable_frag's value: 0 = frag consumed, 1 = kept for retry. */ -static int -deliver_rotor_fec( fd_replay_tile_t * ctx, - ulong slot, - uint fec_set_idx, - ulong parent_slot, - fd_hash_t const * parent_block_id, - fd_hash_t const * mr, - int slot_complete, - int verified ) { - ulong chunk = ctx->in[ TEST_REPAIR_IN_IDX ].chunk0; - fd_rotor_replay_fec_t * fec = fd_chunk_to_laddr( ctx->in[ TEST_REPAIR_IN_IDX ].mem, chunk ); - memset( fec, 0, sizeof(*fec) ); - fec->slot = slot; - fec->fec_set_idx = fec_set_idx; - fec->mr = *mr; - fec->parent_slot = parent_slot; - fec->parent_block_id = *parent_block_id; - fec->slot_complete = slot_complete; - fec->known_id = verified; - - /* Clear the pacing gate at the top of the handler - (execrp_idle_cnt >= 2*in_cnt || is_leader) so the FEC reaches the - drain gate rather than being kept for retry. */ - ctx->execrp_idle_cnt = 2UL*ctx->in_cnt + 4UL; - - return returnable_frag( ctx, TEST_REPAIR_IN_IDX, 0UL, ROTOR_SIG_FEC_REPLAY, chunk, - sizeof(fd_rotor_replay_fec_t), 0UL, 0UL, - fd_frag_meta_ts_comp( fd_tickcount() ), test_stem ); -} - -/* Like deliver_rotor_fec, but also sets fec->block_id -- the field the - deliver_from_root recovery path always populates (non-zero), and that - replay dedups on to skip already-replayed blocks. */ -static int -deliver_rotor_fec_bid( fd_replay_tile_t * ctx, - ulong slot, - uint fec_set_idx, - ulong parent_slot, - fd_hash_t const * parent_block_id, - fd_hash_t const * block_id, - fd_hash_t const * mr, - int slot_complete, - int verified ) { - ulong chunk = ctx->in[ TEST_REPAIR_IN_IDX ].chunk0; - fd_rotor_replay_fec_t * fec = fd_chunk_to_laddr( ctx->in[ TEST_REPAIR_IN_IDX ].mem, chunk ); - memset( fec, 0, sizeof(*fec) ); - fec->slot = slot; - fec->fec_set_idx = fec_set_idx; - fec->mr = *mr; - fec->parent_slot = parent_slot; - fec->parent_block_id = *parent_block_id; - fec->block_id = *block_id; - fec->slot_complete = slot_complete; - fec->known_id = verified; - ctx->execrp_idle_cnt = 2UL*ctx->in_cnt + 4UL; - return returnable_frag( ctx, TEST_REPAIR_IN_IDX, 0UL, ROTOR_SIG_FEC_REPLAY, chunk, - sizeof(fd_rotor_replay_fec_t), 0UL, 0UL, - fd_frag_meta_ts_comp( fd_tickcount() ), test_stem ); -} - -/* test_process_rotor_fec_skip_replayed: on recovery rotor re-delivers - the entire ancestry path from the rotor root, but most of those - blocks are already replayed. Replay must SKIP a redelivered FEC - whose (slot, block_id) names an already-replayed block -- returning - before it touches the store or creates a duplicate bank -- while - still processing a FEC for a block it has NOT yet replayed. - - Observability: the skip returns at the top of process_rotor_fec, - before the store lock, so store_query_cnt does NOT advance for a - skipped FEC. The control FEC forces payload pinning to fail after a - successful lookup and verifies replay abandons it before creating a - bank or changing the block-id map. */ -static void -test_process_rotor_fec_skip_replayed( fd_wksp_t * wksp ) { - static fd_replay_tile_t ctx[ 1 ]; - setup_ctx( ctx, wksp ); - - ulong chain_cnt = fd_ag_block_id_map_chain_cnt_est( TEST_BANKS_MAX ); - void * ag_map_mem = fd_wksp_alloc_laddr( wksp, fd_ag_block_id_map_align(), fd_ag_block_id_map_footprint( chain_cnt ), 1UL ); - FD_TEST( ag_map_mem ); - ctx->ag_block_id_map_seed = 7UL; - ctx->ag_block_id_map = fd_ag_block_id_map_join( fd_ag_block_id_map_new( ag_map_mem, chain_cnt, ctx->ag_block_id_map_seed ) ); - FD_TEST( ctx->ag_block_id_map ); - - fd_hash_t mr_root = { .ul = { 100 } }; - init_root_fec( ctx, &mr_root ); - - fd_bank_t * root_bank = fd_banks_root( ctx->banks ); - FD_TEST( root_bank ); - root_bank->state = FD_BANK_STATE_FROZEN; - - /* Register the root as a resolvable parent (keyed {0, parent_bid}) so - a NOT-yet-replayed FEC 0 can resolve its parent and reach the store - query -- i.e. the control FEC is not dropped earlier for a missing - parent. */ - fd_hash_t parent_bid = { .ul = { 0xBEEFUL } }; - fd_block_id_ele_t * root_ele = &ctx->block_id_arr[ root_bank->idx ]; - root_ele->block_info = ag_block_id( 0UL, parent_bid.uc ); - root_ele->block_id_seen = 1; - FD_TEST( fd_ag_block_id_map_ele_insert( ctx->ag_block_id_map, root_ele, ctx->block_id_arr ) ); - - /* An ALREADY-REPLAYED complete block at slot 5 with block_id B: a live - bank plus an ele keyed by {5, B} with block_id_seen=1. */ - fd_hash_t B = { .ul = { 0x5150UL } }; - fd_bank_t * bank5 = fd_banks_new_bank( ctx->banks, root_bank->idx, 0L, 0 ); - FD_TEST( bank5 ); - bank5->state = FD_BANK_STATE_FROZEN; - fd_block_id_ele_t * ele5 = &ctx->block_id_arr[ bank5->idx ]; - ele5->slot = 5UL; - ele5->bank_seq = bank5->bank_seq; - ele5->latest_mr = B; - ele5->latest_fec_idx = 0U; - ele5->block_id_seen = 1; - ele5->block_info = ag_block_id( 5UL, B.uc ); - FD_TEST( fd_ag_block_id_map_ele_insert( ctx->ag_block_id_map, ele5, ctx->block_id_arr ) ); - - mock_sched_capacity = ULONG_MAX; - ulong used0 = fd_banks_pool_used_cnt( ctx->banks ); - ulong q0 = ctx->metrics.store_query_cnt; + mock_sched_capacity = ULONG_MAX; + ulong used0 = fd_banks_pool_used_cnt( ctx->banks ); + ulong q0 = ctx->metrics.store_query_cnt; /* (1) Redelivered FEC for the already-replayed block {5, B}: SKIP. No store query, no duplicate bank, existing ele untouched. */ @@ -4331,17 +2317,8 @@ test_drain_rotor_fecs( fd_wksp_t * wksp ) { static fd_replay_tile_t ctx[ 1 ]; setup_ctx( ctx, wksp ); - /* setup_ctx builds block_id_map but not ag_block_id_map, which the - rotor-FEC path uses; stand it up here. */ - ulong chain_cnt = fd_ag_block_id_map_chain_cnt_est( TEST_BANKS_MAX ); - void * ag_map_mem = fd_wksp_alloc_laddr( wksp, fd_ag_block_id_map_align(), fd_ag_block_id_map_footprint( chain_cnt ), 1UL ); - FD_TEST( ag_map_mem ); - ctx->ag_block_id_map_seed = 7UL; - ctx->ag_block_id_map = fd_ag_block_id_map_join( fd_ag_block_id_map_new( ag_map_mem, chain_cnt, ctx->ag_block_id_map_seed ) ); - FD_TEST( ctx->ag_block_id_map ); - fd_hash_t mr_root = { .ul = { 100 } }; - init_root_fec( ctx, &mr_root ); /* root bank + block_id_map entry */ + init_root( ctx, &mr_root ); ulong out_idx = ctx->replay_out->idx; ulong seq0 = test_stem_seqs[ out_idx ]; @@ -4410,15 +2387,8 @@ test_drain_rotor_fecs_skip_wait_reentry( fd_wksp_t * wksp ) { static fd_replay_tile_t ctx[ 1 ]; setup_ctx( ctx, wksp ); - ulong chain_cnt = fd_ag_block_id_map_chain_cnt_est( TEST_BANKS_MAX ); - void * ag_map_mem = fd_wksp_alloc_laddr( wksp, fd_ag_block_id_map_align(), fd_ag_block_id_map_footprint( chain_cnt ), 1UL ); - FD_TEST( ag_map_mem ); - ctx->ag_block_id_map_seed = 7UL; - ctx->ag_block_id_map = fd_ag_block_id_map_join( fd_ag_block_id_map_new( ag_map_mem, chain_cnt, ctx->ag_block_id_map_seed ) ); - FD_TEST( ctx->ag_block_id_map ); - fd_hash_t mr_root = { .ul = { 100 } }; - init_root_fec( ctx, &mr_root ); + init_root( ctx, &mr_root ); fd_bank_t * root_bank = fd_banks_root( ctx->banks ); FD_TEST( root_bank ); @@ -4493,25 +2463,15 @@ test_drain_rotor_fecs_skip_wait_reentry( fd_wksp_t * wksp ) { FD_LOG_NOTICE(( "pass: test_drain_rotor_fecs_skip_wait_reentry" )); } -/* setup_ag_block_id_map stands up the alpenglow compound block-id map - the rotor-FEC path uses (setup_ctx only builds block_id_map), and - registers the root bank as a resolvable parent keyed +/* setup_ag_root registers the root bank as a resolvable parent keyed {0, parent_bid}, frozen, so a FEC 0 naming that parent passes the parent checks. */ static void -setup_ag_block_id_map( fd_replay_tile_t * ctx, - fd_wksp_t * wksp, - fd_hash_t const * parent_bid ) { - ulong chain_cnt = fd_ag_block_id_map_chain_cnt_est( TEST_BANKS_MAX ); - void * ag_map_mem = fd_wksp_alloc_laddr( wksp, fd_ag_block_id_map_align(), fd_ag_block_id_map_footprint( chain_cnt ), 1UL ); - FD_TEST( ag_map_mem ); - ctx->ag_block_id_map_seed = 7UL; - ctx->ag_block_id_map = fd_ag_block_id_map_join( fd_ag_block_id_map_new( ag_map_mem, chain_cnt, ctx->ag_block_id_map_seed ) ); - FD_TEST( ctx->ag_block_id_map ); - +setup_ag_root( fd_replay_tile_t * ctx, + fd_hash_t const * parent_bid ) { fd_hash_t mr_root = { .ul = { 100 } }; - init_root_fec( ctx, &mr_root ); + init_root( ctx, &mr_root ); fd_bank_t * root_bank = fd_banks_root( ctx->banks ); FD_TEST( root_bank ); @@ -4554,7 +2514,7 @@ test_rotor_fec_turbine_keying( fd_wksp_t * wksp ) { static fd_replay_tile_t ctx[ 1 ]; setup_ctx( ctx, wksp ); fd_hash_t parent_bid = { .ul = { 0xBEEFUL } }; - setup_ag_block_id_map( ctx, wksp, &parent_bid ); + setup_ag_root( ctx, &parent_bid ); ulong out_idx = ctx->replay_out->idx; ulong seq0 = test_stem_seqs[ out_idx ]; @@ -4631,7 +2591,7 @@ test_rotor_fec_rooted_past( fd_wksp_t * wksp ) { static fd_replay_tile_t ctx[ 1 ]; setup_ctx( ctx, wksp ); fd_hash_t parent_bid = { .ul = { 0xBEEFUL } }; - setup_ag_block_id_map( ctx, wksp, &parent_bid ); + setup_ag_root( ctx, &parent_bid ); ctx->published_root_slot = 10UL; ulong out_idx = ctx->replay_out->idx; @@ -4671,7 +2631,7 @@ rotor_fec_merged_copies( fd_wksp_t * wksp, static fd_replay_tile_t ctx[ 1 ]; setup_ctx( ctx, wksp ); fd_hash_t parent_bid = { .ul = { 0xBEEFUL } }; - setup_ag_block_id_map( ctx, wksp, &parent_bid ); + setup_ag_root( ctx, &parent_bid ); fd_hash_t B = { .ul = { 0x5150UL } }, zero = {0}; fd_hash_t mr[3] = { { .ul = { 501 } }, { .ul = { 502 } }, { .ul = { 503 } } }; @@ -4724,7 +2684,7 @@ test_dead_block_children_drop( fd_wksp_t * wksp ) { static fd_replay_tile_t ctx[ 1 ]; setup_ctx( ctx, wksp ); fd_hash_t parent_bid = { .ul = { 0xBEEFUL } }; - setup_ag_block_id_map( ctx, wksp, &parent_bid ); + setup_ag_root( ctx, &parent_bid ); ulong out_idx = ctx->replay_out->idx; @@ -4809,7 +2769,7 @@ test_stale_id_key_does_not_shadow_rebuild( fd_wksp_t * wksp ) { static fd_replay_tile_t ctx[ 1 ]; setup_ctx( ctx, wksp ); fd_hash_t parent_bid = { .ul = { 0xBEEFUL } }; - setup_ag_block_id_map( ctx, wksp, &parent_bid ); + setup_ag_root( ctx, &parent_bid ); ulong out_idx = ctx->replay_out->idx; /* Replay blocks 5 and 6 (siblings off the root) in full, then rule @@ -4899,9 +2859,9 @@ test_identity_switch_quiesces_replay( fd_wksp_t * wksp ) { memcpy( keyswitch->bytes, &new_identity, sizeof(new_identity) ); fd_keyswitch_state( keyswitch, FD_KEYSWITCH_STATE_SWITCH_PENDING ); - ulong replay_out_idx = ctx->replay_out->idx; - fd_mcache_seq_update( fd_mcache_seq_laddr( test_stem_mcaches[ replay_out_idx ] ), 40UL ); - ctx->replay_out_seq = fd_mcache_seq_laddr_const( test_stem_mcaches[ replay_out_idx ] ); + ulong slot_out_idx = ctx->slot_out->idx; + fd_mcache_seq_update( fd_mcache_seq_laddr( test_stem_mcaches[ slot_out_idx ] ), 40UL ); + ctx->slot_out_seq = fd_mcache_seq_laddr_const( test_stem_mcaches[ slot_out_idx ] ); ctx->is_booted = 0; during_housekeeping( ctx ); @@ -4910,14 +2870,14 @@ test_identity_switch_quiesces_replay( fd_wksp_t * wksp ) { ctx->is_booted = 1; ctx->is_leader = 1; - fd_mcache_seq_update( fd_mcache_seq_laddr( test_stem_mcaches[ replay_out_idx ] ), 41UL ); + fd_mcache_seq_update( fd_mcache_seq_laddr( test_stem_mcaches[ slot_out_idx ] ), 41UL ); during_housekeeping( ctx ); FD_TEST( fd_keyswitch_state_query( keyswitch )==FD_KEYSWITCH_STATE_SWITCH_PENDING ); FD_TEST( keyswitch->result==ULONG_MAX ); FD_TEST( fd_pubkey_eq( ctx->identity_pubkey, &old_identity ) ); ctx->is_leader = 0; - fd_mcache_seq_update( fd_mcache_seq_laddr( test_stem_mcaches[ replay_out_idx ] ), 42UL ); + fd_mcache_seq_update( fd_mcache_seq_laddr( test_stem_mcaches[ slot_out_idx ] ), 42UL ); during_housekeeping( ctx ); FD_TEST( ctx->halt_replay ); FD_TEST( fd_keyswitch_state_query( keyswitch )==FD_KEYSWITCH_STATE_COMPLETED ); @@ -4952,8 +2912,7 @@ test_ag_set_identity_leader_slot( fd_wksp_t * wksp, setup_ctx( ctx, wksp ); setup_node_info( ctx ); fd_hash_t parent_bid = { .ul = { 0xBEEFUL } }; - setup_ag_block_id_map( ctx, wksp, &parent_bid ); - ctx->alpenglow = 1; + setup_ag_root( ctx, &parent_bid ); static fd_keyswitch_t keyswitch[ 1 ]; ctx->keyswitch = fd_keyswitch_join( fd_keyswitch_new( keyswitch, FD_KEYSWITCH_STATE_UNLOCKED ) ); @@ -4962,7 +2921,6 @@ test_ag_set_identity_leader_slot( fd_wksp_t * wksp, FD_TEST( vote_tracker_mem ); ctx->vote_tracker = fd_vote_tracker_join( fd_vote_tracker_new( vote_tracker_mem, 42UL ) ); FD_TEST( ctx->vote_tracker ); - ctx->replay_out_seq = fd_mcache_seq_laddr_const( test_stem_mcaches[ ctx->replay_out->idx ] ); ctx->slot_out_seq = fd_mcache_seq_laddr_const( test_stem_mcaches[ ctx->slot_out->idx ] ); fd_mcache_seq_update( fd_mcache_seq_laddr( test_stem_mcaches[ ctx->replay_out->idx ] ), 40UL ); fd_mcache_seq_update( fd_mcache_seq_laddr( test_stem_mcaches[ ctx->slot_out->idx ] ), 7UL ); @@ -5034,8 +2992,7 @@ setup_ag_leader_ctx( fd_replay_tile_t * ctx, fd_hash_t const * root_id, fd_hash_t const * id1 ) { setup_ctx( ctx, wksp ); - setup_ag_block_id_map( ctx, wksp, root_id ); - ctx->alpenglow = 1; + setup_ag_root( ctx, root_id ); fd_bank_t * root = fd_banks_root( ctx->banks ); root->f.block_id = *root_id; @@ -5059,19 +3016,17 @@ setup_ag_leader_ctx( fd_replay_tile_t * ctx, return b1; } -/* process_poh_message's Alpenglow footer path reads accounts this ctx - does not have; the rest of the slot_ended handling is shared. The - sched registration that path does is replayed by hand. */ +/* process_poh_message's footer path reads accounts this ctx does not + have, so the footer finalize and producer time bounds are mocked. */ static void end_leader_slot( fd_replay_tile_t * ctx, ulong slot ) { fd_poh_leader_slot_ended_t slot_ended = { .completed = 1, .slot = slot }; - ctx->alpenglow = 0; + mock_footer_finalize = 1; process_poh_message( ctx, test_stem, &slot_ended ); - ctx->alpenglow = 1; + mock_footer_finalize = 0; FD_TEST( ctx->recv_poh ); - fd_sched_block_add_done( ctx->sched, ctx->leader_bank->idx, ctx->leader_bank->parent_idx, slot ); } /* Our block id never comes back (rotor dropped our own shreds) while @@ -5096,7 +3051,6 @@ test_ag_leader_abandon_on_root_past_leader_slot( fd_wksp_t * wksp ) { FD_TEST( vote_tracker_mem ); ctx->vote_tracker = fd_vote_tracker_join( fd_vote_tracker_new( vote_tracker_mem, 42UL ) ); FD_TEST( ctx->vote_tracker ); - ctx->replay_out_seq = fd_mcache_seq_laddr_const( test_stem_mcaches[ ctx->replay_out->idx ] ); ctx->slot_out_seq = fd_mcache_seq_laddr_const( test_stem_mcaches[ ctx->slot_out->idx ] ); FD_TEST( try_become_leader_ag( ctx, test_stem, FD_METRICS_SAMPLE_REASON_LEADER_START, 0UL ) ); @@ -5140,12 +3094,6 @@ test_ag_leader_abandon_on_root_past_leader_slot( fd_wksp_t * wksp ) { FD_TEST( ctx->is_leader && ctx->leader_bank==leader ); ctx->recv_poh = 1; - /* Tower keeps waiting for the block id. */ - ctx->alpenglow = 0; - FD_TEST( !try_fini_leader( ctx, test_stem ) ); - FD_TEST( ctx->is_leader && ctx->leader_bank==leader ); - ctx->alpenglow = 1; - ulong seq0 = test_stem_seqs[ out_idx ]; ulong used = fd_banks_pool_used_cnt( ctx->banks ); FD_TEST( try_fini_leader( ctx, test_stem ) ); @@ -5345,6 +3293,7 @@ setup_snapshot_test_ctx( fd_replay_tile_t * ctx, fd_bank_t * root = fd_banks_init_bank( ctx->banks ); FD_TEST( root ); fd_features_disable_all( &root->f.features ); + root->f.features.alpenglow = 0UL; root->f.slot = snapshot_slot; root->f.parent_slot = snapshot_slot ? (snapshot_slot - 1UL) : 0UL; /* Nonzero, else the WFS bank hash memcmp compares zero against zero @@ -5363,17 +3312,10 @@ setup_snapshot_test_ctx( fd_replay_tile_t * ctx, fd_blockhashes_init( &root->f.block_hash_queue, 42UL ); FD_TEST( fd_blockhashes_push_new( &root->f.block_hash_queue, &root_id ) ); - ulong chain_cnt = fd_block_id_map_chain_cnt_est( bank_cnt ); ctx->block_id_arr = fd_wksp_alloc_laddr( wksp, alignof(fd_block_id_ele_t), sizeof(fd_block_id_ele_t)*bank_cnt, 1UL ); FD_TEST( ctx->block_id_arr ); memset( ctx->block_id_arr, 0, sizeof(fd_block_id_ele_t)*bank_cnt ); - void * map_mem = fd_wksp_alloc_laddr( wksp, fd_block_id_map_align(), fd_block_id_map_footprint( chain_cnt ), 1UL ); - FD_TEST( map_mem ); - ctx->block_id_map = fd_block_id_map_join( fd_block_id_map_new( map_mem, chain_cnt, 44UL ) ); - FD_TEST( ctx->block_id_map ); - /* Joined unconditionally: on_snapshot_message inserts here instead of - block_id_map when ctx->alpenglow is set. */ ulong ag_chain_cnt = fd_ag_block_id_map_chain_cnt_est( bank_cnt ); void * ag_map_mem = fd_wksp_alloc_laddr( wksp, fd_ag_block_id_map_align(), fd_ag_block_id_map_footprint( ag_chain_cnt ), 1UL ); FD_TEST( ag_map_mem ); @@ -5383,14 +3325,9 @@ setup_snapshot_test_ctx( fd_replay_tile_t * ctx, ctx->block_id_len = bank_cnt; - void * reasm_mem = fd_wksp_alloc_laddr( wksp, fd_reasm_align(), fd_reasm_footprint( 2UL ), 1UL ); - FD_TEST( reasm_mem ); - ctx->reasm = fd_reasm_join( fd_reasm_new( reasm_mem, 2UL, 0UL ) ); - FD_TEST( ctx->reasm ); - - void * store_mem = fd_wksp_alloc_laddr( wksp, fd_store_align(), fd_store_footprint( 2UL, 1UL, 0UL, 0UL, 0UL, 0, 0UL ), 1UL ); + void * store_mem = fd_wksp_alloc_laddr( wksp, fd_store_align(), fd_store_footprint( 2UL, 1UL, 0UL, 0UL, 0UL, 1, 1UL ), 1UL ); FD_TEST( store_mem ); - ctx->store = fd_store_join( fd_store_new( store_mem, 2UL, 1UL, 0UL, 0UL, 0UL, FD_SHRED_BLK_MAX, 0UL, 0, 0UL ) ); + ctx->store = fd_store_join( fd_store_new( store_mem, 2UL, 1UL, 0UL, 0UL, 0UL, FD_SHRED_BLK_MAX, 0UL, 1, 1UL ) ); FD_TEST( ctx->store ); FD_TEST( fd_store_map_ljoin( ctx->store, ctx->map_join ) ); @@ -5649,8 +3586,7 @@ test_wfs_done_idempotency( fd_wksp_t * wksp ) { } static void -test_wfs_boot_publish_order( fd_wksp_t * wksp, - int alpenglow ) { +test_wfs_boot_publish_order( fd_wksp_t * wksp ) { static fd_replay_tile_t ctx[1]; static fd_runtime_stack_t stack[1]; @@ -5660,8 +3596,6 @@ test_wfs_boot_publish_order( fd_wksp_t * wksp, memset( ctx, 0, sizeof(*ctx) ); fd_bank_t * root = setup_snapshot_test_ctx( ctx, wksp, stack, 100UL ); - ctx->alpenglow = alpenglow; - root->f.features.alpenglow = alpenglow ? 0UL : FD_FEATURE_DISABLED; ctx->wfs_enabled = 1; ctx->wfs_hash_is_zero = 0; ctx->expected_bank_hash = root->f.bank_hash; @@ -5700,7 +3634,7 @@ test_wfs_boot_publish_order( fd_wksp_t * wksp, fd_frag_meta_t const * done = test_stem_mcaches[ out_idx ] + fd_mcache_line_idx( seq1, test_stem_depths[ out_idx ] ); FD_TEST( done->sig==REPLAY_SIG_WFS_DONE ); - FD_LOG_NOTICE(( "pass: test_wfs_boot_publish_order (alpenglow=%d)", alpenglow )); + FD_LOG_NOTICE(( "pass: test_wfs_boot_publish_order" )); } static void @@ -5723,17 +3657,15 @@ test_wfs_leader_gate( fd_wksp_t * wksp ) { /* All other preconditions for leadership are satisfied. */ ctx->next_leader_slot = 101UL; ctx->is_leader = 0; - ctx->identity_vote_rooted = 1; - /* Blocked by wfs_complete==0. try_become_leader short-circuits on an - OR, so pin every earlier term open (replay_out->idx==ULONG_MAX in + /* Blocked by wfs_complete==0. try_become_leader_ag short-circuits on + an OR, so pin every earlier term open (replay_out->idx==ULONG_MAX in particular would return 0 on its own and hide the WFS gate). */ FD_TEST( ctx->next_leader_slot!=ULONG_MAX ); FD_TEST( !ctx->is_leader ); - FD_TEST( ctx->identity_vote_rooted ); FD_TEST( ctx->replay_out->idx!=ULONG_MAX ); FD_TEST( !ctx->wfs_complete ); - FD_TEST( try_become_leader( ctx, test_stem )==0 ); + FD_TEST( try_become_leader_ag( ctx, test_stem, FD_METRICS_SAMPLE_REASON_LEADER_START, 0UL )==0 ); /* after_credit returns immediately when wfs_complete==0. If the gate didn't fire, it would crash on our minimal ctx. */ @@ -5826,13 +3758,9 @@ main( int argc, fd_metrics_register( (ulong *)fd_metrics_new( metrics_scratch, 0UL ) ); /* leader paths run metrics_write */ test_txn_completion_publish( wksp ); fd_wksp_reset( wksp, 42U ); - test_leader_fec_payload_retained( wksp ); fd_wksp_reset( wksp, 42U ); - test_reception_metrics_sidecar( wksp ); fd_wksp_reset( wksp, 42U ); test_reward_cert_signer_count(); test_snapshot_intervals_use_block_height(); test_snapshot_found_deleted_state( wksp ); fd_wksp_reset( wksp, 42U ); - test_delinquent_classifier(); - test_update_delinquent_stake( wksp ); fd_wksp_reset( wksp, 42U ); test_next_leader_slot_cache(); test_wait_info_health_signal(); test_wait_info_leader_gap(); @@ -5843,8 +3771,7 @@ main( int argc, test_wfs_error_aborts( wksp ); fd_wksp_reset( wksp, 42U ); test_wfs_bank_hash_mismatch( wksp ); fd_wksp_reset( wksp, 42U ); test_wfs_done_idempotency( wksp ); fd_wksp_reset( wksp, 42U ); - test_wfs_boot_publish_order( wksp, 0 ); fd_wksp_reset( wksp, 42U ); - test_wfs_boot_publish_order( wksp, 1 ); fd_wksp_reset( wksp, 42U ); + test_wfs_boot_publish_order( wksp ); fd_wksp_reset( wksp, 42U ); test_wfs_leader_gate( wksp ); fd_wksp_reset( wksp, 42U ); test_wfs_done_before_boot( wksp ); fd_wksp_reset( wksp, 42U ); test_wfs_shred_version_deferral(); @@ -5860,27 +3787,6 @@ main( int argc, test_root_from_footer( wksp ); fd_wksp_reset( wksp, 42U ); test_footer_cert_tasks( wksp ); fd_wksp_reset( wksp, 42U ); test_slot_completed_keeps_leader_sentinel( wksp ); fd_wksp_reset( wksp, 42U ); - test_banks_full_prune_leaf( wksp ); fd_wksp_reset( wksp, 42U ); - test_leader_fec_bypasses_backpressure( wksp, 0 ); fd_wksp_reset( wksp, 42U ); - test_leader_fec_bypasses_backpressure( wksp, 1 ); fd_wksp_reset( wksp, 42U ); - test_leader_fec_waits_for_chain( wksp ); fd_wksp_reset( wksp, 42U ); - test_leader_fec_eqvoc_guard( wksp ); fd_wksp_reset( wksp, 42U ); - test_leader_fec_recycled_parent( wksp ); fd_wksp_reset( wksp, 42U ); - test_reused_parent_bank_idx_not_leader_bank( wksp ); fd_wksp_reset( wksp, 42U ); - test_oc_skips_unfrozen_bank( wksp ); fd_wksp_reset( wksp, 42U ); - test_banks_evict_backfill( wksp ); fd_wksp_reset( wksp, 42U ); - test_become_leader_waits_for_rereplayed_reset( wksp ); fd_wksp_reset( wksp, 42U ); - test_become_leader_grace_init_child( wksp ); fd_wksp_reset( wksp, 42U ); - test_tower_reset_waits_for_rereplayed_bank( wksp ); fd_wksp_reset( wksp, 42U ); - test_backfill_partial_sched_capacity( wksp ); fd_wksp_reset( wksp, 42U ); - test_double_confirm_backfill( wksp ); fd_wksp_reset( wksp, 42U ); - test_partial_exec_evict( wksp ); fd_wksp_reset( wksp, 42U ); - test_eqvoc_mid_slot_evicted( wksp ); fd_wksp_reset( wksp, 42U ); - test_confirm( wksp ); fd_wksp_reset( wksp, 42U ); - test_eqvoc_last_fec( wksp ); fd_wksp_reset( wksp, 42U ); - test_eqvoc_first_fec( wksp ); fd_wksp_reset( wksp, 42U ); - test_stale_redeliver( wksp ); fd_wksp_reset( wksp, 42U ); - test_eqvoc_child_confirm( wksp ); fd_wksp_reset( wksp, 42U ); test_drain_rotor_fecs( wksp ); fd_wksp_reset( wksp, 42U ); test_drain_rotor_fecs_skip_wait_reentry( wksp ); fd_wksp_reset( wksp, 42U ); test_process_rotor_fec_skip_replayed( wksp ); fd_wksp_reset( wksp, 42U ); From 57aba5f4c538ecdae3fa153d75e60329633ae320 Mon Sep 17 00:00:00 2001 From: Richard Patel Date: Wed, 21 Oct 2026 13:38:51 +0000 Subject: [PATCH 05/13] treewide: remove references to Tower BFT --- src/app/shared/commands/run/run.c | 5 +- src/app/shared/fd_action.h | 1 - .../commands/quic_trace/fd_quic_trace.h | 4 - .../commands/quic_trace/fd_quic_trace_main.c | 45 ++--- .../quic_trace/fd_quic_trace_rx_tile.c | 23 +-- src/disco/diag/fd_diag_tile.c | 29 --- src/disco/fd_disco_base.h | 1 - src/disco/gui/fd_gui.c | 125 ------------ src/disco/gui/fd_gui.h | 6 - src/disco/gui/fd_gui_tile.c | 27 +-- src/disco/net/fd_net_tile_private.h | 1 - src/disco/net/fd_net_tile_topo.c | 1 - src/disco/net/mlx5/test_mlx5_tile.c | 7 - src/disco/net/sock/fd_sock_tile.c | 5 +- src/disco/shred/fd_shred_tile.c | 17 +- src/disco/topo/fd_topo.h | 1 - src/disco/topo/fd_topob_layout.c | 24 +-- src/disco/topo/test_topob.c | 181 ++++++++---------- src/discof/gossip/Local.mk | 2 - src/discof/gossip/fd_gossip_tile.c | 34 +--- src/discof/gossip/fd_gossip_tile.h | 1 - src/discof/gossip/test_gossip_tile.c | 161 ---------------- src/discof/rpc/fd_rpc_tile.c | 11 -- 23 files changed, 122 insertions(+), 590 deletions(-) delete mode 100644 src/discof/gossip/test_gossip_tile.c diff --git a/src/app/shared/commands/run/run.c b/src/app/shared/commands/run/run.c index abc71d677ad..24b57b8e34f 100644 --- a/src/app/shared/commands/run/run.c +++ b/src/app/shared/commands/run/run.c @@ -469,9 +469,8 @@ main_pid_namespace( void * _args ) { if( FD_UNLIKELY( -1==fcntl( FD_COST_TRACKER_FD, F_SETFD, !strcmp( tile->name, "replay" ) ? 0 : FD_CLOEXEC ) ) ) FD_LOG_ERR(( "fcntl(F_SETFD) failed (%i-%s)", errno, fd_io_strerror( errno ) )); - int tile_uses_vote_stakes = !strcmp( tile->name, "replay" ) || !strcmp( tile->name, "tower" ) || - !strcmp( tile->name, "execle" ) || !strcmp( tile->name, "execrp" ) || - !strcmp( tile->name, "snapmk" ); + int tile_uses_vote_stakes = !strcmp( tile->name, "replay" ) || !strcmp( tile->name, "execle" ) || + !strcmp( tile->name, "execrp" ) || !strcmp( tile->name, "snapmk" ); if( FD_UNLIKELY( -1==fcntl( FD_VOTE_STAKES_FD, F_SETFD, tile_uses_vote_stakes ? 0 : FD_CLOEXEC ) ) ) FD_LOG_ERR(( "fcntl(F_SETFD) failed (%i-%s)", errno, fd_io_strerror( errno ) )); diff --git a/src/app/shared/fd_action.h b/src/app/shared/fd_action.h index 6244fd53168..7fabca6d981 100644 --- a/src/app/shared/fd_action.h +++ b/src/app/shared/fd_action.h @@ -129,7 +129,6 @@ union fdctl_args { int dump; /* whether the user requested --dump */ int dump_config; /* whether the user requested to dump the quic config */ int dump_conns; /* whether the user requested to dump the quic connections */ - int trace_send; /* whether the user requested tracing send tile (1) or quic tile (0) */ } quic_trace; struct { diff --git a/src/app/shared_dev/commands/quic_trace/fd_quic_trace.h b/src/app/shared_dev/commands/quic_trace/fd_quic_trace.h index 697ea40b879..39044849bfb 100644 --- a/src/app/shared_dev/commands/quic_trace/fd_quic_trace.h +++ b/src/app/shared_dev/commands/quic_trace/fd_quic_trace.h @@ -43,7 +43,6 @@ struct fd_quic_trace_ctx { int dump; /* whether the user requested --dump */ int dump_config; /* whether the user requested --dump-config */ int dump_conns; /* whether the user requested --dump-conns */ - int trace_send; /* whether the user requested tracing send tile (1) or quic tile (0) */ ulong net_out_base; /* base address of net-out chunks in local addr space */ fd_quic_t * quic; /* local join to remote quic */ @@ -69,9 +68,6 @@ typedef struct fd_quic_trace_frame_ctx fd_quic_trace_frame_ctx_t; (__typeof__(ptr))(laddr); \ }) -#define tile_member( ctx_ptr, field, is_send ) \ -*fd_ptr_if( is_send, &(((fd_txsend_tile_t*)(ctx_ptr))->field), &(((fd_quic_ctx_t*)(ctx_ptr))->field)) - FD_PROTOTYPES_BEGIN diff --git a/src/app/shared_dev/commands/quic_trace/fd_quic_trace_main.c b/src/app/shared_dev/commands/quic_trace/fd_quic_trace_main.c index 98c7c8eda70..468f8981565 100644 --- a/src/app/shared_dev/commands/quic_trace/fd_quic_trace_main.c +++ b/src/app/shared_dev/commands/quic_trace/fd_quic_trace_main.c @@ -18,7 +18,6 @@ #include "../../../../disco/metrics/fd_metrics.h" #include "../../../../disco/quic/fd_quic_tile.h" -#include "../../../../discof/txsend/fd_txsend_tile.h" #include "../../../../waltz/quic/log/fd_quic_log_user.h" #include "../../../../ballet/hex/fd_hex.h" #include @@ -52,7 +51,6 @@ quic_trace_cmd_args( int * pargc, args->quic_trace.dump = fd_env_strip_cmdline_contains( pargc, pargv, "--dump" ); args->quic_trace.dump_config = fd_env_strip_cmdline_contains( pargc, pargv, "--dump-config" ); args->quic_trace.dump_conns = fd_env_strip_cmdline_contains( pargc, pargv, "--dump-conns" ); - args->quic_trace.trace_send = fd_env_strip_cmdline_contains( pargc, pargv, "--send-tile" ); } static char const * @@ -191,20 +189,17 @@ quic_trace_cmd_fn( args_t * args, fd_topo_join_workspaces( topo, FD_SHMEM_JOIN_MODE_READ_ONLY, FD_TOPO_CORE_DUMP_LEVEL_DISABLED ); fd_topo_fill( topo ); - int trace_send = args->quic_trace.trace_send; - - char const * tile_names[] = {"quic", "txsend"}; - fd_topo_tile_t * target_tile = NULL; + fd_topo_tile_t * target_tile = NULL; for( ulong tile_idx=0UL; tile_idxtile_cnt; tile_idx++ ) { - if( 0==strcmp( topo->tiles[tile_idx].name, tile_names[trace_send] ) ) { + if( 0==strcmp( topo->tiles[tile_idx].name, "quic" ) ) { target_tile = &topo->tiles[tile_idx]; break; } } - if( !target_tile ) FD_LOG_ERR(( "%s tile not found in topology", tile_names[trace_send] )); + if( !target_tile ) FD_LOG_ERR(( "quic tile not found in topology" )); ulong const target_in_cnt = target_tile->in_cnt; - if( FD_UNLIKELY( !trace_send && target_in_cnt != 1UL ) ) { /* FIXME */ + if( FD_UNLIKELY( target_in_cnt != 1UL ) ) { /* FIXME */ FD_LOG_ERR(( "Sorry, fd_quic_trace does not support multiple net tiles yet" )); } @@ -214,48 +209,42 @@ quic_trace_cmd_fn( args_t * args, object. */ fd_quic_trace_tile_ctx_remote = fd_topo_obj_laddr( topo, target_tile->tile_obj_id ); - ulong quic_raddr = (ulong)tile_member( fd_quic_trace_tile_ctx_remote, quic, trace_send ); - ulong tile_align = fd_ulong_if( trace_send, alignof(fd_txsend_tile_t), alignof(fd_quic_ctx_t) ); - ulong tile_ctx_sz = fd_ulong_if( trace_send, sizeof(fd_txsend_tile_t), sizeof(fd_quic_ctx_t) ); - fd_quic_trace_tile_ctx_raddr = quic_raddr - fd_ulong_align_up( tile_ctx_sz, fd_ulong_max( tile_align, fd_quic_align() ) ); + ulong quic_raddr = (ulong)((fd_quic_ctx_t *)fd_quic_trace_tile_ctx_remote)->quic; + fd_quic_trace_tile_ctx_raddr = quic_raddr - fd_ulong_align_up( sizeof(fd_quic_ctx_t), fd_ulong_max( alignof(fd_quic_ctx_t), fd_quic_align() ) ); FD_LOG_INFO(("quic_raddr %p", (void *)quic_raddr)); FD_LOG_INFO(( - "%s tile state at %p in tile address space and %p in local address space", - tile_names[trace_send], (void *)fd_quic_trace_tile_ctx_raddr, fd_quic_trace_tile_ctx_remote)); + "quic tile state at %p in tile address space and %p in local address space", + (void *)fd_quic_trace_tile_ctx_raddr, fd_quic_trace_tile_ctx_remote)); /* target_net link tracking */ - char out_link_name[16]; - FD_TEST( fd_cstr_printf_check( out_link_name, sizeof(out_link_name), NULL, "%s_net", tile_names[trace_send] ) ); - ulong link_id = fd_topo_find_link( topo, out_link_name, 0 ); - if( FD_UNLIKELY( link_id == ULONG_MAX ) ) FD_LOG_ERR(("%s not found", out_link_name)); + ulong link_id = fd_topo_find_link( topo, "quic_net", 0 ); + if( FD_UNLIKELY( link_id == ULONG_MAX ) ) FD_LOG_ERR(("quic_net not found")); fd_topo_link_t * target_net = &topo->links[link_id]; /* net_target link tracking */ - FD_TEST( fd_cstr_printf_check( out_link_name, sizeof(out_link_name), NULL, "net_%s", tile_names[trace_send] ) ); - link_id = fd_topo_find_link( topo, out_link_name, 0 ); - if( FD_UNLIKELY( link_id == ULONG_MAX ) ) FD_LOG_ERR(("%s not found", out_link_name)); + link_id = fd_topo_find_link( topo, "net_quic", 0 ); + if( FD_UNLIKELY( link_id == ULONG_MAX ) ) FD_LOG_ERR(("net_quic not found")); fd_topo_link_t * net_target = &topo->links[link_id]; fd_net_rx_bounds_t net_in_bounds; fd_net_rx_bounds_init(&net_in_bounds, net_target->dcache); - FD_LOG_INFO(("net->%s dcache at %p", tile_names[trace_send], (void *)net_target->dcache)); + FD_LOG_INFO(("net->quic dcache at %p", (void *)net_target->dcache)); /* Join shared memory objects Mostly nops but verifies object magic numbers to ensure that derived pointers are correct. */ - FD_LOG_INFO(( "Joining fd_quic in %s tile", tile_names[trace_send] )); + FD_LOG_INFO(( "Joining fd_quic in quic tile" )); fd_quic_t * quic_remote = fd_type_pun( translate_ptr( (void*)quic_raddr ) ); fd_quic_t * quic = fd_quic_join( quic_remote ); - if( !quic ) FD_LOG_ERR( ("Failed to join fd_quic in %s tile", tile_names[trace_send])); + if( !quic ) FD_LOG_ERR( ("Failed to join fd_quic in quic tile")); /* build ctx */ fd_quic_trace_ctx_t trace_ctx[1] = { {.dump = args->quic_trace.dump, .dump_config = args->quic_trace.dump_config, .dump_conns = args->quic_trace.dump_conns, - .trace_send = args->quic_trace.trace_send, .net_out_base = (ulong)fd_wksp_containing(target_net->dcache), .quic = quic, .net_in_bounds = {net_in_bounds} } }; @@ -343,7 +332,7 @@ quic_trace_cmd_fn( args_t * args, void * log = (void *)((ulong)quic_remote + quic->layout.log_off); fd_quic_log_rx_t log_rx[1]; - FD_LOG_DEBUG(( "Joining %s log", tile_names[trace_send] )); + FD_LOG_DEBUG(( "Joining quic log" )); if( FD_UNLIKELY( !fd_quic_log_rx_join( log_rx, log ) ) ) { FD_LOG_ERR(( "fd_quic_log_rx_join failed" )); } @@ -368,7 +357,7 @@ quic_trace_cmd_fn( args_t * args, trace_ctx->quic = quic; - FD_LOG_NOTICE(( "quic-trace on %s tile starting ...", tile_names[trace_send] )); + FD_LOG_NOTICE(( "quic-trace on quic tile starting ..." )); switch( args->quic_trace.event ) { case EVENT_STREAM: fd_quic_trace_rx_tile( trace_ctx, rx_mcache, tx_mcache ); diff --git a/src/app/shared_dev/commands/quic_trace/fd_quic_trace_rx_tile.c b/src/app/shared_dev/commands/quic_trace/fd_quic_trace_rx_tile.c index a9b7db1c970..d24b8dbadfb 100644 --- a/src/app/shared_dev/commands/quic_trace/fd_quic_trace_rx_tile.c +++ b/src/app/shared_dev/commands/quic_trace/fd_quic_trace_rx_tile.c @@ -13,26 +13,13 @@ #include "../../../../util/net/fd_udp.h" static int -before_frag( fd_quic_trace_ctx_t * ctx, +before_frag( fd_quic_trace_ctx_t * ctx FD_FN_UNUSED, ulong in_idx FD_FN_UNUSED, ulong seq FD_FN_UNUSED, ulong sig ) { - /* Skip non-QUIC packets, based on the tile we target */ + /* Skip non-QUIC packets */ ulong proto = fd_disco_netmux_sig_proto( sig ); - int send = ctx->trace_send; - - switch( proto ) { - case DST_PROTO_OUTGOING: - return 0; - case DST_PROTO_TPU_QUIC: - return send; - case DST_PROTO_SEND: - return !send; - default: - return 1; - } - - return 0; + return (proto!=DST_PROTO_OUTGOING) & (proto!=DST_PROTO_TPU_QUIC); } static void @@ -44,7 +31,7 @@ during_frag( fd_quic_trace_ctx_t * ctx, ulong sz, ulong ctl ) { ulong proto = fd_disco_netmux_sig_proto( sig ); - if( (proto==DST_PROTO_TPU_QUIC) | (proto==DST_PROTO_SEND) ) { + if( proto==DST_PROTO_TPU_QUIC ) { fd_memcpy( ctx->buffer, fd_net_rx_translate_frag( &ctx->net_in_bounds[0], chunk, ctl, sz ), sz ); } else if( proto == DST_PROTO_OUTGOING ) { ulong p = ctx->net_out_base + (chunk<pkt_key, EMPTY, sizeof( keys->pkt_key ) ) == 0 ) { - int is_client = ctx->trace_send; + int is_client = 0; int is_egress = (int)key_idx; /* (server,egress) and (client,ingress) don't have the client's orig DCID avail */ diff --git a/src/disco/diag/fd_diag_tile.c b/src/disco/diag/fd_diag_tile.c index 31c0b580f4d..8a93ad74484 100644 --- a/src/disco/diag/fd_diag_tile.c +++ b/src/disco/diag/fd_diag_tile.c @@ -74,7 +74,6 @@ struct fd_diag_tile { ulong bundle_cnt; ulong shred_tile_idx[ FD_TILE_MAX ]; ulong shred_cnt; - ulong tower_idx; ulong votor_idx; ulong replay_idx; } tiles; @@ -177,8 +176,6 @@ struct fd_diag_tile { ulong file_cnt; struct { - ulong prev_vote_slot; - long vote_slot_changed_ns; ulong prev_reset_slot; long reset_slot_changed_ns; ulong prev_turbine_slot; @@ -381,8 +378,6 @@ check_builder_status( fd_diag_tile_t * ctx ) { static void check_engine_metric( fd_diag_tile_t * ctx, long now ) { - static ulong const vote_distance_threshold = 150UL; - static long const vote_stall_threshold_ns = 60L*1000L*1000L*1000L; static ulong const replay_distance_threshold = 12UL; static long const replay_stall_threshold_ns = 12L*1000L*1000L*1000L; static long const turbine_stall_threshold_ns = 12L*1000L*1000L*1000L; @@ -409,7 +404,6 @@ check_engine_metric( fd_diag_tile_t * ctx, long now ) { else bundle_status = FD_DIAG_BUNDLE_STATUS_DISCONNECTED; } - ulong tower_idx = ctx->tiles.tower_idx; ulong votor_idx = ctx->tiles.votor_idx; ulong vote_status = FD_DIAG_VOTE_STATUS_DISABLED; @@ -432,27 +426,6 @@ check_engine_metric( fd_diag_tile_t * ctx, long now ) { vote_status = fd_ulong_if( current, FD_DIAG_VOTE_STATUS_VOTING, FD_DIAG_VOTE_STATUS_DELINQUENT ); } } - } else if( FD_LIKELY( ctx->is_voting && tower_idx!=ULONG_MAX ) ) { - if( FD_UNLIKELY( ctx->metrics[ tower_idx ][ FD_METRICS_GAUGE_TILE_STATE_OFF ]!=1UL ) ) { - vote_status = FD_DIAG_VOTE_STATUS_NOT_STARTED; - } else { - volatile ulong * m = ctx->metrics[ tower_idx ]; - ulong vote_slot = m[ FD_METRICS_GAUGE_TOWER_LAST_VOTE_SLOT_OFF ]; - ulong replay_slot = m[ FD_METRICS_GAUGE_TOWER_LAST_REPLAYED_SLOT_OFF ]; - if( FD_UNLIKELY( !vote_slot || replay_slot==0UL ) ) { - vote_status = FD_DIAG_VOTE_STATUS_NOT_STARTED; - } else { - if( FD_UNLIKELY( vote_slot!=ctx->check_engine.prev_vote_slot ) ) { - ctx->check_engine.prev_vote_slot = vote_slot; - ctx->check_engine.vote_slot_changed_ns = now; - } - int delinquent = (replay_slot>vote_slot && replay_slot-vote_slot>vote_distance_threshold) || - (now-ctx->check_engine.vote_slot_changed_ns>vote_stall_threshold_ns); - vote_status = fd_ulong_if( delinquent, - FD_DIAG_VOTE_STATUS_DELINQUENT, - FD_DIAG_VOTE_STATUS_VOTING ); - } - } } /* Votes land but earn nothing: the stake is not admitted. */ @@ -1745,7 +1718,6 @@ unprivileged_init( fd_topo_t const * topo, for( ulong i=0UL; itiles.bundle_cnt; i++ ) ctx->tiles.bundle_tile_idx[ i ] = fd_topo_find_tile( topo, "bundle", i ); ctx->tiles.shred_cnt = fd_topo_tile_name_cnt( topo, "shred" ); for( ulong i=0UL; itiles.shred_cnt; i++ ) ctx->tiles.shred_tile_idx[ i ] = fd_topo_find_tile( topo, "shred", i ); - ctx->tiles.tower_idx = fd_topo_find_tile( topo, "tower", 0UL ); ctx->tiles.votor_idx = fd_topo_find_tile( topo, "votor", 0UL ); ctx->tiles.replay_idx = fd_topo_find_tile( topo, "replay", 0UL ); @@ -1753,7 +1725,6 @@ unprivileged_init( fd_topo_t const * topo, cpu_owner_refresh( ctx, topo ); ctx->is_voting = tile->diag.is_voting; - ctx->check_engine.vote_slot_changed_ns = ctx->next_report_nanos; ctx->check_engine.reset_slot_changed_ns = ctx->next_report_nanos; ctx->check_engine.turbine_slot_changed_ns = ctx->next_report_nanos; ctx->check_engine.byte_snapshot_ns = ctx->next_report_nanos; diff --git a/src/disco/fd_disco_base.h b/src/disco/fd_disco_base.h index 2e8637d4c63..ec230dbc024 100644 --- a/src/disco/fd_disco_base.h +++ b/src/disco/fd_disco_base.h @@ -13,7 +13,6 @@ #define DST_PROTO_SHRED (3UL) #define DST_PROTO_REPAIR (4UL) #define DST_PROTO_GOSSIP (5UL) -#define DST_PROTO_SEND (6UL) #define DST_PROTO_RSERVE (7UL) #define DST_PROTO_VOTOR (8UL) diff --git a/src/disco/gui/fd_gui.c b/src/disco/gui/fd_gui.c index 74f8522c761..c78f1aa88da 100644 --- a/src/disco/gui/fd_gui.c +++ b/src/disco/gui/fd_gui.c @@ -1061,7 +1061,6 @@ fd_gui_accounts_stats_snap( fd_gui_t * gui, if( !strcmp( topo->tiles[ t_idx ].name, "execle" ) ) { DO_RW( EXECLE ); } else if( !strcmp( topo->tiles[ t_idx ].name, "execrp" ) ) { DO_RW( EXECRP ); } else if( !strcmp( topo->tiles[ t_idx ].name, "replay" ) ) { DO_RW( REPLAY ); } - else if( !strcmp( topo->tiles[ t_idx ].name, "tower" ) ) { DO_RW( TOWER ); } cur->acquired += t_acq; cur->acquired_writable += t_acw; cur->bytes_read += t_bytes_read; @@ -1730,19 +1729,11 @@ static void fd_gui_sample_repair_slot( fd_gui_t * gui, long now ) { ulong slot; - if( FD_UNLIKELY( gui->summary.is_alpenglow ) ) { ulong rotor_tile_idx = fd_topo_find_tile( gui->topo, "rotor", 0UL ); if( FD_UNLIKELY( rotor_tile_idx==ULONG_MAX ) ) return; fd_topo_tile_t const * rotor = &gui->topo->tiles[ rotor_tile_idx ]; volatile ulong const * rotor_metrics = fd_metrics_tile( rotor->metrics ); slot = rotor_metrics[ MIDX( GAUGE, ROTOR, HIGHEST_DELIVERED_SLOT ) ]; - } else { - ulong repair_tile_idx = fd_topo_find_tile( gui->topo, "repair", 0UL ); - if( FD_UNLIKELY( repair_tile_idx==ULONG_MAX ) ) return; - fd_topo_tile_t const * repair = &gui->topo->tiles[ repair_tile_idx ]; - volatile ulong const * repair_metrics = fd_metrics_tile( repair->metrics ); - slot = repair_metrics[ MIDX( GAUGE, REPAIR, HIGHEST_REPAIRED_SLOT ) ]; - } fd_gui_handle_repair_slot( gui, slot, now ); } @@ -3341,122 +3332,6 @@ set_vote_state( fd_gui_t * gui, } } -static inline void -publish_vote_status( fd_gui_t * gui, - int is_voting ) { - if( FD_UNLIKELY( !is_voting ) ) { - set_vote_state( gui, FD_GUI_VOTE_STATE_NON_VOTING ); - return; - } - - fd_gui_slot_t * slot = fd_gui_slot_get( gui, gui->summary.slot_tower, gui->summary.slot_tower_bank_seq ); - FD_TEST( slot ); - - if( FD_UNLIKELY( gui->summary.slot_tower==ULONG_MAX ) ) return; - - if( FD_UNLIKELY( slot->vote_slot==ULONG_MAX ) ) { - /* Voting is enabled but no vote has landed yet. */ - set_vote_state( gui, FD_GUI_VOTE_STATE_DELINQUENT ); - return; - } - - /* Snapshot the fields before the inner loop (which re-resolves slots). */ - ulong vote_slot = slot->vote_slot; - ulong reset_slot = gui->summary.slot_tower; - - ulong vote_distance = reset_slot-vote_slot; - if( FD_LIKELY( vote_distancesummary.vote_distance!=vote_distance ) ) { - gui->summary.vote_distance = vote_distance; - fd_gui_printf_vote_distance( gui ); - fd_http_server_ws_broadcast( gui->http ); - } - - if( FD_UNLIKELY( vote_slot==ULONG_MAX || vote_distance>150UL ) ) { - set_vote_state( gui, FD_GUI_VOTE_STATE_DELINQUENT ); - } else { - set_vote_state( gui, FD_GUI_VOTE_STATE_VOTING ); - } -} - -/* fd_gui_handle_tower_update handles updates from the tower tile, which - manages consensus related fork switching, rooting, slot confirmation. - - The gui tile consumes tower_out via returnable_frag and defers any - update whose replay_slot has not yet been recorded from replay, so by - the time this runs both tower->replay_slot and tower->reset_slot are - guaranteed to have a DB record. */ -void -fd_gui_handle_tower_update( fd_gui_t * gui, - fd_tower_slot_done_t const * tower, - long now ) { - if( FD_UNLIKELY( tower->active_fork_cnt!=gui->summary.active_fork_cnt ) ) { - gui->summary.active_fork_cnt = tower->active_fork_cnt; - fd_gui_printf_active_fork_cnt( gui ); - fd_http_server_ws_broadcast( gui->http ); - } - - fd_gui_slot_t * replay_slot = fd_gui_slot_get( gui, tower->replay_slot, tower->replay_bank_seq ); - FD_TEST( replay_slot ); - if( FD_LIKELY( tower->is_voting ) ) replay_slot->is_voter = FD_GUI_IS_VOTER_YES; - else replay_slot->is_voter = FD_GUI_IS_VOTER_NO; - - /* Handle already-rooted edge case. */ - if( FD_UNLIKELY( replay_slot->level>=FD_GUI_SLOT_LEVEL_ROOTED ) ) { - fd_gui_epoch_t * epoch = fd_gui_get_epoch_by_slot( gui, tower->replay_slot ); - if( FD_LIKELY( epoch ) ) { - ulong cidx = tower->replay_slot - epoch->start_slot; - if( FD_LIKELY( cidxslot_cnt ) ) epoch->is_voter[ cidx ] = replay_slot->is_voter; - } - } - - if( FD_LIKELY( tower->reset_slot!=ULONG_MAX ) ) { - handle_tower_slot( gui, tower->reset_slot, tower->reset_bank_seq, now ); - publish_vote_status( gui, tower->is_voting ); - } else if( FD_UNLIKELY( !tower->is_voting ) ) { - /* NON_VOTING does not need a reset slot, unlike publish_vote_status. */ - set_vote_state( gui, FD_GUI_VOTE_STATE_NON_VOTING ); - } - - if( FD_LIKELY( gui->summary.slot_reset!=tower->reset_slot ) ) { - gui->summary.slot_reset = tower->reset_slot; - fd_gui_printf_reset_slot( gui ); - fd_http_server_ws_broadcast( gui->http ); - } - - if( FD_UNLIKELY( tower->vote_acct_bal!=ULONG_MAX && gui->summary.vote_account_balance!=tower->vote_acct_bal ) ) { - gui->summary.vote_account_balance = tower->vote_acct_bal; - fd_gui_printf_vote_balance( gui ); - fd_http_server_ws_broadcast( gui->http ); - } - - if( FD_UNLIKELY( gui->summary.vote_commission!=tower->vote_acct_com ) ) { - gui->summary.vote_commission = tower->vote_acct_com; - fd_gui_printf_vote_commission( gui ); - fd_http_server_ws_broadcast( gui->http ); - } - - /* Update vote latencies. This is the speculative, true correct - latencies are published as a vote's landed slot is rooted. */ - for( ulong r=0UL; rlanded_vote_cnt; r++ ) { - if( FD_UNLIKELY( gui->landed_votes[ r ].landed_slot>gui->summary.slot_tower ) ) continue; - - ulong voted_bank_seq, exact; - if( FD_UNLIKELY( !fd_gui_compute_vote_latency( gui, gui->landed_votes[ r ].landed_slot, gui->landed_votes[ r ].landed_bank_seq, gui->landed_votes[ r ].voted_slot, &voted_bank_seq, &exact ) ) ) continue; - exact = (uchar)fd_ulong_min( exact, FD_GUI_VOTE_LATENCY_MAX ); - fd_gui_slot_t * slot = fd_gui_slot_get( gui, gui->landed_votes[ r ].voted_slot, voted_bank_seq ); - if( FD_UNLIKELY( !slot ) ) continue; - - if( FD_LIKELY( exact>=slot->vote_latency_exact ) ) continue; - fd_gui_record_vote_latency( gui, gui->landed_votes[ r ].voted_slot, voted_bank_seq, (uchar)exact ); - } -} - void fd_gui_stage_landed_vote( fd_gui_t * gui, ulong landed_slot, diff --git a/src/disco/gui/fd_gui.h b/src/disco/gui/fd_gui.h index 65a1748d5cf..db61943cc83 100644 --- a/src/disco/gui/fd_gui.h +++ b/src/disco/gui/fd_gui.h @@ -16,7 +16,6 @@ #include "../../discof/restore/fd_snapct_tile.h" #include "../../discof/restore/utils/fd_ssmsg.h" #include "../../discof/restore/utils/fd_wfs.h" -#include "../../discof/tower/fd_tower_tile.h" #include "../../discof/replay/fd_replay_tile.h" #include "../../flamenco/leaders/fd_leaders.h" #include "../../flamenco/runtime/sysvar/fd_sysvar_epoch_schedule.h" /* fd_slot_to_epoch */ @@ -1470,11 +1469,6 @@ fd_gui_handle_epoch_info( fd_gui_t * gui, fd_epoch_info_msg_t const * epoch_info, long now ); -void -fd_gui_handle_tower_update( fd_gui_t * gui, - fd_tower_slot_done_t const * msg, - long now ); - void fd_gui_handle_replay_update( fd_gui_t * gui, fd_replay_slot_completed_t const * slot_completed, diff --git a/src/disco/gui/fd_gui_tile.c b/src/disco/gui/fd_gui_tile.c index 49c27465a39..f4431d2ced1 100644 --- a/src/disco/gui/fd_gui_tile.c +++ b/src/disco/gui/fd_gui_tile.c @@ -69,7 +69,6 @@ static ulong const gui_db_type_idx[ FD_GUI_HIST_CNT ] = { #define IN_KIND_GOSSIP_OUT ( 8UL) /* firedancer only */ #define IN_KIND_SNAPCT ( 9UL) /* firedancer only */ #define IN_KIND_REPAIR_NET (10UL) /* firedancer only */ -#define IN_KIND_TOWER_OUT (11UL) /* firedancer only */ #define IN_KIND_REPLAY_OUT (12UL) /* firedancer only */ #define IN_KIND_EPOCH (13UL) /* firedancer only */ #define IN_KIND_GENESI_OUT (14UL) /* firedancer only */ @@ -357,8 +356,6 @@ during_frag( fd_gui_ctx_t * ctx, ulong chunk, ulong sz, ulong ctl ) { - if( FD_UNLIKELY( ctx->in_kind[ in_idx ]==IN_KIND_TOWER_OUT ) ) return; /* process in returnable_frag */ - uchar * src = (uchar *)fd_chunk_to_laddr( ctx->in[ in_idx ].mem, chunk ); if( FD_LIKELY( ctx->in_kind[ in_idx ]==IN_KIND_EPOCH ) ) { @@ -439,8 +436,6 @@ after_frag( fd_gui_ctx_t * ctx, uchar * src = (uchar *)fd_chunk_to_laddr( ctx->in[ in_idx ].mem, ctx->chunk ); - if( FD_UNLIKELY( ctx->in_kind[ in_idx ]==IN_KIND_TOWER_OUT ) ) return; /* process in returnable_frag */ - switch( ctx->in_kind[ in_idx ] ) { case IN_KIND_EXECRP_REPLAY: { if( FD_LIKELY( sig>>32==FD_EXECRP_TT_TXN_EXEC ) ) { @@ -647,29 +642,14 @@ static int returnable_frag( fd_gui_ctx_t * ctx, ulong in_idx, ulong seq FD_PARAM_UNUSED, - ulong sig, - ulong chunk, - ulong sz, + ulong sig FD_PARAM_UNUSED, + ulong chunk FD_PARAM_UNUSED, + ulong sz FD_PARAM_UNUSED, ulong ctl FD_PARAM_UNUSED, ulong tsorig FD_PARAM_UNUSED, ulong tspub FD_PARAM_UNUSED, fd_stem_context_t * stem FD_PARAM_UNUSED ) { if( FD_UNLIKELY( ctx->in_kind[ in_idx ]==IN_KIND_REPLAY_OUT && !ctx->gui->epoch.has_epoch_schedule ) ) return 1; /* epoch info not received yet: defer polling */ - - if( FD_LIKELY( ctx->in_kind[ in_idx ]!=IN_KIND_TOWER_OUT ) ) return 0; /* process in {before|during|after}_frag */ - - if( FD_UNLIKELY( (sz>0UL && (chunkin[ in_idx ].chunk0 || chunk>ctx->in[ in_idx ].wmark)) || sz>ctx->in[ in_idx ].mtu ) ) - FD_LOG_ERR(( "in_kind %lu chunk %lu %lu corrupt, not in range [%lu,%lu] or too large (%lu)", ctx->in_kind[ in_idx ], chunk, sz, ctx->in[ in_idx ].chunk0, ctx->in[ in_idx ].wmark, ctx->in[ in_idx ].mtu )); - - uchar * src = (uchar *)fd_chunk_to_laddr( ctx->in[ in_idx ].mem, chunk ); - - if( FD_LIKELY( sig==FD_TOWER_SIG_SLOT_DONE ) ) { - FD_TEST( sz>=sizeof(fd_tower_slot_done_t) ); - fd_tower_slot_done_t const * tower = (fd_tower_slot_done_t const *)src; - if( FD_UNLIKELY( !fd_gui_slot_get( ctx->gui, tower->replay_slot, tower->replay_bank_seq ) ) ) return 1; /* block not replayed yet: defer polling */ - fd_gui_handle_tower_update( ctx->gui, tower, fd_clock_tile_now( ctx->clock ) ); - } - return 0; } @@ -1014,7 +994,6 @@ unprivileged_init( fd_topo_t const * topo, else if( FD_LIKELY( !strcmp( link->name, "gossip_ciseen" ) ) ) ctx->in_kind[ i ] = IN_KIND_GOSSIP_OUT; else if( FD_LIKELY( !strcmp( link->name, "snapct_gui" ) ) ) ctx->in_kind[ i ] = IN_KIND_SNAPCT; else if( FD_LIKELY( !strcmp( link->name, "repair_net" ) ) ) ctx->in_kind[ i ] = IN_KIND_REPAIR_NET; - else if( FD_LIKELY( !strcmp( link->name, "tower_out" ) ) ) ctx->in_kind[ i ] = IN_KIND_TOWER_OUT; else if( FD_LIKELY( !strcmp( link->name, "replay_out" ) ) ) ctx->in_kind[ i ] = IN_KIND_REPLAY_OUT; else if( FD_LIKELY( !strcmp( link->name, "replay_epoch" ) ) ) ctx->in_kind[ i ] = IN_KIND_EPOCH; else if( FD_LIKELY( !strcmp( link->name, "genesi_out" ) ) ) ctx->in_kind[ i ] = IN_KIND_GENESI_OUT; diff --git a/src/disco/net/fd_net_tile_private.h b/src/disco/net/fd_net_tile_private.h index d60b5a4a31b..4c5d53519e5 100644 --- a/src/disco/net/fd_net_tile_private.h +++ b/src/disco/net/fd_net_tile_private.h @@ -296,7 +296,6 @@ fd_net_rx_dst_ports_init( fd_net_tile_t * ctx, fd_net_rx_dst_port_add( ctx, topo, tile, DST_PROTO_GOSSIP, "net_gossvf", tile->net.gossip_listen_port, 1 ); fd_net_rx_dst_port_add( ctx, topo, tile, DST_PROTO_REPAIR, "net_shred", tile->net.repair_client_listen_port, 1 ); fd_net_rx_dst_port_add( ctx, topo, tile, DST_PROTO_RSERVE, "net_rserve", tile->net.repair_serve_listen_port, 0 ); - fd_net_rx_dst_port_add( ctx, topo, tile, DST_PROTO_SEND, "net_txsend", tile->net.txsend_src_port, 1 ); fd_net_rx_dst_port_add( ctx, topo, tile, DST_PROTO_VOTOR, "net_votor", tile->net.votor_quic_client_listen_port, 1 ); fd_net_rx_dst_port_add( ctx, topo, tile, DST_PROTO_VOTOR, "net_votor", tile->net.votor_quic_server_listen_port, 1 ); diff --git a/src/disco/net/fd_net_tile_topo.c b/src/disco/net/fd_net_tile_topo.c index e93f02c58df..2b699e99fa7 100644 --- a/src/disco/net/fd_net_tile_topo.c +++ b/src/disco/net/fd_net_tile_topo.c @@ -480,7 +480,6 @@ fd_topo_install_xdp( fd_topo_t const * topo, (ushort)net0_tile->xdp.net.gossip_listen_port, (ushort)net0_tile->xdp.net.repair_client_listen_port, (ushort)net0_tile->xdp.net.repair_serve_listen_port, - (ushort)net0_tile->xdp.net.txsend_src_port, (ushort)net0_tile->xdp.net.votor_quic_client_listen_port, (ushort)net0_tile->xdp.net.votor_quic_server_listen_port, }; diff --git a/src/disco/net/mlx5/test_mlx5_tile.c b/src/disco/net/mlx5/test_mlx5_tile.c index b48badbd645..520ab71af69 100644 --- a/src/disco/net/mlx5/test_mlx5_tile.c +++ b/src/disco/net/mlx5/test_mlx5_tile.c @@ -165,7 +165,6 @@ test_rx_routes( void ) { topo_tile->mlx5.net.gossip_listen_port = 8001U; topo_tile->mlx5.net.repair_client_listen_port = 8002U; topo_tile->mlx5.net.repair_serve_listen_port = 8003U; - topo_tile->mlx5.net.txsend_src_port = 8004U; fd_topo_tile_t * topo_tile1 = fd_topob_tile( topo, "mlx5", "net_umem", "net_umem", 1UL, 0, 0, 0 ); fd_topo_obj_t * umem_obj1 = fd_topob_obj( topo, "dcache", "net_umem" ); @@ -179,7 +178,6 @@ test_rx_routes( void ) { fd_topos_net_rx_link( topo, "net_gossvf", 0UL, 128UL ); fd_topos_net_rx_link( topo, "net_repair", 0UL, 128UL ); fd_topos_net_rx_link( topo, "net_rserve", 0UL, 128UL ); - fd_topos_net_rx_link( topo, "net_txsend", 0UL, 128UL ); fd_topos_net_rx_link( topo, "net_quic", 1UL, 128UL ); fd_topos_net_rx_link( topo, "net_shred", 1UL, 128UL ); fd_topos_net_rx_link( topo, "net_repair", 1UL, 128UL ); @@ -215,11 +213,6 @@ test_rx_routes( void ) { FD_TEST( out_idx==fd_topo_find_tile_out_link( topo, topo_tile, "net_rserve", 0UL ) ); FD_TEST( proto==DST_PROTO_RSERVE ); - FD_TEST( fd_net_rx_dst_port_lookup( &tile->net, 8004U, 64UL, &out_idx, &proto ) ); - FD_TEST( out_idx==fd_topo_find_tile_out_link( topo, topo_tile, "net_txsend", 0UL ) ); - FD_TEST( proto==DST_PROTO_SEND ); - FD_TEST( !fd_net_rx_dst_port_lookup( &tile->net, 9999U, 64UL, &out_idx, &proto ) ); - fd_mlx5_tile_t tile1[1]; fd_memset( tile1, 0, sizeof(tile1) ); fd_net_rx_dst_ports_init( &tile1->net, topo, topo_tile1 ); diff --git a/src/disco/net/sock/fd_sock_tile.c b/src/disco/net/sock/fd_sock_tile.c index 49bf75c53b4..f7b64827c42 100644 --- a/src/disco/net/sock/fd_sock_tile.c +++ b/src/disco/net/sock/fd_sock_tile.c @@ -230,7 +230,6 @@ privileged_init( fd_topo_t const * topo, (ushort)tile->sock.net.gossip_listen_port, (ushort)tile->sock.net.repair_client_listen_port, (ushort)tile->sock.net.repair_serve_listen_port, - (ushort)tile->sock.net.txsend_src_port, (ushort)tile->sock.net.votor_quic_client_listen_port, (ushort)tile->sock.net.votor_quic_server_listen_port }; @@ -241,7 +240,6 @@ privileged_init( fd_topo_t const * topo, "net_gossvf", /* gossip_listen_port */ "net_shred", /* shred_listen_port (repair) */ "net_rserve", /* repair_serve_listen_port */ - "net_txsend", /* txsend_src_port */ "net_votor", /* votor_quic_client_listen_port */ "net_votor" /* votor_quic_server_listen_port */ }; @@ -252,11 +250,10 @@ privileged_init( fd_topo_t const * topo, DST_PROTO_GOSSIP, /* gossip_listen_port */ DST_PROTO_REPAIR, /* shred_listen_port (repair) */ DST_PROTO_RSERVE, /* repair_serve_listen_port */ - DST_PROTO_SEND, /* send_src_port */ DST_PROTO_VOTOR, /* votor_quic_client_listen_port */ DST_PROTO_VOTOR /* votor_quic_server_listen_port */ }; - for( uint candidate_idx=0U; candidate_idx<9; candidate_idx++ ) { + for( uint candidate_idx=0U; candidate_idxsock_cnt; if( sock_idx>=FD_SOCK_TILE_MAX_SOCKETS ) FD_LOG_ERR(( "too many sockets" )); diff --git a/src/disco/shred/fd_shred_tile.c b/src/disco/shred/fd_shred_tile.c index f8bf82a7e64..f20598bf455 100644 --- a/src/disco/shred/fd_shred_tile.c +++ b/src/disco/shred/fd_shred_tile.c @@ -31,7 +31,6 @@ #include "../../flamenco/gossip/fd_gossip_message.h" #include "../../flamenco/runtime/sysvar/fd_sysvar_epoch_schedule.h" #include "../../flamenco/runtime/fd_slot_params.h" -#include "../../discof/tower/fd_tower_slot_rooted.h" #include "../../discof/replay/fd_replay_tile.h" #if FD_HAS_AVX #include "../../util/simd/fd_nt_memcpy.h" @@ -98,7 +97,6 @@ #define IN_KIND_REPAIR ( 6UL) #define IN_KIND_IPECHO ( 7UL) #define IN_KIND_GOSSIP ( 8UL) -#define IN_KIND_ROOTED ( 9UL) #define IN_KIND_ROOTEDR (11UL) /* Alpenglow */ #define NET_OUT_IDX 1 @@ -428,9 +426,6 @@ before_frag( fd_shred_ctx_t * ctx, return sig!=FD_GOSSIP_UPDATE_TAG_CONTACT_INFO && sig!=FD_GOSSIP_UPDATE_TAG_CONTACT_INFO_REMOVE; } - if( FD_UNLIKELY( ctx->in_kind[ in_idx ]==IN_KIND_ROOTED ) ) { - return sig!=FD_TOWER_SIG_SLOT_ROOTED; /* only care about slot_confirmed messages */ - } if( FD_UNLIKELY( ctx->in_kind[ in_idx ]==IN_KIND_ROOTEDR ) ) { return sig!=REPLAY_SIG_ROOT_ADVANCED; /* only care about root_advanced messages */ } @@ -470,15 +465,6 @@ during_frag( fd_shred_ctx_t * ctx, return; } - if( FD_UNLIKELY( ctx->in_kind[ in_idx ]==IN_KIND_ROOTED ) ) { - if( FD_UNLIKELY( chunkin[ in_idx ].chunk0 || chunk>ctx->in[ in_idx ].wmark || szin[ in_idx ].chunk0, ctx->in[ in_idx ].wmark )); - fd_tower_slot_rooted_t const * rooted_msg = fd_chunk_to_laddr_const( ctx->in[ in_idx ].mem, chunk ); - ctx->new_root = rooted_msg->slot; - return; - } - /* Alpenglow */ if( FD_UNLIKELY( ctx->in_kind[ in_idx ]==IN_KIND_ROOTEDR ) ) { if( FD_UNLIKELY( chunkin[ in_idx ].chunk0 || chunk>ctx->in[ in_idx ].wmark || szin_kind[ in_idx ]==IN_KIND_ROOTED) | (ctx->in_kind[ in_idx ]==IN_KIND_ROOTEDR) ) ) { + if( FD_UNLIKELY( ctx->in_kind[ in_idx ]==IN_KIND_ROOTEDR ) ) { if( FD_LIKELY( (ctx->new_root > 0UL) & (ctx->new_rootresolver, ctx->new_root ); return; } @@ -1461,7 +1447,6 @@ unprivileged_init( fd_topo_t const * topo, else if( FD_LIKELY( !strcmp( link->name, "replay_epoch" ) ) ) ctx->in_kind[ i ] = IN_KIND_EPOCH; else if( FD_LIKELY( !strcmp( link->name, "sign_shred" ) ) ) ctx->in_kind[ i ] = IN_KIND_SIGN; else if( FD_LIKELY( !strcmp( link->name, "ipecho_out" ) ) ) ctx->in_kind[ i ] = IN_KIND_IPECHO; - else if( FD_LIKELY( !strcmp( link->name, "tower_out" ) ) ) ctx->in_kind[ i ] = IN_KIND_ROOTED; else if( FD_LIKELY( !strcmp( link->name, "replay_slot" ) ) ) ctx->in_kind[ i ] = IN_KIND_ROOTEDR; else if( FD_LIKELY( !strcmp( link->name, "gossip_ciaddr" ) ) ) ctx->in_kind[ i ] = IN_KIND_GOSSIP; diff --git a/src/disco/topo/fd_topo.h b/src/disco/topo/fd_topo.h index 8ab381bf82a..5f9968f5801 100644 --- a/src/disco/topo/fd_topo.h +++ b/src/disco/topo/fd_topo.h @@ -111,7 +111,6 @@ struct fd_topo_net_tile { ushort gossip_listen_port; ushort repair_client_listen_port; ushort repair_serve_listen_port; - ushort txsend_src_port; ushort votor_quic_client_listen_port; ushort votor_quic_server_listen_port; }; diff --git a/src/disco/topo/fd_topob_layout.c b/src/disco/topo/fd_topob_layout.c index 14bb9774dc4..a80d6251ee0 100644 --- a/src/disco/topo/fd_topob_layout.c +++ b/src/disco/topo/fd_topob_layout.c @@ -42,11 +42,9 @@ static struct { char const * name; uint flags; } const TILE_CLASS[] = { { "rpc", ALWAYS }, { "gossvf", ALWAYS }, { "gossip", ALWAYS }, - { "repair", ALWAYS }, { "rotor", ALWAYS }, { "rserve", ALWAYS }, { "replay", ALWAYS|THRUPUT }, - { "tower", ALWAYS }, { "votor", ALWAYS }, { "pktgen", ALWAYS }, { "mwaitx", ALWAYS|PIN_EFF }, @@ -60,10 +58,8 @@ static struct { char const * name; uint flags; } const TILE_CLASS[] = { /* Must sleep until snapshot load finishes */ { "accdb", POSTSTART|THRUPUT }, { "execle", POSTSTART|THRUPUT }, - { "poh", POSTSTART|CRITICAL }, { "motor", POSTSTART|CRITICAL }, { "execrp", POSTSTART|DEDICATED|THRUPUT }, - { "txsend", POSTSTART }, { "solcap", POSTSTART|PIN_EFF }, /* Yield to the kernel scheduler */ @@ -574,9 +570,9 @@ static ulong smt_rank( fd_topo_tile_t const * tile, ulong execrp_floor, int replay_first ) { - static char const * const T4[] = { "tower", "votor", "shred", "net", "mlx5", "sock", "accdb", NULL }; - static char const * const T5[] = { "repair", "rotor", NULL }; - static char const * const T6[] = { "execle", "dedup", "sign", "resolv", "quic", "verify", "txsend", "bundle", "shred", "net", "mlx5", "sock", NULL }; + static char const * const T4[] = { "votor", "shred", "net", "mlx5", "sock", "accdb", NULL }; + static char const * const T5[] = { "rotor", NULL }; + static char const * const T6[] = { "execle", "dedup", "sign", "resolv", "quic", "verify", "bundle", "shred", "net", "mlx5", "sock", NULL }; static char const * const T7[] = { "gui", "rpc", "event", "rserve", "gossip", "gossvf", NULL }; char const * name = tile->name; @@ -965,11 +961,11 @@ fd_topob_auto_layout_cpus( fd_topo_t * topo, for( ulong i=0UL; icpu_cnt && graded && efficient; i++ ) graded = cpu_bv_test( cpu_assigned, i ) || !cpus->cpu[ i ].isolated; cpu_bv_t numa_fence[ cpu_bv_word_cnt ]; cpu_bv_new( numa_fence ); - /* Pack, poh, accdb and execle go first into one L3 with the cores the + /* Pack, motor, accdb and execle go first into one L3 with the cores the plan gives them, pairing execle to fit and spilling the highest to the next L3. The rest is planned around them, or without them if it then does not fit. */ - static char const * const GROUP[] = { "pack", "poh", "motor", "accdb", "execle", NULL }; + static char const * const GROUP[] = { "pack", "motor", "accdb", "execle", NULL }; int group = !efficient && fd_topo_cpus_l3_complete( cpus ); cpu_bv_t base [ cpu_bv_word_cnt ]; cpu_bv_copy( base, cpu_assigned ); cpu_bv_t group_post[ cpu_bv_word_cnt ]; cpu_bv_new( group_post ); /* free during snapshot load */ @@ -1020,12 +1016,12 @@ fd_topob_auto_layout_cpus( fd_topo_t * topo, } if( attempt || !group || !( graded || skip_ht_pairs ) ) break; - ulong lead = 0UL, ex = 0UL, ew = 0UL; /* cores of pack, poh and accdb, execle, execle on whole cores */ + ulong lead = 0UL, ex = 0UL, ew = 0UL; /* cores of pack, motor and accdb, execle, execle on whole cores */ for( ulong i=0UL; i<( graded ? steady_cnt : topo->tile_cnt ); i++ ) { ulong g = name_idx( topo->tiles[ graded ? steady_idx[ i ] : i ].name, GROUP ); - lead += (ulong)( g<4UL ); - ex += (ulong)( g==4UL ); - ew += (ulong)( g==4UL && ( !graded || steady_mode[ i ]==SMT_WHOLE ) ); + lead += (ulong)( g<3UL ); + ex += (ulong)( g==3UL ); + ew += (ulong)( g==3UL && ( !graded || steady_mode[ i ]==SMT_WHOLE ) ); } /* First L3 in CPU order that fits, else fits execle paired, else the largest */ ulong l3 = ULONG_MAX, l3_cores = 0UL, best = 0UL; @@ -1054,7 +1050,7 @@ fd_topob_auto_layout_cpus( fd_topo_t * topo, if( c==ULONG_MAX ) { l3 = ULONG_MAX; break; } /* the rest is placed as usual */ ulong s = cpus->cpu[ c ].sibling; ulong m = ULONG_MAX; /* tile on the sibling, which goes to the rest if s is cleared */ - if( g<4UL ) { + if( g<3UL ) { for( ulong i=0UL; i 23 physical cores) ------ - 13 cores shared: pack and poh alone, execrp:0..7 whole, replay, tower, +/* ---- Firedancer 24×2 (no skip HT, 35 tiles > 23 physical cores) ------ + 12 cores shared: pack and motor alone, execrp:0..8 whole, replay, votor, shred and net:0 next to cold tiles. */ static char const * const FD_24X2[] = { /* 0 */ __, /* 1 */ "net", /* 2 */ "net", /* 3 */ "quic", - /* 4 */ "verify", /* 5 */ "verify", /* 6 */ "verify", /* 7 */ "pack", - /* 8 */ "sign", /* 9 */ "sign", /* 10 */ "shred", /* 11 */ "gui", - /* 12 */ "gossvf", /* 13 */ "execle", /* 14 */ "poh", /* 15 */ "execrp", + /* 4 */ "verify", /* 5 */ "verify", /* 6 */ "verify", /* 7 */ "resolv", + /* 8 */ "pack", /* 9 */ "sign", /* 10 */ "shred", /* 11 */ "gui", + /* 12 */ "gossvf", /* 13 */ "execle", /* 14 */ "motor", /* 15 */ "execrp", /* 16 */ "execrp", /* 17 */ "execrp", /* 18 */ "execrp", /* 19 */ "execrp", /* 20 */ "execrp", /* 21 */ "execrp", /* 22 */ "execrp", /* 23 */ "execrp", /* --- HT siblings (24-47) --- */ - /* 24 */ __, /* 25 */ "gossip", /* 26 */ "dedup", /* 27 */ "resolv", - /* 28 */ "verify", /* 29 */ "verify", /* 30 */ "verify", /* 31 */ __, - /* 32 */ "txsend", /* 33 */ "replay", /* 34 */ "gossvf", /* 35 */ "repair", - /* 36 */ "tower", /* 37 */ "execle", /* 38 */ __, /* 39 */ __, - /* 40 */ __, /* 41 */ __, /* 42 */ __, /* 43 */ __, - /* 44 */ __, /* 45 */ __, /* 46 */ __, /* 47 */ "execrp", + /* 24 */ __, /* 25 */ "gossip", /* 26 */ "rotor", /* 27 */ "dedup", + /* 28 */ "verify", /* 29 */ "verify", /* 30 */ "verify", /* 31 */ "sign", + /* 32 */ __, /* 33 */ "replay", /* 34 */ "gossvf", /* 35 */ "execrp", + /* 36 */ "votor", /* 37 */ "execle", }; #define FD_24X2_LEN (sizeof(FD_24X2)/sizeof(FD_24X2[0])) -/* ---- Firedancer 32×2 (no skip HT, 36 tiles > 31 physical cores) ------ +/* ---- Firedancer 32×2 (no skip HT, 35 tiles > 31 physical cores) ------ 5 cores shared, no core left idle. */ static char const * const FD_32X2[] = { @@ -498,14 +494,14 @@ static char const * const FD_32X2[] = { /* 4 */ "verify", /* 5 */ "verify", /* 6 */ "verify", /* 7 */ "verify", /* 8 */ "verify", /* 9 */ "dedup", /* 10 */ "resolv", /* 11 */ "pack", /* 12 */ "sign", /* 13 */ "sign", /* 14 */ "shred", /* 15 */ "gossvf", - /* 16 */ "repair", /* 17 */ "replay", /* 18 */ "tower", /* 19 */ "execle", - /* 20 */ "execle", /* 21 */ "poh", /* 22 */ "execrp", /* 23 */ "execrp", + /* 16 */ "rotor", /* 17 */ "replay", /* 18 */ "votor", /* 19 */ "execle", + /* 20 */ "execle", /* 21 */ "motor", /* 22 */ "execrp", /* 23 */ "execrp", /* 24 */ "execrp", /* 25 */ "execrp", /* 26 */ "execrp", /* 27 */ "execrp", /* 28 */ "execrp", /* 29 */ "execrp", /* 30 */ "execrp", /* 31 */ "execrp", /* --- HT siblings (32-63) --- */ - /* 32 */ __, /* 33 */ __, /* 34 */ "txsend", /* 35 */ __, - /* 36 */ __, /* 37 */ __, /* 38 */ __, /* 39 */ "verify", - /* 40 */ "gossvf", /* 41 */ __, /* 42 */ __, /* 43 */ __, + /* 32 */ __, /* 33 */ __, /* 34 */ "gossvf", /* 35 */ __, + /* 36 */ __, /* 37 */ __, /* 38 */ __, /* 39 */ __, + /* 40 */ "verify", /* 41 */ __, /* 42 */ __, /* 43 */ __, /* 44 */ __, /* 45 */ "gui", /* 46 */ __, /* 47 */ "gossip", }; #define FD_32X2_LEN (sizeof(FD_32X2)/sizeof(FD_32X2[0])) @@ -518,10 +514,10 @@ static tile_spec_t const FD_FEWER_TILES[] = { { "metric", 1 }, { "diag", 1 }, { "genesi", 1 }, { "ipecho", 1 }, { "admin", 1 }, { "net", 2 }, { "quic", 1 }, { "verify", 2 }, { "dedup", 1 }, - { "resolv", 1 }, { "pack", 1 }, { "execle", 2 }, { "poh", 1 }, + { "resolv", 1 }, { "pack", 1 }, { "execle", 2 }, { "motor", 1 }, { "shred", 1 }, { "sign", 2 }, { "gui", 1 }, { "gossvf", 2 }, - { "gossip", 1 }, { "repair", 1 }, { "replay", 1 }, { "execrp", 4 }, - { "txsend", 1 }, { "tower", 1 }, + { "gossip", 1 }, { "rotor", 1 }, { "replay", 1 }, { "execrp", 4 }, + { "votor", 1 }, { NULL, 0 } }; @@ -531,28 +527,28 @@ static char const * const FD_32X2_FEWER[] = { /* 4 */ "verify", /* 5 */ "verify", /* 6 */ "dedup", /* 7 */ "resolv", /* 8 */ "pack", /* 9 */ "sign", /* 10 */ "sign", /* 11 */ "shred", /* 12 */ "gui", /* 13 */ "gossvf", /* 14 */ "gossvf", /* 15 */ "gossip", - /* 16 */ "repair", /* 17 */ "replay", /* 18 */ "tower", /* 19 */ "execle", - /* 20 */ "execle", /* 21 */ "poh", /* 22 */ "execrp", /* 23 */ "execrp", - /* 24 */ "execrp", /* 25 */ "execrp", /* 26 */ "txsend", + /* 16 */ "rotor", /* 17 */ "replay", /* 18 */ "votor", /* 19 */ "execle", + /* 20 */ "execle", /* 21 */ "motor", /* 22 */ "execrp", /* 23 */ "execrp", + /* 24 */ "execrp", /* 25 */ "execrp", }; #define FD_32X2_FEWER_LEN (sizeof(FD_32X2_FEWER)/sizeof(FD_32X2_FEWER[0])) /* ---- Variation: Firedancer 32×2, extra blocklist ----------------------- - blocklist = "0h,5h" → {0,32,5,37}. 30 phys avail. 36 tiles > 30. */ + blocklist = "0h,5h" → {0,32,5,37}. 30 phys avail. 35 tiles > 30. */ static char const * const FD_32X2_EXTRA_BL[] = { /* 0 */ __, /* 1 */ "net", /* 2 */ "net", /* 3 */ "quic", /* 4 */ "verify", /* 5 */ __, /* 6 */ "verify", /* 7 */ "verify", /* 8 */ "verify", /* 9 */ "dedup", /* 10 */ "resolv", /* 11 */ "pack", /* 12 */ "sign", /* 13 */ "sign", /* 14 */ "shred", /* 15 */ "gossvf", - /* 16 */ "repair", /* 17 */ "replay", /* 18 */ "tower", /* 19 */ "execle", - /* 20 */ "execle", /* 21 */ "poh", /* 22 */ "execrp", /* 23 */ "execrp", + /* 16 */ "rotor", /* 17 */ "replay", /* 18 */ "votor", /* 19 */ "execle", + /* 20 */ "execle", /* 21 */ "motor", /* 22 */ "execrp", /* 23 */ "execrp", /* 24 */ "execrp", /* 25 */ "execrp", /* 26 */ "execrp", /* 27 */ "execrp", /* 28 */ "execrp", /* 29 */ "execrp", /* 30 */ "execrp", /* 31 */ "execrp", /* --- HT siblings (32-63) --- */ - /* 32 */ __, /* 33 */ __, /* 34 */ "txsend", /* 35 */ __, - /* 36 */ __, /* 37 */ __, /* 38 */ "verify", /* 39 */ "verify", - /* 40 */ "gossvf", /* 41 */ __, /* 42 */ __, /* 43 */ __, + /* 32 */ __, /* 33 */ __, /* 34 */ "gossvf", /* 35 */ __, + /* 36 */ __, /* 37 */ __, /* 38 */ __, /* 39 */ "verify", + /* 40 */ "verify", /* 41 */ __, /* 42 */ __, /* 43 */ __, /* 44 */ __, /* 45 */ "gui", /* 46 */ __, /* 47 */ "gossip", }; #define FD_32X2_EXTRA_BL_LEN (sizeof(FD_32X2_EXTRA_BL)/sizeof(FD_32X2_EXTRA_BL[0])) @@ -569,8 +565,8 @@ FD_UNIT_TEST( test_firedancer_24x2 ) { run_test( 24, FIREDANCER_TILES, BLOCKLIST_0H( 24 ), FD_24X2_LEN, FD_24X2 ); } -/* Spare cores go to execrp in kind_id order: on 24x2 execrp:0..7 get a - physical core to themselves and only the last two share. */ +/* Spare cores go to execrp in kind_id order: on 24x2 execrp:0..8 get a + physical core to themselves and only the last one shares. */ FD_UNIT_TEST( test_execrp_dedicated_order ) { fd_topo_cpus_t cpus[1]; @@ -590,7 +586,7 @@ FD_UNIT_TEST( test_execrp_dedicated_order ) { FD_TEST( sibling!=ULONG_MAX ); int shared = 0; for( ulong j=0UL; jtile_cnt; j++ ) shared |= topo->tiles[ j ].cpu_idx==sibling; - FD_TEST( shared==(tile->kind_id>=8UL) ); + FD_TEST( shared==(tile->kind_id>=9UL) ); } } @@ -629,7 +625,7 @@ FD_UNIT_TEST( test_firedancer_128x2 ) { /* --- Firedancer efficient mode ------------------------------------------ */ -/* Performance mode only places pack, poh and execle by L3 */ +/* Performance mode only places pack, motor and execle by L3 */ FD_UNIT_TEST( test_firedancer_48x2_l3_performance ) { run_layout( 48, 6, NULL, 0, FIREDANCER_TILES, BLOCKLIST_0H( 48 ), FD_48X2_L3_PERF_LEN, FD_48X2_L3_PERF ); } @@ -742,13 +738,13 @@ FD_UNIT_TEST( test_offline_low_index ) { } } -/* 36x2 is one physical core short of skipping HT pairs. Offline +/* 35x2 is one physical core short of skipping HT pairs. Offline siblings must not count as free cores: with them the layout would skip pairs and place tiles on the offline CPUs. */ FD_UNIT_TEST( test_offline_skip_ht_threshold ) { - fd_topo_t * topo = layout_offline( 36, (ulong const[]){ 48, 61, ULONG_MAX }, FIREDANCER_TILES ); + fd_topo_t * topo = layout_offline( 35, (ulong const[]){ 47, 60, ULONG_MAX }, FIREDANCER_TILES ); int on_sibling = 0; - for( ulong i=0UL; itile_cnt; i++ ) on_sibling |= topo->tiles[ i ].cpu_idx!=ULONG_MAX && topo->tiles[ i ].cpu_idx>=36UL; + for( ulong i=0UL; itile_cnt; i++ ) on_sibling |= topo->tiles[ i ].cpu_idx!=ULONG_MAX && topo->tiles[ i ].cpu_idx>=35UL; FD_TEST( on_sibling ); } @@ -985,9 +981,9 @@ static tile_spec_t const FIREDANCER_SNAP_EFF_TILES[] = { { "netlnk", 1 }, { "metric", 1 }, { "diag", 1 }, { "genesi", 1 }, { "ipecho", 1 }, { "admin", 1 }, { "waker", 1 }, { "mwaitx", 1 }, { "net", 1 }, { "quic", 1 }, { "verify", 3 }, { "dedup", 1 }, { "resolv", 1 }, - { "pack", 1 }, { "execle", 2 }, { "poh", 1 }, { "shred", 1 }, { "sign", 1 }, - { "gui", 1 }, { "gossvf", 1 }, { "gossip", 1 }, { "repair", 1 }, { "replay", 1 }, - { "accdb", 1 }, { "execrp",10 }, { "txsend", 1 }, { "tower", 1 }, + { "pack", 1 }, { "execle", 2 }, { "motor", 1 }, { "shred", 1 }, { "sign", 1 }, + { "gui", 1 }, { "gossvf", 1 }, { "gossip", 1 }, { "rotor", 1 }, { "replay", 1 }, + { "accdb", 1 }, { "execrp",10 }, { "votor", 1 }, { "snapct", 1 }, { "snapld", 1 }, { "snapdc", 3 }, { "snapin", 5 }, { NULL, 0 } }; @@ -1019,7 +1015,7 @@ FD_UNIT_TEST( test_tile_class_phase ) { FD_TOPOB_TILE_ALWAYS, FD_TOPOB_TILE_POSTSTART, FD_TOPOB_TILE_FLOATING, 0U }; for( ulong i=0UL; isleep_obj_id = 0UL; @@ -1192,11 +1188,11 @@ layout_isolated( ulong physical_cores, } /* Whole cores are planned before isolated CPUs are handed out: verify - taking isolated 3 and pack the non-isolated core 1/5 left txsend no + taking isolated 3 and pack the non-isolated core 1/5 left rserve no CPU though isolated 7 was free */ FD_UNIT_TEST( test_isolated_plan_whole_cores ) { layout_isolated( 4, 0, (ulong const[]){ 3, 7, ULONG_MAX }, (ulong const[]){ ULONG_MAX }, - (tile_spec_t const[]){ { "quic", 1 }, { "verify", 1 }, { "dedup", 1 }, { "pack", 1 }, { "sign", 1 }, { "shred", 1 }, { "txsend", 1 }, { NULL, 0 } }, + (tile_spec_t const[]){ { "quic", 1 }, { "verify", 1 }, { "dedup", 1 }, { "pack", 1 }, { "sign", 1 }, { "shred", 1 }, { "rserve", 1 }, { NULL, 0 } }, (ulong const[]){ 0, 4, 1, 3, 5, 2, 6 } ); } @@ -1204,23 +1200,23 @@ FD_UNIT_TEST( test_isolated_plan_whole_cores ) { tile, or a pinned tile took the all-isolated core pack wanted */ FD_UNIT_TEST( test_isolated_plan_fits ) { layout_isolated( 5, 0, (ulong const[]){ 1, 2, 3, 4, 6, ULONG_MAX }, (ulong const[]){ 0, 5, ULONG_MAX }, - (tile_spec_t const[]){ { "shred", 1 }, { "txsend", 1 }, { "execle", 1 }, { "execrp", 1 }, { "gossip", 1 }, { NULL, 0 } }, + (tile_spec_t const[]){ { "shred", 1 }, { "rserve", 1 }, { "execle", 1 }, { "execrp", 1 }, { "gossip", 1 }, { NULL, 0 } }, (ulong const[]){ 7, 9, 1, 6, 8 } ); layout_isolated( 3, 0, (ulong const[]){ 1, 2, ULONG_MAX }, (ulong const[]){ 0, 3, ULONG_MAX }, - (tile_spec_t const[]){ { "sign", 1 }, { "txsend", 1 }, { "execrp", 1 }, { NULL, 0 } }, + (tile_spec_t const[]){ { "sign", 1 }, { "rserve", 1 }, { "execrp", 1 }, { NULL, 0 } }, (ulong const[]){ 4, 5, 1 } ); layout_isolated( 3, 2, (ulong const[]){ 0, 1, 2, 4, ULONG_MAX }, (ulong const[]){ ULONG_MAX }, - (tile_spec_t const[]){ { "pack", 1 }, { "txsend", 1 }, { "tower", 1 }, { NULL, 0 } }, - (ulong const[]){ 1, 5, 3 } ); + (tile_spec_t const[]){ { "pack", 1 }, { "rserve", 1 }, { "votor", 1 }, { NULL, 0 } }, + (ulong const[]){ 1, 3, 5 } ); layout_isolated( 3, 2, (ulong const[]){ 0, 1, 2, 5, ULONG_MAX }, (ulong const[]){ ULONG_MAX }, - (tile_spec_t const[]){ { "shred", 1 }, { "txsend", 1 }, { "execrp", 1 }, { NULL, 0 } }, + (tile_spec_t const[]){ { "shred", 1 }, { "rserve", 1 }, { "execrp", 1 }, { NULL, 0 } }, (ulong const[]){ 3, 4, 2 } ); layout_isolated( 3, 0, (ulong const[]){ 0, 1, 2, 5, ULONG_MAX }, (ulong const[]){ ULONG_MAX }, (tile_spec_t const[]){ { "pack", 1 }, { "shred", 1 }, { "mlx5", 1 }, { NULL, 0 } }, (ulong const[]){ 2, 4, 3 } ); layout_isolated( 3, 2, (ulong const[]){ 0, 1, 2, ULONG_MAX }, (ulong const[]){ ULONG_MAX }, - (tile_spec_t const[]){ { "txsend", 1 }, { "execrp", 1 }, { "mlx5", 1 }, { "tower", 1 }, { NULL, 0 } }, - (ulong const[]){ 5, 0, 3, 4 } ); + (tile_spec_t const[]){ { "rserve", 1 }, { "execrp", 1 }, { "mlx5", 1 }, { "votor", 1 }, { NULL, 0 } }, + (ulong const[]){ 4, 0, 3, 5 } ); layout_isolated( 3, 2, (ulong const[]){ 0, 1, 3, ULONG_MAX }, (ulong const[]){ ULONG_MAX }, (tile_spec_t const[]){ { "verify", 1 }, { "resolv", 1 }, { "pack", 1 }, { "sign", 1 }, { NULL, 0 } }, (ulong const[]){ 1, 4, 0, 2 } ); @@ -1398,27 +1394,14 @@ make_cpus_shape( fd_topo_cpus_t * cpus, } /* Default Firedancer topology with snapshots, rpc, telemetry and - bundles (mainnet configuration), and its Alpenglow variant. */ + bundles (mainnet configuration). */ static tile_spec_t const MAINNET_TILES[] = { - { "netlnk", 1 }, { "net", 2 }, { "metric", 1 }, { "diag", 1 }, - { "snapct", 1 }, { "snapld", 1 }, { "snapdc", 3 }, { "snapin", 5 }, - { "genesi", 1 }, { "ipecho", 1 }, { "gossvf", 1 }, { "gossip", 1 }, - { "shred", 1 }, { "repair", 1 }, { "rserve", 1 }, { "replay", 1 }, - { "accdb", 1 }, { "execrp",10 }, { "tower", 1 }, { "txsend", 1 }, - { "quic", 1 }, { "verify", 3 }, { "dedup", 1 }, { "resolv", 1 }, - { "pack", 1 }, { "execle", 2 }, { "poh", 1 }, { "sign", 2 }, - { "rpc", 1 }, { "admin", 1 }, { "event", 1 }, { "bundle", 1 }, - { "gui", 1 }, - { NULL, 0 } -}; - -static tile_spec_t const MAINNET_AG_TILES[] = { { "netlnk", 1 }, { "net", 2 }, { "metric", 1 }, { "diag", 1 }, { "snapct", 1 }, { "snapld", 1 }, { "snapdc", 3 }, { "snapin", 5 }, { "genesi", 1 }, { "ipecho", 1 }, { "gossvf", 1 }, { "gossip", 1 }, { "shred", 1 }, { "rotor", 1 }, { "rserve", 1 }, { "replay", 1 }, - { "accdb", 1 }, { "execrp",10 }, { "votor", 1 }, { "tower", 1 }, + { "accdb", 1 }, { "execrp",10 }, { "votor", 1 }, { "quic", 1 }, { "verify", 3 }, { "dedup", 1 }, { "resolv", 1 }, { "pack", 1 }, { "execle", 2 }, { "motor", 1 }, { "sign", 2 }, { "rpc", 1 }, { "admin", 1 }, { "event", 1 }, { "bundle", 1 }, @@ -1431,10 +1414,10 @@ static tile_spec_t const MAINNET_WIDE_TILES[] = { { "netlnk", 1 }, { "net", 2 }, { "metric", 1 }, { "diag", 1 }, { "snapct", 1 }, { "snapld", 1 }, { "snapdc", 3 }, { "snapin", 5 }, { "genesi", 1 }, { "ipecho", 1 }, { "gossvf", 2 }, { "gossip", 1 }, - { "shred", 1 }, { "repair", 1 }, { "rserve", 1 }, { "replay", 1 }, - { "accdb", 1 }, { "execrp",10 }, { "tower", 1 }, { "txsend", 1 }, + { "shred", 1 }, { "rotor", 1 }, { "rserve", 1 }, { "replay", 1 }, + { "accdb", 1 }, { "execrp",10 }, { "votor", 1 }, { "quic", 1 }, { "verify", 6 }, { "dedup", 1 }, { "resolv", 1 }, - { "pack", 1 }, { "execle", 2 }, { "poh", 1 }, { "sign", 2 }, + { "pack", 1 }, { "execle", 2 }, { "motor", 1 }, { "sign", 2 }, { "rpc", 1 }, { "admin", 1 }, { "event", 1 }, { "bundle", 1 }, { "gui", 1 }, { NULL, 0 } @@ -1501,7 +1484,7 @@ check_layout_invariants( fd_topo_t const * topo, FD_TEST( !( shared && idle ) ); } -/* Performance mode with a cache topology keeps poh, accdb and the +/* Performance mode with a cache topology keeps motor, accdb and the lowest execle in pack's L3, spilling only the highest execle */ static void check_group_l3( fd_topo_t const * topo, @@ -1512,7 +1495,7 @@ check_group_l3( fd_topo_t const * topo, ulong spill = 0UL; for( ulong i=0UL; itile_cnt; i++ ) { fd_topo_tile_t const * tile = &topo->tiles[ i ]; - if( !name_in( tile->name, (char const *[]){ "poh", "motor", "accdb", "execle", NULL } ) ) continue; + if( !name_in( tile->name, (char const *[]){ "motor", "motor", "accdb", "execle", NULL } ) ) continue; int in = cpus->cpu[ tile->cpu_idx ].l3_idx==l3; if( strcmp( tile->name, "execle" ) ) FD_TEST( in ); else if( !in ) spill++; @@ -1608,7 +1591,7 @@ FD_UNIT_TEST( test_fleet_shapes ) { { 2, 1, 32,32, SMT_ON, NULL, 0 }, /* Xeon 8462Y+ 2S */ { 1, 1, 24, 6, SMT_ADJACENT, NULL, 0 }, /* VM, adjacent siblings */ }; - tile_spec_t const * const TILES[] = { MAINNET_TILES, MAINNET_AG_TILES, MAINNET_WIDE_TILES, FIREDANCER_TILES, FD_FEWER_TILES }; + tile_spec_t const * const TILES[] = { MAINNET_TILES, MAINNET_WIDE_TILES, FIREDANCER_TILES, FD_FEWER_TILES }; for( ulong i=0UL; itile_cnt; j++ ) { fd_topo_tile_t const * b = &topo->tiles[ j ]; if( j==i || b->cpu_idx!=cpus->cpu[ a->cpu_idx ].sibling || fd_topob_tile_priority_type( b )==FD_TOPOB_PRIORITY_STARTUP ) continue; - int protect = !strcmp( a->name, "replay" ) || !strcmp( a->name, "tower" ) || !strcmp( a->name, "accdb" ) || + int protect = !strcmp( a->name, "replay" ) || !strcmp( a->name, "votor" ) || !strcmp( a->name, "accdb" ) || ( !a->kind_id && ( !strcmp( a->name, "shred" ) || !strcmp( a->name, "net" ) ) ); int b_cold = name_in( b->name, cold ) && ( strcmp( b->name, "sign" ) || b->kind_id ); if( protect ) FD_TEST( b_cold ); diff --git a/src/discof/gossip/Local.mk b/src/discof/gossip/Local.mk index f94e7ea22b9..ea5b00c952c 100644 --- a/src/discof/gossip/Local.mk +++ b/src/discof/gossip/Local.mk @@ -3,6 +3,4 @@ $(call add-objs,fd_gossip_tile,fd_discof) $(call add-objs,fd_gossvf_tile,fd_discof) $(call make-fuzz-test,fuzz_gossvf_tile,fuzz_gossvf_tile,fd_disco fd_waltz fd_flamenco fd_ballet fd_tango fd_util) $(call make-fuzz-test,fuzz_gossvf_gossip_pair,fuzz_gossvf_gossip_pair,fd_disco fd_waltz fd_flamenco fd_ballet fd_tango fd_util) -$(call make-unit-test,test_gossip_tile,test_gossip_tile,fd_discof fd_choreo fd_disco fd_flamenco fd_waltz fd_tango fd_ballet fd_util) -$(call run-unit-test,test_gossip_tile) endif diff --git a/src/discof/gossip/fd_gossip_tile.c b/src/discof/gossip/fd_gossip_tile.c index 9d2f3e2e0d2..d828d734d42 100644 --- a/src/discof/gossip/fd_gossip_tile.c +++ b/src/discof/gossip/fd_gossip_tile.c @@ -3,7 +3,6 @@ #include #include "generated/fd_gossip_tile_seccomp.h" -#include "../../choreo/eqvoc/fd_eqvoc.h" #include "../../flamenco/gossip/fd_gossip_out.h" #include "../../flamenco/gossip/fd_active_set.h" #include "../../flamenco/features/fd_features.h" @@ -12,16 +11,13 @@ #include "../../flamenco/stakes/fd_stake_weight_sort.h" #include "../../disco/topo/fd_dns_resolve.h" #include "../../disco/fd_txn_m.h" -#include "../tower/fd_tower_tile.h" #include "../restore/utils/fd_ssmsg.h" #include "../restore/utils/fd_wfs.h" #define IN_KIND_GOSSVF (0) #define IN_KIND_SHRED_VERSION (1) #define IN_KIND_SIGN (2) -#define IN_KIND_TXSEND (3) #define IN_KIND_EPOCH (4) -#define IN_KIND_TOWER (5) #define IN_KIND_SNAPIN_MANIF (6) FD_FN_CONST static inline ulong @@ -182,9 +178,6 @@ during_housekeeping( fd_gossip_tile_ctx_t * ctx ) { } if( FD_UNLIKELY( fd_keyswitch_state_query( ctx->keyswitch )==FD_KEYSWITCH_STATE_SWITCH_PENDING ) ) { - /* TxSend votes before seq param are signed by the old identity, so - push them while the sign tile still has the old key. */ - if( FD_UNLIKELY( fd_seq_lt( ctx->txsend_in_seq, ctx->keyswitch->param ) ) ) return; ctx->is_halting_signing = 1; if( FD_LIKELY( !fd_gossip_sign_pend_cnt( ctx->gossip ) ) ) fd_keyswitch_state( ctx->keyswitch, FD_KEYSWITCH_STATE_COMPLETED ); } @@ -343,13 +336,6 @@ handle_shred_version( fd_gossip_tile_ctx_t * ctx, fd_gossip_set_shred_version( ctx->gossip, (ushort)sig ); } -static void -handle_local_vote( fd_gossip_tile_ctx_t * ctx, - fd_txn_m_t const * txn_m ) { - long now = fd_clock_tile_now( ctx->clock ); - fd_gossip_push_vote( ctx->gossip, fd_txn_m_payload_const( txn_m ), txn_m->payload_sz, now ); -} - static void handle_epoch( fd_gossip_tile_ctx_t * ctx, fd_epoch_info_msg_t const * msg ) { @@ -388,16 +374,6 @@ handle_packet( fd_gossip_tile_ctx_t * ctx, } } -static void -handle_local_duplicate_shred( fd_gossip_tile_ctx_t * ctx, - ulong sig, - fd_gossip_duplicate_shred_t const chunk[FD_EQVOC_CHUNK_CNT] ) { - if( FD_UNLIKELY( sig==FD_TOWER_SIG_SLOT_DUPLICATE ) ) { - long now = fd_clock_tile_now( ctx->clock ); - for( ulong i=0UL; igossip, &chunk[i], now ); - } -} - static inline int before_frag( fd_gossip_tile_ctx_t * ctx, ulong in_idx, @@ -471,7 +447,7 @@ after_frag( fd_gossip_tile_ctx_t * ctx, static inline int returnable_frag( fd_gossip_tile_ctx_t * ctx, ulong in_idx, - ulong seq, + ulong seq FD_PARAM_UNUSED, ulong sig, ulong chunk, ulong sz, @@ -488,13 +464,9 @@ returnable_frag( fd_gossip_tile_ctx_t * ctx, 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 %d corrupt, not in range [%lu,%lu]", chunk, sz, ctx->in[ in_idx ].kind, ctx->in[ in_idx ].chunk0, ctx->in[ in_idx ].wmark )); - if( FD_UNLIKELY( ctx->in[ in_idx ].kind==IN_KIND_TXSEND ) ) ctx->txsend_in_seq = seq+1UL; - switch( ctx->in[ in_idx ].kind ) { case IN_KIND_SHRED_VERSION: handle_shred_version( ctx, sig ); break; - case IN_KIND_TXSEND: handle_local_vote( ctx, fd_chunk_to_laddr_const( ctx->in[ in_idx ].mem, chunk ) ); break; case IN_KIND_EPOCH: handle_epoch( ctx, fd_chunk_to_laddr_const( ctx->in[ in_idx ].mem, chunk ) ); break; - case IN_KIND_TOWER: handle_local_duplicate_shred( ctx, sig, fd_chunk_to_laddr_const( ctx->in[ in_idx ].mem, chunk ) ); break; case IN_KIND_SNAPIN_MANIF: { if( FD_LIKELY( ctx->wfs_state==FD_GOSSIP_WFS_STATE_DONE ) ) break; @@ -661,12 +633,8 @@ unprivileged_init( fd_topo_t const * topo, } else if( FD_UNLIKELY( !strcmp( link->name, "sign_gossip" ) ) ) { ctx->in[ i ].kind = IN_KIND_SIGN; sign_in_tile_idx = i; - } else if( FD_UNLIKELY( !strcmp( link->name, "txsend_out" ) ) ) { - ctx->in[ i ].kind = IN_KIND_TXSEND; } else if( FD_UNLIKELY( !strcmp( link->name, "replay_epoch" ) ) ) { ctx->in[ i ].kind = IN_KIND_EPOCH; - } else if( FD_UNLIKELY( !strcmp( link->name, "tower_out" ) ) ) { - ctx->in[ i ].kind = IN_KIND_TOWER; } else if( FD_UNLIKELY( !strcmp( link->name, "snapin_manif" ) ) ) { ctx->in[ i ].kind = IN_KIND_SNAPIN_MANIF; } else { diff --git a/src/discof/gossip/fd_gossip_tile.h b/src/discof/gossip/fd_gossip_tile.h index e7be343aa78..28ffb1a280b 100644 --- a/src/discof/gossip/fd_gossip_tile.h +++ b/src/discof/gossip/fd_gossip_tile.h @@ -62,7 +62,6 @@ struct fd_gossip_tile_ctx { fd_keyswitch_t * keyswitch; int is_halting_signing; int is_pending_set_identity; - ulong txsend_in_seq; ushort net_id; fd_ip4_udp_hdrs_t net_out_hdr[ 1 ]; diff --git a/src/discof/gossip/test_gossip_tile.c b/src/discof/gossip/test_gossip_tile.c deleted file mode 100644 index 38ec03b8ee6..00000000000 --- a/src/discof/gossip/test_gossip_tile.c +++ /dev/null @@ -1,161 +0,0 @@ -#include "fd_gossip_tile.c" - -#include - -static fd_gossip_tile_ctx_t ctx; -static fd_keyswitch_t keyswitch[1]; -static ulong sign_cnt; - -static uchar txsend_mem[ FD_TPU_RAW_MTU ] __attribute__((aligned(FD_CHUNK_ALIGN))); - -static void -test_sign_fn( void * sign_ctx FD_PARAM_UNUSED, - uchar const * data FD_PARAM_UNUSED, - ulong sz FD_PARAM_UNUSED, - int sign_type FD_PARAM_UNUSED ) { - sign_cnt++; -} - -/* txsend_vote delivers the TxSend vote frag with sequence seq. The - vote is paid for by our identity, which follows the signature and - the 4 byte message header. */ - -static void -txsend_vote( ulong seq ) { - fd_txn_m_t * txn_m = (fd_txn_m_t *)txsend_mem; - uchar * txn = fd_txn_m_payload( txn_m ); - memset( txn, 0, 101UL ); - txn[ 0 ] = 1; - memcpy( txn+69UL, ctx.identity_key, sizeof(fd_pubkey_t) ); - txn_m->payload_sz = 101; - FD_TEST( !returnable_frag( &ctx, 0UL, seq, 1UL, 0UL, fd_txn_m_realized_footprint( txn_m, 0, 0 ), 0UL, 0UL, 0UL, NULL ) ); -} - -/* Votes TxSend published before set-identity are signed by the old - identity, so gossip must push them before it halts signing. */ - -static void -test_switch_drains_txsend( void ) { - fd_clock_tile_init( ctx.clock ); - memset( ctx.identity_key, 0x11, sizeof(fd_pubkey_t) ); - - ulong max_values = 256UL; - void * mem = aligned_alloc( fd_gossip_align(), fd_gossip_footprint( max_values, 1UL ) ); - FD_TEST( mem ); - - uchar ping_seed[ 32 ] = {0}; - fd_ip4_port_t entrypoint[1] = {0}; - ctx.gossip = fd_gossip_join( fd_gossip_new( mem, fd_rng_join( fd_rng_new( ctx.rng, 0U, 0UL ) ), ping_seed, max_values, 1UL, entrypoint, - ctx.identity_key->uc, ctx.my_contact_info, fd_clock_tile_now( ctx.clock ), - gossip_send_fn, &ctx, - test_sign_fn, NULL, - gossip_ping_tracker_change_fn, &ctx, - gossip_activity_update_fn, &ctx, - ctx.update_out, ctx.net_out ) ); - FD_TEST( ctx.gossip ); - - ctx.in[ 0 ] = (fd_gossip_in_ctx_t){ .kind = IN_KIND_TXSEND, .mem = (fd_wksp_t *)txsend_mem, .mtu = FD_TPU_RAW_MTU }; - ctx.keyswitch = fd_keyswitch_join( fd_keyswitch_new( keyswitch, FD_KEYSWITCH_STATE_UNLOCKED ) ); - FD_TEST( ctx.keyswitch ); - - /* TxSend published two votes before it switched */ - keyswitch->param = 2UL; - fd_keyswitch_state( keyswitch, FD_KEYSWITCH_STATE_SWITCH_PENDING ); - - during_housekeeping( &ctx ); - FD_TEST( !ctx.is_halting_signing ); - - txsend_vote( 0UL ); - during_housekeeping( &ctx ); - FD_TEST( !ctx.is_halting_signing ); - - txsend_vote( 1UL ); - during_housekeeping( &ctx ); - FD_TEST( ctx.is_halting_signing && sign_cnt==2UL ); - - /* Halting completes once both votes are signed */ - FD_TEST( keyswitch->state==FD_KEYSWITCH_STATE_SWITCH_PENDING ); - - free( mem ); -} - - -static uchar * manifest_mem; - -/* Drives snapin_manif: the manifest carries the boot slot, the - FD_SSMSG_DONE that follows classifies it (see fd_wfs.h). */ - -static void -wfs_manifest( ulong slot ) { - fd_snapshot_manifest_t * manifest = (fd_snapshot_manifest_t *)manifest_mem; - manifest->slot = slot; - manifest->vote_accounts_len = 0UL; - FD_TEST( !returnable_frag( &ctx, 1UL, 0UL, fd_ssmsg_sig( FD_SSMSG_MANIFEST_FULL ), 0UL, - sizeof(fd_snapshot_manifest_t), 0UL, 0UL, 0UL, NULL ) ); -} - -static void -wfs_done( void ) { - FD_TEST( !returnable_frag( &ctx, 1UL, 0UL, fd_ssmsg_sig( FD_SSMSG_DONE ), 0UL, 0UL, 0UL, 0UL, 0UL, NULL ) ); -} - -/* Boots with the given WFS config, feeds a manifest at boot_slot and - the DONE after it, and returns the state gossip settles in. */ - -static int -wfs_state_for( ulong wfs_slot, - int hash_is_zero, - ushort shred_version, - ulong boot_slot ) { - ctx.wfs_slot = wfs_slot; - ctx.wfs_hash_is_zero = hash_is_zero; - ctx.wfs_shred_version = shred_version; - ctx.wfs_boot_slot = ULONG_MAX; - ctx.wfs_state = fd_int_if( fd_wfs_configured( wfs_slot, hash_is_zero, (ulong)shred_version ), - FD_GOSSIP_WFS_STATE_INIT, FD_GOSSIP_WFS_STATE_DONE ); - ctx.in[ 1 ] = (fd_gossip_in_ctx_t){ .kind = IN_KIND_SNAPIN_MANIF, - .mem = (fd_wksp_t *)manifest_mem, - .mtu = sizeof(fd_snapshot_manifest_t) }; - wfs_manifest( boot_slot ); - wfs_done(); - return ctx.wfs_state; -} - -/* Gossip is the only tile that signals the wait is over, so a - misclassification here leaves replay waiting forever. */ - -static void -test_wfs_state_transitions( void ) { - static ulong metrics[ FD_METRICS_TOTAL_SZ/sizeof(ulong) ]; - fd_metrics_tl = metrics; - - /* aligned_alloc requires a size that is a multiple of the alignment. */ - manifest_mem = aligned_alloc( FD_CHUNK_ALIGN, fd_ulong_align_up( sizeof(fd_snapshot_manifest_t), FD_CHUNK_ALIGN ) ); - FD_TEST( manifest_mem ); - - /* MATCH waits for the supermajority. */ - FD_TEST( wfs_state_for( 100UL, 0, 1234, 100UL )==FD_GOSSIP_WFS_STATE_WAIT ); - - /* NOOP and DISABLED are finished at boot and never signal. */ - FD_TEST( wfs_state_for( 100UL, 0, 1234, 101UL )==FD_GOSSIP_WFS_STATE_DONE ); - FD_TEST( wfs_state_for( 0UL, 1, 0, 100UL )==FD_GOSSIP_WFS_STATE_DONE ); - - /* ERROR holds at INIT. Only WAIT can become PUBLISH, so completion - is unreachable without ever testing after_credit. */ - FD_TEST( wfs_state_for( 100UL, 0, 1234, 99UL )==FD_GOSSIP_WFS_STATE_INIT ); - - free( manifest_mem ); -} - -int -main( int argc, - char ** argv ) { - fd_boot( &argc, &argv ); - - test_switch_drains_txsend(); - test_wfs_state_transitions(); - - FD_LOG_NOTICE(( "pass" )); - fd_halt(); - return 0; -} diff --git a/src/discof/rpc/fd_rpc_tile.c b/src/discof/rpc/fd_rpc_tile.c index da56cbafad1..9e661b85653 100644 --- a/src/discof/rpc/fd_rpc_tile.c +++ b/src/discof/rpc/fd_rpc_tile.c @@ -1,5 +1,4 @@ #include "../replay/fd_replay_tile.h" -#include "../tower/fd_tower_tile.h" #include "../genesis/fd_genesi_tile.h" #include "../votor/fd_votor_tile.h" #include "../../choreo/votor/ag_cert.h" @@ -56,7 +55,6 @@ #define IN_KIND_REPLAY (0) #define IN_KIND_GENESI (1) #define IN_KIND_GOSSIP_OUT (2) -#define IN_KIND_TOWER (3) #define IN_KIND_EPOCH (5) #define IN_KIND_VOTOR (6) @@ -1047,14 +1045,6 @@ returnable_frag( fd_rpc_tile_t * ctx, } default: break; } - } else if( ctx->in_kind[ in_idx ]==IN_KIND_TOWER ) { - if( FD_LIKELY( sig==FD_TOWER_SIG_SLOT_CONFIRMED ) ) { - fd_tower_slot_confirmed_t const * msg = fd_chunk_to_laddr_const( ctx->in[ in_idx ].mem, chunk ); - if( FD_LIKELY( msg->level==FD_TOWER_SLOT_CONFIRMED_OPTIMISTIC ) ) { - if( FD_LIKELY( ctx->cluster_confirmed_slot==ULONG_MAX ) ) ctx->cluster_confirmed_slot = msg->slot; - else ctx->cluster_confirmed_slot = fd_ulong_max( ctx->cluster_confirmed_slot, msg->slot ); - } - } } else if( ctx->in_kind[ in_idx ]==IN_KIND_EPOCH ) { fd_epoch_info_msg_t const * msg = fd_chunk_to_laddr_const( ctx->in[ in_idx ].mem, chunk ); FD_TEST( msg->staked_vote_cnt<=MAX_STAKE_WEIGHTS ); @@ -3263,7 +3253,6 @@ unprivileged_init( fd_topo_t const * topo, else if( FD_LIKELY( !strcmp( link->name, "genesi_out" ) ) ) ctx->in_kind[ i ] = IN_KIND_GENESI; else if( FD_LIKELY( !strcmp( link->name, "gossip_ciaddr" ) ) ) ctx->in_kind[ i ] = IN_KIND_GOSSIP_OUT; else if( FD_LIKELY( !strcmp( link->name, "gossip_vote" ) ) ) ctx->in_kind[ i ] = IN_KIND_GOSSIP_OUT; - else if( FD_LIKELY( !strcmp( link->name, "tower_out" ) ) ) ctx->in_kind[ i ] = IN_KIND_TOWER; else if( FD_LIKELY( !strcmp( link->name, "replay_epoch" ) ) ) ctx->in_kind[ i ] = IN_KIND_EPOCH; else if( FD_UNLIKELY( !strcmp( link->name, "votor_out" ) ) ) ctx->in_kind[ i ] = IN_KIND_VOTOR; else FD_LOG_ERR(( "unexpected link name %s", link->name )); From 80374fd699901f9e5184267ac1bb31b9768423d6 Mon Sep 17 00:00:00 2001 From: Richard Patel Date: Wed, 21 Oct 2026 13:40:24 +0000 Subject: [PATCH 06/13] remove txsend tile --- book/api/metrics-generated.md | 90 -- src/disco/metrics/generated/fd_metrics_all.c | 87 -- src/disco/metrics/generated/fd_metrics_all.h | 3 +- .../metrics/generated/fd_metrics_sample_gen.c | 2 +- .../metrics/generated/fd_metrics_txsend.h | 263 ----- src/disco/metrics/metrics.xml | 53 - src/disco/topo/fd_topo.h | 9 - src/discof/txsend/Local.mk | 3 - src/discof/txsend/fd_txsend_tile.c | 936 ------------------ src/discof/txsend/fd_txsend_tile.h | 159 --- .../txsend/fd_txsend_tile.seccomppolicy | 43 - .../txsend/generated/fd_txsend_tile_seccomp.h | 110 -- 12 files changed, 2 insertions(+), 1756 deletions(-) delete mode 100644 src/disco/metrics/generated/fd_metrics_txsend.h delete mode 100644 src/discof/txsend/Local.mk delete mode 100644 src/discof/txsend/fd_txsend_tile.c delete mode 100644 src/discof/txsend/fd_txsend_tile.h delete mode 100644 src/discof/txsend/fd_txsend_tile.seccomppolicy delete mode 100644 src/discof/txsend/generated/fd_txsend_tile_seccomp.h diff --git a/book/api/metrics-generated.md b/book/api/metrics-generated.md index eefe3e14dda..6fdc3e71a7a 100644 --- a/book/api/metrics-generated.md +++ b/book/api/metrics-generated.md @@ -1588,96 +1588,6 @@ -## Txsend Tile - -
- -| Metric | Type | Description | -|--------|------|-------------| -| txsend_​pkt_​rx | counter | UDP datagrams received (one may carry several coalesced QUIC packets) | -| txsend_​pkt_​rx_​bytes | counter | Bytes of UDP datagrams received, including Ethernet, IPv4 and UDP headers | -| txsend_​pkt_​tx | counter | UDP datagrams sent | -| txsend_​pkt_​tx_​bytes | counter | Bytes of UDP datagrams sent, including Ethernet, IPv4 and UDP headers | -| txsend_​conn_​used
{quic_​conn_​state="handshake"} | gauge | Connection slots in use, by state; sum + ConnFree = ConnCapacity (Handshaking peer) | -| txsend_​conn_​used
{quic_​conn_​state="handshake_​complete"} | gauge | Connection slots in use, by state; sum + ConnFree = ConnCapacity (Handshake complete, confirming with peer) | -| txsend_​conn_​used
{quic_​conn_​state="active"} | gauge | Connection slots in use, by state; sum + ConnFree = ConnCapacity (Active connection) | -| txsend_​conn_​used
{quic_​conn_​state="peer_​close"} | gauge | Connection slots in use, by state; sum + ConnFree = ConnCapacity (Peer requested close) | -| txsend_​conn_​used
{quic_​conn_​state="abort"} | gauge | Connection slots in use, by state; sum + ConnFree = ConnCapacity (Connection terminating due to error) | -| txsend_​conn_​used
{quic_​conn_​state="close_​pending"} | gauge | Connection slots in use, by state; sum + ConnFree = ConnCapacity (Connection is closing) | -| txsend_​conn_​used
{quic_​conn_​state="dead"} | gauge | Connection slots in use, by state; sum + ConnFree = ConnCapacity (Connection about to be freed) | -| txsend_​conn_​free | gauge | Connection slots free | -| txsend_​conn_​capacity | gauge | Connection slots | -| txsend_​conn_​created | counter | Connections created | -| txsend_​conn_​closed | counter | Connections gracefully closed | -| txsend_​conn_​aborted | counter | Connections aborted | -| txsend_​conn_​timed_​out | counter | Connections timed out | -| txsend_​conn_​no_​slot_​rejected | counter | Connections that failed to create due to lack of slots | -| txsend_​conn_​retry_​rejected | counter | Retries rejected: an invalid or expired token as server, a late or invalid Retry packet as client | -| txsend_​pkt_​decrypt_​failed
{quic_​encryption_​level="initial"} | counter | Packets that failed decryption, by encryption level (Initial) | -| txsend_​pkt_​decrypt_​failed
{quic_​encryption_​level="early"} | counter | Packets that failed decryption, by encryption level (Early data) | -| txsend_​pkt_​decrypt_​failed
{quic_​encryption_​level="handshake"} | counter | Packets that failed decryption, by encryption level (Handshake) | -| txsend_​pkt_​decrypt_​failed
{quic_​encryption_​level="app"} | counter | Packets that failed decryption, by encryption level (App data) | -| txsend_​pkt_​no_​key_​dropped
{quic_​encryption_​level="initial"} | counter | Packets dropped because the decryption key was missing, by encryption level (Initial) | -| txsend_​pkt_​no_​key_​dropped
{quic_​encryption_​level="early"} | counter | Packets dropped because the decryption key was missing, by encryption level (Early data) | -| txsend_​pkt_​no_​key_​dropped
{quic_​encryption_​level="handshake"} | counter | Packets dropped because the decryption key was missing, by encryption level (Handshake) | -| txsend_​pkt_​no_​key_​dropped
{quic_​encryption_​level="app"} | counter | Packets dropped because the decryption key was missing, by encryption level (App data) | -| txsend_​pkt_​dropped
{quic_​pkt_​drop_​reason="undersize"} | counter | Received packets dropped, by reason (QUIC packet too small) | -| txsend_​pkt_​dropped
{quic_​pkt_​drop_​reason="oversize"} | counter | Received packets dropped, by reason (QUIC packet too large) | -| txsend_​pkt_​dropped
{quic_​pkt_​drop_​reason="src_​ip_​mismatch"} | counter | Received packets dropped, by reason (Wrong source IP) | -| txsend_​pkt_​dropped
{quic_​pkt_​drop_​reason="header_​invalid"} | counter | Received packets dropped, by reason (Invalid QUIC header) | -| txsend_​pkt_​dropped
{quic_​pkt_​drop_​reason="net_​header_​invalid"} | counter | Received packets dropped, by reason (Invalid IPv4 or UDP header) | -| txsend_​pkt_​dropped
{quic_​pkt_​drop_​reason="version_​unsupported"} | counter | Received packets dropped, by reason (Version negotiation or unsupported version) | -| txsend_​pkt_​dropped
{quic_​pkt_​drop_​reason="duplicate"} | counter | Received packets dropped, by reason (Packet number already received or too old) | -| txsend_​pkt_​no_​conn_​dropped
{quic_​pkt_​type="initial"} | counter | Packets with an unknown connection ID, by packet type (Initial) | -| txsend_​pkt_​no_​conn_​dropped
{quic_​pkt_​type="retry"} | counter | Packets with an unknown connection ID, by packet type (Retry) | -| txsend_​pkt_​no_​conn_​dropped
{quic_​pkt_​type="handshake"} | counter | Packets with an unknown connection ID, by packet type (Handshake) | -| txsend_​pkt_​no_​conn_​dropped
{quic_​pkt_​type="one_​rtt"} | counter | Packets with an unknown connection ID, by packet type (1-RTT) | -| txsend_​pkt_​retransmitted
{quic_​encryption_​level="initial"} | counter | QUIC packets retransmitted, by encryption level (Initial) | -| txsend_​pkt_​retransmitted
{quic_​encryption_​level="early"} | counter | QUIC packets retransmitted, by encryption level (Early data) | -| txsend_​pkt_​retransmitted
{quic_​encryption_​level="handshake"} | counter | QUIC packets retransmitted, by encryption level (Handshake) | -| txsend_​pkt_​retransmitted
{quic_​encryption_​level="app"} | counter | QUIC packets retransmitted, by encryption level (App data) | -| txsend_​handshake_​created | counter | Handshake flows created | -| txsend_​handshake_​pool_​full_​rejected | counter | Handshakes dropped due to alloc fail | -| txsend_​handshake_​evicted | counter | Handshakes dropped due to eviction | -| txsend_​stream_​rx | counter | Stream receive events | -| txsend_​stream_​rx_​bytes | counter | Stream payload bytes received | -| txsend_​frame_​rx
{quic_​frame_​type="unknown"} | counter | QUIC frames received, by frame type (Unknown frame type) | -| txsend_​frame_​rx
{quic_​frame_​type="ack"} | counter | QUIC frames received, by frame type (ACK frame) | -| txsend_​frame_​rx
{quic_​frame_​type="reset_​stream"} | counter | QUIC frames received, by frame type (RESET_STREAM frame) | -| txsend_​frame_​rx
{quic_​frame_​type="stop_​sending"} | counter | QUIC frames received, by frame type (STOP_SENDING frame) | -| txsend_​frame_​rx
{quic_​frame_​type="crypto"} | counter | QUIC frames received, by frame type (CRYPTO frame) | -| txsend_​frame_​rx
{quic_​frame_​type="new_​token"} | counter | QUIC frames received, by frame type (NEW_TOKEN frame) | -| txsend_​frame_​rx
{quic_​frame_​type="stream"} | counter | QUIC frames received, by frame type (STREAM frame) | -| txsend_​frame_​rx
{quic_​frame_​type="max_​data"} | counter | QUIC frames received, by frame type (MAX_DATA frame) | -| txsend_​frame_​rx
{quic_​frame_​type="max_​stream_​data"} | counter | QUIC frames received, by frame type (MAX_STREAM_DATA frame) | -| txsend_​frame_​rx
{quic_​frame_​type="max_​streams"} | counter | QUIC frames received, by frame type (MAX_STREAMS frame) | -| txsend_​frame_​rx
{quic_​frame_​type="data_​blocked"} | counter | QUIC frames received, by frame type (DATA_BLOCKED frame) | -| txsend_​frame_​rx
{quic_​frame_​type="stream_​data_​blocked"} | counter | QUIC frames received, by frame type (STREAM_DATA_BLOCKED frame) | -| txsend_​frame_​rx
{quic_​frame_​type="streams_​blocked"} | counter | QUIC frames received, by frame type (STREAMS_BLOCKED(bidi) frame) | -| txsend_​frame_​rx
{quic_​frame_​type="new_​conn_​id"} | counter | QUIC frames received, by frame type (NEW_CONN_ID frame) | -| txsend_​frame_​rx
{quic_​frame_​type="retire_​conn_​id"} | counter | QUIC frames received, by frame type (RETIRE_CONN_ID frame) | -| txsend_​frame_​rx
{quic_​frame_​type="path_​challenge"} | counter | QUIC frames received, by frame type (PATH_CHALLENGE frame) | -| txsend_​frame_​rx
{quic_​frame_​type="path_​response"} | counter | QUIC frames received, by frame type (PATH_RESPONSE frame) | -| txsend_​frame_​rx
{quic_​frame_​type="conn_​close_​quic"} | counter | QUIC frames received, by frame type (CONN_CLOSE(transport) frame) | -| txsend_​frame_​rx
{quic_​frame_​type="conn_​close_​app"} | counter | QUIC frames received, by frame type (CONN_CLOSE(app) frame) | -| txsend_​frame_​rx
{quic_​frame_​type="handshake_​done"} | counter | QUIC frames received, by frame type (HANDSHAKE_DONE frame) | -| txsend_​frame_​rx
{quic_​frame_​type="ping"} | counter | QUIC frames received, by frame type (PING frame) | -| txsend_​frame_​rx
{quic_​frame_​type="padding"} | counter | QUIC frames received, by frame type (PADDING frame) | -| txsend_​frame_​rx
{quic_​frame_​type="datagram"} | counter | QUIC frames received, by frame type (DATAGRAM frame) | -| txsend_​frame_​parse_​failed | counter | QUIC frames that failed to parse | -| txsend_​pkt_​meta_​acquire_​attempted
{pkt_​meta_​acquire_​result="success"} | counter | Attempts to acquire packet metadata for transmission, by result (Success) | -| txsend_​pkt_​meta_​acquire_​attempted
{pkt_​meta_​acquire_​result="pool_​empty"} | counter | Attempts to acquire packet metadata for transmission, by result (Packet metadata pool empty) | -| txsend_​pkt_​meta_​acquire_​attempted
{pkt_​meta_​acquire_​result="conn_​limit"} | counter | Attempts to acquire packet metadata for transmission, by result (Connection reached its in-flight frame limit) | -| txsend_​pkt_​ack_​processed
{ack_​result="no_​pkt_​number"} | counter | Received packets processed for acknowledgement, by result (Packet had no packet number to acknowledge) | -| txsend_​pkt_​ack_​processed
{ack_​result="new_​range"} | counter | Received packets processed for acknowledgement, by result (New ACK range) | -| txsend_​pkt_​ack_​processed
{ack_​result="merged"} | counter | Received packets processed for acknowledgement, by result (Merged into existing ACK range) | -| txsend_​pkt_​ack_​processed
{ack_​result="queue_​full"} | counter | Received packets processed for acknowledgement, by result (Out of buffers) | -| txsend_​pkt_​ack_​processed
{ack_​result="cancelled"} | counter | Received packets processed for acknowledgement, by result (ACK suppressed by handler) | -| txsend_​service_​duration_​nanos | histogram | Duration of one fd_quic_service call that processed a due connection timer | -| txsend_​pkt_​rx_​duration_​nanos | histogram | Duration of processing one received packet | - -
- ## Diag Tile
diff --git a/src/disco/metrics/generated/fd_metrics_all.c b/src/disco/metrics/generated/fd_metrics_all.c index 249d965da59..60f9d872459 100644 --- a/src/disco/metrics/generated/fd_metrics_all.c +++ b/src/disco/metrics/generated/fd_metrics_all.c @@ -1400,90 +1400,6 @@ const fd_metrics_meta_t FD_METRICS_TOWER[FD_METRICS_TOWER_TOTAL] = { DECLARE_METRIC( TOWER_ACCDB_COPIED_BYTES, COUNTER ), }; -const fd_metrics_meta_t FD_METRICS_TXSEND[FD_METRICS_TXSEND_TOTAL] = { - DECLARE_METRIC( TXSEND_PKT_RX, COUNTER ), - DECLARE_METRIC( TXSEND_PKT_RX_BYTES, COUNTER ), - DECLARE_METRIC( TXSEND_PKT_TX, COUNTER ), - DECLARE_METRIC( TXSEND_PKT_TX_BYTES, COUNTER ), - DECLARE_METRIC_ENUM( TXSEND_CONN_USED, GAUGE, QUIC_CONN_STATE, HANDSHAKE ), - DECLARE_METRIC_ENUM( TXSEND_CONN_USED, GAUGE, QUIC_CONN_STATE, HANDSHAKE_COMPLETE ), - DECLARE_METRIC_ENUM( TXSEND_CONN_USED, GAUGE, QUIC_CONN_STATE, ACTIVE ), - DECLARE_METRIC_ENUM( TXSEND_CONN_USED, GAUGE, QUIC_CONN_STATE, PEER_CLOSE ), - DECLARE_METRIC_ENUM( TXSEND_CONN_USED, GAUGE, QUIC_CONN_STATE, ABORT ), - DECLARE_METRIC_ENUM( TXSEND_CONN_USED, GAUGE, QUIC_CONN_STATE, CLOSE_PENDING ), - DECLARE_METRIC_ENUM( TXSEND_CONN_USED, GAUGE, QUIC_CONN_STATE, DEAD ), - DECLARE_METRIC( TXSEND_CONN_FREE, GAUGE ), - DECLARE_METRIC( TXSEND_CONN_CAPACITY, GAUGE ), - DECLARE_METRIC( TXSEND_CONN_CREATED, COUNTER ), - DECLARE_METRIC( TXSEND_CONN_CLOSED, COUNTER ), - DECLARE_METRIC( TXSEND_CONN_ABORTED, COUNTER ), - DECLARE_METRIC( TXSEND_CONN_TIMED_OUT, COUNTER ), - DECLARE_METRIC( TXSEND_CONN_NO_SLOT_REJECTED, COUNTER ), - DECLARE_METRIC( TXSEND_CONN_RETRY_REJECTED, COUNTER ), - DECLARE_METRIC_ENUM( TXSEND_PKT_DECRYPT_FAILED, COUNTER, QUIC_ENCRYPTION_LEVEL, INITIAL ), - DECLARE_METRIC_ENUM( TXSEND_PKT_DECRYPT_FAILED, COUNTER, QUIC_ENCRYPTION_LEVEL, EARLY ), - DECLARE_METRIC_ENUM( TXSEND_PKT_DECRYPT_FAILED, COUNTER, QUIC_ENCRYPTION_LEVEL, HANDSHAKE ), - DECLARE_METRIC_ENUM( TXSEND_PKT_DECRYPT_FAILED, COUNTER, QUIC_ENCRYPTION_LEVEL, APP ), - DECLARE_METRIC_ENUM( TXSEND_PKT_NO_KEY_DROPPED, COUNTER, QUIC_ENCRYPTION_LEVEL, INITIAL ), - DECLARE_METRIC_ENUM( TXSEND_PKT_NO_KEY_DROPPED, COUNTER, QUIC_ENCRYPTION_LEVEL, EARLY ), - DECLARE_METRIC_ENUM( TXSEND_PKT_NO_KEY_DROPPED, COUNTER, QUIC_ENCRYPTION_LEVEL, HANDSHAKE ), - DECLARE_METRIC_ENUM( TXSEND_PKT_NO_KEY_DROPPED, COUNTER, QUIC_ENCRYPTION_LEVEL, APP ), - DECLARE_METRIC_ENUM( TXSEND_PKT_DROPPED, COUNTER, QUIC_PKT_DROP_REASON, UNDERSIZE ), - DECLARE_METRIC_ENUM( TXSEND_PKT_DROPPED, COUNTER, QUIC_PKT_DROP_REASON, OVERSIZE ), - DECLARE_METRIC_ENUM( TXSEND_PKT_DROPPED, COUNTER, QUIC_PKT_DROP_REASON, SRC_IP_MISMATCH ), - DECLARE_METRIC_ENUM( TXSEND_PKT_DROPPED, COUNTER, QUIC_PKT_DROP_REASON, HEADER_INVALID ), - DECLARE_METRIC_ENUM( TXSEND_PKT_DROPPED, COUNTER, QUIC_PKT_DROP_REASON, NET_HEADER_INVALID ), - DECLARE_METRIC_ENUM( TXSEND_PKT_DROPPED, COUNTER, QUIC_PKT_DROP_REASON, VERSION_UNSUPPORTED ), - DECLARE_METRIC_ENUM( TXSEND_PKT_DROPPED, COUNTER, QUIC_PKT_DROP_REASON, DUPLICATE ), - DECLARE_METRIC_ENUM( TXSEND_PKT_NO_CONN_DROPPED, COUNTER, QUIC_PKT_TYPE, INITIAL ), - DECLARE_METRIC_ENUM( TXSEND_PKT_NO_CONN_DROPPED, COUNTER, QUIC_PKT_TYPE, RETRY ), - DECLARE_METRIC_ENUM( TXSEND_PKT_NO_CONN_DROPPED, COUNTER, QUIC_PKT_TYPE, HANDSHAKE ), - DECLARE_METRIC_ENUM( TXSEND_PKT_NO_CONN_DROPPED, COUNTER, QUIC_PKT_TYPE, ONE_RTT ), - DECLARE_METRIC_ENUM( TXSEND_PKT_RETRANSMITTED, COUNTER, QUIC_ENCRYPTION_LEVEL, INITIAL ), - DECLARE_METRIC_ENUM( TXSEND_PKT_RETRANSMITTED, COUNTER, QUIC_ENCRYPTION_LEVEL, EARLY ), - DECLARE_METRIC_ENUM( TXSEND_PKT_RETRANSMITTED, COUNTER, QUIC_ENCRYPTION_LEVEL, HANDSHAKE ), - DECLARE_METRIC_ENUM( TXSEND_PKT_RETRANSMITTED, COUNTER, QUIC_ENCRYPTION_LEVEL, APP ), - DECLARE_METRIC( TXSEND_HANDSHAKE_CREATED, COUNTER ), - DECLARE_METRIC( TXSEND_HANDSHAKE_POOL_FULL_REJECTED, COUNTER ), - DECLARE_METRIC( TXSEND_HANDSHAKE_EVICTED, COUNTER ), - DECLARE_METRIC( TXSEND_STREAM_RX, COUNTER ), - DECLARE_METRIC( TXSEND_STREAM_RX_BYTES, COUNTER ), - DECLARE_METRIC_ENUM( TXSEND_FRAME_RX, COUNTER, QUIC_FRAME_TYPE, UNKNOWN ), - DECLARE_METRIC_ENUM( TXSEND_FRAME_RX, COUNTER, QUIC_FRAME_TYPE, ACK ), - DECLARE_METRIC_ENUM( TXSEND_FRAME_RX, COUNTER, QUIC_FRAME_TYPE, RESET_STREAM ), - DECLARE_METRIC_ENUM( TXSEND_FRAME_RX, COUNTER, QUIC_FRAME_TYPE, STOP_SENDING ), - DECLARE_METRIC_ENUM( TXSEND_FRAME_RX, COUNTER, QUIC_FRAME_TYPE, CRYPTO ), - DECLARE_METRIC_ENUM( TXSEND_FRAME_RX, COUNTER, QUIC_FRAME_TYPE, NEW_TOKEN ), - DECLARE_METRIC_ENUM( TXSEND_FRAME_RX, COUNTER, QUIC_FRAME_TYPE, STREAM ), - DECLARE_METRIC_ENUM( TXSEND_FRAME_RX, COUNTER, QUIC_FRAME_TYPE, MAX_DATA ), - DECLARE_METRIC_ENUM( TXSEND_FRAME_RX, COUNTER, QUIC_FRAME_TYPE, MAX_STREAM_DATA ), - DECLARE_METRIC_ENUM( TXSEND_FRAME_RX, COUNTER, QUIC_FRAME_TYPE, MAX_STREAMS ), - DECLARE_METRIC_ENUM( TXSEND_FRAME_RX, COUNTER, QUIC_FRAME_TYPE, DATA_BLOCKED ), - DECLARE_METRIC_ENUM( TXSEND_FRAME_RX, COUNTER, QUIC_FRAME_TYPE, STREAM_DATA_BLOCKED ), - DECLARE_METRIC_ENUM( TXSEND_FRAME_RX, COUNTER, QUIC_FRAME_TYPE, STREAMS_BLOCKED ), - DECLARE_METRIC_ENUM( TXSEND_FRAME_RX, COUNTER, QUIC_FRAME_TYPE, NEW_CONN_ID ), - DECLARE_METRIC_ENUM( TXSEND_FRAME_RX, COUNTER, QUIC_FRAME_TYPE, RETIRE_CONN_ID ), - DECLARE_METRIC_ENUM( TXSEND_FRAME_RX, COUNTER, QUIC_FRAME_TYPE, PATH_CHALLENGE ), - DECLARE_METRIC_ENUM( TXSEND_FRAME_RX, COUNTER, QUIC_FRAME_TYPE, PATH_RESPONSE ), - DECLARE_METRIC_ENUM( TXSEND_FRAME_RX, COUNTER, QUIC_FRAME_TYPE, CONN_CLOSE_QUIC ), - DECLARE_METRIC_ENUM( TXSEND_FRAME_RX, COUNTER, QUIC_FRAME_TYPE, CONN_CLOSE_APP ), - DECLARE_METRIC_ENUM( TXSEND_FRAME_RX, COUNTER, QUIC_FRAME_TYPE, HANDSHAKE_DONE ), - DECLARE_METRIC_ENUM( TXSEND_FRAME_RX, COUNTER, QUIC_FRAME_TYPE, PING ), - DECLARE_METRIC_ENUM( TXSEND_FRAME_RX, COUNTER, QUIC_FRAME_TYPE, PADDING ), - DECLARE_METRIC_ENUM( TXSEND_FRAME_RX, COUNTER, QUIC_FRAME_TYPE, DATAGRAM ), - DECLARE_METRIC( TXSEND_FRAME_PARSE_FAILED, COUNTER ), - DECLARE_METRIC_ENUM( TXSEND_PKT_META_ACQUIRE_ATTEMPTED, COUNTER, PKT_META_ACQUIRE_RESULT, SUCCESS ), - DECLARE_METRIC_ENUM( TXSEND_PKT_META_ACQUIRE_ATTEMPTED, COUNTER, PKT_META_ACQUIRE_RESULT, POOL_EMPTY ), - DECLARE_METRIC_ENUM( TXSEND_PKT_META_ACQUIRE_ATTEMPTED, COUNTER, PKT_META_ACQUIRE_RESULT, CONN_LIMIT ), - DECLARE_METRIC_ENUM( TXSEND_PKT_ACK_PROCESSED, COUNTER, ACK_RESULT, NO_PKT_NUMBER ), - DECLARE_METRIC_ENUM( TXSEND_PKT_ACK_PROCESSED, COUNTER, ACK_RESULT, NEW_RANGE ), - DECLARE_METRIC_ENUM( TXSEND_PKT_ACK_PROCESSED, COUNTER, ACK_RESULT, MERGED ), - DECLARE_METRIC_ENUM( TXSEND_PKT_ACK_PROCESSED, COUNTER, ACK_RESULT, QUEUE_FULL ), - DECLARE_METRIC_ENUM( TXSEND_PKT_ACK_PROCESSED, COUNTER, ACK_RESULT, CANCELLED ), - DECLARE_METRIC_HISTOGRAM_TICKS( TXSEND_SERVICE_DURATION_NANOS ), - DECLARE_METRIC_HISTOGRAM_TICKS( TXSEND_PKT_RX_DURATION_NANOS ), -}; - const fd_metrics_meta_t FD_METRICS_DIAG[FD_METRICS_DIAG_TOTAL] = { DECLARE_METRIC( DIAG_BUNDLE_STATE, GAUGE ), DECLARE_METRIC( DIAG_VOTE_STATE, GAUGE ), @@ -2191,7 +2107,6 @@ const char * FD_METRICS_TILE_KIND_NAMES[FD_METRICS_TILE_KIND_CNT] = { "execrp", "accdb", "tower", - "txsend", "diag", "event", "gui", @@ -2241,7 +2156,6 @@ const ulong FD_METRICS_TILE_KIND_SIZES[FD_METRICS_TILE_KIND_CNT] = { FD_METRICS_EXECRP_TOTAL, FD_METRICS_ACCDB_TOTAL, FD_METRICS_TOWER_TOTAL, - FD_METRICS_TXSEND_TOTAL, FD_METRICS_DIAG_TOTAL, FD_METRICS_EVENT_TOTAL, FD_METRICS_GUI_TOTAL, @@ -2290,7 +2204,6 @@ const fd_metrics_meta_t * FD_METRICS_TILE_KIND_METRICS[FD_METRICS_TILE_KIND_CNT] FD_METRICS_EXECRP, FD_METRICS_ACCDB, FD_METRICS_TOWER, - FD_METRICS_TXSEND, FD_METRICS_DIAG, FD_METRICS_EVENT, FD_METRICS_GUI, diff --git a/src/disco/metrics/generated/fd_metrics_all.h b/src/disco/metrics/generated/fd_metrics_all.h index 71357f17809..8ab82aea273 100644 --- a/src/disco/metrics/generated/fd_metrics_all.h +++ b/src/disco/metrics/generated/fd_metrics_all.h @@ -64,7 +64,6 @@ enum { #include "fd_metrics_mlx5.h" #include "fd_metrics_sock.h" #include "fd_metrics_quic.h" -#include "fd_metrics_txsend.h" #include "fd_metrics_bundle.h" #include "fd_metrics_verify.h" #include "fd_metrics_dedup.h" @@ -254,7 +253,7 @@ extern const fd_metrics_meta_t FD_METRICS_ALL_LINK_IN[FD_METRICS_ALL_LINK_IN_TOT #define FD_METRICS_TOTAL_SZ (8UL*301UL) -#define FD_METRICS_TILE_KIND_CNT 47 +#define FD_METRICS_TILE_KIND_CNT 46 extern const char * FD_METRICS_TILE_KIND_NAMES[FD_METRICS_TILE_KIND_CNT]; extern const ulong FD_METRICS_TILE_KIND_SIZES[FD_METRICS_TILE_KIND_CNT]; extern const fd_metrics_meta_t * FD_METRICS_TILE_KIND_METRICS[FD_METRICS_TILE_KIND_CNT]; diff --git a/src/disco/metrics/generated/fd_metrics_sample_gen.c b/src/disco/metrics/generated/fd_metrics_sample_gen.c index b7a13f43154..25bfaa30df2 100644 --- a/src/disco/metrics/generated/fd_metrics_sample_gen.c +++ b/src/disco/metrics/generated/fd_metrics_sample_gen.c @@ -4411,6 +4411,6 @@ fd_metrics_sample_schema_t const fd_metrics_sample_tiles[] = { { NULL, 0U, NULL, 0UL, 0UL } }; -char const * const fd_metrics_sample_skip[] = { "backt", "benchs", "poh", "repair", "solcap", "tower", "txsend", NULL }; +char const * const fd_metrics_sample_skip[] = { "backt", "benchs", "poh", "repair", "solcap", "tower", NULL }; fd_metrics_sample_schema_t const fd_metrics_sample_link = { "link", 61U, fields_link, 9UL, 284UL }; diff --git a/src/disco/metrics/generated/fd_metrics_txsend.h b/src/disco/metrics/generated/fd_metrics_txsend.h deleted file mode 100644 index 5730131438d..00000000000 --- a/src/disco/metrics/generated/fd_metrics_txsend.h +++ /dev/null @@ -1,263 +0,0 @@ -#ifndef HEADER_fd_src_disco_metrics_generated_fd_metrics_txsend_h -#define HEADER_fd_src_disco_metrics_generated_fd_metrics_txsend_h - -/* THIS FILE IS GENERATED BY gen_metrics.py. DO NOT HAND EDIT. */ - -#include "../fd_metrics_base.h" -#include "fd_metrics_enums.h" - -enum { - FD_METRICS_COUNTER_TXSEND_PKT_RX_OFF = FD_METRICS_TILE_OFF, - FD_METRICS_COUNTER_TXSEND_PKT_RX_BYTES_OFF, - FD_METRICS_COUNTER_TXSEND_PKT_TX_OFF, - FD_METRICS_COUNTER_TXSEND_PKT_TX_BYTES_OFF, - FD_METRICS_GAUGE_TXSEND_CONN_USED_OFF, - FD_METRICS_GAUGE_TXSEND_CONN_USED_HANDSHAKE_OFF = FD_METRICS_GAUGE_TXSEND_CONN_USED_OFF, - FD_METRICS_GAUGE_TXSEND_CONN_USED_HANDSHAKE_COMPLETE_OFF, - FD_METRICS_GAUGE_TXSEND_CONN_USED_ACTIVE_OFF, - FD_METRICS_GAUGE_TXSEND_CONN_USED_PEER_CLOSE_OFF, - FD_METRICS_GAUGE_TXSEND_CONN_USED_ABORT_OFF, - FD_METRICS_GAUGE_TXSEND_CONN_USED_CLOSE_PENDING_OFF, - FD_METRICS_GAUGE_TXSEND_CONN_USED_DEAD_OFF, - FD_METRICS_GAUGE_TXSEND_CONN_FREE_OFF, - FD_METRICS_GAUGE_TXSEND_CONN_CAPACITY_OFF, - FD_METRICS_COUNTER_TXSEND_CONN_CREATED_OFF, - FD_METRICS_COUNTER_TXSEND_CONN_CLOSED_OFF, - FD_METRICS_COUNTER_TXSEND_CONN_ABORTED_OFF, - FD_METRICS_COUNTER_TXSEND_CONN_TIMED_OUT_OFF, - FD_METRICS_COUNTER_TXSEND_CONN_NO_SLOT_REJECTED_OFF, - FD_METRICS_COUNTER_TXSEND_CONN_RETRY_REJECTED_OFF, - FD_METRICS_COUNTER_TXSEND_PKT_DECRYPT_FAILED_OFF, - FD_METRICS_COUNTER_TXSEND_PKT_DECRYPT_FAILED_INITIAL_OFF = FD_METRICS_COUNTER_TXSEND_PKT_DECRYPT_FAILED_OFF, - FD_METRICS_COUNTER_TXSEND_PKT_DECRYPT_FAILED_EARLY_OFF, - FD_METRICS_COUNTER_TXSEND_PKT_DECRYPT_FAILED_HANDSHAKE_OFF, - FD_METRICS_COUNTER_TXSEND_PKT_DECRYPT_FAILED_APP_OFF, - FD_METRICS_COUNTER_TXSEND_PKT_NO_KEY_DROPPED_OFF, - FD_METRICS_COUNTER_TXSEND_PKT_NO_KEY_DROPPED_INITIAL_OFF = FD_METRICS_COUNTER_TXSEND_PKT_NO_KEY_DROPPED_OFF, - FD_METRICS_COUNTER_TXSEND_PKT_NO_KEY_DROPPED_EARLY_OFF, - FD_METRICS_COUNTER_TXSEND_PKT_NO_KEY_DROPPED_HANDSHAKE_OFF, - FD_METRICS_COUNTER_TXSEND_PKT_NO_KEY_DROPPED_APP_OFF, - FD_METRICS_COUNTER_TXSEND_PKT_DROPPED_OFF, - FD_METRICS_COUNTER_TXSEND_PKT_DROPPED_UNDERSIZE_OFF = FD_METRICS_COUNTER_TXSEND_PKT_DROPPED_OFF, - FD_METRICS_COUNTER_TXSEND_PKT_DROPPED_OVERSIZE_OFF, - FD_METRICS_COUNTER_TXSEND_PKT_DROPPED_SRC_IP_MISMATCH_OFF, - FD_METRICS_COUNTER_TXSEND_PKT_DROPPED_HEADER_INVALID_OFF, - FD_METRICS_COUNTER_TXSEND_PKT_DROPPED_NET_HEADER_INVALID_OFF, - FD_METRICS_COUNTER_TXSEND_PKT_DROPPED_VERSION_UNSUPPORTED_OFF, - FD_METRICS_COUNTER_TXSEND_PKT_DROPPED_DUPLICATE_OFF, - FD_METRICS_COUNTER_TXSEND_PKT_NO_CONN_DROPPED_OFF, - FD_METRICS_COUNTER_TXSEND_PKT_NO_CONN_DROPPED_INITIAL_OFF = FD_METRICS_COUNTER_TXSEND_PKT_NO_CONN_DROPPED_OFF, - FD_METRICS_COUNTER_TXSEND_PKT_NO_CONN_DROPPED_RETRY_OFF, - FD_METRICS_COUNTER_TXSEND_PKT_NO_CONN_DROPPED_HANDSHAKE_OFF, - FD_METRICS_COUNTER_TXSEND_PKT_NO_CONN_DROPPED_ONE_RTT_OFF, - FD_METRICS_COUNTER_TXSEND_PKT_RETRANSMITTED_OFF, - FD_METRICS_COUNTER_TXSEND_PKT_RETRANSMITTED_INITIAL_OFF = FD_METRICS_COUNTER_TXSEND_PKT_RETRANSMITTED_OFF, - FD_METRICS_COUNTER_TXSEND_PKT_RETRANSMITTED_EARLY_OFF, - FD_METRICS_COUNTER_TXSEND_PKT_RETRANSMITTED_HANDSHAKE_OFF, - FD_METRICS_COUNTER_TXSEND_PKT_RETRANSMITTED_APP_OFF, - FD_METRICS_COUNTER_TXSEND_HANDSHAKE_CREATED_OFF, - FD_METRICS_COUNTER_TXSEND_HANDSHAKE_POOL_FULL_REJECTED_OFF, - FD_METRICS_COUNTER_TXSEND_HANDSHAKE_EVICTED_OFF, - FD_METRICS_COUNTER_TXSEND_STREAM_RX_OFF, - FD_METRICS_COUNTER_TXSEND_STREAM_RX_BYTES_OFF, - FD_METRICS_COUNTER_TXSEND_FRAME_RX_OFF, - FD_METRICS_COUNTER_TXSEND_FRAME_RX_UNKNOWN_OFF = FD_METRICS_COUNTER_TXSEND_FRAME_RX_OFF, - FD_METRICS_COUNTER_TXSEND_FRAME_RX_ACK_OFF, - FD_METRICS_COUNTER_TXSEND_FRAME_RX_RESET_STREAM_OFF, - FD_METRICS_COUNTER_TXSEND_FRAME_RX_STOP_SENDING_OFF, - FD_METRICS_COUNTER_TXSEND_FRAME_RX_CRYPTO_OFF, - FD_METRICS_COUNTER_TXSEND_FRAME_RX_NEW_TOKEN_OFF, - FD_METRICS_COUNTER_TXSEND_FRAME_RX_STREAM_OFF, - FD_METRICS_COUNTER_TXSEND_FRAME_RX_MAX_DATA_OFF, - FD_METRICS_COUNTER_TXSEND_FRAME_RX_MAX_STREAM_DATA_OFF, - FD_METRICS_COUNTER_TXSEND_FRAME_RX_MAX_STREAMS_OFF, - FD_METRICS_COUNTER_TXSEND_FRAME_RX_DATA_BLOCKED_OFF, - FD_METRICS_COUNTER_TXSEND_FRAME_RX_STREAM_DATA_BLOCKED_OFF, - FD_METRICS_COUNTER_TXSEND_FRAME_RX_STREAMS_BLOCKED_OFF, - FD_METRICS_COUNTER_TXSEND_FRAME_RX_NEW_CONN_ID_OFF, - FD_METRICS_COUNTER_TXSEND_FRAME_RX_RETIRE_CONN_ID_OFF, - FD_METRICS_COUNTER_TXSEND_FRAME_RX_PATH_CHALLENGE_OFF, - FD_METRICS_COUNTER_TXSEND_FRAME_RX_PATH_RESPONSE_OFF, - FD_METRICS_COUNTER_TXSEND_FRAME_RX_CONN_CLOSE_QUIC_OFF, - FD_METRICS_COUNTER_TXSEND_FRAME_RX_CONN_CLOSE_APP_OFF, - FD_METRICS_COUNTER_TXSEND_FRAME_RX_HANDSHAKE_DONE_OFF, - FD_METRICS_COUNTER_TXSEND_FRAME_RX_PING_OFF, - FD_METRICS_COUNTER_TXSEND_FRAME_RX_PADDING_OFF, - FD_METRICS_COUNTER_TXSEND_FRAME_RX_DATAGRAM_OFF, - FD_METRICS_COUNTER_TXSEND_FRAME_PARSE_FAILED_OFF, - FD_METRICS_COUNTER_TXSEND_PKT_META_ACQUIRE_ATTEMPTED_OFF, - FD_METRICS_COUNTER_TXSEND_PKT_META_ACQUIRE_ATTEMPTED_SUCCESS_OFF = FD_METRICS_COUNTER_TXSEND_PKT_META_ACQUIRE_ATTEMPTED_OFF, - FD_METRICS_COUNTER_TXSEND_PKT_META_ACQUIRE_ATTEMPTED_POOL_EMPTY_OFF, - FD_METRICS_COUNTER_TXSEND_PKT_META_ACQUIRE_ATTEMPTED_CONN_LIMIT_OFF, - FD_METRICS_COUNTER_TXSEND_PKT_ACK_PROCESSED_OFF, - FD_METRICS_COUNTER_TXSEND_PKT_ACK_PROCESSED_NO_PKT_NUMBER_OFF = FD_METRICS_COUNTER_TXSEND_PKT_ACK_PROCESSED_OFF, - FD_METRICS_COUNTER_TXSEND_PKT_ACK_PROCESSED_NEW_RANGE_OFF, - FD_METRICS_COUNTER_TXSEND_PKT_ACK_PROCESSED_MERGED_OFF, - FD_METRICS_COUNTER_TXSEND_PKT_ACK_PROCESSED_QUEUE_FULL_OFF, - FD_METRICS_COUNTER_TXSEND_PKT_ACK_PROCESSED_CANCELLED_OFF, - FD_METRICS_HISTOGRAM_TXSEND_SERVICE_DURATION_NANOS_OFF, - FD_METRICS_HISTOGRAM_TXSEND_SERVICE_DURATION_NANOS_OFF_END = FD_METRICS_HISTOGRAM_TXSEND_SERVICE_DURATION_NANOS_OFF + 16, - FD_METRICS_HISTOGRAM_TXSEND_PKT_RX_DURATION_NANOS_OFF, - FD_METRICS_HISTOGRAM_TXSEND_PKT_RX_DURATION_NANOS_OFF_END = FD_METRICS_HISTOGRAM_TXSEND_PKT_RX_DURATION_NANOS_OFF + 16, -}; - -#define FD_METRICS_COUNTER_TXSEND_PKT_RX_NAME "txsend_pkt_rx" -#define FD_METRICS_COUNTER_TXSEND_PKT_RX_TYPE (FD_METRICS_TYPE_COUNTER) -#define FD_METRICS_COUNTER_TXSEND_PKT_RX_DESC "UDP datagrams received (one may carry several coalesced QUIC packets)" -#define FD_METRICS_COUNTER_TXSEND_PKT_RX_CVT (FD_METRICS_CONVERTER_NONE) - -#define FD_METRICS_COUNTER_TXSEND_PKT_RX_BYTES_NAME "txsend_pkt_rx_bytes" -#define FD_METRICS_COUNTER_TXSEND_PKT_RX_BYTES_TYPE (FD_METRICS_TYPE_COUNTER) -#define FD_METRICS_COUNTER_TXSEND_PKT_RX_BYTES_DESC "Bytes of UDP datagrams received, including Ethernet, IPv4 and UDP headers" -#define FD_METRICS_COUNTER_TXSEND_PKT_RX_BYTES_CVT (FD_METRICS_CONVERTER_NONE) - -#define FD_METRICS_COUNTER_TXSEND_PKT_TX_NAME "txsend_pkt_tx" -#define FD_METRICS_COUNTER_TXSEND_PKT_TX_TYPE (FD_METRICS_TYPE_COUNTER) -#define FD_METRICS_COUNTER_TXSEND_PKT_TX_DESC "UDP datagrams sent" -#define FD_METRICS_COUNTER_TXSEND_PKT_TX_CVT (FD_METRICS_CONVERTER_NONE) - -#define FD_METRICS_COUNTER_TXSEND_PKT_TX_BYTES_NAME "txsend_pkt_tx_bytes" -#define FD_METRICS_COUNTER_TXSEND_PKT_TX_BYTES_TYPE (FD_METRICS_TYPE_COUNTER) -#define FD_METRICS_COUNTER_TXSEND_PKT_TX_BYTES_DESC "Bytes of UDP datagrams sent, including Ethernet, IPv4 and UDP headers" -#define FD_METRICS_COUNTER_TXSEND_PKT_TX_BYTES_CVT (FD_METRICS_CONVERTER_NONE) - -#define FD_METRICS_GAUGE_TXSEND_CONN_USED_NAME "txsend_conn_used" -#define FD_METRICS_GAUGE_TXSEND_CONN_USED_TYPE (FD_METRICS_TYPE_GAUGE) -#define FD_METRICS_GAUGE_TXSEND_CONN_USED_DESC "Connection slots in use, by state; sum + ConnFree = ConnCapacity" -#define FD_METRICS_GAUGE_TXSEND_CONN_USED_CVT (FD_METRICS_CONVERTER_NONE) -#define FD_METRICS_GAUGE_TXSEND_CONN_USED_CNT (7UL) - -#define FD_METRICS_GAUGE_TXSEND_CONN_FREE_NAME "txsend_conn_free" -#define FD_METRICS_GAUGE_TXSEND_CONN_FREE_TYPE (FD_METRICS_TYPE_GAUGE) -#define FD_METRICS_GAUGE_TXSEND_CONN_FREE_DESC "Connection slots free" -#define FD_METRICS_GAUGE_TXSEND_CONN_FREE_CVT (FD_METRICS_CONVERTER_NONE) - -#define FD_METRICS_GAUGE_TXSEND_CONN_CAPACITY_NAME "txsend_conn_capacity" -#define FD_METRICS_GAUGE_TXSEND_CONN_CAPACITY_TYPE (FD_METRICS_TYPE_GAUGE) -#define FD_METRICS_GAUGE_TXSEND_CONN_CAPACITY_DESC "Connection slots" -#define FD_METRICS_GAUGE_TXSEND_CONN_CAPACITY_CVT (FD_METRICS_CONVERTER_NONE) - -#define FD_METRICS_COUNTER_TXSEND_CONN_CREATED_NAME "txsend_conn_created" -#define FD_METRICS_COUNTER_TXSEND_CONN_CREATED_TYPE (FD_METRICS_TYPE_COUNTER) -#define FD_METRICS_COUNTER_TXSEND_CONN_CREATED_DESC "Connections created" -#define FD_METRICS_COUNTER_TXSEND_CONN_CREATED_CVT (FD_METRICS_CONVERTER_NONE) - -#define FD_METRICS_COUNTER_TXSEND_CONN_CLOSED_NAME "txsend_conn_closed" -#define FD_METRICS_COUNTER_TXSEND_CONN_CLOSED_TYPE (FD_METRICS_TYPE_COUNTER) -#define FD_METRICS_COUNTER_TXSEND_CONN_CLOSED_DESC "Connections gracefully closed" -#define FD_METRICS_COUNTER_TXSEND_CONN_CLOSED_CVT (FD_METRICS_CONVERTER_NONE) - -#define FD_METRICS_COUNTER_TXSEND_CONN_ABORTED_NAME "txsend_conn_aborted" -#define FD_METRICS_COUNTER_TXSEND_CONN_ABORTED_TYPE (FD_METRICS_TYPE_COUNTER) -#define FD_METRICS_COUNTER_TXSEND_CONN_ABORTED_DESC "Connections aborted" -#define FD_METRICS_COUNTER_TXSEND_CONN_ABORTED_CVT (FD_METRICS_CONVERTER_NONE) - -#define FD_METRICS_COUNTER_TXSEND_CONN_TIMED_OUT_NAME "txsend_conn_timed_out" -#define FD_METRICS_COUNTER_TXSEND_CONN_TIMED_OUT_TYPE (FD_METRICS_TYPE_COUNTER) -#define FD_METRICS_COUNTER_TXSEND_CONN_TIMED_OUT_DESC "Connections timed out" -#define FD_METRICS_COUNTER_TXSEND_CONN_TIMED_OUT_CVT (FD_METRICS_CONVERTER_NONE) - -#define FD_METRICS_COUNTER_TXSEND_CONN_NO_SLOT_REJECTED_NAME "txsend_conn_no_slot_rejected" -#define FD_METRICS_COUNTER_TXSEND_CONN_NO_SLOT_REJECTED_TYPE (FD_METRICS_TYPE_COUNTER) -#define FD_METRICS_COUNTER_TXSEND_CONN_NO_SLOT_REJECTED_DESC "Connections that failed to create due to lack of slots" -#define FD_METRICS_COUNTER_TXSEND_CONN_NO_SLOT_REJECTED_CVT (FD_METRICS_CONVERTER_NONE) - -#define FD_METRICS_COUNTER_TXSEND_CONN_RETRY_REJECTED_NAME "txsend_conn_retry_rejected" -#define FD_METRICS_COUNTER_TXSEND_CONN_RETRY_REJECTED_TYPE (FD_METRICS_TYPE_COUNTER) -#define FD_METRICS_COUNTER_TXSEND_CONN_RETRY_REJECTED_DESC "Retries rejected: an invalid or expired token as server, a late or invalid Retry packet as client" -#define FD_METRICS_COUNTER_TXSEND_CONN_RETRY_REJECTED_CVT (FD_METRICS_CONVERTER_NONE) - -#define FD_METRICS_COUNTER_TXSEND_PKT_DECRYPT_FAILED_NAME "txsend_pkt_decrypt_failed" -#define FD_METRICS_COUNTER_TXSEND_PKT_DECRYPT_FAILED_TYPE (FD_METRICS_TYPE_COUNTER) -#define FD_METRICS_COUNTER_TXSEND_PKT_DECRYPT_FAILED_DESC "Packets that failed decryption, by encryption level" -#define FD_METRICS_COUNTER_TXSEND_PKT_DECRYPT_FAILED_CVT (FD_METRICS_CONVERTER_NONE) -#define FD_METRICS_COUNTER_TXSEND_PKT_DECRYPT_FAILED_CNT (4UL) - -#define FD_METRICS_COUNTER_TXSEND_PKT_NO_KEY_DROPPED_NAME "txsend_pkt_no_key_dropped" -#define FD_METRICS_COUNTER_TXSEND_PKT_NO_KEY_DROPPED_TYPE (FD_METRICS_TYPE_COUNTER) -#define FD_METRICS_COUNTER_TXSEND_PKT_NO_KEY_DROPPED_DESC "Packets dropped because the decryption key was missing, by encryption level" -#define FD_METRICS_COUNTER_TXSEND_PKT_NO_KEY_DROPPED_CVT (FD_METRICS_CONVERTER_NONE) -#define FD_METRICS_COUNTER_TXSEND_PKT_NO_KEY_DROPPED_CNT (4UL) - -#define FD_METRICS_COUNTER_TXSEND_PKT_DROPPED_NAME "txsend_pkt_dropped" -#define FD_METRICS_COUNTER_TXSEND_PKT_DROPPED_TYPE (FD_METRICS_TYPE_COUNTER) -#define FD_METRICS_COUNTER_TXSEND_PKT_DROPPED_DESC "Received packets dropped, by reason" -#define FD_METRICS_COUNTER_TXSEND_PKT_DROPPED_CVT (FD_METRICS_CONVERTER_NONE) -#define FD_METRICS_COUNTER_TXSEND_PKT_DROPPED_CNT (7UL) - -#define FD_METRICS_COUNTER_TXSEND_PKT_NO_CONN_DROPPED_NAME "txsend_pkt_no_conn_dropped" -#define FD_METRICS_COUNTER_TXSEND_PKT_NO_CONN_DROPPED_TYPE (FD_METRICS_TYPE_COUNTER) -#define FD_METRICS_COUNTER_TXSEND_PKT_NO_CONN_DROPPED_DESC "Packets with an unknown connection ID, by packet type" -#define FD_METRICS_COUNTER_TXSEND_PKT_NO_CONN_DROPPED_CVT (FD_METRICS_CONVERTER_NONE) -#define FD_METRICS_COUNTER_TXSEND_PKT_NO_CONN_DROPPED_CNT (4UL) - -#define FD_METRICS_COUNTER_TXSEND_PKT_RETRANSMITTED_NAME "txsend_pkt_retransmitted" -#define FD_METRICS_COUNTER_TXSEND_PKT_RETRANSMITTED_TYPE (FD_METRICS_TYPE_COUNTER) -#define FD_METRICS_COUNTER_TXSEND_PKT_RETRANSMITTED_DESC "QUIC packets retransmitted, by encryption level" -#define FD_METRICS_COUNTER_TXSEND_PKT_RETRANSMITTED_CVT (FD_METRICS_CONVERTER_NONE) -#define FD_METRICS_COUNTER_TXSEND_PKT_RETRANSMITTED_CNT (4UL) - -#define FD_METRICS_COUNTER_TXSEND_HANDSHAKE_CREATED_NAME "txsend_handshake_created" -#define FD_METRICS_COUNTER_TXSEND_HANDSHAKE_CREATED_TYPE (FD_METRICS_TYPE_COUNTER) -#define FD_METRICS_COUNTER_TXSEND_HANDSHAKE_CREATED_DESC "Handshake flows created" -#define FD_METRICS_COUNTER_TXSEND_HANDSHAKE_CREATED_CVT (FD_METRICS_CONVERTER_NONE) - -#define FD_METRICS_COUNTER_TXSEND_HANDSHAKE_POOL_FULL_REJECTED_NAME "txsend_handshake_pool_full_rejected" -#define FD_METRICS_COUNTER_TXSEND_HANDSHAKE_POOL_FULL_REJECTED_TYPE (FD_METRICS_TYPE_COUNTER) -#define FD_METRICS_COUNTER_TXSEND_HANDSHAKE_POOL_FULL_REJECTED_DESC "Handshakes dropped due to alloc fail" -#define FD_METRICS_COUNTER_TXSEND_HANDSHAKE_POOL_FULL_REJECTED_CVT (FD_METRICS_CONVERTER_NONE) - -#define FD_METRICS_COUNTER_TXSEND_HANDSHAKE_EVICTED_NAME "txsend_handshake_evicted" -#define FD_METRICS_COUNTER_TXSEND_HANDSHAKE_EVICTED_TYPE (FD_METRICS_TYPE_COUNTER) -#define FD_METRICS_COUNTER_TXSEND_HANDSHAKE_EVICTED_DESC "Handshakes dropped due to eviction" -#define FD_METRICS_COUNTER_TXSEND_HANDSHAKE_EVICTED_CVT (FD_METRICS_CONVERTER_NONE) - -#define FD_METRICS_COUNTER_TXSEND_STREAM_RX_NAME "txsend_stream_rx" -#define FD_METRICS_COUNTER_TXSEND_STREAM_RX_TYPE (FD_METRICS_TYPE_COUNTER) -#define FD_METRICS_COUNTER_TXSEND_STREAM_RX_DESC "Stream receive events" -#define FD_METRICS_COUNTER_TXSEND_STREAM_RX_CVT (FD_METRICS_CONVERTER_NONE) - -#define FD_METRICS_COUNTER_TXSEND_STREAM_RX_BYTES_NAME "txsend_stream_rx_bytes" -#define FD_METRICS_COUNTER_TXSEND_STREAM_RX_BYTES_TYPE (FD_METRICS_TYPE_COUNTER) -#define FD_METRICS_COUNTER_TXSEND_STREAM_RX_BYTES_DESC "Stream payload bytes received" -#define FD_METRICS_COUNTER_TXSEND_STREAM_RX_BYTES_CVT (FD_METRICS_CONVERTER_NONE) - -#define FD_METRICS_COUNTER_TXSEND_FRAME_RX_NAME "txsend_frame_rx" -#define FD_METRICS_COUNTER_TXSEND_FRAME_RX_TYPE (FD_METRICS_TYPE_COUNTER) -#define FD_METRICS_COUNTER_TXSEND_FRAME_RX_DESC "QUIC frames received, by frame type" -#define FD_METRICS_COUNTER_TXSEND_FRAME_RX_CVT (FD_METRICS_CONVERTER_NONE) -#define FD_METRICS_COUNTER_TXSEND_FRAME_RX_CNT (23UL) - -#define FD_METRICS_COUNTER_TXSEND_FRAME_PARSE_FAILED_NAME "txsend_frame_parse_failed" -#define FD_METRICS_COUNTER_TXSEND_FRAME_PARSE_FAILED_TYPE (FD_METRICS_TYPE_COUNTER) -#define FD_METRICS_COUNTER_TXSEND_FRAME_PARSE_FAILED_DESC "QUIC frames that failed to parse" -#define FD_METRICS_COUNTER_TXSEND_FRAME_PARSE_FAILED_CVT (FD_METRICS_CONVERTER_NONE) - -#define FD_METRICS_COUNTER_TXSEND_PKT_META_ACQUIRE_ATTEMPTED_NAME "txsend_pkt_meta_acquire_attempted" -#define FD_METRICS_COUNTER_TXSEND_PKT_META_ACQUIRE_ATTEMPTED_TYPE (FD_METRICS_TYPE_COUNTER) -#define FD_METRICS_COUNTER_TXSEND_PKT_META_ACQUIRE_ATTEMPTED_DESC "Attempts to acquire packet metadata for transmission, by result" -#define FD_METRICS_COUNTER_TXSEND_PKT_META_ACQUIRE_ATTEMPTED_CVT (FD_METRICS_CONVERTER_NONE) -#define FD_METRICS_COUNTER_TXSEND_PKT_META_ACQUIRE_ATTEMPTED_CNT (3UL) - -#define FD_METRICS_COUNTER_TXSEND_PKT_ACK_PROCESSED_NAME "txsend_pkt_ack_processed" -#define FD_METRICS_COUNTER_TXSEND_PKT_ACK_PROCESSED_TYPE (FD_METRICS_TYPE_COUNTER) -#define FD_METRICS_COUNTER_TXSEND_PKT_ACK_PROCESSED_DESC "Received packets processed for acknowledgement, by result" -#define FD_METRICS_COUNTER_TXSEND_PKT_ACK_PROCESSED_CVT (FD_METRICS_CONVERTER_NONE) -#define FD_METRICS_COUNTER_TXSEND_PKT_ACK_PROCESSED_CNT (5UL) - -#define FD_METRICS_HISTOGRAM_TXSEND_SERVICE_DURATION_NANOS_NAME "txsend_service_duration_nanos" -#define FD_METRICS_HISTOGRAM_TXSEND_SERVICE_DURATION_NANOS_TYPE (FD_METRICS_TYPE_HISTOGRAM) -#define FD_METRICS_HISTOGRAM_TXSEND_SERVICE_DURATION_NANOS_DESC "Duration of one fd_quic_service call that processed a due connection timer" -#define FD_METRICS_HISTOGRAM_TXSEND_SERVICE_DURATION_NANOS_CVT (FD_METRICS_CONVERTER_NANOSECONDS) -#define FD_METRICS_HISTOGRAM_TXSEND_SERVICE_DURATION_NANOS_EDGES_NS { 10UL, 32UL, 101UL, 319UL, 1008UL, 3185UL, 10064UL, 31800UL, 100480UL, 317492UL, 1003197UL, 3169857UL, 10015972UL, 31648020UL, 100000000UL } - -#define FD_METRICS_HISTOGRAM_TXSEND_PKT_RX_DURATION_NANOS_NAME "txsend_pkt_rx_duration_nanos" -#define FD_METRICS_HISTOGRAM_TXSEND_PKT_RX_DURATION_NANOS_TYPE (FD_METRICS_TYPE_HISTOGRAM) -#define FD_METRICS_HISTOGRAM_TXSEND_PKT_RX_DURATION_NANOS_DESC "Duration of processing one received packet" -#define FD_METRICS_HISTOGRAM_TXSEND_PKT_RX_DURATION_NANOS_CVT (FD_METRICS_CONVERTER_NANOSECONDS) -#define FD_METRICS_HISTOGRAM_TXSEND_PKT_RX_DURATION_NANOS_EDGES_NS { 10UL, 32UL, 101UL, 319UL, 1008UL, 3185UL, 10064UL, 31800UL, 100480UL, 317492UL, 1003197UL, 3169857UL, 10015972UL, 31648020UL, 100000000UL } - -#define FD_METRICS_TXSEND_TOTAL (81UL) -extern const fd_metrics_meta_t FD_METRICS_TXSEND[FD_METRICS_TXSEND_TOTAL]; - -#endif /* HEADER_fd_src_disco_metrics_generated_fd_metrics_txsend_h */ diff --git a/src/disco/metrics/metrics.xml b/src/disco/metrics/metrics.xml index 00114bbf810..2e244aa070a 100644 --- a/src/disco/metrics/metrics.xml +++ b/src/disco/metrics/metrics.xml @@ -437,59 +437,6 @@ deliberate exception with lint-allow="") - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Duration of one fd_quic_service call that processed a due connection timer - - - Duration of processing one received packet - - - diff --git a/src/disco/topo/fd_topo.h b/src/disco/topo/fd_topo.h index 5f9968f5801..aef6fdb183b 100644 --- a/src/disco/topo/fd_topo.h +++ b/src/disco/topo/fd_topo.h @@ -605,15 +605,6 @@ struct fd_topo_tile { ulong blockdb_max; /* 0 disables the block metadata db */ } rserve; - struct { - ushort txsend_src_port; - - /* non-config */ - - uint ip_addr; - char identity_key_path[ PATH_MAX ]; - } txsend; - struct { uint fake_dst_ip; } pktgen; diff --git a/src/discof/txsend/Local.mk b/src/discof/txsend/Local.mk deleted file mode 100644 index a9297945cc9..00000000000 --- a/src/discof/txsend/Local.mk +++ /dev/null @@ -1,3 +0,0 @@ -ifdef FD_HAS_HOSTED -$(call add-objs,fd_txsend_tile,fd_discof) -endif diff --git a/src/discof/txsend/fd_txsend_tile.c b/src/discof/txsend/fd_txsend_tile.c deleted file mode 100644 index 2e835b3ecd8..00000000000 --- a/src/discof/txsend/fd_txsend_tile.c +++ /dev/null @@ -1,936 +0,0 @@ -/* The txsend tile relays new transactions to the current leader. - - To recap, every ~1.6 seconds, a new validator is elected by the - protocol to receive all transactions, and pack them into blocks. - To submit a transaction to Solana, one has to track the "leader - schedule" and get the timing right to submit a transaction early - enough. - - The txsend tile supports the TPU-UDP (connectionless) and TPU-QUIC - transports. This tile has the following jobs: - - decide who to connect to (continually monitor gossip, leader schedule) - - manage a pool of QUIC connections - - actually send transactions - - An important quirk is that txsend tolerates temporary gossip outages - by indefinitely caching old endpoint info (until the gossip feed is - revived). */ - -#include "fd_txsend_tile.h" - -#include "../../disco/topo/fd_topo.h" -#include "../../disco/fd_txn_m.h" -#include "../../disco/metrics/fd_metrics.h" -#include "../../disco/events/generated/fd_event_gen.h" -#include "../../disco/events/fd_event_report.h" -#include "../../choreo/tower/fd_tower_serdes.h" -#include "../../flamenco/runtime/fd_system_ids.h" -#include "../../disco/keyguard/fd_keyguard.h" -#include "../../disco/keyguard/fd_keyload.h" -#include "../fd_startup.h" -#include "../tower/fd_tower_tile.h" -#include "../../util/net/fd_net_headers.h" -#include "../../waltz/quic/fd_quic.h" - -#include -#include -#include "generated/fd_txsend_tile_seccomp.h" - -#define IN_KIND_SIGN (0UL) -#define IN_KIND_GOSSIP (1UL) -#define IN_KIND_EPOCH (2UL) -#define IN_KIND_TOWER (3UL) -#define IN_KIND_NET (4UL) - -fd_quic_limits_t quic_limits = { - .conn_cnt = 128UL, - .handshake_cnt = 128UL, - .conn_id_cnt = FD_QUIC_MIN_CONN_ID_CNT, - .inflight_frame_cnt = 16UL * 128UL, - .min_inflight_frame_cnt_conn = 4UL, - .stream_id_cnt = 64UL, - .tx_buf_sz = FD_TXN_MTU, - .stream_pool_cnt = 128UL, -}; - -#define MAP_NAME peer_map -#define MAP_KEY pubkey -#define MAP_ELE_T peer_entry_t -#define MAP_KEY_T fd_pubkey_t -#define MAP_NEXT map.next -#define MAP_KEY_EQ(k0,k1) fd_pubkey_eq( k0, k1 ) -#define MAP_KEY_HASH(key,seed) fd_progcache_rec_key_hash1( (key)->uc, (seed) ) -#define MAP_IMPL_STYLE 2 -#include "../../util/tmpl/fd_map_chain.c" - -FD_FN_CONST static inline ulong -scratch_align( void ) { - return fd_ulong_max( 128UL, fd_quic_align() ); -} - -FD_FN_PURE static inline ulong -scratch_footprint( fd_topo_tile_t const * tile FD_PARAM_UNUSED ) { - ulong l = FD_LAYOUT_INIT; - l = FD_LAYOUT_APPEND( l, alignof(fd_txsend_tile_t), sizeof(fd_txsend_tile_t) ); - l = FD_LAYOUT_APPEND( l, fd_quic_align(), fd_quic_footprint( &quic_limits ) ); - l = FD_LAYOUT_APPEND( l, peer_map_align(), peer_map_footprint( 2UL*FD_CONTACT_INFO_TABLE_SIZE ) ); - return FD_LAYOUT_FINI( l, scratch_align() ); -} - -static void -during_housekeeping( fd_txsend_tile_t * ctx ) { - if( FD_UNLIKELY( fd_clock_tile_recal_due( ctx->clock ) ) ) fd_clock_tile_recal( ctx->clock ); - - if( FD_UNLIKELY( fd_keyswitch_state_query( ctx->keyswitch )==FD_KEYSWITCH_STATE_UNHALT_PENDING ) ) { - FD_LOG_DEBUG(( "keyswitch: unhalting" )); - ctx->halt_net_frags = 0; - fd_keyswitch_state( ctx->keyswitch, FD_KEYSWITCH_STATE_COMPLETED ); - } - - if( FD_UNLIKELY( fd_keyswitch_state_query( ctx->keyswitch )==FD_KEYSWITCH_STATE_SWITCH_PENDING ) ) { - FD_LOG_DEBUG(( "keyswitch: switching identity" )); - ulong seq_must_complete = ctx->keyswitch->param; - if( FD_UNLIKELY( fd_seq_lt( ctx->tower_in_expect_seq, seq_must_complete ) ) ) { - /* See fd_keyswitch.h, we need to flush any in-flight shreds from - the leader pipeline before switching key. */ - FD_LOG_WARNING(( "Flushing in-flight unpublished votes from tower, must reach seq %lu, currently at %lu ...", seq_must_complete, ctx->tower_in_expect_seq )); - return; - } - - /* Halt net frags to avoid potential quic callback in after_frag */ - ctx->halt_net_frags = 1; - - fd_quic_set_identity_public_key( ctx->quic, ctx->keyswitch->bytes ); - - memcpy( ctx->identity_key, ctx->keyswitch->bytes, 32UL ); - ctx->keyswitch->result = ctx->txsend_out_seq; - fd_keyswitch_state( ctx->keyswitch, FD_KEYSWITCH_STATE_COMPLETED ); - } - - if( FD_UNLIKELY( ctx->av_keyswitch && fd_keyswitch_state_query( ctx->av_keyswitch )==FD_KEYSWITCH_STATE_SWITCH_PENDING ) ) { - ulong seq_must_complete = fd_keyswitch_param_query( ctx->av_keyswitch ); - if( FD_UNLIKELY( fd_seq_lt( ctx->tower_in_expect_seq, seq_must_complete ) ) ) { - FD_LOG_WARNING(( "Flushing in-flight votes from tower, must reach seq %lu, currently at %lu ...", seq_must_complete, ctx->tower_in_expect_seq )); - return; - } - fd_keyswitch_state( ctx->av_keyswitch, FD_KEYSWITCH_STATE_COMPLETED ); - } -} - -static void -metrics_write( fd_txsend_tile_t * ctx ) { - FD_MCNT_SET( TXSEND, PKT_RX_BYTES, ctx->quic->metrics.net_rx_byte_cnt+sizeof(fd_eth_hdr_t)*ctx->quic->metrics.net_rx_pkt_cnt ); - FD_MCNT_ENUM_COPY( TXSEND, FRAME_RX, ctx->quic->metrics.frame_rx_cnt ); - FD_MCNT_SET( TXSEND, PKT_RX, ctx->quic->metrics.net_rx_pkt_cnt ); - FD_MCNT_SET( TXSEND, STREAM_RX_BYTES, ctx->quic->metrics.stream_rx_byte_cnt ); - FD_MCNT_SET( TXSEND, STREAM_RX, ctx->quic->metrics.stream_rx_event_cnt ); - - FD_MCNT_SET( TXSEND, PKT_TX, ctx->quic->metrics.net_tx_pkt_cnt ); - FD_MCNT_SET( TXSEND, PKT_TX_BYTES, ctx->quic->metrics.net_tx_byte_cnt+sizeof(fd_eth_hdr_t)*ctx->quic->metrics.net_tx_pkt_cnt ); - FD_MCNT_ENUM_COPY( TXSEND, PKT_ACK_PROCESSED, ctx->quic->metrics.ack_tx ); - FD_MGAUGE_ENUM_COPY( TXSEND, CONN_USED, (ctx->quic->metrics.conn_state_cnt+1) ); /* skip FD_QUIC_CONN_STATE_INVALID (free slots) */ - FD_MGAUGE_SET( TXSEND, CONN_FREE, ctx->quic->limits.conn_cnt-ctx->quic->metrics.conn_alloc_cnt ); - FD_MGAUGE_SET( TXSEND, CONN_CAPACITY, ctx->quic->limits.conn_cnt ); - FD_MCNT_SET( TXSEND, CONN_CREATED, ctx->quic->metrics.conn_created_cnt ); - FD_MCNT_SET( TXSEND, CONN_CLOSED, ctx->quic->metrics.conn_closed_cnt ); - FD_MCNT_SET( TXSEND, CONN_ABORTED, ctx->quic->metrics.conn_aborted_cnt ); - FD_MCNT_SET( TXSEND, CONN_TIMED_OUT, ctx->quic->metrics.conn_timeout_cnt ); - FD_MCNT_SET( TXSEND, CONN_NO_SLOT_REJECTED, ctx->quic->metrics.conn_err_no_slots_cnt ); - FD_MCNT_SET( TXSEND, CONN_RETRY_REJECTED, ctx->quic->metrics.conn_err_retry_fail_cnt ); - - FD_MCNT_ENUM_COPY( TXSEND, PKT_DECRYPT_FAILED, ctx->quic->metrics.pkt_decrypt_fail_cnt ); - FD_MCNT_ENUM_COPY( TXSEND, PKT_NO_KEY_DROPPED, ctx->quic->metrics.pkt_no_key_cnt ); - FD_MCNT_ENUM_COPY( TXSEND, PKT_NO_CONN_DROPPED, ctx->quic->metrics.pkt_no_conn_cnt ); - ulong pkt_drop[ FD_METRICS_ENUM_QUIC_PKT_DROP_REASON_CNT ] = { - [ FD_METRICS_ENUM_QUIC_PKT_DROP_REASON_V_UNDERSIZE_IDX ] = ctx->quic->metrics.pkt_undersz_cnt, - [ FD_METRICS_ENUM_QUIC_PKT_DROP_REASON_V_OVERSIZE_IDX ] = ctx->quic->metrics.pkt_oversz_cnt, - [ FD_METRICS_ENUM_QUIC_PKT_DROP_REASON_V_SRC_IP_MISMATCH_IDX ] = ctx->quic->metrics.pkt_wrong_src_cnt, - [ FD_METRICS_ENUM_QUIC_PKT_DROP_REASON_V_HEADER_INVALID_IDX ] = ctx->quic->metrics.pkt_quic_hdr_err_cnt, - [ FD_METRICS_ENUM_QUIC_PKT_DROP_REASON_V_NET_HEADER_INVALID_IDX ] = ctx->quic->metrics.pkt_net_hdr_err_cnt, - [ FD_METRICS_ENUM_QUIC_PKT_DROP_REASON_V_VERSION_UNSUPPORTED_IDX ] = ctx->quic->metrics.pkt_verneg_cnt, - [ FD_METRICS_ENUM_QUIC_PKT_DROP_REASON_V_DUPLICATE_IDX ] = ctx->quic->metrics.pkt_dup_cnt, - }; - FD_MCNT_ENUM_COPY( TXSEND, PKT_DROPPED, pkt_drop ); - FD_MCNT_ENUM_COPY( TXSEND, PKT_META_ACQUIRE_ATTEMPTED, ctx->quic->metrics.frame_tx_alloc_cnt ); - FD_MCNT_ENUM_COPY( TXSEND, PKT_RETRANSMITTED, ctx->quic->metrics.pkt_retransmissions_cnt ); - - FD_MCNT_SET( TXSEND, HANDSHAKE_CREATED, ctx->quic->metrics.hs_created_cnt ); - FD_MCNT_SET( TXSEND, HANDSHAKE_POOL_FULL_REJECTED, ctx->quic->metrics.hs_err_alloc_fail_cnt ); - FD_MCNT_SET( TXSEND, HANDSHAKE_EVICTED, ctx->quic->metrics.hs_evicted_cnt ); - - FD_MCNT_SET( TXSEND, FRAME_PARSE_FAILED, ctx->quic->metrics.frame_rx_err_cnt ); - - FD_MHIST_COPY( TXSEND, SERVICE_DURATION_NANOS, ctx->quic->metrics.service_duration ); - FD_MHIST_COPY( TXSEND, PKT_RX_DURATION_NANOS, ctx->quic->metrics.receive_duration ); -} - -static void -quic_tls_cv_sign( void * signer_ctx, - uchar signature[ static 64 ], - uchar const payload[ static 130 ] ) { - fd_txsend_tile_t * ctx = signer_ctx; - - fd_keyguard_client_sign( ctx->keyguard_client, signature, payload, 130UL, FD_KEYGUARD_SIGN_TYPE_ED25519 ); -} - -static void -send_to_net( fd_txsend_tile_t * ctx, - fd_ip4_hdr_t const * ip4_hdr, - fd_udp_hdr_t const * udp_hdr, - uchar const * payload, - ulong payload_sz, - long now ) { - uint const ip_dst = FD_LOAD( uint, ip4_hdr->daddr_c ); - ulong const ip_sz = FD_IP4_GET_LEN( *ip4_hdr ); - - ulong const hdr_sz = sizeof(fd_eth_hdr_t) + ip_sz + sizeof(fd_udp_hdr_t); - if( FD_UNLIKELY( payload_sz>FD_ETH_PAYLOAD_MAX-hdr_sz ) ) return; - ulong const sz_l2 = hdr_sz + payload_sz; - - fd_txsend_out_t * net_out_link = ctx->net_out; - uchar * packet_l2 = fd_chunk_to_laddr( net_out_link->mem, net_out_link->chunk ); - uchar * packet_l3 = packet_l2 + sizeof(fd_eth_hdr_t); - uchar * packet_l4 = packet_l3 + ip_sz; - uchar * packet_l5 = packet_l4 + sizeof(fd_udp_hdr_t); - - fd_memcpy( packet_l2, ctx->packet_hdr->eth, sizeof(fd_eth_hdr_t) ); - fd_memcpy( packet_l3, ip4_hdr, ip_sz ); - fd_memcpy( packet_l4, udp_hdr, sizeof(fd_udp_hdr_t) ); - fd_memcpy( packet_l5, payload, payload_sz ); - - ulong sig = fd_disco_netmux_sig( ip_dst, 0U, ip_dst, DST_PROTO_OUTGOING, FD_NETMUX_SIG_MIN_HDR_SZ ); - - ulong tspub = (ulong)fd_frag_meta_ts_comp( now ); - fd_stem_publish( ctx->stem, net_out_link->idx, sig, net_out_link->chunk, sz_l2, 0UL, 0, tspub ); - net_out_link->chunk = fd_dcache_compact_next( net_out_link->chunk, sz_l2, net_out_link->chunk0, net_out_link->wmark ); -} - -static int -quic_tx_aio_send( void * _ctx, - fd_aio_pkt_info_t const * batch, - ulong batch_cnt, - ulong * opt_batch_idx, - int flush FD_PARAM_UNUSED ) { - fd_txsend_tile_t * ctx = _ctx; - - long now = fd_clock_tile_now( ctx->clock ); - - for( ulong i=0; i= ip4_len + sizeof(fd_udp_hdr_t) ); - ulong payload_sz = batch[ i ].buf_sz - ip4_len - sizeof(fd_udp_hdr_t); - send_to_net( ctx, ip4_hdr, udp_hdr, payload, payload_sz, now ); - } - - if( FD_LIKELY( opt_batch_idx ) ) { - *opt_batch_idx = batch_cnt; - } - - return FD_AIO_SUCCESS; -} - -/* quic_conn_deregister removes references to a quic_conn object from - txsend state. */ - -static void -quic_conn_deregister( fd_txsend_tile_t * tile, - fd_quic_conn_t * conn ) { - for( ulong i=0UL; iconns_len; i++ ) { - if( FD_LIKELY( tile->conns[ i ].conn!=conn ) ) continue; - peer_entry_t * peer = peer_map_ele_query( tile->peer_map, &tile->conns[ i ].pubkey, NULL, tile->peers ); - if( FD_LIKELY( peer ) ) { - for( ulong j=0UL; j<2UL; j++ ) { - if( peer->quic_conns[ j ].quic_conn==conn ) { - peer->quic_conns[ j ].quic_conn = NULL; - } - } - } - if( FD_UNLIKELY( i!=tile->conns_len-1UL ) ) tile->conns[ i ] = tile->conns[ tile->conns_len-1UL ]; - tile->conns_len--; - return; - } -} - -/* quic_conn_final is invoked by fd_quic just before a conn object is - deallocated. Here, we must remove all references to this conn. */ - -static void -quic_conn_final( fd_quic_conn_t * conn, - void * ctx ) { - fd_txsend_tile_t * tile = ctx; - quic_conn_deregister( tile, conn ); -} - -/* quic_conn_close instructs fd_quic to deallocate the given conn and - say goodbye (CONNECTION_CLOSE) to the peer. */ - -static void -quic_conn_close( fd_txsend_tile_t * tile, - fd_quic_conn_t * conn, - uint reason ) { - if( FD_UNLIKELY( !conn ) ) return; - /* Defer a conn close operation */ - fd_quic_conn_close( conn, reason ); - quic_conn_deregister( tile, conn ); - /* Send out a packet and invoke quic_conn_final */ - fd_quic_service( tile->quic, fd_clock_tile_now( tile->clock ) ); -} - -/* This QUIC servicing is very precarious. Recall a few facts, - - 1) QUIC needs to be serviced periodically to make progress - 2) QUIC servicing may produce outgoing packets that need to be sent - to the network - 3) Elsewhere, the the tile publishes frags to the verify tile to - send our own votes into our leader pipeline - - You could service QUIC in before_credit, as the QUIC tile does, but - this has a problem. If you publish frags in before_credit, you might - overrun the downstream consumer. For net tile, this is OK because it - expects that (as does verify). But the credit counting mechanism - doesn't expect this behavior and will underflow. (That's also not - ideal, in case some plugin wanted to listen reliably on quic->verify - they could not, if it got underflowed). Here though, we want to - avoid dropping outgoing votes to verify, since they might be needed - for liveness of a small cluster. - - We thus take the trade of servicing QUIC in after_credit, which means - it could theoretically get backpressured by verify, however this - isn't realistic in practice, as verify polls round robin and there's - only one vote per slot. */ - -static inline long -next_deadline( fd_txsend_tile_t * ctx ) { - long next = fd_quic_get_next_wakeup( ctx->quic ); - return next==LONG_MAX ? LONG_MAX : fd_clock_tile_wallclock_to_tickcount( ctx->clock, next ); -} - -static inline void -after_credit( fd_txsend_tile_t * ctx, - fd_stem_context_t * stem, - int * opt_poll_in, - int * charge_busy ) { - ctx->stem = stem; - - if( FD_UNLIKELY( !fd_startup_gate_idle( ctx->startup_gate ) ) ) return; - - *charge_busy = fd_quic_service( ctx->quic, fd_clock_tile_now( ctx->clock ) ); - *opt_poll_in = !*charge_busy; /* refetch credits to prevent above documented situation */ - - if( FD_UNLIKELY( ctx->leader_schedules<2UL ) ) return; - if( FD_UNLIKELY( ctx->voted_slot==ULONG_MAX ) ) return; - - fd_pubkey_t const * leaders[ 7UL ]; - - for( ulong i=0UL; i<7UL; i++ ) { - /* It's possible for leaders[i] to be NULL if target slot is two - epochs ahead of the replay root. This is not possible on mainnet - but can occur on local clusters during warmup epochs. */ - ulong target_slot = ctx->voted_slot+1UL + i*FD_EPOCH_SLOTS_PER_ROTATION; - leaders[ i ] = fd_multi_epoch_leaders_get_leader_for_slot( ctx->mleaders, target_slot ); - } - - /* Disconnect any QUIC connection to a leader that does not have a - rotation coming up in the next 7 slots. */ - ulong conn_cnt = ctx->conns_len; - for( ulong i=0UL; iconns[ i ].pubkey, leaders[ j ] ) ) { - keep_conn = 1; - break; - } - } - - if( FD_UNLIKELY( !keep_conn ) ) quic_conn_close( ctx, ctx->conns[ i ].conn, 0 ); - if( ctx->conns_len==conn_cnt ) i++; - conn_cnt = ctx->conns_len; - } - - /* Connect to any leader that does not have a connection yet. */ - for( ulong i=0UL; i<7UL; i++ ) { - fd_pubkey_t const * leader = leaders[ i ]; - if( FD_UNLIKELY( !leader ) ) continue; - peer_entry_t * peer = peer_map_ele_query( ctx->peer_map, leader, NULL, ctx->peers ); - if( FD_UNLIKELY( !peer ) ) continue; /* no contact info */ - - for( ulong j=0UL; j<2UL; j++ ) { - if( FD_UNLIKELY( ctx->conns_len==128UL ) ) break; /* connection limit reached */ - txsend_conn_t * conn = &peer->quic_conns[ j ]; - if( FD_LIKELY( conn->quic_conn ) ) continue; /* already connected */ - if( FD_UNLIKELY( !conn->quic_ip_addr || !conn->quic_port ) ) continue; - - /* Don't try to reconnect more than once every two seconds ... - Basically Agave limits us to 8 connections per minute, so if we - keep trying to reconnect rapidly it's much less effective than - waiting a little bit to ensure we stay under the threshold. - - We should probably make this a bit more sophisticated, with a - simple model that considers past connection attempts, and - future leader slots (e.g. we might still want to burn an - attempt if a leader slot is imminent, even if we recently tried - to connect). For now the dumb logic seems to work well enough. */ - long now = fd_clock_tile_now( ctx->clock ); - if( FD_UNLIKELY( conn->quic_last_connected+2e9L>now ) ) continue; - - fd_quic_conn_t * quic_conn = - fd_quic_connect( ctx->quic, - conn->quic_ip_addr, - conn->quic_port, - ctx->src_ip_addr, - ctx->src_port, - now ); - if( FD_UNLIKELY( !quic_conn ) ) { - /* Should never happen, but handle it gracefully */ - return; - } - ctx->conns[ ctx->conns_len ].conn = quic_conn; - ctx->conns[ ctx->conns_len ].pubkey = *leader; - conn->quic_conn = quic_conn; - conn->quic_last_connected = now; - ctx->conns_len++; - } - } -} - -static void -send_vote_to_leader( fd_txsend_tile_t * ctx, - fd_pubkey_t const * leader_pubkey, - uchar const * vote_payload, - ulong vote_payload_sz ) { - peer_entry_t const * peer = peer_map_ele_query_const( ctx->peer_map, leader_pubkey, NULL, ctx->peers ); - if( FD_UNLIKELY( !peer ) ) return; /* no known contact info */ - - for( ulong i=0UL; i<2UL; i++ ) { - if( FD_UNLIKELY( !peer->udp_ip_addrs[ i ] | !peer->udp_ports[ i ] ) ) continue; - - fd_ip4_hdr_t * ip4_hdr = ctx->packet_hdr->ip4; - fd_udp_hdr_t * udp_hdr = ctx->packet_hdr->udp; - - ip4_hdr->daddr = peer->udp_ip_addrs[ i ]; - ip4_hdr->net_tot_len = fd_ushort_bswap( (ushort)(vote_payload_sz+sizeof(fd_ip4_hdr_t)+sizeof(fd_udp_hdr_t)) ); - ip4_hdr->net_id = fd_ushort_bswap( ctx->net_id++ ); - ip4_hdr->check = 0; - ip4_hdr->check = fd_ip4_hdr_check_fast( ip4_hdr ); - - udp_hdr->net_dport = fd_ushort_bswap( peer->udp_ports[ i ] ); - udp_hdr->net_len = fd_ushort_bswap( (ushort)( vote_payload_sz+sizeof(fd_udp_hdr_t) ) ); - send_to_net( ctx, ip4_hdr, udp_hdr, vote_payload, vote_payload_sz, fd_clock_tile_now( ctx->clock ) ); - } - - for( ulong i=0UL; i<2UL; i++ ) { - fd_quic_conn_t * conn = peer->quic_conns[ i ].quic_conn; - if( FD_UNLIKELY( !conn ) ) continue; - - fd_quic_stream_t * stream = fd_quic_conn_new_stream( conn ); - if( FD_UNLIKELY( !stream ) ) continue; - - fd_quic_stream_send( stream, vote_payload, vote_payload_sz, 1 ); - } -} - -/* gossip -> txsend peer synchronization - - The gossip update stream can be replayed to perfectly replicate the - ContactInfo table. The stream is laid out so there are no duplicate - pubkeys. - - However, the txsend tile wants to retain pubkeys past deletion. - Gossip evicts ContactInfos without updates quickly, but txsend should - continue sending to staked leaders even if there is a temporary - gossip outage. - - Therefore, the txsend tile only tombstones entries when the gossip - stream instructs to remove them, instead of deleting. The tombstone - handling is then resolved downstream during ContactInfo updates. */ - -static inline void -handle_contact_info_remove( fd_txsend_tile_t * ctx, - fd_gossip_update_message_t const * msg ) { - FD_TEST( msg->contact_info_remove->idx < FD_CONTACT_INFO_TABLE_SIZE ); - peer_entry_t * entry = &ctx->peers[ msg->contact_info_remove->idx ]; - entry->tombstoned = 1; -} - -static void -handle_contact_info_update( fd_txsend_tile_t * ctx, - fd_gossip_update_message_t const * msg ) { - FD_TEST( msg->contact_info->idx < FD_CONTACT_INFO_TABLE_SIZE ); - - /* Key updated by the gossip event */ - fd_pubkey_t key = FD_LOAD( fd_pubkey_t, msg->origin ); - - /* Storage entry updated by the gossip event */ - peer_entry_t * entry = &ctx->peers[ msg->contact_info->idx ]; - - /* At this point, entry contains an arbitrary old tombstoned entry or - a previous version of this key. */ - - if( FD_UNLIKELY( !fd_pubkey_eq( &entry->pubkey, &key ) ) ) { - /* Overwriting an unrelated (tombstoned) entry, free it */ - quic_conn_close( ctx, entry->quic_conns[ 0 ].quic_conn, 0 ); - quic_conn_close( ctx, entry->quic_conns[ 1 ].quic_conn, 0 ); - peer_map_ele_remove( ctx->peer_map, &entry->pubkey, NULL, ctx->peers ); - memset( entry, 0, sizeof(peer_entry_t) ); - } - - /* At this point, entry contains a stale version of the same key or is - empty. */ - - peer_entry_t * stale = peer_map_ele_query( ctx->peer_map, &key, NULL, ctx->peers ); - if( FD_UNLIKELY( stale && stale!=entry ) ) { - /* The key exists at another entry location, drop that and migrate - it to this slot. */ - for( ulong i=0UL; i<2UL; i++ ) { - entry->quic_conns [ i ] = stale->quic_conns [ i ]; - entry->udp_ip_addrs[ i ] = stale->udp_ip_addrs[ i ]; - entry->udp_ports [ i ] = stale->udp_ports [ i ]; - } - peer_map_ele_remove( ctx->peer_map, &stale->pubkey, NULL, ctx->peers ); - memset( stale, 0, sizeof(peer_entry_t) ); - fd_memcpy( entry->pubkey.uc, msg->origin, 32UL ); - FD_TEST( peer_map_ele_insert( ctx->peer_map, entry, ctx->peers ) ); - } else if( !stale ) { - fd_memcpy( entry->pubkey.uc, msg->origin, 32UL ); - FD_TEST( peer_map_ele_insert( ctx->peer_map, entry, ctx->peers ) ); - } - - entry->tombstoned = 0; - - static ulong const quic_socket_idx[ 2UL ] = { - FD_GOSSIP_CONTACT_INFO_SOCKET_TPU_VOTE_QUIC, - FD_GOSSIP_CONTACT_INFO_SOCKET_TPU_QUIC, - }; - - static ulong const udp_socket_idx[ 2UL ] = { - FD_GOSSIP_CONTACT_INFO_SOCKET_TPU_VOTE, - FD_GOSSIP_CONTACT_INFO_SOCKET_TPU, - }; - - /* At this point, entry is not in a map and *entry might still contain - stale endpoint info (from a previous update). Only overwrite it if - update actually contains endpoint info. */ - - for( ulong i=0UL; i<2UL; i++ ) { - if( FD_LIKELY( !msg->contact_info->value->sockets[ quic_socket_idx[ i ] ].is_ipv6 && msg->contact_info->value->sockets[ quic_socket_idx[ i ] ].ip4 ) ) { - entry->quic_conns[ i ].quic_ip_addr = msg->contact_info->value->sockets[ quic_socket_idx[ i ] ].ip4; - } - ushort port = fd_ushort_bswap( msg->contact_info->value->sockets[ quic_socket_idx[ i ] ].port ); - if( FD_LIKELY( port ) ) { - entry->quic_conns[ i ].quic_port = port; - } - } - - for( ulong i=0UL; i<2UL; i++ ) { - if( FD_LIKELY( !msg->contact_info->value->sockets[ udp_socket_idx[ i ] ].is_ipv6 && msg->contact_info->value->sockets[ udp_socket_idx[ i ] ].ip4 ) ) { - entry->udp_ip_addrs[ i ] = msg->contact_info->value->sockets[ udp_socket_idx[ i ] ].ip4; - } - if( FD_LIKELY( fd_ushort_bswap( msg->contact_info->value->sockets[ udp_socket_idx[ i ] ].port ) ) ) { - entry->udp_ports [ i ] = fd_ushort_bswap( msg->contact_info->value->sockets[ udp_socket_idx[ i ] ].port ); - } - } -} - -static void -report_signed_vote( uchar const * payload, - fd_txn_t const * txn, - uchar const * signatures, - ulong vote_txn_sz ) { - if( FD_LIKELY( !fd_event_tl ) ) return; - - if( FD_UNLIKELY( txn->instr_cnt!=1UL ) ) return; - if( FD_UNLIKELY( txn->acct_addr_cnt<3UL || txn->acct_addr_cnt>4UL ) ) return; - fd_txn_instr_t const * instr = &txn->instr[ 0 ]; - fd_acct_addr_t const * addrs = fd_txn_get_acct_addrs( txn, payload ); - if( FD_UNLIKELY( 0!=memcmp( addrs[ instr->program_id ].b, fd_solana_vote_program_id.uc, sizeof(fd_pubkey_t) ) ) ) return; - if( FD_UNLIKELY( instr->acct_cnt!=2UL ) ) return; - uchar const * instr_addrs = fd_txn_get_instr_accts( instr, payload ); - uchar const * instr_data = payload + instr->data_off; - ulong instr_data_sz = instr->data_sz; - if( FD_UNLIKELY( instr_data_szlockouts_cnt==0 || sync->lockouts_cnt>31 ) ) return; - - uchar const * rbh = fd_txn_get_recent_blockhash( txn, payload ); - - fd_event_signed_vote_t ev = {0}; - FD_TEST( vote_txn_sz<=sizeof(ev.signed_txn) ); - fd_memcpy( ev.signed_txn, payload, vote_txn_sz ); - ev.signed_txn_len = vote_txn_sz; - fd_memcpy( ev.vote_account, addrs[ instr_addrs[ 0 ] ].b, sizeof(fd_pubkey_t) ); - fd_memcpy( ev.vote_authority, addrs[ instr_addrs[ 1 ] ].b, sizeof(fd_pubkey_t) ); - fd_memcpy( ev.fee_payer, addrs[ 0 ].b, sizeof(fd_pubkey_t) ); - fd_memcpy( ev.signature, signatures, sizeof(fd_ed25519_sig_t) ); - fd_memcpy( ev.vote_bank_hash, sync->hash.uc, sizeof(fd_hash_t) ); - fd_memcpy( ev.vote_block_id, sync->block_id.uc, sizeof(fd_hash_t) ); - fd_memcpy( ev.txn_blockhash, rbh, sizeof(fd_hash_t) ); - - ulong root_slot = fd_ulong_if( sync->root==ULONG_MAX, 0UL, sync->root ); - ulong slot = root_slot; - for( ulong i=0UL; ilockouts_cnt; i++ ) { - slot += sync->lockouts[ i ].offset; - ev.tower[ i ].slot = slot; - ev.tower[ i ].confirmation_count = (uchar)sync->lockouts[ i ].confirmation_count; - } - ev.tower_cnt = sync->lockouts_cnt; - ev.vote_slot = slot; /* top of tower */ - - fd_event_report_signed_vote( &ev ); -} - -static void -handle_vote_msg( fd_txsend_tile_t * ctx, - fd_stem_context_t * stem, - fd_tower_slot_done_t const * slot_done ) { - if( FD_UNLIKELY( slot_done->vote_slot==ULONG_MAX ) ) return; - if( FD_UNLIKELY( !slot_done->has_vote_txn ) ) return; - - ctx->voted_slot = slot_done->vote_slot; - - fd_txn_m_t * txnm = fd_chunk_to_laddr( ctx->txsend_out->mem, ctx->txsend_out->chunk ); - FD_TEST( slot_done->vote_txn_sz<=FD_TXN_MTU ); - txnm->payload_sz = (ushort)slot_done->vote_txn_sz; - txnm->source_ipv4 = ctx->src_ip_addr; - txnm->source_tpu = FD_TXN_M_TPU_SOURCE_TXSEND; - txnm->block_engine.bundle_id = 0UL; - txnm->first_seen_nanos = slot_done->vote_created_nanos; - fd_memcpy( fd_txn_m_payload( txnm ), slot_done->vote_txn, slot_done->vote_txn_sz ); - - uchar txn_mem[ FD_TXN_MAX_SZ ] __attribute__((aligned(alignof(fd_txn_t)))); - txnm->txn_t_sz = (ushort)fd_txn_parse( slot_done->vote_txn, slot_done->vote_txn_sz, txn_mem, NULL ); - FD_TEST( txnm->txn_t_sz ); - - uchar * payload = fd_txn_m_payload( txnm ); - fd_txn_t const * txn = (fd_txn_t const *)txn_mem; - - uchar * signatures = payload + txn->signature_off; - uchar const * message = payload + txn->message_off; - ulong message_sz = fd_txn_msg_sz( txn, slot_done->vote_txn_sz ); - fd_keyguard_client_vote_txn_sign( ctx->keyguard_client, signatures, slot_done->authority_idx, message, message_sz ); - - FD_BASE58_ENCODE_64_BYTES( signatures, vote_sig_b58 ); - FD_LOG_INFO(( "vote txn for slot %lu created: %s", slot_done->vote_slot, vote_sig_b58 )); - - report_signed_vote( payload, txn, signatures, slot_done->vote_txn_sz ); - - for( ulong i=0UL; i<3UL; i++ ) { - ulong target_slot = slot_done->vote_slot+1UL + i*FD_EPOCH_SLOTS_PER_ROTATION; - fd_pubkey_t const * leader = fd_multi_epoch_leaders_get_leader_for_slot( ctx->mleaders, target_slot ); - if( FD_UNLIKELY( !leader ) ) { - FD_LOG_WARNING(( "no leader found for slot %lu", target_slot )); - continue; - } - send_vote_to_leader( ctx, leader, payload, slot_done->vote_txn_sz ); - } - - ulong msg_sz = fd_txn_m_realized_footprint( txnm, 0, 0 ); - ulong tspub_comp = fd_frag_meta_ts_comp( fd_tickcount() ); - fd_stem_publish( stem, ctx->txsend_out->idx, 1UL, ctx->txsend_out->chunk, msg_sz, 0UL, 0UL, tspub_comp ); - ctx->txsend_out->chunk = fd_dcache_compact_next( ctx->txsend_out->chunk, msg_sz, ctx->txsend_out->chunk0, ctx->txsend_out->wmark ); - ctx->txsend_out_seq = stem->seqs[ ctx->txsend_out->idx ]; -} - - -static inline int -before_frag( fd_txsend_tile_t * ctx, - ulong in_idx, - ulong seq, - ulong sig ) { - fd_startup_gate_busy( ctx->startup_gate ); - - if( FD_LIKELY( ctx->in_kind[ in_idx ]==IN_KIND_TOWER ) ) ctx->tower_in_expect_seq = seq+1UL; - if( FD_UNLIKELY( ctx->halt_net_frags && ctx->in_kind[ in_idx ]==IN_KIND_NET ) ) return -1; - - if( FD_UNLIKELY( ctx->in_kind[ in_idx ]==IN_KIND_GOSSIP ) ) { - return sig!=FD_GOSSIP_UPDATE_TAG_CONTACT_INFO && sig!=FD_GOSSIP_UPDATE_TAG_CONTACT_INFO_REMOVE; - } else if( FD_UNLIKELY( ctx->in_kind[ in_idx ]==IN_KIND_TOWER ) ) { - return sig!=FD_TOWER_SIG_SLOT_DONE; - } - - return 0; -} - -static void -during_frag( fd_txsend_tile_t * ctx, - ulong in_idx, - ulong seq, - ulong sig, - ulong chunk, - ulong sz, - ulong ctl ) { - (void)seq; (void)sig; - - ctx->chunk = chunk; - - if( FD_UNLIKELY( ctx->in_kind[ in_idx ]==IN_KIND_EPOCH ) ) { - if( FD_UNLIKELY( chunkin[ in_idx ].chunk0 || chunk>ctx->in[ in_idx ].wmark ) ) - FD_LOG_ERR(( "chunk %lu %lu corrupt, not in range [%lu,%lu,%lu]", chunk, sz, ctx->in[in_idx].chunk0, ctx->in[in_idx].wmark, ctx->in[ in_idx ].mtu )); - - fd_epoch_info_msg_t const * msg = fd_chunk_to_laddr_const( ctx->in[ in_idx ].mem, chunk ); - FD_TEST( msg->staked_vote_cnt<=MAX_STAKE_WEIGHTS ); /* implicit sz verification since sz field on frag_meta too small */ - FD_TEST( msg->staked_id_cnt<=MAX_STAKE_WEIGHTS ); - } else { - if( FD_UNLIKELY( chunkin[ in_idx ].chunk0 || chunk>ctx->in[ in_idx ].wmark || sz>ctx->in[ in_idx ].mtu ) ) - FD_LOG_ERR(( "chunk %lu %lu corrupt, not in range [%lu,%lu,%lu]", chunk, sz, ctx->in[in_idx].chunk0, ctx->in[in_idx].wmark, ctx->in[ in_idx ].mtu )); - } - - if( FD_UNLIKELY( ctx->in_kind[ in_idx ]==IN_KIND_NET ) ) { - void const * src = fd_net_rx_translate_frag( &ctx->net_in_bounds[ in_idx ], chunk, ctl, sz ); - fd_memcpy( ctx->quic_buf, src, sz ); - } -} - -static void -after_frag( fd_txsend_tile_t * ctx, - ulong in_idx, - ulong seq, - ulong sig, - ulong sz, - ulong tsorig, - ulong tspub, - fd_stem_context_t * stem ) { - (void)seq; (void)sig; (void)tsorig; (void)tspub; - - if( FD_LIKELY( ctx->in_kind[ in_idx ]==IN_KIND_NET ) ) { - uchar * ip_packet = ctx->quic_buf+sizeof(fd_eth_hdr_t); - ulong ip_packet_sz = sz-sizeof(fd_eth_hdr_t); - fd_quic_process_packet( ctx->quic, ip_packet, ip_packet_sz, fd_clock_tile_now( ctx->clock ) ); - } else if( FD_UNLIKELY( ctx->in_kind[ in_idx ]==IN_KIND_GOSSIP ) ) { - if( FD_LIKELY( sig==FD_GOSSIP_UPDATE_TAG_CONTACT_INFO ) ) handle_contact_info_update( ctx, fd_chunk_to_laddr_const( ctx->in[ in_idx ].mem, ctx->chunk ) ); - else handle_contact_info_remove( ctx, fd_chunk_to_laddr_const( ctx->in[ in_idx ].mem, ctx->chunk ) ); - } else if( FD_UNLIKELY( ctx->in_kind[ in_idx ]==IN_KIND_TOWER ) ) { - handle_vote_msg( ctx, stem, fd_chunk_to_laddr_const( ctx->in[ in_idx ].mem, ctx->chunk ) ); - } else if( FD_UNLIKELY( ctx->in_kind[ in_idx ]==IN_KIND_EPOCH ) ) { - fd_multi_epoch_leaders_epoch_msg_init( ctx->mleaders, fd_chunk_to_laddr_const( ctx->in[ in_idx ].mem, ctx->chunk ) ); - fd_multi_epoch_leaders_stake_msg_fini( ctx->mleaders ); - ctx->leader_schedules++; - } else { - FD_LOG_ERR(( "unknown in_kind %d on link %lu", ctx->in_kind[ in_idx ], in_idx )); - } -} - -static void -privileged_init( fd_topo_t const * topo, - fd_topo_tile_t const * tile ) { - void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id ); - - FD_SCRATCH_ALLOC_INIT( l, scratch ); - fd_txsend_tile_t * ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_txsend_tile_t), sizeof(fd_txsend_tile_t) ); - - if( FD_UNLIKELY( !strcmp( tile->txsend.identity_key_path, "" ) ) ) - FD_LOG_ERR(( "identity_key_path not set" )); - - ctx->identity_key[ 0 ] = *(fd_pubkey_t const *)fd_type_pun_const( fd_keyload_load( tile->txsend.identity_key_path, /* pubkey only: */ 1 ) ); - - FD_TEST( fd_rng_secure( &ctx->seed, sizeof(ctx->seed) ) ); -} - -static inline fd_txsend_out_t -out1( fd_topo_t const * topo, - fd_topo_tile_t const * tile, - char const * name ) { - ulong idx = ULONG_MAX; - - for( ulong i=0UL; iout_cnt; i++ ) { - fd_topo_link_t const * link = &topo->links[ tile->out_link_id[ i ] ]; - if( !strcmp( link->name, name ) ) { - if( FD_UNLIKELY( idx!=ULONG_MAX ) ) FD_LOG_ERR(( "tile %s:%lu had multiple output links named %s but expected one", tile->name, tile->kind_id, name )); - idx = i; - } - } - - if( FD_UNLIKELY( idx==ULONG_MAX ) ) FD_LOG_ERR(( "tile %s:%lu had no output link named %s", tile->name, tile->kind_id, name )); - - void * mem = topo->workspaces[ topo->objs[ topo->links[ tile->out_link_id[ idx ] ].dcache_obj_id ].wksp_id ].wksp; - ulong chunk0 = fd_dcache_compact_chunk0( mem, topo->links[ tile->out_link_id[ idx ] ].dcache ); - ulong wmark = fd_dcache_compact_wmark ( mem, topo->links[ tile->out_link_id[ idx ] ].dcache, topo->links[ tile->out_link_id[ idx ] ].mtu ); - - return (fd_txsend_out_t){ .idx = idx, .mem = mem, .chunk0 = chunk0, .wmark = wmark, .chunk = chunk0 }; -} - -static void -unprivileged_init( fd_topo_t const * topo, - fd_topo_tile_t const * tile ) { - void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id ); - FD_SCRATCH_ALLOC_INIT( l, scratch ); - fd_txsend_tile_t * ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_txsend_tile_t), sizeof(fd_txsend_tile_t) ); - void * _quic = FD_SCRATCH_ALLOC_APPEND( l, fd_quic_align(), fd_quic_footprint( &quic_limits ) ); - void * _peer_map = FD_SCRATCH_ALLOC_APPEND( l, peer_map_align(), peer_map_footprint( 2UL*FD_CONTACT_INFO_TABLE_SIZE ) ); - - ctx->quic = fd_quic_join( fd_quic_new( _quic, &quic_limits ) ); - FD_TEST( ctx->quic ); - fd_clock_tile_init( ctx->clock ); - - ctx->leader_schedules = 0UL; - - ctx->mleaders = fd_multi_epoch_leaders_join( fd_multi_epoch_leaders_new( ctx->mleaders_mem ) ); - FD_TEST( ctx->mleaders ); - - ctx->peer_map = peer_map_join( peer_map_new( _peer_map, 2UL*FD_CONTACT_INFO_TABLE_SIZE, ctx->seed ) ); - FD_TEST( ctx->peer_map ); - - fd_aio_t * quic_tx_aio = fd_aio_join( fd_aio_new( ctx->quic_tx_aio, ctx, quic_tx_aio_send ) ); - FD_TEST( quic_tx_aio ); - fd_quic_set_aio_net_tx( ctx->quic, quic_tx_aio ); - - ctx->quic->config.role = FD_QUIC_ROLE_CLIENT; - ctx->quic->config.idle_timeout = 30e9L; - ctx->quic->config.ack_delay = 25e6L; - ctx->quic->config.keep_alive = 1; - ctx->quic->config.sign = quic_tls_cv_sign; - ctx->quic->config.sign_ctx = ctx; - fd_memcpy( ctx->quic->config.identity_public_key, ctx->identity_key, sizeof(ctx->identity_key) ); - - ctx->quic->cb.conn_final = quic_conn_final; - ctx->quic->cb.quic_ctx = ctx; - - FD_TEST( fd_quic_init( ctx->quic )); - - for( ulong i=0UL; ipeers[ i ] = (peer_entry_t){0}; - } - - ctx->conns_len = 0UL; - ctx->voted_slot = ULONG_MAX; - ctx->net_id = 0; - - ctx->src_ip_addr = tile->txsend.ip_addr; - ctx->src_port = tile->txsend.txsend_src_port; - fd_ip4_udp_hdr_init( ctx->packet_hdr, FD_TXN_MTU, ctx->src_ip_addr, ctx->src_port ); - - fd_startup_gate_init( ctx->startup_gate, topo, tile->in_cnt ); - - FD_TEST( tile->in_cntin_kind)/sizeof(ctx->in_kind[ 0 ]) ); - for( ulong i=0UL; iin_cnt; i++ ) { - fd_topo_link_t const * link = &topo->links[ tile->in_link_id[ i ] ]; - fd_topo_wksp_t const * link_wksp = &topo->workspaces[ topo->objs[ link->dcache_obj_id ].wksp_id ]; - - ctx->in[ i ].mem = link_wksp->wksp; - ctx->in[ i ].chunk0 = fd_dcache_compact_chunk0( ctx->in[ i ].mem, link->dcache ); - ctx->in[ i ].wmark = fd_dcache_compact_wmark ( ctx->in[ i ].mem, link->dcache, link->mtu ); - ctx->in[ i ].mtu = link->mtu; - - if( !strcmp( link->name, "net_txsend" ) ) { - fd_net_rx_bounds_init( &ctx->net_in_bounds[ i ], link->dcache ); - ctx->in_kind[ i ] = IN_KIND_NET; - } else if( !strcmp( link->name, "gossip_ciaddr" ) ) ctx->in_kind[ i ] = IN_KIND_GOSSIP; - else if( !strcmp( link->name, "replay_epoch" ) ) ctx->in_kind[ i ] = IN_KIND_EPOCH; - else if( !strcmp( link->name, "tower_out" ) ) ctx->in_kind[ i ] = IN_KIND_TOWER; - else if( !strcmp( link->name, "sign_txsend" ) ) ctx->in_kind[ i ] = IN_KIND_SIGN; - else FD_LOG_ERR(( "unexpected input link name %s", link->name )); - } - - *ctx->txsend_out = out1( topo, tile, "txsend_out" ); - *ctx->net_out = out1( topo, tile, "txsend_net" ); - - ulong sign_in_idx = fd_topo_find_tile_in_link ( topo, tile, "sign_txsend", tile->kind_id ); - ulong sign_out_idx = fd_topo_find_tile_out_link( topo, tile, "txsend_sign", tile->kind_id ); - FD_TEST( sign_in_idx!=ULONG_MAX ); - fd_topo_link_t const * sign_in = &topo->links[ tile->in_link_id[ sign_in_idx ] ]; - fd_topo_link_t const * sign_out = &topo->links[ tile->out_link_id[ sign_out_idx ] ]; - - fd_sleep_t * sleep = NULL; - if( FD_UNLIKELY( topo->sleep_obj_id!=ULONG_MAX ) ) { - sleep = fd_sleep_join( fd_topo_obj_laddr( topo, topo->sleep_obj_id ) ); - FD_TEST( sleep ); - } - - if( FD_UNLIKELY( !fd_keyguard_client_join( fd_keyguard_client_new( ctx->keyguard_client, - sign_out->mcache, - sign_out->dcache, - sign_in->mcache, - sign_in->dcache, - sign_out->mtu, - sign_in->mtu, - sleep, - sign_out->id, - fd_topo_find_link_consumer( topo, sign_out ) ) ) ) ) { - FD_LOG_ERR(( "failed to construct keyguard" )); - } - - ctx->keyswitch = fd_keyswitch_join( fd_topo_obj_laddr( topo, tile->id_keyswitch_obj_id ) ); - FD_TEST( ctx->keyswitch ); - - ctx->av_keyswitch = NULL; - if( FD_UNLIKELY( tile->av_keyswitch_obj_id!=ULONG_MAX ) ) { - ctx->av_keyswitch = fd_keyswitch_join( fd_topo_obj_laddr( topo, tile->av_keyswitch_obj_id ) ); - FD_TEST( ctx->av_keyswitch ); - } - - ctx->tower_in_expect_seq = 0UL; - ctx->txsend_out_seq = 0UL; - ctx->halt_net_frags = 0; - - fd_histf_join( fd_metrics_histf_new_ns( ctx->quic->metrics.service_duration, FD_MHIST_EDGES_NS( TXSEND, SERVICE_DURATION_NANOS ) ) ); - fd_histf_join( fd_metrics_histf_new_ns( ctx->quic->metrics.receive_duration, FD_MHIST_EDGES_NS( TXSEND, PKT_RX_DURATION_NANOS ) ) ); - - 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 ulong -populate_allowed_seccomp( fd_topo_t const * topo FD_PARAM_UNUSED, - fd_topo_tile_t const * tile FD_PARAM_UNUSED, - ulong out_cnt, - struct sock_filter * out ) { - - populate_sock_filter_policy_fd_txsend_tile( out_cnt, out, (uint)fd_log_private_logfile_fd() ); - return sock_filter_policy_fd_txsend_tile_instr_cnt; -} - -static ulong -populate_allowed_fds( fd_topo_t const * topo FD_PARAM_UNUSED, - fd_topo_tile_t const * tile FD_PARAM_UNUSED, - ulong out_fds_cnt, - int * out_fds ) { - if( FD_UNLIKELY( out_fds_cnt<2UL ) ) FD_LOG_ERR(( "out_fds_cnt %lu", out_fds_cnt )); - - ulong out_cnt = 0; - out_fds[ out_cnt++ ] = 2UL; /* stderr */ - if( FD_LIKELY( -1!=fd_log_private_logfile_fd() ) ) - out_fds[ out_cnt++ ] = fd_log_private_logfile_fd(); /* logfile */ - return out_cnt; -} - -#define STEM_BURST 1UL -#define STEM_LAZY (128L*3000L) - -#define STEM_CALLBACK_CONTEXT_TYPE fd_txsend_tile_t -#define STEM_CALLBACK_CONTEXT_ALIGN alignof(fd_txsend_tile_t) - -#define STEM_CALLBACK_DURING_HOUSEKEEPING during_housekeeping -#define STEM_CALLBACK_NEXT_DEADLINE next_deadline -#define STEM_CALLBACK_METRICS_WRITE metrics_write -#define STEM_CALLBACK_AFTER_CREDIT after_credit -#define STEM_CALLBACK_BEFORE_FRAG before_frag -#define STEM_CALLBACK_DURING_FRAG during_frag -#define STEM_CALLBACK_AFTER_FRAG after_frag - -#include "../../disco/stem/fd_stem.c" - -static ulong -max_event_sz( fd_topo_tile_t const * tile FD_PARAM_UNUSED ) { - return sizeof(fd_event_signed_vote_t); -} - -fd_topo_run_tile_t fd_tile_txsend = { - .name = "txsend", - .max_event_sz = max_event_sz, - .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/txsend/fd_txsend_tile.h b/src/discof/txsend/fd_txsend_tile.h deleted file mode 100644 index 70767c768b7..00000000000 --- a/src/discof/txsend/fd_txsend_tile.h +++ /dev/null @@ -1,159 +0,0 @@ -#ifndef HEADER_fd_src_discof_txsend_fd_txsend_tile_h -#define HEADER_fd_src_discof_txsend_fd_txsend_tile_h - -#include "../../waltz/quic/fd_quic.h" -#include "../../flamenco/progcache/fd_progcache_xid.h" -#include "../../flamenco/leaders/fd_multi_epoch_leaders.h" -#include "../../flamenco/gossip/fd_gossip_message.h" -#include "../../disco/stem/fd_stem.h" -#include "../../disco/fd_clock_tile.h" -#include "../../disco/net/fd_net_tile.h" -#include "../../disco/keyguard/fd_keyguard_client.h" -#include "../../disco/keyguard/fd_keyswitch.h" -#include "../../util/net/fd_net_headers.h" -#include "../fd_startup.h" - -struct fd_txsend_in { - fd_wksp_t * mem; - ulong chunk0; - ulong wmark; - ulong mtu; -}; - -typedef struct fd_txsend_in fd_txsend_in_t; - -struct fd_txsend_out { - ulong idx; - fd_wksp_t * mem; - ulong chunk0; - ulong wmark; - ulong chunk; -}; - -typedef struct fd_txsend_out fd_txsend_out_t; - -/* QUIC conn table state management - - fd_quic_conn_t objects are managed by fd_quic_t. - peer_map holds pointers to quic_conns (up to 2 conns per peer). - The lifetime of these must be synchronized with quic_conn state. - - This implies the following: - - fd_quic must inform txsend via a conn_final callback before freeing - a conn object - - in rare cases, txsend may see conn alloc failures even if peer_map - has free conn slots - - The following procedure closes a conn: - - call fd_quic_conn_close (enqueues an immediate conn close) - - deregister the conn from the table - - call fd_quic_service (sends out CONN_CLOSE packet, conn_final - callback, frees conn) */ - -struct txsend_conn { - uint quic_ip_addr; - ushort quic_port; - fd_quic_conn_t * quic_conn; - long quic_last_connected; -}; -typedef struct txsend_conn txsend_conn_t; - -struct peer_entry { - /* Key */ - fd_pubkey_t pubkey; - - /* State */ - txsend_conn_t quic_conns[ 2UL ]; - uint udp_ip_addrs[ 2UL ]; - ushort udp_ports[ 2UL ]; - int tombstoned; - - struct { - ulong next; - } map; -}; - -typedef struct peer_entry peer_entry_t; - -#define MAP_NAME peer_map -#define MAP_KEY pubkey -#define MAP_ELE_T peer_entry_t -#define MAP_KEY_T fd_pubkey_t -#define MAP_NEXT map.next -#define MAP_KEY_EQ(k0,k1) fd_pubkey_eq( k0, k1 ) -#define MAP_KEY_HASH(key,seed) fd_progcache_rec_key_hash1( (key)->uc, (seed) ) -#define MAP_IMPL_STYLE 1 -#include "../../util/tmpl/fd_map_chain.c" - -struct quic_entry { - fd_quic_conn_t * conn; - fd_pubkey_t pubkey; -}; - -typedef struct quic_entry quic_entry_t; - -/* txsend tile data structure - - notable data structures: - - the quic instance manages quic_conns - - the conns array is a list of pointers to quic_conns, kept in sync - with quic instance - - the peers table is replicated from gossip ContactInfo updates - - the peer_map hashmap maps pubkey to peers[i] entry; drift is - tolerated - - peers[i] contains pointers to quic_conn */ - -struct fd_txsend_tile { - fd_quic_t * quic; - - fd_clock_tile_t clock[1]; - - ulong leader_schedules; - fd_multi_epoch_leaders_t * mleaders; - - ulong seed; - peer_map_t * peer_map; - - peer_entry_t peers[ FD_CONTACT_INFO_TABLE_SIZE ]; - - ulong conns_len; - quic_entry_t conns[ 128UL ]; - - ulong voted_slot; - - fd_stem_context_t * stem; - - ulong chunk; - uchar quic_buf[ FD_NET_MTU ]; - - uint src_ip_addr; - ushort src_port; - fd_ip4_udp_hdrs_t packet_hdr[1]; /* template, but will be modified directly */ - ushort net_id; - - fd_aio_t quic_tx_aio[1]; - - int in_kind[ 32UL ]; - fd_txsend_in_t in[ 32UL ]; - fd_net_rx_bounds_t net_in_bounds[ 64UL ]; - - fd_txsend_out_t txsend_out[1]; - fd_txsend_out_t net_out[1]; - - fd_keyswitch_t * keyswitch; - fd_keyswitch_t * av_keyswitch; - ulong tower_in_expect_seq; - ulong txsend_out_seq; - int halt_net_frags; - - fd_startup_gate_t startup_gate[1]; - - fd_pubkey_t identity_key[1]; - fd_keyguard_client_t keyguard_client[1]; - - uchar __attribute__((aligned(FD_MULTI_EPOCH_LEADERS_ALIGN))) mleaders_mem[ FD_MULTI_EPOCH_LEADERS_FOOTPRINT ]; -}; - -typedef struct fd_txsend_tile fd_txsend_tile_t; - -#endif /* HEADER_fd_src_discof_txsend_fd_txsend_tile_h */ diff --git a/src/discof/txsend/fd_txsend_tile.seccomppolicy b/src/discof/txsend/fd_txsend_tile.seccomppolicy deleted file mode 100644 index 7ca6c85dd73..00000000000 --- a/src/discof/txsend/fd_txsend_tile.seccomppolicy +++ /dev/null @@ -1,43 +0,0 @@ -# logfile_fd: It can be disabled by configuration, but typically tiles -# will open a log file on boot and write all messages there. -uint logfile_fd - -# logging: all log messages are written to a file and/or pipe -# -# 'WARNING' and above are written to the STDERR pipe, while all messages -# are always written to the log file. -# -# arg 0 is the file descriptor to write to. The boot process ensures -# that descriptor 2 is always STDERR. -write: (or (eq (arg 0) 2) - (eq (arg 0) logfile_fd)) - -# logging: 'WARNING' and above fsync the logfile to disk immediately -# -# arg 0 is the file descriptor to fsync. -fsync: (eq (arg 0) logfile_fd) - -# QUIC uses getrandom for cryptographically secure randomness. -getrandom - -# startup: sleep until snapshot loaded -clock_nanosleep: (eq (arg 0) CLOCK_REALTIME) - -# power saving: sleep when there is no work to do -# -# In the efficient scheduler mode, an idle tile parks on its sleep word -# with futex_wait until a producer has work for it or its next deadline -# passes. -# -# arg 1 is the futex op, always FUTEX_WAIT_BITSET so the timeout is an -# absolute deadline. arg 2 is the expected value of the word, always 0 -# (parked). -futex: (and (eq (arg 1) FUTEX_WAIT_BITSET) - (eq (arg 2) 0)) - -# power saving: resume an interrupted sleep -# -# A signal delivered to a parked tile interrupts its futex_wait, and the -# kernel resumes the wait with the remaining timeout via restart_syscall -# when the tile next runs. -restart_syscall diff --git a/src/discof/txsend/generated/fd_txsend_tile_seccomp.h b/src/discof/txsend/generated/fd_txsend_tile_seccomp.h deleted file mode 100644 index 59f638c7c23..00000000000 --- a/src/discof/txsend/generated/fd_txsend_tile_seccomp.h +++ /dev/null @@ -1,110 +0,0 @@ -/* THIS FILE WAS GENERATED BY generate_filters.py. DO NOT EDIT BY HAND! */ -#ifndef HEADER_fd_src_discof_txsend_generated_fd_txsend_tile_seccomp_h -#define HEADER_fd_src_discof_txsend_generated_fd_txsend_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 -#elif defined(__riscv) && __riscv_xlen == 64 -# define ARCH_NR AUDIT_ARCH_RISCV64 -#else -# error "Target architecture is unsupported by seccomp." -#endif - -#define FD_SECCOMP_ARG_LO_OFFSET(argno) ( offsetof( struct seccomp_data, args[(argno)] ) ) -#define FD_SECCOMP_ARG_HI_OFFSET(argno) ( offsetof( struct seccomp_data, args[(argno)] ) + 4U ) - -#define FD_SECCOMP_ARG_LO(x) ((uint)(((ulong)(uint)(int)(x) ) & 0xffffffffUL)) -#define FD_SECCOMP_ARG_HI(x) ((uint)(((ulong)(x) >> 32) & 0xffffffffUL)) - -static const uint sock_filter_policy_fd_txsend_tile_instr_cnt = 31; - -static void populate_sock_filter_policy_fd_txsend_tile( ulong out_cnt, struct sock_filter out[ static 31 ], uint logfile_fd ) { - FD_TEST( out_cnt >= 31 ); - struct sock_filter filter[31] = { - /* validate architecture */ - 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 */ 7 ), - /* load syscall number */ - BPF_STMT( BPF_LD | BPF_W | BPF_ABS, ( offsetof( struct seccomp_data, nr ) )), - /* check write */ - BPF_JUMP( BPF_JMP | BPF_JEQ | BPF_K, SYS_write, /* check_write */ 7, 0 ), - /* check fsync */ - BPF_JUMP( BPF_JMP | BPF_JEQ | BPF_K, SYS_fsync, /* check_fsync */ 12, 0 ), - /* allow getrandom */ - BPF_JUMP( BPF_JMP | BPF_JEQ | BPF_K, SYS_getrandom, /* RET_ALLOW */ 4, 0 ), - /* check clock_nanosleep */ - BPF_JUMP( BPF_JMP | BPF_JEQ | BPF_K, SYS_clock_nanosleep, /* check_clock_nanosleep */ 14, 0 ), - /* check futex */ - BPF_JUMP( BPF_JMP | BPF_JEQ | BPF_K, SYS_futex, /* check_futex */ 17, 0 ), - /* allow restart_syscall */ - BPF_JUMP( BPF_JMP | BPF_JEQ | BPF_K, SYS_restart_syscall, /* RET_ALLOW */ 1, 0 ), -// RET_KILL_PROCESS: - /* default deny */ - BPF_STMT( BPF_RET | BPF_K, SECCOMP_RET_KILL_PROCESS ), -// RET_ALLOW: - /* allow */ - BPF_STMT( BPF_RET | BPF_K, SECCOMP_RET_ALLOW ), -// check_write: - /* arg 0 low 32 bits */ - BPF_STMT( BPF_LD | BPF_W | BPF_ABS, FD_SECCOMP_ARG_LO_OFFSET(0)), - BPF_JUMP( BPF_JMP | BPF_JEQ | BPF_K, 0x00000002U, /* write_ALLOW */ 3, /* or_1 */ 0 ), -// or_1: - /* arg 0 low 32 bits */ - BPF_STMT( BPF_LD | BPF_W | BPF_ABS, FD_SECCOMP_ARG_LO_OFFSET(0)), - BPF_JUMP( BPF_JMP | BPF_JEQ | BPF_K, ((uint)(logfile_fd)), /* write_ALLOW */ 1, /* write_KILL */ 0 ), -// write_KILL: - BPF_STMT( BPF_RET | BPF_K, SECCOMP_RET_KILL_PROCESS ), -// write_ALLOW: - BPF_STMT( BPF_RET | BPF_K, SECCOMP_RET_ALLOW ), -// check_fsync: - /* arg 0 low 32 bits */ - BPF_STMT( BPF_LD | BPF_W | BPF_ABS, FD_SECCOMP_ARG_LO_OFFSET(0)), - BPF_JUMP( BPF_JMP | BPF_JEQ | BPF_K, ((uint)(logfile_fd)), /* fsync_ALLOW */ 1, /* fsync_KILL */ 0 ), -// fsync_KILL: - BPF_STMT( BPF_RET | BPF_K, SECCOMP_RET_KILL_PROCESS ), -// fsync_ALLOW: - BPF_STMT( BPF_RET | BPF_K, SECCOMP_RET_ALLOW ), -// check_clock_nanosleep: - /* arg 0 low 32 bits */ - BPF_STMT( BPF_LD | BPF_W | BPF_ABS, FD_SECCOMP_ARG_LO_OFFSET(0)), - BPF_JUMP( BPF_JMP | BPF_JEQ | BPF_K, FD_SECCOMP_ARG_LO(CLOCK_REALTIME), /* clock_nanosleep_ALLOW */ 1, /* clock_nanosleep_KILL */ 0 ), -// clock_nanosleep_KILL: - BPF_STMT( BPF_RET | BPF_K, SECCOMP_RET_KILL_PROCESS ), -// clock_nanosleep_ALLOW: - BPF_STMT( BPF_RET | BPF_K, SECCOMP_RET_ALLOW ), -// check_futex: - /* arg 1 low 32 bits */ - BPF_STMT( BPF_LD | BPF_W | BPF_ABS, FD_SECCOMP_ARG_LO_OFFSET(1)), - BPF_JUMP( BPF_JMP | BPF_JEQ | BPF_K, FD_SECCOMP_ARG_LO(FUTEX_WAIT_BITSET), /* and_2 */ 0, /* futex_KILL */ 2 ), -// and_2: - /* arg 2 low 32 bits */ - BPF_STMT( BPF_LD | BPF_W | BPF_ABS, FD_SECCOMP_ARG_LO_OFFSET(2)), - BPF_JUMP( BPF_JMP | BPF_JEQ | BPF_K, 0x00000000U, /* futex_ALLOW */ 1, /* futex_KILL */ 0 ), -// futex_KILL: - BPF_STMT( BPF_RET | BPF_K, SECCOMP_RET_KILL_PROCESS ), -// futex_ALLOW: - BPF_STMT( BPF_RET | BPF_K, SECCOMP_RET_ALLOW ), - }; - fd_memcpy( out, filter, sizeof( filter ) ); -} - -#endif /* defined(__linux__) */ - -#endif /* HEADER_fd_src_discof_txsend_generated_fd_txsend_tile_seccomp_h */ From 17b0d787fe28f8fc5f2c9d6b84a977d8e0422bcb Mon Sep 17 00:00:00 2001 From: Richard Patel Date: Wed, 21 Oct 2026 13:42:55 +0000 Subject: [PATCH 07/13] remove repair tile --- book/api/metrics-generated.md | 39 - src/disco/metrics/generated/fd_metrics_all.c | 36 - src/disco/metrics/generated/fd_metrics_all.h | 3 +- .../metrics/generated/fd_metrics_repair.h | 164 -- .../metrics/generated/fd_metrics_sample_gen.c | 2 +- src/disco/metrics/metrics.xml | 29 - src/discof/repair/Local.mk | 11 - src/discof/repair/fd_inflight.c | 230 --- src/discof/repair/fd_inflight.h | 317 ---- src/discof/repair/fd_policy.c | 444 ------ src/discof/repair/fd_policy.h | 241 --- src/discof/repair/fd_repair_metrics.c | 215 --- src/discof/repair/fd_repair_metrics.h | 73 - src/discof/repair/fd_repair_tile.c | 1383 ----------------- src/discof/repair/fd_repair_tile.h | 62 - .../repair/fd_repair_tile.seccomppolicy | 37 - src/discof/repair/fd_repair_tile_private.h | 280 ---- src/discof/repair/fd_reqlim.c | 129 -- src/discof/repair/fd_reqlim.h | 135 -- .../repair/generated/fd_repair_tile_seccomp.h | 98 -- src/discof/repair/test_policy.c | 1089 ------------- src/discof/repair/test_repair_tile.c | 1004 ------------ 22 files changed, 2 insertions(+), 6019 deletions(-) delete mode 100644 src/disco/metrics/generated/fd_metrics_repair.h delete mode 100644 src/discof/repair/fd_inflight.c delete mode 100644 src/discof/repair/fd_inflight.h delete mode 100644 src/discof/repair/fd_policy.c delete mode 100644 src/discof/repair/fd_policy.h delete mode 100644 src/discof/repair/fd_repair_metrics.c delete mode 100644 src/discof/repair/fd_repair_metrics.h delete mode 100644 src/discof/repair/fd_repair_tile.c delete mode 100644 src/discof/repair/fd_repair_tile.h delete mode 100644 src/discof/repair/fd_repair_tile.seccomppolicy delete mode 100644 src/discof/repair/fd_repair_tile_private.h delete mode 100644 src/discof/repair/fd_reqlim.c delete mode 100644 src/discof/repair/fd_reqlim.h delete mode 100644 src/discof/repair/generated/fd_repair_tile_seccomp.h delete mode 100644 src/discof/repair/test_policy.c delete mode 100644 src/discof/repair/test_repair_tile.c diff --git a/book/api/metrics-generated.md b/book/api/metrics-generated.md index 6fdc3e71a7a..7145b53af78 100644 --- a/book/api/metrics-generated.md +++ b/book/api/metrics-generated.md @@ -960,45 +960,6 @@
-## Repair Tile - -
- -| Metric | Type | Description | -|--------|------|-------------| -| repair_​pkt_​tx | counter | Network packets sent, including reqs, pings, pongs, etc | -| repair_​request_​tx
{repair_​request_​type="pong"} | counter | Repair requests sent, by type (Pong) | -| repair_​request_​tx
{repair_​request_​type="window_​index"} | counter | Repair requests sent, by type (Shred at an index) | -| repair_​request_​tx
{repair_​request_​type="highest_​window_​index"} | counter | Repair requests sent, by type (Highest shred at or above an index) | -| repair_​request_​tx
{repair_​request_​type="orphan"} | counter | Repair requests sent, by type (Ancestors of an orphan slot) | -| repair_​request_​tx
{repair_​request_​type="parent_​fec_​set_​count"} | counter | Repair requests sent, by type (Parent and FEC set count of a block (Alpenglow)) | -| repair_​request_​tx
{repair_​request_​type="fec_​set_​root"} | counter | Repair requests sent, by type (Merkle root of a FEC set (Alpenglow)) | -| repair_​request_​tx
{repair_​request_​type="window_​index_​for_​block_​id"} | counter | Repair requests sent, by type (Shred at an index of a specific block (Alpenglow)) | -| repair_​highest_​repaired_​slot | gauge | Highest slot on the repair frontier, 0 if none; its ancestors are complete, the slot itself may not be | -| repair_​highest_​turbine_​slot | gauge | Highest slot a turbine shred was received for, 0 if none | -| repair_​shred_​old_​ignored | counter | Shreds received older than the root, ignored | -| repair_​last_​requested_​slot | gauge | Last slot we requested a single shred for, 0 if none | -| repair_​last_​requested_​orphan_​slot | gauge | Last slot we requested an orphan for, 0 if none | -| repair_​request_​in_​flight | gauge | Requests awaiting a response, excluding orphan and highest window requests | -| repair_​peer_​count | gauge | Repair peers currently tracked | -| repair_​sign_​backpressure | counter | Loop iterations where no sign tile was available to sign a request | -| repair_​turbine_​grace_​elapsed | counter | Times a missing shred outlived the turbine grace period and became eligible for repair | -| repair_​shred_​rerequested | counter | Times we re-requested a shred from the inflights queue | -| repair_​ping_​rejected
{ping_​reject_​reason="malformed"} | counter | Pings rejected, by reason (Failed to decode) | -| repair_​ping_​rejected
{ping_​reject_​reason="unknown_​peer"} | counter | Pings rejected, by reason (Sender is not a known peer) | -| repair_​ping_​rejected
{ping_​reject_​reason="signature_​failed"} | counter | Pings rejected, by reason (Signature did not verify) | -| repair_​slot_​complete_​duration_​nanos | histogram | Time from a slot's first shred (first request for catchup slots) until all its data shreds are received | -| repair_​response_​latency_​nanos | histogram | Time from sending a repair request to receiving its response | -| repair_​block_​evicted | counter | Blocks evicted from the forest | -| repair_​block_​insert_​failed | counter | Blocks we failed to insert into the forest due to failed eviction | -| repair_​last_​evicted_​slot | gauge | Most recent slot evicted from the forest, 0 if none | -| repair_​last_​eviction_​cause_​slot | gauge | Slot that caused the most recent forest eviction, 0 if none | -| repair_​last_​insert_​failed_​slot | gauge | Most recent slot we failed to insert into the forest, 0 if none | -| repair_​chain_​verify_​failed | counter | Times any block failed chained merkle verification | -| repair_​last_​chain_​verify_​failed_​slot | gauge | Most recent slot that failed chained merkle verification, 0 if none | - -
- ## Rserve Tile
diff --git a/src/disco/metrics/generated/fd_metrics_all.c b/src/disco/metrics/generated/fd_metrics_all.c index 60f9d872459..a13f97d6b2b 100644 --- a/src/disco/metrics/generated/fd_metrics_all.c +++ b/src/disco/metrics/generated/fd_metrics_all.c @@ -808,39 +808,6 @@ const fd_metrics_meta_t FD_METRICS_GOSSIP[FD_METRICS_GOSSIP_TOTAL] = { DECLARE_METRIC( GOSSIP_WAIT_FOR_SUPERMAJORITY_STATE, GAUGE ), }; -const fd_metrics_meta_t FD_METRICS_REPAIR[FD_METRICS_REPAIR_TOTAL] = { - DECLARE_METRIC( REPAIR_PKT_TX, COUNTER ), - DECLARE_METRIC_ENUM( REPAIR_REQUEST_TX, COUNTER, REPAIR_REQUEST_TYPE, PONG ), - DECLARE_METRIC_ENUM( REPAIR_REQUEST_TX, COUNTER, REPAIR_REQUEST_TYPE, WINDOW_INDEX ), - DECLARE_METRIC_ENUM( REPAIR_REQUEST_TX, COUNTER, REPAIR_REQUEST_TYPE, HIGHEST_WINDOW_INDEX ), - DECLARE_METRIC_ENUM( REPAIR_REQUEST_TX, COUNTER, REPAIR_REQUEST_TYPE, ORPHAN ), - DECLARE_METRIC_ENUM( REPAIR_REQUEST_TX, COUNTER, REPAIR_REQUEST_TYPE, PARENT_FEC_SET_COUNT ), - DECLARE_METRIC_ENUM( REPAIR_REQUEST_TX, COUNTER, REPAIR_REQUEST_TYPE, FEC_SET_ROOT ), - DECLARE_METRIC_ENUM( REPAIR_REQUEST_TX, COUNTER, REPAIR_REQUEST_TYPE, WINDOW_INDEX_FOR_BLOCK_ID ), - DECLARE_METRIC( REPAIR_HIGHEST_REPAIRED_SLOT, GAUGE ), - DECLARE_METRIC( REPAIR_HIGHEST_TURBINE_SLOT, GAUGE ), - DECLARE_METRIC( REPAIR_SHRED_OLD_IGNORED, COUNTER ), - DECLARE_METRIC( REPAIR_LAST_REQUESTED_SLOT, GAUGE ), - DECLARE_METRIC( REPAIR_LAST_REQUESTED_ORPHAN_SLOT, GAUGE ), - DECLARE_METRIC( REPAIR_REQUEST_IN_FLIGHT, GAUGE ), - DECLARE_METRIC( REPAIR_PEER_COUNT, GAUGE ), - DECLARE_METRIC( REPAIR_SIGN_BACKPRESSURE, COUNTER ), - DECLARE_METRIC( REPAIR_TURBINE_GRACE_ELAPSED, COUNTER ), - DECLARE_METRIC( REPAIR_SHRED_REREQUESTED, COUNTER ), - DECLARE_METRIC_ENUM( REPAIR_PING_REJECTED, COUNTER, PING_REJECT_REASON, MALFORMED ), - DECLARE_METRIC_ENUM( REPAIR_PING_REJECTED, COUNTER, PING_REJECT_REASON, UNKNOWN_PEER ), - DECLARE_METRIC_ENUM( REPAIR_PING_REJECTED, COUNTER, PING_REJECT_REASON, SIGNATURE_FAILED ), - DECLARE_METRIC_HISTOGRAM_TICKS( REPAIR_SLOT_COMPLETE_DURATION_NANOS ), - DECLARE_METRIC_HISTOGRAM_NONE( REPAIR_RESPONSE_LATENCY_NANOS ), - DECLARE_METRIC( REPAIR_BLOCK_EVICTED, COUNTER ), - DECLARE_METRIC( REPAIR_BLOCK_INSERT_FAILED, COUNTER ), - DECLARE_METRIC( REPAIR_LAST_EVICTED_SLOT, GAUGE ), - DECLARE_METRIC( REPAIR_LAST_EVICTION_CAUSE_SLOT, GAUGE ), - DECLARE_METRIC( REPAIR_LAST_INSERT_FAILED_SLOT, GAUGE ), - DECLARE_METRIC( REPAIR_CHAIN_VERIFY_FAILED, COUNTER ), - DECLARE_METRIC( REPAIR_LAST_CHAIN_VERIFY_FAILED_SLOT, GAUGE ), -}; - const fd_metrics_meta_t FD_METRICS_RSERVE[FD_METRICS_RSERVE_TOTAL] = { DECLARE_METRIC_ENUM( RSERVE_REQUEST_RX, COUNTER, REPAIR_REQUEST_TYPE, PONG ), DECLARE_METRIC_ENUM( RSERVE_REQUEST_RX, COUNTER, REPAIR_REQUEST_TYPE, WINDOW_INDEX ), @@ -2101,7 +2068,6 @@ const char * FD_METRICS_TILE_KIND_NAMES[FD_METRICS_TILE_KIND_CNT] = { "shred", "gossvf", "gossip", - "repair", "rserve", "replay", "execrp", @@ -2150,7 +2116,6 @@ const ulong FD_METRICS_TILE_KIND_SIZES[FD_METRICS_TILE_KIND_CNT] = { FD_METRICS_SHRED_TOTAL, FD_METRICS_GOSSVF_TOTAL, FD_METRICS_GOSSIP_TOTAL, - FD_METRICS_REPAIR_TOTAL, FD_METRICS_RSERVE_TOTAL, FD_METRICS_REPLAY_TOTAL, FD_METRICS_EXECRP_TOTAL, @@ -2198,7 +2163,6 @@ const fd_metrics_meta_t * FD_METRICS_TILE_KIND_METRICS[FD_METRICS_TILE_KIND_CNT] FD_METRICS_SHRED, FD_METRICS_GOSSVF, FD_METRICS_GOSSIP, - FD_METRICS_REPAIR, FD_METRICS_RSERVE, FD_METRICS_REPLAY, FD_METRICS_EXECRP, diff --git a/src/disco/metrics/generated/fd_metrics_all.h b/src/disco/metrics/generated/fd_metrics_all.h index 8ab82aea273..fc3cc1ea03d 100644 --- a/src/disco/metrics/generated/fd_metrics_all.h +++ b/src/disco/metrics/generated/fd_metrics_all.h @@ -74,7 +74,6 @@ enum { #include "fd_metrics_shred.h" #include "fd_metrics_accdb.h" #include "fd_metrics_replay.h" -#include "fd_metrics_repair.h" #include "fd_metrics_rotor.h" #include "fd_metrics_rserve.h" #include "fd_metrics_gossip.h" @@ -253,7 +252,7 @@ extern const fd_metrics_meta_t FD_METRICS_ALL_LINK_IN[FD_METRICS_ALL_LINK_IN_TOT #define FD_METRICS_TOTAL_SZ (8UL*301UL) -#define FD_METRICS_TILE_KIND_CNT 46 +#define FD_METRICS_TILE_KIND_CNT 45 extern const char * FD_METRICS_TILE_KIND_NAMES[FD_METRICS_TILE_KIND_CNT]; extern const ulong FD_METRICS_TILE_KIND_SIZES[FD_METRICS_TILE_KIND_CNT]; extern const fd_metrics_meta_t * FD_METRICS_TILE_KIND_METRICS[FD_METRICS_TILE_KIND_CNT]; diff --git a/src/disco/metrics/generated/fd_metrics_repair.h b/src/disco/metrics/generated/fd_metrics_repair.h deleted file mode 100644 index 0a88c4b4405..00000000000 --- a/src/disco/metrics/generated/fd_metrics_repair.h +++ /dev/null @@ -1,164 +0,0 @@ -#ifndef HEADER_fd_src_disco_metrics_generated_fd_metrics_repair_h -#define HEADER_fd_src_disco_metrics_generated_fd_metrics_repair_h - -/* THIS FILE IS GENERATED BY gen_metrics.py. DO NOT HAND EDIT. */ - -#include "../fd_metrics_base.h" -#include "fd_metrics_enums.h" - -enum { - FD_METRICS_COUNTER_REPAIR_PKT_TX_OFF = FD_METRICS_TILE_OFF, - FD_METRICS_COUNTER_REPAIR_REQUEST_TX_OFF, - FD_METRICS_COUNTER_REPAIR_REQUEST_TX_PONG_OFF = FD_METRICS_COUNTER_REPAIR_REQUEST_TX_OFF, - FD_METRICS_COUNTER_REPAIR_REQUEST_TX_WINDOW_INDEX_OFF, - FD_METRICS_COUNTER_REPAIR_REQUEST_TX_HIGHEST_WINDOW_INDEX_OFF, - FD_METRICS_COUNTER_REPAIR_REQUEST_TX_ORPHAN_OFF, - FD_METRICS_COUNTER_REPAIR_REQUEST_TX_PARENT_FEC_SET_COUNT_OFF, - FD_METRICS_COUNTER_REPAIR_REQUEST_TX_FEC_SET_ROOT_OFF, - FD_METRICS_COUNTER_REPAIR_REQUEST_TX_WINDOW_INDEX_FOR_BLOCK_ID_OFF, - FD_METRICS_GAUGE_REPAIR_HIGHEST_REPAIRED_SLOT_OFF, - FD_METRICS_GAUGE_REPAIR_HIGHEST_TURBINE_SLOT_OFF, - FD_METRICS_COUNTER_REPAIR_SHRED_OLD_IGNORED_OFF, - FD_METRICS_GAUGE_REPAIR_LAST_REQUESTED_SLOT_OFF, - FD_METRICS_GAUGE_REPAIR_LAST_REQUESTED_ORPHAN_SLOT_OFF, - FD_METRICS_GAUGE_REPAIR_REQUEST_IN_FLIGHT_OFF, - FD_METRICS_GAUGE_REPAIR_PEER_COUNT_OFF, - FD_METRICS_COUNTER_REPAIR_SIGN_BACKPRESSURE_OFF, - FD_METRICS_COUNTER_REPAIR_TURBINE_GRACE_ELAPSED_OFF, - FD_METRICS_COUNTER_REPAIR_SHRED_REREQUESTED_OFF, - FD_METRICS_COUNTER_REPAIR_PING_REJECTED_OFF, - FD_METRICS_COUNTER_REPAIR_PING_REJECTED_MALFORMED_OFF = FD_METRICS_COUNTER_REPAIR_PING_REJECTED_OFF, - FD_METRICS_COUNTER_REPAIR_PING_REJECTED_UNKNOWN_PEER_OFF, - FD_METRICS_COUNTER_REPAIR_PING_REJECTED_SIGNATURE_FAILED_OFF, - FD_METRICS_HISTOGRAM_REPAIR_SLOT_COMPLETE_DURATION_NANOS_OFF, - FD_METRICS_HISTOGRAM_REPAIR_SLOT_COMPLETE_DURATION_NANOS_OFF_END = FD_METRICS_HISTOGRAM_REPAIR_SLOT_COMPLETE_DURATION_NANOS_OFF + 16, - FD_METRICS_HISTOGRAM_REPAIR_RESPONSE_LATENCY_NANOS_OFF, - FD_METRICS_HISTOGRAM_REPAIR_RESPONSE_LATENCY_NANOS_OFF_END = FD_METRICS_HISTOGRAM_REPAIR_RESPONSE_LATENCY_NANOS_OFF + 16, - FD_METRICS_COUNTER_REPAIR_BLOCK_EVICTED_OFF, - FD_METRICS_COUNTER_REPAIR_BLOCK_INSERT_FAILED_OFF, - FD_METRICS_GAUGE_REPAIR_LAST_EVICTED_SLOT_OFF, - FD_METRICS_GAUGE_REPAIR_LAST_EVICTION_CAUSE_SLOT_OFF, - FD_METRICS_GAUGE_REPAIR_LAST_INSERT_FAILED_SLOT_OFF, - FD_METRICS_COUNTER_REPAIR_CHAIN_VERIFY_FAILED_OFF, - FD_METRICS_GAUGE_REPAIR_LAST_CHAIN_VERIFY_FAILED_SLOT_OFF, -}; - -#define FD_METRICS_COUNTER_REPAIR_PKT_TX_NAME "repair_pkt_tx" -#define FD_METRICS_COUNTER_REPAIR_PKT_TX_TYPE (FD_METRICS_TYPE_COUNTER) -#define FD_METRICS_COUNTER_REPAIR_PKT_TX_DESC "Network packets sent, including reqs, pings, pongs, etc" -#define FD_METRICS_COUNTER_REPAIR_PKT_TX_CVT (FD_METRICS_CONVERTER_NONE) - -#define FD_METRICS_COUNTER_REPAIR_REQUEST_TX_NAME "repair_request_tx" -#define FD_METRICS_COUNTER_REPAIR_REQUEST_TX_TYPE (FD_METRICS_TYPE_COUNTER) -#define FD_METRICS_COUNTER_REPAIR_REQUEST_TX_DESC "Repair requests sent, by type" -#define FD_METRICS_COUNTER_REPAIR_REQUEST_TX_CVT (FD_METRICS_CONVERTER_NONE) -#define FD_METRICS_COUNTER_REPAIR_REQUEST_TX_CNT (7UL) - -#define FD_METRICS_GAUGE_REPAIR_HIGHEST_REPAIRED_SLOT_NAME "repair_highest_repaired_slot" -#define FD_METRICS_GAUGE_REPAIR_HIGHEST_REPAIRED_SLOT_TYPE (FD_METRICS_TYPE_GAUGE) -#define FD_METRICS_GAUGE_REPAIR_HIGHEST_REPAIRED_SLOT_DESC "Highest slot on the repair frontier, 0 if none; its ancestors are complete, the slot itself may not be" -#define FD_METRICS_GAUGE_REPAIR_HIGHEST_REPAIRED_SLOT_CVT (FD_METRICS_CONVERTER_NONE) - -#define FD_METRICS_GAUGE_REPAIR_HIGHEST_TURBINE_SLOT_NAME "repair_highest_turbine_slot" -#define FD_METRICS_GAUGE_REPAIR_HIGHEST_TURBINE_SLOT_TYPE (FD_METRICS_TYPE_GAUGE) -#define FD_METRICS_GAUGE_REPAIR_HIGHEST_TURBINE_SLOT_DESC "Highest slot a turbine shred was received for, 0 if none" -#define FD_METRICS_GAUGE_REPAIR_HIGHEST_TURBINE_SLOT_CVT (FD_METRICS_CONVERTER_NONE) - -#define FD_METRICS_COUNTER_REPAIR_SHRED_OLD_IGNORED_NAME "repair_shred_old_ignored" -#define FD_METRICS_COUNTER_REPAIR_SHRED_OLD_IGNORED_TYPE (FD_METRICS_TYPE_COUNTER) -#define FD_METRICS_COUNTER_REPAIR_SHRED_OLD_IGNORED_DESC "Shreds received older than the root, ignored" -#define FD_METRICS_COUNTER_REPAIR_SHRED_OLD_IGNORED_CVT (FD_METRICS_CONVERTER_NONE) - -#define FD_METRICS_GAUGE_REPAIR_LAST_REQUESTED_SLOT_NAME "repair_last_requested_slot" -#define FD_METRICS_GAUGE_REPAIR_LAST_REQUESTED_SLOT_TYPE (FD_METRICS_TYPE_GAUGE) -#define FD_METRICS_GAUGE_REPAIR_LAST_REQUESTED_SLOT_DESC "Last slot we requested a single shred for, 0 if none" -#define FD_METRICS_GAUGE_REPAIR_LAST_REQUESTED_SLOT_CVT (FD_METRICS_CONVERTER_NONE) - -#define FD_METRICS_GAUGE_REPAIR_LAST_REQUESTED_ORPHAN_SLOT_NAME "repair_last_requested_orphan_slot" -#define FD_METRICS_GAUGE_REPAIR_LAST_REQUESTED_ORPHAN_SLOT_TYPE (FD_METRICS_TYPE_GAUGE) -#define FD_METRICS_GAUGE_REPAIR_LAST_REQUESTED_ORPHAN_SLOT_DESC "Last slot we requested an orphan for, 0 if none" -#define FD_METRICS_GAUGE_REPAIR_LAST_REQUESTED_ORPHAN_SLOT_CVT (FD_METRICS_CONVERTER_NONE) - -#define FD_METRICS_GAUGE_REPAIR_REQUEST_IN_FLIGHT_NAME "repair_request_in_flight" -#define FD_METRICS_GAUGE_REPAIR_REQUEST_IN_FLIGHT_TYPE (FD_METRICS_TYPE_GAUGE) -#define FD_METRICS_GAUGE_REPAIR_REQUEST_IN_FLIGHT_DESC "Requests awaiting a response, excluding orphan and highest window requests" -#define FD_METRICS_GAUGE_REPAIR_REQUEST_IN_FLIGHT_CVT (FD_METRICS_CONVERTER_NONE) - -#define FD_METRICS_GAUGE_REPAIR_PEER_COUNT_NAME "repair_peer_count" -#define FD_METRICS_GAUGE_REPAIR_PEER_COUNT_TYPE (FD_METRICS_TYPE_GAUGE) -#define FD_METRICS_GAUGE_REPAIR_PEER_COUNT_DESC "Repair peers currently tracked" -#define FD_METRICS_GAUGE_REPAIR_PEER_COUNT_CVT (FD_METRICS_CONVERTER_NONE) - -#define FD_METRICS_COUNTER_REPAIR_SIGN_BACKPRESSURE_NAME "repair_sign_backpressure" -#define FD_METRICS_COUNTER_REPAIR_SIGN_BACKPRESSURE_TYPE (FD_METRICS_TYPE_COUNTER) -#define FD_METRICS_COUNTER_REPAIR_SIGN_BACKPRESSURE_DESC "Loop iterations where no sign tile was available to sign a request" -#define FD_METRICS_COUNTER_REPAIR_SIGN_BACKPRESSURE_CVT (FD_METRICS_CONVERTER_NONE) - -#define FD_METRICS_COUNTER_REPAIR_TURBINE_GRACE_ELAPSED_NAME "repair_turbine_grace_elapsed" -#define FD_METRICS_COUNTER_REPAIR_TURBINE_GRACE_ELAPSED_TYPE (FD_METRICS_TYPE_COUNTER) -#define FD_METRICS_COUNTER_REPAIR_TURBINE_GRACE_ELAPSED_DESC "Times a missing shred outlived the turbine grace period and became eligible for repair" -#define FD_METRICS_COUNTER_REPAIR_TURBINE_GRACE_ELAPSED_CVT (FD_METRICS_CONVERTER_NONE) - -#define FD_METRICS_COUNTER_REPAIR_SHRED_REREQUESTED_NAME "repair_shred_rerequested" -#define FD_METRICS_COUNTER_REPAIR_SHRED_REREQUESTED_TYPE (FD_METRICS_TYPE_COUNTER) -#define FD_METRICS_COUNTER_REPAIR_SHRED_REREQUESTED_DESC "Times we re-requested a shred from the inflights queue" -#define FD_METRICS_COUNTER_REPAIR_SHRED_REREQUESTED_CVT (FD_METRICS_CONVERTER_NONE) - -#define FD_METRICS_COUNTER_REPAIR_PING_REJECTED_NAME "repair_ping_rejected" -#define FD_METRICS_COUNTER_REPAIR_PING_REJECTED_TYPE (FD_METRICS_TYPE_COUNTER) -#define FD_METRICS_COUNTER_REPAIR_PING_REJECTED_DESC "Pings rejected, by reason" -#define FD_METRICS_COUNTER_REPAIR_PING_REJECTED_CVT (FD_METRICS_CONVERTER_NONE) -#define FD_METRICS_COUNTER_REPAIR_PING_REJECTED_CNT (3UL) - -#define FD_METRICS_HISTOGRAM_REPAIR_SLOT_COMPLETE_DURATION_NANOS_NAME "repair_slot_complete_duration_nanos" -#define FD_METRICS_HISTOGRAM_REPAIR_SLOT_COMPLETE_DURATION_NANOS_TYPE (FD_METRICS_TYPE_HISTOGRAM) -#define FD_METRICS_HISTOGRAM_REPAIR_SLOT_COMPLETE_DURATION_NANOS_DESC "Time from a slot's first shred (first request for catchup slots) until all its data shreds are received" -#define FD_METRICS_HISTOGRAM_REPAIR_SLOT_COMPLETE_DURATION_NANOS_CVT (FD_METRICS_CONVERTER_NANOSECONDS) -#define FD_METRICS_HISTOGRAM_REPAIR_SLOT_COMPLETE_DURATION_NANOS_EDGES_NS { 200000000UL, 235753727UL, 277899099UL, 327578742UL, 386139546UL, 455169185UL, 536539159UL, 632455532UL, 745518744UL, 878794112UL, 1035894936UL, 1221080459UL, 1439371346UL, 1696685796UL, 2000000000UL } - -#define FD_METRICS_HISTOGRAM_REPAIR_RESPONSE_LATENCY_NANOS_NAME "repair_response_latency_nanos" -#define FD_METRICS_HISTOGRAM_REPAIR_RESPONSE_LATENCY_NANOS_TYPE (FD_METRICS_TYPE_HISTOGRAM) -#define FD_METRICS_HISTOGRAM_REPAIR_RESPONSE_LATENCY_NANOS_DESC "Time from sending a repair request to receiving its response" -#define FD_METRICS_HISTOGRAM_REPAIR_RESPONSE_LATENCY_NANOS_CVT (FD_METRICS_CONVERTER_NONE) -#define FD_METRICS_HISTOGRAM_REPAIR_RESPONSE_LATENCY_NANOS_MIN (10000000UL) -#define FD_METRICS_HISTOGRAM_REPAIR_RESPONSE_LATENCY_NANOS_MAX (1000000000UL) - -#define FD_METRICS_COUNTER_REPAIR_BLOCK_EVICTED_NAME "repair_block_evicted" -#define FD_METRICS_COUNTER_REPAIR_BLOCK_EVICTED_TYPE (FD_METRICS_TYPE_COUNTER) -#define FD_METRICS_COUNTER_REPAIR_BLOCK_EVICTED_DESC "Blocks evicted from the forest" -#define FD_METRICS_COUNTER_REPAIR_BLOCK_EVICTED_CVT (FD_METRICS_CONVERTER_NONE) - -#define FD_METRICS_COUNTER_REPAIR_BLOCK_INSERT_FAILED_NAME "repair_block_insert_failed" -#define FD_METRICS_COUNTER_REPAIR_BLOCK_INSERT_FAILED_TYPE (FD_METRICS_TYPE_COUNTER) -#define FD_METRICS_COUNTER_REPAIR_BLOCK_INSERT_FAILED_DESC "Blocks we failed to insert into the forest due to failed eviction" -#define FD_METRICS_COUNTER_REPAIR_BLOCK_INSERT_FAILED_CVT (FD_METRICS_CONVERTER_NONE) - -#define FD_METRICS_GAUGE_REPAIR_LAST_EVICTED_SLOT_NAME "repair_last_evicted_slot" -#define FD_METRICS_GAUGE_REPAIR_LAST_EVICTED_SLOT_TYPE (FD_METRICS_TYPE_GAUGE) -#define FD_METRICS_GAUGE_REPAIR_LAST_EVICTED_SLOT_DESC "Most recent slot evicted from the forest, 0 if none" -#define FD_METRICS_GAUGE_REPAIR_LAST_EVICTED_SLOT_CVT (FD_METRICS_CONVERTER_NONE) - -#define FD_METRICS_GAUGE_REPAIR_LAST_EVICTION_CAUSE_SLOT_NAME "repair_last_eviction_cause_slot" -#define FD_METRICS_GAUGE_REPAIR_LAST_EVICTION_CAUSE_SLOT_TYPE (FD_METRICS_TYPE_GAUGE) -#define FD_METRICS_GAUGE_REPAIR_LAST_EVICTION_CAUSE_SLOT_DESC "Slot that caused the most recent forest eviction, 0 if none" -#define FD_METRICS_GAUGE_REPAIR_LAST_EVICTION_CAUSE_SLOT_CVT (FD_METRICS_CONVERTER_NONE) - -#define FD_METRICS_GAUGE_REPAIR_LAST_INSERT_FAILED_SLOT_NAME "repair_last_insert_failed_slot" -#define FD_METRICS_GAUGE_REPAIR_LAST_INSERT_FAILED_SLOT_TYPE (FD_METRICS_TYPE_GAUGE) -#define FD_METRICS_GAUGE_REPAIR_LAST_INSERT_FAILED_SLOT_DESC "Most recent slot we failed to insert into the forest, 0 if none" -#define FD_METRICS_GAUGE_REPAIR_LAST_INSERT_FAILED_SLOT_CVT (FD_METRICS_CONVERTER_NONE) - -#define FD_METRICS_COUNTER_REPAIR_CHAIN_VERIFY_FAILED_NAME "repair_chain_verify_failed" -#define FD_METRICS_COUNTER_REPAIR_CHAIN_VERIFY_FAILED_TYPE (FD_METRICS_TYPE_COUNTER) -#define FD_METRICS_COUNTER_REPAIR_CHAIN_VERIFY_FAILED_DESC "Times any block failed chained merkle verification" -#define FD_METRICS_COUNTER_REPAIR_CHAIN_VERIFY_FAILED_CVT (FD_METRICS_CONVERTER_NONE) - -#define FD_METRICS_GAUGE_REPAIR_LAST_CHAIN_VERIFY_FAILED_SLOT_NAME "repair_last_chain_verify_failed_slot" -#define FD_METRICS_GAUGE_REPAIR_LAST_CHAIN_VERIFY_FAILED_SLOT_TYPE (FD_METRICS_TYPE_GAUGE) -#define FD_METRICS_GAUGE_REPAIR_LAST_CHAIN_VERIFY_FAILED_SLOT_DESC "Most recent slot that failed chained merkle verification, 0 if none" -#define FD_METRICS_GAUGE_REPAIR_LAST_CHAIN_VERIFY_FAILED_SLOT_CVT (FD_METRICS_CONVERTER_NONE) - -#define FD_METRICS_REPAIR_TOTAL (30UL) -extern const fd_metrics_meta_t FD_METRICS_REPAIR[FD_METRICS_REPAIR_TOTAL]; - -#endif /* HEADER_fd_src_disco_metrics_generated_fd_metrics_repair_h */ diff --git a/src/disco/metrics/generated/fd_metrics_sample_gen.c b/src/disco/metrics/generated/fd_metrics_sample_gen.c index 25bfaa30df2..05f071dbd60 100644 --- a/src/disco/metrics/generated/fd_metrics_sample_gen.c +++ b/src/disco/metrics/generated/fd_metrics_sample_gen.c @@ -4411,6 +4411,6 @@ fd_metrics_sample_schema_t const fd_metrics_sample_tiles[] = { { NULL, 0U, NULL, 0UL, 0UL } }; -char const * const fd_metrics_sample_skip[] = { "backt", "benchs", "poh", "repair", "solcap", "tower", NULL }; +char const * const fd_metrics_sample_skip[] = { "backt", "benchs", "poh", "solcap", "tower", NULL }; fd_metrics_sample_schema_t const fd_metrics_sample_link = { "link", 61U, fields_link, 9UL, 284UL }; diff --git a/src/disco/metrics/metrics.xml b/src/disco/metrics/metrics.xml index 2e244aa070a..04d48b17c3a 100644 --- a/src/disco/metrics/metrics.xml +++ b/src/disco/metrics/metrics.xml @@ -1056,35 +1056,6 @@ deliberate exception with lint-allow="") - - - - - - - - - - - - - - - - Time from a slot's first shred (first request for catchup slots) until all its data shreds are received - - - Time from sending a repair request to receiving its response - - - - - - - - - - diff --git a/src/discof/repair/Local.mk b/src/discof/repair/Local.mk index bc98f6ba09b..933d7e96a70 100644 --- a/src/discof/repair/Local.mk +++ b/src/discof/repair/Local.mk @@ -1,17 +1,8 @@ ifdef FD_HAS_HOSTED -$(call add-objs,fd_repair_tile,fd_discof) $(call add-objs,fd_rserve_tile,fd_discof) endif -$(call add-objs,fd_reqlim,fd_discof) -$(call add-hdrs,fd_reqlim.h) -$(call add-objs,fd_policy,fd_discof) -$(call add-hdrs,fd_policy.h) -$(call add-objs,fd_inflight,fd_discof) -$(call add-hdrs,fd_inflight.h) $(call add-objs,fd_repair,fd_discof) $(call add-hdrs,fd_repair.h) -$(call add-objs,fd_repair_metrics,fd_discof) -$(call add-hdrs,fd_repair_metrics.h) $(call add-objs,fd_rserve,fd_discof) $(call add-hdrs,fd_rserve.h) @@ -19,8 +10,6 @@ $(call add-objs,fd_blockdb,fd_discof) $(call add-hdrs,fd_blockdb.h) ifdef FD_HAS_HOSTED $(call make-unit-test,test_blockdb,test_blockdb,fd_discof fd_disco fd_flamenco fd_tango fd_ballet fd_util) -$(call make-unit-test,test_policy,test_policy,fd_discof fd_disco fd_waltz fd_tango fd_ballet fd_util) -$(call make-unit-test,test_repair_tile,test_repair_tile,fd_discof fd_disco fd_waltz fd_tango fd_ballet fd_util) $(call make-unit-test,test_rserve_tile,test_rserve_tile,fd_discof fd_disco fd_flamenco fd_waltz fd_tango fd_ballet fd_util) $(call make-fuzz-test,fuzz_repair_serde,fuzz_repair_serde,fd_discof fd_disco fd_waltz fd_tango fd_ballet fd_util) endif diff --git a/src/discof/repair/fd_inflight.c b/src/discof/repair/fd_inflight.c deleted file mode 100644 index 51af8f58759..00000000000 --- a/src/discof/repair/fd_inflight.c +++ /dev/null @@ -1,230 +0,0 @@ -#include "fd_inflight.h" - -void * -fd_inflights_new( void * shmem, - ulong seed ) { - if( FD_UNLIKELY( !shmem ) ) { - FD_LOG_WARNING(( "NULL mem" )); - return NULL; - } - - ulong footprint = fd_inflights_footprint(); - ulong chain_cnt = fd_inflight_map_chain_cnt_est( FD_INFLIGHT_REQ_MAX ); - - FD_SCRATCH_ALLOC_INIT( l, shmem ); - fd_inflights_t * table = FD_SCRATCH_ALLOC_APPEND( l, fd_inflights_align(), sizeof(fd_inflights_t) ); - void * pool = FD_SCRATCH_ALLOC_APPEND( l, fd_inflight_pool_align(), fd_inflight_pool_footprint( FD_INFLIGHT_REQ_MAX ) ); - void * map = FD_SCRATCH_ALLOC_APPEND( l, fd_inflight_map_align(), fd_inflight_map_footprint ( chain_cnt ) ); - void * pmap = FD_SCRATCH_ALLOC_APPEND( l, fd_inflight_map_align(), fd_inflight_map_footprint ( chain_cnt ) ); - FD_TEST( FD_SCRATCH_ALLOC_FINI( l, fd_inflights_align() ) == (ulong)shmem + footprint ); - - table->pool = fd_inflight_pool_join( fd_inflight_pool_new( pool, FD_INFLIGHT_REQ_MAX ) ); - table->map = fd_inflight_map_join ( fd_inflight_map_new ( map, chain_cnt, seed ) ); - table->popped_map = fd_inflight_map_join ( fd_inflight_map_new ( pmap, chain_cnt, seed ) ); - table->popped_cnt = 0UL; - FD_TEST( table->outstanding_dl==fd_inflight_dlist_join( fd_inflight_dlist_new( table->outstanding_dl ) ) ); - FD_TEST( table->popped_dl ==fd_inflight_dlist_join( fd_inflight_dlist_new( table->popped_dl ) ) ); - - FD_TEST( table->pool ); - FD_TEST( table->map ); - FD_TEST( table->popped_map ); - return shmem; -} - -fd_inflights_t * -fd_inflights_join( void * shmem ) { - fd_inflights_t * table = (fd_inflights_t *)shmem; - - if( FD_UNLIKELY( !table ) ) { - FD_LOG_WARNING(( "NULL inflight table" )); - return NULL; - } - - if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)table, fd_inflights_align() ) ) ) { - FD_LOG_WARNING(( "misaligned inflight table" )); - return NULL; - } - - return table; -} - -/* inflight_acquire returns a FREE record, evicting the oldest POPPED - record if none is free, and the oldest OUTSTANDING one as a last - resort. The caller fills the key and payload, then commits it to - the OUTSTANDING set with inflight_commit. */ - -static fd_inflight_t * -inflight_acquire( fd_inflights_t * table ) { - if( FD_UNLIKELY( !fd_inflight_pool_free( table->pool ) ) ) { - if( FD_LIKELY( !fd_inflight_dlist_is_empty( table->popped_dl, table->pool ) ) ) { - fd_inflight_t * evict = fd_inflight_dlist_ele_pop_head( table->popped_dl, table->pool ); - table->popped_cnt--; - fd_inflight_map_ele_remove_fast( table->popped_map, evict, table->pool ); - fd_inflight_pool_ele_release ( table->pool, evict ); - } else { - /* (pool free cnt) + (popped_dl cnt) + (outstanding_dl cnt) == - FD_INFLIGHT_REQ_MAX, so they can't all be 0. Should be - impossible in practice: callers gate new requests on - fd_inflights_outstanding_free. */ - fd_inflight_t * evict = fd_inflight_dlist_ele_pop_head( table->outstanding_dl, table->pool ); - fd_inflight_map_ele_remove_fast( table->map, evict, table->pool ); - fd_inflight_pool_ele_release ( table->pool, evict ); - } - } - return fd_inflight_pool_ele_acquire( table->pool ); -} - -static void -inflight_commit( fd_inflights_t * table, - fd_inflight_t * req, - long now ) { - req->timestamp_ns = now; - fd_inflight_map_ele_insert ( table->map, req, table->pool ); - fd_inflight_dlist_ele_push_tail( table->outstanding_dl, req, table->pool ); -} - -/* inflight_match removes every record with key from the outstanding - then the popped set, copying the oldest to *out. Returns the number - of records removed. */ - -static ulong -inflight_match( fd_inflights_t * table, - fd_inflight_key_t const * key, - fd_inflight_t * out ) { - ulong cnt = 0UL; - long oldest = LONG_MAX; - for( int popped=0; popped<2; popped++ ) { - fd_inflight_map_t * map = popped ? table->popped_map : table->map; - fd_inflight_dlist_t * dlist = popped ? table->popped_dl : table->outstanding_dl; - for(;;) { - fd_inflight_t * req = fd_inflight_map_ele_remove( map, key, NULL, table->pool ); - if( FD_LIKELY( !req ) ) break; - if( FD_LIKELY( req->timestamp_nstimestamp_ns; *out = *req; } - fd_inflight_dlist_ele_remove( dlist, req, table->pool ); - fd_inflight_pool_ele_release( table->pool, req ); - table->popped_cnt -= (ulong)popped; - cnt++; - } - } - return cnt; -} - -void -fd_inflights_shred_insert( fd_inflights_t * table, - uint kind, - ulong nonce, - fd_pubkey_t const * pubkey, - ulong slot, - ulong shred_idx, - fd_hash_t const * block_id, - fd_hash_t const * fec_root, - long now ) { - fd_inflight_t * req = inflight_acquire( table ); - fd_inflight_key_init( &req->key, kind, slot, shred_idx, nonce, fec_root ); - req->pubkey = *pubkey; - if( FD_LIKELY( block_id ) ) req->block_id = *block_id; - else fd_memset( &req->block_id, 0, sizeof(fd_hash_t) ); - inflight_commit( table, req, now ); -} - -long -fd_inflights_shred_match( fd_inflights_t * table, - uint kind, - ulong nonce, - ulong slot, - ulong shred_idx, - fd_hash_t const * fec_root, - fd_pubkey_t * peer_out, - fd_hash_t * block_id_out, - long now ) { - fd_inflight_key_t key[1]; - fd_inflight_key_init( key, kind, slot, shred_idx, nonce, fec_root ); - fd_inflight_t req[1]; - if( FD_UNLIKELY( !inflight_match( table, key, req ) ) ) return 0L; - *peer_out = req->pubkey; - if( FD_LIKELY( block_id_out ) ) *block_id_out = req->block_id; - return fd_long_max( now-req->timestamp_ns, 1L ); /* >0 marks a match even if now has not advanced */ -} - -void -fd_inflights_meta_insert( fd_inflights_t * table, - ulong nonce, - uint kind, - fd_pubkey_t const * pubkey, - ulong slot, - fd_hash_t const * block_id, - uint fec_set_idx, - long now ) { - fd_inflight_t * req = inflight_acquire( table ); - fd_inflight_key_init( &req->key, kind, slot, fec_set_idx, nonce, NULL ); - req->pubkey = *pubkey; - req->block_id = *block_id; - inflight_commit( table, req, now ); -} - -int -fd_inflights_meta_match( fd_inflights_t * table, - ulong nonce, - fd_inflight_t * out ) { - fd_inflight_key_t key[1]; - fd_inflight_key_init( key, AG_REPAIR_KIND_PARENT_FEC_COUNT, 0UL, 0UL, nonce, NULL ); /* only the nonce is key material for metadata kinds */ - return !!inflight_match( table, key, out ); -} - -void -fd_inflights_pop( fd_inflights_t * table, - fd_inflight_t * out ) { - fd_inflight_t * req = fd_inflight_dlist_ele_pop_head( table->outstanding_dl, table->pool ); - fd_inflight_map_ele_remove_fast( table->map, req, table->pool ); - *out = *req; - - /* A null record (nonce 0) was never sent, release it rather than - parking it in the popped set. */ - if( FD_UNLIKELY( !req->key.nonce ) ) { - fd_inflight_pool_ele_release( table->pool, req ); - return; - } - - fd_inflight_map_ele_insert ( table->popped_map, req, table->pool ); - fd_inflight_dlist_ele_push_tail( table->popped_dl, req, table->pool ); - table->popped_cnt++; -} - -ulong -fd_inflights_expire( fd_inflights_t * table, - long cutoff, - ulong max ) { - ulong cnt = 0UL; - for( ; cntoutstanding_dl, table->pool ) ? NULL : fd_inflight_dlist_ele_peek_head( table->outstanding_dl, table->pool ); - fd_inflight_t * pop = fd_inflight_dlist_is_empty( table->popped_dl, table->pool ) ? NULL : fd_inflight_dlist_ele_peek_head( table->popped_dl, table->pool ); - int popped = !!pop && ( !out || pop->timestamp_nstimestamp_ns ); - fd_inflight_t * req = popped ? pop : out; - if( FD_LIKELY( !req || req->timestamp_ns>=cutoff ) ) break; - - fd_inflight_dlist_ele_pop_head ( popped ? table->popped_dl : table->outstanding_dl, table->pool ); - fd_inflight_map_ele_remove_fast( popped ? table->popped_map : table->map, req, table->pool ); - fd_inflight_pool_ele_release ( table->pool, req ); - table->popped_cnt -= (ulong)popped; - } - return cnt; -} - -#include - -void -fd_inflights_print( fd_inflight_dlist_t * dlist, fd_inflight_t * pool ) { - - printf("%-5s %-15s %-8s %-15s %-44s\n", "Kind", "Slot", "Idx", "Timestamp", "Peer"); - printf("%-5s %-15s %-8s %-15s %-44s\n", - "-----", "---------------", "--------", "------------", - "--------------------------------------------"); - for( fd_inflight_dlist_iter_t iter = fd_inflight_dlist_iter_fwd_init( dlist, pool ); - !fd_inflight_dlist_iter_done( iter, dlist, pool ); - iter = fd_inflight_dlist_iter_fwd_next( iter, dlist, pool ) ) { - fd_inflight_t * req = fd_inflight_dlist_iter_ele( iter, dlist, pool ); - FD_BASE58_ENCODE_32_BYTES( req->pubkey.uc, peer ); - printf("%-5u %-15lu %-8u %-15lu %-44.44s\n", req->key.kind, req->key.slot, req->key.idx, (ulong)req->timestamp_ns / (ulong)1e6, peer ); - } - printf("\n"); -} diff --git a/src/discof/repair/fd_inflight.h b/src/discof/repair/fd_inflight.h deleted file mode 100644 index a9e906ca776..00000000000 --- a/src/discof/repair/fd_inflight.h +++ /dev/null @@ -1,317 +0,0 @@ -#ifndef HEADER_fd_src_discof_repair_fd_inflight_h -#define HEADER_fd_src_discof_repair_fd_inflight_h - -#include "fd_policy.h" -#include "../../ballet/shred/fd_shred.h" - -/* fd_inflight tracks repair requests that are inflight to other - validators, so that a response can be credited to the request (and - peer) that solicited it and a request that gets no response can be - redispatched after a timeout. Request kinds: - - - Shred requests -- positional FD_REPAIR_KIND_SHRED and Alpenglow - ShredForBlockId, which are indistinguishable on response -- are - keyed by (slot, shred_idx, nonce, kind, fec_root). fec_root is - all zero for a positional request (i.e., we didnt know the FEC - root when the request was issued). For a ShredForBlockId request, - it is the 20-byte prefix of the known FEC root. - - - Metadata requests -- getParentAndFecSetCount and getFecSetRoot -- - are matched by nonce alone. Their kind, slot and FEC set index - ride in the key for redispatch (and so the caller can reject a - response of the wrong kind) but do not participate in matching. - - Exact updates of shred requests are critical: the repair policy does - not request any shred twice, so re-requests come only from this - table. Whether a shred belongs to a version is decided structurally - by the rotor, which keys FECs by root; this table is request - accounting only. - - Each record is FREE, OUTSTANDING, or POPPED: - - insert pop - FREE --------> OUTSTANDING -----------> POPPED - ^ | | - | match | match, or evicted | - ------------------------------------------- - - OUTSTANDING records are in map and outstanding_dl (insertion order, - oldest at head). A record that ages past FD_REQLIM_DEDUP_TIMEOUT is - popped: the caller redispatches it under a fresh nonce and the record - moves to popped_map / popped_dl rather than being released, so a late - response to the old nonce still matches. When the pool is exhausted - the oldest POPPED record is evicted first. A caller that does not - redispatch from the table releases aged records with - fd_inflights_expire instead of popping them. */ - -/* Max number of pending requests */ -#define FD_INFLIGHT_REQ_MAX (1<<20) - -struct fd_inflight_key { - ulong slot; - uint idx; /* shred idx (shred kinds) or fec_set_idx (AG_REPAIR_KIND_FEC_ROOT) */ - uint nonce; /* rnonce or counter nonce (metadata) */ - uint kind; /* FD_REPAIR_KIND_SHRED, AG_REPAIR_KIND_SHRED_FOR_BLOCK_ID, AG_REPAIR_KIND_PARENT_FEC_COUNT or AG_REPAIR_KIND_FEC_ROOT */ - /* shred kinds: first FD_SHRED_MERKLE_NODE_SZ bytes of the FEC root a - ShredForBlockId request was issued against, all-zero for a - positional request. All-zero for metadata kinds. */ - uchar fec_root[ FD_SHRED_MERKLE_NODE_SZ ]; -}; -typedef struct fd_inflight_key fd_inflight_key_t; -FD_STATIC_ASSERT( sizeof(fd_inflight_key_t)==40UL, fd_inflight_key_sz ); - -/* fd_inflight_key_init fills key. For shred requests fec_root is the - (full or 20-byte-padded) FEC root the request targets, or NULL for a - positional request; only the first FD_SHRED_MERKLE_NODE_SZ bytes are - kept. For metadata kinds pass NULL. */ -static inline void -fd_inflight_key_init( fd_inflight_key_t * key, - uint kind, - ulong slot, - ulong idx, - ulong nonce, - fd_hash_t const * fec_root ) { - key->slot = slot; - key->idx = (uint)idx; - key->nonce = (uint)nonce; - key->kind = kind; - if( FD_LIKELY( fec_root ) ) memcpy( key->fec_root, fec_root->uc, FD_SHRED_MERKLE_NODE_SZ ); - else memset( key->fec_root, 0, FD_SHRED_MERKLE_NODE_SZ ); -} - -/* Key equality and hashing. A shred request is identified by the whole - key. A metadata request is identified by its nonce alone -- the - counter nonce is unique -- so its kind, slot and idx are not - considered when matching. */ - -static inline int -fd_inflight_key_is_shred( fd_inflight_key_t const * k ) { - return k->kind==FD_REPAIR_KIND_SHRED || k->kind==AG_REPAIR_KIND_SHRED_FOR_BLOCK_ID; -} - -static inline int -fd_inflight_key_eq( fd_inflight_key_t const * k0, - fd_inflight_key_t const * k1 ) { - if( FD_UNLIKELY( k0->nonce!=k1->nonce ) ) return 0; - if( FD_UNLIKELY( fd_inflight_key_is_shred( k0 )!=fd_inflight_key_is_shred( k1 ) ) ) return 0; - if( FD_UNLIKELY( !fd_inflight_key_is_shred( k0 ) ) ) return 1; - return ( k0->slot==k1->slot ) & ( k0->idx==k1->idx ) & ( k0->kind==k1->kind ) & !memcmp( k0->fec_root, k1->fec_root, FD_SHRED_MERKLE_NODE_SZ ); -} - -static inline ulong -fd_inflight_key_hash( fd_inflight_key_t const * k, - ulong seed ) { - if( FD_UNLIKELY( !fd_inflight_key_is_shred( k ) ) ) return fd_hash( seed, &k->nonce, sizeof(uint) ); - return fd_hash( seed, k, sizeof(fd_inflight_key_t) ); -} - -struct __attribute__((aligned(128UL))) fd_inflight { - fd_inflight_key_t key; - uint next; /* reserved for internal use by fd_pool and fd_map_chain */ - uint prev; /* for fd_map_chain */ - uint prevll; /* for fd_inflight_dlist */ - uint nextll; - long timestamp_ns; /* when the request was created (caller's clock, nanoseconds) */ - - fd_pubkey_t pubkey; /* peer the request went to (all-zero if parked unsent) */ - - /* Version being repaired: the block_id of a ShredForBlockId or - metadata request, all-zero for a positional shred request. */ - fd_hash_t block_id; -}; -typedef struct fd_inflight fd_inflight_t; -FD_STATIC_ASSERT( sizeof(fd_inflight_t)==128UL, fd_inflight_sz ); - -#define POOL_NAME fd_inflight_pool -#define POOL_T fd_inflight_t -#define POOL_IDX_T uint -#include "../../util/tmpl/fd_pool.c" - -#define MAP_NAME fd_inflight_map -#define MAP_KEY key -#define MAP_ELE_T fd_inflight_t -#define MAP_KEY_T fd_inflight_key_t -#define MAP_IDX_T uint -#define MAP_KEY_EQ(k0, k1) fd_inflight_key_eq( (k0), (k1) ) -#define MAP_KEY_HASH(k,s) fd_inflight_key_hash( (k), (s) ) -#define MAP_MULTI 1 /* the same shred request within one rnonce time bucket */ -#define MAP_OPTIMIZE_RANDOM_ACCESS_REMOVAL 1 -#include "../../util/tmpl/fd_map_chain.c" - -#define DLIST_NAME fd_inflight_dlist -#define DLIST_ELE_T fd_inflight_t -#define DLIST_IDX_T uint -#define DLIST_PREV prevll -#define DLIST_NEXT nextll -#include "../../util/tmpl/fd_dlist.c" - -struct fd_inflights { - fd_inflight_t * pool; - fd_inflight_map_t * map; /* OUTSTANDING */ - fd_inflight_map_t * popped_map; /* POPPED */ - fd_inflight_dlist_t outstanding_dl[1]; - fd_inflight_dlist_t popped_dl[1]; - ulong popped_cnt; -}; -typedef struct fd_inflights fd_inflights_t; - -FD_FN_CONST static inline ulong -fd_inflights_align( void ) { return 128UL; } - -FD_FN_CONST static inline ulong -fd_inflights_footprint( void ) { - ulong chain_cnt = fd_inflight_map_chain_cnt_est( FD_INFLIGHT_REQ_MAX ); - return FD_LAYOUT_FINI( - FD_LAYOUT_APPEND( - FD_LAYOUT_APPEND( - FD_LAYOUT_APPEND( - FD_LAYOUT_APPEND( - FD_LAYOUT_INIT, - alignof(fd_inflights_t), sizeof(fd_inflights_t) ), - fd_inflight_pool_align(), fd_inflight_pool_footprint( FD_INFLIGHT_REQ_MAX ) ), - fd_inflight_map_align(), fd_inflight_map_footprint ( chain_cnt ) ), - fd_inflight_map_align(), fd_inflight_map_footprint ( chain_cnt ) ), - fd_inflights_align() ); -} - -void * -fd_inflights_new( void * shmem, - ulong seed ); - -fd_inflights_t * -fd_inflights_join( void * shmem ); - -/* Timestamps. Every insert and match takes now, the caller's current - time in nanoseconds, rather than reading a clock itself: records are - stamped with it on insert, fd_inflights_shred_match reports RTT - against it, and fd_inflights_should_drain compares against it. All - calls on a table must use the same clock (the tile clock, - fd_clock_tile_now) so age and RTT are consistent. */ - -/* fd_inflights_shred_insert records a shred request of kind - FD_REPAIR_KIND_SHRED (positional) or AG_REPAIR_KIND_SHRED_FOR_BLOCK_ID - to pubkey. block_id is the ShredForBlockId version being repaired - and fec_root the root the rotor holds for that version at the - requested FEC set (the key a response is matched by); both NULL (or - all-zero) for a positional request. */ - -void -fd_inflights_shred_insert( fd_inflights_t * table, - uint kind, - ulong nonce, - fd_pubkey_t const * pubkey, - ulong slot, - ulong shred_idx, - fd_hash_t const * block_id, - fd_hash_t const * fec_root, - long now ); - -/* fd_inflights_shred_match matches a shred response against requests - of kind (FD_REPAIR_KIND_SHRED or AG_REPAIR_KIND_SHRED_FOR_BLOCK_ID). - fec_root is the response shred's merkle root (only the first - FD_SHRED_MERKLE_NODE_SZ bytes are keyed on), or NULL to match a - positional request. Removes - every record with that key from both the outstanding and popped sets - and credits the response to the oldest: returns its RTT in - nanoseconds relative to now (>0), or 0 if nothing matched. On a - match *peer_out is set, and *block_id_out (if non-NULL) to the - matched request's block_id. */ - -long -fd_inflights_shred_match( fd_inflights_t * table, - uint kind, - ulong nonce, - ulong slot, - ulong shred_idx, - fd_hash_t const * fec_root, - fd_pubkey_t * peer_out, - fd_hash_t * block_id_out, - long now ); - -/* fd_inflights_meta_insert records a metadata request to pubkey. kind - is AG_REPAIR_KIND_PARENT_FEC_COUNT or AG_REPAIR_KIND_FEC_ROOT; - fec_set_idx is meaningful for the latter only. */ - -void -fd_inflights_meta_insert( fd_inflights_t * table, - ulong nonce, - uint kind, - fd_pubkey_t const * pubkey, - ulong slot, - fd_hash_t const * block_id, - uint fec_set_idx, - long now ); - -/* fd_inflights_meta_match matches a metadata response by nonce in the - outstanding then the popped set. On a match the record is copied to - *out (out->key.kind is the kind that was requested), removed, and 1 - is returned; 0 otherwise. */ - -int -fd_inflights_meta_match( fd_inflights_t * table, - ulong nonce, - fd_inflight_t * out ); - -/* fd_inflights_should_drain returns 1 if the oldest outstanding request - has aged past FD_REQLIM_DEDUP_TIMEOUT and should be redispatched. */ - -static inline int -fd_inflights_should_drain( fd_inflights_t * table, long now ) { - if( FD_UNLIKELY( fd_inflight_dlist_is_empty( table->outstanding_dl, table->pool ) ) ) return 0; - fd_inflight_t * head = fd_inflight_dlist_ele_peek_head( table->outstanding_dl, table->pool ); - return head->timestamp_ns + FD_REQLIM_DEDUP_TIMEOUT < now; -} - -/* fd_inflights_pop copies the oldest outstanding request to *out and - moves it to the popped set, so a late response to its nonce still - matches. A record with nonce 0 (parked by the caller, never sent) is - released instead, since nothing can match it. The outstanding set - must be non-empty: only call this after fd_inflights_should_drain - returns 1. */ - -void -fd_inflights_pop( fd_inflights_t * table, - fd_inflight_t * out ); - -/* fd_inflights_expire releases, oldest first, up to max OUTSTANDING or - POPPED records inserted before cutoff and returns how many. For - callers that never redispatch from the table. */ - -ulong -fd_inflights_expire( fd_inflights_t * table, - long cutoff, - ulong max ); - -/* fd_inflights_oldest_ts returns the insert time of the oldest - OUTSTANDING or POPPED record (the next one fd_inflights_expire - releases), LONG_MAX if there is none. */ - -static inline long -fd_inflights_oldest_ts( fd_inflights_t * table ) { - long ts = LONG_MAX; - if( FD_LIKELY( !fd_inflight_dlist_is_empty( table->outstanding_dl, table->pool ) ) ) ts = fd_inflight_dlist_ele_peek_head( table->outstanding_dl, table->pool )->timestamp_ns; - if( FD_UNLIKELY( !fd_inflight_dlist_is_empty( table->popped_dl, table->pool ) ) ) ts = fd_long_min( ts, fd_inflight_dlist_ele_peek_head( table->popped_dl, table->pool )->timestamp_ns ); - return ts; -} - -/* fd_inflights_outstanding_free returns how many new requests can be - inserted before an insert would have to evict an OUTSTANDING record - (FREE records plus evictable POPPED ones). */ - -static inline ulong -fd_inflights_outstanding_free( fd_inflights_t * table ) { - return fd_inflight_pool_free( table->pool ) + table->popped_cnt; -} - -/* fd_inflights_outstanding_cnt returns the number of OUTSTANDING - records. */ - -static inline ulong -fd_inflights_outstanding_cnt( fd_inflights_t * table ) { - return fd_inflight_pool_used( table->pool ) - table->popped_cnt; -} - -void -fd_inflights_print( fd_inflight_dlist_t * dlist, fd_inflight_t * pool ); - -#endif /* HEADER_fd_src_discof_repair_fd_inflight_h */ diff --git a/src/discof/repair/fd_policy.c b/src/discof/repair/fd_policy.c deleted file mode 100644 index b271b450b7c..00000000000 --- a/src/discof/repair/fd_policy.c +++ /dev/null @@ -1,444 +0,0 @@ -#include "fd_policy.h" -#include "../../disco/metrics/fd_metrics.h" - -#define NONCE_NULL (UINT_MAX) -#define DEFER_REPAIR_MS (150UL) - -void * -fd_policy_new( void * shmem, ulong peer_max, ulong seed, fd_rnonce_ss_t const * rnonce_ss ) { - - if( FD_UNLIKELY( !shmem ) ) { - FD_LOG_WARNING(( "NULL mem" )); - return NULL; - } - - if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)shmem, fd_policy_align() ) ) ) { - FD_LOG_WARNING(( "misaligned mem" )); - return NULL; - } - - ulong footprint = fd_policy_footprint( peer_max ); - fd_memset( shmem, 0, footprint ); - - ulong peer_chain_cnt = fd_policy_peer_map_chain_cnt_est( peer_max ); - FD_SCRATCH_ALLOC_INIT( l, shmem ); - fd_policy_t * policy = FD_SCRATCH_ALLOC_APPEND( l, fd_policy_align(), sizeof(fd_policy_t) ); - void * peers = FD_SCRATCH_ALLOC_APPEND( l, fd_policy_peer_map_align(), fd_policy_peer_map_footprint( peer_chain_cnt ) ); - void * peers_pool = FD_SCRATCH_ALLOC_APPEND( l, fd_policy_peer_pool_align(), fd_policy_peer_pool_footprint( peer_max ) ); - void * peers_fast = FD_SCRATCH_ALLOC_APPEND( l, fd_policy_peer_dlist_align(), fd_policy_peer_dlist_footprint() ); - void * peers_slow = FD_SCRATCH_ALLOC_APPEND( l, fd_policy_peer_dlist_align(), fd_policy_peer_dlist_footprint() ); - FD_TEST( FD_SCRATCH_ALLOC_FINI( l, fd_policy_align() ) == (ulong)shmem + footprint ); - - policy->peers.map = fd_policy_peer_map_new ( peers, peer_chain_cnt, seed ); - policy->peers.pool = fd_policy_peer_pool_new ( peers_pool, peer_max ); - policy->peers.fast = fd_policy_peer_dlist_new( peers_fast ); - policy->peers.slow = fd_policy_peer_dlist_new( peers_slow ); - policy->turbine_slot0 = ULONG_MAX; - policy->rnonce_ss[0] = *rnonce_ss; - - return shmem; -} - -fd_policy_t * -fd_policy_join( void * shpolicy ) { - fd_policy_t * policy = (fd_policy_t *)shpolicy; - - if( FD_UNLIKELY( !policy ) ) { - FD_LOG_WARNING(( "NULL policy" )); - return NULL; - } - - if( FD_UNLIKELY( !fd_ulong_is_aligned((ulong)policy, fd_policy_align() ) ) ) { - FD_LOG_WARNING(( "misaligned policy" )); - return NULL; - } - - fd_wksp_t * wksp = fd_wksp_containing( policy ); - if( FD_UNLIKELY( !wksp ) ) { - FD_LOG_WARNING(( "policy must be part of a workspace" )); - return NULL; - } - - policy->peers.map = fd_policy_peer_map_join ( policy->peers.map ); - policy->peers.pool = fd_policy_peer_pool_join ( policy->peers.pool ); - policy->peers.fast = fd_policy_peer_dlist_join( policy->peers.fast ); - policy->peers.slow = fd_policy_peer_dlist_join( policy->peers.slow ); - - policy->peers.select.fast_iter = fd_policy_peer_dlist_iter_fwd_init( policy->peers.fast, policy->peers.pool ); - policy->peers.select.slow_iter = fd_policy_peer_dlist_iter_fwd_init( policy->peers.slow, policy->peers.pool ); - policy->peers.select.cnt = 0; - - return policy; -} - -void * -fd_policy_leave( fd_policy_t const * policy ) { - - if( FD_UNLIKELY( !policy ) ) { - FD_LOG_WARNING(( "NULL policy" )); - return NULL; - } - - return (void *)policy; -} - -void * -fd_policy_delete( void * policy ) { - - if( FD_UNLIKELY( !policy ) ) { - FD_LOG_WARNING(( "NULL policy" )); - return NULL; - } - - if( FD_UNLIKELY( !fd_ulong_is_aligned((ulong)policy, fd_policy_align() ) ) ) { - FD_LOG_WARNING(( "misaligned policy" )); - return NULL; - } - - return policy; -} - -static ulong ts_ms( long wallclock ) { - return (ulong)wallclock / (ulong)1e6; -} - -/* throttle_remaining_ns returns how long (ns) until the candidate - missing shred cand_idx of ele passes the eager repair threshold, 0 if - it already passes. A missing shred becomes eligible DEFER_REPAIR_MS - after turbine was first observed reaching its FEC set (see - fd_forest_blk_recv_ms). - - If the set has not been observed, we wait DEFER_REPAIR_MS from the - last set turbine delivered before it; if no set has been observed the - window runs from first_shred_ts. */ - -static long -throttle_remaining_ns( fd_policy_t * policy, fd_forest_t const * forest, fd_forest_blk_t const * ele, uint cand_idx ) { - if( FD_UNLIKELY( ele->slot < policy->turbine_slot0 ) ) return 0L; - if( FD_UNLIKELY( !ele->first_shred_ts ) ) return 0L; /* nothing observed yet, nothing to defer against */ - - fd_forest_recv_t const * recv = fd_forest_blk_recv( forest, ele ); - uint fec_idx = (uint)fd_ulong_min( cand_idx/FD_FEC_SHRED_CNT, forest->shred_max/FD_FEC_SHRED_CNT-1UL ); - ushort ms = 0; - - for(;;) { - ms = recv[ fec_idx ].first; - if( FD_LIKELY( ms || !fec_idx ) ) break; - fec_idx--; - } - - double first_ms = (double)ms; /* time since first_shred_ts */ - double elapsed_ms = (double)(fd_tickcount() - ele->first_shred_ts) / fd_tempo_tick_per_ns( NULL ) * 1e-6; - double deadline = first_ms + (double)DEFER_REPAIR_MS; - if( elapsed_ms >= deadline ) { - FD_MCNT_INC( REPAIR, TURBINE_GRACE_ELAPSED, 1 ); - return 0L; - } - return (long)( (deadline - elapsed_ms) * 1e6 ); -} - -static inline fd_policy_peer_dlist_iter_t -peer_iter_advance( fd_policy_peer_dlist_iter_t iter, - fd_policy_peer_dlist_t * dlist, - fd_policy_peer_t * pool ) { - iter = fd_policy_peer_dlist_iter_fwd_next( iter, dlist, pool ); - if( FD_UNLIKELY( fd_policy_peer_dlist_iter_done( iter, dlist, pool ) ) ) { - iter = fd_policy_peer_dlist_iter_fwd_init( dlist, pool ); - } - return iter; -} - -fd_pubkey_t const * -fd_policy_peer_select( fd_policy_t * policy ) { - fd_policy_peer_dlist_t * fast = policy->peers.fast; - fd_policy_peer_dlist_t * slow = policy->peers.slow; - fd_policy_peer_t * pool = policy->peers.pool; - - if( FD_UNLIKELY( fd_policy_peer_pool_used( pool ) == 0 ) ) return NULL; - - /* reinit stale iterators. happens when peers are inserted into a - previously-empty list after the iterator was initialized. */ - int fast_empty = fd_policy_peer_dlist_iter_done( fd_policy_peer_dlist_iter_fwd_init( fast, pool ), fast, pool ); - int slow_empty = fd_policy_peer_dlist_iter_done( fd_policy_peer_dlist_iter_fwd_init( slow, pool ), slow, pool ); - - if( FD_UNLIKELY( !fast_empty && fd_policy_peer_dlist_iter_done( policy->peers.select.fast_iter, fast, pool ) ) ) { - policy->peers.select.fast_iter = fd_policy_peer_dlist_iter_fwd_init( fast, pool ); - } - if( FD_UNLIKELY( !slow_empty && fd_policy_peer_dlist_iter_done( policy->peers.select.slow_iter, slow, pool ) ) ) { - policy->peers.select.slow_iter = fd_policy_peer_dlist_iter_fwd_init( slow, pool ); - } - - fd_policy_peer_t * select; - - /* select will be set to current iterator status. Then iterator should - be advanced for the following peer_select call. */ - - if( FD_UNLIKELY( fast_empty ) ) { - select = fd_policy_peer_dlist_iter_ele( policy->peers.select.slow_iter, slow, pool ); - policy->peers.select.slow_iter = peer_iter_advance( policy->peers.select.slow_iter, slow, pool ); - return &select->key; - } - - if( FD_UNLIKELY( slow_empty ) ) { - select = fd_policy_peer_dlist_iter_ele( policy->peers.select.fast_iter, fast, pool ); - policy->peers.select.fast_iter = peer_iter_advance( policy->peers.select.fast_iter, fast, pool ); - return &select->key; - } - - /* interleave FD_POLICY_FAST_PER_SLOW fast, 1 slow. */ - if( FD_LIKELY( policy->peers.select.cnt < FD_POLICY_FAST_PER_SLOW ) ) { - select = fd_policy_peer_dlist_iter_ele( policy->peers.select.fast_iter, fast, pool ); - policy->peers.select.fast_iter = peer_iter_advance( policy->peers.select.fast_iter, fast, pool ); - policy->peers.select.cnt++; - return &select->key; - } - - select = fd_policy_peer_dlist_iter_ele( policy->peers.select.slow_iter, slow, pool ); - policy->peers.select.slow_iter = peer_iter_advance( policy->peers.select.slow_iter, slow, pool ); - policy->peers.select.cnt = 0; - return &select->key; -} - -fd_repair_msg_t const * -fd_policy_next( fd_policy_t * policy, fd_reqlim_t * dedup, fd_forest_t * forest, fd_repair_t * repair, long now, ulong highest_known_slot, int * charge_busy ) { - fd_forest_blk_t * pool = fd_forest_pool( forest ); - *charge_busy = 0; - - if( FD_UNLIKELY( forest->root == ULONG_MAX ) ) return NULL; - if( FD_UNLIKELY( fd_policy_peer_pool_used( policy->peers.pool ) == 0 ) ) return NULL; - - fd_repair_msg_t * out = NULL; - ulong now_ms = ts_ms( now ); - - fd_forest_orphan_ent_t * orphanq = fd_forest_orphanq( forest ); - ulong budget = 64UL; - while( budget-- && fd_forest_orphanq_cnt( orphanq ) && orphanq[ 0 ].due<=now ) { - *charge_busy = 1; - fd_forest_orphan_ent_t ent = orphanq[ 0 ]; - fd_forest_orphanq_remove_min( orphanq ); - fd_forest_blk_t * orphan = fd_forest_subtrees_ele_query( fd_forest_subtrees( forest ), &ent.slot, NULL, pool ); - if( FD_UNLIKELY( !orphan || orphan->orphan_seq!=ent.seq ) ) continue; - ulong key = fd_reqlim_key( FD_REPAIR_KIND_ORPHAN, ent.slot, UINT_MAX ); - int deduped = fd_reqlim_next( dedup, key, now ); - ulong seq = forest->orphan_seq_next++; - orphan->orphan_seq = seq; - fd_forest_orphan_ent_t nxt = { .due = fd_reqlim_next_due( dedup, key, now ), .slot = ent.slot, .seq = seq }; - fd_forest_orphanq_insert( orphanq, &nxt ); - if( FD_LIKELY( !deduped ) ) { - uint nonce = fd_rnonce_ss_compute( policy->rnonce_ss, 0, ent.slot, 0U, now ); - out = fd_repair_orphan( repair, fd_policy_peer_select( policy ), now_ms, nonce, ent.slot ); - orphan->req_orphan_cnt++; - return out; - } - } - - /* Select a slot to operate on 🔪. Advance either the orphan iter or - regular iter. */ - fd_forest_iter_t * iter = NULL; - if( FD_UNLIKELY( fd_forest_reqslist_is_empty( fd_forest_reqslist( forest ), fd_forest_reqspool( forest ) ) ) ) { - /* If the main tree has nothing to iterate at the moment, we can - request down the ORPHAN trees on slots we know about. */ - iter = &forest->orphiter; - } else { - iter = &forest->iter; - } - - fd_forest_iter_next( iter, forest ); - if( FD_UNLIKELY( fd_forest_iter_done( iter, forest ) ) ) { - // This happens when we have already requested all the shreds we know about. - return NULL; - } - - fd_forest_blk_t * ele = fd_forest_pool_ele( pool, iter->ele_idx ); - - /* The next request this call would produce. If it was recently - declined and nothing about it changed, skip the turn. A throttle - memo applies to any slot (see fd_policy_skip_t for why memos are - per slot). A dedup memo is only written for, and only applies to, - the head slot: once highest_known_slot moves on, the same candidate - maps to a highest-shred probe under a different reqlim key, so the - memo would be stale. Older slots with both their probe and tail - request rate limited are simply re-evaluated each turn. */ - uint cand_idx = iter->shred_idx==UINT_MAX ? ele->buffered_idx+1U : iter->shred_idx; - fd_policy_skip_t * skip = fd_policy_skip( policy, ele->slot ); - if( FD_UNLIKELY( ( skip->throttled || ( iter->shred_idx==UINT_MAX && ele->slot==highest_known_slot ) ) && - skip->slot==ele->slot && - skip->idx==cand_idx && - nowuntil ) ) { - iter->shred_idx = UINT_MAX; - return NULL; - } - - long throttle_ns = throttle_remaining_ns( policy, forest, ele, cand_idx ); - if( FD_UNLIKELY( throttle_ns ) ) { - /* When we are at the head of the turbine, we should give turbine the - chance to complete the shreds. - - Here we did not pass the timeout threshold, so we are not ready - to repair this slot yet. But it's possible we have another slot - (an older one still within its window, or another fork) that we - need to repair... so we just should skip to the next SLOT in the - main tree iterator. Setting shred_idx to UINT_MAX makes the next - fd_forest_iter_next advance the iter to the next slot (and - re-queue this one at the tail while it is incomplete). */ - iter->shred_idx = UINT_MAX; - - /* Cap at 1ms: the deadline is derived from tick estimates that can - move as more shreds land without changing the candidate. */ - skip->until = now + fd_long_min( throttle_ns, (long)1e6 ); - skip->slot = ele->slot; - skip->idx = cand_idx; - skip->throttled = 1; - return NULL; - } - - *charge_busy = 1; - - if( FD_UNLIKELY( iter->shred_idx == UINT_MAX ) ) { - /* No known interior missing shred: the next missing shred is the - tail (cand_idx = buffered_idx+1) and the block's end is unknown. - For a slot turbine has moved past, first probe the highest shred - a peer holds. Only if that probe is still inside its reqlim rate - window from an earlier turn, or this is the head turbine slot - whose end no peer knows yet, ask for the tail shred directly - instead */ - ulong highest_key = fd_reqlim_key( FD_REPAIR_KIND_HIGHEST_SHRED, ele->slot, UINT_MAX ); - if( FD_UNLIKELY( ele->slot < highest_known_slot && !fd_reqlim_next( dedup, highest_key, now ) ) ) { - uint nonce = fd_rnonce_ss_compute( policy->rnonce_ss, 0, ele->slot, 0U, now ); - out = fd_repair_highest_shred( repair, fd_policy_peer_select( policy ), now_ms, nonce, ele->slot, 0 ); - ele->req_highest_cnt++; - } else if( FD_LIKELY( (ulong)cand_idx < forest->shred_max ) ) { - ulong key = fd_reqlim_key( FD_REPAIR_KIND_SHRED, ele->slot, cand_idx ); - if( FD_UNLIKELY( fd_reqlim_query( dedup, key, now ) ) ) { - // TODO should this be gated on ele->slot == highest_known_slot? - skip->slot = ele->slot; - skip->idx = cand_idx; - skip->throttled = 0; - skip->until = fd_reqlim_next_due( dedup, key, now ); - *charge_busy = 0; - return NULL; - } - uint nonce = fd_rnonce_ss_compute( policy->rnonce_ss, 1, ele->slot, cand_idx, now ); - out = fd_repair_shred( repair, fd_policy_peer_select( policy ), now_ms, nonce, ele->slot, cand_idx ); - } - } else { - /* Regular repair requests are not deduped here. The repair tile - dedups them and parks a deduped one in its inflight table so it - is retried on timeout. Metrics increment also occurs in the tile. */ - uint nonce = fd_rnonce_ss_compute( policy->rnonce_ss, 1, ele->slot, iter->shred_idx, now ); - out = fd_repair_shred( repair, fd_policy_peer_select( policy ), now_ms, nonce, ele->slot, iter->shred_idx ); - } - return out; -} - -fd_policy_peer_t const * -fd_policy_peer_upsert( fd_policy_t * policy, fd_pubkey_t const * key, fd_ip4_port_t const * addr ) { - fd_policy_peer_map_t * peer_map = policy->peers.map; - fd_policy_peer_t * pool = policy->peers.pool; - fd_policy_peer_t * peer = fd_policy_peer_map_ele_query( peer_map, key, NULL, pool ); - if( FD_UNLIKELY( !peer && fd_policy_peer_pool_free( pool ) ) ) { - peer = fd_policy_peer_pool_ele_acquire( pool ); - peer->key = *key; - peer->ip4 = addr->addr; - peer->port = addr->port; - peer->req_cnt = 0; - peer->res_cnt = 0; - peer->first_req_ts = 0; - peer->last_req_ts = 0; - peer->first_resp_ts = 0; - peer->last_resp_ts = 0; - peer->total_lat = 0; - peer->ewma_lat = 0; - peer->stake = 0; - peer->unanswered = 0; - peer->ping = 0; - - fd_policy_peer_map_ele_insert( peer_map, peer, pool ); - fd_policy_peer_dlist_ele_push_tail( policy->peers.slow, peer, pool ); - return peer; - } - if( FD_LIKELY( peer ) ) { - peer->ip4 = addr->addr; - peer->port = addr->port; - } - return NULL; -} - -fd_policy_peer_t * -fd_policy_peer_query( fd_policy_t * policy, fd_pubkey_t const * key ) { - if( FD_UNLIKELY( memcmp( key->key, null_pubkey.key, 32UL ) == 0 ) ) return NULL; - fd_policy_peer_t * pool = policy->peers.pool; - return fd_policy_peer_map_ele_query( policy->peers.map, key, NULL, pool ); -} - -int -fd_policy_peer_remove( fd_policy_t * policy, fd_pubkey_t const * key ) { - fd_policy_peer_t * pool = policy->peers.pool; - fd_policy_peer_t * peer = fd_policy_peer_map_ele_query( policy->peers.map, key, NULL, pool ); - if( FD_UNLIKELY( !peer ) ) return 0; - - ulong peer_idx = fd_policy_peer_pool_idx( pool, peer ); - fd_policy_peer_dlist_t * bucket = fd_policy_peer_latency_bucket( policy, peer->ewma_lat, peer->res_cnt ); - - /* Advance iterators past the peer being removed while the dlist links - are still intact, so iter_fwd_next can follow the forward pointer. */ - if( FD_UNLIKELY( policy->peers.select.fast_iter == peer_idx ) ) { - policy->peers.select.fast_iter = fd_policy_peer_dlist_iter_fwd_next( policy->peers.select.fast_iter, bucket, pool ); - } - if( FD_UNLIKELY( policy->peers.select.slow_iter == peer_idx ) ) { - policy->peers.select.slow_iter = fd_policy_peer_dlist_iter_fwd_next( policy->peers.select.slow_iter, bucket, pool ); - } - - fd_policy_peer_dlist_ele_remove( bucket, peer, pool ); - fd_policy_peer_map_ele_remove ( policy->peers.map, key, NULL, pool ); - fd_policy_peer_pool_ele_release( pool, peer ); - return 1; -} - -void -fd_policy_peer_request_update( fd_policy_t * policy, fd_pubkey_t const * to ) { - fd_policy_peer_t * active = fd_policy_peer_query( policy, to ); - if( FD_LIKELY( active ) ) { - active->req_cnt++; - active->unanswered++; - active->last_req_ts = fd_tickcount(); - if( FD_UNLIKELY( active->first_req_ts == 0 ) ) active->first_req_ts = active->last_req_ts; - } -} - -void -fd_policy_peer_response_update( fd_policy_t * policy, fd_pubkey_t const * to, long rtt /* ns */ ) { - fd_policy_peer_t * peer = fd_policy_peer_query( policy, to ); - if( FD_LIKELY( peer ) ) { - long now = fd_tickcount(); - fd_policy_peer_dlist_t * prev_bucket = fd_policy_peer_latency_bucket( policy, peer->ewma_lat, peer->res_cnt ); - peer->res_cnt++; - peer->unanswered = 0; - if( FD_UNLIKELY( peer->first_resp_ts == 0 ) ) peer->first_resp_ts = now; - peer->last_resp_ts = now; - peer->total_lat += rtt; - - if( FD_UNLIKELY( peer->res_cnt == 1 ) ) { - peer->ewma_lat = rtt; - } else { - peer->ewma_lat = peer->ewma_lat - peer->ewma_lat / (long)FD_POLICY_EWMA_ALPHA_DENOM - + rtt / (long)FD_POLICY_EWMA_ALPHA_DENOM; - } - fd_policy_peer_dlist_t * new_bucket = fd_policy_peer_latency_bucket( policy, peer->ewma_lat, peer->res_cnt ); - if( prev_bucket != new_bucket ) { - /* Advance stale iterators */ - ulong peer_idx = fd_policy_peer_pool_idx( policy->peers.pool, peer ); - if( FD_UNLIKELY( policy->peers.select.fast_iter == peer_idx ) ) policy->peers.select.fast_iter = fd_policy_peer_dlist_iter_fwd_next( policy->peers.select.fast_iter, policy->peers.fast, policy->peers.pool ); - if( FD_UNLIKELY( policy->peers.select.slow_iter == peer_idx ) ) policy->peers.select.slow_iter = fd_policy_peer_dlist_iter_fwd_next( policy->peers.select.slow_iter, policy->peers.slow, policy->peers.pool ); - - fd_policy_peer_dlist_ele_remove ( prev_bucket, peer, policy->peers.pool ); - fd_policy_peer_dlist_ele_push_tail( new_bucket, peer, policy->peers.pool ); - } - } -} - -void -fd_policy_set_turbine_slot0( fd_policy_t * policy, ulong slot ) { - policy->turbine_slot0 = slot; -} - diff --git a/src/discof/repair/fd_policy.h b/src/discof/repair/fd_policy.h deleted file mode 100644 index bd3c4680dc8..00000000000 --- a/src/discof/repair/fd_policy.h +++ /dev/null @@ -1,241 +0,0 @@ -#ifndef HEADER_fd_src_discof_repair_fd_policy_h -#define HEADER_fd_src_discof_repair_fd_policy_h - -/* fd_policy implements the policy of the Repair agent. It determines - what next repair request the validator should make. It also - determines which peer(s) the validator should make the request to. - - The default policy implementation is to prioritize discovering - ancestry for orphaned slots first (making an orphan request), and - then making forward progress on the main ancestry tree (making a - regular request) when there are no orphan requests to make. - - Regular shred requests are made round-robin BFS with time-based - dedup: round-robin through all the repair peers we know about, and - BFS down the repair forest (see fd_forest.h). - - This policy dedups identical repair requests that occur within a - specified amount of time window of each other. */ - -#include "../forest/fd_forest.h" -#include "../../util/net/fd_net_headers.h" -#include "fd_repair.h" -#include "fd_reqlim.h" -#include "../../disco/shred/fd_rnonce_ss.h" - -/* fd_policy_peer_t describes a peer validator that serves repairs. - Peers are discovered through gossip, via a "ContactInfo" message that - shares the validator's ip and repair server port. */ - -struct fd_policy_peer { - fd_pubkey_t key; /* map key, pubkey of the validator */ - ulong next; /* reserved for map_chain, pool */ - uint ip4; /* ip4 addr of the peer */ - ushort port; /* repair server port of the peer */ - ulong req_cnt; /* count of requests we've sent to this peer */ - ulong res_cnt; /* count of responses we've received from this peer */ - - struct { - ulong next; - ulong prev; - } dlist; - - /* below are for measuring bandwidth usage */ - long first_req_ts; - long last_req_ts; - - long first_resp_ts; - long last_resp_ts; - - long total_lat; /* total RTT over all responses in ns */ - long ewma_lat; /* exponential weighted moving average of RTT in ns */ - ulong stake; - - uint unanswered; /* requests sent since last response received */ - uint ping; /* whether this peer currently has a ping in our sign queue */ -}; -typedef struct fd_policy_peer fd_policy_peer_t; - -#define MAP_NAME fd_policy_peer_map -#define MAP_ELE_T fd_policy_peer_t -#define MAP_KEY_T fd_pubkey_t -#define MAP_KEY_EQ(k0,k1) (!memcmp( (k0)->uc, (k1)->uc, 32UL )) -#define MAP_KEY_HASH(key,seed) fd_hash( (seed), (key)->uc, sizeof(fd_pubkey_t) ) -#include "../../util/tmpl/fd_map_chain.c" - -#define POOL_NAME fd_policy_peer_pool -#define POOL_T fd_policy_peer_t -#include "../../util/tmpl/fd_pool.c" - -#define DLIST_NAME fd_policy_peer_dlist -#define DLIST_ELE_T fd_policy_peer_t -#define DLIST_NEXT dlist.next -#define DLIST_PREV dlist.prev -#include "../../util/tmpl/fd_dlist.c" - -/* fd_policy_peers implements the data structures and bookkeeping for - selecting repair peers via round-robin. */ - -struct fd_policy_peers { - fd_policy_peer_t * pool; /* memory pool of peers */ - fd_policy_peer_dlist_t * fast; /* peers with ewma RTT <= FD_POLICY_LATENCY_THRESH */ - fd_policy_peer_dlist_t * slow; /* peers with ewma RTT > FD_POLICY_LATENCY_THRESH or no RTT measured */ - fd_policy_peer_map_t * map; /* map keyed by pubkey to peer data */ - struct { - uint cnt; /* fast selections since last slow, wraps at FD_POLICY_FAST_PER_SLOW */ - fd_policy_peer_dlist_iter_t fast_iter; /* round-robin iterator into fast list */ - fd_policy_peer_dlist_iter_t slow_iter; /* round-robin iterator into slow list */ - } select; -}; -typedef struct fd_policy_peers fd_policy_peers_t; - -/* Policy parameters start */ -#define FD_POLICY_LATENCY_THRESH 100e6L /* less than this is a BEST peer, otherwise a WORST peer */ -#define FD_POLICY_FAST_PER_SLOW 6U /* pick 6 fast peers per 1 slow peer */ -#define FD_POLICY_EWMA_ALPHA_DENOM 8UL /* EWMA weight = 1/DENOM, i.e. ewma = 7/8*old + 1/8*sample */ -#define FD_POLICY_SKIP_CNT 4UL /* per-slot skip memos */ -/* Policy parameters end */ - -/* fd_policy_skip memoizes the last declined request for a slot so that - fd_policy_next can skip its turn without re-deriving the decline. - Memos are direct-mapped by slot: the requests list may flip between - throttled slots so we need to track >=2 (turbine head and its parent). */ -struct fd_policy_skip { - ulong slot; - uint idx; - int throttled; - long until; -}; -typedef struct fd_policy_skip fd_policy_skip_t; - -struct fd_policy { - fd_policy_peers_t peers; /* repair peers (strategy & data) */ - long tsmax; /* maximum time for an iteration before resetting the DFS to root */ - long tsref; /* reference timestamp for resetting DFS */ - - fd_policy_skip_t skip[ FD_POLICY_SKIP_CNT ]; - - fd_rnonce_ss_t rnonce_ss[1]; - - ulong turbine_slot0; -}; -typedef struct fd_policy fd_policy_t; - -/* fd_policy_skip returns the skip memo that slot maps to. */ - -FD_FN_PURE static inline fd_policy_skip_t * -fd_policy_skip( fd_policy_t * policy, ulong slot ) { - return &policy->skip[ slot & (FD_POLICY_SKIP_CNT-1UL) ]; -} - -/* fd_policy_peer_cnt returns how many repair peers are known. */ - -FD_FN_PURE static inline ulong -fd_policy_peer_cnt( fd_policy_t const * policy ) { - return fd_policy_peer_pool_used( policy->peers.pool ); -} - -/* Constructors */ - -/* fd_policy_{align,footprint} return the required alignment and - footprint of a memory region suitable for use as policy with up to - ele_max eles and vote_max votes. */ - -FD_FN_CONST static inline ulong -fd_policy_align( void ) { - return 128UL; -} - -FD_FN_CONST static inline ulong -fd_policy_footprint( ulong peer_max ) { - ulong peer_chain_cnt = fd_policy_peer_map_chain_cnt_est( peer_max ); - return FD_LAYOUT_FINI( - FD_LAYOUT_APPEND( - FD_LAYOUT_APPEND( - FD_LAYOUT_APPEND( - FD_LAYOUT_APPEND( - FD_LAYOUT_APPEND( - FD_LAYOUT_INIT, - fd_policy_align(), sizeof(fd_policy_t) ), - fd_policy_peer_map_align(), fd_policy_peer_map_footprint ( peer_chain_cnt ) ), - fd_policy_peer_pool_align(), fd_policy_peer_pool_footprint( peer_max ) ), - fd_policy_peer_dlist_align(), fd_policy_peer_dlist_footprint() ), - fd_policy_peer_dlist_align(), fd_policy_peer_dlist_footprint() ), - fd_policy_align() ); -} - -/* fd_policy_new formats an unused memory region for use as a policy. - mem is a non-NULL pointer to this region in the local address space - with the required footprint and alignment. rnonce_ss is copied - locally, so the read interest is not retained after this function - returns. */ - -void * -fd_policy_new( void * shmem, ulong peer_max, ulong seed, fd_rnonce_ss_t const * rnonce_ss ); - -/* fd_policy_join joins the caller to the policy. policy points to the - first byte of the memory region backing the policy in the caller's - address space. Returns a pointer in the local address space to - policy on success. */ - -fd_policy_t * -fd_policy_join( void * policy ); - -/* fd_policy_leave leaves a current local join. Returns a pointer to - the underlying shared memory region on success and NULL on failure - (logs details). Reasons for failure include policy is NULL. */ - -void * -fd_policy_leave( fd_policy_t const * policy ); - -/* fd_policy_delete unformats a memory region used as a policy. Assumes - only the nobody is joined to the region. Returns a pointer to the - underlying shared memory region or NULL if used obviously in error - (e.g. policy is obviously not a policy ... logs details). The - ownership of the memory region is transferred to the caller. */ - -void * -fd_policy_delete( void * policy ); - -/* fd_policy_next returns the next repair request that should be made. - Currently wraps on top of the forest iterator, but also handles - making orphan requests and highest shred requests. For non-normal - repair requests, policy uses the dedup cache to deduplicate requests. - For all normal requests, the caller must check the dedup cache before - making a request. */ - -fd_repair_msg_t const * -fd_policy_next( fd_policy_t * policy, fd_reqlim_t * dedup, fd_forest_t * forest, fd_repair_t * repair, long now, ulong highest_known_slot, int * charge_busy ); - -/* fd_policy_peer_upsert upserts a peer into the policy. If the peer - does not exist, it is created. If the peer already exists, it is - updated. Returns a pointer to the peer if a new peer was created, - otherwise NULL (including on updates). */ -fd_policy_peer_t const * -fd_policy_peer_upsert( fd_policy_t * policy, fd_pubkey_t const * key, fd_ip4_port_t const * addr ); - -fd_policy_peer_t * -fd_policy_peer_query( fd_policy_t * policy, fd_pubkey_t const * key ); - -int -fd_policy_peer_remove( fd_policy_t * policy, fd_pubkey_t const * key ); - -fd_pubkey_t const * -fd_policy_peer_select( fd_policy_t * policy ); - -void -fd_policy_peer_request_update( fd_policy_t * policy, fd_pubkey_t const * to ); - -static inline fd_policy_peer_dlist_t * -fd_policy_peer_latency_bucket( fd_policy_t * policy, long lat, ulong res_cnt ) { - if( res_cnt == 0 || lat > FD_POLICY_LATENCY_THRESH ) return policy->peers.slow; - return policy->peers.fast; -} - -void -fd_policy_peer_response_update( fd_policy_t * policy, fd_pubkey_t const * to, long rtt ); - -void -fd_policy_set_turbine_slot0( fd_policy_t * policy, ulong slot ); - -#endif /* HEADER_fd_src_choreo_policy_fd_policy_h */ diff --git a/src/discof/repair/fd_repair_metrics.c b/src/discof/repair/fd_repair_metrics.c deleted file mode 100644 index 2d119197fec..00000000000 --- a/src/discof/repair/fd_repair_metrics.c +++ /dev/null @@ -1,215 +0,0 @@ -#include "fd_repair_metrics.h" -#include -#include - -#include "../../disco/metrics/fd_metrics.h" - -void * -fd_repair_metrics_new( void * mem ) { - fd_repair_metrics_t * repair_metrics = (fd_repair_metrics_t *)mem; - repair_metrics->st = UINT_MAX; - repair_metrics->en = UINT_MAX; - repair_metrics->turbine_slot0 = 0; - - return repair_metrics; -} - -fd_repair_metrics_t * -fd_repair_metrics_join( void * repair_metrics ) { - return (fd_repair_metrics_t *)repair_metrics; -} - -void -fd_repair_metrics_set_turbine_slot0( fd_repair_metrics_t * repair_metrics, ulong turbine_slot0 ) { - repair_metrics->turbine_slot0 = turbine_slot0; -} - -void -fd_repair_metrics_add_slot( fd_repair_metrics_t * repair_metrics, - ulong slot, - long first_shred_ts, - long slot_complete_ts, - uint repair_cnt, - uint turbine_cnt ) { - uint next_en = (repair_metrics->en + 1) % FD_CATCHUP_METRICS_MAX; - if( FD_UNLIKELY( next_en == repair_metrics->st || repair_metrics->st == UINT_MAX ) ) { - repair_metrics->st = (repair_metrics->st + 1) % FD_CATCHUP_METRICS_MAX; - } - repair_metrics->slots[ next_en ].slot = slot; - repair_metrics->slots[ next_en ].first_shred_ts = first_shred_ts; - repair_metrics->slots[ next_en ].slot_complete_ts = slot_complete_ts; - repair_metrics->slots[ next_en ].repair_cnt = repair_cnt; - repair_metrics->slots[ next_en ].turbine_cnt = turbine_cnt; - repair_metrics->en = next_en; -} - -#define MAX_WIDTH 120 -static char dashes[MAX_WIDTH + 1] = "========================================================================================================================"; -static char spaces[MAX_WIDTH + 1] = " "; - -#define print_slot_interval_bar( slot_metrics, tick_sz, min_ts, verbose ) \ - long duration = slot_metrics->slot_complete_ts - slot_metrics->first_shred_ts; \ - int width = (int)((double)(duration) / tick_sz); \ - int start = (int)((double)(slot_metrics->first_shred_ts - min_ts) / tick_sz); \ - if( FD_UNLIKELY( verbose ) ) { \ - printf( "%lu [repaired: %u/%u]%.*s|%.*s| (%.2f ms)", \ - slot_metrics->slot, \ - slot_metrics->repair_cnt, slot_metrics->turbine_cnt + slot_metrics->repair_cnt, \ - start, spaces, width, dashes, \ - (double)fd_metrics_convert_ticks_to_nanoseconds((ulong)duration) / 1e6 ); \ - } else { \ - printf( "%lu %.*s|%.*s| (%.2f ms)", \ - slot_metrics->slot, \ - start, spaces, width, dashes, \ - (double)fd_metrics_convert_ticks_to_nanoseconds((ulong)duration) / 1e6 ); \ - } \ - if( FD_UNLIKELY( slot_metrics->slot == repair_metrics->turbine_slot0 ) ) { \ - printf( " <--- (first turbine shred received)" ); \ - } \ - printf( "\n" ); - -static int -compare_slots( const void * a, const void * b ) { - const fd_slot_metrics_t * slot_a = (const fd_slot_metrics_t *)a; - const fd_slot_metrics_t * slot_b = (const fd_slot_metrics_t *)b; - - if( slot_a->slot < slot_b->slot ) return -1; - if( slot_a->slot > slot_b->slot ) return 1; - return 0; -} - -/* Should be typically only called once, at the end of a catchup. Other - wise the information is not meaningful (during regular turbine, the - slots are pretty much sequentially arriving in order anyway) */ -void -fd_repair_metrics_print_sorted( fd_repair_metrics_t * repair_metrics, int verbose, fd_slot_metrics_t * temp_slots ) { - if( repair_metrics->st == UINT_MAX ) return; // no data to sort - - uint temp_idx = 0; - long min_ts = repair_metrics->slots[ repair_metrics->st ].first_shred_ts; - long max_ts = repair_metrics->slots[ repair_metrics->en ].slot_complete_ts; - long repair_kickoff_ts = 0; /* When we receive the first turbine shred, is when we begin orphans. */ - long finish_catchup_ts = 0; /* the max of all slot < turbine slot0 completion times */ - int num_catchup_slots = 0; - uint total_slots = 0; - long slot_durations_sum = 0; - long incremental_cmpl_sum = 0; - long prev_slot_ts = repair_metrics->slots[ repair_metrics->st ].slot_complete_ts; - - for( uint i = repair_metrics->st;; i = (i + 1) % FD_CATCHUP_METRICS_MAX ) { - fd_slot_metrics_t * slot_data = &repair_metrics->slots[ i ]; - temp_slots[ temp_idx++ ] = *slot_data; - total_slots++; - min_ts = fd_min( min_ts, slot_data->first_shred_ts ); - max_ts = fd_max( max_ts, slot_data->slot_complete_ts ); - if( FD_UNLIKELY( slot_data->slot == repair_metrics->turbine_slot0 ) ) repair_kickoff_ts = slot_data->first_shred_ts; - if( FD_UNLIKELY( slot_data->slot <= repair_metrics->turbine_slot0 ) ) finish_catchup_ts = fd_max( finish_catchup_ts, slot_data->slot_complete_ts ); - if( FD_UNLIKELY( slot_data->slot <= repair_metrics->turbine_slot0 ) ) num_catchup_slots++; - slot_durations_sum += slot_data->slot_complete_ts - slot_data->first_shred_ts; - - incremental_cmpl_sum += slot_data->slot_complete_ts - prev_slot_ts; - prev_slot_ts = slot_data->slot_complete_ts; - - if( i == repair_metrics->en ) break; - } - - /* Sort temp array by slot */ - qsort( temp_slots, total_slots, sizeof(fd_slot_metrics_t), compare_slots ); - - /* prints a stacked depth chart of the catchup metrics: - slot |===============| (duration in ms) - slot |================| - etc. */ - - double tick_sz = (double)(max_ts - min_ts) / (double)MAX_WIDTH; - long orphan_cmpl_ts = temp_slots[0].first_shred_ts; /* When we make the request for the snapshot slot, this is about when the full tree is connected. */ - int orphans_cmpl_cnt = 0; /* Count of slots completed by the time orphan requests are done */ - for( uint i = 0; i < total_slots; i++ ) { - fd_slot_metrics_t * slot_metrics = &temp_slots[ i ]; - if( FD_UNLIKELY( slot_metrics->slot_complete_ts < orphan_cmpl_ts ) ) orphans_cmpl_cnt++; - print_slot_interval_bar( slot_metrics, tick_sz, min_ts, verbose ); - } - fflush( stdout ); - - - double pipelined_time = (double)(max_ts - min_ts); - double non_pipelined_time = (double)slot_durations_sum; - FD_LOG_NOTICE(( "\n" - "Completed %u slots in %.2f seconds total. \n" - "Average slot duration (time from first shred/rq to all shreds received): %.2f ms\n" - "Average time between slot completions: %.2f ms\n" - "Average slots per second: %.2f\n" - "Pipeline factor (sum duration of all slots / total time): %.2f\n", - total_slots, - (double)fd_metrics_convert_ticks_to_nanoseconds((ulong)pipelined_time) / 1e9, - (double)fd_metrics_convert_ticks_to_nanoseconds((ulong)slot_durations_sum) / (double)total_slots / 1e6, - (double)fd_metrics_convert_ticks_to_nanoseconds((ulong)incremental_cmpl_sum) / (double)total_slots / 1e6, - (double)total_slots / (double)fd_metrics_convert_ticks_to_nanoseconds((ulong)pipelined_time) * 1e9, - non_pipelined_time / pipelined_time )); - - FD_LOG_NOTICE(( "\n" - "Caught up %d slots in %.2f ms total. \n" - "Time to repair orphans: %.2f ms \n" - "Total time from connected orphan to done: %.2f ms \n" - "Slots completed by orphans connected: %d\n", - num_catchup_slots, - (double)fd_metrics_convert_ticks_to_nanoseconds((ulong)(finish_catchup_ts - repair_kickoff_ts)) / 1e6, - (double)fd_metrics_convert_ticks_to_nanoseconds((ulong)(orphan_cmpl_ts - repair_kickoff_ts)) / 1e6, - (double)fd_metrics_convert_ticks_to_nanoseconds((ulong)(finish_catchup_ts - orphan_cmpl_ts )) / 1e6, - orphans_cmpl_cnt )); -} - -void -fd_repair_metrics_print( fd_repair_metrics_t * repair_metrics, int verbose ) { - if( repair_metrics->st == UINT_MAX ) return; // no data to print - long min_ts = repair_metrics->slots[ repair_metrics->st ].first_shred_ts; - long max_ts = repair_metrics->slots[ repair_metrics->en ].slot_complete_ts; - uint total_slots = 0; - long prev_slot_ts = repair_metrics->slots[ repair_metrics->st ].slot_complete_ts; - long incremental_cmpl_sum = 0; - - long slot_durations_sum = 0; - for( uint i = repair_metrics->st;; i = (i + 1) % FD_CATCHUP_METRICS_MAX ) { - fd_slot_metrics_t * slot_data = &repair_metrics->slots[ i ]; - slot_durations_sum += (slot_data->slot_complete_ts - slot_data->first_shred_ts); - total_slots++; - min_ts = fd_min( min_ts, slot_data->first_shred_ts ); - max_ts = fd_max( max_ts, slot_data->slot_complete_ts ); - - /* st -> en are already ordered by completion time. so this-prev - is guaranteed to be > 0 */ - incremental_cmpl_sum += slot_data->slot_complete_ts - prev_slot_ts; - prev_slot_ts = slot_data->slot_complete_ts; - - if( FD_UNLIKELY( i == repair_metrics->en ) ) break; - } - - /* prints a stacked depth chart of the catchup metrics: - slot |===============| (duration in ms) - slot |================| - etc. */ - - double tick_sz = (double)(max_ts - min_ts) / (double)MAX_WIDTH; - for( uint i = repair_metrics->st;; i = (i + 1) % FD_CATCHUP_METRICS_MAX ) { - fd_slot_metrics_t * slot_metrics = &repair_metrics->slots[ i ]; - print_slot_interval_bar( slot_metrics, tick_sz, min_ts, verbose ); - if( i == repair_metrics->en ) break; - } - fflush( stdout ); - - double pipelined_time = (double)(max_ts - min_ts); - double non_pipelined_time = (double)slot_durations_sum; - FD_LOG_NOTICE(( "\n" - "Over past %u completed slots: \n" - "Average slot duration (time from first shred/rq to all shreds received): %.2f ms\n" - "Average time between slot completions: %.2f ms\n" - "Average slots per second: %.2f\n" - "Pipeline factor (sum duration of all slots / total time): %.2f\n", - total_slots, - (double)fd_metrics_convert_ticks_to_nanoseconds((ulong)slot_durations_sum) / (double)total_slots / 1e6, - (double)fd_metrics_convert_ticks_to_nanoseconds((ulong)incremental_cmpl_sum) / (double)total_slots / 1e6, - (double)total_slots / (double)fd_metrics_convert_ticks_to_nanoseconds((ulong)pipelined_time) * 1e9, - non_pipelined_time / pipelined_time )); -} - -#undef MAX_WIDTH diff --git a/src/discof/repair/fd_repair_metrics.h b/src/discof/repair/fd_repair_metrics.h deleted file mode 100644 index 20f5b7267ce..00000000000 --- a/src/discof/repair/fd_repair_metrics.h +++ /dev/null @@ -1,73 +0,0 @@ -#ifndef HEADER_fd_src_discof_repair_fd_repair_metrics_h -#define HEADER_fd_src_discof_repair_fd_repair_metrics_h - -/* fd_repair_metrics tracks metadata on the N most recent slots, in particular - the time it took to complete the slot. As this purpose of this - module currently is exclusively to print a waterfall diagram of the - repair_metrics progress, this is a circular buffer of the last N slots, - where N=256 and is non-configurable. */ - -#include "../../util/fd_util_base.h" - -struct fd_slot_metrics { - ulong slot; - - long first_shred_ts; - long slot_complete_ts; /* tick */ - - uint repair_cnt; - uint turbine_cnt; -}; -typedef struct fd_slot_metrics fd_slot_metrics_t; - -struct fd_shred_metrics { - ulong slot; - uint shred_idx; - ulong req_cnt; - ulong res_cnt; -}; -typedef struct fd_shred_metrics fd_shred_metrics_t; - -#define FD_CATCHUP_METRICS_MAX 16384 - -struct fd_repair_metrics_t { - fd_slot_metrics_t slots[FD_CATCHUP_METRICS_MAX]; - fd_shred_metrics_t shreds[FD_CATCHUP_METRICS_MAX]; - uint st; - uint en; - ulong turbine_slot0; -}; -typedef struct fd_repair_metrics_t fd_repair_metrics_t; - - -FD_FN_CONST static inline ulong -fd_repair_metrics_align( void ) { - return alignof( fd_slot_metrics_t ); -} - -FD_FN_CONST static inline ulong -fd_repair_metrics_footprint( void ) { - return sizeof( fd_repair_metrics_t ); -} - -void * -fd_repair_metrics_new( void * mem ); - -fd_repair_metrics_t * -fd_repair_metrics_join( void * repair_metrics ); - -void -fd_repair_metrics_set_turbine_slot0( fd_repair_metrics_t * repair_metrics, ulong turbine_slot0 ); - -void -fd_repair_metrics_print( fd_repair_metrics_t * repair_metrics, int verbose ); - -void -fd_repair_metrics_print_sorted( fd_repair_metrics_t * repair_metrics, int verbose, fd_slot_metrics_t * temp_slots ); - -void -fd_repair_metrics_add_slot( fd_repair_metrics_t * repair_metrics, ulong slot, long first_ts, long slot_complete_ts, uint repair_cnt, uint turbine_cnt ); - - - -#endif /* HEADER_fd_src_discof_repair_fd_repair_metrics_h */ diff --git a/src/discof/repair/fd_repair_tile.c b/src/discof/repair/fd_repair_tile.c deleted file mode 100644 index 8c58a91f2b4..00000000000 --- a/src/discof/repair/fd_repair_tile.c +++ /dev/null @@ -1,1383 +0,0 @@ -/* The repair tile is responsible for repairing missing shreds that were - not received via Turbine. The goal is to ensure that slots we "care" - about have their FEC sets inserted into store. - - Generally there are two distinct traffic patterns: - - a. Firedancer boots up and fires off a large number of repairs to - recover all the blocks between the snapshot on which it is booting - and the head of the chain. In this mode, repair tile utilization - is very high along with net and sign utilization. - - b. Firedancer catches up to the head of the chain and enters steady - state where most shred traffic is delivered over turbine. In this - state, repairs are only occasionally needed to recover shreds lost - due to anomalies like packet loss, transmitter (leader) never sent - them or even a malicious leader etc. On rare occasion, repair - will also need to recover a different version of a block that - equivocated. - - To accomplish the above, repair mainly processes 4 kinds of frags: - - 1. Shred data (from shred tile) - - Any shred (coding or data) that passes validation and filtering in - the shred tile is forwarded to repair. Repair uses these to track - which shreds have been received in `fd_forest`, a tree data - structure that mirrors the block/slot ancestry chain. It also - uses these shreds to discover slots or ancestries that were not - known. fd_forest tracks metadata for each slot, including slot - completion status, merkle roots, and metrics. - - Any shred that we can correlate with a repair request we made is - used to update peer response latency metrics in fd_policy (See - fd_policy.h for more details). - - 2. FEC status messages (from shred tile) - - These fall under two categories: FEC completion and FEC eviction. - When all shreds in a FEC set have been recovered, the shred tile - sends a completion message. This may trigger chained merkle - verification if the slot has a confirmed block_id. The completed - FEC message is always forwarded to replay via repair_out. - - When an incomplete FEC set is evicted from the shred tile's FEC - resolver (e.g. due to capacity), it also notifies repair. Repair - clears the corresponding FEC set entries from the forest so those - shred indices can be re-requested if they are necessary. As - mentioned in fd_forest.h, forest needs to maintain a strict subset - of shreds that are known by fec_resolver, store, and reasm in - order to guarantee forward progress always. - - 3. Pings (from net tile) - - Repair peers use a ping-pong protocol to verify liveness before - serving repair requests. When a ping arrives over the network, - repair validates the message and constructs a pong response. To - prevent spam attacks, repair has stopgaps like tracking how many - pongs per peer are currently in the sign queue and dropping pings - from unknown peers. These are the only untrusted inputs to - repair. - - 4. Sign task responses (from sign tile) - - Repair requests are signed asynchronously; the repair tile - constructs a repair request, dispatches it to a sign tile via the - repair_sign output link. The repair-sign communication is - manually managed via credit tracking in the repair tile (see - comment in out_ctx_t struct definition). - - After receiving the signature back from sign tile, repair injects - the signature into the pending request and dispatches it to the - net tile via the repair_net output link. The behavior depends on - the request type: - - Pong: the signed pong is sent to the peer that pinged us. - - Warmup: a proactive request sent when a new peer's contact info - first arrives, prepaying the ping-pong RTT cost. The signed - request is sent to the peer if it is still active. - - Regular shred request: the request is recorded in an inflight - table (for tracking response latency and timeouts) and the - signed packet is sent to the selected peer. - - Secondary "other" frags that are processed but not part of core - repair logic: - - 5. Confirmation messages from tower - - Tower sends two kinds of messages relevant to repair: - - slot_done: indicates a slot has finished replay and may advance - the root. Repair publishes (roots) the forest up to that slot, - pruning old ancestry. - - slot_confirmed: indicates a slot has reached a confirmation - level (e.g. duplicate-confirmed). If the slot is not yet in the - forest, repair creates a sentinel block so it can be repaired. - It also stores the confirmed block_id and may trigger chained - merkle verification. See fd_forest.h on more details about - chained merkle verification. - - 6. Eviction messages from replay (reasm) - - When the replay tile's reassembly buffer evicts a FEC set (e.g. - due to pool capacity) from itself and from store, it notifies - repair with the slot and fec_set_idx. Repair clears those FEC - entries from the forest so the shreds can be re-requested. - - 7. Contact info messages from gossip - - Gossip forwards contact info updates and removals for other - validators. Repair uses these to maintain a list of peers to - make requests to. - - If fd_forest tracks what we know about each shred, fd_policy and - fd_inflights is responsible for deciding what next repair request to - make. fd_policy and fd_inflights split responsibility: fd_policy - makes any new requests, orphan requests, and requests directly off - the forest iterator, while fd_inflights re-requests anything that has - been requested but not received yet within a timeout window. */ - -#define _GNU_SOURCE - -#include "../../disco/topo/fd_topo.h" -#include "../../disco/fd_clock_tile.h" -#include -#include "generated/fd_repair_tile_seccomp.h" -#include "../../disco/keyguard/fd_keyload.h" -#include "../../disco/keyguard/fd_keyguard.h" -#include "../../disco/keyguard/fd_keyswitch.h" -#include "../../disco/metrics/fd_metrics.h" -#include "../../disco/net/fd_net_tile.h" -#include "../../disco/shred/fd_rnonce_ss.h" -#include "../../disco/shred/fd_shred_tile.h" -#include "fd_repair_tile.h" -#include "../../flamenco/gossip/fd_gossip_message.h" -#include "../replay/fd_replay_tile.h" -#include "../tower/fd_tower_tile.h" -#include "../../discof/restore/utils/fd_ssmsg.h" -#include "../../util/net/fd_net_headers.h" -#include "../../util/pod/fd_pod_format.h" -#include "../../tango/fd_tango_base.h" - -#include "../forest/fd_forest.h" -#include "fd_repair_metrics.h" -#include "fd_inflight.h" -#include "fd_repair.h" -#include "fd_policy.h" -#include "fd_repair_tile_private.h" - -#define DEBUG_LOGGING 0 - -#define IN_KIND_CONTACT (0) -#define IN_KIND_NET (1) -#define IN_KIND_TOWER (2) -#define IN_KIND_SHRED (3) -#define IN_KIND_SIGN (4) -#define IN_KIND_SNAP (5) -#define IN_KIND_GOSSIP (6) -#define IN_KIND_REPLAY (7) - -/* static map from request type to metric array index */ -static uint metric_index[FD_REPAIR_KIND_ORPHAN + 1] = { - [FD_REPAIR_KIND_PONG] = FD_METRICS_ENUM_REPAIR_REQUEST_TYPE_V_PONG_IDX, - [FD_REPAIR_KIND_SHRED] = FD_METRICS_ENUM_REPAIR_REQUEST_TYPE_V_WINDOW_INDEX_IDX, - [FD_REPAIR_KIND_HIGHEST_SHRED] = FD_METRICS_ENUM_REPAIR_REQUEST_TYPE_V_HIGHEST_WINDOW_INDEX_IDX, - [FD_REPAIR_KIND_ORPHAN] = FD_METRICS_ENUM_REPAIR_REQUEST_TYPE_V_ORPHAN_IDX, -}; - -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 ) { - ulong total_sign_depth = tile->repair.repair_sign_depth * tile->repair.repair_sign_cnt; - int lg_sign_depth = fd_ulong_find_msb( fd_ulong_pow2_up(total_sign_depth) ) + 1; - - ulong l = FD_LAYOUT_INIT; - l = FD_LAYOUT_APPEND( l, alignof(ctx_t), sizeof(ctx_t) ); - l = FD_LAYOUT_APPEND( l, fd_repair_align(), fd_repair_footprint () ); - l = FD_LAYOUT_APPEND( l, fd_forest_align(), fd_forest_footprint ( tile->repair.slot_max, tile->repair.max_shreds_per_block ) ); - l = FD_LAYOUT_APPEND( l, fd_policy_align(), fd_policy_footprint ( FD_REPAIR_PEER_MAX ) ); - l = FD_LAYOUT_APPEND( l, fd_reqlim_align(), fd_reqlim_footprint ( FD_REQLIM_CACHE_MAX ) ); - l = FD_LAYOUT_APPEND( l, fd_inflights_align(), fd_inflights_footprint () ); - l = FD_LAYOUT_APPEND( l, fd_signs_map_align(), fd_signs_map_footprint ( lg_sign_depth ) ); - l = FD_LAYOUT_APPEND( l, fd_signs_queue_align(), fd_signs_queue_footprint() ); - l = FD_LAYOUT_APPEND( l, fd_repair_metrics_align(), fd_repair_metrics_footprint() ); - return FD_LAYOUT_FINI( l, scratch_align() ); -} - -/* Below functions manage the current pending sign requests. */ - -static sign_req_t * -sign_map_insert( ctx_t * ctx, - fd_repair_msg_t const * msg, - pong_data_t const * opt_pong_data ) { - - /* Check if there is any space for a new pending sign request. Should never fail as long as credit management is working. */ - if( FD_UNLIKELY( fd_signs_map_key_cnt( ctx->signs_map )==fd_signs_map_key_max( ctx->signs_map ) ) ) return NULL; - - sign_req_t * pending = fd_signs_map_insert( ctx->signs_map, ctx->pending_key_next++ ); - if( FD_UNLIKELY( !pending ) ) return NULL; /* Not possible, unless the same key is used twice. */ - pending->msg = *msg; - pending->buflen = fd_repair_sz( msg ); - if( FD_UNLIKELY( opt_pong_data ) ) pending->pong_data = *opt_pong_data; - return pending; -} - -static int -sign_map_remove( ctx_t * ctx, - ulong key ) { - sign_req_t * pending = fd_signs_map_query( ctx->signs_map, key, NULL ); - if( FD_UNLIKELY( !pending ) ) return -1; - fd_signs_map_remove( ctx->signs_map, pending ); - return 0; -} - -static void -send_packet( ctx_t * ctx, - fd_stem_context_t * stem, - uint dst_ip_addr, - ushort dst_port, - uint src_ip_addr, - uchar const * payload, - ulong payload_sz, - ulong tsorig ) { - ctx->metrics->send_pkt_cnt++; - uchar * packet = fd_chunk_to_laddr( ctx->net_out_ctx->mem, ctx->net_out_ctx->chunk ); - fd_ip4_udp_hdrs_t * hdr = (fd_ip4_udp_hdrs_t *)packet; - *hdr = *ctx->intake_hdr; - - fd_ip4_hdr_t * ip4 = hdr->ip4; - ip4->saddr = src_ip_addr; - ip4->daddr = dst_ip_addr; - ip4->net_id = fd_ushort_bswap( ctx->net_id++ ); - ip4->check = 0U; - ip4->net_tot_len = fd_ushort_bswap( (ushort)(payload_sz + sizeof(fd_ip4_hdr_t)+sizeof(fd_udp_hdr_t)) ); - ip4->check = fd_ip4_hdr_check_fast( ip4 ); - - fd_udp_hdr_t * udp = hdr->udp; - udp->net_dport = dst_port; - udp->net_len = fd_ushort_bswap( (ushort)(payload_sz + sizeof(fd_udp_hdr_t)) ); - fd_memcpy( packet+sizeof(fd_ip4_udp_hdrs_t), payload, payload_sz ); - hdr->udp->check = 0U; - - ulong tspub = fd_frag_meta_ts_comp( fd_tickcount() ); - ulong sig = fd_disco_netmux_sig( dst_ip_addr, dst_port, dst_ip_addr, DST_PROTO_OUTGOING, sizeof(fd_ip4_udp_hdrs_t) ); - ulong packet_sz = payload_sz + sizeof(fd_ip4_udp_hdrs_t); - ulong chunk = ctx->net_out_ctx->chunk; - fd_stem_publish( stem, ctx->net_out_ctx->idx, sig, chunk, packet_sz, 0UL, tsorig, tspub ); - ctx->net_out_ctx->chunk = fd_dcache_compact_next( chunk, packet_sz, ctx->net_out_ctx->chunk0, ctx->net_out_ctx->wmark ); -} - -/* Returns a sign_out context with max available credits. - If no sign_out context has available credits, returns NULL. */ -static out_ctx_t * -sign_avail_credits( ctx_t * ctx ) { - out_ctx_t * sign_out = NULL; - ulong max_credits = 0; - for( uint i = 0; i < ctx->repair_sign_cnt; i++ ) { - if( ctx->repair_sign_out_ctx[i].credits > max_credits ) { - max_credits = ctx->repair_sign_out_ctx[i].credits; - sign_out = &ctx->repair_sign_out_ctx[i]; - } - } - return sign_out; -} - -/* Prepares the signing preimage and publishes a signing request that - will be signed asynchronously by the sign tile. The signed data will - be returned via dcache as a frag. */ -static void -fd_repair_send_sign_request( ctx_t * ctx, - out_ctx_t * sign_out, - fd_repair_msg_t const * msg, - pong_data_t const * opt_pong_data ) { - - if( FD_UNLIKELY( ctx->halt_signing ) ) FD_LOG_CRIT(( "can't dispatch sign requests while halting signing" )); - - /* New sign request */ - sign_req_t * pending = sign_map_insert( ctx, msg, opt_pong_data ); - if( FD_UNLIKELY( !pending ) ) return; - - ulong sig = 0; - ulong preimage_sz = 0; - uchar * dst = fd_chunk_to_laddr( sign_out->mem, sign_out->chunk ); - - if( FD_UNLIKELY( msg->kind == FD_REPAIR_KIND_PONG ) ) { - uchar pre_image[FD_REPAIR_PONG_PREIMAGE_SZ]; - preimage_pong( &opt_pong_data->hash, pre_image ); - preimage_sz = FD_REPAIR_PONG_PREIMAGE_SZ; - fd_memcpy( dst, pre_image, preimage_sz ); - sig = ((ulong)pending->key << 32) | (uint)FD_KEYGUARD_SIGN_TYPE_SHA256_ED25519; - } else { - /* Sign and prepare the message directly into the pending buffer */ - uchar * preimage = preimage_req( &pending->msg, &preimage_sz ); - fd_memcpy( dst, preimage, preimage_sz ); - sig = ((ulong)pending->key << 32) | (uint)FD_KEYGUARD_SIGN_TYPE_ED25519; - } - - fd_stem_publish( ctx->stem, sign_out->idx, sig, sign_out->chunk, preimage_sz, 0UL, 0UL, 0UL ); - sign_out->chunk = fd_dcache_compact_next( sign_out->chunk, preimage_sz, sign_out->chunk0, sign_out->wmark ); - - ctx->metrics->sent_pkt_types[metric_index[msg->kind]]++; - sign_out->credits--; -} - -static inline int -before_frag( ctx_t * ctx, - ulong in_idx, - ulong seq FD_PARAM_UNUSED, - ulong sig ) { - uint in_kind = ctx->in_kind[ in_idx ]; - if( FD_LIKELY ( in_kind==IN_KIND_NET ) ) return fd_disco_netmux_sig_proto( sig )!=DST_PROTO_REPAIR; - if( FD_UNLIKELY( in_kind==IN_KIND_SHRED ) ) return fd_int_if( fd_forest_root_slot( ctx->forest )==ULONG_MAX, -1, 0 ); /* not ready to read frag */ - if( FD_UNLIKELY( in_kind==IN_KIND_GOSSIP ) ) { - return sig!=FD_GOSSIP_UPDATE_TAG_CONTACT_INFO && - sig!=FD_GOSSIP_UPDATE_TAG_CONTACT_INFO_REMOVE; - } - if( FD_UNLIKELY( in_kind==IN_KIND_REPLAY ) ) return sig!=REPLAY_SIG_REASM_EVICTED; - return 0; -} - -static void -during_frag( ctx_t * ctx, - ulong in_idx, - ulong seq FD_PARAM_UNUSED, - ulong sig, - ulong chunk, - ulong sz, - ulong ctl ) { - ctx->skip_frag = 0; - - uint in_kind = ctx->in_kind[ in_idx ]; - in_ctx_t const * in_ctx = &ctx->in_links[ in_idx ]; - ctx->chunk = chunk; - - if( FD_UNLIKELY( in_kind==IN_KIND_NET ) ) { - ulong hdr_sz = fd_disco_netmux_sig_hdr_sz( sig ); - FD_TEST( hdr_sz <= sz ); /* Should be ensured by the net tile */ - uchar const * dcache_entry = fd_net_rx_translate_frag( &in_ctx->net_rx, chunk, ctl, sz ); - fd_memcpy( ctx->net_buf, dcache_entry, sz ); - return; - } - - if( FD_UNLIKELY( sz!=0UL && ( chunkchunk0 || chunk>in_ctx->wmark || sz>in_ctx->mtu ) ) ) - FD_LOG_ERR(( "chunk %lu %lu corrupt, not in range [%lu,%lu] in kind %u", chunk, sz, in_ctx->chunk0, in_ctx->wmark, in_kind )); - - if( FD_UNLIKELY( in_kind==IN_KIND_SNAP ) ) { - if( FD_UNLIKELY( fd_ssmsg_sig_message( sig )!=FD_SSMSG_DONE ) ) ctx->snap_out_chunk = chunk; - return; - } - - if( FD_UNLIKELY( in_kind==IN_KIND_SIGN ) ) { - /* sign_repair is unreliable, so we copy the frag for convention. - Theoretically impossible to overrun. */ - uchar const * dcache_entry = fd_chunk_to_laddr_const( in_ctx->mem, chunk ); - fd_memcpy( ctx->sign_buf, dcache_entry, sz ); - return; - } -} - -static inline void -after_snap( ctx_t * ctx, - ulong sig, - uchar const * chunk ) { - if( FD_UNLIKELY( fd_ssmsg_sig_message( sig )!=FD_SSMSG_DONE ) ) return; - fd_snapshot_manifest_t * manifest = (fd_snapshot_manifest_t *)chunk; - - fd_forest_init( ctx->forest, manifest->slot ); -} - -static inline void -after_gossip( ctx_t * ctx, fd_gossip_update_message_t const * msg, ulong sig ) { - switch( sig ) { - case FD_GOSSIP_UPDATE_TAG_CONTACT_INFO_REMOVE: { - fd_policy_peer_remove( ctx->policy, fd_type_pun_const( msg->origin ) ); - break; - } - case FD_GOSSIP_UPDATE_TAG_CONTACT_INFO: { - fd_gossip_contact_info_t const * contact_info = msg->contact_info->value; - fd_ip4_port_t repair_peer; - repair_peer.addr = contact_info->sockets[ FD_GOSSIP_CONTACT_INFO_SOCKET_SERVE_REPAIR ].is_ipv6 ? 0U : contact_info->sockets[ FD_GOSSIP_CONTACT_INFO_SOCKET_SERVE_REPAIR ].ip4; - repair_peer.port = contact_info->sockets[ FD_GOSSIP_CONTACT_INFO_SOCKET_SERVE_REPAIR ].port; - if( FD_UNLIKELY( !repair_peer.addr || !repair_peer.port ) ) return; - fd_policy_peer_t const * peer = fd_policy_peer_upsert( ctx->policy, fd_type_pun_const( msg->origin ), &repair_peer ); - if( FD_LIKELY( peer && !fd_signs_queue_full( ctx->pong_queue ) ) ) { - /* The repair process uses a Ping-Pong protocol that incurs one - round-trip time (RTT) for the initmial repair request. To - optimize this, we proactively send a placeholder repair request - as soon as we receive a peer's contact information for the first - time, effectively prepaying the RTT cost. */ - fd_repair_msg_t * init = fd_repair_shred( ctx->protocol, fd_type_pun_const( msg->origin ), (ulong)fd_clock_tile_now( ctx->clock )/1000000UL, 0, 0, 0 ); - fd_signs_queue_push( ctx->pong_queue, (sign_pending_t){ .msg = *init } ); - } - break; - } - default: FD_LOG_ERR(( "bad gossip sig %lu", sig )); - } -} - -static inline void -after_sign( ctx_t * ctx, - ulong in_idx, - ulong sig, - fd_stem_context_t * stem ) { - ulong pending_key = sig >> 32; - /* Look up the pending request. Since the repair_sign links are - reliable, the incoming sign_repair fragments represent a complete - set of the previously sent outgoing messages. However, with - multiple sign tiles, the responses may arrive interleaved. */ - - /* Find which sign tile sent this response and increment its credits */ - for( uint i = 0; i < ctx->repair_sign_cnt; i++ ) { - if( ctx->repair_sign_out_ctx[i].in_idx == in_idx ) { - if( FD_LIKELY( ctx->repair_sign_out_ctx[i].credits < ctx->repair_sign_out_ctx[i].max_credits ) ) ctx->repair_sign_out_ctx[i].credits++; - break; - } - } - - sign_req_t * pending_ = fd_signs_map_query( ctx->signs_map, pending_key, NULL ); - if( FD_UNLIKELY( !pending_ ) ) FD_LOG_CRIT(( "No pending request found for key %lu", pending_key )); /* implies either bad programmer error or something happened with sign tile */ - - sign_req_t pending[1] = { *pending_ }; /* Make a copy of the pending request so we can sign_map_remove immediately. */ - sign_map_remove( ctx, pending_key ); - - /* This is a pong message */ - if( FD_UNLIKELY( pending->msg.kind == FD_REPAIR_KIND_PONG ) ) { - fd_policy_peer_t * peer = fd_policy_peer_query( ctx->policy, &pending->pong_data.key ); - if( FD_LIKELY( peer && peer->ping ) ) peer->ping--; /* prevent underflow if the peer was removed/readded */ - - fd_memcpy( pending->msg.pong.sig, ctx->sign_buf, 64UL ); - send_packet( ctx, stem, pending->pong_data.peer_addr.addr, pending->pong_data.peer_addr.port, pending->pong_data.daddr, pending->buf, fd_repair_sz( &pending->msg ), fd_frag_meta_ts_comp( fd_tickcount() ) ); - return; - } - - /* Inject the signature into the pending request */ - fd_memcpy( pending->buf + 4, ctx->sign_buf, 64UL ); - uint src_ip4 = 0U; - - /* This is a warmup message */ - if( FD_UNLIKELY( pending->msg.kind == FD_REPAIR_KIND_SHRED && pending->msg.shred.slot == 0 ) ) { - fd_policy_peer_t * peer = fd_policy_peer_query( ctx->policy, &pending->msg.shred.to ); - if( FD_UNLIKELY( peer ) ) send_packet( ctx, stem, peer->ip4, peer->port, src_ip4, pending->buf, pending->buflen, fd_frag_meta_ts_comp( fd_tickcount() ) ); - else { /* This is a warmup request for a peer that is no longer active. There's no reason to pick another peer for a warmup rq, so just drop it. */ } - return; - } - - /* This is a regular repair shred request - - We need to ensure we always send out any shred requests we have, - because policy_next has no way to revisit a shred. But the fact - that peers can drop out of the peer list makes this complicated. - If the peer is still there (common), it's fine. If the peer is not - there, we can add this request to the inflights table, pretend - we've sent it and let the inflight timeout request it down the - line. */ - - fd_policy_peer_t * active = fd_policy_peer_query( ctx->policy, &pending->msg.shred.to ); - if( FD_UNLIKELY( !active ) ) { - /* Already added to the inflights table, pretend we've sent it - and let the inflight timeout request it down the line. */ - return; - } - /* Happy path - all is well, our peer didn't drop out from beneath us. */ - if( FD_UNLIKELY( pending->msg.kind == FD_REPAIR_KIND_ORPHAN ) ) ctx->metrics->last_requested_orphan = pending->msg.orphan.slot; - else ctx->metrics->last_requested_slot = pending->msg.shred.slot; - - send_packet( ctx, stem, active->ip4, active->port, src_ip4, pending->buf, pending->buflen, fd_frag_meta_ts_comp( fd_tickcount() ) ); -} - -static int -blk_insert_check( ctx_t * ctx, fd_forest_blk_t * new_blk, ulong new_slot, ulong evicted ) { - if( FD_UNLIKELY( !new_blk ) ) { - ctx->metrics->blk_failed_insert++; - ctx->metrics->slot_failed_insert = new_slot; - return 0; - } else { - if( FD_UNLIKELY( evicted != ULONG_MAX ) ) { - ctx->metrics->blk_evicted++; - ctx->metrics->slot_evicted = evicted; - ctx->metrics->slot_evicted_by = new_slot; - } - return 1; - } -} - -static inline int shred_src( ulong sig ) { - uint sig_src = fd_shred_sig_src( sig ); - switch( sig_src ) { - case SHRED_SIG_SRC_TURBINE: - return SHRED_SRC_TURBINE; - case SHRED_SIG_SRC_RECONSTRUCTED: - return SHRED_SRC_RECOVERED; - case SHRED_SIG_SRC_REPAIR: - return SHRED_SRC_REPAIR; - case SHRED_SIG_SRC_BAD_REPAIR: - return SHRED_SRC_TURBINE; /* like fec_resolver, treat bad repair shreds as turbine shreds */ - case SHRED_SIG_SRC_LEADER: - return SHRED_SRC_LEADER; - default: FD_LOG_CRIT(( "bad shred sig src %u", sig_src )); - } -} - -static inline void -after_shred( ctx_t * ctx, - ulong sig, - fd_shred_t * shred, - ulong nonce, - fd_hash_t * mr, - fd_hash_t * cmr, - long rx_tick ) { - - int is_code = fd_shred_is_code( fd_shred_type( shred->variant ) ); - - /* we don't want to add a slot to the forest that chains to a slot - older than root, to avoid filling forest up with junk. - Especially if we are close to full and we are having trouble - rooting, we can't rely on publishing to prune these useless - subtrees. TODO: do the same with reasm/store/shred? */ - - if( FD_UNLIKELY( shred->slot <= fd_forest_root_slot( ctx->forest ) || - ( !is_code && shred->slot - shred->data.parent_off < fd_forest_root_slot( ctx->forest ) ) ) ) { - ctx->metrics->old_shred++; - return; - } - - /* Insert the shred sig (shared by all shred members in the FEC set) - into the map. */ - int src = shred_src( sig ); - - if( FD_LIKELY( !is_code ) ) { - long rtt = 0; - fd_pubkey_t peer; - - int slot_complete = !!(shred->data.flags & FD_SHRED_DATA_FLAG_SLOT_COMPLETE); - int ref_tick = shred->data.flags & FD_SHRED_DATA_REF_TICK_MASK; - ulong evicted = ULONG_MAX; - fd_forest_blk_t * blk = fd_forest_blk_insert( ctx->forest, shred->slot, shred->slot - shred->data.parent_off, &evicted ); - if( FD_UNLIKELY( !blk_insert_check( ctx, blk, shred->slot, evicted ) ) ) return; - - if( FD_LIKELY( fd_forest_data_shred_insert( ctx->forest, shred->slot, shred->slot - shred->data.parent_off, shred->idx, shred->fec_set_idx, slot_complete, ref_tick, src, mr, cmr, rx_tick ) ) ) { - if( FD_UNLIKELY( src == SHRED_SRC_REPAIR && ( rtt = fd_inflights_shred_match( ctx->inflights, FD_REPAIR_KIND_SHRED, nonce, shred->slot, shred->idx, NULL, &peer, NULL, fd_clock_tile_now( ctx->clock ) ) ) > 0 ) ) { - fd_policy_peer_response_update( ctx->policy, &peer, rtt ); - fd_histf_sample( ctx->metrics->response_latency, (ulong)rtt ); - blk->response_cnt++; - blk->last_repair_resp_ts = rx_tick; - } - } - } else { - fd_forest_code_shred_insert( ctx->forest, shred->slot, shred->idx, rx_tick ); - } -} - -/* Kicks off the chained merkle verification starting at a slot with - a confirmed, canonical block_id. Either finishes successfully and - returns early, or detects an incorrect FEC set and clears it. In - this case the verification is paused and state is saved at where - it left off. Verification can be re-triggered in after_fec as well. */ -static inline void -check_confirmed( ctx_t * ctx, - fd_forest_blk_t * blk, - fd_hash_t const * confirmed_bid ) { - - if( FD_LIKELY( !blk->chain_confirmed && blk->complete_idx != UINT_MAX ) ) { - /* The above conditions say that all the shreds of the block have arrived. */ - fd_forest_blk_t * bad_blk = fd_forest_fec_chain_verify( ctx->forest, blk, confirmed_bid ); - if( FD_LIKELY( !bad_blk ) ) { - /* chain verified successfully from blk to as far as we have fec data */ - return; - } - - uint bad_fec_idx = fd_forest_merkle_last_incorrect_idx( bad_blk ); - /* bad_fec_idx UINT_MAX implies this slot is fully validated. Not - possible here because that implies bad_blk is NULL and would - early exit above. */ - FD_TEST( bad_fec_idx != UINT_MAX ); - - fd_forest_mr_t const * bad_mroots = fd_forest_blk_mroots( ctx->forest, bad_blk ); - fd_hash_t const * expected = (bad_fec_idx == bad_blk->complete_idx - (FD_FEC_SHRED_CNT - 1)) ? &bad_blk->confirmed_bid : &bad_mroots[(bad_fec_idx / 32) + 1].cmr; - - FD_BASE58_ENCODE_32_BYTES( confirmed_bid->uc, confirmed_bid_b58 ); - FD_BASE58_ENCODE_32_BYTES( expected->uc, expected_mr ); - FD_BASE58_ENCODE_32_BYTES( bad_mroots[bad_fec_idx / 32].mr.uc, recorded_mr ); - - FD_LOG_WARNING(( "[%s] slot %lu block_id %s confirmation detected incorrect FECs. bad FEC is slot %lu fec set %u. expected mr (%s) != recorded mr (%s)", - __func__, - blk->slot, - confirmed_bid_b58, - bad_blk->slot, - bad_fec_idx, - expected_mr, - recorded_mr )); - - ctx->metrics->failed_chain_verify_cnt++; - ctx->metrics->failed_chain_verify_slot = bad_blk->slot; - bad_blk->chain_verify_failed = 1; - - /* If we have a bad block, we need to dump and repair backwards from - the point where the merkle root is incorrect. - We start only by dumping the last incorrect FEC. It's possible that - this is the only incorrect one. If it isn't though, when the slot - recompletes, this function will trigger again and we will dump the - second to last incorrect FEC. */ - - fd_forest_fec_clear( ctx->forest, bad_blk->slot, bad_fec_idx, FD_FEC_SHRED_CNT - 1 ); - } -} - -/* Returns 1 if the fec is guaranteed invalid, 0 otherwise. */ -static inline int -after_fec( ctx_t * ctx, - fd_shred_t * shred, - fd_hash_t * mr, - fd_hash_t * cmr, - long rx_tick ) { - - /* When this is a FEC completes msg, it is implied that all the - other shreds in the FEC set can also be inserted. Shred inserts - into the forest are idempotent so it is fine to insert the same - shred multiple times. */ - - int slot_complete = !!( shred->data.flags & FD_SHRED_DATA_FLAG_SLOT_COMPLETE ); - int ref_tick = shred->data.flags & FD_SHRED_DATA_REF_TICK_MASK; - - /* Similar to after_shred, do not insert a slot that chains to a slot older than root */ - if( FD_UNLIKELY( shred->slot <= fd_forest_root_slot( ctx->forest ) || - shred->slot - shred->data.parent_off < fd_forest_root_slot( ctx->forest ) ) ) return 0; - - ulong evicted = ULONG_MAX; - fd_forest_blk_t * ele = fd_forest_blk_insert( ctx->forest, shred->slot, shred->slot - shred->data.parent_off, &evicted ); - if( FD_UNLIKELY( !blk_insert_check( ctx, ele, shred->slot, evicted ) ) ) return 0; - ele = fd_forest_fec_insert( ctx->forest, shred->slot, shred->slot - shred->data.parent_off, shred->idx, shred->fec_set_idx, slot_complete, ref_tick, mr, cmr, rx_tick ); - if( FD_UNLIKELY( !ele ) ) return 1; - - /* metrics for completed slots */ - if( FD_UNLIKELY( ele->complete_idx != UINT_MAX && ele->buffered_idx==ele->complete_idx ) ) { - long now = fd_tickcount(); - long start_ts = ele->first_req_ts == 0 || ele->slot >= ctx->turbine_slot0 ? ele->first_shred_ts : ele->first_req_ts; - ulong duration_ticks = (ulong)(now - start_ts); - fd_histf_sample( ctx->metrics->slot_compl_time, duration_ticks ); - fd_repair_metrics_add_slot( ctx->slot_metrics, ele->slot, start_ts, now, ele->repair_cnt, ele->turbine_cnt ); - /* Note: this log does not imply that the slot is fully executable. - It's possible that we have a slot that doesn't chain verify, - which could be un-executable. */ - FD_BASE58_ENCODE_32_BYTES( fd_forest_blk_mroots( ctx->forest, ele )[ele->complete_idx / 32].mr.uc, block_id ); - FD_BASE58_ENCODE_32_BYTES( mr->uc, fec_mr ); - FD_LOG_INFO(( "[%s] slot is complete %lu. num_data_shreds: %u, num_repaired: %u, num_turbine: %u, num_recovered: %u, duration: %.2f ms. last recvd fec: %u, mr %s. current block_id: %s", - __func__, - ele->slot, - ele->complete_idx + 1, - ele->repair_cnt, - ele->turbine_cnt, - ele->recovered_cnt, - (double)fd_metrics_convert_ticks_to_nanoseconds(duration_ticks) / 1e6, - shred->fec_set_idx, - fec_mr, - block_id )); - } - - /* re-trigger continuation of chained merkle verification if slot is - complete. TODO MOVE TO AFTER_SHRED? */ - fd_hash_t empty_mr = (fd_hash_t){ .ul = { 0, 0, 0, 0 } }; - if( FD_UNLIKELY( ele->buffered_idx == ele->complete_idx && !fd_hash_eq( &ele->confirmed_bid, &empty_mr ) ) ) { - check_confirmed( ctx, ele, &ele->confirmed_bid /* if lowest_verified_fec is not UINT_MAX, confirmed_bid must be populated */ ); - } - return 0; -} - -static inline void -after_net( ctx_t * ctx, - ulong sz ) { - fd_eth_hdr_t * eth; fd_ip4_hdr_t * ip4; fd_udp_hdr_t * udp; - uchar * data; ulong data_sz; - if( FD_UNLIKELY( !fd_ip4_udp_hdr_strip( ctx->net_buf, sz, &data, &data_sz, ð, &ip4, &udp ) ) ) { - ctx->metrics->malformed_ping++; - return; - } - fd_ip4_port_t peer_addr = { .addr=ip4->saddr, .port=udp->net_sport }; - - fd_repair_ping_t ping[1]; - int err = fd_repair_ping_de( ping, data, data_sz ); - if( FD_UNLIKELY( err ) ) { - ctx->metrics->malformed_ping++; - return; - } - - fd_policy_peer_t * peer = fd_policy_peer_query( ctx->policy, &ping->ping.from ); - if( FD_UNLIKELY( !peer ) ) { - ctx->metrics->unknown_peer_ping++; - return; - } - if( FD_UNLIKELY( peer->ping ) ) return; - if( FD_UNLIKELY( fd_signs_queue_full( ctx->pong_queue ) ) ) return; - - fd_sha512_t sha[1]; - if( FD_UNLIKELY( FD_ED25519_SUCCESS != fd_ed25519_verify( ping->ping.hash.uc, 32UL, ping->ping.sig, ping->ping.from.uc, sha ) ) ) { - ctx->metrics->fail_sigverify_ping++; - return; - } - - /* Any gossip peer can send a ping, but they are bounded to at most - one ping in the queue so they can't evict others' pings without - multiple gossip identities. */ - - fd_repair_msg_t * pong = fd_repair_pong( ctx->protocol, &ping->ping.hash ); - fd_signs_queue_push( ctx->pong_queue, (sign_pending_t){ .msg = *pong, .pong_data = { .peer_addr = peer_addr, .hash = ping->ping.hash, .daddr = ip4->daddr, .key = ping->ping.from } } ); - peer->ping++; -} - -static inline void -after_evict( ctx_t * ctx, - fd_fec_evicted_t * evicted ) { - fd_forest_fec_clear( ctx->forest, evicted->slot, evicted->fec_set_idx, FD_FEC_SHRED_CNT - 1 ); -} - -static inline void -after_tower( ctx_t * ctx, - ulong sig, - uchar const * chunk ) { - - switch( sig ) { - case FD_TOWER_SIG_SLOT_DONE: { - fd_tower_slot_done_t const * msg = (fd_tower_slot_done_t const *)fd_type_pun_const( chunk ); - if( FD_LIKELY( msg->root_slot!=ULONG_MAX && msg->root_slot > fd_forest_root_slot( ctx->forest ) ) ) fd_forest_publish( ctx->forest, msg->root_slot ); - break; - } - case FD_TOWER_SIG_SLOT_CONFIRMED: { - fd_tower_slot_confirmed_t const * msg = (fd_tower_slot_confirmed_t const *)fd_type_pun_const( chunk ); - if( msg->slot > fd_forest_root_slot( ctx->forest ) && (msg->level >= FD_TOWER_SLOT_CONFIRMED_DUPLICATE ) ) { - fd_forest_blk_t * blk = fd_forest_query( ctx->forest, msg->slot ); - if( FD_UNLIKELY( !blk ) ) { - /* If we receive a confirmation for a slot we don't have, - create a sentinel forest block that we can repair from. */ - ulong evicted = ULONG_MAX; - blk = fd_forest_blk_insert( ctx->forest, msg->slot, ULONG_MAX, &evicted ); - FD_LOG_INFO(("[%s] creating sentinel for duplicate confirmed block %lu", __func__, msg->slot)); - if( FD_UNLIKELY( !blk_insert_check( ctx, blk, msg->slot, evicted ) ) ) return; - } - - /* Confirm the block */ - blk->confirmed_bid = msg->block_id; - check_confirmed( ctx, blk, &msg->block_id ); - } - break; - } - default: return; - } -} - -static void -after_frag( ctx_t * ctx, - ulong in_idx, - ulong seq FD_PARAM_UNUSED, - ulong sig, - ulong sz, - ulong tsorig, - ulong tspub, - fd_stem_context_t * stem ) { - if( FD_UNLIKELY( ctx->skip_frag ) ) return; - - long now_tick = fd_tickcount(); - long rx_tick = fd_frag_meta_ts_decomp( tsorig, now_tick ); - if( FD_UNLIKELY( rx_tick>now_tick ) ) rx_tick -= 1L<<32; - - ctx->stem = stem; - in_ctx_t const * in_ctx = &ctx->in_links[ in_idx ]; - uint in_kind = ctx->in_kind[ in_idx ]; - - switch( in_kind ) { - /* Unreliable frags */ - case IN_KIND_NET: { - after_net( ctx, sz ); - break; - } - case IN_KIND_SIGN: { - after_sign( ctx, in_idx, sig, stem ); - break; - } - /* Reliable frags read directly from dcache */ - case IN_KIND_SNAP: { - after_snap( ctx, sig, fd_chunk_to_laddr( ctx->in_links[ in_idx ].mem, ctx->snap_out_chunk ) ); - break; - } - case IN_KIND_GOSSIP: { - fd_gossip_update_message_t const * msg = (fd_gossip_update_message_t const *)fd_type_pun_const( fd_chunk_to_laddr( in_ctx->mem, ctx->chunk ) ); - after_gossip( ctx, msg, sig ); - break; - } - case IN_KIND_REPLAY: { - fd_replay_fec_evicted_t const * msg = (fd_replay_fec_evicted_t const *)fd_type_pun_const( fd_chunk_to_laddr( in_ctx->mem, ctx->chunk ) ); - fd_forest_fec_clear( ctx->forest, msg->slot, msg->fec_set_idx, FD_FEC_SHRED_CNT - 1 ); - break; - } - case IN_KIND_TOWER: { - after_tower( ctx, sig, fd_chunk_to_laddr( in_ctx->mem, ctx->chunk ) ); - break; - } - case IN_KIND_SHRED: { - - /* There are 3 message types from shred: - 1. resolver evict - incomplete FEC set is evicted by resolver - 2. fec complete - FEC set is completed by resolver. Also contains a shred. - 3. shred - new shred - - Msgs 2 and 3 have a shred header in the dcache. Msg 1 is empty. */ - - uint sig_src = fd_shred_sig_src( sig ); - int sig_res = fd_shred_sig_res( sig ); - - if( FD_UNLIKELY( sig_src==SHRED_SIG_FEC_EVICTED ) ) { - fd_fec_evicted_t * evicted = (fd_fec_evicted_t *)fd_type_pun( fd_chunk_to_laddr( in_ctx->mem, ctx->chunk ) ); - after_evict( ctx, evicted ); - return; - } - - uchar * src = fd_chunk_to_laddr( in_ctx->mem, ctx->chunk ); - fd_shred_base_t * shred_msg = (fd_shred_base_t *)fd_type_pun( src ); - fd_shred_t * shred = &shred_msg->shred; /* completes & shred messages all have a shred header at the same offset (after merkle root) */ - - if( FD_UNLIKELY( shred->slot > ctx->metrics->current_slot && sig_src == SHRED_SIG_SRC_TURBINE ) ) { - FD_LOG_INFO(( "[Turbine] slot: %lu, root: %lu", shred->slot, fd_forest_root_slot( ctx->forest ) )); - ctx->metrics->current_slot = shred->slot; - } - - if( FD_UNLIKELY( ctx->turbine_slot0 == ULONG_MAX && sig_src == SHRED_SIG_SRC_TURBINE ) ) { - ctx->turbine_slot0 = shred->slot; - fd_repair_metrics_set_turbine_slot0( ctx->slot_metrics, shred->slot ); - fd_policy_set_turbine_slot0( ctx->policy, shred->slot ); - - /* On first turbine shred, seed repair by queuing highest_shred - requests for slots between snapshot and turbine_slot0. This - bypasses forest entirely and dispatches directly via the sign - queue. Cap at half queue capacity to leave room for pongs. */ - ulong root = fd_forest_root_slot( ctx->forest ); - if( FD_LIKELY( root != ULONG_MAX && shred->slot > root ) ) { - ulong capacity = fd_signs_queue_max( ctx->pong_queue ) - fd_signs_queue_cnt( ctx->pong_queue ); - ulong seed_cnt = fd_ulong_min( shred->slot-root, capacity/2 ); - long now_ms = fd_clock_tile_now( ctx->clock )/(long)1e6; - for( ulong i=1; i<=seed_cnt; i++ ) { - ulong slot = root + i; - fd_pubkey_t const * peer = fd_policy_peer_select( ctx->policy ); - if( FD_UNLIKELY( !peer ) ) break; - fd_repair_msg_t * msg = fd_repair_highest_shred( ctx->protocol, peer, (ulong)now_ms, 0, slot, 0 ); - if( FD_LIKELY( msg ) ) fd_signs_queue_push( ctx->pong_queue, (sign_pending_t){ .msg = *msg } ); - } - } - } - - if( FD_UNLIKELY( sig==SHRED_SIG_FEC_COMPLETE || sig==SHRED_SIG_FEC_COMPLETE_LEADER ) ) { - FD_TEST( sz==sizeof(fd_fec_complete_t) ); - fd_fec_complete_t * in_msg = (fd_fec_complete_t *)fd_type_pun( src ); - int invalid = after_fec( ctx, &in_msg->last_shred_hdr, &in_msg->merkle_root, &in_msg->chained_merkle_root, rx_tick ); - ulong fwd_sig = invalid ? REPAIR_SIG_FEC_INVALID : (sig==SHRED_SIG_FEC_COMPLETE_LEADER ? REPAIR_SIG_FEC_LEADER : REPAIR_SIG_FEC); - - fd_repair_fec_complete_t * complete_msg = fd_chunk_to_laddr( ctx->repair_out_ctx->mem, ctx->repair_out_ctx->chunk ); - complete_msg->fec = *in_msg; - - fd_fec_complete_metrics_t * m = &complete_msg->metrics; - m->fec_completed_ts_nanos = fd_clock_epoch_y( ctx->clock->epoch, rx_tick ); - - m->stats_valid = 0U; - fd_forest_blk_t const * blk = fd_forest_query( ctx->forest, in_msg->last_shred_hdr.slot ); - if( FD_LIKELY( blk ) ) { - m->stats_valid = 1U; - m->blk_turbine_cnt = blk->turbine_cnt; - m->blk_repair_cnt = blk->repair_cnt; - m->blk_recovered_cnt = blk->recovered_cnt; - ulong word_cnt = fd_forest_blk_idxs_word_cnt( ctx->forest->shred_max ); - m->blk_data_cnt = (uint)fd_forest_blk_idxs_cnt( fd_forest_blk_idxs( ctx->forest, blk ), word_cnt ); - m->blk_parity_cnt = (uint)fd_forest_blk_idxs_cnt( fd_forest_blk_code( ctx->forest, blk ), word_cnt ); - m->blk_lowest_verified_fec = blk->lowest_verified_fec; - m->blk_chain_confirmed = blk->chain_confirmed; - m->blk_slot_complete = (uchar)( blk->complete_idx!=UINT_MAX ); - m->blk_req_window_cnt = blk->req_window_cnt; - m->blk_req_highest_cnt = blk->req_highest_cnt; - m->blk_req_orphan_cnt = blk->req_orphan_cnt; - m->blk_req_retransmit_cnt = blk->req_retransmit_cnt; - m->blk_repair_responses = blk->response_cnt; - m->blk_chain_verify_failed = blk->chain_verify_failed; - - /* 0 = never stamped (e.g. no repair request was ever sent). */ - m->blk_first_shred_ts_nanos = blk->first_shred_ts ? (ulong)fd_clock_epoch_y( ctx->clock->epoch, blk->first_shred_ts ) : 0UL; - m->blk_last_shred_ts_nanos = blk->last_shred_ts ? (ulong)fd_clock_epoch_y( ctx->clock->epoch, blk->last_shred_ts ) : 0UL; - m->blk_first_req_ts_nanos = blk->first_req_ts ? (ulong)fd_clock_epoch_y( ctx->clock->epoch, blk->first_req_ts ) : 0UL; - m->blk_last_repair_resp_ts_nanos = blk->last_repair_resp_ts ? (ulong)fd_clock_epoch_y( ctx->clock->epoch, blk->last_repair_resp_ts ) : 0UL; - } - - /* indiscriminately forward FEC complete messages along to replay */ - fd_stem_publish( ctx->stem, ctx->repair_out_ctx->idx, fwd_sig, ctx->repair_out_ctx->chunk, sizeof(fd_repair_fec_complete_t), 0UL, 0UL, tspub ); - ctx->repair_out_ctx->chunk = fd_dcache_compact_next( ctx->repair_out_ctx->chunk, sizeof(fd_repair_fec_complete_t), ctx->repair_out_ctx->chunk0, ctx->repair_out_ctx->wmark ); - } else if( FD_LIKELY( sig_res!=SHRED_SIG_RESULT_EQVOC ) ) { - fd_hash_t * cmr = (fd_hash_t *)fd_type_pun(shred_msg->shred_ + fd_shred_chain_off( shred->variant )); - after_shred( ctx, sig, shred, shred_msg->rnonce, &shred_msg->merkle_root, cmr, rx_tick ); - } - return; - } - default: FD_LOG_ERR(( "bad in_kind %u", in_kind )); /* Should never reach here since before_frag should have filtered out any unexpected frags. */ - } -} - -/* Defer a request by adding it to the outstanding inflights table so it - can be re-requested after a timeout window. Nonce is 0 because these - are not real requests made to the network, and cannot be matched - by a shred response. */ -static void -defer_inflight_request( ctx_t * ctx, ulong slot, ulong shred_idx, long now ) { - fd_hash_t hash = { .ul[0] = 0 }; - fd_inflight_key_t inflight_req[1]; - fd_inflight_key_init( inflight_req, FD_REPAIR_KIND_SHRED, slot, shred_idx, 0UL, NULL ); - if( FD_LIKELY( !fd_inflight_map_ele_query( ctx->inflights->map, inflight_req, NULL, ctx->inflights->pool ) ) ) { - fd_inflights_shred_insert( ctx->inflights, FD_REPAIR_KIND_SHRED, 0, &hash, slot, shred_idx, NULL, NULL, now ); - } -} - -/* Should be called for any regular FD_REPAIR_KIND_SHRED request made. */ -static void -record_inflight_request( ctx_t * ctx, ulong nonce, fd_pubkey_t const * peer, ulong slot, ulong shred_idx, long now ) { - fd_inflights_shred_insert( ctx->inflights, FD_REPAIR_KIND_SHRED, nonce, peer, slot, shred_idx, NULL, NULL, now ); - fd_policy_peer_request_update( ctx->policy, peer ); -} - -/* Repair request invariants: - - Highest window index requests and orphan requests are much less - fragile than regular shred requests. This is because we request - orphans and highest window index requests whenever we "need" them, - i.e. we fire and forget them. - - But regular shred requests are only iterated once per shred per slot. - This is for performance reasons - we don't want to iterate over the - forest more than necessary. If we let knowledge of a regular shred - request get dropped, it's possible the request could get lost - forever. This is why any regular shred request that is conceived - needs to be handled at the repair tile level. All regular shred - requests must be added to the inflights outstanding table so it can - be re-requested after a timeout window. - - An inflight entry should only ever be permanently removed upon - receipt of a shred response. - - There are two methods through which we make regular shred requests: - 1. policy_next - 2. fd_inflights_pop - - In general, policy_next makes the first request for shred X, and - fd_inflights_pop makes all subsequent requests for shred X at - DEDUP_TIMEOUT intervals. This is to give each request a fair chance - to be received, but also to avoid colliding nonces. This is because - we want to be as accurate as possible when tracking per-peer response - latency. - - With eviction, it's possible for policy_next to make the same request - for shred X multiple times, in short timeout intervals. Therefore we - need to have both policy_next and fd_inflights_pop requests - pass through the same dedup cache. At the point of eviction, we - leave requests for that slot in the dedup cache and in the inflights - table. If an old request from before eviction happens to - dedup a request on the new insertion of the slot, these requests must - be queued up in inflights table so they can be re-requested after a - timeout window. */ - -static inline void -after_credit( ctx_t * ctx, - fd_stem_context_t * stem FD_PARAM_UNUSED, - int * opt_poll_in FD_PARAM_UNUSED, - int * charge_busy ) { - long now = fd_clock_tile_now( ctx->clock ); - - if( FD_UNLIKELY( ctx->halt_signing ) ) { - *charge_busy = 1; - return; - } - - /* Verify that there is at least one sign tile with available credits. - If not, we can't send any requests and leave early. */ - out_ctx_t * sign_out = sign_avail_credits( ctx ); - if( FD_UNLIKELY( !sign_out ) ) { - ctx->metrics->sign_tile_unavail++; - return; - } - - /* If inflights is at capacity, then the only thing we can send is: - pongs, initial highest window index requests, or resend things that - are already inflight. Any new requests that would cause an - inflight to be added to the queue must be deferred. */ - - if( FD_UNLIKELY( !fd_signs_queue_empty( ctx->pong_queue ) ) ) { - sign_pending_t signable = fd_signs_queue_pop( ctx->pong_queue ); - fd_repair_send_sign_request( ctx, sign_out, &signable.msg, signable.msg.kind == FD_REPAIR_KIND_PONG ? &signable.pong_data : NULL ); - *charge_busy = 1; - return; - } - - if( FD_UNLIKELY( fd_inflights_should_drain( ctx->inflights, now ) ) ) { - fd_inflight_t req[1]; - fd_inflights_pop( ctx->inflights, req ); - *charge_busy = 1; - ulong nonce = req->key.nonce; - ulong slot = req->key.slot; - ulong shred_idx = req->key.idx; - - fd_forest_blk_t * blk = fd_forest_query( ctx->forest, slot ); - if( FD_UNLIKELY( blk && shred_idx <= blk->complete_idx && !fd_forest_blk_idxs_test( fd_forest_blk_idxs( ctx->forest, blk ), shred_idx ) ) ) { - fd_pubkey_t const * peer = fd_policy_peer_select( ctx->policy ); - - if( FD_UNLIKELY( !peer || fd_reqlim_next( ctx->dedup, fd_reqlim_key( FD_REPAIR_KIND_SHRED, slot, (uint)shred_idx ), now ) ) ) { - /* No peers available, park the request in inflights. */ - defer_inflight_request( ctx, slot, shred_idx, now ); - } else { - ctx->metrics->rerequest++; - blk->req_retransmit_cnt++; - nonce = fd_rnonce_ss_compute( ctx->repair_nonce_ss, 1, slot, (uint)shred_idx, now ); - fd_repair_msg_t * msg = fd_repair_shred( ctx->protocol, peer, (ulong)now/(ulong)1e6, (uint)nonce, slot, shred_idx ); - fd_repair_send_sign_request( ctx, sign_out, msg, NULL ); - record_inflight_request( ctx, nonce, peer, slot, shred_idx, now ); /* Request is definitely a regular shred request. */ - return; - } - } - } - - if( FD_UNLIKELY( fd_inflights_outstanding_free( ctx->inflights ) <= fd_signs_map_key_cnt( ctx->signs_map ) ) ) return; /* no new requests allowed */ - - fd_repair_msg_t const * cout = fd_policy_next( ctx->policy, ctx->dedup, ctx->forest, ctx->protocol, now, ctx->metrics->current_slot, charge_busy ); - if( FD_UNLIKELY( !cout ) ) return; - - if( ( cout->kind == FD_REPAIR_KIND_SHRED && fd_reqlim_next( ctx->dedup, fd_reqlim_key( FD_REPAIR_KIND_SHRED, cout->shred.slot, (uint)cout->shred.shred_idx ), now ) ) ) { - /* Here if policy_next is re-requesting a shred that's already been - requested. This could be happen during a race - imagine we make a - request for shred 0 in slot Y. Then eviction causes slot Y to be - removed and then readded. policy_next will re-request shred 0, - but if we let it get dropped here, it's possible the request - could get lost forever. */ - defer_inflight_request( ctx, cout->shred.slot, cout->shred.shred_idx, now ); - return; - } - - /* finally, send the request made by policy */ - fd_repair_send_sign_request( ctx, sign_out, cout, NULL ); - if( FD_LIKELY( cout->kind == FD_REPAIR_KIND_SHRED ) ) { - record_inflight_request( ctx, cout->shred.nonce, &cout->shred.to, cout->shred.slot, cout->shred.shred_idx, now ); - fd_forest_blk_t * blk = fd_forest_query( ctx->forest, cout->shred.slot ); - if( FD_LIKELY( blk ) ) { - blk->req_window_cnt++; - if( FD_UNLIKELY( !blk->first_req_ts ) ) blk->first_req_ts = fd_tickcount(); - } - } -} - -static void -signs_queue_update_identity( ctx_t * ctx ) { - ulong queue_cnt = fd_signs_queue_cnt( ctx->pong_queue ); - for( ulong i=0UL; ipong_queue ); - switch( signable.msg.kind ) { - case FD_REPAIR_KIND_PONG: - memcpy( signable.msg.pong.from.uc, ctx->identity_public_key.uc, sizeof(fd_pubkey_t) ); - break; - case FD_REPAIR_KIND_SHRED: - memcpy( signable.msg.shred.from.uc, ctx->identity_public_key.uc, sizeof(fd_pubkey_t) ); - break; - case FD_REPAIR_KIND_HIGHEST_SHRED: - memcpy( signable.msg.highest_shred.from.uc, ctx->identity_public_key.uc, sizeof(fd_pubkey_t) ); - break; - case FD_REPAIR_KIND_ORPHAN: - memcpy( signable.msg.orphan.from.uc, ctx->identity_public_key.uc, sizeof(fd_pubkey_t) ); - break; - default: - FD_LOG_CRIT(( "Unhandled repair kind %u", signable.msg.kind )); - break; - } - fd_signs_queue_push( ctx->pong_queue, signable ); - } -} - -static inline void -during_housekeeping( ctx_t * ctx ) { - if( FD_UNLIKELY( fd_clock_tile_recal_due( ctx->clock ) ) ) fd_clock_tile_recal( ctx->clock ); - -# if DEBUG_LOGGING - long now = fd_log_wallclock(); - if( FD_UNLIKELY( now - ctx->tsdebug > (long)10e9 ) ) { - fd_forest_print( ctx->forest ); - ctx->tsdebug = fd_log_wallclock(); - } -# endif - - if( FD_UNLIKELY( fd_keyswitch_state_query( ctx->keyswitch )==FD_KEYSWITCH_STATE_UNHALT_PENDING ) ) { - FD_LOG_DEBUG(( "keyswitch: unhalting" )); - FD_CHECK_CRIT( ctx->halt_signing, "state machine corruption" ); - ctx->halt_signing = 0; - fd_keyswitch_state( ctx->keyswitch, FD_KEYSWITCH_STATE_COMPLETED ); - - /* Peers key their ping-pong cache by our pubkey, so every peer is - cold for the new identity. Re-warm them all. */ - fd_policy_peers_t * peers = &ctx->policy->peers; - fd_policy_peer_dlist_t * lists[ 2 ] = { peers->fast, peers->slow }; - for( ulong l=0UL; l<2UL; l++ ) { - for( fd_policy_peer_dlist_iter_t iter = fd_policy_peer_dlist_iter_fwd_init( lists[ l ], peers->pool ); - !fd_policy_peer_dlist_iter_done( iter, lists[ l ], peers->pool ) && !fd_signs_queue_full( ctx->pong_queue ); - iter = fd_policy_peer_dlist_iter_fwd_next( iter, lists[ l ], peers->pool ) ) { - fd_policy_peer_t const * peer = fd_policy_peer_dlist_iter_ele_const( iter, lists[ l ], peers->pool ); - fd_repair_msg_t * init = fd_repair_shred( ctx->protocol, &peer->key, (ulong)fd_clock_tile_now( ctx->clock )/1000000UL, 0, 0, 0 ); - fd_signs_queue_push( ctx->pong_queue, (sign_pending_t){ .msg = *init } ); - } - } - } - - if( FD_UNLIKELY( fd_keyswitch_state_query( ctx->keyswitch )==FD_KEYSWITCH_STATE_SWITCH_PENDING ) ) { - - if( !ctx->halt_signing ) { - /* At this point, stop sending new sign requests to the sign tile - and wait for all outstanding sign requests to be received back - from the sign tile. We also need to update any pending - outgoing sign requests with the new identity key. */ - FD_LOG_DEBUG(( "keyswitch: halting signing" )); - ctx->halt_signing = 1; - memcpy( ctx->identity_public_key.uc, ctx->keyswitch->bytes, 32UL ); - ctx->protocol->identity_key = ctx->identity_public_key; - signs_queue_update_identity( ctx ); - } - - if( fd_signs_map_key_cnt( ctx->signs_map )==0UL ) { - /* Once there are no more in flight sign requests, we are ready to - say that the keyswitch is completed. */ - FD_LOG_DEBUG(( "keyswitch: completed, no more outstanding stale sign requests" )); - fd_keyswitch_state( ctx->keyswitch, FD_KEYSWITCH_STATE_COMPLETED ); - } - } -} - -static void -privileged_init( fd_topo_t const * topo, - fd_topo_tile_t const * tile ) { - void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id ); - - FD_SCRATCH_ALLOC_INIT( l, scratch ); - ctx_t * ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof(ctx_t), sizeof(ctx_t) ); - fd_memset( ctx, 0, sizeof(ctx_t) ); - - uchar const * identity_key = fd_keyload_load( tile->repair.identity_key_path, /* pubkey only: */ 1 ); - fd_memcpy( ctx->identity_public_key.uc, identity_key, sizeof(fd_pubkey_t) ); - - FD_TEST( fd_rng_secure( &ctx->repair_seed, sizeof(ulong) ) ); - - ulong rnonce_ss_id = fd_pod_queryf_ulong( topo->props, ULONG_MAX, "rnonce_ss" ); - FD_TEST( rnonce_ss_id!=ULONG_MAX ); - memcpy( ctx->repair_nonce_ss, fd_topo_obj_laddr( topo, rnonce_ss_id ), sizeof(fd_rnonce_ss_t) ); -} - -static void -unprivileged_init( fd_topo_t const * topo, - fd_topo_tile_t const * tile ) { - void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id ); - - ulong total_sign_depth = tile->repair.repair_sign_depth * tile->repair.repair_sign_cnt; - int lg_sign_depth = fd_ulong_find_msb( fd_ulong_pow2_up(total_sign_depth) ) + 1; - - FD_SCRATCH_ALLOC_INIT( l, scratch ); - ctx_t * ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof(ctx_t), sizeof(ctx_t) ); - ctx->protocol = FD_SCRATCH_ALLOC_APPEND( l, fd_repair_align(), fd_repair_footprint() ); - ctx->forest = FD_SCRATCH_ALLOC_APPEND( l, fd_forest_align(), fd_forest_footprint( tile->repair.slot_max, tile->repair.max_shreds_per_block ) ); - ctx->policy = FD_SCRATCH_ALLOC_APPEND( l, fd_policy_align(), fd_policy_footprint( FD_REPAIR_PEER_MAX ) ); - ctx->dedup = FD_SCRATCH_ALLOC_APPEND( l, fd_reqlim_align(), fd_reqlim_footprint( FD_REQLIM_CACHE_MAX ) ); - ctx->inflights = FD_SCRATCH_ALLOC_APPEND( l, fd_inflights_align(), fd_inflights_footprint() ); - ctx->signs_map = FD_SCRATCH_ALLOC_APPEND( l, fd_signs_map_align(), fd_signs_map_footprint( lg_sign_depth ) ); - ctx->pong_queue = FD_SCRATCH_ALLOC_APPEND( l, fd_signs_queue_align(), fd_signs_queue_footprint() ); - ctx->slot_metrics = FD_SCRATCH_ALLOC_APPEND( l, fd_repair_metrics_align(), fd_repair_metrics_footprint() ); - 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 ) )); - - ctx->protocol = fd_repair_join ( fd_repair_new ( ctx->protocol, &ctx->identity_public_key ) ); - ctx->forest = fd_forest_join ( fd_forest_new ( ctx->forest, tile->repair.slot_max, tile->repair.max_shreds_per_block, ctx->repair_seed ) ); - ctx->policy = fd_policy_join ( fd_policy_new ( ctx->policy, FD_REPAIR_PEER_MAX, ctx->repair_seed, ctx->repair_nonce_ss ) ); - ctx->dedup = fd_reqlim_join ( fd_reqlim_new ( ctx->dedup, FD_REQLIM_CACHE_MAX, ctx->repair_seed ) ); - ctx->inflights = fd_inflights_join ( fd_inflights_new ( ctx->inflights, ctx->repair_seed+1234UL ) ); - ctx->signs_map = fd_signs_map_join ( fd_signs_map_new ( ctx->signs_map, lg_sign_depth, 0UL ) ); - ctx->pong_queue = fd_signs_queue_join ( fd_signs_queue_new ( ctx->pong_queue ) ); - ctx->slot_metrics = fd_repair_metrics_join( fd_repair_metrics_new( ctx->slot_metrics ) ); - - ctx->keyswitch = fd_keyswitch_join( fd_topo_obj_laddr( topo, tile->id_keyswitch_obj_id ) ); - FD_TEST( ctx->keyswitch ); - - ctx->halt_signing = 0; - - /* Process in links */ - - if( FD_UNLIKELY( tile->in_cnt > MAX_IN_LINKS ) ) FD_LOG_ERR(( "repair tile has too many input links" )); - - uint sign_repair_in_idx[ MAX_SIGN_TILE_CNT ] = {0}; - uint sign_repair_idx = 0; - ulong sign_link_depth = 0; - - for( uint in_idx=0U; in_idx<(tile->in_cnt); in_idx++ ) { - fd_topo_link_t const * link = &topo->links[ tile->in_link_id[ in_idx ] ]; - if( 0==strcmp( link->name, "net_repair" ) ) { - ctx->in_kind[ in_idx ] = IN_KIND_NET; - fd_net_rx_bounds_init( &ctx->in_links[ in_idx ].net_rx, link->dcache ); - continue; - } else if( 0==strcmp( link->name, "sign_repair" ) ) { - ctx->in_kind[ in_idx ] = IN_KIND_SIGN; - sign_repair_in_idx[ sign_repair_idx++ ] = in_idx; - sign_link_depth = link->depth; - } - else if( 0==strcmp( link->name, "gossip_ciaddr" ) ) ctx->in_kind[ in_idx ] = IN_KIND_GOSSIP; - else if( 0==strcmp( link->name, "tower_out" ) ) ctx->in_kind[ in_idx ] = IN_KIND_TOWER; - else if( 0==strcmp( link->name, "shred_out" ) ) ctx->in_kind[ in_idx ] = IN_KIND_SHRED; - else if( 0==strcmp( link->name, "snapin_manif" ) ) ctx->in_kind[ in_idx ] = IN_KIND_SNAP; - else if( 0==strcmp( link->name, "replay_slot" ) ) ctx->in_kind[ in_idx ] = IN_KIND_REPLAY; - else FD_LOG_ERR(( "repair tile has unexpected input link %s", link->name )); - - ctx->in_links[ in_idx ].mem = topo->workspaces[ topo->objs[ link->dcache_obj_id ].wksp_id ].wksp; - ctx->in_links[ in_idx ].chunk0 = fd_dcache_compact_chunk0( ctx->in_links[ in_idx ].mem, link->dcache ); - ctx->in_links[ in_idx ].wmark = fd_dcache_compact_wmark ( ctx->in_links[ in_idx ].mem, link->dcache, link->mtu ); - ctx->in_links[ in_idx ].mtu = link->mtu; - - FD_TEST( fd_dcache_compact_is_safe( ctx->in_links[in_idx].mem, link->dcache, link->mtu, link->depth ) ); - } - - ctx->net_out_ctx->idx = UINT_MAX; - ctx->repair_out_ctx->idx = UINT_MAX; - ctx->repair_sign_cnt = 0; - ctx->sign_rrobin_idx = 0; - - for( uint out_idx=0U; out_idx<(tile->out_cnt); out_idx++ ) { - fd_topo_link_t const * link = &topo->links[ tile->out_link_id[ out_idx ] ]; - - if( 0==strcmp( link->name, "repair_net" ) ) { - - if( ctx->net_out_ctx->idx!=UINT_MAX ) continue; /* only use first net link */ - ctx->net_out_ctx->idx = out_idx; - ctx->net_out_ctx->mem = topo->workspaces[ topo->objs[ link->dcache_obj_id ].wksp_id ].wksp; - ctx->net_out_ctx->chunk0 = fd_dcache_compact_chunk0( ctx->net_out_ctx->mem, link->dcache ); - ctx->net_out_ctx->wmark = fd_dcache_compact_wmark( ctx->net_out_ctx->mem, link->dcache, link->mtu ); - ctx->net_out_ctx->chunk = ctx->net_out_ctx->chunk0; - - } else if( 0==strcmp( link->name, "repair_out" ) ) { - - out_ctx_t * replay_out = ctx->repair_out_ctx; - replay_out->idx = out_idx; - replay_out->mem = topo->workspaces[ topo->objs[ link->dcache_obj_id ].wksp_id ].wksp; - replay_out->chunk0 = fd_dcache_compact_chunk0( replay_out->mem, link->dcache ); - replay_out->wmark = fd_dcache_compact_wmark( replay_out->mem, link->dcache, link->mtu ); - replay_out->chunk = replay_out->chunk0; - - } else if( 0==strcmp( link->name, "repair_sign" ) ) { - - out_ctx_t * repair_sign_out = &ctx->repair_sign_out_ctx[ ctx->repair_sign_cnt ]; - repair_sign_out->idx = out_idx; - repair_sign_out->mem = topo->workspaces[ topo->objs[ link->dcache_obj_id ].wksp_id ].wksp; - repair_sign_out->chunk0 = fd_dcache_compact_chunk0( repair_sign_out->mem, link->dcache ); - repair_sign_out->wmark = fd_dcache_compact_wmark( repair_sign_out->mem, link->dcache, link->mtu ); - repair_sign_out->chunk = repair_sign_out->chunk0; - repair_sign_out->in_idx = sign_repair_in_idx[ ctx->repair_sign_cnt++ ]; /* match to the sign_repair input link */ - repair_sign_out->max_credits = sign_link_depth; - repair_sign_out->credits = sign_link_depth; - - } else { - FD_LOG_ERR(( "repair tile has unexpected output link %s", link->name )); - } - } - FD_TEST( ctx->net_out_ctx->idx!=UINT_MAX ); - FD_TEST( ctx->repair_out_ctx->idx!=UINT_MAX ); - if( FD_UNLIKELY( ctx->repair_sign_cnt!=sign_repair_idx ) ) { - FD_LOG_ERR(( "Mismatch between repair_sign output links (%lu) and sign_repair input links (%u)", ctx->repair_sign_cnt, sign_repair_idx )); - } - if( FD_UNLIKELY( fd_signs_map_key_max( ctx->signs_map ) < tile->repair.repair_sign_depth * tile->repair.repair_sign_cnt ) ) { - FD_LOG_ERR(( "Repair pending signs tracking map is too small: %lu < %lu.", fd_signs_map_key_max( ctx->signs_map ), tile->repair.repair_sign_depth * tile->repair.repair_sign_cnt )); - } - - ctx->wksp = topo->workspaces[ topo->objs[ tile->tile_obj_id ].wksp_id ].wksp; - ctx->repair_intake_addr.port = fd_ushort_bswap( tile->repair.repair_client_listen_port ); - - /* TODO clean these up */ - ctx->net_id = (ushort)0; - fd_ip4_udp_hdr_init( ctx->intake_hdr, 0, 0, tile->repair.repair_client_listen_port ); - - /* Repair set up */ - - ctx->turbine_slot0 = ULONG_MAX; - FD_LOG_INFO(( "repair my addr - intake addr: " FD_IP4_ADDR_FMT ":%u", - FD_IP4_ADDR_FMT_ARGS( ctx->repair_intake_addr.addr ), fd_ushort_bswap( ctx->repair_intake_addr.port ) - )); - - memset( ctx->metrics, 0, sizeof(ctx->metrics) ); - - fd_histf_join( fd_metrics_histf_new_ns( ctx->metrics->slot_compl_time, FD_MHIST_EDGES_NS( REPAIR, SLOT_COMPLETE_DURATION_NANOS ) ) ); - fd_histf_join( fd_histf_new( ctx->metrics->response_latency, FD_MHIST_MIN( REPAIR, RESPONSE_LATENCY_NANOS ), - FD_MHIST_MAX( REPAIR, RESPONSE_LATENCY_NANOS ) ) ); - - fd_clock_tile_init( ctx->clock ); - - ctx->tsdebug = fd_log_wallclock(); - ctx->pending_key_next = 0; -} - -static ulong -populate_allowed_seccomp( fd_topo_t const * topo FD_PARAM_UNUSED, - fd_topo_tile_t const * tile FD_PARAM_UNUSED, - ulong out_cnt, - struct sock_filter * out ) { - populate_sock_filter_policy_fd_repair_tile( out_cnt, out, (uint)fd_log_private_logfile_fd() ); - return sock_filter_policy_fd_repair_tile_instr_cnt; -} - -static ulong -populate_allowed_fds( fd_topo_t const * topo FD_PARAM_UNUSED, - fd_topo_tile_t const * tile FD_PARAM_UNUSED, - ulong out_fds_cnt, - int * out_fds ) { - if( FD_UNLIKELY( out_fds_cnt<2UL ) ) FD_LOG_ERR(( "out_fds_cnt %lu", out_fds_cnt )); - - ulong out_cnt = 0UL; - out_fds[ out_cnt++ ] = 2; /* stderr */ - if( FD_LIKELY( -1!=fd_log_private_logfile_fd() ) ) - out_fds[ out_cnt++ ] = fd_log_private_logfile_fd(); /* logfile */ - return out_cnt; -} - -static inline void -metrics_write( ctx_t * ctx ) { - FD_MGAUGE_SET( REPAIR, HIGHEST_TURBINE_SLOT, ctx->metrics->current_slot ); - FD_MGAUGE_SET( REPAIR, HIGHEST_REPAIRED_SLOT, fd_forest_highest_repaired_slot( ctx->forest ) ); - FD_MCNT_SET( REPAIR, SHRED_OLD_IGNORED, ctx->metrics->old_shred ); - FD_MGAUGE_SET( REPAIR, PEER_COUNT, fd_policy_peer_pool_used( ctx->policy->peers.pool ) ); - FD_MCNT_SET( REPAIR, SIGN_BACKPRESSURE, ctx->metrics->sign_tile_unavail ); - FD_MCNT_SET( REPAIR, SHRED_REREQUESTED, ctx->metrics->rerequest ); - - FD_MGAUGE_SET( REPAIR, LAST_REQUESTED_SLOT, ctx->metrics->last_requested_slot ); - FD_MGAUGE_SET( REPAIR, LAST_REQUESTED_ORPHAN_SLOT, ctx->metrics->last_requested_orphan ); - FD_MGAUGE_SET( REPAIR, REQUEST_IN_FLIGHT, fd_inflights_outstanding_cnt( ctx->inflights ) ); - - FD_MCNT_SET ( REPAIR, PKT_TX, ctx->metrics->send_pkt_cnt ); - FD_MCNT_ENUM_COPY( REPAIR, REQUEST_TX, ctx->metrics->sent_pkt_types ); - - FD_MHIST_COPY( REPAIR, SLOT_COMPLETE_DURATION_NANOS, ctx->metrics->slot_compl_time ); - FD_MHIST_COPY( REPAIR, RESPONSE_LATENCY_NANOS, ctx->metrics->response_latency ); - - FD_MCNT_SET ( REPAIR, BLOCK_EVICTED, ctx->metrics->blk_evicted ); - FD_MCNT_SET ( REPAIR, BLOCK_INSERT_FAILED, ctx->metrics->blk_failed_insert ); - FD_MGAUGE_SET( REPAIR, LAST_EVICTED_SLOT, ctx->metrics->slot_evicted ); - FD_MGAUGE_SET( REPAIR, LAST_EVICTION_CAUSE_SLOT, ctx->metrics->slot_evicted_by ); - FD_MGAUGE_SET( REPAIR, LAST_INSERT_FAILED_SLOT, ctx->metrics->slot_failed_insert ); - - FD_MCNT_SET ( REPAIR, CHAIN_VERIFY_FAILED, ctx->metrics->failed_chain_verify_cnt ); - FD_MGAUGE_SET( REPAIR, LAST_CHAIN_VERIFY_FAILED_SLOT, ctx->metrics->failed_chain_verify_slot ); - - FD_MCNT_SET( REPAIR, PING_REJECTED_UNKNOWN_PEER, ctx->metrics->unknown_peer_ping ); - FD_MCNT_SET( REPAIR, PING_REJECTED_MALFORMED, ctx->metrics->malformed_ping ); - FD_MCNT_SET( REPAIR, PING_REJECTED_SIGNATURE_FAILED, ctx->metrics->fail_sigverify_ping ); -} - -#undef DEBUG_LOGGING - -/* At most one sign request is made in after_credit. Then at most one - message is published in after_frag. */ -#define STEM_BURST (2UL) - -/* Set LAZY to a reasonable value that keeps housekeeping time low. - Repair tile's only reliable consumer is replay. */ -#define STEM_LAZY (64000) - -#define STEM_CALLBACK_CONTEXT_TYPE ctx_t -#define STEM_CALLBACK_CONTEXT_ALIGN alignof(ctx_t) - -#define STEM_CALLBACK_AFTER_CREDIT after_credit -#define STEM_CALLBACK_BEFORE_FRAG before_frag -#define STEM_CALLBACK_DURING_FRAG during_frag -#define STEM_CALLBACK_AFTER_FRAG after_frag -#define STEM_CALLBACK_DURING_HOUSEKEEPING during_housekeeping -#define STEM_CALLBACK_METRICS_WRITE metrics_write - -#include "../../disco/stem/fd_stem.c" - -fd_topo_run_tile_t fd_tile_repair = { - .name = "repair", - .populate_allowed_seccomp = populate_allowed_seccomp, - .populate_allowed_fds = populate_allowed_fds, - .scratch_align = scratch_align, - .scratch_footprint = scratch_footprint, - .unprivileged_init = unprivileged_init, - .privileged_init = privileged_init, - .run = stem_run, -}; diff --git a/src/discof/repair/fd_repair_tile.h b/src/discof/repair/fd_repair_tile.h deleted file mode 100644 index f3d1298311f..00000000000 --- a/src/discof/repair/fd_repair_tile.h +++ /dev/null @@ -1,62 +0,0 @@ -#ifndef HEADER_fd_src_discof_repair_fd_repair_tile_h -#define HEADER_fd_src_discof_repair_fd_repair_tile_h - -#include "../../disco/tiles.h" -#include "../../disco/shred/fd_shred_tile.h" - -/* Repair tile forwards all FEC completes received by the shred tile. - It forwards the FEC completes with new sigs: - - REPAIR_SIG_FEC: FEC set complete - - REPAIR_SIG_FEC_LEADER: Leader FEC set complete - - REPAIR_SIG_FEC_INVALID: FEC set detected as invalid based on duplicate confirmations - - Note that invalidity is very strict. i.e., sometimes replay may not - want FEC set at the root / or older than the root to be inserted. - Repair should still forward these as REPAIR_SIG_FEC, because they are - FEC sets that are either valid, or cannot be verified as invalid. - Similarly, repair may have received two versions of one FEC, but is - unsure which one is canonical. These are also forwarded as - REPAIR_SIG_FEC. It's up to replay tile to arbitrate whether to insert - these FEC sets. - - A FEC set forwarded as REPAIR_SIG_FEC_INVALID is with confidence - guaranteed to be not part of the canonical chain. */ - -struct fd_fec_complete_metrics { - uint stats_valid; /* 1 if the forest still tracked the block and the blk_* stats are filled */ - uint blk_turbine_cnt; /* block: shreds received via turbine */ - uint blk_repair_cnt; /* block: data shreds received via repair */ - uint blk_recovered_cnt; /* block: shreds recovered via reed-solomon */ - uint blk_data_cnt; /* block: data shreds received so far (reaches complete_idx+1 once the slot is fully buffered) */ - uint blk_parity_cnt; /* block: coding (parity) shreds received so far */ - uint blk_lowest_verified_fec; /* block: lowest verified FEC index, UINT_MAX if none */ - uchar blk_chain_confirmed; /* block: 1 if the whole chain has been merkle-verified */ - uchar blk_slot_complete; /* block: 1 if the slot-complete shred has been received */ - - uint blk_req_window_cnt; /* block: window (specific-shred) repair requests sent */ - uint blk_req_highest_cnt; /* block: highest-window repair requests sent */ - uint blk_req_orphan_cnt; /* block: orphan repair requests sent */ - uint blk_req_retransmit_cnt; /* block: repair requests re-sent after a response timeout */ - uint blk_repair_responses; /* block: repair responses received */ - uchar blk_chain_verify_failed; /* block: 1 if merkle chain verification flagged this block bad */ - - long fec_completed_ts_nanos; - - ulong blk_first_shred_ts_nanos; /* when the first shred of the slot arrived */ - ulong blk_last_shred_ts_nanos; /* when the slot became fully buffered */ - ulong blk_first_req_ts_nanos; /* when the first repair request for the slot was sent */ - ulong blk_last_repair_resp_ts_nanos; /* when the most recent repair response for the slot arrived */ -}; -typedef struct fd_fec_complete_metrics fd_fec_complete_metrics_t; - -struct fd_repair_fec_complete { - fd_fec_complete_t fec; - fd_fec_complete_metrics_t metrics; -}; -typedef struct fd_repair_fec_complete fd_repair_fec_complete_t; - -#define REPAIR_SIG_FEC (0UL) /* FEC set complete */ -#define REPAIR_SIG_FEC_LEADER (1UL) /* Leader FEC set complete */ -#define REPAIR_SIG_FEC_INVALID (2UL) /* FEC set detected as invalid based on duplicate confirmations */ - -#endif /* HEADER_fd_src_discof_repair_fd_repair_tile_h */ diff --git a/src/discof/repair/fd_repair_tile.seccomppolicy b/src/discof/repair/fd_repair_tile.seccomppolicy deleted file mode 100644 index 432950ca64c..00000000000 --- a/src/discof/repair/fd_repair_tile.seccomppolicy +++ /dev/null @@ -1,37 +0,0 @@ -# logfile_fd: It can be disabled by configuration, but typically tiles -# will open a log file on boot and write all messages there. -uint logfile_fd - -# logging: all log messages are written to a file and/or pipe -# -# 'WARNING' and above are written to the STDERR pipe, while all messages -# are always written to the log file. -# -# arg 0 is the file descriptor to write to. The boot process ensures -# that descriptor 2 is always STDERR. -write: (or (eq (arg 0) 2) - (eq (arg 0) logfile_fd)) - -# logging: 'WARNING' and above fsync the logfile to disk immediately -# -# arg 0 is the file descriptor to fsync. -fsync: (eq (arg 0) logfile_fd) - -# power saving: sleep when there is no work to do -# -# In the efficient scheduler mode, an idle tile parks on its sleep word -# with futex_wait until a producer has work for it or its next deadline -# passes. -# -# arg 1 is the futex op, always FUTEX_WAIT_BITSET so the timeout is an -# absolute deadline. arg 2 is the expected value of the word, always 0 -# (parked). -futex: (and (eq (arg 1) FUTEX_WAIT_BITSET) - (eq (arg 2) 0)) - -# power saving: resume an interrupted sleep -# -# A signal delivered to a parked tile interrupts its futex_wait, and the -# kernel resumes the wait with the remaining timeout via restart_syscall -# when the tile next runs. -restart_syscall diff --git a/src/discof/repair/fd_repair_tile_private.h b/src/discof/repair/fd_repair_tile_private.h deleted file mode 100644 index 5d5c697a6ac..00000000000 --- a/src/discof/repair/fd_repair_tile_private.h +++ /dev/null @@ -1,280 +0,0 @@ -#ifndef HEADER_fd_src_discof_repair_fd_repair_tile_private_h -#define HEADER_fd_src_discof_repair_fd_repair_tile_private_h - -/* Internal types of the repair tile. Included by the tile and by - firedancer-dev's repair command, which reads the tile's ctx out of - shared memory. */ - -#include "fd_repair_tile.h" -#include "fd_repair_metrics.h" -#include "fd_inflight.h" -#include "fd_repair.h" -#include "fd_policy.h" -#include "../forest/fd_forest.h" -#include "../../disco/fd_clock_tile.h" -#include "../../disco/keyguard/fd_keyswitch.h" -#include "../../disco/metrics/fd_metrics.h" -#include "../../disco/net/fd_net_tile.h" -#include "../../disco/shred/fd_rnonce_ss.h" -#include "../../disco/topo/fd_topo.h" -#include "../../flamenco/gossip/fd_gossip_message.h" -#include "../../util/net/fd_net_headers.h" - -#define MAX_IN_LINKS (32) -#define MAX_SHRED_TILE_CNT ( 16UL ) -#define MAX_SIGN_TILE_CNT ( 16UL ) - -/* Max number of validators that can be actively queried */ -#define FD_REPAIR_PEER_MAX (FD_CONTACT_INFO_TABLE_SIZE) - -/* Max number of pending repair requests recently made to keep track of. - Calculated generally as we estimate around 50k/s/core to sign - requests. Assuming an over-provisioned 4 sign tiles just for repair, - this means we can make up to ~200k requests per second. With a dedup - timeout of 80ms, this means we can make up to ~16k requests within - the dedup timeout window. We round up to the next power of two to - get the dedup cache max. Since we are sizing the dedup cache for a - generous margin, and this number not particularly fragile or - sensitive, we can leave it static. */ -#define FD_REQLIM_CACHE_MAX (1<<20) - -typedef union { - struct { - fd_wksp_t * mem; - ulong chunk0; - ulong wmark; - ulong mtu; - }; - fd_net_rx_bounds_t net_rx; -} in_ctx_t; - -struct out_ctx { - ulong idx; - fd_wksp_t * mem; - ulong chunk0; - ulong wmark; - ulong chunk; - - /* Repair tile directly tracks credit outside of stem for these - asynchronous sign links. In particular, credits tracks the RETURN - sign_repair link. This is because repair_sign and - sign_repair are unreliable. If both links were reliable, and the - links filled completely, stem would get into a deadlock. Neither - repair or sign would have credits, which would prevent frags from - getting polled in repair or sign, which would prevent any credits - from getting returned back to the tiles. So the sign_repair return - link must be unreliable. credits / max_credits are used by the - repair_sign link, but credits tracks the RETURN - sign_repair link. - - Consider the scenario: - - repair_sign (depth 128) sign_repair (depth 128) - repair ----------------------> sign ------------------------> repair - [rest free, r130, r129] [r128, r127, ... , r1] (full) - - If repair is publishing too many requests too fast(common in - catchup), and not polling enough frags from sign, without manual - management the sign_repair link would be overrun. Nothing is - stopping repair from publishing more requests, because sign is - functioning fast enough to handle the requests. However, nothing is - stopping sign from polling the next request and signing it, and - PUBLISHING it on the sign_repair link that is already full, because - the sign_repair link is unreliable. - - This is why we need to manually track credits for the sign_repair - link. We must ensure that there are never more than 128 items in - the ENTIRE repair_sign -> sign tile -> sign_repair work queue, else - there is always a possibility of an overrun in the sign_repair - link. - - We can furthermore ensure some nice properties by having the - repair_sign link have a greater depth than the sign_repair link. - This way, we exclusively use manual credit management to control - the rate at which we publish requests to sign. This allows for - repair_sign to also be unreliable. Even when the repair sign link - is "full", we can avoid backpressure and continue polling frags, - without overruning the sign_repair link. - - To lose a frag to overrun isn't necessarily critical, but in - general the repair tile relies on the fact that a signing task - published to sign tile will always come back. If we lose a frag to - overrun, then there will be an entry in the pending signs structure - that is never removed, and theoretically the map could fill up. - Conceptually, with a reliable (unreliable links, but strictly - controlled count-per-link) sign->repair->sign structure, there - should be no eviction needed in this pending signs structure. */ - - ulong in_idx; /* index of the incoming link */ - ulong credits; /* available credits for link */ - ulong max_credits; /* maximum credits (depth) */ -}; -typedef struct out_ctx out_ctx_t; - -/* Data needed to sign and send a pong that is not contained in the - pong msg itself. */ - -struct pong_data { - fd_ip4_port_t peer_addr; - fd_hash_t hash; - uint daddr; - fd_pubkey_t key; -}; -typedef struct pong_data pong_data_t; - -struct sign_req { - ulong key; /* map key, ctx->pending_key_next */ - ulong buflen; - union { - uchar buf[sizeof(fd_repair_msg_t)]; - fd_repair_msg_t msg; - }; - pong_data_t pong_data; /* populated only for pong msgs */ -}; -typedef struct sign_req sign_req_t; - -#define MAP_NAME fd_signs_map -#define MAP_KEY key -#define MAP_KEY_NULL ULONG_MAX -#define MAP_KEY_INVAL(k) (k==ULONG_MAX) -#define MAP_T sign_req_t -#define MAP_MEMOIZE 0 -#include "../../util/tmpl/fd_map_dynamic.c" - -/* Because the sign tiles could be all busy when a contact info or a - ping arrives, we need to save ping messages to be signed in a queue - and dispatched in after_credit when there are sign tiles available. - The size of the queue is sized to be the number of warm up - requests we might burst to the queue all at once (at most - FD_REPAIR_PEER_MAX), then doubled for good measure. - - There is a possibility that someone could spam pings to block other - peers' pings (and prevent us from responding to those pings). To - mitigate this, we track the number of pings currently living in the - sign queue that belong to each peer. If a peer already has a pong - living in the sign queue, we drop the pings from that peer. - - The peer could send us a new bogus ping every time we pop their ping - from the sign queue, but there would be no way to prevent other - peers' pings from getting processed, so the wasted work and impact - would be minimal. - - Typical flow is that a pong will get added to the pong_queue during - an after_frag call. Then on the following after_credit will get - popped from the sign_queue and added to sign_map, and then dispatched - to the sign tile. - - Note that after the first turbine shred arrives, the signs_queue also - stores highest window index requests for slots between snapshot and - turbine_slot0, which are dispatched first before any other requests - as a catchup optimization. This doesn't break any of the inflight - invariants as highest window index requests do not get added to the - inflight table. */ - -struct sign_pending { - fd_repair_msg_t msg; - pong_data_t pong_data; /* populated only for pong msgs */ -}; -typedef struct sign_pending sign_pending_t; - -#define QUEUE_NAME fd_signs_queue -#define QUEUE_T sign_pending_t -#define QUEUE_MAX (2*FD_REPAIR_PEER_MAX) -#include "../../util/tmpl/fd_queue.c" - -struct ctx { - long tsdebug; /* timestamp for debug printing */ - - fd_clock_tile_t clock[1]; - - ulong repair_seed; - - fd_keyswitch_t * keyswitch; - int halt_signing; - - fd_ip4_port_t repair_intake_addr; - - fd_forest_t * forest; - fd_policy_t * policy; - fd_reqlim_t * dedup; - fd_inflights_t * inflights; - fd_repair_t * protocol; - - ulong enforce_fixed_fec_set; /* min slot where the feature is enforced */ - - fd_pubkey_t identity_public_key; - - fd_wksp_t * wksp; - - fd_stem_context_t * stem; - - uchar in_kind[ MAX_IN_LINKS ]; - in_ctx_t in_links[ MAX_IN_LINKS ]; - - int skip_frag; - - out_ctx_t net_out_ctx[1]; - - out_ctx_t repair_out_ctx[1]; - - /* repair_sign links (to sign tiles 1+) - for round-robin distribution */ - - ulong repair_sign_cnt; - out_ctx_t repair_sign_out_ctx[ MAX_SIGN_TILE_CNT ]; - - ulong sign_rrobin_idx; - - /* Pending sign requests for async operations */ - - uint pending_key_next; - sign_req_t * signs_map; /* contains any request currently in the repair->sign or sign->repair dcache */ - sign_pending_t * pong_queue; /* contains any pong or initial warmup request waiting to be dispatched to repair->sign. Size is 2*FD_REPAIR_PEER_MAX */ - - ushort net_id; - - /* Buffers for incoming unreliable frags */ - uchar net_buf[ FD_NET_MTU ]; - uchar sign_buf[ sizeof(fd_ed25519_sig_t) ]; - - /* Store chunk for incoming reliable frags */ - ulong chunk; - ulong snap_out_chunk; /* store second to last chunk for snap_out */ - - fd_ip4_udp_hdrs_t intake_hdr[1]; - - fd_rnonce_ss_t repair_nonce_ss[1]; - - ulong manifest_slot; - struct { - ulong send_pkt_cnt; - ulong sent_pkt_types[FD_METRICS_ENUM_REPAIR_REQUEST_TYPE_CNT]; - ulong current_slot; - ulong old_shred; - ulong last_requested_slot; - ulong last_requested_orphan; - ulong sign_tile_unavail; - ulong rerequest; - ulong malformed_ping; - ulong unknown_peer_ping; - ulong fail_sigverify_ping; - fd_histf_t slot_compl_time[ 1 ]; - fd_histf_t response_latency[ 1 ]; - ulong blk_evicted; - ulong blk_failed_insert; - - ulong slot_evicted; - ulong slot_evicted_by; - ulong slot_failed_insert; - - ulong failed_chain_verify_cnt; - ulong failed_chain_verify_slot; - } metrics[ 1 ]; - - /* Slot-level metrics */ - - fd_repair_metrics_t * slot_metrics; - ulong turbine_slot0; // catchup considered complete after this slot -}; -typedef struct ctx ctx_t; - -#endif /* HEADER_fd_src_discof_repair_fd_repair_tile_private_h */ diff --git a/src/discof/repair/fd_reqlim.c b/src/discof/repair/fd_reqlim.c deleted file mode 100644 index 83aafde8607..00000000000 --- a/src/discof/repair/fd_reqlim.c +++ /dev/null @@ -1,129 +0,0 @@ -#include "fd_reqlim.h" - -void * -fd_reqlim_new( void * shmem, ulong dedup_max, ulong seed ) { - - if( FD_UNLIKELY( !shmem ) ) { - FD_LOG_WARNING(( "NULL mem" )); - return NULL; - } - - if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)shmem, fd_reqlim_align() ) ) ) { - FD_LOG_WARNING(( "misaligned mem" )); - return NULL; - } - - ulong footprint = fd_reqlim_footprint( dedup_max ); - fd_memset( shmem, 0, footprint ); - - FD_SCRATCH_ALLOC_INIT( l, shmem ); - fd_reqlim_t * dedup = FD_SCRATCH_ALLOC_APPEND( l, fd_reqlim_align(), sizeof(fd_reqlim_t) ); - void * map = FD_SCRATCH_ALLOC_APPEND( l, fd_reqlim_map_align(), fd_reqlim_map_footprint ( dedup_max ) ); - void * pool = FD_SCRATCH_ALLOC_APPEND( l, fd_reqlim_pool_align(), fd_reqlim_pool_footprint( dedup_max ) ); - void * lru = FD_SCRATCH_ALLOC_APPEND( l, fd_reqlim_lru_align(), fd_reqlim_lru_footprint() ); - FD_TEST( FD_SCRATCH_ALLOC_FINI( l, fd_reqlim_align() ) == (ulong)shmem + footprint ); - - dedup->map = fd_reqlim_map_new ( map, dedup_max, seed ); - dedup->pool = fd_reqlim_pool_new( pool, dedup_max ); - dedup->lru = fd_reqlim_lru_new ( lru ); - - return shmem; -} - -fd_reqlim_t * -fd_reqlim_join( void * shdedup ) { - fd_reqlim_t * dedup = (fd_reqlim_t *)shdedup; - - if( FD_UNLIKELY( !dedup ) ) { - FD_LOG_WARNING(( "NULL dedup" )); - return NULL; - } - - if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)dedup, fd_reqlim_align() ) ) ) { - FD_LOG_WARNING(( "misaligned dedup" )); - return NULL; - } - - dedup->map = fd_reqlim_map_join ( dedup->map ); - dedup->pool = fd_reqlim_pool_join( dedup->pool ); - dedup->lru = fd_reqlim_lru_join ( dedup->lru ); - - return dedup; -} - -void * -fd_reqlim_leave( fd_reqlim_t const * dedup ) { - - if( FD_UNLIKELY( !dedup ) ) { - FD_LOG_WARNING(( "NULL dedup" )); - return NULL; - } - - return (void *)dedup; -} - -void * -fd_reqlim_delete( void * dedup ) { - - if( FD_UNLIKELY( !dedup ) ) { - FD_LOG_WARNING(( "NULL dedup" )); - return NULL; - } - - if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)dedup, fd_reqlim_align() ) ) ) { - FD_LOG_WARNING(( "misaligned dedup" )); - return NULL; - } - - return dedup; -} - -/* dedup_evict evicts the least recently used element from the cache. */ - -static void -dedup_evict( fd_reqlim_t * dedup ) { - fd_reqlim_ele_t * ele = fd_reqlim_lru_ele_pop_head( dedup->lru, dedup->pool ); - fd_reqlim_map_ele_remove( dedup->map, &ele->key, NULL, dedup->pool ); - fd_reqlim_pool_ele_release( dedup->pool, ele ); -} - -int -fd_reqlim_next( fd_reqlim_t * dedup, ulong key, long now ) { - fd_reqlim_ele_t * ele = fd_reqlim_map_ele_query( dedup->map, &key, NULL, dedup->pool ); - if( FD_UNLIKELY( !ele ) ) { - if( FD_UNLIKELY( !fd_reqlim_pool_free( dedup->pool ) ) ) dedup_evict( dedup ); - ele = fd_reqlim_pool_ele_acquire( dedup->pool ); - ele->key = key; - ele->req_ts = 0; - fd_reqlim_map_ele_insert ( dedup->map, ele, dedup->pool ); - fd_reqlim_lru_ele_push_tail( dedup->lru, ele, dedup->pool ); - } - if( FD_LIKELY( now < ele->req_ts + (long)FD_REQLIM_DEDUP_TIMEOUT ) ) { - fd_reqlim_lru_ele_remove ( dedup->lru, ele, dedup->pool ); - fd_reqlim_lru_ele_push_tail( dedup->lru, ele, dedup->pool ); - return 1; - } - ele->req_ts = now; - fd_reqlim_lru_ele_remove ( dedup->lru, ele, dedup->pool ); - fd_reqlim_lru_ele_push_tail( dedup->lru, ele, dedup->pool ); - return 0; -} - -int -fd_reqlim_query( fd_reqlim_t const * dedup, ulong key, long now ) { - fd_reqlim_ele_t const * ele = fd_reqlim_map_ele_query_const( dedup->map, &key, NULL, dedup->pool ); - if( FD_LIKELY( ele && now < ele->req_ts + (long)FD_REQLIM_DEDUP_TIMEOUT ) ) { - return 1; - } - return 0; -} - -long -fd_reqlim_next_due( fd_reqlim_t const * dedup, ulong key, long now ) { - fd_reqlim_ele_t const * ele = fd_reqlim_map_ele_query_const( dedup->map, &key, NULL, dedup->pool ); - if( FD_LIKELY( ele ) ) { - long next_due = ele->req_ts + (long)FD_REQLIM_DEDUP_TIMEOUT; - if( FD_LIKELY( now < next_due ) ) return next_due; - } - return now; -} diff --git a/src/discof/repair/fd_reqlim.h b/src/discof/repair/fd_reqlim.h deleted file mode 100644 index 338231ba2d9..00000000000 --- a/src/discof/repair/fd_reqlim.h +++ /dev/null @@ -1,135 +0,0 @@ -#ifndef HEADER_fd_src_discof_repair_fd_reqlim_h -#define HEADER_fd_src_discof_repair_fd_reqlim_h - -/* fd_reqlim implements a dedup cache for already sent Repair requests. - It is backed by a map and linked list, in which the least recently - used (oldest Repair request) in the map is evicted when the map is - full. */ - -#include "../../util/fd_util.h" - -#define FD_REQLIM_DEDUP_TIMEOUT 80000000L /* 80 ms - how long to wait before re-requesting the same shred */ - -FD_STATIC_ASSERT( 16000000L < FD_REQLIM_DEDUP_TIMEOUT, "DEDUP_TIMEOUT must be greater than 16ms to avoid rnonces from trivially clashing" ); - -/* fd_reqlim_ele describes an element in the dedup cache. The key - compactly encodes an fd_repair_req_t. - - | kind (4 bits) | slot (32 bits) | shred_idx (28 bits) | - | bits 63:60 | bits 59:28 | bits 27:0 | - - kind uses the FD_REPAIR_KIND_* wire values directly. - - Note the common header (sig, from, to, ts, nonce) is not included. */ - -struct fd_reqlim_ele { - ulong key; /* compact encoding of fd_repair_req_t detailed above */ - ulong prev; /* reserved by lru */ - ulong next; - ulong hash; /* reserved by pool and map_chain */ - long req_ts; /* timestamp when the request was sent */ -}; -typedef struct fd_reqlim_ele fd_reqlim_ele_t; - -FD_FN_CONST static inline ulong -fd_reqlim_key( uint kind, ulong slot, uint shred_idx ) { - ulong k = (ulong)fd_uint_extract_lsb( kind, 4 ); - ulong i = (ulong)fd_uint_extract_lsb( shred_idx, 28 ); - ulong s = fd_ulong_extract_lsb( slot, 32 ); - return k << 60 | s << 28 | i; -} - -#define POOL_NAME fd_reqlim_pool -#define POOL_T fd_reqlim_ele_t -#define POOL_NEXT hash -#include "../../util/tmpl/fd_pool.c" - -#define MAP_NAME fd_reqlim_map -#define MAP_ELE_T fd_reqlim_ele_t -#define MAP_NEXT hash -#include "../../util/tmpl/fd_map_chain.c" - -#define DLIST_NAME fd_reqlim_lru -#define DLIST_ELE_T fd_reqlim_ele_t -#define DLIST_NEXT next -#define DLIST_PREV prev -#include "../../util/tmpl/fd_dlist.c" - -struct fd_reqlim { - fd_reqlim_map_t * map; /* map of dedup elements */ - fd_reqlim_ele_t * pool; /* memory pool of dedup elements */ - fd_reqlim_lru_t * lru; /* linked list of dedup elements by insertion order */ -}; -typedef struct fd_reqlim fd_reqlim_t; - -/* Constructors */ - -FD_FN_CONST static inline ulong -fd_reqlim_align( void ) { - return 128UL; -} - -FD_FN_CONST static inline ulong -fd_reqlim_footprint( ulong dedup_max ) { - return FD_LAYOUT_FINI( - FD_LAYOUT_APPEND( - FD_LAYOUT_APPEND( - FD_LAYOUT_APPEND( - FD_LAYOUT_APPEND( - FD_LAYOUT_INIT, - fd_reqlim_align(), sizeof(fd_reqlim_t) ), - fd_reqlim_map_align(), fd_reqlim_map_footprint ( dedup_max ) ), - fd_reqlim_pool_align(), fd_reqlim_pool_footprint( dedup_max ) ), - fd_reqlim_lru_align(), fd_reqlim_lru_footprint() ), - fd_reqlim_align() ); -} - -/* fd_reqlim_new formats an unused memory region for use as a dedup - cache. mem is a non-NULL pointer to this region in the local address - space with the required footprint and alignment. */ - -void * -fd_reqlim_new( void * shmem, ulong dedup_max, ulong seed ); - -/* fd_reqlim_join joins the caller to the dedup cache. Returns a pointer - in the local address space to dedup on success. */ - -fd_reqlim_t * -fd_reqlim_join( void * shdedup ); - -/* fd_reqlim_leave leaves a current local join. */ - -void * -fd_reqlim_leave( fd_reqlim_t const * dedup ); - -/* fd_reqlim_delete unformats a memory region used as a dedup cache. */ - -void * -fd_reqlim_delete( void * dedup ); - -/* fd_reqlim_next returns 1 if key is deduped (already sent within the - dedup timeout window), 0 otherwise. When not deduped, the key is - inserted/refreshed in the cache with the current timestamp. */ - -int -fd_reqlim_next( fd_reqlim_t * dedup, ulong key, long now ); - -/* fd_reqlim_query returns 1 if key is in the dedup cache and was sent - within the dedup timeout window, 0 otherwise. Read-only: does not - insert or update any state. */ - -int -fd_reqlim_query( fd_reqlim_t const * dedup, ulong key, long now ); - -/* fd_reqlim_next_due returns the earliest timestamp at which key may be - sent without being deduped. It is read-only. If key is absent or its - timeout has elapsed, returns now. Immediately after - fd_reqlim_next( dedup, key, now ) it returns a strictly future - deadline: the key is in the table, either still in its window or - just recorded at now (fd_reqlim_next evicts to make room, it cannot - fail to record). */ - -long -fd_reqlim_next_due( fd_reqlim_t const * dedup, ulong key, long now ); - -#endif /* HEADER_fd_src_discof_repair_fd_reqlim_h */ diff --git a/src/discof/repair/generated/fd_repair_tile_seccomp.h b/src/discof/repair/generated/fd_repair_tile_seccomp.h deleted file mode 100644 index 05395c07077..00000000000 --- a/src/discof/repair/generated/fd_repair_tile_seccomp.h +++ /dev/null @@ -1,98 +0,0 @@ -/* THIS FILE WAS GENERATED BY generate_filters.py. DO NOT EDIT BY HAND! */ -#ifndef HEADER_fd_src_discof_repair_generated_fd_repair_tile_seccomp_h -#define HEADER_fd_src_discof_repair_generated_fd_repair_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 -#elif defined(__riscv) && __riscv_xlen == 64 -# define ARCH_NR AUDIT_ARCH_RISCV64 -#else -# error "Target architecture is unsupported by seccomp." -#endif - -#define FD_SECCOMP_ARG_LO_OFFSET(argno) ( offsetof( struct seccomp_data, args[(argno)] ) ) -#define FD_SECCOMP_ARG_HI_OFFSET(argno) ( offsetof( struct seccomp_data, args[(argno)] ) + 4U ) - -#define FD_SECCOMP_ARG_LO(x) ((uint)(((ulong)(uint)(int)(x) ) & 0xffffffffUL)) -#define FD_SECCOMP_ARG_HI(x) ((uint)(((ulong)(x) >> 32) & 0xffffffffUL)) - -static const uint sock_filter_policy_fd_repair_tile_instr_cnt = 25; - -static void populate_sock_filter_policy_fd_repair_tile( ulong out_cnt, struct sock_filter out[ static 25 ], uint logfile_fd ) { - FD_TEST( out_cnt >= 25 ); - struct sock_filter filter[25] = { - /* validate architecture */ - 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 */ 5 ), - /* load syscall number */ - BPF_STMT( BPF_LD | BPF_W | BPF_ABS, ( offsetof( struct seccomp_data, nr ) )), - /* check write */ - BPF_JUMP( BPF_JMP | BPF_JEQ | BPF_K, SYS_write, /* check_write */ 5, 0 ), - /* check fsync */ - BPF_JUMP( BPF_JMP | BPF_JEQ | BPF_K, SYS_fsync, /* check_fsync */ 10, 0 ), - /* check futex */ - BPF_JUMP( BPF_JMP | BPF_JEQ | BPF_K, SYS_futex, /* check_futex */ 13, 0 ), - /* allow restart_syscall */ - BPF_JUMP( BPF_JMP | BPF_JEQ | BPF_K, SYS_restart_syscall, /* RET_ALLOW */ 1, 0 ), -// RET_KILL_PROCESS: - /* default deny */ - BPF_STMT( BPF_RET | BPF_K, SECCOMP_RET_KILL_PROCESS ), -// RET_ALLOW: - /* allow */ - BPF_STMT( BPF_RET | BPF_K, SECCOMP_RET_ALLOW ), -// check_write: - /* arg 0 low 32 bits */ - BPF_STMT( BPF_LD | BPF_W | BPF_ABS, FD_SECCOMP_ARG_LO_OFFSET(0)), - BPF_JUMP( BPF_JMP | BPF_JEQ | BPF_K, 0x00000002U, /* write_ALLOW */ 3, /* or_1 */ 0 ), -// or_1: - /* arg 0 low 32 bits */ - BPF_STMT( BPF_LD | BPF_W | BPF_ABS, FD_SECCOMP_ARG_LO_OFFSET(0)), - BPF_JUMP( BPF_JMP | BPF_JEQ | BPF_K, ((uint)(logfile_fd)), /* write_ALLOW */ 1, /* write_KILL */ 0 ), -// write_KILL: - BPF_STMT( BPF_RET | BPF_K, SECCOMP_RET_KILL_PROCESS ), -// write_ALLOW: - BPF_STMT( BPF_RET | BPF_K, SECCOMP_RET_ALLOW ), -// check_fsync: - /* arg 0 low 32 bits */ - BPF_STMT( BPF_LD | BPF_W | BPF_ABS, FD_SECCOMP_ARG_LO_OFFSET(0)), - BPF_JUMP( BPF_JMP | BPF_JEQ | BPF_K, ((uint)(logfile_fd)), /* fsync_ALLOW */ 1, /* fsync_KILL */ 0 ), -// fsync_KILL: - BPF_STMT( BPF_RET | BPF_K, SECCOMP_RET_KILL_PROCESS ), -// fsync_ALLOW: - BPF_STMT( BPF_RET | BPF_K, SECCOMP_RET_ALLOW ), -// check_futex: - /* arg 1 low 32 bits */ - BPF_STMT( BPF_LD | BPF_W | BPF_ABS, FD_SECCOMP_ARG_LO_OFFSET(1)), - BPF_JUMP( BPF_JMP | BPF_JEQ | BPF_K, FD_SECCOMP_ARG_LO(FUTEX_WAIT_BITSET), /* and_2 */ 0, /* futex_KILL */ 2 ), -// and_2: - /* arg 2 low 32 bits */ - BPF_STMT( BPF_LD | BPF_W | BPF_ABS, FD_SECCOMP_ARG_LO_OFFSET(2)), - BPF_JUMP( BPF_JMP | BPF_JEQ | BPF_K, 0x00000000U, /* futex_ALLOW */ 1, /* futex_KILL */ 0 ), -// futex_KILL: - BPF_STMT( BPF_RET | BPF_K, SECCOMP_RET_KILL_PROCESS ), -// futex_ALLOW: - BPF_STMT( BPF_RET | BPF_K, SECCOMP_RET_ALLOW ), - }; - fd_memcpy( out, filter, sizeof( filter ) ); -} - -#endif /* defined(__linux__) */ - -#endif /* HEADER_fd_src_discof_repair_generated_fd_repair_tile_seccomp_h */ diff --git a/src/discof/repair/test_policy.c b/src/discof/repair/test_policy.c deleted file mode 100644 index be2dce5be5a..00000000000 --- a/src/discof/repair/test_policy.c +++ /dev/null @@ -1,1089 +0,0 @@ -#include "fd_policy.h" -#include "../../disco/metrics/fd_metrics.h" - -/* A turbine observation stamp whose defer window will not expire during - the test: marks the candidate's FEC set as first observed ~65 s after - first_shred_ts so the throttle stays active. */ -#define FAR_MS USHORT_MAX - -void -test_peer_removal( fd_wksp_t * wksp ) { - ulong peer_max = 1024; - - fd_rnonce_ss_t rnonce[1]; - fd_memset( rnonce, '\xCC', sizeof(fd_rnonce_ss_t) ); - void * mem = fd_wksp_alloc_laddr( wksp, fd_policy_align(), fd_policy_footprint( peer_max ), 1 ); - fd_policy_t * policy = fd_policy_join( fd_policy_new( mem, peer_max, 0, rnonce ) ); - FD_TEST( policy ); - - int num_slow = 0; - int num_fast = 0; - for( int i = 1; i < 64; i++ ){ - fd_pubkey_t key = { .key = { (uchar)i } }; - fd_ip4_port_t addr = { 0 }; - fd_policy_peer_t const * peer = fd_policy_peer_upsert( policy, &key, &addr ); - fd_policy_peer_response_update( policy, &key, (long)(i*10e6L)); - if ( i <= 10 ) num_fast++; - else num_slow++; - FD_TEST( peer ); - } - /* peek the first peer in the fast dlist */ - fd_policy_peer_t * peer = fd_policy_peer_dlist_ele_peek_head( policy->peers.fast, policy->peers.pool ); - FD_TEST( peer ); - fd_pubkey_t key1 = peer->key; - FD_TEST( fd_policy_peer_remove( policy, &peer->key ) ); - - - peer = fd_policy_peer_dlist_ele_peek_head( policy->peers.slow, policy->peers.pool ); - FD_TEST( peer ); - fd_pubkey_t key2 = peer->key; - FD_TEST( fd_policy_peer_remove( policy, &peer->key ) ); - - /* check that neither dlist contains the peer */ - int i = 0; - for( fd_policy_peer_dlist_iter_t iter = fd_policy_peer_dlist_iter_fwd_init( policy->peers.fast, policy->peers.pool ); - !fd_policy_peer_dlist_iter_done( iter, policy->peers.fast, policy->peers.pool ); - iter = fd_policy_peer_dlist_iter_fwd_next( iter, policy->peers.fast, policy->peers.pool ) ){ - fd_policy_peer_t * peer = fd_policy_peer_dlist_iter_ele( iter, policy->peers.fast, policy->peers.pool ); - FD_BASE58_ENCODE_32_BYTES( peer->key.key, p ); - FD_TEST( memcmp( peer->key.key, key1.key, 32UL ) != 0 ); - FD_TEST( memcmp( peer->key.key, key2.key, 32UL ) != 0 ); - FD_LOG_DEBUG(( " %d. fast peer identity: %s", i, p )); - i++; - } - FD_TEST( num_fast-1 == i ); - - i = 0; - for( fd_policy_peer_dlist_iter_t iter = fd_policy_peer_dlist_iter_fwd_init( policy->peers.slow, policy->peers.pool ); !fd_policy_peer_dlist_iter_done( iter, policy->peers.slow, policy->peers.pool ); iter = fd_policy_peer_dlist_iter_fwd_next( iter, policy->peers.slow, policy->peers.pool ) ){ - fd_policy_peer_t * peer = fd_policy_peer_dlist_iter_ele( iter, policy->peers.slow, policy->peers.pool ); - FD_BASE58_ENCODE_32_BYTES( peer->key.key, p ); - i++; - FD_TEST( memcmp( peer->key.key, key1.key, 32UL ) != 0 ); - FD_TEST( memcmp( peer->key.key, key2.key, 32UL ) != 0 ); - FD_LOG_DEBUG(( " %d. slow peer identity: %s", i, p )); - } - FD_TEST( num_slow-1 == i ); - - peer = fd_policy_peer_dlist_ele_peek_tail( policy->peers.fast, policy->peers.pool ); - FD_TEST( peer ); - FD_TEST( fd_policy_peer_remove( policy, &peer->key ) ); - - peer = fd_policy_peer_dlist_ele_peek_tail( policy->peers.slow, policy->peers.pool ); - FD_TEST( peer ); - FD_TEST( fd_policy_peer_remove( policy, &peer->key ) ); - - fd_pubkey_t key65 = { .key = { (uchar)65 } }; - fd_ip4_port_t addr65 = { 0 }; - FD_TEST( fd_policy_peer_upsert( policy, &key65, &addr65 ) ); - /* check this peer is in the slow dlist */ - - int found = 0; - i = 0; - for( fd_policy_peer_dlist_iter_t iter = fd_policy_peer_dlist_iter_fwd_init( policy->peers.slow, policy->peers.pool ); - !fd_policy_peer_dlist_iter_done ( iter, policy->peers.slow, policy->peers.pool ); - iter = fd_policy_peer_dlist_iter_fwd_next( iter, policy->peers.slow, policy->peers.pool ) ){ - fd_policy_peer_t * peer = fd_policy_peer_dlist_iter_ele( iter, policy->peers.slow, policy->peers.pool ); - FD_BASE58_ENCODE_32_BYTES( peer->key.key, p ); - FD_LOG_DEBUG(( " %d. slow peer identity: %s", i, p )); - i++; - if( memcmp( peer->key.key, key65.key, 32UL ) == 0 ) found = 1; - } - FD_TEST( num_slow-1 == i ); - FD_TEST( found ); - - /* print fast peers */ - i = 0; - for( fd_policy_peer_dlist_iter_t iter = fd_policy_peer_dlist_iter_fwd_init( policy->peers.fast, policy->peers.pool ); - !fd_policy_peer_dlist_iter_done ( iter, policy->peers.fast, policy->peers.pool ); - iter = fd_policy_peer_dlist_iter_fwd_next( iter, policy->peers.fast, policy->peers.pool ) ){ - fd_policy_peer_t * peer = fd_policy_peer_dlist_iter_ele( iter, policy->peers.fast, policy->peers.pool ); - FD_BASE58_ENCODE_32_BYTES( peer->key.key, p ); - FD_LOG_DEBUG(( " %d. fast peer identity: %s", i, p )); - FD_TEST( memcmp( peer->key.key, key65.key, 32UL ) != 0 ); - i++; - } - FD_TEST( num_fast-2 == i ); - - /* put another peer in the slow -> fast dlist */ - fd_pubkey_t key66 = { .key = { (uchar)66 } }; - fd_ip4_port_t addr66 = { 0 }; - FD_TEST( fd_policy_peer_upsert( policy, &key66, &addr66 ) ); - /* update key66s latency to move it to the fast dlist */ - fd_policy_peer_response_update( policy, &key66, (long)(10e6L)); - peer = fd_policy_peer_dlist_ele_peek_tail( policy->peers.fast, policy->peers.pool ); - FD_TEST( peer ); - FD_BASE58_ENCODE_32_BYTES( peer->key.key, p ); - FD_TEST( memcmp( peer->key.key, key66.key, 32UL ) == 0 ); - - /* move it to slow list (EWMA needs a large enough sample to cross threshold) */ - fd_policy_peer_response_update( policy, &key66, (long)(1000e6L)); - peer = fd_policy_peer_dlist_ele_peek_tail( policy->peers.slow, policy->peers.pool ); - FD_TEST( peer ); - FD_TEST( memcmp( peer->key.key, key66.key, 32UL ) == 0 ); -} - -void -test_peer_interleave( fd_wksp_t * wksp ) { - ulong peer_max = 1024; - - fd_rnonce_ss_t rnonce[1]; - fd_memset( rnonce, '\xCC', sizeof(fd_rnonce_ss_t) ); - void * mem = fd_wksp_alloc_laddr( wksp, fd_policy_align(), fd_policy_footprint( peer_max ), 1 ); - fd_policy_t * policy = fd_policy_join( fd_policy_new( mem, peer_max, 0, rnonce ) ); - FD_TEST( policy ); - - /* Insert 6 fast peers (ids 1-6) and 6 slow peers (ids 7-12). */ - for( int i = 1; i <= 12; i++ ) { - fd_pubkey_t key = { .key = { (uchar)i } }; - fd_ip4_port_t addr = { 0 }; - fd_policy_peer_upsert( policy, &key, &addr ); - if( i <= 6 ) { - fd_policy_peer_response_update( policy, &key, (long)(10e6L) ); - } - } - - /* Verify 6:1 interleaving pattern over two full cycles (14 selects). */ - for( int cycle = 0; cycle < 2; cycle++ ) { - for( int j = 0; j < (int)FD_POLICY_FAST_PER_SLOW; j++ ) { - fd_pubkey_t const * sel = fd_policy_peer_select( policy ); - FD_TEST( sel ); - fd_policy_peer_t * p = fd_policy_peer_query( policy, sel ); - FD_TEST( p && p->res_cnt > 0 ); /* fast peer */ - } - fd_pubkey_t const * sel = fd_policy_peer_select( policy ); - FD_TEST( sel ); - fd_policy_peer_t * p = fd_policy_peer_query( policy, sel ); - FD_TEST( p && p->res_cnt == 0 ); /* slow peer */ - } - - /* Test all-slow: insert only slow peers. */ - void * mem2 = fd_wksp_alloc_laddr( wksp, fd_policy_align(), fd_policy_footprint( peer_max ), 1 ); - fd_policy_t * policy2 = fd_policy_join( fd_policy_new( mem2, peer_max, 0, rnonce ) ); - for( int i = 1; i <= 5; i++ ) { - fd_pubkey_t key = { .key = { (uchar)i } }; - fd_ip4_port_t addr = { 0 }; - fd_policy_peer_upsert( policy2, &key, &addr ); - } - for( int i = 0; i < 15; i++ ) { - fd_pubkey_t const * sel = fd_policy_peer_select( policy2 ); - FD_TEST( sel ); - } - - /* Test all-fast: insert only fast peers. */ - void * mem3 = fd_wksp_alloc_laddr( wksp, fd_policy_align(), fd_policy_footprint( peer_max ), 1 ); - fd_policy_t * policy3 = fd_policy_join( fd_policy_new( mem3, peer_max, 0, rnonce ) ); - for( int i = 1; i <= 5; i++ ) { - fd_pubkey_t key = { .key = { (uchar)i } }; - fd_ip4_port_t addr = { 0 }; - fd_policy_peer_upsert( policy3, &key, &addr ); - fd_policy_peer_response_update( policy3, &key, (long)(10e6L) ); - } - for( int i = 0; i < 15; i++ ) { - fd_pubkey_t const * sel = fd_policy_peer_select( policy3 ); - FD_TEST( sel ); - } -} - -void -test_ewma_latency( fd_wksp_t * wksp ) { - ulong peer_max = 1024; - - fd_rnonce_ss_t rnonce[1]; - fd_memset( rnonce, '\xCC', sizeof(fd_rnonce_ss_t) ); - void * mem = fd_wksp_alloc_laddr( wksp, fd_policy_align(), fd_policy_footprint( peer_max ), 1 ); - fd_policy_t * policy = fd_policy_join( fd_policy_new( mem, peer_max, 0, rnonce ) ); - FD_TEST( policy ); - - fd_pubkey_t key = { .key = { 1 } }; - fd_ip4_port_t addr = { 0 }; - fd_policy_peer_upsert( policy, &key, &addr ); - - /* First response seeds EWMA. */ - fd_policy_peer_response_update( policy, &key, (long)(50e6L) ); - fd_policy_peer_t * peer = fd_policy_peer_query( policy, &key ); - FD_TEST( peer ); - FD_TEST( peer->ewma_lat == (long)(50e6L) ); - FD_TEST( fd_policy_peer_latency_bucket( policy, peer->ewma_lat, peer->res_cnt ) == policy->peers.fast ); - - /* Several fast responses should keep it fast. */ - for( int i = 0; i < 10; i++ ) { - fd_policy_peer_response_update( policy, &key, (long)(40e6L) ); - } - peer = fd_policy_peer_query( policy, &key ); - FD_TEST( peer->ewma_lat < (long)FD_POLICY_LATENCY_THRESH ); - FD_TEST( fd_policy_peer_latency_bucket( policy, peer->ewma_lat, peer->res_cnt ) == policy->peers.fast ); - - /* Sustained high-latency responses should eventually push to slow. */ - for( int i = 0; i < 30; i++ ) { - fd_policy_peer_response_update( policy, &key, (long)(200e6L) ); - } - peer = fd_policy_peer_query( policy, &key ); - FD_TEST( peer->ewma_lat > (long)FD_POLICY_LATENCY_THRESH ); - FD_TEST( fd_policy_peer_latency_bucket( policy, peer->ewma_lat, peer->res_cnt ) == policy->peers.slow ); -} - -void -test_remove_sole_peer( fd_wksp_t * wksp ) { - ulong peer_max = 1024; - - fd_rnonce_ss_t rnonce[1]; - fd_memset( rnonce, '\xCC', sizeof(fd_rnonce_ss_t) ); - void * mem = fd_wksp_alloc_laddr( wksp, fd_policy_align(), fd_policy_footprint( peer_max ), 1 ); - fd_policy_t * policy = fd_policy_join( fd_policy_new( mem, peer_max, 0, rnonce ) ); - FD_TEST( policy ); - - /* Single slow peer — iterator sits on it. */ - fd_pubkey_t key1 = { .key = { 1 } }; - fd_ip4_port_t addr = { 0 }; - fd_policy_peer_upsert( policy, &key1, &addr ); - fd_policy_peer_select( policy ); /* parks slow_iter on key1 */ - FD_TEST( fd_policy_peer_remove( policy, &key1 ) ); - - /* Iterator must not dangle — select should return NULL (no peers). */ - FD_TEST( fd_policy_peer_select( policy ) == NULL ); - - /* Re-insert a peer and verify select still works. */ - fd_pubkey_t key2 = { .key = { 2 } }; - fd_policy_peer_upsert( policy, &key2, &addr ); - fd_pubkey_t const * sel = fd_policy_peer_select( policy ); - FD_TEST( sel && memcmp( sel->key, key2.key, 32UL ) == 0 ); - - /* Single fast peer — same scenario. */ - fd_policy_peer_response_update( policy, &key2, (long)(10e6L) ); - fd_policy_peer_select( policy ); /* parks fast_iter on key2 */ - FD_TEST( fd_policy_peer_remove( policy, &key2 ) ); - FD_TEST( fd_policy_peer_select( policy ) == NULL ); -} - -/* Helper: count elements in a peer dlist. */ -static ulong -dlist_cnt( fd_policy_peer_dlist_t * dlist, fd_policy_peer_t * pool ) { - ulong cnt = 0; - for( fd_policy_peer_dlist_iter_t it = fd_policy_peer_dlist_iter_fwd_init( dlist, pool ); - !fd_policy_peer_dlist_iter_done( it, dlist, pool ); - it = fd_policy_peer_dlist_iter_fwd_next( it, dlist, pool ) ) { - cnt++; - } - return cnt; -} - -/* Helper: return 1 if pubkey is found in dlist, 0 otherwise. */ -static int -dlist_contains( fd_policy_peer_dlist_t * dlist, fd_policy_peer_t * pool, fd_pubkey_t const * key ) { - for( fd_policy_peer_dlist_iter_t it = fd_policy_peer_dlist_iter_fwd_init( dlist, pool ); - !fd_policy_peer_dlist_iter_done( it, dlist, pool ); - it = fd_policy_peer_dlist_iter_fwd_next( it, dlist, pool ) ) { - fd_policy_peer_t * p = fd_policy_peer_dlist_iter_ele( it, dlist, pool ); - if( !memcmp( p->key.key, key->key, 32UL ) ) return 1; - } - return 0; -} - -/* Helper: create a fresh policy on wksp. */ -static fd_policy_t * -new_policy( fd_wksp_t * wksp ) { - fd_rnonce_ss_t rnonce[1]; - fd_memset( rnonce, '\xCC', sizeof(fd_rnonce_ss_t) ); - void * mem = fd_wksp_alloc_laddr( wksp, fd_policy_align(), fd_policy_footprint( 1024UL ), 1 ); - return fd_policy_join( fd_policy_new( mem, 1024UL, 0, rnonce ) ); -} - -/* Orphan heads pop in creation order, reschedule one dedup window - after each request, and stop being served once connected. */ -static void -test_orphan_due_prq( fd_wksp_t * wksp ) { - ulong const slot_max = 16UL; - void * forest_mem = fd_wksp_alloc_laddr( wksp, fd_forest_align(), fd_forest_footprint( slot_max, FD_SHRED_BLK_MAX ), 1UL ); - void * dedup_mem = fd_wksp_alloc_laddr( wksp, fd_reqlim_align(), fd_reqlim_footprint( slot_max ), 1UL ); - void * repair_mem = fd_wksp_alloc_laddr( wksp, fd_repair_align(), fd_repair_footprint(), 1UL ); - FD_TEST( forest_mem && dedup_mem && repair_mem ); - - fd_pubkey_t identity = {0}; - fd_forest_t * forest = fd_forest_join( fd_forest_new( forest_mem, slot_max, FD_SHRED_BLK_MAX, 0UL ) ); - fd_reqlim_t * dedup = fd_reqlim_join( fd_reqlim_new( dedup_mem, slot_max, 0UL ) ); - fd_repair_t * repair = fd_repair_join( fd_repair_new( repair_mem, &identity ) ); - fd_policy_t * policy = new_policy( wksp ); - FD_TEST( forest && dedup && repair && policy ); - - fd_forest_init( forest, 0UL ); - - fd_pubkey_t peer = { .ul = { 1UL } }; - fd_ip4_port_t addr = { .addr = 1U, .port = 1U }; - FD_TEST( fd_policy_peer_upsert( policy, &peer, &addr ) ); - - fd_forest_blk_t * orphan0 = fd_forest_blk_insert( forest, 102UL, 100UL, NULL ); - fd_forest_blk_t * orphan1 = fd_forest_blk_insert( forest, 103UL, 101UL, NULL ); - FD_TEST( orphan0 && orphan1 ); - FD_TEST( !fd_forest_verify( forest ) ); - - /* Fresh heads are immediately eligible, in creation order. */ - long now = 1000000000L; - int charge_busy; - fd_repair_msg_t const * msg = fd_policy_next( policy, dedup, forest, repair, now, ULONG_MAX, &charge_busy ); - FD_TEST( charge_busy && msg && msg->kind==FD_REPAIR_KIND_ORPHAN && msg->orphan.slot==102UL ); - FD_TEST( fd_reqlim_next_due( dedup, fd_reqlim_key( FD_REPAIR_KIND_ORPHAN, 102UL, UINT_MAX ), now )==now+FD_REQLIM_DEDUP_TIMEOUT ); - - msg = fd_policy_next( policy, dedup, forest, repair, now+1L, ULONG_MAX, &charge_busy ); - FD_TEST( charge_busy && msg && msg->kind==FD_REPAIR_KIND_ORPHAN && msg->orphan.slot==103UL ); - FD_TEST( !fd_forest_verify( forest ) ); - - /* Both in their dedup window: no orphan requests. */ - msg = fd_policy_next( policy, dedup, forest, repair, now+2L, ULONG_MAX, &charge_busy ); - FD_TEST( !msg || msg->kind!=FD_REPAIR_KIND_ORPHAN ); - - /* Window expiry: earliest touched head goes first again. */ - msg = fd_policy_next( policy, dedup, forest, repair, now+FD_REQLIM_DEDUP_TIMEOUT, ULONG_MAX, &charge_busy ); - FD_TEST( charge_busy && msg && msg->kind==FD_REPAIR_KIND_ORPHAN && msg->orphan.slot==102UL ); - - /* Connect 102 under the root: its entry goes stale and 103 is the - only head still served. */ - FD_TEST( fd_forest_blk_insert( forest, 100UL, 0UL, NULL ) ); - FD_TEST( !fd_forest_verify( forest ) ); - now += 2L*FD_REQLIM_DEDUP_TIMEOUT; - msg = fd_policy_next( policy, dedup, forest, repair, now, ULONG_MAX, &charge_busy ); - FD_TEST( msg && msg->kind==FD_REPAIR_KIND_ORPHAN && msg->orphan.slot==103UL ); - msg = fd_policy_next( policy, dedup, forest, repair, now+1L, ULONG_MAX, &charge_busy ); - FD_TEST( !msg || msg->kind!=FD_REPAIR_KIND_ORPHAN ); - FD_TEST( !fd_forest_verify( forest ) ); -} - -/* Helper: run fd_policy_next up to 4 turns (the DFS iterator may need - a turn to rewind) and return the first message produced. */ -static fd_repair_msg_t const * -next_msg( fd_policy_t * policy, fd_reqlim_t * dedup, fd_forest_t * forest, fd_repair_t * repair, long now, ulong highest ) { - int charge_busy; - for( ulong i=0UL; i<4UL; i++ ) { - fd_repair_msg_t const * msg = fd_policy_next( policy, dedup, forest, repair, now+(long)i, highest, &charge_busy ); - if( FD_LIKELY( msg ) ) return msg; - } - return NULL; -} - -/* Declined head-slot shred candidates are memoized until the dedup - window opens, a shred arrives, or the highest known slot moves. */ -static void -test_shred_skip_memo( fd_wksp_t * wksp ) { - ulong const slot_max = 16UL; - void * forest_mem = fd_wksp_alloc_laddr( wksp, fd_forest_align(), fd_forest_footprint( slot_max, FD_SHRED_BLK_MAX ), 1UL ); - void * dedup_mem = fd_wksp_alloc_laddr( wksp, fd_reqlim_align(), fd_reqlim_footprint( slot_max ), 1UL ); - void * repair_mem = fd_wksp_alloc_laddr( wksp, fd_repair_align(), fd_repair_footprint(), 1UL ); - FD_TEST( forest_mem && dedup_mem && repair_mem ); - - fd_pubkey_t identity = {0}; - fd_forest_t * forest = fd_forest_join( fd_forest_new( forest_mem, slot_max, FD_SHRED_BLK_MAX, 0UL ) ); - fd_reqlim_t * dedup = fd_reqlim_join( fd_reqlim_new( dedup_mem, slot_max, 0UL ) ); - fd_repair_t * repair = fd_repair_join( fd_repair_new( repair_mem, &identity ) ); - fd_policy_t * policy = new_policy( wksp ); - FD_TEST( forest && dedup && repair && policy ); - - fd_forest_init( forest, 0UL ); - - fd_pubkey_t peer = { .ul = { 1UL } }; - fd_ip4_port_t addr = { .addr = 1U, .port = 1U }; - FD_TEST( fd_policy_peer_upsert( policy, &peer, &addr ) ); - - /* Head-of-turbine slot: shreds 0..4 buffered, end unknown. */ - fd_forest_blk_t * blk = fd_forest_blk_insert( forest, 1UL, 0UL, NULL ); - FD_TEST( blk ); - blk->buffered_idx = 4U; - - /* Candidate (1,5) is fresh: requested immediately. */ - long now = 1000000000L; - fd_repair_msg_t const * msg = next_msg( policy, dedup, forest, repair, now, 1UL ); - FD_TEST( msg && msg->kind==FD_REPAIR_KIND_SHRED && msg->shred.slot==1UL && msg->shred.shred_idx==5UL ); - - /* Requests are recorded in the dedup table at the repair tile level: - model that, then the deduped candidate memoizes to the window - expiry with no further dedup probes. */ - long t1 = now+10L; - FD_TEST( !fd_reqlim_next( dedup, fd_reqlim_key( FD_REPAIR_KIND_SHRED, 1UL, 5U ), t1 ) ); - FD_TEST( !next_msg( policy, dedup, forest, repair, t1+1L, 1UL ) ); - FD_TEST( fd_policy_skip( policy, 1UL )->slot==1UL && fd_policy_skip( policy, 1UL )->idx==5U ); - FD_TEST( fd_policy_skip( policy, 1UL )->until==t1+FD_REQLIM_DEDUP_TIMEOUT ); - FD_TEST( !next_msg( policy, dedup, forest, repair, t1+10L, 1UL ) ); - - /* A new shred moves the candidate and bypasses the memo. */ - blk->buffered_idx = 5U; - msg = next_msg( policy, dedup, forest, repair, t1+20L, 1UL ); - FD_TEST( msg && msg->kind==FD_REPAIR_KIND_SHRED && msg->shred.slot==1UL && msg->shred.shred_idx==6UL ); - - /* Memoize (1,6), then advance the highest known slot: the memo must - not suppress the now-due highest-shred request for slot 1. */ - long t2 = t1+30L; - FD_TEST( !fd_reqlim_next( dedup, fd_reqlim_key( FD_REPAIR_KIND_SHRED, 1UL, 6U ), t2 ) ); - FD_TEST( !next_msg( policy, dedup, forest, repair, t2+1L, 1UL ) ); - FD_TEST( fd_policy_skip( policy, 1UL )->slot==1UL && fd_policy_skip( policy, 1UL )->idx==6U ); - msg = next_msg( policy, dedup, forest, repair, t1+40L, 2UL ); - FD_TEST( msg && msg->kind==FD_REPAIR_KIND_HIGHEST_SHRED ); - - /* Back at the head, the memo holds until its recorded expiry. */ - FD_TEST( !next_msg( policy, dedup, forest, repair, fd_policy_skip( policy, 1UL )->until-1L-3L, 1UL ) ); - msg = next_msg( policy, dedup, forest, repair, fd_policy_skip( policy, 1UL )->until, 1UL ); - FD_TEST( msg && msg->kind==FD_REPAIR_KIND_SHRED && msg->shred.slot==1UL && msg->shred.shred_idx==6UL ); - - /* A concrete hole under an active throttle memoizes too: the decline - resets the iterator to UINT_MAX, the next visit restores the - concrete index, and the memo must still match. */ - fd_policy_set_turbine_slot0( policy, 1UL ); /* live slot: throttle applies */ - fd_forest_blk_recv( forest, blk )[0].first = FAR_MS; /* deadline far out: throttle active, memo capped at 1ms */ - blk->first_shred_ts = fd_tickcount(); - blk->complete_idx = 8U; /* end known: iterator yields concrete missing idxs */ - long t3 = t2+50L; - FD_TEST( !next_msg( policy, dedup, forest, repair, t3, 1UL ) ); - FD_TEST( fd_policy_skip( policy, 1UL )->slot==1UL && fd_policy_skip( policy, 1UL )->throttled ); - long until = fd_policy_skip( policy, 1UL )->until; - FD_TEST( until==t3+(long)1e6 ); - FD_TEST( !next_msg( policy, dedup, forest, repair, t3+10L, 1UL ) ); - FD_TEST( fd_policy_skip( policy, 1UL )->until==until ); /* memo hit: throttle not re-derived */ - - /* Throttle clears: the hole is requested. */ - fd_forest_blk_recv( forest, blk )[0].first = 0; - blk->first_shred_ts = 0L; - msg = next_msg( policy, dedup, forest, repair, until+1L, 1UL ); - FD_TEST( msg && msg->kind==FD_REPAIR_KIND_SHRED && msg->shred.slot==1UL ); -} - -/* The throttle applies to every live slot, not just the head. Two - concurrently throttled slots (head and its parent) round-robin - through the iterator; each must be memoized on its own so neither is - re-derived nor clobbers the other. */ -/* An older slot (below the head) with no known interior hole probes - the highest shred first; while that probe is still rate limited it - asks for the tail shred directly; once both are rate limited it sends - nothing and writes no memo (only the head memoizes dedup declines). */ -static void -test_non_head_tail_request( fd_wksp_t * wksp ) { - ulong const slot_max = 16UL; - void * forest_mem = fd_wksp_alloc_laddr( wksp, fd_forest_align(), fd_forest_footprint( slot_max, FD_SHRED_BLK_MAX ), 1UL ); - void * dedup_mem = fd_wksp_alloc_laddr( wksp, fd_reqlim_align(), fd_reqlim_footprint( slot_max ), 1UL ); - void * repair_mem = fd_wksp_alloc_laddr( wksp, fd_repair_align(), fd_repair_footprint(), 1UL ); - FD_TEST( forest_mem && dedup_mem && repair_mem ); - - fd_pubkey_t identity = {0}; - fd_forest_t * forest = fd_forest_join( fd_forest_new( forest_mem, slot_max, FD_SHRED_BLK_MAX, 0UL ) ); - fd_reqlim_t * dedup = fd_reqlim_join( fd_reqlim_new( dedup_mem, slot_max, 0UL ) ); - fd_repair_t * repair = fd_repair_join( fd_repair_new( repair_mem, &identity ) ); - fd_policy_t * policy = new_policy( wksp ); - FD_TEST( forest && dedup && repair && policy ); - fd_forest_init( forest, 0UL ); - - fd_pubkey_t peer = { .ul = { 1UL } }; - fd_ip4_port_t addr = { .addr = 1U, .port = 1U }; - FD_TEST( fd_policy_peer_upsert( policy, &peer, &addr ) ); - - /* Slot 1: shreds 0..4 buffered, end unknown, no turbine observed so - the throttle passes. Head is slot 2. */ - fd_forest_blk_t * blk = fd_forest_blk_insert( forest, 1UL, 0UL, NULL ); - FD_TEST( blk ); - blk->buffered_idx = 4U; - - /* First turn: highest-shred probe. */ - long now = 1000000000L; - fd_repair_msg_t const * msg = next_msg( policy, dedup, forest, repair, now, 2UL ); - FD_TEST( msg && msg->kind==FD_REPAIR_KIND_HIGHEST_SHRED && msg->highest_shred.slot==1UL ); - FD_TEST( blk->req_highest_cnt==1U && blk->req_window_cnt==0U ); - - /* Second turn, probe still rate limited: direct request for the tail - shred (1,5). req_window_cnt / first_req_ts are the repair tile's - to bump once the request is actually signed and sent, so they stay - untouched here: only req_highest_cnt is policy's. */ - msg = next_msg( policy, dedup, forest, repair, now+10L, 2UL ); - FD_TEST( msg && msg->kind==FD_REPAIR_KIND_SHRED && msg->shred.slot==1UL && msg->shred.shred_idx==5UL ); - FD_TEST( blk->req_window_cnt==0U && blk->first_req_ts==0L ); - - /* The repair tile records the tail request in the dedup table. Now - both are rate limited: nothing is sent. A dedup memo is written - even though this is not the head slot -- it is keyed on slot and - idx, so it can only ever match the candidate it was written for, - and the read side additionally gates it on the head. Writing it - unconditionally just keeps the store simple. */ - long t1 = now+20L; - FD_TEST( !fd_reqlim_next( dedup, fd_reqlim_key( FD_REPAIR_KIND_SHRED, 1UL, 5U ), t1 ) ); - fd_policy_skip_t * skip = fd_policy_skip( policy, 1UL ); - FD_TEST( !next_msg( policy, dedup, forest, repair, t1+1L, 2UL ) ); - FD_TEST( skip->slot==1UL && skip->idx==5U && !skip->throttled ); - FD_TEST( !next_msg( policy, dedup, forest, repair, t1+10L, 2UL ) ); - - /* Probe window expires first: probe again. */ - msg = next_msg( policy, dedup, forest, repair, now+FD_REQLIM_DEDUP_TIMEOUT, 2UL ); - FD_TEST( msg && msg->kind==FD_REPAIR_KIND_HIGHEST_SHRED && msg->highest_shred.slot==1UL ); - FD_TEST( blk->req_highest_cnt==2U ); - - /* A new shred moves the candidate: the tail request follows it. */ - blk->buffered_idx = 5U; - msg = next_msg( policy, dedup, forest, repair, now+FD_REQLIM_DEDUP_TIMEOUT+10L, 2UL ); - FD_TEST( msg && msg->kind==FD_REPAIR_KIND_SHRED && msg->shred.slot==1UL && msg->shred.shred_idx==6UL ); -} - -static void -test_skip_memo_non_head_throttle( fd_wksp_t * wksp ) { - ulong const slot_max = 16UL; - void * forest_mem = fd_wksp_alloc_laddr( wksp, fd_forest_align(), fd_forest_footprint( slot_max, FD_SHRED_BLK_MAX ), 1UL ); - void * dedup_mem = fd_wksp_alloc_laddr( wksp, fd_reqlim_align(), fd_reqlim_footprint( slot_max ), 1UL ); - void * repair_mem = fd_wksp_alloc_laddr( wksp, fd_repair_align(), fd_repair_footprint(), 1UL ); - FD_TEST( forest_mem && dedup_mem && repair_mem ); - - fd_pubkey_t identity = {0}; - fd_forest_t * forest = fd_forest_join( fd_forest_new( forest_mem, slot_max, FD_SHRED_BLK_MAX, 0UL ) ); - fd_reqlim_t * dedup = fd_reqlim_join( fd_reqlim_new( dedup_mem, slot_max, 0UL ) ); - fd_repair_t * repair = fd_repair_join( fd_repair_new( repair_mem, &identity ) ); - fd_policy_t * policy = new_policy( wksp ); - FD_TEST( forest && dedup && repair && policy ); - - fd_forest_init( forest, 0UL ); - fd_policy_set_turbine_slot0( policy, 1UL ); /* both slots live: throttle applies */ - - fd_pubkey_t peer = { .ul = { 1UL } }; - fd_ip4_port_t addr = { .addr = 1U, .port = 1U }; - FD_TEST( fd_policy_peer_upsert( policy, &peer, &addr ) ); - - /* Slot 1 (parent of the head) and slot 2 (head), both with the end - unknown and both far from their eager repair deadline. */ - fd_forest_blk_t * blk1 = fd_forest_blk_insert( forest, 1UL, 0UL, NULL ); - fd_forest_blk_t * blk2 = fd_forest_blk_insert( forest, 2UL, 1UL, NULL ); - FD_TEST( blk1 && blk2 ); - blk1->buffered_idx = 4U; fd_forest_blk_recv( forest, blk1 )[0].first = FAR_MS; blk1->first_shred_ts = fd_tickcount(); - blk2->buffered_idx = 2U; fd_forest_blk_recv( forest, blk2 )[0].first = FAR_MS; blk2->first_shred_ts = fd_tickcount(); - - fd_policy_skip_t * skip1 = fd_policy_skip( policy, 1UL ); - fd_policy_skip_t * skip2 = fd_policy_skip( policy, 2UL ); - FD_TEST( skip1!=skip2 ); - - /* Round-robin until both slots have been declined once. Every turn - is a decline. */ - long now = 1000000000L; - int charge_busy; - ulong turn = 0UL; - while( turn<8UL && !( skip1->slot==1UL && skip1->throttled && skip2->slot==2UL && skip2->throttled ) ) { - FD_TEST( !fd_policy_next( policy, dedup, forest, repair, now+(long)turn, 2UL, &charge_busy ) ); - FD_TEST( !charge_busy ); - turn++; - } - FD_TEST( skip1->slot==1UL && skip1->idx==5U && skip1->throttled ); - FD_TEST( skip2->slot==2UL && skip2->idx==3U && skip2->throttled ); - long until1 = skip1->until; - long until2 = skip2->until; - FD_TEST( until1<=now+(long)turn+(long)1e6 && until2<=now+(long)turn+(long)1e6 ); - - /* Further turns within both windows: each slot hits its own memo. - Neither is re-derived (until would move with now) nor clobbered. */ - for( ulong i=0UL; i<6UL; i++ ) { - FD_TEST( !fd_policy_next( policy, dedup, forest, repair, now+(long)turn+10L*(long)(i+1UL), 2UL, &charge_busy ) ); - FD_TEST( !charge_busy ); - FD_TEST( skip1->slot==1UL && skip1->idx==5U && skip1->throttled && skip1->until==until1 ); - FD_TEST( skip2->slot==2UL && skip2->idx==3U && skip2->throttled && skip2->until==until2 ); - } - - /* Non-head slot 1 passes its deadline while the head stays - throttled: slot 1 gets its highest-shred request, slot 2 is - re-memoized rather than requested. */ - fd_forest_blk_recv( forest, blk1 )[0].first = 0; - blk1->first_shred_ts = 0L; - long t1 = fd_long_max( until1, until2 )+1L; - fd_repair_msg_t const * msg = next_msg( policy, dedup, forest, repair, t1, 2UL ); - FD_TEST( msg && msg->kind==FD_REPAIR_KIND_HIGHEST_SHRED && msg->highest_shred.slot==1UL ); - /* On its next visit the probe is still rate limited, so slot 1 asks - for its tail shred (1,5) directly; slot 2 stays throttled. */ - msg = next_msg( policy, dedup, forest, repair, t1+10L, 2UL ); - FD_TEST( msg && msg->kind==FD_REPAIR_KIND_SHRED && msg->shred.slot==1UL && msg->shred.shred_idx==5UL ); - FD_TEST( skip2->slot==2UL && skip2->idx==3U && skip2->throttled && skip2->until>t1 ); -} - -/* A pop whose reqlim key is still in its dedup window reschedules the - head without sending. */ -static void -test_orphan_dedup_reschedule( fd_wksp_t * wksp ) { - ulong const slot_max = 16UL; - void * forest_mem = fd_wksp_alloc_laddr( wksp, fd_forest_align(), fd_forest_footprint( slot_max, FD_SHRED_BLK_MAX ), 1UL ); - void * dedup_mem = fd_wksp_alloc_laddr( wksp, fd_reqlim_align(), fd_reqlim_footprint( slot_max ), 1UL ); - void * repair_mem = fd_wksp_alloc_laddr( wksp, fd_repair_align(), fd_repair_footprint(), 1UL ); - FD_TEST( forest_mem && dedup_mem && repair_mem ); - - fd_pubkey_t identity = {0}; - fd_forest_t * forest = fd_forest_join( fd_forest_new( forest_mem, slot_max, FD_SHRED_BLK_MAX, 0UL ) ); - fd_reqlim_t * dedup = fd_reqlim_join( fd_reqlim_new( dedup_mem, slot_max, 0UL ) ); - fd_repair_t * repair = fd_repair_join( fd_repair_new( repair_mem, &identity ) ); - fd_policy_t * policy = new_policy( wksp ); - FD_TEST( forest && dedup && repair && policy ); - - fd_forest_init( forest, 0UL ); - - fd_pubkey_t peer = { .ul = { 1UL } }; - fd_ip4_port_t addr = { .addr = 1U, .port = 1U }; - FD_TEST( fd_policy_peer_upsert( policy, &peer, &addr ) ); - - FD_TEST( fd_forest_blk_insert( forest, 102UL, 100UL, NULL ) ); - - long now = 1000000000L; - FD_TEST( !fd_reqlim_next( dedup, fd_reqlim_key( FD_REPAIR_KIND_ORPHAN, 102UL, UINT_MAX ), now ) ); - - int charge_busy; - fd_repair_msg_t const * msg = fd_policy_next( policy, dedup, forest, repair, now+1L, ULONG_MAX, &charge_busy ); - FD_TEST( !msg || msg->kind!=FD_REPAIR_KIND_ORPHAN ); - FD_TEST( !fd_forest_verify( forest ) ); - - msg = fd_policy_next( policy, dedup, forest, repair, now+2L, ULONG_MAX, &charge_busy ); - FD_TEST( !msg || msg->kind!=FD_REPAIR_KIND_ORPHAN ); - - msg = fd_policy_next( policy, dedup, forest, repair, now+FD_REQLIM_DEDUP_TIMEOUT, ULONG_MAX, &charge_busy ); - FD_TEST( msg && msg->kind==FD_REPAIR_KIND_ORPHAN && msg->orphan.slot==102UL ); - FD_TEST( !fd_forest_verify( forest ) ); -} - -/* Fill the orphanq to capacity via head evictions so the reclaim scan - runs, then re-create an evicted head and check it is served exactly - once per dedup window. */ -static void -test_orphan_reclaim_and_rehead( fd_wksp_t * wksp ) { - ulong const slot_max = 4UL; - void * forest_mem = fd_wksp_alloc_laddr( wksp, fd_forest_align(), fd_forest_footprint( slot_max, FD_SHRED_BLK_MAX ), 1UL ); - void * dedup_mem = fd_wksp_alloc_laddr( wksp, fd_reqlim_align(), fd_reqlim_footprint( 64UL ), 1UL ); - void * repair_mem = fd_wksp_alloc_laddr( wksp, fd_repair_align(), fd_repair_footprint(), 1UL ); - FD_TEST( forest_mem && dedup_mem && repair_mem ); - - fd_pubkey_t identity = {0}; - fd_forest_t * forest = fd_forest_join( fd_forest_new( forest_mem, slot_max, FD_SHRED_BLK_MAX, 0UL ) ); - fd_reqlim_t * dedup = fd_reqlim_join( fd_reqlim_new( dedup_mem, 64UL, 0UL ) ); - fd_repair_t * repair = fd_repair_join( fd_repair_new( repair_mem, &identity ) ); - fd_policy_t * policy = new_policy( wksp ); - FD_TEST( forest && dedup && repair && policy ); - - fd_forest_init( forest, 100UL ); - - fd_pubkey_t peer = { .ul = { 1UL } }; - fd_ip4_port_t addr = { .addr = 1U, .port = 1U }; - FD_TEST( fd_policy_peer_upsert( policy, &peer, &addr ) ); - - fd_forest_orphan_ent_t * orphanq = fd_forest_orphanq( forest ); - ulong max = fd_forest_orphanq_max( orphanq ); - - /* Each insert past pool capacity evicts a head and pushes one more - entry; nothing pops, so the queue walks up to max and the next - insert exercises the reclaim scan. */ - ulong slot = 102UL; - while( fd_forest_orphanq_cnt( orphanq )kind==FD_REPAIR_KIND_ORPHAN ) ) { - for( ulong j=0UL; jorphan.slot ); /* once per window */ - served[ served_cnt++ ] = msg->orphan.slot; - } - } - ulong served_116 = 0UL; - for( ulong j=0UL; jkind==FD_REPAIR_KIND_ORPHAN && msg->orphan.slot==102UL+2UL*i ); - } - - /* Evict requested head 106 (highest), then re-create it in-window. */ - FD_TEST( fd_forest_blk_insert( forest, 108UL, 107UL, NULL ) ); - FD_TEST( fd_forest_blk_insert( forest, 106UL, 105UL, NULL ) ); - FD_TEST( !fd_forest_verify( forest ) ); - - /* 106's fresh entry pops deduped and reschedules to the same due as - its dead entry: the dead one must not come back live. */ - fd_repair_msg_t const * msg = fd_policy_next( policy, dedup, forest, repair, now+3L, ULONG_MAX, &charge_busy ); - FD_TEST( !msg || msg->kind!=FD_REPAIR_KIND_ORPHAN ); - FD_TEST( !fd_forest_verify( forest ) ); - - /* Next window: every head exactly once. */ - ulong served[ 8UL ]; ulong served_cnt = 0UL; - for( ulong i=0UL; i<8UL; i++ ) { - msg = fd_policy_next( policy, dedup, forest, repair, now+3L+FD_REQLIM_DEDUP_TIMEOUT+(long)i, ULONG_MAX, &charge_busy ); - FD_TEST( !fd_forest_verify( forest ) ); - if( FD_LIKELY( msg && msg->kind==FD_REPAIR_KIND_ORPHAN ) ) { - for( ulong j=0UL; jorphan.slot ); - served[ served_cnt++ ] = msg->orphan.slot; - } - } - ulong served_106 = 0UL; - for( ulong j=0UL; jpeers.fast, policy->peers.pool ) == 4 ); - FD_TEST( dlist_cnt( policy->peers.slow, policy->peers.pool ) == 4 ); - FD_TEST( fd_policy_peer_pool_used( policy->peers.pool ) == 8 ); - - /* Remove one fast, one slow. */ - fd_pubkey_t k_fast = { .key = { 2 } }; - fd_pubkey_t k_slow = { .key = { 6 } }; - FD_TEST( fd_policy_peer_remove( policy, &k_fast ) ); - FD_TEST( fd_policy_peer_remove( policy, &k_slow ) ); - - FD_TEST( dlist_cnt( policy->peers.fast, policy->peers.pool ) == 3 ); - FD_TEST( dlist_cnt( policy->peers.slow, policy->peers.pool ) == 3 ); - FD_TEST( fd_policy_peer_pool_used( policy->peers.pool ) == 6 ); - FD_TEST( !dlist_contains( policy->peers.fast, policy->peers.pool, &k_fast ) ); - FD_TEST( !dlist_contains( policy->peers.slow, policy->peers.pool, &k_slow ) ); - - /* Query must return NULL for removed peers. */ - FD_TEST( !fd_policy_peer_query( policy, &k_fast ) ); - FD_TEST( !fd_policy_peer_query( policy, &k_slow ) ); - - /* Remaining peers must still be queryable. */ - for( int i = 1; i <= 8; i++ ) { - if( i == 2 || i == 6 ) continue; - fd_pubkey_t key = { .key = { (uchar)i } }; - FD_TEST( fd_policy_peer_query( policy, &key ) ); - } - - /* Double-remove must return 0 (no-op). */ - FD_TEST( !fd_policy_peer_remove( policy, &k_fast ) ); -} - -/* Verify that response_update on a removed/unknown peer is a no-op and - does not corrupt dlist state. */ -void -test_response_update_unknown_peer( fd_wksp_t * wksp ) { - fd_policy_t * policy = new_policy( wksp ); - FD_TEST( policy ); - - fd_pubkey_t k1 = { .key = { 1 } }; - fd_ip4_port_t addr = { 0 }; - fd_policy_peer_upsert( policy, &k1, &addr ); - - ulong fast_before = dlist_cnt( policy->peers.fast, policy->peers.pool ); - - /* Update a peer that was never inserted. */ - fd_pubkey_t k_unknown = { .key = { 99 } }; - fd_policy_peer_response_update( policy, &k_unknown, (long)(10e6L) ); - - /* Remove k1 then update it — should be a no-op. */ - fd_policy_peer_remove( policy, &k1 ); - fd_policy_peer_response_update( policy, &k1, (long)(10e6L) ); - - FD_TEST( dlist_cnt( policy->peers.fast, policy->peers.pool ) == fast_before ); - FD_TEST( dlist_cnt( policy->peers.slow, policy->peers.pool ) == 0 ); - FD_TEST( fd_policy_peer_pool_used( policy->peers.pool ) == 0 ); -} - -/* Verify the null pubkey guard returns NULL and does not crash. */ -void -test_null_pubkey_query( fd_wksp_t * wksp ) { - fd_policy_t * policy = new_policy( wksp ); - FD_TEST( policy ); - - fd_pubkey_t null_key; - fd_memset( &null_key, 0, sizeof(fd_pubkey_t) ); - FD_TEST( !fd_policy_peer_query( policy, &null_key ) ); - - /* Insert a real peer and verify null query still returns NULL. */ - fd_pubkey_t k1 = { .key = { 1 } }; - fd_ip4_port_t addr = { 0 }; - fd_policy_peer_upsert( policy, &k1, &addr ); - FD_TEST( !fd_policy_peer_query( policy, &null_key ) ); - FD_TEST( fd_policy_peer_query( policy, &k1 ) ); - - /* request_update and response_update with null pubkey must be no-ops. */ - ulong used_before = fd_policy_peer_pool_used( policy->peers.pool ); - fd_policy_peer_request_update( policy, &null_key ); - fd_policy_peer_response_update( policy, &null_key, (long)(10e6L) ); - FD_TEST( fd_policy_peer_pool_used( policy->peers.pool ) == used_before ); -} - -/* Verify dlist integrity across slow->fast and fast->slow bucket - transitions. Each peer must be in exactly one dlist at all times. */ -void -test_bucket_transition_dlist_integrity( fd_wksp_t * wksp ) { - fd_policy_t * policy = new_policy( wksp ); - FD_TEST( policy ); - - fd_pubkey_t k1 = { .key = { 1 } }; - fd_ip4_port_t addr = { 0 }; - fd_policy_peer_upsert( policy, &k1, &addr ); - - /* Starts in slow (no responses yet). */ - FD_TEST( dlist_contains( policy->peers.slow, policy->peers.pool, &k1 ) ); - FD_TEST( !dlist_contains( policy->peers.fast, policy->peers.pool, &k1 ) ); - FD_TEST( dlist_cnt( policy->peers.slow, policy->peers.pool ) == 1 ); - FD_TEST( dlist_cnt( policy->peers.fast, policy->peers.pool ) == 0 ); - - /* Fast response → should move to fast. */ - fd_policy_peer_response_update( policy, &k1, (long)(10e6L) ); - FD_TEST( !dlist_contains( policy->peers.slow, policy->peers.pool, &k1 ) ); - FD_TEST( dlist_contains( policy->peers.fast, policy->peers.pool, &k1 ) ); - FD_TEST( dlist_cnt( policy->peers.slow, policy->peers.pool ) == 0 ); - FD_TEST( dlist_cnt( policy->peers.fast, policy->peers.pool ) == 1 ); - - /* Sustained high latency → should move back to slow. */ - for( int i = 0; i < 50; i++ ) { - fd_policy_peer_response_update( policy, &k1, (long)(300e6L) ); - } - fd_policy_peer_t * p = fd_policy_peer_query( policy, &k1 ); - FD_TEST( p && p->ewma_lat > (long)FD_POLICY_LATENCY_THRESH ); - FD_TEST( dlist_contains( policy->peers.slow, policy->peers.pool, &k1 ) ); - FD_TEST( !dlist_contains( policy->peers.fast, policy->peers.pool, &k1 ) ); - FD_TEST( dlist_cnt( policy->peers.slow, policy->peers.pool ) == 1 ); - FD_TEST( dlist_cnt( policy->peers.fast, policy->peers.pool ) == 0 ); - - /* Sustained fast again → back to fast. */ - for( int i = 0; i < 50; i++ ) { - fd_policy_peer_response_update( policy, &k1, (long)(5e6L) ); - } - p = fd_policy_peer_query( policy, &k1 ); - FD_TEST( p && p->ewma_lat < (long)FD_POLICY_LATENCY_THRESH ); - FD_TEST( !dlist_contains( policy->peers.slow, policy->peers.pool, &k1 ) ); - FD_TEST( dlist_contains( policy->peers.fast, policy->peers.pool, &k1 ) ); - - /* Total across both dlists must always equal pool used. */ - FD_TEST( dlist_cnt( policy->peers.fast, policy->peers.pool ) - + dlist_cnt( policy->peers.slow, policy->peers.pool ) - == fd_policy_peer_pool_used( policy->peers.pool ) ); -} - -/* Verify that removing the peer the select iterator is parked on does - not break subsequent selects from the same or opposite dlist. */ -void -test_remove_iter_peer_dlist_intact( fd_wksp_t * wksp ) { - fd_policy_t * policy = new_policy( wksp ); - FD_TEST( policy ); - - /* 3 fast, 3 slow. */ - for( int i = 1; i <= 6; i++ ) { - fd_pubkey_t key = { .key = { (uchar)i } }; - fd_ip4_port_t addr = { 0 }; - fd_policy_peer_upsert( policy, &key, &addr ); - if( i <= 3 ) fd_policy_peer_response_update( policy, &key, (long)(10e6L) ); - } - - /* Park fast_iter on first fast peer via select. */ - fd_pubkey_t const * sel = fd_policy_peer_select( policy ); - FD_TEST( sel ); - fd_pubkey_t parked = *sel; - - /* Remove that peer. */ - FD_TEST( fd_policy_peer_remove( policy, &parked ) ); - FD_TEST( !dlist_contains( policy->peers.fast, policy->peers.pool, &parked ) ); - FD_TEST( dlist_cnt( policy->peers.fast, policy->peers.pool ) == 2 ); - - /* Subsequent selects must not return the removed peer and must not crash. */ - for( int i = 0; i < 20; i++ ) { - sel = fd_policy_peer_select( policy ); - FD_TEST( sel ); - FD_TEST( memcmp( sel->key, parked.key, 32UL ) != 0 ); - } - - /* Dlist totals must match pool. */ - FD_TEST( dlist_cnt( policy->peers.fast, policy->peers.pool ) - + dlist_cnt( policy->peers.slow, policy->peers.pool ) - == fd_policy_peer_pool_used( policy->peers.pool ) ); -} - -/* Verify dlist integrity after a peer is evicted by - fd_policy_peer_request_update (unanswered threshold). */ -void -test_eviction_dlist_integrity( fd_wksp_t * wksp ) { - fd_policy_t * policy = new_policy( wksp ); - FD_TEST( policy ); - - /* Insert a fast peer and a slow peer. */ - fd_pubkey_t k_fast = { .key = { 1 } }; - fd_pubkey_t k_slow = { .key = { 2 } }; - fd_ip4_port_t addr = { 0 }; - fd_policy_peer_upsert( policy, &k_fast, &addr ); - fd_policy_peer_response_update( policy, &k_fast, (long)(10e6L) ); - fd_policy_peer_upsert( policy, &k_slow, &addr ); - - FD_TEST( dlist_cnt( policy->peers.fast, policy->peers.pool ) == 1 ); - FD_TEST( dlist_cnt( policy->peers.slow, policy->peers.pool ) == 1 ); - - /* Slow peer must be unaffected. */ - FD_TEST( fd_policy_peer_query( policy, &k_slow ) ); - FD_TEST( dlist_contains( policy->peers.slow, policy->peers.pool, &k_slow ) ); - - /* Pool used must match dlist totals. */ - FD_TEST( dlist_cnt( policy->peers.fast, policy->peers.pool ) - + dlist_cnt( policy->peers.slow, policy->peers.pool ) - == fd_policy_peer_pool_used( policy->peers.pool ) ); -} - -/* Verify that reinserting a previously removed peer places it into the - correct dlist and that the old state does not leak. */ -void -test_remove_and_reinsert( fd_wksp_t * wksp ) { - fd_policy_t * policy = new_policy( wksp ); - FD_TEST( policy ); - - fd_pubkey_t k1 = { .key = { 1 } }; - fd_ip4_port_t addr = { .addr = 0x01020304, .port = 8000 }; - fd_policy_peer_upsert( policy, &k1, &addr ); - fd_policy_peer_response_update( policy, &k1, (long)(10e6L) ); - FD_TEST( dlist_contains( policy->peers.fast, policy->peers.pool, &k1 ) ); - - /* Remove it. */ - FD_TEST( fd_policy_peer_remove( policy, &k1 ) ); - FD_TEST( fd_policy_peer_pool_used( policy->peers.pool ) == 0 ); - - /* Reinsert — should go to slow (fresh peer, no responses). */ - fd_policy_peer_upsert( policy, &k1, &addr ); - FD_TEST( fd_policy_peer_pool_used( policy->peers.pool ) == 1 ); - FD_TEST( dlist_contains( policy->peers.slow, policy->peers.pool, &k1 ) ); - FD_TEST( !dlist_contains( policy->peers.fast, policy->peers.pool, &k1 ) ); - - /* Counters should be reset. */ - fd_policy_peer_t * p = fd_policy_peer_query( policy, &k1 ); - FD_TEST( p ); - FD_TEST( p->req_cnt == 0 ); - FD_TEST( p->res_cnt == 0 ); - FD_TEST( p->ewma_lat == 0 ); - FD_TEST( p->unanswered == 0 ); - - /* Move to fast again and verify. */ - fd_policy_peer_response_update( policy, &k1, (long)(10e6L) ); - FD_TEST( dlist_contains( policy->peers.fast, policy->peers.pool, &k1 ) ); - FD_TEST( !dlist_contains( policy->peers.slow, policy->peers.pool, &k1 ) ); -} - -/* Verify that multiple peers can transition buckets independently - without corrupting each other's dlist membership. */ -void -test_multi_peer_bucket_transitions( fd_wksp_t * wksp ) { - fd_policy_t * policy = new_policy( wksp ); - FD_TEST( policy ); - - fd_ip4_port_t addr = { 0 }; - - /* Insert 5 peers, all start slow. */ - for( int i = 1; i <= 5; i++ ) { - fd_pubkey_t key = { .key = { (uchar)i } }; - fd_policy_peer_upsert( policy, &key, &addr ); - } - FD_TEST( dlist_cnt( policy->peers.slow, policy->peers.pool ) == 5 ); - FD_TEST( dlist_cnt( policy->peers.fast, policy->peers.pool ) == 0 ); - - /* Move peers 1, 3, 5 to fast. */ - for( int i = 1; i <= 5; i += 2 ) { - fd_pubkey_t key = { .key = { (uchar)i } }; - fd_policy_peer_response_update( policy, &key, (long)(10e6L) ); - } - FD_TEST( dlist_cnt( policy->peers.fast, policy->peers.pool ) == 3 ); - FD_TEST( dlist_cnt( policy->peers.slow, policy->peers.pool ) == 2 ); - - /* Remove peer 3 (fast) and peer 2 (slow). */ - fd_pubkey_t k3 = { .key = { 3 } }; - fd_pubkey_t k2 = { .key = { 2 } }; - fd_policy_peer_remove( policy, &k3 ); - fd_policy_peer_remove( policy, &k2 ); - FD_TEST( dlist_cnt( policy->peers.fast, policy->peers.pool ) == 2 ); - FD_TEST( dlist_cnt( policy->peers.slow, policy->peers.pool ) == 1 ); - - /* Move peer 1 from fast to slow with high latency. */ - fd_pubkey_t k1 = { .key = { 1 } }; - for( int i = 0; i < 50; i++ ) { - fd_policy_peer_response_update( policy, &k1, (long)(300e6L) ); - } - FD_TEST( dlist_contains( policy->peers.slow, policy->peers.pool, &k1 ) ); - FD_TEST( !dlist_contains( policy->peers.fast, policy->peers.pool, &k1 ) ); - FD_TEST( dlist_cnt( policy->peers.fast, policy->peers.pool ) == 1 ); /* only peer 5 */ - FD_TEST( dlist_cnt( policy->peers.slow, policy->peers.pool ) == 2 ); /* peers 1, 4 */ - - /* Invariant: dlist total == pool used. */ - FD_TEST( dlist_cnt( policy->peers.fast, policy->peers.pool ) - + dlist_cnt( policy->peers.slow, policy->peers.pool ) - == fd_policy_peer_pool_used( policy->peers.pool ) ); -} - -static uchar metrics_scratch[ FD_METRICS_FOOTPRINT( 0 ) ] __attribute__((aligned(FD_METRICS_ALIGN))); - -int -main( int argc, char ** argv ) { - fd_boot( &argc, &argv ); - fd_metrics_register( (ulong *)fd_metrics_new( metrics_scratch, 0UL ) ); - - char const * _page_sz = fd_env_strip_cmdline_cstr ( &argc, &argv, "--page-sz", NULL, "gigantic" ); - ulong page_cnt = fd_env_strip_cmdline_ulong( &argc, &argv, "--page-cnt", NULL, 1UL ); - ulong numa_idx = fd_env_strip_cmdline_ulong( &argc, &argv, "--numa-idx", NULL, fd_shmem_numa_idx( 0UL ) ); - fd_wksp_t * wksp = fd_wksp_new_anonymous( fd_cstr_to_shmem_page_sz( _page_sz ), page_cnt, fd_shmem_cpu_idx( numa_idx ), "wksp", 0UL ); - FD_TEST( wksp ); - - test_peer_removal( wksp ); - test_peer_interleave( wksp ); - test_ewma_latency( wksp ); - test_remove_sole_peer( wksp ); - test_orphan_due_prq( wksp ); - test_shred_skip_memo( wksp ); - test_skip_memo_non_head_throttle( wksp ); - test_non_head_tail_request( wksp ); - test_orphan_dedup_reschedule( wksp ); - test_orphan_reclaim_and_rehead( wksp ); - test_orphan_rehead_revival( wksp ); - test_dlist_consistency_after_remove( wksp ); - test_response_update_unknown_peer( wksp ); - test_null_pubkey_query( wksp ); - test_bucket_transition_dlist_integrity( wksp ); - test_remove_iter_peer_dlist_intact( wksp ); - test_eviction_dlist_integrity( wksp ); - test_remove_and_reinsert( wksp ); - test_multi_peer_bucket_transitions( wksp ); - - FD_LOG_NOTICE(( "pass" )); - fd_halt(); -} diff --git a/src/discof/repair/test_repair_tile.c b/src/discof/repair/test_repair_tile.c deleted file mode 100644 index 2271220d5f8..00000000000 --- a/src/discof/repair/test_repair_tile.c +++ /dev/null @@ -1,1004 +0,0 @@ -/* Mock fd_stem_publish to a no-op so we can test the sign lifecycle - without real tile infrastructure. - - Include fd_stem.h (via fd_topo.h) first so the static - inline declaration parses normally. Then override with a macro - before including fd_repair_tile.c so all call sites expand to our - mock instead. */ - -#include "../../disco/topo/fd_topo.h" /* pulls in fd_stem.h */ -#include "../../disco/shred/fd_shred_tile.h" - -static ulong mock_stem_publish_cnt; -#undef fd_stem_publish /* no prior macro, but harmless */ -#define fd_stem_publish( stem, out_idx, sig, chunk, sz, ctl, tsorig, tspub ) \ - do { (void)(stem); (void)(out_idx); (void)(sig); (void)(chunk); (void)(sz); \ - (void)(ctl); (void)(tsorig); (void)(tspub); mock_stem_publish_cnt++; } while(0) - -#include "fd_repair_tile.c" - -static ulong dedup_max = 64; -static ulong peer_max = 64; -static int lg_sign_depth = 6; - -static void -setup_ctx( ctx_t * ctx, fd_wksp_t * wksp, ulong slot_max ) { - memset( ctx, 0, sizeof(*ctx) ); - - FD_TEST( fd_rng_secure( ctx->repair_nonce_ss, sizeof(fd_rnonce_ss_t) ) ); - - void * forest_mem = fd_wksp_alloc_laddr( wksp, fd_forest_align(), fd_forest_footprint( slot_max, FD_SHRED_BLK_MAX ), 1UL ); - void * policy_mem = fd_wksp_alloc_laddr( wksp, fd_policy_align(), fd_policy_footprint( peer_max ), 1UL ); - void * dedup_mem = fd_wksp_alloc_laddr( wksp, fd_reqlim_align(), fd_reqlim_footprint( dedup_max ), 1UL ); - void * inflights_mem = fd_wksp_alloc_laddr( wksp, fd_inflights_align(), fd_inflights_footprint(), 1UL ); - void * signs_map_mem = fd_wksp_alloc_laddr( wksp, fd_signs_map_align(), fd_signs_map_footprint( lg_sign_depth ), 1UL ); - void * pong_queue_mem = fd_wksp_alloc_laddr( wksp, fd_signs_queue_align(), fd_signs_queue_footprint(), 1UL ); - void * repair_mem = fd_wksp_alloc_laddr( wksp, fd_repair_align(), fd_repair_footprint(), 1UL ); - void * metrics_mem = fd_wksp_alloc_laddr( wksp, fd_repair_metrics_align(), fd_repair_metrics_footprint(), 1UL ); - - ctx->forest = fd_forest_join ( fd_forest_new ( forest_mem, slot_max, FD_SHRED_BLK_MAX, 0UL ) ); - ctx->policy = fd_policy_join ( fd_policy_new ( policy_mem, peer_max, 0UL, ctx->repair_nonce_ss ) ); - ctx->dedup = fd_reqlim_join ( fd_reqlim_new ( dedup_mem, dedup_max, 0UL ) ); - ctx->inflights = fd_inflights_join ( fd_inflights_new ( inflights_mem, 0UL ) ); - ctx->signs_map = fd_signs_map_join ( fd_signs_map_new ( signs_map_mem, lg_sign_depth, 0UL ) ); - ctx->pong_queue = fd_signs_queue_join ( fd_signs_queue_new ( pong_queue_mem ) ); - ctx->protocol = fd_repair_join ( fd_repair_new ( repair_mem, &ctx->identity_public_key ) ); - ctx->slot_metrics = fd_repair_metrics_join( fd_repair_metrics_new( metrics_mem ) ); - - /* Set up output link contexts with workspace-allocated buffers so - send_packet and fd_repair_send_sign_request can write into them. - fd_chunk_to_laddr(mem, chunk) = (void*)((ulong)mem + (chunk<<6)). - We set mem to a wksp-allocated buffer and chunk0=0 so - fd_chunk_to_laddr(mem, 0) == mem. */ - - ulong dcache_sz = 4096UL; /* plenty for test packets */ - - void * net_dcache = fd_wksp_alloc_laddr( wksp, FD_CHUNK_ALIGN, dcache_sz, 1UL ); - void * sign_dcache = fd_wksp_alloc_laddr( wksp, FD_CHUNK_ALIGN, dcache_sz, 1UL ); - FD_TEST( net_dcache && sign_dcache ); - - ctx->net_out_ctx->idx = 0; - ctx->net_out_ctx->mem = net_dcache; - ctx->net_out_ctx->chunk0 = 0; - ctx->net_out_ctx->wmark = (dcache_sz >> FD_CHUNK_LG_SZ) - 1; - ctx->net_out_ctx->chunk = 0; - - /* One mock sign tile. */ - - ctx->repair_sign_cnt = 1; - ctx->repair_sign_out_ctx[0].idx = 1; - ctx->repair_sign_out_ctx[0].in_idx = 42; /* arbitrary, matches what we pass to after_sign */ - ctx->repair_sign_out_ctx[0].mem = sign_dcache; - ctx->repair_sign_out_ctx[0].chunk0 = 0; - ctx->repair_sign_out_ctx[0].wmark = (dcache_sz >> FD_CHUNK_LG_SZ) - 1; - ctx->repair_sign_out_ctx[0].chunk = 0; - ctx->repair_sign_out_ctx[0].max_credits = 128; - ctx->repair_sign_out_ctx[0].credits = 128; - - /* Initialize header templates (send_packet dereferences these). */ - - fd_ip4_udp_hdr_init( ctx->intake_hdr, 0, 0, 1234 ); -} - -/* Helper to build a raw network packet containing a repair ping in - ctx->net_buf. Returns the total packet size. */ - -static ulong -mock_ping_packet( ctx_t * ctx, - fd_pubkey_t const * from, - uchar const * private_key, /* if non-NULL, sign hash with this key (from must be the corresponding pubkey) */ - uint src_ip, - ushort src_port ) { - - ulong payload_sz = sizeof(fd_repair_ping_t); - - /* Build Eth+IP4+UDP headers. */ - - fd_ip4_udp_hdrs_t hdrs[1]; - fd_ip4_udp_hdr_init( hdrs, payload_sz, src_ip, src_port ); - - /* Build the ping payload. */ - - fd_repair_ping_t ping[1]; - memset( ping, 0, sizeof(*ping) ); - ping->kind = FD_REPAIR_KIND_PING; - ping->ping.from = *from; - /* hash is zeroed — arbitrary 32-byte token */ - - if( private_key ) { - fd_sha512_t sha[1]; - fd_ed25519_sign( ping->ping.sig, ping->ping.hash.uc, 32UL, from->uc, private_key, sha ); - } - /* otherwise sig is zeroed — will fail sigverify if reached */ - - uchar ping_buf[ sizeof(fd_repair_ping_t) ]; - FD_TEST( 0==fd_repair_ping_ser( ping, ping_buf, sizeof(ping_buf) ) ); - - /* Assemble into ctx->net_buf. */ - - ulong hdr_sz = sizeof(fd_ip4_udp_hdrs_t); - ulong total = hdr_sz + payload_sz; - FD_TEST( total <= FD_NET_MTU ); - memcpy( ctx->net_buf, hdrs, hdr_sz ); - memcpy( ctx->net_buf + hdr_sz, ping_buf, payload_sz ); - - return total; -} - -static void -test_after_net( fd_wksp_t * wksp ) { - - /* Allocate a minimal ctx with just the fields after_net touches. */ - - static ctx_t ctx[1]; - setup_ctx( ctx, wksp, 512 ); - - ulong unknown_peer_ping_b4 = ctx->metrics->unknown_peer_ping; - ulong malformed_ping_b4 = ctx->metrics->malformed_ping; - ulong fail_sigverify_ping_b4 = ctx->metrics->fail_sigverify_ping; - - /* Build a valid ping from an unknown peer (not in policy). */ - - { - fd_pubkey_t unknown_peer = { .ul = { 0xDEAD } }; - ulong sz = mock_ping_packet( ctx, &unknown_peer, NULL, 0x01020304U, 1234 ); - - /* Call after_net — should hit the unknown_peer_ping path. */ - after_net( ctx, sz ); - - FD_TEST( ctx->metrics->unknown_peer_ping== ++unknown_peer_ping_b4 ); - FD_TEST( ctx->metrics->malformed_ping== malformed_ping_b4 ); - FD_TEST( ctx->metrics->fail_sigverify_ping== fail_sigverify_ping_b4 ); - - FD_LOG_NOTICE(( "pass: test_after_net_unknown_peer" )); - } - - { - /* Add peer to policy, then call after_net */ - fd_pubkey_t known_peer = { .ul = { 0xBEEF } }; - fd_ip4_port_t known_addr = { .addr = 0x01020304U, .port = 1234 }; - fd_policy_peer_upsert( ctx->policy, &known_peer, &known_addr ); - - ulong sz = mock_ping_packet( ctx, &known_peer, NULL, 0x01020304U, 1234 ); - after_net( ctx, sz ); - - FD_TEST( ctx->metrics->unknown_peer_ping == unknown_peer_ping_b4 ); - FD_TEST( ctx->metrics->malformed_ping == malformed_ping_b4 ); - FD_TEST( ctx->metrics->fail_sigverify_ping==++fail_sigverify_ping_b4 ); - - FD_LOG_NOTICE(( "pass: test_after_net_fail_sigverify" )); - } - - { - /* Generate a valid ed25519 keypair, register the pubkey as a known - peer, then send a ping with a valid signature. after_net should - pass sigverify and enqueue a pong. */ - - uchar private_key[32] = { 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32 }; - fd_pubkey_t pubkey; - fd_sha512_t sha[1]; - fd_ed25519_public_from_private( pubkey.uc, private_key, sha ); - - fd_ip4_port_t addr = { .addr = 0x05060708U, .port = 5678 }; - fd_policy_peer_upsert( ctx->policy, &pubkey, &addr ); - - ulong pong_cnt_before = fd_signs_queue_cnt( ctx->pong_queue ); - - ulong sz = mock_ping_packet( ctx, &pubkey, private_key, 0x05060708U, 5678 ); - after_net( ctx, sz ); - - FD_TEST( ctx->metrics->unknown_peer_ping ==unknown_peer_ping_b4 ); /* unchanged from earlier tests */ - FD_TEST( ctx->metrics->malformed_ping ==malformed_ping_b4 ); - FD_TEST( ctx->metrics->fail_sigverify_ping==fail_sigverify_ping_b4 ); /* unchanged from earlier tests */ - FD_TEST( fd_signs_queue_cnt( ctx->pong_queue )==++pong_cnt_before ); - - /* Verify the peer's ping rate-limit counter was incremented. */ - - fd_policy_peer_t * peer = fd_policy_peer_query( ctx->policy, &pubkey ); - FD_TEST( peer && peer->ping==1 ); - - /* Peer tries to send a ping again */ - - sz = mock_ping_packet( ctx, &pubkey, private_key, 0x05060708U, 5678 ); - after_net( ctx, sz ); - - FD_TEST( ctx->metrics->unknown_peer_ping ==unknown_peer_ping_b4 ); /* unchanged from earlier tests */ - FD_TEST( ctx->metrics->malformed_ping ==malformed_ping_b4 ); - FD_TEST( ctx->metrics->fail_sigverify_ping==fail_sigverify_ping_b4 ); /* unchanged from earlier tests */ - FD_TEST( fd_signs_queue_cnt( ctx->pong_queue )==pong_cnt_before ); - FD_TEST( peer && peer->ping==1 ); /* still has a ping in the queue */ - - FD_LOG_NOTICE(( "pass: test_after_net_valid_ping" )); - } -} -/* test_sign_lifecycle - - Tests the sign request lifecycle: - - 1. Pongs: insert a pong into signs_map via - fd_repair_send_sign_request, simulate the sign response via - after_sign, verify the pending entry is removed, credits are - restored, and peer->ping is decremented. - - 2. Regular shreds: insert a shred request, simulate the - sign response, verify the inflight entry is created and credits - track correctly. - - 3. Peer disappears: insert a shred request, remove the peer before - the sign response arrives, verify the inflight entry is still - created (so the request can be re-sent on timeout). */ - -static void -test_sign_lifecycle( fd_wksp_t * wksp ) { - - static ctx_t ctx[1]; - setup_ctx( ctx, wksp, 512 ); - - ulong sign_in_idx = ctx->repair_sign_out_ctx[0].in_idx; - - /* 1. Pong sign lifecycle */ - { - /* Set up a known peer and simulate a pong needing to be signed. */ - - fd_pubkey_t peer_key = { .ul = { 0xAA } }; - fd_ip4_port_t peer_addr = { .addr = 0x0A0B0C0DU, .port = 4000 }; - fd_policy_peer_upsert( ctx->policy, &peer_key, &peer_addr ); - fd_policy_peer_t * peer = fd_policy_peer_query( ctx->policy, &peer_key ); - FD_TEST( peer ); - peer->ping = 1; /* simulate that a ping was received */ - - /* Construct a pong message */ - - fd_hash_t ping_hash = { .ul = { 0xCC } }; - fd_repair_msg_t * pong = fd_repair_pong( ctx->protocol, &ping_hash ); - - pong_data_t pong_data = { .peer_addr = peer_addr, .hash = ping_hash, - .daddr = 0x01020304U, .key = peer_key }; - - /* Dispatch the sign request. */ - - out_ctx_t * sign_out = sign_avail_credits( ctx ); - FD_TEST( sign_out ); - ulong credits_before = sign_out->credits; - ulong map_cnt_before = fd_signs_map_key_cnt( ctx->signs_map ); - - fd_repair_send_sign_request( ctx, sign_out, pong, &pong_data ); - - FD_TEST( sign_out->credits==credits_before-1 ); - FD_TEST( fd_signs_map_key_cnt( ctx->signs_map )==map_cnt_before+1 ); - - /* The pending key is ctx->pending_key_next - 1 (post-increment in - sign_map_insert). */ - - ulong pending_key = (ulong)(ctx->pending_key_next - 1); - - /* Simulate sign tile returning the signature. The sig field of - after_sign encodes pending_key<<32 | sign_type. */ - - ulong after_sign_sig = pending_key << 32; - memset( ctx->sign_buf, 0xAB, sizeof(ctx->sign_buf) ); /* mock signature */ - - mock_stem_publish_cnt = 0; - after_sign( ctx, sign_in_idx, after_sign_sig, NULL ); - - /* Verify: pending entry removed, credit restored, peer->ping - decremented, packet sent (mock_stem_publish called by - send_packet). */ - - FD_TEST( fd_signs_map_key_cnt( ctx->signs_map )==map_cnt_before ); - FD_TEST( sign_out->credits==credits_before ); - FD_TEST( peer->ping==0 ); - FD_TEST( mock_stem_publish_cnt==1 ); /* send_packet called once */ - FD_TEST( ctx->metrics->send_pkt_cnt>=1 ); - - FD_LOG_NOTICE(( "pass: test_sign_lifecycle_pong" )); - } - - /* 2. Regular shred request lifecycle */ - { - fd_pubkey_t peer_key = { .ul = { 0xBB } }; - fd_ip4_port_t peer_addr = { .addr = 0x0A0B0C0EU, .port = 5000 }; - fd_policy_peer_upsert( ctx->policy, &peer_key, &peer_addr ); - - /* Construct a shred request: slot=100, shred_idx=5, nonce=999. */ - - fd_repair_msg_t * shred_req = fd_repair_shred( ctx->protocol, &peer_key, - (ulong)fd_log_wallclock()/1000000L, - 999U, 100UL, 5UL ); - - out_ctx_t * sign_out = sign_avail_credits( ctx ); - FD_TEST( sign_out ); - ulong credits_before = sign_out->credits; - ulong map_cnt_before = fd_signs_map_key_cnt( ctx->signs_map ); - - fd_repair_send_sign_request( ctx, sign_out, shred_req, NULL ); - - FD_TEST( sign_out->credits==credits_before-1 ); - FD_TEST( fd_signs_map_key_cnt( ctx->signs_map )==map_cnt_before+1 ); - - ulong pending_key = (ulong)(ctx->pending_key_next - 1); - ulong after_sign_sig = pending_key << 32; - memset( ctx->sign_buf, 0xCD, sizeof(ctx->sign_buf) ); - - mock_stem_publish_cnt = 0; - after_sign( ctx, sign_in_idx, after_sign_sig, NULL ); - - /* Verify: pending removed, credit restored, inflight created, - packet sent. */ - - FD_TEST( fd_signs_map_key_cnt( ctx->signs_map )==map_cnt_before ); - FD_TEST( sign_out->credits==credits_before ); - FD_TEST( mock_stem_publish_cnt==1 ); - - FD_LOG_NOTICE(( "pass: test_sign_lifecycle_shred" )); - } - - /* 3. Peer-gone shred request */ - { - fd_pubkey_t peer_key = { .ul = { 0xDD } }; - fd_ip4_port_t peer_addr = { .addr = 0x0A0B0C0FU, .port = 6000 }; - fd_policy_peer_upsert( ctx->policy, &peer_key, &peer_addr ); - - fd_repair_msg_t * shred_req = fd_repair_shred( ctx->protocol, &peer_key, - (ulong)fd_log_wallclock()/1000000L, - 777U, 200UL, 3UL ); - - out_ctx_t * sign_out = sign_avail_credits( ctx ); - FD_TEST( sign_out ); - ulong credits_before = sign_out->credits; - - fd_repair_send_sign_request( ctx, sign_out, shred_req, NULL ); - - /* Remove the peer before the sign response arrives. */ - - fd_policy_peer_remove( ctx->policy, &peer_key ); - FD_TEST( !fd_policy_peer_query( ctx->policy, &peer_key ) ); - - ulong pending_key = (ulong)(ctx->pending_key_next - 1); - ulong after_sign_sig = pending_key << 32; - memset( ctx->sign_buf, 0xEF, sizeof(ctx->sign_buf) ); - - mock_stem_publish_cnt = 0; - after_sign( ctx, sign_in_idx, after_sign_sig, NULL ); - - /* Verify: pending removed, credit restored, but NO packet sent - (peer is gone). The inflight is still inserted so the request - will be retried on timeout. */ - - FD_TEST( sign_out->credits==credits_before ); - FD_TEST( mock_stem_publish_cnt==0 ); /* no send_packet */ - - FD_LOG_NOTICE(( "pass: test_sign_lifecycle_peer_gone" )); - } -} - -#define PCAP_PKT_MAX (1<<19) /* max packets per file */ - -FD_IMPORT_BINARY( shred_messages_min, "src/discof/repair/fixtures/shred_messages_min.bin" ); -FD_IMPORT_BINARY( future_shred_messages_min, "src/discof/repair/fixtures/future_shred_messages_min.bin" ); - -/* Minimized shred records (96 bytes for shreds, 92 for fec_complete). - so that we don't need to write the full shred payload to file - - Layout: - sig u64 (8) - merkle_root [32] - slot u64 (8) - idx u32 (4) - fec_set_idx u32 (4) - variant u8 (1) - flags u8 (1) - parent_off u16 (2) - rnonce u32 (4) <-- shreds only (src 0-4) - chained_merkle_root [32] -*/ -typedef struct __attribute__((packed)) { - ulong sig; - fd_hash_t merkle_root; - ulong slot; - uint idx; - uint fec_set_idx; - uchar variant; - uchar flags; - ushort parent_off; - uint rnonce; /* only valid for shreds (src 0-4) */ - fd_hash_t chained_merkle_root; -} shred_pkt_t; - -#define MIN_SHRED_SZ (96UL) /* sizeof shred record (with rnonce) */ -#define MIN_FEC_COMPLETE_SZ (92UL) /* sizeof fec_complete record (no rnonce) */ - -/* Parse minimized records from an in-memory buffer into pkts[0..cap). - Iterates by pointer arithmetic over variable-length records. - Returns the number of packets parsed. */ -static ulong -read_shred_bin( uchar const * buf, ulong buf_sz, shred_pkt_t * pkts, ulong cap ) { - uchar const * cur = buf; - uchar const * end = buf + buf_sz; - ulong cnt = 0UL; - - while( cur + sizeof(ulong) <= end && cnt < cap ) { - ulong sig = FD_LOAD( ulong, cur ); - uint src = fd_shred_sig_src( sig ); - int is_fec = ( src==SHRED_SIG_FEC_COMPLETE || src==SHRED_SIG_FEC_COMPLETE_LEADER ); - ulong rec_sz = is_fec ? MIN_FEC_COMPLETE_SZ : MIN_SHRED_SZ; - - if( cur + rec_sz > end ) break; - - memset( &pkts[cnt], 0, sizeof(shred_pkt_t) ); - pkts[cnt].sig = sig; - - /* Copy fields after sig. For fec_complete the rnonce field is - absent in the file, so copy in two parts around it. */ - uchar const * src_ptr = cur + sizeof(ulong); - uchar * dst = (uchar *)&pkts[cnt] + sizeof(ulong); - ulong common_sz = 32+8+4+4+1+1+2; /* merkle_root..parent_off = 52 bytes */ - - fd_memcpy( dst, src_ptr, common_sz ); - if( is_fec ) { - /* No rnonce in file — skip the field (stays 0), copy cmr. */ - fd_memcpy( &pkts[cnt].chained_merkle_root, src_ptr + common_sz, 32 ); - } else { - /* rnonce(4) + chained_merkle_root(32) */ - fd_memcpy( dst + common_sz, src_ptr + common_sz, 4 + 32 ); - } - - cur += rec_sz; - cnt++; - } - - return cnt; -} - -static void -test_future_slots( fd_wksp_t * wksp ) { - /* Tests future slot attacks by sampling from shred_out during a - testnet run, and interleaving them evenly with future shreds. */ - static ctx_t ctx[1]; - setup_ctx( ctx, wksp, 128 ); - - fd_forest_init( ctx->forest, 402053352 ); - - /* wksp allocate for 2^19 packets per file */ - void * reg_pkts_mem = fd_wksp_alloc_laddr( wksp, FD_CHUNK_ALIGN, PCAP_PKT_MAX * sizeof(shred_pkt_t), 1UL ); - void * future_pkts_mem = fd_wksp_alloc_laddr( wksp, FD_CHUNK_ALIGN, PCAP_PKT_MAX * sizeof(shred_pkt_t), 1UL ); - - shred_pkt_t * reg_pkts = (shred_pkt_t *)reg_pkts_mem; - shred_pkt_t * future_pkts = (shred_pkt_t *)future_pkts_mem; - - ulong reg_cnt = read_shred_bin( shred_messages_min, shred_messages_min_sz, reg_pkts, PCAP_PKT_MAX ); - ulong future_cnt = read_shred_bin( future_shred_messages_min, future_shred_messages_min_sz, future_pkts, PCAP_PKT_MAX ); - - fd_rng_t rng[1]; - fd_rng_join( fd_rng_new( rng, 42U, 0UL ) ); - - ulong reg_idx = 0; - ulong future_idx = 0; - - while( reg_idx < reg_cnt || future_idx < future_cnt ) { - int pick; - if ( reg_idx >= reg_cnt ) pick = 0; - else if( future_idx >= future_cnt ) pick = 1; - else pick = (int)(fd_rng_uint( rng ) & 1U); - - shred_pkt_t * pkt = pick ? ®_pkts[ reg_idx++ ] : &future_pkts[ future_idx++ ]; - uint src = fd_shred_sig_src( pkt->sig ); - - /* Reconstruct a minimal fd_shred_t on the stack from the compact fields. */ - fd_shred_t shred_hdr[1]; - memset( shred_hdr, 0, sizeof(fd_shred_t) ); - shred_hdr->variant = pkt->variant; - shred_hdr->slot = pkt->slot; - shred_hdr->idx = pkt->idx; - shred_hdr->fec_set_idx = pkt->fec_set_idx; - shred_hdr->data.parent_off = pkt->parent_off; - shred_hdr->data.flags = pkt->flags; - - if( src == SHRED_SIG_FEC_COMPLETE || src == SHRED_SIG_FEC_COMPLETE_LEADER ) { - after_fec( ctx, shred_hdr, &pkt->merkle_root, &pkt->chained_merkle_root, fd_tickcount() ); - } else { - after_shred( ctx, pkt->sig, shred_hdr, pkt->rnonce, &pkt->merkle_root, &pkt->chained_merkle_root, fd_tickcount() ); - } - } - fd_forest_print( ctx->forest ); - FD_TEST( !fd_forest_verify( ctx->forest ) ); - - fd_rng_delete( fd_rng_leave( rng ) ); - FD_LOG_NOTICE(( "pass: test_future_slots" )); -} - -/* test_after_tower_confirmed_eviction - - Tests the after_tower confirmation path that detects an incorrect FEC - set and evicts it. - - Setup: - root = 0 - slot 2: two FEC sets (0..31, 32..63), both correct - slot 3: two FEC sets (0..31, 32..63) - - FEC 32..63 has a WRONG merkle root (doesn't match the confirmed block_id) - - Then send a FD_TOWER_SIG_SLOT_CONFIRMED for slot 3 with the correct - block_id. after_tower -> check_confirmed -> fec_chain_verify should - detect the mismatch at FEC 32 of slot 3 and call fec_clear on it. - - We verify: - - The bad FEC's shred indices are cleared (buffered_idx regresses) - - complete_idx is reset (since fec 32 was the last FEC) - - The slot is not chain_confirmed */ - -static void -test_after_tower_confirmed_eviction( fd_wksp_t * wksp ) { - - static ctx_t ctx[1]; - setup_ctx( ctx, wksp, 512 ); - - fd_forest_init( ctx->forest, 0 ); - - /* Merkle root / chained merkle root values. - Chain for slot 2: bid confirms mr_2_32 -> cmr=mr_2_0 -> cmr=mr_root - Chain for slot 3: bid confirms mr_3_32 -> cmr=mr_3_0 -> cmr=mr_2_32 - - We'll insert slot 3's FEC 32 with a WRONG merkle root (mr_3_32_bad) - so that when tower confirms with the correct block_id (mr_3_32), - the chain verify fails at slot 3's last FEC. */ - - fd_hash_t mr_root = (fd_hash_t){ .ul = { 100 } }; - fd_hash_t mr_2_0 = (fd_hash_t){ .ul = { 200 } }; - fd_hash_t mr_2_32 = (fd_hash_t){ .ul = { 201 } }; - fd_hash_t mr_3_0 = (fd_hash_t){ .ul = { 300 } }; - fd_hash_t mr_3_32 = (fd_hash_t){ .ul = { 301 } }; /* correct */ - - fd_hash_t mr_3_32_bad = (fd_hash_t){ .ul = { 999 } }; /* wrong version */ - - /* Insert blocks and their FEC sets. - fd_forest_fec_insert( forest, slot, parent, last_shred_idx, fec_set_idx, slot_complete, ref_tick, mr, cmr, rx_tick ) */ - - fd_forest_blk_insert( ctx->forest, 2, 0, NULL ); - fd_forest_blk_insert( ctx->forest, 3, 2, NULL ); - - /* Slot 2: two correct FEC sets */ - fd_forest_fec_insert( ctx->forest, 2, 0, 31, 0, 0, 0, &mr_2_0, &mr_root, fd_tickcount() ); - fd_forest_fec_insert( ctx->forest, 2, 0, 63, 32, 1, 0, &mr_2_32, &mr_2_0, fd_tickcount() ); - - /* Slot 3: FEC 0 is correct, FEC 32 is the WRONG version */ - fd_forest_fec_insert( ctx->forest, 3, 2, 31, 0, 0, 0, &mr_3_0, &mr_2_32, fd_tickcount() ); - fd_forest_fec_insert( ctx->forest, 3, 2, 63, 32, 1, 0, &mr_3_32_bad, &mr_3_0, fd_tickcount() ); - - /* Verify pre-conditions: slot 3 is complete */ - fd_forest_blk_t * blk3 = fd_forest_query( ctx->forest, 3 ); - FD_TEST( blk3 ); - FD_TEST( blk3->complete_idx == 63 ); - FD_TEST( blk3->buffered_idx == 63 ); - FD_TEST( blk3->chain_confirmed == 0 ); - - /* Now send a tower confirmation for slot 3 with the CORRECT block_id. - The confirmed block_id is the merkle root of the last FEC set. */ - - fd_tower_slot_confirmed_t confirmed_msg[1]; - memset( confirmed_msg, 0, sizeof(*confirmed_msg) ); - confirmed_msg->level = FD_TOWER_SLOT_CONFIRMED_DUPLICATE; - confirmed_msg->fwd = 0; - confirmed_msg->slot = 3; - confirmed_msg->block_id = mr_3_32; /* correct block_id */ - - after_tower( ctx, FD_TOWER_SIG_SLOT_CONFIRMED, (uchar *)confirmed_msg ); - - /* After tower: fec_chain_verify should have detected that slot 3's - last FEC (index 32) has mr_3_32_bad != mr_3_32 (the confirmed bid). - It should have called fec_clear on slot 3, fec_set_idx=32. */ - - blk3 = fd_forest_query( ctx->forest, 3 ); - FD_TEST( blk3 ); - - /* complete_idx should be reset because fec_clear clears the last FEC */ - FD_TEST( blk3->complete_idx == UINT_MAX ); - - /* chain_confirmed should still be 0 — we evicted a bad FEC */ - FD_TEST( blk3->chain_confirmed == 0 ); - - /* buffered_idx should regress to 31 (only FEC 0..31 remains) */ - FD_TEST( blk3->buffered_idx == 31 ); - - /* Slot 2 should be unaffected — not yet chain_confirmed because - verification stopped at slot 3 before reaching slot 2. */ - fd_forest_blk_t * blk2 = fd_forest_query( ctx->forest, 2 ); - FD_TEST( blk2 ); - FD_TEST( blk2->chain_confirmed == 0 ); - - /* Now simulate re-receiving the CORRECT version of slot 3's FEC 32. - This time the merkle root matches the confirmed block_id. */ - - fd_hash_t mr_3_0_correct_cmr = mr_2_32; /* cmr of FEC 0 should chain to slot 2's last mr */ - (void)mr_3_0_correct_cmr; - - fd_forest_fec_insert( ctx->forest, 3, 2, 63, 32, 1, 0, &mr_3_32, &mr_3_0, fd_tickcount() ); - - /* Now manually re-trigger check_confirmed (in the real flow, - after_fec would do this when lowest_verified_fec == fec_set_idx/32 + 1). */ - blk3 = fd_forest_query( ctx->forest, 3 ); - FD_TEST( blk3->complete_idx == 63 ); - FD_TEST( blk3->buffered_idx == 63 ); - - /* Re-verify the chain with the correct FEC */ - fd_forest_blk_t * bad = fd_forest_fec_chain_verify( ctx->forest, blk3, &mr_3_32 ); - FD_TEST( !bad ); /* should succeed now */ - - FD_TEST( blk3->chain_confirmed == 1 ); - - /* Slot 2 should also be chain_confirmed now (chain verify walks - backwards through parents). */ - blk2 = fd_forest_query( ctx->forest, 2 ); - FD_TEST( blk2->chain_confirmed == 1 ); - - fd_forest_verify( ctx->forest ); - - FD_LOG_NOTICE(( "pass: test_after_tower_confirmed_eviction" )); -} - - -/* test_after_fec_dup_confirm_larger_slot - - An attacker sends us a non-canonical version of slot 3 that can never - complete contiguously: - - 1. We receive 64 contiguous shreds (FEC 0 and FEC 1, shreds 0-63), - no slot_complete. - 2. The attacker sends the maximum FEC set (fec_set_idx=511*32) - with slot_complete. complete_idx is now at a very high shred - index, but buffered_idx stays at 63 because there's a huge gap. - - Then tower sends a duplicate confirmation with the correct block_id. - fec_chain_verify detects the wrong last FEC and fec_clears it, - resetting complete_idx to UINT_MAX. - - Finally, the canonical FEC 1 (shreds 32-63, slot_complete) arrives - via after_fec. This sets complete_idx=63 and buffered_idx==63, - triggering check_confirmed which chain-verifies the whole slot. */ - -static void -test_after_fec_dup_confirm_larger_slot( fd_wksp_t * wksp ) { - - static ctx_t ctx[1]; - setup_ctx( ctx, wksp, 512 ); - - fd_forest_init( ctx->forest, 0 ); - - fd_hash_t mr_root = (fd_hash_t){ .ul = { 100 } }; - fd_hash_t mr_2_0 = (fd_hash_t){ .ul = { 200 } }; - fd_hash_t mr_2_32 = (fd_hash_t){ .ul = { 201 } }; - fd_hash_t mr_3_0 = (fd_hash_t){ .ul = { 300 } }; - fd_hash_t mr_3_1 = (fd_hash_t){ .ul = { 301 } }; /* correct block_id for slot 3 */ - fd_hash_t mr_3_1_bad = (fd_hash_t){ .ul = { 997 } }; /* attacker's FEC 1 mr */ - - fd_hash_t mr_3_bad = (fd_hash_t){ .ul = { 999 } }; /* attacker's max FEC mr */ - fd_hash_t cmr_3_bad = (fd_hash_t){ .ul = { 998 } }; - - /* Set up ancestry: root=0 -> slot 2 (2 correct FEC sets) */ - fd_forest_blk_insert( ctx->forest, 2, 0, NULL ); - fd_forest_fec_insert( ctx->forest, 2, 0, 31, 0, 0, 0, &mr_2_0, &mr_root, fd_tickcount() ); - fd_forest_fec_insert( ctx->forest, 2, 0, 63, 32, 1, 0, &mr_2_32, &mr_2_0, fd_tickcount() ); - - /* Slot 3: receive FEC 0 and FEC 1 (shreds 0-63), no slot_complete */ - fd_forest_blk_insert( ctx->forest, 3, 2, NULL ); - fd_forest_fec_insert( ctx->forest, 3, 2, 31, 0, 0, 0, &mr_3_0, &mr_2_32, fd_tickcount() ); - fd_forest_fec_insert( ctx->forest, 3, 2, 63, 32, 0, 0, &mr_3_1_bad, &mr_3_0, fd_tickcount() ); - - fd_forest_blk_t * blk3 = fd_forest_query( ctx->forest, 3 ); - FD_TEST( blk3->complete_idx == UINT_MAX ); - FD_TEST( blk3->buffered_idx == 63 ); - - /* Attacker sends the max FEC set with slot_complete. - complete_idx jumps to a very high value, but buffered_idx stays 63 - because there's a gap between shred 63 and the max FEC. */ - uint max_fec_set_idx = (FD_FEC_BLK_MAX - 1) * 32; - uint max_last_shred = max_fec_set_idx + 31; - fd_forest_fec_insert( ctx->forest, 3, 2, max_last_shred, max_fec_set_idx, 1, 0, &mr_3_bad, &cmr_3_bad, fd_tickcount() ); - - blk3 = fd_forest_query( ctx->forest, 3 ); - FD_TEST( blk3->complete_idx == max_last_shred ); - FD_TEST( blk3->buffered_idx == 63 ); /* gap prevents contiguous buffering */ - FD_TEST( blk3->chain_confirmed == 0 ); - - /* Tower sends duplicate confirmation with the correct block_id. - check_confirmed -> fec_chain_verify should fail because the last - FEC (at max index) has mr_3_bad != mr_3_1 (the correct block_id). - fec_clear should remove the bad max FEC. */ - - fd_tower_slot_confirmed_t confirmed_msg[1]; - memset( confirmed_msg, 0, sizeof(*confirmed_msg) ); - confirmed_msg->level = FD_TOWER_SLOT_CONFIRMED_DUPLICATE; - confirmed_msg->fwd = 0; - confirmed_msg->slot = 3; - confirmed_msg->block_id = mr_3_1; - - after_tower( ctx, FD_TOWER_SIG_SLOT_CONFIRMED, (uchar *)confirmed_msg ); - - blk3 = fd_forest_query( ctx->forest, 3 ); - FD_TEST( blk3 ); - - /* fec_clear should have reset complete_idx since it cleared the last FEC */ - FD_TEST( blk3->complete_idx == UINT_MAX ); - FD_TEST( blk3->chain_confirmed == 0 ); - - /* Now the canonical FEC 1 arrives via after_fec with slot_complete. - This is the correct version with complete_idx=63. - after_fec should set complete_idx=63, and since buffered_idx==63 - and lowest_verified_fec was set by fec_chain_verify, it should - trigger check_confirmed and chain-verify the whole slot. */ - - fd_shred_t shred_hdr[1]; - memset( shred_hdr, 0, sizeof(fd_shred_t) ); - shred_hdr->variant = fd_shred_variant( FD_SHRED_TYPE_MERKLE_DATA, 5 ); - shred_hdr->slot = 3; - shred_hdr->idx = 63; - shred_hdr->fec_set_idx = 32; - shred_hdr->data.parent_off = 1; /* parent = slot 2 */ - shred_hdr->data.flags = FD_SHRED_DATA_FLAG_SLOT_COMPLETE; - - after_fec( ctx, shred_hdr, &mr_3_1, &mr_3_0, fd_tickcount() ); - - blk3 = fd_forest_query( ctx->forest, 3 ); - FD_TEST( blk3->complete_idx == 63 ); - FD_TEST( blk3->buffered_idx == 63 ); - FD_TEST( blk3->chain_confirmed == 1 ); - - /* Slot 2 should also be chain_confirmed (chain verify walks parents) */ - fd_forest_blk_t * blk2 = fd_forest_query( ctx->forest, 2 ); - FD_TEST( blk2->chain_confirmed == 1 ); - - FD_TEST( !fd_forest_verify( ctx->forest ) ); - - FD_LOG_NOTICE(( "pass: test_after_fec_dup_confirm_larger_slot" )); -} - - -/* - slot 8 has a duplicate-confirmed block id, then only its - slot-complete shred arrives. This verifies FEC 0 immediately - (lowest_verified_fec=0) but leaves the slot unbuffered and not - chain_confirmed. - - slot 10 is first linked under slot 8. - - a completed duplicate-confirmed FEC for slot 10 later arrives with a - chained merkle root that names a different parent block id. - - At that point chain verification returns slot 8, but there is no - clearable incorrect FEC in slot 8: fd_forest_merkle_last_incorrect_idx - returns UINT_MAX. */ - -static void -test_parent_edge_mismatch_with_verified_fec0( fd_wksp_t * wksp ) { - - static ctx_t ctx[1]; - setup_ctx( ctx, wksp, 512 ); - - fd_forest_init( ctx->forest, 0 ); - - fd_hash_t mr_root = (fd_hash_t){ .ul = { 100 } }; - fd_hash_t mr_8 = (fd_hash_t){ .ul = { 108 } }; - fd_hash_t mr_8_stale = (fd_hash_t){ .ul = { 808 } }; - fd_hash_t mr_9 = (fd_hash_t){ .ul = { 109 } }; - fd_hash_t mr_10 = (fd_hash_t){ .ul = { 110 } }; - fd_hash_t mr_10_stale = (fd_hash_t){ .ul = { 1010 } }; - - fd_tower_slot_confirmed_t confirmed_msg[1]; - memset( confirmed_msg, 0, sizeof(*confirmed_msg) ); - confirmed_msg->level = FD_TOWER_SLOT_CONFIRMED_DUPLICATE; - confirmed_msg->fwd = 0; - confirmed_msg->slot = 8; - confirmed_msg->block_id = mr_8; - - after_tower( ctx, FD_TOWER_SIG_SLOT_CONFIRMED, (uchar *)confirmed_msg ); - - fd_forest_blk_t * slot8 = fd_forest_query( ctx->forest, 8 ); - FD_TEST( slot8 ); - FD_TEST( fd_hash_eq( &slot8->confirmed_bid, &mr_8 ) ); - FD_TEST( slot8->complete_idx == UINT_MAX ); - - fd_shred_t shred_hdr[1]; - memset( shred_hdr, 0, sizeof(fd_shred_t) ); - shred_hdr->variant = fd_shred_variant( FD_SHRED_TYPE_MERKLE_DATA, 5 ); - shred_hdr->slot = 8; - shred_hdr->idx = FD_FEC_SHRED_CNT - 1U; - shred_hdr->fec_set_idx = 0; - shred_hdr->data.parent_off = 8; /* parent = root slot 0 */ - shred_hdr->data.flags = FD_SHRED_DATA_FLAG_SLOT_COMPLETE; - - after_shred( ctx, SHRED_SIG_SRC_TURBINE, shred_hdr, 0, &mr_8, &mr_root, fd_tickcount() ); - - slot8 = fd_forest_query( ctx->forest, 8 ); - FD_TEST( slot8 ); - FD_TEST( slot8->complete_idx == FD_FEC_SHRED_CNT - 1U ); - FD_TEST( slot8->buffered_idx == UINT_MAX ); - FD_TEST( slot8->lowest_verified_fec == 0 ); - FD_TEST( !slot8->chain_confirmed ); - - memset( shred_hdr, 0, sizeof(fd_shred_t) ); - shred_hdr->variant = fd_shred_variant( FD_SHRED_TYPE_MERKLE_DATA, 5 ); - shred_hdr->slot = 10; - shred_hdr->idx = 0; - shred_hdr->fec_set_idx = 0; - shred_hdr->data.parent_off = 2; /* stale parent = slot 8 */ - - after_shred( ctx, SHRED_SIG_SRC_TURBINE, shred_hdr, 0, &mr_10_stale, &mr_8_stale, fd_tickcount() ); - - fd_forest_blk_t * slot10 = fd_forest_query( ctx->forest, 10 ); - FD_TEST( slot10 ); - FD_TEST( slot10->parent_slot == 8 ); - FD_TEST( slot10->buffered_idx == 0 ); - FD_TEST( slot10->complete_idx == UINT_MAX ); - - memset( shred_hdr, 0, sizeof(fd_shred_t) ); - shred_hdr->variant = fd_shred_variant( FD_SHRED_TYPE_MERKLE_DATA, 5 ); - shred_hdr->slot = 10; - shred_hdr->idx = FD_FEC_SHRED_CNT - 1U; - shred_hdr->fec_set_idx = 0; - shred_hdr->data.parent_off = 1; /* canonical duplicate-confirmed parent = slot 9 */ - shred_hdr->data.flags = FD_SHRED_DATA_FLAG_SLOT_COMPLETE; - - after_fec( ctx, shred_hdr, &mr_10, &mr_9, fd_tickcount() ); - - slot10 = fd_forest_query( ctx->forest, 10 ); - FD_TEST( slot10 ); - FD_TEST( slot10->parent_slot == 9 ); - FD_TEST( slot10->buffered_idx == slot10->complete_idx ); - FD_TEST( slot10->complete_idx == FD_FEC_SHRED_CNT - 1U ); - FD_TEST( fd_hash_eq( &fd_forest_blk_mroots( ctx->forest, slot10 )[0].mr, &mr_10 ) ); - FD_TEST( fd_hash_eq( &fd_forest_blk_mroots( ctx->forest, slot10 )[0].cmr, &mr_9 ) ); - - /* Slot 10 gets duplicate confirmed which triggers check_confirmed. - The parent edge mismatch is detected and slot 8 is returned as the bad block. - Because repair logic assumes the returned bad block must contain a - clearable bad FEC, this trips the UINT_MAX FD_TEST on bad_fec_idx. */ - fd_tower_slot_confirmed_t confirmed_msg_10[1]; - memset( confirmed_msg_10, 0, sizeof(*confirmed_msg_10) ); - confirmed_msg_10->level = FD_TOWER_SLOT_CONFIRMED_DUPLICATE; - confirmed_msg_10->fwd = 0; - confirmed_msg_10->slot = 10; - confirmed_msg_10->block_id = mr_10; - - after_tower( ctx, FD_TOWER_SIG_SLOT_CONFIRMED, (uchar *)confirmed_msg_10 ); - - FD_LOG_NOTICE(( "pass: test_parent_edge_mismatch_with_verified_fec0" )); -} - -static void -test_after_fec0_parent_update_unconfirmed_stale_parent( fd_wksp_t * wksp ) { - - static ctx_t ctx[1]; - setup_ctx( ctx, wksp, 512 ); - - ulong const root_slot = 40UL; - ulong const real_parent_slot = 41UL; - ulong const stale_parent_slot = 42UL; - ulong const child_slot = 43UL; - - fd_forest_init( ctx->forest, root_slot ); - - fd_hash_t empty_mr = {0}; - fd_hash_t mr_root = (fd_hash_t){ .ul = { 400 } }; - fd_hash_t mr_41 = (fd_hash_t){ .ul = { 410 } }; - fd_hash_t mr_42 = (fd_hash_t){ .ul = { 420 } }; - fd_hash_t mr_43 = (fd_hash_t){ .ul = { 430 } }; - fd_hash_t mr_43_fec = (fd_hash_t){ .ul = { 431 } }; - - fd_shred_t shred_hdr[1]; - /* Complete slot 41 on top of the root without confirming it. */ - memset( shred_hdr, 0, sizeof(fd_shred_t) ); - shred_hdr->variant = fd_shred_variant( FD_SHRED_TYPE_MERKLE_DATA, 5 ); - shred_hdr->slot = real_parent_slot; - shred_hdr->idx = FD_FEC_SHRED_CNT - 1U; - shred_hdr->fec_set_idx = 0; - shred_hdr->data.parent_off = (ushort)( real_parent_slot - root_slot ); - shred_hdr->data.flags = FD_SHRED_DATA_FLAG_SLOT_COMPLETE; - FD_TEST( !after_fec( ctx, shred_hdr, &mr_41, &mr_root, fd_tickcount() ) ); - - /* Complete slot 42 as an unconfirmed local branch. */ - memset( shred_hdr, 0, sizeof(fd_shred_t) ); - shred_hdr->variant = fd_shred_variant( FD_SHRED_TYPE_MERKLE_DATA, 5 ); - shred_hdr->slot = stale_parent_slot; - shred_hdr->idx = FD_FEC_SHRED_CNT - 1U; - shred_hdr->fec_set_idx = 0; - shred_hdr->data.parent_off = (ushort)( stale_parent_slot - real_parent_slot ); - shred_hdr->data.flags = FD_SHRED_DATA_FLAG_SLOT_COMPLETE; - FD_TEST( !after_fec( ctx, shred_hdr, &mr_42, &mr_41, fd_tickcount() ) ); - - /* Verify the stale branch is complete but not duplicate-confirmed. */ - fd_forest_blk_t * slot42 = fd_forest_query( ctx->forest, stale_parent_slot ); - FD_TEST( slot42 ); - FD_TEST( slot42->complete_idx == FD_FEC_SHRED_CNT - 1U ); - FD_TEST( !slot42->chain_confirmed ); - FD_TEST( fd_hash_eq( &slot42->confirmed_bid, &empty_mr ) ); - - /* Link slot 43 under slot 42 using a later-FEC shred. */ - memset( shred_hdr, 0, sizeof(fd_shred_t) ); - shred_hdr->variant = fd_shred_variant( FD_SHRED_TYPE_MERKLE_DATA, 5 ); - shred_hdr->slot = child_slot; - shred_hdr->idx = FD_FEC_SHRED_CNT; - shred_hdr->fec_set_idx = FD_FEC_SHRED_CNT; - shred_hdr->data.parent_off = (ushort)( child_slot - stale_parent_slot ); - after_shred( ctx, SHRED_SIG_SRC_TURBINE, shred_hdr, 0, &mr_43_fec, &mr_42, fd_tickcount() ); - - /* Verify the stale parent link exists before FEC 0 arrives.*/ - fd_forest_blk_t * slot43 = fd_forest_query( ctx->forest, child_slot ); - FD_TEST( slot43 ); - FD_TEST( slot43->parent_slot == stale_parent_slot ); - FD_TEST( slot43->complete_idx == UINT_MAX ); - - /* Complete slot 43 FEC 0 with slot 41 as its actual parent. */ - memset( shred_hdr, 0, sizeof(fd_shred_t) ); - shred_hdr->variant = fd_shred_variant( FD_SHRED_TYPE_MERKLE_DATA, 5 ); - shred_hdr->slot = child_slot; - shred_hdr->idx = FD_FEC_SHRED_CNT - 1U; - shred_hdr->fec_set_idx = 0; - shred_hdr->data.parent_off = (ushort)( child_slot - real_parent_slot ); - shred_hdr->data.flags = FD_SHRED_DATA_FLAG_SLOT_COMPLETE; - FD_TEST( !after_fec( ctx, shred_hdr, &mr_43, &mr_41, fd_tickcount() ) ); - - /* Verify FEC 0 reparented slot 43 to slot 41. */ - slot43 = fd_forest_query( ctx->forest, child_slot ); - FD_TEST( slot43 ); - FD_TEST( slot43->parent_slot == real_parent_slot ); - FD_TEST( slot43->complete_idx == FD_FEC_SHRED_CNT - 1U ); - FD_TEST( slot43->buffered_idx == slot43->complete_idx ); - - fd_tower_slot_confirmed_t confirmed_msg[1]; - /* Duplicate-confirm only slot 43. */ - memset( confirmed_msg, 0, sizeof(*confirmed_msg) ); - confirmed_msg->level = FD_TOWER_SLOT_CONFIRMED_DUPLICATE; - confirmed_msg->fwd = 0; - confirmed_msg->slot = child_slot; - confirmed_msg->block_id = mr_43; - after_tower( ctx, FD_TOWER_SIG_SLOT_CONFIRMED, (uchar *)confirmed_msg ); - - /* Verify the stale unconfirmed slot 42 was not touched. */ - fd_forest_blk_t * slot41 = fd_forest_query( ctx->forest, real_parent_slot ); - slot42 = fd_forest_query( ctx->forest, stale_parent_slot ); - slot43 = fd_forest_query( ctx->forest, child_slot ); - FD_TEST( slot41 ); - FD_TEST( slot42 ); - FD_TEST( slot43 ); - FD_TEST( slot41->chain_confirmed ); - FD_TEST( !slot42->chain_confirmed ); - FD_TEST( slot43->chain_confirmed ); - FD_TEST( fd_hash_eq( &slot41->confirmed_bid, &mr_41 ) ); - FD_TEST( fd_hash_eq( &slot42->confirmed_bid, &empty_mr ) ); - FD_TEST( slot42->complete_idx == FD_FEC_SHRED_CNT - 1U ); - FD_TEST( !fd_forest_verify( ctx->forest ) ); - - FD_LOG_NOTICE(( "pass: test_after_fec0_parent_update_unconfirmed_stale_parent" )); -} - -int main( int argc, char ** argv ) { - fd_boot( &argc, &argv ); - - char const * _page_sz = fd_env_strip_cmdline_cstr ( &argc, &argv, "--page-sz", NULL, "gigantic" ); - ulong page_cnt = fd_env_strip_cmdline_ulong( &argc, &argv, "--page-cnt", NULL, 2UL ); - ulong numa_idx = fd_env_strip_cmdline_ulong( &argc, &argv, "--numa-idx", NULL, fd_shmem_numa_idx( 0UL ) ); - fd_wksp_t * wksp = fd_wksp_new_anonymous( fd_cstr_to_shmem_page_sz( _page_sz ), page_cnt, fd_shmem_cpu_idx( numa_idx ), "wksp", 0UL ); - FD_TEST( wksp ); - - test_after_net( wksp ); - - fd_wksp_reset( wksp, 1UL ); - test_sign_lifecycle( wksp ); - - fd_wksp_reset( wksp, 1UL ); - test_future_slots( wksp ); - - fd_wksp_reset( wksp, 1UL ); - test_after_tower_confirmed_eviction( wksp ); - - fd_wksp_reset( wksp, 1UL ); - test_after_fec_dup_confirm_larger_slot( wksp ); - - fd_wksp_reset( wksp, 1UL ); - test_parent_edge_mismatch_with_verified_fec0( wksp ); - - fd_wksp_reset( wksp, 1UL ); - test_after_fec0_parent_update_unconfirmed_stale_parent( wksp ); - - - - fd_halt(); - return 0; -} From 0c77e66a2132be1764dbaf88f1159fb1151aa5c8 Mon Sep 17 00:00:00 2001 From: Richard Patel Date: Wed, 21 Oct 2026 17:06:08 +0000 Subject: [PATCH 08/13] remove poh tile --- book/api/metrics-generated.md | 9 - src/disco/metrics/generated/fd_metrics_all.c | 3 - src/disco/metrics/generated/fd_metrics_all.h | 3 +- src/disco/metrics/generated/fd_metrics_poh.h | 11 - .../metrics/generated/fd_metrics_sample_gen.c | 2 +- src/disco/metrics/metrics.xml | 3 - src/discof/poh/Local.mk | 3 - src/discof/poh/fd_poh.c | 846 ------------------ src/discof/poh/fd_poh.h | 511 ----------- src/discof/poh/fd_poh_tile.c | 392 -------- src/discof/poh/fd_poh_tile.seccomppolicy | 40 - .../poh/generated/fd_poh_tile_seccomp.h | 108 --- 12 files changed, 2 insertions(+), 1929 deletions(-) delete mode 100644 src/disco/metrics/generated/fd_metrics_poh.h delete mode 100644 src/discof/poh/Local.mk delete mode 100644 src/discof/poh/fd_poh.c delete mode 100644 src/discof/poh/fd_poh_tile.c delete mode 100644 src/discof/poh/fd_poh_tile.seccomppolicy delete mode 100644 src/discof/poh/generated/fd_poh_tile_seccomp.h diff --git a/book/api/metrics-generated.md b/book/api/metrics-generated.md index 7145b53af78..5d048b9645b 100644 --- a/book/api/metrics-generated.md +++ b/book/api/metrics-generated.md @@ -685,15 +685,6 @@
-## Poh Tile - -
- -| Metric | Type | Description | -|--------|------|-------------| - -
- ## Sign Tile
diff --git a/src/disco/metrics/generated/fd_metrics_all.c b/src/disco/metrics/generated/fd_metrics_all.c index a13f97d6b2b..8ff6b158324 100644 --- a/src/disco/metrics/generated/fd_metrics_all.c +++ b/src/disco/metrics/generated/fd_metrics_all.c @@ -2063,7 +2063,6 @@ const char * FD_METRICS_TILE_KIND_NAMES[FD_METRICS_TILE_KIND_CNT] = { "resolv", "pack", "execle", - "poh", "sign", "shred", "gossvf", @@ -2111,7 +2110,6 @@ const ulong FD_METRICS_TILE_KIND_SIZES[FD_METRICS_TILE_KIND_CNT] = { FD_METRICS_RESOLV_TOTAL, FD_METRICS_PACK_TOTAL, FD_METRICS_EXECLE_TOTAL, - FD_METRICS_POH_TOTAL, FD_METRICS_SIGN_TOTAL, FD_METRICS_SHRED_TOTAL, FD_METRICS_GOSSVF_TOTAL, @@ -2158,7 +2156,6 @@ const fd_metrics_meta_t * FD_METRICS_TILE_KIND_METRICS[FD_METRICS_TILE_KIND_CNT] FD_METRICS_RESOLV, FD_METRICS_PACK, FD_METRICS_EXECLE, - NULL, FD_METRICS_SIGN, FD_METRICS_SHRED, FD_METRICS_GOSSVF, diff --git a/src/disco/metrics/generated/fd_metrics_all.h b/src/disco/metrics/generated/fd_metrics_all.h index fc3cc1ea03d..88c32a2e9e9 100644 --- a/src/disco/metrics/generated/fd_metrics_all.h +++ b/src/disco/metrics/generated/fd_metrics_all.h @@ -70,7 +70,6 @@ enum { #include "fd_metrics_resolv.h" #include "fd_metrics_pack.h" #include "fd_metrics_execle.h" -#include "fd_metrics_poh.h" #include "fd_metrics_shred.h" #include "fd_metrics_accdb.h" #include "fd_metrics_replay.h" @@ -252,7 +251,7 @@ extern const fd_metrics_meta_t FD_METRICS_ALL_LINK_IN[FD_METRICS_ALL_LINK_IN_TOT #define FD_METRICS_TOTAL_SZ (8UL*301UL) -#define FD_METRICS_TILE_KIND_CNT 45 +#define FD_METRICS_TILE_KIND_CNT 44 extern const char * FD_METRICS_TILE_KIND_NAMES[FD_METRICS_TILE_KIND_CNT]; extern const ulong FD_METRICS_TILE_KIND_SIZES[FD_METRICS_TILE_KIND_CNT]; extern const fd_metrics_meta_t * FD_METRICS_TILE_KIND_METRICS[FD_METRICS_TILE_KIND_CNT]; diff --git a/src/disco/metrics/generated/fd_metrics_poh.h b/src/disco/metrics/generated/fd_metrics_poh.h deleted file mode 100644 index 18c79883722..00000000000 --- a/src/disco/metrics/generated/fd_metrics_poh.h +++ /dev/null @@ -1,11 +0,0 @@ -#ifndef HEADER_fd_src_disco_metrics_generated_fd_metrics_poh_h -#define HEADER_fd_src_disco_metrics_generated_fd_metrics_poh_h - -/* THIS FILE IS GENERATED BY gen_metrics.py. DO NOT HAND EDIT. */ - -#include "../fd_metrics_base.h" -#include "fd_metrics_enums.h" - -#define FD_METRICS_POH_TOTAL (0UL) - -#endif /* HEADER_fd_src_disco_metrics_generated_fd_metrics_poh_h */ diff --git a/src/disco/metrics/generated/fd_metrics_sample_gen.c b/src/disco/metrics/generated/fd_metrics_sample_gen.c index 05f071dbd60..e1460c66088 100644 --- a/src/disco/metrics/generated/fd_metrics_sample_gen.c +++ b/src/disco/metrics/generated/fd_metrics_sample_gen.c @@ -4411,6 +4411,6 @@ fd_metrics_sample_schema_t const fd_metrics_sample_tiles[] = { { NULL, 0U, NULL, 0UL, 0UL } }; -char const * const fd_metrics_sample_skip[] = { "backt", "benchs", "poh", "solcap", "tower", NULL }; +char const * const fd_metrics_sample_skip[] = { "backt", "benchs", "solcap", "tower", NULL }; fd_metrics_sample_schema_t const fd_metrics_sample_link = { "link", 61U, fields_link, 9UL, 284UL }; diff --git a/src/disco/metrics/metrics.xml b/src/disco/metrics/metrics.xml index 04d48b17c3a..1727e30eba3 100644 --- a/src/disco/metrics/metrics.xml +++ b/src/disco/metrics/metrics.xml @@ -784,9 +784,6 @@ deliberate exception with lint-allow="") - - - diff --git a/src/discof/poh/Local.mk b/src/discof/poh/Local.mk deleted file mode 100644 index 14b39695284..00000000000 --- a/src/discof/poh/Local.mk +++ /dev/null @@ -1,3 +0,0 @@ -ifdef FD_HAS_HOSTED -$(call add-objs,fd_poh_tile fd_poh,fd_discof) -endif diff --git a/src/discof/poh/fd_poh.c b/src/discof/poh/fd_poh.c deleted file mode 100644 index 7ee4d8c0ab7..00000000000 --- a/src/discof/poh/fd_poh.c +++ /dev/null @@ -1,846 +0,0 @@ -#include "fd_poh.h" -#include "../../disco/tiles.h" - -/* The PoH implementation is at its core a state machine ... - - +--------+ - | UNINIT | - +--------+ - | - +---------+ | +---------+ - | v v v | - +-------------------+ +----------+ +------------------+ - | WAITING_FOR_SLOT |<----| FOLLOWER |----->| WAITING_FOR_BANK | - +-------------------+ +----------+ +------------------+ - | ^ | - | | | - | +----------+ | - |------>| LEADER |<--------+ - +----------+ - - The state machine starts UNINIT, but once a snapshot is loaded it - will transition to follower. - - The state machine is in a resting the state when FOLLOWER, in this - state it knows a `next_leader_slot` and will continually hash to - advance towards that slot. When it reaches the `next_leader_slot` - it will transition to the WAITING_FOR_BANK state, where it waits for - the replay stage to tell it some information relevant to that leader - slot, so that it can start doing mixins and hashing towards the end - of the block. When the block ends, the state transitions back to - follower, even if the next slot is the leader, as we need the replay - stage to tell us about the new leader slot. - - Sometimes it might happen that we have received the bank from replay - stage before we have reached the `next_leader_slot`, in which case - we transition to the WAITING_FOR_SLOT state, where we wait for the - hash count to reach the leader slot. - - At any time, during any state except UNINIT, we can be suddenly - "reset" by the replay tile. Such reset actions may move the reset - slot backwards or forwards, or set it back to something we have - already seen before. BUT, the `next_leader_slot` must always - advance forward. - - If the PoH machine successfully completes a leader slot, by hashing - it until the end, then the a completion message is sent back to - replay with the final blockhash, after which the state machine enters - the follower state once again, and waits for further instructions - from replay. */ - -#define STATE_UNINIT (0) -#define STATE_FOLLOWER (1) -#define STATE_WAITING_FOR_BANK (2) -#define STATE_WAITING_FOR_SLOT (3) -#define STATE_LEADER (4) -#define STATE_WAITING_FOR_RESET (5) - -FD_FN_CONST ulong -fd_poh_align( void ) { - return FD_POH_ALIGN; -} - -FD_FN_CONST ulong -fd_poh_footprint( void ) { - return sizeof(fd_poh_t); -} - -void * -fd_poh_new( void * shmem ) { - fd_poh_t * poh = (fd_poh_t *)shmem; - - if( FD_UNLIKELY( !poh ) ) { - FD_LOG_WARNING(( "NULL shmem" )); - return NULL; - } - - if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)poh, fd_poh_align() ) ) ) { - FD_LOG_WARNING(( "misaligned shmem" )); - return NULL; - } - - poh->hashcnt_per_tick = ULONG_MAX; - poh->state = STATE_UNINIT; - poh->wfs_paused = 0; - memset( poh->completed_block_id, 0, 32UL ); - - FD_COMPILER_MFENCE(); - FD_VOLATILE( poh->magic ) = FD_POH_MAGIC; - FD_COMPILER_MFENCE(); - - return (void *)poh; -} - -fd_poh_t * -fd_poh_join( void * shpoh, - fd_poh_out_t * shred_out, - fd_poh_out_t * replay_out, - fd_leader_txn_timing_table_t * timing_tables, - ulong timing_table_max ) { - if( FD_UNLIKELY( !shpoh ) ) { - FD_LOG_WARNING(( "NULL shpoh" )); - return NULL; - } - - if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)shpoh, fd_poh_align() ) ) ) { - FD_LOG_WARNING(( "misaligned shpoh" )); - return NULL; - } - - fd_poh_t * poh = (fd_poh_t *)shpoh; - - if( FD_UNLIKELY( poh->magic!=FD_POH_MAGIC ) ) { - FD_LOG_WARNING(( "bad magic" )); - return NULL; - } - - *poh->shred_out = *shred_out; - *poh->replay_out = *replay_out; - - poh->timing_tables = timing_tables; - poh->timing_table_idx = 0UL; - poh->timing_table_max = timing_table_max; - - return poh; -} - -static void -transition_to_follower( fd_poh_t * poh, - fd_stem_context_t * stem, - int completed_leader_slot ) { - FD_TEST( poh->state==STATE_LEADER || poh->state==STATE_WAITING_FOR_BANK || poh->state==STATE_WAITING_FOR_SLOT || poh->state==STATE_WAITING_FOR_RESET ); - - if( FD_LIKELY( completed_leader_slot ) ) FD_TEST( poh->state==STATE_LEADER ); - - if( FD_LIKELY( poh->state==STATE_LEADER || poh->state==STATE_WAITING_FOR_SLOT ) ) { - fd_poh_leader_slot_ended_t * dst = fd_chunk_to_laddr( poh->replay_out->mem, poh->replay_out->chunk ); - dst->completed = completed_leader_slot; - dst->slot = fd_ulong_if( completed_leader_slot, poh->slot-1UL, poh->slot ); - fd_memcpy( dst->blockhash, poh->hash, 32UL ); - - dst->microblock_count = poh->pack_microblock_count; - dst->pack_block_cost = poh->pack_block_cost; - dst->pack_vote_cost = poh->pack_vote_cost; - dst->pack_data_bytes = poh->pack_data_bytes; - dst->bundle_txn_count = poh->pack_bundle_txn_count; - dst->pack_end_reason = poh->pack_end_reason; - dst->pack_start_ns = poh->pack_start_ns; - dst->pack_end_ns = poh->pack_end_ns; - dst->timing_table_idx = poh->timing_tables ? poh->timing_table_idx : ULONG_MAX; - - ulong tspub = (ulong)fd_frag_meta_ts_comp( fd_tickcount() ); - fd_stem_publish( stem, poh->replay_out->idx, 0UL, poh->replay_out->chunk, sizeof(fd_poh_leader_slot_ended_t), 0UL, 0UL, tspub ); - poh->replay_out->chunk = fd_dcache_compact_next( poh->replay_out->chunk, sizeof(fd_poh_leader_slot_ended_t), poh->replay_out->chunk0, poh->replay_out->wmark ); - } - - poh->state = STATE_FOLLOWER; -} - -static void -update_hashes_per_tick( fd_poh_t * poh, - ulong hashcnt_per_tick ) { - if( FD_UNLIKELY( poh->hashcnt_per_tick!=hashcnt_per_tick ) ) { - if( FD_UNLIKELY( poh->hashcnt_per_tick!=ULONG_MAX ) ) { - FD_LOG_WARNING(( "hashes per tick changed from %lu to %lu", poh->hashcnt_per_tick, hashcnt_per_tick )); - } - - /* Recompute derived information about the clock. */ - poh->hashcnt_duration_ns = (double)poh->tick_duration_ns/(double)hashcnt_per_tick; - poh->hashcnt_per_slot = poh->ticks_per_slot*hashcnt_per_tick; - poh->hashcnt_per_tick = hashcnt_per_tick; - - /* Discard any ticks we might have done in the interim. They will - have the wrong number of hashes per tick. We can just catch back - up quickly if not too many slots were skipped and hopefully - publish on time. Note that tick production and verification of - skipped slots is done for the eventual bank that publishes a - slot, for example: - - Reset Slot: 998 - Epoch Transition Slot: 1000 - Leader Slot: 1002 - - In this case, if a feature changing the hashcnt_per_tick is - activated in slot 1000, and we are publishing empty ticks for - slots 998, 999, 1000, and 1001, they should all have the new - hashes_per_tick number of hashes, rather than the older one, or - some combination. */ - - FD_TEST( poh->last_slot==poh->reset_slot ); - FD_TEST( !poh->last_hashcnt ); - poh->slot = poh->reset_slot; - poh->hashcnt = 0UL; - fd_memcpy( poh->hash, poh->reset_hash, 32UL ); - } -} - -void -fd_poh_reset( fd_poh_t * poh, - fd_stem_context_t * stem, - long timestamp, /* The local timestamp when the reset is occurring */ - ulong hashcnt_per_tick, /* The hashcnt per tick of the bank that completed */ - ulong ticks_per_slot, - ulong tick_duration_ns, - ulong completed_slot, /* The slot that successfully produced a block */ - uchar const * completed_blockhash, /* The hash of the last tick in the produced block */ - ulong next_leader_slot, /* The next slot where this node will be leader */ - ulong max_microblocks_in_slot, /* The maximum number of microblocks that may appear in a slot */ - uchar const * completed_block_id /* The block id of the completed block */) { - FD_TEST( memcmp( poh->completed_block_id, completed_block_id, 32UL ) ); - - memcpy( poh->reset_hash, completed_blockhash, 32UL ); - memcpy( poh->hash, completed_blockhash, 32UL ); - memcpy( poh->completed_block_id, completed_block_id, 32UL ); - poh->slot = completed_slot+1UL; - poh->hashcnt = 0UL; - poh->last_slot = poh->slot; - poh->last_hashcnt = 0UL; - poh->reset_slot = poh->slot; - poh->next_leader_slot = next_leader_slot; - poh->max_microblocks_per_slot = max_microblocks_in_slot; - poh->reset_slot_start_ns = timestamp; - - if( FD_UNLIKELY( poh->state==STATE_UNINIT ) ) { - poh->tick_duration_ns = tick_duration_ns; - poh->ticks_per_slot = ticks_per_slot; - poh->state = STATE_FOLLOWER; - } else { - poh->tick_duration_ns = tick_duration_ns; - FD_TEST( ticks_per_slot==poh->ticks_per_slot ); - } - update_hashes_per_tick( poh, hashcnt_per_tick ); - - /* When we reset, we need to allow PoH to tick freely again rather - than being constrained. If we are leader after the reset, this - is OK because we won't tick until we get a bank, and the lower - bound will be reset with the value from the bank. */ - poh->microblocks_lower_bound = poh->max_microblocks_per_slot; - - if( FD_UNLIKELY( poh->state!=STATE_FOLLOWER ) ) transition_to_follower( poh, stem, 0 ); - if( FD_UNLIKELY( poh->slot==poh->next_leader_slot ) ) poh->state = STATE_WAITING_FOR_BANK; - -} - -void -fd_poh_begin_leader( fd_poh_t * poh, - ulong slot, - ulong hashcnt_per_tick, - ulong ticks_per_slot, - ulong tick_duration_ns, - ulong max_microblocks_in_slot, - long slot_start_ns ) { - FD_TEST( poh->state==STATE_FOLLOWER || poh->state==STATE_WAITING_FOR_BANK ); - FD_TEST( slot==poh->next_leader_slot ); - - /* PoH ends the slot once it "ticks" through all of the hashes, but we - only want that to happen if we have received a done packing message - from pack, so we always reserve an empty microblock at the end so - the tick advance will not end the slot without being told. */ - poh->max_microblocks_per_slot = max_microblocks_in_slot+1UL; - - poh->tick_duration_ns = tick_duration_ns; - FD_TEST( ticks_per_slot==poh->ticks_per_slot ); - update_hashes_per_tick( poh, hashcnt_per_tick ); - - FD_TEST( poh->slot<=poh->next_leader_slot ); - if( FD_LIKELY( poh->slotnext_leader_slot ) ) poh->state = STATE_WAITING_FOR_SLOT; - else poh->state = STATE_LEADER; - - poh->microblocks_lower_bound = 0UL; - poh->leader_slot_start_ns = slot_start_ns; - - if( FD_LIKELY( poh->timing_tables ) ) { - poh->timing_table_idx ^= 1UL; - fd_leader_txn_timing_table_t * table = fd_leader_txn_timing_table( poh->timing_tables, poh->timing_table_idx, poh->timing_table_max ); - table->slot = slot; - table->cnt = 0UL; - } - - FD_LOG_INFO(( "begin_leader(slot=%lu, last_slot=%lu, last_hashcnt=%lu)", slot, poh->last_slot, poh->last_hashcnt )); -} - -int -fd_poh_have_leader_bank( fd_poh_t const * poh ) { - return poh->state==STATE_WAITING_FOR_SLOT || poh->state==STATE_LEADER; -} - -int -fd_poh_hashing_to_leader_slot( fd_poh_t const * poh ) { - int hashing = poh->state==STATE_WAITING_FOR_SLOT || poh->state==STATE_LEADER; - return hashing && poh->slotnext_leader_slot; -} - -static int -fd_poh_is_hashing( fd_poh_t const * poh ) { - if( FD_UNLIKELY( poh->state==STATE_UNINIT || poh->state==STATE_WAITING_FOR_RESET ) ) return 0; - if( FD_UNLIKELY( poh->wfs_paused ) ) return 0; - if( FD_UNLIKELY( poh->state==STATE_WAITING_FOR_BANK ) ) return 0; - - /* No need to hash if we are so far from a leader slot that skipping - all the required ticks would exceed MAX_SKIPPED_TICKS, or the u16 - shred parent_offset (not possible to make the block anymore). */ - if( FD_LIKELY( poh->state==STATE_FOLLOWER ) ) { - if( FD_LIKELY( poh->next_leader_slot==ULONG_MAX || - poh->next_leader_slot-poh->slot>fd_ulong_min( MAX_SKIPPED_TICKS, USHORT_MAX-poh->ticks_per_slot )/poh->ticks_per_slot ) ) return 0; - } - return 1; -} - -long -fd_poh_next_deadline( fd_poh_t const * poh ) { - if( FD_LIKELY( !fd_poh_is_hashing( poh ) ) ) return LONG_MAX; - if( FD_UNLIKELY( fd_poh_must_publish_skipped_tick( poh ) ) ) return 0L; /* one per call, no clock gate */ - - ulong next_tick_hashcnt = (poh->hashcnt/poh->hashcnt_per_tick+1UL)*poh->hashcnt_per_tick; - if( FD_LIKELY( poh->state==STATE_FOLLOWER || poh->state==STATE_WAITING_FOR_SLOT ) ) { - ulong hashcnt_since_reset = (poh->slot-poh->reset_slot)*poh->hashcnt_per_slot+next_tick_hashcnt; - return poh->reset_slot_start_ns+(long)((double)hashcnt_since_reset*poh->hashcnt_duration_ns); - } - return poh->leader_slot_start_ns+(long)((double)next_tick_hashcnt*poh->hashcnt_duration_ns); -} - -int -fd_poh_must_tick( fd_poh_t const * poh ) { - return poh->state==STATE_LEADER && (poh->hashcnt%poh->hashcnt_per_tick)==(poh->hashcnt_per_tick-1UL); -} - -int -fd_poh_must_publish_skipped_tick( fd_poh_t const * poh ) { - return poh->state==STATE_LEADER && poh->last_slotslot; -} - -void -fd_poh_wfs_done( fd_poh_t * poh ) { - poh->wfs_paused = 0; - poh->reset_slot_start_ns = fd_log_wallclock(); -} - -void -fd_poh_update_max_microblocks( fd_poh_t * poh, - ulong new_max ) { - ulong inflated = new_max + 1UL; - - /* Guaranteed to be monotonically decreasing. */ - FD_TEST( inflated <= poh->max_microblocks_per_slot ); - poh->max_microblocks_per_slot = inflated; - FD_TEST( poh->max_microblocks_per_slot >= poh->microblocks_lower_bound ); -} - -void -fd_poh_done_packing( fd_poh_t * poh, - fd_stem_context_t * stem, - fd_done_packing_t const * done_packing ) { - ulong microblocks_in_slot = done_packing->microblocks_in_slot; - - FD_TEST( poh->state==STATE_LEADER ); - FD_LOG_INFO(( "done_packing(slot=%lu,seen_microblocks=%lu,microblocks_in_slot=%lu)", - poh->slot, - poh->microblocks_lower_bound, - microblocks_in_slot )); - FD_TEST( poh->microblocks_lower_bound==microblocks_in_slot ); - FD_TEST( poh->microblocks_lower_bound<=poh->max_microblocks_per_slot ); - - poh->pack_microblock_count = microblocks_in_slot; - poh->pack_block_cost = done_packing->limits_usage->block_cost; - poh->pack_vote_cost = done_packing->limits_usage->vote_cost; - poh->pack_data_bytes = done_packing->limits_usage->block_data_bytes; - poh->pack_bundle_txn_count = done_packing->bundle_txn_count; - poh->pack_end_reason = done_packing->end_slot_reason; - poh->pack_start_ns = done_packing->pack_start_ns; - poh->pack_end_ns = done_packing->pack_end_ns; - - if( FD_UNLIKELY( done_packing->end_slot_reason==FD_PACK_END_SLOT_REASON_ABANDONED ) ) { - transition_to_follower( poh, stem, 0 ); - return; - } - - poh->microblocks_lower_bound += 1UL /* done_packing as a phantom "microblock"*/ - + (poh->max_microblocks_per_slot-1UL) /* the canonical microblock limit */ - - microblocks_in_slot /* the actual microblock count */; - FD_TEST( poh->microblocks_lower_bound==poh->max_microblocks_per_slot ); -} - -static void -publish_tick( fd_poh_t * poh, - fd_stem_context_t * stem, - uchar hash[ static 32 ], - int is_skipped ) { - ulong hashcnt = poh->hashcnt_per_tick*(1UL+(poh->last_hashcnt/poh->hashcnt_per_tick)); - - uchar * dst = (uchar *)fd_chunk_to_laddr( poh->shred_out->mem, poh->shred_out->chunk ); - - FD_TEST( poh->last_slot>=poh->reset_slot ); - fd_entry_batch_meta_t * meta = (fd_entry_batch_meta_t *)dst; - if( FD_UNLIKELY( is_skipped ) ) { - /* We are publishing ticks for a skipped slot, the reference tick - and block complete flags should always be zero. */ - meta->reference_tick = 0UL; - meta->block_complete = 0; - } else { - meta->reference_tick = hashcnt/poh->hashcnt_per_tick; - meta->block_complete = hashcnt==poh->hashcnt_per_slot; - } - - meta->parent_block_id_valid = 1; - fd_memcpy( meta->parent_block_id, poh->completed_block_id, 32UL ); - - ulong slot = fd_ulong_if( meta->block_complete, poh->slot-1UL, poh->slot ); - meta->parent_offset = 1UL+slot-poh->reset_slot; - - FD_TEST( hashcnt>poh->last_hashcnt ); - ulong hash_delta = hashcnt-poh->last_hashcnt; - - dst += sizeof(fd_entry_batch_meta_t); - fd_entry_batch_header_t * tick = (fd_entry_batch_header_t *)dst; - tick->hashcnt_delta = hash_delta; - fd_memcpy( tick->hash, hash, 32UL ); - tick->txn_cnt = 0UL; - - ulong tspub = (ulong)fd_frag_meta_ts_comp( fd_tickcount() ); - ulong sz = sizeof(fd_entry_batch_meta_t)+sizeof(fd_entry_batch_header_t); - ulong sig = fd_disco_poh_sig( slot, POH_PKT_TYPE_MICROBLOCK, 0UL ); - fd_stem_publish( stem, poh->shred_out->idx, sig, poh->shred_out->chunk, sz, 0UL, 0UL, tspub ); - poh->shred_out->chunk = fd_dcache_compact_next( poh->shred_out->chunk, sz, poh->shred_out->chunk0, poh->shred_out->wmark ); - - if( FD_UNLIKELY( hashcnt==poh->hashcnt_per_slot ) ) { - poh->last_slot++; - poh->last_hashcnt = 0UL; - } else { - poh->last_hashcnt = hashcnt; - } -} - -void -fd_poh_advance( fd_poh_t * poh, - fd_stem_context_t * stem, - int * opt_poll_in, - int * charge_busy ) { - if( FD_LIKELY( !fd_poh_is_hashing( poh ) ) ) return; - - /* If we have skipped ticks pending because we skipped some slots to - become leader, register them now one at a time. */ - if( FD_UNLIKELY( fd_poh_must_publish_skipped_tick( poh ) ) ) { - FD_TEST( poh->hashcnt==0UL ); /* Current hashcnt stays 0 until the math below is run at least once. */ - FD_TEST( !(poh->last_hashcnt%poh->hashcnt_per_tick) ); /* While skipped ticks are being published, last_hashcnt marches forward in increments of hashcnt_per_tick. */ - ulong publish_hashcnt = poh->last_hashcnt+poh->hashcnt_per_tick; - ulong tick_idx = (poh->last_slot*poh->ticks_per_slot+publish_hashcnt/poh->hashcnt_per_tick)%MAX_SKIPPED_TICKS; - - publish_tick( poh, stem, poh->skipped_tick_hashes[ tick_idx ], 1 ); - - /* If we are catching up now and publishing a bunch of skipped - ticks, we do not want to process any incoming microblocks until - all the skipped ticks have been published out; otherwise we would - intersperse skipped tick messages with microblocks. */ - *opt_poll_in = 0; - *charge_busy = 1; - return; - } - - int low_power_mode = poh->hashcnt_per_tick==1UL; - - /* If we are the leader, always leave enough capacity in the slot so - that we can mixin any potential microblocks still coming from the - pack tile for this slot. - - When not leading (FOLLOWER or WAITING_FOR_SLOT), no microblocks - will be mixed in, so there is nothing to reserve so - restricted_hashcnt does not need to be reduced from hashcnt_per_slot. */ - ulong max_remaining_microblocks; - ulong restricted_hashcnt; - if( FD_LIKELY( poh->state==STATE_LEADER ) ) { - max_remaining_microblocks = poh->max_microblocks_per_slot - poh->microblocks_lower_bound; - - /* With hashcnt_per_tick hashes per tick, we actually get - hashcnt_per_tick-1 chances to mixin a microblock. For each tick - span that we need to reserve, we also need to reserve the hashcnt - for the tick, hence the + - max_remaining_microblocks/(hashcnt_per_tick-1) rounded up. - - However, if hashcnt_per_tick is 1 because we're in low power mode, - this should probably just be max_remaining_microblocks. */ - ulong max_remaining_ticks_or_microblocks = max_remaining_microblocks; - if( FD_LIKELY( !low_power_mode ) ) max_remaining_ticks_or_microblocks += (max_remaining_microblocks+poh->hashcnt_per_tick-2UL)/(poh->hashcnt_per_tick-1UL); - - restricted_hashcnt = fd_ulong_if( poh->hashcnt_per_slot>=max_remaining_ticks_or_microblocks, poh->hashcnt_per_slot-max_remaining_ticks_or_microblocks, 0UL ); - } else { - max_remaining_microblocks = 0UL; - restricted_hashcnt = poh->hashcnt_per_slot; - } - - ulong min_hashcnt = poh->hashcnt; - - if( FD_LIKELY( !low_power_mode ) ) { - /* Recall that there are two kinds of events that will get published - to the shredder, - - (a) Ticks. These occur every 62,500 (hashcnt_per_tick) hashcnts, - and there will be 64 (ticks_per_slot) of them in each slot. - - Ticks must not have any transactions mixed into the hash. - This is not strictly needed in theory, but is required by the - current consensus protocol. They get published here in - after_credit. - - (b) Microblocks. These can occur at any other hashcnt, as long - as it is not a tick. Microblocks cannot be empty, and must - have at least one transactions mixed in. These get - published in after_frag. - - If hashcnt_per_tick is 1, then we are in low power mode and the - following does not apply, since we can mix in transactions at any - time. - - In the normal, non-low-power mode, though, we have to be careful - to make sure that we do not publish microblocks on tick - boundaries. To do that, we need to obey two rules: - (i) after_credit must not leave hashcnt one before a tick - boundary - (ii) if after_credit begins one before a tick boundary, it must - advance hashcnt and publish the tick - - There's some interplay between min_hashcnt and restricted_hashcnt - here, and we need to show that there's always a value of - target_hashcnt we can pick such that - min_hashcnt <= target_hashcnt <= restricted_hashcnt. - We'll prove this by induction for current_slot==0 and - is_leader==true, since all other slots should be the same. - - Let m_j and r_j be the min_hashcnt and restricted_hashcnt - (respectively) for the jth call to after_credit in a slot. We - want to show that for all values of j, it's possible to pick a - value h_j, the value of target_hashcnt for the jth call to - after_credit (which is also the value of hashcnt after - after_credit has completed) such that m_j<=h_j<=r_j. - - Additionally, let T be hashcnt_per_tick and N be ticks_per_slot. - - Starting with the base case, j==0. m_j=0, and - r_0 = N*T - max_microblocks_per_slot - - ceil(max_microblocks_per_slot/(T-1)). - - This is monotonic decreasing in max_microblocks_per_slot, so it - achieves its minimum when max_microblocks_per_slot is its - maximum. - r_0 >= N*T - N*(T-1) - ceil( (N*(T-1))/(T-1)) - = N*T - N*(T-1)-N = 0. - Thus, m_0 <= r_0, as desired. - - - - Then, for the inductive step, assume there exists h_j such that - m_j<=h_j<=r_j, and we want to show that there exists h_{j+1}, - which is the same as showing m_{j+1}<=r_{j+1}. - - Let a_j be 1 if we had a microblock immediately following the jth - call to after_credit, and 0 otherwise. Then hashcnt at the start - of the (j+1)th call to after_frag is h_j+a_j. - Also, set b_{j+1}=1 if we are in the case covered by rule (ii) - above during the (j+1)th call to after_credit, i.e. if - (h_j+a_j)%T==T-1. Thus, m_{j+1} = h_j + a_j + b_{j+1}. - - If we received an additional microblock, then - max_remaining_microblocks goes down by 1, and - max_remaining_ticks_or_microblocks goes down by either 1 or 2, - which means restricted_hashcnt goes up by either 1 or 2. In - particular, it goes up by 2 if the new value of - max_remaining_microblocks (at the start of the (j+1)th call to - after_credit) is congruent to 0 mod T-1. Let b'_{j+1} be 1 if - this condition is met and 0 otherwise. If we receive a - done_packing message, restricted_hashcnt can go up by more, but - we can ignore that case, since it is less restrictive. - Thus, r_{j+1}=r_j+a_j+b'_{j+1}. - - If h_j < r_j (strictly less), then h_j+a_j < r_j+a_j. And thus, - since b_{j+1}<=b'_{j+1}+1, just by virtue of them both being - binary, - h_j + a_j + b_{j+1} < r_j + a_j + b'_{j+1} + 1, - which is the same (for integers) as - h_j + a_j + b_{j+1} <= r_j + a_j + b'_{j+1}, - m_{j+1} <= r_{j+1} - - On the other hand, if h_j==r_j, this is easy unless b_{j+1}==1, - which can also only happen if a_j==1. Then (h_j+a_j)%T==T-1, - which means there's an integer k such that - - h_j+a_j==(ticks_per_slot-k)*T-1 - h_j ==ticks_per_slot*T - k*(T-1)-1 - k-1 - ==ticks_per_slot*T - (k*(T-1)+1) - ceil( (k*(T-1)+1)/(T-1) ) - - Since h_j==r_j in this case, and - r_j==(ticks_per_slot*T) - max_remaining_microblocks_j - ceil(max_remaining_microblocks_j/(T-1)), - we can see that the value of max_remaining_microblocks at the - start of the jth call to after_credit is k*(T-1)+1. Again, since - a_j==1, then the value of max_remaining_microblocks at the start - of the j+1th call to after_credit decreases by 1 to k*(T-1), - which means b'_{j+1}=1. - - Thus, h_j + a_j + b_{j+1} == r_j + a_j + b'_{j+1}, so, in - particular, h_{j+1}<=r_{j+1} as desired. */ - min_hashcnt += (ulong)(min_hashcnt%poh->hashcnt_per_tick == (poh->hashcnt_per_tick-1UL)); /* add b_{j+1}, enforcing rule (ii) */ - } - /* Now figure out how many hashes are needed to "catch up" the hash - count to the current system clock, and clamp it to the allowed - range. */ - long now = fd_clock_tile_now( poh->clock ); - ulong target_hashcnt; - if( FD_LIKELY( poh->state==STATE_FOLLOWER ||poh->state==STATE_WAITING_FOR_SLOT ) ) { - target_hashcnt = (ulong)((double)(now - poh->reset_slot_start_ns) / poh->hashcnt_duration_ns) - (poh->slot-poh->reset_slot)*poh->hashcnt_per_slot; - } else { - FD_TEST( poh->state==STATE_LEADER ); - target_hashcnt = (ulong)((double)(now - poh->leader_slot_start_ns) / poh->hashcnt_duration_ns); - } - /* Clamp to [min_hashcnt, restricted_hashcnt] as above */ - target_hashcnt = fd_ulong_max( fd_ulong_min( target_hashcnt, restricted_hashcnt ), min_hashcnt ); - - /* The above proof showed that it was always possible to pick a value - of target_hashcnt, but we still have a lot of freedom in how to - pick it. It simplifies the code a lot if we don't keep going after - a tick in this function. In particular, we want to publish at most - 1 tick in this call, since otherwise we could consume infinite - credits to publish here. The credits are set so that we should - only ever publish one tick during this loop. Also, all the extra - stuff (leader transitions, publishing ticks, etc.) we have to do - happens at tick boundaries, so this lets us consolidate all those - cases. - - Mathematically, since the current value of hashcnt is h_j+a_j, the - next tick (advancing a full tick if we're currently at a tick) is - t_{j+1} = T*(floor( (h_j+a_j)/T )+1). We need to show that if we set - h'_{j+1} = min( h_{j+1}, t_{j+1} ), it is still valid. - - First, h'_{j+1} <= h_{j+1} <= r_{j+1}, so we're okay in that - direction. - - Next, observe that t_{j+1}>=h_j + a_j + 1, and recall that b_{j+1} - is 0 or 1. So then, - t_{j+1} >= h_j+a_j+b_{j+1} = m_{j+1}. - - We know h_{j+1) >= m_{j+1} from before, so then h'_{j+1} >= - m_{j+1}, as desired. */ - - ulong next_tick_hashcnt = poh->hashcnt_per_tick * (1UL+(poh->hashcnt/poh->hashcnt_per_tick)); - target_hashcnt = fd_ulong_min( target_hashcnt, next_tick_hashcnt ); - - /* We still need to enforce rule (i). We know that min_hashcnt%T != - T-1 because of rule (ii). That means that if target_hashcnt%T == - T-1 at this point, target_hashcnt > min_hashcnt (notice the - strict), so target_hashcnt-1 >= min_hashcnt and is thus still a - valid choice for target_hashcnt. */ - target_hashcnt -= (ulong)( (!low_power_mode) & ((target_hashcnt%poh->hashcnt_per_tick)==(poh->hashcnt_per_tick-1UL)) ); - - FD_TEST( target_hashcnt >= poh->hashcnt ); - FD_TEST( target_hashcnt >= min_hashcnt ); - FD_TEST( target_hashcnt <= restricted_hashcnt ); - - if( FD_UNLIKELY( poh->hashcnt==target_hashcnt ) ) return; /* Nothing to do, don't publish a tick twice */ - - *charge_busy = 1; - - if( FD_LIKELY( poh->hashcnthash, poh->hash, target_hashcnt-poh->hashcnt ); - poh->hashcnt = target_hashcnt; - } - - if( FD_UNLIKELY( poh->hashcnt==poh->hashcnt_per_slot ) ) { - poh->slot++; - poh->hashcnt = 0UL; - } - - switch( poh->state ) { - case STATE_LEADER: { - if( FD_UNLIKELY( !(poh->hashcnt%poh->hashcnt_per_tick) ) ) { - /* We ticked while leader... send an empty microblock (a tick) - to the shred tile. */ - publish_tick( poh, stem, poh->hash, 0 ); - } - if( FD_UNLIKELY( poh->slot>poh->next_leader_slot ) ) { - /* We ticked while leader and are no longer leader... transition - the state machine. */ - FD_TEST( !max_remaining_microblocks ); - FD_LOG_INFO(( "fd_poh_ticked_outof_leader(slot=%lu)", poh->slot-1UL )); - transition_to_follower( poh, stem, 1 ); - poh->state = STATE_WAITING_FOR_RESET; - } - break; - } - case STATE_WAITING_FOR_SLOT: - case STATE_FOLLOWER: { - if( FD_UNLIKELY( !(poh->hashcnt%poh->hashcnt_per_tick ) && poh->next_leader_slot!=ULONG_MAX ) ) { - /* We finished a tick while not leader... save the current hash - so it can be played back into the bank when we become the - leader. - - If next_leader_slot is ULONG_MAX, we have no upcoming leader - slot and these tick hashes will never be published, so we - skip storing them. */ - ulong tick_idx = (poh->slot*poh->ticks_per_slot+poh->hashcnt/poh->hashcnt_per_tick)%MAX_SKIPPED_TICKS; - fd_memcpy( poh->skipped_tick_hashes[ tick_idx ], poh->hash, 32UL ); - - ulong initial_tick_idx = (poh->last_slot*poh->ticks_per_slot+poh->last_hashcnt/poh->hashcnt_per_tick)%MAX_SKIPPED_TICKS; - if( FD_UNLIKELY( tick_idx==initial_tick_idx ) ) FD_LOG_ERR(( "Too many skipped ticks from slot %lu to slot %lu, chain must halt", poh->last_slot, poh->slot )); - } - - FD_TEST( poh->slot<=poh->next_leader_slot ); - if( FD_UNLIKELY( poh->slot==poh->next_leader_slot ) ) { - /* We ticked while not leader and are now leader... transition - the state machine. */ - if( FD_LIKELY( poh->state==STATE_FOLLOWER ) ) poh->state = STATE_WAITING_FOR_BANK; - else poh->state = STATE_LEADER; - } - break; - } - default: { - break; - } - } -} - -static void -publish_microblock( fd_poh_t * poh, - fd_stem_context_t * stem, - ulong slot, - ulong hashcnt_delta, - ulong txn_cnt, - fd_txn_p_t const * txns ) { - uchar * dst = (uchar *)fd_chunk_to_laddr( poh->shred_out->mem, poh->shred_out->chunk ); - FD_TEST( slot>=poh->reset_slot ); - fd_entry_batch_meta_t * meta = (fd_entry_batch_meta_t *)dst; - meta->parent_offset = 1UL+slot-poh->reset_slot; - meta->reference_tick = (poh->hashcnt/poh->hashcnt_per_tick) % poh->ticks_per_slot; - meta->block_complete = !poh->hashcnt; - - meta->parent_block_id_valid = 1; - fd_memcpy( meta->parent_block_id, poh->completed_block_id, 32UL ); - - dst += sizeof(fd_entry_batch_meta_t); - fd_entry_batch_header_t * header = (fd_entry_batch_header_t *)dst; - header->hashcnt_delta = hashcnt_delta; - fd_memcpy( header->hash, poh->hash, 32UL ); - - dst += sizeof(fd_entry_batch_header_t); - ulong payload_sz = 0UL; - ulong included_txn_cnt = 0UL; - for( ulong i=0UL; iflags & FD_TXN_P_FLAGS_EXECUTE_SUCCESS) ) ) continue; - - fd_memcpy( dst, txn->payload, txn->payload_sz ); - payload_sz += txn->payload_sz; - dst += txn->payload_sz; - included_txn_cnt++; - } - header->txn_cnt = included_txn_cnt; - - /* We always have credits to publish here, because we have a burst - value of 3 credits, and at most we will publish_tick() once and - then publish_became_leader() once, leaving one credit here to - publish the microblock. */ - ulong tspub = (ulong)fd_frag_meta_ts_comp( fd_tickcount() ); - ulong sz = sizeof(fd_entry_batch_meta_t)+sizeof(fd_entry_batch_header_t)+payload_sz; - ulong new_sig = fd_disco_poh_sig( slot, POH_PKT_TYPE_MICROBLOCK, 0UL ); - fd_stem_publish( stem, poh->shred_out->idx, new_sig, poh->shred_out->chunk, sz, 0UL, 0UL, tspub ); - poh->shred_out->chunk = fd_dcache_compact_next( poh->shred_out->chunk, sz, poh->shred_out->chunk0, poh->shred_out->wmark ); -} - -void -fd_poh1_mixin( fd_poh_t * poh, - fd_stem_context_t * stem, - ulong slot, - uchar const * hash, - ulong txn_cnt, - fd_txn_p_t const * txns, - fd_leader_txn_timing_rec_t const * timing ) { - if( FD_UNLIKELY( slot!=poh->next_leader_slot || slot!=poh->slot ) ) { - FD_LOG_ERR(( "packed too early or late slot=%lu, current_slot=%lu", slot, poh->slot )); - } - if( FD_UNLIKELY( (poh->hashcnt%poh->hashcnt_per_tick)==(poh->hashcnt_per_tick-1UL) ) ) FD_LOG_CRIT(( "a tick will be skipped due to hashcnt %lu hashcnt_per_tick %lu", poh->hashcnt, poh->hashcnt_per_tick )); - - FD_TEST( poh->state==STATE_LEADER ); - FD_TEST( poh->microblocks_lower_boundmax_microblocks_per_slot ); - poh->microblocks_lower_bound += 1UL; - - ulong executed_txn_cnt = 0UL; - for( ulong i=0UL; itiming_tables ) ) { - fd_leader_txn_timing_table_t * table = fd_leader_txn_timing_table( poh->timing_tables, poh->timing_table_idx, poh->timing_table_max ); - long poh_mixed_ticks = fd_tickcount(); - for( ulong i=0UL; icnt>=poh->timing_table_max ) ) break; - - fd_leader_txn_timing_rec_t * rec = &table->rec[ table->cnt++ ]; - *rec = *timing; - rec->received_ns = txns[ i ].first_seen_nanos; - rec->poh_mixed_ticks = poh_mixed_ticks; - } - } - - uchar data[ 64 ]; - fd_memcpy( data, poh->hash, 32UL ); - fd_memcpy( data+32UL, hash, 32UL ); - fd_sha256_hash( data, 64UL, poh->hash ); - - poh->hashcnt++; - FD_TEST( poh->hashcnt>poh->last_hashcnt ); - ulong hashcnt_delta = poh->hashcnt - poh->last_hashcnt; - - /* The hashing loop above will never leave us exactly one away from - crossing a tick boundary, so this increment will never cause the - current tick (or the slot) to change, except in low power mode - for development, in which case we do need to register the tick - with the leader bank. We don't need to publish the tick since - sending the microblock below is the publishing action. */ - if( FD_UNLIKELY( !(poh->hashcnt%poh->hashcnt_per_slot ) ) ) { - poh->slot++; - poh->hashcnt = 0UL; - } - - poh->last_slot = poh->slot; - poh->last_hashcnt = poh->hashcnt; - - if( FD_UNLIKELY( !(poh->hashcnt%poh->hashcnt_per_tick ) ) ) { - if( FD_UNLIKELY( poh->slot>poh->next_leader_slot ) ) { - /* We ticked while leader and are no longer leader... transition - the state machine. */ - transition_to_follower( poh, stem, 1 ); - poh->state = STATE_WAITING_FOR_RESET; - } - } - - publish_microblock( poh, stem, slot, hashcnt_delta, txn_cnt, txns ); -} diff --git a/src/discof/poh/fd_poh.h b/src/discof/poh/fd_poh.h index a1bbed1dd82..9e0a2f45fc6 100644 --- a/src/discof/poh/fd_poh.h +++ b/src/discof/poh/fd_poh.h @@ -1,326 +1,7 @@ #ifndef HEADER_fd_src_discof_poh_fd_poh_h #define HEADER_fd_src_discof_poh_fd_poh_h -/* Let's say there was a computer, the "leader" computer, that acted as - a bank. Users could send it messages saying they wanted to deposit - money, or transfer it to someone else. - - That's how, for example, Bank of America works but there are problems - with it. One simple problem is: the bank can set your balance to - zero if they don't like you. - - You could try to fix this by having the bank periodically publish the - list of all account balances and transactions. If the customers add - unforgeable signatures to their deposit slips and transfers, then - the bank cannot zero a balance without it being obvious to everyone. - - There's still problems. The bank can't lie about your balance now or - take your money, but it can just not accept deposits on your behalf - by ignoring you. - - You could fix this by getting a few independent banks together, lets - say Bank of America, Bank of England, and Westpac, and having them - rotate who operates the leader computer periodically. If one bank - ignores your deposits, you can just wait and send them to the next - one. - - This is Solana. - - There's still problems of course but they are largely technical. How - do the banks agree who is leader? How do you recover if a leader - misbehaves? How do customers verify the transactions aren't forged? - How do banks receive and publish and verify each others work quickly? - These are the main technical innovations that enable Solana to work - well. - - What about Proof of History? - - One particular niche problem is about the leader schedule. When the - leader computer is moving from one bank to another, the new bank must - wait for the old bank to say it's done and provide a final list of - balances that it can start working off of. But: what if the computer - at the old bank crashes and never says its done? - - Does the new leader just take over at some point? What if the new - leader is malicious, and says the past thousand leaders crashed, and - there have been no transactions for days? How do you check? - - This is what Proof of History solves. Each bank in the network must - constantly do a lot of busywork (compute hashes), even when it is not - leader. - - If the prior thousand leaders crashed, and no transactions happened - in an hour, the new leader would have to show they did about an hour - of busywork for everyone else to believe them. - - A better name for this is proof of skipping. If a leader is skipping - slots (building off of a slot that is not the direct parent), it must - prove that it waited a good amount of time to do so. - - It's not a perfect solution. For one thing, some banks have really - fast computers and can compute a lot of busywork in a short amount of - time, allowing them to skip prior slot(s) anyway. But: there is a - social component that prevents validators from skipping the prior - leader slot. It is easy to detect when this happens and the network - could respond by ignoring their votes or stake. - - You could come up with other schemes: for example, the network could - just use wall clock time. If a new leader publishes a block without - waiting 400 milliseconds for the prior slot to complete, then there - is no "proof of skipping" and the nodes ignore the slot. - - These schemes have a problem in that they are not deterministic - across the network (different computers have different clocks), and - so they will cause frequent forks which are very expensive to - resolve. Even though the proof of history scheme is not perfect, - it is better than any alternative which is not deterministic. - - With all that background, we can now describe at a high level what - this PoH tile actually does, - - (1) Whenever any other leader in the network finishes a slot, and - the slot is determined to be the best one to build off of, this - tile gets "reset" onto that block, the so called "reset slot". - - (2) The tile is constantly doing busy work, hash(hash(hash(...))) on - top of the last reset slot, even when it is not leader. - - (3) When the tile becomes leader, it continues hashing from where it - was. Typically, the prior leader finishes their slot, so the - reset slot will be the parent one, and this tile only publishes - hashes for its own slot. But if prior slots were skipped, then - there might be a whole chain already waiting. - - That's pretty much it. When we are leader, in addition to doing - busywork, we publish ticks and microblocks to the shred tile. A - microblock is a non-empty group of transactions whose hashes are - mixed-in to the chain, while a tick is a periodic stamp of the - current hash, with no transactions (nothing mixed in). We need - to send both to the shred tile, as ticks are important for other - validators to verify in parallel. - - As well, the tile should never become leader for a slot that it has - published anything for, otherwise it may create a duplicate block. - - Some particularly common misunderstandings: - - - PoH is critical to security. - - This largely isn't true. The target hash rate of the network is - so slow (1 hash per 500 nanoseconds) that a malicious leader can - easily catch up if they start from an old hash, and the only - practical attack prevented is the proof of skipping. Most of the - long range attacks in the Solana whitepaper are not relevant. - - - PoH keeps passage of time. - - This is also not true. The way the network keeps time so it can - decide who is leader is that, each leader uses their operating - system clock to time 400 milliseconds and publishes their block - when this timer expires. - - If a leader just hashed as fast as they could, they could publish - a block in tens of milliseconds, and the rest of the network - would happily accept it. This is why the Solana "clock" as - determined by PoH is not accurate and drifts over time. - - - PoH prevents transaction reordering by the leader. - - The leader can, in theory, wait until the very end of their - leader slot to publish anything at all to the network. They can, - in particular, hold all received transactions for 400 - milliseconds and then reorder and publish some right at the end - to advantage certain transactions. - - You might be wondering... if all the PoH chain is helping us do is - prove that slots were skipped correctly, why do we need to "mix in" - transactions to the hash value? Or do anything at all for slots - where we don't skip the prior slot? - - It's a good question, and the answer is that this behavior is not - necessary. An ideal implementation of PoH have no concept of ticks - or mixins, and would not be part of the TPU pipeline at all. - Instead, there would be a simple field "skip_proof" on the last - shred we send for a slot, the hash(hash(...)) value. This field - would only be filled in (and only verified by replayers) in cases - where the slot actually skipped a parent. - - Then what is the "clock? In Solana, time is constructed as follows: - - HASHES - - The base unit of time is a hash. Hereafter, any values whose - units are in hashes are called a "hashcnt" to distinguish them - from actual hashed values. - - Agave generally defines a constant duration for each tick - (see below) and then varies the number of hashcnt per tick, but - as we consider the hashcnt the base unit of time, Firedancer and - this PoH implementation defines everything in terms of hashcnt - duration instead. - - In mainnet-beta, testnet, and devnet the hashcnt ticks over - (increments) every 100 nanoseconds. The hashcnt rate is - specified as 500 nanoseconds according to the genesis, but there - are several features which increase the number of hashes per - tick while keeping tick duration constant, which make the time - per hashcnt lower. These features up to and including the - `update_hashes_per_tick6` feature are activated on mainnet-beta, - devnet, and testnet, and are described in the TICKS section - below. - - Other chains and development environments might have a different - hashcnt rate in the genesis, or they might not have activated - the features which increase the rate yet, which we also support. - - In practice, although each validator follows a hashcnt rate of - 100 nanoseconds, the overall observed hashcnt rate of the - network is a little slower than once every 100 nanoseconds, - mostly because there are gaps and clock synchronization issues - during handoff between leaders. This is referred to as clock - drift. - - TICKS - - The leader needs to periodically checkpoint the hash value - associated with a given hashcnt so that they can publish it to - other nodes for verification. - - On mainnet-beta, testnet, and devnet this occurs once every - 62,500 hashcnts, or approximately once every 6.4 microseconds. - This value is determined at genesis time, and according to the - features below, and could be different in development - environments or on other chains which we support. - - Due to protocol limitations, when mixing in transactions to the - proof-of-history chain, it cannot occur on a tick boundary (but - can occur at any other hashcnt). - - Ticks exist mainly so that verification can happen in parallel. - A verifier computer, rather than needing to do hash(hash(...)) - all in sequence to verify a proof-of-history chain, can do, - - Core 0: hash(hash(...)) - Core 1: hash(hash(...)) - Core 2: hash(hash(...)) - Core 3: hash(hash(...)) - ... - - Between each pair of tick boundaries. - - Solana sometimes calls the current tick the "tick height", - although it makes more sense to think of it as a counter from - zero, it's just the number of ticks since the genesis hash. - - There is a set of features which increase the number of hashcnts - per tick. These are all deployed on mainnet-beta, devnet, and - testnet. - - name: update_hashes_per_tick - id: 3uFHb9oKdGfgZGJK9EHaAXN4USvnQtAFC13Fh5gGFS5B - hashes per tick: 12,500 - hashcnt duration: 500 nanos - - name: update_hashes_per_tick2 - id: EWme9uFqfy1ikK1jhJs8fM5hxWnK336QJpbscNtizkTU - hashes per tick: 17,500 - hashcnt duration: 357.142857143 nanos - - name: update_hashes_per_tick3 - id: 8C8MCtsab5SsfammbzvYz65HHauuUYdbY2DZ4sznH6h5 - hashes per tick: 27,500 - hashcnt duration: 227.272727273 nanos - - name: update_hashes_per_tick4 - id: 8We4E7DPwF2WfAN8tRTtWQNhi98B99Qpuj7JoZ3Aikgg - hashes per tick: 47,500 - hashcnt duration: 131.578947368 nanos - - name: update_hashes_per_tick5 - id: BsKLKAn1WM4HVhPRDsjosmqSg2J8Tq5xP2s2daDS6Ni4 - hashes per tick: 57,500 - hashcnt duration: 108.695652174 nanos - - name: update_hashes_per_tick6 - id: FKu1qYwLQSiehz644H6Si65U5ZQ2cp9GxsyFUfYcuADv - hashes per tick: 62,500 - hashcnt duration: 100 nanos - - In development environments, there is a way to configure the - hashcnt per tick to be "none" during genesis, for a so-called - "low power" tick producer. The idea is not to spin cores during - development. This is equivalent to setting the hashcnt per tick - to be 1, and increasing the hashcnt duration to the desired tick - duration. - - SLOTS - - Each leader needs to be leader for a fixed amount of time, which - is called a slot. During a slot, a leader has an opportunity to - receive transactions and produce a block for the network, - although they may miss ("skip") the slot if they are offline or - not behaving. - - In mainnet-beta, testnet, and devnet a slot is 64 ticks, or - 4,000,000 hashcnts, or approximately 400 milliseconds. - - Due to the way the leader schedule is constructed, each leader - is always given at least four (4) consecutive slots in the - schedule. This means when becoming leader you will be leader - for at least 4 slots, or 1.6 seconds. - - It is rare, although can happen that a leader gets more than 4 - consecutive slots (eg, 8, or 12), if they are lucky with the - leader schedule generation. - - The number of ticks in a slot is fixed at genesis time, and - could be different for development or other chains, which we - support. There is nothing special about 4 leader slots in a - row, and this might be changed in future, and the proof of - history makes no assumptions that this is the case. - - EPOCHS - - Infrequently, the network needs to do certain housekeeping, - mainly things like collecting rent and deciding on the leader - schedule. The length of an epoch is fixed on mainnet-beta, - devnet and testnet at 420,000 slots, or around ~2 (1.94) days. - This value is fixed at genesis time, and could be different for - other chains including development, which we support. Typically - in development, epochs are every 8,192 slots, or around ~1 hour - (54.61 minutes), although it depends on the number of ticks per - slot and the target hashcnt rate of the genesis as well. - - In development, epochs need not be a fixed length either. There - is a "warmup" option, where epochs start short and grow, which - is useful for quickly warming up stake during development. - - The epoch is important because it is the only time the leader - schedule is updated. The leader schedule is a list of which - leader is leader for which slot, and is generated by a special - algorithm that is deterministic and known to all nodes. - - The leader schedule is computed one epoch in advance, so that - at slot T, we always know who will be leader up until the end - of slot T+EPOCH_LENGTH. Specifically, the leader schedule for - epoch N is computed during the epoch boundary crossing from - N-2 to N-1. For mainnet-beta, the slots per epoch is fixed and - will always be 420,000. */ - -#include "../../disco/fd_disco_base.h" #include "../../disco/pack/fd_pack.h" -#include "../../disco/stem/fd_stem.h" -#include "../../disco/fd_clock_tile.h" -#include "../../util/fd_util_base.h" -#include "../../ballet/sha256/fd_sha256.h" - -typedef struct fd_done_packing fd_done_packing_t; - -/* FD_POH_ALIGN is the alignment needed for a memory region to hold a - fd_poh_t. It is a positive integer power of 2. */ -#define FD_POH_ALIGN (128UL) - -#define FD_POH_MAGIC (0xF17EDA2CE580A000) /* FIREDANCE POH V0 */ /* The maximum number of microblocks pack may put in a slot: one per non-tick hash position (one per tick in low power mode), less the @@ -433,196 +114,4 @@ struct fd_poh_out_private { typedef struct fd_poh_out_private fd_poh_out_t; -struct __attribute__((aligned(FD_POH_ALIGN))) fd_poh_private { - int state; - int wfs_paused; /* 1 if wait_for_supermajority is active; PoH should not advance */ - - /* Static configuration determined at genesis creation time. See - long comment above for more information. */ - ulong tick_duration_ns; - ulong hashcnt_per_tick; - ulong ticks_per_slot; - - /* Derived from the above configuration, but we precompute it. */ - double slot_duration_ns; - double hashcnt_duration_ns; - ulong hashcnt_per_slot; - - /* The maximum number of real microblocks that the pack tile is - allowed to publish in each slot. - - While we are leader, PoH internally treats this limit as having - one extra phantom "microblock" reserved for the done_packing - message, so that PoH does not finish the slot before pack - confirms it is done. Pack itself is configured with the - un-inflated limit and never publishes more than this many real - microblocks per slot. */ - ulong max_microblocks_per_slot; - - /* The block id of the completed block. */ - uchar completed_block_id[ 32UL ]; - - /* The slot we were reset on (what we are building on top of). */ - ulong reset_slot; - long reset_slot_start_ns; - - /* The current slot and hashcnt within that slot of the proof of - history, including hashes we have been producing in the background - while waiting for our next leader slot. */ - ulong slot; - ulong hashcnt; - - ulong next_leader_slot; - long leader_slot_start_ns; - - /* When we send a microblock on to the shred tile, we need to tell - it how many hashes there have been since the last microblock, so - this tracks the hashcnt of the last published microblock. - - If we are skipping slots prior to our leader slot, the last_slot - will be quite old, and potentially much larger than the number of - hashcnts in one slot. */ - ulong last_slot; - ulong last_hashcnt; - - /* The PoH tile must never drop microblocks that get committed by the - bank, so it needs to always be able to mixin a microblock hash. - Mixing in requires incrementing the hashcnt, so we need to ensure - at all times that there is enough hascnts left in the slot to - mixin whatever future microblocks pack might produce for it. - - This value tracks that. At any time, max_microblocks_per_slot - - microblocks_lower_bound is an upper bound on the maximum number - of microblocks that might still be received in this slot. */ - ulong microblocks_lower_bound; - - uchar __attribute__((aligned(32UL))) reset_hash[ 32 ]; - uchar __attribute__((aligned(32UL))) hash[ 32 ]; - - /* When we are not leader, we need to save the hashes that were - produced in case the prior leader skips. If they skip, we will - replay these skipped hashes into our next leader bank so that - the slot hashes sysvar can be updated correctly, and also publish - them to peer nodes as part of our outgoing shreds. */ - uchar skipped_tick_hashes[ MAX_SKIPPED_TICKS ][ 32 ]; - - fd_sha256_t * sha256; - - fd_clock_tile_t clock[ 1 ]; - - fd_poh_out_t shred_out[ 1 ]; - fd_poh_out_t replay_out[ 1 ]; - - /* Summary for the current leader slot, captured from the pack - done_packing message and forwarded to replay in the slot-ended - message. */ - ulong pack_microblock_count; - ulong pack_block_cost; - ulong pack_vote_cost; - ulong pack_data_bytes; - ulong pack_bundle_txn_count; - int pack_end_reason; - long pack_start_ns; - long pack_end_ns; - - /* Shared per-transaction timing tables or NULL when the topology does - not provide them; timing_table_max records per table. */ - fd_leader_txn_timing_table_t * timing_tables; - ulong timing_table_idx; - ulong timing_table_max; - - ulong magic; -}; - -typedef struct fd_poh_private fd_poh_t; - -FD_PROTOTYPES_BEGIN - -FD_FN_CONST ulong -fd_poh_align( void ); - -FD_FN_CONST ulong -fd_poh_footprint( void ); - -void * -fd_poh_new( void * shmem ); - -fd_poh_t * -fd_poh_join( void * shpoh, - fd_poh_out_t * shred_out, - fd_poh_out_t * replay_out, - fd_leader_txn_timing_table_t * timing_tables, - ulong timing_table_max ); /* records per table, config->limits.max_txn_per_slot */ - -void -fd_poh_reset( fd_poh_t * poh, - fd_stem_context_t * stem, - long timestamp, - ulong hashcnt_per_tick, - ulong ticks_per_slot, - ulong tick_duration_ns, - ulong completed_slot, - uchar const * completed_blockhash, - ulong next_leader_slot, - ulong max_microblocks_in_slot, - uchar const * completed_block_id ); - -int -fd_poh_have_leader_bank( fd_poh_t const * poh ); - -int -fd_poh_hashing_to_leader_slot( fd_poh_t const * poh ); - -long -fd_poh_next_deadline( fd_poh_t const * poh ); - -int -fd_poh_must_tick( fd_poh_t const * poh ); - -int -fd_poh_must_publish_skipped_tick( fd_poh_t const * poh ); - -void -fd_poh_begin_leader( fd_poh_t * poh, - ulong slot, - ulong hashcnt_per_tick, - ulong ticks_per_slot, - ulong tick_duration_ns, - ulong max_microblocks_in_slot, - long slot_start_ns ); - -void -fd_poh_done_packing( fd_poh_t * poh, - fd_stem_context_t * stem, - fd_done_packing_t const * done_packing ); - -void -fd_poh_advance( fd_poh_t * poh, - fd_stem_context_t * stem, - int * opt_poll_in, - int * charge_busy ); - -void -fd_poh1_mixin( fd_poh_t * poh, - fd_stem_context_t * stem, - ulong slot, - uchar const * hash, - ulong txn_cnt, - fd_txn_p_t const * txns, - fd_leader_txn_timing_rec_t const * timing ); - -void -fd_poh_wfs_done( fd_poh_t * poh ); - -/* fd_poh_update_max_microblocks: Tighten the upper bound on - max_microblocks_per_slot using the latest bound from pack. - new_max is the un-inflated bound (pack's view). PoH inflates - by +1 which causes it to wait for pack's slot_done message before - finishing a slot. */ -void -fd_poh_update_max_microblocks( fd_poh_t * poh, - ulong new_max ); - -FD_PROTOTYPES_END - #endif /* HEADER_fd_src_discof_poh_fd_poh_h */ diff --git a/src/discof/poh/fd_poh_tile.c b/src/discof/poh/fd_poh_tile.c deleted file mode 100644 index 11012fdb56f..00000000000 --- a/src/discof/poh/fd_poh_tile.c +++ /dev/null @@ -1,392 +0,0 @@ -#include "fd_poh.h" -#include "fd_poh_tile.h" -#include "../replay/fd_replay_tile.h" -#include "../../util/pod/fd_pod.h" -#include "../../disco/tiles.h" -#include "../../disco/fd_clock_tile.h" -#include "../../discof/fd_startup.h" -#include -#include -#include "generated/fd_poh_tile_seccomp.h" - -#define IN_KIND_REPLAY (0) -#define IN_KIND_PACK (1) -#define IN_KIND_EXECLE (2) - -struct fd_poh_in { - fd_wksp_t * mem; - ulong chunk0; - ulong wmark; - ulong mtu; -}; - -typedef struct fd_poh_in fd_poh_in_t; - -struct fd_poh_tile { - fd_poh_t poh[1]; - - /* There's a race condition ... let's say two execles A and B, execle - A processes some transactions, then releases the account locks, and - sends the microblock to PoH to be stamped. Pack now re-packs the - same accounts with a new microblock, sends to execle B, execle B - executes and sends the microblock to PoH, and this all happens fast - enough that PoH picks the 2nd block to stamp before the 1st. The - accounts database changes now are misordered with respect to PoH so - replay could fail. - - To prevent this race, we order all microblocks and only process - them in PoH in the order they are produced by pack. This is a - little bit over-strict, we just need to ensure that microblocks - with conflicting accounts execute in order, but this is easiest to - implement for now. */ - uint expect_pack_idx; - - ulong in_cnt; - ulong idle_cnt; - - fd_startup_gate_t startup_gate[1]; - - int in_kind[ 64 ]; - fd_poh_in_t in[ 64 ]; - - fd_poh_out_t shred_out[ 1 ]; - fd_poh_out_t replay_out[ 1 ]; -}; - -typedef struct fd_poh_tile fd_poh_tile_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_poh_tile_t), sizeof(fd_poh_tile_t) ); - return FD_LAYOUT_FINI( l, scratch_align() ); -} - -static inline void -during_housekeeping( fd_poh_tile_t * ctx ) { - if( FD_UNLIKELY( fd_clock_tile_recal_due( ctx->poh->clock ) ) ) { - fd_clock_tile_recal( ctx->poh->clock ); - } -} - -static inline void -after_credit( fd_poh_tile_t * ctx, - fd_stem_context_t * stem, - int * opt_poll_in, - int * charge_busy ) { - if( FD_UNLIKELY( !fd_startup_gate_idle( ctx->startup_gate ) ) ) return; - - ctx->idle_cnt++; - if( FD_LIKELY( ctx->idle_cnt>=2UL*ctx->in_cnt || fd_poh_must_tick( ctx->poh ) || fd_poh_must_publish_skipped_tick( ctx->poh ) ) ) { - /* We would like to fully drain input links to the best of our - knowledge, before we spend cycles on hashing. That is, we would - like to assert that all input links have stayed empty since the - last time we polled. Given an arbitrary input link L, the worst - case is when L is at idx 0 in the input link shuffle the last - time we polled a frag from it, but then link L ends up at idx - in_cnt-1 in the subsequent input link shuffle. So strictly - speaking we will need to have observed 2*in_cnt-1 consecutive - empty in links to be able to assert that link L has been empty - since the last time we polled it. - - Except that when we are leader and the hashcnt is right before a - tick boundary, poh must advance to the tick boundary and produce - the tick. Otherwise, a tick will be skipped if a microblock - mixin happens. Additionally, when there are pending skipped - ticks to be published, we should do that before processing any - incoming microblocks. */ - fd_poh_advance( ctx->poh, stem, opt_poll_in, charge_busy ); - ctx->idle_cnt = 0UL; - } -} - -static inline long -next_deadline( fd_poh_tile_t * ctx ) { - long next = fd_poh_next_deadline( ctx->poh ); - if( FD_LIKELY( next==LONG_MAX ) ) return LONG_MAX; - if( FD_UNLIKELY( next<=0L ) ) return 0L; - return fd_clock_tile_wallclock_to_tickcount( ctx->poh->clock, next ); -} - -/* .... - - 1. replay -> (pack, poh) ... start packing for slot - 2. if slot in progress -> pack -> poh (abandon_packing) for old slot - 3. pack free to start packing - 4. if poh slot in progress, refuse replay frag ... until see abandon_packing - 5. poh must process pack frags in order - 6. when poh sees done_packing/abandon_packing, return poh -> replay saying execle unused now */ - -static int -before_frag( fd_poh_tile_t * ctx, - ulong in_idx, - ulong seq FD_PARAM_UNUSED, - ulong sig ) { - if( FD_LIKELY( ctx->in_kind[ in_idx ]==IN_KIND_REPLAY ) ) { - int filter = sig!=REPLAY_SIG_RESET && sig!=REPLAY_SIG_BECAME_LEADER && sig!=REPLAY_SIG_WFS_DONE; - if( FD_LIKELY( filter && !fd_poh_have_leader_bank( ctx->poh ) ) ) ctx->idle_cnt = 0UL; - return filter; - } - return 0; -} - -static inline int -returnable_frag( fd_poh_tile_t * ctx, - ulong in_idx, - ulong seq, - ulong sig, - ulong chunk, - ulong sz, - ulong ctl, - ulong tsorig, - ulong tspub, - fd_stem_context_t * stem ) { - (void)seq; - (void)ctl; - (void)tsorig; - (void)tspub; - - fd_startup_gate_busy( ctx->startup_gate ); - - /* DONE_DRAINING is only consumed by monitoring (the GUI). */ - if( FD_UNLIKELY( sig==FD_PACK_MSG_DONE_DRAINING && ctx->in_kind[ in_idx ]==IN_KIND_PACK ) ) { - ctx->idle_cnt = 0UL; - return 0; - } - - /* Pack periodically publishes a tighter microblock bound over the - pack_poh link. */ - if( FD_UNLIKELY( sig==FD_PACK_MSG_REDUCE_MB_BOUND && ctx->in_kind[ in_idx ]==IN_KIND_PACK ) ) { - ctx->idle_cnt = 0UL; - if( FD_UNLIKELY( !fd_poh_have_leader_bank( ctx->poh ) ) ) return 0; /* must have become leader first */ - FD_TEST( sz==sizeof(ulong) ); - ulong const * new_max = fd_chunk_to_laddr_const( ctx->in[ in_idx ].mem, chunk ); - fd_poh_update_max_microblocks( ctx->poh, *new_max ); - return 0; - } - - if( FD_UNLIKELY( sig==REPLAY_SIG_WFS_DONE && ctx->in_kind[ in_idx ]==IN_KIND_REPLAY ) ) { - fd_poh_wfs_done( ctx->poh ); - ctx->idle_cnt = 0UL; - return 0; - } - - if( FD_UNLIKELY( chunkin[ in_idx ].chunk0 || chunk>ctx->in[ in_idx ].wmark || sz>ctx->in[ in_idx ].mtu ) ) - FD_LOG_ERR(( "chunk %lu %lu corrupt, not in range [%lu,%lu]", chunk, sz, ctx->in[ in_idx ].chunk0, ctx->in[ in_idx ].wmark )); - - /* There's a race condition where we might receive microblocks from - execles (or pack's done_packing, when pack ends the block on a - reset) before we have learned what the leader bank is from replay - (the become_leader message makes it from replay->pack->execle->poh) - before it just makes it from replay->poh. This is rare but - violates invariants in poh, so we simply do not process any - transactions for mixin until we have learned what the leader bank - is. become_leader always precedes the reset on the replay link, so - these holds always drain. */ - if( FD_UNLIKELY( ( ctx->in_kind[ in_idx ]==IN_KIND_EXECLE || ctx->in_kind[ in_idx ]==IN_KIND_PACK ) && !fd_poh_have_leader_bank( ctx->poh ) ) ) return 1; - - if( FD_UNLIKELY( ctx->in_kind[ in_idx ]==IN_KIND_REPLAY && fd_poh_have_leader_bank( ctx->poh ) ) ) return 1; - /* If prior leaders skipped, it might happen that replay tells us to - become leader, but poh is still hashing through the skipped slots - and could not yet mixin any microblocks. In this case, we hold - the microblocks and do not mixin them yet until we have hashed - through to the actual leader slot. - - It might actually be allowed by the protocol to mixin earlier, but - that really doesn't seem like a good idea. - - It's fine to block pack/execles on hashing here, because they we - are going to have the wait for the full block to timeout once it - starts. */ - if( FD_UNLIKELY( ( ctx->in_kind[ in_idx ]==IN_KIND_EXECLE || ctx->in_kind[ in_idx ]==IN_KIND_PACK ) && fd_poh_hashing_to_leader_slot( ctx->poh ) ) ) return 1; - /* If prior leaders skipped, it might happen that replay tells us to - become leader, but we haven't published the skipped ticks yet. - - Skipped ticks need to be published before any microblocks, so we - hold the microblocks and do not mixin them yet until we have - published any skipped ticks. - - It's fine to block pack/execles here, because the skipped ticks - will be published in the immediate after_credit iterations. */ - if( FD_UNLIKELY( ctx->in_kind[ in_idx ]==IN_KIND_EXECLE && fd_poh_must_publish_skipped_tick( ctx->poh ) ) ) return 1; - if( FD_LIKELY( ctx->in_kind[ in_idx ]==IN_KIND_EXECLE || ctx->in_kind[ in_idx ]==IN_KIND_PACK ) ) { - uint pack_idx = (uint)fd_disco_execle_sig_pack_idx( sig ); - if( FD_UNLIKELY( ((int)(pack_idx-ctx->expect_pack_idx))<0L ) ) FD_LOG_ERR(( "received out of order pack_idx %u (expecting %u)", pack_idx, ctx->expect_pack_idx )); - if( FD_UNLIKELY( pack_idx!=ctx->expect_pack_idx ) ) return 1; - ctx->expect_pack_idx++; - } - - switch( ctx->in_kind[ in_idx ] ) { - case IN_KIND_PACK: { - fd_done_packing_t const * done_packing = fd_chunk_to_laddr_const( ctx->in[ in_idx ].mem, chunk ); - fd_poh_done_packing( ctx->poh, stem, done_packing ); - break; - } - case IN_KIND_REPLAY: { - if( FD_LIKELY( sig==REPLAY_SIG_BECAME_LEADER ) ) { - fd_became_leader_t const * became_leader = fd_chunk_to_laddr_const( ctx->in[ in_idx ].mem, chunk ); - fd_poh_begin_leader( ctx->poh, became_leader->slot, became_leader->hashcnt_per_tick, became_leader->ticks_per_slot, became_leader->tick_duration_ns, became_leader->max_microblocks_in_slot, became_leader->slot_start_ns ); - } else if( sig==REPLAY_SIG_RESET ) { - fd_poh_reset_t const * reset = fd_chunk_to_laddr_const( ctx->in[ in_idx ].mem, chunk ); - fd_poh_reset( ctx->poh, stem, reset->timestamp, reset->hashcnt_per_tick, reset->ticks_per_slot, reset->tick_duration_ns, reset->completed_slot, reset->completed_blockhash, reset->next_leader_slot, reset->max_microblocks_in_slot, reset->completed_cmr ); - ctx->poh->wfs_paused = reset->wfs_paused; - } - break; - } - case IN_KIND_EXECLE: { - ulong target_slot = fd_disco_execle_sig_slot( sig ); - FD_TEST( sz>=sizeof(fd_microblock_trailer_t) && (sz-sizeof(fd_microblock_trailer_t))%sizeof(fd_txn_p_t)==0UL ); - ulong txn_cnt = (sz-sizeof(fd_microblock_trailer_t))/sizeof(fd_txn_p_t); - fd_txn_p_t const * txns = fd_chunk_to_laddr_const( ctx->in[ in_idx ].mem, chunk ); - fd_microblock_trailer_t const * trailer = fd_type_pun_const( (uchar const*)txns+sz-sizeof(fd_microblock_trailer_t) ); - - fd_leader_txn_timing_rec_t timing = { - .dispatched_ticks = trailer->exec_start_ticks, - .replayed_ticks = trailer->exec_end_ticks, - }; - fd_poh1_mixin( ctx->poh, stem, target_slot, trailer->hash, txn_cnt, txns, &timing ); - break; - } - default: { - FD_LOG_ERR(( "unexpected input kind %d", ctx->in_kind[ in_idx ] )); - break; - } - } - - ctx->idle_cnt = 0UL; - return 0; -} - -static inline fd_poh_out_t -out1( fd_topo_t const * topo, - fd_topo_tile_t const * tile, - char const * name ) { - ulong idx = ULONG_MAX; - - for( ulong i=0UL; iout_cnt; i++ ) { - fd_topo_link_t const * link = &topo->links[ tile->out_link_id[ i ] ]; - if( !strcmp( link->name, name ) ) { - if( FD_UNLIKELY( idx!=ULONG_MAX ) ) FD_LOG_ERR(( "tile %s:%lu had multiple output links named %s but expected one", tile->name, tile->kind_id, name )); - idx = i; - } - } - - if( FD_UNLIKELY( idx==ULONG_MAX ) ) FD_LOG_ERR(( "tile %s:%lu had no output link named %s", tile->name, tile->kind_id, name )); - - void * mem = topo->workspaces[ topo->objs[ topo->links[ tile->out_link_id[ idx ] ].dcache_obj_id ].wksp_id ].wksp; - ulong chunk0 = fd_dcache_compact_chunk0( mem, topo->links[ tile->out_link_id[ idx ] ].dcache ); - ulong wmark = fd_dcache_compact_wmark ( mem, topo->links[ tile->out_link_id[ idx ] ].dcache, topo->links[ tile->out_link_id[ idx ] ].mtu ); - - return (fd_poh_out_t){ .idx = idx, .mem = mem, .chunk0 = chunk0, .wmark = wmark, .chunk = chunk0 }; -} - -static void -unprivileged_init( fd_topo_t const * topo, - fd_topo_tile_t const * tile ) { - void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id ); - - FD_SCRATCH_ALLOC_INIT( l, scratch ); - fd_poh_tile_t * ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof( fd_poh_tile_t ), sizeof( fd_poh_tile_t ) ); - - ctx->expect_pack_idx = 0UL; - - ctx->in_cnt = tile->in_cnt; - ctx->idle_cnt = 0UL; - - FD_CHECK_ERR( tile->in_cnt<=sizeof(ctx->in)/sizeof(ctx->in[0]), "too many input links" ); - - for( ulong i=0UL; iin_cnt; i++ ) { - fd_topo_link_t const * link = &topo->links[ tile->in_link_id[ i ] ]; - fd_topo_wksp_t const * link_wksp = &topo->workspaces[ topo->objs[ link->dcache_obj_id ].wksp_id ]; - - ctx->in[ i ].mem = link_wksp->wksp; - ctx->in[ i ].chunk0 = fd_dcache_compact_chunk0( ctx->in[ i ].mem, link->dcache ); - ctx->in[ i ].wmark = fd_dcache_compact_wmark ( ctx->in[ i ].mem, link->dcache, link->mtu ); - ctx->in[ i ].mtu = link->mtu; - - if( !strcmp( link->name, "replay_slot" ) ) ctx->in_kind[ i ] = IN_KIND_REPLAY; - else if( !strcmp( link->name, "pack_poh" ) ) ctx->in_kind[ i ] = IN_KIND_PACK; - else if( !strcmp( link->name, "execle_poh" ) ) ctx->in_kind[ i ] = IN_KIND_EXECLE; - else FD_LOG_ERR(( "unexpected input link name %s", link->name )); - } - - *ctx->shred_out = out1( topo, tile, "poh_shred" ); - *ctx->replay_out = out1( topo, tile, "poh_replay" ); - - void * timing_tables = NULL; - ulong ldr_tt_obj_id = fd_pod_query_ulong( topo->props, "ldr_tt", ULONG_MAX ); - if( FD_LIKELY( ldr_tt_obj_id!=ULONG_MAX ) ) timing_tables = fd_topo_obj_laddr( topo, ldr_tt_obj_id ); - - FD_TEST( fd_poh_join( fd_poh_new( ctx->poh ), ctx->shred_out, ctx->replay_out, timing_tables, tile->poh.max_txn_per_slot ) ); - - fd_clock_tile_init( ctx->poh->clock ); - - 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 ) )); - - fd_startup_gate_init( ctx->startup_gate, topo, tile->in_cnt ); -} - -static ulong -populate_allowed_seccomp( fd_topo_t const * topo, - fd_topo_tile_t const * tile, - ulong out_cnt, - struct sock_filter * out ) { - (void)topo; - (void)tile; - - populate_sock_filter_policy_fd_poh_tile( out_cnt, out, (uint)fd_log_private_logfile_fd() ); - return sock_filter_policy_fd_poh_tile_instr_cnt; -} - -static ulong -populate_allowed_fds( fd_topo_t const * topo, - fd_topo_tile_t const * tile, - ulong out_fds_cnt, - int * out_fds ) { - (void)topo; - (void)tile; - - if( FD_UNLIKELY( out_fds_cnt<2UL ) ) FD_LOG_ERR(( "out_fds_cnt %lu", out_fds_cnt )); - - ulong out_cnt = 0UL; - out_fds[ out_cnt++ ] = 2; /* stderr */ - if( FD_LIKELY( -1!=fd_log_private_logfile_fd() ) ) - out_fds[ out_cnt++ ] = fd_log_private_logfile_fd(); /* logfile */ - return out_cnt; -} - -/* One tick, one microblock */ -#define STEM_BURST (2UL) - -/* See explanation in fd_pack */ -#define STEM_LAZY (128L*3000L) - -#define STEM_CALLBACK_CONTEXT_TYPE fd_poh_tile_t -#define STEM_CALLBACK_CONTEXT_ALIGN alignof(fd_poh_tile_t) - -#define STEM_CALLBACK_DURING_HOUSEKEEPING during_housekeeping -#define STEM_CALLBACK_NEXT_DEADLINE next_deadline -#define STEM_CALLBACK_AFTER_CREDIT after_credit -#define STEM_CALLBACK_BEFORE_FRAG before_frag -#define STEM_CALLBACK_RETURNABLE_FRAG returnable_frag - -#include "../../disco/stem/fd_stem.c" - -fd_topo_run_tile_t fd_tile_poh = { - .name = "poh", - .populate_allowed_seccomp = populate_allowed_seccomp, - .populate_allowed_fds = populate_allowed_fds, - .scratch_align = scratch_align, - .scratch_footprint = scratch_footprint, - .privileged_init = NULL, - .unprivileged_init = unprivileged_init, - .run = stem_run, -}; diff --git a/src/discof/poh/fd_poh_tile.seccomppolicy b/src/discof/poh/fd_poh_tile.seccomppolicy deleted file mode 100644 index 1f254e0e054..00000000000 --- a/src/discof/poh/fd_poh_tile.seccomppolicy +++ /dev/null @@ -1,40 +0,0 @@ -# logfile_fd: It can be disabled by configuration, but typically tiles -# will open a log file on boot and write all messages there. -uint logfile_fd - -# logging: all log messages are written to a file and/or pipe -# -# 'WARNING' and above are written to the STDERR pipe, while all messages -# are always written to the log file. -# -# arg 0 is the file descriptor to write to. The boot process ensures -# that descriptor 2 is always STDERR. -write: (or (eq (arg 0) 2) - (eq (arg 0) logfile_fd)) - -# logging: 'WARNING' and above fsync the logfile to disk immediately -# -# arg 0 is the file descriptor to fsync. -fsync: (eq (arg 0) logfile_fd) - -# startup: sleep until snapshot loaded -clock_nanosleep: (eq (arg 0) CLOCK_REALTIME) - -# power saving: sleep when there is no work to do -# -# In the efficient scheduler mode, an idle tile parks on its sleep word -# with futex_wait until a producer has work for it or its next deadline -# passes. -# -# arg 1 is the futex op, always FUTEX_WAIT_BITSET so the timeout is an -# absolute deadline. arg 2 is the expected value of the word, always 0 -# (parked). -futex: (and (eq (arg 1) FUTEX_WAIT_BITSET) - (eq (arg 2) 0)) - -# power saving: resume an interrupted sleep -# -# A signal delivered to a parked tile interrupts its futex_wait, and the -# kernel resumes the wait with the remaining timeout via restart_syscall -# when the tile next runs. -restart_syscall diff --git a/src/discof/poh/generated/fd_poh_tile_seccomp.h b/src/discof/poh/generated/fd_poh_tile_seccomp.h deleted file mode 100644 index 53d4b050658..00000000000 --- a/src/discof/poh/generated/fd_poh_tile_seccomp.h +++ /dev/null @@ -1,108 +0,0 @@ -/* THIS FILE WAS GENERATED BY generate_filters.py. DO NOT EDIT BY HAND! */ -#ifndef HEADER_fd_src_discof_poh_generated_fd_poh_tile_seccomp_h -#define HEADER_fd_src_discof_poh_generated_fd_poh_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 -#elif defined(__riscv) && __riscv_xlen == 64 -# define ARCH_NR AUDIT_ARCH_RISCV64 -#else -# error "Target architecture is unsupported by seccomp." -#endif - -#define FD_SECCOMP_ARG_LO_OFFSET(argno) ( offsetof( struct seccomp_data, args[(argno)] ) ) -#define FD_SECCOMP_ARG_HI_OFFSET(argno) ( offsetof( struct seccomp_data, args[(argno)] ) + 4U ) - -#define FD_SECCOMP_ARG_LO(x) ((uint)(((ulong)(uint)(int)(x) ) & 0xffffffffUL)) -#define FD_SECCOMP_ARG_HI(x) ((uint)(((ulong)(x) >> 32) & 0xffffffffUL)) - -static const uint sock_filter_policy_fd_poh_tile_instr_cnt = 30; - -static void populate_sock_filter_policy_fd_poh_tile( ulong out_cnt, struct sock_filter out[ static 30 ], uint logfile_fd ) { - FD_TEST( out_cnt >= 30 ); - struct sock_filter filter[30] = { - /* validate architecture */ - 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 */ 6 ), - /* load syscall number */ - BPF_STMT( BPF_LD | BPF_W | BPF_ABS, ( offsetof( struct seccomp_data, nr ) )), - /* check write */ - BPF_JUMP( BPF_JMP | BPF_JEQ | BPF_K, SYS_write, /* check_write */ 6, 0 ), - /* check fsync */ - BPF_JUMP( BPF_JMP | BPF_JEQ | BPF_K, SYS_fsync, /* check_fsync */ 11, 0 ), - /* check clock_nanosleep */ - BPF_JUMP( BPF_JMP | BPF_JEQ | BPF_K, SYS_clock_nanosleep, /* check_clock_nanosleep */ 14, 0 ), - /* check futex */ - BPF_JUMP( BPF_JMP | BPF_JEQ | BPF_K, SYS_futex, /* check_futex */ 17, 0 ), - /* allow restart_syscall */ - BPF_JUMP( BPF_JMP | BPF_JEQ | BPF_K, SYS_restart_syscall, /* RET_ALLOW */ 1, 0 ), -// RET_KILL_PROCESS: - /* default deny */ - BPF_STMT( BPF_RET | BPF_K, SECCOMP_RET_KILL_PROCESS ), -// RET_ALLOW: - /* allow */ - BPF_STMT( BPF_RET | BPF_K, SECCOMP_RET_ALLOW ), -// check_write: - /* arg 0 low 32 bits */ - BPF_STMT( BPF_LD | BPF_W | BPF_ABS, FD_SECCOMP_ARG_LO_OFFSET(0)), - BPF_JUMP( BPF_JMP | BPF_JEQ | BPF_K, 0x00000002U, /* write_ALLOW */ 3, /* or_1 */ 0 ), -// or_1: - /* arg 0 low 32 bits */ - BPF_STMT( BPF_LD | BPF_W | BPF_ABS, FD_SECCOMP_ARG_LO_OFFSET(0)), - BPF_JUMP( BPF_JMP | BPF_JEQ | BPF_K, ((uint)(logfile_fd)), /* write_ALLOW */ 1, /* write_KILL */ 0 ), -// write_KILL: - BPF_STMT( BPF_RET | BPF_K, SECCOMP_RET_KILL_PROCESS ), -// write_ALLOW: - BPF_STMT( BPF_RET | BPF_K, SECCOMP_RET_ALLOW ), -// check_fsync: - /* arg 0 low 32 bits */ - BPF_STMT( BPF_LD | BPF_W | BPF_ABS, FD_SECCOMP_ARG_LO_OFFSET(0)), - BPF_JUMP( BPF_JMP | BPF_JEQ | BPF_K, ((uint)(logfile_fd)), /* fsync_ALLOW */ 1, /* fsync_KILL */ 0 ), -// fsync_KILL: - BPF_STMT( BPF_RET | BPF_K, SECCOMP_RET_KILL_PROCESS ), -// fsync_ALLOW: - BPF_STMT( BPF_RET | BPF_K, SECCOMP_RET_ALLOW ), -// check_clock_nanosleep: - /* arg 0 low 32 bits */ - BPF_STMT( BPF_LD | BPF_W | BPF_ABS, FD_SECCOMP_ARG_LO_OFFSET(0)), - BPF_JUMP( BPF_JMP | BPF_JEQ | BPF_K, FD_SECCOMP_ARG_LO(CLOCK_REALTIME), /* clock_nanosleep_ALLOW */ 1, /* clock_nanosleep_KILL */ 0 ), -// clock_nanosleep_KILL: - BPF_STMT( BPF_RET | BPF_K, SECCOMP_RET_KILL_PROCESS ), -// clock_nanosleep_ALLOW: - BPF_STMT( BPF_RET | BPF_K, SECCOMP_RET_ALLOW ), -// check_futex: - /* arg 1 low 32 bits */ - BPF_STMT( BPF_LD | BPF_W | BPF_ABS, FD_SECCOMP_ARG_LO_OFFSET(1)), - BPF_JUMP( BPF_JMP | BPF_JEQ | BPF_K, FD_SECCOMP_ARG_LO(FUTEX_WAIT_BITSET), /* and_2 */ 0, /* futex_KILL */ 2 ), -// and_2: - /* arg 2 low 32 bits */ - BPF_STMT( BPF_LD | BPF_W | BPF_ABS, FD_SECCOMP_ARG_LO_OFFSET(2)), - BPF_JUMP( BPF_JMP | BPF_JEQ | BPF_K, 0x00000000U, /* futex_ALLOW */ 1, /* futex_KILL */ 0 ), -// futex_KILL: - BPF_STMT( BPF_RET | BPF_K, SECCOMP_RET_KILL_PROCESS ), -// futex_ALLOW: - BPF_STMT( BPF_RET | BPF_K, SECCOMP_RET_ALLOW ), - }; - fd_memcpy( out, filter, sizeof( filter ) ); -} - -#endif /* defined(__linux__) */ - -#endif /* HEADER_fd_src_discof_poh_generated_fd_poh_tile_seccomp_h */ From 8d0a26d3c3b76ab9e0f2278f418b255ac6444d62 Mon Sep 17 00:00:00 2001 From: Richard Patel Date: Wed, 21 Oct 2026 17:09:09 +0000 Subject: [PATCH 09/13] choreo: remove Tower BFT --- book/api/metrics-generated.md | 123 - contrib/test/run_script_tests.sh | 1 - contrib/test/run_test_vectors.sh | 3 +- contrib/test/run_unit_tests.sh | 1 - src/app/shared/fd_config_json.c | 2 +- src/choreo/eqvoc/Local.mk | 7 - src/choreo/eqvoc/fd_eqvoc.c | 923 ------- src/choreo/eqvoc/fd_eqvoc.h | 195 -- src/choreo/eqvoc/fixtures/id.bin | Bin 1203 -> 0 bytes src/choreo/eqvoc/fixtures/pay1.bin | Bin 1203 -> 0 bytes src/choreo/eqvoc/fixtures/pay2.bin | Bin 1203 -> 0 bytes src/choreo/eqvoc/fuzz_eqvoc.c | 105 - src/choreo/eqvoc/test_eqvoc.c | 536 ---- src/choreo/fd_choreo.h | 2 - src/choreo/ghost/Local.mk | 6 - src/choreo/ghost/fd_ghost.c | 853 ------ src/choreo/ghost/fd_ghost.h | 427 --- src/choreo/ghost/test_ghost.c | 1010 ------- src/choreo/hfork/Local.mk | 6 - src/choreo/hfork/fd_hfork.c | 653 ----- src/choreo/hfork/fd_hfork.h | 174 -- src/choreo/hfork/test_hfork.c | 505 ---- src/choreo/tower/Local.mk | 10 +- src/choreo/tower/fd_tower.c | 1765 ------------ src/choreo/tower/fd_tower.h | 6 - src/choreo/tower/fd_tower_file.c | 166 -- src/choreo/tower/fd_tower_file.h | 62 - src/choreo/tower/fd_tower_serdes.c | 29 - src/choreo/tower/fd_tower_serdes.h | 14 - src/choreo/tower/fd_tower_stakes.c | 45 - src/choreo/tower/fd_tower_stakes.h | 117 - src/choreo/tower/fuzz_tower_file.c | 88 - src/choreo/tower/test_tower.c | 1325 --------- src/choreo/tower/test_tower_lockos.c | 321 --- src/choreo/tower/test_tower_serdes.c | 123 - src/disco/metrics/generated/fd_metrics_all.c | 120 - src/disco/metrics/generated/fd_metrics_all.h | 3 +- .../metrics/generated/fd_metrics_enums.h | 126 - .../metrics/generated/fd_metrics_sample_gen.c | 2 +- .../metrics/generated/fd_metrics_tower.h | 339 --- src/disco/metrics/metrics.xml | 138 - src/disco/topo/fd_topo.h | 18 - src/discof/forest/Local.mk | 5 - src/discof/forest/fd_forest.c | 2151 --------------- src/discof/forest/fd_forest.h | 1037 ------- src/discof/forest/test_forest.c | 2378 ----------------- src/discof/reasm/Local.mk | 6 - src/discof/reasm/fd_reasm.c | 1135 -------- src/discof/reasm/fd_reasm.h | 458 ---- src/discof/reasm/fd_reasm_private.h | 110 - src/discof/reasm/fuzz_reasm.c | 539 ---- src/discof/reasm/test_reasm.c | 1466 ---------- src/discof/rotor/fd_rotor_tile.h | 4 - src/discof/tower/Local.mk | 5 - src/discof/tower/fd_tower_slot_rooted.h | 39 - src/discof/tower/fd_tower_tile.c | 2194 --------------- src/discof/tower/fd_tower_tile.h | 208 -- src/discof/tower/fd_tower_tile.seccomppolicy | 104 - src/discof/tower/fd_tower_tile_private.h | 246 -- .../tower/fixtures/voters-398915634.bin | Bin 384200 -> 0 bytes .../tower/fixtures/voters-398915635.bin | Bin 384200 -> 0 bytes .../tower/fixtures/voters-398915636.bin | Bin 384200 -> 0 bytes .../tower/fixtures/voters-398915637.bin | Bin 384200 -> 0 bytes .../tower/fixtures/voters-398915638.bin | Bin 384200 -> 0 bytes .../tower/fixtures/voters-398915639.bin | Bin 384200 -> 0 bytes .../tower/fixtures/voters-398915640.bin | Bin 384200 -> 0 bytes .../tower/fixtures/voters-398915641.bin | Bin 384200 -> 0 bytes .../tower/fixtures/voters-398915642.bin | Bin 384200 -> 0 bytes .../tower/fixtures/voters-398915643.bin | Bin 384200 -> 0 bytes .../tower/fixtures/voters-398915644.bin | Bin 384200 -> 0 bytes .../tower/fixtures/voters-398915645.bin | Bin 384200 -> 0 bytes .../tower/fixtures/voters-398915646.bin | Bin 384200 -> 0 bytes .../tower/fixtures/voters-398915647.bin | Bin 384200 -> 0 bytes .../tower/fixtures/voters-398915648.bin | Bin 384200 -> 0 bytes .../tower/fixtures/voters-398915649.bin | Bin 384200 -> 0 bytes .../tower/fixtures/voters-398915650.bin | Bin 384200 -> 0 bytes .../tower/fixtures/voters-398915651.bin | Bin 384200 -> 0 bytes .../tower/fixtures/voters-398915652.bin | Bin 384200 -> 0 bytes .../tower/fixtures/voters-398915653.bin | Bin 384200 -> 0 bytes .../tower/fixtures/voters-398915654.bin | Bin 384200 -> 0 bytes .../tower/fixtures/voters-398915655.bin | Bin 384200 -> 0 bytes .../tower/fixtures/voters-398915656.bin | Bin 384200 -> 0 bytes .../tower/fixtures/voters-398915657.bin | Bin 384200 -> 0 bytes .../tower/fixtures/voters-398915658.bin | Bin 384200 -> 0 bytes .../tower/fixtures/voters-398915659.bin | Bin 384200 -> 0 bytes .../tower/fixtures/voters-398915660.bin | Bin 384200 -> 0 bytes .../tower/fixtures/voters-398915661.bin | Bin 384200 -> 0 bytes .../tower/fixtures/voters-398915662.bin | Bin 384200 -> 0 bytes .../tower/fixtures/voters-398915663.bin | Bin 384200 -> 0 bytes .../tower/fixtures/voters-398915664.bin | Bin 384200 -> 0 bytes .../tower/fixtures/voters-398915665.bin | Bin 384200 -> 0 bytes .../tower/generated/fd_tower_tile_seccomp.h | 196 -- src/discof/tower/test_tower_tile.c | 1410 ---------- src/flamenco/runtime/tests/Local.mk | 6 +- src/flamenco/runtime/tests/fd_shred_harness.c | 460 ---- src/flamenco/runtime/tests/fd_sol_compat.c | 24 - src/flamenco/runtime/tests/fd_sol_compat.h | 6 - src/flamenco/runtime/tests/fd_solfuzz.h | 14 - src/flamenco/runtime/tests/fd_solfuzz_exec.c | 21 - src/flamenco/runtime/tests/test_sol_compat.c | 8 +- .../runtime/tests/test_sol_compat_so.c | 3 +- 101 files changed, 10 insertions(+), 24572 deletions(-) delete mode 100644 src/choreo/eqvoc/Local.mk delete mode 100644 src/choreo/eqvoc/fd_eqvoc.c delete mode 100644 src/choreo/eqvoc/fd_eqvoc.h delete mode 100644 src/choreo/eqvoc/fixtures/id.bin delete mode 100644 src/choreo/eqvoc/fixtures/pay1.bin delete mode 100644 src/choreo/eqvoc/fixtures/pay2.bin delete mode 100644 src/choreo/eqvoc/fuzz_eqvoc.c delete mode 100644 src/choreo/eqvoc/test_eqvoc.c delete mode 100644 src/choreo/ghost/Local.mk delete mode 100644 src/choreo/ghost/fd_ghost.c delete mode 100644 src/choreo/ghost/fd_ghost.h delete mode 100644 src/choreo/ghost/test_ghost.c delete mode 100644 src/choreo/hfork/Local.mk delete mode 100644 src/choreo/hfork/fd_hfork.c delete mode 100644 src/choreo/hfork/fd_hfork.h delete mode 100644 src/choreo/hfork/test_hfork.c delete mode 100644 src/choreo/tower/fd_tower.c delete mode 100644 src/choreo/tower/fd_tower_file.c delete mode 100644 src/choreo/tower/fd_tower_file.h delete mode 100644 src/choreo/tower/fd_tower_stakes.c delete mode 100644 src/choreo/tower/fd_tower_stakes.h delete mode 100644 src/choreo/tower/fuzz_tower_file.c delete mode 100644 src/choreo/tower/test_tower.c delete mode 100644 src/choreo/tower/test_tower_lockos.c delete mode 100644 src/disco/metrics/generated/fd_metrics_tower.h delete mode 100644 src/discof/forest/Local.mk delete mode 100644 src/discof/forest/fd_forest.c delete mode 100644 src/discof/forest/fd_forest.h delete mode 100644 src/discof/forest/test_forest.c delete mode 100644 src/discof/reasm/Local.mk delete mode 100644 src/discof/reasm/fd_reasm.c delete mode 100644 src/discof/reasm/fd_reasm.h delete mode 100644 src/discof/reasm/fd_reasm_private.h delete mode 100644 src/discof/reasm/fuzz_reasm.c delete mode 100644 src/discof/reasm/test_reasm.c delete mode 100644 src/discof/tower/Local.mk delete mode 100644 src/discof/tower/fd_tower_slot_rooted.h delete mode 100644 src/discof/tower/fd_tower_tile.c delete mode 100644 src/discof/tower/fd_tower_tile.h delete mode 100644 src/discof/tower/fd_tower_tile.seccomppolicy delete mode 100644 src/discof/tower/fd_tower_tile_private.h delete mode 100644 src/discof/tower/fixtures/voters-398915634.bin delete mode 100644 src/discof/tower/fixtures/voters-398915635.bin delete mode 100644 src/discof/tower/fixtures/voters-398915636.bin delete mode 100644 src/discof/tower/fixtures/voters-398915637.bin delete mode 100644 src/discof/tower/fixtures/voters-398915638.bin delete mode 100644 src/discof/tower/fixtures/voters-398915639.bin delete mode 100644 src/discof/tower/fixtures/voters-398915640.bin delete mode 100644 src/discof/tower/fixtures/voters-398915641.bin delete mode 100644 src/discof/tower/fixtures/voters-398915642.bin delete mode 100644 src/discof/tower/fixtures/voters-398915643.bin delete mode 100644 src/discof/tower/fixtures/voters-398915644.bin delete mode 100644 src/discof/tower/fixtures/voters-398915645.bin delete mode 100644 src/discof/tower/fixtures/voters-398915646.bin delete mode 100644 src/discof/tower/fixtures/voters-398915647.bin delete mode 100644 src/discof/tower/fixtures/voters-398915648.bin delete mode 100644 src/discof/tower/fixtures/voters-398915649.bin delete mode 100644 src/discof/tower/fixtures/voters-398915650.bin delete mode 100644 src/discof/tower/fixtures/voters-398915651.bin delete mode 100644 src/discof/tower/fixtures/voters-398915652.bin delete mode 100644 src/discof/tower/fixtures/voters-398915653.bin delete mode 100644 src/discof/tower/fixtures/voters-398915654.bin delete mode 100644 src/discof/tower/fixtures/voters-398915655.bin delete mode 100644 src/discof/tower/fixtures/voters-398915656.bin delete mode 100644 src/discof/tower/fixtures/voters-398915657.bin delete mode 100644 src/discof/tower/fixtures/voters-398915658.bin delete mode 100644 src/discof/tower/fixtures/voters-398915659.bin delete mode 100644 src/discof/tower/fixtures/voters-398915660.bin delete mode 100644 src/discof/tower/fixtures/voters-398915661.bin delete mode 100644 src/discof/tower/fixtures/voters-398915662.bin delete mode 100644 src/discof/tower/fixtures/voters-398915663.bin delete mode 100644 src/discof/tower/fixtures/voters-398915664.bin delete mode 100644 src/discof/tower/fixtures/voters-398915665.bin delete mode 100644 src/discof/tower/generated/fd_tower_tile_seccomp.h delete mode 100644 src/discof/tower/test_tower_tile.c delete mode 100644 src/flamenco/runtime/tests/fd_shred_harness.c diff --git a/book/api/metrics-generated.md b/book/api/metrics-generated.md index 5d048b9645b..6748fd35e06 100644 --- a/book/api/metrics-generated.md +++ b/book/api/metrics-generated.md @@ -1417,129 +1417,6 @@
-## Tower Tile - -
- -| Metric | Type | Description | -|--------|------|-------------| -| tower_​pruned_​fork_​slot_​ignored | counter | replay_slot_completed frags we ignored | -| tower_​last_​pruned_​fork_​ignored_​slot | gauge | Slot of the most recent replay_slot_completed frag ignored for being on a pruned fork, 0 if none | -| tower_​last_​replayed_​slot | gauge | Most recently replayed slot, 0 if none; can decrease | -| tower_​last_​vote_​slot | gauge | Highest voted slot in the local tower, 0 if we have not voted | -| tower_​reset_​slot | gauge | Most recent reset slot; can decrease | -| tower_​root_​slot | gauge | Highest rooted slot, 0 if none | -| tower_​init_​slot | gauge | Init slot, either the snapshot or genesis slot. Set once and does not change | -| tower_​frag_​not_​ready_​dropped | counter | Frags dropped before the tower tile initialized (vote txns, gossip updates), plus duplicate-shred messages with no leader schedule | -| tower_​eqvoc_​chunk_​processed
{eqvoc_​proof_​result="verified"} | counter | Equivocation proof chunks and conflicting shreds processed, by result (Completed and verified an equivocation proof) | -| tower_​eqvoc_​chunk_​processed
{eqvoc_​proof_​result="ignored"} | counter | Equivocation proof chunks and conflicting shreds processed, by result (Buffered, duplicate, or for a slot already proven) | -| tower_​eqvoc_​chunk_​processed
{eqvoc_​proof_​result="invalid"} | counter | Equivocation proof chunks and conflicting shreds processed, by result (Failed validation) | -| tower_​ghost_​vote_​processed
{ghost_​vote_​result="success"} | counter | Votes counted toward ghost, by result (Vote was counted towards ghost) | -| tower_​ghost_​vote_​processed
{ghost_​vote_​result="already_​voted"} | counter | Votes counted toward ghost, by result (Slot was not newer than previous vote) | -| tower_​hard_​fork_​vote_​processed
{hard_​fork_​vote_​result="pending"} | counter | Votes counted toward hard fork detection, by result (Counted, no 52% agreement yet) | -| tower_​hard_​fork_​vote_​processed
{hard_​fork_​vote_​result="matched"} | counter | Votes counted toward hard fork detection, by result (Counted, and 52%+ of stake agreed on our bank hash) | -| tower_​hard_​fork_​vote_​processed
{hard_​fork_​vote_​result="mismatched"} | counter | Votes counted toward hard fork detection, by result (Counted, and 52%+ of stake agreed on a different bank hash (we hard forked)) | -| tower_​hard_​fork_​vote_​processed
{hard_​fork_​vote_​result="unknown_​voter"} | counter | Votes counted toward hard fork detection, by result (Voter not in the voter set) | -| tower_​hard_​fork_​vote_​processed
{hard_​fork_​vote_​result="already_​voted"} | counter | Votes counted toward hard fork detection, by result (Voter already voted for this block_id) | -| tower_​hard_​fork_​vote_​processed
{hard_​fork_​vote_​result="too_​old"} | counter | Votes counted toward hard fork detection, by result (Slot not newer than the voter's previous vote) | -| tower_​hard_​fork_​bank_​hash_​checked
{hard_​fork_​bank_​hash_​result="pending"} | counter | Our own bank hashes checked against hard fork votes, by result (No 52% agreement on this block yet) | -| tower_​hard_​fork_​bank_​hash_​checked
{hard_​fork_​bank_​hash_​result="matched"} | counter | Our own bank hashes checked against hard fork votes, by result (52%+ of stake agreed on our bank hash) | -| tower_​hard_​fork_​bank_​hash_​checked
{hard_​fork_​bank_​hash_​result="mismatched"} | counter | Our own bank hashes checked against hard fork votes, by result (52%+ of stake agreed on a different bank hash (we hard forked)) | -| tower_​highest_​hard_​fork_​matched_​slot | gauge | Highest slot where 52%+ of stake agreed on our bank hash, 0 if none | -| tower_​highest_​hard_​fork_​mismatched_​slot | gauge | Highest slot where 52%+ of stake agreed on a different bank hash than ours (we hard forked), 0 if none | -| tower_​fork_​chosen
{tower_​fork_​result="empty_​tower_​stale"} | counter | Fork choices for voting and reset, by result (Haven't voted yet and best block is not recent enough to vote (can't vote)) | -| tower_​fork_​chosen
{tower_​fork_​result="empty_​tower_​vote"} | counter | Fork choices for voting and reset, by result (Haven't voted yet, voting for best block (can vote)) | -| tower_​fork_​chosen
{tower_​fork_​result="ancestor_​rollback"} | counter | Fork choices for voting and reset, by result (Rollback to an ancestor of our prev vote (can't vote)) | -| tower_​fork_​chosen
{tower_​fork_​result="sibling_​confirmed"} | counter | Fork choices for voting and reset, by result (Duplicate sibling got confirmed (can vote)) | -| tower_​fork_​chosen
{tower_​fork_​result="same_​fork"} | counter | Fork choices for voting and reset, by result (Same fork as prev vote (can vote)) | -| tower_​fork_​chosen
{tower_​fork_​result="switch_​passed"} | counter | Fork choices for voting and reset, by result (Prev vote was on a different fork, but we are allowed to switch (can vote)) | -| tower_​fork_​chosen
{tower_​fork_​result="switch_​failed"} | counter | Fork choices for voting and reset, by result (Prev vote was on a different fork, and we are not allowed to switch (can't vote)) | -| tower_​vote_​gate_​checked
{tower_​vote_​gate_​result="voted"} | counter | Vote gating checks after a fork was chosen, by result (Passed all vote checks and cast a vote) | -| tower_​vote_​gate_​checked
{tower_​vote_​gate_​result="lockout_​failed"} | counter | Vote gating checks after a fork was chosen, by result (Locked out (can't vote)) | -| tower_​vote_​gate_​checked
{tower_​vote_​gate_​result="threshold_​failed"} | counter | Vote gating checks after a fork was chosen, by result (Did not pass threshold check (can't vote)) | -| tower_​vote_​gate_​checked
{tower_​vote_​gate_​result="propagated_​failed"} | counter | Vote gating checks after a fork was chosen, by result (Prev leader block did not propagate (can't vote)) | -| tower_​vote_​gate_​checked
{tower_​vote_​gate_​result="no_​candidate"} | counter | Vote gating checks after a fork was chosen, by result (Fork decision did not produce a votable block) | -| tower_​tower_​file_​written | counter | Times the tower file was written | -| tower_​tower_​file_​slot | gauge | Newest vote slot in the last written tower file, 0 if not written yet | -| tower_​tower_​file_​size_​bytes | gauge | Size of the last written tower file | -| tower_​vote_​txn_​processed
{vote_​txn_​result="success"} | counter | Vote transactions processed, by result (Vote txn passed per-txn validation and reached slot counting (per-slot outcomes tracked by VoteSlotResult)) | -| tower_​vote_​txn_​processed
{vote_​txn_​result="not_​simple_​vote"} | counter | Vote transactions processed, by result (Txn was not a simple vote transaction) | -| tower_​vote_​txn_​processed
{vote_​txn_​result="bad_​deser"} | counter | Vote transactions processed, by result (Vote txn failed to deserialize) | -| tower_​vote_​txn_​processed
{vote_​txn_​result="not_​tower_​sync"} | counter | Vote transactions processed, by result (Vote txn was not a TowerSync instruction) | -| tower_​vote_​txn_​processed
{vote_​txn_​result="empty_​tower"} | counter | Vote transactions processed, by result (Vote txn had an empty tower (validator hasn't voted)) | -| tower_​vote_​txn_​processed
{vote_​txn_​result="bad_​tower"} | counter | Vote transactions processed, by result (Vote txn deserialized but tower was invalid) | -| tower_​vote_​txn_​processed
{vote_​txn_​result="null_​block_​id"} | counter | Vote transactions processed, by result (Last vote had a null block id) | -| tower_​vote_​txn_​processed
{vote_​txn_​result="too_​old"} | counter | Vote transactions processed, by result (Last vote slot was behind the votes root) | -| tower_​vote_​txn_​processed
{vote_​txn_​result="not_​staked"} | counter | Vote transactions processed, by result (Unable to query stake for vote slot) | -| tower_​vote_​txn_​processed
{vote_​txn_​result="bad_​signer"} | counter | Vote transactions processed, by result (Vote txn was not signed by the authorized voter for the vote account at the vote epoch) | -| tower_​vote_​slot_​processed
{vote_​slot_​result="success"} | counter | Individual vote slots from vote transactions counted, by result (Vote slot was counted) | -| tower_​vote_​slot_​processed
{vote_​slot_​result="too_​new"} | counter | Individual vote slots from vote transactions counted, by result (Vote slot was too far ahead of the votes root) | -| tower_​vote_​slot_​processed
{vote_​slot_​result="unknown_​voter"} | counter | Individual vote slots from vote transactions counted, by result (Vote account was not in the voter set) | -| tower_​vote_​slot_​processed
{vote_​slot_​result="already_​voted"} | counter | Individual vote slots from vote transactions counted, by result (Voter already voted for this slot) | -| tower_​vote_​slot_​processed
{vote_​slot_​result="too_​old"} | counter | Individual vote slots from vote transactions counted, by result (Vote slot was behind the votes root) | -| tower_​vote_​slot_​processed
{vote_​slot_​result="unknown_​slot"} | counter | Individual vote slots from vote transactions counted, by result (Vote slot was for a slot we haven't replayed) | -| tower_​vote_​intermediate_​slot_​checked
{vote_​intermediate_​slot_​result="counted"} | counter | Vote transactions whose last vote slot was checked before counting intermediate slots, by result (Last vote slot was replayed and matched, intermediate vote slots will be counted) | -| tower_​vote_​intermediate_​slot_​checked
{vote_​intermediate_​slot_​result="unreplayed_​slot"} | counter | Vote transactions whose last vote slot was checked before counting intermediate slots, by result (Last vote slot was not replayed, intermediate vote slots skipped) | -| tower_​vote_​intermediate_​slot_​checked
{vote_​intermediate_​slot_​result="mismatched_​block_​id"} | counter | Vote transactions whose last vote slot was checked before counting intermediate slots, by result (Last vote slot block id did not match, intermediate vote slots skipped) | -| tower_​accdb_​account_​readonly_​acquired
{accdb_​cache_​class="le128_​b"} | counter | Readonly accounts acquired from the account database, by cache size class of the account's current data size (0-128 B) | -| tower_​accdb_​account_​readonly_​acquired
{accdb_​cache_​class="le512_​b"} | counter | Readonly accounts acquired from the account database, by cache size class of the account's current data size (129-512 B) | -| tower_​accdb_​account_​readonly_​acquired
{accdb_​cache_​class="le2_​kib"} | counter | Readonly accounts acquired from the account database, by cache size class of the account's current data size (513 B-2 KiB) | -| tower_​accdb_​account_​readonly_​acquired
{accdb_​cache_​class="le8_​kib"} | counter | Readonly accounts acquired from the account database, by cache size class of the account's current data size (2-8 KiB) | -| tower_​accdb_​account_​readonly_​acquired
{accdb_​cache_​class="le32_​kib"} | counter | Readonly accounts acquired from the account database, by cache size class of the account's current data size (8-32 KiB) | -| tower_​accdb_​account_​readonly_​acquired
{accdb_​cache_​class="le128_​kib"} | counter | Readonly accounts acquired from the account database, by cache size class of the account's current data size (32-128 KiB) | -| tower_​accdb_​account_​readonly_​acquired
{accdb_​cache_​class="le1_​mib"} | counter | Readonly accounts acquired from the account database, by cache size class of the account's current data size (128 KiB-1 MiB) | -| tower_​accdb_​account_​readonly_​acquired
{accdb_​cache_​class="le10_​mib"} | counter | Readonly accounts acquired from the account database, by cache size class of the account's current data size (1-10 MiB) | -| tower_​accdb_​account_​writable_​acquired
{accdb_​cache_​class="le128_​b"} | counter | Writable accounts acquired from the account database, by cache size class of the account's current data size (0-128 B) | -| tower_​accdb_​account_​writable_​acquired
{accdb_​cache_​class="le512_​b"} | counter | Writable accounts acquired from the account database, by cache size class of the account's current data size (129-512 B) | -| tower_​accdb_​account_​writable_​acquired
{accdb_​cache_​class="le2_​kib"} | counter | Writable accounts acquired from the account database, by cache size class of the account's current data size (513 B-2 KiB) | -| tower_​accdb_​account_​writable_​acquired
{accdb_​cache_​class="le8_​kib"} | counter | Writable accounts acquired from the account database, by cache size class of the account's current data size (2-8 KiB) | -| tower_​accdb_​account_​writable_​acquired
{accdb_​cache_​class="le32_​kib"} | counter | Writable accounts acquired from the account database, by cache size class of the account's current data size (8-32 KiB) | -| tower_​accdb_​account_​writable_​acquired
{accdb_​cache_​class="le128_​kib"} | counter | Writable accounts acquired from the account database, by cache size class of the account's current data size (32-128 KiB) | -| tower_​accdb_​account_​writable_​acquired
{accdb_​cache_​class="le1_​mib"} | counter | Writable accounts acquired from the account database, by cache size class of the account's current data size (128 KiB-1 MiB) | -| tower_​accdb_​account_​writable_​acquired
{accdb_​cache_​class="le10_​mib"} | counter | Writable accounts acquired from the account database, by cache size class of the account's current data size (1-10 MiB) | -| tower_​accdb_​account_​evicted
{accdb_​cache_​class="le128_​b"} | counter | Accounts evicted from the account database cache to make space, by cache size class (0-128 B) | -| tower_​accdb_​account_​evicted
{accdb_​cache_​class="le512_​b"} | counter | Accounts evicted from the account database cache to make space, by cache size class (129-512 B) | -| tower_​accdb_​account_​evicted
{accdb_​cache_​class="le2_​kib"} | counter | Accounts evicted from the account database cache to make space, by cache size class (513 B-2 KiB) | -| tower_​accdb_​account_​evicted
{accdb_​cache_​class="le8_​kib"} | counter | Accounts evicted from the account database cache to make space, by cache size class (2-8 KiB) | -| tower_​accdb_​account_​evicted
{accdb_​cache_​class="le32_​kib"} | counter | Accounts evicted from the account database cache to make space, by cache size class (8-32 KiB) | -| tower_​accdb_​account_​evicted
{accdb_​cache_​class="le128_​kib"} | counter | Accounts evicted from the account database cache to make space, by cache size class (32-128 KiB) | -| tower_​accdb_​account_​evicted
{accdb_​cache_​class="le1_​mib"} | counter | Accounts evicted from the account database cache to make space, by cache size class (128 KiB-1 MiB) | -| tower_​accdb_​account_​evicted
{accdb_​cache_​class="le10_​mib"} | counter | Accounts evicted from the account database cache to make space, by cache size class (1-10 MiB) | -| tower_​accdb_​account_​new_​committed
{accdb_​cache_​class="le128_​b"} | counter | New account versions committed to the index, by destination cache size class (0-128 B) | -| tower_​accdb_​account_​new_​committed
{accdb_​cache_​class="le512_​b"} | counter | New account versions committed to the index, by destination cache size class (129-512 B) | -| tower_​accdb_​account_​new_​committed
{accdb_​cache_​class="le2_​kib"} | counter | New account versions committed to the index, by destination cache size class (513 B-2 KiB) | -| tower_​accdb_​account_​new_​committed
{accdb_​cache_​class="le8_​kib"} | counter | New account versions committed to the index, by destination cache size class (2-8 KiB) | -| tower_​accdb_​account_​new_​committed
{accdb_​cache_​class="le32_​kib"} | counter | New account versions committed to the index, by destination cache size class (8-32 KiB) | -| tower_​accdb_​account_​new_​committed
{accdb_​cache_​class="le128_​kib"} | counter | New account versions committed to the index, by destination cache size class (32-128 KiB) | -| tower_​accdb_​account_​new_​committed
{accdb_​cache_​class="le1_​mib"} | counter | New account versions committed to the index, by destination cache size class (128 KiB-1 MiB) | -| tower_​accdb_​account_​new_​committed
{accdb_​cache_​class="le10_​mib"} | counter | New account versions committed to the index, by destination cache size class (1-10 MiB) | -| tower_​accdb_​account_​overwrite_​committed
{accdb_​cache_​class="le128_​b"} | counter | Account versions committed over an existing version of the same fork and generation, by destination cache size class (0-128 B) | -| tower_​accdb_​account_​overwrite_​committed
{accdb_​cache_​class="le512_​b"} | counter | Account versions committed over an existing version of the same fork and generation, by destination cache size class (129-512 B) | -| tower_​accdb_​account_​overwrite_​committed
{accdb_​cache_​class="le2_​kib"} | counter | Account versions committed over an existing version of the same fork and generation, by destination cache size class (513 B-2 KiB) | -| tower_​accdb_​account_​overwrite_​committed
{accdb_​cache_​class="le8_​kib"} | counter | Account versions committed over an existing version of the same fork and generation, by destination cache size class (2-8 KiB) | -| tower_​accdb_​account_​overwrite_​committed
{accdb_​cache_​class="le32_​kib"} | counter | Account versions committed over an existing version of the same fork and generation, by destination cache size class (8-32 KiB) | -| tower_​accdb_​account_​overwrite_​committed
{accdb_​cache_​class="le128_​kib"} | counter | Account versions committed over an existing version of the same fork and generation, by destination cache size class (32-128 KiB) | -| tower_​accdb_​account_​overwrite_​committed
{accdb_​cache_​class="le1_​mib"} | counter | Account versions committed over an existing version of the same fork and generation, by destination cache size class (128 KiB-1 MiB) | -| tower_​accdb_​account_​overwrite_​committed
{accdb_​cache_​class="le10_​mib"} | counter | Account versions committed over an existing version of the same fork and generation, by destination cache size class (1-10 MiB) | -| tower_​accdb_​account_​cache_​missed
{accdb_​cache_​class="le128_​b"} | counter | Accounts not found in the account database cache and read from disk, by cache size class (0-128 B) | -| tower_​accdb_​account_​cache_​missed
{accdb_​cache_​class="le512_​b"} | counter | Accounts not found in the account database cache and read from disk, by cache size class (129-512 B) | -| tower_​accdb_​account_​cache_​missed
{accdb_​cache_​class="le2_​kib"} | counter | Accounts not found in the account database cache and read from disk, by cache size class (513 B-2 KiB) | -| tower_​accdb_​account_​cache_​missed
{accdb_​cache_​class="le8_​kib"} | counter | Accounts not found in the account database cache and read from disk, by cache size class (2-8 KiB) | -| tower_​accdb_​account_​cache_​missed
{accdb_​cache_​class="le32_​kib"} | counter | Accounts not found in the account database cache and read from disk, by cache size class (8-32 KiB) | -| tower_​accdb_​account_​cache_​missed
{accdb_​cache_​class="le128_​kib"} | counter | Accounts not found in the account database cache and read from disk, by cache size class (32-128 KiB) | -| tower_​accdb_​account_​cache_​missed
{accdb_​cache_​class="le1_​mib"} | counter | Accounts not found in the account database cache and read from disk, by cache size class (128 KiB-1 MiB) | -| tower_​accdb_​account_​cache_​missed
{accdb_​cache_​class="le10_​mib"} | counter | Accounts not found in the account database cache and read from disk, by cache size class (1-10 MiB) | -| tower_​accdb_​account_​waited | counter | Accounts that had to wait for another reader to finish loading them into the cache | -| tower_​accdb_​batch_​acquired | counter | Batch acquire calls made by this tile | -| tower_​accdb_​cache_​reserve_​retried | counter | Spin retries while waiting to reserve cache lines for a transaction | -| tower_​accdb_​disk_​read_​bytes | counter | Bytes read from the account database file | -| tower_​accdb_​disk_​read | counter | Reads from the account database file | -| tower_​accdb_​disk_​written_​bytes | counter | Bytes written to the account database file | -| tower_​accdb_​disk_​written | counter | Writes to the account database file | -| tower_​accdb_​copied_​bytes | counter | Account data bytes copied in memory between cache lines, staging buffers and callers; disk reads excluded | - -
- ## Diag Tile
diff --git a/contrib/test/run_script_tests.sh b/contrib/test/run_script_tests.sh index b51037e15e3..0f5eb345cbf 100755 --- a/contrib/test/run_script_tests.sh +++ b/contrib/test/run_script_tests.sh @@ -29,7 +29,6 @@ $UNIT_TEST/test_cnc --tile-cpus 0,2 2> $LOG_PATH/cnc || test_status= $UNIT_TEST/test_tile --tile-cpus 0-8/2 2> $LOG_PATH/tile_multi || test_status=1 $UNIT_TEST/test_tpool --tile-cpus 0-7 2> $LOG_PATH/tpool_large || test_status=1 $UNIT_TEST/test_store --tile-cpus 0-3 --require-multitile 2> $LOG_PATH/store_multi || test_status=1 -$UNIT_TEST/test_reasm --tile-cpus 0-3 --require-multitile 2> $LOG_PATH/reasm_multi || test_status=1 if $UNIT_TEST/test_ipc_init $OBJDIR && \ $UNIT_TEST/test_ipc_meta 16 && \ diff --git a/contrib/test/run_test_vectors.sh b/contrib/test/run_test_vectors.sh index b9d75f7d5ad..d0661e541f3 100755 --- a/contrib/test/run_test_vectors.sh +++ b/contrib/test/run_test_vectors.sh @@ -25,7 +25,7 @@ fi # If WORK_DIR is provided, use it directly and skip the whole # git clone/fetch/checkout/cache/rsync dance. The provided directory # is expected to already contain the fixtures laid out as -# /{block,syscall,txn,elf_loader,instr,shred}/fixtures +# /{block,syscall,txn,elf_loader,instr}/fixtures if [ -n "${WORK_DIR:-}" ]; then echo "Using provided WORK_DIR: $WORK_DIR" else @@ -79,7 +79,6 @@ export FD_LOG_PATH="$LOG_PATH/solfuzz.log" "$WORK_DIR/txn/fixtures" \ "$WORK_DIR/elf_loader/fixtures" \ "$WORK_DIR/instr/fixtures" \ - "$WORK_DIR/shred/fixtures" \ "$WORK_DIR/cost/fixtures" \ "$WORK_DIR/vm_serialization/fixtures" \ "$WORK_DIR/gossip/fixtures" diff --git a/contrib/test/run_unit_tests.sh b/contrib/test/run_unit_tests.sh index bfa5a411d5a..003bb9684ba 100755 --- a/contrib/test/run_unit_tests.sh +++ b/contrib/test/run_unit_tests.sh @@ -34,7 +34,6 @@ declare -A TEST_GIGANTIC_PAGES=( [test_vm_syscall_curve]=5 [test_vm_interp]=5 [test_bpf_loader_program]=5 - [test_tower_tile]=4 [test_log_collector]=4 [test_ssmanifest_parser]=2 [test_replay_tile]=2 diff --git a/src/app/shared/fd_config_json.c b/src/app/shared/fd_config_json.c index 7f8d2e45302..56129762d1a 100644 --- a/src/app/shared/fd_config_json.c +++ b/src/app/shared/fd_config_json.c @@ -12,7 +12,7 @@ or knowingly skipped) before the constant is bumped. String keys of the user's own file are separately forced through the classification lists below. */ -FD_STATIC_ASSERT( sizeof(fd_config_t)==26553472UL, update_fd_config_to_json_for_the_layout_change ); +FD_STATIC_ASSERT( sizeof(fd_config_t)==24458368UL, update_fd_config_to_json_for_the_layout_change ); #define REDACTED "[redacted]" diff --git a/src/choreo/eqvoc/Local.mk b/src/choreo/eqvoc/Local.mk deleted file mode 100644 index 9048ea1369f..00000000000 --- a/src/choreo/eqvoc/Local.mk +++ /dev/null @@ -1,7 +0,0 @@ -$(call add-hdrs,fd_eqvoc.h) -$(call add-objs,fd_eqvoc,fd_choreo) -ifdef FD_HAS_HOSTED -$(call make-unit-test,test_eqvoc,test_eqvoc,fd_choreo fd_flamenco fd_ballet fd_util) -$(call run-unit-test,test_eqvoc) -$(call make-fuzz-test,fuzz_eqvoc,fuzz_eqvoc,fd_choreo fd_flamenco fd_ballet fd_util) -endif diff --git a/src/choreo/eqvoc/fd_eqvoc.c b/src/choreo/eqvoc/fd_eqvoc.c deleted file mode 100644 index 08ee4cadd3d..00000000000 --- a/src/choreo/eqvoc/fd_eqvoc.c +++ /dev/null @@ -1,923 +0,0 @@ -#include "fd_eqvoc.h" -#include "../fd_choreo_base.h" -#include "../../ballet/shred/fd_shred.h" -#include "../../disco/shred/fd_fec_set.h" - -/* fd_eqvoc maintains four bounded maps: - - dup_map (capacity dup_max): maps slot -> equivocation result, - recording slots we've already verified are duplicates - (equivocations). LRU-evicts when at capacity: querying an - entry moves it to the tail of the recency list; when an - insert is needed and the map is full, the head - (least-recently-used) entry is evicted. - - fec_map (capacity fec_max): maps (slot, fec_set_idx) -> shred, - storing the first shred seen in each FEC set so it can be - compared against later siblings for equivocation. Same LRU - eviction policy as dup_map. Entries are also explicitly - removed when equivocation is confirmed (proof constructed). - - prf_map (capacity per_vtr_max * vtr_max): maps (slot, voter index) - -> in-progress proof ("chunks") assembly state, tracking - proofs per voter per slot. The voter index refers into - vtr_pool; all of a voter's proofs are removed before that - index is reused. Entries are LRU-evicted per voter when - that voter's in-progress proof count reaches per_vtr_max. - Entries are also removed when proof assembly completes (all - chunks received), regardless of verification outcome, or - when the corresponding voter is removed from vtr_map. - - vtr_map (capacity vtr_max): maps voter pubkey -> per-voter proof- - assembly state. vtr entries are not evicted automatically; - they are explicitly inserted and removed by - fd_eqvoc_update_voters when the epoch stake set changes. - Each vtr has a pre-allocated prf_dlist that tracks - in-progress proofs for that voter. Each voter's in-progress - proof count is bounded by per_vtr_max; when that limit is - reached, the oldest proof for that voter is evicted. - - dup_map fec_map - map[0] +--------------------+ map[0] +--------------------+ - | (dup_t) { | | (fec_t) { | - | .slot = 1, | | .key = 5|0, | - | ... | | ... | - | } | | } | - map[1] +--------------------+ map[1] +--------------------+ - | (dup_t) { | | (fec_t) { | - | .slot = 2, | | .key = 6|0, | - | ... | | ... | - | } | | } | - +--------------------+ +--------------------+ - - vtr_map prf_map - map[0] +--------------------+ map[0] +--------------------+ - | (vtr_t) { | | (prf_t) { | - | .from = X, | | .key.slot = 5, | - | ... | | .key.vtr_idx = 1,|<-----+ - | ... | | ... | | - | } | | } | | - map[1] +--------------------+ map[1] +--------------------+ | - | (vtr_t) { | | (prf_t) { | | - | .from = Y, | | .key.slot = 6, | | - | ... | | .key.vtr_idx = 1,|<--+ | - | .prf_dlist = + | | ... | | | - | } | | | } | | | - +----------------|---+ +--------------------+ | | - | | | - | | | - | +------------------------+ | - | | | - V | +------------------+ - prf_dlist | | - +---------+---------+---------+ - | (prf_t) | (prf_t) | (prf_t) | - | ... | ... | ... | - | } | } | } | - +---------+---------+---------+ - oldest newest - - Each vtr_t owns a prf_dlist of in-progress proofs (prf_t). - prf_t elements are also in the global prf_map for lookup by - (slot, vtr_idx). When prf_dlist_cnt == per_vtr_max, the oldest - prf is evicted from both prf_dlist and prf_map. */ - -typedef struct { - ulong slot; - uint next; /* pool next */ - struct { - uint prev; - uint next; - } map; - struct { - uint prev; - uint next; - } dlist; -} dup_t; - -#define POOL_NAME dup_pool -#define POOL_LAZY 1 -#define POOL_T dup_t -#define POOL_IDX_T uint -#include "../../util/tmpl/fd_pool.c" - -#define MAP_NAME dup_map -#define MAP_ELE_T dup_t -#define MAP_KEY slot -#define MAP_PREV map.prev -#define MAP_NEXT map.next -#define MAP_IDX_T uint -#define MAP_OPTIMIZE_RANDOM_ACCESS_REMOVAL 1 -#include "../../util/tmpl/fd_map_chain.c" - -#define DLIST_NAME dup_dlist -#define DLIST_ELE_T dup_t -#define DLIST_PREV dlist.prev -#define DLIST_NEXT dlist.next -#define DLIST_IDX_T uint -#include "../../util/tmpl/fd_dlist.c" - -typedef struct { - ulong key; /* 32 bits = slot | 32 lsb = fec_set_idx */ - uint next; /* pool next */ - struct { - uint prev; - uint next; - } map; - struct { - uint prev; - uint next; - } dlist; - union { - fd_shred_t sample_shred; /* highest shred seen so far, by index */ - uchar sample_bytes[FD_SHRED_MAX_SZ]; /* entire shred, both header and payload */ - }; -} fec_t; - -#define POOL_NAME fec_pool -#define POOL_LAZY 1 -#define POOL_T fec_t -#define POOL_IDX_T uint -#include "../../util/tmpl/fd_pool.c" - -#define MAP_NAME fec_map -#define MAP_ELE_T fec_t -#define MAP_PREV map.prev -#define MAP_NEXT map.next -#define MAP_IDX_T uint -#define MAP_OPTIMIZE_RANDOM_ACCESS_REMOVAL 1 -#include "../../util/tmpl/fd_map_chain.c" - -#define DLIST_NAME fec_dlist -#define DLIST_ELE_T fec_t -#define DLIST_PREV dlist.prev -#define DLIST_NEXT dlist.next -#define DLIST_IDX_T uint -#include "../../util/tmpl/fd_dlist.c" - -typedef struct { - ulong slot; - uint vtr_idx; -} xid_t; - -struct prf { - xid_t key; - uint next; - struct { - uint prev; - uint next; - } map; - struct { - uint prev; - uint next; - } dlist; - uchar chunk_cnt; - uchar chunk_idx[ FD_EQVOC_CHUNK_CNT - 1 ]; - ushort chunk_len[ FD_EQVOC_CHUNK_CNT - 1 ]; - uchar chunks [ FD_EQVOC_CHUNK_CNT - 1 ][ FD_EQVOC_CHUNK_SZ ]; -}; -typedef struct prf prf_t; - -#define POOL_NAME prf_pool -#define POOL_LAZY 1 -#define POOL_T prf_t -#define POOL_IDX_T uint -#include "../../util/tmpl/fd_pool.c" - -#define MAP_NAME prf_map -#define MAP_ELE_T prf_t -#define MAP_KEY_T xid_t -#define MAP_PREV map.prev -#define MAP_NEXT map.next -#define MAP_IDX_T uint -#define MAP_KEY_EQ(k0,k1) ((((k0)->slot)==((k1)->slot)) & (((k0)->vtr_idx)==((k1)->vtr_idx))) -#define MAP_KEY_HASH(key,seed) fd_ulong_hash( ((key)->slot) ^ ((key)->vtr_idx) ^ (seed) ) -#define MAP_OPTIMIZE_RANDOM_ACCESS_REMOVAL 1 -#include "../../util/tmpl/fd_map_chain.c" - -#define DLIST_NAME prf_dlist -#define DLIST_ELE_T prf_t -#define DLIST_PREV dlist.prev -#define DLIST_NEXT dlist.next -#define DLIST_IDX_T uint -#include "../../util/tmpl/fd_dlist.c" - -struct vtr { - fd_pubkey_t from; - uint next; /* pool next; reused as kept flag during update_voters */ - struct { - uint prev; - uint next; - } map; - struct { - uint prev; - uint next; - } dlist; - ulong prf_dlist_cnt; - prf_dlist_t * prf_dlist; -}; -typedef struct vtr vtr_t; - -#define POOL_NAME vtr_pool -#define POOL_LAZY 1 -#define POOL_T vtr_t -#define POOL_IDX_T uint -#include "../../util/tmpl/fd_pool.c" - -#define MAP_NAME vtr_map -#define MAP_ELE_T vtr_t -#define MAP_KEY_T fd_pubkey_t -#define MAP_KEY from -#define MAP_PREV map.prev -#define MAP_NEXT map.next -#define MAP_IDX_T uint -#define MAP_KEY_EQ(k0,k1) (!memcmp((k0)->key,(k1)->key,sizeof(fd_pubkey_t))) -#define MAP_KEY_HASH(key,seed) ((ulong)((key)->ul[1]^(seed))) -#define MAP_OPTIMIZE_RANDOM_ACCESS_REMOVAL 1 -#include "../../util/tmpl/fd_map_chain.c" - -#define DLIST_NAME vtr_dlist -#define DLIST_ELE_T vtr_t -#define DLIST_PREV dlist.prev -#define DLIST_NEXT dlist.next -#define DLIST_IDX_T uint -#include "../../util/tmpl/fd_dlist.c" - -struct fd_eqvoc { - - /* copy */ - - ulong dup_max; - ulong fec_max; - ulong per_vtr_max; - ulong vtr_max; - - /* owned */ - - fd_sha512_t * sha512; - void * bmtree_mem; - dup_t * dup_pool; - dup_map_t * dup_map; - dup_dlist_t * dup_dlist; - fec_t * fec_pool; - fec_map_t * fec_map; - fec_dlist_t * fec_dlist; - prf_t * prf_pool; - prf_map_t * prf_map; - vtr_t * vtr_pool; - vtr_map_t * vtr_map; - vtr_dlist_t * vtr_dlist; -}; -typedef struct fd_eqvoc fd_eqvoc_t; - -ulong -fd_eqvoc_align( void ) { - return 128UL; -} - -ulong -fd_eqvoc_footprint( ulong dup_max, - ulong fec_max, - ulong per_vtr_max, - ulong vtr_max ) { - - dup_max = fd_ulong_pow2_up( dup_max ); - fec_max = fd_ulong_pow2_up( fec_max ); - per_vtr_max = fd_ulong_pow2_up( per_vtr_max ); - vtr_max = fd_ulong_pow2_up( vtr_max ); - if( FD_UNLIKELY( !dup_max || dup_max>UINT_MAX || - !fec_max || fec_max>UINT_MAX || - !per_vtr_max || !vtr_max || vtr_max>UINT_MAX || - per_vtr_max>UINT_MAX/vtr_max ) ) return 0UL; - ulong prf_max = per_vtr_max * vtr_max; - - ulong l = FD_LAYOUT_INIT; - l = FD_LAYOUT_APPEND( l, alignof(fd_eqvoc_t), sizeof(fd_eqvoc_t) ); - l = FD_LAYOUT_APPEND( l, fd_sha512_align(), fd_sha512_footprint() ); - l = FD_LAYOUT_APPEND( l, FD_BMTREE_COMMIT_ALIGN, FD_BMTREE_COMMIT_FOOTPRINT( FD_SHRED_MERKLE_LAYER_CNT ) ); - l = FD_LAYOUT_APPEND( l, dup_pool_align(), dup_pool_footprint( dup_max ) ); - l = FD_LAYOUT_APPEND( l, dup_map_align(), dup_map_footprint( dup_map_chain_cnt_est( dup_max ) ) ); - l = FD_LAYOUT_APPEND( l, dup_dlist_align(), dup_dlist_footprint() ); - l = FD_LAYOUT_APPEND( l, fec_pool_align(), fec_pool_footprint( fec_max ) ); - l = FD_LAYOUT_APPEND( l, fec_map_align(), fec_map_footprint( fec_map_chain_cnt_est( fec_max ) ) ); - l = FD_LAYOUT_APPEND( l, fec_dlist_align(), fec_dlist_footprint() ); - l = FD_LAYOUT_APPEND( l, prf_pool_align(), prf_pool_footprint( prf_max ) ); - l = FD_LAYOUT_APPEND( l, prf_map_align(), prf_map_footprint( prf_map_chain_cnt_est( prf_max ) ) ); - l = FD_LAYOUT_APPEND( l, vtr_pool_align(), vtr_pool_footprint( vtr_max ) ); - l = FD_LAYOUT_APPEND( l, vtr_map_align(), vtr_map_footprint( vtr_map_chain_cnt_est( vtr_max ) ) ); - l = FD_LAYOUT_APPEND( l, vtr_dlist_align(), vtr_dlist_footprint() ); - for( ulong i = 0UL; i < vtr_max; i++ ) { - l = FD_LAYOUT_APPEND( l, prf_dlist_align(), prf_dlist_footprint() ); - } - return FD_LAYOUT_FINI( l, fd_eqvoc_align() ); -} - -void * -fd_eqvoc_new( void * shmem, - ulong dup_max, - ulong fec_max, - ulong per_vtr_max, - ulong vtr_max, - ulong seed ) { - - if( FD_UNLIKELY( !shmem ) ) { - FD_LOG_WARNING(( "NULL mem" )); - return NULL; - } - - if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)shmem, fd_eqvoc_align() ) ) ) { - FD_LOG_WARNING(( "misaligned mem" )); - return NULL; - } - - ulong footprint = fd_eqvoc_footprint( dup_max, fec_max, per_vtr_max, vtr_max ); - if( FD_UNLIKELY( !footprint ) ) { - FD_LOG_WARNING(( "bad dup_max (%lu), fec_max (%lu), or vtr_max (%lu)", dup_max, fec_max, vtr_max )); - return NULL; - } - - dup_max = fd_ulong_pow2_up( dup_max ); - fec_max = fd_ulong_pow2_up( fec_max ); - per_vtr_max = fd_ulong_pow2_up( per_vtr_max ); - vtr_max = fd_ulong_pow2_up( vtr_max ); - ulong prf_max = per_vtr_max * vtr_max; - - FD_SCRATCH_ALLOC_INIT( l, shmem ); - void * eqvoc_mem = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_eqvoc_t), sizeof(fd_eqvoc_t) ); - void * sha512 = FD_SCRATCH_ALLOC_APPEND( l, fd_sha512_align(), fd_sha512_footprint() ); - void * bmtree_mem = FD_SCRATCH_ALLOC_APPEND( l, FD_BMTREE_COMMIT_ALIGN, FD_BMTREE_COMMIT_FOOTPRINT( FD_SHRED_MERKLE_LAYER_CNT ) ); - void * dup_pool = FD_SCRATCH_ALLOC_APPEND( l, dup_pool_align(), dup_pool_footprint( dup_max ) ); - void * dup_map = FD_SCRATCH_ALLOC_APPEND( l, dup_map_align(), dup_map_footprint( dup_map_chain_cnt_est( dup_max ) ) ); - void * dup_dlist = FD_SCRATCH_ALLOC_APPEND( l, dup_dlist_align(), dup_dlist_footprint() ); - void * fec_pool = FD_SCRATCH_ALLOC_APPEND( l, fec_pool_align(), fec_pool_footprint( fec_max ) ); - void * fec_map = FD_SCRATCH_ALLOC_APPEND( l, fec_map_align(), fec_map_footprint( fec_map_chain_cnt_est( fec_max ) ) ); - void * fec_dlist = FD_SCRATCH_ALLOC_APPEND( l, fec_dlist_align(), fec_dlist_footprint() ); - void * prf_pool = FD_SCRATCH_ALLOC_APPEND( l, prf_pool_align(), prf_pool_footprint( prf_max ) ); - void * prf_map = FD_SCRATCH_ALLOC_APPEND( l, prf_map_align(), prf_map_footprint( prf_map_chain_cnt_est( prf_max ) ) ); - void * vtr_pool = FD_SCRATCH_ALLOC_APPEND( l, vtr_pool_align(), vtr_pool_footprint( vtr_max ) ); - void * vtr_map = FD_SCRATCH_ALLOC_APPEND( l, vtr_map_align(), vtr_map_footprint( vtr_map_chain_cnt_est( vtr_max ) ) ); - void * vtr_dlist = FD_SCRATCH_ALLOC_APPEND( l, vtr_dlist_align(), vtr_dlist_footprint() ); - - fd_eqvoc_t * eqvoc = (fd_eqvoc_t *)eqvoc_mem; - eqvoc->dup_max = dup_max; - eqvoc->fec_max = fec_max; - eqvoc->per_vtr_max = per_vtr_max; - eqvoc->vtr_max = vtr_max; - - eqvoc->sha512 = fd_sha512_new( sha512 ); - eqvoc->bmtree_mem = bmtree_mem; - eqvoc->dup_pool = dup_pool_new ( dup_pool, dup_max ); - eqvoc->dup_map = dup_map_new ( dup_map, dup_map_chain_cnt_est( dup_max ), seed ); - eqvoc->dup_dlist = dup_dlist_new( dup_dlist ); - eqvoc->fec_pool = fec_pool_new ( fec_pool, fec_max ); - eqvoc->fec_map = fec_map_new ( fec_map, fec_map_chain_cnt_est( fec_max ), seed ); - eqvoc->fec_dlist = fec_dlist_new( fec_dlist ); - eqvoc->prf_pool = prf_pool_new ( prf_pool, prf_max ); - eqvoc->prf_map = prf_map_new ( prf_map, prf_map_chain_cnt_est( prf_max ), seed ); - eqvoc->vtr_pool = vtr_pool_new ( vtr_pool, vtr_max ); - eqvoc->vtr_map = vtr_map_new ( vtr_map, vtr_map_chain_cnt_est( vtr_max ), seed ); - eqvoc->vtr_dlist = vtr_dlist_new( vtr_dlist ); - - vtr_t * pool_join = vtr_pool_join( eqvoc->vtr_pool ); - for( ulong i = 0UL; i < vtr_max; i++ ) { - void * prf_dlist = FD_SCRATCH_ALLOC_APPEND( l, prf_dlist_align(), prf_dlist_footprint() ); - pool_join[i].prf_dlist_cnt = 0; - pool_join[i].prf_dlist = prf_dlist_new( prf_dlist ); - } - FD_TEST( FD_SCRATCH_ALLOC_FINI( l, fd_eqvoc_align() )==(ulong)shmem + footprint ); - - return shmem; -} - -fd_eqvoc_t * -fd_eqvoc_join( void * sheqvoc ) { - - if( FD_UNLIKELY( !sheqvoc ) ) { - FD_LOG_WARNING(( "NULL eqvoc" )); - return NULL; - } - - if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)sheqvoc, fd_eqvoc_align() ) ) ) { - FD_LOG_WARNING(( "misaligned eqvoc" )); - return NULL; - } - - fd_eqvoc_t * eqvoc = (fd_eqvoc_t *)sheqvoc; - eqvoc->sha512 = fd_sha512_join( eqvoc->sha512 ); - /* bmtree */ - eqvoc->dup_pool = dup_pool_join ( eqvoc->dup_pool ); - eqvoc->dup_map = dup_map_join ( eqvoc->dup_map ); - eqvoc->dup_dlist = dup_dlist_join( eqvoc->dup_dlist ); - eqvoc->fec_pool = fec_pool_join ( eqvoc->fec_pool ); - eqvoc->fec_map = fec_map_join ( eqvoc->fec_map ); - eqvoc->fec_dlist = fec_dlist_join( eqvoc->fec_dlist ); - eqvoc->prf_pool = prf_pool_join ( eqvoc->prf_pool ); - eqvoc->prf_map = prf_map_join ( eqvoc->prf_map ); - eqvoc->vtr_pool = vtr_pool_join ( eqvoc->vtr_pool ); - eqvoc->vtr_map = vtr_map_join ( eqvoc->vtr_map ); - eqvoc->vtr_dlist = vtr_dlist_join( eqvoc->vtr_dlist ); - for( ulong i = 0UL; i < eqvoc->vtr_max; i++ ) { - eqvoc->vtr_pool[i].prf_dlist = prf_dlist_join( eqvoc->vtr_pool[i].prf_dlist ); - } - - return (fd_eqvoc_t *)sheqvoc; -} - -void * -fd_eqvoc_leave( fd_eqvoc_t const * eqvoc ) { - - if( FD_UNLIKELY( !eqvoc ) ) { - FD_LOG_WARNING(( "NULL eqvoc" )); - return NULL; - } - - return (void *)eqvoc; -} - -void * -fd_eqvoc_delete( void * eqvoc ) { - - if( FD_UNLIKELY( !eqvoc ) ) { - FD_LOG_WARNING(( "NULL eqvoc" )); - return NULL; - } - - if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)eqvoc, fd_eqvoc_align() ) ) ) { - FD_LOG_WARNING(( "misaligned eqvoc" )); - return NULL; - } - - return eqvoc; -} - -static dup_t * -dup_query( fd_eqvoc_t * eqvoc, - ulong slot ) { - dup_t * dup = dup_map_ele_query( eqvoc->dup_map, &slot, NULL, eqvoc->dup_pool ); - if( FD_LIKELY( dup ) ) { - dup_dlist_ele_remove( eqvoc->dup_dlist, dup, eqvoc->dup_pool ); - dup_dlist_ele_push_tail( eqvoc->dup_dlist, dup, eqvoc->dup_pool ); - } - return dup; -} - -static fec_t * -fec_query( fd_eqvoc_t * eqvoc, - ulong slot, - ulong fec_set_idx ) { - ulong key = slot << 32 | fec_set_idx; - fec_t * fec = fec_map_ele_query( eqvoc->fec_map, &key, NULL, eqvoc->fec_pool ); - if( FD_LIKELY( fec ) ) { - fec_dlist_ele_remove( eqvoc->fec_dlist, fec, eqvoc->fec_pool ); - fec_dlist_ele_push_tail( eqvoc->fec_dlist, fec, eqvoc->fec_pool ); - } - return fec; -} - -static prf_t * -prf_query( fd_eqvoc_t * eqvoc, - vtr_t * vtr, - ulong slot ) { - xid_t key = { .slot = slot, .vtr_idx = (uint)vtr_pool_idx( eqvoc->vtr_pool, vtr ) }; - prf_t * prf = prf_map_ele_query( eqvoc->prf_map, &key, NULL, eqvoc->prf_pool ); - if( FD_LIKELY( prf ) ) { - prf_dlist_ele_remove( vtr->prf_dlist, prf, eqvoc->prf_pool ); - prf_dlist_ele_push_tail( vtr->prf_dlist, prf, eqvoc->prf_pool ); - } - return prf; -} - -static dup_t * -dup_insert( fd_eqvoc_t * eqvoc, - ulong slot ) { - - /* FIFO evict if full. Invariant: iff in dlist then in map / pool. */ - - if( FD_UNLIKELY( !dup_pool_free( eqvoc->dup_pool ) ) ) { - dup_t * dup = dup_dlist_ele_pop_head( eqvoc->dup_dlist, eqvoc->dup_pool ); - dup_map_ele_remove_fast( eqvoc->dup_map, dup, eqvoc->dup_pool ); - dup_pool_ele_release( eqvoc->dup_pool, dup ); - } - - /* Insert. Invariant: pool free => map / dlist free. */ - - dup_t * dup = dup_pool_ele_acquire( eqvoc->dup_pool ); - dup->slot = slot; - dup_map_ele_insert( eqvoc->dup_map, dup, eqvoc->dup_pool ); - dup_dlist_ele_push_tail( eqvoc->dup_dlist, dup, eqvoc->dup_pool ); - return dup; -} - -static fec_t * -fec_insert( fd_eqvoc_t * eqvoc, - ulong slot, - uint fec_set_idx ) { - - ulong key = slot << 32 | fec_set_idx; - - /* FIFO evict if full. Invariant: iff in dlist then in map / pool. */ - - if( FD_UNLIKELY( !fec_pool_free( eqvoc->fec_pool ) ) ) { - fec_t * fec = fec_dlist_ele_pop_head( eqvoc->fec_dlist, eqvoc->fec_pool ); - fec_map_ele_remove_fast( eqvoc->fec_map, fec, eqvoc->fec_pool ); - fec_pool_ele_release( eqvoc->fec_pool, fec ); - } - - /* Insert. Invariant: pool free => map / dlist free. */ - - fec_t * fec = fec_pool_ele_acquire( eqvoc->fec_pool ); - fec->key = key; - fec_map_ele_insert( eqvoc->fec_map, fec, eqvoc->fec_pool ); - fec_dlist_ele_push_tail( eqvoc->fec_dlist, fec, eqvoc->fec_pool ); - return fec; -} - -static prf_t * -prf_insert( fd_eqvoc_t * eqvoc, - vtr_t * vtr, - ulong slot ) { - - /* Each from pubkey in gossip is limited to per_vtr_max proofs. - If we receive more than per_vtr_max from one pubkey, FIFO evict. - We group by pubkey to prevent a single pubkey from spamming - junk proofs. */ - - if( FD_UNLIKELY( vtr->prf_dlist_cnt==eqvoc->per_vtr_max ) ) { - prf_t * evict = prf_dlist_ele_pop_head( vtr->prf_dlist, eqvoc->prf_pool ); - prf_map_ele_remove_fast( eqvoc->prf_map, evict, eqvoc->prf_pool ); - prf_pool_ele_release( eqvoc->prf_pool, evict ); - vtr->prf_dlist_cnt--; - } - - xid_t key = { .slot = slot, .vtr_idx = (uint)vtr_pool_idx( eqvoc->vtr_pool, vtr ) }; - prf_t * prf = prf_pool_ele_acquire( eqvoc->prf_pool ); - prf->key = key; - prf->chunk_cnt = 0; - prf_map_ele_insert( eqvoc->prf_map, prf, eqvoc->prf_pool ); - prf_dlist_ele_push_tail( vtr->prf_dlist, prf, eqvoc->prf_pool ); - vtr->prf_dlist_cnt++; - return prf; -} - -static int -is_last_shred( fd_shred_t const * shred ) { - return fd_shred_is_data( fd_shred_type( shred->variant ) ) && shred->data.flags & FD_SHRED_DATA_FLAG_SLOT_COMPLETE; -} - -/* construct_proof constructs a DuplicateShred proof from shred1 and - shred2. Assumes shred1 and shred2 have already been verified via - verify_proof. On return, chunks_out will be populated with the - serialized format of the proof. - - [ shred1_sz (8 bytes) | shred1 | shred2_sz (8 bytes) | shred2 ] - - Caller supplies `chunks_out`, which is an array that MUST contain - FD_EQVOC_CHUNK_CNT elements. */ - -static void -construct_proof( fd_shred_t const * shred1, - fd_shred_t const * shred2, - fd_gossip_duplicate_shred_t chunks_out[static FD_EQVOC_CHUNK_CNT] ) { - - for (uchar i = 0; i < FD_EQVOC_CHUNK_CNT; i++ ) { - chunks_out[i].index = i; - chunks_out[i].slot = shred1->slot; - chunks_out[i].num_chunks = FD_EQVOC_CHUNK_CNT; - chunks_out[i].chunk_index = i; - } - - ulong shred1_sz = fd_shred_sz( shred1 ); - ulong shred2_sz = fd_shred_sz( shred2 ); - - /* Populate chunk0 */ - - FD_STORE( ulong, chunks_out[0].chunk, shred1_sz ); - memcpy( chunks_out[0].chunk + sizeof(ulong), shred1, FD_EQVOC_CHUNK_SZ - sizeof(ulong) ); - chunks_out[0].chunk_len = FD_EQVOC_CHUNK_SZ; - - /* Populate chunk1 */ - - ulong shred1_off = FD_EQVOC_CHUNK_SZ - sizeof(ulong); - ulong shred1_rem = shred1_sz - shred1_off; - memcpy( chunks_out[1].chunk, (uchar *)shred1 + shred1_off, shred1_rem ); - FD_STORE( ulong, chunks_out[1].chunk + shred1_rem, shred2_sz ); - ulong chunk1_off = shred1_rem + sizeof(ulong); - ulong chunk1_rem = FD_EQVOC_CHUNK_SZ - chunk1_off; - memcpy( chunks_out[1].chunk + chunk1_off, shred2, chunk1_rem ); - chunks_out[1].chunk_len = FD_EQVOC_CHUNK_SZ; - - /* Populate chunk2 */ - - ulong shred2_off = chunk1_rem; - ulong shred2_rem = shred2_sz - shred2_off; - memcpy( chunks_out[2].chunk, (uchar *)shred2 + shred2_off, shred2_rem ); - chunks_out[2].chunk_len = shred2_rem; -} - -/* verify_proof verifies that the two shreds contained in `proof` do in - fact equivocate. The two shreds came from untrusted gossip msgs, so - it needs validation. - - Returns: FD_EQVOC_SUCCESS (1) if equivocation detected, - FD_EQVOC_IGNORED (0) if not, or FD_EQVOC_ERR_{...} (<0) if - the shreds were not valid inputs (untrusted path only). - - The implementation mirrors the Agave version very closely. See: - https://github.com/anza-xyz/agave/blob/v3.1/gossip/src/duplicate_shred.rs#L137-L142 - - Two shreds equivocate if they satisfy any of the following: - - 1. Both shreds specify the same index and shred type, however their - payloads differ. - 2. Both shreds specify the same FEC set, however their merkle roots - differ. - 3. Both shreds specify the same FEC set and are coding shreds, - however their erasure configs conflict. - 4. The shreds specify different FEC sets, the lower index shred is a - coding shred, and its erasure meta indicates an FEC set overlap. - 5. The shreds specify different FEC sets, the lower index shred has a - merkle root that is not equal to the chained merkle root of the - higher index shred. - 6. The shreds are data shreds with different indices and the shred - with the lower index has the LAST_SHRED_IN_SLOT flag set. - - Ref: - https://github.com/solana-foundation/solana-improvement-documents/blob/main/proposals/0204-slashable-event-verification.md#proof-verification - - Note: two shreds are in the same FEC set if they have the same - verified and FEC set index. - - To prevent false positives, this function also performs the following - input validation on the shreds: - - 1. shred1 and shred2 are for the same verified. - 2. shred1 and shred2 are both the expected shred_version. - 3. shred1 and shred2 are either chained merkle or chained resigned - merkle variants. - 4. shred1 and shred2 contain valid signatures signed by the same - producer pubkey. - - If any of the above input validation fails, this function returns - FD_EQVOC_ERR_{...}. */ - -static int -verify_proof( fd_eqvoc_t * eqvoc, - ushort shred_version, - fd_epoch_leaders_t const * leader_schedule, - fd_shred_t const * shred1, - fd_shred_t const * shred2 ) { - - /* Must be same slot. */ - - if( FD_UNLIKELY( shred1->slot != shred2->slot ) ) return FD_EQVOC_ERR_SLOT; - - /* Must be same shred version. */ - - if( FD_UNLIKELY( shred1->version != shred_version ) ) return FD_EQVOC_ERR_VERSION; - if( FD_UNLIKELY( shred2->version != shred_version ) ) return FD_EQVOC_ERR_VERSION; - - /* Must be chained merkle shreds. */ - - if( FD_UNLIKELY( !fd_shred_is_chained ( fd_shred_type( shred1->variant ) ) ) ) return FD_EQVOC_ERR_TYPE; - if( FD_UNLIKELY( !fd_shred_is_chained ( fd_shred_type( shred2->variant ) ) ) ) return FD_EQVOC_ERR_TYPE; - - /* Must have valid merkle roots. */ - - fd_bmtree_node_t mr1; - fd_bmtree_node_t mr2; - if( FD_UNLIKELY( !fd_shred_merkle_root( shred1, eqvoc->bmtree_mem, &mr1 ) ) ) return FD_EQVOC_ERR_MERKLE; - if( FD_UNLIKELY( !fd_shred_merkle_root( shred2, eqvoc->bmtree_mem, &mr2 ) ) ) return FD_EQVOC_ERR_MERKLE; - - /* Must sigverify (if leader_schedule provided). */ - - if( FD_LIKELY( leader_schedule ) ) { - fd_pubkey_t const * leader = fd_epoch_leaders_get( leader_schedule, shred1->slot ); - if( FD_UNLIKELY( !leader ) ) return FD_EQVOC_ERR_SIG; - fd_sha512_t _sha512[1]; - fd_sha512_t * sha512 = fd_sha512_join( fd_sha512_new( _sha512 ) ); - if( FD_UNLIKELY( FD_ED25519_SUCCESS != fd_ed25519_verify( mr1.hash, 32UL, shred1->signature, leader->uc, sha512 ) || - FD_ED25519_SUCCESS != fd_ed25519_verify( mr2.hash, 32UL, shred2->signature, leader->uc, sha512 ) ) ) { - return FD_EQVOC_ERR_SIG; - } - } - - /* If both are data shreds, then check for last shred conflicts. */ - - if( FD_LIKELY( fd_shred_is_data( fd_shred_type( shred1->variant ) ) && fd_shred_is_data( fd_shred_type( shred2->variant ) ) ) ) { - if( FD_LIKELY( ( shred1->data.flags & FD_SHRED_DATA_FLAG_SLOT_COMPLETE && shred2->idx > shred1->idx ) || - ( shred2->data.flags & FD_SHRED_DATA_FLAG_SLOT_COMPLETE && shred1->idx > shred2->idx ) ) ) { - return FD_EQVOC_SUCCESS; - } - } - - /* If both shreds are in the same FEC set, then check for merkle root - conflicts. */ - - if( FD_UNLIKELY( shred1->fec_set_idx == shred2->fec_set_idx ) ) { - if( FD_LIKELY( 0!=memcmp( mr1.hash, mr2.hash, sizeof(mr1.hash)) ) ) { - return FD_EQVOC_SUCCESS; - } - } - - return FD_EQVOC_IGNORED; -} - -int -fd_eqvoc_shred_insert( fd_eqvoc_t * eqvoc, - int shred_hint, - fd_shred_t const * shred, - fd_gossip_duplicate_shred_t chunks_out[static FD_EQVOC_CHUNK_CNT] ) { - - ulong slot = shred->slot; - dup_t * dup = dup_query( eqvoc, slot ); - if( FD_UNLIKELY( dup ) ) return 0; /* no proof constructed */ - - if( FD_UNLIKELY( shred_hint ) ) { - - /* shred tile has hinted to us that this shred equivocates */ - - fec_t * fec = fec_query( eqvoc, shred->slot, shred->fec_set_idx ); - if( FD_UNLIKELY( !fec ) ) return 0; /* it's possible we already evicted this FEC set */ - construct_proof( &fec->sample_shred, shred, chunks_out ); - dup_insert( eqvoc, slot ); - fec_dlist_ele_remove( eqvoc->fec_dlist, fec, eqvoc->fec_pool ); - fec_map_ele_remove_fast( eqvoc->fec_map, fec, eqvoc->fec_pool ); - fec_pool_ele_release( eqvoc->fec_pool, fec ); - return 1; /* proof constructed */ - - } else { - - /* last index conflicts are not hinted by shred */ - - fec_t * last = fec_query( eqvoc, shred->slot, UINT_MAX ); - if( FD_UNLIKELY( !last ) ) { - last = fec_insert( eqvoc, slot, UINT_MAX ); /* specially index the last shred in a slot */ - fd_memcpy( &last->sample_shred, shred, fd_shred_sz( shred ) ); - } else if( FD_UNLIKELY( ( is_last_shred( shred ) && shred->idx < last->sample_shred.idx ) || - ( is_last_shred( &last->sample_shred ) && shred->idx > last->sample_shred.idx ) ) ) { - construct_proof( shred, &last->sample_shred, chunks_out ); - dup_insert( eqvoc, slot ); - return 1; - } else if( FD_UNLIKELY( shred->idx > last->sample_shred.idx ) ) { - fd_memcpy( &last->sample_shred, shred, fd_shred_sz( shred ) ); - } - - fec_t * fec = fec_query( eqvoc, shred->slot, shred->fec_set_idx ); - if( FD_UNLIKELY( !fec ) ) { - fec = fec_insert( eqvoc, shred->slot, shred->fec_set_idx ); - fd_memcpy( &fec->sample_shred, shred, fd_shred_sz( shred ) ); - } - - return 0; - } -} - -int -fd_eqvoc_chunk_insert( fd_eqvoc_t * eqvoc, - ulong root, - ushort shred_version, - fd_epoch_leaders_t const * leader_schedule, - fd_pubkey_t const * from, - fd_gossip_duplicate_shred_t const * chunk, - fd_gossip_duplicate_shred_t chunks_out[static FD_EQVOC_CHUNK_CNT] ) { - - if( FD_UNLIKELY( chunk->slot <= root ) ) return FD_EQVOC_ERR_CHUNK_SLOT; - - vtr_t * vtr = vtr_map_ele_query( eqvoc->vtr_map, from, NULL, eqvoc->vtr_pool ); - if( FD_UNLIKELY( !vtr ) ) return FD_EQVOC_ERR_CHUNK_FROM; - - if( FD_UNLIKELY( chunk->num_chunks !=FD_EQVOC_CHUNK_CNT ) ) return FD_EQVOC_ERR_CHUNK_CNT; - if( FD_UNLIKELY( chunk->chunk_index>=FD_EQVOC_CHUNK_CNT ) ) return FD_EQVOC_ERR_CHUNK_IDX; - - if( FD_UNLIKELY( chunk->chunk_index==0 && chunk->chunk_len!=FD_EQVOC_CHUNK0_LEN ) ) return FD_EQVOC_ERR_CHUNK_LEN; - if( FD_UNLIKELY( chunk->chunk_index==1 && chunk->chunk_len!=FD_EQVOC_CHUNK1_LEN ) ) return FD_EQVOC_ERR_CHUNK_LEN; - if( FD_UNLIKELY( chunk->chunk_index==2 && chunk->chunk_len!=FD_EQVOC_CHUNK2_LEN_CC && - chunk->chunk_len!=FD_EQVOC_CHUNK2_LEN_DD && - chunk->chunk_len!=FD_EQVOC_CHUNK2_LEN_DC && - chunk->chunk_len!=FD_EQVOC_CHUNK2_LEN_CD ) ) return FD_EQVOC_ERR_CHUNK_LEN; - - if( FD_UNLIKELY( dup_query( eqvoc, chunk->slot ) ) ) return FD_EQVOC_IGNORED; /* already verified an equivocation proof for this slot */ - - prf_t * prf = prf_query( eqvoc, vtr, chunk->slot ); - if( FD_UNLIKELY( !prf ) ) prf = prf_insert( eqvoc, vtr, chunk->slot ); - for( uchar i = 0U; i < prf->chunk_cnt; i++ ) { - if( FD_UNLIKELY( prf->chunk_idx[ i ]==chunk->chunk_index ) ) return FD_EQVOC_IGNORED; - } - - if( FD_LIKELY( prf->chunk_cntchunk_cnt++; - prf->chunk_idx[ i ] = chunk->chunk_index; - prf->chunk_len[ i ] = (ushort)chunk->chunk_len; - fd_memcpy( prf->chunks[ i ], chunk->chunk, chunk->chunk_len ); - return FD_EQVOC_IGNORED; - } - - uchar buf[ 2 * FD_SHRED_MAX_SZ + 2 * sizeof(ulong) ] __attribute__((aligned(8))); - fd_memcpy( buf + chunk->chunk_index * FD_EQVOC_CHUNK_SZ, chunk->chunk, chunk->chunk_len ); - ulong buf_sz = chunk->chunk_len; - for( uchar i = 0U; i < prf->chunk_cnt; i++ ) { - fd_memcpy( buf + prf->chunk_idx[ i ] * FD_EQVOC_CHUNK_SZ, prf->chunks[ i ], prf->chunk_len[ i ] ); - buf_sz += prf->chunk_len[ i ]; - } - - int err = FD_EQVOC_ERR_SERDE; ulong off = 0; - - if( FD_UNLIKELY( buf_sz - off < sizeof(ulong) ) ) goto cleanup; - ulong shred1_sz = fd_ulong_load_8( buf ); - off += sizeof(ulong); - - if( FD_UNLIKELY( buf_sz - off < shred1_sz ) ) goto cleanup; - /* We use FD_SHRED_PROTOCOL_BLK_MAX as max_shred_idx because that is - what Agave does here (Shred::new_from_serialized_shred in - into_shreds): - https://github.com/anza-xyz/agave/blob/v4.2/gossip/src/duplicate_shred.rs#L350-L351 */ - fd_shred_t const * shred1 = fd_shred_parse( buf + off, shred1_sz, FD_SHRED_PROTOCOL_BLK_MAX ); - if( FD_UNLIKELY( !shred1 || fd_shred_sz( shred1 )!=shred1_sz ) ) goto cleanup; /* check the sz matches parsed shred's type */ - off += shred1_sz; - - if( FD_UNLIKELY( buf_sz - off < sizeof(ulong) ) ) goto cleanup; - ulong shred2_sz = fd_ulong_load_8( buf + off ); - off += sizeof(ulong); - - if( FD_UNLIKELY( buf_sz - off < shred2_sz ) ) goto cleanup; - fd_shred_t const * shred2 = fd_shred_parse( buf + off, shred2_sz, FD_SHRED_PROTOCOL_BLK_MAX ); - if( FD_UNLIKELY( !shred2 || fd_shred_sz( shred2 )!=shred2_sz ) ) goto cleanup; /* check the sz matches parsed shred's type */ - off += shred2_sz; - - if( FD_UNLIKELY( off!=buf_sz ) ) goto cleanup; - - if( FD_UNLIKELY( shred1->slot != chunk->slot || shred2->slot != chunk->slot ) ) goto cleanup; - - err = verify_proof( eqvoc, shred_version, leader_schedule, shred1, shred2 ); - if( FD_UNLIKELY( err==FD_EQVOC_SUCCESS ) ) { - construct_proof( shred1, shred2, chunks_out ); - dup_insert( eqvoc, chunk->slot ); - } - -cleanup:; - prf_dlist_ele_remove( vtr->prf_dlist, prf, eqvoc->prf_pool ); - prf_map_ele_remove_fast( eqvoc->prf_map, prf, eqvoc->prf_pool ); - prf_pool_ele_release( eqvoc->prf_pool, prf ); - vtr->prf_dlist_cnt--; - return err; -} - -int -fd_eqvoc_proof_verified( fd_eqvoc_t * eqvoc, - ulong slot ) { - return !!dup_query( eqvoc, slot ); -} - -void -fd_eqvoc_update_voters( fd_eqvoc_t * eqvoc, - fd_pubkey_t const * id_keys, - ulong cnt ) { - - for( vtr_dlist_iter_t iter = vtr_dlist_iter_fwd_init( eqvoc->vtr_dlist, eqvoc->vtr_pool ); - !vtr_dlist_iter_done( iter, eqvoc->vtr_dlist, eqvoc->vtr_pool ); - iter = vtr_dlist_iter_fwd_next( iter, eqvoc->vtr_dlist, eqvoc->vtr_pool ) ) { - eqvoc->vtr_pool[iter].next = 1; /* mark for removal */ - } - - /* First pass: unmark kept voters from being released. */ - - for( ulong i=0UL; ivtr_map, id, NULL, eqvoc->vtr_pool ); - if( FD_LIKELY( vtr ) ) { - vtr_dlist_ele_remove( eqvoc->vtr_dlist, vtr, eqvoc->vtr_pool ); - vtr->next = 0; /* unmark for removal */ - vtr_dlist_ele_push_tail( eqvoc->vtr_dlist, vtr, eqvoc->vtr_pool ); - } - } - - /* Pop and release marked voters until the first unmarked voter. */ - - while( FD_LIKELY( !vtr_dlist_is_empty( eqvoc->vtr_dlist, eqvoc->vtr_pool ) ) ) { - vtr_t * vtr = vtr_dlist_ele_pop_head( eqvoc->vtr_dlist, eqvoc->vtr_pool ); - if( FD_UNLIKELY( !vtr->next ) ) { /* can short-circuit since all the existing and new voters were appended */ - vtr_dlist_ele_push_tail( eqvoc->vtr_dlist, vtr, eqvoc->vtr_pool ); - break; - } - while( FD_LIKELY( !prf_dlist_is_empty( vtr->prf_dlist, eqvoc->prf_pool ) ) ) { - prf_t * prf = prf_dlist_ele_pop_head( vtr->prf_dlist, eqvoc->prf_pool ); - prf_map_ele_remove_fast( eqvoc->prf_map, prf, eqvoc->prf_pool ); - prf_pool_ele_release( eqvoc->prf_pool, prf ); - } - vtr_map_ele_remove_fast( eqvoc->vtr_map, vtr, eqvoc->vtr_pool ); - vtr_pool_ele_release( eqvoc->vtr_pool, vtr ); - } - - /* Second pass: acquire and insert new voters. */ - - for( ulong i=0UL; ivtr_map, id, NULL, eqvoc->vtr_pool ) ) ) continue; - vtr_t * vtr = vtr_pool_ele_acquire( eqvoc->vtr_pool ); - vtr->from = *id; - vtr->prf_dlist_cnt = 0; - vtr->next = 0; - vtr_map_ele_insert( eqvoc->vtr_map, vtr, eqvoc->vtr_pool ); - vtr_dlist_ele_push_tail( eqvoc->vtr_dlist, vtr, eqvoc->vtr_pool ); - } -} diff --git a/src/choreo/eqvoc/fd_eqvoc.h b/src/choreo/eqvoc/fd_eqvoc.h deleted file mode 100644 index 1ff712b0981..00000000000 --- a/src/choreo/eqvoc/fd_eqvoc.h +++ /dev/null @@ -1,195 +0,0 @@ -#ifndef HEADER_fd_src_choreo_eqvoc_fd_eqvoc_h -#define HEADER_fd_src_choreo_eqvoc_fd_eqvoc_h - -#include "../fd_choreo_base.h" -#include "../../ballet/shred/fd_shred.h" -#include "../../flamenco/leaders/fd_leaders.h" -#include "../../flamenco/gossip/fd_gossip_message.h" - -/* fd_eqvoc presents an API for detecting and sending & receiving proofs - of equivocation. Agave calls "equivocation" duplicates, including in - their type names, so these terms will be used interchangeably for the - sake of conformity. - - Equivocation is when a leader produces two or more blocks for the - same slot. Proving equivocation does not require the complete blocks - however; in fact, only two shreds are required. The idea is these - shreds conflict in a way that implies equivocating blocks for a slot. - See `verify_proof` in fd_eqvoc.c for details. */ - -#define FD_EQVOC_SUCCESS ( 1) /* successful proof verification */ -#define FD_EQVOC_IGNORED ( 0) /* proof ignored */ -#define FD_EQVOC_ERR_SERDE (-1) /* invalid serialization */ -#define FD_EQVOC_ERR_SLOT (-2) /* shreds were for different slots */ -#define FD_EQVOC_ERR_VERSION (-3) /* either shred had wrong shred version */ -#define FD_EQVOC_ERR_TYPE (-4) /* wrong shred type (must be chained merkle) */ -#define FD_EQVOC_ERR_MERKLE (-5) /* failed to derive merkle root */ -#define FD_EQVOC_ERR_SIG (-6) /* failed to sigverify */ - -/* chunk_insert errors */ - -#define FD_EQVOC_ERR_CHUNK_CNT (-7) /* num_chunks != FD_EQVOC_CHUNK_CNT */ -#define FD_EQVOC_ERR_CHUNK_IDX (-8) /* chunk_index >= FD_EQVOC_CHUNK_CNT */ -#define FD_EQVOC_ERR_CHUNK_LEN (-9) /* chunk_len does not match expected length for chunk_index */ -#define FD_EQVOC_ERR_CHUNK_FROM (-10) /* unrecognized from address */ -#define FD_EQVOC_ERR_CHUNK_SLOT (-11) /* slot older than root */ - -/* FD_EQVOC_CHUNK_CNT: the count of chunks is hardcoded because Agave - discards any chunks where count != 3 (even though technically the - schema supports it). - - See: https://github.com/anza-xyz/agave/blob/v3.1/gossip/src/duplicate_shred_handler.rs#L21 */ - -#define FD_EQVOC_CHUNK_CNT (3) - -/* FD_EQVOC_CHUNK_SZ: the size of data in each chunk Firedancer produces - in a DuplicateShred message is derived below. - - IPv6 MTU - IP / UDP headers = 1232 - DuplicateShredMaxPayloadSize = 1232 - 115 - DuplicateShred headers = 63 - - See: https://github.com/anza-xyz/agave/blob/v2.0.3/gossip/src/cluster_info.rs#L113 */ - -#define FD_EQVOC_CHUNK_SZ (1232UL - 115UL - 63UL) -FD_STATIC_ASSERT( FD_EQVOC_CHUNK_SZ<=sizeof(((fd_gossip_duplicate_shred_t*)0)->chunk), "DuplicateShred chunk max mismatch" ); - -/* FD_EQVOC_CHUNK{0,1,2}_LEN: the chunk lengths for each of the 3 chunks - in a DuplicateShred proof. The memory layout is: - - [ shred1_sz (8 bytes) | shred1 | shred2_sz (8 bytes) | shred2 ] - - Chunks 0 and 1 are always FD_EQVOC_CHUNK_SZ bytes. Chunk 2 gets - whatever bytes remain, which depends on the shred types: - - CC = code + code (both FD_SHRED_MAX_SZ) - DD = data + data (both FD_SHRED_MIN_SZ) - DC = data + code (FD_SHRED_MIN_SZ + FD_SHRED_MAX_SZ) - CD = same as above, reversed - - Firedancer is particularly strict with the validation of duplicate - shred chunks. Even though the schema supports both a variable-length - and a variable-number of chunks, Agave restricts duplicate shred msgs - to have 3 chunks (as mentioned above). Firedancer chooses to further - restrict chunks to exactly how vanilla Agave implements serialization - (there is no reason for an honest sender to "mod" the code nor reason - to even support variable-length in the schema in the first place). - - Firedancer might miss a valid modded duplicate shred proof, but their - proof would propagate from other validators too (and gossip tx is - unreliable and not strictly required for the protocol to work). - - Agave validation: https://github.com/anza-xyz/agave/blob/v3.1/gossip/src/duplicate_shred.rs#L262-L268 */ - -#define FD_EQVOC_CHUNK0_LEN FD_EQVOC_CHUNK_SZ -#define FD_EQVOC_CHUNK1_LEN FD_EQVOC_CHUNK_SZ -#define FD_EQVOC_CHUNK2_LEN_CC (2UL * sizeof(ulong) + 2UL * FD_SHRED_MAX_SZ - 2UL * FD_EQVOC_CHUNK_SZ) -#define FD_EQVOC_CHUNK2_LEN_DD (2UL * sizeof(ulong) + 2UL * FD_SHRED_MIN_SZ - 2UL * FD_EQVOC_CHUNK_SZ) -#define FD_EQVOC_CHUNK2_LEN_DC (2UL * sizeof(ulong) + FD_SHRED_MIN_SZ + FD_SHRED_MAX_SZ - 2UL * FD_EQVOC_CHUNK_SZ) -#define FD_EQVOC_CHUNK2_LEN_CD (FD_EQVOC_CHUNK2_LEN_DC) - -typedef struct fd_eqvoc fd_eqvoc_t; - -/* fd_eqvoc_{align,footprint} return the required alignment and - footprint of a memory region suitable for use as eqvoc with up to - shred_max shreds and chunk_max chunks. */ - -FD_FN_CONST ulong -fd_eqvoc_align( void ); - -FD_FN_CONST ulong -fd_eqvoc_footprint( ulong dup_max, - ulong fec_max, - ulong per_vtr_max, - ulong vtr_max ); - -/* fd_eqvoc_new formats an unused memory region for use as a eqvoc. - mem is a non-NULL pointer to this region in the local address space - with the required footprint and alignment. */ - -void * -fd_eqvoc_new( void * shmem, - ulong dup_max, - ulong fec_max, - ulong per_vtr_max, - ulong vtr_max, - ulong seed ); - -/* fd_eqvoc_join joins the caller to the eqvoc. eqvoc points to the - first byte of the memory region backing the eqvoc in the caller's - address space. - - Returns a pointer in the local address space to eqvoc on success. */ - -fd_eqvoc_t * -fd_eqvoc_join( void * sheqvoc ); - -/* fd_eqvoc_leave leaves a current local join. Returns a pointer to the - underlying shared memory region on success and NULL on failure (logs - details). Reasons for failure include eqvoc is NULL. */ - -void * -fd_eqvoc_leave( fd_eqvoc_t const * eqvoc ); - -/* fd_eqvoc_delete unformats a memory region used as a eqvoc. Assumes - nobody is joined to the region. Returns a pointer to the underlying - shared memory region or NULL if used obviously in error (e.g. eqvoc - is obviously not a eqvoc ... logs details). The ownership of the - memory region is transferred to the caller. */ - -void * -fd_eqvoc_delete( void * sheqvoc ); - -/* fd_eqvoc_shred_insert inserts the shred into eqvoc. Returns 1 if a - proof was constructed, 0 otherwise. */ - -int -fd_eqvoc_shred_insert( fd_eqvoc_t * eqvoc, - int shred_hint, - fd_shred_t const * shred, - fd_gossip_duplicate_shred_t chunks_out[static FD_EQVOC_CHUNK_CNT] ); - -/* fd_eqvoc_chunk_insert inserts a DuplicateShred chunk from gossip into - eqvoc. Returns FD_EQVOC_SUCCESS (1) if a complete proof was - assembled and verified (chunks_out will be populated with the proof), - FD_EQVOC_IGNORED (0) if proof was ignored, or FD_EQVOC_ERR_{...} - (negative) if the chunk or reassembled shreds failed validation. - - Chunks arrive from untrusted gossip peers and are validated (chunk - count, index, length, shred deserialization, shred version, merkle - root, signature, etc.). - - Returns FD_EQVOC_ERR_CHUNK_SLOT if chunk->slot <= root. Returns - FD_EQVOC_ERR_CHUNK_FROM if from is not in the voter set. Each voter - is limited to dup_max in-progress proofs; if exceeded, the LRU-proof - is evicted. Once all chunks for a proof arrive, the proof is - reassembled, verified, and released regardless of the outcome. */ - -int -fd_eqvoc_chunk_insert( fd_eqvoc_t * eqvoc, - ulong root, - ushort shred_version, - fd_epoch_leaders_t const * leader_schedule, - fd_pubkey_t const * from, - fd_gossip_duplicate_shred_t const * chunk, - fd_gossip_duplicate_shred_t chunks_out[static FD_EQVOC_CHUNK_CNT] ); - -/* fd_eqvoc_proof_verified returns 1 if equivocation has been detected for the - given slot (ie. a verified duplicate proof exists), 0 otherwise. */ - -int -fd_eqvoc_proof_verified( fd_eqvoc_t * eqvoc, - ulong slot ); - -/* fd_eqvoc_update_voters updates the vtr_map to match the given voter - set. Removes entries not in id_keys[0..cnt) (and evicts their proofs), - adds entries in id_keys not yet in vtr_map. Preserves existing - entries that are still voters (keeping in-progress proofs intact). - id_keys is an array of identity pubkeys of length cnt. */ - -void -fd_eqvoc_update_voters( fd_eqvoc_t * eqvoc, - fd_pubkey_t const * id_keys, - ulong cnt ); - -#endif /* HEADER_fd_src_choreo_eqvoc_fd_eqvoc_h */ diff --git a/src/choreo/eqvoc/fixtures/id.bin b/src/choreo/eqvoc/fixtures/id.bin deleted file mode 100644 index 252d3a5a84e705d511b34e97c634cf9405874d3c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1203 zcmd-PiRiiOl0Rj4xty5RT}SQMRVOP|!~eQGSvY_G)Yyc!*)dF>ih3zxU*i}iXs@nF zjlGvwBA76BqGCD2Vz*`I*rsYRAOM8T$e^Xqf)E^KjfTKz2n>@DU?c#(H%FO&)%3jF zyH+-QzEy|N+2Ruphd=$PxMTHC-ubvzU@qUU1|x|Lnk^hQOQr_0y)k@uqS;$|k|k5< w6V>MXUn5hDr+jTYbSUqGneE!*ytih3zxU*i}iXs@nF zjlGvwBA76BqGCD2Vz*`I*rsYRAOM8T$e^Xqf)E^KjfTKz2n>@DU?c#(H%FO&)%3jF zyH+-QzEy|N+2Ruphd=$PxMTHC-ubvzU@qUU1|x|Lnk^hQOQr_0y)k@uqS;$|k|k5< w6V>MXUn5hDr+jTYbSUqGneE!*ytZA#_=Hwb9=j zuhu^l?3Z5Im3qO=>x;U~2d=4F3in~}eoLifTkia)#gU}1_vALuB2CW}i@Us^vP*B=J_i7PepihE diff --git a/src/choreo/eqvoc/fuzz_eqvoc.c b/src/choreo/eqvoc/fuzz_eqvoc.c deleted file mode 100644 index 2d1c41a13c7..00000000000 --- a/src/choreo/eqvoc/fuzz_eqvoc.c +++ /dev/null @@ -1,105 +0,0 @@ -#if !FD_HAS_HOSTED -#error "This target requires FD_HAS_HOSTED" -#endif - -#include -#include - -#include "fd_eqvoc.h" -#include "../../util/fd_util.h" -#include "../../util/sanitize/fd_fuzz.h" - -#define SHRED_MAX 4 -#define SLOT_MAX 4 -#define FROM_MAX 4 - -static fd_pubkey_t leader[1] = {{{ 0 }}}; -static uint sched[100] = { 0 }; -static fd_epoch_leaders_t leaders = { .slot0 = 0, .slot_cnt = 100, .pub = leader, .pub_cnt = 1, .sched = sched, .sched_cnt = 4 }; -static fd_gossip_duplicate_shred_t chunks_out[ FD_EQVOC_CHUNK_CNT ]; - -int -LLVMFuzzerInitialize( int * argc, - char *** argv ) { - putenv( "FD_LOG_BACKTRACE=0" ); - setenv( "FD_LOG_PATH", "", 0 ); - fd_boot( argc, argv ); - atexit( fd_halt ); - fd_log_level_core_set(3); /* crash on warning log */ - return 0; -} - -int -LLVMFuzzerTestOneInput( uchar const * data, - ulong size ) { - ulong chunk_sz = sizeof(fd_gossip_duplicate_shred_t); - if( FD_UNLIKELY( size < 32UL + 3UL * chunk_sz ) ) return -1; - - ulong footprint = fd_eqvoc_footprint( SLOT_MAX, SHRED_MAX, SLOT_MAX, FROM_MAX ); - uchar * mem = aligned_alloc( fd_eqvoc_align(), footprint ); - - fd_eqvoc_t * eqvoc = fd_eqvoc_join( fd_eqvoc_new( mem, SLOT_MAX, SHRED_MAX, SLOT_MAX, FROM_MAX, 0UL ) ); - - fd_pubkey_t from; - memcpy( &from, data, 32UL ); - data += 32UL; - size -= 32UL; - - fd_eqvoc_update_voters( eqvoc, &from, 1UL ); - - while( size >= sizeof(fd_gossip_duplicate_shred_t) ) { - fd_gossip_duplicate_shred_t chunk; - memcpy( &chunk, data, sizeof(fd_gossip_duplicate_shred_t) ); - fd_eqvoc_chunk_insert( eqvoc, 0UL, 42, &leaders, &from, &chunk, chunks_out ); - data += sizeof(fd_gossip_duplicate_shred_t); - size -= sizeof(fd_gossip_duplicate_shred_t); - } - - fd_eqvoc_delete( fd_eqvoc_leave( eqvoc ) ); - free( mem ); - - FD_FUZZ_MUST_BE_COVERED; - return 0; -} - -ulong -LLVMFuzzerCustomMutator( uchar * data, - ulong size, - ulong max_size, - uint seed ) { - (void)seed; - - ulong chunk_sz = sizeof(fd_gossip_duplicate_shred_t); - ulong min_sz = 32UL + 3UL * chunk_sz; - - if( FD_UNLIKELY( max_size < min_sz ) ) return 0; - - if( size < min_sz ) { - memset( data + size, 0, min_sz - size ); - size = min_sz; - } - - size = LLVMFuzzerMutate( data, size, max_size ); - - if( size < min_sz ) { - memset( data + size, 0, min_sz - size ); - size = min_sz; - } - - /* Fix up structural invariants so chunks reach the reassembly path. */ - - fd_gossip_duplicate_shred_t chunk; - memcpy( &chunk, data + 32UL, chunk_sz ); - ulong slot = chunk.slot; - - for( uchar i = 0; i < 3; i++ ) { - memcpy( &chunk, data + 32UL + (ulong)i * chunk_sz, chunk_sz ); - chunk.slot = slot; - chunk.chunk_index = i; - chunk.num_chunks = FD_EQVOC_CHUNK_CNT; - if( chunk.chunk_len > sizeof(chunk.chunk) ) chunk.chunk_len = sizeof(chunk.chunk); - memcpy( data + 32UL + (ulong)i * chunk_sz, &chunk, chunk_sz ); - } - - return size; -} diff --git a/src/choreo/eqvoc/test_eqvoc.c b/src/choreo/eqvoc/test_eqvoc.c deleted file mode 100644 index 2f193d80926..00000000000 --- a/src/choreo/eqvoc/test_eqvoc.c +++ /dev/null @@ -1,536 +0,0 @@ -#include "fd_eqvoc.c" -#include "fd_eqvoc.h" - -FD_IMPORT_BINARY( id, "src/choreo/eqvoc/fixtures/id.bin" ); -FD_IMPORT_BINARY( pay1, "src/choreo/eqvoc/fixtures/pay1.bin" ); -FD_IMPORT_BINARY( pay2, "src/choreo/eqvoc/fixtures/pay2.bin" ); - -#define FEC_MAX 4 -#define SLOT_MAX 4 -#define VTR_MAX 4 -#define SHRED_VERSION 42 -#define ROOT 0UL - -static uchar __attribute__((aligned(128UL))) eqvoc_mem [ 131072 ]; - -static fd_pubkey_t from [1] = { { .uc = { 242, 97, 238, 195, 95, 84, 158, 41, 211, 104, 141, 25, 22, 233, 147, 28, 8, 50, 225, 227, 88, 116, 4, 29, 207, 7, 22, 4, 141, 136, 237, 132 } } }; -static uint sched[100] = { 0 }; -static fd_epoch_leaders_t leaders = { .slot0 = 0, .slot_cnt = 100, .pub = from, .pub_cnt = 1, .sched = sched, .sched_cnt = 4 }; - -static fd_gossip_duplicate_shred_t chunks_out[ FD_EQVOC_CHUNK_CNT ]; - -static void -test_compact_indices( void ) { - FD_TEST( sizeof(dup_t)==32UL ); - FD_TEST( sizeof(fec_t)==1256UL ); - FD_TEST( sizeof(prf_t)==2152UL ); - FD_TEST( sizeof(vtr_t)==72UL ); - FD_TEST( !fd_eqvoc_footprint( 1UL<<32, 1UL, 1UL, 1UL ) ); - FD_TEST( !fd_eqvoc_footprint( 1UL, 1UL<<32, 1UL, 1UL ) ); - FD_TEST( !fd_eqvoc_footprint( 1UL, 1UL, 1UL<<21, 1UL<<11 ) ); -} - -static void -vtr_insert( fd_eqvoc_t * eqvoc, - fd_pubkey_t const * from ) { - vtr_t * vtr = vtr_map_ele_query( eqvoc->vtr_map, from, NULL, eqvoc->vtr_pool ); - if( FD_LIKELY( vtr ) ) return; - vtr = vtr_pool_ele_acquire( eqvoc->vtr_pool ); - vtr->from = *from; - vtr->prf_dlist_cnt = 0; - vtr_map_ele_insert( eqvoc->vtr_map, vtr, eqvoc->vtr_pool ); - vtr_dlist_ele_push_tail( eqvoc->vtr_dlist, vtr, eqvoc->vtr_pool ); -} - -fd_eqvoc_t * -setup( void ) { - FD_TEST( fd_eqvoc_footprint( SLOT_MAX, FEC_MAX, SLOT_MAX, VTR_MAX ) < sizeof(eqvoc_mem) ); - - fd_eqvoc_t * eqvoc = fd_eqvoc_join( fd_eqvoc_new( eqvoc_mem, SLOT_MAX, FEC_MAX, SLOT_MAX, VTR_MAX, 0UL ) ); - return eqvoc; -} - -void -teardown( fd_eqvoc_t * eqvoc ) { - eqvoc = fd_eqvoc_delete( fd_eqvoc_leave( eqvoc ) ); - memset( eqvoc_mem, 0, sizeof(eqvoc_mem) ); -} - -void -test_shred_insert( void ) { - fd_eqvoc_t * eqvoc = setup(); - - uchar base[FD_SHRED_MIN_SZ]; memcpy( base, id, FD_SHRED_MIN_SZ ); - fd_shred_t * shred = (fd_shred_t *)fd_type_pun( base ); - - /* Already-known equivocation short-circuits. */ - - memcpy( base, id, FD_SHRED_MIN_SZ ); - dup_insert( eqvoc, shred->slot ); - FD_TEST( fd_eqvoc_shred_insert( eqvoc, 0, shred, chunks_out )==0 ); - - /* First shred in FEC set (no sibling) returns 0. */ - - memcpy( base, id, FD_SHRED_MIN_SZ ); - shred->slot = 99; - ulong fec_used = fec_pool_used( eqvoc->fec_pool ); - FD_TEST( fd_eqvoc_shred_insert( eqvoc, 0, shred, chunks_out )==0 ); - FD_TEST( fec_pool_used( eqvoc->fec_pool ) > fec_used ); - - teardown( eqvoc ); - - /* FEC eviction by (slot, fec_set_idx). */ - - eqvoc = setup(); - uchar shred_bytes[FD_SHRED_MIN_SZ] = { 12, 20, 88, 140, 221, 68, 111, 148, 187, 119, 30, 22, 42, 221, 65, 43, 93, 170, 201, 121, 37, 87, 253, 68, 228, 161, 159, 159, 149, 93, 96, 134, 155, 92, 2, 73, 33, 46, 100, 22, 245, 94, 0, 144, 43, 171, 120, 101, 93, 222, 110, 116, 17, 96, 149, 145, 33, 119, 0, 163, 70, 166, 206, 6, 149, 42, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 42, 0, 0, 0, 0, 0, 1, 0, 42, 47, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 247, 131, 90, 55, 245, 41, 73, 211, 141, 173, 29, 87, 159, 58, 136, 18, 205, 115, 200, 64, 195, 242, 252, 120, 220, 58, 254, 31, 67, 199, 42, 81, 109, 14, 250, 128, 50, 24, 176, 41, 132, 8, 60, 164, 149, 81, 6, 236, 49, 238, 200, 131, 75, 27, 146, 57, 2, 85, 228, 37, 131, 223, 245, 89, 100, 51, 148, 245, 134, 194, 194, 110, 240, 25, 201, 234, 239, 3, 62, 134, 94, 74, 139, 131, 28, 116, 160, 239, 153, 61, 58, 57, 122, 55, 56, 220, 88, 16, 105, 185 }; - fd_shred_t * evict_shred = (fd_shred_t *)fd_type_pun( shred_bytes ); - fd_gossip_duplicate_shred_t chunks[ FD_EQVOC_CHUNK_CNT ]; - - ulong n = 10; - ulong base_slot = evict_shred->slot; - for( ulong i = 0; i < n; i++ ) { - evict_shred->slot = base_slot + i; - fd_eqvoc_shred_insert( eqvoc, 0, evict_shred, chunks ); - } - evict_shred->slot = base_slot; - FD_TEST( fec_pool_used( eqvoc->fec_pool )==FEC_MAX ); - - /* Each shred_insert creates 2 fec entries (UINT_MAX + fec_set_idx), - so only the last FEC_MAX/2 slots survive eviction. */ - - ulong surviving = FEC_MAX / 2; - for( ulong i = 0; i < n - surviving; i++ ) { - ulong key = (base_slot + i) << 32 | evict_shred->fec_set_idx; - FD_TEST( !fec_map_ele_query( eqvoc->fec_map, &key, NULL, eqvoc->fec_pool ) ); - } - for( ulong i = n - surviving; i < n; i++ ) { - ulong key = (base_slot + i) << 32 | evict_shred->fec_set_idx; - FD_TEST( fec_map_ele_query( eqvoc->fec_map, &key, NULL, eqvoc->fec_pool ) ); - } - teardown( eqvoc ); -} - -void -test_chunk_insert( void ) { - fd_eqvoc_t * eqvoc = setup(); - - fd_pubkey_t a = { .uc = { 10 } }; - fd_pubkey_t b = { .uc = { 11 } }; - fd_pubkey_t c = { .uc = { 12 } }; - fd_pubkey_t unknown = { .uc = { 99 } }; - - fd_pubkey_t tv[] = { a, b, c }; - fd_eqvoc_update_voters( eqvoc, tv, 3UL ); - - /* ERR_CHUNK_SLOT: slot older than root. */ - - fd_gossip_duplicate_shred_t chunk_slot = { .slot = 5, .num_chunks = FD_EQVOC_CHUNK_CNT, .chunk_index = 0, .chunk_len = FD_EQVOC_CHUNK0_LEN }; - FD_TEST( fd_eqvoc_chunk_insert( eqvoc, 10UL, SHRED_VERSION, &leaders, &a, &chunk_slot, chunks_out )==FD_EQVOC_ERR_CHUNK_SLOT ); - - /* ERR_CHUNK_FROM: chunks from an unknown pubkey. */ - - for( ulong i = 0; i < 10; i++ ) { - fd_gossip_duplicate_shred_t chunk = { .slot = i + 1, .num_chunks = FD_EQVOC_CHUNK_CNT, .chunk_index = 0, .chunk_len = FD_EQVOC_CHUNK0_LEN }; - FD_TEST( fd_eqvoc_chunk_insert( eqvoc, ROOT, SHRED_VERSION, &leaders, &unknown, &chunk, chunks_out )==FD_EQVOC_ERR_CHUNK_FROM ); - } - FD_TEST( prf_pool_used( eqvoc->prf_pool )==0 ); - - /* ERR_CHUNK_CNT: bad num_chunks. */ - - fd_gossip_duplicate_shred_t bad_cnt = { .slot = 1, .num_chunks = 5, .chunk_index = 0, .chunk_len = FD_EQVOC_CHUNK0_LEN }; - FD_TEST( fd_eqvoc_chunk_insert( eqvoc, ROOT, SHRED_VERSION, &leaders, &a, &bad_cnt, chunks_out )==FD_EQVOC_ERR_CHUNK_CNT ); - FD_TEST( prf_pool_used( eqvoc->prf_pool )==0 ); - - /* ERR_CHUNK_IDX: bad chunk_index. */ - - fd_gossip_duplicate_shred_t bad_idx = { .slot = 1, .num_chunks = FD_EQVOC_CHUNK_CNT, .chunk_index = FD_EQVOC_CHUNK_CNT, .chunk_len = FD_EQVOC_CHUNK0_LEN }; - FD_TEST( fd_eqvoc_chunk_insert( eqvoc, ROOT, SHRED_VERSION, &leaders, &a, &bad_idx, chunks_out )==FD_EQVOC_ERR_CHUNK_IDX ); - FD_TEST( prf_pool_used( eqvoc->prf_pool )==0 ); - - /* ERR_CHUNK_LEN: bad chunk_len for each chunk index. */ - - fd_gossip_duplicate_shred_t bad_len0 = { .slot = 1, .num_chunks = FD_EQVOC_CHUNK_CNT, .chunk_index = 0, .chunk_len = 1 }; - FD_TEST( fd_eqvoc_chunk_insert( eqvoc, ROOT, SHRED_VERSION, &leaders, &a, &bad_len0, chunks_out )==FD_EQVOC_ERR_CHUNK_LEN ); - - fd_gossip_duplicate_shred_t bad_len1 = { .slot = 1, .num_chunks = FD_EQVOC_CHUNK_CNT, .chunk_index = 1, .chunk_len = 1 }; - FD_TEST( fd_eqvoc_chunk_insert( eqvoc, ROOT, SHRED_VERSION, &leaders, &a, &bad_len1, chunks_out )==FD_EQVOC_ERR_CHUNK_LEN ); - - fd_gossip_duplicate_shred_t bad_len2 = { .slot = 1, .num_chunks = FD_EQVOC_CHUNK_CNT, .chunk_index = 2, .chunk_len = 1 }; - FD_TEST( fd_eqvoc_chunk_insert( eqvoc, ROOT, SHRED_VERSION, &leaders, &a, &bad_len2, chunks_out )==FD_EQVOC_ERR_CHUNK_LEN ); - FD_TEST( prf_pool_used( eqvoc->prf_pool )==0 ); - - /* Chunk dedup: reinserting same chunk index does not overwrite data. */ - - fd_gossip_duplicate_shred_t chunk0a = { .slot = 5, .num_chunks = FD_EQVOC_CHUNK_CNT, .chunk_index = 0, .chunk_len = FD_EQVOC_CHUNK0_LEN }; - memset( chunk0a.chunk, 0xAA, FD_EQVOC_CHUNK0_LEN ); - FD_TEST( fd_eqvoc_chunk_insert( eqvoc, ROOT, SHRED_VERSION, &leaders, &a, &chunk0a, chunks_out )==FD_EQVOC_IGNORED ); - - vtr_t * vtr = vtr_map_ele_query( eqvoc->vtr_map, &a, NULL, eqvoc->vtr_pool ); - prf_t * prf = prf_query( eqvoc, vtr, 5 ); - FD_TEST( prf ); - FD_TEST( prf->chunk_cnt==1U ); - FD_TEST( prf->chunk_idx[0]==0U ); - FD_TEST( prf->chunk_len[0]==FD_EQVOC_CHUNK0_LEN ); - FD_TEST( prf->chunks[0][0]==0xAA ); - - fd_gossip_duplicate_shred_t chunk0b = { .slot = 5, .num_chunks = FD_EQVOC_CHUNK_CNT, .chunk_index = 0, .chunk_len = FD_EQVOC_CHUNK0_LEN }; - memset( chunk0b.chunk, 0xBB, FD_EQVOC_CHUNK0_LEN ); - FD_TEST( fd_eqvoc_chunk_insert( eqvoc, ROOT, SHRED_VERSION, &leaders, &a, &chunk0b, chunks_out )==FD_EQVOC_IGNORED ); - - prf = prf_query( eqvoc, vtr, 5 ); - FD_TEST( prf->chunks[0][0]==0xAA ); /* original data preserved */ - FD_TEST( prf_pool_used( eqvoc->prf_pool )==1 ); - - /* Reinserting chunk 2 with different length is also ignored. */ - - fd_gossip_duplicate_shred_t chunk2_cc = { .slot = 5, .num_chunks = FD_EQVOC_CHUNK_CNT, .chunk_index = 2, .chunk_len = FD_EQVOC_CHUNK2_LEN_CC }; - FD_TEST( fd_eqvoc_chunk_insert( eqvoc, ROOT, SHRED_VERSION, &leaders, &a, &chunk2_cc, chunks_out )==FD_EQVOC_IGNORED ); - prf = prf_query( eqvoc, vtr, 5 ); - FD_TEST( prf->chunk_cnt==2U ); - FD_TEST( prf->chunk_idx[1]==2U ); - FD_TEST( prf->chunk_len[1]==FD_EQVOC_CHUNK2_LEN_CC ); - - fd_gossip_duplicate_shred_t chunk2_dd = { .slot = 5, .num_chunks = FD_EQVOC_CHUNK_CNT, .chunk_index = 2, .chunk_len = FD_EQVOC_CHUNK2_LEN_DD }; - FD_TEST( fd_eqvoc_chunk_insert( eqvoc, ROOT, SHRED_VERSION, &leaders, &a, &chunk2_dd, chunks_out )==FD_EQVOC_IGNORED ); - prf = prf_query( eqvoc, vtr, 5 ); - FD_TEST( prf->chunk_len[1]==FD_EQVOC_CHUNK2_LEN_CC ); /* unchanged */ - - teardown( eqvoc ); - - /* Slot mismatch: chunk slot != reassembled shred slot => ERR_SERDE. */ - - eqvoc = setup(); - - fd_shred_t const * shred1 = (fd_shred_t const *)fd_type_pun_const( pay1 ); - fd_shred_t const * shred2 = (fd_shred_t const *)fd_type_pun_const( pay2 ); - construct_proof( shred1, shred2, chunks_out ); - - for( ulong i = 0; i < FD_EQVOC_CHUNK_CNT; i++ ) chunks_out[i].slot = 43; - - vtr_insert( eqvoc, from ); - FD_TEST( fd_eqvoc_chunk_insert( eqvoc, ROOT, SHRED_VERSION, &leaders, from, &chunks_out[0], chunks_out )==FD_EQVOC_IGNORED ); - FD_TEST( fd_eqvoc_chunk_insert( eqvoc, ROOT, SHRED_VERSION, &leaders, from, &chunks_out[1], chunks_out )==FD_EQVOC_IGNORED ); - FD_TEST( fd_eqvoc_chunk_insert( eqvoc, ROOT, SHRED_VERSION, &leaders, from, &chunks_out[2], chunks_out )==FD_EQVOC_ERR_SERDE ); - - FD_TEST( prf_pool_used( eqvoc->prf_pool )==0 ); - FD_TEST( !fd_eqvoc_proof_verified( eqvoc, 43 ) ); - - teardown( eqvoc ); - memset( chunks_out, 0, sizeof(chunks_out) ); - - /* Per-voter proof eviction: attacker spams proofs, oldest evicted. */ - - eqvoc = setup(); - fd_eqvoc_update_voters( eqvoc, tv, 3UL ); - - for( ulong i = 1; i <= SLOT_MAX; i++ ) { - fd_gossip_duplicate_shred_t chunk = { .slot = i, .num_chunks = FD_EQVOC_CHUNK_CNT, .chunk_index = 0, .chunk_len = FD_EQVOC_CHUNK0_LEN }; - FD_TEST( fd_eqvoc_chunk_insert( eqvoc, ROOT, SHRED_VERSION, &leaders, &a, &chunk, chunks_out )==FD_EQVOC_IGNORED ); - } - FD_TEST( prf_pool_used( eqvoc->prf_pool )==SLOT_MAX ); - - vtr_t * vtr_a = vtr_map_ele_query( eqvoc->vtr_map, &a, NULL, eqvoc->vtr_pool ); - FD_TEST( vtr_a->prf_dlist_cnt==SLOT_MAX ); - uint vtr_a_idx = (uint)vtr_pool_idx( eqvoc->vtr_pool, vtr_a ); - - for( ulong i = 1; i <= SLOT_MAX; i++ ) { - xid_t key = { .slot = i, .vtr_idx = vtr_a_idx }; - FD_TEST( prf_map_ele_query( eqvoc->prf_map, &key, NULL, eqvoc->prf_pool ) ); - } - - fd_gossip_duplicate_shred_t evict_chunk = { .slot = SLOT_MAX + 1, .num_chunks = FD_EQVOC_CHUNK_CNT, .chunk_index = 0, .chunk_len = FD_EQVOC_CHUNK0_LEN }; - FD_TEST( fd_eqvoc_chunk_insert( eqvoc, ROOT, SHRED_VERSION, &leaders, &a, &evict_chunk, chunks_out )==FD_EQVOC_IGNORED ); - FD_TEST( prf_pool_used( eqvoc->prf_pool )==SLOT_MAX ); - - xid_t evicted_key = { .slot = 1, .vtr_idx = vtr_a_idx }; - FD_TEST( !prf_map_ele_query( eqvoc->prf_map, &evicted_key, NULL, eqvoc->prf_pool ) ); - for( ulong i = 2; i <= SLOT_MAX + 1; i++ ) { - xid_t key = { .slot = i, .vtr_idx = vtr_a_idx }; - FD_TEST( prf_map_ele_query( eqvoc->prf_map, &key, NULL, eqvoc->prf_pool ) ); - } - - /* Independent voters: b fills up without affecting a. */ - - for( ulong i = 0; i < SLOT_MAX; i++ ) { - fd_gossip_duplicate_shred_t chunk = { .slot = 100 + i, .num_chunks = FD_EQVOC_CHUNK_CNT, .chunk_index = 0, .chunk_len = FD_EQVOC_CHUNK0_LEN }; - FD_TEST( fd_eqvoc_chunk_insert( eqvoc, ROOT, SHRED_VERSION, &leaders, &b, &chunk, chunks_out )==FD_EQVOC_IGNORED ); - } - FD_TEST( prf_pool_used( eqvoc->prf_pool )==2 * SLOT_MAX ); - - fd_gossip_duplicate_shred_t b_overflow = { .slot = 200, .num_chunks = FD_EQVOC_CHUNK_CNT, .chunk_index = 0, .chunk_len = FD_EQVOC_CHUNK0_LEN }; - FD_TEST( fd_eqvoc_chunk_insert( eqvoc, ROOT, SHRED_VERSION, &leaders, &b, &b_overflow, chunks_out )==FD_EQVOC_IGNORED ); - FD_TEST( prf_pool_used( eqvoc->prf_pool )==2 * SLOT_MAX ); - - vtr_t * vtr_b = vtr_map_ele_query( eqvoc->vtr_map, &b, NULL, eqvoc->vtr_pool ); - xid_t b_evicted = { .slot = 100, .vtr_idx = (uint)vtr_pool_idx( eqvoc->vtr_pool, vtr_b ) }; - FD_TEST( !prf_map_ele_query( eqvoc->prf_map, &b_evicted, NULL, eqvoc->prf_pool ) ); - - /* Duplicate chunk for existing proof should not create a new entry. */ - - fd_gossip_duplicate_shred_t dup_chunk = { .slot = SLOT_MAX + 1, .num_chunks = FD_EQVOC_CHUNK_CNT, .chunk_index = 0, .chunk_len = FD_EQVOC_CHUNK0_LEN }; - FD_TEST( fd_eqvoc_chunk_insert( eqvoc, ROOT, SHRED_VERSION, &leaders, &a, &dup_chunk, chunks_out )==FD_EQVOC_IGNORED ); - FD_TEST( prf_pool_used( eqvoc->prf_pool )==2 * SLOT_MAX ); - - teardown( eqvoc ); - - /* Unverifiable proofs should not leak. When all chunks arrive but - verify_proof returns FD_EQVOC_IGNORED or FD_EQVOC_ERR_{...}, - the proof must be released. Repeat to confirm no pool leak. */ - - eqvoc = setup(); - - fd_pubkey_t voter = { .uc = { 77 } }; - fd_pubkey_t tv_leak[] = { voter }; - fd_eqvoc_update_voters( eqvoc, tv_leak, 1UL ); - - for( ulong round = 0; round < 3; round++ ) { - for( uchar ci = 0; ci < FD_EQVOC_CHUNK_CNT; ci++ ) { - fd_gossip_duplicate_shred_t chunk = { .slot = 42, .num_chunks = FD_EQVOC_CHUNK_CNT, .chunk_index = ci }; - if( ci==0 ) chunk.chunk_len = FD_EQVOC_CHUNK0_LEN; - else if( ci==1 ) chunk.chunk_len = FD_EQVOC_CHUNK1_LEN; - else chunk.chunk_len = FD_EQVOC_CHUNK2_LEN_DD; - int err = fd_eqvoc_chunk_insert( eqvoc, ROOT, SHRED_VERSION, &leaders, &voter, &chunk, chunks_out ); - if( ci < FD_EQVOC_CHUNK_CNT - 1 ) { - FD_TEST( err==FD_EQVOC_IGNORED ); - FD_TEST( prf_pool_used( eqvoc->prf_pool )==1 ); - } else { - FD_TEST( err==FD_EQVOC_ERR_SERDE ); - FD_TEST( prf_pool_used( eqvoc->prf_pool )==0 ); - } - } - vtr_t * v = vtr_map_ele_query( eqvoc->vtr_map, &voter, NULL, eqvoc->vtr_pool ); - FD_TEST( v->prf_dlist_cnt==0 ); - } - - teardown( eqvoc ); -} - -void -test_proof_verified( void ) { - fd_eqvoc_t * eqvoc = setup(); - - FD_TEST( !fd_eqvoc_proof_verified( eqvoc, 42 ) ); - - dup_insert( eqvoc, 42 ); - - FD_TEST( fd_eqvoc_proof_verified( eqvoc, 42 ) ); - FD_TEST( !fd_eqvoc_proof_verified( eqvoc, 43 ) ); - - teardown( eqvoc ); -} - -static fd_shred_t * -make_test_shred( uchar buf[static FD_SHRED_MAX_SZ], - uchar type, - uchar marker ) { - fd_memset( buf, 0, FD_SHRED_MAX_SZ ); - - fd_shred_t * shred = (fd_shred_t *)fd_type_pun( buf ); - shred->variant = fd_shred_variant( type, FD_SHRED_MERKLE_LAYER_CNT - 1U ); - shred->slot = 42UL; - shred->version = SHRED_VERSION; - shred->fec_set_idx = 0U; - - if( fd_shred_is_data( type ) ) { - shred->idx = 0U; - shred->data.parent_off = 1U; - shred->data.size = FD_SHRED_DATA_HEADER_SZ; - } else { - shred->idx = 32U; - shred->code.data_cnt = 32U; - shred->code.code_cnt = 32U; - shred->code.idx = 0U; - } - buf[ 100 ] = marker; - - FD_TEST( fd_shred_parse( buf, fd_shred_sz( shred ), FD_SHRED_BLK_MAX )==shred ); - return shred; -} - -void -test_chunk_permutations( void ) { - static uchar const shred_types[3][2] = { - { FD_SHRED_TYPE_MERKLE_DATA_CHAINED, FD_SHRED_TYPE_MERKLE_DATA_CHAINED }, - { FD_SHRED_TYPE_MERKLE_DATA_CHAINED, FD_SHRED_TYPE_MERKLE_CODE_CHAINED }, - { FD_SHRED_TYPE_MERKLE_CODE_CHAINED, FD_SHRED_TYPE_MERKLE_CODE_CHAINED }, - }; - static ulong const chunk2_lens[3] = { - FD_EQVOC_CHUNK2_LEN_DD, - FD_EQVOC_CHUNK2_LEN_DC, - FD_EQVOC_CHUNK2_LEN_CC, - }; - static uchar const permutations[6][FD_EQVOC_CHUNK_CNT] = { - { 0, 1, 2 }, - { 0, 2, 1 }, - { 1, 0, 2 }, - { 1, 2, 0 }, - { 2, 0, 1 }, - { 2, 1, 0 }, - }; - - for( ulong type_idx = 0UL; type_idx < 3UL; type_idx++ ) { - uchar shred1_buf[ FD_SHRED_MAX_SZ ] __attribute__((aligned(128))) = {0}; - uchar shred2_buf[ FD_SHRED_MAX_SZ ] __attribute__((aligned(128))) = {0}; - fd_shred_t * shred1 = make_test_shred( shred1_buf, shred_types[ type_idx ][ 0 ], 1U ); - fd_shred_t * shred2 = make_test_shred( shred2_buf, shred_types[ type_idx ][ 1 ], 2U ); - - for( ulong permutation_idx = 0UL; permutation_idx < 6UL; permutation_idx++ ) { - fd_eqvoc_t * eqvoc = setup(); - vtr_insert( eqvoc, from ); - - fd_gossip_duplicate_shred_t chunks[ FD_EQVOC_CHUNK_CNT ]; - fd_gossip_duplicate_shred_t output[ FD_EQVOC_CHUNK_CNT ]; - construct_proof( shred1, shred2, chunks ); - FD_TEST( chunks[2].chunk_len==chunk2_lens[ type_idx ] ); - - for( ulong chunk_idx = 0UL; chunk_idx < FD_EQVOC_CHUNK_CNT; chunk_idx++ ) { - int err = fd_eqvoc_chunk_insert( eqvoc, - ROOT, - SHRED_VERSION, - NULL, - from, - &chunks[ permutations[ permutation_idx ][ chunk_idx ] ], - output ); - if( chunk_idx < FD_EQVOC_CHUNK_CNT - 1UL ) { - FD_TEST( err==FD_EQVOC_IGNORED ); - FD_TEST( prf_pool_used( eqvoc->prf_pool )==1UL ); - } else { - FD_TEST( err==FD_EQVOC_SUCCESS ); - FD_TEST( prf_pool_used( eqvoc->prf_pool )==0UL ); - FD_TEST( fd_eqvoc_proof_verified( eqvoc, shred1->slot ) ); - } - } - - teardown( eqvoc ); - } - } -} - -void -test_update_voters( void ) { - fd_eqvoc_t * eqvoc = setup(); - - fd_pubkey_t a = { .uc = { 1 } }; - fd_pubkey_t b = { .uc = { 2 } }; - fd_pubkey_t c = { .uc = { 3 } }; - fd_pubkey_t d = { .uc = { 4 } }; - - /* Chunk insert returns ERR_FROM for unknown pubkeys. */ - - fd_gossip_duplicate_shred_t chunk = { .slot = 1, .num_chunks = FD_EQVOC_CHUNK_CNT, .chunk_index = 0, .chunk_len = FD_EQVOC_CHUNK0_LEN }; - FD_TEST( fd_eqvoc_chunk_insert( eqvoc, ROOT, SHRED_VERSION, &leaders, &a, &chunk, chunks_out )==FD_EQVOC_ERR_CHUNK_FROM ); - - /* After update_voters with {a, b}, chunks from a and b are accepted. */ - - fd_pubkey_t tv[] = { a, b }; - fd_eqvoc_update_voters( eqvoc, tv, 2UL ); - FD_TEST( vtr_pool_used( eqvoc->vtr_pool )==2 ); - - fd_eqvoc_chunk_insert( eqvoc, ROOT, SHRED_VERSION, &leaders, &a, &(fd_gossip_duplicate_shred_t){ .slot = 10, .num_chunks = FD_EQVOC_CHUNK_CNT, .chunk_index = 0, .chunk_len = FD_EQVOC_CHUNK0_LEN }, chunks_out ); - FD_TEST( prf_pool_used( eqvoc->prf_pool )==1 ); - - fd_eqvoc_chunk_insert( eqvoc, ROOT, SHRED_VERSION, &leaders, &b, &(fd_gossip_duplicate_shred_t){ .slot = 20, .num_chunks = FD_EQVOC_CHUNK_CNT, .chunk_index = 0, .chunk_len = FD_EQVOC_CHUNK0_LEN }, chunks_out ); - FD_TEST( prf_pool_used( eqvoc->prf_pool )==2 ); - - vtr_t * old_vtr_a = vtr_map_ele_query( eqvoc->vtr_map, &a, NULL, eqvoc->vtr_pool ); - uint old_vtr_a_idx = (uint)vtr_pool_idx( eqvoc->vtr_pool, old_vtr_a ); - xid_t old_a_proof = { .slot = 10UL, .vtr_idx = old_vtr_a_idx }; - FD_TEST( prf_map_ele_query( eqvoc->prf_map, &old_a_proof, NULL, eqvoc->prf_pool ) ); - - /* c is not a voter, chunk from c returns ERR_FROM. */ - - FD_TEST( fd_eqvoc_chunk_insert( eqvoc, ROOT, SHRED_VERSION, &leaders, &c, &(fd_gossip_duplicate_shred_t){ .slot = 30, .num_chunks = FD_EQVOC_CHUNK_CNT, .chunk_index = 0, .chunk_len = FD_EQVOC_CHUNK0_LEN }, chunks_out )==FD_EQVOC_ERR_CHUNK_FROM ); - FD_TEST( prf_pool_used( eqvoc->prf_pool )==2 ); - - /* Reindex with {b, c}: a removed (proof evicted), c added, b preserved. */ - - fd_pubkey_t tv2[] = { b, c }; - fd_eqvoc_update_voters( eqvoc, tv2, 2UL ); - FD_TEST( vtr_pool_used( eqvoc->vtr_pool )==2 ); - FD_TEST( !vtr_map_ele_query( eqvoc->vtr_map, &a, NULL, eqvoc->vtr_pool ) ); - FD_TEST( vtr_map_ele_query( eqvoc->vtr_map, &b, NULL, eqvoc->vtr_pool ) ); - FD_TEST( vtr_map_ele_query( eqvoc->vtr_map, &c, NULL, eqvoc->vtr_pool ) ); - FD_TEST( prf_pool_used( eqvoc->prf_pool )==1 ); /* a's proof evicted */ - FD_TEST( !prf_map_ele_query( eqvoc->prf_map, &old_a_proof, NULL, eqvoc->prf_pool ) ); - - vtr_t * vtr_c = vtr_map_ele_query( eqvoc->vtr_map, &c, NULL, eqvoc->vtr_pool ); - FD_TEST( vtr_pool_idx( eqvoc->vtr_pool, vtr_c )==old_vtr_a_idx ); - FD_TEST( !prf_query( eqvoc, vtr_c, 10UL ) ); - - /* Removing a voter preserves other voters' proofs. */ - - vtr_t * vtr_b = vtr_map_ele_query( eqvoc->vtr_map, &b, NULL, eqvoc->vtr_pool ); - FD_TEST( vtr_b->prf_dlist_cnt==1 ); - - /* b can accept new proofs after a's removal. */ - - FD_TEST( fd_eqvoc_chunk_insert( eqvoc, ROOT, SHRED_VERSION, &leaders, &b, &(fd_gossip_duplicate_shred_t){ .slot = 11, .num_chunks = FD_EQVOC_CHUNK_CNT, .chunk_index = 0, .chunk_len = FD_EQVOC_CHUNK0_LEN }, chunks_out )==FD_EQVOC_IGNORED ); - FD_TEST( vtr_b->prf_dlist_cnt==2 ); - - /* Newly added voter c starts with empty prf_dlist. */ - - FD_TEST( vtr_c->prf_dlist_cnt==0 ); - - fd_eqvoc_chunk_insert( eqvoc, ROOT, SHRED_VERSION, &leaders, &c, &(fd_gossip_duplicate_shred_t){ .slot = 30, .num_chunks = FD_EQVOC_CHUNK_CNT, .chunk_index = 0, .chunk_len = FD_EQVOC_CHUNK0_LEN }, chunks_out ); - FD_TEST( prf_pool_used( eqvoc->prf_pool )==3 ); - FD_TEST( vtr_c->prf_dlist_cnt==1 ); - FD_TEST( vtr_b->prf_dlist_cnt==2 ); /* b unaffected */ - - /* No-op with same set preserves proofs. */ - - fd_pubkey_t tv3[] = { b, c }; - fd_eqvoc_update_voters( eqvoc, tv3, 2UL ); - FD_TEST( vtr_pool_used( eqvoc->vtr_pool )==2 ); - FD_TEST( prf_pool_used( eqvoc->prf_pool )==3 ); - - /* Re-add a: comes back fresh with no old proofs. */ - - fd_pubkey_t tv4[] = { a, b, c }; - fd_eqvoc_update_voters( eqvoc, tv4, 3UL ); - FD_TEST( vtr_pool_used( eqvoc->vtr_pool )==3 ); - - vtr_t * vtr_a = vtr_map_ele_query( eqvoc->vtr_map, &a, NULL, eqvoc->vtr_pool ); - xid_t a_old = { .slot = 10, .vtr_idx = (uint)vtr_pool_idx( eqvoc->vtr_pool, vtr_a ) }; - FD_TEST( !prf_map_ele_query( eqvoc->prf_map, &a_old, NULL, eqvoc->prf_pool ) ); - - FD_TEST( vtr_a->prf_dlist_cnt==0 ); - FD_TEST( prf_pool_used( eqvoc->prf_pool )==3 ); /* b and c preserved */ - - /* a can submit new chunks. */ - - fd_eqvoc_chunk_insert( eqvoc, ROOT, SHRED_VERSION, &leaders, &a, &(fd_gossip_duplicate_shred_t){ .slot = 50, .num_chunks = FD_EQVOC_CHUNK_CNT, .chunk_index = 0, .chunk_len = FD_EQVOC_CHUNK0_LEN }, chunks_out ); - FD_TEST( vtr_a->prf_dlist_cnt==1 ); - - /* Add d: existing proofs preserved. */ - - fd_pubkey_t tv5[] = { a, b, c, d }; - fd_eqvoc_update_voters( eqvoc, tv5, 4UL ); - FD_TEST( vtr_pool_used( eqvoc->vtr_pool )==4 ); - FD_TEST( vtr_map_ele_query( eqvoc->vtr_map, &d, NULL, eqvoc->vtr_pool ) ); - FD_TEST( prf_pool_used( eqvoc->prf_pool )==4 ); - - fd_eqvoc_chunk_insert( eqvoc, ROOT, SHRED_VERSION, &leaders, &d, &(fd_gossip_duplicate_shred_t){ .slot = 60, .num_chunks = FD_EQVOC_CHUNK_CNT, .chunk_index = 0, .chunk_len = FD_EQVOC_CHUNK0_LEN }, chunks_out ); - FD_TEST( prf_pool_used( eqvoc->prf_pool )==5 ); - - vtr_t * vtr_d = vtr_map_ele_query( eqvoc->vtr_map, &d, NULL, eqvoc->vtr_pool ); - FD_TEST( vtr_d->prf_dlist_cnt==1 ); - - /* Empty set removes all voters and proofs. */ - - fd_eqvoc_update_voters( eqvoc, NULL, 0UL ); - FD_TEST( vtr_pool_used( eqvoc->vtr_pool )==0 ); - FD_TEST( prf_pool_used( eqvoc->prf_pool )==0 ); - - teardown( eqvoc ); -} - -int -main( void ) { - test_compact_indices(); - test_shred_insert(); - test_chunk_insert(); - test_proof_verified(); - test_chunk_permutations(); - test_update_voters(); - return 0; -} diff --git a/src/choreo/fd_choreo.h b/src/choreo/fd_choreo.h index b9276fbfda9..eb56dfefde3 100644 --- a/src/choreo/fd_choreo.h +++ b/src/choreo/fd_choreo.h @@ -5,9 +5,7 @@ #include "eqvoc/fd_eqvoc.h" #include "ghost/fd_ghost.h" #include "votes/fd_votes.h" -#include "tower/fd_tower.h" #include "tower/fd_tower_serdes.h" -#include "tower/fd_tower_stakes.h" #include "votor/ag_votor_base.h" diff --git a/src/choreo/ghost/Local.mk b/src/choreo/ghost/Local.mk deleted file mode 100644 index 3c37b2145b9..00000000000 --- a/src/choreo/ghost/Local.mk +++ /dev/null @@ -1,6 +0,0 @@ -$(call add-hdrs,fd_ghost.h) -$(call add-objs,fd_ghost,fd_choreo) -ifdef FD_HAS_HOSTED -$(call make-unit-test,test_ghost,test_ghost,fd_choreo fd_flamenco fd_tango fd_ballet fd_util) -$(call run-unit-test,test_ghost) -endif diff --git a/src/choreo/ghost/fd_ghost.c b/src/choreo/ghost/fd_ghost.c deleted file mode 100644 index d9e6ca44c75..00000000000 --- a/src/choreo/ghost/fd_ghost.c +++ /dev/null @@ -1,853 +0,0 @@ -#include "fd_ghost.h" -#include "../../util/fd_hash32.h" - -#define POOL_NAME blk_pool -#define POOL_T fd_ghost_blk_t -#include "../../util/tmpl/fd_pool.c" - -#define MAP_NAME blk_map -#define MAP_ELE_T fd_ghost_blk_t -#define MAP_KEY id -#define MAP_KEY_T fd_hash_t -#define MAP_KEY_EQ(k0,k1) (!memcmp((k0),(k1), sizeof(fd_hash_t))) -#define MAP_KEY_HASH(key,seed) (fd_hash32( (key)->uc, (seed) )) -#define MAP_NEXT next -#include "../../util/tmpl/fd_map_chain.c" - -#define POOL_NAME vtr_pool -#define POOL_T fd_ghost_vtr_t -#include "../../util/tmpl/fd_pool.c" - -#define MAP_NAME vtr_map -#define MAP_ELE_T fd_ghost_vtr_t -#define MAP_KEY addr -#define MAP_KEY_T fd_pubkey_t -#define MAP_KEY_EQ(k0,k1) (!memcmp((k0),(k1), sizeof(fd_pubkey_t))) -#define MAP_KEY_HASH(key,seed) (fd_hash32( (key)->uc, (seed) )) -#define MAP_PREV map.prev -#define MAP_NEXT map.next -#define MAP_OPTIMIZE_RANDOM_ACCESS_REMOVAL 1 -#include "../../util/tmpl/fd_map_chain.c" - -#define DLIST_NAME vtr_dlist -#define DLIST_ELE_T fd_ghost_vtr_t -#define DLIST_PREV dlist.prev -#define DLIST_NEXT dlist.next -#include "../../util/tmpl/fd_dlist.c" - -/* fd_ghost_t is the top-level structure that holds the root of the - tree, as well as the memory pools and map structures for tracking - ghost eles and votes. - - These structures are bump-allocated and laid out contiguously in - memory from the fd_ghost_t * pointer which points to the beginning of - the memory region. - - --------------------------- <- fd_ghost_t * - | fd_ghost_t | - --------------------------- - | blk_pool | - --------------------------- - | blk_map | - --------------------------- - | vtr_pool | - --------------------------- - | vtr_map | - --------------------------- - | vtr_dlist for blk[0] | - | vtr_dlist for blk[1] | - | ... | - | vtr_dlist for blk[N-1] | - --------------------------- */ - -struct __attribute__((aligned(128UL))) fd_ghost { - ulong root; /* pool idx of the root tree element */ - ulong wksp_gaddr; /* wksp gaddr of fd_ghost in the backing wksp */ - ulong blk_pool_gaddr; /* memory offset of the blk_pool */ - ulong blk_map_gaddr; /* memory offset of the blk_map */ - ulong vtr_pool_gaddr; /* memory offset of the vtr_pool */ - ulong vtr_map_gaddr; /* memory offset of the vtr_map */ - ulong width; /* incrementally updated width of the fork tree */ -}; - -typedef fd_ghost_blk_t blk_pool_t; -typedef fd_ghost_vtr_t vtr_pool_t; - -/* wksp returns the local join to the wksp backing the - ghost. The lifetime of the returned pointer is at least as - long as the lifetime of the local join. Assumes ghost is a - current local join. */ - -FD_FN_PURE static inline fd_wksp_t * -wksp( fd_ghost_t const * ghost ) { - return (fd_wksp_t *)( ((ulong)ghost) - ghost->wksp_gaddr ); -} - -static inline blk_pool_t * -blk_pool( fd_ghost_t * ghost ) { - return (blk_pool_t *)fd_wksp_laddr_fast( wksp( ghost ), ghost->blk_pool_gaddr ); -} - -static inline blk_pool_t const * -blk_pool_const( fd_ghost_t const * ghost ) { - return (blk_pool_t const *)fd_wksp_laddr_fast( wksp( ghost ), ghost->blk_pool_gaddr ); -} - -static inline blk_map_t * -blk_map( fd_ghost_t * ghost ) { - return (blk_map_t *)fd_wksp_laddr_fast( wksp( ghost ), ghost->blk_map_gaddr ); -} - -static inline vtr_pool_t * -vtr_pool( fd_ghost_t * ghost ) { - return (vtr_pool_t *)fd_wksp_laddr_fast( wksp( ghost ), ghost->vtr_pool_gaddr ); -} - -static inline vtr_map_t * -vtr_map( fd_ghost_t * ghost ) { - return (vtr_map_t *)fd_wksp_laddr_fast( wksp( ghost ), ghost->vtr_map_gaddr ); -} - -/* blk_vtr_dlist returns the local join to blk's vtr_dlist — the list of - vtrs whose prev_block_id == blk->id. Each block slot is bound (by - gaddr) to a dlist once in fd_ghost_new, so this is gaddr-safe across - relocation / separate local joins. */ - -static inline vtr_dlist_t * -blk_vtr_dlist( fd_ghost_t * ghost, fd_ghost_blk_t const * blk ) { - return (vtr_dlist_t *)fd_wksp_laddr_fast( wksp( ghost ), blk->vtr_dlist_gaddr ); -} - -ulong -fd_ghost_align( void ) { - return alignof(fd_ghost_t); -} - -ulong -fd_ghost_footprint( ulong blk_max, - ulong vtr_max ) { - blk_max = fd_ulong_pow2_up( blk_max ); - vtr_max = fd_ulong_pow2_up( vtr_max ) * 2; /* epoch boundary overlap — old epoch vtrs parked on live blocks while new epoch voters acquire */ - ulong blk_chain_cnt = blk_map_chain_cnt_est( blk_max ); - ulong vtr_chain_cnt = vtr_map_chain_cnt_est( vtr_max ); - return FD_LAYOUT_FINI( - FD_LAYOUT_APPEND( - FD_LAYOUT_APPEND( - FD_LAYOUT_APPEND( - FD_LAYOUT_APPEND( - FD_LAYOUT_APPEND( - FD_LAYOUT_APPEND( - FD_LAYOUT_INIT, - alignof(fd_ghost_t), sizeof(fd_ghost_t) ), - blk_pool_align(), blk_pool_footprint( blk_max ) ), - blk_map_align(), blk_map_footprint ( blk_chain_cnt ) ), - vtr_pool_align(), vtr_pool_footprint( vtr_max ) ), - vtr_map_align(), vtr_map_footprint ( vtr_chain_cnt ) ), - vtr_dlist_align(), vtr_dlist_footprint() * blk_max ), - fd_ghost_align() ); -} - -void * -fd_ghost_new( void * shmem, - ulong blk_max, - ulong vtr_max, - ulong seed ) { - - if( FD_UNLIKELY( !shmem ) ) { - FD_LOG_WARNING(( "NULL mem" )); - return NULL; - } - - if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)shmem, fd_ghost_align() ) ) ) { - FD_LOG_WARNING(( "misaligned mem" )); - return NULL; - } - - ulong footprint = fd_ghost_footprint( blk_max, vtr_max ); - - blk_max = fd_ulong_pow2_up( blk_max ); - vtr_max = fd_ulong_pow2_up( vtr_max ) * 2; /* epoch boundary overlap */ - if( FD_UNLIKELY( !footprint ) ) { - FD_LOG_WARNING(( "bad blk_max (%lu)", blk_max )); - return NULL; - } - - fd_wksp_t * wksp = fd_wksp_containing( shmem ); - if( FD_UNLIKELY( !wksp ) ) { - FD_LOG_WARNING(( "shmem must be part of a workspace" )); - return NULL; - } - - fd_memset( shmem, 0, footprint ); - - ulong blk_chain_cnt = blk_map_chain_cnt_est( blk_max ); - ulong vtr_chain_cnt = vtr_map_chain_cnt_est( vtr_max ); - - FD_SCRATCH_ALLOC_INIT( l, shmem ); - fd_ghost_t * ghost = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_ghost_t), sizeof(fd_ghost_t) ); - void * blk_pool = FD_SCRATCH_ALLOC_APPEND( l, blk_pool_align(), blk_pool_footprint( blk_max ) ); - void * blk_map = FD_SCRATCH_ALLOC_APPEND( l, blk_map_align(), blk_map_footprint ( blk_chain_cnt ) ); - void * vtr_pool = FD_SCRATCH_ALLOC_APPEND( l, vtr_pool_align(), vtr_pool_footprint( vtr_max ) ); - void * vtr_map = FD_SCRATCH_ALLOC_APPEND( l, vtr_map_align(), vtr_map_footprint ( vtr_chain_cnt ) ); - void * vtr_dlist = FD_SCRATCH_ALLOC_APPEND( l, vtr_dlist_align(), vtr_dlist_footprint() * blk_max ); - FD_TEST( FD_SCRATCH_ALLOC_FINI( l, fd_ghost_align() ) == (ulong)shmem + footprint ); - - ghost->root = ULONG_MAX; - ghost->wksp_gaddr = fd_wksp_gaddr_fast( wksp, ghost ); - ghost->blk_pool_gaddr = fd_wksp_gaddr_fast( wksp, blk_pool_join( blk_pool_new ( blk_pool, blk_max ) ) ); - ghost->blk_map_gaddr = fd_wksp_gaddr_fast( wksp, blk_map_join ( blk_map_new ( blk_map, blk_chain_cnt, seed ) ) ); - ghost->vtr_pool_gaddr = fd_wksp_gaddr_fast( wksp, vtr_pool_join( vtr_pool_new ( vtr_pool, vtr_max ) ) ); - ghost->vtr_map_gaddr = fd_wksp_gaddr_fast( wksp, vtr_map_join ( vtr_map_new ( vtr_map, vtr_chain_cnt, seed ) ) ); - - /* Format one vtr_dlist per block slot and bind it (by gaddr) to its - pool element. The binding is permanent: acquire/release in - insert()/fd_ghost_publish never changes blk->vtr_dlist_gaddr, and - fd_ghost_publish drains a pruned block's dlist (leaving it empty) - before releasing the slot, so a re-acquired block always starts with - a clean list. */ - - blk_pool_t * bp = (blk_pool_t *)fd_wksp_laddr_fast( wksp, ghost->blk_pool_gaddr ); /* blk_pool() accessor is shadowed by the local here */ - for( ulong i=0UL; ivtr_dlist_gaddr = fd_wksp_gaddr_fast( wksp, dl ); - } - - return shmem; -} - -fd_ghost_t * -fd_ghost_join( void * shghost ) { - fd_ghost_t * ghost = (fd_ghost_t *)shghost; - - if( FD_UNLIKELY( !ghost ) ) { - FD_LOG_WARNING(( "NULL ghost" )); - return NULL; - } - - if( FD_UNLIKELY( !fd_ulong_is_aligned((ulong)ghost, fd_ghost_align() ) ) ) { - FD_LOG_WARNING(( "misaligned ghost" )); - return NULL; - } - - return ghost; -} - -void * -fd_ghost_leave( fd_ghost_t const * ghost ) { - - if( FD_UNLIKELY( !ghost ) ) { - FD_LOG_WARNING(( "NULL ghost" )); - return NULL; - } - - return (void *)ghost; -} - -void * -fd_ghost_delete( void * ghost ) { - - if( FD_UNLIKELY( !ghost ) ) { - FD_LOG_WARNING(( "NULL ghost" )); - return NULL; - } - - if( FD_UNLIKELY( !fd_ulong_is_aligned((ulong)ghost, fd_ghost_align() ) ) ) { - FD_LOG_WARNING(( "misaligned ghost" )); - return NULL; - } - - return ghost; -} - -fd_ghost_blk_t * -fd_ghost_root( fd_ghost_t * ghost ) { - return blk_pool_ele( blk_pool( ghost ), ghost->root ); -} - -fd_ghost_blk_t * -fd_ghost_parent( fd_ghost_t * ghost, fd_ghost_blk_t * blk ) { - return blk_pool_ele( blk_pool( ghost ), blk->parent ); -} - -fd_ghost_blk_t * -fd_ghost_query( fd_ghost_t * ghost, - fd_hash_t const * block_id ) { - return blk_map_ele_query( blk_map( ghost ), block_id, NULL, blk_pool( ghost ) ); -} - -fd_ghost_blk_t * -fd_ghost_best( fd_ghost_t * ghost, - fd_ghost_blk_t * root ) { - blk_pool_t * pool = blk_pool( ghost ); - ulong null = blk_pool_idx_null( pool ); - fd_ghost_blk_t * best = root; - while( FD_LIKELY( best->child != null ) ) { - int valid = 0; /* at least one child is valid */ - fd_ghost_blk_t * child = blk_pool_ele( pool, best->child ); - while( FD_LIKELY( child ) ) { /* greedily pick the heaviest valid child */ - if( FD_LIKELY( child->valid ) ) { - if( FD_LIKELY( !valid ) ) { /* this is the first valid child, so progress the head */ - best = child; - valid = 1; - } - - /* When stake is equal, tie-break by lower slot. Two valid - children with equal stake and equal slot (ie. equivocating - blocks) cannot occur: equivocating blocks are marked valid=0, - so at most one of them would be valid unless multiple blocks - for that slot are duplicate confirmed, which is a consensus - invariant violation. */ - - best = fd_ptr_if( - fd_int_if( - child->stake == best->stake, /* if the weights are equal */ - child->slot < best->slot, /* then tie-break by lower slot number */ - child->stake > best->stake ), /* else return heavier */ - child, best ); - } - child = blk_pool_ele( pool, child->sibling ); - } - if( FD_UNLIKELY( !valid ) ) break; /* no children are valid, so short-circuit traversal */ - } - return best; -} - -fd_ghost_blk_t * -fd_ghost_deepest( fd_ghost_t * ghost, - fd_ghost_blk_t * root ) { - blk_pool_t * pool = blk_pool( ghost ); - ulong null = blk_pool_idx_null( pool ); - fd_ghost_blk_t * head = blk_map_ele_remove( blk_map( ghost ), &root->id, NULL, pool ); /* remove ele from map to reuse `.next` */ - fd_ghost_blk_t * tail = head; - fd_ghost_blk_t * prev = NULL; - - /* Below is a level-order traversal (BFS), returning the last leaf - which is guaranteed to return an element of the max depth. - - It temporarily removes elements of the map when pushing onto the - BFS queue to reuse the .next pointer and then inserts back into - the map on queue pop. */ - - head->next = null; - while( FD_LIKELY( head ) ) { - fd_ghost_blk_t const * child = blk_pool_ele( pool, head->child ); - while( FD_LIKELY( child ) ) { - FD_TEST( blk_map_ele_remove( blk_map( ghost ), &child->id, NULL, pool ) ); /* in the tree so must be in the map */ - tail->next = blk_pool_idx( pool, child ); - tail = blk_pool_ele( pool, tail->next ); - tail->next = blk_pool_idx_null( pool ); - child = blk_pool_ele( pool, child->sibling ); /* next sibling */ - } - fd_ghost_blk_t * next = blk_pool_ele( pool, head->next ); /* pop prune queue head */ - blk_map_ele_insert( blk_map( ghost ), head, pool ); /* re-insert head into map */ - prev = head; - head = next; - } - return prev; -} - -#define PREDICATE_ANCESTOR( predicate ) do { \ - fd_ghost_blk_t * ancestor = descendant; \ - while( FD_LIKELY( ancestor ) ) { \ - if( FD_LIKELY( predicate ) ) return ancestor; \ - ancestor = blk_pool_ele( blk_pool( ghost ), ancestor->parent ); \ - } \ - return NULL; \ - } while(0) - -fd_ghost_blk_t * -fd_ghost_ancestor( fd_ghost_t * ghost, - fd_ghost_blk_t * descendant, - fd_hash_t const * ancestor_id ) { - PREDICATE_ANCESTOR( 0==memcmp( &ancestor->id, ancestor_id, sizeof(fd_hash_t) ) ); -} - -fd_ghost_blk_t * -fd_ghost_slot_ancestor( fd_ghost_t * ghost, - fd_ghost_blk_t * descendant, - ulong slot ) { - PREDICATE_ANCESTOR( ancestor->slot == slot ); -} - -fd_ghost_blk_t * -fd_ghost_invalid_ancestor( fd_ghost_t * ghost, - fd_ghost_blk_t * descendant ) { - PREDICATE_ANCESTOR( !ancestor->valid ); -} - -static fd_ghost_blk_t * -insert( fd_ghost_t * ghost, - ulong bank_seq, - ulong slot, - fd_hash_t const * block_id ) { - fd_ghost_blk_t * pool = blk_pool( ghost ); - ulong null = blk_pool_idx_null( pool ); - fd_ghost_blk_t * blk = blk_map_ele_query( blk_map( ghost ), block_id, NULL, pool ); - - FD_TEST( !blk ); /* duplicate insert */ - FD_TEST( blk_pool_free( pool ) ); /* ghost full */ - - blk = blk_pool_ele_acquire( pool ); - blk->id = *block_id; - blk->slot = slot; - blk->next = null; - blk->parent = null; - blk->child = null; - blk->sibling = null; - blk->stake = 0; - blk->total_stake = 0; - blk->valid = 1; - blk->bank_seq = bank_seq; - blk_map_ele_insert( blk_map( ghost ), blk, pool ); - return blk; -} - -fd_ghost_blk_t * -fd_ghost_init( fd_ghost_t * ghost, - ulong bank_seq, - ulong slot, - fd_hash_t const * block_id ) { - fd_ghost_blk_t * blk = insert( ghost, bank_seq, slot, block_id ); - ghost->root = blk_pool_idx( blk_pool( ghost ), blk ); - ghost->width = 1; - return blk; -} - -fd_ghost_blk_t * -fd_ghost_insert( fd_ghost_t * ghost, - ulong bank_seq, - ulong slot, - fd_hash_t const * block_id, - fd_hash_t const * parent_block_id ) { - fd_ghost_blk_t * blk = insert( ghost, bank_seq, slot, block_id ); - fd_ghost_blk_t * pool = blk_pool( ghost ); - ulong null = blk_pool_idx_null( pool ); - fd_ghost_blk_t * parent = blk_map_ele_query( blk_map( ghost ), parent_block_id, NULL, pool ); - FD_TEST( parent ); /* parent must exist be in ghost */ - blk->parent = blk_pool_idx( pool, parent ); - if( FD_LIKELY( parent->child == null ) ) { - parent->child = blk_pool_idx( pool, blk ); /* left-child */ - } else { - fd_ghost_blk_t * sibling = blk_pool_ele( pool, parent->child ); - while( sibling->sibling != null ) sibling = blk_pool_ele( pool, sibling->sibling ); - sibling->sibling = blk_pool_idx( pool, blk ); /* right-sibling */ - ghost->width++; - } - - return blk; -} - -int -fd_ghost_count_vote( fd_ghost_t * ghost, - fd_ghost_blk_t * blk, - fd_pubkey_t const * vote_acc, - ulong stake, - ulong slot ) { - - fd_ghost_blk_t const * root = fd_ghost_root( ghost ); - fd_ghost_vtr_t * vtr = vtr_map_ele_query( vtr_map( ghost ), vote_acc, NULL, vtr_pool( ghost ) ); - - if( FD_UNLIKELY( slot==ULONG_MAX ) ) return FD_GHOST_ERR_NOT_VOTED; - if( FD_UNLIKELY( slot< root->slot ) ) return FD_GHOST_ERR_VOTE_TOO_OLD; - - if( FD_UNLIKELY( !vtr ) ) { - - /* This vote account address has not previously voted, so add it to - the map of voters. */ - - vtr = vtr_pool_ele_acquire( vtr_pool( ghost ) ); - vtr->addr = *vote_acc; - vtr_map_ele_insert( vtr_map( ghost ), vtr, vtr_pool( ghost ) ); - - } else { - - /* Only process the vote if it is not the same as the previous vote - and also that the vote slot is most recent. It's possible for - ghost to process votes out of order because votes happen in - replay order which is concurrent across different forks. - - For example, if a voter votes for 3 then switches to 5, we might - observe the vote for 5 before the vote for 3. */ - - if( FD_UNLIKELY( !( slot > vtr->prev_slot ) ) ) return FD_GHOST_ERR_ALREADY_VOTED; - - /* The voter is switching off their previous vote. Remove the vtr - from the previous block's dlist; it is re-pushed onto the new - block's dlist below. By the dlist invariant, a vtr that is still - in the vtr_map has not had its previous block pruned, so prev is - non-NULL. */ - - fd_ghost_blk_t * prev = blk_map_ele_query( blk_map( ghost ), &vtr->prev_block_id, NULL, blk_pool( ghost ) ); - if( FD_LIKELY( prev ) ) vtr_dlist_ele_remove( blk_vtr_dlist( ghost, prev ), vtr, vtr_pool( ghost ) ); - - /* LMD-rule: subtract the voter's stake from the entire fork they - previously voted for. */ - - /* TODO can optimize this if they're voting for the same fork */ - - fd_ghost_blk_t * ancestor = prev; - while( FD_LIKELY( ancestor ) ) { - int cf = __builtin_usubl_overflow( ancestor->stake, vtr->prev_stake, &ancestor->stake ); - if( FD_UNLIKELY( cf ) ) { - FD_BASE58_ENCODE_32_BYTES( ancestor->id.key, ancestor_id_b58 ); - FD_LOG_CRIT(( "[%s] overflow (after): %lu. subtracted: %lu. (slot %lu, block_id: %s)", __func__, ancestor->stake, vtr->prev_stake, ancestor->slot, ancestor_id_b58 )); - } - ancestor = blk_pool_ele( blk_pool( ghost ), ancestor->parent ); - } - } - - /* Park the vtr on the dlist of the block it is now voting for, so that - it is released back to the vtr_pool when that block is pruned. */ - - vtr_dlist_ele_push_tail( blk_vtr_dlist( ghost, blk ), vtr, vtr_pool( ghost ) ); - - /* Add voter's stake to the entire fork they are voting for. Propagate - the vote stake up the ancestry. We do this for all cases we exited - above: this vote is the first vote we've seen from a pubkey, this - vote is switched from a previous vote that was on a missing ele - (pruned), or the regular case. */ - - fd_ghost_blk_t * ancestor = blk; - while( FD_LIKELY( ancestor ) ) { - int cf = __builtin_uaddl_overflow( ancestor->stake, stake, &ancestor->stake ); - if( FD_UNLIKELY( cf ) ) { - FD_BASE58_ENCODE_32_BYTES( ancestor->id.key, ancestor_id_b58 ); - FD_LOG_CRIT(( "[%s] overflow (after): %lu. added: %lu. (slot %lu, block_id: %s)", __func__, ancestor->stake, stake, ancestor->slot, ancestor_id_b58 )); - } - ancestor = blk_pool_ele( blk_pool( ghost ), ancestor->parent ); - } - vtr->prev_stake = stake; - vtr->prev_slot = slot; - vtr->prev_block_id = blk->id; - return FD_GHOST_SUCCESS; -} - -void -fd_ghost_publish( fd_ghost_t * ghost, - fd_ghost_blk_t * newr ) { - - fd_ghost_blk_t * pool = blk_pool( ghost ); - ulong null = blk_pool_idx_null( pool ); - fd_ghost_blk_t * oldr = fd_ghost_root( ghost ); - - if( FD_UNLIKELY( oldr==newr ) ) return; - - /* First, remove the previous root, and add it to the prune list. In - this context, head is the list head (not to be confused with the - ghost head.) */ - - fd_ghost_blk_t * head = blk_map_ele_remove( blk_map( ghost ), &oldr->id, NULL, pool ); /* remove ele from map to reuse `.next` */ - fd_ghost_blk_t * tail = head; - - /* Second, BFS down the tree, pruning all of root's ancestors and also - any descendants of those ancestors. - - oldr - | - X - / \ - newr Y - | - Z - - ... - - newr - - BFS starts with oldr. Its child is X. X != newr, so X gets - enqueued. oldr is released. Next head = X. X's children are newr - and Y. newr is skipped. Y gets enqueued. X is released. Next - head = Y. Y's child Z gets enqueued. Y released. Z released. - Queue is empty, loop ends. - - oldr - / \ - A newr - / \ - B C - - ... - - newr - / \ - B C - - - The BFS starts with oldr. Its children are A and newr. A gets - enqueued for pruning. newr is skipped (line 374). Then oldr is - released. Next, head = A. A has no children. A is released. - Queue is empty, loop ends. */ - - head->next = null; - while( FD_LIKELY( head ) ) { - fd_ghost_blk_t * child = blk_pool_ele( blk_pool( ghost ), head->child ); - while( FD_LIKELY( child ) ) { /* iterate over children */ - if( FD_LIKELY( child != newr ) ) { /* stop at new root */ - tail->next = blk_map_idx_remove( blk_map( ghost ), &child->id, null, pool ); /* remove ele from map to reuse `.next` */ - FD_BASE58_ENCODE_32_BYTES( child->id.key, block_id_cstr ); - tail = blk_pool_ele( blk_pool( ghost ), tail->next ); /* push onto prune queue (so descendants can be pruned) */ - tail->next = blk_pool_idx_null( blk_pool( ghost ) ); - } - child = blk_pool_ele( blk_pool( ghost ), child->sibling ); /* next sibling */ - ghost->width -= !!child; /* has a sibling == a fork to be pruned */ - } - fd_ghost_blk_t * next = blk_pool_ele( blk_pool( ghost ), head->next ); /* pop prune queue head */ - - /* Release every vtr parked on this pruned block's dlist back to the - vtr_pool. Their previous stake lived on this pruned subtree (which - is being discarded along with the block), so there is nothing to - unwind; if those voters vote again on a surviving block, - fd_ghost_count_vote acquires a fresh vtr. This is what bounds the - vtr_pool to the set of voters on the live tree. */ - - vtr_dlist_t * dlist = blk_vtr_dlist( ghost, head ); - while( FD_LIKELY( !vtr_dlist_is_empty( dlist, vtr_pool( ghost ) ) ) ) { - fd_ghost_vtr_t * vtr = vtr_dlist_ele_pop_head( dlist, vtr_pool( ghost ) ); - vtr_map_ele_remove_fast( vtr_map( ghost ), vtr, vtr_pool( ghost ) ); - vtr_pool_ele_release( vtr_pool( ghost ), vtr ); - } - - blk_pool_ele_release( blk_pool( ghost ), head ); /* free prune queue head */ - head = next; /* move prune queue head forward */ - } - newr->parent = null; /* unlink old root */ - newr->sibling = null; /* root has no siblings */ - ghost->root = blk_pool_idx( blk_pool( ghost ), newr ); /* replace with new root */ -} - -/* mark_invalid marks the entire subtree beginning from root as invalid. - Implementation is iterative pre-order traversal using O(1) space. */ - -static void -mark_invalid( fd_ghost_t * ghost, - fd_ghost_blk_t * root ) { - fd_ghost_blk_t * pool = blk_pool( ghost ); - fd_ghost_blk_t * curr = root; - - /* Loop invariant: curr has not been visited. - - Before: curr = root, which has not been visited. Trivially true. - - After: curr is set to either a child (step 2) or a right sibling of - an ancestor found during backtracking (step 3). Preorder visits - parents before children and left before right, so neither has been - visited yet. If backtracking reaches root (step 4), loop exits. */ - - for(;;) { - - /* 1. Visit: mark the current curr invalid. */ - - curr->valid = 0; - - /* 2. Descend: if the curr has a child, pivot to it. */ - - fd_ghost_blk_t * child = blk_pool_ele( pool, curr->child ); - if( FD_LIKELY( child ) ) { curr = child; continue; } - - /* 3. Backtrack: if the curr is a leaf, traverse up until we find an - ancestor with a right sibling, then pivot to that sibling. */ - - while( FD_LIKELY( curr!=root ) ) { - fd_ghost_blk_t * sibling = blk_pool_ele( pool, curr->sibling ); - if( FD_LIKELY( sibling ) ) { curr = sibling; break; } - curr = blk_pool_ele( pool, curr->parent ); - } - - /* 4. Terminate: if we backtrack all the way to root, the traversal - is complete. */ - - if( FD_UNLIKELY( curr==root ) ) break; - } -} - -void -fd_ghost_confirm( fd_ghost_t * ghost, - fd_hash_t const * confirmed_block_id ) { - fd_ghost_blk_t * pool = blk_pool( ghost ); - fd_ghost_blk_t * blk = blk_map_ele_query( blk_map( ghost ), confirmed_block_id, NULL, pool ); - if( FD_UNLIKELY( !blk ) ) return; - - /* Mark the confirmed block and its ancestors as valid, short- - circuiting at the first ancestor that is already valid. */ - - fd_ghost_blk_t * anc = blk; - while( FD_LIKELY( anc ) ) { - if( FD_LIKELY( anc->valid ) ) break; - anc->valid = 1; - anc = blk_pool_ele( pool, anc->parent ); - } -} - -void -fd_ghost_eqvoc( fd_ghost_t * ghost, - fd_hash_t const * block_id ) { - fd_ghost_blk_t * pool = blk_pool( ghost ); - fd_ghost_blk_t * blk = blk_map_ele_query( blk_map( ghost ), block_id, NULL, pool ); - if( FD_UNLIKELY( !blk ) ) return; - mark_invalid( ghost, blk ); -} - -ulong -fd_ghost_width( fd_ghost_t * ghost ) { - return ghost->width; -} - -fd_ghost_blk_t * -fd_ghost_blk_map_remove( fd_ghost_t * ghost, - fd_ghost_blk_t * blk ) { - return blk_map_ele_remove( blk_map( ghost ), &blk->id, NULL, blk_pool( ghost ) ); -} - -void -fd_ghost_blk_map_insert( fd_ghost_t * ghost, - fd_ghost_blk_t * blk ) { - blk_map_ele_insert( blk_map( ghost ), blk, blk_pool( ghost ) ); -} - -fd_ghost_blk_t * -fd_ghost_blk_child( fd_ghost_t * ghost, - fd_ghost_blk_t * blk ) { - return blk_pool_ele( blk_pool( ghost ), blk->child ); -} - -fd_ghost_blk_t * -fd_ghost_blk_sibling( fd_ghost_t * ghost, - fd_ghost_blk_t * blk ) { - return blk_pool_ele( blk_pool( ghost ), blk->sibling ); -} - -fd_ghost_blk_t * -fd_ghost_blk_next( fd_ghost_t * ghost, - fd_ghost_blk_t * blk ) { - return blk_pool_ele( blk_pool( ghost ), blk->next ); -} - -ulong -fd_ghost_blk_idx( fd_ghost_t * ghost, - fd_ghost_blk_t * blk ) { - return blk_pool_idx( blk_pool( ghost ), blk ); -} - -ulong -fd_ghost_blk_idx_null( fd_ghost_t * ghost ) { - return blk_pool_idx_null( blk_pool( ghost ) ); -} - -int -fd_ghost_verify( fd_ghost_t * ghost ) { - if( FD_UNLIKELY( !ghost ) ) { - FD_LOG_WARNING(( "NULL ghost" )); - return -1; - } - - if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)ghost, fd_ghost_align() ) ) ) { - FD_LOG_WARNING(( "misaligned ghost" )); - return -1; - } - - fd_wksp_t * wksp = fd_wksp_containing( ghost ); - if( FD_UNLIKELY( !wksp ) ) { - FD_LOG_WARNING(( "ghost must be part of a workspace" )); - return -1; - } - - fd_ghost_blk_t const * pool = blk_pool( ghost ); - - /* Check every ele that exists in pool exists in map. */ - - if( blk_map_verify( blk_map( ghost ), blk_pool_max( pool ), pool ) ) return -1; - - return 0; -} - -#include -#include - -#define BUF_MAX 4096 -#define DEPTH_MAX 512 - -static void -to_cstr( fd_ghost_t const * ghost, - fd_ghost_blk_t const * ele, - ulong total_stake, - int space, - const char * prefix, - char * cstr, - ulong len, - ulong * off, - ulong depth ) { - if( FD_UNLIKELY( depth>DEPTH_MAX ) ) return; - - fd_ghost_blk_t const * pool = blk_pool_const( ghost ); - int n; - - if( FD_UNLIKELY( ele == NULL ) ) return; - - if( FD_LIKELY( space > 0 ) && *off < len ) { - cstr[(*off)++] = '\n'; - } - - for( int i = 0; i < space && *off < len; i++ ) { - cstr[(*off)++] = ' '; - } - - if( FD_UNLIKELY( ele->stake > 100 ) ) { - } - - if( FD_UNLIKELY( total_stake == 0 ) ) { - if( *off < len ) { - n = snprintf( cstr + *off, len - *off, "%s%lu (%lu)", prefix, ele->slot, ele->stake ); - if( FD_UNLIKELY( n < 0 )) FD_LOG_CRIT(( "snprintf: %d", n )); - *off += (ulong)n; - } - } else { - double pct = ( (double)ele->stake / (double)total_stake ) * 100; - if( FD_UNLIKELY( pct < 0.99 ) ) { - if( *off < len ) { - n = snprintf( cstr + *off, len - *off, "%s%lu (%.0lf%%, %lu)", prefix, ele->slot, pct, ele->stake ); - if( FD_UNLIKELY( n < 0 )) FD_LOG_CRIT(( "snprintf: %d", n )); - *off += (ulong)n; - } - } else { - if( *off < len ) { - n = snprintf( cstr + *off, len - *off, "%s%lu (%.0lf%%)", prefix, ele->slot, pct ); - if( FD_UNLIKELY( n < 0 )) FD_LOG_CRIT(( "snprintf: %d", n )); - *off += (ulong)n; - } - } - } - - fd_ghost_blk_t const * curr = blk_pool_ele_const( pool, ele->child ); - - while( curr ) { - char const * next_prefix = blk_pool_ele_const( pool, curr->sibling ) ? "├── " : "└── "; - to_cstr( ghost, curr, total_stake, space + 4, next_prefix, cstr, len, off, depth + 1 ); /* TODO remove recursion */ - curr = blk_pool_ele_const( pool, curr->sibling ); - } -} - -char * -fd_ghost_to_cstr( fd_ghost_t const * ghost, - fd_ghost_blk_t const * root, - char * cstr, - ulong cstr_max, - ulong * cstr_len ) { - - ulong off = 0; - - int n = snprintf( cstr + off, cstr_max - off, "[Ghost]\n\n" ); - if( FD_UNLIKELY( n < 0 )) FD_LOG_CRIT(( "snprintf: %d", n )); - off += (ulong)n; - - to_cstr( ghost, root, root->total_stake, 0, "", cstr, cstr_max, &off, 0 ); - - if( off < cstr_max ) { - n = snprintf( cstr + off, cstr_max - off, "\n\n" ); - if( FD_UNLIKELY( n < 0 )) FD_LOG_CRIT(( "snprintf: %d", n )); - off += (ulong)n; - } - - cstr[fd_ulong_min( off++, cstr_max - 1 )] = '\0'; - *cstr_len = fd_ulong_min( off, cstr_max ); - return cstr; -} diff --git a/src/choreo/ghost/fd_ghost.h b/src/choreo/ghost/fd_ghost.h deleted file mode 100644 index e35d3c28a0c..00000000000 --- a/src/choreo/ghost/fd_ghost.h +++ /dev/null @@ -1,427 +0,0 @@ -#ifndef HEADER_fd_src_choreo_ghost_fd_ghost_h -#define HEADER_fd_src_choreo_ghost_fd_ghost_h - -/* fd_ghost implements Solana's LMD-GHOST ("latest message-driven greedy - heaviest-observed subtree") fork choice rule. - - LMD ("latest message-driven") means only a validator's latest vote - counts. If a validator votes for one fork than subsequently votes - for a different fork, their vote only counts towards the latter fork - and not the former. - - GHOST ("greedy heaviest-observed subtree") describes the fork choice - rule. Forks form a tree, where each node is a block. Here's an - example of a fork tree in which every block is labeled with its slot: - - /-- 3 - 1-- 2 - \-- 4 - - In the above tree 3 and 4 are different forks. The responsibility of - fork choice is to decide whether the validator should vote for 3 or 4 - which ultimately determines which fork the cluster converges on. - - In Solana, votes are stake-weighted. Here is the same tree with - stakes associated with each block. - - /-- 3 (30%) - 1 (80%) -- 2 (70%) - \-- 4 (38%) - - 80% of stake voted for 1, 70% for 2, 30% for 3 and 38% for 4. How - does fork choice pick 3 or 4? It traverses down the tree, beginning - from the root (1), then picks (2), then picks (4) where it terminates - and returns 4 as the best leaf. - - greedy: fork choice is a greedy algorithm. During traversal, on - each level of the tree it picks the locally optimal value. - - heaviest: pick based on the heaviest (highest) stake. - - observed: this is the validator's local view, and other validators - may have differing views because they've observed - different votes or different forks. - - subtree: sum the vote stake of an entire subtree rooted at a given - block, not just votes for the individual block itself. In - the tree above, 1 and 2 both have strictly more stake than - 3 or 4. That is because the stake for 3 and 4 both rolled - up into 2, and the stake for 2 rolled up into 1. There - were also votes for 1 and 2 that weren't just roll-ups so - the total stake for a parent can exceed (>=) the sum of its - children. - - The above diagrams used slot numbers for simplicity but ghost in fact - uses the `block_id`, a 32-byte hash that uniquely identifies a block, - to key the elements of the tree. The block_id ensures that when - there is equivocation (two or more blocks labeled with the same slot) - the blocks can be disambiguated. - - Ghost handles equivocation by marking forks invalid for fork choice - if any block along that fork equivocates. For example, consider the - following tree: - - /-- 4'(30%) - 1 (80%) -- 2 (70%) - \-- 4 (38%) - - This is the same tree from earlier except 3 has been replaced with - 4'. There are two equivocating blocks for slot 4: how does ghost - handle this? Ghost marks both 4 and 4' as invalid for fork choice. - So in this example, fork choice will pick 2 as the best leaf. - - Ghost can mark a fork valid again if it becomes "duplicate confirmed" - ie. it has received votes from >=52% of the cluster. Revisiting the - above tree, modified: - - /-- 4'(30%) - 1 (80%) -- 2 (70%) - \-- 4 (52%) <- gossip duplicate confirmed (>=52%) - - Now that 4 is "duplicate confirmed", ghost marks 4 as valid again. - Fork choice picks 4 as the best leaf. Note gossip duplicate - confirmation is separately tracked outside of fd_ghost. See the - fd_ghost API for how that works. */ - -#include "../fd_choreo_base.h" - -typedef struct fd_ghost fd_ghost_t; /* forward decl*/ - -/* fd_ghost_blk_t implements a left-child, right-sibling n-ary tree. - Each ele maintains the `pool` index of its left-most child - (`child_idx`), its immediate-right sibling (`sibling_idx`), and its - parent (`parent_idx`). - - This tree structure is gaddr-safe and supports accesses and - operations from processes with separate local ghost joins. */ - -struct __attribute__((aligned(128UL))) fd_ghost_blk { - fd_hash_t id; /* block_id (merkle root of the last FEC set in the slot) */ - ulong slot; /* slot this ele is tracking */ - ulong next; /* reserved for internal use by fd_pool, fd_map_chain and fd_ghost_publish */ - ulong parent; /* pool idx of the parent */ - ulong child; /* pool idx of the left-child */ - ulong sibling; /* pool idx of the right-sibling */ - ulong stake; /* sum of stake that has voted for this slot or any of its descendants */ - ulong total_stake; /* total stake for this blk */ - int valid; /* whether this block is valid for fork choice. an equivocating block is valid iff duplicate confirmed */ - ulong vtr_dlist_gaddr; /* wksp gaddr of the dlist of vtrs whose prev_block_id is this blk's id */ - ulong bank_seq; /* app-wide bank sequence number of the replayed bank for this block (fd_bank.bank_seq, >=1; refreshed if an evicted block is re-replayed) */ -}; -typedef struct fd_ghost_blk fd_ghost_blk_t; - -/* fd_ghost_vtr_t keeps track of what a voter previously voted for. A - vtr lives on exactly one ghost blk's vtr_dlist at any time, identified - by prev_block_id. When the voter switches forks, the vtr is moved - off the old blk's dlist onto the new blk's dlist. When a blk is - pruned by fd_ghost_publish, every vtr on its dlist is released back - to the vtr_pool; if those voters vote again on a non-pruned blk, a - fresh vtr is acquired. */ - -struct __attribute__((aligned(128UL))) fd_ghost_vtr { - fd_pubkey_t addr; /* map key, vote account address */ - ulong next; /* reserved for internal use by fd_pool */ - struct { - ulong prev; - ulong next; - } map; /* reserved for internal use by vtr_map_chain */ - struct { - ulong prev; - ulong next; - } dlist; /* reserved for internal use by vtr_dlist */ - ulong prev_stake; /* previous vote stake (vote can be from prior epoch) */ - ulong prev_slot; /* previous vote slot */ - fd_hash_t prev_block_id; /* previous vote block_id */ -}; -typedef struct fd_ghost_vtr fd_ghost_vtr_t; - -FD_PROTOTYPES_BEGIN - -/* Constructors */ - -/* fd_ghost_{align,footprint} return the required alignment and - footprint of a memory region suitable for use as ghost with up to - blk_max blocks and vtr_max voters. */ - -FD_FN_CONST ulong -fd_ghost_align( void ); - -FD_FN_CONST ulong -fd_ghost_footprint( ulong blk_max, - ulong vtr_max ); - -/* fd_ghost_new formats an unused memory region for use as a ghost. - mem is a non-NULL pointer to this region in the local address space - with the required footprint and alignment. */ - -void * -fd_ghost_new( void * shmem, - ulong blk_max, - ulong vtr_max, - ulong seed ); - -/* fd_ghost_join joins the caller to the ghost. ghost points to the - first byte of the memory region backing the ghost in the caller's - address space. - - Returns a pointer in the local address space to ghost on success. */ - -fd_ghost_t * -fd_ghost_join( void * ghost ); - -/* fd_ghost_leave leaves a current local join. Returns a pointer to the - underlying shared memory region on success and NULL on failure (logs - details). Reasons for failure include ghost is NULL. */ - -void * -fd_ghost_leave( fd_ghost_t const * ghost ); - -/* fd_ghost_delete unformats a memory region used as a ghost. - Assumes only the nobody is joined to the region. Returns a - pointer to the underlying shared memory region or NULL if used - obviously in error (e.g. ghost is obviously not a ghost ... logs - details). The ownership of the memory region is transferred to the - caller. */ - -void * -fd_ghost_delete( void * ghost ); - -/* Accessors */ - -/* fd_ghost_root returns a pointer in the caller's address space to the - root. Assumes ghost is a current local join. */ - -fd_ghost_blk_t * -fd_ghost_root( fd_ghost_t * ghost ); - -/* fd_ghost_parent returns a pointer in the caller's address space to - blk's parent. Assumes ghost is a current local join and blk is a - valid pointer to a pool element inside ghost. */ - -fd_ghost_blk_t * -fd_ghost_parent( fd_ghost_t * ghost, fd_ghost_blk_t * blk ); - -/* fd_ghost_width returns the width of the ghost tree. */ - -ulong -fd_ghost_width( fd_ghost_t * ghost ); - -/* fd_ghost_query returns the block keyed by block_id. Returns NULL if - not found. */ - -fd_ghost_blk_t * -fd_ghost_query( fd_ghost_t * ghost, - fd_hash_t const * block_id ); - -/* fd_ghost_best returns the best block (according to fork choice) in - the subtree beginning at root. This is the ideal block to vote on or - reset to, and feeds into downstream TowerBFT rules. This is usually - a leaf node in the tree but may not when blocks are marked invalid - due to unconfirmed duplicates. Assumes root is marked valid so this - will never return NULL. */ - -fd_ghost_blk_t * -fd_ghost_best( fd_ghost_t * ghost, - fd_ghost_blk_t * root ); - -/* fd_ghost_deepest returns the deepest ghost block (highest tree depth) - in the subtree beginning at root. Unlike fd_ghost_best, deepest can - return a block marked invalid for fork choice. In case of ties, the - returned block will be the most recently inserted one. This will - never return NULL. */ - -fd_ghost_blk_t * -fd_ghost_deepest( fd_ghost_t * ghost, - fd_ghost_blk_t * root ); - - -/* fd_ghost_ancestor returns the ancestor on the same fork as descendant - keyed by ancestor_id. Returns NULL if not found. */ - -fd_ghost_blk_t * -fd_ghost_ancestor( fd_ghost_t * ghost, - fd_ghost_blk_t * descendant, - fd_hash_t const * ancestor_id ); - -/* fd_ghost_slot_ancestor returns the ancestor on the same fork as - descendant with ancestor_slot. Returns NULL if not found. */ - -fd_ghost_blk_t * -fd_ghost_slot_ancestor( fd_ghost_t * ghost, - fd_ghost_blk_t * descendant, - ulong ancestor_slot ); - -/* fd_ghost_invalid_ancestor returns the first ancestor on the same fork - as descendant that is marked invalid. Includes checking descendant - itself. Returns NULL if there are no invalid ancestors. */ - -fd_ghost_blk_t * -fd_ghost_invalid_ancestor( fd_ghost_t * ghost, - fd_ghost_blk_t * descendant ); - -/* Operations */ - -/* fd_ghost_init initializes the ghost tree with a root block keyed by - block_id at the given slot. bank_seq is the app-wide bank sequence - number of the block (or ULONG_MAX if unknown). Must be called exactly - once before any fd_ghost_insert calls. Returns the new root block. */ - -fd_ghost_blk_t * -fd_ghost_init( fd_ghost_t * ghost, - ulong bank_seq, - ulong slot, - fd_hash_t const * block_id ); - -/* fd_ghost_insert inserts a new tree node representing a block keyed by - block_id (and slot). bank_seq is the app-wide bank sequence number of - the block (or ULONG_MAX if unknown). parent_block_id is used to link - this new block to its parent in the ghost tree. Returns the new - block. */ - -fd_ghost_blk_t * -fd_ghost_insert( fd_ghost_t * ghost, - ulong bank_seq, - ulong slot, - fd_hash_t const * block_id, - fd_hash_t const * parent_block_id ); - -/* fd_ghost_count_vote return codes. */ - -#define FD_GHOST_SUCCESS ( 0) /* vote counted successfully */ -#define FD_GHOST_ERR_NOT_VOTED (-1) /* slot == ULONG_MAX (hasn't voted) */ -#define FD_GHOST_ERR_VOTE_TOO_OLD (-2) /* slot < root->slot */ -#define FD_GHOST_ERR_ALREADY_VOTED (-3) /* slot <= prev_slot (not newer) */ - -/* fd_ghost_count_vote updates ghost's stake weights with the latest - vote on the given voter's tower. This counts pubkey's stake towards - all ancestors on the same fork as block_id. If the voter has - previously voted, it subtracts the voter's previous stake from all - ancestors of vtr->prev_block_id. This ensures that only the most - recent vote from a voter is counted (see top-level documentation - about the LMD rule). Returns FD_GHOST_SUCCESS on success, or a - negative FD_GHOST_ERR_* code if the vote was not counted. - - Note that the vote is not counted if it's not newer than the voter's - previous slot. This is done intentionally: equivocating votes will - be ignored. - - TODO the implementation can be made more efficient by incrementally - updating and short-circuiting ancestor traversals. Currently this is - bounded to O(h), where h is the height of ghost ie. O(block_max) in - the worst case. */ - -int -fd_ghost_count_vote( fd_ghost_t * ghost, - fd_ghost_blk_t * blk, - fd_pubkey_t const * vote_acc, - ulong stake, - ulong slot ); - -/* fd_ghost_publish publishes newr as the new ghost root, pruning any - blocks not in the subtree beginning from the new root (ie. new root - and all its descendants). Returns the new root. */ - -void -fd_ghost_publish( fd_ghost_t * ghost, - fd_ghost_blk_t * newr ); - -/* fd_ghost_confirm marks the block keyed by confirmed_block_id and its - entire ancestry (up to root) as valid for fork choice, short- - circuiting at the first ancestor that is already valid. No-op if - confirmed_block_id is not in ghost. Does not invalidate equivocating - blocks — the caller is responsible for calling fd_ghost_eqvoc on any - known equivocating block_ids. */ - -void -fd_ghost_confirm( fd_ghost_t * ghost, - fd_hash_t const * confirmed_block_id ); - -/* fd_ghost_eqvoc marks the block keyed by block_id and all of its - descendants as invalid for fork choice. No-op if block_id is not - in ghost. */ - -void -fd_ghost_eqvoc( fd_ghost_t * ghost, - fd_hash_t const * block_id ); - -/* Misc */ - -/* fd_ghost_verify checks the ghost is not obviously corrupt. Returns 0 - if verify succeeds, -1 otherwise. */ - -int -fd_ghost_verify( fd_ghost_t * ghost ); - -/* fd_ghost_to_cstr pretty-prints a formatted ghost tree beginning from - root (arbitrary node, not necessarily the current ghost root) to the - buffer cstr of at least cstr_max capacity. Returns a pointer to cstr - and on return cstr_len contains actual bytes written including NUL. - - Typical usage: - - fd_ghost_to_cstr( ghost, fd_ghost_root( ghost ) ); // stringify ghost beginning from the current ghost root - - Alternative usage: - - fd_ghost_ele_t const * ele = fd_ghost_query( slot ); - fd_ghost_to_cstr( ghost, fd_ghost_parent( fd_ghost_parent( ele ) ) ); // print from ele's grandparent - - */ -char * -fd_ghost_to_cstr( fd_ghost_t const * ghost, - fd_ghost_blk_t const * root, - char * cstr, - ulong cstr_max, - ulong * cstr_len ); - -/* fd_ghost_blk_map_remove removes blk from the ghost map. Assumes blk - is in the map. Returns blk. */ - -fd_ghost_blk_t * -fd_ghost_blk_map_remove( fd_ghost_t * ghost, - fd_ghost_blk_t * blk ); - -/* fd_ghost_blk_map_insert inserts a ghost blk into the ghost map. */ - -void -fd_ghost_blk_map_insert( fd_ghost_t * ghost, - fd_ghost_blk_t * blk ); - -/* fd_ghost_blk_child returns the left-most child of blk, or NULL if - blk has no children. */ - -fd_ghost_blk_t * -fd_ghost_blk_child( fd_ghost_t * ghost, - fd_ghost_blk_t * blk ); - -/* fd_ghost_blk_sibling returns the next sibling of blk, or NULL if - blk has no more siblings. */ - -fd_ghost_blk_t * -fd_ghost_blk_sibling( fd_ghost_t * ghost, - fd_ghost_blk_t * blk ); - -/* fd_ghost_blk_next returns the block linked via blk->next (used for - traversing a temporary linked list built from removed map elements), - or NULL if at the end of the list. */ - -fd_ghost_blk_t * -fd_ghost_blk_next( fd_ghost_t * ghost, - fd_ghost_blk_t * blk ); - -/* fd_ghost_blk_idx returns the pool index corresponding to blk. */ - -ulong -fd_ghost_blk_idx( fd_ghost_t * ghost, - fd_ghost_blk_t * blk ); - -/* fd_ghost_blk_idx_null returns the sentinel null index for the ghost - block pool. */ - -ulong -fd_ghost_blk_idx_null( fd_ghost_t * ghost ); - - -FD_PROTOTYPES_END - -#endif /* HEADER_fd_src_choreo_ghost_fd_ghost_h */ diff --git a/src/choreo/ghost/test_ghost.c b/src/choreo/ghost/test_ghost.c deleted file mode 100644 index cac3f56a418..00000000000 --- a/src/choreo/ghost/test_ghost.c +++ /dev/null @@ -1,1010 +0,0 @@ -#include "fd_ghost.c" - -void setup_block_ids( fd_hash_t * block_ids, ulong cnt ) { - for( ulong i = 0; i < cnt; i++ ) block_ids[i] = (fd_hash_t){ .ul = { i } }; -} - -/* - slot 0 - | - slot 1 - / \ - slot 2 | - | slot 3 - slot 4 | - slot 5 - | - slot 6 -*/ - -fd_ghost_t * -setup_ghost( fd_wksp_t * wksp, ulong blk_max, ulong vtr_max ) { - void * mem = fd_wksp_alloc_laddr( wksp, fd_ghost_align(), fd_ghost_footprint( blk_max, vtr_max ), 42UL ); - fd_ghost_t * ghost = fd_ghost_join( fd_ghost_new( mem, blk_max, vtr_max, 42UL ) ); - FD_TEST( ghost ); - fd_hash_t block_ids[7]; setup_block_ids( block_ids, 7 ); - fd_ghost_init( ghost, 0, 0, &block_ids[0] ); - fd_ghost_insert( ghost, 1, 1, &block_ids[1], &block_ids[0] ); - fd_ghost_insert( ghost, 2, 2, &block_ids[2], &block_ids[1] ); - fd_ghost_insert( ghost, 3, 3, &block_ids[3], &block_ids[1] ); - fd_ghost_insert( ghost, 4, 4, &block_ids[4], &block_ids[2] ); - fd_ghost_insert( ghost, 5, 5, &block_ids[5], &block_ids[3] ); - fd_ghost_insert( ghost, 6, 6, &block_ids[6], &block_ids[5] ); - return ghost; -} - -void -teardown_ghost( fd_ghost_t * ghost ) { - fd_wksp_free_laddr( fd_ghost_delete( fd_ghost_leave( ghost ) ) ); -} - -// fd_tower_vtr_t * -// setup_tower_voters( fd_wksp_t * wksp, ulong max, ... ) { -// void * mem = fd_wksp_alloc_laddr( wksp, fd_tower_vtr_align(), fd_tower_vtr_footprint( max ), 1UL ); -// fd_tower_vtr_t * tower_voters = fd_tower_vtr_join( fd_tower_vtr_new( mem, max ) ); -// FD_TEST( tower_voters ); - -// va_list ap; -// va_start( ap, max ); -// for( ulong i = 0; i < max; i++ ) { -// ulong addr = va_arg( ap, ulong ); -// ulong stake = va_arg( ap, ulong ); -// ulong vote = va_arg( ap, ulong ); - -// uchar data[3762]; -// memset( data, 0, sizeof(data) ); -// fd_vote_acc_state_t * state = (fd_vote_acc_state_t *)fd_type_pun( data ); -// state->kind = fd_vote_acc_STATE_CURRENT; -// state->cnt = 1; -// state->votes[0] = (fd_vote_acc_vote_t){ .slot = vote }; - -// fd_tower_vtr_push_tail( tower_voters, (fd_tower_vtr_t){ .addr = (fd_pubkey_t){ .ul = { addr } }, .stake = stake, .data = data } ); -// } -// va_end( ap ); -// return tower_voters; -// } - -// void -// teardown_tower_voters( fd_tower_vtr_t * accts ) { -// fd_wksp_free_laddr( fd_tower_vtr_delete( fd_tower_vtr_leave( accts ) ) ); -// } - -void -test_simple( fd_wksp_t * wksp ) { - fd_ghost_t * ghost = setup_ghost( wksp, 8, 8 ); - fd_hash_t block_ids[7]; setup_block_ids( block_ids, 7 ); - - fd_ghost_blk_t * root = fd_ghost_root( ghost ); - FD_TEST( root ); - FD_TEST( 0==memcmp( &root->id, &block_ids[0], sizeof(fd_hash_t) ) ); - FD_TEST( fd_ghost_best ( ghost, root )==fd_ghost_query( ghost, &block_ids[4] ) ); - FD_TEST( fd_ghost_deepest( ghost, root )==fd_ghost_query( ghost, &block_ids[6] ) ); - FD_TEST( !fd_ghost_verify( ghost ) ); - - teardown_ghost( ghost ); -} - -/* - slot 0 - | - slot 1 - / \ - slot 2 | - | slot 3 - slot 4 | - slot 5 - | - slot 6 - - ... - - slot 2 - | - slot 4 -*/ - -void -test_publish_left( fd_wksp_t * wksp ) { - fd_ghost_t * ghost = setup_ghost( wksp, 8, 8 ); - fd_hash_t block_ids[7]; setup_block_ids( block_ids, 7 ); - - FD_TEST( ghost->width == 2 ); - - fd_ghost_blk_t * blk2 = fd_ghost_query( ghost, &block_ids[2] ); - FD_TEST( blk2 ); - fd_ghost_publish( ghost, blk2 ); - - FD_TEST( ghost->width == 1 ); - - FD_TEST( !fd_ghost_query( ghost, &block_ids[0] ) ); - FD_TEST( !fd_ghost_query( ghost, &block_ids[1] ) ); - FD_TEST( !fd_ghost_query( ghost, &block_ids[3] ) ); - FD_TEST( !fd_ghost_query( ghost, &block_ids[5] ) ); - FD_TEST( !fd_ghost_query( ghost, &block_ids[6] ) ); - FD_TEST( fd_ghost_query( ghost, &block_ids[2] ) ); - FD_TEST( fd_ghost_query( ghost, &block_ids[4] ) ); - - fd_ghost_blk_t * root = fd_ghost_root( ghost ); - FD_TEST( root==fd_ghost_query( ghost, &block_ids[2] ) ); - FD_TEST( !fd_ghost_blk_sibling( ghost, root ) ); - FD_TEST( blk_pool_ele( blk_pool( ghost ), root->child )->slot == 4 ); - FD_TEST( fd_ghost_best ( ghost, root )==fd_ghost_query( ghost, &block_ids[4] ) ); - FD_TEST( fd_ghost_deepest( ghost, root )==fd_ghost_query( ghost, &block_ids[4] ) ); - FD_TEST( !fd_ghost_verify( ghost ) ); - FD_TEST( blk_pool_free( blk_pool( ghost ) )==blk_pool_max( blk_pool( ghost ) ) - 2 ); - - teardown_ghost( ghost ); -} - -/* - slot 0 - | - slot 1 - / \ - slot 2 | - | slot 3 - slot 4 | - slot 5 - | - slot 6 - - ... - - slot 3 - | - slot 5 - | - slot 6 -*/ - -void -test_publish_right( fd_wksp_t * wksp ) { - fd_ghost_t * ghost = setup_ghost( wksp, 8, 8 ); - fd_hash_t block_ids[7]; setup_block_ids( block_ids, 7 ); - - FD_TEST( ghost->width == 2 ); - - fd_ghost_blk_t * blk3 = fd_ghost_query( ghost, &block_ids[3] ); - FD_TEST( blk3 ); - fd_ghost_publish( ghost, blk3 ); - - FD_TEST( ghost->width == 1 ); - - FD_TEST( !fd_ghost_query( ghost, &block_ids[0] ) ); - FD_TEST( !fd_ghost_query( ghost, &block_ids[1] ) ); - FD_TEST( !fd_ghost_query( ghost, &block_ids[2] ) ); - FD_TEST( !fd_ghost_query( ghost, &block_ids[4] ) ); - FD_TEST( fd_ghost_query( ghost, &block_ids[3] ) ); - FD_TEST( fd_ghost_query( ghost, &block_ids[5] ) ); - FD_TEST( fd_ghost_query( ghost, &block_ids[6] ) ); - - fd_ghost_blk_t * root = fd_ghost_root( ghost ); - FD_TEST( root==fd_ghost_query( ghost, &block_ids[3] ) ); - FD_TEST( blk_pool_ele( blk_pool( ghost ), root->child )->slot == 5 ); - FD_TEST( blk_pool_ele( blk_pool( ghost ), blk_pool_ele( blk_pool( ghost ), root->child )->child )->slot == 6 ); - FD_TEST( fd_ghost_best ( ghost, root )==fd_ghost_query( ghost, &block_ids[6] ) ); - FD_TEST( fd_ghost_deepest( ghost, root )==fd_ghost_query( ghost, &block_ids[6] ) ); - FD_TEST( !fd_ghost_verify( ghost ) ); - FD_TEST( blk_pool_free( blk_pool( ghost ) )==blk_pool_max( blk_pool( ghost ) ) - 3 ); - - teardown_ghost( ghost ); -} - -void -test_best( fd_wksp_t * wksp ){ - fd_ghost_t * ghost = setup_ghost( wksp, 8, 8 ); - fd_hash_t block_ids[7]; setup_block_ids( block_ids, 7 ); - - fd_pubkey_t a = { .ul = { 0xa } }; - fd_pubkey_t b = { .ul = { 0xb } }; - - fd_ghost_count_vote( ghost, fd_ghost_query( ghost, &block_ids[2] ), &a, 50, 2 ); - fd_ghost_count_vote( ghost, fd_ghost_query( ghost, &block_ids[3] ), &b, 100, 3 ); - - FD_TEST( fd_ghost_query( ghost, &block_ids[2] )->stake==50 ); - FD_TEST( fd_ghost_query( ghost, &block_ids[3] )->stake==100 ); - - FD_TEST( fd_ghost_best( ghost, fd_ghost_root( ghost ) )->slot==6 ); - FD_TEST( fd_ghost_deepest( ghost, fd_ghost_root( ghost ) )->slot==6 ); - FD_TEST( !fd_ghost_verify( ghost ) ); - - /* Both a and b switched forks. */ - - fd_ghost_count_vote( ghost, fd_ghost_query( ghost, &block_ids[4] ), &a, 100, 4 ); - fd_ghost_count_vote( ghost, fd_ghost_query( ghost, &block_ids[5] ), &b, 50, 5 ); - - FD_TEST( fd_ghost_query( ghost, &block_ids[2] )->stake==100 ); - FD_TEST( fd_ghost_query( ghost, &block_ids[4] )->stake==100 ); - FD_TEST( fd_ghost_query( ghost, &block_ids[3] )->stake==50 ); - FD_TEST( fd_ghost_query( ghost, &block_ids[5] )->stake==50 ); - - FD_TEST( fd_ghost_best( ghost, fd_ghost_root( ghost ) )->slot==4 ); - FD_TEST( fd_ghost_deepest( ghost, fd_ghost_root( ghost ) )->slot==6 ); - FD_TEST( !fd_ghost_verify( ghost ) ); - - teardown_ghost( ghost ); -} - -void -test_ignore_out_of_order_vote( fd_wksp_t * wksp ) { - fd_ghost_t * ghost = setup_ghost( wksp, 8, 8 ); - fd_hash_t block_ids[7]; setup_block_ids( block_ids, 7 ); - - fd_pubkey_t voter = { .ul = { 0xc } }; - ulong stake = 100UL; - - fd_ghost_count_vote( ghost, fd_ghost_query( ghost, &block_ids[5] ), &voter, stake, 5UL ); - FD_TEST( fd_ghost_query( ghost, &block_ids[5] )->stake == stake ); - FD_TEST( fd_ghost_best( ghost, fd_ghost_root( ghost ) )->slot == 6UL ); - - /* Vote for slot 4 arrives after slot 5. Since 4 < 5, this vote - should be ignored and should not move stake to the other fork. */ - fd_ghost_count_vote( ghost, fd_ghost_query( ghost, &block_ids[4] ), &voter, stake, 4UL ); - - FD_TEST( fd_ghost_query( ghost, &block_ids[5] )->stake == stake ); - FD_TEST( fd_ghost_query( ghost, &block_ids[4] )->stake == 0UL ); - FD_TEST( fd_ghost_best( ghost, fd_ghost_root( ghost ) )->slot == 6UL ); - FD_TEST( !fd_ghost_verify( ghost ) ); - - teardown_ghost( ghost ); -} - -// void -// test_vote_leaves( fd_wksp_t * wksp ) { -// ulong blk_max = 8; -// ulong total_stake = 40; - -// void * mem = fd_wksp_alloc_laddr( wksp, fd_ghost_align(), fd_ghost_footprint( 8, 8 ), 42UL ); -// fd_ghost_t * ghost = fd_ghost_join( fd_ghost_new( mem, 8, 8, 42UL ) ); -// FD_TEST( ghost ); - -// fd_hash_t block_ids[blk_max]; setup_block_ids( block_ids, blk_max ); -// block_ids[0] = (fd_hash_t) { .ul = { ULONG_MAX } }; -// for( ulong i = 1; i < blk_max; i++ ) { -// block_ids[i] = (fd_hash_t) { .key = { (uchar)i } }; -// } - -// /* make a full binary tree */ -// fd_ghost_insert( ghost, &block_ids[0], NULL, 0 ); -// for( ulong i = 1; i < blk_max - 1; i++){ -// FD_LOG_NOTICE(( "inserting %lu with parent %lu", i, (i-1)/2 )); -// fd_ghost_insert( ghost, &block_ids[i], &block_ids[(i-1)/2], i ); -// FD_TEST( !fd_ghost_verify( ghost ) ); -// } -// FD_TEST( !fd_ghost_verify( ghost ) ); - -// /* one validator changes votes along leaves */ -// int d = 3; -// ulong first_leaf = fd_ulong_pow2(d-1) - 1; -// fd_vote_acc_t v = { .key = { { 0 } }, .stake = 10, .replay_vote = { .slot = FD_SLOT_NULL } }; -// for( ulong i = first_leaf; i < blk_max - 1; i++){ -// fd_ghost_count_vote( ghost, &v, &block_ids[i] ); -// } -// FD_TEST( !fd_ghost_verify( ghost ) ); - - -// # if PRINT -// fd_ghost_print( ghost, total_stake, fd_ghost_root( ghost ) ); -// # else -// (void)total_stake; -// # endif - -// ulong path[d]; -// ulong leaf = blk_max - 2; -// for( int i = d - 1; i >= 0; i--){ -// path[i] = leaf; -// leaf = (leaf - 1) / 2; -// } - -// /* check weights and stakes */ -// int j = 0; -// for( ulong i = 0; i < blk_max - 1; i++){ -// fd_ghost_blk_t const * blk = fd_ghost_query( ghost, &hash_arr[i] ); -// if ( i == blk_max - 2) FD_TEST( blk->replay_stake == 10 ); -// else FD_TEST( blk->replay_stake == 0 ); - -// if( i == path[j] ) { /* if on fork */ -// FD_TEST( blk->stake == 10 ); -// j++; -// } else { -// FD_TEST( blk->stake == 0 ); -// } -// } - -// /* have other validators vote for rest of leaves */ -// for ( ulong i = first_leaf; i < blk_max - 2; i++){ -// fd_vote_acc_t v = { .key = { .key = { (uchar)i } }, .stake = 10, .replay_vote = { .slot = FD_SLOT_NULL } }; -// fd_ghost_count_vote( ghost, &v, &hash_arr[i] ); -// FD_TEST( !fd_ghost_verify( ghost ) ); -// } - -// /* check weights and stakes */ -// for( ulong i = 0; i < blk_max - 1; i++){ -// fd_ghost_blk_t const * blk = fd_ghost_query( ghost, &hash_arr[i] ); -// if ( i >= first_leaf){ -// FD_TEST( blk->replay_stake == 10 ); -// FD_TEST( blk->stake == 10 ); -// } else { -// FD_TEST( blk->replay_stake == 0 ); -// FD_TEST( blk->stake > 10); -// } -// } - -// FD_TEST( !fd_ghost_verify( ghost ) ); -// # if PRINT -// fd_ghost_print( ghost, total_stake, fd_ghost_root( ghost ) ); -// # endif -// } - -// void -// test_old_vote_pruned( fd_wksp_t * wksp ){ -// ulong blk_max = 16; -// ulong total_stake = 50; -// void * mem = fd_wksp_alloc_laddr( wksp, -// fd_ghost_align(), -// fd_ghost_footprint( blk_max ), -// 1UL ); -// FD_TEST( mem ); -// fd_ghost_t * ghost = fd_ghost_join( fd_ghost_new( mem, blk_max, 0UL ) ); - -// fd_hash_t hash_arr[blk_max]; -// hash_arr[0] = (fd_hash_t) { .ul = { ULONG_MAX } }; -// for( ulong i = 1; i < blk_max; i++){ -// hash_arr[i] = (fd_hash_t) { .key = { (uchar)i } }; -// } - -// fd_ghost_init( ghost, 0, &hash_arr[0] ); -// for ( ulong i = 1; i < blk_max - 1; i++ ) { -// fd_ghost_update( ghost, &hash_arr[(i-1)/2], i, &hash_arr[i], total_stake ); -// fd_vote_acc_t v = { .key = { { (uchar)i } }, .stake = i, .replay_vote = { .slot = FD_SLOT_NULL } }; -// fd_ghost_count_vote( ghost, &v, &hash_arr[i] ); -// } - -// fd_ghost_publish( ghost, &hash_arr[1]); -// # if PRINT -// fd_ghost_print( ghost, total_stake, fd_ghost_root( ghost ) ); -// # else -// (void)total_stake; -// # endif - -// fd_vote_acc_t switch_voter = { .key = { { 5 } }, .stake = 5, .replay_vote = { .slot = 5 } }; -// fd_ghost_count_vote( ghost, &switch_voter, &hash_arr[9] ); -// /* switching to vote 9, from voting 5, that is > than the root */ -// # if PRINT -// fd_ghost_print( ghost, total_stake, fd_ghost_root( ghost ) ); -// # endif - -// FD_TEST( fd_ghost_query( ghost, &hash_arr[9] )->stake == 14 ); -// FD_TEST( fd_ghost_query( ghost, &hash_arr[3] )->stake == 18 ); -// FD_TEST( fd_ghost_query( ghost, &hash_arr[4] )->stake == 28 ); -// FD_TEST( fd_ghost_query( ghost, &hash_arr[1] )->stake == 47 ); /* full tree */ - -// FD_TEST( !fd_ghost_verify( ghost ) ); - -// fd_ghost_publish( ghost, &hash_arr[3] ); /* cut down to blks 3,7,8 */ -// /* now previously voted 2 ( < the root ) votes for 7 */ -// fd_vote_acc_t switch_voter2 = { .key = { { 2 } }, .stake = 2, .replay_vote = { .slot = 2 } }; -// fd_ghost_count_vote( ghost, &switch_voter2, &hash_arr[7] ); - -// # if PRINT -// fd_ghost_print( ghost, total_stake, fd_ghost_root( ghost ) ); -// # endif -// FD_TEST( fd_ghost_query( ghost, &hash_arr[7] )->stake == 9 ); -// FD_TEST( fd_ghost_query( ghost, &hash_arr[8] )->stake == 8 ); -// FD_TEST( fd_ghost_query( ghost, &hash_arr[3] )->stake == 20 ); - -// FD_TEST( !fd_ghost_verify( ghost ) ); -// } - -// void -// test_best_full_tree( fd_wksp_t * wksp ){ -// ulong blk_max = 16; -// ulong total_stake = 120; -// void * mem = fd_wksp_alloc_laddr( wksp, -// fd_ghost_align(), -// fd_ghost_footprint( blk_max ), -// 1UL ); -// FD_TEST( mem ); -// fd_ghost_t * ghost = fd_ghost_join( fd_ghost_new( mem, blk_max, 0UL ) ); - -// fd_hash_t hash_arr[blk_max]; -// hash_arr[0] = (fd_hash_t) { .ul = { ULONG_MAX } }; -// for( ulong i = 1; i < blk_max; i++){ -// hash_arr[i] = (fd_hash_t) { .key = { (uchar)i } }; -// } - -// fd_ghost_init( ghost, 0, &hash_arr[0] ); - -// for ( ulong i = 1; i < blk_max - 1; i++ ) { -// fd_ghost_update( ghost, &hash_arr[(i-1)/2], i, &hash_arr[i], total_stake ); -// fd_vote_acc_t v = { .key = { { (uchar)i } }, .stake = i, .replay_vote = { .slot = FD_SLOT_NULL } }; -// fd_ghost_count_vote( ghost, &v, &hash_arr[i] ); -// } - -// for ( ulong i = 0; i < blk_max - 1; i++ ) { -// fd_ghost_blk_t const * blk = fd_ghost_query( ghost, &hash_arr[i] ); -// FD_TEST( blk->replay_stake == i ); -// } - -// FD_TEST( !fd_ghost_verify( ghost ) ); - -// # if PRINT -// fd_ghost_print( ghost, total_stake, fd_ghost_root( ghost ) ); -// # endif -// fd_ghost_blk_t const * head = fd_ghost_best( ghost, fd_ghost_root( ghost ) ); - -// /* head will always be rightmost blk in this complete binary tree */ - -// FD_TEST( head->slot == 14 ); - -// /* add one more blk */ - -// fd_ghost_update( ghost, &hash_arr[(blk_max-2)/2], blk_max - 1, &hash_arr[blk_max - 1], total_stake ); -// fd_vote_acc_t v = { .key = { { (uchar)( blk_max - 1 ) } }, .stake = blk_max - 1, .replay_vote = { .slot = FD_SLOT_NULL } }; -// fd_ghost_count_vote( ghost, &v, &hash_arr[blk_max - 1]); - -// FD_TEST( !fd_ghost_verify( ghost ) ); -// head = fd_ghost_best( ghost, fd_ghost_root( ghost ) ); -// FD_TEST( head->slot == 14 ); - -// /* adding one more blk would fail. */ -// } - -/* - slot 10 - / \ - slot 11 | - | slot 12 - slot 13 -*/ - -// void -// test_best_valid( fd_wksp_t * wksp ) { -// ulong blk_max = 16; -// void * mem = fd_wksp_alloc_laddr( wksp, fd_ghost_align(), fd_ghost_footprint( blk_max ), 1UL ); -// FD_TEST( mem ); -// fd_ghost_t * ghost = fd_ghost_join( fd_ghost_new( mem, blk_max, 0UL ) ); - -// fd_pubkey_t pk1 = { { 1 } }; -// fd_pubkey_t pk2 = { { 2 } }; -// ulong total = 150; -// fd_epoch_t * epoch = mock_epoch( wksp, 150, 2, pk1, 50, pk2, 100 ); -// fd_vote_acc_t * v1 = fd_epoch_voters_query( fd_epoch_voters( epoch ), pk1, NULL ); -// fd_vote_acc_t * v2 = fd_epoch_voters_query( fd_epoch_voters( epoch ), pk2, NULL ); - -// fd_hash_t hash_10 = { .key = { 10 } }; -// fd_hash_t hash_11 = { .key = { 11 } }; -// fd_hash_t hash_12 = { .key = { 12 } }; -// fd_hash_t hash_13 = { .key = { 13 } }; - -// fd_ghost_init( ghost, 10, &hash_10 ); -// INSERT( 11, 10 ); -// INSERT( 12, 10 ); -// INSERT( 13, 11 ); - -// fd_ghost_count_vote( ghost, v1, &hash_11 ); -// FD_TEST( !fd_ghost_verify( ghost ) ); - -// fd_ghost_count_vote( ghost, v2, &hash_12 ); -// FD_TEST( !fd_ghost_verify( ghost ) ); - -// // fd_ghost_blk_t const * head = fd_ghost_best( ghost, fd_ghost_root( ghost ) ); -// // FD_TEST( head->slot == 12 ); - -// fd_ghost_count_vote( ghost, v1, &hash_13 ); -// FD_TEST( !fd_ghost_verify( ghost ) ); - -// // fd_ghost_blk_t const * head2 = fd_ghost_best( ghost, fd_ghost_root( ghost ) ); -// // FD_TEST( head2->slot == 12 ); - -// query_mut( ghost, 12 )->valid = 0; // mark 12 as invalid -// // fd_ghost_blk_t const * head3 = fd_ghost_best( ghost, fd_ghost_root( ghost ) ); -// // FD_TEST( head3->slot == 13 ); - -// fd_ghost_count_vote( ghost, v2, &hash_13 ); -// query_mut( ghost, 11 )->valid = 0; // mark 11 as invalid -// // fd_ghost_blk_t const * head4 = fd_ghost_best( ghost, fd_ghost_root( ghost ) ); -// // FD_TEST( head4->slot == 10 ); - -// query_mut( ghost, 12 )->valid = 1; // mark 12 as valid -// fd_ghost_blk_t const * head5 = fd_ghost_best( ghost, fd_ghost_root( ghost ) ); -// FD_TEST( head5->slot == 12 ); - -// # if PRINT -// fd_ghost_print( ghost, total, fd_ghost_root( ghost ) ); -// # else -// (void)total; -// # endif - -// fd_wksp_free_laddr( mem ); -// } - -// void -// test_duplicate_simple( fd_wksp_t * wksp ) { -// ulong blk_max = 16; -// ulong total_stake = 10; -// void * mem = fd_wksp_alloc_laddr( wksp, fd_ghost_align(), fd_ghost_footprint( blk_max ), 1UL ); -// FD_TEST( mem ); - - /* 1 - / \ - 2 2' - | | - 3 4 */ - -// fd_ghost_t * ghost = fd_ghost_join( fd_ghost_new( mem, blk_max, 0UL ) ); -// fd_ghost_blk_t * pool = pool( ghost ); - -// fd_hash_t hash_1 = { .key = { 1 } }; -// fd_hash_t hash_2 = { .key = { 2 } }; -// fd_hash_t hash_2_prime = { .key = { 2, 1 } }; -// fd_hash_t hash_3 = { .key = { 3 } }; -// fd_hash_t hash_4 = { .key = { 4 } }; - -// fd_ghost_init( ghost, 1, &hash_1 ); - -// /* We see 2 and 3 first, so we replay down the left branch first */ -// fd_ghost_update( ghost, &hash_1, 2, &hash_2, total_stake ); -// fd_ghost_update( ghost, &hash_2, 3, &hash_3, total_stake ); - -// /* We see evidence of 2' and 4. Add them to the tree */ -// fd_ghost_update( ghost, &hash_1, 2, &hash_2_prime, total_stake ); -// fd_ghost_update( ghost, &hash_2_prime, 4, &hash_4, total_stake ); - -// /* Only 1 - 2 - 3 should be visible in the slot map */ -// FD_TEST( memcmp( fd_ghost_hash( ghost, 1 ), &hash_1, sizeof(fd_hash_t) ) == 0 ); -// FD_TEST( memcmp( fd_ghost_hash( ghost, 2 ), &hash_2, sizeof(fd_hash_t) ) == 0 ); -// FD_TEST( memcmp( fd_ghost_hash( ghost, 3 ), &hash_3, sizeof(fd_hash_t) ) == 0 ); -// FD_TEST( memcmp( fd_ghost_hash( ghost, 4 ), &hash_4, sizeof(fd_hash_t) ) == 0 ); - -// fd_ghost_blk_t const * dup_child = fd_ghost_query( ghost, &hash_4 ); -// fd_ghost_blk_t const * dup_parent = pool_ele( pool, dup_child->parent ); -// FD_TEST( dup_parent->slot == 2 ); -// FD_TEST( memcmp( &dup_parent->key, &hash_2_prime, sizeof(fd_hash_t) ) == 0 ); - -// fd_ghost_print( ghost, total_stake, fd_ghost_root( ghost ) ); - -// FD_TEST( !fd_ghost_verify( ghost ) ); -// } - -// void -// test_many_duplicates( fd_wksp_t * wksp ){ -// ulong blk_max = 16; -// ulong total_stake = 10; -// void * mem = fd_wksp_alloc_laddr( wksp, -// fd_ghost_align(), -// fd_ghost_footprint( blk_max ), -// 1UL ); -// FD_TEST( mem ); - -// fd_ghost_t * ghost = fd_ghost_join( fd_ghost_new( mem, blk_max, 0UL ) ); -// fd_ghost_slot_map_t * map_slot = fd_ghost_slot_map( ghost ); -// fd_ghost_blk_t * pool = pool( ghost ); - -// fd_hash_t hash_1 = { .key = { 1 } }; -// fd_hash_t hash_2 = { .key = { 2 } }; -// fd_hash_t hash_2_prime = { .key = { 2, 1 } }; -// fd_hash_t hash_3 = { .key = { 3 } }; -// fd_hash_t hash_3_prime = { .key = { 3, 1 } }; -// fd_hash_t hash_4 = { .key = { 4 } }; -// fd_hash_t hash_4_prime = { .key = { 4, 1 } }; -// fd_hash_t hash_4_prime_prime = { .key = { 4, 1, 1 } }; - - /* 1 - / \ - 2 2' - | | - 3 3' - / \ | - 4 4' 4'' */ - -// fd_ghost_init( ghost, 1, &hash_1 ); - -// /* Slots I see initially*/ - -// fd_ghost_update( ghost, &hash_1, 2, &hash_2, total_stake ); -// fd_ghost_update( ghost, &hash_2, 3, &hash_3, total_stake ); -// fd_ghost_update( ghost, &hash_3, 4, &hash_4, total_stake ); - -// /* Evidence of duplicates */ - -// fd_ghost_update( ghost, &hash_1, 2, &hash_2_prime, total_stake ); -// fd_ghost_update( ghost, &hash_2_prime, 3, &hash_3_prime, total_stake ); -// fd_ghost_update( ghost, &hash_3_prime, 4, &hash_4_prime_prime, total_stake ); -// fd_ghost_update( ghost, &hash_3, 4, &hash_4_prime, total_stake ); - -// ulong visible_slots[4] = { 3, 1, 2, 4 }; -// fd_hash_t visible_hashes[4] = { hash_3, hash_1, hash_2, hash_4 }; -// int cnt = 0; -// for( fd_ghost_slot_map_iter_t iter = fd_ghost_slot_map_iter_init( map_slot, pool ); -// !fd_ghost_slot_map_iter_done( iter, map_slot, pool ); -// iter = fd_ghost_slot_map_iter_next( iter, map_slot, pool ) ) { -// fd_ghost_blk_t const * ele = fd_ghost_slot_map_iter_ele( iter, map_slot, pool ); -// FD_LOG_NOTICE(( "ele->slot: %lu, visible_slots[cnt]: %lu", ele->slot, visible_slots[cnt] )); -// FD_TEST( ele->slot == visible_slots[cnt] ); -// FD_TEST( memcmp( &ele->key, &visible_hashes[cnt], sizeof(fd_hash_t) ) == 0 ); -// cnt++; -// } -// FD_TEST( cnt == 4 ); - -// fd_ghost_print( ghost, total_stake, fd_ghost_root( ghost ) ); - -// /* Vote down the left branch */ -// fd_vote_acc_t v1 = { .key = { { 1 } }, .stake = 10, .replay_vote = { .slot = FD_SLOT_NULL } }; -// fd_ghost_count_vote( ghost, &v1, &hash_4 ); -// } - -// /* Key differences between Agave and Firedancer: Agave inserts to their -// fork tracking structure before the bank is frozen. Thus they can -// build forks and mark things duplicate/duplicate confirmed, but it has -// no effect because the bank is not yet frozen. However, Firedancer -// only adds to ghost once the bank is frozen, and the slot has been -// fully replayed. */ - -// void -// run_test_state_duplicate_then_bank_frozen( fd_wksp_t * wksp ) { -// /* covers both mid-replay and haven't started replay test cases */ -// ulong blk_max = 16; -// ulong total_stake = 10; -// void * mem = fd_wksp_alloc_laddr( wksp, -// fd_ghost_align(), -// fd_ghost_footprint( blk_max ), -// 1UL ); -// FD_TEST( mem ); - -// fd_ghost_t * ghost = fd_ghost_join( fd_ghost_new( mem, blk_max, 0UL ) ); - -// fd_hash_t hash_0 = { .ul = { ULONG_MAX } }; -// fd_hash_t hash_1 = { .key = { 1 } }; -// fd_hash_t hash_2 = { .key = { 2 } }; - -// fd_ghost_init( ghost, 0, &hash_0 ); -// fd_ghost_update( ghost, &hash_0, 1, &hash_1, total_stake ); - -// /* Get a duplicate message shred/gossip/repair. Nothing happens because -// the slot 2 has not yet been replayed */ -// process_duplicate( ghost, 2, total_stake ); -// FD_TEST( fd_ghost_hash( ghost, 2 ) == NULL ); -// FD_TEST( fd_ghost_best( ghost, fd_ghost_root( ghost ) )->slot == 1 ); - -// /* Finish replaying slot 2, hash 2 */ -// fd_ghost_update( ghost, &hash_1, 2, &hash_2, total_stake ); -// FD_TEST( !fd_ghost_query( ghost, &hash_2 )->valid ); -// /* Parent of 2 is 1, so head should be 1 */ -// FD_TEST( fd_ghost_best( ghost, fd_ghost_root( ghost ) )->slot == 1 ); -// } - -// void -// test_state_ancestor_confirmed_descendant_duplicate( fd_wksp_t * wksp ){ -// ulong blk_max = 16; -// ulong total_stake = 10; -// void * mem = fd_wksp_alloc_laddr( wksp, -// fd_ghost_align(), -// fd_ghost_footprint( blk_max ), -// 1UL ); -// FD_TEST( mem ); - -// /* 0 - 1 - 2 - 3 */ -// fd_ghost_t * ghost = fd_ghost_join( fd_ghost_new( mem, blk_max, 0UL ) ); -// fd_dup_seen_t * dup_map = fd_ghost_dup_map( ghost ); - -// fd_hash_t hash_0 = { .ul = { ULONG_MAX } }; -// fd_hash_t hash_1 = { .key = { 1 } }; -// fd_hash_t hash_2 = { .key = { 2 } }; -// fd_hash_t hash_3 = { .key = { 3 } }; - -// fd_ghost_init( ghost, 0, &hash_0 ); -// fd_ghost_update( ghost, &hash_0, 1, &hash_1, total_stake ); -// fd_ghost_update( ghost, &hash_1, 2, &hash_2, total_stake ); -// fd_ghost_update( ghost, &hash_2, 3, &hash_3, total_stake ); - -// FD_TEST( fd_ghost_best( ghost, fd_ghost_root( ghost ) )->slot == 3 ); -// process_duplicate_confirmed( ghost, &hash_2, 2 ); -// FD_TEST( fd_ghost_best( ghost, fd_ghost_root( ghost ) )->slot == 3 ); - -// /* mark 3 as duplicate */ -// process_duplicate( ghost, 3, total_stake ); -// FD_TEST( fd_dup_seen_map_query( dup_map, 3, NULL ) ); - -// FD_TEST( fd_ghost_best( ghost, fd_ghost_root( ghost ) )->slot == 2 ); -// } - -// void -// test_state_ancestor_duplicate_descendant_confirmed( fd_wksp_t * wksp ){ -// ulong blk_max = 16; -// ulong total_stake = 18; -// void * mem = fd_wksp_alloc_laddr( wksp, -// fd_ghost_align(), -// fd_ghost_footprint( blk_max ), -// 1UL ); -// FD_TEST( mem ); - -// fd_ghost_t * ghost = fd_ghost_join( fd_ghost_new( mem, blk_max, 0UL ) ); -// fd_dup_seen_t * dup_map = fd_ghost_dup_map( ghost ); - -// fd_hash_t hash_0 = { .ul = { ULONG_MAX } }; -// fd_hash_t hash_1 = { .key = { 1 } }; -// fd_hash_t hash_2 = { .key = { 2 } }; -// fd_hash_t hash_3 = { .key = { 3 } }; - -// fd_ghost_init( ghost, 0, &hash_0 ); -// fd_ghost_update( ghost, &hash_0, 1, &hash_1, total_stake ); -// fd_ghost_update( ghost, &hash_1, 2, &hash_2, total_stake ); -// fd_ghost_update( ghost, &hash_2, 3, &hash_3, total_stake ); - -// FD_TEST( fd_ghost_best( ghost, fd_ghost_root( ghost ) )->slot == 3 ); - -// process_duplicate( ghost, 2, total_stake ); -// FD_TEST( fd_dup_seen_map_query( dup_map, 2, NULL ) ); -// FD_TEST( fd_ghost_best( ghost, fd_ghost_root( ghost ) )->slot == 1 ); - -// fd_vote_acc_t v1 = { .key = { { 1 } }, .stake = 10, .replay_vote = { .slot = FD_SLOT_NULL } }; -// fd_ghost_count_vote( ghost, &v1, &hash_3 ); - -// FD_TEST( is_duplicate_confirmed( ghost, &hash_3, total_stake ) ); - -// /* 3 becomes duplicate confirmed */ -// if( is_duplicate_confirmed( ghost, &hash_3, total_stake ) ) { -// process_duplicate_confirmed( ghost, &hash_3, 3 ); -// } -// FD_TEST( fd_ghost_query( ghost, &hash_3 )->valid ); -// FD_TEST( fd_ghost_best( ghost, fd_ghost_root( ghost ) )->slot == 3 ); -// } - -// void -// test_state_descendant_confirmed_ancestor_duplicate( fd_wksp_t * wksp ){ -// ulong blk_max = 16; -// ulong total_stake = 18; -// void * mem = fd_wksp_alloc_laddr( wksp, -// fd_ghost_align(), -// fd_ghost_footprint( blk_max ), -// 1UL ); -// FD_TEST( mem ); - -// /* 0 - 1 - 2 - 3 */ -// fd_ghost_t * ghost = fd_ghost_join( fd_ghost_new( mem, blk_max, 0UL ) ); -// fd_dup_seen_t * dup_map = fd_ghost_dup_map( ghost ); - -// fd_hash_t hash_0 = { .ul = { ULONG_MAX } }; -// fd_hash_t hash_1 = { .key = { 1 } }; -// fd_hash_t hash_2 = { .key = { 2 } }; -// fd_hash_t hash_3 = { .key = { 3 } }; - -// fd_ghost_init( ghost, 0, &hash_0 ); -// fd_ghost_update( ghost, &hash_0, 1, &hash_1, total_stake ); -// fd_ghost_update( ghost, &hash_1, 2, &hash_2, total_stake ); -// fd_ghost_update( ghost, &hash_2, 3, &hash_3, total_stake ); - -// FD_TEST( fd_ghost_best( ghost, fd_ghost_root( ghost ) )->slot == 3 ); -// fd_vote_acc_t v1 = { .key = { { 1 } }, .stake = 10, .replay_vote = { .slot = FD_SLOT_NULL } }; -// fd_ghost_count_vote( ghost, &v1, &hash_3 ); - -// FD_TEST( is_duplicate_confirmed( ghost, &hash_3, total_stake ) ); - -// /* 3 becomes duplicate confirmed */ -// if( is_duplicate_confirmed( ghost, &hash_3, total_stake ) ) { -// process_duplicate_confirmed( ghost, &hash_3, 3 ); -// } -// fd_ghost_print( ghost, total_stake, fd_ghost_root( ghost ) ); -// for( ulong slot = 0; slot < 4; slot++ ) { -// fd_ghost_blk_t const * ele = fd_ghost_query( ghost, fd_ghost_hash( ghost, slot ) ); -// FD_TEST( ele->valid ); -// FD_LOG_NOTICE(("slot %lu, ele->key: %s", slot, FD_BASE58_ENC_32_ALLOCA(&ele->key) )); -// FD_TEST( is_duplicate_confirmed( ghost, &ele->key, total_stake ) ); -// } - -// process_duplicate( ghost, 1, total_stake ); -// FD_TEST( fd_dup_seen_map_query( dup_map, 1, NULL ) ); -// FD_TEST( fd_ghost_best( ghost, fd_ghost_root( ghost ) )->slot == 3 ); - -// } - -// void -// test_duplicate_after_frozen( fd_wksp_t * wksp ){ -// ulong blk_max = 16; -// ulong total_stake = 10; -// void * mem = fd_wksp_alloc_laddr( wksp, -// fd_ghost_align(), -// fd_ghost_footprint( blk_max ), -// 1UL ); -// FD_TEST( mem ); - -// fd_ghost_t * ghost = fd_ghost_join( fd_ghost_new( mem, blk_max, 0UL ) ); - -// fd_hash_t hash_0 = { .ul = { ULONG_MAX } }; -// fd_hash_t hash_1 = { .key = { 1 } }; -// fd_ghost_init( ghost, 0, &hash_0 ); -// fd_ghost_update( ghost, &hash_0, 1, &hash_1, total_stake ); -// process_duplicate( ghost, 1, total_stake ); - -// FD_TEST( fd_ghost_best( ghost, fd_ghost_root( ghost ) )->slot == 0 ); -// } - -// void -// test_duplicate_blk_inserted( fd_wksp_t * wksp ) { -// ulong blk_max = 16; -// ulong total_stake = 10; -// void * mem = fd_wksp_alloc_laddr( wksp, -// fd_ghost_align(), -// fd_ghost_footprint( blk_max ), -// 1UL ); -// FD_TEST( mem ); - -// fd_ghost_t * ghost = fd_ghost_join( fd_ghost_new( mem, blk_max, 0UL ) ); -// fd_hash_t hash0 = { .ul = { ULONG_MAX } }; -// fd_hash_t hash1 = { .key = { 1 } }; - -// fd_ghost_init( ghost, 0, &hash0 ); - -// ulong duplicate_slot = 1; - -// FD_TEST( fd_ghost_hash( ghost, duplicate_slot ) == NULL ); - -// // Simulate finish replaying a bank by inserting the slot - equivalent to: -// fd_ghost_update( ghost, &hash0, 1, &hash1, total_stake ); - -// // Test equivalent to: assert_eq!(blockstore.get_bank_hash(duplicate_slot).unwrap(), duplicate_slot_hash); -// fd_hash_t const * stored_hash = fd_ghost_hash( ghost, duplicate_slot ); -// FD_TEST( stored_hash != NULL ); -// FD_TEST( memcmp( stored_hash, &hash1, sizeof(fd_hash_t) ) == 0 ); - -// // Now test freezing another version of the same bank - this creates a duplicate scenario -// fd_hash_t new_bank_hash = { .key = { 2 } }; - -// // In Ghost, inserting the same slot with a different hash creates a duplicate -// fd_ghost_update( ghost, &hash0, duplicate_slot, &new_bank_hash, total_stake ); - -// // The slot map should still point to the original version (the "happy tree") -// // but the new hash should be tracked in the hash map -// fd_hash_t const * slot_map_hash = fd_ghost_hash( ghost, duplicate_slot ); -// FD_TEST( slot_map_hash != NULL ); -// FD_TEST( memcmp( slot_map_hash, &hash1, sizeof(fd_hash_t) ) == 0 ); // Still original hash - -// // The new hash should be queryable directly -// fd_ghost_blk_t const * new_hash_ele = fd_ghost_query( ghost, &new_bank_hash ); -// FD_TEST( new_hash_ele != NULL ); -// FD_TEST( new_hash_ele->slot == duplicate_slot ); - -// // Clean up -// fd_wksp_free_laddr( fd_ghost_delete( fd_ghost_leave( ghost ) ) ); -// } - - -void -test_npow2_init( fd_wksp_t * wksp ) { - ulong npow2_blk_maxs[] = { 7, 10, 13, 17, 33, 65, 100 }; - ulong cnt = sizeof(npow2_blk_maxs) / sizeof(npow2_blk_maxs[0]); - - for( ulong i = 0; i < cnt; i++ ) { - ulong blk_max = npow2_blk_maxs[i]; - ulong vtr_max = blk_max; - - /* Verify footprint is nonzero. */ - ulong footprint = fd_ghost_footprint( blk_max, vtr_max ); - FD_TEST( footprint ); - - /* new / join */ - void * mem = fd_wksp_alloc_laddr( wksp, fd_ghost_align(), footprint, 42UL ); - FD_TEST( mem ); - fd_ghost_t * ghost = fd_ghost_join( fd_ghost_new( mem, blk_max, vtr_max, 42UL ) ); - FD_TEST( ghost ); - - /* Insert a small tree and verify. */ - fd_hash_t ids[4]; - for( ulong j = 0; j < 4; j++ ) ids[j] = (fd_hash_t){ .ul = { j + 1 } }; - fd_ghost_init( ghost, 0, 0, &ids[0] ); - fd_ghost_insert( ghost, 1, 1, &ids[1], &ids[0] ); - fd_ghost_insert( ghost, 2, 2, &ids[2], &ids[1] ); - fd_ghost_insert( ghost, 3, 3, &ids[3], &ids[1] ); - - FD_TEST( fd_ghost_query( ghost, &ids[0] ) ); - FD_TEST( fd_ghost_query( ghost, &ids[1] ) ); - FD_TEST( fd_ghost_query( ghost, &ids[2] ) ); - FD_TEST( fd_ghost_query( ghost, &ids[3] ) ); - FD_TEST( !fd_ghost_verify( ghost ) ); - - /* Cleanup */ - fd_wksp_free_laddr( fd_ghost_delete( fd_ghost_leave( ghost ) ) ); - } -} - -/* test_vtr_release_on_publish exercises the vtr lifecycle fix: vtrs are - acquired from the vtr_pool on first vote, parked on the dlist of the - block they voted for, and released back to the pool when that block - is pruned by fd_ghost_publish. - - 0 - | - 1 - / \ - 2 3 - | | - 4 5 - | - 6 - - Publishing 3 prunes {0,1,2,4}; survivors {3,5,6}. */ - -void -test_vtr_release_on_publish( fd_wksp_t * wksp ) { - fd_ghost_t * ghost = setup_ghost( wksp, 8, 8 ); - fd_hash_t block_ids[7]; setup_block_ids( block_ids, 7 ); - - vtr_pool_t * vp = vtr_pool( ghost ); - ulong vmax = vtr_pool_max( vp ); - FD_TEST( vtr_pool_free( vp )==vmax ); /* no voters tracked yet */ - - fd_pubkey_t a = { .ul = { 0xa } }; - fd_pubkey_t b = { .ul = { 0xb } }; - fd_pubkey_t c = { .ul = { 0xc } }; - - /* a -> slot 4 (left fork), b -> slot 2 (left fork), c -> slot 3 (right fork). */ - fd_ghost_count_vote( ghost, fd_ghost_query( ghost, &block_ids[4] ), &a, 10, 4 ); - fd_ghost_count_vote( ghost, fd_ghost_query( ghost, &block_ids[2] ), &b, 10, 2 ); - fd_ghost_count_vote( ghost, fd_ghost_query( ghost, &block_ids[3] ), &c, 10, 3 ); - FD_TEST( vtr_pool_free( vp )==vmax-3 ); /* three vtrs acquired */ - - /* Publish slot 3. a and b lived on the pruned left fork, so their - vtrs must be released; c is on a surviving block, so it stays. */ - fd_ghost_publish( ghost, fd_ghost_query( ghost, &block_ids[3] ) ); - - FD_TEST( !fd_ghost_query( ghost, &block_ids[2] ) ); - FD_TEST( !fd_ghost_query( ghost, &block_ids[4] ) ); - FD_TEST( fd_ghost_query( ghost, &block_ids[3] ) ); - FD_TEST( vtr_pool_free( vp )==vmax-1 ); /* a and b reclaimed, c kept */ - FD_TEST( vtr_map_ele_query( vtr_map( ghost ), &c, NULL, vp ) ); /* c still tracked */ - FD_TEST( !vtr_map_ele_query( vtr_map( ghost ), &a, NULL, vp ) ); /* a released */ - FD_TEST( !vtr_map_ele_query( vtr_map( ghost ), &b, NULL, vp ) ); /* b released */ - FD_TEST( !fd_ghost_verify( ghost ) ); - - /* Freed slots are reusable: a brand-new voter and a previously-released - voter both (re)acquire fresh vtrs on the surviving fork. */ - fd_pubkey_t d = { .ul = { 0xd } }; - fd_ghost_count_vote( ghost, fd_ghost_query( ghost, &block_ids[6] ), &d, 10, 6 ); - FD_TEST( vtr_pool_free( vp )==vmax-2 ); - fd_ghost_count_vote( ghost, fd_ghost_query( ghost, &block_ids[5] ), &a, 10, 5 ); - FD_TEST( vtr_pool_free( vp )==vmax-3 ); - FD_TEST( !fd_ghost_verify( ghost ) ); - - teardown_ghost( ghost ); -} - -int -main( int argc, char ** argv ) { - fd_boot( &argc, &argv ); - - char const * _page_sz = fd_env_strip_cmdline_cstr ( &argc, &argv, "--page-sz", NULL, "gigantic" ); - ulong page_cnt = fd_env_strip_cmdline_ulong( &argc, &argv, "--page-cnt", NULL, 1UL ); - ulong numa_idx = fd_env_strip_cmdline_ulong( &argc, &argv, "--numa-idx", NULL, fd_shmem_numa_idx( 0UL ) ); - fd_wksp_t * wksp = fd_wksp_new_anonymous( fd_cstr_to_shmem_page_sz( _page_sz ), page_cnt, fd_shmem_cpu_idx( numa_idx ), "wksp", 0UL ); - FD_TEST( wksp ); - - // // test_print( wksp ); - test_simple( wksp ); - test_publish_left( wksp ); - test_publish_right( wksp ); - test_best( wksp ); - test_ignore_out_of_order_vote( wksp ); - test_vtr_release_on_publish( wksp ); - test_npow2_init( wksp ); - // test_vote_leaves( wksp ); - // test_best_full_tree( wksp ); - // test_rooted_vote( wksp ); - // test_old_vote_pruned( wksp ); - // test_best_valid( wksp ); - // test_duplicate_simple( wksp ); - // test_many_duplicates( wksp ); - - // test_duplicate_after_frozen( wksp ); - // test_duplicate_blk_inserted( wksp ); - - // /* agave cluster_slot_state_verifier tests*/ - // test_state_ancestor_confirmed_descendant_duplicate( wksp ); - // run_test_state_duplicate_then_bank_frozen( wksp ); - // test_state_ancestor_duplicate_descendant_confirmed( wksp ); - // test_state_ancestor_duplicate_descendant_confirmed( wksp ); - // test_state_descendant_confirmed_ancestor_duplicate( wksp ); - - fd_halt(); - return 0; -} diff --git a/src/choreo/hfork/Local.mk b/src/choreo/hfork/Local.mk deleted file mode 100644 index e813f1167e1..00000000000 --- a/src/choreo/hfork/Local.mk +++ /dev/null @@ -1,6 +0,0 @@ -$(call add-hdrs,fd_hfork.h) -$(call add-objs,fd_hfork,fd_choreo) -ifdef FD_HAS_HOSTED -$(call make-unit-test,test_hfork,test_hfork,fd_choreo fd_flamenco fd_tango fd_ballet fd_util) -$(call run-unit-test,test_hfork) -endif diff --git a/src/choreo/hfork/fd_hfork.c b/src/choreo/hfork/fd_hfork.c deleted file mode 100644 index 4ba1afe7704..00000000000 --- a/src/choreo/hfork/fd_hfork.c +++ /dev/null @@ -1,653 +0,0 @@ -#include "fd_hfork.h" - -/* fd_hfork maintains four pools and four maps: - - bhm_pool (capacity max = per_vtr_max * vtr_max): pool of bhm_t - elements, where bhm stands for "bank hash matcher". Each - bhm tracks the aggregate stake and vote count for a - particular (block_id, bank_hash) pair. - - bhm_map (capacity max): maps bhm_key_t (block_id, bank_hash) -> - bhm_t for O(1) lookup of a specific bank hash matcher. - - blk_pool (capacity max): pool of blk_t elements. Each blk_t stores - per-block metadata (our_bank_hash, replayed, dead, matched, - mismatched, checked) and owns a bhm_dlist of all bhm entries sharing the - same block_id. - - blk_map (capacity max): maps block_id -> blk_t for O(1) lookup of - per-block metadata. - - vte_pool (capacity max): pool of vte_t elements. Each vte - records a single vote (block_id, bank_hash, slot, stake) - from a voter, stored in that voter's vte_dlist. When a - voter's vte_dlist reaches per_vtr_max entries, the oldest vte - is popped and its stake contribution is subtracted from - the corresponding bhm. - - vte_map (capacity max): maps vte_key_t (vote_acc, block_id) -> vte_t - for O(1) check of whether a voter has already voted for a - given block_id. If they have, the vote is ignored. - - vtr_map (capacity vtr_max): maps vote_acc -> vtr_t, tracking each - known voter. vtr entries are explicitly managed by - fd_hfork_update_voters when the epoch stake set changes. - Each vtr has a pre-allocated vte_dlist that tracks the - voter's recent votes in FIFO order. - - vtr_map blk_map - map[0] +--------------------+ map[0] +--------------------+ - | (vtr_t) { | | (blk_t) { | - | .vote_acc = X, | | .block_id = A, | - | ... | | ... | - | .vte_dlist = ... | | .bhm_dlist = ... | - | } | | } | - map[1] +--------------------+ map[1] +--------------------+ - | (vtr_t) { | | (blk_t) { | - | .vote_acc = Y, | | .block_id = B | - | ... | | ... | - | .vte_dlist = + | | .bhm_dlist = + | - | } | | | } | | - +----------------|---+ +----------------|---+ - | | - | | - | | - | bhm_dlist <------+ - | +--------------+--------------+--------------+ - | | (bhm_t) { | (bhm_t) { | (bhm_t) { | - | | .key = A0, | .key = A1, | .key = A2, | - | | ... | ... | ... | - | | } | } | } | - | +--------------+--------------+--------------+ - | - V - vte_dlist - +------------------------+------------------------+------------------------+ - | (vte_t) { | (vte_t) { | (vte_t) { | - | .key.vote_acc = Y, | .key.vote_acc = Y, | .key.vote_acc = Y, | - | .key.block_id = A, | .key.block_id = C, | .key.block_id = B, | - | .bank_hash = A0, | .bank_hash = C0, | .bank_hash = B0, | - | ... | ... | ... | - | } | } | } | - +------------------------+------------------------+------------------------+ - oldest newest - - vte_map prevents a voter from voting for the same block_id twice. - The adversary is bounded because when vte_cnt == per_vtr_max, the - oldest vte is popped and its stake is subtracted from the matching - bhm. */ - -typedef struct { - fd_hash_t block_id; - fd_hash_t bank_hash; -} bhm_key_t; - -struct bhm { - bhm_key_t key; /* bhm_map key */ - uint next; /* pool next */ - struct { - uint prev; - uint next; - } map; - struct { - uint prev; - uint next; - } dlist; - ulong slot; - ulong stake; -}; -typedef struct bhm bhm_t; - -#define POOL_NAME bhm_pool -#define POOL_LAZY 1 -#define POOL_T bhm_t -#define POOL_IDX_T uint -#include "../../util/tmpl/fd_pool.c" - -#define MAP_NAME bhm_map -#define MAP_ELE_T bhm_t -#define MAP_KEY_T bhm_key_t -#define MAP_PREV map.prev -#define MAP_NEXT map.next -#define MAP_IDX_T uint -#define MAP_KEY_EQ(k0,k1) (!memcmp((k0)->block_id.key,(k1)->block_id.key,32UL) & \ - !memcmp((k0)->bank_hash.key,(k1)->bank_hash.key,32UL)) -#define MAP_KEY_HASH(key,seed) ((ulong)((key)->block_id.ul[1]^(key)->bank_hash.ul[1]^(seed))) -#define MAP_OPTIMIZE_RANDOM_ACCESS_REMOVAL 1 -#include "../../util/tmpl/fd_map_chain.c" - -#define DLIST_NAME bhm_dlist -#define DLIST_ELE_T bhm_t -#define DLIST_PREV dlist.prev -#define DLIST_NEXT dlist.next -#define DLIST_IDX_T uint -#include "../../util/tmpl/fd_dlist.c" - -struct blk { - fd_hash_t block_id; /* blk_map key */ - uint prev; /* blk_map prev */ - uint next; /* pool next / blk_map next */ - struct { - uint prev; - uint next; - } dlist; - fd_hash_t our_bank_hash; /* 0: not replayed, -1: dead, else: our bank hash */ - void * bhm_dlist; /* dlist of bank hash objects for this block id */ - ulong bhm_cnt; /* number of competing bank hashes for this block id */ -}; -typedef struct blk blk_t; - -#define POOL_NAME blk_pool -#define POOL_LAZY 1 -#define POOL_T blk_t -#define POOL_IDX_T uint -#include "../../util/tmpl/fd_pool.c" - -#define MAP_NAME blk_map -#define MAP_ELE_T blk_t -#define MAP_KEY_T fd_hash_t -#define MAP_KEY block_id -#define MAP_PREV prev -#define MAP_NEXT next -#define MAP_IDX_T uint -#define MAP_KEY_EQ(k0,k1) (!memcmp((k0)->key,(k1)->key,32UL)) -#define MAP_KEY_HASH(key,seed) ((ulong)((key)->ul[1]^(seed))) -#define MAP_OPTIMIZE_RANDOM_ACCESS_REMOVAL 1 -#include "../../util/tmpl/fd_map_chain.c" - -#define DLIST_NAME blk_dlist -#define DLIST_ELE_T blk_t -#define DLIST_PREV dlist.prev -#define DLIST_NEXT dlist.next -#define DLIST_IDX_T uint -#include "../../util/tmpl/fd_dlist.c" - -typedef struct { - fd_pubkey_t vote_acc; - fd_hash_t block_id; -} vte_key_t; - -struct vte { - vte_key_t key; /* vte_map key: (vote_acc, block_id) */ - uint next; - struct { - uint prev; - uint next; - } vte_map; - struct { - uint prev; - uint next; - } dlist; - fd_hash_t bank_hash; - ulong slot; - ulong stake; -}; -typedef struct vte vte_t; - -#define POOL_NAME vte_pool -#define POOL_LAZY 1 -#define POOL_T vte_t -#define POOL_IDX_T uint -#include "../../util/tmpl/fd_pool.c" - -#define MAP_NAME vte_map -#define MAP_ELE_T vte_t -#define MAP_KEY_T vte_key_t -#define MAP_PREV vte_map.prev -#define MAP_NEXT vte_map.next -#define MAP_IDX_T uint -#define MAP_KEY_EQ(k0,k1) (!memcmp((k0)->vote_acc.key,(k1)->vote_acc.key,32UL) & \ - !memcmp((k0)->block_id.key,(k1)->block_id.key,32UL)) -#define MAP_KEY_HASH(key,seed) ((ulong)((key)->vote_acc.ul[1]^(key)->block_id.ul[1]^(seed))) -#define MAP_OPTIMIZE_RANDOM_ACCESS_REMOVAL 1 -#include "../../util/tmpl/fd_map_chain.c" - -#define DLIST_NAME vte_dlist -#define DLIST_ELE_T vte_t -#define DLIST_PREV dlist.prev -#define DLIST_NEXT dlist.next -#define DLIST_IDX_T uint -#include "../../util/tmpl/fd_dlist.c" - -struct vtr { - fd_pubkey_t vote_acc; - uint next; /* pool next; reused as kept flag during update_voters */ - struct { - uint prev; - uint next; - } map; - struct { - uint prev; - uint next; - } dlist; - vte_dlist_t * vte_dlist; - ulong vte_cnt; -}; -typedef struct vtr vtr_t; - -#define POOL_NAME vtr_pool -#define POOL_LAZY 1 -#define POOL_T vtr_t -#define POOL_IDX_T uint -#include "../../util/tmpl/fd_pool.c" - -#define MAP_NAME vtr_map -#define MAP_ELE_T vtr_t -#define MAP_KEY_T fd_pubkey_t -#define MAP_KEY vote_acc -#define MAP_PREV map.prev -#define MAP_NEXT map.next -#define MAP_IDX_T uint -#define MAP_KEY_EQ(k0,k1) (!memcmp((k0)->key,(k1)->key,sizeof(fd_pubkey_t))) -#define MAP_KEY_HASH(key,seed) ((ulong)((key)->ul[1]^(seed))) -#define MAP_OPTIMIZE_RANDOM_ACCESS_REMOVAL 1 -#include "../../util/tmpl/fd_map_chain.c" - -#define DLIST_NAME vtr_dlist -#define DLIST_ELE_T vtr_t -#define DLIST_PREV dlist.prev -#define DLIST_NEXT dlist.next -#define DLIST_IDX_T uint -#include "../../util/tmpl/fd_dlist.c" - -struct __attribute__((aligned(128UL))) fd_hfork { - ulong max; - ulong per_vtr_max; - ulong vtr_max; - bhm_t * bhm_pool; - bhm_map_t * bhm_map; - blk_t * blk_pool; - blk_map_t * blk_map; - blk_dlist_t * blk_dlist; - vte_t * vte_pool; - vte_map_t * vte_map; - vtr_t * vtr_pool; - vtr_map_t * vtr_map; - vtr_dlist_t * vtr_dlist; -}; -typedef struct fd_hfork fd_hfork_t; - - -/* bhm_remove removes a bhm from bhm_map, its owning blk's bhm_dlist, - and releases it back to bhm_pool. If the blk has no remaining bhm - entries, then the blk is also removed and released. */ - -static void -bhm_remove( fd_hfork_t * hfork, - bhm_t * bhm ) { - blk_t * blk = blk_map_ele_query( hfork->blk_map, &bhm->key.block_id, NULL, hfork->blk_pool ); - FD_TEST( blk ); - bhm_dlist_ele_remove( blk->bhm_dlist, bhm, hfork->bhm_pool ); - bhm_map_ele_remove_fast( hfork->bhm_map, bhm, hfork->bhm_pool ); - bhm_pool_ele_release( hfork->bhm_pool, bhm ); - blk->bhm_cnt--; - if( FD_UNLIKELY( !blk->bhm_cnt ) ) { - blk_map_ele_remove_fast( hfork->blk_map, blk, hfork->blk_pool ); - blk_pool_ele_release( hfork->blk_pool, blk ); - } -} - -static int -compare( blk_t * blk, - bhm_t * bhm, - ulong total_stake ) { - - if( FD_UNLIKELY( 0==memcmp( &blk->our_bank_hash, &hash_null, sizeof(fd_hash_t) ) ) ) return 0; - - double pct = (double)bhm->stake * 100.0 / (double)total_stake; - if( FD_UNLIKELY( pct < 52.0 ) ) return 0; - - if( FD_UNLIKELY( 0!=memcmp( &blk->our_bank_hash, &bhm->key.bank_hash, sizeof(fd_hash_t) ) ) ) return -1; - return 1; -} - -ulong -fd_hfork_align( void ) { - return 128UL; -} - -ulong -fd_hfork_footprint( ulong per_vtr_max, - ulong vtr_max ) { - - vtr_max = fd_ulong_pow2_up( vtr_max ); - if( FD_UNLIKELY( !per_vtr_max || !vtr_max || vtr_max>UINT_MAX || per_vtr_max>UINT_MAX/vtr_max ) ) return 0UL; - ulong max = fd_ulong_pow2_up( per_vtr_max * vtr_max ); - if( FD_UNLIKELY( !max || max>UINT_MAX ) ) return 0UL; - - ulong l = FD_LAYOUT_INIT; - l = FD_LAYOUT_APPEND( l, alignof(fd_hfork_t), sizeof(fd_hfork_t) ); - l = FD_LAYOUT_APPEND( l, bhm_pool_align(), bhm_pool_footprint( max ) ); - l = FD_LAYOUT_APPEND( l, bhm_map_align(), bhm_map_footprint( bhm_map_chain_cnt_est( max ) ) ); - l = FD_LAYOUT_APPEND( l, blk_pool_align(), blk_pool_footprint( max ) ); - l = FD_LAYOUT_APPEND( l, blk_map_align(), blk_map_footprint( blk_map_chain_cnt_est( max ) ) ); - l = FD_LAYOUT_APPEND( l, blk_dlist_align(), blk_dlist_footprint() ); - l = FD_LAYOUT_APPEND( l, vte_pool_align(), vte_pool_footprint( max ) ); - l = FD_LAYOUT_APPEND( l, vte_map_align(), vte_map_footprint( vte_map_chain_cnt_est( max ) ) ); - l = FD_LAYOUT_APPEND( l, vtr_pool_align(), vtr_pool_footprint( vtr_max ) ); - l = FD_LAYOUT_APPEND( l, vtr_map_align(), vtr_map_footprint( vtr_map_chain_cnt_est( vtr_max ) ) ); - l = FD_LAYOUT_APPEND( l, vtr_dlist_align(), vtr_dlist_footprint() ); - for( ulong i = 0UL; i < max; i++ ) { - l = FD_LAYOUT_APPEND( l, bhm_dlist_align(), bhm_dlist_footprint() ); - } - for( ulong i = 0UL; i < vtr_max; i++ ) { - l = FD_LAYOUT_APPEND( l, vte_dlist_align(), vte_dlist_footprint() ); - } - return FD_LAYOUT_FINI( l, fd_hfork_align() ); -} - -void * -fd_hfork_new( void * shmem, - ulong per_vtr_max, - ulong vtr_max, - ulong seed ) { - - if( FD_UNLIKELY( !shmem ) ) { - FD_LOG_WARNING(( "NULL mem" )); - return NULL; - } - - if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)shmem, fd_hfork_align() ) ) ) { - FD_LOG_WARNING(( "misaligned mem" )); - return NULL; - } - - ulong footprint = fd_hfork_footprint( per_vtr_max, vtr_max ); - if( FD_UNLIKELY( !footprint ) ) { - FD_LOG_WARNING(( "bad per_vtr_max (%lu) or vtr_max (%lu)", per_vtr_max, vtr_max )); - return NULL; - } - - vtr_max = fd_ulong_pow2_up( vtr_max ); - ulong max = fd_ulong_pow2_up( per_vtr_max * vtr_max ); - - FD_SCRATCH_ALLOC_INIT( l, shmem ); - fd_hfork_t * hfork = FD_SCRATCH_ALLOC_APPEND( l, fd_hfork_align(), sizeof(fd_hfork_t) ); - void * bhm_pool = FD_SCRATCH_ALLOC_APPEND( l, bhm_pool_align(), bhm_pool_footprint( max ) ); - void * bhm_map = FD_SCRATCH_ALLOC_APPEND( l, bhm_map_align(), bhm_map_footprint( bhm_map_chain_cnt_est( max ) ) ); - void * blk_pool = FD_SCRATCH_ALLOC_APPEND( l, blk_pool_align(), blk_pool_footprint( max ) ); - void * blk_map = FD_SCRATCH_ALLOC_APPEND( l, blk_map_align(), blk_map_footprint( blk_map_chain_cnt_est( max ) ) ); - void * blk_dlist = FD_SCRATCH_ALLOC_APPEND( l, blk_dlist_align(), blk_dlist_footprint() ); - void * vte_pool = FD_SCRATCH_ALLOC_APPEND( l, vte_pool_align(), vte_pool_footprint( max ) ); - void * vte_map = FD_SCRATCH_ALLOC_APPEND( l, vte_map_align(), vte_map_footprint( vte_map_chain_cnt_est( max ) ) ); - void * vtr_pool = FD_SCRATCH_ALLOC_APPEND( l, vtr_pool_align(), vtr_pool_footprint( vtr_max ) ); - void * vtr_map = FD_SCRATCH_ALLOC_APPEND( l, vtr_map_align(), vtr_map_footprint( vtr_map_chain_cnt_est( vtr_max ) ) ); - void * vtr_dlist = FD_SCRATCH_ALLOC_APPEND( l, vtr_dlist_align(), vtr_dlist_footprint() ); - - hfork->max = max; - hfork->per_vtr_max = per_vtr_max; - hfork->vtr_max = vtr_max; - hfork->bhm_pool = bhm_pool_new ( bhm_pool, max ); - hfork->bhm_map = bhm_map_new ( bhm_map, bhm_map_chain_cnt_est( max ), seed ); - hfork->blk_pool = blk_pool_new ( blk_pool, max ); - hfork->blk_map = blk_map_new ( blk_map, blk_map_chain_cnt_est( max ), seed ); - hfork->blk_dlist = blk_dlist_new( blk_dlist ); - hfork->vte_pool = vte_pool_new ( vte_pool, max ); - hfork->vte_map = vte_map_new ( vte_map, vte_map_chain_cnt_est( max ), seed ); - hfork->vtr_pool = vtr_pool_new ( vtr_pool, vtr_max ); - hfork->vtr_map = vtr_map_new ( vtr_map, vtr_map_chain_cnt_est( vtr_max ), seed ); - hfork->vtr_dlist = vtr_dlist_new( vtr_dlist ); - - blk_t * blk_join = blk_pool_join( hfork->blk_pool ); - for( ulong i = 0UL; i < max; i++ ) { - void * bhm_dlist = FD_SCRATCH_ALLOC_APPEND( l, bhm_dlist_align(), bhm_dlist_footprint() ); - blk_join[i].bhm_cnt = 0; - blk_join[i].bhm_dlist = bhm_dlist_new( bhm_dlist ); - } - - vtr_t * vtr_join = vtr_pool_join( hfork->vtr_pool ); - for( ulong i = 0UL; i < vtr_max; i++ ) { - void * vte_dlist = FD_SCRATCH_ALLOC_APPEND( l, vte_dlist_align(), vte_dlist_footprint() ); - vtr_join[i].vte_cnt = 0; - vtr_join[i].vte_dlist = vte_dlist_new( vte_dlist ); - } - FD_TEST( FD_SCRATCH_ALLOC_FINI( l, fd_hfork_align() ) == (ulong)shmem + footprint ); - return shmem; -} - -fd_hfork_t * -fd_hfork_join( void * shhfork ) { - fd_hfork_t * hfork = (fd_hfork_t *)shhfork; - - if( FD_UNLIKELY( !hfork ) ) { - FD_LOG_WARNING(( "NULL hfork" )); - return NULL; - } - - if( FD_UNLIKELY( !fd_ulong_is_aligned((ulong)hfork, fd_hfork_align() ) ) ) { - FD_LOG_WARNING(( "misaligned hfork" )); - return NULL; - } - - hfork->bhm_pool = bhm_pool_join ( hfork->bhm_pool ); - hfork->bhm_map = bhm_map_join ( hfork->bhm_map ); - hfork->blk_pool = blk_pool_join ( hfork->blk_pool ); - hfork->blk_map = blk_map_join ( hfork->blk_map ); - hfork->blk_dlist = blk_dlist_join( hfork->blk_dlist ); - hfork->vte_pool = vte_pool_join ( hfork->vte_pool ); - hfork->vte_map = vte_map_join ( hfork->vte_map ); - hfork->vtr_pool = vtr_pool_join ( hfork->vtr_pool ); - hfork->vtr_map = vtr_map_join ( hfork->vtr_map ); - hfork->vtr_dlist = vtr_dlist_join( hfork->vtr_dlist ); - for( ulong i = 0UL; i < hfork->max; i++ ) { - hfork->blk_pool[i].bhm_dlist = bhm_dlist_join( hfork->blk_pool[i].bhm_dlist ); - } - for( ulong i = 0UL; i < hfork->vtr_max; i++ ) { - hfork->vtr_pool[i].vte_dlist = vte_dlist_join( hfork->vtr_pool[i].vte_dlist ); - } - - return hfork; -} - -void * -fd_hfork_leave( fd_hfork_t const * hfork ) { - - if( FD_UNLIKELY( !hfork ) ) { - FD_LOG_WARNING(( "NULL hfork" )); - return NULL; - } - - return (void *)hfork; -} - -void * -fd_hfork_delete( void * hfork ) { - - if( FD_UNLIKELY( !hfork ) ) { - FD_LOG_WARNING(( "NULL hfork" )); - return NULL; - } - - if( FD_UNLIKELY( !fd_ulong_is_aligned((ulong)hfork, fd_hfork_align() ) ) ) { - FD_LOG_WARNING(( "misaligned hfork" )); - return NULL; - } - - return hfork; -} - -static blk_t * -blk_insert( fd_hfork_t * hfork, - fd_hash_t const * block_id ) { - if( FD_UNLIKELY( !blk_pool_free( hfork->blk_pool ) ) ) { - if( FD_UNLIKELY( blk_dlist_is_empty( hfork->blk_dlist, hfork->blk_pool ) ) ) return NULL; - blk_t * evicted = blk_dlist_ele_pop_head( hfork->blk_dlist, hfork->blk_pool ); - blk_map_ele_remove_fast( hfork->blk_map, evicted, hfork->blk_pool ); - blk_pool_ele_release( hfork->blk_pool, evicted ); - } - blk_t * blk = blk_pool_ele_acquire( hfork->blk_pool ); - blk->block_id = *block_id; - blk->our_bank_hash = hash_null; - blk->bhm_cnt = 0; - blk_map_ele_insert( hfork->blk_map, blk, hfork->blk_pool ); - return blk; -} - -int -fd_hfork_count_vote( fd_hfork_t * hfork, - fd_pubkey_t const * vote_acc, - fd_hash_t const * block_id, - fd_hash_t const * bank_hash, - ulong slot, - ulong stake, - ulong total_stake ) { - - /* Get the vtr. If not in the voter set, ignore. */ - - vtr_t * vtr = vtr_map_ele_query( hfork->vtr_map, vote_acc, NULL, hfork->vtr_pool ); - if( FD_UNLIKELY( !vtr ) ) return FD_HFORK_ERR_UNKNOWN_VTR; - - /* If voter already voted for this block_id, ignore. */ - - bhm_key_t bhm_key = { .block_id = *block_id, .bank_hash = *bank_hash }; - vte_key_t vte_key = { .vote_acc = *vote_acc, .block_id = *block_id }; - if( FD_UNLIKELY( vte_map_ele_query_const( hfork->vte_map, &vte_key, NULL, hfork->vte_pool ) ) ) return FD_HFORK_ERR_ALREADY_VOTED; - - /* Only process newer votes (by vote slot) from a given voter. */ - - if( FD_UNLIKELY( vtr->vte_cnt && vte_dlist_ele_peek_tail_const( vtr->vte_dlist, hfork->vte_pool )->slot >= slot ) ) return FD_HFORK_ERR_VOTE_TOO_OLD; - - /* Zero-stake votes don't contribute to hard fork detection. */ - - if( FD_UNLIKELY( !stake ) ) return FD_HFORK_SUCCESS; - - /* If voter has reached their quota, evict their oldest vote. */ - - if( FD_UNLIKELY( vtr->vte_cnt==hfork->per_vtr_max ) ) { - vte_t * evicted_vte = vte_dlist_ele_pop_head( vtr->vte_dlist, hfork->vte_pool ); - bhm_key_t evicted_bhm_key = { .block_id = evicted_vte->key.block_id, .bank_hash = evicted_vte->bank_hash }; - ulong evicted_stake = evicted_vte->stake; - vte_map_ele_remove_fast( hfork->vte_map, evicted_vte, hfork->vte_pool ); - vte_pool_ele_release( hfork->vte_pool, evicted_vte ); - vtr->vte_cnt--; - - bhm_t * bhm = bhm_map_ele_query( hfork->bhm_map, &evicted_bhm_key, NULL, hfork->bhm_pool ); - bhm->stake -= evicted_stake; - if( FD_UNLIKELY( !bhm->stake ) ) bhm_remove( hfork, bhm ); - } - - /* Upsert the blk. */ - - blk_t * blk = blk_map_ele_query( hfork->blk_map, block_id, NULL, hfork->blk_pool ); - if ( FD_UNLIKELY( !blk ) ) blk = blk_insert( hfork, block_id ); - else if( FD_UNLIKELY( !blk->bhm_cnt ) ) blk_dlist_ele_remove( hfork->blk_dlist, blk, hfork->blk_pool ); /* record_our_bank_hash before any count_vote */ - - /* Upsert the bhm. */ - - bhm_t * bhm = bhm_map_ele_query( hfork->bhm_map, &bhm_key, NULL, hfork->bhm_pool ); - if( FD_UNLIKELY( !bhm ) ) { - bhm = bhm_pool_ele_acquire( hfork->bhm_pool ); - bhm->key = bhm_key; - bhm->slot = slot; - bhm->stake = 0UL; - bhm_map_ele_insert( hfork->bhm_map, bhm, hfork->bhm_pool ); - bhm_dlist_ele_push_tail( blk->bhm_dlist, bhm, hfork->bhm_pool ); - blk->bhm_cnt++; - } - bhm->stake += stake; - bhm_dlist_ele_remove( blk->bhm_dlist, bhm, hfork->bhm_pool ); - bhm_dlist_ele_push_tail( blk->bhm_dlist, bhm, hfork->bhm_pool ); - - /* Push the vte onto the vtr. */ - - vte_t * vte = vte_pool_ele_acquire( hfork->vte_pool ); - vte->key = vte_key; - vte->bank_hash = *bank_hash; - vte->slot = slot; - vte->stake = stake; - vte_map_ele_insert( hfork->vte_map, vte, hfork->vte_pool ); - vte_dlist_ele_push_tail( vtr->vte_dlist, vte, hfork->vte_pool ); - vtr->vte_cnt++; - - /* Check for hard forks. */ - - return compare( blk, bhm, total_stake ); -} - -int -fd_hfork_record_our_bank_hash( fd_hfork_t * hfork, - fd_hash_t const * block_id, - fd_hash_t const * bank_hash, - ulong total_stake ) { - - blk_t * blk = blk_map_ele_query( hfork->blk_map, block_id, NULL, hfork->blk_pool ); - if( FD_LIKELY( !blk ) ) { - blk = blk_insert( hfork, block_id ); - if( FD_UNLIKELY( !blk ) ) return 0; - blk_dlist_ele_push_tail( hfork->blk_dlist, blk, hfork->blk_pool ); - } - blk->our_bank_hash = *fd_ptr_if( !!bank_hash, bank_hash, &hash_invalid ); - - /* Check all bhm entries for this block_id. */ - - for( bhm_dlist_iter_t iter = bhm_dlist_iter_fwd_init( blk->bhm_dlist, hfork->bhm_pool ); - !bhm_dlist_iter_done( iter, blk->bhm_dlist, hfork->bhm_pool ); - iter = bhm_dlist_iter_fwd_next( iter, blk->bhm_dlist, hfork->bhm_pool ) ) { - bhm_t * bhm = bhm_dlist_iter_ele( iter, blk->bhm_dlist, hfork->bhm_pool ); - int cmp = compare( blk, bhm, total_stake ); - if( cmp ) return cmp; - } - return 0; -} - -void -fd_hfork_update_voters( fd_hfork_t * hfork, - fd_pubkey_t const * vote_accs, - ulong cnt ) { - - for( vtr_dlist_iter_t iter = vtr_dlist_iter_fwd_init( hfork->vtr_dlist, hfork->vtr_pool ); - !vtr_dlist_iter_done( iter, hfork->vtr_dlist, hfork->vtr_pool ); - iter = vtr_dlist_iter_fwd_next( iter, hfork->vtr_dlist, hfork->vtr_pool ) ) { - hfork->vtr_pool[iter].next = 1; /* mark for removal */ - } - - /* First pass: unmark kept voters from being released. */ - - for( ulong i=0UL; ivtr_map, vote_acc, NULL, hfork->vtr_pool ); - if( FD_LIKELY( vtr ) ) { - vtr_dlist_ele_remove( hfork->vtr_dlist, vtr, hfork->vtr_pool ); - vtr->next = 0; /* unmark for removal */ - vtr_dlist_ele_push_tail( hfork->vtr_dlist, vtr, hfork->vtr_pool ); - } - } - - /* Pop and release marked voters until the first unmarked voter. */ - - while( FD_LIKELY( !vtr_dlist_is_empty( hfork->vtr_dlist, hfork->vtr_pool ) ) ) { - vtr_t * vtr = vtr_dlist_ele_pop_head( hfork->vtr_dlist, hfork->vtr_pool ); - if( FD_UNLIKELY( !vtr->next ) ) { /* can short-circuit since all the existing and new voters were appended */ - vtr_dlist_ele_push_tail( hfork->vtr_dlist, vtr, hfork->vtr_pool ); - break; - } - while( FD_LIKELY( !vte_dlist_is_empty( vtr->vte_dlist, hfork->vte_pool ) ) ) { - vte_t * vte = vte_dlist_ele_pop_head( vtr->vte_dlist, hfork->vte_pool ); - vte_map_ele_remove_fast( hfork->vte_map, vte, hfork->vte_pool ); - - bhm_key_t vte_xid = { .block_id = vte->key.block_id, .bank_hash = vte->bank_hash }; - bhm_t * bhm = bhm_map_ele_query( hfork->bhm_map, &vte_xid, NULL, hfork->bhm_pool ); - if( FD_LIKELY( bhm ) ) { - bhm->stake -= vte->stake; - if( FD_UNLIKELY( !bhm->stake ) ) bhm_remove( hfork, bhm ); - } - - vte_pool_ele_release( hfork->vte_pool, vte ); - } - vtr_map_ele_remove_fast( hfork->vtr_map, vtr, hfork->vtr_pool ); - vtr_pool_ele_release( hfork->vtr_pool, vtr ); - } - - /* Second pass: acquire and insert new voters. */ - - for( ulong i=0UL; ivtr_map, vote_acc, NULL, hfork->vtr_pool ) ) ) continue; - vtr_t * vtr = vtr_pool_ele_acquire( hfork->vtr_pool ); - vtr->vote_acc = *vote_acc; - vtr->vte_cnt = 0; - vtr->next = 0; - vtr_map_ele_insert( hfork->vtr_map, vtr, hfork->vtr_pool ); - vtr_dlist_ele_push_tail( hfork->vtr_dlist, vtr, hfork->vtr_pool ); - } -} diff --git a/src/choreo/hfork/fd_hfork.h b/src/choreo/hfork/fd_hfork.h deleted file mode 100644 index f24c4835bd7..00000000000 --- a/src/choreo/hfork/fd_hfork.h +++ /dev/null @@ -1,174 +0,0 @@ -#ifndef HEADER_fd_src_discof_tower_fd_hfork_h -#define HEADER_fd_src_discof_tower_fd_hfork_h - -/* fd_hfork presents an API for detecting hard forks by monitoring - votes from validators and comparing their bank hashes against ours. - - The Solana chain occasionally forks for what we will call soft and - hard reasons. - - - Soft forks occur due to network latency and the distributed - nature of the network. Different validators may see different - blocks at different times, and so may disagree on ledger state - temporarily until the network converges. This is expected and - normal. - - - Hard forks occur when validators have a disagreement about the - rules of the protocol and come to different conclusions about the - permanent state of the ledger. This is not expected or normal. - - For Firedancer, it is useful to be able to detect when any hard fork - has occurred, because it means there was likely some consensus bug - that caused us to diverge from Agave. What we check is actually - broader: it is possible that we disagreed about the result of some - block, but did not diverge from Agave as the block we disagreed on - did not become canonical. We still want to detect this case, as it - indicates a consensus bug even if we did not ultimately diverge. - - We detect hard forked blocks by monitoring votes from validators, the - specific criteria is: - - - 52% of stake has voted on a block_id and agreed on a bank_hash - which is different than the bank_hash we have for that block_id. - - - 52% of stake has voted on a block_id and agreed on a bank_hash - when we marked the block dead because it failed to execute. - - Interestingly we do not need to handle the case where we thought a - block succeeded, but the rest of the cluster failed it, because this - does not cause a hard fork. We simply switch to the canonical fork - the rest of the cluster will converge on. It is not really possible - to detect this case, because other nodes do not report that the block - failed, they simply vote somewhere else. - - We are essentially receiving a - - Stream> - - The first variant is a vote from some validator for a certain - block_id and block_hash. This could come from either any replayed - block, or gossip. It does not matter where it comes from, or even if - the source is valid (e.g. it could come from a block which gets - marked dead because it did not validate), all the matters is that the - vote is validly signed by the vote account. - - Internally, we maintain a Map>. - Any time we receive a vote, if it causes an entry in this map to - exceed 52% of the total stake weight, or it is from our own validator - identity, we check if our vote is different, and potentially raise a - warning or error. - - The last max_live_slots votes for each vote account are kept in a - ring buffer and replaced as new votes come in, to prevent unbounded - memory usage. This makes the structure somewhat heuristic: it might - be that if some nodes are very far ahead, and some behind, we might - evict old votes for those ahead and never see a fork exceed 52% in - our window. This is unlikely to happen in practice, and even if it - does, it only means we might miss detecting a hard fork, which is not - catastrophic. The default behavior anyway should be to continue - running on the forked chain. */ - -#include "../fd_choreo_base.h" - -struct fd_hfork; -typedef struct fd_hfork fd_hfork_t; - -FD_PROTOTYPES_BEGIN - -/* fd_hfork_{align,footprint} return the required alignment and - footprint of a memory region suitable for use as a hfork. align - returns fd_hfork_ALIGN. footprint returns fd_hfork_FOOTPRINT. */ - -FD_FN_CONST ulong -fd_hfork_align( void ); - -FD_FN_CONST ulong -fd_hfork_footprint( ulong per_vtr_max, - ulong vtr_max ); - -/* fd_hfork_new formats an unused memory region for use as a hfork. mem - is a non-NULL pointer to this region in the local address space with - the required footprint and alignment. */ - -void * -fd_hfork_new( void * mem, - ulong per_vtr_max, - ulong vtr_max, - ulong seed ); - -/* fd_hfork_join joins the caller to the hfork. hfork points to the - first byte of the memory region backing the hfork in the caller's - address space. - - Returns a pointer in the local address space to hfork on success. */ - -fd_hfork_t * -fd_hfork_join( void * hfork ); - -/* fd_hfork_leave leaves a current local join. Returns a pointer to the - underlying shared memory region on success and NULL on failure (logs - details). Reasons for failure include hfork is NULL. */ - -void * -fd_hfork_leave( fd_hfork_t const * hfork ); - -/* fd_hfork_delete unformats a memory region used as a hfork. Assumes - only the local process is joined to the region. Returns a pointer to - the underlying shared memory region or NULL if used obviously in - error (e.g. hfork is obviously not a hfork ... logs details). The - ownership of the memory region is transferred to the caller. */ - -void * -fd_hfork_delete( void * hfork ); - -/* fd_hfork_count_vote return codes. */ - -#define FD_HFORK_SUCCESS_MATCHED ( 1) /* vote counted, bank hash matches ours */ -#define FD_HFORK_SUCCESS ( 0) /* vote counted successfully */ -#define FD_HFORK_ERR_MISMATCHED (-1) /* vote counted, bank hash does not match ours */ -#define FD_HFORK_ERR_UNKNOWN_VTR (-2) /* voter not in vtr_map */ -#define FD_HFORK_ERR_ALREADY_VOTED (-3) /* voter already voted for this block_id */ -#define FD_HFORK_ERR_VOTE_TOO_OLD (-4) /* vote slot not newer than previous */ - -/* fd_hfork_count_vote updates the hard fork detector with a newly - observed vote. Returns FD_HFORK_SUCCESS_MATCHED if the bank hash - matches ours, FD_HFORK_SUCCESS if counted but not yet compared, - FD_HFORK_ERR_MISMATCHED if the bank hash does not match ours, or a - negative FD_HFORK_ERR_* code if the vote was not counted. */ - -int -fd_hfork_count_vote( fd_hfork_t * hfork, - fd_pubkey_t const * vote_acc, - fd_hash_t const * block_id, - fd_hash_t const * bank_hash, - ulong slot, - ulong stake, - ulong total_stake ); - -/* fd_hfork_record_our_bank_hash updates the hard fork detector with our - bank hash (computed on replay) for a given block ID. If bank_hash is - NULL, this indicates the block was marked dead during replay and we - did not think it was valid. Returns a flag indicating the outcome: - 1 (match), -1 (mismatch), or 0 (not yet compared). */ - -int -fd_hfork_record_our_bank_hash( fd_hfork_t * hfork, - fd_hash_t const * block_id, - fd_hash_t const * bank_hash, - ulong total_stake ); - -/* fd_hfork_update_voters updates the set of voters tracked by the hard - fork detector. Voters not in vote_accs[0..cnt) are removed along - with all their vote entries. New voters are added. This should be - called on each epoch boundary when the stake-weighted voter set - changes. vote_accs is an array of vote account addresses of length - cnt. */ - -void -fd_hfork_update_voters( fd_hfork_t * hfork, - fd_pubkey_t const * vote_accs, - ulong cnt ); - -FD_PROTOTYPES_END - -#endif /* HEADER_fd_src_discof_tower_fd_hfork_h */ diff --git a/src/choreo/hfork/test_hfork.c b/src/choreo/hfork/test_hfork.c deleted file mode 100644 index 1b70cd71243..00000000000 --- a/src/choreo/hfork/test_hfork.c +++ /dev/null @@ -1,505 +0,0 @@ -#include "fd_hfork.c" - -#define SCRATCH_MAX (1UL<<18) -static uchar scratch[ SCRATCH_MAX ] __attribute__((aligned(128))); - -static void -test_compact_indices( void ) { - FD_TEST( sizeof(bhm_t)==104UL ); - FD_TEST( sizeof(blk_t)==96UL ); - FD_TEST( sizeof(vte_t)==136UL ); - FD_TEST( sizeof(vtr_t)==72UL ); - FD_TEST( bhm_dlist_footprint()==16UL ); - FD_TEST( !fd_hfork_footprint( 1UL<<21, 1UL<<11 ) ); - FD_TEST( !fd_hfork_footprint( 1UL, 1UL<<32 ) ); -} - -/* Helper to query bhm by (block_id, bank_hash). */ -static bhm_t * -bhm_query( fd_hfork_t * hfork, fd_hash_t const * block_id, fd_hash_t const * bank_hash ) { - bhm_key_t bhm_key = { .block_id = *block_id, .bank_hash = *bank_hash }; - return bhm_map_ele_query( hfork->bhm_map, &bhm_key, NULL, hfork->bhm_pool ); -} - -/* Register voters manually (since we don't have tower_voters). */ -static void -register_voters( fd_hfork_t * hfork, fd_pubkey_t * voters, ulong cnt ) { - for( ulong i = 0; i < cnt; i++ ) { - vtr_t * vtr = vtr_pool_ele_acquire( hfork->vtr_pool ); - vtr->vote_acc = voters[i]; - vtr->vte_cnt = 0; - vtr_map_ele_insert( hfork->vtr_map, vtr, hfork->vtr_pool ); - vtr_dlist_ele_push_tail( hfork->vtr_dlist, vtr, hfork->vtr_pool ); - } -} - -void -test_count_vote( void ) { - ulong per_vtr_max = 8; - ulong vtr_max = 4; - - FD_TEST( fd_hfork_footprint( per_vtr_max, vtr_max ) <= SCRATCH_MAX ); - fd_hfork_t * hfork = fd_hfork_join( fd_hfork_new( scratch, per_vtr_max, vtr_max, 0 ) ); - FD_TEST( hfork ); - - fd_hash_t block_id = { .ul = { 100 } }; - fd_hash_t bank_hash = { .ul = { 200 } }; - fd_hash_t bank_hash2 = { .ul = { 202 } }; - fd_hash_t bank_hash3 = { .ul = { 203 } }; - ulong slot = 1000; - - fd_pubkey_t voters[4] = { - (fd_pubkey_t){ .ul = { 1 } }, - (fd_pubkey_t){ .ul = { 2 } }, - (fd_pubkey_t){ .ul = { 3 } }, - (fd_pubkey_t){ .ul = { 4 } }, - }; - register_voters( hfork, voters, 4 ); - - /* Unknown voter returns ERR_UNKNOWN_VTR. */ - - fd_pubkey_t unknown = { .ul = { 99 } }; - FD_TEST( fd_hfork_count_vote( hfork, &unknown, &block_id, &bank_hash, slot, 10, 100 )==FD_HFORK_ERR_UNKNOWN_VTR ); - - /* voter[0] votes for (block_id, bank_hash). */ - - FD_TEST( blk_dlist_is_empty( hfork->blk_dlist, hfork->blk_pool ) ); - fd_hfork_count_vote( hfork, &voters[0], &block_id, &bank_hash, slot, 10, 100 ); - FD_TEST( blk_dlist_is_empty( hfork->blk_dlist, hfork->blk_pool ) ); - bhm_t * bhm = bhm_query( hfork, &block_id, &bank_hash ); - FD_TEST( bhm ); - FD_TEST( bhm->stake ==10 ); - - /* voter[1] votes for the same (block_id, bank_hash) — per-voter - dedup allows different voters to vote for the same block_id. */ - - fd_hfork_count_vote( hfork, &voters[1], &block_id, &bank_hash, slot, 51, 100 ); - FD_TEST( bhm->stake ==61 ); - - /* voter[0] tries to vote again for the same block_id — ALREADY_VOTED. */ - - FD_TEST( fd_hfork_count_vote( hfork, &voters[0], &block_id, &bank_hash, slot+1, 10, 100 )==FD_HFORK_ERR_ALREADY_VOTED ); - FD_TEST( bhm->stake ==61 ); - - /* voter[0] votes for a new block_id but with an older slot — VOTE_TOO_OLD. */ - - fd_hash_t block_id2 = { .ul = { 101 } }; - FD_TEST( fd_hfork_count_vote( hfork, &voters[0], &block_id2, &bank_hash, slot - 1, 10, 100 )==FD_HFORK_ERR_VOTE_TOO_OLD ); - - /* 4 voters each report a different bank hash for the same block_id. */ - - fd_hfork_count_vote( hfork, &voters[2], &block_id, &bank_hash2, slot, 2, 100 ); - fd_hfork_count_vote( hfork, &voters[3], &block_id, &bank_hash3, slot, 3, 100 ); - - FD_TEST( bhm_query( hfork, &block_id, &bank_hash ) ); - FD_TEST( bhm_query( hfork, &block_id, &bank_hash2 ) ); - FD_TEST( bhm_query( hfork, &block_id, &bank_hash3 ) ); - - /* record_our_bank_hash and verify check fires when 52% match. */ - - FD_TEST( fd_hfork_record_our_bank_hash( hfork, &block_id, &bank_hash, 100 )==1 ); - - /* Eviction: use a fresh instance with per_vtr_max=3. */ - - fd_hfork_delete( fd_hfork_leave( hfork ) ); - - per_vtr_max = 3; - vtr_max = 2; - - FD_TEST( fd_hfork_footprint( per_vtr_max, vtr_max ) <= SCRATCH_MAX ); - hfork = fd_hfork_join( fd_hfork_new( scratch, per_vtr_max, vtr_max, 0 ) ); - FD_TEST( hfork ); - - fd_hash_t block_ids[5]; - fd_hash_t bank_hashes[5]; - ulong slots[5]; - for( ulong i = 0; i < 5; i++ ) { - block_ids[i] = (fd_hash_t){ .ul = { 100+i } }; - bank_hashes[i] = (fd_hash_t){ .ul = { 200+i } }; - slots[i] = 1000+i; - } - - fd_pubkey_t voters2[2] = { - (fd_pubkey_t){ .ul = { 1 } }, - (fd_pubkey_t){ .ul = { 2 } }, - }; - register_voters( hfork, voters2, 2 ); - - /* voter[0] votes for 3 different blocks (reaching per_vtr_max). */ - - fd_hfork_count_vote( hfork, &voters2[0], &block_ids[0], &bank_hashes[0], slots[0], 10, 100 ); - fd_hfork_count_vote( hfork, &voters2[0], &block_ids[1], &bank_hashes[1], slots[1], 10, 100 ); - fd_hfork_count_vote( hfork, &voters2[0], &block_ids[2], &bank_hashes[2], slots[2], 10, 100 ); - - FD_TEST( bhm_query( hfork, &block_ids[0], &bank_hashes[0] ) ); - FD_TEST( bhm_query( hfork, &block_ids[1], &bank_hashes[1] ) ); - FD_TEST( bhm_query( hfork, &block_ids[2], &bank_hashes[2] ) ); - - /* voter[0] votes for a 4th block — oldest (block_ids[0]) is evicted. - Since voter[0] was the only voter for block_ids[0], its bhm is - removed and the blk is also removed. */ - - fd_hfork_count_vote( hfork, &voters2[0], &block_ids[3], &bank_hashes[3], slots[3], 10, 100 ); - - FD_TEST( !bhm_query( hfork, &block_ids[0], &bank_hashes[0] ) ); - FD_TEST( !blk_map_ele_query_const( hfork->blk_map, &block_ids[0], NULL, hfork->blk_pool ) ); - FD_TEST( bhm_query( hfork, &block_ids[1], &bank_hashes[1] ) ); - FD_TEST( bhm_query( hfork, &block_ids[2], &bank_hashes[2] ) ); - FD_TEST( bhm_query( hfork, &block_ids[3], &bank_hashes[3] ) ); - - /* continued eviction — voter[0] votes for a 5th block, evicting - block_ids[1]. */ - - fd_hfork_count_vote( hfork, &voters2[0], &block_ids[4], &bank_hashes[4], slots[4], 10, 100 ); - - FD_TEST( !bhm_query( hfork, &block_ids[1], &bank_hashes[1] ) ); - FD_TEST( !blk_map_ele_query_const( hfork->blk_map, &block_ids[1], NULL, hfork->blk_pool ) ); - FD_TEST( bhm_query( hfork, &block_ids[2], &bank_hashes[2] ) ); - FD_TEST( bhm_query( hfork, &block_ids[3], &bank_hashes[3] ) ); - FD_TEST( bhm_query( hfork, &block_ids[4], &bank_hashes[4] ) ); - - fd_hfork_delete( fd_hfork_leave( hfork ) ); - - /* Eviction where evicted bhm still has other voters (stake > 0 - after decrement). Two voters vote for the same block, then one - voter evicts their vote by reaching per_vtr_max. */ - - per_vtr_max = 2; - vtr_max = 2; - - FD_TEST( fd_hfork_footprint( per_vtr_max, vtr_max ) <= SCRATCH_MAX ); - hfork = fd_hfork_join( fd_hfork_new( scratch, per_vtr_max, vtr_max, 0 ) ); - FD_TEST( hfork ); - - fd_pubkey_t voters3[2] = { - (fd_pubkey_t){ .ul = { 1 } }, - (fd_pubkey_t){ .ul = { 2 } }, - }; - register_voters( hfork, voters3, 2 ); - - fd_hash_t bid_shared = { .ul = { 500 } }; - fd_hash_t bh_shared = { .ul = { 600 } }; - - fd_hfork_count_vote( hfork, &voters3[0], &bid_shared, &bh_shared, 2000, 10, 100 ); - fd_hfork_count_vote( hfork, &voters3[1], &bid_shared, &bh_shared, 2000, 20, 100 ); - - bhm_t * bhm_shared = bhm_query( hfork, &bid_shared, &bh_shared ); - FD_TEST( bhm_shared ); - FD_TEST( bhm_shared->stake==30 ); - - /* voter[0] votes for a second block (reaching per_vtr_max=2). */ - - fd_hash_t bid_fill = { .ul = { 501 } }; - fd_hash_t bh_fill = { .ul = { 601 } }; - fd_hfork_count_vote( hfork, &voters3[0], &bid_fill, &bh_fill, 2001, 10, 100 ); - - /* voter[0] votes for a third block — evicts their oldest (bid_shared). - The bhm for (bid_shared, bh_shared) survives with voter[1]'s stake. */ - - fd_hash_t bid_evict = { .ul = { 502 } }; - fd_hash_t bh_evict = { .ul = { 602 } }; - fd_hfork_count_vote( hfork, &voters3[0], &bid_evict, &bh_evict, 2002, 10, 100 ); - - bhm_shared = bhm_query( hfork, &bid_shared, &bh_shared ); - FD_TEST( bhm_shared ); - FD_TEST( bhm_shared->stake==20 ); - - fd_hfork_delete( fd_hfork_leave( hfork ) ); - - /* Mismatch: record our bank hash, then voter votes with a different - bank hash at 52%+ stake — check returns -1 (mismatch). */ - - per_vtr_max = 4; - vtr_max = 2; - - FD_TEST( fd_hfork_footprint( per_vtr_max, vtr_max ) <= SCRATCH_MAX ); - hfork = fd_hfork_join( fd_hfork_new( scratch, per_vtr_max, vtr_max, 0 ) ); - FD_TEST( hfork ); - - fd_pubkey_t voter_mm = (fd_pubkey_t){ .ul = { 1 } }; - register_voters( hfork, &voter_mm, 1 ); - - fd_hash_t bid_mm = { .ul = { 700 } }; - fd_hash_t bh_ours = { .ul = { 800 } }; - fd_hash_t bh_theirs = { .ul = { 801 } }; - - fd_hfork_record_our_bank_hash( hfork, &bid_mm, &bh_ours, 100 ); - FD_TEST( fd_hfork_count_vote( hfork, &voter_mm, &bid_mm, &bh_theirs, 3000, 60, 100 )==FD_HFORK_ERR_MISMATCHED ); - - fd_hfork_delete( fd_hfork_leave( hfork ) ); -} - -void -test_update_voters( void ) { - ulong per_vtr_max = 8; - ulong vtr_max = 4; - - FD_TEST( fd_hfork_footprint( per_vtr_max, vtr_max ) <= SCRATCH_MAX ); - fd_hfork_t * hfork = fd_hfork_join( fd_hfork_new( scratch, per_vtr_max, vtr_max, 0 ) ); - FD_TEST( hfork ); - - fd_pubkey_t a = { .ul = { 1 } }; - fd_pubkey_t b = { .ul = { 2 } }; - fd_pubkey_t c = { .ul = { 3 } }; - - /* Initial update with {a, b}. */ - - fd_pubkey_t tv1[] = { a, b }; - fd_hfork_update_voters( hfork, tv1, 2UL ); - - FD_TEST( vtr_pool_used( hfork->vtr_pool )==2 ); - FD_TEST( vtr_map_ele_query( hfork->vtr_map, &a, NULL, hfork->vtr_pool ) ); - FD_TEST( vtr_map_ele_query( hfork->vtr_map, &b, NULL, hfork->vtr_pool ) ); - - /* Both voters can submit votes. */ - - fd_hash_t block_id = { .ul = { 100 } }; - fd_hash_t bank_hash = { .ul = { 200 } }; - - fd_hfork_count_vote( hfork, &a, &block_id, &bank_hash, 1000, 10, 100 ); - fd_hfork_count_vote( hfork, &b, &block_id, &bank_hash, 1000, 20, 100 ); - - bhm_t * bhm = bhm_query( hfork, &block_id, &bank_hash ); - FD_TEST( bhm ); - FD_TEST( bhm->stake==30 ); - - vtr_t * vtr_a = vtr_map_ele_query( hfork->vtr_map, &a, NULL, hfork->vtr_pool ); - FD_TEST( vtr_a->vte_cnt==1 ); - - /* Reindex with {b, c}: a removed (and its vote entries evicted), - c added. b's vote entries preserved. */ - - fd_pubkey_t tv2[] = { b, c }; - fd_hfork_update_voters( hfork, tv2, 2UL ); - - FD_TEST( vtr_pool_used( hfork->vtr_pool )==2 ); - FD_TEST( !vtr_map_ele_query( hfork->vtr_map, &a, NULL, hfork->vtr_pool ) ); - FD_TEST( vtr_map_ele_query( hfork->vtr_map, &b, NULL, hfork->vtr_pool ) ); - FD_TEST( vtr_map_ele_query( hfork->vtr_map, &c, NULL, hfork->vtr_pool ) ); - - /* a's vote was evicted: bhm stake decreased. */ - - bhm = bhm_query( hfork, &block_id, &bank_hash ); - FD_TEST( bhm ); - FD_TEST( bhm->stake==20 ); - - /* b's vote entry survived. */ - - vtr_t * vtr_b = vtr_map_ele_query( hfork->vtr_map, &b, NULL, hfork->vtr_pool ); - FD_TEST( vtr_b->vte_cnt==1 ); - - /* c starts with zero vote entries. */ - - vtr_t * vtr_c = vtr_map_ele_query( hfork->vtr_map, &c, NULL, hfork->vtr_pool ); - FD_TEST( vtr_c->vte_cnt==0 ); - - /* Same set is a no-op (votes preserved). */ - - fd_pubkey_t tv3[] = { b, c }; - fd_hfork_update_voters( hfork, tv3, 2UL ); - - FD_TEST( vtr_pool_used( hfork->vtr_pool )==2 ); - bhm = bhm_query( hfork, &block_id, &bank_hash ); - FD_TEST( bhm ); - FD_TEST( bhm->stake==20 ); - - /* record_our_bank_hash for the same block_id (blk already exists). */ - - fd_hfork_record_our_bank_hash( hfork, &block_id, &bank_hash, 100 ); - - blk_t * blk = blk_map_ele_query( hfork->blk_map, &block_id, NULL, hfork->blk_pool ); - FD_TEST( blk ); - FD_TEST( !memcmp( &blk->our_bank_hash, &bank_hash, 32 ) ); - - /* Empty set removes all voters and their vote entries. The blk - is also released because its last bhm was removed. */ - - fd_hfork_update_voters( hfork, NULL, 0UL ); - - FD_TEST( vtr_pool_used( hfork->vtr_pool )==0 ); - FD_TEST( !bhm_query( hfork, &block_id, &bank_hash ) ); - FD_TEST( !blk_map_ele_query( hfork->blk_map, &block_id, NULL, hfork->blk_pool ) ); - - fd_hfork_delete( fd_hfork_leave( hfork ) ); -} - -void -test_record_our_bank_hash( void ) { - ulong per_vtr_max = 4; - ulong vtr_max = 2; - ulong max = per_vtr_max * vtr_max; /* blk_pool capacity */ - - FD_TEST( fd_hfork_footprint( per_vtr_max, vtr_max ) <= SCRATCH_MAX ); - fd_hfork_t * hfork = fd_hfork_join( fd_hfork_new( scratch, per_vtr_max, vtr_max, 0 ) ); - FD_TEST( hfork ); - - /* record_our_bank_hash for a block with no votes — blk is in the - dlist and survives update_voters (it's not voter-owned). */ - - fd_hash_t block_id0 = { .ul = { 100 } }; - fd_hash_t bank_hash = { .ul = { 200 } }; - fd_hfork_record_our_bank_hash( hfork, &block_id0, &bank_hash, 100 ); - - FD_TEST( blk_map_ele_query( hfork->blk_map, &block_id0, NULL, hfork->blk_pool ) ); - FD_TEST( !blk_dlist_is_empty( hfork->blk_dlist, hfork->blk_pool ) ); - - fd_hfork_update_voters( hfork, NULL, 0UL ); - FD_TEST( blk_map_ele_query( hfork->blk_map, &block_id0, NULL, hfork->blk_pool ) ); - - /* Fill the pool with record_our_bank_hash calls. */ - - for( ulong i = 1; i < max; i++ ) { - fd_hash_t bid = { .ul = { 100+i } }; - fd_hfork_record_our_bank_hash( hfork, &bid, &bank_hash, 100 ); - FD_TEST( blk_map_ele_query( hfork->blk_map, &bid, NULL, hfork->blk_pool ) ); - } - FD_TEST( blk_pool_free( hfork->blk_pool )==0 ); - - /* One more record_our_bank_hash evicts the oldest (block_id0). */ - - fd_hash_t bid_new = { .ul = { 100+max } }; - fd_hfork_record_our_bank_hash( hfork, &bid_new, &bank_hash, 100 ); - - FD_TEST( !blk_map_ele_query( hfork->blk_map, &block_id0, NULL, hfork->blk_pool ) ); - FD_TEST( blk_map_ele_query( hfork->blk_map, &bid_new, NULL, hfork->blk_pool ) ); - - /* A vote removes the blk from the dlist. */ - - fd_pubkey_t a = { .ul = { 1 } }; - fd_pubkey_t tv[] = { a }; - fd_hfork_update_voters( hfork, tv, 1UL ); - - fd_hash_t bid_voted = { .ul = { 101 } }; - fd_hash_t bh_voted = { .ul = { 300 } }; - fd_hfork_count_vote( hfork, &a, &bid_voted, &bh_voted, 1000, 10, 100 ); - - blk_t * blk_voted = blk_map_ele_query( hfork->blk_map, &bid_voted, NULL, hfork->blk_pool ); - FD_TEST( blk_voted ); - FD_TEST( blk_voted->bhm_cnt==1 ); - - /* Removing voter evicts its bhm and blk. */ - - fd_hfork_update_voters( hfork, NULL, 0UL ); - FD_TEST( !blk_map_ele_query( hfork->blk_map, &bid_voted, NULL, hfork->blk_pool ) ); - - fd_hfork_delete( fd_hfork_leave( hfork ) ); - - /* record_our_bank_hash with bank_hash == NULL marks the block dead. - A vote with 52%+ stake triggers mismatch because block is dead. */ - - per_vtr_max = 4; - vtr_max = 2; - - FD_TEST( fd_hfork_footprint( per_vtr_max, vtr_max ) <= SCRATCH_MAX ); - hfork = fd_hfork_join( fd_hfork_new( scratch, per_vtr_max, vtr_max, 0 ) ); - FD_TEST( hfork ); - - fd_hash_t bid_dead = { .ul = { 900 } }; - fd_hfork_record_our_bank_hash( hfork, &bid_dead, NULL, 100 ); - - blk_t * blk_dead = blk_map_ele_query( hfork->blk_map, &bid_dead, NULL, hfork->blk_pool ); - FD_TEST( blk_dead ); - FD_TEST( (blk_dead->our_bank_hash.ul[0] & blk_dead->our_bank_hash.ul[1] & - blk_dead->our_bank_hash.ul[2] & blk_dead->our_bank_hash.ul[3])==~0UL ); - - fd_pubkey_t voter_dead = (fd_pubkey_t){ .ul = { 1 } }; - register_voters( hfork, &voter_dead, 1 ); - - fd_hash_t bh_dead = { .ul = { 999 } }; - FD_TEST( fd_hfork_count_vote( hfork, &voter_dead, &bid_dead, &bh_dead, 5000, 60, 100 )==FD_HFORK_ERR_MISMATCHED ); - - fd_hfork_delete( fd_hfork_leave( hfork ) ); - - /* Pool full with all blks having votes — dlist is empty, - eviction is not possible. */ - - per_vtr_max = 2; - vtr_max = 2; - - FD_TEST( fd_hfork_footprint( per_vtr_max, vtr_max ) <= SCRATCH_MAX ); - hfork = fd_hfork_join( fd_hfork_new( scratch, per_vtr_max, vtr_max, 0 ) ); - FD_TEST( hfork ); - - fd_pubkey_t voters_full[2] = { - (fd_pubkey_t){ .ul = { 1 } }, - (fd_pubkey_t){ .ul = { 2 } }, - }; - register_voters( hfork, voters_full, 2 ); - - for( ulong i = 0; i < per_vtr_max * vtr_max; i++ ) { - fd_hash_t bid = { .ul = { 1000+i } }; - fd_hash_t bh = { .ul = { 2000+i } }; - fd_hfork_count_vote( hfork, &voters_full[i % 2], &bid, &bh, 6000+i, 10, 100 ); - } - FD_TEST( blk_pool_free( hfork->blk_pool )==0 ); - FD_TEST( blk_dlist_is_empty( hfork->blk_dlist, hfork->blk_pool ) ); - - fd_hash_t bid_nope = { .ul = { 9999 } }; - fd_hash_t bh_nope = { .ul = { 8888 } }; - FD_TEST( fd_hfork_record_our_bank_hash( hfork, &bid_nope, &bh_nope, 100 )==0 ); - FD_TEST( !blk_map_ele_query( hfork->blk_map, &bid_nope, NULL, hfork->blk_pool ) ); - - fd_hfork_delete( fd_hfork_leave( hfork ) ); -} - -void -test_lru_eviction( void ) { - ulong per_vtr_max = 4; - ulong vtr_max = 2; - - FD_TEST( fd_hfork_footprint( per_vtr_max, vtr_max ) <= SCRATCH_MAX ); - fd_hfork_t * hfork = fd_hfork_join( fd_hfork_new( scratch, per_vtr_max, vtr_max, 0 ) ); - FD_TEST( hfork ); - - /* Create blks 0, 1, 2 via record_our_bank_hash. - dlist order: 0 (head), 1, 2 (tail). */ - - fd_hash_t bids[3]; - fd_hash_t bh = { .ul = { 200 } }; - for( ulong i = 0; i < 3; i++ ) { - bids[i] = (fd_hash_t){ .ul = { 100+i } }; - fd_hfork_record_our_bank_hash( hfork, &bids[i], &bh, 100 ); - } - - /* Vote on blk 0 — removes it from dlist. - dlist order: 1 (head), 2 (tail). */ - - fd_pubkey_t voter = { .ul = { 1 } }; - register_voters( hfork, &voter, 1 ); - - fd_hash_t bh_vote = { .ul = { 300 } }; - fd_hfork_count_vote( hfork, &voter, &bids[0], &bh_vote, 1000, 10, 100 ); - - /* Fill remaining pool slots. max = pow2_up(4*2) = 8, 3 used. */ - - for( ulong i = 3; i < 8; i++ ) { - fd_hash_t bid = { .ul = { 100+i } }; - fd_hfork_record_our_bank_hash( hfork, &bid, &bh, 100 ); - } - FD_TEST( blk_pool_free( hfork->blk_pool )==0 ); - - /* One more evicts blk 1 (head), NOT blk 0 (removed from dlist by vote). */ - - fd_hash_t bid_new = { .ul = { 999 } }; - fd_hfork_record_our_bank_hash( hfork, &bid_new, &bh, 100 ); - - FD_TEST( !blk_map_ele_query( hfork->blk_map, &bids[1], NULL, hfork->blk_pool ) ); - FD_TEST( blk_map_ele_query( hfork->blk_map, &bids[0], NULL, hfork->blk_pool ) ); - FD_TEST( blk_map_ele_query( hfork->blk_map, &bid_new, NULL, hfork->blk_pool ) ); - - fd_hfork_delete( fd_hfork_leave( hfork ) ); -} - -int -main( int argc, char ** argv ) { - fd_boot( &argc, &argv ); - - test_compact_indices(); - test_count_vote(); - test_record_our_bank_hash(); - test_update_voters(); - test_lru_eviction(); - - fd_halt(); - return 0; -} diff --git a/src/choreo/tower/Local.mk b/src/choreo/tower/Local.mk index 598d7a2dd05..e3ee87f7889 100644 --- a/src/choreo/tower/Local.mk +++ b/src/choreo/tower/Local.mk @@ -1,15 +1,7 @@ -$(call add-hdrs,fd_tower.h fd_tower_serdes.h fd_tower_stakes.h fd_tower_file.h) -$(call add-objs,fd_tower,fd_choreo) +$(call add-hdrs,fd_tower_serdes.h) $(call add-objs,fd_tower_serdes,fd_choreo) -$(call add-objs,fd_tower_stakes,fd_choreo) -$(call add-objs,fd_tower_file,fd_choreo) ifdef FD_HAS_HOSTED -$(call make-unit-test,test_tower,test_tower,fd_choreo fd_flamenco fd_tango fd_ballet fd_util) -$(call run-unit-test,test_tower) -$(call make-unit-test,test_tower_lockos,test_tower_lockos,fd_choreo fd_flamenco fd_tango fd_ballet fd_util) -$(call run-unit-test,test_tower_lockos) $(call make-unit-test,test_tower_serdes,test_tower_serdes,fd_choreo fd_flamenco fd_tango fd_ballet fd_util) $(call run-unit-test,test_tower_serdes) $(call make-fuzz-test,fuzz_tower_serdes,fuzz_tower_serdes,fd_choreo fd_flamenco fd_tango fd_ballet fd_util) -$(call make-fuzz-test,fuzz_tower_file,fuzz_tower_file,fd_choreo fd_flamenco fd_tango fd_ballet fd_util) endif diff --git a/src/choreo/tower/fd_tower.c b/src/choreo/tower/fd_tower.c deleted file mode 100644 index 1162110d241..00000000000 --- a/src/choreo/tower/fd_tower.c +++ /dev/null @@ -1,1765 +0,0 @@ -#include -#include - -#include "fd_tower.h" -#include "../../flamenco/txn/fd_txn_generate.h" -#include "../../flamenco/runtime/fd_system_ids.h" -#include "../../flamenco/runtime/program/vote/fd_vote_state_versioned.h" - -/* Pool and map_chain for fd_tower_blk_t. */ - -#define POOL_NAME blk_pool -#define POOL_T fd_tower_blk_t -#define POOL_IDX_T uint -#include "../../util/tmpl/fd_pool.c" - -#define MAP_NAME blk_map -#define MAP_ELE_T fd_tower_blk_t -#define MAP_KEY_T ulong -#define MAP_KEY slot -#define MAP_PREV prev -#define MAP_NEXT next -#define MAP_IDX_T uint -#define MAP_KEY_EQ(k0,k1) (*(k0)==*(k1)) -#define MAP_KEY_HASH(key,seed) ((*(key))^(seed)) -#define MAP_OPTIMIZE_RANDOM_ACCESS_REMOVAL 1 -#include "../../util/tmpl/fd_map_chain.c" - -/* lockout_interval tracks a map of lockout intervals. - - We need to track a list of lockout intervals per validator per slot. - Intervals are inclusive. Example: - - After executing slot 33, validator A votes for slot 32, has a tower - - vote | confirmation count | lockout interval - ----- | -------------------|------------------ - 32 | 1 | [32, 33] - 2 | 3 | [2, 6] - 1 | 4 | [1, 9] - - The lockout interval is the interval of slots that the validator is - locked out from voting for if they want to switch off that vote. For - example if validator A wants to switch off fork 1, they have to wait - until slot 9. - - Agave tracks a similar structure. - - key: for an interval [vote, vote+lockout] for validator A, - it is stored like: - vote+lockout -> (vote, validator A) -> (2, validator B) -> (any other vote, any other validator) - - Since a validator can have up to 31 entries in the tower, and we have - a max_vote_accounts, we can pool the interval objects to be - 31*max_vote_accounts entries PER bank / executed slot. A compact - per-bank list of unique interval ends lets switch checks and pruning - enumerate the corresponding map keys. Keys use the per-bank slot - header pool index instead of repeating the full fork slot. */ - -struct lockout_interval_key { - uint interval_end; - uint slot_conf_pubkey; -}; -typedef struct lockout_interval_key lockout_interval_key_t; - -struct lockout_interval { - lockout_interval_key_t key; - uint next; /* reserved for fd_map_chain and fd_pool */ - uint end_next; /* next unique-end interval for this fork slot */ -}; -typedef struct lockout_interval lockout_interval_t; - -FD_STATIC_ASSERT( sizeof(lockout_interval_key_t)==8UL, lockout_interval_key ); -FD_STATIC_ASSERT( sizeof(lockout_interval_t )==16UL, lockout_interval ); - -#define LOCKOUT_INTERVAL_SLOT_IDX_BITS (13U) -#define LOCKOUT_INTERVAL_CONF_BITS (5U) -#define LOCKOUT_INTERVAL_PUBKEY_IDX_BITS (14U) -#define LOCKOUT_INTERVAL_SLOT_IDX_MASK ((1U<slot_conf_pubkey & LOCKOUT_INTERVAL_SLOT_IDX_MASK; -} - -#define MAP_NAME lockout_interval_map -#define MAP_ELE_T lockout_interval_t -#define MAP_KEY_T lockout_interval_key_t -#define MAP_KEY_EQ(k0,k1) ((lockout_interval_key_slot_idx( (k0) )==lockout_interval_key_slot_idx( (k1) )) & ((k0)->interval_end==(k1)->interval_end)) -#define MAP_KEY_HASH(key,seed) (fd_ulong_hash( ((((ulong)lockout_interval_key_slot_idx( (key) ))<<32) | (ulong)(key)->interval_end) ^ (seed) )) -#define MAP_MULTI 1 -#define MAP_KEY key -#define MAP_NEXT next -#define MAP_IDX_T uint -#include "../../util/tmpl/fd_map_chain.c" - -#define POOL_NAME lockout_interval_pool -#define POOL_T lockout_interval_t -#define POOL_NEXT next -#define POOL_IDX_T uint -#define POOL_LAZY 1 -#include "../../util/tmpl/fd_pool.c" - -/* lockout_slot owns the list of unique interval ends for one fork slot. */ - -struct lockout_slot { - uint slot; /* fork slot */ - uint next; /* map/pool pointer for other lockout_slot_t */ - uint end_head; /* start of unique interval ends linked list */ -}; -typedef struct lockout_slot lockout_slot_t; - -FD_STATIC_ASSERT( sizeof(lockout_slot_t)==12UL, lockout_slot ); - -#define POOL_NAME lockout_slot_pool -#define POOL_T lockout_slot_t -#define POOL_NEXT next -#define POOL_IDX_T uint -#define POOL_LAZY 1 -#include "../../util/tmpl/fd_pool.c" - -#define MAP_NAME lockout_slot_map -#define MAP_ELE_T lockout_slot_t -#define MAP_KEY_T uint -#define MAP_KEY slot -#define MAP_KEY_EQ(k0,k1) (*(k0)==*(k1)) -#define MAP_KEY_HASH(key,seed) (fd_ulong_hash( (ulong)*(key) ^ (seed) )) -#define MAP_NEXT next -#define MAP_IDX_T uint -#include "../../util/tmpl/fd_map_chain.c" - -/* lockout_pubkey_ref dedups pubkeys across lockout intervals. We know - in the worst case there can be 2 * vtr_max pubkeys across all lockout - intervals across all live banks since there are vtr_max unique - pubkeys per epoch and a running validator can process up to 2 epochs - at a time. */ - -struct lockout_pubkey_ref { - fd_pubkey_t addr; - uint next; /* reserved for fd_map_chain and fd_pool */ - uint ref_cnt; /* number of normal lockout intervals referencing addr */ -}; -typedef struct lockout_pubkey_ref lockout_pubkey_ref_t; - -FD_STATIC_ASSERT( sizeof(lockout_pubkey_ref_t)==40UL, lockout_pubkey_ref ); - -#define POOL_NAME lockout_pubkey_pool -#define POOL_T lockout_pubkey_ref_t -#define POOL_NEXT next -#define POOL_IDX_T uint -#define POOL_LAZY 1 -#include "../../util/tmpl/fd_pool.c" - -#define MAP_NAME lockout_pubkey_map -#define MAP_ELE_T lockout_pubkey_ref_t -#define MAP_KEY_T fd_pubkey_t -#define MAP_KEY addr -#define MAP_KEY_EQ(k0,k1) (!memcmp( (k0), (k1), sizeof(fd_pubkey_t) )) -#define MAP_KEY_HASH(key,seed) (fd_ulong_hash( (key)->ul[0] ^ (seed) )) -#define MAP_NEXT next -#define MAP_IDX_T uint -#include "../../util/tmpl/fd_map_chain.c" - -FD_FN_CONST static inline lockout_interval_key_t -lockout_interval_key( ulong slot_idx, - ulong interval_end, - uint conf, - ulong pubkey_idx ) { - return (lockout_interval_key_t) { - .interval_end = (uint)interval_end, - .slot_conf_pubkey = (uint)slot_idx | - (conf << LOCKOUT_INTERVAL_CONF_SHIFT) | - ((uint)pubkey_idx << LOCKOUT_INTERVAL_PUBKEY_IDX_SHIFT), - }; -} - -FD_FN_PURE static inline uint -lockout_interval_key_conf( lockout_interval_key_t const * key ) { - return (key->slot_conf_pubkey >> LOCKOUT_INTERVAL_CONF_SHIFT) & LOCKOUT_INTERVAL_CONF_MASK; -} - -FD_FN_PURE static inline uint -lockout_interval_key_pubkey_idx( lockout_interval_key_t const * key ) { - return key->slot_conf_pubkey >> LOCKOUT_INTERVAL_PUBKEY_IDX_SHIFT; -} - -FD_FN_PURE static inline uint -lockout_interval_start( lockout_interval_t const * interval ) { - return interval->key.interval_end - (1U << lockout_interval_key_conf( &interval->key )); -} - -#define THRESHOLD_DEPTH (8) -#define THRESHOLD_RATIO (2.0 / 3.0) -#define SWITCH_RATIO (0.38) - -ulong -fd_tower_align( void ) { - return 128UL; -} - -static int -fd_tower_max_valid( ulong blk_max, - ulong vtr_max ) { - /* Lockout intervals use 13-bit slot-header and 14-bit pubkey pool indices. */ - if( FD_UNLIKELY( blk_max>(1UL<(1UL<<(LOCKOUT_INTERVAL_PUBKEY_IDX_BITS-1U)) ) ) return 0; - if( FD_UNLIKELY( blk_max && vtr_max>UINT_MAX/blk_max ) ) return 0; - - ulong pair_max = blk_max * vtr_max; - if( FD_UNLIKELY( pair_max>UINT_MAX/FD_TOWER_LOCKOS_MAX ) ) return 0; - return 1; -} - -ulong -fd_tower_footprint( ulong blk_max, - ulong vtr_max ) { - if( FD_UNLIKELY( !fd_tower_max_valid( blk_max, vtr_max ) ) ) return 0UL; - - ulong lck_interval_max = FD_TOWER_LOCKOS_MAX*blk_max*vtr_max; - ulong lck_map_chain_est = lockout_interval_map_chain_cnt_est( lck_interval_max ); - ulong lck_slot_chains = lockout_slot_map_chain_cnt_est( blk_max ); - ulong lck_pubkey_max = 2UL * vtr_max; - ulong lck_pubkey_chains = lockout_pubkey_map_chain_cnt_est( lck_pubkey_max ); - - ulong stk_vtr_chain_cnt = fd_tower_stakes_vtr_map_chain_cnt_est( vtr_max * blk_max ); - int stk_lg_slot_cnt = fd_ulong_find_msb( fd_ulong_pow2_up( blk_max ) ) + 1; - - ulong l = FD_LAYOUT_INIT; - l = FD_LAYOUT_APPEND( l, 128UL, sizeof(fd_tower_t) ); - l = FD_LAYOUT_APPEND( l, fd_tower_vote_align(), fd_tower_vote_footprint() ); - l = FD_LAYOUT_APPEND( l, blk_pool_align(), blk_pool_footprint ( blk_max ) ); - l = FD_LAYOUT_APPEND( l, blk_map_align(), blk_map_footprint ( blk_map_chain_cnt_est( blk_max ) ) ); - l = FD_LAYOUT_APPEND( l, fd_tower_vtr_align(), fd_tower_vtr_footprint ( vtr_max ) ); - for( ulong i = 0; i < vtr_max; i++ ) { - l = FD_LAYOUT_APPEND( l, fd_tower_vote_align(), fd_tower_vote_footprint() ); - } - /* lockos */ - l = FD_LAYOUT_APPEND( l, lockout_interval_pool_align(), lockout_interval_pool_footprint( lck_interval_max ) ); - l = FD_LAYOUT_APPEND( l, lockout_interval_map_align(), lockout_interval_map_footprint ( lck_map_chain_est ) ); - l = FD_LAYOUT_APPEND( l, lockout_slot_pool_align(), lockout_slot_pool_footprint ( blk_max ) ); - l = FD_LAYOUT_APPEND( l, lockout_slot_map_align(), lockout_slot_map_footprint ( lck_slot_chains ) ); - l = FD_LAYOUT_APPEND( l, lockout_pubkey_pool_align(), lockout_pubkey_pool_footprint ( lck_pubkey_max ) ); - l = FD_LAYOUT_APPEND( l, lockout_pubkey_map_align(), lockout_pubkey_map_footprint ( lck_pubkey_chains ) ); - /* stakes */ - l = FD_LAYOUT_APPEND( l, fd_tower_stakes_vtr_map_align(), fd_tower_stakes_vtr_map_footprint ( stk_vtr_chain_cnt ) ); - l = FD_LAYOUT_APPEND( l, fd_tower_stakes_vtr_pool_align(), fd_tower_stakes_vtr_pool_footprint( vtr_max * blk_max ) ); - l = FD_LAYOUT_APPEND( l, fd_tower_stakes_slot_align(), fd_tower_stakes_slot_footprint( stk_lg_slot_cnt ) ); - l = FD_LAYOUT_APPEND( l, fd_used_acc_scratch_align(), fd_used_acc_scratch_footprint( vtr_max * blk_max ) ); - return FD_LAYOUT_FINI( l, fd_tower_align() ); -} - -void * -fd_tower_new( void * shmem, - ulong blk_max, - ulong vtr_max, - ulong seed ) { - - if( FD_UNLIKELY( !shmem ) ) { - FD_LOG_WARNING(( "NULL mem" )); - return NULL; - } - - if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)shmem, fd_tower_align() ) ) ) { - FD_LOG_WARNING(( "misaligned mem" )); - return NULL; - } - - ulong footprint = fd_tower_footprint( blk_max, vtr_max ); - if( FD_UNLIKELY( !footprint ) ) { - FD_LOG_WARNING(( "bad blk_max (%lu) or vtr_max (%lu)", blk_max, vtr_max )); - return NULL; - } - - ulong lck_interval_max = FD_TOWER_LOCKOS_MAX*blk_max*vtr_max; - ulong lck_map_chain_est = lockout_interval_map_chain_cnt_est( lck_interval_max ); - ulong lck_slot_chains = lockout_slot_map_chain_cnt_est( blk_max ); - ulong lck_pubkey_max = 2UL * vtr_max; - ulong lck_pubkey_chains = lockout_pubkey_map_chain_cnt_est( lck_pubkey_max ); - - ulong stk_vtr_chain_cnt = fd_tower_stakes_vtr_map_chain_cnt_est( vtr_max * blk_max ); - int stk_lg_slot_cnt = fd_ulong_find_msb( fd_ulong_pow2_up( blk_max ) ) + 1; - - FD_SCRATCH_ALLOC_INIT( l, shmem ); - fd_tower_t * tower = FD_SCRATCH_ALLOC_APPEND( l, 128UL, sizeof(fd_tower_t) ); - void * votes = FD_SCRATCH_ALLOC_APPEND( l, fd_tower_vote_align(), fd_tower_vote_footprint() ); - void * blk_pool = FD_SCRATCH_ALLOC_APPEND( l, blk_pool_align(), blk_pool_footprint ( blk_max ) ); - void * blk_map = FD_SCRATCH_ALLOC_APPEND( l, blk_map_align(), blk_map_footprint ( blk_map_chain_cnt_est( blk_max ) ) ); - void * vtrs = FD_SCRATCH_ALLOC_APPEND( l, fd_tower_vtr_align(), fd_tower_vtr_footprint ( vtr_max ) ); - void * towers[ vtr_max ]; - for( ulong i = 0; i < vtr_max; i++ ) { - towers[i] = FD_SCRATCH_ALLOC_APPEND( l, fd_tower_vote_align(), fd_tower_vote_footprint() ); - } - void * lck_pool_mem = FD_SCRATCH_ALLOC_APPEND( l, lockout_interval_pool_align(), lockout_interval_pool_footprint( lck_interval_max ) ); - void * lck_map_mem = FD_SCRATCH_ALLOC_APPEND( l, lockout_interval_map_align(), lockout_interval_map_footprint ( lck_map_chain_est ) ); - void * lck_slot_pool = FD_SCRATCH_ALLOC_APPEND( l, lockout_slot_pool_align(), lockout_slot_pool_footprint ( blk_max ) ); - void * lck_slot_map = FD_SCRATCH_ALLOC_APPEND( l, lockout_slot_map_align(), lockout_slot_map_footprint ( lck_slot_chains ) ); - void * lck_pk_pool = FD_SCRATCH_ALLOC_APPEND( l, lockout_pubkey_pool_align(), lockout_pubkey_pool_footprint ( lck_pubkey_max ) ); - void * lck_pk_map = FD_SCRATCH_ALLOC_APPEND( l, lockout_pubkey_map_align(), lockout_pubkey_map_footprint ( lck_pubkey_chains ) ); - void * stk_vtr_map = FD_SCRATCH_ALLOC_APPEND( l, fd_tower_stakes_vtr_map_align(), fd_tower_stakes_vtr_map_footprint ( stk_vtr_chain_cnt ) ); - void * stk_vtr_pool = FD_SCRATCH_ALLOC_APPEND( l, fd_tower_stakes_vtr_pool_align(), fd_tower_stakes_vtr_pool_footprint( vtr_max * blk_max ) ); - void * stk_slot_map = FD_SCRATCH_ALLOC_APPEND( l, fd_tower_stakes_slot_align(), fd_tower_stakes_slot_footprint( stk_lg_slot_cnt ) ); - void * stk_used_acc = FD_SCRATCH_ALLOC_APPEND( l, fd_used_acc_scratch_align(), fd_used_acc_scratch_footprint( vtr_max * blk_max ) ); - FD_TEST( FD_SCRATCH_ALLOC_FINI( l, fd_tower_align() ) == (ulong)shmem + footprint ); - - tower->root = ULONG_MAX; - tower->blk_max = blk_max; - tower->vtr_max = vtr_max; - tower->votes = fd_tower_vote_new( votes ); - tower->blk_pool = blk_pool_new( blk_pool, blk_max ); - tower->blk_map = blk_map_new( blk_map, blk_map_chain_cnt_est( blk_max ), seed ); - tower->vtrs = fd_tower_vtr_new( vtrs, vtr_max ); - for( ulong i = 0; i < vtr_max; i++ ) { - fd_tower_vtr_join( tower->vtrs )[i].votes = fd_tower_vote_new( towers[i] ); - } - - tower->wait_to_vote_slot = 0UL; - - tower->lck_pool = lockout_interval_pool_new( lck_pool_mem, lck_interval_max ); - tower->lck_map = lockout_interval_map_new ( lck_map_mem, lck_map_chain_est, seed ); - tower->lck_slot_pool = lockout_slot_pool_new ( lck_slot_pool, blk_max ); - tower->lck_slot_map = lockout_slot_map_new ( lck_slot_map, lck_slot_chains, seed ); - tower->lck_pubkey_pool = lockout_pubkey_pool_new ( lck_pk_pool, lck_pubkey_max ); - tower->lck_pubkey_map = lockout_pubkey_map_new ( lck_pk_map, lck_pubkey_chains, seed ); - tower->stk_vtr_map = fd_tower_stakes_vtr_map_new ( stk_vtr_map, stk_vtr_chain_cnt, seed ); - tower->stk_vtr_pool = fd_tower_stakes_vtr_pool_new( stk_vtr_pool, vtr_max * blk_max ); - tower->stk_slot_map = fd_tower_stakes_slot_new ( stk_slot_map, stk_lg_slot_cnt, seed ); - tower->stk_used_acc = fd_used_acc_scratch_new ( stk_used_acc, vtr_max * blk_max ); - - return shmem; -} - -fd_tower_t * -fd_tower_join( void * shtower ) { - fd_tower_t * tower = (fd_tower_t *)shtower; - - if( FD_UNLIKELY( !tower ) ) { - FD_LOG_WARNING(( "NULL tower" )); - return NULL; - } - - if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)tower, fd_tower_align() ) ) ) { - FD_LOG_WARNING(( "misaligned tower" )); - return NULL; - } - - tower->votes = fd_tower_vote_join( tower->votes ); - tower->blk_pool = blk_pool_join ( tower->blk_pool ); - tower->blk_map = blk_map_join ( tower->blk_map ); - tower->vtrs = fd_tower_vtr_join ( tower->vtrs ); - for( ulong i = 0; i < tower->vtr_max; i++ ) { - tower->vtrs[i].votes = fd_tower_vote_join( tower->vtrs[i].votes ); - } - tower->lck_pool = lockout_interval_pool_join( tower->lck_pool ); - tower->lck_map = lockout_interval_map_join ( tower->lck_map ); - tower->lck_slot_pool = lockout_slot_pool_join ( tower->lck_slot_pool ); - tower->lck_slot_map = lockout_slot_map_join ( tower->lck_slot_map ); - tower->lck_pubkey_pool = lockout_pubkey_pool_join ( tower->lck_pubkey_pool ); - tower->lck_pubkey_map = lockout_pubkey_map_join ( tower->lck_pubkey_map ); - tower->stk_vtr_map = fd_tower_stakes_vtr_map_join ( tower->stk_vtr_map ); - tower->stk_vtr_pool = fd_tower_stakes_vtr_pool_join( tower->stk_vtr_pool ); - tower->stk_slot_map = fd_tower_stakes_slot_join ( tower->stk_slot_map ); - tower->stk_used_acc = fd_used_acc_scratch_join ( tower->stk_used_acc ); - - return tower; -} - -void * -fd_tower_leave( fd_tower_t const * tower ) { - - if( FD_UNLIKELY( !tower ) ) { - FD_LOG_WARNING(( "NULL tower" )); - return NULL; - } - - return (void *)tower; -} - -void * -fd_tower_delete( void * shtower ) { - - if( FD_UNLIKELY( !shtower ) ) { - FD_LOG_WARNING(( "NULL tower" )); - return NULL; - } - - if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)shtower, fd_tower_align() ) ) ) { - FD_LOG_WARNING(( "misaligned tower" )); - return NULL; - } - - return shtower; -} - -/* expiration calculates the expiration slot of vote given a slot and - confirmation count. */ - -static inline ulong -expiration_slot( fd_tower_vote_t const * vote ) { - ulong lockout = 1UL << vote->conf; - return vote->slot + lockout; -} - -/* simulate_vote simulates voting for slot, popping all votes from the - top that would be consecutively expired by voting for slot. */ - -static ulong -simulate_vote( fd_tower_vote_t const * votes, - ulong slot ) { - ulong cnt = fd_tower_vote_cnt( votes ); - while( cnt ) { - fd_tower_vote_t const * top_vote = fd_tower_vote_peek_index_const( votes, cnt - 1 ); - if( FD_LIKELY( expiration_slot( top_vote ) >= slot ) ) break; /* expire only if consecutive */ - cnt--; - } - return cnt; -} - -/* push_vote pushes a new vote for slot onto the tower. Pops and - returns the new root (bottom of the tower) if it reaches max lockout - as a result of the new vote. Otherwise, returns ULONG_MAX. - - Max lockout is equivalent to 1 << FD_TOWER_VOTE_MAX + 1 (which - implies confirmation count is FD_TOWER_VOTE_MAX + 1). As a result, - fd_tower_vote also maintains the invariant that the tower contains at - most FD_TOWER_VOTE_MAX votes, because (in addition to vote expiry) - there will always be a pop before reaching FD_TOWER_VOTE_MAX + 1. */ - -static ulong -push_vote( fd_tower_t * tower, - ulong slot ) { - - /* Sanity check: slot should always be greater than previous vote slot in tower. */ - - fd_tower_vote_t const * vote = fd_tower_vote_peek_tail_const( tower->votes ); - if( FD_UNLIKELY( vote && slot <= vote->slot ) ) FD_LOG_CRIT(( "[%s] slot %lu <= vote->slot %lu", __func__, slot, vote->slot )); - - /* Use simulate_vote to determine how many expired votes to pop. */ - - ulong cnt = simulate_vote( tower->votes, slot ); - - /* Pop everything that got expired. */ - - while( FD_LIKELY( fd_tower_vote_cnt( tower->votes ) > cnt ) ) { - fd_tower_vote_pop_tail( tower->votes ); - } - - /* If the tower is still full after expiring, then pop and return the - bottom vote slot as the new root because this vote has incremented - it to max lockout. Otherwise this is a no-op and there is no new - root (ULONG_MAX). */ - - ulong root = ULONG_MAX; - if( FD_LIKELY( fd_tower_vote_full( tower->votes ) ) ) { /* optimize for full tower */ - root = fd_tower_vote_pop_head( tower->votes ).slot; - } - - /* Increment confirmations (double lockouts) for consecutive - confirmations in prior votes. */ - - ulong prev_conf = 0; - for( fd_tower_vote_iter_t iter = fd_tower_vote_iter_init_rev( tower->votes ); - !fd_tower_vote_iter_done_rev( tower->votes, iter ); - iter = fd_tower_vote_iter_prev ( tower->votes, iter ) ) { - fd_tower_vote_t * vote = fd_tower_vote_iter_ele( tower->votes, iter ); - if( FD_UNLIKELY( vote->conf != ++prev_conf ) ) break; - vote->conf++; - } - - /* Add the new vote to the tower. */ - - fd_tower_vote_push_tail( tower->votes, (fd_tower_vote_t){ .slot = slot, .conf = 1 } ); - - /* Return the new root (FD_SLOT_NULL if there is none). */ - - return root; -} - -/* lockout_check checks if we are locked out from voting for slot. - Returns 1 if we can vote for slot without violating lockout, 0 - otherwise. - - After voting for a slot n, we are locked out for 2^k slots, where k - is the confirmation count of that vote. Once locked out, we cannot - vote for a different fork until that previously-voted fork expires at - slot n+2^k. This implies the earliest slot in which we can switch - from the previously-voted fork is (n+2^k)+1. We use `ghost` to - determine whether `slot` is on the same or different fork as previous - vote slots. - - In the case of the tower, every vote has its own expiration slot - depending on confirmations. The confirmation count is the max number - of consecutive votes that have been pushed on top of the vote, and - not necessarily its current height in the tower. - - For example, the following is a diagram of a tower pushing and - popping with each vote: - - - slot | confirmation count - -----|------------------- - 4 | 1 <- vote - 3 | 2 - 2 | 3 - 1 | 4 - - - slot | confirmation count - -----|------------------- - 9 | 1 <- vote - 2 | 3 - 1 | 4 - - - slot | confirmation count - -----|------------------- - 10 | 1 <- vote - 9 | 2 - 2 | 3 - 1 | 4 - - - slot | confirmation count - -----|------------------- - 11 | 1 <- vote - 10 | 2 - 9 | 3 - 2 | 4 - 1 | 5 - - - slot | confirmation count - -----|------------------- - 18 | 1 <- vote - 2 | 4 - 1 | 5 - - - In the final tower, note the gap in confirmation counts between slot - 18 and slot 2, even though slot 18 is directly above slot 2. */ - -static int -lockout_check( fd_tower_t * tower, - ulong slot ) { - - if( FD_UNLIKELY( slotwait_to_vote_slot ) ) return 0; - - /* Mirrors Agave's Tower::is_recent(): reject slot if it is not strictly - newer than our last vote (non-empty tower) or our root (empty tower, - e.g. snapshot boot). - https://github.com/anza-xyz/agave/blob/v4.0.0-alpha.0/core/src/consensus.rs#L825-L836 */ - if( FD_UNLIKELY( fd_tower_vote_empty( tower->votes ) ) ) - return tower->root==ULONG_MAX || slot>tower->root; - if( FD_UNLIKELY( slot<=fd_tower_vote_peek_tail_const( tower->votes )->slot ) ) return 0; - - /* Simulate a vote to pop off all the votes that would be expired by - voting for slot. Then check if the newly top-of-tower vote is on - the same fork as slot (if so this implies we can vote for it). */ - - ulong cnt = simulate_vote( tower->votes, slot ); /* pop off votes that would be expired */ - if( FD_UNLIKELY( !cnt ) ) return 1; /* tower is empty after popping expired votes */ - - fd_tower_vote_t const * vote = fd_tower_vote_peek_index_const( tower->votes, cnt - 1 ); /* newly top-of-tower */ - int lockout = fd_tower_blocks_is_slot_descendant( tower, vote->slot, slot ); /* check if on same fork */ - return lockout; -} - -/* switch_check checks if we can switch to the fork of `slot`. Returns - 1 if we can switch, 0 otherwise. Assumes tower is non-empty. - - There are two forks of interest: our last vote fork ("vote fork") and - the fork we want to switch to ("switch fork"). The switch fork is on - the fork of `slot`. - - In order to switch, SWITCH_RATIO of stake must have voted for - a slot that satisfies the following conditions: the - GCA(slot, last_vote) is an ancestor of the switch_slot - - Recall from the lockout check a validator is locked out from voting - for our last vote slot when their last vote slot is on a different - fork, and that vote's expiration slot > our last vote slot. - - The following pseudocode describes the algorithm: - - ``` - for every fork f in the fork tree, take the most recently executed - slot `s` (the leaf of the fork). - - Take the greatest common ancestor of the `s` and the our last vote - slot. If the switch_slot is a descendant of this GCA, then votes for - `s` can count towards the switch threshold. - - query banks(`s`) for vote accounts in `s` - for all vote accounts v in `s` - if v's locked out[1] from voting for our latest vote slot - add v's stake to switch stake - - return switch stake >= total_stake * SWITCH_RATIO - ``` - - The switch check is used to safeguard optimistic confirmation. - Specifically: optimistic confirmation pct + SWITCH_RATIO >= 1. */ - -static int -is_purged( fd_tower_t * tower, - fd_ghost_blk_t * blk ) { - fd_tower_blk_t * tower_blk = fd_tower_blocks_query( tower, blk->slot ); - return tower_blk->confirmed && memcmp( &tower_blk->confirmed_block_id, &blk->id, sizeof(fd_hash_t) ); -} - -static int -switch_check( fd_tower_t * tower, - fd_ghost_t * ghost, - ulong total_stake, - ulong switch_slot ) { - - lockout_interval_map_t * lck_map = tower->lck_map; - lockout_interval_t * lck_pool = tower->lck_pool; - lockout_slot_map_t const * lck_slot_map = tower->lck_slot_map; - lockout_slot_t const * lck_slot_pool = tower->lck_slot_pool; - - ulong switch_stake = 0; - ulong vote_slot = fd_tower_vote_peek_tail_const( tower->votes )->slot; - ulong root_slot = tower->root; - - ulong null = fd_ghost_blk_idx_null( ghost ); - fd_ghost_blk_t * head = fd_ghost_blk_map_remove( ghost, fd_ghost_root( ghost ) ); - fd_ghost_blk_t * tail = head; - head->next = null; - - while( FD_LIKELY( head ) ) { - fd_ghost_blk_t * blk = head; /* guaranteed to not be purged */ - - /* Because agave has particular behavior where if they replay a - equivocating version of a slot and then the correct version, the - original version and all of it's children get purged from all - structures. None of the nodes on this subtree can be considered - for the switch proof. Note that this means as we BFS, a node - can be considered a "valid leaf" if either it has no children, - or if all of it's children are purged/superseded slots. We - detect this by comparing against tower_blocks confirmed. */ - - int is_valid_leaf = 1; - fd_ghost_blk_t * child = fd_ghost_blk_child( ghost, head ); - while( FD_LIKELY( child ) ) { - if( FD_LIKELY( !is_purged( tower, child ) ) ) { - fd_ghost_blk_map_remove( ghost, child ); - tail->next = fd_ghost_blk_idx( ghost, child ); - tail = child; - tail->next = null; - is_valid_leaf = 0; - } - child = fd_ghost_blk_sibling( ghost, child ); - } - - head = fd_ghost_blk_next( ghost, blk ); /* pop queue head */ - fd_ghost_blk_map_insert( ghost, blk ); /* re-insert into map */ - - if( FD_UNLIKELY( !is_valid_leaf ) ) continue; /* not a real candidate */ - - ulong candidate_slot = blk->slot; - ulong lca = fd_tower_blocks_lowest_common_ancestor( tower, candidate_slot, vote_slot ); - if( FD_UNLIKELY( candidate_slot == vote_slot ) ) continue; - if( FD_UNLIKELY( lca==ULONG_MAX ) ) continue; /* unlikely but this leaf is an already pruned minority fork */ - - if( FD_UNLIKELY( fd_tower_blocks_is_slot_descendant( tower, lca, switch_slot ) ) ) { - - /* This candidate slot may be considered for the switch proof, if - it passes the following conditions: - - https://github.com/anza-xyz/agave/blob/c7b97bc77addacf03b229c51b47c18650d909576/core/src/consensus.rs#L1117 - - Now for this candidate slot, look at the lockouts that were - created at the time that we processed the bank for this - candidate slot. */ - - uint candidate_slot_key = (uint)candidate_slot; - lockout_slot_t const * lockout_slot = lockout_slot_map_ele_query_const( lck_slot_map, &candidate_slot_key, NULL, lck_slot_pool ); - if( FD_UNLIKELY( !lockout_slot ) ) continue; - ulong slot_idx = lockout_slot_pool_idx( lck_slot_pool, lockout_slot ); - - for( lockout_interval_t const * lockout = lockout_interval_pool_ele_const( lck_pool, lockout_slot->end_head ); - !!lockout; - lockout = lockout_interval_pool_ele_const( lck_pool, lockout->end_next ) ) { - uint interval_end = lockout->key.interval_end; - lockout_interval_key_t key = lockout_interval_key( slot_idx, interval_end, 0U, 0UL ); - - /* Intervals are keyed by the end of the interval. If the end of - the interval is < the last vote slot, then these vote - accounts with this particular lockout are NOT locked out from - voting for the last vote slot, which means we can skip this - set of intervals. */ - - if( FD_LIKELY( interval_end < vote_slot ) ) continue; - - /* At this point we can actually query for the intervals by - end interval to get the vote accounts. */ - - for( lockout_interval_t const * interval = lockout_interval_map_ele_query_const( lck_map, &key, NULL, lck_pool ); - interval; - interval = lockout_interval_map_ele_next_const( interval, NULL, lck_pool ) ) { - fd_hash_t const * vote_acc = &lockout_pubkey_pool_ele_const( tower->lck_pubkey_pool, lockout_interval_key_pubkey_idx( &interval->key ) )->addr; - - uint interval_start = lockout_interval_start( interval ); - if( FD_UNLIKELY( !fd_tower_blocks_is_slot_descendant( tower, interval_start, vote_slot ) && interval_start > root_slot ) ) { - fd_tower_stakes_vtr_xid_t key = { .addr = *vote_acc, .slot = switch_slot }; - fd_tower_stakes_vtr_t const * voter_stake = fd_tower_stakes_vtr_map_ele_query_const( tower->stk_vtr_map, &key, NULL, tower->stk_vtr_pool ); - - /* Vote account could have been closed on the switch fork, - and therefore not in the tower stakes map. In this case - just count the vote stake as 0 and skip this voter. - matches Agave. */ - if( FD_UNLIKELY( !voter_stake ) ) continue; - ulong voter_idx = fd_tower_stakes_vtr_pool_idx( tower->stk_vtr_pool, voter_stake ); - if( FD_UNLIKELY( fd_used_acc_scratch_test( tower->stk_used_acc, voter_idx ) ) ) continue; /* exclude already counted voters */ - fd_used_acc_scratch_insert( tower->stk_used_acc, voter_idx ); - switch_stake += voter_stake->stake; - if( FD_LIKELY( (double)switch_stake / (double)total_stake > SWITCH_RATIO ) ) { - fd_used_acc_scratch_null( tower->stk_used_acc ); - FD_LOG_DEBUG(( "[%s] vote_slot: %lu. switch_slot: %lu. pct: %.0lf%%", __func__, vote_slot, switch_slot, (double)switch_stake / (double)total_stake * 100.0 )); - while( FD_LIKELY( head ) ) { /* cleanup: re-insert remaining BFS queue into map */ - fd_ghost_blk_t * next = fd_ghost_blk_next( ghost, head ); - fd_ghost_blk_map_insert( ghost, head ); - head = next; - } - return 1; - } - } - } - } - } - } - fd_used_acc_scratch_null( tower->stk_used_acc ); - FD_LOG_DEBUG(( "[%s] vote_slot: %lu. switch_slot: %lu. pct: %.0lf%%", __func__, vote_slot, switch_slot, (double)switch_stake / (double)total_stake * 100.0 )); - return 0; -} - -/* threshold_check checks if we pass the threshold required to vote for - `slot`. Returns 1 if we pass the threshold check, 0 otherwise. - - The following pseudocode describes the algorithm: - - ``` - simulate that we have voted for `slot` - - for all vote accounts in the current epoch - - simulate that the vote account has voted for `slot` - - pop all votes expired by that simulated vote - - if the validator's latest tower vote after expiry >= our threshold - slot ie. our vote from THRESHOLD_DEPTH back also after simulating, - then add validator's stake to threshold_stake. - - return threshold_stake >= FD_TOWER_THRESHOLD_RATIO - ``` - - The threshold check simulates voting for the current slot to expire - stale votes. This is to prevent validators that haven't voted in a - long time from counting towards the threshold stake. */ - -static int -threshold_check( fd_tower_t const * tower, - fd_tower_vtr_t const * accts, - ulong total_stake, - ulong slot ) { - - /* First, simulate a vote on our tower, popping off everything that - would be expired by voting for slot. */ - - ulong cnt = simulate_vote( tower->votes, slot ); - - /* We can always vote if our tower is not at least THRESHOLD_DEPTH - deep after simulating. */ - - if( FD_UNLIKELY( cnt < THRESHOLD_DEPTH ) ) return 1; - - /* Get the vote slot from THRESHOLD_DEPTH back. Note THRESHOLD_DEPTH - is the 8th index back _including_ the simulated vote at index 0. */ - - ulong threshold_slot = fd_tower_vote_peek_index_const( tower->votes, cnt - THRESHOLD_DEPTH )->slot; - ulong threshold_stake = 0; - for( fd_tower_vtr_iter_t iter = fd_tower_vtr_iter_init( accts ); - !fd_tower_vtr_iter_done( accts, iter ); - iter = fd_tower_vtr_iter_next( accts, iter ) ) { - fd_tower_vtr_t const * acct = fd_tower_vtr_iter_ele_const( accts, iter ); - - ulong cnt = simulate_vote( acct->votes, slot ); /* expire votes */ - if( FD_UNLIKELY( !cnt ) ) continue; /* no votes left after expiry */ - - /* Count their stake towards the threshold check if their prev vote - slot >= our threshold slot. - - We know their prev vote slot is definitely on the same fork as - our threshold slot, because these towers are sourced from vote - _accounts_, not vote _transactions_ and the Vote Program - validates that all slots in the vote account's tower exist on the - current fork. - - Therefore, if their prev vote slot >= our threshold slot, we know - that vote must be for the threshold slot itself or one of - threshold slot's descendants. */ - - ulong vote_slot = fd_tower_vote_peek_index_const( acct->votes, cnt - 1 )->slot; - if( FD_LIKELY( vote_slot >= threshold_slot ) ) threshold_stake += acct->stake; - } - - double threshold_pct = (double)threshold_stake / (double)total_stake; - int threshold = threshold_pct > THRESHOLD_RATIO; - if( FD_UNLIKELY( !threshold ) ) FD_LOG_DEBUG(( "[%s] vote_slot: %lu. threshold_slot: %lu. pct: %.0lf%%.", __func__, fd_tower_vote_peek_tail_const( tower->votes )->slot, threshold_slot, threshold_pct * 100.0 )); - return threshold; -} - -static int -propagated_check( fd_tower_t * tower, - ulong slot ) { - - fd_tower_blk_t * blk = fd_tower_blocks_query( tower, slot ); - FD_TEST( blk ); - - if( FD_LIKELY( blk->leader ) ) return 1; /* can always vote for slot in which we're leader */ - if( FD_LIKELY( blk->prev_leader_slot==ULONG_MAX ) ) return 1; /* haven't been leader yet */ - - fd_tower_blk_t * prev_leader_blk = fd_tower_blocks_query( tower, blk->prev_leader_slot ); - if( FD_LIKELY( !prev_leader_blk ) ) return 1; /* already pruned / rooted */ - - return prev_leader_blk->propagated; -} - -uchar -fd_tower_vote_and_reset( fd_tower_t * tower, - fd_ghost_t * ghost, - fd_votes_t * votes FD_PARAM_UNUSED, - ulong * reset_slot, - fd_hash_t * reset_block_id, - ulong * reset_bank_seq, - ulong * vote_slot, - fd_hash_t * vote_block_id, - fd_hash_t * vote_bank_hash, - ulong * root_slot, - fd_hash_t * root_block_id ) { - - uchar flags = 0; - fd_ghost_blk_t const * best_blk = fd_ghost_best( ghost, fd_ghost_root( ghost ) ); - fd_ghost_blk_t const * reset_blk = NULL; - fd_ghost_blk_t const * vote_blk = NULL; - - /* Case 0: if we haven't voted yet then there are two subcases where - we short-circuit. */ - - /* Case 0a: on boot, tower->root is set to the snapshot slot before - any votes are recorded. In this case, lockout_check returns 0 for - slot <= root, preventing a vote on the snapshot slot itself. */ - - /* TODO refactor: 0a is a tile-concern not logic-concern */ - - if( FD_UNLIKELY( fd_tower_vote_empty( tower->votes ) && !lockout_check( tower, best_blk->slot ) ) ) { - FD_BASE58_ENCODE_32_BYTES( best_blk->id.uc, best_blk_id ); - FD_LOG_DEBUG(( "[%s] case 0a: not recent (slot %lu <= root %lu). reset_blk: (%lu, %s). vote_blk: (NULL)", __func__, best_blk->slot, tower->root, best_blk->slot, best_blk_id )); - *reset_slot = best_blk->slot; - *reset_block_id = best_blk->id; - *reset_bank_seq = best_blk->bank_seq; - *vote_slot = ULONG_MAX; - *vote_block_id = (fd_hash_t){0}; - *root_slot = ULONG_MAX; - *root_block_id = (fd_hash_t){0}; - return flags; - } - - /* Case 0b: if we haven't voted yet then we can always vote and reset - to ghost_best. */ - - if( FD_UNLIKELY( fd_tower_vote_empty( tower->votes ) ) ) { - FD_BASE58_ENCODE_32_BYTES( best_blk->id.uc, best_blk_id ); - FD_LOG_DEBUG(( "[%s] case 0b: empty tower. reset_blk: (%lu, %s). vote_blk: (%lu, %s)", __func__, best_blk->slot, best_blk_id, best_blk->slot, best_blk_id )); - fd_tower_blk_t * tower_blk = fd_tower_blocks_query( tower, best_blk->slot ); - tower_blk->voted = 1; - tower_blk->voted_block_id = best_blk->id; - *reset_slot = best_blk->slot; - *reset_block_id = best_blk->id; - *reset_bank_seq = best_blk->bank_seq; - *vote_slot = best_blk->slot; - *vote_block_id = best_blk->id; - *vote_bank_hash = tower_blk->bank_hash; - *root_slot = push_vote( tower, best_blk->slot ); - *root_block_id = ( fd_hash_t ){ 0 }; - return flags; - } - - ulong prev_vote_slot = fd_tower_vote_peek_tail_const( tower->votes )->slot; - fd_tower_blk_t * prev_vote_fork = fd_tower_blocks_query( tower, prev_vote_slot ); /* must exist */ - - fd_hash_t * prev_vote_block_id = &prev_vote_fork->voted_block_id; - fd_ghost_blk_t * prev_vote_blk = fd_ghost_query( ghost, prev_vote_block_id ); - - /* Case 1: if any ancestor of our prev vote (including prev vote - itself) is an unconfirmed duplicate, then our prev vote was on a - duplicate fork. - - There are three subcases to check. */ - - int invalid_ancestor = !!fd_ghost_invalid_ancestor( ghost, prev_vote_blk ); - - /* Case 1a: ghost_best is an ancestor of prev vote. This means - ghost_best is rolling back to an ancestor that precedes the - duplicate ancestor on the same fork as our prev vote. In this - case, we can't vote on our ancestor, but we do reset to that - ancestor. - - https://github.com/anza-xyz/agave/blob/v2.3.7/core/src/consensus.rs#L1016-L1019 */ - - int ancestor_rollback = prev_vote_blk != best_blk && !!fd_ghost_ancestor( ghost, prev_vote_blk, &best_blk->id ); - - /* Case 1b: ghost_best is not an ancestor, but prev_vote is a - duplicate and we've confirmed its duplicate sibling. In this - case, we allow switching to ghost_best without a switch proof. - - Example: slot 5 is a duplicate. We first receive, replay and - vote for block 5, so that is our prev vote. We later receive - block 5' and observe that it is duplicate confirmed. ghost_best - now returns block 5' and we both vote and reset to block 5' - regardless of the switch check. - - https://github.com/anza-xyz/agave/blob/v2.3.7/core/src/consensus.rs#L1021-L1024 */ - - int sibling_confirmed = prev_vote_fork->confirmed && 0!=memcmp( &prev_vote_fork->voted_block_id, &prev_vote_fork->confirmed_block_id, sizeof(fd_hash_t) ); - - if( FD_UNLIKELY( invalid_ancestor && ancestor_rollback ) ) { - flags = fd_uchar_set_bit( flags, FD_TOWER_FLAG_ANCESTOR_ROLLBACK ); - reset_blk = best_blk; - FD_BASE58_ENCODE_32_BYTES( reset_blk->id.uc, reset_blk_id ); - FD_LOG_DEBUG(( "[%s] case 1a: ancestor rollback. prev_vote_slot: %lu. reset_blk: (%lu, %s). vote_blk: (NULL)", __func__, prev_vote_slot, reset_blk->slot, reset_blk_id )); - - } else if( FD_UNLIKELY( invalid_ancestor && sibling_confirmed ) ) { - flags = fd_uchar_set_bit( flags, FD_TOWER_FLAG_SIBLING_CONFIRMED ); - reset_blk = best_blk; - vote_blk = best_blk; - FD_BASE58_ENCODE_32_BYTES( reset_blk->id.uc, reset_blk_id ); - FD_BASE58_ENCODE_32_BYTES( vote_blk->id.uc, vote_blk_id ); - FD_LOG_DEBUG(( "[%s] case 1b: sibling confirmed. prev_vote_slot: %lu. reset_blk: (%lu, %s). vote_blk: (%lu, %s)", __func__, prev_vote_slot, reset_blk->slot, reset_blk_id, vote_blk->slot, vote_blk_id )); - } - - /* Case 2: if our prev vote slot is an ancestor of the best slot, then - they are on the same fork and we can both reset to it. We can also - vote for it if we pass the can_vote checks. - - https://github.com/anza-xyz/agave/blob/v2.3.7/core/src/consensus.rs#L1057 */ - - else if( FD_LIKELY( best_blk->slot == prev_vote_slot || fd_tower_blocks_is_slot_ancestor( tower, best_blk->slot, prev_vote_slot ) ) ) { - flags = fd_uchar_set_bit( flags, FD_TOWER_FLAG_SAME_FORK ); - reset_blk = best_blk; - vote_blk = best_blk; - FD_BASE58_ENCODE_32_BYTES( reset_blk->id.uc, reset_blk_id ); - FD_BASE58_ENCODE_32_BYTES( vote_blk->id.uc, vote_blk_id ); - FD_LOG_DEBUG(( "[%s] case 2: same fork. prev_vote_slot: %lu. reset_blk: (%lu, %s). vote_blk: (%lu, %s)", __func__, prev_vote_slot, reset_blk->slot, reset_blk_id, vote_blk->slot, vote_blk_id )); - } - - /* Case 3: if our prev vote is not an ancestor of the best block, then - it is on a different fork. If we pass the switch check, we can - reset to it. If we additionally pass the lockout check, we can - also vote for it. - - https://github.com/anza-xyz/agave/blob/v2.3.7/core/src/consensus.rs#L1208-L1215 - - Note also Agave uses the best blk's total stake for checking the - threshold. - - https://github.com/anza-xyz/agave/blob/v2.3.7/core/src/consensus/fork_choice.rs#L443-L445 */ - - else if( FD_LIKELY( switch_check( tower, ghost, best_blk->total_stake, best_blk->slot ) ) ) { - flags = fd_uchar_set_bit( flags, FD_TOWER_FLAG_SWITCH_PASS ); - reset_blk = best_blk; - vote_blk = best_blk; - FD_BASE58_ENCODE_32_BYTES( reset_blk->id.uc, reset_blk_id ); - FD_BASE58_ENCODE_32_BYTES( vote_blk->id.uc, vote_blk_id ); - FD_LOG_DEBUG(( "[%s] case 3: switch pass. prev_vote_slot: %lu. reset_blk: (%lu, %s). vote_blk: (%lu, %s)", __func__, prev_vote_slot, reset_blk->slot, reset_blk_id, vote_blk->slot, vote_blk_id )); - } - - /* Case 4: same as case 3 but we didn't pass the switch check. In - this case we reset to either ghost_best or ghost_deepest beginning - from our prev vote blk. - - We must reset to a block beginning from our prev vote fork to - ensure votes get a chance to propagate. Because in order for votes - to land, someone needs to build a block on that fork. - - We reset to ghost_best or ghost_deepest depending on whether our - prev vote is valid. When it's invalid we use ghost_deepest instead - of ghost_best, because ghost_best won't be able to return a valid - block beginning from our prev_vote because by definition the entire - subtree will be invalid. - - When our prev vote fork is not a duplicate, we want to propagate - votes that might allow others to switch to our fork. In addition, - if our prev vote fork is a duplicate, we want to propagate votes - that might "duplicate confirm" that block (reach 52% of stake). - - See top-level documentation in fd_tower.h for more details on vote - propagation. */ - - else { - - /* Case 4a: failed switch check and last vote slot has an invalid - ancestor. - - https://github.com/anza-xyz/agave/blob/v2.3.7/core/src/consensus/heaviest_subtree_fork_choice.rs#L1187 */ - - if( FD_UNLIKELY( invalid_ancestor ) ) { - flags = fd_uchar_set_bit( flags, FD_TOWER_FLAG_SWITCH_FAIL ); - reset_blk = fd_ghost_deepest( ghost, prev_vote_blk ); - FD_BASE58_ENCODE_32_BYTES( reset_blk->id.uc, reset_blk_id ); - FD_LOG_DEBUG(( "[%s] case 4a: switch fail, invalid ancestor. prev_vote_slot: %lu. reset_blk: (%lu, %s). vote_blk: (NULL)", __func__, prev_vote_slot, reset_blk->slot, reset_blk_id )); - } - - /* Case 4b: failed switch check (no invalid ancestor). - - https://github.com/anza-xyz/agave/blob/v2.3.7/core/src/consensus/fork_choice.rs#L200 */ - - else { - flags = fd_uchar_set_bit( flags, FD_TOWER_FLAG_SWITCH_FAIL ); - reset_blk = fd_ghost_best( ghost, prev_vote_blk ); - FD_BASE58_ENCODE_32_BYTES( reset_blk->id.uc, reset_blk_id ); - FD_LOG_DEBUG(( "[%s] case 4b: switch fail, no invalid ancestor. prev_vote_slot: %lu. reset_blk: (%lu, %s). vote_blk: (NULL)", __func__, prev_vote_slot, reset_blk->slot, reset_blk_id )); - } - } - - /* If there is a block to vote for, there are a few additional checks - to make sure we can actually vote for it. - - Specifically, we need to make sure we're not locked out, pass the - threshold check and that our previous leader block has propagated - (reached the prop threshold according to fd_votes). - - https://github.com/firedancer-io/agave/blob/master/core/src/consensus/fork_choice.rs#L382-L385 - - Agave uses the total stake on the fork being threshold checked - (vote_blk) for determining whether it meets the stake threshold. */ - - if( FD_LIKELY( vote_blk ) ) { - if ( FD_UNLIKELY( !lockout_check( tower, vote_blk->slot ) ) ) { - FD_BASE58_ENCODE_32_BYTES( vote_blk->id.uc, vote_blk_id ); - FD_LOG_DEBUG(( "[%s] lockout check failed. prev_vote_slot: %lu. vote_blk: (%lu, %s)", __func__, prev_vote_slot, vote_blk->slot, vote_blk_id )); - flags = fd_uchar_set_bit( flags, FD_TOWER_FLAG_LOCKOUT_FAIL ); - vote_blk = NULL; - } - else if( FD_UNLIKELY( !threshold_check( tower, tower->vtrs, vote_blk->total_stake, vote_blk->slot ) ) ) { - FD_BASE58_ENCODE_32_BYTES( vote_blk->id.uc, vote_blk_id ); - FD_LOG_DEBUG(( "[%s] threshold check failed. prev_vote_slot: %lu. vote_blk: (%lu, %s)", __func__, prev_vote_slot, vote_blk->slot, vote_blk_id )); - flags = fd_uchar_set_bit( flags, FD_TOWER_FLAG_THRESHOLD_FAIL ); - vote_blk = NULL; - } - else if( FD_UNLIKELY( !propagated_check( tower, vote_blk->slot ) ) ) { - FD_BASE58_ENCODE_32_BYTES( vote_blk->id.uc, vote_blk_id ); - FD_LOG_DEBUG(( "[%s] propagated check failed. prev_vote_slot: %lu. vote_blk: (%lu, %s)", __func__, prev_vote_slot, vote_blk->slot, vote_blk_id )); - flags = fd_uchar_set_bit( flags, FD_TOWER_FLAG_PROPAGATED_FAIL ); - vote_blk = NULL; - } - } - - FD_TEST( reset_blk ); /* always a reset_blk */ - *reset_slot = reset_blk->slot; - *reset_block_id = reset_blk->id; - *reset_bank_seq = reset_blk->bank_seq; - *vote_slot = ULONG_MAX; - *vote_block_id = (fd_hash_t){0}; - *vote_bank_hash = (fd_hash_t){0}; - *root_slot = ULONG_MAX; - *root_block_id = (fd_hash_t){0}; - - /* Finally, if our vote passed all the checks, we actually push the - vote onto the tower. */ - - if( FD_LIKELY( vote_blk ) ) { - *vote_slot = vote_blk->slot; - *vote_block_id = vote_blk->id; - *root_slot = push_vote( tower, vote_blk->slot ); - - /* Query our tower fork for this slot we're voting for. Note this - can never be NULL because we record tower forks as we replay, and - we should never be voting on something we haven't replayed. */ - - fd_tower_blk_t * fork = fd_tower_blocks_query( tower, vote_blk->slot ); - fork->voted = 1; - fork->voted_block_id = vote_blk->id; - *vote_bank_hash = fork->bank_hash; - - /* Query the root slot's block id from tower forks. This block id - may not necessarily be confirmed, because confirmation requires - votes on the block itself (vs. block and its descendants). - - So if we have a confirmed block id, we return that. Otherwise - we return our own vote block id for that slot, which we assume - is the cluster converged on by the time we're rooting it. - - The only way it is possible for us to root the wrong version of - a block (ie. not the one the cluster confirmed) is if there is - mass equivocation (>2/3 of threshold check stake has voted for - two versions of a block). This exceeds the equivocation safety - threshold and we would eventually detect this via a bank hash - mismatch and error out. */ - - if( FD_LIKELY( *root_slot!=ULONG_MAX ) ) { - fd_tower_blk_t * root_fork = fd_tower_blocks_query( tower, *root_slot ); - *root_block_id = *fd_ptr_if( root_fork->confirmed, &root_fork->confirmed_block_id, &root_fork->voted_block_id ); - } - } - - FD_BASE58_ENCODE_32_BYTES( reset_block_id->uc, reset_block_id_b58 ); - FD_BASE58_ENCODE_32_BYTES( vote_block_id->uc, vote_block_id_b58 ); - FD_BASE58_ENCODE_32_BYTES( root_block_id->uc, root_block_id_b58 ); - FD_LOG_DEBUG(( "[%s] flags: %d. reset_slot: %lu (%s). vote_slot: %lu (%s). root_slot: %lu (%s).", __func__, flags, *reset_slot, reset_block_id_b58, *vote_slot, vote_block_id_b58, *root_slot, root_block_id_b58 )); - return flags; -} - -/* fd_tower_reconcile reconciles our local tower with our on-chain tower - (stored inside our vote account). This function is important in two - contexts: - - ON BOOT - - When Firedancer boots up its local tower contains no votes, only a - root slot set to the snapshot slot. It needs to restore its "latest" - tower votes and root as of its previous run. This information is - stored on-chain itself, in a vote account, and Firedancer updates - vote account states during catchup by replaying blocks since the - snapshot. Firedancer reconciles its local tower with the on-chain - one every time it replays a block, and will by definition have its - "latest" tower once it has caught up. - - Note that it is possible Firedancer had voted for a minority fork in - the previous run. In this case, its true "latest" tower contains - votes for slots that were pruned by the time of this boot. In theory - TowerBFT stipulates that lockout can be up to 2^32 slots, but in - practice slots are pruned once they fall out of the slot hash history - limit, because they can no longer be canonically verified on-chain. - Therefore, Firedancer can safely ignore slots that are pruned and - restore its latest tower on the majority fork as of boot time. - - HIGH-AVAILABILITY SETUP - - A typical validator setup involves two nodes, a primary and a backup. - The primary is a valid fee payer, and the one landing votes recording - the latest state of its tower on-chain. The two nodes' towers will - usually be identical but occasionally diverge when one node votes - for slots that the other one doesn't. This usually happens when - there are multiple forks. - - This becomes a problem, because the primary's tower may contain votes - the backup doesn't have and/or vice versa. The primary's tower is - the canonical one, since it's the one recorded on-chain, so reconcile - is a no-op on the primary. - - On the backup, reconcile is more involved. Because what's on-chain - is the primary's tower, there may be slots the backup never actually - voted for. When the backup node reads back the on-chain tower, some - metadata, namely `voted` and `voted_block_id`, will be missing from - its fd_tower instance. - - fd_tower_reconcile assumes that if a tower has been recorded on-chain - then it is safe to assume the vote account registered with the - currently running Firedancer has in fact at some point voted for the - slots in that tower. - - In case the instance is the backup, it updates the local tower votes, - root, and metadata structures accordingly with this assumption namely - by inserting voted_block_id for votes that the backup didn't actually - vote for but can safely assume the primary did. - - This affects the Tower voting rules (see fd_tower_vote_and_reset) in - that the voted_block_id is used for certain vote and reset decisions. - - There are some corner cases to consider related to equivocation: - - 2 - / \ - 3 3' (confirmed) - - Assume 3 and 3' are alternate blocks for the same slot (3) and have - different block ids. 3' is the block that eventually gets confirmed. - Let's consider a scenario in which the primary votes for "3" and the - backup misses the vote for "3". fd_tower_reconcile needs to backfill - the voted_block_id for "3" on the backup. However, it's unclear - whether that vote is for 3 (unconfirmed) or 3' (confirmed), because - all the on-chain tower contains is the slot "3" (with no block_id). - How does the backup figure out the voted_block_id? - - It turns out it doesn't really matter either way, the backup can just - backfill with whichever block_id it happened to replay (we know the - backup has to have replayed either 3 or 3' in order to observe an - on-chain tower containing 3 in the first place): - - If the primary voted for 3 and the backup backfills with 3', we know - the primary will eventually switch to the DC block (3') via repair. - So backfilling with 3' is ok because the primary will converge to it. - - If the primary voted for 3' and the backup backfills with 3, then the - backup will similarly eventually switch to the DC block via repair. - Indeed, it will "freebie" switch in fd_tower_vote_and_reset ie. case - 1b: "sibling confirmed". Thus, the backup will converge to 3'. */ - -void -fd_tower_reconcile( fd_tower_t * tower, - fd_tower_vote_t * onchain_votes, - ulong onchain_root ) { - - fd_tower_vote_t * local_votes = tower->votes; - ulong local_root = tower->root; - - ulong local_vote = fd_tower_vote_empty( local_votes ) ? ULONG_MAX : fd_tower_vote_peek_tail_const( local_votes )->slot; - ulong onchain_vote = fd_tower_vote_empty( onchain_votes ) ? ULONG_MAX : fd_tower_vote_peek_tail_const( onchain_votes )->slot; - - /* Cases: - - Agave checks Option <= Option. Breakdown of Ord>: - - None, None => True - None, Some => True - Some, None => False - Some, Some => onchain_vote <= local_vote */ - - if( FD_LIKELY( onchain_vote==ULONG_MAX || /* None, None or None, Some */ - ( local_vote !=ULONG_MAX && onchain_vote<=local_vote ) /* Some, Some */ ) ) return; - - /* On-chain tower is newer, so sync our local tower to the on-chain tower. */ - - FD_LOG_NOTICE(( "[%s] overwriting local tower (last: %lu, root: %lu) with onchain tower (last: %lu, root: %lu)", __func__, local_vote, local_root, onchain_vote, onchain_root )); - - FD_TEST( local_root!=ULONG_MAX ); /* local root should always be set before fd_tower_reconcile */ - if( FD_LIKELY( onchain_root==ULONG_MAX || local_root > onchain_root ) ) { - - /* Local root is larger than on-chain root. Overwrite on-chain root - with local root (this is just a copy, not writing to accdb). */ - - FD_LOG_DEBUG(( "[%s] local_root %lu > onchain_root %lu", __func__, local_root, onchain_root )); - onchain_root = local_root; - - /* Drop on-chain votes <= local root. */ - - while( FD_LIKELY( !fd_tower_vote_empty( onchain_votes ) ) ) { - fd_tower_vote_t const * vote = fd_tower_vote_peek_head_const( onchain_votes ); - if( FD_LIKELY( vote->slot > local_root ) ) break; - FD_LOG_DEBUG(( "[%s] dropping on-chain vote for slot %lu since it's <= local root %lu", __func__, vote->slot, local_root )); - fd_tower_vote_pop_head( onchain_votes ); - } - - /* TODO add sanity-check that onchain_root is an ancestor of the - first vote's ancestor at this point. */ - } - - for( fd_tower_vote_iter_t iter = fd_tower_vote_iter_init( tower->votes ); - !fd_tower_vote_iter_done( tower->votes, iter ); - iter = fd_tower_vote_iter_next( tower->votes, iter ) ) { - fd_tower_vote_t const * vote = fd_tower_vote_iter_ele_const( tower->votes, iter ); - fd_tower_blk_t * tower_blk = fd_tower_blocks_query( tower, vote->slot ); - FD_TEST( tower_blk ); /* must exist if it's in our tower */ - tower_blk->voted = 0; - } - - /* Overwrite the root. No-op if local_root > onchain_root. The slots - below it stay until the caller publishes the root to ghost. */ - - tower->root = onchain_root; - - /* Clear out all local_votes. */ - - fd_tower_vote_remove_all( tower->votes ); - - /* Replace them with onchain_votes. */ - - for( fd_tower_vote_iter_t iter = fd_tower_vote_iter_init( onchain_votes ); - !fd_tower_vote_iter_done( onchain_votes, iter ); - iter = fd_tower_vote_iter_next( onchain_votes, iter ) ) { - fd_tower_vote_t const * vote = fd_tower_vote_iter_ele_const( onchain_votes, iter ); - fd_tower_vote_push_tail( tower->votes, *vote ); - - /* Additionally, backfill voted_block_id for the slots we didn't - actually vote for. This is intentionally always using the latest - replayed_block_id if we overwrote it with a second replay. */ - - fd_tower_blk_t * tower_blk = fd_tower_blocks_query( tower, vote->slot ); - FD_TEST( tower_blk ); /* must exist because - 1. all on-chain votes > root slot - 2. all on-chain votes <= replay slot */ - if( FD_UNLIKELY( !tower_blk->voted ) ) { - tower_blk->voted = 1; - tower_blk->voted_block_id = tower_blk->replayed_block_id; - } - } -} - -void -fd_tower_from_vote_acc( fd_tower_vote_t * votes, - ulong * root, - uchar const * data, - ulong data_sz ) { - fd_vote_acc_desc_t desc[1]; - if( FD_UNLIKELY( !fd_vote_acc_desc( desc, data, data_sz ) ) ) { - *root = ULONG_MAX; - return; - } - *root = desc->root_slot; - for( ulong i=0UL; ivote_cnt; i++ ) { - fd_tower_vote_t vote = {0}; - switch( desc->kind ) { - case FD_VOTE_ACC_V2: { - fd_vote_acc_vote_v2_t const * v = fd_vote_acc_desc_vote( desc, data, i ); - vote.slot = v->slot; - vote.conf = v->conf; - break; - } - case FD_VOTE_ACC_V3: - case FD_VOTE_ACC_V4: { - fd_vote_acc_vote_t const * v = fd_vote_acc_desc_vote( desc, data, i ); - vote.slot = v->slot; - vote.conf = v->conf; - break; - } - } - fd_tower_vote_push_tail( votes, vote ); - } -} - -ulong -fd_tower_with_lat_from_vote_acc( fd_vote_acc_vote_t tower[ static FD_TOWER_VOTE_MAX ], - uchar const * data, - ulong data_sz ) { - fd_vote_acc_desc_t desc[1]; - if( FD_UNLIKELY( !fd_vote_acc_desc( desc, data, data_sz ) ) ) return 0UL; - FD_DCHECK_CRIT( desc->vote_cnt <= FD_TOWER_VOTE_MAX, "invalid vote account" ); - switch( desc->kind ) { - case FD_VOTE_ACC_V2: { - for( ulong i=0UL; ivote_cnt; i++ ) { - fd_vote_acc_vote_v2_t const * v = fd_vote_acc_desc_vote( desc, data, i ); - tower[ i ] = (fd_vote_acc_vote_t){ - .slot = v->slot, - .conf = v->conf, - .latency = UCHAR_MAX - }; - } - break; - } - case FD_VOTE_ACC_V3: - case FD_VOTE_ACC_V4: - fd_memcpy( tower, fd_vote_acc_desc_vote( desc, data, 0UL ), desc->vote_cnt * sizeof(fd_vote_acc_vote_t) ); - break; - } - return desc->vote_cnt; -} - -void -fd_tower_to_vote_txn( fd_tower_t const * tower, - fd_hash_t const * bank_hash, - fd_hash_t const * block_id, - fd_hash_t const * recent_blockhash, - fd_pubkey_t const * validator_identity, - fd_pubkey_t const * vote_authority, - fd_pubkey_t const * vote_acc, - fd_txn_p_t * vote_txn ) { - - FD_TEST( fd_tower_vote_cnt( tower->votes )<=FD_TOWER_VOTE_MAX ); - fd_compact_tower_sync_serde_t tower_sync_serde = { - .root = fd_ulong_if( tower->root == ULONG_MAX, 0UL, tower->root ), - .lockouts_cnt = (ushort)fd_tower_vote_cnt( tower->votes ), - /* .lockouts populated below */ - .hash = *bank_hash, - .timestamp_option = 1, - .timestamp = fd_log_wallclock() / (long)1e9, /* seconds */ - .block_id = *block_id - }; - - ulong i = 0UL; - ulong prev = tower_sync_serde.root; - for( fd_tower_vote_iter_t iter = fd_tower_vote_iter_init( tower->votes ); - !fd_tower_vote_iter_done( tower->votes, iter ); - iter = fd_tower_vote_iter_next( tower->votes, iter ) ) { - fd_tower_vote_t const * vote = fd_tower_vote_iter_ele_const( tower->votes, iter ); - tower_sync_serde.lockouts[i].offset = vote->slot - prev; - tower_sync_serde.lockouts[i].confirmation_count = (uchar)vote->conf; - prev = vote->slot; - i++; - } - - uchar * txn_out = vote_txn->payload; - uchar * txn_meta_out = vote_txn->_; - - int same_addr = !memcmp( validator_identity, vote_authority, sizeof(fd_pubkey_t) ); - if( FD_LIKELY( same_addr ) ) { - - /* 0: validator identity - 1: vote account address - 2: vote program */ - - fd_txn_accounts_t votes; - votes.signature_cnt = 1; - votes.readonly_signed_cnt = 0; - votes.readonly_unsigned_cnt = 1; - votes.acct_cnt = 3; - votes.signers_w = validator_identity; - votes.signers_r = NULL; - votes.non_signers_w = vote_acc; - votes.non_signers_r = &fd_solana_vote_program_id; - FD_TEST( fd_txn_base_generate( txn_meta_out, txn_out, votes.signature_cnt, &votes, recent_blockhash->uc ) ); - - } else { - - /* 0: validator identity - 1: vote authority - 2: vote account address - 3: vote program */ - - fd_txn_accounts_t votes; - votes.signature_cnt = 2; - votes.readonly_signed_cnt = 1; - votes.readonly_unsigned_cnt = 1; - votes.acct_cnt = 4; - votes.signers_w = validator_identity; - votes.signers_r = vote_authority; - votes.non_signers_w = vote_acc; - votes.non_signers_r = &fd_solana_vote_program_id; - FD_TEST( fd_txn_base_generate( txn_meta_out, txn_out, votes.signature_cnt, &votes, recent_blockhash->uc ) ); - } - - /* Add the vote instruction to the transaction. */ - - uchar vote_ix_buf[FD_TXN_MTU]; - ulong vote_ix_sz = 0; - FD_STORE( uint, vote_ix_buf, FD_VOTE_IX_KIND_TOWER_SYNC ); - FD_TEST( 0==fd_compact_tower_sync_ser( &tower_sync_serde, vote_ix_buf + sizeof(uint), FD_TXN_MTU - sizeof(uint), &vote_ix_sz ) ); // cannot fail if fd_tower_vote_cnt( tower->votes ) <= FD_TOWER_VOTE_MAX - vote_ix_sz += sizeof(uint); - uchar program_id; - uchar ix_accs[2]; - if( FD_LIKELY( same_addr ) ) { - ix_accs[0] = 1; /* vote account address */ - ix_accs[1] = 0; /* vote authority */ - program_id = 2; /* vote program */ - } else { - ix_accs[0] = 2; /* vote account address */ - ix_accs[1] = 1; /* vote authority */ - program_id = 3; /* vote program */ - } - vote_txn->payload_sz = (ushort)fd_txn_add_instr( txn_meta_out, txn_out, program_id, ix_accs, 2, vote_ix_buf, vote_ix_sz ); -} - -int -fd_tower_verify( fd_tower_t const * tower ) { - if( FD_UNLIKELY( fd_tower_vote_cnt( tower->votes )>FD_TOWER_VOTE_MAX ) ) { - FD_LOG_WARNING(( "[%s] invariant violation: cnt %lu > FD_TOWER_VOTE_MAX %lu", __func__, fd_tower_vote_cnt( tower->votes ), (ulong)FD_TOWER_VOTE_MAX )); - return -1; - } - - fd_tower_vote_t const * prev = NULL; - for( fd_tower_vote_iter_t iter = fd_tower_vote_iter_init( tower->votes ); - !fd_tower_vote_iter_done( tower->votes, iter ); - iter = fd_tower_vote_iter_next( tower->votes, iter ) ) { - fd_tower_vote_t const * vote = fd_tower_vote_iter_ele_const( tower->votes, iter ); - if( FD_UNLIKELY( prev && ( vote->slot <= prev->slot || vote->conf >= prev->conf ) ) ) { - FD_LOG_WARNING(( "[%s] invariant violation: vote (slot:%lu conf:%lu) prev (slot:%lu conf:%lu)", __func__, vote->slot, vote->conf, prev->slot, prev->conf )); - return -1; - } - prev = vote; - } - return 0; -} - -static void -to_cstr( fd_tower_t const * tower, char * s, ulong len ) { - ulong root = tower->root; - ulong off = 0; - int n; - - n = snprintf( s + off, len - off, "[Tower]\n\n" ); - if( FD_UNLIKELY( n < 0 )) FD_LOG_CRIT(( "snprintf: %d", n )); - off += (ulong)n; - - if( FD_UNLIKELY( fd_tower_vote_empty( tower->votes ) ) ) return; - - ulong max_slot = 0; - - /* Determine spacing. */ - - for( fd_tower_vote_iter_t iter = fd_tower_vote_iter_init_rev( tower->votes ); - !fd_tower_vote_iter_done_rev( tower->votes, iter ); - iter = fd_tower_vote_iter_prev ( tower->votes, iter ) ) { - max_slot = fd_ulong_max( max_slot, fd_tower_vote_iter_ele_const( tower->votes, iter )->slot ); - } - - /* Calculate the number of digits in the maximum slot value. */ - - - int digit_cnt = (int)fd_ulong_base10_dig_cnt( max_slot ); - - /* Print the column headers. */ - - if( off < len ) { - n = snprintf( s + off, len - off, "slot%*s | %s\n", digit_cnt - (int)strlen("slot"), "", "confirmation count" ); - if( FD_UNLIKELY( n < 0 )) FD_LOG_CRIT(( "snprintf: %d", n )); - off += (ulong)n; - } - - /* Print the divider line. */ - - for( int i = 0; i < digit_cnt && off < len; i++ ) { - s[off++] = '-'; - } - if( off < len ) { - n = snprintf( s + off, len - off, " | " ); - if( FD_UNLIKELY( n < 0 )) FD_LOG_CRIT(( "snprintf: %d", n )); - off += (ulong)n; - } - for( ulong i = 0; i < strlen( "confirmation count" ) && off < len; i++ ) { - s[off++] = '-'; - } - if( off < len ) { - s[off++] = '\n'; - } - - /* Print each vote as a table. */ - - for( fd_tower_vote_iter_t iter = fd_tower_vote_iter_init_rev( tower->votes ); - !fd_tower_vote_iter_done_rev( tower->votes, iter ); - iter = fd_tower_vote_iter_prev ( tower->votes, iter ) ) { - fd_tower_vote_t const * vote = fd_tower_vote_iter_ele_const( tower->votes, iter ); - if( off < len ) { - n = snprintf( s + off, len - off, "%*lu | %lu\n", digit_cnt, vote->slot, vote->conf ); - if( FD_UNLIKELY( n < 0 )) FD_LOG_CRIT(( "snprintf: %d", n )); - off += (ulong)n; - } - } - - if( FD_UNLIKELY( root == ULONG_MAX ) ) { - if( off < len ) { - n = snprintf( s + off, len - off, "%*s | root\n", digit_cnt, "NULL" ); - if( FD_UNLIKELY( n < 0 )) FD_LOG_CRIT(( "snprintf: %d", n )); - off += (ulong)n; - } - } else { - if( off < len ) { - n = snprintf( s + off, len - off, "%*lu | root\n", digit_cnt, root ); - if( FD_UNLIKELY( n < 0 )) FD_LOG_CRIT(( "snprintf: %d", n )); - off += (ulong)n; - } - } - - /* Ensure null termination */ - if( off < len ) { - s[off] = '\0'; - } else { - s[len - 1] = '\0'; - } -} - -char * -fd_tower_to_cstr( fd_tower_t const * tower, - char * cstr ) { - to_cstr( tower, cstr, FD_TOWER_CSTR_MIN ); - return cstr; -} - -void -fd_tower_count_vote( fd_tower_t * tower, - fd_pubkey_t const * vote_acc, - ulong stake, - uchar const * data, - ulong data_sz ) { - fd_tower_vtr_t * vtr = fd_tower_vtr_push_tail_nocopy( tower->vtrs ); - vtr->vote_acc = *vote_acc; - vtr->stake = stake; - fd_tower_vote_remove_all( vtr->votes ); - fd_tower_from_vote_acc( vtr->votes, &vtr->root, data, data_sz ); -} - -/* Block functions ********************************************************/ - -static int -is_ancestor( fd_tower_t * tower, - ulong slot, - ulong ancestor_slot ) { - fd_tower_blk_t * anc = blk_map_ele_query( tower->blk_map, &slot, NULL, tower->blk_pool ); - while( FD_LIKELY( anc ) ) { - if( FD_LIKELY( anc->parent_slot == ancestor_slot ) ) return 1; - anc = anc->parent_slot == ULONG_MAX ? NULL : blk_map_ele_query( tower->blk_map, &anc->parent_slot, NULL, tower->blk_pool ); - } - return 0; -} - -int -fd_tower_blocks_is_slot_ancestor( fd_tower_t * tower, - ulong descendant_slot, - ulong ancestor_slot ) { - return is_ancestor( tower, descendant_slot, ancestor_slot ); -} - -int -fd_tower_blocks_is_slot_descendant( fd_tower_t * tower, - ulong ancestor_slot, - ulong descendant_slot ) { - return is_ancestor( tower, descendant_slot, ancestor_slot ); -} - -ulong -fd_tower_blocks_lowest_common_ancestor( fd_tower_t * tower, - ulong slot1, - ulong slot2 ) { - - fd_tower_blk_t * fork1 = blk_map_ele_query( tower->blk_map, &slot1, NULL, tower->blk_pool ); - fd_tower_blk_t * fork2 = blk_map_ele_query( tower->blk_map, &slot2, NULL, tower->blk_pool ); - - if( FD_UNLIKELY( !fork1 )) FD_LOG_CRIT(( "slot1 %lu not found", slot1 )); - if( FD_UNLIKELY( !fork2 )) FD_LOG_CRIT(( "slot2 %lu not found", slot2 )); - - while( FD_LIKELY( fork1 && fork2 ) ) { - if( FD_UNLIKELY( fork1->slot == fork2->slot ) ) return fork1->slot; - if( fork1->slot > fork2->slot ) fork1 = blk_map_ele_query( tower->blk_map, &fork1->parent_slot, NULL, tower->blk_pool ); - else fork2 = blk_map_ele_query( tower->blk_map, &fork2->parent_slot, NULL, tower->blk_pool ); - } - - return ULONG_MAX; -} - -fd_hash_t const * -fd_tower_blocks_canonical_block_id( fd_tower_t * tower, - ulong slot ) { - fd_tower_blk_t * blk = blk_map_ele_query( tower->blk_map, &slot, NULL, tower->blk_pool ); - if( FD_UNLIKELY( !blk ) ) return NULL; - return fd_tower_blk_canonical_block_id( blk ); -} - -fd_tower_blk_t * -fd_tower_blocks_query( fd_tower_t * tower, ulong slot ) { - return blk_map_ele_query( tower->blk_map, &slot, NULL, tower->blk_pool ); -} - -fd_tower_blk_t * -fd_tower_blocks_insert( fd_tower_t * tower, - ulong slot, - ulong parent_slot ) { - FD_TEST( blk_pool_free( tower->blk_pool ) ); - fd_tower_blk_t * blk = blk_pool_ele_acquire( tower->blk_pool ); - - memset( blk, 0, sizeof(fd_tower_blk_t) ); - blk->parent_slot = parent_slot; - blk->slot = slot; - blk->prev_leader_slot = ULONG_MAX; - blk_map_ele_insert( tower->blk_map, blk, tower->blk_pool ); - return blk; -} - -void -fd_tower_blocks_remove( fd_tower_t * tower, - ulong slot ) { - fd_tower_blk_t * blk = blk_map_ele_query( tower->blk_map, &slot, NULL, tower->blk_pool ); - if( FD_LIKELY( blk ) ) { - blk_map_ele_remove_fast( tower->blk_map, blk, tower->blk_pool ); - blk_pool_ele_release( tower->blk_pool, blk ); - } -} - -/* Lockos implementation */ - -void -fd_tower_lockos_insert( fd_tower_t * tower, - ulong slot, - fd_hash_t const * addr, - fd_tower_vote_t * votes ) { - - lockout_interval_map_t * lck_map = tower->lck_map; - lockout_interval_t * lck_pool = tower->lck_pool; - lockout_slot_map_t * lck_slot_map = tower->lck_slot_map; - lockout_slot_t * lck_slot_pool = tower->lck_slot_pool; - - ulong vote_cnt = fd_tower_vote_cnt( votes ); - uint pubkey_idx = UINT_MAX; - lockout_slot_t * lockout_slot = NULL; - if( FD_LIKELY( vote_cnt ) ) { - FD_CHECK_CRIT( vote_cnt<=UINT_MAX, "tower lockout vote count overflow" ); - - uint slot_key = (uint)slot; - lockout_slot = lockout_slot_map_ele_query( lck_slot_map, &slot_key, NULL, lck_slot_pool ); - if( FD_UNLIKELY( !lockout_slot ) ) { - FD_CHECK_CRIT( lockout_slot_pool_free( lck_slot_pool ), "no free entries in tower lockout slot pool" ); - lockout_slot = lockout_slot_pool_ele_acquire( lck_slot_pool ); - lockout_slot->slot = slot_key; - lockout_slot->end_head = (uint)lockout_interval_pool_idx_null( lck_pool ); - FD_CHECK_CRIT( lockout_slot_map_ele_insert( lck_slot_map, lockout_slot, lck_slot_pool ), "unable to insert into tower lockout slot map" ); - } - - lockout_pubkey_ref_t * ref = lockout_pubkey_map_ele_query( tower->lck_pubkey_map, addr, NULL, tower->lck_pubkey_pool ); - if( FD_UNLIKELY( !ref ) ) { - FD_CHECK_CRIT( lockout_pubkey_pool_free( tower->lck_pubkey_pool ), "no free entries in tower lockout pubkey pool" ); - ref = lockout_pubkey_pool_ele_acquire( tower->lck_pubkey_pool ); - ref->addr = *addr; - ref->ref_cnt = 0U; - FD_CHECK_CRIT( lockout_pubkey_map_ele_insert( tower->lck_pubkey_map, ref, tower->lck_pubkey_pool ), "unable to insert into tower lockout pubkey map" ); - } - - ref->ref_cnt += (uint)vote_cnt; - pubkey_idx = (uint)lockout_pubkey_pool_idx( tower->lck_pubkey_pool, ref ); - FD_TEST( pubkey_idx<(1U<conf<=31U ); - uint interval_end = (uint)(vote->slot + (1UL << vote->conf)); - lockout_interval_key_t key = lockout_interval_key( slot_idx, interval_end, (uint)vote->conf, pubkey_idx ); - - int is_unique_end = !lockout_interval_map_ele_query( lck_map, &key, NULL, lck_pool ); - FD_TEST( lockout_interval_pool_free( lck_pool ) ); - lockout_interval_t * interval = lockout_interval_pool_ele_acquire( lck_pool ); - interval->key = key; - interval->end_next = (uint)lockout_interval_pool_idx_null( lck_pool ); - FD_TEST( lockout_interval_map_ele_insert( lck_map, interval, lck_pool ) ); - if( is_unique_end ) { - interval->end_next = lockout_slot->end_head; - lockout_slot->end_head = (uint)lockout_interval_pool_idx( lck_pool, interval ); - } - } -} - -void -fd_tower_lockos_remove( fd_tower_t * tower, - ulong slot ) { - - lockout_interval_map_t * lck_map = tower->lck_map; - lockout_interval_t * lck_pool = tower->lck_pool; - lockout_slot_map_t * lck_slot_map = tower->lck_slot_map; - lockout_slot_t * lck_slot_pool = tower->lck_slot_pool; - - /* First remove the lockout slot header from the fork slot map */ - uint slot_key = (uint)slot; - lockout_slot_t * lockout_slot = lockout_slot_map_ele_remove( lck_slot_map, &slot_key, NULL, lck_slot_pool ); - if( FD_UNLIKELY( !lockout_slot ) ) return; - - /* Iterate through unique end interval and for each of those, release - every interval sharing that same end slot. */ - uint end_idx = lockout_slot->end_head; - ulong slot_idx = lockout_slot_pool_idx( lck_slot_pool, lockout_slot ); - while( end_idx!=lockout_interval_pool_idx_null( lck_pool ) ) { - lockout_interval_t * unique_end = lockout_interval_pool_ele( lck_pool, end_idx ); - uint next_end_idx = unique_end->end_next; - uint interval_end = unique_end->key.interval_end; - - lockout_interval_key_t key = lockout_interval_key( slot_idx, interval_end, 0U, 0UL ); - for( lockout_interval_t * itrvl = lockout_interval_map_ele_remove( lck_map, &key, NULL, lck_pool ); - !!itrvl; - itrvl = lockout_interval_map_ele_remove( lck_map, &key, NULL, lck_pool ) ) { - lockout_pubkey_ref_t * ref = lockout_pubkey_pool_ele( tower->lck_pubkey_pool, lockout_interval_key_pubkey_idx( &itrvl->key ) ); - if( FD_LIKELY( !--ref->ref_cnt ) ) { - FD_CHECK_CRIT( lockout_pubkey_map_ele_remove( tower->lck_pubkey_map, &ref->addr, NULL, tower->lck_pubkey_pool ), "unable to remove tower lockout pubkey" ); - lockout_pubkey_pool_ele_release( tower->lck_pubkey_pool, ref ); - } - lockout_interval_pool_ele_release( lck_pool, itrvl ); - } - end_idx = next_end_idx; - } - lockout_slot_pool_ele_release( lck_slot_pool, lockout_slot ); -} diff --git a/src/choreo/tower/fd_tower.h b/src/choreo/tower/fd_tower.h index cebfa3f34de..652dc2ba45c 100644 --- a/src/choreo/tower/fd_tower.h +++ b/src/choreo/tower/fd_tower.h @@ -371,7 +371,6 @@ #include "../fd_choreo_base.h" #include "fd_tower_serdes.h" -#include "fd_tower_stakes.h" #include "../ghost/fd_ghost.h" #include "../votes/fd_votes.h" #include "../../disco/pack/fd_microblock.h" @@ -487,11 +486,6 @@ struct fd_tower { void * lck_slot_map; /* lockout end headers map */ void * lck_pubkey_pool; /* refcounted vote-account pubkey pool for lockouts */ void * lck_pubkey_map; /* map of vote-account pubkeys for lockouts */ - - fd_tower_stakes_vtr_map_t * stk_vtr_map; - fd_tower_stakes_vtr_t * stk_vtr_pool; - fd_tower_stakes_slot_t * stk_slot_map; - fd_used_acc_scratch_t * stk_used_acc; }; typedef struct fd_tower fd_tower_t; diff --git a/src/choreo/tower/fd_tower_file.c b/src/choreo/tower/fd_tower_file.c deleted file mode 100644 index c9323805a8a..00000000000 --- a/src/choreo/tower/fd_tower_file.c +++ /dev/null @@ -1,166 +0,0 @@ -#include "fd_tower_file.h" - -#include "../../ballet/ed25519/fd_ed25519.h" - -#define SAVED_TOWER_KIND (1U) /* SavedTowerVersions::Current */ -#define LAST_VOTE_KIND (3U) /* VoteTransaction::TowerSync */ -#define THRESHOLD_DEPTH (8UL) -#define THRESHOLD_SIZE (2.0/3.0) -#define TOWER_SYNC_MAX (512UL) - -#define SIG_OFF FD_TOWER_FILE_SIG_OFF -#define DATA_OFF FD_TOWER_FILE_DATA_OFF - -#define LOAD( T, dst ) do { \ - if( FD_UNLIKELY( sizeof(T)>buf_sz-off ) ) return FD_TOWER_FILE_ERR_SIZE; \ - (dst) = FD_LOAD( T, buf+off ); \ - off += sizeof(T); \ - } while(0) - -#define SKIP( sz ) do { \ - if( FD_UNLIKELY( (sz)>buf_sz-off ) ) return FD_TOWER_FILE_ERR_SIZE; \ - off += (sz); \ - } while(0) - -int -fd_tower_file_de( uchar const * buf, - ulong buf_sz, - fd_pubkey_t const * identity, - fd_tower_file_t * out ) { - if( FD_UNLIKELY( buf_sz>FD_TOWER_FILE_MAX ) ) return FD_TOWER_FILE_ERR_SIZE; - ulong off = 0UL; - - uint kind; LOAD( uint, kind ); - if( FD_UNLIKELY( kind!=SAVED_TOWER_KIND ) ) return FD_TOWER_FILE_ERR_VERSION; - - if( FD_UNLIKELY( buf_szuc, sha ) ) ) return FD_TOWER_FILE_ERR_SIG; - - if( FD_UNLIKELY( 32UL>buf_sz-off ) ) return FD_TOWER_FILE_ERR_SIZE; - if( FD_UNLIKELY( !fd_memeq( buf+off, identity->uc, 32UL ) ) ) return FD_TOWER_FILE_ERR_IDENTITY; - off += 32UL; - - ulong threshold_depth; LOAD( ulong, threshold_depth ); - double threshold_size; LOAD( double, threshold_size ); - if( FD_UNLIKELY( threshold_depth!=THRESHOLD_DEPTH ) ) return FD_TOWER_FILE_ERR_TOWER; - if( FD_UNLIKELY( threshold_size !=THRESHOLD_SIZE ) ) return FD_TOWER_FILE_ERR_TOWER; - - SKIP( 32UL ); /* node_pubkey */ - SKIP( 32UL ); /* authorized_withdrawer */ - SKIP( 1UL ); /* commission */ - - ulong votes_cnt; LOAD( ulong, votes_cnt ); - if( FD_UNLIKELY( !votes_cnt || votes_cnt>FD_TOWER_VOTE_MAX ) ) return FD_TOWER_FILE_ERR_TOWER; - fd_tower_vote_t votes[ FD_TOWER_VOTE_MAX ]; - for( ulong i=0UL; i(buf_sz-off)/40UL ) ) return FD_TOWER_FILE_ERR_SIZE; - SKIP( authorized_voters_cnt*40UL ); - SKIP( 32UL*48UL+8UL ); /* prior_voters buf, idx */ - uchar prior_voters_empty; LOAD( uchar, prior_voters_empty ); - if( FD_UNLIKELY( prior_voters_empty>1U ) ) return FD_TOWER_FILE_ERR_TOWER; - ulong epoch_credits_cnt; LOAD( ulong, epoch_credits_cnt ); - if( FD_UNLIKELY( epoch_credits_cnt>(buf_sz-off)/24UL ) ) return FD_TOWER_FILE_ERR_SIZE; - SKIP( epoch_credits_cnt*24UL ); - SKIP( 8UL ); /* last_timestamp slot */ - SKIP( 8UL ); /* last_timestamp ts */ - - uint last_vote_kind; LOAD( uint, last_vote_kind ); - if( FD_UNLIKELY( last_vote_kind!=LAST_VOTE_KIND ) ) return FD_TOWER_FILE_ERR_VERSION; - - if( FD_UNLIKELY( off>buf_sz || buf_sz-off<16UL ) ) return FD_TOWER_FILE_ERR_SIZE; - ulong sync_sz = buf_sz-off-16UL; - fd_compact_tower_sync_serde_t sync; - if( FD_UNLIKELY( fd_compact_tower_sync_de( &sync, buf+off, sync_sz ) ) ) return FD_TOWER_FILE_ERR_TOWER; - - uchar sync_buf[ TOWER_SYNC_MAX ]; - ulong canonical_sz; - if( FD_UNLIKELY( fd_compact_tower_sync_ser( &sync, sync_buf, sizeof(sync_buf), &canonical_sz ) ) ) return FD_TOWER_FILE_ERR_TOWER; - if( FD_UNLIKELY( canonical_sz!=sync_sz || !fd_memeq( sync_buf, buf+off, sync_sz ) ) ) return FD_TOWER_FILE_ERR_SIZE; - if( FD_UNLIKELY( sync.root!=root || sync.lockouts_cnt!=votes_cnt ) ) return FD_TOWER_FILE_ERR_TOWER; - - fd_tower_vote_t decoded[ FD_TOWER_VOTE_MAX ]; - ulong decoded_cnt; - ulong decoded_root; - if( FD_UNLIKELY( fd_compact_tower_sync_to_votes( &sync, decoded, &decoded_cnt, &decoded_root ) ) ) return FD_TOWER_FILE_ERR_TOWER; - if( FD_UNLIKELY( decoded_cnt!=votes_cnt || decoded_root!=root || - !fd_memeq( decoded, votes, votes_cnt*sizeof(fd_tower_vote_t) ) ) ) return FD_TOWER_FILE_ERR_TOWER; - if( FD_UNLIKELY( votes[ votes_cnt-1UL ].slot==ULONG_MAX ) ) return FD_TOWER_FILE_ERR_TOWER; - - off += sync_sz; - ulong last_timestamp_slot; LOAD( ulong, last_timestamp_slot ); - long last_timestamp; LOAD( long, last_timestamp ); - if( FD_UNLIKELY( off!=buf_sz ) ) return FD_TOWER_FILE_ERR_SIZE; - - fd_memset( out, 0, sizeof(fd_tower_file_t) ); - fd_memcpy( out->votes, votes, votes_cnt*sizeof(fd_tower_vote_t) ); - out->votes_cnt = votes_cnt; - out->root = root; - out->bank_hash = sync.hash; - out->block_id = sync.block_id; - out->timestamp_slot = last_timestamp_slot; - out->timestamp = last_timestamp; - - return FD_TOWER_FILE_SUCCESS; -} - -#undef LOAD -#undef SKIP - -ulong -fd_tower_file_ser( fd_compact_tower_sync_serde_t const * sync, - fd_pubkey_t const * identity, - uchar buf[ static FD_TOWER_FILE_MAX ] ) { - fd_tower_vote_t votes[ FD_TOWER_VOTE_MAX ]; - ulong votes_cnt; - ulong root; - FD_TEST( !fd_compact_tower_sync_to_votes( sync, votes, &votes_cnt, &root ) && votes_cnt ); - ulong last = votes[ votes_cnt-1UL ].slot; - long timestamp = fd_long_if( sync->timestamp_option, sync->timestamp, 0L ); - - ulong off = DATA_OFF; -# define PUT( T, v ) do { FD_STORE( T, buf+off, (v) ); off += sizeof(T); } while(0) - fd_memcpy( buf+off, identity->uc, 32UL ); off += 32UL; - PUT( ulong, THRESHOLD_DEPTH ); - PUT( double, THRESHOLD_SIZE ); - fd_memset( buf+off, 0, 65UL ); off += 65UL; /* node_pubkey, authorized_withdrawer, commission */ - PUT( ulong, votes_cnt ); - for( ulong i=0UL; i.bin, bincode - of SavedTowerVersions::Current, a 64 byte Ed25519 signature by the - node identity over a Tower1_14_11 body. */ - -#include "fd_tower.h" -#include "fd_tower_serdes.h" - -/* FD_TOWER_FILE_MAX is the largest file we read. The signature is at - FD_TOWER_FILE_SIG_OFF and covers [FD_TOWER_FILE_DATA_OFF, size). */ -#define FD_TOWER_FILE_MAX (8192UL) -#define FD_TOWER_FILE_SIG_OFF (4UL) /* u32 kind precedes the signature */ -#define FD_TOWER_FILE_DATA_OFF (4UL+64UL+8UL) /* kind, signature, data_sz */ - -/* Return codes of fd_tower_file_de. */ -#define FD_TOWER_FILE_SUCCESS ( 0) -#define FD_TOWER_FILE_ERR_SIZE (-1) /* truncated, too large, or the lengths disagree */ -#define FD_TOWER_FILE_ERR_VERSION (-2) /* not SavedTowerVersions::Current, or the last vote is not a TowerSync */ -#define FD_TOWER_FILE_ERR_SIG (-3) /* the signature does not verify under identity */ -#define FD_TOWER_FILE_ERR_IDENTITY (-4) /* the node pubkey in the file is not identity (Agave's WrongTower) */ -#define FD_TOWER_FILE_ERR_TOWER (-5) /* thresholds, votes, root or lockouts are not a tower Agave writes */ - -/* A decoded and verified tower file. bank_hash and block_id belong - to the last vote, votes[votes_cnt-1]. */ -struct fd_tower_file { - fd_tower_vote_t votes[ FD_TOWER_VOTE_MAX ]; - ulong votes_cnt; - ulong root; - fd_hash_t bank_hash; - fd_hash_t block_id; - ulong timestamp_slot; - long timestamp; -}; -typedef struct fd_tower_file fd_tower_file_t; - -FD_PROTOTYPES_BEGIN - -/* fd_tower_file_de verifies the signature under identity, checks that - the file is for identity, the two checks Agave makes, and decodes - into out. A tower without a root is rejected, Agave always writes - one and cannot restore a tower without it. Returns - FD_TOWER_FILE_SUCCESS, or an FD_TOWER_FILE_ERR_* code with out left - untouched. */ -int -fd_tower_file_de( uchar const * buf, - ulong buf_sz, - fd_pubkey_t const * identity, - fd_tower_file_t * out ); - -/* fd_tower_file_ser writes the tower file into a caller-owned buffer, - except the signature. The vote state fields Agave does not restore - from are not populated. Returns the file size. */ -ulong -fd_tower_file_ser( fd_compact_tower_sync_serde_t const * sync, - fd_pubkey_t const * identity, - uchar buf[ static FD_TOWER_FILE_MAX ] ); - -FD_PROTOTYPES_END - -#endif /* HEADER_fd_src_choreo_tower_fd_tower_file_h */ diff --git a/src/choreo/tower/fd_tower_serdes.c b/src/choreo/tower/fd_tower_serdes.c index 5b311aaab0b..68f78c8970c 100644 --- a/src/choreo/tower/fd_tower_serdes.c +++ b/src/choreo/tower/fd_tower_serdes.c @@ -227,34 +227,5 @@ fd_txn_parse_simple_vote( fd_txn_t const * txn, return 0; } -int -fd_compact_tower_sync_to_votes( fd_compact_tower_sync_serde_t const * serde, - fd_tower_vote_t * out, - ulong * out_cnt, - ulong * out_root ) { - if( FD_UNLIKELY( serde->lockouts_cnt>FD_TOWER_VOTE_MAX ) ) return -1; - - fd_tower_vote_t votes[ FD_TOWER_VOTE_MAX ]; - ulong slot = fd_ulong_if( serde->root==ULONG_MAX, 0UL, serde->root ); - ulong prev_conf = FD_TOWER_VOTE_MAX+1UL; - /* Only the vote below is checked, Agave pops expired votes from the - top only, so a deeper expired vote is a normal state. */ - for( ulong i=0UL; ilockouts_cnt; i++ ) { - ulong offset = serde->lockouts[ i ].offset; - ulong conf = serde->lockouts[ i ].confirmation_count; - int repeats_slot = !offset && (i || serde->root!=ULONG_MAX); - if( FD_UNLIKELY( repeats_slot || offset>ULONG_MAX-slot || !conf || - conf>=prev_conf || conf>FD_TOWER_VOTE_MAX ) ) return -1; - if( FD_UNLIKELY( i && offset>(1UL<lockouts_cnt*sizeof(fd_tower_vote_t) ); - *out_cnt = serde->lockouts_cnt; - *out_root = serde->root; - return 0; -} - #undef DE #undef SER diff --git a/src/choreo/tower/fd_tower_serdes.h b/src/choreo/tower/fd_tower_serdes.h index db5f001cf2f..88fd1aaa009 100644 --- a/src/choreo/tower/fd_tower_serdes.h +++ b/src/choreo/tower/fd_tower_serdes.h @@ -8,9 +8,6 @@ #include "../../ballet/txn/fd_txn.h" #include "../../flamenco/runtime/program/vote/fd_vote_codec.h" -struct fd_tower_vote; -typedef struct fd_tower_vote fd_tower_vote_t; - /* FD_VOTE_IX_KIND_* give vote program instruction discriminants (first four bytes of instruction data). Older instruction types are ignored by tower. */ @@ -67,17 +64,6 @@ fd_compact_tower_sync_de( fd_compact_tower_sync_serde_t * serde, uchar const * buf, ulong buf_sz ); -/* fd_compact_tower_sync_to_votes expands the lockout offsets into - ascending votes (out holds FD_TOWER_VOTE_MAX), count and root, - ULONG_MAX if none. Returns -1 for a repeated or descending slot, a - bad confirmation count or a vote past the lockout of the vote below. */ - -int -fd_compact_tower_sync_to_votes( fd_compact_tower_sync_serde_t const * serde, - fd_tower_vote_t * out, - ulong * out_cnt, - ulong * out_root ); - /* A buffer with capacity FD_VOTE_STATE_DATA_MAX can fit any valid vote account supported by tower (v2, v3, and v4). */ diff --git a/src/choreo/tower/fd_tower_stakes.c b/src/choreo/tower/fd_tower_stakes.c deleted file mode 100644 index 5f47bda9bd2..00000000000 --- a/src/choreo/tower/fd_tower_stakes.c +++ /dev/null @@ -1,45 +0,0 @@ -#include "fd_tower.h" - -ulong -fd_tower_stakes_insert( fd_tower_t * tower, - ulong slot, - fd_hash_t const * vote_account, - ulong stake, - ulong prev_voter_idx ) { - - fd_tower_stakes_vtr_t * pool = tower->stk_vtr_pool; - if( FD_UNLIKELY( !fd_tower_stakes_vtr_pool_free( pool ) ) ) FD_LOG_CRIT(( "no free voter stakes in pool" )); - FD_TEST( prev_voter_idx==ULONG_MAX || prev_voter_idxkey = (fd_tower_stakes_vtr_xid_t){ .addr = *vote_account, .slot = slot }; - new_voter_stake->stake = stake; - new_voter_stake->prev = (uint)prev_voter_idx; - fd_tower_stakes_vtr_map_ele_insert( tower->stk_vtr_map, new_voter_stake, pool ); - - /* Point to first vtr (head of list). */ - - fd_tower_stakes_slot_t * blk = fd_tower_stakes_slot_query( tower->stk_slot_map, slot, NULL ); - if( FD_UNLIKELY( !blk ) ) blk = fd_tower_stakes_slot_insert( tower->stk_slot_map, slot ); - blk->head = (uint)fd_tower_stakes_vtr_pool_idx( pool, new_voter_stake ); - return (ulong)blk->head; -} - -void -fd_tower_stakes_remove( fd_tower_t * tower, - ulong slot ) { - - fd_tower_stakes_slot_t * blk = fd_tower_stakes_slot_query( tower->stk_slot_map, slot, NULL ); - if( FD_UNLIKELY( !blk ) ) return; - uint voter_idx = blk->head; - - /* Remove the linked list of voters. */ - - while( FD_UNLIKELY( voter_idx!=UINT_MAX ) ) { - fd_tower_stakes_vtr_t * voter_stake = fd_tower_stakes_vtr_pool_ele( tower->stk_vtr_pool, voter_idx ); - voter_idx = voter_stake->prev; - fd_tower_stakes_vtr_t * remove = fd_tower_stakes_vtr_map_ele_remove( tower->stk_vtr_map, &voter_stake->key, NULL, tower->stk_vtr_pool ); - if( FD_UNLIKELY( !remove ) ) FD_LOG_CRIT(( "invariant violation: voter stake does not exist in map" )); - fd_tower_stakes_vtr_pool_ele_release( tower->stk_vtr_pool, voter_stake ); - } - fd_tower_stakes_slot_remove( tower->stk_slot_map, blk ); -} diff --git a/src/choreo/tower/fd_tower_stakes.h b/src/choreo/tower/fd_tower_stakes.h deleted file mode 100644 index 23c003d79b0..00000000000 --- a/src/choreo/tower/fd_tower_stakes.h +++ /dev/null @@ -1,117 +0,0 @@ -#ifndef HEADER_fd_src_choreo_tower_fd_tower_stakes_h -#define HEADER_fd_src_choreo_tower_fd_tower_stakes_h - -/* fd_tower_stakes_t tracks stakes of the top k voters in a slot (as of - SIMD-0357, k=2000). While the stakes are usually the same for every - slot in a given epoch, it is not guaranteed. Specifically, if there - is a fork across an epoch boundary, the set of voters and their - stakes may be different across slots in the same epoch because the - slots are on different forks that calculated the stakes differently. - If we have not rooted a slot in the new epoch yet, this could be - different from other forks, but if we have rooted an epoch slot, the - stakes for each voter are the same for all forks that descend from - that root (forks that do not descend will have been pruned). - - This is currently exclusively used for the switch check, which - requires the stakes of each voter on the HEAVIEST fork in the epoch. - We need to do all this tracking because tower cannot query the vote - states from any bank at will. Replay determines which banks can be - queried, and won't know beforehand which banks will be the heaviest. - - fd_tower_stakes_t is backed by two hash maps: - 1. fd_tower_stakes_vtr_map: this maps a vote account to a voter stake - 2. fd_tower_stakes_slot_map: this maps a slot to a voter stake index - - The voter_stake_map has a compound key {vote_account, slot}, so that - the map can be queries O(1) by slot and vote account. As we populate - the map, we also thread a linkedlist through all the entries for the - same slot. This is possible because the vote stake map is populated/ - updated all at once when a slot arrives from the bank, so we can - sequentially link the current entry to the previous entry. Then the - last entry in the linkedlist (last voter we process for a slot) will - have its key {vote_account, slot} put in the slot_stakes_map. This - way on publish, we have a way to query all the stakes / voters for a - slot without doing a full scan of the voter_stake_map. */ - -#include "../fd_choreo_base.h" - -struct fd_tower_stakes_vtr_xid { - fd_hash_t addr; /* vote account address */ - ulong slot; -}; -typedef struct fd_tower_stakes_vtr_xid fd_tower_stakes_vtr_xid_t; - -struct fd_tower_stakes_vtr { - fd_tower_stakes_vtr_xid_t key; - uint prev; - uint next; - ulong stake; -}; -typedef struct fd_tower_stakes_vtr fd_tower_stakes_vtr_t; - -#define MAP_NAME fd_tower_stakes_vtr_map -#define MAP_ELE_T fd_tower_stakes_vtr_t -#define MAP_KEY_T fd_tower_stakes_vtr_xid_t -#define MAP_IDX_T uint -#define MAP_KEY_EQ(k0,k1) (!memcmp( k0, k1, sizeof(fd_tower_stakes_vtr_xid_t) )) -#define MAP_KEY_HASH(key, seed) fd_ulong_hash( ((key)->slot) ^ ((key)->addr.ul[0]) ^ (seed) ) -#include "../../util/tmpl/fd_map_chain.c" - -#define POOL_NAME fd_tower_stakes_vtr_pool -#define POOL_T fd_tower_stakes_vtr_t -#define POOL_IDX_T uint -#define POOL_LAZY 1 -#include "../../util/tmpl/fd_pool.c" - -/* Some really terrible witchcraft to track used vote accounts for - whatever reason. For example, switch check needs to make sure it's - not repeating usage of the same vote account. We can flip a bit on - the vote stake pool index if its been used. Caller should ensure that - the set is cleared before and after each use. The size of the set - will be the number of elements in the vote stake pool. */ - -#define SET_NAME fd_used_acc_scratch -#include "../../util/tmpl/fd_set_dynamic.c" - -struct fd_tower_stakes_slot { - ulong slot; - uint head; /* pool idx of the head of a linked list of voters in this slot */ -}; -typedef struct fd_tower_stakes_slot fd_tower_stakes_slot_t; - -#define MAP_NAME fd_tower_stakes_slot -#define MAP_T fd_tower_stakes_slot_t -#define MAP_KEY slot -#define MAP_KEY_NULL ULONG_MAX -#define MAP_KEY_INVAL(key) ((key)==ULONG_MAX) -#define MAP_MEMOIZE 0 -#include "../../util/tmpl/fd_map_dynamic.c" - -struct fd_tower; - -FD_PROTOTYPES_BEGIN - -/* fd_tower_stakes_insert adds a new (voter, stake) pair to the epoch - stakes for a specific slot, and returns the index of the new voter - stake in the pool. prev_voter_idx is the index of the previous voter - stake in the pool. If this is the first voter inserted for this - slot, prev_voter_idx should be ULONG_MAX. Example usage: - - prev_voter_idx = ULONG_MAX; - for( v : voters ) { - voter_idx = fd_tower_stakes_insert( tower, slot, v.vote_account, v.stake, prev_voter_idx ); - prev_voter_idx = voter_idx; - } */ - -ulong -fd_tower_stakes_insert( struct fd_tower * tower, - ulong slot, - fd_hash_t const * vote_account, - ulong stake, - ulong prev_voter_idx ); - -void -fd_tower_stakes_remove( struct fd_tower * tower, - ulong slot ); - -#endif /* HEADER_fd_src_choreo_tower_fd_tower_stakes_h */ diff --git a/src/choreo/tower/fuzz_tower_file.c b/src/choreo/tower/fuzz_tower_file.c deleted file mode 100644 index fdaeb7d3b60..00000000000 --- a/src/choreo/tower/fuzz_tower_file.c +++ /dev/null @@ -1,88 +0,0 @@ -#if !FD_HAS_HOSTED -#error "This target requires FD_HAS_HOSTED" -#endif - -#include -#include -#include - -#include "../../util/fd_util.h" -#include "../../util/sanitize/fd_fuzz.h" -#include "../../ballet/ed25519/fd_ed25519.h" -#include "fd_tower_file.h" - -static uchar keypair[ 64 ]; /* private key then public key */ -static fd_sha512_t sha[ 1 ]; - -int -LLVMFuzzerInitialize( int * argc, - char *** argv ) { - /* Set up shell without signal handlers */ - putenv( "FD_LOG_BACKTRACE=0" ); - setenv( "FD_LOG_PATH", "", 0 ); - fd_boot( argc, argv ); - atexit( fd_halt ); - fd_log_level_core_set(3); /* crash on warning log */ - - fd_sha512_join( fd_sha512_new( sha ) ); - fd_ed25519_public_from_private( keypair+32UL, keypair, sha ); - return 0; -} - -/* sign writes data_sz, the identity and the signature into buf, so the - fuzzer gets past the signature check. */ - -static void -sign( uchar * buf, - ulong buf_sz ) { - if( buf_szFD_TOWER_FILE_MAX ) return; - FD_STORE( ulong, buf+FD_TOWER_FILE_SIG_OFF+64UL, buf_sz-FD_TOWER_FILE_DATA_OFF ); - memcpy( buf+FD_TOWER_FILE_DATA_OFF, keypair+32UL, 32UL ); - fd_ed25519_sign( buf+FD_TOWER_FILE_SIG_OFF, buf+FD_TOWER_FILE_DATA_OFF, buf_sz-FD_TOWER_FILE_DATA_OFF, keypair+32UL, keypair, sha ); -} - -int -LLVMFuzzerTestOneInput( uchar const * data, - ulong data_sz ) { - fd_pubkey_t const * identity = (fd_pubkey_t const *)fd_type_pun_const( keypair+32UL ); - - uchar * buf = malloc( data_sz+1UL ); /* exact size so ASan catches overreads */ - memcpy( buf, data, data_sz ); - sign( buf, data_sz ); - - fd_tower_file_t out; - int err = fd_tower_file_de( buf, data_sz, identity, &out ); - free( buf ); - if( err ) { - FD_FUZZ_MUST_BE_COVERED; - return 0; - } - FD_FUZZ_MUST_BE_COVERED; - - /* Write the decoded tower back and check that it decodes the same. */ - - fd_compact_tower_sync_serde_t sync = { - .root = out.root, - .lockouts_cnt = (ushort)out.votes_cnt, - .hash = out.bank_hash, - .timestamp_option = 1, - .timestamp = out.timestamp, - .block_id = out.block_id - }; - ulong prev = out.root; - for( ulong i=0UL; ihead)==sizeof(uint) ); - FD_TEST( !fd_tower_footprint( 1UL<<32, 0UL ) ); - FD_TEST( !fd_tower_footprint( 1UL, 1UL<<32 ) ); - FD_TEST( !fd_tower_footprint( 1UL<<20, 1UL<<20 ) ); - FD_TEST( fd_tower_footprint( 8192UL, 1UL ) ); - FD_TEST( !fd_tower_footprint( 8193UL, 1UL ) ); - FD_TEST( fd_tower_footprint( 1UL, 1UL<<13 ) ); - FD_TEST( !fd_tower_footprint( 1UL, (1UL<<13)+1UL ) ); - FD_TEST( fd_tower_footprint( 2048UL, 2000UL )==2540191360UL ); -} - -static void -test_wait_to_vote_slot( void ) { - fd_tower_t * tower = fd_tower_join( fd_tower_new( scratch, 2UL, 2UL, 0UL ) ); - FD_TEST( tower ); - tower->root = 100UL; - FD_TEST( lockout_check( tower, 101UL ) ); - - tower->wait_to_vote_slot = 103UL; - FD_TEST( !lockout_check( tower, 101UL ) ); - FD_TEST( !lockout_check( tower, 102UL ) ); - FD_TEST( lockout_check( tower, 103UL ) ); - - fd_tower_delete( fd_tower_leave( tower ) ); -} - -static void -test_compact_stake_indices( void ) { - fd_tower_t * tower = fd_tower_join( fd_tower_new( scratch, 2UL, 2UL, 0UL ) ); - FD_TEST( tower ); - - fd_hash_t vote_acc0 = { .ul = { 1UL } }; - fd_hash_t vote_acc1 = { .ul = { 2UL } }; - - ulong idx0 = fd_tower_stakes_insert( tower, 1UL, &vote_acc0, 10UL, ULONG_MAX ); - ulong idx1 = fd_tower_stakes_insert( tower, 1UL, &vote_acc1, 20UL, idx0 ); - - FD_TEST( tower->stk_vtr_pool[ idx0 ].prev==UINT_MAX ); - FD_TEST( tower->stk_vtr_pool[ idx1 ].prev==(uint)idx0 ); - - fd_tower_stakes_remove( tower, 1UL ); - FD_TEST( !fd_tower_stakes_vtr_pool_used( tower->stk_vtr_pool ) ); -} - -void -mock( fd_ghost_t * ghost, - fd_tower_blk_t * blk, - ulong bank_idx, - fd_hash_t * replayed_block_id, - fd_hash_t * parent_block_id ) { - blk->epoch = 1; - blk->replayed = 1; - blk->replayed_block_id = *replayed_block_id; - if( FD_UNLIKELY( !parent_block_id ) ) { - FD_TEST( fd_ghost_init( ghost, bank_idx, blk->slot, replayed_block_id ) ); - } else { - FD_TEST( fd_ghost_insert( ghost, bank_idx, blk->slot, replayed_block_id, parent_block_id ) ); - } -} - -void -test_vote( void ) { - fd_tower_t * tower = fd_tower_join( fd_tower_new( scratch, 2, 2, 0 ) ); - FD_TEST( tower ); - - /* Add some votes to the tower - - (0, 31) expiration = 0 + 1<<31 - (1, 30) expiration = 1 + 1<<30 - (2, 29) expiration = 2 + 1<<29 - .. - (28, 3) expiration = 28 + 1<<3 = 36 - (29, 2) expiration = 29 + 1<<2 = 33 - (30, 1) expiration = 30 + 1<<1 = 32 */ - - for( ulong i = 0; i < 31; i++ ) { - push_vote( tower, i ); - FD_TEST( fd_tower_vote_cnt( tower->votes ) == i + 1 ); - } - for( ulong i = 0; i < 31; i++ ) { - fd_tower_vote_t expected_vote = { .slot = i, .conf = 31 - i }; - fd_tower_vote_t const * actual_vote = fd_tower_vote_peek_index_const( tower->votes, i ); - FD_TEST( expected_vote.slot == actual_vote->slot ); - FD_TEST( expected_vote.conf == actual_vote->conf ); - } - - /* CASE 1: NEW VOTE WHICH REPLACES EXPIRED VOTE */ - - /* Test expiration - - A vote for 33 should make the vote for 30 expire. - A full tower has 31 votes. One expired vote => 30 remaining. */ - - ulong new_vote_expiry = 33; - ulong vote_cnt = simulate_vote( tower->votes, new_vote_expiry ); - FD_TEST( vote_cnt == 30 ); - - /* Test slots 1 through 30 are unchanged after voting */ - - push_vote( tower, new_vote_expiry ); - for( ulong i = 0; i < 30; i++ ) { - fd_tower_vote_t expected_vote = { .slot = i, .conf = 31 - i }; - fd_tower_vote_t const * actual_vote = fd_tower_vote_peek_index_const( tower->votes, i ); - FD_TEST( expected_vote.slot == actual_vote->slot ); - FD_TEST( expected_vote.conf == actual_vote->conf ); - } - - /* Check new vote */ - - fd_tower_vote_t expected_vote = { .slot = new_vote_expiry, .conf = 1 }; - fd_tower_vote_t const * actual_vote = fd_tower_vote_peek_index_const( tower->votes, 30 ); - FD_TEST( expected_vote.slot == actual_vote->slot ); - FD_TEST( expected_vote.conf == actual_vote->conf ); - - /* CASE 2: NEW VOTE WHICH PRODUCES NEW ROOT */ - - ulong new_vote_root = 34; - FD_TEST( push_vote( tower, new_vote_root ) == 0 ); - - /* Check all existing votes were repositioned one index lower and one - confirmation higher. */ - - for( ulong i = 0; i < 29 /* one of the original slots was rooted */; i++ ) { - fd_tower_vote_t expected_vote2 = { .slot = i + 1, .conf = 31 - i }; - fd_tower_vote_t const * actual_vote2 = fd_tower_vote_peek_index_const( tower->votes, i ); - FD_TEST( expected_vote2.slot == actual_vote2->slot ); - FD_TEST( expected_vote2.conf == actual_vote2->conf ); - } - - /* Check new vote in the tower. */ - - fd_tower_vote_t expected_vote_root = { .slot = new_vote_root, .conf = 1 }; - fd_tower_vote_t const * actual_vote_root = fd_tower_vote_peek_index_const( tower->votes, 30 ); - FD_TEST( expected_vote_root.slot == actual_vote_root->slot ); - FD_TEST( expected_vote_root.conf == actual_vote_root->conf ); -} - -static void -test_verify( void ) { - fd_tower_t * tower = fd_tower_join( fd_tower_new( scratch, 2UL, 2UL, 0UL ) ); - FD_TEST( tower ); - - /* Full towers and gaps in confirmation counts are valid. */ - - for( ulong i=0UL; ivotes, (fd_tower_vote_t){ .slot=i, .conf=FD_TOWER_VOTE_MAX-i } ); - FD_TEST( !fd_tower_verify( tower ) ); - - fd_tower_vote_remove_all( tower->votes ); - fd_tower_vote_push_tail( tower->votes, (fd_tower_vote_t){ .slot=1UL, .conf=5UL } ); - fd_tower_vote_push_tail( tower->votes, (fd_tower_vote_t){ .slot=2UL, .conf=3UL } ); - fd_tower_vote_push_tail( tower->votes, (fd_tower_vote_t){ .slot=3UL, .conf=1UL } ); - FD_TEST( !fd_tower_verify( tower ) ); - - /* Slots must strictly increase. */ - - fd_tower_vote_remove_all( tower->votes ); - fd_tower_vote_push_tail( tower->votes, (fd_tower_vote_t){ .slot=2UL, .conf=2UL } ); - fd_tower_vote_push_tail( tower->votes, (fd_tower_vote_t){ .slot=2UL, .conf=1UL } ); - FD_TEST( fd_tower_verify( tower )==-1 ); - - fd_tower_vote_remove_all( tower->votes ); - fd_tower_vote_push_tail( tower->votes, (fd_tower_vote_t){ .slot=2UL, .conf=2UL } ); - fd_tower_vote_push_tail( tower->votes, (fd_tower_vote_t){ .slot=1UL, .conf=1UL } ); - FD_TEST( fd_tower_verify( tower )==-1 ); - - /* Confirmation counts must strictly decrease. */ - - fd_tower_vote_remove_all( tower->votes ); - fd_tower_vote_push_tail( tower->votes, (fd_tower_vote_t){ .slot=1UL, .conf=2UL } ); - fd_tower_vote_push_tail( tower->votes, (fd_tower_vote_t){ .slot=2UL, .conf=2UL } ); - FD_TEST( fd_tower_verify( tower )==-1 ); - - fd_tower_vote_remove_all( tower->votes ); - fd_tower_vote_push_tail( tower->votes, (fd_tower_vote_t){ .slot=1UL, .conf=1UL } ); - fd_tower_vote_push_tail( tower->votes, (fd_tower_vote_t){ .slot=2UL, .conf=2UL } ); - FD_TEST( fd_tower_verify( tower )==-1 ); -} - - -void -test_tower_from_vote_acc_data_v1_14_11( void ) { - fd_tower_t * tower = fd_tower_join( fd_tower_new( scratch, 2, 2, 0 ) ); - FD_TEST( tower ); - - fd_tower_from_vote_acc( tower->votes, &tower->root, vote_acc_v2, vote_acc_v2_sz ); - - fd_tower_vote_t expected_votes[31] = { - { 159175525, 31 }, - { 159175526, 30 }, - { 159175527, 29 }, - { 159175528, 28 }, - { 159175529, 27 }, - { 159175530, 26 }, - { 159175531, 25 }, - { 159175532, 24 }, - { 159175533, 23 }, - { 159175534, 22 }, - { 159175535, 21 }, - { 159175536, 20 }, - { 159175537, 19 }, - { 159175538, 18 }, - { 159175539, 17 }, - { 159175540, 16 }, - { 159175541, 15 }, - { 159175542, 14 }, - { 159175543, 13 }, - { 159175544, 12 }, - { 159175545, 11 }, - { 159175546, 10 }, - { 159175547, 9 }, - { 159175548, 8 }, - { 159175549, 7 }, - { 159175550, 6 }, - { 159175551, 5 }, - { 159175552, 4 }, - { 159175553, 3 }, - { 159175554, 2 }, - { 159175555, 1 }, - }; - - FD_TEST( fd_tower_vote_cnt( tower->votes ) == 31UL ); - ulong expected_idx = 0UL; - for( fd_tower_vote_iter_t iter = fd_tower_vote_iter_init( tower->votes ); - !fd_tower_vote_iter_done( tower->votes, iter ); - iter = fd_tower_vote_iter_next( tower->votes, iter ) ) { - fd_tower_vote_t const * actual_vote = fd_tower_vote_iter_ele_const( tower->votes, iter ); - fd_tower_vote_t * expected_vote = &expected_votes[ expected_idx++ ]; - FD_TEST( expected_vote->slot == actual_vote->slot ); - FD_TEST( expected_vote->conf == actual_vote->conf ); - } -} - -void -test_tower_from_vote_acc_data_current( void ) { - fd_tower_t * tower = fd_tower_join( fd_tower_new( scratch, 2, 2, 0 ) ); - FD_TEST( tower ); - - fd_tower_from_vote_acc( tower->votes, &tower->root, vote_acc_v3, vote_acc_v3_sz ); - - fd_tower_vote_t expected_votes[31] = { - { 285373759, 31 }, - { 285373760, 30 }, - { 285373761, 29 }, - { 285373762, 28 }, - { 285373763, 27 }, - { 285373764, 26 }, - { 285373765, 25 }, - { 285373766, 24 }, - { 285373767, 23 }, - { 285373768, 22 }, - { 285373769, 21 }, - { 285373770, 20 }, - { 285373771, 19 }, - { 285373772, 18 }, - { 285373773, 17 }, - { 285373780, 16 }, - { 285373781, 15 }, - { 285373782, 14 }, - { 285373783, 13 }, - { 285373784, 12 }, - { 285373785, 11 }, - { 285373786, 10 }, - { 285373787, 9 }, - { 285373788, 8 }, - { 285373789, 7 }, - { 285373790, 6 }, - { 285373791, 5 }, - { 285373792, 4 }, - { 285373793, 3 }, - { 285373794, 2 }, - { 285373795, 1 }, - }; - - FD_TEST( fd_tower_vote_cnt( tower->votes ) == 31UL ); - ulong expected_idx = 0UL; - for( fd_tower_vote_iter_t iter = fd_tower_vote_iter_init( tower->votes ); - !fd_tower_vote_iter_done( tower->votes, iter ); - iter = fd_tower_vote_iter_next( tower->votes, iter ) ) { - fd_tower_vote_t const * actual_vote = fd_tower_vote_iter_ele_const( tower->votes, iter ); - fd_tower_vote_t * expected_vote = &expected_votes[ expected_idx++ ]; - FD_TEST( expected_vote->slot == actual_vote->slot ); - FD_TEST( expected_vote->conf == actual_vote->conf ); - } -} - -void -mock_vote_acc( fd_hash_t const * pubkey, ulong stake, ulong vote, uint conf, fd_tower_vtr_t * out, fd_tower_vote_t * votes_mem ) { - fd_vote_acc_t voter = { - .kind = FD_VOTE_ACC_V3, - .v3 = { - .node_pubkey = *pubkey, - .votes_cnt = 1, - .votes = { - { .slot = vote, .conf = conf }, - }, - } - }; - - fd_tower_vote_remove_all( votes_mem ); - fd_tower_from_vote_acc( votes_mem, &out->root, (uchar const *)&voter, sizeof(fd_vote_acc_t) ); - out->votes = votes_mem; - out->stake = stake; - out->vote_acc = *pubkey; -} - -void -test_to_vote_txn( fd_wksp_t * wksp ) { - fd_txn_p_t txnp[1]; - - void * tower_mem = fd_wksp_alloc_laddr( wksp, fd_tower_align(), fd_tower_footprint( 2, 2 ), 1UL ); - fd_tower_t * tower = fd_tower_join( fd_tower_new( tower_mem, 2, 2, 0 ) ); - for ( ulong i = 1; i <= 31; i++ ) { - push_vote( tower, i ); - } - tower->root = 1; - fd_hash_t bank_hash = { .ul = { 1 } }; - fd_hash_t block_id = { .ul = { 1 } }; - fd_hash_t recent_blockhash = { .ul = { 1 } }; - fd_pubkey_t validator_identity = { .ul = { 2 } }; - fd_pubkey_t * vote_authority = &validator_identity; - fd_pubkey_t vote_acc = { .ul = { 3 } }; - fd_tower_to_vote_txn( tower, &bank_hash, &block_id, &recent_blockhash, &validator_identity, vote_authority, &vote_acc, txnp ); - - FD_TEST( txnp->payload_sz && txnp->payload_sz<=FD_TPU_MTU ); - - /* Check we can parse our own txn and validate its a vote txn. */ - - uchar txn_mem[FD_TXN_MAX_SZ]; - ulong parse_result = fd_txn_parse_core( txnp->payload, txnp->payload_sz, txn_mem, NULL, NULL ); - FD_TEST( parse_result > 0UL ); - fd_txn_t const * txn = (fd_txn_t *)txn_mem; - FD_TEST( fd_txn_is_simple_vote_transaction( txn, txnp->payload ) ); - - /* Check we can deserialize the txn into a CompactTowerSync serde. */ - - fd_compact_tower_sync_serde_t compact_tower_sync_serde; - - fd_txn_instr_t const * instr = &txn->instr[0]; - uchar const * instr_data = txnp->payload + instr->data_off; - uint kind = fd_uint_load_4_fast( instr_data ); - FD_TEST( kind == FD_VOTE_IX_KIND_TOWER_SYNC ); - int err = fd_compact_tower_sync_de( &compact_tower_sync_serde, instr_data + sizeof(uint), instr->data_sz - sizeof(uint) ); - FD_TEST( err == 0 ); - FD_TEST( compact_tower_sync_serde.root == 1 ); - FD_TEST( compact_tower_sync_serde.lockouts_cnt == 31 ); - FD_TEST( compact_tower_sync_serde.timestamp_option == 1 ); - FD_TEST( 0==memcmp( &compact_tower_sync_serde.block_id, &block_id, sizeof(fd_hash_t) )); -} - -void -test_switch_simple( fd_wksp_t * wksp ) { - (void)scratch; - ulong blk_max = 64; - ulong voter_max = 16; - ulong total_stake = 100; - - void * tower_mem = fd_wksp_alloc_laddr( wksp, fd_tower_align(), fd_tower_footprint( blk_max, voter_max ), 1UL ); - void * ghost_mem = fd_wksp_alloc_laddr( wksp, fd_ghost_align(), fd_ghost_footprint( blk_max, voter_max ), 1UL ); - - fd_tower_t * tower = fd_tower_join ( fd_tower_new ( tower_mem, blk_max, voter_max, 0UL ) ); - fd_ghost_t * ghost = fd_ghost_join ( fd_ghost_new ( ghost_mem, blk_max, voter_max, 0UL ) ); - FD_TEST( tower ); - FD_TEST( tower ); - FD_TEST( ghost ); - tower->root = 1; - - push_vote( tower, 2 ); - - /* lets make a fork with - - 1 - / \ - / \ - 2 3 2 is last vote - | | - 4 5 - */ - - /* add all the executed slots to forks */ - mock( ghost, fd_tower_blocks_insert( tower, 1, ULONG_MAX ), 0, &(fd_hash_t){.ul = {1}}, NULL ); - mock( ghost, fd_tower_blocks_insert( tower, 2, 1 ), 1, &(fd_hash_t){.ul = {2}}, &(fd_hash_t){.ul = {1}} ); - mock( ghost, fd_tower_blocks_insert( tower, 3, 1 ), 2, &(fd_hash_t){.ul = {3}}, &(fd_hash_t){.ul = {1}} ); - mock( ghost, fd_tower_blocks_insert( tower, 4, 2 ), 3, &(fd_hash_t){.ul = {4}}, &(fd_hash_t){.ul = {2}} ); - mock( ghost, fd_tower_blocks_insert( tower, 5, 3 ), 4, &(fd_hash_t){.ul = {5}}, &(fd_hash_t){.ul = {3}} ); - - fd_tower_vtr_t acct; - uchar __attribute__((aligned(FD_TOWER_VOTE_ALIGN))) mock_tower_mem[ FD_TOWER_VOTE_FOOTPRINT ]; - fd_tower_vote_t * mock_tower = fd_tower_vote_join( fd_tower_vote_new( mock_tower_mem ) ); - - mock_vote_acc( &(fd_hash_t){.ul = {1}}, 10, 5, 1, &acct, mock_tower ); - fd_tower_lockos_insert( tower, 5, &acct.vote_acc, acct.votes ); - ulong prev = fd_tower_stakes_insert( tower, 5, &acct.vote_acc, acct.stake, ULONG_MAX ); - - mock_vote_acc( &(fd_hash_t){.ul = {2}}, 10, 5, 1, &acct, mock_tower ); - fd_tower_lockos_insert( tower, 5, &acct.vote_acc, acct.votes ); - prev = fd_tower_stakes_insert( tower, 5, &acct.vote_acc, acct.stake, prev ); - - mock_vote_acc( &(fd_hash_t){.ul = {3}}, 10, 5, 1, &acct, mock_tower ); - fd_tower_lockos_insert( tower, 5, &acct.vote_acc, acct.votes ); - prev = fd_tower_stakes_insert( tower, 5, &acct.vote_acc, acct.stake, prev ); - - mock_vote_acc( &(fd_hash_t){.ul = {4}}, 9, 5, 1, &acct, mock_tower ); - fd_tower_lockos_insert( tower, 5, &acct.vote_acc, acct.votes ); - prev = fd_tower_stakes_insert( tower, 5, &acct.vote_acc, acct.stake, prev ); - - FD_TEST( switch_check( tower, ghost, total_stake, 5 ) == 1 ); -} - -void -test_switch_threshold( fd_wksp_t * wksp ) { - (void)scratch; - ulong blk_max = 64; - ulong voter_max = 16; - ulong total_stake = 100; - - void * tower_mem = fd_wksp_alloc_laddr( wksp, fd_tower_align(), fd_tower_footprint( blk_max, voter_max ), 1UL ); - void * ghost_mem = fd_wksp_alloc_laddr( wksp, fd_ghost_align(), fd_ghost_footprint( blk_max, voter_max ), 1UL ); - - fd_tower_t * tower = fd_tower_join ( fd_tower_new ( tower_mem, blk_max, voter_max, 0UL) ); - fd_ghost_t * ghost = fd_ghost_join ( fd_ghost_new ( ghost_mem, blk_max, voter_max, 0UL ) ); - - FD_TEST( tower ); - FD_TEST( tower ); - FD_TEST( ghost ); - - /* create tower forks tree like this - // Create the tree of banks - let forks = tr(0) - / (tr(1) - / (tr(2) - // Minor fork 1 - / (tr(10) / (tr(11) / (tr(12) / (tr(13) / (tr(14)))))) - / (tr(43) - / (tr(44) - // Minor fork 2 - / (tr(45) / (tr(46) / (tr(47) / (tr(48) / (tr(49) / (tr(50))))))) - / (tr(110))) - / tr(112)))); - */ - - mock( ghost, fd_tower_blocks_insert( tower, 0, ULONG_MAX ), 0, &(fd_hash_t){.ul = {0}}, NULL ); - mock( ghost, fd_tower_blocks_insert( tower, 1, 0 ), 1, &(fd_hash_t){.ul = {1}}, &(fd_hash_t){.ul = {0}} ); - mock( ghost, fd_tower_blocks_insert( tower, 2, 1 ), 2, &(fd_hash_t){.ul = {2}}, &(fd_hash_t){.ul = {1}} ); - mock( ghost, fd_tower_blocks_insert( tower, 10, 2 ), 3, &(fd_hash_t){.ul = {10}}, &(fd_hash_t){.ul = {2}} ); - mock( ghost, fd_tower_blocks_insert( tower, 11, 10 ), 4, &(fd_hash_t){.ul = {11}}, &(fd_hash_t){.ul = {10}} ); - mock( ghost, fd_tower_blocks_insert( tower, 12, 11 ), 5, &(fd_hash_t){.ul = {12}}, &(fd_hash_t){.ul = {11}} ); - mock( ghost, fd_tower_blocks_insert( tower, 13, 12 ), 6, &(fd_hash_t){.ul = {13}}, &(fd_hash_t){.ul = {12}} ); - mock( ghost, fd_tower_blocks_insert( tower, 14, 13 ), 7, &(fd_hash_t){.ul = {14}}, &(fd_hash_t){.ul = {13}} ); - - mock( ghost, fd_tower_blocks_insert( tower, 43, 2 ), 8, &(fd_hash_t){.ul = {43}}, &(fd_hash_t){.ul = {2}} ); - mock( ghost, fd_tower_blocks_insert( tower, 44, 43 ), 9, &(fd_hash_t){.ul = {44}}, &(fd_hash_t){.ul = {43}} ); - mock( ghost, fd_tower_blocks_insert( tower, 45, 44 ), 10, &(fd_hash_t){.ul = {45}}, &(fd_hash_t){.ul = {44}} ); - mock( ghost, fd_tower_blocks_insert( tower, 46, 45 ), 11, &(fd_hash_t){.ul = {46}}, &(fd_hash_t){.ul = {45}} ); - mock( ghost, fd_tower_blocks_insert( tower, 47, 46 ), 12, &(fd_hash_t){.ul = {47}}, &(fd_hash_t){.ul = {46}} ); - mock( ghost, fd_tower_blocks_insert( tower, 48, 47 ), 13, &(fd_hash_t){.ul = {48}}, &(fd_hash_t){.ul = {47}} ); - mock( ghost, fd_tower_blocks_insert( tower, 49, 48 ), 14, &(fd_hash_t){.ul = {49}}, &(fd_hash_t){.ul = {48}} ); - mock( ghost, fd_tower_blocks_insert( tower, 50, 49 ), 15, &(fd_hash_t){.ul = {50}}, &(fd_hash_t){.ul = {49}} ); - - mock( ghost, fd_tower_blocks_insert( tower, 110, 44 ), 16, &(fd_hash_t){.ul = {110}}, &(fd_hash_t){.ul = {44}} ); - - mock( ghost, fd_tower_blocks_insert( tower, 112, 43 ), 17, &(fd_hash_t){.ul = {112}}, &(fd_hash_t){.ul = {43}} ); - - tower->root = 0; - - /* our last vote is 47 */ - push_vote( tower, 1 ); - push_vote( tower, 2 ); - push_vote( tower, 43 ); - push_vote( tower, 44 ); - push_vote( tower, 45 ); - push_vote( tower, 46 ); - push_vote( tower, 47 ); - - /* Pretend we want to switch to 110, which is the heaviest fork */ - - FD_TEST( switch_check( tower, ghost, total_stake, 110 ) == 0 ); - - fd_tower_vtr_t acct; - uchar __attribute__((aligned(FD_TOWER_VOTE_ALIGN))) mock_tower_mem[ FD_TOWER_VOTE_FOOTPRINT ]; - fd_tower_vote_t * mock_tower = fd_tower_vote_join( fd_tower_vote_new( mock_tower_mem ) ); - - mock_vote_acc( &(fd_hash_t){.ul = {1}}, 100, 49, 6, &acct, mock_tower ); /* interval is 49 -> 114 */ - fd_tower_lockos_insert( tower, 50, &acct.vote_acc, acct.votes ); - ulong prev = fd_tower_stakes_insert( tower, 50, &acct.vote_acc, acct.stake, ULONG_MAX ); - - /* Trying to switch to another fork at 110 should fail */ - FD_TEST( switch_check( tower, ghost, total_stake, 110 ) == 0 ); - - // Adding another validator lockout on an ancestor of last vote should - // not count toward the switch threshold - mock_vote_acc( &(fd_hash_t){.ul = {2}}, 100, 45, 6, &acct, mock_tower ); /* interval is 45 -> 109 */ - fd_tower_lockos_insert( tower, 50, &acct.vote_acc, acct.votes ); - prev = fd_tower_stakes_insert( tower, 50, &acct.vote_acc, acct.stake, prev ); - - FD_TEST( switch_check( tower, ghost, total_stake, 110 ) == 0 ); - - // Adding another validator lockout on a different fork, but the lockout - // doesn't cover the last vote, should not satisfy the switch threshold - - mock_vote_acc( &(fd_hash_t){.ul = {3}}, 100, 12, 5, &acct, mock_tower ); /* interval is 12 -> 44 */ - fd_tower_lockos_insert( tower, 14, &acct.vote_acc, acct.votes ); - prev = fd_tower_stakes_insert( tower, 14, &acct.vote_acc, acct.stake, ULONG_MAX ); - - FD_TEST( switch_check( tower, ghost, total_stake, 110 ) == 0 ); - - - // Adding another validator lockout on a different fork, and the lockout - // covers the last vote would count towards the switch threshold, - // unless the bank is not the most recent frozen bank on the fork (14 is a - // frozen/computed bank > 13 on the same fork in this case) - mock_vote_acc( &(fd_hash_t){.ul = {4}}, 100, 12, 6, &acct, mock_tower ); /* interval is 12 -> 76 */ - fd_tower_lockos_insert( tower, 13, &acct.vote_acc, acct.votes ); - fd_tower_stakes_insert( tower, 13, &acct.vote_acc, acct.stake, ULONG_MAX ); - - FD_TEST( switch_check( tower, ghost, total_stake, 110 ) == 0 ); - - // Adding another validator lockout on a different fork, and the lockout - // covers the last vote, should satisfy the switch threshold - - fd_tower_vote_push_head( tower->votes, (fd_tower_vote_t){.slot = 1, .conf = 32} ); // I NEED AN ARTIFICIAL ROOT, - - mock_vote_acc( &(fd_hash_t){.ul = {5}}, 39, 12, 6, &acct, mock_tower ); /* interval is 14 -> 76 */ - fd_tower_lockos_insert( tower, 14, &acct.vote_acc, acct.votes ); - prev = fd_tower_stakes_insert( tower, 14, &acct.vote_acc, acct.stake, prev ); - fd_tower_stakes_insert( tower, 110, &acct.vote_acc, acct.stake, ULONG_MAX ); - - FD_TEST( switch_check( tower, ghost, total_stake, 110 ) == 1 ); - /* Simulate adding a lockout */ -} - -void -test_switch_threshold_common_ancestor( fd_wksp_t * wksp ) { - (void)scratch; - ulong blk_max = 64; - ulong voter_max = 16; - ulong total_stake = 100; - - void * tower_mem = fd_wksp_alloc_laddr( wksp, fd_tower_align(), fd_tower_footprint( blk_max, voter_max ), 1UL ); - void * ghost_mem = fd_wksp_alloc_laddr( wksp, fd_ghost_align(), fd_ghost_footprint( blk_max, voter_max ), 1UL ); - - fd_tower_t * tower = fd_tower_join ( fd_tower_new ( tower_mem, blk_max, voter_max, 0UL) ); - fd_ghost_t * ghost = fd_ghost_join ( fd_ghost_new ( ghost_mem, blk_max, voter_max, 0UL ) ); - - FD_TEST( tower ); - FD_TEST( tower ); - FD_TEST( ghost ); - - // Create the tree of banks - // /- 50 - // /- 51 /- 45 - 46 - 47 - 48 - 49 - // 0 - 1 - 2 - 43 - 44 - // \- 110 - 111 - 112 - // \- 113 - - mock( ghost, fd_tower_blocks_insert( tower, 0, ULONG_MAX ), 0, &(fd_hash_t){.ul = {0}}, NULL ); - mock( ghost, fd_tower_blocks_insert( tower, 1, 0 ), 1, &(fd_hash_t){.ul = {1}}, &(fd_hash_t){.ul = {0}} ); - mock( ghost, fd_tower_blocks_insert( tower, 2, 1 ), 2, &(fd_hash_t){.ul = {2}}, &(fd_hash_t){.ul = {1}} ); - mock( ghost, fd_tower_blocks_insert( tower, 43, 2 ), 3, &(fd_hash_t){.ul = {43}}, &(fd_hash_t){.ul = {2}} ); - mock( ghost, fd_tower_blocks_insert( tower, 44, 43 ), 4, &(fd_hash_t){.ul = {44}}, &(fd_hash_t){.ul = {43}} ); - mock( ghost, fd_tower_blocks_insert( tower, 45, 44 ), 5, &(fd_hash_t){.ul = {45}}, &(fd_hash_t){.ul = {44}} ); - mock( ghost, fd_tower_blocks_insert( tower, 46, 45 ), 6, &(fd_hash_t){.ul = {46}}, &(fd_hash_t){.ul = {45}} ); - mock( ghost, fd_tower_blocks_insert( tower, 47, 46 ), 7, &(fd_hash_t){.ul = {47}}, &(fd_hash_t){.ul = {46}} ); - mock( ghost, fd_tower_blocks_insert( tower, 48, 47 ), 8, &(fd_hash_t){.ul = {48}}, &(fd_hash_t){.ul = {47}} ); - mock( ghost, fd_tower_blocks_insert( tower, 49, 48 ), 9, &(fd_hash_t){.ul = {49}}, &(fd_hash_t){.ul = {48}} ); - - mock( ghost, fd_tower_blocks_insert( tower, 50, 48 ), 10, &(fd_hash_t){.ul = {50}}, &(fd_hash_t){.ul = {48}} ); - - mock( ghost, fd_tower_blocks_insert( tower, 51, 2 ), 11, &(fd_hash_t){.ul = {51}}, &(fd_hash_t){.ul = {2}} ); - - mock( ghost, fd_tower_blocks_insert( tower, 110, 44 ), 11, &(fd_hash_t){.ul = {110}}, &(fd_hash_t){.ul = {44}} ); - mock( ghost, fd_tower_blocks_insert( tower, 111, 110 ), 12, &(fd_hash_t){.ul = {111}}, &(fd_hash_t){.ul = {110}} ); - mock( ghost, fd_tower_blocks_insert( tower, 112, 111 ), 13, &(fd_hash_t){.ul = {112}}, &(fd_hash_t){.ul = {111}} ); - - mock( ghost, fd_tower_blocks_insert( tower, 113, 44 ), 14, &(fd_hash_t){.ul = {113}}, &(fd_hash_t){.ul = {44}} ); - - tower->root = 0; - - // 43 -> 49 is our tower - push_vote( tower, 43 ); - push_vote( tower, 44 ); - push_vote( tower, 45 ); - push_vote( tower, 46 ); - push_vote( tower, 47 ); - push_vote( tower, 48 ); - push_vote( tower, 49 ); - - /* for some reason in these tests through black magic, agave tower root - is still 0. So I will manually set the root to 1 */ - fd_tower_vote_push_head( tower->votes, (fd_tower_vote_t){.slot = 1, .conf = 32} ); - - // Candidate slot 50 should *not* work - //vote_simulator.simulate_lockout_interval(50, (10, 49), &other_vote_acc); - fd_tower_vtr_t acct; - uchar __attribute__((aligned(FD_TOWER_VOTE_ALIGN))) mock_tower_mem[ FD_TOWER_VOTE_FOOTPRINT ]; - fd_tower_vote_t * mock_tower = fd_tower_vote_join( fd_tower_vote_new( mock_tower_mem ) ); - mock_vote_acc( &(fd_hash_t){.ul = {1}}, 100, 10, 6, &acct, mock_tower ); - fd_tower_lockos_insert( tower, 50, &acct.vote_acc, acct.votes ); - fd_tower_stakes_insert( tower, 50, &acct.vote_acc, acct.stake, ULONG_MAX ); - fd_tower_stakes_insert( tower, 111, &acct.vote_acc, acct.stake, ULONG_MAX ); // the switch slot - - FD_TEST( switch_check( tower, ghost, total_stake, 111 ) == 0 ); - - // 51, 111, 112, and 113 are all valid - - fd_tower_lockos_insert( tower, 51, &acct.vote_acc, acct.votes ); - fd_tower_stakes_insert( tower, 51, &acct.vote_acc, acct.stake, ULONG_MAX ); - FD_TEST( switch_check( tower, ghost, total_stake, 111 ) == 1 ); - fd_tower_lockos_remove( tower, 51 ); - - fd_tower_lockos_insert( tower, 112, &acct.vote_acc, acct.votes ); - fd_tower_stakes_insert( tower, 112, &acct.vote_acc, acct.stake, ULONG_MAX ); - FD_TEST( switch_check( tower, ghost, total_stake, 111 ) == 1 ); - fd_tower_lockos_remove( tower, 112 ); - - fd_tower_lockos_insert( tower, 113, &acct.vote_acc, acct.votes ); - fd_tower_stakes_insert( tower, 113, &acct.vote_acc, acct.stake, ULONG_MAX ); - FD_TEST( switch_check( tower, ghost, total_stake, 111 ) == 1 ); - fd_tower_lockos_remove( tower, 113 ); -} - -void -test_switch_eqvoc( fd_wksp_t * wksp ) { - (void)scratch; - ulong blk_max = 64; - ulong voter_max = 16; - ulong total_stake = 100; - - void * tower_mem = fd_wksp_alloc_laddr( wksp, fd_tower_align(), fd_tower_footprint( blk_max, voter_max ), 1UL ); - void * ghost_mem = fd_wksp_alloc_laddr( wksp, fd_ghost_align(), fd_ghost_footprint( blk_max, voter_max ), 1UL ); - - fd_tower_t * tower = fd_tower_join ( fd_tower_new ( tower_mem, blk_max, voter_max, 0UL) ); - fd_ghost_t * ghost = fd_ghost_join ( fd_ghost_new ( ghost_mem, blk_max, voter_max, 0UL ) ); - - FD_TEST( tower ); - FD_TEST( tower ); - FD_TEST( ghost ); - - /* - 7 (switch slot) - / - 1 - 2 - 3 - 5 (last vote) - \ - 6 - 8 - - Only 8 should be a candidate here. - - Now let's say 6 was actually equivocating, and 6' was the correct version. - - 7 (switch slot) - / - 1 - 2 - 3 - 5 (last vote) - \ \ - 6' 6 - 8 - - Now only 6' should be a candidate here */ - - mock( ghost, fd_tower_blocks_insert( tower, 1, ULONG_MAX ), 1, &(fd_hash_t){.ul = {1}}, NULL ); - mock( ghost, fd_tower_blocks_insert( tower, 2, 1 ), 2, &(fd_hash_t){.ul = {2}}, &(fd_hash_t){.ul = {1}} ); - mock( ghost, fd_tower_blocks_insert( tower, 3, 2 ), 3, &(fd_hash_t){.ul = {3}}, &(fd_hash_t){.ul = {2}} ); - mock( ghost, fd_tower_blocks_insert( tower, 5, 3 ), 4, &(fd_hash_t){.ul = {5}}, &(fd_hash_t){.ul = {3}} ); - mock( ghost, fd_tower_blocks_insert( tower, 6, 2 ), 5, &(fd_hash_t){.ul = {6}}, &(fd_hash_t){.ul = {2}} ); - mock( ghost, fd_tower_blocks_insert( tower, 8, 6 ), 6, &(fd_hash_t){.ul = {8}}, &(fd_hash_t){.ul = {6}} ); - mock( ghost, fd_tower_blocks_insert( tower, 7, 2 ), 7, &(fd_hash_t){.ul = {7}}, &(fd_hash_t){.ul = {2}} ); - - // 1 -> 5 is our tower - tower->root = 0; - push_vote( tower, 1 ); - push_vote( tower, 2 ); - push_vote( tower, 3 ); - push_vote( tower, 5 ); - - - fd_tower_vtr_t acct; - uchar __attribute__((aligned(FD_TOWER_VOTE_ALIGN))) mock_tower_mem[ FD_TOWER_VOTE_FOOTPRINT ]; - fd_tower_vote_t * mock_tower = fd_tower_vote_join( fd_tower_vote_new( mock_tower_mem ) ); - mock_vote_acc( &(fd_hash_t){.ul = {2}}, 100, 6, 6, &acct, mock_tower ); - fd_tower_stakes_insert( tower, 7, &acct.vote_acc, acct.stake, ULONG_MAX ); // the switch slot - - fd_tower_lockos_insert( tower, 8, &acct.vote_acc, acct.votes ); - fd_tower_stakes_insert( tower, 8, &acct.vote_acc, acct.stake, ULONG_MAX ); - FD_TEST( switch_check( tower, ghost, total_stake, 7 ) == 1 ); - - /* Now add 6' */ - fd_tower_blk_t * blk6 = fd_tower_blocks_query( tower, 6 ); - blk6->confirmed = 1; - blk6->confirmed_block_id = (fd_hash_t){.ul = {6, 1}}; - blk6->parent_slot = 1; - blk6->replayed_block_id = (fd_hash_t){.ul = {6, 1}}; - fd_ghost_insert( ghost, 6, 6, &(fd_hash_t){.ul = {6, 1}}, &(fd_hash_t){.ul = {1}} ); - - FD_TEST( switch_check( tower, ghost, total_stake, 7 ) == 0 ); /* would fail since 8 is not a candidate anymore */ - - /* add lockouts for 6', allow switching */ - fd_tower_lockos_insert( tower, 6, &acct.vote_acc, acct.votes ); - fd_tower_stakes_insert( tower, 6, &acct.vote_acc, acct.stake, ULONG_MAX ); - FD_TEST( switch_check( tower, ghost, total_stake, 7 ) == 1 ); -} - -void -test_switch_closed_vote_account( fd_wksp_t * wksp ) { - - /* Regression test: a vote account that is closed on the switch fork - but still active on a candidate fork should not crash switch_check. - - Fork tree: - - 100 (root) - | - 101 - / | \ - 102 108 105 - | | | - 103 109 106 <-- switch_slot (vote account ABC closed here) - | - 104 <-- our last vote - - Fork A (right): 105 -> 106. Vote account ABC is closed on this fork. - Fork B (left): 102 -> 103 -> 104. Our last vote. - Fork C (middle): 108 -> 109. Vote account ABC is active and voted - for slot 108 with lockout covering our vote slot. - - The lockout map for candidate_slot=109 contains ABC (from fork C), - but stk_vtr_map has no entry for (ABC, switch_slot=106) because ABC - was closed on fork A. switch_check should handle this gracefully - instead of hitting LOG_CRIT. */ - - (void)scratch; - ulong blk_max = 64; - ulong voter_max = 16; - ulong total_stake = 100; - - void * tower_mem = fd_wksp_alloc_laddr( wksp, fd_tower_align(), fd_tower_footprint( blk_max, voter_max ), 1UL ); - void * ghost_mem = fd_wksp_alloc_laddr( wksp, fd_ghost_align(), fd_ghost_footprint( blk_max, voter_max ), 1UL ); - - fd_tower_t * tower = fd_tower_join( fd_tower_new( tower_mem, blk_max, voter_max, 0UL ) ); - fd_ghost_t * ghost = fd_ghost_join( fd_ghost_new( ghost_mem, blk_max, voter_max, 0UL ) ); - FD_TEST( tower ); - FD_TEST( ghost ); - - mock( ghost, fd_tower_blocks_insert( tower, 100, ULONG_MAX ), 0, &(fd_hash_t){.ul = {100}}, NULL ); - mock( ghost, fd_tower_blocks_insert( tower, 101, 100 ), 1, &(fd_hash_t){.ul = {101}}, &(fd_hash_t){.ul = {100}} ); - - /* Fork B (our vote fork) */ - mock( ghost, fd_tower_blocks_insert( tower, 102, 101 ), 2, &(fd_hash_t){.ul = {102}}, &(fd_hash_t){.ul = {101}} ); - mock( ghost, fd_tower_blocks_insert( tower, 103, 102 ), 3, &(fd_hash_t){.ul = {103}}, &(fd_hash_t){.ul = {102}} ); - mock( ghost, fd_tower_blocks_insert( tower, 104, 103 ), 4, &(fd_hash_t){.ul = {104}}, &(fd_hash_t){.ul = {103}} ); - - /* Fork A (switch target -- ABC closed here) */ - mock( ghost, fd_tower_blocks_insert( tower, 105, 101 ), 5, &(fd_hash_t){.ul = {105}}, &(fd_hash_t){.ul = {101}} ); - mock( ghost, fd_tower_blocks_insert( tower, 106, 105 ), 6, &(fd_hash_t){.ul = {106}}, &(fd_hash_t){.ul = {105}} ); - - /* Fork C (ABC active here) */ - mock( ghost, fd_tower_blocks_insert( tower, 108, 101 ), 7, &(fd_hash_t){.ul = {108}}, &(fd_hash_t){.ul = {101}} ); - mock( ghost, fd_tower_blocks_insert( tower, 109, 108 ), 8, &(fd_hash_t){.ul = {109}}, &(fd_hash_t){.ul = {108}} ); - - /* Our tower: voted for 100 (root) and 104. vote_slot=104. */ - push_vote( tower, 100 ); - push_vote( tower, 104 ); - - fd_tower_vtr_t acct; - uchar __attribute__((aligned(FD_TOWER_VOTE_ALIGN))) mock_tower_mem[ FD_TOWER_VOTE_FOOTPRINT ]; - fd_tower_vote_t * mock_tower = fd_tower_vote_join( fd_tower_vote_new( mock_tower_mem ) ); - - /* ABC voted for slot 108 with conf=6, so lockout end = 108 + 64 = 172. - This lockout covers our vote_slot=104, so ABC is locked out from - voting for our vote slot (interval_slot=108 is not a descendant of - vote_slot=104, and 108 > root_slot=100). */ - mock_vote_acc( &(fd_hash_t){.ul = {0xABC}}, 50, 108, 6, &acct, mock_tower ); - - /* Insert lockout and stake for candidate_slot=109 (fork C, ABC active). - Do NOT insert stake for ABC on switch_slot=106 — ABC's vote account - was closed on fork A, so it was skipped in slot 106. */ - fd_tower_lockos_insert( tower, 109, &acct.vote_acc, acct.votes ); - fd_tower_stakes_insert( tower, 109, &acct.vote_acc, acct.stake, ULONG_MAX ); - - /* candidate_slot=109 (a leaf on fork C). switch_check then - queries stk_vtr_map for (ABC, switch_slot=106) which does not exist - because ABC was closed on fork A. Used to hit LOG_CRIT. */ - FD_TEST( switch_check( tower, ghost, total_stake, 106 ) == 0 ); - - FD_LOG_NOTICE(( "test_switch_closed_vote_account passed" )); -} - -void -test_case_1c_switch_pass( fd_wksp_t * wksp ) { - - /* Case 1c falling through to Case 3 (switch pass). - - Setup: prev vote is on a fork with an invalid ancestor (duplicate). - ghost_best is on a different fork (not an ancestor of prev_vote - and not a sibling-confirmed duplicate). The switch check passes - because enough stake is on a different fork. - - 1 (invalid / eqvoc) - / \ - 2 4 - 5 (ghost_best, most stake) - | - 3 (prev vote) - */ - - ulong blk_max = 64; - ulong voter_max = 16; - ulong total_stake = 100; - - void * tower_mem = fd_wksp_alloc_laddr( wksp, fd_tower_align(), fd_tower_footprint( blk_max, voter_max ), 1UL ); - void * ghost_mem = fd_wksp_alloc_laddr( wksp, fd_ghost_align(), fd_ghost_footprint( blk_max, voter_max ), 1UL ); - - fd_tower_t * tower = fd_tower_join( fd_tower_new( tower_mem, blk_max, voter_max, 0UL ) ); - fd_ghost_t * ghost = fd_ghost_join( fd_ghost_new( ghost_mem, blk_max, voter_max, 0UL ) ); - FD_TEST( tower ); - FD_TEST( ghost ); - - mock( ghost, fd_tower_blocks_insert( tower, 0, ULONG_MAX ), 0, &(fd_hash_t){.ul = {0}}, NULL ); - mock( ghost, fd_tower_blocks_insert( tower, 1, 0 ), 1, &(fd_hash_t){.ul = {1}}, &(fd_hash_t){.ul = {0}} ); - mock( ghost, fd_tower_blocks_insert( tower, 2, 1 ), 2, &(fd_hash_t){.ul = {2}}, &(fd_hash_t){.ul = {1}} ); - mock( ghost, fd_tower_blocks_insert( tower, 3, 2 ), 3, &(fd_hash_t){.ul = {3}}, &(fd_hash_t){.ul = {2}} ); - mock( ghost, fd_tower_blocks_insert( tower, 4, 0 ), 4, &(fd_hash_t){.ul = {4}}, &(fd_hash_t){.ul = {0}} ); - mock( ghost, fd_tower_blocks_insert( tower, 5, 4 ), 5, &(fd_hash_t){.ul = {5}}, &(fd_hash_t){.ul = {4}} ); - - tower->root = 0; - /* Mark slot 1 as a duplicate (invalid). This makes prev_vote's fork - have an invalid ancestor, triggering Case 1. */ - - fd_ghost_eqvoc( ghost, &(fd_hash_t){.ul = {1}} ); - - /* Give slot 5's fork lots of stake so ghost_best returns slot 5 and - the switch check passes. */ - - fd_ghost_query( ghost, &(fd_hash_t){.ul = {5}} )->stake = total_stake; - fd_ghost_query( ghost, &(fd_hash_t){.ul = {5}} )->total_stake = total_stake; - fd_ghost_query( ghost, &(fd_hash_t){.ul = {4}} )->total_stake = total_stake; - fd_ghost_query( ghost, &(fd_hash_t){.ul = {0}} )->total_stake = total_stake; - - /* Our tower: voted for slots 0 and 3. Prev vote is slot 3. - Must set voted and voted_block_id on the fork blocks so - fd_tower_vote_and_reset can look up prev_vote_blk in ghost. */ - - push_vote( tower, 3 ); - - fd_tower_blk_t * blk0 = fd_tower_blocks_query( tower, 0 ); - blk0->voted = 1; blk0->voted_block_id = (fd_hash_t){.ul = {0}}; - - fd_tower_blk_t * blk3 = fd_tower_blocks_query( tower, 3 ); - blk3->voted = 1; blk3->voted_block_id = (fd_hash_t){.ul = {3}}; - - /* Set up switch proof: a voter locked out on slot 5's fork covering - our last vote. This provides enough switch stake. */ - - fd_tower_vtr_t acct; - uchar __attribute__((aligned(FD_TOWER_VOTE_ALIGN))) mock_tower_mem[ FD_TOWER_VOTE_FOOTPRINT ]; - fd_tower_vote_t * mock_tower = fd_tower_vote_join( fd_tower_vote_new( mock_tower_mem ) ); - - mock_vote_acc( &(fd_hash_t){.ul = {1}}, total_stake, 5, 6, &acct, mock_tower ); - fd_tower_lockos_insert( tower, 5, &acct.vote_acc, acct.votes ); - fd_tower_stakes_insert( tower, 5, &acct.vote_acc, acct.stake, ULONG_MAX ); - - fd_tower_out_t out = { .vote_slot = ULONG_MAX, .root_slot = ULONG_MAX }; - out.flags = fd_tower_vote_and_reset( tower, ghost, NULL, - &out.reset_slot, &out.reset_block_id, &out.reset_bank_seq, - &out.vote_slot, &out.vote_block_id, &out.vote_bank_hash, - &out.root_slot, &out.root_block_id ); - - /* Should have SWITCH_PASS flag set (Case 1c → Case 3). */ - - FD_TEST( fd_uchar_extract_bit( out.flags, FD_TOWER_FLAG_SWITCH_PASS ) ); - - /* reset_blk should be ghost_best (slot 5). */ - - FD_TEST( out.reset_slot == 5 ); - - FD_LOG_NOTICE(( "test_case_1c_switch_pass passed" )); -} - -void -test_case_1c_switch_fail( fd_wksp_t * wksp ) { - - /* Case 1c falling through to Case 4a (switch fail, invalid ancestor). - - Setup: same fork structure as above, but no switch stake so the - switch check fails. Because invalid_ancestor is true, we go to - Case 4a and reset to fd_ghost_deepest from prev_vote_blk. - - 1 (invalid / eqvoc) - / \ - 2 4 - 5 (ghost_best, most stake) - | - 3 (prev vote) - */ - - ulong blk_max = 64; - ulong voter_max = 16; - ulong total_stake = 100; - - void * tower_mem = fd_wksp_alloc_laddr( wksp, fd_tower_align(), fd_tower_footprint( blk_max, voter_max ), 1UL ); - void * ghost_mem = fd_wksp_alloc_laddr( wksp, fd_ghost_align(), fd_ghost_footprint( blk_max, voter_max ), 1UL ); - - fd_tower_t * tower = fd_tower_join( fd_tower_new( tower_mem, blk_max, voter_max, 0UL ) ); - fd_ghost_t * ghost = fd_ghost_join( fd_ghost_new( ghost_mem, blk_max, voter_max, 0UL ) ); - FD_TEST( tower ); - FD_TEST( ghost ); - - mock( ghost, fd_tower_blocks_insert( tower, 0, ULONG_MAX ), 0, &(fd_hash_t){.ul = {0}}, NULL ); - mock( ghost, fd_tower_blocks_insert( tower, 1, 0 ), 1, &(fd_hash_t){.ul = {1}}, &(fd_hash_t){.ul = {0}} ); - mock( ghost, fd_tower_blocks_insert( tower, 2, 1 ), 2, &(fd_hash_t){.ul = {2}}, &(fd_hash_t){.ul = {1}} ); - mock( ghost, fd_tower_blocks_insert( tower, 3, 2 ), 3, &(fd_hash_t){.ul = {3}}, &(fd_hash_t){.ul = {2}} ); - mock( ghost, fd_tower_blocks_insert( tower, 4, 0 ), 4, &(fd_hash_t){.ul = {4}}, &(fd_hash_t){.ul = {0}} ); - mock( ghost, fd_tower_blocks_insert( tower, 5, 4 ), 5, &(fd_hash_t){.ul = {5}}, &(fd_hash_t){.ul = {4}} ); - - /* Mark slot 1 as a duplicate (invalid). */ - - fd_ghost_eqvoc( ghost, &(fd_hash_t){.ul = {1}} ); - - /* Give slot 5's fork lots of stake so ghost_best returns slot 5. */ - - fd_ghost_query( ghost, &(fd_hash_t){.ul = {5}} )->stake = total_stake; - fd_ghost_query( ghost, &(fd_hash_t){.ul = {5}} )->total_stake = total_stake; - fd_ghost_query( ghost, &(fd_hash_t){.ul = {4}} )->total_stake = total_stake; - fd_ghost_query( ghost, &(fd_hash_t){.ul = {0}} )->total_stake = total_stake; - - /* Our tower: voted for slots 0 and 3. Prev vote is slot 3. - Must set voted and voted_block_id on the fork blocks so - fd_tower_vote_and_reset can look up prev_vote_blk in ghost. */ - - push_vote( tower, 0 ); - push_vote( tower, 3 ); - - fd_tower_blk_t * blk0 = fd_tower_blocks_query( tower, 0 ); - blk0->voted = 1; blk0->voted_block_id = (fd_hash_t){.ul = {0}}; - - fd_tower_blk_t * blk3 = fd_tower_blocks_query( tower, 3 ); - blk3->voted = 1; blk3->voted_block_id = (fd_hash_t){.ul = {3}}; - - /* No switch stake set up — switch check will fail. */ - - fd_tower_out_t out = { .vote_slot = ULONG_MAX, .root_slot = ULONG_MAX }; - out.flags = fd_tower_vote_and_reset( tower, ghost, NULL, - &out.reset_slot, &out.reset_block_id, &out.reset_bank_seq, - &out.vote_slot, &out.vote_block_id, &out.vote_bank_hash, - &out.root_slot, &out.root_block_id ); - - /* Should have SWITCH_FAIL flag set (Case 1c → Case 4a). */ - - FD_TEST( fd_uchar_extract_bit( out.flags, FD_TOWER_FLAG_SWITCH_FAIL ) ); - - /* reset_blk should be fd_ghost_deepest from prev_vote_blk (slot 3). - Since slot 3 is a leaf, deepest from slot 3 is slot 3 itself. */ - - FD_TEST( out.reset_slot == 3 ); - - /* No vote should be cast. */ - - FD_TEST( out.vote_slot == ULONG_MAX ); - - FD_LOG_NOTICE(( "test_case_1c_switch_fail passed" )); -} - - - -void -test_vtr_valid_join( fd_wksp_t * wksp ) { - ulong vtr_max = 4; - - ulong blk_max = 2; - void * tower_mem = fd_wksp_alloc_laddr( wksp, fd_tower_align(), fd_tower_footprint( blk_max, vtr_max ), 1UL ); - fd_tower_t * tower = fd_tower_join( fd_tower_new( tower_mem, blk_max, vtr_max, 0 ) ); - FD_TEST( tower ); - - fd_pubkey_t pks[3] = { { .ul = { 1 } }, { .ul = { 2 } }, { .ul = { 3 } } }; - uchar data[FD_VOTE_STATE_DATA_MAX]; - for( ulong i = 0; i < 3; i++ ) { - memset( data, 0, sizeof(data) ); - fd_vote_acc_t * voter = (fd_vote_acc_t *)fd_type_pun( data ); - voter->kind = FD_VOTE_ACC_V3; - voter->v3.node_pubkey = pks[i]; - voter->v3.votes_cnt = 1; - voter->v3.votes[0] = (fd_vote_acc_vote_t){ .slot = i + 1, .conf = 1 }; - fd_tower_count_vote( tower, &pks[i], 100, data, sizeof(data) ); - } - - /* Every vtr in the deque must have a valid (non-NULL) votes join. */ - - ulong cnt = 0; - for( fd_tower_vtr_iter_t iter = fd_tower_vtr_iter_init( tower->vtrs ); - !fd_tower_vtr_iter_done( tower->vtrs, iter ); - iter = fd_tower_vtr_iter_next( tower->vtrs, iter ) ) { - fd_tower_vtr_t const * vtr = fd_tower_vtr_iter_ele_const( tower->vtrs, iter ); - FD_TEST( vtr->votes ); - FD_TEST( !fd_tower_vote_empty( vtr->votes ) ); - cnt++; - } - FD_TEST( cnt == 3 ); - - fd_wksp_free_laddr( fd_tower_delete( fd_tower_leave( tower ) ) ); -} - -/* test_reconcile_boot: on boot the local tower is empty with - root set to the snapshot slot. The on-chain tower (from the vote - account) has votes for slots after the snapshot. Reconcile should - overwrite the local tower with the on-chain votes and adopt the - on-chain root, backfilling voted_block_id from replayed_block_id. */ - -void -test_reconcile_boot( fd_wksp_t * wksp ) { - ulong blk_max = 64; - ulong vtr_max = 2; - - void * tower_mem = fd_wksp_alloc_laddr( wksp, fd_tower_align(), fd_tower_footprint( blk_max, vtr_max ), 1UL ); - fd_tower_t * tower = fd_tower_join( fd_tower_new( tower_mem, blk_max, vtr_max, 0UL ) ); - FD_TEST( tower ); - - ulong snapshot_slot = 100; - tower->root = snapshot_slot; - - /* Simulate replaying slots 100..105 after booting from a snapshot at - slot 100. The on-chain tower is read back from the vote account - after each replay. Each replay advances the on-chain state: - - After replaying 100 (snapshot): on-chain empty (no votes yet). - After replaying 101: on-chain has vote for 101. - After replaying 102: on-chain has votes for 101, 102. - ... - After replaying 105: on-chain has votes for 101..105, root 100. */ - - uchar __attribute__((aligned(FD_TOWER_VOTE_ALIGN))) onchain_mem[ FD_TOWER_VOTE_FOOTPRINT ]; - fd_tower_vote_t * onchain_votes = fd_tower_vote_join( fd_tower_vote_new( onchain_mem ) ); - - fd_tower_blk_t * blk; - - /* Replay slot 100 (snapshot slot). On-chain tower still empty at - this point — reconcile is a no-op. */ - - blk = fd_tower_blocks_insert( tower, 100, 99 ); - blk->replayed = 1; - blk->replayed_block_id = ( fd_hash_t ){ .ul = { 100 } }; - fd_tower_reconcile( tower, onchain_votes, ULONG_MAX ); - - /* Replay slot 101. On-chain vote account now shows vote for 101, - root 100. */ - - blk = fd_tower_blocks_insert( tower, 101, 100 ); - blk->replayed = 1; - blk->replayed_block_id = ( fd_hash_t ){ .ul = { 101 } }; - fd_tower_vote_push_tail( onchain_votes, (fd_tower_vote_t){ .slot = 101, .conf = 1 } ); - fd_tower_reconcile( tower, onchain_votes, 100 ); - - /* Replay slots 102..105. On-chain accumulates votes. */ - - for( ulong s = 102; s <= 105; s++ ) { - blk = fd_tower_blocks_insert( tower, s, s - 1 ); - blk->replayed = 1; - blk->replayed_block_id = ( fd_hash_t ){ .ul = { s } }; - fd_tower_vote_push_tail( onchain_votes, (fd_tower_vote_t){ .slot = s, .conf = 1 } ); - fd_tower_reconcile( tower, onchain_votes, 100 ); - } - - /* After final reconcile: on-chain tip (105) > local tip so local - tower is overwritten. Root stays at snapshot_slot (100) since - local_root (100) == onchain_root (100). */ - - FD_TEST( fd_tower_vote_peek_tail( tower->votes )->slot==105 ); - FD_TEST( fd_tower_vote_cnt( tower->votes )==5 ); - FD_TEST( tower->root==snapshot_slot ); - - /* Verify: voted and voted_block_id backfilled from replayed_block_id - for all on-chain vote slots. */ - - for( ulong s = 101; s <= 105; s++ ) { - blk = fd_tower_blocks_query( tower, s ); - FD_TEST( blk ); - FD_TEST( blk->voted == 1 ); - FD_TEST( 0 == memcmp( &blk->voted_block_id, &blk->replayed_block_id, sizeof( fd_hash_t ) ) ); - } - - fd_wksp_free_laddr( fd_tower_delete( fd_tower_leave( tower ) ) ); - FD_LOG_NOTICE(( "pass: test_reconcile_boot" )); -} - -/* test_reconcile_ha: in a HA setup the backup's local tower - may lag behind the primary's on-chain tower. Reconcile should - overwrite the backup's local tower with primary's tower that has - landed on-chain. - - Sub-cases: - 1. On-chain tip ahead, on-chain root ahead → adopt on-chain root - 2. On-chain tip ahead, local root ahead → keep local root, drop - on-chain votes <= local root - 3. On-chain tip <= local tip (no-op) */ - -void -test_reconcile_ha( fd_wksp_t * wksp ) { - ulong blk_max = 64; - ulong voter_max = 2; - - /* ---- Sub-case 1: on-chain root ahead of local root ---- */ - - { - void * tower_mem = fd_wksp_alloc_laddr( wksp, fd_tower_align(), fd_tower_footprint( blk_max, voter_max ), 1UL ); - fd_tower_t * tower = fd_tower_join( fd_tower_new( tower_mem, blk_max, voter_max, 0UL ) ); - FD_TEST( tower ); - - tower->root = 10; - - /* Local tower: votes for 11, 12. */ - - for( ulong s = 11; s <= 15; s++ ) { - fd_tower_blk_t * blk = fd_tower_blocks_insert( tower, s, s - 1 ); - blk->replayed = 1; - blk->replayed_block_id = (fd_hash_t){ .ul = { s } }; - } - push_vote( tower, 11 ); - push_vote( tower, 12 ); - - /* On-chain tower: votes for 13, 14, 15. Root = 12 (ahead of local - root 10). */ - - uchar __attribute__((aligned(FD_TOWER_VOTE_ALIGN))) onchain_mem[ FD_TOWER_VOTE_FOOTPRINT ]; - fd_tower_vote_t * onchain = fd_tower_vote_join( fd_tower_vote_new( onchain_mem ) ); - fd_tower_vote_push_tail( onchain, (fd_tower_vote_t){ .slot = 13, .conf = 3 } ); - fd_tower_vote_push_tail( onchain, (fd_tower_vote_t){ .slot = 14, .conf = 2 } ); - fd_tower_vote_push_tail( onchain, (fd_tower_vote_t){ .slot = 15, .conf = 1 } ); - - fd_tower_reconcile( tower, onchain, 12 ); - - /* On-chain root (12) > local root (10) → adopt on-chain root. */ - - FD_TEST( tower->root == 12 ); - - /* Local votes overwritten with on-chain votes. */ - - FD_TEST( fd_tower_vote_cnt( tower->votes ) == 3 ); - FD_TEST( fd_tower_vote_peek_head_const( tower->votes )->slot == 13 ); - FD_TEST( fd_tower_vote_peek_tail_const( tower->votes )->slot == 15 ); - - /* Backfilled: backup didn't vote for 13..15. */ - - for( ulong s = 13; s <= 15; s++ ) { - fd_tower_blk_t * blk = fd_tower_blocks_query( tower, s ); - FD_TEST( blk && blk->voted == 1 ); - FD_TEST( 0 == memcmp( &blk->voted_block_id, &blk->replayed_block_id, sizeof(fd_hash_t) ) ); - } - - fd_wksp_free_laddr( fd_tower_delete( fd_tower_leave( tower ) ) ); - } - - /* ---- Sub-case 2: local root ahead of on-chain root ---- - On-chain votes <= local root should be dropped. */ - - { - void * tower_mem = fd_wksp_alloc_laddr( wksp, fd_tower_align(), fd_tower_footprint( blk_max, voter_max ), 1UL ); - fd_tower_t * tower = fd_tower_join( fd_tower_new( tower_mem, blk_max, voter_max, 0UL ) ); - FD_TEST( tower ); - - tower->root = 20; - - for( ulong s = 18; s <= 25; s++ ) { - fd_tower_blk_t * blk = fd_tower_blocks_insert( tower, s, s - 1 ); - blk->replayed = 1; - blk->replayed_block_id = (fd_hash_t){ .ul = { s } }; - } - push_vote( tower, 21 ); - push_vote( tower, 22 ); - - /* On-chain: root 15, votes for 18..25. Tip 25 > local tip 22. */ - - uchar __attribute__((aligned(FD_TOWER_VOTE_ALIGN))) onchain_mem[ FD_TOWER_VOTE_FOOTPRINT ]; - fd_tower_vote_t * onchain = fd_tower_vote_join( fd_tower_vote_new( onchain_mem ) ); - for( ulong s = 18; s <= 25; s++ ) { - fd_tower_vote_push_tail( onchain, (fd_tower_vote_t){ .slot = s, .conf = 26 - s } ); - } - - fd_tower_reconcile( tower, onchain, 15 ); - - /* Local root kept (20 > 15). Votes 18..20 dropped. */ - - FD_TEST( tower->root == 20 ); - FD_TEST( fd_tower_vote_cnt( tower->votes ) == 5 ); - FD_TEST( fd_tower_vote_peek_head_const( tower->votes )->slot == 21 ); - FD_TEST( fd_tower_vote_peek_tail_const( tower->votes )->slot == 25 ); - - fd_wksp_free_laddr( fd_tower_delete( fd_tower_leave( tower ) ) ); - } - - /* ---- Sub-case 3: on-chain tip <= local tip (no-op) ---- */ - - { - void * tower_mem = fd_wksp_alloc_laddr( wksp, fd_tower_align(), fd_tower_footprint( blk_max, voter_max ), 1UL ); - fd_tower_t * tower = fd_tower_join( fd_tower_new( tower_mem, blk_max, voter_max, 0UL ) ); - FD_TEST( tower ); - - tower->root = 10; - - for( ulong s = 11; s <= 15; s++ ) { - fd_tower_blk_t * blk = fd_tower_blocks_insert( tower, s, s - 1 ); - blk->replayed = 1; - blk->replayed_block_id = (fd_hash_t){ .ul = { s } }; - } - push_vote( tower, 11 ); - push_vote( tower, 12 ); - push_vote( tower, 13 ); - push_vote( tower, 14 ); - push_vote( tower, 15 ); - - /* On-chain tower: votes for 11..13. Tip 13 <= local tip 15. */ - - uchar __attribute__((aligned(FD_TOWER_VOTE_ALIGN))) onchain_mem[ FD_TOWER_VOTE_FOOTPRINT ]; - fd_tower_vote_t * onchain = fd_tower_vote_join( fd_tower_vote_new( onchain_mem ) ); - fd_tower_vote_push_tail( onchain, (fd_tower_vote_t){ .slot = 11, .conf = 3 } ); - fd_tower_vote_push_tail( onchain, (fd_tower_vote_t){ .slot = 12, .conf = 2 } ); - fd_tower_vote_push_tail( onchain, (fd_tower_vote_t){ .slot = 13, .conf = 1 } ); - - fd_tower_reconcile( tower, onchain, 10 ); - - /* No-op: local tower unchanged. */ - - FD_TEST( tower->root == 10 ); - FD_TEST( fd_tower_vote_cnt( tower->votes ) == 5 ); - FD_TEST( fd_tower_vote_peek_tail_const( tower->votes )->slot == 15 ); - - fd_wksp_free_laddr( fd_tower_delete( fd_tower_leave( tower ) ) ); - } - - FD_LOG_NOTICE(( "pass: test_reconcile_ha" )); -} - -/* test_reconcile_ha_eqvoc: backup missed a vote for an equivocating - slot. The primary voted for block 3 but the backup only replayed - block 3' (the version that eventually gets duplicate-confirmed). - Reconcile should backfill voted_block_id with replayed_block_id (3'), - which is correct because the primary will converge to the DC block. - - 2 - / \ - 3 3' (confirmed, replayed by backup) */ - -void -test_reconcile_ha_eqvoc( fd_wksp_t * wksp ) { - ulong blk_max = 64; - ulong voter_max = 2; - - void * tower_mem = fd_wksp_alloc_laddr( wksp, fd_tower_align(), fd_tower_footprint( blk_max, voter_max ), 1UL ); - fd_tower_t * tower = fd_tower_join( fd_tower_new( tower_mem, blk_max, voter_max, 0UL ) ); - FD_TEST( tower ); - - /* Backup: root 2, no votes. Replayed slot 3 as block 3' - (block_id = {3, 0xA}), not block 3 ({3}). */ - - tower->root = 2; - - fd_tower_blk_t * blk3 = fd_tower_blocks_insert( tower, 3, 2 ); - blk3->replayed = 1; - blk3->replayed_block_id = (fd_hash_t){ .ul = { 3, 0xA } }; - - /* On-chain (primary's): root 2, one vote for slot 3. */ - - uchar __attribute__((aligned(FD_TOWER_VOTE_ALIGN))) onchain_mem[ FD_TOWER_VOTE_FOOTPRINT ]; - fd_tower_vote_t * onchain = fd_tower_vote_join( fd_tower_vote_new( onchain_mem ) ); - fd_tower_vote_push_tail( onchain, (fd_tower_vote_t){ .slot = 3, .conf = 1 } ); - - fd_tower_reconcile( tower, onchain, 2 ); - - FD_TEST( tower->root == 2 ); - FD_TEST( fd_tower_vote_cnt( tower->votes ) == 1 ); - FD_TEST( fd_tower_vote_peek_head_const( tower->votes )->slot == 3 ); - - /* Backfilled voted_block_id from replayed_block_id (3', not 3). */ - - FD_TEST( blk3->voted == 1 ); - FD_TEST( blk3->voted_block_id.ul[1] == 0xA ); - - fd_wksp_free_laddr( fd_tower_delete( fd_tower_leave( tower ) ) ); - FD_LOG_NOTICE(( "pass: test_reconcile_ha_eqvoc" )); -} - -int -main( int argc, char ** argv ) { - fd_boot( &argc, &argv ); - - char const * _page_sz = fd_env_strip_cmdline_cstr ( &argc, &argv, "--page-sz", NULL, "gigantic" ); - ulong page_cnt = fd_env_strip_cmdline_ulong( &argc, &argv, "--page-cnt", NULL, 1UL ); - ulong numa_idx = fd_env_strip_cmdline_ulong( &argc, &argv, "--numa-idx", NULL, fd_shmem_numa_idx( 0UL ) ); - fd_wksp_t * wksp = fd_wksp_new_anonymous( fd_cstr_to_shmem_page_sz( _page_sz ), page_cnt, fd_shmem_cpu_idx( numa_idx ), "wksp", 0UL ); - FD_TEST( wksp ); - - test_compact_indices(); - test_compact_stake_indices(); - test_wait_to_vote_slot(); - test_vote(); - test_verify(); - test_tower_from_vote_acc_data_v1_14_11(); - test_tower_from_vote_acc_data_current(); - test_to_vote_txn( wksp ); - - test_switch_simple( wksp ); - test_switch_threshold( wksp ); - test_switch_threshold_common_ancestor( wksp ); - - - test_switch_eqvoc( wksp ); - - test_switch_closed_vote_account( wksp ); - - test_case_1c_switch_pass( wksp ); - test_case_1c_switch_fail( wksp ); - - test_reconcile_boot( wksp ); - test_reconcile_ha( wksp ); - test_reconcile_ha_eqvoc( wksp ); - - test_vtr_valid_join( wksp ); - - fd_halt(); - return 0; -} diff --git a/src/choreo/tower/test_tower_lockos.c b/src/choreo/tower/test_tower_lockos.c deleted file mode 100644 index 31795a3304a..00000000000 --- a/src/choreo/tower/test_tower_lockos.c +++ /dev/null @@ -1,321 +0,0 @@ -#include "fd_tower.c" - -FD_STATIC_ASSERT( sizeof(lockout_interval_key_t)==8UL, lockout_interval_key_compact ); -FD_STATIC_ASSERT( sizeof(lockout_interval_t )==16UL, lockout_interval_compact ); -FD_STATIC_ASSERT( alignof(lockout_interval_t)==4UL, lockout_interval_align ); -FD_STATIC_ASSERT( sizeof(lockout_slot_t)==12UL, lockout_slot_compact ); - -void -mock_vote_acc( fd_hash_t const * pubkey, ulong stake, ulong vote, uint conf, fd_tower_vtr_t * out, fd_tower_vote_t * votes_mem ) { - fd_vote_acc_t voter = { - .kind = FD_VOTE_ACC_V3, - .v3 = { - .node_pubkey = *pubkey, - .votes_cnt = 1, - .votes = { - { .slot = vote, .conf = conf }, - }, - } - }; - - fd_tower_vote_remove_all( votes_mem ); - fd_tower_from_vote_acc( votes_mem, &out->root, (uchar const *)&voter, sizeof(fd_vote_acc_t) ); - out->votes = votes_mem; - out->stake = stake; - out->vote_acc = *pubkey; -} - -static lockout_pubkey_ref_t * -pubkey_ref_query( fd_tower_t * tower, fd_pubkey_t const * addr ) { - return lockout_pubkey_map_ele_query( tower->lck_pubkey_map, addr, NULL, tower->lck_pubkey_pool ); -} - -static ulong -representative_cnt( fd_tower_t * tower, - uint slot ) { - lockout_slot_t const * lockout_slot = lockout_slot_map_ele_query_const( tower->lck_slot_map, &slot, NULL, tower->lck_slot_pool ); - if( !lockout_slot ) return 0UL; - - ulong slot_idx = lockout_slot_pool_idx( tower->lck_slot_pool, lockout_slot ); - ulong cnt = 0UL; - for( lockout_interval_t const * representative = lockout_interval_pool_ele_const( tower->lck_pool, lockout_slot->end_head ); - representative; - representative = lockout_interval_pool_ele_const( tower->lck_pool, representative->end_next ) ) { - FD_TEST( lockout_interval_key_slot_idx( &representative->key )==slot_idx ); - FD_TEST( ++cnt<=lockout_interval_pool_used( tower->lck_pool ) ); - } - return cnt; -} - -static void -test_lockout_interval_encoding( void ) { - for( uint conf=1U; conf<=31U; conf++ ) { - uint start = 0xf0000000U + conf; - uint end = start + (1U << conf); - lockout_interval_key_t key = lockout_interval_key( 8191UL, end, conf, 16383UL ); - lockout_interval_t interval = { .key = key }; - FD_TEST( lockout_interval_key_slot_idx ( &key )==8191U ); - FD_TEST( lockout_interval_key_conf ( &key )==conf ); - FD_TEST( lockout_interval_key_pubkey_idx( &key )==16383U ); - FD_TEST( lockout_interval_start ( &interval )==start ); - } - - lockout_interval_key_t key1 = lockout_interval_key( 7UL, 100U, 1U, 1UL ); - lockout_interval_key_t key31 = lockout_interval_key( 7UL, 100U, 31U, 16383UL ); - FD_TEST( lockout_interval_map_key_eq ( &key1, &key31 ) ); - FD_TEST( lockout_interval_map_key_hash( &key1, 0UL )==lockout_interval_map_key_hash( &key31, 0UL ) ); -} - -void -test_lockos( fd_wksp_t * wksp ) { - ulong slot_max = 64; - ulong voter_max = 16; - - void * tower_mem = fd_wksp_alloc_laddr( wksp, fd_tower_align(), fd_tower_footprint( slot_max, voter_max ), 1UL ); - fd_tower_t * tower = fd_tower_join( fd_tower_new( tower_mem, slot_max, voter_max, 0UL ) ); - - lockout_interval_map_t * lck_map = tower->lck_map; - lockout_interval_t * lck_pool = tower->lck_pool; - lockout_slot_map_t * lck_slot_map = tower->lck_slot_map; - lockout_slot_t * lck_slot_pool = tower->lck_slot_pool; - ulong interval_max = FD_TOWER_LOCKOS_MAX * slot_max * voter_max; - - FD_TEST( lockout_interval_pool_max( lck_pool )==interval_max ); - FD_TEST( lockout_slot_pool_max( lck_slot_pool )==slot_max ); - - uchar __attribute__((aligned(FD_TOWER_VOTE_ALIGN))) mock_votes_mem[ FD_TOWER_VOTE_FOOTPRINT ]; - fd_tower_vote_t * mock_votes = fd_tower_vote_join( fd_tower_vote_new( mock_votes_mem ) ); - - fd_tower_vtr_t acct; - fd_hash_t vote_acc = { .ul = { 1 } }; - fd_tower_lockos_insert( tower, 0UL, &vote_acc, mock_votes ); - FD_TEST( !lockout_slot_pool_used( lck_slot_pool ) ); - - ulong fork_slot = 1; - ulong end_intervals[31]; - for( ulong i = 1; i < 32; i++ ) { - ulong vote_slot = 50 - (i - 1); - mock_vote_acc( &vote_acc, 100, vote_slot, (uint)i, &acct, mock_votes ); - fd_tower_lockos_insert( tower, fork_slot, &acct.vote_acc, acct.votes ); - end_intervals[i - 1] = vote_slot + (1UL << (uint)i); - } - - uint fork_slot_key = (uint)fork_slot; - lockout_slot_t const * lockout_slot = lockout_slot_map_ele_query_const( lck_slot_map, &fork_slot_key, NULL, lck_slot_pool ); - FD_TEST( lockout_slot ); - ulong slot_idx = lockout_slot_pool_idx( lck_slot_pool, lockout_slot ); - - for( ulong i = 0; i < 31; i++ ) { - lockout_interval_key_t key = lockout_interval_key( slot_idx, end_intervals[i], 0U, 0UL ); - FD_TEST( lockout_interval_map_ele_query( lck_map, &key, NULL, lck_pool ) ); - } - - /* Verify the per-slot list contains every unique interval end. */ - - ulong num_keys = 0; - for( lockout_interval_t const * representative = lockout_interval_pool_ele_const( lck_pool, lockout_slot->end_head ); - representative; - representative = lockout_interval_pool_ele_const( lck_pool, representative->end_next ) ) { - ulong interval_end = representative->key.interval_end; - lockout_interval_key_t key = lockout_interval_key( slot_idx, interval_end, 0U, 0UL ); - num_keys++; - - /* Intervals are keyed by the end of the interval. */ - - ulong num_pubkeys = 0; - for( lockout_interval_t const * interval = lockout_interval_map_ele_query_const( lck_map, &key, NULL, lck_pool ); - interval; - interval = lockout_interval_map_ele_next_const( interval, NULL, lck_pool ) ) { - lockout_pubkey_ref_t const * ref = lockout_pubkey_pool_ele_const( tower->lck_pubkey_pool, lockout_interval_key_pubkey_idx( &interval->key ) ); - fd_pubkey_t const * resolved = &ref->addr; - FD_TEST( memcmp( resolved, &acct.vote_acc, sizeof(fd_hash_t) ) == 0 ); - num_pubkeys++; - } - FD_TEST( num_pubkeys == 1 ); - } - FD_TEST( num_keys == 31 ); - FD_TEST( representative_cnt( tower, fork_slot_key )==31UL ); - FD_TEST( lockout_slot_pool_used( lck_slot_pool )==1UL ); - - /* All 31 inserts used the same vote account with one vote each, so - the pubkey pool should hold a single entry with ref_cnt==31. */ - - lockout_pubkey_ref_t * ref = pubkey_ref_query( tower, &vote_acc ); - FD_TEST( ref ); - FD_TEST( ref->ref_cnt==31U ); - FD_TEST( lockout_pubkey_pool_free( tower->lck_pubkey_pool )==2UL*voter_max - 1UL ); - - - fd_tower_lockos_remove( tower, fork_slot ); - for( ulong i = 0; i < 31; i++ ) { - lockout_interval_key_t key = lockout_interval_key( slot_idx, end_intervals[i], 0U, 0UL ); - FD_TEST( !lockout_interval_map_ele_query( lck_map, &key, NULL, lck_pool ) ); - } - FD_TEST( !lockout_slot_map_ele_query( lck_slot_map, &fork_slot_key, NULL, lck_slot_pool ) ); - FD_TEST( !lockout_slot_pool_used( lck_slot_pool ) ); - FD_TEST( !lockout_interval_pool_used( lck_pool ) ); - - /* Zero-ref reclamation: pubkey entry is gone and free count restored. */ - FD_TEST( !pubkey_ref_query( tower, &vote_acc ) ); - FD_TEST( lockout_pubkey_pool_free( tower->lck_pubkey_pool )==2UL*voter_max ); -} - -void -test_lockos_pubkey_pool( fd_wksp_t * wksp ) { - ulong slot_max = 64; - ulong voter_max = 16; - - void * tower_mem = fd_wksp_alloc_laddr( wksp, fd_tower_align(), fd_tower_footprint( slot_max, voter_max ), 1UL ); - fd_tower_t * tower = fd_tower_join( fd_tower_new( tower_mem, slot_max, voter_max, 0UL ) ); - - uchar __attribute__((aligned(FD_TOWER_VOTE_ALIGN))) votes_mem_a[ FD_TOWER_VOTE_FOOTPRINT ]; - uchar __attribute__((aligned(FD_TOWER_VOTE_ALIGN))) votes_mem_b[ FD_TOWER_VOTE_FOOTPRINT ]; - fd_tower_vote_t * votes_a = fd_tower_vote_join( fd_tower_vote_new( votes_mem_a ) ); - fd_tower_vote_t * votes_b = fd_tower_vote_join( fd_tower_vote_new( votes_mem_b ) ); - - fd_tower_vtr_t acct_a; - fd_tower_vtr_t acct_b; - fd_hash_t pk_a = { .ul = { 11 } }; - fd_hash_t pk_b = { .ul = { 22 } }; - - mock_vote_acc( &pk_a, 100, 10, 1, &acct_a, votes_a ); - mock_vote_acc( &pk_b, 100, 10, 1, &acct_b, votes_b ); - - /* Same pubkey across two slots shares one pool entry; refs accumulate. */ - fd_tower_lockos_insert( tower, 1, &acct_a.vote_acc, acct_a.votes ); - fd_tower_lockos_insert( tower, 2, &acct_a.vote_acc, acct_a.votes ); - lockout_pubkey_ref_t * ref_a = pubkey_ref_query( tower, &pk_a ); - FD_TEST( ref_a ); - FD_TEST( ref_a->ref_cnt==2U ); - uint reused_idx = (uint)lockout_pubkey_pool_idx( tower->lck_pubkey_pool, ref_a ); - - uint slot_key1 = 1U; - uint slot_key2 = 2U; - lockout_slot_t const * slot1 = lockout_slot_map_ele_query_const( tower->lck_slot_map, &slot_key1, NULL, tower->lck_slot_pool ); - lockout_slot_t const * slot2 = lockout_slot_map_ele_query_const( tower->lck_slot_map, &slot_key2, NULL, tower->lck_slot_pool ); - FD_TEST( slot1 && slot2 ); - lockout_interval_key_t key1 = lockout_interval_key( lockout_slot_pool_idx( tower->lck_slot_pool, slot1 ), 10 + (1UL << 1), 0U, 0UL ); - lockout_interval_key_t key2 = lockout_interval_key( lockout_slot_pool_idx( tower->lck_slot_pool, slot2 ), 10 + (1UL << 1), 0U, 0UL ); - lockout_interval_t * iv1 = lockout_interval_map_ele_query( tower->lck_map, &key1, NULL, tower->lck_pool ); - lockout_interval_t * iv2 = lockout_interval_map_ele_query( tower->lck_map, &key2, NULL, tower->lck_pool ); - FD_TEST( iv1 && iv2 ); - FD_TEST( lockout_interval_key_pubkey_idx( &iv1->key )==reused_idx ); - FD_TEST( lockout_interval_key_pubkey_idx( &iv2->key )==reused_idx ); - uint slot1_representative_idx = slot1->end_head; - FD_TEST( lockout_interval_key_pubkey_idx( &lockout_interval_pool_ele_const( tower->lck_pool, slot1_representative_idx )->key )==reused_idx ); - - /* Distinct pubkey gets a distinct pool entry. */ - fd_tower_lockos_insert( tower, 1, &acct_b.vote_acc, acct_b.votes ); - lockout_pubkey_ref_t * ref_b = pubkey_ref_query( tower, &pk_b ); - FD_TEST( ref_b ); - FD_TEST( ref_b->ref_cnt==1U ); - FD_TEST( lockout_pubkey_pool_idx( tower->lck_pubkey_pool, ref_b )!=reused_idx ); - FD_TEST( slot1->end_head==slot1_representative_idx ); - FD_TEST( representative_cnt( tower, slot_key1 )==1UL ); - FD_TEST( representative_cnt( tower, slot_key2 )==1UL ); - - ulong interval_cnt = 0UL; - for( lockout_interval_t const * interval = lockout_interval_map_ele_query_const( tower->lck_map, &key1, NULL, tower->lck_pool ); - interval; - interval = lockout_interval_map_ele_next_const( interval, NULL, tower->lck_pool ) ) { - interval_cnt++; - } - FD_TEST( interval_cnt==2UL ); - - /* Removing one slot drops shared refs by one but keeps the entry. */ - fd_tower_lockos_remove( tower, 2 ); - ref_a = pubkey_ref_query( tower, &pk_a ); - FD_TEST( ref_a ); - FD_TEST( ref_a->ref_cnt==1U ); - FD_TEST( pubkey_ref_query( tower, &pk_b ) ); - FD_TEST( representative_cnt( tower, slot_key1 )==1UL ); - FD_TEST( representative_cnt( tower, slot_key2 )==0UL ); - - /* Removing the last references reclaims both entries. */ - fd_tower_lockos_remove( tower, 1 ); - FD_TEST( !pubkey_ref_query( tower, &pk_a ) ); - FD_TEST( !pubkey_ref_query( tower, &pk_b ) ); - FD_TEST( lockout_pubkey_pool_free( tower->lck_pubkey_pool )==2UL*voter_max ); - FD_TEST( representative_cnt( tower, slot_key1 )==0UL ); - - /* Index reuse must not leave a stale map entry. */ - mock_vote_acc( &pk_a, 100, 20, 1, &acct_a, votes_a ); - fd_tower_lockos_insert( tower, 3, &acct_a.vote_acc, acct_a.votes ); - ref_a = pubkey_ref_query( tower, &pk_a ); - FD_TEST( ref_a ); - FD_TEST( ref_a->ref_cnt==1U ); - FD_TEST( (uint)lockout_pubkey_pool_idx( tower->lck_pubkey_pool, ref_a )==reused_idx ); - FD_TEST( !memcmp( &ref_a->addr, &pk_a, sizeof(fd_pubkey_t) ) ); - uint slot_key3 = 3U; - lockout_slot_t const * slot3 = lockout_slot_map_ele_query_const( tower->lck_slot_map, &slot_key3, NULL, tower->lck_slot_pool ); - FD_TEST( slot3 ); - lockout_interval_key_t key3 = lockout_interval_key( lockout_slot_pool_idx( tower->lck_slot_pool, slot3 ), 20 + (1UL << 1), 0U, 0UL ); - lockout_interval_t const * iv3 = lockout_interval_map_ele_query_const( tower->lck_map, &key3, NULL, tower->lck_pool ); - FD_TEST( iv3 && lockout_interval_key_pubkey_idx( &iv3->key )==reused_idx ); - - fd_tower_lockos_remove( tower, 3 ); - FD_TEST( !pubkey_ref_query( tower, &pk_a ) ); -} - -static void -test_lockos_representative_teardown( fd_wksp_t * wksp ) { - ulong slot_max = 64UL; - ulong voter_max = 16UL; - - void * tower_mem = fd_wksp_alloc_laddr( wksp, fd_tower_align(), fd_tower_footprint( slot_max, voter_max ), 1UL ); - fd_tower_t * tower = fd_tower_join( fd_tower_new( tower_mem, slot_max, voter_max, 0UL ) ); - - uchar __attribute__((aligned(FD_TOWER_VOTE_ALIGN))) votes_mem_a[ FD_TOWER_VOTE_FOOTPRINT ]; - uchar __attribute__((aligned(FD_TOWER_VOTE_ALIGN))) votes_mem_b[ FD_TOWER_VOTE_FOOTPRINT ]; - fd_tower_vote_t * votes_a = fd_tower_vote_join( fd_tower_vote_new( votes_mem_a ) ); - fd_tower_vote_t * votes_b = fd_tower_vote_join( fd_tower_vote_new( votes_mem_b ) ); - - fd_hash_t pk_a = { .ul = { 31UL } }; - fd_hash_t pk_b = { .ul = { 32UL } }; - fd_tower_vtr_t acct_a; - fd_tower_vtr_t acct_b; - - mock_vote_acc( &pk_a, 100UL, 10UL, 1U, &acct_a, votes_a ); - mock_vote_acc( &pk_b, 100UL, 10UL, 1U, &acct_b, votes_b ); - fd_tower_lockos_insert( tower, 1UL, &acct_a.vote_acc, acct_a.votes ); - fd_tower_lockos_insert( tower, 1UL, &acct_b.vote_acc, acct_b.votes ); - - mock_vote_acc( &pk_a, 100UL, 20UL, 1U, &acct_a, votes_a ); - fd_tower_lockos_insert( tower, 1UL, &acct_a.vote_acc, acct_a.votes ); - - uint slot_key = 1U; - lockout_slot_t const * slot = lockout_slot_map_ele_query_const( tower->lck_slot_map, &slot_key, NULL, tower->lck_slot_pool ); - FD_TEST( slot ); - lockout_interval_t const * representative = lockout_interval_pool_ele_const( tower->lck_pool, slot->end_head ); - FD_TEST( representative->key.interval_end==22U ); - representative = lockout_interval_pool_ele_const( tower->lck_pool, representative->end_next ); - FD_TEST( representative->key.interval_end==12U ); - FD_TEST( representative_cnt( tower, slot_key )==2UL ); - FD_TEST( lockout_interval_pool_used( tower->lck_pool )==3UL ); - - fd_tower_lockos_remove( tower, 1UL ); - FD_TEST( !lockout_interval_pool_used( tower->lck_pool ) ); - FD_TEST( !lockout_slot_pool_used( tower->lck_slot_pool ) ); - FD_TEST( !pubkey_ref_query( tower, &pk_a ) ); - FD_TEST( !pubkey_ref_query( tower, &pk_b ) ); -} - -int -main( int argc, char ** argv ) { - fd_boot( &argc, &argv ); - - test_lockout_interval_encoding(); - - char const * _page_sz = fd_env_strip_cmdline_cstr ( &argc, &argv, "--page-sz", NULL, "gigantic" ); - ulong page_cnt = fd_env_strip_cmdline_ulong( &argc, &argv, "--page-cnt", NULL, 1UL ); - ulong numa_idx = fd_env_strip_cmdline_ulong( &argc, &argv, "--numa-idx", NULL, fd_shmem_numa_idx( 0UL ) ); - fd_wksp_t * wksp = fd_wksp_new_anonymous( fd_cstr_to_shmem_page_sz( _page_sz ), page_cnt, fd_shmem_cpu_idx( numa_idx ), "wksp", 0UL ); - FD_TEST( wksp ); - - test_lockos( wksp ); - test_lockos_pubkey_pool( wksp ); - test_lockos_representative_teardown( wksp ); - - fd_halt(); - return 0; -} diff --git a/src/choreo/tower/test_tower_serdes.c b/src/choreo/tower/test_tower_serdes.c index bfeb7f3363b..81dc21081ee 100644 --- a/src/choreo/tower/test_tower_serdes.c +++ b/src/choreo/tower/test_tower_serdes.c @@ -1,6 +1,5 @@ #include "fd_tower_serdes.h" #include "fd_tower.h" -#include "fd_tower_file.h" #include "../../ballet/ed25519/fd_ed25519.h" #include @@ -301,126 +300,6 @@ test_de_attacker( void ) { FD_LOG_NOTICE(( "pass: test_de_attacker" )); } -static void -test_compact_to_votes( void ) { - fd_compact_tower_sync_serde_t in; - fd_memset( &in, 0, sizeof(in) ); - in.root = 100UL; - in.lockouts_cnt = 3; - in.lockouts[ 0 ] = ( __typeof__(in.lockouts[0]) ){ .offset=5UL, .confirmation_count=3 }; - in.lockouts[ 1 ] = ( __typeof__(in.lockouts[0]) ){ .offset=2UL, .confirmation_count=2 }; - in.lockouts[ 2 ] = ( __typeof__(in.lockouts[0]) ){ .offset=4UL, .confirmation_count=1 }; - in.timestamp_option = 1; - - uchar buf[ 512 ]; - ulong sz = 0UL; - FD_TEST( !fd_compact_tower_sync_ser( &in, buf, sizeof(buf), &sz ) ); - - fd_compact_tower_sync_serde_t out; - FD_TEST( !fd_compact_tower_sync_de( &out, buf, sz ) ); - - fd_tower_vote_t votes[ FD_TOWER_VOTE_MAX ]; - ulong cnt; - ulong root; - FD_TEST( !fd_compact_tower_sync_to_votes( &out, votes, &cnt, &root ) ); - FD_TEST( root==100UL && cnt==3UL ); - FD_TEST( votes[ 0 ].slot==105UL && votes[ 0 ].conf==3UL ); - FD_TEST( votes[ 1 ].slot==107UL && votes[ 1 ].conf==2UL ); - FD_TEST( votes[ 2 ].slot==111UL && votes[ 2 ].conf==1UL ); - - fd_memset( votes, 0xA5, sizeof(votes) ); - fd_tower_vote_t first = votes[ 0 ]; - cnt = 99UL; - root = 99UL; - out.lockouts[ 1 ].offset = 0UL; - FD_TEST( fd_compact_tower_sync_to_votes( &out, votes, &cnt, &root )==-1 ); - FD_TEST( cnt==99UL && root==99UL && fd_memeq( &votes[ 0 ], &first, sizeof(first) ) ); - out.lockouts[ 1 ].offset = 2UL; - out.lockouts[ 0 ].confirmation_count = FD_TOWER_VOTE_MAX+1U; - FD_TEST( fd_compact_tower_sync_to_votes( &out, votes, &cnt, &root )==-1 ); - out.lockouts[ 0 ].confirmation_count = 3U; - out.lockouts[ 1 ].confirmation_count = 3U; - FD_TEST( fd_compact_tower_sync_to_votes( &out, votes, &cnt, &root )==-1 ); - out.lockouts[ 1 ].confirmation_count = 2U; - out.lockouts[ 2 ].confirmation_count = 0U; - FD_TEST( fd_compact_tower_sync_to_votes( &out, votes, &cnt, &root )==-1 ); - out.lockouts[ 2 ].confirmation_count = 1U; - out.lockouts[ 2 ].offset = 5UL; - FD_TEST( fd_compact_tower_sync_to_votes( &out, votes, &cnt, &root )==-1 ); - /* The oldest vote expired by the newest slot, still a valid tower, - Agave pops expired votes from the top only. */ - out.lockouts[ 1 ].offset = 7UL; - out.lockouts[ 2 ].offset = 2UL; - FD_TEST( !fd_compact_tower_sync_to_votes( &out, votes, &cnt, &root ) ); - FD_TEST( votes[ 0 ].slot==105UL && votes[ 1 ].slot==112UL && votes[ 2 ].slot==114UL ); - out.lockouts[ 1 ].offset = 9UL; - FD_TEST( fd_compact_tower_sync_to_votes( &out, votes, &cnt, &root )==-1 ); - out.lockouts[ 1 ].offset = 2UL; - out.lockouts[ 2 ].offset = 4UL; - out.root = ULONG_MAX-4UL; - FD_TEST( fd_compact_tower_sync_to_votes( &out, votes, &cnt, &root )==-1 ); - - out.root = ULONG_MAX; - out.lockouts[ 0 ].offset = 0UL; - FD_TEST( !fd_compact_tower_sync_to_votes( &out, votes, &cnt, &root ) ); - FD_TEST( root==ULONG_MAX && cnt==3UL ); - FD_TEST( votes[ 0 ].slot==0UL && votes[ 1 ].slot==2UL && votes[ 2 ].slot==6UL ); - - out.root = 1UL; - FD_TEST( fd_compact_tower_sync_to_votes( &out, votes, &cnt, &root )==-1 ); - - out.root = 0UL; - FD_TEST( fd_compact_tower_sync_to_votes( &out, votes, &cnt, &root )==-1 ); - - FD_LOG_NOTICE(( "pass: test_compact_to_votes" )); -} - -/* Helper: write an Agave tower file into buf whose tower matches sync, - signed by keypair (private key then public key). Returns the file - size. */ - -static ulong -make_tower_file( uchar * buf, - uchar const * keypair, - fd_compact_tower_sync_serde_t const * sync, - fd_sha512_t * sha ) { - ulong sz = fd_tower_file_ser( sync, (fd_pubkey_t const *)fd_type_pun_const( keypair+32UL ), buf ); - fd_ed25519_sign( buf+FD_TOWER_FILE_SIG_OFF, buf+FD_TOWER_FILE_DATA_OFF, sz-FD_TOWER_FILE_DATA_OFF, keypair+32UL, keypair, sha ); - return sz; -} - -static void -test_tower_file_root( void ) { - fd_sha512_t _sha[1]; - fd_sha512_t * sha = fd_sha512_join( fd_sha512_new( _sha ) ); - - uchar keypair[ 64 ]; - for( ulong i=0UL; i<32UL; i++ ) keypair[ i ] = (uchar)i; - fd_ed25519_public_from_private( keypair+32UL, keypair, sha ); - fd_pubkey_t identity; - memcpy( identity.uc, keypair+32UL, 32UL ); - - fd_compact_tower_sync_serde_t sync; - fd_memset( &sync, 0, sizeof(sync) ); - sync.root = 100UL; - sync.lockouts_cnt = 3; - sync.lockouts[ 0 ] = ( __typeof__(sync.lockouts[0]) ){ .offset=5UL, .confirmation_count=3 }; - sync.lockouts[ 1 ] = ( __typeof__(sync.lockouts[0]) ){ .offset=2UL, .confirmation_count=2 }; - sync.lockouts[ 2 ] = ( __typeof__(sync.lockouts[0]) ){ .offset=4UL, .confirmation_count=1 }; - - uchar buf[ FD_TOWER_FILE_MAX ]; - fd_tower_file_t out; - FD_TEST( fd_tower_file_de( buf, make_tower_file( buf, keypair, &sync, sha ), &identity, &out )==FD_TOWER_FILE_SUCCESS ); - FD_TEST( out.root==100UL && out.votes_cnt==3UL && out.votes[ 2 ].slot==111UL && out.timestamp_slot==111UL ); - - sync.root = ULONG_MAX; - sync.lockouts[ 0 ].offset = 105UL; - FD_TEST( fd_tower_file_de( buf, make_tower_file( buf, keypair, &sync, sha ), &identity, &out )==FD_TOWER_FILE_ERR_TOWER ); - - fd_sha512_delete( fd_sha512_leave( sha ) ); - FD_LOG_NOTICE(( "pass: test_tower_file_root" )); -} - int main( int argc, char ** argv ) { @@ -430,8 +309,6 @@ main( int argc, test_voter_v3(); test_voter_v4(); test_de_attacker(); - test_compact_to_votes(); - test_tower_file_root(); FD_LOG_NOTICE(( "pass" )); fd_halt(); diff --git a/src/disco/metrics/generated/fd_metrics_all.c b/src/disco/metrics/generated/fd_metrics_all.c index 8ff6b158324..c36d3d337fe 100644 --- a/src/disco/metrics/generated/fd_metrics_all.c +++ b/src/disco/metrics/generated/fd_metrics_all.c @@ -1250,123 +1250,6 @@ const fd_metrics_meta_t FD_METRICS_ACCDB[FD_METRICS_ACCDB_TOTAL] = { DECLARE_METRIC_ENUM( ACCDB_PROGCACHE_PREEVICTED, COUNTER, PROGCACHE_CLASS, LE11_MIB ), }; -const fd_metrics_meta_t FD_METRICS_TOWER[FD_METRICS_TOWER_TOTAL] = { - DECLARE_METRIC( TOWER_PRUNED_FORK_SLOT_IGNORED, COUNTER ), - DECLARE_METRIC( TOWER_LAST_PRUNED_FORK_IGNORED_SLOT, GAUGE ), - DECLARE_METRIC( TOWER_LAST_REPLAYED_SLOT, GAUGE ), - DECLARE_METRIC( TOWER_LAST_VOTE_SLOT, GAUGE ), - DECLARE_METRIC( TOWER_RESET_SLOT, GAUGE ), - DECLARE_METRIC( TOWER_ROOT_SLOT, GAUGE ), - DECLARE_METRIC( TOWER_INIT_SLOT, GAUGE ), - DECLARE_METRIC( TOWER_FRAG_NOT_READY_DROPPED, COUNTER ), - DECLARE_METRIC_ENUM( TOWER_EQVOC_CHUNK_PROCESSED, COUNTER, EQVOC_PROOF_RESULT, VERIFIED ), - DECLARE_METRIC_ENUM( TOWER_EQVOC_CHUNK_PROCESSED, COUNTER, EQVOC_PROOF_RESULT, IGNORED ), - DECLARE_METRIC_ENUM( TOWER_EQVOC_CHUNK_PROCESSED, COUNTER, EQVOC_PROOF_RESULT, INVALID ), - DECLARE_METRIC_ENUM( TOWER_GHOST_VOTE_PROCESSED, COUNTER, GHOST_VOTE_RESULT, SUCCESS ), - DECLARE_METRIC_ENUM( TOWER_GHOST_VOTE_PROCESSED, COUNTER, GHOST_VOTE_RESULT, ALREADY_VOTED ), - DECLARE_METRIC_ENUM( TOWER_HARD_FORK_VOTE_PROCESSED, COUNTER, HARD_FORK_VOTE_RESULT, PENDING ), - DECLARE_METRIC_ENUM( TOWER_HARD_FORK_VOTE_PROCESSED, COUNTER, HARD_FORK_VOTE_RESULT, MATCHED ), - DECLARE_METRIC_ENUM( TOWER_HARD_FORK_VOTE_PROCESSED, COUNTER, HARD_FORK_VOTE_RESULT, MISMATCHED ), - DECLARE_METRIC_ENUM( TOWER_HARD_FORK_VOTE_PROCESSED, COUNTER, HARD_FORK_VOTE_RESULT, UNKNOWN_VOTER ), - DECLARE_METRIC_ENUM( TOWER_HARD_FORK_VOTE_PROCESSED, COUNTER, HARD_FORK_VOTE_RESULT, ALREADY_VOTED ), - DECLARE_METRIC_ENUM( TOWER_HARD_FORK_VOTE_PROCESSED, COUNTER, HARD_FORK_VOTE_RESULT, TOO_OLD ), - DECLARE_METRIC_ENUM( TOWER_HARD_FORK_BANK_HASH_CHECKED, COUNTER, HARD_FORK_BANK_HASH_RESULT, PENDING ), - DECLARE_METRIC_ENUM( TOWER_HARD_FORK_BANK_HASH_CHECKED, COUNTER, HARD_FORK_BANK_HASH_RESULT, MATCHED ), - DECLARE_METRIC_ENUM( TOWER_HARD_FORK_BANK_HASH_CHECKED, COUNTER, HARD_FORK_BANK_HASH_RESULT, MISMATCHED ), - DECLARE_METRIC( TOWER_HIGHEST_HARD_FORK_MATCHED_SLOT, GAUGE ), - DECLARE_METRIC( TOWER_HIGHEST_HARD_FORK_MISMATCHED_SLOT, GAUGE ), - DECLARE_METRIC_ENUM( TOWER_FORK_CHOSEN, COUNTER, TOWER_FORK_RESULT, EMPTY_TOWER_STALE ), - DECLARE_METRIC_ENUM( TOWER_FORK_CHOSEN, COUNTER, TOWER_FORK_RESULT, EMPTY_TOWER_VOTE ), - DECLARE_METRIC_ENUM( TOWER_FORK_CHOSEN, COUNTER, TOWER_FORK_RESULT, ANCESTOR_ROLLBACK ), - DECLARE_METRIC_ENUM( TOWER_FORK_CHOSEN, COUNTER, TOWER_FORK_RESULT, SIBLING_CONFIRMED ), - DECLARE_METRIC_ENUM( TOWER_FORK_CHOSEN, COUNTER, TOWER_FORK_RESULT, SAME_FORK ), - DECLARE_METRIC_ENUM( TOWER_FORK_CHOSEN, COUNTER, TOWER_FORK_RESULT, SWITCH_PASSED ), - DECLARE_METRIC_ENUM( TOWER_FORK_CHOSEN, COUNTER, TOWER_FORK_RESULT, SWITCH_FAILED ), - DECLARE_METRIC_ENUM( TOWER_VOTE_GATE_CHECKED, COUNTER, TOWER_VOTE_GATE_RESULT, VOTED ), - DECLARE_METRIC_ENUM( TOWER_VOTE_GATE_CHECKED, COUNTER, TOWER_VOTE_GATE_RESULT, LOCKOUT_FAILED ), - DECLARE_METRIC_ENUM( TOWER_VOTE_GATE_CHECKED, COUNTER, TOWER_VOTE_GATE_RESULT, THRESHOLD_FAILED ), - DECLARE_METRIC_ENUM( TOWER_VOTE_GATE_CHECKED, COUNTER, TOWER_VOTE_GATE_RESULT, PROPAGATED_FAILED ), - DECLARE_METRIC_ENUM( TOWER_VOTE_GATE_CHECKED, COUNTER, TOWER_VOTE_GATE_RESULT, NO_CANDIDATE ), - DECLARE_METRIC( TOWER_TOWER_FILE_WRITTEN, COUNTER ), - DECLARE_METRIC( TOWER_TOWER_FILE_SLOT, GAUGE ), - DECLARE_METRIC( TOWER_TOWER_FILE_SIZE_BYTES, GAUGE ), - DECLARE_METRIC_ENUM( TOWER_VOTE_TXN_PROCESSED, COUNTER, VOTE_TXN_RESULT, SUCCESS ), - DECLARE_METRIC_ENUM( TOWER_VOTE_TXN_PROCESSED, COUNTER, VOTE_TXN_RESULT, NOT_SIMPLE_VOTE ), - DECLARE_METRIC_ENUM( TOWER_VOTE_TXN_PROCESSED, COUNTER, VOTE_TXN_RESULT, BAD_DESER ), - DECLARE_METRIC_ENUM( TOWER_VOTE_TXN_PROCESSED, COUNTER, VOTE_TXN_RESULT, NOT_TOWER_SYNC ), - DECLARE_METRIC_ENUM( TOWER_VOTE_TXN_PROCESSED, COUNTER, VOTE_TXN_RESULT, EMPTY_TOWER ), - DECLARE_METRIC_ENUM( TOWER_VOTE_TXN_PROCESSED, COUNTER, VOTE_TXN_RESULT, BAD_TOWER ), - DECLARE_METRIC_ENUM( TOWER_VOTE_TXN_PROCESSED, COUNTER, VOTE_TXN_RESULT, NULL_BLOCK_ID ), - DECLARE_METRIC_ENUM( TOWER_VOTE_TXN_PROCESSED, COUNTER, VOTE_TXN_RESULT, TOO_OLD ), - DECLARE_METRIC_ENUM( TOWER_VOTE_TXN_PROCESSED, COUNTER, VOTE_TXN_RESULT, NOT_STAKED ), - DECLARE_METRIC_ENUM( TOWER_VOTE_TXN_PROCESSED, COUNTER, VOTE_TXN_RESULT, BAD_SIGNER ), - DECLARE_METRIC_ENUM( TOWER_VOTE_SLOT_PROCESSED, COUNTER, VOTE_SLOT_RESULT, SUCCESS ), - DECLARE_METRIC_ENUM( TOWER_VOTE_SLOT_PROCESSED, COUNTER, VOTE_SLOT_RESULT, TOO_NEW ), - DECLARE_METRIC_ENUM( TOWER_VOTE_SLOT_PROCESSED, COUNTER, VOTE_SLOT_RESULT, UNKNOWN_VOTER ), - DECLARE_METRIC_ENUM( TOWER_VOTE_SLOT_PROCESSED, COUNTER, VOTE_SLOT_RESULT, ALREADY_VOTED ), - DECLARE_METRIC_ENUM( TOWER_VOTE_SLOT_PROCESSED, COUNTER, VOTE_SLOT_RESULT, TOO_OLD ), - DECLARE_METRIC_ENUM( TOWER_VOTE_SLOT_PROCESSED, COUNTER, VOTE_SLOT_RESULT, UNKNOWN_SLOT ), - DECLARE_METRIC_ENUM( TOWER_VOTE_INTERMEDIATE_SLOT_CHECKED, COUNTER, VOTE_INTERMEDIATE_SLOT_RESULT, COUNTED ), - DECLARE_METRIC_ENUM( TOWER_VOTE_INTERMEDIATE_SLOT_CHECKED, COUNTER, VOTE_INTERMEDIATE_SLOT_RESULT, UNREPLAYED_SLOT ), - DECLARE_METRIC_ENUM( TOWER_VOTE_INTERMEDIATE_SLOT_CHECKED, COUNTER, VOTE_INTERMEDIATE_SLOT_RESULT, MISMATCHED_BLOCK_ID ), - DECLARE_METRIC_ENUM( TOWER_ACCDB_ACCOUNT_READONLY_ACQUIRED, COUNTER, ACCDB_CACHE_CLASS, LE128_B ), - DECLARE_METRIC_ENUM( TOWER_ACCDB_ACCOUNT_READONLY_ACQUIRED, COUNTER, ACCDB_CACHE_CLASS, LE512_B ), - DECLARE_METRIC_ENUM( TOWER_ACCDB_ACCOUNT_READONLY_ACQUIRED, COUNTER, ACCDB_CACHE_CLASS, LE2_KIB ), - DECLARE_METRIC_ENUM( TOWER_ACCDB_ACCOUNT_READONLY_ACQUIRED, COUNTER, ACCDB_CACHE_CLASS, LE8_KIB ), - DECLARE_METRIC_ENUM( TOWER_ACCDB_ACCOUNT_READONLY_ACQUIRED, COUNTER, ACCDB_CACHE_CLASS, LE32_KIB ), - DECLARE_METRIC_ENUM( TOWER_ACCDB_ACCOUNT_READONLY_ACQUIRED, COUNTER, ACCDB_CACHE_CLASS, LE128_KIB ), - DECLARE_METRIC_ENUM( TOWER_ACCDB_ACCOUNT_READONLY_ACQUIRED, COUNTER, ACCDB_CACHE_CLASS, LE1_MIB ), - DECLARE_METRIC_ENUM( TOWER_ACCDB_ACCOUNT_READONLY_ACQUIRED, COUNTER, ACCDB_CACHE_CLASS, LE10_MIB ), - DECLARE_METRIC_ENUM( TOWER_ACCDB_ACCOUNT_WRITABLE_ACQUIRED, COUNTER, ACCDB_CACHE_CLASS, LE128_B ), - DECLARE_METRIC_ENUM( TOWER_ACCDB_ACCOUNT_WRITABLE_ACQUIRED, COUNTER, ACCDB_CACHE_CLASS, LE512_B ), - DECLARE_METRIC_ENUM( TOWER_ACCDB_ACCOUNT_WRITABLE_ACQUIRED, COUNTER, ACCDB_CACHE_CLASS, LE2_KIB ), - DECLARE_METRIC_ENUM( TOWER_ACCDB_ACCOUNT_WRITABLE_ACQUIRED, COUNTER, ACCDB_CACHE_CLASS, LE8_KIB ), - DECLARE_METRIC_ENUM( TOWER_ACCDB_ACCOUNT_WRITABLE_ACQUIRED, COUNTER, ACCDB_CACHE_CLASS, LE32_KIB ), - DECLARE_METRIC_ENUM( TOWER_ACCDB_ACCOUNT_WRITABLE_ACQUIRED, COUNTER, ACCDB_CACHE_CLASS, LE128_KIB ), - DECLARE_METRIC_ENUM( TOWER_ACCDB_ACCOUNT_WRITABLE_ACQUIRED, COUNTER, ACCDB_CACHE_CLASS, LE1_MIB ), - DECLARE_METRIC_ENUM( TOWER_ACCDB_ACCOUNT_WRITABLE_ACQUIRED, COUNTER, ACCDB_CACHE_CLASS, LE10_MIB ), - DECLARE_METRIC_ENUM( TOWER_ACCDB_ACCOUNT_EVICTED, COUNTER, ACCDB_CACHE_CLASS, LE128_B ), - DECLARE_METRIC_ENUM( TOWER_ACCDB_ACCOUNT_EVICTED, COUNTER, ACCDB_CACHE_CLASS, LE512_B ), - DECLARE_METRIC_ENUM( TOWER_ACCDB_ACCOUNT_EVICTED, COUNTER, ACCDB_CACHE_CLASS, LE2_KIB ), - DECLARE_METRIC_ENUM( TOWER_ACCDB_ACCOUNT_EVICTED, COUNTER, ACCDB_CACHE_CLASS, LE8_KIB ), - DECLARE_METRIC_ENUM( TOWER_ACCDB_ACCOUNT_EVICTED, COUNTER, ACCDB_CACHE_CLASS, LE32_KIB ), - DECLARE_METRIC_ENUM( TOWER_ACCDB_ACCOUNT_EVICTED, COUNTER, ACCDB_CACHE_CLASS, LE128_KIB ), - DECLARE_METRIC_ENUM( TOWER_ACCDB_ACCOUNT_EVICTED, COUNTER, ACCDB_CACHE_CLASS, LE1_MIB ), - DECLARE_METRIC_ENUM( TOWER_ACCDB_ACCOUNT_EVICTED, COUNTER, ACCDB_CACHE_CLASS, LE10_MIB ), - DECLARE_METRIC_ENUM( TOWER_ACCDB_ACCOUNT_NEW_COMMITTED, COUNTER, ACCDB_CACHE_CLASS, LE128_B ), - DECLARE_METRIC_ENUM( TOWER_ACCDB_ACCOUNT_NEW_COMMITTED, COUNTER, ACCDB_CACHE_CLASS, LE512_B ), - DECLARE_METRIC_ENUM( TOWER_ACCDB_ACCOUNT_NEW_COMMITTED, COUNTER, ACCDB_CACHE_CLASS, LE2_KIB ), - DECLARE_METRIC_ENUM( TOWER_ACCDB_ACCOUNT_NEW_COMMITTED, COUNTER, ACCDB_CACHE_CLASS, LE8_KIB ), - DECLARE_METRIC_ENUM( TOWER_ACCDB_ACCOUNT_NEW_COMMITTED, COUNTER, ACCDB_CACHE_CLASS, LE32_KIB ), - DECLARE_METRIC_ENUM( TOWER_ACCDB_ACCOUNT_NEW_COMMITTED, COUNTER, ACCDB_CACHE_CLASS, LE128_KIB ), - DECLARE_METRIC_ENUM( TOWER_ACCDB_ACCOUNT_NEW_COMMITTED, COUNTER, ACCDB_CACHE_CLASS, LE1_MIB ), - DECLARE_METRIC_ENUM( TOWER_ACCDB_ACCOUNT_NEW_COMMITTED, COUNTER, ACCDB_CACHE_CLASS, LE10_MIB ), - DECLARE_METRIC_ENUM( TOWER_ACCDB_ACCOUNT_OVERWRITE_COMMITTED, COUNTER, ACCDB_CACHE_CLASS, LE128_B ), - DECLARE_METRIC_ENUM( TOWER_ACCDB_ACCOUNT_OVERWRITE_COMMITTED, COUNTER, ACCDB_CACHE_CLASS, LE512_B ), - DECLARE_METRIC_ENUM( TOWER_ACCDB_ACCOUNT_OVERWRITE_COMMITTED, COUNTER, ACCDB_CACHE_CLASS, LE2_KIB ), - DECLARE_METRIC_ENUM( TOWER_ACCDB_ACCOUNT_OVERWRITE_COMMITTED, COUNTER, ACCDB_CACHE_CLASS, LE8_KIB ), - DECLARE_METRIC_ENUM( TOWER_ACCDB_ACCOUNT_OVERWRITE_COMMITTED, COUNTER, ACCDB_CACHE_CLASS, LE32_KIB ), - DECLARE_METRIC_ENUM( TOWER_ACCDB_ACCOUNT_OVERWRITE_COMMITTED, COUNTER, ACCDB_CACHE_CLASS, LE128_KIB ), - DECLARE_METRIC_ENUM( TOWER_ACCDB_ACCOUNT_OVERWRITE_COMMITTED, COUNTER, ACCDB_CACHE_CLASS, LE1_MIB ), - DECLARE_METRIC_ENUM( TOWER_ACCDB_ACCOUNT_OVERWRITE_COMMITTED, COUNTER, ACCDB_CACHE_CLASS, LE10_MIB ), - DECLARE_METRIC_ENUM( TOWER_ACCDB_ACCOUNT_CACHE_MISSED, COUNTER, ACCDB_CACHE_CLASS, LE128_B ), - DECLARE_METRIC_ENUM( TOWER_ACCDB_ACCOUNT_CACHE_MISSED, COUNTER, ACCDB_CACHE_CLASS, LE512_B ), - DECLARE_METRIC_ENUM( TOWER_ACCDB_ACCOUNT_CACHE_MISSED, COUNTER, ACCDB_CACHE_CLASS, LE2_KIB ), - DECLARE_METRIC_ENUM( TOWER_ACCDB_ACCOUNT_CACHE_MISSED, COUNTER, ACCDB_CACHE_CLASS, LE8_KIB ), - DECLARE_METRIC_ENUM( TOWER_ACCDB_ACCOUNT_CACHE_MISSED, COUNTER, ACCDB_CACHE_CLASS, LE32_KIB ), - DECLARE_METRIC_ENUM( TOWER_ACCDB_ACCOUNT_CACHE_MISSED, COUNTER, ACCDB_CACHE_CLASS, LE128_KIB ), - DECLARE_METRIC_ENUM( TOWER_ACCDB_ACCOUNT_CACHE_MISSED, COUNTER, ACCDB_CACHE_CLASS, LE1_MIB ), - DECLARE_METRIC_ENUM( TOWER_ACCDB_ACCOUNT_CACHE_MISSED, COUNTER, ACCDB_CACHE_CLASS, LE10_MIB ), - DECLARE_METRIC( TOWER_ACCDB_ACCOUNT_WAITED, COUNTER ), - DECLARE_METRIC( TOWER_ACCDB_BATCH_ACQUIRED, COUNTER ), - DECLARE_METRIC( TOWER_ACCDB_CACHE_RESERVE_RETRIED, COUNTER ), - DECLARE_METRIC( TOWER_ACCDB_DISK_READ_BYTES, COUNTER ), - DECLARE_METRIC( TOWER_ACCDB_DISK_READ, COUNTER ), - DECLARE_METRIC( TOWER_ACCDB_DISK_WRITTEN_BYTES, COUNTER ), - DECLARE_METRIC( TOWER_ACCDB_DISK_WRITTEN, COUNTER ), - DECLARE_METRIC( TOWER_ACCDB_COPIED_BYTES, COUNTER ), -}; - const fd_metrics_meta_t FD_METRICS_DIAG[FD_METRICS_DIAG_TOTAL] = { DECLARE_METRIC( DIAG_BUNDLE_STATE, GAUGE ), DECLARE_METRIC( DIAG_VOTE_STATE, GAUGE ), @@ -2071,7 +1954,6 @@ const char * FD_METRICS_TILE_KIND_NAMES[FD_METRICS_TILE_KIND_CNT] = { "replay", "execrp", "accdb", - "tower", "diag", "event", "gui", @@ -2118,7 +2000,6 @@ const ulong FD_METRICS_TILE_KIND_SIZES[FD_METRICS_TILE_KIND_CNT] = { FD_METRICS_REPLAY_TOTAL, FD_METRICS_EXECRP_TOTAL, FD_METRICS_ACCDB_TOTAL, - FD_METRICS_TOWER_TOTAL, FD_METRICS_DIAG_TOTAL, FD_METRICS_EVENT_TOTAL, FD_METRICS_GUI_TOTAL, @@ -2164,7 +2045,6 @@ const fd_metrics_meta_t * FD_METRICS_TILE_KIND_METRICS[FD_METRICS_TILE_KIND_CNT] FD_METRICS_REPLAY, FD_METRICS_EXECRP, FD_METRICS_ACCDB, - FD_METRICS_TOWER, FD_METRICS_DIAG, FD_METRICS_EVENT, FD_METRICS_GUI, diff --git a/src/disco/metrics/generated/fd_metrics_all.h b/src/disco/metrics/generated/fd_metrics_all.h index 88c32a2e9e9..7481258cadf 100644 --- a/src/disco/metrics/generated/fd_metrics_all.h +++ b/src/disco/metrics/generated/fd_metrics_all.h @@ -94,7 +94,6 @@ enum { #include "fd_metrics_backt.h" #include "fd_metrics_execrp.h" #include "fd_metrics_benchs.h" -#include "fd_metrics_tower.h" #include "fd_metrics_votor.h" #include "fd_metrics_gui.h" #include "fd_metrics_event.h" @@ -251,7 +250,7 @@ extern const fd_metrics_meta_t FD_METRICS_ALL_LINK_IN[FD_METRICS_ALL_LINK_IN_TOT #define FD_METRICS_TOTAL_SZ (8UL*301UL) -#define FD_METRICS_TILE_KIND_CNT 44 +#define FD_METRICS_TILE_KIND_CNT 43 extern const char * FD_METRICS_TILE_KIND_NAMES[FD_METRICS_TILE_KIND_CNT]; extern const ulong FD_METRICS_TILE_KIND_SIZES[FD_METRICS_TILE_KIND_CNT]; extern const fd_metrics_meta_t * FD_METRICS_TILE_KIND_METRICS[FD_METRICS_TILE_KIND_CNT]; diff --git a/src/disco/metrics/generated/fd_metrics_enums.h b/src/disco/metrics/generated/fd_metrics_enums.h index 59e1b0cd39c..072aa017a82 100644 --- a/src/disco/metrics/generated/fd_metrics_enums.h +++ b/src/disco/metrics/generated/fd_metrics_enums.h @@ -1352,132 +1352,6 @@ #define FD_METRICS_ENUM_VM_REGIME_G_ALL_MASK (0x1fUL) #define FD_METRICS_ENUM_VM_REGIME_G_CPI_MASK (0xcUL) -#define FD_METRICS_ENUM_GHOST_VOTE_RESULT_NAME "ghost_vote_result" -#define FD_METRICS_ENUM_GHOST_VOTE_RESULT_CNT (2UL) -#define FD_METRICS_ENUM_GHOST_VOTE_RESULT_V_SUCCESS_IDX 0 -#define FD_METRICS_ENUM_GHOST_VOTE_RESULT_V_SUCCESS_NAME "success" -#define FD_METRICS_ENUM_GHOST_VOTE_RESULT_V_ALREADY_VOTED_IDX 1 -#define FD_METRICS_ENUM_GHOST_VOTE_RESULT_V_ALREADY_VOTED_NAME "already_voted" -#define FD_METRICS_ENUM_GHOST_VOTE_RESULT_G_ALL_MASK (0x3UL) - -#define FD_METRICS_ENUM_HARD_FORK_VOTE_RESULT_NAME "hard_fork_vote_result" -#define FD_METRICS_ENUM_HARD_FORK_VOTE_RESULT_CNT (6UL) -#define FD_METRICS_ENUM_HARD_FORK_VOTE_RESULT_V_PENDING_IDX 0 -#define FD_METRICS_ENUM_HARD_FORK_VOTE_RESULT_V_PENDING_NAME "pending" -#define FD_METRICS_ENUM_HARD_FORK_VOTE_RESULT_V_MATCHED_IDX 1 -#define FD_METRICS_ENUM_HARD_FORK_VOTE_RESULT_V_MATCHED_NAME "matched" -#define FD_METRICS_ENUM_HARD_FORK_VOTE_RESULT_V_MISMATCHED_IDX 2 -#define FD_METRICS_ENUM_HARD_FORK_VOTE_RESULT_V_MISMATCHED_NAME "mismatched" -#define FD_METRICS_ENUM_HARD_FORK_VOTE_RESULT_V_UNKNOWN_VOTER_IDX 3 -#define FD_METRICS_ENUM_HARD_FORK_VOTE_RESULT_V_UNKNOWN_VOTER_NAME "unknown_voter" -#define FD_METRICS_ENUM_HARD_FORK_VOTE_RESULT_V_ALREADY_VOTED_IDX 4 -#define FD_METRICS_ENUM_HARD_FORK_VOTE_RESULT_V_ALREADY_VOTED_NAME "already_voted" -#define FD_METRICS_ENUM_HARD_FORK_VOTE_RESULT_V_TOO_OLD_IDX 5 -#define FD_METRICS_ENUM_HARD_FORK_VOTE_RESULT_V_TOO_OLD_NAME "too_old" -#define FD_METRICS_ENUM_HARD_FORK_VOTE_RESULT_G_ALL_MASK (0x3fUL) - -#define FD_METRICS_ENUM_HARD_FORK_BANK_HASH_RESULT_NAME "hard_fork_bank_hash_result" -#define FD_METRICS_ENUM_HARD_FORK_BANK_HASH_RESULT_CNT (3UL) -#define FD_METRICS_ENUM_HARD_FORK_BANK_HASH_RESULT_V_PENDING_IDX 0 -#define FD_METRICS_ENUM_HARD_FORK_BANK_HASH_RESULT_V_PENDING_NAME "pending" -#define FD_METRICS_ENUM_HARD_FORK_BANK_HASH_RESULT_V_MATCHED_IDX 1 -#define FD_METRICS_ENUM_HARD_FORK_BANK_HASH_RESULT_V_MATCHED_NAME "matched" -#define FD_METRICS_ENUM_HARD_FORK_BANK_HASH_RESULT_V_MISMATCHED_IDX 2 -#define FD_METRICS_ENUM_HARD_FORK_BANK_HASH_RESULT_V_MISMATCHED_NAME "mismatched" -#define FD_METRICS_ENUM_HARD_FORK_BANK_HASH_RESULT_G_ALL_MASK (0x7UL) - -#define FD_METRICS_ENUM_TOWER_FORK_RESULT_NAME "tower_fork_result" -#define FD_METRICS_ENUM_TOWER_FORK_RESULT_CNT (7UL) -#define FD_METRICS_ENUM_TOWER_FORK_RESULT_V_EMPTY_TOWER_STALE_IDX 0 -#define FD_METRICS_ENUM_TOWER_FORK_RESULT_V_EMPTY_TOWER_STALE_NAME "empty_tower_stale" -#define FD_METRICS_ENUM_TOWER_FORK_RESULT_V_EMPTY_TOWER_VOTE_IDX 1 -#define FD_METRICS_ENUM_TOWER_FORK_RESULT_V_EMPTY_TOWER_VOTE_NAME "empty_tower_vote" -#define FD_METRICS_ENUM_TOWER_FORK_RESULT_V_ANCESTOR_ROLLBACK_IDX 2 -#define FD_METRICS_ENUM_TOWER_FORK_RESULT_V_ANCESTOR_ROLLBACK_NAME "ancestor_rollback" -#define FD_METRICS_ENUM_TOWER_FORK_RESULT_V_SIBLING_CONFIRMED_IDX 3 -#define FD_METRICS_ENUM_TOWER_FORK_RESULT_V_SIBLING_CONFIRMED_NAME "sibling_confirmed" -#define FD_METRICS_ENUM_TOWER_FORK_RESULT_V_SAME_FORK_IDX 4 -#define FD_METRICS_ENUM_TOWER_FORK_RESULT_V_SAME_FORK_NAME "same_fork" -#define FD_METRICS_ENUM_TOWER_FORK_RESULT_V_SWITCH_PASSED_IDX 5 -#define FD_METRICS_ENUM_TOWER_FORK_RESULT_V_SWITCH_PASSED_NAME "switch_passed" -#define FD_METRICS_ENUM_TOWER_FORK_RESULT_V_SWITCH_FAILED_IDX 6 -#define FD_METRICS_ENUM_TOWER_FORK_RESULT_V_SWITCH_FAILED_NAME "switch_failed" -#define FD_METRICS_ENUM_TOWER_FORK_RESULT_G_ALL_MASK (0x7fUL) - -#define FD_METRICS_ENUM_TOWER_VOTE_GATE_RESULT_NAME "tower_vote_gate_result" -#define FD_METRICS_ENUM_TOWER_VOTE_GATE_RESULT_CNT (5UL) -#define FD_METRICS_ENUM_TOWER_VOTE_GATE_RESULT_V_VOTED_IDX 0 -#define FD_METRICS_ENUM_TOWER_VOTE_GATE_RESULT_V_VOTED_NAME "voted" -#define FD_METRICS_ENUM_TOWER_VOTE_GATE_RESULT_V_LOCKOUT_FAILED_IDX 1 -#define FD_METRICS_ENUM_TOWER_VOTE_GATE_RESULT_V_LOCKOUT_FAILED_NAME "lockout_failed" -#define FD_METRICS_ENUM_TOWER_VOTE_GATE_RESULT_V_THRESHOLD_FAILED_IDX 2 -#define FD_METRICS_ENUM_TOWER_VOTE_GATE_RESULT_V_THRESHOLD_FAILED_NAME "threshold_failed" -#define FD_METRICS_ENUM_TOWER_VOTE_GATE_RESULT_V_PROPAGATED_FAILED_IDX 3 -#define FD_METRICS_ENUM_TOWER_VOTE_GATE_RESULT_V_PROPAGATED_FAILED_NAME "propagated_failed" -#define FD_METRICS_ENUM_TOWER_VOTE_GATE_RESULT_V_NO_CANDIDATE_IDX 4 -#define FD_METRICS_ENUM_TOWER_VOTE_GATE_RESULT_V_NO_CANDIDATE_NAME "no_candidate" -#define FD_METRICS_ENUM_TOWER_VOTE_GATE_RESULT_G_ALL_MASK (0x1fUL) - -#define FD_METRICS_ENUM_VOTE_TXN_RESULT_NAME "vote_txn_result" -#define FD_METRICS_ENUM_VOTE_TXN_RESULT_CNT (10UL) -#define FD_METRICS_ENUM_VOTE_TXN_RESULT_V_SUCCESS_IDX 0 -#define FD_METRICS_ENUM_VOTE_TXN_RESULT_V_SUCCESS_NAME "success" -#define FD_METRICS_ENUM_VOTE_TXN_RESULT_V_NOT_SIMPLE_VOTE_IDX 1 -#define FD_METRICS_ENUM_VOTE_TXN_RESULT_V_NOT_SIMPLE_VOTE_NAME "not_simple_vote" -#define FD_METRICS_ENUM_VOTE_TXN_RESULT_V_BAD_DESER_IDX 2 -#define FD_METRICS_ENUM_VOTE_TXN_RESULT_V_BAD_DESER_NAME "bad_deser" -#define FD_METRICS_ENUM_VOTE_TXN_RESULT_V_NOT_TOWER_SYNC_IDX 3 -#define FD_METRICS_ENUM_VOTE_TXN_RESULT_V_NOT_TOWER_SYNC_NAME "not_tower_sync" -#define FD_METRICS_ENUM_VOTE_TXN_RESULT_V_EMPTY_TOWER_IDX 4 -#define FD_METRICS_ENUM_VOTE_TXN_RESULT_V_EMPTY_TOWER_NAME "empty_tower" -#define FD_METRICS_ENUM_VOTE_TXN_RESULT_V_BAD_TOWER_IDX 5 -#define FD_METRICS_ENUM_VOTE_TXN_RESULT_V_BAD_TOWER_NAME "bad_tower" -#define FD_METRICS_ENUM_VOTE_TXN_RESULT_V_NULL_BLOCK_ID_IDX 6 -#define FD_METRICS_ENUM_VOTE_TXN_RESULT_V_NULL_BLOCK_ID_NAME "null_block_id" -#define FD_METRICS_ENUM_VOTE_TXN_RESULT_V_TOO_OLD_IDX 7 -#define FD_METRICS_ENUM_VOTE_TXN_RESULT_V_TOO_OLD_NAME "too_old" -#define FD_METRICS_ENUM_VOTE_TXN_RESULT_V_NOT_STAKED_IDX 8 -#define FD_METRICS_ENUM_VOTE_TXN_RESULT_V_NOT_STAKED_NAME "not_staked" -#define FD_METRICS_ENUM_VOTE_TXN_RESULT_V_BAD_SIGNER_IDX 9 -#define FD_METRICS_ENUM_VOTE_TXN_RESULT_V_BAD_SIGNER_NAME "bad_signer" -#define FD_METRICS_ENUM_VOTE_TXN_RESULT_G_ALL_MASK (0x3ffUL) - -#define FD_METRICS_ENUM_VOTE_SLOT_RESULT_NAME "vote_slot_result" -#define FD_METRICS_ENUM_VOTE_SLOT_RESULT_CNT (6UL) -#define FD_METRICS_ENUM_VOTE_SLOT_RESULT_V_SUCCESS_IDX 0 -#define FD_METRICS_ENUM_VOTE_SLOT_RESULT_V_SUCCESS_NAME "success" -#define FD_METRICS_ENUM_VOTE_SLOT_RESULT_V_TOO_NEW_IDX 1 -#define FD_METRICS_ENUM_VOTE_SLOT_RESULT_V_TOO_NEW_NAME "too_new" -#define FD_METRICS_ENUM_VOTE_SLOT_RESULT_V_UNKNOWN_VOTER_IDX 2 -#define FD_METRICS_ENUM_VOTE_SLOT_RESULT_V_UNKNOWN_VOTER_NAME "unknown_voter" -#define FD_METRICS_ENUM_VOTE_SLOT_RESULT_V_ALREADY_VOTED_IDX 3 -#define FD_METRICS_ENUM_VOTE_SLOT_RESULT_V_ALREADY_VOTED_NAME "already_voted" -#define FD_METRICS_ENUM_VOTE_SLOT_RESULT_V_TOO_OLD_IDX 4 -#define FD_METRICS_ENUM_VOTE_SLOT_RESULT_V_TOO_OLD_NAME "too_old" -#define FD_METRICS_ENUM_VOTE_SLOT_RESULT_V_UNKNOWN_SLOT_IDX 5 -#define FD_METRICS_ENUM_VOTE_SLOT_RESULT_V_UNKNOWN_SLOT_NAME "unknown_slot" -#define FD_METRICS_ENUM_VOTE_SLOT_RESULT_G_ALL_MASK (0x3fUL) - -#define FD_METRICS_ENUM_VOTE_INTERMEDIATE_SLOT_RESULT_NAME "vote_intermediate_slot_result" -#define FD_METRICS_ENUM_VOTE_INTERMEDIATE_SLOT_RESULT_CNT (3UL) -#define FD_METRICS_ENUM_VOTE_INTERMEDIATE_SLOT_RESULT_V_COUNTED_IDX 0 -#define FD_METRICS_ENUM_VOTE_INTERMEDIATE_SLOT_RESULT_V_COUNTED_NAME "counted" -#define FD_METRICS_ENUM_VOTE_INTERMEDIATE_SLOT_RESULT_V_UNREPLAYED_SLOT_IDX 1 -#define FD_METRICS_ENUM_VOTE_INTERMEDIATE_SLOT_RESULT_V_UNREPLAYED_SLOT_NAME "unreplayed_slot" -#define FD_METRICS_ENUM_VOTE_INTERMEDIATE_SLOT_RESULT_V_MISMATCHED_BLOCK_ID_IDX 2 -#define FD_METRICS_ENUM_VOTE_INTERMEDIATE_SLOT_RESULT_V_MISMATCHED_BLOCK_ID_NAME "mismatched_block_id" -#define FD_METRICS_ENUM_VOTE_INTERMEDIATE_SLOT_RESULT_G_ALL_MASK (0x7UL) - -#define FD_METRICS_ENUM_EQVOC_PROOF_RESULT_NAME "eqvoc_proof_result" -#define FD_METRICS_ENUM_EQVOC_PROOF_RESULT_CNT (3UL) -#define FD_METRICS_ENUM_EQVOC_PROOF_RESULT_V_VERIFIED_IDX 0 -#define FD_METRICS_ENUM_EQVOC_PROOF_RESULT_V_VERIFIED_NAME "verified" -#define FD_METRICS_ENUM_EQVOC_PROOF_RESULT_V_IGNORED_IDX 1 -#define FD_METRICS_ENUM_EQVOC_PROOF_RESULT_V_IGNORED_NAME "ignored" -#define FD_METRICS_ENUM_EQVOC_PROOF_RESULT_V_INVALID_IDX 2 -#define FD_METRICS_ENUM_EQVOC_PROOF_RESULT_V_INVALID_NAME "invalid" -#define FD_METRICS_ENUM_EQVOC_PROOF_RESULT_G_ALL_MASK (0x7UL) - #define FD_METRICS_ENUM_DATAGRAM_RX_RESULT_NAME "datagram_rx_result" #define FD_METRICS_ENUM_DATAGRAM_RX_RESULT_CNT (6UL) #define FD_METRICS_ENUM_DATAGRAM_RX_RESULT_V_VOTE_IDX 0 diff --git a/src/disco/metrics/generated/fd_metrics_sample_gen.c b/src/disco/metrics/generated/fd_metrics_sample_gen.c index e1460c66088..7c77bb5b549 100644 --- a/src/disco/metrics/generated/fd_metrics_sample_gen.c +++ b/src/disco/metrics/generated/fd_metrics_sample_gen.c @@ -4411,6 +4411,6 @@ fd_metrics_sample_schema_t const fd_metrics_sample_tiles[] = { { NULL, 0U, NULL, 0UL, 0UL } }; -char const * const fd_metrics_sample_skip[] = { "backt", "benchs", "solcap", "tower", NULL }; +char const * const fd_metrics_sample_skip[] = { "backt", "benchs", "solcap", NULL }; fd_metrics_sample_schema_t const fd_metrics_sample_link = { "link", 61U, fields_link, 9UL, 284UL }; diff --git a/src/disco/metrics/generated/fd_metrics_tower.h b/src/disco/metrics/generated/fd_metrics_tower.h deleted file mode 100644 index 57d25afcc56..00000000000 --- a/src/disco/metrics/generated/fd_metrics_tower.h +++ /dev/null @@ -1,339 +0,0 @@ -#ifndef HEADER_fd_src_disco_metrics_generated_fd_metrics_tower_h -#define HEADER_fd_src_disco_metrics_generated_fd_metrics_tower_h - -/* THIS FILE IS GENERATED BY gen_metrics.py. DO NOT HAND EDIT. */ - -#include "../fd_metrics_base.h" -#include "fd_metrics_enums.h" - -enum { - FD_METRICS_COUNTER_TOWER_PRUNED_FORK_SLOT_IGNORED_OFF = FD_METRICS_TILE_OFF, - FD_METRICS_GAUGE_TOWER_LAST_PRUNED_FORK_IGNORED_SLOT_OFF, - FD_METRICS_GAUGE_TOWER_LAST_REPLAYED_SLOT_OFF, - FD_METRICS_GAUGE_TOWER_LAST_VOTE_SLOT_OFF, - FD_METRICS_GAUGE_TOWER_RESET_SLOT_OFF, - FD_METRICS_GAUGE_TOWER_ROOT_SLOT_OFF, - FD_METRICS_GAUGE_TOWER_INIT_SLOT_OFF, - FD_METRICS_COUNTER_TOWER_FRAG_NOT_READY_DROPPED_OFF, - FD_METRICS_COUNTER_TOWER_EQVOC_CHUNK_PROCESSED_OFF, - FD_METRICS_COUNTER_TOWER_EQVOC_CHUNK_PROCESSED_VERIFIED_OFF = FD_METRICS_COUNTER_TOWER_EQVOC_CHUNK_PROCESSED_OFF, - FD_METRICS_COUNTER_TOWER_EQVOC_CHUNK_PROCESSED_IGNORED_OFF, - FD_METRICS_COUNTER_TOWER_EQVOC_CHUNK_PROCESSED_INVALID_OFF, - FD_METRICS_COUNTER_TOWER_GHOST_VOTE_PROCESSED_OFF, - FD_METRICS_COUNTER_TOWER_GHOST_VOTE_PROCESSED_SUCCESS_OFF = FD_METRICS_COUNTER_TOWER_GHOST_VOTE_PROCESSED_OFF, - FD_METRICS_COUNTER_TOWER_GHOST_VOTE_PROCESSED_ALREADY_VOTED_OFF, - FD_METRICS_COUNTER_TOWER_HARD_FORK_VOTE_PROCESSED_OFF, - FD_METRICS_COUNTER_TOWER_HARD_FORK_VOTE_PROCESSED_PENDING_OFF = FD_METRICS_COUNTER_TOWER_HARD_FORK_VOTE_PROCESSED_OFF, - FD_METRICS_COUNTER_TOWER_HARD_FORK_VOTE_PROCESSED_MATCHED_OFF, - FD_METRICS_COUNTER_TOWER_HARD_FORK_VOTE_PROCESSED_MISMATCHED_OFF, - FD_METRICS_COUNTER_TOWER_HARD_FORK_VOTE_PROCESSED_UNKNOWN_VOTER_OFF, - FD_METRICS_COUNTER_TOWER_HARD_FORK_VOTE_PROCESSED_ALREADY_VOTED_OFF, - FD_METRICS_COUNTER_TOWER_HARD_FORK_VOTE_PROCESSED_TOO_OLD_OFF, - FD_METRICS_COUNTER_TOWER_HARD_FORK_BANK_HASH_CHECKED_OFF, - FD_METRICS_COUNTER_TOWER_HARD_FORK_BANK_HASH_CHECKED_PENDING_OFF = FD_METRICS_COUNTER_TOWER_HARD_FORK_BANK_HASH_CHECKED_OFF, - FD_METRICS_COUNTER_TOWER_HARD_FORK_BANK_HASH_CHECKED_MATCHED_OFF, - FD_METRICS_COUNTER_TOWER_HARD_FORK_BANK_HASH_CHECKED_MISMATCHED_OFF, - FD_METRICS_GAUGE_TOWER_HIGHEST_HARD_FORK_MATCHED_SLOT_OFF, - FD_METRICS_GAUGE_TOWER_HIGHEST_HARD_FORK_MISMATCHED_SLOT_OFF, - FD_METRICS_COUNTER_TOWER_FORK_CHOSEN_OFF, - FD_METRICS_COUNTER_TOWER_FORK_CHOSEN_EMPTY_TOWER_STALE_OFF = FD_METRICS_COUNTER_TOWER_FORK_CHOSEN_OFF, - FD_METRICS_COUNTER_TOWER_FORK_CHOSEN_EMPTY_TOWER_VOTE_OFF, - FD_METRICS_COUNTER_TOWER_FORK_CHOSEN_ANCESTOR_ROLLBACK_OFF, - FD_METRICS_COUNTER_TOWER_FORK_CHOSEN_SIBLING_CONFIRMED_OFF, - FD_METRICS_COUNTER_TOWER_FORK_CHOSEN_SAME_FORK_OFF, - FD_METRICS_COUNTER_TOWER_FORK_CHOSEN_SWITCH_PASSED_OFF, - FD_METRICS_COUNTER_TOWER_FORK_CHOSEN_SWITCH_FAILED_OFF, - FD_METRICS_COUNTER_TOWER_VOTE_GATE_CHECKED_OFF, - FD_METRICS_COUNTER_TOWER_VOTE_GATE_CHECKED_VOTED_OFF = FD_METRICS_COUNTER_TOWER_VOTE_GATE_CHECKED_OFF, - FD_METRICS_COUNTER_TOWER_VOTE_GATE_CHECKED_LOCKOUT_FAILED_OFF, - FD_METRICS_COUNTER_TOWER_VOTE_GATE_CHECKED_THRESHOLD_FAILED_OFF, - FD_METRICS_COUNTER_TOWER_VOTE_GATE_CHECKED_PROPAGATED_FAILED_OFF, - FD_METRICS_COUNTER_TOWER_VOTE_GATE_CHECKED_NO_CANDIDATE_OFF, - FD_METRICS_COUNTER_TOWER_TOWER_FILE_WRITTEN_OFF, - FD_METRICS_GAUGE_TOWER_TOWER_FILE_SLOT_OFF, - FD_METRICS_GAUGE_TOWER_TOWER_FILE_SIZE_BYTES_OFF, - FD_METRICS_COUNTER_TOWER_VOTE_TXN_PROCESSED_OFF, - FD_METRICS_COUNTER_TOWER_VOTE_TXN_PROCESSED_SUCCESS_OFF = FD_METRICS_COUNTER_TOWER_VOTE_TXN_PROCESSED_OFF, - FD_METRICS_COUNTER_TOWER_VOTE_TXN_PROCESSED_NOT_SIMPLE_VOTE_OFF, - FD_METRICS_COUNTER_TOWER_VOTE_TXN_PROCESSED_BAD_DESER_OFF, - FD_METRICS_COUNTER_TOWER_VOTE_TXN_PROCESSED_NOT_TOWER_SYNC_OFF, - FD_METRICS_COUNTER_TOWER_VOTE_TXN_PROCESSED_EMPTY_TOWER_OFF, - FD_METRICS_COUNTER_TOWER_VOTE_TXN_PROCESSED_BAD_TOWER_OFF, - FD_METRICS_COUNTER_TOWER_VOTE_TXN_PROCESSED_NULL_BLOCK_ID_OFF, - FD_METRICS_COUNTER_TOWER_VOTE_TXN_PROCESSED_TOO_OLD_OFF, - FD_METRICS_COUNTER_TOWER_VOTE_TXN_PROCESSED_NOT_STAKED_OFF, - FD_METRICS_COUNTER_TOWER_VOTE_TXN_PROCESSED_BAD_SIGNER_OFF, - FD_METRICS_COUNTER_TOWER_VOTE_SLOT_PROCESSED_OFF, - FD_METRICS_COUNTER_TOWER_VOTE_SLOT_PROCESSED_SUCCESS_OFF = FD_METRICS_COUNTER_TOWER_VOTE_SLOT_PROCESSED_OFF, - FD_METRICS_COUNTER_TOWER_VOTE_SLOT_PROCESSED_TOO_NEW_OFF, - FD_METRICS_COUNTER_TOWER_VOTE_SLOT_PROCESSED_UNKNOWN_VOTER_OFF, - FD_METRICS_COUNTER_TOWER_VOTE_SLOT_PROCESSED_ALREADY_VOTED_OFF, - FD_METRICS_COUNTER_TOWER_VOTE_SLOT_PROCESSED_TOO_OLD_OFF, - FD_METRICS_COUNTER_TOWER_VOTE_SLOT_PROCESSED_UNKNOWN_SLOT_OFF, - FD_METRICS_COUNTER_TOWER_VOTE_INTERMEDIATE_SLOT_CHECKED_OFF, - FD_METRICS_COUNTER_TOWER_VOTE_INTERMEDIATE_SLOT_CHECKED_COUNTED_OFF = FD_METRICS_COUNTER_TOWER_VOTE_INTERMEDIATE_SLOT_CHECKED_OFF, - FD_METRICS_COUNTER_TOWER_VOTE_INTERMEDIATE_SLOT_CHECKED_UNREPLAYED_SLOT_OFF, - FD_METRICS_COUNTER_TOWER_VOTE_INTERMEDIATE_SLOT_CHECKED_MISMATCHED_BLOCK_ID_OFF, - FD_METRICS_COUNTER_TOWER_ACCDB_ACCOUNT_READONLY_ACQUIRED_OFF, - FD_METRICS_COUNTER_TOWER_ACCDB_ACCOUNT_READONLY_ACQUIRED_LE128_B_OFF = FD_METRICS_COUNTER_TOWER_ACCDB_ACCOUNT_READONLY_ACQUIRED_OFF, - FD_METRICS_COUNTER_TOWER_ACCDB_ACCOUNT_READONLY_ACQUIRED_LE512_B_OFF, - FD_METRICS_COUNTER_TOWER_ACCDB_ACCOUNT_READONLY_ACQUIRED_LE2_KIB_OFF, - FD_METRICS_COUNTER_TOWER_ACCDB_ACCOUNT_READONLY_ACQUIRED_LE8_KIB_OFF, - FD_METRICS_COUNTER_TOWER_ACCDB_ACCOUNT_READONLY_ACQUIRED_LE32_KIB_OFF, - FD_METRICS_COUNTER_TOWER_ACCDB_ACCOUNT_READONLY_ACQUIRED_LE128_KIB_OFF, - FD_METRICS_COUNTER_TOWER_ACCDB_ACCOUNT_READONLY_ACQUIRED_LE1_MIB_OFF, - FD_METRICS_COUNTER_TOWER_ACCDB_ACCOUNT_READONLY_ACQUIRED_LE10_MIB_OFF, - FD_METRICS_COUNTER_TOWER_ACCDB_ACCOUNT_WRITABLE_ACQUIRED_OFF, - FD_METRICS_COUNTER_TOWER_ACCDB_ACCOUNT_WRITABLE_ACQUIRED_LE128_B_OFF = FD_METRICS_COUNTER_TOWER_ACCDB_ACCOUNT_WRITABLE_ACQUIRED_OFF, - FD_METRICS_COUNTER_TOWER_ACCDB_ACCOUNT_WRITABLE_ACQUIRED_LE512_B_OFF, - FD_METRICS_COUNTER_TOWER_ACCDB_ACCOUNT_WRITABLE_ACQUIRED_LE2_KIB_OFF, - FD_METRICS_COUNTER_TOWER_ACCDB_ACCOUNT_WRITABLE_ACQUIRED_LE8_KIB_OFF, - FD_METRICS_COUNTER_TOWER_ACCDB_ACCOUNT_WRITABLE_ACQUIRED_LE32_KIB_OFF, - FD_METRICS_COUNTER_TOWER_ACCDB_ACCOUNT_WRITABLE_ACQUIRED_LE128_KIB_OFF, - FD_METRICS_COUNTER_TOWER_ACCDB_ACCOUNT_WRITABLE_ACQUIRED_LE1_MIB_OFF, - FD_METRICS_COUNTER_TOWER_ACCDB_ACCOUNT_WRITABLE_ACQUIRED_LE10_MIB_OFF, - FD_METRICS_COUNTER_TOWER_ACCDB_ACCOUNT_EVICTED_OFF, - FD_METRICS_COUNTER_TOWER_ACCDB_ACCOUNT_EVICTED_LE128_B_OFF = FD_METRICS_COUNTER_TOWER_ACCDB_ACCOUNT_EVICTED_OFF, - FD_METRICS_COUNTER_TOWER_ACCDB_ACCOUNT_EVICTED_LE512_B_OFF, - FD_METRICS_COUNTER_TOWER_ACCDB_ACCOUNT_EVICTED_LE2_KIB_OFF, - FD_METRICS_COUNTER_TOWER_ACCDB_ACCOUNT_EVICTED_LE8_KIB_OFF, - FD_METRICS_COUNTER_TOWER_ACCDB_ACCOUNT_EVICTED_LE32_KIB_OFF, - FD_METRICS_COUNTER_TOWER_ACCDB_ACCOUNT_EVICTED_LE128_KIB_OFF, - FD_METRICS_COUNTER_TOWER_ACCDB_ACCOUNT_EVICTED_LE1_MIB_OFF, - FD_METRICS_COUNTER_TOWER_ACCDB_ACCOUNT_EVICTED_LE10_MIB_OFF, - FD_METRICS_COUNTER_TOWER_ACCDB_ACCOUNT_NEW_COMMITTED_OFF, - FD_METRICS_COUNTER_TOWER_ACCDB_ACCOUNT_NEW_COMMITTED_LE128_B_OFF = FD_METRICS_COUNTER_TOWER_ACCDB_ACCOUNT_NEW_COMMITTED_OFF, - FD_METRICS_COUNTER_TOWER_ACCDB_ACCOUNT_NEW_COMMITTED_LE512_B_OFF, - FD_METRICS_COUNTER_TOWER_ACCDB_ACCOUNT_NEW_COMMITTED_LE2_KIB_OFF, - FD_METRICS_COUNTER_TOWER_ACCDB_ACCOUNT_NEW_COMMITTED_LE8_KIB_OFF, - FD_METRICS_COUNTER_TOWER_ACCDB_ACCOUNT_NEW_COMMITTED_LE32_KIB_OFF, - FD_METRICS_COUNTER_TOWER_ACCDB_ACCOUNT_NEW_COMMITTED_LE128_KIB_OFF, - FD_METRICS_COUNTER_TOWER_ACCDB_ACCOUNT_NEW_COMMITTED_LE1_MIB_OFF, - FD_METRICS_COUNTER_TOWER_ACCDB_ACCOUNT_NEW_COMMITTED_LE10_MIB_OFF, - FD_METRICS_COUNTER_TOWER_ACCDB_ACCOUNT_OVERWRITE_COMMITTED_OFF, - FD_METRICS_COUNTER_TOWER_ACCDB_ACCOUNT_OVERWRITE_COMMITTED_LE128_B_OFF = FD_METRICS_COUNTER_TOWER_ACCDB_ACCOUNT_OVERWRITE_COMMITTED_OFF, - FD_METRICS_COUNTER_TOWER_ACCDB_ACCOUNT_OVERWRITE_COMMITTED_LE512_B_OFF, - FD_METRICS_COUNTER_TOWER_ACCDB_ACCOUNT_OVERWRITE_COMMITTED_LE2_KIB_OFF, - FD_METRICS_COUNTER_TOWER_ACCDB_ACCOUNT_OVERWRITE_COMMITTED_LE8_KIB_OFF, - FD_METRICS_COUNTER_TOWER_ACCDB_ACCOUNT_OVERWRITE_COMMITTED_LE32_KIB_OFF, - FD_METRICS_COUNTER_TOWER_ACCDB_ACCOUNT_OVERWRITE_COMMITTED_LE128_KIB_OFF, - FD_METRICS_COUNTER_TOWER_ACCDB_ACCOUNT_OVERWRITE_COMMITTED_LE1_MIB_OFF, - FD_METRICS_COUNTER_TOWER_ACCDB_ACCOUNT_OVERWRITE_COMMITTED_LE10_MIB_OFF, - FD_METRICS_COUNTER_TOWER_ACCDB_ACCOUNT_CACHE_MISSED_OFF, - FD_METRICS_COUNTER_TOWER_ACCDB_ACCOUNT_CACHE_MISSED_LE128_B_OFF = FD_METRICS_COUNTER_TOWER_ACCDB_ACCOUNT_CACHE_MISSED_OFF, - FD_METRICS_COUNTER_TOWER_ACCDB_ACCOUNT_CACHE_MISSED_LE512_B_OFF, - FD_METRICS_COUNTER_TOWER_ACCDB_ACCOUNT_CACHE_MISSED_LE2_KIB_OFF, - FD_METRICS_COUNTER_TOWER_ACCDB_ACCOUNT_CACHE_MISSED_LE8_KIB_OFF, - FD_METRICS_COUNTER_TOWER_ACCDB_ACCOUNT_CACHE_MISSED_LE32_KIB_OFF, - FD_METRICS_COUNTER_TOWER_ACCDB_ACCOUNT_CACHE_MISSED_LE128_KIB_OFF, - FD_METRICS_COUNTER_TOWER_ACCDB_ACCOUNT_CACHE_MISSED_LE1_MIB_OFF, - FD_METRICS_COUNTER_TOWER_ACCDB_ACCOUNT_CACHE_MISSED_LE10_MIB_OFF, - FD_METRICS_COUNTER_TOWER_ACCDB_ACCOUNT_WAITED_OFF, - FD_METRICS_COUNTER_TOWER_ACCDB_BATCH_ACQUIRED_OFF, - FD_METRICS_COUNTER_TOWER_ACCDB_CACHE_RESERVE_RETRIED_OFF, - FD_METRICS_COUNTER_TOWER_ACCDB_DISK_READ_BYTES_OFF, - FD_METRICS_COUNTER_TOWER_ACCDB_DISK_READ_OFF, - FD_METRICS_COUNTER_TOWER_ACCDB_DISK_WRITTEN_BYTES_OFF, - FD_METRICS_COUNTER_TOWER_ACCDB_DISK_WRITTEN_OFF, - FD_METRICS_COUNTER_TOWER_ACCDB_COPIED_BYTES_OFF, -}; - -#define FD_METRICS_COUNTER_TOWER_PRUNED_FORK_SLOT_IGNORED_NAME "tower_pruned_fork_slot_ignored" -#define FD_METRICS_COUNTER_TOWER_PRUNED_FORK_SLOT_IGNORED_TYPE (FD_METRICS_TYPE_COUNTER) -#define FD_METRICS_COUNTER_TOWER_PRUNED_FORK_SLOT_IGNORED_DESC "replay_slot_completed frags we ignored" -#define FD_METRICS_COUNTER_TOWER_PRUNED_FORK_SLOT_IGNORED_CVT (FD_METRICS_CONVERTER_NONE) - -#define FD_METRICS_GAUGE_TOWER_LAST_PRUNED_FORK_IGNORED_SLOT_NAME "tower_last_pruned_fork_ignored_slot" -#define FD_METRICS_GAUGE_TOWER_LAST_PRUNED_FORK_IGNORED_SLOT_TYPE (FD_METRICS_TYPE_GAUGE) -#define FD_METRICS_GAUGE_TOWER_LAST_PRUNED_FORK_IGNORED_SLOT_DESC "Slot of the most recent replay_slot_completed frag ignored for being on a pruned fork, 0 if none" -#define FD_METRICS_GAUGE_TOWER_LAST_PRUNED_FORK_IGNORED_SLOT_CVT (FD_METRICS_CONVERTER_NONE) - -#define FD_METRICS_GAUGE_TOWER_LAST_REPLAYED_SLOT_NAME "tower_last_replayed_slot" -#define FD_METRICS_GAUGE_TOWER_LAST_REPLAYED_SLOT_TYPE (FD_METRICS_TYPE_GAUGE) -#define FD_METRICS_GAUGE_TOWER_LAST_REPLAYED_SLOT_DESC "Most recently replayed slot, 0 if none; can decrease" -#define FD_METRICS_GAUGE_TOWER_LAST_REPLAYED_SLOT_CVT (FD_METRICS_CONVERTER_NONE) - -#define FD_METRICS_GAUGE_TOWER_LAST_VOTE_SLOT_NAME "tower_last_vote_slot" -#define FD_METRICS_GAUGE_TOWER_LAST_VOTE_SLOT_TYPE (FD_METRICS_TYPE_GAUGE) -#define FD_METRICS_GAUGE_TOWER_LAST_VOTE_SLOT_DESC "Highest voted slot in the local tower, 0 if we have not voted" -#define FD_METRICS_GAUGE_TOWER_LAST_VOTE_SLOT_CVT (FD_METRICS_CONVERTER_NONE) - -#define FD_METRICS_GAUGE_TOWER_RESET_SLOT_NAME "tower_reset_slot" -#define FD_METRICS_GAUGE_TOWER_RESET_SLOT_TYPE (FD_METRICS_TYPE_GAUGE) -#define FD_METRICS_GAUGE_TOWER_RESET_SLOT_DESC "Most recent reset slot; can decrease" -#define FD_METRICS_GAUGE_TOWER_RESET_SLOT_CVT (FD_METRICS_CONVERTER_NONE) - -#define FD_METRICS_GAUGE_TOWER_ROOT_SLOT_NAME "tower_root_slot" -#define FD_METRICS_GAUGE_TOWER_ROOT_SLOT_TYPE (FD_METRICS_TYPE_GAUGE) -#define FD_METRICS_GAUGE_TOWER_ROOT_SLOT_DESC "Highest rooted slot, 0 if none" -#define FD_METRICS_GAUGE_TOWER_ROOT_SLOT_CVT (FD_METRICS_CONVERTER_NONE) - -#define FD_METRICS_GAUGE_TOWER_INIT_SLOT_NAME "tower_init_slot" -#define FD_METRICS_GAUGE_TOWER_INIT_SLOT_TYPE (FD_METRICS_TYPE_GAUGE) -#define FD_METRICS_GAUGE_TOWER_INIT_SLOT_DESC "Init slot, either the snapshot or genesis slot. Set once and does not change" -#define FD_METRICS_GAUGE_TOWER_INIT_SLOT_CVT (FD_METRICS_CONVERTER_NONE) - -#define FD_METRICS_COUNTER_TOWER_FRAG_NOT_READY_DROPPED_NAME "tower_frag_not_ready_dropped" -#define FD_METRICS_COUNTER_TOWER_FRAG_NOT_READY_DROPPED_TYPE (FD_METRICS_TYPE_COUNTER) -#define FD_METRICS_COUNTER_TOWER_FRAG_NOT_READY_DROPPED_DESC "Frags dropped before the tower tile initialized (vote txns, gossip updates), plus duplicate-shred messages with no leader schedule" -#define FD_METRICS_COUNTER_TOWER_FRAG_NOT_READY_DROPPED_CVT (FD_METRICS_CONVERTER_NONE) - -#define FD_METRICS_COUNTER_TOWER_EQVOC_CHUNK_PROCESSED_NAME "tower_eqvoc_chunk_processed" -#define FD_METRICS_COUNTER_TOWER_EQVOC_CHUNK_PROCESSED_TYPE (FD_METRICS_TYPE_COUNTER) -#define FD_METRICS_COUNTER_TOWER_EQVOC_CHUNK_PROCESSED_DESC "Equivocation proof chunks and conflicting shreds processed, by result" -#define FD_METRICS_COUNTER_TOWER_EQVOC_CHUNK_PROCESSED_CVT (FD_METRICS_CONVERTER_NONE) -#define FD_METRICS_COUNTER_TOWER_EQVOC_CHUNK_PROCESSED_CNT (3UL) - -#define FD_METRICS_COUNTER_TOWER_GHOST_VOTE_PROCESSED_NAME "tower_ghost_vote_processed" -#define FD_METRICS_COUNTER_TOWER_GHOST_VOTE_PROCESSED_TYPE (FD_METRICS_TYPE_COUNTER) -#define FD_METRICS_COUNTER_TOWER_GHOST_VOTE_PROCESSED_DESC "Votes counted toward ghost, by result" -#define FD_METRICS_COUNTER_TOWER_GHOST_VOTE_PROCESSED_CVT (FD_METRICS_CONVERTER_NONE) -#define FD_METRICS_COUNTER_TOWER_GHOST_VOTE_PROCESSED_CNT (2UL) - -#define FD_METRICS_COUNTER_TOWER_HARD_FORK_VOTE_PROCESSED_NAME "tower_hard_fork_vote_processed" -#define FD_METRICS_COUNTER_TOWER_HARD_FORK_VOTE_PROCESSED_TYPE (FD_METRICS_TYPE_COUNTER) -#define FD_METRICS_COUNTER_TOWER_HARD_FORK_VOTE_PROCESSED_DESC "Votes counted toward hard fork detection, by result" -#define FD_METRICS_COUNTER_TOWER_HARD_FORK_VOTE_PROCESSED_CVT (FD_METRICS_CONVERTER_NONE) -#define FD_METRICS_COUNTER_TOWER_HARD_FORK_VOTE_PROCESSED_CNT (6UL) - -#define FD_METRICS_COUNTER_TOWER_HARD_FORK_BANK_HASH_CHECKED_NAME "tower_hard_fork_bank_hash_checked" -#define FD_METRICS_COUNTER_TOWER_HARD_FORK_BANK_HASH_CHECKED_TYPE (FD_METRICS_TYPE_COUNTER) -#define FD_METRICS_COUNTER_TOWER_HARD_FORK_BANK_HASH_CHECKED_DESC "Our own bank hashes checked against hard fork votes, by result" -#define FD_METRICS_COUNTER_TOWER_HARD_FORK_BANK_HASH_CHECKED_CVT (FD_METRICS_CONVERTER_NONE) -#define FD_METRICS_COUNTER_TOWER_HARD_FORK_BANK_HASH_CHECKED_CNT (3UL) - -#define FD_METRICS_GAUGE_TOWER_HIGHEST_HARD_FORK_MATCHED_SLOT_NAME "tower_highest_hard_fork_matched_slot" -#define FD_METRICS_GAUGE_TOWER_HIGHEST_HARD_FORK_MATCHED_SLOT_TYPE (FD_METRICS_TYPE_GAUGE) -#define FD_METRICS_GAUGE_TOWER_HIGHEST_HARD_FORK_MATCHED_SLOT_DESC "Highest slot where 52%+ of stake agreed on our bank hash, 0 if none" -#define FD_METRICS_GAUGE_TOWER_HIGHEST_HARD_FORK_MATCHED_SLOT_CVT (FD_METRICS_CONVERTER_NONE) - -#define FD_METRICS_GAUGE_TOWER_HIGHEST_HARD_FORK_MISMATCHED_SLOT_NAME "tower_highest_hard_fork_mismatched_slot" -#define FD_METRICS_GAUGE_TOWER_HIGHEST_HARD_FORK_MISMATCHED_SLOT_TYPE (FD_METRICS_TYPE_GAUGE) -#define FD_METRICS_GAUGE_TOWER_HIGHEST_HARD_FORK_MISMATCHED_SLOT_DESC "Highest slot where 52%+ of stake agreed on a different bank hash than ours (we hard forked), 0 if none" -#define FD_METRICS_GAUGE_TOWER_HIGHEST_HARD_FORK_MISMATCHED_SLOT_CVT (FD_METRICS_CONVERTER_NONE) - -#define FD_METRICS_COUNTER_TOWER_FORK_CHOSEN_NAME "tower_fork_chosen" -#define FD_METRICS_COUNTER_TOWER_FORK_CHOSEN_TYPE (FD_METRICS_TYPE_COUNTER) -#define FD_METRICS_COUNTER_TOWER_FORK_CHOSEN_DESC "Fork choices for voting and reset, by result" -#define FD_METRICS_COUNTER_TOWER_FORK_CHOSEN_CVT (FD_METRICS_CONVERTER_NONE) -#define FD_METRICS_COUNTER_TOWER_FORK_CHOSEN_CNT (7UL) - -#define FD_METRICS_COUNTER_TOWER_VOTE_GATE_CHECKED_NAME "tower_vote_gate_checked" -#define FD_METRICS_COUNTER_TOWER_VOTE_GATE_CHECKED_TYPE (FD_METRICS_TYPE_COUNTER) -#define FD_METRICS_COUNTER_TOWER_VOTE_GATE_CHECKED_DESC "Vote gating checks after a fork was chosen, by result" -#define FD_METRICS_COUNTER_TOWER_VOTE_GATE_CHECKED_CVT (FD_METRICS_CONVERTER_NONE) -#define FD_METRICS_COUNTER_TOWER_VOTE_GATE_CHECKED_CNT (5UL) - -#define FD_METRICS_COUNTER_TOWER_TOWER_FILE_WRITTEN_NAME "tower_tower_file_written" -#define FD_METRICS_COUNTER_TOWER_TOWER_FILE_WRITTEN_TYPE (FD_METRICS_TYPE_COUNTER) -#define FD_METRICS_COUNTER_TOWER_TOWER_FILE_WRITTEN_DESC "Times the tower file was written" -#define FD_METRICS_COUNTER_TOWER_TOWER_FILE_WRITTEN_CVT (FD_METRICS_CONVERTER_NONE) - -#define FD_METRICS_GAUGE_TOWER_TOWER_FILE_SLOT_NAME "tower_tower_file_slot" -#define FD_METRICS_GAUGE_TOWER_TOWER_FILE_SLOT_TYPE (FD_METRICS_TYPE_GAUGE) -#define FD_METRICS_GAUGE_TOWER_TOWER_FILE_SLOT_DESC "Newest vote slot in the last written tower file, 0 if not written yet" -#define FD_METRICS_GAUGE_TOWER_TOWER_FILE_SLOT_CVT (FD_METRICS_CONVERTER_NONE) - -#define FD_METRICS_GAUGE_TOWER_TOWER_FILE_SIZE_BYTES_NAME "tower_tower_file_size_bytes" -#define FD_METRICS_GAUGE_TOWER_TOWER_FILE_SIZE_BYTES_TYPE (FD_METRICS_TYPE_GAUGE) -#define FD_METRICS_GAUGE_TOWER_TOWER_FILE_SIZE_BYTES_DESC "Size of the last written tower file" -#define FD_METRICS_GAUGE_TOWER_TOWER_FILE_SIZE_BYTES_CVT (FD_METRICS_CONVERTER_NONE) - -#define FD_METRICS_COUNTER_TOWER_VOTE_TXN_PROCESSED_NAME "tower_vote_txn_processed" -#define FD_METRICS_COUNTER_TOWER_VOTE_TXN_PROCESSED_TYPE (FD_METRICS_TYPE_COUNTER) -#define FD_METRICS_COUNTER_TOWER_VOTE_TXN_PROCESSED_DESC "Vote transactions processed, by result" -#define FD_METRICS_COUNTER_TOWER_VOTE_TXN_PROCESSED_CVT (FD_METRICS_CONVERTER_NONE) -#define FD_METRICS_COUNTER_TOWER_VOTE_TXN_PROCESSED_CNT (10UL) - -#define FD_METRICS_COUNTER_TOWER_VOTE_SLOT_PROCESSED_NAME "tower_vote_slot_processed" -#define FD_METRICS_COUNTER_TOWER_VOTE_SLOT_PROCESSED_TYPE (FD_METRICS_TYPE_COUNTER) -#define FD_METRICS_COUNTER_TOWER_VOTE_SLOT_PROCESSED_DESC "Individual vote slots from vote transactions counted, by result" -#define FD_METRICS_COUNTER_TOWER_VOTE_SLOT_PROCESSED_CVT (FD_METRICS_CONVERTER_NONE) -#define FD_METRICS_COUNTER_TOWER_VOTE_SLOT_PROCESSED_CNT (6UL) - -#define FD_METRICS_COUNTER_TOWER_VOTE_INTERMEDIATE_SLOT_CHECKED_NAME "tower_vote_intermediate_slot_checked" -#define FD_METRICS_COUNTER_TOWER_VOTE_INTERMEDIATE_SLOT_CHECKED_TYPE (FD_METRICS_TYPE_COUNTER) -#define FD_METRICS_COUNTER_TOWER_VOTE_INTERMEDIATE_SLOT_CHECKED_DESC "Vote transactions whose last vote slot was checked before counting intermediate slots, by result" -#define FD_METRICS_COUNTER_TOWER_VOTE_INTERMEDIATE_SLOT_CHECKED_CVT (FD_METRICS_CONVERTER_NONE) -#define FD_METRICS_COUNTER_TOWER_VOTE_INTERMEDIATE_SLOT_CHECKED_CNT (3UL) - -#define FD_METRICS_COUNTER_TOWER_ACCDB_ACCOUNT_READONLY_ACQUIRED_NAME "tower_accdb_account_readonly_acquired" -#define FD_METRICS_COUNTER_TOWER_ACCDB_ACCOUNT_READONLY_ACQUIRED_TYPE (FD_METRICS_TYPE_COUNTER) -#define FD_METRICS_COUNTER_TOWER_ACCDB_ACCOUNT_READONLY_ACQUIRED_DESC "Readonly accounts acquired from the account database, by cache size class of the account's current data size" -#define FD_METRICS_COUNTER_TOWER_ACCDB_ACCOUNT_READONLY_ACQUIRED_CVT (FD_METRICS_CONVERTER_NONE) -#define FD_METRICS_COUNTER_TOWER_ACCDB_ACCOUNT_READONLY_ACQUIRED_CNT (8UL) - -#define FD_METRICS_COUNTER_TOWER_ACCDB_ACCOUNT_WRITABLE_ACQUIRED_NAME "tower_accdb_account_writable_acquired" -#define FD_METRICS_COUNTER_TOWER_ACCDB_ACCOUNT_WRITABLE_ACQUIRED_TYPE (FD_METRICS_TYPE_COUNTER) -#define FD_METRICS_COUNTER_TOWER_ACCDB_ACCOUNT_WRITABLE_ACQUIRED_DESC "Writable accounts acquired from the account database, by cache size class of the account's current data size" -#define FD_METRICS_COUNTER_TOWER_ACCDB_ACCOUNT_WRITABLE_ACQUIRED_CVT (FD_METRICS_CONVERTER_NONE) -#define FD_METRICS_COUNTER_TOWER_ACCDB_ACCOUNT_WRITABLE_ACQUIRED_CNT (8UL) - -#define FD_METRICS_COUNTER_TOWER_ACCDB_ACCOUNT_EVICTED_NAME "tower_accdb_account_evicted" -#define FD_METRICS_COUNTER_TOWER_ACCDB_ACCOUNT_EVICTED_TYPE (FD_METRICS_TYPE_COUNTER) -#define FD_METRICS_COUNTER_TOWER_ACCDB_ACCOUNT_EVICTED_DESC "Accounts evicted from the account database cache to make space, by cache size class" -#define FD_METRICS_COUNTER_TOWER_ACCDB_ACCOUNT_EVICTED_CVT (FD_METRICS_CONVERTER_NONE) -#define FD_METRICS_COUNTER_TOWER_ACCDB_ACCOUNT_EVICTED_CNT (8UL) - -#define FD_METRICS_COUNTER_TOWER_ACCDB_ACCOUNT_NEW_COMMITTED_NAME "tower_accdb_account_new_committed" -#define FD_METRICS_COUNTER_TOWER_ACCDB_ACCOUNT_NEW_COMMITTED_TYPE (FD_METRICS_TYPE_COUNTER) -#define FD_METRICS_COUNTER_TOWER_ACCDB_ACCOUNT_NEW_COMMITTED_DESC "New account versions committed to the index, by destination cache size class" -#define FD_METRICS_COUNTER_TOWER_ACCDB_ACCOUNT_NEW_COMMITTED_CVT (FD_METRICS_CONVERTER_NONE) -#define FD_METRICS_COUNTER_TOWER_ACCDB_ACCOUNT_NEW_COMMITTED_CNT (8UL) - -#define FD_METRICS_COUNTER_TOWER_ACCDB_ACCOUNT_OVERWRITE_COMMITTED_NAME "tower_accdb_account_overwrite_committed" -#define FD_METRICS_COUNTER_TOWER_ACCDB_ACCOUNT_OVERWRITE_COMMITTED_TYPE (FD_METRICS_TYPE_COUNTER) -#define FD_METRICS_COUNTER_TOWER_ACCDB_ACCOUNT_OVERWRITE_COMMITTED_DESC "Account versions committed over an existing version of the same fork and generation, by destination cache size class" -#define FD_METRICS_COUNTER_TOWER_ACCDB_ACCOUNT_OVERWRITE_COMMITTED_CVT (FD_METRICS_CONVERTER_NONE) -#define FD_METRICS_COUNTER_TOWER_ACCDB_ACCOUNT_OVERWRITE_COMMITTED_CNT (8UL) - -#define FD_METRICS_COUNTER_TOWER_ACCDB_ACCOUNT_CACHE_MISSED_NAME "tower_accdb_account_cache_missed" -#define FD_METRICS_COUNTER_TOWER_ACCDB_ACCOUNT_CACHE_MISSED_TYPE (FD_METRICS_TYPE_COUNTER) -#define FD_METRICS_COUNTER_TOWER_ACCDB_ACCOUNT_CACHE_MISSED_DESC "Accounts not found in the account database cache and read from disk, by cache size class" -#define FD_METRICS_COUNTER_TOWER_ACCDB_ACCOUNT_CACHE_MISSED_CVT (FD_METRICS_CONVERTER_NONE) -#define FD_METRICS_COUNTER_TOWER_ACCDB_ACCOUNT_CACHE_MISSED_CNT (8UL) - -#define FD_METRICS_COUNTER_TOWER_ACCDB_ACCOUNT_WAITED_NAME "tower_accdb_account_waited" -#define FD_METRICS_COUNTER_TOWER_ACCDB_ACCOUNT_WAITED_TYPE (FD_METRICS_TYPE_COUNTER) -#define FD_METRICS_COUNTER_TOWER_ACCDB_ACCOUNT_WAITED_DESC "Accounts that had to wait for another reader to finish loading them into the cache" -#define FD_METRICS_COUNTER_TOWER_ACCDB_ACCOUNT_WAITED_CVT (FD_METRICS_CONVERTER_NONE) - -#define FD_METRICS_COUNTER_TOWER_ACCDB_BATCH_ACQUIRED_NAME "tower_accdb_batch_acquired" -#define FD_METRICS_COUNTER_TOWER_ACCDB_BATCH_ACQUIRED_TYPE (FD_METRICS_TYPE_COUNTER) -#define FD_METRICS_COUNTER_TOWER_ACCDB_BATCH_ACQUIRED_DESC "Batch acquire calls made by this tile" -#define FD_METRICS_COUNTER_TOWER_ACCDB_BATCH_ACQUIRED_CVT (FD_METRICS_CONVERTER_NONE) - -#define FD_METRICS_COUNTER_TOWER_ACCDB_CACHE_RESERVE_RETRIED_NAME "tower_accdb_cache_reserve_retried" -#define FD_METRICS_COUNTER_TOWER_ACCDB_CACHE_RESERVE_RETRIED_TYPE (FD_METRICS_TYPE_COUNTER) -#define FD_METRICS_COUNTER_TOWER_ACCDB_CACHE_RESERVE_RETRIED_DESC "Spin retries while waiting to reserve cache lines for a transaction" -#define FD_METRICS_COUNTER_TOWER_ACCDB_CACHE_RESERVE_RETRIED_CVT (FD_METRICS_CONVERTER_NONE) - -#define FD_METRICS_COUNTER_TOWER_ACCDB_DISK_READ_BYTES_NAME "tower_accdb_disk_read_bytes" -#define FD_METRICS_COUNTER_TOWER_ACCDB_DISK_READ_BYTES_TYPE (FD_METRICS_TYPE_COUNTER) -#define FD_METRICS_COUNTER_TOWER_ACCDB_DISK_READ_BYTES_DESC "Bytes read from the account database file" -#define FD_METRICS_COUNTER_TOWER_ACCDB_DISK_READ_BYTES_CVT (FD_METRICS_CONVERTER_NONE) - -#define FD_METRICS_COUNTER_TOWER_ACCDB_DISK_READ_NAME "tower_accdb_disk_read" -#define FD_METRICS_COUNTER_TOWER_ACCDB_DISK_READ_TYPE (FD_METRICS_TYPE_COUNTER) -#define FD_METRICS_COUNTER_TOWER_ACCDB_DISK_READ_DESC "Reads from the account database file" -#define FD_METRICS_COUNTER_TOWER_ACCDB_DISK_READ_CVT (FD_METRICS_CONVERTER_NONE) - -#define FD_METRICS_COUNTER_TOWER_ACCDB_DISK_WRITTEN_BYTES_NAME "tower_accdb_disk_written_bytes" -#define FD_METRICS_COUNTER_TOWER_ACCDB_DISK_WRITTEN_BYTES_TYPE (FD_METRICS_TYPE_COUNTER) -#define FD_METRICS_COUNTER_TOWER_ACCDB_DISK_WRITTEN_BYTES_DESC "Bytes written to the account database file" -#define FD_METRICS_COUNTER_TOWER_ACCDB_DISK_WRITTEN_BYTES_CVT (FD_METRICS_CONVERTER_NONE) - -#define FD_METRICS_COUNTER_TOWER_ACCDB_DISK_WRITTEN_NAME "tower_accdb_disk_written" -#define FD_METRICS_COUNTER_TOWER_ACCDB_DISK_WRITTEN_TYPE (FD_METRICS_TYPE_COUNTER) -#define FD_METRICS_COUNTER_TOWER_ACCDB_DISK_WRITTEN_DESC "Writes to the account database file" -#define FD_METRICS_COUNTER_TOWER_ACCDB_DISK_WRITTEN_CVT (FD_METRICS_CONVERTER_NONE) - -#define FD_METRICS_COUNTER_TOWER_ACCDB_COPIED_BYTES_NAME "tower_accdb_copied_bytes" -#define FD_METRICS_COUNTER_TOWER_ACCDB_COPIED_BYTES_TYPE (FD_METRICS_TYPE_COUNTER) -#define FD_METRICS_COUNTER_TOWER_ACCDB_COPIED_BYTES_DESC "Account data bytes copied in memory between cache lines, staging buffers and callers; disk reads excluded" -#define FD_METRICS_COUNTER_TOWER_ACCDB_COPIED_BYTES_CVT (FD_METRICS_CONVERTER_NONE) - -#define FD_METRICS_TOWER_TOTAL (114UL) -extern const fd_metrics_meta_t FD_METRICS_TOWER[FD_METRICS_TOWER_TOTAL]; - -#endif /* HEADER_fd_src_disco_metrics_generated_fd_metrics_tower_h */ diff --git a/src/disco/metrics/metrics.xml b/src/disco/metrics/metrics.xml index 1727e30eba3..07223ed56d9 100644 --- a/src/disco/metrics/metrics.xml +++ b/src/disco/metrics/metrics.xml @@ -1731,144 +1731,6 @@ deliberate exception with lint-allow="") - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/disco/topo/fd_topo.h b/src/disco/topo/fd_topo.h index aef6fdb183b..2c4060088bf 100644 --- a/src/disco/topo/fd_topo.h +++ b/src/disco/topo/fd_topo.h @@ -617,24 +617,6 @@ struct fd_topo_tile { long boot_timestamp_nanos; } backtest; - struct { - ulong accdb_obj_id; - - ulong authorized_voter_paths_cnt; - char authorized_voter_paths[ FD_KEYGUARD_AUTH_VOTERS_MAX ][ PATH_MAX ]; - int hard_fork_fatal; - ulong wait_for_supermajority_at_slot; - int wait_for_supermajority_hash_is_zero; - ushort expected_shred_version; - ulong max_live_slots; - char identity_key[ PATH_MAX ]; - char vote_account[ PATH_MAX ]; - char tower_path[ PATH_MAX ]; - uint target_gid; - uint target_uid; - ulong max_shreds_per_block; - } tower; - struct { char identity_key_path[ PATH_MAX ]; char vote_history_path[ PATH_MAX ]; diff --git a/src/discof/forest/Local.mk b/src/discof/forest/Local.mk deleted file mode 100644 index 0737a5ae3ec..00000000000 --- a/src/discof/forest/Local.mk +++ /dev/null @@ -1,5 +0,0 @@ -$(call add-objs,fd_forest,fd_discof) -ifdef FD_HAS_HOSTED -$(call make-unit-test,test_forest,test_forest,fd_discof fd_disco fd_flamenco fd_tango fd_ballet fd_util) -$(call run-unit-test,test_forest) -endif diff --git a/src/discof/forest/fd_forest.c b/src/discof/forest/fd_forest.c deleted file mode 100644 index 2927721654c..00000000000 --- a/src/discof/forest/fd_forest.c +++ /dev/null @@ -1,2151 +0,0 @@ -#include "fd_forest.h" - -static fd_hash_t empty_mr = { .ul = { 0, 0, 0, 0 } }; -static fd_hash_t invalid_mr = { .ul = { ULONG_MAX, ULONG_MAX, ULONG_MAX, ULONG_MAX } }; - -static ulong * -fd_forest_deque( fd_forest_t * forest ) { - return fd_wksp_laddr_fast( fd_forest_wksp( forest ), forest->deque_gaddr ); -} - -void * -fd_forest_new( void * shmem, ulong ele_max, ulong shred_max, ulong seed ) { - FD_TEST( fd_ulong_is_pow2( ele_max ) ); - - if( FD_UNLIKELY( !shmem ) ) { - FD_LOG_WARNING(( "NULL mem" )); - return NULL; - } - - if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)shmem, fd_forest_align() ) ) ) { - FD_LOG_WARNING(( "misaligned mem" )); - return NULL; - } - - if( FD_UNLIKELY( !shred_max || shred_max%FD_FEC_SHRED_CNT || shred_max>UINT_MAX ) ) { - FD_LOG_WARNING(( "bad shred_max (%lu)", shred_max )); - return NULL; - } - - ulong footprint = fd_forest_footprint( ele_max, shred_max ); - if( FD_UNLIKELY( !footprint ) ) { - FD_LOG_WARNING(( "bad ele_max (%lu)", ele_max )); - return NULL; - } - - fd_wksp_t * wksp = fd_wksp_containing( shmem ); - if( FD_UNLIKELY( !wksp ) ) { - FD_LOG_WARNING(( "shmem must be part of a workspace" )); - return NULL; - } - - fd_memset( shmem, 0, footprint ); - fd_forest_t * forest; - - ulong idxs_sz = ele_max*fd_forest_blk_idxs_word_cnt( shred_max )*sizeof(fd_forest_blk_idxs_t); - ulong mroots_sz = ele_max*(shred_max/FD_FEC_SHRED_CNT)*sizeof(fd_forest_mr_t); - ulong recv_sz = ele_max*(shred_max/FD_FEC_SHRED_CNT)*sizeof(fd_forest_recv_t); - - FD_SCRATCH_ALLOC_INIT( l, shmem ); - forest = FD_SCRATCH_ALLOC_APPEND( l, fd_forest_align(), sizeof(fd_forest_t) ); - void * pool = FD_SCRATCH_ALLOC_APPEND( l, fd_forest_pool_align(), fd_forest_pool_footprint ( ele_max ) ); - void * idxs = FD_SCRATCH_ALLOC_APPEND( l, 128UL, idxs_sz ); - void * code = FD_SCRATCH_ALLOC_APPEND( l, 128UL, idxs_sz ); - void * mroots = FD_SCRATCH_ALLOC_APPEND( l, 128UL, mroots_sz ); - void * recv = FD_SCRATCH_ALLOC_APPEND( l, 128UL, recv_sz ); - void * ancestry = FD_SCRATCH_ALLOC_APPEND( l, fd_forest_ancestry_align(), fd_forest_ancestry_footprint( ele_max ) ); - void * frontier = FD_SCRATCH_ALLOC_APPEND( l, fd_forest_frontier_align(), fd_forest_frontier_footprint( ele_max ) ); - void * subtrees = FD_SCRATCH_ALLOC_APPEND( l, fd_forest_subtrees_align(), fd_forest_subtrees_footprint( ele_max ) ); - void * orphaned = FD_SCRATCH_ALLOC_APPEND( l, fd_forest_orphaned_align(), fd_forest_orphaned_footprint( ele_max ) ); - void * subtlist = FD_SCRATCH_ALLOC_APPEND( l, fd_forest_subtlist_align(), fd_forest_subtlist_footprint( ) ); - void * orphanq = FD_SCRATCH_ALLOC_APPEND( l, fd_forest_orphanq_align(), fd_forest_orphanq_footprint ( 2UL*ele_max ) ); - - /* indexers */ - - void * requestd = FD_SCRATCH_ALLOC_APPEND( l, fd_forest_requests_align(), fd_forest_requests_footprint( ele_max ) ); - void * reqslist = FD_SCRATCH_ALLOC_APPEND( l, fd_forest_reqslist_align(), fd_forest_reqslist_footprint( ) ); - void * reqspool = FD_SCRATCH_ALLOC_APPEND( l, fd_forest_reqspool_align(), fd_forest_reqspool_footprint( ele_max ) ); - void * consumed = FD_SCRATCH_ALLOC_APPEND( l, fd_forest_consumed_align(), fd_forest_consumed_footprint( ele_max ) ); - void * conslist = FD_SCRATCH_ALLOC_APPEND( l, fd_forest_conslist_align(), fd_forest_conslist_footprint( ) ); - void * conspool = FD_SCRATCH_ALLOC_APPEND( l, fd_forest_conspool_align(), fd_forest_conspool_footprint( ele_max ) ); - void * orphreqs = FD_SCRATCH_ALLOC_APPEND( l, fd_forest_requests_align(), fd_forest_requests_footprint( ele_max ) ); - void * orphlist = FD_SCRATCH_ALLOC_APPEND( l, fd_forest_reqslist_align(), fd_forest_reqslist_footprint( ) ); - void * deque = FD_SCRATCH_ALLOC_APPEND( l, fd_forest_deque_align(), fd_forest_deque_footprint ( ele_max ) ); - FD_TEST( FD_SCRATCH_ALLOC_FINI( l, fd_forest_align() ) == (ulong)shmem + footprint ); - - forest->root = ULONG_MAX; - forest->wksp_gaddr = fd_wksp_gaddr_fast( wksp, forest ); - forest->pool_gaddr = fd_wksp_gaddr_fast( wksp, fd_forest_pool_join ( fd_forest_pool_new ( pool, ele_max ) ) ); - forest->shred_max = shred_max; - forest->idxs_gaddr = fd_wksp_gaddr_fast( wksp, idxs ); - forest->code_gaddr = fd_wksp_gaddr_fast( wksp, code ); - forest->mroots_gaddr = fd_wksp_gaddr_fast( wksp, mroots ); - forest->recv_gaddr = fd_wksp_gaddr_fast( wksp, recv ); - forest->ancestry_gaddr = fd_wksp_gaddr_fast( wksp, fd_forest_ancestry_join( fd_forest_ancestry_new( ancestry, ele_max, seed ) ) ); - forest->frontier_gaddr = fd_wksp_gaddr_fast( wksp, fd_forest_frontier_join( fd_forest_frontier_new( frontier, ele_max, seed ) ) ); - forest->subtrees_gaddr = fd_wksp_gaddr_fast( wksp, fd_forest_subtrees_join( fd_forest_subtrees_new( subtrees, ele_max, seed ) ) ); - forest->orphaned_gaddr = fd_wksp_gaddr_fast( wksp, fd_forest_orphaned_join( fd_forest_orphaned_new( orphaned, ele_max, seed ) ) ); - forest->subtlist_gaddr = fd_wksp_gaddr_fast( wksp, fd_forest_subtlist_join( fd_forest_subtlist_new( subtlist ) ) ); - forest->orphanq_gaddr = fd_wksp_gaddr_fast( wksp, fd_forest_orphanq_join ( fd_forest_orphanq_new ( orphanq, 2UL*ele_max ) ) ); - forest->orphan_seq_next = 0UL; - - /* indexers */ - - forest->requests_gaddr = fd_wksp_gaddr_fast( wksp, fd_forest_requests_join( fd_forest_requests_new( requestd, ele_max, seed ) ) ); - forest->reqslist_gaddr = fd_wksp_gaddr_fast( wksp, fd_forest_reqslist_join( fd_forest_reqslist_new( reqslist ) ) ); - forest->reqspool_gaddr = fd_wksp_gaddr_fast( wksp, fd_forest_reqspool_join( fd_forest_reqspool_new( reqspool, ele_max ) ) ); - forest->consumed_gaddr = fd_wksp_gaddr_fast( wksp, fd_forest_consumed_join( fd_forest_consumed_new( consumed, ele_max, seed ) ) ); - forest->conslist_gaddr = fd_wksp_gaddr_fast( wksp, fd_forest_conslist_join( fd_forest_conslist_new( conslist ) ) ); - forest->conspool_gaddr = fd_wksp_gaddr_fast( wksp, fd_forest_conspool_join( fd_forest_conspool_new( conspool, ele_max ) ) ); - forest->orphreqs_gaddr = fd_wksp_gaddr_fast( wksp, fd_forest_requests_join( fd_forest_requests_new( orphreqs, ele_max, seed ) ) ); - forest->orphlist_gaddr = fd_wksp_gaddr_fast( wksp, fd_forest_reqslist_join( fd_forest_reqslist_new( orphlist ) ) ); - forest->deque_gaddr = fd_wksp_gaddr_fast( wksp, fd_forest_deque_join ( fd_forest_deque_new ( deque, ele_max ) ) ); - forest->iter = (fd_forest_iter_t){ .ele_idx = ULONG_MAX, .list_gaddr = forest->reqslist_gaddr }; - forest->orphiter = (fd_forest_iter_t){ .ele_idx = ULONG_MAX, .list_gaddr = forest->orphlist_gaddr }; - - FD_COMPILER_MFENCE(); - FD_VOLATILE( forest->magic ) = FD_FOREST_MAGIC; - FD_COMPILER_MFENCE(); - - return shmem; -} - -fd_forest_t * -fd_forest_join( void * shforest ) { - fd_forest_t * forest = (fd_forest_t *)shforest; - - if( FD_UNLIKELY( !forest ) ) { - FD_LOG_WARNING(( "NULL forest" )); - return NULL; - } - - if( FD_UNLIKELY( !fd_ulong_is_aligned((ulong)forest, fd_forest_align() ) ) ) { - FD_LOG_WARNING(( "misaligned forest" )); - return NULL; - } - - fd_wksp_t * wksp = fd_wksp_containing( forest ); - if( FD_UNLIKELY( !wksp ) ) { - FD_LOG_WARNING(( "forest must be part of a workspace" )); - return NULL; - } - - return forest; -} - -void * -fd_forest_leave( fd_forest_t const * forest ) { - - if( FD_UNLIKELY( !forest ) ) { - FD_LOG_WARNING(( "NULL forest" )); - return NULL; - } - - return (void *)forest; -} - -void * -fd_forest_delete( void * forest ) { - - if( FD_UNLIKELY( !forest ) ) { - FD_LOG_WARNING(( "NULL forest" )); - return NULL; - } - - if( FD_UNLIKELY( !fd_ulong_is_aligned((ulong)forest, fd_forest_align() ) ) ) { - FD_LOG_WARNING(( "misaligned forest" )); - return NULL; - } - - // TODO: zero out mem? - - return forest; -} - -static void -requests_insert( fd_forest_t * forest, - fd_forest_requests_t * reqsmap, - fd_forest_reqslist_t * reqslist, - ulong pool_idx ) { - fd_forest_ref_t * pool = fd_forest_reqspool( forest ); - if( fd_forest_requests_ele_query( reqsmap, &pool_idx, NULL, pool ) ) return; - fd_forest_ref_t * ele = fd_forest_reqspool_ele_acquire( pool ); - ele->idx = pool_idx; - fd_forest_requests_ele_insert( reqsmap, ele, pool ); - fd_forest_reqslist_ele_push_tail( reqslist, ele, pool ); -} - -static void -requests_remove( fd_forest_t * forest, - fd_forest_requests_t * reqsmap, - fd_forest_reqslist_t * reqslist, - fd_forest_iter_t * reqiter, - ulong pool_idx ) { - fd_forest_ref_t * pool = fd_forest_reqspool( forest ); - fd_forest_ref_t * ele; - if( FD_LIKELY( ele = fd_forest_requests_ele_remove( reqsmap, &pool_idx, NULL, pool ) ) ) { - /* invalidate the iterator if it is on the removed slot. */ - if( FD_UNLIKELY( reqiter->ele_idx == pool_idx ) ) { - reqiter->ele_idx = ULONG_MAX; - } - fd_forest_reqslist_ele_remove( reqslist, ele, pool ); - fd_forest_reqspool_ele_release( pool, ele ); - } -} - -static void -consumed_insert( fd_forest_t * forest, ulong pool_idx ) { - fd_forest_consumed_t * consumed = fd_forest_consumed( forest ); - fd_forest_ref_t * pool = fd_forest_conspool( forest ); - fd_forest_ref_t * ele = fd_forest_conspool_ele_acquire( pool ); - ele->idx = pool_idx; - fd_forest_consumed_ele_insert( consumed, ele, pool ); - fd_forest_conslist_ele_push_tail( fd_forest_conslist( forest ), ele, pool ); -} - -static void -consumed_remove( fd_forest_t * forest, ulong forest_pool_idx ) { - fd_forest_consumed_t * consumed = fd_forest_consumed( forest ); - fd_forest_ref_t * pool = fd_forest_conspool( forest ); - fd_forest_ref_t * ele; - if( FD_LIKELY( ele = fd_forest_consumed_ele_remove( consumed, &forest_pool_idx, NULL, pool ) ) ) { - fd_forest_conslist_ele_remove( fd_forest_conslist( forest ), ele, pool ); - fd_forest_conspool_ele_release( pool, ele ); - } -} - -fd_forest_t * -fd_forest_init( fd_forest_t * forest, ulong root_slot ) { - fd_forest_blk_t * pool = fd_forest_pool( forest ); - ulong null = fd_forest_pool_idx_null( pool ); - fd_forest_frontier_t * frontier = fd_forest_frontier( forest ); - - /* Initialize the root node from a pool element. */ - - fd_forest_blk_t * root_ele = fd_forest_pool_ele_acquire( pool ); - root_ele->slot = root_slot; - root_ele->parent = null; - root_ele->child = null; - root_ele->sibling = null; - root_ele->orphan_seq = ULONG_MAX; - root_ele->buffered_idx = 0; - root_ele->complete_idx = 0; - root_ele->chain_confirmed = 1; - - fd_forest_blk_mroots( forest, root_ele )[0].mr = (fd_hash_t){ .key = { 0 } }; - - forest->root = fd_forest_pool_idx( pool, root_ele ); - fd_forest_frontier_ele_insert( frontier, root_ele, pool ); /* cannot fail */ - consumed_insert( forest, fd_forest_pool_idx( pool, root_ele ) ); - - /* Sanity checks. */ - - FD_TEST( root_ele == fd_forest_frontier_ele_query( frontier, &root_slot, NULL, pool )); - FD_TEST( root_ele->slot == root_slot ); - - return forest; -} - -int -fd_forest_verify( fd_forest_t const * forest ) { - #define FAIL( msg ) do { FD_LOG_WARNING(( "fd_forest_verify: %s", msg )); return -1; } while(0) - if( FD_UNLIKELY( !forest ) ) { - FAIL( "NULL forest" ); - } - - if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)forest, fd_forest_align() ) ) ) { - FAIL( "misaligned forest" ); - } - - fd_wksp_t * wksp = fd_wksp_containing( forest ); - if( FD_UNLIKELY( !wksp ) ) { - FAIL( "forest must be part of a workspace" ); - } - - if( FD_UNLIKELY( forest->magic!=FD_FOREST_MAGIC ) ) { - FAIL( "bad magic" ); - } - - fd_forest_blk_t const * pool = fd_forest_pool_const( forest ); - - fd_forest_frontier_t const * frontier = fd_forest_frontier_const( forest ); - fd_forest_orphaned_t const * orphaned = fd_forest_orphaned_const( forest ); - fd_forest_ancestry_t const * ancestry = fd_forest_ancestry_const( forest ); - fd_forest_subtrees_t const * subtrees = fd_forest_subtrees_const( forest ); - - if( fd_forest_ancestry_verify( ancestry, fd_forest_pool_max( pool ), pool ) == -1 ) FAIL( "ancestry map corrupted" ); - if( fd_forest_frontier_verify( frontier, fd_forest_pool_max( pool ), pool ) == -1 ) FAIL( "frontier map corrupted" ); - if( fd_forest_subtrees_verify( subtrees, fd_forest_pool_max( pool ), pool ) == -1 ) FAIL( "subtrees map corrupted" ); - if( fd_forest_orphaned_verify( orphaned, fd_forest_pool_max( pool ), pool ) == -1 ) FAIL( "orphaned map corrupted" ); - - /* Invariant: elements can only appear in one of the four maps. */ - for( fd_forest_frontier_iter_t iter = fd_forest_frontier_iter_init( frontier, pool ); !fd_forest_frontier_iter_done( iter, frontier, pool ); iter = fd_forest_frontier_iter_next( iter, frontier, pool ) ) { - fd_forest_blk_t const * ele = fd_forest_frontier_iter_ele_const( iter, frontier, pool ); - if( fd_forest_ancestry_ele_query_const( ancestry, &ele->slot, NULL, pool ) ) FAIL( "element in frontier map also in ancestry map" ); - if( fd_forest_orphaned_ele_query_const( orphaned, &ele->slot, NULL, pool ) ) FAIL( "element in frontier map also in orphaned map" ); - if( fd_forest_subtrees_ele_query_const( subtrees, &ele->slot, NULL, pool ) ) FAIL( "element in frontier map also in subtrees map" ); - } - - for( fd_forest_orphaned_iter_t iter = fd_forest_orphaned_iter_init( orphaned, pool ); !fd_forest_orphaned_iter_done( iter, orphaned, pool ); iter = fd_forest_orphaned_iter_next( iter, orphaned, pool ) ) { - fd_forest_blk_t const * ele = fd_forest_orphaned_iter_ele_const( iter, orphaned, pool ); - if( fd_forest_ancestry_ele_query_const( ancestry, &ele->slot, NULL, pool ) ) FAIL( "element in orphaned map also in ancestry map" ); - if( fd_forest_frontier_ele_query_const( frontier, &ele->slot, NULL, pool ) ) FAIL( "element in orphaned map also in frontier map" ); - if( fd_forest_subtrees_ele_query_const( subtrees, &ele->slot, NULL, pool ) ) FAIL( "element in orphaned map also in subtrees map" ); - } - - for( fd_forest_subtrees_iter_t iter = fd_forest_subtrees_iter_init( subtrees, pool ); !fd_forest_subtrees_iter_done( iter, subtrees, pool ); iter = fd_forest_subtrees_iter_next( iter, subtrees, pool ) ) { - fd_forest_blk_t const * ele = fd_forest_subtrees_iter_ele_const( iter, subtrees, pool ); - if( fd_forest_ancestry_ele_query_const( ancestry, &ele->slot, NULL, pool ) ) FAIL( "element in subtrees map also in ancestry map" ); - if( fd_forest_frontier_ele_query_const( frontier, &ele->slot, NULL, pool ) ) FAIL( "element in subtrees map also in frontier map" ); - if( fd_forest_orphaned_ele_query_const( orphaned, &ele->slot, NULL, pool ) ) FAIL( "element in subtrees map also in orphaned map" ); - } - - fd_forest_subtlist_t const * subtlist = fd_forest_subtlist_const( forest ); - fd_forest_orphan_ent_t const * orphanq = fd_forest_orphanq_const( forest ); - ulong live = 0UL; - for( ulong i=0UL; iorphan_seq==orphanq[ i ].seq ) live++; - } - ulong head_cnt = 0UL; - for( fd_forest_subtlist_iter_t iter = fd_forest_subtlist_iter_fwd_init( subtlist, pool ); - !fd_forest_subtlist_iter_done( iter, subtlist, pool ); - iter = fd_forest_subtlist_iter_fwd_next( iter, subtlist, pool ) ) head_cnt++; - if( FD_UNLIKELY( live!=head_cnt ) ) FAIL( "orphanq live entries != subtree heads" ); - - fd_forest_consumed_t const * consumed = fd_forest_consumed_const( forest ); - fd_forest_ref_t const * conspool = fd_forest_conspool_const( forest ); - - /* from every frontier walk back and verify that there is an ancestor in the consumed map */ - for( fd_forest_frontier_iter_t iter = fd_forest_frontier_iter_init( frontier, pool ); !fd_forest_frontier_iter_done( iter, frontier, pool ); iter = fd_forest_frontier_iter_next( iter, frontier, pool ) ) { - fd_forest_blk_t const * ele = fd_forest_frontier_iter_ele_const( iter, frontier, pool ); - int found = 0; - ulong steps = 0; - while( FD_LIKELY( ele ) ) { - if( FD_UNLIKELY( ++steps > fd_forest_pool_max( pool ) ) ) FAIL( "frontier parent chain longer than pool (cycle detected)" ); - ulong ele_idx = fd_forest_pool_idx( pool, ele ); - if( fd_forest_consumed_ele_query_const( consumed, &ele_idx, NULL, conspool ) ) { - found = 1; - break; - } - ele = fd_forest_pool_ele_const( pool, ele->parent ); - } - if( FD_UNLIKELY( !found ) ) FAIL( "element in frontier map does not have an ancestor in the consumed map" ); - } - - /* Consumed map elements must be in the frontier or ancestry map. */ - - for( fd_forest_consumed_iter_t iter = fd_forest_consumed_iter_init( consumed, conspool ); !fd_forest_consumed_iter_done( iter, consumed, conspool ); iter = fd_forest_consumed_iter_next( iter, consumed, conspool ) ) { - fd_forest_ref_t const * ele = fd_forest_consumed_iter_ele_const( iter, consumed, conspool ); - fd_forest_blk_t const * ele_ = fd_forest_pool_ele_const( pool, ele->idx ); - if( !fd_forest_ancestry_ele_query_const( ancestry, &ele_->slot, NULL, pool ) && !fd_forest_frontier_ele_query_const( frontier, &ele_->slot, NULL, pool ) ) { - FAIL( "element in consumed map not in the ancestry or frontier map" ); - } - } - - /* Request map + list invariants */ - fd_forest_requests_t const * requests = fd_forest_requests_const( forest ); - fd_forest_reqslist_t const * reqslist = fd_forest_reqslist_const( forest ); - fd_forest_ref_t const * reqspool = fd_forest_reqspool_const( forest ); - - if( forest->iter.ele_idx != fd_forest_pool_idx_null( pool ) && - forest->iter.ele_idx != fd_forest_reqslist_ele_peek_head_const( reqslist, reqspool )->idx ) { - FAIL( "iterator is not at the head of the request list" ); - } - - /* Every element in the request list must be in the request map */ - for( fd_forest_reqslist_iter_t iter = fd_forest_reqslist_iter_fwd_init( reqslist, reqspool ); !fd_forest_reqslist_iter_done( iter, reqslist, reqspool ); iter = fd_forest_reqslist_iter_fwd_next( iter, reqslist, reqspool ) ) { - fd_forest_ref_t const * ele = fd_forest_reqslist_iter_ele_const( iter, reqslist, reqspool ); - fd_forest_blk_t const * ele_ = fd_forest_pool_ele_const( pool, ele->idx ); - if( !fd_forest_ancestry_ele_query_const( ancestry, &ele_->slot, NULL, pool ) && !fd_forest_frontier_ele_query_const( frontier, &ele_->slot, NULL, pool ) ) { - FAIL( "element in request list not in the ancestry or frontier map" ); - } - if( !fd_forest_requests_ele_query_const( requests, &ele->idx, NULL, reqspool ) ) FAIL( "element in request list not in the request map" ); - } - - /* Orphan request map + list invariants. The orphan request map and - list share the same reqspool with the main request map and list, so - in addition to being internally consistent, a block must never be in - both the main request map and the orphan request map - that would - leave duplicate references to the same pool idx and corrupt the - shared pool when one of them is removed. */ - - fd_forest_requests_t const * orphreqs = fd_forest_orphreqs_const( forest ); - fd_forest_reqslist_t const * orphlist = fd_forest_orphlist_const( forest ); - - if( forest->orphiter.ele_idx != fd_forest_pool_idx_null( pool ) && - forest->orphiter.ele_idx != fd_forest_reqslist_ele_peek_head_const( orphlist, reqspool )->idx ) { - FAIL( "orphan iterator is not at the head of the orphan request list" ); - } - - for( fd_forest_reqslist_iter_t iter = fd_forest_reqslist_iter_fwd_init( orphlist, reqspool ); !fd_forest_reqslist_iter_done( iter, orphlist, reqspool ); iter = fd_forest_reqslist_iter_fwd_next( iter, orphlist, reqspool ) ) { - fd_forest_ref_t const * ele = fd_forest_reqslist_iter_ele_const( iter, orphlist, reqspool ); - fd_forest_blk_t const * ele_ = fd_forest_pool_ele_const( pool, ele->idx ); - if( !fd_forest_subtrees_ele_query_const( subtrees, &ele_->slot, NULL, pool ) && !fd_forest_orphaned_ele_query_const( orphaned, &ele_->slot, NULL, pool ) ) { - FAIL( "element in orphan request list not in the subtrees or orphaned map" ); - } - if( !fd_forest_requests_ele_query_const( orphreqs, &ele->idx, NULL, reqspool ) ) FAIL( "element in orphan request list not in the orphan request map" ); - if( fd_forest_requests_ele_query_const( requests, &ele->idx, NULL, reqspool ) ) FAIL( "element in both the main request map and the orphan request map" ); - } - - /* Tree structure invariants. Walk every element in every map and - verify the left-child / right-sibling / parent links are mutually - consistent, that slots strictly increase from parent to child, that - there are no cycles, and that each element lives in the correct map - for its position in the tree. */ - - ulong null = fd_forest_pool_idx_null( pool ); - ulong max = fd_forest_pool_max( pool ); - - /* Root must exist, be parentless, and live in ancestry or frontier. */ - - if( FD_UNLIKELY( forest->root == null ) ) FAIL( "root is null" ); - { - fd_forest_blk_t const * root = fd_forest_pool_ele_const( pool, forest->root ); - if( FD_UNLIKELY( root->parent != null ) ) FAIL( "root has a parent" ); - if( FD_UNLIKELY( !fd_forest_ancestry_ele_query_const( ancestry, &root->slot, NULL, pool ) && - !fd_forest_frontier_ele_query_const( frontier, &root->slot, NULL, pool ) ) ) { - FAIL( "root not in ancestry or frontier" ); - } - } - - /* Per-element parent/child/sibling link checks, applied to every - element regardless of which map it lives in. */ - -# define CHECK_TREE_ELE( ele ) do { \ - ulong ele_idx = fd_forest_pool_idx( pool, (ele) ); \ - if( (ele)->parent != null ) { \ - fd_forest_blk_t const * p = fd_forest_pool_ele_const( pool, (ele)->parent ); \ - if( FD_UNLIKELY( (ele)->parent_slot != p->slot ) ) FAIL( "parent_slot != parent ele slot" ); \ - if( FD_UNLIKELY( (ele)->slot <= p->slot ) ) FAIL( "child slot <= parent slot" ); \ - } \ - ulong steps = 0; \ - ulong child_idx = (ele)->child; \ - while( child_idx != null ) { \ - if( FD_UNLIKELY( ++steps > max ) ) FAIL( "sibling chain longer than pool (cycle)" ); \ - fd_forest_blk_t const * c = fd_forest_pool_ele_const( pool, child_idx ); \ - if( FD_UNLIKELY( c->parent != ele_idx ) ) FAIL( "child->parent does not point back to ele" ); \ - if( FD_UNLIKELY( c->parent_slot != (ele)->slot ) ) FAIL( "child->parent_slot mismatch" ); \ - if( FD_UNLIKELY( c->slot <= (ele)->slot ) ) FAIL( "child slot <= parent slot" ); \ - child_idx = c->sibling; \ - } \ - } while(0) - - /* ancestry: connected interior nodes - must have at least one child, - and may only be parentless if it is the root. */ - - for( fd_forest_ancestry_iter_t iter = fd_forest_ancestry_iter_init( ancestry, pool ); !fd_forest_ancestry_iter_done( iter, ancestry, pool ); iter = fd_forest_ancestry_iter_next( iter, ancestry, pool ) ) { - fd_forest_blk_t const * ele = fd_forest_ancestry_iter_ele_const( iter, ancestry, pool ); - CHECK_TREE_ELE( ele ); - if( FD_UNLIKELY( ele->child == null ) ) FAIL( "ancestry element has no child" ); - if( FD_UNLIKELY( ele->parent == null && fd_forest_pool_idx( pool, ele ) != forest->root ) ) FAIL( "ancestry element parentless but not root" ); - } - - /* frontier: tips of the connected tree - must be childless, and may - only be parentless if it is the root. */ - - for( fd_forest_frontier_iter_t iter = fd_forest_frontier_iter_init( frontier, pool ); !fd_forest_frontier_iter_done( iter, frontier, pool ); iter = fd_forest_frontier_iter_next( iter, frontier, pool ) ) { - fd_forest_blk_t const * ele = fd_forest_frontier_iter_ele_const( iter, frontier, pool ); - CHECK_TREE_ELE( ele ); - if( FD_UNLIKELY( ele->child != null ) ) FAIL( "frontier element has a child" ); - if( FD_UNLIKELY( ele->parent == null && fd_forest_pool_idx( pool, ele ) != forest->root ) ) FAIL( "frontier element parentless but not root" ); - } - - /* subtrees: heads of orphan trees - must be parentless. */ - - for( fd_forest_subtrees_iter_t iter = fd_forest_subtrees_iter_init( subtrees, pool ); !fd_forest_subtrees_iter_done( iter, subtrees, pool ); iter = fd_forest_subtrees_iter_next( iter, subtrees, pool ) ) { - fd_forest_blk_t const * ele = fd_forest_subtrees_iter_ele_const( iter, subtrees, pool ); - CHECK_TREE_ELE( ele ); - if( FD_UNLIKELY( ele->parent != null ) ) FAIL( "subtree head has a parent" ); - } - - /* orphaned: interior orphan nodes - must have a parent, and walking - up the parent chain must terminate at a subtree head. */ - - for( fd_forest_orphaned_iter_t iter = fd_forest_orphaned_iter_init( orphaned, pool ); !fd_forest_orphaned_iter_done( iter, orphaned, pool ); iter = fd_forest_orphaned_iter_next( iter, orphaned, pool ) ) { - fd_forest_blk_t const * ele = fd_forest_orphaned_iter_ele_const( iter, orphaned, pool ); - CHECK_TREE_ELE( ele ); - if( FD_UNLIKELY( ele->parent == null ) ) FAIL( "orphaned element has no parent" ); - fd_forest_blk_t const * cur = ele; - ulong steps = 0; - while( cur->parent != null ) { - if( FD_UNLIKELY( ++steps > max ) ) FAIL( "orphan parent chain longer than pool (cycle)" ); - cur = fd_forest_pool_ele_const( pool, cur->parent ); - } - if( FD_UNLIKELY( !fd_forest_subtrees_ele_query_const( subtrees, &cur->slot, NULL, pool ) ) ) FAIL( "orphan tree head not in subtrees map" ); - } - -# undef CHECK_TREE_ELE - - return 0; -} -#undef FAIL - -/* {maps}_remove removes an ele from {maps}. does not unlink ele. */ - -static fd_forest_blk_t * -ancestry_frontier_remove( fd_forest_t * forest, ulong slot ) { - fd_forest_blk_t * pool = fd_forest_pool( forest ); - fd_forest_blk_t * ele; - ele = fd_forest_ancestry_ele_remove( fd_forest_ancestry( forest ), &slot, NULL, pool ); - if( !ele ) ele = fd_forest_frontier_ele_remove( fd_forest_frontier( forest ), &slot, NULL, pool ); - return ele; -} - -static void -subtrees_insert( fd_forest_t * forest, - fd_forest_blk_t * ele ) { - fd_forest_blk_t * pool = fd_forest_pool( forest ); - fd_forest_subtrees_ele_insert( fd_forest_subtrees( forest ), ele, pool ); - fd_forest_subtlist_ele_push_tail( fd_forest_subtlist( forest ), ele, pool ); - - ulong seq = forest->orphan_seq_next++; - ele->orphan_seq = seq; - fd_forest_orphan_ent_t * orphanq = fd_forest_orphanq( forest ); - if( FD_UNLIKELY( fd_forest_orphanq_cnt( orphanq )==fd_forest_orphanq_max( orphanq ) ) ) { - ulong cnt = fd_forest_orphanq_cnt( orphanq ); - ulong i; - for( i=0UL; iorphan_seq!=orphanq[ i ].seq ) ) { fd_forest_orphanq_remove( orphanq, i ); break; } - } - FD_TEST( islot, .seq = seq }; - fd_forest_orphanq_insert( orphanq, &ent ); -} - -static fd_forest_blk_t * -subtrees_orphaned_remove( fd_forest_t * forest, ulong slot ) { - fd_forest_blk_t * pool = fd_forest_pool( forest ); - fd_forest_blk_t * ele = NULL; - ele = fd_forest_orphaned_ele_remove( fd_forest_orphaned( forest ), &slot, NULL, pool ); - if( ele ) return ele; - ele = fd_forest_subtrees_ele_remove( fd_forest_subtrees( forest ), &slot, NULL, pool ); - if( ele ) fd_forest_subtlist_ele_remove( fd_forest_subtlist( forest ), ele, pool ); - return ele; -} - -/* link ele to the tree via its sibling. */ - -static void -link_sibling( fd_forest_t * forest, fd_forest_blk_t * sibling, fd_forest_blk_t * ele ) { - fd_forest_blk_t * pool = fd_forest_pool( forest ); - ulong null = fd_forest_pool_idx_null( pool ); - while( FD_UNLIKELY( sibling->sibling != null )) sibling = fd_forest_pool_ele( pool, sibling->sibling ); - sibling->sibling = fd_forest_pool_idx( pool, ele ); -} - -/* link child to the tree via its parent. */ - -static void -link( fd_forest_t * forest, fd_forest_blk_t * parent, fd_forest_blk_t * child ) { - fd_forest_blk_t * pool = fd_forest_pool( forest ); - ulong null = fd_forest_pool_idx_null( pool ); - if( FD_LIKELY( parent->child == null ) ) parent->child = fd_forest_pool_idx( pool, child ); /* left-child */ - else link_sibling( forest, fd_forest_pool_ele( pool, parent->child ), child ); /* right-sibling */ - child->parent = fd_forest_pool_idx( pool, parent ); -} - -/* advance_consumed_frontier attempts to advance the consumed frontier beginning from slot - using BFS. head is the first element of a linked list representing - the BFS queue. A slot can be advanced if all shreds for the block - are received ie. consumed_idx = complete_idx. */ - -static void -advance_consumed_frontier( fd_forest_t * forest, ulong slot, ulong parent_slot ) { - fd_forest_blk_t * pool = fd_forest_pool( forest ); - fd_forest_ref_t * conspool = fd_forest_conspool( forest ); - fd_forest_consumed_t * consumed = fd_forest_consumed( forest ); - ulong * queue = fd_forest_deque( forest ); - - ulong slot_pool_idx = fd_forest_pool_idx( pool, fd_forest_query( forest, slot ) ); - ulong parent_pool_idx = fd_forest_pool_idx( pool, fd_forest_query( forest, parent_slot ) ); - fd_forest_ref_t * ele; - ele = fd_forest_consumed_ele_query( consumed, &slot_pool_idx, NULL, conspool ); - if( !ele ) ele = fd_forest_consumed_ele_query( consumed, &parent_pool_idx, NULL, conspool ); - if( FD_UNLIKELY( !ele ) ) return; - - FD_CHECK_CRIT( fd_forest_deque_cnt( queue ) == 0, "invariant violation" ); - - /* BFS elements as pool idxs. - Invariant: whatever is in the queue, must be in the consumed map. */ - fd_forest_deque_push_tail( queue, ele->idx ); - while( FD_LIKELY( fd_forest_deque_cnt( queue ) ) ) { - fd_forest_blk_t * head = fd_forest_pool_ele( pool, fd_forest_deque_pop_head( queue ) ); - fd_forest_blk_t * child = fd_forest_pool_ele( pool, head->child ); - - int all_shreds_received = head->complete_idx != UINT_MAX && head->complete_idx == head->buffered_idx; - - if( FD_LIKELY( child && all_shreds_received ) ) { /* we've received all the shreds for the slot - not all the FECs for the slot need to be completed */ - consumed_remove( forest, fd_forest_pool_idx( pool, head ) ); - while( FD_LIKELY( child ) ) { /* add children to consumed frontier */ - consumed_insert( forest, fd_forest_pool_idx( pool, child ) ); - fd_forest_deque_push_tail( queue, fd_forest_pool_idx( pool, child ) ); - child = fd_forest_pool_ele( pool, child->sibling ); - } - } - } -} - -fd_forest_blk_t * -fd_forest_query( fd_forest_t * forest, ulong slot ) { - fd_forest_blk_t * pool = fd_forest_pool( forest ); - fd_forest_ancestry_t * ancestry = fd_forest_ancestry( forest ); - fd_forest_frontier_t * frontier = fd_forest_frontier( forest ); - fd_forest_subtrees_t * subtrees = fd_forest_subtrees( forest ); - fd_forest_orphaned_t * orphaned = fd_forest_orphaned( forest ); - - fd_forest_blk_t * ele; - ele = fd_forest_ancestry_ele_query( ancestry, &slot, NULL, pool ); - if( !ele ) ele = fd_forest_frontier_ele_query( frontier, &slot, NULL, pool ); - if( !ele ) ele = fd_forest_subtrees_ele_query( subtrees, &slot, NULL, pool ); - if( !ele ) ele = fd_forest_orphaned_ele_query( orphaned, &slot, NULL, pool ); - return ele; -} - -/* remove_and_unlink removes a block from the forest and unlinks it from - its parent. Orphans all the descendants of the block. Also removes - from sibling chain, consumed map, and requests map if needed. Does - NOT release the block from the pool. */ -static void -remove_and_unlink( fd_forest_t * forest, fd_forest_blk_t * blk ) { - fd_forest_blk_t * pool = fd_forest_pool( forest ); - fd_forest_orphaned_t * orphaned = fd_forest_orphaned( forest ); - fd_forest_frontier_t * frontier = fd_forest_frontier( forest ); - fd_forest_ancestry_t * ancestry = fd_forest_ancestry( forest ); - fd_forest_consumed_t * consumed = fd_forest_consumed( forest ); - fd_forest_ref_t * conspool = fd_forest_conspool( forest ); - ulong null = fd_forest_pool_idx_null( pool ); - - /* Clean up the parent, and remove block from the maps */ - fd_forest_blk_t * parent = fd_forest_pool_ele( pool, blk->parent ); - if( FD_UNLIKELY( !parent ) ) { - subtrees_orphaned_remove( forest, blk->slot ); /* remove from subtrees and subtree list */ - } else { - /* remove the block from the parent's child list */ - - blk->parent = null; - fd_forest_blk_t * child = fd_forest_pool_ele( pool, parent->child ); - if( FD_LIKELY( child->slot == blk->slot ) ) { - parent->child = child->sibling; - } else { - /* go through the sibling list, and remove the block */ - fd_forest_blk_t * sibling = fd_forest_pool_ele( pool, child->sibling ); - fd_forest_blk_t * prev = child; - while( FD_LIKELY( sibling ) ) { - if( FD_LIKELY( sibling->slot == blk->slot ) ) { - prev->sibling = sibling->sibling; - break; - } - prev = sibling; - sibling = fd_forest_pool_ele( pool, sibling->sibling ); - } - } - blk->sibling = null; - - /* remove the block itself from the maps */ - - fd_forest_blk_t * removed = fd_forest_orphaned_ele_remove( orphaned, &blk->slot, NULL, pool ); - if( !removed ) { - removed = ancestry_frontier_remove( forest, blk->slot ); FD_TEST( removed ); - - /* We removed from the main tree, so we possible need to insert parent into the frontier. - Only need to add parent to the frontier if it doesn't have any other children. */ - - if( parent->child == null ) { - parent = fd_forest_ancestry_ele_remove( ancestry, &blk->parent_slot, NULL, pool ); - fd_forest_frontier_ele_insert( frontier, parent, pool ); - /* ensure parent is reachable from consumed frontier */ - ulong ancestor = fd_forest_pool_idx( pool, parent ); - while( FD_UNLIKELY( ancestor!=null && - !fd_forest_consumed_ele_query( consumed, &ancestor, NULL, conspool ) ) ) { - ancestor = fd_forest_pool_ele( pool, ancestor )->parent; - } - if( FD_UNLIKELY( ancestor==null ) ) consumed_insert( forest, fd_forest_pool_idx( pool, parent ) ); - } - } - } - - /* finally, release the block from reference maps */ - consumed_remove( forest, fd_forest_pool_idx( pool, blk ) ); - requests_remove( forest, fd_forest_orphreqs( forest ), fd_forest_orphlist( forest ), &forest->orphiter, fd_forest_pool_idx( pool, blk ) ); - requests_remove( forest, fd_forest_requests( forest ), fd_forest_reqslist( forest ), &forest->iter, fd_forest_pool_idx( pool, blk ) ); - - /* orphan/subtree all the descendants of ele, if there are any. In - eviction case, this is no-op. */ - ulong * queue = fd_forest_deque( forest ); - fd_forest_deque_push_tail( queue, fd_forest_pool_idx( pool, blk ) ); - - while( FD_LIKELY( fd_forest_deque_cnt( queue ) ) ) { - fd_forest_blk_t * curr = fd_forest_pool_ele( pool, fd_forest_deque_pop_head( queue ) ); - fd_forest_blk_t * child = fd_forest_pool_ele( pool, curr->child ); - while( FD_LIKELY( child ) ) { - /* remove all descendants from all structures */ - ancestry_frontier_remove( forest, child->slot ); - subtrees_orphaned_remove( forest, child->slot ); - consumed_remove( forest, fd_forest_pool_idx( pool, child ) ); - requests_remove( forest, fd_forest_requests( forest ), fd_forest_reqslist( forest ), &forest->iter, fd_forest_pool_idx( pool, child ) ); - - fd_forest_deque_push_tail( queue, fd_forest_pool_idx( pool, child ) ); - child = fd_forest_pool_ele( pool, child->sibling ); - } - /* this is the ele itself, do not reinsert */ - if( FD_UNLIKELY( curr==blk ) ) continue; - - else if( FD_UNLIKELY( fd_forest_pool_ele( pool, curr->parent ) == blk ) ) { /* direct child of the ele, insert it into subtrees */ - curr->parent = fd_forest_pool_idx_null( pool ); - curr->sibling = fd_forest_pool_idx_null( pool ); - subtrees_insert( forest, curr ); - requests_insert( forest, fd_forest_orphreqs( forest ), fd_forest_orphlist( forest ), fd_forest_pool_idx( pool, curr ) ); - - } else { /* otherwise, not direct descendant of ele, insert it into orphaned */ - fd_forest_orphaned_ele_insert( orphaned, curr, pool ); - } - } -} - -static ulong -clear_leaf( fd_forest_t * forest, ulong slot ) { - fd_forest_blk_t * pool = fd_forest_pool( forest ); - fd_forest_blk_t * blk = fd_forest_query( forest, slot ); - FD_TEST( blk ); - - remove_and_unlink( forest, blk ); - fd_forest_pool_ele_release( pool, blk ); - - return slot; -} - -/* returns latest confirmed leaf in the subtree rooted at root */ -static fd_forest_blk_t * -latest_confirmed_slot( fd_forest_t * forest, ulong root_idx ) { - ulong * queue = fd_forest_deque( forest ); - fd_forest_blk_t * latest_confirmed = NULL; - fd_forest_blk_t * pool = fd_forest_pool( forest ); - fd_forest_deque_remove_all( queue ); - fd_forest_deque_push_tail( queue, root_idx ); - - /* BFS through the tree. Since there can only be one confirmed fork, - the last confirmed node we find must be the latest confirmed slot. - We could be more efficient by limiting the search when we find a - confirmed node, but left like this for now. */ - - while( FD_LIKELY( !fd_forest_deque_empty( queue ) ) ) { - fd_forest_blk_t * blk = fd_forest_pool_ele( pool, fd_forest_deque_pop_head( queue ) ); - if( FD_LIKELY( blk->chain_confirmed || memcmp( &blk->confirmed_bid, &empty_mr, sizeof( fd_hash_t ) ) != 0 ) ) { - latest_confirmed = blk; - } - fd_forest_blk_t * child = fd_forest_pool_ele( pool, blk->child ); - while( FD_LIKELY( child ) ) { - fd_forest_deque_push_tail( queue, fd_forest_pool_idx( pool, child ) ); - child = fd_forest_pool_ele( pool, child->sibling ); - } - } - return latest_confirmed; -} - -static fd_forest_blk_t * -gca( fd_forest_t * forest, fd_forest_blk_t * blk1, fd_forest_blk_t * blk2 ) { - fd_forest_blk_t * pool = fd_forest_pool( forest ); - fd_forest_blk_t * parent1 = blk1; - fd_forest_blk_t * parent2 = blk2; - while( FD_LIKELY( parent1 && parent2 ) ) { - if( FD_LIKELY( parent1->slot == parent2->slot ) ) return parent1; - if( parent1->slot > parent2->slot ) parent1 = fd_forest_pool_ele( pool, parent1->parent ); - else parent2 = fd_forest_pool_ele( pool, parent2->parent ); - } - return NULL; -} - -#define UPDATE_BEST_CANDIDATE( best_confrmd, best_unconfrmd, ele, filter ) \ - if( FD_UNLIKELY( filter ) ) continue; \ - do { \ - int _confirmed = ele->chain_confirmed || !fd_hash_eq( &ele->confirmed_bid, &empty_mr ); \ - if( FD_UNLIKELY( _confirmed ) ) { \ - if( FD_LIKELY( !best_confrmd ) ) best_confrmd = ele; \ - else best_confrmd = fd_ptr_if( best_confrmd->slot < ele->slot, ele, best_confrmd ); \ - } else { \ - if( FD_LIKELY( !best_unconfrmd ) ) best_unconfrmd = ele; \ - else best_unconfrmd = fd_ptr_if( best_unconfrmd->slot < ele->slot, ele, best_unconfrmd ); \ - } \ - } while(0) - -/* fd_forest_evict is called when the forest has no more free elements, - but we are trying to insert a new block. - When this happens, forest begins evicting in the following order: - - 1. Orphaned, unconfirmed leaves - 2. Connected, unconfirmed leaves - 3. Orphaned, confirmed leaves - - We follow a general heuristic of evicting the leaf (youngest - descendant) in each category first, with an exception. If the leaf is - the parent of the slot we are adding, we pick a different leaf to - evict. This is to avoid getting stuck in a cycle of creating an - orphan that would immediately get evicted again by its parent getting - requested. - - If we have confirmations we also avoid adding new slots that we are - certain won't get confirmed. - - The likely most common case of eviction being called is when we have - disconnected from the cluster for a while, or if we are catching up - from far behind. In these cases, the distance from the last root to - current turbine could be > slot max. But if we just blindly evict - orphans at will, this could make the problem worse. Imagine slot_max = - 1000, and we are 2000 slots behind. - - slot [unconnected] slot - slot - ... - slot - 1 1001 1002 2000 - - At this point if we receive slot 1000 -- this is a good case. Ideally - we would evict slot 2000, and add slot 1000, and we make progress - towards completing catchup. But if we receive slot 2002, and we evict - slot 2000, then the next orphan request would give us 2001, 2000 again - and again, and theoretically we never make progress towards completing - catchup. - - It's unclear if we should evict orphans ONLY if the slot being added - is closer to the root. It's possible that the new, later orphan is - actually closer to the root than the older, earlier orphan, and those - were just dud slots sent to us by an ancestor. In practice, the need - repair orphan process is much faster than the turbine process, so for - now we make the choice to optimistically keep orphans, and rely on the - repair orphan process to quickly connect ancestry, faster than the - future slots can come and evict it. - - WHAT IF WE HAVE NO ORPHANS. - - If we don't have orphans, we need to evict the newest unconfirmed - leaf. I.e. start by trimming from the tip of the tree, but on - a fork that is a minority. - - i.e best case: - - 1 ── 2 ── 4 ── 6 ── 7 ── 8 ...... ── 1000 <- 1001 would like to be added to the rree - └── 3 ── 5 - - If 1000 is confirmed, and 5 is not, we should evict 5 first, and then add 1001. - - 1 ── 2 ── 4 ── 6 ── 7 ── 8 ...... ── 1000 ── 1001 <- 1002 would like to be added to the rree - └── 3 - - Similarly, after 1002 arrives: - 1 ── 2 ── 4 ── 6 ── 7 ── 8 ...... ── 1000 ── 1001 ── 1002 <- 1003 would like to be added to the rree - - Now we have one fork, with 1003 chaining to 1002. If 1002 is - confirmed, then it's truly unfortunate... We (and most likely the - cluster) hasn't rooted in max_live_slots! As long as 2 is also - confirmed, then we are just going to optimistically publish forward to - slot 2 and make it our new root. Note 2 MUST have undergone - fec_chain_verify before it can be confirmed. If 2 is still not - confirmed, we could still be in the process of evicting + repairing - duplicates, so we must wait for 2 to be confirmed before we can - publish forward. - - If 1002 is NOT confirmed, we cannot evict it and add 1003. This puts - us under the case where we can't evict our parent. At this point we - would rely on a confirmation to occur eventually that prunes state and - frees up pool elements. - - This also works in the degenerate DoS case, where we have an extremely - wide tree. Imagine someone someone with leader slots 1001 thru 1995 is - doing the following attck: - - 1 ── 2 ── 3 ── 4 ── 5 ── <-- when we try to add 6, we run into eviction policy - ├── 1001' - ├── 1003' - ... - └── 1995' - Even if the confirmation for 5 is lagging coming in (or it requires us - to replay 6 to see it), we can follow a general policy of evicting the - newest unconfirmed leaf. Newest implies furthest from the root. So we - would evict 1995' first, and then add 6. */ - - -static ulong -evict( fd_forest_t * forest, ulong new_slot, ulong parent_slot ) { - /* TODO If we've reached the point that we need to evict, - should we stop using the orphan iterator to make requests? i.e. - focus only on rebuilding ancestry. */ - - (void)new_slot; - fd_forest_frontier_t * frontier = fd_forest_frontier( forest ); - fd_forest_subtlist_t * subtlist = fd_forest_subtlist( forest ); - fd_forest_subtrees_t * subtrees = fd_forest_subtrees( forest ); - fd_forest_orphaned_t * orphaned = fd_forest_orphaned( forest ); - fd_forest_blk_t * pool = fd_forest_pool( forest ); - - /* Generally, best policy for eviction is to evict in the order of: - 1. Highest unconfirmed orphan leaf - furthest from root - 2. Highest unconfirmed leaf in ancestry - furthest from tip of execution - 3. Highest confirmed orphan leaf - 4. Highest confirmed leaf in ancestry - at this point we would not evict this candidate. - - Since there can only be one confirmed fork, if we have more than - one fork, then we should always be able to evict the unconfirmed - slots with ease. - - There's some exceptions. We cannot evict slots that would be our - parent, because this would create a loop of evictions. Or, if the - slot we are adding is older than the rest of our orphans, we - shouldn't add it. or maybe we should? FAAAAA currently we will. */ - - fd_forest_blk_t * unconfrmd_orphan = NULL; /* 1st best candidate for eviction is the highest unconfirmed orphan. */ - fd_forest_blk_t * confirmed_orphan = NULL; /* 3rd best candidate for eviction is the highest confirmed orphan. */ - for( fd_forest_subtlist_iter_t iter = fd_forest_subtlist_iter_fwd_init( subtlist, pool ); - !fd_forest_subtlist_iter_done( iter, subtlist, pool ); - iter = fd_forest_subtlist_iter_fwd_next( iter, subtlist, pool ) ) { - fd_forest_blk_t * ele = fd_forest_subtlist_iter_ele( iter, subtlist, pool ); - UPDATE_BEST_CANDIDATE( confirmed_orphan, unconfrmd_orphan, ele, ele->child != ULONG_MAX || ele->slot == parent_slot ); - } - for( fd_forest_orphaned_iter_t iter = fd_forest_orphaned_iter_init( orphaned, pool ); - !fd_forest_orphaned_iter_done( iter, orphaned, pool ); - iter = fd_forest_orphaned_iter_next( iter, orphaned, pool ) ) { - fd_forest_blk_t * ele = fd_forest_orphaned_iter_ele( iter, orphaned, pool ); - UPDATE_BEST_CANDIDATE( confirmed_orphan, unconfrmd_orphan, ele, ele->child != ULONG_MAX || ele->slot == parent_slot ); - } - - fd_forest_blk_t * unconfrmd_leaf = NULL; /* 2nd best candidate for eviction is the highest unconfirmed leaf. */ - fd_forest_blk_t * confirmed_leaf = NULL; /* 4th best candidate for eviction is the highest confirmed leaf. */ - for( fd_forest_frontier_iter_t iter = fd_forest_frontier_iter_init( frontier, pool ); - !fd_forest_frontier_iter_done( iter, frontier, pool ); - iter = fd_forest_frontier_iter_next( iter, frontier, pool ) ) { - fd_forest_blk_t * ele = fd_forest_frontier_iter_ele( iter, frontier, pool ); - UPDATE_BEST_CANDIDATE( confirmed_leaf, unconfrmd_leaf, ele, iter.ele_idx == forest->root || ele->slot == parent_slot ); - } - - if( FD_UNLIKELY( !unconfrmd_leaf && !confirmed_leaf && !unconfrmd_orphan && !confirmed_orphan ) ) { - /* This can only happen 1 of two ways: - 1. One fork in orphans, and root is alone (common situation in - catchup). The new slot's parent is the tip of the orphan - fork. Ignore the slot in this case. - 2. One long fork, and the new slot's parent is the tip of the - fork. Force a root in this case. */ - if( fd_forest_orphaned_ele_query( orphaned, &parent_slot, NULL, pool ) ) return ULONG_MAX; - - ulong new_root = fd_forest_pool_ele( pool, forest->root )->child; - fd_forest_blk_t * new_root_ele = new_root==fd_forest_pool_idx_null( pool ) ? NULL : fd_forest_pool_ele( pool, new_root ); - - if( FD_UNLIKELY( !new_root_ele || !new_root_ele->chain_confirmed ) ) return ULONG_MAX; - - FD_LOG_INFO(( "[%s] forest force rooting on slot %lu", __func__, new_root_ele->slot )); - ulong evicted_slot = fd_forest_pool_ele( pool, forest->root )->slot; - fd_forest_publish( forest, new_root_ele->slot ); - return evicted_slot; - } - if( FD_UNLIKELY( unconfrmd_orphan )) { - return clear_leaf( forest, unconfrmd_orphan->slot ); - } - if( FD_UNLIKELY( unconfrmd_leaf )) { - return clear_leaf( forest, unconfrmd_leaf->slot ); - } - if( FD_UNLIKELY( confirmed_orphan )) { - fd_forest_blk_t * parent = fd_forest_query( forest, parent_slot ); - /* Always accept a new orphan subtree root, as it could bring us - closer to confirmation */ - if( !parent ) { - return clear_leaf( forest, confirmed_orphan->slot ); - } - - /* While in general it's safe to evict a confirmed orphan, we don't - want to evict them if this new slot is uselessly adding to a - fork we KNOW isn't confirmed. i.e., if there is another fork in - this subtree that isn't confirmed, but it's parent is parent_slot. - - Ex. We shouldn't evict a confirmed orphan leaf if the parent_slot - is the other fork that is unconfirmed. Also can't evict a - confirmed orphan if we are creating a new fork in the main tree - that doesn't continue the singular confirmed fork. - - i.e. for any subtree: - - 0 ── 1 ── 2 ── 3 (confirmed) ── 4(confirmed) ── 5 ── 6 ──> add 7 here is valid. - └──> add 7 here is valid. - └──> add 7 here is invalid. */ - ulong subtree_root = forest->root; - if( fd_forest_subtrees_ele_query( subtrees, &parent_slot, NULL, pool ) || - fd_forest_orphaned_ele_query( orphaned, &parent_slot, NULL, pool ) ) { - /* if adding to an orphan, find the root of the orphan subtree. */ - fd_forest_blk_t * root = parent; - while( FD_LIKELY( root->parent != ULONG_MAX ) ) { - root = fd_forest_pool_ele( pool, root->parent ); - } - subtree_root = fd_forest_pool_idx( pool, root ); - } - - fd_forest_blk_t * latest_confirmed_leaf = latest_confirmed_slot( forest, subtree_root ); - if( !latest_confirmed_leaf || latest_confirmed_leaf == gca( forest, latest_confirmed_leaf, parent )) { - return clear_leaf( forest, confirmed_orphan->slot ); /* is not a useless new fork. */ - } - /* is a useless new fork. */ - return ULONG_MAX; - } else { - /* Should never be evicting a confirmed leaf. This is only non-NULL - if: - (1) we have no orphans, and there's only two forks in the main - tree, and the parent of the non confirmed fork is is our parent. - in this case we should just ignore this insert. TODO: optionally - we could evict the non confirmed fork if its a separate fork. - (2) we could have one orphan fork where parent_slot is at the - tip, and everything in main tree is confirmed. in this case we - should also ignore this insert. */ - return ULONG_MAX; - } -} -#undef UPDATE_BEST_CANDIDATE - -static fd_forest_blk_t * -acquire( fd_forest_t * forest, ulong slot, ulong parent_slot, ulong * evicted ) { - fd_forest_blk_t * pool = fd_forest_pool( forest ); - if( FD_UNLIKELY( !fd_forest_pool_free( pool ) ) ) { - ulong evicted_ = evict( forest, slot, parent_slot ); - if( FD_LIKELY( evicted )) *evicted = evicted_; - if( FD_UNLIKELY( evicted_ == ULONG_MAX ) ) { - return NULL; - } - } - fd_forest_blk_t * blk = fd_forest_pool_ele_acquire( pool ); - ulong null = fd_forest_pool_idx_null( pool ); - - blk->slot = slot; - blk->parent_slot = parent_slot; - blk->next = null; - blk->parent = null; - blk->child = null; - blk->sibling = null; - blk->orphan_seq = ULONG_MAX; - blk->chain_confirmed = 0; - - blk->buffered_idx = UINT_MAX; - blk->complete_idx = UINT_MAX; - - ulong idxs_sz = fd_forest_blk_idxs_word_cnt( forest->shred_max )*sizeof(fd_forest_blk_idxs_t); - memset( fd_forest_blk_idxs ( forest, blk ), 0, idxs_sz ); - blk->lowest_verified_fec = UINT_MAX; - memset( fd_forest_blk_mroots( forest, blk ), 0, (forest->shred_max/FD_FEC_SHRED_CNT)*sizeof(fd_forest_mr_t) ); /* expensive*/ - blk->confirmed_bid = empty_mr; - memset( fd_forest_blk_recv( forest, blk ), 0, (forest->shred_max/FD_FEC_SHRED_CNT)*sizeof(fd_forest_recv_t) ); - - /* Metrics tracking */ - - memset( fd_forest_blk_code( forest, blk ), 0, idxs_sz ); - blk->first_shred_ts = 0; - blk->last_shred_ts = 0; - blk->first_req_ts = 0; - blk->last_repair_resp_ts = 0; - blk->turbine_cnt = 0; - blk->repair_cnt = 0; - blk->recovered_cnt = 0; - blk->req_window_cnt = 0; - blk->req_highest_cnt = 0; - blk->req_orphan_cnt = 0; - blk->req_retransmit_cnt = 0; - blk->response_cnt = 0; - blk->chain_verify_failed = 0; - - return blk; -} - -fd_forest_blk_t * -fd_forest_blk_insert( fd_forest_t * forest, ulong slot, ulong parent_slot, ulong * evicted ) { - FD_CHECK_CRIT( slot > fd_forest_root_slot( forest ), "invalid argument" ); - - fd_forest_ancestry_t * ancestry = fd_forest_ancestry( forest ); - fd_forest_frontier_t * frontier = fd_forest_frontier( forest ); - fd_forest_subtrees_t * subtrees = fd_forest_subtrees( forest ); - fd_forest_subtlist_t * subtlist = fd_forest_subtlist( forest ); - fd_forest_orphaned_t * orphaned = fd_forest_orphaned( forest ); - fd_forest_consumed_t * consumed = fd_forest_consumed( forest ); - fd_forest_ref_t * conspool = fd_forest_conspool( forest ); - fd_forest_requests_t * requests = fd_forest_requests( forest ); - fd_forest_ref_t * reqspool = fd_forest_reqspool( forest ); - fd_forest_blk_t * pool = fd_forest_pool ( forest ); - ulong * bfs = fd_forest_deque( forest ); - ulong null = fd_forest_pool_idx_null( pool ); - - fd_forest_blk_t * ele = fd_forest_query( forest, slot ); - if( FD_LIKELY( ele ) ) { - /* May need to update the parent_slot, if this - this was a sentinel block that was created for a confirmed msg. - A parent update for a sentinel block only occurs once. This - is separate from the parent update for a confirmed equivocating - block. */ - if( FD_UNLIKELY( ele->parent_slot == ULONG_MAX && parent_slot != ULONG_MAX ) ) { - ele->parent_slot = parent_slot; - FD_TEST( fd_forest_subtrees_ele_query( subtrees, &slot, NULL, pool ) || fd_forest_orphaned_ele_query( orphaned, &slot, NULL, pool ) ); - subtrees_orphaned_remove( forest, slot ); // if this is a sentinel block, then it must be orphaned - /* The sentinel was an orphan subtree head, so it is in the orphan - requests list. Now that its parent is known it will be re-linked - below and may join the main tree (frontier) or become an interior - orphan, neither of which belongs in the orphan requests list. Drop - the orphan request entry now; if it ends up a subtree head again the - subtrees branch below will re-add it. Failing to remove it here - leaves the same pool idx in both the orphan and main request maps - (which share a single reqspool), corrupting both lists. */ - requests_remove( forest, fd_forest_orphreqs( forest ), fd_forest_orphlist( forest ), &forest->orphiter, fd_forest_pool_idx( pool, ele ) ); - } else { - return ele; - } - } else { - ele = acquire( forest, slot, parent_slot, evicted ); - if( FD_UNLIKELY( !ele ) ) return NULL; /* no space in pool, so we can't add this slot */ - } - - fd_forest_blk_t * parent = NULL; - - if( FD_LIKELY ( parent = fd_forest_ancestry_ele_query ( ancestry, &parent_slot, NULL, pool ) ) ) { /* parent is in ancestry, ele makes new frontier */ - fd_forest_frontier_ele_insert( frontier, ele, pool ); - } else if( FD_UNLIKELY( parent = fd_forest_frontier_ele_remove( frontier, &parent_slot, NULL, pool ) ) ) { /* parent is in frontier, ele makes new frontier */ - fd_forest_ancestry_ele_insert( ancestry, parent, pool ); - fd_forest_frontier_ele_insert( frontier, ele, pool ); - } else if( FD_UNLIKELY( parent = fd_forest_orphaned_ele_query ( orphaned, &parent_slot, NULL, pool ) ) ) { /* parent is in orphaned, ele makes new orphaned */ - fd_forest_orphaned_ele_insert( orphaned, ele, pool ); - } else if( FD_UNLIKELY( parent = fd_forest_subtrees_ele_query ( subtrees, &parent_slot, NULL, pool ) ) ) { /* parent is in subtrees, ele makes new orphaned */ - fd_forest_orphaned_ele_insert( orphaned, ele, pool ); - } else { /* parent is not in any map, ele makes new subtree */ - subtrees_insert( forest, ele ); - - requests_insert( forest, fd_forest_orphreqs( forest ), fd_forest_orphlist( forest ), fd_forest_pool_idx( pool, ele ) ); - } - - if( FD_LIKELY( parent ) ) link( forest, parent, ele ); - - /* Iterate subtrees and connect ones where the parent slot matches up - to the new ele.*/ - - for( fd_forest_subtlist_iter_t iter = fd_forest_subtlist_iter_fwd_init( subtlist, pool ); - !fd_forest_subtlist_iter_done( iter, subtlist, pool ); - iter = fd_forest_subtlist_iter_fwd_next( iter, subtlist, pool ) ) { - fd_forest_blk_t * orphan = fd_forest_subtlist_iter_ele( iter, subtlist, pool ); - // edge case where for a sentinel node the parent_slot == slot, so we want to avoid linking it to itself - if( FD_LIKELY( orphan->slot != ele->slot ) ) fd_forest_deque_push_tail( bfs, fd_forest_pool_idx( pool, orphan ) ); - } - while( FD_LIKELY( fd_forest_deque_cnt( bfs ) ) ) { - fd_forest_blk_t * orphan = fd_forest_pool_ele( pool, fd_forest_deque_pop_head( bfs ) ); - if( FD_UNLIKELY( orphan->parent_slot == ele->slot ) ) { - link( forest, ele, orphan ); - fd_forest_subtrees_ele_remove( subtrees, &orphan->slot, NULL, pool ); - fd_forest_subtlist_ele_remove( fd_forest_subtlist( forest ), orphan, pool ); - requests_remove( forest, fd_forest_orphreqs( forest ), fd_forest_orphlist( forest ), &forest->orphiter, fd_forest_pool_idx( pool, orphan ) ); - fd_forest_orphaned_ele_insert( orphaned, orphan, pool ); - } - } - - /* At this point we are in the state where: - - ele < in frontier/subtrees/orphaned > - | - children < all in orphaned > - - if ele is in frontier, we need to extend the frontier from this child. - if ele is in orphaned/subtrees, we are done. don't do anything, */ - - if( FD_LIKELY( fd_forest_frontier_ele_query( frontier, &ele->slot, NULL, pool ) ) ) fd_forest_deque_push_tail( bfs, fd_forest_pool_idx( pool, ele ) ); - while( FD_LIKELY( !fd_forest_deque_empty( bfs ) ) ) { - fd_forest_blk_t * parent = fd_forest_pool_ele( pool, fd_forest_deque_pop_head( bfs ) ); - fd_forest_blk_t * child = fd_forest_pool_ele( pool, parent->child ); - if( FD_LIKELY( child ) ) { - fd_forest_frontier_ele_remove( frontier, &parent->slot, NULL, pool ); - fd_forest_ancestry_ele_insert( ancestry, parent, pool ); - } - while( FD_LIKELY( child ) ) { - fd_forest_orphaned_ele_remove( orphaned, &child->slot, NULL, pool ); - requests_remove( forest, fd_forest_orphreqs( forest ), fd_forest_orphlist( forest ), &forest->orphiter, fd_forest_pool_idx( pool, child ) ); - fd_forest_frontier_ele_insert( frontier, child, pool ); - fd_forest_deque_push_tail( bfs, fd_forest_pool_idx( pool, child ) ); - child = fd_forest_pool_ele( pool, child->sibling ); - } - } - - if( FD_LIKELY( fd_forest_ancestry_ele_query( ancestry, &ele->slot, NULL, pool ) || - fd_forest_frontier_ele_query( frontier, &ele->slot, NULL, pool ) ) ) { - /* There is a chance that we connected this ele to the main tree. If - this ele doesn't have a parent in the consumed/requests map, add it to the - consumed/requests map. */ - ulong ancestor = fd_forest_pool_idx( pool, ele ); - int has_requests_anc = 0; - int has_consumed_anc = 0; - while( ancestor != null && (!has_requests_anc || !has_consumed_anc) ) { - if( fd_forest_consumed_ele_query( consumed, &ancestor, NULL, conspool ) ) has_consumed_anc = 1; - if( fd_forest_requests_ele_query( requests, &ancestor, NULL, reqspool ) ) has_requests_anc = 1; - ancestor = fd_forest_pool_ele( pool, ancestor )->parent; - } - if( FD_UNLIKELY( !has_requests_anc ) ) { - requests_insert( forest, fd_forest_requests( forest ), fd_forest_reqslist( forest ), fd_forest_pool_idx( pool, ele ) ); - /* we want to remove any children than are in the requests list. This isn't necessary during any regular boot. - However if we are booting from very far behind (>30k slots), the requests list will be very large and in - nearly reverse order. */ - ulong * queue = fd_forest_deque( forest ); - fd_forest_deque_push_tail( queue, fd_forest_pool_idx( pool, ele ) ); - while( FD_LIKELY( fd_forest_deque_cnt( queue ) ) ) { - fd_forest_blk_t * child = fd_forest_pool_ele( pool, fd_forest_deque_pop_head( queue ) ); - if( FD_LIKELY( child != ele ) ) { - requests_remove( forest, fd_forest_requests( forest ), fd_forest_reqslist( forest ), &forest->iter, fd_forest_pool_idx( pool, child ) ); - } - child = fd_forest_pool_ele( pool, child->child ); - while( FD_LIKELY( child ) ) { - fd_forest_deque_push_tail( queue, fd_forest_pool_idx( pool, child ) ); - child = fd_forest_pool_ele( pool, child->sibling ); - } - } - } - if( FD_UNLIKELY( !has_consumed_anc ) ) consumed_insert( forest, fd_forest_pool_idx( pool, ele ) ); - } - return ele; -} - -/* Updates a forest_blk_t's parent, which requires updates to the blk - itself, the blk's old parent, the new parent, and all its - descendants. The blk is released and re-inserted fresh - under the new parent: all recorded state (shred idxs, merkle roots, - verification progress) is discarded except confirmed_bid, on the - assumption it belonged to a different version of the slot. Returns - the new ele; the old pointer must not be reused. */ -static fd_forest_blk_t * -verified_parent_update( fd_forest_t * forest, fd_forest_blk_t * ele, ulong parent_slot ) { - fd_forest_blk_t * pool = fd_forest_pool( forest ); - fd_hash_t confirmed_bid = ele->confirmed_bid; /* save confirmation status for re-insertion */ - - /* remove from maps, unlink from old parent. children orphaned. */ - remove_and_unlink( forest, ele ); - - ulong slot = ele->slot; - fd_forest_pool_ele_release( pool, ele ); - - /* ele is now gone. blk_insert it! and then restore saved verified state */ - - fd_forest_blk_t * new_ele = fd_forest_blk_insert( forest, slot, parent_slot, NULL ); - new_ele->confirmed_bid = confirmed_bid; - - return new_ele; -} - -static inline int -merkle_recvd( fd_forest_mr_t const * mroots, uint fec_idx ) { - return memcmp( &mroots[fec_idx].mr, &empty_mr, sizeof(fd_hash_t) ) != 0; -} - -/* returns 1 if the FEC set after the given FEC set has been confirmed */ -static inline int -next_merkle_confirmed( fd_forest_blk_t * ele, uint fec_idx ) { - // not possible for anything to be verified if the slot doesn't know the last index - if( ele->complete_idx == UINT_MAX ) return 0; - /* if we are asking about the block_id, it's stored in the confirmed_bid field */ - if( FD_UNLIKELY( fec_idx == (ele->complete_idx / 32UL) ) ) { - return !fd_hash_eq( &ele->confirmed_bid, &empty_mr ); - } - return ele->lowest_verified_fec <= (fec_idx + 1UL); -} - -/* Returns the chained merkle root that commits to the given FEC set. - Only meaningful when next_merkle_confirmed() is true; otherwise the - returned cmr may be uninitialized. For most FEC sets this is - mroots[fec_idx+1].cmr. For the last FEC in the slot (fec_idx == - complete_idx/32) it is confirmed_bid. - - Guard against OOB read: an attacker can send a fec_idx beyond - complete_idx. The shred gets filtered upstream, so we don't need to - guard against it here. */ - -static inline fd_hash_t * -next_chained_merkle( fd_forest_blk_t * ele, fd_forest_mr_t * mroots, uint fec_idx ) { - if( FD_UNLIKELY( fec_idx == ele->complete_idx / 32UL ) ) { - return &ele->confirmed_bid; - } - return &mroots[fec_idx + 1].cmr; -} - -/* recv_ts_stamp records the first time turbine was observed reaching - fec_idx, as a ms offset from first_shred_ts, and backfills any - still-unstamped FEC sets below it: first_shred_ts must - already be set. */ - -static void -recv_ts_stamp( fd_forest_t * forest, fd_forest_blk_t * ele, uint fec_idx, long now ) { - fd_forest_recv_t * recv = fd_forest_blk_recv( forest, ele ); - if( FD_LIKELY( recv[ fec_idx ].first ) ) return; - now = fd_long_max( now, ele->first_shred_ts ); - ulong off_ms = (ulong)( (double)(now - ele->first_shred_ts) / fd_tempo_tick_per_ns( NULL ) * 1e-6 ); - ushort stamp = (ushort)fd_ulong_min( fd_ulong_max( off_ms, 1UL ), (ulong)USHORT_MAX ); /* clamp to >=1: 0 means unstamped */ - recv[ fec_idx ].first = stamp; - for( uint i=fec_idx; i>0U && !recv[ i-1U ].first; i-- ) recv[ i-1U ].first = stamp; -} - -/* data_shred_insert accepts the first complete_idx it sees while - complete_idx is UINT_MAX, and rejects any subsequent shreds that are - greater than the complete_idx. This is applies for the very first - slot_complete seen (could be incorrect), or after the complete_idx has - been cleared by fec_clear due to an incorrect FEC. */ - -fd_forest_blk_t * -fd_forest_data_shred_insert( fd_forest_t * forest, - ulong slot, - ulong parent_slot, - uint shred_idx, - uint fec_set_idx, - int slot_complete, - int ref_tick FD_PARAM_UNUSED, - int src, - fd_hash_t * mr, - fd_hash_t * cmr, - long rx_tick ) { - FD_TEST( shred_idx < forest->shred_max ); /* guaranteed by fec_resolver */ - fd_forest_blk_t * ele = fd_forest_query( forest, slot ); - FD_CHECK_ERR( !!ele, "ele is not in the forest. data_shred_insert should be preceded by blk_insert" ); - fd_forest_mr_t * mroots = fd_forest_blk_mroots( forest, ele ); - - /* Pre-filtering on merkle root. - If we have knowledge of the confirmed merkle root, we can reject - shreds that don't match it. Else, we'll accept any and all shreds, - and invalidating the merkle root if we see more than 1 version of - the FEC. */ - - uint fec_idx = fec_set_idx / 32UL; - - /* We can automatically reject if the shred index is greater than the - complete index, as it clearly signifies some duplicity is - occurring. What if this shred is part of the canonical chain - though? When the duplicate confirmation arrives from tower, the - false complete_idx will be cleared, and shreds higher than the - false complete_idx will be accepted. */ - - if( FD_UNLIKELY( shred_idx > ele->complete_idx ) ) { - FD_LOG_WARNING(( "[%s] slot %lu shred index %u is greater than known complete_idx %u. rejecting shred", __func__, slot, shred_idx, ele->complete_idx )); - return NULL; - } - - /* If this is a slot_complete shred and we know the confirmed - block_id, we can immediately verify or reject. This check is - independent of the complete_idx / lowest_verified_fec state, so it - covers the case after fec_clear resets those fields. */ - - if( FD_UNLIKELY( slot_complete && !fd_hash_eq( &ele->confirmed_bid, &empty_mr ) ) ) { - if( FD_UNLIKELY( !fd_hash_eq( &ele->confirmed_bid, mr ) ) ) return NULL; /* wrong version */ - if( FD_UNLIKELY( ele->parent_slot != parent_slot ) ) { ele = verified_parent_update( forest, ele, parent_slot ); mroots = fd_forest_blk_mroots( forest, ele ); } - ele->lowest_verified_fec = fec_idx; /* last FEC verified */ - mroots[fec_idx].mr = *mr; - mroots[fec_idx].cmr = *cmr; - } - - /* Otherwise if this is any other shred and we know the verification - status, we can immediately verify or reject. */ - - if( FD_UNLIKELY( next_merkle_confirmed( ele, fec_idx ) ) ) { /* if the cmr pointing to this FEC has been confirmed, then... */ - if( FD_UNLIKELY( !fd_hash_eq( next_chained_merkle( ele, mroots, fec_idx ), mr ) ) ) { - /* merkle root doesn't match the verified CMR */ - return NULL; /* do not accept this shred. */ - } else { - - /* A validated mr, but the parent slot is wrong. This means we - initially received the wrong version of the slot that also - had a different parent slot. We need to update the parent - slot to the correct one. */ - - if( FD_UNLIKELY( ele->parent_slot != parent_slot ) ) { ele = verified_parent_update( forest, ele, parent_slot ); mroots = fd_forest_blk_mroots( forest, ele ); } - mroots[fec_idx].mr = *mr; - mroots[fec_idx].cmr = *cmr; - - } - } else { /* No verification / knowledge of canonical merkle root */ - if( FD_UNLIKELY( !merkle_recvd( mroots, fec_idx ) ) ) { - - /* On first mr received for FEC set 0, adopt the shred's parent. - Otherwise later confirmation would chain verify into the wrong - ancestor. */ - - if( FD_UNLIKELY( ele->parent_slot != parent_slot && fec_idx == 0 ) ) { ele = verified_parent_update( forest, ele, parent_slot ); mroots = fd_forest_blk_mroots( forest, ele ); } - - mroots[fec_idx].mr = *mr; - mroots[fec_idx].cmr = *cmr; - } else { - /* verify that the received merkle root is consistent with the current merkle root. - No need to check the cmr, because matching mr implies matching cmr. */ - fd_hash_t * current_mr = &mroots[fec_idx].mr; - if( FD_UNLIKELY( !fd_hash_eq( current_mr, mr ) ) ) { - FD_BASE58_ENCODE_32_BYTES( current_mr->key, current_mr_b58 ); FD_BASE58_ENCODE_32_BYTES( mr->key, mr_b58 ); - FD_LOG_INFO(( "[%s] multiple versions detected for slot %lu fec set %u, invalidating. current_mr %s, received_mr %s", __func__, slot, fec_set_idx, current_mr_b58, mr_b58 )); - mroots[fec_idx].mr = invalid_mr; /* invalidate the merkle root */ - } - } - } - - /* Shred accepted, merkle root verified (as much as possible). */ - - if( FD_UNLIKELY( slot_complete && ele->complete_idx != UINT_MAX && shred_idx != ele->complete_idx ) ) { - /* It is always beneficial for us to take the minimum slot complete - index because this enables the chain verification to start - earlier. */ - FD_LOG_WARNING(( "[%s] slot %lu shred_idx %u is slot_complete, but recorded complete_idx is %u. Updating complete_idx to %u", __func__, slot, shred_idx, ele->complete_idx, shred_idx )); - } - ele->complete_idx = fd_uint_if( slot_complete, shred_idx, ele->complete_idx ); - - fd_forest_blk_idxs_t * idxs = fd_forest_blk_idxs( forest, ele ); - if( !fd_forest_blk_idxs_test( idxs, shred_idx ) ) { /* newly seen shred */ - ele->turbine_cnt += (src==SHRED_SRC_TURBINE); - ele->repair_cnt += (src==SHRED_SRC_REPAIR); - ele->recovered_cnt += (src==SHRED_SRC_RECOVERED); - } - if( FD_UNLIKELY( !ele->first_shred_ts || rx_tickfirst_shred_ts ) ) ele->first_shred_ts = rx_tick; - if( FD_LIKELY( src==SHRED_SRC_TURBINE ) ) recv_ts_stamp( forest, ele, fec_idx, rx_tick ); - - fd_forest_blk_idxs_insert( idxs, shred_idx ); - while( ele->buffered_idx + 1 < forest->shred_max && fd_forest_blk_idxs_test( idxs, ele->buffered_idx + 1U ) ) { - ele->buffered_idx++; - } - - /* If equivocating, buffered_idx needs to be clamped to complete_idx */ - if( FD_UNLIKELY( ele->buffered_idx != UINT_MAX && ele->buffered_idx > ele->complete_idx ) ) ele->buffered_idx = ele->complete_idx; - - if( FD_UNLIKELY( !ele->last_shred_ts && ele->complete_idx!=UINT_MAX && ele->buffered_idx==ele->complete_idx ) ) { - ele->last_shred_ts = rx_tick; - } - - advance_consumed_frontier( forest, slot, parent_slot ); - return ele; -} - -fd_forest_blk_t * -fd_forest_fec_insert( fd_forest_t * forest, ulong slot, ulong parent_slot, uint last_shred_idx, uint fec_set_idx, int slot_complete, int ref_tick, fd_hash_t * mr, fd_hash_t * cmr, long rx_tick ) { - FD_TEST( last_shred_idx < forest->shred_max ); /* guaranteed by fec_resolver */ - - fd_forest_blk_t * ele = fd_forest_query( forest, slot ); - FD_CHECK_ERR( !!ele, "ele is not in the forest. fec_insert should be preceded by blk_insert" ); - fd_forest_mr_t * mroots = fd_forest_blk_mroots( forest, ele ); - - uint fec_idx = fec_set_idx / 32UL; /* index into merkle root array */ - - /* if the FEC set is beyond the complete_idx, then we reject the FEC */ - if( FD_UNLIKELY( fec_set_idx > ele->complete_idx ) ) { - FD_LOG_WARNING(( "[%s] slot %lu fec set %u is greater than known complete_idx %u. rejecting FEC", __func__, slot, fec_set_idx, ele->complete_idx )); - return NULL; - } - - /* reject if the fec is confirmed and the merkle root doesn't match */ - if( FD_UNLIKELY( next_merkle_confirmed( ele, fec_idx ) && !fd_hash_eq( next_chained_merkle( ele, mroots, fec_idx ), mr ) ) ) return NULL; - - if( FD_UNLIKELY( merkle_recvd( mroots, fec_idx ) && !fd_hash_eq( &mroots[fec_idx].mr, mr ) ) ) { - /* overwrite the merkle root with the new one */ - FD_BASE58_ENCODE_32_BYTES( mroots[fec_idx].mr.key, mr_b58 ); - FD_BASE58_ENCODE_32_BYTES( mr->key, mr_recv_b58 ); - FD_LOG_WARNING(( "[%s] received a version of slot %lu fec_set_idx %u that isn't recorded. current_mr %s, received_mr %s", __func__, slot, fec_set_idx, mr_b58, mr_recv_b58 )); - - /* Overwriting FEC 0's merkle root means we've adopted a different - version of the slot, whose first shred may name a different - parent. Re-link to that parent so chained verification walks the - correct ancestor, instead of walking into the previously-linked - slot. */ - - if( FD_UNLIKELY( fec_idx == 0 && ele->parent_slot != parent_slot ) ) { - ele = verified_parent_update( forest, ele, parent_slot ); - mroots = fd_forest_blk_mroots( forest, ele ); - } - /* there are two cases: - (1) the first and common case is that we've received a mix of - shreds from equivocating FEC siblings A & B. In forest we have - recorded hash = { invalid_mr } for this fec set because we've - received a mix of merkle roots, so we nulled the FEC set. Let's - say fec_resolver then completes version B, and delivers it. We - can safely overwrite our null merkle root with B because we know - we must've received all the data for version B! - - (2) the second case is that we get two FEC completion msgs: one - for both version B and A. They get completed, one after the - other. We first overwrite from { invalid_mr } to B. But if - version A arrives, what should we do? If B is the correct - version, but we choose to overwrite the fec when A arrive, then - we need to ask shred to re-deliver the FEC set. Since we - don't know at this time if B or A is correct, we optimize for - case 1, and overwrite the merkle root with the new one. */ - mroots[fec_idx].mr = *mr; - mroots[fec_idx].cmr = *cmr; - } - - if( FD_UNLIKELY( slot_complete && ele->child != ULONG_MAX ) ) { - /* check for a child that is confirmed */ - fd_forest_blk_t * child = fd_forest_pool_ele( fd_forest_pool( forest ), ele->child ); - while( FD_UNLIKELY( child ) ) { - if( FD_UNLIKELY( child->chain_confirmed ) ) { - ele->confirmed_bid = fd_forest_blk_mroots( forest, child )[0].cmr; - break; - } - child = fd_forest_pool_ele( fd_forest_pool( forest ), child->sibling ); - } - } - - /* It's important that we set the cmpl idx here. If this happens to be - the last fec_complete we needed to finish the slot, then we rely on - the advance_consumed_frontier call in the below data_shred_insert - to move forward the consumed frontier. */ - for( uint idx = fec_set_idx; idx <= last_shred_idx; idx++ ) { - ele = fd_forest_data_shred_insert( forest, slot, parent_slot, idx, fec_set_idx, slot_complete & (idx == last_shred_idx), ref_tick, SHRED_SRC_RECOVERED, mr, cmr, rx_tick ); - } - return ele; -} - -fd_forest_blk_t * -fd_forest_code_shred_insert( fd_forest_t * forest, ulong slot, uint shred_idx, long rx_tick ) { - fd_forest_blk_t * ele = fd_forest_query( forest, slot ); - if( FD_UNLIKELY( !ele ) ) { - return NULL; - } - if( FD_UNLIKELY( !ele->first_shred_ts || rx_tickfirst_shred_ts ) ) ele->first_shred_ts = rx_tick; - - if( FD_UNLIKELY( shred_idx >= forest->shred_max ) ) { - ele->turbine_cnt += 1; - return ele; - } - - recv_ts_stamp( forest, ele, (uint)(shred_idx/FD_FEC_SHRED_CNT), rx_tick ); - - fd_forest_blk_idxs_t * code = fd_forest_blk_code( forest, ele ); - if( FD_LIKELY( !fd_forest_blk_idxs_test( code, shred_idx ) ) ) { /* newly seen shred */ - ele->turbine_cnt += 1; - fd_forest_blk_idxs_insert( code, shred_idx ); - } - return ele; -} - -fd_forest_blk_t * -fd_forest_fec_chain_verify( fd_forest_t * forest, fd_forest_blk_t * ele, fd_hash_t const * bid ) { - uint fec_idx = ele->complete_idx / 32UL; - - ele->confirmed_bid = *bid; /* confirmed */ - fd_hash_t const * expected_mr = bid; - - while( FD_UNLIKELY( !ele->chain_confirmed ) ) { - fd_forest_mr_t * mroots = fd_forest_blk_mroots( forest, ele ); - if( FD_UNLIKELY( fd_hash_eq( &mroots[fec_idx].mr, &empty_mr ) ) ) return NULL; /* can't verify the chain further */ - if( FD_UNLIKELY( !fd_hash_eq( expected_mr, &mroots[fec_idx].mr ) ) ) return ele; - - /* This FEC merkle is correct, and the chained merkle is correct. */ - ele->lowest_verified_fec = fec_idx; - expected_mr = &mroots[fec_idx].cmr; - - if( FD_UNLIKELY( fec_idx==0 ) ) { - /* hop to the parent slot, but first we've made it through this - slot successfully verifying the chain! mark it confirmed! */ - ele->chain_confirmed = 1; - FD_LOG_DEBUG(( "[%s] confirmed full slot %lu", __func__, ele->slot )); - ele = fd_forest_pool_ele( fd_forest_pool( forest ), ele->parent ); - - if( FD_UNLIKELY( !ele ) ) return NULL; /* can't verify the chain further */ - - ele->confirmed_bid = *expected_mr; /* CMR of child slot */ - if( FD_UNLIKELY( ele->complete_idx == UINT_MAX ) ) return NULL; /* can't verify the chain further */ - - fec_idx = ele->complete_idx / 32UL; - continue; - } - fec_idx--; /* go back one FEC set */ - } - return NULL; -} - -void -fd_forest_fec_clear( fd_forest_t * forest, ulong slot, uint fec_set_idx, uint max_shred_idx ) { - if( FD_UNLIKELY( slot <= fd_forest_root_slot( forest ) ) ) return; - FD_TEST( (ulong)fec_set_idx+max_shred_idxshred_max ); - fd_forest_blk_t * ele = fd_forest_query( forest, slot ); - if( FD_UNLIKELY( !ele ) ) return; - - fd_forest_blk_idxs_t * idxs = fd_forest_blk_idxs( forest, ele ); - for( uint i=fec_set_idx; i<=fec_set_idx+max_shred_idx; i++ ) { - fd_forest_blk_idxs_remove( idxs, i ); - } - - /* clear complete_idx if we've cleared the last FEC in the slot */ - if( FD_UNLIKELY( fec_set_idx+max_shred_idx == ele->complete_idx ) ) { - ele->complete_idx = UINT_MAX; - ele->lowest_verified_fec = UINT_MAX; - } - - /* There is a chance that the repair iterator is on this exact slot. - This means that this slot is in the requests list, and also at the - head of it. If we fec_clear on a range that is less than the - iterator's next_shred_idx, then the iterator will pop the slot as - "done" (next_shred_idx > complete_idx) without ever rerequesting - this fec. We must mark the slot incomplete so that the iterator can - re-request everything. Don't particularly care about the clear of - orphan slots as they are guaranteed to be iterated again. */ - - if( FD_UNLIKELY( forest->iter.ele_idx == fd_forest_pool_idx( fd_forest_pool( forest ), ele ) ) ) { - forest->iter.shred_idx = UINT_MAX; - } - - if( FD_UNLIKELY( fec_set_idx == 0 ) ) ele->buffered_idx = UINT_MAX; - else ele->buffered_idx = fd_uint_if( ele->buffered_idx != UINT_MAX, fd_uint_min( ele->buffered_idx, fec_set_idx - 1 ), UINT_MAX ); - - ele->last_shred_ts = 0; - - uint fec_idx = fec_set_idx / 32UL; - memset( &fd_forest_blk_mroots( forest, ele )[fec_idx].mr, 0, sizeof(fd_hash_t) ); - - /* Add this slot back to requests map */ - fd_forest_blk_t * pool = fd_forest_pool( forest ); - fd_forest_ancestry_t * ancestry = fd_forest_ancestry( forest ); - fd_forest_frontier_t * frontier = fd_forest_frontier( forest ); - if( FD_LIKELY( fd_forest_ancestry_ele_query( ancestry, &ele->slot, NULL, pool ) || - fd_forest_frontier_ele_query( frontier, &ele->slot, NULL, pool ) ) ) { - int has_requests_anc = 0; - ulong ancestor = fd_forest_pool_idx( pool, ele ); - while( ancestor != fd_forest_pool_idx_null( pool ) && !has_requests_anc ) { - if( fd_forest_requests_ele_query( fd_forest_requests( forest ), &ancestor, NULL, fd_forest_reqspool( forest ) ) ) { - has_requests_anc = 1; - break; - } - ancestor = fd_forest_pool_ele( pool, ancestor )->parent; - } - if( FD_UNLIKELY( !has_requests_anc ) ) { - requests_insert( forest, fd_forest_requests( forest ), fd_forest_reqslist( forest ), fd_forest_pool_idx( pool, ele ) ); - - /* remove any children than are in the requests list */ - ulong * queue = fd_forest_deque( forest ); - fd_forest_deque_push_tail( queue, fd_forest_pool_idx( pool, ele ) ); - while( FD_LIKELY( fd_forest_deque_cnt( queue ) ) ) { - fd_forest_blk_t * child = fd_forest_pool_ele( pool, fd_forest_deque_pop_head( queue ) ); - if( FD_LIKELY( child != ele ) ) requests_remove( forest, fd_forest_requests( forest ), fd_forest_reqslist( forest ), &forest->iter, fd_forest_pool_idx( pool, child ) ); - child = fd_forest_pool_ele( pool, child->child ); - while( FD_LIKELY( child ) ) { - fd_forest_deque_push_tail( queue, fd_forest_pool_idx( pool, child ) ); - child = fd_forest_pool_ele( pool, child->sibling ); - } - } - } - /* TODO we could update consumed, but it's not that necessary since - clearing a fec of a completed slot shouldn't really affect the - notion of when we completed the slot. consumed is also updated - mainly for metrics. For now we leave it alone. */ - } - // FD_LOG_INFO(( "cleared slot %lu fec set %u", slot, fec_set_idx )); -} - -fd_forest_blk_t const * -fd_forest_publish( fd_forest_t * forest, ulong new_root_slot ) { - fd_forest_ancestry_t * ancestry = fd_forest_ancestry( forest ); - fd_forest_orphaned_t * orphaned = fd_forest_orphaned( forest ); - fd_forest_frontier_t * frontier = fd_forest_frontier( forest ); - fd_forest_subtrees_t * subtrees = fd_forest_subtrees( forest ); - fd_forest_subtlist_t * subtlist = fd_forest_subtlist( forest ); - fd_forest_ref_t * conspool = fd_forest_conspool( forest ); - fd_forest_blk_t * pool = fd_forest_pool( forest ); - ulong null = fd_forest_pool_idx_null( pool ); - ulong * queue = fd_forest_deque( forest ); - - fd_forest_blk_t * old_root_ele = fd_forest_pool_ele( pool, forest->root ); - fd_forest_blk_t * new_root_ele = fd_forest_query( forest, new_root_slot ); - - /* As an unfortunate side effect of maintaining forest slots in such - a fine-grained way, and also the possibility we can publish forwards - and backwards non-monotically, we have to consider every possible case of - what the new root could be. - 1. new root not in forest. - 2. new root in ancestry or frontier. - 3. new root in orphaned or subtrees. */ - - /* 1. If we haven't been getting repairs, and we have a gap between - the root and orphans. we publish forward to a slot that we don't - have. In that case this isn't a bug, but we should be treating - this new root like the snapshot slot / init root. TODO: possible - could be publishing backwards to a slot that we don't have. */ - - if( FD_UNLIKELY( !new_root_ele ) ) { - /* TODO remove this codepath, we should never be publishing to a slot that we don't have any more */ - new_root_ele = fd_forest_blk_insert( forest, new_root_slot, old_root_ele->slot, NULL ); /* ensures new root is inserted as a frontier element */ - new_root_ele->complete_idx = 0; - new_root_ele->buffered_idx = 0; - requests_insert( forest, fd_forest_requests( forest ), fd_forest_reqslist( forest ), fd_forest_pool_idx( pool, new_root_ele ) ); - advance_consumed_frontier( forest, new_root_slot, 0 ); /* advances consumed frontier if possible */ - } - - /* First, remove the previous root, and add it to a FIFO prune queue. - head points to the queue head (initialized with old_root_ele). */ - FD_CHECK_CRIT( fd_forest_deque_cnt( queue ) == 0, "invariant violation" ); - - /* 2. New root is in forest, and is either in ancestry or frontier - (means it is part of the main repair tree). This is the common - case. */ - - fd_forest_blk_t * head = ancestry_frontier_remove( forest, old_root_ele->slot ); - if( FD_LIKELY( head ) ) fd_forest_deque_push_tail( queue, fd_forest_pool_idx( pool, head ) ); - - /* BFS down the tree, inserting each ele into the prune queue except - for the new root. Loop invariant: head always descends from - old_root_ele and never descends from new_root_ele. */ - - while( FD_LIKELY( fd_forest_deque_cnt( queue ) ) ) { - head = fd_forest_pool_ele( pool, fd_forest_deque_pop_head( queue ) ); - fd_forest_blk_t * child = fd_forest_pool_ele( pool, head->child ); - while( FD_LIKELY( child ) ) { - if( FD_LIKELY( child != new_root_ele ) ) { /* do not prune new root or descendants */ - child = ancestry_frontier_remove( forest, child->slot ); - fd_forest_deque_push_tail( queue, fd_forest_pool_idx( pool, child ) ); - } - child = fd_forest_pool_ele( pool, child->sibling ); - } - - consumed_remove( forest, fd_forest_pool_idx( pool, head ) ); - requests_remove( forest, fd_forest_requests( forest ), fd_forest_reqslist( forest ), &forest->iter, fd_forest_pool_idx( pool, head ) ); - fd_forest_pool_ele_release( pool, head ); - } - - new_root_ele->parent = null; /* unlink new root from parent */ - new_root_ele->chain_confirmed = 1; - forest->root = fd_forest_pool_idx( pool, new_root_ele ); - - /* 3. New root is in orphaned. This is the case where maybe the - expected snapshot slot has jumped far ahead. Invariants tell - us that the entire ancestry and frontier must have been pruned - above, so the consumed list and requests list must be empty.*/ - - int new_root_is_orphan = fd_forest_subtrees_ele_query( subtrees, &new_root_ele->slot, NULL, pool ) || - fd_forest_orphaned_ele_query( orphaned, &new_root_ele->slot, NULL, pool ); - - if( FD_UNLIKELY( new_root_is_orphan ) ) { - - /* Extend the frontier from the new root */ - - fd_forest_deque_push_tail( queue, fd_forest_pool_idx( pool, new_root_ele ) ); - while( FD_LIKELY( fd_forest_deque_cnt( queue ) ) ) { - head = fd_forest_pool_ele( pool, fd_forest_deque_pop_head( queue ) ); - subtrees_orphaned_remove( forest, head->slot ); - - fd_forest_blk_t * child = fd_forest_pool_ele( pool, head->child ); - if( FD_LIKELY( child ) ) fd_forest_ancestry_ele_insert( ancestry, head, pool ); - else fd_forest_frontier_ele_insert( frontier, head, pool ); - while( child ) { - fd_forest_deque_push_tail( queue, fd_forest_pool_idx( pool, child ) ); - child = fd_forest_pool_ele( pool, child->sibling ); - } - requests_remove( forest, fd_forest_orphreqs( forest ), fd_forest_orphlist( forest ), &forest->orphiter, fd_forest_pool_idx( pool, head ) ); - } - } - - /* If there is nothing on the consumed, like in the case where we - publish to an orphan, or during catchup where all of our repair - consumed frontiers were < the new root. In that case we need to - continue repairing from the new root, so add it to the consumed - map. */ - - if( FD_UNLIKELY( fd_forest_conslist_is_empty( fd_forest_conslist( forest ), conspool ) ) ) { - consumed_insert( forest, fd_forest_pool_idx( pool, new_root_ele ) ); - requests_insert( forest, fd_forest_requests( forest ), fd_forest_reqslist( forest ), fd_forest_pool_idx( pool, new_root_ele ) ); - /* TODO: is there a chance when we actually need to repair the root - after snapshot expected slot goes in? in this case this is - invalid */ - new_root_ele->complete_idx = 0; - new_root_ele->buffered_idx = 0; - advance_consumed_frontier( forest, new_root_ele->slot, 0 ); - } - - /* Lastly, cleanup orphans if there orphan heads < new_root_slot. - First, add any relevant orphans to the prune queue. */ - - for( fd_forest_subtlist_iter_t iter = fd_forest_subtlist_iter_fwd_init( subtlist, pool ); - !fd_forest_subtlist_iter_done( iter, subtlist, pool ); - iter = fd_forest_subtlist_iter_fwd_next( iter, subtlist, pool ) ) { - fd_forest_blk_t * ele = fd_forest_subtlist_iter_ele( iter, subtlist, pool ); - if( FD_UNLIKELY( ele->slot < new_root_slot ) ) { - fd_forest_deque_push_tail( queue, fd_forest_pool_idx( pool, ele ) ); - } - } - - /* Now BFS and clean up children of these orphan heads */ - while( FD_UNLIKELY( fd_forest_deque_cnt( queue ) ) ) { - head = fd_forest_pool_ele( pool, fd_forest_deque_pop_head( queue ) ); - fd_forest_blk_t * child = fd_forest_pool_ele( pool, head->child ); - while( FD_LIKELY( child ) ) { - if( FD_LIKELY( child != new_root_ele ) ) { - fd_forest_deque_push_tail( queue, fd_forest_pool_idx( pool, child ) ); - } - child = fd_forest_pool_ele( pool, child->sibling ); - } - subtrees_orphaned_remove( forest, head->slot ); - /* Remove from orphan requests if present */ - requests_remove( forest, fd_forest_orphreqs( forest ), fd_forest_orphlist( forest ), &forest->orphiter, fd_forest_pool_idx( pool, head ) ); - fd_forest_pool_ele_release( pool, head ); /* free head */ - } - - new_root_ele->sibling = null; /* unlink new root from siblings, just in case */ - return new_root_ele; -} - - -ulong -fd_forest_highest_repaired_slot( fd_forest_t const * forest ) { - fd_forest_blk_t const * pool = fd_forest_pool_const( forest ); - fd_forest_blk_t const * root = fd_forest_pool_ele_const( pool, forest->root ); - fd_forest_conslist_t const * conslist = fd_forest_conslist_const( forest ); - fd_forest_ref_t const * conspool = fd_forest_conspool_const( forest ); - - if( FD_UNLIKELY( !root ) ) return 0; - - ulong max_repaired_slot = root->slot; - for( fd_forest_conslist_iter_t iter = fd_forest_conslist_iter_fwd_init( conslist, conspool ); - !fd_forest_conslist_iter_done( iter, conslist, conspool ); - iter = fd_forest_conslist_iter_fwd_next( iter, conslist, conspool ) ) { - fd_forest_ref_t const * ele = fd_forest_conslist_iter_ele_const( iter, conslist, conspool ); - fd_forest_blk_t const * ele_ = fd_forest_pool_ele_const( pool, ele->idx ); - if( FD_LIKELY( ele_->slot > max_repaired_slot ) ) max_repaired_slot = ele_->slot; - } - return max_repaired_slot; -} - - -fd_forest_t * -fd_forest_clear( fd_forest_t * forest ) { - return forest; -} - -fd_forest_iter_t * -fd_forest_iter_next( fd_forest_iter_t * iter, fd_forest_t * forest ) { - fd_forest_blk_t const * pool = fd_forest_pool_const( forest ); - fd_forest_blk_t const * ele = fd_forest_pool_ele_const( pool, iter->ele_idx ); - fd_forest_reqslist_t * reqslist = iter->list_gaddr == forest->reqslist_gaddr ? fd_forest_reqslist( forest ) : fd_forest_orphlist( forest ); - fd_forest_requests_t * reqsmap = iter->list_gaddr == forest->reqslist_gaddr ? fd_forest_requests( forest ) : fd_forest_orphreqs( forest ); - fd_forest_ref_t * reqspool = fd_forest_reqspool( forest ); - - /* forest->iter.ele_idx should always refer to the head of the - requests list, unless iter.ele_idx is null (initializing)*/ - if( FD_UNLIKELY( iter->ele_idx != fd_forest_pool_idx_null( pool ) && - iter->ele_idx != fd_forest_reqslist_ele_peek_head( reqslist, reqspool )->idx ) ) { - FD_LOG_WARNING(("invariant violation: forest iterator ele_idx %lu != head of request list %lu", iter->ele_idx, fd_forest_reqslist_ele_peek_head( reqslist, reqspool )->idx)); - /* check if the iterator ele_idx lives in the forest for debugging. */ - fd_forest_blk_t const * ele_iter = fd_forest_pool_ele_const( pool, iter->ele_idx ); - fd_forest_blk_t const * req_head = fd_forest_pool_ele_const( pool, fd_forest_reqslist_ele_peek_head( reqslist, reqspool )->idx ); - ulong slot_iter = ele_iter ? ele_iter->slot : 0; - ulong slot_req_head = req_head ? req_head->slot : 0; - FD_LOG_CRIT(( "Forest iterator slot %lu != head of request list slot %lu. Does forest have %lu? %p. Does forest have %lu? %p.", slot_iter, slot_req_head, slot_iter, (void *)fd_forest_query( forest, slot_iter ), req_head->slot, (void *)fd_forest_query( forest, slot_req_head ) )); - } - - uint next_shred_idx = iter->shred_idx; - for(;;) { - next_shred_idx++; - - /* Case 1: No more shreds in this slot to request, move to the - next one. Wraparound the shred_idx. - - Case 2: original iter.shred_idx == UINT_MAX (implies prev req - was a highest_window_idx request). Also requires moving to next - slot and wrapping the shred_idx. */ - - if( FD_UNLIKELY( !ele || next_shred_idx > ele->complete_idx || iter->shred_idx == UINT_MAX ) ) { - - /* done requesting this slot. peek the next slot from requests - deque. But first, add this slot's children to the requests - deque! Debatable: should we add this slot's children to - the requests deque until we have actually sent reqs for every - shred of the slot? */ - - if( FD_LIKELY( ele ) ) { - fd_forest_blk_t const * child = fd_forest_pool_ele_const( pool, ele->child ); - while( FD_LIKELY( child ) ) { - requests_insert( forest, reqsmap, reqslist, fd_forest_pool_idx( pool, child ) ); - child = fd_forest_pool_ele_const( pool, child->sibling ); - } - /* so annoying. can't call requests_remove because itll invalidate the current iter->ele_idx, - so we explicitly pop the head and free the ele here. */ - fd_forest_ref_t * head = fd_forest_reqslist_ele_pop_head( reqslist, reqspool ); - fd_forest_requests_ele_remove ( reqsmap, &head->idx, NULL, reqspool ); - fd_forest_reqspool_ele_release( reqspool, head ); - - if( FD_UNLIKELY( iter->shred_idx == UINT_MAX && ( ele->buffered_idx == UINT_MAX || ele->buffered_idx < ele->complete_idx ) ) ) { - /* If we just made a highest_window_idx request, add this slot - back to the requests deque at the end. Also condition on - whether or not this slot is still incomplete. If the slot - is complete and we add it back to the loop, we will end up - infinite looping. */ - requests_insert( forest, reqsmap, reqslist, iter->ele_idx ); - } - } - - /* Move onto the next slot */ - if( FD_UNLIKELY( fd_forest_reqslist_is_empty( reqslist, reqspool ) ) ) { - iter->ele_idx = fd_forest_pool_idx_null( pool ); - iter->shred_idx = UINT_MAX; - return iter; - } - - iter->ele_idx = fd_forest_reqslist_ele_peek_head( reqslist, reqspool )->idx; - ele = fd_forest_pool_ele_const( pool, iter->ele_idx ); - - if( FD_UNLIKELY( !fd_forest_query( forest, ele->slot ) ) ) { - /* TODO: should never meet this condition if the iterator - invariants are maintained. Can consider changing back to - LOG_CRIT after dynamic expected snapshot slot changes go in, - or removing this check entirely. */ - FD_LOG_WARNING(( "[%s] slot %lu not found in forest. purging from requests list.", __func__, ele->slot )); - requests_remove( forest, reqsmap, reqslist, iter, iter->ele_idx ); - return iter; - } - next_shred_idx = ele->buffered_idx + 1; - } - - /* Common case - valid shred to request. Note you can't know the - ele->complete_idx until you have actually received the slot - complete shred, but the last shred may have been evicted, so we - need leq. */ - - if( ele->complete_idx != UINT_MAX && next_shred_idx <= ele->complete_idx ) { - fd_forest_blk_idxs_t const * idxs = fd_forest_blk_idxs( forest, ele ); - - /* Jump a word at a time to the first missing shred, or to - complete_idx if none. Not after a highest_window_idx request, - which tests one shred of the next slot and moves on. */ - - if( FD_LIKELY( iter->shred_idx != UINT_MAX ) ) { - next_shred_idx = (uint)fd_ulong_min( fd_forest_blk_idxs_next_unset( idxs, next_shred_idx, ele->complete_idx ), ele->complete_idx ); - } - if( !fd_forest_blk_idxs_test( idxs, next_shred_idx ) ) { - iter->shred_idx = next_shred_idx; - break; - } - } - - /* Current slot actually needs a highest_window_idx request */ - - if( FD_UNLIKELY( ele->complete_idx == UINT_MAX ) ) { - iter->shred_idx = UINT_MAX; - break; - } - } - return iter; -} - -int -fd_forest_iter_done( fd_forest_iter_t * iter, fd_forest_t * forest ) { - fd_forest_blk_t const * pool = fd_forest_pool_const( forest ); - return iter->ele_idx == fd_forest_pool_idx_null( pool ); /* no more elements */ -} - -#include - -#define FD_FOREST_ORPHANED_PRINT_MAX_DEPTH 500UL - -static void -orphaned_print( fd_forest_t const * forest, - fd_forest_blk_t const * ele, - fd_forest_blk_t const * prev, - ulong last_printed, - int depth, - const char * prefix, - ulong print_depth ) { - - if( FD_UNLIKELY( ele == NULL ) ) return; - - /* Prevent stack overflow from excessive recursion */ - if( FD_UNLIKELY( print_depth >= FD_FOREST_ORPHANED_PRINT_MAX_DEPTH ) ) { - printf( "... (truncated: too many orphaned nodes, max depth %lu reached)\n", FD_FOREST_ORPHANED_PRINT_MAX_DEPTH ); - return; - } - - fd_forest_blk_t const * pool = fd_forest_pool_const( forest ); - int digits = (int)fd_ulong_base10_dig_cnt( ele->slot ); - - /* If there is a prefix, this means we are on a fork, and we need to - indent to the correct depth. We do depth - 1 for more satisfying - spacing. */ - if( FD_UNLIKELY( strcmp( prefix, "" ) ) ) { - for( int i = 0; i < depth - 1; i++ ) printf( " " ); - if( depth > 0 ) printf( "%s", prefix ); - } - - if ( FD_UNLIKELY( !prev ) ) { // New interval - printf("[%lu" , ele->slot ); - last_printed = ele->slot; - depth += 1 + digits; - } - - fd_forest_blk_t const * curr = fd_forest_pool_ele_const( pool, ele->child ); - - /* Cases in which we close the interval: - 1. the slots are no longer consecutive. no eliding, close bracket - 2. current ele has multiple children, want to print forks. - Maintain last_printed on this fork so that we don't print [a, a] - intervals. */ - - fd_forest_blk_t const * new_prev = ele; - - if( prev && prev->slot != ele->slot - 1 ) { // non-consecutive, do not elide - if( last_printed == prev->slot ){ - printf( "] ── [%lu", ele->slot ); - depth += digits + 6; - } else { - printf( ", %lu] ── [%lu", prev->slot, ele->slot ); - depth += digits + (int)fd_ulong_base10_dig_cnt( prev->slot ) + 8; - } - last_printed = ele->slot; - } else if( curr && curr->sibling != ULONG_MAX ) { // has multiple children, do not elide - if( last_printed == ele->slot ){ - printf( "] ── " ); - depth += 5; - } else { - printf( ", %lu] ── ", ele->slot ); - depth += digits + 2; - } - last_printed = ele->slot; - new_prev = NULL; - } - - if( !curr ){ // no children, close bracket, end fork - if( last_printed == ele->slot ){ - printf( "]\n" ); - } else { - printf( ", %lu]\n", ele->slot ); - } - return; - } - - char new_prefix[32]; - new_prefix[0] = '\0'; /* first fork stays on the same line, no prefix */ - while( curr ) { - orphaned_print( forest, curr, new_prev, last_printed, depth, new_prefix, print_depth + 1UL ); - curr = fd_forest_pool_ele_const( pool, curr->sibling ); - - /* Set up prefix for following iterations */ - if( curr && curr->sibling != ULONG_MAX ) { - fd_cstr_printf( new_prefix, sizeof(new_prefix), NULL, "├── " ); /* any following forks start on new lines */ - } else { - fd_cstr_printf( new_prefix, sizeof(new_prefix), NULL, "└── " ); /* any following forks start on new lines */ - } - } - -} - -#define FD_FOREST_ANCESTRY_PRINT_MAX_DEPTH 500UL - -static void -ancestry_print( fd_forest_t const * forest, fd_forest_blk_t const * ele, int space, const char * prefix, fd_forest_blk_t const * prev, int elide, ulong print_depth ) { - fd_forest_blk_t const * pool = fd_forest_pool_const( forest ); - - char new_prefix[32]; /* only ever holds a fixed 4-5 byte branch glyph */ - - while( ele ) { - - /* excessive recursion */ - if( FD_UNLIKELY( print_depth >= FD_FOREST_ANCESTRY_PRINT_MAX_DEPTH ) ) { - printf( "... (truncated: too many forks, max depth %lu reached)\n", FD_FOREST_ANCESTRY_PRINT_MAX_DEPTH ); - return; - } - - /* print the slot itself. either we might need to start a new interval, or it may get elided */ - fd_forest_blk_t const * child = fd_forest_pool_ele_const( pool, ele->child ); - - if( !elide ) { - if( space > 0 ) printf( "\n" ); - for( int i = 0; i < space; i++ ) printf( " " ); - printf( "%s", prefix ); - printf( "%lu", ele->slot ); - } - - if( !child && !elide ) { /* double check these cases aren't the same...*/ - printf( "]" ); - return; - } /* no children, close bracket */ - - if( !child && elide ) { - printf( ", %lu]", ele->slot ); - return; - } - - prev = ele; - int one_child = child && child->sibling == ULONG_MAX; - if( one_child && - child->slot != ele->slot + 1 ) { // if I have ONE CHILD and one child is non-consecutive - - if( elide ) { - /* current slot wasn't printed, but now that we are branching, - we will want to print the current slot and close the bracket */ - printf( ", %lu]", ele->slot ); - space += fd_int_max( (int)fd_ulong_base10_dig_cnt( ele->slot ) + 2, 0 ); - } else { - printf( "]"); - } - - fd_cstr_printf( new_prefix, sizeof(new_prefix), NULL, "└── [" ); /* end branch */ - ele = child; - space += 5; - prefix = new_prefix; - elide = 0; - } else if ( one_child && child->slot == ele->slot + 1 ) { - ele = child; - elide = 1; - } else { /* multiple children */ - if( elide ) { - /* current slot wasn't printed, but now that we are branching, - we will want to print the current slot and close the bracket */ - printf( ", %lu]", ele->slot ); - space += fd_int_max( (int)fd_ulong_base10_dig_cnt( ele->slot ) + 2, 0 ); - } else { - printf( "]"); - } - - while( child ) { - if( fd_forest_pool_ele_const( pool, child->sibling ) ) { - fd_cstr_printf( new_prefix, sizeof(new_prefix), NULL, "├── [" ); /* branch indicating more siblings follow */ - ancestry_print( forest, child, space + 5, new_prefix, prev, 0, print_depth + 1UL ); - } else { - fd_cstr_printf( new_prefix, sizeof(new_prefix), NULL, "└── [" ); /* end branch */ - ancestry_print( forest, child, space + 5, new_prefix, prev, 0, print_depth + 1UL ); - } - child = fd_forest_pool_ele_const( pool, child->sibling ); - } - return; - } - } -} - -void -fd_forest_ancestry_print( fd_forest_t const * forest ) { - printf(("\n\n[Ancestry]\n" ) ); - ancestry_print( forest, fd_forest_pool_ele_const( fd_forest_pool_const( forest ), forest->root ), 0, "[", NULL, 0, 0UL ); - fflush(stdout); /* Ensure ancestry printf output is flushed */ -} - -void -fd_forest_frontier_print( fd_forest_t const * forest ) { - printf( "\n\n[Repairing Next]\n" ); - fd_forest_conslist_t const * conslist = fd_forest_conslist_const( forest ); - fd_forest_ref_t const * conspool = fd_forest_conspool_const( forest ); - fd_forest_blk_t const * pool = fd_forest_pool_const( forest ); - for( fd_forest_conslist_iter_t iter = fd_forest_conslist_iter_fwd_init( conslist, conspool ); - !fd_forest_conslist_iter_done( iter, conslist, conspool ); - iter = fd_forest_conslist_iter_fwd_next( iter, conslist, conspool ) ) { - fd_forest_ref_t const * ele = fd_forest_conslist_iter_ele_const( iter, conslist, conspool ); - fd_forest_blk_t const * ele_ = fd_forest_pool_ele_const( pool, ele->idx ); - printf("%lu (%u/%u)\n", ele_->slot, ele_->buffered_idx + 1, ele_->complete_idx + 1 ); - } - fflush(stdout); -} - -void -fd_forest_orphaned_print( fd_forest_t const * forest ) { - printf( "\n[Orphaned]\n" ); - fd_forest_subtlist_t const * subtlist = fd_forest_subtlist_const( forest ); - fd_forest_blk_t const * pool = fd_forest_pool_const( forest ); - for( fd_forest_subtlist_iter_t iter = fd_forest_subtlist_iter_fwd_init( subtlist, pool ); - !fd_forest_subtlist_iter_done( iter, subtlist, pool ); - iter = fd_forest_subtlist_iter_fwd_next( iter, subtlist, pool ) ) { - fd_forest_blk_t const * ele = fd_forest_subtlist_iter_ele_const( iter, subtlist, pool ); - orphaned_print( forest, fd_forest_pool_ele_const( fd_forest_pool_const( forest ), fd_forest_pool_idx( pool, ele ) ), NULL, 0, 0, "", 0UL ); - } - fflush(stdout); -} - -void -fd_forest_print( fd_forest_t const * forest ) { - if( FD_UNLIKELY( forest->root == ULONG_MAX ) ) return; - FD_LOG_NOTICE(("\n\n[Forest]" ) ); - fd_forest_ancestry_print( forest ); - fd_forest_frontier_print( forest ); - fd_forest_orphaned_print( forest ); - printf("\n"); - - fflush(stdout); -} - -#undef FD_FOREST_ANCESTRY_PRINT_MAX_DEPTH -#undef FD_FOREST_ORPHANED_PRINT_MAX_DEPTH - -#undef FD_FOREST_PRINT diff --git a/src/discof/forest/fd_forest.h b/src/discof/forest/fd_forest.h deleted file mode 100644 index 62b84e1706e..00000000000 --- a/src/discof/forest/fd_forest.h +++ /dev/null @@ -1,1037 +0,0 @@ -#ifndef HEADER_fd_src_discof_forest_fd_forest_h -#define HEADER_fd_src_discof_forest_fd_forest_h - -/* Forest is an API for repairing blocks as they are discovered from the - cluster via Turbine or Gossip. Shreds (from Turbine) and - confirmations (from Tower) inform forest that slot exists. Repair - ensures that this block is received in its entirety by requesting - repairs for missing shreds for the block. - - Note that forest needs to track the strict subset of shreds that are - known by fec_resolver, store, and reasm. If any of these structures - have evicted shreds, forest needs to clear out the corresponding FEC - sets from forest to be re-requested. It's okay if shreds are evicted - from reasm and we re-request for them and they pass through - fec_resolver again. Although we could be creating duplicate ctxs, - that's fine! We might later on get an evict notice for the second - incomplete ctx but that's okay too!!! just have a bunch of useless - messages that eventually will get ignored when we publish past it!!!! - - Like other fork-aware structures, forest maintains a tree that - records the ancestry of slots. It also maintains references to the - tips of each known fork (the frontier map), and also the latest - slot we finished repairing on each fork (the consumed map). Any slot - that doesn't have a known ancestry connecting it back to the root yet - is part of the orphaned map. And the head of every orphaned tree is - part of the subtrees map. While this seems very verbose, it allows - for fast iteration and lookup of the different types of slots. - - fd_policy makes orphan requests that recover gaps between orphaned - subtrees and the main ancestry tree, and then the forest iterator - suggest repairs to make progress on the tree forwards (using BFS). */ - -/* Merkle root tracking. - For each FEC set in the slot, we record the merkle root of the first - shred we receive in `mroots[ fec_set_idx / 32 ]`. Then for any - shred in the same FEC inserted later, the merkle root of the new - shred is compared to the merkle root we have stored. - - If they are the same -> good. - If they are different -> we're going to mark this merkle root as - incorrect. We do this by setting the merkle - root to a null hash for later detection. - - Note we don't verify the chain on each FEC arrival, because we can't - tell whether the CMR of the following FEC is incorrect or if the - current MR we have is incorrect. We can only verify the chain when - we get a confirmation of a block_id. - - Eventually one of two things happen: - 1. We are able to complete the version of the FEC with the merkle root - we have stored. This is the common case, and means we only saw one - version of the merkle root. - - 2. We are not able to complete any version of the FEC. - - Imagine we get shred 0-15 of FEC_A. then get shreds 16-31 of - FEC_B. We would have set the merkle root to the null hash for - that FEC set, but fec_resolver would not be able to complete the - FEC because from a shred index POV, we don't have anything we - need to repair (and we won't be making any new requests for that - FEC set). - It's difficult to differentiate between a slot where we haven't - finished repairing, and a slot we can't repair because the - version we have is a bad version. So merkle chaining - verification can only be performed on slots that have all the - shreds received. - - 3. We receive some shreds for both FEC_A and FEC_B, but get a FEC - completion for FEC_B. - - Could possibly happen during turbine, like we repair some data - shreds from FEC_A, but get a completion for FEC_B through - turbine. At this point we'll take whatever we have completed - first, so overwrite our merkle root entry. It's likely being - overwritten from the null_hash to the FEC_B merkle root. - - So unfortunately...because of case 2, we determine "slot completion" - status when all the shreds in the slot have been received, NOT when - the slot completes with all the FEC completions. We can rely on that - at least some version of all the shreds in the slot will arrive - eventually. - - As soon as we have a confirmed block id, we can verify the slot by - verifying the chain of merkle roots backwards. As the CMRs correctly - chain, the verified status on each FEC set is set. If they don't - chain, we dump & repair that specific FEC set. For example, say the - 2nd & 3rd FEC set is incorrect. In this case, the merkle roots array - and bitset will look like the following after one call of - chain_verify(slot, confirmed_bid): - actual last fec - | - v - merkle_roots [ A, B', C', D, E, F, confirmed_bid] <- confirmed_bid stored for convenience - merkle_verified [ 0, 0, 0, 1, 1, 1, 1 ] - - At this point, C' will be dumped and repaired. Since D is verified, - and the CMR entry contains the correct version of C's merkle root, we - can now verify any shred of FEC set C that arrives and reject if the - merkle root doesn't match the cmr entry in D. - - After C is successfully repaired, the after_fec call in repair_tile - will re-trigger chain_verify on the slot again. After this call of - chain_verify, the merkle roots array and bitset will look like this: - - merkle_roots [ A, B', C, D, E, F, confirmed_bid] <- confirmed_bid stored for convenience - merkle_verified [ 0, 0, 1, 1, 1, 1, 1 ] - - At this point, C is verified, but B' is detected as incorrect. The same - dump and repair process is repeated for B'. Once that after_fec on B - is called, the merkle roots array and bitset will look like this: - - merkle_roots [ A, B, C, D, E, F, confirmed_bid] <- confirmed_bid stored for convenience - merkle_verified [ 1, 1, 1, 1, 1, 1, 1 ] - confirmed = 1 - - The chain verify progresses beyond this slot, and the ancestors of - this slots will also be traversed until a confirmed slot is found, or - another incorrect FEC is detected. Note that because earlier - confirmations may have confirmed ancestors, and because there is once - verification "in-progress at all times", confirmation status can look - like: - - slot 1 - slot 2 - slot 3 - slot 4 - slot 5 - slot 6 - slot 7 .... - confirmed: 1 1 0 0 0 1 1 - - i.e. there will be up to two contiguous chains of confirmed slots in - the forest, but not more. There can be unconfirmed slots after slot 7. - There may be forks as well, but only one fork can be confirmed. -*/ - -#include "../../disco/fd_disco_base.h" -#include "../../disco/shred/fd_fec_set.h" - -#define FD_FOREST_MAGIC (0xf17eda2ce7b1c0UL) /* firedancer forest version 0 */ - -/* Per-block shred idx bitsets are raw ulong words, word_cnt per block, - living in side arrays indexed by pool idx so the per-block shred - bound (shred_max) is a runtime value. idx must be < shred_max. */ - -typedef ulong fd_forest_blk_idxs_t; - -FD_FN_CONST static inline ulong fd_forest_blk_idxs_word_cnt( ulong shred_max ) { return (shred_max+63UL)>>6; } -FD_FN_PURE static inline int fd_forest_blk_idxs_test ( fd_forest_blk_idxs_t const * set, ulong idx ) { return fd_ulong_extract_bit( set[ idx>>6 ], (int)(idx&63UL) ); } -static inline void fd_forest_blk_idxs_insert( fd_forest_blk_idxs_t * set, ulong idx ) { set[ idx>>6 ] = fd_ulong_set_bit ( set[ idx>>6 ], (int)(idx&63UL) ); } -static inline void fd_forest_blk_idxs_remove( fd_forest_blk_idxs_t * set, ulong idx ) { set[ idx>>6 ] = fd_ulong_clear_bit( set[ idx>>6 ], (int)(idx&63UL) ); } - -FD_FN_PURE static inline ulong -fd_forest_blk_idxs_cnt( fd_forest_blk_idxs_t const * set, ulong word_cnt ) { - ulong cnt = 0UL; - for( ulong i=0UL; i>6; - ulong hi_w = hi>>6; - ulong bits = ~set[ w ] & (ULONG_MAX<<(lo&63UL)); - while( !bits && wwksp_gaddr ); -} - -/* fd_forest_{pool, pool_const} returns a pointer in the caller's address - space to forest's element pool. */ - -FD_FN_PURE static inline fd_forest_blk_t * -fd_forest_pool( fd_forest_t * forest ) { - return fd_wksp_laddr_fast( fd_forest_wksp( forest ), forest->pool_gaddr ); -} - -FD_FN_PURE static inline fd_forest_blk_t const * -fd_forest_pool_const( fd_forest_t const * forest ) { - return fd_wksp_laddr_fast( fd_forest_wksp( forest ), forest->pool_gaddr ); -} - -/* fd_forest_blk_{idxs,code} return blk's data / code shred idx bitset - (fd_forest_blk_idxs_word_cnt( forest->shred_max ) words) and - fd_forest_blk_{mroots,recv} blk's merkle roots / per-FEC turbine - arrival stamps (forest->shred_max / FD_FEC_SHRED_CNT entries each). - blk must be a pool element of forest. */ - -FD_FN_PURE static inline fd_forest_blk_idxs_t * -fd_forest_blk_idxs( fd_forest_t const * forest, fd_forest_blk_t const * blk ) { - fd_forest_blk_idxs_t * idxs = fd_wksp_laddr_fast( fd_forest_wksp( forest ), forest->idxs_gaddr ); - return idxs + fd_forest_pool_idx( fd_forest_pool_const( forest ), blk )*fd_forest_blk_idxs_word_cnt( forest->shred_max ); -} - -FD_FN_PURE static inline fd_forest_blk_idxs_t * -fd_forest_blk_code( fd_forest_t const * forest, fd_forest_blk_t const * blk ) { - fd_forest_blk_idxs_t * code = fd_wksp_laddr_fast( fd_forest_wksp( forest ), forest->code_gaddr ); - return code + fd_forest_pool_idx( fd_forest_pool_const( forest ), blk )*fd_forest_blk_idxs_word_cnt( forest->shred_max ); -} - -FD_FN_PURE static inline fd_forest_mr_t * -fd_forest_blk_mroots( fd_forest_t const * forest, fd_forest_blk_t const * blk ) { - fd_forest_mr_t * mroots = fd_wksp_laddr_fast( fd_forest_wksp( forest ), forest->mroots_gaddr ); - return mroots + fd_forest_pool_idx( fd_forest_pool_const( forest ), blk )*(forest->shred_max/FD_FEC_SHRED_CNT); -} - -FD_FN_PURE static inline fd_forest_recv_t * -fd_forest_blk_recv( fd_forest_t const * forest, fd_forest_blk_t const * blk ) { - fd_forest_recv_t * recv = fd_wksp_laddr_fast( fd_forest_wksp( forest ), forest->recv_gaddr ); - return recv + fd_forest_pool_idx( fd_forest_pool_const( forest ), blk )*(forest->shred_max/FD_FEC_SHRED_CNT); -} - -/* fd_forest_{ancestry, ancestry_const} returns a pointer in the caller's - address space to forest's ancestry map. */ - -FD_FN_PURE static inline fd_forest_ancestry_t * -fd_forest_ancestry( fd_forest_t * forest ) { - return fd_wksp_laddr_fast( fd_forest_wksp( forest ), forest->ancestry_gaddr ); -} - -FD_FN_PURE static inline fd_forest_ancestry_t const * -fd_forest_ancestry_const( fd_forest_t const * forest ) { - return fd_wksp_laddr_fast( fd_forest_wksp( forest ), forest->ancestry_gaddr ); -} - -/* fd_forest_{frontier, frontier_const} returns a pointer in the caller's - address space to forest's frontier map. */ - -FD_FN_PURE static inline fd_forest_frontier_t * -fd_forest_frontier( fd_forest_t * forest ) { - return fd_wksp_laddr_fast( fd_forest_wksp( forest ), forest->frontier_gaddr ); -} - -FD_FN_PURE static inline fd_forest_frontier_t const * -fd_forest_frontier_const( fd_forest_t const * forest ) { - return fd_wksp_laddr_fast( fd_forest_wksp( forest ), forest->frontier_gaddr ); -} - -/* fd_forest_{subtrees, subtrees_const} returns a pointer in the caller's - address space to forest's subtrees map. */ - -FD_FN_PURE static inline fd_forest_subtrees_t * -fd_forest_subtrees( fd_forest_t * forest ) { - return fd_wksp_laddr_fast( fd_forest_wksp( forest ), forest->subtrees_gaddr ); -} - -FD_FN_PURE static inline fd_forest_subtrees_t const * -fd_forest_subtrees_const( fd_forest_t const * forest ) { - return fd_wksp_laddr_fast( fd_forest_wksp( forest ), forest->subtrees_gaddr ); -} - -/* fd_forest_{subtlist, subtlist_const} returns a pointer in the caller's - address space to forest's subtlist. */ - -FD_FN_PURE static inline fd_forest_subtlist_t * -fd_forest_subtlist( fd_forest_t * forest ) { - return fd_wksp_laddr_fast( fd_forest_wksp( forest ), forest->subtlist_gaddr ); -} - -FD_FN_PURE static inline fd_forest_subtlist_t const * -fd_forest_subtlist_const( fd_forest_t const * forest ) { - return fd_wksp_laddr_fast( fd_forest_wksp( forest ), forest->subtlist_gaddr ); -} - -FD_FN_PURE static inline fd_forest_orphan_ent_t * -fd_forest_orphanq( fd_forest_t * forest ) { - return fd_wksp_laddr_fast( fd_forest_wksp( forest ), forest->orphanq_gaddr ); -} - -FD_FN_PURE static inline fd_forest_orphan_ent_t const * -fd_forest_orphanq_const( fd_forest_t const * forest ) { - return fd_wksp_laddr_fast( fd_forest_wksp( forest ), forest->orphanq_gaddr ); -} - -/* fd_forest_{orphaned, orphaned_const} returns a pointer in the caller's - address space to forest's orphaned map. */ - -FD_FN_PURE static inline fd_forest_orphaned_t * -fd_forest_orphaned( fd_forest_t * forest ) { - return fd_wksp_laddr_fast( fd_forest_wksp( forest ), forest->orphaned_gaddr ); -} - -FD_FN_PURE static inline fd_forest_orphaned_t const * -fd_forest_orphaned_const( fd_forest_t const * forest ) { - return fd_wksp_laddr_fast( fd_forest_wksp( forest ), forest->orphaned_gaddr ); -} - -/* fd_forest_{consumed, consumed_const} returns a pointer in the caller's - address space to forest's consumed map. */ - -FD_FN_PURE static inline fd_forest_consumed_t * -fd_forest_consumed( fd_forest_t * forest ) { - return fd_wksp_laddr_fast( fd_forest_wksp( forest ), forest->consumed_gaddr ); -} - -FD_FN_PURE static inline fd_forest_consumed_t const * -fd_forest_consumed_const( fd_forest_t const * forest ) { - return fd_wksp_laddr_fast( fd_forest_wksp( forest ), forest->consumed_gaddr ); -} - -/* fd_forest_{conslist, conslist_const} returns a pointer in the caller's - address space to forest's consumed list. */ - -FD_FN_PURE static inline fd_forest_conslist_t * -fd_forest_conslist( fd_forest_t * forest ) { - return fd_wksp_laddr_fast( fd_forest_wksp( forest ), forest->conslist_gaddr ); -} - -FD_FN_PURE static inline fd_forest_conslist_t const * -fd_forest_conslist_const( fd_forest_t const * forest ) { - return fd_wksp_laddr_fast( fd_forest_wksp( forest ), forest->conslist_gaddr ); -} - -/* fd_forest_{conspool, conspool_const} returns a pointer in the caller's - address space to forest's consumed pool. */ - -FD_FN_PURE static inline fd_forest_ref_t * -fd_forest_conspool( fd_forest_t * forest ) { - return fd_wksp_laddr_fast( fd_forest_wksp( forest ), forest->conspool_gaddr ); -} - -FD_FN_PURE static inline fd_forest_ref_t const * -fd_forest_conspool_const( fd_forest_t const * forest ) { - return fd_wksp_laddr_fast( fd_forest_wksp( forest ), forest->conspool_gaddr ); -} - -/* fd_forest_{requests, requests_const} returns a pointer in the caller's - address space to forest's requests map. */ - -FD_FN_PURE static inline fd_forest_requests_t * -fd_forest_requests( fd_forest_t * forest ) { - return fd_wksp_laddr_fast( fd_forest_wksp( forest ), forest->requests_gaddr ); -} - -FD_FN_PURE static inline fd_forest_requests_t const * -fd_forest_requests_const( fd_forest_t const * forest ) { - return fd_wksp_laddr_fast( fd_forest_wksp( forest ), forest->requests_gaddr ); -} - -/* fd_forest_{reqslist, reqslist_const} returns a pointer in the caller's - address space to forest's reqslist. */ - -FD_FN_PURE static inline fd_forest_reqslist_t * -fd_forest_reqslist( fd_forest_t * forest ) { - return fd_wksp_laddr_fast( fd_forest_wksp( forest ), forest->reqslist_gaddr ); -} - -FD_FN_PURE static inline fd_forest_reqslist_t const * -fd_forest_reqslist_const( fd_forest_t const * forest ) { - return fd_wksp_laddr_fast( fd_forest_wksp( forest ), forest->reqslist_gaddr ); -} - -/* fd_forest_{orphreqs, orphanreqs_const} returns a pointer in the caller's - address space to forest's orphanreqs. */ - -FD_FN_PURE static inline fd_forest_requests_t * -fd_forest_orphreqs( fd_forest_t * forest ) { - return fd_wksp_laddr_fast( fd_forest_wksp( forest ), forest->orphreqs_gaddr ); -} - -FD_FN_PURE static inline fd_forest_requests_t const * -fd_forest_orphreqs_const( fd_forest_t const * forest ) { - return fd_wksp_laddr_fast( fd_forest_wksp( forest ), forest->orphreqs_gaddr ); -} - -/* fd_forest_{orphlist, orphanlist_const} returns a pointer in the caller's - address space to forest's orphanlist. */ - -FD_FN_PURE static inline fd_forest_reqslist_t * -fd_forest_orphlist( fd_forest_t * forest ) { - return fd_wksp_laddr_fast( fd_forest_wksp( forest ), forest->orphlist_gaddr ); -} - -FD_FN_PURE static inline fd_forest_reqslist_t const * -fd_forest_orphlist_const( fd_forest_t const * forest ) { - return fd_wksp_laddr_fast( fd_forest_wksp( forest ), forest->orphlist_gaddr ); -} - -/* fd_forest_{reqspool, reqspool_const} returns a pointer in the caller's - address space to forest's reqspool pool. */ - -FD_FN_PURE static inline fd_forest_ref_t * -fd_forest_reqspool( fd_forest_t * forest ) { - return fd_wksp_laddr_fast( fd_forest_wksp( forest ), forest->reqspool_gaddr ); -} - -FD_FN_PURE static inline fd_forest_ref_t const * -fd_forest_reqspool_const( fd_forest_t const * forest ) { - return fd_wksp_laddr_fast( fd_forest_wksp( forest ), forest->reqspool_gaddr ); -} - -/* fd_forest_root_slot returns forest's root slot. Assumes - forest is a current local join. */ - -FD_FN_PURE static inline ulong -fd_forest_root_slot( fd_forest_t const * forest ) { - if( FD_UNLIKELY( forest->root == fd_forest_pool_idx_null( fd_forest_pool_const( forest ) ) )) return ULONG_MAX; /* uninitialized */ - return fd_forest_pool_ele_const( fd_forest_pool_const( forest ), forest->root )->slot; -} - -fd_forest_blk_t * -fd_forest_query( fd_forest_t * forest, ulong slot ); - -/* Operations */ - -/* fd_forest_blk_insert inserts a new block into the forest. Assumes - slot >= forest->root. blk_insert can also be called to create a - sentinel block, i.e. a placeholder block that we know exists but - don't know the parent slot of. The caller should pass in parent_slot - == ULONG_MAX. In this case, the block inserted will remain an - orphan/subtree at least until the next blk_insert is called with a - different parent_slot, after which point blk_insert will not update - the parent_slot again (shred inserts may still update it, see - fd_forest_data_shred_insert). For non-sentinel blocks, blk insert is - idempotent, and can be called multiple times with the same slot. - - If the forest pool is full at the time of insertion, a block will be - chosen for eviction (see fd_forest.c:evict for more details). If the - caller passes in a non-NULL evicted pointer, the evicted slot will be - stored to the pointer. - - Returns the inserted (or existing) forest ele. NULL if the forest - pool is full and no block could be evicted. */ - -fd_forest_blk_t * -fd_forest_blk_insert( fd_forest_t * forest, ulong slot, ulong parent_slot, ulong * evicted ); - -#define SHRED_SRC_TURBINE 0 -#define SHRED_SRC_REPAIR 1 -#define SHRED_SRC_RECOVERED 2 -#define SHRED_SRC_LEADER 3 - -/* fd_forest_shred_insert inserts a new shred into the forest. Assumes - slot is already in forest, and should typically be preceded by a - fd_forest_blk_insert. Returns the forest ele corresponding to the - shred slot if the shred is accepted, and NULL if the shred is - rejected. A shred can only be rejected if slot is able to verify - that this shred does not belong to the canonical FEC set. - - A possible side effect of data_shred_insert is that it may update the - parent slot of the block IF 1) the inserted shred has a verifiably - correct merkle root, or 2) the shred belongs in fec set 0, and no - other merkle roots has arrived for fec set 0. - - Note this is different from a sentinel block parent update. A - sentinel block will update its parent with the first parent slot it - receives, but it can be later updated with a data_shred_insert. */ - -fd_forest_blk_t * -fd_forest_data_shred_insert( fd_forest_t * forest, - ulong slot, - ulong parent_slot, - uint shred_idx, - uint fec_set_idx, - int slot_complete, - int ref_tick, - int src, - fd_hash_t * mr, - fd_hash_t * cmr, - long rx_tick ); - -fd_forest_blk_t * -fd_forest_code_shred_insert( fd_forest_t * forest, ulong slot, uint shred_idx, long rx_tick ); - -/* fd_forest_fec_insert inserts a new fully completed FEC set into the - forest. Assumes slot is already in forest, and should typically be - called directly after fd_forest_block_insert. Returns the forest ele - corresponding to the shred slot if the FEC was accepted, NULL - otherwise. Like data_shred_insert, this may update the block's - parent slot: a completed FEC set 0 whose merkle root overwrites a - conflicting recorded version re-links the block to the parent named - by the completing shred. */ - -fd_forest_blk_t * -fd_forest_fec_insert( fd_forest_t * forest, - ulong slot, - ulong parent_slot, - uint last_shred_idx, - uint fec_set_idx, - int slot_complete, - int ref_tick, - fd_hash_t * mr, - fd_hash_t * cmr, - long rx_tick ); - -/* fd_forest_fec_clear clears the FEC set at the given slot and - fec_set_idx. - Can fec_clear break requests frontier invariants? No. - - 2) If slot n is in scope of the forest root, then the shred - delivered to repair will trigger a data_shred_insert call - that does nothing, as repair already has record of that - shred. Eventually the fec_completes or fec_clear msg will be - delivered to repair. fec_insert will do nothing. fec_clear - will remove the idxs for the shreds from the bitset, and - update the buffered_idx. This doesn't matter though! because - we already have moved past slot n on the requests frontier. - No need to request those shreds again. - - Except 2) breaks a bit with in specific leader slot cases. See - fd_forest_fec_clear for more details. */ -void -fd_forest_fec_clear( fd_forest_t * forest, ulong slot, uint fec_set_idx, uint max_shred_idx ); - -/* fd_forest_fec_chain_verify verifies the chain of merkle roots for a - given block. Should only be called on a block that has all the shreds - received. Returns a pointer to the first slot that does not confirm, - or NULL if the chain is valid. */ -fd_forest_blk_t * -fd_forest_fec_chain_verify( fd_forest_t * forest, fd_forest_blk_t * ele, fd_hash_t const * mr ); - -void -fd_forest_confirm( fd_forest_t * forest, fd_forest_blk_t * ele, fd_hash_t const * bid ); - -/* fd_forest_merkle_last_incorrect_idx returns the highest incorrect FEC - index for a given block. */ -static inline uint -fd_forest_merkle_last_incorrect_idx( fd_forest_blk_t * ele ) { - ulong first_verified_fec = ele->lowest_verified_fec; - /* UNLIKELY because this is being called because we've detected an incorrect FEC */ - if( FD_UNLIKELY( first_verified_fec == 0 ) ) return UINT_MAX; - - uint bad_fec_idx = first_verified_fec == UINT_MAX ? ele->complete_idx / 32UL /* last FEC is wrong */ - : (uint)first_verified_fec - 1; - return bad_fec_idx * 32UL; -} - -/* fd_forest_publish publishes slot as the new forest root, setting - the subtree beginning from slot as the new forest tree (ie. slot - and all its descendants). Prunes all eles not in slot's forest. - Assumes slot is present in forest. Returns the new root. */ - -fd_forest_blk_t const * -fd_forest_publish( fd_forest_t * forest, ulong slot ); - -/* fd_forest_highest_repaired_slot returns the highest child of a fully, - contiguously repaired slot. */ -ulong -fd_forest_highest_repaired_slot( fd_forest_t const * forest ); - -/* fd_forest_iter_* takes either the standard iterator or the orphan - iterator and returns the next shred to request. The iterator must - one of the two iterators that is owned by the forest. - - The iterator will be in an iter_done state if there are no current - shreds to request. - - The forward forest iterator will visit each shred at most once over - the lifetime of the forest, without revisiting past shreds, so it is - up to the caller to track which shreds will need re-requesting. The - exception to the rule is slots where the slot_complete shred is still - not known - the highest window idx will be requested for that slot, - and the slot will be added to the tail of the requests deque so that - later we may revisit it. As a result, the children of that slot may - also be revisited multiple times. - - Note this case is pretty rare. - - An iterator signifies to the repair tile to request the - highest_window_index when the ele_idx is not null and shred_idx is - UINT_MAX. - - Otherwise, the iterator signifies to the repair tile to request a - regular shred window_idx. - - Invariants for requests map and requests deque: - - There can only be one occurrence of the slot in the requests deque at - any time. Any slot in the requests deque must exist in the requests - map, and vice versa. Any slot in the requests map must also exist in - the forest. During publish the requests map must also be pruned. - - If we are mid-request of a slot that gets pruned, forest will take - responsibility to update the iterator to a valid slot. - - TODO: should this really be an iterator?? or just a _next function? */ - -fd_forest_iter_t * -fd_forest_iter_next( fd_forest_iter_t * iter, fd_forest_t * forest ); - -int -fd_forest_iter_done( fd_forest_iter_t * iter, fd_forest_t * forest ); - -/* Misc */ - -/* fd_forest_verify checks the forest is not obviously corrupt. - Returns 0 if verify succeeds, -1 otherwise. */ - -int -fd_forest_verify( fd_forest_t const * forest ); - -/* fd_forest_print pretty-prints a formatted forest tree. Printing begins - from `ele` (it will appear as the root in the print output). - - The most straightforward and commonly used printing pattern is: - `fd_forest_print( forest, fd_forest_root( forest ) )` - - This would print forest beginning from the root. - - Alternatively, caller can print a more localized view, for example - starting from the grandparent of the most recently executed slot: - - ``` - fd_forest_blk_t const * ele = fd_forest_query( slot ); - fd_forest_print( forest, fd_forest_parent( fd_forest_parent( ele ) ) ) - ``` - - Callers should add null-checks as appropriate in actual usage. */ - -void -fd_forest_print( fd_forest_t const * forest ); - -FD_PROTOTYPES_END - -#endif /* HEADER_fd_src_discof_forest_fd_forest_h */ diff --git a/src/discof/forest/test_forest.c b/src/discof/forest/test_forest.c deleted file mode 100644 index 083048a2f06..00000000000 --- a/src/discof/forest/test_forest.c +++ /dev/null @@ -1,2378 +0,0 @@ -#include "fd_forest.h" - -#include - -/* - slot 0 - | - slot 1 - / \ - slot 2 | - | slot 3 - slot 4 | - slot 5 - | - slot 6 -*/ - -fd_forest_blk_t * -fd_forest_blk_data_shred_insert( fd_forest_t * forest, ulong slot, ulong parent_slot, uint shred_idx, uint fec_set_idx, int data_complete FD_PARAM_UNUSED, int slot_complete ) { - ulong evicted = ULONG_MAX; - fd_forest_blk_insert( forest, slot, parent_slot, &evicted ); - fd_hash_t mr = (fd_hash_t){ .key = { 1 } }; - fd_hash_t cmr = (fd_hash_t){ .key = { 1 } }; - return fd_forest_data_shred_insert( forest, slot, parent_slot, shred_idx, fec_set_idx, slot_complete, 0, SHRED_SRC_REPAIR, &mr, &cmr, fd_tickcount() ); -} - -fd_forest_blk_t * -fd_forest_blk_fec_insert( fd_forest_t * forest, ulong slot, ulong parent_slot, uint last_shred_idx, uint fec_set_idx, int slot_complete ) { - ulong evicted = ULONG_MAX; - fd_forest_blk_insert( forest, slot, parent_slot, &evicted ); - fd_hash_t mr = (fd_hash_t){ .key = { 1 } }; - fd_hash_t cmr = (fd_hash_t){ .key = { 1 } }; - return fd_forest_fec_insert( forest, slot, parent_slot, last_shred_idx, fec_set_idx, slot_complete, 0, &mr, &cmr, fd_tickcount() ); -} - -#define slot_idx( forest, slot ) fd_forest_pool_idx( fd_forest_pool( forest ), fd_forest_query( forest, slot ) ) -#define idx_slot( forest, idx ) fd_forest_pool_ele_const( fd_forest_pool_const( forest ), idx )->slot - -fd_forest_t * -setup_preorder( fd_forest_t * forest ) { - fd_forest_init( forest, 0 ); - fd_forest_blk_data_shred_insert( forest, 1, 0, 0, 0, 0, 0 ); - fd_forest_blk_data_shred_insert( forest, 2, 1, 0, 0, 0, 0 ); - fd_forest_blk_data_shred_insert( forest, 4, 2, 0, 0, 0, 0 ); - fd_forest_blk_data_shred_insert( forest, 3, 1, 0, 0, 0, 0 ); - fd_forest_blk_data_shred_insert( forest, 5, 3, 0, 0, 0, 0 ); - fd_forest_blk_data_shred_insert( forest, 6, 5, 0, 0, 0, 0 ); - FD_TEST( !fd_forest_verify( forest ) ); - fd_forest_print( forest ); - return forest; -} - -/* - slot 0 - | - slot 1 - / \ - slot 2 | - | slot 3 - slot 4 | - slot 5 - | - slot 6 -*/ - -void -test_publish( fd_wksp_t * wksp ) { - ulong publish_test_cases[2] = { - - /* - slot 2 - | - slot 4 - */ - - 2, - - /* - slot 3 - | - slot 5 - | - slot 6 - */ - - 3 }; - - for( ulong i = 0; i < sizeof(publish_test_cases) / sizeof(ulong); i++ ) { - ulong ele_max = 8UL; - void * mem = fd_wksp_alloc_laddr( wksp, fd_forest_align(), fd_forest_footprint( ele_max, FD_SHRED_BLK_MAX ), 1UL ); - FD_TEST( mem ); - fd_forest_t * forest = fd_forest_join( fd_forest_new( mem, ele_max, FD_SHRED_BLK_MAX, 42UL /* seed */ ) ); - - FD_TEST( forest ); - fd_forest_publish( setup_preorder( forest ), publish_test_cases[i] ); - FD_TEST( !fd_forest_verify( forest ) ); - fd_forest_print( forest ); - - fd_wksp_free_laddr( fd_forest_delete( fd_forest_leave( forest ) ) ); - } -} - -void -test_publish_incremental( fd_wksp_t * wksp ){ - /* as the name suggests. tests the complications introduced by loading - two incremental snapshots */ - - ulong ele_max = 32UL; - void * mem = fd_wksp_alloc_laddr( wksp, fd_forest_align(), fd_forest_footprint( ele_max, FD_SHRED_BLK_MAX ), 1UL ); - FD_TEST( mem ); - fd_forest_t * forest = fd_forest_join( fd_forest_new( mem, ele_max, FD_SHRED_BLK_MAX, 42UL /* seed */ ) ); - fd_forest_ancestry_t * ancestry = fd_forest_ancestry( forest ); - fd_forest_frontier_t * frontier = fd_forest_frontier( forest ); - //fd_forest_orphaned_t * orphaned = fd_forest_orphaned( forest ); - fd_forest_subtrees_t * subtrees = fd_forest_subtrees( forest ); - fd_forest_consumed_t * consumed = fd_forest_consumed( forest ); - fd_forest_ref_t * conspool = fd_forest_conspool( forest ); - fd_forest_blk_t * pool = fd_forest_pool( forest ); - - /* 1. Try publishing to a slot that doesn't exist - - 0 10? -> 11 - - */ - - fd_forest_init( forest, 0 ); - fd_forest_blk_data_shred_insert( forest, 11, 10, 0, 0, 1, 1 ); - - ulong new_root = 1; - ulong _11 = 11; - fd_forest_publish( forest, new_root ); - ulong new_root_idx = slot_idx( forest, new_root ); - FD_TEST( fd_forest_root_slot( forest ) == new_root ); - FD_TEST( fd_forest_consumed_ele_query( consumed, &new_root_idx, NULL, conspool ) ); - FD_TEST( fd_forest_frontier_ele_query( frontier, &new_root, NULL, pool ) || fd_forest_ancestry_ele_query( ancestry, &new_root, NULL, pool ) ); - FD_TEST( fd_forest_subtrees_ele_query( subtrees, &_11, NULL, pool ) ); - FD_TEST( !fd_forest_query( forest, 0 ) ); - FD_TEST( !fd_forest_query( forest, 10 ) ); - - - /* 2. Try publishing to a slot on the frontier - v - 1 -> 2 -> 3 10? -> 11 - - */ - - fd_forest_blk_fec_insert( forest, 2, 1, 0, 0, 1 ); - fd_forest_blk_fec_insert( forest, 3, 2, 0, 0, 1 ); - - ulong front_slot = 3; - ulong front_slot_idx = slot_idx( forest, front_slot ); - fd_forest_print( forest ); - FD_TEST( fd_forest_consumed_ele_query( consumed, &front_slot_idx, NULL, conspool ) ); - FD_TEST( fd_forest_frontier_ele_query( frontier, &front_slot, NULL, pool ) ); - fd_forest_publish( forest, front_slot ); - FD_TEST( fd_forest_root_slot( forest ) == front_slot ); - FD_TEST( fd_forest_consumed_ele_query( consumed, &front_slot_idx, NULL, conspool ) ); - FD_TEST( !fd_forest_query( forest, 1 ) ); - FD_TEST( !fd_forest_query( forest, 2 ) ); - FD_TEST( !fd_forest_query( forest, 10 ) ); - FD_TEST( fd_forest_query( forest, 11 ) ); - - /* 3. Try publishing to a slot in ancestry but in front of the frontier - - frontier new_root - 3 -> 4 -> 5 -> 6 -> 7 10 -> 11 - - */ - - fd_forest_blk_data_shred_insert( forest, 4, 3, 0, 0, 0, 0 ); - fd_forest_blk_data_shred_insert( forest, 5, 4, 0, 0, 0, 0 ); - fd_forest_blk_data_shred_insert( forest, 6, 5, 0, 0, 0, 0 ); - fd_forest_blk_data_shred_insert( forest, 7, 6, 0, 0, 0, 0 ); - FD_TEST( !fd_forest_verify( forest ) ); - - front_slot = 4; - front_slot_idx = slot_idx( forest, front_slot ); - new_root = 6; - FD_TEST( fd_forest_consumed_ele_query( fd_forest_consumed( forest ), &front_slot_idx, NULL, fd_forest_conspool( forest ) ) ); - - fd_forest_publish( forest, new_root ); - FD_TEST( fd_forest_root_slot( forest ) == new_root ); - front_slot = 7; - front_slot_idx = slot_idx( forest, front_slot ); - FD_TEST( fd_forest_consumed_ele_query( fd_forest_consumed( forest ), &front_slot_idx, NULL, fd_forest_conspool( forest ) ) ); - FD_TEST( !fd_forest_query( forest, 3 ) ); - FD_TEST( !fd_forest_query( forest, 4 ) ); - FD_TEST( !fd_forest_query( forest, 5 ) ); - - /* 4. Try publishing to an orphan slot - - 6 -> 7 10 -> 11 - 8 -> 9 (should get pruned) - */ - - fd_forest_blk_data_shred_insert( forest, 9, 8, 0, 0, 0, 0 ); - - new_root = 10; - front_slot = 11; - front_slot_idx = slot_idx( forest, front_slot ); - fd_forest_publish( forest, new_root ); - FD_TEST( !fd_forest_verify( forest ) ); - FD_TEST( fd_forest_root_slot( forest ) == new_root ); - FD_TEST( fd_forest_consumed_ele_query( fd_forest_consumed( forest ), &front_slot_idx, NULL, fd_forest_conspool( forest ) ) ); - FD_TEST( !fd_forest_query( forest, 6 ) ); - FD_TEST( !fd_forest_query( forest, 7 ) ); - FD_TEST( !fd_forest_query( forest, 8 ) ); - FD_TEST( !fd_forest_query( forest, 9 ) ); - FD_TEST( fd_forest_ancestry_ele_query( ancestry, &new_root, NULL, pool ) ); - FD_TEST( fd_forest_frontier_ele_query( frontier, &front_slot, NULL, pool ) ); - - /* 5. Try publishing to an orphan slot that is not a "head" of orphans - (publish) - 10 -> 11 14 -> 15 -> 16 - - */ - - fd_forest_blk_data_shred_insert( forest, 14, 13, 0, 0, 0, 0 ); - fd_forest_blk_data_shred_insert( forest, 15, 14, 0, 0, 0, 0 ); - fd_forest_blk_data_shred_insert( forest, 16, 15, 0, 0, 0, 0 ); - - new_root = 15; - front_slot = 16; - front_slot_idx = slot_idx( forest, front_slot ); - fd_forest_publish( forest, new_root ); - FD_TEST( !fd_forest_verify( forest ) ); - FD_TEST( fd_forest_root_slot( forest ) == new_root ); - FD_TEST( fd_forest_consumed_ele_query( fd_forest_consumed( forest ), &front_slot_idx, NULL, fd_forest_conspool( forest ) ) ); - FD_TEST( !fd_forest_query( forest, 10 ) ); - FD_TEST( !fd_forest_query( forest, 11 ) ); - FD_TEST( !fd_forest_query( forest, 14 ) ); -} -#define SORT_NAME sort -#define SORT_KEY_T ulong -#include "../../util/tmpl/fd_sort.c" - -ulong * consumed_arr( fd_wksp_t * wksp, fd_forest_t * forest ) { - fd_forest_consumed_t const * consumed = fd_forest_consumed_const( forest ); - fd_forest_ref_t const * conspool = fd_forest_conspool_const( forest ); - fd_forest_blk_t const * pool = fd_forest_pool_const( forest ); - ulong cnt = fd_forest_pool_used( pool ); - - FD_TEST( !fd_forest_frontier_verify( fd_forest_frontier_const( forest ), fd_forest_pool_max( pool ), pool ) ); - ulong * arr = fd_wksp_alloc_laddr( wksp, 8, cnt*sizeof(ulong), 42UL ); - - ulong i = 0; - for( fd_forest_consumed_iter_t iter = fd_forest_consumed_iter_init( consumed, conspool ); - !fd_forest_consumed_iter_done( iter, consumed, conspool ); - iter = fd_forest_consumed_iter_next( iter, consumed, conspool ) ) { - fd_forest_ref_t const * ele = fd_forest_consumed_iter_ele_const( iter, consumed, conspool ); - arr[i++] = fd_forest_pool_ele_const( pool, ele->idx )->slot; - FD_TEST( i < cnt ); - } - for( ulong j = i; j < cnt; j++ ) arr[j] = ULONG_MAX; - return sort_inplace( arr, cnt ); -} - -void test_out_of_order( fd_wksp_t * wksp ) { - ulong ele_max = 8UL; - void * mem = fd_wksp_alloc_laddr( wksp, fd_forest_align(), fd_forest_footprint( ele_max, FD_SHRED_BLK_MAX ), 1UL ); - FD_TEST( mem ); - fd_forest_t * forest = fd_forest_join( fd_forest_new( mem, ele_max, FD_SHRED_BLK_MAX, 42UL /* seed */ ) ); - - fd_forest_init( forest, 0 ); - fd_forest_blk_data_shred_insert( forest, 6, 5, 0, 0, 0, 0 ); - fd_forest_blk_data_shred_insert( forest, 5, 3, 0, 0, 0, 0 ); - - fd_forest_print( forest ); - - fd_forest_blk_data_shred_insert( forest, 2, 1, 0, 0, 0, 0 ); - fd_forest_blk_data_shred_insert( forest, 1, 0, 0, 0, 0, 0 ); - fd_forest_blk_data_shred_insert( forest, 3, 1, 0, 0, 0, 0 ); - - fd_forest_print( forest ); - ulong * arr = consumed_arr( wksp, forest ); - FD_TEST( arr[0] == 1 ); - FD_TEST( arr[1] == ULONG_MAX ); - FD_TEST( !fd_forest_verify( forest ) ); - fd_wksp_free_laddr( arr ); - - fd_forest_blk_data_shred_insert( forest, 1, 0, 1, 0, 1, 1 ); - fd_forest_blk_fec_insert ( forest, 1, 0, 1, 0, 1 ); - fd_forest_print( forest ); - arr = consumed_arr( wksp, forest ); - FD_TEST( arr[0] == 2 ); - FD_TEST( arr[1] == 3 ); - FD_TEST( arr[2] == ULONG_MAX ); - FD_TEST( !fd_forest_verify( forest ) ); - fd_wksp_free_laddr( arr ); - - fd_forest_blk_data_shred_insert( forest, 3, 1, 1, 0, 1, 1 ); - fd_forest_blk_fec_insert ( forest, 3, 1, 1, 0, 1 ); - fd_forest_print( forest ); - arr = consumed_arr( wksp, forest ); - FD_TEST( arr[0] == 2 ); - FD_TEST( arr[1] == 5 ); - FD_TEST( arr[2] == ULONG_MAX ); - FD_TEST( !fd_forest_verify( forest ) ); - fd_wksp_free_laddr( arr ); - - fd_forest_blk_data_shred_insert( forest, 5, 3, 1, 0, 1, 1 ); - fd_forest_blk_fec_insert ( forest, 5, 3, 1, 0, 1 ); - fd_forest_print( forest ); - arr = consumed_arr( wksp, forest ); - FD_TEST( arr[0] == 2 ); - FD_TEST( arr[1] == 6 ); - FD_TEST( arr[2] == ULONG_MAX ); - FD_TEST( !fd_forest_verify( forest ) ); - fd_wksp_free_laddr( arr ); - - fd_forest_blk_data_shred_insert( forest, 4, 2, 0, 0, 0, 0 ); - fd_forest_blk_data_shred_insert( forest, 2, 1, 1, 0, 1, 1 ); - fd_forest_blk_fec_insert ( forest, 2, 1, 1, 0, 1 ); - fd_forest_print( forest ); - arr = consumed_arr( wksp, forest ); - FD_TEST( arr[0] == 4 ); - FD_TEST( arr[1] == 6 ); - FD_TEST( arr[2] == ULONG_MAX ); - FD_TEST( !fd_forest_verify( forest ) ); - fd_wksp_free_laddr( arr ); - - fd_forest_blk_data_shred_insert( forest, 6, 5, 1, 0, 1, 1 ); - fd_forest_blk_fec_insert ( forest, 6, 5, 1, 0, 1 ); - fd_forest_print( forest ); - arr = consumed_arr( wksp, forest ); - FD_TEST( arr[0] == 4 ); - FD_TEST( arr[1] == 6 ); - FD_TEST( arr[2] == ULONG_MAX ); - FD_TEST( !fd_forest_verify( forest ) ); - fd_wksp_free_laddr( arr ); - - fd_forest_blk_data_shred_insert( forest, 4, 2, 1, 0, 0, 0 ); /* shred complete arrives before */ - //fd_forest_blk_data_shred_insert( forest, 4, 2, 2, 0, 1, 1 ); - fd_forest_blk_fec_insert ( forest, 4, 2, 2, 0, 1 ); - fd_forest_print( forest ); - arr = consumed_arr( wksp, forest ); - FD_TEST( arr[0] == 4 ); - FD_TEST( arr[1] == 6 ); - FD_TEST( arr[2] == ULONG_MAX ); - FD_TEST( !fd_forest_verify( forest ) ); - fd_wksp_free_laddr( arr ); - - - fd_forest_print( forest ); - // for( ulong i = 0; i < 7; i++ ) { - // FD_LOG_NOTICE(( "i %lu %lu", i, arr[i] )); - // } - // preorder( forest, fd_forest_pool_ele( fd_forest_pool( forest ), forest->root ) ); - - fd_wksp_free_laddr( fd_forest_delete( fd_forest_leave( forest ) ) ); -} - -void -test_forks( fd_wksp_t * wksp ){ - - ulong ele_max = 32UL; - void * mem = fd_wksp_alloc_laddr( wksp, fd_forest_align(), fd_forest_footprint( ele_max, FD_SHRED_BLK_MAX ), 1UL ); - FD_TEST( mem ); - fd_forest_t * forest = fd_forest_join( fd_forest_new( mem, ele_max, FD_SHRED_BLK_MAX, 42UL /* seed */ ) ); - - // these slots all have 2 shreds, 0,1 - fd_forest_init( forest, 0 ); - fd_forest_blk_data_shred_insert( forest, 1, 0, 1, 0, 0, 1 ); - fd_forest_blk_data_shred_insert( forest, 2, 1, 1, 0, 0, 1 ); - fd_forest_blk_data_shred_insert( forest, 3, 2, 1, 0, 0, 1 ); - fd_forest_blk_data_shred_insert( forest, 4, 3, 1, 0, 0, 1 ); - fd_forest_blk_data_shred_insert( forest, 10, 9, 1, 0, 0, 1 ); /* orphan */ - - /* Frontier should be slot 1. */ - ulong key = slot_idx( forest, 1 ); - FD_TEST( fd_forest_consumed_ele_query( fd_forest_consumed( forest ), &key, NULL, fd_forest_conspool( forest ) ) ); - - int cnt = 0; - for( fd_forest_consumed_iter_t iter = fd_forest_consumed_iter_init( fd_forest_consumed( forest ), fd_forest_conspool( forest ) ); - !fd_forest_consumed_iter_done( iter, fd_forest_consumed( forest ), fd_forest_conspool( forest ) ); - iter = fd_forest_consumed_iter_next( iter, fd_forest_consumed( forest ), fd_forest_conspool( forest ) ) ) { - fd_forest_ref_t * ele = fd_forest_consumed_iter_ele( iter, fd_forest_consumed( forest ), fd_forest_conspool( forest ) ); - cnt++; - (void) ele; - } - - FD_TEST( cnt == 1 ); - // advance frontier to slot 3 - fd_forest_blk_fec_insert ( forest, 1, 0, 1, 0, 1 ); - fd_forest_blk_fec_insert ( forest, 2, 1, 1, 0, 1 ); - - key = slot_idx( forest, 3 ); - FD_TEST( fd_forest_consumed_ele_query( fd_forest_consumed( forest ), &key, NULL, fd_forest_conspool( forest ) ) ); - - // add a new fork off slot 1 - fd_forest_blk_data_shred_insert( forest, 5, 1, 1, 0, 1, 1 ); - fd_forest_blk_fec_insert ( forest, 5, 1, 1, 0, 1 ); - - fd_forest_print( forest ); - - key = slot_idx( forest, 5 ); - FD_TEST( fd_forest_consumed_ele_query( fd_forest_consumed( forest ), &key, NULL, fd_forest_conspool( forest ) ) ); - - cnt = 0; - for( fd_forest_consumed_iter_t iter = fd_forest_consumed_iter_init( fd_forest_consumed( forest ), fd_forest_conspool( forest ) ); - !fd_forest_consumed_iter_done( iter, fd_forest_consumed( forest ), fd_forest_conspool( forest ) ); - iter = fd_forest_consumed_iter_next( iter, fd_forest_consumed( forest ), fd_forest_conspool( forest ) ) ) { - fd_forest_ref_t * ele = fd_forest_consumed_iter_ele( iter, fd_forest_consumed( forest ), fd_forest_conspool( forest ) ); - cnt++; - (void) ele; - } - FD_TEST( cnt == 2 ); - - // add a fork off of the orphan - fd_forest_blk_data_shred_insert( forest, 11, 10, 1, 0, 1, 1 ); - fd_forest_blk_data_shred_insert( forest, 12, 8, 1, 0, 1, 1 ); - - cnt = 0; - for( fd_forest_consumed_iter_t iter = fd_forest_consumed_iter_init( fd_forest_consumed( forest ), fd_forest_conspool( forest ) ); - !fd_forest_consumed_iter_done( iter, fd_forest_consumed( forest ), fd_forest_conspool( forest ) ); - iter = fd_forest_consumed_iter_next( iter, fd_forest_consumed( forest ), fd_forest_conspool( forest ) ) ) { - fd_forest_ref_t * ele = fd_forest_consumed_iter_ele( iter, fd_forest_consumed( forest ), fd_forest_conspool( forest ) ); - cnt++; - (void) ele; - } - FD_TEST( cnt == 2 ); - - fd_forest_print( forest ); - -} - -void -test_print_tree( fd_wksp_t *wksp ){ - ulong ele_max = 512UL; - void * mem = fd_wksp_alloc_laddr( wksp, fd_forest_align(), fd_forest_footprint( ele_max, FD_SHRED_BLK_MAX ), 1UL ); - FD_TEST( mem ); - fd_forest_t * forest = fd_forest_join( fd_forest_new( mem, ele_max, FD_SHRED_BLK_MAX, 42UL /* seed */ ) ); - - fd_forest_init( forest, 1568376 ); - fd_forest_blk_data_shred_insert( forest, 1568377, 1568376, 0, 0, 1, 1 ); - fd_forest_blk_data_shred_insert( forest, 1568378, 1568377, 0, 0, 1, 1 ); - fd_forest_blk_data_shred_insert( forest, 1568379, 1568378, 0, 0, 1, 1 ); - fd_forest_blk_data_shred_insert( forest, 1568380, 1568379, 0, 0, 1, 1 ); - fd_forest_blk_data_shred_insert( forest, 1568381, 1568379, 0, 0, 1, 1 ); - fd_forest_blk_data_shred_insert( forest, 1568382, 1568381, 0, 0, 1, 1 ); - fd_forest_blk_data_shred_insert( forest, 1568383, 1568379, 0, 0, 1, 1 ); - fd_forest_blk_data_shred_insert( forest, 1568384, 1568379, 0, 0, 1, 1 ); - fd_forest_blk_data_shred_insert( forest, 1568385, 1568380, 0, 0, 1, 1 ); - fd_forest_blk_data_shred_insert( forest, 1568386, 15683806, 0, 0, 1, 1 ); - - for( ulong i = 1568387; i < 1568400; i++ ){ - FD_TEST( fd_forest_blk_data_shred_insert( forest, i, i-1, 0, 0, 1, 1) ); - } - - FD_TEST( !fd_forest_verify( forest ) ); - fd_forest_print( forest ); - - /*[330090532, 330090539] ── [330090544, 330090583] ── [330090588, 330090851] ── [330090856, 330090859] ── [330090864, 330091003] ── [330091008] - └── [330091004, 330091007] ── [330091010, 330091048] - └── [330090852, 330090855]*/ - -} - -void -test_large_print_tree( fd_wksp_t * wksp ){ - /*[330090532, 330090539] ── [330090544, 330090583] ── [330090588, 330090851] ── [330090856, 330090859] ── [330090864, 330091003] ── [330091008] - └── [330091004, 330091007] ── [330091010, 330091048] - └── [330090852, 330090855]*/ - ulong ele_max = 512UL; - void * mem = fd_wksp_alloc_laddr( wksp, fd_forest_align(), fd_forest_footprint( ele_max, FD_SHRED_BLK_MAX ), 1UL ); - FD_TEST( mem ); - fd_forest_t * forest = fd_forest_join( fd_forest_new( mem, ele_max, FD_SHRED_BLK_MAX, 42UL /* seed */ ) ); - - fd_forest_init( forest, 330090532 ); - - for( ulong slot = 330090533; slot <= 330090539; slot++ ){ - fd_forest_blk_data_shred_insert( forest, slot, slot-1, 0, 0, 1, 1 ); - } - - fd_forest_blk_data_shred_insert( forest, 330090544, 330090539, 0, 0, 1, 1 ); - - for( ulong slot = 330090545; slot <= 330090583; slot++ ){ - fd_forest_blk_data_shred_insert( forest, slot, slot - 1, 0, 0, 1, 1 ); - } - - fd_forest_blk_data_shred_insert( forest, 330090588, 330090588 - 5, 0, 0, 1, 1 ); - for( ulong slot = 330090589; slot <= 330090855; slot++ ){ - fd_forest_blk_data_shred_insert( forest, slot, slot -1, 0, 0, 1, 1 ); - } - fd_forest_blk_data_shred_insert( forest, 330090856, 330090588 - 5, 0, 0, 1, 1 ); - for( ulong slot = 330090857; slot <= 330090859; slot++ ){ - fd_forest_blk_data_shred_insert( forest, slot, slot - 1, 0, 0, 1, 1 ); - } - fd_forest_blk_data_shred_insert( forest, 330090864, 330090864 - 5, 0, 0, 1, 1 ); - for( ulong slot = 330090865; slot <= 330091007; slot++ ){ - fd_forest_blk_data_shred_insert( forest, slot, slot - 1, 0, 0, 1, 1 ); - } - fd_forest_blk_data_shred_insert( forest, 330091008, 330091008 - 5, 0, 0, 1, 1 ); - - fd_forest_blk_data_shred_insert( forest, 330091010, 330091010 - 5, 0, 0, 1, 1 ); - for( ulong slot = 330091011; slot <= 330091048; slot++ ){ - fd_forest_blk_data_shred_insert( forest, slot, slot - 1, 0, 0, 1, 1 ); - } - - FD_TEST( !fd_forest_verify( forest ) ); - fd_forest_print( forest ); - -} - -struct iter_order { - ulong slot; - uint idx; -}; -typedef struct iter_order iter_order_t; - -void -test_linear_forest_iterator( fd_wksp_t * wksp ) { - /* Repar forest iterator for a linear chain (expected behavior for - start up) */ - ulong ele_max = 512UL; - void * mem = fd_wksp_alloc_laddr( wksp, fd_forest_align(), fd_forest_footprint( ele_max, FD_SHRED_BLK_MAX ), 1UL ); - FD_TEST( mem ); - fd_forest_t * forest = fd_forest_join( fd_forest_new( mem, ele_max, FD_SHRED_BLK_MAX, 42UL /* seed */ ) ); - - /* - 0 - 1(-/1) - 2(-/2) - 3(0/?) - 4(-/?) - 5(-/5) - - expected iterator order: - (slot 1, idx 0), (slot 2, idx 0), (slot 2, idx 1), (slot 3, idx UINT_MAX), (slot 4, idx UINT_MAX), - (slot 5, idx 0), (slot 5, idx 1), (slot 5, idx 2), (slot 5, idx 3), (slot 5, idx 4) */ - - fd_forest_init( forest, 0 ); - fd_forest_blk_data_shred_insert( forest, 1, 0, 1, 0, 1, 1 ); - fd_forest_blk_data_shred_insert( forest, 2, 1, 2, 0, 1, 1 ); - fd_forest_blk_data_shred_insert( forest, 3, 2, 0, 0, 0, 0 ); - fd_forest_blk_data_shred_insert( forest, 4, 3, 3, 0, 0, 0 ); - fd_forest_blk_data_shred_insert( forest, 5, 4, 5, 0, 1, 1 ); - - iter_order_t expected[10] = { - { 1, 0 }, { 2, 0 }, { 2, 1 }, { 3, UINT_MAX }, { 4, UINT_MAX }, - { 3, UINT_MAX }, { 5, 0 }, { 5, 1 }, { 5, 2 }, { 5, 3 } - }; - - fd_forest_blk_t const * pool = fd_forest_pool_const( forest ); - for( ulong i = 0; i < sizeof(expected) / sizeof(iter_order_t); i++ ) { - fd_forest_iter_t iter = *fd_forest_iter_next( &forest->iter, forest ); - fd_forest_blk_t const * ele = fd_forest_pool_ele_const( pool, iter.ele_idx ); - FD_TEST( ele->slot == expected[i].slot ); - FD_TEST( iter.shred_idx == expected[i].idx ); - } - FD_LOG_DEBUG(("success")); -} - -/* The word-at-a-time iterator yields exactly what a bit-at-a-time scan - does, over a chain of slots with random received sets. */ - -static void -test_iter_random_bitsets( fd_wksp_t * wksp ) { - ulong ele_max = 64UL; - ulong shred_max = 2048UL; - void * mem = fd_wksp_alloc_laddr( wksp, fd_forest_align(), fd_forest_footprint( ele_max, shred_max ), 1UL ); - FD_TEST( mem ); - fd_forest_t * forest = fd_forest_join( fd_forest_new( mem, ele_max, shred_max, 42UL /* seed */ ) ); - fd_rng_t _rng[1]; fd_rng_t * rng = fd_rng_join( fd_rng_new( _rng, 7U, 0UL ) ); - - /* fd_forest_blk_idxs_next_unset against the bit loop */ - for( ulong trial=0UL; trial<20000UL; trial++ ) { - ulong set[ 4 ]; - uint density = fd_rng_uint_roll( rng, 5U ); - for( ulong w=0UL; w<4UL; w++ ) { - ulong x = fd_rng_ulong( rng ); - switch( density ) { - case 0: x = 0UL; break; - case 1: x = x & fd_rng_ulong( rng ); break; - case 2: break; - case 3: x = x | fd_rng_ulong( rng ) | fd_rng_ulong( rng ); break; - default: x = ULONG_MAX; if( !fd_rng_uint_roll( rng, 4U ) ) x = fd_ulong_clear_bit( x, (int)fd_rng_uint_roll( rng, 64U ) ); break; - } - set[ w ] = x; - } - ulong lo = fd_rng_ulong_roll( rng, 256UL ); - ulong hi = lo + fd_rng_ulong_roll( rng, 256UL-lo ); - ulong expected = hi+1UL; - for( ulong i=lo; i<=hi; i++ ) if( !fd_forest_blk_idxs_test( set, i ) ) { expected = i; break; } - FD_TEST( fd_forest_blk_idxs_next_unset( set, lo, hi )==expected ); - } - - /* a chain 0 - 1 - 2 - ... - 8, random received sets */ - ulong slot_cnt = 8UL; - fd_forest_init( forest, 0 ); - for( ulong slot=1UL; slot<=slot_cnt; slot++ ) { - uint complete_idx = 64U+fd_rng_uint_roll( rng, (uint)shred_max-64U ); - uint density = fd_rng_uint_roll( rng, 100U )+1U; /* percent received */ - uint hole = fd_rng_uint_roll( rng, complete_idx ); /* at least one missing, so the slot stays on the list */ - fd_forest_blk_data_shred_insert( forest, slot, slot-1UL, complete_idx, complete_idx & ~31U, 1, 1 ); - for( uint idx=0U; idx=density ) continue; - fd_forest_blk_data_shred_insert( forest, slot, slot-1UL, idx, idx & ~31U, 0, 0 ); - } - } - - for( ulong slot=1UL; slot<=slot_cnt; slot++ ) { - fd_forest_blk_t const * ele = fd_forest_query( forest, slot ); - FD_TEST( ele && ele->complete_idx!=UINT_MAX ); - FD_TEST( ele->buffered_idx==UINT_MAX || ele->buffered_idxcomplete_idx ); /* UINT_MAX: shred 0 missing */ - fd_forest_blk_idxs_t const * idxs = fd_forest_blk_idxs( forest, ele ); - ulong yielded = 0UL; - for( uint idx=ele->buffered_idx+1U; idx<=ele->complete_idx; idx++ ) { /* UINT_MAX+1 wraps to 0 as in the iterator */ - if( fd_forest_blk_idxs_test( idxs, idx ) ) continue; - fd_forest_iter_t iter = *fd_forest_iter_next( &forest->iter, forest ); - FD_TEST( !fd_forest_iter_done( &iter, forest ) ); - FD_TEST( fd_forest_pool_ele_const( fd_forest_pool_const( forest ), iter.ele_idx )->slot==slot ); - FD_TEST( iter.shred_idx==idx ); - yielded++; - } - FD_TEST( yielded ); - } - FD_TEST( fd_forest_iter_done( fd_forest_iter_next( &forest->iter, forest ), forest ) ); - - fd_wksp_free_laddr( fd_forest_delete( fd_forest_leave( forest ) ) ); -} - -void -test_branched_forest_iterator( fd_wksp_t * wksp ) { - /* Repair forest iterator for a branched chain (expected behavior for - regular turbine) */ - ulong ele_max = 512UL; - void * mem = fd_wksp_alloc_laddr( wksp, fd_forest_align(), fd_forest_footprint( ele_max, FD_SHRED_BLK_MAX ), 1UL ); - FD_TEST( mem ); - fd_forest_t * forest = fd_forest_join( fd_forest_new( mem, ele_max, FD_SHRED_BLK_MAX, 42UL /* seed */ ) ); - - /* - slot 0 - | - slot 1 - / \ - slot 2 | - | slot 3 - slot 4 | - slot 5 - - slot complete_idx received - 0 1 - 1 1 1 - 2 2 2 - 3 3 0 - 4 4 3 - 5 5 5 */ - fd_forest_init( forest, 0 ); - fd_forest_blk_data_shred_insert( forest, 1, 0, 1, 0, 1, 1 ); - fd_forest_blk_data_shred_insert( forest, 2, 1, 2, 0, 1, 1 ); - fd_forest_blk_data_shred_insert( forest, 3, 1, 0, 0, 0, 0 ); - fd_forest_blk_data_shred_insert( forest, 4, 2, 3, 0, 0, 0 ); - fd_forest_blk_data_shred_insert( forest, 5, 3, 5, 0, 1, 1 ); - - /* Expected iterator order: */ - iter_order_t inital_expected[11] = { - { 1, 0 }, { 2, 0 }, { 2, 1 }, { 3, UINT_MAX }, { 4, UINT_MAX }, - { 5, 0 }, { 5, 1 }, { 5, 2 }, { 5, 3 }, { 5, 4 }, {3, UINT_MAX } - }; - for( ulong i = 0; i < sizeof(inital_expected) / sizeof(iter_order_t); i++ ) { - fd_forest_iter_t iter = *fd_forest_iter_next( &forest->iter, forest ); - fd_forest_blk_t const * ele = fd_forest_pool_ele_const( fd_forest_pool_const( forest ), iter.ele_idx ); - FD_LOG_DEBUG(( "iter: slot %lu, idx %u", ele->slot, iter.shred_idx )); - FD_TEST( ele->slot == inital_expected[i].slot ); - FD_TEST( iter.shred_idx == inital_expected[i].idx ); - } - - /* Now frontier advances to the point where we have two things in the - frontier */ - fd_forest_blk_data_shred_insert( forest, 1, 0, 0, 0, 0, 0 ); - fd_forest_blk_fec_insert ( forest, 1, 0, 1, 0, 1 ); - // slot one is complete, so we should now have two things in the frontier - - iter_order_t expected[9] = { - { 4, UINT_MAX }, { 5, 0 }, { 5, 1 }, { 5, 2 }, { 5, 3 }, { 5, 4 }, { 3, UINT_MAX }, - { 4, UINT_MAX }, { 5, 0 } - }; - - for( ulong i = 0; i < sizeof(expected) / sizeof(iter_order_t); i++ ) { - fd_forest_iter_t iter = *fd_forest_iter_next( &forest->iter, forest ); - fd_forest_blk_t const * ele = fd_forest_pool_ele_const( fd_forest_pool_const( forest ), iter.ele_idx ); - FD_TEST( ele->slot == expected[i].slot ); - FD_TEST( iter.shred_idx == expected[i].idx ); - } - - - iter_order_t expected2[8] = { - { 5, 1 }, { 5, 2 }, { 5, 3 }, { 5, 4 }, { 3, 1 }, - { 3, 2 }, { 4, UINT_MAX }, { 5, 0 } - }; - - /* Lets do a data shred insert in the middle that affects the frontier */ - for( ulong i = 0; i < sizeof(expected2) / sizeof(iter_order_t); i++ ) { - fd_forest_iter_t iter = *fd_forest_iter_next( &forest->iter, forest ); - fd_forest_blk_t const * ele = fd_forest_pool_ele_const( fd_forest_pool_const( forest ), iter.ele_idx ); - FD_TEST( ele->slot == expected2[i].slot ); - FD_TEST( iter.shred_idx == expected2[i].idx ); - if( i == 2 ) { - /* insert a data shred in the middle of the iteration */ - fd_forest_blk_data_shred_insert( forest, 3, 1, 3, 0, 1, 1 ); - } - } -} - -void -test_frontier( fd_wksp_t * wksp ) { - /* bug where we added ele to frontier but didn't remove from ancestry */ - ulong ele_max = 8; - void * mem = fd_wksp_alloc_laddr( wksp, fd_forest_align(), fd_forest_footprint( ele_max, FD_SHRED_BLK_MAX ), 1UL ); - FD_TEST( mem ); - fd_forest_t * forest = fd_forest_join( fd_forest_new( mem, ele_max, FD_SHRED_BLK_MAX, 42UL /* seed */ ) ); - - fd_forest_init( forest, 0 ); - fd_forest_blk_data_shred_insert( forest, 1, 0, 0, 0, 1, 1 ); - fd_forest_blk_fec_insert ( forest, 1, 0, 0, 0, 1 ); - fd_forest_blk_data_shred_insert( forest, 2, 1, 0, 0, 1, 1 ); - fd_forest_blk_fec_insert ( forest, 2, 1, 0, 0, 1 ); - fd_forest_blk_data_shred_insert( forest, 3, 2, 0, 0, 0, 0 ); /* new frontier */ - - ulong frontier_slot = 3; - FD_TEST( !fd_forest_verify( forest ) ); - FD_TEST( fd_forest_frontier_ele_query( fd_forest_frontier( forest ), &frontier_slot, NULL, fd_forest_pool( forest ) ) ); - - /* frontier chaining from slot 1 */ - fd_forest_blk_data_shred_insert( forest, 4, 1, 0, 0, 0, 0 ); /* new frontier */ - frontier_slot = 4; - ulong frontier_slot_idx = slot_idx( forest, frontier_slot ); - FD_TEST( !fd_forest_verify( forest ) ); - FD_TEST( fd_forest_consumed_ele_query( fd_forest_consumed( forest ), &frontier_slot_idx, NULL, fd_forest_conspool( forest ) ) ); - FD_TEST( !fd_forest_ancestry_ele_query( fd_forest_ancestry( forest ), &frontier_slot, NULL, fd_forest_pool( forest ) ) ); -} - -void -test_invalid_frontier_insert( fd_wksp_t * wksp ) { - - /* We had a gnarly race where suppose we were executing at the head of - turbine, caught up, and suddenly got dropped from turbine for a - bit. Let's say we were at slot 100, and we executed it fully, but - from our POV it looks like there aren't any new shreds coming in. - Soon we get added back to the turbine tree, and slot 109 comes in. - slot 109 is a child of 108, and we don't know yet that slot 108 is - a child of 100. In the old forest paradigm, 108 would have been - created by 109 getting created. Then we have another slot 101 - arrive, and 101 would chain successfully to 100, and the frontier - would advance to 101. But NOW slot 108 arrives, and chains off of - 100. But the original !query(slot)->acquire->insert that catches - that 108 would need to be added to the frontier as well would - *never get called*, because 108 ALREADY EXISTED. - */ - ulong ele_max = 8; - void * mem = fd_wksp_alloc_laddr( wksp, fd_forest_align(), fd_forest_footprint( ele_max, FD_SHRED_BLK_MAX ), 1UL ); - FD_TEST( mem ); - fd_forest_t * forest = fd_forest_join( fd_forest_new( mem, ele_max, FD_SHRED_BLK_MAX, 42UL /* seed */ ) ); - - fd_forest_init( forest, 0 ); - fd_forest_blk_data_shred_insert( forest, 100, 0, 0, 0, 1, 1 ); - fd_forest_blk_fec_insert ( forest, 100, 0, 0, 0, 1 ); - - /* turbine pause, state: [100] */ - - fd_forest_blk_data_shred_insert( forest, 109, 108, 0, 0, 0, 0 ); - - ulong _109 = 109; - FD_TEST( fd_forest_subtrees_ele_query( fd_forest_subtrees( forest ), &_109, NULL, fd_forest_pool( forest ) ) ); - - fd_forest_blk_data_shred_insert( forest, 101, 100, 0, 0, 0, 0 ); - - /* turbine resume, state: [100, 101] [109] */ - FD_TEST( fd_forest_subtrees_ele_query( fd_forest_subtrees( forest ), &_109, NULL, fd_forest_pool( forest ) ) ); - - fd_forest_blk_data_shred_insert( forest, 108, 100, 0, 0, 0, 0 ); - - FD_TEST( !fd_forest_subtrees_ele_query( fd_forest_subtrees( forest ), &_109, NULL, fd_forest_pool( forest ) ) ); - - /* turbine resume, state: [100, 101] - [108, 109]*/ - - fd_forest_print( forest ); - FD_TEST( !fd_forest_verify( forest ) ); - ulong _101idx = slot_idx( forest, 101 ); - ulong _108idx = slot_idx( forest, 108 ); - ulong _109idx = slot_idx( forest, 109 ); - FD_TEST( fd_forest_consumed_ele_query ( fd_forest_consumed( forest ), &_101idx, NULL, fd_forest_conspool( forest ) ) ); - FD_TEST( fd_forest_consumed_ele_query ( fd_forest_consumed( forest ), &_108idx, NULL, fd_forest_conspool( forest ) ) ); - FD_TEST( !fd_forest_consumed_ele_query( fd_forest_consumed( forest ), &_109idx, NULL, fd_forest_conspool( forest ) ) ); - - fd_forest_print( forest ); - -} - -void -test_fec_clear( fd_wksp_t * wksp ) { - ulong ele_max = 8; - void * mem = fd_wksp_alloc_laddr( wksp, fd_forest_align(), fd_forest_footprint( ele_max, FD_SHRED_BLK_MAX ), 1UL ); - FD_TEST( mem ); - fd_forest_t * forest = fd_forest_join( fd_forest_new( mem, ele_max, FD_SHRED_BLK_MAX, 42UL /* seed */ ) ); - - fd_forest_init( forest, 0 ); - - /* simulate block 1 getting completed with 2 FEC sets */ - - - fd_forest_blk_data_shred_insert( forest, 2, 1, 0, 0, 0, 0 ); - fd_forest_blk_fec_insert( forest, 1, 0, 31, 0, 0 ); - fd_forest_blk_fec_insert( forest, 1, 0, 63, 32, 1 ); - - ulong _2 = slot_idx( forest, 2 ); - FD_TEST( fd_forest_consumed_ele_query( fd_forest_consumed( forest ), &_2, NULL, fd_forest_conspool( forest ) ) ); - - /* something funky happened in fec_resolver, sending a clear msg for - something that was completed already */ - fd_forest_fec_clear( forest, 1, 0, 17 ); - /* but its ok because consumed should be unaffected */ - FD_TEST( fd_forest_consumed_ele_query( fd_forest_consumed( forest ), &_2, NULL, fd_forest_conspool( forest ) ) ); - - fd_forest_blk_fec_insert( forest, 3, 2, 32, 0, 1 ); /* despite being completed, we are still stuck at slot 2 */ - ulong _3 = slot_idx( forest, 3 ); - FD_TEST( fd_forest_consumed_ele_query( fd_forest_consumed( forest ), &_2, NULL, fd_forest_conspool( forest ) ) ); - - /* receiving all the shreds for slot 2 but not the fec completes will now - advance the frontier */ - fd_forest_blk_data_shred_insert( forest, 2, 1, 0, 0, 0, 0 ); - fd_forest_blk_data_shred_insert( forest, 2, 1, 1, 0, 0, 0 ); - fd_forest_blk_data_shred_insert( forest, 2, 1, 2, 0, 0, 0 ); - fd_forest_blk_data_shred_insert( forest, 2, 1, 3, 3, 0, 0 ); - fd_forest_blk_data_shred_insert( forest, 2, 1, 4, 3, 0, 0 ); - fd_forest_blk_data_shred_insert( forest, 2, 1, 5, 3, 0, 1 ); - FD_TEST( fd_forest_consumed_ele_query( fd_forest_consumed( forest ), &_3, NULL, fd_forest_conspool( forest ) ) ); - - /* receiving 1 fec for slot 2 does not complete the slot */ - fd_forest_blk_fec_insert( forest, 2, 1, 2, 0, 0 ); - FD_TEST( fd_forest_consumed_ele_query( fd_forest_consumed( forest ), &_3, NULL, fd_forest_conspool( forest ) ) ); - /* finally complete */ - fd_forest_blk_fec_insert( forest, 2, 1, 5, 3, 1 ); - FD_TEST( fd_forest_consumed_ele_query( fd_forest_consumed( forest ), &_3, NULL, fd_forest_conspool( forest ) ) ); - FD_TEST( !fd_forest_consumed_ele_query( fd_forest_consumed( forest ), &_2, NULL, fd_forest_conspool( forest ) ) ); -} - -void -test_iter_publish( fd_wksp_t * wksp ) { - ulong ele_max = 8; - void * mem = fd_wksp_alloc_laddr( wksp, fd_forest_align(), fd_forest_footprint( ele_max, FD_SHRED_BLK_MAX ), 1UL ); - FD_TEST( mem ); - fd_forest_t * forest = fd_forest_join( fd_forest_new( mem, ele_max, FD_SHRED_BLK_MAX, 42UL /* seed */ ) ); - - fd_forest_init( forest, 0 ); - fd_forest_blk_data_shred_insert( forest, 1, 0, 0, 0, 0, 0 ); - fd_forest_blk_data_shred_insert( forest, 1, 0, 10,0, 1, 1 ); - fd_forest_blk_data_shred_insert( forest, 2, 1, 0, 0, 0, 0 ); - fd_forest_blk_fec_insert ( forest, 2, 1, 0, 0, 0 ); - fd_forest_blk_data_shred_insert( forest, 3, 2, 0, 0, 0, 0 ); - fd_forest_blk_fec_insert ( forest, 3, 2, 0, 0, 0 ); - - #define iter_cnt 13 - fd_forest_iter_t iter[iter_cnt] = { - { 1, 1, 0 }, { 1, 2, 0 }, { 1, 3, 0 }, { 1, 4, 0 }, { 1, 5, 0 }, - { 1, 6, 0 }, { 1, 7, 0 }, { 1, 8, 0 }, { 1, 9, 0 }, { 2, UINT_MAX, 0 }, - { 3, UINT_MAX, 0 }, { 2, UINT_MAX, 0 }, { 3, UINT_MAX, 0 }, - }; - - int i = 0; - for(;;) { - fd_forest_iter_next( &forest->iter, forest ); - FD_TEST( forest->iter.ele_idx == iter[i].ele_idx ); - FD_TEST( forest->iter.shred_idx == iter[i].shred_idx ); - i++; - if( i == iter_cnt ) break; - } - - /* check forest deque head is at slot 3 */ - FD_TEST( fd_forest_reqslist_ele_peek_head_const( fd_forest_reqslist( forest ), fd_forest_reqspool( forest ) )->idx == 3 ); - /* print everything in the request queue */ - for( fd_forest_reqslist_iter_t iter = fd_forest_reqslist_iter_fwd_init( fd_forest_reqslist( forest ), fd_forest_reqspool( forest ) ); - !fd_forest_reqslist_iter_done( iter, fd_forest_reqslist( forest ), fd_forest_reqspool( forest ) ); - iter = fd_forest_reqslist_iter_fwd_next( iter, fd_forest_reqslist( forest ), fd_forest_reqspool( forest ) ) ) { - fd_forest_ref_t const * ele = fd_forest_reqslist_iter_ele_const( iter, fd_forest_reqslist( forest ), fd_forest_reqspool( forest ) ); - FD_LOG_NOTICE(("req: slot %lu, idx %lu", fd_forest_pool_ele_const( fd_forest_pool_const( forest ), ele->idx )->slot, ele->idx)); - } - - /* add highest windows for 2, 3, and also add forks for - 4 and 5 off of 3 - - - slot 0 - | - slot 1 - slot 6 - | - slot 2 - | - slot 3 - / \ - slot 4 slot 5 - - */ - - fd_forest_blk_data_shred_insert( forest, 2, 1, 4, 0, 1, 1 ); - fd_forest_blk_fec_insert( forest, 2, 1, 4, 0, 1 ); // slot 2 must be completed and "executed" for us to publish to it. - - /* At this point iter_next should pop 3 off the top, add 3's children - to the end (4,5). Then add 3 itself to the end. iter_next would - then normally return 2, but because 2 is now completed, itll move - on and actually return 4 as the next slot to request. - - Queue should be [4,5,3] - */ - fd_forest_blk_data_shred_insert( forest, 3, 2, 4, 0, 1, 1 ); - fd_forest_blk_data_shred_insert( forest, 4, 3, 4, 0, 1, 1 ); - fd_forest_blk_data_shred_insert( forest, 5, 3, 4, 0, 1, 1 ); - - fd_forest_blk_data_shred_insert( forest, 6, 1, 4, 0, 1, 1 ); - ulong _6 = slot_idx( forest, 6 ); - FD_TEST( fd_forest_requests_ele_query( fd_forest_requests( forest ), &_6, NULL, fd_forest_reqspool( forest ) ) ); - - fd_forest_print( forest ); - fd_forest_publish( forest, 2 ); - FD_TEST( !fd_forest_verify( forest ) ); - /* since the last request was for highest idx of 3, we expect to start - requesting for 2 */ - - fd_forest_iter_next( &forest->iter, forest ); - FD_TEST( fd_forest_query( forest, idx_slot( forest, forest->iter.ele_idx ) ) ); - - ulong expected_queue[3] = { 4, 5, 3 }; - i = 0; - for( fd_forest_reqslist_iter_t iter = fd_forest_reqslist_iter_fwd_init( fd_forest_reqslist( forest ), fd_forest_reqspool( forest ) ); - !fd_forest_reqslist_iter_done( iter, fd_forest_reqslist( forest ), fd_forest_reqspool( forest ) ); - iter = fd_forest_reqslist_iter_fwd_next( iter, fd_forest_reqslist( forest ), fd_forest_reqspool( forest ) ) ) { - fd_forest_ref_t const * ele = fd_forest_reqslist_iter_ele_const( iter, fd_forest_reqslist( forest ), fd_forest_reqspool( forest ) ); - FD_TEST( idx_slot( forest, ele->idx ) == expected_queue[i] ); - i++; - } - - /* now branch 7 off of 4 */ - fd_forest_blk_fec_insert( forest, 7, 4, 4, 0, 1 ); - fd_forest_blk_data_shred_insert( forest, 8, 7, 4, 0, 1, 1 ); - - /* Since the iterator is on 4, try publishing to 7 (slaying 4), and see what happens to the iterator */ - - fd_forest_publish( forest, 7 ); - FD_TEST( forest->iter.ele_idx == fd_forest_pool_idx_null( fd_forest_pool( forest ) ) ); - fd_forest_iter_next( &forest->iter, forest ); - FD_TEST( idx_slot( forest, forest->iter.ele_idx ) == 8 ); -} - -void -test_iter_subtree( fd_wksp_t * wksp ) { - ulong ele_max = 8; - void * mem = fd_wksp_alloc_laddr( wksp, fd_forest_align(), fd_forest_footprint( ele_max, FD_SHRED_BLK_MAX ), 1UL ); - FD_TEST( mem ); - fd_forest_t * forest = fd_forest_join( fd_forest_new( mem, ele_max, FD_SHRED_BLK_MAX, 42UL /* seed */ ) ); - - fd_forest_init( forest, 0 ); - fd_forest_blk_fec_insert ( forest, 1, 0, 0, 0, 1 ); - fd_forest_blk_fec_insert ( forest, 2, 1, 0, 0, 1 ); /* fully caught up */ - fd_forest_blk_data_shred_insert( forest, 3, 2, 0, 0, 0, 0 ); - fd_forest_blk_data_shred_insert( forest, 8, 7, 10, 0, 1, 1 ); /* subtree */ - - for( int i = 0; i < 10; i++ ) { - fd_forest_iter_next( &forest->iter, forest ); - FD_TEST( idx_slot( forest, forest->iter.ele_idx ) == 3 ); - FD_TEST( forest->iter.shred_idx == UINT_MAX ); - } - for( int i = 0; i < 5; i++ ) { - fd_forest_iter_next( &forest->orphiter, forest ); - FD_TEST( idx_slot( forest, forest->orphiter.ele_idx ) == 8 ); - FD_TEST( forest->orphiter.shred_idx == (uint)i ); - } - - fd_forest_blk_data_shred_insert( forest, 7, 6, 10, 0, 1, 1 ); - for( int i = 0; i < 20; i++ ) { - fd_forest_iter_next( &forest->orphiter, forest ); - if( i < 10 ) FD_TEST( idx_slot( forest, forest->orphiter.ele_idx ) == 7 ); - else FD_TEST( idx_slot( forest, forest->orphiter.ele_idx ) == 8 ); - FD_TEST( forest->orphiter.shred_idx == (uint)i % 10 ); - } -} - -void -test_orphan_requests( fd_wksp_t * wksp ) { - ulong ele_max = 8; - void * mem = fd_wksp_alloc_laddr( wksp, fd_forest_align(), fd_forest_footprint( ele_max, FD_SHRED_BLK_MAX ), 1UL ); - FD_TEST( mem ); - fd_forest_t * forest = fd_forest_join( fd_forest_new( mem, ele_max, FD_SHRED_BLK_MAX, 42UL /* seed */ ) ); - fd_forest_init( forest, 0 ); - - fd_forest_iter_next( &forest->iter, forest ); - FD_TEST( forest->iter.ele_idx == ULONG_MAX ); - - fd_forest_blk_data_shred_insert( forest, 8, 7, 0, 0, 0, 0 ); - fd_forest_blk_data_shred_insert( forest, 7, 6, 0, 0, 0, 0 ); - fd_forest_blk_data_shred_insert( forest, 6, 5, 0, 0, 0, 0 ); - FD_TEST( forest->orphiter.ele_idx == fd_forest_pool_idx_null( fd_forest_pool( forest ) ) ); - fd_forest_iter_next( &forest->orphiter, forest ); - FD_TEST( forest->orphiter.ele_idx == slot_idx( forest, 6 ) ); - - fd_forest_publish( forest, 7 ); - //FD_LOG_NOTICE(( "orphan iter ele_idx: %lu slot: %lu", forest->orphiter.ele_idx, idx_slot( forest, forest->orphiter.ele_idx ) )); - FD_TEST( forest->orphiter.ele_idx == fd_forest_pool_idx_null( fd_forest_pool( forest ) ) ); - fd_forest_iter_next( &forest->orphiter, forest ); - FD_TEST( forest->orphiter.ele_idx == fd_forest_pool_idx_null( fd_forest_pool( forest ) ) ); -} - -void -test_slot_clear( fd_wksp_t * wksp ) { - ulong ele_max = 8; - void * mem = fd_wksp_alloc_laddr( wksp, fd_forest_align(), fd_forest_footprint( ele_max, FD_SHRED_BLK_MAX ), 1UL ); - FD_TEST( mem ); - fd_forest_t * forest = fd_forest_join( fd_forest_new( mem, ele_max, FD_SHRED_BLK_MAX, 42UL /* seed */ ) ); - - /* - 2 - | \ - 3 3' - */ - - /* We execute (2, 0) -> (2,32) -> (3, 0') -> (3, 32'). Tower detects - that (3, 32) is the duplicate confirmed version. - We must dump slot 3 and re-repair. */ - - fd_hash_t mr_0 = (fd_hash_t){ .key = { 1 } }; - fd_hash_t mr_2_0 = (fd_hash_t){ .key = { 2 } }; - fd_hash_t mr_2_32 = (fd_hash_t){ .key = { 3 } }; - fd_hash_t mr_3_0 = (fd_hash_t){ .key = { 4 } }; - fd_hash_t mr_3_32 = (fd_hash_t){ .key = { 5 } }; - fd_hash_t mr_3_0_ = (fd_hash_t){ .key = { 6 } }; (void)mr_3_0_; - fd_hash_t mr_3_32_ = (fd_hash_t){ .key = { 7 } }; - - fd_forest_blk_t * ele; - fd_forest_init( forest, 0 ); - fd_forest_blk_insert( forest, 2, 0, NULL ); - fd_forest_blk_insert( forest, 3, 2, NULL ); - /* slot paren last fec_set slot_cmpl rt mr cmr */ - fd_forest_fec_insert( forest, 2, 0, 31, 0, 0, 0, &mr_2_0, &mr_0, fd_tickcount() ); - fd_forest_fec_insert( forest, 2, 0, 63, 32, 1, 0, &mr_2_32, &mr_2_0, fd_tickcount() ); - - fd_forest_fec_insert( forest, 3, 2, 31, 0, 0, 0, &mr_3_0, &mr_2_0, fd_tickcount() ); - fd_forest_fec_insert( forest, 3, 2, 63, 32, 1, 0, &mr_3_32, &mr_3_0, fd_tickcount() ); - - ele = fd_forest_fec_chain_verify( forest, fd_forest_query( forest, 3 ), &mr_3_32_ ); - FD_TEST( ele == fd_forest_query( forest, 3 ) ); - FD_TEST( fd_forest_merkle_last_incorrect_idx( ele ) == 32UL ); - - /* Now we would dump the last incorrect FEC */ - fd_forest_fec_clear( forest, 3, 32, 31 ); - - /* Now pretend we have received the correct FEC for 3, 32 */ - fd_forest_fec_insert( forest, 3, 2, 63, 32, 1, 0, &mr_3_32_, &mr_3_0_, fd_tickcount() ); - ele = fd_forest_fec_chain_verify( forest, fd_forest_query( forest, 3 ), &mr_3_32_ ); - FD_TEST( ele == fd_forest_query( forest, 3 ) ); - FD_TEST( fd_forest_merkle_last_incorrect_idx( ele ) == 0 ); - - FD_TEST( ele->chain_confirmed == 0 ); - FD_TEST( fd_forest_query( forest, 2 )->chain_confirmed == 0 ); - - - /* Now we dump incorrect FEC (3, 0). */ - - fd_forest_fec_clear( forest, 3, 0, 31 ); - - /* Now pretend we have received the correct FEC for 3, 0 */ - fd_hash_t garbage_mr = (fd_hash_t){ .key = { 67 } }; - FD_TEST( !fd_forest_data_shred_insert( forest, 3, 2, 0, 0, 0, 0, 0, &garbage_mr, &garbage_mr, fd_tickcount() ) ); // first get an incorrect shred, should be rejected. - fd_forest_fec_insert( forest, 3, 2, 31, 0, 0, 0, &mr_3_0_, &mr_2_32, fd_tickcount() ); - - ele = fd_forest_fec_chain_verify( forest, fd_forest_query( forest, 3 ), &mr_3_32_ ); - FD_TEST( !ele ); - FD_TEST( fd_forest_merkle_last_incorrect_idx( fd_forest_query( forest, 3 ) ) == UINT_MAX ); - FD_TEST( fd_forest_query( forest, 3 )->chain_confirmed == 1 ); - FD_TEST( fd_forest_query( forest, 2 )->chain_confirmed == 1 ); - - fd_forest_print( forest ); - - /* - 2 - | \ - 3' \ - / | \ - 8 4 4' - - We receive 4 (that has only 1 FEC set) built off of 3'. We later - see that 4' is the duplicate confirmed version. 4' may have been - built off an entirely different parent. - - We must clear 4 and re-repair 4'. - */ - - /* now pretend that we rerepaired 4 */ - - /* now scenario is we've replayed through 6, but then learn 5 is - an incorrect version. We need to dump it. At that point we - expect slot 6 to be in subtrees, and 7 in the orphaned. - 0 - 2 - 4 - 5 - 6 - 7 0 - 2 - 4 6 - 7 - \ -> \ - 3 3 - */ - - /* now we add back 5, . - 0 - 2 - 4 - 5 - 6 - 7 0 - 2 - 4 6 - 7 - \ -> \ - 3 3 - */ -} -void -print_orphan_requests( fd_forest_t * forest ) { - fd_forest_reqslist_iter_t iter = fd_forest_reqslist_iter_fwd_init( fd_forest_orphlist( forest ), fd_forest_reqspool( forest ) ); - while( !fd_forest_reqslist_iter_done( iter, fd_forest_orphlist( forest ), fd_forest_reqspool( forest ) ) ) { - fd_forest_ref_t * ele = fd_forest_reqslist_iter_ele( iter, fd_forest_orphlist( forest ), fd_forest_reqspool( forest ) ); - FD_LOG_NOTICE(( "orphan request: %lu", idx_slot( forest, ele->idx ) )); - iter = fd_forest_reqslist_iter_fwd_next( iter, fd_forest_orphlist( forest ), fd_forest_reqspool( forest ) ); - } -} - -void -test_verify_orphans( fd_wksp_t * wksp ) { - ulong ele_max = 8; - void * mem = fd_wksp_alloc_laddr( wksp, fd_forest_align(), fd_forest_footprint( ele_max, FD_SHRED_BLK_MAX ), 1UL ); - FD_TEST( mem ); - fd_forest_t * forest = fd_forest_join( fd_forest_new( mem, ele_max, FD_SHRED_BLK_MAX, 42UL /* seed */ ) ); - /* - 2 - | \ - 3 3' - */ - - /* We execute (2, 0) -> (2,32) -> (3, 0') -> (3, 32'). Tower detects - that (3, 32) is the duplicate confirmed version. - We must dump slot 3 and re-repair. */ - - fd_hash_t mr_0 = (fd_hash_t){ .key = { 1 } }; - fd_hash_t mr_2_0 = (fd_hash_t){ .key = { 2 } }; - fd_hash_t mr_2_32 = (fd_hash_t){ .key = { 3 } }; - fd_hash_t mr_3_0 = (fd_hash_t){ .key = { 4 } }; - fd_hash_t mr_3_32 = (fd_hash_t){ .key = { 5 } }; - fd_hash_t mr_3_0_ = (fd_hash_t){ .key = { 6 } }; (void)mr_3_0_; - fd_hash_t mr_1_0 = (fd_hash_t){ .key = { 7 } }; - fd_hash_t mr_1_32 = (fd_hash_t){ .key = { 8 } }; - - - fd_forest_blk_t * ele; - fd_forest_init( forest, 0 ); - fd_forest_blk_insert( forest, 2, 1, NULL ); - fd_forest_blk_insert( forest, 3, 2, NULL ); - print_orphan_requests( forest ); - - /* check that block 1 is in orphan requests list */ - ulong idx = slot_idx( forest, 2 ); - FD_TEST( fd_forest_requests_ele_query( fd_forest_orphreqs( forest ), &idx, NULL, fd_forest_reqspool( forest ) ) ); - - /* slot paren last fec_set slot_cmpl rt mr cmr */ - fd_forest_fec_insert( forest, 2, 1, 31, 0, 0, 0, &mr_2_0, &mr_1_32, fd_tickcount() ); - fd_forest_fec_insert( forest, 2, 1, 63, 32, 1, 0, &mr_2_32, &mr_2_0, fd_tickcount() ); - - fd_forest_fec_insert( forest, 3, 2, 31, 0, 0, 0, &mr_3_0, &mr_2_32, fd_tickcount() ); - fd_forest_fec_insert( forest, 3, 2, 63, 32, 1, 0, &mr_3_32, &mr_3_0, fd_tickcount() ); - - FD_TEST( !fd_forest_fec_chain_verify( forest, fd_forest_query( forest, 3 ), &mr_3_32 ) ); - /* orphans verify */ - - /* Now chain 1 to 0, and this should trigger a verification of the chain from 2 to 0 */ - fd_forest_blk_insert( forest, 1, 0, NULL ); - ele = fd_forest_fec_insert( forest, 1, 0, 31, 0, 0, 0, &mr_1_0, &mr_0, fd_tickcount() ); - FD_TEST( ele->lowest_verified_fec == UINT_MAX ); - ele = fd_forest_fec_insert( forest, 1, 0, 63, 32, 1, 0, &mr_1_32, &mr_1_0, fd_tickcount() ); - FD_TEST( ele->lowest_verified_fec == 32/32 ); - FD_TEST( !fd_forest_fec_chain_verify( forest, fd_forest_query( forest, 3 ), &mr_3_32 ) ); - /* orphans verify */ -} - -void -test_eviction_simple( fd_wksp_t * wksp ) { - ulong ele_max = 8; - void * mem = fd_wksp_alloc_laddr( wksp, fd_forest_align(), fd_forest_footprint( ele_max, FD_SHRED_BLK_MAX ), 1UL ); - FD_TEST( mem ); - fd_forest_t * forest = fd_forest_join( fd_forest_new( mem, ele_max, FD_SHRED_BLK_MAX, 42UL /* seed */ ) ); - fd_forest_init( forest, 0 ); - - fd_hash_t mr = (fd_hash_t){ .key = { 1 } }; - fd_forest_blk_fec_insert( forest, 1, 0, 0, 0, 1 ); - FD_TEST( !fd_forest_fec_chain_verify( forest, fd_forest_query( forest, 1 ), &mr ) ); - fd_forest_blk_fec_insert( forest, 2, 1, 0, 0, 1 ); - fd_forest_blk_fec_insert( forest, 3, 2, 0, 0, 1 ); - fd_forest_blk_fec_insert( forest, 4, 3, 0, 0, 1 ); - fd_forest_blk_fec_insert( forest, 5, 4, 0, 0, 1 ); - fd_forest_blk_fec_insert( forest, 6, 5, 0, 0, 1 ); - fd_forest_blk_fec_insert( forest, 7, 6, 0, 0, 1 ); - - /* now forest is full, and there is only one fork. */ - ulong evicted = ULONG_MAX; - /* try to add 8, parent is leaf 7. should force a root. */ - FD_TEST( fd_forest_blk_fec_insert( forest, 8, 7, 0, 0, 1 ) ); - FD_TEST( !fd_forest_verify( forest ) ); - - /* add 9, parent is non-leaf 6. should succeed. 8 is evicted */ - fd_forest_print( forest ); - FD_TEST( fd_forest_blk_insert( forest, 9, 6, &evicted ) ); - FD_TEST( evicted == 8 ); - fd_forest_print( forest ); - - FD_TEST( !fd_forest_query( forest, 8 ) ); - FD_TEST( !fd_forest_verify( forest ) ); - - /* add 16, which would be an orphan. should succeed.*/ - FD_TEST( fd_forest_blk_insert( forest, 16, 15, &evicted ) ); - fd_forest_print( forest ); - - FD_TEST( !fd_forest_verify( forest ) ); - - /* add 10, parent 6. (fork) */ - FD_TEST( fd_forest_blk_fec_insert( forest, 10, 6, 0, 0, 1 ) ); - - FD_TEST( !fd_forest_query( forest, 16 ) ); /* 16 gets evicted*/ - - fd_forest_print( forest ); - - /* clear */ - fd_forest_publish( forest, 10 ); - - /* create forks 10 - 11 - 12 - 13 - 17 - 14 - 15 - 16 */ - - fd_forest_blk_fec_insert( forest, 11, 10, 0, 0, 1 ); - fd_forest_blk_fec_insert( forest, 12, 11, 0, 0, 1 ); - fd_forest_blk_fec_insert( forest, 13, 12, 0, 0, 1 ); - fd_forest_blk_fec_insert( forest, 17, 13, 0, 0, 1 ); - fd_forest_blk_fec_insert( forest, 14, 10, 0, 0, 1 ); - fd_forest_blk_fec_insert( forest, 15, 14, 0, 0, 1 ); - fd_forest_blk_fec_insert( forest, 16, 15, 0, 0, 1 ); - fd_forest_print( forest ); - FD_TEST( fd_forest_blk_insert( forest, 18, 16, &evicted ) ); - fd_forest_print( forest ); -} - -void -test_eviction_confirmations( fd_wksp_t * wksp ) { - ulong ele_max = 8; - void * mem = fd_wksp_alloc_laddr( wksp, fd_forest_align(), fd_forest_footprint( ele_max, FD_SHRED_BLK_MAX ), 1UL ); - FD_TEST( mem ); - fd_forest_t * forest = fd_forest_join( fd_forest_new( mem, ele_max, FD_SHRED_BLK_MAX, 42UL /* seed */ ) ); - fd_forest_init( forest, 0 ); - - /* create forks 10 - 11 - 12 - 13 - 17 (confirmed) - \ 14 - 15 */ - - fd_forest_blk_fec_insert( forest, 10, 0, 31, 0, 1 ); - fd_forest_blk_fec_insert( forest, 11, 10, 31, 0, 1 ); - fd_forest_blk_fec_insert( forest, 12, 11, 31, 0, 1 ); - fd_forest_blk_fec_insert( forest, 13, 12, 31, 0, 1 ); - fd_forest_blk_fec_insert( forest, 17, 13, 31, 0, 1 ); - fd_forest_blk_fec_insert( forest, 14, 10, 31, 0, 1 ); - fd_forest_blk_fec_insert( forest, 15, 14, 31, 0, 1 ); - - fd_forest_print( forest ); - - fd_hash_t mr_17 = (fd_hash_t){ .key = { 1 } }; - ulong evicted = ULONG_MAX; - - FD_TEST( !fd_forest_fec_chain_verify( forest, fd_forest_query( forest, 17 ), &mr_17 ) ); - - FD_TEST( fd_forest_blk_fec_insert( forest, 18, 17, 31, 0, 1 ) ); - FD_TEST( !fd_forest_query( forest, 15 ) ); - - FD_TEST( !fd_forest_fec_chain_verify( forest, fd_forest_query( forest, 18 ), &mr_17 ) ); - /* now 18 is verified. don't want to evict it */ - FD_TEST( !fd_forest_blk_insert( forest, 16, 14, &evicted ) ); /* fails because we add to a bad fork */ - - fd_forest_publish( forest, 11 ); /* publish forwards to 11. Now we only have one fork. (that's confirmed) */ - - // lets say we get a slot in the future, 24. - // We start repairing orphans backwards - FD_TEST( fd_forest_blk_fec_insert( forest, 24, 23, 31, 0, 1 ) ); - FD_TEST( fd_forest_blk_fec_insert( forest, 23, 22, 31, 0, 1 ) ); - FD_TEST( fd_forest_blk_fec_insert( forest, 22, 21, 31, 0, 1 ) ); - /* atp we are filled up. slot 21 NEEDs to be accepted. */ - FD_TEST( fd_forest_blk_fec_insert( forest, 21, 20, 31, 0, 1 ) ); - FD_TEST( !fd_forest_query( forest, 24 ) ); /* 24 gets evicted. */ - - /* Now we confirm the orphan chain starting at 23. - 21 - 22 - 23 are all verified. */ - fd_hash_t mr_23 = (fd_hash_t){ .key = { 1 } }; - FD_TEST( !fd_forest_fec_chain_verify( forest, fd_forest_query( forest, 23 ), &mr_23 ) ); - - /* now we add 20, parent is 19. */ - FD_TEST( fd_forest_blk_fec_insert( forest, 20, 19, 31, 0, 1 ) ); - FD_TEST( !fd_forest_query( forest, 23 ) ); /* 23 gets evicted. */ - - /* an older orphan contributes to the awesomeness */ - FD_TEST( fd_forest_blk_insert( forest, 25, 24, &evicted ) ); - FD_TEST( !fd_forest_query ( forest, 22 ) ); /* 22 gets evicted. */ -} - -void -test_eviction_confirmed_orphan_gca( fd_wksp_t * wksp ) { - /* Test the eviction path where: - - The evict candidate is a confirmed orphan leaf - - latest_confirmed_leaf is non-NULL - - latest_confirmed_leaf == gca(latest_confirmed_leaf, parent) - i.e. parent extends the confirmed chain - - Main tree: 0 ── 1 ── 2 ── 3 ── 4(confirmed) ── 5(frontier, unconfirmed) - - Orphan: 20(confirmed) ── 21(confirmed) - - Insert slot 6 with parent 5: - - slot 5 is filtered out (it IS the parent) - - only orphan leaf candidate is 21 (confirmed) → confirmed_orphan - - latest_confirmed_leaf in the main tree = slot 4 - - gca(slot 4, slot 5) = slot 4 = latest_confirmed_leaf → evict confirmed_orphan */ - - ulong ele_max = 8; - void * mem = fd_wksp_alloc_laddr( wksp, fd_forest_align(), fd_forest_footprint( ele_max, FD_SHRED_BLK_MAX ), 1UL ); - FD_TEST( mem ); - fd_forest_t * forest = fd_forest_join( fd_forest_new( mem, ele_max, FD_SHRED_BLK_MAX, 42UL ) ); - fd_forest_init( forest, 0 ); - - fd_hash_t mr = (fd_hash_t){ .key = { 1 } }; - - /* Build main tree: 0 ── 1 ── 2 ── 3 ── 4 ── 5 - All slot_complete via fec_insert. */ - fd_forest_blk_fec_insert( forest, 1, 0, 0, 0, 1 ); - fd_forest_blk_fec_insert( forest, 2, 1, 0, 0, 1 ); - fd_forest_blk_fec_insert( forest, 3, 2, 0, 0, 1 ); - fd_forest_blk_fec_insert( forest, 4, 3, 0, 0, 1 ); - fd_forest_blk_fec_insert( forest, 5, 4, 0, 0, 1 ); - - /* Confirm slots 1-4 via fec_chain_verify from slot 4 backwards. */ - FD_TEST( !fd_forest_fec_chain_verify( forest, fd_forest_query( forest, 4 ), &mr ) ); - - /* Build orphan subtree: 20 ── 21, both slot_complete. */ - fd_forest_blk_fec_insert( forest, 20, 19, 0, 0, 1 ); - fd_forest_blk_fec_insert( forest, 21, 20, 0, 0, 1 ); - - /* Confirm orphan: chain_verify from 21 confirms 21 and 20. */ - FD_TEST( !fd_forest_fec_chain_verify( forest, fd_forest_query( forest, 21 ), &mr ) ); - - /* Pool is now full: 0,1,2,3,4,5,20,21 = 8 elements. */ - FD_TEST( !fd_forest_verify( forest ) ); - fd_forest_print( forest ); - - /* Insert slot 6 with parent 5. Eviction should pick - confirmed_orphan (slot 21) because: - - No unconfirmed orphan/frontier leaves (5 is filtered as parent) - - confirmed_orphan = 21 - - parent (slot 5) exists → not a new orphan subtree root - - subtree_root = forest->root (parent is in main tree) - - latest_confirmed_leaf = slot 4 (deepest confirmed in main tree) - - gca(slot 4, slot 5) = slot 4 = latest_confirmed_leaf → evict */ - ulong evicted = ULONG_MAX; - FD_TEST( fd_forest_blk_insert( forest, 6, 5, &evicted ) ); - FD_TEST( evicted == 21 ); - FD_TEST( !fd_forest_query( forest, 21 ) ); - FD_TEST( fd_forest_query( forest, 6 ) ); - FD_TEST( !fd_forest_verify( forest ) ); - - /* Now extend: make slot 6 complete and confirm 5-6 so all leaves - are confirmed. Pool: 0,1,2,3,4,5,6,20 = 8 (full). - - Main tree: 0 ── 1 ── 2 ── 3 ── 4 ── 5 ── 6 (all confirmed) - Orphan: 20(confirmed, leaf) - - Insert slot 7 with parent 2 — a useless new fork that branches - before the confirmed tip. Eviction should FAIL (ULONG_MAX): - - confirmed_orphan = 20 - - parent = slot 2 (exists, not NULL) - - subtree_root = forest->root (parent in main tree) - - latest_confirmed_leaf = slot 6 - - gca(slot 6, slot 2) = slot 2 ≠ slot 6 → useless fork → no evict */ - - fd_forest_blk_fec_insert( forest, 6, 5, 0, 0, 1 ); /* make slot 6 complete */ - FD_TEST( !fd_forest_fec_chain_verify( forest, fd_forest_query( forest, 6 ), &mr ) ); - - FD_TEST( !fd_forest_blk_insert( forest, 7, 2, &evicted ) ); - FD_TEST( fd_forest_query( forest, 20 ) ); /* orphan 20 survives */ - FD_TEST( !fd_forest_query( forest, 7 ) ); /* slot 7 was not added */ - FD_TEST( !fd_forest_verify( forest ) ); - - fd_wksp_free_laddr( fd_forest_delete( fd_forest_leave( forest ) ) ); -} - -void -test_eviction_confirmed_bid_orphan( fd_wksp_t * wksp ) { - /* An orphan with confirmed_bid set (confirmed by the cluster but not - fully chain-verified) should be treated as confirmed for eviction, - protecting it from eviction before truly unconfirmed orphans. - - Main tree: 0 -> 1 -> 2 -> 3 -> 4 -> 5 - Orphan 10: no confirmation (should be evicted) - Orphan 20: confirmed_bid is set (should survive) */ - - ulong ele_max = 8; - void * mem = fd_wksp_alloc_laddr( wksp, fd_forest_align(), fd_forest_footprint( ele_max, FD_SHRED_BLK_MAX ), 1UL ); - FD_TEST( mem ); - fd_forest_t * forest = fd_forest_join( fd_forest_new( mem, ele_max, FD_SHRED_BLK_MAX, 42UL ) ); - fd_forest_init( forest, 0 ); - - fd_forest_blk_fec_insert( forest, 1, 0, 31, 0, 1 ); - fd_forest_blk_fec_insert( forest, 2, 1, 31, 0, 1 ); - fd_forest_blk_fec_insert( forest, 3, 2, 31, 0, 1 ); - fd_forest_blk_fec_insert( forest, 4, 3, 31, 0, 1 ); - fd_forest_blk_fec_insert( forest, 5, 4, 31, 0, 1 ); - - fd_forest_blk_insert( forest, 10, 9, NULL ); - - fd_forest_blk_insert( forest, 20, 19, NULL ); - fd_forest_query( forest, 20 )->confirmed_bid = (fd_hash_t){ .ul = { 42 } }; - - FD_TEST( !fd_forest_pool_free( fd_forest_pool( forest ) ) ); - - ulong evicted = ULONG_MAX; - FD_TEST( fd_forest_blk_insert( forest, 6, 5, &evicted ) ); - - FD_TEST( evicted == 10 ); - FD_TEST( !fd_forest_query( forest, 10 ) ); - FD_TEST( fd_forest_query( forest, 20 ) ); - FD_TEST( !fd_forest_verify( forest ) ); - - FD_TEST( fd_forest_blk_insert( forest, 7, 5, &evicted ) ); - FD_TEST( evicted == 6 ); - - fd_wksp_free_laddr( fd_forest_delete( fd_forest_leave( forest ) ) ); -} - -void -test_eviction_deep( fd_wksp_t * wksp ) { - ulong ele_max = 512; - void * mem = fd_wksp_alloc_laddr( wksp, fd_forest_align(), fd_forest_footprint( ele_max, FD_SHRED_BLK_MAX ), 1UL ); - FD_TEST( mem ); - fd_forest_t * forest = fd_forest_join( fd_forest_new( mem, ele_max, FD_SHRED_BLK_MAX, 42UL /* seed */ ) ); - fd_forest_init( forest, 1 ); - - /* Scenario for DoS attack: - - We are happily traversing along, 1 - 2 ... - 30. Slot 29 - is confirmed. The fire nation attacks!!! - (1) The first attack is an equivocation vector. Luckily since repair - is keyed by slot, this attack is not very effective. We won't - start allocating a bunch of new pool elements because of diff - versions of the same slot. - (2) first REAL attack: - they send us a lot of future slots, because they have a lot of - stake. So imagine that they have slots 4000->8096 . 4000 connects - to slot 29 - (3) variation on the first attack: - they send us a lot of future slots, because they have a lot of - stake. So imagine that they have slots 4000->8096 . 4000 connects - to slot 3000, which is some slot that needs to be built by a - thenselves in the future. - (3) second attack: - instead of sending us one long chain, they send us a very WIDE - tree. everything chains to 29. */ - - fd_hash_t mr = (fd_hash_t){ .key = { 1 } }; // all merkle roots have this value for ease - - for( ulong i = 2; i <= 30; i++ ) { - FD_TEST( fd_forest_blk_fec_insert( forest, i, i - 1, 31, 0, 1 ) ); - } - FD_TEST( !fd_forest_fec_chain_verify( forest, fd_forest_query( forest, 29 ), &mr )); - - /* now the fire nation attacks! We have space for 512 - 30 = 482 slots, - which should allow us slots 4000 - 4481 */ - for( ulong i = 4000; i < 4000 + 482; i++ ) { - FD_TEST( fd_forest_blk_fec_insert( forest, i, i-1, 31, 0, 1 ) ); - FD_TEST( fd_forest_query( forest, 30 ) ); /* should remain */ - /* throughout all of this we will be repairing for the ancestry, - and not really receiving it!!!!! */ - } - ulong evicted = ULONG_MAX; - FD_TEST( fd_forest_blk_insert( forest, 4482, 4481, &evicted ) ); - FD_TEST( !fd_forest_query( forest, 30 ) ); /* should be evicted */ - - // resolving: lets say we get a gossip confirmation for slot 70 amidst all this - FD_TEST( fd_forest_blk_fec_insert( forest, 70, 69, 31, 0, 1 ) ); - FD_TEST( !fd_forest_fec_chain_verify( forest, fd_forest_query( forest, 70 ), &mr )); - - /* somehow we gotta prioritize resolving the ancestry for 70 over the - firehose. only creating one fork i.e. always chain parent after - itself case is kind of unclear. Eventually fec resolver though will - stop passing on duplicate FECs and ones with slots that don't - verify. */ - - for( ulong i = 4000 + 483; i < 8096; i++ ) { - fd_forest_blk_insert( forest, i, i-1, &evicted ); - FD_TEST( fd_forest_query( forest, 29 ) ); /* should remain */ - } - FD_TEST( fd_forest_query( forest, 8095 ) ); - fd_forest_print( forest ); -} - -void -test_sentinel_blocks( fd_wksp_t * wksp ) { - ulong ele_max = 16; - void * mem = fd_wksp_alloc_laddr( wksp, fd_forest_align(), fd_forest_footprint( ele_max, FD_SHRED_BLK_MAX ), 1UL ); - FD_TEST( mem ); - fd_forest_t * forest = fd_forest_join( fd_forest_new( mem, ele_max, FD_SHRED_BLK_MAX, 42UL /* seed */ ) ); - fd_forest_init( forest, 0 ); - - fd_forest_subtrees_t * subtrees = fd_forest_subtrees( forest ); - - /* insert sentinel blocks */ - for( ulong i = 1; i <= 5; i++ ) { - FD_TEST( fd_forest_blk_insert( forest, i, ULONG_MAX, NULL ) ); - FD_TEST( fd_forest_subtrees_ele_query( subtrees, &i, NULL, fd_forest_pool( forest ) ) ); - } - - FD_TEST( fd_forest_blk_insert( forest, 2, 0, NULL ) ); - FD_TEST( fd_forest_query( forest, 2 )->parent_slot == 0 ); - FD_TEST( fd_forest_blk_insert( forest, 2, 0, NULL ) == fd_forest_query( forest, 2 ) ); - FD_TEST( fd_forest_blk_insert( forest, 2, 1, NULL ) ); - FD_TEST( fd_forest_query( forest, 2 )->parent_slot == 0 ); /* parent slot should not change */ - FD_TEST( !fd_forest_verify( forest ) ); -} - -void -test_parent_update( fd_wksp_t * wksp ) { - /* o o - _ ` */ - - ulong ele_max = 8; - void * mem = fd_wksp_alloc_laddr( wksp, fd_forest_align(), fd_forest_footprint( ele_max, FD_SHRED_BLK_MAX ), 1UL ); - FD_TEST( mem ); - fd_forest_t * forest = fd_forest_join( fd_forest_new( mem, ele_max, FD_SHRED_BLK_MAX, 42UL /* seed */ ) ); - fd_forest_init( forest, 0 ); - - fd_hash_t mr_1 = (fd_hash_t){ .ul = { 1 } }; - fd_hash_t mr_2 = (fd_hash_t){ .ul = { 2 } }; - fd_hash_t mr_3_2 = (fd_hash_t){ .ul = { 3, 2 } }; - fd_hash_t mr_3 = (fd_hash_t){ .ul = { 3 } }; - fd_hash_t mr_7_0 = (fd_hash_t){ .ul = { 7, 0 } }; - fd_hash_t mr_7_32_ = (fd_hash_t){ .ul = { 7, 32 } }; - fd_hash_t mr_7_1 = (fd_hash_t){ .ul = { 7, 1 } }; - fd_hash_t mr_6_0 = (fd_hash_t){ .ul = { 6, 0 } }; - fd_hash_t mr_8_0 = (fd_hash_t){ .ul = { 8, 0 } }; - - - /* - 0 - 1 - 3 7 - 8 - */ - - fd_forest_blk_insert( forest, 1, 0, NULL ); - fd_forest_blk_insert( forest, 3, 1, NULL ); - fd_forest_fec_insert( forest, 3, 1, 31, 0, 1, 0, &mr_3_2, &mr_1, fd_tickcount() ); - /* orphans */ - fd_forest_blk_insert( forest, 7, 6, NULL ); - fd_forest_fec_insert( forest, 7, 6, 31, 0, 0, 0, &mr_7_0, &mr_6_0, fd_tickcount() ); - fd_forest_fec_insert( forest, 7, 6, 63, 32, 1, 0, &mr_7_32_, &mr_7_0, fd_tickcount() ); - fd_forest_blk_insert( forest, 8, 7, NULL ); - fd_forest_data_shred_insert( forest, 8, 7, 31, 0, 0, 0, SHRED_SRC_REPAIR, &mr_8_0, &mr_7_0, fd_tickcount() ); - - /* now confirm 7 with a different block id. */ - FD_TEST( fd_forest_fec_chain_verify( forest, fd_forest_query( forest, 7 ), &mr_7_1 ) ); - FD_TEST( fd_forest_merkle_last_incorrect_idx( fd_forest_query( forest, 7 ) ) == 32UL ); /* INCORRECT. */ - - /* now clear 7, 0. */ - fd_forest_fec_clear( forest, 7, 32, 31 ); - - /* now get a data shred / fec that is correct */ - //FD_TEST( fd_forest_data_shred_insert( forest, 7, 3, 31, 0, 1, 0, SHRED_SRC_REPAIR, &mr_7_1, &mr_3, fd_tickcount() ) ); - fd_forest_fec_insert( forest, 7, 3, 31, 0, 1, 0, &mr_7_1, &mr_3, fd_tickcount() ); - FD_TEST( fd_forest_query( forest, 7 )->parent_slot == 3 ); /* parent slot should be updated */ - ulong _8 = 8; - FD_TEST( fd_forest_frontier_ele_query( fd_forest_frontier( forest ), &_8, NULL, fd_forest_pool( forest ) ) ); - FD_TEST( !fd_forest_verify( forest ) ); - fd_forest_print( forest ); - FD_TEST( fd_forest_query( forest, 7 )->lowest_verified_fec == 0 ); - FD_TEST( fd_forest_query( forest, 7 )->complete_idx == 31 ); - - /* an old data shred with the wrong parent_slot shouldn't cause the - parent_slot to update */ - FD_TEST( !fd_forest_data_shred_insert( forest, 7, 6, 31, 0, 1, 0, 0, &mr_7_0, &mr_6_0, fd_tickcount() ) ); - FD_TEST( fd_forest_query( forest, 7 )->parent_slot == 3 ); - - FD_TEST( fd_forest_fec_chain_verify( forest, fd_forest_query( forest, 7 ), &mr_7_1 ) == fd_forest_query( forest, 3 ) ); - FD_TEST( fd_forest_merkle_last_incorrect_idx( fd_forest_query( forest, 3 ) ) == 0UL ); - - /* SURPRISE! let's say slot 3's real parent should be slot 2 */ - - fd_forest_data_shred_insert( forest, 3, 2, 31, 0, 0, 0, SHRED_SRC_REPAIR, &mr_3, &mr_2, fd_tickcount() ); - /*should get accepted*/ - FD_TEST( fd_forest_query( forest, 3 )->parent_slot == 2 ); - ulong _3 = 3; ulong _7 = 7; - fd_forest_print( forest ); - FD_TEST( fd_forest_subtrees_ele_query( fd_forest_subtrees( forest ), &_3, NULL, fd_forest_pool( forest ) ) ); - FD_TEST( fd_forest_orphaned_ele_query( fd_forest_orphaned( forest ), &_8, NULL, fd_forest_pool( forest ) ) ); - FD_TEST( fd_forest_orphaned_ele_query( fd_forest_orphaned( forest ), &_7, NULL, fd_forest_pool( forest ) ) ); - FD_TEST( !fd_forest_verify( forest ) ); - - /* now get something that's an orphan, real parent keeps it an orphan */ - ulong _9 = 9; - fd_hash_t mr_9_0 = (fd_hash_t){ .ul = { 9, 0 } }; - fd_forest_blk_insert( forest, 9, 8, NULL ); - fd_forest_data_shred_insert( forest, 9, 8, 31, 0, 1, 1, SHRED_SRC_REPAIR, &mr_9_0, &mr_8_0, fd_tickcount() ); - - fd_hash_t mr_9_1 = (fd_hash_t){ .ul = { 9, 1 } }; - fd_hash_t mr_5 = (fd_hash_t){ .ul = { 5 } }; - FD_TEST( fd_forest_fec_chain_verify( forest, fd_forest_query( forest, 9 ), &mr_9_1 ) == fd_forest_query( forest, 9 ) ); - FD_TEST( fd_forest_merkle_last_incorrect_idx( fd_forest_query( forest, 9 ) ) == 0UL ); - fd_forest_fec_clear( forest, 9, 0, 31 ); - - /* slot_complete must be 1 here: shred 31 IS the last shred in this - 1-FEC slot. Previously the test passed with slot_complete=0 because - the stale lowest_verified_fec (from the old fec_chain_verify pre-set) - made merkle_verified return true. */ - fd_forest_data_shred_insert( forest, 9, 5, 31, 0, 1, 1, SHRED_SRC_REPAIR, &mr_9_1, &mr_5, fd_tickcount() ); - FD_TEST( fd_forest_query( forest, 9 )->parent_slot == 5 ); - FD_TEST( fd_forest_subtrees_ele_query( fd_forest_subtrees( forest ), &_9, NULL, fd_forest_pool( forest ) ) ); - FD_TEST( !fd_forest_verify( forest ) ); -} - -void -test_eqvoc_blk_wrong_parent( fd_wksp_t * wksp ) { - /* initial block 3 version has slot 1 as parent. It also has multiple siblings 5 -> 4 -> 3, correct version has slot 2 as parent. */ - ulong ele_max = 8; - void * mem = fd_wksp_alloc_laddr( wksp, fd_forest_align(), fd_forest_footprint( ele_max, FD_SHRED_BLK_MAX ), 1UL ); - FD_TEST( mem ); - fd_forest_t * forest = fd_forest_join( fd_forest_new( mem, ele_max, FD_SHRED_BLK_MAX, 42UL /* seed */ ) ); - fd_forest_init( forest, 0 ); - fd_forest_blk_data_shred_insert( forest, 1, 0, 31, 0, 0, 1 ); - fd_forest_blk_data_shred_insert( forest, 2, 1, 31, 0, 0, 1 ); - - fd_hash_t mr1 = (fd_hash_t){ .key = { 1 } }; - fd_hash_t mr_3_32 = (fd_hash_t){ .key = { 3, 1 } }; - fd_hash_t mr_3_0 = (fd_hash_t){ .key = { 3, 0 } }; - - fd_forest_blk_insert( forest, 5, 1, NULL ); - fd_forest_blk_insert( forest, 4, 1, NULL ); - - fd_forest_blk_insert( forest, 3, 1, NULL ); - fd_forest_data_shred_insert( forest, 3, 1, 63, 32, 1, 0, SHRED_SRC_REPAIR, &mr_3_32, &mr_3_0, fd_tickcount() ); - fd_forest_fec_insert( forest, 3, 1, 31, 0, 0, 0, &mr_3_0, &mr1, fd_tickcount() ); - - fd_hash_t mr_3_32_ = (fd_hash_t){ .key = { 3, 1, 1 } }; - fd_hash_t mr_3_0_ = (fd_hash_t){ .key = { 3, 0, 1 } }; - - FD_TEST( fd_forest_fec_chain_verify( forest, fd_forest_query( forest, 3 ), &mr_3_32_ ) ); - FD_TEST( fd_forest_merkle_last_incorrect_idx( fd_forest_query( forest, 3 ) ) == 32UL ); - - fd_forest_fec_clear( forest, 3, 32, 31 ); - fd_forest_data_shred_insert( forest, 3, 2, 63, 32, 1, 0, SHRED_SRC_REPAIR, &mr_3_32_, &mr_3_0_, fd_tickcount() ); - FD_TEST( fd_forest_query( forest, 3 )->parent_slot == 2 ); /* parent slot should be updated */ - FD_TEST( !fd_forest_verify( forest ) ); - - fd_forest_fec_insert( forest, 3, 2, 63, 32, 1, 0, &mr_3_32_, &mr_3_0_, fd_tickcount() ); - FD_TEST( !fd_forest_fec_chain_verify( forest, fd_forest_query( forest, 3 ), &mr_3_32_ ) ); - FD_TEST( fd_forest_merkle_last_incorrect_idx( fd_forest_query( forest, 3 ) ) == 0UL ); -} - -void -test_buffered_idx_gt_complete_idx( fd_wksp_t * wksp ) { - /* Regression: equivocating leader sends shreds that push buffered_idx - far ahead. When the real slot-complete shred arrives at a smaller - index, complete_idx is set but buffered_idx is not clamped down. - - buffered_idx > complete_idx breaks: - - advance_consumed_frontier (checks complete_idx == buffered_idx) - - fec_chain_verify (checks buffered_idx != complete_idx) - - iter_next (thinks slot is done, skips remaining shreds) */ - - ulong ele_max = 8; - void * mem = fd_wksp_alloc_laddr( wksp, fd_forest_align(), fd_forest_footprint( ele_max, FD_SHRED_BLK_MAX ), 1UL ); - FD_TEST( mem ); - fd_forest_t * forest = fd_forest_join( fd_forest_new( mem, ele_max, FD_SHRED_BLK_MAX, 42UL ) ); - fd_forest_init( forest, 0 ); - - /* Complete slot 1 so consumed frontier can advance */ - fd_forest_blk_fec_insert( forest, 1, 0, 31, 0, 1 ); - - /* Insert slot 2 */ - fd_forest_blk_insert( forest, 2, 1, NULL ); - - /* Equivocating shreds: 2 FEC sets (shreds 0-63) arrive for slot 2 */ - fd_hash_t mr_a = (fd_hash_t){ .key = { 1 } }; - fd_hash_t cmr_a = (fd_hash_t){ .key = { 1 } }; - for( uint i = 0; i < 32; i++ ) { - fd_forest_data_shred_insert( forest, 2, 1, i, 0, 0, 0, SHRED_SRC_TURBINE, &mr_a, &cmr_a, fd_tickcount() ); - } - - fd_hash_t mr_b = (fd_hash_t){ .key = { 2 } }; - fd_hash_t cmr_b = (fd_hash_t){ .key = { 2 } }; - for( uint i = 32; i < 64; i++ ) { - fd_forest_data_shred_insert( forest, 2, 1, i, 32, 0, 0, SHRED_SRC_TURBINE, &mr_b, &cmr_b, fd_tickcount() ); - } - - FD_TEST( fd_forest_query( forest, 2 )->buffered_idx == 63 ); - - /* Real slot-complete shred: slot is actually only 1 FEC set (32 shreds). - complete_idx = 31, but buffered_idx stays at 63 without the fix. */ - fd_forest_data_shred_insert( forest, 2, 1, 31, 0, 1, 0, SHRED_SRC_REPAIR, &mr_a, &cmr_a, fd_tickcount() ); - - fd_forest_blk_t * ele = fd_forest_query( forest, 2 ); - FD_TEST( ele->complete_idx == 31 ); - FD_TEST( ele->buffered_idx <= ele->complete_idx ); - - FD_TEST( !fd_forest_verify( forest ) ); - - fd_wksp_free_laddr( fd_forest_delete( fd_forest_leave( forest ) ) ); -} - -void -test_verified_parent_update_stale_sibling( fd_wksp_t * wksp ) { - /* Regression: verified_parent_update detaches children into subtrees - but never clears their sibling pointers. When blk_insert relinks - them to the newly created node, link_sibling walks the stale chain - and sets child->sibling = child (self-loop). The frontier-extension - BFS inside blk_insert then hangs forever. - - Tree before: - root(0) -> 1 -> 3 6? -> 7 -> {8, 9} - - Trigger verified_parent_update on slot 7 (parent 6 -> 3). - After the update slot 7 should be: 0 -> 1 -> 3 -> 7 -> {8, 9} - with no sibling self-loops. */ - - ulong ele_max = 16; - void * mem = fd_wksp_alloc_laddr( wksp, fd_forest_align(), fd_forest_footprint( ele_max, FD_SHRED_BLK_MAX ), 1UL ); - FD_TEST( mem ); - fd_forest_t * forest = fd_forest_join( fd_forest_new( mem, ele_max, FD_SHRED_BLK_MAX, 42UL ) ); - fd_forest_init( forest, 0 ); - - fd_hash_t mr_1 = (fd_hash_t){ .ul = { 1 } }; - fd_hash_t mr_3 = (fd_hash_t){ .ul = { 3 } }; - fd_hash_t mr_6_0 = (fd_hash_t){ .ul = { 6, 0 } }; - fd_hash_t mr_7_0 = (fd_hash_t){ .ul = { 7, 0 } }; - fd_hash_t mr_7_1 = (fd_hash_t){ .ul = { 7, 1 } }; - fd_hash_t mr_8_0 = (fd_hash_t){ .ul = { 8, 0 } }; - fd_hash_t mr_9_0 = (fd_hash_t){ .ul = { 9, 0 } }; - - /* Main tree: 0 -> 1 -> 3 */ - fd_forest_blk_insert( forest, 1, 0, NULL ); - fd_forest_blk_insert( forest, 3, 1, NULL ); - fd_forest_fec_insert( forest, 3, 1, 31, 0, 1, 0, &mr_3, &mr_1, fd_tickcount() ); - - /* Orphan subtree: 7 -> {8, 9} (8 and 9 are siblings under 7) */ - fd_forest_blk_insert( forest, 7, 6, NULL ); - fd_forest_fec_insert( forest, 7, 6, 31, 0, 1, 0, &mr_7_0, &mr_6_0, fd_tickcount() ); - - fd_forest_blk_insert( forest, 8, 7, NULL ); - fd_forest_data_shred_insert( forest, 8, 7, 0, 0, 0, 0, SHRED_SRC_REPAIR, &mr_8_0, &mr_7_0, fd_tickcount() ); - - fd_forest_blk_insert( forest, 9, 7, NULL ); - fd_forest_data_shred_insert( forest, 9, 7, 0, 0, 0, 0, SHRED_SRC_REPAIR, &mr_9_0, &mr_7_0, fd_tickcount() ); - - /* Confirm slot 7 with a different block id to mark the current FEC - as incorrect. */ - FD_TEST( fd_forest_fec_chain_verify( forest, fd_forest_query( forest, 7 ), &mr_7_1 ) ); - FD_TEST( fd_forest_merkle_last_incorrect_idx( fd_forest_query( forest, 7 ) ) == 0UL ); - - /* Clear the bad FEC. */ - fd_forest_fec_clear( forest, 7, 0, 31 ); - - /* Re-insert the correct FEC with parent=3 (was 6). This triggers - verified_parent_update because confirmed_bid matches mr_7_1 and - the parent changed. - - If sibling pointers are not cleared during detach, blk_insert's - frontier-extension BFS will loop forever here. */ - fd_forest_fec_insert( forest, 7, 3, 31, 0, 1, 0, &mr_7_1, &mr_3, fd_tickcount() ); - - FD_TEST( fd_forest_query( forest, 7 )->parent_slot == 3 ); - FD_TEST( fd_forest_query( forest, 8 ) ); - FD_TEST( fd_forest_query( forest, 9 ) ); - - /* Walk slot 7's children and verify no sibling self-loop. */ - fd_forest_blk_t * pool = fd_forest_pool( forest ); - fd_forest_blk_t * child = fd_forest_pool_ele( pool, fd_forest_query( forest, 7 )->child ); - int child_cnt = 0; - while( child ) { - FD_TEST( child->sibling != fd_forest_pool_idx( pool, child ) ); - child = fd_forest_pool_ele( pool, child->sibling ); - child_cnt++; - FD_TEST( child_cnt <= 2 ); - } - FD_TEST( child_cnt == 2 ); - - FD_TEST( !fd_forest_verify( forest ) ); - fd_forest_print( forest ); - - fd_wksp_free_laddr( fd_forest_delete( fd_forest_leave( forest ) ) ); -} - -void -test_eqvoc_different_slot_size( fd_wksp_t * wksp ) { - /* Equivocating slot where the bad version has a DIFFERENT number of - FEC sets (and a different parent) than the correct version. - - Bad version of slot 3: 1 FEC set (complete_idx=31), parent=1 - Correct version of slot 3: 3 FEC sets (complete_idx=95), parent=2 - - The merkle chain for the correct version: - - FEC 0 (mr=mr_3_0c, cmr=mr_2c) <- chains to parent slot 2 - FEC 1 (mr=mr_3_1c, cmr=mr_3_0c) <- chains to FEC 0 - FEC 2 (mr=mr_3_2c, cmr=mr_3_1c) <- chains to FEC 1, last FEC - block_id = mr_3_2c - - We receive the bad version first (1 FEC, parent=1), then get a - confirmation with the correct block_id (mr_3_2c). - - fec_chain_verify will fail because the bad FEC 0's mr doesn't match - the correct block_id. But it sets lowest_verified_fec = complete_idx/32 + 1 - = 0/32 + 1 = 1 BEFORE checking, using the bad version's complete_idx. - - After fec_clear, complete_idx is still 31 (stale) and - lowest_verified_fec is still 1 (stale). - - When correct shreds for FEC 0 arrive, merkle_verified(ele, 1) - returns true (stale lowest_verified_fec=1 <= 1). The code then - checks confirmed_bid against the incoming mr, but because - complete_idx is stale (31 -> fec_idx 0 == complete_idx/32), - it compares against confirmed_bid (which is mr_3_2c, the block_id - for the LAST fec in the correct version) instead of the CMR that - actually points to FEC 0. - - This causes CORRECT shreds to be REJECTED. */ - - ulong ele_max = 16; - void * mem = fd_wksp_alloc_laddr( wksp, fd_forest_align(), fd_forest_footprint( ele_max, FD_SHRED_BLK_MAX ), 1UL ); - FD_TEST( mem ); - fd_forest_t * forest = fd_forest_join( fd_forest_new( mem, ele_max, FD_SHRED_BLK_MAX, 42UL /* seed */ ) ); - fd_forest_init( forest, 0 ); - - fd_hash_t mr_0 = (fd_hash_t){ .key = { 0 } }; - fd_hash_t mr_1 = (fd_hash_t){ .key = { 1 } }; - fd_hash_t mr_2 = (fd_hash_t){ .key = { 2 } }; - /* Merkle roots for the correct version of slot 3 (3 FEC sets). - Chain: bid -> FEC2 -> FEC1 -> FEC0 -> parent_cmr */ - fd_hash_t mr_2c = (fd_hash_t){ .key = { 20 } }; /* cmr that FEC 0 chains to (parent slot 2's block_id) */ - fd_hash_t mr_3_0c = (fd_hash_t){ .key = { 30 } }; /* correct FEC 0 mr */ - fd_hash_t mr_3_1c = (fd_hash_t){ .key = { 31 } }; /* correct FEC 1 mr */ - fd_hash_t mr_3_2c = (fd_hash_t){ .key = { 32 } }; /* correct FEC 2 mr = block_id */ - - /* Merkle roots for the BAD version of slot 3 (1 FEC set) */ - fd_hash_t mr_3_bad = (fd_hash_t){ .key = { 99 } }; /* bad FEC 0 mr */ - - /* Set up ancestry: root=0, slot 1, slot 2 */ - fd_forest_blk_insert( forest, 1, 0, NULL ); - fd_forest_data_shred_insert( forest, 1, 0, 31, 0, 1, 0, SHRED_SRC_REPAIR, &mr_1, &mr_0, fd_tickcount() ); - fd_forest_blk_insert( forest, 2, 1, NULL ); - fd_forest_data_shred_insert( forest, 2, 1, 31, 0, 1, 0, SHRED_SRC_REPAIR, &mr_2, &mr_1, fd_tickcount() ); - - /* Insert bad version of slot 3: 1 FEC set, parent=1, complete_idx=31 */ - fd_forest_blk_insert( forest, 3, 1, NULL ); - fd_forest_fec_insert( forest, 3, 1, 31, 0, 1, 0, &mr_3_bad, &mr_1, fd_tickcount() ); - - fd_forest_blk_t * ele = fd_forest_query( forest, 3 ); - FD_TEST( ele->complete_idx == 31 ); - - /* Confirm with the correct block_id (mr of the correct version's - last FEC set). fec_chain_verify should fail because the bad FEC 0's - mr doesn't match the correct block_id. */ - ele = fd_forest_fec_chain_verify( forest, fd_forest_query( forest, 3 ), &mr_3_2c ); - FD_TEST( ele ); /* should fail */ - FD_TEST( ele == fd_forest_query( forest, 3 ) ); - FD_TEST( fd_forest_merkle_last_incorrect_idx( ele ) == 0UL ); - - FD_TEST( ele->lowest_verified_fec == UINT_MAX ); /* set before verification */ - FD_TEST( ele->complete_idx == 31 ); /* from bad version */ - - /* Now clear the bad FEC 0 */ - fd_forest_fec_clear( forest, 3, 0, 31 ); - - FD_TEST( ele->complete_idx == UINT_MAX ); /* cleared by fec_clear (used to be stale!) */ - FD_TEST( ele->lowest_verified_fec == UINT_MAX ); /* still UINT_MAX, but maybe need a test where this gets cleared. */ - - /* Now try to insert a correct shred for FEC 0 with the correct - parent. This SHOULD succeed because we cleared the bad FEC and the - correct shred has a valid merkle root. But the stale - lowest_verified_fec and complete_idx cause merkle_verified(ele, 1) - to return true, and then the code compares confirmed_bid (mr_3_2c, - block_id of the correct LAST fec) against mr_3_0c (the correct FEC - 0 mr). They don't match, so the correct shred is REJECTED. BUG: - result is NULL because the correct shred was rejected. After the - fix, this should be non-NULL. */ - - ele = fd_forest_data_shred_insert( forest, 3, 2, 0, 0, 0, 0, SHRED_SRC_REPAIR, &mr_3_0c, &mr_2c, fd_tickcount() ); - FD_TEST( ele ); - FD_TEST( ele->parent_slot == 2 ); /* parent_slot changes on shred in fec 0 */ - FD_TEST( fd_hash_eq( &fd_forest_blk_mroots( forest, ele )[0].mr, &mr_3_0c ) ); - - /* shred in the last FEC set doesn't do anything because we still don't know complete_idx */ - FD_TEST( fd_forest_data_shred_insert( forest, 3, 2, 94, 64, 0, 0, SHRED_SRC_REPAIR, &mr_3_2c, &mr_3_1c, fd_tickcount() ) ); - FD_TEST( ele->lowest_verified_fec == UINT_MAX ); - FD_TEST( ele->parent_slot == 2 ); - - FD_TEST( fd_forest_data_shred_insert( forest, 3, 2, 95, 64, 1, 0, SHRED_SRC_REPAIR, &mr_3_2c, &mr_3_1c, fd_tickcount() ) ); - FD_TEST( ele->lowest_verified_fec == 2 ); - FD_TEST( ele->complete_idx == 95 ); - FD_TEST( ele->parent_slot == 2 ); - - fd_wksp_free_laddr( fd_forest_delete( fd_forest_leave( forest ) ) ); -} - -void -test_fec_complete_no_poison_verified( fd_wksp_t * wksp ) { - /* A conflicting FEC_COMPLETE arrives for an FEC index that has - already been chain-verified. The merkle root metadata doesn't get - overwritten. */ - ulong ele_max = 16; - void * mem = fd_wksp_alloc_laddr( wksp, fd_forest_align(), fd_forest_footprint( ele_max, FD_SHRED_BLK_MAX ), 1UL ); - FD_TEST( mem ); - fd_forest_t * forest = fd_forest_join( fd_forest_new( mem, ele_max, FD_SHRED_BLK_MAX, 42UL ) ); - fd_forest_init( forest, 0 ); - - fd_hash_t mr_0 = (fd_hash_t){ .key = { 0 } }; - fd_hash_t mr_1 = (fd_hash_t){ .key = { 1 } }; - fd_hash_t mr_2 = (fd_hash_t){ .key = { 2 } }; - fd_hash_t mr_3_0 = (fd_hash_t){ .key = { 30 } }; /* correct FEC 0 mr */ - fd_hash_t mr_3_1 = (fd_hash_t){ .key = { 31 } }; /* correct FEC 1 mr = block_id */ - - /* Conflicting FEC 1 merkle roots */ - fd_hash_t mr_3_1_bad = (fd_hash_t){ .key = { 99 } }; - fd_hash_t cmr_bad = (fd_hash_t){ .key = { 98 } }; - - /* Set up ancestry: root=0 -> slot 1 -> slot 2 -> slot 3 */ - fd_forest_blk_insert( forest, 1, 0, NULL ); - fd_forest_data_shred_insert( forest, 1, 0, 31, 0, 1, 0, SHRED_SRC_REPAIR, &mr_1, &mr_0, fd_tickcount() ); - fd_forest_blk_insert( forest, 2, 1, NULL ); - fd_forest_data_shred_insert( forest, 2, 1, 31, 0, 1, 0, SHRED_SRC_REPAIR, &mr_2, &mr_1, fd_tickcount() ); - - /* Insert correct version of slot 3: 2 FEC sets, parent=2 */ - fd_forest_blk_insert( forest, 3, 2, NULL ); - /* FEC 0: shreds 0-31 */ - fd_forest_fec_insert( forest, 3, 2, 31, 0, 0, 0, &mr_3_0, &mr_2, fd_tickcount() ); - /* FEC 1: shreds 32-63, slot_complete=1 */ - fd_forest_fec_insert( forest, 3, 2, 63, 32, 1, 0, &mr_3_1, &mr_3_0, fd_tickcount() ); - - fd_forest_blk_t * ele = fd_forest_query( forest, 3 ); - FD_TEST( ele->complete_idx == 63 ); - - /* Chain-verify with the correct block_id. Both FEC 1 and FEC 0 - should be verified, and chain_confirmed should be set. */ - fd_forest_blk_t * fail = fd_forest_fec_chain_verify( forest, ele, &mr_3_1 ); - FD_TEST( !fail ); /* NULL means full success */ - FD_TEST( ele->chain_confirmed ); - FD_TEST( ele->lowest_verified_fec == 0 ); - - fd_hash_t saved_mr = fd_forest_blk_mroots( forest, ele )[1].mr; - fd_hash_t saved_cmr = fd_forest_blk_mroots( forest, ele )[1].cmr; - FD_TEST( fd_hash_eq( &saved_mr, &mr_3_1 ) ); - - /* Now a conflicting FEC_COMPLETE arrives for FEC 1 (fec_set_idx=32) - with a different merkle root. This is the bug scenario: without - the fix, this would overwrite the verified merkle entry. */ - fd_forest_fec_insert( forest, 3, 2, 63, 32, 1, 0, &mr_3_1_bad, &cmr_bad, fd_tickcount() ); - - /* The verified merkle root must NOT have been overwritten */ - FD_TEST( fd_hash_eq( &fd_forest_blk_mroots( forest, ele )[1].mr, &saved_mr ) ); - FD_TEST( fd_hash_eq( &fd_forest_blk_mroots( forest, ele )[1].cmr, &saved_cmr ) ); - - /* The block should still be chain-confirmed */ - FD_TEST( ele->chain_confirmed ); - FD_TEST( ele->lowest_verified_fec == 0 ); - - FD_TEST( !fd_forest_verify( forest ) ); - - fd_wksp_free_laddr( fd_forest_delete( fd_forest_leave( forest ) ) ); -} - - -/* test_buffered_idx_oob - - fd_forest_data_shred_insert used to OOB read on the idxs bitmap when - buffered_idx reaches FD_SHRED_BLK_MAX - 1 (16383). - - The while loop: - while( fd_forest_blk_idxs_test( ele->idxs, ele->buffered_idx + 1U ) ) - ele->buffered_idx++; - - Could end up testing into the in merkle_roots array. If that memory - has bit 0 set, buffered_idx advances to 16384 or beyond, corrupting - the slot. - - Trigger: fill a slot to max capacity (16384 shreds across 512 FEC - sets), then insert an equivocating data shred. The equivocation sets - merkle_roots[0].mr = invalid_mr = {ULONG_MAX, ...}, whose bit 0 is - set, causing the OOB read to return true. */ - -static void -test_buffered_idx_oob( fd_wksp_t * wksp ) { - - ulong ele_max = 16; - void * mem = fd_wksp_alloc_laddr( wksp, fd_forest_align(), fd_forest_footprint( ele_max, FD_SHRED_BLK_MAX ), 1UL ); - FD_TEST( mem ); - fd_forest_t * forest = fd_forest_join( fd_forest_new( mem, ele_max, FD_SHRED_BLK_MAX, 42UL /* seed */ ) ); - fd_forest_init( forest, 0 ); - - ulong slot = 2; - ulong parent_slot = 0; - - fd_forest_blk_insert( forest, slot, parent_slot, NULL ); - - /* Fill every FEC set (0 .. FD_FEC_BLK_MAX-1). Each FEC set covers - 32 shred indices. The last FEC set marks slot_complete. */ - - for( uint fec = 0; fec < FD_FEC_BLK_MAX; fec++ ) { - uint fec_set_idx = fec * FD_FEC_SHRED_CNT; - uint last_shred_idx = fec_set_idx + FD_FEC_SHRED_CNT - 1; - int slot_complete = (fec == FD_FEC_BLK_MAX - 1); - - /* Each FEC gets a unique merkle root so they're all distinct. */ - fd_hash_t mr = {0}; mr.ul[0] = fec + 1; - fd_hash_t cmr = {0}; cmr.ul[0] = fec; /* chained to previous FEC's mr */ - - fd_forest_fec_insert( forest, slot, parent_slot, - last_shred_idx, fec_set_idx, slot_complete, 0, - &mr, &cmr, fd_tickcount() ); - } - - /* the slot is fully buffered and complete. */ - fd_forest_blk_t * blk = fd_forest_query( forest, slot ); - FD_TEST( blk ); - FD_TEST( blk->complete_idx == FD_SHRED_BLK_MAX - 1 ); - FD_TEST( blk->buffered_idx == FD_SHRED_BLK_MAX - 1 ); /* originally merkle root[0] causes buffered_idx to increment to FD_SHRED_BLK_MAX */ - - /* Now insert an equivocating data shred at index 0 with a DIFFERENT - merkle root. This triggers: - merkle_roots[0].mr = invalid_mr = { ULONG_MAX, ULONG_MAX, ... } - which puts all-ones into the memory word immediately past idxs[]. */ - - fd_hash_t bad_mr = {0}; bad_mr.ul[0] = 0xBAD; - fd_hash_t bad_cmr = {0}; bad_cmr.ul[0] = 0xBAD; - - fd_forest_data_shred_insert( forest, slot, parent_slot, - 0, /* shred_idx */ - 0, /* fec_set_idx */ - 0, /* slot_complete */ - 0, /* ref_tick */ - SHRED_SRC_TURBINE, - &bad_mr, &bad_cmr, fd_tickcount() ); - - blk = fd_forest_query( forest, slot ); - FD_TEST( blk ); - - /* buffered_idx should still be FD_SHRED_BLK_MAX - 1 */ - FD_TEST( blk->buffered_idx == FD_SHRED_BLK_MAX - 1 ); - FD_TEST( fd_forest_blk_mroots( forest, blk )[0].mr.ul[0] == ULONG_MAX ); - - /* no longer blocks consumed-frontier advancement and FEC chain verification. */ - FD_TEST( blk->buffered_idx == blk->complete_idx ); -} - - -void -test_fec_insert_reject_oob_after_verify( fd_wksp_t * wksp ) { - /* After a slot is fully received and chain-verified, an attacker - sends a FEC set with the maximum possible fec_set_idx (well beyond - complete_idx). The FEC is rejected because fec_set_idx > complete_idx. */ - ulong ele_max = 16; - void * mem = fd_wksp_alloc_laddr( wksp, fd_forest_align(), fd_forest_footprint( ele_max, FD_SHRED_BLK_MAX ), 1UL ); - FD_TEST( mem ); - fd_forest_t * forest = fd_forest_join( fd_forest_new( mem, ele_max, FD_SHRED_BLK_MAX, 42UL ) ); - fd_forest_init( forest, 0 ); - - fd_hash_t mr_0 = (fd_hash_t){ .key = { 0 } }; - fd_hash_t mr_1 = (fd_hash_t){ .key = { 1 } }; - fd_hash_t mr_2 = (fd_hash_t){ .key = { 2 } }; - fd_hash_t mr_3_0 = (fd_hash_t){ .key = { 30 } }; - fd_hash_t mr_3_1 = (fd_hash_t){ .key = { 31 } }; - - /* Build chain: root=0 -> slot 1 -> slot 2 -> slot 3 (2 FEC sets) */ - fd_forest_blk_insert( forest, 1, 0, NULL ); - fd_forest_data_shred_insert( forest, 1, 0, 31, 0, 1, 0, SHRED_SRC_REPAIR, &mr_1, &mr_0, fd_tickcount() ); - fd_forest_blk_insert( forest, 2, 1, NULL ); - fd_forest_data_shred_insert( forest, 2, 1, 31, 0, 1, 0, SHRED_SRC_REPAIR, &mr_2, &mr_1, fd_tickcount() ); - - fd_forest_blk_insert( forest, 3, 2, NULL ); - fd_forest_fec_insert( forest, 3, 2, 31, 0, 0, 0, &mr_3_0, &mr_2, fd_tickcount() ); - fd_forest_fec_insert( forest, 3, 2, 63, 32, 1, 0, &mr_3_1, &mr_3_0, fd_tickcount() ); - - fd_forest_blk_t * ele = fd_forest_query( forest, 3 ); - FD_TEST( ele->complete_idx == 63 ); - - /* Chain-verify slot 3 all the way back. */ - FD_TEST( !fd_forest_fec_chain_verify( forest, ele, &mr_3_1 ) ); - FD_TEST( ele->chain_confirmed ); - FD_TEST( ele->lowest_verified_fec == 0 ); - - /* Attacker sends a FEC with the max possible fec_set_idx. - complete_idx == 63, attacker's fec_set_idx == (511*32) >> 63. - This must be rejected (return NULL). */ - uint attack_fec_set_idx = (FD_FEC_BLK_MAX - 1) * 32; - uint attack_last_shred = attack_fec_set_idx + 31; - fd_hash_t mr_bad = (fd_hash_t){ .key = { 0xBA } }; - fd_hash_t cmr_bad = (fd_hash_t){ .key = { 0xBB } }; - - fd_forest_blk_t * result = fd_forest_fec_insert( forest, 3, 2, attack_last_shred, attack_fec_set_idx, 0, 0, &mr_bad, &cmr_bad, fd_tickcount() ); - FD_TEST( !result ); - - /* Slot 3 state must be untouched. */ - FD_TEST( ele->chain_confirmed ); - FD_TEST( ele->lowest_verified_fec == 0 ); - FD_TEST( ele->complete_idx == 63 ); - - FD_TEST( !fd_forest_verify( forest ) ); - - fd_wksp_free_laddr( fd_forest_delete( fd_forest_leave( forest ) ) ); -} - -void -test_fec_insert_dup_confirm_larger_complete_idx( fd_wksp_t * wksp ) { - /* Attacker sends a false slot_complete shred at shred 31 (1 FEC set), - so complete_idx == 31. Later, duplicate confirmation arrives and - we fec_clear the bad FEC. The canonical version has 2 FEC sets - (complete_idx == 63). After clearing, we must be able to receive - the canonical FEC sets that extend beyond the old complete_idx. - - 0 - | - 2 - | - 3 (attacker: complete_idx=31, canonical: complete_idx=63) - */ - - ulong ele_max = 16; - void * mem = fd_wksp_alloc_laddr( wksp, fd_forest_align(), fd_forest_footprint( ele_max, FD_SHRED_BLK_MAX ), 1UL ); - FD_TEST( mem ); - fd_forest_t * forest = fd_forest_join( fd_forest_new( mem, ele_max, FD_SHRED_BLK_MAX, 42UL ) ); - fd_forest_init( forest, 0 ); - - fd_hash_t mr_0 = (fd_hash_t){ .key = { 0 } }; - fd_hash_t mr_2 = (fd_hash_t){ .key = { 2 } }; - - /* Attacker's version of slot 3: 1 FEC set, slot_complete at shred 31 */ - fd_hash_t mr_3_bad = (fd_hash_t){ .key = { 99 } }; - - /* Canonical version of slot 3: 2 FEC sets */ - fd_hash_t mr_3_0 = (fd_hash_t){ .key = { 30 } }; - fd_hash_t mr_3_1 = (fd_hash_t){ .key = { 31 } }; - - /* Build ancestry: root=0 -> slot 2 (1 FEC) */ - fd_forest_blk_insert( forest, 2, 0, NULL ); - fd_forest_data_shred_insert( forest, 2, 0, 31, 0, 1, 0, SHRED_SRC_REPAIR, &mr_2, &mr_0, fd_tickcount() ); - - /* Receive the attacker's version of slot 3: complete at shred 31 */ - fd_forest_blk_insert( forest, 3, 2, NULL ); - fd_forest_fec_insert( forest, 3, 2, 31, 0, 1, 0, &mr_3_bad, &mr_2, fd_tickcount() ); - - fd_forest_blk_t * ele = fd_forest_query( forest, 3 ); - FD_TEST( ele->complete_idx == 31 ); - - /* Canonical FEC 1 (fec_set_idx=32) should be rejected because - fec_set_idx 32 > complete_idx 31. */ - FD_TEST( !fd_forest_fec_insert( forest, 3, 2, 63, 32, 0, 0, &mr_3_1, &mr_3_0, fd_tickcount() ) ); - - /* Duplicate confirmation arrives: clear the bad FEC (shreds 0-31). - This resets complete_idx to UINT_MAX. */ - FD_TEST( fd_forest_fec_chain_verify( forest, ele, &mr_3_1 ) == ele ); - FD_TEST( fd_forest_merkle_last_incorrect_idx( ele ) == 0 ); - fd_forest_fec_clear( forest, 3, 0, 31 ); - FD_TEST( ele->complete_idx == UINT_MAX ); - - /* Try inserting the bad version again. It should be rejected. */ - FD_TEST( !fd_forest_fec_insert( forest, 3, 2, 31, 0, 1, 0, &mr_3_bad, &mr_2, fd_tickcount() ) ); - /* Now we can receive the canonical version: 2 FEC sets */ - FD_TEST( fd_forest_fec_insert( forest, 3, 2, 31, 0, 0, 0, &mr_3_0, &mr_2, fd_tickcount() ) ); - FD_TEST( fd_forest_fec_insert( forest, 3, 2, 63, 32, 1, 0, &mr_3_1, &mr_3_0, fd_tickcount() ) ); - FD_TEST( ele->complete_idx == 63 ); - - /* Chain-verify succeeds */ - FD_TEST( !fd_forest_fec_chain_verify( forest, ele, &mr_3_1 ) ); - FD_TEST( ele->chain_confirmed ); - FD_TEST( ele->lowest_verified_fec == 0 ); - - FD_TEST( !fd_forest_verify( forest ) ); - - fd_wksp_free_laddr( fd_forest_delete( fd_forest_leave( forest ) ) ); -} - - - -void -test_data_shred_insert_reject_oob_confirmed_complete( fd_wksp_t * wksp ) { - /* The attacker's version of slot 3 completes at shred 31. After - confirmation with the canonical block_id, the canonical - slot_complete shred (shred 95, FEC 2) arrives and matches - confirmed_bid but is beyond complete_idx. It must be rejected - without touching lowest_verified_fec, else the last incorrect FEC - is computed as 32 (an empty FEC above complete_idx) instead of 0, - and fec_clear never resets complete_idx. */ - - ulong ele_max = 16; - void * mem = fd_wksp_alloc_laddr( wksp, fd_forest_align(), fd_forest_footprint( ele_max, FD_SHRED_BLK_MAX ), 1UL ); - FD_TEST( mem ); - fd_forest_t * forest = fd_forest_join( fd_forest_new( mem, ele_max, FD_SHRED_BLK_MAX, 42UL ) ); - fd_forest_init( forest, 0 ); - - fd_hash_t mr_0 = (fd_hash_t){ .key = { 0 } }; - fd_hash_t mr_2 = (fd_hash_t){ .key = { 2 } }; - fd_hash_t mr_3_bad = (fd_hash_t){ .key = { 99 } }; - fd_hash_t mr_3_1 = (fd_hash_t){ .key = { 31 } }; - fd_hash_t mr_3_2 = (fd_hash_t){ .key = { 32 } }; /* canonical block_id */ - - fd_forest_blk_insert( forest, 2, 0, NULL ); - fd_forest_data_shred_insert( forest, 2, 0, 31, 0, 1, 0, SHRED_SRC_REPAIR, &mr_2, &mr_0, fd_tickcount() ); - - fd_forest_blk_insert( forest, 3, 2, NULL ); - fd_forest_fec_insert( forest, 3, 2, 31, 0, 1, 0, &mr_3_bad, &mr_2, fd_tickcount() ); - fd_forest_blk_t * ele = fd_forest_query( forest, 3 ); - FD_TEST( ele->complete_idx == 31 ); - - FD_TEST( fd_forest_fec_chain_verify( forest, ele, &mr_3_2 ) == ele ); - FD_TEST( fd_hash_eq( &ele->confirmed_bid, &mr_3_2 ) ); - - FD_TEST( !fd_forest_data_shred_insert( forest, 3, 2, 95, 64, 1, 0, SHRED_SRC_REPAIR, &mr_3_2, &mr_3_1, fd_tickcount() ) ); - FD_TEST( ele->lowest_verified_fec == UINT_MAX ); - FD_TEST( ele->complete_idx == 31 ); - FD_TEST( fd_hash_eq( &fd_forest_blk_mroots( forest, ele )[2].mr, &(fd_hash_t){ 0 } ) ); - - FD_TEST( fd_forest_fec_chain_verify( forest, ele, &mr_3_2 ) == ele ); - FD_TEST( fd_forest_merkle_last_incorrect_idx( ele ) == 0 ); - fd_forest_fec_clear( forest, 3, 0, 31 ); - FD_TEST( ele->complete_idx == UINT_MAX ); - - FD_TEST( fd_forest_data_shred_insert( forest, 3, 2, 95, 64, 1, 0, SHRED_SRC_REPAIR, &mr_3_2, &mr_3_1, fd_tickcount() ) ); - FD_TEST( ele->complete_idx == 95 ); - FD_TEST( ele->lowest_verified_fec == 2 ); - - fd_wksp_free_laddr( fd_forest_delete( fd_forest_leave( forest ) ) ); -} - -static void -test_sentinel_parent_update_orphreqs_leak( fd_wksp_t * wksp ) { - /* test_sentinel_parent_update_orphreqs_leak - - Regression test for a stale orphan-request entry left behind when a - sentinel block's parent is updated. - - A sentinel block (blk_insert with parent_slot == ULONG_MAX) is created - as the head of an orphan subtree and therefore lives in the orphan - request list (orphreqs / orphlist). When a later blk_insert supplies - the real parent slot, forest removes the block from the subtrees / - orphaned maps and re-links it - here into the main tree as a frontier - child of the root. Previously the block was NOT removed from the - orphan request list during this transition. - - Because the main request list and the orphan request list share a - single reqspool, the block then ended up referenced by both the main - request map and the orphan request map (dual membership). The first - removal from either list releases the shared pool ref and corrupts the - other list - which only surfaces much later (e.g. during a publish) as - a request-list entry pointing at a freed / reused pool slot. */ - ulong ele_max = 8; - void * mem = fd_wksp_alloc_laddr( wksp, fd_forest_align(), fd_forest_footprint( ele_max, FD_SHRED_BLK_MAX ), 1UL ); - FD_TEST( mem ); - fd_forest_t * forest = fd_forest_join( fd_forest_new( mem, ele_max, FD_SHRED_BLK_MAX, 0UL ) ); - fd_forest_init( forest, 0 ); - - fd_forest_blk_t * pool = fd_forest_pool( forest ); - fd_forest_requests_t * requests = fd_forest_requests( forest ); - fd_forest_requests_t * orphreqs = fd_forest_orphreqs( forest ); - fd_forest_ref_t * reqspool = fd_forest_reqspool( forest ); - - /* Create slot 10 as a sentinel orphan (unknown parent). It becomes a - subtree head and is added to the orphan request list. */ - ulong evicted = ULONG_MAX; - FD_TEST( fd_forest_blk_insert( forest, 10, ULONG_MAX, &evicted ) ); - ulong idx10 = slot_idx( forest, 10 ); - FD_TEST( fd_forest_subtrees_ele_query( fd_forest_subtrees( forest ), (ulong[]){10}, NULL, pool ) ); - FD_TEST( fd_forest_requests_ele_query( orphreqs, &idx10, NULL, reqspool ) ); /* in orphan reqs */ - FD_TEST( !fd_forest_requests_ele_query( requests, &idx10, NULL, reqspool ) ); /* not in main reqs */ - FD_TEST( !fd_forest_verify( forest ) ); - - /* Now supply slot 10's real parent (0, the root). This takes the - sentinel parent-update path: slot 10 is removed from subtrees and - re-linked into the main tree as a frontier child of the root, and - gets added to the main request list. */ - FD_TEST( fd_forest_blk_insert( forest, 10, 0, &evicted ) ); - FD_TEST( idx10 == slot_idx( forest, 10 ) ); /* same pool element */ - - /* Slot 10 is now a main-tree node: it must be in the main request map - and must NOT remain in the orphan request map. */ - FD_TEST( fd_forest_frontier_ele_query( fd_forest_frontier( forest ), (ulong[]){10}, NULL, pool ) ); - FD_TEST( fd_forest_requests_ele_query( requests, &idx10, NULL, reqspool ) ); /* in main reqs */ - FD_TEST( !fd_forest_requests_ele_query( orphreqs, &idx10, NULL, reqspool ) ); /* NOT in orphreqs */ - - /* And the structure as a whole must be consistent. */ - FD_TEST( !fd_forest_verify( forest ) ); - - fd_wksp_free_laddr( fd_forest_delete( fd_forest_leave( forest ) ) ); -} - -/* shred_max is a runtime value: a forest sized for 4x FD_SHRED_BLK_MAX - accepts a block that fills every extra FEC set, and rejects shred - idxs / fec_set_idxs at or beyond shred_max instead of aborting. Also - checks the production footprint equals the pre-side-array layout - (blk 256 B + idxs 2 KiB + code 2 KiB + mroots 32 KiB + recv 1 KiB - = 38144 B/blk). */ - -static void -test_shred_max_runtime( fd_wksp_t * wksp ) { - ulong ele_max = 4UL; - ulong shred_max = 4UL*FD_SHRED_BLK_MAX; - ulong fec_cnt = shred_max/FD_FEC_SHRED_CNT; - - ulong fp_prod = fd_forest_footprint( ele_max, FD_SHRED_BLK_MAX ); - ulong fp_big = fd_forest_footprint( ele_max, shred_max ); - FD_TEST( sizeof(fd_forest_blk_t)==256UL ); - FD_TEST( fp_big-fp_prod==ele_max*3UL*(2048UL+2048UL+32768UL+1024UL) ); - - void * mem = fd_wksp_alloc_laddr( wksp, fd_forest_align(), fp_big, 1UL ); - FD_TEST( mem ); - FD_TEST( !fd_forest_new( mem, ele_max, FD_SHRED_BLK_MAX+1UL, 0UL ) ); /* not a FEC multiple */ - fd_forest_t * forest = fd_forest_join( fd_forest_new( mem, ele_max, shred_max, 0UL ) ); - FD_TEST( forest && forest->shred_max==shred_max ); - fd_forest_init( forest, 0 ); - - fd_forest_blk_insert( forest, 1, 0, NULL ); - fd_forest_blk_insert( forest, 2, 1, NULL ); - fd_forest_blk_t * blk1 = fd_forest_query( forest, 1 ); - fd_forest_blk_t * blk2 = fd_forest_query( forest, 2 ); - FD_TEST( fd_forest_blk_idxs ( forest, blk2 )-fd_forest_blk_idxs ( forest, blk1 )==(long)(shred_max/64UL) ); - FD_TEST( fd_forest_blk_mroots( forest, blk2 )-fd_forest_blk_mroots( forest, blk1 )==(long)fec_cnt ); - - /* A code shred past the limit only counts toward turbine_cnt. */ - fd_hash_t mr = { .ul = { 1 } }; fd_hash_t cmr = { .ul = { 0 } }; - FD_TEST( fd_forest_code_shred_insert( forest, 1, (uint)shred_max, 0L )==blk1 && blk1->turbine_cnt==1U ); - FD_TEST( !fd_forest_blk_idxs_cnt( fd_forest_blk_code( forest, blk1 ), shred_max/64UL ) ); - - /* Fill every FEC set of slot 1 beyond the production bound. */ - for( uint fec=0; feccomplete_idx==shred_max-1UL ); - FD_TEST( blk1->buffered_idx==shred_max-1UL ); - FD_TEST( fd_forest_blk_idxs_cnt( fd_forest_blk_idxs( forest, blk1 ), shred_max/64UL )==shred_max ); - FD_TEST( fd_forest_blk_mroots( forest, blk1 )[ fec_cnt-1UL ].mr.ul[0]==fec_cnt ); - FD_TEST( fd_forest_blk_mroots( forest, blk2 )[ 0 ].mr.ul[0]==0UL ); /* neighbor untouched */ - FD_TEST( !fd_forest_verify( forest ) ); - - fd_wksp_free_laddr( fd_forest_delete( fd_forest_leave( forest ) ) ); -} - -int -main( int argc, char ** argv ) { - fd_boot( &argc, &argv ); - - char const * _page_sz = fd_env_strip_cmdline_cstr ( &argc, &argv, "--page-sz", NULL, "gigantic" ); - ulong page_cnt = fd_env_strip_cmdline_ulong( &argc, &argv, "--page-cnt", NULL, 1UL ); - ulong numa_idx = fd_env_strip_cmdline_ulong( &argc, &argv, "--numa-idx", NULL, fd_shmem_numa_idx( 0UL ) ); - fd_wksp_t * wksp = fd_wksp_new_anonymous( fd_cstr_to_shmem_page_sz( _page_sz ), page_cnt, fd_shmem_cpu_idx( numa_idx ), "wksp", 0UL ); - FD_TEST( wksp ); - - test_buffered_idx_oob( wksp ); - test_shred_max_runtime( wksp ); - test_invalid_frontier_insert( wksp ); - test_publish( wksp ); - test_publish_incremental( wksp ); - test_out_of_order( wksp ); - test_forks( wksp ); - test_print_tree( wksp ); - //test_large_print_tree( wksp); - test_linear_forest_iterator( wksp ); - test_iter_random_bitsets( wksp ); - test_branched_forest_iterator( wksp ); - test_frontier( wksp ); - test_fec_clear( wksp ); - test_iter_publish( wksp ); - test_iter_subtree( wksp ); - test_orphan_requests( wksp ); - test_slot_clear( wksp ); - test_eviction_simple( wksp ); - test_eviction_confirmations( wksp ); - test_eviction_confirmed_orphan_gca( wksp ); - test_eviction_confirmed_bid_orphan( wksp ); - test_verify_orphans( wksp ); - test_eviction_deep( wksp ); - test_sentinel_blocks( wksp ); - test_eqvoc_blk_wrong_parent( wksp ); - test_parent_update( wksp ); - test_buffered_idx_gt_complete_idx( wksp ); - test_verified_parent_update_stale_sibling( wksp ); - test_eqvoc_different_slot_size( wksp ); - test_fec_complete_no_poison_verified( wksp ); - test_fec_insert_reject_oob_after_verify( wksp ); - test_fec_insert_dup_confirm_larger_complete_idx( wksp ); - test_data_shred_insert_reject_oob_confirmed_complete( wksp ); - test_sentinel_parent_update_orphreqs_leak( wksp ); - - FD_LOG_NOTICE(( "pass" )); - fd_halt(); - return 0; -} diff --git a/src/discof/reasm/Local.mk b/src/discof/reasm/Local.mk deleted file mode 100644 index d3794d56c06..00000000000 --- a/src/discof/reasm/Local.mk +++ /dev/null @@ -1,6 +0,0 @@ -$(call add-objs,fd_reasm,fd_discof) -ifdef FD_HAS_HOSTED -$(call make-unit-test,test_reasm,test_reasm,fd_discof fd_disco fd_flamenco fd_ballet fd_util) -$(call run-unit-test,test_reasm) -$(call make-fuzz-test,fuzz_reasm,fuzz_reasm,fd_discof fd_disco fd_ballet fd_util) -endif diff --git a/src/discof/reasm/fd_reasm.c b/src/discof/reasm/fd_reasm.c deleted file mode 100644 index c21976fe4cb..00000000000 --- a/src/discof/reasm/fd_reasm.c +++ /dev/null @@ -1,1135 +0,0 @@ -#include "fd_reasm.h" -#include "fd_reasm_private.h" -#include "../../disco/shred/fd_fec_set.h" - -#define LOGGING 0 - -FD_FN_CONST ulong -fd_reasm_align( void ) { - return alignof(fd_reasm_t); -} - -FD_FN_CONST ulong -fd_reasm_footprint( ulong fec_max ) { - ulong max_slots = fd_ulong_max(fec_max / FD_FEC_BLK_MAX, 1UL); /* add capacity for a block id per slot */ - ulong chain_cnt = ancestry_chain_cnt_est( fec_max ); /* estimated buckets for ancestry map */ - int lgf_max = fd_ulong_find_msb( fd_ulong_pow2_up( fec_max + max_slots ) ); /* capacity for fec_max fecs + (fec_max / 1024) more block ids */ - return FD_LAYOUT_FINI( - FD_LAYOUT_APPEND( - FD_LAYOUT_APPEND( - FD_LAYOUT_APPEND( - FD_LAYOUT_APPEND( - FD_LAYOUT_APPEND( - FD_LAYOUT_APPEND( - FD_LAYOUT_APPEND( - FD_LAYOUT_APPEND( - FD_LAYOUT_INIT, - alignof(fd_reasm_t), sizeof(fd_reasm_t) ), - pool_align(), pool_footprint ( fec_max ) ), - ancestry_align(), ancestry_footprint( chain_cnt ) ), - frontier_align(), frontier_footprint( chain_cnt ) ), - orphaned_align(), orphaned_footprint( chain_cnt ) ), - subtrees_align(), subtrees_footprint( chain_cnt ) ), - bfs_align(), bfs_footprint ( fec_max ) ), - xid_align(), xid_footprint ( lgf_max ) ), - fd_reasm_align() ); -} - -void * -fd_reasm_new( void * shmem, - ulong fec_max, - ulong seed ) { - - if( FD_UNLIKELY( !shmem ) ) { - FD_LOG_WARNING(( "NULL mem" )); - return NULL; - } - - if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)shmem, fd_reasm_align() ) ) ) { - FD_LOG_WARNING(( "misaligned mem" )); - return NULL; - } - - ulong footprint = fd_reasm_footprint( fec_max ); - if( FD_UNLIKELY( !footprint ) ) { - FD_LOG_WARNING(( "bad fec_max (%lu)", fec_max )); - return NULL; - } - - fd_wksp_t * wksp = fd_wksp_containing( shmem ); - if( FD_UNLIKELY( !wksp ) ) { - FD_LOG_WARNING(( "shmem must be part of a workspace" )); - return NULL; - } - - fd_memset( shmem, 0, footprint ); - - ulong max_slots = fd_ulong_max(fec_max / FD_FEC_BLK_MAX, 1UL); /* add capacity for a block id per slot */ - int lgf_max = fd_ulong_find_msb( fd_ulong_pow2_up( fec_max + max_slots ) ); /* capacity for fec_max fecs + (fec_max / 1024) more block ids */ - ulong chain_cnt = ancestry_chain_cnt_est( fec_max ); /* estimated buckets for ancestry map */ - - fd_reasm_t * reasm; - FD_SCRATCH_ALLOC_INIT( l, shmem ); - reasm = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_reasm_t), sizeof(fd_reasm_t) ); - void * pool = FD_SCRATCH_ALLOC_APPEND( l, pool_align(), pool_footprint ( fec_max ) ); - void * ancestry = FD_SCRATCH_ALLOC_APPEND( l, ancestry_align(), ancestry_footprint( chain_cnt ) ); - void * frontier = FD_SCRATCH_ALLOC_APPEND( l, frontier_align(), frontier_footprint( chain_cnt ) ); - void * orphaned = FD_SCRATCH_ALLOC_APPEND( l, orphaned_align(), orphaned_footprint( chain_cnt ) ); - void * subtrees = FD_SCRATCH_ALLOC_APPEND( l, subtrees_align(), subtrees_footprint( chain_cnt ) ); - void * bfs = FD_SCRATCH_ALLOC_APPEND( l, bfs_align(), bfs_footprint ( fec_max ) ); - void * xid = FD_SCRATCH_ALLOC_APPEND( l, xid_align(), xid_footprint ( lgf_max ) ); - FD_TEST( FD_SCRATCH_ALLOC_FINI( l, fd_reasm_align() ) == (ulong)shmem + footprint ); - - reasm->slot0 = ULONG_MAX; - reasm->root = pool_idx_null( pool ); - reasm->pool_gaddr = fd_wksp_gaddr_fast( wksp, pool_join( pool_new( pool, fec_max ) ) ); - reasm->wksp_gaddr = fd_wksp_gaddr_fast( wksp, reasm ); - reasm->ancestry = ancestry_new( ancestry, chain_cnt, seed ); - reasm->frontier = frontier_new( frontier, chain_cnt, seed ); - reasm->orphaned = orphaned_new( orphaned, chain_cnt, seed ); - reasm->subtrees = subtrees_new( subtrees, chain_cnt, seed ); - reasm->subtreel = subtreel_new( reasm->_subtrlf ); - reasm->out = out_new ( reasm->_out ); - reasm->bfs = bfs_new ( bfs, fec_max ); - reasm->xid = xid_new ( xid, lgf_max, seed ); - - return shmem; -} - -fd_reasm_t * -fd_reasm_join( void * shreasm ) { - fd_reasm_t * reasm = (fd_reasm_t *)shreasm; - - if( FD_UNLIKELY( !reasm ) ) { - FD_LOG_WARNING(( "NULL reasm" )); - return NULL; - } - /* pool join handled in fd_reasm_new */ - reasm->ancestry = ancestry_join( reasm->ancestry ); - reasm->frontier = frontier_join( reasm->frontier ); - reasm->orphaned = orphaned_join( reasm->orphaned ); - reasm->subtrees = subtrees_join( reasm->subtrees ); - reasm->subtreel = subtreel_join( reasm->_subtrlf ); - reasm->out = out_join ( reasm->_out ); - reasm->bfs = bfs_join ( reasm->bfs ); - reasm->xid = xid_join ( reasm->xid ); - - return reasm; -} - -void * -fd_reasm_leave( fd_reasm_t * reasm ) { - - if( FD_UNLIKELY( !reasm ) ) { - FD_LOG_WARNING(( "NULL reasm" )); - return NULL; - } - - return (void *)reasm; -} - -void * -fd_reasm_delete( void * shreasm ) { - fd_reasm_t * reasm = (fd_reasm_t *)shreasm; - - if( FD_UNLIKELY( !reasm ) ) { - FD_LOG_WARNING(( "NULL reasm" )); - return NULL; - } - - if( FD_UNLIKELY( !fd_ulong_is_aligned((ulong)reasm, fd_reasm_align() ) ) ) { - FD_LOG_WARNING(( "misaligned reasm" )); - return NULL; - } - - return reasm; -} - -fd_reasm_fec_t * fd_reasm_root ( fd_reasm_t * reasm ) { return pool_ele ( reasm_pool ( reasm ), reasm->root ); } -fd_reasm_fec_t const * fd_reasm_root_const ( fd_reasm_t const * reasm ) { return pool_ele_const( reasm_pool_const( reasm ), reasm->root ); } -fd_reasm_fec_t * fd_reasm_parent ( fd_reasm_t * reasm, fd_reasm_fec_t * child ) { return pool_ele ( reasm_pool ( reasm ), child->parent ); } -fd_reasm_fec_t const * fd_reasm_parent_const ( fd_reasm_t const * reasm, fd_reasm_fec_t const * child ) { return pool_ele_const( reasm_pool_const( reasm ), child->parent ); } -fd_reasm_fec_t * fd_reasm_child ( fd_reasm_t * reasm, fd_reasm_fec_t * parent ) { return pool_ele ( reasm_pool ( reasm ), parent->child ); } -fd_reasm_fec_t const * fd_reasm_child_const ( fd_reasm_t const * reasm, fd_reasm_fec_t const * parent ) { return pool_ele_const( reasm_pool_const( reasm ), parent->child ); } -fd_reasm_fec_t * fd_reasm_sibling ( fd_reasm_t * reasm, fd_reasm_fec_t * sibling ) { return pool_ele ( reasm_pool ( reasm ), sibling->sibling ); } -fd_reasm_fec_t const * fd_reasm_sibling_const( fd_reasm_t const * reasm, fd_reasm_fec_t const * sibling ) { return pool_ele_const( reasm_pool_const( reasm ), sibling->sibling ); } - -ulong -fd_reasm_slot0( fd_reasm_t * reasm ) { - return reasm->slot0; -} - -ulong -fd_reasm_free( fd_reasm_t * reasm ) { - return pool_free( reasm_pool( reasm ) ); -} - -fd_reasm_fec_t * -fd_reasm_peek( fd_reasm_t * reasm ) { - fd_reasm_fec_t * pool = reasm_pool( reasm ); - for( out_iter_t iter = out_iter_fwd_init( reasm->out, pool ); - !out_iter_done ( iter, reasm->out, pool ); - iter = out_iter_fwd_next( iter, reasm->out, pool ) ) { - fd_reasm_fec_t * fec = out_iter_ele( iter, reasm->out, pool ); - if( FD_LIKELY( fec && !fec->popped && ( !fec->eqvoc || fec->confirmed ) ) ) return fec; - } - return NULL; -} - -fd_reasm_fec_t * -fd_reasm_pop( fd_reasm_t * reasm ) { - fd_reasm_fec_t * pool = reasm_pool( reasm ); - while( FD_LIKELY( !out_is_empty( reasm->out, pool ) ) ) { - fd_reasm_fec_t * fec = out_ele_pop_head( reasm->out, pool ); - fec->in_out = 0; - if( FD_LIKELY( !fec->popped && ( !fec->eqvoc || fec->confirmed ) ) ) { - fec->popped = 1; - return fec; - } - } - return NULL; -} - -fd_reasm_fec_t * -fd_reasm_pop_fec( fd_reasm_t * reasm, - fd_reasm_fec_t * fec ) { - if( FD_UNLIKELY( !fec->in_out || fec->popped || ( fec->eqvoc && !fec->confirmed ) ) ) return NULL; - out_ele_remove( reasm->out, fec, reasm_pool( reasm ) ); - fec->in_out = 0; - fec->popped = 1; - return fec; -} - -fd_reasm_fec_t * -fd_reasm_query( fd_reasm_t * reasm, - fd_hash_t const * merkle_root ) { - fd_reasm_fec_t * pool = reasm_pool( reasm ); - fd_reasm_fec_t * fec = NULL; - fec = ancestry_ele_query( reasm->ancestry, merkle_root, NULL, pool ); - fec = fd_ptr_if( !fec, frontier_ele_query( reasm->frontier, merkle_root, NULL, pool ), fec ); - fec = fd_ptr_if( !fec, orphaned_ele_query( reasm->orphaned, merkle_root, NULL, pool ), fec ); - fec = fd_ptr_if( !fec, subtrees_ele_query( reasm->subtrees, merkle_root, NULL, pool ), fec ); - return fec; -} - -void -fd_reasm_confirm( fd_reasm_t * reasm, - fd_hash_t const * block_id ) { - fd_reasm_fec_t * pool = reasm_pool( reasm ); - fd_reasm_fec_t * fec = ancestry_ele_query( reasm->ancestry, block_id, NULL, pool ); - fec = fd_ptr_if( !fec, frontier_ele_query( reasm->frontier, block_id, NULL, pool ), fec ); - - /* TODO there is a potential optimization where we don't actually need - to confirm every FEC and instead just confirm at the slot-level. - Given roughly ~1k shreds per slot at 32 shreds per FEC, this would - save ~32 loop iterations. Punting given the additional complexity - of bookkeeping and logic this would require. - - If this FEC has not been popped, then that means we need to - redeliver it for execution. Reasm could be in a state where by - confirming the child of an equivocating chain, the child is - confirmed & popped, but the parent of it is confirmed & not - *popped*. It was not delivered through the reasm out queue because - it was backfilled. In the off chance that the parent confirmation - arrives after the child confirmation, we need to make sure to not - redeliver the parent again. The invariant is that if a FEC is - already confirmed, either it or its child must have already been - delivered for execution. */ - - if( FD_LIKELY( fec && !fec->popped && !fec->in_out && !fec->confirmed ) ) { - out_ele_push_tail( reasm->out, fec, pool ); - fec->in_out = 1; - } - - while( FD_LIKELY( fec && !fec->confirmed ) ) { - fec->confirmed = 1; - fec = fd_reasm_parent( reasm, fec ); - } -} - -/* Mark the entire subtree beginning from root as equivocating. This is - linear in the number of descendants in the subtree, but amortizes - because we can short-circuit the BFS at nodes that are already marked - equivocating, so each node is visited at most once. */ - -static void -eqvoc( fd_reasm_t * reasm, - fd_reasm_fec_t * root ) { - fd_reasm_fec_t * pool = reasm_pool( reasm ); - ulong * bfs = reasm->bfs; - bfs_push_tail( bfs, pool_idx( pool, root ) ); - while( FD_LIKELY( !bfs_empty( bfs ) ) ) { - fd_reasm_fec_t * descendant = pool_ele( pool, bfs_pop_head( bfs ) ); - if( FD_LIKELY( descendant->eqvoc ) ) continue; - descendant->eqvoc = 1; - fd_reasm_fec_t * child = fd_reasm_child( reasm, descendant ); - while( FD_LIKELY( child ) ) { - bfs_push_tail( bfs, pool_idx( pool, child ) ); - child = fd_reasm_sibling( reasm, child ); - } - } -} - -static int -validate_intra( fd_reasm_fec_t const * parent, - fd_reasm_fec_t const * child ) { - return child->fec_set_idx==parent->fec_set_idx+FD_FEC_SHRED_CNT && - child->parent_off ==parent->parent_off && - !parent->slot_complete; -} - -static int -validate_inter( fd_reasm_fec_t const * parent, - fd_reasm_fec_t const * child ) { - return child->slot - child->parent_off==parent->slot && - child->fec_set_idx==0 && - parent->slot_complete; -} - -static inline int -validate_chained_block_id( fd_reasm_fec_t const * parent, - fd_reasm_fec_t const * child ) { - return fd_int_if( parent->slot==child->slot, validate_intra( parent, child ), validate_inter( parent, child ) ); -} - -static void -reasm_link( fd_reasm_t * reasm, - fd_reasm_fec_t * parent, - fd_reasm_fec_t * child ) { - fd_reasm_fec_t * pool = reasm_pool( reasm ); - child->parent = (uint)pool_idx( pool, parent ); - if( FD_LIKELY( parent->child == pool_idx_null( pool ) ) ) { - parent->child = (uint)pool_idx( pool, child ); /* set as left-child. */ - } else { - fd_reasm_fec_t * sibling = pool_ele( pool, parent->child ); - while( FD_LIKELY( sibling->sibling != pool_idx_null( pool ) ) ) sibling = pool_ele( pool, sibling->sibling ); - sibling->sibling = (uint)pool_idx( pool, child ); /* set as right-sibling. */ - } -} - -/* Assumes caller is de-duplicating FEC sets of the same merkle root. - Updates the xid map and inserts the new FEC into the head of the - xid list. */ -static xid_t * -xid_update( fd_reasm_t * reasm, ulong slot, uint fec_set_idx, ulong pool_idx ) { - fd_reasm_fec_t * new_fec = pool_ele( reasm_pool( reasm ), pool_idx ); - xid_t * xid = xid_query( reasm->xid, (slot << 32) | fec_set_idx, NULL ); - if( FD_UNLIKELY( xid ) ) { - new_fec->xid_next = (uint)xid->idx; - xid->idx = pool_idx; /* updates head ptr */ - xid->cnt++; - } else { - xid = xid_insert( reasm->xid, (slot << 32) | fec_set_idx ); - if( FD_UNLIKELY( !xid ) ) FD_LOG_CRIT(( "xid map full, slot=%lu fec_set_idx=%u", slot, fec_set_idx )); - xid->idx = pool_idx; - xid->cnt = 1; - } - return xid; -} - -static fd_reasm_fec_t * -clear_xid_metadata( fd_reasm_t * reasm, - fd_reasm_fec_t * fec ) { - fd_reasm_fec_t * pool = reasm_pool( reasm ); - ulong fec_idx = pool_idx( pool, fec ); - - xid_t * xid = xid_query( reasm->xid, (fec->slot << 32)|fec->fec_set_idx, NULL ); - xid->cnt--; - if( FD_LIKELY( !xid->cnt ) ) { - xid_remove( reasm->xid, xid ); - } else if( xid->idx==fec_idx ) { - xid->idx = fec->xid_next; - } else { - fd_reasm_fec_t * prev = pool_ele( pool, xid->idx ); - while( prev->xid_next!=fec_idx ) prev = pool_ele( pool, prev->xid_next ); - prev->xid_next = fec->xid_next; - } - - return fec; -} - -static void -subtrees_remove( fd_reasm_t * reasm, - fd_reasm_fec_t * root, - fd_store_t * opt_store, - fd_store_map_t * opt_map_join ) { - fd_reasm_fec_t * pool = reasm_pool( reasm ); - ulong * bfs = reasm->bfs; - - FD_TEST( bfs_empty( bfs ) ); - bfs_push_tail( bfs, pool_idx( pool, root ) ); - while( FD_LIKELY( !bfs_empty( bfs ) ) ) { - fd_reasm_fec_t * ele = pool_ele( pool, bfs_pop_head( bfs ) ); - - fd_reasm_fec_t * child = fd_reasm_child( reasm, ele ); - while( FD_LIKELY( child ) ) { - bfs_push_tail( bfs, pool_idx( pool, child ) ); - child = fd_reasm_sibling( reasm, child ); - } - - if( FD_UNLIKELY( subtrees_ele_query( reasm->subtrees, &ele->key, NULL, pool )==ele ) ) { - subtrees_ele_remove( reasm->subtrees, &ele->key, NULL, pool ); - subtreel_ele_remove( reasm->subtreel, ele, pool ); - } else { - FD_TEST( orphaned_ele_remove( reasm->orphaned, &ele->key, NULL, pool )==ele ); - } - - clear_xid_metadata( reasm, ele ); - if( FD_LIKELY( opt_store ) ) { - fd_store_remove( opt_store, opt_map_join, &ele->key ); - } - pool_ele_release( pool, ele ); - } - FD_TEST( bfs_empty( bfs ) ); -} - -void -fd_reasm_pool_release( fd_reasm_t * reasm, - fd_reasm_fec_t * ele ){ - pool_ele_release( reasm_pool( reasm ), ele ); -} - -ulong -fd_reasm_pool_idx( fd_reasm_t * reasm, fd_reasm_fec_t * ele ) { - return pool_idx( reasm_pool( reasm ), ele ); -} - -fd_reasm_fec_t * -fd_reasm_remove( fd_reasm_t * reasm, - fd_reasm_fec_t * head, - fd_store_t * opt_store, - fd_store_map_t * opt_map_join ) { - /* see fd_forest.c clear_leaf */ - - fd_reasm_fec_t * pool = reasm_pool( reasm ); - orphaned_t * orphaned = reasm->orphaned; - frontier_t * frontier = reasm->frontier; - ancestry_t * ancestry = reasm->ancestry; - subtrees_t * subtrees = reasm->subtrees; - subtreel_t * subtreel = reasm->subtreel; - uint null = (uint)pool_idx_null( pool ); - - FD_TEST( head ); - - fd_reasm_fec_t * tail = head; - - if( FD_LIKELY( orphaned_ele_query( orphaned, &head->key, NULL, pool ) || - subtrees_ele_query( subtrees, &head->key, NULL, pool ) ) ) { - FD_TEST( head->child == null ); /* must be a leaf node */ - } else { - /* Node is in frontier or ancestry. If the leaf is in the frontier, - we could be removing something that has been executed. Move the - head pointer up to where we begin evicting. - - We search up the tree until the theoretical boundary of a bank. - This is usually when we jump to a parent slot, but if an - equivocation occurred, this could also be the middle of the slot. - - 0 ── 32 ── 64 ── 96 (confirmed) - └── 64' ── 96' ── 128' (eqvoc) - - Note we only have execute a slot twice (have 2 bank idxs for it) - if the slot equivocated, we replayed the wrong version, and then - replayed the confirmed version afterwards. - - The state after executing the wrong version first is: - - 0 ──── 32 ──── 64 ──── 96 ──── 128 - (bank_idx=1) (bank_idx=1) .... (all bank_idx=1) - - After receiving and executing the confirmed version, the state - looks like: - - 0 (b=2) ── 32 (b=2) ── 64 (b=2) ── 96 (b=2) (confirmed) - └── 64' (b=1) ── 96' (b=1) ── 128' (b=1) (eqvoc) - - Here we want to evict only until fec 64'. Or let's say we are - getting around to executing the confirmed version, but we haven't - executed it yet. - - 0 (b=1) ── 32 (b=1) ── 64 (b=ULONG_MAX) ── 96 (b=ULONG_MAX) (confirmed, but not executed yet) - └── 64' (b=1) ── 96' (b=1) ── 128'(b=1) (eqvoc) - - Now we know we should evict until the max(parent has > 1 child, or fec set idx == 0) */ - - while( FD_LIKELY( head ) ) { - fd_reasm_fec_t * parent = fd_reasm_parent( reasm, head ); /* parent must exist. It's not possible to walk up past root */ - - if( FD_UNLIKELY( head->fec_set_idx==0 ) ) break; - if( FD_UNLIKELY( head->sibling != pool_idx_null( pool ) ) ) break; /* if the parent has more than 1 child, we know for sure the parent is a slot boundary or eqvoc point, so we can stop here. */ - if( FD_UNLIKELY( parent->child != pool_idx( pool, head ) ) ) break; /* if the parent has more than 1 child, we know for sure the parent is a slot boundary or eqvoc point, so we can stop here. */ - head = parent; - } - } - FD_LOG_INFO(( "evicting reasm slot %lu, fec idx %u, down to %u bank_idx %u", head->slot, head->fec_set_idx, tail->fec_set_idx, head->bank_idx )); - - /* Orphan the entire subtree from the tail :( */ - if( FD_UNLIKELY( fd_reasm_child( reasm, tail ) ) ) { - /* subtree this child. This code path should only be hit if this is - banks-driven eviction, so children are guaranteed to be in main - tree right now. */ - ulong * bfs = reasm->bfs; - bfs_push_tail( bfs, pool_idx( pool, tail ) ); - while( FD_LIKELY( !bfs_empty( bfs ) ) ) { - fd_reasm_fec_t * ele = pool_ele( pool, bfs_pop_head( bfs ) ); - fd_reasm_fec_t * child = fd_reasm_child( reasm, ele ); - while( FD_LIKELY( child ) ) { - bfs_push_tail( bfs, pool_idx( pool, child ) ); - child = pool_ele( pool, child->sibling ); - } - - if( FD_UNLIKELY( ele == tail ) ) continue; - /* remove each child from the maps */ - if( FD_UNLIKELY( !ancestry_ele_remove( ancestry, &ele->key, NULL, pool ) ) ) frontier_ele_remove( frontier, &ele->key, NULL, pool ); - if( FD_LIKELY( ele->in_out ) ) { out_ele_remove( reasm->out, ele, pool ); ele->in_out = 0; } - - if( FD_UNLIKELY( ele->parent == pool_idx( pool, tail ) ) ) { - subtrees_ele_insert( subtrees, ele, pool ); - subtreel_ele_push_tail( reasm->subtreel, ele, pool ); - ele->parent = null; - ele->sibling = null; - } else { - orphaned_ele_insert( orphaned, ele, pool ); - } - } - /* unlink the leaf from its children. */ - tail->child = null; - } - - fd_reasm_fec_t * parent = fd_reasm_parent( reasm, head ); - if( FD_LIKELY( parent ) ) { - /* Clean up the parent pointing to this head, and remove block from the maps - remove the block from the parent's child list */ - - fd_reasm_fec_t * child = pool_ele( pool, parent->child ); - if( FD_LIKELY( 0==memcmp( &child->key, &head->key, sizeof(fd_hash_t) ) ) ) { /* evicted is left-child (or only child) */ - parent->child = child->sibling; - } else { - /* evicted is a right-sibling */ - fd_reasm_fec_t * sibling = pool_ele( pool, child->sibling ); - fd_reasm_fec_t * prev = child; - while( FD_LIKELY( sibling && memcmp( &sibling->key, &head->key, sizeof(fd_hash_t) ) ) ) { - prev = sibling; - sibling = pool_ele( pool, sibling->sibling ); - } - prev->sibling = sibling->sibling; - } - - /* remove the chain itself from the maps */ - - fd_reasm_fec_t * removed_orphan = NULL; - if( FD_LIKELY ( removed_orphan = orphaned_ele_remove( orphaned, &head->key, NULL, pool ) ) ) { - clear_xid_metadata( reasm, head ); - if( FD_LIKELY( opt_store ) ) { - fd_store_remove( opt_store, opt_map_join, &head->key ); - } - return head; - } - - /* remove from ancestry and frontier */ - fd_reasm_fec_t * curr = head; - while( FD_LIKELY( curr ) ) { - fd_reasm_fec_t * removed = ancestry_ele_remove( ancestry, &curr->key, NULL, pool ); - if( !removed ) removed = frontier_ele_remove( frontier, &curr->key, NULL, pool ); - if( FD_LIKELY( removed->in_out ) ) { out_ele_remove( reasm->out, removed, pool ); removed->in_out = 0; } - - curr = fd_reasm_child( reasm, curr ); /* guaranteed only one child */ - clear_xid_metadata( reasm, removed ); - if( FD_LIKELY( opt_store ) ) { - fd_store_remove( opt_store, opt_map_join, &removed->key ); - } - } - - /* We removed from the main tree, so we might need to insert parent into the frontier. - Only need to add parent to the frontier if it doesn't have any other children. */ - - if( parent->child == pool_idx_null( pool ) ) { - parent = ancestry_ele_remove( ancestry, &parent->key, NULL, pool ); - FD_TEST( parent ); - frontier_ele_insert( frontier, parent, pool ); - } - return head; - } - - /* No parent, remove from subtrees and subtree list */ - subtrees_ele_remove( subtrees, &head->key, NULL, pool ); - subtreel_ele_remove( subtreel, head, pool ); - clear_xid_metadata( reasm, head ); - if( FD_LIKELY( opt_store ) ) { - fd_store_remove( opt_store, opt_map_join, &head->key ); - } - return head; -} - -fd_reasm_fec_t * -latest_confirmed_fec( fd_reasm_t * reasm, - ulong subtree_root ) { - ulong * bfs = reasm->bfs; - fd_reasm_fec_t * pool = reasm_pool( reasm ); - bfs_push_tail( bfs, subtree_root ); - fd_reasm_fec_t * latest_confirmed = NULL; - while( FD_LIKELY( !bfs_empty( bfs ) ) ) { - fd_reasm_fec_t * ele = pool_ele( pool, bfs_pop_head( bfs ) ); - if( FD_LIKELY( ele->confirmed ) ) { - if( FD_LIKELY( latest_confirmed == NULL || - latest_confirmed->slot < ele->slot || - (latest_confirmed->slot == ele->slot && latest_confirmed->fec_set_idx < ele->fec_set_idx)) ) - latest_confirmed = ele; - } - fd_reasm_fec_t * child = fd_reasm_child( reasm, ele ); - while( FD_LIKELY( child ) ) { - bfs_push_tail( bfs, pool_idx( pool, child ) ); - child = pool_ele( pool, child->sibling ); - } - } - return latest_confirmed; -} - -static fd_reasm_fec_t * -gca( fd_reasm_t * reasm, - fd_reasm_fec_t * a, - fd_reasm_fec_t * b ) { - fd_reasm_fec_t * parent1 = a; - fd_reasm_fec_t * parent2 = b; - while( FD_LIKELY( parent1 && parent2 ) ) { - if( FD_LIKELY( parent1 == parent2 ) ) return parent1; - if( parent1->slot > parent2->slot || - ( parent1->slot == parent2->slot && parent1->fec_set_idx > parent2->fec_set_idx ) ) parent1 = fd_reasm_parent( reasm, parent1 ); - else parent2 = fd_reasm_parent( reasm, parent2 ); - } - return NULL; -} - -/* Caller guarantees new_root and parent_root are non-NULL */ -static fd_reasm_fec_t * -evict( fd_reasm_t * reasm, - fd_store_t * opt_store, - fd_store_map_t * opt_map_join, - fd_hash_t const * new_root FD_PARAM_UNUSED, - fd_hash_t const * parent_root ) { - fd_reasm_fec_t * pool = reasm_pool( reasm ); - frontier_t * frontier = reasm->frontier; - orphaned_t * orphaned = reasm->orphaned; - subtrees_t * subtrees = reasm->subtrees; - subtreel_t * subtreel = reasm->subtreel; - uint null = (uint)pool_idx_null( pool ); - - /* Generally, best policy for eviction is to evict in the order of: - 1. Highest unconfirmed orphan leaf - furthest from root - 2. Highest incomplete, unconfirmed leaf in ancestry - furthest from tip of execution - 3. Highest confirmed orphan leaf - evictable, since unrelated to banks, but less ideal */ - - fd_reasm_fec_t * unconfrmd_orphan = NULL; /* 1st best candidate for eviction is the highest unconfirmed orphan. */ - fd_reasm_fec_t * confirmed_orphan = NULL; /* 3rd best candidate for eviction is the highest confirmed orphan. */ - for( subtreel_iter_t iter = subtreel_iter_fwd_init( subtreel, pool ); - !subtreel_iter_done ( iter, subtreel, pool ); - iter = subtreel_iter_fwd_next( iter, subtreel, pool ) ) { - fd_reasm_fec_t * ele = subtreel_iter_ele( iter, subtreel, pool ); - if( ele->child != null || memcmp( &ele->key, parent_root, sizeof(fd_hash_t) ) == 0 ) continue; - if( FD_UNLIKELY( ele->confirmed ) ) confirmed_orphan = fd_ptr_if( !confirmed_orphan || ele->slot > confirmed_orphan->slot, ele, confirmed_orphan ); - else unconfrmd_orphan = fd_ptr_if( !unconfrmd_orphan || ele->slot > unconfrmd_orphan->slot, ele, unconfrmd_orphan ); - } - for( orphaned_iter_t iter = orphaned_iter_init( orphaned, pool ); - !orphaned_iter_done( iter, orphaned, pool ); - iter = orphaned_iter_next( iter, orphaned, pool ) ) { - fd_reasm_fec_t * ele = orphaned_iter_ele( iter, orphaned, pool ); - if( ele->child != null || memcmp( &ele->key, parent_root, sizeof(fd_hash_t) ) == 0 ) continue; - if( FD_UNLIKELY( ele->confirmed ) ) confirmed_orphan = fd_ptr_if( !confirmed_orphan || ele->slot > confirmed_orphan->slot, ele, confirmed_orphan ); - else unconfrmd_orphan = fd_ptr_if( !unconfrmd_orphan || ele->slot > unconfrmd_orphan->slot, ele, unconfrmd_orphan ); - } - - if( FD_UNLIKELY( unconfrmd_orphan )) { - return fd_reasm_remove( reasm, unconfrmd_orphan, opt_store, opt_map_join ); - } - - fd_reasm_fec_t * unconfrmd_leaf = NULL; /* 2nd best candidate for eviction is the highest unconfirmed, incomplete slot. */ - for( frontier_iter_t iter = frontier_iter_init( frontier, pool ); - !frontier_iter_done( iter, frontier, pool ); - iter = frontier_iter_next( iter, frontier, pool ) ) { - fd_reasm_fec_t * ele = frontier_iter_ele( iter, frontier, pool ); - if( iter.ele_idx == reasm->root - || 0==memcmp( &ele->key, parent_root, sizeof(fd_hash_t) ) - || ele->confirmed - || ele->slot_complete - || ele->is_leader ) continue; /* not a candidate */ - unconfrmd_leaf = fd_ptr_if( !unconfrmd_leaf || ele->slot > unconfrmd_leaf->slot, ele, unconfrmd_leaf ); - } - - if( FD_UNLIKELY( unconfrmd_leaf )) { - return fd_reasm_remove( reasm, unconfrmd_leaf, opt_store, opt_map_join ); - } - - /* Already did traversal to find best confirmed orphan candidate, - which is the third choice */ - - if( FD_UNLIKELY( confirmed_orphan )) { - fd_reasm_fec_t * parent = fd_reasm_query( reasm, parent_root ); - if( !parent ) { - return fd_reasm_remove( reasm, confirmed_orphan, opt_store, opt_map_join ); - } - /* for any subtree: - 0 ── 1 ── 2 ── 3 (confirmed) ── 4(confirmed) ── 5 ── 6 ──> add 7 here is valid. - └──> add 7 here is valid. - └──> add 7 here is invalid. */ - ulong subtree_root = reasm->root; - if( subtrees_ele_query( subtrees, parent_root, NULL, pool ) || - orphaned_ele_query( orphaned, parent_root, NULL, pool ) ) { - /* if adding to an orphan, find the root of the orphan subtree. */ - fd_reasm_fec_t * root = parent; - while( FD_LIKELY( root->parent != null ) ) { - root = pool_ele( pool, root->parent ); - } - subtree_root = pool_idx( pool, root ); - } - - fd_reasm_fec_t * latest_confirmed_leaf = latest_confirmed_fec( reasm, subtree_root ); - if( !latest_confirmed_leaf || latest_confirmed_leaf == gca( reasm, latest_confirmed_leaf, parent )) { - return fd_reasm_remove( reasm, confirmed_orphan, opt_store, opt_map_join ); - } - /* is a useless new fork. */ - return NULL; - } - return NULL; /* nothing else could be evicted */ -} - -fd_reasm_fec_t * -fd_reasm_init( fd_reasm_t * reasm, - fd_hash_t const * initial_block_id, - ulong slot ) { - - fd_reasm_fec_t * pool = reasm_pool( reasm ); - uint idx_null = (uint)pool_idx_null( pool ); - - FD_TEST( pool_free( pool ) ); - fd_reasm_fec_t * fec = pool_ele_acquire( pool ); - fec->key = *initial_block_id; - fec->next = idx_null; - fec->parent = idx_null; - fec->child = idx_null; - fec->sibling = idx_null; - fec->slot = slot; - fec->parent_off = 0; - fec->fec_set_idx = 0U; - fec->data_cnt = 0U; - fec->data_complete = 0; - fec->slot_complete = 1; - fec->is_leader = 0; - fec->eqvoc = 0; - fec->confirmed = 0; - fec->popped = 0; - fec->bank_dead = 0; - fec->dead_reported = 0; - fec->bank_idx = UINT_MAX; - fec->parent_bank_idx = UINT_MAX; - fec->bank_seq = ULONG_MAX; - fec->fec_completed_ts_nanos = 0UL; - fec->out.next = idx_null; - fec->out.prev = idx_null; - fec->in_out = 0; - fec->xid_next = UINT_MAX; - fec->subtreel.next = idx_null; - fec->subtreel.prev = idx_null; - - - FD_TEST( reasm->root==pool_idx_null( pool ) ); - fec->confirmed = 1; - fec->popped = 1; - /* */ xid_update( reasm, slot, 0U, pool_idx( pool, fec ) ); - reasm->root = pool_idx( pool, fec ); - reasm->slot0 = slot; - frontier_ele_insert( reasm->frontier, fec, pool ); - return fec; -} - -fd_reasm_fec_t * -fd_reasm_insert( fd_reasm_t * reasm, - fd_hash_t const * merkle_root, - fd_hash_t const * chained_merkle_root, - ulong slot, - uint fec_set_idx, - ushort parent_off, - ushort data_cnt, - int data_complete, - int slot_complete, - int is_leader, - fd_store_t * opt_store, - fd_store_map_t * opt_map_join, - fd_reasm_fec_t ** evicted ) { - -# if LOGGING - FD_BASE58_ENCODE_32_BYTES( merkle_root->key, merkle_root_b58 ); - FD_BASE58_ENCODE_32_BYTES( chained_merkle_root->key, chained_merkle_root_b58 ); - FD_LOG_NOTICE(( "inserting (%lu %u) %s %s. %u %d %d", slot, fec_set_idx, merkle_root_b58, chained_merkle_root_b58, data_cnt, data_complete, slot_complete )); -# endif - - fd_reasm_fec_t * pool = reasm_pool( reasm ); -# if FD_REASM_USE_HANDHOLDING - FD_TEST( !fd_reasm_query( reasm, merkle_root ) ); -# endif - - FD_TEST( chained_merkle_root ); - - uint idx_null = (uint)pool_idx_null( pool ); - ancestry_t * ancestry = reasm->ancestry; - frontier_t * frontier = reasm->frontier; - orphaned_t * orphaned = reasm->orphaned; - subtrees_t * subtrees = reasm->subtrees; - subtreel_t * subtreel = reasm->subtreel; - - ulong * bfs = reasm->bfs; - out_t * out = reasm->out; - - *evicted = NULL; - - if( FD_UNLIKELY( pool_free( pool )==1UL ) ) { - FD_TEST( reasm->root!=pool_idx_null( pool ) ); - /* The eviction removes evicted elements from the maps, but leaves - the elements in the pool for caller to release. Thus, in order - for the following insert/acquire to succeed, we have to start - evicting when we have 1 remaining free element in the pool. This - element is the one that will be acquired below. reasm is - dependent on the caller to then release the evicted elements back - to the pool before the next insert/acquire. */ - fd_reasm_fec_t * evicted_fec = evict( reasm, opt_store, opt_map_join, merkle_root, chained_merkle_root ); - if( FD_UNLIKELY( evicted_fec == NULL ) ) { - FD_LOG_INFO(("reasm failed to evict a fec set when inserting slot %lu fec set %u", slot, fec_set_idx)); - - /* in this case we want to signal to the replay tile that we - failed to insert the FEC set. This is effectively is the same - logic as if we had this FEC set, and then it got evicted, and - then the caller now needs to process the evicted FEC set. So - here we acquire the final pool element for it and return it - to the caller as the evicted FEC set. */ - - fd_reasm_fec_t * fec = pool_ele_acquire( pool ); - fec->key = *merkle_root; - fec->cmr = *chained_merkle_root; - fec->parent = idx_null; - fec->child = idx_null; - fec->slot = slot; - fec->parent_off = parent_off; - fec->fec_set_idx = fec_set_idx; - fec->bank_idx = UINT_MAX; - - *evicted = fec; - return NULL; - } - - *evicted = evicted_fec; - } - - FD_TEST( pool_free( pool ) ); - fd_reasm_fec_t * fec = pool_ele_acquire( pool ); - fec->key = *merkle_root; - fec->next = idx_null; - fec->parent = idx_null; - fec->child = idx_null; - fec->sibling = idx_null; - fec->slot = slot; - fec->parent_off = parent_off; - fec->fec_set_idx = fec_set_idx; - fec->data_cnt = data_cnt; - fec->data_complete = (uchar)!!data_complete; - fec->slot_complete = (uchar)!!slot_complete; - fec->is_leader = (uchar)!!is_leader; - fec->eqvoc = 0; - fec->confirmed = 0; - fec->popped = 0; - fec->bank_dead = 0; - fec->dead_reported = 0; - fec->bank_idx = UINT_MAX; - fec->parent_bank_idx = UINT_MAX; - fec->bank_seq = ULONG_MAX; - fec->fec_completed_ts_nanos = 0UL; - - /* set the out and subtreel pointers to null */ - fec->out.next = idx_null; - fec->out.prev = idx_null; - fec->in_out = 0; - fec->xid_next = UINT_MAX; - fec->subtreel.next = idx_null; - fec->subtreel.prev = idx_null; - - fec->cmr = *chained_merkle_root; - - fd_reasm_fec_t * parent = fd_reasm_query( reasm, chained_merkle_root ); - if( FD_UNLIKELY( parent && !validate_chained_block_id( parent, fec ) ) ) { - FD_BASE58_ENCODE_32_BYTES( fec->key.key, child_key_cstr ); - FD_BASE58_ENCODE_32_BYTES( parent->key.key, parent_key_cstr ); - FD_LOG_INFO(( "[%s] failed to validate chained block id FEC: (%lu %u %s). parent (%lu %u %s).", __func__, fec->slot, fec->fec_set_idx, child_key_cstr, parent->slot, parent->fec_set_idx, parent_key_cstr )); - pool_ele_release( pool, fec ); - return NULL; - } - - /* If the FEC's parent already exists link it correctly: the new FEC - set may result in a new leaf or a new orphan tree root so we need - to check that. */ - - parent = NULL; - if( FD_LIKELY( parent = ancestry_ele_query( ancestry, &fec->cmr, NULL, pool ) ) ) { /* parent is connected non-leaf */ - frontier_ele_insert( frontier, fec, pool ); - out_ele_push_tail( out, fec, pool ); - fec->in_out = 1; - } else if( FD_LIKELY ( parent = frontier_ele_remove( frontier, &fec->cmr, NULL, pool ) ) ) { /* parent is connected leaf */ - ancestry_ele_insert( ancestry, parent, pool ); - frontier_ele_insert( frontier, fec, pool ); - out_ele_push_tail( out, fec, pool ); - fec->in_out = 1; - } else if( FD_LIKELY ( parent = orphaned_ele_query( orphaned, &fec->cmr, NULL, pool ) ) ) { /* parent is orphaned non-root */ - orphaned_ele_insert( orphaned, fec, pool ); - } else if( FD_LIKELY ( parent = subtrees_ele_query( subtrees, &fec->cmr, NULL, pool ) ) ) { /* parent is orphaned root */ - orphaned_ele_insert( orphaned, fec, pool ); - } else { /* parent not found */ - subtrees_ele_insert( subtrees, fec, pool ); - subtreel_ele_push_tail( subtreel, fec, pool ); - } - - if( FD_LIKELY( parent ) ) reasm_link( reasm, parent, fec ); - - /* Second, we search for children of this new FEC and link them to it. - By definition any children must be orphaned (a child cannot be part - of a connected tree before its parent). Therefore, we only search - through the orphaned subtrees. As part of this operation, we also - coalesce orphans into orphan subtrees. An orphan may be connected - to its parent, but part of an orphaned subtree. This way we only - need to search for children the orphan subtree roots (vs. all - orphaned nodes). */ - - ulong min_descendant = ULONG_MAX; /* needed for eqvoc checks below */ - FD_TEST( bfs_empty( bfs ) ); - for( subtreel_iter_t iter = subtreel_iter_fwd_init( subtreel, pool ); - !subtreel_iter_done ( iter, subtreel, pool ); - iter = subtreel_iter_fwd_next( iter, subtreel, pool ) ) { - fd_reasm_fec_t * parent = fec; - fd_reasm_fec_t * child = subtreel_iter_ele( iter, subtreel, pool ); - if( FD_UNLIKELY( !fd_hash_eq( &parent->key, &child->cmr ) ) ) continue; - if( FD_UNLIKELY( !validate_chained_block_id( parent, child ) ) ) { - FD_BASE58_ENCODE_32_BYTES( child->key.key, child_key_cstr ); - FD_BASE58_ENCODE_32_BYTES( parent->key.key, parent_key_cstr ); - FD_LOG_INFO(( "[%s] failed to validate chained block id FEC: (%lu %u %s). parent (%lu %u %s).", __func__, child->slot, child->fec_set_idx, child_key_cstr, parent->slot, parent->fec_set_idx, parent_key_cstr )); - subtrees_remove( reasm, child, opt_store, opt_map_join ); - continue; - } - reasm_link( reasm, parent, child ); - subtrees_ele_remove( subtrees, &child->key, NULL, pool ); - subtreel_ele_remove( subtreel, child, pool ); - orphaned_ele_insert( orphaned, child, pool ); - min_descendant = fd_ulong_min( min_descendant, child->slot ); - } - - /* Third, we advance the frontier outward beginning from fec as we may - have connected orphaned descendants to fec in the above step. This - does a BFS outward from fec until it reaches leaves, moving fec and - its non-leaf descendants into ancestry and leaves into frontier. - - parent (ancestry) orphan root (subtrees) - | | - fec (frontier) orphan child (orphaned) - - parent - | - fec <- frontier is here - | - orphan root - | - orphan child <- advance to here */ - - if( FD_LIKELY( frontier_ele_query( frontier, &fec->key, NULL, pool ) ) ) bfs_push_tail( bfs, pool_idx( pool, fec ) ); - while( FD_LIKELY( !bfs_empty( bfs ) ) ) { - fd_reasm_fec_t * parent = pool_ele( pool, bfs_pop_head( bfs ) ); - fd_reasm_fec_t * child = pool_ele( pool, parent->child ); - if( FD_LIKELY( child ) ) { - frontier_ele_remove( frontier, &parent->key, NULL, pool ); - ancestry_ele_insert( ancestry, parent, pool ); - } - while( FD_LIKELY( child ) ) { - FD_TEST( orphaned_ele_remove( orphaned, &child->key, NULL, pool ) ); - frontier_ele_insert( frontier, child, pool ); - bfs_push_tail( bfs, pool_idx( pool, child ) ); - out_ele_push_tail( out, child, pool ); - child->in_out = 1; - child = pool_ele( pool, child->sibling ); - } - } - - /* Fourth, check and handle equivocation. There are three cases. - - 1. we've already seen this FEC's xid (slot, fec_set_idx) - 2. this FEC's parent equivocates. */ - - xid_t * xid = xid_query( reasm->xid, (slot<<32) | fec_set_idx, NULL ); - if( FD_UNLIKELY( xid ) ) { - eqvoc( reasm, fec ); - eqvoc( reasm, pool_ele( pool, xid->idx ) ); /* most recent appearance of this xid */ - /* We can call eqvoc on the head of the xid list because we maintain - the order of most recent to least recent. Inductively, this marks - all FECs with the same xid as equivocating. */ - } - xid_update( reasm, slot, fec_set_idx, pool_idx( pool, fec ) ); - if( FD_UNLIKELY( parent && parent->eqvoc && !parent->confirmed ) ) eqvoc( reasm, fec ); - - /* Finally, return the newly inserted FEC. */ - return fec; -} - -fd_reasm_fec_t * -fd_reasm_publish( fd_reasm_t * reasm, - fd_hash_t const * merkle_root, - fd_store_t * opt_store, - fd_store_map_t * opt_map_join ) { - -# if FD_REASM_USE_HANDHOLDING - if( FD_UNLIKELY( !pool_ele( reasm_pool( reasm ), reasm->root ) ) ) { FD_LOG_WARNING(( "missing root" )); return NULL; } - if( FD_UNLIKELY( !fd_reasm_query( reasm, merkle_root ) ) ) { - FD_BASE58_ENCODE_32_BYTES( merkle_root->key, merkle_root_b58 ); - FD_LOG_WARNING(( "merkle root %s not found", merkle_root_b58 )); - return NULL; - } -# endif - - fd_reasm_fec_t * pool = reasm_pool( reasm ); - uint null = (uint)pool_idx_null( pool ); - fd_reasm_fec_t * oldr = pool_ele( pool, reasm->root ); - fd_reasm_fec_t * newr = fd_reasm_query( reasm, merkle_root ); - ulong * bfs = reasm->bfs; - - bfs_push_tail( bfs, pool_idx( pool, oldr ) ); - - /* First, BFS down the tree, pruning all of root's ancestors and also - any descendants of those ancestors. */ - - /* Also, prune any subtrees who's root is less than the new root. */ - - subtreel_t * subtreel = reasm->subtreel; - for( subtreel_iter_t iter = subtreel_iter_fwd_init( subtreel, pool ); - !subtreel_iter_done ( iter, subtreel, pool ); - iter = subtreel_iter_fwd_next( iter, subtreel, pool ) ) { - fd_reasm_fec_t * ele = subtreel_iter_ele( iter, subtreel, pool ); - if( ele->slot < newr->slot ) { - bfs_push_tail( bfs, pool_idx( pool, ele ) ); - } - } - - while( FD_LIKELY( !bfs_empty( bfs ) ) ) { - fd_reasm_fec_t * head = pool_ele( pool, bfs_pop_head( bfs ) ); - - fd_reasm_fec_t * fec = ancestry_ele_remove( reasm->ancestry, &head->key, NULL, pool ); - if( FD_UNLIKELY( !fec ) ) fec = frontier_ele_remove( reasm->frontier, &head->key, NULL, pool ); - if( FD_UNLIKELY( !fec ) ) fec = orphaned_ele_remove( reasm->orphaned, &head->key, NULL, pool ); - if( FD_UNLIKELY( !fec ) ) { - fec = subtrees_ele_remove( reasm->subtrees, &head->key, NULL, pool ); - subtreel_ele_remove( reasm->subtreel, head, pool ); - } - - fd_reasm_fec_t * child = pool_ele( pool, head->child ); - while( FD_LIKELY( child ) ) { /* iterate over children */ - if( FD_LIKELY( child != newr ) ) { /* stop at new root */ - bfs_push_tail( bfs, pool_idx( pool, child ) ); - } - child = pool_ele( pool, child->sibling ); /* right-sibling */ - } - clear_xid_metadata( reasm, head ); - if( FD_LIKELY( opt_store ) ) { - fd_store_remove( opt_store, opt_map_join, &head->key ); - } - if( FD_LIKELY( head->in_out ) ) { out_ele_remove( reasm->out, head, pool ); head->in_out = 0; } - pool_ele_release( pool, head ); - } - - newr->parent = null; /* unlink old root */ - newr->sibling = null; - reasm->root = pool_idx( pool, newr ); /* replace with new root */ - return newr; -} - -#include - -FD_FN_UNUSED static void -print( fd_reasm_t const * reasm, fd_reasm_fec_t const * fec, int space, const char * prefix ) { - fd_reasm_fec_t const * pool = reasm_pool_const( reasm ); - - if( fec == NULL ) return; - - if( space > 0 ) printf( "\n" ); - for( int i = 0; i < space; i++ ) printf( " " ); - FD_BASE58_ENCODE_32_BYTES( fec->key.key, key_b58 ); - printf( "%s%s", prefix, key_b58 ); - - fd_reasm_fec_t const * curr = pool_ele_const( pool, fec->child ); - char new_prefix[1024]; /* FIXME size this correctly */ - while( curr ) { - if( pool_ele_const( pool, curr->sibling ) ) { - sprintf( new_prefix, "├── " ); /* branch indicating more siblings follow */ - print( reasm, curr, space + 4, new_prefix ); - } else { - sprintf( new_prefix, "└── " ); /* end branch */ - print( reasm, curr, space + 4, new_prefix ); - } - curr = pool_ele_const( pool, curr->sibling ); - } -} - -static void -ancestry_print( fd_reasm_t const * reasm, fd_reasm_fec_t const * fec, int space, const char * prefix, fd_reasm_fec_t const * prev, ulong recurse_depth ) { - fd_reasm_fec_t const * pool = reasm_pool_const( reasm ); - if( fec == NULL ) return; - recurse_depth++; - if( recurse_depth == 2048 ) { - FD_BASE58_ENCODE_32_BYTES( fec->key.key, key_b58 ); - FD_LOG_NOTICE(("Cutting off ancestry print at depth %lu, slot %lu. Continue printing with this root key %s.", recurse_depth, fec->slot, key_b58 )); - return; - } - fd_reasm_fec_t const * child = fd_reasm_child_const( reasm, fec ); - - if( !prev || /* root OR */ - ( fec->slot_complete || (!prev->eqvoc && fec->eqvoc) || fec->child == pool_idx_null( pool ) || child->sibling != pool_idx_null( pool ) )) { - if( space > 0 ) printf( "\n" ); - for( int i = 0; i < space; i++ ) printf( " " ); - printf( "%s", prefix ); - - FD_BASE58_ENCODE_32_BYTES( fec->key.key, key_b58 ); - key_b58[5] = '\0'; /* only print first 5 characters of key_b58 */ - printf( "%lu(%u) %s", fec->slot, fec->fec_set_idx, key_b58 ); - if( fec->eqvoc ) printf( " [eqvoc]" ); - if( fec->is_leader ) printf( " [leader]" ); - space += 5; - fflush(stdout); - } - - char new_prefix[1024]; /* FIXME size this correctly */ - - while( child ) { - if( pool_ele_const( pool, child->sibling ) ) { - sprintf( new_prefix, "├── " ); /* branch indicating more siblings follow */ - ancestry_print( reasm, child, space, new_prefix, fec, recurse_depth ); - } else { - sprintf( new_prefix, "└── " ); /* end branch */ - ancestry_print( reasm, child, space, new_prefix, fec, recurse_depth ); - } - child = pool_ele_const( pool, child->sibling ); - } -} - -void -fd_reasm_print( fd_reasm_t const * reasm ) { - FD_LOG_NOTICE( ( "\n\n[Reasm - showing only leaves, slot completes, and branches]" ) ); - fd_reasm_fec_t const * pool = reasm_pool_const( reasm ); - printf( "ele cnt: %lu\n", pool_used( pool ) ); - - if( FD_LIKELY( reasm->root != pool_idx_null( pool ) ) ) { - printf( "\n\n[Connected Fecs]\n" ); - ancestry_print( reasm, fd_reasm_root_const( reasm ), 0, "", NULL, 0 ); - } - - printf( "\n\n[Unconnected Fecs]\n" ); - subtreel_t const * subtreel = reasm->_subtrlf; - for( subtreel_iter_t iter = subtreel_iter_fwd_init( subtreel, pool ); - !subtreel_iter_done ( iter, subtreel, pool ); - iter = subtreel_iter_fwd_next( iter, subtreel, pool ) ) { - fd_reasm_fec_t const * fec = subtreel_iter_ele_const( iter, subtreel, pool ); - ancestry_print( reasm, fec, 0, "", NULL, 0 ); - } - - printf( "\n\n" ); - fflush(stdout); -} diff --git a/src/discof/reasm/fd_reasm.h b/src/discof/reasm/fd_reasm.h deleted file mode 100644 index b22cf94de17..00000000000 --- a/src/discof/reasm/fd_reasm.h +++ /dev/null @@ -1,458 +0,0 @@ -#ifndef HEADER_fd_src_discof_reasm_fd_reasm_h -#define HEADER_fd_src_discof_reasm_fd_reasm_h - -/* fd_reasm reassembles FEC sets into Replay order as they are received - over the network via Turbine and Repair. Every FEC set is guaranteed - to be eventually delivered by reasm to the caller after it has been - "chained" to its parent (defined below). - - Every FEC set has a parent (the immediately preceding FEC set in the - slot or parent slot) and children (immediately succeeding FEC set(s) - for the same slot or child slots). Reasm always delivers a parent - before its child. This guarantees that every fork will be delivered - in-order Forks are treated as concurrent, and thus reasm only - provides a partial ordering such that reasm makes no guarantees about - the delivery order of FEC sets across forks, but in general this will - be the order in which the reasm is able to chain them to their - connected parents. - - Forks manifest in reasm as a FEC set with more than one child, and - mostly occur across slots due to leader skipping (ie. parent and - child(s) have different slots). For example, the leader for slot 14 - forks slot 9 when another leader already built slot 10 from slot 9, - so now the last FEC set in slot 9 has two child FEC sets ie. the - first FEC in slot 10 and first FEC in slot 14. - - There is a protocol violation called equivocation (also known as - "duplicates") that can also cause forks. Unlike skips, equivocation - is not honest behavior and only happens when validators are behaving - maliciously or their software has a bug. Equivocation can result in - observing two or more child FEC sets for a given parent FEC set in - the _same_ slot. Moreover, there might be two FEC sets with the same - slot and FEC set index but different payloads. Due to equivocation, - reasm cannot key FEC sets by the natural pair (slot, fec_set_idx) and - instead keys by the FEC set merkle root. Similarly, reasm connects - FEC sets to its parent via the chained merkle root. Not all cases of - equivocation can be detected by the reasm however, as not all the - necessary information is yet available at this stage in the validator - pipeline. Reasm will simply deliver all the equivocating FEC sets it - does observe (with a flag indicating its detection). */ - -#include "../../disco/store/fd_store.h" - -/* FD_REASM_USE_HANDHOLDING: Define this to non-zero at compile time - to turn on additional runtime checks and logging. */ - -#ifndef FD_REASM_USE_HANDHOLDING -#define FD_REASM_USE_HANDHOLDING 1 -#endif - -/* fd_reasm is represented as a forest (multi-tree) structure. Each - node in the forest corresponds to a FEC set. - - The forest contains a single connected tree component. Nodes in the - connected tree are FEC sets that have chained to the reasm root. An - internal node is referred to as "cnode" and a leaf node "cleaf". - - reasm root - / \ - cnode cnode - / \ / \ - cnode cleaf cleaf cleaf - / \ - cleaf cleaf - - - The forest also potentially contains one or more orphaned tree - components. Nodes in orphaned trees are FEC sets that has not yet - chained to the reasm root, but may chain to one another. Each orphan - tree root is an "oroot". Leaves are not distinguished from internal - nodes in orphan trees, any non-root node is an "onode". - - oroot oroot - / \ / - onode onode onode - | - onode - - - At any given point in time, a FEC set is in one of four possible - positions corresponding to one of the four maps: connected node - (cnode), connected leaf (cleaf), orphaned node (onode) or orphaned - root (oroot). Therefore, a given element will always be present in - exactly one of these maps, depending on where (and whether) it - currently is in the tree. - - KEYING - - The reasm keys FEC sets by the merkle root of the FEC set. - - INSERTING - - When inserting a new FEC set, the reasm first checks whether the - parent is a FEC set already in the frontier map. This indicates that - the new FEC set directly chains off the frontier. If it does, the - parent FEC set is removed, and the new FEC set is inserted into the - frontier map. This is the common case because we expect FEC sets to - chain linearly the vast majority (ie. not start new forks), so the - new FEC set is simply "advancing" the frontier. The parent FEC set - is also added to the ancestry map, so that every leaf can trace back - to the root. - - If the FEC set's parent is not already in the frontier, the reasm - checks the ancestry map next. If the parent is in the ancestry map, - the reasm knows that this FEC set is starting a new fork, because it - is part of the tree (the ancestry) but not one of the leaves (the - frontier). In this case, the new FEC set is simply inserted into the - frontier map, and now the frontier has an additional fork (leaf). - - Lastly, if the FEC set's parent is not in the ancestry map, the reasm - knows that this FEC set is orphaned. It is inserted into the - orphaned map for later retry of tree insertion when its ancestors - have been inserted. - - Here are some more important details on forks. Note a FEC set can - only start a new fork when it is across a slot boundary (different - slot than its parent). It is invalid for two FEC sets to chain off - the same parent FEC within the same slot - this would imply there are - two FECs keyed by the (same slot, fec_set_idx) combination, which as - detailed earlier, is equivocation. Therefore, only the first FEC set - in a slot can start a fork from the last FEC set in a parent slot. We - know a FEC set is the first one in a slot when the fec_set_idx is 0, - and we know it is the last one when the last shred in the FEC set has - the SLOT_COMPLETE flag set. - - QUERYING - - The reasm can fast O(1) query any FEC set using the key. As - mentioned earlier, any FEC set except the last one in a slot can - derive its direct child's key and therefore query for it. - - For the special case of the first FEC set in a slot, the reasm can - derive the parent key by subtracting the parent_off from the slot. - - CHAINING - - As mentioned in the top-level documentation, the purpose of the reasm - is to chain FEC sets. On insertion, the reasm will attempt to chain - as many FEC sets as possible to the frontier. The reasm does this by - conducting a BFS from the just-inserted FEC set, looking for parents - and orphans to traverse. See `chain` in the .c file for the - implementation. */ - -typedef struct fd_reasm fd_reasm_t; /* forward decl */ - -struct fd_reasm_fec { - - /* Keys */ - - fd_hash_t key; /* map key, merkle root of the FEC set */ - fd_hash_t cmr; /* parent's map key, chained merkle root of the FEC set */ - - /* Pointers */ - - uint next; /* reserved for internal use by fd_pool, fd_map_chain */ - uint parent; /* pool idx of the parent */ - uint child; /* pool idx of the left-child */ - uint sibling; /* pool idx of the right-sibling */ - - /* When it's in the subtrees map, it's also in the subtreel dlist, - which uses these two pointers. */ - struct { - uint prev; - uint next; - } subtreel; - - /* dlist threaded through elements if they are in the out queue. - Internal reasm APIs need to maintain the invariant that elements in - the out dlist must exist in and only in the ancestry/frontier map. - If an element exists in the out dlist, in_out must be 1 (and the - vice versa). */ - struct { - uint prev; - uint next; - } out; - - /* Data (set on insert) */ - - ulong slot; /* slot of the FEC set */ - uint xid_next; /* pool idx of next FEC with same (slot, fec_set_idx). UINT_MAX if no next. - Maintain the order of most recent to least recent. */ - uint fec_set_idx; /* index of first shred in the FEC set */ - ushort parent_off; /* offset for the parent slot of the FEC set */ - ushort data_cnt; /* number of data shreds in the FEC set */ - uchar data_complete; /* whether this FEC completes an entry batch */ - uchar slot_complete; /* whether this FEC completes the slot */ - uchar is_leader; /* whether this FEC was produced by us as leader */ - uchar eqvoc; /* whether this FEC equivocates */ - uchar confirmed; /* whether this FEC has been confirmed */ - uchar popped; /* whether this FEC has been previously delivered by fd_reasm_pop */ - uchar in_out; /* whether this FEC is currently present in the out dlist */ - - /* Data (set by caller) */ - - uchar bank_dead; /* 0: live; 1: dead lineage; 2: abandoned lineage (set by replay) */ - ulong dead_reported; /* dead telemetry row already emitted for this block */ - ulong bank_seq; - uint bank_idx; - uint parent_bank_idx; - - /* Replay needs the completion timestamp for every FEC. Detailed - block-level reception stats are stored separately per slot. */ - ulong fec_completed_ts_nanos; -}; -typedef struct fd_reasm_fec fd_reasm_fec_t; -FD_STATIC_ASSERT( sizeof(fd_reasm_fec_t)==160UL, fd_reasm_fec_footprint ); - -FD_PROTOTYPES_BEGIN - -/* Constructors */ - -/* fd_reasm_{align,footprint} return the required alignment and - footprint of a memory region suitable for use as reasm with up to - fec_max elements. */ - -FD_FN_CONST ulong -fd_reasm_align( void ); - -FD_FN_CONST ulong -fd_reasm_footprint( ulong fec_max ); - -/* fd_reasm_new formats an unused memory region for use as a - reasm. mem is a non-NULL pointer to this region in the local - address space with the required footprint and alignment. */ - -void * -fd_reasm_new( void * shmem, - ulong fec_max, - ulong seed ); - -/* fd_reasm_join joins the caller to the reasm. reasm points - to the first byte of the memory region backing the reasm in the - caller's address space. - - Returns a pointer in the local address space to reasm on - success. */ - -fd_reasm_t * -fd_reasm_join( void * reasm ); - -/* fd_reasm_leave leaves a current local join. Returns a pointer - to the underlying shared memory region on success and NULL on failure - (logs details). Reasons for failure include reasm is NULL. */ - -void * -fd_reasm_leave( fd_reasm_t * reasm ); - -/* fd_reasm_delete unformats a memory region used as a reasm. - Assumes only the nobody is joined to the region. Returns a pointer - to the underlying shared memory region or NULL if used obviously in - error (e.g. reasm is obviously not a reasm ... logs details). - The ownership of the memory region is transferred to the caller. */ - -void * -fd_reasm_delete( void * reasm ); - -/* fd_reasm_query returns a pointer to the ele keyed by merkle_root if - found, NULL otherwise. */ - -fd_reasm_fec_t * -fd_reasm_query( fd_reasm_t * reasm, - fd_hash_t const * merkle_root ); - -/* fd_reasm_{root,parent,child,sibling} returns a pointer in the - caller's address space to the {root,parent,left-child,right-sibling}. - Assumes reasm is a current local join and blk is a valid pointer to a - pool element inside reasm. const versions for each are also - provided. */ - -FD_FN_PURE fd_reasm_fec_t * fd_reasm_root ( fd_reasm_t * reasm ); -FD_FN_PURE fd_reasm_fec_t const * fd_reasm_root_const ( fd_reasm_t const * reasm ); -FD_FN_PURE fd_reasm_fec_t * fd_reasm_parent ( fd_reasm_t * reasm, fd_reasm_fec_t * child ); -FD_FN_PURE fd_reasm_fec_t const * fd_reasm_parent_const ( fd_reasm_t const * reasm, fd_reasm_fec_t const * child ); -FD_FN_PURE fd_reasm_fec_t * fd_reasm_child ( fd_reasm_t * reasm, fd_reasm_fec_t * parent ); -FD_FN_PURE fd_reasm_fec_t const * fd_reasm_child_const ( fd_reasm_t const * reasm, fd_reasm_fec_t const * parent ); -FD_FN_PURE fd_reasm_fec_t * fd_reasm_sibling ( fd_reasm_t * reasm, fd_reasm_fec_t * sibling ); -FD_FN_PURE fd_reasm_fec_t const * fd_reasm_sibling_const( fd_reasm_t const * reasm, fd_reasm_fec_t const * sibling ); - -/* FIXME manifest_block_id */ - -ulong -fd_reasm_slot0( fd_reasm_t * reasm ); - -/* fd_reasm_free returns the free count of FEC sets that can be inserted - into the reasm. */ - -ulong -fd_reasm_free( fd_reasm_t * reasm ); - -/* fd_reasm_peek returns the next successfully reassembled FEC set, NULL - if there is no FEC set to return. This peeks at the head of the - reasm out queue. Any FEC sets in the out queue are part of a - connected ancestry chain to the root therefore a parent is always - guaranteed to be returned by consume before its child (see top-level - documentation for details). In order to actually consume and make - progress on consuming FEC sets, use fd_reasm_pop(). */ - -fd_reasm_fec_t * -fd_reasm_peek( fd_reasm_t * reasm ); - -/* fd_reasm_pop returns the next successfully reassembled FEC set, NULL - if there is no FEC set to return. This pops and returns the head of - the reasm out queue. Any FEC sets in the out queue are part of a - connected ancestry chain to the root. reasm_pop does NOT guarantee - that the parent has been delivered before its child from the - viewpoint of replay; this can occur if banks evicts but reasm - doesn't, or if we forward confirm a child on an equivocating chain. - In this case the child will be delivered before the parent, and the - parent may not have been replayed yet (assigned a bank_idx). */ - -fd_reasm_fec_t * -fd_reasm_pop( fd_reasm_t * reasm ); - -/* fd_reasm_pop_fec consumes a specific queued FEC without consuming the - FECs ahead of it. fec must be a non-NULL element of this reasm. - Returns NULL if it is not queued or is not eligible for delivery. - The caller must ensure that its parent has already - been processed before bypassing the normal delivery order. */ - -fd_reasm_fec_t * -fd_reasm_pop_fec( fd_reasm_t * reasm, - fd_reasm_fec_t * fec ); - -/* fd_reasm_init initializes the reasm with the initial block id and a - given slot. Returns the new root FEC set. */ - -fd_reasm_fec_t * -fd_reasm_init( fd_reasm_t * reasm, - fd_hash_t const * initial_block_id, - ulong slot ); - -/* fd_reasm_insert inserts a new FEC set into reasm. Returns the newly - inserted fd_reasm_fec_t, NULL if unsuccessful. Inserting this FEC - set may make one or more FEC sets available for in-order delivery. - Caller can consume these FEC sets via fd_reasm_pop. - - Insert can fail before acquiring a pool element, in which case it - returns NULL and *evicted remains NULL. If the reasm is full - (fd_reasm_free() returns 1 [sic!]), reasm_insert will evict a FEC set - by the policy outlined in the evict function. The evicted FEC set(s) - will be removed from reasm, but will remain in the pool. If no FEC - set was able to be evicted after the insert reaches the eviction - path, then evicted will be set to a pool element that is populated - with data of the failed insert. - - It is the caller's responsibility to read, traverse, and release back - to the pool the evicted reasm_fec_t chain before the next - fd_reasm_insert or fd_reasm_remove call. - - See top-level documentation for further details on insertion. - - opt_store and opt_map_join must either both be NULL or identify the - same store. When supplied, reasm releases corresponding store FECs - whenever insertion evicts reassembly entries. */ - -fd_reasm_fec_t * -fd_reasm_insert( fd_reasm_t * reasm, - fd_hash_t const * merkle_root, - fd_hash_t const * chained_merkle_root, - ulong slot, - uint fec_set_idx, - ushort parent_off, - ushort data_cnt, - int data_complete, - int slot_complete, - int leader, - fd_store_t * opt_store, - fd_store_map_t * opt_map_join, - fd_reasm_fec_t ** evicted ); - -/* fd_reasm_remove removes a leaf node or a chain of nodes that - terminates with the provided node from reasm. Returns the start of - the chain of evicted fd_reasm_fec_t. This function cannot return - NULL. - - It is assumed that the passed `head` exists in reasm. If `head` is - in orphans, it is assumed that it is a leaf node, and only `head` - will be cleared. If `head` is in ancestry, a chain of nodes will be - cleared starting from `head` and walking up the tree until one of the - following conditions is met: we reach fec_set_idx 0, we reach a fec - set with an equivocating sibling. Any children nodes of `head` will - be appropriately orphaned. - - Note that an invariant reasm_remove guarantees is that from the - returned head to the end of the chain, it is a linear chain of nodes - with no branches. This is important because it allows the caller to - traverse the chain without needing to BFS. opt_store and - opt_map_join follow the same contract as fd_reasm_insert. - - The evicted fd_reasm_fec_t will be returned as a pointer to a pool - element. At this point the evicted pool element will still be - acquired in the pool, but no longer in any map. It is the caller's - responsibility to read, traverse, and release back to the pool the - evicted reasm_fec_t chain before the next fd_reasm_insert or - fd_reasm_remove call. */ - -fd_reasm_fec_t * -fd_reasm_remove( fd_reasm_t * reasm, - fd_reasm_fec_t * head, - fd_store_t * opt_store, - fd_store_map_t * opt_map_join ); - -/* fd_reasm_pool_release releases a reasm_fec element back to the pool. - Assumes ele is a valid pointer to a pool element inside reasm. This - is exposed to the caller because eviction removes elements from the - maps, but leaves them in the pool for caller to release. */ - -void -fd_reasm_pool_release( fd_reasm_t * reasm, - fd_reasm_fec_t * ele ); - -/* fd_reasm_pool_idx returns the pool index of the provided element. - Assumes ele is a valid pointer to a pool element inside reasm. This - is exposed to caller so that it can link elements as a dlist. */ -ulong -fd_reasm_pool_idx( fd_reasm_t * reasm, fd_reasm_fec_t * ele ); - -/* fd_reasm_confirm confirms the FEC keyed by block_id. The ancestry - beginning from this FEC then becomes the canonical chain of FEC sets - back to the reasm root, and any equivocating siblings along this - chain will not be delivered by fd_reasm_pop. If the FEC is not found - or not part of the connected tree, this has no effect. - - Because fd_reasm_pop is usually called eagerly during replay before - confirmation, it's possible that a FEC set not in this ancestry chain - was delivered prior to this confirmation. Consumers will observe at - most two versions of a given FEC xid (slot, fec_set_idx). - - Note that while this may appear to be an expensive operation linear - in the length of the ancestry chain, the traversal can terminate at - the previous confirmation and therefore the cost is amortized across - n inserts since the last confirmation. - - Note also that it's possible for confirmation to precede insertion. - This is ok because confirmations of descendants imply confirmation of - ancestors. Thus, an earlier "missed" confirmation will still trigger - with a subsequent descendant's confirmation. */ - -void -fd_reasm_confirm( fd_reasm_t * reasm, - fd_hash_t const * block_id ); - -/* fd_reasm_publish publishes merkle root as the new reasm root, pruning - (ie. map remove and pool release) any FEC sets that do not descend - from this new root. opt_store and opt_map_join follow the same - contract as fd_reasm_insert. */ - -fd_reasm_fec_t * -fd_reasm_publish( fd_reasm_t * reasm, - fd_hash_t const * merkle_root, - fd_store_t * opt_store, - fd_store_map_t * opt_map_join ); - -void -fd_reasm_print( fd_reasm_t const * reasm ); - -FD_PROTOTYPES_END - -#endif /* HEADER_fd_src_discof_reasm_fd_reasm_h */ diff --git a/src/discof/reasm/fd_reasm_private.h b/src/discof/reasm/fd_reasm_private.h deleted file mode 100644 index adcbf00be72..00000000000 --- a/src/discof/reasm/fd_reasm_private.h +++ /dev/null @@ -1,110 +0,0 @@ -#ifndef HEADER_fd_src_discof_reasm_fd_reasm_private_h -#define HEADER_fd_src_discof_reasm_fd_reasm_private_h - -#include "fd_reasm.h" -#include "../../util/fd_hash32.h" - -#define POOL_NAME pool -#define POOL_T fd_reasm_fec_t -#define POOL_IDX_T uint -#include "../../util/tmpl/fd_pool.c" - -#define MAP_NAME ancestry -#define MAP_ELE_T fd_reasm_fec_t -#define MAP_KEY_T fd_hash_t -#define MAP_IDX_T uint -#define MAP_KEY_EQ(k0,k1) (!memcmp((k0),(k1),sizeof(fd_hash_t))) -#define MAP_KEY_HASH(key,seed) (fd_hash32( (key)->uc, (seed) )) -#include "../../util/tmpl/fd_map_chain.c" - -#define MAP_NAME frontier -#define MAP_ELE_T fd_reasm_fec_t -#define MAP_KEY_T fd_hash_t -#define MAP_IDX_T uint -#define MAP_KEY_EQ(k0,k1) (!memcmp((k0),(k1),sizeof(fd_hash_t))) -#define MAP_KEY_HASH(key,seed) (fd_hash32( (key)->uc, (seed) )) -#include "../../util/tmpl/fd_map_chain.c" - -#define MAP_NAME orphaned -#define MAP_ELE_T fd_reasm_fec_t -#define MAP_KEY_T fd_hash_t -#define MAP_IDX_T uint -#define MAP_KEY_EQ(k0,k1) (!memcmp((k0),(k1),sizeof(fd_hash_t))) -#define MAP_KEY_HASH(key,seed) (fd_hash32( (key)->uc, (seed) )) -#include "../../util/tmpl/fd_map_chain.c" - -#define MAP_NAME subtrees -#define MAP_ELE_T fd_reasm_fec_t -#define MAP_KEY_T fd_hash_t -#define MAP_IDX_T uint -#define MAP_KEY_EQ(k0,k1) (!memcmp((k0),(k1),sizeof(fd_hash_t))) -#define MAP_KEY_HASH(key,seed) (fd_hash32( (key)->uc, (seed) )) -#include "../../util/tmpl/fd_map_chain.c" - -#define DLIST_NAME subtreel -#define DLIST_ELE_T fd_reasm_fec_t -#define DLIST_IDX_T uint -#define DLIST_PREV subtreel.prev -#define DLIST_NEXT subtreel.next -#include "../../util/tmpl/fd_dlist.c" - -#define DLIST_NAME out -#define DLIST_ELE_T fd_reasm_fec_t -#define DLIST_IDX_T uint -#define DLIST_PREV out.prev -#define DLIST_NEXT out.next -#include "../../util/tmpl/fd_dlist.c" - -#define DEQUE_NAME bfs -#define DEQUE_T ulong -#include "../../util/tmpl/fd_deque_dynamic.c" - -struct xid { - ulong key; /* 32 msb slot | 32 lsb fec_set_idx */ - ulong idx; /* pool idx of first FEC seen. Updated only on confirmation. */ - uint cnt; /* count of FECs with this xid key. If > 1, equivocation occurred on this FEC set */ -}; -typedef struct xid xid_t; - -#define MAP_NAME xid -#define MAP_T xid_t -#define MAP_KEY_NULL ULONG_MAX -#define MAP_KEY_INVAL(k) ((k)==MAP_KEY_NULL) -#define MAP_MEMOIZE 0 -#include "../../util/tmpl/fd_map_dynamic.c" - -struct __attribute__((aligned(128UL))) fd_reasm { - ulong slot0; /* special initialization slot. chains first FEC */ - ulong root; /* pool idx of the root FEC set */ - ulong pool_gaddr; /* gaddr of the pool of FEC nodes backing the above maps / tree */ - ulong wksp_gaddr; /* gaddr of this reasm struct within the workspace */ - ancestry_t * ancestry; /* map of mr->fec. non-leaves of the connected tree */ - frontier_t * frontier; /* map of mr->fec. leaves of the connected tree */ - orphaned_t * orphaned; /* map of mr->fec. non-roots of the orphaned subtrees */ - subtrees_t * subtrees; /* map of mr->fec. roots of the orphaned subtrees */ - subtreel_t _subtrlf[1]; /* internal dlist of the elements in subtrees in no particular order */ - subtreel_t * subtreel; /* the join to the dlist */ - - out_t _out[1]; /* delivery queue(dlist) of elements to output */ - out_t * out; /* the join to the dlist */ - - ulong * bfs; /* internal queue of pool idxs for BFS */ - xid_t * xid; /* map of (slot, fec_set_idx)->mr */ -}; - -FD_FN_PURE static inline fd_wksp_t * -wksp( fd_reasm_t const * reasm ) { - return (fd_wksp_t *)( (ulong)reasm - reasm->wksp_gaddr ); -} - -static inline fd_reasm_fec_t * -reasm_pool( fd_reasm_t * reasm ) { - return (fd_reasm_fec_t *)fd_wksp_laddr_fast( wksp( reasm ), reasm->pool_gaddr ); -} - -static inline fd_reasm_fec_t const * -reasm_pool_const( fd_reasm_t const * reasm ) { - return (fd_reasm_fec_t const *)fd_wksp_laddr_fast( wksp( reasm ), reasm->pool_gaddr ); -} - -#endif /* HEADER_fd_src_discof_reasm_fd_reasm_private_h */ diff --git a/src/discof/reasm/fuzz_reasm.c b/src/discof/reasm/fuzz_reasm.c deleted file mode 100644 index 1ed1122ea47..00000000000 --- a/src/discof/reasm/fuzz_reasm.c +++ /dev/null @@ -1,539 +0,0 @@ -#if !FD_HAS_HOSTED -#error "This target requires FD_HAS_HOSTED" -#endif - -#include -#include - -#include "../../util/fd_util.h" -#include "../../util/sanitize/fd_fuzz.h" -#include "fd_reasm.h" -#include "fd_reasm_private.h" - -/* This is a metadata-level actor fuzzer for fd_reasm_t. Raw shred - receive is not exposed by the reasm API, so the program drives the - same FEC metadata operations used by replay: insert, confirm, pop, - remove, and publish. */ - -#define FUZZ_FEC_MAX (16UL) -#define FUZZ_STORE_FEC_MAX (FUZZ_FEC_MAX+2UL) -#define FUZZ_NODE_MAX (26UL) -#define FUZZ_OP_MAX (96UL) -#define FUZZ_WKSP_PG (256UL) - -#define FUZZ_OP_INSERT 0U -#define FUZZ_OP_CONFIRM 1U -#define FUZZ_OP_POP 2U -#define FUZZ_OP_REMOVE 3U -#define FUZZ_OP_PUBLISH 4U -#define FUZZ_OP_QUERY 5U -#define FUZZ_OP_DRAIN 6U -#define FUZZ_OP_CNT 7U -#define FUZZ_PARENT_ROOT ((int)-1) -#define FUZZ_PARENT_FAKE ((int)-2) - -typedef struct { - uchar const * data; - ulong data_sz; - ulong data_off; -} fuzz_cursor_t; - -typedef struct { - int parent; - ulong slot; - uint fec_set_idx; - ushort parent_off; - ushort data_cnt; - uchar data_complete; - uchar slot_complete; - uchar is_leader; - uchar expect_invalid_if_parent_present; -} fuzz_node_desc_t; - -typedef struct { - fd_hash_t key[1]; - fd_hash_t cmr[1]; - int present; /* whether reasm currently has this FEC */ -} fuzz_node_t; - -/* fuzz_model_t shadows reasm so each op can be checked against what we - expect. pop_cnt stamps delivered FECs; remove_cnt and eviction_cnt - gate publish and tell fuzz_verify when a FEC is allowed to vanish. */ - -typedef struct { - fuzz_node_t node[ FUZZ_NODE_MAX ]; - uint pop_cnt; - ulong remove_cnt; - ulong eviction_cnt; -} fuzz_model_t; - -static fd_wksp_t * fuzz_wksp; -static void * fuzz_reasm_mem; -static void * fuzz_store_mem; -static fd_store_t * fuzz_store; -static fd_store_map_t fuzz_store_map[1]; - -static fuzz_node_desc_t const fuzz_desc[ FUZZ_NODE_MAX ] = { - /* parent, slot, fec_idx, parent_off, data_cnt, dc, sc, ldr, invalid */ - { FUZZ_PARENT_ROOT, 1UL, 0U, 1U, 32U, 0U, 0U, 0U, 0U }, - { 0, 1UL, 32U, 1U, 32U, 0U, 0U, 0U, 0U }, - { 1, 1UL, 64U, 1U, 32U, 1U, 1U, 0U, 0U }, - { 2, 2UL, 0U, 1U, 32U, 0U, 0U, 0U, 0U }, - { 3, 2UL, 32U, 1U, 32U, 0U, 0U, 0U, 0U }, - { 4, 2UL, 64U, 1U, 32U, 1U, 1U, 0U, 0U }, - { 2, 3UL, 0U, 2U, 32U, 0U, 0U, 0U, 0U }, - { 6, 3UL, 32U, 2U, 32U, 1U, 1U, 0U, 0U }, - { 5, 4UL, 0U, 2U, 32U, 0U, 0U, 0U, 0U }, - { 8, 4UL, 32U, 2U, 32U, 1U, 1U, 0U, 0U }, - - /* Alternate slot 1 chain with the same (slot,fec_set_idx) values to - exercise equivocation and confirmation backfill. */ - { FUZZ_PARENT_ROOT, 1UL, 0U, 1U, 32U, 0U, 0U, 0U, 0U }, - { 10, 1UL, 32U, 1U, 32U, 0U, 0U, 0U, 0U }, - { 11, 1UL, 64U, 1U, 32U, 1U, 1U, 0U, 0U }, - { 12, 5UL, 0U, 4U, 32U, 1U, 1U, 0U, 0U }, - - /* Invalid when the referenced parent is present. If inserted - before its parent, reasm should hold it as an orphan and drop it - when the bad chain becomes checkable. */ - { 2, 1UL, 96U, 1U, 32U, 0U, 0U, 0U, 1U }, - { 2, 2UL, 32U, 1U, 32U, 0U, 0U, 0U, 1U }, - { 1, 1UL, 64U, 2U, 32U, 0U, 0U, 0U, 1U }, - { 1, 2UL, 0U, 1U, 32U, 0U, 0U, 0U, 1U }, - - /* Backfill-like chain. The fuzzer can insert 20 last, after 18 and - 19 are already orphaned, to connect the whole chain. */ - { 19, 6UL, 64U, 5U, 32U, 1U, 1U, 0U, 0U }, - { 20, 6UL, 32U, 5U, 32U, 0U, 0U, 0U, 0U }, - { 2, 6UL, 0U, 5U, 32U, 0U, 0U, 0U, 0U }, - - /* Fork and same-xid fork off a later slot. */ - { 9, 7UL, 0U, 3U, 32U, 1U, 1U, 0U, 0U }, - { 9, 7UL, 0U, 3U, 32U, 1U, 1U, 0U, 0U }, - - /* Orphan subtree that usually never connects, plus a child that - becomes invalid if its slot-complete parent appears. */ - { 24, 8UL, 32U, 1U, 32U, 1U, 1U, 1U, 0U }, - { FUZZ_PARENT_FAKE, 8UL, 0U, 1U, 32U, 0U, 0U, 1U, 0U }, - { 23, 8UL, 64U, 1U, 32U, 0U, 0U, 1U, 1U }, -}; - -static void -fuzz_fini( void ) { - if( FD_LIKELY( fuzz_wksp ) ) { - fd_wksp_delete_anonymous( fuzz_wksp ); - fuzz_wksp = NULL; - } - fd_halt(); -} - -int -LLVMFuzzerInitialize( int * argc, - char *** argv ) { - putenv( "FD_LOG_BACKTRACE=0" ); - setenv( "FD_LOG_PATH", "", 0 ); - fd_boot( argc, argv ); - fd_log_level_core_set( 3 ); /* crash on warning log */ - fd_log_level_stderr_set( 4 ); - fd_log_level_logfile_set( 4 ); - - fuzz_wksp = fd_wksp_new_anonymous( FD_SHMEM_NORMAL_PAGE_SZ, FUZZ_WKSP_PG, fd_shmem_cpu_idx( 0UL ), "reasm_fuzz", 0UL ); - FD_TEST( fuzz_wksp ); - fuzz_reasm_mem = fd_wksp_alloc_laddr( fuzz_wksp, fd_reasm_align(), fd_reasm_footprint( FUZZ_FEC_MAX ), 1UL ); - FD_TEST( fuzz_reasm_mem ); - fuzz_store_mem = fd_wksp_alloc_laddr( fuzz_wksp, fd_store_align(), - fd_store_footprint( FUZZ_STORE_FEC_MAX, 1UL, 0UL, 0UL, 0UL, 0, 0UL ), 1UL ); - FD_TEST( fuzz_store_mem ); - atexit( fuzz_fini ); - return 0; -} - -static uchar -fuzz_fallback_u8( fuzz_cursor_t const * cur ) { - ulong h = fd_ulong_hash( cur->data_off ^ (cur->data_sz<<1) ^ 0x91fce7c4a1d09b35UL ); - return (uchar)h; -} - -static uchar -fuzz_u8( fuzz_cursor_t * cur ) { - if( FD_LIKELY( cur->data_offdata_sz ) ) - return cur->data[ cur->data_off++ ]; - - cur->data_off++; - return fuzz_fallback_u8( cur ); -} - -static ulong -fuzz_bounded( fuzz_cursor_t * cur, - ulong bound ) { - if( FD_UNLIKELY( bound<=1UL ) ) return 0UL; - - ulong x = 0UL; - for( ulong shift=0UL, max=bound-1UL; max; shift+=8UL, max>>=8UL ) - x |= (ulong)fuzz_u8( cur ) << shift; - return x % bound; -} - -static void -fuzz_hash( fd_hash_t * out, - ulong id ) { - for( ulong i=0UL; i<4UL; i++ ) { - out->ul[ i ] = 0x9e3779b97f4a7c15UL*(id+i+1UL) ^ (0xd1b54a32d192ed03UL + (id<<17)); - } -} - -static void -fuzz_model_init( fuzz_model_t * model, - fd_hash_t const * root_key ) { - fd_memset( model, 0, sizeof(fuzz_model_t) ); - - for( ulong i=0UL; inode[ i ].key, 1000UL+i ); - } - - for( ulong i=0UL; inode[ i ].cmr = *root_key; - } else if( fuzz_desc[ i ].parent==FUZZ_PARENT_FAKE ) { - fuzz_hash( model->node[ i ].cmr, 9000UL+i ); - } else { - *model->node[ i ].cmr = *model->node[ (ulong)fuzz_desc[ i ].parent ].key; - } - } -} - -static int -fuzz_connected( fd_reasm_t * reasm, - fd_reasm_fec_t * fec ) { - fd_reasm_fec_t * root = fd_reasm_root( reasm ); - for( ulong depth=0UL; FD_LIKELY( fec && depth<=FUZZ_FEC_MAX ); depth++ ) { - if( FD_LIKELY( fec==root ) ) return 1; - fec = fd_reasm_parent( reasm, fec ); - } - return 0; -} - -static void -fuzz_model_refresh( fuzz_model_t * model, - fd_reasm_t * reasm, - int may_drop ) { - for( ulong i=0UL; inode[ i ]; - fd_reasm_fec_t * fec = fd_reasm_query( reasm, node->key ); - - /* A present FEC may only vanish as a result of a remove, publish or - eviction. pop, confirm and query never drop a FEC. */ - if( FD_UNLIKELY( !fec && node->present ) ) FD_TEST( may_drop ); - - FD_TEST( !!fd_store_query( fuzz_store_map, node->key )==!!fec ); - - node->present = !!fec; - } -} - -static void -fuzz_release_chain( fd_reasm_t * reasm, - fd_reasm_fec_t * head ) { - while( FD_LIKELY( head ) ) { - fd_reasm_fec_t * next = fd_reasm_child( reasm, head ); - fd_reasm_pool_release( reasm, head ); - head = next; - } -} - -static void -fuzz_verify_out( fd_reasm_t * reasm ) { - fd_reasm_fec_t * pool = reasm_pool( reasm ); - - ulong out_cnt = 0UL; - fd_reasm_fec_t * expect_peek = NULL; - for( out_iter_t iter = out_iter_fwd_init( reasm->out, pool ); - !out_iter_done( iter, reasm->out, pool ); - iter = out_iter_fwd_next( iter, reasm->out, pool ) ) { - fd_reasm_fec_t * fec = out_iter_ele( iter, reasm->out, pool ); - FD_TEST( fec->in_out ); - FD_TEST( ancestry_ele_query( reasm->ancestry, &fec->key, NULL, pool ) || - frontier_ele_query( reasm->frontier, &fec->key, NULL, pool ) ); - if( FD_UNLIKELY( !expect_peek && !fec->popped && ( !fec->eqvoc || fec->confirmed ) ) ) { - expect_peek = fec; - } - out_cnt++; - } - - ulong in_out_cnt = 0UL; - for( ancestry_iter_t iter = ancestry_iter_init( reasm->ancestry, pool ); - !ancestry_iter_done( iter, reasm->ancestry, pool ); - iter = ancestry_iter_next( iter, reasm->ancestry, pool ) ) { - fd_reasm_fec_t * fec = ancestry_iter_ele( iter, reasm->ancestry, pool ); - if( fec->in_out ) in_out_cnt++; - } - for( frontier_iter_t iter = frontier_iter_init( reasm->frontier, pool ); - !frontier_iter_done( iter, reasm->frontier, pool ); - iter = frontier_iter_next( iter, reasm->frontier, pool ) ) { - fd_reasm_fec_t * fec = frontier_iter_ele( iter, reasm->frontier, pool ); - if( fec->in_out ) in_out_cnt++; - } - - FD_TEST( in_out_cnt==out_cnt ); - FD_TEST( fd_reasm_peek( reasm )==expect_peek ); -} - -static void -fuzz_verify( fuzz_model_t * model, - fd_reasm_t * reasm, - int may_drop ) { - FD_TEST( fd_reasm_root( reasm ) ); - fuzz_verify_out( reasm ); - fuzz_model_refresh( model, reasm, may_drop ); -} - -static ulong -fuzz_pick_node( fuzz_cursor_t * cur ) { - return fuzz_bounded( cur, FUZZ_NODE_MAX ); -} - -/* Pick a slot-complete FEC that reasm currently has, scanning from a - fuzzer-chosen offset. Used to target confirm and publish at valid - FECs. require_connected skips FECs not chained to the root; - exclude_root skips the root itself. Returns NULL if none match. */ - -static fd_reasm_fec_t * -fuzz_pick_slot_complete_present( fuzz_cursor_t * cur, - fuzz_model_t * model, - fd_reasm_t * reasm, - int require_connected, - int exclude_root ) { - ulong start = fuzz_pick_node( cur ); - fd_reasm_fec_t * root = fd_reasm_root( reasm ); - - for( ulong j=0UL; jnode[ i ].key ); - if( FD_UNLIKELY( !fec ) ) continue; - if( FD_UNLIKELY( exclude_root && fec==root ) ) continue; - if( FD_UNLIKELY( require_connected && !fuzz_connected( reasm, fec ) ) ) continue; - return fec; - } - - return NULL; -} - -static fd_reasm_fec_t * -fuzz_pick_removable( fuzz_cursor_t * cur, - fuzz_model_t * model, - fd_reasm_t * reasm ) { - fd_reasm_fec_t * pool = reasm_pool( reasm ); - ulong start = fuzz_pick_node( cur ); - fd_reasm_fec_t * root = fd_reasm_root( reasm ); - - for( ulong j=0UL; jnode[ i ].key ); - if( FD_UNLIKELY( !fec || fec==root ) ) continue; - if( FD_UNLIKELY( fd_reasm_child( reasm, fec ) ) ) continue; - - int orphan_tree = !!orphaned_ele_query( reasm->orphaned, &fec->key, NULL, pool ) || - !!subtrees_ele_query( reasm->subtrees, &fec->key, NULL, pool ); - if( FD_UNLIKELY( !orphan_tree && ( !fec->popped || !fuzz_connected( reasm, fec ) ) ) ) continue; - if( FD_UNLIKELY( !orphan_tree && fec->fec_set_idx && !fd_reasm_parent( reasm, fec ) ) ) continue; - return fec; - } - - return NULL; -} - -static void -fuzz_insert( fuzz_cursor_t * cur, - fuzz_model_t * model, - fd_reasm_t * reasm ) { - ulong i = fuzz_pick_node( cur ); - fuzz_node_desc_t const * desc = &fuzz_desc[ i ]; - fuzz_node_t * node = &model->node[ i ]; - - if( FD_UNLIKELY( fd_reasm_query( reasm, node->key ) ) ) return; /* duplicate */ - - /* reasm chains a FEC to the parent named by its chained merkle root. - Snapshot whether that parent is present so we can check reasm's - accept/reject decision against the static node table. */ - fd_reasm_fec_t * parent = fd_reasm_query( reasm, node->cmr ); - - uint data_idx; - FD_TEST( fd_store_fec_data_reserve( fuzz_store, -1, &data_idx, NULL ) ); - ushort shred_sz[ FD_FEC_SHRED_CNT ] = { 0 }; - fd_store_fec_t * store_fec; - FD_TEST( !fd_store_insert( fuzz_store, fuzz_store_map, node->key, 0UL, 0UL, data_idx, 0U, shred_sz, &store_fec ) && store_fec ); - - fd_reasm_fec_t * evicted = NULL; - fd_reasm_fec_t * inserted = fd_reasm_insert( reasm, - node->key, - node->cmr, - desc->slot, - desc->fec_set_idx, - desc->parent_off, - desc->data_cnt, - !!desc->data_complete, - !!desc->slot_complete, - !!desc->is_leader, - fuzz_store, - fuzz_store_map, - &evicted ); - - if( FD_UNLIKELY( evicted ) ) { - model->eviction_cnt++; - fuzz_release_chain( reasm, evicted ); - } - - /* An invalid chain must be rejected the moment its parent is present, - unless an eviction perturbed the tree during this insert. */ - if( FD_UNLIKELY( desc->expect_invalid_if_parent_present && parent && !evicted ) ) - FD_TEST( !inserted ); - - if( FD_LIKELY( inserted ) ) { - FD_TEST( fd_reasm_query( reasm, node->key )==inserted ); - node->present = 1; - - /* A FEC whose parent isn't present yet must be parked as an orphan - subtree root. */ - if( FD_UNLIKELY( !parent ) ) - FD_TEST( subtrees_ele_query( reasm->subtrees, node->key, NULL, reasm_pool( reasm ) ) ); - } else { - FD_TEST( !fd_reasm_query( reasm, node->key ) ); - fd_store_remove( fuzz_store, fuzz_store_map, node->key ); - } -} - -static void -fuzz_confirm( fuzz_cursor_t * cur, - fuzz_model_t * model, - fd_reasm_t * reasm ) { - if( FD_UNLIKELY( fuzz_u8( cur ) & 1U ) ) { - fd_hash_t fake[1]; - fuzz_hash( fake, 12000UL + fuzz_bounded( cur, 64UL ) ); - fd_reasm_confirm( reasm, fake ); - return; - } - - fd_reasm_fec_t * fec = fuzz_pick_slot_complete_present( cur, model, reasm, 1, 0 ); - if( FD_UNLIKELY( !fec ) ) return; - - fd_hash_t key = fec->key; - fd_reasm_confirm( reasm, &key ); - - /* Confirming a present, connected FEC must mark it confirmed. */ - fd_reasm_fec_t * c = fd_reasm_query( reasm, &key ); - FD_TEST( c && c->confirmed ); -} - -static void -fuzz_pop_one( fuzz_model_t * model, - fd_reasm_t * reasm ) { - fd_reasm_fec_t * expected = fd_reasm_peek( reasm ); - fd_reasm_fec_t * popped = fd_reasm_pop ( reasm ); - FD_TEST( popped==expected ); - - if( FD_LIKELY( popped ) ) { - model->pop_cnt++; - popped->bank_idx = model->pop_cnt; - popped->bank_seq = model->pop_cnt; - } -} - -static void -fuzz_remove( fuzz_cursor_t * cur, - fuzz_model_t * model, - fd_reasm_t * reasm ) { - fd_reasm_fec_t * head = fuzz_pick_removable( cur, model, reasm ); - if( FD_UNLIKELY( !head ) ) return; - - fd_reasm_fec_t * evicted = fd_reasm_remove( reasm, head, fuzz_store, fuzz_store_map ); - FD_TEST( evicted ); - model->remove_cnt++; - fuzz_release_chain( reasm, evicted ); -} - -static void -fuzz_publish( fuzz_cursor_t * cur, - fuzz_model_t * model, - fd_reasm_t * reasm ) { - if( FD_UNLIKELY( model->remove_cnt || model->eviction_cnt ) ) return; - - fd_reasm_fec_t * fec = fuzz_pick_slot_complete_present( cur, model, reasm, 1, 1 ); - if( FD_UNLIKELY( !fec ) ) return; - if( FD_UNLIKELY( !fec->popped ) ) return; - - fd_hash_t key = fec->key; - fd_reasm_fec_t * root = fd_reasm_publish( reasm, &key, fuzz_store, fuzz_store_map ); - FD_TEST( root ); - FD_TEST( fd_hash_eq( &root->key, &key ) ); -} - -static void -fuzz_query( fuzz_cursor_t * cur, - fuzz_model_t * model, - fd_reasm_t * reasm ) { - if( fuzz_u8( cur ) & 1U ) { - fd_hash_t fake[1]; - fuzz_hash( fake, 13000UL + fuzz_bounded( cur, 64UL ) ); - FD_TEST( !fd_reasm_query( reasm, fake ) ); - return; - } else { - ulong i = fuzz_pick_node( cur ); - fd_reasm_fec_t * fec = fd_reasm_query( reasm, model->node[ i ].key ); - if( FD_UNLIKELY( fec ) ) { - FD_TEST( fd_hash_eq( &fec->key, model->node[ i ].key ) ); - } - } -} - -int -LLVMFuzzerTestOneInput( uchar const * data, - ulong size ) { - fuzz_cursor_t cur = { .data = data, .data_sz = size, .data_off = 0UL }; - - fd_hash_t root_key[1]; - fuzz_hash( root_key, 1UL + fuzz_bounded( &cur, 8UL ) ); - - fd_reasm_t * reasm = fd_reasm_join( fd_reasm_new( fuzz_reasm_mem, FUZZ_FEC_MAX, fuzz_bounded( &cur, 1024UL ) ) ); - FD_TEST( reasm ); - fuzz_store = fd_store_join( fd_store_new( fuzz_store_mem, FUZZ_STORE_FEC_MAX, 1UL, 0UL, 0UL, 0UL, FD_SHRED_BLK_MAX, - fuzz_bounded( &cur, 1024UL ), 0, 0UL ) ); - FD_TEST( fuzz_store ); - FD_TEST( fd_store_map_ljoin( fuzz_store, fuzz_store_map ) ); - FD_TEST( fd_reasm_init( reasm, root_key, 0UL ) ); - FD_TEST( fd_reasm_root( reasm ) ); - - fuzz_model_t model[1]; - fuzz_model_init( model, root_key ); - - ulong op_cnt = 1UL + fuzz_bounded( &cur, FUZZ_OP_MAX ); - for( ulong op_idx=0UL; op_idx -#include -#include - -#define TEST_STORE_PATH "/tmp/test_reasm_store.db" - -#define PRINT_MAP(name) \ - do { \ - name##_t * name = reasm->name; \ - fd_reasm_fec_t * pool = reasm->pool; \ - FD_LOG_NOTICE(( #name )); \ - for( name##_iter_t iter = name##_iter_init( name, pool ); \ - !name##_iter_done( iter, name, pool ); \ - iter = name##_iter_next( iter, name, pool ) ) { \ - fd_reasm_fec_t const * fec = name##_iter_ele_const( iter, name, pool ); \ - FD_LOG_NOTICE(( "(%lu, %u)", fec->slot, fec->fec_set_idx )); \ - } \ - } while(0) - -static fd_reasm_t * release_test_reasm; -static fd_store_t * release_test_store; -static fd_hash_t release_test_root; -static atomic_ulong release_test_done; - -static int -publish_with_store( int argc FD_PARAM_UNUSED, - char ** argv FD_PARAM_UNUSED ) { - fd_store_map_t map[1]; - FD_TEST( fd_store_map_ljoin( release_test_store, map ) ); - FD_TEST( fd_reasm_publish( release_test_reasm, &release_test_root, release_test_store, map ) ); - atomic_store_explicit( &release_test_done, 1UL, memory_order_release ); - return 0; -} - -static fd_store_fec_t * -store_insert_payload( fd_store_t * store, - fd_store_map_t * map, - int fd, - fd_hash_t const * key, - uchar value ) { - uint data_idx; - uchar * data = fd_store_fec_data_reserve( store, fd, &data_idx, NULL ); - FD_TEST( data ); - data[0] = value; - ushort shred_sz[ FD_FEC_SHRED_CNT ] = { 1 }; - fd_store_fec_t * fec; - FD_TEST( !fd_store_insert( store, map, key, 0UL, 0UL, data_idx, 1U, shred_sz, &fec ) && fec ); - return fec; -} - -void -test_store_release( fd_wksp_t * wksp ) { - ulong reasm_max = 4UL; - void * reasm_mem = fd_wksp_alloc_laddr( wksp, fd_reasm_align(), fd_reasm_footprint( reasm_max ), 1UL ); - fd_reasm_t * reasm = fd_reasm_join( fd_reasm_new( reasm_mem, reasm_max, 42UL ) ); - FD_TEST( reasm ); - - ulong payload_slot_sz = fd_store_payload_slot_sz( 64UL ); - void * store_mem = fd_wksp_alloc_laddr( wksp, fd_store_align(), - fd_store_footprint( reasm_max, 64UL, 0UL, payload_slot_sz, 0UL, 0, 0UL ), 1UL ); - fd_store_t * store = fd_store_join( fd_store_new( store_mem, reasm_max, 64UL, 0UL, - payload_slot_sz, 0UL, FD_SHRED_BLK_MAX, 43UL, 0, 0UL ) ); - FD_TEST( store ); - int fd = fd_store_file_create( TEST_STORE_PATH, store->wire_off, store->disk_max_shreds ); - FD_TEST( fd>=0 ); - fd_store_map_t map[1]; - FD_TEST( fd_store_map_ljoin( store, map ) ); - - fd_hash_t root = { .ul = { 100UL } }; - fd_hash_t a = { .ul = { 200UL } }; - fd_hash_t b = { .ul = { 300UL } }; - fd_reasm_fec_t * evicted[1]; - fd_reasm_init( reasm, &root, 0UL ); - FD_TEST( fd_reasm_insert( reasm, &a, &root, 1UL, 0U, 1U, 32U, 1, 1, 0, store, map, evicted ) ); - FD_TEST( fd_reasm_insert( reasm, &b, &a, 2UL, 0U, 1U, 32U, 1, 1, 0, store, map, evicted ) ); - - fd_store_fec_t * fec_a = store_insert_payload( store, map, fd, &a, 0xa0U ); - fd_store_fec_t * fec_b = store_insert_payload( store, map, fd, &b, 0xb0U ); - FD_TEST( fec_a->data_state==FD_STORE_FEC_DATA_DISK ); - FD_TEST( fec_b->data_state==FD_STORE_FEC_DATA_RAM_READY ); - - if( fd_tile_cnt()>1UL ) { - fd_store_fec_data_view_t view[1]; - FD_TEST( !fd_store_fec_data_view( store, fd, fec_a, view ) ); - FD_TEST( view->data[0]==0xa0U ); - - release_test_reasm = reasm; - release_test_store = store; - release_test_root = b; - atomic_store_explicit( &release_test_done, 0UL, memory_order_relaxed ); - fd_tile_exec_new( 1UL, publish_with_store, 0, NULL ); - while( !FD_VOLATILE_CONST( fec_a->data_consume_pending ) ) FD_SPIN_PAUSE(); - FD_TEST( !atomic_load_explicit( &release_test_done, memory_order_acquire ) ); - FD_TEST( !fd_store_query( map, &a ) ); - FD_TEST( fec_a->data_state==FD_STORE_FEC_DATA_DISK ); - FD_TEST( fec_a->data_pin_cnt==1U ); - fd_store_fec_data_view_release( store, view ); - fd_tile_exec_delete( fd_tile_exec( 1UL ), NULL ); - FD_TEST( atomic_load_explicit( &release_test_done, memory_order_acquire ) ); - } else { - FD_LOG_NOTICE(( "skip: cross-thread store release requires at least two tiles" )); - FD_TEST( fd_reasm_publish( reasm, &b, store, map ) ); - } - - FD_TEST( !fd_store_query( map, &a ) ); - FD_TEST( fd_store_query( map, &b )==fec_b ); - FD_TEST( store->spill_free_cnt==1UL ); - - /* Invalid orphan release. */ - fd_hash_t late_parent = { .ul = { 400UL } }; - fd_hash_t invalid_child = { .ul = { 500UL } }; - FD_TEST( fd_reasm_insert( reasm, &invalid_child, &late_parent, - 4UL, 0U, 1U, 32U, 0, 0, 0, store, map, evicted ) ); - fd_store_fec_t * invalid_store_fec = store_insert_payload( store, map, fd, &invalid_child, 0xc0U ); - FD_TEST( fd_store_query( map, &invalid_child )==invalid_store_fec ); - fd_reasm_fec_t * parent_fec = fd_reasm_insert( reasm, &late_parent, &b, - 3UL, 0U, 1U, 32U, 0, 0, 0, store, map, evicted ); - FD_TEST( parent_fec ); - FD_TEST( !fd_reasm_query( reasm, &invalid_child ) ); - FD_TEST( !fd_store_query( map, &invalid_child ) ); - - /* Direct eviction release. */ - fd_store_fec_t * parent_store_fec = store_insert_payload( store, map, fd, &late_parent, 0xd0U ); - FD_TEST( fd_store_query( map, &late_parent )==parent_store_fec ); - fd_reasm_fec_t * removed_parent = fd_reasm_remove( reasm, parent_fec, store, map ); - FD_TEST( removed_parent==parent_fec ); - FD_TEST( !fd_store_query( map, &late_parent ) ); - fd_reasm_pool_release( reasm, removed_parent ); - - FD_TEST( fd_store_remove( store, map, &b ) ); - close( fd ); - fd_wksp_free_laddr( fd_store_delete( fd_store_leave( store ) ) ); - fd_wksp_free_laddr( fd_reasm_delete( fd_reasm_leave( reasm ) ) ); - FD_TEST( !unlink( TEST_STORE_PATH ) || errno==ENOENT ); -} - -void -test_insert( fd_wksp_t * wksp ) { - ulong fec_max = 32; - void * mem = fd_wksp_alloc_laddr( wksp, fd_reasm_align(), fd_reasm_footprint( fec_max ), 1UL ); - fd_reasm_t * reasm = fd_reasm_join( fd_reasm_new( mem, fec_max, 0UL ) ); - FD_TEST( reasm ); - - fd_reasm_fec_t * pool = reasm_pool( reasm ); - // ulong null = pool_idx_null( pool ); - - ancestry_t * ancestry = reasm->ancestry; - frontier_t * frontier = reasm->frontier; - orphaned_t * orphaned = reasm->orphaned; - subtrees_t * subtrees = reasm->subtrees; - - /* Receive (0, 64) -> (1, 32) -> (1, 0) -> (2, 0) -> (3, 64) -> (2, 32) -> (3, 32) -> (3, 0) */ - - fd_hash_t mr0_64[1] = {{{ 0 }}}; - fd_hash_t mr1_00[1] = {{{ 1 }}}; - fd_hash_t mr1_32[1] = {{{ 2 }}}; - fd_hash_t mr2_00[1] = {{{ 3 }}}; - fd_hash_t mr2_32[1] = {{{ 4 }}}; - fd_hash_t mr3_00[1] = {{{ 5 }}}; - fd_hash_t mr3_32[1] = {{{ 6 }}}; - fd_hash_t mr3_64[1] = {{{ 7 }}}; - - fd_reasm_fec_t * ev[1]; - fd_reasm_init( reasm, mr0_64, 0 ); - fd_reasm_fec_t * f0_64 = fd_reasm_query( reasm, mr0_64 ); - FD_TEST( f0_64 ); - FD_TEST( frontier_ele_query( frontier, &f0_64->key, NULL, pool ) == f0_64 ); - - fd_reasm_fec_t * f1_32 = fd_reasm_insert( reasm, mr1_32, mr1_00, 1, 32, 1, 32, 1, 1, 0, NULL, NULL, ev ); - FD_TEST( subtrees_ele_query( subtrees, &f1_32->key, NULL, pool ) == f1_32 ); - - fd_reasm_fec_t * f1_00 = fd_reasm_insert( reasm, mr1_00, mr0_64, 1, 0, 1, 32, 0, 0, 0, NULL, NULL, ev ); - FD_TEST( ancestry_ele_query( ancestry, &f1_00->key, NULL, pool ) ); - FD_TEST( ancestry_ele_query( ancestry, &f0_64->key, NULL, pool ) ); - FD_TEST( frontier_ele_query( frontier, &f1_32->key, NULL, pool ) ); - - fd_reasm_fec_t * f2_00 = fd_reasm_insert( reasm, mr2_00, mr1_32, 2, 0, 1, 32, 0, 0, 0, NULL, NULL, ev ); - FD_TEST( frontier_ele_query( frontier, &f2_00->key, NULL, pool ) ); - FD_TEST( ancestry_ele_query( ancestry, &f1_32->key, NULL, pool ) ); - - fd_reasm_fec_t * f3_64 = fd_reasm_insert( reasm, mr3_64, mr3_32, 3, 64, 2, 32, 1, 1, 0, NULL, NULL, ev ); - FD_TEST( subtrees_ele_query( subtrees, &f3_64->key, NULL, pool ) ); - - fd_reasm_fec_t * f2_32 = fd_reasm_insert( reasm, mr2_32, mr2_00, 2, 32, 1, 32, 1, 1, 0, NULL, NULL, ev ); - FD_TEST( frontier_ele_query( frontier, &f2_32->key, NULL, pool ) ); - FD_TEST( ancestry_ele_query( ancestry, &f2_00->key, NULL, pool ) ); - - fd_reasm_fec_t * f3_32 = fd_reasm_insert( reasm, mr3_32, mr3_00, 3, 32, 2, 32, 0, 0, 0, NULL, NULL, ev); - FD_TEST( subtrees_ele_query( subtrees, &f3_32->key, NULL, pool ) ); - FD_TEST( orphaned_ele_query( orphaned, &f3_64->key, NULL, pool ) ); - - fd_reasm_fec_t * f3_00 = fd_reasm_insert( reasm, mr3_00, mr1_32, 3, 0, 2, 32, 0, 0, 0, NULL, NULL, ev); - FD_TEST( ancestry_ele_query( ancestry, &f3_00->key, NULL, pool ) ); - FD_TEST( frontier_ele_query( frontier, &f2_32->key, NULL, pool ) ); - FD_TEST( ancestry_ele_query( ancestry, &f3_32->key, NULL, pool ) ); - FD_TEST( frontier_ele_query( frontier, &f3_64->key, NULL, pool ) ); - - fd_hash_t order[7] = { - f1_00->key, - f1_32->key, - f2_00->key, - f2_32->key, - f3_00->key, - f3_32->key, - f3_64->key, - }; - fd_reasm_fec_t * fec = NULL; ulong i = 0; - while( FD_LIKELY( fec = fd_reasm_pop( reasm ) ) ) { FD_TEST( 0==memcmp( &fec->key, &order[i], sizeof(fd_hash_t) ) ); i++; } - FD_TEST( i==sizeof(order) / sizeof(fd_hash_t) ); - - /* Equivocating last FEC set for slot 3 (mr3_64a), child (3, 64) of - parent (3, 32). */ - - fd_hash_t mr3_64a[1] = { { { 9 } } }; /* equivocating */ - fd_reasm_fec_t * f3_64a = fd_reasm_insert( reasm, mr3_64a, mr3_32, 3, 64, 2, 32, 1, 1, 0, NULL, NULL, ev ); - FD_TEST( frontier_ele_query( frontier, &f3_64a->key, NULL, pool ) ); - - /* Equivocating first FEC set for slot 3 (mr3_0a) that chains of a - different parent slot (2, 32) than the other version (mr3_0 chains - off mr1_32). */ - - fd_hash_t mr3_0a[1] = { { { 10 } } }; - fd_reasm_fec_t * f3_0a = fd_reasm_insert( reasm, mr3_0a, mr2_32, 3, 0, 1, 32, 0, 0, 0, NULL, NULL, ev ); - FD_TEST( frontier_ele_query( frontier, &f3_0a->key, NULL, pool ) ); - - fd_wksp_free_laddr( fd_reasm_delete( fd_reasm_leave( reasm ) ) ); -} - -void -test_publish( fd_wksp_t * wksp ) { - ulong fec_max = 32; - void * mem = fd_wksp_alloc_laddr( wksp, fd_reasm_align(), fd_reasm_footprint( fec_max ), 1UL ); - fd_reasm_t * reasm = fd_reasm_join( fd_reasm_new( mem, fec_max, 0UL ) ); - FD_TEST( reasm ); - - fd_hash_t mr0[1] = {{{ 0 }}}; - fd_hash_t mr1[1] = {{{ 1 }}}; - fd_hash_t mr2[1] = {{{ 2 }}}; - fd_hash_t mr3[1] = {{{ 3 }}}; - fd_hash_t mr4[1] = {{{ 4 }}}; - fd_hash_t mr5[1] = {{{ 5 }}}; - fd_hash_t mr6[1] = {{{ 6 }}}; - - /* - slot 0 - | - slot 1 - / \ - slot 2 | - | slot 3 - slot 4 | - slot 5 - | - slot 6 - */ - - /* Set the root (snapshot slot). */ - - fd_reasm_fec_t * ev[1]; - fd_reasm_init( reasm, mr0, 0 ); - - /* Typical startup behavior, turbine orphan FECs added. */ - - fd_reasm_insert( reasm, mr4, mr2, 4, 0, 2, 0, 1, 1, 0, NULL, NULL, ev ); - fd_reasm_insert( reasm, mr6, mr5, 6, 0, 1, 0, 1, 1, 0, NULL, NULL, ev ); - - /* Repairing backwards, interleaved. */ - - fd_reasm_insert( reasm, mr5, mr3, 5, 0, 2, 0, 1, 1, 0, NULL, NULL, ev ); - fd_reasm_insert( reasm, mr2, mr1, 2, 0, 1, 0, 1, 1, 0, NULL, NULL, ev ); - fd_reasm_insert( reasm, mr3, mr1, 3, 0, 2, 0, 1, 1, 0, NULL, NULL, ev ); - fd_reasm_insert( reasm, mr1, mr0, 1, 0, 1, 0, 1, 1, 0, NULL, NULL, ev ); - - /* Check in-order delivery. */ - - fd_reasm_fec_t * fec = NULL; - while( FD_LIKELY( fec ) ) { - fec = fd_reasm_pop( reasm ); - FD_TEST( 0==memcmp( &fec->key, &mr0, sizeof(fd_hash_t) ) ); - FD_TEST( 0==memcmp( &fec->key, &mr1, sizeof(fd_hash_t) ) ); - FD_TEST( 0==memcmp( &fec->key, &mr2, sizeof(fd_hash_t) ) ); - FD_TEST( 0==memcmp( &fec->key, &mr4, sizeof(fd_hash_t) ) ); - FD_TEST( 0==memcmp( &fec->key, &mr3, sizeof(fd_hash_t) ) ); - FD_TEST( 0==memcmp( &fec->key, &mr5, sizeof(fd_hash_t) ) ); - } - - /* Publish. */ - - fd_reasm_fec_t * oldr = fd_reasm_root( reasm ); - FD_TEST( oldr ); - fd_reasm_publish( reasm, mr2, NULL, NULL ); - fd_reasm_fec_t * newr = fd_reasm_root( reasm ); - FD_TEST( newr ); - FD_TEST( 0==memcmp( newr, mr2, sizeof(fd_hash_t) ) ); - FD_TEST( ancestry_ele_query( reasm->ancestry, mr2, NULL, reasm_pool( reasm ) ) != NULL ); - FD_TEST( frontier_ele_query( reasm->frontier, mr4, NULL, reasm_pool( reasm ) ) != NULL ); - FD_TEST( !fd_reasm_query( reasm, mr0 ) ); - FD_TEST( !fd_reasm_query( reasm, mr1 ) ); - FD_TEST( !fd_reasm_query( reasm, mr3 ) ); - FD_TEST( !fd_reasm_query( reasm, mr5 ) ); - - fd_wksp_free_laddr( fd_reasm_delete( fd_reasm_leave( reasm ) ) ); -} - -void -test_eqvoc( fd_wksp_t * wksp ) { - ulong fec_max = 32; - void * mem = fd_wksp_alloc_laddr( wksp, fd_reasm_align(), fd_reasm_footprint( fec_max ), 1UL ); - fd_reasm_t * reasm = fd_reasm_join( fd_reasm_new( mem, fec_max, 0UL ) ); - FD_TEST( reasm ); - - fd_hash_t mr1[1] = {{{ 1 }}}; - fd_hash_t mr1a[1] = {{{ 1, 0xa }}}; - fd_hash_t mr1b[1] = {{{ 1, 0xb }}}; - fd_reasm_fec_t * ev[1]; - - fd_reasm_init( reasm, mr1, 1 ); - fd_reasm_fec_t * root_fec = fd_reasm_query( reasm, mr1 ); - root_fec->parent_off = 1; /* preserve old same-slot root behavior */ - root_fec->slot_complete = 0; - - /* Slot 1 equivocates. */ - - fd_reasm_insert( reasm, mr1a, mr1, /* slot */ 1, /* fec_set_idx */ 32, 1, 32, 0, 0, 0, NULL, NULL, ev ); - fd_reasm_insert( reasm, mr1b, mr1, /* slot */ 1, /* fec_set_idx */ 32, 1, 32, 0, 0, 0, NULL, NULL, ev ); - - fd_reasm_fec_t * fec1a = fd_reasm_query( reasm, mr1a ); - FD_TEST( fec1a ); - FD_TEST( fec1a->eqvoc ); - FD_TEST( !fec1a->confirmed ); - FD_TEST( fec1a==fd_reasm_query( reasm, &out_ele_peek_head( reasm->out, reasm_pool( reasm ) )->key ) ); - - fd_reasm_fec_t * fec1b = fd_reasm_query( reasm, mr1b ); - FD_TEST( fec1b ); - FD_TEST( fec1b->eqvoc ); - FD_TEST( !fec1b->confirmed ); - - /* get second element in out dlist */ - out_iter_t iter = out_iter_fwd_init( reasm->out, reasm_pool( reasm ) ); - iter = out_iter_fwd_next( iter, reasm->out, reasm_pool( reasm ) ); - - FD_TEST( fec1b==fd_reasm_query( reasm, &out_iter_ele( iter, reasm->out, reasm_pool( reasm ) )->key ) ); - - FD_TEST( !fd_reasm_pop( reasm ) ); /* everything is equivocating so nothing should get popped */ - - /* Confirm one branch of the equivocation. */ - - fd_reasm_confirm( reasm, mr1b ); - FD_TEST( fec1b->confirmed ); - FD_TEST( fd_reasm_query( reasm, mr1 )->confirmed ); - - FD_TEST( fd_reasm_pop( reasm ) == fec1b ); /* pop fec1b */ - fd_hash_t mr1ba[1] = {{{ 1, 0xb, 0xa }}}; - fd_hash_t mr1bb[1] = {{{ 1, 0xb, 0xb }}}; - fd_hash_t mr2[1] = {{{ 2 }}}; - - /* Confirm 1bb before it has been inserted. This has no effect. */ - - fd_reasm_confirm( reasm, mr1bb ); - FD_TEST( !fd_reasm_pop( reasm ) ); - - /* Insert some descendants of mr1b. */ - - fd_reasm_insert( reasm, mr1ba, mr1b, /* slot */ 1, 64, /* parent_off */ 1, /* data_cnt */ 32, /* data_complete */ 1, /* slot_complete */ 1, /* is_leader */ 0, NULL, NULL, ev ); - fd_reasm_insert( reasm, mr1bb, mr1b, /* slot */ 1, 64, /* parent_off */ 1, /* data_cnt */ 32, /* data_complete */ 1, /* slot_complete */ 1, /* is_leader */ 0, NULL, NULL, ev ); - fd_reasm_insert( reasm, mr2, mr1bb, /* slot */ 2, 0, /* parent_off */ 1, /* data_cnt */ 32, /* data_complete */ 0, /* slot_complete */ 0, /* is_leader */ 0, NULL, NULL, ev ); - - /* mr1ba and mr1bb should not be delivered by pop even though they are - chained to mr1b because they equivocate and are not valid. */ - - FD_TEST( !fd_reasm_pop( reasm ) ); - - /* Confirming a descendant should confirm the ancestors. Note this - confirms 1bb after the "missed" earlier confirmation (1bb's confirm - arrived before its insertion). */ - - fd_reasm_confirm( reasm, mr2 ); - FD_TEST( fd_reasm_query( reasm, mr2 )->confirmed ); - FD_TEST( fd_reasm_query( reasm, mr1bb )->confirmed ); - FD_TEST( !fd_reasm_query( reasm, mr1ba )->confirmed ); - - /* mr2 should be delivered, mr1bb will be backfilled */ - - FD_TEST( fd_reasm_pop( reasm ) == fd_reasm_query( reasm, mr2 ) ); - FD_TEST( fd_reasm_peek( reasm ) == NULL ); - - fd_wksp_free_laddr( fd_reasm_delete( fd_reasm_leave( reasm ) ) ); -} - -void -test_eqvoc_xid( fd_wksp_t * wksp ) { - ulong fec_max = 32; - void * mem = fd_wksp_alloc_laddr( wksp, fd_reasm_align(), fd_reasm_footprint( fec_max ), 1UL ); - fd_reasm_t * reasm = fd_reasm_join( fd_reasm_new( mem, fec_max, 0UL ) ); - FD_TEST( reasm ); - fd_reasm_fec_t * ev[1]; - - fd_hash_t mr0[1] = {{{ 1, 0 }}}; - fd_reasm_init( reasm, mr0, 0 ); - - fd_hash_t mr1[1] = {{{ 1, 1 }}}; - fd_hash_t mr2[1] = {{{ 1, 2 }}}; - fd_hash_t mr3[1] = {{{ 1, 3 }}}; - fd_hash_t mr4[1] = {{{ 1, 4 }}}; - - fd_reasm_insert( reasm, mr1, mr0, 1, 0, 1, 32, 0, 0, 0, NULL, NULL, ev ); - fd_reasm_insert( reasm, mr2, mr1, 1, 32, 1, 32, 0, 0, 0, NULL, NULL, ev ); - fd_reasm_fec_t * fec3 = fd_reasm_insert( reasm, mr3, mr2, 1, 64, 1, 32, 0, 1, 0, NULL, NULL, ev ); - - ulong last_xid = (1UL << 32) | 64UL; - - FD_TEST( xid_query( reasm->xid, last_xid, NULL )->cnt == 1 ); - FD_TEST( xid_query( reasm->xid, last_xid, NULL )->idx == pool_idx( reasm_pool( reasm ), fec3 ) ); - - fd_reasm_fec_t * fec4 = fd_reasm_insert( reasm, mr4, mr2, 1, 64, 1, 32, 0, 1, 0, NULL, NULL, ev ); /* equivocate on last fec set idx, gets placed at head of list*/ - FD_TEST( xid_query( reasm->xid, last_xid, NULL )->cnt == 2 ); - FD_TEST( xid_query( reasm->xid, last_xid, NULL )->idx == pool_idx( reasm_pool( reasm ), fec4 ) ); - - fd_reasm_confirm( reasm, mr4 ); - FD_TEST( xid_query( reasm->xid, last_xid, NULL )->cnt == 2 ); - FD_TEST( xid_query( reasm->xid, last_xid, NULL )->idx == pool_idx( reasm_pool( reasm ), fec4 ) ); - - /* publishing forward to mr4 should update the xid map to cnt 1 */ - fd_reasm_publish( reasm, mr4, NULL, NULL ); - FD_TEST( xid_query( reasm->xid, last_xid, NULL )->cnt == 1 ); - FD_TEST( xid_query( reasm->xid, last_xid, NULL )->idx == pool_idx( reasm_pool( reasm ), fec4 ) ); - - fd_hash_t mr5[1] = {{{ 1, 5 }}}; - fd_reasm_insert( reasm, mr5, mr4, 2, 0, 1, 32, 0, 1, 0, NULL, NULL, ev ); - fd_reasm_publish( reasm, mr5, NULL, NULL ); - FD_TEST( !xid_query( reasm->xid, last_xid, NULL ) ); -} - -void -test_eqvoc_transitive( fd_wksp_t * wksp ) { - ulong fec_max = 32; - void * mem = fd_wksp_alloc_laddr( wksp, fd_reasm_align(), fd_reasm_footprint( fec_max ), 1UL ); - fd_reasm_t * reasm = fd_reasm_join( fd_reasm_new( mem, fec_max, 0UL ) ); - FD_TEST( reasm ); - - fd_hash_t mr1[1] = {{{ 1 }}}; - fd_hash_t mr2a[1] = {{{ 2, 0xa }}}; - fd_hash_t mr2b[1] = {{{ 2, 0xb }}}; - fd_hash_t mr2aa[1] = {{{ 2, 0xa, 0xa }}}; - fd_hash_t mr3[1] = {{{ 3 }}}; - fd_hash_t mr4[1] = {{{ 4 }}}; - fd_hash_t mr5[1] = {{{ 5 }}}; - - fd_reasm_fec_t * ev[1]; - fd_reasm_init( reasm, mr1, 1 ); - fd_reasm_insert( reasm, mr2a, mr1, /* slot */ 2, /* fec_set_idx */ 0, 1, 32, 0, 0, 0, NULL, NULL, ev ); - fd_reasm_insert( reasm, mr3, mr2aa, /* slot */ 3, /* fec_set_idx */ 0, 1, 32, 0, 0, 0, NULL, NULL, ev ); - fd_reasm_insert( reasm, mr4, mr2aa, /* slot */ 4, /* fec_set_idx */ 0, 2, 32, 0, 1, 0, NULL, NULL, ev ); - - /* Introduce 2b (which equivocates with 2a). */ - - fd_reasm_insert( reasm, mr2b, mr1, /* slot */ 2, /* fec_set_idx */ 0, 1, 32, 0, 0, 0, NULL, NULL, ev ); - - /* Introduce 2aa, which un-orphans 3 and 4. This should transitively - mark 2aa, 3 and 4 all as equivocating because 2a equivocates. */ - - fd_reasm_insert( reasm, mr2aa, mr2a, /* slot */ 2, /* fec_set_idx */ 32, 1, 32, 0, 1, 0, NULL, NULL, ev ); - - FD_TEST( !fd_reasm_query( reasm, mr1 )->eqvoc ); - FD_TEST( fd_reasm_query( reasm, mr2a )->eqvoc ); - FD_TEST( fd_reasm_query( reasm, mr2b )->eqvoc ); - FD_TEST( fd_reasm_query( reasm, mr2aa )->eqvoc ); - FD_TEST( fd_reasm_query( reasm, mr3 )->eqvoc ); - FD_TEST( fd_reasm_query( reasm, mr4 )->eqvoc ); - - /* Confirm 3, which also should confirm mr1aa, mr1a, mr1. */ - - fd_reasm_confirm( reasm, mr4 ); - - FD_TEST( fd_reasm_query( reasm, mr4 )->confirmed ); - FD_TEST( fd_reasm_query( reasm, mr2aa )->confirmed ); - FD_TEST( fd_reasm_query( reasm, mr2a )->confirmed ); - FD_TEST( fd_reasm_query( reasm, mr1 )->confirmed ); - FD_TEST( !fd_reasm_query( reasm, mr3 )->confirmed ); - FD_TEST( !fd_reasm_query( reasm, mr2b )->confirmed ); - - /* Insert 4, which is a child of 3. Even though 3 is eqvoc, 4 should - not be eqvoc because 3 is confirmed. */ - - fd_reasm_insert( reasm, mr5, mr4, /* slot */ 5, /* fec_set_idx */ 0, 1, 32, 0, 0, 0, NULL, NULL, ev ); - - FD_TEST( !fd_reasm_query( reasm, mr5 )->eqvoc ); - FD_TEST( !fd_reasm_query( reasm, mr5 )->confirmed ); - fd_reasm_print( reasm ); -} - -void -test_fec_after_eos(fd_wksp_t *wksp) { - ulong fec_max = 32; - void *mem = fd_wksp_alloc_laddr(wksp, fd_reasm_align(), - fd_reasm_footprint(fec_max), 1UL); - fd_reasm_t *reasm = fd_reasm_join(fd_reasm_new(mem, fec_max, 0UL)); - FD_TEST(reasm); - - fd_hash_t mr0[1] = {{{0}}}; fd_hash_t mr7[1] = {{{7}}}; - fd_hash_t mr1[1] = {{{1}}}; fd_hash_t mr8[1] = {{{8}}}; - fd_hash_t mr2[1] = {{{2}}}; fd_hash_t mr9[1] = {{{9}}}; - fd_hash_t mr3[1] = {{{3}}}; - fd_hash_t mr4[1] = {{{4}}}; - fd_hash_t mr6[1] = {{{6}}}; - fd_reasm_fec_t * ev[1]; - /* slot fecidx p_off data_cnt data_cmpl slot_cmpl is_leader */ - fd_reasm_init(reasm, mr0, 0 ); - fd_reasm_insert(reasm, mr1, mr0, 1, 0, 1, 32, 0, 0, 0, NULL, NULL, ev ); - fd_reasm_insert(reasm, mr2, mr1, 1, 32, 1, 32, 0, 0, 0, NULL, NULL, ev ); - fd_reasm_insert(reasm, mr3, mr2, 1, 64, 1, 32, 0, 1, 0, NULL, NULL, ev ); - FD_TEST( fd_reasm_pop(reasm) == fd_reasm_query(reasm, mr1) ); - /* FECs after a slot-complete parent in the same slot are rejected. */ - FD_TEST( !fd_reasm_insert(reasm, mr4, mr3, 1, 96, 1, 32, 0, 0, 0, NULL, NULL, ev ) ); - FD_TEST( !fd_reasm_query(reasm, mr4) ); - FD_TEST( fd_reasm_pop(reasm) == fd_reasm_query(reasm, mr2) ); - FD_TEST( fd_reasm_pop(reasm) == fd_reasm_query(reasm, mr3) ); - FD_TEST( fd_reasm_pop(reasm) == NULL ); - - fd_reasm_insert( reasm, mr6, mr3, 2, 0, 1, 32, 0, 0, 0, NULL, NULL, ev ); - FD_TEST( fd_reasm_pop(reasm) == fd_reasm_query(reasm, mr6) ); - - /* now get these out of order. */ - fd_reasm_insert( reasm, mr9, mr8, 2, 96, 1, 32, 0, 0, 0, NULL, NULL, ev ); /* currently an orphan */ - fd_reasm_insert( reasm, mr8, mr7, 2, 64, 1, 32, 0, 1, 0, NULL, NULL, ev ); /* middle slot complete */ - - FD_TEST( fd_reasm_query(reasm, mr8) ); - FD_TEST( !fd_reasm_query(reasm, mr8)->eqvoc ); - FD_TEST( !fd_reasm_query(reasm, mr9) ); - - /* now connect slot 2 from frontier to orphan*/ - fd_reasm_insert( reasm, mr7, mr6, 2, 32, 1, 32, 0, 0, 0, NULL, NULL, ev ); - FD_TEST( fd_reasm_pop(reasm) == fd_reasm_query(reasm, mr7) ); - FD_TEST( fd_reasm_pop(reasm) == fd_reasm_query(reasm, mr8) ); - FD_TEST( fd_reasm_pop(reasm) == NULL ); -} - -void -test_xid_capacity( fd_wksp_t * wksp ) { - ulong fec_max = 32; - void * mem = fd_wksp_alloc_laddr( wksp, fd_reasm_align(), fd_reasm_footprint( fec_max ), 1UL ); - fd_reasm_t * reasm = fd_reasm_join( fd_reasm_new( mem, fec_max, 0UL ) ); - FD_TEST( reasm ); - - FD_LOG_NOTICE( ( "xid key cnt %lu", xid_key_max( reasm->xid ) )); - FD_TEST( xid_key_max( reasm->xid ) >= fec_max + fd_ulong_max(fec_max / 1024, 1UL) ); - - fd_wksp_free_laddr( fd_reasm_delete( fd_reasm_leave( reasm ) ) ); - - fec_max = 4096; - mem = fd_wksp_alloc_laddr( wksp, fd_reasm_align(), fd_reasm_footprint( fec_max ), 1UL ); - reasm = fd_reasm_join( fd_reasm_new( mem, fec_max, 0UL ) ); - FD_TEST( reasm ); - - FD_LOG_NOTICE( ( "xid key cnt %lu", xid_key_max( reasm->xid ) )); - FD_TEST( xid_key_max( reasm->xid ) >= fec_max + (fec_max / 1024) ); - - fd_wksp_free_laddr( fd_reasm_delete( fd_reasm_leave( reasm ) ) ); -} - -void -test_evict( fd_wksp_t * wksp ) { - ulong fec_max = 8; - void * mem = fd_wksp_alloc_laddr( wksp, fd_reasm_align(), fd_reasm_footprint( fec_max ), 1UL ); - fd_reasm_t * reasm = fd_reasm_join( fd_reasm_new( mem, fec_max, 0UL ) ); - - /* we expect xid map to hold > fec_max + fec_max / 1024 keys. In this case, it should be at least - fec_max / 1024 is 0..... so idk. lets floor it at 1 in reasm for now. */ - - FD_LOG_NOTICE( ( "xid key max %lu", xid_key_max( reasm->xid ) )); - FD_TEST( xid_key_max( reasm->xid ) >= fec_max + fd_ulong_max(fec_max / 1024, 1UL) ); - FD_TEST( reasm ); - - fd_hash_t mr0[1] = {{{ 0 }}}; fd_hash_t mr9 [1] = {{{ 9 }}}; - fd_hash_t mr1[1] = {{{ 1 }}}; fd_hash_t mr10[1] = {{{ 10 }}}; - fd_hash_t mr2[1] = {{{ 2 }}}; - fd_hash_t mr3[1] = {{{ 3 }}}; - fd_hash_t mr4[1] = {{{ 4 }}}; - fd_hash_t mr5[1] = {{{ 5 }}}; - fd_hash_t mr6[1] = {{{ 6 }}}; - fd_hash_t mr7[1] = {{{ 7 }}}; - fd_hash_t mr8[1] = {{{ 8 }}}; - - fd_reasm_fec_t * evicted[1]; - /* mr cmr slot fec_idx p_off data_cnt - slot_cmpl - - */ - FD_TEST( fd_reasm_init( reasm, mr0, 1 ) ); - FD_TEST( fd_reasm_insert( reasm, mr1, mr0, 2, 0, 1, 32, 0, 1, 0, NULL, NULL, evicted ) ); - FD_TEST( fd_reasm_insert( reasm, mr2, mr1, 3, 0, 1, 32, 0, 1, 0, NULL, NULL, evicted ) ); - /* fork 4 and 5 off 3 */ - FD_TEST( fd_reasm_insert( reasm, mr3, mr2, 4, 0, 1, 32, 0, 0, 0, NULL, NULL, evicted ) ); - FD_TEST( fd_reasm_insert( reasm, mr4, mr2, 5, 0, 2, 32, 0, 0, 0, NULL, NULL, evicted ) ); - FD_TEST( fd_reasm_insert( reasm, mr5, mr4, 5, 32, 2, 32, 0, 1, 0, NULL, NULL, evicted ) ); - fd_reasm_fec_t * replay5 = fd_reasm_query( reasm, mr4 ); - replay5->bank_idx = 1; - replay5 = fd_reasm_query( reasm, mr5 ); - replay5->bank_idx = 1; - - /* now for orphans */ - FD_TEST( fd_reasm_insert( reasm, mr8, mr7, 8, 0, 1, 32, 0, 0, 0, NULL, NULL, evicted ) ); - - /* should be full - 1 */ - fd_reasm_print( reasm ); - FD_TEST( *evicted == NULL ); /* nothing should have been evicted yet */ - - /* evict and unconfirmed orphan */ - FD_TEST( fd_reasm_insert( reasm, mr6, mr5, 6, 0, 1, 32, 0, 0, 0, NULL, NULL, evicted ) ); - FD_TEST( !fd_reasm_query( reasm, mr8 )); /* evicts unconfirmed orphan */ - FD_TEST( *evicted ); - - FD_LOG_NOTICE(("evicted: %lu, %u", (*evicted)->slot, (*evicted)->fec_set_idx )); - - FD_TEST( (*evicted)->slot == 8 && (*evicted)->fec_set_idx == 0 ); - fd_reasm_pool_release( reasm, *evicted ); - - /* evict an unconfirmed leaf */ - FD_TEST( fd_reasm_insert( reasm, mr9, mr8, 8, 32, 1, 32, 0, 1, 0, NULL, NULL, evicted ) ); - FD_TEST( !fd_reasm_query( reasm, mr6 )); /* evicts unconfirmed leaf */ - FD_TEST( evicted[0] ); - FD_TEST( (*evicted)->slot == 6 && (*evicted)->fec_set_idx == 0 ); - fd_reasm_pool_release( reasm, *evicted ); - - FD_TEST( fd_reasm_insert( reasm, mr10, mr9, 10, 0, 2, 32, 0, 0, 0, NULL, NULL, evicted ) ); - - FD_TEST( fd_reasm_query( reasm, mr5 ) ); /* Evicts slot 4, because slot 5 is complete */ - FD_TEST( !fd_reasm_query( reasm, mr3 ) ); - FD_TEST( (*evicted)->slot == 4 && (*evicted)->fec_set_idx == 0 ); - fd_reasm_pool_release( reasm, *evicted ); -} - -/* Verify that every element in the out dlist has in_out==1, is in - ancestry or frontier (ie. the connected tree), and that parents - precede children. */ -static void -verify_out_invariants( fd_reasm_t * reasm ) { - fd_reasm_fec_t * pool = reasm_pool( reasm ); - - /* 1. Every element with in_out==1 is in the dlist and vice versa. */ - ulong out_cnt = 0; - for( out_iter_t iter = out_iter_fwd_init( reasm->out, pool ); - !out_iter_done ( iter, reasm->out, pool ); - iter = out_iter_fwd_next( iter, reasm->out, pool ) ) { - fd_reasm_fec_t * fec = out_iter_ele( iter, reasm->out, pool ); - FD_TEST( fec->in_out ); - - /* Must be in the connected tree (ancestry or frontier). */ - FD_TEST( ancestry_ele_query( reasm->ancestry, &fec->key, NULL, pool ) || - frontier_ele_query( reasm->frontier, &fec->key, NULL, pool ) ); - out_cnt++; - } - - /* Check that no element outside the dlist has in_out==1. */ - ulong in_out_cnt = 0; - for( ancestry_iter_t iter = ancestry_iter_init( reasm->ancestry, pool ); - !ancestry_iter_done( iter, reasm->ancestry, pool ); - iter = ancestry_iter_next( iter, reasm->ancestry, pool ) ) { - fd_reasm_fec_t * fec = ancestry_iter_ele( iter, reasm->ancestry, pool ); - if( fec->in_out ) in_out_cnt++; - } - for( frontier_iter_t iter = frontier_iter_init( reasm->frontier, pool ); - !frontier_iter_done( iter, reasm->frontier, pool ); - iter = frontier_iter_next( iter, reasm->frontier, pool ) ) { - fd_reasm_fec_t * fec = frontier_iter_ele( iter, reasm->frontier, pool ); - if( fec->in_out ) in_out_cnt++; - } - FD_TEST( in_out_cnt == out_cnt ); -} - -/* Verify the out dlist contains exactly the expected keys in order. - `expected` is an array of fd_hash_t, `n` is the count. */ -static void -verify_out_order( fd_reasm_t * reasm, fd_hash_t const * expected, ulong n ) { - fd_reasm_fec_t * pool = reasm_pool( reasm ); - ulong i = 0; - for( out_iter_t iter = out_iter_fwd_init( reasm->out, pool ); - !out_iter_done ( iter, reasm->out, pool ); - iter = out_iter_fwd_next( iter, reasm->out, pool ) ) { - fd_reasm_fec_t * fec = out_iter_ele( iter, reasm->out, pool ); - if( fec->popped ) continue; /* skip already-popped elements */ - FD_TEST( i < n ); - FD_TEST( 0==memcmp( &fec->key, &expected[i], sizeof(fd_hash_t) ) ); - i++; - } - FD_TEST( i == n ); -} - -void -test_validate_cross_slot( fd_wksp_t * wksp ) { - ulong fec_max = 32; - - /* Parent first: reject links that fail each validate() predicate. */ - { - void * mem = fd_wksp_alloc_laddr( wksp, fd_reasm_align(), fd_reasm_footprint( fec_max ), 1UL ); - fd_reasm_t * reasm = fd_reasm_join( fd_reasm_new( mem, fec_max, 0UL ) ); - FD_TEST( reasm ); - - fd_reasm_fec_t * pool = reasm_pool( reasm ); - fd_reasm_fec_t * ev[1]; - - fd_hash_t root[1] = {{{ 100, 0 }}}; - fd_hash_t parent[1] = {{{ 100, 1 }}}; - fd_hash_t complete_parent[1] = {{{ 100, 2 }}}; - fd_hash_t same_parent[1] = {{{ 100, 3 }}}; - fd_hash_t complete_same_parent[1] = {{{ 100, 4 }}}; - fd_hash_t child[1] = {{{ 100, 5 }}}; - fd_hash_t child_bad_fec_idx[1] = {{{ 100, 6 }}}; - fd_hash_t child_bad_parent_off_zero[1]= {{{ 100, 7 }}}; - fd_hash_t child_bad_parent_off_big[1] = {{{ 100, 8 }}}; - fd_hash_t child_bad_parent_slot[1] = {{{ 100, 9 }}}; - fd_hash_t same_bad_fec_idx[1] = {{{ 100, 10 }}}; - fd_hash_t same_bad_parent_off[1] = {{{ 100, 11 }}}; - fd_hash_t same_after_complete[1] = {{{ 100, 12 }}}; - - fd_reasm_init( reasm, root, 0 ); - fd_reasm_insert( reasm, parent, root, 1, 0, 1, 32, 0, 0, 0, NULL, NULL, ev ); - fd_reasm_insert( reasm, complete_parent, root, 10, 0, 10, 32, 0, 1, 0, NULL, NULL, ev ); - fd_reasm_insert( reasm, same_parent, root, 20, 0, 20, 32, 0, 0, 0, NULL, NULL, ev ); - fd_reasm_insert( reasm, complete_same_parent, root, 30, 0, 30, 32, 0, 1, 0, NULL, NULL, ev ); - - FD_TEST( !fd_reasm_insert( reasm, child, parent, 2, 0, 1, 32, 0, 0, 0, NULL, NULL, ev ) ); /* incomplete cross-slot parent */ - FD_TEST( !fd_reasm_insert( reasm, child_bad_fec_idx, complete_parent, 11, 32, 1, 32, 0, 0, 0, NULL, NULL, ev ) ); /* first FEC in child slot must be FEC 0 */ - FD_TEST( !fd_reasm_insert( reasm, child_bad_parent_off_zero, complete_parent, 11, 0, 0, 32, 0, 0, 0, NULL, NULL, ev ) ); /* parent_off must be nonzero */ - FD_TEST( !fd_reasm_insert( reasm, child_bad_parent_off_big, complete_parent, 11, 0, 12, 32, 0, 0, 0, NULL, NULL, ev ) ); /* parent_off must not exceed slot */ - FD_TEST( !fd_reasm_insert( reasm, child_bad_parent_slot, complete_parent, 12, 0, 1, 32, 0, 0, 0, NULL, NULL, ev ) ); /* parent_off must point to linked parent slot */ - FD_TEST( !fd_reasm_insert( reasm, same_bad_fec_idx, same_parent, 20, 64, 20, 32, 0, 0, 0, NULL, NULL, ev ) ); /* same-slot child must be adjacent */ - FD_TEST( !fd_reasm_insert( reasm, same_bad_parent_off, same_parent, 20, 32, 19, 32, 0, 0, 0, NULL, NULL, ev ) ); /* same-slot parent_off must match */ - FD_TEST( !fd_reasm_insert( reasm, same_after_complete, complete_same_parent, 30, 32, 30, 32, 0, 0, 0, NULL, NULL, ev ) ); /* same-slot parent must not be complete */ - - fd_reasm_fec_t * parent_fec = fd_reasm_query( reasm, parent ); - fd_reasm_fec_t * complete_parent_fec = fd_reasm_query( reasm, complete_parent ); - fd_reasm_fec_t * same_parent_fec = fd_reasm_query( reasm, same_parent ); - fd_reasm_fec_t * complete_same_parent_fec = fd_reasm_query( reasm, complete_same_parent ); - FD_TEST( parent_fec ); - FD_TEST( complete_parent_fec ); - FD_TEST( same_parent_fec ); - FD_TEST( complete_same_parent_fec ); - FD_TEST( fd_reasm_child ( reasm, parent_fec )==NULL ); - FD_TEST( fd_reasm_child ( reasm, complete_parent_fec )==NULL ); - FD_TEST( fd_reasm_child ( reasm, same_parent_fec )==NULL ); - FD_TEST( fd_reasm_child ( reasm, complete_same_parent_fec )==NULL ); - FD_TEST( !fd_reasm_query( reasm, child ) ); - FD_TEST( !fd_reasm_query( reasm, child_bad_fec_idx ) ); - FD_TEST( !fd_reasm_query( reasm, child_bad_parent_off_zero ) ); - FD_TEST( !fd_reasm_query( reasm, child_bad_parent_off_big ) ); - FD_TEST( !fd_reasm_query( reasm, child_bad_parent_slot ) ); - FD_TEST( !fd_reasm_query( reasm, same_bad_fec_idx ) ); - FD_TEST( !fd_reasm_query( reasm, same_bad_parent_off ) ); - FD_TEST( !fd_reasm_query( reasm, same_after_complete ) ); - FD_TEST( !subtrees_ele_query( reasm->subtrees, child, NULL, pool ) ); - verify_out_invariants( reasm ); - - FD_TEST( fd_reasm_pop( reasm )==parent_fec ); - FD_TEST( fd_reasm_pop( reasm )==complete_parent_fec ); - FD_TEST( fd_reasm_pop( reasm )==same_parent_fec ); - FD_TEST( fd_reasm_pop( reasm )==complete_same_parent_fec ); - FD_TEST( !fd_reasm_pop( reasm ) ); - - fd_wksp_free_laddr( fd_reasm_delete( fd_reasm_leave( reasm ) ) ); - } - - /* Parent first: accept valid cross-slot and same-slot links. */ - { - void * mem = fd_wksp_alloc_laddr( wksp, fd_reasm_align(), fd_reasm_footprint( fec_max ), 1UL ); - fd_reasm_t * reasm = fd_reasm_join( fd_reasm_new( mem, fec_max, 0UL ) ); - FD_TEST( reasm ); - - fd_reasm_fec_t * ev[1]; - - fd_hash_t root[1] = {{{ 101, 0 }}}; - fd_hash_t parent[1] = {{{ 101, 1 }}}; - fd_hash_t child[1] = {{{ 101, 2 }}}; - fd_hash_t grandchild[1] = {{{ 101, 3 }}}; - - fd_reasm_init( reasm, root, 0 ); - fd_reasm_insert( reasm, parent, root, 1, 0, 1, 32, 0, 1, 0, NULL, NULL, ev ); - fd_reasm_insert( reasm, child, parent, 2, 0, 1, 32, 0, 0, 0, NULL, NULL, ev ); - fd_reasm_insert( reasm, grandchild, child, 2, 32, 1, 32, 0, 0, 0, NULL, NULL, ev ); - - fd_reasm_fec_t * parent_fec = fd_reasm_query( reasm, parent ); - fd_reasm_fec_t * child_fec = fd_reasm_query( reasm, child ); - fd_reasm_fec_t * grandchild_fec = fd_reasm_query( reasm, grandchild ); - FD_TEST( parent_fec ); - FD_TEST( child_fec ); - FD_TEST( grandchild_fec ); - FD_TEST( fd_reasm_child ( reasm, parent_fec )==child_fec ); - FD_TEST( fd_reasm_parent( reasm, child_fec )==parent_fec ); - FD_TEST( fd_reasm_child ( reasm, child_fec )==grandchild_fec ); - FD_TEST( fd_reasm_parent( reasm, grandchild_fec )==child_fec ); - FD_TEST( !child_fec->eqvoc ); - FD_TEST( !grandchild_fec->eqvoc ); - verify_out_invariants( reasm ); - - FD_TEST( fd_reasm_pop( reasm )==parent_fec ); - FD_TEST( fd_reasm_pop( reasm )==child_fec ); - FD_TEST( fd_reasm_pop( reasm )==grandchild_fec ); - FD_TEST( !fd_reasm_pop( reasm ) ); - - fd_wksp_free_laddr( fd_reasm_delete( fd_reasm_leave( reasm ) ) ); - } - - /* Child first: remove invalid orphaned subtree when parent arrives. */ - { - void * mem = fd_wksp_alloc_laddr( wksp, fd_reasm_align(), fd_reasm_footprint( fec_max ), 1UL ); - fd_reasm_t * reasm = fd_reasm_join( fd_reasm_new( mem, fec_max, 0UL ) ); - FD_TEST( reasm ); - - fd_reasm_fec_t * pool = reasm_pool( reasm ); - fd_reasm_fec_t * ev[1]; - - fd_hash_t root[1] = {{{ 102, 0 }}}; - fd_hash_t parent[1] = {{{ 102, 1 }}}; - fd_hash_t child[1] = {{{ 102, 2 }}}; - fd_hash_t grandchild[1] = {{{ 102, 3 }}}; - - fd_reasm_init( reasm, root, 0 ); - fd_reasm_insert( reasm, child, parent, 2, 0, 1, 32, 0, 0, 0, NULL, NULL, ev ); - fd_reasm_insert( reasm, grandchild, child, 2, 32, 1, 32, 0, 0, 0, NULL, NULL, ev ); - fd_reasm_insert( reasm, parent, root, 1, 0, 1, 32, 0, 0, 0, NULL, NULL, ev ); - - fd_reasm_fec_t * parent_fec = fd_reasm_query( reasm, parent ); - fd_reasm_fec_t * child_fec = fd_reasm_query( reasm, child ); - fd_reasm_fec_t * grandchild_fec = fd_reasm_query( reasm, grandchild ); - FD_TEST( parent_fec ); - FD_TEST( !child_fec ); - FD_TEST( !grandchild_fec ); - FD_TEST( fd_reasm_child ( reasm, parent_fec )==NULL ); - FD_TEST( !subtrees_ele_query( reasm->subtrees, child, NULL, pool ) ); - FD_TEST( !orphaned_ele_query( reasm->orphaned, grandchild, NULL, pool ) ); - verify_out_invariants( reasm ); - - FD_TEST( fd_reasm_pop( reasm )==parent_fec ); - FD_TEST( !fd_reasm_pop( reasm ) ); - - fd_wksp_free_laddr( fd_reasm_delete( fd_reasm_leave( reasm ) ) ); - } - - /* Child first: link valid orphaned subtree when parent arrives. */ - { - void * mem = fd_wksp_alloc_laddr( wksp, fd_reasm_align(), fd_reasm_footprint( fec_max ), 1UL ); - fd_reasm_t * reasm = fd_reasm_join( fd_reasm_new( mem, fec_max, 0UL ) ); - FD_TEST( reasm ); - - fd_reasm_fec_t * ev[1]; - - fd_hash_t root[1] = {{{ 103, 0 }}}; - fd_hash_t parent[1] = {{{ 103, 1 }}}; - fd_hash_t child[1] = {{{ 103, 2 }}}; - fd_hash_t grandchild[1] = {{{ 103, 3 }}}; - - fd_reasm_init( reasm, root, 0 ); - fd_reasm_insert( reasm, child, parent, 2, 0, 1, 32, 0, 0, 0, NULL, NULL, ev ); - fd_reasm_insert( reasm, grandchild, child, 2, 32, 1, 32, 0, 0, 0, NULL, NULL, ev ); - fd_reasm_insert( reasm, parent, root, 1, 0, 1, 32, 0, 1, 0, NULL, NULL, ev ); - - fd_reasm_fec_t * parent_fec = fd_reasm_query( reasm, parent ); - fd_reasm_fec_t * child_fec = fd_reasm_query( reasm, child ); - fd_reasm_fec_t * grandchild_fec = fd_reasm_query( reasm, grandchild ); - FD_TEST( parent_fec ); - FD_TEST( child_fec ); - FD_TEST( grandchild_fec ); - FD_TEST( fd_reasm_child ( reasm, parent_fec )==child_fec ); - FD_TEST( fd_reasm_parent( reasm, child_fec )==parent_fec ); - FD_TEST( fd_reasm_child ( reasm, child_fec )==grandchild_fec ); - FD_TEST( fd_reasm_parent( reasm, grandchild_fec )==child_fec ); - FD_TEST( !child_fec->eqvoc ); - FD_TEST( !grandchild_fec->eqvoc ); - verify_out_invariants( reasm ); - - FD_TEST( fd_reasm_pop( reasm )==parent_fec ); - FD_TEST( fd_reasm_pop( reasm )==child_fec ); - FD_TEST( fd_reasm_pop( reasm )==grandchild_fec ); - FD_TEST( !fd_reasm_pop( reasm ) ); - - fd_wksp_free_laddr( fd_reasm_delete( fd_reasm_leave( reasm ) ) ); - } -} - -void -test_remove_deep_orphan_subtree( fd_wksp_t * wksp ) { -# define DEEP_ORPHAN_CHAIN_LEN (4096UL) - - ulong fec_max = DEEP_ORPHAN_CHAIN_LEN + 3UL; - void * mem = fd_wksp_alloc_laddr( wksp, fd_reasm_align(), fd_reasm_footprint( fec_max ), 1UL ); - fd_reasm_t * reasm = fd_reasm_join( fd_reasm_new( mem, fec_max, 0UL ) ); - FD_TEST( reasm ); - - fd_reasm_fec_t * pool = reasm_pool( reasm ); - fd_reasm_fec_t * ev[1]; - - fd_hash_t root[1] = {{{ 104, 0 }}}; - fd_hash_t parent[1] = {{{ 104, 1 }}}; - fd_hash_t chain[ DEEP_ORPHAN_CHAIN_LEN ]; - fd_memset( chain, 0, sizeof(chain) ); - for( ulong i=0UL; i> 8 ); - chain[ i ].uc[ 3 ] = 1U; - } - - FD_TEST( fd_reasm_init( reasm, root, 0UL ) ); - FD_TEST( fd_reasm_insert( reasm, &chain[0], parent, 2UL, 0U, 1, 32, 0, 0, 0, NULL, NULL, ev ) ); - for( ulong i=1UL; isubtrees, &chain[0], NULL, pool ) ); - FD_TEST( orphaned_ele_query( reasm->orphaned, &chain[DEEP_ORPHAN_CHAIN_LEN-1UL], NULL, pool ) ); - - fd_reasm_fec_t * parent_fec = fd_reasm_insert( reasm, parent, root, 1UL, 0U, 1, 32, 0, 0, 0, NULL, NULL, ev ); - FD_TEST( parent_fec ); - - for( ulong i=0UL; isubtrees, &chain[0], NULL, pool ) ); - FD_TEST( !orphaned_ele_query( reasm->orphaned, &chain[DEEP_ORPHAN_CHAIN_LEN-1UL], NULL, pool ) ); - FD_TEST( fd_reasm_free( reasm )==fec_max-2UL ); - verify_out_invariants( reasm ); - - FD_TEST( fd_reasm_pop( reasm )==parent_fec ); - FD_TEST( !fd_reasm_pop( reasm ) ); - - fd_wksp_free_laddr( fd_reasm_delete( fd_reasm_leave( reasm ) ) ); - - FD_LOG_NOTICE(( "test_remove_deep_orphan_subtree passed" )); - -# undef DEEP_ORPHAN_CHAIN_LEN -} - -void -test_confirm_out_ordering( fd_wksp_t * wksp ) { - ulong fec_max = 32; - void * mem = fd_wksp_alloc_laddr( wksp, fd_reasm_align(), fd_reasm_footprint( fec_max ), 1UL ); - fd_reasm_t * reasm = fd_reasm_join( fd_reasm_new( mem, fec_max, 0UL ) ); - FD_TEST( reasm ); - - fd_reasm_fec_t * ev[1]; - - fd_hash_t root[1] = {{{ 100 }}}; - fd_hash_t mrA[1] = {{{ 1 }}}; - fd_hash_t mrB[1] = {{{ 2 }}}; - fd_hash_t mrC[1] = {{{ 3 }}}; - fd_hash_t mrD[1] = {{{ 4 }}}; - - /* Set the root (snapshot slot). */ - fd_reasm_init( reasm, root, 0 ); - verify_out_invariants( reasm ); - - /* Case 1: Linear chain inserted in order. - Insert A -> B -> C -> D, all connected immediately. - Out dlist should be [A, B, C, D] after inserts. */ - - fd_reasm_insert( reasm, mrA, root, 1, 0, 1, 32, 0, 0, 0, NULL, NULL, ev ); - fd_reasm_insert( reasm, mrB, mrA, 1, 32, 1, 32, 0, 0, 0, NULL, NULL, ev ); - fd_reasm_insert( reasm, mrC, mrB, 1, 64, 1, 32, 0, 0, 0, NULL, NULL, ev ); - fd_reasm_insert( reasm, mrD, mrC, 1, 96, 1, 32, 0, 1, 0, NULL, NULL, ev ); - - verify_out_invariants( reasm ); - - /* Verify insertion order: A, B, C, D. */ - { - fd_hash_t expected[4] = { *mrA, *mrB, *mrC, *mrD }; - verify_out_order( reasm, expected, 4 ); - } - - /* Pop all so we start fresh for next cases. */ - FD_TEST( fd_reasm_pop( reasm ) ); - FD_TEST( fd_reasm_pop( reasm ) ); - FD_TEST( fd_reasm_pop( reasm ) ); - FD_TEST( fd_reasm_pop( reasm ) ); - FD_TEST( !fd_reasm_pop( reasm ) ); - - fd_wksp_free_laddr( fd_reasm_delete( fd_reasm_leave( reasm ) ) ); - - /* Case 2: Confirm adds FECs to the out dlist that were not already - there (eqvoc FECs that become confirmed). Confirm must maintain - parent-before-child ordering even though it walks upward. - - Build: root -> A -> B -> C, all equivocating. - Since they equivocate and are not confirmed, pop returns nothing. - Then confirm C, which walks C -> B -> A, confirming and adding - each to out. Out ordering must be [A, B, C]. */ - - mem = fd_wksp_alloc_laddr( wksp, fd_reasm_align(), fd_reasm_footprint( fec_max ), 1UL ); - reasm = fd_reasm_join( fd_reasm_new( mem, fec_max, 0UL ) ); - FD_TEST( reasm ); - - fd_hash_t root2[1] = {{{ 200 }}}; - fd_hash_t mr2A[1] = {{{ 11 }}}; - fd_hash_t mr2B[1] = {{{ 12 }}}; - fd_hash_t mr2C[1] = {{{ 13 }}}; - fd_hash_t mr2Ap[1] = {{{ 14 }}}; /* equivocating versions */ - fd_hash_t mr2Bp[1] = {{{ 15 }}}; - fd_hash_t mr2Cp[1] = {{{ 16 }}}; - - fd_reasm_init( reasm, root2, 0 ); - - /* Insert first version of the chain. */ - fd_reasm_insert( reasm, mr2A, root2, 1, 0, 1, 32, 0, 0, 0, NULL, NULL, ev ); - fd_reasm_insert( reasm, mr2B, mr2A, 1, 32, 1, 32, 0, 0, 0, NULL, NULL, ev ); - fd_reasm_insert( reasm, mr2C, mr2B, 1, 64, 1, 32, 0, 1, 0, NULL, NULL, ev ); - - verify_out_invariants( reasm ); - - /* Pop A, B, C — they are not eqvoc yet. */ - FD_TEST( fd_reasm_pop( reasm ) == fd_reasm_query( reasm, mr2A ) ); - FD_TEST( fd_reasm_pop( reasm ) == fd_reasm_query( reasm, mr2B ) ); - FD_TEST( fd_reasm_pop( reasm ) == fd_reasm_query( reasm, mr2C ) ); - verify_out_invariants( reasm ); - - /* Insert equivocating versions. These cause eqvoc to be set on both - versions. The primed versions are in_out but can't be popped. */ - fd_reasm_insert( reasm, mr2Ap, root2, 1, 0, 1, 32, 0, 0, 0, NULL, NULL, ev ); - fd_reasm_insert( reasm, mr2Bp, mr2Ap, 1, 32, 1, 32, 0, 0, 0, NULL, NULL, ev ); - fd_reasm_insert( reasm, mr2Cp, mr2Bp, 1, 64, 1, 32, 0, 1, 0, NULL, NULL, ev ); - - verify_out_invariants( reasm ); - - /* All primed versions are eqvoc, unconfirmed. Pop returns nothing. */ - FD_TEST( !fd_reasm_pop( reasm ) ); - - /* Confirm the primed chain via C'. Walks C' -> B' -> A'. - After confirm, out dlist must deliver just, C'. */ - fd_reasm_confirm( reasm, mr2Cp ); - verify_out_invariants( reasm ); - - FD_TEST( fd_reasm_query( reasm, mr2Ap )->confirmed ); - FD_TEST( fd_reasm_query( reasm, mr2Bp )->confirmed ); - FD_TEST( fd_reasm_query( reasm, mr2Cp )->confirmed ); - - FD_TEST( fd_reasm_pop( reasm ) == fd_reasm_query( reasm, mr2Cp ) ); - FD_TEST( !fd_reasm_pop( reasm ) ); - - fd_wksp_free_laddr( fd_reasm_delete( fd_reasm_leave( reasm ) ) ); - - /* Case 3: Confirm interleaves with insert-driven out pushes. - - Build: root -> A -> B (all eqvoc, not in out because popped already - or similar). Then insert C (child of B), which pushes C to out. - Then confirm D (child of C), which should insert D after C in out. - - More concretely: - root -> A -> B -> C -> D -> E - A and B are inserted and popped (non-eqvoc initially). - Then equivocating versions A', B' arrive, marking the originals eqvoc. - C is inserted (connected to B, eqvoc propagated from B). - D is inserted (connected to C, eqvoc propagated). - E is inserted (connected to D, eqvoc propagated). - All of C, D, E are in out but can't be popped (eqvoc, unconfirmed). - Confirm E: walks E -> D -> C -> B -> A. B and A are already popped. - C, D, E should become confirmed. - Pop should return C, D, E in that order. */ - - mem = fd_wksp_alloc_laddr( wksp, fd_reasm_align(), fd_reasm_footprint( fec_max ), 1UL ); - reasm = fd_reasm_join( fd_reasm_new( mem, fec_max, 0UL ) ); - FD_TEST( reasm ); - - fd_hash_t root3[1] = {{{ 67 }}}; - fd_hash_t mr3A[1] = {{{ 21 }}}; - fd_hash_t mr3B[1] = {{{ 22 }}}; - fd_hash_t mr3C[1] = {{{ 23 }}}; - fd_hash_t mr3D[1] = {{{ 24 }}}; - fd_hash_t mr3E[1] = {{{ 25 }}}; - fd_hash_t mr3Ap[1] = {{{ 26 }}}; /* equivocating */ - fd_hash_t mr3Bp[1] = {{{ 27 }}}; - - fd_reasm_init( reasm, root3, 0 ); - - /* Insert A and B, pop them (not eqvoc yet). */ - fd_reasm_insert( reasm, mr3A, root3, 1, 0, 1, 32, 0, 0, 0, NULL, NULL, ev ); - fd_reasm_insert( reasm, mr3B, mr3A, 1, 32, 1, 32, 0, 0, 0, NULL, NULL, ev ); - FD_TEST( fd_reasm_pop( reasm ) == fd_reasm_query( reasm, mr3A ) ); - FD_TEST( fd_reasm_pop( reasm ) == fd_reasm_query( reasm, mr3B ) ); - verify_out_invariants( reasm ); - - /* Insert equivocating A', B'. */ - fd_reasm_insert( reasm, mr3Ap, root3, 1, 0, 1, 32, 0, 0, 0, NULL, NULL, ev ); - fd_reasm_insert( reasm, mr3Bp, mr3Ap, 1, 32, 1, 32, 0, 0, 0, NULL, NULL, ev ); - - /* A and B are now eqvoc (already popped). A', B' are eqvoc, in out. */ - FD_TEST( fd_reasm_query( reasm, mr3A )->eqvoc ); - FD_TEST( fd_reasm_query( reasm, mr3B )->eqvoc ); - FD_TEST( fd_reasm_query( reasm, mr3Ap )->eqvoc ); - FD_TEST( fd_reasm_query( reasm, mr3Bp )->eqvoc ); - - /* Insert C, D, E — children of B. They are eqvoc because B is - eqvoc and unconfirmed. */ - fd_reasm_insert( reasm, mr3C, mr3B, 1, 64, 1, 32, 0, 0, 0, NULL, NULL, ev ); - fd_reasm_insert( reasm, mr3D, mr3C, 1, 96, 1, 32, 0, 0, 0, NULL, NULL, ev ); - fd_reasm_insert( reasm, mr3E, mr3D, 1, 128, 1, 32, 0, 1, 0, NULL, NULL, ev ); - - FD_TEST( fd_reasm_query( reasm, mr3C )->eqvoc ); - FD_TEST( fd_reasm_query( reasm, mr3D )->eqvoc ); - FD_TEST( fd_reasm_query( reasm, mr3E )->eqvoc ); - - verify_out_invariants( reasm ); - - /* Nothing poppable — all eqvoc, unconfirmed. */ - //FD_TEST( !fd_reasm_pop( reasm ) ); calling reasm pop will clear out CDE. - - /* Confirm E. Walks E -> D -> C -> B -> A. - A and B are already popped so they stay as-is. - C, D, E become confirmed and are already in out. - The dlist ordering must still have C before D before E. */ - fd_reasm_confirm( reasm, mr3E ); - verify_out_invariants( reasm ); - - FD_TEST( fd_reasm_query( reasm, mr3C )->confirmed ); - FD_TEST( fd_reasm_query( reasm, mr3D )->confirmed ); - FD_TEST( fd_reasm_query( reasm, mr3E )->confirmed ); - - FD_TEST( fd_reasm_pop( reasm ) == fd_reasm_query( reasm, mr3C ) ); - FD_TEST( fd_reasm_pop( reasm ) == fd_reasm_query( reasm, mr3D ) ); - FD_TEST( fd_reasm_pop( reasm ) == fd_reasm_query( reasm, mr3E ) ); - FD_TEST( !fd_reasm_pop( reasm ) ); - - fd_wksp_free_laddr( fd_reasm_delete( fd_reasm_leave( reasm ) ) ); - FD_LOG_NOTICE(( "test_confirm_out_ordering passed" )); -} - -void -test_remove_bank_eviction( fd_wksp_t * wksp ) { - /* Tree structure after all inserts: - - root(0,0) ── (1,0) ── (1,32) ── (1,64)[slot_complete] - ├── (2,0) ── (2,32) ── (2,64) ── (2,96) - └── (3,0) ── (3,32) - - Simulate the replay tile having popped and replayed the first 2 - FECs of slot 1 ((1,0) and (1,32)). Then simulate bank eviction - by calling fd_reasm_remove on (1,32) — the latest FEC that was - replayed on this bank */ - - ulong fec_max = 32; - void * mem = fd_wksp_alloc_laddr( wksp, fd_reasm_align(), fd_reasm_footprint( fec_max ), 1UL ); - fd_reasm_t * reasm = fd_reasm_join( fd_reasm_new( mem, fec_max, 0UL ) ); - FD_TEST( reasm ); - - fd_reasm_fec_t * pool = reasm_pool( reasm ); - frontier_t * frontier = reasm->frontier; - subtrees_t * subtrees = reasm->subtrees; - orphaned_t * orphaned = reasm->orphaned; - - fd_reasm_fec_t * ev[1]; - - /* Merkle roots for each FEC. */ - fd_hash_t mr_root[1] = {{{ 99 }}}; - - fd_hash_t mr1_0 [1] = {{{ 10 }}}; - fd_hash_t mr1_32[1] = {{{ 11 }}}; - fd_hash_t mr1_64[1] = {{{ 12 }}}; - - fd_hash_t mr2_0 [1] = {{{ 20 }}}; - fd_hash_t mr2_32[1] = {{{ 21 }}}; - fd_hash_t mr2_64[1] = {{{ 22 }}}; - fd_hash_t mr2_96[1] = {{{ 23 }}}; - - fd_hash_t mr3_0 [1] = {{{ 30 }}}; - fd_hash_t mr3_32[1] = {{{ 31 }}}; - - /* Insert root (snapshot slot). */ - fd_reasm_init( reasm, mr_root, 0 ); - - /* Insert slot 1: 3 FECs. (1,64) has slot_complete. */ - /* mr cmr slot fec_idx p_off data_cnt dc sc ldr */ - fd_reasm_insert( reasm, mr1_0, mr_root, 1, 0, 1, 32, 0, 0, 0, NULL, NULL, ev ); - fd_reasm_insert( reasm, mr1_32, mr1_0, 1, 32, 1, 32, 0, 0, 0, NULL, NULL, ev ); - fd_reasm_insert( reasm, mr1_64, mr1_32, 1, 64, 1, 32, 0, 1, 0, NULL, NULL, ev ); - - /* Insert slot 2: 4 FECs chaining off slot 1. */ - fd_reasm_insert( reasm, mr2_0, mr1_64, 2, 0, 1, 32, 0, 0, 0, NULL, NULL, ev ); - fd_reasm_insert( reasm, mr2_32, mr2_0, 2, 32, 1, 32, 0, 0, 0, NULL, NULL, ev ); - fd_reasm_insert( reasm, mr2_64, mr2_32, 2, 64, 1, 32, 0, 0, 0, NULL, NULL, ev ); - fd_reasm_insert( reasm, mr2_96, mr2_64, 2, 96, 1, 32, 0, 1, 0, NULL, NULL, ev ); - - /* Insert slot 3: 2 FECs chaining off slot 1 (fork). */ - fd_reasm_insert( reasm, mr3_0, mr1_64, 3, 0, 2, 32, 0, 0, 0, NULL, NULL, ev ); - fd_reasm_insert( reasm, mr3_32, mr3_0, 3, 32, 2, 32, 0, 1, 0, NULL, NULL, ev ); - - verify_out_invariants( reasm ); - - /* Simulate replay: pop the first 2 FECs in slot 1. - In the replay tile, this means the bank has replayed (1,0) and - (1,32). The replay tile's latest_mr would be mr1_32. */ - - fd_reasm_fec_t * popped1 = fd_reasm_pop( reasm ); - FD_TEST( popped1 ); - FD_TEST( 0==memcmp( &popped1->key, mr1_0, sizeof(fd_hash_t) ) ); - popped1->bank_idx = 1; /* simulate bank assignment */ - - fd_reasm_fec_t * popped2 = fd_reasm_pop( reasm ); - FD_TEST( popped2 ); - FD_TEST( 0==memcmp( &popped2->key, mr1_32, sizeof(fd_hash_t) ) ); - popped2->bank_idx = 1; /* same bank */ - - /* Simulate bank eviction: call fd_reasm_remove on the second FEC - that was popped ((1,32)), mimicking the replay tile querying - latest_mr and passing the result to remove. */ - - fd_reasm_fec_t * fec_to_evict = fd_reasm_query( reasm, mr1_32 ); - FD_TEST( fec_to_evict ); - FD_TEST( fec_to_evict->child != pool_idx_null( reasm_pool( reasm ) ) ); /* NOT a leaf */ - - fd_reasm_fec_t * evicted_head = fd_reasm_remove( reasm, fec_to_evict, NULL, NULL ); - FD_TEST( evicted_head ); - - /* The evicted chain should be (1,0) -> (1,32). head walks up from - (1,32) to (1,0) because fec_set_idx 0 is the stop condition. */ - - FD_TEST( 0==memcmp( &evicted_head->key, mr1_0, sizeof(fd_hash_t) ) ); - fd_reasm_fec_t * evicted_child = fd_reasm_child( reasm, evicted_head ); - FD_TEST( 0==memcmp( &evicted_child->key, mr1_32, sizeof(fd_hash_t) ) ); - - /* (1,0) and (1,32) should no longer be in any map. */ - - FD_TEST( !fd_reasm_query( reasm, mr1_0 ) ); - FD_TEST( !fd_reasm_query( reasm, mr1_32 ) ); - - /* (1,64) should now be a subtree root — it was the direct child of - tail=(1,32) and became orphaned by the removal. */ - - FD_TEST( subtrees_ele_query( subtrees, mr1_64, NULL, pool ) ); - - /* (1,64)'s children from slot 2 and slot 3 should be in orphaned. */ - - FD_TEST( orphaned_ele_query( orphaned, mr2_0, NULL, pool ) ); - FD_TEST( orphaned_ele_query( orphaned, mr2_32, NULL, pool ) ); - FD_TEST( orphaned_ele_query( orphaned, mr2_64, NULL, pool ) ); - FD_TEST( orphaned_ele_query( orphaned, mr2_96, NULL, pool ) ); - FD_TEST( orphaned_ele_query( orphaned, mr3_0, NULL, pool ) ); - FD_TEST( orphaned_ele_query( orphaned, mr3_32, NULL, pool ) ); - - /* Slot 2's first FEC and slot 3's first FEC should be subtree roots - (they are direct children of (1,64) which is the tail's child, so - actually (2,0) and (3,0) should be subtree roots if the code - re-roots each direct child of tail). */ - - /* The out dlist should be empty — all previously connected FECs - were either popped ((1,0), (1,32)) or orphaned ((1,64) and - descendants). */ - - FD_TEST( out_is_empty( reasm->out, pool ) ); - - /* The root should now be a frontier leaf (its only child (1,0) was - evicted, so it has no children). */ - - FD_TEST( frontier_ele_query( frontier, mr_root, NULL, pool ) ); - - /* Clean up: release evicted elements back to pool. */ - - fd_reasm_pool_release( reasm, evicted_child ); - fd_reasm_pool_release( reasm, evicted_head ); - - fd_wksp_free_laddr( fd_reasm_delete( fd_reasm_leave( reasm ) ) ); - - FD_LOG_NOTICE(( "test_remove_bank_eviction passed" )); -} - -void -test_insert_rejects_when_full_and_nothing_is_evictable( fd_wksp_t * wksp ) { - ulong fec_max = 4UL; - void * mem = fd_wksp_alloc_laddr( wksp, fd_reasm_align(), fd_reasm_footprint( fec_max ), 1UL ); - fd_reasm_t * reasm = fd_reasm_join( fd_reasm_new( mem, fec_max, 0UL ) ); - FD_TEST( reasm ); - - fd_hash_t mr0[1] = {{{ 0 }}}; - fd_hash_t mr1[1] = {{{ 1 }}}; - fd_hash_t mr2[1] = {{{ 2 }}}; - fd_hash_t mr3[1] = {{{ 3 }}}; - - fd_reasm_fec_t * evicted[1]; - - FD_TEST( fd_reasm_init( reasm, mr0, 0UL ) ); - FD_TEST( fd_reasm_insert( reasm, mr1, mr0, 1UL, 0U, 1, 32, 0, 1, 0, NULL, NULL, evicted ) ); - FD_TEST( fd_reasm_insert( reasm, mr2, mr1, 2UL, 0U, 1, 32, 0, 1, 0, NULL, NULL, evicted ) ); - - fd_reasm_fec_t * inserted = fd_reasm_insert( reasm, mr3, mr2, 3UL, 0U, 1, 32, 0, 1, 0, NULL, NULL, evicted ); - FD_TEST( inserted == NULL ); - FD_TEST( evicted[0] ); - FD_TEST( 0==memcmp( &evicted[0]->key, mr3, sizeof(fd_hash_t) ) ); - FD_TEST( 0==memcmp( &evicted[0]->cmr, mr2, sizeof(fd_hash_t) ) ); - FD_TEST( evicted[0]->slot == 3UL ); - FD_TEST( evicted[0]->fec_set_idx == 0U ); - FD_TEST( evicted[0]->parent_off == 1U ); - FD_TEST( !fd_reasm_query( reasm, mr3 ) ); - - fd_reasm_pool_release( reasm, evicted[0] ); - - fd_wksp_free_laddr( fd_reasm_delete( fd_reasm_leave( reasm ) ) ); - - FD_LOG_NOTICE(( "test_insert_rejects_when_full_and_nothing_is_evictable passed" )); -} - -void -test_eqvoc_xid_evict( fd_wksp_t * wksp ) { - /* Verify xid->idx correctly tracks the surviving FEC after eviction - when equivocation has occurred (cnt > 1). */ - - ulong fec_max = 32; - void * mem = fd_wksp_alloc_laddr( wksp, fd_reasm_align(), fd_reasm_footprint( fec_max ), 1UL ); - fd_reasm_t * reasm = fd_reasm_join( fd_reasm_new( mem, fec_max, 0UL ) ); - FD_TEST( reasm ); - fd_reasm_fec_t * ev[1]; - - fd_hash_t mr0[1] = {{{ 1, 0 }}}; - fd_reasm_init( reasm, mr0, 0 ); - - fd_hash_t mr1[1] = {{{ 1, 1 }}}; - fd_hash_t mr2[1] = {{{ 1, 2 }}}; - fd_hash_t mr3[1] = {{{ 1, 3 }}}; - fd_hash_t mr4[1] = {{{ 1, 4 }}}; - - /* mr cmr slot fecidx p_off data_cnt d_cmpl s_cmpl ldr */ - fd_reasm_insert( reasm, mr1, mr0, 1, 0, 1, 32, 0, 0, 0, NULL, NULL, ev ); - fd_reasm_insert( reasm, mr2, mr1, 1, 32, 1, 32, 0, 0, 0, NULL, NULL, ev ); - fd_reasm_insert( reasm, mr3, mr2, 1, 64, 1, 32, 0, 1, 0, NULL, NULL, ev ); - fd_reasm_fec_t * fec4 = fd_reasm_insert( reasm, mr4, mr2, 1, 64, 1, 32, 0, 1, 0, NULL, NULL, ev ); - - ulong last_xid = (1UL << 32) | 64UL; - - FD_TEST( xid_query( reasm->xid, last_xid, NULL )->cnt == 2 ); - - /* Confirm fec4 (the second equivocating FEC). This moves fec4 to - the head of the xid linked list. */ - fd_reasm_confirm( reasm, mr4 ); - FD_TEST( xid_query( reasm->xid, last_xid, NULL )->idx == pool_idx( reasm_pool( reasm ), fec4 ) ); - - /* Publish to mr4. This prunes fec3 (the unconfirmed equivocating - FEC) via clear_slot_metadata. After publish, cnt should be 1 and - idx should still point to fec4 (the confirmed survivor). */ - fd_reasm_publish( reasm, mr4, NULL, NULL ); - FD_TEST( xid_query( reasm->xid, last_xid, NULL )->cnt == 1 ); - FD_TEST( xid_query( reasm->xid, last_xid, NULL )->idx == pool_idx( reasm_pool( reasm ), fec4 ) ); - - fd_wksp_free_laddr( fd_reasm_delete( fd_reasm_leave( reasm ) ) ); - mem = fd_wksp_alloc_laddr( wksp, fd_reasm_align(), fd_reasm_footprint( fec_max ), 1UL ); - reasm = fd_reasm_join( fd_reasm_new( mem, fec_max, 0UL ) ); - FD_TEST( reasm ); - - fd_hash_t mra[1] = {{{ 2, 0 }}}; - fd_reasm_init( reasm, mra, 0 ); - - fd_hash_t mrb[1] = {{{ 2, 1 }}}; - fd_hash_t mrc[1] = {{{ 2, 2 }}}; - fd_hash_t mrd[1] = {{{ 2, 3 }}}; - fd_hash_t mre[1] = {{{ 2, 4 }}}; - fd_hash_t mrf[1] = {{{ 2, 5 }}}; - fd_hash_t mrg[1] = {{{ 2, 6 }}}; - - /* mr cmr slot fecidx p_off data_cnt d_cmpl s_cmpl ldr */ - fd_reasm_insert( reasm, mrb, mra, 1, 0, 1, 32, 0, 0, 0, NULL, NULL, ev ); - fd_reasm_insert( reasm, mrc, mrb, 1, 32, 1, 32, 0, 0, 0, NULL, NULL, ev ); - /* fece */ fd_reasm_insert( reasm, mre, mrc, 1, 64, 1, 32, 0, 1, 0, NULL, NULL, ev ); /* third in list */ - fd_reasm_fec_t * fecd = fd_reasm_insert( reasm, mrd, mrc, 1, 64, 1, 32, 0, 1, 0, NULL, NULL, ev ); /* second in list */ - fd_reasm_fec_t * fecf = fd_reasm_insert( reasm, mrf, mrc, 1, 64, 1, 32, 0, 1, 0, NULL, NULL, ev ); /* first in list */ - - ulong xid2 = (1UL << 32) | 64UL; - - FD_TEST( xid_query( reasm->xid, xid2, NULL )->cnt == 3 ); - - /* Confirm fecd (the first equivocating FEC this time). Should not move head of list. */ - fd_reasm_confirm( reasm, mrd ); - FD_TEST( xid_query( reasm->xid, xid2, NULL )->idx == pool_idx( reasm_pool( reasm ), fecf ) ); - - /* simulate eviction of fecf */ - fd_reasm_remove( reasm, fecf, NULL, NULL ); - fd_reasm_pool_release( reasm, fecf ); - - FD_TEST( xid_query( reasm->xid, xid2, NULL )->cnt == 2 ); - FD_TEST( xid_query( reasm->xid, xid2, NULL )->idx == pool_idx( reasm_pool( reasm ), fecd ) ); - - /* Build forward from fecd so publish prunes fece */ - fd_reasm_insert( reasm, mrg, mrd, 2, 0, 1, 32, 0, 1, 0, NULL, NULL, ev ); - fd_reasm_publish( reasm, mrg, NULL, NULL ); - - /* fece should have been pruned. fecd survives at cnt 1. */ - FD_TEST( !xid_query( reasm->xid, xid2, NULL ) ); - - fd_wksp_free_laddr( fd_reasm_delete( fd_reasm_leave( reasm ) ) ); - FD_LOG_NOTICE(( "test_eqvoc_xid_evict passed" )); -} - -static void -test_pop_fec( fd_wksp_t * wksp ) { - void * mem = fd_wksp_alloc_laddr( wksp, fd_reasm_align(), fd_reasm_footprint( 32UL ), 1UL ); - fd_reasm_t * reasm = fd_reasm_join( fd_reasm_new( mem, 32UL, 0UL ) ); - fd_reasm_fec_t * ev[1]; - fd_hash_t root = { .ul = { 100UL } }; - fd_hash_t a = { .ul = { 1UL } }; - fd_hash_t b = { .ul = { 2UL } }; - fd_hash_t c = { .ul = { 3UL } }; - fd_hash_t d = { .ul = { 4UL } }; - fd_reasm_init( reasm, &root, 0UL ); - fd_reasm_fec_t * fa = fd_reasm_insert( reasm, &a, &root, 1UL, 0U, 1U, 32U, 1, 1, 0, NULL, NULL, ev ); - fd_reasm_fec_t * fb = fd_reasm_insert( reasm, &b, &root, 2UL, 0U, 2U, 32U, 1, 0, 1, NULL, NULL, ev ); - fd_reasm_fec_t * fc = fd_reasm_insert( reasm, &c, &b, 2UL, 32U,2U, 32U, 1, 1, 1, NULL, NULL, ev ); - fd_reasm_fec_t * fd = fd_reasm_insert( reasm, &d, &root, 3UL, 0U, 3U, 32U, 1, 1, 0, NULL, NULL, ev ); - - /* Consume the leader chain without disturbing the ordinary FIFO. */ - FD_TEST( fd_reasm_peek( reasm )==fa ); - FD_TEST( fd_reasm_pop_fec( reasm, fb )==fb ); - FD_TEST( !fb->in_out && fb->popped ); - FD_TEST( !fd_reasm_pop_fec( reasm, fb ) ); - fd_reasm_confirm( reasm, &b ); - FD_TEST( !fb->in_out ); - FD_TEST( fd_reasm_pop_fec( reasm, fc )==fc ); - verify_out_invariants( reasm ); - FD_TEST( fd_reasm_pop( reasm )==fa ); - FD_TEST( fd_reasm_pop( reasm )==fd ); - FD_TEST( !fd_reasm_pop( reasm ) ); - - /* A queued equivocating FEC still requires confirmation. */ - fd_hash_t e = { .ul = { 5UL } }; - fd_hash_t f = { .ul = { 6UL } }; - fd_reasm_fec_t * fe = fd_reasm_insert( reasm, &e, &d, 4UL, 0U, 1U, 32U, 1, 1, 1, NULL, NULL, ev ); - fd_reasm_insert( reasm, &f, &d, 4UL, 0U, 1U, 32U, 1, 1, 0, NULL, NULL, ev ); - FD_TEST( fe->eqvoc ); - FD_TEST( !fd_reasm_pop_fec( reasm, fe ) ); - FD_TEST( fe->in_out && !fe->popped ); - fd_reasm_confirm( reasm, &e ); - FD_TEST( fd_reasm_pop_fec( reasm, fe )==fe ); - FD_TEST( fd_reasm_publish( reasm, &e, NULL, NULL ) ); - verify_out_invariants( reasm ); - FD_TEST( !fd_reasm_pop( reasm ) ); - fd_wksp_free_laddr( fd_reasm_delete( fd_reasm_leave( reasm ) ) ); - FD_LOG_NOTICE(( "test_pop_fec passed" )); -} - -int -main( int argc, char ** argv ) { - fd_boot( &argc, &argv ); - - int require_multitile = fd_env_strip_cmdline_contains( &argc, &argv, "--require-multitile" ); - if( FD_UNLIKELY( require_multitile && fd_tile_cnt()<2UL ) ) - FD_LOG_ERR(( "--require-multitile needs at least two tile CPUs" )); - - char const * _page_sz = fd_env_strip_cmdline_cstr ( &argc, &argv, "--page-sz", NULL, "gigantic" ); - ulong page_cnt = fd_env_strip_cmdline_ulong( &argc, &argv, "--page-cnt", NULL, 1UL ); - ulong numa_idx = fd_env_strip_cmdline_ulong( &argc, &argv, "--numa-idx", NULL, fd_shmem_numa_idx( 0 ) ); - ulong page_sz = fd_cstr_to_shmem_page_sz( _page_sz ); - if( FD_UNLIKELY( !page_sz ) ) FD_LOG_ERR(( "unsupported --page-sz" )); - - fd_wksp_t * wksp = fd_wksp_new_anonymous( page_sz, page_cnt, fd_shmem_cpu_idx( numa_idx ), "wksp", 0UL ); - FD_TEST( wksp ); - - test_insert( wksp ); - test_pop_fec( wksp ); - test_store_release( wksp ); - test_publish( wksp ); - test_eqvoc( wksp ); - test_xid_capacity( wksp ); - test_fec_after_eos( wksp ); - test_eqvoc_transitive( wksp ); - test_eqvoc_xid( wksp ); - test_eqvoc_xid_evict( wksp ); - test_evict( wksp ); - test_validate_cross_slot( wksp ); - test_remove_deep_orphan_subtree( wksp ); - test_confirm_out_ordering( wksp ); - test_remove_bank_eviction( wksp ); - test_insert_rejects_when_full_and_nothing_is_evictable( wksp ); - - fd_halt(); - return 0; -} diff --git a/src/discof/rotor/fd_rotor_tile.h b/src/discof/rotor/fd_rotor_tile.h index 89c458e5fcc..b28006decc0 100644 --- a/src/discof/rotor/fd_rotor_tile.h +++ b/src/discof/rotor/fd_rotor_tile.h @@ -101,10 +101,6 @@ filters and validates aggressively. The responses are matches by nonce and verified before being ingested by the rotor. */ -/* keep in line with repair tile sigs */ -#define REPAIR_SIG_FEC (0UL) -#define REPAIR_SIG_FEC_LEADER (1UL) -#define REPAIR_SIG_FEC_INVALID (2UL) /* alpenglow type - replayable fec */ #define ROTOR_SIG_FEC_REPLAY (3UL) /* alpenglow type - completed block metadata, on rotor_rserve */ diff --git a/src/discof/tower/Local.mk b/src/discof/tower/Local.mk deleted file mode 100644 index f1df97c2b84..00000000000 --- a/src/discof/tower/Local.mk +++ /dev/null @@ -1,5 +0,0 @@ -ifdef FD_HAS_HOSTED -$(call add-objs,fd_tower_tile,fd_discof) -$(call make-unit-test,test_tower_tile,test_tower_tile,fd_discof fd_choreo fd_disco fd_flamenco fd_waltz fd_tango fd_ballet fd_util) -$(call run-unit-test,test_tower_tile) -endif diff --git a/src/discof/tower/fd_tower_slot_rooted.h b/src/discof/tower/fd_tower_slot_rooted.h deleted file mode 100644 index 94f6486b6c8..00000000000 --- a/src/discof/tower/fd_tower_slot_rooted.h +++ /dev/null @@ -1,39 +0,0 @@ -#ifndef HEADER_fd_src_discof_tower_fd_tower_slot_rooted_h -#define HEADER_fd_src_discof_tower_fd_tower_slot_rooted_h - -#include "../../disco/fd_disco_base.h" - -/* This belongs most cleanly in fd_tower_tile.h, but the shred tile - needs this type, and the shred tile is in disco, not discof. Nothing - in this header requires discof, so we factor it out so that the shred - tile can just include this one. */ - -// #define FD_TOWER_SIG_SLOT_CONFIRMED (0) -// #define FD_TOWER_SIG_SLOT_DONE (1) -// #define FD_TOWER_SIG_SLOT_DUPLICATE (2) -// #define FD_TOWER_SIG_SLOT_IGNORED (3) -#define FD_TOWER_SIG_SLOT_ROOTED (4) - -/* fd_tower_slot_rooted describes a Tower frag that notifies a new root. - A block is rooted if it or any of its descendants reach max lockout - per TowerBFT rules. Once a block is rooted, it can never be rolled - back (whereas technically, any of the above confirmation levels can - be rolled back, but it is extremely unlikely and would only happen - with a large percentage of malicious stake in the network). This is - the most important confirmation level for Firedancer's internal - structures, since it is the only one that guarantees a block will - never be rolled back, many structures "publish" a new root which - prunes forks that do not descend from that root. - - Note even if tower never actually voted on a slot (and therefore the - slot never became a tower root), tower will still send a root - confirmation for that slot if a descendant is voted on and eventually - rooted. */ - -struct fd_tower_slot_rooted { - ulong slot; - fd_hash_t block_id; -}; -typedef struct fd_tower_slot_rooted fd_tower_slot_rooted_t; - -#endif /* HEADER_fd_src_discof_tower_fd_tower_slot_rooted_h */ diff --git a/src/discof/tower/fd_tower_tile.c b/src/discof/tower/fd_tower_tile.c deleted file mode 100644 index da343b7ee17..00000000000 --- a/src/discof/tower/fd_tower_tile.c +++ /dev/null @@ -1,2194 +0,0 @@ -#define _GNU_SOURCE /* syscall, RENAME_EXCHANGE */ -#include "fd_tower_tile.h" -#include -#include "generated/fd_tower_tile_seccomp.h" - -#include "../../choreo/eqvoc/fd_eqvoc.h" -#include "../../choreo/ghost/fd_ghost.h" -#include "../../choreo/hfork/fd_hfork.h" -#include "../../choreo/votes/fd_votes.h" -#include "../../choreo/tower/fd_tower.h" -#include "../../choreo/tower/fd_tower_file.h" -#include "../../choreo/tower/fd_tower_serdes.h" -#include "../../choreo/tower/fd_tower_stakes.h" -#include "../../disco/fd_txn_p.h" -#include "../../disco/events/generated/fd_event_gen.h" -#include "../../disco/shred/fd_shred_tile.h" -#include "../../disco/keyguard/fd_keyguard.h" -#include "../../disco/keyguard/fd_keyload.h" -#include "../../disco/keyguard/fd_keyswitch.h" -#include "../../disco/metrics/fd_metrics.h" -#include "../../disco/node_info/fd_node_info.h" -#include "../../disco/topo/fd_topo.h" -#include "../../disco/fd_txn_m.h" -#include "../../discof/replay/fd_replay_tile.h" -#include "../../discof/restore/utils/fd_wfs.h" -#include "../../flamenco/leaders/fd_multi_epoch_leaders.h" -#include "../../flamenco/runtime/fd_bank.h" -#include "../../flamenco/leaders/fd_multi_epoch_leaders.h" -#include "../../flamenco/runtime/fd_system_ids.h" -#include "../../flamenco/runtime/sysvar/fd_sysvar_slot_history.h" -#include "../../flamenco/runtime/program/vote/fd_vote_state_versioned.h" -#include "../../flamenco/runtime/program/vote/fd_vote_codec_tmpl.h" -#include "../../util/pod/fd_pod.h" -#include "../../util/fd_hash32.h" - -#include -#include -#include -#include -#include -#include -#include - -/* TODO: the tower file is not read during boot, just during identity - switches. */ - -/* The Tower tile broadly processes three classes of frags, leading to - three distinct kinds of frag processing: - - 1. Processing vote _accounts_ (after replaying a block) - - When Replay finishes executing a block, Tower reads back the vote - account state for every staked validator. This is deterministic: - the vote account state is the result of executing all vote txns in - the block through the vote program, so it is guaranteed to - converge with Agave's view of the same accounts. Tower uses these - accounts to run the fork choice rule (fd_ghost) and TowerBFT - (fd_tower). - - 2. Processing vote _transactions_ (at arbitrary points in time) - - Tower also receives vote txns from Gossip and TPU. These arrive - at arbitrary, nondeterministic times because Gossip and TPU are - both unreliable mediums: there's no guarantee we observe all the - same vote txns as Agave (nor another Firedancer, for that matter). - - Tower is stricter than Agave when validating these vote txns (e.g. - we use is_simple_vote which requires at most two signers, whereas - Agave's Gossip vote parser does not). Being stricter is - acceptable given vote txns from Gossip and TPU are inherently - unreliable, so dropping a small number of votes that Agave allows - but Firedancer does not is not significant to convergence. - - However, these same vote txns are (redundantly) transmitted as - part of a block as well ie. through Replay. The validation of - these Replay-sourced vote txns _is_ one-to-one with Agave (namely - the Vote Program), and critical for convergence. Specifically, we - only process Replay vote txns that have been successfully executed - when counting them towards confirmations. - - The guarantee is "eventual consistency": even though individual - Gossip or TPU vote txns may be lost, we are guaranteed to - "eventually" confirm a block and converge with Agave as long as we - receive the block and replay its contained vote txns, because our - vote programs match 1-1. Gossip / TPU can provide a fast-path for - earlier confirmations as well as a source of security via - redundancy in case we are not receiving the blocks from the rest - of the network. - - The processing of vote txns is important to (as already alluded) - fd_votes and fd_hfork. - - 3. Processing "other" frags. Vote account and vote transaction - processing (1 and 2 above) is the meat and potatoes, but Tower also - processes several auxiliary frag types: - - a. Duplicate shred gossip messages (from the gossip tile): Tower - receives duplicate shred proofs from other validators via - gossip. These proofs arrive in chunks (fd_eqvoc_chunk_insert) - and are reassembled and cryptographically verified before being - accepted. - - b. Epoch stake updates (from the replay tile): Tower receives - epoch stake information to maintain the leader schedule via - fd_stake_ci, which is needed by eqvoc for signature - verification of shred proofs. - - c. Shred version (from the ipecho tile): Tower receives the shred - version from ipecho to configure eqvoc's shred version - filtering for proof verification. - - d. Shreds (from the shred tile): Tower checks incoming shreds for - equivocation via fd_eqvoc. If two conflicting shreds are - detected for the same FEC set, Tower constructs a duplicate - proof and publishes it (FD_TOWER_SIG_SLOT_DUPLICATE). - - e. Slot dead (from the replay tile): Tower records a NULL bank - hash for dead slots in the hard fork detector (fd_hfork). - - Tower signals to other tiles about events that occur as a result of - those three modes, such as what block to vote on, what block to reset - onto as leader, what block got rooted, what blocks are duplicates, - and what blocks are confirmed. - - In general, Tower uses "block_id" as the identifier for a block. The - block_id is the merkle root of the last FEC set for a block. Unlike - slot numbers, this is guaranteed to be unique for a given block and - is therefore a canonical identifier because slot numbers can identify - multiple blocks, if a leader equivocates (produces multiple blocks - for the same slot), whereas it is not feasible for a leader to - produce block_id collisions. - - However, the block_id was only introduced into the Solana protocol - recently, and TowerBFT still uses the "legacy" identifier of slot - numbers for blocks. So the tile (and relevant modules) will use - block_id when possible to interface with the protocol but otherwise - fallback to slot number when block_id is unsupported due to limits of - the protocol. */ - -#define LOGGING 0 - -#define IN_KIND_DEDUP (0) -#define IN_KIND_EPOCH (1) -#define IN_KIND_REPLAY (2) -#define IN_KIND_GOSSIP (3) -#define IN_KIND_IPECHO (4) -#define IN_KIND_SHRED (5) -#define IN_KIND_SIGN (6) - -#define OUT_IDX 0 /* only a single out link tower_out */ - -#include "fd_tower_tile_private.h" - -/* Compile-time dependency injection. This macro defaults to the - production implementation defined below. Tests can #define it before - #include-ing this file to substitute a mock. */ - -#ifndef QUERY_TOWERS -#define QUERY_TOWERS query_towers -#endif - -#ifndef QUERY_VOTERS -#define QUERY_VOTERS query_voters -#endif - -ulong QUERY_TOWERS( fd_tower_tile_t *, fd_replay_slot_completed_t *, fd_ghost_blk_t *, int *, ulong *, ushort * ); -void QUERY_VOTERS( fd_tower_tile_t *, fd_replay_slot_completed_t *, ulong ); - -/* vote_account_config extracts configuration of this validator's vote - account (on-chain state). data points to the first byte of the - vote account's data. Sets: - - *authority_out to the selected authorized voter's public key - - *authority_idx_out to the tile's auth_vtr index (matches sign tile) - or ULONG_MAX it the authorized voter is the node identity - or LONG_MAX if it matches neither - - *node_pubkey to the vote account's pubkey - Returns 1 if the validator has a key for the found vote authority, - and 0 otherwise. */ - -static int -vote_account_config( fd_tower_tile_t * ctx, - uchar const * data, - ulong data_sz, - ulong epoch, - fd_pubkey_t * authority_out, - ulong * authority_idx_out, - fd_pubkey_t * node_pubkey_out ) { - - fd_vote_state_versioned_t vsv[1]; - FD_CHECK_CRIT( fd_vote_state_versioned_deserialize( vsv, data, data_sz ), "unable to decode vote state versioned" ); - - fd_pubkey_t const * auth_vtr_addr = NULL; - switch( vsv->kind ) { - case fd_vote_state_versioned_enum_v1_14_11: - *node_pubkey_out = vsv->v1_14_11.node_pubkey; - for( fd_vote_authorized_voters_treap_rev_iter_t iter = fd_vote_authorized_voters_treap_rev_iter_init( vsv->v1_14_11.authorized_voters.treap, vsv->v1_14_11.authorized_voters.pool ); - !fd_vote_authorized_voters_treap_rev_iter_done( iter ); - iter = fd_vote_authorized_voters_treap_rev_iter_next( iter, vsv->v1_14_11.authorized_voters.pool ) ) { - fd_vote_authorized_voter_t * ele = fd_vote_authorized_voters_treap_rev_iter_ele( iter, vsv->v1_14_11.authorized_voters.pool ); - if( FD_LIKELY( ele->epoch<=epoch ) ) { - auth_vtr_addr = &ele->pubkey; - break; - } - } - break; - case fd_vote_state_versioned_enum_v3: - *node_pubkey_out = vsv->v3.node_pubkey; - for( fd_vote_authorized_voters_treap_rev_iter_t iter = fd_vote_authorized_voters_treap_rev_iter_init( vsv->v3.authorized_voters.treap, vsv->v3.authorized_voters.pool ); - !fd_vote_authorized_voters_treap_rev_iter_done( iter ); - iter = fd_vote_authorized_voters_treap_rev_iter_next( iter, vsv->v3.authorized_voters.pool ) ) { - fd_vote_authorized_voter_t * ele = fd_vote_authorized_voters_treap_rev_iter_ele( iter, vsv->v3.authorized_voters.pool ); - if( FD_LIKELY( ele->epoch<=epoch ) ) { - auth_vtr_addr = &ele->pubkey; - break; - } - } - break; - case fd_vote_state_versioned_enum_v4: - *node_pubkey_out = vsv->v4.node_pubkey; - for( fd_vote_authorized_voters_treap_rev_iter_t iter = fd_vote_authorized_voters_treap_rev_iter_init( vsv->v4.authorized_voters.treap, vsv->v4.authorized_voters.pool ); - !fd_vote_authorized_voters_treap_rev_iter_done( iter ); - iter = fd_vote_authorized_voters_treap_rev_iter_next( iter, vsv->v4.authorized_voters.pool ) ) { - fd_vote_authorized_voter_t * ele = fd_vote_authorized_voters_treap_rev_iter_ele( iter, vsv->v4.authorized_voters.pool ); - if( FD_LIKELY( ele->epoch<=epoch ) ) { - auth_vtr_addr = &ele->pubkey; - break; - } - } - break; - default: - FD_LOG_CRIT(( "unsupported vote state versioned discriminant: %u", vsv->kind )); - } - - FD_CHECK_CRIT( auth_vtr_addr, "unable to find authorized voter, likely corrupt vote account state" ); - *authority_out = *auth_vtr_addr; - - if( fd_pubkey_eq( auth_vtr_addr, ctx->identity_key ) ) { - *authority_idx_out = ULONG_MAX; - return 1; - } - - auth_vtr_t * auth_vtr = auth_vtr_query( ctx->auth_vtr, *auth_vtr_addr, NULL ); - if( FD_LIKELY( auth_vtr ) ) { - *authority_idx_out = auth_vtr->paths_idx; - return 1; - } - - *authority_idx_out = LONG_MAX; - return 0; -} - -static void -update_metrics_eqvoc( fd_tower_tile_t * ctx, - int err ) { - ctx->metrics.eqvoc[ FD_METRICS_ENUM_EQVOC_PROOF_RESULT_V_VERIFIED_IDX ] += (ulong)(err==FD_EQVOC_SUCCESS); - ctx->metrics.eqvoc[ FD_METRICS_ENUM_EQVOC_PROOF_RESULT_V_IGNORED_IDX ] += (ulong)(err==FD_EQVOC_IGNORED); - ctx->metrics.eqvoc[ FD_METRICS_ENUM_EQVOC_PROOF_RESULT_V_INVALID_IDX ] += (ulong)(err<0); -} - -static void -update_metrics_ghost( fd_tower_tile_t * ctx, - int err ) { - ctx->metrics.ghost[ FD_METRICS_ENUM_GHOST_VOTE_RESULT_V_SUCCESS_IDX ] += (ulong)(err==FD_GHOST_SUCCESS); - ctx->metrics.ghost[ FD_METRICS_ENUM_GHOST_VOTE_RESULT_V_ALREADY_VOTED_IDX ] += (ulong)(err==FD_GHOST_ERR_ALREADY_VOTED); -} - -static void -update_metrics_hfork( fd_tower_tile_t * ctx, - int hfork_err, - ulong slot, - fd_hash_t const * block_id, - int own_bank ) { - switch( hfork_err ) { - case FD_HFORK_SUCCESS_MATCHED: - if( own_bank ) ctx->metrics.hfork_bank[ FD_METRICS_ENUM_HARD_FORK_BANK_HASH_RESULT_V_MATCHED_IDX ]++; - else ctx->metrics.hfork [ FD_METRICS_ENUM_HARD_FORK_VOTE_RESULT_V_MATCHED_IDX ]++; - ctx->metrics.hfork_matched_slot = fd_ulong_max( ctx->metrics.hfork_matched_slot, slot ); - break; - case FD_HFORK_SUCCESS: - if( own_bank ) ctx->metrics.hfork_bank[ FD_METRICS_ENUM_HARD_FORK_BANK_HASH_RESULT_V_PENDING_IDX ]++; - else ctx->metrics.hfork [ FD_METRICS_ENUM_HARD_FORK_VOTE_RESULT_V_PENDING_IDX ]++; - break; - case FD_HFORK_ERR_MISMATCHED: - if( own_bank ) ctx->metrics.hfork_bank[ FD_METRICS_ENUM_HARD_FORK_BANK_HASH_RESULT_V_MISMATCHED_IDX ]++; - else ctx->metrics.hfork [ FD_METRICS_ENUM_HARD_FORK_VOTE_RESULT_V_MISMATCHED_IDX ]++; - if( FD_UNLIKELY( ctx->hard_fork_fatal ) ) { - FD_BASE58_ENCODE_32_BYTES( block_id->uc, _block_id ); - FD_LOG_ERR(( "HARD FORK DETECTED for slot %lu block ID `%s`", slot, _block_id )); - } - ctx->metrics.hfork_mismatched_slot = fd_ulong_max( ctx->metrics.hfork_mismatched_slot, slot ); - break; - case FD_HFORK_ERR_UNKNOWN_VTR: - ctx->metrics.hfork[ FD_METRICS_ENUM_HARD_FORK_VOTE_RESULT_V_UNKNOWN_VOTER_IDX ]++; - break; - case FD_HFORK_ERR_ALREADY_VOTED: - ctx->metrics.hfork[ FD_METRICS_ENUM_HARD_FORK_VOTE_RESULT_V_ALREADY_VOTED_IDX ]++; - break; - case FD_HFORK_ERR_VOTE_TOO_OLD: - ctx->metrics.hfork[ FD_METRICS_ENUM_HARD_FORK_VOTE_RESULT_V_TOO_OLD_IDX ]++; - break; - default: - FD_LOG_ERR(( "unhandled hfork_err %d", hfork_err )); - } -} - -static void -update_metrics_vote_slot( fd_tower_tile_t * ctx, - int err ) { - ctx->metrics.vote_slots[ FD_METRICS_ENUM_VOTE_SLOT_RESULT_V_SUCCESS_IDX ] += (ulong)(err==FD_VOTES_SUCCESS); - ctx->metrics.vote_slots[ FD_METRICS_ENUM_VOTE_SLOT_RESULT_V_TOO_NEW_IDX ] += (ulong)(err==FD_VOTES_ERR_VOTE_TOO_NEW); - ctx->metrics.vote_slots[ FD_METRICS_ENUM_VOTE_SLOT_RESULT_V_UNKNOWN_VOTER_IDX ] += (ulong)(err==FD_VOTES_ERR_UNKNOWN_VTR); - ctx->metrics.vote_slots[ FD_METRICS_ENUM_VOTE_SLOT_RESULT_V_ALREADY_VOTED_IDX ] += (ulong)(err==FD_VOTES_ERR_ALREADY_VOTED); -} - -static int -event_level_from_tower( int tower_level ) { - switch( tower_level ) { - case FD_TOWER_SLOT_CONFIRMED_PROPAGATED: return FD_EVENT_SLOT_CONFIRMED_LEVEL_PROPAGATED; - case FD_TOWER_SLOT_CONFIRMED_DUPLICATE: return FD_EVENT_SLOT_CONFIRMED_LEVEL_DUPLICATE; - case FD_TOWER_SLOT_CONFIRMED_OPTIMISTIC: return FD_EVENT_SLOT_CONFIRMED_LEVEL_OPTIMISTIC; - case FD_TOWER_SLOT_CONFIRMED_SUPER: return FD_EVENT_SLOT_CONFIRMED_LEVEL_SUPER; - default: FD_LOG_ERR(( "unexpected tower confirmation level %d", tower_level )); - } -} - -static void -report_slot_confirmed( ulong bank_seq, - ulong slot, - fd_hash_t const * block_id, - ulong stake, - ulong total_stake, - int valid, - int level, - int forward ) { - fd_event_slot_confirmed_t ev = { - .bank_seq = bank_seq, - .slot = slot, - .stake = stake, - .total_stake = total_stake, - .valid = valid, - .level = level, - .forward = forward, - }; - fd_memcpy( ev.block_id, block_id->uc, sizeof(fd_hash_t) ); - fd_event_report_slot_confirmed( &ev ); -} - -struct block_equivocated_args { - ulong slot; - ulong parent_slot; - ulong epoch; - fd_hash_t const * block_id; /* our replayed block (or the just-replayed block) */ - fd_hash_t const * sibling_block_id; /* conflicting block; NULL if unknown (shred proof) */ - fd_hash_t const * bank_hash; /* our block's bank hash; NULL if not replayed locally */ - fd_hash_t const * block_hash; /* our block's last microblock hash; NULL if not replayed locally */ - ulong bank_seq; /* our replayed bank seq; 0 if no local bank */ - int is_leader; - int our_block_voted; - int our_block_confirmed; - ulong block_stake; /* stake voted on our replayed block; 0 if none/unknown */ - ulong sibling_stake; /* stake on the conflicting block; 0 if unknown */ - ulong total_stake; /* 0 if unknown */ - int detection; -}; -typedef struct block_equivocated_args block_equivocated_args_t; - -static ulong -votes_stake( fd_tower_tile_t * ctx, ulong slot, fd_hash_t const * block_id ) { - fd_votes_blk_t * vb = fd_votes_query( ctx->votes, slot, block_id ); - return vb ? vb->stake : 0UL; -} - -static int -our_block_confirmed( fd_tower_blk_t const * blk ) { - return blk && blk->confirmed && 0==memcmp( &blk->replayed_block_id, &blk->confirmed_block_id, sizeof(fd_hash_t) ); -} - -static void -report_block_equivocated( block_equivocated_args_t const * a ) { - fd_event_block_equivocated_t ev = { - .slot = a->slot, - .parent_slot = a->parent_slot, - .epoch = a->epoch, - .bank_seq = a->bank_seq, - .is_leader = a->is_leader, - .our_block_voted = a->our_block_voted, - .our_block_confirmed = a->our_block_confirmed, - .block_stake = a->block_stake, - .sibling_stake = a->sibling_stake, - .total_stake = a->total_stake, - .detection = a->detection, - }; - fd_memcpy( ev.block_id, a->block_id->uc, sizeof(fd_hash_t) ); - if( FD_LIKELY( a->sibling_block_id ) ) fd_memcpy( ev.sibling_block_id, a->sibling_block_id->uc, sizeof(fd_hash_t) ); - if( FD_LIKELY( a->bank_hash ) ) fd_memcpy( ev.bank_hash, a->bank_hash->uc, sizeof(fd_hash_t) ); - if( FD_LIKELY( a->block_hash ) ) fd_memcpy( ev.block_hash, a->block_hash->uc, sizeof(fd_hash_t) ); - fd_event_report_block_equivocated( &ev ); -} - -static void -publish_slot_confirmed( fd_tower_tile_t * ctx, - ulong slot, - fd_hash_t const * block_id, - ulong total_stake ) { - - fd_tower_blk_t * tower_blk = fd_tower_blocks_query( ctx->tower, slot ); - fd_ghost_blk_t * ghost_blk = fd_ghost_query( ctx->ghost, block_id ); - fd_votes_blk_t * votes_blk = fd_votes_query( ctx->votes, slot, block_id ); - if( FD_UNLIKELY( !votes_blk ) ) return; - - static double const ratios[FD_TOWER_SLOT_CONFIRMED_LEVEL_CNT] = FD_TOWER_SLOT_CONFIRMED_RATIOS; - int const levels[FD_TOWER_SLOT_CONFIRMED_LEVEL_CNT] = FD_TOWER_SLOT_CONFIRMED_LEVELS; - for( int i = 0; i < FD_TOWER_SLOT_CONFIRMED_LEVEL_CNT; i++ ) { - if( FD_LIKELY( fd_uchar_extract_bit( votes_blk->flags, i ) ) ) continue; /* already contiguously confirmed */ - double ratio = (double)votes_blk->stake / (double)total_stake; - if( FD_LIKELY( ratio <= ratios[i] ) ) break; /* threshold not met */ - - /* If the ghost_blk is missing, then we know this is a forward - confirmation (ie. we haven't replayed the block yet). */ - - if( FD_UNLIKELY( !ghost_blk ) ) { - if( fd_uchar_extract_bit( votes_blk->flags, i+4 ) ) continue; /* already forward confirmed */ - votes_blk->flags = fd_uchar_set_bit( votes_blk->flags, i+4 ); - publishes_push_head( ctx->publishes, (publish_t){ .sig = FD_TOWER_SIG_SLOT_CONFIRMED, .msg = { .slot_confirmed = (fd_tower_slot_confirmed_t){ .level = levels[i], .fwd = 1, .slot = votes_blk->key.slot, .block_id = votes_blk->key.block_id } } } ); - report_slot_confirmed( 0UL, votes_blk->key.slot, &votes_blk->key.block_id, votes_blk->stake, total_stake, 1 /* valid */, event_level_from_tower( levels[ i ] ), 1 /* forward */ ); - - /* If we have a tower_blk for the slot when the ghost_blk is - missing, we usually replayed an equivocating block_id that is - not the confirmed_block_id. The exception: ghost also prunes - blocks we replayed whose fork lost, so an identical block_id - means the cluster duplicate-confirmed a block our root already - passed. Consensus divergence; halt deliberately. Only - relevant for the duplicate confirmed level. */ - - if( FD_UNLIKELY( levels[i]==FD_TOWER_SLOT_CONFIRMED_DUPLICATE && tower_blk ) ) { - if( FD_UNLIKELY( 0==memcmp( &tower_blk->replayed_block_id, &votes_blk->key.block_id, sizeof(fd_hash_t) ) ) ) { - FD_BASE58_ENCODE_32_BYTES( votes_blk->key.block_id.uc, dup_blk_id ); - FD_LOG_CRIT(( "cluster duplicate-confirmed block %s (slot %lu), which we replayed but pruned: our root conflicts with the cluster", dup_blk_id, votes_blk->key.slot )); - } - tower_blk->confirmed = 1; - tower_blk->confirmed_block_id = votes_blk->key.block_id; - FD_BASE58_ENCODE_32_BYTES( tower_blk->replayed_block_id.uc, eqvoc_blk_id ); - FD_LOG_DEBUG(( "[%s] equivocation detected via forward-confirmed block id mismatch (replayed before confirmed). slot: %lu. block_id: %s", __func__, votes_blk->key.slot, eqvoc_blk_id )); - fd_ghost_eqvoc( ctx->ghost, &tower_blk->replayed_block_id ); - report_block_equivocated( &(block_equivocated_args_t){ - .slot = votes_blk->key.slot, .parent_slot = tower_blk->parent_slot, .epoch = tower_blk->epoch, - .block_id = &tower_blk->replayed_block_id, .sibling_block_id = &votes_blk->key.block_id, - .bank_hash = &tower_blk->bank_hash, .block_hash = &tower_blk->block_hash, - .is_leader = tower_blk->leader, .our_block_voted = tower_blk->voted, .our_block_confirmed = our_block_confirmed( tower_blk ), - .block_stake = votes_stake( ctx, votes_blk->key.slot, &tower_blk->replayed_block_id ), - .sibling_stake = votes_blk->stake, .total_stake = total_stake, - .detection = FD_EVENT_BLOCK_EQUIVOCATED_DETECTION_CONFIRM_MISMATCH } ); - } - continue; - } - - /* Otherwise if they are present, then we know this is not a forward - confirmation and thus we have replayed and confirmed the block, - which also implies we have replayed and confirmed all its - ancestors. So we publish confirmations for all its ancestors - (short-circuiting at the first ancestor already confirmed). - - We use ghost to walk up the ancestry and also mark ghost and - tower blocks as confirmed as we walk if this is the duplicate - confirmation level. */ - - fd_ghost_blk_t * ghost_anc = ghost_blk; - fd_tower_blk_t * tower_anc = tower_blk; - fd_votes_blk_t * votes_anc = votes_blk; - while( FD_LIKELY( ghost_anc ) ) { - - tower_anc = fd_tower_blocks_query( ctx->tower, ghost_anc->slot ); - votes_anc = fd_votes_query( ctx->votes, ghost_anc->slot, &ghost_anc->id ); - if( FD_UNLIKELY( !tower_anc || !votes_anc ) ) break; - - /* Terminate at the first ancestor that has already reached this - confirmation level. */ - - if( FD_LIKELY( fd_uchar_extract_bit( votes_anc->flags, i ) ) ) break; - - /* Mark the ancestor as confirmed at this level, before reporting, - so a duplicate-level row reflects the eligibility it restores. - If this is the duplicate confirmation level, also mark the ghost - and tower blocks as confirmed. */ - - votes_anc->flags = fd_uchar_set_bit( votes_anc->flags, i ); - if( FD_UNLIKELY( levels[i]==FD_TOWER_SLOT_CONFIRMED_PROPAGATED ) ) { - tower_anc->propagated = 1; - } - if( FD_UNLIKELY( levels[i]==FD_TOWER_SLOT_CONFIRMED_DUPLICATE ) ) { - tower_anc->confirmed = 1; - tower_anc->confirmed_block_id = ghost_anc->id; - fd_ghost_confirm( ctx->ghost, &ghost_anc->id ); - if( FD_UNLIKELY( memcmp( &tower_anc->replayed_block_id, &ghost_anc->id, sizeof(fd_hash_t) ) ) ) { - FD_BASE58_ENCODE_32_BYTES( tower_anc->replayed_block_id.uc, eqvoc_blk_id ); - FD_LOG_DEBUG(( "[%s] equivocation detected via ancestor duplicate confirmation. slot: %lu. block_id: %s", __func__, ghost_anc->slot, eqvoc_blk_id )); - fd_ghost_eqvoc( ctx->ghost, &tower_anc->replayed_block_id ); - report_block_equivocated( &(block_equivocated_args_t){ - .slot = ghost_anc->slot, .parent_slot = tower_anc->parent_slot, .epoch = tower_anc->epoch, - .block_id = &tower_anc->replayed_block_id, .sibling_block_id = &ghost_anc->id, - .bank_hash = &tower_anc->bank_hash, .block_hash = &tower_anc->block_hash, - .bank_seq = 0UL, - .is_leader = tower_anc->leader, .our_block_voted = tower_anc->voted, .our_block_confirmed = our_block_confirmed( tower_anc ), - .block_stake = votes_stake( ctx, ghost_anc->slot, &tower_anc->replayed_block_id ), - .sibling_stake = votes_anc->stake, .total_stake = total_stake, - .detection = FD_EVENT_BLOCK_EQUIVOCATED_DETECTION_CONFIRM_MISMATCH } ); - } - } - - publishes_push_head( ctx->publishes, (publish_t){ .sig = FD_TOWER_SIG_SLOT_CONFIRMED, .msg = { .slot_confirmed = (fd_tower_slot_confirmed_t){ .level = levels[i], .fwd = 0, .slot = ghost_anc->slot, .block_id = ghost_anc->id } } } ); - if( FD_LIKELY( !fd_uchar_extract_bit( votes_anc->flags, i+4 ) ) ) { - /* Skip telemetry report if already forward reported. */ - report_slot_confirmed( ghost_anc->bank_seq, ghost_anc->slot, &ghost_anc->id, votes_anc->stake, total_stake, ghost_anc->valid, event_level_from_tower( levels[ i ] ), 0 /* not forward */ ); - } - /* Walk up to next ancestor. */ - - ghost_anc = fd_ghost_parent( ctx->ghost, ghost_anc ); - } - } -} - -static void -publish_slot_done( fd_tower_tile_t * ctx, - fd_replay_slot_completed_t * slot_completed, - fd_tower_out_t * out, - int found, - ulong our_vote_acct_bal, - ushort our_vote_acct_com, - ulong tsorig FD_PARAM_UNUSED, - fd_stem_context_t * stem FD_PARAM_UNUSED ) { - - publish_t * pub = publishes_push_head_nocopy( ctx->publishes ); - pub->sig = FD_TOWER_SIG_SLOT_DONE; - - fd_tower_slot_done_t * msg = &pub->msg.slot_done; - msg->replay_slot = slot_completed->slot; - msg->active_fork_cnt = fd_ghost_width( ctx->ghost ); - msg->vote_slot = out->vote_slot; - msg->reset_slot = out->reset_slot; - msg->reset_block_id = out->reset_block_id; - msg->reset_bank_seq = out->reset_bank_seq; - msg->root_slot = out->root_slot; - msg->root_block_id = out->root_block_id; - msg->replay_bank_idx = slot_completed->bank_idx; - msg->replay_bank_seq = slot_completed->bank_seq; - msg->vote_acct_bal = our_vote_acct_bal; - msg->vote_acct_com = our_vote_acct_com; - - ulong authority_idx = ULONG_MAX; - fd_pubkey_t authority[1]; - fd_pubkey_t identity[1]; - /* Refuse to vote if we don't have a matching vote authority key */ - int found_authority = found && vote_account_config( ctx, ctx->our_vote_acct, ctx->our_vote_acct_sz, slot_completed->epoch, authority, &authority_idx, identity ); - /* Refuse to vote if our node identity does not match the one - specified in the vote account (hot spare check) */ - int identity_matches = found_authority && fd_pubkey_eq( identity, ctx->identity_key ); - msg->is_voting = found_authority && identity_matches; - - if( FD_LIKELY( out->vote_slot!=ULONG_MAX && - found_authority && - identity_matches && - !fd_tower_vote_empty( ctx->tower->votes ) ) ) { - /* The reason to use a historical blockhash and not the most recent - one is because if a vote txn lands on another validator, they - may not have finished processing the slot and therefore the - newest blockhash may not be available to the leader yet; this is - especially true for the first leader block in a rotation. */ - msg->has_vote_txn = 1; - fd_txn_p_t txn[1]; - fd_tower_blk_t * parent_tower_blk = fd_tower_blocks_query( ctx->tower, slot_completed->parent_slot ); - FD_TEST( parent_tower_blk ); - fd_hash_t const * recent_blockhash = &parent_tower_blk->block_hash; - fd_tower_to_vote_txn( ctx->tower, &out->vote_bank_hash, &out->vote_block_id, recent_blockhash, ctx->identity_key, authority, ctx->vote_account, txn ); - FD_TEST( !fd_tower_vote_empty( ctx->tower->votes ) ); - FD_TEST( txn->payload_sz && txn->payload_sz<=FD_TPU_MTU ); - fd_memcpy( msg->vote_txn, txn->payload, txn->payload_sz ); - FD_TEST( fd_txn_parse_simple_vote( TXN(txn), txn->payload, &ctx->compact_tower_sync_serde ) ); - ctx->tower_file_pending = out->vote_slot; - msg->vote_txn_sz = txn->payload_sz; - msg->authority_idx = authority_idx; - msg->vote_created_nanos = fd_log_wallclock(); - } else { - msg->has_vote_txn = 0; - } -} - -static void -publish_slot_ignored( fd_tower_tile_t * ctx, - fd_replay_slot_completed_t * slot_completed, - ulong tsorig FD_PARAM_UNUSED, - fd_stem_context_t * stem FD_PARAM_UNUSED ) { - publishes_push_head( ctx->publishes, (publish_t){ - .sig = FD_TOWER_SIG_SLOT_IGNORED, - .msg = { .slot_ignored = { .slot = slot_completed->slot, .bank_idx = slot_completed->bank_idx } } - }); -} - -static void -publish_slot_rooted( fd_tower_tile_t * ctx, - ulong slot, - fd_hash_t const * block_id ) { - publishes_push_head( ctx->publishes, (publish_t){ - .sig = FD_TOWER_SIG_SLOT_ROOTED, - .msg = { .slot_rooted = { .slot = slot, .block_id = *block_id } } - }); -} - -static void -publish_slot_duplicate( fd_tower_tile_t * ctx, - fd_gossip_duplicate_shred_t const chunks[static FD_EQVOC_CHUNK_CNT], - ulong slot ) { - publish_t * pub = publishes_push_head_nocopy( ctx->publishes ); - pub->sig = FD_TOWER_SIG_SLOT_DUPLICATE; - memcpy( pub->msg.slot_duplicate.chunks, chunks, sizeof(pub->msg.slot_duplicate.chunks) ); - - /* If we already have a tower blk for this just-proved duplicate - slot, then we know we have replayed one of the equivocating - blocks. So determine: - - 1. whether we already know what is the confirmed block_id - 2. if our replayed_block_id is the confirmed_block_id - - If either 1. or 2. are false (with 2. contingent on 1.), then - mark the replayed block as eqvoc in ghost. */ - - fd_tower_blk_t * tower_blk = fd_tower_blocks_query( ctx->tower, slot ); - int eqvoc = tower_blk && (!tower_blk->confirmed || memcmp( &tower_blk->replayed_block_id, &tower_blk->confirmed_block_id, sizeof(fd_hash_t) ) ); - if( FD_LIKELY( eqvoc ) ) { - FD_BASE58_ENCODE_32_BYTES( tower_blk->replayed_block_id.uc, eqvoc_blk_id ); - FD_LOG_DEBUG(( "[%s] equivocation detected via duplicate shred proof. slot: %lu. block_id: %s", __func__, slot, eqvoc_blk_id )); - fd_ghost_eqvoc( ctx->ghost, &tower_blk->replayed_block_id ); - report_block_equivocated( &(block_equivocated_args_t){ - .slot = slot, .parent_slot = tower_blk->parent_slot, .epoch = tower_blk->epoch, - .block_id = &tower_blk->replayed_block_id, .sibling_block_id = NULL /* unknown */, - .bank_hash = &tower_blk->bank_hash, .block_hash = &tower_blk->block_hash, - .is_leader = tower_blk->leader, .our_block_voted = tower_blk->voted, .our_block_confirmed = our_block_confirmed( tower_blk ), - .block_stake = votes_stake( ctx, slot, &tower_blk->replayed_block_id ), - .detection = FD_EVENT_BLOCK_EQUIVOCATED_DETECTION_SHRED_PROOF } ); - } -} - -static void -count_vote_acc( fd_tower_tile_t * ctx, - fd_replay_slot_completed_t * slot_completed, - fd_ghost_blk_t * ghost_blk, - fd_pubkey_t const * vote_acc, - ulong stake, - uchar const * data, - ulong data_sz ) { - - fd_tower_count_vote( ctx->tower, vote_acc, stake, data, data_sz ); - - fd_tower_vtr_t const * vtr = fd_tower_vtr_peek_tail_const( ctx->tower->vtrs ); - - /* 1. Update forks with lockouts. */ - - fd_tower_lockos_insert( ctx->tower, slot_completed->slot, vote_acc, vtr->votes ); - - /* 2. Count the last vote slot in the vote state towards ghost. */ - - ulong vote_slot = fd_tower_vote_empty( vtr->votes ) ? ULONG_MAX : fd_tower_vote_peek_tail_const( vtr->votes )->slot; - if( FD_LIKELY( vote_slot!=ULONG_MAX && /* has voted */ - vote_slot>=fd_ghost_root( ctx->ghost )->slot ) ) { /* vote not too old */ - - fd_ghost_blk_t * ancestor_blk = fd_ghost_slot_ancestor( ctx->ghost, ghost_blk, vote_slot ); /* FIXME potentially slow */ - - if( FD_UNLIKELY( !ancestor_blk ) ) { - FD_BASE58_ENCODE_32_BYTES( vote_acc->key, vote_acc_cstr ); - FD_LOG_CRIT(( "missing ancestor. replay slot %lu vote slot %lu voter %s", slot_completed->slot, vote_slot, vote_acc_cstr )); - } - - int ghost_err = fd_ghost_count_vote( ctx->ghost, ancestor_blk, vote_acc, stake, vote_slot ); - update_metrics_ghost( ctx, ghost_err ); - } - - FD_TEST( !fd_vote_account_node_pubkey( data, data_sz, &ctx->id_keys[ctx->vtr_cnt] ) ); - ctx->vote_accs[ctx->vtr_cnt] = *vote_acc; - ctx->vtr_cnt++; -} - -/* Query all the relevant towers for running Tower rules on this slot: - - 1. staked voter set from banks - 2. vote accounts (for each staked voter, which contains their tower) - from accountsDB. */ - -FD_FN_UNUSED ulong -query_towers( fd_tower_tile_t * ctx, - fd_replay_slot_completed_t * slot_completed, - fd_ghost_blk_t * ghost_blk, - int * found_our_vote_acct, - ulong * our_vote_acct_bal, - ushort * our_vote_acct_com ) { - - ulong total_stake = 0UL; - ulong prev_voter_idx = ULONG_MAX; - - fd_bank_t * bank = fd_banks_bank_query( ctx->banks, slot_completed->bank_idx ); - if( FD_UNLIKELY( !bank ) ) FD_LOG_CRIT(( "invariant violation: bank %lu is missing", slot_completed->bank_idx )); - - fd_vote_stakes_t * vote_stakes = fd_bank_vote_stakes( bank ); - ulong fork_id = bank->vote_stakes_fork_id; - uchar __attribute__((aligned(FD_VOTE_STAKES_ITER_ALIGN))) iter_mem[ FD_VOTE_STAKES_ITER_FOOTPRINT ]; - -#define BATCH 64UL - fd_pubkey_t vote_accs[ BATCH ]; - ulong stakes[ BATCH ]; - uchar const * pubkeys[ BATCH ]; - int writable[ BATCH ]; - fd_acc_t accs[ BATCH ]; - - fd_vote_stakes_iter_t * iter = fd_vote_stakes_iter_init( vote_stakes, fork_id, FD_VOTE_STAKES_ITER_T_2, iter_mem ); - while( !fd_vote_stakes_iter_done( vote_stakes, fork_id, FD_VOTE_STAKES_ITER_T_2, iter ) ) { - ulong batch_n = 0UL; - while( !fd_vote_stakes_iter_done( vote_stakes, fork_id, FD_VOTE_STAKES_ITER_T_2, iter ) && batch_npubkey; - stakes [ batch_n ] = ele->stake; - fd_vote_stakes_iter_next( vote_stakes, fork_id, FD_VOTE_STAKES_ITER_T_2, iter ); - total_stake += stakes[ batch_n ]; - if( FD_UNLIKELY( !ele->is_valid ) ) continue; - pubkeys[ batch_n ] = vote_accs[ batch_n ].uc; - writable[ batch_n ] = 0; - batch_n++; - } - if( FD_UNLIKELY( !batch_n ) ) continue; - - fd_accdb_acquire( ctx->accdb, bank->accdb_fork_id, batch_n, pubkeys, writable, accs ); - - for( ulong j=0UL; jtower, slot_completed->slot, &vote_accs[ j ], stakes[ j ], prev_voter_idx ); - } - - fd_accdb_release( ctx->accdb, batch_n, accs ); - } -#undef BATCH - - /* Reconcile our local tower with the on-chain tower (stored inside - our vote account). - - Skip reconciliation on the first replay_slot_completed in MATCH - only: a pre-restart tower fails lockout_check (slot <= - last_vote_slot) and threshold_check. A NOOP boot is normal and - keeps its tower. The first completion is the boot slot, which - is what fd_wfs_mode needs. */ - - *our_vote_acct_bal = ULONG_MAX; - *our_vote_acct_com = USHORT_MAX; - *found_our_vote_acct = 0; - fd_acc_t reconcile_ro = fd_accdb_read_one( ctx->accdb, bank->accdb_fork_id, ctx->vote_account->uc ); - if( FD_LIKELY( reconcile_ro.lamports ) ) { - *found_our_vote_acct = 1; - ctx->our_vote_acct_sz = fd_ulong_min( reconcile_ro.data_len, FD_VOTE_STATE_DATA_MAX ); - *our_vote_acct_bal = reconcile_ro.lamports; - FD_TEST( !fd_vote_account_commission_bps( reconcile_ro.data, - reconcile_ro.data_len, - FD_FEATURE_ACTIVE_BANK( bank, commission_rate_in_basis_points ), - our_vote_acct_com ) ); - fd_memcpy( ctx->our_vote_acct, reconcile_ro.data, ctx->our_vote_acct_sz ); - int skip_reconcile = !ctx->init && - fd_wfs_mode( ctx->wfs_slot, ctx->wfs_hash_is_zero, - (ulong)ctx->wfs_shred_version, - slot_completed->slot )==FD_WFS_MODE_MATCH; - if( FD_LIKELY( !skip_reconcile ) ) { - ulong root; - fd_tower_vote_remove_all( ctx->scratch_tower ); - fd_tower_from_vote_acc( ctx->scratch_tower, &root, ctx->our_vote_acct, ctx->our_vote_acct_sz ); - /* After a switch with a vote history, our tower holds only its - replayed votes, so like Agave we compare against its last vote. */ - ulong onchain_last = fd_tower_vote_empty( ctx->scratch_tower ) ? ULONG_MAX : fd_tower_vote_peek_tail_const( ctx->scratch_tower )->slot; - if( FD_LIKELY( !ctx->vote_history_last || onchain_last>ctx->vote_history_last ) ) fd_tower_reconcile( ctx->tower, ctx->scratch_tower, root ); - } else { - FD_LOG_NOTICE(( "wait_for_supermajority: skipping tower reconcile on init slot %lu", slot_completed->slot )); - } - } - fd_accdb_unread_one( ctx->accdb, &reconcile_ro ); - - return total_stake; -} - -/* validate_vote_txn is the C equivalent of Agave's - parse_vote_transaction. Returns the vote account on success, NULL - on failure. Deserializes the vote instruction into ctx->scratch_ix. - - https://github.com/anza-xyz/agave/blob/v2.3.7/sdk/src/transaction/versioned/mod.rs#L79 */ - -static fd_pubkey_t const * -validate_vote_txn( fd_tower_tile_t * ctx, - fd_txn_t const * txn, - uchar const * payload ) { - - if( FD_UNLIKELY( !txn->instr_cnt ) ) return NULL; - fd_txn_instr_t const * instr = &txn->instr[ 0 ]; - - fd_pubkey_t const * accs = (fd_pubkey_t const *)fd_type_pun_const( payload + txn->acct_addr_off ); - if( FD_UNLIKELY( 0!=memcmp( &accs[ instr->program_id ], &fd_solana_vote_program_id, FD_TXN_ACCT_ADDR_SZ ) ) ) return NULL; - - uchar const * instr_data = payload + instr->data_off; - if( FD_UNLIKELY( !fd_vote_instruction_deserialize( &ctx->scratch_ix, instr_data, instr->data_sz ) ) ) return NULL; - - if( FD_UNLIKELY( !instr->acct_cnt ) ) return NULL; - uchar const * instr_accts = payload + instr->acct_off; - return (fd_pubkey_t const *)fd_type_pun_const( &accs[ instr_accts[ 0 ] ] ); -} - -/* count_vote_txn counts vote txns from Gossip, TPU and Replay. Note - these txns have already been parsed and sigverified before they are - sent to tower. In addition, vote txns coming from Replay have also - been successfully executed. They are counted towards hfork and votes - (see point 2 in the top-level documentation). */ - -static void -count_vote_txn( fd_tower_tile_t * ctx, - fd_txn_t const * txn, - uchar const * payload ) { - - /* We are a little stricter than Agave here because Agave only does - the is_simple_vote check on replay vote txns, whereas we are doing - the check on both replay and gossip / TPU vote txns. - - Being a little stricter with non-replay vote txns is ok because - even if we drop some votes that Agave would consider valid - (unlikely unless they were sent by an actively malicious - validator), gossip votes are in general considered unreliable and - ultimately consensus (fork choice, tower rules, rooting, etc.) is - reached with vote accounts updated by replaying blocks. - - See: https://github.com/anza-xyz/agave/blob/v4.1.0-beta.1/runtime/src/bank_utils.rs#L54 */ - - if( FD_UNLIKELY( !fd_txn_is_simple_vote_transaction( txn, payload ) ) ) { ctx->metrics.votes[ FD_METRICS_ENUM_VOTE_TXN_RESULT_V_NOT_SIMPLE_VOTE_IDX ]++; return; } - - fd_pubkey_t const * vote_acc = validate_vote_txn( ctx, txn, payload ); - if( FD_UNLIKELY( !vote_acc ) ) { ctx->metrics.votes[ FD_METRICS_ENUM_VOTE_TXN_RESULT_V_BAD_DESER_IDX ]++; return; } - - /* Filter any non-TowerSync vote instructions. For gossip / TPU this - filters deprecated vote kinds; for replay this shouldn't happen - after SIMD-0138 is activated. */ - - /* TODO SECURITY ensure SIMD-0138 is activated */ - - if( FD_UNLIKELY( ctx->scratch_ix.discriminant!=fd_vote_instruction_enum_tower_sync && ctx->scratch_ix.discriminant!=fd_vote_instruction_enum_tower_sync_switch ) ) { - ctx->metrics.votes[ FD_METRICS_ENUM_VOTE_TXN_RESULT_V_NOT_TOWER_SYNC_IDX ]++; - return; - } - - fd_tower_sync_t * tower_sync = &ctx->scratch_ix.tower_sync; /* this is safe, because TowerSyncSwitch is the same as TowerSync except with 32-bytes appended */ - if( FD_UNLIKELY( tower_sync->lockouts_cnt>FD_TOWER_VOTE_MAX ) ) { ctx->metrics.votes[ FD_METRICS_ENUM_VOTE_TXN_RESULT_V_BAD_TOWER_IDX ]++; return; } - if( FD_UNLIKELY( !tower_sync->lockouts_cnt ) ) { ctx->metrics.votes[ FD_METRICS_ENUM_VOTE_TXN_RESULT_V_EMPTY_TOWER_IDX ]++; return; } - - fd_tower_vote_remove_all( ctx->scratch_tower ); - for( ulong i = 0; i < tower_sync->lockouts_cnt; i++ ) { - fd_vote_lockout_t const * lockout = deq_fd_vote_lockout_t_peek_index_const( tower_sync->lockouts, i ); - fd_tower_vote_push_tail( ctx->scratch_tower, (fd_tower_vote_t){ .slot = lockout->slot, .conf = lockout->confirmation_count } ); - } - - /* Validate the tower. */ - - fd_tower_vote_t const * prev = fd_tower_vote_peek_head_const( ctx->scratch_tower ); - if( FD_UNLIKELY( prev->conf > FD_TOWER_VOTE_MAX ) ) { ctx->metrics.votes[ FD_METRICS_ENUM_VOTE_TXN_RESULT_V_BAD_TOWER_IDX ]++; return; } - - fd_tower_vote_iter_t iter = fd_tower_vote_iter_next( ctx->scratch_tower, fd_tower_vote_iter_init( ctx->scratch_tower ) ); - for( ; !fd_tower_vote_iter_done( ctx->scratch_tower, iter ); iter = fd_tower_vote_iter_next( ctx->scratch_tower, iter ) ) { - fd_tower_vote_t const * vote = fd_tower_vote_iter_ele( ctx->scratch_tower, iter ); - if( FD_UNLIKELY( vote->slot <= prev->slot ) ) { ctx->metrics.votes[ FD_METRICS_ENUM_VOTE_TXN_RESULT_V_BAD_TOWER_IDX ]++; return; } - if( FD_UNLIKELY( vote->conf >= prev->conf ) ) { ctx->metrics.votes[ FD_METRICS_ENUM_VOTE_TXN_RESULT_V_BAD_TOWER_IDX ]++; return; } - if( FD_UNLIKELY( vote->conf > FD_TOWER_VOTE_MAX ) ) { ctx->metrics.votes[ FD_METRICS_ENUM_VOTE_TXN_RESULT_V_BAD_TOWER_IDX ]++; return; } - prev = vote; - } - - if( FD_UNLIKELY( 0==memcmp( &tower_sync->block_id, &hash_null, sizeof(fd_hash_t) ) ) ) { ctx->metrics.votes[ FD_METRICS_ENUM_VOTE_TXN_RESULT_V_NULL_BLOCK_ID_IDX ]++; return; }; - - /* The vote txn contains a block id and bank hash for their last vote - slot in the tower. Agave always counts the last vote. - - https://github.com/anza-xyz/agave/blob/v2.3.7/core/src/cluster_info_vote_listener.rs#L476-L487 */ - - fd_tower_vote_t const * their_last_vote = fd_tower_vote_peek_tail_const( ctx->scratch_tower ); - fd_hash_t const * their_block_id = &tower_sync->block_id; - fd_hash_t const * their_bank_hash = &tower_sync->hash; - - /* Return early if their last vote is too old. */ - - if( FD_UNLIKELY( their_last_vote->slot <= ctx->tower->root ) ) { ctx->metrics.votes[ FD_METRICS_ENUM_VOTE_TXN_RESULT_V_TOO_OLD_IDX ]++; return; } - - /* Determine the epoch of the vote slot and look up the voter's stake - for that epoch. Votes can be at most 1 epoch ahead of root. */ - - fd_epoch_leaders_t const * lsched = fd_multi_epoch_leaders_get_lsched_for_slot( ctx->mleaders, their_last_vote->slot ); - if( FD_UNLIKELY( !lsched ) ) { ctx->metrics.votes[ FD_METRICS_ENUM_VOTE_TXN_RESULT_V_BAD_DESER_IDX ]++; return; } /* no leader schedule to resolve the vote's epoch */ - ulong vote_epoch = lsched->epoch; - - epoch_vtr_t * epoch_vtr_pool = NULL; - epoch_vtr_map_t * epoch_vtr_map = NULL; - ulong total_stake = 0UL; - if( FD_LIKELY( vote_epoch==ctx->root_epoch ) ) { epoch_vtr_pool = ctx->root_epoch_vtr_pool; epoch_vtr_map = ctx->root_epoch_vtr_map; total_stake = ctx->root_epoch_total_stake; } - else if( FD_LIKELY( vote_epoch==ctx->root_epoch + 1 ) ) { epoch_vtr_pool = ctx->next_epoch_vtr_pool; epoch_vtr_map = ctx->next_epoch_vtr_map; total_stake = ctx->next_epoch_total_stake; } - else { ctx->metrics.votes[ FD_METRICS_ENUM_VOTE_TXN_RESULT_V_NOT_STAKED_IDX ]++; return; } - epoch_vtr_t * vtr = epoch_vtr_map_ele_query( epoch_vtr_map, vote_acc, NULL, epoch_vtr_pool ); - if( FD_UNLIKELY( !vtr ) ) { ctx->metrics.votes[ FD_METRICS_ENUM_VOTE_TXN_RESULT_V_NOT_STAKED_IDX ]++; return; } - - /* Verify the authorized voter for this vote account at vote_epoch is - among the txn signers. Mirrors Agave's cluster_info_vote_listener - check. authorized_voter is cached on the epoch_vtr by - query_voters; an all-zero value means we couldn't read it. */ - - if( FD_UNLIKELY( 0==memcmp( &vtr->auth_vtr, &pubkey_null, sizeof(fd_pubkey_t) ) ) ) { ctx->metrics.votes[ FD_METRICS_ENUM_VOTE_TXN_RESULT_V_BAD_SIGNER_IDX ]++; return; } - fd_pubkey_t const * accs = (fd_pubkey_t const *)fd_type_pun_const( payload + txn->acct_addr_off ); - int signer_ok = 0; - for( ulong i=0; isignature_cnt; i++ ) { - if( 0==memcmp( &accs[i], &vtr->auth_vtr, sizeof(fd_pubkey_t) ) ) { signer_ok = 1; break; } - } - if( FD_UNLIKELY( !signer_ok ) ) { ctx->metrics.votes[ FD_METRICS_ENUM_VOTE_TXN_RESULT_V_BAD_SIGNER_IDX ]++; return; } - - /* The txn passed all per-txn validation; we will now count its - individual vote slots (per-slot metrics below). */ - - ctx->metrics.votes[ FD_METRICS_ENUM_VOTE_TXN_RESULT_V_SUCCESS_IDX ]++; - - int hfork_err = fd_hfork_count_vote( ctx->hfork, vote_acc, their_block_id, their_bank_hash, their_last_vote->slot, vtr->stake, total_stake ); - update_metrics_hfork( ctx, hfork_err, their_last_vote->slot, their_block_id, 0 ); - - /* One voter lookup, shared by every slot of this txn (the voter set - only changes at epoch boundaries, outside this function). */ - fd_votes_vtr_t * votes_vtr = fd_votes_vtr_query( ctx->votes, vote_acc ); - - int votes_err = fd_votes_count_vote_vtr( ctx->votes, votes_vtr, vtr->stake, their_last_vote->slot, their_block_id ); - update_metrics_vote_slot( ctx, votes_err ); - if( FD_LIKELY( votes_err==FD_VOTES_SUCCESS ) ) publish_slot_confirmed( ctx, their_last_vote->slot, their_block_id, total_stake ); - - /* Agave decides to count intermediate vote slots in the tower iff: - - 1. they've replayed the slot - 2. their replay bank hash matches the vote's bank hash. - - This guarantees the intermediate slots they are counting are in - fact for the correct ancestry (in case of equivocation). We do the - same thing, but using block ids instead of bank hashes. - - It's possible we haven't yet replayed this slot being voted on - because gossip votes can be ahead of our replay. - - https://github.com/anza-xyz/agave/blob/v2.3.7/core/src/cluster_info_vote_listener.rs#L483-L487 */ - - fd_tower_blk_t const * our_blk = fd_tower_blocks_query( ctx->tower, their_last_vote->slot ); - if( FD_UNLIKELY( !our_blk ) ) { - /* we haven't replayed this block yet */ - ctx->metrics.gate_int[ FD_METRICS_ENUM_VOTE_INTERMEDIATE_SLOT_RESULT_V_UNREPLAYED_SLOT_IDX ]++; - return; - }; - fd_hash_t const * our_block_id = fd_tower_blk_canonical_block_id( our_blk ); - if( FD_UNLIKELY( 0!=memcmp( our_block_id, their_block_id, sizeof(fd_hash_t) ) ) ) { ctx->metrics.gate_int[ FD_METRICS_ENUM_VOTE_INTERMEDIATE_SLOT_RESULT_V_MISMATCHED_BLOCK_ID_IDX ]++; return; } /* we don't recognize this block id */ - - /* At this point, we know we have replayed the same slot and also have - a matching block id, so we can count the intermediate votes. */ - - ctx->metrics.gate_int[ FD_METRICS_ENUM_VOTE_INTERMEDIATE_SLOT_RESULT_V_COUNTED_IDX ]++; - - int skipped_last_vote = 0; - for( fd_tower_vote_iter_t iter = fd_tower_vote_iter_init_rev( ctx->scratch_tower ); - !fd_tower_vote_iter_done_rev( ctx->scratch_tower, iter ); - iter = fd_tower_vote_iter_prev ( ctx->scratch_tower, iter ) ) { - if( FD_UNLIKELY( !skipped_last_vote ) ) { skipped_last_vote = 1; continue; } - fd_tower_vote_t const * their_intermediate_vote = fd_tower_vote_iter_ele_const( ctx->scratch_tower, iter ); - - /* If we don't recognize an intermediate vote slot in their tower, - it means their tower either: - - 1. Contains intermediate vote slots that are too old (older than - our root) so we already pruned them for tower_forks. Normally - if the descendant (last vote slot) is in tower forks, then all - of its ancestors should be in there too. - - 2. Is invalid. Even though at this point we have successfully - sigverified and deserialized their vote txn, the tower itself - might still be invalid because unlike TPU vote txns, we have - not plumbed through the vote program, but obviously gossip - votes do not so we need to do some light validation here. - - We could throwaway this voter's tower, but we handle it the same - way as Agave which is to just skip this intermediate vote slot: - - https://github.com/anza-xyz/agave/blob/v2.3.7/core/src/cluster_info_vote_listener.rs#L513-L518 */ - - if( FD_UNLIKELY( their_intermediate_vote->slot <= ctx->tower->root ) ) { ctx->metrics.vote_slots[ FD_METRICS_ENUM_VOTE_SLOT_RESULT_V_TOO_OLD_IDX ]++; continue; } - - fd_tower_blk_t const * tower_blk = fd_tower_blocks_query( ctx->tower, their_intermediate_vote->slot ); - if( FD_UNLIKELY( !tower_blk ) ) { ctx->metrics.vote_slots[ FD_METRICS_ENUM_VOTE_SLOT_RESULT_V_UNKNOWN_SLOT_IDX ]++; continue; } - - /* Otherwise, we count the vote using our own block id for that slot - (again, mirroring what Agave does albeit with bank hashes). - - Agave uses the current root bank's total stake when counting vote - txns from gossip / replay: - - https://github.com/anza-xyz/agave/blob/v2.3.7/core/src/cluster_info_vote_listener.rs#L500 */ - - fd_hash_t const * intermediate_block_id = fd_tower_blk_canonical_block_id( tower_blk ); - int votes_err = fd_votes_count_vote_vtr( ctx->votes, votes_vtr, vtr->stake, their_intermediate_vote->slot, intermediate_block_id ); - update_metrics_vote_slot( ctx, votes_err ); - if( FD_LIKELY( votes_err==FD_VOTES_SUCCESS ) ) publish_slot_confirmed( ctx, their_intermediate_vote->slot, intermediate_block_id, total_stake ); - } -} - -/* Query the staked voters in the provided epoch: - - 1. identity keys (aka. node pubkeys) - 2. vote account addresses - 3. associated stake (for the epoch) - 4. authorized voter (for the epoch) */ - -static ulong -query_epoch_voters( fd_tower_tile_t * ctx, - ulong epoch, - int iter_kind, - fd_accdb_fork_id_t accdb_fork_id, - fd_vote_stakes_t * vote_stakes, - ulong vote_stakes_fork_id, - epoch_vtr_t * pool, - epoch_vtr_map_t * map, - int update_id_keys_vote_accs ) { - - epoch_vtr_pool_reset( pool ); - epoch_vtr_map_reset( map ); - ulong total_stake = 0UL; - fd_vote_stakes_iter_t * iter = fd_vote_stakes_iter_init( vote_stakes, vote_stakes_fork_id, iter_kind, ctx->iter_mem ); - while( !fd_vote_stakes_iter_done( vote_stakes, vote_stakes_fork_id, iter_kind, iter ) ) { - fd_vote_stakes_ele_t ele[1]; - fd_vote_stakes_iter_ele( vote_stakes, vote_stakes_fork_id, iter_kind, iter, ele ); - fd_vote_stakes_iter_next( vote_stakes, vote_stakes_fork_id, iter_kind, iter ); - total_stake += ele->stake; - epoch_vtr_t * vtr = epoch_vtr_pool_ele_acquire( pool ); - vtr->vote_acc = ele->pubkey; - vtr->stake = ele->stake; - memset( &vtr->auth_vtr, 0, sizeof(fd_pubkey_t) ); - - /* Cache the authorized voter for target_epoch. Leaves - auth_vtr all-zero if the vote account is unreadable — - count_vote_txn will reject txns whose signer can't match. */ - - fd_acc_t ro = fd_accdb_read_one( ctx->accdb, accdb_fork_id, ele->pubkey.uc ); - if( FD_LIKELY( ro.lamports && fd_vsv_is_correct_size_owner_and_init( ro.owner, ro.data, ro.data_len ) ) ) { - fd_pubkey_t identity[1]; - ulong dummy_idx; - vote_account_config( ctx, ro.data, ro.data_len, epoch, &vtr->auth_vtr, &dummy_idx, identity ); - if( update_id_keys_vote_accs ) { - FD_TEST( 0==fd_vote_account_node_pubkey( ro.data, ro.data_len, &ctx->id_keys[ctx->vtr_cnt] ) ); /* check vote account is not corrupt */ - ctx->vote_accs[ctx->vtr_cnt] = ele->pubkey; - ctx->vtr_cnt++; - } - } - fd_accdb_unread_one( ctx->accdb, &ro ); - - epoch_vtr_map_ele_insert( map, vtr, pool ); - } - return total_stake; -} - -/* Update the cached voters for both the currently rooted epoch and the - next epoch, to allow processing vote transactions for vote slots that - span both these epochs. */ - -FD_FN_UNUSED void -query_voters( fd_tower_tile_t * ctx, - fd_replay_slot_completed_t * slot_completed, - ulong epoch ) { - if( FD_LIKELY( ctx->banks ) ) { - fd_bank_t * bank = fd_banks_bank_query( ctx->banks, slot_completed->bank_idx ); - if( FD_UNLIKELY( !bank ) ) FD_LOG_CRIT(( "invariant violation: bank %lu is missing", slot_completed->bank_idx )); - - ctx->vtr_cnt = 0; - fd_vote_stakes_t * vote_stakes = fd_bank_vote_stakes( bank ); - ctx->root_epoch_total_stake = query_epoch_voters( ctx, epoch, FD_VOTE_STAKES_ITER_T_2, bank->accdb_fork_id, vote_stakes, bank->vote_stakes_fork_id, ctx->root_epoch_vtr_pool, ctx->root_epoch_vtr_map, 1 ); - ctx->next_epoch_total_stake = query_epoch_voters( ctx, epoch+1UL, FD_VOTE_STAKES_ITER_T_1, bank->accdb_fork_id, vote_stakes, bank->vote_stakes_fork_id, ctx->next_epoch_vtr_pool, ctx->next_epoch_vtr_map, 0 ); - } - ctx->root_epoch = epoch; - - fd_eqvoc_update_voters( ctx->eqvoc, ctx->id_keys, ctx->vtr_cnt ); - fd_hfork_update_voters( ctx->hfork, ctx->vote_accs, ctx->vtr_cnt ); - fd_votes_update_voters( ctx->votes, ctx->vote_accs, ctx->vtr_cnt ); -} - -static void -clear_votes( fd_tower_t * tower ) { - for( ulong i=0UL; iblk_max; i++ ) tower->blk_pool[ i ].voted = 0; /* blocks stay voted after their votes expire */ - fd_tower_vote_remove_all( tower->votes ); -} - -static ulong -vote_history_ahead( fd_tower_t * tower, - fd_ghost_t * ghost, - fd_tower_file_t const * file ) { - fd_tower_blk_t const * root_blk = fd_tower_blocks_query( tower, file->root ); - int ahead = file->root>tower->root && ( !root_blk || !fd_ghost_query( ghost, &root_blk->replayed_block_id ) ); - ulong wait = file->votes[ file->votes_cnt-1UL ].slot+1UL; - int missing = 0; - for( ulong i=0UL; ivotes_cnt; i++ ) { - fd_tower_vote_t const * vote = &file->votes[ i ]; - fd_tower_blk_t const * blk = fd_tower_blocks_query( tower, vote->slot ); - missing |= vote->slot>tower->root && ( !blk || !fd_ghost_query( ghost, &blk->replayed_block_id ) ); - if( missing ) { - ahead = 1; - wait = fd_ulong_max( wait, vote->slot+(1UL<conf)+1UL ); - } - } - return ahead ? wait : 0UL; -} - -static ulong -vote_history_floor( ulong root, - fd_tower_file_t const * file, - fd_slot_history_view_t const * slot_history ) { - ulong floor = 0UL; - for( ulong i=0UL; ivotes_cnt; i++ ) { - fd_tower_vote_t const * vote = &file->votes[ i ]; - if( vote->slot<=root && fd_sysvar_slot_history_find_slot( slot_history, vote->slot )==FD_SLOT_HISTORY_SLOT_NOT_FOUND ) { - floor = fd_ulong_max( floor, vote->slot+(1UL<conf)+1UL ); - } - } - return floor; -} - -static void -adopt_vote_history( fd_tower_tile_t * ctx ) { - /* A minor note is that adopt_vote_history ignores the tower - file's latest vote timestamp. The only downside of this is that - if the Firedancer validator's wallclock is behind the tower - file's latest vote timestamp, its votes will land, but will not - execute successfully. This will self-heal when the wallclock of - the switched-to validator catches up to the tower file's latest - vote timestamp. */ - - fd_tower_t * tower = ctx->tower; - fd_tower_file_t const * file = &ctx->vote_history; - ulong last = file->votes[ file->votes_cnt-1UL ].slot; - - /* The old validator may have voted for a different copy of this slot - than the one we replayed. Agave only compares slot numbers: so we - keep the vote and treat it as a vote for our copy. */ - fd_tower_blk_t const * last_blk = fd_tower_blocks_query( tower, last ); - if( FD_UNLIKELY( last_blk && !fd_hash_eq( &last_blk->bank_hash, &file->bank_hash ) ) ) { - FD_LOG_WARNING(( "set-identity: last vote %lu in vote history is for a different bank than we replayed", last )); - } - - fd_tower_vote_remove_all( ctx->scratch_tower ); - for( ulong i=0UL; ivotes_cnt; i++ ) { - fd_tower_vote_t const * vote = &file->votes[ i ]; - fd_tower_blk_t const * blk = fd_tower_blocks_query( tower, vote->slot ); - if( FD_UNLIKELY( vote->slot>tower->root && ( !blk || !fd_ghost_query( ctx->ghost, &blk->replayed_block_id ) ) ) ) break; - fd_tower_vote_push_tail( ctx->scratch_tower, *vote ); - } - if( FD_LIKELY( !fd_tower_vote_empty( ctx->scratch_tower ) && !fd_tower_vote_empty( tower->votes ) && - fd_tower_vote_peek_tail_const( tower->votes )->slot<=last ) ) clear_votes( tower ); - fd_tower_blk_t const * root_blk = fd_tower_blocks_query( tower, file->root ); - int root_replayed = file->root<=tower->root || ( root_blk && fd_ghost_query( ctx->ghost, &root_blk->replayed_block_id ) ); - fd_tower_reconcile( tower, ctx->scratch_tower, root_replayed ? file->root : tower->root ); -} - -/* check_vote_history runs on each replayed slot while a vote history - is pending. Our tower takes the history's votes on blocks we have - replayed as they arrive. The history stops pending once all of its - votes and root are replayed, or it is behind our root, or its - missing blocks still haven't been replayed when their lockouts - expire. We don't vote until votes that are missing, or were on an - abandoned fork, would no longer lock us out. */ - -static void -check_vote_history( fd_tower_tile_t * ctx, - fd_replay_slot_completed_t const * slot_completed ) { - fd_tower_t * tower = ctx->tower; - fd_tower_file_t const * file = &ctx->vote_history; - ulong last = file->votes[ file->votes_cnt-1UL ].slot; - - /* If the tower file has votes that are behind our view of the root, - and has votes which were on abandoned forks, then we may need to - wait to vote to avoid violating lockout. */ - ulong floor = 0UL; - fd_bank_t * bank = fd_banks_bank_query( ctx->banks, slot_completed->bank_idx ); - FD_TEST( bank ); - ulong sz; - uchar const * data = fd_sysvar_cache_data_query( &bank->f.sysvar_cache, &fd_sysvar_slot_history_id, &sz ); - fd_slot_history_view_t slot_history[1]; - if( FD_LIKELY( data && fd_sysvar_slot_history_view( slot_history, data, sz ) ) ) floor = vote_history_floor( fd_ghost_root( ctx->ghost )->slot, file, slot_history ); - - /* If the tower file is ahead of what we have replayed, we need to - wait to catch up (or for lockout to expire) to make sure we aren't - accidentally violating lockout or double voting. */ - ulong wait = vote_history_ahead( tower, ctx->ghost, file ); - if( FD_LIKELY( last>tower->root ) ) adopt_vote_history( ctx ); - if( FD_UNLIKELY( wait && slot_completed->slotwait_to_vote_slot!=wait_to_vote_slot ) ) FD_LOG_INFO(( "set-identity: vote history is ahead of replay, not voting below slot %lu", wait_to_vote_slot )); - tower->wait_to_vote_slot = wait_to_vote_slot; - return; - } - -/* Either: - 1. The tower file is behind our root, so drop it. - 2. The tower file was ahead, but its missing blocks never arrived - and their lockouts have expired, so drop the votes on them. - 3. All of its blocks are replayed, so it is fully adopted. - - In every case we still don't vote until any of its votes on - abandoned forks stop locking us out. */ - if ( FD_UNLIKELY( last<=tower->root ) ) { FD_LOG_NOTICE(( "set-identity: vote history is behind root %lu (last vote %lu)", tower->root, last )); ctx->vote_history_last = 0UL; } - else if( FD_UNLIKELY( wait ) ) FD_LOG_WARNING(( "set-identity: vote history blocks were never replayed, dropping the votes on them" )); - else FD_LOG_NOTICE(( "set-identity: restored vote history, last vote %lu, root %lu", last, tower->root )); - ctx->vote_history_pending = 0; - tower->wait_to_vote_slot = fd_ulong_max( wait, floor ); -} - -static void -replay_slot_completed( fd_tower_tile_t * ctx, - fd_replay_slot_completed_t * slot_completed, - ulong tsorig, - fd_stem_context_t * stem ) { - - /* If the slot has already been replayed, we can just ignore it (but - refresh bank_seq so confirmations reference the surviving row, and - release the bank ref). */ - fd_ghost_blk_t * replayed = fd_ghost_query( ctx->ghost, &slot_completed->block_id ); - if( FD_UNLIKELY( replayed ) ) { - replayed->bank_seq = slot_completed->bank_seq; - publish_slot_ignored( ctx, slot_completed, tsorig, stem ); - return; - } - - /* Sanity checks. */ - - FD_TEST( 0!=memcmp( &slot_completed->block_id, &hash_null, sizeof(fd_hash_t) ) ); - - fd_tower_stakes_remove( ctx->tower, slot_completed->slot ); /* no-op for 99% of cases except for eqvoc */ - fd_tower_vtr_t * tower_voters = ctx->tower->vtrs; - fd_tower_vtr_remove_all( tower_voters ); - ctx->vtr_cnt = 0; - - /* Insert into ghost. */ - - fd_ghost_blk_t * ghost_blk; - if( FD_UNLIKELY( !ctx->init ) ) { - - /* This is the first replay_slot_completed (ie. the snapshot - slot), so initialize the ghost root. */ - - ghost_blk = fd_ghost_init( ctx->ghost, slot_completed->bank_seq, slot_completed->slot, &slot_completed->block_id ); - - } else if ( FD_UNLIKELY( !fd_ghost_query( ctx->ghost, &slot_completed->parent_block_id ) )) { - - /* Due to asynchronous frag processing, it's possible this block from - replay_slot_completed is on a minority fork Tower already pruned - after publishing a new root. */ - - ctx->metrics.ignored_cnt++; - ctx->metrics.ignored_slot = slot_completed->slot; - publish_slot_ignored( ctx, slot_completed, tsorig, stem ); - report_slot_confirmed( slot_completed->bank_seq, slot_completed->slot, &slot_completed->block_id, 0UL /* stake */, 0UL /* total_stake */, 1 /* valid */, FD_EVENT_SLOT_CONFIRMED_LEVEL_IGNORED, 0 /* not forward */ ); - return; /* short-circuit processing this slot */ - - } else { - - /* Common case. */ - - ghost_blk = fd_ghost_insert( ctx->ghost, slot_completed->bank_seq, slot_completed->slot, &slot_completed->block_id, &slot_completed->parent_block_id ); - } - FD_TEST( ghost_blk ); - - /* Insert into tower. */ - - fd_tower_blk_t * eqvoc_tower_blk = NULL; - if( FD_UNLIKELY( eqvoc_tower_blk = fd_tower_blocks_query( ctx->tower, slot_completed->slot ) ) ) { - - /* If eqvoc_tower_blk is not NULL, then we know this slot - equivocates (there are multiple blocks in the slot). - - Replay processes at most 2 equivocating blocks for a given slot, - and the latter block is guaranteed to be confirmed. - - At this point, we know we are processing the latter block, so we - record that in the tower_blk. */ - - fd_tower_lockos_remove( ctx->tower, slot_completed->slot ); - - ctx->metrics.eqvoc_cnt++; - ctx->metrics.eqvoc_slot = fd_ulong_max( ctx->metrics.eqvoc_slot, slot_completed->slot ); - - fd_ghost_confirm( ctx->ghost, &slot_completed->block_id ); - FD_BASE58_ENCODE_32_BYTES( eqvoc_tower_blk->replayed_block_id.uc, eqvoc_blk_id ); - FD_LOG_DEBUG(( "[%s] equivocation detected via duplicate replay. slot: %lu. block_id: %s", __func__, slot_completed->slot, eqvoc_blk_id )); - fd_ghost_eqvoc( ctx->ghost, &eqvoc_tower_blk->replayed_block_id ); - report_block_equivocated( &(block_equivocated_args_t){ - .slot = slot_completed->slot, .parent_slot = eqvoc_tower_blk->parent_slot, .epoch = eqvoc_tower_blk->epoch, - .block_id = &eqvoc_tower_blk->replayed_block_id, .sibling_block_id = &slot_completed->block_id, - .bank_hash = &eqvoc_tower_blk->bank_hash, .block_hash = &eqvoc_tower_blk->block_hash, - .bank_seq = 0UL, .is_leader = eqvoc_tower_blk->leader, - .our_block_voted = eqvoc_tower_blk->voted, .our_block_confirmed = our_block_confirmed( eqvoc_tower_blk ), - .block_stake = votes_stake( ctx, slot_completed->slot, &eqvoc_tower_blk->replayed_block_id ), - .sibling_stake = votes_stake( ctx, slot_completed->slot, &slot_completed->block_id ), - .total_stake = ghost_blk->total_stake, - .detection = FD_EVENT_BLOCK_EQUIVOCATED_DETECTION_DUPLICATE_REPLAY } ); - - eqvoc_tower_blk->parent_slot = slot_completed->parent_slot; - eqvoc_tower_blk->bank_hash = slot_completed->bank_hash; - eqvoc_tower_blk->block_hash = slot_completed->block_hash; - eqvoc_tower_blk->replayed_block_id = slot_completed->block_id; - } else { - - /* Otherwise this is the first replay of this block, so insert a new - tower_blk. */ - - fd_tower_blk_t * tower_blk = fd_tower_blocks_insert( ctx->tower, slot_completed->slot, slot_completed->parent_slot ); - tower_blk->parent_slot = slot_completed->parent_slot; - tower_blk->epoch = slot_completed->epoch; - tower_blk->bank_hash = slot_completed->bank_hash; - tower_blk->block_hash = slot_completed->block_hash; - tower_blk->replayed = 1; - tower_blk->replayed_block_id = slot_completed->block_id; - tower_blk->voted = 0; - tower_blk->confirmed = 0; - tower_blk->leader = slot_completed->is_leader; - tower_blk->propagated = 0; - - /* Set the prev_leader_slot. */ - - if( FD_UNLIKELY( tower_blk->leader ) ) { - tower_blk->prev_leader_slot = slot_completed->slot; - } else if ( FD_UNLIKELY( ghost_blk==fd_ghost_root( ctx->ghost ) ) ) { - tower_blk->prev_leader_slot = ULONG_MAX; - } else { - fd_tower_blk_t * parent_tower_blk = fd_tower_blocks_query( ctx->tower, slot_completed->parent_slot ); - FD_TEST( parent_tower_blk ); - tower_blk->prev_leader_slot = parent_tower_blk->prev_leader_slot; - } - - fd_votes_blk_t * fwd_votes_blk = fd_votes_query( ctx->votes, slot_completed->slot, NULL ); - if( FD_UNLIKELY( fwd_votes_blk && fd_uchar_extract_bit( fwd_votes_blk->flags, FD_TOWER_SLOT_CONFIRMED_DUPLICATE+4 ) ) ) { - - /* A block_id for this slot was forward-confirmed at the duplicate - level before replay (publish_slot_confirmed ran when no - ghost_blk existed). Resolve the pending confirmation now. */ - - tower_blk->confirmed = 1; - tower_blk->confirmed_block_id = fwd_votes_blk->key.block_id; - - if( FD_LIKELY( 0==memcmp( &tower_blk->replayed_block_id, &fwd_votes_blk->key.block_id, sizeof(fd_hash_t) ) ) ) { - - /* The forward-confirmed block_id matches what we replayed. */ - - fd_ghost_confirm( ctx->ghost, &slot_completed->block_id ); - - } else { - - /* The forward-confirmed block_id differs from what we replayed, - so our replayed block is an equivocating sibling. */ - - FD_BASE58_ENCODE_32_BYTES( slot_completed->block_id.uc, eqvoc_blk_id ); - FD_LOG_DEBUG(( "[%s] equivocation detected via forward-confirmed block id mismatch (confirmed before replayed). slot: %lu. block_id: %s", __func__, slot_completed->slot, eqvoc_blk_id )); - fd_ghost_eqvoc( ctx->ghost, &slot_completed->block_id ); - report_block_equivocated( &(block_equivocated_args_t){ - .slot = slot_completed->slot, .parent_slot = slot_completed->parent_slot, .epoch = slot_completed->epoch, - .block_id = &slot_completed->block_id, .sibling_block_id = &fwd_votes_blk->key.block_id, - .bank_hash = &slot_completed->bank_hash, .block_hash = &slot_completed->block_hash, - .bank_seq = slot_completed->bank_seq, .is_leader = slot_completed->is_leader, - .our_block_confirmed = 0, - .block_stake = votes_stake( ctx, slot_completed->slot, &slot_completed->block_id ), - .sibling_stake = fwd_votes_blk->stake, - .detection = FD_EVENT_BLOCK_EQUIVOCATED_DETECTION_CONFIRM_MISMATCH } ); - } - - } else if( FD_UNLIKELY( fd_eqvoc_proof_verified( ctx->eqvoc, slot_completed->slot ) ) ) { - - /* Eqvoc already detected equivocation for this slot (via shreds - or gossip before replay). Mark the ghost block invalid. */ - - FD_BASE58_ENCODE_32_BYTES( slot_completed->block_id.uc, eqvoc_blk_id ); - FD_LOG_DEBUG(( "[%s] equivocation detected via eqvoc shred proof before replay. slot: %lu. block_id: %s", __func__, slot_completed->slot, eqvoc_blk_id )); - fd_ghost_eqvoc( ctx->ghost, &slot_completed->block_id ); - report_block_equivocated( &(block_equivocated_args_t){ - .slot = slot_completed->slot, .parent_slot = slot_completed->parent_slot, .epoch = slot_completed->epoch, - .block_id = &slot_completed->block_id, .sibling_block_id = NULL /* unknown */, - .bank_hash = &slot_completed->bank_hash, .block_hash = &slot_completed->block_hash, - .bank_seq = slot_completed->bank_seq, .is_leader = slot_completed->is_leader, - .block_stake = votes_stake( ctx, slot_completed->slot, &slot_completed->block_id ), - .detection = FD_EVENT_BLOCK_EQUIVOCATED_DETECTION_SHRED_PROOF } ); - } - } - - if( FD_UNLIKELY( !ctx->init ) ) { - ctx->metrics.init_slot = slot_completed->slot; - ctx->tower->root = slot_completed->slot; - fd_votes_publish( ctx->votes, slot_completed->slot ); - } - - /* Count the vote accounts and reconcile our own vote account. */ - - ulong our_vote_acct_bal = ULONG_MAX; - ushort our_vote_acct_com = USHORT_MAX; - int found = 0; - ghost_blk->total_stake = QUERY_TOWERS( ctx, slot_completed, ghost_blk, &found, &our_vote_acct_bal, &our_vote_acct_com ); - - /* Capture the values needed for the processed event now: advancing the - root below (fd_ghost_publish) can prune ghost_blk if this block was - replayed on a minority fork, freeing it before we report. */ - - ulong processed_stake = ghost_blk->stake; - ulong processed_total_stake = ghost_blk->total_stake; - int processed_valid = ghost_blk->valid; - - /* The first replay_slot_completed msg is used to initialize the tower - tile's various structures. */ - - if( FD_UNLIKELY( !ctx->init ) ) { - ctx->init = 1; - QUERY_VOTERS( ctx, slot_completed, slot_completed->epoch ); - } - - /* Insert into hard fork detector. */ - - fd_epoch_leaders_t const * lsched = fd_multi_epoch_leaders_get_lsched_for_slot( ctx->mleaders, slot_completed->slot ); - int hfork_flag = fd_hfork_record_our_bank_hash( ctx->hfork, &slot_completed->block_id, &slot_completed->bank_hash, fd_ulong_if( lsched->epoch==ctx->root_epoch, ctx->root_epoch_total_stake, ctx->next_epoch_total_stake ) ); - update_metrics_hfork( ctx, hfork_flag, slot_completed->slot, &slot_completed->block_id, 1 ); - - if( FD_UNLIKELY( ctx->vote_history_pending ) ) check_vote_history( ctx, slot_completed ); - - /* Determine reset, vote, and root slots. There may not be a vote or - root slot but there is always a reset slot. */ - - fd_tower_out_t out = { .vote_slot = ULONG_MAX, .root_slot = ULONG_MAX }; - out.flags = fd_tower_vote_and_reset( ctx->tower, ctx->ghost, ctx->votes, - &out.reset_slot, &out.reset_block_id, &out.reset_bank_seq, - &out.vote_slot, &out.vote_block_id, &out.vote_bank_hash, - &out.root_slot, &out.root_block_id ); - if( FD_LIKELY( out.vote_slot!=ULONG_MAX ) ) { /* if there is a vote slot we record it. */ - fd_tower_blk_t * vote_tower_blk = fd_tower_blocks_query( ctx->tower, out.vote_slot ); - vote_tower_blk->voted = 1; - vote_tower_blk->voted_block_id = out.vote_block_id; - } - - /* Publish structures if there is a new root. */ - - if( FD_UNLIKELY( out.root_slot!=ULONG_MAX ) ) { - if( FD_UNLIKELY( 0==memcmp( &out.root_block_id, &hash_null, sizeof(fd_hash_t) ) ) ) { - FD_LOG_CRIT(( "invariant violation: root block id is null at slot %lu", out.root_slot )); - } - - fd_tower_blk_t * newr_tower_blk = fd_tower_blocks_query( ctx->tower, out.root_slot ); - FD_TEST( newr_tower_blk ); - - /* It is a Solana consensus protocol invariant that a validator must - make at least one root in an epoch, so the root's epoch cannot - advance by more than one. Compare against root_epoch, the epoch - voters were last indexed for, since reconcile can move tower->root - into the next epoch without publishing a root. */ - - FD_TEST( ctx->root_epoch==newr_tower_blk->epoch || ctx->root_epoch+1==newr_tower_blk->epoch ); /* root can only move forward one epoch */ - - /* Publish votes: 1. reindex if it's a new epoch. 2. publish the new - root to votes. */ - - if( FD_UNLIKELY( ctx->root_epoch+1==newr_tower_blk->epoch ) ) { - FD_TEST( newr_tower_blk->epoch==slot_completed->epoch ); /* new root's epoch must be same as current slot_completed */ - QUERY_VOTERS( ctx, slot_completed, newr_tower_blk->epoch ); - } - fd_votes_publish( ctx->votes, out.root_slot ); - - /* Publish tower_blocks and tower_stakes by removing any entries - older than the new root. Start from the ghost root, which trails - tower->root when reconcile moved it. */ - - for( ulong slot = fd_ghost_root( ctx->ghost )->slot; slot < out.root_slot; slot++ ) { - fd_tower_blocks_remove( ctx->tower, slot ); - fd_tower_lockos_remove( ctx->tower, slot ); - fd_tower_stakes_remove( ctx->tower, slot ); - } - - /* Publish roots by walking up the ghost ancestry to publish new root - frags for intermediate slots we couldn't vote for. */ - - fd_ghost_blk_t * newr = fd_ghost_query( ctx->ghost, &out.root_block_id ); - fd_ghost_blk_t * oldr = fd_ghost_root( ctx->ghost ); - - if( FD_UNLIKELY( !newr ) ) { - FD_BASE58_ENCODE_32_BYTES( out.root_block_id.uc, root_blk_id ); - FD_LOG_ERR(( "new root block %s (slot %lu) is not in ghost: our root conflicts with the cluster", root_blk_id, out.root_slot )); - } - - /* oldr is not guaranteed to be the immediate parent of newr, but is - rather an arbitrary ancestor. This can happen if we couldn't - vote for those intermediate slot(s). We publish those slots as - intermediate roots. */ - - fd_ghost_blk_t * intr = newr; - while( FD_LIKELY( intr!=oldr ) ) { - publish_slot_rooted( ctx, intr->slot, &intr->id ); - report_slot_confirmed( intr->bank_seq, intr->slot, &intr->id, intr->stake, intr->total_stake, intr->valid, FD_EVENT_SLOT_CONFIRMED_LEVEL_ROOTED, 0 /* not forward */ ); - intr = fd_ghost_parent( ctx->ghost, intr ); - } - - /* Publish ghost. */ - - fd_ghost_publish( ctx->ghost, newr ); - - /* Update the new root. */ - - ctx->tower->root = out.root_slot; - } - - /* Publish a slot_done frag to tower_out. */ - - publish_slot_done( ctx, slot_completed, &out, found, our_vote_acct_bal, our_vote_acct_com, tsorig, stem ); - report_slot_confirmed( slot_completed->bank_seq, slot_completed->slot, &slot_completed->block_id, processed_stake, processed_total_stake, processed_valid, FD_EVENT_SLOT_CONFIRMED_LEVEL_PROCESSED, 0 /* not forward */ ); - - /* Write out metrics. */ - - ctx->metrics.replay_slot = slot_completed->slot; - if( FD_LIKELY( out.vote_slot!=ULONG_MAX ) ) ctx->metrics.last_vote_slot = out.vote_slot; - ctx->metrics.last_vote_slot = fd_ulong_if( out.vote_slot!=ULONG_MAX, out.vote_slot, ctx->metrics.last_vote_slot ); - ctx->metrics.reset_slot = out.reset_slot; /* always set */ - ctx->metrics.root_slot = ctx->tower->root; - - /* Fork-decision axis: fd_tower_vote_and_reset sets exactly one of these - five fork flags, except in the two no-vote-yet short-circuits (case 0a - and 0b) where it sets no flags. Those are distinguished by vote_slot: - 0a does not vote (vote_slot==ULONG_MAX), 0b votes (vote_slot set). */ - - if( fd_uchar_extract_bit( out.flags, FD_TOWER_FLAG_ANCESTOR_ROLLBACK ) ) ctx->metrics.fork[ FD_METRICS_ENUM_TOWER_FORK_RESULT_V_ANCESTOR_ROLLBACK_IDX ]++; - else if( fd_uchar_extract_bit( out.flags, FD_TOWER_FLAG_SIBLING_CONFIRMED ) ) ctx->metrics.fork[ FD_METRICS_ENUM_TOWER_FORK_RESULT_V_SIBLING_CONFIRMED_IDX ]++; - else if( fd_uchar_extract_bit( out.flags, FD_TOWER_FLAG_SAME_FORK ) ) ctx->metrics.fork[ FD_METRICS_ENUM_TOWER_FORK_RESULT_V_SAME_FORK_IDX ]++; - else if( fd_uchar_extract_bit( out.flags, FD_TOWER_FLAG_SWITCH_PASS ) ) ctx->metrics.fork[ FD_METRICS_ENUM_TOWER_FORK_RESULT_V_SWITCH_PASSED_IDX ]++; - else if( fd_uchar_extract_bit( out.flags, FD_TOWER_FLAG_SWITCH_FAIL ) ) ctx->metrics.fork[ FD_METRICS_ENUM_TOWER_FORK_RESULT_V_SWITCH_FAILED_IDX ]++; - else if( out.vote_slot!=ULONG_MAX ) ctx->metrics.fork[ FD_METRICS_ENUM_TOWER_FORK_RESULT_V_EMPTY_TOWER_VOTE_IDX ]++; - else ctx->metrics.fork[ FD_METRICS_ENUM_TOWER_FORK_RESULT_V_EMPTY_TOWER_STALE_IDX ]++; - - /* Vote-gate axis: if a votable block was selected, it is gated by the - lockout/threshold/propagated checks (at most one fails) or it passes - all of them and we vote. If no votable block was selected, there is - no candidate to gate. */ - - if( out.vote_slot!=ULONG_MAX ) ctx->metrics.gate[ FD_METRICS_ENUM_TOWER_VOTE_GATE_RESULT_V_VOTED_IDX ]++; - else if( fd_uchar_extract_bit( out.flags, FD_TOWER_FLAG_LOCKOUT_FAIL ) ) ctx->metrics.gate[ FD_METRICS_ENUM_TOWER_VOTE_GATE_RESULT_V_LOCKOUT_FAILED_IDX ]++; - else if( fd_uchar_extract_bit( out.flags, FD_TOWER_FLAG_THRESHOLD_FAIL ) ) ctx->metrics.gate[ FD_METRICS_ENUM_TOWER_VOTE_GATE_RESULT_V_THRESHOLD_FAILED_IDX ]++; - else if( fd_uchar_extract_bit( out.flags, FD_TOWER_FLAG_PROPAGATED_FAIL ) ) ctx->metrics.gate[ FD_METRICS_ENUM_TOWER_VOTE_GATE_RESULT_V_PROPAGATED_FAILED_IDX ]++; - else ctx->metrics.gate[ FD_METRICS_ENUM_TOWER_VOTE_GATE_RESULT_V_NO_CANDIDATE_IDX ]++; - - /* Log out structures. */ - - char cstr[4096]; ulong cstr_sz; - FD_LOG_DEBUG(( "\n\n%s", fd_ghost_to_cstr( ctx->ghost, fd_ghost_root( ctx->ghost ), cstr, sizeof(cstr), &cstr_sz ) )); - FD_LOG_DEBUG(( "\n\n%s", fd_tower_to_cstr( ctx->tower, cstr ) )); -} - -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 ) { - ulong slot_max = fd_ulong_pow2_up( tile->tower.max_live_slots ); - ulong blk_max = slot_max * EQVOC_MAX; - ulong fec_max = slot_max * tile->tower.max_shreds_per_block / FD_FEC_SHRED_CNT; - ulong pub_max = slot_max * FD_TOWER_SLOT_CONFIRMED_LEVEL_CNT; - - ulong l = FD_LAYOUT_INIT; - l = FD_LAYOUT_APPEND( l, alignof(fd_tower_tile_t), sizeof(fd_tower_tile_t) ); - l = FD_LAYOUT_APPEND( l, auth_vtr_align(), auth_vtr_footprint() ); - /* auth_vtr_keyswitch */ - l = FD_LAYOUT_APPEND( l, fd_eqvoc_align(), fd_eqvoc_footprint( slot_max, fec_max, PER_VTR_MAX, VTR_MAX ) ); - l = FD_LAYOUT_APPEND( l, fd_ghost_align(), fd_ghost_footprint( blk_max, VTR_MAX ) ); - l = FD_LAYOUT_APPEND( l, fd_hfork_align(), fd_hfork_footprint( PER_VTR_MAX, VTR_MAX ) ); - l = FD_LAYOUT_APPEND( l, fd_votes_align(), fd_votes_footprint( slot_max, VTR_MAX ) ); - l = FD_LAYOUT_APPEND( l, fd_tower_align(), fd_tower_footprint( slot_max, VTR_MAX ) ); - l = FD_LAYOUT_APPEND( l, fd_tower_vote_align(), fd_tower_vote_footprint() ); - l = FD_LAYOUT_APPEND( l, publishes_align(), publishes_footprint( pub_max ) ); - l = FD_LAYOUT_APPEND( l, fd_accdb_align(), fd_accdb_footprint( tile->tower.max_live_slots, 0 ) ); - ulong epoch_vtr_chain_cnt = epoch_vtr_map_chain_cnt_est( VTR_MAX ); - l = FD_LAYOUT_APPEND( l, epoch_vtr_pool_align(), epoch_vtr_pool_footprint( VTR_MAX ) ); - l = FD_LAYOUT_APPEND( l, epoch_vtr_map_align(), epoch_vtr_map_footprint( epoch_vtr_chain_cnt ) ); - l = FD_LAYOUT_APPEND( l, epoch_vtr_pool_align(), epoch_vtr_pool_footprint( VTR_MAX ) ); - l = FD_LAYOUT_APPEND( l, epoch_vtr_map_align(), epoch_vtr_map_footprint( epoch_vtr_chain_cnt ) ); - return FD_LAYOUT_FINI( l, scratch_align() ); -} - -/* init_choreo allocates and initializes all choreo consensus structures - from scratch memory. scratch must be at least scratch_footprint - bytes aligned to scratch_align(). The seed field at the start of - scratch must be pre-initialized (eg. by privileged_init). Returns a - handle to the fd_tower_tile_t in scratch. */ - -static fd_tower_tile_t * -init_choreo( void * scratch, - fd_topo_t const * topo, - fd_topo_tile_t const * tile ) { - ulong slot_max = fd_ulong_pow2_up( tile->tower.max_live_slots ); - ulong blk_max = slot_max * EQVOC_MAX; - ulong fec_max = slot_max * tile->tower.max_shreds_per_block / FD_FEC_SHRED_CNT; - ulong pub_max = slot_max * FD_TOWER_SLOT_CONFIRMED_LEVEL_CNT; - - void * _accdb_shmem = fd_topo_obj_laddr( topo, tile->tower.accdb_obj_id ); - fd_accdb_shmem_t * accdb_shmem = fd_accdb_shmem_join( _accdb_shmem ); - FD_TEST( accdb_shmem ); - - FD_SCRATCH_ALLOC_INIT( l, scratch ); - fd_tower_tile_t * ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_tower_tile_t), sizeof(fd_tower_tile_t) ); - void * auth_vtr = FD_SCRATCH_ALLOC_APPEND( l, auth_vtr_align(), auth_vtr_footprint() ); - void * eqvoc = FD_SCRATCH_ALLOC_APPEND( l, fd_eqvoc_align(), fd_eqvoc_footprint( slot_max, fec_max, PER_VTR_MAX, VTR_MAX ) ); - void * ghost = FD_SCRATCH_ALLOC_APPEND( l, fd_ghost_align(), fd_ghost_footprint( blk_max, VTR_MAX ) ); - void * hfork = FD_SCRATCH_ALLOC_APPEND( l, fd_hfork_align(), fd_hfork_footprint( PER_VTR_MAX, VTR_MAX ) ); - void * votes = FD_SCRATCH_ALLOC_APPEND( l, fd_votes_align(), fd_votes_footprint( slot_max, VTR_MAX ) ); - void * tower = FD_SCRATCH_ALLOC_APPEND( l, fd_tower_align(), fd_tower_footprint( slot_max, VTR_MAX ) ); - void * scratch_tower = FD_SCRATCH_ALLOC_APPEND( l, fd_tower_vote_align(), fd_tower_vote_footprint() ); - void * publishes = FD_SCRATCH_ALLOC_APPEND( l, publishes_align(), publishes_footprint( pub_max ) ); - void * accdb = FD_SCRATCH_ALLOC_APPEND( l, fd_accdb_align(), fd_accdb_footprint( tile->tower.max_live_slots, 0 ) ); - ulong epoch_vtr_chain_cnt = epoch_vtr_map_chain_cnt_est( VTR_MAX ); - void * root_epoch_vtr_pool = FD_SCRATCH_ALLOC_APPEND( l, epoch_vtr_pool_align(), epoch_vtr_pool_footprint( VTR_MAX ) ); - void * root_epoch_vtr_map = FD_SCRATCH_ALLOC_APPEND( l, epoch_vtr_map_align(), epoch_vtr_map_footprint( epoch_vtr_chain_cnt ) ); - void * next_epoch_vtr_pool = FD_SCRATCH_ALLOC_APPEND( l, epoch_vtr_pool_align(), epoch_vtr_pool_footprint( VTR_MAX ) ); - void * next_epoch_vtr_map = FD_SCRATCH_ALLOC_APPEND( l, epoch_vtr_map_align(), epoch_vtr_map_footprint( epoch_vtr_chain_cnt ) ); - 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 ) )); - (void)auth_vtr; /* privileged_init */ - ctx->eqvoc = fd_eqvoc_join ( fd_eqvoc_new ( eqvoc, slot_max, fec_max, PER_VTR_MAX, VTR_MAX, ctx->seed ) ); - ctx->ghost = fd_ghost_join ( fd_ghost_new ( ghost, blk_max, VTR_MAX, ctx->seed ) ); - ctx->hfork = fd_hfork_join ( fd_hfork_new ( hfork, PER_VTR_MAX, VTR_MAX, ctx->seed ) ); - ctx->votes = fd_votes_join ( fd_votes_new ( votes, slot_max, VTR_MAX, ctx->seed ) ); - ctx->tower = fd_tower_join ( fd_tower_new ( tower, slot_max, VTR_MAX, ctx->seed ) ); - ctx->scratch_tower = fd_tower_vote_join ( fd_tower_vote_new ( scratch_tower ) ); - ctx->publishes = publishes_join ( publishes_new ( publishes, pub_max ) ); - fd_sleep_t * accdb_sleep = topo->sleep_obj_id!=ULONG_MAX ? fd_sleep_join( fd_topo_obj_laddr( topo, topo->sleep_obj_id ) ) : NULL; - ctx->accdb = fd_accdb_join ( fd_accdb_new ( accdb, _accdb_shmem, FD_ACCDB_FD_RW, 0UL, NULL, accdb_sleep, fd_topo_find_tile( topo, "accdb", 0UL ), 0 ) ); - ctx->mleaders = fd_multi_epoch_leaders_join( fd_multi_epoch_leaders_new( ctx->mleaders_mem ) ); - ctx->root_epoch_vtr_pool = epoch_vtr_pool_join( epoch_vtr_pool_new( root_epoch_vtr_pool, VTR_MAX ) ); - ctx->root_epoch_vtr_map = epoch_vtr_map_join ( epoch_vtr_map_new ( root_epoch_vtr_map, epoch_vtr_chain_cnt, ctx->seed ) ); - ctx->next_epoch_vtr_pool = epoch_vtr_pool_join( epoch_vtr_pool_new( next_epoch_vtr_pool, VTR_MAX ) ); - ctx->next_epoch_vtr_map = epoch_vtr_map_join ( epoch_vtr_map_new ( next_epoch_vtr_map, epoch_vtr_chain_cnt, ctx->seed ) ); - - FD_TEST( ctx->eqvoc ); - FD_TEST( ctx->ghost ); - FD_TEST( ctx->hfork ); - FD_TEST( ctx->votes ); - FD_TEST( ctx->tower ); - FD_TEST( ctx->scratch_tower ); - FD_TEST( ctx->publishes ); - FD_TEST( ctx->accdb ); - FD_TEST( ctx->mleaders ); - FD_TEST( ctx->root_epoch_vtr_pool ); - FD_TEST( ctx->root_epoch_vtr_map ); - FD_TEST( ctx->next_epoch_vtr_pool ); - FD_TEST( ctx->next_epoch_vtr_map ); - - memset( ctx->duplicate_chunks, 0, sizeof(ctx->duplicate_chunks) ); - memset( &ctx->compact_tower_sync_serde, 0, sizeof(ctx->compact_tower_sync_serde) ); - memset( ctx->vote_txn, 0, sizeof(ctx->vote_txn) ); - ctx->vote_history_pending = 0; - ctx->vote_history_last = 0UL; - ctx->tower_file_pending = 0UL; - - ctx->halt_signing = 0; - ctx->hard_fork_fatal = tile->tower.hard_fork_fatal; - ctx->wfs_slot = tile->tower.wait_for_supermajority_at_slot; - ctx->wfs_hash_is_zero = tile->tower.wait_for_supermajority_hash_is_zero; - ctx->wfs_shred_version = tile->tower.expected_shred_version; - ctx->shred_version = 0; - ctx->init = 0; - ctx->root_epoch = ULONG_MAX; - - memset( &ctx->metrics, 0, sizeof(ctx->metrics) ); - ctx->metrics.last_vote_slot = ULONG_MAX; - ctx->metrics.tower_file_slot = ULONG_MAX; - - return ctx; -} - -/* tower_file_write saves our last vote the way Agave does, so an - operator can move it to another validator with set-identity. The - file is signed by the identity, so it is only written while the sign - tile is known to hold the same identity key as we do. - - The first write after a set-identity gives the files the name of - the new identity. A file that already has that name, e.g. the - tower file passed to set-identity, is kept under a free .old name, - not replaced. */ - -static void -tower_file_write( fd_tower_tile_t * ctx ) { - FD_BASE58_ENCODE_32_BYTES( ctx->identity_key->uc, identity_key_b58 ); - char name[ 2 ][ PATH_MAX ]; - FD_TEST( fd_cstr_printf_check( name[ 1 ], PATH_MAX, NULL, "tower-1_9-%s.bin", identity_key_b58 ) ); - FD_TEST( fd_cstr_printf_check( name[ 0 ], PATH_MAX, NULL, "tower-1_9-%s.bin.new", identity_key_b58 ) ); - - /* Handle the case around an identity switch. Swap the name of the - files and handle naming collisions. */ - if( FD_UNLIKELY( strcmp( name[ 1 ], ctx->tower_name[ 1 ] ) ) ) { - for( ulong i=0UL; i<2UL; i++ ) { /* [0] staging, [1] live */ - if( FD_LIKELY( !syscall( SYS_renameat2, ctx->tower_dir_fd, ctx->tower_name[ i ], ctx->tower_dir_fd, name[ i ], RENAME_NOREPLACE ) ) ) continue; - if( FD_UNLIKELY( errno!=EEXIST ) ) FD_LOG_ERR(( "renameat2(%s, %s) failed (%i-%s)", ctx->tower_name[ i ], name[ i ], errno, fd_io_strerror( errno ) )); - - char old[ PATH_MAX ]; - for( ulong j=0UL;; j++ ) { - if( j ) FD_TEST( fd_cstr_printf_check( old, PATH_MAX, NULL, "%s.old.%lu", name[ i ], j ) ); - else FD_TEST( fd_cstr_printf_check( old, PATH_MAX, NULL, "%s.old", name[ i ] ) ); - if( FD_LIKELY( !syscall( SYS_renameat2, ctx->tower_dir_fd, name[ i ], ctx->tower_dir_fd, old, RENAME_NOREPLACE ) ) ) break; - if( FD_UNLIKELY( errno!=EEXIST ) ) FD_LOG_ERR(( "renameat2(%s, %s) failed (%i-%s)", name[ i ], old, errno, fd_io_strerror( errno ) )); - } - FD_LOG_WARNING(( "tower file %s already existed, moved it to %s", name[ i ], old )); - if( FD_UNLIKELY( syscall( SYS_renameat2, ctx->tower_dir_fd, ctx->tower_name[ i ], ctx->tower_dir_fd, name[ i ], RENAME_NOREPLACE ) ) ) - FD_LOG_ERR(( "renameat2(%s, %s) failed (%i-%s)", ctx->tower_name[ i ], name[ i ], errno, fd_io_strerror( errno ) )); - } - FD_LOG_NOTICE(( "tower file %s renamed to %s for the new identity", ctx->tower_name[ 1 ], name[ 1 ] )); - memcpy( ctx->tower_name, name, sizeof(name) ); - } - - /* Produce and sign the contents of a valid tower file. */ - uchar buf[ FD_TOWER_FILE_MAX ]; - ulong sz = fd_tower_file_ser( &ctx->compact_tower_sync_serde, ctx->identity_key, buf ); - fd_keyguard_client_sign( ctx->keyguard_client, buf+FD_TOWER_FILE_SIG_OFF, buf+FD_TOWER_FILE_DATA_OFF, sz-FD_TOWER_FILE_DATA_OFF, FD_KEYGUARD_SIGN_TYPE_ED25519 ); - - /* Write and truncate staging file */ - for( ulong off=0UL; offtower_fd[ 0 ], &(struct iovec){ .iov_base=buf+off, .iov_len=sz-off }, 1, (long)off, 0 ); - if( FD_UNLIKELY( -1==n && errno==EINTR ) ) continue; - if( FD_UNLIKELY( n<=0 ) ) FD_LOG_ERR(( "pwritev2(%s) failed (%i-%s)", ctx->tower_name[ 0 ], errno, fd_io_strerror( errno ) )); - off += (ulong)n; - } - while( FD_UNLIKELY( ftruncate( ctx->tower_fd[ 0 ], (long)sz ) ) ) { - if( FD_UNLIKELY( errno!=EINTR ) ) FD_LOG_ERR(( "ftruncate(%s) failed (%i-%s)", ctx->tower_name[ 0 ], errno, fd_io_strerror( errno ) )); - } - - /* The exchange makes the new tower live atomically. Files cannot be - opened after boot, so old live file is now the staging file. */ - if( FD_UNLIKELY( syscall( SYS_renameat2, ctx->tower_dir_fd, ctx->tower_name[ 0 ], ctx->tower_dir_fd, ctx->tower_name[ 1 ], RENAME_EXCHANGE ) ) ) - FD_LOG_ERR(( "renameat2(%s, %s) failed (%i-%s)", ctx->tower_name[ 0 ], ctx->tower_name[ 1 ], errno, fd_io_strerror( errno ) )); - int staging = ctx->tower_fd[ 0 ]; ctx->tower_fd[ 0 ] = ctx->tower_fd[ 1 ]; ctx->tower_fd[ 1 ] = staging; - - ctx->metrics.tower_file_write++; - ctx->metrics.tower_file_slot = ctx->tower_file_pending; - ctx->metrics.tower_file_sz = sz; - ctx->tower_file_pending = 0UL; -} - -static void -during_housekeeping( fd_tower_tile_t * ctx ) { - if( FD_UNLIKELY( ctx->tower_file_pending && !ctx->halt_signing && ctx->tower_dir_fd!=-1 ) ) tower_file_write( ctx ); - - if( FD_UNLIKELY( fd_keyswitch_state_query( ctx->auth_vtr_keyswitch )==FD_KEYSWITCH_STATE_UNHALT_PENDING ) ) { - if( fd_keyswitch_param_query( ctx->auth_vtr_keyswitch )==FD_KEYSWITCH_PARAM_AV_CLEAR ) ctx->halt_signing = 0; - fd_keyswitch_state( ctx->auth_vtr_keyswitch, FD_KEYSWITCH_STATE_UNLOCKED ); - } - - if( FD_UNLIKELY( fd_keyswitch_state_query( ctx->auth_vtr_keyswitch )==FD_KEYSWITCH_STATE_SWITCH_PENDING ) ) { - ulong param = fd_keyswitch_param_query( ctx->auth_vtr_keyswitch ); - if( FD_LIKELY( param==FD_KEYSWITCH_PARAM_AV_ADD ) ) { - fd_pubkey_t pubkey = *(fd_pubkey_t const *)fd_type_pun_const( ctx->auth_vtr_keyswitch->bytes ); - if( FD_UNLIKELY( auth_vtr_query( ctx->auth_vtr, pubkey, NULL ) ) ) FD_LOG_CRIT(( "keyswitch: duplicate authorized voter key, keys not synced up with sign tile" )); - if( FD_UNLIKELY( ctx->auth_vtr_path_cnt==FD_KEYGUARD_AUTH_VOTERS_MAX ) ) FD_LOG_CRIT(( "keyswitch: too many authorized voters, keys not synced up with sign tile" )); - - auth_vtr_t * auth_vtr = auth_vtr_insert( ctx->auth_vtr, pubkey ); - auth_vtr->paths_idx = ctx->auth_vtr_path_cnt; - ctx->auth_vtr_path_cnt++; - } else if( FD_LIKELY( param==FD_KEYSWITCH_PARAM_AV_CLEAR ) ) { - ctx->halt_signing = 1; - auth_vtr_clear( ctx->auth_vtr ); - ctx->auth_vtr_path_cnt = 0UL; - if( FD_UNLIKELY( !publishes_empty( ctx->publishes ) ) ) return; - ctx->auth_vtr_keyswitch->result = ctx->out_seq; - } else { - FD_LOG_CRIT(( "keyswitch: unexpected authorized voter operation %lu", param )); - } - fd_keyswitch_state( ctx->auth_vtr_keyswitch, FD_KEYSWITCH_STATE_COMPLETED ); - } - - if( FD_UNLIKELY( fd_keyswitch_state_query( ctx->identity_keyswitch )==FD_KEYSWITCH_STATE_UNHALT_PENDING ) ) { - FD_LOG_DEBUG(( "keyswitch: unhalting signing" )); - FD_CHECK_CRIT( ctx->halt_signing, "state machine corruption" ); - ctx->halt_signing = 0; - fd_keyswitch_state( ctx->identity_keyswitch, FD_KEYSWITCH_STATE_COMPLETED ); - } - - if( FD_UNLIKELY( fd_keyswitch_state_query( ctx->identity_keyswitch )==FD_KEYSWITCH_STATE_SWITCH_PENDING ) ) { - ulong seq_must_complete = fd_keyswitch_param_query( ctx->identity_keyswitch ); - if( FD_UNLIKELY( fd_seq_lt( ctx->replay_in_seq, seq_must_complete ) ) ) return; - - if( FD_LIKELY( !ctx->halt_signing ) ) FD_LOG_DEBUG(( "keyswitch: halting signing" )); - ctx->halt_signing = 1; - if( FD_UNLIKELY( !publishes_empty( ctx->publishes ) ) ) return; - - int identity_changed = !!memcmp( ctx->identity_key, ctx->identity_keyswitch->bytes, 32UL ); - memcpy( ctx->identity_key, ctx->identity_keyswitch->bytes, 32UL ); - if( identity_changed ) { - clear_votes( ctx->tower ); - ctx->tower->wait_to_vote_slot = 0UL; - ctx->vote_history_pending = !!FD_LOAD( ulong, ctx->identity_keyswitch->bytes+32UL ); - if( ctx->vote_history_pending ) memcpy( &ctx->vote_history, ctx->identity_keyswitch->bytes+40UL, sizeof(fd_tower_file_t) ); - ctx->vote_history_last = ctx->vote_history_pending ? ctx->vote_history.votes[ ctx->vote_history.votes_cnt-1UL ].slot : 0UL; - } - FD_BASE58_ENCODE_32_BYTES( ctx->identity_key->uc, pubkey_str ); - FD_LOG_INFO(( "my identity key: %s (key switched)", pubkey_str )); - ctx->identity_keyswitch->result = ctx->out_seq; - fd_keyswitch_state( ctx->identity_keyswitch, FD_KEYSWITCH_STATE_COMPLETED ); - } -} - -static inline void -metrics_write( fd_tower_tile_t * ctx ) { - fd_accdb_flush_metrics( ctx->accdb ); - - FD_MCNT_SET( TOWER, FRAG_NOT_READY_DROPPED, ctx->metrics.not_ready ); - - FD_MCNT_SET ( TOWER, PRUNED_FORK_SLOT_IGNORED, ctx->metrics.ignored_cnt ); - FD_MGAUGE_SET( TOWER, LAST_PRUNED_FORK_IGNORED_SLOT, ctx->metrics.ignored_slot ); - - FD_MGAUGE_SET( TOWER, LAST_REPLAYED_SLOT, ctx->metrics.replay_slot ); - FD_MGAUGE_SET( TOWER, LAST_VOTE_SLOT, fd_ulong_if( ctx->metrics.last_vote_slot==ULONG_MAX, 0UL, ctx->metrics.last_vote_slot ) ); - FD_MGAUGE_SET( TOWER, RESET_SLOT, ctx->metrics.reset_slot ); - FD_MGAUGE_SET( TOWER, ROOT_SLOT, fd_ulong_if( ctx->metrics.root_slot==ULONG_MAX, 0UL, ctx->metrics.root_slot ) ); - FD_MGAUGE_SET( TOWER, INIT_SLOT, ctx->metrics.init_slot ); - - FD_MCNT_ENUM_COPY( TOWER, FORK_CHOSEN, ctx->metrics.fork ); - FD_MCNT_ENUM_COPY( TOWER, VOTE_GATE_CHECKED, ctx->metrics.gate ); - - FD_MCNT_SET ( TOWER, TOWER_FILE_WRITTEN, ctx->metrics.tower_file_write ); - FD_MGAUGE_SET( TOWER, TOWER_FILE_SLOT, fd_ulong_if( ctx->metrics.tower_file_slot==ULONG_MAX, 0UL, ctx->metrics.tower_file_slot ) ); - FD_MGAUGE_SET( TOWER, TOWER_FILE_SIZE_BYTES, ctx->metrics.tower_file_sz ); - - FD_MCNT_ENUM_COPY( TOWER, VOTE_TXN_PROCESSED, ctx->metrics.votes ); - FD_MCNT_ENUM_COPY( TOWER, VOTE_SLOT_PROCESSED, ctx->metrics.vote_slots ); - FD_MCNT_ENUM_COPY( TOWER, VOTE_INTERMEDIATE_SLOT_CHECKED, ctx->metrics.gate_int ); - - FD_MCNT_ENUM_COPY( TOWER, EQVOC_CHUNK_PROCESSED, ctx->metrics.eqvoc ); - - FD_MCNT_ENUM_COPY( TOWER, GHOST_VOTE_PROCESSED, ctx->metrics.ghost ); - - FD_MCNT_ENUM_COPY( TOWER, HARD_FORK_VOTE_PROCESSED, ctx->metrics.hfork ); - FD_MCNT_ENUM_COPY( TOWER, HARD_FORK_BANK_HASH_CHECKED, ctx->metrics.hfork_bank ); - - FD_MGAUGE_SET( TOWER, HIGHEST_HARD_FORK_MATCHED_SLOT, ctx->metrics.hfork_matched_slot ); - FD_MGAUGE_SET( TOWER, HIGHEST_HARD_FORK_MISMATCHED_SLOT, ctx->metrics.hfork_mismatched_slot ); - - FD_ACCDB_METRICS_WRITE( TOWER, fd_accdb_metrics( ctx->accdb ) ); -} - -static inline void -after_credit( fd_tower_tile_t * ctx, - fd_stem_context_t * stem, - int * opt_poll_in, - int * charge_busy ) { - if( FD_LIKELY( !publishes_empty( ctx->publishes ) ) ) { - publish_t * pub = publishes_pop_head_nocopy( ctx->publishes ); - memcpy( fd_chunk_to_laddr( ctx->out_mem, ctx->out_chunk ), &pub->msg, sizeof(fd_tower_msg_t) ); - fd_stem_publish( stem, OUT_IDX, pub->sig, ctx->out_chunk, sizeof(fd_tower_msg_t), 0UL, fd_frag_meta_ts_comp( fd_tickcount() ), fd_frag_meta_ts_comp( fd_tickcount() ) ); - ctx->out_chunk = fd_dcache_compact_next( ctx->out_chunk, sizeof(fd_tower_msg_t), ctx->out_chunk0, ctx->out_wmark ); - ctx->out_seq = stem->seqs[ OUT_IDX ]; - *opt_poll_in = 0; /* drain the publishes */ - *charge_busy = 1; - } -} - -static inline int -before_frag( fd_tower_tile_t * ctx, - ulong in_idx, - ulong seq, - ulong sig ) { - switch( ctx->in_kind[ in_idx ] ) { - case IN_KIND_GOSSIP: return sig!=FD_GOSSIP_UPDATE_TAG_DUPLICATE_SHRED; - case IN_KIND_REPLAY: { - int filter = sig!=REPLAY_SIG_SLOT_COMPLETED && sig!=REPLAY_SIG_SLOT_DEAD && sig!=REPLAY_SIG_TXN_EXECUTED; - if( filter ) ctx->replay_in_seq = seq+1UL; - return filter; - } - case IN_KIND_SHRED: return fd_shred_sig_src( sig )!=SHRED_SIG_SRC_TURBINE && fd_shred_sig_src( sig )!=SHRED_SIG_SRC_REPAIR; - default: return 0; - } -} - -static inline int -returnable_frag( fd_tower_tile_t * ctx, - ulong in_idx, - ulong seq, - ulong sig, - ulong chunk, - ulong sz, - ulong ctl FD_PARAM_UNUSED, - ulong tsorig, - ulong tspub FD_PARAM_UNUSED, - fd_stem_context_t * stem ) { - - if( FD_UNLIKELY( !ctx->in[ in_idx ].mcache_only && ( chunkin[ in_idx ].chunk0 || chunk>ctx->in[ in_idx ].wmark || sz>ctx->in[ in_idx ].mtu ) ) ) - FD_LOG_ERR(( "chunk %lu %lu from in %d corrupt, not in range [%lu,%lu]", chunk, sz, ctx->in_kind[ in_idx ], ctx->in[ in_idx ].chunk0, ctx->in[ in_idx ].wmark )); - - switch( ctx->in_kind[ in_idx ] ) { - case IN_KIND_DEDUP:{ - if( FD_UNLIKELY( !ctx->init ) ) { - /* we cannot backpressure vote txns on boot, without risking a - deadlock on the snapshot load pipeline. */ - ctx->metrics.not_ready++; - return 0; - } - fd_txn_m_t * txnm = (fd_txn_m_t *)fd_chunk_to_laddr( ctx->in[in_idx].mem, chunk ); - count_vote_txn( ctx, fd_txn_m_txn_t_const( txnm ), fd_txn_m_payload_const( txnm ) ); - return 0; - } - case IN_KIND_EPOCH: { - fd_epoch_info_msg_t const * msg = fd_chunk_to_laddr_const( ctx->in[ in_idx ].mem, chunk ); - FD_TEST( msg->staked_vote_cnt<=MAX_STAKE_WEIGHTS ); - FD_TEST( msg->staked_id_cnt<=MAX_STAKE_WEIGHTS ); - fd_multi_epoch_leaders_epoch_msg_init( ctx->mleaders, msg ); - fd_multi_epoch_leaders_epoch_msg_fini( ctx->mleaders ); - return 0; - } - case IN_KIND_GOSSIP: { - if( FD_UNLIKELY( !ctx->init ) ) { ctx->metrics.not_ready++; return 0; } /* don't backpressure gossip on boot */ - if( FD_LIKELY( sig==FD_GOSSIP_UPDATE_TAG_DUPLICATE_SHRED ) ) { - fd_gossip_update_message_t const * msg = (fd_gossip_update_message_t const *)fd_type_pun_const( fd_chunk_to_laddr_const( ctx->in[ in_idx ].mem, chunk ) ); - fd_gossip_duplicate_shred_t const * duplicate_shred = msg->duplicate_shred; - fd_pubkey_t const * from = (fd_pubkey_t const *)fd_type_pun_const( msg->origin ); - fd_epoch_leaders_t const * lsched = fd_multi_epoch_leaders_get_lsched_for_slot( ctx->mleaders, duplicate_shred->slot ); - if( FD_UNLIKELY( !lsched ) ) { ctx->metrics.not_ready++; return 0; } - int eqvoc_err = fd_eqvoc_chunk_insert( ctx->eqvoc, ctx->tower->root, ctx->shred_version, lsched, from, duplicate_shred, ctx->duplicate_chunks ); - update_metrics_eqvoc( ctx, eqvoc_err ); - if( FD_UNLIKELY( eqvoc_err==FD_EQVOC_SUCCESS ) ) { - publish_slot_duplicate( ctx, ctx->duplicate_chunks, duplicate_shred->slot ); - } - } - return 0; - } - case IN_KIND_IPECHO: { - FD_TEST( sig && sig<=USHORT_MAX ); - ctx->shred_version = (ushort)sig; - return 0; - } - case IN_KIND_REPLAY: { - switch( sig ) { - case REPLAY_SIG_SLOT_COMPLETED:; - if( FD_UNLIKELY( ctx->halt_signing ) ) return 1; /* backpressure replay_slot_completed during halt_signing. */ - fd_replay_slot_completed_t * slot_completed = (fd_replay_slot_completed_t *)fd_type_pun( fd_chunk_to_laddr( ctx->in[ in_idx ].mem, chunk ) ); - /* Return frags until the epoch schedule is loaded. The epoch - schedule frag (replay_epoch link) can arrive after this - slot frag (replay_out link) because they travel on different - links with no cross-link ordering guarantee. */ - if( FD_UNLIKELY( !fd_multi_epoch_leaders_get_lsched_for_slot( ctx->mleaders, slot_completed->slot ) ) ) return 1; - replay_slot_completed( ctx, slot_completed, tsorig, stem ); - break; - case REPLAY_SIG_SLOT_DEAD:; - fd_replay_slot_dead_t * slot_dead = (fd_replay_slot_dead_t *)fd_chunk_to_laddr( ctx->in[ in_idx ].mem, chunk ); - if( FD_UNLIKELY( slot_dead->slot < ctx->tower->root ) ) break; /* ignore dead slots before root */ - fd_epoch_leaders_t const * lsched = fd_multi_epoch_leaders_get_lsched_for_slot( ctx->mleaders, slot_dead->slot ); - FD_TEST( lsched ); - FD_TEST( lsched->epoch==ctx->root_epoch || lsched->epoch==ctx->root_epoch + 1 ); - ulong total_stake = fd_ulong_if( lsched->epoch==ctx->root_epoch, ctx->root_epoch_total_stake, ctx->next_epoch_total_stake ); - int hfork_flag = fd_hfork_record_our_bank_hash( ctx->hfork, &slot_dead->block_id, NULL, total_stake ); - update_metrics_hfork( ctx, hfork_flag, slot_dead->slot, &slot_dead->block_id, 1 ); - break; - case REPLAY_SIG_TXN_EXECUTED:; - FD_TEST( ctx->init ); /* replay_txn_executed should never be received before replay_slot_completed, which sets init to 1. */ - fd_replay_txn_executed_t * txn_executed = fd_type_pun( fd_chunk_to_laddr( ctx->in[in_idx].mem, chunk ) ); - if( FD_UNLIKELY( !txn_executed->is_committable || txn_executed->is_fees_only || txn_executed->txn_err ) ) break; - count_vote_txn( ctx, TXN(txn_executed->txn), txn_executed->txn->payload ); - break; - default: - break; - } - ctx->replay_in_seq = seq+1UL; - return 0; - } - case IN_KIND_SHRED: { - if( FD_LIKELY( fd_shred_sig_src( sig )==SHRED_SIG_SRC_TURBINE || fd_shred_sig_src( sig )==SHRED_SIG_SRC_REPAIR ) ) { - fd_shred_base_t * msg = (fd_shred_base_t *)fd_type_pun( fd_chunk_to_laddr( ctx->in[ in_idx ].mem, chunk ) ); - fd_shred_t * shred = &msg->shred; - int eqvoc_err = fd_eqvoc_shred_insert( ctx->eqvoc, fd_shred_sig_res( sig )==SHRED_SIG_RESULT_EQVOC, shred, ctx->duplicate_chunks ); - update_metrics_eqvoc( ctx, eqvoc_err ); - if( FD_UNLIKELY( eqvoc_err==FD_EQVOC_SUCCESS ) ) publish_slot_duplicate( ctx, ctx->duplicate_chunks, shred->slot ); - } - return 0; - } - default: FD_LOG_ERR(( "unexpected input kind %d", ctx->in_kind[ in_idx ] )); - } -} - -static void -privileged_init( fd_topo_t const * topo, - fd_topo_tile_t const * tile ) { - void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id ); - FD_SCRATCH_ALLOC_INIT( l, scratch ); - fd_tower_tile_t * ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_tower_tile_t), sizeof(fd_tower_tile_t) ); - void * auth_vtr = FD_SCRATCH_ALLOC_APPEND( l, auth_vtr_align(), auth_vtr_footprint() ); - 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 ) )); - - FD_TEST( fd_rng_secure( &ctx->seed, sizeof(ctx->seed) ) ); - - if( FD_UNLIKELY( !strcmp( tile->tower.identity_key, "" ) ) ) FD_LOG_ERR(( "missing [paths.identity_key]" )); - ctx->identity_key[ 0 ] = *(fd_pubkey_t const *)fd_type_pun_const( fd_keyload_load( tile->tower.identity_key, /* pubkey only: */ 1 ) ); - - /* The vote key can be specified either directly as a base58 encoded - pubkey, or as a file path. We first try to decode as a pubkey. */ - - uchar * vote_key = fd_base58_decode_32( tile->tower.vote_account, ctx->vote_account->uc ); - if( FD_UNLIKELY( !vote_key ) ) { - if( FD_UNLIKELY( !strcmp( tile->tower.vote_account, "" ) ) ) FD_LOG_ERR(( "missing [paths.vote_account]" )); - ctx->vote_account[ 0 ] = *(fd_pubkey_t const *)fd_type_pun_const( fd_keyload_load( tile->tower.vote_account, /* pubkey only: */ 1 ) ); - } - - ulong node_info_obj_id = fd_pod_query_ulong( topo->props, "node_info", ULONG_MAX ); FD_TEST( node_info_obj_id!=ULONG_MAX ); - fd_node_info_box_t * node_info = fd_node_info_box_join( fd_topo_obj_laddr( topo, node_info_obj_id ) ); FD_TEST( node_info ); - fd_node_info_write_begin( node_info ); - node_info->info.vote_account = *ctx->vote_account; - fd_node_info_write_end( node_info ); - - ctx->auth_vtr = auth_vtr_join( auth_vtr_new( auth_vtr ) ); - for( ulong i=0UL; itower.authorized_voter_paths_cnt; i++ ) { - fd_pubkey_t pubkey = *(fd_pubkey_t const *)fd_type_pun_const( fd_keyload_load( tile->tower.authorized_voter_paths[ i ], /* pubkey only: */ 1 ) ); - if( FD_UNLIKELY( auth_vtr_query( ctx->auth_vtr, pubkey, NULL ) ) ) { - FD_BASE58_ENCODE_32_BYTES( pubkey.uc, pubkey_b58 ); - FD_LOG_ERR(( "authorized voter key duplicate %s", pubkey_b58 )); - } - - auth_vtr_t * auth_vtr = auth_vtr_insert( ctx->auth_vtr, pubkey ); - auth_vtr->paths_idx = i; - } - ctx->auth_vtr_path_cnt = tile->tower.authorized_voter_paths_cnt; - - ctx->tower_dir_fd = -1; - ctx->tower_fd[ 0 ] = -1; - ctx->tower_fd[ 1 ] = -1; - if( FD_LIKELY( tile->tower.tower_path[ 0 ] ) ) { - FD_BASE58_ENCODE_32_BYTES( ctx->identity_key->uc, identity_key_b58 ); - FD_TEST( fd_cstr_printf_check( ctx->tower_name[ 1 ], PATH_MAX, NULL, "tower-1_9-%s.bin", identity_key_b58 ) ); - FD_TEST( fd_cstr_printf_check( ctx->tower_name[ 0 ], PATH_MAX, NULL, "tower-1_9-%s.bin.new", identity_key_b58 ) ); - - ctx->tower_dir_fd = open( tile->tower.tower_path, O_RDONLY|O_DIRECTORY ); - if( FD_UNLIKELY( -1==ctx->tower_dir_fd ) ) FD_LOG_ERR(( "open(`%s`) failed (%i-%s)", tile->tower.tower_path, errno, fd_io_strerror( errno ) )); - - /* Switch to non-root uid/gid for file creation. */ - gid_t gid = getgid(); - uid_t uid = getuid(); - if( FD_LIKELY( !gid && -1==syscall( __NR_setresgid, -1, tile->tower.target_gid, -1 ) ) ) FD_LOG_ERR(( "setresgid() failed (%i-%s)", errno, fd_io_strerror( errno ) )); - if( FD_LIKELY( !uid && -1==syscall( __NR_setresuid, -1, tile->tower.target_uid, -1 ) ) ) FD_LOG_ERR(( "setresuid() failed (%i-%s)", errno, fd_io_strerror( errno ) )); - - for( ulong i=0UL; i<2UL; i++ ) { - ctx->tower_fd[ i ] = openat( ctx->tower_dir_fd, ctx->tower_name[ i ], O_WRONLY|O_CREAT, 0644 ); - if( FD_UNLIKELY( -1==ctx->tower_fd[ i ] ) ) FD_LOG_ERR(( "open(`%s/%s`) failed (%i-%s), make sure the configured user owns it", tile->tower.tower_path, ctx->tower_name[ i ], errno, fd_io_strerror( errno ) )); - struct stat st; - if( FD_UNLIKELY( -1==fstat( ctx->tower_fd[ i ], &st ) ) ) FD_LOG_ERR(( "fstat(`%s/%s`) failed (%i-%s)", tile->tower.tower_path, ctx->tower_name[ i ], errno, fd_io_strerror( errno ) )); - if( FD_UNLIKELY( st.st_uid!=tile->tower.target_uid ) ) { - FD_LOG_ERR(( "tower file `%s/%s` is owned by uid %u but the configured user is uid %u, chown it to the configured user", tile->tower.tower_path, ctx->tower_name[ i ], st.st_uid, tile->tower.target_uid )); - } - } - - if( FD_UNLIKELY( -1==syscall( __NR_setresuid, -1, uid, -1 ) ) ) FD_LOG_ERR(( "setresuid() failed (%i-%s)", errno, fd_io_strerror( errno ) )); - if( FD_UNLIKELY( -1==syscall( __NR_setresgid, -1, gid, -1 ) ) ) FD_LOG_ERR(( "setresgid() failed (%i-%s)", errno, fd_io_strerror( errno ) )); - } -} - -static void -unprivileged_init( fd_topo_t const * topo, - fd_topo_tile_t const * tile ) { - void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id ); - fd_tower_tile_t * ctx = init_choreo( scratch, topo, tile ); - - ctx->wksp = topo->workspaces[ topo->objs[ tile->tile_obj_id ].wksp_id ].wksp; - ctx->identity_keyswitch = fd_keyswitch_join( fd_topo_obj_laddr( topo, tile->id_keyswitch_obj_id ) ); - ctx->auth_vtr_keyswitch = fd_keyswitch_join( fd_topo_obj_laddr( topo, tile->av_keyswitch_obj_id ) ); - - FD_TEST( ctx->wksp ); - FD_TEST( ctx->identity_keyswitch ); - FD_TEST( ctx->auth_vtr_keyswitch ); - FD_TEST( ctx->auth_vtr ); - - ulong banks_obj_id = fd_pod_query_ulong( topo->props, "banks", ULONG_MAX ); - FD_TEST( banks_obj_id!=ULONG_MAX ); - ctx->banks = fd_banks_join( fd_topo_obj_laddr( topo, banks_obj_id ) ); - FD_TEST( ctx->banks ); - - FD_TEST( tile->in_cntin_kind)/sizeof(ctx->in_kind[0]) ); - for( ulong i=0UL; iin_cnt; i++ ) { - fd_topo_link_t const * link = &topo->links[ tile->in_link_id[ i ] ]; - fd_topo_wksp_t const * link_wksp = &topo->workspaces[ topo->objs[ link->dcache_obj_id ].wksp_id ]; - - if ( FD_LIKELY( !strcmp( link->name, "dedup_resolv" ) ) ) ctx->in_kind[ i ] = IN_KIND_DEDUP; - else if( FD_LIKELY( !strcmp( link->name, "replay_epoch" ) ) ) ctx->in_kind[ i ] = IN_KIND_EPOCH; - else if( FD_LIKELY( !strcmp( link->name, "gossip_misc" ) ) ) ctx->in_kind[ i ] = IN_KIND_GOSSIP; - else if( FD_LIKELY( !strcmp( link->name, "ipecho_out" ) ) ) ctx->in_kind[ i ] = IN_KIND_IPECHO; - else if( FD_LIKELY( !strcmp( link->name, "replay_out" ) ) ) ctx->in_kind[ i ] = IN_KIND_REPLAY; - else if( FD_LIKELY( !strcmp( link->name, "shred_out" ) ) ) ctx->in_kind[ i ] = IN_KIND_SHRED; - else if( FD_LIKELY( !strcmp( link->name, "sign_tower" ) ) ) ctx->in_kind[ i ] = IN_KIND_SIGN; - else FD_LOG_ERR(( "tower tile has unexpected input link %lu %s", i, link->name )); - - ctx->in[ i ].mcache_only = !link->mtu; - if( FD_LIKELY( !ctx->in[ i ].mcache_only ) ) { - ctx->in[ i ].mem = link_wksp->wksp; - ctx->in[ i ].mtu = link->mtu; - ctx->in[ i ].chunk0 = fd_dcache_compact_chunk0( ctx->in[ i ].mem, link->dcache ); - ctx->in[ i ].wmark = fd_dcache_compact_wmark ( ctx->in[ i ].mem, link->dcache, link->mtu ); - } - } - - ctx->out_mem = topo->workspaces[ topo->objs[ topo->links[ tile->out_link_id[ 0 ] ].dcache_obj_id ].wksp_id ].wksp; - ctx->out_chunk0 = fd_dcache_compact_chunk0( ctx->out_mem, topo->links[ tile->out_link_id[ 0 ] ].dcache ); - ctx->out_wmark = fd_dcache_compact_wmark ( ctx->out_mem, topo->links[ tile->out_link_id[ 0 ] ].dcache, topo->links[ tile->out_link_id[ 0 ] ].mtu ); - ctx->out_chunk = ctx->out_chunk0; - ctx->out_seq = 0UL; - ctx->replay_in_seq = 0UL; - - ulong sign_in_idx = fd_topo_find_tile_in_link ( topo, tile, "sign_tower", tile->kind_id ); - ulong sign_out_idx = fd_topo_find_tile_out_link( topo, tile, "tower_sign", tile->kind_id ); - if( FD_LIKELY( sign_in_idx!=ULONG_MAX && sign_out_idx!=ULONG_MAX ) ) { - fd_topo_link_t const * sign_in = &topo->links[ tile->in_link_id [ sign_in_idx ] ]; - fd_topo_link_t const * sign_out = &topo->links[ tile->out_link_id[ sign_out_idx ] ]; - fd_sleep_t * sleep = topo->sleep_obj_id!=ULONG_MAX ? fd_sleep_join( fd_topo_obj_laddr( topo, topo->sleep_obj_id ) ) : NULL; - FD_TEST( fd_keyguard_client_join( fd_keyguard_client_new( ctx->keyguard_client, sign_out->mcache, sign_out->dcache, sign_in->mcache, sign_in->dcache, - sign_out->mtu, sign_in->mtu, sleep, sign_out->id, fd_topo_find_link_consumer( topo, sign_out ) ) ) ); - } else { - ctx->tower_dir_fd = -1; /* no signer, the tower file is not written */ - } - - FD_BASE58_ENCODE_32_BYTES( ctx->vote_account->uc, vote_account_b58 ); - FD_BASE58_ENCODE_32_BYTES( ctx->identity_key->uc, identity_key_b58 ); - FD_LOG_INFO(( "my vote account: %s", vote_account_b58 )); - FD_LOG_INFO(( "my identity key: %s", identity_key_b58 )); -} - -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_SCRATCH_ALLOC_INIT( l, scratch ); - fd_tower_tile_t * ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_tower_tile_t), sizeof(fd_tower_tile_t) ); - - populate_sock_filter_policy_fd_tower_tile( out_cnt, out, (uint)fd_log_private_logfile_fd(), (uint)ctx->tower_dir_fd, (uint)ctx->tower_fd[ 0 ], (uint)ctx->tower_fd[ 1 ], FD_ACCDB_FD_RW, FD_VOTE_STAKES_FD ); - return sock_filter_policy_fd_tower_tile_instr_cnt; -} - -static ulong -populate_allowed_fds( fd_topo_t const * topo, - fd_topo_tile_t const * tile, - ulong out_fds_cnt, - int * out_fds ) { - void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id ); - FD_SCRATCH_ALLOC_INIT( l, scratch ); - fd_tower_tile_t * ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_tower_tile_t), sizeof(fd_tower_tile_t) ); - - if( FD_UNLIKELY( out_fds_cnt<7UL ) ) FD_LOG_ERR(( "out_fds_cnt %lu", out_fds_cnt )); - - ulong out_cnt = 0UL; - out_fds[ out_cnt++ ] = 2; /* stderr */ - if( FD_LIKELY( -1!=fd_log_private_logfile_fd() ) ) - out_fds[ out_cnt++ ] = fd_log_private_logfile_fd(); /* logfile */ - if( FD_LIKELY( -1!=ctx->tower_dir_fd ) ) { - out_fds[ out_cnt++ ] = ctx->tower_dir_fd; - out_fds[ out_cnt++ ] = ctx->tower_fd[ 0 ]; - out_fds[ out_cnt++ ] = ctx->tower_fd[ 1 ]; - } - out_fds[ out_cnt++ ] = FD_ACCDB_FD_RW; /* accounts database */ - out_fds[ out_cnt++ ] = FD_VOTE_STAKES_FD; /* vote stakes disk spill */ - - return out_cnt; -} - -#define STEM_BURST (2UL) /* MAX( slot_confirmed, slot_rooted AND (slot_done OR slot_ignored) ) */ -#define STEM_LAZY (128L*3000L) /* see explanation in fd_pack */ - -#define STEM_CALLBACK_CONTEXT_TYPE fd_tower_tile_t -#define STEM_CALLBACK_CONTEXT_ALIGN alignof(fd_tower_tile_t) -#define STEM_CALLBACK_DURING_HOUSEKEEPING during_housekeeping -#define STEM_CALLBACK_METRICS_WRITE metrics_write -#define STEM_CALLBACK_AFTER_CREDIT after_credit -#define STEM_CALLBACK_BEFORE_FRAG before_frag -#define STEM_CALLBACK_RETURNABLE_FRAG returnable_frag - -#include "../../disco/stem/fd_stem.c" - -static ulong -max_event_sz( fd_topo_tile_t const * tile FD_PARAM_UNUSED ) { - return sizeof(fd_event_slot_confirmed_t) > sizeof(fd_event_block_equivocated_t) ? - sizeof(fd_event_slot_confirmed_t) : sizeof(fd_event_block_equivocated_t); -} - -fd_topo_run_tile_t fd_tile_tower = { - .name = "tower", - .allow_renameat = 1, /* tower_file_write */ - .max_event_sz = max_event_sz, - .populate_allowed_seccomp = populate_allowed_seccomp, - .populate_allowed_fds = populate_allowed_fds, - .scratch_align = scratch_align, - .scratch_footprint = scratch_footprint, - .unprivileged_init = unprivileged_init, - .privileged_init = privileged_init, - .run = stem_run, -}; diff --git a/src/discof/tower/fd_tower_tile.h b/src/discof/tower/fd_tower_tile.h deleted file mode 100644 index c80b9834dce..00000000000 --- a/src/discof/tower/fd_tower_tile.h +++ /dev/null @@ -1,208 +0,0 @@ -#ifndef HEADER_fd_src_discof_tower_fd_tower_tile_h -#define HEADER_fd_src_discof_tower_fd_tower_tile_h - -#include "fd_tower_slot_rooted.h" -#include "../../choreo/eqvoc/fd_eqvoc.h" -#include "../../choreo/tower/fd_tower.h" -#include "../../choreo/tower/fd_tower_serdes.h" -#include "../../disco/topo/fd_topo.h" - -#define FD_TOWER_SIG_SLOT_CONFIRMED (0) -#define FD_TOWER_SIG_SLOT_DONE (1) -#define FD_TOWER_SIG_SLOT_DUPLICATE (2) -#define FD_TOWER_SIG_SLOT_IGNORED (3) -// #define FD_TOWER_SIG_SLOT_ROOTED (4) /* defined in fd_tower_slot_rooted.h */ - -/* fd_tower_slot_confirmed describes a Tower frag that notifies protocol - confirmations. There are multiple confirmation levels: - - - propagation confirmed: a block is propagated if it has received - votes from more than 1/3 of stake in the cluster. This threshold - is important in two contexts: - - 1. When becoming leader, we need to check that our previous leader - block _as of_ the parent slot we're building on, has propagated. - If it has not propagated, we need to instead retransmit our last - block that failed to propagate. The protocol currently allows - for a grace period of one leader rotation for leader blocks to - propagate. - - 2. When voting, we need to check our previous leader block _as of_ - the slot we're voting for has propagated (unless we're voting - for one of our own leader blocks). We cannot vote for a slot in - which our last ancestor leader block failed to propagate. - - - duplicate confirmed: a block is duplicate confirmed if it has - received votes from more than 52% of stake in the cluster. The - "duplicate" adjective is a bit of a misnomer, and a more accurate - technical term is equivocation: two (or more) different blocks for - the same slot. This threshold is important for consensus safety, - because it ensures Solana eventually converges to the same block - per slot. Specifically fork choice allows choosing a fork if it is - duplicate confirmed, even if there is equivocation. - - - optimistically confirmed: a block is optimistically confirmed if it - has received votes from more than 2/3 of stake in the cluster. - This threshold is important for end-users, who rely on the - "confirmed" commitment status of blocks (queryable via RPC) to - determine that their transaction has landed on a block that will - not rollback. This is unimplemented in Firedancer and only relevant - for RPC. (TODO verify this?) - - - super confirmed: same as optimistic, but the stake threshold is 4/5 - of stake. This is used during boot for `--wait-for-supermajority`. - - It's possible Firedancer reaches a confirmation level before the - block has actually been replayed. Firedancer listens to votes from - both Gossip and TPU, so if a given block id has received enough votes - it might get "forward-confirmed". - - Tower will also notify of forward confirmations, denoted by the `fwd` - field on the fd_tower_slot_confirmed frag. Forward confirmations are - only for the given block, and do not imply the ancestry chain leading - up to the block are also confirmed. This is distinct from replay - confirmations, which are only emitted after replaying a block (`fwd` - = 0), and imply the ancestry chain up from that block is also - confirmed. Forward confirmations are needed for both repair (in case - we never got the block over Turbine) and for RPC (since RPC needs to - know about confirmations even if replay is behind). - - Other guarantees include that the confirmation frags with `fwd` = 0 - are delivered in-order with no gaps from tower (there still might be - skipped slots, but no gaps means you will always receive an ancestor - block's confirmation before its descendants). That is, if a consumer - receives a confirmation frag for slot N, it will have prior received - confirmations for all ancestor slots N - 1, N - 2, ... (if they are - not skipped / on a different fork). */ - -#define FD_TOWER_SLOT_CONFIRMED_PROPAGATED (0) -#define FD_TOWER_SLOT_CONFIRMED_DUPLICATE (1) -#define FD_TOWER_SLOT_CONFIRMED_OPTIMISTIC (2) -#define FD_TOWER_SLOT_CONFIRMED_SUPER (3) -#define FD_TOWER_SLOT_CONFIRMED_LEVEL_CNT (4) -#define FD_TOWER_SLOT_CONFIRMED_RATIO_CNT FD_TOWER_SLOT_CONFIRMED_LEVEL_CNT -#define FD_TOWER_SLOT_CONFIRMED_LEVELS { FD_TOWER_SLOT_CONFIRMED_PROPAGATED, FD_TOWER_SLOT_CONFIRMED_DUPLICATE, FD_TOWER_SLOT_CONFIRMED_OPTIMISTIC, FD_TOWER_SLOT_CONFIRMED_SUPER } -#define FD_TOWER_SLOT_CONFIRMED_RATIOS { 1.0/3, 0.52, 2.0/3, 4.0/5 } - -struct fd_tower_slot_confirmed { - int level; /* the confirmation level, see FD_TOWER_SLOT_CONFIRMED_{...} above */ - int fwd; /* whether this is a "forward confirmation" ie. we have not yet replayed but the slot is confirmed based on gossip and TPU votes */ - ulong slot; /* slot being confirmed (in general, a slot being confirmed more than once is possible but highly unlikely ) */ - fd_hash_t block_id; /* block id being confirmed (guaranteed unique) */ -}; -typedef struct fd_tower_slot_confirmed fd_tower_slot_confirmed_t; - -/* In response to finishing replay of a slot, the tower tile will - produce both a block to vote for and block to reset to, and - potentially advance the root. */ - -struct fd_tower_slot_done { - - /* This tower_slot_done message is 1-to-1 with the completion of a - replayed slot. When that slot was done, the bank_idx was sent to - tower, which tower used to query the bank and populate the vote - accounts. Tower needs to send back the bank_idx to replay so it - can decrement the reference count on the bank. */ - - ulong replay_slot; - ulong replay_bank_idx; - ulong replay_bank_seq; - - /* The slot being voted on. There is not always a vote slot (locked - out, failed switch threshold, etc.) and will be set to ULONG_MAX - when there is no slot to vote on. When set, the vote slot is used - by the vote sending tile to do some internal book-keeping related - to leader targeting. */ - - ulong vote_slot; - - /* The slot to reset leader pipeline to. Unlike vote slot, the reset - slot is always set and represents the consensus fork to build on. - It may be unchanged since the last slot done. reset_block_id is - a unique identifier in case there are multiple blocks for the reset - slot due to equivocation. */ - - ulong reset_slot; - fd_hash_t reset_block_id; - ulong reset_bank_seq; - - /* Sometimes, finishing replay of a slot may cause a new slot to be - rooted. If this happens, new root will be 1 and both root_slot and - root_block_id will be set to the new root values accordingly. - Otherwise, new_root will be 0 and root_slot and root_block_id will - be undefined. Note it is possible tower emits a new root slot but - the new root slot's block_id is unavailable (eg. it is an old tower - vote that precedes the snapshot slot). In this case new_root will - _not_ be set to 1. */ - - ulong root_slot; - fd_hash_t root_block_id; - - /* The number of leaves in the forks tree. */ - - ulong active_fork_cnt; - - /* is_voting reflects whether this validator is structurally a voter - (holds the authorized voter key and its identity matches the vote - account's node identity). Unlike has_vote_txn, it is independent - of whether a vote txn was produced this slot, so transient - non-votes (lockout, failed switch threshold, empty tower, etc.) do - not clear it. A hot spare running under a dummy identity has - is_voting=0. */ - - int is_voting; - - /* authority_idx is the index of the authorized voter that needs to - sign the vote transaction, or ULONG_MAX if it is the identity. */ - - int has_vote_txn; - ulong authority_idx; - ulong vote_txn_sz; - uchar vote_txn[ FD_TPU_MTU ]; - long vote_created_nanos; - - /* The latest balance in lamports and commission in basis points of our - vote account. These are ULONG_MAX and USHORT_MAX respectively if - our account is not found. */ - - ulong vote_acct_bal; - ushort vote_acct_com; -}; -typedef struct fd_tower_slot_done fd_tower_slot_done_t; - -struct fd_tower_slot_duplicate { - fd_gossip_duplicate_shred_t chunks[ FD_EQVOC_CHUNK_CNT ]; -}; -typedef struct fd_tower_slot_duplicate fd_tower_slot_duplicate_t; - -struct fd_tower_slot_ignored { - ulong slot; - ulong bank_idx; -}; -typedef struct fd_tower_slot_ignored fd_tower_slot_ignored_t; - -union fd_tower_msg { - fd_tower_slot_confirmed_t slot_confirmed; - fd_tower_slot_done_t slot_done; - fd_tower_slot_duplicate_t slot_duplicate; - fd_tower_slot_ignored_t slot_ignored; - fd_tower_slot_rooted_t slot_rooted; -}; -typedef union fd_tower_msg fd_tower_msg_t; - -extern fd_topo_run_tile_t fd_tile_tower; - -/* The danger of the circular reliable link between tower and replay is - that if tower backpressures replay, and happens to have backed-up - confirmations to publish in after_credit, then tower_out link will - become full. If tower doesn't drain from replay_exec in the next - returnable_frag call, this will cause a credit starvation loop - between tower and replay, which causes both tiles to stall - completely. - - Since there's no way to guarantee tower read from a specific link, - (and no way to guarantee replay will read from a specific link), so - we just make sure tower_out is large enough that the likelihood that - the link is close to full and the above scenario happens is low. */ - -#endif /* HEADER_fd_src_discof_tower_fd_tower_tile_h */ diff --git a/src/discof/tower/fd_tower_tile.seccomppolicy b/src/discof/tower/fd_tower_tile.seccomppolicy deleted file mode 100644 index 697573800f4..00000000000 --- a/src/discof/tower/fd_tower_tile.seccomppolicy +++ /dev/null @@ -1,104 +0,0 @@ -# logfile_fd: It can be disabled by configuration, but typically tiles -# will open a log file on boot and write all messages there. -# -# dir_fd: The directory holding the tower file. -# -# tower_fd_0, tower_fd_1: The staging and live tower files. They swap -# roles after every write. -# -# accounts_fd: The file descriptor for the accounts database. Execution -# tiles may need to pull accounts into the cache from disk, -# or purge out of cache to the disk to make forward -# progress. -# -# vote_stakes_fd: Disk backing for t-1 vote stakes sets beyond the -# in-memory cache. Any tile reading a set that is not -# cached loads it, writing back the evicted set. -uint logfile_fd, uint dir_fd, uint tower_fd_0, uint tower_fd_1, uint accounts_fd, uint vote_stakes_fd - -# logging: all log messages are written to a file and/or pipe -# -# 'WARNING' and above are written to the STDERR pipe, while all messages -# are always written to the log file. -# -# arg 0 is the file descriptor to write to. The boot process ensures -# that descriptor 2 is always STDERR. -write: (or (eq (arg 0) 2) - (eq (arg 0) logfile_fd)) - -# logging: 'WARNING' and above fsync the logfile to disk immediately -# -# arg 0 is the file descriptor to fsync. -fsync: (eq (arg 0) logfile_fd) - -# tower: atomically replace the live tower file with the staging file -# -# The staging and live names are exchanged after every write, so the -# live file is always complete. After an identity switch, the files -# are renamed to the new identity, and a file that already has that -# name is moved to an .old name. A rename that replaces a file is not -# allowed. -# -# arg 0 and arg 2 are the directory file descriptors of the two names. -# arg 4 is the flags, RENAME_EXCHANGE (2) for a write, or -# RENAME_NOREPLACE (1) for a rename. -renameat2: (and (eq (arg 0) dir_fd) - (eq (arg 2) dir_fd) - (or (eq (arg 4) 1) - (eq (arg 4) 2))) - -# accounts database: write to the accounts database. Users of the -# accounts database may need to write data to persistent storage if the -# cache is full (to complete an eviction). -# -# tower: the tower file is also written out with pwritev2. -# -# arg 0 is the file descriptor to write to. -pwritev2: (or (eq (arg 0) accounts_fd) - (eq (arg 0) tower_fd_0) - (eq (arg 0) tower_fd_1)) - -# tower: the staging file might be longer than the new tower so the -# staging file is truncated to the new tower's size. -# -# arg 0 is the file descriptor to truncate. -ftruncate: (or (eq (arg 0) tower_fd_0) - (eq (arg 0) tower_fd_1)) - -# accounts database: read from the accounts database -# -# Users of the accounts database may need to read data into the cache. -# -# arg 0 is the file descriptor to read from -preadv2: (eq (arg 0) accounts_fd) - -# accounts database: allocate more space from the disk -# -# The accounts database allocates more space from the underlying storage -# device in chunks, as it runs out. -fallocate: (and (eq (arg 0) accounts_fd) - (eq (arg 1) 0)) - -# vote stakes: load a spilled t-1 set into the cache and write back the -# evicted set. -pread64: (eq (arg 0) vote_stakes_fd) -pwrite64: (eq (arg 0) vote_stakes_fd) - -# power saving: sleep when there is no work to do -# -# In the efficient scheduler mode, an idle tile parks on its sleep word -# with futex_wait until a producer has work for it or its next deadline -# passes. -# -# arg 1 is the futex op, always FUTEX_WAIT_BITSET so the timeout is an -# absolute deadline. arg 2 is the expected value of the word, always 0 -# (parked). -futex: (and (eq (arg 1) FUTEX_WAIT_BITSET) - (eq (arg 2) 0)) - -# power saving: resume an interrupted sleep -# -# A signal delivered to a parked tile interrupts its futex_wait, and the -# kernel resumes the wait with the remaining timeout via restart_syscall -# when the tile next runs. -restart_syscall diff --git a/src/discof/tower/fd_tower_tile_private.h b/src/discof/tower/fd_tower_tile_private.h deleted file mode 100644 index 7a6a35ef469..00000000000 --- a/src/discof/tower/fd_tower_tile_private.h +++ /dev/null @@ -1,246 +0,0 @@ -#ifndef HEADER_fd_src_discof_tower_fd_tower_tile_private_h -#define HEADER_fd_src_discof_tower_fd_tower_tile_private_h - -/* Internal types of the tower tile. Included by the tile and by - firedancer-dev's tower command, which reads the tile's ctx out of - shared memory. */ - -#include "fd_tower_tile.h" -#include "../../choreo/eqvoc/fd_eqvoc.h" -#include "../../choreo/ghost/fd_ghost.h" -#include "../../choreo/hfork/fd_hfork.h" -#include "../../choreo/votes/fd_votes.h" -#include "../../choreo/tower/fd_tower.h" -#include "../../choreo/tower/fd_tower_file.h" -#include "../../choreo/tower/fd_tower_serdes.h" -#include "../../choreo/tower/fd_tower_stakes.h" -#include "../../disco/keyguard/fd_keyguard_client.h" -#include "../../disco/keyguard/fd_keyswitch.h" -#include "../../disco/metrics/fd_metrics.h" -#include "../../disco/fd_txn_m.h" -#include "../../discof/replay/fd_replay_tile.h" -#include "../../flamenco/leaders/fd_multi_epoch_leaders.h" -#include "../../flamenco/runtime/fd_bank.h" -#include "../../flamenco/runtime/program/vote/fd_vote_state_versioned.h" -#include "../../flamenco/stakes/fd_vote_stakes.h" -#include "../../util/fd_hash32.h" - -#define AUTH_VTR_LG_MAX (5) /* The Solana Vote Interface supports up to 32 authorized voters. */ -FD_STATIC_ASSERT( 1<uc, (seed) )) -#define MAP_NEXT next -#include "../../util/tmpl/fd_map_chain.c" - -struct in_ctx { - int mcache_only; - fd_wksp_t * mem; - ulong chunk0; - ulong wmark; - ulong mtu; -}; -typedef struct in_ctx in_ctx_t; - -struct fd_tower_tile { - ulong seed; /* map seed */ - fd_pubkey_t identity_key[1]; - fd_pubkey_t vote_account[1]; - ulong auth_vtr_path_cnt; /* number of authorized voter paths passed to tile */ - uchar our_vote_acct[FD_VOTE_STATE_DATA_MAX]; /* buffer for reading back our own vote acct data */ - ulong our_vote_acct_sz; - - /* owned joins */ - - fd_wksp_t * wksp; /* workspace */ - fd_keyswitch_t * identity_keyswitch; - auth_vtr_t * auth_vtr; - fd_keyswitch_t * auth_vtr_keyswitch; /* authorized voter keyswitch */ - fd_keyguard_client_t keyguard_client[1]; - - /* The tower file. Each write goes to the staging file, then the two - names are exchanged, so the live file is always a complete file. */ - - int tower_dir_fd; - int tower_fd [ 2 ]; /* [0] staging (.new), [1] live (), -1 if not written */ - char tower_name[ 2 ][ PATH_MAX ]; - ulong tower_file_pending; /* newest vote slot not yet written, 0 if none (slot 0 is never voted on) */ - - fd_eqvoc_t * eqvoc; - fd_ghost_t * ghost; - fd_hfork_t * hfork; - fd_votes_t * votes; - fd_tower_t * tower; - - fd_vote_instruction_t scratch_ix; - fd_tower_vote_t * scratch_tower; /* spare deque used during vote txn processing */ - - publish_t * publishes; /* deque of slot_confirmed msgs queued for publishing */ - fd_multi_epoch_leaders_t * mleaders; /* multi-epoch leaders */ - - /* borrowed joins */ - - fd_banks_t * banks; - fd_accdb_t * accdb; - - /* static structures */ - - fd_pubkey_t id_keys [VTR_MAX]; /* identity keys */ - fd_pubkey_t vote_accs[VTR_MAX]; /* vote account addresses */ - ulong vtr_cnt; /* actual cnt of elements in above arrays */ - fd_gossip_duplicate_shred_t duplicate_chunks[FD_EQVOC_CHUNK_CNT]; - fd_compact_tower_sync_serde_t compact_tower_sync_serde; /* our last vote, for the tower file */ - uchar vote_txn[FD_TPU_PARSED_MTU]; - fd_tower_file_t vote_history; - int vote_history_pending; /* vote history not yet adopted or dropped */ - ulong vote_history_last; /* last vote of the vote history from the last switch, 0 if none or behind our root */ - - uchar __attribute__((aligned(FD_MULTI_EPOCH_LEADERS_ALIGN))) mleaders_mem[ FD_MULTI_EPOCH_LEADERS_FOOTPRINT ]; - uchar __attribute__((aligned(FD_VOTE_STAKES_ITER_ALIGN))) iter_mem[ FD_VOTE_STAKES_ITER_FOOTPRINT ]; - - ulong root_epoch; - ulong root_epoch_total_stake; - ulong next_epoch_total_stake; - epoch_vtr_t * root_epoch_vtr_pool; - epoch_vtr_map_t * root_epoch_vtr_map; - epoch_vtr_t * next_epoch_vtr_pool; - epoch_vtr_map_t * next_epoch_vtr_map; - - /* metadata */ - - int halt_signing; - int hard_fork_fatal; - ulong wfs_slot; - int wfs_hash_is_zero; - ushort wfs_shred_version; - ushort shred_version; - int init; /* 1 after ghost_init has been called */ - - /* in/out link setup */ - - int in_kind[ 64UL ]; - in_ctx_t in [ 64UL ]; - - fd_wksp_t * out_mem; - ulong out_chunk0; - ulong out_wmark; - ulong out_chunk; - ulong out_seq; - ulong replay_in_seq; - - /* metrics */ - - struct { - ulong not_ready; - - ulong ignored_cnt; - ulong ignored_slot; - ulong eqvoc_cnt; - ulong eqvoc_slot; - - ulong replay_slot; - ulong last_vote_slot; - ulong reset_slot; - ulong root_slot; - ulong init_slot; - - ulong fork[ FD_METRICS_ENUM_TOWER_FORK_RESULT_CNT ]; - ulong gate[ FD_METRICS_ENUM_TOWER_VOTE_GATE_RESULT_CNT ]; - - ulong tower_file_write; - ulong tower_file_slot; - ulong tower_file_sz; - - ulong votes [ FD_METRICS_ENUM_VOTE_TXN_RESULT_CNT ]; - ulong vote_slots[ FD_METRICS_ENUM_VOTE_SLOT_RESULT_CNT ]; - ulong gate_int [ FD_METRICS_ENUM_VOTE_INTERMEDIATE_SLOT_RESULT_CNT ]; - - ulong eqvoc[ FD_METRICS_ENUM_EQVOC_PROOF_RESULT_CNT ]; - - ulong ghost[ FD_METRICS_ENUM_GHOST_VOTE_RESULT_CNT ]; - - ulong hfork [ FD_METRICS_ENUM_HARD_FORK_VOTE_RESULT_CNT ]; - ulong hfork_bank[ FD_METRICS_ENUM_HARD_FORK_BANK_HASH_RESULT_CNT ]; - - ulong hfork_matched_slot; - ulong hfork_mismatched_slot; - } metrics; -}; -typedef struct fd_tower_tile fd_tower_tile_t; - -#endif /* HEADER_fd_src_discof_tower_fd_tower_tile_private_h */ diff --git a/src/discof/tower/fixtures/voters-398915634.bin b/src/discof/tower/fixtures/voters-398915634.bin deleted file mode 100644 index 90496599d6f982ba5f36a4d9053dc03205698fcb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 384200 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 W1I{zxJOj=%;5-A)Gw{E02L2BSy-NrH diff --git a/src/discof/tower/fixtures/voters-398915635.bin b/src/discof/tower/fixtures/voters-398915635.bin deleted file mode 100644 index c8eb8c48905ba50d394c342aa256d8fddc25227b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 384200 zcmeFab##=;qIaFf-GaNj2KPX40)Yf~x8UyX?(P=c-F1C`*Byl z|IE5G@7r1HoJ{TgP#w}CRlA-hEG<(x$-1z$Qs-;Fqh;14$(~la;oIZF@TR2;C0w~P zZQk*9*VUS~`{|i}9e1wT-|yq`I;Hm=X`Q!Rx683A9-9=l`sQNMk5^3QpSy>L$A)Mg zdJ7%$|4UuWxjRO*syRAeJp6QxBWi}>!F6q_=~}W{|-SBAG!(JBVlond~5f8DzGD@Me(34#Js1Ryzo5 z2HET&j2UFN1M}73$YBSe%)rYI9A@D8A0}k%5oE3pd2{syFZ(~d&U~WH+hlwF@p9(r z`meVsmUju;Nf1BP^0s-;iQ}hPo~2P{^YMG&r(1ra^$>CV49g?GyeW>KX}NEseCCtl zfuCjhm?@LR@v|*|*Ws-=evak)3k2xD@N+G16l$yf3qQ~D#zzyHr^y39-}2HveZ}z$ zEKl3$jyQgy<-gS_VV-*r{36SDw+<4=FSfjLv54ji#{<8_@|=Nz;`pWTUZ=(Je0|Jp zpWS>ldgQd8uZP>$ONit7VmmS8R7-Qc`_26saDN8ep8@x0!2KC;e+Jy20rzLX{Tc8$ z_3A^!IRjJf8+dH${mL)G`K-9sF7Jn|gpy4@?^d$0Q8$LDUG>hRzbhKE_6>%SGSt2teHSC8rH*SibY+4}##0uJ>rDPUJm zFva8QI&b?2yrnIwjV-5FG^l&4yn5ft@@MrvYm5)HJobSae&%|7Ys;6_+9r-~WBKXR z@hX|?@og>7oVANMzMbV$_8$_*x3~QK>I9X|JopZlXWiag9N*D$uOGL>@trJxxu}G{ znFrt5@-QuyiQ~Ii-aII3fVm#u)$%#VI*Q}FSzfZ)WpR9W%kxDnP{quH?_v4J7PG|h zJuSZy{<}E7m*w?8)U9gf!S}X&+x>mw_&%1$yO*+>xgOuwa_>Te#qs?tFBHrL}vS-!%vyEuNd<(+-5 ziQ~suey4HjI%XdHSj#t^8X}G#XSx63$Kv?$mN)h-U)RiopJ4g%@@vHL6D_~-K1Mxr zJ${nqnNqhG$8!?a({WZD&q>(9v)SsKdGMTs-Hkd<9M4JE_!57J<2ec2T&;KmGY_7V zu=e+6iQ_p5`_SmUIG&TR9?L5?H1ps&2^%?Xoj9J8u()+%G&0xYISFf#va>jzldxH@ zE{o$i3Cq{JU}G~6o|CY;OXiB>ISG5X%F)DJkLM(8_~9nvcuvB`c>N}h=OnCalMGGG zJa|sRUUeHSj^`w-bJ7>$c-DoKBSSSa^WcAZ5*DzdTPbtVoR*38zKZ>#iP7Ms#`vn1 z_ivuGw7DK%&GHq?I*Q|~TmB{ZlsLYI<;`DbD`V!t*R=fm&xzvrT3lcFgE+pn<<+`Y zFKgy$Zhalg8y(y#j<0KZg*Nfand|ZOEYBaSr#Qa8<BNc_GO$_s8`U3mFAu+4n6C z%5&$^sV?|H00r+}ZNijmP=kEeh~c4{Mzr+|-z+bxc#fLFhbnaRw9r+`Oz zbP>l>z;{Xoi{mNa9M217HuKB9FOH{x$Gr=i#axf4fI}w=6vtD*K0D5c<0;^R z^}Mp0dGHkQ?7fr3@f7fsdhf;Y6!5!3)v}p+@D%Xx8Mlk$Dd0I76J&<@TouPtz(=MP%32vaFUP5#qkty)>o-= zntAXPaE6qF#PJmH+iwrV@f2{=H)V2}dGHjl|It75j+eDd6R$Z;Rt8;OA$G=QZ=-Dd2+VmWbmi;6A-0Mz-31c6~|M+84unT$5X&jPv$RR=D}0IF&a-2 z$5X)Po<9@EQ^2o^mMv)J!BfDkaxN3cQ^0j1Mkr*i$5X&pF9wR^Dd6>M&WPhF;BR?z z7B=(XDd3i^r;6h#;IBPDi{mNa4|D4jG4tRl;3((zh~p{XxiM1|HP_=Q;178Qi{mNa zF!LUW<5?HdME)X<|78K6d(k1ed9csF$JKiZcu?~r8sjP8pd#^8nCtNraF6P(#qkvI zpw_>M<0;@6gEFMlJl0ddrzegU$5X%$kG&MfQ^0?`uawHngQtM~@~#)hQ@}6t#7u3j z$5X(*7dwjMDd0(a&xzwH;Pth<(wKSh6!7Ax6U6Zp@a}%E#PJmHo0ooR%{+JtI9JQH z;&=);-?8ZF%=LH*IM=au;&=-9!=y9L>#e7NS^V+t&4F|C;NBcKHxKU3fphclcW(~< zDFqyE(9j))-!?uqBu2A{qocn3b|Xxb#mBeqIQ1odyhyh`G;4MD(XEXa`t0Ak|MHXT z4bpjy+0fuh{kO}T+!=6w_v{f93NGLFIp&quo&Q?EuI4;AZtJi??+0{z?;ZNZjm*CO zzlXb1D`uO*Rn8A?b;j#(#L%uD)7AX$d%*wld%&)qV0JpLuJbl4X2=_hWriAaikorN zNrm;E#q!!8?={A=ShfsP&Bt7iXR#a_eycd1k`DVLUI}wOo|5*f)lD2vNjE%mNgPi} z*DRi=q?rd#Nf)0pNgPi}r>VYN98XC<9rjTiPf0fzR!g@z@RW3$!@I@tl=OkdDNCD= z2hU=8B-v1LJSE+z)l+diB^@-Ql5S_o5k^zbodwXbUOo2Nq6<>A&#e{U){MX zj;EwMoGYl?8F)(i)Bd^QcuIQTL67q0_lu{byCiNTj%Tq9{;^jaPf35fl0vsL@RW3- z6hp-El=Oqk&&2VRbftn-DwvN4Pf4$BI7S>lG_<)YUQuXVsv%zgV98WQ4uMkbIb-+{1#cDJc$5YIq!}Jx$Q^2>T4Hw5#!2Ycti{mNaIQe|_ znh87wyeIrRaXbb5eUfK2^L*hc;5!?-isLEZ5$msr<0;^*M+;Rq^WZ7qFD+(><0;_h zv%ZSsm)g(&l?C-`n0fFN@cGgE#PO^PH=bP($N#c`9X(3w7$MZompXn3un%_Qz(v3G6; z{5brto2dR_w~0WOw5g_0lgNc;;(%-(1h`oNo7GRI|{L-y|w>ulAiC z>AJUhyY;vG+rIc;_1nI_WTR21Cm)Di8}T#232U+(TVvU{! zuElOY6US4oi3a%PH6K5oa@}xioj9IyUDP+0?#baP*Ys^WiQ_5P$>%SM<5|q^U(Bm} za(K$McE%avc*-?fq>tiw%5`?Qn%WfODOa!RyTtL7Yx$_DwK>L9uIXBj630`nq2E6e z#}BcawpGu(ottCpDcU=?=85Ae+Uz4kX>*LHXg#|$700v4ttx#?98b|+S&&hiV?0G$ zCeCPaJVonY?4>xKqV+qPN1J9mMcbs`RB=2->xlkQ98b~Kj9kOpeEr}l+M^M6isLC- zpD9VSImT18Uvl;q$5XV`A6*m2Q?vow^64ZDPtg{AJWU)=(UytvMjTJkc1>O0$9(*F zigy3D#p3vx_IV#t?1wmhmgQ&0*U||Yo&s(7Z3A8|YdochUa zaXbYazi$bhgyAXR;1xmQcnUbnhDbW`!c)KrF0>KHQ^1j`oE67Yz!|!D=_CwK0UsMX zMI287zZv#X9M8J2>F&7wS{zU5R=6FI$viHe(k*ali#VRrz5XtNUa*6wbaO`OC652Ibn{#j$5Xmj zek-UK>;zg*=_ac%M;uS-4oLZ398c*s-BU*|*uhh}g%@oW$5XnaKgZMycJP$$yV@PZ z@s#e(qi4nOly1`aIkKD28=lgQ?K@5!Pw8Gb{X!hy!v3LR@yEM22hPoddvoC2Jh(Rp z&dtN$y*c=&6maN?@29k$<^4L+{v{<6#~fXzXqr-oBUG+@amU_1e=T5Fb4Dv%;9KSQZ;Pk< zqr~(($t%{{ALGrurEg0*nkFm~SUmd4Vy+(3)%@-P{+Ac9t0$P9j;rgu&59ZF#v*rS zQKxujynanAy{CY?d0x;MPXSl0pEJI>9!~-1{xDG-PXR{?d?SvhfFmFEOJL@~Q^2>Y z&lAT}z$=Qs6US4)%PLk+Xy(CFz+uPl5XV!%A95#4WUj}v$Zh^IP#jMI$13_*98Uoc z>{%hPnFmh+N6xxI98Up%x*R)+xgJjeC(YMY98Up9ym(n0PXPxW$d}a2gQtK~t(hf` zr+{az`7Vy9fX{`gpUljIXOZ(gyGtBT0Z;WzmfT#Ar+^)e2a4k<;OPmTh~p{X=-n%) zF!SIk;L1-|i{mNa?DxW@G}q%P;0IruisLEZEfQ^2dQ{}9Jhz&naINMq)~Q@|JV92Cb>z=L+CNo%gh zQ@|H@3>U{!zz-WgbzW~h1w4I1xpZb8JO#X~-3oC$1zg!XQhIYeo&pZvr=>Wa0-n6- zpg5ia9yLC(A_`9dd#39uj;DZ2uRkM>r+`0&&a8;SQ@}wVMvCJp;Os}Ah~p{XP?IYv zuJ9D_*T4Q5UC6oMhB*G01$_QTKy>qDN7?u(nt4wF59qv198UqKto=nC zPXX5`STTnA^>_++b%o{PcnbJy)5tN+^>_;SOy{=ZcnbLC+TX?T6!6=}Ua`zPcnWxR z)=A=c3b^>)x8isTxY&rQo@O3A1w57-QXK_3QyuZhJ@jrcN z^Q%Mt{`DVTam;%>1-zr#L~%R?JoVTcaXbZl_=JC4GY_5uz7x1X98UqW_~YH11Lx+! zy*Y4h9^9J)=jP$>-W>c>3b;w%qmE{kj`%ey_xNJzs%7V`N*HD5u&_M?2IlBl=;+8T zTUXro^9tyB{N1T|zb85B@3*@3sS$aW%pFnUQt*i=y<_FM;j!qBzCZux&w_R}=km?3 zF6Dk(W`6Lc)4x@i*ROZe)C;DjxHC3fnPwA8)b$kK>LVm>&&kBw z_>08xY(8>b45v*eo|B2&aaxPxIhly+^}9HplZghWb8FLy=VW3~o9W_sP9{np{49>= zWa4h4+S+vDIhm*wuuB}z$wVjbWZHD%Ihn{@Y=AhPlZiSr?uz5td~Cf`RNt!coJ<@` zxKJF=$;6SCVRiC^=VYRH@;2glP9~ZUy&#U~Wa4RXcAY%oIhiPVcZ4{elZldbAB*ET znTUR=oKB?hoJ_1ax>_91$wZ}jF?I5UXY(=haYu1HClkqPUl7N0G7&T~uTG@!oJ<7l z>?@AvWMXyd+v0dmCYB}f(TNnElL^m7OT_V28!c3nV8h;xHz7Z zi8eJe>O>09$wd9MBgOHo6T6o`7RR$L+|Ljyx%obZXVJ+VX;DkV^)~&A!z^hY^A;~P ztJ$9goO@kWaXbb5eR_~Mo&t94|0<5BfLUbl?nTJCNVyjw=OX1^gq(|%dl7OjQva_l zLjRNkj#~Eip5fJUwAeSIeT>#qE^QjLv010K=@M`6H{(G3MfI8lmW-4oQ-pn~pM{w@ z)4z74st+Rd?UQ=H*U21HtL6!O*)O>6?8Lp62UJb;mwye()ts)ptH*Tp>wjIq?tcyG zU-Pdaxq5;*>vwgXx7pc*yir;|9{Omr^7-yvZF(rJ+_x8t<0-8(`NOD`@s!riV~xe} zlvZ$&gW`A=x$@JJtC;bW*61(o#PO6?^u;@s!q)M#-$rFA9tOL07<^`x=CiWyI7 zb&0z}98YN-?43eojAxO{m$0ump3*v;{jxZo((?WzkBS*jX?dNWA&#fCGM@M*j;FNB z&u^e&##36~9S6kml-7=RsZ_>zN~`nSq2hQ->sEp@;`p)lBx7v<>?&qFrM0p9Byl{Y zRs8#VaXh6J?MDq2GoI4Qy=+A3-M9Lv|2 z-6@WrYk9fY&ewwA=UE>3-1%A%{Cvv~^?Rto#xJn^@QSi3X8c0Sr|wuKj$dT?^{dfU z%=pEYSFF`h9KXc!Dl;yKZ(RGg*&- z?hDOvtOv)K#rHH8$8(HXrurdqJja;V@1)U(gXb7?zz*jhYdpu8OY1+-^>~gkZ^kQO zPLDnC9Age1ut*$FL8g5k${am=;G5V_%Gtn1;`pYPUpsX`9M3VP@9C6U4DlReZW}#N z9N)su!{Ui|FRsqT*S)wp7hm_{>Rf#P?#1<=Qjq3*(7*Yu(1@_vY6i~RGw?{gB%>3* z9P8;BP`C21wmxCH#`E>cUNlDjIGxh;t35GR@>hSX-8iUFm)$2r-}(6D$sn(?KW8sm z*RAWjl;IDh3HbzY57;acuI9|sxp%*!=7`NSlDyo$1@`LP5& zTP|p#gWZsc9DeDeYfEB)qmmt%#m!OK4$O+_@V5iA3_Aksz^vhpDt2Ht29BzBV0IOb zYIb0@Dvs)QU>?j-!w$@*%2Cq}%#O@a%MQ#o&r#bB%-+#a#}3TK(^1zB%x>0E&koEM z*-_sP%>LTZzz)o&-O=#zHOi#n1#;K+78Uq>1bmIx^G+YKL#PvgI%$!C*54@esg~Y z+@AsWXTbd#_J~He;A>jm>ib@CJTFkHeLGokb3L9HD29WFKpaom7ahA_9M7@& z&-5wFn0fFVo4*}4KpfAp`RrJC#PJ-PAMN8^*35%%YoGVJ$LEOSIX2&r|Cu~kFWAmlO`-tNSgf;tSi{q(^v89H!H1qs#z5P}xf=~+|? zrs<|@@hmDSP6dnOSybxfji=iQcovn@XM2g`SyZkKx+RWhQAxhGggJWfz_X}C+__vF z&!SSLWHfy{#IvYOIpzGF;8|3XraGtV@hmD~w&yg5As%=Zm4Qnqi{n{T-cI}|j%QH` z8dfu`nFr6J5_D^aIG#nN)86>zc*X|~VsobGf#+Uq??PR~@!X3YdEvA;o_nz+>!&vdl^%HR#ok*#SR6mw ze*C$X+!M#ovHWrmA9MQYfuC#nx>7;n_<5FBO%~Z4oqFKsTkf-^tvG&x<=3A4E{2nI*v&ZxO zxkuAH;`m?Qi{+cHc|h~#{-*5wrtSWw?EI$vf9p48_dPu4pLh5FyFUZ}o1cLdUmQ#8 z6iN2_y5EltGm9@Y8zMP$SeXp#aJu~fzl41Im#Ix={9J%&*z9kb9H;&|y zK2prCTME?uzOsAU-1E|&?i%Lhz`qu-t2v7{e^@VN@WP>`?q<4v@aK#;?bdW$)hxou zY5^eBAgtmN%>0Rvcf~ z^7j$Xh~w*7{@e2Gy2p&KZ~6KeHT7dS0B=E!tif5o)v7wb`j# z1ZN6={NmaX{<8zS4&5Iu*6B9)57k?iVpH4+L&iIfKi*gAQ-HZI#XVs2->&A&I&#aQ zRWV1->9y`_vIPC>mCyYuPPW!7lYOn3IKq;<$&a=BuUA1wo#XtWy~#pmbwsm+q-GG^ z4w9Hb3_D0{1~Kg*kr~9Y19NZB;b{j6%pkTM#5aRDcA!^5$F&2!3Ob%0n6qq0d^<1) z;f@4$U{28;3GKif%{vm=fjRGYB(?){v%rzW4$K`0M^Za5w>2Ee>>#QcB)5YoW{|=T z%v~KvN;@#OgdC~tz}!!Aq_zWdlgg3C4$Pe|M_M~Dx6K^s?7-ZM3zgmu%#A!p20Jhd zMW~E+V4hA#COa_qD;-h(V-Pa_^HtEUo^*4q`_26saDN8ep8@x0;QzhPfLX%Lo5Nf0 zJUlAcBJmqiT4h;x%2SnSMaye%S}KnBv%GhWuqsG=CCkf|t1FJLZ27n(>%{T?md~0V zR%M9~V4j{0#PO7IhX$L(%UMqupG+1`za>0nd?RTsaXe*w$z!uPo-%&aAg;<1PZ`Jm z)Lk4;88^CdRUA(ld&e!L;>1(N+Y`+c$5X}!Pk#}|Q^pfI)bTM-6`nHw*l3qHo-)p# zEU}6cPZaZT4>wVq@1osA3V*cs2U`NNMh#PJ-Pmu&f2 z9M7?N(|y%+9F1>hzdon>EfvReY(BiuS8+VY=C^$6={OqCv3b40`^E7bo8L^6R>#qJ z$~e*K5#o4`%_l{BDURpZyj1ooI*!J3Y~KFJHgP=1=9!x$(y=q1WAg=jdyC^aHveAh zrZ}Esb6=mLI*!J3Y#uKCLUBCD<~LJ@(XlhWul+oa@7z=zPZ=jEa!?%K-(KHtP#PUa z<2g2udv>@uexSX+;DA@+c#h3q-LJ0WXgtT}b#6@-$8&6c;pKC2Jjdo;_LbLhG@fJg zepA9urf?(Rhx{^Bim;j;D-gq&qH-=h(c(v`jjV#&c{QA#Fc#JXNu1+VGa< z`^^8=Tje#?I@De;p;Md!8v}~29a|=B^5NSzmWkW!LdE7m-DXYBeZN!Fo3VQ@nzsJ) z;W3FPY;RHU<;aHH)_u%(bXC+lVLz|ibfawijph#$1#F&PS93ndH$21UC-uiIomFT> z`Lv@$)m+h}{KLb)Y+43vxa9eYdfQ#IG)9<$Ls6jcowsXFEeSA zhi5VKxzJx6&tlf)$2D<0i`nwxMYYMpvzYnaTO^KWF)Okpyf$-q7PCtaTZ`ja%&LYy zCyr+^>ozBkxA{EcSIX)S-(zg#qlg=b+26% z$FrE#>*}RV8=l4Ndec$jcowtDqo0W5S;-CfcMOC{uMYp_kjNhe@~k{{5ZP_80jdbO&gwjz#T>{ z6UTE8xXhTy+O*-h2Ru4oTX8)1fJ3!7C61qLA9s1FEZVf;r&wM+?ig|WRLciceI|~d zW_jvDPq%!@l6m6z8I~7J^jRD~)AD;u{k56H&$7Jn=QZN^*_OY28daM){2a^2 z9B3nspKE#a@xP1X=UM)tMQ&~8@bfJ%>6jsoUtsx~+~36U3oV~fs=hXJ_(hic2Obc| zFSfkNAE~uz!!NP?Rr+D#_@$OVD)Yp-`LjOA@=(W%Ycq#uUFej%x;Xxq_khEfy6a{B z@HhWFRPQO>S?lxUG#pRq?wmDT98c+XICxzgPwB3@m^YW12T$ofJv~DlPw9@?`b`{9 z=@$RJer_`lp3?2}aGyAy(!CQUMILiKp3?0xXMi}qp8X^qNOeyfPwCEjT|BRu2T$oH z8M{y%-_XwEli!igT#u)8pH68Yj&E$Q-}h;+IG)lC-85N#GY_899lxQUIG)lyHvg75 zp3=SHRaA>SzJ+~U7Js~ZbKu-OxHkvR&4YV$;M_d?-J64d%01u%_x9v1mT7fB-^s;K z)jam8N6A&8*LZtXPM@vhw3P#|^zO8-z?pnEuTKdT<&NLw7h_H=JP>{F;|XK>&W^p{ z$$>j}FFbs4d)>v{J^ot2uI7xe{XvCvomO@!z4&gM6=PPU8+R%8hM~(wFKD>%ShVLU zel&OWn6Bn`7x4d3z>f5voF;O}3ggIN2a(Jmqa8#vgG_c1!3;9nL3lIBVh7>OAgdjO zHG^z+5XKC$+kshh969VDlo_xYahQSUf0&T52Rj>APcSA-acL&PXXujd?AjffM;hbtir%kz?&0I z6vtD*h10(m$5X%u+tyHF;3?oOk#>vYS>(c;NUp-bQ^2c24Hd^zz-!Vx6~|M+KbHEb zFz^)c#_}7)@f2|Xm7XdIJOz9#Zx?Yq1zaiVC2>3j+`*AwC4r}a`-Gk)j;DZgR`@24 zXOYX2wU!D4PXVvXze^lX0gvpIOeKM*fE%P5CXT0oleK>#j;Dab+|HS#O#{Bb}b9V6o@;DwRriQ_5YaRr>Oj>c2KnQk|zV*Z@tDd2rE z_KD*u;9{?n>DA773OMj!KXE(-{5A9~aXbY)KY#ve=5g^9aO89o#PJmHiH%Rh@f2{i zq9v-EdGHkQ_X+dG@f7gT0YAm@6mX9k^=p`U@D%WeqzA?E6!3``X=|G6@f7f!VI#%y z6!4iH&&BZ+aD$VTYMFWP6mXqa8^!Sy@Rt>_YMblv6mYvIoyGAKaDf=X;&|4HP>~AM zG4tSA7vgoTD2`{*IWgl@OLM*6YUTN;W630`X6ro#JT0)ijv4V3@cB4%#PJmH!Belq z@f2{%spWP2h^K&i)m<%)r+^#Ai=ksiJO!L3c_(o^1w8xfd2u`iTsvMaZ}S_(Q^3)B zOcuvezz?3k7spe;NoQ8o@gtrBp8Ru@IGzH&H_21SjCcyT-j$BxcnUZ|O0=(ox~WKneVL0LjLFYWCuyj;IkbhF@rC5kk|~q z+Cd^S_+|$Q&EUHoBrt;?b`akTe%gU~Fo(w`=Yz&I1BV^NF@sQc5Zeqw+kvMUgt3EI zW)Rj6VwyoXJBVQh;q4&08APyyXl4-64x*YtBs+*=29fPR1suf=RKQW~AfmY}njNTs zquYTBIEEdlfMeQ$3OJS>sDM4~z%29q zhX1#@(|C)0^w@Y%==R z>@ALGlaW5lb#XkKjQuAIW-#;M*<_sQJyRUdCS(59&*FGC8JF|c%xLDpv&pC%v`rk( zCS&P`_?gW0cs3csE_4^iv&rxu6fBNslhNXG-ppnmJe!P)7pIHkIf2M`^0PRe6NtCz zYGyI>;5mU960}Vm&k02Ac=5BE>+zgGRA|>l9M1{Fon(i^@ti;;h@2psnFr4a#COl$ z;&@IVzWU!1$9FPc_aSdL-uPrU@9}Ih8mtKt$8!P^<8_oA=6XCQ5I5s>6vuM{QSQ(s zaXcpw0~_VnZ7n<}5N&tQ6vuM{k)p;oaXcpw-HX-L?JYbf5M#6K6UTD`u_|Lq-NwRm z0+FuQAaOjKjPtK=i{ty-&s&e|KDo{30nZ6Uv93$S@ti=^OcO1SxgO66M7UvH#POU! z#7}%q9M1_vv9y`;ntAY?KwRH2LLAQt#IYjJ#POU!EXeMs+go@}AdZIKD2`{7vCuzu ze)G6^P9Q!V>>`fm1S0FxU~xPr5UDaI*KIF6ClFc24-m(501GKjqwz4sB4WQ@p3Ot&c*Bh*2U?cQovpOBFr5V z-0<)=k7a|}WS-l)M~CvO)=!(&{Pv?GUb{MPdGhsTlDM}k?c03ub|R*KiX{(2AC)tueuXF8oYW~NFbHXiYAzQ5w|n2s#9*ZUt? z*?G~$9ouJp4RZCEuIB$`0Xy9P9`L{B-vf5_1asE!>N;<;V+nbq!0LoiJO$<-c9l4u0$W%1I*8*bumqK_h~p`+d+WR_o5#gdVBN|) z|G@)1i`>L&A9OvQ0z2hf)!)p6r@*Sk+9r;tz+Oa3q7c1xjAy=6>K^bKJ4hM=9Bb_$i5aZ3gT!XA-VPF( z!3H}>Xa*bYAb}ZdvV-_$u-OjGgE_X?L0mJ~Y6o%5V4EGpHiPYU;AsXs>>!pI?6iZJ zX0Xc+Vwk~hJBV%ud+Z>Z8SJ%#sAjOw4x*UBemjV41_$gQk{KMdgNPx6(1+|GLdYQ0 zVLJ$KE<0ie;mqJSI|yqAN9`bt862|%v*o{Qtx(B@KKTL@9U?=4231(;H z>N;;TX~-LkV$$btRf-KtPu6=D#gS>NsQmCOibXPP7ROV}nN!78`Qa(%pEzj@D%frF3rX96!V9!$Hnm!bB&Z)RDO7hd4T_DaXiKBm*bf@o?`AB%2(xwr16~|M|pEGTBUT;0c{Bmv#6&{{qj(4TCIG$oYwef^Fo?@QX zD^q>*{Si+whbuNt98WQ~4Et6bPca`HRIP!T2Tw8QS+HFkPci47m$;$19#1i^TGLM) zPcgraaaSDAI+3byiAH7~JnMqj@b=<(7M=JxkNwv@;8Kwv`IuiEfA=W8r+_=oE~sL| zQ^2n$Ie)e=o&sLC`;M;1Q@}xc3aQxe6!6azbH(u#@R_p?6&s!cE>_t26P@rB@TqTe zbv>Q}9$Vz6IGzIbcvfG>ym$&Y-x%k=cZ;WhTlG$%<6JxiJTv}u| zy!dl$9p~aH;39#Y#qkvI^#|w0@lEX_;axhH7HvEQ%wmprFZRyOfP1lbZU)?oy>m10 zPh0H&DFqy6Q<+OOH=Os~ushSfM&o~9*nau*0duDuKRGkx->E3QyVTrWy@!O$vbFz) z92?@y{yfa{dFy&Fk|ocbw(0kYhkjPxe5vX5j>q>DI`-EBb~WdyNZ;%JoK@iQs$J15 z`TaQ_3kW=r)WBnc2*P zr)X=A8!wKhX!|C*Esm#XeM05VV&=h9v?U@=7ss>6HSzr_j;ClRJguMA%!8+B2Q5D+ zj;Cl}JWQX>T#u({kA@j5j;Cm&FL)!4r)V1&ubSP=gQsX8P2M7or)Yz-#Lr=_$5XWL zWA_lpQ?yq;UlYf($X$D0*vrg=r)U=xnJ12?XmdvSE{>;YKl;?qY39LGw9&d85XV!r z=i_F~Wv<6lv^85#5XV!r<8D0{$5XVQ_LRzP=D}06B@;Pc{fnn)+kA?U$6SwRk*oAa zpg5kQEwl5CIG&;n@XV3d%!8+BYxbEaj;ClZFL*DGr)V>7DU#33gQsZs#hovXr)b+f z4wc_rkEduo>opa}Q?#4?j*8xXX@$5XTqTPO53*W)SL z6K(s5<0;zrhi{4FDcTHoiWM{S;3-<4&x^(JLH7Iqg6rXQTNBT^FuYhIar`gu0ef|y zn$kR2>+S{ho&r9Q>WRj93b@tGf~m~)cnUb&wsGQk3b^5^C*pVtc+f%L)Mg$$1>AMh zT5&uD+~i2CH0F9d1^n(zCviLl-0=H(aXbZlcv!BqdR*%%;M-BBisLEZl8@hu<0)Y8 zaaGfqdGHkQkdm9k@f7fuwVvtC^>_-ndy-D#cnUbr)N|r^3V7>luMB1$JO%vm#YAyD z1zc;@8*zLK`$fazk9ThloSO&t=D@jmaBmKrn}@%9bMQ|o;P)%5_xg14>x9Jv!wq=+ zdzqb^iBaq%p2HNHAu1&ybGFXX?X>+uxuo*G4T4;xPb_xs>{6*Qg#jyTi#Drh_f zob-$HRnT|}_|8n{tDx}|@QC3Vbgvpu0r&1YS{zRSXX@ts$y0a=c*JSvtDx~La+~L@ z*1c^!1-$Zhblt1QQ^3CIJBi~d;I}ugisLEZ1SN~>9yXo=?*4SHIGzHY_35KHo&wGt z#rd;s@f2{;kK1)`8&3gWf0bDGs__)?>8<_6@hoyNVqO!+Q^1qD7ScUzJO$im$vkm9 z1>ArCV{tqMd?Hgh-NVLHz~xh}7ROV-Qx-+nJ#0J${9r*xaXbZlr2Iv3JO#X@TQ284 zZ0jlD%vC0f<0;^+Dc_0XDd4mV{dEr;PXTv7y-FNU0hhTFR`;;+6madD^~LcNaIfRr z#PJkxo6vD;nLodH3fSX%CviLloO;1|aXbb5wrO77!^Ts_*Q^5VIJQK%Lz)KqV=^i$o0xnx^y*QqA zp~r#4;`m<|aH`VE`G8wbbv!@;M>#%A+{=0jxa;rF#qktybkE}6<~NC_fG?GrBaWwl zN7eW#j;Da{wP>Ww$5X(umme0#Q^0;w1JXp)J#Rb(T;gSOaXbY)x#4f(cnbLTxwN_$j;Daf`wbPxQ^4uhKNQDP zz}=RY(miiH1>CRmQgJ*5%;JxCZw{QB2lwW{xp{DJ4xF2Zzk74=PbuJtcVh>?*`M*` z-bl;*5~NOCB~RQEt#*|;Qskyb@GJBbHKuMBlC57GN8$rORu-qo71Iqq8%?n z%{srJT}t-4cjM%5c6nXvs>$I(@6~?tax`MClQbKHTHp zkA`J`o-Lot$8_;Ay9@aLq<};HbMFDW`T}C75V#8CwM~8F~$5X(OI$sjUQ@{}#og3cnUb%+bk+JJd501|8e4Y3V8drSK@dIxZ1-26&s!c{uFnP zIGzHI-2aC-o&pZvuAzzzPXVWXe^4Ax0l$2m)>(4aQ^2uij1b3Dz&-~bi{mNaG4so) z*zgo^<+jVj@f7f@`QcS&cnY}S#fIW|3b^Uh9pZQjI7gS*Dl6mT@}cn!_$%LxLMS&jm`CV3OHQ2=HhtPg>W5viQ|7+ zz%%cEQ{fF*y+H3N;CbyUtJv@quy55x;&=*paJP@*cnWy!IDZuzo&w%Ke3Lky0-oI? zo{9}m0jHVNQyfnL$6I|(98Uq4zFAPmxp)dVS)1A7cnbLH$M52J3b@eax;oCqQ^2YE zJO6D$EuRr{ z*@ip;nFf^jeEoaM!rk{a-y5_tV>zG6Co^B%kmbX-H_d8o&wn@4hzs=+#cO+dXSvjk zYgg_P`qk9Qe=T5FbGC0df5rEQvuo|h?^)+St4=-Y4=j5nuiv0&r7EPF{h(93oUR_z z)%>f{#z_fTP~1FOH{x&-`{k98Uqi-kTVr6*0)CaRMk+H8o&uhoYo|D#0`9aYNosRFo&qkuzrQ%10`_Th zPaIDHU!PMvjhP2e0r%*=P#jMIkE#$Vt+^gg0dE-9P#jMI_dBpt98UpfJQrIF5S{{F z-K4EJo&x^<^fz%l1w1cT>h$L0!BfB+lME8aQ@~pv-4(}Ez%v(@%3$WfQ@|NouMo#m zzzN?)&1kO2Q@{-(cM!)@z{$Uy7spe;eShZ3WahzB!0Wb87spe;zR$jh<0;@4-|A*I z^WZ7qtatW`<5?H_mAfF0|78IWja?_InY8s&551>=x3^rUF`feUN*pShxgJjeSBz0j z98UqaPPASePXR}a>=|8;Ydr-V`e_$&JOzB`S+F>s0xt3{e+)AZo&p{oeWo~`0!}yS zi#VPF9uuifOfwIj0*-ZNr#PMhKF~B#EOR}c0{$7br#PMh&e8meIGzIDxG$flnFmh+ zKh8Q`98Uqa8t_RRPXVXvSR=NX2TuVn%eYM(PXV*|CKV>1N5y;7pdgk!1pHf`|JH|WRFYt>KnZ@p?t)P>b^hYuRqYW%WYqi06A zRK852b>+qt+IK9lxA%*!k@ifga-mPt^U*)w4jc0QXM?`mhx`rGil>;0Nip$ZjG0Vi$IM;uQ9UwCy(98UpP zeB~@!JOx~H))Eyho&tVwEQ|^jPXWhV-b5Tv0l(~eNE}ZAU)_>Qg^H(uOWyAzj;DY> zzPu!kr+~|L$gV=gQ^3LZM~LGo;Nd5ai{mNa6T363T=5ie{%YgI@f7g>!|%lL6!2l6 zS}IpO1$=q*UU57H+;v)N6)K(r4jwQ<98Upn@AX0)PXRys>aRk@Q^4E&wu<8^;82MY zXE9&bcnY{_;r`-y)`i?pCyV2MS-@5LU(}xEROoSf?{EL`jLncq#fcBFyi@z3;(vO} z`=`Dpj;EOWCd#7X#8b@knhy}iQ_M#eTo=bv%*D4CQHkOy=BmdQh~p{d(^12!IPnzo z%<|2|@f7o`*+<0j6!Xu3bSh3f#a#W{aB)1v-2C-paXiI5X?+=$D4t^e+&4%ZPccV- z7f#2~c#64Oi)P|@iuu%|!{T^~Id_G$TIBH*GmAgoy*Y4h9^9J)=jOq^IdEY%&bkdzckF0CVG!t3$qnonJS_8l`v;pB?zW_Q9aD8sjNi|25glo9pouZLdP(#PJmE z=T$Gn@f2;XpXGhcJa~%Md!q9nVaHRnhx)wI^>~VQ4&*z+8`~ zXv+ll5yw-slaoIX$5XTwhWS=8^WZ7k&(W5PPIbc{%$s+#NZLoNStprJUP0#3K= zkT{+KUNAjfH8T&M0#4R?lsKM6?#Rh!;&=+U^Pck6%{+JtxbO2d;&=-9Tmt9cqs3Fe zr3O{4VdlY8z=?Zr6US4)2f`$(X|Bgpz^NR4#qktynV{R^cnUZ;xM(di51s<bfa65yA&#ejt6c~d$5X&>+ZL>6=D}0IA8*YS$5X)b>xHUsuE$fr8!I*y$5X)L z>K+xxQ^2WmWolsN!BfETmW>g|Q^2nuy%fh&z+<8WG&J+zDd7HLH;dz07p}KICXWAQ z0jFQ$U))TZbJ53Q=6x0Whiy*RrQ&#s_D-Eo;&_TSxUa8|`So~;w)4y&aXdx4Yozn< z(c&rEc5PahF!SIk+V_*rh~p{RCR?+YG}q%P+JV={i{mNU+uxrWZw?rc#1aKwzJ}RiuQPn>}AY6c#1Z-;5czSMcZKXOL06! zo3OcGSu+owqFqvXjX1uAeSTT|@$SumbMxTd95^=*?#+R7^YC|X4*n?xJif$`){#=} zoVBiNp>b>He)_R#b@5~+U!>`rf09R|BA+sK%+;q`{Q394gdbDkVu5K*eCG4aMLTjq{>{NH~Pma93Z$1UNryhOFYXqP_5d|YtNq9M6H#Mt|O%J5vd zlD{l@Gs`?zkLhZDcLDzo1^jQh2khzzW~bxoI&ZTwhP<)J?P%Yvpc$9D8dvWr;An}1 zHO5oGU-RZIWUj|kz-bpw6~|M+DYw2C$5X&v?)eus^WZ7qRRv~<<0;^?WlxIZDd53H zvlTJ(;3;6wz7xgq6mW#tAI0%3avM(8Dr)AzQ@}+W`^51SaD_-|yv_A^3V21+5#o3X zc>VXM;&=);Ta^mhY~m^4gy+_X<0;@s3!-b&iKl?a_UkB)r+`!SydaLJfYUF@t<5N& zMecCVN#b}4_-feq;&=-9WT_h3jN&Qak!Sac<0;^BG1F<&iKl?u*6%aK@f2{JdSAuy6mZ4BdfJTQ zDd6n!mWtyk;0rCoYcq}e&Ar+|l@IVg^&fUDF`qLV5-1-$J^ zS8+TA{Grf!aXbb5Je!wJs_+!>sj$wUAAqNTpPh65`~W-!e060_onYZ9;8i7@KR*CZ z0YB*G{P_WR3ixubK{~<0Q@}gsJ`~4Oz`Ht^)=3qf0Pzc{|S{o+{i zEKzPV51s=IGzHY zwyADjGe4dJK9zE}IGzIDKP+)Rb3L8{F5uZq98Up{t$tM;PXRkh=g)8E!BfCthfEj8 zQ^2K$d=ke~z>f>nC}8HnQ^20}wu<8^U>1M8dvoC2Jh(Rp&dq~+bKu-O{N0;_e@X#w z+uJvC{N{BE#Q*VXO^PyW_pT_?G~SSnl}22-pEu39RG(uG|2#OU{}=Dqr*3rlHY(%L zSFvu7|D54;Lr>p$5jPx2Rj*mCk9|9Njr;4Xpk2**;NhVXW&2I@I~1XF`tnD2KY4iT zakN;ir{o%XtbMI+i#`r<^_Z^aPoK*D_khis5H;j~j&$}PK#CGF__q|Wt0$P9j;rgu zLw?WZjYY2e)A%Zb`H!}$G$`P64|<5>Dd20xu889);HkmRlE72Ij#ATA40sB7|NQsj zcnUaT_TnlAJOw;E@icKf1zak_7jZlVT(VtV6$73EP7>>YIG#nW$)z+Z13U$MB*7?g zJOzB)`;|Dpo89Eqz2*FOl<^es+(BDa5_k&O?_PYB0iFVGGPtKWo&rwR`I-v0S|km0yEo&x@nID_ta<0;?|Z-$HG zDd4#Ko{8fr;IcC+>7F;90&bIIlQ^CNuGlP|?s?-W;CY>UisLEZFf(q5<0;@$S&QnP zH=Y8n-e!R~o&v5g#`*6k<0;^b!1I;VIxEU%HFqDd0P) zuZrU-;3xG9=r|Ei0eAR0QyfnLx1Ih?98UqK>Rm_2iFgXQXO3OscnY}X`-DE`YRSsYIRXD(Yqi#(nJX7R_nHwVtm zgL`w}+&s892hPpI-@Q5brxb9cz7vYRns#wc;yWca#4T~tEA^=f>#wC9S-4=Q4Cz)D zs`4?!mq>kf+?LkY&purJbzHx`SvxE{^CLl_n^gywPd&wB`TQGA&+qN?*8=`On6vc9 z6niEu|8Q+;(f^OVw}6g1S@dqZgS%_60KtR126y-14uiW}aCdiicXxMpcXzj!`gQHO z=iJBr`di<6^UlnBowe@GslESHm;_R_s~UK8Al2n&ckVw)6nOaljf%<39S_~z-N$q{ zzvmA4f4Bqwr@RW<-4~dhj=SfyLkL=wS>(R7&mY$;(hCD`$1(3I;L26zisLEZaC4mA z<~ckCoGWWRaXbb5Yte3TJOzBxD?vOn51sAfo&w&~cds~}0&X(gH@=w%PXW(5 zHCP-^0Y7N_LLAQ`S0_vP1ZEyQ1-zrfMsYj^Tyv6lLi0SH0zQ?#k2t=I-ML3QdsiG! z0msf*D3O^5PXUKJvp^hA0WaPW(#JfHr+~XIZzhhXfK#qGCXT0oy9V=1Z05nU$n`lj zR2)wM$B6zy98UqiZcr|XnFmh+XNt2~98UrF@02j9c^*#zU%cI298UrFymVe1PXV|2 zl*!l3gQtLlpPeXk#xHz5yzE>n|3NsI$0*;$+j5wYGK6l`qIGzHovA;@6GY_5uuG4CZIGzHoJ}q`C z^E{pcPTRGMIGzF?TkNtpo&p{_Fk5Of51s|tj;DY#^t&gHr-17eD-g@fgQtLZY?>pEr+~|)aK<*z<0;^8 z^XiJ@Dc}#m_lV;u;Nii1w3y>5VDEH&#PJj`i$C78IdE+rJevd8=E1W$aBUv`>dnF5 zrhq>duTeMBhkm_p&-gTH=8(%@SJiMvj5NC0#8rcv{p^>|H|)pabz8LVJn?bp6SXIN zxH{$1;}7+wPw!D<#mV$FvUZFWv2~Br%l~f$?0(LG^WQ_4NmIK^)R*u2dSAG@F!`-G zIkxredFx~UY6qvd`M@~hH~{)7)xq7 z^_~JAkzt?4cnY|6_rxj*JO!LITA({{GSEFXFun|=fcnWwx^|s=83ix8AE8=(x_(3Osl?0vw-hXAfIGzHYa^{0L zo&qlMu#yS`PXSNevq>CJ0q@%qS0#a`fD?}CC5~s2Tl4XjIGzHo-XgzB0#5sQ3_6!7K6d9s^%@Dy;^z&YZ03fK`RL=N*j zo&sK9x~Vvx0{&Iwm^hvS_UfB9rYMyJX1p-}p^hyn;2z^AisLEZO_Lvs<0;@G=X1M`C9S7`EB~A% zj;Db0Mg1y{r+{%5HHy1^jxbkB%Af6mXeyeZ}z<@XcPXV@W&(oF`$S zMCOx=r-0){nJo&98UocZR?|BMmz;v?nNJQJO#Y8 z>>Y7D1$?ngejPvJDd1P3=7{4dU>1M8XLI1%Ja{$-uFZpIbKu%M{MDO-ze@p6o#T9a zw`!<`PrCUhe|*00+({Y3__ZG$?^1*g;R-!|KVwDdO)<}YJsW@4mP27HN8hz+cIDv3 z!rv&9JYk;P$J?)*^t$-&a-S=D1%3bg*S`ze-JD6&tc-u~V)7;V_6<*2uj$m&OQ!Zb z5&7MZe3b)Vgud8eNNsl?)7|{PFW}La6Y4C|EDz2xc3{>P=U6*13zKu49hlY5Io=M; z66u^^2j;raInfTxF{5*m9hfsp=VUuDAIv$$4$P^kbG99rqgdx0J22*MigHiutg2oPZ z((b;%?5y2A=WiwrdMj&>-+NDLr(^tni=yd$Im^#iI;JtcyyaI@B`{wR2fl*kU7ocU z$5*txN%m9Xcs5lLM

|W(S^4)x=&M#qn&aBKABYj%QOfqft^F9c?;L9M7gI zS^X#Ccs5nH1524-kPbYXs^!yHh~wE*E#DtOzjg3zsxFpo<>J<}sY-nHlsKME)%s=W zbc~N@Qx&<)C~-WSsvF;4h~wE*WeX^6e&0FpY^nzDTrQ4hQ}xO(y!lP*z_Y13*RzE< zo=sJVBFDw?Y^pkLkD$#mo=sKxHZ{fZY^qYU*&&YS#N}gS*J}dsoVWz+?Ws*Ro)edF zxo(K#IdQqM+_m|}bK=q~_*8AO@tn8>UcVuZ=fow>p^VyO<2iA;eP)6w@RRCEbl-;&@J6j&yr1j_1VXj(0_Evhkd_ z4DPmG9M6eMY44cYOyfCmnHR3JIGz)i*I%xP<2iBJ|1DP%^Yh1Z;C2 z8=e!Ft{3}?LFTNg$&GhZ*g@y*!t&D!(L+Wdmxo3ZDcwdb3) z`SrjzW4@VtzFE7zng2ia&HC?Bz#Wb>`8=%GnR*pI-)m7aM5zan&gO}e?#9yV0}IWG zb+bU&Q;R!gAM9w?dfa@#y4AO1@k2kI$5!3B*6V535FgWh*&W__FJsWMNCBJ7 zaW`koW0|Y>nB~>!d5a$X-}k?LFV?#6L(<%A?F`p-d6j)v>xOjwlOsVBIUkRHs4cmw z^@$y*TA$j1s`Z&2s9K-ffvWX|9jIDg+JV_6d%dy)v&Hs$Z3kxG?)AnF%t!WmYX_>< zcXps^eQyV<)(>`|YW-*js@6|-plbbW2ddUDcA#qgY6q&;Z+4(+{cZ=U)*p7DYW-;k zs@7k2V5&8^V~p$bW~w!q(+)zK{~j+pP_+iL166BqJ21O4X9znmUruL8J5aSgvLD9` z%p1EVcVBeZ)1LqD`3!hI1D?--=QHsCx6go%K!X0Go&NT}yr|9puDM6%F=*(yGZQ_y#PJ-f&YyEv9M7@p;d0pu zn0fFVs}_qjL>$ktYN7@=#PJ-fMwynqpqU5Hv1-wcQ^oNdt9C#7LmVGqKR*-0)+=P@ z!E>ydv*=!NJjbfulM@#<&*M2(onE4!IG$tG@$c`8<2hDc@uE->GY_6))efuXi{m*~ z4d@=MsCgdGv1*0a^~LcVtJW*JM;y(H`H_ecTg1d^h{+ zKXJQS<;*;Ij#WF=-6@XeSoLK&pYrB;JjbftO7#=RbF7+p!F_Q&$Ev6M7pP$7!Bgso zC(jedbF3Qwamb41c|6CepJ%ob$8)TjJ;X(EJjbe^mS?PF=D~BU`gZ#;aXf)AHsw@t zJXNu8SLgpamNXOozx;O9XUFBLl`a$?m@8GO?wMt3@&do!+3&U7sY_6(b?>wgun`Q_kl&K-L{ zb@;TldWze3Ud)YjwMdeiF`~uvZ5@7D#!8p7{qZKvL3bb1-Tc451CDP0N+`1wJ7d^^ zYC5JJsHS7tfoeLo9jK<`*nw&~t{s?~c6!@^scC0CJ1{lvjBf{~rkx4wKsBAv4ph^L z>_9c`V+X3~#CD*XPGSeD>7;g`n)bB=)pRmDP)+~q--Pu~`E$VTzQBv^HywA+`3HU0 z%^Qoyh7GUuBcr67sP`-$yZaV2hX4*di^ta~v&Hc&9zN~fi{n{5Qm?2I!pwtb@u*v8 zlQ^Em!>>SCb1dP&vv|Z`-%uRS;?Z=%5pg_=$In@5%~^*7&*E`%@py4Oi-%w2ui|(X zkIX&mngbLEp2g$!%Y)*07LR0K(wNg12cE^_VgE7WcovV~nLmi*Sv>B=tRB|PgJ{wp2Z_}aUXLYLIq;(`ueNQfIDU-fS!Vtc$B(sq_=*PRJEsFb&hqi+4~XN( zTYjK(3iEB(fuCUc&oINp@viR@LH`<8mKWmqN%r}j8%pXm7Wm1QCn&!}96!bK)HOos zKo>vN@>lg5iQ}hP-lFvZar|`4AHDO{Yb@|HEUz+TusD9EFkY9K3Q*u-up4aj$ey?cK7`E0(LiNtOx-!t*?~zzXL>s@TL5PUJ23kMCz}zo>2OB-7ZWsgu(NUZ z1!kw??m2%mY0w*+jPdvH=@|L*oj|>(fZrao$H>-Gz%~3_$H;gJxY0h>F*2S4_Rrd& zsQGw!3OM_?-Qsu(IJIwN9Sh?r;D9KN#PJkx+IWY>@f2{1I;o4BkBg^(w+A0Bj%Sl` z>+m~qJO#Wna&;XS<0;^8#dnJ1DPZTX#5xwnQ@~>u4iLvvz>Pyb630`({tJugSQt+M z4;r^b98UqC=^MVZ`R~P3z`lX4#PJmHth1-Y@oX}VFZU~B=D}0IUEhon$5X%=^S=|v zQ@|@f1(Y@O;3?ogs_hlWQ^1Xj#nZ7co&rvtzoR&w0`8f`_3tI)Dd2_P*~*)bi>H9s zN1ZB;r-0u)`5}&HlQFqWoeE|iJOzAp-)?a{1zh=E5*-WUDd4P8TZ-c;;HkY%isLEZ zI-N6AG9MRD0cZI(P8?4Gzx?r098UpnTT;EUnFmh+Z@#)&98Uq)-Wt1#c^*#zcNyMA z98Upz9lIosr+@>3WvpuE!BfB=yoZV7Dd4{A?u+9o;Bk5LRx|V9Dd75Jri$Y!;A#E8 zh~p{X-T~FCn|bgQa6s}M;&=-9)~`eX=6O5?{C-k@aXbaw?f4^cJO!NWW{DbR9y|rS z*=waZo&uiNJ916)Je~qRzq6eEPq^Mr#QZ<<#9*H(OojWn&lVf zbQi~0w|vUVo8tHY%je(8?{9vd_!^ei2|Z67U(@o46@%qB&*N)Z9@5cJ9ADe=F*6T{ zJ@v)$oIo_#=elFYa{`fRgzJtO&j|#JKi;!BaBUtun*-P8!LvDVZ65yW&B5R0 zRnS+v78q48#k~~E<8R$`cgv{W6MvO%mG|uvpNJ_FZkZfx(aZI@Ry_O^Ir71ZUdw8S zE#w@vd}PwOnb+p1+ADm@m7kOKnKouf^lEv7u2p#zwE6btZq83zOXO}pFih~>QEKK+ zer(g;;)#!*dXsU7Pp?Doj(ylVZ=Ac2>2Cht7jXV?yL9Fg^pkZs3)q3~fD76|d}9jP zf$o3{+krVzb{4S%b4=|lY6s?w+gZ#G%)z;{xE+`e<}6_c<|yA;(hki1fU}exn9Buc zX*)1C7S1wuV6HoyW$nP+p*YLgfw`D*mbU|QtK+O-2j;5CStqZPyyGp0~K&BJ21Z*oVD#h1zg7toW}V7 ziwPP#*zvgg0D$$F#JLo-&-h?kDdR1(NUe_jw<0<3sUB8IqDdT&oYNq^%(Wy4^=zkS(iUYCy%@6L` zZM({o0Q^51Pof5}W!0$U}bQPra6mWyl z6U6ZpaNflq#qktyzROh#n*T051>C;fCUHCkd^=bil_j16Zuy{#IKHm^B5fUeSsYIR zpDdS6$I5sLc+K{S;&=-9c=C_pcnbLHoXSPa&mT_#5Ba`998Up{_l}{(98UqW$m2bm z0M{nLvk7o*5!jpTo3&c%$SN;`tp8)eewUkljX zoP#dxDqX$suAX^HUETWmV((UqQ)YZLu6&n9FQQByk-1-`&+a~^yZL`#z@Aq@|6~3= zV0T~O#Vp2de({=4H*f5`IyHf~ICl~&f8-Frv-1kQua!8Soma4pr^WH?ys|FOXv*G! z=jZt}R#9>MZ|@{Dqv!v5J_DZ5faf#d`3!hI1D?--=QH5>40t{Rp3lHv`WXnHYwqIQ zJ^X6?3>9(G*}IveCM=z^UI^c_*_JOEHMT>%uo1%Ndf&2Wp*h1Btls*vMv^rbZZw^+ zEY0y^%Z?v;T_D1URbzIooiZ?U*MBcy_j7)GR5SA8{^oz;oBT zuJsIYJa@h4Iy%g20UUVldUG#tAdcsl*Ei!~aXfduogSt(FEDW6x$7+ve~dVuyIx10 zkK%aldb3^#Ft1v0;JNF)(Qb!0p1a=crxTc$JUH;&^$u*+M;y;x@9r9R#PQtqrYv44 zhM5P?UGMec3&rsq^Om0B`tz4~?s^xrcm4TGJa@e%HXkxC+Hl~x>n#^5wO&So=dL&E z_Oar4?s_A{{VI;@Z9y*IxZC=0Qz;oAot=Ld;Ja@h4lRp*5 zbJzRjeR1=mAP1hi-X9s4h~v5I?ea%3^NJz|p1a;ceQJv1x$C_iW1~2pyWWPgBPBBP z;QzeqebZbV&t31K`$xr3TF+hY>8+`J%shDRde`(HDURo^_foC5;&|?QeLhxBZ05mp z*PA{07I8dxy^piTOJbhKbK=-5ptm@l6UR|=Z;RtOaXfsZKvFXgo)gEIzZQt&IdPo( zF_f=)9?!Z^rd~X8{BKVjqc1)e!u;SowY%s&cffh`rq($ro;%>IX#>Ua+yPfFa8(@7 z9q^c3+04tP9C+@44b(7?cynDyQk|me=Z1^y)ao38k*M%I?<7K_n zyE|Un9^~uoZ&zUXx87}6PuTv}``*3rPc9y5HDc`W)3?rKZ`9gR@L~UW-Tu9R-Obr) z-irLilNa8;qO#AKd4an^&D->9Lm{7^W%EX_xjf~!2c z=WkZhpf?t|&vU1zFyol}bLl;=cAg#OhsJmc_*to%Db4eE3fSw+HgP-!+$E-WD)T&^ z0`A+Si#VPFZt(h$IGzH&eIj0JGY_5uzILLwIGzFy?{!ZcPXRxeRy2*72hSo`GrjBI z!NpU+U5ZEcGtc8G;Ow0{isLEZ=Z{?f4(^|xP1S(hY0W%%3i$518R8?Yr+^=C|0#~A zfOn3rlg`Y8r+{&n%+E*r+|Ys9w3fqkxQBPo;aQY4(M7qgP8|U0r%Om zSR79Q=V~4~qj?@r0S6xLERLsuhm|`mj;Da5G)kGt%!8+ZGi4hrj;DZcC4MH3r-0`~ zE0fvGgQtL3ZdxObXOVjvFIpD!Je~s1KDvWAo&tWq<&rp_0?vOkQC2e#o&xqO5h#wQ zfD31OAdaViV+<{t&CG+RfE^u|isLEZlhGq&H_ziK;NhQ|i{mNaMy-yC<0;@GSyJaP z)8HxKCGm!e<0;_xsqc&9Dd2Qb^W-%1;3?qPgC~mPDc~FRo{Qrt;OeW3L><0;^u#kY&&Dd5Xrt0e2sFQyfnLud9$( z#|wB0IBC2Y;&=-9<*4uCcnbKYvt}~$^T$)bx!Y_L$5X(I&v+*{&*LfJI>)<<<0;_L zZLf*rDPR_Vyk~RZ+B|qR2d>S7XLI1%Jp9$0gTG4wXU^xF_sinoiT2;i`#ja`vG<0T zT@g2ak&xepJbu$4m{-%Cv&K)lx_3jY-dztaEzzUH^8T69CK#A(NU$#xejc2#=mR#E!p|3FXks5+v`ESo9;fQ zyZL`#z@9$`{Ezu_!0x`l?10=o=WkZbpf?u3zJr#lIHzPPrS~j;393X;LE!qS}IOFWnBH^PH{YCydha)l_j1szL2h& zIG!@@JME}Ao-*D&!B1t0r;Idj0bwZ6US4=pAJ@5S>h?PZ>`;KS3N% z8Nb=~UK~#u2M(*G;>1(N_bP4{$5Y1XlEhO%;wj@et9y&%DdX>#?~3Co<0Ur>`=YT)#f2FdlKVZDx zQ@{Zk3aKFR6!7pE6UFfq@WOmg#qktysicKfka!AsWbQ@ccna9NR9KZFo&qjZtfe@f z0zTU4q&S`ezB@jx3KCBN$4fj)98Up9U-4QTPXW*GS55_qr+}j-TP=>KfCpTRsAFV2 z1)MaC>mPZ>Q^18rp3w7n3fSjY8XYg=Dd5Z%M~LGo;2b$#h~p_>7J0m96X4n;cs2p9 zO@e0=;Myeor#1n9mjaG9`1RMmAU8M6FS5EL5=;p{(Awtn{)h?E@u;e z*t+A~*I#XVzppcI!rmgAkL{?lG+pP}!CF<{k=EVEbT|L+3)u7Lfd4Un4%ponc(E-i zch6~?6||$Gbc1!>QrhgWmc6R3_muAX4spvEj;D0TuIws~r*vNxx+0FJbRTcdUe?Tm zr*u<)nIMj*bZ-oMAdaVWH+RWd&dh_SbVv4^A&#eXuO4=kH_zi)!qrhdGM5OlIP3B@szI5_K4NZ^LR@4*4nn> zcow-NLtTFp6Q0tY_%dsN=CPjAO;UHVIG)m7JN%pLdFv_Nx%&#$F!SIk-N&gHisLEW zIf=s5G|%HH-LDIqi{mNXFLO_d<0;(>h5TxndGM5OxHiMZ@s#e7($B^5lx~XnrD~gb z@RY7^wPoUXO1Jf}5OvJ+cuM#F?AqdZO7}(P&Ej}UxA~4Jbd%HGTv0Jf6~> z7`LxDp3*Jc=$<&9(mg%7a6>Z>p3<$hez7>7(k(JIY$NkLo^@eO$b{nf-`)XVk29^X z`N6e=`|CXgymR#{jqw!l8PXR}% zS+j(h2TuX#@ZBbkr+{xvcKr=ZcnWw?*ls1wJa`JYO3N$acnWyT=^UlZ^LPsQ#px;H zcna8m+81#=1p5jXpF*AY zUTWfu9Bh5I2tnVMxC8bI`rq#6T-&$Ez9$76PnfhSQO%d3LTC1C_p);2!Do*gE8?Fd z;@uD78zwa0>4M&zMN>qIW&Vc&&SG|uzzmApL3}ePVF&Tdprjpmn?Wf%h-(I=?I4aB zl(B=@W>D4+%m;IpvxAssP~Hw=m_Y?Qh;9ZI?I4;NRI-DpW>DD z8C0`_h-OgT4$SA$8DIzD&7g)IgfoMhb`aJKYS}>;GpKC`q0OL<9fUH2x^@uK4C>iI z2s5Z}2f@vtfgPBI(b>=r%$L*I$PS!lP~=|*L1PEIVs~G3^Q`C1^BM4b20Whu&u8HO zf1iOr*K~jT-(K1l*zQ@g^BB}^cp<$nWBKu*zs}fjd|AsI&#f(vFK7A7?Hk4M0%jgOW&C5&Byl`toN)LjaXe)l_`GUCGY_6JK2>>( zIG!@TdM|b%^E{q19+%JccSqwX<4}7q>Ulh69Px1$oiyMnly;z%g(dl+g5Qr zW$aiPOV<$iKKA*|Z@Y@)IX0h>?uP5y!umk_e3ui2bPa*$*!)3^<>Gjb%^%!;ERN^c zyw=0~x`x1WY@VpjEO9)?=IIl8=^6shvH8!{4aD&ro1f@*NE}ZYk3W|}*ARG)%~NL` zDvl=*POe-cj;AVy6&cjbd_Vb5y~R9U^~>QJT_4s?6FS4)=g(7K*f=Lv{ggv@?<){1 zaf95gA}$`cv|EUID{15+g!0x`l zob|hV&fos7VlkWIcUp^C>{s{h;&>Lbv`21= z<5|oe_bj5t3D07-|HcY&Jd4@9Het0m;aSY))o37&XEE#FZ?B76&tmofam_PT^!FH z@R))=x>Lt<2ORIv0C7Bbz(40a6~}W2Jj=hV?$q(z0UxZlMjSuhZa&idil#et`~=HG z4(ue3pJ;iK<(I|rlPrIiGMnoT-ulUwuRJ$i96!bKOgG<( zxb5qh`SHKK13tHQqe{0#%Nlx5=^ou3*5CYKd}aH=n$4>zj;D0Tu3asTr*xAqcfF(= zPw7V3>3T^wp3?2N&-IdSJf*wRFO$j@U&H=E^5#NsBzbsr|gM_~SjB1J~xkvpH~W9z2@^*XH4`-W>d0?tuGj zjnZY?%&9d()vN#JC3f(}b^06~P&0I-CtNK=%^65 zHj8=BVmvBqTX8&#@x?`_#qli0&%dY7W_~=L#n|W3XmLD?an`x7#qli0HTIUyZsx(W z7{@=jQXEgI*Igeehj|`Psh4xK7ROWSm5U!0$5ZMFH>J#J=D}0yeIpGL$5ZN8dYlr+ zQ|fch-V?`Dzzx&+z#PJkxokKBn zjfAIwPe$r2j%Sg(`1-s!o&t`#F>7A)aq$%J++S0~@f7gDO-^m1@f7f=1kJ?p6mZgu z%f#^%aQ0`x{msY4Q^1e9*B8fAzzG^25XTR;o4lxdlIz+CPXX_kJWL$VB6r~Xb8$Qc z+`M%eZKm-QaK`Ct#PJmHsmfo(@f2{q-2vKU<0;^yn|FxgDd2j!6KOMzr+~xc?kA3? zfMY&-AdaVi%f~9L%`~0@&ii4$IGzH|us^sq(|8IvWXT5NcnY{d*B#<`3b=m1nA&{f zDc}kr+KA&R;Kf~zh~p{Xjq7}M(uSvi%%Lla+$5X%w23!!wQ^1|aXV*y^o&sKfev&wz0)GGNi#VPFz7?)k zsi5B$4m<@M<<(AcJnKS>8t26Ezb)X&a}uOAleWFR)6cvwZ9ge>K6Vqwm$AISgsbBC zvX(CyoFkq2@%VCUPfbW*&S+%Oj_sD2}JpM~`_bj;GWU zG{~L7%!8-Yd-{wN$5ZN!$37OvQ|f)kl*nl2!BgrW2B?6<+X1`OSeN zi;ue$@7%rf%Tp&_d%w%wHw!!GE4kuz*D_~n#a!Mub?nB$`@HY0%`2~hHs6-q%{lgA z8}CS6L!>HwpqSUbzy-Mm+}Qr8U%_0byv7vCdEotuf$l!0yZL`xz+V3ce-79y_Ged1 zdI$YiIpf$tTr-Gk2XV~6+YVv}4ZPynK`dk9+d)h-NMHvs%pjp1L^p#(b`Z@BeC!~q z86>uYC}xnv4kDXDQagwgH1P7ZgNVi?vx5j`klYT!o5xbvK{#XnzW*AMyDu<19e2-# z4EoPBZ|ul+oQ~%L_W!dZn{cy+2%a6;=wLg=@$AS>MD%eQfoDgyG*zHDo}XvR`4hzP zzul4PBYFOx=QH5>40t{Rp3i{iGvN6Qcs>K3&w%GM;Q0*vrJsSoZ3DMQEBg8K(eGn6 z<@g!(&YV`cBVRjFsd%vXIoD@za=ZVX$aiDqi8A#|w-I^Y9$L86zyI~Q&q75F{$bL< z(mT&QKNhUnt{*Ah1@*(f7O=ZH-FbH()7_8%Z2>#~4+_}%e^9{A|3m>h|G!nh?!Lg3 zrMu_+?Qb3KZu``!QP!Y4&0MddA z;&|?E{bRabC5z|oc2(yE<<0zf?rs-E3{k;wJjb&m0vn3sxw{>BdyhDtyW4nW!&Nl% z;JLdE%vDz$-`4(jg)h2K9M7w)^AAr}$;^Z2Rn}S44-?1pLMs1vFU9d4?fmC6R?@3s z@trJxS!s(nzO&^8oe8R#kBjGyIm+Tb;&|?ur-r^Oj^~c~bvg0?1w^E{qA zWOEw^}xM zR$Z9mxmykWVU#$YyVXWfUy0+nTP>ESqAtww+^u>i*es6cZuM@m__`R!bGJHvb02X$ zcdH?0+!M!hxBBX65nYVqxmyif;QAvjc>2Ms+Vj^}Q5 z*qKaa%ohpYzbYc>i7QfbSJZ(kI`N zMMGOenX$g&(}_#dFA8?7e$UNw5>(H5r00=^-uJHWIkoQ1(srABe`;~IZ1XYAwjXpn zcz?a-uGwj#MvQxP(+&*JcT;y;{tI!g?g<_Fl|ck6l#KM#l`U~a#ah=6vtDjO-ehp ziNRB-@5eM0$5W^wvKcBur_p8wPob_ZHdY)@q55_IB#x(0&!nxXO$?qwoqu7c zIG#cclO>T(8t@eAiVJZ)F71l`uoJH>SUUDecpX4gprog()_#PJmB<9VTV(txK>b4+V4j;B!DCO#>Sr%Y$}-#qktsnA}lx(txK>W4CKAj;By_58W@0r%=PhOrVnnJcU|$LT7RO zG#dlmubvafPq#ej?uho&uh*sD(J5 zbs=)}zT)`b7Vy|8;q#kG3+!I)Z{AbD4ffU*$5X)5+O8GHQ@{x}hb&-zJe~sH@U)&d zo&p}>wO<@h0SA`#EokP!Q^3^*4i?8#z&|%X5yw-&;hiP)M*{E^aFdlw#qkvItFqzs zM*{E^@Ri&x#PJkxhL6X@@f2|S9jS_#pFf@gE}DL*IGzGdGUtgno&p}6sJQ+}0G)zup16n{!I(j(yHQT-7H0x@#rVl#ZJ) zQMBxJ4wO#cG3>A6`Nl*@lHJ|MbT|L+3%GOZ(y`2U=!8N4=j>t!3Cy6Y9mF?-ZgvpQ z47%Haw;A-XgSckU(+=X8K`%RqZ3eyVzL%wVt`L^OjTb`Ze~hT1`RGZk*rgE4jx!VJdRL2xq|X9s4{agMhG^UK6J!48~e(CJ@H(AdEa)!i4E z9jm+N{LQ36Z){dJ4(Q}(#&KRI)_XQ9yQ`hi7|&*G$`)+BVl+p zD+@kO&0yxovsqbD?yLBp-fkL7uBo2UJdbCyGAPj&aXg!q>TTj=GSB1LtXxgnRUFS| zrOx?_;&?VIO^;;FZ05nUS$TV6k~p4|j3Wm>i{m-T7?HSI7Bdf?lZ<3jH;Lmp$=DGt zR#x*oo|BB3O*)9YvzdK1MRQ#^UcEKHP7QY$%uEljX0i@j0*3Uh~qiQs5bAMIR4L*jNuvb znfdX9?8n{bJ6_z&dQLJnoct(`=OiQC!T^6W51!4+(lFb_@tkCI>KR}0hUX-s#)>}T zcuq1>d^seJ=On|?Fs0%R&q+r8fZ^hJPBMnwekqRUB;&#D@&(P$AJ0ifl;P{e@tkCg z*&a>thG(5fH@Uqy{?B#c<92a8i_VICvznQYtGAwCdZaP`qv3KNy{}}y7#BmH(HKtw zvuNNwi;8Q}@hmE?MaQ$KxE3AHqT*U~Jd28J(fOYimA^{?FLy+Fy|Un>(0N|^9Ui^5 zYuyW(hd(aXcj2wC;nG$dQMXLvXNl}>p$Y35Q*qmKAl>g38R8;4dK zo;ZDlm94w(`tN@iw7WU8_(xwHP^-YaM^&DT_W${AY_^TJd!%b{W0P;0c0Hf$@!jw4 zW4fFF_XX^E74$#m&jGvp0&~{y?m2&Lb%HiIc5{<%eKK>B;CNl>kU2zfP;41L4;05! zY>Asb5XVz&p~Dw8#}f`b#g-+{0&zUW_APEmbGG5YQ*6txH5SKHY>ONR#qlhzb=&)z z0}}_HVjH%WAcow;-HfF zl+%)DiOdO>15Y_kiP29SPdSw+@=zSlBA4i5X>%;*z*9~sqiqn!Q%?EDM=@t=4m{=5 zXWSp+c*-eX_aowX$|+B*H0Gerfv22;Z5k<#r<`W`y%on(PRn0ZFsFYGJmu8!{5o+w z<>dV!W&-m(o<*)|xVqwa%4t!pJ>q!EX?)(K<~-7Yr<`6b9Vm{coUV;|B95n=F8h{B zWahzBPEGPI6US3d_dErw6Ds0-VXs^Y{gp zZ;8`D9KX==DJc(%;}==JDpHCRW*$82!j(?B#qqzr18(n=*BtjcBAvf!PI?_>?W9%r zOc%$Ov;5rEC*t_>mIwIcHfP8VdsXi<|b$Q>B@IjYaFGG)s9bT;CS+njLD;J9m@VB_j8|+hcC%?6=A_i}EDf z_EdSxE_#@C(!bvUyPNYsgQmfX=NuI2*tCoBf2QB{%|GeYr~L=FOH%*BwZ-Q{ZNKX7 zW4fE)Q^5c5S3$e`0<+U`_ng03NrT=f(R<+@6*uF;AqVO`CF&EVSP8@NlxW}|i^TDi z=xt}HlID3lC0d|kV{tqsn(gd9aXgF7h@VkQnR)Qs9j1QTL>$lEVX4nY#qr!7&dry$ zw3!Fr-fjX?-E+OF8Q;P3Bnv<4c{~N2;cAUCW*$5Re9^H-98UpXo9nr0q61zhvoEO9&q>~rv^IGzGNF|~FrGY_5u9@=QHIGzIb&6BLQc^*#zKRiD~ z98Uo!5BWkIPXV8bQm&4f2TuWK%e+n;PXXT<6Qizq9!~+EJk?noPXS+le^ne$0pEL( zyPlZ`&$CCAuT;LUBALTCQ)%!sg@RDN(;PO~mn( z==_6+#qpGAhczkmDrGz+>fKSBpHpspTyGc+cj*wR!Mt4qTfD&*s3jdHAb0 z2Y;6W4m)Js=#-I@ExmFuOYVWEeynPo?DW8wHPiGvoP6Mo(0k^$K9K)H^0NKEe@)o0 z`Me!bo5#)`A?>jpgJ!m9xv5s${R@vh4_|Y|t>fSSy@1`#S$TW4NFjSq2bdHKK`-fzMjx98Vcv ziSSGuPZ@VvTvA1gr;IyPYATMWjMMn<5XV!-XGGS1y;v^btJ zKK$;zIG!?obELXT7f%_-Y2+$kJY~G5Pa+j9o-$tT-A^1(8TX$5KpamQ|8X{#iWW}+ z#|S@398Uo+2>n4EPXTZCs;HvHQ^4m#tP{smz>i-=SJC1r;1Yd1i{mNa(9f@n<0;_J zjSHw~@f2{8rHjPz6!3=S;Z?MF3i$Haw&HjSIP!pt;&=*p(T|*U&G%(I1stu^EO9&q zTr_`h744ti{(AmWr>QuebzyA8p5pl57VwINiBzuhY9IKw0-lzvqd1-duJHb#IGzH| z>!S(faBG@B#x(mz4qnMVveVPS>*AaO@M2Y;MoMYHVK|hfNPWRpV|ccZ3_5t zm$w^}=g&7IV8pA0KNbzmxT<{IbN!RIDUmDu+DSe4YzX_f%g10vd=q{6IJ)wV1MfF@ zt#96A&inK~E=6g5VRgd;&rde@PUsu>-wN36oZX(T`(7e^rFm!TcAb&yXrC?pypw12 zE<8A0zTt;s=GyM=V+Q&E<Jd50&`_IJjlcf|3OZkpu{Ym zZPxLW?tr#|;&@8;>6ByQcuIG22tRGs@sw`kU!%nFly2<7Z^iMHZpdMkwTZ`5x<9jQ z7ROV%ZJNZ=W*txI4lLYR98c-?k9kQPPw7snoK>56{G?#!s~Ys`^AV%P@f2{`1y98P z^!E9_c?xP1kEejU9G)(Yr+|Zbe-X!1z-1y<)21I!0bjbiNgPiBw_NG1lRP{H9Bxo= zaXbYaCc!;%JO$jXLJ^(h;VIx3b(e_aDd4&T!t2BiPXVuZ(@Gpq0bfjiMjTH8$EuQ9 zCwBNn_VejqcA_|*bz%IrrQ-PC7V!GF<0;@Kd%|Qe&*LfJaH0PY z$5X&Zmmd|!Q^4a(rO9aK!BfCv@(dTpQ^1WrJQK%Lz?HU@$YkchQ@~EYrQ&!BxW~*e zna%Tf3bdvFpy{GmMD6t}N-pK?TS7o`?FU$K+Yp&Ej8!g(+RLhq8 z-d~bG$+f4e7nHl!Cr;@%<(7;bIz8F`s@}W8mdNIRrmi!@zx_>E?&fso-F-}VKmKtr z&mFK?TOyeC*OE-X@NxfH!_lq&q@91$-vYKyf?;T>tx1aXbaQY+Y&H z9pWkADREbc<0;^odm`(O5KjR|S<_Y=PXQm9b6y-z0q0-fdQ~l+0-o}7QZ@7S#k0uW z^Z6i-r-0`*tyym3%I-4Ws` z;LKNNisLEZB!`{4Bg9j{Y1TIs$5X%?haDEjv&ij!lTvqtcnWx0lVRd`3b@R~m*RK| zc+Y}tx;w;Ez;U8Z5yw-&6T*KN$5X(OC)d{9A)W$0)O(LOo&qkJCTU&s^}0syv=6O5?TsBT4aXbb5Jmh|HJO%vldXk1_9y|p+Z`mMmJO$ji>N9aX1swiOnMP(F zJO$j+cda;{0zQ#7Mq~3lo&xStvx_*M0^Ym#ia4GEULGuW6EhE<0?wFnmN=dQ4x7~3 z)I5)8T_`;|hWMZUv+GFs*pw5^%=3B+tTsu<3ol~k)_V##?6GGW<0;_LZS&}O0Z##E zt}{j)PXXu8_*@)M0ap%JM#l?y3i!mO)#7*xc&5XDaE(yW^#US11-!3JdvQDk z9B}NcIGzHI;G0P=Ai`6?@4k%{$5X(|m%kOqQ@||#c+cj*wR!Mt4qTfD&*s3jdHAb0 z2Y;6WUOc10tNEkmPg>lw_@SHYkJd`nYg~~k+17Q;+UfYj>6cR9O4-7HQvO4``yD8n zBf^iWnT|xN+`WQV@eLhghfmp}NSc=A&RhuoF;mxnzXNtRXQY;M3gs;tII-5r2cu^A z9GKz%Izs$V$DccvbU&TC$GOMF+`75}pD+y?dEB zo&wIh!l64PJOx}}O9OE{1)TT$L2*0joMocaDDaXbaw?|v9< z+VB+ciagE5@hozUqr3jKC_Du`w|Z)A=I|8oxVa<6@f2|0>aIUmji-Q1?XIQG9G(I$ z-FT}wo&qk~J(@OccnbK=(01Z@3V3qc3*vYRcy+VvI^n@nz@Pl5i{n}3t}XZ>j;Da1 z#;K!|9y|s7s>EJ#JOx~{eK?)y;3?oEH(Q9~Dd3iuPKo0wV81#Ub)ti(fG5|TAdaVi zgZ`;LaXbYaVpSEL^x!GrA0s!4<0;^X^!b%y0sG7zAdaVi`>cE{j;Dafo-L`99y|qntNcoFJOzAyOk|z(;3?oaYubzB zDd3pPFNot=7y8D2Dvtl{9dL&5b@alM&0B-%Jq282?K+L|6!5NXt{0@>Dd4W3tLud) zcnbJ&(hcHx3V2cOSbBj8o&p|Ox|=wj0?yInhB%%A9=jp0UU-71fMW)mC61?nohyHd z<0;@dmFj7o#8be1itQ1{Q^1RZ`{)HGcnbKy%HHC53ix7-TjF>MxZmKsdf^G40?rwH zrZ}DgZddQ8IGzF?S+jOQv(Dow;7c)fisLC@7Js~FbKu%Mcs2*F&4Xui;MzR=)tiIA zO92Nz{WdV&r|9cu75`Pz(e!NLN#&3JdRi{m1OFb?S2oOYvh1s?emgddjM64`$UT{V zruid6@oYc+PE{@)r_IIsO%HuI@BgCm+S?`mTLHV9^IoR59kx80cm3nA#4DO+&UqkQ zh1ZD_c8=iNxwoTN%RKR;x%-&z=Jyov|4_hQ|ByRicVA$3I_{p+3LdmLvdCqwm@2bb zEaOx>lF7WMfFqR|CXT0ow~Tr!j;DYR`Ig9HemtH6ZZvU;IGzHo_%38t^E{pcKDxhv zIGzH2v3im?o&t`$^{Y6Z0^aqtRyH#~o&s(;b+0&{MJ`J%*Q>Je6mW+n!?K%s@Dy-~ z2d~BP6tK_a$~nyQcnbJ+-p%583V7qJI62MpcnY|pe|K>_1zaG*B5{0=VCF{${mn!6 z)!KLpxN4pUdLB;!Pn}pKm-%sc7P%o^=8NMg;D<{>Y7>m7fbYI-CXT0oQ!F_nj;DZc zM9QH}FrEVbk+GdPo&w&#p{F>W0&d&?nmC>U&Q&|FHph4hxKoum;&=-9hF=J6g7GYJ zciJ@;$5X)RZXFWGQ^3DIrt&wRPdo)&t!O)OJOw;x%{g&A1-xlVR&9>)6mZ{6Q^fHU zaJ($v#qktyj61cpImT1KHKXhl$5X)hUdGqv7*7Fr+}TqcPXV9*eq9_-0iVv4O($n~ z3OGZ_(c*Xtc=+Wf;&=*pSM`EAIm1)HW#_m~+VIouumAf4T_25OF*OT=eBr zaePhtMJ*bqWI8hso&pZOa+!-;PXYJO8X>)T9$&}KGc}r44$cj~IYrFg13NDd+35DLM1A9Dc#!VF{?xA?f7@`c-SUlF|1b9b0yxT~;opU4 za0%}2?hYY9a0n8D1PunD}hXA1aUf7j0z##6x4?zIju*W)SR=e1LNULEuG;3?pp zTlS0NDc}W((*&98@f2|I*umm>3b??oN8)%2xYOI5b-Qsu(xMH$o zjmx}jJOvySoIlvigQtL}j+icvr+^O(_$H2LU5NiOMl&-H{-*^T;r&SE`PGFv^qB&F zG5watcnY}byj;p~JOy0%#t?Bl1$@ZsjyRqIo{^%Y@*GbA56`+p98Up1Z53H>c)?S^ z@fWue$5X(e-kcD}Q^1?&XVDvB@DyN*&Mhw51!3|YxD3=Zw~$~1sti#?uH*ubh>st_4=!sGv4mKuiBjJojXk2GWhW9 zf;*}tcZA7uWAV&YKE*csq$^zOkKPx1q%ZldW`dw%*IsXL(DRVjsS-hF|M>rX7qq)M z_m1o^AoZFG`yIF1Oult=+{W*#3*21PBv$?$(I39d5}anPyT^1lzo&rz;|1*Q31+9` z?mAzy4GQ^Sk&77ScOAcvb9B{b3V5o+TgT~m3b=LSsp5DFIP%wz;&=-9X{#DKX2(;& z8;)-j$5X(|f`5qPDc~ZNs_K{>PXT|dvsoNZ0biY$P{-+b3bDd4JJJH_!Va_1i>&^>iL1w1fEPjNg29HIXWaXbaQK3{3w zYsXW-RVJ?z$5X&v^G4P^bvy+;!mqJ7o&tWFVZS(@0`__D`pIZ`3V8B!*AF+rQ^0Qz zx_&Ymo<;6mh6=iOj;DZ+&srspr-0iYk5SNkpWrFr!&z&J<0;^IQ?`lYDd0n6ljz<# zo&pX`&{rH!0oRXzM;uQ9-``iVkokJ>6!3#63&imhaHnUXb&niR0jHSSNE}ZAr@FjP z98UpXik?LG$ng~LoOGSV@f7gT-KWLz6mYO#24C~_<0;_#(+7#;Dd3i4?uz3n;NYeu zbdMZQ0Y~q>P#jMIzi1X-_sH=SaNln&#PJlcf7Vmtcna93aE{{U>&H{TJL>%^j;DYl z-FYXDr+{;$tf714cnUaIsqNx;3OI&u65Tt;vo1VYoRUH5^X?pN-N;98UocEx1`6PXQmO9y6Vp2TuXluijA{PXX7hbV(df0e^4ro!-oY zr+_Dpnl6r~fG=$NB95nk50tBu!OVlFfXiRlDUPRr^V~?B(Oi$GfRpy?DUPRrJLUL8 z98Uqa+E*ZxnFmh+k4-sM98UpS7XLI1%Jp9v}gMUi_7jC`t_RHgQFI`&IXtsaUu_vQt+8X|S_n6yn z%}zD&OwV89o?4bDex7Z~S9iHzvgWj356>HTtMs%P)el$b+^)2D>}$DCI99YN6h7qj zZCzCJD(7&74*8$EIWz3-zy6Nbqgz)CRm*c}^1(QrKEEs%I&}C&HDmA1AGUMxb?zS1 z-Ta;c{*M>1yC;~Pj=Sr8&39zTM+Ng;8S>FPTiPV%^TeA+6PwQ!EwA&(AaT6E<=w8` z6~|Yyyu>P>q~_P-11zsGaEv&-(+SUJe#nvM<$5l*@P`zdtV&S zCTx5Q@8o74Je#mxCB}>6*@Ol7zZJ)`39AuMJ%yPE&n7Hs|83%UHevCXCQ50p$Fm75 zcciU2o&v64X@@wT0`7S~Nh&iBo&xUBytg>Mr~Q3vuP8Ir+^n7o+OT^fIHNBBaWwl(=;lt zO)#DU&N*?BIGzH|ohhO=yLbwCN6J>>cnY}gtJC6m7P%hPa%dBbr+{A`9WRcjfH%B< zCyxKKfWH*dCKyiv$7?cM{IK;D@Xp$ywAsZ|z%TAJ7ROV-)o&jb$5X%uM`qL}7*7E= z-7!oYPXXtd@kks`0XJw_R-0x#1-x$A5^+2QeDP)|orK{j;3Xexi{mNa%wso-<0;_$ ziK6Kw3{L^~Z{1QHKf`{1eye{>98Up{%a>6nWOxerbg0qd_}O-zOm zkl`udql>nP<0;^&n-l4T3{L^i^6f2-r+~xny(x~TfJ-`x>x2wX0nbUjNE}ZA`z4K_ zlQ2B%g3s{8;`pBy@bg4XWSlvSniu_vN*n|OIv=j;Tv&$ z8Oz(9^Gjsr!I!nXe}?7act6V|iuWWiK<@|lWdiPpaIa})+1LI_05%F&Y>~7A67slmm)ojFx zplVH5U+gry!~F>b0>2kJvu;SilYL4KnQ_3~W4fE)Q^5aA0smKi5|+Csn4ONh>wL{h z8uGz0?{CkVmo#Ia#}V|og%{RC@W%kx zk1xejz@_GOsc7cGQ@}lH+z`i8z?FBF@;BGxDd1-{=8NMg;9^C-isLEZ5zac5%shAs zIR5@!;&=);!G;t8=6XB@9DTqbaXgFMng{pA@f2{pz%rH1Ja`JYK)>bUcnWx3o0sBv z3i$fPfGTDlJOv#3)CO@p1w5%*ysGAUJO#YJT6b|g1)R!pO&m`FAIs*Wcj@CP;2c+{ zi{mNaiOar-<0;@lk7@;)uLn;7KS;h&98Uqy&L6e9xgJjeS3Mmpj;DZ~wf2hRDd2|7 zlhiQt;3?p+0X@a>6mZIPSHldgX+8BYP9IJmKnnIF%(&?v_- zar{pUxX=3vdV@f{)vtZbBpg3Btujv>&++5LlyAlH96!d}Ra$Qlz*n~~f|=IN6US2+ z6()z#8v*b&?e%wS1&iZrS$=!UF>!ot%eS4*zYYd>53>Bws;A=kx|ScT zR8embz}K__%b!2>(;Ecv!IsZmvqT)<%yJfgyk~RZ+B|qR2d>S7XLI1%Jp9v} zgMUi_kIHf3@gKjP+0}hc&Bc)?Ejjh#*N?@P9r^nH;I%|258atCD`xoq)#{voT|aV> zwtd1*^i6d!TCY#fPWxt#8hP5T(^+yKj{E-T+EN4leh=8)oOylIw=KNvZOxc{Hhj4d zspFO!e!2XPE*jftWZ{ybn+IL(W@k16_7P%9} zEbEi?r|rG<0;^honDFKDPZpozA9Zj z1svDuDq}nad^yrbU5}@LQ~B3c0pls)NN;zG<5}eTY)Yll#Z$l?-whSVQ@}kEJ{QMR zz!x`HRvF_d;L5c&isLEZitFR5bnz5$nJQhy@f2{G;=haIDd0!heN?)53i!9|v&8Wf z@S)*9#PKY0WugSBfbkS?yh^*p@f5J%)RZb+JO#X>;&5?11^ml~SK@dIxPG0ADqTDU z+_>FhaXbb5IZ-&3F8H9SN{;&=);{+b9XT|5PR=0-ztJOy0({dRFY1^lC59F;Df z0v_*ZD~_jtHrq8I-#Z$mh-V77RQ^21$KNH7OzzYWisDSYlu)o(vaXbaw zK6iYTE}jAo$lpU8PXRZqd|ez*0oSSQtJ1|&z#Hn!6US4)W4nY^(c&rKa;<~K@vIA1 ze(xcU|7ih7nekrb8>8ZMeWrlFhApSk#Z$oNL(de)Q@~NKy%fh&!21rCQvu^C;8d4Z zisLEZk~gEPbnz5$gt#5W@f7eMWiE*0Dd47~@@U<|Q^1|`PZGydz~3)^5XV!%-49n+ z0pls)O8qvA<0;@WrQ@n}@f2{e&7H*Y6mYn77sT-tu>XSG#Y5I?2c8065O$(Co&v5J z?wvTE0*-LBsup=X1$@`VXcg{ohb(V~ z56zHo`q2rKyF}VtDb=}Y*`BQQz7RLnfJw|_%r)EVJ zB%T8PvTCI`o&x@u`ja@G0^ZR-Pz8ynfcLGOEsm#v>r8tqj;DZ^FQ~4v#8bdkA8ixI zQ^37OCsRS&!(86b|Qfa6SjD2}IqTduF5g2Z>RrIjpToj9HX_M87!98UocN$01s z#8bfeb}bjjQ^2KHd8r`r6!68FZN>2vaDmxp#qkvIg^xK^mUtGqWxtOV$5X&pp1u^v zQ^2(fRaIHyDd3fFw~ON`;5W5XsUYzbaKX@hUBziV1>7?J@8Wn0INhXtDoZ>CJRs{d zaXbaQFU>b`JO$kNYkid^o<(k6{e9wi3i$c?R4Pb31)L!C5OF*O{2}ETaXbawc~lOS zC7uHIA2>lAPXSMj{6QQ~0eeMu-HXLjzcnUa2rxGelJOv!~?M!j}p9P%%vpAjtzLB`5 z%5tOi6!7c^Tg34caNFgHRhD=PxN4Wa;&=-9t>dmZo&uheuC&S$PXWKmwoDvP0VgRJ zCCJ1Ko&uiNr>!`i0=}{StT>(mZg@0zT{91!0#3JQk~p4qp;hMP;`pByaGeh~RFGen zOws2m_DPMKJGHNQFg#_P;bB*CJZ0=N;g~p{GLC*TsY(%F-OisWUUzXkWt=VBRdGCJ zywSUeN)b;P2X&h#j;D;PuM1t=eEoRJc>9k=;(vPk_1s%?KpamQmkx}jg2Yq6-rci_ z;~Us{u9X-qj;DavZ+Rw;r+}Mgs-WX#JO!L=$qI2i1?-F*MaRg0di%Ieuh!yt3b@mm z6E1E&1i7i)_o;_&kkn&GbcFuBleUUnYBJ8PG_gm=eO&V1{v~qRCQgN(fH@m%A}r{G;_0aOb!$#PJmH?NB9D zLU;<;5p}URo&vrX5J|;@r+}lqZzGOpk^6P)IdMD%+}fF6C4{Gd+vJ-mj;DYRKXR&g z@D%Xz(T&CN6mYrc2gUIeaEsCDR6KYJxKpp8;&=);U&|-rcnWw>%L*zXJO$kHz$$S( zi`?j6y;MAS3fTWr8*zL;Hd`sqiQ_5Yn3oHvc%0T#z)=d%6~|M+oiBY8$5X)l=LM>G z@D%W<6Rvx5cnWy-3fDb3JO%t>zU!VGo&t{E`i2S#&muSStm~c}o&sK5(sfS`PXVv& z6tLIpu=PwN;3?oNgPMusDd3B%j)~(b;Q1Ld)Hn0sDd5EC2aDq=;Oaf@h~p{X z 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 C_f~fR diff --git a/src/discof/tower/fixtures/voters-398915636.bin b/src/discof/tower/fixtures/voters-398915636.bin deleted file mode 100644 index 580d2d49bc066ec1116c1983f84a4aa77f635e6b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 384200 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

9Ln(hE%R6!6H>-Nf+}aE=}~#PJmH z*bRC0!V^3N95dJ~aXba=T=`2JPXW)VRL|cml6VTZPq97XcnWxN@I-on37!Hzu(G!} zo&vrYe9r+{+?pDB)~fZNskDUPRrN7k%e(ER-I6!4`OJH_!7FpEFl zvpH~W9z2@^*XF^qIdE+r{_4%a-=%1cYk@TBrbe?2Xi>w$ld z>MI*&Ia&7ARlgk@Mn-9qI^>?rKhyjXp?J2Rey1vzj??C1{icULocDiGdF|~I|D}N4 z&3P|V+YVbE&Aa|_SmG5;Gv_=IuEOiY2|Gvd?cCeZt7V?}(cFDZck_D+_+JI=^-sJ5 zcJ~Elr{nH9t>8h6Ba2+-im5W2#WGICBbm&53OG`kVd8iSc+04#;&=-9kZ*}B=EvhH z;6@Xdh~p{Xitj>ZHP7QI;G_Esh~p{X7po_U<0;^{Tfd6qDd1gSYh^R@<0;^lQ}>GF zS>&?Ra=j`WPXTvWGAz592TuW)c<@>rPXQ;ITsen%9!~+k&bwJ0PXTY76(^^89!~*R z^zSZ?r+^D&SR{_`5zPGPpuc&@zFHek0awlQK+oeT;HeXf1^jSH zNNs}g6!6`*&BXB(aEc{o#PJmHjYv7P3C2^vKQgux$5X)jH}n+8Q^0NeUlYeuz`1JY z)#eya0e7l0M;uQ9-|!2eO)#EC?oPYL;&=);-K|67cnbK}$5j62^NFW`s}*f0j;DYJ ztvM%-r+_yt$*RpUo&xThX^J?W0*;sEyEvW#j&Y~9Hph4hxMq}{;&=);-^=*g9OEhA zjyrpb<0;_t->-|~Dd5wYvgzauPXT8rIa(Y~0S~|YL>x~6@2XxnY&g`__u%Dc~--V(8=yPXX5p>?Dq-fEP`^B95nkOOMK>lQTT)Lg2Tu;`rYd zuvg*mY0NL;p&wSKHt#9ma--{t<0;_d9oLHEDd5?&Li(8>kEejUZmTDbr+`E4+9!^u zfbVQfme$OJr+`yk86u9SfQ!C-DvqydzonY&=StF!3&*SUZ zd4@)ADUPRrN3J_Aj;DabHcXwt%!8+Zd+r%3j;DZs`aTuMQ@|6)70+nq!BfDUA1o2a zQ^3V8htXn=r+^0yZYGYWfLZ+Up3Q-4^WfPWxHb>H?XQ0pf6wONuh|^@T?%+chv`3t zY`H&U`^8Zf%dtm3~AsgKu=F>NRh6m{$>`(pb z@wW~4+AZI>_50*ZC6;{Xax>S)|BJo90FE+g;O@~GT!Op1yF&;N9D;-(L4!kZ7I$}- z#ogWA-CY)07I$~ooBmCoeRpqBHTPEi_y6tIH>r9nZ=dtflOvchn2JO!Mf<#};D1^lFXdfn5)Q^0E*jS$CEz`rhj zE{>;wYnH2`dtG=IxzABIi{mNag8qqhPYX{0NAJ*298UqCi+o=kPXTvYQ6|v*zTqk0 zwUL*J<0;@Q%OmUF6`lefJGGTKo&p{@=(ISV0$x2Rhwf?NDd5+?j~BzaA+6!4><3F3GP_XFp;3?qO-Is~uDd5Io!!$D2 z<0;^$f%V1l6mYY$yT$PoaK&WF8k>3W6!7Io{l)PVaN)i8#qktyj~aeW%shAsIMm>k z;&=);*SP3S&GmQ+c-Hz3;&=);d&Z06cnUZuIDfF22TuV{9Wh-TPXQko@J$@gx{&^5 zjAmvY{7(xw!uyfR^Q#MU=raZUV)`wO@f2{=dAXF~cnY}ijUnQA3iy!M9dSGbJR?O( zc}3OG`e-3>pS=ydIP z>h)JMXT05eU$r^cJ9n74W$@wI1$R_Q?g*3R#^RZ)e2Q)MNmsbmAH6U3NMG_@%>+Tk zuD#ygpywg4Qze4V{_+3)Drk3e?j6}-K$z}m>o|6Z#ceD98Uo+3;rRFr+|x8s;XmlJO%u*&Sr5u1$=d0 zLLH~$Dd1Wq`-tONz&c?6!7Hdt~WQqQ^0Qzy51QL&m#9OLj~PC$5X(^XRQ*)Q^0MH$0%riPVf}) z;jA^q@f7g9Dci*H6!4+3Np$ZVPXPxe=qrw=fa}M$%L zxYM)Hx<`(ufKyCuB#x(mQ(fLCj;DYxMNgu84U`a6mZKicg67(aB$NSx<`(ufTQ+sGoXj;DaXxA#tO=D}0I6Gu%K$5X%;HhmGtQ@{tx)yZJy z!BfEHFYFY@Q^0v{B+h8A$5X&b`}GvZQ^1{a{2`8~fLrY=kjc!0r+~+%oGOl|fWwdd zD2}IqPu!@Xd+>M)xZJrd;&=-9VW#+6CM5v zrGN{!-g*1w@wt~St!gyeKkC?%(K2lf|Gs<7?YCy98hEDXFL6&TOB6rPw&bh3+%H*k z+OLP_4ZKx)+KlRlD|BvG+B^2O+$S6>+7t>O^8B_gs(F@kI6{Z~&)u9E_V!wL{mWXMMa z^HUk}(K}n(B0^Yf5fjFK;&Odo5ZGQ26?fjP;H5SKH!28x6 z7ROV-UxKq~6O5;T7ag7?j;DY-)OsV1r-0KmDz8m2o&wG}agjKl0?wT&qBgsD3V27# zR^oUHxbCad;&>Lh9@TPa6O5;TUmhJVj;DY(yniQ-|FeL<6w)RbPXWhkGF$wx^%U^V z+M%@B#Z$m9?lcz1Q^3`49~Q?`zz0WW)Fv2D0XN+-OdL-E=b74XeV0nhU7 zEsm#v!|%N*j;DZ2I*RLr3{L^iNxeuMPXYTSji8e-JnKUF;fclZKfMS1JW0&sr_V z!S+K+X1?G5{MBgFD*bp;>SVgcC)QS6(RlXhlcUcc9~6IAufrpjPQB7QXOh(`n;uFz zf8Vd(z1CIE*80Z4IN4W3{96ILo3r7CaXDKx8*w72TGQ1RJI(HJe?o!4?}g5+8&dFO zpOQmn9B}uT?&kLt@c#<b4~}_%d)B<98T&krpwBJrqL}>b zF^%!u1I`#TV<~ezo_oOQmktxhx3=>vN%%+{&pqJPtxA_Wp2E*W)SRnjNy1H}l{r;AoA; ziQ_5Yi;doh<0;@yo2phY^Wa(J=0{y8j;DY>2Dsk76i)${n%AYGnFmh+_o#7098UpP z-dW1uT#u)KpVgQzj;DZ&75OTTr+`N|>r^uH;3?qv`*(@sDc}SfQUsXm@f2|M0fWTx zEOKie+!x1F!0`ghR5tVADc}PAmW$&l;B{?YisLEZ>lXv6n0fFNaOhJT#PJmHq-yc1 zn(Oft@cwGu#qktyD#tZ(JOz9#n~z?lkEeihT%9hCr+_Ce`y!5~fCoLQ6==R5JO%t9 z`9^U(1w1={)avGXJOy0!bg(#{0(REgD~_jt8!k^$!_0%HfWrp#6vtD*DbrmQ$5X(Q z-xkrU_wf|))syqZ@f2|QTH$M%$Hh~?BL=q+$5X)dC!G|>Q@~d?Wv^}K!BfDm*Nzs) zQ^1#(y%fh&z)?@S{=H;81$^S*#yVzxJnKS-9LL1*KP})s?nraj<03;?J38^ z@wF}Ab~=-<`MU9SET8dxgg8FP@w4;XDV*Ketcugj|~|vj&EZ5*+-AX@l7p%{?tz|5Wojp zK6lL$aeOn&S^V*y&4Fw4;Mp9wHV>Z7fot>dPj3$XEd@L($A!m#{B~wn_c=8eN1n9g z)Qev~7F%}Y>-&S(5}iDBXTq$Q;rmysbN+Sx$VJ-r2|Lj@)x~JNK0Q0_n>lLaX}eBm z$$dEP`=e`14fy*#V0Uxo^-bTl@UpixWA@qbvWYdo&vrc>7%a4Q^2YGYpa0q z6mX=syT$P=a(yci#qkvIqwGE^T|5Q+TlQJvcnbK?@E_uM7P&G}f>gkG3OHV+-Qsu( z*l%h|l`ftFUQuzlIGzIjWy33RJOy08PDPb2o&s*%Zm~F?0{)yRoJtq}X94eRCXOF! zH+fyR9v6>qJq7$`Y&MlHo<;7^+p*$!3b=p6*W!2zxbOICDqTDUJbu$GaXbaQHibi_ zi>H9s#BU^yr-0wgJSdK*fJ4npuhPX+z+R|IUxa@f2|5s(vb6JOz9q)iQBB z1ss1(1eGqH0zPx2p*WrbF8zMHIGzIj(Jzil7f%6?ceEA9Q@|Tb92dt^z#r3RRO#X= z;3#i~iQ_5Y&zql#<0;^U0|HdQcna9xYoj=x0&br>zDgHQ0SDyoA&#ej8&@lVDd4eP!m4QT6mYrL!QyzVFd z#PJkx(@}Y}?%^rm&iN;a<0;_p7e9#ODd6shtE+(V6mX?}o5k@IaGBC^Rl0ZzxY*`S z;&=);T)GS5cna8mLGI!qYqkSV0WSzUQ5;VJ*9`Yg98Up9I9gSUJe~q(@yB~M2d>S7 zXLI1%Ja{$-uFb@R(fBE zn`*$MRigUwb1%g#fpZ@c;?WV9re>k3_Z2x$jc48{k?$Q&H1Qw zqHI3SWqB**Tij<}k&C|-n^>hl%1NU>3>$H=QST)~%Da0^ck_D+_&;92?!Lk7blhF1 zl|5uJWs%F6X|xLRZQ3mQOaVuK_grH<1)Qg5MHM8T0{*gUr8u4f{+RlcIGzID(LYcH ziKl?~t(+~6r-18Bdn=BofR`_*uCl~az*Qe@6US4)y+$WfLE>5Du2vZ!j;Da*OnfMg zr+{0oub_g&cd@0FEMT2Do&xrp|5hAN0S`&%r?SLT!1;D97spe;rB`{WAn_FN#hGoz z@f2`@*=NP^6!3+QIaQW;7P)1=j}^yLz*nBW6vtD*wF*^LS>h?+m2bC;<0;@bwNt4e z@f2{u(0yISX*~tpGXC%4cnUb(qojpZ1-viKH*q`#-1uvKl_j1AX54wWUI0`?y`K^#v3PmTOR98UpzMRwhb z#Z$n0HtbMw;wj*Jw-c!#@f2|Lojt_y6!3C9QN%@ar~bJod2^po&vs+xTeZ-qxBT<><3%K@f2{|<%w06cnY{` zm%ie73iz$#t~j0oo|CS$$`Vfjzsj~u98Uo!DHkQk#0;JSp4X?XIGzH&vHq+$o&s)o zGwehUzbeL=PLG_8aH=pU-Mvi$~eQruHtyg z*k{5qaXe)l{bo{?BEGtvKU2K!;&{q9Tehp>c*=OAcM+8$o-z*VHcuQ+8CPEyy14oJ z@s#oQAC1KS^!Dqyx8{I2o-!^S7)u3-r+~e?XA{Rau=89iF}^yc8-Qou1Pgz?FlZ~da2F%FhK-S*d3gBmyOSazuY@lX?6t}H!!(9$90pQP-Z z;gW=lb=YJD`D919X8z={PzNOH>W%A?lIl{dQSoW zuYmuPuYz{>4Q8j~?mF#ELN)^|a()5(RY)2A8|pI!TqIFy6%U>Qj@DyRXlhK`1t6?;&=+U-1CFtcnY}1=yWO` zJO$jT*HCdh1)Q(t6LCBRJg8*_l@Oi+Zh2spIG#mr^sinj9y|r?f2oZ)z8{;d6z9b8 z6mZPT1ynpv>nY$Uh3AUnDd5hRzKP>0;QsRhRXlhKc+?5kJvlrDJbQ)fo*bS6elg#5 zPYzE3M{j*Yg@k938+q1sPYzE3uPy1iCx@qiS9WsUlfzTMO{Y{;3E?T=y`k5L<0;^H z8Dpt<@Dyg;&=+!>vh+uxuzNrnx@f2|32uH;66mWroSsIyn@Dy;8l;g$mbM4RndD>pUs;8_=9<_{Lf|FnP?#+k1| zdUdF@K3BE>k-v`npfR4(Eik#Pj!E&9?u9Ee#qpGGw`iZl@s#ewG_HGYcuMz8?p-=Q z#Z$W8zogJ{D4xDL`fOo!9Ezua z?>}lRKGJ#$xY+9b;&=*pRf<$P4#iWz+sF48$5X(|qumzAQ@~f-71J>(zN!5>*R`J~ zj;DY-L=UA!9!~+Y_~SjB1J~xkvpH~W9z2@^*XH4$-W>c}3iv^uik}W-tNyydq&(sK z`b}$7C(H8{5ogBVnYd%=oB2L<%;9}4XyLshcMk8a+UUfpu%Z1EXWH7=%fIKXf-!QZ zKDF^{_LVuk#x3dn_X2h|=l2JXEA05C>4TV~FI9UTcA-y`{&`}CN^s+Gyq!Vw%5@J_ z(%oaaoByW;944lHL&#(yOJ>#EYB)5YYW{|=TqMJcVJBSuC z2$RYVqMAW!JBVTiY3#tu{F=0O5ZMgU*+C>T;GYeSXa*UqiC_j9?I64vWU_;BA%n1) z?I3K(AWRlJ2xBhGY6oVK3Z2akLYYB!J8*^!oH71lLdFhuHtxQ`>~!2+=W8Yn`CyTo zHZFHAGfsE$kDTT+1zc?FNO3#`T%yt)aXba=+sZq)`So}TILY&I;&=-9)ua33cnbL0 z$n<&4Ja`KDP`l3JcnY|}xwGPU3V3y=EP2g5cnbK|-r?eS7P&!v9*E;9;Igqx1o0f!nqr;wQ+PXYU%{wj{AfWOqPRoGmQ zXOYVgaich%0&cc8S`l+So&xsqYbB1SfNRZNCXT0of9weBYv#dIz*F-#6vtD*F*5BE z$5X&%>Lk={GCT#muz6>3JO%u4?J05m1iNWI6r8SD$d}{5Q@~3y^%2KYz>nfw630`( zE1G90Zsx&Lz!z6`7spe;#TJ|p$5X(Uk0jM?GCT$RyhaCcJOzBT{V{Po1-vq9%97^m z!BfC%uk{kgQ^092{w|KEfX`LUqX@)Pz+R`uisLEZvelo8<0;^d?Mf>G@f2{zsq@6~ z6!4lB-^B5Y?Dwn8u$qcPJnO{tDoe!itP4d7ZWqV1==|-ypkL%Uk;(jK4JhALpDEz+ z3*%=t98Uo!Zq!H|PXYguZH+jd0^VOfbQUuYo&t^^CrBJm0Vi&|MI287FP#x1tC+UUnFmh+*Xz|m98UpfUFdqdH9Q3zCQ8ceW*$5R9BNEg zaXbYabo#V7o&w$$EnN;X51s;!kh`}yo&v6u`l2|#sr}(dd_9vEb36rHe$fDNJO#|+ zkN0d2T$=~a=D@Xi@N5oTn}>gTb8xjtLX$|2P8&uvH=qAceROL%xj^!EzpWYEv}gAR zONv+ZoxOQ$@ZB))0~|^Im|k+xx|!kj7fcm*gKw8J`4@yaocWP=*vO#+Gq29H^+=K_ zebeq+4+4yxZWXOagy)?W+zq5S5# z?jF?CbX0Xs=sQ@~Er))cUlv^53nByCLrJ4stpz)sTE6!3rY?*Y5} z2D8&~cb#v@Z-Dt=@#}rRbaFH9wcvsIDfqLDmp@vP+>G&*alcy;Qy7k?j8o=pE{>;+ zHyk=5j;D+(dZ$lm=D}0O?JH{+lv#${YsS{%cnY{`+B4#K3OMD-oZ7_WDd0ID#);!8;A9tHiQ_5Yq4NT? zS;ter^RBKD$5X(Ln31(9$NyQt=^BgUDd513JH;sO09Oc(yI+?>$z^PU&6vtD*Lw*aVlQ}%=LgV0s;`pE51NPsX-K^J+ zi*K%ICsxJ&;W->(q&S{3zMbKwIG!@@kRzwr1UT@Nap&N{;&{q9^6*>Yc*^+Ytm0;8 z;lNYI!&WR3$5Y0co<}s>5(l0#9+bAFIG!^8rTuYnJY_s5O=h##ao{QAxW`9{<0<2W z+nz7tMb~oUcam~j?t=uD3 zripc`U7dS6W6!@Au)8^T)gE~CbEt2H>L=X&Don-$#TqW0GvIOLR9?ld6g!`~%lFgn z9@E|Yo&x^gtDygr1?=t{%udJMb-reW4f)`hw}w|*b1L8%8vUS``Aq4K4>Lp@Pw8fk zcwZb(>86-d(i}B7@RV+x*bBw+ly1V7q0Bjj15fE@?-?MDr*tnjTO^LBba!-&XbwLd zcuIH3wpQYJ7PF&u&WhtH-E*<>m=hHTp3+@bZJIcq(k;>Phd7?nEnU2!Ii_*oDcvQH z4~XL_-CBNW%^8maPwB>aJVYE%=}vxsUmQ>ACc0PF92`0Dly0;K%f<05X2Xj_F{e%r zJf-`oe{*p>rCayNadAAQ+dW1$bCl)4Q@VSPOcKXay0LP67spe&xsTV^*7cpbzkuc^nrJKzE7jZnLTWsfEaXh6P{brnS=GWsXU7rqZ z#qpHx;C^St@sw`GX1T+gdGM6(FY_jg<0;+er#_0~Dcu3xYeX>f;3?fn12>D~Dczeb zBSkdV<0;)*Ra=PTDcuiqkBZ|d-KNdbn^%)L@RaV7Js~FbKu%Mcs2*F&4Xui;MzR=)0=~TO94j- z9pmcbyv{6_awW;RcHgtxVMexW(L2ZblsT`?>vwr_qSqn*ezUBRlPxRvA$QBdgI^V| zbhvZmtNy)e>|0s?Ro9X8#z%`Y+AIuzy$9@W&JtO#ZVQ-G`q#5v0`olEpE&a9u5o&0 zeHc4x^3gRDReUz#qr1m+H~-HIxM<|v+FqJHg|nC)n2n0FxE+|?kF$gwn5~nuq#c<3 zn6s1}m`$Fuv>lioqO*)0m~E%CtR0vKbNbnV*$6w!*@4+zJImXF*|Iw;*n!#aJ1g3O zi3q2^9hd@fRG#Fs1G5e}tJ{HD z+?+M+z^s_gns#88TW2jhFl)H8wjG%7zq5`VnB9ak$PUcT-dWcULYYB5J8+tT?_W&F z*ujp+-8YyWkh|-A&7>h8EPktzoeG#1>z~=n3H#k<0<1kQ<7yfkBg^_k38)wj%V>}cq;&{q9*I?H@ zLOf+0vuXEyW*$6cyfV)faXe+*v15V!=6XD3{Pq2KaXe*QH_B6SJY^htLCFGU9z13I zBg<@YJY_tp_ZM+IW$g7!EpIaqo-z(sa+^4wGL8~0X+d*6ezyHN-*vyAIDU@hOMBfD z$5Y0omzVJ|^WZ7tggcgt<0<2t*P|9P*W)SU%?;X#<0<1Uv(Jm;StoKF$XnRVgJ)eR zYB`=or^cjX&CT`tc=EA_ju$R2Pp;1taH_dyG{#fF5f-FOZhj%20>1sIvpAjtj+FYG zIGzGd>78503wR1Rzu#nWJO!L&Mp8aM&%Y9uOPXV8OSxCnVcnUbo_8H=M3ix=Iui|(LIQ^X3Y0TGyr+`-_bG?8F zPXT)sPNYQ}PXRCX?IDh*fLY}6o=t#jli=9|xHbu%O@M2Y@PBF(@NX&LISKdd8eQYS zkx9=Y|FYV9*0lX83dcLvwPf2RhZ|=O8&Ep)r3d4BCV3j<41Bh5TC*J^9S3(LTAw0C zm9G~lio4$N3Qy^7{+g$X zxgJmHwkR@P98c*cYvQPCuE$flj?hiS@sw`Ug@?uQly3L18LF9i@RV+!MZ?AMlS>#roj#1rQkMCoDekPQ5{VOzhN_X?9v$`Hn>BhdE zyM~zuPw6h3I71vy>E8Stx~91vPwD3V0&*ucz#r*yZc z943yZbklr%D2}Ie&m1b>(9DCMZGZmT&sigmr*z}ykKM>zkEe9Ewdx{{r*xC|xgw6I zbcg=#)7Z>|r*tROnk|l}bbCy7HZj-ZDcxLq8jIs8-O$?(iQ`!pYNY)`{7-M+Z{lx% z4>(cWoTbf!1s%Fr%6z7P_f8osj;Da<^|~yMr+_oh%Us6%dOQW3amx^KJOx~4+aqy2 z1zdD>`LbpnJOvzhdbK#70xthBx}UioPXUjL(M}vs0neO&Rvb?OugRFJoS6qt0l)e@ zUK~#W$Di_A98Uq4C=pQJ%!8+ZYtLCDj;DY>ypC4ET#u)KyJ%qbM?f zJ6lR7OSX3Blhl`EY)sU!Z`4OsLWMk!{q-KOyE(H|os_KC;7X@sUrtf*Wu4ZO>fZ1A zdUV+=L$8lp+I!CU_iNofrn~unTENY2w#y&SJmZ=J-WIp)z}y3Fb=wY-gbdo;v4g~B zaMunJg$&x>vx9_YaNiCRn85=(h#xZe<)Iyz2W$7p4&sIkIy|<6I3a`1PwXJJ89cRv zSRsS1&+H&($e`zQJBSf7=>5VDqK6FnytIR8A%lLe>>z5$p#N(-h!Qdw^u`XnLIy+M z+Ck)y!SHu>5XlVQ+d;&T!H5rb5FunR@}nJu4;hU4WC!6w2ID^4L0B{RVh3SD2IIfl zf!S&O`ppi^kIBUEcHj&dIB)#Lgp3{RblrV}+4;J=&eu#D@)2OZj*yS<&woj8J|C@~ zRG%wbULxTkjqz11Z}%;E26H{0P13pFJBs7kB$Y^hNF2{5snC-I88wgfY?87r=^&10 zlXN%K1#vu^q#J?xGMRbsY?987n<|cHljN28vpAkj(u3ZD&nBs5)E?q^Hc9ONiq+K{=CssyLn#lsh@U zisLy!3Eie%ZZkig6O`4V4~XO0B>ApSr+cw@PEgiA8YzzF1mzE}*W&mt_WQDFcA)Oj z;yFR_DY8`@-_2g%@ajo%JSQkMC%+KKbAqyXNTqz{zYEU^N|vVU#qpe=#POV-^W<)RAb9fjJh^+G+|ADePu`v<_kZTeecQj}ovO_+QU1N92+%~X#_`Ykp)+qU-?XwkcHvfM4+0wEv{wTJnU95Ly z3Xg0$CDnurrxvc>ko2urpT8HdyE#WBi*jh+=JfXpmyS?qe&yIJvJ{D4yWrCT1zH!p zo@#%mXDQu1rn~u*hmM}k{0!^E1h4t?EWcJe0A5zT-%c{T;? z^ zlLJq|JlwZO98bY)xSz&+j~#dlW>Ej(;&=*XSn6lucnYSGUq$nSy1StdEq@oT54K6iiI-QQ~+C=5Do%;&=+?{GNQ~ zXwHEjqn}ELA1nGz98d9FDPG^4_c`zsPn#qA#PO~_4Iu}1A5)maLI-|= zecZN}`-$TxS{{G-EphxL%hNsfH7Ae`{AA0$V@wyvPqF+>`nTfvsg_^bUBMh{I`Gph zKV53EIDWe26{m!bWv<81uzcKzhT`~{miMT?PaHqXa^Eaz%|WXJKil$h;f9Oj=UBdA z)H8AXT+7q#t7J}Z9r$^cM?SM&96#UkGjHR@HP_=8SU#azH*x$z%i~Y@LmdC-J>ac{ zb$E?uooKOkj(9HXSr>9A*(m;}|JglYo{T+D)}AM8^Fzv$@&Bz)*8i4QrJC=B=fm?m z@c-8DK-mJNhh|B)s_T@^`<8DkIrPd6@2q=H6f1o2)A!*M-*s#J+x$rVGv$eTtY@9M zw;!#_8UCf$jBvwN%n6^TL4W5T%e}^5J9G5i<^&-N*kA7fyPGrPobugX#cMJ1$Jbkn zM*J2~;Pci{hdN&gm;Q9#Fv}yH&6&;JW4fFFr}u!Jp1%kDpZR;h?!Lk7fZSb|&i*N) zVEiMR`}hC*pkUri>L-Y&U@m@oD2}IK3RNzt0>)D?6Kc#7$5Sx%r+pH~Q!u~y)luo< zDVSJAcZ=gGm=p0*X|IT3398bYCjT%vF{7{Mt|)Pr=N)y-OTV!T6m^qSD1v zFdOFg630_8`&-@;$5Sx9i@M%422a7Hy0AoLjHh7shxJnF;whNZ(c6mSDVPE|&xzwH zm{XlyWsIj_Qmh}T0>)D?H!r*p$Mfg+rRDga{(a5={c-jDzyI*>z@I14`bh}+5Ai%H zyPmZF|9w*Sl*)hjPl4x4_xuiceh2+Hb13fj5e4$P~dog3`Hyb9X6(GJY3pq-oSz*LlTvmKaMK|8nDL3}gV zY6s@QoZIZc9Bn(d+krVRckZwQbJ*_OX$R&6-?_^U%xwYZZaXmd6r6kPz}%>C?zIDR z_rtl*4$Q3+=YBgddkyCSJ1|>W=RrHrJ>WxjU^dsz!**bH?9L;0U^2jY)DBETIFH$Z z2@~gWJ2185JYfeWm7FK-!1R~%lpUC;bDp*XQ-sbne=#9r2L4I}a%(imz^YsZ*WB@ii>3I%|tKzNY14`zKQA;%iyn;Y4q7d~M6q zdEFGp*Ri~C178&|KFIPt3FnIA>stQqKq&n`d_BucZf&Ulhp%t>(jNQt|L_eg4_`Kg zN*C|?)gj-P8vVrajqLToweN`I8(Uttc1aa5zKP|II7`IwO)Y=lCz47RA8dKt4XwoS z%`8uy__R2lS1F&XkW;0L=N|vDP7}rP+~ePw|ARQ5d;C#y)^wGz_1xnhK5nr%o_qYI zc5V{Kx3ypAq{(l@@xNGJB2xvGF`j$;3pTG6$8(SW;E=c~U_AHuw>R!7j^`f#tWmCF z#&eH<=H1dNV?6ixD`j3Kj^`eKuDG#Pzl{8e-R5XW2>LEP$_!EzrFOKhJ=dU|3yzUL-xyPUQOAB#4_xPjiJMOyPdhYRepO{VejPbF za7P@^37LPDGP-As=Y(wgx|QO1PRK_4#L&HAJSSu+j&&5r6JvXq|1OT_gzQM;bhRX9_>|Pb`Tlh13 zd;PtD-OcIFyL(J`zy9Y1{D0>=Vf`onH6(Z6U|#L!?mA!l=YT>=Q)GArgF+3-pwAT2 zhWt-7##2aR#`;w>*W)Rq6{(kr<0+(qQzQDD>+uxQs>e;m@f6a!ZX3k$6jE^0h?UGd zcnaxbyH?_O3aQVA)8cpvseR4d0cIXNg%l&n6mdL-6y5)uIG#e9Hl$%?GY_6Zx|9E~ zIG#cpa6Lm6b3L9yieGqyIG#c}ckQV-ooYLECNj;D|cIx1B+^WZ6@ zM4y(3<0+)esUy@d*W)Rq`QKZJ<0+)y?w%CKQ%LW&=csAs!Ba?$Q~WB9r;vh2zZJ(* zNV9iU*I^N!LYi`ajyRq|3JwTW+dM9wLfTili8!7@8hHA!IG#dkel%kpGY_8E5G))! zR2#FTIG)!KBnnt4 zj-P5vdY}I%aXhaf$X7C;p80z4yoO+Tua)9>UPF+6SLFKUdi+c~|FV58#PPg_U{23t z;`rJ2`tU`vHZb$xc@4qL9pl9DbM5sDF25DW&$B#bJpYDf9z3rh*syY~_@CZhU%)F| zBXd1|A-sLCIG)!KEQoyA#jWQx1Sb}!Z>-0)o^|17tU}^={_4%&-U;hM{eY6@7mrEy zzJ&Q))qW8*zAqHVSF=1qn2+N4K+DIJDzEcXJjbVFsx21Bb9~yZS;W$MT%9Q<2KeR!VN zm43u*-Nh@>nvF4+Cn)!((976wQ})~aU}E#2ACZ$z3>6_u;(h^p%Jw-q%aQTj)v5a@ zl};aeeBk$K!LcX5s&%64&W;~$Zy)&g0(Lj&A4>+k-H^6i^Zq}s=kFdZZi@jc23D9@ zbmG|xksnnm-EL1mcaQ0Aeoq1a$NxQGci&)kI_|FXH7jPw2a8;;2>G?yIhFNtal1pRfjFK5etKe)IGzGd ze>s6RLwE}KdXiq^cnY}J{#)XB7P&gZN@_ENr-08LT_%pFfE(P6rp*wZ0$$Olqd1-d zexLG^IGzG__Rp`)5S{`KN-<3wPXYTz`y!5~fU|uK(k2N{0SABHEsm#vqgP3x%@CeN zZr-)t;&=*pe)3!5cna9NMG2jh;3;6A`r-0*S{Y4y40dFjHR2)wM zH@TBeCnb0ac+IHc;&=-9$)IQAcnUakodBJb;3?p^+t-QXDc}rQW9vi&PXUh&>LQM( zfOq%ZBaWwlw>(avlM*}yob=W}aXbZFDfodno&pXP?5C3wJO!LA`bu#;1-!C|mrhFX z6mW;ct;F#Zu=Ck*aXbaQqf17eh~O#U39|=^<0;_Y%dUyzDd6Fm^65kbPXTwEIaVA` z0sCBhDvqas8>}g(lM*}yT(a|WaXbZ_$S0akMDP@_)6v0ovSK|2+@|kEaXbZlc18i6 zh~O#U0~2P5<0;^6^M8or7ue0E&)^0+DZx{~)7I=4$5X&@_ovl~2%ZA=+cH!f&$_TB z^-OX6PYZZh*Y-YU5!fD-K%Xh#)X5HNjHiI#6pX9a6yYi0M3aNX@f7fm7kk9<6mYp6 z$qSpu#Z$oZV)YltQ^3v_*TwM^aQrVtikNxu6!7L_v&Hcg@acTt#qkud*O(w*GY_5u zK3!~#IGzGdSn0Xzdh03R)L;CHntAXPaF?fx#qkvIh9Y5$nd|Ws@WQGM#qkty{8zih z@f7f_FNw97<0;@PTe^$mDPR_Vyk~RZ+B|qR2d>S7XLI1%Jp9v}gMUi_&-|V`>XWt+ z`@a1e`0?8MbuE&N*mE{Ttpt&_cQ~K;%lb<3E|gB6w$-;1IYX@~nPpQe- zDzE=B`D~hR?jFr*tGnFmh+|GsLDIGzF?8|J$>o&qk@yHt2H51s<9 z*J8Rjo&xUD<-0hZ0xq?yK?E}oo&v7c=#V&`MJ{x@j1kTCcnWw)(=pY?DhDDIGzGd@<%Z*GY_5uj&Xf~ zIGzFyl`EY2v*5t9$gPjsSR79Q7Y;lmj;Daz&dCtf%!8+Z%La}U$5X)LHhmPwQ@}NA zREuWj!BfDATCEbtQ@{^;M2T*$$5X)d8vi1Wr+`OPKQE4_fKz147sJehXOSE8>lE=n zy)Bjb5k8CKDc~e|gJPO_@Dy-^c?-nx6mUPUaIwtwcnUaExaQ(`3b^o?;w zd#u?@9^*t}ZY z6vtC++aDLv9~V5ucB7)}w-ZmX)edlkF^`9**v>|&FOH|!Sgi1##mu$Xc@{I*V&_@R zT#Ma5y_o%5ip@NCcs~B0`yD8|)2l;=OP8CR+j=VSRQTm*_DmTv;BBTawcA{%@P4&l zweRCYKOEk6YK0+n0(TYee!g3$2aB(bI9w}f>)8umefgBRW%$+E7Ie-WvP}Q=?*Y4; z)34W*OK&PnY5u(4)v7O(OqoCI%;w1JlEhea_TA1Vj*Zw(S^ATrlQ6;rCh~p{go^wBm<04&%n`>^($3+ba+&M#l=Rd) zjl}V6KB`yQD~_k6r@l{_+suQfq-*&M7ROW4K5w3h<0UE zT~O?-IGzG7n=nsaGY_6k!{Q#3#PQq%zWwrpIGzGtnzVL4GY_5uF8g_&IGzG-^KhRy zo&rvICry4c51sWMg>0)8E*VgWM`o&rwVbDB7w0!|(DSsYIRhdEf& z+suQffLDFmB95nkcjSy!&|HtFfajKMC61?nV_Z2Xj;DanHb|!1j(7@q`-UFkcnY}w z)GOk63V3@nZ{2ppQ@|Iq&JxE{z)wSm((Oe&1w3s+6LCBRT=BpWaXbaQ^+G1yUc^(t zu|AC!$5X(5yb4Kh zMTq)3Pxg+t27|{tDyhmuYz{>4Q8j~?mAzyGKPFmx?%P_(7l-hZ-(eIrJFo< znUF&qhXYUP#*e>L98c+HjTTAwRPdB;=B&-d@sw`QgGa>ily0a3>2CW_@E{>;klg^zfj%P9JJ=vjqFnCIL{Mkn0cuKcg+auz5N;iGl%(^#&r*zW>j1$LG zy5~E+7spe&hoaZky%{{E`(y1+aXh8lIcBmX=KF%DbT_Q)D~_jh1S~~r5k^At`ue-{1CfYEOmL1IGzHIaQdM*o&uh`u)H?+cowH8lRBSJfr+}vfY!k;*z=?Jw(k32H0e4>CTO3aTk12Rd98UpH^)8`JJe~s1aBq<~ zo&w$xE1Wj*cnUb>n<}@ z$5X(oYmE@cQ@|T?J`%@Mz{^sX)8-ye0dL&7QXEeKx5yexCwzDcc<;)t;&=);*N1E3 zcna7zRB@f~;VIxL&lZd0Dc~;my>!Bdr+|AkX(x`SfGcgiAdaViTlDnSNgkd8J~?Za zIG%MOcZK!h_@5SVy9$@|0*yT9#_Drr`-f`hjm&x>2A8d!M(w%T3 zt6qqKr*s|h28iP+-GP;^i{mNX0Zm;06c?V-?cZ^J4D*K*Pw9T!98NF9z*D->-v^81 zDcz`Lj)>za-GtxM>xCG2N_WV@;o^8ox53QE;&@6oT0Otm=Ig{$y4Nx-6~|M$7v@IL z3o-DNZaJ@J;&@6oVB}$OJf&ObWjZbLcuJSWAMe>5xHb=-&4Fw4;Mp9wHV^;w=HTB_ zz|p2QaSr)1&9_mN?1x%AvNh}!cWsS=n_7<;SMX@8F8&2>JX!NNc+uWM@3PmezUR!2 zg>woGFLt-K*P7msC-uI#?(~8mdn$db5-Q~R?XLywZq9M#gL(~__PX!)Y)=>D=v2DY zgetRl9rzJ1Qj^)US3VeY)L_Y&pYecfr$iXeLFDa;cQ?BCUcw(?Z7;kvymN`=VE7LJ1|e!&L(zXRsm;IJ1|R# zGuRHyy5wwT2WGKzHn#(_QaW4Mfmwc?E$zUp<<3@i5XB5y+kuxEw6TN8X3*9SBALN2 zb`a4F+Sx$_GiYxI;mx3f9fUK3j&@*9K%AZIAdDGwwga=NaCWf+^JC)dY6ng;sQDKY zGIp?Ya`z2pC*|%sUo&aQ2aDyUD;2dfderWPc3Lc!4^yrd$Fo@0j2=z*MDZ+^>3_5q z$5YaNWlxFYDd}k?vSl-02cD9ynro~$o|29+_Juf}k{+4NKf9R+Pf7cZS|yIBq@z8K ztb3w(N_ue4isE=mx@&^@;&@6re2Q>6&Ew)(EUz?bE{><9XN5l{j;ExPoyd{P%!8+- zmq(i{j;Ex*l=>=;r=)kitDoDznKS5N!P44 zMI28_C*Jl$96!)*?$iB|zkrzsKiKj*S;mOtDePJOUy0)>?4sYQc$<0f6!ycDo5k@g zq6M=jC}^(7Q`j-5cNfP~*qOFm6US57R~n?zy-z%a{c!zYaXf_`wCs^Mp2Ciot9&8z z_24P&fZVIa@f3EJn=y3n6Hj4Ri`GsYPhr0td0HG#VLO7d6)}&Cr?6vAaosD$Q`p`I z9_o5Lg}wGtQC~9;p2Ch4JY5`5Vf(y(D~_kI{YnHBHS^#p?65i4h~p{jBN3t(GuPuO z?ESxW5XV#4?N?k9$5Ys+OB5__=D}0gN!rg6$5Yrf`#4LO>+!4;Kjt?5^sUJ7cXG*uriSorl2Ab6 zH`bPzI@Z8bx?u;m7spe&F54W z^49-t?VwXR3!Lq>r^v4#9j9(4D4Vy-#x5~(oOoBd{FQIx?+y8T0lS;iop<+`?tcBx z3)u5s&<%D`!B}qN;unh_YLN(-`#b-X3~%kO7u$M8UALxs%b%erbPRCf72LG ziPoE3r;@oIPl=|?zC#>OiH=^FD8O8gr$l#O?kbLFF{{>LlQ^CdJzpIGz&C6^Wu0)bb5I~ zrUprBna_9@xor!3isLENiDPbv<0;g-LyOfm^WZ7ek#!e|<0;fZ`@`2U*W)SF4mX;K z|LN`5c|QCJaXf|E>{k3BGY_6Z?ee<4IG#dHc=D1soB2T!4vc{oQLPodVH z8oHjj9#5h6xY$q}PoY-YvrimPp&pr$vc8!IPobW^*H0Wzp=L{YRUA*Djx3(1ftd&Y zXQ3_~Esm#9M;CY~9?5zNHTJj?4b41w3U%eM`QmsA^=)wIM&^1vh1xb>usEJV&6@PM zIG#eyu_J3^GY_6Zjr(n!IG#d1678KhoIt9M7WjxA%h1lB;`Z^I!#EB-Ce0H&?(}jq#N3#7e2k znCtPBZl%~=#PO8w<)Y`r@s#c}|J-HGJa|g?eD%rVcuIHw=+EMKN;muN+J0spJf&MP z%norprQ32{;&SGCJf*w3a&K`wrCYtkb#XkU8}njA#Bl-SNU78I!LKYd5RoXs_tSqGj#5txfSN z1@Dx<9PBvim8abCx(&WA1Vj*Z!$uv3$@mzwXto-+Nj2_*g8L<)0#sXR*9D@}oGO#j;bYjQPye zcuG3iJwI_gC7r13GI2a5{q9^u-K)b>(g7u!i{mNj+>;K8<0etR`bD*P zx>tv%q=Vjc6US51)hqoWj;ExrKl9N&JUk^ma%}n^DPc}A=3398sjTjz9^`aUYLWgV)?1}1H|!^bizmH#qpH%qTkc#1v_|3 zy6e?$;&@6r>fGPN@sxD4#d-CD9Xus{ecn`YJS82w*bZ?#C4GBXBE4V-Pf5qw)k7Rl zNq;YNRUA)AkGfb$FWA9T(oas#6vtE2b)&jo;De{6FJ!Kz7xLgK>DzO+isLEi;8+Q? znBytw-^O=!E%Me=(k%XX&*s3jdGKrwT$=~a=D@Xi_@_4q|CW2eQOcF?mMv7KofQwX zS=yk^t*YmTop@4iRKCg&J0+YJbL{!1Q7TLwm~ltonFW_mMQfEL?V72%s^&>hXMcf` zZ6mg=m|}A4HS-dsZP@Ma1?+B4uOjarjqP)?WQAM#eOhj*`f}gUeBFNU`~JpA-}K$b z#W?Qc?lIlX?N>u|Q?7HH)YmZ`o^l-;Yp*z-a-F>1 zb$o}XTno(ZuVXqq-aLo0Ai;I@s;F#;9etyc~m}~dDyOqN+*EM%iXflVlv|k)oUh1bD-pbCI z{NXHXGx@`v&4JZCIGY2jd2ludR`c-xRdeunG2rEM>a{PBFUYUo;&8_@ChT;uYNU`9 zTdNi**lK^YtrPDx9v`mNhFx3Zj9e0O;iIef{8dvY2c6&0wo?1*7w$$Mk#f+>1`9U6 zzTQ2?j|S{$Oh??&V>37;wM(ca+01;6W~B)QQ6};Nbnsl*5_iW=3#VCl1GeV`S{69F75(J#bk$90R`8 zypTF^I0jtzpw*$nG2k4{zUz262JBwlPn|d%1Fq}6PdOX|F6o_C9XA{Uo;7`_aySND zy3;-7a3;A8>r1N>hhxCMgSA}Qx3;~N3Za2Va9=Dz?aWYQx3;~3&;DU9F74`%hjNz83&F5 zPsqAUIh=VR+|0Ae;XgIt^QC)fnXcHoWV*+Ile{{i8jb;9{g^__ac~T{V-ag39gYES zZ*oe1ceX0sm>h;XLiXwRH>YApR+T2khtxrqgkBoVRJ4!fs4* zA;F9Cn|k??Qo6^02Of%8z~pcYI51Ol7?0vrR*P+_2QI0pQp zQ?PP42JF``g|-{u7;y8JgOtND;AblyD2HRf`KtM7y8+H5w;}#2 zel*_G+dPkO47lN=k;>sra%B!bQVz#}ef-L6y8(^?UthXXIUEBHXcSM|4R8#2$-zF# z;TZ744wsd~G2r)oy-J$zAC3W+`!rrT90RU9@r`mg23&8XudYRaW55gjw!sT__0&v^PwIUEDd-ldAJMSx?# zc{;CC4#$AcC5kU;fn&hE%63x@$AD*~Kd&5)0srzYm!t)b0Y@4>NjV$?uDtP?aySNj zY)@%P3mgL;ReX+eI0o#y;iGam2E6H6P00)#1MYofy>d7PJb6M~NedhU{^Dz0Eeywi z8z#0S2aW-^`?aW~1&#r)+%iu&90Q)PDT1U0jsb^1(o#7b1HSbBsB$<4JTYw+NedhU z&X9AwaySM&%=5K!IP=1@Db}?M@Shs+*Xl!am>>H1u9G=HQVjsd?-SvHRu2aW+34q2oejsZt$7%{Io9*zMY zKiNz<90P9db3i#90}fr5HlG;>jsZVgHbglb1CHJ8zH)dg+klz;;m+p3Y95@;fz>=X zn**zP_^X?Pzl#C)I(z>1xOEAi#JV{=pkvH}0Tr9ijS-aeON%uvGi39cwZ6dNT!D>; z)_5M$W!IxOd#a2bS+ITJw%?+7t-PFOLdjvx;=dVrG~U@_|7O6B#&pCTJ*K0tcN*}Y z2KVOsC`MIBzp!hTWLtTIF4(&g)EpO1j5@8)kP`Ck4lVU7L4M4#$8ee>$NY zjsbURmsNYoa11zE@F?YQ3^;m&tIFXRaJq8YwZ{y{fIC;8q#TX`_n!7nIUEDNU7()! znBh!vr@!p9_MUBy0k0^OMtjL{47g^a5z65h@PQA{l*2LLBY{=4#|+1Sue@8M9F75> znjA-a$#4vK>Y%R5;TUlL&KH%#G2pJ9^J^~|jsZ^(o}v5?ZZ|`*R(wzn$AEW*)YTp{ z90PupYo~HJ2Hf?DwR{Z6fR9%hqdjIg1{}NKUFC2Lc+c^CbvrXE47la#B-%@cW5D^IbXE?>fO8(cpd5|?&+#bK)O`PN47lH>Im+P}@Y?CF z+DnFGzzKYsD~DsitvVl74#$9(Eza89JT4pqcAqp!IUECiz2v=eIP-#A_2}A5hX2%n zKMo$N<(FG^a_Sxfo}S*i`V)=;Kh2t3%P(*Y_*K^t%HbIBFN^LehhxAiwwKoO3mgL; zb!@3}I0oz)%}vWJa18iv@%GB$7;u--CzQi6;Ou#`X_*C%0e8DLUO5~C_Bj1gIUEBH z8CONiFK`TaLhV(`;TZ6^;22t#fn&fW3b$7d$AF(~Ij$Uz0e480Rm(4M3^+=XvC82X z@RJA6l*2J#CV#lIIk1`sXLDdT56~J3RH3DZ??~+JWnp!!h84BVx;p|KRqE>DhekmBTUM9_~Ao!!h8aXJX5Y z;TZ7j3*D8&G2ozDSCqps;F2qf%8cPmaudoeP!7j{Zw`wrGlpZpfz#S4hhxCU7g}#4 z3CDn!=guQDhGW2Py#kfPG2qWPJ}HM|z?+sekTJtC;Jpp@D2HRf_oAke8N)H)n{9?F zhqKI^a`*$~a11#3ZaJAT90R_)YNc{G1{^dduFM#Y0S8RztsIU4|9--H4{A6DeBx;i znK2v#j&@|caySOOG{PI@a140yu9`AqI0pQ%=~m@%Cb`OelWNj~W5AOm_qR;i<`{76 z!FQFzG2o*6a>|V17;x?2smkFP@Wx)Bl*2LL_pKYqjNusYgVuYL!!h86FVe`2;TUlC z^+POUwmAk|=gtG=a140W&ayINI0jtk;Zo&r4ERO+NHSnJ2Hfh4^&Zr447gmAJvtta z0hc!G-!D>%HbIB)XDFZ!!h7CAADuZa16L``c2B=81UOj31r4_ z3^;0_b%6sM15Ua9l8%RCz)b#dXLDdR56?x)y=`*#eln=TV3<`{hg^E zE?-va(z;BKpKLpvYV@HuO`9FR=KC<{+|$j)kGo88+uFNcxYbQvo_2m!cW1T6lXjPE zlKf$np@nieO^1)M- z!>ikQ(fm)9!)w_2@w%mpnQ`Elaki8*l*2LObS@#v;h6EG??t`MIB?9^edP@0aLjnh z=FiIEnDN~N4U3y`;FxiQPdk*uG2;*1Mz` z2aXwsKR8!895eRoZ@q0K95eo2#;=qa2aXwgZQ7+Ajv3ERn_MdpaLjo8tY4JFG2?}w zZzzXj##25N(Fz0{GrqWFmU1{|JpcPQ<#5b+u#2BoAmA*UXFtDNIh~p zxzCAy%Hb@Vzb$%GIh+f}F3u>V6$m)X=AO4kDTlLc{`S}{<#3kGlbmDnIvuy4a!AIK@ zaLjo8sU^zcESooq7fss`aF)$|>$g`9A7p?3MPpu24rkf?VW+~{o`AD#zOqR#<#3kG zTXa9G9L}=&z&ts$Z2@Q5{Gi)p<#3kGmxq6^9L}=&ii>rvZ3~-Y#ygT(ZyO0`*}Urb zq}rZFu`~ zo_?p3MJ{xDU%#Q=S<}>6o-Fne?=3MVjUTWre^7A6#qsJTk3Q*@@6LZWVDt1k8Z%M* z=2tG%Uy!S~XS>xgb9Sy#a$%J_lX@LpTcJq#4*6#`Y47MU9gY9cu7YW-=`H|#<- zebX*v)3@wGHhtSJWYc%-LN_Rqu-!5d+59~rV{m?FC(~s;zHvQNxWYbUV zLN@)>E@aaob|IU7W*4&Q=XN2Reqk4~>6dmPn|@^%vgy}$A)9_<7g58WKi5#ZkWIg} z3)%ELyO2%4w~L78Yd+Y8Z2F^Jn4bpMPj(@j{%jYr>1+Q&!j5#AX1_Q%dV<-5ade!w z88qxhlW`$V#Z;y)5%XzEb5E0T*=@CQI8Da5Y;jVXBN6eMU zj0301NF8gMayU)K#hRa#!)Y>(zi*h?j0301SiW_yayU)Kx-aRonB(Cz8M{)BQ4Xic z=&|#aayU)Kyhb&$nsMMX8RvIxP!6ZbaP{-ZW{!u`WUQ|epd3z<(V^&7<#3veQ$-7B zH{-x*GMY@Dr5sL^aku*y<#3ve_oEu-Fyp{!GKOB=qa03?QF@Z6mpL9zlQHJgc;#@K zjBzQCDu>f##NL`Trx^!Mli^*sw{kd5M$38D2gbl@GI~xgmdlI-r^)!-Zh>+*O-7Tm z5ptX3;WQbpWm_nR(`2;oepES}z4_49vGSO4;Ox!kOVUL-oW1!C@h>Qcvp3&vR=&Ju z95{RP2j|RG4rgzEPla#F;q1+)?AIWl83#Vae%(LRaEEgER6AdmHEDixJe+xA+M3?V z;mixW>WoniXVPhLZhmVsj&3XBPnDI2J6b^ZHSGu674Sqg+}F-)H!PIWd?CD+ou9}x zUOBwBoioY6os*C?NjWDWYm#zKLe?bZoP?}N>VIt#`n&7_pBVMMWTDWCUUxUTdKBGQ zWc6XM*CVU0-n;Nl>yhW1Rd|;;e3Aj-r!-sl<lr(XC`@Ql{(a z=rJ9Q|2TXsHPmLl()O{rca3kqC6t-oTs`bU2AseyWWWjSLI#}3E@Z%o?Lr2e#4cpO zN$o-goXjp{z{%}G2Ask!WWXuyLI#}5E@Z%|?Lr2e#x7*QY3)J=oX##}!0GKGruoNZ zunS|r|H!ML9X-KIX+9kM@Op=x0CU5%YTa?IZ0eCW=Tjr9pmI2q+@3)>`Vm!!fNMuF^p$y#O{ zIHvV|^djYOOe^a3D7DS;a7-)b#7@fLm{$3Zmz2YqV)rw3P(n;eqHpjzf*?HfOot499+j-AD7nH;2*!jdI1)7*~ z;B)Q#K&e^E;q&bL;xLz{=6LvgJ3lwGsdD%NI}h1^NI86=o#)P#v6&eMzR1p9x{Ov1 zUu@^oC%#Y)Ut;IWMpbQY#(^_0ME}rCIsB(rLEmlmptSj77tc|;$AHg_D^SM7a13~3 zx6#Vs7;utux0J&%;Bzwylr`hPG2kQ9e^m~zYyV?Ljs311&N6K0m5s}pao{Y&{(5Mi zayZMd%ig3eZ;pqv44b~sVC8U@Ve@~xuN=-YY_yZ5e9Sm-fBXCV(0-wEILokglZLNg zj)$`hd#Ps=%?I-kPxw_9^%HS@ts@ z^6d&*J2O|f#PidweObEGzM|36AFF@0_`d4P$0o`>vBsSjYj2JpJ-p89wTDV)YS#K> zr4c_Gu%j_2xYsJ&^?rm3jZb}w-OGJ{XP;jaUYj@bwrl_EZne*OFUaKRF&&NXG~j>u z-vK*%g6VV|9p`Ok(y$wo+_ojw2UnbVZ2d_T27GI<^(Rko3^?+G+?wp+7;xlYtPie$ zW56Gth3a@X2K=ggHLE$WIR>2i#$4ra3^;Z0JLPZ;c;>}An(W~iuz#{0%HbIB-hC<6 z1i+c(S_~he9F76UIQm3690M-+ys}mT;27}J5!T;B!!h8iSv+L?a17XIT!3;o2D~u) z73FXYczV);GJZG)JTKZTIqo2=0yqYIbz=ps1i&%i&C^yWhhxBnT1J=g z!!h6@i`poMW57|aA5ad*fQyGF(@Fpw10FCcKsg)(&Jh2MaySOuwnbL01i&%iBX!0o zhhxBNvOZG|$AD|RucDOzI0n4*eV39F7484XIMXd_5ck?m2zAaySOuXo*`%b37aaKEAZ0 zaySOO;r3bOa16L$yxgVCIB*QOT7xOd;TZ62w|C0n81ST3wX^~N$AH_;-J~3j0WYcJ zA>)T*z;9D_R}RO38%;j19F753j%dBJ7#sthJ7}_20^k_%<@=$^;TZ78`_|t>!&}-v z98IUK*9rj~17`AvJDUTmd2ludR`cL&4y@+kuWk^SEhIF0h_roM%aJ<$oGPF^aRuCI6BVToQ1F(lib)- z8DvOrTn@{)FyIo0M<|D5z`fEwRu0F2_v|bybAn^Qzk4lF4#$9(wu&G_f@8pmU)E3# z$AI%)TdEw60jGT&UFHPGfTw2fs2q*~54mtoIh;vuTl;)6CpZRty;qQOI0iguwyO*Y zjsagP)IvEN10H?nh;ldv+_yv~nG+lX{9UerlB90T4Ea#lGU18!I-ugnRK0iSs_OF0|^K3gfG3<-__&py~# zIUEBXyK9?rI0k$_Rbm+u90T4S{TJnM3^;j&+sfe>aG`A_Wk_%)xwgp{DTiagjmAZi zA;B@=Mx9$LhhxBrDsHlJn`6M2KE#qC!7<=FXS*whW5Bt#T~ZGJ!+>KKmQlem;7a>v zE8lK&3^;bg@5fO0qnJgnnspCsoH$vqaySP3y^-}^&2S8OTBixk&G!SxfJe*-RSw618&~&jVUCAmz!7?{Ru0F2 z%g&C|(i{)RfGx@Y)VhWJnv=Etgqgz_%VYR1U|0U-w4d#&1^1j6Ez8)Uk$eAWz>dar z#2r1Rqp$z@-vR$`UIqP+G+;+hFrAL0y7mu%kW?9d~E1-%Ybd(&(0Hj7nRY%ndB;@ z3{noqfG;=yt{jd5SKQE8rVGb_2edk>9F76kI-EmB3&(&@RT!fjjsfTMd!QVS0q>n$ zQbr5MfWHSURu0F2TXc#lqlII@+sn3B4rh`}w(_KMI0jrJMGhG)90N{OX|i%S20Xj> zb>(mjc*ETyGFmtWJoV~a=Xn**zPa5e{4^YB+U2Y;76;IgrkC7ZXlbjxMEuP(@vsaG4fvaWrb zZteccYhu1valGz)tNnK8x6r5?qfK*pFd%pEljOHgB|6`0aPKt5-LGaJ+-O~gU$f5{ zQoPrX2JC1|N8Hh4I{JF20sq6Vf_C%-)9E-m&fBz)VK*i@pZ2>oDgN5A(GLc^wqZ&c zFdPF;xwpS^I0oFT-Yw;D40zB<>(hte81Te&K{94I2K=Pv1LbfG_?ve@8893J_NX*V zIUEC?KgmU=3&(&*=4-AT&LlVV^AY8640wCtY%*Xt2K>=)vT`^E-1E~1iBv zU^oU`40zbeP~~t8IOeK~GGI6c{NUgU zc09-)u5eIUEB{a^{h8I0l@~-A4ut$ABN_{7pF=18$SWU8W0XUbrwljdJ);4Y;(& zJelw2gQb5k;K|$HsfJ^~^Y@pP0mCt1HjLF)47htNSD7vx1CDyiUpX8D z-alivaySNj*e!(&7>)sd?blB^90Tt3^@egd27It#Q5i5C15VsvwsJTI?4J0$aySOe z${7#LJ~(nry*!5tdb=$zWSWL` zG0p#mz4KDoe~!lN_&!mOYH4>L^Xzjd!hmJH&ra@huxIR$m9;#ZwNIFNMzQXHej3t> z65l+`s42q!b6sf{$xZQ_T_iKbD!WK(iq&?J#1w1nBC#pf+C?H$th0-RrdV$m2~4rU zF3cmlZnTT|rr2Z`@l3JVF5-q2;kMXC97DF+MQlU1*+ncvw%difDR$U}IdiT%?IMOL zcG-oSDR$dMbW`lHi)g0UYZp;XvCl4|m}0+OL=G#$9k7c?rZ{L95zS$T>>`3G4%53)IIB*Qx`@l-&a18rSf45TRcsPdLX@5KAa149W z<`c@{ZS0?i)$_8IHsioqe*gG(ta3QZ?_2vmR}N?SJvY184cY{OQih;TU$n?sLlFEWdvmmRBo4 zaF*X+HJ_yX4{ravzAbpC9M1Cl)uc78l_8t6{Qk@SwaVcvzq>DVv#yP>c^`ZH@6+2V zhqL@%Iqz}haF*YXJn_)6E({*blDF`Q?9^Nj;bgoL~Mvu{!E_ zIKTXhtDIC0=a)Ze_Ux_(z*$~9`hARYID2n>CO=gUXYVa=e|fVc;R0vxt?9`T%HiYe z7t=FS9x8{k_xABqX&nJ)?``#RYxf4u-rHi|a5_P7_TCze^H&aM@9oIN?aJZoz5Vtk zK}7TY!`XXVyQQmgID2nTYM)XLXYXzKko0Cb#|6&boBx5n%Hiz2_0N1xIh?(>;sY|9 zH6j=Iul9KhIWt^2oV~ZJQ|~K>v-h^dr=(eUa)Gn=_9EUA4E4_9Lg(#&?hO24!1@Uc`{)0!y(nKVX8qk; z_YMEuzkB1TPvjZD|RWwI^BckS}`c$Kj8 z=+#{p__vH0#w(TdPgA<*i|9d1j(59#etMz_lji<1;NxMg8mOzL5Is| ze@>F=Yh5$hg=tA#Guwsfv|Y2~>)+#Wjar81-@WvJ2BO zyXLeDBV(>K#m4Ylt6k`@9X-Kx+>Va(HiL%UTG^d@?lxV^n)+e3xVmR~wo}LR zs^Kip2Di$ixiH|xXg;jF}jw6i|k9?nY4l|uD& z^*fxEnDBkA54VT25|j3|_2Kq#R$}rb8=$M-;jF~uYh?W$Ih>W4REIpRtKV(TN=)pX zqm{#1iK$oWg>pD6F*!N=(MYadq`OoRygKpSmcAvl3J2@LA<> zR$`)7@Y2=qa8_b=4jQc-&PvRr8TXaLS&5mOt!Q2Idjw}CX5LooYI*n{D>2dD>UcOS zF@?TYuV=;yu{kR-^UtqS4re81z-*8D=6ExYKkJ>;;H<3BGk&aoLMTAOintG%JV_A>juiKzP;_KR8+*UhToHSK)=km%m#3*o+Y?o+a% za(FE}znp!oayWapvHjeNn{nWE?BiSI@1PuB*UrnOJfj?5&(23?%U!~Z1FvuAZUv?) zhc~eE5jEc_hc~qIpv%4`%{cHzb{?_jX60}{JCB?wK`C=Q+~3Y?uI-^5-q_Ad)VrV@ z-o(y_ZOC2Pj010K=Ud}UQ4Vir=j#WADu*|>^VDZ+lriJLTiAK>{p*#(TiQ93Kit_I zSj~g8Ik1`sXLDdR4}W!Y@OQZi`q709sk&quT|Ps+<)vOV*^|Eh&hH`L3SS)OQMTgo zMIlL+7Jv0&)$M6d3wgZ0?c?L+xwdJokEbr?O+0mS?|?N!c13<#=)$@k(R=-9z>dZ| zR`%tY;eMkl1_pfc&zNLQod$z{8#nKKoJ+&}u3v1v^K5%ZkLhT9rvd*519ts~7_g%! zm`=yhao%QT47)K|zAG0ko2j>dT9(z^GgwdY- zIB*QR^TY$n;TZPB0vYm{dV= za}2viwOPvH7>SZ*>OxUCh8=$8Cgt!Eb`zOrZX#U}3dgYP2K=HN&LsN& z`c36<3_EgFZ(S$~$FLj!vOqZ;!`|!>q8yH4#~xTw7mC6$Y@gn%mBTUY+TUaAf>1by zUEo_c27FSA(I&gj_F>Sv_d%?)2*4#Ewecuj_KB1-Cj8y)7{?hgq7PI)AdT8 zHH#Ssj_IyHIa)a!)6JGSL^&MOT`|f>lQ109Wpai)CvR(VcTV2cw|FPj6GrYLb_x&M;N^@ptYnK-1XXWKKMANZ~=I`zP!s*&b-=bzMd$kYd8UDo>C zsh;ssnW(kDCVwA#|5MF}(<%nl-{n62*NTIFG+;+#&S)KB%Fd=yEAQ$w@>cI(ZWYb3 z?ZD?+N#dXVrBTvWbE0>8>gX{YjqfzzKMnXF`CibDo?tp1N5^@anL6x-xmLgErDf#M z*=N)tVXlE!CMkzwuC*^%%g%7jwM%Mi*%^+x7TCB>%g%7jwV!7k?J>hK*PeqrDu-jP zK81%WhhwNwTHI9*$502%E3Lg}IEFgC*mC7?40Y@G80F1*fn%s^i*{8G$56L)zN{RM zp;q%OqCI9fhC1QQ9OZBfb$(9Q3g&U)80zA4O_al# zl*2L9=rOH7xq)M-gKFayW(>e@<2HJ;O27eUq*CHiu)V{%sRzPa2M)Rz2NQ zIUGYhQTnQKIEH#RposRQ;TY<^7ekc8G1Of39x8`psIJk<*D&V+j-f`Fv_d%?L#;O< zruLrU80yM`ot48e)S<0UD~C_9pO;4Ea%hhkKGn|ac}!3apJwMlzAu%-1MNKfxpLZj zhGW3dL*^-mW5CY`eo+p`fUmmwYVR420cZ8IKA;1R0avRYOMA?440v`%>(jm97;vPw z=X5+A172|=UtM$l;TUjrv}_>IUF;N)jn7`95en>#`=3)IA(k_-*_#9z%k?S zHQp$PW5$8aYj~R{6^`Fo%s9|1jwW+>3wxaUX}Tzfx3qI6 zf4H+bu$l*Fb6_cqQxdkZRxFUrIFpa~I$24fYR;5)`?` z#{}I2Z|^NxA=;W`(N;Xn8$bD#QrY(xTezS>^XUzS-Mi&`aBPnhi6SlEGe3!+S>ONH zzXNtO=5O;Ow7ERvNS-Rg_9t|`=GnK*=x(vMTnX9Hc98$nz?uGc9X+O_@qcc>=lyCY zFt4mB!v1r;U>C_vanUZ4nc|XNBsIllyGUY+D|V6C6j$vcktwd(MM6_tw+r)Nt~cz$ z!xT5|BEBhZ*+o24+_sCjrnqAlaZGX7E@GSFo?XN;#eKVQH^l?Hh-r$4b`irAkL<$D z6p!s9x+$L6MKn{qvWuvucx@L^O!3ApBAX)AE_4<2Tf2y8$UD1;V2by4VJ02d4|WmG z6d&!v)fDIcg@n~EbexW!U^-Ao$7MEyhTSmHsB6r(`sWQ3O*62GLO3S6tL+NqaF$_b z`?{F#-vy4LhWBf#9FC#7%s-?Yj-dvY$ZSqZ*yV{9XA;U3X}oechB~~=Tjg*JwOAXg zU4mn%UnB0)v=7HnUoJ{%o+cMKhT0{PZ})}STI;TUSy zK~c>Qjtd+^t#YM}ayXOF(`rYQ!!gvs(=wT#Mi)4S8tuVYpEC-TF=}?RCL1>}(NxD2HR%uCuNxhttg6FIGf* zU2qJ$_MF+u;TU$EVlLVPgJakivNTo>$FLVBKA;?qVRuQAUVB||4EuGP;mY9{cIAo> zl*2LXbrZ{KuM3W0U%t6qIUK{DH6>0x^P>i55BTMqUdrJZcAr5Pl*2LXF;lZ?uM3W0 z$5=L6IUK`&H|B+MIEH;^P&Ms=!7=PtbvG)9W7uD&=qtyIq9X{^oc%hMhEf zC*^Pq`#|{9%HbGxzq8pJn{nV6c0u>?%HbIH?ov;b!>8L#{Ihx`w4?@~Vdpho{;C|# z9`K58@07#Y1AaWST2u4=z}W*%K5~t6ID5bw8pdj7j)%{-AGd>NH|6j-c7F2fCFOAT zfagvw)ZB~%XAd~u-r36G>;e0obZuddhqDJu&K~fRnHgJ}ap3F$ zui89DIh;MU(&eae}G2J}v;`>Bvs7g!rAMcUagOEID5V6qh3=E zZ)N|xnEc_+=D=zmoXvsNJUE*Jt9kgVn}fg0N>5cvY`-jU|h6c;@d^W?sHdZvy6zZRgf!&Tp>{HQRSL$@deVx=&kw zr%UvlKN_&3G4tMj(QEMZIu}PCNK-Fz?wP@tx(}%CKH+iHx}OJ*xt4y%2uF|UXndyu z|A(uf|0(Z;<>(2f({Xg1x0x}+ZcK8wpRFom>eNpv=^g`qa>2c<$>A7q&HNpe!!h7^ zCr>JeW559=v#EK4W57A*jaClFfY%IJs~nC2`*x0`<_V4gx9i(lIUEE2yyv)bI0hV{ zrI(r~IFnr6Y*Vdf%jOtx@m^N51;>B`R{E)_f@8psnjTON$AIg^%An>6jsb7$WL;v%W@yst|>t*pQ?;BMV#Du-jh!%}@y4#$9tS8t#d7dQs|Ir|Rf za13~RxTIQHfn&hOiuX|t$AF9Wxu6`50hgJQO)D#K47gyH5z65haIwvIl*2LLHc_p2 z&VpmW+n)t#B?gWG_uThYIUEDNJlr-o;ZZD{u_>L2hd$295y_ zC~2+4z%k&ZEv;q>jseGevR*4Oa16Mor?nCT$AFJi>ZKJII0n3`^;P9?=7qyAo-2p{ z)PUR09$(bF=*MfETlX07(wz5I!!h9V3G)^+$HOt;qgh5NhhxAi8az-A$ADKfDC=#; zfn&gzYAv^Oi(|kiy2L1Mj)!Bw&&OKtoCU{#!|ge#m~r43aLjWPl*2LL z1{+=}hhxAk{i~KVESLkDU|zVsYQ1>*1om) zbh|!T61a|hZ9)>KKX&RD*B`Eeb~NU}7!yK@oSt?!w)?H@#f~R?dZNqjpeN_9 zmHAr7b$7$Ni#!}XrlaxyCq50y_5ag{3eHEz=cL^QVz#}zqYbIx)P27hhNvbfEfpl0e2jFO*tF`&KK%c&>RoP zfTPSDqa2O_2aS2H9F76MTUo1+83&F5k3YCWIh;vuZ_(6+&GB#yxZv&)%HbGr@w_jU z!!cn0QocpZIB*QO%Zshb;TW*{>SSsz;TUlFVgbtG7;wQ=SCzvt-~uCxskww>z;8D% zP!7j{-*t}cZN5)9lU(}HmdfE6aL4??%HbIBm*H8}T*5KnK1HW0hhxB=v%e~bW5B_s z>#DhgW5AzMr0l(eVO*tF`zPJH;TZ6% zR^OGwG2mD;8><f$x>WF<_T~T3UI6W5B-UHz|i>z|%&?w^p8PjsXYl>!=)#0Uxq)yfkb1Ku`vxN{< zY~^qaxJrd@%HbIBriP8Z%s6liI8v5F%HbIBE|1J%I}BletZj1)xNz5T%HbIB&GN66 z!!h7=^=sxboJhhxCY#w5;dj)!BwJC62M4#$AIjk~EFjsf?((NR%90M-CI#@Xz11{o~Rg*j%17`AvJDUTmd2ludR`cL& z4y@+kuWkfz;U}NB_%Bk+B zN?3n;+hiBZmF~HD@}}JfOE3E^`GcVT*WWmLOh@B8_kjPy9`HZqy`UXE!E`!~j&n0J zY1oYpvBdU>R$%`x9b%C>Y7kxEbcg}jR_jPO9b&%yaa;|6(;)`D?XDb7hq&YWW$SpG z(;;S$UpSl@2Tq50J8-sgI2~f`qb}ji@o+lCLm^gM0jEPefA64GT8>8=Hr7^3Kv+PBmIgKS0e=6>=!qtf9|FSy+2n@>F6;Xjqfzz zKMnXF`FFsMo?uMQ(Q)4PZ!62ZvmVdYo=U}m-nwTGxWzr|J&NJ%0naVmSZWMs57_7Y z9_4WMfU}lQsl5|8d%z8rSf850%s36QTCzA;p_p=>ipQs zZO$I>!+6$S3!G)%#h2D-uLa&+J{rbuV>;acQeM`r~*#q|L zSVC9%!r23!dSZccID5e3+D6pXym0n_Q-?NF4rdRz@twoU;p_qbekY@@@`bYpJgwkp z<#3jHFC}@R96rE459z8_&{e*0_J9-2U8Nk(9&q8B9=e(rKG+^7>9${#!`TB~?|)S} ze5if=xdyp(H7}e!V6P#Ql*8ErZqw+Ua`*^)oWs6#bd@ihJ>ZU6tgCjG&K~fx4ApftFPuH# zF6lQahqDJ9^Hc&|R&i}z&Ub^Qx0bjcys;7 z%Hiw*-)>=jZZ4cX;3W@dHZZ?maQ1+Q)Oo8M&K_`#iB)wqFPuH#eyvw4hqJl%50dH*YS<(y&XAk&%$G*z{;Px-!>P6OG3Y;d=No=O*1z#G}$=Pduh%He)?zNF?o z<#2yHpBcNPF06pF2VCabJmqlqfR|Tt)rA#sYq8eFWz%kd<#6_Z(-hgG9Nyf1+(~m% z>cR>*d%$Do4p0tf54d`k+sfgs>~Wa<;m+p3Y95@;fz>=Xn**zP_^X?PzsnwQ^tDS8 z_sN&!UFRny^2|JtBv0z!a}ODxFIwvLiK}fdv2FNn!#8A}o_qAVRaw&hmh(!Aj}!CG zZ8WNDv}Ng1_Nl&StlM%Q_uT(xz>da@J1d|?)AtqY*FV+pbriR;aW32+K0W`+Wh>i% zE}gV~)roT)J*K1aod*05H(*CkFrAL0{DW*aySP3`s-rla16L&=qu%L47khZ8oEFa zjsZU@xLG+I1NOR=L>J=0ndA;G=&u}(0lUAruN;m6CrIU^3-sU^aEFVll*2LL!~J9H zLOeJIeDQV{!ETu23);JWnG{L$AF*SU#=XE0iXRCUKj4c zG2liCnkt85z!AeAQVt(sH+lO%d+O?aI0oG0_!#AICb2=EC7;v8#UzEc!;J|tf3z~7@7;w>=dz8a5;1$*{?t20Zx2P~~t8c)^Ir%HbIB zmaS!V^*$T}_M5&$IUED-+d8tY-iKqrd6uZ*P?2K>Ik2<31LxO1+@%HbIBwBIXuoAVFHfS+7mtsIU4XYL%g zxH%q<0sp?JhjKUuykf%@DIl$Ru*uJo=pF-Z)9aXOI0oFOL9%q_csK@}aeQax@Y>t+Q0SCr-p&X6@ADmt-gBb^o0Z(kSK{*@)&N(uH zr#T*u0Uv7EM>!k=?)c?~aySMYy|Z^lGY%XBZd_!(aySNDXmEr~=6E;;ocu%!mgWHSdim$gegSl$eOdi6 zL$Y574~qS6Y@I`A&TfBs_1AZSr|tz7$o6vfr_j{dlhe*PI*vBR(Q$u%C(i%pJOj=% i;5-A)GvGV}&NJXV1I{zxJOj=%;5-A)Gw|Oy1OE@nFkxr_ diff --git a/src/discof/tower/fixtures/voters-398915637.bin b/src/discof/tower/fixtures/voters-398915637.bin deleted file mode 100644 index 20694d71edd7ed292035b215148b5ce5522986d1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 384200 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

Gw+U)my$r*yBsi?0{#;3?f4k$Z{b|190y*TnIZ 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%0AhI!etwAI+$Y%}A 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

  • D=TyVf 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

    *zvtWk)7F-Q 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 baGwG98E~Hg_Ze`X0rwejpMn34Gw}ZaZFcD9 diff --git a/src/discof/tower/fixtures/voters-398915650.bin b/src/discof/tower/fixtures/voters-398915650.bin deleted file mode 100644 index 10a8c79d5bd594c25e9178988c1338073c4f3753..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 384200 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 Sh~sBi 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

    WAq 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 diff --git a/src/discof/tower/fixtures/voters-398915653.bin b/src/discof/tower/fixtures/voters-398915653.bin deleted file mode 100644 index 337799c07c581b70157007f2577e0166602697ff..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 384200 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 F{67`M5bOW| diff --git a/src/discof/tower/fixtures/voters-398915654.bin b/src/discof/tower/fixtures/voters-398915654.bin deleted file mode 100644 index c7f3b9ddc249358e5df7f2d8eec8ad7ef34e0df9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 384200 zcmeFa1#pzhqIR7L?h@SHHMj?YTOh&REx5b8ySoK zS6}Tq-0x1R&dKhzUV1_X(!Kf}lBK09Cteq(R;s+sceKosIO)?$H_G?8FuZB0f(cgc zOp|AP-F3C5?S6WuU&ozm_V@dEyiTcoM_T78+wF49ipM5}slK^rwBr?%`seE5;jtmA zhu%Vk{QpuHbMB5FwQBax7Y{#O}56>f0HaSMsx)|(8SNmU8Dz491ZI%g4&s|Z z7CVS%23hUEJeVV!9mFw%>~;{_47}|imKo%*11~ekX$LXQAeSA)FoWE75Zw&&*g-Ti z$ZH4YS#adDgD7T@-wq<1K><66WCjK8Afg!*vV#a_P}mN_n?Vsf2xkU9b`aJKirPUK zGbm;Uq0PY84$PkhM{zswG=ma$;4p(U|7JqQ9zo{%kT+LP@Us7(*O^bWd7EsnKVH^6 zUH|nq#qus;Itk*ZTHZF#IdS|n%QH91WIlcm{B+Atv>qakpJ92Vmp8@nGc7OQD6jdX zc;IJQK4!{far|t{-*tE^j-O-s{`>*@FZ^7~8+mTkf8pm@-uP$&^E7$j=UZONx34&U zf#qo$-4VwxwET}c#m#f?fnQ|#?$$x#_{El2E*io7!STQ^u{=j$pg4Xhyw_=QJbylB zw$En%G+W{5u!0t0$P^ zadn-K{R`gG7S+a<(<>U(y;UB)Z)N$jdY?7M2U;HMKn*{0J-)T&%W7>C$G5Tk^y#>j z%=P%TmS@V+MI7JG@+tcdiR0T_etvcQ%4QyX2g|c;?=6n+Xu0=~TjKammcLw7+~3TD z?`(PK7R$u(T`X@N6eYl1kMC;voMRou@!c#h(d@D~zPsglBjm4Q=E3){{9}t*;`pAH zUkUeJ9N)|G`XB06HS^$mTfXi7K5={>%j4clQO#VB?`yeF!NKDAewG)BdsiIa-||bf zi&r=E;0IWqc1e&pexT*aYelPJuE!6uyw<+1;`qUqdo?>Rjvr!qkv5rXntAX;Esru} zlsJBv<#PgGh~tM_o}fwPT4o;n2+PM6*es48Y5Al1acZ0E@uMtX;niInKicxnzSqR@ zV=TYZI8_}p4}PrWn@$Z8$B(nz|L|jR{CLY7moHb>%!8j``SEgV#PJg?zwthLJ##&N zlI0mwwHL>864ujkRvgbs*uk?|>zjG-oP^ztGEN-NN!a+}e~IHc3ENz)SOYT;o|CZl z_hyOXISKpF=)E|eldvAkD>pRr;5i8!Ic}Xeo|CXRb)q*i*W)<}YmuU}IG&TRS+6dO z<2eb-+q*zxGY_7Vu)0g;isLy6d$`Kc#9WW(By9NMCgONb!p3<2A&%!HtZS3>P0c)b zPQqSw8!e9KB&>6i7vgx<1^-c=&CERbU!H^o?C4g~Tr{U;BE7F-|IkEla8hG@Rm=M~ zPg2TUkFRF=ie(+e@zpK=5`0P=U&Hd|ud|jm^WbY*{{81faeOVVFZ4kiU)%C(U8|Qd z^E9`gCNmy{vCy`MZ}}#PLlnpI<3%1#>;VndQNL-NfZc z=hy{bA2?rf)x(+*1AULRPx~y-$OHQ_Oe@$c=)=l(!w$dcm@WRM>pxDkt(NrrmIkFe zPQCeP+m^`b4}9(a_j|yu<{W!GTH~yfdPnWiBia3aPbSpd8NI+CGl!*|IXQacRbfh& z>gnn+UCsZ_;p!f+yMROP!2Z7XfL%So>~vgR=VMmPkT({&=-0-lGvis8v+6wsyywts zjqw!l_B55#o9pouaE-m|#PJmHla$djnCtNr@W@VW#PJmHv9PK39y|qH;M@{%JO$jRclf;K zdOQWZd1Z5PJOw;* zN#b}4_}uen;&=-9Rgp3U%shAsxK)m2;&=+UPK59U&GmQ+`0B+#aXbaQe$5$iJO%tM zPmV%n9y|ryvh`GPJO%u<=Vx&|1^i)dox)}wJOv#2+#Yc}1w1!K@*?JXJO%tA_h4~6 z1srdd-q>{ za=k%X?=c%1T&e$dd6PQ>&hMT*VnTuC+djv*^1Aci3)t112ghw4Ht79;j_-X!y||I7 zy#JqJ@6?LXrcjmhgIk^PJ{%#GtH*RTzxy7r^Yz|QLe`7_k*|Vw^#rriadn-KSusQ2 zSS-`mm{ZJ*qf9EK_bisz{(7%5p2f0d=xV;^dOVBe&~RJD@sxC!A90JD>+zJdU#)K9 zcuKnAnM>k$O1fsT+$GFBcuKn1oJrz%N;-A*<>Gis`suKb;&@8B!LVAo&4H(++Z^64 zj;Ev#G)_^oXMVqUO1ev;M&fuD%ite-#qpH% zw=2nYI|ENiCrmy>98XCNOB}ig{*=P2zZpIa`IOdaVPVVlG;vxj3F;4i&nuIGzH&HEp;!o&xr7 z{a74N0msf;Uay(JQ^0$|trN#nz~3i%RWr{Qo&vtJp{qEa0v@scia4GE&T_P1bu$m1 z0{+rshB%%Aem?7~IDV=9{9jp6uZEciPXV7Fy-ys^x)9~X1#$c@3)s=4gpLtBcfQo| zLx6p-8wci#lgt~8TXj6 zLmW>Tci)g$#|U`J`0dfY;&{q9@a;`;JY}4)ZxJ0A;3?zeVHSwvDdTPzJ$%fQfv1eS zbZQ`ur;HOO+bfQzjL-K>Qq;_Yr;JNp>?4k+j2{-eA&#evS( zv3G6;+>5<)Gw`2U?0=U6j`SvRoK4}v1g+SWDe>;cE9#|NKJd)fY~^!4yK}nTi&4!& zMS7F4@V(l1cBJjz=Iz!$?r;0zf7NgM`Vx&sot}IkR+Y0uc9%Z$wp@?D7qII&lV(_R z%5i^9vPR!82e#<=qn4ONR z>$LKQEV7jA(RAx{53gftKfR}1hlh=!dvbWn^-it!;&{q+>AN%Hc*^xov#h!&ho@XG z?jJ3Vr(7$Qyef{TTzh2Cn$v6!@RaL@f)mB@EM|cN-;3iZ*Pm%>>0TY4at-s>ZgD*2 zIyY5H-IK#pu476M7spesMQ=Y7$5XBe2l(YNA3vUQ-EeE2IG%D{)HkN?$>Ay2bZt9{ z<0;q4=P!!mS)AiQ_5SY$H9jImT18UR|1s<5}cZ zl{zMlr)aM%$e_(Jo}w)sd$c&7qV+HOQXEgw`W?-!O*5XNZPIV5IG&<)MEfX?r)XLd(L(H40;O&m|r zmX7{L98b}9O;ygd9?!JOv!DZ*iT3;VIzY6+z;73OMtIh&u7YQ^4^rv=PTsz!9sQ z6~|M+>AQIABn(dh9~(PG98Uqi8TL^e&$@8&T_l}^;eT1cy(i2`Z=NJS-(q@C=`IQN zT4Ow=yYzW6yrn!#xvy>?>^DH`OqaVu4U{|`eCh@k8_@0vSpf2jM1fwq%L_leC5g)ckJ!+ z_X2h`XVgOZzg2$!wpiM~ici0jtYWSG(cjEl`nH6lX@bIm#iE@o>gq9F&F?PY|GNVI zkAEjDS5GiI9aq=+m=!bRjYaOvqE2zmc>S7~dQSm&^SYoho&v5~KSw-sJ)Q#2^@IOU1w7R&X)<#? zo&t6>9w?5dfTzcQB95nkqjj&G+{}ZgfGa;;Esm#vv)v1u!d#E1fFFEqDvqasw_H3b zj;DZ6oywTf%!8+Zr!5{Qj%Sg}AO5vCo&sLd+dq|=2TuW?Td`RjPXVvK{zDv30q-c% zAhnqXPXS-deNY@v0T0@lI*qv=PXS-tFAao&x?9Dw84# zPXPyg7%7gYfU_NaB95nkJttRGT;VC;uYnuH@f2{^WMHHR_K6_z-IGzGt7b~pkj3M>+uxu>I%!n@f7garjcTp>+uxuna*v+ z@f7gOwSS7^Dd4w{y&3bIG z%shAs_)g#kaXbag;*WQ44xF0@_vXO4d2nwIoSTP#dUNo*6mXNiM;*;79r0^a_VLA1 zRm;p$qwMX2a~bW(Q^$?znCTW^3=bVF#uQ95?O2#D?RR9hg#a z+_nRgKaM+gU>?kI*AC3rV#hr@Fki?W_wB%}4vq(QV3r)mLpw0*mE(~em`}3fu^pI| z)$x}dm}S}V$_~t0?s#nnQOw|t9Yi*Rw{{T84Bpv6L^F7A2NBHRgB^r7gO7F)&I~@; zL0B{RYzJY?;ENrEHiNHrU=};aH#_h&gYS0WFoWQKGa+LScAT!BV0NIcuJbXIhP<)) zXm((m`7L?0+FwoY*?jnKPH2AL9(Xn%vCj4t$FuoJbnlipp3R3>6d&`+^uV+Em^g2~ zIG)W%_q3konef1~`S|pyp*Wt+hxe@g;&?V6$;&1)Pp=1_%}2TA{lxJczbA-zOB~PU z<5t(g=Ff}=p3O)2BXh;^Y(9F7{vnQM^Kn0KJ#8xSY(BPJ-7Su1^O5Rm5^XN=t?VZ0 z%7uR7cupq5Ww|Ae=VW5VqQcsA;yIb{iZfRn&&kB`c3;KuoJ=(LFQG4LcupoxR_ZQ} z=VW49{%hiRP9}zTE38c?o|B2U@fL~W*?i=@7*?B3JSP*kW49K^b21Ud`%iH^Cld`$ z=hCJV&&kA~Hq*uNoJ^ED_*op!$;918wYBNQb23pWV3#s=m2p% zClhsM+!e>O`Ph1?h`vuvfKhgs4*<}F@oR6>_okm2UJb? zx1WaOYED<))nmH)^}jA)_fJFmAM?|YTs^^@^}D*x$LwrE-YBgf4}G;+`F!`THa(P9 zuG@>n@s!q?e4$m!cuH&MvBu(fN-MbVL2*2bT)An(z+7sr8u6_deYcm#f+!4y2RNbj;FK^_D-%c# zTg8m0w7k#H5XVzm8BTl?$5UG6<~L9=<0-B0jsxO&N^3{ElqzF9rPX=vP;oq^bu0cE zar{_&k}$B~{G$nU!TmGU`ZIv{Bj^*pi>=eh(wY+RB=W9Xm^DGa1?tCo>e!k_0`aM)(;}=+d zctsf%Gk&4vQ+KQq$1k${`qgMEX8dByE7s~Lj$dMVl^GYr@k=cayCaW^86RYMuZ=Ur z@vIAe$<~PDe|Zmh>-bRSOx7cy`$BUZ>%lQ*u|18&@f>58u6{@y&oSopJE`^I;5o(| zu*3P+8qYE2()tf{J)UFCn{kVq(_;@j$C!f$EE304kZGQKnxkhAd=vXgIUCqW9N*OP zYo`u~<2lAGe>#O0Lp;Zr+eQx*$G5Qauz2F#i>q_-buX^Y#n-*KIv3x6dU5?-3etQH z`k(w(a737_H3R4E8F-{#;?ap-j`i{is9SkhTi?)K%*#rAH8Sl>NSn>zlyx5`LXytTP|p#gWZsc9DeDdX-i^&qmmt%#m!OK4$O+_@V5iA z3_Aksz^vhpDt2Ht29BzBV0IObYIb0@Dvs)QU>?j-!w$@*%2Cq}%#O@a%MQ#o&r#bB z%-+#a#}3TK(^1zB%x>0E&koEM*-_sP%>LTZzz)o&-O=#zHOi#n1#;K+78Uq>1bmI zx^G+Y-v%MlgI%$!C*54@esg~Y+@AsWXTbd#_}}*#_}7~5V}I`zY*D527+1)kZ!f&{ zz9Ple@`c8DKg*}RDqq-Kf6e+zmXCh8SR7y3^7=s^#qs`@PxY%%#LR;auzW`2W#afM zmZ$OkCXVOcaE;*=e9Sy}?hO}fus|Hoz2ODvzls0L+xfHZsaw>{gRg0MtM7Zo@w`B( z_U)v_%=LI)pfon#0C7BJzqP>~aXe+eB1$n|GY_7!kGx`$IG(Z}GCfprb3LB2A6c=n zIG(Z(j($KKPuWN5mb`?S2T$1_y47DCPuW)vxGj#S?B}fXDQV`xQ}&Jp^TqL$eJ>x6 zQs#O*Wxs7w193cMUu5ilaXiQ7KhvcsZRWvqZ2orG0C7CW=Cfno5yx|EezcEI88Z*Q zt$p6>9-kwQ=h%EhzGvcij?FWqE?U;igXh>hNBYI$c#h4RH3?VFT#x72yx)&NaXe)| zZ^{{QJjdoq&g3j_=D~ApUh?h?aXiQ7Yuo)0$8&66K0?C^W*$7p<{#@H5XW9Z|zLL2f-`_r8XRo#r z#}BYPD8*@UJjdpt&g80W=D~ApJ~z`GaXiQ7JEL9|#}Bdde@&9Z-^_#O*u3}8@#1)n z%|~2#D~{*beBg;{0cIXN$L9Vsc8KFC`}7wRRx#J(IW}KftdBUJKv=VHwm6=u7+Z2! zOEb@Z>g|t8AwLCpai!ef_Pohjs?pkHHUA9X@8iLwSBIU-@p)aP728+ejTdj_(#S2x z&&`r`;PRQVx74q5XL8#bu>zZ|T$QqLs$u;{ZFsRGq^&4m^R&2{v%%1cYsT)W{xo9W ziAT13G}|(N@|Ouq{lfN(6TBr&vR%EByLwDl^SizW?EYy;AuGVY|1>1eLZ6)fb~r-F z^Y(`|=~_ICO7c^| z;&>L7dU@jNb^@M7<@DKJ;&>L7YlCiy<5^Uatu1bj9z5_YDiL-r7ss=xR4EZvUk>ps zDpO85e3TeiO6cu5%wdQJo<(Kg(#hg@7L~UXKZ@g7RDy=p3}fcOv#12! z+98f-QR%cdo;jZJz_X~l4&6%}&!SSU%1v=Ri%RzH)UY{;WUvWJ5 zVz(r_BaWYFuMaxmt9$u)?!_MXx=r^T2a2_Snis;&|@G)_%HQ9M8Sj zq^S~_Ger+P_hNe&>>`fmUhK#Vr^WHyi!D(Feu$KQ_!P zwrp*hHxJr8c;@wTcB1vYvV8W+xF>S@>01)by8m$G+T(ebOi0u?qDQ)jF}iNaU-$dU z?rn3;OLMww=$8ZkUcj#AEYkd8y%fO&!T`( zt{&6X{O$t&AO4-NTs^_;uv}f|WB+DxoO|_jMWt@SJI{42&2jGGqiezK<-N4s7acp}Deq0*^~#vv7oPGy)$^b@p7L(CF`bT)@sxLrW@E(hly{QIuf*|` zcf`QTI!4B`$x9P)qd1=OzWdor$HjQcJIUj&;&{q?*`r`_Jmp>3C%=x7@s#({z%#IG*wj=kHU;%!8-Amy}*8j%Qs+ zuw{!l{+H#wszX-YV?LSULP0Zm6`S+PuM85$SGBy+)r;czYL;JHkzV(h@zpK&n>tt= zU&HcjK@Y|8H7#$szKrfM<7-)7ccCqSob`y4<1bac~19U`UhtsUy~%`|&L&5NC?McWG_JU4q^o1LmfaK>=QFRmTo zKRdAN(EU+ko^EshP`zczH^rGSWW3|}<9(Gr1(^F%+ygfM?P|^}Bext{6=UR_UhBRl zjo-gsxm>SeXKlSQ>DQWx!Y{d->{z@1dKGk(InHm|n>1urM^rmVVg}LdAh8)lw}V7x z5W@}p$vjcMw?uc&(<`mtL zzz)pOyd$9pm4x*SrGCPQD2FdNf+|_ZUumf{T z$dS?x%>5)sDmySYsT`^8z})$Aq_G2Y+su*H4$Qqc&vbTRZsa-A+ksgqJTusVc{&{# z?ZDixbVUBQLCE-DuYz{EqZ|={4`!nGF47fi7|NA}zW(hZM4j;Yq@Tg#m#BWF` zm1W&2PgSB7Ew8<4sW{%x^4>MVs37r`EH7KOt~kE3<>L~s6UX~oK5Kdyl_frad3rVw z$5X}~8f+3TYdvLrGHF=-mhhDEjU=_i@s#l;kImwE%J@-(I4Vm#WgPEQcX2#r+~~$t zaXe-06Q`hx6Hgg$PdHN?PZ=LP{Y4y48Bgd?$Jabnc*^)=qg~>7$~a%rL@G`^Wjy?G zPjNhDJgDkbaXe+5=5Rh8N8>5u(S>G+<0<2sGd_#sDdWMDYw9=}PZ>wewp|=g85hVI zU&qe)f0c1==P@;&WAmhrYr6ib^&Fe;Y@A=m&UlW^AAX!6j_26CM9a_Oc#h4R?yIKb zXnZ^S=X0vxQgJ-T=EDnq6~}XIe#^I>j-&A$o7Wq>UmVY|`OVa6bR3PRj1!(7A&%$R zd{Wex;&_hDOJ=L0<7hm`=IxJc6UTFGo~cPf9XsPWHeax}w>X|-^Y688isLypFYjAK z$I*C>&BLZ!D30gY{AP;KI(EkQwV&tlotui|DdWV24~paa+w0p6O0DB)Jjdp7&JGvH z546`881PCQ&$0Qd`_*+Ejpx|B&aKJfc#h34ynHT>=h(cpYvF`&rWv8BT#8@_#G={U_URBRs9ZPw&m_d7Mc8LRiAY3n~99+POo_7(+R zj%>JX-N(E~S4FuK=JUEuH_F7@X#OHmz~=OEcQD=87ie zPQL5^qw>mrbD9=D9NpDpx|-j85BUH19`Nt=Dri?vFlYU)uJf^ft60o7w)ZGt(6k}* z@|*W8W-(hf6vwleZJo1E9M58wDqAvb*6=K5ZD;fo$FrFAczsF`q{~i&?xvGsN*MWjp-S%l2H#qlg=YZ4~YrVY$;&>Lb$-Ba8(}rg;>({BRIG)9JBi`lR`6||YdvzWcExK13;Vm2X7EN$BGEN0h6brHw2 zm@WHxNgU5&Rw$EmlZWRX@SZu&e*%Z+9`Ik`?rD>UA7?iKBON8RX~T05xWlMr;&|=> zmmU*In>IZ6fJf(TD~{(LuxE=?;`qt-ahI3OtW6t!isjYgj1k9AwR}L;XX5y2miHc8 zQkyyabjz14nJ12)VR?aspT+SrEx)(aUz<7nEXx~zUL%g5ZTZWmQM8%E&#`>Wfi~j! zxt2#8|ED;9p5-4}uWQIUu3y|-~n;`V#}NS zl}ej7{1VGwr5h%WUuyZI(odY5KkI`m_dH%qn>jq|!lK;O#qqzq2OO^CU2pT3zxn5( zdQa)jTAw?I;dn}S=d9u4cuKd!!Rz99N_W-8JUPugcuM!_=^5g9N_WiGZ{m1Lx7eTc zbD4SYly0Ag`^52-?w!cVbDQh&lx~kX1H|$5>?iR+%6sB?N_W=lVtLFwcuF_%*oETw zhISs`e2%>4dOW53bV>tpd}Dk4zE69_@sw_;rb+XedGM6(_znHU@s#ed`M1RJlcc_ka)F+mokg#?=9RCl@KE*Tg9ly&j#++DqAllx?6UOwN9c#gp19$FT zc=+P>x{JAb{Jnr(%^80Cg9>Rot?W{2@!d8n#;iy??ozA`Lzj(S&~V|gsLzxCXzuDU zUCr;hfF0@VKYL9Q@}<_1-VPF*K?XZWWCj`SAfXv#vV#O>kl7C6n?V*kh-U^_?LhaF zv)O^}Cug?<-B0$mgIMO*;b0Dd6DkX|fF7%0HDhxaYyvlQ^IGzGtllrMRo&x@{)K7(hr+_z>+aQjo zfcvlXQc2(`;A45Zh~p{XN=Yt><0;?{j(jQ!JO$h*)GTp41)QV8H*q|RT;?pbR2X;) zcwN3-;&=*pWT&Jm2|NYdAmuP|JO!Mz{R?qC1swWz29*S!0uFdNNE}ZAZ)ki+98Uok zjZj>Lfv14OIfBIT6mZld5mgd+7P+^{T8rZ;;EKafiQ_5Ys*AI!Fz^&`yPVy{@f2{S z;m%h_<0;^e0}2M1zn^#tcwwY@;&=*pTz==Pqwy4Q#@h|5n0fFN@V@B##PJkx(bq|< zn(OftaNxmy;&=-9Yp7e|cnWxazI@fpJa`H?QrZdPcnbK$#wX%<3bPOcnbLY zg!$rl3V7&%pW=85xJQlpHOxGC3iv~kgW`A!_{57eHO=*S3V6=2k>Yp?_{@&y;&=+U z!O2Rs%shAsxX!DM;&=-9%Ziw_&GmQ+xZRV^;&=);fAnB+{J#n~V*Wa29z5$pscsd; zJ+1#`0oTg?QOA;rQ_j|V3V2#zIUO_NDd6+5=ZNDe;De`LiQ_5Y6jRIT_z_P5_o};E z98Uo^j2m6YjCcw-bFxn2cnWy-*Yo0d3b=OMoId80f~SC^_LwY=r+^ebj;DYh z^mr|fr+|kYtE5F9PXV*|g9jk*tgx$?c~>2&xX}}QaeoY^xba9zFa6` zo74qf6?hZ;-(Ll74%%GJnf+7fIhoRYTM#vM!%3;my?OX1M~sbC)75<({ae_#CB1k0 zx_V4k^Z&AdJzsBZ5Z8QVO&an)$0s{b0e`jw74R23NMtViY6mLdZ+4&p{%!{<;2(CN z0{&?S=D{2uo170C#|#{HpaS-^0~K&6J5T|KwgVM#7&}k_hqVJ0a5y_q0f)B(6>tPQ zPyt7@0~K&2J5T{fwu6Y~V~b)35zHW}9fUW7Xm$|J45HgXSTl%W2Vu-0rX7Sf11~!; ze;ORI?7-6uV%vek3|`q!l`-axou#WMn4PAp>wGk;hX1#@(|C(wXb=Y%==R>@ALGlaVg-b#XkKjQuAIq&M^6 z*<_sQJyRUdCS(59&*FGC8JF|a%wXohv&pC%v`rk(CS&P`cp1(0cs3csE_4^iv&rxu z6fBNslhNXGo=j#QJe!P)7pIHkIf2M~^0PRe6NtBIYi2g{;5mU960}Vm&k01Vxbd=> z>+zgGRA|>l9M1{Four4v@ti=!j}$+vnFr4a#CNaW;&@IVzWU!1$9FP+?nB;gyz$Lu z-s9P1G*}ZPj^_j-`s>Kq&GmRrAa2I(D30d@qU@nd;&@IV1~$s)ZRWvq0?~H&OmRFX z5XozN6UTD`(YI@m=V&k019r@`WQ zP9RcdNS5EsgXaVy^Y{VccupX;4!SRn=L8~3#8L&!Ja|qZ7KdLUj^_lzZ$o57Bz}^8 z9v9pW6vuM{(Qx+(aXcpwM@Kqe6NBdjBG2KGicCD~MBwWC;&|4D_AinvGVv@r2d6J> zX?|aN8|T?CmHCfmn^WpN1zh?08IAE2u;;ntsm=9x3i$D-F5-9!xYy6K;&=*}#RubBj_Im##D?2Z`xMTaQuR*RJ)7AXHEMSNG z=K=qZxd-g(3FfTd)pb5*#}e{Jfz=7sO7~E^I3nmh1yLhMIJ}R@f6to7wIaR$Hh}%d2fso$5UX_Z#@^sQ($9q`uUl8@D!MTm{sC<3T$Dy zsJe%Wr@(s5>L8A%z~WcFB95oP?yd8wY#tX+fpshA{DB8}7P*PlKInQp1$L@@Rev)N zo&u{DbDKDx0(%iLq3)sLDX_42`-R>V3aozfJk`xScna)e ziD}|^3hZ*MkK%X=?5%&b8fG3m1@@rtI&nM&HgZ^mn&x`^4Ey(@ZHq?Y_?eb3y1G*w zKg;qSUh!&~dGNC>PxGv+IDU@h{ij|O$IrF=Nws{n%{=&dmamRGM;t%j@`n>V>X_^C z3oMVaqOmxBq2+Ps9}&kdvi#Zd^mWZV_{Ek-jx|~wzr^wtJ)eu?ms*~9c%^z~9(<7H zXM1iC$FnXJeRM<||I2&8w_BChJ=F4vUMrU=V2^9_#qkvIq784v@f5K4pCx_F2ZyJC z%RN{ij;Daz-wvgFsCWuE=80zF_*(WiS>gUMaXiPftDk4kJybl$v;AX@6325q+i~$z zaXiPff2OS9Yd(HF$Fq0etrEvKupdvA@sW$0>+u}V)-Dhzj^}vxo7Zu1Jjb)`hovuJ z=D}0I`NEA5$2YZ)`=-@j;&_f{g9FQyH1ps&o=tsbnK-_MorlFA@7^3ZHxKU3fphcV z-W)hL5C8P$;CCtDea(lxtJ|~h?eha;`PQ5Bc(&i=R-xk7sx|NJ>RdyAu3Ygv)6=NW zCQch%>W>R^R!mxurq+@2pC_l;lxTC9-T|o^`en|3D$C2my+{7NfL+b$%DZ|@SHJ$( z1-!c1&ba0)rS1W*v4bQbz_Hd25}Uy~J4j>(>+K++8EmkF1ZJ?&4&s}^COe2{2Al1` zJeXsP9mFw%t#%OG47S-pEHl_{2VQ2d!wzDa!A?7fVFtVGAi5dswu5M9u*VLfn!#Q> zh++o&>>#ok?6-qRW^lj`BAUTLJBSc62zAH~!iNkz58FXFbJ-C)2x|s^*g+UGIBEx> z&ES|Fm_^5N+z!m0UdIVL&^_Q)|7Jp@2Rk8GPcSz5 zD2_~1MdgQQQ7oK(vpAk&&Xh8a$`4O5|IFS+98WP<+Sw2m<0%u^1fSNY*7 z=7RUfisLEfYXjek2fd~rOB;^Trp#PJk!=+@3x;o>RgP@4~`2=Ns2h*lX@et3$x^`8^P z@f35e?k~mh6!Wv8rB!}-iaG11AaOj!9B+C=6&{{qJ~FP2IG$qe*X&PmJd5JZecmcR zJjL8I=VWm_#oV;XM{zvGd|c#1jKyhIJn^>~VT)tY|dc#8RT^tE zhqo8Uv*^UjaqPeD0hf&Q$k+Vhc)LgGJq6rxb^#R|o&tV7$@#s7@f7g7-FI|7o&pZq zQ&7c*r+|N+m@AH_fX|$DsMzonaM41}@92c5fKPp!tLyO;@YupX#qkud$Fur6=EYOM zdB-?^-YuR2Zq+-vj&t!8@YGz+f9#K^fZq(?tn2X<@Z!(0bexN)fC~q97ROV-*B_i0 z$2YZ$giooQTD0*LFpD|fz1TZ91MbD%xfyUT_Rh_~@3z?gE(IKVQ|U`JH=HlOVRy!T zjmH1Eu>JDq1LjUSesX5WT2yX4$my@!O&ytV&^>>Faw{yfaq>DJofhjb~Wdyh~Mk}oR$Cas$J14`Ta-`e^kDU<$YH_I#nT4^V3UT zw*2hsFjM7wd%*6W2OJWkzwf^TcJ%}=vx}mu>wL@(C*+Ny9r0~r1~Y#7-Cyr1 z+JnzyWi%X5(U$k=ERLsWPai%nj;Cms=E{-D%!8+BYmOT)j;Cn*CcG_&3O|0<5BXeT_apT*3Br)UQ)KPZl;XkR=`m(^U4r)ZCc9xINgXrnE7 zBaWwN8yBmZ&CG+RXdg}9B95nMgEPm=Zm!2uwC`i}5XV!rS3X}8$Fs;?dtS)f%!8+B z7Zjc+j;CmIME)+0r)WR=*3V()!BezRyBrY5Q?%#fWXNf*$5XU5TTc+jQ?%o5Jr~DQ zw4e5r%w^`mQ?w-#I$!;Zr)b-J3ZL6tk7tpq^jDxbo}w+i^Ncv2q7CrMp2y6Cr)X>T znJA8@XfH2#FOH{ZGi)iG*UW>bX!pgLFOH{Z+dcNoXRgOnv|ja^isLET&3;G4@f7XL zml^V#dGHkNo(Lnw@f2So!_F?M;KIVEnMSG%cA8|ZI`~L7PaXdwv{!Y=NW*$66 z>-%}JI6laJ{a@xZVH9{*W-=jI}%3N^@A=k3{N6V^^VQ?By* zna%2XzuZ!6e7(urAsZ}Tk+`-pU)}c&2!W#f9>gi|4vx0=G<`YR?D2l zPpmuGXyg10O}3QV>_6x1(Ir72!J``EDc~`w)9F|nPXX^;J4_r;0iR9vNE}ZAuO3*u zjQKe56!4vZ@#1(2xSr1gaXbZ_xm;--gX1aS=o40o<0;@wxufeIHl9VU##iU7pz##& zg?v|ZJ)Q#IQ=_o%VdE*_ejl8#g2q$85oS7H1&ybGlYDW$3K~xV-&0r%hkSR79QpU7BN_ptF4aJdw# z#qkvIlts~W4;xPbKUmOF98UosDR)sEPXRCKmeY9;+joz@@K*(LHQD1zfvkeQ`Vm-0S!@aXbawCRFTN=IaKY0`|DxNgPiB zr&@4c98UqiZJI~-u<;b|*L1VQ@f7g;Wk1C66!7&!4RvoDPXT|~b4VOd0oOU5wyyd3 z@f7fb^drUb6mY*P&&2T*@RA08x`&OYfXh@{FOFwjSbp%ZIR4iKoU)X1KH%0<9S=~z zk&n+3_qLt_?)v9*aXbYa&8wJ?`Ay;};7cXvh~p{XQ8j*w<0;^KEgC8F@f2{Z<%h-b z6tLf&G|GHD1spcjFmXHuyz2BLaXbaQpi>!N^YP;;;A1V8i{mNafYcFm&l^tx7k}AY z98UpHZuo~do&vsoE{*Pm<0;_renZ9a6mYur55@5maJS_pbGjrnbGnpDxZ{QAtn&-Xwl14|z|{zWN8?of`1gCj zuI7wzH+H^em)EtfI;Ya4)3*xk?pG~({;-sIGzGdaNSSEhNpnX4c#D)r+|mt^HQJOy0sVStJaPXT|5Ge;ax0Y~cpLmW>5hilhR#fGPV zQ@uYZj;DZMzE0yTIqNClm@`I*<0)X@gOA1W6!4h&rB!Tr3b=CHW#V`W_|^PyDl;w4%shAsxNW>m;&=);s!!a8=6XB@{GfDCaXbZFuEh;;JO#XWZIMQ1 z9y|qn^vMEoJO$h=N|?sxdOQUjHf(coJnKU4PQAqOzbxRH_rIy|2CQD7_Z0BF_LWs^ zcnY|D)kWfX3V3k0kK%X=cHrzyO{C9v?Z8j&1Q2aH4L# z#PJmH9}ljI<0;^P#`$%ei>H7q9-JwTr+^3F{w$8Cfd5!jQ;R&F0%q~YyEg~U&4YV$ z;M_d8HwVtm!#}+__+1LvZ^W=Bem4eY^{fBKmz>LI#8|c=cR1%=vm1MDQ7?E)Gmjs$84i9+0uJA-TwF5`o&qjgd4@Qi0{&3&g*cu9ZeO-?JTniT0Y+$R5Bas0muxOS-oW*$5RyzuKvaZl?h;F1fYCN$UMDd5qe+Kb~U z;D$^76vtD*$)_XH1-x>&M>6xccnUbmjr!tv3i!+) z2gLCd@aw&)lbd<)6mYSABgFA6a#E;%?u=$0JO#XN`*d+U1zi5w7jZlV+~Ql^OlBTD1)Sy1UU59@!rF2d#PPo@ z;GwbVL@|@Le(It36!7+z>omqwz}|^GqnhjS6mZ4p)x_}>aO;HY#qktygh*b|^tjej zz@eUY5yw-&cb)}{<0;_6@A5@A^WZ7q;n8M_<0;^@lfH=KDc~^?>%=hg;3?pkXLgF? zDc}Q56UH>x<0;^uQF@BwDd6nQuZZI*;Ent8dYO6f6!7CL)5Y-=aH|2I#PJkx+Kx41 znR)OO@Ujfs#PJj`i$C7IIdECM6KQox>(caBXT{Ps%mCKHaO z+}pIl&+l{Q zQk4sRnx2pL`F5C)uRj~~)js6AD24pDt2xs&4!qp%T$FE{rrhnBt=XtgJHsSiTP)Rt zg1P+?9BO?exSXrUbTz;0d%*4j{;dnx)f3E4$JKQ{A%7Un8;jyVuZJqs<&}o&J&WSm z79~}jcoxOFt%Jnz6mzH+;Z>Y?iuvQF=HhsYxkdRu#PJmKtbJ)zoOp_PK+?hDc#8Q| z@C66RDd5Ya_ln~w;I7kBsZjA0 zaPWW;;&=*pd#@MbcnbL0SAP{Mo&w(Hw^bZZ0edD)l-c~b##6ve3-uSrvo17!Hd!41 z%L1;_|DyIRr$UX>dw=_vXKeb6Do%WW<(=9O75|sFynm`|;&_U=Z^FzfPCUgtulWFR zJjHxu!F6#w#awKAVU;MJVy=2@fjFLGJ{={DiW5&U&n(wW98WR7ntenZPci=tNUP$+ zQ_R)B4Hw5#%*|gv7ROV}lh&73iQ*~d&wYc$@f35kcVTrLji;EqwP+@eru{y&Q-fPJ^c}q;Vz!!rV`got{iR{X)X{q6T$r`c z%9IIwu7p0@Dsi~K6|k#0U3piJ>FU?JE@1b+1OBZG*wquvPRG@CK4#k#@hnt1<0;yWV*)CedGIV+MS{18<0)F7z6mRu>+uwAf$aUn@f7WZj`zj!6m7%R zW&F%Mc#3vty*1)^inc|p7?sTRc#3v#ua4q)iZ)(~3*vZ+HgvRHmCZbOinhDQba6b3 zR;4eW#qktv0^i#HW*$66d;k1aaXdx4I6;B{b3LA-EgjfL98b|sPWC_?PtjHwR=$dv z2T##{j=EeNKg4dPqet|tYOco*wfx6{hT?b%IPJ1S;&=*p!SuA%%shAsIBDxq;&>Lh zBPXAU<0;_Id&*Te^WZ7qzR%Z)<0;^C@tyyU7Eb|}98|T2nFmh+C+fXT98Uos2%WH| zxgJjer*!lc$5X(igKmrCDd6DXBDKsscnY|~j(OsE3b@JWpW=85_{NcXwaq+u3V7l7 zZQ^(eI9HsQbGMYwP{`4%!8+B-%mOtj;Cmw zY|U1}T#u({2VNg9j;Cmo^?NOjr)aZA2`Fji!Be!;UTzS_Q?&c0#Vlp6$5XVE%XbjR zQ?yaHofXGZw8x`oD{bb%Q?$Va#);!8+6JRvisLET1kL@*n0fFN?UKrC#PKcc^ULCo zcW(}yn+Nyiz`1#FZw{QBhktr=@VgZ7_~JuaM@+eM*1E0*$E}_F>Bpwk#gdkIk-Brf zNgj;~f6CY~XP<8I=HLGkZcK%X`KLAg8NY1s)F!(hq>Ju2^5k;IL>sScnLFz7fB#Nc zuI8K`r?~I(;?)A9Uiuj0ae+09hUEMZeee4z!*k|L_Oisy%=275rmOi~7qI&t@NZqf zuAX3aIw9rL1>EJHe<3puo&sK#e}*`o0zO;jq&S`e9$X}AVKWb&0`}@VQ5;VJhmZA99M2-R z;bg5MW*$5RT-dQs98UpPh?v^PT#u)KS2P_Vj;Dave}5{Dr+~9osi4g!o&rvAZjCse z0*<&Knl_zy3V3Y4j^cOfSj;DaHhIucJr+`nEtf9>) zo&p|ucCR>|0xlaPtu~!_3bzOlIGzITF+93X zr0~=1X8O*aR^oUHc-WbP;&=+UO8vw-slrph+n#h4$5X%`3Z56oQ^3!&dh4VLPXV6_ zscnbL0Ip_Bez*E3iSJu=C7M=oLRowag1Mn2^gKp06AAqNTFZUXx6D&LhykqV| zaXbaQt8*!xRN*P$+H04K<0;@e=OgQ+3QqwaIoVbm&$`eh?;vsfFAF%s(PKHx7qzsP z+v+_9+Ed__xa5#e;&=-9alsn-%{+Jt*sI=F zaXbag;*WQ44xF0@_vXO4d2nwIoSTP#dUNo*6!5maeIvzdUMGLNAFtLVFTHl}io#9f z4%t{~#FhJbQlCrtImYnMgOm7w@p*mfMwf4+G7Nnc^Y-}9=}$NGDjzq(h65?guvrtLg#6Eu|I?AUW)Rs73fMs;Gx&Xf8j`Cgn4ONR>wH3fo92y0uKUw?Duelt zwyHEJ;Byaph~p{XYelb!<0;^&!OoJvQ^1ar(^U+33V8qg_u_a8I6}5!Dh50SJUh`e zaXbZFGW{2EJOx~$U0oFeo&ruB^ME*>MXt%E)G7l!1$-p_C~-UmeB0-hIKG?R}=*IGzG-{Ozqco&sK* zp}OvQ<0;^ZsX~5+UdS7s0zNd}`E!)<6tMrOhPoGyr+^2;werIycnWx4=bqwt3OMwP8{&8hxMY?hy626jfUCDzAdaViD~xge z9A!KOoMBiq-Sft?E{uNHQyl-x0$!IdnvMr=KHsS0Mhdv%&Bo$*3b7_HE^#~s-12<_U-R+fDd4?LdWz#I;Pxl4h~p{X zk?Zp6I1x_)*NZtr98Uqq^!hB0r+_n+si8$4PXV*|zlu~9LdpMb8R_Jc6!vt7Jr_8u;S~e-o4VbU3}_CoZL4m_b-`jf@8_->y6Is>iKsA z{@+-$=*Pr6$1VAAZDPKM2U1^dcKhDrM1hCzU9Xs;-0{%e-QB0V_1&KVd)@{8TN|*u zCzwvh-F4~^f;MF~xzFwM$2FVu!oXW`%q<37xyoGSa11!y9H+Or4vqom@~fvDjsgE% zv|BkG13u}MAf8zVjsf2t-%>do1K!kkuW~pB++?^9Z;Ol+=)W58XPH&YJBfK#nFrW}p|cMay7#H<5nlk0P8sB$<493%R3$nd{*g@Wor*mBTUMo|n!mhhxBPK4$hY>%cMK;AbZ) zhhxC+4u4h-$AI6@s+HWV180+~7<`vCBe11RWa140ZpU0KMG2pvJ z(xo)(z%k&s`Nk-RW5DMQyj2dzfNSiplFF`jVI0l>{eWA2w9XJL&Xv+-c@M(6P zl;OT8hhxBhe6Qwf)`4TdtIljz4#$9t&WM-JTo1>9KXmM)9F76^4S!EL90Ts3w`h8^ z4jcpSS8}OxI0hW7RfG)YdN>9g_eN{wa13~VytB&T81VP}Su>h-;23bHT$7Z;*%t~g zUZx!WO9QTSAaPW)=+^-UqnKL^_+hn zsc9};Q4Ys|i{Hu}-K+!0fKNx8tsIU4FDdGbVXlW`zzc@fQx3;~7i8P39F74ux|}qo zSqF{*Upd@QIUEDd*zc}#I0js&Sb(2Tyb0Y92hzfvb7=r<;S{#(+N*uTeMB z`+mJ|&GQJ6SXJ2zdGg8qxbcu zPw!D<#mNjc{5r;p*t*B*<^MMWcE4u8`EQ}iq^;d0>WjC1y)Rr{nBr!f9NT*Ky!oMj zwS!aK-DeR0Uw+N*4cPN8=-=Cb-8{i`I_|y^{LKy<^uZ>#p0mW5CIx1uBPQz}*JkRu0F2-`>qD!+>MJ9deFT4#$AUCBLg2jsYLZR78dW z$ACk2U7{S00f$TwQ6>RrlUsbZtt+=21Fn|tl5#i(oTX`Q83r5!9vFX)aySP3WPdQ3 z1RMhn@M)wRjsYLve^5Cb1HQR7g-imD0au?hR5=_2zB>Dva`;~c{IRr50?sBE`rtC< zT`k9eona%&B;XkEfa-0P!!h8Ck*+9*W5D-2`O75W81Vip)0M+9;3;R`D~DsiB_33g zVZbrqse3jlhhxC|w#1c5z%k&2V|povv&pUba8o%P1Fqg8zf1y-0neB=UpX8D9)9S# zaySNDFLT*U=IJBAi*0L_!!h7nv%>kA>){x1%WRF5!!h7qdv_{_ zW59!=d1o{0z%k$nA-gGuW5C7!yrLYA0bgF6C%ah(jsb@aoTD6$0XyP^$YHLBW5DZ6 zH&qVDfPdCFrW}p|d-YA1)2su>fLFa9tsIU4pU&`BIUECSQKd>Qvkn{su3B!haya|K z{vF4a!+&YOS6}DXnDN5=2O3*qz&*xKR1U|0H%)$|9F74OIiK4#mb4rLuKZ(?aySN@ zFX|WNa11zS{8}16!ZF}#Rdy?fW5BP5Cf1k{jscfB*H<|l1HRGAHI{^9zY;RG2lX@>MMt1z{MSVmBTUMp=}du%m~MT%RTR-9F74mEqhxz z90R^sCcnmya18ims5#2v7%-bZ+|wMmng>sF;A$Q`&4H_V_@|qL-^GBZ&T+oEQ#I7W z$KCu>JUZWZ?xaj%eA|zXcPT=LaD^Vdo3WzwrkH2HoQ*$g%b~E9qwm@@yK?Yi;jfoT zkuXp0|ye-TY>6~B- zb6n`0XbUrDbWXB`nNd0?+rr$LbBZm@)YLiK7G@OdoMQ_!-*wKlg&7Vz=h?zcoSpM+ zVaC+X1-6K6iiNg_WQs+$h-iw%wuoSgCAJ7}ilw#)XNqOE2y2Svwg_X26}AX%ij}qq zWr|g{2x*Gdwg_R0HMR(DinX>dV{hj=TbOSX=XzT>O)=_UNKoydlXmw6(^GJlhgY44KzEuX$ZIh>|y`Thv{s{^O0x>&ZAE4Q4cD#_JT%HcFs>zAe17#~hk6}ikP<#3v+ z>tCNMhtpJL3n*>=-Z|hjRfBgfR}QDCdg&Y9{H1lkX{yfkY@r-ZQx&4fapiEDs*c+u zs9A>7RF!X2Q#qWbDrK7;%Hd30J~Vc{CjidGC17t)HQ8_`F5z-rR}N?5a(%h0`Gzxb z=@opcnrt`|m%u--D~B_2iE}8Enrt`|ms@8hD2Fp~IT`b_ayS#0wwr3H`G(U}Rq4J* zIh={h`kl!Wndb%0#3lXpVanl5T#j^mr5w)0<+gW4HQ8_`E`z(RR}N?5QrbJFnrS!_ zmwDkjD~B_2dG+~HD5oRiOc>9GnB(=s+MK>>dGzeV_!EmhN-0q9Gr znYiSyT2vFbsg^TwspY#&Ih>}d`q=QAw85FUG+i?#WF;Ki61bI*&l>&5*4s~79v z#eh2;Y4T}UuQT;3e7f7BWQbDtBc06?C;j!Me-11(C)SMuVNWgYlzp(HUF(4tE^V9| z%x|kt$Kr>6IFGHmeXZ9MzYrhNf8HJ5c{fwgwuk{6&2hJ8%wt)q_n76?=~;^&{onP! zbvM?!Z$r}FXzdKwb$OM2SL=p!{mPM`g`AH@KTu09Ykh1BS?d#9$XcJ;Le~1s7P8jo zwve^Hu!ZT8ynB^tT0h%D*80U3vevJ*khOlZg{<|vEo7}fY$0p?X$xbm!5w2z+!x^jITysM?oU!WO zAsO?Vb>NIuFO?po9L`vE{+v6?;fz%em&;bbtOIAPS}fKO<#5KTi5gs24riakp;fz%Sx(6$2u7@*Lt?;V8ayVnvdPVmrhci}9 zu|H8UvksiGYTp!nmBSgUu1RZgKgt;EhShZG+ z2Fl@#Ri|~{s~pZ)HPQLRCCxf;#;OBK^i>XLtU7Pe4drmgst-E&lrrnU8LMun=laJz z;EYwHuvkn|nKRkJ!ayVnv_>V$XG}pr!tA3iWk*%8hP! zKKFWDp`7Vk4(u5=Ti5?GVDs(ZZp|HgKX&-IwtC82x1Z0AbhSv*8!@8A^l2S_S*A*t zv;FZp?Ll|<>2CdBp8=<^{|c1ZiakFE9P}9czW)-IyC-%iGO>ekt$9M0z9TOh0% zOE}JRZf=5%+efi&gAL^8>t-5 zncUajZJov3(5Iee7; z`41;3`S|k(l*7l{{6OcFW{By4 zPq6uqFvFC?U9S>Bzl|&FbLH?!_WGS0O6olp@X0n$P=1MW_!OI`sS!#8UHDX+zpU3t zIeePUTeLo)96sIV58wLeJr?j8Hm@>duyXiJn{WK`TseG}&98Q=sP|aFXWRVrzRk+v zb8LR&WPH8H0zTK~skQ|whtIQl;cxeq!{^(4ZHp3mj|F^z&7P7MP++qJ#12eBRo{xXu zBM`y2P~Hh)BF4UZxWkrdts|GqoUrurQQ0Dw&2fBc>kj)1rk)x)QtovLURO_=`|jXR zLvJ>1(e6mtTbIsW|CreCM(DjCBJB9NIPvbDe>Y%vYu5U-abJ^BKN5CnKCMs6Z%MA? zO7?zC)K#}KJgm9x*}nVh4!XNfck8=1;J^JGuvaSU#_@xm24`wp=sY=%Ep(oo))qQX z_O*rC;hpJhVcG^~dRv&kUd{}*FpZ2eqb+ovoXHlZwQ^>*h3VIvS!`jNJ!e*1m=4nE zXA9GoI5Z$W$Iv7ciM#hKR@Mthw3Y+;2C`oV$S@w zFm~rGU<;#$&VsfuEr7F-Eli)_q!}?yhco%Vkf7Q@XXEY(rqgkEoxfQ$=z}I>{Jpyx zBY(Obs9Oy9%`rPhwj2Yl;p-YB!!h7S`&?sWI0o$R*Py7mKR5=QecW#4a11z&Ph^dS z;TUj0lt#+o7;w6Hhn2%I;FNXJ6gT$^$AGs7AFUitlX3I#Tjg*JcxUA58W+Pc;IGAZ zDu-jh&Ywv%7KUTMV-^lj4#$8ShkU3Ujsg2GET*wA90MLSZi#X@27IP(_|oRj3&((c z0$VAEW5Bb{o>C5{$vD2;w~Sc_jsbUlJxVzo1J0EHt#UX9yz*l}S+fot1OB7hUgdBM zxN)&~8VkcQ;57L=Du-jhJ+r!gUosp6Ug({zyt!XE2E0D%RON6C`1RxO%HcE_lgrep zVAg?Sz*qO}Ru0F2E5A*uu`nD1_KVt5IUEC?+UulpI0js&bH+;Me&HB!*01A~!!h6& z-#;jaW5C;%RIhB-fn&g%uWnWj$AD{Zja|iD566JJ4DX^GjsbfeyQCbB0S5%jRMo5l z$AI5^4^s}ufcvhyryPy}kIS34npp>q0oNZhRXH33p4R`faySOuJD_@Xvkn{s4oI;> zIUEDN`7=>~xgL%Iznj!wIUED-cKo4oI0l^jMu{3`9XJNO*=wb8I0ihgcjTJpdN>Ar zerG%7a16NXy$j0W>=S!$WUFP?fwM0pEmcZ6oK5HKloQR&_4-KEabF(uA3iCX>b8>A z>Lc-D>ns^w+2-jJ{h=IQ#paJ{>{Je~YV)|Gx9>^d7aSnl*4PS1F_cRBt=E2h(xS9t~bKq(o{^{o6cX=1|)vg6b)k}Fde2g6VU`4NGwZj&2j#@r4+1xB^b5!jW zKGn)k$@@$jGbDPoyg|pRybIdAyt!NR=S|JMebKin=OrH-I4Hptg(k-!uMZ4uuTg=`@ME^G@Ka1mR`fQ#Bf z23*V*GT`F2Fn8uGVG9{>Nn6N(OW8sOT-p{g;4-$50hhIf47i*vWWeQZAp@>p3mI@l zTgZSb*}@!ZIV;=3oQOHA*uosMIjh?mB<7XwV0nU;Zo?WTF>ubQ-^6>|HR4%o2_n-+SC&IUF;-F#Mr%IA**sZAqCWoXs!hAIp`)G2@6UBgib_m~os{ zZIr_?<0}icD2HRliyp?6S;8^nP=P&_!!hG#+iog{W5(kK7m!)PG2<=o<|~I|#u+XK zlR?5U# zPAP|D!0$R|av7xM7;uBp6O_X-;Jk}JD2HRf`7T!}X#QMq47h!}P0HaI@U38RWR`FY zxaIvW%HehGleBg0W#w=T_++_k8Y{yw;5FMPDu-jh$5VVz4#$A6&Z%6)eEo0?c*wU6 z%HbIBc<&h6%;6X?n>^gp1h|?6PZQv35`zCn7sqe*BLiXQRVPoo=Ip$&;NNI0na1gc?3LFzpz`&Wmv-oQ8UBAV#k`)%g2`Rn%qce>kCIh?cJb$PBU zhjZ53=xAT z;DB@1n|pZ!<#5KlKA8?HhjZ53=|LLv1_KA2v)&T%$0&z$*6YagK{=eWUcU+C24-PnIy#rhIQ4Z&#rYc@2hFJ&BS?{047b=G{ z<}E$L_4Ai-&UzQLcm4b&oU`5%n-7^cZ8+eZ^_B~jMt`y3ob^WCK2|xLv)%}CzbJ=u z)|)YBX7hFt2b{CsBrS_8hjZ3DwDcn7aL#(Crw$v(tOFlxCqvh~TPlY$=AHHCv~oCS zy#=deGH+~gz&Y!!b!41!IA^_S-hEIG=d8E(u)OBoG7dOrz1!Z-QV!>=cjFT;^Hv)N zoU`7*c8!$7IqN;#_ONm|XT2}I)0p?~IN+T1UMn_KIh?cJ^C_MvhjZ5Z`CW1IrXUBL zv)=ESmMDjF*4yQeVCEe~4mfAMh5FP~4(F`*&lnq(!#V41I6G1zvkv^Pv)cnOpIA^_U`j1o&=dAZqtvAZyob@LDP&tWN2hLe<_7q!`!#V4H zIJSUhjRuzCRaA|wkZdk zGvI^CrYZl6+rPB$R{c~C=L~pWhlb|8SPnR6z#G>dQV!<~ctobu=8aqqIA_55LXJ=l zuWj#lWUS}P;hX`Vsb4m%SqIJ;aNS;Ol*2g#esVjiX)7FX&Vb+jXr~;`8Sv$g=as`b z1Mc_C&-5w|IA_2Q40a|XQe{a591&Va`}tD#LE-jv7LZvJpjbKq(oJk5ctdGIs` zuIAyNZVrBzGvK8uhSW`(H}dWs6HAs{ns~$eagDoHe6=p*kRC7UrPddNUITJho8QACVQjSj)D*R$LscY19rD&r+F*#7f(@m`-;km z&&&(l6>8q5mm3Nt{!un>^qR{PE?<|%-F>=S-($ePlL7x9{2Z{mCzwvh-F5zECk^^w zllwGxdP-Br+@DLgyxVzpl<%tH81U0lHB*`E;TW*jnQhA97;u-E-l@&?a16L_lP=2P z7;uAEhm^xH;9Do+r7`QkG2m+_dMk%xz~Q~_Du-jh_oo$2Yu16Y$<@r@`aQUC47f}2 z$iC)!I0l@(b4TTH4EWhY*YCmoi_=sM$eqrt1IK{xoSUJ1q~#d!qwPPG!!h8UqwAzM z>%cMKT)p-xhhxCWnkLI&u7_j5!5R-x4rh}~mG`c4I0hWhwQxqW4jcpSvt_YzI0l@n zdE`vydN>9gc(}82I0ihd+-c=-3^+=oRGG~>a11zew!zBb81T&`PnE+l;5pICWHIZ& zG2oS()+mRw$vue|Evva6jsa&M-9b4V1Ae#Vl5#i(oc~55KeG-T1NJQus2q*~7tVZN zIUECyF|=qlvkn{sc63~-9F75>j2dCyq^~bPqhDT-e;+2kG(s*?25SY zi-i0-kmiNz`F2TU$LxO#t@Z;dry*U;(J9EW3 z%Zx7n^xN zVlqoOX1va4g)6t5&2Rb5$TCYfW?Vf{C*^R=IBlV8%Hf!C`mhdjN$xGjJu5vhL zynRnFnI#-E-dwMdayVu@zx+YvaLl-0FUuHc0nQ}OrpR-p9nPo7` zG2>YKmMe#2#)YznlUc$s<0&uODTiam39DRH4#$jNwvQ*Xgk#3J2X|Bs$BZlYIj0;<{liY>aLjl^@+2}#IA(kyeKX~7 z%((Bgqsrl!@$Lz}GD|pS{65ZT<#5b+p!ZwlaLoAQ!OAjAIA%Qc(|YA_%y`eG=rT(< zX1r-(2jy_gc!Tdb<#5cnPa|KMB^)zu+GC({IA**h>@DSR%y`Z495PEdW<2fu1m$qd z`1Q7T%Hf!C;IK+EPB>x_-$s90M*i@`SF3W59`jrqy^Ejsa(>I6^rb1J04-xpFuL%q9=_Gy$$A!P5k| zngmZ1;A#^7T}{C6V!+V`zxvX*eCaK%8v0zk`o_D|?r>oq%xV(m)rLk5op(0pP1rV7 z(Z1t*2SyK`X<69t4R&>@vwrZlJlpg8Og_{uL)ScULT8*msIh<7zZ@BkS*p50&(|4X7tX1_L>D=9?yY+us5meQufTK2NKZZX~U9paWTIULg+yRxftIHvou&=uuyO!v{| z>}Ab5a7;JN=LyQ;nC|sq_m#sj-OXKcmNVpZ7%A4!qY;tc~G*S-7 zbX&$eq8yItI;y3sVAg?Sx&wNSQx3;;TgUmN9FFPs*d9>PtOLh%lf>Vy9FFPc+Y!H# zxgL(`zFgKzIULhHKlQe9IHtR9O2Nuz9XOlZoaghE!!cc-6JAx!^>9o#z%kvCr6wtdW4g2My;Tm!bZh=us+w5`j_D?SwoEx3(@nfRVs&#p z9MiqIwyknFn_SYNu78OMj_FQ(;TNEFEXQ<{)}5>zj_Ix){?&E8<(Tf=eT8b6b>Nup zqcjVZ!!g}CiNe%0*TXU0FAJM1hhw^*=bls!$8;|g@~vgofn&Pi+6-3?$8?XBex@9b z>86Zds9!sgqK>&9j_KZ;U0XRE(|w+0vvN45+k8iqx@H|Xrn@#= zE9G!ZH`V9k%Hf!<&$zVp%sOyPcVYF>%Hf!9R^NBZ;h1iT-Bs(Gb>NupotImc!!g|* zU*b0~*TXU0iE;ZXhhw^h8{Jh7$8=9mF5J+p1IKi$tzWDhj_DSe8n%(S9?rgSJ5)mD z@L!$*{~2dmVe`qggZt|i1Kzp%rD`|^e0fX$BIbHH2AreM1m$oHxN_ZB%HbGr-!2u3 znswk9aF)Rvl*2LLwEJThGuOj0;M705Du-jhk4jxt4#$8KIdT;@>%eQB`|4 z@QWGWl*2LLC^c)AFzdiE;2b{Nl*2LL>yuso0uvkqUKF-lNwW?d1Fq8YigGvxJmz$c zQs#O%2K@Z=6yi3tzYSlxs^&fY6c&)@Wnto~;V$$c#r=B!C z(y!-~1$_#2-g~KuGjg!?*&+nJE^!9z74+Zk)?C}S$iBw~8&8BrrvBTf{d-30uT7MM+zDo1&C0 z;+mqgE#jD>j4fiDqO2{!MQvMzHbos)Kr&1d!4svM3PN2nXo->d`2jQbU&)bRtHvHA6` zZI#0rn|IuCRymxp`QkiTbPNISZoi(J$tEa=GdBPJ;iGamWAppBs_Pg6-pj7D;Okc9 zaLm}TGM0`Z;C<}%o8NR*4rgpWBmH&Pv4!P<_WCX-3h5XE&e;5ZjOEJVjLq-gdZZlA z*u2(*{5pn!Gd53DXO?m}WAhA&ymSl!XKemsbpz#a#^xuw9a0X*jK`l#sbdH@WAikA zLzTl3gp(_mD2HPe!-@=QW?oPJT^})zSN(jrM%M?m(}vD?=h?GV7dFm`RX^3x-TMl} zO41;AtB8xoE$tR!-pWXC%RVbQwd1{r9k$MIF@I_Bbu}Uly4Gq&l?Gi-yoveW|0OJU zYyR9eGv<+6qh3^5xNT0FvmZvDsCKevjB7Vb7t0j4W4eKRL%F+8ck6r3fd9jP4%poj z%&gztb^i8m6`R=<-_zR6VsB`zTQ;-p$unwmg0q<&$~0OzoXu?U;g`zcY-W3NmeuA2 zXEO_!yj(e)&8%%I*O(p7W;QoQd2L#7HnXCM*DHs!nayhyOPdp%&1`U3*JcK1GkbOP zrmlyxnGFr=+RWf=W{c9gHZwSz*^vXT%?!?F7PX0MGlR34-9G5r%;0Qh1Dd!tGdP=B z#tN>@49;eDKF=p@W^gvMj#*v5mlw`v_H^hrZDw#bvt531v^l}q%zk$7t{l#0mhQ+6 z<#0B$M?H&ZbAq#(?Z3W4Ih@UGUYoGmoZxI`^J+9u4rep#-*2xgx17!FencN_PH;A} z-WLWbhqIa88}L*)oXzZf?^4>F;B00eudGlGXEVzlI*QKJ;hX`_&l8{=&KdCBKej7} za|S%7U}BxA!#M+vcW8ieIA_2=<~&gj=L~q3e_5TW!#M*!SZ|GT_;}lVr2QF9XX@|? zHV-+llXCb(nvlZ@f_spK9~j_sZ!^9X`$G zZCUq`Tm;zI#Y+wwz==T122=i0p5*w)J7^K3qL@OkC%`8KayAzN+p(hOf<^Bbe5D2Fe!dB+Xkl*1R<{N_s6 zSv#D4VY+wwI%a+NFVBF_t=%ZoEzz=uZZX}XyTkgMPli{vch+oPP33S*ckJ5L%Hf!9 zisi1ibi*;-2s>SG>4syv{r0)u(hbLSSNdj_xx#DMujgyGamwMCZi6OomBTUJJ{Kw$ zG=DBQrrX|sgK{{g>mNLZ%oUF5CL7d2IULgs8Tp)YIHsGURwkJ%9Me58e~fZCrW_cRBt=E2h(xS9t~bKq(o{^{o6 zcR2&@vo%VWZ8N9V2vx8C>yx)4mD#!ffPeaItwOJx5HRC=w`uhXeGSue(d0)LcFYcO zGVff+)e-t}3z~*Jf-I`zLohs5R<&ca^nok&a zH)DWfcIxH}Cw*vhyYJ}Eq5N<9Y;kv=?$-Ah@PD`gyL*D^blhF1&Lrp?lFfMhjuR%Hf!L!cD1inswlqdf!Ncl*2Lg zD?Lsrhhyq<&fZlH$ABBA_swP2hhxA+T8vZ<$ADLOy;2UxfX}`um)ooZ$ADK|Sf?D0 z0oOScL&r#P4ESWE&dT9zau;8nR}RO3qi*!eYwj100nhz8ML8S;9=OS=CK`?bk4n%? zIUEB{c5#_fmYziYfxjHiBcoJ0=fP4rh}) z@a>s$I0oFjbs06&a11!p^fk)i81SjepOwQg;Cj0Q)MUdk;G>&&D2HRf^>QatGY!Xp z!{qL#9F76Ue0X0u90M*NtFW4BI0l^e{e0zc3^?Qd;A*Df7;wmv4V1$%;09fHD2HRf z_4~zC^9{#cp9XO_*Y{ClVa7_K?fykN7 z^>9o*|MRxW;h1{ZRA-gLG4(@-Gi5RBz%ljNHODK5*RhXJ=4|hj!!h+s*D7k0hhyq& z{%}uo;A$Q`&4H_V@H7Xm=HZ`i4t^H{&V3@m=YGw0&K$M7Sikgl9-Q)e=zFnR+x>B? z6<+X1`OSeNi;ue$@7&$<%hM!Td#}r#*9$x6E4kuT*D_~n#a!MuP3*?O`@HL{=9PCr zo0lbbYmR-;#ye8i5UEQaDCV^L|AU_ccJ~C+>A1TtWYFE44?40Pr{lQ-`~P%g6K>Q{ z0;eMz9c-s^I33xEh>4vB!0E`ArVdmN=j*J#V1jb^FFP{bljr|DkAUY9@H_&ZN5Jz4 zcpd@IBj9-iJdc3q5%@C@3~V>adZ5%u<*R=FczJ5i~4u=qLGXK!+= z|Lw?kV&#c4^>eoodEXpbxYWP@pL3sviW>a=q=BV(o_TgGShHQD|Cj;0dx9}bch~vbzdD@V_Nh}t?}okI%=Io>&TfB3-l-hU*=?JF ziCph`wY;evJFoB5PdS{k+q}u|D~EG->mSqgE?GEdx2rlY(7R;eoZT*n7((xQg)^QV z5!g^UoU_}3xArK9b9NiAY&gB^70%ghV6M8#;ce}oD}2#?%Hh1rI{)zGdN(YbcUk*o z7^WP~8>#%?zEBSDXxBfVsgm9e3-4s}7nQashj+GlL1zNJ>lMx!bCkt>l*2h=o*Md& zayVzq9~KqVyJ6v+G3S{yUpbsJ=FN3N>0PgI&X|V;G*u4gjCu6hBg)~7k(UllQQdqC z!Z~BUbZUrlIA_f56FgTA=Zv}Hxk>?M9XMyq!!mAH4(E(H{Ds&W>A*Q-j(4H0ayVzq z9p0W+4(E({@#V~VH!OUxeO|7eny4Jk8S{&oUzEccBd-lsQ}2d_bH*Idaffm^XUs|F zC8}-yyl~E#TVHLa9L^c@yn@G+!#QLABWJogW*sAi9|XTW>sM{Ho$hjRw(%-v2ooHO8Rtu82sa|XP$TlR)#9XJ!m zSTm<7hcj_Z6XS<+I1|T^73(%K>%f^f_U*h+Ih=`OlQuq$&Gm5hgA zKOLCwDw|BVoULA5bKP~2ZaHVGWrO?az#PumYUuZ)l*2h&Z4~vTayVzJ#qw0tfjOMB zRqq6wmBTq(y^}n?4#we}t&ZQ^M>(9c)etl8Du;8n`toQI9gM>{TMb;``Vkj6XRA4j zM$pe+!Z}<0x~;WxIA^PahMrLl=WKP@naui;6?g;tB2qo>MCI^?HfM8&dp2*^=I+_N zU7NdS^LB0S|LL3e?{Wrww?NW9`Iami+9Jw~^%b8?T$*7~uw(UmZl05%dd?#~k1X`Q z`{$lh>s~Kyx4HMn7H7*gAJc66LC5`ff7aYJJ8jg6aj#TJ8>vahDqa6>!0y(pe&Ep2 zy3djBe8Y0^fSAM+ z!!cCf?jM!IG1N2ZYO0BWW2o~l>{JfNP{U+Rq)7uDLtSy9uW~q>T!sU8l*2L9o$Cr~ z(g4R$FK<|+9FC#B4;Mz01~`UV`&nb$57iPuBu4`978RXb+vLho1E9mh?*$CG1NEt+bV}+sF|~$R}RNee@4u%Ndp{1 zUDaWVayW+C=g}ACa13>H?An?%z%kTU*%v8?W2lejh1R42j-lq5)?7IpLv5Snq;fch z8Z~J~O&Z`B>igs4l*2L9#NXa1hhwPGu2s;a0gj;#TDn#_977G0JBlU^a11qeyXMN_ z7;5gJ`<25n)G#pz+dSFsOqw*nXV^T+@UhC_Gi~0g z;s@n$4ES`S08Jp^7;va%uE_)(18#E8HJN~8z!k3d(*y#J0Xx4uP!7j{7nCckNdp`M zzCCi0aySN@dS%$U=5-v70Z&-eLOGm$!9PY{|X6}ZZY5nd+REP zW5Cneu2l}lfD>#ES-^Zg90T6)q@Hp(20X%Rzj8PR99Y(;pjiiw0aqJ1SUDU6{;~P7 zaySMY-dUoMSqF{*H(9w;)W59zG71xghz%k&)eU>PPH@4s0SpvdnGlw^^dFzDDl*60aoXsEZ zX%1Y?gQq!gH4mQVz|}nb)6K!}V!(^PR5^co^yPLp*S9+KN4+X3!Yz0(duY}qhnl@@ zy>`(29RBm(v>#b`&xtlq>NZTbV)%h>m+yyeeSX=z!iSnY-8;8==jpr4eX8gc^t$)2 zXTa{(oKm`DpYsn^wF$rOTFJDf<0ecLEqk2tIhb`ioqNgq5n4*_0Vw0d}t?Vv+-_XN|iy1UNbEE@Ddv$AnOCtp*?d67i7G%LHSoly;^S?OLgb2@W9 zoMvTWrHRVnG%JHUepU{rS;;agAiY@!PP5{(eXDXf&C11c-WkmGaGI5^7kVg%)2uvM z_@{C>%}Rsft{(}5)2uA`Fg2rDA5OEfqTCnde{tJ1lw4Ciler#Fvoa{r7Ugi7mFjKc zWH#5sX;!W#>#7`1vr^~$Mdfgsm8M7hvY2(?G%IgTOi~VKl5ynVC*^P^86%QZ%WBqv zGs#Fkb(3;9lZ+kVV)>ct;Y>1SHtC=o&LpF69L^+TRnaUt z%{p)<8D$quP!4C3(Ye@1K}uzFC;O=6X1jjCiNpD2Fr2sPJxyayXNW zYV*!1hyOLn7@jeoSsy;g-tRu2@yfj{XOgkuRGV#qV9;TSNR2Hdl$xHcWnrsCRkJe!JZ)A4L7u1&|Yskk#Rb=O`0{ojIiw`NxV=!*kt6`1$1%Hz@gKi-bbw((Yv^bM|W z@+s4<=i@y-``z8AyY+u?;TT&{#~tNxj4kBakILbg(}{hh&2+{A$DGceT&o<; zCO5TwEHm11z%i$V`?@NJV@~(JUsVproO*T6ZRSM|IObHg)C}ct%qhIL!wj1oaLg&X zv%YdT=F~dZKIL%CY01+>W`gB_V@^|I^ivMUoJtgVpd8L7m*`??GZu5eF{f0~HYkT< zPWi@1F*7v>9CPY3?hoZ~%qd^@Bg)~JQ=V99&7jT!$DD#~8mSzPInDHaqa2PoEq`9Y zO#d8k%&Ft~b;{wGllT3Y3C#6yHo2MDn0PK#>oQ4YtP#^+6D=8+CK=JayuK;>}E z>Drjb%Hf#PWuH=s%sOz)sY%{t%Hf#P-H_oDo9p42)BW9lD2Gq9uN#3Ek0^&vviYtr zNt2j$;FE10=W7q;@F_O$)BU1y_*9!8d7VC~SqDDN=7-Y_R}PUo`QYOv zl9_ejGi`pg-%921SvL1C64l3C51(!GhDSOohtIKj!uyw%!{^$3=byQfn|0uv0WW$w zQ#pLTy}nj}GljVxzQE>N;xteWUug3wsSYZKFS7ZnNGVgAb>Qp^VY=j24*%sDaQno0 z&A8VQ>HG~d>2;K~i&ot;T{*m*&CgwZtQ=n6<^hRwn;EhLUcu%`vX4;?uW0icJD)0t zW8O)R+IX$*w-> zKe%1e`WLP(J|AlPRd@I4Zhemd|A)T|+T9aOr{nHAf3uSYePE(@!#yl+>cSxh>J}4C z9Hv+alfyC5z&{o#hhw6*oS{mZ>*1JafsT!p!!glpXZIww!VkJ0jsa)9TBD3v2aW+>bnHbHgCUj zI0hWQaN9gETEfmI0oE(?G@#440zp)+?CBba140#m|4o<7;x{p!K#?+;cRjz z_cv4y$AG70Kd2mz0Xv(fs%qAOW58cpv{MepfMZ@dryPy}H#+WD&8!2*fFoC$q8yF^ zN2u^kIUECy{IzyQ?*@aF!!gmQsU9kaW1=4> z7b$AK9ylg?BGf|Va7=XZz!1gE_3%daMSsVmM#|xhZ64z30p;)}Hb1-0N1Hsnsm47h5rtNn) z#lY*K_snm7ApeCFW&3~olCWR%c{`#ukDWb2x??*A&1}(fQ?0i97an^SzUGRX$G`pE zfZeTGd3&`;A$w#lRQ7YCHv#7oe@T)h=7RG>!)F+L{6oooL$?occc1Rof9H&>k+FFl z)tjEpt=kJUZ(d<#5dSa;=y$S~zC>>T5UUaLl;l{Oii$ znDL2V`DD6q%-C!GY~^swc-knZj27O*Ha7{nHB=7Aj2m@2s2q+N*FToRWx$rR`8}I3 zKsg*UZg=^iayVu@IDSc)E*vw?c51D1IA**cQ(PG>95XJwzmsw}W_;-4Ddlj?I8T%G zGF>=koT9>5d8aoFzeZnJ%2oZ+`2o%Hf!CnLFMxS~zC>Eqrg~aLjmm z@WaaCnDMUJX=Jo;%s6+a(aPbN@!_}cl*2LO*GH<$bm5qBoJKAKhGWKS`XrLk!ZG93 z-u;xrG2`Cz?<vW56Z)c2*9@fI~m~Q#l+1{?xdDj24ao7g@SUIUEDtuspns7LEa59@|zq90QI# z;G%Lk2E6Ed&bsDx8IA!*D>X|w90M+zKe&wcFK)k|Ki6rh9L~P5H&RdK@LwA6iiL?} zuJdXi_?rPwOWsjA90RWK?x1ow2As#syP)~Qz%k$g2|Fo=W5AK4UsevsfDiu6DWipB zz)Ks=P!7j{_m=sg9F75xe^FOP3&((WKHsMtjsagAn?mLa$AA}ycK!S=90Pt*{+X_a zW58YBm6p-MG2k^ZRx5{Nz}-tkmAS$(;I@Gsl*2LLcy%u+hhxBA`*LVAhhxBO@^DWR z;A#>)O@OOO@H7FgCgI=J1pGDz{HV*DjVbcyn-MVLWy0@^hGtq-KJK~xDcY3C6@Klc zo_jWgebnVcup&N*-hUWfdB=fw8@$#x?=k0HhVPf6w7#&q;elr-n|mkp3H)yc>~_s= zPu6`a5x&yAvvs@9$aS>OmVVwTGI89`>q#VvBcl+K`<#0^*;Nr45 z%{p*QciY4D%Hf!9xd?G{nd{-0?)0U-l*2LIm8EVghhw^Fmlw!w)`4TX*-Fk=4##vu zj<(O{g>t*tp zb>Nuptg#!F!!h0GpW^$Q>*1L0?it;b!!g~(gU>04W4hgTW>S+5$8^^$ouC|!>4qHp zNjaQNZe`P&YU1ITZn~2@l*2LI4+RpcS%+h~1KI{Ehhw@=rW{ia$8;x$@Kv)8$8;P2 z9HktN>Bb)XMmZeQ4LPi`ns_*-`y=aS<#0^5O_Nw^*5R1$z`~uC!!h0dF)t~HW4co+ z`>BbCPYPz9)u4Buj~J~Sjscfl@L2g@++N=|PeC>Da16N1;pxiZ7;rG}&&uH#aG8kJ z)bztK;7fNlDTiagEmwMLk_X3t!wu@K9F75pNpM#=90TrFp@=4Va18i)-6hK57;xPI z;We>?W56q3w^9zrfG=h^qa2O_$EuP=6Fc}K`*`}7ov0knzOaA$QswYp8u0q@sneM! zaYm(6zUH=)U37Hh-pb(^urt~jBcNp4#$9->9!-f7sIUEB%y8NheI0ihvRN72t9XJL&CeLu?a16N7 z`=`p`7;vR+B{G|J;25yecd2qX2Haz2m@MXcI0jrZUNhxz47huTBg)|z@TT^uw3)*( z;5^xeD2HRfZ2oXhbKq(oJk5ctdGIs`uIAyNZVrAI18&uC%3^1ds5_5V_nz86pu~#6 zc_$NWT$S}^zpU>*uDMeCY_w=MQZHNXb8kuhq}QITUQq5@pE#voms>J&==9|Kt9tJW zTOynPnYzvpfBToP+^y-3ySq=+jb#M&0cc>1^;TUk^sppi# zG2mI@GFLF`z%k(NO-3t+W55^gUQ!OnfXkjqtFuEm20Zr8DCKYrxJIV8%HbGrg&S2X znXd=VCbxI-Hsx>(c;lx;mCf~V4ERi*fy&_+aQ$yjl*2LLW$Q}o>=2FtPl>xqIUEC? zxhHZ}bH8v5ILex~%HbIBkvZp;!!h9e3taE2g=4@|eoU%n)`zpn-A(*nIlPZ;g6B1@ zTHRa^$AA;9-Krdp0q<&(L}!F>47l{kAJ9qoTyWj!!h6q;lC+|W5AIo*Vfq~90NYodyjHB23#_2vbyGe;TZ6l+WnNnG2o>6 z?kb04z!@SJtmg>&fMdXs%Fk5}$AHfa`KBC>0Y{rxxxQHkKE-}t9nHB)IUED7bTD)S zb3GgbE*qzjaySP3EaZOWa18jtpGg~JBAyk&!w!!h8-Ri7${W5D5GmuY0yfn&fe zeby?6W56f;Vl+0_!!h74HM=N>W59d&UQrImfR_i$-NdW|$AB}Xnx!0$0f$ZIY-+BD zvoG`<6GJ)tmj)bIZIZ?d&tvA+Ee0I+*i+ST40v?gJQ^>+G2kq9#wdql!1*&hQx3;~ zD~Bti@d6wJK5=QaaySM&^<*@S7vLCh&!Zib!!h6}m(DAPW5AayWYu^9jscI|HBmVn z1CG@DgK{_qd@5WOjThh;aOM^3mBTUM8lj?>F;5a41KwAry>d7P9B}NcaySMY!6$QB zvkn{se*1N-aySOOeEA#Ya15BuAMR-mT+M^0IdC-(p60;SJp9wm!S7=S-~G=4dki>8V1Hi&cJ~C+>A1Vj-|Uz{A8c}M{^+f9HD)P?W58)o|4qQIXDKKu*pv4 za16LpsKjdK;23b8CV|S~7;wyx_msmi;8X7lshNXgz*+7sQVz#}``rtprVWk(ugKF} zIh;+dadg*ji-KdobE~IOGY7|j$ITt79F76!t?v4{YB&a5YIiL)b8rl}bmOhc;TUky z?$Ok=!749Uw zFH7uI4#$9NwhyO?4jcnMa-)TEI0oGE(kbO|4A{3$CQWqU81Uq}6O_X-;GkdCryPy} zhgelblO8w*{C(s`{JfNfZ6=vp60;SJb0P|SM%U$4qVN{KiwSsE(RR@ z^qau+AEU3ERs3g3N7J)~CzU_?^GUf}_x*cRU)eD0$+9o6`tH~;GD@2?A@^kYk@k-W z#k2kJJyp4MoHiHhH$C+Jy#MpcYj2hKZwBmc&AXY~cG&W8-k%?aC0WrlOU?t~D!fXP zuyX{T&b=MITIPu#&E0*vTi?9_dwI@)e`f=B_XN}FxVug}c+lp^CYPmR>MUlnj8pMQ zW^;=HM=CQ+IUEDtGU|zPI0k&kr$kos`EU%l(ZnUn;TUkmw;}z^^>7UM=>7uA;TZ7q z)svLNG2pmczbJ=ez`MTG%4XJwW56w^?o|$FlgnDm^{#9<2HauEuv}i_JauA`T;}uOY;r@o%vTP_fFCRgsU{eX0pEGkOgS6_PPycaaySNjJyH%e z!Eg-td!}~E;TZ7#4Ly~^G2piSuPKLPz`1JYRdWo-fIC&0qa2O_U-u26CK%2pce`C< zDmSe!Z_pMb9$AG)!ilNCF90RTw z*hx7Y170-wigGvxTzXV4P0rx#3v<7fRSy590eck=pVoX64}HHnjk(2u%Z;w99F74W z@3>Yu90Q&`E2OXad^iT&bz42J9q@RiG4x#bvef4>MB%=Pd(cAcS-TPlZRz$4ckR}RO3!!}Hl(X0c< zfP3y4svM31|L}RD9F74`99KM(SqF{*cfP+w`Txb$l1Ie3rcLe51(5ZvVUo>}XC$-qB+^`t!~L{*V70u%j<9I~_;Yd7G6q z{EbC!?AR6lX1s1(RN-XxBvfQ$I0 z&^;|Y1su0sA8|Ydd^Y9-aXbawVR`vL^LfKlz-wYI6~|M+IhMuLy(>HgJbFqCaXbY) ze84GjJO#XJKpx%G!c)L+{v0cgXOZi?=#@C00&eoWitcIQDc~+%8^!Sy@Y&ZXYn%UG zJO$h%V1PKD0`B(Vwm6;w-u2N__pb01@M+hn;&=*p&YN%IcnbK+lX^ks@f5Jf&d1_-3b@0&ymieycnbJ&@OW`N1^l7fM{zs_yzkFi z^~^kY3iyx9+r{w|@RB~M>znKG6!5bZJ;m`9@U>w##PJmHz1qbZn0fFN@Z5;fo&s)CVV5|b0`^Uvx{;X& zPXS+k+*ce=0TV+_P3b??b72sLZ}r(XI<}jzdElX0MYdN> z;}R*y%|$a-dX?PdmAP2$t35Av%Tnfjtz^L^ufN&Wp!-4Blcj^tT>XE)3fj?}dxp2` zmtl3~eJ;0KO}c$#%!VJUJZ>#)oS<-?xQ|}t2+KIf(PKKA-&w%_@d9@A1!kw?=sIt+ z4GMo_k&7PbPaVIHap|o06z~)mPaUV@Dd3ikrikMy;F#Y&iQ_5YXDw>#m>o|6uRpdy z98Uo+4f`pMr+|z5SJN>&o&x?9v`HLK0biS&T*v8n3b=NeUgCHbxidfSh~p{XY+1|a zHJ>j$1>D?sg*cu9o_aH$j@j`P@ZHVr#PJkxV6qG1cnUc7)`Gg%j;DaXESV&Zr+^br z{vwX2fZaCL(LH!P1zgQ_hd7=^?%b1Px~Gn(fcxj^E{>;wqxHQhj;Dav6)LBD?RW~f z>ZFz8cnY|4!I-+Ij;DZ!`7{#8Q^3!%?i0sTz+NBhzZnfr0Z)2i|K%ol3i#at`)@|W zv&g;AT3Pqb@f7f}nJdNd6maWf@rs!56FdccC}%BkJOw;=@>X#?1$=OHO5HohQ^0}A zdW+*J;QC4LisLEZ2YbsDH6IV20)7~4zBrx&?(jUK?vdju;Iva3isLEZ^q2RF<0;@v zaZ~CZIi3QZow=hpo&p}c>y$X20uJ-Z>TN!LJO%t<+5mAp1>AhpJ#jn*92Qzy_sH=S zaNM2?#PJmH%O+8Ej~q_{_x|2Y98UrJfSk?b-`^#9&!9H3wYn%?HSDn{c%8=y;3?qI>86O|Dd4E1KZ)Zh;Nv%I>K;6v0RfzE-q)flHGPBCE+bHenSHw-(1! z!1evNi{mNa?hjI?H}l{r;C4-WisQRmpSKS+Zj0k7;Qh5qWia#LDc~I|=ZoW6E4+nWD!!$#tG3V83@L*jS}_-j}$ZG!O>@WMkA#qktyyV`HX@f2{zhLyAl##6v~ zCoB}lQ^5J%qieH^r+~MoYax!Ofa|_KC5~s2>sCFFHopmNr+`mI7%7gQWzCc3+G}zAY>Pi_UtK3;cnbK) z!p-7%3V6yUH=U5-Dd3sjJ;m`9aMV4w#PJkx8JAKzA;VL^vokCd$5X&QsiNs549~jI zVps}s{4WdmgoCY_@(zOcprOmRmX?_u#oCDJD`^WZ%#UhYkZ zIKGI*b5)%uj`y;7iEnqr@kK3O^LpB(W`2Azi$8l`PaI#|;@+tziQ`LHe8G(E;`ovl zZ}2*4GBZEEl*O-P?IMmZZShwTuZZKzSUmmeLdnfM__7uca+xfSFK6)+A#cU;glpO!t5xzX`8zRMfUI(1^?xnl#8 z?(A`B*pew%dge{JYDMV5bo2I(^X##LBzGP9JAxgOui;+H+_FWkd-ws_&EF)Ett@f7e{fBOsf@D#A; z#}m39PXX6zm$QMz)AP* z6vtD*$=0V0FxTTL;JEz;h~ru0RzG|oj;DZ=1eUL2=D}0I9(|UH<0;^^tzL=aDc~Cy z1FD*N@Dy;wlk3It6!66ANvfIa@f7gB>RrY06mWW%>*9C{_-HOKy-FWX0q40kO&m`F zPgwd@98Uoccw9Tsd^~sx_+gq2;&=*pR^iw+%=LH*xZ0^OaXbZVtG!1YPXUK4OIg#* zgQtKa2Xq(5Q^4smUlYeuz?0q;*Q@vO6!5hZ^ThEKaMapSYn#W#Q^3OpHWSBF!1X7d z5XV!%S2pIZW9GqAz;D)!6vtD*mzTa0$5X(uPuTyxWIP3Y{J@4FGe4enVO-v$;`m<{ zaIX)Q^#Xxo z0AJ7I54~23uG)uC*xU#Kk9Hj{8({>EUl54qSITaq#Z=nen6ctsZplP5qd~Tlb1Q z!8`rMI6Xc;Kjoc0cFd_ePvyveDDj6QYs&Wf=RIIYa~AZ@(z@8vceUd8TL1MzjP{#r z`sDLDvT$_6;l;{CY#My6gQLfEG{55lcK#-;-@1SueSz8OIJ!+uwDdcQgc9k#qkty{UBeJE}jBz)Mk-5 zo&x^j7Dc6t|EqxaG!e%Swl;a4w;U5sYIzFy?dV)8T|A52!FQv@@f2|1kT>FZ3b^;! z>MC741w3}+OmRE~ye6%SN*7N7uTI)f98UqiopC@MPXR}mkwvA8r+{6{4HL&xz&x_AosLi(lRcnUb_>S!umJOzCEW{5bR0xtJqn>d~V{@EvyN*7N7k9BD+ zj;DY(ls+bor+`0Y$)?i9Q^2v_4iU#wz+W~!7spe;3;G49fbkTtpX&y3JO$h~e^Qk$ zo&pXi+)W%$0f$t%A&#ejgQ|F|bnz7M`k=YucnWxQr^qT=JOx~_Wtcdgbs@&pZsPb~ z7I3WTA5^~ae5dI>1^g{?MU^g|0zMaUhB%%Aj&=Q&IGzIDd!V8U7*7GGzqCRePXU*? z6<4K;r+}j+ZZD3ffUlOnAdaViLq`L1Gr+~X2s-XhLQ^5Xx zHi_dY;PT}Xt90=caLG*_#PJkxl*|{z@f5J%{QRZD*K8L&1w23U1aUkCTr0|ZaXbYa z?MO8(@^}iE#UJn79N0Gx&dq^+^WfYZ*f$UV_U7PsDc~DBvu|$^sl z@sm2m*yNx7?9^OOS9o4XoW9@0G#OU-js7yQN>IFsL6>rEDZ1`t$r4eroj!Pad;KiY zBTlI`{BlT_e-^N#IUkpE%jIQTTF}4HqF!^0Up!xOLRF7+6Gwa;GVEf*o{I-na`c#v z=64qGfBaR@j=sR`bR1o$l|6hhWs%F~K2inwE>jM@r-0+Wf1xp+0xnR?R|ScufWNL> zA&#ejKV|qVj;DaP_YG7*;wj+0D`tt~Dd3=~@5J#G@Ur$w zNIZ+&wW|HZ@f2{P36I3_6mau(l~s`VPF8894p=LWr+|Ity%Wb%z=JaTs4VdmaG{;c z#PJkxxs|ReNIV66aYk!#JO%7A>x?*_0>1DmugVh7BDeI<(c*Xt_{y_a;&=+UcF}4o zOFRX<;@viJJO%u=PI?t2o&qisvA4ZAEl&YAPx_}go&wH1v5?9VPXYJKIaM4_0q@QD zT^vsVH~LmzWr=5zn_GXcIGzH2aW1_I5>Ejqi#SLePXT{ScUl}z0e2jcM`ekpfc^T9 z7spe;Q(}G;$5X(rG41za@f7gx_1jgPcnbLb9XAyuo&s*VqnkLM0$xA-syLnk9-h)u z1&Nx~6=jl*dWr?SNBfpy=j{mEG3x5&EQ@}S<)KXb)usj7k z>)~c`JO$i(SqhaUo&v7cskbSPEM-1^JY~G?XG8J7y!G+iU%g)(PZ^gBOrV0qQ^1~GbBW^{So2&j zJyIM`0k7NqTpUjUhq_nR@iLwQPQ7@!IGzHw#f+t6oJ1$`7tDt}Dd%%vq!0dD!U8kK%_-24b&L?1>3Mrdkh~87c#oaQf zc<>Z(oNoQZ@f7g!ICsVIe-&__k}4rQ1zhpwEb&j4r+_;qekqQpfbT>otrEghz%H>D ziQ_5Y`vEajJa`H?&WBdwcow;FTh5B(Dd3j2!YUy=1>CC83~@XKeCV-F#e=7SkBw|3 zj;DYtzBnL`r+}M{%&g+UQ@|a13>L>zz=fJW6~|M+1DaP>3E?T==KEKQ<5}cJj&oJ< z;3;6gORdE5eb{WJJu8lAyBSm%X#*Da(D_jZp)i0Bs`1U@H6&%a(D`OO&R+=IXnftqJ#aO9G(IW zo$RX;!c)L|BCZz4Q@}~GB~bC;Dd0-0I*H>c;G!e0iQ_5Y8QDvyc<>bP4*z-Lcna9{ zP2_qe67UpojsZ=?@f7gIl}E+#6!5%kS?im5@Dy;0a|6Zk6mX4hcg67(@UgZf8iY^d zf~SCwRhlV|r-0|Db_p@p<0;_1Q$ob?6mYRVD)JOwCXm3voOJJZo^3P%{sn zbs?`um^l8I1-u~9JQdRGgXQ$Tn)M$!Zp=rG@szH|qzXDF#Z$T$uFMd}Q@UN^d=|%3 zx)Ui85->3aT=R>z@uN;mbwzT$XFxBaU-;`m_eziapu`;W5VDc#6L z7wGsDUyti^Mb>dBo&tXGxRLm9%TvH5SM3wWQ@|_Jrq^*Oo&w%Bwy!vz0$vv9jyRqI zzSgFsj!E&M*2lTF?Obs@1>7!f1TFG-3Yf(o@7x^NHxJIufqnDf+#J|95C8V&;CCtD zhXs5;@6T1^O@oOAqW1Qg+A1i=i{;U0B;ApseYsnOKDW=~c|CZ+{lj+;?W)%B_{zu; z{ZhDZ>Fw&*{dSRf`7@l{@GbX>ysl#w_xxu8JDT&y!zY!u{}K8y{>V$!-$Y*E)wpkg z_z{xbe3E2G@Z5@BBb0IUn2zTEWdTRZ_{n}}Gh-wDKtU(krC~gfRhYuooTZ2f(l&}V7k%~~#8bmOrlr^x04{RC!X2QoV z>}(u;f!XOey3X578ve#2H+4+@d}f^a;?=z7Jq28H%W!c#1zg(yt~j0o_HN;s-~4$z z1)TE57;!uW{QB_&aXbb5e0Y`uW*$5Re6USNaXbZF`Ro~SJO#X}Lym%G9y|qnd(TjD zJd50bUJu3b6mW%vWeb^k@Dy;xF7w6l6mW@-5el2@@f2|R@4@1D3V7b&&Ej|pxKG;z z9%ddq1sqVXr8u4f?p*(fIGzHo-!y}#nFmh+cRt=z9M2+m*7t(_ddpM5WAElJV&=h9 zz;Pyz5XV!%53{}$$5X)P7WjFYdGHi)htJc+@f2{+&==x(3b=JX`|mo&Q@{}h&Ms=^ z$5X(5r@o2fDd4YlY8Nxt<5}dgM&BTgr+}NRiBsHMkEejWd|HU(Dd5_3mWtyk;Gf$g zdz*Ri6!4V7A>w!nIG+1XaXbZFJ}9|vli?}g1x-7O<0;@rYfg&e$6K4`gJGFVhJQFN zcnWy2doOW31^hVCC2>3jyu4}FQf3}J1$=QuS8+TATyp+#aXbZl`EV-TCc{&}FKV_E z$5X&Z+8!0hQ@|@?rz>MV9y|rS=6VltJO!NT;-BJp3ixcb0*XL91?+lqv^bsuu2AEd zIGzG--=>@*5KjSTn=)4%PXVuP_FWvm(E56nA5u$kh-aOcR&}vBo^_$R*EVrHi_Y08 zCz_hikKSGuIPPx#Prpi?^_~JAyC7+H!|@bwiiQov@f7eMxmJtgDd2sTBIYpj;3?pw ziGs!P6mW{xo5k@I@RI5Aa+-PY6!6@}&BXB(aHAS~#qkudZ-eBy%shAsxL%KT;&=); z=K}k$TfU@m zq1HD?iW}})%<&X(rG@>(@f0wNKi;`Hux}omn*;mi!MQoGZyx^b&B3+e$xR};bXY&^ zf1Z>L_}~22rTHX}G;PkW9vIra>%+yRs(H`av?c6bqz?fuDX&f|vvBQh=JKxxot>cs)|o99jWaQnLJkv~@2G{@0nI-38N1#F{pO#wT94*0je2khtz%udJAb>7xr zCKkV*56Y!6sK@+==DXmpGG6w0aT+tmQ^tL6M^9@wo-$6Cx2ZUuGG2f1usEJF_Vvt? z&dh_SjNeZjAdaVuFD+Rtj;D+Vj{h!>r;Hy?uAkn_kEe{EoY^Oir;K;B&XU1gk7x1A z>pol@PZ=)^eJzfsjQ|Q^pUI^b*HY#y>aQ630`< zS?86=Z05mJ#-B&d7spe^E1E{hVy?$i#yPf!iQ`%PDo#Hjj;D<4+{~QS%!8+l@9!Kc zj;D;LwS6Owr;Ll8u9eNqgQtu=-;Nc>541LUX)~@8$5X&BV#U|y9Y5H*zGCDK;&=*p zUZP9lcow-#T?%UxkEeiZKb<0ur+}9x`7Vy9fbW$pt4%zf0`9(QkvN_L?l?b&HtTo_ zIA^w&;&=+UTBg(DcnUb(iM-mx<0;_TAIFH}Dd5x>UyI`@;K6ePv{}bfz;myy7ROV- zF7ab(Q;z?ufHOA|$5X(88+M3av^)jes6Y~J*6|eZkZ$e7@f7g&24}_b6!4+6d39ok zr+_m*m>`a)fG@89ERLsupA-+)i5;E-UY>EYIGzH&kjYIab9f5)eX*Y6cnWxWgInTw z3OLrdk~*2gQ^4t$FA&F5z=O_5(a9X1b)kQgp>n6JJ72PQo4s;v{IqU~@6me!e~f85CU%u>>D?y;RlhdpRJQK_ zEMP}-?yS@Q$d?G;i`GxR>vg1T`%8u_nBDJ5%=E4$uarEOq0^63jvmv|{LTXYkN+I7 zqc1Q!9Y@!Bn-w@u;3OHv$n|*fx}b4T?>bb<5?FfEbJmx zI2_5mr*u>M{UMI0bW85oBaWwZBg*7CZ?GOPw9FrUm%XBbYCuuti>Ep=?0x{ zB#x(aS^V+N&4GRM;M^S8HxJIufqnDvZ*LBMmjaFzG2XQ&1#LMlGEEi+voBmw>RPbL6e*@Q!lOfF@N)717DZ&Kh!biHNPG;_pYe_y7Tb4W8)+m zX%>dR-vf3uXX%{Rwg${DH||WQzyi>PW@Ifl-{<2$IeJV-^Z&Yl zOT^rz?WNgM*h*Rhvr(~?vIb`NV=HY9%+|?P#u}LYn60ceFq=GEIcs2ch_>?9z-&8h z6|8}IFq@AxFdJc8MQdPo*S1R5z--xVm92r<@7sK>fr$v4pEWQAV)M5KCTnZ~*1)uq zt%@};0cNXe4NSe+s#ycG8rZ5^1GALa0ST1fmv>CwXK0! z!)RN*cW>C)>*v!EDZzg=~!j8w$7nmK8qwBoQq~UKY zehKRo$!x}rV_eN--m~~ss5@O8PZ?jU{#_hT8Ta%I(mg^vW&HZoHgP;Rje=@s#o5XT8PoEPf$JZ;0c2TZP)>rFU*K51ukEQhdHR zo-%g%V82I*r;Pus-Y$=s2TvI{@334PPZ=M%_NO?WGETUqknRQIDdS@!ri$Y!kxgctp>y;&{r~^^e+~ zW*$6c9Hq=waXe)lD^98+=6d`r>-&7?gFfQ;*%n{Y9-P8`oVk!OFwVrCvZ>p}^O<5_fSPCVMwT(7sMpStOI z;o`D1dQSnTpL1GcJOvzWe!4W~58^4{JC8ex<0;@68P1C1Dd2RT`E|U2r+^FlOcKXa zz$u4+5yw-&pC;AO@dBO#PW5n`IGzG-GCf6l^YP;;;5nswisS29U+m}kZ;0b5;4`m^ z>UaT90Y}<4T^vsVAItGg98UpfnO!HN`FQXY@XFNo7ZBkoVAo=9TD0*L@FMSS;&=*} zMIP_m1lTtT&P{-Qli=J0*f$CPr#1n-?URh~1a?n_%N$UXxzX>b6Kly226)->T8$EnYvhmZtDqw!vR|U*Z`>KHX zXx1aX4aU8D#=BKeu8=E(FFpj>!i>*c4(RJRM^?yqDM*<%| zgVrZ}>}%drx;bJk6US4!%l%^bo9pnDuE(9G;&@6oWwj&XcuKeVs!Rc99z3PHD73ve zp3+U!!u}ht@RaU}y7pgMfv0qBvs+d%^W$0M%9pbL#w$FfyXjkjs^)q;rQ59dG;uto zo4T<}HFG_l(sha0SR7C3hAucHj;D0HM$TH@%!8+Ndo3I)j;D0*O|bvQD?Fw9sk?8W znFmklw(7K69M2-R@>IMU=6ZZD>-%SXIs3mtgQs*ijX0z0@sw`D8~JORdGM6((h1YW z@s#eZFA-~*>+zKCpEK)=<0;+E_1B2wDcxEFV%IkF;3?fBK5fMDly19N=f&|Xa+mHE zs$=HCQ@Z(lrikMy-QB0oiQ_5VK1X8*nR)P(?xozB#qpGG`jvIX@s#e4mV3nUlx~A} z>4VMucuM!t`+?$kO85Jm$KrTOx6S$Tb7G7VDa6c!pJjdjx1GIO98c*cE}XETxgJmHZf(&?98c+{>2*aMPw5W+)2or02T$pa zuRTi~Pw94>Vry)!$5Xobb~h5ovra_ZdQcqCy3jfERdGCv&Od$**e!A1a^}H;4_+*5 z-c!JPCJz+HQ^0e3To%Vuz}e?!FK_-lo&wIcd5}1s0xrMxu{fRrF0rao1v3wx0uDU2 zN*qrCS9%oJ$6Sx6fJek@BaWwlXUsbzj;DZEXUkX7%!8+ZUw;`Zj;DZ=PJSbfr+`bB z4ya`2!BfC>X0H~yA!sb?r#R*x#2&ezd(-e%J9w*d2TgunfG@kbW({z#2fdSAujrIR1j7+=-m zZN8_;YOcq#Njm#ydvQFQq|#{)isRWN6@8j4o93}Ro1~nJ+lk}ZB;AW}K^)H}>1JRd zcQX&3P12b$Q^fIXl3cTY5y!JhdU&Ij?$P4eB=yL%RUFSIX=*pO9OiNHY?7MC?k0|B zlhmi>6>&V9q*-?h=QQ)+*(B9_J4qbRCMh)UM{zuxq?gSCb&nR$CaLa_jpBcK>+5*f zH*s$BxOg^6iTgGe$FoWLbo7`wo=sBq64`Yx7SATBP?Qnkcur8-mU$+Q=LBWK(u%ql zi{}I-(cPuucur9AWQ~^3eEfJ$P$ritEso~|<#g^T;&@I_?&kd_j^_j=Vyk-j&HQ*y zP*z3UFOF|-eLlU{W!Ak|Je#C-kB5unIYGJV`bHez$(m>5tU%qP#dCt(h_K@obV-Wb!F$=D~A< zayrWjaXcp|2@1Rx$8&-*z~Xq;i5GtMU)q9aUC2J9fbI?BS#+`%nbg#LJbL5F+xg_~ zd~!G65IlK1pWK~K?&kY}CvWGI`@i$#zV$cxO<37`Kgl?&=ea0Hhezn2sYve_WB1-T zdAzYl=Y*ZIw+`$YwfE}IHOu^L{e1b`O@Cf~zNEs-t0fn1(g zsouHv`ey+Yq_J*+nqyyngMo3O$^2mHooQ^3FRJzz&)V9xp-UFU7S@ZoP1j8BpN8h`dpulE$p zqEz=a##1oydzCh&?t-UaqJ5qxj;CNw2SzlR?}DdbI&7#fj;CM_1$P$5Q!usO9T&$_ zFxS82(w-Ji!Q3xwzw3pkV2)h*sO#|*%*FwA%t3_E2XQCaD~`ASZ3sWG`;^uk7P{caTaVlNavyR01dAtKc3T`j(c+n(c$*VQ z7yKlPd&Zk4j-PDtw^`nahBfD&$PIAj!fpD)dfGx;uWI|701uE`1}#i#qo12o_VjoIlXnk&$W2W)9b|X z^DKV)UE;*%di;EgkFVZE9KXQgNylFm$NzN?cuO%IUgH;8*Ed@;TRflTSr@9M+8~bq z@F+JpN9L8CCvV!jY(tsB zS8jUd++rf44v@4OQZAiV)S({5c_ENpgDIQugn|umFx5R zW3Qh+@_tjY@CE7b_kbPEnQeBZF0Yd`oALA8?S;e62Y7th65(LSD^aqXDi~>5v@>~g zIeJV-^Z)W5u+8~%!2g+_19tQUW(VZxI?h2T7{BP|{{8>FQ7~^O_7TKWFc&{R630_8 zMXQui0plr{@ik|P<0+W>Q$LI2DVRTef>gSA3MN5`UE+8O=6I6y+WX-tm_*I(9~qv4 zId$c}uE$d_C!$tR>EbDvki28X@f6I9sc*&c6wLe4fht`*1v4n&7I8cUvoC%Ml`fuw z`5Mww98bag7aB+JOz{f z!eW&%o`TsI*;S>Br(jOSZ7q(cU_A1k6~|LBCp+597*D~ZT{m0>jHh63U3e*u=kM<; zi{pR!^P2zj*VXy|{==VvzfPj{oe=&X;(SuJKWYE}eNuLo%76H8f%8Lmeg>SMf#2#g z5M#-My@}c!*w*2G!lOr0X0`dwezvakz;`=xXPtZ~!R0Ii?jCwJ#eaL3>gQK>*&b<; z>#9SsRwQu0)n{U{!CN1`9`$&L_o`e6&xCK+|6ah3<{W}_K1W7$ zXj#7fo8dExZf#WJ+N?d^u8tnl(fq$I;57^L>R{L`#kRH9z`P3Dw$2)uS3%p>TLbed zXxj#BU|t1n+h`3;McFo41M@0q+h%K!)C{&*1M^_Et=7OCZQHh419M((+infaVY_XI zH83amww>0%+!nCyvIgd!f^D}oFgGe}d#r)E`(fK_4a}_*+dgYx_8PYR*1&9KZ3nD@ z?g1aP24-_@J7f*aj@@?H8kh{Q9kB+cA#6vjfe91aF>7FI$9CKrm{hWzum+~TY$vUO zi8|XUYha4dw)$@-eC$F&Ir;)qRF1CGOX|Ys^-~9i|9hn7LH_pUZ}E;A^Ly#`W9c(ZLj_xzJbM~R!FPT#oPaM_}8UoA8~v`>-w-dcg69I zEMB)x85JfSJ(d;BTBHWSBlk3ZJlWA^JU&prOG6LRUEF`gKE5;9&K-`9FvTk~(?cuvUNJsRnr zG2WguE-vYY?iR;$LgqR=rS1*mIUx&8&`TW83E8Ukcg69Xkoi?DuY1OLPRO>cT_KL= zglwc&Jlz|{b3&H(XnS!yF}7#fpW=8<$PPEktb4W#d$FJd4GKh!dLr^|Z~CuJcLR`J`;VDR|O#J}En&l+E`8PrCoNJ}Em- z$jo=a|M$1^`?~cW+QaMi)M8$7!#sxhyeeONanrsfM(j;D=V8>6xi=r)d@5G&v$eJy z>AE^_<>9X=m4v|=Zm0OUYh{w}QBUvb@y`NwG^Zo)=rJAr`Ck|C-+vR9 z^WOsw@6X@&H(@#Y0`qD&N7s2SDT<0+&fF8(#lJa`Jp?ek)BJcV>QL$sRadOU?R z??*FnJcV@r-U)F$h4g+~o?2!eJcZOK?Kp8fg%mdOoj9ICnzgfr4vX*<(&PuT#qktU zSU`k2=5g^9(%y27#qktU|5Jy=@f1?iBiVw?Ja}G1uwe9HaXhaf*u3+hIG)!KESp*` z*vy0HH3TV}EfUA`8iKNqBI`G2;du?gv_$pA@w|q>EntB-eu`Dnd;LC(<9Q81p)vvW z%*TW0H3Z9gtPscZ8iFi4W7ap<<7Zg&FWuWr9M5YAW_Ld-j-O>+AGLVS24)^SuOXPR zeT+DMj&=R~%kRYTb1j}OiC>7B2hVE=)~{G2{+G9|_i&BU&|HsS0B;*6j^{N5^J5;e zbIbD@g5!&_G}7Z*o^`<`QBiUHFJA?Hp?*La^M^;J{!rSyuV#G^HGeD+$5*#_v`C-C z@qrc}Rko7OPw^a|j;g*$9MAD-mnPB6>2WR3@o9yQEyVF0pN{BqS{%>usm+$ZyqO2j z@oD@U6UFht*2n1{<%>9;Cy>D#PLn6 zd071M&dq^+^WfYZ*f$TotnU*Rf9K}l-`O1eE~P%Sz#IRc@mqFsbz8k5{<35h-xht9 z@Ljq-yBwFVV`^B!Nv~@k@4Tb^ z$2;5l|FeJ{&3Se4fOqRNRczY#=Z(T$<0Nj@Z+ZX96G}`tQ#s~i|8i}1_i^-?j^=k3 z@PGW@19tQUW~bxmI&ZULhQG1M<%?EWo1K$6FPAc$0#287k~p3MF8=ntIGzGtIJmkt zLwE{!&(HPZcnUZ}-T2xJ;VIzDuNsKsDd1E>_GuKDZx{~iMOp4$5X&rb0*Y@2%Z8S9o$JAPXX`hy;~el0dIbiRwpHR3OLp6 z{^EEF*gx!{IGzHI5ay$k5xT{V|@Dy;n6fMN@6tM02F>yQvyuDL4 zorvHm;PJEii{mNao=dNb<0;^w?uB$Bf~SDH%or_>r+~dKJ`=}NzztSc)JX}R0xr{W znK+&TcJqp(6A?TGY;$R6KUuLn1>CClMR7a@d}g|bPDJn&@c!}B#qkvI)_Fh0@$;?C zrPsg)Iw`?Zz*AT66US4)iT7pFi3pwo_SrmG9M8INIpYj*{4Wc5Nawa*W)avHoJ{X2 z;0&n`XpEFE8cnWya(OKen3iwo^AL4il*mYE}x0we|0iP;4N*qrCC-;A0zuxi`aE7lw zCCof{3b@m=MdEl0czyB6CC&AC3V1=a5OF*Oob>fBaXbZl`)dj<=6DMD%I2=(cnX-s zAMe~8*f$T(&4GRM;M^S8HxK{z=HPcJ;2A$M#D3a3dhd7N0zX|}x3*czVY|JJGV}yjXS&i zvw$7V8E0Ruycq}eo4zyO$kK;DB=x8gIK+KgfwGfwRay6Q(wU6k9X+O_`QOJ#PJkxr%pe_@f2{`oeiRydGHi) z^@az<@hox?GiQr#uE$fri$h0=<0;@nv)_s1Dc}|XwPKig@D%W<&)dZD6!3~#DPx-J z@f7gx&ppNQ6tL@)o8ov1IOWxnu4W!Q1sw0jd~rMl93fwnSmt^>i`=@{jl}U3aIwIH z;&=+U_3W&%%{+JtxI*9Z$Li}pnvPXVVa7#!csgQtKS%$+Ze zr-1voMoD0<$5X)WQJRY5Dd1wGj)~(b;A^9ECN%TlDd3l$W5n?k@VREM#PJkxlZy7= z=!B<$qoi1!$jpzYfJX<#No+Wt0**GJsW_ej?zVidIGzGNTPj5oGY_5uu5zk_IGzGd zlJ2ZHo&w&IC{I!|51s<<_Dmo&t{JzFizo0s94~PHwKp zQ@~kT^%cicz!$pT7spe;OI*sknR)OOaPQ(v#qkudN8?y2%=LH*I8VLS;&|4Dg2(%b z<9}JerTV3fU?xqyX|K(^r`UFV>Mf3^*j$_4630_)+nyBHUl%;ZcGK7X>BLiPbpl)> zna9IZY-eKC7spdzb(a_dYDh~<@+*z#axh@?ZF1kMK zQ0-JLXDxXB^>g;-QCH=f-!XgmGX3|T19mj0Pmjr$-d3L6^hLdE)n28XJa5S9O)=M| zjJNR2i4&hMtaKmZ=rJA5KRKGSfX#P^*~QyDt$&Tzq``mXcR@S)0<+U`be(tjf3taG z^O0yxqikl}XlEq7XY+BP;eL(rY(AoI$lz|S$FupUwtj#(o|5)l@jx6;Nk58OHoKVz zPf5?7u|OP8Ne9M>oWop?r=){hg^1%R>09gPi{mNj?sGni<0+x(p9%URXj;Ews2R|3bQ_`P?RLO1T!Bf(EvTqW{Q_|@UCd*^4$5YaN zX}gKzDd`W}uZiO+>G{ix<~8%+De3vsXN%)0Y1_~U`ONirN_xuOhT?cOA2q7(5yw-~ zQ$D22Z|1>M(zU$?isLD1ueZ;|@s#v;-|7X-Ja|gFPur2=_`z1W&M$dJ98UpPNM4|z znFr6NVNth<;&|=>-+A>>98Uo+NmZwinFmh+SNJkl98UqadbC#@PXQ;to3XH&2TuWK zYcW_HPXRw|@l+g70l!J)>tW`>Q^2XZPZh^gz!`$Sh~p{XNC#?pntAXP@XF7d#qkvI z_Phy-nCtNr@SHL&#PJkxyekL9@f7fx2B~%15l;bcTi;C_PXX7Tazz|Z0dH&KsoRct z3ix8qnc{c~_*ujVy1j^}fTxacERLsuefJ+0$5X&tF1YLVBAxuV!(drbor`)h)g{dWsBY9(1clEtDyhmuYz{;1!kw?=sIt+GKRlVx{-E2)V-PgZwKi;rJE*U`S3#=7Z*IGn>6VX zaXh7)GfoWMQ^8ZZ*>g4($5Xo94;&W9Q@Rm6vglq5p3*HkVSqTE(w*TqO&m|@rkXQD z9M59bbCQeh!Qd&~v1b~J<0;+htq+UiDcvlYvg_Uqp3=<}Fh(3t>7MKGK^#x%9*kQ@ z_h#^v?$0$l#PO7F$M~sJny(9<(p|r{w>X~CO*;3sIG)99*qG9(%shBX_x`wr;&@7T z;^U~P&GmRnH{Q|4;&@7TOPs^vcuKcmuzMOa51!KP5pA3}p3?0b`b8X1=_VbSFRhse zKgilFmc2Yc98Up9JM~B$PXW(aP)VD6Jd0eK$1B9~6!6*#anhT|#Z$oDeA|lSDd5Qg zTgCAdu-kSwZQ}71aL08$#qkvIs3Nz;@f7eB&(hk&<0;^*_ZN!eDd6o1qG%J3r-0MF zZ7hzbfRpY$AdaVi&$wjNCLT`#KPlKt98UqCs(e`-PXYgNC66}ocnWw`?P2103V40q z$KrSjcxi@;+T7zQ;0-%gh~p{XW;qk+gbz;v?^)4V98Uq~`*>X(PXT*JD5VoVJOw=Y z`66*V1>EU@t4{dv6mXBmZN%{uu>Y0|;&=+US$9vJV+71O4lVxKXE*z z+u#3&IG)n&*Vz6~ap5W5zU}A5Gk-bplYh&~ z_Pn_E)cl{j{XbQW5dQr3_X2h_=a@>tJqAsE)B8uRXY=!PC|7oT)mc0D|4b62@vK=Z z9uBzq)X`%)n*Y}YT)WF^<&4>@+Ui&XvjMgRSp&0swgp=Qvn99HwFYKCZ>whwOeEOq zTLV)bwg%R~WR5Mw8kh&OHM9oix!Bgo8ki?+TVrcrRsmb6H84wvEzBC2b;;Jm8koh- z*3=r9mD1MC8kptR*4!GHwcOUi8pJY#me#=43|d)(m}b!08pJSzKdeD?GiYNCqM1Qk zYY^28+F64rX3*Xmm=h3N2Wt??3_4l^v#GFkvIgec#Mapw*vz2T-%R+}g`Jb5FEBeP zN7s3qNyFb*EH7R0)z0W~o0r;Yu~=dQu!uNvA%ZC!d)IPf0I}Gf5mzNq;T- zO&m{2?|ffBznKS5NvEBKHj;Ew2cJe7`=E1XA zuJ5v398XD~I~A*txgJkRuUpex98XENs&PUbPf6dNox8A^2Tw`Ysy%`AFjl}V+3oB=?700vaREmC}shLM_ zEtYK3aYu$H4fLMU?esFfjxX?(?){Nr;&@8;)37b#cuMzpIaeKD;3?e%O~b_Tly0$R zN5t`zZj`#&b*zD>bW?O5C61?bD-C@mj;D0PR{HB$15fD|D6&QzPw6Ju9AC#8cuF_& zz_#LeN_T9^v*LJ4cig_*T5R!@?uuli#qpFbi!Pl?8#l(2@m9#4rLdf7o7Pl-m$cupKoiB4-)pr)AzPl-0!GFco? ziLPnwQcI6(c}n!|$uMy|C3>RCesTN&t3)?8NLky=gJ+T3x}du_oZUlJLajTv zWF0dPoaaDCk;09;3?EjZ`z9E zDb(aAE{WqQ)RR@c>Y91*6l(cLv&HcgYMm(&>zV8E6l%ANA>w!n)qnS1aXf{3czU|} zW*$6+dg^{3aXf{ZE8R75JcT;ERDlL&9{gX0x@4p{o_-kb)hhEJcXJw)iH5Ag_>u3&PHY)JcXM0`xtRNg?c#7dvQF4 zTEVksV>1t)LS40Zt2mx}z}Fw92sPK^7h2zMZ({Wp$1k$DU$i^oc-Dm(9X^QTe|Zl$ zN4~D*%!3tqnOyHF-FyLOG{#f96a3ScH`n7SUH^og#PO8wk}N;mheIzDC|Jf-U!X}dU{(rvyrMMZNxp3+@arKdQa(ydYYhB%(m zjsLA^B{L76(v7))x;UQF-5d3rIG)m7)45J%GY_89z4Kzg1T zu$wrZW9kZVuZrWFSl6@ofse7P(vLa+u2z*8&xf)ot*7{jrv#>vvBwtz+yt zy~}4FGog0(qS5bk&v0)=p~acvxp*fT@6jN`KMUB=ocjaAvaUEfJc@7VyDG1SbpEr! ztqki59}9W&DE`hS(>|YU>*z5Z&F?JW|M;t*9esh>={UO1+xlC@V)?LrVcn}+x976% z@v&GgEj(Eq&tiFh_$P5Zi)Duv*$SDd@sxDx`#$1$O4_Z$QgJ*b{r+rp-K)b>(gCHL zisLEi{1Xp~<0ePf0hdlT-Kb@Ran2w*AHNES8b; zJ`%@M(hWLR);&BtC4FVlT5&uj{jz!z-K)b>(!p=Lh~p{g8va+s@s#w9=U%#pho__m z-IyVcr=$y?a?!myJSDyVbci^flJ1^mzc`-7a#i$n#m(m*Pe}**^%uue($yzF6vtE2 z4SfB)%{+KYI^u?n;&@8>;GZwW@q?{RUX1%WOPG1^6!5`qW5w|-a+%V;vtMs{3OK{W z>bi%A=N@gELz~6%6mXdbwo>MC@f2{c{OQE;6mZ2H1H|zZaI*Ce#qkty?2SI9&HQ)@ zcub8I;&=);q_=Asb3L8{zEZD+IGzH2=6YNlPXR~I>t5E(gQtM2H5(v~r+^>zy&;aL zfF~v|thmBcz_X{15yw-&1ztWE$5X&F?^IG;;VIyVo0p5@Dd2By<0_)?6mZm1_TS}> zr+`l$w*M}7JOv!_yQks`PXYIgIa3@@0iVoeQ$*n@;PPu4isLEZX0Hy2<0;^|akDC- z@Dy;IOr6B>tP6`04-?1#vVh&+&DINcwl*)J_kI!0mpJ@|NH>0JjQ6+r!r-!cVGh2k z#ZP|dCyuA2lRrKuj;Ev-{+UrP*uhiMov(Ef$5Ya==bRVEQ_@Wq71RrM@Ran8xl_dP zlyt(9+r{ye^qnDYdch8!l1{Xe15{!#RrIG&Ooaj~dgu!Ek8*@Xa`TZJ~=yE98b9hq|!I(vGE<0;p&A$P>_ll!*HcwX?=kuFGDu7ROVrgY%yi$5XCV1{BcF8c(@SD>6eIPq{{( z8%eh_@RaNDk`2W11FX&T^+5aW5Bxxje`%jgw=?jAEZ*^!{q_f*0#0=PhOWm`z*Cl$ z)N38^EOG}g&J)K|z*j0n)@vQ`6mXOdO~mmO@S%dM#qkvIo|*CVS_eD@JZMsTaXbZ_ zE6pWwJOw-JQd<8;H|G4isLEZJVOqN<0;@6JF@9D5O@ms%!v`=cna91^DA*Y1>Ev; zm0EggwmbzKzt9G8JOv!7PEx(r0Z#!ZtKL-{&$=*q&q#6nFAKPFzjQi|`&jUhj`1kh zpgA4I@s#U{KMsrIDc4)y+;kj=r(7c~=qQe-T!*f?ERLsK-)<&$LI^voA*O=ic z*T$*r$9H(jbxz~@I;O)@u7eZo5yw-mlh)af@9>nX$E?0Oro&UNu7P*N@sw-F)Fr&l z7Zp#r)*3Tg98bC4cX83N8~*=k?=7IC%GP$>1eZW?cXtTx1c%@f9D-}(E{(gpHSX3p zjk^bTcPBLN)+h5_^Xc9EgFEW}=Und3KD%m+-nGB?Ssys;UHRPYh(nX~e^zJyh8I^mNBY))U5)9AyLwDlf8K4t|K@wZu3lg| z9aqO`<_(^dndCm7o2CxE!0X()XOe4u@}p`v2HZ1gEp_5>4EWim&C37gc5)q+DXuzh zI0k%kL>J|74ERQ(1IpnT@VJQa)rrF~;GD60D~Dsiy=&i64#$A|dz4To4#$84_bycq zXOf#9%3GZ{90QJ&wv%!=23&IAMdfe|_(GF>>crs~aLxTrhYrVpvo!vy+{^#le$AFW68LS+R0k3}eNI4t>{_{fFI%XU= z27F=VYUOYY*egafU5yN9k~=Z9qjER~?7Q*2aySN@@lN7;=5gT|aP|uQl*2LLP^IoF zhhxBJPnHNUX_E#6(rbcNm| z)IA0q|M@Z1a18kJ`@~v~gJZyL3pg9;a13~B!xK6ljsZXK;iF|bI0js8)I{ZQ4EXbb zcgo=ya5ImZTBd_zz>&&rRu0F22Y!vKWjZ(pe1Bd~2giUr z#GS4jjsd?O_CYxu1K#+lhL-8z7;v=io0P*b;O1B2XqgU<0p~f}O*tF`4manb^1r#A zq?!ET?&iR09^B1=(>%DF1E+cT(apinV!%VzHjOxKZ`$~yU+znOWpS~!Z+;(>X<^rt z+YXo8^z7B77HP}I>zbxO;FrCV7tUTW>CXtcpFW8D$FjK>AM^^BY2%kB>667>zkXlS z?%x`)t1+i;_stYN#1uq}1{@-d z{f?kS!SkwTT3aMEMLJs~FhzP>#5Y9-Tf{R(Mq9)+MJ8LsF-2xu#5P41Tf{Pjk1b-F zBC9Q8m?E1kqMIVSEuxtshb^L-BBw2)m?D=gyiJkY7LiSn#}>g06aSvyhUDr6rqgkC zoNw?6FgGT-pumN>O}*?;G2LUp{SJiAV{$kKJSANdJYZd+f@T~z2HYU}T;*^KxcS-;+E##Lz{lq{ zQVz#}JB~e|9F74$7?Z}=JT4pquKQq^ayXM*iGvT6!!cmLfYRD-fMdW{m#k0@$AG)m zi=piXI0n3Ue^2Fb4ETPVi^|~`@Y`NKMa}bvW5A_8j8P89fNPF>sT__0*Ba)pYZ2fW z@PhiAmBX3j3ha+p+&nHE174J?mvT4;yr9VqU)5B{OggxW}P&%HbIBgt5^j zEpQC@lfQGdFdPG}8_$s(I0pR7Zv`bSa140Grn$=D81UFXLP=WS7;wl#&6LA2;0tdL zD~Dsi<5Fahw7@ao)LF+UhhxBl)4Wg)XI}7{>|DD5|E>Xlt~@A<`9~kqGo9`+;Mk!r ztA=C1w|AxYF~`F(;Pv79Du-jhg$rI$4#$9#`a56E2FHLmH*vn24UPflm>n{kd0aRK z-1vNB zW5D6+hR$h@hhxA;k2h8h$AFvo?NbiNfL|?5k;{w&$AF(K9jF|R0Y~k0S2?`7HDD%x zxVt%Ung@4t;4}~J=D=wlespv2vlwvq(`RpvUK{68lA}a140Dhhxg&7;uMQGFCK?2giUD1`bya$ABZ&xvU(H0jDgLxsn+N zjsdr?JYG2*1MV^TjdC~!d^1li?J>id;JyG2jyuqG>M~jsZ{V-%&Xn1MbuQymB}O+_8P`YUXj_ z81U4{&nhGW15JNHx$$ACLmy`mhB0T)Y^PkYR84ET2A>B`|4aD|z#mBTUMOdHD8 zH_soA0lzM~SUDU6-n!32d&zJN_(sIK%HbGr-p^Z=!!h7yC*x}`8IA$xdemMy90SgJ z@SJiu20S}fzDDNx!!h9Af6P`6$AH&N_0(Q690QK+*F-rS18&~_uyQyCymV2D2HRfX(D)OnFWpk-!9x*IUED-FyfeU zI0l?KXC^JPz%k%XSH>uZW5BUaK2r|IfP+R?(DDl$10Gv_rE)k1JUTFvmSx}=aFP72 zmBTUMN1Ki+hhxBP;%3zH3mgLu7k`v;I0pRa-V^0;44BCu?rsj8=E2<@*yh3A9H>e7 z(apinV!-{*?ToiG#f(GM8tk~5VSLUSIsFD68rrw@wh`qg&M%z0P?lfHcP}3_rP8Ql zN2f#{v^Hb+rco~Xf9T!fQ2E!M)jup9lBG+miyjsJivhbD(-n91n6Cc3YXf%g0SE66 z|Gd8k?CJ%k({Xj2uW6@(Z%lH>_D|EKSgSxD-DAMzHojL4$AH^B_LnKcG2rS`)+vW$ z!25?rl^Ord?VIW8T&7 z!yz_Be~o7>)r)I5b8%90Ohw>ZNix2E1rT zRhcmy1HRvAvvN3-T=`xJH0i-H;0a;-I3{g52Hax6ZRKzbxZs|wGGjOfTs?4-aySOO zq5B8ra18iui#jr6I0k&L#V+M=40yrQ499@GoLQ?Jjse#`9$jV($ABXh>Z}}&0S8_?ryPy}A4!{2W(>!GZ+Drb z9F74`n(#(B90P9o&R@n1$AI&v`a?M!1AZMQw#*og0f(RBT;Kr5fRk*!pyS~fFq1#r z-5fa0gS$C!ng@4t;4}|Ex;gk+47k&oRaKAP-JbORvZci?tWEdu(UyZrM;v(BsPWM& z{`Y5`dAzakQID}+n|ss>v8s{BM^w(vGZ99u*-MO<4%GDSRFc$p%;Eh3sCfh{7K zBB3q9n<9}d!kHqmEy9{2i7mpIBB?DxnA{xxA~Mr zBb37{*gVa;N6O(9ZGLlQ;ezJR!zxd$BYB|I3F7c$Be(02qG z^E5YxD~Gde{`$xb<#3kG6P+!h6$m)X=J8@IQVwU?eExcHQ4VL>{O$5ASZrSyamT*hqK}n>V<-N81)~md!68 zO|I<;ILqc!{}`+s&a!#BwGWlUSvL0x<)`flIA%QN#A4-emd)$Mh@fo1zD%?*U&uQSDzgea#lK>Fc(TP2aGEZ2G1xWYf27A)CH!3)%D? zTgayG+Cnyc&la-j`?ioxKd^;t`k^gk(~oQ+n|^Ey*>sRCWYbSxh-VVFKi*3erb#F!LOg^D_h8>U)w@9{l*ru>9@8BZT`$VTgay0+rs>7@cdv4 z+4M(S$fmFSg9IPxG147uNgG>Mw4+aTG^zgE)w~15_3X^w}}WL)dtK{=czBk9Xi%HcE_1Bzu$Wrl&%WL!x#UOAj5 zbGPcFNt{hI2u_|=7bY>hlO-8aPla<41GR{~1pd3z)|^I88>w2{V<$X)&GB%W49}8HmBVQ=T6aFI9M0bSt5s2Q zm~r6j&F6~WK{=eg`Smf+DTlK+|I5r=In6k5_U8A`o~|6u-u$jIUzEeyn@`faPA)SJ ze4>5dKTvm@a`+^hFU^=Bw>cipJTZB759M&?g&j3UDu*-aG(9t~g&9Y;xUd`smvW-y=uWoZD8Mu2AawaMFB;-s|?n%g*q}-E` zGfDlYO+r7*9`Lc@UyJ5@Ro3VB2G3XpHxyWP(C5XlimP@nxYc6V*~Vqw#0wd}Z^(&_ z*M2(CKl!;Sm%Vb;$&kHRtm$7r1az*mDBrE4MYH9Zx2|B1Z+{Qi)tH4oJ&l~hyK9rY zYsOZqeKGub&o38#Nn7&s=%Bba5++ZQvYV^NbT$6NkWti7oB2xH$L8KMwf%f3GrhU5 zf(||*|GWx1L;-tt!kbf7&=xY_Lbi|r``SVVT-X*e;3Bq=0T;D}X$Cxt*+K@)1!*$i zRR18sM|v!#sc`X+a>@V8<9yAa!8c5++AYuWrXF^Ej+PHGt&mq5Du-iQzoyuu9FA$d z+nlt583&GO9ZT0wIh;vuSO4qE;h0wc;pw#u2*nAWqxZI#0@t)WfMDu-iQTSMg0@*o`3D!bq}Gfnb37c=$~vx{ayX_{`uzpva3;AmW3$&VwSCW@L4wRw(Fd7_-vbxTb!q%83#Vc z=KG4xR1TkO^Yeo}8kyta^K5=*dL!lV`8E&Qdq6pSfz7jLOWW9t17B!!j}9Z0!x!0n z>bR%M;mi|DhgWQ3#(^_0q<`O7Ih;x7^t5CDGT_^d?-e(H*dxtw-DAL~M&~JEVmJo8 zq0)W*j)nu)iJH zqa4mM?9!LXN}J>1EW@ViIY2p_W!T(b?kb0~3>)EiF+Vd7yuN*&?^-WV4rdv*W`dAq z%<*uRVJ~!Rs2q*~zd65KIlQqQKV6ZeWz9J7CN}Rpp`UU%%dk-f+)@s2W{#G99L&9ma|_7see>PYR&h4)lmHY#rRaaC?TU2}cRh#@srtvOIUUE>zV z%MJb3fL)C_*1KB%j(0j60)b8GU+xz_%=gQpaH$D4Y^{RfxcYZookLhZBw*mi~ ze;bmk7nn}R)p5RNCJnwZ$!%Hed~wC8ht97=VZb*AIKT1)$AH7$%dW{Djsb`L)%oHI zI0pRg$txWX$AF)guH-ZamSe!luFX*n$AFUszEKXxfTy3Yp~)VO0oPBsO*tF`-n}P@ zngBSHT+<-~mBTUMNQWOOhhxBb|17VS05}Hxc&PLH&~Oa+a)wwkemDl~H@d5GI0n2R z^CjhQ40vjSyfS_`20S;yOyzJ4xOv3Cl*2LLsrBk>B>>JOcQySE2v23+WLWf?yl1Fl$Qm2x-+yenHc89y8YKH9jMa`+J2%yrEds2q*~ z_s*J8D*)05}HhA8Dg$ACj6PA%hyW58Pz4Ob4wfUh2MRtVr2@Z}9a;23b<;a!!(G2qlOPbr6Ez^$5Q)Jgyx13pw^ zq;fb0ygK6(LtOG>H8FQw?(<; z-!tyI;;(X^t}yRg19mm0EAHwsUH$nLsa)*=|J!^P^nd2JA-Q^i>2zEj=W9BI;2V?N zs1vDWNH0AO%D6D#A_s>mhhxCqQ#@1-$AEWjFDY|^W59p;ELIN3fR{86B}0N^!110{ zQ4Ys|b6r`Y9F75}co2@jsefw z-#|GW10JfRHS|~Ia17Y{N!|M9eF2UEA3m^MIUEB{=$%k|sBjGURk^Op;TZ7XwkMUt zG2qIP(`j!NjsZWJIY2oa11|RZhH^LtocKw>M&|j$G2kADW+{iyvOh1;r-jfSDjWlD z-nXf8I0hUqVW4t22K=?2^I6Ss40v+8u}#eLfn&f!r@c}R$ABAD_HSyAhhxB@daP0o z$AC-Diq^~=566JdPwu1~&b&}J)G+1n?;7x$HsNGQ8`dt9Sz*98?$=cg$ADk-TBRJ0 z0ry@QQhTUy4EX-hddlG#aEcSVl*2LLhli7E`4o-;H+eW%IUEBX`SgKuI0k(7Q%NnK z!t2_91P>=HQ4X(X^QB=UX!#V50XIM1LOC1*_V068IUEBn?VCo+r*I6o#;PI8;TZ7D zgb$R%G2l?+N@%$ijscf@u}C=_1CIYHj3#q92JE%Gsd6|5%;XPuHwRAh;BF3_=E2<@ zIL*V4ZVr0Jj2GLyso$D6qJ_EtpStC(lzzeZ+A(URNb=xz<4I%YFFUj4R@vdD3P!tI zvz2%E%>$~$x|qDc{=*%wmV6x)cShDydou-8{(V}*_@|4<=}|4P+uR5lMhyzv<68rE zHKr@>>M>pY`S1T8@ISADb{p`2xeD6zr@9K-)eB6gZ)VFmiM-K zJl}#cS~!zjnItom!!h8CO};9JW58wCH<0PVG2p(<4=aabz|{_BkXW9vc?*DJz+uA_jse%L`%yU@177*6ii{SH z0jKZ%yK*=NocBcdM&|j$G2rvNn=6N7z=Nh7QVz#}#|C7O(ZVs{RJq0~hhxAgkH1n5 z$ADY^RZT_<$AGhVZBY)#fY07fAftt2z++qXQ4Ys|Kl|NM4#$88wkjs0g=4@s8!u80 zXI{8{YKwCCcMUk#j%+g8>%A_1W57!ThAM|+zzdpRRu0F2A1}-#qlII@QI8K(4#$93 zTnkbT$AJ3=m6y@NG2jOo*D8l&z`2`7m(jv8;9u8uR1U|0XE!^m9F75>iJVhL3&((a z|20WD90NYQ?TvCc23&YZRT(WD18)6kopLw^oV;~(87&+G4t(4}IUECyQ|^p%I0l@t zd^Szya16Nfrg6&Q7%-DR+}#{F&4ar+aGD2qbKo=&Ke{>iS@wWSMopM-?waDwmiD+j zKSR3iExk&5_G+}b^K+kZxt>S!x%H*`>+N4&h2Icivd6u?*#jRXx_Kh*+3o{+BroiJ zIrD&eYuo&mdG^4<-M=+pS7W;3t{&6XpLcD*?%xCcsSViG3rwfu>NsE1J_g^I zW58d0^U8qX7;vm|GnK# zjsZtrSyl!N$AItcU#=XE0dHCwSq2Qpfa}j~uN;m64{3W&Ih;xE;^~|+U^oW+q{LL^ za18ijr%%e^81SuW#bm&64ERaNMatnA@WxN!WWaC?_;9~g%HbIB+CImX!!h8iX?$eB za16M*|5)X44ES-j=gQ$2@T3qG>znrrI0n4bca?HD23)>tc$qF71Aacbp>j9|9F%gK zaySNjc2!InFdPHE+v90RWX`lNC=23#*Niwqc!0autXQ8^p~UfSxtaySNDBSak; zFdPFuo@J+UI0k&LP%;@X90R`IaFB911|0v?1LbfGIHk9r3>c09Kg{~OaySOuGK05F z7tXx!W?FLP@b4OM@mOB2GK@F(gkhhxBdr|nb@$AAxdC6)ohG2qX=dn<=y!0kR?Qx3;~_tz~b1BPS3 z@#@S{4#$AK<9$^Q$AFpq;qKdd6GJ0~iicHgZ1 z!$#K1aWJp1*Rp)3X;>TC{BQ7QOa=evYRtB8<94Z(V&{=GJr9KHyR^sY2|f3BiyE|| zTAIeKpc**}Wl+Jo-e)r)S9b>G})z+bxf|k8>)H?#tLbMc@V1@Uk|qnlV>ib3DA9&8v)>pd4P_ z=0gg;QVy?R^VTIQt8;-@wE5u)PDcZ;Wb=7dzvy^)WyTL#CBJz-@G3UnP++CvV3Yg>J81{{0Rh7dr?5?4fDu-j(g))RHXvTqK*g-j)Du-j(&GH;p4#%+XH_uSW zj04B8n~oc$9FAePJ@lt?IEFo{MR{K{4jjXt`PVAta149R9q+>CcsPc=cWhhba16VA z$>Yl581}hk8H<>4;28ESuTje3820I2Pn5$k>})5?7B%C*F>K#`E0n`A>|1@jikai# z7sj!V$AHbt=xWTv+k?hef10**)sK$?lFiJWF3s`EUDl^M_cq4JHsK;p4GQn- zFvwfrO8ZmA4}Xm(-~O2K!#`)L zSpx|Ehif@M{Lj*<2lIgQ!{0MXTOAMQhksFpn-oV*=TjU=? zCkW2oTfNcsmBZP4J9K`layWZ$zrTzf+B|(w|WdXYZ|W-}GjU$OHbHJ#Rs$hA4-#_jY;GUFC50-WK~6H49H3 zaQ5Dw##pQzKHHAJASgn3b3A;G&0qCxqa4oO+xxYrl*8G33)qp}EQNW%ombDpBiyYi z%Ha#__}l7#R1RNgbKhBY%=(%Ke38vNtlX&_zS!o0i;{Yo{}IsChOZ{`oW zZ~t*;;BN!gzrf(X{(st=^3_7l@4ael7qm?dD z_e-jiPjZGBma=uTgqsTQ*ik>$^EmBCtm-(wezVZQyj;=x$%}V;8ga(rqn&P^of>!S z_&LA!J^ppyo-ltt411+glo|PleQUt3#tgci`Azz1TQ}S|6EfE&$hjH zKslUc*uQ!@AC>}V8FuwX=fhIqEW=i<8sujl7tS(l_b3&#j0tBMwnqN-%Hb@-uBjKd ztQiO1)t*nEXTK_kvkcp6+)d?hmSKxNFRW!uILokc$IVv`XBl>4icngvgtH90D|$2K zaF$`y#yYGV&N8fDt8^92^M|tx`?SPR<#3i^=k$N99L_TA{3GSGj0tBMwrT$j%Hb@- z{(38cu5yRd^q21Ie7QZGm6*T@&hL@KS&6wa&PP|j!&!-W-^uxMdpIjGLBBX(ZVzWA z=2E^|y80c?N=(RJ&X?Q6S&2#U!ufK0I4dzZ686>A?{HRPa@BKwj~vcQOwt2soU7k0 zXC)@;_7TeAti;qR_Eb5Xm6$9=D^)YEBb=3(B2U&ShqDrsHePgH{SId(=InW4@MU~-^*fxEnC<;XD2KBWGk)4#<#1MF=42{Z(>#ATD=~97J6FrY|6YlS z@LI>iS&7N_wQ?;pPLSoS#LPRpRymxNn7*@O)i%e&S&2E?wTE&zD>3IIT~`igB_?9t z!gb6za8_cTmR_J7&PvRkR$=R!if+V&*l<;q2W;4e%;##(~$c$2ZU2MmfBu%}XUYr5s+%=EE{&*Ip*Pw#~irOi~W7 zWAmX^-zbOIwfT&T{zc96f!DKn=&Bo)!vky{Hg4=<=6HC0n^#@aMLE2I&5P7JrySnU z=7ZN~FK)(xH?sNWXcLvg8{2$c-&e}vO>Ca*bQSGo!kgMW(cX2+;mvH$Fb+WQmdbH5;h1!%^?q5*IB-n*V7g_>;h6O8nc;oR@o-Ff&-|FeX^Nx;7prGM)$FK)1`=}g_VW+&?krgAuj-J|AP#xe; zOrmeEURMssu)|jL)rF#P471Xhq-!7<=aWtJ+3W59Q>o>C6S zfJY2U>ub(G90T50ewcDN23$SPpUUAF@cA1RC0B3^cuK?d%HbIB)}FB?QE&{nS@#~w z;TUkF5!aN%G2lk?3KccaAC3WsIW|{090RU&J(MI0jsb@}-BdZ8dEsQCp333hHQX zXNYn*rdz1uW94v6cYUie>C8BAO!vb0<;vlhZq<}t>CN$QOt|S?$+MNoZNCu z*C$cN3}zfSrn~O=2<320H&gl`<#0@Q`EWl?!f;HN$rCij2$ z$@^z9;4lL}ZJe?sagjsI>Mt%|d*C|1aRV!+X?5!3J^xh&C+%BUG0a@w+~YeAoOExL z#~Qy|mD4^b5x)B8L~mc+{ZRG(`bTiO2K8nCM|r?m(*aeJfi<#)6jcB99y zHwtFivhQQH_%ToaS}#HK*%8}4cJ-L9#&>PN?$3h$srP_gy})!ju8#9HGj;F{bFF;d zN6W~sW}Q-pgt<<+G+sFzbFF^PS$2kFt{sv&%g%7jHP42%T6TtGuD#Pl(;hP%bL}>u zt#UZ#>X(0zayW(>uIX*%a16EY+~V4MhGVEx3oTO)$51zaja1s47dVExreH_qa13=* z`-{rq7;2?71+>Qu$56+fnynm;q0YrZqHQ%$6+Ixm$s5eqCQx3;ai?)xXJ!UwD`Z!Hz5(r^rQ&(nd*;TUSRTKAR1F;ve8rK_0p0LM^6jbE-D zj-l2X8(DkLa13=t-uBAj80w%FCzZn|+SjFCsVv%KhEKA2typ7~!zbH(hW|6=@F_OW ze5RE4p5Yj9#Gtv#;TZ6r{XQv&W5Aca{I&NC$AB{iIA72K$ABwUj-owgI0if`t@G_( za11!i>oYnYjsY(}maC>Y|8NYrNTc7B!!h8l$;h6E)cITACG2_Cz>X6cvCx0?c^Pl z!<*Th$sg`+4xHw}-5fa0gS$C!nuj0V9Q-T>oUeSeyq7XA3##?;q7AaojM=cFl_) zVAl8l`Fp^w#{7L=sFoL}9m-K*@ZLC{SJLz7I92*)fVQ# zJg?azmMN~=BBm*B*dm50ZrUQcDQ?*!nkjDEBC09w*dmH4?%KlJ6!&Zq*%bF}5y=z} zY~f{!hqj1libuAHV2bCq2ycoPwg_j6m$nFNidVKUzgFq_+7_Wr@x~URO!3whX43I| zXNwS~cy9|&Q=It+39dcpI9%HbI5`2}~B!!guYlZxqt!ZFk+{TC~T zW2hbbhd2LlJm45=g-b1!!~Vg77mlGWedhfBE*wMkN*mU^4S2vY)C?1vD~DsK1xFlF z4#!ZV$H-*fTRh+xYNhDol*2L9J=$_wE0`Ghn7&bMs8<+`HeD?VGmyP1(L_yWf;e zTjuezn1THF$q!2i`}AYsTdSwj?<7L?=8{Y{x#c}{+i<;&=gb3Z4Kk#FMlQ5Vwm z$Xa-N;fQZqBnTYPvuWuZ9deH^8?D~5{--bgk?Cu}Q|%Wg$~Ws$*#telHDFg`y5g=L z)778}ed%bUJ&d{FO#CrL3&t7k? zvR_og+3U^SxQ_O~;OzB&{IFFy9K(*;CcgH%;28GWz;4Rn81~zchm^xH?3>RMXb%jI zVbA{DM>!nBE>-xBayW+F>Rw6hfx$8C6pNQDhhx|+Zbi{v7aYUR6uOIYIEL*x^RjX{ z&D`BW1+>=%$FQr_+p*=7-hJ7wW1LbfGdqKQ?%HbGxhxn&ukGG3=QWqt!D1)Zpv^KYQ6-IUK|8+5en!IEFoP zQYP(n!7=PeOGhY&W7uy-K2;9Kuy6IRq&+Y=hW)(e2IX)Jd-jf4bI>_Q4YtjyA4ZG&x`}duzz_pNI4wCetrF+ayW*)yJ49CGY%ZXu3USiayW+lOQ@*z z&GB#yJ3;1l%HbIHzK|!C!!hjMr!zM&6LpC?#z`wf(ygsmJDRWTY9!Ydx)t;0Mtxu|k z``f%omt>{Q@$hOkzrU`Fa(H!{N4a)NIlP9=L%qo6XU2g$i_4ri}7R^|8=%;Un@>z!Jur*b%Z zy{W=qQ4Viz|6WY~aCdXyG!O3Pz-b=b&4JTA{OIQ3XIbf)Qa#ac@4O@IT%Yn&m%zQ% zLWLbzKF^J{VKW~IeYs5J{biolDqdyr!YpZWw<|F>SG$*8=Z*TstLN}DmBAh7-*~09idHUMIY|Z^R19tTS)9JW6 z&Nuj3nj4ec%_l2Mm^#^`a=OQWAD#0qX>vFQTs3!Fh zb4MtLW5BBiu2Bxhfc@KtQS$`HfPd-LLOC1*{!dpchX z0LOrPzxZ8E6&wRT^2}S!6C4AMS*pEqIFsC#R40_fG2lDCK5Cxe81T~J6P3d;;KqeN zD~DsieU~)S$_g99^9*zOuYgJGyD{u@r zNzr-A;Y@O^wujIP3LFEzm9?>QI0ihj!9nG43^>f-C|X&8W58QpI9DmdG2pL{&+2$M z2E3<3F0HJE3;M^^otCZmw@EDIYT3LZ(!1uB{D=}~kxNlKsB?gWGFKOm9Q*aD8 z#-nvwiGgFl-O@NKF>nm{P`U0}ae-sND_dMv4rg9?@a#|J@b4OMt65_Tnm7Fz4YKPV z174E#u4*_2d^S$bLgsil27EZfaOH3eczKa}hHR90QJgW~_2J23%+TbLDUhxLN&*Ma?*H z3^*{}D&=qt*k?%OV&-@_2HfjyYvphZIAlBLTZ7;jaG_Hfi<@!a81Tgt&bJ1^G2r+U zoo@|-W57)QaCdXyG!O3Pz-b=b&4JTA{OIQ3XEERwRZd@slwqA;(yyJ0?~7M=X1XNK4>r#}k0=}gO@ zc&Uz@_|@~9tDs$txj)j_paLf+-;U~iBXgmn2_GNpuye+vGgnG{uHm_}?(K!KTs@|% z@m+r#(l-X|{v@nmoBgQ_*wqV6r{n54Uo&F{-y=Mlb37aa4mW+IaySM&W8@3v za18j(ifZ}HIB*Pj%>Hf4;Y@P73nt5Nj)!Bwd3O#~4#$8C=X|Cdjse#%=3l^!1IK_n zJl(7ujsbhGN~q=%jscf0)Kxhg1J1khvT`^EoM&huHJ5M<`1Qv5%HbIBoAzOS&GUpa z$)$SLOgS6_Zksz$IUEE2G$f;%OE?DHv*0A z;2XE1s=0(?z^`|7QVz#}Z*IJx9F74m8ktD zW5D|kEK?50fTw2l(uxxt174Y_t#UX9{CVRk%DF1E+a#HwRAh@S~f9pT&U7 zoH-lAbHkB&JDyaVk*;{IMQ463`&Y&^C0n1~^JjzM{wpp#@A~;g_LP?=wTu;FN6$L@ z20eQdx?1JzJ5R^SnqYX$mrGW>967jTs9xXh0lOOWO5Y7rR+dkCH))*OTU#YOU#fVw zjT8RZxxe_*-xJ-N(dX(*SC8px{Qu@2u-kzD_YC;QeIKx^7nn}R)p1^CCJnyPAr{#h z+6nCc(jgY8p$5?dPKVev(<&Var$fxOH=3sba5}`UuRAM;(;;sAdeJ%FayrD!G4qEo z0{H&)(Uu<+-Jaj z2Ha=BeFofTz$0R;e(Z9nuGf1;cco-#}^%XAju#>@MYS z_JA{%PNKaNID5c#7CYaY3uh0wW$t4-9?l+c+?2_*cLHY*IBw=a%Hiw*&ustD$t`CO z_A z`NG))o^))!ayWayqg#d6)x2=_fRnvytQ^iBaD!V1mBZNs{_9p+UF8dB4|sCk5z65# z^InMmNIAT(Jr5}>meEzdaQ1*>&snJ)&K_|7s8oJ6C&K_{v49?ZRaF%&n^i8O%dEx8< zFMZWpIh;M<+evRLhqDJfwQzb}%?oD_ILq>p%Hiw*`_Fx*9L^r_($tl8H7}e!;0~!a zD2KBL9Qj0SUF8dB5BTWkF3RET0k_X_LOGm0;BKKZ>*`-Pd%#(;j8+b34|rqkhsxpX z0pD!ud~YtCJ>bRnr`It*UvTz-2iAD49L^qaq;VB>H7}e!;NC4(DTlMdcP*25Ju^O> z6~0BUIw*&;!k2L4dF60c_(tW(8(_wPv%*)S@=WD$R``P7CF`5x;jHj=tl3C8oE5%S zGxjTov%=SIU+MZ~8eGTr5bX zd-i~-ok1ni$vj-ewr*k0& zoIT(V!~fFpaQ1*7?5(d0F5v6|caO4LIh;M<-dmIE!U}jj`#fjXAD|o_VDrUQ?e`OyyCRc=H>*7_~T4c+RorbSZKQ;S^wJS5E_&w{T#P7%DoKtUj z#|TSPCh1vu*C?-Le%{&tivhbDGy2S~O&h%}Tf6p&x-Y_cjf!^e?vSauS1eu8`eX3~ zwJVOB?dmaIjqf(#1F_9q9Vz%f&%E|Gcg=pNXFgj*G{w(rz^-0kIvrQX`3C=RWN;=wWC^EW#vhhxCo`khq{$AF8s&!G$S;23a$22+*8 zG2l-PobMZkW5D6J*471ja3;B5rf*gb$ABN-h_4Iu;27}Pw0)GrG2qNM?kk65z?BP> z*9CfT4EXWgWy;|g@agv_*Dr7;xy22b9By+9q%B$27WnAC3VxJUUW2 zoJp=u`lrg_7_iTr3VF=)fn&fqPOeoB$AEi2{iGa@0Z*w_H?J87jsX{}x=T46173b5 znXcZ4W55Hh4N?xrfaec=s2q*~Z`xc^SMS3y;DD)%mBTUMUM<4v>U}r{oMUNIj9| zJo&FOzUKVHG2llRS1E^M!0FpZFKmv7W59nc?4lfw0WV*FNjV$?u6-%LWD<@6x2ZK- zIUEC?+S;?Id0aRKTw_9GY&57RY;d9Vpj;_Ds*ZrS~a zYB&bmvrfX4=6E;;oOVomt$DYY2~jscIWw_Z6M1I{`ub{ca$90NX3x2JMA2Hf`3HRW&&IAVL>v}PPQ2Hc>) zJmqiCEwP3^>uTrpn z7bbtWyE$;02X}MeG!O3Pz-bA#<)7}U(dw-|J-N5eFofTzI*R|)Kxr=Z0 z`~ETOGVkrIbxx-Cey9%Vkg8oz6PA>&oOo@RTB-6j-`+Ay;-pV1T`%AL{II5_3MN>w zBTb%hb=TIKy6eg5z8!X~-q-iTu{x#p9&VkdY}ZRMD;}K~ruwF$(T-J2>YuB-hsXM; z9(oHE^8ZU*%(*Li)T-G#T{!e)wIfRU-#rgc-sl)v>q59Msg5?AH>K;dWv(96)%?-Z zMo;VfXhQx!sr@fUx{rtBn*TPyk=_oHm_Y_RNNfff?I4jEWU_;VW{}wq5|}|2JBV)v zS?wU68Dz5q^I(qbb`Zx5yzL;i8RW2oSZ0vZ4!q1DmmS13gWPry!wmA+L3A_7YX{NH zAfFwWXTg!*4x*Sr0Xv9n1_kXPk{J}TgNSBO*bX9?K@mF$Zw5Yg5Y7yW+Cf+|C}sy? z%)r+ULYqNxJ1}1jjuLj@X$B?jz+nby|HFigJ%Y^jA#bjp;HCeE*O^bWd7EUfKUUT} zUH|nq+49a|Itt>aSl%|zS#kVS%QH91WIlcm{4~ptw;n8xpKf`i7dOQ5Gb}IPD6jdX zc;IJRK6>&bar`XH-~RSS96#Igefb0QU-&teH}c$~|H99;yz!9)=4tZ4&$GOgZy#~| ze9O}`x-E`hVEONLiks)&1HaJnU9E$}@rx|4Tr`6D!tuZ_wme5*pg4XByyq!#JYOF( z+GjIgjUG9y=j$QajpE{XzSz!AKiSe;?|yTC2Hc+k_h-QU8E}6F+@AsWXTbd#aDN6o zPQH8}VfKI&dj}kya}pO|-qmBe`t?68U{7}e|7!}^)e}td zxVp~A{sC`ki)us5X%!9X)+&$Qx3c_cy-ynB11*oWzlNW=9^cyXrM0$-QvlH z=6ZZv%QI!^ERJty`Q&{E#qsSeKesA=Wit={H_NkZ>m`owV7d4Ao8tJ6mcLk7+~3TD z?__!C7E8tPoh@%36eYl1kMCmn?4upT@m(!1(d?2qzMJKFBjm4Q=D~Nj{6mYG;`knx zUk>+89N*LO`tR#jHS^$mS-$n&UU7VH%j4cnQO#VB?_;@7!9n8qzLpn>dq*7K&+?14 zi&r=E;QL#ic5#q6et_l4YelPJuE!6wyw=_>;`l+9do?>Jjvs7!kv5rXntAX;ERQmH zq&R-4<+B5yi{pn`o}fwPT4o;naLdOQ*d&f0Vfn*(acZ0E@gpr??$u2kKg#k>zE{QZ zqb%!40q`LS}V#qkp?zy2864t|UMjX#c*nu-y>zjG-oP^zpGFBYVN!YmJe~RNd3ENbySOYT;o|CZl zcV~*@ISG5;=$$y8ld$f~DmOIq;5i8!F?Ovuo|CXRb)q*i*W)<}YmuUpIG&TRnJ+Ji z<2eb-+p9ohGY_7Vu)2%qh~qg4d$7{c#9WW(By8BBCgONb!bW@lE{^9UtV@&hP0c)b zPQqSx9VL$EB&<`C=i+$Qg+?Pio0)m=KRpQx*xt3IxoCFFM0#Jve$hm4a6)5zRm=M| zPg2TUkFRF=@}(Wb@zpK=9DGt7U&Hd|udgCNmJ*{tI`P&zp#qmuopI0ev1#>;VndQNLUB&UuEkEgXNgUt8 zau$EQdvoC2Jh(Rp&dq~+bKu-O{L`C*8$}Z6j%4SJBmYNlPpl3-`)sDXUo#v@)Hu$C zPO%HV+JCO($_F(g2KpXtpY~~<5&QRMm|C!B(EAnbh8}v|AzS>7*S;TbTP^9g%?(O- zm~!Lc*3FUA@Bh;8@ArUR%{k^+w8mK{_KMoQd$N0dACIrOBYJ_~XADg_V^Z|SE5np5 z)x*_ex|;v3<0tok-31(S)Ae6+57^Zc%udJEbv|ar40&Ubi+*)nIy0VmDXZR7z`GB= z(il$xZ%b1-y}2Gw0oT~GRvb?OKTa7vgSj400gvd|MjTH89}T-p98UqSdJ`j~nFmh+ z5BKOSj;DZcmkbuiQ^47u70hJj!L!JXTR%@6PXUj88z!^49!~*>N*E}Pr+|I8pBBec zzy<1gXEF2ODd1UqCW_-J;K}vgiQ_5Yw*{+ZHS^#p;6E~K6US4)voj>jX0FFmz@G;8 z634U1Wed0>j;DYRPc4|;%!8+ZTRfU8j;DZw=7jS$*W)SRyiMAQ<0;_8AC8IRDc~$G zQ|2)9;3?qrDF%w;Dd0C>?~CIp;HIxj=QQ)+DPaF2E5-3Fa+?!J&1J5~Q^0p7v=hfu zz|9Vv6~|M+N$w@gZRWvKz~?LW7ROV-%Szo6$5X)1P8Z8#=D}0I1qH1&&|HtFfUjH#6vtD*>sFr@$5X&x z^W-RG=D}0IEn80!$5X&xdVCVcQ^4=%)G2J{!BfDI&+Zn-Q^0d#BrjsF$5X)Xa}N^7 zQ^29;-WSKSE;Nn&Ssee<0zUivw`AtQKK&6#?98UqStL2^A%!8+Z7eyH_j;DZk^?fOhr+{C-@JnOn!BfCF zTdonuQ^0wTMoVk1$5X&LkG2!XQ^4;ho_1buJq66-k9ThloSO&t=D@jmaBmKrn}>gT zbMUJaaM*!EwikNS_~hW|%_5A7^5X0D(2*A%+p_)S=Xh}=-hAJz)t!eoH=OUiZ_mC< zkFPaI>pgmXgUj{bENgPR|G8bWhL10>Z0n~OmtS@IdjY$e^T60GLkGU=-{GB4sOQ%+ zmG}Q6?Cn}H+7zmCZcwY!-iIQDa`l+5=6Bx%cD~*_O2~TguY47>t0$P9j;rf@%!(QE z#$uVi#_VEd9A#o5y=Sq!`sX{1@hp}tLs#=P*W+0%hlJZAj;Exx~^H){1n98XCHO|PWe8F)&1Zuw2(cuG3l^SHX5fv2Rq z_;we^Q_?SQUlGSs(!ZT8pxYUEO8VozIpTOqdhY>`a_0Aor=&Y4Y9x+lu?+scM;uQ{ zf4!Vsw=?jRbi(9=#qpH%{Yy{9@sxC>0#z!Qj|WdluW2}196uzKxhmw|dz%g7c#8S+ zAF=gX2mCO5ef7iL#PJmK?OE5v@hpmCz8BJKAn+7(oVIht@f7pSS)P^5$Azbumu4#~ zj;ENj%~&ChropK~in-hC$>R7)_VaW9 z%sX*B#r%3+RlNoRPchF(u~8gPF=wj~Rj+lxQ_MwcG#AHH%%MW}5yw-&H>VC0$5X)m ztsjZwDd5<7%j-20cnWxTxV7SV3i#VZuWIJ`!c)Mv*LM-eQ^3R5T^7evz*&wItZwGP zQ^22FOc%#fz|UrW5yvmFpa0AA>(wyx;3?p9qxOp9Sr-yNKQE5|X#qRBm(VeS=Z+UT zeh9D+c76XmaeNiaTZVlnj;D<0J}aqX1UzLN>evi%JY`&V?mKZjWjt*`bsZz%DdXlgt~8NWHwM;uQX2fn!>j;D+h_9>#{0z74$Jj{G?JZ0SVf`^ZJGVqjf z=Z+1;@sx4GWP8N%l<~P9Ns5|z@RV`M3%$khl<|XN*TwOaF^f6AnSH)k%<=BU-nkiY zFZRyOfP1lbZU+9R7W-eNfFr$59A{&=FhR?AW=g!P@$!1#(pfbWaXXx!$hTUK~%kE_r)e98bC4ZkAQ|lxz3wS#z4r0iJSQUvPprp2aM1z&mj~<@zH{E#0fbQ?6nD+$D~u zT<4@pse5vG%5`+>Jvlt(nyzg} zaXjTZ>D&czJd4@A3wd-;4o|t(&M;jZPq}7|_(2>`xy}k(Q=4Ku5yw-sj%Xjm@f2;% zNHu)S*AJeeJraJ0IG&>Qot#*kV?0IsIY%#XJVjgm;Z<=wMH{d+uTH}76m5}5Q^oNV zZRzN*#qktvmsI6^&Bu?YX!mVhB#xh9pZCEe5*1st); z8F4%XoW8TSPQvgM@X;}o#qkvI>!BaS@vIB?-$v3&82+aP+-v;o^yW$O^DUJf(a2_X3&C;|5w!=_ai(TO3d6_D}Im98c*s-Caj7*uhh}g%)lS z$5XnaKE==rcJP$$+uFa0<0;)6N6v`jDcvOTvS%|NKc3Q!Rer2Ep3*&k>bW?+h5bXt z;*WQ44xF0@_vXO4d2nwIoSTP#dUNor6mX~s?GLY$zQx58=Jt-(b54o2!SQ|^ z>p6Yh`7yb3zC2vYNnkFb5SS;F!qOKm()%@-P{=Y8Z zU+X7fxq5=x>A1Si$E=tkZ!B`B7j}$m#_Lwc)O!lJtJisr@f2{?`Z?m6>+uwDuJ;qf z@f2{xz}Mn<3OLdczxZYzJOzBK`do251-!i2TX8%EytHEV1ZEyQ1srDFc5yrf{61IG zgywoYi`=Hq1H|zZaLgi)#PJmHfF2bRnR)OOaHK5j#qkvI$4jvio9pouaFV=T#PJkx zgbSC%@f2|2{=7-dJa`H?TlwB3ixd3`bo_^cowbDsemoob7Jd6y|z71$_TYQ*k^6y!pZr zaXbZl@?^%8W*$5RJay4naXgD${_wBF@f7glUjC`fJa`KD?D9?GcnWycweRA13V3^w z2C2|93iv_eC(i4wr+}x8FPqlPgQtLZwp%Wa zr+_Q_L`-L{$5X)JdbbqEQ^1op9uUVcQbge?V6U`Y#PJkxsdcBt@f7gKP?;1_ zcnUb^{RnY91)S~3V{tqM>^Z5T;tEdze+gVKj;DZ=hKZwy!c)NQb9EQTQ@~?=uZiO+ z;G9j1D5CHb@R{@T#qkvI+E}3#QFsb?QU2!QcnUaFuA}03)`gA>u8ZS;TEOSN2ShVZ zcH|8oqnh^=aQ{wA#qktyirSyW@f2{40u`g1UyrANS5;Uhj;DaXG>sI)T#u)KPj_l7 zj;DZMtocJ6PXWJqz@gQtL3Wtk|Br+|yyc_WUefQt^V>SgA^Q@}HOZxY8-z>N#U zj%BXLQ@|aCM5fQov369C0+Obl9&^*+&;j zRV_1jWrD~%hKA`8Fd%!6f=5Pd-m?6jpLalyV{cE!{Uh-af4^0&PY%z$c+T+R7lV&S z?iDllb&rL&_5Jxje-^Z>IhSpEc`?_U(({5Zp8CDQ+`hedrHt1ENNp4V~34$Owxan%mYF5Gd=4$Rixaor9~7dUR%fr$;rO*=59 z;<#l8CVw2a?Z7;kJ;CfiU0vs6CJlLG z^U-YoR`Xl(Xtl4J-n04e-;~h&zCG}4K4P8eBaUbDk?8JCaXgz3uP8p|lj(tH^D$xW zJaIgmk8Wu_%`@SFXY=v#V?%K~n-A}q`^52VK9ZMBW}aRTJe!Yl%leArIet$N@uoPQ z&Bx6yh0WKD2cFGG_``F=@oYZ2kNPf-XY+9{Z#``)@oYXeU)d#&XY-NjN)l}@@vZD8 z>GJu$;&@Ib!ezNBj^|`z`NG25bmBRg@QO1>9M8$bv36g?@tjOF_b;JuYIsg2PE_h9 zj^|`zYW}O@cuppUbuFw-C!UjuH}MvV*z$8$0f#rqF&JSP(k zPUX_36VJ)Sz&6vw@tjPQI`Byx&&kA{Mzyu+#B(xHDPX5Ko|B1=K1sFd#B(x{sc3(3 zJSP)%rr#0Av-#L^v53A^<2ji)nqYxAo|B2gEyL*K3D3zyuVih+@tjOFA97wC&&kA- z;A}d1!gDfF;?8h!JSP(+>OK<3b21U_Vp*L?;W?RDePoq5o|B16b7Sb_3D4$Z#-k46 zcuppg);=$e=VT&iL>`?;;W?QI*wIHE&&kB9RJX+OoJ=fD?5h(gJSP)g3m1#yIhi;* zKfF$!@SIGvFCQq5=VW4H&tu|vPA1ya%%Bq~JSP+N(~Jc+|aCJ+q8+c z^_{*y-okoK0!u{9oH6{~R8K?CnBiYLV%7T*`}9t=&-+C7DOGa^zUUiVcUGdF%L1w< z{M)~V`fakQ~Gp3({~d_Ww}B3Eu|G8HqP(i-)-oj9J-s$TS#`a^wNxcuK3; zwtM1u7P)J2ORAXhlvbo&LE?By>+q8BDrP*Tm3c~_IG)nlJoThFp3*w^B8#)It*5k3 zoE#;Nr?f7|dLfRdv>rG1S25!$t%cn`6H?p3>UhE~UyCPib|UGejIuY2A!} zS{y&do@9*amrcctr?fV7n<$Q_w2FOuCyuAIqJFQTV#ZTixt8t_$5UE~gPlKa5>IKx z-_k=RjiQ(9@iP87#av%lZjF`kLzr(51{X-O3`eum{`a?BIQ z&$N8Qz^~%?S(ZQVSX(8HpKbZNGCRcab1W|#%lTRm{9MZepE+L(f}dyk!M+bv*!cOD zA6i~U#f)EI`IPM|#qkR*zjh^>iW$Gi@`|-Oh~pPqUS;}uar_d?!*0)`V#Wtq-gCor zaXjln{bZ}f@jtx>yk%S{b0+H%&~1S^j`iRev)Jy&;&_fROIJTAj^`Nj+U?Z(aPS;s z_TTROV~yt+b4mUCx*pFl=8d?;&FQfRo@2~G{TGVkDabU>Jk8Ow2fm5@q?`$CB#v)t z`PGyA#qk_tmOqt3iy@w4%&nsah~rz>d00I0?#0!)__`NY=i=*LT%C*WKfSpADg|l2 z2mPDh3JwpGwPxVl-2)ESOFSyki!oka0d*@6ZR;DlOWg9_*@{HZ7rSHXzO^UBO!o56 zH5&#N?7ZtlsM{YNKOX2^=EtmsYrA%Nne`>4$Ol&YS@9ezwVcslCZf!WPE>e+$WB0K8af!SX>8rXr^v^yHwfhhq;BRen|;b?3JrcE48 z?7)PLqp2O3T5>eA1CwBm=5}DZ&e6gS%zEZ%X$NL8bhNSq-M0<21GCUMTHAqnIvs87 zK=*Ac{>LChdax^Y^`x6?-EZ#CfcrDx{tUQ31ONLz1AndQKKA!s!4_3IkFkXe`uf~k z?<-PlEuU+Q_p^NJ%kqWI^;fO0WcjEEi^TDjEw3N+K^*UI`4qnjMa(?-0L!O0UMh~S zVtE?hui|*_4c8b}!N<&l=iYF!2J^-7+#8;s{;T+3-p-$Ocip099(+y9TYcLjj^_nR zwQnUYX0FHc0;MtW`itW!`z;M_i{mN#+aWCOEp@PtmYr#`+hi(^vckaIX+hqM(1Y@QZZb2b=KarKzp)t^M{ zGvV+yk7k?aP5L~3iC@^hae_CeNw%|Ra#xS(YW|+viqp||BQhanz#7L@@@CW+%&RNhSZAdY8I2^v~6jF|_|q7rm- zyEvXjrQ@D>=6J>f&!X}wbWd?Si%PjFH^lKQD(xebG-o~@cor3(_N&D4EGjz(MKA|O z9(WcNud$89@hmD{RdLYPHi^{tWWy~K&4?K%X zokz>X@hmFg<3%;+Vjg(z#fI{(E{-2#7t2|*o%i_h+>32BC80TN^T3a@*QZO;M;y<+ z*v-jqi{mHQ>w}K_>Rvvcd$Ie!ED*;}ve)Om;%ScmJn-C$J-VWiIG%g4wV&)0$8#?> zX{to#Owj|+z1Us_JB#DF7dztoDRDgaVoTIdXAUYo@Z5{NyKay;ewO|Cb1uFsj-PG$ zrS87w^wR@B$MUr$gT(Q3Ew7q1k~uo{z|XVXcXL~D{Cvx=KK?@-zrgaApK|J6K7OI) zg_2AY$1k$H#N5x~_{Gd~wT?OD^}sK&Jj0JY;w`NYvOMFbr zH(m39=FR<0+4)V|{Y}~VP5b}VZ_4g_c+NlX?*Dgx2L2~M1Is@FA>-%l)KlOSsE77{1Sw4AX+#Nanw9ScT-g_`&&9S_T$0uqW(IZ{N7+p5!ulsF9 zx3;9O0b?y$01_z~3tmamJGXJJ4n zSC8pxes=-?%YPD3$H;g#d1)eU5XV#AcRqRPxEN1)CwbIG98Y;KeHbi`r@RaMIN8j*;<{cY(-<#PO8(y$+dljEtweJ0%z{j%SmXHR~O5JmtOgoR5x? z@s#)1UJJ$Xly}?CVRc-Lr@X6`ZzYbWybrHBE{>yyo{&3FLb}G z>+zI#!Mpi&jEtwePXx~v$5Y;W>v`z77*BZ@uG?4~|5tg3J1mZ;yu;*9UDbS@2U$;f zXN)me98YRZjsgQvW+&zvoer@SMqe=m-wypI;HRNc&jr@V{oS|*OC zypwr`tzoXmQ{J&2G!e&B-l5kY6vtEEk-Mg=Y39LG-cO2+630{Ck&nL+$5Y;sU;EcG z^WZ7(-k&!)x%HIy$~f_Bo9pqEcgJu&#qpGPp#s;%@sxKsf1f&L9z5l}xby;XJnKS+ z&6~yXKP~T-zh%`u<`XH-7c`Ssu{odo@<4HXRm&S)xgd_OX8G0S>2;48U)^%QDTBoE zH7w5-^gtY6)AE+<%IF?5zLw=h9<3C|*S7p&+^9b0P*w71g70CQi8d%)(uUCo(g#O8x5V~m*HbM2R; z@%z;)m+NKhtgTlh{Zcbg_{DdU9c}ktuY!&;+xbI#lZMRdh-wE(%pjT_BsPQSc96&n zV%R}KGl*#i=H8ye%MRk3K`c9nX9ltDK(B(1V+VQ_bX+?yXW5Q;c3=*|9r5kJoT57t z*nv5kcOAcW(SeYAh{iwyE={(c3^G^ zIa1nzxu4`nWe4Ual_Rwsm^)vNGo_jfw>pwna&Q(jXXzsJ1`4{X9hbkPp2cJ z9hm!-j>!Kp2pRwNDri?vy1CZ<=Kc)0KLhU1fcrD>zwa|(mT>dt@Xgkjr$+;E zJZ1b_gN@>4t*4AnBn_+I5}q=?o}`vIo-)4Zu}K_H89!_gM`ekpjN^UmCXT0!8(qI5 zj;D-$;uKVI;wj^831^7oDdPjDK8xciXs0-yGR~JYk%|*f84r8Z zLmW>T53G7c98VdiIh0Sw(Rj*uRH5nOc*?lu^iSe=%6QPEnmUfgQ^rxVZ4<{+#szZ5 z*ReDHuQJZdCr-v01baXiQ7nVKZju``}y^Z9#viQ_po|5odUIG$tk^1elM z9F6DLJZ!oJ;&_hDZ=?vVV`qFH`*|MMsi`=gGEQ9hfH=ONy}sSR)H;sFb8H^x%rJ5M z0DFCb{x8Mx9GkzqS6#=^c#h5M+?*ti=h*!Gi)Z3^j?FvoEvMsXJjdpJr>qvob8PN2 zI);v;@f@4yKF~rOPZ>{7drTb9v3ZNB8Fd_u=h!@an!e(As$${PVJ*$~ng6M`%B!pW zR(t;Vj~bg&dMkgA-twn(s zBN}d9`yuauI6{&1O7j`2mA}a3fk2Z%vryy>wN5=Di*U1?L7(@Gy2#mw*SLUBBcS>eUuw3)-Rm|c9(S{%<}RyEXFaXgDz z*V(y!%;yo$VivE^ba6b3+2EO<#qlg=;S1K$W)9C{79sO4aXgFJ>V(O(X~VOa#Yr?k z9M57Fy3<{8Jd4?-szrUx$B$<*Yt(U}IG)99($28jwBcFI`gUw9j%P8ed-Z}ip2e(Q z7jJFa@GNH6nvN95vzS#L^;jIwVm7o+1#RZ=EM~7Nt`*0#n2k>pOPe-4i`mtYoyGAi zW=nru6vwle70Tq?1{%zz^aXj~c zOOKAEO&gwjz@zfE6~}WA*t5k+ar`9vxXVgr)}{?V+4AafMvLR8Sl++tQ*r!M%X^I} zsm&aIn&pcZ&lSf{x4b~YPvZC)mfv0Cugx5Orsa)4tro}6vi!x9DB8^7XInmce;aZ9 z9LuAP`$HT**Yfu*a%nS%pJ#aq$8>T0e9Kqo`YMiJVEOct^|hJ9FSOi0aKAWyk>ySP zOr=d5ezD~*(+w5JFR}b#>Br8^pY=hOdmbyM%^aR}VMFff;`pE50}fa6j<@;4-~7`M zy{B|%uFIXna6F~EW9Be%Jf-{FfotM;N_XXjJUPugcuM!lsp;Z)N_X^@ui|)0x7Z)` zbD4SYly2__d&TjT?(N9QbDQh&ly3Lg{l)S1>?d)5%DdutN_Xa~VtLFwcuF_%m<8ha zhISs`e2%>4dOW53WO4&>d}Dk4-j934@sw_;rb+XedGM6(xb=O-@s#e-c{j!JlcY_kj1`-JPds##I4*CKWqb^XSL! zC02%7?c-fJUDgs)R}8q^tK-`Ir}N&pHrX@sZNE#;M;~9XKiZy0<45I+E{kQL& zfAIX)+6%e5|Gj`+%^7~%{R(M2uIOB9(VaHSM=wu1_F}B{Lza%3-*CavsLzsrZ|>?b zUCsZ~0(Q867WCi$9T!o?v!5uCDVjD{06Zi`;|RF;o&6&#YCMP{2oqb`Zx? zz&S4dA&#ejXVlED!oX9&!P~}(<0;@AUeCqx6!5I9g;W@L3V2h(3F3GPxKO%x;&=-9 zK-(HB3_JzAIpQvHJd0fD=MZr`1-v@-6LCBR{C$a^3Ik68Zz#8398UrF zTj8aWz*E3S^K=%+Q^1vyTolJsz`r^2sU+|eaPLqv#qktyjtXDJ@hozgv(!>y;3?p> z`F4uqDc}(uld2@}6mWx-L&fnFaMJeA#qkty=vx_75_k$Y;Ke|3JO#YI@ojND1za>j zaTNxh0uJX0630`(Q4dE{N#I%J-Xv=+j;DYt4m&B1r+}+2%BI4=Q^4(Vb`!@_z?FtM zUmcC7fIsvvsAFV21-u~ATyZ=FJT|}c)zNqgIODAbRm`7rJO#Wr`d)E71zhx1QoY(4 zPXPxW=qrw=fWL&gDUPRr=jF>+%{(ri0*;h+yf~f$KEC0xIGzHoR-|}!GY_5u{x*J| zIGzF?(*K7zo&xS(qkauD51s;kpX7izo&rApJWWk=J)Q!dJ#>UPo&r9-{h2tP0&Z}k zQY|wNo&v7(a)UUY0{*-_W^Hplo&s+7xRW@Z0?r>jSRDUX0Y}VV$IOFgU8vr*qPVB^ zKP})|xj*PwGI7dTdQSmQ4J@Z)Mmz<4F7|A3JOzB<zENw0cTFuQ5;VJ&-!vs98Uq)j+@iRd{XcfaMbRT#PJmH{b%pQ@f2{98C7-s zh^K%j{n#jur-1KH^wKdSo&v6Sxq~>K0uCSPtT>(mj@~l6ule}#6!5w+K$m1zs7Js~ZbKu-OxHkvR&4YV$;M_d?)0=}|rGVqVd|N8uyw{4;fjj0j zn)V^)mcnvZLTNuIvzt=N|eMQoG0 zz{>)!ga7-hpv^&>t2wiO3_Uwjny>StrfxVf)!Ek%UgwChp=!FikD`AK`=+G#4qsP~ z>1zI;7O>~54GrR&@2p8f{^$5;2T9D}lN}^BgU@!5$PB*NK|(Y5Y6l6-;F}%9H-qnX z5YG&L*nxR4hsQ?egT^rfhaJQ=15Z1MWd@<_z{?Cm+d)h-2xA8^%pj~CL^p$Qb`Z@B z!rMVqGl*aZQOqEs9Yi*RNOlm(3?kb>L^FtD2NBF5svU$kgJ^aT&J3d4L0B`0VFzK% zAf_FJHUlp^FkcOhSa#rP2C?nHVFoYlr^*=f#?I2!6U&@N6XOpqyeY}k3dOVwq zq364a9J@H8#&k4kvv^6uEdGMS-3=Y~V zj^_j-R@``5%=LIqAS$%$ERN>{;&#%5;&@IV;zx>~)y#wE1mc@lFL6945MTUnisL() zulta<>#u#YnfG`$84XqkiQ_qei2f>ac5^+R6NnpeJBZ^sfhc?MqBx!thyjiA>9!W0 z6Nt9EW{BfCfkh>0%6NoXH_ln~=fmoR#g>GZvIe|#qbD%h$O~$!b zx5V-N?B}g}Hs4(4^ML0BqG*>T;&@IVYNn2w+gy+51S0Iv&f<7ZAmSxDD~{&`qG+0o zdCWX`P9UzWA1;pP1mbAnr{Z`{Am(TD)9o!hClE(MZ4k$^$yneYE1!8>JSPwz4|EpC za{`g&Nw7Gc6Nr=v_zq&a6Dh1rxFZ`U*!3_^>^;kNvO{O`m zyZ=^h<+`aeo8NkP*n4NE&5yslNF3)@rM;Ujyy{WB^Rv}wc6u#-H@(Qc?Vk5Kt&DxL z=I}!CW+s2t^Y8b7UCr5TUdB_2Vq~l|e8XX%=KCrRi{Z#zd!7H`6`dAd*uHJ%mmpV< z>1zI;7O?vsa7c3g=g)%vh3^5odV)FYcXgeQ*|CJYQDAjKwbDJ*&W;FrPk~jqa7<%7 z1(q>KhSKKO<0-JXM}~{zDX@$A{uIYkU>7GlU+sx!k=wg*Y#B2@o&sAp@18iG0&^@c zrF*M*3asJN<>Gh>tnJ8X<;?te3M_rOj^cOVy!6~|Lx8!lAPJybjeHvQ;YaXbY!sj^o!^SF2l>{-Pw;&=*dT!CP5 zJOx(2d7kQK9y|s1p~O^iJOy?s)(3Gs1@^|jS`9M~o&vkyXRSD%0vj9M2zrgai^A3yS7h3*wS^BzW9{eK9BgYygj$dr~@*dB` z@k=aEJgibZGY>w<@-scwi{n`rDnC3dj{oUB;9ITA>mF+PM6Z;~6tKtDdE$5qc;Wij z;&=+!`;U@7=7Ymiz~%1G7spe;?QezBJybje9P@ZHaeOWNo2+o}s5qYE*;UUn=pHJb z)EPjNiQv%!I7N}74_9M7gcy;L0E!p_6u zk9ThloSO&t=D@jmaBmKrn}>gTbMUJa@ZRP_-`4HX=hnFav3%>zel*MPQmasLYt@?j zW>u~sKUOS%mgz~{<_ zU?k|*$(2E!4^A+Z3bKIAeI?yvjZ<}U4EEVUBs17=2NBKSfE`2#8H74$2jN2oo`>upoVo0< z9fUQ5-|Zlb862^L&}MMd4$PwCIA#atPOsy*9q1nL%KtDS(u19lt0$P9k*n)`%%mZ2 zEQ(2L1bM-UR#PJk!hd5uw@f7pSoeflkcoxM; z0}hJgDdx!s)2saO6m!9QW5n?k^VI=w#PQwirsPQ4YAQcG#r$;W7I8eqye?gQl^>pB zz7o5KIG$quBlLA~JjEP7Oc9kIo??zxcb+(&Me$L=@8WohIdp61t8noYbEr)RRD^hn zd3dXgDnC5M-1?6R;&_TVSGO19c#8SykkTqYJjI-KV~{wWVvaX0q6!aBF&`e=MjTHu z_igrvIG#oE#$IogAD&|Fk#mwbo?>p=0dBFOH{}A3qDN!oyR{ zhdVbH$5YJjyBrh8Q_M9|WLEj%DdztEqr~wPvtRb7;&_U=k7s$6AD&`fKW4c&o?^~7 zJG2T9Pcg4wR#zNPF@MUq#d*E;6!VKY(N%bOiaGA(*5Y`I`Q(P<;&_UAYR`=I&G$z< z#T>TiSaCeX+%n7?aXiI*U|_WdW*$7noO}K@aXiJGYi^>3=6XEEymED4aXiKRD*7F9 zJnKZtLd6@IdGM?Y-ox69<5_g#&cE!Br+{A%+obF96!4-?v2>h^ zr+^Cwb`r-^z}N1d6UR5Ti-b?9oLaQ;6flc9-o4m6Hv{g)-nkiYFZRyOz^}I0|0)F> zdSmH}HP@djzkXN7y^Y5Gv7r63XZ`0)K6YY8$iGujYFEiQJ9`Zdn|VvW_1V|Qp7m*{ z*R$63o+nL~D^1gH6Au2Uyy;@oX&sL3E_n3s1?+0hkrBVu{V_BDqm?_ORr33uApXdF z7s~stdU&!zrsk)XylDB!)nmGv|EC4){`Y`G`tx6M57^Zcywom=uCDVjJDiX=igx(d z4H?Y%#W#Pwr)Up6iQ^~{yo%!8+Bi$|Cyj%Sf;QvQoLo}wN9q<$7N51yhOxa@#9o}zvJAYE2-J)WXH z5_*g{o}!I5|Ft-tqHSEPYBnY;9Eb3nFmkNM(w;`98b}ni<2RzxgJl^)@(gq z98b}Xz4=TWPtktdT{4%M2T#$KNa%d^FP@@p^D%sGb3L9#uF{`@;&_U-^p4Zwc#1Z_ zD|;R@51yi}*?WRGo}#@p|D8CVqRp_ma9%SHo}%3wXP!8oqHXucGoQI0PtkhSYbuVX zXgB#C5yw-sFJ5HGZ|1>Mw7VmW5XV!r+5h-c98b|!8DFM=nFmkNo;%zdv}|X!aP{(ZUyw70^XnU zvBr1`xYdjTDb4kG3OMZ6vEq0NxZ%mi;&=*p;DPd~%shAsxXZ>h;&=+U$>Er(&GmQ+ z`0eSA;&=+U;kR?*cnbK?(41-XxYkp^x1vlD$5X&19=#LCQ@}oBtEM&c;3?q2B{quV zDd5d(ywaKL@f2{k#2v-)6mae-XT|Xp@RnEJ>CHTN3i!kG3F3GPxYo+o;`kQ!i-yG? z@7^3ZHxKU3fphcV-W)hL5C8P$;8!W&cPpy*{CMHZ_(cQ4_J8z8=^dMjlq%F9U!6B+ zo{nEL;dHsm>t-~o=lx=Hv2ppDh0U?O&D<^7MwTwMY){1}gMB`wh&R_!r~K6?|NSRn zxteqR)tfDI7C*lBK%))wGBnv-Zj=A)Gfy60%AeF@aGz5RPZxIen6BpkX#u-m1^w?S zU{_BtI~`Zo`Ir?mlhqQ0Y@LdLL5&4XUZL2_ptFS zay7mb)fY0Z2z3V8S_=c}OcEOMJ>uhPA3JO#YsRy5tK z##6xM({&WbQ^0R-ToK1p!0}5I(>-iF1>Eh)9C17aJoDoRaXbZ_E3)%v+u|wUA|JNt z-Zq{BzV%z7Jhs5zeFW{7= zl=A^MpXhji0*-uarntBD6mXY6o{8fr;AmdOe9UhWPXS*nIa?f00gtTlLmW>5-)+%I znUAM{V=X%*j;DbAZl_V^<0;^QN3wx$=ii9#zoY+xv?KC%eB8gu=!>tm)SKP8RKo4gJ!hVqU$%AG1{v)jkr`yNgM?;~*$xtzK^8lRZw6WIAf6dyvxB&1 zklhaAn1Qz)#5RK*b`Z-9a@v8H8RW8qm}ZdM4q})=9y^F`26^ouni=G?gQ#YZ-wvXf zK><66YzF^%5BL}U*N|L&0kP9@b)8Sh@7cVu$VF(KKqYr?G3JOv!F(?xMS1suLnJ{23D0zTewrZ}Dg{w?UMIG#mr@UU7cHarD9=)_KO zJO!LGRx%YEo&s)pbf`F<0-lxD`RZjn1^jM(ITah80^YG;kvN_Lu23Yj$_!5dpH0|5gPXT9rlUc=vXOWxZKUN%10dL#-QXEeKS9=hkV#8CwALGmx$5X(O`h6G2 zQ^4WcHB_QDUSbX0nfPiRfX4o z)qK6DfakWatYX7cz~!qh6vtD*gSvhY$5X&-#`>$+@D%XAVH?Hq6!5I>aaC-13OMz| z9^!ZkIPR*e;&=+U)Qti<&c#!}N!!d4$5X&pK713$Q@{l;)zxt>o&rwU&-o7w;3?p7 zkrV0I7Eb{u>e^ErPXYga|B5)C0uE@LU&pz43b^8d8RB>fc+jm+;&=-9_k}gJ$m1zs z7Js~ZbKu-OxHkvR&4YV$;M_d?)0=}|rGWj04{hRieLz;f`oDk9xomojrR#GCWb9x3 z)3t9Y3U%Ald{5Ad3}t;Moyc@yedhOHUpK3@E#IAp!_U`C7`N@I9c5EBu3fovsFzbF z{k?!)&Dp-;yyf2>%&N6LpI4pztvYtEKcLLzJbnY8maLF+*8Ps{a=3a-SM#q-6Fse2 zF!h$yzIVS0`o9(MFZ`3RTs^_;bX;BMW1o8#xtGPdo6`r6J;mbbJq7%v@D+{m6!5pu zSA5(b0uJA-TwF5`o&qjgdAc~B0)Aicxj3EzZeO;tIb`v` zQ@~XxZWPB;z%_EmHzzb6cow<9cfG{%6mXmTcf|336>#lR=GeyrPXRCZvO?U`dJ4GY zf~e-q$OBISj|$aZ98Uo^T>OVPo&rub+1ngMdEhDFq=P1l<0;^*Lq3S(Dd4#`Ynan5 z4?K%pilv*y@f2{EN%73lm$Ys6%SR79Q$EsaEg_#FW z0Y4tTMjTH8PdNEr98Uqi%v&R+nFmh+&&s(&98Upv+?_a;xgJje7u(lQ98UrJwz(^g zr+}}`E|%KNgQtMI_gWy1r+`OR@JwT_$5X)TM>Z74Q^0-q?-0jRz!}cQ(gK91fLAqX zD~_jtzdiX~98Uqy&6z5l`FQXY@P@<##qkvImWOx5@f7fkMJ3aldGHi)`qs+uwDgGj%L<0;@|pU;WoDd0Xoa%VL2;3?p>+op-*Dd6%?Ka1lj;1*x&W-{~O zDc~%(_lVV} zo&s*2aGf}w0*(;L%N$yJ;3?oxPdba^Dd5{rgT?U_aN)Q4%t^Qho&p{gZH73W0!};e zvpAjt9v!icIbQd`Q@}A#?-0jR!26pfG-vZ3cnbJOlpf-E3OIZ7%i?$nc*EYjUS=LV z1^g(>G;ury+^YXaaXbZ_wnGhb7r_Hh0WZz4RUA(Nv-soPn*-Ra!EY`XZ!-R9$~{dR{3svv{m9koC;PQtIXTLL>bb%N4QMrP>CRCz z!e1;`I^o)~V+-y*8raL{`Id;gCssM%yXm=TpKgT-`TnzB-|a(wic-jbyP7jiG51UILmW>rhpFC3<%*}6)4e?` zj%QK4wIqWI6;CnmzdcqQPci$1dnb;kn7hxfsY1n5%wE1b#qkt#j)jR;sCbGwn{Qun zJjMLl`<6JKV*ZrUSA~kFnElf%c9yI46myn~38n<}>pTi{mNg5r3vn zX1@OL6!WDi8MXITb3N zMXp7!mEw2`*ymmp6)K(rzVW%eIGzH27;c+5o&vtxK7k4qPXQ-s(OVo(0iS<)QyfnL zSA6L#T08|@bLL_dEuI3te>AiT6;A=jSk^=wPXWK^a!?#k0bkjiQiY1AfJ@x#Esm#v zKfJgoj;Daj{FY6Hil=~s?+q8nQ^3Pc923V=z{huGQn}(O;C$7_isLEZeTUwP<0;@n zzO_`YcnbK^s6FC%3b@PER4P0L`fU-%Q^1}H z6J<7E*LVuJX`z1Nc-Do^PbZ1ve_Fs*`d!eTw~| z%loCeDvqa^`y|Y);>1(TbDQ@U$5YIQ=U)@YQ_RJ-6;_GjDdwt2=ZoVh=2KC^s5tQy z^Ne!M#PJmK%UOrT@f7oqfV3)3JjGo7>o9RV#oYYWBXK;%JaJuVl_;KK{?sQ(98WPv zdmC2A(RhlvYl~*$c#8St!$abDiaA$>G+N~G6f=uI-n}_+ZXVp51Lx+!y*Y4h9{%ag z!LL%l*W(ZD@?m?L+gOeo6S8GwRy_!I}cT=J|(ziL*G%` zBW9}^IC|#B+MgR{OdYLz&IMTutw@=`=W^&XtrCa(TLHV8)0KDin67^PPYc-n_kjOB z1?=hxW~bxoIv=xb3VEYw_snZo){HyNiK_P$?frpgG{#f3{;RW=GuPuO+MWf+isLET zPb;5`<0;x&KgyLi^WZ63p9#)?gdI=O9_;;6*W)SL4Wk1pn0fFlT1A34i{mL;pFRmI zn(OftZGr54#qkvF_zw5P@f2;tRb~9lJa~$BNWInKc#5_~tQeKd^>~VQP|ptHc#1Y& ziu2-liZ*n#T$Rl{c#5{0$24&~i&mx2pTzMLZ35rg{$?ILMSJhu7I8d9yC^||0CPQ_ zqAeZRTO3c(PD*xP98b|!7+SuHnFmkNeu}zG96#7@rlUvntZJ^u53&6F{)Xas3OMc3 zgW`A!c>c7s)yzD23OH%&k>Yq3xx*)(isLEZPP@xhH}l{r;6Be*i{mNav+ zmmFBNhM5OX0VnFURUA(N?+=}@rnw$Z0jG5I5yw-&rGsvX<0;_a;3BomJa`KDx9xMq z@f2{AQ9s1-6!7)K^=g}W@D%WZZ(GIj6mYIMG3%J?@f7gHpg?gv1>7g?A#pqf95r8p zx@I0c1spqkcX2!gTZQ*98Up{ zt$RcqPXVXQnX!SH2TuXVT{>DEPXWJr_(B{{0gsLx(9q0-r-1u~*(8oX%udJEbv|Zg40&Ub+upuw0W&UpC63-xz)=$g zYmBFWzvRhN&|HtFfYU6PB95nkQ*3!Bj;DY--}NtK=D}0IEAvkm$5X&(%A63#Q^13Y zWG!sw!BfCqeI|(GDd6z2K8WL4v;0h5_`DxgZPXVXwab6rx0jFD>OPf(V zi`=0e6UFfq@Rcy{#PJmHiIO$68O2k;BhKs*$5X&%W2Dul6HfuR%iTa6PXULHxn3Mk z0bi^YO`B0X1zhI#Z{m0g_(t$~aXbb5dQM(#M)53iTfR*f$5X(u>wOW&Q@|Ai>uEEJ zr+~A?T_TRBfX}xKr_Csy0)E}Fl{lUPPVo4IIGzIbxtUcbRd@=xTmK2-cnbLUwXekS z6!7p-m32~ur-0|yUMG&HfV&Tit`jNzRJ)nJy}Ok-o&p|v`hYl|0$u z1$@7&^XCWPDd0;z2kHb1PXTYA^FSO=0q^WoN+(r#3b^)~W#V`WxX!uAI;p}_z=uz? z700tK49+`H9RJe-&T!;t4)aYd?WMMQPXYIvo;auBcnY}ko3`S33b@sbed74)_KRcj z(}cOqJa`H?(~REYcnbL4i<{zj3b^;_=(){2cnWyg+5+Nu3b@zZnc{c~cxmOY;&=*p z>c+Zx%=~x?_+*M*;&=*p-_S&P&GmQ+IKNjg1)@>{(v8Y23jZDhTDL=&+_GwTO|Ia?JPG0Z)b!3Ji zFJs;s_bL6UhF<04Mp(Z;WxZy#KJ@A6J@)Uff_63M{s#w(m+3pz?_l^&>B=41_4vWf zM^R(8o}6>Y(e|~vF8naq)nmGvKV3@q-vc&lLX?pI{r~?YtY7%=0lRvF+3C2t&c}Qf zLf%;9x;=@fGMM*pi%Nq6K6}5rIGzH&TJ*9wo&uf{>?{d91?(s}O~rtxfcMRNCyu9p zBV;S4V!%_tvl2}e$5X&1(|;DnQ@|zK)m1UzDd5C0_lx6M#B_)e;Q;&=+UN~csR20R6vKhr32JOzAm{2OsR z1)TnxpUMDF0pGr`R2)wMZ%-0lWq_xEGq-HzED7r=;QIAWisLEZ`MI*I4Dc*+vxZC% z$5X(KzrGR2Q^0F7RM$OkJOx}aRmi`g7xIRufDevy{yWNe3fO;SL){C3xaGS9zUJe{Q^0$g z^bp5W!0k_57ROV-Bi82CaUz}qt`~EGeq*PXT8tQ$ve9o&sj^$GbNN&dq~+ zbKu-OxHkvR&BH&vIrvoyIAWjiMP5$5Fgwxh;_KrSzu}$g(#iCgyu|Aob;DckVw)6nOaljfyGC9S_~z-F>=S z|F>tro_9h2bIyR>J;8K3?yggZ5VR??$$e>`Kd#xN7Y5#rV{S3v%2nnnhhxCu<~Y60 zb#M$gmtQ^Qa18j@qTR~j81PB21o6x|a18j~_?F7y81SaPdzHg6;3mU;;+u8g81Sr9 zgO$TE;0JAAD2KDj)yY~ufmsKR0q^LrQ8^p~t~tp&p}8K80iVjyM>)KU?cAfCy{jCK z0msf%D3MtQjsb@|vp_i<175r%WMXqY90TsUyqR)12ApceG39U!xN9)qBxW5rn_Qn$ zLzTlZ;26B1IK{l<{P6Njsc%L@J=}#1Fo^ZN-DDs90RV?YKw9>23&nw z?9}FZI0l@qYZv8k40vp@%gW&x@YsRb(wKGN81UsCV_doAlkL~P_mro~;TUj+^o7!z zb>JBApe-|$!>8GGQil7g9F771@uQlrSqF{*uR60?IUEBnIwM{>b3Ggb{@AgPaySOu zH~f9&a16MA-lFNvI&ciQU&*D);TUkRRuM9o>){x1+?%bH!!h9f@y;rTW57T1XU%BV zfn&g(a!pbWXJ4qic$sqeZwN(K=w=-_27Ef&Y~^qacu7%b40Amk170w+o^m(_ zydc|N%cMK9h>GThhxCyQaWRs z>){yiw|RAy!!h6w!S^VKW5C0MCDvvR$AG=l_fZbVfZ6=vp60;SJb0P|SM%U$4qVN{ zU)>!1Z4CHh@fvj_edyQw_KZ)HW)8XhbyW>##7Lu?O?nkh^>2^UjF|wVE1bVoc|uWOxoIAqP~3B*ZacN zg(+^u$+4|x&s!h+S35Yx-F*h}|K;cWTLbpI3;G{qz;2#kIvsak2>xb=4f!k=e!4%HOahJp2lzBn4#$9x??0#~z@~)O+z|OD{WfE`0S`a)LOC1*u9vxNCi8s=$AFKfU8@|90k7B)J+rwUjsZ8^)LA(k10Guc zs&Y66+<07$EM^@z2Hdp$6ysOS+G2qLK^JF*cz%k&kfpe6@ zF88rz81Sze$CSe{V6VREa+-DE81Sk$qm{!k;L{o2DTiagEvi(> zW!8aXz*Wm_Rt{%hIKSh#a`qytt;%lYa18kM(8L-u!ZF}7=lUv#W573i zxyF)k3^-51LW#_S3&()tMVYT0jse#?986 zaJd(Kl*2LLrDg9ZhhxAO%jDPi5sm@B3N=SL90O+ahkKd>SM%U$4qVNHr#Wyn4}W!Y z@OLrbsdJof?^X@9@JToS6pzpMojWO07~l4z<6Vl-AzY!y?`Nziy(#9|uV>@W+Hxpt z<>}2Y+;TIofB%obry zvD_A6OtHcip-r*U7NJbB$`&C_vDy|POtHom!A-H&7G~`2TxSdOW#U|K3#TbY{R;`I z9dy#}o?tp_ch~uwMT0)d+VOkuN$oVo@3$zLZp+#He5GTm;pJ_9HFW~>j5y#GY~JNr zd*$$oHgA&slyW#tRm4$A&C~3F(^O6D)loT|rYd63Bg)}4RWlkT(-|n zs^s;bD2LNj-3}~ez91cNnyTf~S15pDeRkncA=KIb8r>PpebGdRjP1P&k@aCJ=0jH@t*RzFk zI89ZEBFB}(X{tJIkDz85PE%FBO-#HF-%Of}PRCNA^Bbyf~%;_~{-73FXyF8jabN@_lTI1`us6=o=h(^M_X^39c7 z-p9UfYz$LN6F4{%m)HTjmBX319FLMxlQuXLmusCzDTgz0>D~C4ayS#0tKE}n(gtVZ z()D6rU$v+va8oU3;!?|ZnQ}NyRrRsqHEDx0amf?1m2x-}mxX?3 zl*8F4T8xOT2^^e#VSLEu%HeD}w=RrmW}Yv7@M7$FvG%-Jn=c4nj6E;bo)>HL^}vfU zFXo;XYuAhU|6ecGe-{JpaHPrSVZF}OtMK_=i;^KqJ&1HRPn`5OmR=uNXilt~1;U3{d68%b>~{Ir+y(mrvI`#yz^eBpluNYHk#vZ&6vls zRPQm%tJCuqJ^H`zfBRmnb>D}iz1i9suIutD`>xgv>H3u;K?^w_kAA3@T-N%;7P8i- zwve?xvxThnxh-U^FKi)eeQ68RC40THg=w+9UfaU-?OtzeVeZ-Mtu17&?`$D!eQyg{ z>jzuNT0h!C*80g7vewVGkhOlXg{<|fEo80VY$0p?ZVOrK4_nAuf7(LU`pXu^T7x^r zxE?oSt-+kO2xt`pDjoDa;35le;I~b+zaJ zJ&%Cr5%4?$o=4#SwnsoCkf6V`)8GEhi(38fntNm(6Ae9=R=34$zIOHt)$rmr&-JZz zUUNOXgw3<`SfLzV(&j_HhR`4cUa!%N$|Z-yhv;bm-o_hw3evksiG>cj zs)-uhP!4CT8f9Acf@U2!W7VP?rz(dtR_%WDhjMs;eS9W{tyjpb181z7v*=#saK@_M zlamxS*TWgBPA}05 zzH&HY)p|wuD2FpvO|d^wF|!Vwv1;EGeU-x*tFBIdM>(9aYNuEJ#mzc!#;Uyv&ruF% ztm<3LtAx28&RDfpj0Vc#j8&&~-m4tWST)i4#3ju-aK@?wO7v9@XRJDJ(M{!W#;OlH z_>?m1z!|GZO0+c&+T>*0)53(fN{ZPtM^R*hb0fpR!w)h}LQ%9!inj8#`< zZKWK}SaoixbIRe2Rm0xRR@SToXRNxb+f?On#;UI_d{YkZX21TEw5wIltOIAP+NthN z<#5KTFUut^Z?1^3)g%k9=R1+xwuQ$IX;o^m*2)%cG?Ry5bc z8LNJt*-AN_v1;}Z7nQ>qtA1LZsghX-&RF&B_F>B52*TJ@QI>6TL=FNmr@u)Q0=B52=5$V#85w zyKKG_@Nxgyq?H@p>3r_ZxI#J8w;b3rY__idWx(dk!QGlW_I~Q{X>IkCx9_}|8|iA1 zq&H(ki|Nxk{IX1yE@%7WP1=L*?$h1+zdr*``T3v@rp#9COl1q%bZT43rqkF$Hl5ZM zvT0vi$fncT!q~Jky)BGQJ2TkA+?g|@EsRY&Guc8mo!J($=`6O8O=q=*Y}(HjvgvHL zkWFW|g={*9Eo9R0HPq#1#BruY{UR67>g7ZZ0+@Z$f?>-^24K_6@$8#cVs zpNx`zqHftdcK0o4K5Yk_&ExBo*~;N;9*Nt%R}N?MNVB4fc`O`oHjlb>HYta*dH5Cx zYaU1koXsQt`i9EkY#vP~98nHu^Y}R{oq2W~a5j&Vi^nU6vw8SN{;C|#=8>gmUGtPX z;A|eRUmjErXY)w@C9U~-alqL;9`+xj9M0wuJj(~=a5j&7F{_&|LI<49qe9p1%HeDt zv5O}*U%?JIo5zyo1C+zrJo1Nqq8!fV(Ku=e^KIdPvw7rivQjym&7;HMXy$v$0cZ2@ z+S^?@oXsO_@O#SPY#w)pB{JWr4mg`f((DbD!`VCv9^bDV&Y4{0WU0(|zXQ&hT)ki; zmBTrc`_}uNayVyl2}V>gFDVWu~YJ}227e3YI zuj(~Y4xeW87Of8`hflZpqjx^q%HT6>US-H&=@3DZ-w)yFO zo0Y@o*!FkY9iT!Sd-up4aj$ext@9z2U2JCLlTAw%WYclF* z!Y<9H^-1|X$(3BmK8%UF>UM@lHMc$A_h8*YclYUT{ofk!zx^Drm&bq|UVmQ$cJ~C+ z>A1Vj-+n98WQ@OmPh;fIcLH^b0lz(F$HZ9e$@A zjsfqCTwUX0I0pQ!_)g_;4A}WANlA0Ra140N!U4+R7;xi|kCekPVE={1G!}+qz=Osu zQ4Ys|&-4vn+T1T31NI4Qr5ug{&pLZbIh-cr_;TMeW*s;N-1W^UiT`ja13~%cee88e&HDK`lwTt z!!h7DPktze(_~C8Q>TJi2aW+>-M3pg90RWWE~&=Ca17WlYD?vC40vj zE1CO+W58LzjZ+TCfM5Rjs2q*~Z(CBmvRMa?0dKy#Sved7uDvyO6>~it1MV`si*h&y z>~-vtaySMY5G+$wvkn{s{@^`KIUED-yY9YnI0if}Z{BKV9XJMDf6P?na13}_|1Zkn z7;x`^>ebCUa11yg#SZ0g4EWZsL;>b{I0pQFQh()e47l6zN6O(CaPpfaYM6E481QDV zmCE55@VwrUYntof81VU>?UchY;I8*CD2HRfdv9i|W!8bSFJvxNN;&+u2ArtlzC7j+ z`=n^9+e%iqkHm|uvt)Q>o2O6ohjMron?J6xQ#rh<&Et-aqqAgqHJe|U(_J~dy3MDo zyr~=>VDtHR^81_56JEpSbwbZm4zFqRh!un7H`l{!**v79p>lX_n~#}!KsmgQ&94pj zDPY!t*R}cl0t1x8>)HHi@cYW)^=A3%=K_45V!W! zR}N88e&-1e-tH(;T>(2Tyb0Y92hzfvb7=tDA$r%e$bjb}cZf zUdnqZm&f0_>F$18D|#)f9k!5j)bf$Z z=4M%&qiV14saAeY-e=mFANqVy*u_{>%4L9?$h1+zcpZI{&2fA^9lN7gN`q4p)=ruwuo;?AzSDSxUenE zMA=!y7G_NCENTlg<8~Iag&CYXi`&B7nX`l~%qZVk(iY}?z*))`=5WDT+7{--!db=^ z=D5RI))wXr#aYf4=3vHI-WKLm$63J^=BUV7(H7?X$yv!3=1|L7*%szR%vr@2=9tY{ z)fQn)QOy?S;Llmz7UtB@8DI;Y0oSmFIp1{Fw1qhwb=I; zAwji+j>p{-Ob6udI)Afh&&@G$amW+NfOE_jcJ&EhL62UR!QQbc3 zdN^j>IaC#yB^)#E*?og@IA(nMTvVAQ95e1wvc7USW}G<2CgpIGYZQ%;h6E2eM^uAtp&X7GkFT^(IUFSmRAmNzt*)a{2!!hHR zh4(3kW5!o%C6QUeG2@m)dnkuv#&0HFP!7k8OD0M$vxH;DTLunR4#$j7=Dx2Sju}_> z^OJGHF=MZ56O_X-t(GuP`Q<$+lK z=0AFN+b;9OfUDGMp&X6@r}5vZ9F76|REsKugk!*;y0lad$AIT|JEa_s0l)8@$z_n1 zW55kYPf!lWfb%Z?s2q*~=et~`p!s{jG2r&?HYtZ=z_)|Nky*kq;Fb@%D2LazPtw-0 zmzBdY;FIODX{-##fY)rFs2q*~A5Za7IUEDNI;V0G^ZCOu;33~PD2HRf)dc)q40vmqHT7Ga*p_e9`p;7{mxz??>+s=aTE2?4 z*SpEu^5+A?-wS%$#n|9wBl(}1b1_q?(vD#DMj5sF zR|9sp=Aa9^N>?wut7o24SGRt?*t^x@RGA))E8nHjizt&vWa(Gwv%C9rxBhPp_}|We zJ@0}J^3T8T8L+!2crlx?n=f9yWjN@Ilg_JC6B4t<2K@+?U1#Nw9ID}TUZMB3QVyr{ z3bygIayXrr-||ex>>Y4E&(v{>Du@5}OhPMq{?GFWcpd@IBj9-iJdc3q5%4?$o=3p* z2zVZWzw{9ZpKI>o+&z42{0tRw(%HLNq9!bzvt9_Fv)Ps}88x;;ys#0%=X&3=XrVd7 z7p&g;vPRN17j872uq^HIV#|&nd0imFh*e{Dt(`J3OV@ulVE1c&dsH*>;uJMg9!M1a z{f{tD+s=D-_?>t5=f3kxEV|U}80B!zdL4N_Du;8{>vti*ylcS$=dAZeyB*5mob_fuoxr^1!2##2 zcVMeN%Hf>#?yhl1Ih?cJRK*L$FzdiM>%Crlp>jB5-qJH%KYt15tam|s*Uw+VIqNO4 z`H*?jh6B!7Z@Exu^fnSWXT4Fkk5vxmtT#g3ugc+^^=8bO*}PrE0q3kYNz3BO;hgmj zExkxNoU`8Psl&!G>%a%wiOx0emdfFbd1t*ntsKr-Z^0^=%o|%AaL#&b9T}$_&RK7o z_aBwRIqR)GEU$UDj04VD@3wcdl*2je-T2hYyw%15=d3rdT_fdi&Uz2GJ**tgS???F zH0J#~4mfAM*NP2Q4(F`*e2S;a;hgn;d0*VTDaZlmtoKKzCCcHP^>+Cqn0ZH$1I}4* zp*}U0!#V4{9%G|&IA^^LXGcn8)`94 z|B=e!ob_I+^;S8Yv);rXD~PryTy%ch! zuG?#kayVzePwzwxXRe2H2K@eKJLPcBfG>YKuN=-9aKGn%=KX38IA_2Q40a|XQe z!#Cw{&Va`}uc1vI-jv7LZvJpjbKq(oJk5ctdGIs`uIAydZVvt~XTVES45^zmZ{*!O zCYCI@H1UQH;~ICZ_PcWU1yX*YTP8#&VCii*n^pvKKxj&a~dAIZIC_hxg zG2myVYNj&R!!cm5GuxEIG2kvSy;Ga(;TUk=CS8=nG2jNT4=IOZz_(AtOJml7W5Cx= z^i~eXfWv#;Qx3;~A51Hn)~o|(ldGA*^?PvP7;u;3k$uhea11zm=Z?zZ81VB)uHS?E zC#R_zkUO1O2aW;XJvT%7NXs$c$J>7@hhxAyN7qSj)`4Tdxq9tY4#$9#HBFYmTo1>9 zgEbzY9L^?}D(^kza11!0YvGJ$9XJNuXUk&ca11zC^T?UZ^>7S0@Nj44a13}@xzozw z7;uzEsWO{&;23b`Y=f1yTbSsI*1#k@b zMxxHj;TZ6ffV0Zs81SnOSu{p~SGO;kZ3j+L4#$9p?E9)5jseg7UL&db{NWhz&*Iya z!!h8?U*lfa@IZt{jd5k8XQSIUECK^M`wy16T9lX%1Y?gQq!g zH4lGvbMSXD;4Jxk@_tzyJkkDpd7r1AJ@(%4vMb`oFB09F7^czm-pB3GZT?y!1WiDu-jn+xG;MS;8^n&Gi~7hhxU` z%O6w@$Bc{mr;=I1G2;sbhAD^tX~xr^D~GfBIeV3mSq8HlGmf=yxpFvWTqt`unI#-E zp7N@layVw3u*y~CaLo8s`*<=-IA)xCa7X2E%(!x&bIReE@rA)zWtMQv_;``Y%Hf!C zvIO6i!`b}u_p2r2gk#3lKkifx$BZ{5Pa?B~W5yTKH&YJBjQdVIsvM3P@1Ec*vxH;D zAL5Kw4#$iKdcRW+$BaK6tSqyHW5!cIuU8JojQ3oMF0+JV#+w#)P!7k8H~5}Y4#$l9 zH1d^M!ZG8fJq9X=W5!#;-c}CBjMog$A+v;I#?#JEP!7k8-)wuY9F7?W4yz>Ngk#3{ zDsEN|$Bfe_jVFVIW5#h-_f`(ajK5#Ls~nCQFS%LR-@N|9G2=#G7b}Nj#;x0i%WtlS zW5)FtxqhS;ju|iBbz0ZM*%vn39RAy%1OBl8mCUmKfbqJ;fCDlXl0m{T;NdYQDu-jh z3-diy4#$8?B`Yk0gk!)Xb1za3$AG;{g_SA7G2lYQS}KQQz(*UMR1U|0?~YF=gM?$i z@sf;E4#$9_uXwE-jsef_S55{A$AF_IU#%RD0S~wsQDbB{2AnLb>z6#kG2lWYPw09$ z2Aud;T8)?C7;u(~Bb382;2b$#D2HRfZ1QkV6X0qRJWYVBN$@lQt|s9>)dc)q3^>~0 z*I)aVFTJHzL!XOR-+GtY9WKnnSxv&c-q5I_^X}%n3EQSB+IM{K!05pdoK5m!>yC3@f3@lTzRtV}dy8y7 zwxiC{^qpr1YgK(mI(PT!ZvEdHu;=H1gADi|^4pNyJ;95uQMtQLZB|f6h3N+Cx}~(~ zu$H~5u3JoZeTTSZOb*9%$FA(E9FFO}Ds)9T9MgTgIeS^N4jj`>^JRi^IHr4J*aPKo zOm}maoaM|qa7=e(zZuHmnC{iXj`HSuIGfzN7LAm{G2NCik0^&@x{hk;DwuWPnC^g{ z&G@XLayX`&J?CNNa7;J&%k)*vI&e&PWT{EY z;h65M`|p&)G2NQiOI0)Lz%kvV&zC8OW4ejAN33qHhhw_8*0xm+XOl}h)b%eh!7<&5 zFZ}|vj^&tc(z=tC!!g~p!@s$%w;a=*yRT3Uvkn~7eVk^YayX_tCsCN1=6X1$`*mS+ z<#0^*%iNR7;h64)LcX=kI&e%kT$|y_;h64`($AH{G2N8$OVu{(z%gB)YRi8AQ}Tsa)m^%>Qu z=`O53S~(ok&FcGJIULh1vAb%0vkn~7z58mbayX{D<7@l|=6X1$J27rw<#0^5aHD(5 z;h65}$%Px5b>Nt8we^dY!!g|=Q^Phg*TdNt-h@i19RAxg;OlXw6*hmkc5r{)V!%6B zzfujyfG=;!U&LGw$AEM6nV=kw0avd3S~(m8?%Sn8QL_#l1I{vdgK{_qoOXZgV&-}{ z2Auk5SLJXF_;IPL%HbGrB1f*`W*vBKyNPyPG+j9y1AaN5@sDZ2AsoZ zn{qe?d}FffUtoe`z>C6mD{0n&W588fUQrImfXAH9QOaBo$ADj)o}wI%0sBw;q8yF^ zv-!h4&4H_V@H7Xm=E2h(xSEH*x;gl}7;ujlr<(12kiDDNuIS+g)SGv7*upn$KIdA} zrSOdR)7Srz??vDIQT@kV9$qVPji#TQhM4pv^Qor|kM!#~WkH`po%dd9;*1<@eYOZe zuS=W(djXX%`08d zhqGwPNU_X+n7~=g770vI+!paoQNkASOi|Jn-liyJi@2sJZHqXjC}WG*rYLI*b7#(S zwuotp^0tU!iVC)fZiKM3h-8Xtwuoqo>b5YCr8B@5 z;Z0G)7U4`$(-vV(QOg!#Oi|kwp-oZ87NJa0*A^j7QO_13Oi|w!!A;S?7G`5~HnfF# zI-QMd;WR~&e<^}$2VJqdC*54@`S3gfo=3p*2zVZW|N9<+KlgNh`*$yG4Q%(U*?COV zZFnKwma+NqpnuNTnMj;wE3(a zTb08x;|O&l`kQs&m~p>ib(O<0<7KVZD~Dso;~PcFZ`Ofh#v!w}RSw6D@0>ZO9F7@x zKH^uvtOLi4e=M4$9F7?$9R5i;95W7lUbUcE2aXw^s=P%x95cRpFLohwJsdM0m(TTY zN5e7WPd$RhhxT$m9caT0q7hjx8(?wAXhzQAo!SaK`2jVk}nIAimls~aeXGd4fb?T~UfW<35}N*zPM8Jnl^8>$?RAe>yeL^&L*7*=FZGxK`# zpZbV-yy}<3HM%~moi=pFyU(Aey0CFhtoo^j?%r1*R+0v}TSZ(vZfUm=^HxTBSN3_) zsU7b}?67ryi}_1~ud5Mh(6v@Gsx;_w;%&_T{x4y%;T9n%fm8_L~%x?A6K2K*2IIbe5BFtdJl*ZJG;DmJqzzNfXB#oo|b zw`^wHlV{ZC1ZOillxehbIGfqx!>^RX+06FlEUV24&Sn-cdAV{pn_1gbt}#2D&1`Or z^4hfEY-U9huU8IdGn>~amNq9io7v#7uFVY2X7>8%EnN?1GaDM#wVA=$%oe3}ZDw#b zvm*ywn;D$VENTbCT{)c1EZvct%HeEgk9!u;<^*Rm+kaz)ayXmW zyf$IAIl4repFKj4{iIGfq|-leoT z!P(3{U0I>$oHO9Le{5F{=L~pE!NfXKhjRuT@6Z6{aL#~# z&UvaF&KdA5|FSw$hjRveu-+Qw@bR|!Nc$_A&eY)(Y#wr8C*|;oHcz_zvU2z&o4-qy z&2XcGnL2!i&0lor zs2o1i=2`DvRSutJ^Zhmbb*2uVZFApw3zWm>*!6L-LY#|D~DscDVDq5(hbLSBkXj&r5ldv_S@%r zOE(i#wmwmx(%AVQx3;;`&_74(EPpNm~MOj4a(t|u7B_tGFLdJ zn`}@A<#0?lWaM+o;h1iYTA5_7a7_2W{4vVmm~PB1ua(0w-4Sui%V^=4Zq>=Fl*2LI zob@7UlZQ98j~kmm+|wMmng>sF;A$Q`&4H_V_^X?PzsnhLpRG~4Y@0c?MyPuA-<-T1 zsm#v(2mI4#YZZFkgn$`8x=pKB=v$bkizYw5uw!Slu9jF?eOlAHlc&TE-ndSm zqXTM&j`Sp~U)TRS12!)+?$-P^?^KaqDTicS(tN_Wdl>^9vr{);IO$`XJAFrY4&{H# zXN$Z0bhp08fdAnJ?CuGs({Xp5I+LI;NH*i~m!tRvm5vI5YqOeLHseu#ZI#2>j4v)a ztsKr~{QP@{Z066y*^CoE8m%18X6!fjwQ@L{agDvDvzv9`Y{u~qu2c@k)a$N~l*3#P z$JEO?S}TWR>XnNhRSw706K+bC)2su>)cZynq#TZ^U+Hm5IUG};bM~HcI0oD>y>BkF zJ{$us(qg1?I0n4J>$P$?27LByx!h(QI0n4x!aC(}47kpr7&=CRW56dPbyf~%le_r( zymB}O9Cf2#UUR>240!IZDazp(@W4$@HPLVkcvOOB%HbGrvWv@gotqxvg{W5DaH-d7ICfM+EtrU@K;mi;=({b-qTI0oFSStL!`;27}o zLG6^oG2jFPE+~g%z@5iu*Q5=O0k1zlNjV$?e*f!>aySNjD_pHoLEjb*I0hW$)lTJb z_J!p&&nbuh)_^C^Ns!Jg+V=KNUvpd9J}7lQc2f>7WAg$Nt}2I@wfU04IntXy4=-o) zcE!djhnKhct^^O1!z%cMfWD`~>hhyru4n)puu7_jl`CqhE4#(8PraG$}j;S9y zoGFW02ac)Ft~p*gypDZ*GG}|Q9FD19x>iw}JRDPJ^M`wy16T9lX%1Y?gQq!gH4lGv zbMSXD;M^w?eCgM0=gd*Ni}g!?_u(n8N4^)UwcQ`LTHytMl;0dUviP`5@y^{lzdTKn zwfDQ+eY3E0zLG0mcP(?KR?Ovn)5LBZywCg2YF>F4w0T)_x8~S~ZM-9O4UxL^fnr|! z0vF^OaAW(Ueg$)#@)}bl=YjVt2D-aXck6o$_^&cxul%1~ONR`4nwByF* z4pa{3^K7dkpxmGGOQb83T6r1Y?%& zuJgCwI-K41sZ&GmhP~6w^)6b@ZhuAIsT|JPZJU9KT<-7bh2LhpKoGoBq0*ibo~v)h5U_b7*Rb{ns3IKAr? z&e?5XuDZ(MZSCJHe9?W%;k?T_|M28`H!PfYS^H%erX0>2sr=u)R1WWG*FT@BlHLsq z?_~3rm9{8{ceZ&!X9B(J70wxRl*N6N!#QJ~8v3qsIA_cs7ZudIVd0!H=b1ELIh-@* z&2>WQU9WJ?n1=*3RSxHjdGy*N%HfQWmkv!)-FykcIb*(bYKU?;XUy#ryigA3jJe{u zN&#jaIA_emGHzE6=Zrc0h1eSDz&T@%ccHCvIA_cq-knws=Ztyr<;;3FEPSwiUap;* zs2t82^UIlEmBSe$uMJjH?}mkQ#vIUbhjKV)%t_`Ys%`$haL$-pUu~uw&KdK(g2$A@ zIb;4KXSzCO9XMyqk@k#M4(E*dz|MEd;hZslY*MAJSqIJ;bJ_75l*2h=j@33sJ##&r zGiG1^j>_SjG1u&QUOAjI=J@N=)i>+FIb+UyY>;v|XUtyp?ozj$ zz?nGq?YvJpoQY$THa?Bb^>FruQ#W!bhyQls7JA;Ih?aq?*yBb!#P{Mn>@Y_#^Ic;j^Erz zIh?cA5Hs#6hjX_2>Sz%ijKeuw4P4;*5f?aTt2v8C(9d7OIa~d30x zY<1X~%=(cPcmw+)Qa$cO zmMj|DBFc>Q6`xLAnqg6}WA%G(o|B+@&LcgKEcCv2eb1?NZU}{R>7;1uQ#g)U^HYx2? z69dOk-;Zgi9FC!e$aX|I97FB%J*}D*IEK2q*jVLo4Ar;$C*^Pq^-Q{&YGU9R>ii2k zmBTUAFj*66(g4R$S6t|;9L^?};lN$xa13?ly26??z%kUz8x|>tW2hg(h0&w|j-l3m z-dH&tL#-A2uyQzt+Nw?(O&Z`BYNwd4e=8e~q2B57RoBBY)OLxhYSIA5Pzz;UtsKrK z=e07TCJJy2^={8c#|LmeHvwk8d5 z4E1&PMatnA>f?E#HEDojs5z!JR}RNe+a@`w9FCzzO`1`Y1~`WL;rKY^a11r^_qWR7 z7;3a@6*OspW2l3cu2l}lP{ZVoqDccBLyg_8xpFv$ntSMe90Pt; zHoSf$0FD7)$=yOZ90Shy@tATr2Ap9>>LTWT;TUky3`3Q}G2oWrBhc~f#>x9je!<*Wi%^&V*4qVNHr#Wyn51!`0)ja&w&B5Qr zfERzQa{lz_%k6HhZ*}O8dR0<{Tkvr9(5y)gHG9{3?V$NN{O7%GKeF(i6K$T>ZJ2Jw z@B`g0KM39W{IYq44>fzXcW(2}(|4EqT+u7&b?;x#fZeS*rF6$W=O3tIhb`ioqNgq5n4*_0Vw0d}t?Vv+-_XN|iy1UNb zEE@Ddv$AnOCtp*?d6`7FG%LHSoly;^S?OLgb2@W9oMvTWrHRVnG%JHUeo+pmS;;ag zAiY@!PP5{(eXDXf&C11c-WkmGaGI5^7kVg%)2uvQcwIT1W~D)K*N=q3X;v0|oSM2Zy$hAYTn}fG@jh}(!OjbDQhoOfpgh_EHX~S!vtwx^g&^jI~Yk<}vHQnPj|K zGeGnPhb88DH`SXOdB4MIYsGCK)Ne98wNv zlHq8WO7aG0l2JckxNwB|n=E>Eo6O7_XP81js2I0nq70rzYwu1&|Y zskk;B&!*zqbUd4iYt!*;Dy~iE|7%nEyBP3tN0iqq3r-51=cVuA(QCWby^v-2<6?am z-ufCYUBwY~%QRjduS$cB(=sP`IKqb^T{5c{qFA5-TJ>ZV9)OZ z4pPp4$nOJo_XIQRcXyq?TAiRK$2K?V*C#iV1jp-2hs+Scfw5)!JWx3tV@uNXfpR#; z7CL-kGoEn3F}AFE7AS{fY~SLBG_wr{9AjI4t+8@A#+XvYD^oEGlusvM3v z-T!e_IUIB9)j7AB7dhaVQ`u58l*2Kn@ZJtHY;wRcr|8c5%Hfz(>s1w2%mK%oQbpUK9F95V8z05Y)Escksn57Sl*2Kn zeBF;Ihht88Vx={MItLtc3btvaayaHR)Ay}%IOeqcMFlhcbHFjDj_21Yhht9O4`L=T z*TdQ5s)nnp9F93Hs8Kn&$L0wiTviUBYxAAgb0s(Hz&Qh6^lYYb_O0_!bDxJ^svM4ahgug}n>oC`eH_{Z zZ>k*5*)y9z+|wMmng>sF;A$Q`&4H_V_^X?Pzl#CK@apV0f5YU_^)60*o!s|Jtc*Xp z^qaIX#^RByk>_Q^v>NRc8SP+@AjA*I{U41&7wTXwmo&; zvWp)2P5SpUV0UXCXwWoR@tlJq9h-JB{?81%zWFD+`n3Pxc1i1BxVHFwsO?wX-KV?t zJqG*_e;2g7Czwvh-F5zECk^_*MDK-rRNT~sLk`p}CYm@*u@WYSW1@k7EK&}~L~lDo zl{DAGG0_4Y8!LxnqS?;wQx0dd8Syh}DYFiov%@q`n<$5Kc3A53QRQ&X4(H}eSK6!t zZ*QA`)c0KPYKC{PdD4X+bv+yd&Um#(8M6)?1HS0kqa2O_Uz_ez)?5$AfbSF-tQ?L3 z_n7-cIUEDtR;Xk-vkn{s?sILK@;|x#{8t`{Sl(O@$AC{CYoi>F0XIr;RymwauI0D% z70fzt40uhRvC82XaG#MMl*2LL2!(1@)aPS42K;^Ae&uisIDX;8mCW^U3^-UoH|1~) zxck~G%HbIBx*54En|0tA@aQqKl*2LL-gSdjG1tS{yD2HRf z)%zB#Y1V;bz%|d!QVz#}6CeDk9F75>m|DA*SqF{*4{fwpIUED_$&6}<^>7UMOx_L)A91|TAxv&nj;h5--ybG1XG0}2;Ll!po3&%u#(>75K z$3*8JJggj!iFR0%QtwiRW1`*-1}leSqES;lQVz#NKTa-E)OnCZ!yDT?#Ipm+;Z1CQcAbwld3aNsv-!h4&4H_V@H7Xm=E2h(xSEH* zx;gl}7;xAj>qe)FoP6n(gIRMAJoRH$-^0VAN0ZIH!S;4ZHm);r|Xu@Z~c^dGF>=k{Pp#I<#5b6;AnCgEgUnx@@cSgIA(k$ z!ZYP?%(%7lNS~zArU_?LVaLhPchR4d`Y<^MS zm6YkiG2_u$*C>Z$#+Pfwl+nU5qF zakf)ymBTUP1)1W?XyKS~;r*SI!!hGS7f&gNW5#)!q?hT!G2;{!#wv#owvW&3q92sQ zG2<)=s>^iYY<}}wZ&eP*jLY2hmeIm7$N8893(UehO$j24a=ulDYz9F7_Hp8r5O95epoY%Uos z90QIKev)!H2D~8j2jy@Kc(YeU87&+GJ|AM8aySP3_*HZnEgSuV;TZ6uA35uq*JU^c9IezW zSec;~=cv|v~%HbGrh4%-Q z!!h7IUfu=GUj~i=7f9GiIUECy9R0F#I0k(1S56r%90Ok3Xohk)2E4b-Pvvk7c>K${ zGFmtWyz|9A^1if;7LEb0iLqKa90TrN zBC5<4jsdp~?4TTu0mrL*NjV$?_S%<2n>icz|@ciCp2=PU^X5L)gb%J_ak|ljy_8(Uo@` zc)!7Gee)i3-e>r6DN5@Ls~aA8ezLiDLZ86@X25RO?Dlls_Y&bN%{yDS>x^7S`)ujw zog$NW;lb(i4L=++*LHXJ8N~mWpR>n+|KVrA?w(*e9e3CHo6aQY1JnJHy@a2s;|zL| z)!bsb!80vW4##xU3<{ggTnEQ=2gUnCIULiSKKh7qIHsHJRr2g+9XO_&FlGzoa7?#? z*B<3?OgBt~WI4<_a7;Ia{~+aXHn}_ZpDBl9x(64R&1u$wW4hZOtyd1mbjwAEo6B4e z$8@JJ?WG)!>8>nwM>!nRO}o56ZnF*?)6G_LzH&IG8zN_jJmz{hrrRuKQ{`|>_kGHv z%HeEsll!L0Yu15dx|7QcQ4Ys+%Z+>H$}PuqJKrdi&#VK-bZ3p-s2q;zzW5y9-&_yJ zba&6_rW}sxE*^YNIULjNwlkBObU3EFX6Xdwa7;Jk*w4!0Y;r4`)>IP@$8^)3+@Tzf z>3%GbP|Z3V(;d(@P&pjaeLCfsayX_tIfSp8bvUNm_}3`qa7;J$;J3=*m~O~nmDR+< zG2Ne8H!Fu@x^0@oQnL=nbO#phtQ?N%_K$fI0k$%!x`mp z3^-PmESlKC7umJc%_ZR}RO38+~}D9F753+Eya7SqF{*JAIcb zhhxA!W`@aPu7_j5HRCl?4#$AIcQ~RPjsb6KpGuoK90SgiZHRI>2F&IU_cRBt=E2h( zxS9t~bKq(o{_5u7?_$8M`b}BvOcHhH(dyn)`v;U*5jgK;f{m-P-s+e2{iiioYM+f3 z?Pls_%YE)I$)EJv)71;gUF#F4^qX=^Mh=~xe1BE%U13XP^FLG98RFmmB`kMqy5sKd z)7_tc9PBq|z|NHR7uF^;uezyhk-!wGZ4uuTX>1YC6lrbYZ3yMcrb+881gKZ?1!5z`a9t zP!7j{8&5r_9F76c3YWQpSqF{*cW*LUIUEDNaPN|GI0jtyOj?~C!ZF~ncSk9QW56{s zy;BayfGgarTFHDqa5lNUi?=CNA zaF(kxmBTUMq=%g~&Gm2$IPLm|%HbIB#$kt*!`b9^ze!cgtOLh@r!^U-9F75(nfOvU z90T67AX{y-4jcoH6LpGmI0if+{CDMW3^?-S+I7r2a18iR?>)-l7;wq7$?BTx;TZ6l z+WnNnG2o>6?kR_3z!@SJtmg>&fMdXs%Fk5}$AHfa`K}y}0Y{rxxxQHkKE=w_(VVN4 z!!h7W2SYb7*TXU3vT+(IhhxCcL+)1&$ABMRPukF|1IK{pEgPg9jsZ8W`b;?-0}lVD zOe3=n90P9YvsO7A13uvwqp`UjjsbV6*+n@V1Kzv$igGvxygXR$CT1Nt2AnC?Eah+v zIBYU!Q*%9>ePP6y7|P+lHQ>N%lQdp<5i_@LG2pPro~edoz@yvd(Rcxl0cWW*MmZb< z&Y$VIaySNDIb0cy7vLE1iA$@M!!h8gC!=Y+0LOrP9_^qUjsZuxbY3|e1HN1#tHuj( z40!CWiOS&^aHQTJmBTUMQ{k#;ya305Gp|^$9F76k2o=4Id6M85@V+wbmBTUMfMaKs z!!h6pKAFp!b>JBAyKiHa!!h9H%ik)8W58_wa8GmKY92hzfvb7&GzYHc;jeBE{w@Z* zct(R)^GD5}w76&SLpRqSt(CghxFS`ut?TI5>G;Iym(tuy)xv*L{zJR_9VnV3!jGz% zk3_26y@FTq4IN{LPt~GG+Lq-^1*8T7#>*XEDjI$K)W zJb`X8;B$3vtA=C1-S_3w*%BNBu2^H1aySN@_ViEXa11zox|%v$f@8p^cP~>8$AI&$ zaOeyPjsX|g(m**J1J3*XpmI0{d~bFdHFIz_xv7mtDTiagtromj4#$9Z9jUHn4vqmQ zY_d~190TqYDzTb5I0l@jNuY8#1{^cwedTZr_|%6&YUbb=aF+Xvl*2LLe)q$uX@g_H zEAli~4rh~V9NqQXqTm?t-0Er6%)v3>adSs1hhxBbtGj-#8jb;%+FeV{92^5K-FT~V zI0js_do(p|a18j)(00n<81Uq_7nH*>;ML8tYr+G^fIs<9R}N>BySCtmaySP3G)^5& zdf*uFs}g&a!!h8R?ZauJ1IK`m+-#v7jsds4bV@lK1NN_;J z+CL%`&-T;zROQlf+FY#P^w5X%{x2%8yrbyG}cJ(B{Y{m!)FrEM~Ke zQ}IYZx)#27Jh;L{{_X;TUkEiA$8jG2n{tLi(BO;TZ7I{RNc6 zG2j=gCn<+xz;U;JRSw61cYUpu&8!c{fLl)8s~pZIm$jDbUDc;El85J-~t&JDTnt6X8v^0zdU5$ ztqsS3tLAy2>){yi)QLrMnLh_-lN-`yzH&GQ{BTJ~HNkKU`0m?g%HbGr$|Yx%!!h6+ zk#eXBhGW1#GPP3<$AI^5=&2lz0k`dcO*tF`&Q&|FnqxQy+^NbOa}39TYew0r9F76!dl_HNF&qQ#xU;8nI0k(F`*r1T4ES{BY?_?G zG2o0PM=OV8z{4*;Q4Ys|cU3Q_$r&62E<49HX@gIeAIsCT<>{U2?TJuFb^uy{j<`x4kH@dEJ zI0k&Y<67l#40!gekiO>6!!h8l+v+KYW5A(y?NbiNfbVQfp3bZT$AD8`8KNAH0T+Gw zR5`q+eNv0YDVg4^1IK`auUzKJEysZS`$foLu7}sL>kN(DQaKz09=YzgaySMYwqcr# zW*s;N+;h)Ri4Sd+&fdZQ}tE8z1CrO zhGcs0wNH1Z;^mlH%<&ZPkfF`R@f0wNKi;`Hux}omn*;mip^x?R&*Jag9Q-qzgMUi_ z&+Itk`>-wdW^TW@E6V5jw!_04-MJaRdzXthWDd4m8eb*bsHum`YJzz(3I`WPl)6t)I7Vv-keZY>s!R&M# zUFU69(ufZhx$)z}{mppYq$>JM0pIU$|J}lP3V7Q6)&b^vJO%us*9mbv1^n<;PTkYO zQ@|~&4j0E$z$sdu6US4)PitkBo)(@0e)Gq8aXgD$*Tt{I@f2{g=hbvi3r_)e^V%qmr-0AC zPF>gh^WrJsUIBx|@f2{654Xkf6!5N(p1OC1r+`npP7}vdz;oYx6US4)SDrKoGG7m# z0^YHCpE#ZZUXUttu(=*j0f&tpERLsuJ$618$5X(a-sP`v=D}0IkAo+O<0;?|H9m^t zDd2s7)M;Sm!BfD$UEVH^r+}CCOWV*~kEej2h4&W6Q^40o+z`i8!1wBwXk_NWQ^4~k z%@oH|z-vx?6vtD*TkrXYn0fFN@SE<-#PJkxlc-S|o9pou@Uy^%;&=+US*2a#cna7z zZQ3Sg9y|qn`Eh@7JOx~0&jWEh1>B>yPg64wo&t_EI9wc00T&n-Kh#{0r+{az>mZJ& zfb-_KD2}IqgTso3nR)OO@YE60#qkvI{sG^`@vIBAUngj0=E486fMa|ZsXV{7FrPkC zz%QrY))-F#ht4aY498QzC2kH8$5X%uUGIwHDc~9D%PG(C6!7reOT_UM@UvF2^@0~X z1)O|wD{(vp9O>tK&?2)b9`#LFtOJ9Gptx?Z|t|!X|pSk+~eigK% zIroh0Fd)$`zJ*uA|3vG{3We|KkPh=o`#V z$I*4(W*Zdo!6Fwk${#v@ALr6lpDEy}E}lA0$5X(qn@knQQ^2vmeGke@|i`=;EU*WZfgjQ^4t`HWtTIz!@*^ z6~|M+m*S_^J#st+JSS^saXbY)bk`|yJOv!)lik~V{dfxa!Sq4mcnY}Xn0w-Q3OFpZ ztnQKHDd70M7mDL4;Fry!>mE6t0`B|0g*cu9_RD=z98UpzmB?4deEoO|czc82#qkty zth?{U@f2{r47GKS98UokD8Ee{PXQ57rL1$hq)e40jKHLQyfnLcglBF z98Uqa+UwzN=D}0IV>3(@$5X)3$9@vWQ^3b>*490EJOy0&>}GL11^mc8xfXdm1_3Nb*BA(Xx_lv6{gLob*O6Rb`?AmUoUvvWks9f(IcMU z*2Xo@axN~BBmU=T&g^^oue~1uFRv;`jvReaoy2>JM(tc?t)s_u zG{3We|KkPh=o`#V$I*4(<|i`ZqpJC-jQHrCCrfJcdE%`jsmy0ziw9jDB#!s9c(?2K z#PR+XFT2t!jrsHV0E^ciI7S>_&EhA@-xtSMxA>T$-f7J|cs5~C4^I%svk6%Z?Yvvk43Ednb-(6IMH*R(dlJo=sSq{#(WIY{HT+b<1F`$Fm8m ze7LPRo&s*@zg-+p0rz~6I-{8fPXTvm-dh~s)B1b+Q0ulho&w%qr%Wa@51sPXRAFG)Wv!0e7hTRvb?OXKq|Y zn_xTzoPXjXaXbZF&^@L$yLbwCdxloxcnY}w>r>)*7P%fZ^Jx=|r+{A_8842ffY*O` zFOL7SfWH>kCKyivCu=%e{E+1-;2rfMX|s!`fM4EiB95nkYuz~{j;DYRjLe};FrESq z-9AhlPXQO2@mL&B0XJ${Nt8`yN$Ir3&(~dQD zLWZY+k1X0Oj;Da9ZgSHJ8J+^3<=tBxPXR~Yb4wgg0he$Pf{vlGBXd} z)8ZB0goxvdSv*hm`Qmsli4C$csg~Y`sTnSc~`{zTLC+oGvva!{H>ae zI38Rxbk)U9vpYPP;1T$v`02Gnik;|FZpe)Njvmv|{J$(<=WoLL@4OG#(Knc#j-%_m z%}N^a!7=ao=grHRvDcFr`rN`=6w{qKsxh8>z&R4;C~vOEa}PM%(qZEG*48{rQa%>P za}Rh`tMV1hJb3N_XB<6I9M3)AjoqGzUapSBv92Sl7qsVt>CK zo@3tT#k*B9^WZyK*N_WXE4*W)SRIvsLXG4tRl;CPM4iQ_5Yi;drk<0;@y8*5ZG^Wa(J=Eq$tj;DY> z1=xRmDV_o@Kd+0gnFmh+_o#hS98UpP+fm-nT#u)KpVyu*j;DZ2m;5GxIGzHYR5Mu(b3L8{-dD4`IGzH|=yF{gPXQmz+XI+?| z|EM_rmj&GCLsh*%puws)US<-GA499p6UTG>I5ERJaXiP5$#z!I3k2}BtS3SDHS@&r z6h_s_QS?Fpd>!lhdv(La@pUbJXUb7=d_9YAJ>~9gzHWSw#b^8&A&w8W_`#LW#PRhl ze!$;XFA%^tu=qo-mE!n@7SH`Lj$R0WZ)EWl*;Yl9N0Gx z|MceI-%`M%@?Cgx_5A6b-RIO<9DCA|lP`b&RC?LrZyyd^cRO+L?u1zhqxY{FbnZ>V z*d^Qci8|3cn1?=b>%udJAbz1Qw7Fia# z<7JYlbUhEP*WyS4SM%yDj;DZMWxXJdr+|a!7F6ltDd6r&Cy3)I;BuW_i{mL^&ko)y zT|5Px)MhVZJOz9?)+b$$r+_p1)l&iEDd1S|c8TLz>i(JJx!75-p1)R)(mpGmR_L-VNrHiM4SNIMW$5X(+t$!_!r+^y<`KomB z6mXMvi^cI2@E5meDqZ}a1-z%3IDV+L$?Ll1n0Ru_Q^0S>=27Y5S>z7B8!L{dfcuBM z5yw-&eaF{S>EbEi@f&A}<0;@Z>0MO1cnWxR^2XwL3i$2J1LAlJIMU2)DqTDU>{?-j zIGzIb>-yQv{3%-wl`ftFj`MbyIGzIjvgx@vo&sJtAV39-r-1!jH;CgY z;PwTRt90=ca6r)>;&=);q}mN}JOvz7&0D35r-0W7%@fB{z+<~aRng)p;L5GT#PO^P zDX;br$N#c`#xM|6!6{yl~ura3OM7X zaB(~ZT<%tUl`ftFj*+yZIGzH&TJeH7o&pXXRY>a|o&xS%bdorp0{(IFqd1-d?tZA2 z3K&lT`}f-4A_$AAAqV>|_1sE)4+ z5>EktT^TNpr+`0Y`Yev8fVcM#R6*h?;Jx9q#qkty(6o2rcnWy=f?6s|JOy0i@m6s> z1>9?NS`{RoMebVl0pfTHILX9E;&=+U<+`dWNPHKow9*Ew6~|M+KJ(v+<0;@FS$$NN zcnY}4&gJ5G3b?{bR~00l0=_u2tvH?n_LzM}98UpX_>^B|iD!{p_QzOpJOzB^*(-58 z1zfjy4V5LH0uFz-O&m`Fzpa;11&ODCi$(5hFHXx-z%7&iA&#ejvra0avcyxs19DFj z$5X(2Gk+JyQ@~BWHB?#RS>)z5+$)Z!fM1-;sDi{(z$qdR5yw-&A2Xa5$5X(aN99vl z;wfOiffK~>6!6s8AI0$$uxo7ly;wX2ynFq26(^nozJJF}1&ODCoA2l$j;DavkGv|5 zr+`PM_EbUQCtE-NiO!7^$5X)3A3qVtQ^5H;l~q~dDd4E@W{TtgEa0MF#PJmH%~W+% zmK!Wj0ndK8SsYIRw_Tn}Wr?SNYjo)=j;Da%x!e=SQ^0ewR!~{uDd5+6mWksj;MA4l z1e=(_Q^523v=zrwz&F>O5yw-&Ax8?n@I`jPvBVCXT0! zH+Ys*DdH*P;BNE8@sx3`wUNu1uOCktZ~NI;{7-LvJ@;4d7spe^6#^5fAn_EiXZJkf z_(s+|*UOF;$5X)THa{1~Q^29_Rdu|Kr-0KgSs{+6fNink=otA=Z#}NfwY4~&0`7GB zxSd;`0%q~YJ2waR&4Y7uVBb7AHwX63!#}+___q{rf~rxx@)udRD1U+j6;8GNz15&5 zp&cs?^*a`6V$1Lfvj;65Qsrre&bjWbD;YE>#_k67zem2&v~jJ2;j=xwj-@-EwLr?K z(K~FsJNWMf>}XC$-qB+^`t!dmVCSo#|2y}99esn@={UMhJClgb0E?VYz&;gH4!;n6 zrhrSjWm56rDd2cL28iP+;N$V`isSz*;C`i5LU;#5ZvEP%!Q^0G=+3(5WDd6x< z_Iq-83OIC%uSy6{0q=>tS{zRSC(DsY#e=7StE}oGj;DZ&kGdv~r+{bXET!VXQ@}g? z=ZoVhVAnTM8<3U4Aq+?P%rF-GZOmRG= z+b!N_aXh6vF|+-i8=lgATX3h2Pw|wl=WpqC9EzuO(=O^Sj;C}xzPclh54QffMozWA zl?6}fMlHTj$EWxPT%RYZjzjSj@Po%q#7A150xrF3pE#ZZUYS0ljzjSj@V4>&#qkvI z@_2W|@f7g2cBOSpiVwBE&b965iQ_5Y4)G&tk;hZOEdF@s=D@yraBdFln+NCSz`l9- zr#A=xmI8iQ$oKR9Jhk35np7xyU!Q4hf^xlB5p!np9jQ82xK-qH$9$gGgBRXEeD~0< z8jX*yj2hW5mHU>yu6{jl7fVnu)5#6r@`mSk9k-O!xk{R<_Gl;Y--WIKZARqh_CoTJBdH2*IPIC9oc_B*3#BbLm_*{netGstcYQky{z zYmmwe+^vCI#2{);YmhQx5G9v2NMSC^Z4Hu}K^|+6%nb5cgQVuqCl)Brt=*)*ybwAW{))5YL#R)*!AKcvyotX5eWJT+N`EHHd8nUe+L% z85Fk$G0mWaHHcvbC9Og9h(Q!@YY@$tQq~}9#2`{>YY@elGSQjyDAgGlDGa@N2W zF|cL%i-{P!u(NUW4Q8j~=sIsRX~YMM+_Z583z%`%i&yiT&lGU!EhEM86mVJpyW)5X z*t?ZyLG$PF6maSnFrx>n%?K zkH4F@n3)Gp0mqv%N*qrCKg|A898UqCTj=Ly=D}0Ioj%VH$5X()LtlvFDd4sR?7!<6 zPXR|7Jg2ysA5Q`Mo%$w@r+~lKt6Rcck7tp~9&>{@o&s*RCSFN%J)Q#g@@XZGr-19u zT_%pFfPZd}>TTx1Q@~S;hKS=S-~{eF#qkty#h{eBO@^m{7dG!Kj;DYhtvM-(mu2k!pIGzIT*sg*i5KjT;m^x1!PXVuP@m(Ch$ohU&99Bnhh-aOcUVVu;o^_$U z*EVrHi_W>JCz_kTAAP(mbllzip8-|6>N5pAeqr*QhT|#VRE-;p<0;_Z@~jreQ^5PG zM9yXA!BfD=lLU+7Dd1FXH;dyb;H5JX9isLC@-$p6(n0fFN zaD!eQ#PJkx?uGVWw}z*Hqr}OO*UW>bfFq6RDvqasgHN3j$5X&t<7Lff=D}0IF$(q; z$5X&TnJ$XsL#-c9l^t|2!!h@W1(^Tg%BF>DrxNJvg*y_lHZ$)bO6YX-n9>C?5h`QeT~3ZqeGA z(e@R~m~_2&m(xWTL^+i6v1ioSkppwCa^G?|^_0F@dzJ3nq(@|vLx25U&<^L!ns(*e zaz36lLfbD1?a+Es^oxTUw#=XU;r4acqkgQiX|AKkbTt1j3)uNS;Q!7&U`O9zb~=u( z^S1tFV)5(!ph7x>dM$Wp&O!bxT+q%&hYW!&#}%=CuiDdP?jyzVl<}g_*W!4}__rZ}naw8JY`((X4dRx9z11y zf9G&doy_4`7bY}IDUSc;Jz&30dCiHn%f+`>v=ggt z{mXMG#z=8IWqc?5Epa?$+#z3na}4f+r;Iy?4Hm~!#<7Rr7ROV@uV$4oXX`F_%6Qm{ zMdEnM*!@LJH*-CnG9Hwrr8u54{;mBnaXe)_Cv(nJW*$6cob=czaXe+5a@%upJY{^h zYgKa#!39qlukv0Yj;DP8zLgQ4E9yQvRu~Nst7Aae{ z?YzS?e#Z{(GvamMko|1NqP>dcEs)~fX0JROKdoEpd-PtwZ{wPei(9QnM)!$9HLuM* zm80k13)s<|JL?TR@+H#u;tf;odL1Rl{?Z`}=L~ofJELpqE2Yn6>hj~1qsMeKzq5e< z|7*Z_fa6Jf(ZN*&=Z~rMtaj%=l&=Jf%BiYb$X)i`kK& zGvatk_iUm<3CuirN_Sb!Y2tWFw`|9s;&@88LYa_+W*$7HyX47!aXh74*C$INb3LBY zP4Z-jIG)m-{NaH(p3-%@Un#Mf2T$q7d$?R2&tf*bWSk`CdOW53xqowUJf&Oz@G)^b zrQ1D0o}^|TJf*wm@Fa0OrJE?<4{c;4A|#i{mNa=chfi`Np%zg$|h_j;DZqzJ3P6!86t z+0vPxLp%jstJP3(JOw<{^O-oF0uD>$uM;~w1w3iqI&nM&yx~$(oxtHK;K0Y-#PJkx zhd9^8@f2|M%Ox_JuOCkVha{UTj;DZQmx!VhI6MX1y=bU7o^_$lqVD4OUlwq~jj5uV zFJi{b!%@s7|DcxHwW5qPr<0;*`HCl+{Dcz5A zkBH+b-Oy&)%&W;<@RaVv)ZtN=MVw-vJl&;5$h2nTh_vP}aTFmj3 zZqVr_;&@7z#UJn79N0Gx&dq^+^WfYZ*f$UV^yc8-QowN{C%E>cur1f60;%(_+57xX zl#wl4^v<^~L;h>?`dyyv_9ntVXqG#6+GUkL7HnB!@ar=EhdRf;=GUwC-tdO6yN;YU zK3gz-*mt<*b3(kJ-vw1GCAqRj>wThiI#4 z4a~OFR>>Ne2ebKD1G5pfRkj9ZcWtX;4a}C^R@EAq{l3lD8kmT%`B?)~AU1z%V6w&* zU=2(w*{WFs6JWOL*1*)8t%fx)tAVYiH84wwEzla6b;wrB8koh+R@)kw71LJ78kptQ zR@WMsHQZLu8kq0DEyx;}-GnXJ8kn8Et-dvgWCjhafz1rO|6(G>F6?+5eS_HnIl9i< zOd9dQ;+MEyv8-m?G}hHD<}-_5rTR0(@s#nkn%~9olyPs*Al)OxQ^v1PZ4<{+#;MA< zWjBw9r;N|d=_-z=jEfxIBaWwxn{7y%!_0%Hj9-rECXT0!bDg**j;D-+TbI(kKs<|I z%sdOl@s#nZ7O`@g$Hh~|H^#IT$NyQzInImYDdR#X3g^WZ7t zVkH-d<0)g85B7V6c*^*XnjP|)dGM5R%T6oA@s#nAYk!F2DdWUjis)V-o-#f*YMMBn zGQQaDyEvXQ{*<{<0rTbHS^S0#+bfQzjN5+CsC$8U%D8LxA>w$-_~-tU;&{r~H&5O| z=5g_q@vf}Di{mL{*H!Pu@sx4z@Y;pVJb22uYOQVJc*?lIVEa8nJY}3Pw0jXV51uj( zFLXs5PZ@XY=uy;MkEe{keHbr}r;O{zc_xmhjAJh-=V9i-Q^r4Y%@)T~#-nf_*kxQ;&=);+njot&DVpcfLErqzkmo&0lSuP)1r;1fERoB z5XV!%Eb@5gCcwT)aBc$Zn*`@3z`jZNKeY+?w-oT4l)HD1uD$>8r021JTje=x+P?H9 zk{#_@uI-XTO>#yJsF3s0!*M-RKMS@6K3_Pk+4hkx2e!MdOP{{UhbfO=Zp)b}Yn${b z1~!P=@&-ukNo=B>XfVBY$x0_Ls1Dq!CF>lX1$ZR4%KDq!CF zs{-b&zbatf`l|xwt-mT@-ukNo=B>XfVBY$x0_Ls1Dq!CFs{-b&zbatf`l|xwt-mT@ z-ukNo=B>XfVBY$x0_Ls1Dq!CF8`J!g^44D!FmL@;0rS>h6)uW6TFT7)RgWCDtPC=sIuB`ah-nBax4vLF*Gg_BEd=-CS{&i{mNX z6@Ib&&2@N6*W*reaXh7)y2cT4Jf+)mRh9rV51!Ip9NJMFPw6IUW&e#=cuMy~efux1 zz*D-mIjyUi`SC1r70cLv;}xFL-Sn+cb#pzQ(rr<4x;UQFP21F^hPfV3>AFO2Dvqag zLl+(r$5Xo9qh_yZ=D}0CeHIND$5XoZCfa}F6`s=l)YCW6%!8+N+jLnij%Sfuc`89I zb3ML~_46~Kg8jcjgQs*ijXI<2@sw`j8wG2ddGM6(vWYXq@s#eZFOln*>+zKCAF~>Y z<0;+E4cCa{Dcw4Q;?_0u;3?fBKJCQulx~MO=f&|Xa+mHEsb}WFQ@RCwri$Y!-QB0o ziQ_5Ven;a5nR)P(?xnn0#qpGG#+CKO@s#e4)_cV9ly0MU8H3IIcuM!t`@!OPO85KR z$KrTOx83=Q_02qZN;mVYCE|EWcX!(u4b1g;O83}`FmXJkyYKoQaXh8_^F_*rW*$7H zJ1eZCIG)mN_2z^)p3?QqlcSND2T$p4%P>qFPw8g<^hg{}>7G7VCB)2wpKbm8x1Y0G z98c*cEtvKgMPw5W*!>fsz2T$ovs5@I6PwDoUYHMn)$5Xlm zb~h2nvra_bdQcqCx-c~BRdGCv&e^}d57;ef{tD*7f)8FSZ$4ANd!`H)$5X)bdR-RB zQ@}ar<*aD_Je~s1v3ZC%o&v78^|3gf0xq?xN+mN7o&pX$wMraM0atkx-^W~!r+`N# zXeW-RfM?D>BaWwlSLY~D+028dfM0(ZFOH{xlTUdgj;DajmJO(4=D}0I_2#S=$5X%` z-^8nGuE$frN!zy-$5X(qE}jy{Q^3hD=h0%0r-1tnxBn;V@DwnMKb~W77Js~Rb70>* zI5!9O&4Y7uVBb9aui6}Jv)%_B`hWO+z+0lPj~N^^5_lViFHS(wYhdA z@{0G~H&a|$`K@``!kM}Zz4Lrm?d_e)yee_8!QIW}(xzRr<7uYL2{yQe^o{$tx(S%S z-UD_tXRaEP()Jqce=70i^u=BUwVqV}LDx5E+&H82I-_JK7>VFnMafhpkM9$5qP zVC^1T15>~qo>&7@z@49315?1ApIHM_z+InP15>~~UswZEz`b8u15?0#UReWE!2MoZ z15?2L-&g}vz=Pge15?05-&q4wz{B5L15?1mKUf1(z#~3d15>~wKUo7)z+*mJ15?1` zzF32(X7JS-m;xUE%^H}U*6-h~f%!3+_`@2gfN%cAL`WBQx{kiV?0g+v=WQm9_y{mx zN5sdE7r$jQpO4f^qtDeWUN+@Hjq%kj-tK$4?B;qro20XUbQH(4Nh+J}pg5jQQt_uL za%djQvq{Rmq=Pt~P13zc7sT;wl5PeTaX0he*(9AAH&q8A&GmRTNlE*+6vwkk`gHV|IG#;X%~Cn@ znR)PRl8QtdC64C=rG2?);&@I_CN8V2d$D*C13U!1d~ml*nxw6g2bWIYC(!dA~TmqxJXcy)J7Zb3L9-(z?eZ#qpe= zTy=dTj_+d4vvGD{VKWb&6BMtKTg36*tm{Lroe;-!f>LMlOL06WD2s>q7cukWIYG%4 zx=tL=2};olNs5~5@tmO4uisf5&k0J_*yqLZY?9g(DC}Y8!E=J*IdO_Oo)eVu?k=9@ zdORm6gZqVv<2gZDJASD+o)eV9QzI2K^Ze9%GejKE3Cio!kHqn8lESn26gTtWIYBv{EnFPW2}+_uuf_44pbWA&o^|4dpZ%A% z;8_=P4l7i`JT9I^CwsBU%?;NFPu|WacjuG4`GMfc+xg_~d~!EG4?KB0pWOeMC-<%Y zlHY`tv+tA4vwNS5c64NyN)y}Vj_QM*L%E0!1;Iwj+T3nv$@TA${fYoEUtu%kIgq>XcM@1|_`OH_zae15gW zD{_^LU$59R50BQxZe-lo>3If6kLhUsbdlp{H9y1pFu`j+oWBVx;&$wR{Hg1x(l9yiSc>9IG%zz9T?eUz6+j$ z>9nDtIG%zz6x>xDPr=lAcU&A#!Ce26M|)a41#`cs{jL|Df;n>KqprtOFdGNeGY1te zcnapx-reGO3TFL-%;uEC1y8{Y>OWi@Pr(e!^jsWI!8G>qHAg2dcnap}<5l8#3Z~zs z_~zWj1y8}$YT7{@Pr)P$y&#UKVA?G!Yz}i=@D$Ac!4t&s6wKs>AH?w#OwVI=Vw!pI z6wK3pd&Kb+%@EhWI?g8IhTil#kx!@_7B(vs-<0+T| z6C#@fFBd!o^VK^{98bZdUUXO-Pre}q zzgQeU-QvDeA}2D}<7ZfW+=viy{7j4YXt-A#Kg;6Yxw4ppRu}wii&u^|TpT~g;tNJS z7st=Fc-FoC=JeJDKhNT^Pp=cl&$syLcS)0)>+uULKA~near{DyC!cUt9RKG%;4LL| zc#U6dUEgBO9Pt8{XI*HLW`j8Xm-m2qGIl;$JD;r04=GQ^|F=F_|65*_YQ7iF59i;& z|E|A*N*)!4=E}OV>y%A}y4T|5OdB=Ht;JO#5aVJej_o`U%r(pwx)!TcC~T^vuroN88FrHiLvk`|dMj;COD zB<&7t>vTWy(Ics|+kEFdTUU1QyB&G6 zPdSw6a<)Nt4?Uaezr9<{^DDb;kFwZx)uA}yiQI4Xn^a=x)`zdhJRat~D$l_)5!>~@ z7OJ0} z44b9cw$>V$S3%p>Sp)MbXxn;gU|t1n+h7gMtDtQgt%0d1+a_yZUIlI2Yz>l|!4_*^ z9?Z7Y8knPP+cs-p&dY7vt${ggx9zY7<^)%Sq%LA! zKXqWle~;8W$X`DEE#7%Zc~N{ViAA@_`cNcCysAyT_0BOt~kDl#p~BArvk<|wYW=?CF1x{i+|`7OQnksvv|_= zt;F%oES@RVDRDfnQa)QXze*R+J^rJeCW_;^$G@ZKM{zv&_~YcSV=rUNbB}-cxW(dl z?(vu3u~8h~*7`apO@1qm|IOlM-K(mM@!aEIuxX7ro_qWUh9p%1$~IKG!PfBk{cb#EBYJ^oZ*TZrSi#~)|!G5htF=N^CeiFtI- z7*C8n37H^{?{7V>t>rgyJSSxC9!+%57;jG+7ncmfcZ=gWA#)v>TK9(WoREbk>LZTl zglyINyW)6G$o#5T)IDQ7CuG~!hKu7lAsg+LK=+36oRFnI+EE-&jO|(ehd7=SvcpZX z>fSJ(6SA7m?DvxKoRF28_fps6d48|^t-9_R<9R-J**Hxc&tkD5^2FwUK5g@)>wHpn zJ}H|Y3Z8VGPs+|GW%KjElkWemPs+{{GV@dL|NZ0qz8-yt_wu?ut%O(nFpm*FuPWAE z(!77EQF}AYeHguT-pz+MpNiA>Y@IDfx~~pgdHCyzk$u~^w(u-fEpc$B+o^u;TAA#7 z^wWEK{k?!4&FRQHdQ3-u{+9*pd=+#=a{fnN1?}h?%&XlTUFU86dq5#&E;+oaL6L@J z*Jlc8ebJ{H<0+&uV|{$h^>_+tMW$uqcnYc5)R=zedOU@+@=2&To2K!2Q%IlMwGziuNPX6y630_W?duc_F!SIkqy(v_h~p`w__*?d5IC?cnaz4^=IOE3Mu;Wsx{0!cnays*45&83dwU@!kXrK zJcV>MVMlR1g%oi6j5wY`y7J?_IG#dE*eOS#nIBIft<5%G98V$D8}V5jPazd^@vmj( z!Ba?XpO=W^DWuDpV$?R*<0+*1KU#?6DWvoFPKe_vr1#tM)iLwnDWoRpe;3D7NMWPj ziQ_4x**k0Lun12fO?fa!98V#I1w^W69v4p`?XA#M98Vz)JatGMPa!owk|W5>gXc8_ z3&#!>$MYJ3%{w28<9Q9i@@W-<%{+KsLy)?~VsSjLAt?VSs(y18p4SjePts5v&ua+W z0v3wnr&=Yw*YC49p4Si*DHqVdd_8zxL$JJ8xHz8I5M%rKhv6j+1?i7cwR#= zr{_^|{A}y`=p}PEGV|bh4Z+OqO(xis$%rOwGmOc#coIHH%q6k8633Pb+n9 zC64F#bX2#~;&_fvZMK3H%{+LHPZQplB#sZZzE1aOU&QeopB}#)T*=IXZ(vbQS z{GZ3CcQg2y>+u|)4hbJ9zSQy@pXPn_KpfBU>E42sDw}!mO{~Y=?7vJL-_+s>R>Z1e zuE%qHTH$zeaXiPT%O)NX$2YU)Ve!X1HwX63gL89W-#qlOeok2YotuMyW^?dwDfQun z-uVAa*t(0W+v*Jom#3)ww)m^W?=tk;^>AYI;GeP6OpFvGSE_yiyDRlMG0P>#`)gD8 zO{$PB^7z0X)4~!@eqHx?*Bu={-q|+r?*;5=&Z|oXy<4B9a`XN_ZxrnwFKLSbD+X4b zSZd;#s<9vYS7^7ppQFcgG{3We|KtBXU`O9zb~=u(^ENAH#0QI9ffz-#**Tf}av8%Z z;0(zpi{mNal5gLO<0;@pLu+a?gr|V_{9G@Nr+_onPpHiho&vu7s*yOJ0)BRUqd1-d z&UQJ4HbZy{_(tkp;&=+U*S_20cow;!Vdb=MUQ!11f6*JcRM zA~)}PZ*e>YJU`uSaXba=*`lmYO7Il0SGpDAcna8Ua3Y zz)kOF)kz7S0$x38xHz5yemdy6IGzH|85E$C5MPFR(V3UV|Iyqy$d^Pg}iD98Uo!-IqltB6teeXY){TJnO=v z%rnLDzbxQkUE6z^MPOTS3Vo)4Go?MCF`fc`TP&$wQ-r60-6n^L<0;_nFL#UMDd5W6 z)0Hrfi>H9+CF(DZr+{rQZ;0b5;N)LRmNfI=Dd0^*D=A~W*$5R ze5&*qaXbZ_(*K41ddpM5nZEjzGV|am;4aS=i{mNa^(CX0HrL}R;Dt3p#PJkx^4Gh> z@f7gwuc@?{<0;@Po4bqSDPR_VymND4-#j=s2lmZ_b8}$dJp9v}gMUi_&-{@o?$fq0 z`@Z`Y`04t(wJlPQ*nK8_-4wC5bvT#m>pK5r7b;}S(&~HJ{E=2x%2#CY`SMj_P7O@A z_Uyg;6)N5@eIsxGdG)Gn+}Z8#1?*_fc>C()&pc$njGYBWmp%L;xkt6YVeZolm7koa z+Pa^U&t(4Y=rJA5|IYS{--Kmz{@0NHGxvZUeS_KQIJ(Z;tgsOuEOK?~rH^XHy=&Qj z;}Zp(xpIGTJOw;+(QR=&1-!?rR5UXWo&x@3B&; zba6Zd+@;G8aXbZFerKZ?W*$5RT(j{(aXgD$KfP5d z^J9Dw$5X(m3kN4O^WZ7qM)MYk<0;^NuF(>i>+uw@d$i`_cnY}0m}BC23i#UC+=D{(vp+^n+wH#*@d;Ap8uP@KTqGZe|`l1>CpfGI2Zw z?9nt%Dsw%a0?yZU|Leq4Z1n;48R?QreT;nL-%XSbXTJQ;oY>D^O?40z}MwO*SGRX?oqsrh4k zdDb>~tJm=WYHLB0kuBBw5oWhZ#578AYGjd|YU}Ut>I*kC+=Wxtr_pY(8qNA0&>a zq&>qQh~p{gN72jYH1pso={YkOisLEiz<5z}nd|YCba0yxaXck`YyARoJSE+8?q_j4 zCB1b{P;N6no|0a9ahEurlAhK&Qyz0Yp3TRj%tOWTlyuwR=i+!u`qQv#dCfd{N_tPu zP2zY;I>W&f`ONirO4=`d4{nvyhnwPf7P{KUy3= z)GF5nrO$}tDd0*e3l%o=;Mp`R?lDOm&pqHfuRe<7Dd44P>J>5b;3?orU*?J9Dd0Ab z_KM>v;FNbW7d7+XDc~HfhKl1U;HRygisLEZH%WXw%shAsI8D!K;&=);Q}7pYJOv!( zKpjsr51s;E`FXQAo&w&UKT$DrJ)Q!dTdtKjo&rvA<$ySz0zT6yt!_KwDd27Edx+yH z;D%GLh~p{XZOuG&+YwIzU(7vA98Up1iyTR}7x5JEvw< z;;tLlisOSVzO+H2tmb-reT(M{X(NtrVDYcrj)>zMT6|*VY}w2__(m459eJ=go|B2P zUG9tHIhnZosBCsK51x~WHVqeu<2jjN@y9zi2lmZ_b8}$dJUBN8_RYgTy*c=|oG3&Y zT)FI(Lp8pZzq5PY@l*%DuJXEe`gz@VQ|jBI&p%V>`u!MT&Ae*&X?&xwSDBr*{K=B6 zYWwJHjJR(KDquSgC2*hxNq*0{k4D{&6#P(tk__Jf)jEUM$^H!Be_9b2k^qQ@TA592Un@x{*Ax>0S$-(k(u5 zkT{;wo#{7S98c+{nLAS)&tlelvWxD);3?hlXBvy+DczcF4~yd|-E3KM>fQ{V(#;Yu zP8?6^p6m2M98c*Uj9*XpX7H5m&ow*5@sw`oglSWo?+c#NUB9-kIG)l?KJT_Tp2cj$ zxUy-?Ja|g?{_l&#@s#eQ$I;W8>+zIsf}>5v@s#eCc!$ODly2c*_jG0+Jf+(!#_!^I zO1FRL7jZnLn|ySE^kyFX5Noqo{_-GkJOv!%)FW{`1w40Q6>aYEEOO}{hl}GW;I)9Qx@D%Wg zS+m6PtP4$i*NNkQS-|b8UeXIR3Y{IR&(*Adsdn7VsTX44Dcw3*hKl1U-C>umiQ_5V z3CDBmg&25B*Cp8iaXh6v(Eo-wp3)uA)c&91!c)5aJI+sF{^i6|y5BcN(+e^1ly3YF zVd8j7H*Uql;&@6o<&SK7AqJk(9kOt^IG)mNH1ml#p3;riz$dZ!I`Ne5b@!#>cuM!e z+!%Tx2AL+1~_7s1gw|=eN zr?)ShQ+#;od-Ysb_kJ>|_r;svIb`NYzwvq zW=n3XZw<_T-qyexm`JcSv<9X;Y>lje$sAjVH82lmYite7bFr<7H84-uwx-s=tOB-B zYhacTTbMO4>yoXRH86{vt+_QYE2XW4H89Jst)(?EYq_nJHHc#dt*wEp8MLtmvCW{Z zHHc*fzgdHrX3)+W#4v;Q)*!kWbg%}|%%Gz+Fef0kPSzlb8FaP=W>aD7VhzlXiLI+O zu$e)fznF-z3p*!A-(Yr9j;`}IlSX{7SYEo~tDVu~b}zNlVzGRbVU;+Z#j;NPc)BNw zXR*xov$Z&$lJ==|QXEf7Pb-@zkNG(W5w~5bb_%j#qpH%$h3ZW%{+KY+I!SW zaXcj*?@4Uk6U9@~gY)}}<0e15-ub>^K{F4Yl1@KupE#b9zW60mA#**Rk`9?ZSR7AD z4;}tc98XD4>f%$_%!6mKT;FYlIG&O|cPdU1b3LAtUbm*DIG&PjQ|p8{o|3*jCvQZ9?dGHkW zqZ6CN@hqan@}?+euE$f@38!}#$5Ytuo3D%GDeNnaGV9(ap2B{#Zm>9>!VX^cSR7Ab zCo52;xcPeU6m~$tRpNLGJJ+oQy7!5vuxrL^CyuAEUyVE^j;F9)g7cI#kBg_U6Hc+; zE5%dTp8Fr^dOU@_=29tdGY_7^jukdt98Y0;y?G~&r?7p>29z@M;3@2=`B#hMDeS{B z;+Hnp<0{Dfnl`-?+DeTnkXNlt}>^gmHWzF??)`_2Un~39C z7go+%D~@N;sS@)*b2E=VS}onAza-Dvf5>R1C$>89#BMjTJ+RvG?E98c+nt@PKi2A%6Va4kdb}#0)=!R)!YSXG*lM=XZ_qlxTy= zLH_1?JSCbT?{;xKB|3VcTY$M9Pl@ij+*KUUVpg-mMsYkPdagn2YGxihC7QQc8*w}( zx@*}PaXcmZ+P83ZGY_5;eH(Y0IGz%9_xUM~r$i66Yf{6^gQrAy#XTa9r$jricdx0( zwLB%7Cg~_~Jd4@v4KKv;l&ISRzd$n&o)T>~VXZix5=}TcaV>K_o)SIuvXeNT5{;4h zoH(8mo!+8QZ8Hy^5^c6+ia4GUUDMX3jvm+Yl<3`)Vd8j7^hC4$;`l*UiEeC^x~`cA z&my;VVNY>9g*tJ}O>sPhT7PKidS)Ixg*vkSB5^#0I%r??AagyQLhW#~nfRaH`Z~`= zKQ4}^P@COO9&F~pQ>b0uv=_%ys3}ie630`hC#!qaH}l{r)QXShh~p{LdQ&4eFxTTL z)E*Z@#PJlW|L(owcnbCKj0_FUJa`KA)ct1c60 zg*w{fk$5c2Q>cl@l?^fT;3?GbVe`fD6zaRM$c@eQcnY;`kuY&Qg_=9fF>ySFns0mV zCT1Qyg_`vHIB`6MdN|&DaXf`u$+LD-GY_6ZUA1JZIG%gJ*B_<|HP_=8SwC-Y;`9~A zFSfW}j633Z)`jp+AH?y$ya$}CK=%sf!HT_1sn3*dfq*j_<0;*V{uwKp>+zJXf8s9U zcuMzjsk7pEO82>6!AfQxJf(ZC)?{%!rMqwR7jZnLn|D_|A2Sc0()Eq9T^vv8wp^R4 zvbi2l>8`5QTO3d6)+&2L98c*c{8qe*nFmkl#@;_e98c-)js8s>PwB4dTCb{^2T$qV zd9zg<&oTApnr^=4dVHw$Ll6+yLmbaBb*1=M#qrIo>skEq&dq^+^WfYZ*f$T(&4GRM z@K0|JdL&7i%>4U&d)|oV=JWrlk5Nx%Oujy>-K>tIUE`OIm%HcIHf5?8yIbXQn9C8@ zLY0rzZ}ffrvDRZ7_DnXtbKJRoD`p)xv2M@eG4J%ubT7Qfk}L^aypv7vXq4&i1?*_f z{efZG!;g-P<{SF1+N)t*|7dh8)4HO^Lf$+|xU<>x&nMeEdQ3<2I}7+f{wiok-(Yq+ zj;`~z{;guMeAuz5?$xc^b6NNJSS*(nog$8BvAjR>lQ^EmvQw)ZMaU*A8|Y- z?N(`-IG&Pze>SG>)!`}WfU?cS@sxDINe9L8lyr}ri9F2LgQuj2ByT2;r=%07J0^~& zq#M`Et$TQQO8P_lf#P@;%c%JuiQ_5hMxCqb9v+^OzOs0&IG&PzSu>gL)!`}W;J4kx z@sxBe|EuD7O8UlgFWtk#Q_@3j%oN8{(nU|X=w2P3lHPwhL>x~^_sq6m9M598DrSa~ z=Ix`&779&Nfqo5k@IaJdJzGUjpd6mXw{8N~4vaOGTs z#PJkxiuDi0@f2{}jXq_~{CEm@T&-|%JOv!m*R`Cv9!~*ZY0yd>PXRx3JuZ%?fMe!& zFK_0-Q@}M^3=+pvz>oUh5XV!%lTsE{T;VC;IWxwI<0;@mFQ1F!Dd1Umswl4T6maCt zE5z{>@VEBy6;XH!IC>fT?{ddez$Xvef0sL+0*?IMQ*ni-fP2TDC61?nPv)^HqVN=O z#Wjt^@f2{2R|mxL6!5(G*%eWE3OHVtF5-CBg-uCEh~s}*!0zwn=mk4lTb9yizsTlW z9PvV=8$UJ1`&)cbaCyBj2VdRdCqE1j$5YZNAD1K-y>jgV_O8UmUsp5D_I&tak;&@8>&M-HGPf3ru zSX?jI!Bf&tPs|j@Q_}V0+F#&sqi--f9Y@!BYfJM#zY99`#gf`tS2}+~J9x_V$=NyLc*->(zl(O(c*=Fm z(FWpp%5_YMUE+AkwcGSm+F9c%*SaG^#PO7C#jcyg@hnbNd#BXS8c(^_+tW)NPq~&4 zxg(CJT<>HoubnlXa!p--xj3G39o|2_cGh^x^;?oo;&{q6#+FOsc*^x?au4mq@sw-p zEz`vDlxw}EU&Zk(P8;0oYsZbJT#x47Esm#LKMhE)oi(0vUH+o2IG%DHTJWqmo^q`= zsE~Hnc*=Epv6@y@sG zw?FU{aFX*kbUmH|p1QoWUh9Bokvn*CzBrx&zEUZwUh9CTfTMM4CXT0o4;5Z5j;Db4 z%u1lwI^ZebA(K0b<0;@g=`M-mDd36eJoQ=!JO#Y#=nQc@1zhR#S8+TA-0*Tez19Iw z0iRv6O&m`F2R%%w*E--S;1%CGisLEZLkUlc<0;?~%d+aV4tNUqOu>HQcnUbx5c_{s z2~Pp9>FTA|K;S9h*}gNx@f7f@{xdn%h|%Pr07>?XWnWa=rD&$LIyBK9aXjTZd7b_E4o|sy%>IAc zdkg5OvbEhhf#89l!QFxr+(~ePOK?bVcL~K67VP*p<|J=UO{e1GG#B$6VcO+*S_9T)QRr^)XK>9CNKbevWcD z=6cV?Mayn*%=JN^0OfGZwd?Jj%Hf#n>RZV*nZujgFODlN^id9PVdqT#aF(^1{Nc{# zz-k_x&4JZCIGY2jdHBE89Q-T>yli%zw)ykU2s0kI_$3;F*Nz+%J~bl*c)~8 z_&W{9hHJ5Y$L83>7l)qv;OaAP<>ZMo&aQ7=p>5T3x1$Y9G2nT<`5Rtd?Hc`i19mi~ zBkt%i9eusifdAopz>c0^Ivq#HY32=^l$qqdoSUW&z3`iSx@VGWd-9WNI0oD+WgT_m za18kQrp?O#eUgxVFz8#Q7(0sCw`uN;m6XStKS zfq7gw2Ar!>f8}rt*saW6Q*7s}xn z@UlV`8k=$87;x(BE0n`A;Gz-To0#L_81Tk3?UlnZ;0Y_vD2HRf#Y*IDYQ}+Mz=OI^ zR}RO38xHxZ9F75xTprlWj04AjFP@#E9F74Oiu+MH90Q(`vtDyE4jcm>mt}`?IP*e| zUr#HC|ImQXmh7Qrx}tBB>K+45{Nk8uI0k(ALvk(0!7^}g{wKF5X(oTTvpKMu2WN9&H4o0_z-k`;?&jcUG2o$Vn?;+pH$&nv zulA+Bvbe_Nicm(9KS zpm*e~8^1Qqlq%u+_4}Ij_}+jWjX8C@Pu4hbpD$doee0VDTQZOFFP|k~QJOJ1kJhgH zu-z~BV>^0GN8|s{fSrE_{Lk3~cJu_(={P#h$FxmhHzv8zkcIh7z3fm4-DAN04@Ags zaySMYoUy5LI0oz&v`;ylJ>ZVdQfoT_jsd4H*Izjt1Af*aL^&J-4yd18+YN9GxM}kN z%HbIBljZl6!!h8zRs6Kw0B4e0A8(~{I0k&IdK_&-z%k%F-McG?W5CJcTvHCmfCsKC zs_h0i2HY^tT;*^KxW(FV+E##Lz{lq{Ru0F2JC8e{9F74$80+O@o<}$aT>rsvhhxBg0cEw_0LOr@E?J=*jsXWXh^y@eI0n3Ue=p^54ETP#i^|~`@VnmL#m)B* z$AHUx9IG6T0oNY?N;w<@t~190ShQsgkZmfMdY9JFZm@$AHfyj3;S%vM;tmqIUED7xZ#O%I0k%VS4l|=90MNVJ6ky%1NLA4 zK{*@){{2aH$qXC=?s;gPaySM&aa~CEy499@$C$c05jsds%wTPqzjsdUO zG*>ws10MIgo1_Jf0f#@-Tsa&AzVPm_aySM&K22sx3mgMZpJS|YI0ih#>!ory^Fop- z*0l@p9~$tNs)MteANqK%8Fh~V$B%GXH5>!Jy(^QqIUbGyuaDeMIUED_EpkOU90N}2 zZ+$cy90T6m)cR;PI0l@1cKDp;ap4$nlk-iK!!h9Cl>3##G2knQ)8;bcz%k(G9fm50 zW5AD^K2Q$FfZwDjo!g89$AAlkE>sT3fFswBkjESk$AFI>Z=xKI0XOyAryPy}zh0Uq zuNeo90Y6zfNI4t>j@9L^a(D~dfSLT^&gQ^s9-PgA)jT+x1FL!XyPJcb#ejR9K6`V_ z+60ecULP9NK1P9{@=fPNpONHqv(?ShXZ4=BF8{%t!3_sjdm7ql$Aee9Dvcgqpl$G$ z-y(ajxR`oe@gYs(y&8Tv?&+fcX26cdbi^G!rlYU_p#lG6u7VDq`-8QG8$0afK3pEV zh!s}2=CzBMrpRX(9;V1|7copxz%HVj;-C0=z>c0^Ivq#H`GlPSb7PWgk!PhkuT%Le z=pF;EpUp#^6dVI~ZQ4#b90Q*C@tATr2HdGlmdfVw;23bykP*t^7;vCn$$wz&)qDRSw61Z|1L~J!UwQ+{w?|t-WWPW5CM`rq*6E90RW2V3=|^ z2E6b66XkFW_)u^q?J>hK;7e~;D~DsiCnm<$UNRg5o;;wlaySOux8r%`a16L}$9&pL zhGW1}L#8SJliSTu%;oQu!!h6;p|!Qg499?9Mrn^3jseFi za9cSX1KxEsZ*B9u!!h8GXQwEKW55+ReN+y|fP1g5SI3M4$ABjf-K8APB5!I zW572{B+*_n90M-WwU=@@2Hds!73FXYxJ0^w+GB=ez_*)BR}RO3E6seP9F75J-B2OW zoPRh5{HFL~pmClCBre`8`0`3hhxA6zHC(v$AFukOsu_RI0l^eQAg!)3^>QZ zbIRcu@NCb5jm`HD$AJ6%K3h2)170)LReQ;B3^=}DQ{`|BxJAdq%HbIB(nVRCn#YA> zz#bDOD2HRfFBiX44rg8{R4tnJlHorz;12^wYx(6ytsJ_?fTyOjuKt8$z>l-!(((%& z1AftYm~uD<{L8{S%HbIB@~tJc`~t^-M;uwA9F75dMRnIQ3mgN!?b}v490Tq&@|bcs z2AnNVRxPu@G2kv&#wv$nz@8_cD~Dsip<^m(`2~&vkE^*-IUEBX6B1p^GH?vISfRGc z;TZ6vO-Gf(G2nIyvuODRjsZtbJX$#%1AcVxiE=mw%;XPuHV0Po;A{@;=E2z_{O#7$E8EFck{gHQt96e*wL7dxTD8(^z}b9VCNoim=6C# z-V55%6HKS$=r|wKPKDi=)tE_Xt)F$ACXw`=}g_ z0sp?Vo{Sle0q?H2OF0|^z7r+2%ovUVUvD*7Ihe8Q7;w!G@3q{e9F74mc$Qjb499@8ts7(+v&}K!TDR^ghhxAix0jX~!!h83 z_m?P#W5CbaMw9`=G2j-Tt@ogYW58t^?b7jZ47kMcq=Dvj3XTDndfZz%90Q(z>Y8#m z23%!yQJFCu1D;ZEfpRzo+~`DP8893Ju6U=7aySN@-RG2YI0k&TMQ)if90R^xdzx}M z2HZ8tC*^Pq_}sO+GGjOf{I>B9ArTbku90Pt6F}}j3Sq!+#nN`(~-rb(^{<5VdF09S?@X?loDMuc7)ws#gEB^Oq zoO!&__o&M__su=)gj?0v<#ERswYOJkIALe;M#=718eFKyo9_SqdBEYq<`hR``UNE1 zysdrTSDhA~Pf)j%PiThpQ~kcCUbgN^pAzxAWr;ANNpdqYh22~u&5Rx2{2vpzMz)J& zrifw}Nlg*eE|Qocnq4F|g}YrOGDUQ|NN9=}c9Fmo9(G|K%r&N6c$y-XUBoj*Y`chS zia2%=#}skxBDN{w*+ncX*%Zm`B9bXm*hNHBq_m3&rbuNMZl*|W7slFL)7XW1I$hJ+g{vtd{-p@3 zUFeD(J?Z9H=goNroM*s!2ApT$zwZqEIj8&B|Mm)Iq6xcIy^*P~xgUAtL?Lru(awXD zjZ_Y=WanP%9w~=cw)2}SeT$f{hgY%lWCJHDhgY@pBKaOGhgY-nqqR#GHRHfB-(B<;Fz)7$8E~tm~l1l zWW~(!aLjl{Z|j|*;F$5fEH`vK95c>$+NZb~2aXws-#P>tQ812W_*6}OyzLQc;2_K z%Hf#tK$ifmK)_ix&vtgFayZN8`SK;#3Iv>GbH8JKl*3sze^cbTayS=`ou5`vD-dv& z&Ao1nP!4C={LPUY%Hb@VCp%kAD-dv&%@f64q#Vw&`TPx0wE_Xhj7K$UryS0*`Q7Pf zl*7B(zelaMFFOj9^S(qXHgmJZ6o15?Y!aLJ=(T_vuu9( zXliXwz*#n*`uh;&aF)$8u6?K+&a%0;o1eBP;F$5)6N{C@SvGGFH>$QF;4GW_*KMmD zKEVF|i^Mpm9L}=&{SJk+JppIgd_|)k%Hb@VH|u&@IhzZ#+ZHy*jJGAV-Zm1>vU%mPNwhryM-Vb6?VucvRm=$<*3!H_{7>CB zX7O9?*62jqr43>nE55Tt?q-884vsx0;+wD4yaG-pja2aDo<4(pvZSuHENQGmKAWOX z7~5}4z8N9q7saiUEZT$@{@efEfX&nEXv~Cdn_fCscYaP^uQsb<3qTUC5?i+J$WTm0d&$d;VNs+l6fUja|s5-`a(2`kh@wFkkcDE@aam?85vs zxPG(?+4Lv7kWF9t3kf^YWs3dc;OGfv5601PK4#Fc8%@T!*yU52x>$_IDa<`h#zps4 z%HcE_W3t9hWsZZ>WJD<6Svj00BX-v_%HcE_Z=>c)ZN`DqWK^3xSvj00qgT7P%HcE_ z4?Xf#_z$j<-i!mM$>=a}t#UX`M$_JLyv*@%nvBP1Iw^Snv8ZuE-Q!AWSl5cD4Q7vPLt7S;!NdmnvC0BKP!jRWV{D6wLUNgPLt7nYSEl#95_wJr#ADI!)Y=aop#G*j)&7^xR!3F98Qzbw(DW#aQ5b3 zuZo%5j00zHK5ybq%HizIua9?5Ih?)uHZ$|)G2_75o8Lcsx^g&s^SjD@RSsuwK1H8; zdCfTRN%rghf%@B&!zbJM(kw~xnd9Ni6H`|AR1Rle*imbgayXMtvorHrnsIbn9&fU& zJlx^@y030O*p8q_s^R{2UbB9|6y^)zHSGLY&aukjHSL^92JW1MtVzl_30aera}u&9 zDd!|)O;Z1DlhDty2YhVAx8em~m-oKC!PT?KhQg~3dcPcAdDZR(w^|NA+oasvMBx+n z3qPsJ+Rq0Dq&^pX**$N)%(+Ts8R<3fq4!ECViL7g54am1|MEkOBMHg$&r&E@Z&P>_P@y+%9CmCG5g91Fj|QLI%tQ zX)@sSe<9{MT1->n;D>U_|Kf2zX3(%3rd8vXYeiEJzdlFHhnQCQD~*)HF|A+H>`@NK zwBBz{S;>q8$Fz=R?5`ZoB)4n8b>(nOYru$1S_Xt;T5J4zDTiZPA^z8u!!fPW(|oI# z?+1=)%__V|IULjK<{DMYeQ-?cxo>;ra7=4h)3eIqnAX;C`L#R<$F#~X_*FTaNiMwG zSLJX_tLOX%)y?-0$F$~;->DpqX&vjE%HJFh$Fvso9i$wNY0bW1UF{6Vv_eZ4uVKc4 zV_M%vE>sT3w4z*%T+c@1PuxX_ftOK{=dBZq2w{wahqhOe^m&>r-Xmm{#1m zPjoyS)9N>%lCEloV_G$Ou2l}lv>sIQtYaP*j%kfsAEX?PX;sK@NjV(TdR(iJu3m;? zTK5MHRu0FsioCG?ZWxYfHGN#Rp80;@m{#FVE0n`At=YLe>YL-?m{zZc9hJi|EziuS zl*2KtHSxR~m~r5kR{s~{l*2Ktj0IjOhhtjjdiVvHao|(!*QG);<|~I!v-2*6zAA@L zxAT!lYXq8c;J?~=v()R9!)Mre$t1BGn&aU!?Y#Ggj>_S)?7aJ~bIRef?R@;={Ef^w z@HuwAuf$B{@VRz=euztBb3A;Wou8TBSUG&Yormr{pd7xy&U59=(8P=bUufqpokl8$ zFS7Hgja3-D8(~kYifNwXsSJHg3i`NL ze?72AIh78H=SXZ^nT)wezkM`zwdD3>$OcE#>g$_VHJr z_-K-cx3F_2f4H+bu$l*Fb6_ury)6dg6VV|9p__a(y$wo+?K`G2UnbWX#GhP27F_n^(Rko3^>xgT$=3R7;vOt ztPie$W5Dm9yw>q>4EROaDpqq~a|}4uwK>Y+7;vhPx60ud@bvSwG}*&3;J~EYl*2LL z-Fs4~34k-nH5)oeIUECye)y4cI0jtcX+^CBz%k&*!>qrDhGW2&GkePT;TW*rm>}hF z40u7dOUmIG@YEy)Wc+Xpcy830%HbGri)eo+hhxA~8w6@40L~7@ zhFS@LW5Az+_bG>Cz;4OY%lP3K@YZA_l*2LLtB0%=0yqYIc|$p^1i&%ijZ>B@hhxA6 zn@5xJ!!h8(3tK6NW5AKG?o$rOfPG&l)k**y1MW8>NI4t>P9N`-aySOux>**j1i&%i zL$yXJhhxC2vpi7_$AGK7tE80xI0n4r_&Viq47hdE_%ePt20S>pr*b$3{AlqtPAdd(44BCu?raXM=E2zj3SqykU)w)|s=8WT)(ErZN zA!}zuxm&k?%g`Bnj?6vNxrG1eMC-dJ9+@_8m$~VdwLRA;?$%xb{Z|H#xt8)nn)=&f zUi0sja9zpQc}`cF_q_o-8q*PX^q7vmenq+;>;eB{-V56GukQuz`d|58(2ky9Ivq#H z`IrtN?8YQF`b2se(kqvPGA<0b*ui1S;TUj_G!K=-G2mU>OUs<#81Nt7iBmQm&e$ACZX7^xhN0UuueMERfGZf)daq_U20W$1xTfa&fn&hKroC1U$ABAF^>1d5 zhhxBQJy$7*W5A_n#cpnnhhxC!r*u&cXI^OQHe5OUhX%Z+U1S;3hPBINRv7S&`}LK> zG2oZIS1E^MzfpRzooaV$Xz3(4A^aaDJ{3cG2jX>7b%Bhz=>Z+)MO6FfZdljQx3;~nf&3-=D=zm zoXvsNJUE*Jt9kgln}c5Q62&(!>bK^NY-#TQr)~wRWLhwxZroaFQarfbWb)Yg%g$`M zRenU7BC+q*Ztc-y^T2AJ7gHDBf4KA2(r-c&&d5<_Z`OdSzfEhD_;kqxJ!^z?pBpvv z=)sYCes92z#&pCTJ*K0t|MA}ed)b%VCJ#H+t{LninJF^bMN(5_vWq09$ZQvhO_9Ye z5}6{aT_iL`HoHh*itKh_o?cgPyYMtc4!ekFikx;4*A%(zB91AX2JGkwrqgkBoKM*4 zG&d%>^XIF|XuCvz^_>AHIkrwY90M-p7Dq-4$AE85?W7!z0f$7h-jo220at91O-2jH zfQQfRqa2O_`;9rH9F75dP0J&rg=4^Lu1-@9$AA;{`=%VuB&#TJe81VYrg=MsG40!V8Im+P}utz|6nJXLv zu3WE~aySN@F2Z5ua16M*w^yL~{fA?~ks=LK4#$8SpM9VljsXY9DlMahW5AnA&s7e` zfa}-)q#TX`uY6riMhnM)GxhmRIUEBna3V@$^Zmmy;Pbm%D2HRfgM$w#hhxCw0y4{J z;TUkbyyKL^G2pbvUn_@Wz-|AiA)|$3z}elmD2HRfXYVJG(ZVs{ajp6)hhxBB{B9|S zW59!2myprIG2oj`7Ac1_FT6UnMLGP32Ap?CP8sd>-WR_!;H3e>l*2LL1uZTshhxBx z7iN{w!ZF}j$A>G2W56q}g(`<*!2Loi%4p#j@PjOCmBTUMe9hv>XyF*}FY7uhhhxCA zo1aw<$AHho$RneLW59j>n5-O*0UzG>RyiC4_8nSXMhnM)+kRfB9F75}ZW~8N3&(&% z9(Pg>$AA-5IHMen0cWY0Q!{D2Fr2ooO6SrVGb_`z>u^sMGaOC$AE{dc&!|c0moQbUIq-ufbZ>Jt{jd5Z(15d1`Nl51Lt;B z4#$9pwm+vF&Lns7bRHQn90Pt*YN~QL27Iy0XXS7V_|~)%GGI6c{3QG$R81PjuZy7Kg1Fq>mPB|O{ew_1#aySM&Ib7vH^LhctfS3BL zQVz#}D+Wc8>B2GK7h@VJhhxB@X}2kdW58!u#ghTUG2lzCos`2d;JR;4Du-jh4MMWZ zfZ-T$rHPZ2!!h8ctv@J-W5Bh-)sq3kG2r9bcPfWt!1s!#k^#dp;OmVBD~DsiiBCOH z4#$AgdicqJ;TZ749KR`tW5BI4d&qR*%nK2wr&bRCp#hiloGbI)xWD9g20U@=Th(w3 zc;4R9GGI6c?Cvr{IUED-l&JI_}R$AG)Wbd~ACG2kdC0+qut;Jwp!Du-jh2i=p)fZ-VM zmp*-z!!h6vU#=;KW5E0C7m)$OG2le?W+{hbz#fUdDTiagO#X0Zb6_*c(oT{pV=R_U{sQ ztCD8t5wBhc-1;r;d3s{6{oP}QuBhSFq-}ys(~5Te*QX&ZFZR{bjG8>`Ki3s@k<1jo z*+o)Qth9?HrdVYciA}NEE)tnyja?)(#ag>aV2X8i5#JQ+?ZP~=>jt}sXNuqLBCaVm z+C`kOBHSjsh;7JbyNG4T7Q2XP$X2`XFvT{zFlWwnyIn*##SXi0H^ol7h-Qjib`jMS zyX_*1DfZY!WK-<5i%4NbxP5jJ(G>gbB7!;WfL*wm;-FoGH^m{lFi)rJVY_fO#j?K? zVYLh0wWBB99P7L}&w%p`IM0Cd4E*<<0W*c0o2!rRTwMIjL=$$aTE2UJbD!W;0^OIh z^E4qBRKv^LdG#!L3z*~K73{p)=!weV743Xzk=M%MmF&E2sjBK+;FazC@I{i5#1IMsK^E6Wq$FQ5{Kdc;%Vc&0&xu_Wjj$t<&KUz5)!)|}*sd6}mJ-TH@A2SXd z!=CxaD&=qtd(9mWUvoSh!`?fty>d8)U9t3Wao`xX&%PDP;TZOhBk2hQ?)*^?cW!!hijooAH8S$_XGB#%~r;4HttXgWdppWOa;eVzYS zIh^J9%SozRD?>JC`Tdu@Ym~!Te)m}5Ze1H;^IrD&-=?-!4rlqjVxFVQ;Vi!&d7N1* zKya4dvwDqD4#%+frGKm(-p_vA)hEhn1qjaa``frHmBU$nFEuKjR(jwpzo$&nTREKN z_u5C#D~GfE?!P{pwuRslTekRdpG<4=?QZ+o*ETfXy>M;s z<@2`}=u>9(!3ouJb=c4^>5wxe`=>ZLCU~2JE~peXj4O@IC@M+;Bfz$_kwox z1hdxf=(x1@Zxz4%wVr(cYsN4CoT+9FAnXU%=KS(M&!`^E1# zhx5yyBwIFD1K=#L9sV{-Ih?(>UK1ZHhqLzj~WID2mm#sn&dv-fuB{8r^~_TGMb6+eRc{^9Js zt=ZIBIh?(>M>S6edoC`KZv`udFR;ho7Whdye4(BD%&KSB*IeL>?7Y*;oyy^h z?L1^rN_TTSoZrU>;qxem|8Vcke4+F9Uv~!nG+_M%hW+#Z+g_Bf7PbEFt?T;#*}r@H zulvb!{)3%ozBC4@s>Ga8X!!RA>}br;>)GCB znznVrjWgk6ZK@G+GSc4P`V8-uZ`=K`k+zSTkhq_t$8E&$hpJKslUc*gtw%Z&;T&EW=i>9_nWv z7tS(lkC>ITj0tBMwpO9_%Hb@-u4$04ycq``WS>v(=f5b2vkcpM{7vO>mSKy(@YOOV zoMqUA88?Zq+oMqTwZY9xG?r@s^vi+*{wnD={CsSRZZ=XC)@IjrHO7a8_b26|AGH-{Gvpgzs&AxILVem^3e~54VT25|cY= zKVAI}XC)?Y1MBa|;jF}@Jm6(r{cdwsVq$F{sT|HqOq~+Xl*3tx$zHsQu6~EJ5>xES zI^}RyVlpI(qpRQHti+uC*hx8@m6(DDPb-JB5)-AIx2}GNvl6p?z)0nAR$?YhyQ>_| zO3a+BMQWShBRDHDb2nR8%ftU%iHZ6~$HQ5PDfq2w9Wzd-%~^?=cXq9EI4d#zW_i{% z$HQ5PIU3YcIh>W4^U<#>hqDqBt$?qteuuLX^Q`Ow<#1MF?zE0n-#jjym6#`etanm_ zvl7!}`w<-vXCnn%Xu=9)A)+mRwcN;6f-Peo*uVo+KB40b@@Y;4> zCdDb`@H%!rJZmoPWy0&)xqJS}%Hj3wd|36j%Hj3xe8xrp;^zB-H?Z>v)i)}K2iSR} zgz-z5{m*dy;Z^6FXnm@3nGxQ#(&} zx|;Sf;mzzk+1_=^;mz%w$sg`)4y@+E*&JBSgR?oXnuou;Irv$wf_`u=eacQ5N0v<= zcUg%SjdrE0yZu|}*FxvVc$O}IbYW=XCB84-ue>?saY4_QH~sv)z1B3Y@!`bzJc%Yx z>>0Fr(2hut3!YoMEn1K74cO6`M@m0GH8fyk`QV_Bff*97u2pZ~Z)4`3jeTKAz}53j zx1Vn7=rJ9Q@7REy?}Qb$A^h|H9k8P(m`=yhaXw~-4ZAT}zAY0qtEsnsT$;t)GgdV_a}2v$m6^)n7q1dDh8=#z z@5?ShzoP@d{6pmrn4*EqooJsWE)$7XP7ZJO9@%%HbGx!6g@z!!hh~b`HmYBj$Ujw%1R&J?sI0k(8>M7-L40z<=3_j-P2aW;nt2kUa90RVI;Hh#r27LZTWyuvB z0}gJqUO5~C-r6g^BnplJH}BC?IUECyKJuD!I0oE!UeV&_`-fw|5s%GP4#$A2Tz8X1 z!7*;hGV)R>*IKtJbRSGG2P_((_}E?z%kw5yoV}>W4c8vKUNOMbl0~om(h#^$8;}DSgst7=~hqc zp2-{!$8@W&YO5TM>2B?F%*t(!>3S#2lG%&{$8^^nAE_LU>1NFosvM5#E+65iNf?gl zGC9MYleaawJ11{za(7PN*5v+ApS*t-1CBW8^TyyM$%`FY7Pz=#-9hX8#t*9O)%w(@ zd;Y76Oy0M!a>Thl`6hH8H2L0Wmo-Qh9y*YX3t zH(*C&PHX8lX?x=+6?b$Pexv6vH;QE6vhP!k#PLr5(jZBT+0i;YcJ!Ez#{Z!KJ3kHS z|H4(!uK$^h$ggLeQip`O249+>9FDoxJZCLC!!g%R zsjOvZIOdvv!&)sn!!g%BUa_^u498r%4{Wa-j=B038mt_Sp+;_YTR9veQmkl*2L9&EKMzHRlD6p{^;?Sved--PG}-ayW)s#jCLPnBf@exKp#0!!gu(Ib6$` z$Ax34i_SDs4rh`pyKk>@IEMPix^&uOhGVE{HVjh^$55liu>Rx*j-eKOURrz4a18ZE z`en-D7;5p3(Y416$50=8byW@@Xq#fbaaWbYG1O%_^H(zG0gjg}My+LMN3sC%9b zQVz#ZbJn@99FC#7MlD;-oCi3D>Na7yayW)sXIu>JJ;O276$LsfhhwONTb@)7pJYET z4a#KK9y5Hho!9XkryM@T&S&^PR}K%h^K56zXzv-00Y?j+s~nC2KkffnIUEDN?C!6< zXE+9&CBXWC4mbu}rD{y=F~c$7SsAQP_kv@<5#OBA@o)@y`LVpU&H0C8z{MK>svM31 z2aW%x9F74WpKE=(7ks{bewOUBJ}()L0mr$TPJ7I74ES=Q;mY9{@cAN7l*2LL$9XGi z?-|a#kY-^cp_;V@i?{VRn@!`BuU*c6xSB9SRB+eJcCT(OG;rnqVs=D}RA*@dSmuG>XCQ{1qNxTd&i7jaB+%PwM@ z;W-Ob6=d zxJ+iyup1^CWwrTM|8>JeQ}=JA5RQrNXuVuHoMqTq{x0VGcY$N5;R6~ghhwNN^A0G7 zW2nK!GMSSSc6p-3nS?S%9IG6Tp$;weMmZcqE!xUzm*5!cmk2vF?ZYwD=L=Jqr^yA5 zp>}c`pd5~&o?mcBIUGavoLoXD6po?B9I#k997F9qAd30Hae-r~l`geX4rda2T;-5* zIEFfKN=Eb3=mN)3quv{>9FC!OnekTnpWJSShK8$ee#Bki80xXIo0Y>c)XWWHo8J!? zIEI?=Xb0tR40T71Gs@u@>N&qW=9eyP@89AW>Ynz4ST1l3wcasnI)P)TcYlp-UhQ1qyk4YF+fg~3*9Z3%Bb38=@yQrl8}MDnw8DnDc^+hu^TKK zaQfo!S-%xI)p2pMg0n7_Ptx;y19mi~Bkt%i9ew=|4cPfkSYh4A|LHqn{fG8|9X-LU z^*cJw$Ntr2ulH^38QL?O)S#g5+3U?){;O&@d%gLZ)YBdqoW0&pAGa!pW7zT9CDvXS z9K&84(p@U?Sa8D?Ac%XDu-j(Wqj`_hhx~S@0Hdb7#zb+vv|33 zIELNwR!r@6!7=Qt5xOadW7w`UFDr-B%-t}Q*yl1g zR1U|m7bM!J9FAdkN}Nu6U2qKhWvije;TU$s^7oX(G3>SDOKYzSj$vQCzDzkB!=5=Q zb{+Gh24@fW`Kunv;TU$W0q2y%G3-&3vudvkj$ub%I#M|t!+tyJnQ}OWeQQ7!?Sa8D z>=(5+D2HR%vv+vbH{U-T!=8|%yK*>&?H_nWIUK|8K0HkWGY%ZXZu4lcayW+l=K4eB za148QqjCXe95{wuweCvga16VRTdY8HJRHMLlC6VsIEKA1{7L0-47<TN7hWd~-@=Ro z|KT3+`jB2_%t8HnrqF$L`=o4Wdr~#r-_DD5OI6k!53gb8_t$k(4zFqFF|VCc4zFeB zZZC8CnQ`FO;-!mAz=tWy;q3M1%Ku3@oW0(9UFw!I^XVt`&%;Un@>z!JqmvT6Jz3HM{Q4Vim|GSv{;m+p3Y95@;fz>=Xn**zP z_`92fpJk;dxMs3n-+M&exjyaZZXtVXxJ4RNG5?LVk+L0$aJgKJ{pDWNDOqjt!t7r8 zI+U85x5KNTd86C7_nQ6Nivvye+)n)M*vGC@*4^q9Eywo;>}bq9H=p$wIJMUK;rmk8 ziIi)4$c3)`s(Orj7^U{7{-ds>+cwP6V>%lDhX(xJRnX3N!V3Fi#-I1^fE_)-bUKcX z^D#4K*o{f<=987BOr7dc1>Ix7kIs3NHaQ#vuAZ;GaySMY_xN$;a11!8cvdw}a11!d z+>y%R81U*rYm~z=VE>L0)jYv5;5NNmDu-jhpLQKp4#$AqntQ8xf-}k0&N|s@wrq|8 z`}VM!EjR`ov?4%F6&wS8(0HG6I0jrRW_mSGa13}$2kSy3I0pP%Z0jmzI0pR1)%sun zI0oG3^-l`1Vo` z$AF9UI;R|t0hgMTRVyoS47fn%VankcaM6vol*2LLR#B{X&VpmWTc6C(N(>wW?!M=X zaySNjacrPgT;LdRzLwTi%5V&Ltc#acR^S-$y*XQSKL~Ofn&f+np@2j90QK~ zXq{GK;23asFKZ-r$-1+m@*(PJWy4 z@q-k*$^~6L9cjn%8(nkn8b18`)Ntn-70mU##KK$cYu;FNvQ4kd@n21U6n@j0R-uW~ z9Xauf>vvZ{I~sF;^l_nuPfocV%i~72qDPZHKGtdHj7Mj#l=@Q3b!YwC3q2h@rlawH z_-RPr8L;!6u);Qke_jK2^aRuCI6BV9%$Q*}Cb^gKUgkCRl33$(j{&bJS3RG};TUki zVZSSfW58cpSRY*p$AH7H?U~<<1IK{d55A%tjsfR=?Onhe566HbPamZmjsedY^-?(; z1Ae=rMnN+U90MM^f17eRlicnisS26n;TUj%ox_yFF<{?3&y~Y5;J_08h0Qo{47k&? z&C1~zu*a&TYA)dzaM_|k%HbGrft8n)!!h9e!-}f8gk!*OHqKWL$AI5!!h9Y`9hS#G2qWbv#7a*W5B(NOjZuZfW2mYQ4Ys|LrT_Ga|y?QM;2SF9F76s zxD`vyB^(2Ov!jc0I0k%k;|1k#40ze-f@(V9OmcBv&rlA>fM2xurW}p|$DH0!%_tlL z-gjV`aySM&HHW)aoZuMn%B<~`!!h758&4^RW55Ns|geG^+~CfCpZS&C`vEo za18iS-3!X$81S=7d9?Bb$ACvhouV9$0Z+O3Q8^p~uCu1TR;u6_a1+1X%HbGr@xE!a z@&w0#w~QXD9F76MU-MWw90Q)^Qb8+Ea16M#&no3`47f@_Y^^ZCG2kS%yC{bmxzORl8(%b37aa_RBF#IUED7RPL*CI0pQC z{f6FV95@CXG4lcCa13~dXQr?nhOj@@wmAk|sPh=*a18i**_X=U7;xIU)pMG0;23c6 z_ZyYNG2o@666G?-!!h7(hkGlBW58X;TvraqfcsqV$!*4gW55kE%~uY`fD3hrkjESk z$AIf}YoQ#D0sF2BQ4Ys|3wvkLBoD`cnf&3-=D=zmoXvsNJUE*Jt9kgln}eUlfXkgZ z8`pKik$F3w)R>X6WZp$*ekuP)7O&E6Pw#o!aD@Mg3onAcyv&vM^5j;Y;db<@w{P(C zw-IVo&9(D%f*eUk#Cx@5#j8<6O1t&`eh=8um{TYeF^n96;-8WAB zedqp?OMgptZ${s%uN*z5qwyW@0Xq%&f6svbL!Sri=n1CNade!!nMuQLbcn^aMz8|= zf9Vhl*HVM%0;fX^%DPHN!s!t6?v3qg0Gtjn=uKDUa5}_o-!59m+nf$DTf9Qy%s6m5 z#GAphl*8!|YaVt9Z;pr4Asz^|+6p)w;@Lan#^9(r8fb$Iezjg-7e~5bi#OHR8vUwdJl0UB3Q`a4_@3ts4V3x9jg+fo~UcL}=3^tM9e`Eps$(oYQl}^VGRtd4Ag6b3BGMzzNf)(%uQ2J>Z1d1}lfN2RyUmLo2sAd%*YOT6-;U zmU$OlSgpMlcvtyw*xPT$^{ix08Js=fj=8NrwSuz;T=wJ*9S>&@IG}woUF8dB4|wvi z`O4w!0gq`NL09v_*#l1Xx`}c)d%z8E9aIiy5BQH;8FZB|oIT(v1x6}|v&?%T@gwE% ze)f4tTe+OB@`bYp9DmMA<#6_Z3sv{j)x7Y5_Bctl{GuGr9`L%r%gW({?c>kX%c-k* z;p_o>51ODH&K_{925*(ahuPyC^sl9>eBtZ?x6f={{R?NAw`ISix|$cx9`MrFeU!u5 z1HPT|wsJUoz*BuQ>1tj$d%)S3k5Ue257>Y1bLDXMfS0DPs;hb7>;ZR5w?R3aJ>VE8 z;_E73ID5cHzjRX$XAihz?i0%4>;ZRo%ciS;;p_ot&pt*uoIT);bss8+vj=>$nf1B3 zaQ1)~-=AL3{C>gN10GcCjdD19z|qH7*44al_JI4eT%{b&3g5M?9u3U+a8~#hz3!wO z&I(`BjpvoaS>YR$2hIv#t*SGX!&%`AdzB0{$HQ6S>s-6BayTn|t!M044rhh0 z!@l$l!-jEzv%(kg#0ceZR``N;KT!^6Ug(q6wUHSI{=*f%nQ!}B7hEhzr+fB*Yc@Tv z8qOZ@hPGbC&7g4hfX}w?t^7}J{}QfRXziuI*#n-lGQTdYfU^f2cc*nB2An>H&9$@E-tKU%$547{?v5M=$ z3OIYfrLN3X4rdQ|Sp`>JSOK>dYh7G^-x;VJ&K_{;!n>5io7#^%VRi~#SOI4bc=Vip z%Hiw*SIvA=IlP5E4wFCJ*&JBSgR?oXng?fdU^NeacXRNw>;Xqxvp7+&youj-d{iv= z^nHnQr}`t;ps{(QrdpS%%GP3AhVC?ceWt0oMy_3%In8f5E+zjkKF^#6BRWT2nl?qR zs=G$JFZ1)r^=}63Xv{b>gPJvdSH5oD6ZKz4b{`%4+})v5^Q~CAqV1=WN$OS}Kikn` zIvU?;zz5=+xjK5-f35{TScTaS{inPWmZK+_PRG%4K4Jf6b7PWw*nfRyQ{U@dP4^hE zZ|`_nOb*9@S6=L*9F76E>UdT;90Q(oKWA1m4jcpaD>hy^90Pv&Ws!0?23-F23*~SO zxYMUB7;wpsxpjda90M-gaH?`R2K>39^?Ac^3^>Zx zy1GCQ&Lr1n`ex;D4EXVl#JWHajsc&|&{sJe1I~8izH&GQT(xjTU7!cYfFIvorW}p| zpZ*X&ulf0bW55mKH&zbEfFp!Ipd3ETZu0hi^3v7&a16N7(NW6bOmg)yJyQx~v@jLjx`pF({3Bu!&C+>mCDc)#He2I0oFSUedJYcsK@} zVQfd`@S5SxQDKuwvE9ny81T|eY0~|bW59!=4p$DxfPzO4F?KIyLm2gG_iy4HbHr?)=8{OjA`6L*61XMH~F};;w%G z%(^r0b=Ep3Q+t0@hjd8QuCED8OIJ?3E=;Xdd7JNOnI&=3r#&8RWBrXl9V# z4$QOQC}0Os%%GqhL^gv$b`Z%73fn>wpoATmuLeg+JMc7vQg+}lgLMC4LdG6J=K7E~S5NS=|I6#lC)&JCw$~poYo4zE zdYfW-moS|K@l!2to9CQ3ewyW(8)Y&dzXyK0z_v;}=@~Tb<(Ox%a>?vV3>zAaVR+%PSX+V7_oX@JlSu5f~_rUkdMaS{%>U$ISNG z%vYmF4(s`P$ab^1IG!)I^D|DhG}pV|+@AsWXTbd#aDN8ep8@x0!2KC;e+Jy20gqF! zK17%^FvY%s$EMz|{35LHifirie8^Jp)ygE>z4N~Jt{-lEuEr@34?ZzI->lGiexF|a zN!Q%DIz~#;u2QCqVS|_SX>ee8=;b;8TLHV8)0KDin67@kyMUdo|F11zS5Gj-C*Nu!Co_);IIuISIQPWt=#kld$o{{}9J>61KTou?A)yJSSo8@68g& za}xHU(R*<`Ct*F7S8izL!E+Kea@;y`JSSms>O^m3uE%o{)*?k`aXcqsvtC^m$8!>v zw|9ZYW*$5zVRe_x6~}WD_HdP>iMbxnN!akiO~mn>gpKk3O&rfjSl1@$o0@s>oP@pV zHd-9dNm%D3FU0Yz3mr#!HZ$|!e|ZuXu%lZ^bJ3iZiS)jT{i2E9;H1X*s+RX}o}`qy z9$(G!70Wt`he^=4ozy z9m^XX+$xT*Yk7q>@yeR(@%1dv7qh20zP{zr!d(@|H?aKhp?u}cJotu|_ZmB09N!4; z_#}>RY${h~t}DKEG1j3g&uzGs}bhx{2ePTYk#xvN*nl&ul&bdr^zhyX@sBxT&onse# zec*h_RS#=M4D>zLKJBwSBMK(O5k7W1zJ(*B*XY>NU%^a3;=H%#&SA{8As;8^R zbT$7whx;C|Su6gv1?=hxW~bxoIv?{{2zg_Xi+*i zy}2Gw0oT~OP8?4GKS>!qgSj400gvp|MjTH89}Bx%98UqSej6jBnFmh+kMQUsj;Da{ zlnfTfQ^47u7tCbl!L!JX-!NYsPXUj67bdg09!~*>N*E}Pr+|HToDs)Uzy<1gXEF2O zDd5?ACyC=J;3@Uqi{mNacLl3uHS^#p;NLTB7spe;b222%X0FFmz@G>87RR&5Wed0} zj;DZ+Oe>h(%!8+ZTRfg8j;DZw=7#e&*W)SRyiMAQ<0;_8ACHUUDc~%xQsyx8;3?qr zDF%t-Dd4x?9*E;9;HGa%=QQ)+DPaGjtHkjva$6Ed&1J5~Q^0p8wiCxwz|9Vx6US4) zN$w}iZRWvKz!xg^5yw-&%S+uB$5X)1&lJmJ=D}0I1qH1&&|HtFfUjN*6vtD*>(`tS$5X)H^5iIF=D}0I zEn80&$5X&xdwv$jQ@|hQ)+ub}!BfDI&+QS%Q^0d$BrjsF$5X%`at{{AQ^29;JrKvU zE_8|fMI8Uj0zUVmLo)MVpMQ^|_Z0A;=0`NfQ@}xm<0UuO<0;@C)mw|>Dd0h^e-p=3 zz|jY#Poa6Nr+`mS94(HgfFB-vDUPRr|9D?1rI`m$0sG}yFOH{xU*?XH%3P19fXiR( zD2}IqC+$5aj;Dav*YZwn=D}0Ii=#{s$5X(&`@Is!Q^0Rt`lT`R;3?poE!T?UDd4=v zqNO$0<0;^r$J&YGDc}#2&N#2Po&sj^$GbNN&dq~+bKu-OxHkvR&BNclIryg(aM(dZ zcNBWt_|%Z-%_5AB^77k_(2*A(-@4<}mw0g_-ulq2)!j$8HeTqnfA9XwPp&se>pf;e zgDdslE^l&Y!1>*?M@%TNeB0+3S6+AiYXQ5O^WeCx!v?({(DA)bs24XfmG}QW?44RM z+7zmCesHTZ-iITEa`l+5=6Bx%{+HhacJ&0a({Xj3k6AH8-dHTt*O*hxjH65{r1vbA z*Zz2~F`mV;W$0?Y=6XDf<+SAo9pqEv|p`m;&@8B;h9U~cuKlvvD_uh zJa|gF*qlk?cuG2T_2uGtO8V)rkK%Yry1}qoy3K*7q}v?cEsm$84>V3u%6vR{7Rw_^ zhl=AV=|-)disLEipc$2PI|ENi&nv%K98XDydl6T+Gw_sjSKl7ucuM-!ovY$_O1i_j z0=k`nr=&mapDT{1r1u^4C})1ZcuKlUqDJC)7R%rtd&TjT^tUU?bvpx3NheG`L>x~^ zKe+r%98XDCDo~|@`FQY@^xB4F#PLHznX5wXy|>vYj;ENv{2p7cb-)j|*H=H%T^vs_ z-Q^4ORc~vvd7oGyXv!Savo&p}R{)#xB0?u-@V0AMOo&x^TVum=L0)9U0t2lnC z{rq29P_KrW2TuW?AH7c;&$^KL#RYNvFALbwqlAtTJa@j-@k4-pup0;Fi{q%J}Wk zzT$YwIPmRFaXe+5ux}9^7vL%5?dwZ48g+W|fml_}4%uD$%-eE3{#wAU=S-Sm%_+zIHOU%%zZ}@26H{d>T!o?v!5uCCL{8?wk!u1C|Y(>=V7 zsr~exavdHvhVIGXDc3u-+Kb~U*QM{yh~p{OJI%7{o*bTXy|{n0IG%E?RPw4go^tJx zJ!?+0Ilxn{8wySo$FrCP4ty_;r(A!gsik{$c*-@*AG^izleu+by44#x+jOHT+_AfB#x(CC!fD4j%P8ue=(2l z$>Ay2+8Jhu<0;py5kHFKDc9LyYid)Br(C_O?-IvTuH~Yn(&iXXxu$JBN*qtQhI;=< z96!Wv+EzXDac+*Sr)clonkSB@XtRy<)aDpZ(Ry`hDvoE7TUF|qIG&=tvLJ&t$9Rgi zbnMaMc#77)=u2@tMeBDow>Hgqind9=sp5Ew))DQaIG&=d8L5Vk`TD_Av`5456vtDv zzEcuwbBw2GzvSpGj;Cm=Ke{H4r)UGV<<&_To}w-Cc$zq#qAeZ$jX0j7?V75bule}# z6z%?Pi^cIX?ejjQ=nrxHEX&V~ucZ?*JO$izz;b1srN_Xk=VtT<2p3)s2 zYnnKo(%o_UwK$&At#CUaqj_9BrJMiK7I8eKd;MK}yE?*sOC0}a>E^yBj;D05 z{8m6O*a@_r(oI@njyRsu9gyO?IG)mNx~Gm_u!E;`3oY6#j;C}-e~zIS?BFTgceOi+ z<0;*nN6(7mDcvOTvS%}&H$0^qtNb`|Jf(Z#^b2u(3;TzP#UJn995^=*?#+R7^Wfeb zI5!V}_vYZAQox}mzMs;1me1>m`!f<87PjYfMXVU zERLsu2llLx$jpPMfFotuAdaViKV6QM*j$gNfRp6yDvqasBV4>Jj;DYF59Cc^=D}0I zDc8&r$5X(w)_fPoQ^4m!*H3EZ!L!JfKf6mDPXSN$N}9}EkEehgjR%V3Dd6expNQir z;Aq_|CpYupDd5UaSBv8*;B5E8rZCszDc}cRn~LKp;4K%AisLEZQ>QYfH1pso;AxA; ziQ`%1@`rydj;DZ^^!86>=D}0I=T>YM$5X(oum2FoQ@}flG)Qgc!BfB&a~~APQ^13E zrcPt7$5X%;cMKQDQ@{@!KXqPjJq0{{LfN!t9y|rStKAB5JOy0YCt^BtJ)Qy%*Qcd8 zo&uh{>7Y2C0vh03Ie!c)LOA4ZDfDd22JpNQir zV9&`F6<2r)_-o(>aXbZ_G)x>t6rKWZpR0#Bo&p}{dtDq)0q1O5L=lCjfX`l7AdaVi z*To90h{99Ai}N=Z$5X(eavc-Lvn~u;ctafj%K|?CBOsc2vLkQ&6xF<^fCqG5CXT0o zQ`G(8{CYeEyt=}2aXbb5wP~an=6XB@e5P|-aXbb5a_#TpcnbLKWAB(| z9y|rSI?E(+JOy0r?ptv@1zdDQRWCCSo&uiLXR|n-0&ZL&b}Vx}o&xR|wzD{%0^Z-_ zy!f9!l=;;mfB*Uq@7U%&o&w&{Y@#@x0-k#8jX0hHK77JIj+qBf0pAJSAdaViS^V+t z&4F|C;NBcKHxKU3fphclcW(~pyN=N(}m3@4%RMj%`RwanMb6A+30Ryx5 zEO>O}maQx9`*{cSJpS%f+}{%)_4ixd`qYTrOXiL!eku4wT@(&^tS%?X{rTNliwK|wsf-z#p{+I+0WHux|;vj1$?D`b!Cp( z^E$5Df!Q!SuGxXvg*&d>f!W$SZrFk80>@1|FtOpdWe28I9JlSjB1V+WDV;H@1*GJ|(^5YY_Y+d%{~_+SU&&ETUQgfoLrb`aJKKHEVUGx%Z$q0Qi{ z9hk+=@y!lA&EUHoILsjUKTOEjgB_=N`MA}!u=$$tz_a-Xe`KyW zp3O&((LcoTY(DPit*1>Tp3TRWtGmVVY(7$5O`^>uzLni1UAfRt9M8!_xGcBC@tjPo zSX5Y>PCO?QUUBA%<2ji)-tMb7o|B2@{w4HH4bREM$x7YD@tjOd%YRKA&&kB_ZiTh! z#B(z7Hr^s}Je!Z47sG1PiRWbEcI?*Tcuppwc>gYr=VYS6>0H`$;yIZZ)MmOko|B1E z2S1DBIhnZIsJ1qpcupoN1?&>Xb28D%C#g1_cupoV6&)as=VYSJjJx7^HXmCr716h9 zJSP*!5-b$Qb24$HWf+}2;W?S;ove*Go|B2@LobNqIhlAGoJ}WBcupot+#Ml~=VYQp z-N)j1P9~yVDytJIJSP)tj;b5wZlZj=CeRU#*=VZcb(GqbyClkjOgxARvo|B38oJ>sW zbzB_J$wZr)8FV6r=VYROnvvpo)`{KAAB*Ez7w)I`OlH21;aPMtMO@UiAA$hk@D_GJ3CRY&iY+l=VNv@A#aq{kB7e6tbD$ESDPM6E7$GC;&@8yOuo=6Wjv*| z^H^hXJf#&}_@FqRMXubmWGZGnr8W9XJ8?XvRlVpTaXh6}-!H978c%6Goi$P%Piajk z{!$!IXl*j|c@sw7x?f1p;EOOW5mQ*q0DXmDm zgT(Qa){&**Rm^xwEA!MqaXh89W!fomJf(I1Wfo^)TTf}7JT+PzPib9=^->&9X+3G| zuVThiT3zDo5XVzm2YV-18RJ>x@+Rmjj;FK^XS*zpr?hcgF#7Jf*dxT}qWPp3>?(cc?g?(z+G@j5vO*J;@l`KbwjfPibxJ zK1m!;X%+kaUK~$pMg37j#f+!4axL2_j;FK|2RnbIyvmFV;`pVOhux7!#f%TKyw}DV;&|4DcFER=<9~S%cx!d)|!Fy_6$5y zFY)L^FUNX$1=OuPtgUb8u5rtIXDbptU+hk)`_-NpGuf*@)@~eBu*>d~q3(Qq@??;A znV+*4t?SnHU5aprQips3xCd+&30HIG?p&RVzTNOujJ1`ICs9^_YQ{|{>2WCg+sAUIco9C!)2WIc+sAC6a%t}j;3~CYRS>e4oreM zn%jZtI!6mTFzcD4r5%{X(9y~cbl*164$MO5Xl)1P>2$QQ1Kqc+_#cB1>A|kp)st?n zb-%ek1Mbg&`!nGF4E+E18TfNe_p!hC3bv@yd5kM$(6<-fdS8)ZYxzQByr1RMUX?Fw zuD@n|CCf)YTr7^SY^hh7buO5H$WUu z*>7!dM;uStuZU92*UW>b>?5yOB#x)-hfEJu++2^R>_=8?ERLt_gQFi1$5ZxEx+O1R z=D}0;hi>&3$5Zx|18$4sDf>AqeM*{n@RYq{!F+K%W#7xkqm;QGPuXvq)Ic0h*%ukR zUmVY|`OkDIN}GA`9Gkx#Hb5NDvH9$ncf|1=n;-4tQ^w4LZ)>0Ty2s~;<2g3pknfo| zo@4V2sf(61^WZr)&yjwyIG$tkW=+DCGuPueHt+W%P#jO$&zo{a9M7?Nk~2BWn|bgY zo0q&hLmbbs`Pz0r#PJ-Pmygh}f|&=;vH8dP2gLCln=kt#Wkqv6o@4W%V}r%<9GjP3 z@<<%dvANIeGJa+rJjdoeAFL3^Q}$b@g|B3;$M?6-*V(JB#PI_x4@z-b9M7?Ns57}L zn|bgYo6pTOM;yQ-uvA5K(b7ykf z8nFVKtz4C|aH?VbM{RhqBc!b;VDq%NnzO;sifhL1ss1!#--$=IdojX1ka+9B;`3>k7rQ{y*-CH4DrCT zs0>^>Ssc%z@^<1!aXgDk(6E|e%shA&m7rTY#PKXDo%Y5v$1@&y7M0hbdx_&&RLWJk zDUN4RX&<4aIrH(rv#9vAUoDPjQQ0*(f;lkqz_X}$jcX*1XHoI0xx=CI8JKi*!SE=gZ;JojR^B)cPypJ=ZSI^nB(`FQTd9{9RY96#A! zpZBV#IsWs&b1(MT%0}XN?#0%Ax?dd6z1XCw5}7ka4?Oo`dl&2?j^|$N$P1^%@!X3o zQ9qqIsPw>dFZSO0!Q%MY_T$gF2mEWh^TcX9ke%UgcVseAeOMV1#zGF=?M*zywdzKG+OFweC*=8)F|ztr*! zKlh5av_8o4jGvS1o;{xL&&QhO7RUecUM$~q%>$Y@_cvwdH*NPfW#>2T|5v{$yYJyS z|Gc~Z-~Ac*-~0@$_~KYvr*P8O*ZqEMm|1Mu+B9z-w0ZE%>*ef3>w9JS?3Hm(q;mEbe^Ddc?sBuJ(bP;28-IBlV_m$n-=9-u0bl1=?2mZByUCmjf`NMiCf)@@g zc{k(rgFk1)Znvi6s%GIwRts3MK2DxR0ij$yrmOkg1^ho0u;X7+z^J2e z<(=elS8+V$z3fr2IG*w@?2})|$auG5gec*?t0)Gy+A%6n?{x;jS2Q{Dw49~Q?` z-uF9Z(lIig^6s2qggBl}Ue>I4#qpH)vhzMVM#fX#-+C_+$5Y;IyM)zoF`n|SR=$-u zp7K7j`h+;1@*a>ZvyO}Ll=o>r=kYS0^1j&Pimt~~-UaXF*D*4l@;(_nM;uRi@2lsb z<6=DJUAS&zar~d<9qx!Yp7IWpJ9Sm_bsl6r<()Cc5OF-^UGnyQaXjU{qN#5+GY_8f z&OU37IG*y3u;GI^p7K6cxKed951#TavU|BWp7KuS8McPG9#474de}r9PkD#la7Y|a zc}MP+uBMp>PkBErHd-7{c}G6+QXEftM}Fg9%glqPy!(9F?Bv!{-mBuouWhc!Q{J7z z^%BQZ-h~R>5XV#A;rxB-n0fG&_ma{J#qq2Q`L}El$N#dtS9Qp$d(0*zNY0Z*O$>fW_&Hni#%Q> zj<0R`qqtFh%*TzdV|lZ>ZN>3*Eq@>3j5xlY<-aY@rhClz`j)SsF*z%s=qWGGRAK!%Qd$bY9H?{n3@G0l@);F{KRd8l4=J@88 zk61KD9N)rn7Js~ZbKu-OxHkvR&4YV$;M_d?-J64d$_dcv+CImRKONmPQHMw=d~1jL zd^63SQ1fD^YSH$>2+z&l*Jh_`5u7pH@r!Fm_|FdPI&^>3n5Wy^KU8m7@=b9j3>oh@ z{&-)dPXXq>6!(D5f4iD9%g8Nc@Or`Ni#N#pmgS1#A9*jZbzO!~EEqVP-ZCOg*d zzg`6$WsdWQ_9hLP)e+SWl9)j>J4kE>(d{6S8N{%Igk})a4$QqhhnF41H-lJq5YG%^ z+ksvM9mfvzD(JX&V9v4~@$A4HggfHffjLEYB(MW>H19}g2j;xrk;o3r%>qYaJ1}=3 z97*iJ+}3a;wSy>TkjxGun?Z6rFn4tvDeS=95^|)p19Lygk;)FtO)5ufJ1}>?9BJ&p z+%|KhwF7f6&NH1Im>YSH^mbqt3eOC7V4hA#MmsR~D;<&lV-Pa_^HtEUo^*4q`_26s zaDN8ep8@x0;QznRfLX%Lo5M%%JUlAcBJmqiN@ZDh%2SnSMaye%S}KnBv%GhWFe*rV zCCkf}tt*bNZ27pv>%{T?md~0VMrDZ)V4j{0#PO7IhX$L(%UVwvpG+E7za>0nd?QIM zaXe*w$z!uPo-%&aAdbosPZ`Ji)Lk4;88^CdRUA(l`@|`z;>1(N+Y`6`nHw*l3qHo-)ptG?9uEPZovuzj0Q^o~y#@Dek{?9Vb?L4N&b8Mc}aZT4>wVq@1 zosIMB*cs2U`NNMh#PJ-PmuUG}9M7?N(|y%+9F1>hzdon>EfvReY(Bi;S8+VY=C^$7 z={OqCv3b40`^E7bo8L^GM#s^3$~fWa5#o4`%_l{DDURpZykxd2I*!J3Y~KFJHgP=1 z=9!u#)Uh+3WAg=jdyC^aHveAhrZ}Es^YXq$bR3Q6*gR~yh2nUQ&2Od%tz&0=U;B9; z-?^zco-$5c_@FqxzrDWQpwv2!#&c{Q=j?EC{6KqsfdQ|?@f@4Kx?f$#(Rhx{>)e_w zj_26?!prC4c#h4x>?^0^XgtT}{id!F$8&7%GbV_EFGuW zg^JCCy3LxL>wc%EH)Hi)G;RIo!($Rn*xsVR%aIMYt^1hw=&C4p!hBx0=|-7&8_gdi z3fMfouI7A@cX;~EPwJ0bI;-G{a%o0;)?Cq~+{t(Se^g%CZ%)&~hoifCOjq-}?*ac0 z_kjN`uYz{<1asE!>N+3$r;5dFV|$MR22C3>FTZ)uVivPyLvcKd+15Gx#PKXo4hNmHf?wovwof0isM<#>R!7jj%P8e*VS8_Hav^j^`@i5@hoPQM?VqAvzQI5 zQ$d?KJd4@uitEJjEM^nZ#L}h>&ti6MR2OkPi`lZDm&EZbW`#02H+gvO0q>dP{3~#H z?g9T1?w&Sz_;GdIZ6fIEy@CXVMGaOp9Tv}wb04|sImw&Hm10eiMMC61qL zA9s1l%-Xc!r&wM+&KPn0RLciceI|~dW_j=iOcg`9vj;C}x9K0@$ zr*v0c%#+j1gQs+#o}M9&r*y|`{U(m5bc_97KbM&YPwDn~xKA8U>E4N)Jh!)E^D!%F z$Qz5?!#Ocj5*g2~Q<+e}M~8J3$5X&LF8wZ!r+{bH%&x+~Q^3L7$BW}B;2d5r#PJmH z?5u@U70>@A zcnUabkICYA3i!eE_u_a8ILXYaI)21cz>|M&630`(_a=Gim=R9_*SpeD98Upnbt`;)aPfgF;|C;Kz5I_G`?fo} zo&5Uh*|3^VYKKXlzT55CmkUK~le)mG0&jx<`>UYML7S^NvwsRbCsUel3!aHiJlZ5XlT8+d)J#h++p3 z%pj^Agg1j|b`Z`CqT4}OGl*daVay<=9fURmFFP<_4USlL;AsZ2?Z9CMuk5GF81u%? z($y2pPSe$OKAP3TgH6W$cT>_B6zYe!-m}Rl5#zJQcs3cClGRFUuE(> z?%4$C%=LIS8GUQ^7RR&6NSFD#IG#<${*wjLn|bhTGS2j#DUN58G5_jkaXg!h%Xw;M zF!SKqWYi7XCXQ#5vGhZ{jOKbgn~Y%>x{Kr4WcUvX7RR&6XmL4DCNmG7O-99w)5YTX7M>G`w!3GF<2ivyUgMiMo)d`fMeFMJ7M>G`v6=UY z<2iv?l_7<0W8pc0NZV_WIG#<$`Pa9_@%`=Rtw%QBT;}tD=LDi?*QMfkP9SQgj+)zC zkLLs;?65B4cupYVB|0aL=LDi?nv8kOJa|qZu5TD2j^_m8Sm9^lcupV|Wb@PQEj%X> zM?-BC$Fs>;=pQSed0adv5T6cq5yx`^k>zQyIGz)Tlo^ugwiligh|J>$h~qhd*gELG zIGz)TC=p8)Fdq+|6Nts(SBT>|f$-Z9SrLh!WS_?cw*$rToIo_(eL@`13B=Kn&ez1? zIf2M?c%&i|&pHvf`o1`xb)o%>TY+ zIQ>%!xQk!-xnqJG9^U4$Y*3p_b6fZ5P;S-wX|tN&essipSLZEHzP?Nx=XRxin=iiZ zS-i{hHD`BuEqOnq$o(Cj_dBnOeX8b&Lh)uLf8Fb^_kdl^*?oS-(}`katTbZd5ufJ! zD-Mt0$Xt89|B;oQ7hT-3eb(0?SC8px{$Ccb!~O38|7-p|U{_BtXZ^0O^D#S?kT(jf zPN-J8huXyvLGLNB3Kx%SjHkdd=EzXm{CYeE7We1~aXbZfDc>LBcna*&6z8iw@ho!t zHjOJ|=EqZD>*wDW$5UXA6{U1<6;FXRe6~UyPl2@^6|J0^A5Vd$FV{&NPl0vs8!V2e zz#4BXRNl;kr@;K{FBJdN+nsN)XBgc>#ZzE`4V#JMS>zUZ92Lh?VE137t7skoKc?IGzHFU-^nSo&vkK z&Zn|@Ts#HVt(@~8JixQaO|14o*W)R$Q{}7rn|bgQShbkj#PJl^i--wz4;4>=g}vKX z98ZCT%W_*BPk}9;SG0Hl%Q(!ZWtrN#nV3R9* zRWpx^r@)?9>?)3@z{VE{7ROUy^_%CZZsx&LU>{3N6US3vmt%bt$5UW${j1e5^WZ74 z2YuIx<0-I_!y?o)*W+i{KNoFVG!n0>@PJOy0t!2)qS1>F92DBVNFQ@}A#G!w_y zvcJg+_m7F=Ii6kpJcI6`;yIq}A8V92p5xh$i=T?)IiCGJWd&dJ@#8t3z58yJIKF}X zc&dz#T-;ob=Xkbufk1IQ$FtwOj*H_to^3xYeF-xUo&wGnZiG0#seRlxt^N?lb37Xy zSf-?z2hZ_r>NCs4@h$8;EdF@+=D@jmaBmKrn+Nyiz`1$&yEg~_lmgz@eAv6XJ^S81 zKQNYWy*ZC(`(17oDsHV>^WLt`HT37o70)w0jrwfjw85o*yD(?Pqy=ee9V!2La+*zv zHizjQkgB0y=Ip1kygb}{wjIqtDEhNYra$J9`G7FND=}ZYwaMh z8LYE|L}sww4icKd20KV#1{>`lz8P$?gLr1J*$&KuIkwnA95dKz2eHjyn;pb5gY9>#Qc?6rd^X0Xo=BAdZ}JBVZk2kaoC8632O z2qA+|hwLDH$iVZk9fUKN9kGM3X7HOGgfWAob`aVOj@f}(bR5U+z})F|oUjAk177tX zCPaF$6LR$gvomsaosXF`phF&$TU?{es~te!s$1Q<0|Mn16m!Mh7sT-t^Fi;NDnC5M{Bhg_aXiJG^3rW_JjJ~8KrWRZo?@e0gp0%riiV)ADIC)oo?^Z>@U1w$yWNx=OukyoF%p1n85XV!@`R0UH;o&Lf4a@6_<0v}JeCd-wk>Gz3;epcRmsp<5N$M+OG_SXV-HRq^^ z-|POImH+XoUC}D}{YVghRKAPleOEs^RUuRJ(@S5r{Osy6UCsaN0(Rd6{@2_CcJ%}= zvx}mu>wL@(C*+Ny9r0~r1~Y#7-Cyr1+JnzyWi%X5(U$k=ERLsWPai%nj;Cms=E{-D z%!8+BYmOT)j;Cn*CcG_&3O|0<5BXeT_apT*3Br)UQ) zKPZl;XkR=`m(^U4r)ZCc9xINgXrnE7BaWwN8yBmZ&CG+RXdg}9B95nMgEPm=Zm!2u zwC`i}5XV!rS3X}8$Fs;?dtS)f%!8+B7Zjc+j;CmIME)+0r)WR=*3V()!BezRyBrY5 zQ?%#fWXNf*$5XU5TTc+jQ?%o5Jr~DQw4e5r%w^`mQ?w-#I$!;Zr)b-J3ZL6tk7tpq z^hcmLo}w+i^Ncv2q7CrMp2y6Cr)X>TnJA8@XfH2#FOH{ZGi)iG*UW>bX!pgLFOH{Z z+dcNoXRgOnv|ja^isLET&3;G4@f7XLml^V#dGHkNo(Lnw@f2d{=eXQSl!mdvn~V|ODK;2@xZVH z9{*l?=jI}%3N^@A=k3{N6V^^VQ?By*na%2XzuZ!6e7(urAsZ}Tk+`- zpU)}c&2!W#f9>gi|4CS`=G<`YR?D2lPpmuGXyg10O}3QV>_6x1()E^D!%C$Qz4X&121V3|{njSiPr!&m26eF`fb*lRBM_ zweb}2-nGNT@f7gcRFA~*6!7YS#mktF15W|p2^cSqr-1AEJP^lIz?sXH)-gDq0**dm zr8u4f&Xha4?qTCuMc3mg;5{`8>mD|q0`B+0`6_5U1sq|f^HtDz z3OLCZ=c}Oc6!4vy&R0R>Dc}*q)9YR}o&xUOb+kC10?ydY`ID#c6!3`C&R0R>S>!g) zS*?59cnWys?P$7Jji-Rir|Tq+r-0wyyef{Tfa8}arhC|U3b^~zx#D;Vc-E(n;&=); zS7hhUw#8GxMLurVy=^=NeEn4--K)k^z^AwN7ss>6MUQb!98UpH>RM3uu<;adpC$9e z@f2|X{g1`*6!3|RWpxi5PXU)pv05BY0Z&;JP4}?z6!3!u9mVk!@R4#C#qkvIl5RPj z_pq&}fHPH@ERLsux2AX}j;DarEcDksY&-?r{q!nvJOy0(N*LY4##6wxYt|RXQ^38B zZxhE;z->asu4TS&;3;5_>z%~$6mY5q=f&|9@Y|+&bPpR(0e?+5OB_!DzhCx498UpX zKh#k7w(%73mpzBX@f2{K(`oCPj~`C~KS)1P98UrFtMW`7PXRA!;HP`ocnY{owe{k7 z)`epS4~ye}UBD?zDdz)jJ=O651swVKEOBq^Dd4WZKNrVSz|p*l`Iz4%o&vs9a*jBj z0v=W4r#PMhzSp9WG9OO?$69_^98UrJ-ASX&$5X&zQwB)d5V=lekT5nF5QVDmw z@SJsiLD|-2lMlEWA@FFN${+uF57^b55$?v$*X;7T)>Y?JnsoYBq22weMUUJgAYItU zy&nzB_B?A|myhY3j9KKOL6&s!cKGATNIGzIT5cEwP&muQu zcr6tho&p|xa+f%s0!|q#nTich0k=FhOdL-E&ra)n^)j9Ue!rodiVaTz?_9W898UpP zC=yy_hNpnfrD`sYr+||tIxdc;VIxx zaps8QDd0%`e~9BL;Bf64s@U)paH{tQ#qkvI%hzd~C1*Va9COA9aXba=d+@P1o&p{- zzqE=CPXSkMyG$HU0l%6bPGyFtfD2q~D2}Iqn?Btkj;DaLcZsDk!&AVI-?S0OQ^0+4 z9~Z|{z-yAHuWzCSPXT{@Ib0l10k7QjOdL-EXCGFnftd$S0k@5}NgPiBNA-!@&|HtF zfFG3ZDUPRr%eA;6j;Db4t}W8Y%!8+Zk3Lx-j;DZ|MG4c`T#u)K!-j1xj%Qt{)~S~` z{+9(j^Zqv#-hkB$^qvBq*S@lf4Nn1=uewMaPXQ0^_E8*90k0kBuVTYf!25@9630`( zvwOr^EXq6Tce+v-;Kl?Mu$( zGh!^;kUJpbfa0I8e@{`U``+ezgH~oJ>pS^mri&XgfB5#MS*`8)?nWGOpKtg*sYm^RWv=A$8}zJXg_N@&bZVEw z)nmGve^r|3Y0ZMEx1{#H`zK+A{4Lx$ zGlODw5Y`NQ?I4U96t{!WW>CToLYYBHJMc6EHX{x*NcSHmWbDDt#?=$dPRG@CK4#L8 zHx{{9#d^dx+uxunOS4S@f2|NNH4_k6ma-v<>H!o z@Dy;_$}`0A6!3?FFU0W_aQm{Ax~6$EsaEg_#FW0Y4eBRvb?OPdxQO98Uqi%3C9)nFmh+&(67198Upv z+LJhyxgJje7u(-o98UrJwz(&cr+}}|DVEyIgQtLd^j;{Ar+`OQ@JwT_$5X%?Ml}@2 zQ^5TW>=egSz!}cP(gK91fLAwZD~_jtzd!v=98Uqy%b6;j`FQXY@W#Z0#PJmH)<<{6 z@f7gP#U<05dGHi)`qnGN@f2`^cTqB!>+uwDgGe33@f2{fFXzSa6mZ|4xigx1@D%X6 z?bF5a6ma=xU&QegaEotsGnskttP@%8>=nneE}SWMK^)JbbAHCDmS!Hk4UJVNiusS$ zPd)UW0^Z(ooyK?y*gKJDRC7I^0UZk=$wIGzHI5Xmc=9@lybIMmZF;&=-9 z&a+@~JOy0%UB2jM9y|p+JlafgJO!L~(id?&1w1BVofu{wJOv!{%uaDU1$>}s!kFfI zJO%tSN>6b-1)RP46>&TTym4P%FEbCG0)Cujx;UN!ZZ+VOIGzGd+p$J0GY_5uUY22- zIGzG#@yEM22hPoddvoC2Jh(Rp&dtN$y*c=&6tHLHonzAnzr9kt$%JDm_cm?tvwYBx zqt~jR>fd_Rlqd_U=L#1zu+{iwyGG9pf2myQgzL(VE4c4iU~iunTO;n7ROLdSrst!5 zzHJIY zZ51(T1CkCF$5YI&f@g^1Ddz0y zXN%)0=Ke{3isLEfFx4BWT=5ihx_3v!@hpnBmu66*;wk0>cgBh1DQ2H=@5S*HbB_fz zRj7E1*~@pAIG$q8u_&<$6;Cl|^X(^&r#X^L*9PWeI$;jfP3UBr$WWE$hGLbN*qrC``nMBLd8?SH@~zO$5X(M!fhAF zQ^5DyCs3i{Dc~e6`iSEx;0v#AiQ_5Yim#kSi>H8V&RU|P#Z$l!j)hjC;wj)5%bSSf zDd3k~4~gR`;Hz6ws!;J1aEbeU#PJmH$CsDH@f2{G4%t+wcnUc9{s?hA1w8!ZadA8a zd}4Pdl`EbC&R1=mIGzIDfB2m^o&rAXTTA7Nr+_by-Ybr$fV)mhr9#D1z`+AXh~p{X z?Y&-z<0;^0U;S06cnWx%-&S!v1?-tHQD*aXji-Q{7V0mKXI&WbY_d51mjzs<|3&Rt zPK6q$_x|<|&)D=CRh;+$%R99nD*mUpynm`|;&_U=Z^FzfPCUgtulWFRJjHxu!F6#w z#awKAVU;MJVy=2@fjFLGJ{={DiW5&U&n(wW98WR7ntenZPci=tNUP$+Q_R)B4Hw5# z%*|gv7ROV}lh&73iQ*~d&wYc$@f35kcVTrLji;EqwP+@err!z%MSG{tCviMQ z8{D_Nulet@)~h`3;X=C_~YH11Lx+!y*Y4h z9^9J)=jP$>-W>c>3V3|+A*~~(+&ODq*Mj5L&i(Xb)9PYLOT0+kIo~9YMuk6R?3lAp zw|MjKe+f6H!o~d4n*NMmHh5~2-4D`5cN}?gxnrV@SGLR@_4vR4BrI2RPLEUEcX{z@ zfl)7gjPba@nngo$eu%#J{gmN3b0&LP;%4S~t{&6X{O$t&mlv?BCzzd%tLuEs${6y- zBDbS`w*qEd_G%oxr+}j-3f34;0e{Vtr=YnWPXVV{I8_`^0jJpdUK~#Wce&?Z$jpPM zfLG<8A&#ej&z3nUj;DYJ7s*=K%!8+Zz4}fR$5X)JV|^6Iv&d~YS*wVd2TuVPcI*?! zQ@|A>ruH${<0;@3O-G31Dd6?rpNiut;H*_DXtRl@fD@csBaWwlBQA)hO(&iL9^0>@ zIGzGd+4F)po&rv{B$qa$cow0H#a$|nr+_cC45!T~o&tW;u$4HT0#5Mcq&S`e_PLc+ zCslX~xch*K;&=-9w{@?@@f7fg(Uo;lg{Oe$)m|@-r+|A5kFFCb{4~3nzO$#5IGzF? zcIKcso&v5?Ke0}#@D%X2Ctbzy6!3?F=f&|9@bj$RI;p}_z^B4Ee|`X-0)BSR`SSzt z6!6uRHFbi8r+`-#cmDhUJO%uqoAc)f;3?qCy$0z73r_*>nEOy1PXX`hTuLWZcnY}o z+U4SS3b@Ys$U3RQQ@}?~wiU;-E=Cl{>h_Cc$+LvH%shAsIMd8N;&=-9{mWb8cnY}BndrIAJa`It`MLt) zcnY}py;l0OdCm2B3OK)4FL68tJhu8( zaXba=D3vdtnFmh+hZ!t2N0>uid+%aMQR$HdY#O<$j*j=Td%- zG5qu3B>rD~UZ1+r<=dzXLtn+bJ^pk0(+$1K$BnSzK+1Z}YJKe6$$Q*iUj^-I&I1n* z6))3on%|-Dozs;&y8Fq)TaTm0Y&|9C&|~dubzAgth^xnRHGjI4?!O0Y)`Tb_|8o?y zf7dp$8T?CL1?}nyW~bxoI-ii=vw35Y>;5#J%3%JZttt%)_}qgY;&=-9TG1=wcnWxG zu(Kra6tJV@bQJ@h0^UFWy*Qo%j*zXGiUCgn&rUQ=98Uq4O#ek3PXU)`S69V=r+^d3 zJRpu|k!x}(waNfb0UwD!N*qrC-}ZSWj_+nSd3A3&{~cvK1w41qR+R*v0`|KXPi26o zfSU~NDUPRrlXku)j;DYpH!Q3&z*E3+8qXKUQ^41EI8+RH7P<1X>x<(l;Jc~zi{mNa zDxFiQ81NKu{!F9A@f7f>32(*m6ma_Mekubz1$^h?GI2Zwydz0?l>wdt&fKzDPaFm4RtRZPXP~zbx0gf0r!ZTR`GfG0 zPXT8tQ$ve9o&sj^$GbNN&dq~+bKu-OxHkvR&BNclIryg(aKye7ioBY3aZaK;#W%z$ ze$zYEsR`?^r5RbMK&SL+R~D@DF~k>7b#~mA*4NKIT>W)izrI;IEIacfe!-hn2bW7V z#bf#W8%@ve{Xguzb$FD?qIcUJ+}&M*2PZfLclY29gS%UBcXzko?(Xh3xVyXE)URvJ z-g7SZ>-)!^vuB>K^E~Hd*IF+XCV^C~ss?)fxq$zF%vtPXlAYt1ez-O%|HA`mE;qk@ z?{VUQ!}qRNN?HDR=pODqrn~t)cfkMfJ79NTV0Jq0p3@E?Xi;X7``n>GT(d|o3b+-= zyr+PxRGlY|r+~xFb;dK#;VIzUzV*fN6!6c*yT$Po@JX+P@y$GV3i$4XR^oUHcvHW< z;&=+U=?L!xW*$5RJp0rTaXbZlzuj|jJd0f2Y!wokdGHkQj*c6}@f2{a$?+1I=kXNq zsf>Na@m=lCJ=)nj;&=);cILu~%{+JtINX_q;&=*p$&Qdo%=35(xLaUzaXbZ_dc`qu zJO$h>m`_qO51vJ?@2O$pcnUa1^ylJu3iwsS^2y9RcnUa6oXz5R3V1;0M9IzbcnbL9 ztsdfd3b@y$^Wu04xb4R*-ew*=1sweBByl_i{O<5)aXbb5es=8?W*$6?T&3W<#PJmH z!_J9Qn&tEM*d z;3?p`t+$BdDc~B@W2Z6C<0;_u-MWh7Dd2I%FN@j;DZs`(EA0%!8+ZSDo1`j;DZ&&5WPk zJddYfWH^WmdVV6r+_=>o-B@MU1+>yxj6op1zh<+lBj0VuLBQ8G4CnhhxI#)<0;^7 zjSh?BDd4x$;zu(-9!~*}+}lMQU)_FD(_XkDj;DZ2+{zQ(%!8+ZPe+?0j;DZ^7IVfh z&*LfJg~RHL<0;^U+4qX$Dd5JJlf^Xi;3?oMhx?1;Dd0@~?~3Co;JU>N#xnEZDc~KO z=8EGf;PR=QvCZ>%3i#{%dg6Er_M@~hVl(m7)xtA^_~JAnQ5QKcnY{pkEALIJO!LQT7Wp70`5NOwm6;wetS2c3Ik68 zcg!_P98Up{PjOcqPXQmvTvUaDr+`CtTPlvHfI}vXsFJ|5$St|s&c&^#fUBp!B#x(m zvo_14!oX9&gA&XY$5X&h_6Ji*;3;5#@5bVI3i$Z`gW`A!_~zP_DhWIVTx0GqaXbZl zbFXFun|=fcnWx6jdtRA3ix8AE8=(x_CJ0q@%qS0#a`fD?`FEskfATl3+jIGzHo z(XxO_0#5-<{Dd5XX^5!t};3?p+ z0dvLi6tE*sh@9qmJO#YIOfzvj1^lz-F>yQv?A0%QE;A3F0$%lcj5wYGKArKcIGzG- zS+#0zGY_5uu2z1tIG%Ok-j3tq_+J+A)z<}d%y?nJ107pZz&$5S630`(o2EPx$5X&X z&*yO+OIl9>SNSnn98Uq~kNQO%PXXsjP+P~3cnY|B)!pKF3i#EqBsylqQ@~}<^%KWa zz&CokjwSIFaNb0P6Pr&io&t^^Wq~-J0Y>IjI%h?38 zw;T#vCHk&SbE*U{9{zgSl!@}@Io@IA@zyDp(?&eIMZe@al7gH|H zzi&k9`pu@DUOKJUiO6ri=dTj*JoLqmL+iNvnC|BPbpemLoJePpW_fUqwF9%ZILFz6 zS(u#T?ZB*d&Ixv4mPqGBJ22OU&PjG)jv1Yk?ZBK-I;Yry`C!hec3@6TopbEK9K|~4 z+JQOWb93`YN(2^u@tNxS<3v$J;hoS&IA=&hVRe(y86y^itwFOH`73$+se0)X z-u$L@;Mr82>(x>m&!#Fw(c|KHHdURrN6=;&&!(zE+gjpyHdU$G?hwaw;_{)1>ooy* zPF(!=_R=OB&xuR8+}FkNoVZ*MbZx%zoVfH3K24i!JSQ#zzh4)}bK(-`P-bni@tnBa zIx|rm&xy;)n4iV*oVc{xR9l;GJe#VjJ@$y>IdNIPGeu(ab-{DulHvMraXcq3N4mcf z$8+LxJ6)r#o^ZkqL+xYYJpE{LFn-@klH(xKk@y*!t&D!(L z+Wdmxo3ZDcwdb3)`SrjzW4@VtzFE7zng6H0S^r%MxZ{zgpN987Q@`S;yDdwFD1ATD z*}QQwTwnJ4pu%%w-6$CL)RN9QhB(@{8Fb;&#%aNPw|aLfap;Hh*s9ytdOz_E@gc+K z-Qk^gGY2h;6tKx0cXP%(mbFID*!lr-EwjyhfwSKe%RqH1^P_=%x16Au6J5aTLwF6b_ zH#<km6nwf?jNQ?0=rV_ly&Q?0?Ab`aA1_juWXsx_D$s9J;Df!UQgL)d}& zaymoWfvWYP{WxY|-q3wm_*UovaF}{T5xxbdlXP(EGv^;Ci72^0(mJj_BKEHV$U)u5tCt8Z* z%UIqo;}LOuSzo;r?K8Jjbdx*W3`tbF8{|Xr=;Y9z4gYm&yzl$8)T@ zVD24pJjbes%V#fW=D~BUT0GWJaXiPWi5p%Q$8)S2WqOW6W*$7ps>L==6UTF`+T-YV zalF6%{7ed4zp$AH&#`K*Vtd8$9IM8glC+3<9?!ArjFSDu@f@p8cy~`6&#~%?=Y@-! zdGH*oc3ibU9M7?;e~)0r%=37TRV%(~Adcr)wSKWZ;&_f#Q|?b(+{}aLShZiue&TqJ zRad9DEsp0{wew5A5@sGe$Ev-H%oWFTtm;$TtE71z&#`Ll7!Aeo9IH<6vR53>v1;P; zNlKY{@EofSEZI*S&#~(K#W%$99IHO)=v~^(gXdUvLw(m@?t$l6wL`P(dLGZQYT@~Q zWz0Nyj#Z-y-tNS!@ zJjbdpFMJipcelU(leVv2-pqsNShaJ#o#J?oRbP}(Qo%fr=UBCS>Hgw)j#ZN`yeE$5 zSoQRPf)&j?cuM{7l=myP|3c zGY_7{qh8%j;&>JhpMqh{v4jK9;*nr|BXK;7N3)4X#PKX1KW3*lXB`eai^s_&6U6Z> z9zK!3h~rs2vi7QH4p1C;7LQji4vOPhJW_m4XHH)ncovTb1ICKuSv-PgeJ_q@@wgka zMp!csp2eeLx9#G17LV8^l9=-#2cE@aX^Vm4covTWVIPa*Sv;CVEoly!9C#Lw0!>$n z<5@g94vA)dPdV@`9$tHUh~rs2!Un%9j%V?>Jv_1bjq1R&cqGfwNF2}NQRw)7aXfc& zRg$MRzxy3{?&RtR8zqkCPVQ@;x8iv2l%K`nGA}__3B}oApy1KhE+ID;k>boDTeW z%O{*aAda74`GGE}%(quGEMMEQq+VlzUub!h zy(`7>i!2|vGn!sYfnRKSi+f$f@vI9YCXNut|MFGPwQ{c4DekZ%mGz#oDY+qvp<2^^h0a=Dzxr?u(0zfhWK zp(Ev4m+*CsWO?om`84civzG0TguQj??DdaHd~bx_`ys-PpG%VL?)B#ab~k73PaF3& z9sMIw*A~Jr+^(^|B$~6+T9nJ zosPTb{Os>5HW?G{-PJMjr`rK~PXWIzW6ma8xu48081?=bBP{+l1 z3OL93-Qsu(IIVYN9Sh?rVE-tM#qkty`uK;%@f2{Xx@k+8|5Q8$ygm3BaXg!hn}^?u z<0;^sk!$F<7*7FzEwNJ^PXRlBCe^Vpo&p}bXrMTr0&WuWp*Wrb_FGh3$HI6Dc<}h8 z;&=-9Ouz7D%zrPQ0`?AQEsm#vXP-SKj%Sl`JkY1CnFmh+cY8fr98UpfF7Q?yPXVv| z=wHsvgQtLhtG-tpPXRY69$&}8cnUaefllIh3bsB=L;3?p%`*w@tDc~w^lj&F(PXYTzZ6%JUfT#67DUPRr>vqXh z*?e3)1)S~ccyT-h{NnouaXbaQZE1}vW*$5Ry!q;8aXbZFXKU=L=6O5?+;v1(aXba= zb?lNjo&xp{mbsdl2TuXNk2hQ#PXYH^cTXHo0gum@uezBBPXRX=J53x<0Z$+BSsYIR z_wlb$!_0%Hfc;bM5XV!%H-9GfH_ziK;CGV;h~p{X?#CaB<0;@2H%itt^WZ7q&0Z_T z@f7g@f2{kdl$s<6!6{~*=w75@T?1kN|zSL|FVD+ciNZN{9x~t z&Gf#q&FUlZW9u#%U&ZnaiGLHvSGD|6&7I=-YL>?x6GwN+`0AElnA<}fU&HdLD{qM7 z{ViW`yMUkhdE#qYUN`i7aeOVyBUTEgJ7aup%R@REiR0^7K6cgtaeQ6NuZ{55oiVt z>m5Gz%12CgC7O=BGxLrE)3Hr%8oCWPbcff`0Ab~N3?Lc?HMeM+wC_9VV zfjOpj7PA9$#_cR_2j<}1S;7v?2XmIR19Oz`EM*7ge!yAU4$S3(vy2^>8w+PyJ22NB z&T@8O?ogcN?Z8~jI4jtJxz%x2v;%WhV%?RXZ@(Y|d(S z5XKCu+kv_GbJnl}b8G1Iw*zw(>8xo7=6=&z%MQ%tsI#^mm|qRfI(A@A%AIxXz-b14 z|7L>54t6~5zQF8&+&$-KCJlOH@hgz^f=cw_t&V!n;%{i8Tab3K^#vRpFS5=Wr?SZdzNY-j;D;1#MmT`r;PW;j;pf7Q^xHo z^%BQZ#`z!K7RR&r`OYk&;>1(NSN1Iv$5X~{e~+ZH#8bv&x_1=EQ^xVaUJ}Ps#zjl! zP+8(B;}=(^isLEc*K@vz<0<3w?Q5$z@sx4A2D`-Zl=0-%u7BSY&*C?>eQ%X0zOUW1 z`AxYcj;D;XtSzLn#8bv$GAUMO9hiDdXfbeiO%2#uF;<6US4=@d_nXS>h?< zdyfZ*<0<0{BOZ$5DdR=yN~tXIEPgS63lzsw#t~OWP+8(B<2b9@isLEcD+{-X<0<3C z596vV@sx3>fL`Kw%DDNqo8oxNc>IupDoZ?Nyye{jaXe+5@nSF)B%U%pJGP-Xo-%$> zWS=;mGQLtfsmc;h8MhkNQyfnjzn*+S98VdSN}NGuiKmRW3>qSir;JbLxhIaNjH~$i zsyOkKvDdYU;&{sVTer{Rc*^*0n%XK({2cr1zu~mq;&{q9P19-p>E_O{qHO`!$|2ZX9E!IG%OFCux+r=6O8pLSD=9EIQRE9c^x&*ITLsvHZ;c z=+%9@%98@FTDzq(1gk&Vq|Lt< zu)8@2U)WWqMv+~;@|M23_0z>Zt(T40t{Rp3lHv z`WXnHd)|^fJ$-8a2o-Vi**jUICMuJwehBZg*#no39@jB`*a+csziU;j@Z1p#S8sh$ zGufI8*PBgTp6+<@<;Rb_Di~qpsA{hVJP){49&Wvx^P5{G~HJ1bQZM(b5P#E)pk@7`}S_v3&3d%&IV_7cZ)*Sjw7b#Xj* zy^W9NF*olHJa@fwGtUsmbJrVk&}VTxcfGwv7dO}W4m@|g>)Ol|$8*r2IG(%SVPzJJ;|JT5n;B`s#xe8Yhgg0sUMq1t$Go%OoEFD(*ITG+ zX7j=p2cEm$+DFEV%)Fw=f#^KOabIdMFEyeF)`kp2xE;%xVx{9M7V2 zZt97D-2q2maxR4V!FlU+)qC!M^W{scb5cBaz`p4N#PQq#*C=>Z9M2u_*xcF8%cdN7 z?tl*_pDzA~x4&uMt@d6324~JTh~dFy?tYcfk2Wjugk& zu^)F-tmoo*?tsrUC>PeugXa#oUhg&HcRQ~JdfuN_}!28;&|?WFMm8Qj^_@z z|1)3ndNl{0J7Di=Q^oPz0dIW&RUF^c{&$UiR#S^Sz8RlqyZGZhn*-P8!LvDVZ5}+E z1J~x^uihN|UG9LFr5su>S-!}-cT6f(YFUyE@5eXkR_WEckVAXEsGoLsr)%4TeEt3H z3kH7e({A;|?Qi1Uy*uIY#UrgpjvI0M=9wIg+c*k67!bevp9|RCoSo;dC{QA0k?kw0 zBsnubU{|R5n_g}xoa9Hje9>zKCJJ1a*4@W+H@~NV|Mv^n`JXIccVA$3I_{qHGb?G( z8;jhhc{5U(am@X>^`2Kd&x!I~V>|`?v~;c1=6O5?>~&_FIGzIT8Z%xR^E{pc?$@-d zIGzG-`09{2o&vsgB7Ryk51s z!OVlFfOGfWD~_jtlQ&DA(L9f*fP*y|D2`{5OP%koIGzIb?^Yy}nFmh+_uaBY98Uq~ zZV@@Nc^*#z2ORDqj;DZ!mp?6zr+}k0PMyWfgQtMAWFI1qr+{xJeJYNpfagXlo7K#N zr+`;(S|g5Uk$Vz9S~l}Mo&wG>rlUBX0)DsUk~p3ME^s5UubBr=0sE8;5XV!%MY7x% z$5X&Dh84?h=D}0Ij!w(O@f7gM=n-<5=kXNqh>tDA@f2|5*2l#06mZdOX>*!s@D%XU z_`}5U6!5#W_r&oOaE7RPbD4SY6maYzlf>~9@b&u7#PJkxjn&0-n|bgQaP`59#qkud zZ{<*V%=35(czC!L;&=-9(~*A4BwcnUb8|3`5=1-z}fe?Bu0 zo&s+FV7oY;0*)9mkzSCDr+`mH4-m&wz0s_mBRe|@f2{Lw%f$<6!4NW@lu-S@f2{~<2}Uj6!4gK*TnG@ zFpEFlvpH~W9z2@^*XF^qIdE+r{_4%a-=%=F=J(F`c}eiZ`|sv^mS)blyCcf2h?}5j z$ge{my>1xHtJ%)k6DD8XyCGJeZU>i@?AbAJK$i3g2c;Mq?DNDQ2dC}Lxv2S>E6&;G z=t#<$cvE$0=}j=-Jm3-nRdm;=J$k1h?<3k8RZLn0{v^NIPsKmjSoA;@s#m~6iHQd6z{D#o-+P;u!_nOPZ>}9v|b!f8Sl9iU1f=4K?;&{sV^|p87 zc*;0ncx4qQo-)2$X|p(T$=>XCcw2x@N5EH zn}q+?CgAT?&?~1{g7>Ww-@l9a;SaAZh7N`&a_}~6TfbME?{?aPPo$bY|{5zcbxn3 zvu&Sub>~mqTXgfW9d(yw=rSi*>l!=KyZe~#=Kpm8d;T2oKjzN?yZZt!u|?(XIc>9o zc2tyZux?w*m>t&gmo@aB(p}#%Zdt?el@s#e>!;T8(c|42U+m?;R@sw_>m`B9%l&+(C z`if>AJf%CZ*LZO}rQ0UXCviNb+jF~rB{L76(oLFRyEvZG&A%f-W%E3q(tWwSw>X~C zJwNTXIG)m7H?>d|GY_6cZtn91;&@8e`-E3j^E{r?&GfX9IG)nYk?XKHp3)8eB11JZ z51!H;ReG{Gp3FQ=4Jf)lL*>Z6_rJH1X#2V&#Jf(YcZ98#1i(Il{ zuD^*1Pw7s2;p?w?tfzF7)te%Yr*zkj`09GzdP;ZRzQQ%lJa|g?QQAe~cuIF};xM($ z^LR@4%c2(IcuM#4yp!U1O7}uxpW0>~Jf$11?Fex^rF*2zGjTknn<_!+I%Xa`rR!aN zxj3HEZ8JPXUGqGi(!DpQjyRsueV%o*IG)mNu_H=7GY_89T^p{oIG)l?{rR{!p3?Om zpRT@{2T$oPsxd|!Pw8g!c_)sibW858*1*hzr*!YU+$xTzba#A7(9k@Or*tR9?I(_> zbc-~;D~_jhPfsb*$jpPMbgQplB95nYi%tvM*gTJCU2uj@B#!^(9q{jQrWY|kxK40C zy{CY8u70U8o&vtSr9e^hJe~s1*>|Eio&v5?@0B>70`AwfVlguho&wG~WP>=K0#3I- zc5(AOo&rwuqnkLM0)ABbsyLnkPVC5C!pwuOV;9kGi)V=ADc~0~zlq~1;3&0fl{E9< zDd3#m+r;q{@bxLKzkvx)0WS{Qy_A^;PXSkLbwwOc0gpYMv$T00PXRwaJyje}0sBq= zERLsuS^V*y&4Fw4;Mp9wHV>Z7fot>dS8opfE(P54`KjhR@8{_5wJUnKf%WGf9lq#w z+fTWdb}cgV-Hi3W<$vCqV&0S$*2Ss1kX|*(1k_`X!5a=Y9A_ ziOhGppf_i+RFPtt|6zc$xE&-kgA#U-zzj;-L3}ePWe4%hptK#tHG?vC5XTJ4+CgkH zC}#)egE`CFK}<8KUD7-LYYB5I|yk8_3a>p88oni z;AYU!4$Q*nY-9)K%js-v2Tn66`fr1vv4dT)yDz$V*7N5140t{Rp3i{iGw^@kXW)-D z-Ov8Fm$3!5d-j~X26Z1%SntbPemv-}Gd3Jw&hjSn>WJgZTmEAEMsa)v%g2lhli$pP zuV{Jv3U$Ttl`Nm#bE`O>GLBF$qMw-uPZ{?wUQZlP882_WUK~#uPiP#efSCtR8Hdc# zP8?4e-#&9r98Vc{IpSN;%!8+lzb~FFj;D+hjrb^zr;G!hRV!rX!BfVks%#O*Q^r^C z#x880$5Y1R^Sl1;Xgp;cYVSookEe_yKFX$(20Uf_$$O$Wo-%&c@|`%IGEUUHvQ8TC zl=0Pq>&5YuaiYG_bkcyQj5js!AdaVuL$o|Ej_24sfp1owG~hWl&lNCU9M7@&oX>B> z@f@2E3|>(u4S0^td-|*r$8&5RKL2-dJjdqQyesMi0?)B|rA!;d@f@2+s1;i$4S0^t zPaWzhj;D;PwZ0{e?___!tRCw62PE(un>TLl`UfQN9Gge{J)*83@En_8@77Kn&#`%@ z9cRVy9Gfr6n^o5k_#XD>b2IrwaXiQ7-#>g5$8&6c|5goML*RScc@}=%Dvqa&9V=t$ z8Uo+fKEL@*H*q}2<}))~cU@apA7G#FdZMtdA@CfV-;WU}j_26?{;fyic#h3$KPaGU z2t3E;iR;c5$8&6+F|n7fA@CfV|5)8n9M7@&iSCEQ@s#m|bE$L z20}Z#in(-7qR2k1uYjW3%;&qq`}u(&#c<8>xnlp|L5O?Lby}8P1al*5h`A-QH z$FrEVOYJ&l$FrEti%~&~7M{hdSd#VPcowtyjbmwX!n2qS3F}(S@GNGpj^5Pscowr^ zVO@(Ep2ci&I@e-`XE8f+z_pm+S#cW_x*J6feG0RlZwV2^q z%+BZiq{R%+V%8~}>)++YvzR>{woQu}p2cjJZyYU7cowstJ$i`aSOU7RR%g z-5dB+9M58QzE5c_PIwlxk5^WR<5|pdgpQ&+bv$>#3-bDl#e&uwhj^_^eVEr}X_z8CNk?v?@iQ%d-m#N7ewO9g?pzhe&$fJjEkE6<8tey-)$mW0*aIewny)yK6F z$IrKX+>rC)_yv|%shGWv`PPhIX!(sXQ^oO%Ebp}8n>c>4OG}<4Q;zm_;&@6oWaM+=cuF^C?aV4yJf(YJ!B}xTr5kh0D{(xfJ2Gws6)m39 ztu|$qIG)nYRX>szd3-bbd1LX%do~BI&4Xui;MzQRHV3ZF!(Y8Q_`BQz_uU$$>$X|b zYKE%c;PuH{k;?Aef50z8_ST`-P4u7nz5Dd~g};Vrws^{;3p?h7IGJyr<7&y3HKsSK zH)U$<;7#iGJvy*f=tz&l`gZ%*9kBT}<8IEc^G_A+ooZ;Nr7b3oznjV5F(*xnMUy|Y zz1?q2mr#B;y|=jgnC|BH6!1U1fZct8+3C1@PCJvJUyv-u6D~*b4H`Ns2CU6y-m@5w z_H8GQXEDCG__R2l#rWB`jM>eP$FmqGc{oNK&tmL5@0B>7#kl6)GC9mVcoyRX2Um*Y zDfN2mBjq&D<0jyB?WO1(;nqvCiy%6oo&sKVVVyXh0%@hox&zC9DiQ@}0Sl+|V$ zPXTA1u|^zE0iUY!SsYIR*Wc~0O*WnaKDv2_IGzHopC_?4(|8IvOrHMYcnUb?!~5cR z3b;b7BHB#jDd2qX7l`92;7t32Ycq|ffJ2sQD2}Iq8+O|vj;DYd^pB~{H=Y8n7^1B> zo&sLd^@up00^YdJTPJOJ3OMfQ0pfTHczv~d;&=*pcH-hXfy2+XzfSTzTrQ5MfO|KO zq?0y01spiIy*Qo%PB`#_IGzITG9iaf+VB+c`ty^;@f7g8pP$9?6!6V(wMz&6ws7Dn z;3zM5isM-qw%0l*j{ju=PnnxAy_vM#t(`vReHr^nsr#Y3IKHgq1t(q=$CtBw>5!Zm z%#X*Hx4eDvapL$2mhVbDqf;@4Q~rC4Z?Euey~zQ#)p0zqGNN1n>KIayRF=2W{g;>J}nR znFGbW_601=J@ESWhy4rXKIJvGXs!eARt$3YG2PAYDd7KL0ecm&|7209ps%d6pdExX zgFK3&w%GM;Q0)AJ_DZ5 zz+d_q2-r4gd$eMoJ{|owc2mwDQE$&}ohS0O6O~H@OOR`Qj;6N;+>U%FR^BMnK6f9P z@6Dk_%lroXKJRI$sKMV)9#m%MnPHQ)6;)!U$c`1b;KH>W%A?qj<9@ty+y4;HZV zzfi#LzQB~FyXXAuZyoM#`_`?gSHs?J?s^q1ceg(y?-a*#ciVPQV%Mu)t#4+Jo!58n zFOKK#HeZVS;&|?E{bIUaC5z|oc2$>!dX+4myW52kL+Dknc#daB1~d}Kb9X!F)*f*@ zcenA&h108E@!Z`88;A<2goNHY{Zg^GguV9rLAAL&fpjF?UG# zTpZ6GbER{Y{mndh?wE&X+Afagjye2=*gDd|bH^P2LOXFhcg!8%o)*V*$Gqfn7QGr4 zKg51ruAQ1Bj^~c~#jG#lc#e_R2CJo4!{WJP_V2Vq9M2td()o$&nEzfpcg$_BHW$Zp z$2`B#F>yS1%)jMIU)Rio=Z-njo-yKh?wAkkd@GLUj`>5=s`bn~c(e(d^WeE-&Ub9EIG#IZuLifp@!T={Z+b6| z=MH%9f`|>x{CMtwoq5`edT`=buo_TZZ%+`>yNnLxm(RuEQ0?0C7!$0uiM&)k?^(QEi@Rs>b}jCn#oM*G|I-)mzsnu) z-Ga&b=3lyaSj#9g*H?NnX<5d_!HzZPwRvvB8o7@2IGub=tl= zU$T!Hb|g)npP!zD_}H$fyN~H^eoq1a!wcBm7nq%nyXXAO_9*C$LS51#jW#85S|8CS zheEyha)>ydLd{g_kvN`0eK4@7HY<1vb@YdY;&=-6TG?RQl;A1Ugw;!k<5}dY6`UoG zr%;=gacUETr%>OGZ6uDTP(x%tB95m}`+iHO%?h4EU0r;fIG#fF>G4q>H52}@f7Oqo?rAlo^eGWz*DHNax50dQ>c&Tht^30oigs4#qkts zl5cOs@f2#bYZZ0UfTvIgFIy{)r%=P>iK3GRJcSy&eG73sg_>vBesMg78YX5!oiyMn z)G8CZh~uZ*80c~JoH%}l<;izv)=2|?rsYXTj1$Mtvb=Sr58`+V_;g}_oj~9z;84q5 zClh!Ixam39$poGPu6Vt_P9X3Uu=C3UaXbaQuzV4nG~g-V+oKkX<0;@YE5p_^-^cM3 z@WjO}#qq2Q^@V)D{$aG3N*&Mhw51!3|YxD3| zZw~%01-#@-)$^yvTyB4Jed|NN)vuZ|+`jL={NsPhfziLoM`)` zUZeCYMjYsV`F`j&=a~1S35{(2Hor+ zff;nSgZO69!w%w^K~Fn~YX-gSAdVUIwu9Ja(8mtU2XpqdgP3N}&kkakL4X}ZH-r9m z5X}q**g;e?7-$Dk%wUimL^gxLb`Z%7hS)(wGZ<NSTh)D z2Vu-$lpTaNgVA=NS3!@lgOJ9IwSy35FwPEwo56THFpG|Jf*qJ&CeDd=;538I|7L>5 z4tA*SzQF8Q-96`LCJlOHv$AnuXCE_;^CGF5&y&6O+%?QH8Pv$@oZKGC*C5CXR}hHZJaFTc|4ny ztI4~Gcuq3jMQ$aI=OiP=@-yOiPBK;%%bLr~gXbip z+~SGicuq396#poW=Op89j_SG1Ja|qrLZ{j$j^`v}W$FZZ%=36oGExWh7RR$$Y1im? zaXcp(Yn$cEYv#dolJR=YTycDW{dIo6d6<0Wc|0c>@lUrE$8(ZV@!e8!JSQ2|=bsbD z|8bHrB2#`dKYp(3qbc6-tP>fgbP&h?u`YbrE{S&@HsbMtZa*6VZ6bmo6F3QVH+mF*Yf zV#qTZ<0)Vk4ZLSjaVr)O!JrrqK)sShX5TDn=(kv~eG zTzO^Vu<9d{W~{ifO}AbD^Y4OoH)l4#=u7--7o7jF>f0-skjr+*GS z<<#l?I&nPZ6z_h_gywlXi(IvE^~CX%)8g8D#PO8VgnY@(d87kRIlWvqNE}Z&T^svY z98Wo2_AZ^+%!8+#n&w+Bj;EaNh76a)JddZG?(hCh96!l^-w3#PL>xca@?Bq&B{lQl zr&u26Yfo|fRLlGJxG0XFX8DoV8IqZK@Y5|noPLBjeum|5!#@?r&$N8V@si2SJos6b zU+uqA96#G~zoJpS&GYydnF5rGR62b@5%WVak~L z7pJ{S;qy6Grte+*Pu>_~NwZ#gt2Xzmxp>{w=BbZ`>(_FA^Fs~$@- z<7WBhQ66XCo+e+p#SeTZ|M?Es-JAy+HValF*WgIUre93(Bjc{Ge#x&s88D=MvIZBf zEjb@*`&D-z)7|`@0{(};3fkQln4ONh=lsk{8uUhq-VOJ#gc%nJIY{p*(IjDtmoywt zi3a?(SR7A@-g1U2WuC`Vq6IrO5yw-a+0X71$Ftas{1LUZnFr6^VcI86#qr!7mi}~9 z9M9e1y!`3Qn0fFW>?R=1UDvCc@f|Hsw&;VN$5X(WuGTDT=D}0I7ae=V@f7g28Q$g0 z^LPsQcEKUycnY}ZyvO2r3V2)LQsvD&cnY}hwdLY}c>D8Tc_d;5^E{pcK7FjMIGzG- zobaqTo<*+J*9;ZSJa`ItP2O?hcnY}hsQ2P{3OGXH+LiS4v7Q3{Hh;f3o&ru#BuQoS zJe~p$=HFc$PXYH>dqo^i0k4~xr;3>ePXUh^J6jx20r#mFtg3k)&mwnne};00nwbYr0e@-PUK~#W$Gmh-98Uo^KJHuH%!8+ZBUhd(j;DYlRQx86r+_1W zty9CygQtMorrarxr-1haPv~!+$5X(A&h-+7ROV-HTo5*W#++Cz_rfJ7ROV-Ne=!H$5X&3rq!u!=D}0I!y4}u$5X)Gc~jIe z&*LfJ2j_>1<0;^jA)kxmDd1C4%GWjX;3?qjS=Wiq4$(%*+KDiagC=$JM?KKj;BQ9 ztlA@vr$qN%ieJdgkEcX)UGFW9r$k5JxGj#SM8`%hq6=+2CAuTuB5^z=TE1V%BIe`b zDN&zvO~vt)=z@cX#qpGA$2F<+DrGz+8n59HaXcj&HT6SrJSF;JO3`BG=Yyw2PlQ?| zj;BPI3<^=)JdbZ|zv=II)L0zf#PSeN4~XNNT7Gt&w-$MPGs{{0@t)0rYxCgQ9Jn?Q zp3Q-4^YB-14*o6$9CqlsF{vY`Sa#)LwmgGQeP7ii#pyvWYNhLcIOU-0q4z9kbD+S5 zl;s9|`;w@Ci}^dEwuqf0Li%Gn2G45QYE$iY`xhO17QWVso5#QXxq#ixS!H|mNFjS> zDO~Pz;y3>1l6*;;HRi(e!@_4Aa{NQ7eZ#g7b@wsd&Hv6BSx3g^`>0;@Y~DLPcfkLc zJ79NTV0Jq0p7S$b_@Flyzu`H4Xi=Q*GehrL{MJvcuhPX+#$R6T7spe^{zp@&Xz`Ts zm5)Qj@s#nE2v5cFlyTQ3rBt+d%D7|YX5x6tIGx`PaXe*wwq#-zEuJzSII_Puo-)p! z@sT*5#V_jHQYu|MWjrR^8gV>je7SZ^6)m1Je)YAxIG!@@wBWipo-#fWEWb(@PZ@hH zm?Ms-jHi!ws%Y^&?dB$7_eSD)%D8dogW`C~xWTcMt^&56#qZg~f#P_|xc%ja;&{q< zNPrHjgh3_Mdr;KL=KP--?jCajRtD?nI z#(6rA5yw-;hu^*v$5Y0ykJM1<;wj@eja>zdr;OM1O{}8DQ^u>~^%uue#(fst7spe^ zzn#skqQz6dF~Uz4$5X%yL%$ctQ^1?ODyeAk6!7^F>%{RC@S~T}RkU~txMaUB;&=); z^t0c^@f7fiTA5$^!Xk`j32>==msCVM`AI4PKap2tsuk|f@&V85h`=uytF05{J;MvI*@e+9l{Eq^5J7@PN>%Nr? zUwQu7dfjH`KH7In|9B}g$15@GisH|^lC+0Ao!N_TMl-^B5h?u;=<#PO7F_LnJg zn0fG&Zlahi#qpGGMXx>LcuF@+gycERJa|esrQcw2Jd51zdr!sjl$&0H$<)wdCl{9 zO1F8cX5x5C_g$)^;&>LhDgDysGxOjn-6>^87m+NKon|bh*?(A_J z#qpHx^G^x<%=36kclXTh;&@7T$&hp6cuKeX&dl1R<0;)W%O;BBDcz9cK8fR5B$JOx~C;bZYXynVi3-a^{M<0;^-hi8c6Dd1r7K8xci;Ia{`YtxUXfG^$I zB#x(mTdjZIjW#af>-T|+lkS4wPBF?OQ%E!E~ zY$qKPxsN!W0(M3_BaWwlhgR~=V17KF0uGg0wV{VveVP^JX6^j;DZG z{PE50*PX>5@7WxZ7fot>N*&Mhw5C5sn!QZ8TTlb&3#F;ee&Z9NrO&j1}az()W zlL3jT<%Od-5ug7;Bj|Gi{mNanwj5< z<0;^ZH>&CG5KjT`U9wFa&my<+Q)1l_;wj)Wc?XH(Dc}a*o`~Zq;N|Pe=egzMJH|IG#mr zezR&d%=35(IPu!8;&=*pSJR}rBg9j{Wljzi$5X(!+B^`)Q@|Ss=hqz}o&wH#b(T1u z0#0_=sXIbE1)OeuBXK+hym9zpaXbaQ`*mvF5#m|orZ*ifj;DajPI@7Zr-1h?%&xmb zJOvyl>Qr$&1w1kQH*q`#9C=C|-5ug7;6r`(h~p{XQt6V{GhZ(}1$?GXe{nnooGkxc zaXbZ_F>;~$j-WR@1stiuJaIe)d}io3aXbYaZGM#oW*+=hyU9M9Yn3>j0MD+>fcNgbB95nk1B2yhYUaUHz?oCe7ROV-VUs(XndkAW z3p2;Y5Xb+rfCH*e*73sgn0fS`0uFoZsm6E;cuc#zI$pq2z**~#6~|M+1u{Pq$5X&n z!j;wW0-gdsacQ+Uo&uhBGMbJT@Dy;bqaDTZ6mXPF=f&|9@a2lxbi9D4fXD5cB#x(m zBlY3OLJ(_2PI6xMry6dI1rh0^V1)gE*c7_CI!398Up9@Xn$a z5aB7{w_nGJ<0;_4z&GM}3Yf(o@7WxZ7fot>N*&Mhw4}bOM;O|nvOJ+8FxnT5y z$xC{bICNwE(b{Qxk1tv^`?^lPosUnNaVhQ1)Ghrc7dW)L|AAsTBYdxx-N$q{zvs^Z|HJQq-F<=C>9~8&&uou^-dNGh>INu6~?vU^laKSAN z#qktyzHbM`@f7ggIcc?-!?VauYdl&UPXV`H_)Z*80q;6eLz_7~1)QkqPH{X1+&NSd zZRYS4aNec?;&=);X2^TucnbK``@-7H;VIy(_ZExeDd7J1!f4Zmr+`=FZ6S_lk!up& z^{++YDd2fE(rPn@r+~-L8zqjXfb-RG{kdv91zdV}ZEfc86mXd)TgCAdaIqfIv}war zz_*9B7spe;Q`%h+$5X(oo9EC851s=4=r=1QXEeKx4Lvn98UrJ)Xl6D9Xtg*rQSqwJOv!|PxXo8Dc}&Rs_LW% zPXT`)wNV^T0Y|JKQztrj3i$EtPU3h9c+2He;&=+U)$3F`>A_RLPlp7E<0;_%k$)G* zQ@{rs=FmwGo&wHPZL&C?0-o;kQ5;VJSNr0xlO8+;>~(&pIGzG--Xf_^dhir*k~ss# z@f2|1m5;>n6!5sSrF7DRr+{x(SSgODfX|POtdkx*1w4062XQS7XLI1%Ja{$-uFb<=y*c>16mamWut5-%e}nnvtz@kC~eb*+>`Z3y5Ay{$o|9URFyJu z+FoqX?9lu3e$T6{y;bsm6tKHF?`CP&am&N`zke8>bVakQxekP@_$q0lE)l%D^l|iV zl{Y~&cOTQ;{GI~-hZnHBFEBeDch6}B4_X{q|`ix$0bTJOzB+CxkY^cowDc}q@4~gR`;GZAT z_?gcqo&v63ti3p%0v^2PoH(8W-n7(Ln`1l$+%L;iaXbYaKifBPJOv!%b{%bw@f2{a zC_BaR6mb3*3A8!JQ^1{e_7cZaz~{gHE{>;wPiM)llQTR8oT=0paXbY);__p0JO#X~ zMj@S?;VIy9b6qEG_!;)s|Gh!3lQujBJgIUWov7g{-~;jZisNV7d4fOn*2x*30xsEQ zs5t(Q0-pO!98UrFSzJyhXGyH5fcxxQD~_jtyXKCelQTR8TtA?*IGzGtJmrcwo&qj2 zI=4>F@T?1azm*fm|FVF+iiA&Rei09QzdEgXPXU)7Q%@XE0Uz(QRvb?O&zT+4$NYFa z1>9|0eQ`Vm9BS7-aXbZldt-|9W*$5RoaV|x~6 zPa0n$vzZ4^0e88-R2)wM7rz`vi#eVG9z3MEIGzG#@yB~M2d>S7XLI1%JoK@@{#pDz zn}fe*bMSX5;F%p~d>^{y-puV6cLo1E-+6dg{W~`!ckgoX#_Y!j=Y`*#DrTQST>?Wk zzBN2?zXX}?XSlyV?aN1BH{5L>xN+;ZDOpM`ec$y)?u|YEd2#-sr+{1hKkU5)a8z02Z`+MaaCg_>o)9271c)HP5+p!^Gq}6E4DRmk?(PnQ zySqCt``2g9f94fc{a)3bduQr)s=ms%*E;OZ(4qHU`!u1-FmXHuoUFw;aXbb5v}RV_ z)5250YZ?s~$5X(+FM1)4r+{l$s-}Bgcow-Yu{Vk1Dd3`hDRoZ^PXWhm-&Y(@0iTWe zKpam2cU)c}(EPsPDd06Rmx|*l;GD~1>fRNe0vBPXTxTa9bQt z0q^?gse4y=3i!0!RB=27Jm<|faXbZlfc+H8A;&=*p>plMvGY_5u ze$#EKIGzG-963@Wb3L8{eiqn398Uo^t+-1ZPXYU;Nz>TOgQtKmKkg@vr+|y^c_5Cb zfV3<4K>%}Dd3sw+Kb~U;5^wcisLEZ;IJZLW*$5RJZ1Pa zaXbaQzyEh}JnKT!*YTQ~dGNn1;AkI4D9^7g$g9s3@XKkpHO5oGp>y*q!|@bw@tcFi z@f7eux4Yta3V3?DvdVKj1w1U*VsShL{H$e6z2OB<0ViG5QXEeKM|gW&98UpnnwL{= zguzq5bt{b)$5X(0y^2$=Y z&edKQyJs!?zIL+UQrF*XYuMwU+sQJ)XRiLg-v#Y#&OIaA_s_Vx%0Aoe){|}@8N1=f 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ⅇ$JJ}jXkLhUqM-gLd4lu8m|GL>PfG)Hzt3PH;@#~<0ao&xoeem>|Z7;6; z`Yz<;-H?LWU(EjWCQXi%v@?#5qm6NN+`qmP=l^q_0p}TTo&o0>aGn9@8E~Ef=NWLG R0p}TTo&o0>_)naH{{x$sGMfMZ diff --git a/src/discof/tower/fixtures/voters-398915657.bin b/src/discof/tower/fixtures/voters-398915657.bin deleted file mode 100644 index 5ac57576e28852ae436f0d62ba8f909c41b70c63..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 384200 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ࠠ?9GlO~aKm+N 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 diff --git a/src/discof/tower/fixtures/voters-398915658.bin b/src/discof/tower/fixtures/voters-398915658.bin deleted file mode 100644 index 7098b3aa6f7f8dec9e7e7869ea2bec0da47386ab..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 384200 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 diff --git a/src/discof/tower/fixtures/voters-398915659.bin b/src/discof/tower/fixtures/voters-398915659.bin deleted file mode 100644 index 4d26255acc534f8d9d94022ce68634f0415a18d5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 384200 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 b3^>n#^9(r8fb$GE&w%p`IM2ZU#u@lOe4kZI diff --git a/src/discof/tower/fixtures/voters-398915660.bin b/src/discof/tower/fixtures/voters-398915660.bin deleted file mode 100644 index 42377fc4180bf4d97ae4346ec38e61c13d34b1a3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 384200 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 $0skzqT^vsV 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 z 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 $0sknz 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 x66Q9Ki)WLR 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

    =akQ7oK(vpAk&&Xh8a$`4O5|IFT598WP< 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𝔚59Sg=ci}d2a+wsg>3b<;u`QmsA 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 $0T27OQ5;VJPfQR? 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 Tfcp%%&w%?3xX-|U;tc$Mq3eAi diff --git a/src/discof/tower/fixtures/voters-398915664.bin b/src/discof/tower/fixtures/voters-398915664.bin deleted file mode 100644 index d9138156dca134cd04993a4cd9e9d4bb09c6e7a9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 384200 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 A!vFvP diff --git a/src/discof/tower/fixtures/voters-398915665.bin b/src/discof/tower/fixtures/voters-398915665.bin deleted file mode 100644 index dfded4deb05d2a170fbb3c25a14bf66b24312f47..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 384200 zcmeFab##xK?h@SHHMj=?!2*E{=Z7{fSunwh z9cl86t-ZF!lwD6w_i4Xl^}aqIj@2r;_i)QRWx8C7S^nsRFjY4djdrYj(!gBZJUrG% z_0U_WpZ~wu#hkmMN3EQ_YXdH7uK8yJ969VDi5cXygT!W#%MKEmL2f%pXa;%gAb}a=wS)L( zkk1a{nL&O#Fc0P^Uim zp059Rn`C*XFdYQ(lPzzZ=d3t>ishLbWHKMW2Y#yM$6F2($4|37(u*77`01AWH^^%~ zDIWM4mXDe=Q5-+h^0)2Yh~sBjzAt}}{s}+Z@&=w;^iTLXmNz_-z&uSJ__>ys^zAK< zpJ#cR2DioW^DY0aR&n#(d*ByXzN_UDar{EdD;AAlzHmJ7i!9F(94wAs4DWGD9M9Ls z^tRc|SEEM`>-l=vd!x8Go-ekG(@r)w*Sp``p8@x0!2KC;e+Jy20rzLX{TXn72Hc+k zkCQLoN0`+=#oqo$C*Q01Jgo2Xt8MbU&r<0 zpIZEJ=iIs4M@rJBLZ*yiLl*U_w|{8pWjX�lS*hm3Q@+uKv8cfSs-XuPtC#PcX&f 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

    xK+w{Nh`n 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 Sp8@w7aGwG98Tfacf&T@Gk&d_k diff --git a/src/discof/tower/generated/fd_tower_tile_seccomp.h b/src/discof/tower/generated/fd_tower_tile_seccomp.h deleted file mode 100644 index 1b9ee7346bb..00000000000 --- a/src/discof/tower/generated/fd_tower_tile_seccomp.h +++ /dev/null @@ -1,196 +0,0 @@ -/* THIS FILE WAS GENERATED BY generate_filters.py. DO NOT EDIT BY HAND! */ -#ifndef HEADER_fd_src_discof_tower_generated_fd_tower_tile_seccomp_h -#define HEADER_fd_src_discof_tower_generated_fd_tower_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 -#elif defined(__riscv) && __riscv_xlen == 64 -# define ARCH_NR AUDIT_ARCH_RISCV64 -#else -# error "Target architecture is unsupported by seccomp." -#endif - -#define FD_SECCOMP_ARG_LO_OFFSET(argno) ( offsetof( struct seccomp_data, args[(argno)] ) ) -#define FD_SECCOMP_ARG_HI_OFFSET(argno) ( offsetof( struct seccomp_data, args[(argno)] ) + 4U ) - -#define FD_SECCOMP_ARG_LO(x) ((uint)(((ulong)(uint)(int)(x) ) & 0xffffffffUL)) -#define FD_SECCOMP_ARG_HI(x) ((uint)(((ulong)(x) >> 32) & 0xffffffffUL)) - -static const uint sock_filter_policy_fd_tower_tile_instr_cnt = 74; - -static void populate_sock_filter_policy_fd_tower_tile( ulong out_cnt, struct sock_filter out[ static 74 ], uint logfile_fd, uint dir_fd, uint tower_fd_0, uint tower_fd_1, uint accounts_fd, uint vote_stakes_fd ) { - FD_TEST( out_cnt >= 74 ); - struct sock_filter filter[74] = { - /* validate architecture */ - 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 */ 12 ), - /* load syscall number */ - BPF_STMT( BPF_LD | BPF_W | BPF_ABS, ( offsetof( struct seccomp_data, nr ) )), - /* check write */ - BPF_JUMP( BPF_JMP | BPF_JEQ | BPF_K, SYS_write, /* check_write */ 12, 0 ), - /* check fsync */ - BPF_JUMP( BPF_JMP | BPF_JEQ | BPF_K, SYS_fsync, /* check_fsync */ 17, 0 ), - /* check renameat2 */ - BPF_JUMP( BPF_JMP | BPF_JEQ | BPF_K, SYS_renameat2, /* check_renameat2 */ 20, 0 ), - /* check pwritev2 */ - BPF_JUMP( BPF_JMP | BPF_JEQ | BPF_K, SYS_pwritev2, /* check_pwritev2 */ 29, 0 ), - /* check ftruncate */ - BPF_JUMP( BPF_JMP | BPF_JEQ | BPF_K, SYS_ftruncate, /* check_ftruncate */ 36, 0 ), - /* check preadv2 */ - BPF_JUMP( BPF_JMP | BPF_JEQ | BPF_K, SYS_preadv2, /* check_preadv2 */ 41, 0 ), - /* check fallocate */ - BPF_JUMP( BPF_JMP | BPF_JEQ | BPF_K, SYS_fallocate, /* check_fallocate */ 44, 0 ), - /* check pread64 */ - BPF_JUMP( BPF_JMP | BPF_JEQ | BPF_K, SYS_pread64, /* check_pread64 */ 49, 0 ), - /* check pwrite64 */ - BPF_JUMP( BPF_JMP | BPF_JEQ | BPF_K, SYS_pwrite64, /* check_pwrite64 */ 52, 0 ), - /* check futex */ - BPF_JUMP( BPF_JMP | BPF_JEQ | BPF_K, SYS_futex, /* check_futex */ 55, 0 ), - /* allow restart_syscall */ - BPF_JUMP( BPF_JMP | BPF_JEQ | BPF_K, SYS_restart_syscall, /* RET_ALLOW */ 1, 0 ), -// RET_KILL_PROCESS: - /* default deny */ - BPF_STMT( BPF_RET | BPF_K, SECCOMP_RET_KILL_PROCESS ), -// RET_ALLOW: - /* allow */ - BPF_STMT( BPF_RET | BPF_K, SECCOMP_RET_ALLOW ), -// check_write: - /* arg 0 low 32 bits */ - BPF_STMT( BPF_LD | BPF_W | BPF_ABS, FD_SECCOMP_ARG_LO_OFFSET(0)), - BPF_JUMP( BPF_JMP | BPF_JEQ | BPF_K, 0x00000002U, /* write_ALLOW */ 3, /* or_1 */ 0 ), -// or_1: - /* arg 0 low 32 bits */ - BPF_STMT( BPF_LD | BPF_W | BPF_ABS, FD_SECCOMP_ARG_LO_OFFSET(0)), - BPF_JUMP( BPF_JMP | BPF_JEQ | BPF_K, ((uint)(logfile_fd)), /* write_ALLOW */ 1, /* write_KILL */ 0 ), -// write_KILL: - BPF_STMT( BPF_RET | BPF_K, SECCOMP_RET_KILL_PROCESS ), -// write_ALLOW: - BPF_STMT( BPF_RET | BPF_K, SECCOMP_RET_ALLOW ), -// check_fsync: - /* arg 0 low 32 bits */ - BPF_STMT( BPF_LD | BPF_W | BPF_ABS, FD_SECCOMP_ARG_LO_OFFSET(0)), - BPF_JUMP( BPF_JMP | BPF_JEQ | BPF_K, ((uint)(logfile_fd)), /* fsync_ALLOW */ 1, /* fsync_KILL */ 0 ), -// fsync_KILL: - BPF_STMT( BPF_RET | BPF_K, SECCOMP_RET_KILL_PROCESS ), -// fsync_ALLOW: - BPF_STMT( BPF_RET | BPF_K, SECCOMP_RET_ALLOW ), -// check_renameat2: - /* arg 0 low 32 bits */ - BPF_STMT( BPF_LD | BPF_W | BPF_ABS, FD_SECCOMP_ARG_LO_OFFSET(0)), - BPF_JUMP( BPF_JMP | BPF_JEQ | BPF_K, ((uint)(dir_fd)), /* and_2 */ 0, /* renameat2_KILL */ 6 ), -// and_2: - /* arg 2 low 32 bits */ - BPF_STMT( BPF_LD | BPF_W | BPF_ABS, FD_SECCOMP_ARG_LO_OFFSET(2)), - BPF_JUMP( BPF_JMP | BPF_JEQ | BPF_K, ((uint)(dir_fd)), /* and_3 */ 0, /* renameat2_KILL */ 4 ), -// and_3: - /* arg 4 low 32 bits */ - BPF_STMT( BPF_LD | BPF_W | BPF_ABS, FD_SECCOMP_ARG_LO_OFFSET(4)), - BPF_JUMP( BPF_JMP | BPF_JEQ | BPF_K, 0x00000001U, /* renameat2_ALLOW */ 3, /* or_4 */ 0 ), -// or_4: - /* arg 4 low 32 bits */ - BPF_STMT( BPF_LD | BPF_W | BPF_ABS, FD_SECCOMP_ARG_LO_OFFSET(4)), - BPF_JUMP( BPF_JMP | BPF_JEQ | BPF_K, 0x00000002U, /* renameat2_ALLOW */ 1, /* renameat2_KILL */ 0 ), -// renameat2_KILL: - BPF_STMT( BPF_RET | BPF_K, SECCOMP_RET_KILL_PROCESS ), -// renameat2_ALLOW: - BPF_STMT( BPF_RET | BPF_K, SECCOMP_RET_ALLOW ), -// check_pwritev2: - /* arg 0 low 32 bits */ - BPF_STMT( BPF_LD | BPF_W | BPF_ABS, FD_SECCOMP_ARG_LO_OFFSET(0)), - BPF_JUMP( BPF_JMP | BPF_JEQ | BPF_K, ((uint)(accounts_fd)), /* pwritev2_ALLOW */ 5, /* or_5 */ 0 ), -// or_5: - /* arg 0 low 32 bits */ - BPF_STMT( BPF_LD | BPF_W | BPF_ABS, FD_SECCOMP_ARG_LO_OFFSET(0)), - BPF_JUMP( BPF_JMP | BPF_JEQ | BPF_K, ((uint)(tower_fd_0)), /* pwritev2_ALLOW */ 3, /* or_6 */ 0 ), -// or_6: - /* arg 0 low 32 bits */ - BPF_STMT( BPF_LD | BPF_W | BPF_ABS, FD_SECCOMP_ARG_LO_OFFSET(0)), - BPF_JUMP( BPF_JMP | BPF_JEQ | BPF_K, ((uint)(tower_fd_1)), /* pwritev2_ALLOW */ 1, /* pwritev2_KILL */ 0 ), -// pwritev2_KILL: - BPF_STMT( BPF_RET | BPF_K, SECCOMP_RET_KILL_PROCESS ), -// pwritev2_ALLOW: - BPF_STMT( BPF_RET | BPF_K, SECCOMP_RET_ALLOW ), -// check_ftruncate: - /* arg 0 low 32 bits */ - BPF_STMT( BPF_LD | BPF_W | BPF_ABS, FD_SECCOMP_ARG_LO_OFFSET(0)), - BPF_JUMP( BPF_JMP | BPF_JEQ | BPF_K, ((uint)(tower_fd_0)), /* ftruncate_ALLOW */ 3, /* or_7 */ 0 ), -// or_7: - /* arg 0 low 32 bits */ - BPF_STMT( BPF_LD | BPF_W | BPF_ABS, FD_SECCOMP_ARG_LO_OFFSET(0)), - BPF_JUMP( BPF_JMP | BPF_JEQ | BPF_K, ((uint)(tower_fd_1)), /* ftruncate_ALLOW */ 1, /* ftruncate_KILL */ 0 ), -// ftruncate_KILL: - BPF_STMT( BPF_RET | BPF_K, SECCOMP_RET_KILL_PROCESS ), -// ftruncate_ALLOW: - BPF_STMT( BPF_RET | BPF_K, SECCOMP_RET_ALLOW ), -// check_preadv2: - /* arg 0 low 32 bits */ - BPF_STMT( BPF_LD | BPF_W | BPF_ABS, FD_SECCOMP_ARG_LO_OFFSET(0)), - BPF_JUMP( BPF_JMP | BPF_JEQ | BPF_K, ((uint)(accounts_fd)), /* preadv2_ALLOW */ 1, /* preadv2_KILL */ 0 ), -// preadv2_KILL: - BPF_STMT( BPF_RET | BPF_K, SECCOMP_RET_KILL_PROCESS ), -// preadv2_ALLOW: - BPF_STMT( BPF_RET | BPF_K, SECCOMP_RET_ALLOW ), -// check_fallocate: - /* arg 0 low 32 bits */ - BPF_STMT( BPF_LD | BPF_W | BPF_ABS, FD_SECCOMP_ARG_LO_OFFSET(0)), - BPF_JUMP( BPF_JMP | BPF_JEQ | BPF_K, ((uint)(accounts_fd)), /* and_8 */ 0, /* fallocate_KILL */ 2 ), -// and_8: - /* arg 1 low 32 bits */ - BPF_STMT( BPF_LD | BPF_W | BPF_ABS, FD_SECCOMP_ARG_LO_OFFSET(1)), - BPF_JUMP( BPF_JMP | BPF_JEQ | BPF_K, 0x00000000U, /* fallocate_ALLOW */ 1, /* fallocate_KILL */ 0 ), -// fallocate_KILL: - BPF_STMT( BPF_RET | BPF_K, SECCOMP_RET_KILL_PROCESS ), -// fallocate_ALLOW: - BPF_STMT( BPF_RET | BPF_K, SECCOMP_RET_ALLOW ), -// check_pread64: - /* arg 0 low 32 bits */ - BPF_STMT( BPF_LD | BPF_W | BPF_ABS, FD_SECCOMP_ARG_LO_OFFSET(0)), - BPF_JUMP( BPF_JMP | BPF_JEQ | BPF_K, ((uint)(vote_stakes_fd)), /* pread64_ALLOW */ 1, /* pread64_KILL */ 0 ), -// pread64_KILL: - BPF_STMT( BPF_RET | BPF_K, SECCOMP_RET_KILL_PROCESS ), -// pread64_ALLOW: - BPF_STMT( BPF_RET | BPF_K, SECCOMP_RET_ALLOW ), -// check_pwrite64: - /* arg 0 low 32 bits */ - BPF_STMT( BPF_LD | BPF_W | BPF_ABS, FD_SECCOMP_ARG_LO_OFFSET(0)), - BPF_JUMP( BPF_JMP | BPF_JEQ | BPF_K, ((uint)(vote_stakes_fd)), /* pwrite64_ALLOW */ 1, /* pwrite64_KILL */ 0 ), -// pwrite64_KILL: - BPF_STMT( BPF_RET | BPF_K, SECCOMP_RET_KILL_PROCESS ), -// pwrite64_ALLOW: - BPF_STMT( BPF_RET | BPF_K, SECCOMP_RET_ALLOW ), -// check_futex: - /* arg 1 low 32 bits */ - BPF_STMT( BPF_LD | BPF_W | BPF_ABS, FD_SECCOMP_ARG_LO_OFFSET(1)), - BPF_JUMP( BPF_JMP | BPF_JEQ | BPF_K, FD_SECCOMP_ARG_LO(FUTEX_WAIT_BITSET), /* and_9 */ 0, /* futex_KILL */ 2 ), -// and_9: - /* arg 2 low 32 bits */ - BPF_STMT( BPF_LD | BPF_W | BPF_ABS, FD_SECCOMP_ARG_LO_OFFSET(2)), - BPF_JUMP( BPF_JMP | BPF_JEQ | BPF_K, 0x00000000U, /* futex_ALLOW */ 1, /* futex_KILL */ 0 ), -// futex_KILL: - BPF_STMT( BPF_RET | BPF_K, SECCOMP_RET_KILL_PROCESS ), -// futex_ALLOW: - BPF_STMT( BPF_RET | BPF_K, SECCOMP_RET_ALLOW ), - }; - fd_memcpy( out, filter, sizeof( filter ) ); -} - -#endif /* defined(__linux__) */ - -#endif /* HEADER_fd_src_discof_tower_generated_fd_tower_tile_seccomp_h */ diff --git a/src/discof/tower/test_tower_tile.c b/src/discof/tower/test_tower_tile.c deleted file mode 100644 index 2539c10eedb..00000000000 --- a/src/discof/tower/test_tower_tile.c +++ /dev/null @@ -1,1410 +0,0 @@ -#define _GNU_SOURCE -#define QUERY_TOWERS mock_query_towers -#define QUERY_VOTERS mock_query_voters - -#include "fd_tower_tile.c" -#include "../../disco/topo/fd_topob.h" - -void -mock_query_voters( fd_tower_tile_t * ctx, - fd_replay_slot_completed_t * slot_completed FD_PARAM_UNUSED, - ulong epoch ) { - ctx->root_epoch = epoch; -} - -#include -#include -#include -#include -#include -#include -#include - -/* mock_vote_txn builds a vote transaction from a tower. Constructs an - fd_tower_t with the given (slot, conf) pairs, serializes it via - fd_tower_to_vote_txn, and returns the parsed fd_txn_t and payload. - - slots and confs are arrays of length cnt. block_id controls the - block_id in the serde (null block_id causes count_vote_txn to exit at - the hash_null check after tower validation). */ - -static fd_txn_t const * -mock_vote_txn( ulong root, - ulong cnt, - ulong const * slots, - ulong const * confs, - fd_hash_t const * block_id, - fd_txn_p_t * txnp, - uchar txn_out[ static FD_TXN_MAX_SZ ] ) { - - static uchar tower_mem[ 65536 ] __attribute__((aligned(128))); - fd_tower_t * tower = fd_tower_join( fd_tower_new( tower_mem, 2, 2, 0 ) ); - - for( ulong i = 0; i < cnt; i++ ) { - fd_tower_vote_push_tail( tower->votes, (fd_tower_vote_t){ .slot = slots[i], .conf = confs[i] } ); - } - tower->root = root; - - fd_hash_t bank_hash = { .ul = { 0xAA } }; - fd_hash_t recent_blockhash = {0}; - fd_pubkey_t validator_identity = { .ul = { 0x11 } }; - fd_pubkey_t vote_acc = { .ul = { 0x22 } }; - - fd_tower_to_vote_txn( tower, &bank_hash, block_id, &recent_blockhash, &validator_identity, &validator_identity, &vote_acc, txnp ); - FD_TEST( txnp->payload_sz && txnp->payload_sz<=FD_TPU_MTU ); - - FD_TEST( fd_txn_parse_core( txnp->payload, txnp->payload_sz, txn_out, NULL, NULL ) ); - return (fd_txn_t const *)txn_out; -} - -static ulong -mock_vote_account( fd_pubkey_t const * node_pubkey, - fd_pubkey_t const * authorized_voter, - uchar vote_state_data[ static FD_VOTE_STATE_DATA_MAX ] ) { - fd_vote_state_versioned_t versioned[1]; - FD_TEST( fd_vote_state_versioned_new( versioned, fd_vote_state_versioned_enum_v3 ) ); - fd_memset( vote_state_data, 0, FD_VOTE_STATE_DATA_MAX ); - - fd_vote_state_v3_t * vote_state = &versioned->v3; - vote_state->node_pubkey = *node_pubkey; - vote_state->authorized_withdrawer = *authorized_voter; - vote_state->commission = 100; - vote_state->prior_voters.idx = 31; - vote_state->prior_voters.is_empty = 1; - - fd_vote_authorized_voter_t * voter = fd_vote_authorized_voters_pool_ele_acquire( vote_state->authorized_voters.pool ); - fd_memset( voter, 0, sizeof(fd_vote_authorized_voter_t) ); - voter->epoch = 0UL; - voter->pubkey = *authorized_voter; - voter->prio = authorized_voter->uc[0]; - fd_vote_authorized_voters_treap_ele_insert( vote_state->authorized_voters.treap, voter, vote_state->authorized_voters.pool ); - - FD_TEST( !fd_vote_state_versioned_serialize( versioned, vote_state_data, FD_VOTE_STATE_DATA_MAX ) ); - return FD_VOTE_STATE_DATA_MAX; -} - -static void -test_publish_slot_done_identity_mismatch( void ) { - static fd_tower_tile_t ctx[1]; - static uchar tower_mem[ 65536 ] __attribute__((aligned(128))); - static uchar publishes_mem[ 65536 ] __attribute__((aligned(128))); - - memset( ctx, 0, sizeof(*ctx) ); - memset( ctx->identity_key, 0x11, sizeof(fd_pubkey_t) ); - memset( ctx->vote_account, 0x22, sizeof(fd_pubkey_t) ); - - fd_wksp_t * wksp = fd_wksp_new_anonymous( FD_SHMEM_NORMAL_PAGE_SZ, 4096UL, 0UL, "tower_id_test", 0UL ); - FD_TEST( wksp ); - - void * ghost_mem = fd_wksp_alloc_laddr( wksp, fd_ghost_align(), fd_ghost_footprint( 2UL, 2UL ), 1UL ); - FD_TEST( ghost_mem ); - - ctx->tower = fd_tower_join( fd_tower_new( tower_mem, 2UL, 2UL, 0UL ) ); - ctx->ghost = fd_ghost_join( fd_ghost_new( ghost_mem, 2UL, 2UL, 0UL ) ); - ctx->publishes = publishes_join( publishes_new( publishes_mem, 2UL ) ); - FD_TEST( ctx->tower ); - FD_TEST( ctx->ghost ); - FD_TEST( ctx->publishes ); - - ctx->tower->root = 0UL; - fd_tower_blk_t * parent_blk = fd_tower_blocks_insert( ctx->tower, 0UL, ULONG_MAX ); - FD_TEST( parent_blk ); - parent_blk->block_hash = (fd_hash_t){ .ul = { 0x66UL } }; - fd_tower_vote_push_tail( ctx->tower->votes, (fd_tower_vote_t){ .slot = 1UL, .conf = 1UL } ); - - fd_replay_slot_completed_t sc; - memset( &sc, 0, sizeof(sc) ); - sc.slot = 1UL; - sc.parent_slot = 0UL; - sc.epoch = 0UL; - sc.bank_idx = 123UL; - - fd_tower_out_t out; - memset( &out, 0, sizeof(out) ); - out.vote_slot = 1UL; - out.vote_block_id = (fd_hash_t){ .ul = { 0x33UL } }; - out.vote_bank_hash = (fd_hash_t){ .ul = { 0x44UL } }; - out.reset_slot = ULONG_MAX; - out.root_slot = ULONG_MAX; - - /* Matching identity produces votes */ - ctx->our_vote_acct_sz = mock_vote_account( ctx->identity_key, ctx->identity_key, ctx->our_vote_acct ); - publish_slot_done( ctx, &sc, &out, 1, 100UL, 10000U, 0UL, NULL ); - publish_t * pub = publishes_peek_head( ctx->publishes ); - FD_TEST( pub ); - FD_TEST( pub->sig==FD_TOWER_SIG_SLOT_DONE ); - FD_TEST( pub->msg.slot_done.has_vote_txn==1 ); - FD_TEST( pub->msg.slot_done.is_voting==1 ); - FD_TEST( pub->msg.slot_done.authority_idx==ULONG_MAX ); - FD_TEST( pub->msg.slot_done.vote_acct_com==10000U ); - publishes_pop_head_nocopy( ctx->publishes ); - - /* Matching identity but no votable slot: voter with no vote txn */ - fd_tower_out_t out_no_vote = out; - out_no_vote.vote_slot = ULONG_MAX; - publish_slot_done( ctx, &sc, &out_no_vote, 1, 100UL, 10000U, 0UL, NULL ); - pub = publishes_peek_head( ctx->publishes ); - FD_TEST( pub ); - FD_TEST( pub->sig==FD_TOWER_SIG_SLOT_DONE ); - FD_TEST( pub->msg.slot_done.has_vote_txn==0 ); - FD_TEST( pub->msg.slot_done.is_voting==1 ); - publishes_pop_head_nocopy( ctx->publishes ); - - /* Other identity prevents vote publishing */ - fd_pubkey_t other_identity = { .ul = { 0x99UL } }; - ctx->our_vote_acct_sz = mock_vote_account( &other_identity, ctx->identity_key, ctx->our_vote_acct ); - publish_slot_done( ctx, &sc, &out, 1, 100UL, 10000U, 0UL, NULL ); - pub = publishes_peek_head( ctx->publishes ); - FD_TEST( pub ); - FD_TEST( pub->sig==FD_TOWER_SIG_SLOT_DONE ); - FD_TEST( pub->msg.slot_done.has_vote_txn==0 ); - FD_TEST( pub->msg.slot_done.is_voting==0 ); - - fd_wksp_delete( fd_wksp_leave( wksp ) ); - - FD_LOG_NOTICE(( "pass: test_publish_slot_done_identity_mismatch" )); -} - -static void -test_identity_switch_waits_for_replay( void ) { - static fd_tower_tile_t ctx[1]; - static fd_keyswitch_t identity_keyswitch[1]; - static fd_keyswitch_t auth_vtr_keyswitch[1]; - static uchar tower_mem [ 65536 ] __attribute__((aligned(128))); - static uchar publishes_mem[ 65536 ] __attribute__((aligned(128))); - - fd_pubkey_t old_identity = { .ul = { 0x11UL } }; - fd_pubkey_t new_identity = { .ul = { 0x22UL } }; - - memset( ctx, 0, sizeof(*ctx) ); - memset( identity_keyswitch, 0, sizeof(*identity_keyswitch) ); - memset( auth_vtr_keyswitch, 0, sizeof(*auth_vtr_keyswitch) ); - memset( publishes_mem, 0, sizeof(publishes_mem) ); - - ctx->identity_key [0] = old_identity; - ctx->identity_keyswitch = identity_keyswitch; - ctx->auth_vtr_keyswitch = auth_vtr_keyswitch; - ctx->tower = fd_tower_join( fd_tower_new( tower_mem, 2UL, 2UL, 0UL ) ); - ctx->publishes = publishes_join( publishes_new( publishes_mem, 2UL ) ); - identity_keyswitch->param = 1UL; - identity_keyswitch->result = ULONG_MAX; - memcpy( identity_keyswitch->bytes, &new_identity, sizeof(new_identity) ); - fd_keyswitch_state( identity_keyswitch, FD_KEYSWITCH_STATE_SWITCH_PENDING ); - - during_housekeeping( ctx ); - - FD_TEST( fd_keyswitch_state_query( identity_keyswitch )==FD_KEYSWITCH_STATE_SWITCH_PENDING ); - FD_TEST( !ctx->halt_signing ); - FD_TEST( fd_pubkey_eq( ctx->identity_key, &old_identity ) ); - FD_TEST( identity_keyswitch->result==ULONG_MAX ); - - ctx->in_kind[ 0 ] = IN_KIND_REPLAY; - FD_TEST( before_frag( ctx, 0UL, 0UL, REPLAY_SIG_RESET ) ); - FD_TEST( ctx->replay_in_seq==1UL ); - - during_housekeeping( ctx ); - FD_TEST( fd_keyswitch_state_query( identity_keyswitch )==FD_KEYSWITCH_STATE_COMPLETED ); - FD_TEST( ctx->halt_signing ); - FD_TEST( fd_pubkey_eq( ctx->identity_key, &new_identity ) ); - - FD_LOG_NOTICE(( "pass: test_identity_switch_waits_for_replay" )); -} - -static void -test_halt_signing_backpressures_replay( void ) { - static fd_tower_tile_t ctx[1]; - memset( ctx, 0, sizeof(*ctx) ); - - ctx->halt_signing = 1; - ctx->replay_in_seq = 7UL; - ctx->in_kind[ 0 ] = IN_KIND_REPLAY; - ctx->in[ 0 ].mcache_only = 1; - - /* Authorized-voter removal has no Replay barrier and relies on this - fragment remaining unconsumed until signing is unhalted. */ - FD_TEST( returnable_frag( ctx, 0UL, 7UL, REPLAY_SIG_SLOT_COMPLETED, - 0UL, 0UL, 0UL, 0UL, 0UL, NULL )==1 ); - FD_TEST( ctx->replay_in_seq==7UL ); - - FD_LOG_NOTICE(( "pass: test_halt_signing_backpressures_replay" )); -} - -static void -test_identity_switch_waits_for_publishes( void ) { - static fd_tower_tile_t ctx[1]; - static fd_keyswitch_t identity_keyswitch[1]; - static fd_keyswitch_t auth_vtr_keyswitch[1]; - static uchar tower_mem [ 65536 ] __attribute__((aligned(128))); - static uchar publishes_mem[ 65536 ] __attribute__((aligned(128))); - - fd_pubkey_t old_identity = { .ul = { 0x11UL } }; - fd_pubkey_t new_identity = { .ul = { 0x22UL } }; - - memset( ctx, 0, sizeof(*ctx) ); - memset( identity_keyswitch, 0, sizeof(*identity_keyswitch) ); - memset( auth_vtr_keyswitch, 0, sizeof(*auth_vtr_keyswitch) ); - memset( publishes_mem, 0, sizeof(publishes_mem) ); - - ctx->identity_key [0] = old_identity; - ctx->identity_keyswitch = identity_keyswitch; - ctx->auth_vtr_keyswitch = auth_vtr_keyswitch; - ctx->tower = fd_tower_join( fd_tower_new( tower_mem, 2UL, 2UL, 0UL ) ); - ctx->publishes = publishes_join( publishes_new( publishes_mem, 2UL ) ); - ctx->out_seq = 42UL; - identity_keyswitch->result = ULONG_MAX; - memcpy( identity_keyswitch->bytes, &new_identity, sizeof(new_identity) ); - fd_keyswitch_state( identity_keyswitch, FD_KEYSWITCH_STATE_SWITCH_PENDING ); - - publishes_push_head( ctx->publishes, (publish_t){ .sig = FD_TOWER_SIG_SLOT_DONE } ); - during_housekeeping( ctx ); - - FD_TEST( ctx->halt_signing ); - FD_TEST( fd_keyswitch_state_query( identity_keyswitch )==FD_KEYSWITCH_STATE_SWITCH_PENDING ); - FD_TEST( fd_pubkey_eq( ctx->identity_key, &old_identity ) ); - FD_TEST( identity_keyswitch->result==ULONG_MAX ); - - publishes_pop_head_nocopy( ctx->publishes ); - during_housekeeping( ctx ); - - FD_TEST( fd_keyswitch_state_query( identity_keyswitch )==FD_KEYSWITCH_STATE_COMPLETED ); - FD_TEST( fd_pubkey_eq( ctx->identity_key, &new_identity ) ); - FD_TEST( identity_keyswitch->result==42UL ); - - FD_LOG_NOTICE(( "pass: test_identity_switch_waits_for_publishes" )); -} - -/* Banks with a single root bank at index 0, whose sysvar cache is - empty. */ - -static fd_banks_t * -test_banks( void ) { - static uchar mem[ 1UL<<25 ] __attribute__((aligned(FD_BANKS_ALIGN))); - static fd_banks_t * banks; - if( FD_LIKELY( banks ) ) return banks; - FD_TEST( fd_banks_footprint( 1UL, 8UL, 8UL, 8UL )<=sizeof(mem) ); - banks = fd_banks_join( fd_banks_new( mem, FD_STAKE_DELEGATIONS_FD, 1UL, 8UL, 8UL, 128UL, 8UL, 0UL, 42UL ) ); - FD_TEST( banks ); - FD_TEST( fd_banks_init_bank( banks )->idx==0UL ); - return banks; -} - -/* Blocks 100 <- 101 <- 102 <- 104 <- 105 (slot 103 skipped), root 100, - and our own unsent votes for 101 and 105. */ - -static fd_tower_tile_t * -adopt_setup( void ) { - static fd_tower_tile_t ctx[1]; - static fd_keyswitch_t identity_keyswitch[1]; - static fd_keyswitch_t auth_vtr_keyswitch[1]; - static uchar tower_mem [ 1UL<<20 ] __attribute__((aligned(128))); - static uchar scratch_mem [ FD_TOWER_VOTE_FOOTPRINT ] __attribute__((aligned(FD_TOWER_VOTE_ALIGN))); - static uchar publishes_mem[ 65536 ] __attribute__((aligned(128))); - static void * ghost_mem; - - if( FD_UNLIKELY( !ghost_mem ) ) { - fd_wksp_t * wksp = fd_wksp_new_anonymous( FD_SHMEM_NORMAL_PAGE_SZ, 64UL, 0UL, "adopt_test", 0UL ); - FD_TEST( wksp ); - ghost_mem = fd_wksp_alloc_laddr( wksp, fd_ghost_align(), fd_ghost_footprint( 8UL, 2UL ), 1UL ); - FD_TEST( ghost_mem ); - } - - memset( ctx, 0, sizeof(*ctx) ); - memset( identity_keyswitch, 0, sizeof(*identity_keyswitch) ); - memset( auth_vtr_keyswitch, 0, sizeof(*auth_vtr_keyswitch) ); - ctx->identity_keyswitch = identity_keyswitch; - ctx->auth_vtr_keyswitch = auth_vtr_keyswitch; - ctx->banks = test_banks(); - ctx->tower = fd_tower_join( fd_tower_new( tower_mem, 32UL, 1UL, 0UL ) ); - ctx->scratch_tower = fd_tower_vote_join( fd_tower_vote_new( scratch_mem ) ); - ctx->publishes = publishes_join( publishes_new( publishes_mem, 2UL ) ); - ctx->ghost = fd_ghost_join( fd_ghost_new( ghost_mem, 8UL, 2UL, 0UL ) ); - FD_TEST( ctx->ghost ); - - ulong slots [ 5 ] = { 100UL, 101UL, 102UL, 104UL, 105UL }; - ulong parents[ 5 ] = { ULONG_MAX, 100UL, 101UL, 102UL, 104UL }; - for( ulong i=0UL; i<5UL; i++ ) { - fd_tower_blk_t * blk = fd_tower_blocks_insert( ctx->tower, slots[ i ], parents[ i ] ); - blk->replayed = 1; - blk->replayed_block_id = (fd_hash_t){ .ul = { slots[ i ] } }; - blk->bank_hash = (fd_hash_t){ .ul = { slots[ i ]+1000UL } }; - if( !i ) fd_ghost_init ( ctx->ghost, slots[ i ], slots[ i ], &blk->replayed_block_id ); - else fd_ghost_insert( ctx->ghost, slots[ i ], slots[ i ], &blk->replayed_block_id, &(fd_hash_t){ .ul = { parents[ i ] } } ); - } - ctx->tower->root = 100UL; - fd_tower_vote_push_tail( ctx->tower->votes, (fd_tower_vote_t){ .slot = 101UL, .conf = 2UL } ); - fd_tower_vote_push_tail( ctx->tower->votes, (fd_tower_vote_t){ .slot = 105UL, .conf = 1UL } ); - fd_tower_blocks_query( ctx->tower, 101UL )->voted = 1; - fd_tower_blocks_query( ctx->tower, 105UL )->voted = 1; - return ctx; -} - -static int -adopt_tower_is( fd_tower_tile_t const * ctx, - ulong root, - ulong slot0, - ulong slot1 ) { - return ctx->tower->root==root && fd_tower_vote_cnt( ctx->tower->votes )==2UL && - fd_tower_vote_peek_index_const( ctx->tower->votes, 0UL )->slot==slot0 && - fd_tower_vote_peek_index_const( ctx->tower->votes, 1UL )->slot==slot1; -} - -static int -adopt_tower_cleared( fd_tower_tile_t const * ctx ) { - return ctx->tower->root==100UL && fd_tower_vote_empty( ctx->tower->votes ) && !fd_tower_blocks_query( ctx->tower, 105UL )->voted; -} - -static void -adopt_switch( fd_tower_tile_t * ctx, - ulong identity, - fd_tower_file_t const * file ) { - fd_pubkey_t key = { .ul = { identity } }; - memcpy( ctx->identity_keyswitch->bytes, &key, sizeof(key) ); - FD_STORE( ulong, ctx->identity_keyswitch->bytes+32UL, !!file ); - if( file ) memcpy( ctx->identity_keyswitch->bytes+40UL, file, sizeof(fd_tower_file_t) ); - fd_keyswitch_state( ctx->identity_keyswitch, FD_KEYSWITCH_STATE_SWITCH_PENDING ); - during_housekeeping( ctx ); - FD_TEST( fd_keyswitch_state_query( ctx->identity_keyswitch )==FD_KEYSWITCH_STATE_COMPLETED ); -} - -static void -test_identity_switch_adopts_vote_history( void ) { - fd_tower_file_t history = { .votes = {{ 102UL, 2UL }, { 104UL, 1UL }}, .votes_cnt = 2UL, .root = 101UL, .bank_hash = { .ul = { 1104UL } } }; - - /* Switching to a new identity with a history clears our tower, and the - next replayed slot adopts the history in place of our newer unsent - votes. */ - - fd_tower_tile_t * ctx = adopt_setup(); - adopt_switch( ctx, 0x22UL, &history ); - FD_TEST( ctx->vote_history_pending && !memcmp( &ctx->vote_history, &history, sizeof(history) ) ); - FD_TEST( adopt_tower_cleared( ctx ) ); - check_vote_history( ctx, &(fd_replay_slot_completed_t){ .slot = 105UL } ); - FD_TEST( !ctx->vote_history_pending && !ctx->tower->wait_to_vote_slot ); - FD_TEST( adopt_tower_is( ctx, 101UL, 102UL, 104UL ) && fd_tower_blocks_query( ctx->tower, 100UL ) ); /* until ghost publishes 101 */ - FD_TEST( fd_tower_vote_peek_index_const( ctx->tower->votes, 0UL )->conf==2UL ); - FD_TEST( fd_tower_blocks_query( ctx->tower, 104UL )->voted && !fd_tower_blocks_query( ctx->tower, 105UL )->voted ); - - /* Votes at or below our root are dropped and our root is kept. */ - - fd_tower_file_t older = { .votes = {{ 100UL, 3UL }, { 102UL, 2UL }, { 104UL, 1UL }}, .votes_cnt = 3UL, .root = 90UL, .bank_hash = history.bank_hash }; - ctx = adopt_setup(); clear_votes( ctx->tower ); ctx->vote_history = older; adopt_vote_history( ctx ); - FD_TEST( adopt_tower_is( ctx, 100UL, 102UL, 104UL ) ); - - /* A last vote for a different version of the slot is still adopted, - recorded against the version we replayed. */ - - fd_tower_file_t other = history; other.bank_hash.ul[ 0 ] = 1UL; - ctx = adopt_setup(); clear_votes( ctx->tower ); ctx->vote_history = other; adopt_vote_history( ctx ); - FD_TEST( adopt_tower_is( ctx, 101UL, 102UL, 104UL ) ); - FD_TEST( fd_hash_eq( &fd_tower_blocks_query( ctx->tower, 104UL )->voted_block_id, &fd_tower_blocks_query( ctx->tower, 104UL )->replayed_block_id ) ); - - /* Switching to the identity we already have keeps the live tower. */ - - ctx = adopt_setup(); - adopt_switch( ctx, 0UL, &history ); - FD_TEST( !ctx->vote_history_pending && adopt_tower_is( ctx, 100UL, 101UL, 105UL ) ); - - /* A new identity without a history clears our tower. */ - - ctx = adopt_setup(); - adopt_switch( ctx, 0x22UL, NULL ); - FD_TEST( !ctx->vote_history_pending && adopt_tower_cleared( ctx ) ); - - /* A history behind our root is dropped. */ - - fd_tower_file_t bad = { .votes = {{ 95UL, 2UL }, { 100UL, 1UL }}, .votes_cnt = 2UL, .root = 90UL }; - ctx = adopt_setup(); adopt_switch( ctx, 0x22UL, &bad ); check_vote_history( ctx, &(fd_replay_slot_completed_t){ .slot = 105UL } ); - FD_TEST( adopt_tower_cleared( ctx ) && !ctx->vote_history_pending && !ctx->tower->wait_to_vote_slot ); - - /* A history ahead of the blocks we replayed stays pending. Our tower - takes its replayed vote, and we don't vote until its missing vote's - lockout expires. */ - - bad = history; bad.votes[ 1 ].slot = 106UL; - ctx = adopt_setup(); adopt_switch( ctx, 0x22UL, &bad ); check_vote_history( ctx, &(fd_replay_slot_completed_t){ .slot = 105UL } ); - FD_TEST( ctx->tower->root==101UL && fd_tower_vote_cnt( ctx->tower->votes )==1UL && fd_tower_vote_peek_tail_const( ctx->tower->votes )->slot==102UL ); - FD_TEST( ctx->vote_history_pending && ctx->tower->wait_to_vote_slot==109UL ); - - /* If that block still hasn't been replayed when the lockout expires, - its vote is dropped, but the wait stays so the slot we actually - vote for is still checked against it. */ - - check_vote_history( ctx, &(fd_replay_slot_completed_t){ .slot = 109UL } ); - FD_TEST( fd_tower_vote_cnt( ctx->tower->votes )==1UL && fd_tower_vote_peek_tail_const( ctx->tower->votes )->slot==102UL ); - FD_TEST( !ctx->vote_history_pending && ctx->tower->wait_to_vote_slot==109UL ); - - /* Like Agave, our tower is compared against the history's last vote, - even if its block is not replayed yet, so a history whose replayed - votes are older than ours still replaces our tower. */ - - fd_tower_file_t ahead = { .votes = {{ 104UL, 3UL }, { 110UL, 1UL }}, .votes_cnt = 2UL, .root = 102UL, .bank_hash = { .ul = { 1110UL } } }; - ctx = adopt_setup(); ctx->vote_history = ahead; adopt_vote_history( ctx ); - FD_TEST( ctx->tower->root==102UL && fd_tower_vote_cnt( ctx->tower->votes )==1UL && fd_tower_vote_peek_tail_const( ctx->tower->votes )->slot==104UL ); - - /* Switching to another identity drops a pending history. */ - - ctx = adopt_setup(); adopt_switch( ctx, 0x22UL, &bad ); check_vote_history( ctx, &(fd_replay_slot_completed_t){ .slot = 105UL } ); - adopt_switch( ctx, 0x33UL, NULL ); - FD_TEST( !ctx->vote_history_pending && !ctx->tower->wait_to_vote_slot ); - - FD_LOG_NOTICE(( "pass: test_identity_switch_adopts_vote_history" )); -} - -static void -test_vote_history_floor( void ) { - fd_tower_tile_t * ctx = adopt_setup(); /* root 100 */ - - /* Slots 90 and 100 are on the rooted chain, 95 is not. */ - - static ulong bits[ 4 ]; - memset( bits, 0, sizeof(bits) ); - bits[ (90UL /64UL)%4UL ] |= 1UL<<(90UL %64UL); - bits[ (100UL/64UL)%4UL ] |= 1UL<<(100UL%64UL); - fd_slot_history_view_t slot_history = { .bits = (uchar const *)bits, .blocks_len = 4UL, .bits_len = 256UL, .next_slot = 200UL }; - - /* 95 was on an abandoned fork, so we wait out its lockout. 90 is - rooted and 105 is above our root, so neither counts. */ - - fd_tower_file_t file = { .votes = {{ 90UL, 5UL }, { 95UL, 4UL }, { 105UL, 1UL }}, .votes_cnt = 3UL, .root = 80UL }; - FD_TEST( vote_history_floor( ctx->tower->root, &file, &slot_history )==95UL+16UL+1UL ); - - /* Votes above the root we judge against don't count, even if the - history lacks them. */ - - FD_TEST( vote_history_floor( 94UL, &file, &slot_history )==0UL ); - - file.votes[ 1 ].slot = 100UL; - FD_TEST( vote_history_floor( ctx->tower->root, &file, &slot_history )==0UL ); - - FD_LOG_NOTICE(( "pass: test_vote_history_floor" )); -} - -static void -test_count_vote_txn( void ) { - - /* Set up a minimal fd_tower_tile_t with just what count_vote_txn needs - before hitting the tower validation checks: scratch_tower, metrics, - and compact_tower_sync_serde. */ - - static uchar tower_mem2[ 65536 ] __attribute__((aligned(128))); - static uchar scratch_tower_mem[ FD_TOWER_VOTE_FOOTPRINT ] __attribute__((aligned(FD_TOWER_VOTE_ALIGN))); - static uchar root_vtr_pool_mem[ 65536 ] __attribute__((aligned(128))); - static uchar root_vtr_map_mem [ 65536 ] __attribute__((aligned(128))); - static uchar next_vtr_pool_mem[ 65536 ] __attribute__((aligned(128))); - static uchar next_vtr_map_mem [ 65536 ] __attribute__((aligned(128))); - static fd_tower_tile_t ctx[1]; - memset( ctx, 0, sizeof(*ctx) ); - ctx->tower = fd_tower_join( fd_tower_new( tower_mem2, 2, 2, 0 ) ); - ctx->scratch_tower = fd_tower_vote_join( fd_tower_vote_new( scratch_tower_mem ) ); - ctx->tower->root = 0; /* mark as ready */ - - ulong vtr_chain_cnt = epoch_vtr_map_chain_cnt_est( 4UL ); - ctx->root_epoch_vtr_pool = epoch_vtr_pool_join( epoch_vtr_pool_new( root_vtr_pool_mem, 4UL ) ); - ctx->root_epoch_vtr_map = epoch_vtr_map_join ( epoch_vtr_map_new ( root_vtr_map_mem, vtr_chain_cnt, 0UL ) ); - ctx->next_epoch_vtr_pool = epoch_vtr_pool_join( epoch_vtr_pool_new( next_vtr_pool_mem, 4UL ) ); - ctx->next_epoch_vtr_map = epoch_vtr_map_join ( epoch_vtr_map_new ( next_vtr_map_mem, vtr_chain_cnt, 0UL ) ); - - FD_TEST( ctx->tower ); - FD_TEST( ctx->scratch_tower ); - FD_TEST( ctx->root_epoch_vtr_pool && ctx->root_epoch_vtr_map ); - FD_TEST( ctx->next_epoch_vtr_pool && ctx->next_epoch_vtr_map ); - - ctx->mleaders = fd_multi_epoch_leaders_join( fd_multi_epoch_leaders_new( ctx->mleaders_mem ) ); - FD_TEST( ctx->mleaders ); - - fd_txn_p_t txnp[1]; - uchar txn_mem[ FD_TXN_MAX_SZ ] __attribute__((aligned(alignof(fd_txn_t)))); - fd_txn_t const * txn; - fd_hash_t block_id_null = {0}; - fd_hash_t block_id_nonnull = { .ul = { 0xBB } }; - - /* 1. Valid tower: 3 lockouts, strictly increasing slots, strictly - decreasing confirmation counts. Tower validation passes, then - exits at null block_id check. BadTower must stay 0. */ - - { - ulong slots[] = { 52, 57, 60 }; - ulong confs[] = { 31, 20, 1 }; - txn = mock_vote_txn( 42, 3, slots, confs, &block_id_null, txnp, txn_mem ); - memset( &ctx->metrics, 0, sizeof(ctx->metrics) ); - count_vote_txn( ctx, txn, txnp->payload ); - FD_TEST( ctx->metrics.votes[ FD_METRICS_ENUM_VOTE_TXN_RESULT_V_BAD_TOWER_IDX ]==0 ); - FD_TEST( ctx->metrics.votes[ FD_METRICS_ENUM_VOTE_TXN_RESULT_V_NULL_BLOCK_ID_IDX ]==1 ); - } - - /* 2. confirmation_count > FD_TOWER_VOTE_MAX. */ - - { - ulong slots[] = { 52 }; - ulong confs[] = { FD_TOWER_VOTE_MAX + 1 }; - txn = mock_vote_txn( 42, 1, slots, confs, &block_id_null, txnp, txn_mem ); - memset( &ctx->metrics, 0, sizeof(ctx->metrics) ); - count_vote_txn( ctx, txn, txnp->payload ); - FD_TEST( ctx->metrics.votes[ FD_METRICS_ENUM_VOTE_TXN_RESULT_V_BAD_TOWER_IDX ]==1 ); - } - - /* 3. Non-decreasing confirmation counts (equal). */ - - { - ulong slots[] = { 52, 57 }; - ulong confs[] = { 10, 10 }; - txn = mock_vote_txn( 42, 2, slots, confs, &block_id_null, txnp, txn_mem ); - memset( &ctx->metrics, 0, sizeof(ctx->metrics) ); - count_vote_txn( ctx, txn, txnp->payload ); - FD_TEST( ctx->metrics.votes[ FD_METRICS_ENUM_VOTE_TXN_RESULT_V_BAD_TOWER_IDX ]==1 ); - } - - /* 4. Increasing confirmation counts. */ - - { - ulong slots[] = { 52, 57, 60 }; - ulong confs[] = { 10, 5, 7 }; - txn = mock_vote_txn( 42, 3, slots, confs, &block_id_null, txnp, txn_mem ); - memset( &ctx->metrics, 0, sizeof(ctx->metrics) ); - count_vote_txn( ctx, txn, txnp->payload ); - FD_TEST( ctx->metrics.votes[ FD_METRICS_ENUM_VOTE_TXN_RESULT_V_BAD_TOWER_IDX ]==1 ); - } - - /* 5. Valid 1-lockout tower. */ - - { - ulong slots[] = { 10 }; - ulong confs[] = { 1 }; - txn = mock_vote_txn( 0, 1, slots, confs, &block_id_null, txnp, txn_mem ); - memset( &ctx->metrics, 0, sizeof(ctx->metrics) ); - count_vote_txn( ctx, txn, txnp->payload ); - FD_TEST( ctx->metrics.votes[ FD_METRICS_ENUM_VOTE_TXN_RESULT_V_BAD_TOWER_IDX ]==0 ); - } - - /* 6. Single valid lockout — edge case with exactly 1 vote. */ - - { - ulong slots[] = { 1 }; - ulong confs[] = { FD_TOWER_VOTE_MAX }; - txn = mock_vote_txn( 0, 1, slots, confs, &block_id_null, txnp, txn_mem ); - memset( &ctx->metrics, 0, sizeof(ctx->metrics) ); - count_vote_txn( ctx, txn, txnp->payload ); - FD_TEST( ctx->metrics.votes[ FD_METRICS_ENUM_VOTE_TXN_RESULT_V_BAD_TOWER_IDX ]==0 ); - } - - /* 7. Empty tower (0 lockouts) — not a bad tower, hits EmptyTower. - Needs non-null block_id to get past the hash_null check. */ - - { - txn = mock_vote_txn( 42, 0, NULL, NULL, &block_id_nonnull, txnp, txn_mem ); - memset( &ctx->metrics, 0, sizeof(ctx->metrics) ); - count_vote_txn( ctx, txn, txnp->payload ); - FD_TEST( ctx->metrics.votes[ FD_METRICS_ENUM_VOTE_TXN_RESULT_V_BAD_TOWER_IDX ]==0 ); - FD_TEST( ctx->metrics.votes[ FD_METRICS_ENUM_VOTE_TXN_RESULT_V_EMPTY_TOWER_IDX ]==1 ); - } - - /* 8. Max lockouts (FD_TOWER_VOTE_MAX), strictly decreasing confs. */ - - { - ulong slots[FD_TOWER_VOTE_MAX]; - ulong confs[FD_TOWER_VOTE_MAX]; - for( ulong i = 0; i < FD_TOWER_VOTE_MAX; i++ ) { - slots[i] = i + 1; - confs[i] = FD_TOWER_VOTE_MAX - i; - } - txn = mock_vote_txn( 0, FD_TOWER_VOTE_MAX, slots, confs, &block_id_null, txnp, txn_mem ); - memset( &ctx->metrics, 0, sizeof(ctx->metrics) ); - count_vote_txn( ctx, txn, txnp->payload ); - FD_TEST( ctx->metrics.votes[ FD_METRICS_ENUM_VOTE_TXN_RESULT_V_BAD_TOWER_IDX ]==0 ); - } - - /* 9. Vote epoch > root_epoch+1 — too far ahead. Tower validation - passes, block_id is non-null, last_vote_slot > tower root, - lsched returns epoch=2 against root_epoch=0 → votes_too_new. */ - - { - ulong slots[] = { 10 }; - ulong confs[] = { 1 }; - txn = mock_vote_txn( 0, 1, slots, confs, &block_id_nonnull, txnp, txn_mem ); - memset( &ctx->metrics, 0, sizeof(ctx->metrics) ); - ctx->root_epoch = 0; - ctx->mleaders->lsched[0]->epoch = 2; - ctx->mleaders->lsched[0]->slot0 = 0; - ctx->mleaders->lsched[0]->slot_cnt = 100; - ctx->mleaders->init_done[0] = 1; - count_vote_txn( ctx, txn, txnp->payload ); - FD_TEST( ctx->metrics.votes[ FD_METRICS_ENUM_VOTE_TXN_RESULT_V_NOT_STAKED_IDX ]==1 ); - } - - /* 10. Vote epoch == root_epoch+1 — valid case, takes the - next_epoch_vtr_map branch. The map is empty so the per-vtr - lookup returns NULL → NOT_STAKED (not rejected as too new). */ - - { - ulong slots[] = { 10 }; - ulong confs[] = { 1 }; - txn = mock_vote_txn( 0, 1, slots, confs, &block_id_nonnull, txnp, txn_mem ); - memset( &ctx->metrics, 0, sizeof(ctx->metrics) ); - ctx->root_epoch = 0; - ctx->mleaders->lsched[0]->epoch = 1; - count_vote_txn( ctx, txn, txnp->payload ); - FD_TEST( ctx->metrics.votes[ FD_METRICS_ENUM_VOTE_TXN_RESULT_V_NOT_STAKED_IDX ]==1 ); - } - - FD_LOG_NOTICE(( "pass: test_count_vote_txn_tower_checks" )); -} - -static void -test_parent_vote_txn_recent_blockhash( void ) { - static fd_tower_tile_t ctx[1]; - static uchar tower_mem[ 1UL<<20 ] __attribute__((aligned(128))); - memset( ctx, 0, sizeof(ctx) ); - ctx->tower = fd_tower_join( fd_tower_new( tower_mem, 32UL, 1UL, 0UL ) ); - FD_TEST( ctx->tower ); - - fd_tower_blk_t * parent_blk = fd_tower_blocks_insert( ctx->tower, 103UL, 102UL ); - FD_TEST( parent_blk ); - parent_blk->block_hash = (fd_hash_t){ .ul = { 1103UL } }; - - fd_tower_blk_t * root_blk = fd_tower_blocks_insert( ctx->tower, 104UL, 103UL ); - FD_TEST( root_blk ); - root_blk->block_hash = (fd_hash_t){ .ul = { 1104UL } }; - ctx->tower->root = 104UL; - fd_tower_vote_push_tail( ctx->tower->votes, (fd_tower_vote_t){ .slot = 120UL, .conf = 1UL } ); - - fd_replay_slot_completed_t slot_completed = {0}; - slot_completed.parent_slot = 103UL; - fd_hash_t bank_hash = { .ul = { 0xAAUL } }; - fd_hash_t block_id = { .ul = { 0xBBUL } }; - fd_pubkey_t validator_identity = { .ul = { 0x11UL } }; - fd_pubkey_t vote_acc = { .ul = { 0x22UL } }; - fd_txn_p_t txnp[1]; - - fd_tower_blk_t * recent_blockhash_blk = fd_tower_blocks_query( ctx->tower, slot_completed.parent_slot ); - FD_TEST( recent_blockhash_blk ); - fd_hash_t const * recent_blockhash = fd_type_pun_const( recent_blockhash_blk->block_hash.uc ); - fd_tower_to_vote_txn( ctx->tower, &bank_hash, &block_id, recent_blockhash, &validator_identity, &validator_identity, &vote_acc, txnp ); - - uchar txn_mem[ FD_TXN_MAX_SZ ]; - ulong parse_result = fd_txn_parse_core( txnp->payload, txnp->payload_sz, txn_mem, NULL, NULL ); - FD_TEST( parse_result>0UL ); - fd_txn_t const * txn = (fd_txn_t const *)txn_mem; - FD_TEST( 0==memcmp( fd_txn_get_recent_blockhash( txn, txnp->payload ), &parent_blk->block_hash, sizeof(fd_hash_t) ) ); - - FD_LOG_NOTICE(( "pass: test_parent_vote_txn_recent_blockhash" )); -} - -/* ---- test_fixture_replay ---- */ - -#define MOCK_SLOT_MAX (64UL) - -/* Fixture record layout: vote_acc(32) + id_key(32) + stake(8) + - data_sz(8) + vote_data(FD_VOTE_STATE_DATA_MAX). Each fixture file - contains FIXTURE_VTR_CNT records for a single slot. */ - -#define FIXTURE_VTR_CNT (100UL) -#define FIXTURE_RECORD_SZ (32UL + 32UL + 8UL + 8UL + FD_VOTE_STATE_DATA_MAX) -#define FIXTURE_FILE_SZ (FIXTURE_VTR_CNT * FIXTURE_RECORD_SZ) - -/* mock_query_towers: loads voter data from a fixture file for the - current slot and calls count_vote_acc for each record. */ - -ulong -mock_query_towers( fd_tower_tile_t * ctx, - fd_replay_slot_completed_t * slot_completed, - fd_ghost_blk_t * ghost_blk, - int * found_our_vote_acct, - ulong * our_vote_acct_bal, - ushort * our_vote_acct_com ) { - - /* Open the fixture file for this slot. */ - - char path[256]; - FD_TEST( snprintf( path, sizeof(path), "src/discof/tower/fixtures/voters-%lu.bin", slot_completed->slot ) < (int)sizeof(path) ); - - FILE * f = fopen( path, "rb" ); - FD_TEST( f ); - - static uchar buf[ FIXTURE_FILE_SZ ]; - FD_TEST( fread( buf, 1, FIXTURE_FILE_SZ, f )==FIXTURE_FILE_SZ ); - fclose( f ); - - /* Iterate records. */ - - ulong total_stake = 0UL; - ulong prev_voter_idx = ULONG_MAX; - - for( ulong i = 0UL; i < FIXTURE_VTR_CNT; i++ ) { - uchar const * rec = buf + i * FIXTURE_RECORD_SZ; - - fd_pubkey_t vote_acc; - memcpy( &vote_acc, rec, 32UL ); - - ulong stake; - memcpy( &stake, rec + 64UL, 8UL ); - - uchar const * data = rec + 80UL; - - count_vote_acc( ctx, slot_completed, ghost_blk, &vote_acc, stake, data, FD_VOTE_STATE_DATA_MAX ); - - ctx->vote_accs[i] = vote_acc; - fd_vote_account_node_pubkey( data, FD_VOTE_STATE_DATA_MAX, &ctx->id_keys[i] ); - - total_stake += stake; - prev_voter_idx = fd_tower_stakes_insert( ctx->tower, slot_completed->slot, &vote_acc, stake, prev_voter_idx ); - } - ctx->vtr_cnt = FIXTURE_VTR_CNT; - fd_votes_update_voters( ctx->votes, ctx->vote_accs, ctx->vtr_cnt ); - - /* No reconciliation in mock — just report not found. */ - - *found_our_vote_acct = 0; - *our_vote_acct_bal = ULONG_MAX; - *our_vote_acct_com = USHORT_MAX; - - return total_stake; -} - -/* mock_topo_with_accdb constructs a minimal fd_topo_t containing one - accdb shmem object and wires tile->tower.accdb_obj_id to it. This is - needed because init_choreo joins fd_accdb against - tile->tower.accdb_obj_id. Also dups a memfd onto fd FD_ACCDB_FD_RW so that - the writer accdb join in init_choreo has a valid backing fd. */ - -static void -mock_topo_with_accdb( fd_wksp_t * wksp, - fd_topo_t * topo, - fd_topo_tile_t * tile ) { - static int accdb_data_fd = -1; - static int fd_inited = 0; - if( !fd_inited ) { - accdb_data_fd = memfd_create( "tower_accdb_test_data", 0 ); - FD_TEST( accdb_data_fd>=0 ); - FD_TEST( dup2( accdb_data_fd, FD_ACCDB_FD_RW )==FD_ACCDB_FD_RW ); - fd_inited = 1; - } - - ulong const max_accounts = 1024UL; - ulong const max_writes_per_slot = 64UL; - ulong const partition_cnt = 8192UL; - ulong const partition_sz = 1UL<<24UL; - ulong const cache_fp = 64UL<<20UL; - ulong const cache_min_reserved = 1UL; - ulong const joiner_cnt = 1UL; - - memset( topo, 0, sizeof(*topo) ); - fd_topob_new( topo, "topo" ); - fd_topo_wksp_t * topo_wksp = fd_topob_wksp( topo, "wksp" ); - topo_wksp->wksp = wksp; - - ulong shmem_fp = fd_accdb_shmem_footprint( max_accounts, tile->tower.max_live_slots, max_writes_per_slot, partition_cnt, cache_fp, cache_min_reserved, joiner_cnt, 0UL ); - void * shmem_mem = fd_wksp_alloc_laddr( wksp, fd_accdb_shmem_align(), shmem_fp, 1UL ); - FD_TEST( shmem_mem ); - FD_TEST( fd_accdb_shmem_new( shmem_mem, max_accounts, tile->tower.max_live_slots, max_writes_per_slot, partition_cnt, partition_sz, cache_fp, cache_min_reserved, 0, 42UL, joiner_cnt, 0UL ) ); - - fd_topo_obj_t * shmem_obj = fd_topob_obj( topo, "accdb_shmem", "wksp" ); - shmem_obj->wksp_id = topo_wksp->id; - shmem_obj->offset = fd_wksp_gaddr_fast( wksp, shmem_mem ); - - tile->tower.accdb_obj_id = shmem_obj->id; -} - -static void -test_fixture_replay( fd_wksp_t * wksp ) { - - /* Use scratch_footprint to compute the exact allocation size needed, - matching the production init path. We construct a mock - fd_topo_tile_t with just the fields that scratch_footprint and - init_choreo access. */ - - static fd_topo_tile_t tile[1]; - memset( tile, 0, sizeof(*tile) ); - tile->tower.max_live_slots = MOCK_SLOT_MAX; - tile->tower.max_shreds_per_block = FD_SHRED_BLK_MAX; - - static fd_topo_t topo[1]; - mock_topo_with_accdb( wksp, topo, tile ); - - FD_TEST( scratch_align()==128UL ); - - ulong footprint = scratch_footprint( tile ); - FD_TEST( footprint ); - - void * scratch = fd_wksp_alloc_laddr( wksp, scratch_align(), footprint, 1UL ); - FD_TEST( scratch ); - - /* Initialize all choreo structures via the production init path. - init_choreo handles scratch layout, new/join of all - choreo structures, and state initialization. */ - - ((fd_tower_tile_t *)scratch)->seed = 42UL; - fd_tower_tile_t * ctx = init_choreo( scratch, topo, tile ); - FD_TEST( ctx ); - - /* Set fields normally handled by privileged_init. */ - - ctx->tower_dir_fd = -1; - memset( ctx->identity_key, 0x11, sizeof(fd_pubkey_t) ); - memset( ctx->vote_account, 0x22, sizeof(fd_pubkey_t) ); - - /* Replay each fixture slot. */ - - ulong start_slot = 398915634UL; - ulong num_slots = 32UL; - - fd_vote_stake_weight_t fixture_stakes[1] = {{ .vote_key = {{0}}, .id_key = {{0}}, .stake = 1UL }}; - ctx->mleaders->lsched[0] = fd_epoch_leaders_join( fd_epoch_leaders_new( ctx->mleaders->_lsched[0], 0, start_slot - 1, num_slots + MOCK_SLOT_MAX + 100, 1UL, fixture_stakes ) ); - ctx->mleaders->init_done[0] = 1; - - for( ulong slot = start_slot; slot < start_slot + num_slots; slot++ ) { - - fd_replay_slot_completed_t sc; - memset( &sc, 0, sizeof(sc) ); - sc.slot = slot; - sc.parent_slot = slot - 1; - sc.epoch = 0; - sc.block_id = (fd_hash_t){ .ul = { slot } }; - sc.parent_block_id = (fd_hash_t){ .ul = { slot - 1 } }; - sc.bank_hash = (fd_hash_t){ .ul = { slot } }; - sc.block_hash = (fd_hash_t){ .ul = { slot } }; - sc.bank_idx = slot; /* arbitrary */ - sc.is_leader = 0; - - replay_slot_completed( ctx, &sc, 0UL, NULL ); - } - - /* Verify: init flag set after first slot. */ - - FD_TEST( ctx->init==1 ); - - /* Verify: ghost root exists. */ - - FD_TEST( fd_ghost_root( ctx->ghost ) ); - - /* Verify: tower has blocks for all replayed slots. */ - - for( ulong slot = start_slot; slot < start_slot + num_slots; slot++ ) { - fd_tower_blk_t * blk = fd_tower_blocks_query( ctx->tower, slot ); - FD_TEST( blk ); - FD_TEST( blk->replayed==1 ); - } - - /* Verify: tower root set. */ - - FD_TEST( ctx->tower->root!=ULONG_MAX ); - - /* Verify: ghost has entries for all replayed slots. */ - - for( ulong slot = start_slot; slot < start_slot + num_slots; slot++ ) { - fd_hash_t bid = { .ul = { slot } }; - FD_TEST( fd_ghost_query( ctx->ghost, &bid ) ); - } - - FD_LOG_NOTICE(( "pass: test_fixture_replay" )); -} - -/* ---- eqvoc ordering tests ---- - - Three events for a slot with equivocation: - R = first replay_slot_completed (we replay block A) - E = equivocation detected (publish_slot_duplicate) - C = CONFIRMED_DUPLICATE reached (publish_slot_confirmed) - - 3! = 6 orderings × 2 sub-cases for C (C=A: confirmed block matches - replayed; C=B: differs) = 12 total cases. Six reduce, leaving 6 - minimal that we test below: - - C = A (confirmed = replayed) C = B (confirmed ≠ replayed) - +-------+--------+----------------+ +-------+--------+----------------+ - | Order | Status | Reduces to | | Order | Status | Reduces to | - +-------+--------+----------------+ +-------+--------+----------------+ - | RCE | tested | | | RCE | tested | | - | REC | tested | | | ERC | tested | | - | CRE | tested | | | CRE | tested | | - | ERC | | REC | | REC | | RCE | - | ECR | | ERC → REC | | ECR | | CRE | - | CER | | CRE | | CER | | CRE | - +-------+--------+----------------+ +-------+--------+----------------+ - - Why the reductions hold: - C=A: ECR ≡ ERC: C and R commute after E. - ERC ≡ REC: ghost A invalid either way; C=A re-validates. - CER ≡ CRE: E is a no-op once C has forward-confirmed A. - C=B: REC ≡ RCE: C=B invalidates A; swapping C/E preserves end state. - ECR ≡ CRE: R reconciles both — E/C order before R doesn't matter. - CER ≡ CRE: E redundant once C has forward-confirmed B. */ - -#define EQVOC_START_SLOT 398915634UL -#define EQVOC_BOOT_CNT 10UL - -/* eqvoc_setup bootstraps a fresh choreo context by replaying - EQVOC_BOOT_CNT slots. Returns the initialized context. */ - -static fd_tower_tile_t * -eqvoc_setup( fd_wksp_t * wksp ) { - static fd_topo_tile_t tile[1]; - memset( tile, 0, sizeof(*tile) ); - tile->tower.max_live_slots = MOCK_SLOT_MAX; - tile->tower.max_shreds_per_block = FD_SHRED_BLK_MAX; - - static fd_topo_t topo[1]; - memset( topo, 0, sizeof(*topo) ); - mock_topo_with_accdb( wksp, topo, tile ); - - void * scratch = fd_wksp_alloc_laddr( wksp, scratch_align(), scratch_footprint( tile ), 1UL ); - FD_TEST( scratch ); - - ((fd_tower_tile_t *)scratch)->seed = 42UL; - fd_tower_tile_t * ctx = init_choreo( scratch, topo, tile ); - FD_TEST( ctx ); - - ctx->tower_dir_fd = -1; - memset( ctx->identity_key, 0x11, sizeof(fd_pubkey_t) ); - memset( ctx->vote_account, 0x22, sizeof(fd_pubkey_t) ); - - fd_vote_stake_weight_t eqvoc_stakes[1] = {{ .vote_key = {{0}}, .id_key = {{0}}, .stake = 1UL }}; - ulong eqvoc_slot_cnt = EQVOC_BOOT_CNT + MOCK_SLOT_MAX + 100; - ctx->mleaders->lsched[0] = fd_epoch_leaders_join( fd_epoch_leaders_new( ctx->mleaders->_lsched[0], 0, EQVOC_START_SLOT - 1, eqvoc_slot_cnt, 1UL, eqvoc_stakes ) ); - ctx->mleaders->init_done[0] = 1; - - for( ulong slot = EQVOC_START_SLOT; slot < EQVOC_START_SLOT + EQVOC_BOOT_CNT; slot++ ) { - fd_replay_slot_completed_t sc; - memset( &sc, 0, sizeof(sc) ); - sc.slot = slot; - sc.parent_slot = slot - 1; - sc.block_id = (fd_hash_t){ .ul = { slot } }; - sc.parent_block_id = (fd_hash_t){ .ul = { slot - 1 } }; - sc.bank_hash = (fd_hash_t){ .ul = { slot } }; - sc.block_hash = (fd_hash_t){ .ul = { slot } }; - sc.bank_idx = slot; - replay_slot_completed( ctx, &sc, 0UL, NULL ); - } - FD_TEST( ctx->init==1 ); - return ctx; -} - -/* Helpers for simulating R, E, C events. */ - -static void -mock_replay( fd_tower_tile_t * ctx, - ulong slot, - fd_hash_t const * block_id ) { - fd_replay_slot_completed_t sc; - memset( &sc, 0, sizeof(sc) ); - sc.slot = slot; - sc.parent_slot = slot - 1; - sc.block_id = *block_id; - sc.parent_block_id = (fd_hash_t){ .ul = { slot - 1 } }; - sc.bank_hash = (fd_hash_t){ .ul = { slot } }; - sc.block_hash = (fd_hash_t){ .ul = { slot } }; - sc.bank_idx = slot; - replay_slot_completed( ctx, &sc, 0UL, NULL ); -} - -static void -mock_confirmed( fd_tower_tile_t * ctx, - ulong slot, - fd_hash_t const * block_id ) { - /* Create a votes_blk entry for (slot, block_id) if it doesn't - already exist, then set stake high enough for DUPLICATE (>52%). */ - - if( !fd_votes_query( ctx->votes, slot, block_id ) ) { - int err = fd_votes_count_vote( ctx->votes, &ctx->vote_accs[0], 1UL, slot, block_id ); - FD_TEST( err==FD_VOTES_SUCCESS ); - } - fd_votes_blk_t * vblk = fd_votes_query( ctx->votes, slot, block_id ); - FD_TEST( vblk ); - vblk->stake = 53; - publish_slot_confirmed( ctx, slot, block_id, 100 ); -} - -static void -mock_eqvoc( fd_tower_tile_t * ctx, - ulong slot ) { - static fd_gossip_duplicate_shred_t dummy_chunks[FD_EQVOC_CHUNK_CNT]; - publish_slot_duplicate( ctx, dummy_chunks, slot ); -} - -/* ---- C=A tests (confirmed block = replayed block) ---- */ - -static void -test_eqvoc_rce_same( fd_wksp_t * wksp ) { - fd_tower_tile_t * ctx = eqvoc_setup( wksp ); - ulong slot = EQVOC_START_SLOT + EQVOC_BOOT_CNT; - fd_hash_t A = { .ul = { slot } }; - - mock_replay ( ctx, slot, &A ); - mock_confirmed ( ctx, slot, &A ); - mock_eqvoc ( ctx, slot ); - - fd_tower_blk_t * tb = fd_tower_blocks_query( ctx->tower, slot ); - FD_TEST( tb && tb->confirmed==1 ); - FD_TEST( 0==memcmp( &tb->confirmed_block_id, &A, sizeof(fd_hash_t) ) ); - - fd_ghost_blk_t * gb = fd_ghost_query( ctx->ghost, &A ); - FD_TEST( gb && gb->valid==1 ); - - FD_LOG_NOTICE(( "pass: test_eqvoc_rce_same" )); -} - -static void -test_eqvoc_rec_same( fd_wksp_t * wksp ) { - fd_tower_tile_t * ctx = eqvoc_setup( wksp ); - ulong slot = EQVOC_START_SLOT + EQVOC_BOOT_CNT; - fd_hash_t A = { .ul = { slot } }; - - mock_replay ( ctx, slot, &A ); - mock_eqvoc ( ctx, slot ); - - /* After eqvoc, ghost A should be invalid. */ - - FD_TEST( fd_ghost_query( ctx->ghost, &A )->valid==0 ); - - mock_confirmed( ctx, slot, &A ); - - /* fd_ghost_confirm re-validates A. */ - - fd_tower_blk_t * tb = fd_tower_blocks_query( ctx->tower, slot ); - FD_TEST( tb && tb->confirmed==1 ); - - fd_ghost_blk_t * gb = fd_ghost_query( ctx->ghost, &A ); - FD_TEST( gb && gb->valid==1 ); - - FD_LOG_NOTICE(( "pass: test_eqvoc_rec_same" )); -} - -static void -test_eqvoc_cre_same( fd_wksp_t * wksp ) { - fd_tower_tile_t * ctx = eqvoc_setup( wksp ); - ulong slot = EQVOC_START_SLOT + EQVOC_BOOT_CNT; - fd_hash_t A = { .ul = { slot } }; - - mock_confirmed ( ctx, slot, &A ); - - /* Forward confirmation: no ghost or tower yet. */ - - FD_TEST( !fd_tower_blocks_query( ctx->tower, slot ) ); - FD_TEST( !fd_ghost_query( ctx->ghost, &A ) ); - - mock_replay ( ctx, slot, &A ); - mock_eqvoc ( ctx, slot ); - - fd_tower_blk_t * tb = fd_tower_blocks_query( ctx->tower, slot ); - FD_TEST( tb && tb->confirmed==1 ); - FD_TEST( 0==memcmp( &tb->confirmed_block_id, &A, sizeof(fd_hash_t) ) ); - - fd_ghost_blk_t * gb = fd_ghost_query( ctx->ghost, &A ); - FD_TEST( gb && gb->valid==1 ); - - FD_LOG_NOTICE(( "pass: test_eqvoc_cre_same" )); -} - -/* ---- C=B tests (confirmed block differs from replayed block) ---- */ - -static void -test_eqvoc_rce_diff( fd_wksp_t * wksp ) { - fd_tower_tile_t * ctx = eqvoc_setup( wksp ); - ulong slot = EQVOC_START_SLOT + EQVOC_BOOT_CNT; - fd_hash_t A = { .ul = { slot } }; - fd_hash_t B = { .ul = { slot, 0xBB } }; - - mock_replay ( ctx, slot, &A ); - mock_confirmed ( ctx, slot, &B ); - mock_eqvoc ( ctx, slot ); - - fd_tower_blk_t * tb = fd_tower_blocks_query( ctx->tower, slot ); - FD_TEST( tb && tb->confirmed==1 ); - FD_TEST( 0==memcmp( &tb->confirmed_block_id, &B, sizeof(fd_hash_t) ) ); - - fd_ghost_blk_t * gb = fd_ghost_query( ctx->ghost, &A ); - FD_TEST( gb && gb->valid==0 ); - - FD_LOG_NOTICE(( "pass: test_eqvoc_rce_diff" )); -} - -static void -test_eqvoc_erc_diff( fd_wksp_t * wksp ) { - fd_tower_tile_t * ctx = eqvoc_setup( wksp ); - ulong slot = EQVOC_START_SLOT + EQVOC_BOOT_CNT; - fd_hash_t A = { .ul = { slot } }; - fd_hash_t B = { .ul = { slot, 0xBB } }; - - /* E before R: insert two conflicting shreds into eqvoc so that - fd_eqvoc_proof_verified returns true when replay checks it. */ - - { - static uchar s1[FD_SHRED_MAX_SZ], s2[FD_SHRED_MAX_SZ]; - memset( s1, 0, sizeof(s1) ); - memset( s2, 0, sizeof(s2) ); - fd_shred_t * shred1 = (fd_shred_t *)s1; - fd_shred_t * shred2 = (fd_shred_t *)s2; - shred1->variant = FD_SHRED_TYPE_MERKLE_DATA; - shred1->slot = slot; - shred1->fec_set_idx = 0; - shred2->variant = FD_SHRED_TYPE_MERKLE_DATA; - shred2->slot = slot; - shred2->fec_set_idx = 0; - fd_gossip_duplicate_shred_t proof_chunks[FD_EQVOC_CHUNK_CNT]; - FD_TEST( fd_eqvoc_shred_insert( ctx->eqvoc, 0, shred1, proof_chunks )==0 ); - FD_TEST( fd_eqvoc_shred_insert( ctx->eqvoc, 1, shred2, proof_chunks )==1 ); - FD_TEST( fd_eqvoc_proof_verified( ctx->eqvoc, slot ) ); - } - - mock_replay( ctx, slot, &A ); - - /* Replay detected eqvoc → ghost A invalid. */ - - FD_TEST( fd_ghost_query( ctx->ghost, &A )->valid==0 ); - - mock_confirmed ( ctx, slot, &B ); - - fd_tower_blk_t * tb = fd_tower_blocks_query( ctx->tower, slot ); - FD_TEST( tb && tb->confirmed==1 ); - FD_TEST( 0==memcmp( &tb->confirmed_block_id, &B, sizeof(fd_hash_t) ) ); - - fd_ghost_blk_t * gb = fd_ghost_query( ctx->ghost, &A ); - FD_TEST( gb && gb->valid==0 ); - - FD_LOG_NOTICE(( "pass: test_eqvoc_erc_diff" )); -} - -static void -test_eqvoc_cre_diff( fd_wksp_t * wksp ) { - fd_tower_tile_t * ctx = eqvoc_setup( wksp ); - ulong slot = EQVOC_START_SLOT + EQVOC_BOOT_CNT; - fd_hash_t A = { .ul = { slot } }; - fd_hash_t B = { .ul = { slot, 0xBB } }; - - mock_confirmed ( ctx, slot, &B ); - - /* Forward confirmation for B: no ghost or tower yet. */ - - FD_TEST( !fd_tower_blocks_query( ctx->tower, slot ) ); - - mock_replay ( ctx, slot, &A ); - - /* fd_votes_query(NULL) finds B's fwd entry, sets tower confirmed and - ghost_eqvoc(A). */ - - fd_tower_blk_t * tb = fd_tower_blocks_query( ctx->tower, slot ); - FD_TEST( tb && tb->confirmed==1 ); - FD_TEST( 0==memcmp( &tb->confirmed_block_id, &B, sizeof(fd_hash_t) ) ); - - fd_ghost_blk_t * gb = fd_ghost_query( ctx->ghost, &A ); - FD_TEST( gb && gb->valid==0 ); - - mock_eqvoc ( ctx, slot ); - - /* Eqvoc after confirmed is idempotent. */ - - FD_TEST( tb->confirmed==1 ); - FD_TEST( gb->valid==0 ); - - FD_LOG_NOTICE(( "pass: test_eqvoc_cre_diff" )); -} - -static void -replay_fixture_slot( fd_tower_tile_t * ctx, - ulong slot ) { - fd_replay_slot_completed_t sc; - memset( &sc, 0, sizeof(sc) ); - sc.slot = slot; - sc.parent_slot = slot - 1; - sc.block_id = (fd_hash_t){ .ul = { slot } }; - sc.parent_block_id = (fd_hash_t){ .ul = { slot - 1 } }; - sc.bank_hash = (fd_hash_t){ .ul = { slot } }; - sc.block_hash = (fd_hash_t){ .ul = { slot } }; - sc.bank_idx = 0UL; /* test_banks root bank */ - replay_slot_completed( ctx, &sc, 0UL, NULL ); -} - -static void -test_vote_history_pending_replay( fd_wksp_t * wksp ) { - fd_tower_tile_t * ctx = eqvoc_setup( wksp ); - ulong next = EQVOC_START_SLOT + EQVOC_BOOT_CNT; - ctx->banks = test_banks(); - - /* As after a switch: our tower is cleared and the history is pending. - The other machine's last two votes are on slots we haven't replayed - yet. */ - - ctx->vote_history = (fd_tower_file_t){ .votes = {{ next-1UL, 3UL }, { next, 2UL }, { next+1UL, 1UL }}, .votes_cnt = 3UL, .root = ctx->tower->root, .bank_hash = { .ul = { next+1UL } } }; - clear_votes( ctx->tower ); - ctx->vote_history_pending = 1; - - /* Still ahead after replaying one of them: it stays pending, our - tower takes the replayed votes, and we don't vote until the missing - vote's lockout expires. */ - - replay_fixture_slot( ctx, next ); - FD_TEST( ctx->vote_history_pending && ctx->tower->wait_to_vote_slot==next+4UL ); - FD_TEST( fd_tower_vote_cnt( ctx->tower->votes )==2UL && fd_tower_vote_peek_tail_const( ctx->tower->votes )->slot==next ); - - /* Replaying the last one adopts it and lifts the wait. */ - - replay_fixture_slot( ctx, next+1UL ); - FD_TEST( !ctx->vote_history_pending && !ctx->tower->wait_to_vote_slot ); - FD_TEST( fd_tower_vote_cnt( ctx->tower->votes )==3UL && fd_tower_vote_peek_tail_const( ctx->tower->votes )->slot==next+1UL ); - - FD_LOG_NOTICE(( "pass: test_vote_history_pending_replay" )); -} - -static void -tower_file_make( int dir_fd, - char const * name, - char const * text ) { - int fd = openat( dir_fd, name, O_WRONLY|O_CREAT|O_TRUNC, 0644 ); - FD_TEST( -1!=fd ); - FD_TEST( (long)strlen( text )==write( fd, text, strlen( text ) ) ); - FD_TEST( !close( fd ) ); -} - -/* tower_file_is returns whether name holds text, or if text is NULL, a - tower file for identity. */ - -static int -tower_file_is( int dir_fd, - char const * name, - char const * text, - fd_pubkey_t const * identity ) { - uchar buf[ FD_TOWER_FILE_DATA_OFF+32UL ] = {0}; - int fd = openat( dir_fd, name, O_RDONLY ); - if( -1==fd ) return 0; - long n = read( fd, buf, sizeof(buf) ); - FD_TEST( !close( fd ) ); - if( text ) return n==(long)strlen( text ) && !memcmp( buf, text, strlen( text ) ); - return n==(long)sizeof(buf) && FD_LOAD( uint, buf )==1U && fd_memeq( buf+FD_TOWER_FILE_DATA_OFF, identity->uc, 32UL ); -} - -static void -test_tower_file_write( fd_wksp_t * wksp ) { - - /* A fake sign tile, a response is published before each request */ - - ulong depth = 4UL; - ulong req_data_sz = fd_dcache_req_data_sz( FD_KEYGUARD_SIGN_REQ_MTU, depth, 1UL, 1 ); - ulong rsp_data_sz = fd_dcache_req_data_sz( 64UL, depth, 1UL, 1 ); - fd_frag_meta_t * req_mcache = fd_mcache_join( fd_mcache_new( fd_wksp_alloc_laddr( wksp, fd_mcache_align(), fd_mcache_footprint( depth, 0UL ), 1UL ), depth, 0UL, 0UL ) ); - fd_frag_meta_t * rsp_mcache = fd_mcache_join( fd_mcache_new( fd_wksp_alloc_laddr( wksp, fd_mcache_align(), fd_mcache_footprint( depth, 0UL ), 1UL ), depth, 0UL, 0UL ) ); - uchar * req_dcache = fd_dcache_join( fd_dcache_new( fd_wksp_alloc_laddr( wksp, fd_dcache_align(), fd_dcache_footprint( req_data_sz, 0UL ), 1UL ), req_data_sz, 0UL ) ); - uchar * rsp_dcache = fd_dcache_join( fd_dcache_new( fd_wksp_alloc_laddr( wksp, fd_dcache_align(), fd_dcache_footprint( rsp_data_sz, 0UL ), 1UL ), rsp_data_sz, 0UL ) ); - static fd_tower_tile_t ctx[1]; - FD_TEST( fd_keyguard_client_join( fd_keyguard_client_new( ctx->keyguard_client, req_mcache, req_dcache, rsp_mcache, rsp_dcache, FD_KEYGUARD_SIGN_REQ_MTU, 64UL, NULL, 0UL, ULONG_MAX ) ) ); - ulong rsp_chunk0 = fd_dcache_compact_chunk0( wksp, rsp_dcache ); - ulong rsp_seq = 0UL; - - char dir[] = "/tmp/test_tower_file_write_XXXXXX"; - FD_TEST( mkdtemp( dir ) ); - int dir_fd = open( dir, O_RDONLY|O_DIRECTORY ); - FD_TEST( -1!=dir_fd ); - ctx->tower_dir_fd = dir_fd; - - /* identities U, A and B */ - - fd_pubkey_t id[ 3 ]; - char name[ 3 ][ 2 ][ PATH_MAX ]; - char old [ 3 ][ 2 ][ PATH_MAX ]; - for( ulong i=0UL; i<3UL; i++ ) { - memset( id[ i ].uc, (int)(0x11UL*(i+1UL)), 32UL ); - FD_BASE58_ENCODE_32_BYTES( id[ i ].uc, b58 ); - FD_TEST( fd_cstr_printf_check( name[ i ][ 1 ], PATH_MAX, NULL, "tower-1_9-%s.bin", b58 ) ); - FD_TEST( fd_cstr_printf_check( name[ i ][ 0 ], PATH_MAX, NULL, "%s.new", name[ i ][ 1 ] ) ); - FD_TEST( fd_cstr_printf_check( old[ i ][ 0 ], PATH_MAX, NULL, "%s.old", name[ i ][ 1 ] ) ); - FD_TEST( fd_cstr_printf_check( old[ i ][ 1 ], PATH_MAX, NULL, "%s.old.1", name[ i ][ 1 ] ) ); - } - - ctx->identity_key[ 0 ] = id[ 0 ]; - memcpy( ctx->tower_name, name[ 0 ], sizeof(name[ 0 ]) ); - for( ulong i=0UL; i<2UL; i++ ) { - ctx->tower_fd[ i ] = openat( dir_fd, ctx->tower_name[ i ], O_WRONLY|O_CREAT, 0644 ); - FD_TEST( -1!=ctx->tower_fd[ i ] ); - } - memset( &ctx->compact_tower_sync_serde, 0, sizeof(ctx->compact_tower_sync_serde) ); - ctx->compact_tower_sync_serde.root = 100UL; - ctx->compact_tower_sync_serde.lockouts_cnt = 1; - ctx->compact_tower_sync_serde.lockouts[ 0 ] = ( __typeof__(ctx->compact_tower_sync_serde.lockouts[0]) ){ .offset=1UL, .confirmation_count=1 }; - -# define WRITE() do { \ - fd_mcache_publish( rsp_mcache, depth, rsp_seq++, 0UL, rsp_chunk0, 64UL, 0UL, 0UL, 0UL ); \ - ctx->tower_file_pending = 101UL; \ - tower_file_write( ctx ); \ - } while(0) - - /* the first write as U */ - - WRITE(); - FD_TEST( tower_file_is( dir_fd, name[ 0 ][ 1 ], NULL, &id[ 0 ] ) ); - - /* switching to A, whose name has the tower file passed to - set-identity, keeps that file under an .old name */ - - tower_file_make( dir_fd, name[ 1 ][ 1 ], "copy" ); - ctx->identity_key[ 0 ] = id[ 1 ]; - WRITE(); - FD_TEST( tower_file_is( dir_fd, name[ 1 ][ 1 ], NULL, &id[ 1 ] ) ); - FD_TEST( tower_file_is( dir_fd, name[ 1 ][ 0 ], NULL, &id[ 0 ] ) ); - FD_TEST( tower_file_is( dir_fd, old [ 1 ][ 0 ], "copy", NULL ) ); - FD_TEST( -1==faccessat( dir_fd, name[ 0 ][ 0 ], F_OK, 0 ) ); - FD_TEST( -1==faccessat( dir_fd, name[ 0 ][ 1 ], F_OK, 0 ) ); - - /* another write as A only exchanges the staging and live files */ - - WRITE(); - FD_TEST( tower_file_is( dir_fd, name[ 1 ][ 1 ], NULL, &id[ 1 ] ) ); - FD_TEST( tower_file_is( dir_fd, name[ 1 ][ 0 ], NULL, &id[ 1 ] ) ); - - /* switching to B, whose .old name is taken too, uses the next one */ - - tower_file_make( dir_fd, name[ 2 ][ 1 ], "copy" ); - tower_file_make( dir_fd, old [ 2 ][ 0 ], "older" ); - ctx->identity_key[ 0 ] = id[ 2 ]; - WRITE(); - FD_TEST( tower_file_is( dir_fd, name[ 2 ][ 1 ], NULL, &id[ 2 ] ) ); - FD_TEST( tower_file_is( dir_fd, old [ 2 ][ 0 ], "older", NULL ) ); - FD_TEST( tower_file_is( dir_fd, old [ 2 ][ 1 ], "copy", NULL ) ); - - struct stat st; - FD_TEST( !fstatat( dir_fd, name[ 2 ][ 1 ], &st, 0 ) ); - FD_TEST( ctx->metrics.tower_file_write==4UL ); - FD_TEST( ctx->metrics.tower_file_slot ==101UL ); - FD_TEST( !ctx->tower_file_pending ); - FD_TEST( ctx->metrics.tower_file_sz ==(ulong)st.st_size ); - -# undef WRITE - - char const * files[] = { name[ 2 ][ 0 ], name[ 2 ][ 1 ], old[ 2 ][ 0 ], old[ 2 ][ 1 ], old[ 1 ][ 0 ] }; - for( ulong i=0UL; i<5UL; i++ ) FD_TEST( !unlinkat( dir_fd, files[ i ], 0 ) ); - for( ulong i=0UL; i<2UL; i++ ) FD_TEST( !close( ctx->tower_fd[ i ] ) ); - FD_TEST( !close( dir_fd ) ); - FD_TEST( !rmdir( dir ) ); - - FD_LOG_NOTICE(( "pass: test_tower_file_write" )); -} - -int -main( int argc, - char ** argv ) { - fd_boot( &argc, &argv ); - - test_publish_slot_done_identity_mismatch(); - test_identity_switch_waits_for_replay(); - test_halt_signing_backpressures_replay(); - test_identity_switch_waits_for_publishes(); - test_identity_switch_adopts_vote_history(); - test_vote_history_floor(); - test_count_vote_txn(); - test_parent_vote_txn_recent_blockhash(); - char const * _page_sz = fd_env_strip_cmdline_cstr ( &argc, &argv, "--page-sz", NULL, "gigantic" ); - ulong page_cnt = fd_env_strip_cmdline_ulong( &argc, &argv, "--page-cnt", NULL, 4UL ); - ulong numa_idx = fd_env_strip_cmdline_ulong( &argc, &argv, "--numa-idx", NULL, fd_shmem_numa_idx( 0UL ) ); - fd_wksp_t * wksp = fd_wksp_new_anonymous( fd_cstr_to_shmem_page_sz( _page_sz ), page_cnt, fd_shmem_cpu_idx( numa_idx ), "wksp", 0UL ); - FD_TEST( wksp ); - - test_fixture_replay( wksp ); - - fd_wksp_reset( wksp, 1UL ); test_eqvoc_rce_same( wksp ); - fd_wksp_reset( wksp, 1UL ); test_eqvoc_rec_same( wksp ); - fd_wksp_reset( wksp, 1UL ); test_eqvoc_cre_same( wksp ); - fd_wksp_reset( wksp, 1UL ); test_eqvoc_rce_diff( wksp ); - fd_wksp_reset( wksp, 1UL ); test_eqvoc_erc_diff( wksp ); - fd_wksp_reset( wksp, 1UL ); test_eqvoc_cre_diff( wksp ); - fd_wksp_reset( wksp, 1UL ); test_vote_history_pending_replay( wksp ); - fd_wksp_reset( wksp, 1UL ); test_tower_file_write( wksp ); - - fd_halt(); -} diff --git a/src/flamenco/runtime/tests/Local.mk b/src/flamenco/runtime/tests/Local.mk index 6fd307793b7..ad56c919206 100644 --- a/src/flamenco/runtime/tests/Local.mk +++ b/src/flamenco/runtime/tests/Local.mk @@ -9,14 +9,14 @@ $(call add-objs,fd_dump_pb,fd_flamenco) endif $(call add-hdrs,fd_instr_harness.h fd_txn_harness.h fd_bundle_harness.h fd_gossip_harness.h) -$(call add-objs,fd_elf_harness fd_instr_harness fd_txn_harness fd_bundle_harness fd_cost_harness fd_harness_common fd_vm_harness fd_gossip_harness fd_shred_harness,fd_flamenco_test) +$(call add-objs,fd_elf_harness fd_instr_harness fd_txn_harness fd_bundle_harness fd_cost_harness fd_harness_common fd_vm_harness fd_gossip_harness,fd_flamenco_test) $(call add-objs,fd_block_harness,fd_flamenco_test) $(call add-objs,fd_sol_compat,fd_flamenco_test) -$(call add-hdrs,generated/context.pb.h generated/instr.pb.h generated/txn.pb.h generated/bundle.pb.h generated/block.pb.h generated/vm.pb.h generated/vm_serialization.pb.h generated/metadata.pb.h generated/gossip.pb.h generated/cost.pb.h generated/elf.pb.h generated/shred.pb.h) +$(call add-hdrs,generated/context.pb.h generated/instr.pb.h generated/txn.pb.h generated/bundle.pb.h generated/block.pb.h generated/vm.pb.h generated/vm_serialization.pb.h generated/metadata.pb.h generated/gossip.pb.h generated/cost.pb.h generated/elf.pb.h) $(call add-objs,generated/context.pb generated/instr.pb generated/txn.pb generated/block.pb generated/vm.pb generated/metadata.pb,fd_flamenco) # harness-only messages -$(call add-objs,generated/bundle.pb generated/vm_serialization.pb generated/gossip.pb generated/cost.pb generated/elf.pb generated/shred.pb,fd_flamenco_test) +$(call add-objs,generated/bundle.pb generated/vm_serialization.pb generated/gossip.pb generated/cost.pb generated/elf.pb,fd_flamenco_test) ifdef FD_HAS_HOSTED SOL_COMPAT_FLAGS:=-Wl,--version-script=src/flamenco/runtime/tests/libfd_exec_sol_compat.map diff --git a/src/flamenco/runtime/tests/fd_shred_harness.c b/src/flamenco/runtime/tests/fd_shred_harness.c deleted file mode 100644 index 98229b790c1..00000000000 --- a/src/flamenco/runtime/tests/fd_shred_harness.c +++ /dev/null @@ -1,460 +0,0 @@ -#include "fd_solfuzz_private.h" -#include "generated/shred.pb.h" - -#include "../../../ballet/shred/fd_shred.h" -#include "../../../disco/shred/fd_fec_resolver.h" -#include "../../../disco/metrics/fd_metrics.h" -#include "../../../discof/reasm/fd_reasm.h" -#include "../../../discof/replay/fd_sched.h" - -/* Resolver sizing. Production sizing lives in the shred tile setup; - the values below are much smaller because the harness consumes - results synchronously, so partial/complete queue depth has no - consumer-lag to absorb. See fd_fec_resolver.h for the precise - semantics of each depth parameter: - - DEPTH: max in-flight FEC sets before spill - - PARTIAL_DEPTH: out_shred pointer-lifetime guarantee - - COMPLETE_DEPTH: out_fec_set pointer-lifetime guarantee - - DONE_DEPTH: memory of completed (slot, fec_set_idx) - pairs used for duplicate detection - - SLOT_MAX: max slots tracked by the resolver */ -static ulong const RESOLVER_DEPTH = 32UL; -static ulong const RESOLVER_PARTIAL_DEPTH = 8UL; -static ulong const RESOLVER_COMPLETE_DEPTH = 8UL; -static ulong const RESOLVER_DONE_DEPTH = 256UL; -static ulong const RESOLVER_SLOT_MAX = 4096UL; - -/* Reasm + scheduler sizing. Only loosely tied to the resolver depths - above: large enough to cover any reasonable fuzz input while keeping - spad footprint bounded. */ -static ulong const REASM_POOL_MAX = 1024UL; - -/* Scheduler txn/block pool sizes, bounded to the fuzz input so - fd_sched_new's per-run free-list init stays cheap. */ -static ulong const SCHED_DEPTH = 4096UL; -static ulong const SCHED_BLOCK_CNT_MAX = 128UL; -static ulong const SCHED_EXEC_CNT = 4UL; - -/* Tick-verification parameters passed to fd_sched_block_verify_ticks. - ticks_per_slot=64 (tick_height 0, max_tick_height 64) and - hashes_per_tick=62500 to match mainnet and solfuzz-agave's reference - bank. */ -static ulong const SLOT_MAX_TICK_HEIGHT = 64UL; -static ulong const SLOT_HASHES_PER_TICK = 62500UL; - -typedef struct { - fd_hash_t mr; - fd_hash_t cmr; - ulong slot; - uint fec_set_idx; - ushort parent_off; - ushort num_data_shreds; - ushort num_coding_shreds; - uint shred_cnt; - ushort shred_sz[ FD_FEC_SHRED_CNT ]; - int data_complete; - int slot_complete; - ulong payload_sz; - uchar * payload; -} fd_shred_completed_fec_t; - -#define SORT_NAME sort_completed_fec -#define SORT_KEY_T fd_shred_completed_fec_t -#define SORT_BEFORE(a,b) ( (a).slot<(b).slot || ( (a).slot==(b).slot && (a).fec_set_idx<(b).fec_set_idx ) ) -#define SORT_QUICK_SWAP_MINIMIZE 1 /* ~240B key; skip no-op swaps */ -#include "../../../util/tmpl/fd_sort.c" - -/* Return reasm's evicted single-child chain to the pool. */ -static void -release_evicted_chain( fd_reasm_t * reasm, - fd_reasm_fec_t * evicted ) { - while( evicted ) { - fd_reasm_fec_t * next = fd_reasm_child( reasm, evicted ); - fd_reasm_pool_release( reasm, evicted ); - evicted = next; - } -} - -/* This is a harness simplification that avoids having to use store. - Also includes logic from the shred tile to concatenate FEC set - payloads into a contiguous buffer. */ -static void -capture_completed_fec( fd_spad_t * spad, - fd_fec_set_t const * set, - fd_shred_completed_fec_t * out ) { - fd_memset( out, 0, sizeof(fd_shred_completed_fec_t) ); - - fd_shred_t const * base_data = fd_shred_parse( set->data_shreds [ 0 ].b, FD_SHRED_MIN_SZ, FD_SHRED_PROTOCOL_BLK_MAX ); - fd_shred_t const * base_parity = fd_shred_parse( set->parity_shreds[ 0 ].b, FD_SHRED_MAX_SZ, FD_SHRED_PROTOCOL_BLK_MAX ); - FD_TEST( base_data && base_parity ); - - ushort data_cnt = base_parity->code.data_cnt; - ushort code_cnt = base_parity->code.code_cnt; - - /* Concatenate data-shred payloads into a single buffer. */ - uchar * payload = fd_spad_alloc( spad, alignof(uchar), (ulong)data_cnt*FD_SHRED_MAX_SZ ); - ulong payload_sz = 0UL; - for( ushort i=0U; idata_shreds[ i ].b, FD_SHRED_MIN_SZ, FD_SHRED_PROTOCOL_BLK_MAX ); - FD_TEST( shred ); - ulong shred_payload_sz = fd_shred_payload_sz( shred ); - if( FD_LIKELY( shred_payload_sz ) ) { - memcpy( payload + payload_sz, fd_shred_data_payload( shred ), shred_payload_sz ); - payload_sz += shred_payload_sz; - } - FD_TEST( shred_payload_sz<=USHORT_MAX ); - out->shred_sz[ i ] = (ushort)shred_payload_sz; - } - - fd_shred_t const * last = fd_shred_parse( set->data_shreds[ data_cnt-1U ].b, FD_SHRED_MIN_SZ, FD_SHRED_PROTOCOL_BLK_MAX ); - FD_TEST( last ); - - /* Derive the FEC set's merkle root from a shred's inclusion proof. */ - uchar bmtree_mem[ FD_BMTREE_COMMIT_FOOTPRINT( FD_SHRED_MERKLE_LAYER_CNT ) ] __attribute__((aligned(FD_BMTREE_COMMIT_ALIGN))); - fd_bmtree_node_t root[1]; - FD_TEST( fd_shred_merkle_root( base_data, bmtree_mem, root ) ); - memcpy( out->mr.hash, root->hash, sizeof(out->mr.hash) ); - out->slot = base_data->slot; - out->fec_set_idx = base_data->fec_set_idx; - out->parent_off = base_data->data.parent_off; - out->num_data_shreds = data_cnt; - out->num_coding_shreds = code_cnt; - out->shred_cnt = data_cnt; - out->data_complete = !!(last->data.flags & FD_SHRED_DATA_FLAG_DATA_COMPLETE); - out->slot_complete = !!(last->data.flags & FD_SHRED_DATA_FLAG_SLOT_COMPLETE); - out->payload_sz = payload_sz; - out->payload = payload; - - /* Shreds are always chained, so the chained root is present. */ - memcpy( out->cmr.hash, (uchar const *)base_data + fd_shred_chain_off( base_data->variant ), FD_SHRED_MERKLE_ROOT_SZ ); -} - -ulong -fd_solfuzz_pb_shred_run( fd_solfuzz_runner_t * runner, - void const * input_, - void ** output_, - void * output_buf, - ulong output_bufsz ) { - fd_exec_test_shred_parse_context_t const * input = fd_type_pun_const( input_ ); - fd_exec_test_shred_parse_effects_t ** output = fd_type_pun( output_ ); - - /* Initialize output protobuf in the caller-provided buffer. The - fixed-size header allocations (effects struct, per-shred result - array, per-FEC result array) must always fit; the caller is - responsible for sizing output_buf, and a fit-check failure here - is a harness wiring bug. Variable-size payload writes inside the - pop loop are checked separately and treated as soft failures. */ - ulong output_end = (ulong)output_buf + output_bufsz; - FD_SCRATCH_ALLOC_INIT( l, output_buf ); - - fd_exec_test_shred_parse_effects_t * effects = - FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_exec_test_shred_parse_effects_t), sizeof(fd_exec_test_shred_parse_effects_t) ); - FD_TEST( _l <= output_end ); - fd_memset( effects, 0, sizeof(*effects) ); - effects->block_parse_result = FD_EXEC_TEST_BLOCK_PARSE_RESULT_ACCEPTED; - - ulong shred_cnt = (ulong)input->shreds_count; - effects->shred_results_count = (pb_size_t)shred_cnt; - effects->shred_results = - FD_SCRATCH_ALLOC_APPEND( l, alignof(bool), sizeof(bool)*shred_cnt ); - FD_TEST( _l <= output_end ); - fd_memset( effects->shred_results, 0, sizeof(bool)*shred_cnt ); - - ulong max_fec_results = shred_cnt; - effects->fec_set_results = - FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_exec_test_fec_set_parse_result_t), sizeof(fd_exec_test_fec_set_parse_result_t)*max_fec_results ); - FD_TEST( _l <= output_end ); - fd_memset( effects->fec_set_results, 0, sizeof(fd_exec_test_fec_set_parse_result_t)*max_fec_results ); - effects->fec_set_results_count = 0U; - - /* The resolver/sched bump FD_MCNT counters (e.g. reject paths) via a - per-thread pointer that is NULL outside a tile. Register a dummy - write-sink to avoid a NULL deref; the values are never read. */ - static FD_TL uchar harness_metrics[ FD_METRICS_FOOTPRINT( 0 ) ] __attribute__((aligned(FD_METRICS_ALIGN))); - fd_metrics_register( (ulong *)fd_metrics_new( harness_metrics, 0UL ) ); - - /* Build a resolver configured for the fixture's shred version/root - context. */ - ulong const resolver_set_cnt = RESOLVER_DEPTH + RESOLVER_PARTIAL_DEPTH + RESOLVER_COMPLETE_DEPTH; - - fd_fec_set_t * resolver_sets = - fd_spad_alloc( runner->spad, alignof(fd_fec_set_t), sizeof(fd_fec_set_t)*resolver_set_cnt ); - - ulong resolver_footprint = fd_fec_resolver_footprint( RESOLVER_DEPTH, - RESOLVER_PARTIAL_DEPTH, - RESOLVER_COMPLETE_DEPTH, - RESOLVER_DONE_DEPTH ); - void * resolver_mem = fd_spad_alloc( runner->spad, fd_fec_resolver_align(), resolver_footprint ); - - /* Resolver/reasm/sched construction parameters are all compile-time - constants or sourced from the trusted runner spad -- a NULL return - from any of these constructors is a harness setup bug. */ - fd_fec_resolver_t * resolver = fd_fec_resolver_join( fd_fec_resolver_new( - resolver_mem, - RESOLVER_DEPTH, - RESOLVER_PARTIAL_DEPTH, - RESOLVER_COMPLETE_DEPTH, - RESOLVER_DONE_DEPTH, - RESOLVER_SLOT_MAX, - resolver_sets, - 0UL ) ); - FD_TEST( resolver ); - - /* Configure resolver behavior for the fuzz run: - - bypass Merkle proof + Ed25519 signature checks - (see fd_fec_resolver_set_bypass_verify in fd_fec_resolver.h) - - drop shreds for slots strictly older than root_slot */ - fd_fec_resolver_set_shred_version( resolver, (ushort)input->shred_version ); - fd_fec_resolver_set_bypass_verify( resolver, 1 ); - fd_fec_resolver_advance_slot_old ( resolver, input->root_slot ); - - /* Initialize reasm/sched so completed FEC sets can be - replay-ingested in order. */ - void * reasm_mem = fd_spad_alloc( runner->spad, fd_reasm_align(), fd_reasm_footprint( REASM_POOL_MAX ) ); - fd_reasm_t * reasm = fd_reasm_join( fd_reasm_new( reasm_mem, REASM_POOL_MAX, 0UL ) ); - FD_TEST( reasm ); - int reasm_initialized = 0; - - fd_rng_t rng_mem[1]; - fd_rng_t * rng = fd_rng_join( fd_rng_new( rng_mem, 0U, 0UL ) ); - void * sched_mem = fd_spad_alloc( runner->spad, fd_sched_align(), fd_sched_footprint( SCHED_DEPTH, SCHED_BLOCK_CNT_MAX, FD_SHRED_PROTOCOL_BLK_MAX, FD_MAX_TXN_PER_SLOT ) ); - fd_sched_t * sched = fd_sched_join( fd_sched_new( sched_mem, rng, SCHED_DEPTH, SCHED_BLOCK_CNT_MAX, FD_SHRED_PROTOCOL_BLK_MAX, FD_MAX_TXN_PER_SLOT, SCHED_EXEC_CNT, 0 ) ); - FD_TEST( sched ); - fd_sched_set_bypass_poh_verify( sched, 1 ); /* skip PoH end_hash compare */ - fd_sched_set_bypass_alut_resolution( sched, 1 ); /* skip ALUT resolution (no accdb) */ - fd_sched_block_add_done( sched, 0UL, ULONG_MAX, input->root_slot ); - fd_sched_root_notify( sched, 0UL ); - fd_sched_advance_root( sched, 0UL ); - - /* completed[] caches FEC payloads for sched ingestion. This is a - simplified version of store specifically for the harness. Instead - of querying store for FEC set data, we just store it in this - local array. */ - fd_shred_completed_fec_t * completed = - fd_spad_alloc( runner->spad, alignof(fd_shred_completed_fec_t), sizeof(fd_shred_completed_fec_t)*max_fec_results ); - ulong completed_cnt = 0UL; - uint next_bank_idx = 1U; /* 0 is the reasm/sched root, so start at 1 */ - - /* Parse each shred, feed resolver, and only continue when an FEC - completes. */ - fd_pubkey_t dummy_leader_pubkey = {0}; - for( ulong i=0UL; ishreds[ i ]; - FD_TEST( shred_msg ); - FD_TEST( shred_msg->size>=FD_SHRED_MIN_SZ && shred_msg->size<=FD_SHRED_MAX_SZ ); - - /* Step 1: fd_shred_parse() */ - fd_shred_t const * shred = fd_shred_parse( shred_msg->bytes, shred_msg->size, FD_SHRED_PROTOCOL_BLK_MAX ); - - /* Unchained merkle shreds are dropped in the shred tile. */ - if( !shred || !fd_shred_is_chained( fd_shred_type( shred->variant ) ) ) { - effects->shred_results[ i ] = false; - continue; - } - - /* Parse-level accept, matching Agave; the resolver verdict below is not folded in. */ - effects->shred_results[ i ] = true; - - /* Step 2: fd_fec_resolver_add_shred() */ - fd_fec_set_t const * out_fec_set = NULL; - fd_shred_t const * out_shred = NULL; - fd_bmtree_node_t out_merkle_root[1]; - int rc = fd_fec_resolver_add_shred( - resolver, - shred, - shred_msg->size, - FD_SHRED_PROTOCOL_BLK_MAX, - FD_FEC_RESOLVER_SHRED_SRC_TURBINE, - dummy_leader_pubkey.uc, - &out_fec_set, - &out_shred, - out_merkle_root, - NULL - ); - (void)out_shred; - - /* We only want to replay the FEC set once it's complete. */ - if( rc!=FD_FEC_RESOLVER_SHRED_COMPLETES ) continue; - - /* completed[] is sized to shred_cnt, which is a strict upper bound - on completion count, so overflow here is a harness sizing bug. */ - FD_TEST( completed_cntspad, out_fec_set, rec ); - - /* Buffer the completion; reasm runs after the shred loop in - (slot, fec_set_idx) order. */ - completed_cnt++; - } - - /* Sort by (slot, fec_set_idx) so set 0 anchors the reasm root - before any successor inserts. */ - sort_completed_fec_inplace( completed, completed_cnt ); - - /* One result per completed FEC set, as both validators complete sets - whether or not the slot is connected: per slot, the contiguous run - from index 0 in which every set chains to the previous one's - Merkle root. Delivery and ingest below only drive the block - verdict. */ - for( ulong k=0UL; kslot ); - if( first_in_slot ) { - if( rec->fec_set_idx!=0U ) { while( k+1ULslot ) k++; continue; } - } else { - fd_shred_completed_fec_t * prev = &completed[ k-1UL ]; - if( rec->fec_set_idx!=prev->fec_set_idx+FD_FEC_SHRED_CNT || memcmp( rec->cmr.hash, prev->mr.hash, sizeof(fd_hash_t) ) ) { - while( k+1ULslot ) k++; - continue; - } - } - FD_TEST( effects->fec_set_results_countfec_set_results[ effects->fec_set_results_count++ ]; - fd_memset( out_fec, 0, sizeof(*out_fec) ); - out_fec->completed = true; - out_fec->slot = rec->slot; - out_fec->fec_set_index = rec->fec_set_idx; - out_fec->parent_offset = rec->parent_off; - out_fec->shred_version = input->shred_version; - out_fec->num_data_shreds = rec->num_data_shreds; - out_fec->num_coding_shreds = rec->num_coding_shreds; - memcpy( out_fec->merkle_root, rec->mr.hash, FD_SHRED_MERKLE_ROOT_SZ ); - memcpy( out_fec->chained_merkle_root, rec->cmr.hash, FD_SHRED_MERKLE_ROOT_SZ ); - out_fec->payload_hash = fd_solfuzz_hash( rec->payload, rec->payload_sz ); - } - - /* The root block id is the chained Merkle root of the first completed - FEC set that chains to the root slot. Without one, every completed - set is an orphan: the reassembler would hold it and deliver nothing, - so there is nothing to insert. */ - for( ulong k=0UL; kfec_set_idx!=0U || rec->slotparent_off || rec->slot-rec->parent_off!=input->root_slot ) continue; - fd_reasm_fec_t * root = fd_reasm_init( reasm, &rec->cmr, input->root_slot ); - if( FD_UNLIKELY( !root ) ) { - effects->block_parse_result = FD_EXEC_TEST_BLOCK_PARSE_RESULT_REJECTED_INVALID_HEADER; - break; - } - root->bank_idx = 0UL; - reasm_initialized = 1; - } - - for( ulong k=0UL; kmr, - &rec->cmr, - rec->slot, - rec->fec_set_idx, - rec->parent_off, - rec->num_data_shreds, - rec->data_complete, - rec->slot_complete, - 0, - NULL, - NULL, - &evicted ) ) { - release_evicted_chain( reasm, evicted ); - effects->block_parse_result = FD_EXEC_TEST_BLOCK_PARSE_RESULT_REJECTED_INVALID_HEADER; - continue; - } - release_evicted_chain( reasm, evicted ); - - /* Step 4: fd_reasm_pop() and fd_fec_set_ingest() while there are - completed FEC sets remaining. */ - fd_reasm_fec_t * popped; - while( FD_LIKELY( (popped = fd_reasm_pop( reasm )) ) ) { - /* Query our "store"-adjacent structure (completed) for the FEC - set payload */ - fd_shred_completed_fec_t * popped_rec = NULL; - for( ulong j=0UL; jkey.hash, sizeof(fd_hash_t) ) ) ) { - popped_rec = &completed[ j ]; - break; - } - } - FD_TEST( popped_rec ); - - /* Match Agave's model of only parsing deshreddable batches. - Otherwise, Firedancer's eager per-FEC parsing might reject a - block for a bad FEC set that Agave simply doesn't parse at all. - A FEC set is deshreddable iff some FEC set at or after it in - the same slot carries DATA_COMPLETE. Complete batches are - still fed one FEC set at a time. Only the final, potentially - incomplete batch is held back. fec_set_results are captured - from the completed sets above, independent of the scheduler. */ - int deshreddable = 0; - for( ulong j=0UL; jslot && completed[ j ].fec_set_idx>=popped_rec->fec_set_idx && completed[ j ].data_complete ) { - deshreddable = 1; - break; - } - } - if( !deshreddable ) continue; - - /* Bank lineage comes from the reasm tree, like the replay tile. - Reasm pops parents before children, so the parent's bank_idx - was already recorded by the time we read it here. */ - fd_reasm_fec_t * parent = fd_reasm_parent( reasm, popped ); - FD_TEST( parent ); - uint parent_bank_idx = parent->bank_idx; - - /* If the FEC set starts a new slot (no bank yet), use a fresh - bank index; otherwise reuse the parent's. Record it on the - node so this slot's later FECs inherit it. */ - uint bank_idx = ( popped->fec_set_idx==0U ) ? next_bank_idx++ : parent_bank_idx; - popped->bank_idx = bank_idx; - - fd_store_fec_t store_fec[1] = {0}; - store_fec->key = popped_rec->mr; - FD_TEST( popped_rec->payload_sz<=UINT_MAX ); - store_fec->data_sz = (uint)popped_rec->payload_sz; - memcpy( store_fec->shred_sz, popped_rec->shred_sz, sizeof(store_fec->shred_sz) ); - - fd_sched_fec_t sched_fec = { - .bank_idx = bank_idx, - .parent_bank_idx = parent_bank_idx, - .slot = popped_rec->slot, - .parent_slot = fd_ulong_if( popped_rec->slot>=popped_rec->parent_off, popped_rec->slot-popped_rec->parent_off, 0UL ), - .fec = store_fec, - .data = popped_rec->payload, - .shred_cnt = popped_rec->shred_cnt, - .is_last_in_batch = !!popped_rec->data_complete, - .is_last_in_block = !!popped_rec->slot_complete, - .is_first_in_block = !!(popped_rec->fec_set_idx==0U) - }; - - /* Drain abandoned blocks; fd_sched_fec_ingest requires an empty - ref_q. The replay tile drains the same queue (to decrement bank - refcounts); the harness has no banks, so it just discards. */ - while( fd_sched_pruned_block_next( sched )!=ULONG_MAX ) {} - - /* Final step: ingest the completed FEC set. */ - FD_TEST( fd_sched_fec_can_ingest( sched, &sched_fec ) ); - if( !fd_sched_fec_ingest( sched, &sched_fec ) ) { - effects->block_parse_result = FD_EXEC_TEST_BLOCK_PARSE_RESULT_REJECTED_INVALID_HEADER; - } else if( FD_LIKELY( bank_idx!=0UL ) ) { - /* Harness stops at FEC ingest, so verify this slot's tick - window here; skip bank_idx 0 (reasm/sched root). Only - complete batches reach this point, so this matches Agave's - model of only verifying deshredded batches. */ - if( fd_sched_block_verify_ticks( sched, bank_idx, 0UL, SLOT_MAX_TICK_HEIGHT, SLOT_HASHES_PER_TICK ) ) { - effects->block_parse_result = FD_EXEC_TEST_BLOCK_PARSE_RESULT_REJECTED_INVALID_HEADER; - } - } - } - } - - /* Finalize scratch allocation and return encoded effects span. */ - ulong actual_end = FD_SCRATCH_ALLOC_FINI( l, 1UL ); - *output = effects; - return actual_end - (ulong)output_buf; -} diff --git a/src/flamenco/runtime/tests/fd_sol_compat.c b/src/flamenco/runtime/tests/fd_sol_compat.c index ec904a6d2e8..305c6e596a2 100644 --- a/src/flamenco/runtime/tests/fd_sol_compat.c +++ b/src/flamenco/runtime/tests/fd_sol_compat.c @@ -12,7 +12,6 @@ #include "generated/cost.pb.h" #include "generated/elf.pb.h" #include "generated/vm_serialization.pb.h" -#include "generated/shred.pb.h" #include #include @@ -332,26 +331,3 @@ sol_compat_vm_serialize_execute_v1( uchar * out, fd_solfuzz_runner_leak_check( runner ); return ok; } - -int -sol_compat_shred_parse_v1( uchar * out, - ulong * out_sz, - uchar const * in, - ulong in_sz ) { - fd_exec_test_shred_parse_context_t input[1] = {0}; - void * res = sol_compat_decode_lenient( &input, in, in_sz, &fd_exec_test_shred_parse_context_t_msg ); - if( FD_UNLIKELY( !res ) ) return 0; - - int ok = 0; - fd_spad_push( runner->spad ); - void * output = NULL; - fd_solfuzz_pb_execute_wrapper( runner, input, &output, fd_solfuzz_pb_shred_run ); - if( output ) { - ok = !!sol_compat_encode( out, out_sz, output, &fd_exec_test_shred_parse_effects_t_msg ); - } - fd_spad_pop( runner->spad ); - - pb_release( &fd_exec_test_shred_parse_context_t_msg, input ); - fd_solfuzz_runner_leak_check( runner ); - return ok; -} diff --git a/src/flamenco/runtime/tests/fd_sol_compat.h b/src/flamenco/runtime/tests/fd_sol_compat.h index b8f0886ee36..c1fd084c0b1 100644 --- a/src/flamenco/runtime/tests/fd_sol_compat.h +++ b/src/flamenco/runtime/tests/fd_sol_compat.h @@ -132,12 +132,6 @@ sol_compat_vm_serialize_execute_v1( uchar * out, uchar const * in, ulong in_sz ); -int -sol_compat_shred_parse_v1( uchar * out, - ulong * out_sz, - uchar const * in, - ulong in_sz ); - FD_PROTOTYPES_END #endif /* HEADER_fd_src_flamenco_runtime_tests_fd_sol_compat_h */ diff --git a/src/flamenco/runtime/tests/fd_solfuzz.h b/src/flamenco/runtime/tests/fd_solfuzz.h index 572c0794d9b..cbc0c948e85 100644 --- a/src/flamenco/runtime/tests/fd_solfuzz.h +++ b/src/flamenco/runtime/tests/fd_solfuzz.h @@ -170,20 +170,6 @@ fd_solfuzz_pb_block_fixture( fd_solfuzz_runner_t * runner, uchar const * in, ulong in_sz ); -/* Shred parse / FEC / replay ingestion harness */ - -ulong -fd_solfuzz_pb_shred_run( fd_solfuzz_runner_t * runner, - void const * input_, - void ** output_, - void * output_buf, - ulong output_bufsz ); - -int -fd_solfuzz_pb_shred_fixture( fd_solfuzz_runner_t * runner, - uchar const * in, - ulong in_sz ); - /* SVM Program Loading */ ulong diff --git a/src/flamenco/runtime/tests/fd_solfuzz_exec.c b/src/flamenco/runtime/tests/fd_solfuzz_exec.c index 6eacb6b111c..81c141cd7a9 100644 --- a/src/flamenco/runtime/tests/fd_solfuzz_exec.c +++ b/src/flamenco/runtime/tests/fd_solfuzz_exec.c @@ -10,7 +10,6 @@ #include "generated/vm_serialization.pb.h" #include "generated/cost.pb.h" #include "generated/elf.pb.h" -#include "generated/shred.pb.h" #include "generated/gossip.pb.h" #include "../fd_executor_err.h" @@ -359,26 +358,6 @@ fd_solfuzz_pb_elf_loader_fixture( fd_solfuzz_runner_t * runner, return ok; } -int -fd_solfuzz_pb_shred_fixture( fd_solfuzz_runner_t * runner, - uchar const * in, - ulong in_sz ) { - fd_exec_test_shred_parse_fixture_t fixture[1] = {0}; - if( !sol_compat_decode_lenient( &fixture, in, in_sz, &fd_exec_test_shred_parse_fixture_t_msg ) ) { - FD_LOG_WARNING(( "Invalid shred fixture." )); - return 0; - } - - fd_spad_push( runner->spad ); - void * output = NULL; - fd_solfuzz_pb_execute_wrapper( runner, &fixture->input, &output, fd_solfuzz_pb_shred_run ); - int ok = sol_compat_cmp_binary_strict( output, &fixture->output, &fd_exec_test_shred_parse_effects_t_msg, runner->spad ); - fd_spad_pop( runner->spad ); - - pb_release( &fd_exec_test_shred_parse_fixture_t_msg, fixture ); - return ok; -} - int fd_solfuzz_pb_vm_serialize_fixture( fd_solfuzz_runner_t * runner, uchar const * in, diff --git a/src/flamenco/runtime/tests/test_sol_compat.c b/src/flamenco/runtime/tests/test_sol_compat.c index d717f19ed83..90a8b042901 100644 --- a/src/flamenco/runtime/tests/test_sol_compat.c +++ b/src/flamenco/runtime/tests/test_sol_compat.c @@ -31,7 +31,6 @@ static uint shutdown_signal __attribute__((aligned(64))); #define FIXTURE_TYPE_PB_BLOCK 0x04 #define FIXTURE_TYPE_PB_ELF_LOADER 0x05 #define FIXTURE_TYPE_PB_BUNDLE 0x06 -#define FIXTURE_TYPE_PB_SHRED 0x07 #define FIXTURE_TYPE_PB_COST 0x08 #define FIXTURE_TYPE_PB_VM_SERIALIZE 0x09 #define FIXTURE_TYPE_PB_GOSSIP 0x0a @@ -72,7 +71,6 @@ run_test1( fd_solfuzz_runner_t * runner, else if( strstr( path, "/syscall/fixtures/" ) ) type = FIXTURE_TYPE_PB_SYSCALL; else if( strstr( path, "/block/fixtures/" ) ) type = FIXTURE_TYPE_PB_BLOCK; else if( strstr( path, "/bundle/fixtures/" ) ) type = FIXTURE_TYPE_PB_BUNDLE; - else if( strstr( path, "/shred/fixtures/" ) ) type = FIXTURE_TYPE_PB_SHRED; else if( strstr( path, "/cost/fixtures/" ) ) type = FIXTURE_TYPE_PB_COST; else if( strstr( path, "/vm_serialization/fixtures/" ) ) type = FIXTURE_TYPE_PB_VM_SERIALIZE; else if( strstr( path, "/gossip/fixtures/" ) ) type = FIXTURE_TYPE_PB_GOSSIP; @@ -101,9 +99,6 @@ run_test1( fd_solfuzz_runner_t * runner, case FIXTURE_TYPE_PB_BUNDLE: ok = fd_solfuzz_pb_bundle_fixture( runner, buf, file_sz ); break; - case FIXTURE_TYPE_PB_SHRED: - ok = fd_solfuzz_pb_shred_fixture( runner, buf, file_sz ); - break; case FIXTURE_TYPE_PB_COST: ok = fd_solfuzz_pb_cost_fixture( runner, buf, file_sz ); break; @@ -454,7 +449,7 @@ main( int argc, " --wksp [file path] Reuse existing workspace\n" " --wksp-tag 1 Workspace allocation tag\n" " --fail-fast 1 Stop executing after first failure?\n" - " --type pb_{instr,txn,bundle,elf_loader,syscall,block,shred,cost,vm_serialize,gossip}\n" + " --type pb_{instr,txn,bundle,elf_loader,syscall,block,cost,vm_serialize,gossip}\n" "\n", stderr ); return 0; @@ -530,7 +525,6 @@ main( int argc, else if( 0==strcmp( type_str, "pb_syscall" ) ) g_type_override = FIXTURE_TYPE_PB_SYSCALL; else if( 0==strcmp( type_str, "pb_block" ) ) g_type_override = FIXTURE_TYPE_PB_BLOCK; else if( 0==strcmp( type_str, "pb_bundle" ) ) g_type_override = FIXTURE_TYPE_PB_BUNDLE; - else if( 0==strcmp( type_str, "pb_shred" ) ) g_type_override = FIXTURE_TYPE_PB_SHRED; else if( 0==strcmp( type_str, "pb_cost" ) ) g_type_override = FIXTURE_TYPE_PB_COST; else if( 0==strcmp( type_str, "pb_vm_serialize" ) ) g_type_override = FIXTURE_TYPE_PB_VM_SERIALIZE; else if( 0==strcmp( type_str, "pb_gossip" ) ) g_type_override = FIXTURE_TYPE_PB_GOSSIP; diff --git a/src/flamenco/runtime/tests/test_sol_compat_so.c b/src/flamenco/runtime/tests/test_sol_compat_so.c index 705bccefca7..ad832754c87 100644 --- a/src/flamenco/runtime/tests/test_sol_compat_so.c +++ b/src/flamenco/runtime/tests/test_sol_compat_so.c @@ -18,7 +18,7 @@ __attribute__((noreturn)) static void usage( void ) { fputs( "Usage: test_sol_compat_so --target --type [Protobuf input files ...]\n" - "Supported types: instr, txn, txn_cost, block, vm_syscall, shred_parse\n", + "Supported types: instr, txn, txn_cost, block, vm_syscall\n", stderr ); exit( 1 ); } @@ -76,7 +76,6 @@ main( int argc, else if( 0==strcmp( type, "txn_cost" ) ) fn_name = "sol_compat_txn_cost_v1"; else if( 0==strcmp( type, "block" ) ) fn_name = "sol_compat_block_execute_v1"; else if( 0==strcmp( type, "vm_syscall" ) ) fn_name = "sol_compat_vm_syscall_execute_v1"; - else if( 0==strcmp( type, "shred_parse" ) ) fn_name = "sol_compat_shred_parse_v1"; else usage(); __typeof__(&sol_compat_instr_execute_v1) execute_fn = dlsym( so, fn_name ); From 2c126158a16f1b02816137d6d76371126bd4227b Mon Sep 17 00:00:00 2001 From: Richard Patel Date: Wed, 21 Oct 2026 17:17:51 +0000 Subject: [PATCH 10/13] clean up Alpenglow conditionals --- src/app/firedancer/callbacks.c | 4 +- src/app/firedancer/topology.c | 5 - .../shared_dev/commands/configure/genesis.c | 2 - src/disco/gui/fd_gui_tile.c | 2 +- src/disco/shred/fd_shred_tile.c | 14 +- src/disco/store/fd_store.c | 72 +-- src/disco/store/fd_store.h | 41 +- src/disco/store/test_store.c | 163 ++--- src/disco/topo/fd_topo.h | 6 - src/discof/repair/test_rserve_tile.c | 4 +- src/discof/replay/fd_replay_tile.c | 2 +- src/discof/replay/fd_sched.c | 26 +- src/discof/replay/fd_sched.h | 3 +- src/discof/replay/fuzz_sched_rdisp.c | 173 +++-- src/discof/replay/test_replay_tile.c | 12 +- src/discof/replay/test_sched.c | 610 +++++------------- src/discof/restore/fd_snapin_tile.c | 4 +- src/discof/rpc/fd_rpc_tile.c | 13 +- src/discof/rpc/test_rpc_tile.c | 10 - src/flamenco/genesis/fd_genesis_create.c | 60 +- src/flamenco/genesis/fd_genesis_create.h | 6 - src/flamenco/genesis/test_genesis_create.c | 7 +- 22 files changed, 422 insertions(+), 817 deletions(-) diff --git a/src/app/firedancer/callbacks.c b/src/app/firedancer/callbacks.c index 1f60d74f7a0..de275697436 100644 --- a/src/app/firedancer/callbacks.c +++ b/src/app/firedancer/callbacks.c @@ -127,7 +127,7 @@ fd_topo_obj_callbacks_t fd_obj_cb_fec_sets = { static ulong store_footprint( fd_topo_t const * topo, fd_topo_obj_t const * obj ) { - return fd_store_footprint( VAL("fec_max"), VAL("fec_data_max"), VAL("shred_storage_gib"), VAL("shred_cache_bytes"), VAL("fec_set_cnt"), 1, VAL("shred_tile_cnt") ); + return fd_store_footprint( VAL("fec_max"), VAL("fec_data_max"), VAL("shred_storage_gib"), VAL("shred_cache_bytes"), VAL("fec_set_cnt"), VAL("shred_tile_cnt") ); } static ulong @@ -144,7 +144,7 @@ store_new( fd_topo_t const * topo, VAL("fec_max"), VAL("fec_data_max"), VAL("shred_storage_gib"), VAL("shred_cache_bytes"), VAL("fec_set_cnt"), VAL("max_shreds_per_block"), - disk_seed, 1, VAL("shred_tile_cnt") ) ); + disk_seed, VAL("shred_tile_cnt") ) ); } fd_topo_obj_callbacks_t fd_obj_cb_store = { diff --git a/src/app/firedancer/topology.c b/src/app/firedancer/topology.c index 106accec862..669c07f2d7e 100644 --- a/src/app/firedancer/topology.c +++ b/src/app/firedancer/topology.c @@ -1552,7 +1552,6 @@ fd_topo_configure_tile( fd_topo_tile_t * tile, tile->snapin.expected_shred_version = config->consensus.expected_shred_version; tile->snapin.max_live_slots = config->firedancer.runtime.max_live_slots; tile->snapin.max_txn_per_slot = config->limits.max_txn_per_slot; - tile->snapin.alpenglow = 1; tile->snapin.accdb_obj_id = fd_pod_query_ulong( config->topo.props, "accdb", ULONG_MAX ); tile->snapin.txncache_obj_id = fd_pod_query_ulong( config->topo.props, "txncache", ULONG_MAX ); tile->snapin.banks_obj_id = fd_pod_query_ulong( config->topo.props, "banks", ULONG_MAX ); @@ -1599,7 +1598,6 @@ fd_topo_configure_tile( fd_topo_tile_t * tile, tile->replay.expected_shred_version = config->consensus.expected_shred_version; tile->replay.wait_for_vote_to_start_leader = config->consensus.wait_for_vote_to_start_leader; - tile->replay.alpenglow = 1; tile->replay.sched_depth = config->tiles.replay.max_transaction_lookahead_buffer_size; wfs_decode_bank_hash( config, &tile->replay.wait_for_supermajority_with_bank_hash ); @@ -1774,7 +1772,6 @@ fd_topo_configure_tile( fd_topo_tile_t * tile, tile->shred.expected_shred_version = config->consensus.expected_shred_version; tile->shred.shred_listen_port = config->tiles.shred.shred_listen_port; tile->shred.max_shreds_per_block = config->limits.max_shreds_per_block; - tile->shred.alpenglow = 1; tile->shred.bench_max_shreds_per_block = config->development.bench.max_shreds_per_block; tile->shred.adtl_dests_retransmit_cnt = config->tiles.shred.additional_shred_destinations_retransmit_cnt; tile->shred.adtl_dests_leader_cnt = config->tiles.shred.additional_shred_destinations_leader_cnt; @@ -1821,7 +1818,6 @@ fd_topo_configure_tile( fd_topo_tile_t * tile, tile->gui.listen_port = config->tiles.gui.gui_listen_port; tile->gui.max_txn_per_slot = config->limits.max_txn_per_slot; tile->gui.is_voting = strcmp( config->paths.vote_account, "" ); - tile->gui.is_alpenglow = 1; fd_cstr_ncpy( tile->gui.cluster, config->cluster, sizeof(tile->gui.cluster) ); fd_cstr_ncpy( tile->gui.identity_key_path, config->paths.identity_key, sizeof(tile->gui.identity_key_path) ); fd_cstr_ncpy( tile->gui.vote_key_path, config->paths.vote_account, sizeof(tile->gui.vote_key_path) ); @@ -1846,7 +1842,6 @@ fd_topo_configure_tile( fd_topo_tile_t * tile, parse_listen_addr( config->tiles.rpc.rpc_listen_address, "tiles.rpc.rpc_listen_address", &tile->rpc.listen_addr ); tile->rpc.listen_port = config->tiles.rpc.rpc_listen_port; tile->rpc.delay_startup = config->tiles.rpc.delay_startup; - tile->rpc.alpenglow = 1; tile->rpc.max_http_connections = config->tiles.rpc.max_http_connections; tile->rpc.max_websocket_connections = config->tiles.rpc.max_websocket_connections; tile->rpc.send_buffer_size_mb = config->tiles.rpc.send_buffer_size_mb; diff --git a/src/app/shared_dev/commands/configure/genesis.c b/src/app/shared_dev/commands/configure/genesis.c index 1ccc973c1f8..be33ec2f2fd 100644 --- a/src/app/shared_dev/commands/configure/genesis.c +++ b/src/app/shared_dev/commands/configure/genesis.c @@ -194,8 +194,6 @@ create_genesis( config_t const * config, } - /* Firedancer only bootstraps Alpenglow clusters */ - options->alpenglow = 1; options->hashes_per_tick = 0UL; /* PoH is in low power mode */ options->ticks_per_slot = config->development.genesis.ticks_per_slot; diff --git a/src/disco/gui/fd_gui_tile.c b/src/disco/gui/fd_gui_tile.c index f4431d2ced1..c992028e9f8 100644 --- a/src/disco/gui/fd_gui_tile.c +++ b/src/disco/gui/fd_gui_tile.c @@ -965,7 +965,7 @@ unprivileged_init( fd_topo_t const * topo, accdb_shmem = fd_accdb_shmem_join( accdb_shmem_raw ); FD_TEST( accdb_shmem ); } - ctx->gui = fd_gui_join( fd_gui_new( _gui, ctx->gui_server, fd_version_cstr, tile->gui.cluster, ctx->identity_key, ctx->has_vote_key, ctx->vote_key->uc, tile->gui.is_alpenglow, tile->gui.max_live_slots, tile->gui.max_txn_per_slot, ctx->snapshots_enabled, tile->gui.is_voting, tile->gui.schedule_strategy, tile->gui.wfs_bank_hash, tile->gui.expected_shred_version, tile->gui.wfs_slot, tile->gui.accounts_database_path, tile->gui.gui_database_path, ctx->db, ctx->topo, accdb_shmem, fd_clock_tile_now( ctx->clock ) ) ); + ctx->gui = fd_gui_join( fd_gui_new( _gui, ctx->gui_server, fd_version_cstr, tile->gui.cluster, ctx->identity_key, ctx->has_vote_key, ctx->vote_key->uc, 1, tile->gui.max_live_slots, tile->gui.max_txn_per_slot, ctx->snapshots_enabled, tile->gui.is_voting, tile->gui.schedule_strategy, tile->gui.wfs_bank_hash, tile->gui.expected_shred_version, tile->gui.wfs_slot, tile->gui.accounts_database_path, tile->gui.gui_database_path, ctx->db, ctx->topo, accdb_shmem, fd_clock_tile_now( ctx->clock ) ) ); FD_TEST( ctx->gui ); FD_TEST( ctx->db ); diff --git a/src/disco/shred/fd_shred_tile.c b/src/disco/shred/fd_shred_tile.c index f20598bf455..24b52aee3a8 100644 --- a/src/disco/shred/fd_shred_tile.c +++ b/src/disco/shred/fd_shred_tile.c @@ -243,7 +243,6 @@ typedef struct { fd_store_t * store; fd_store_map_t map_join[1]; int disk_fd; - int alpenglow; fd_gossip_update_message_t gossip_upd_buf[1]; @@ -903,8 +902,7 @@ complete_fec_set( fd_shred_ctx_t * ctx, /* Compute merkle root and chained merkle root. */ - int replay_fwd = 1; - int again = 0; + int again = 0; if( FD_LIKELY( ctx->store ) ) { fd_hash_t const * mr = fd_type_pun_const( merkle_root ); @@ -939,10 +937,9 @@ complete_fec_set( fd_shred_ctx_t * ctx, fd_store_fec_t * fec; again = fd_store_insert( ctx->store, ctx->map_join, mr, last->slot, ctx->round_robin_id, data_idx, data_sz, shred_sz, &fec )==FD_MAP_ERR_KEY; } - replay_fwd = !again || ctx->alpenglow; } - if( FD_LIKELY( ctx->shred_out_idx!=ULONG_MAX && replay_fwd ) ) { + if( FD_LIKELY( ctx->shred_out_idx!=ULONG_MAX ) ) { /* Send all of the data shreds we recovered (weren't received). The chunks are all written before any is published, so the @@ -1174,8 +1171,7 @@ after_frag( fd_shred_ctx_t * ctx, /* Send rotor future slots past max_live_slots so that unstaked nodes can catch up. */ - if( FD_UNLIKELY( ctx->alpenglow && - rv==FD_FEC_RESOLVER_SHRED_IGNORED && + if( FD_UNLIKELY( rv==FD_FEC_RESOLVER_SHRED_IGNORED && shred_source==FD_FEC_RESOLVER_SHRED_SRC_TURBINE && shred->version==ctx->shredder->shred_version && shred->slot>ctx->future_slot && @@ -1323,8 +1319,6 @@ unprivileged_init( fd_topo_t const * topo, FD_TEST( ctx->store->magic==FD_STORE_MAGIC ); FD_TEST( fd_store_map_ljoin( ctx->store, ctx->map_join ) ); } - ctx->alpenglow = tile->shred.alpenglow; - /* If the default partial_depth is ever changed, correspondingly change the size of the fd_fec_intra_pool in fd_fec_repair. */ ulong fec_resolver_footprint = fd_fec_resolver_footprint( tile->shred.fec_resolver_depth, 1UL, fec_exposure+1UL, @@ -1412,7 +1406,7 @@ unprivileged_init( fd_topo_t const * topo, tile->shred.fec_resolver_depth, 1UL, fec_exposure+1UL, 128UL * tile->shred.fec_resolver_depth, - tile->shred.alpenglow ? tile->shred.slot_max : 0UL, + tile->shred.slot_max, resolver_sets, ctx->resolver_seed ) ) ); FD_TEST( fd_sha512_join( fd_sha512_new( ctx->sha512 ) ) ); diff --git a/src/disco/store/fd_store.c b/src/disco/store/fd_store.c index 4588cee4b16..ef7a582d81a 100644 --- a/src/disco/store/fd_store.c +++ b/src/disco/store/fd_store.c @@ -324,7 +324,6 @@ fd_store_new( void * shmem, ulong fec_set_cnt, ulong max_shreds_per_block, ulong seed, - int alpenglow, ulong shred_tile_cnt ) { if( FD_UNLIKELY( !shmem ) ) { FD_LOG_WARNING(( "NULL shmem" )); return NULL; } @@ -334,20 +333,19 @@ fd_store_new( void * shmem, if( FD_UNLIKELY( !fec_data_max ) ) { FD_LOG_WARNING(( "fec_data_max must be non-zero" )); return NULL; } if( FD_UNLIKELY( fec_data_max>UINT_MAX ) ) { FD_LOG_WARNING(( "fec_data_max must fit in uint" )); return NULL; } if( FD_UNLIKELY( !max_shreds_per_block || max_shreds_per_block>FD_SHREDB_HINT_VALID ) ) { FD_LOG_WARNING(( "bad max_shreds_per_block (%lu)", max_shreds_per_block )); return NULL; } - if( FD_UNLIKELY( alpenglow && !shred_tile_cnt ) ) { FD_LOG_WARNING(( "alpenglow needs a slot_to_fecs partition per shred tile" )); return NULL; } + if( FD_UNLIKELY( !shred_tile_cnt ) ) { FD_LOG_WARNING(( "shred_tile_cnt must be non-zero" )); return NULL; } if( FD_UNLIKELY( shred_storage_gib>FD_SHREDB_MAX_SIZE_GIB ) ) { FD_LOG_ERR(( "shred database size limit is %lu GiB, but the maximum supported size is %lu GiB", shred_storage_gib, FD_SHREDB_MAX_SIZE_GIB )); } - ulong footprint = fd_store_footprint( fec_max, fec_data_max, shred_storage_gib, shred_cache_bytes, fec_set_cnt, alpenglow, shred_tile_cnt ); + ulong footprint = fd_store_footprint( fec_max, fec_data_max, shred_storage_gib, shred_cache_bytes, fec_set_cnt, shred_tile_cnt ); if( FD_UNLIKELY( !footprint ) ) { FD_LOG_WARNING(( "invalid or overflowing store footprint" )); return NULL; } fd_wksp_t * wksp = fd_wksp_containing( shmem ); if( FD_UNLIKELY( !wksp ) ) { FD_LOG_WARNING(( "shmem must be part of a workspace" )); return NULL; } ulong chain_cnt = fd_store_map_chain_cnt_est( fec_max ); - ulong part_cnt = alpenglow ? shred_tile_cnt : 0UL; ulong slot_chain_cnt = fd_store_slot_to_fecs_chain_cnt_est( fec_max ); ulong slot_to_fecs_sz = fd_ulong_align_up( fd_store_slot_to_fecs_footprint( slot_chain_cnt ), fd_store_slot_to_fecs_align() ); ulong payload_slot_sz = fd_store_payload_slot_sz( fec_data_max ); @@ -360,7 +358,7 @@ fd_store_new( void * shmem, void * map = FD_SCRATCH_ALLOC_APPEND( l, fd_store_map_align(), fd_store_map_footprint( chain_cnt ) ); void * shpool = FD_SCRATCH_ALLOC_APPEND( l, fd_store_pool_align(), fd_store_pool_footprint() ); void * shele = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_store_fec_t), sizeof(fd_store_fec_t)*fec_max ); - uchar * slot_to_fecs = FD_SCRATCH_ALLOC_APPEND( l, fd_store_slot_to_fecs_align(), slot_to_fecs_sz*part_cnt ); + uchar * slot_to_fecs = FD_SCRATCH_ALLOC_APPEND( l, fd_store_slot_to_fecs_align(), slot_to_fecs_sz*shred_tile_cnt ); uchar * cache_mem = FD_SCRATCH_ALLOC_APPEND( l, FD_STORE_PAYLOAD_PAGE_SZ, payload_slot_sz*cache_slot_cnt ); ulong * cache_free = FD_SCRATCH_ALLOC_APPEND( l, alignof(ulong), sizeof(ulong)*cache_slot_cnt ); uint * spill_free = FD_SCRATCH_ALLOC_APPEND( l, alignof(uint), sizeof(uint)*fec_max ); @@ -389,11 +387,9 @@ fd_store_new( void * shmem, shred_pool_mem = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_shredb_shred_entry_t), max_shreds * sizeof(fd_shredb_shred_entry_t) ); shred_tag_mem = FD_SCRATCH_ALLOC_APPEND( l, alignof(atomic_ulong), max_shreds * sizeof(atomic_ulong) ); slot_hint_mem = FD_SCRATCH_ALLOC_APPEND( l, alignof(atomic_ulong), max_slots * sizeof(atomic_ulong) ); - if( alpenglow ) { - root_chain_cnt = fd_shredb_root_map_chain_cnt_est( max_shreds ); - root_map_mem = FD_SCRATCH_ALLOC_APPEND( l, fd_shredb_root_map_align(), fd_shredb_root_map_footprint( root_chain_cnt ) ); - root_pool_mem = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_shredb_root_entry_t), max_shreds * sizeof(fd_shredb_root_entry_t) ); - } + root_chain_cnt = fd_shredb_root_map_chain_cnt_est( max_shreds ); + root_map_mem = FD_SCRATCH_ALLOC_APPEND( l, fd_shredb_root_map_align(), fd_shredb_root_map_footprint( root_chain_cnt ) ); + root_pool_mem = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_shredb_root_entry_t), max_shreds * sizeof(fd_shredb_root_entry_t) ); } FD_TEST( FD_SCRATCH_ALLOC_FINI( l, fd_store_align() )==(ulong)shmem + footprint ); @@ -415,7 +411,6 @@ fd_store_new( void * shmem, store->store_gaddr = fd_wksp_gaddr_fast( wksp, store ); store->pool_mem_gaddr = fd_wksp_gaddr_fast( wksp, shpool ); store->pool_ele_gaddr = fd_wksp_gaddr_fast( wksp, shele ); - store->alpenglow = !!alpenglow; store->shred_tile_cnt = shred_tile_cnt; store->slot_to_fecs_gaddr = fd_wksp_gaddr_fast( wksp, slot_to_fecs ); store->slot_to_fecs_sz = slot_to_fecs_sz; @@ -469,7 +464,7 @@ fd_store_new( void * shmem, fec0[ i ].data_consume_pending = 0U; } - for( ulong i=0UL; ihas_root_map = !!alpenglow; - if( alpenglow ) { - void * root_shmap = fd_shredb_root_map_new( root_map_mem, root_chain_cnt, seed ); - FD_TEST( root_shmap ); - store->root_map_gaddr = fd_wksp_gaddr_fast( wksp, root_shmap ); - store->root_pool_gaddr = fd_wksp_gaddr_fast( wksp, root_pool_mem ); - - fd_shredb_root_entry_t * root_cell = (fd_shredb_root_entry_t *)root_pool_mem; - for( ulong i=0UL; iroot_map_gaddr = fd_wksp_gaddr_fast( wksp, root_shmap ); + store->root_pool_gaddr = fd_wksp_gaddr_fast( wksp, root_pool_mem ); + + fd_shredb_root_entry_t * root_cell = (fd_shredb_root_entry_t *)root_pool_mem; + for( ulong i=0UL; icache_lock ); - if( FD_LIKELY( store->alpenglow ) ) { - FD_TEST( partshred_tile_cnt ); - new_fec->slot = (uint)fd_ulong_max( slot, store->root ); - ulong lo = atomic_load_explicit( &store->slot_lo, memory_order_relaxed ); - while( new_fec->slotslot_lo, &lo, new_fec->slot, memory_order_relaxed, memory_order_relaxed ) ); - fd_store_slot_to_fecs_ele_insert( fd_store_slot_to_fecs_join( slot_to_fecs_laddr( store, part ) ), new_fec, pool_ele_laddr( store ) ); - } + FD_TEST( partshred_tile_cnt ); + new_fec->slot = (uint)fd_ulong_max( slot, store->root ); + ulong lo = atomic_load_explicit( &store->slot_lo, memory_order_relaxed ); + while( new_fec->slotslot_lo, &lo, new_fec->slot, memory_order_relaxed, memory_order_relaxed ) ); + fd_store_slot_to_fecs_ele_insert( fd_store_slot_to_fecs_join( slot_to_fecs_laddr( store, part ) ), new_fec, pool_ele_laddr( store ) ); FD_TEST( !fd_store_map_txn_insert( map, new_fec ) ); *fec = new_fec; err = FD_MAP_SUCCESS; @@ -870,7 +860,7 @@ fd_store_publish( fd_store_t * store, ulong cnt = 0UL; fd_rwlock_write( &store->fec_lock ); ulong lo = atomic_load_explicit( &store->slot_lo, memory_order_relaxed ); - for( ulong part=0UL; store->alpenglow && partshred_tile_cnt; part++ ) { + for( ulong part=0UL; partshred_tile_cnt; part++ ) { fd_store_slot_to_fecs_t * slot_to_fecs = fd_store_slot_to_fecs_join( slot_to_fecs_laddr( store, part ) ); for( ulong slot=lo; slothas_root_map ) { + { fd_shredb_root_map_t map[1]; FD_TEST( disk_root_map_ljoin( store, map ) ); fd_shredb_root_entry_t * pool = disk_root_pool_laddr( store ); @@ -1031,10 +1018,7 @@ disk_exact_publish( fd_store_t * store, fd_shredb_shred_entry_t * new_ele = fd_shredb_shred_map_query_ele( new_query ); if( new_ele!=cell ) { ulong state = disk_cell_state( atomic_load_explicit( tags+(ulong)(new_ele-pool), memory_order_acquire ) ); - if( FD_LIKELY( state==FD_SHREDB_CELL_READY || state==FD_SHREDB_CELL_WRITING ) ) { - link = 0; - if( !store->has_root_map ) result = state==FD_SHREDB_CELL_READY ? DISK_PUBLISH_NOOP : DISK_PUBLISH_ERR; - } + if( FD_LIKELY( state==FD_SHREDB_CELL_READY || state==FD_SHREDB_CELL_WRITING ) ) link = 0; else FD_TEST( !fd_shredb_shred_map_txn_remove( map, &new_map_key, NULL, new_query, 0 ) ); } } @@ -1089,7 +1073,7 @@ fd_store_disk_insert( fd_store_t * store, /* Skip exact duplicates before taking a cell, which would otherwise evict an older shred. disk_exact_publish still catches races. */ - if( FD_UNLIKELY( store->has_root_map && disk_root_present( store, &root_key ) ) ) return FD_STORE_DISK_INSERT_DUP; + if( FD_UNLIKELY( disk_root_present( store, &root_key ) ) ) return FD_STORE_DISK_INSERT_DUP; ulong ticket = atomic_fetch_add_explicit( &store->disk_reservation_head, 1UL, memory_order_relaxed ) + 1UL; if( FD_UNLIKELY( !ticket || ticket>(ULONG_MAX>>2) ) ) return FD_STORE_DISK_INSERT_ERR; @@ -1107,7 +1091,7 @@ fd_store_disk_insert( fd_store_t * store, return FD_STORE_DISK_INSERT_ERR; old_state = disk_cell_state( old_tag ); ulong old_key = cell->key; - fd_shredb_root_key_t old_root_key = store->has_root_map ? disk_root_pool_laddr( store )[ ring_idx ].key : (fd_shredb_root_key_t){0}; + fd_shredb_root_key_t old_root_key = disk_root_pool_laddr( store )[ ring_idx ].key; fd_shredb_entry_t wr_entry[1]; ulong shred_sz = fd_ulong_min( fd_shred_sz( shred ), FD_SHRED_MAX_SZ ); @@ -1250,7 +1234,7 @@ fd_store_disk_query_root( fd_store_t const * store, uchar const * merkle_root, uint shred_idx, uchar out[ FD_SHRED_MAX_SZ ] ) { - if( FD_UNLIKELY( !store || disk_fd<0 || !merkle_root || !out || !fd_store_has_disk( store ) || !store->has_root_map || + if( FD_UNLIKELY( !store || disk_fd<0 || !merkle_root || !out || !fd_store_has_disk( store ) || shred_idx>=store->max_shreds_per_block ) ) return FD_STORE_DISK_QUERY_MISS; fd_shredb_root_key_t root_key = fd_shredb_root_key( merkle_root, shred_idx ); for( ulong retry=0UL; retryUINT_MAX || fec_data_max>UINT_MAX || shred_storage_gib>FD_SHREDB_MAX_SIZE_GIB ) ) return 0UL; @@ -336,7 +332,7 @@ fd_store_footprint( ulong fec_max, fd_store_layout_append( &l, fd_store_map_align(), 1UL, fd_store_map_footprint( chain_cnt ) ) || fd_store_layout_append( &l, fd_store_pool_align(), 1UL, fd_store_pool_footprint() ) || fd_store_layout_append( &l, alignof(fd_store_fec_t), fec_max, sizeof(fd_store_fec_t) ) || - fd_store_layout_append( &l, fd_store_slot_to_fecs_align(), alpenglow ? shred_tile_cnt : 0UL, slot_to_fecs_sz ) || + fd_store_layout_append( &l, fd_store_slot_to_fecs_align(), shred_tile_cnt, slot_to_fecs_sz ) || fd_store_layout_append( &l, FD_STORE_PAYLOAD_PAGE_SZ, cache_slot_cnt, payload_slot_sz ) || fd_store_layout_append( &l, alignof(ulong), cache_slot_cnt, sizeof(ulong) ) || fd_store_layout_append( &l, alignof(uint), fec_max, sizeof(uint) ) || @@ -351,11 +347,9 @@ fd_store_footprint( ulong fec_max, fd_store_layout_append( &l, alignof(fd_shredb_shred_entry_t), max_shreds, sizeof(fd_shredb_shred_entry_t) ) || fd_store_layout_append( &l, alignof(atomic_ulong), max_shreds, sizeof(atomic_ulong) ) || fd_store_layout_append( &l, alignof(atomic_ulong), max_slots, sizeof(atomic_ulong) ) ) ) return 0UL; - if( alpenglow ) { - ulong root_chain_cnt = fd_shredb_root_map_chain_cnt_est( max_shreds ); - if( FD_UNLIKELY( fd_store_layout_append( &l, fd_shredb_root_map_align(), 1UL, fd_shredb_root_map_footprint( root_chain_cnt ) ) || - fd_store_layout_append( &l, alignof(fd_shredb_root_entry_t), max_shreds, sizeof(fd_shredb_root_entry_t) ) ) ) return 0UL; - } + ulong root_chain_cnt = fd_shredb_root_map_chain_cnt_est( max_shreds ); + if( FD_UNLIKELY( fd_store_layout_append( &l, fd_shredb_root_map_align(), 1UL, fd_shredb_root_map_footprint( root_chain_cnt ) ) || + fd_store_layout_append( &l, alignof(fd_shredb_root_entry_t), max_shreds, sizeof(fd_shredb_root_entry_t) ) ) ) return 0UL; } if( FD_UNLIKELY( fd_store_layout_append( &l, fd_store_align(), 0UL, 1UL ) ) ) return 0UL; return l; @@ -365,9 +359,9 @@ fd_store_footprint( ulong fec_max, bounds live FECs; fec_data_max bounds each payload. The remaining size arguments configure the shred ring, RAM cache, and shred-tile arena. max_shreds_per_block bounds shred idxs in the shred ring, in - [1,FD_SHREDB_HINT_VALID]. The shred ring's root map and the - shred_tile_cnt slot_to_fecs partitions exist only if alpenglow, - which must match the footprint. Does not create the backing file. */ + [1,FD_SHREDB_HINT_VALID]. shred_tile_cnt (>=1) is the number of + slot_to_fecs partitions and must match the footprint. Does not + create the backing file. */ void * fd_store_new( void * shmem, @@ -378,7 +372,6 @@ fd_store_new( void * shmem, ulong fec_set_cnt, ulong max_shreds_per_block, ulong seed, - int alpenglow, ulong shred_tile_cnt ); fd_store_t * fd_store_join ( void * shstore ); @@ -479,7 +472,7 @@ fd_store_fec_data_view_release( fd_store_t * store, fd_store_fec_data_view_t * view ); /* Atomically inserts merkle_root, a FEC set of slot, into the caller's - slot_to_fecs partition part (the shred tile's) under Alpenglow. The new FEC takes the payload + slot_to_fecs partition part (the shred tile's). The new FEC takes the payload reserved by fd_store_fec_data_reserve at data_idx, data_sz bytes split by shred_sz, and is RAM_READY when linked. Returns FD_MAP_SUCCESS with the new FEC, or FD_MAP_ERR_KEY with *fec==NULL if @@ -507,7 +500,8 @@ fd_store_query( fd_store_map_t * map, /* Removes merkle_root after active views and spill I/O finish. Returns its payload and metadata before returning. Returns 1 if found and 0 - otherwise. Not allowed under Alpenglow, use fd_store_publish. */ + otherwise. Leaves the FEC linked in slot_to_fecs, so it must not be + mixed with fd_store_publish. */ int fd_store_remove( fd_store_t * store, @@ -548,12 +542,11 @@ typedef struct fd_store_disk_stats fd_store_disk_stats_t; /* Persists one (slot,idx) shred, where idx is below max_shreds_per_block, whose FEC set has merkle root merkle_root (only the first - FD_SHRED_MERKLE_NODE_SZ bytes are used). With a root map, every - version is indexed by (root,idx) and re-inserting a stored (root,idx) - is a no-op. The first version stored for a (slot,idx) is also - indexed by (slot,idx), and without a root map later versions are - dropped. Returns FD_STORE_DISK_INSERT_SUCCESS if written, - FD_STORE_DISK_INSERT_DUP if already stored or dropped, or + FD_SHRED_MERKLE_NODE_SZ bytes are used). Every version is indexed + by (root,idx) and re-inserting a stored (root,idx) is a no-op. The + first version stored for a (slot,idx) is also indexed by (slot,idx). + Returns FD_STORE_DISK_INSERT_SUCCESS if written, + FD_STORE_DISK_INSERT_DUP if already stored, or FD_STORE_DISK_INSERT_ERR. */ int @@ -576,7 +569,7 @@ fd_store_disk_query( fd_store_t const * store, /* fd_store_disk_query_root copies the shred at shred_idx of the FEC set whose merkle root starts with the FD_SHRED_MERKLE_NODE_SZ bytes at merkle_root to out. Finds any stored version. Returns its positive - byte count, MISS (always, without a root map), or retryable BUSY. */ + byte count, MISS, or retryable BUSY. */ int fd_store_disk_query_root( fd_store_t const * store, diff --git a/src/disco/store/test_store.c b/src/disco/store/test_store.c index 7ab6505fb03..e68c1a8d764 100644 --- a/src/disco/store/test_store.c +++ b/src/disco/store/test_store.c @@ -63,8 +63,8 @@ store_file_open( fd_store_t * store, void test_publish( fd_wksp_t * wksp ) { ulong fec_max = 8; - void * mem = fd_wksp_alloc_laddr( wksp, fd_store_align(), fd_store_footprint( fec_max, 31840UL, 0UL, 0UL, 0UL, 1, 2UL ), 1UL ); - fd_store_t * store = fd_store_join( fd_store_new( mem, fec_max, 31840UL, 0UL, 0UL, 0UL, FD_SHRED_BLK_MAX, 0UL, 1, 2UL ) ); + void * mem = fd_wksp_alloc_laddr( wksp, fd_store_align(), fd_store_footprint( fec_max, 31840UL, 0UL, 0UL, 0UL, 2UL ), 1UL ); + fd_store_t * store = fd_store_join( fd_store_new( mem, fec_max, 31840UL, 0UL, 0UL, 0UL, FD_SHRED_BLK_MAX, 0UL, 2UL ) ); FD_TEST( store ); fd_store_map_t map[1]; FD_TEST( fd_store_map_ljoin( store, map ) ); @@ -99,8 +99,8 @@ test_publish( fd_wksp_t * wksp ) { void test_insert_reserved( fd_wksp_t * wksp ) { ulong fec_max = 4; - void * mem = fd_wksp_alloc_laddr( wksp, fd_store_align(), fd_store_footprint( fec_max, 31840UL, 0UL, 0UL, 0UL, 1, 1UL ), 1UL ); - fd_store_t * store = fd_store_join( fd_store_new( mem, fec_max, 31840UL, 0UL, 0UL, 0UL, FD_SHRED_BLK_MAX, 0UL, 1, 1UL ) ); + void * mem = fd_wksp_alloc_laddr( wksp, fd_store_align(), fd_store_footprint( fec_max, 31840UL, 0UL, 0UL, 0UL, 1UL ), 1UL ); + fd_store_t * store = fd_store_join( fd_store_new( mem, fec_max, 31840UL, 0UL, 0UL, 0UL, FD_SHRED_BLK_MAX, 0UL, 1UL ) ); FD_TEST( store ); fd_store_map_t map[1]; FD_TEST( fd_store_map_ljoin( store, map ) ); @@ -159,8 +159,8 @@ test_api( fd_wksp_t * wksp ) { FD_TEST( sizeof(fd_store_fec_t)==128UL ); ulong fec_max = 8; - void * mem = fd_wksp_alloc_laddr( wksp, fd_store_align(), fd_store_footprint( fec_max, 31840UL, 0UL, 0UL, 0UL, 0, 0UL ), 1UL ); - fd_store_t * store = fd_store_join( fd_store_new( mem, fec_max, 31840UL, 0UL, 0UL, 0UL, FD_SHRED_BLK_MAX, 0UL, 0, 0UL ) ); + void * mem = fd_wksp_alloc_laddr( wksp, fd_store_align(), fd_store_footprint( fec_max, 31840UL, 0UL, 0UL, 0UL, 1UL ), 1UL ); + fd_store_t * store = fd_store_join( fd_store_new( mem, fec_max, 31840UL, 0UL, 0UL, 0UL, FD_SHRED_BLK_MAX, 0UL, 1UL ) ); FD_TEST( store ); fd_store_map_t map[1]; @@ -223,10 +223,10 @@ void test_file_create( fd_wksp_t * wksp ) { FD_TEST( !unlink( TEST_PAYLOAD_PATH ) || errno==ENOENT ); - ulong footprint = fd_store_footprint( 2UL, 31840UL, 1UL, 0UL, 0UL, 1, 1UL ); + ulong footprint = fd_store_footprint( 2UL, 31840UL, 1UL, 0UL, 0UL, 1UL ); void * mem = fd_wksp_alloc_laddr( wksp, fd_store_align(), footprint, 1UL ); fd_store_t * store = fd_store_join( fd_store_new( mem, 2UL, 31840UL, 1UL, 0UL, 0UL, FD_SHRED_BLK_MAX, - 0UL, 1, 1UL ) ); + 0UL, 1UL ) ); FD_TEST( store ); store->disk_max_shreds = 16UL; FD_TEST( access( TEST_PAYLOAD_PATH, F_OK ) && errno==ENOENT ); @@ -283,8 +283,8 @@ test_pread_all( void ) { void test_query_miss( fd_wksp_t * wksp ) { ulong fec_max = 16; - void * mem = fd_wksp_alloc_laddr( wksp, fd_store_align(), fd_store_footprint( fec_max, 31840UL, 0UL, 0UL, 0UL, 0, 0UL ), 1UL ); - fd_store_t * store = fd_store_join( fd_store_new( mem, fec_max, 31840UL, 0UL, 0UL, 0UL, FD_SHRED_BLK_MAX, 0UL, 0, 0UL ) ); + void * mem = fd_wksp_alloc_laddr( wksp, fd_store_align(), fd_store_footprint( fec_max, 31840UL, 0UL, 0UL, 0UL, 1UL ), 1UL ); + fd_store_t * store = fd_store_join( fd_store_new( mem, fec_max, 31840UL, 0UL, 0UL, 0UL, FD_SHRED_BLK_MAX, 0UL, 1UL ) ); FD_TEST( store ); fd_store_map_t map[1]; @@ -307,14 +307,9 @@ test_query_miss( fd_wksp_t * wksp ) { void test_disk_index_footprint( void ) { - ulong fp_no_disk = fd_store_footprint( 8UL, 31840UL, 0UL, 0UL, 0UL, 0, 0UL ); - ulong fp_50_gib = fd_store_footprint( 8UL, 31840UL, 50UL, 0UL, 0UL, 1, 0UL ); + ulong fp_no_disk = fd_store_footprint( 8UL, 31840UL, 0UL, 0UL, 0UL, 0UL ); + ulong fp_50_gib = fd_store_footprint( 8UL, 31840UL, 50UL, 0UL, 0UL, 0UL ); FD_TEST( fp_50_gib-fp_no_disk==(2954UL<<20)+4096UL ); /* slot and root maps and entries, tags, hints, and alignment */ - - /* Without alpenglow the root map (chains and 28B entries) is omitted */ - ulong fp_50_gib_no_root = fd_store_footprint( 8UL, 31840UL, 50UL, 0UL, 0UL, 0, 0UL ); - FD_TEST( fp_50_gib-fp_50_gib_no_root==(1632UL<<20) ); - FD_TEST( fd_store_footprint( 8UL, 31840UL, 0UL, 0UL, 0UL, 1, 0UL )==fp_no_disk ); /* no disk, no root map */ } void @@ -322,27 +317,27 @@ test_fec_data_max( fd_wksp_t * wksp ) { ulong fec_max = 8; FD_TEST( fd_shredb_max_slots( 1UL )==fd_shredb_max_shreds( 1UL )/FD_FEC_SHRED_CNT ); - FD_TEST( fd_store_footprint( fec_max, 31840UL, FD_SHREDB_MAX_SIZE_GIB, 0UL, 0UL, 1, 0UL )>0UL ); - FD_TEST( fd_store_footprint( fec_max, 31840UL, FD_SHREDB_MAX_SIZE_GIB+1UL, 0UL, 0UL, 1, 1UL )==0UL ); - FD_TEST( fd_store_footprint( fec_max, 31840UL, 50UL, 0UL, 0UL, 1, 0UL )<(4UL<<30) ); + FD_TEST( fd_store_footprint( fec_max, 31840UL, FD_SHREDB_MAX_SIZE_GIB, 0UL, 0UL, 0UL )>0UL ); + FD_TEST( fd_store_footprint( fec_max, 31840UL, FD_SHREDB_MAX_SIZE_GIB+1UL, 0UL, 0UL, 1UL )==0UL ); + FD_TEST( fd_store_footprint( fec_max, 31840UL, 50UL, 0UL, 0UL, 0UL )<(4UL<<30) ); FD_TEST( !fd_store_payload_slot_sz( ULONG_MAX ) ); - FD_TEST( !fd_store_footprint( fec_max, ULONG_MAX, 0UL, 1UL, 0UL, 0, 0UL ) ); - FD_TEST( !fd_store_footprint( fec_max, (ulong)UINT_MAX+1UL, 0UL, 1UL, 0UL, 0, 0UL ) ); - FD_TEST( !fd_store_footprint( fec_max, 31840UL, 0UL, 0UL, ULONG_MAX, 0, 0UL ) ); + FD_TEST( !fd_store_footprint( fec_max, ULONG_MAX, 0UL, 1UL, 0UL, 0UL ) ); + FD_TEST( !fd_store_footprint( fec_max, (ulong)UINT_MAX+1UL, 0UL, 1UL, 0UL, 0UL ) ); + FD_TEST( !fd_store_footprint( fec_max, 31840UL, 0UL, 0UL, ULONG_MAX, 0UL ) ); /* With shred_cache_bytes==0, the RAM cache can hold all live FECs, so the footprint scales with the aligned payload slot size. With a bounded cache, footprint scales with the cache budget instead. */ - ulong fp_fixed = fd_store_footprint( fec_max, 31840UL, 0UL, 0UL, 0UL, 0, 0UL ); - ulong fp_var = fd_store_footprint( fec_max, 63985UL, 0UL, 0UL, 0UL, 0, 0UL ); - ulong fp_cap = fd_store_footprint( fec_max, 63985UL, 0UL, 2UL*fd_store_payload_slot_sz( 63985UL ), 0UL, 0, 0UL ); + ulong fp_fixed = fd_store_footprint( fec_max, 31840UL, 0UL, 0UL, 0UL, 1UL ); + ulong fp_var = fd_store_footprint( fec_max, 63985UL, 0UL, 0UL, 0UL, 1UL ); + ulong fp_cap = fd_store_footprint( fec_max, 63985UL, 0UL, 2UL*fd_store_payload_slot_sz( 63985UL ), 0UL, 1UL ); FD_TEST( fp_fixed ); FD_TEST( fp_var ); FD_TEST( fp_var > fp_fixed ); FD_TEST( fp_cap < fp_var ); void * mem = fd_wksp_alloc_laddr( wksp, fd_store_align(), fp_var, 1UL ); - fd_store_t * st = fd_store_join( fd_store_new( mem, fec_max, 63985UL, 0UL, 0UL, 0UL, FD_SHRED_BLK_MAX, 0UL, 0, 0UL ) ); + fd_store_t * st = fd_store_join( fd_store_new( mem, fec_max, 63985UL, 0UL, 0UL, 0UL, FD_SHRED_BLK_MAX, 0UL, 1UL ) ); FD_TEST( st ); FD_TEST( st->fec_data_max == 63985UL ); @@ -376,7 +371,7 @@ test_fec_data_max( fd_wksp_t * wksp ) { fd_wksp_free_laddr( fd_store_delete( fd_store_leave( st ) ) ); mem = fd_wksp_alloc_laddr( wksp, fd_store_align(), fp_fixed, 1UL ); - st = fd_store_join( fd_store_new( mem, fec_max, 31840UL, 0UL, 0UL, 0UL, FD_SHRED_BLK_MAX, 0UL, 0, 0UL ) ); + st = fd_store_join( fd_store_new( mem, fec_max, 31840UL, 0UL, 0UL, 0UL, FD_SHRED_BLK_MAX, 0UL, 1UL ) ); FD_TEST( st ); FD_TEST( st->fec_data_max == 31840UL ); @@ -401,12 +396,12 @@ test_fec_sets_arena( fd_wksp_t * wksp ) { ulong fec_max = 4UL; ulong fec_set_cnt = 5UL; - ulong fp_without = fd_store_footprint( fec_max, 31840UL, 0UL, 0UL, 0UL, 0, 0UL ); - ulong fp_with = fd_store_footprint( fec_max, 31840UL, 0UL, 0UL, fec_set_cnt, 0, 0UL ); + ulong fp_without = fd_store_footprint( fec_max, 31840UL, 0UL, 0UL, 0UL, 1UL ); + ulong fp_with = fd_store_footprint( fec_max, 31840UL, 0UL, 0UL, fec_set_cnt, 1UL ); FD_TEST( fp_with > fp_without ); void * mem = fd_wksp_alloc_laddr( wksp, fd_store_align(), fp_with, 1UL ); - fd_store_t * st = fd_store_join( fd_store_new( mem, fec_max, 31840UL, 0UL, 0UL, fec_set_cnt, FD_SHRED_BLK_MAX, 0UL, 0, 0UL ) ); + fd_store_t * st = fd_store_join( fd_store_new( mem, fec_max, 31840UL, 0UL, 0UL, fec_set_cnt, FD_SHRED_BLK_MAX, 0UL, 1UL ) ); FD_TEST( st ); FD_TEST( st->fec_set_cnt==fec_set_cnt ); @@ -428,8 +423,8 @@ test_spill( fd_wksp_t * wksp ) { ulong fec_data_max = 64UL; ulong cache_bytes = 2UL * fd_store_payload_slot_sz( fec_data_max ); - void * mem = fd_wksp_alloc_laddr( wksp, fd_store_align(), fd_store_footprint( fec_max, fec_data_max, 0UL, cache_bytes, 0UL, 0, 0UL ), 1UL ); - fd_store_t * st = fd_store_join( fd_store_new( mem, fec_max, fec_data_max, 0UL, cache_bytes, 0UL, FD_SHRED_BLK_MAX, 0UL, 0, 0UL ) ); + void * mem = fd_wksp_alloc_laddr( wksp, fd_store_align(), fd_store_footprint( fec_max, fec_data_max, 0UL, cache_bytes, 0UL, 1UL ), 1UL ); + fd_store_t * st = fd_store_join( fd_store_new( mem, fec_max, fec_data_max, 0UL, cache_bytes, 0UL, FD_SHRED_BLK_MAX, 0UL, 1UL ) ); FD_TEST( st ); FD_TEST( st->cache_slot_cnt==2UL ); FD_TEST( st->cache_free_cnt==2UL ); @@ -520,8 +515,8 @@ test_spill_high_water( fd_wksp_t * wksp ) { ulong fec_data_max = 64UL; ulong cache_bytes = 1UL * fd_store_payload_slot_sz( fec_data_max ); - void * mem = fd_wksp_alloc_laddr( wksp, fd_store_align(), fd_store_footprint( fec_max, fec_data_max, 0UL, cache_bytes, 0UL, 0, 0UL ), 1UL ); - fd_store_t * st = fd_store_join( fd_store_new( mem, fec_max, fec_data_max, 0UL, cache_bytes, 0UL, FD_SHRED_BLK_MAX, 0UL, 0, 0UL ) ); + void * mem = fd_wksp_alloc_laddr( wksp, fd_store_align(), fd_store_footprint( fec_max, fec_data_max, 0UL, cache_bytes, 0UL, 1UL ), 1UL ); + fd_store_t * st = fd_store_join( fd_store_new( mem, fec_max, fec_data_max, 0UL, cache_bytes, 0UL, FD_SHRED_BLK_MAX, 0UL, 1UL ) ); FD_TEST( st && st->cache_slot_cnt==1UL ); int fd = store_file_open( st, O_RDWR ); FD_TEST( fd>=0 ); @@ -572,8 +567,8 @@ test_preevict( fd_wksp_t * wksp ) { ulong fec_data_max = 64UL; ulong cache_bytes = 2UL * fd_store_payload_slot_sz( fec_data_max ); - void * mem = fd_wksp_alloc_laddr( wksp, fd_store_align(), fd_store_footprint( fec_max, fec_data_max, 0UL, cache_bytes, 0UL, 0, 0UL ), 1UL ); - fd_store_t * st = fd_store_join( fd_store_new( mem, fec_max, fec_data_max, 0UL, cache_bytes, 0UL, FD_SHRED_BLK_MAX, 0UL, 0, 0UL ) ); + void * mem = fd_wksp_alloc_laddr( wksp, fd_store_align(), fd_store_footprint( fec_max, fec_data_max, 0UL, cache_bytes, 0UL, 1UL ), 1UL ); + fd_store_t * st = fd_store_join( fd_store_new( mem, fec_max, fec_data_max, 0UL, cache_bytes, 0UL, FD_SHRED_BLK_MAX, 0UL, 1UL ) ); FD_TEST( st ); int fd = store_file_open( st, O_RDWR ); FD_TEST( fd>=0 ); @@ -618,8 +613,8 @@ test_pinned_spill( fd_wksp_t * wksp ) { ulong fec_data_max = 64UL; ulong cache_bytes = 2UL * fd_store_payload_slot_sz( fec_data_max ); - void * mem = fd_wksp_alloc_laddr( wksp, fd_store_align(), fd_store_footprint( fec_max, fec_data_max, 0UL, cache_bytes, 0UL, 0, 0UL ), 1UL ); - fd_store_t * st = fd_store_join( fd_store_new( mem, fec_max, fec_data_max, 0UL, cache_bytes, 0UL, FD_SHRED_BLK_MAX, 0UL, 0, 0UL ) ); + void * mem = fd_wksp_alloc_laddr( wksp, fd_store_align(), fd_store_footprint( fec_max, fec_data_max, 0UL, cache_bytes, 0UL, 1UL ), 1UL ); + fd_store_t * st = fd_store_join( fd_store_new( mem, fec_max, fec_data_max, 0UL, cache_bytes, 0UL, FD_SHRED_BLK_MAX, 0UL, 1UL ) ); FD_TEST( st ); int fd = store_file_open( st, O_RDWR ); FD_TEST( fd>=0 ); @@ -714,10 +709,10 @@ void test_disk_query_highest( fd_wksp_t * wksp ) { ulong fec_max = 8UL; ulong fec_data_max = 31840UL; - ulong footprint = fd_store_footprint( fec_max, fec_data_max, 1UL, 0UL, 0UL, 1, 1UL ); + ulong footprint = fd_store_footprint( fec_max, fec_data_max, 1UL, 0UL, 0UL, 1UL ); void * mem = fd_wksp_alloc_laddr( wksp, fd_store_align(), footprint, 1UL ); fd_store_t * store = fd_store_join( fd_store_new( mem, fec_max, fec_data_max, 1UL, 0UL, 0UL, FD_SHRED_BLK_MAX, - 42UL, 1, 1UL ) ); + 42UL, 1UL ) ); FD_TEST( store ); store->disk_max_shreds = 64UL; @@ -788,10 +783,10 @@ test_disk_query_highest( fd_wksp_t * wksp ) { void test_disk_many_wraps( fd_wksp_t * wksp ) { - ulong footprint = fd_store_footprint( 8UL, 31840UL, 1UL, 0UL, 0UL, 1, 1UL ); + ulong footprint = fd_store_footprint( 8UL, 31840UL, 1UL, 0UL, 0UL, 1UL ); void * mem = fd_wksp_alloc_laddr( wksp, fd_store_align(), footprint, 1UL ); fd_store_t * store = fd_store_join( fd_store_new( mem, 8UL, 31840UL, 1UL, 0UL, 0UL, FD_SHRED_BLK_MAX, - 42UL, 1, 1UL ) ); + 42UL, 1UL ) ); FD_TEST( store ); store->disk_max_shreds = 16UL; atomic_store_explicit( &store->disk_reservation_head, (1UL<<32)-80UL, memory_order_relaxed ); @@ -827,10 +822,10 @@ test_disk_many_wraps( fd_wksp_t * wksp ) { void test_disk_versions( fd_wksp_t * wksp ) { - ulong footprint = fd_store_footprint( 8UL, 31840UL, 1UL, 0UL, 0UL, 1, 1UL ); + ulong footprint = fd_store_footprint( 8UL, 31840UL, 1UL, 0UL, 0UL, 1UL ); void * mem = fd_wksp_alloc_laddr( wksp, fd_store_align(), footprint, 1UL ); fd_store_t * store = fd_store_join( fd_store_new( mem, 8UL, 31840UL, 1UL, 0UL, 0UL, FD_SHRED_BLK_MAX, - 42UL, 1, 1UL ) ); + 42UL, 1UL ) ); FD_TEST( store ); store->disk_max_shreds = 4UL; @@ -906,55 +901,12 @@ test_disk_versions( fd_wksp_t * wksp ) { fd_wksp_free_laddr( fd_store_delete( fd_store_leave( store ) ) ); } -/* Without alpenglow there is no root map: only the first version of a - (slot,idx) is stored and root queries always miss. */ - -void -test_disk_no_root_map( fd_wksp_t * wksp ) { - ulong footprint = fd_store_footprint( 8UL, 31840UL, 1UL, 0UL, 0UL, 0, 0UL ); - FD_TEST( footprinthas_root_map ); - store->disk_max_shreds = 4UL; - - int disk_fd = store_file_open( store, O_RDWR ); - FD_TEST( disk_fd>=0 ); - - ulong const slot = 60UL; - uint const idx = 5U; - fd_hash_t root_a = disk_root( 2000UL, 0U ); - fd_hash_t root_b = disk_root( 2001UL, 0U ); - uchar buf[ FD_SHRED_MAX_SZ ]; - uchar out[ FD_SHRED_MAX_SZ ]; - - FD_TEST( fd_store_disk_insert( store, disk_fd, disk_make_shred( buf, slot, idx, 0xa0U ), &root_a )==FD_STORE_DISK_INSERT_SUCCESS ); - FD_TEST( fd_store_disk_insert( store, disk_fd, disk_make_shred( buf, slot, idx, 0xb0U ), &root_b )==FD_STORE_DISK_INSERT_DUP ); - - /* The second version is dropped and only the first is readable */ - FD_TEST( atomic_load_explicit( &store->disk_cnt, memory_order_relaxed )==1UL ); - FD_TEST( fd_store_disk_query( store, disk_fd, slot, idx, out )>0 && out[ FD_SHRED_DATA_HEADER_SZ ]==0xa0U ); - FD_TEST( fd_store_disk_query_highest( store, disk_fd, slot, 0U, out )>0 && out[ FD_SHRED_DATA_HEADER_SZ ]==0xa0U ); - FD_TEST( fd_store_disk_query_root( store, disk_fd, root_a.uc, idx, out )==FD_STORE_DISK_QUERY_MISS ); - - /* The ring still wraps and relinks (slot,idx) */ - for( uint i=0U; i<4U; i++ ) - FD_TEST( disk_insert( store, disk_fd, disk_make_shred( buf, slot+1UL, i, (uchar)(0xc0U+i) ) )==FD_STORE_DISK_INSERT_SUCCESS ); - FD_TEST( fd_store_disk_query( store, disk_fd, slot, idx, out )==FD_STORE_DISK_QUERY_MISS ); - FD_TEST( fd_store_disk_insert( store, disk_fd, disk_make_shred( buf, slot, idx, 0xb1U ), &root_b )==FD_STORE_DISK_INSERT_SUCCESS ); - FD_TEST( fd_store_disk_query( store, disk_fd, slot, idx, out )>0 && out[ FD_SHRED_DATA_HEADER_SZ ]==0xb1U ); - - close( disk_fd ); - fd_wksp_free_laddr( fd_store_delete( fd_store_leave( store ) ) ); -} - void test_disk_concurrent_writes( fd_wksp_t * wksp ) { - ulong footprint = fd_store_footprint( 8UL, 31840UL, 1UL, 0UL, 0UL, 1, 1UL ); + ulong footprint = fd_store_footprint( 8UL, 31840UL, 1UL, 0UL, 0UL, 1UL ); void * mem = fd_wksp_alloc_laddr( wksp, fd_store_align(), footprint, 1UL ); fd_store_t * store = fd_store_join( fd_store_new( mem, 8UL, 31840UL, 1UL, 0UL, 0UL, FD_SHRED_BLK_MAX, - 42UL, 1, 1UL ) ); + 42UL, 1UL ) ); FD_TEST( store ); store->disk_max_shreds = fd_ulong_max( 16UL, 2UL*fd_tile_cnt() ); int disk_fd = store_file_open( store, O_RDWR ); @@ -994,10 +946,10 @@ test_disk_concurrent_writes( fd_wksp_t * wksp ) { void test_disk_slot_hint( fd_wksp_t * wksp ) { - ulong footprint = fd_store_footprint( 8UL, 31840UL, 1UL, 0UL, 0UL, 1, 1UL ); + ulong footprint = fd_store_footprint( 8UL, 31840UL, 1UL, 0UL, 0UL, 1UL ); void * mem = fd_wksp_alloc_laddr( wksp, fd_store_align(), footprint, 1UL ); fd_store_t * store = fd_store_join( fd_store_new( mem, 8UL, 31840UL, 1UL, 0UL, 0UL, FD_SHRED_BLK_MAX, - 42UL, 1, 1UL ) ); + 42UL, 1UL ) ); FD_TEST( store ); store->disk_max_shreds = 4UL; @@ -1074,10 +1026,10 @@ test_disk_slot_hint( fd_wksp_t * wksp ) { void test_disk_probe( fd_wksp_t * wksp ) { - ulong footprint = fd_store_footprint( 8UL, 31840UL, 1UL, 0UL, 0UL, 1, 1UL ); + ulong footprint = fd_store_footprint( 8UL, 31840UL, 1UL, 0UL, 0UL, 1UL ); void * mem = fd_wksp_alloc_laddr( wksp, fd_store_align(), footprint, 1UL ); fd_store_t * store = fd_store_join( fd_store_new( mem, 8UL, 31840UL, 1UL, 0UL, 0UL, FD_SHRED_BLK_MAX, - 42UL, 1, 1UL ) ); + 42UL, 1UL ) ); FD_TEST( store ); store->disk_max_shreds = 4UL; @@ -1132,10 +1084,10 @@ test_disk_probe( fd_wksp_t * wksp ) { void test_disk_lazy_highest( fd_wksp_t * wksp ) { - ulong footprint = fd_store_footprint( 8UL, 31840UL, 1UL, 0UL, 0UL, 1, 1UL ); + ulong footprint = fd_store_footprint( 8UL, 31840UL, 1UL, 0UL, 0UL, 1UL ); void * mem = fd_wksp_alloc_laddr( wksp, fd_store_align(), footprint, 1UL ); fd_store_t * store = fd_store_join( fd_store_new( mem, 8UL, 31840UL, 1UL, 0UL, 0UL, FD_SHRED_BLK_MAX, - 42UL, 1, 1UL ) ); + 42UL, 1UL ) ); FD_TEST( store ); store->disk_max_shreds = 2UL; @@ -1165,10 +1117,10 @@ test_disk_lazy_highest( fd_wksp_t * wksp ) { void test_disk_collision_eviction( fd_wksp_t * wksp ) { ulong const seed = 42UL; - ulong footprint = fd_store_footprint( 8UL, 31840UL, 1UL, 0UL, 0UL, 1, 1UL ); + ulong footprint = fd_store_footprint( 8UL, 31840UL, 1UL, 0UL, 0UL, 1UL ); void * mem = fd_wksp_alloc_laddr( wksp, fd_store_align(), footprint, 1UL ); fd_store_t * store = fd_store_join( fd_store_new( mem, 8UL, 31840UL, 1UL, 0UL, 0UL, FD_SHRED_BLK_MAX, - seed, 1, 1UL ) ); + seed, 1UL ) ); FD_TEST( store ); store->disk_max_shreds = 2UL; @@ -1236,13 +1188,13 @@ test_disk_max_shreds_per_block( fd_wksp_t * wksp ) { FD_TEST( fd_shredb_key_slot( fd_shredb_key_pack( FD_SHREDB_KEY_SLOT_MAX-1UL, 7U ) )==FD_SHREDB_KEY_SLOT_MAX-1UL ); FD_TEST( fd_shredb_key_shred_idx( fd_shredb_key_pack( FD_SHREDB_KEY_SLOT_MAX-1UL, 7U ) )==7U ); - ulong footprint = fd_store_footprint( 8UL, 31840UL, 1UL, 0UL, 0UL, 1, 1UL ); + ulong footprint = fd_store_footprint( 8UL, 31840UL, 1UL, 0UL, 0UL, 1UL ); void * mem = fd_wksp_alloc_laddr( wksp, fd_store_align(), footprint, 1UL ); - FD_TEST( !fd_store_new( mem, 8UL, 31840UL, 1UL, 0UL, 0UL, 0UL, 42UL, 1, 1UL ) ); - FD_TEST( !fd_store_new( mem, 8UL, 31840UL, 1UL, 0UL, 0UL, FD_SHREDB_HINT_VALID+1UL, 42UL, 1, 1UL ) ); - FD_TEST( !fd_store_new( mem, 8UL, 31840UL, 1UL, 0UL, 0UL, FD_SHRED_BLK_MAX, 42UL, 1, 0UL ) ); /* no slot_to_fecs partition */ + FD_TEST( !fd_store_new( mem, 8UL, 31840UL, 1UL, 0UL, 0UL, 0UL, 42UL, 1UL ) ); + FD_TEST( !fd_store_new( mem, 8UL, 31840UL, 1UL, 0UL, 0UL, FD_SHREDB_HINT_VALID+1UL, 42UL, 1UL ) ); + FD_TEST( !fd_store_new( mem, 8UL, 31840UL, 1UL, 0UL, 0UL, FD_SHRED_BLK_MAX, 42UL, 0UL ) ); /* no slot_to_fecs partition */ fd_store_t * store = fd_store_join( fd_store_new( mem, 8UL, 31840UL, 1UL, 0UL, 0UL, shred_max, - 42UL, 1, 1UL ) ); + 42UL, 1UL ) ); FD_TEST( store && store->max_shreds_per_block==shred_max ); store->disk_max_shreds = 8UL; @@ -1322,8 +1274,8 @@ void test_concurrent( fd_wksp_t * wksp ) { ulong tile_cnt = fd_tile_cnt(); ulong fec_max = tile_cnt * num_insert + 16UL; - void * mem = fd_wksp_alloc_laddr( wksp, fd_store_align(), fd_store_footprint( fec_max, 31840UL, 0UL, 0UL, 0UL, 0, 0UL ), 1UL ); - g_store = fd_store_join( fd_store_new( mem, fec_max, 31840UL, 0UL, 0UL, 0UL, FD_SHRED_BLK_MAX, 0UL, 0, 0UL ) ); + void * mem = fd_wksp_alloc_laddr( wksp, fd_store_align(), fd_store_footprint( fec_max, 31840UL, 0UL, 0UL, 0UL, 1UL ), 1UL ); + g_store = fd_store_join( fd_store_new( mem, fec_max, 31840UL, 0UL, 0UL, 0UL, FD_SHRED_BLK_MAX, 0UL, 1UL ) ); FD_TEST( g_store ); FD_COMPILER_MFENCE(); @@ -1401,7 +1353,6 @@ main( int argc, char ** argv ) { test_disk_collision_eviction( wksp ); test_disk_many_wraps( wksp ); test_disk_versions( wksp ); - test_disk_no_root_map( wksp ); test_disk_concurrent_writes( wksp ); test_disk_slot_hint( wksp ); test_disk_lazy_highest( wksp ); diff --git a/src/disco/topo/fd_topo.h b/src/disco/topo/fd_topo.h index 2c4060088bf..889b8df1021 100644 --- a/src/disco/topo/fd_topo.h +++ b/src/disco/topo/fd_topo.h @@ -395,7 +395,6 @@ struct fd_topo_tile { char adtl_dests_retransmit[ FD_TOPO_ADTL_DESTS_MAX ][ FD_HOSTPORT_BUF_MAX ]; ulong adtl_dests_leader_cnt; char adtl_dests_leader[ FD_TOPO_ADTL_DESTS_MAX ][ FD_HOSTPORT_BUF_MAX ]; - int alpenglow; } shred; struct { @@ -417,7 +416,6 @@ struct fd_topo_tile { ushort listen_port; int is_voting; - int is_alpenglow; char cluster[ 32 ]; char identity_key_path[ PATH_MAX ]; @@ -459,7 +457,6 @@ struct fd_topo_tile { char identity_key_path[ PATH_MAX ]; int delay_startup; - int alpenglow; int snapshot_server_enabled; char snapshot_server_host[ FD_FQDN_BUF_MAX ]; @@ -529,8 +526,6 @@ struct fd_topo_tile { uchar tip_distribution_program_addr[ 32 ]; char vote_account_path[ PATH_MAX ]; } bundle; - - int alpenglow; } replay; struct { @@ -707,7 +702,6 @@ struct fd_topo_tile { ulong banks_obj_id; ulong shmem_obj_id; /* shared parallel snapin state */ ulong max_txn_per_slot; - int alpenglow; } snapin; struct { diff --git a/src/discof/repair/test_rserve_tile.c b/src/discof/repair/test_rserve_tile.c index 4c6ba0267d8..13861bc7110 100644 --- a/src/discof/repair/test_rserve_tile.c +++ b/src/discof/repair/test_rserve_tile.c @@ -334,10 +334,10 @@ test_shred_for_block_id( fd_repair_t * client, ping_cache_add( ctx ); FD_TEST( fd_blockdb_insert( ctx->blockdb, blk_slot, &blk_id, blk_parent, &blk_parent_id, FEC_CNT, (uchar const *)blk_roots ) ); - ulong footprint = fd_store_footprint( 8UL, 31840UL, 1UL, 0UL, 0UL, 1, 1UL ); + ulong footprint = fd_store_footprint( 8UL, 31840UL, 1UL, 0UL, 0UL, 1UL ); void * store_mem = fd_wksp_alloc_laddr( wksp, fd_store_align(), footprint, 1UL ); FD_TEST( store_mem ); - ctx->store = fd_store_join( fd_store_new( store_mem, 8UL, 31840UL, 1UL, 0UL, 0UL, FD_SHRED_BLK_MAX, 42UL, 1, 1UL ) ); + ctx->store = fd_store_join( fd_store_new( store_mem, 8UL, 31840UL, 1UL, 0UL, 0UL, FD_SHRED_BLK_MAX, 42UL, 1UL ) ); FD_TEST( ctx->store ); ctx->store->disk_max_shreds = 16UL; ctx->disk_fd = fd_store_file_create( TEST_STORE_PATH, ctx->store->wire_off, ctx->store->disk_max_shreds ); diff --git a/src/discof/replay/fd_replay_tile.c b/src/discof/replay/fd_replay_tile.c index e308051837d..c9c317872d8 100644 --- a/src/discof/replay/fd_replay_tile.c +++ b/src/discof/replay/fd_replay_tile.c @@ -4534,7 +4534,7 @@ unprivileged_init( fd_topo_t const * topo, memset( ctx->reception_stats, 0, sizeof(fd_reception_stats_t)*ctx->reception_stats_cnt ); ctx->leader_stats.slot = ULONG_MAX; - ctx->sched = fd_sched_join( fd_sched_new( sched_mem, ctx->rng, tile->replay.sched_depth, tile->replay.max_live_slots, ctx->max_shreds_per_block, ctx->max_txn_per_slot, fd_topo_tile_name_cnt( topo, "execrp" ), 1 ) ); + ctx->sched = fd_sched_join( fd_sched_new( sched_mem, ctx->rng, tile->replay.sched_depth, tile->replay.max_live_slots, ctx->max_shreds_per_block, ctx->max_txn_per_slot, fd_topo_tile_name_cnt( topo, "execrp" ) ) ); FD_TEST( ctx->sched ); ctx->in_cnt = tile->in_cnt; diff --git a/src/discof/replay/fd_sched.c b/src/discof/replay/fd_sched.c index 97029221142..ba890bfba80 100644 --- a/src/discof/replay/fd_sched.c +++ b/src/discof/replay/fd_sched.c @@ -215,7 +215,7 @@ struct fd_sched_block { fd_block_footer_t footer; /* Alpenglow block structure, mirroring agave's BlockComponentStage - as a set of "seen" flags. Only maintained when sched->is_alpenglow. + as a set of "seen" flags. Together with mblk_cnt they determine what may come next: agave stage header footer alpentick mblk_cnt @@ -299,7 +299,6 @@ struct fd_sched { ulong max_mblk_per_slot; /* Immutable, FD_SCHED_MAX_MBLK_PER_SLOT scaled with max_shreds_per_block. */ fd_chkdup_t chkdup[ 1 ]; fd_sched_metrics_t metrics[ 1 ]; - int is_alpenglow; /* set if alpenglow is enabled. */ ulong canary; /* == FD_SCHED_MAGIC */ ulong depth; /* Immutable. */ ulong block_cnt_max; /* Immutable. */ @@ -752,8 +751,7 @@ fd_sched_new( void * mem, ulong block_cnt_max, ulong max_shreds_per_block, ulong max_txn_per_slot, - ulong exec_cnt, - int is_alpenglow ) { + ulong exec_cnt ) { if( FD_UNLIKELY( !mem ) ) { FD_LOG_WARNING(( "NULL mem" )); @@ -832,7 +830,6 @@ fd_sched_new( void * mem, sched->next_ready_last_tick = LONG_MAX; sched->next_ready_last_bank_idx = ULONG_MAX; - sched->is_alpenglow = is_alpenglow; sched->canary = FD_SCHED_MAGIC; sched->depth = depth; sched->block_cnt_max = block_cnt_max; @@ -1261,7 +1258,7 @@ fd_sched_fec_ingest( fd_sched_t * sched, return 0; } - if( FD_UNLIKELY( block->fec_eos && sched->is_alpenglow ) ) { + if( FD_UNLIKELY( block->fec_eos ) ) { int dead_reason = ag_on_final( block ); if( FD_UNLIKELY( dead_reason!=FD_SCHED_DEAD_REASON_NONE ) ) { handle_bad_block( sched, block, dead_reason ); @@ -2459,15 +2456,11 @@ fd_sched_parse( fd_sched_t * sched, fd_sched_block_t * block, fd_sched_alut_ctx_ FD_LOG_INFO(( "bad block: TOO_MANY_TXNS, microblock header declared too many transactions, slot %lu, parent slot %lu, txn_parsed_cnt %u, hdr->txn_cnt %lu", block->slot, block->parent_slot, block->txn_parsed_cnt, hdr->txn_cnt )); return FD_SCHED_DEAD_REASON_TOO_MANY_TXNS; } - if( FD_UNLIKELY( !sched->is_alpenglow && hdr->hash_cnt>fd_ulong_sat_sub( FD_RUNTIME_MAX_HASHES_PER_TICK, block->curr_tick_hashcnt ) ) ) { - FD_LOG_INFO(( "bad block: TICK_HASHES_OVERFLOW_INGEST, slot %lu, parent slot %lu, curr_tick_hashcnt %lu, hdr->hash_cnt %lu", block->slot, block->parent_slot, block->curr_tick_hashcnt, hdr->hash_cnt )); - return FD_SCHED_DEAD_REASON_TICK_HASHES_OVERFLOW_INGEST; - } - if( FD_UNLIKELY( sched->is_alpenglow && hdr->hash_cnt!=1UL ) ) { + if( FD_UNLIKELY( hdr->hash_cnt!=1UL ) ) { FD_LOG_INFO(( "bad block: ALPENGLOW_HASH_CNT, slot %lu, parent slot %lu, mblk idx %u declared hash_cnt %lu", block->slot, block->parent_slot, block->mblk_cnt, hdr->hash_cnt )); return FD_SCHED_DEAD_REASON_ALPENGLOW_HASH_CNT; } - if( FD_UNLIKELY( sched->is_alpenglow && block->footer_seen ) ) { + if( FD_UNLIKELY( block->footer_seen ) ) { if( FD_UNLIKELY( hdr->txn_cnt ) ) { FD_LOG_INFO(( "bad block: ENTRY_AFTER_BLOCK_FOOTER, slot %lu, parent slot %lu, transaction microblock after footer", block->slot, block->parent_slot )); return FD_SCHED_DEAD_REASON_ENTRY_AFTER_BLOCK_FOOTER; @@ -2581,7 +2574,7 @@ fd_sched_parse( fd_sched_t * sched, fd_sched_block_t * block, fd_sched_alut_ctx_ if( block->txns_rem==0UL && block->mblks_rem==0UL && block->fec_sob ) { CHECK_LEFT( sizeof(ulong) ); FD_TEST( block->fec_buf_soff==0U ); - if( FD_UNLIKELY( sched->is_alpenglow && !FD_LOAD( ulong, block->fec_buf ) && !block->fec_eob ) ) { + if( FD_UNLIKELY( !FD_LOAD( ulong, block->fec_buf ) && !block->fec_eob ) ) { /* Block marker split across FEC sets */ fd_block_marker_t peek[1]; if( FD_UNLIKELY( FD_BLOCK_MARKER_DE_ERR_SZ==fd_block_marker_de( peek, block->fec_buf, (ulong)block->fec_buf_sz ) && @@ -2597,7 +2590,7 @@ fd_sched_parse( fd_sched_t * sched, fd_sched_block_t * block, fd_sched_alut_ctx_ block->fec_sob = 0; - if( FD_UNLIKELY( !block->mblks_rem && sched->is_alpenglow ) ) { + if( FD_UNLIKELY( !block->mblks_rem ) ) { /* A zero-microblock batch is a block marker */ fd_block_marker_t marker[1]; int err = fd_block_marker_de( marker, block->fec_buf, (ulong)block->fec_buf_sz ); @@ -2608,12 +2601,9 @@ fd_sched_parse( fd_sched_t * sched, fd_sched_block_t * block, fd_sched_alut_ctx_ int dead_reason = ag_on_marker( sched, block, marker ); if( FD_UNLIKELY( dead_reason!=FD_SCHED_DEAD_REASON_NONE ) ) return dead_reason; block->fec_buf_soff = (uint)( FD_BLOCK_MARKER_PREAMBLE_SZ+FD_LOAD( ushort, block->fec_buf+FD_BLOCK_MARKER_PREAMBLE_SZ-sizeof(ushort) ) ); /* the marker is not trailing bytes */ - } else if( FD_UNLIKELY( block->mblks_rem && sched->is_alpenglow ) ) { + } else { int dead_reason = ag_on_entry_batch( block ); if( FD_UNLIKELY( dead_reason!=FD_SCHED_DEAD_REASON_NONE ) ) return dead_reason; - } else if( FD_UNLIKELY( !block->mblks_rem && !sched->is_alpenglow ) ) { - FD_LOG_INFO(( "bad block: ZERO_MICROBLOCKS, slot %lu, parent slot %lu, mblk_cnt %u (%u ticks)", block->slot, block->parent_slot, block->mblk_cnt, block->mblk_tick_cnt )); - return FD_SCHED_DEAD_REASON_ZERO_MICROBLOCKS; } continue; } diff --git a/src/discof/replay/fd_sched.h b/src/discof/replay/fd_sched.h index 33c148dd0d6..17135e25aee 100644 --- a/src/discof/replay/fd_sched.h +++ b/src/discof/replay/fd_sched.h @@ -303,8 +303,7 @@ fd_sched_new( void * mem, ulong block_cnt_max, ulong max_shreds_per_block, ulong max_txn_per_slot, - ulong exec_cnt, - int is_alpenglow ); + ulong exec_cnt ); fd_sched_t * fd_sched_join( void * mem ); diff --git a/src/discof/replay/fuzz_sched_rdisp.c b/src/discof/replay/fuzz_sched_rdisp.c index 4095a45d7ba..decee67e8e2 100644 --- a/src/discof/replay/fuzz_sched_rdisp.c +++ b/src/discof/replay/fuzz_sched_rdisp.c @@ -12,6 +12,7 @@ #include "fd_sched.h" #include "../../ballet/bmtree/fd_bmtree.h" #include "../../ballet/sha256/fd_sha256.h" +#include "../../flamenco/alpenglow/fd_block_marker_serde.h" #include "../../flamenco/txn/fd_txn_generate.h" /* Keep the generated cases small. These limits are intentionally much @@ -22,7 +23,8 @@ #define TEST_MAX_ACCTS_PER_TXN 8UL #define TEST_MAX_MBLKS_PER_BLK 8UL #define TEST_MAX_BLOCK_BYTES 4096UL -#define TEST_MAX_SEGMENTS 8UL +#define TEST_MAX_SEGMENTS 8UL /* for the transaction batch */ +#define TEST_MAX_BLOCK_SEGMENTS ( TEST_MAX_SEGMENTS + 3UL ) /* + header, footer, alpentick */ /* Production defaults to more execrp tiles. Four is enough here to exercise task interleavings without making the state space blow up. */ #define TEST_EXEC_CNT 4UL @@ -104,7 +106,7 @@ typedef struct { ulong mblk_cnt; fd_hash_t mblk_start_hash[ TEST_MAX_MBLKS_PER_BLK ]; fd_hash_t mblk_hashed_hash[ TEST_MAX_MBLKS_PER_BLK ]; - ulong poh_dispatch_mblk_cnt; + ulong poh_dispatch_mask; /* bit l set once mblk l had a PoH task */ ulong mblk_pool_idx [ TEST_MAX_MBLKS_PER_BLK ]; ulong mblk_curr_hashcnt[ TEST_MAX_MBLKS_PER_BLK ]; fd_hash_t mblk_curr_hash [ TEST_MAX_MBLKS_PER_BLK ]; @@ -113,7 +115,11 @@ typedef struct { ulong encoded_sz; uint txn_end_off[ TEST_MAX_TXNS_PER_BLOCK ]; uchar txn_mblk_idx[ TEST_MAX_TXNS_PER_BLOCK ]; - uint seg_end[ TEST_MAX_SEGMENTS ]; + ulong txn_batch_off; + ulong txn_batch_end; + ulong tick_batch_off; + uint seg_end[ TEST_MAX_BLOCK_SEGMENTS ]; + uchar seg_eob[ TEST_MAX_BLOCK_SEGMENTS ]; ulong seg_cnt; ulong ingested_seg_cnt; int poh_params_set; @@ -266,15 +272,20 @@ merkle_root( fd_hash_t * out, scheduler checks would no longer be meaningful. */ FD_FN_UNUSED static void verify_block_encoding( block_t const * block ) { - FD_TEST( block->encoded_sz>=sizeof(ulong)+sizeof(fd_microblock_hdr_t) ); - FD_TEST( FD_LOAD( ulong, block->encoded )==block->mblk_cnt ); + FD_TEST( block->encoded_sz>=block->tick_batch_off+sizeof(ulong)+sizeof(fd_microblock_hdr_t) ); + FD_TEST( FD_LOAD( ulong, block->encoded+block->txn_batch_off )==block->mblk_cnt-1UL ); + FD_TEST( FD_LOAD( ulong, block->encoded+block->tick_batch_off )==1UL ); - ulong cursor = sizeof(ulong); + ulong cursor = block->txn_batch_off+sizeof(ulong); ulong txn_off = 0UL; fd_hash_t prev_hash[ 1 ]; fd_memcpy( prev_hash, block->start_poh, sizeof(fd_hash_t) ); for( ulong i=0UL; imblk_cnt; i++ ) { + if( FD_UNLIKELY( i==block->mblk_cnt-1UL ) ) { + FD_TEST( cursor==block->txn_batch_end ); + cursor = block->tick_batch_off+sizeof(ulong); + } FD_TEST( cursor + sizeof(fd_microblock_hdr_t) <= block->encoded_sz ); fd_microblock_hdr_t const * hdr = fd_type_pun_const( block->encoded + cursor ); cursor += sizeof(fd_microblock_hdr_t); @@ -470,26 +481,23 @@ build_txn( reader_t * r, ulong block_idx, ulong txn_idx, txn_t * out ) { stream and caches every intermediate artifact that later harness checks need. - The generated wire image always has the form + The generated wire image is an Alpenglow block, four batches: - [ ulong mblk_cnt ][ txn microblock... ][ final tick microblock ] + [ header marker ] + [ ulong mblk_cnt ][ txn microblock... ] + [ footer marker ] + [ ulong 1 ][ alpentick ] where each transaction microblock covers a contiguous slice of - block->txn[]. For transaction microblocks, wire_hash_cnt is stored - exactly as it would appear on the wire: pure_hash_cnt plus one extra - count for the final PoH record/mixin. The harness keeps both the - start hash and the hash after the pure-hash portion so it can mirror - the scheduler's split between PoH work and transaction dispatch. A - final tick-only microblock is always appended so every synthetic - block advances exactly one tick and produces a deterministic - hashes_per_tick value for fd_sched_set_poh_params(). + block->txn[]. Every microblock declares one hash: for a transaction + microblock that is the mixin, so it has no pure hashing, and the + alpentick is a single hash, so hashes_per_tick is 1 and every block + advances exactly one tick. After encoding the block, the builder partitions the byte stream into - fuzz-controlled segments for incremental FEC ingestion. If the block - contains anything beyond the leading microblock-count word, the first - segment is forced to stop right after that word. This keeps the - parser's "new block" boundary stable while still letting later - segments split headers and transactions at arbitrary offsets. + FEC sets. Each batch ends a FEC set. The transaction batch is + split after its microblock-count word and then at fuzz-controlled + offsets, so FEC sets split headers and transactions arbitrarily. The function also precomputes the transaction conflict predecessor masks and selects a failure_mode used later by the harness to inject @@ -522,45 +530,54 @@ build_block( reader_t * r, block->pred_mask[ i ] = pred; } + /* Header marker batch */ + fd_block_marker_t marker[1]; + fd_memset( marker, 0, sizeof(fd_block_marker_t) ); + marker->kind = FD_BLOCK_MARKER_KIND_HEADER; + marker->header.parent_slot = parent_slot; + uchar marker_buf[ FD_BLOCK_MARKER_SER_MAX ]; + ulong header_sz = fd_block_marker_ser( marker, marker_buf ); + FD_TEST( header_sz && header_sz<=sizeof(block->encoded) ); + fd_memcpy( block->encoded, marker_buf, header_sz ); + + /* Transaction batch. Under Alpenglow every microblock declares + exactly one hash, so a transaction microblock is a bare mixin of + the running PoH. */ uchar * p = block->encoded; ulong txns_remaining = block->txn_cnt; - ulong cursor = 0UL; + ulong cursor = header_sz; ulong txn_off = 0UL; - ulong tick_hash_cnt = 0UL; fd_hash_t prev_hash[ 1 ]; fd_memcpy( prev_hash, parent_hash, sizeof(fd_hash_t) ); + block->txn_batch_off = cursor; + cursor += sizeof(ulong); + block->mblk_cnt = 0UL; while( txns_remaining ) { - ulong tx_mblk_slots_rem = TEST_MAX_MBLKS_PER_BLK - block->mblk_cnt - 1UL; /* Reserve one slot for the final tick. */ + ulong tx_mblk_slots_rem = TEST_MAX_MBLKS_PER_BLK - block->mblk_cnt - 1UL; /* Reserve one slot for the alpentick. */ FD_TEST( tx_mblk_slots_rem>0UL ); ulong min_group_txn_cnt = ( txns_remaining + tx_mblk_slots_rem - 1UL ) / tx_mblk_slots_rem; ulong group_txn_cnt = min_group_txn_cnt + read_range( r, txns_remaining - min_group_txn_cnt + 1UL ); - ulong pure_hash_cnt = 1UL + read_range( r, 3UL ); - ulong wire_hash_cnt = pure_hash_cnt + 1UL; fd_memcpy( block->mblk_start_hash + block->mblk_cnt, prev_hash, sizeof(fd_hash_t) ); + fd_memcpy( block->mblk_hashed_hash + block->mblk_cnt, prev_hash, sizeof(fd_hash_t) ); block->mblk[ block->mblk_cnt ].start_txn = (uint)txn_off; block->mblk[ block->mblk_cnt ].txn_cnt = (uint)group_txn_cnt; - block->mblk[ block->mblk_cnt ].wire_hash_cnt = wire_hash_cnt; + block->mblk[ block->mblk_cnt ].wire_hash_cnt = 1UL; block->mblk_cnt++; - tick_hash_cnt += wire_hash_cnt; - - fd_hash_t after_hashes[ 1 ]; - repeat_hash( after_hashes, prev_hash, pure_hash_cnt ); - fd_memcpy( block->mblk_hashed_hash + block->mblk_cnt-1UL, after_hashes, sizeof(fd_hash_t) ); fd_hash_t merkle[ 1 ]; merkle_root( merkle, block->txn + txn_off, group_txn_cnt ); uchar mixin[ 64 ]; - fd_memcpy( mixin, after_hashes->hash, 32UL ); - fd_memcpy( mixin+32UL, merkle->hash, 32UL ); + fd_memcpy( mixin, prev_hash->hash, 32UL ); + fd_memcpy( mixin+32UL, merkle->hash, 32UL ); fd_hash_t end_hash[ 1 ]; fd_sha256_hash( mixin, sizeof(mixin), end_hash->hash ); fd_microblock_hdr_t hdr = { - .hash_cnt = wire_hash_cnt, + .hash_cnt = 1UL, .txn_cnt = group_txn_cnt }; fd_memcpy( hdr.hash, end_hash->hash, sizeof(fd_hash_t) ); @@ -580,53 +597,68 @@ build_block( reader_t * r, txn_off += group_txn_cnt; txns_remaining -= group_txn_cnt; } - - ulong tick_wire_hash_cnt = 1UL + read_range( r, 4UL ); - block->hashes_per_tick = tick_hash_cnt + tick_wire_hash_cnt; + FD_STORE( ulong, block->encoded+block->txn_batch_off, block->mblk_cnt ); + block->txn_batch_end = cursor; + + /* Footer marker batch */ + fd_memset( marker, 0, sizeof(fd_block_marker_t) ); + marker->kind = FD_BLOCK_MARKER_KIND_FOOTER; + ulong footer_sz = fd_block_marker_ser( marker, marker_buf ); + FD_TEST( footer_sz && cursor+footer_sz<=sizeof(block->encoded) ); + fd_memcpy( block->encoded+cursor, marker_buf, footer_sz ); + ulong footer_off = cursor; + cursor += footer_sz; + + /* Alpentick batch: one tick of one hash, so hashes_per_tick is 1. */ + block->hashes_per_tick = 1UL; FD_TEST( block->mblk_cntmblk_start_hash + block->mblk_cnt, prev_hash, sizeof(fd_hash_t) ); block->mblk[ block->mblk_cnt ].start_txn = (uint)block->txn_cnt; block->mblk[ block->mblk_cnt ].txn_cnt = 0U; - block->mblk[ block->mblk_cnt ].wire_hash_cnt = tick_wire_hash_cnt; + block->mblk[ block->mblk_cnt ].wire_hash_cnt = 1UL; block->mblk_cnt++; fd_hash_t tick_end[ 1 ]; - repeat_hash( tick_end, prev_hash, tick_wire_hash_cnt ); + repeat_hash( tick_end, prev_hash, 1UL ); fd_memcpy( block->mblk_hashed_hash + block->mblk_cnt-1UL, tick_end, sizeof(fd_hash_t) ); fd_memcpy( block->end_poh, tick_end, sizeof(fd_hash_t) ); fd_microblock_hdr_t tick_hdr = { - .hash_cnt = tick_wire_hash_cnt, + .hash_cnt = 1UL, .txn_cnt = 0UL }; fd_memcpy( tick_hdr.hash, tick_end->hash, sizeof(fd_hash_t) ); - FD_TEST( cursor + sizeof(fd_microblock_hdr_t) + sizeof(ulong) <= sizeof(block->encoded) ); - memmove( block->encoded + sizeof(ulong), block->encoded, cursor ); - FD_STORE( ulong, block->encoded, block->mblk_cnt ); - for( ulong i=0UL; itxn_cnt; i++ ) block->txn_end_off[ i ] += (uint)sizeof(ulong); + FD_TEST( cursor + sizeof(ulong) + sizeof(fd_microblock_hdr_t) <= sizeof(block->encoded) ); + block->tick_batch_off = cursor; + FD_STORE( ulong, block->encoded+cursor, 1UL ); cursor += sizeof(ulong); fd_memcpy( block->encoded + cursor, &tick_hdr, sizeof(fd_microblock_hdr_t) ); cursor += sizeof(fd_microblock_hdr_t); block->encoded_sz = cursor; - block->seg_cnt = 1UL + read_range( r, fd_ulong_min( TEST_MAX_SEGMENTS, block->encoded_sz ) ); - if( FD_LIKELY( block->encoded_sz>sizeof(ulong) ) ) block->seg_cnt = fd_ulong_max( block->seg_cnt, 2UL ); - ulong off = 0UL; - if( FD_LIKELY( block->encoded_sz>sizeof(ulong) ) ) { - off = sizeof(ulong); - block->seg_end[ 0 ] = (uint)off; - ulong rem = block->encoded_sz - off; - for( ulong i=1UL; iseg_cnt; i++ ) { - ulong min_rem = block->seg_cnt-i-1UL; - ulong len = fd_ulong_if( i+1UL==block->seg_cnt, rem, 1UL + read_range( r, rem-min_rem ) ); - off += len; - block->seg_end[ i ] = (uint)off; - rem -= len; - } - } else { - block->seg_end[ 0 ] = (uint)block->encoded_sz; + /* Each batch ends on a FEC set boundary. The markers and the + alpentick are one FEC set each, the transaction batch is split at + fuzz-controlled offsets after its microblock-count word. */ + ulong seg = 0UL; + block->seg_end[ seg ] = (uint)header_sz; block->seg_eob[ seg++ ] = 1; + ulong txn_seg_cnt = fd_ulong_max( 2UL, 1UL + read_range( r, TEST_MAX_SEGMENTS ) ); + ulong off = block->txn_batch_off + sizeof(ulong); + block->seg_end[ seg ] = (uint)off; block->seg_eob[ seg++ ] = 0; + ulong rem = block->txn_batch_end - off; + txn_seg_cnt = fd_ulong_min( txn_seg_cnt, 1UL + rem ); + for( ulong i=1UL; iseg_end[ seg ] = (uint)off; block->seg_eob[ seg++ ] = 0; } + block->seg_eob[ seg-1UL ] = 1; + block->seg_end[ seg ] = (uint)(footer_off+footer_sz); block->seg_eob[ seg++ ] = 1; + block->seg_end[ seg ] = (uint)block->encoded_sz; block->seg_eob[ seg++ ] = 1; + FD_TEST( seg<=TEST_MAX_BLOCK_SEGMENTS ); + block->seg_cnt = seg; switch( read_range( r, 6UL ) ) { case 0UL: block->failure_mode = TEST_FAIL_EXEC; break; @@ -693,7 +725,7 @@ prepare_fec( block_t const * block, .fec = store_fec, .data = (uchar *)block->encoded + seg_start, .shred_cnt = 1U, - .is_last_in_batch = (uint)(seg+1UL==block->seg_cnt), + .is_last_in_batch = (uint)(!!block->seg_eob[ seg ]), .is_last_in_block = (uint)(seg+1UL==block->seg_cnt), .is_first_in_block = (uint)(seg==0UL), }; @@ -766,7 +798,7 @@ build_case( case_t * tc, uchar const * data, ulong data_sz ) { fd_rng_t rng[1]; fd_rng_join( fd_rng_new( rng, 0U, 0UL ) ); - tc->sched = fd_sched_join( fd_sched_new( tc->mem, rng, depth, block_cnt_max, FD_SHRED_BLK_MAX, FD_MAX_TXN_PER_SLOT, TEST_EXEC_CNT, 0 ) ); + tc->sched = fd_sched_join( fd_sched_new( tc->mem, rng, depth, block_cnt_max, FD_SHRED_BLK_MAX, FD_MAX_TXN_PER_SLOT, TEST_EXEC_CNT ) ); /* Seed the scheduler with bank 0 as a completed snapshot root. The notify/advance pair is a no-op for the current root, but using the @@ -1283,12 +1315,19 @@ run_sched_rdisp_case( uchar const * data, ulong data_sz ) { for( ulong i=0UL; ipoh_hash->cnt; i++ ) { ulong pool_idx = task->poh_hash->mblk_idx[ i ]; ulong local_mblk_idx = ULONG_MAX; - for( ulong l=0UL; lpoh_dispatch_mblk_cnt; l++ ) { + for( ulong l=0UL; lmblk_cnt; l++ ) { + if( !fd_ulong_extract_bit( block->poh_dispatch_mask, (int)l ) ) continue; if( block->mblk_pool_idx[ l ]==pool_idx && block->mblk_curr_hashcnt[ l ]poh_dispatch_mblk_cnt++; + /* Microblocks with nothing to hash may retire without a + PoH task, so find the microblock by its start hash. */ + for( ulong l=0UL; lmblk_cnt; l++ ) { + if( fd_ulong_extract_bit( block->poh_dispatch_mask, (int)l ) ) continue; + if( !memcmp( task->poh_hash->hash+i, block->mblk_start_hash + l, sizeof(fd_hash_t) ) ) { local_mblk_idx = l; break; } + } FD_TEST( local_mblk_idxmblk_cnt ); + block->poh_dispatch_mask = fd_ulong_set_bit( block->poh_dispatch_mask, (int)local_mblk_idx ); block->mblk_pool_idx [ local_mblk_idx ] = pool_idx; block->mblk_curr_hashcnt[ local_mblk_idx ] = 0UL; fd_memcpy( block->mblk_curr_hash + local_mblk_idx, block->mblk_start_hash + local_mblk_idx, sizeof(fd_hash_t) ); @@ -1299,7 +1338,9 @@ run_sched_rdisp_case( uchar const * data, ulong data_sz ) { entry->local_mblk_idx[ i ] = local_mblk_idx; fd_memcpy( entry->hash+i, task->poh_hash->hash+i, sizeof(fd_hash_t) ); } - FD_TEST( task->poh_hash->hashcnt && task->poh_hash->hashcnt<=min_todo ); + /* Transaction microblocks declare one hash, which is the mixin, + so their PoH tasks have nothing to hash. */ + FD_TEST( task->poh_hash->hashcnt<=min_todo && ( !min_todo || task->poh_hash->hashcnt ) ); break; } default: FD_LOG_ERR(( "unexpected task_type %lu", task->task_type )); diff --git a/src/discof/replay/test_replay_tile.c b/src/discof/replay/test_replay_tile.c index d0c9bd7a614..a66fe8457f8 100644 --- a/src/discof/replay/test_replay_tile.c +++ b/src/discof/replay/test_replay_tile.c @@ -1229,9 +1229,9 @@ test_consensus_root_notification_handoff( fd_wksp_t * wksp ) { ctx->block_id_len = bank_cnt; ctx->max_live_slots = bank_cnt; - void * store_mem = fd_wksp_alloc_laddr( wksp, fd_store_align(), fd_store_footprint( 2UL, 1UL, 0UL, 0UL, 0UL, 1, 1UL ), 1UL ); + void * store_mem = fd_wksp_alloc_laddr( wksp, fd_store_align(), fd_store_footprint( 2UL, 1UL, 0UL, 0UL, 0UL, 1UL ), 1UL ); FD_TEST( store_mem ); - ctx->store = fd_store_join( fd_store_new( store_mem, 2UL, 1UL, 0UL, 0UL, 0UL, FD_SHRED_BLK_MAX, 0UL, 1, 1UL ) ); + ctx->store = fd_store_join( fd_store_new( store_mem, 2UL, 1UL, 0UL, 0UL, 0UL, FD_SHRED_BLK_MAX, 0UL, 1UL ) ); FD_TEST( ctx->store ); FD_TEST( fd_store_map_ljoin( ctx->store, ctx->map_join ) ); ctx->store_disk_fd = -1; @@ -1385,9 +1385,9 @@ setup_rooting_ctx( fd_replay_tile_t * ctx, setup_stem( ctx, wksp ); setup_votor_input( ctx, wksp ); - void * store_mem = fd_wksp_alloc_laddr( wksp, fd_store_align(), fd_store_footprint( 2UL, 1UL, 0UL, 0UL, 0UL, 1, 1UL ), 1UL ); + void * store_mem = fd_wksp_alloc_laddr( wksp, fd_store_align(), fd_store_footprint( 2UL, 1UL, 0UL, 0UL, 0UL, 1UL ), 1UL ); FD_TEST( store_mem ); - ctx->store = fd_store_join( fd_store_new( store_mem, 2UL, 1UL, 0UL, 0UL, 0UL, FD_SHRED_BLK_MAX, 0UL, 1, 1UL ) ); + ctx->store = fd_store_join( fd_store_new( store_mem, 2UL, 1UL, 0UL, 0UL, 0UL, FD_SHRED_BLK_MAX, 0UL, 1UL ) ); FD_TEST( ctx->store ); FD_TEST( fd_store_map_ljoin( ctx->store, ctx->map_join ) ); @@ -3325,9 +3325,9 @@ setup_snapshot_test_ctx( fd_replay_tile_t * ctx, ctx->block_id_len = bank_cnt; - void * store_mem = fd_wksp_alloc_laddr( wksp, fd_store_align(), fd_store_footprint( 2UL, 1UL, 0UL, 0UL, 0UL, 1, 1UL ), 1UL ); + void * store_mem = fd_wksp_alloc_laddr( wksp, fd_store_align(), fd_store_footprint( 2UL, 1UL, 0UL, 0UL, 0UL, 1UL ), 1UL ); FD_TEST( store_mem ); - ctx->store = fd_store_join( fd_store_new( store_mem, 2UL, 1UL, 0UL, 0UL, 0UL, FD_SHRED_BLK_MAX, 0UL, 1, 1UL ) ); + ctx->store = fd_store_join( fd_store_new( store_mem, 2UL, 1UL, 0UL, 0UL, 0UL, FD_SHRED_BLK_MAX, 0UL, 1UL ) ); FD_TEST( ctx->store ); FD_TEST( fd_store_map_ljoin( ctx->store, ctx->map_join ) ); diff --git a/src/discof/replay/test_sched.c b/src/discof/replay/test_sched.c index 464b7a6049e..67bd73e5f24 100644 --- a/src/discof/replay/test_sched.c +++ b/src/discof/replay/test_sched.c @@ -43,35 +43,6 @@ repeat_hash( fd_hash_t * out, fd_memcpy( out->hash, cur, 32UL ); } -static void -encode_tick_block( uchar * encoded, - ulong * encoded_sz, - fd_hash_t const * start_poh, - ulong const * tick_hashcnt, - ulong tick_cnt ) { - FD_STORE( ulong, encoded, tick_cnt ); - ulong cursor = sizeof(ulong); - - fd_hash_t prev_hash[ 1 ]; - fd_memcpy( prev_hash, start_poh, sizeof(fd_hash_t) ); - - for( ulong i=0UL; ihash, sizeof(fd_hash_t) ); - fd_memcpy( encoded + cursor, &hdr, sizeof(fd_microblock_hdr_t) ); - cursor += sizeof(fd_microblock_hdr_t); - fd_memcpy( prev_hash, end_hash, sizeof(fd_hash_t) ); - } - - *encoded_sz = cursor; -} - static ulong build_shred_test_txn( uchar * payload ) { fd_pubkey_t payer[ 1 ]; @@ -99,6 +70,101 @@ build_shred_test_txn( uchar * payload ) { return fd_txn_add_instr( meta, payload, 1U, &instr_acct, 1UL, &instr_data, 1UL ); } +/* Alpenglow block structure cases. Each component is its own FEC set + (one batch per FEC set, as sched assumes). Markers are serialized + with fd_block_marker_ser, which emits the whole zero-microblock + batch; a tick is a one-microblock batch chained from the running + PoH. */ + +#define AG_COMP_HEADER (0) +#define AG_COMP_FOOTER (1) +#define AG_COMP_UPDATE_PARENT (2) +#define AG_COMP_TICK (3) /* alpentick: one hash */ +#define AG_COMP_TICK_0HASH (4) /* tick declaring zero hashes: invalid under Alpenglow */ +#define AG_COMP_TICK_2HASH (5) /* tick declaring two hashes: invalid under Alpenglow */ + +/* AG_SPLIT( comp, cut ) is comp delivered as two FEC sets of the same + batch, the first holding the leading cut bytes. */ +#define AG_SPLIT( comp, cut ) ((comp) | (int)((cut)<<8)) + +static ulong +encode_ag_component( uchar * out, + int comp, + fd_hash_t * prev_hash ) { + fd_block_marker_t marker[1]; + fd_memset( marker, 0, sizeof(fd_block_marker_t) ); + switch( comp ) { + case AG_COMP_HEADER: + marker->kind = FD_BLOCK_MARKER_KIND_HEADER; + marker->header.parent_slot = TEST_ROOT_SLOT; + return fd_block_marker_ser( marker, out ); + case AG_COMP_FOOTER: + marker->kind = FD_BLOCK_MARKER_KIND_FOOTER; + return fd_block_marker_ser( marker, out ); + case AG_COMP_UPDATE_PARENT: { + /* fd_block_marker_ser only produces headers and footers (nothing + Firedancer emits is an UpdateParent), so lay the wire format out + by hand: preamble (entry_cnt=0, version=1, tag, length) then a + V1 payload (version=1, new_parent_slot, new_parent_block_id). */ + ulong off = 0UL; + FD_STORE( ulong, out+off, 0UL ); off += sizeof(ulong); + FD_STORE( ushort, out+off, (ushort)1 ); off += sizeof(ushort); + out[ off ] = (uchar)FD_BLOCK_MARKER_KIND_UPDATE_PARENT; off += sizeof(uchar); + FD_STORE( ushort, out+off, (ushort)FD_UPDATE_PARENT_SER_SZ ); off += sizeof(ushort); + out[ off ] = (uchar)1; off += sizeof(uchar); + FD_STORE( ulong, out+off, TEST_ROOT_SLOT ); off += sizeof(ulong); + fd_memset( out+off, 0, sizeof(fd_hash_t) ); off += sizeof(fd_hash_t); + return off; + } + case AG_COMP_TICK: + case AG_COMP_TICK_0HASH: + case AG_COMP_TICK_2HASH: { + ulong hash_cnt = comp==AG_COMP_TICK ? 1UL : comp==AG_COMP_TICK_0HASH ? 0UL : 2UL; + FD_STORE( ulong, out, 1UL ); + fd_hash_t end_hash[ 1 ]; + repeat_hash( end_hash, prev_hash, hash_cnt ); + fd_microblock_hdr_t hdr = { .hash_cnt = hash_cnt, .txn_cnt = 0UL }; + fd_memcpy( hdr.hash, end_hash->hash, sizeof(fd_hash_t) ); + fd_memcpy( out+sizeof(ulong), &hdr, sizeof(fd_microblock_hdr_t) ); + fd_memcpy( prev_hash, end_hash, sizeof(fd_hash_t) ); + return sizeof(ulong)+sizeof(fd_microblock_hdr_t); + } + default: + FD_LOG_ERR(( "bad component %d", comp )); + } +} + +/* Ingests comp as one whole-batch FEC set of bank_idx, a child of bank + 1 at slot TEST_ROOT_SLOT+1. Returns fd_sched_fec_ingest's verdict. */ +static int +ingest_ag_component( fd_sched_t * sched, + ulong bank_idx, + int comp, + fd_hash_t * prev_hash, + int first, + int last ) { + uchar buf[ FD_BLOCK_MARKER_SER_MAX ] __attribute__((aligned(64))); + ulong sz = encode_ag_component( buf, comp, prev_hash ); + fd_store_fec_t store_fec[ 1 ] __attribute__((aligned(alignof(fd_store_fec_t)))); + fd_memset( store_fec, 0, sizeof(fd_store_fec_t) ); + store_fec->data_sz = (uint)sz; + store_fec->shred_sz[ 0 ] = (ushort)sz; + fd_sched_fec_t fec[ 1 ] = {{ + .bank_idx = bank_idx, + .parent_bank_idx = 1UL, + .slot = TEST_ROOT_SLOT+1UL, + .parent_slot = TEST_ROOT_SLOT, + .fec = store_fec, + .data = buf, + .shred_cnt = 1U, + .is_last_in_batch = 1U, + .is_last_in_block = !!last, + .is_first_in_block = !!first, + }}; + FD_TEST( fd_sched_fec_can_ingest( sched, fec ) ); + return fd_sched_fec_ingest( sched, fec ); +} + static void run_interleaved_fec_residual_case( void ) { ulong footprint = fd_sched_footprint( FD_SCHED_MIN_DEPTH, 4UL, FD_SHRED_BLK_MAX, FD_MAX_TXN_PER_SLOT ); @@ -106,7 +172,7 @@ run_interleaved_fec_residual_case( void ) { FD_TEST( mem ); fd_rng_t rng[ 1 ]; fd_rng_join( fd_rng_new( rng, 0U, 0UL ) ); - fd_sched_t * sched = fd_sched_join( fd_sched_new( mem, rng, FD_SCHED_MIN_DEPTH, 4UL, FD_SHRED_BLK_MAX, FD_MAX_TXN_PER_SLOT, TEST_EXEC_CNT, 0 ) ); + fd_sched_t * sched = fd_sched_join( fd_sched_new( mem, rng, FD_SCHED_MIN_DEPTH, 4UL, FD_SHRED_BLK_MAX, FD_MAX_TXN_PER_SLOT, TEST_EXEC_CNT ) ); FD_TEST( sched ); fd_sched_set_bypass_poh_verify( sched, 1 ); fd_sched_block_add_done( sched, 1UL, ULONG_MAX, TEST_ROOT_SLOT ); @@ -122,13 +188,11 @@ run_interleaved_fec_residual_case( void ) { for( ulong i=0UL; i<2UL; i++ ) { fd_hash_t tx_mblk_hash[ 1 ]; - fd_hash_t tick_hash[ 1 ]; hash_from_seed( start_poh+i, 0x459db07a9f2c0321UL+i ); hash_from_seed( tx_mblk_hash, 0x9e33470a182d6cbfUL+i ); - repeat_hash( tick_hash, tx_mblk_hash, 1UL ); ulong cursor = 0UL; - FD_STORE( ulong, encoded[ i ]+cursor, 2UL ); + FD_STORE( ulong, encoded[ i ]+cursor, 1UL ); cursor += sizeof(ulong); fd_microblock_hdr_t tx_hdr = { .hash_cnt = 1UL, @@ -144,18 +208,13 @@ run_interleaved_fec_residual_case( void ) { split_off[ i ] = cursor+txn_sz/2UL; fd_memcpy( encoded[ i ]+cursor, tagged_txn, txn_sz ); cursor += txn_sz; - - fd_microblock_hdr_t tick_hdr = { - .hash_cnt = 1UL, - .txn_cnt = 0UL, - }; - fd_memcpy( tick_hdr.hash, tick_hash->hash, sizeof(fd_hash_t) ); - fd_memcpy( encoded[ i ]+cursor, &tick_hdr, sizeof(tick_hdr) ); - cursor += sizeof(tick_hdr); encoded_sz[ i ] = cursor; } for( ulong i=0UL; i<2UL; i++ ) { + FD_TEST( ingest_ag_component( sched, 2UL+i, AG_COMP_HEADER, NULL, 1, 0 ) ); + fd_sched_set_poh_params( sched, 2UL+i, TEST_ROOT_TICK_HEIGHT, TEST_ROOT_TICK_HEIGHT+1UL, 1UL, start_poh+i ); + fd_store_fec_t store_fec[ 1 ] __attribute__((aligned(alignof(fd_store_fec_t)))); fd_memset( store_fec, 0, sizeof(fd_store_fec_t) ); FD_TEST( split_off[ i ]<=USHORT_MAX ); @@ -169,11 +228,9 @@ run_interleaved_fec_residual_case( void ) { .fec = store_fec, .data = encoded[ i ], .shred_cnt = 1U, - .is_first_in_block = 1U, }}; FD_TEST( fd_sched_fec_can_ingest( sched, fec ) ); FD_TEST( fd_sched_fec_ingest( sched, fec ) ); - fd_sched_set_poh_params( sched, 2UL+i, TEST_ROOT_TICK_HEIGHT, TEST_ROOT_TICK_HEIGHT+1UL, 2UL, start_poh+i ); } for( ulong i=0UL; i<2UL; i++ ) { @@ -183,11 +240,9 @@ run_interleaved_fec_residual_case( void ) { ulong txn_rem = txn_sz-txn_sz/2UL; if( !i ) { FD_TEST( txn_rem<=USHORT_MAX ); - FD_TEST( store_fec->data_sz>=txn_rem ); - FD_TEST( store_fec->data_sz-(uint)txn_rem<=USHORT_MAX ); + FD_TEST( store_fec->data_sz==txn_rem ); store_fec->shred_sz[ 0 ] = (ushort)(txn_rem/2UL); store_fec->shred_sz[ 1 ] = (ushort)(txn_rem-txn_rem/2UL); - store_fec->shred_sz[ 2 ] = (ushort)(store_fec->data_sz-(uint)txn_rem); } else { FD_TEST( store_fec->data_sz<=USHORT_MAX ); store_fec->shred_sz[ 0 ] = (ushort)store_fec->data_sz; @@ -199,14 +254,20 @@ run_interleaved_fec_residual_case( void ) { .parent_slot = TEST_ROOT_SLOT, .fec = store_fec, .data = encoded[ i ]+split_off[ i ], - .shred_cnt = (uint)(i ? 1UL : 3UL), + .shred_cnt = (uint)(i ? 1UL : 2UL), .is_last_in_batch = 1U, - .is_last_in_block = 1U, }}; FD_TEST( fd_sched_fec_can_ingest( sched, fec ) ); FD_TEST( fd_sched_fec_ingest( sched, fec ) ); } + for( ulong i=0UL; i<2UL; i++ ) { + fd_hash_t prev_hash[ 1 ]; + hash_from_seed( prev_hash, 0x9e33470a182d6cbfUL+i ); + FD_TEST( ingest_ag_component( sched, 2UL+i, AG_COMP_FOOTER, NULL, 0, 0 ) ); + FD_TEST( ingest_ag_component( sched, 2UL+i, AG_COMP_TICK, prev_hash, 0, 1 ) ); + } + ulong txn_exec_cnt = 0UL; for( ulong step=0UL; step<100UL; step++ ) { while( fd_sched_pruned_block_next( sched )!=ULONG_MAX ) {} @@ -225,7 +286,7 @@ run_interleaved_fec_residual_case( void ) { FD_TEST( task->txn_exec->bank_idx==2UL || task->txn_exec->bank_idx==3UL ); FD_TEST( FD_LOAD( uint, txn->payload+1UL )==signature_tag[ task->txn_exec->bank_idx-2UL ] ); FD_TEST( txn->start_shred_idx==0U ); - FD_TEST( txn->end_shred_idx==fd_ushort_if( task->txn_exec->bank_idx==2UL, 2U, 1U ) ); + FD_TEST( txn->end_shred_idx==fd_ushort_if( task->txn_exec->bank_idx==2UL, 3U, 2U ) ); txn_exec_cnt++; FD_TEST( !fd_sched_task_done( sched, FD_SCHED_TT_TXN_EXEC, task->txn_exec->txn_idx, task->txn_exec->exec_idx, NULL ) ); break; @@ -255,167 +316,6 @@ run_interleaved_fec_residual_case( void ) { free( mem ); } -static void -run_bad_tick_case( fd_hash_t const * start_poh, - ulong const * tick_hashcnt, - ulong tick_cnt, - ulong max_tick_height, - ulong hashes_per_tick, - int alpenglow, - int expect_mark_dead, - int expect_poh_fail, - int expect_dead_reason ) { - /* This test only needs the root, the parent, the child under test, and - one spare slot. */ - ulong depth = fd_ulong_max( FD_SCHED_MIN_DEPTH, 512UL ); - ulong block_cnt_max = 4UL; - ulong footprint = fd_sched_footprint( depth, block_cnt_max, FD_SHRED_BLK_MAX, FD_MAX_TXN_PER_SLOT ); - void * mem = aligned_alloc( fd_sched_align(), footprint ); - FD_TEST( mem ); - - fd_rng_t rng[1]; fd_rng_join( fd_rng_new( rng, 0U, 0UL ) ); - fd_sched_t * sched = fd_sched_join( fd_sched_new( mem, rng, depth, block_cnt_max, FD_SHRED_BLK_MAX, FD_MAX_TXN_PER_SLOT, TEST_EXEC_CNT, alpenglow ) ); - FD_TEST( sched ); - - fd_sched_block_add_done( sched, 1UL, ULONG_MAX, TEST_ROOT_SLOT ); - - uchar encoded[ sizeof(ulong) + 4UL*sizeof(fd_microblock_hdr_t) ] = {0}; - ulong encoded_sz = 0UL; - encode_tick_block( encoded, &encoded_sz, start_poh, tick_hashcnt, tick_cnt ); - - fd_store_fec_t store_fec[ 1 ] __attribute__((aligned(alignof(fd_store_fec_t)))); - fd_memset( store_fec, 0, sizeof(fd_store_fec_t) ); - FD_TEST( encoded_sz<=USHORT_MAX ); - store_fec->data_sz = (uint)encoded_sz; - store_fec->shred_sz[ 0 ] = (ushort)encoded_sz; - - fd_sched_fec_t fec[ 1 ] = {{ - .bank_idx = 2UL, - .parent_bank_idx = 1UL, - .slot = TEST_ROOT_SLOT + 1UL, - .parent_slot = TEST_ROOT_SLOT, - .fec = store_fec, - .data = encoded, - .shred_cnt = 1U, - .is_last_in_batch = 1U, - .is_last_in_block = 1U, - .is_first_in_block = 1U - }}; - FD_TEST( fd_sched_fec_can_ingest( sched, fec ) ); - FD_TEST( fd_sched_fec_ingest( sched, fec ) ); - fd_sched_set_poh_params( sched, 2UL, TEST_ROOT_TICK_HEIGHT, max_tick_height, hashes_per_tick, start_poh ); - - fd_sched_task_t task[ 1 ]; - while( fd_sched_pruned_block_next( sched )!=ULONG_MAX ) {} - FD_TEST( 1UL==fd_sched_task_next_ready( sched, task ) ); - FD_TEST( task->task_type==FD_SCHED_TT_BLOCK_START ); - FD_TEST( task->block_start->bank_idx==2UL ); - FD_TEST( 0==fd_sched_task_done( sched, FD_SCHED_TT_BLOCK_START, ULONG_MAX, ULONG_MAX, NULL ) ); - - int seen_mark_dead = 0; - int seen_poh_fail = 0; - for(;;) { - while( fd_sched_pruned_block_next( sched )!=ULONG_MAX ) {} - if( FD_UNLIKELY( !fd_sched_task_next_ready( sched, task ) ) ) break; - switch( task->task_type ) { - case FD_SCHED_TT_MARK_DEAD: - FD_TEST( task->mark_dead->bank_idx==2UL ); - seen_mark_dead = 1; - break; - case FD_SCHED_TT_BLOCK_END: /* only reached when the block is valid */ - FD_TEST( 0==fd_sched_task_done( sched, FD_SCHED_TT_BLOCK_END, ULONG_MAX, ULONG_MAX, NULL ) ); - break; - case FD_SCHED_TT_POH_HASH: { - fd_execrp_poh_hash_done_msg_t msg[ 1 ]; - msg->cnt = task->poh_hash->cnt; - for( ulong i=0UL; ipoh_hash->cnt; i++ ) { - repeat_hash( msg->hash+i, task->poh_hash->hash+i, task->poh_hash->hashcnt ); - } - int rc = fd_sched_task_done( sched, FD_SCHED_TT_POH_HASH, ULONG_MAX, task->poh_hash->exec_idx, msg ); - if( FD_UNLIKELY( rc!=FD_SCHED_DEAD_REASON_NONE ) ) seen_poh_fail = 1; - break; - } - default: - FD_LOG_ERR(( "unexpected task_type %lu in bad tick case", task->task_type )); - } - } - - FD_TEST( seen_mark_dead==expect_mark_dead ); - FD_TEST( seen_poh_fail ==expect_poh_fail ); - FD_TEST( fd_sched_get_dead_reason( sched, 2UL )==expect_dead_reason ); - FD_TEST( fd_sched_is_drained( sched ) ); - while( fd_sched_pruned_block_next( sched )!=ULONG_MAX ) {} - - fd_sched_delete( fd_sched_leave( sched ) ); - free( mem ); -} - - - -/* Alpenglow block structure cases. Each component is its own FEC set - (one batch per FEC set, as sched assumes). Markers are serialized - with fd_block_marker_ser, which emits the whole zero-microblock - batch; a tick is a one-microblock batch chained from the running - PoH. */ - -#define AG_COMP_HEADER (0) -#define AG_COMP_FOOTER (1) -#define AG_COMP_UPDATE_PARENT (2) -#define AG_COMP_TICK (3) /* alpentick: one hash */ -#define AG_COMP_TICK_0HASH (4) /* tick declaring zero hashes: invalid under Alpenglow */ -#define AG_COMP_TICK_2HASH (5) /* tick declaring two hashes: invalid under Alpenglow */ - -/* AG_SPLIT( comp, cut ) is comp delivered as two FEC sets of the same - batch, the first holding the leading cut bytes. */ -#define AG_SPLIT( comp, cut ) ((comp) | (int)((cut)<<8)) - -static ulong -encode_ag_component( uchar * out, - int comp, - fd_hash_t * prev_hash ) { - fd_block_marker_t marker[1]; - fd_memset( marker, 0, sizeof(fd_block_marker_t) ); - switch( comp ) { - case AG_COMP_HEADER: - marker->kind = FD_BLOCK_MARKER_KIND_HEADER; - marker->header.parent_slot = TEST_ROOT_SLOT; - return fd_block_marker_ser( marker, out ); - case AG_COMP_FOOTER: - marker->kind = FD_BLOCK_MARKER_KIND_FOOTER; - return fd_block_marker_ser( marker, out ); - case AG_COMP_UPDATE_PARENT: { - /* fd_block_marker_ser only produces headers and footers (nothing - Firedancer emits is an UpdateParent), so lay the wire format out - by hand: preamble (entry_cnt=0, version=1, tag, length) then a - V1 payload (version=1, new_parent_slot, new_parent_block_id). */ - ulong off = 0UL; - FD_STORE( ulong, out+off, 0UL ); off += sizeof(ulong); - FD_STORE( ushort, out+off, (ushort)1 ); off += sizeof(ushort); - out[ off ] = (uchar)FD_BLOCK_MARKER_KIND_UPDATE_PARENT; off += sizeof(uchar); - FD_STORE( ushort, out+off, (ushort)FD_UPDATE_PARENT_SER_SZ ); off += sizeof(ushort); - out[ off ] = (uchar)1; off += sizeof(uchar); - FD_STORE( ulong, out+off, TEST_ROOT_SLOT ); off += sizeof(ulong); - fd_memset( out+off, 0, sizeof(fd_hash_t) ); off += sizeof(fd_hash_t); - return off; - } - case AG_COMP_TICK: - case AG_COMP_TICK_0HASH: - case AG_COMP_TICK_2HASH: { - ulong hash_cnt = comp==AG_COMP_TICK ? 1UL : comp==AG_COMP_TICK_0HASH ? 0UL : 2UL; - FD_STORE( ulong, out, 1UL ); - fd_hash_t end_hash[ 1 ]; - repeat_hash( end_hash, prev_hash, hash_cnt ); - fd_microblock_hdr_t hdr = { .hash_cnt = hash_cnt, .txn_cnt = 0UL }; - fd_memcpy( hdr.hash, end_hash->hash, sizeof(fd_hash_t) ); - fd_memcpy( out+sizeof(ulong), &hdr, sizeof(fd_microblock_hdr_t) ); - fd_memcpy( prev_hash, end_hash, sizeof(fd_hash_t) ); - return sizeof(ulong)+sizeof(fd_microblock_hdr_t); - } - default: - FD_LOG_ERR(( "bad component %d", comp )); - } -} - /* Feeds the components as consecutive FEC sets. If expect_ingest_ok is 0, some FEC ingest must fail and the block must carry expect_dead_reason. Otherwise every ingest succeeds and the block is @@ -434,7 +334,7 @@ run_ag_structure_case( fd_hash_t const * start_poh, FD_TEST( mem ); fd_rng_t rng[1]; fd_rng_join( fd_rng_new( rng, 0U, 0UL ) ); - fd_sched_t * sched = fd_sched_join( fd_sched_new( mem, rng, depth, block_cnt_max, FD_SHRED_BLK_MAX, FD_MAX_TXN_PER_SLOT, TEST_EXEC_CNT, 1 /* alpenglow */ ) ); + fd_sched_t * sched = fd_sched_join( fd_sched_new( mem, rng, depth, block_cnt_max, FD_SHRED_BLK_MAX, FD_MAX_TXN_PER_SLOT, TEST_EXEC_CNT ) ); FD_TEST( sched ); fd_sched_block_add_done( sched, 1UL, ULONG_MAX, TEST_ROOT_SLOT ); @@ -575,11 +475,10 @@ run_ag_structure_cases( void ) { run_ag_structure_case( start_poh, c, 4UL, 0, FD_SCHED_DEAD_REASON_SPURIOUS_UPDATE_PARENT ); } } -/* Regression: the per-tick hash bound at ingest (TICK_HASHES_OVERFLOW_ - INGEST) must apply to vanilla blocks but not to Alpenglow blocks. - agave's verify_ticks returns early for Alpenglow, and every - Alpenglow entry is pinned to exactly one hash, so the cumulative - hash count between ticks is just the entry count and grows past +/* Regression: there is no per-tick hash bound at ingest. agave's + verify_ticks returns early for Alpenglow, and every Alpenglow entry + is pinned to exactly one hash, so the cumulative hash count between + ticks is just the entry count and grows past FD_RUNTIME_MAX_HASHES_PER_TICK in a valid block. Ingests a batch of entry_cnt single-transaction microblocks, each @@ -592,8 +491,7 @@ run_ag_structure_cases( void ) { #define MANY_ENTRIES_MAX_TXN (2UL*FD_MAX_TXN_PER_SLOT) static void -run_many_entries_case( int alpenglow, - ulong entry_cnt, +run_many_entries_case( ulong entry_cnt, ulong bad_idx, ulong bad_hash_cnt, int expect_ingest_ok, @@ -607,7 +505,7 @@ run_many_entries_case( int alpenglow, FD_TEST( mem ); fd_rng_t rng[1]; fd_rng_join( fd_rng_new( rng, 0U, 0UL ) ); - fd_sched_t * sched = fd_sched_join( fd_sched_new( mem, rng, depth, block_cnt_max, MANY_ENTRIES_MAX_SHREDS, MANY_ENTRIES_MAX_TXN, TEST_EXEC_CNT, alpenglow ) ); + fd_sched_t * sched = fd_sched_join( fd_sched_new( mem, rng, depth, block_cnt_max, MANY_ENTRIES_MAX_SHREDS, MANY_ENTRIES_MAX_TXN, TEST_EXEC_CNT ) ); FD_TEST( sched ); fd_sched_set_bypass_poh_verify( sched, 1 ); fd_sched_block_add_done( sched, 1UL, ULONG_MAX, TEST_ROOT_SLOT ); @@ -627,29 +525,14 @@ run_many_entries_case( int alpenglow, } FD_TEST( cursor==stream_sz ); - static uchar marker_buf[ FD_BLOCK_MARKER_SER_MAX ] __attribute__((aligned(64))); static fd_store_fec_t store_fec[ 1 ] __attribute__((aligned(alignof(fd_store_fec_t)))); fd_hash_t prev_hash[ 1 ]; hash_from_seed( prev_hash, 0x5eedUL ); int ingest_ok = 1; - uint first = 1U; ulong off = 0UL; - if( alpenglow ) { - ulong sz = encode_ag_component( marker_buf, AG_COMP_HEADER, prev_hash ); - fd_memset( store_fec, 0, sizeof(fd_store_fec_t) ); - store_fec->data_sz = (uint)sz; - store_fec->shred_sz[0] = (ushort)sz; - fd_sched_fec_t fec[ 1 ] = {{ - .bank_idx = 2UL, .parent_bank_idx = 1UL, - .slot = TEST_ROOT_SLOT+1UL, .parent_slot = TEST_ROOT_SLOT, - .fec = store_fec, .data = marker_buf, .shred_cnt = 1U, - .is_last_in_batch = 1U, .is_first_in_block = 1U, - }}; - FD_TEST( fd_sched_fec_can_ingest( sched, fec ) ); - FD_TEST( fd_sched_fec_ingest( sched, fec ) ); - first = 0U; - } + FD_TEST( ingest_ag_component( sched, 2UL, AG_COMP_HEADER, NULL, 1, 0 ) ); + fd_sched_set_poh_params( sched, 2UL, TEST_ROOT_TICK_HEIGHT, TEST_ROOT_TICK_HEIGHT+1UL, 1UL, prev_hash ); /* Feed the batch in max sized FEC sets so residual handling across FEC boundaries gets exercised too. */ @@ -663,18 +546,13 @@ run_many_entries_case( int alpenglow, .slot = TEST_ROOT_SLOT+1UL, .parent_slot = TEST_ROOT_SLOT, .fec = store_fec, .data = stream+off, .shred_cnt = 1U, .is_last_in_batch = off+sz==stream_sz, - .is_first_in_block = !!first, }}; FD_TEST( fd_sched_fec_can_ingest( sched, fec ) ); int ok = !!fd_sched_fec_ingest( sched, fec ); - if( FD_LIKELY( first ) ) { - fd_sched_set_poh_params( sched, 2UL, TEST_ROOT_TICK_HEIGHT, TEST_ROOT_TICK_HEIGHT+1UL, alpenglow ? 1UL : 2UL, prev_hash ); - } - first = 0U; off += sz; if( FD_UNLIKELY( !ok ) ) { ingest_ok = 0; break; } } - if( ingest_ok!=expect_ingest_ok ) FD_LOG_ERR(( "ag %d cnt %lu bad_idx %lu: ingest_ok %d dead %d", alpenglow, entry_cnt, bad_idx, ingest_ok, fd_sched_get_dead_reason( sched, 2UL ) )); + if( ingest_ok!=expect_ingest_ok ) FD_LOG_ERR(( "cnt %lu bad_idx %lu: ingest_ok %d dead %d", entry_cnt, bad_idx, ingest_ok, fd_sched_get_dead_reason( sched, 2UL ) )); FD_TEST( fd_sched_get_dead_reason( sched, 2UL )==expect_dead_reason ); while( fd_sched_pruned_block_next( sched )!=ULONG_MAX ) {} @@ -689,186 +567,18 @@ run_many_entries_cases( void ) { FD_TEST( FD_SCHED_MAX_MBLK_PER_SLOT>lim+1000UL ); FD_TEST( MANY_ENTRIES_MAX_TXN>90000UL ); - /* Vanilla: exactly the limit is fine, one more is rejected at - ingest. This is the boundary the Alpenglow exemption must not - move. */ - run_many_entries_case( 0, lim, ULONG_MAX, 0UL, 1, FD_SCHED_DEAD_REASON_NONE ); - run_many_entries_case( 0, lim+1UL, ULONG_MAX, 0UL, 0, FD_SCHED_DEAD_REASON_TICK_HASHES_OVERFLOW_INGEST ); - - /* Alpenglow: the same entry counts are valid, well past the limit. */ - run_many_entries_case( 1, lim, ULONG_MAX, 0UL, 1, FD_SCHED_DEAD_REASON_NONE ); - run_many_entries_case( 1, lim+1UL, ULONG_MAX, 0UL, 1, FD_SCHED_DEAD_REASON_NONE ); - run_many_entries_case( 1, 90000UL, ULONG_MAX, 0UL, 1, FD_SCHED_DEAD_REASON_NONE ); - - /* Alpenglow still pins every entry to one hash, deep into a long - batch: this is what bounds PoH work now that the cumulative check - is gone. Neither a huge nor a zero hash count gets through. */ - run_many_entries_case( 1, lim+100UL, lim+50UL, 2UL, 0, FD_SCHED_DEAD_REASON_ALPENGLOW_HASH_CNT ); - run_many_entries_case( 1, lim+100UL, lim+50UL, ULONG_MAX, 0, FD_SCHED_DEAD_REASON_ALPENGLOW_HASH_CNT ); - run_many_entries_case( 1, lim+100UL, lim+50UL, 0UL, 0, FD_SCHED_DEAD_REASON_ALPENGLOW_HASH_CNT ); - run_many_entries_case( 1, 10UL, 0UL, lim+1UL, 0, FD_SCHED_DEAD_REASON_ALPENGLOW_HASH_CNT ); -} - -static void -run_bad_tick_cases( void ) { - fd_hash_t start_poh[ 1 ]; - hash_from_seed( start_poh, 0x4d85f12e7a9b3105UL ); - - { - ulong tick_hashcnt[ 1 ] = { 1UL }; - run_bad_tick_case( start_poh, tick_hashcnt, 1UL, TEST_ROOT_TICK_HEIGHT + 2UL, 1UL, 0, 1, 0, FD_SCHED_DEAD_REASON_TOO_FEW_TICKS ); - } - - { - ulong tick_hashcnt[ 2 ] = { 1UL, 1UL }; - run_bad_tick_case( start_poh, tick_hashcnt, 2UL, TEST_ROOT_TICK_HEIGHT + 1UL, 1UL, 0, 0, 1, FD_SCHED_DEAD_REASON_TOO_MANY_TICKS ); - } - - { - ulong tick_hashcnt[ 2 ] = { 1UL, 2UL }; - run_bad_tick_case( start_poh, tick_hashcnt, 2UL, TEST_ROOT_TICK_HEIGHT + 2UL, 2UL, 0, 0, 1, FD_SCHED_DEAD_REASON_WRONG_HASHES_PER_TICK ); - } -} - -static void -run_poh_spread_case( ulong tick_cnt, - ulong hashes_per_tick ) { - ulong depth = fd_ulong_max( FD_SCHED_MIN_DEPTH, 512UL ); - ulong block_cnt_max = 4UL; - ulong footprint = fd_sched_footprint( depth, block_cnt_max, FD_SHRED_BLK_MAX, FD_MAX_TXN_PER_SLOT ); - void * mem = aligned_alloc( fd_sched_align(), footprint ); - FD_TEST( mem ); - - fd_rng_t rng[1]; fd_rng_join( fd_rng_new( rng, 0U, 0UL ) ); - fd_sched_t * sched = fd_sched_join( fd_sched_new( mem, rng, depth, block_cnt_max, FD_SHRED_BLK_MAX, FD_MAX_TXN_PER_SLOT, TEST_EXEC_CNT, 0 ) ); - FD_TEST( sched ); - - fd_sched_block_add_done( sched, 1UL, ULONG_MAX, TEST_ROOT_SLOT ); - - fd_hash_t start_poh[ 1 ]; - hash_from_seed( start_poh, 0x7c1e5d3a9b204f6dUL ); - ulong tick_hashcnt[ 8 ]; - FD_TEST( tick_cnt<=8UL ); - for( ulong i=0UL; idata_sz = (uint)encoded_sz; - store_fec->shred_sz[ 0 ] = (ushort)encoded_sz; - - fd_sched_fec_t fec[ 1 ] = {{ - .bank_idx = 2UL, - .parent_bank_idx = 1UL, - .slot = TEST_ROOT_SLOT + 1UL, - .parent_slot = TEST_ROOT_SLOT, - .fec = store_fec, - .data = encoded, - .shred_cnt = 1U, - .is_last_in_batch = 1U, - .is_last_in_block = 1U, - .is_first_in_block = 1U - }}; - FD_TEST( fd_sched_fec_can_ingest( sched, fec ) ); - FD_TEST( fd_sched_fec_ingest( sched, fec ) ); - fd_sched_set_poh_params( sched, 2UL, TEST_ROOT_TICK_HEIGHT, TEST_ROOT_TICK_HEIGHT+tick_cnt, hashes_per_tick, start_poh ); - - fd_sched_task_t task[ 1 ]; - while( fd_sched_pruned_block_next( sched )!=ULONG_MAX ) {} - FD_TEST( 1UL==fd_sched_task_next_ready( sched, task ) ); - FD_TEST( task->task_type==FD_SCHED_TT_BLOCK_START ); - FD_TEST( 0==fd_sched_task_done( sched, FD_SCHED_TT_BLOCK_START, ULONG_MAX, ULONG_MAX, NULL ) ); - - /* All exec tiles are idle and the block is fully ingested, so no - tile is held back for transaction execution. */ - ulong min_cnt = fd_sha256_simd_lane_min(); - ulong lane_cnt = fd_sha256_simd_lane_max(); - ulong expect_cnt = fd_ulong_min( (tick_cnt+TEST_EXEC_CNT-1UL)/TEST_EXEC_CNT, lane_cnt ); - if( expect_cntTEST_EXEC_CNT*expect_cnt; - - ulong round_cnt = 0UL; - int saw_end = 0; - for(;;) { - while( fd_sched_pruned_block_next( sched )!=ULONG_MAX ) {} - - /* Drain every dispatchable PoH task for this round without - completing any, so the whole batch of ticks is in flight at - once. */ - fd_sched_task_t in_flight[ TEST_EXEC_CNT ]; - ulong in_flight_cnt = 0UL; - ulong tile_mask = 0UL; - ulong mblk_cnt = 0UL; - while( fd_sched_task_next_ready( sched, task ) ) { - if( task->task_type==FD_SCHED_TT_BLOCK_END ) { - FD_TEST( !in_flight_cnt ); - FD_TEST( 0==fd_sched_task_done( sched, FD_SCHED_TT_BLOCK_END, ULONG_MAX, ULONG_MAX, NULL ) ); - saw_end = 1; - break; - } - FD_TEST( task->task_type==FD_SCHED_TT_POH_HASH ); - FD_TEST( in_flight_cntpoh_hash->cnt<=expect_cnt ); - FD_TEST( task->poh_hash->exec_idxpoh_hash->exec_idx ) ); - tile_mask = fd_ulong_set_bit( tile_mask, (int)task->poh_hash->exec_idx ); - mblk_cnt += task->poh_hash->cnt; - in_flight[ in_flight_cnt++ ] = *task; - } - if( saw_end ) break; - - if( !overflow ) { - FD_TEST( in_flight_cnt==expect_tasks ); - FD_TEST( mblk_cnt==tick_cnt ); - } else { - FD_TEST( in_flight_cnt>=1UL ); - FD_TEST( mblk_cnt<=TEST_EXEC_CNT*expect_cnt ); - } - round_cnt++; - - for( ulong t=0UL; tcnt = ph->cnt; - for( ulong i=0UL; icnt; i++ ) { - repeat_hash( msg->hash+i, ph->hash+i, ph->hashcnt ); - } - FD_TEST( 0==fd_sched_task_done( sched, FD_SCHED_TT_POH_HASH, ULONG_MAX, ph->exec_idx, msg ) ); - } - } - - /* Each tick needs more than one maximally sized task, so the spread - must have survived at least one re-dispatch of partially hashed - ticks. */ - FD_TEST( round_cnt>=2UL ); - FD_TEST( fd_sched_get_dead_reason( sched, 2UL )==FD_SCHED_DEAD_REASON_NONE ); - FD_TEST( fd_sched_is_drained( sched ) ); - while( fd_sched_pruned_block_next( sched )!=ULONG_MAX ) {} - - fd_sched_delete( fd_sched_leave( sched ) ); - free( mem ); -} - -static void -run_poh_spread_cases( void ) { - /* Fewer ticks than tiles: exactly one tick per task, one task per - tile. */ - run_poh_spread_case( 1UL, 9000UL ); - run_poh_spread_case( 3UL, 9000UL ); - run_poh_spread_case( TEST_EXEC_CNT, 9000UL ); - /* More ticks than tiles: at most ceil( tick_cnt/tile_cnt ) per task, - clamped to the SIMD lane width (so exactly one per task on builds - with a serial fallback), still using every tile. */ - run_poh_spread_case( 6UL, 9000UL ); - run_poh_spread_case( 8UL, 9000UL ); + /* Entry counts well past the limit are valid. */ + run_many_entries_case( lim, ULONG_MAX, 0UL, 1, FD_SCHED_DEAD_REASON_NONE ); + run_many_entries_case( lim+1UL, ULONG_MAX, 0UL, 1, FD_SCHED_DEAD_REASON_NONE ); + run_many_entries_case( 90000UL, ULONG_MAX, 0UL, 1, FD_SCHED_DEAD_REASON_NONE ); + + /* Every entry is pinned to one hash, deep into a long batch: this is + what bounds PoH work. Neither a huge nor a zero hash count gets + through. */ + run_many_entries_case( lim+100UL, lim+50UL, 2UL, 0, FD_SCHED_DEAD_REASON_ALPENGLOW_HASH_CNT ); + run_many_entries_case( lim+100UL, lim+50UL, ULONG_MAX, 0, FD_SCHED_DEAD_REASON_ALPENGLOW_HASH_CNT ); + run_many_entries_case( lim+100UL, lim+50UL, 0UL, 0, FD_SCHED_DEAD_REASON_ALPENGLOW_HASH_CNT ); + run_many_entries_case( 10UL, 0UL, lim+1UL, 0, FD_SCHED_DEAD_REASON_ALPENGLOW_HASH_CNT ); } static void @@ -881,7 +591,7 @@ run_lane_policy_case( void ) { FD_TEST( mem ); fd_rng_t rng[1]; fd_rng_join( fd_rng_new( rng, 0U, 0UL ) ); - fd_sched_t * sched = fd_sched_join( fd_sched_new( mem, rng, depth, block_cnt_max, FD_SHRED_BLK_MAX, FD_MAX_TXN_PER_SLOT, TEST_EXEC_CNT, 0 ) ); + fd_sched_t * sched = fd_sched_join( fd_sched_new( mem, rng, depth, block_cnt_max, FD_SHRED_BLK_MAX, FD_MAX_TXN_PER_SLOT, TEST_EXEC_CNT ) ); FD_TEST( sched ); fd_sched_block_add_done( sched, 1UL, ULONG_MAX, TEST_ROOT_SLOT ); @@ -1007,7 +717,7 @@ new_sched( fd_rng_t * rng, void ** mem_out, ulong block_cnt_max ) { ulong footprint = fd_sched_footprint( depth, block_cnt_max, FD_SHRED_BLK_MAX, FD_MAX_TXN_PER_SLOT ); void * mem = aligned_alloc( fd_sched_align(), footprint ); FD_TEST( mem ); - fd_sched_t * sched = fd_sched_join( fd_sched_new( mem, rng, depth, block_cnt_max, FD_SHRED_BLK_MAX, FD_MAX_TXN_PER_SLOT, TEST_EXEC_CNT, 0 ) ); + fd_sched_t * sched = fd_sched_join( fd_sched_new( mem, rng, depth, block_cnt_max, FD_SHRED_BLK_MAX, FD_MAX_TXN_PER_SLOT, TEST_EXEC_CNT ) ); FD_TEST( sched ); *mem_out = mem; return sched; @@ -1025,28 +735,12 @@ run_cert_verify_case( int abandon ) { fd_hash_t start_poh[ 1 ]; hash_from_seed( start_poh, 0x3c5a7e9b1d2f4086UL ); - ulong tick_hashcnt[ 1 ] = { 4UL }; - uchar encoded[ sizeof(ulong)+sizeof(fd_microblock_hdr_t) ]; - ulong encoded_sz = 0UL; - encode_tick_block( encoded, &encoded_sz, start_poh, tick_hashcnt, 1UL ); - fd_store_fec_t store_fec[ 1 ] __attribute__((aligned(alignof(fd_store_fec_t)))); - fd_memset( store_fec, 0, sizeof(fd_store_fec_t) ); - store_fec->data_sz = (uint)encoded_sz; - store_fec->shred_sz[ 0 ] = (ushort)encoded_sz; - fd_sched_fec_t fec[ 1 ] = {{ - .bank_idx = 2UL, - .parent_bank_idx = 1UL, - .slot = TEST_ROOT_SLOT+1UL, - .parent_slot = TEST_ROOT_SLOT, - .fec = store_fec, - .data = encoded, - .shred_cnt = 1U, - .is_last_in_batch = 1U, - .is_last_in_block = 1U, - .is_first_in_block = 1U - }}; - FD_TEST( fd_sched_fec_ingest( sched, fec ) ); - fd_sched_set_poh_params( sched, 2UL, TEST_ROOT_TICK_HEIGHT, TEST_ROOT_TICK_HEIGHT+1UL, tick_hashcnt[ 0 ], start_poh ); + fd_hash_t prev_hash[ 1 ]; + fd_memcpy( prev_hash, start_poh, sizeof(fd_hash_t) ); + FD_TEST( ingest_ag_component( sched, 2UL, AG_COMP_HEADER, NULL, 1, 0 ) ); + fd_sched_set_poh_params( sched, 2UL, TEST_ROOT_TICK_HEIGHT, TEST_ROOT_TICK_HEIGHT+1UL, 1UL, start_poh ); + FD_TEST( ingest_ag_component( sched, 2UL, AG_COMP_FOOTER, NULL, 0, 0 ) ); + FD_TEST( ingest_ag_component( sched, 2UL, AG_COMP_TICK, prev_hash, 0, 1 ) ); fd_sched_task_t task[ 1 ]; for(;;) { @@ -1253,7 +947,7 @@ run_zero_hashcnt_mblk_case( void ) { FD_TEST( mem ); fd_rng_t rng[ 1 ]; fd_rng_join( fd_rng_new( rng, 0U, 0UL ) ); - fd_sched_t * sched = fd_sched_join( fd_sched_new( mem, rng, FD_SCHED_MIN_DEPTH, 4UL, FD_SHRED_BLK_MAX, FD_MAX_TXN_PER_SLOT, TEST_EXEC_CNT, 0 ) ); + fd_sched_t * sched = fd_sched_join( fd_sched_new( mem, rng, FD_SCHED_MIN_DEPTH, 4UL, FD_SHRED_BLK_MAX, FD_MAX_TXN_PER_SLOT, TEST_EXEC_CNT ) ); FD_TEST( sched ); fd_sched_set_bypass_poh_verify( sched, 1 ); fd_sched_block_add_done( sched, 1UL, ULONG_MAX, TEST_ROOT_SLOT ); @@ -1266,6 +960,9 @@ run_zero_hashcnt_mblk_case( void ) { hash_from_seed( start_poh, 0x6d1f4c9a3b57e802UL ); hash_from_seed( mblk_hash, 0xc40a97e5182b6d3fUL ); + FD_TEST( ingest_ag_component( sched, 2UL, AG_COMP_HEADER, NULL, 1, 0 ) ); + fd_sched_set_poh_params( sched, 2UL, TEST_ROOT_TICK_HEIGHT, TEST_ROOT_TICK_HEIGHT+1UL, 1UL, start_poh ); + /* First FEC: a complete single-transaction microblock. The block declares more microblocks than this, so it stays incomplete. */ uchar fec0[ 4096 ]; @@ -1291,11 +988,9 @@ run_zero_hashcnt_mblk_case( void ) { .fec = store_fec0, .data = fec0, .shred_cnt = 1U, - .is_first_in_block = 1U, }}; FD_TEST( fd_sched_fec_can_ingest( sched, fec ) ); FD_TEST( fd_sched_fec_ingest( sched, fec ) ); - fd_sched_set_poh_params( sched, 2UL, TEST_ROOT_TICK_HEIGHT, TEST_ROOT_TICK_HEIGHT+4UL, 64UL, start_poh ); /* Drain everything the first FEC made available. */ ulong exec_cnt = 0UL; @@ -1346,9 +1041,8 @@ run_zero_hashcnt_mblk_case( void ) { fd_memset( store_fec1, 0, sizeof(fd_store_fec_t) ); store_fec1->data_sz = (uint)fec1_sz; store_fec1->shred_sz[ 0 ] = (ushort)fec1_sz; - fec->fec = store_fec1; - fec->data = fec1; - fec->is_first_in_block = 0U; + fec->fec = store_fec1; + fec->data = fec1; FD_TEST( fd_sched_fec_can_ingest( sched, fec ) ); FD_TEST( fd_sched_fec_ingest( sched, fec ) ); @@ -1405,7 +1099,7 @@ run_runtime_limit_case( void ) { ulong footprint = fd_sched_footprint( depth, block_cnt_max, 3UL, FD_MAX_TXN_PER_SLOT ); void * mem = aligned_alloc( fd_sched_align(), footprint ); FD_TEST( mem ); - fd_sched_t * sched = fd_sched_join( fd_sched_new( mem, rng, depth, block_cnt_max, 3UL, FD_MAX_TXN_PER_SLOT, TEST_EXEC_CNT, 0 ) ); + fd_sched_t * sched = fd_sched_join( fd_sched_new( mem, rng, depth, block_cnt_max, 3UL, FD_MAX_TXN_PER_SLOT, TEST_EXEC_CNT ) ); FD_TEST( sched ); fd_sched_block_add_done( sched, 1UL, ULONG_MAX, TEST_ROOT_SLOT ); @@ -1444,9 +1138,10 @@ run_runtime_limit_case( void ) { ulong footprint = fd_sched_footprint( depth, block_cnt_max, FD_SHRED_BLK_MAX, 1UL ); void * mem = aligned_alloc( fd_sched_align(), footprint ); FD_TEST( mem ); - fd_sched_t * sched = fd_sched_join( fd_sched_new( mem, rng, depth, block_cnt_max, FD_SHRED_BLK_MAX, 1UL, TEST_EXEC_CNT, 0 ) ); + fd_sched_t * sched = fd_sched_join( fd_sched_new( mem, rng, depth, block_cnt_max, FD_SHRED_BLK_MAX, 1UL, TEST_EXEC_CNT ) ); FD_TEST( sched ); fd_sched_block_add_done( sched, 1UL, ULONG_MAX, TEST_ROOT_SLOT ); + FD_TEST( ingest_ag_component( sched, 2UL, AG_COMP_HEADER, NULL, 1, 0 ) ); uchar encoded[ sizeof(ulong)+sizeof(fd_microblock_hdr_t) ] = {0}; FD_STORE( ulong, encoded, 1UL ); @@ -1466,7 +1161,6 @@ run_runtime_limit_case( void ) { .fec = store_fec, .data = encoded, .shred_cnt = 1U, - .is_first_in_block = 1U }}; FD_TEST( fd_sched_fec_can_ingest( sched, fec ) ); FD_TEST( !fd_sched_fec_ingest( sched, fec ) ); @@ -1483,9 +1177,10 @@ run_runtime_limit_case( void ) { ulong footprint = fd_sched_footprint( depth, block_cnt_max, 2UL*FD_SHRED_BLK_MAX, FD_MAX_TXN_PER_SLOT ); void * mem = aligned_alloc( fd_sched_align(), footprint ); FD_TEST( mem ); - fd_sched_t * sched = fd_sched_join( fd_sched_new( mem, rng, depth, block_cnt_max, 2UL*FD_SHRED_BLK_MAX, FD_MAX_TXN_PER_SLOT, TEST_EXEC_CNT, 0 ) ); + fd_sched_t * sched = fd_sched_join( fd_sched_new( mem, rng, depth, block_cnt_max, 2UL*FD_SHRED_BLK_MAX, FD_MAX_TXN_PER_SLOT, TEST_EXEC_CNT ) ); FD_TEST( sched ); fd_sched_block_add_done( sched, 1UL, ULONG_MAX, TEST_ROOT_SLOT ); + FD_TEST( ingest_ag_component( sched, 2UL, AG_COMP_HEADER, NULL, 1, 0 ) ); uchar encoded[ sizeof(ulong) ] = {0}; FD_STORE( ulong, encoded, mblk_cnt ); @@ -1503,7 +1198,6 @@ run_runtime_limit_case( void ) { .fec = store_fec, .data = encoded, .shred_cnt = 1U, - .is_first_in_block = 1U }}; FD_TEST( fd_sched_fec_can_ingest( sched, fec ) ); int too_many = mblk_cnt>2UL*FD_SCHED_MAX_MBLK_PER_SLOT; @@ -1522,10 +1216,8 @@ main( int argc, test_sched_footprint(); run_lane_policy_case(); - run_bad_tick_cases(); run_ag_structure_cases(); run_many_entries_cases(); - run_poh_spread_cases(); run_interleaved_fec_residual_case(); run_abandon_flavor_case(); run_root_notify_flavor_case(); diff --git a/src/discof/restore/fd_snapin_tile.c b/src/discof/restore/fd_snapin_tile.c index d9f970a2a43..3564444be52 100644 --- a/src/discof/restore/fd_snapin_tile.c +++ b/src/discof/restore/fd_snapin_tile.c @@ -282,7 +282,6 @@ typedef struct fd_snapin_shmem fd_snapin_shmem_t; struct fd_snapin_tile { int state; uint full : 1; /* loading a full snapshot? */ - int alpenglow; fd_snapin_lead_t lead; @@ -1210,7 +1209,7 @@ process_manifest( fd_snapin_tile_t * ctx, return; } - if( FD_UNLIKELY( ctx->alpenglow && !manifest->has_block_id ) ) { + if( FD_UNLIKELY( !manifest->has_block_id ) ) { FD_LOG_WARNING(( "snapshot manifest missing block id" )); transition_malformed( ctx, stem ); return; @@ -2345,7 +2344,6 @@ unprivileged_init( fd_topo_t const * topo, if( FD_UNLIKELY( ctx->tile_idx>=FD_TOPO_MAX_TILE_IN_LINKS ) ) FD_LOG_ERR(( "tile `" NAME "` has unsupported kind id %lu", tile->kind_id )); ctx->full = 1; - ctx->alpenglow = tile->snapin.alpenglow; ctx->state = FD_SNAPSHOT_STATE_IDLE; ctx->lane_cnt = tile->in_cnt; clear_control_barrier( ctx ); diff --git a/src/discof/rpc/fd_rpc_tile.c b/src/discof/rpc/fd_rpc_tile.c index 9e661b85653..64b045fd6a5 100644 --- a/src/discof/rpc/fd_rpc_tile.c +++ b/src/discof/rpc/fd_rpc_tile.c @@ -438,7 +438,6 @@ fd_rpc_mleaders_get_leader_for_slot( fd_multi_epoch_leaders_t const * mleaders, struct fd_rpc_tile { int delay_startup; - int alpenglow; int waker_rearm_pending; fd_http_server_t * http; @@ -754,8 +753,7 @@ before_frag( fd_rpc_tile_t * ctx, ulong seq FD_PARAM_UNUSED, ulong sig ) { if( FD_UNLIKELY( ctx->in_kind[ in_idx ]==IN_KIND_GOSSIP_OUT ) ) { - /* To match Agave, voteSubscribe shouldn't work under Alpenglow. */ - if( sig==FD_GOSSIP_UPDATE_TAG_VOTE ) return ctx->alpenglow || !ctx->ws_subscribers_vote_cnt; + /* To match Agave, gossip votes are not relayed to voteSubscribe. */ return sig!=FD_GOSSIP_UPDATE_TAG_CONTACT_INFO && sig!=FD_GOSSIP_UPDATE_TAG_CONTACT_INFO_REMOVE; } @@ -988,12 +986,10 @@ returnable_frag( fd_rpc_tile_t * ctx, FD_TEST( msg->bank_idxmax_live_slots ); ctx->finalized_idx = msg->bank_idx; - if( FD_UNLIKELY( ctx->alpenglow ) ) { - if( FD_LIKELY( ctx->confirmed_idx!=ULONG_MAX ) ) fd_stem_publish( stem, ctx->replay_out->idx, ctx->confirmed_idx, 0UL, 0UL, 0UL, 0UL, 0UL ); - ctx->confirmed_idx = msg->bank_idx; + if( FD_LIKELY( ctx->confirmed_idx!=ULONG_MAX ) ) fd_stem_publish( stem, ctx->replay_out->idx, ctx->confirmed_idx, 0UL, 0UL, 0UL, 0UL, 0UL ); + ctx->confirmed_idx = msg->bank_idx; - if( FD_LIKELY( ctx->cluster_confirmed_slot!=ULONG_MAX ) ) fd_rpc_cluster_finalized( ctx, msg->slot ); - } + if( FD_LIKELY( ctx->cluster_confirmed_slot!=ULONG_MAX ) ) fd_rpc_cluster_finalized( ctx, msg->slot ); break; } case REPLAY_SIG_DROP_BANK_REF: { @@ -3194,7 +3190,6 @@ unprivileged_init( fd_topo_t const * topo, void * _zstd_wksp = FD_SCRATCH_ALLOC_APPEND( l, 16UL, zstd_wksp_sz ); ctx->delay_startup = tile->rpc.delay_startup; - ctx->alpenglow = tile->rpc.alpenglow; fd_alpenglow_pda( "carlgration", ctx->ag_genesis_cert_addr ); ctx->waker_rearm_pending = 0; ctx->ws_subscribers_vote = _ws_sub_vote; diff --git a/src/discof/rpc/test_rpc_tile.c b/src/discof/rpc/test_rpc_tile.c index 14a95814f91..9002b2f3917 100644 --- a/src/discof/rpc/test_rpc_tile.c +++ b/src/discof/rpc/test_rpc_tile.c @@ -1253,18 +1253,9 @@ main( int argc, static char const ag_null[] = "{\"jsonrpc\":\"2.0\",\"result\":null,\"id\":1}"; static char const ag_bad [] = "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32603,\"message\":\"Firedancer Error: malformed genesis certificate\"},\"id\":1}"; - FD_TEST( !ctx->alpenglow ); FD_TEST( ctx->cluster_confirmed_slot==ULONG_MAX ); - /* Tower: getAgGenesisCert is served like Agave, null without the - account, and roots do not feed health (optimistic confirmation - does). */ - expect_rpc_response( ctx, ag_req, ag_null ); - DRIVE_ROOT_ADVANCED( 1UL, 10UL ); - FD_TEST( ctx->cluster_confirmed_slot==ULONG_MAX ); - - ctx->alpenglow = 1; /* Agave no longer relays gossip tower votes to voteSubscribe */ @@ -1353,7 +1344,6 @@ main( int argc, expect_rpc_response( ctx, ag_req, ag_bad ); /* Leave the fixture as the later cases expect it */ - ctx->alpenglow = 0; ctx->cluster_confirmed_slot = ULONG_MAX; ctx->processed_idx = 0UL; ctx->confirmed_idx = 0UL; diff --git a/src/flamenco/genesis/fd_genesis_create.c b/src/flamenco/genesis/fd_genesis_create.c index b38499ee4ca..3b4cbbcea5b 100644 --- a/src/flamenco/genesis/fd_genesis_create.c +++ b/src/flamenco/genesis/fd_genesis_create.c @@ -444,7 +444,7 @@ genesis_create( void * buf, /* Genesis certificate and epoch inflation account */ ulong alpenglow_idx = genesis->accounts_len; - if( options->alpenglow ) genesis->accounts_len += 2UL; + genesis->accounts_len += 2UL; ulong accounts_sz; REQUIRE( !__builtin_mul_overflow( genesis->accounts_len, sizeof(fd_genesis_account_pair_t), &accounts_sz ) ); @@ -517,37 +517,35 @@ genesis_create( void * buf, uchar genesis_cert_data [ FD_GENESIS_CERT_SZ ] = {0}; uchar epoch_inflation_data[ FD_EPOCH_INFLATION_ACCOUNT_SOME ]; - if( options->alpenglow ) { - /* WireBlockCertMessage { block: { slot: 0, block_id: 0 }, - signature: { signature: 0, bitmap: encode_base2( [] ) } } - bincode encoded. The bitmap is a Vec holding the base2 - version byte (0) and a u16 bit count (0). */ - FD_STORE( ulong, genesis_cert_data+232UL, 3UL ); - - fd_pubkey_t genesis_cert_addr; - fd_alpenglow_pda( "carlgration", &genesis_cert_addr ); - genesis->accounts[ alpenglow_idx ] = (fd_genesis_account_pair_t) { - .key = genesis_cert_addr, - .account = (fd_genesis_account_t) { - .lamports = fd_rent_exempt_minimum_balance( &genesis->rent, FD_GENESIS_CERT_SZ ), - .data_len = FD_GENESIS_CERT_SZ, - .data = genesis_cert_data, - .owner = fd_solana_system_program_id - } - }; + /* WireBlockCertMessage { block: { slot: 0, block_id: 0 }, + signature: { signature: 0, bitmap: encode_base2( [] ) } } + bincode encoded. The bitmap is a Vec holding the base2 + version byte (0) and a u16 bit count (0). */ + FD_STORE( ulong, genesis_cert_data+232UL, 3UL ); + + fd_pubkey_t genesis_cert_addr; + fd_alpenglow_pda( "carlgration", &genesis_cert_addr ); + genesis->accounts[ alpenglow_idx ] = (fd_genesis_account_pair_t) { + .key = genesis_cert_addr, + .account = (fd_genesis_account_t) { + .lamports = fd_rent_exempt_minimum_balance( &genesis->rent, FD_GENESIS_CERT_SZ ), + .data_len = FD_GENESIS_CERT_SZ, + .data = genesis_cert_data, + .owner = fd_solana_system_program_id + } + }; - fd_epoch_inflation_account_state_t epoch_inflation = { .current = { .slots_per_epoch = genesis->epoch_schedule.slots_per_epoch } }; - ulong epoch_inflation_sz = fd_epoch_inflation_account_encode( epoch_inflation_data, &epoch_inflation ); - genesis->accounts[ alpenglow_idx+1UL ] = (fd_genesis_account_pair_t) { - .key = fd_epoch_inflation_account_address(), - .account = (fd_genesis_account_t) { - .lamports = fd_ulong_max( fd_rent_exempt_minimum_balance( &genesis->rent, epoch_inflation_sz ), 1UL ), - .data_len = epoch_inflation_sz, - .data = epoch_inflation_data, - .owner = fd_solana_system_program_id - } - }; - } + fd_epoch_inflation_account_state_t epoch_inflation = { .current = { .slots_per_epoch = genesis->epoch_schedule.slots_per_epoch } }; + ulong epoch_inflation_sz = fd_epoch_inflation_account_encode( epoch_inflation_data, &epoch_inflation ); + genesis->accounts[ alpenglow_idx+1UL ] = (fd_genesis_account_pair_t) { + .key = fd_epoch_inflation_account_address(), + .account = (fd_genesis_account_t) { + .lamports = fd_ulong_max( fd_rent_exempt_minimum_balance( &genesis->rent, epoch_inflation_sz ), 1UL ), + .data_len = epoch_inflation_sz, + .data = epoch_inflation_data, + .owner = fd_solana_system_program_id + } + }; /* Deploy an SPL Token compatible program and set up the token accounts */ diff --git a/src/flamenco/genesis/fd_genesis_create.h b/src/flamenco/genesis/fd_genesis_create.h index 824c2f81e33..31416eb2601 100644 --- a/src/flamenco/genesis/fd_genesis_create.h +++ b/src/flamenco/genesis/fd_genesis_create.h @@ -40,12 +40,6 @@ struct fd_genesis_options { Alpenglow. */ uchar bls_pubkey[ FD_BLS_PUBKEY_COMPRESSED_SZ ]; - /* alpenglow creates a cluster that runs Alpenglow from slot 0. Adds - the genesis certificate and epoch inflation accounts, like Agave - solana-genesis --alpenglow. The caller must also enable the - alpenglow feature in features. */ - int alpenglow; - /* features points to an externally owned feature map. Adds a feature account to the genesis blob for feature enabled at slot 0. If features==NULL, creates no feature accounts. */ diff --git a/src/flamenco/genesis/test_genesis_create.c b/src/flamenco/genesis/test_genesis_create.c index 90af625850b..1fd3a43be90 100644 --- a/src/flamenco/genesis/test_genesis_create.c +++ b/src/flamenco/genesis/test_genesis_create.c @@ -208,7 +208,7 @@ main( int argc, result_sz = fd_genesis_create( result_mem, sizeof(result_mem), options ); FD_TEST( result_sz ); FD_TEST( fd_genesis_parse( genesis, result_mem, result_sz ) ); - FD_TEST( genesis->account_cnt==20UL+FD_GENESIS_TOKEN_ACCOUNTS_PER_ACCOUNT*16UL+3UL ); + FD_TEST( genesis->account_cnt==20UL+FD_GENESIS_TOKEN_ACCOUNTS_PER_ACCOUNT*16UL+3UL+2UL ); fd_rent_t rent = { .lamports_per_uint8_year = genesis->rent.lamports_per_uint8_year, @@ -279,11 +279,10 @@ main( int argc, } } - /* Alpenglow at genesis, token accounts included so the Alpenglow - accounts land after them in the account table. */ + /* Alpenglow feature at genesis, token accounts included so the + Alpenglow accounts land after them in the account table. */ for( ulong i=0UL; ibls_pubkey); i++ ) options->bls_pubkey[ i ] = (uchar)(i+1UL); - options->alpenglow = 1; features->alpenglow = 0UL; result_sz = fd_genesis_create( result_mem, sizeof(result_mem), options ); From 22fa40dbcce329de5a0058bf65275ab9b7167489 Mon Sep 17 00:00:00 2001 From: Richard Patel Date: Wed, 21 Oct 2026 17:19:41 +0000 Subject: [PATCH 11/13] gui: remove Tower BFT support --- src/disco/gui/fd_gui.c | 418 ++++++---------------------- src/disco/gui/fd_gui.h | 49 +--- src/disco/gui/fd_gui_peers.c | 129 +-------- src/disco/gui/fd_gui_peers.h | 34 +-- src/disco/gui/fd_gui_printf.c | 227 +++------------ src/disco/gui/fd_gui_printf.h | 4 - src/disco/gui/fd_gui_tile.c | 26 +- src/disco/gui/test_gui_hist_evict.c | 25 +- 8 files changed, 140 insertions(+), 772 deletions(-) diff --git a/src/disco/gui/fd_gui.c b/src/disco/gui/fd_gui.c index c78f1aa88da..85a1b73ca3f 100644 --- a/src/disco/gui/fd_gui.c +++ b/src/disco/gui/fd_gui.c @@ -13,7 +13,6 @@ #include "../../disco/genesis/fd_genesis_cluster.h" #include "../../disco/pack/fd_pack.h" #include "../../disco/pack/fd_pack_cost.h" -#include "../../choreo/tower/fd_tower_serdes.h" #include @@ -52,8 +51,8 @@ fd_gui_build_tile_order( fd_gui_t * gui ) { char const * const tile_display_order[] = { "gossvf", "gossip", "snapct", "snapld", "snapdc", "snapin", - "net", "shred", "repair", "rotor", "replay", "execrp", "tower", "votor", "txsend", "sign", - "quic", "verify", "dedup", "pack", "execle", "poh" + "net", "shred", "rotor", "replay", "execrp", "votor", "sign", + "quic", "verify", "dedup", "pack", "execle" }; for( ulong n=0UL; nsummary.vote_key_base58, 0, sizeof(gui->summary.vote_key_base58) ); } - gui->summary.is_alpenglow = is_alpenglow; gui->summary.version = version; gui->summary.cluster = cluster; fd_cstr_ncpy( gui->summary.accounts_database_path, accounts_database_path, sizeof(gui->summary.accounts_database_path) ); @@ -234,7 +231,6 @@ fd_gui_new( void * shmem, gui->summary.vote_commission = USHORT_MAX; gui->summary.estimated_slot_duration_nanos = 0UL; - gui->summary.vote_distance = 0UL; gui->summary.is_voting = !!is_voting; gui->summary.vote_state = is_voting ? FD_GUI_VOTE_STATE_VOTING : FD_GUI_VOTE_STATE_NON_VOTING; @@ -253,7 +249,6 @@ fd_gui_new( void * shmem, gui->summary.slot_rooted = ULONG_MAX; gui->summary.slot_finalized = ULONG_MAX; gui->summary.slot_notarized = ULONG_MAX; - gui->summary.slot_optimistically_confirmed = ULONG_MAX; gui->summary.slot_estimated = ULONG_MAX; gui->summary.slot_caught_up = ULONG_MAX; gui->summary.slot_repair = ULONG_MAX; @@ -345,7 +340,6 @@ fd_gui_new( void * shmem, { "execle", FD_GUI_ACCDB_TILE_KIND_RW }, { "execrp", FD_GUI_ACCDB_TILE_KIND_RW }, { "replay", FD_GUI_ACCDB_TILE_KIND_RW }, - { "tower", FD_GUI_ACCDB_TILE_KIND_RW }, { "rpc", FD_GUI_ACCDB_TILE_KIND_RO }, { "resolv", FD_GUI_ACCDB_TILE_KIND_RO }, { "snapin", FD_GUI_ACCDB_TILE_KIND_SNAPIN }, @@ -407,8 +401,6 @@ fd_gui_new( void * shmem, memset( gui->summary.tile_timers_current, 0, sizeof(gui->summary.tile_timers_current) ); memset( gui->summary.tile_timers_packed, 0, sizeof(gui->summary.tile_timers_packed) ); - gui->landed_vote_cnt = 0UL; - gui->block_engine.has_block_engine = 0; gui->epoch.current_epoch = ULONG_MAX; @@ -440,25 +432,17 @@ fd_gui_set_identity( fd_gui_t * gui, fd_base58_encode_32( identity_pubkey, NULL, gui->summary.identity_key_base58 ); gui->summary.identity_key_base58[ FD_BASE58_ENCODED_32_SZ-1UL ] = '\0'; - gui->summary.vote_distance = 0UL; if( FD_LIKELY( gui->summary.is_voting ) ) gui->summary.vote_state = FD_GUI_VOTE_STATE_VOTING; - gui->landed_vote_cnt = 0UL; - if( FD_UNLIKELY( gui->summary.is_alpenglow ) ) gui->summary.slot_voted = ULONG_MAX; + gui->summary.slot_voted = ULONG_MAX; fd_gui_printf_identity_key( gui ); fd_http_server_ws_broadcast( gui->http ); - if( FD_LIKELY( !gui->summary.is_alpenglow ) ) { - fd_gui_printf_vote_distance( gui ); - fd_http_server_ws_broadcast( gui->http ); - } else { - fd_gui_printf_vote_slot( gui ); - fd_http_server_ws_broadcast( gui->http ); - } + fd_gui_printf_vote_slot( gui ); + fd_http_server_ws_broadcast( gui->http ); fd_gui_printf_vote_state( gui ); fd_http_server_ws_broadcast( gui->http ); - if( FD_LIKELY( !gui->summary.is_alpenglow ) ) fd_gui_printf_late_votes_history ( gui ); - else fd_gui_printf_missed_vote_history( gui, gui->epoch.current_epoch ); + fd_gui_printf_missed_vote_history( gui, gui->epoch.current_epoch ); fd_http_server_ws_broadcast( gui->http ); } @@ -518,7 +502,6 @@ fd_gui_ws_open( fd_gui_t * gui, fd_gui_printf_vote_key, fd_gui_printf_startup_time_nanos, fd_gui_printf_vote_state, - fd_gui_printf_vote_distance, fd_gui_printf_turbine_slot, fd_gui_printf_repair_slot, fd_gui_printf_slot_caught_up, @@ -534,31 +517,26 @@ fd_gui_ws_open( fd_gui_t * gui, fd_gui_printf_storage_slot, fd_gui_printf_reset_slot, fd_gui_printf_active_fork_cnt, - fd_gui_printf_optimistically_confirmed_slot, fd_gui_printf_completed_slot, fd_gui_printf_estimated_slot, fd_gui_printf_live_tile_timers, fd_gui_printf_live_tile_metrics, fd_gui_printf_catch_up_history, - fd_gui_printf_late_votes_history, fd_gui_printf_health }; ulong printers_len = sizeof(printers) / sizeof(printers[0]); for( ulong i=0UL; isummary.is_alpenglow && (printers[ i ]==fd_gui_printf_vote_distance || printers[ i ]==fd_gui_printf_optimistically_confirmed_slot || printers[ i ]==fd_gui_printf_late_votes_history ) ) ) continue; printers[ i ]( gui ); FD_TEST( !fd_http_server_ws_send( gui->http, ws_conn_id ) ); } - if( FD_UNLIKELY( gui->summary.is_alpenglow ) ) { - fd_gui_printf_finalized_slot( gui ); - FD_TEST( !fd_http_server_ws_send( gui->http, ws_conn_id ) ); - fd_gui_printf_notarized_slot( gui ); - FD_TEST( !fd_http_server_ws_send( gui->http, ws_conn_id ) ); - fd_gui_printf_vote_slot( gui ); - FD_TEST( !fd_http_server_ws_send( gui->http, ws_conn_id ) ); - } + fd_gui_printf_finalized_slot( gui ); + FD_TEST( !fd_http_server_ws_send( gui->http, ws_conn_id ) ); + fd_gui_printf_notarized_slot( gui ); + FD_TEST( !fd_http_server_ws_send( gui->http, ws_conn_id ) ); + fd_gui_printf_vote_slot( gui ); + FD_TEST( !fd_http_server_ws_send( gui->http, ws_conn_id ) ); if( FD_LIKELY( gui->system.valid ) ) { fd_gui_printf_system_resources( gui ); @@ -595,10 +573,8 @@ fd_gui_ws_open( fd_gui_t * gui, fd_gui_printf_skipped_history_cluster( gui, gui->epoch.current_epoch ); FD_TEST( !fd_http_server_ws_send( gui->http, ws_conn_id ) ); - if( FD_UNLIKELY( gui->summary.is_alpenglow ) ) { - fd_gui_printf_missed_vote_history( gui, gui->epoch.current_epoch ); - FD_TEST( !fd_http_server_ws_send( gui->http, ws_conn_id ) ); - } + fd_gui_printf_missed_vote_history( gui, gui->epoch.current_epoch ); + FD_TEST( !fd_http_server_ws_send( gui->http, ws_conn_id ) ); } /* rebroadcast 10s of historical shred data */ @@ -773,7 +749,6 @@ fd_gui_estimated_tps_snap( fd_gui_t * gui ) { static void fd_gui_network_stats_snap_egress( fd_topo_t const * topo, char const * tile_name, - int is_alpenglow, fd_gui_network_stats_t * cur ) { ulong tile_cnt = fd_topo_tile_name_cnt( topo, tile_name ); for( ulong i=0UL; iout.repair += bytes; if( FD_UNLIKELY( !strcmp( link_name, "rserve_net" ) ) ) cur->out.rserve += bytes; if( FD_UNLIKELY( !strcmp( link_name, "txsend_net" ) ) ) cur->out.tpu += bytes; - if( FD_UNLIKELY( is_alpenglow && !strcmp( link_name, "votor_net" ) ) ) cur->out.votor += bytes; + if( FD_UNLIKELY( !strcmp( link_name, "votor_net" ) ) ) cur->out.votor += bytes; } } } @@ -811,11 +786,11 @@ fd_gui_network_stats_snap( fd_gui_t * gui, cur->out.tpu = 0UL; cur->out.votor = 0UL; cur->in.votor = 0UL; - fd_gui_network_stats_snap_egress( topo, "net", gui->summary.is_alpenglow, cur ); - fd_gui_network_stats_snap_egress( topo, "mlx5", gui->summary.is_alpenglow, cur ); + fd_gui_network_stats_snap_egress( topo, "net", cur ); + fd_gui_network_stats_snap_egress( topo, "mlx5", cur ); ulong votor_tile_idx = fd_topo_find_tile( topo, "votor", 0UL ); - if( FD_UNLIKELY( gui->summary.is_alpenglow && votor_tile_idx!=ULONG_MAX ) ) { + if( FD_UNLIKELY( votor_tile_idx!=ULONG_MAX ) ) { fd_topo_tile_t const * votor = &topo->tiles[ votor_tile_idx ]; for( ulong i=0UL; iin_cnt; i++ ) { if( FD_UNLIKELY( !strcmp( topo->links[ votor->in_link_id[ i ] ].name, "net_votor" ) ) ) { @@ -825,7 +800,7 @@ fd_gui_network_stats_snap( fd_gui_t * gui, } cur->in.repair = fd_gui_metrics_sum_tiles_counter( topo, "shred", shred_tile_cnt, MIDX( COUNTER, SHRED, REPAIR_RX_BYTES ) ); - ulong repair_tile_idx = fd_topo_find_tile( topo, gui->summary.is_alpenglow ? "rotor" : "repair", 0UL ); + ulong repair_tile_idx = fd_topo_find_tile( topo, "rotor", 0UL ); if( FD_LIKELY( repair_tile_idx!=ULONG_MAX ) ) { fd_topo_tile_t const * repair = &topo->tiles[ repair_tile_idx ]; @@ -897,7 +872,7 @@ fd_gui_network_rate_max_update( fd_gui_t * gui, d_in[ 3 ] = fd_ulong_sat_sub( cur->in.repair, prev->in.repair ); d_in[ 4 ] = fd_ulong_sat_sub( cur->in.rserve, prev->in.rserve ); d_in[ 5 ] = fd_ulong_sat_sub( cur->in.metric, prev->in.metric ); - d_in[ 6 ] = fd_ulong_sat_sub( cur->in.votor, prev->in.votor ); /* 0 if !gui->summary.is_alpenglow */ + d_in[ 6 ] = fd_ulong_sat_sub( cur->in.votor, prev->in.votor ); ulong d_out[ FD_GUI_NET_PROTO_CNT ]; d_out[ 0 ] = fd_ulong_sat_sub( cur->out.turbine, prev->out.turbine ); @@ -906,7 +881,7 @@ fd_gui_network_rate_max_update( fd_gui_t * gui, d_out[ 3 ] = fd_ulong_sat_sub( cur->out.repair, prev->out.repair ); d_out[ 4 ] = fd_ulong_sat_sub( cur->out.rserve, prev->out.rserve ); d_out[ 5 ] = fd_ulong_sat_sub( cur->out.metric, prev->out.metric ); - d_out[ 6 ] = fd_ulong_sat_sub( cur->out.votor, prev->out.votor ); /* 0 if !gui->summary.is_alpenglow */ + d_out[ 6 ] = fd_ulong_sat_sub( cur->out.votor, prev->out.votor ); /* Compute per-protocol instantaneous bytes/sec rate and feed the EMA. */ long dt_ns = now - gui->summary.net_rate_prev_ts; @@ -968,7 +943,7 @@ fd_gui_network_rate_max_update( fd_gui_t * gui, /* Snapshot accdb statistics by reading the accdb tile's metric page (for gauges) and summing counters across all tiles that join accdb - (executors, replay, tower, rpc, resolv, plus the accdb tile itself). + (executors, replay, rpc, resolv, plus the accdb tile itself). The result feeds the GUI "Accounts" page. */ static void @@ -1510,7 +1485,7 @@ fd_gui_run_boot_progress( fd_gui_t * gui, long now ) { /* Backtest topologies have no turbine, so the regular catch-up detection (turbine slot vs. replayed slot) can never trigger. - Consider replay caught up at the first tower root after the + Consider replay caught up at the first reset slot after the snapshot finishes loading. */ if( FD_UNLIKELY( gui->summary.slot_caught_up==ULONG_MAX && snapshot_phase==FD_SNAPCT_STATE_SHUTDOWN && @@ -1740,7 +1715,7 @@ fd_gui_sample_repair_slot( fd_gui_t * gui, long now ) { static void fd_gui_sample_catchup_slot( fd_gui_t * gui ) { - if( FD_LIKELY( !gui->summary.is_alpenglow || gui->summary.slot_caught_up!=ULONG_MAX ) ) return; + if( FD_LIKELY( gui->summary.slot_caught_up!=ULONG_MAX ) ) return; ulong rotor_tile_idx = fd_topo_find_tile( gui->topo, "rotor", 0UL ); if( FD_UNLIKELY( rotor_tile_idx==ULONG_MAX ) ) return; fd_topo_tile_t const * rotor = &gui->topo->tiles[ rotor_tile_idx ]; @@ -2329,7 +2304,6 @@ fd_gui_slot_get_canon_safe( fd_gui_t * gui, ulong _slot ) { return slot; } else { uchar level = FD_GUI_SLOT_LEVEL_COMPLETED; - if( FD_LIKELY( gui->summary.slot_optimistically_confirmed!=ULONG_MAX && _slotsummary.slot_optimistically_confirmed ) ) level = FD_GUI_SLOT_LEVEL_OPTIMISTICALLY_CONFIRMED; if( FD_LIKELY( gui->summary.slot_rooted!=ULONG_MAX && _slotsummary.slot_rooted ) ) level = FD_GUI_SLOT_LEVEL_ROOTED; *gui->skipped_scratch = (fd_gui_slot_t){ @@ -2349,7 +2323,6 @@ fd_gui_slot_get_canon_safe( fd_gui_t * gui, ulong _slot ) { .priority_fee = ULONG_MAX, .tips = ULONG_MAX, .shred_cnt = UINT_MAX, - .vote_latency_exact = FD_GUI_VOTE_LATENCY_NOT_VOTED, .is_voter = FD_GUI_IS_VOTER_UNKNOWN, .vote_rewarded = fd_gui_slot_vote_rewarded_state( gui, _slot ), .vote_count = fd_gui_slot_vote_count( gui, _slot ), @@ -2421,10 +2394,7 @@ fd_gui_handle_epoch_info( fd_gui_t * gui, epoch->rankings_slot = epoch_info->start_slot; memset( epoch->rankings, (int)(UINT_MAX), sizeof(epoch->rankings) ); memset( epoch->my_rankings, (int)(UINT_MAX), sizeof(epoch->my_rankings) ); - memset( epoch->latency_exact, (int)FD_GUI_VOTE_LATENCY_NOT_VOTED, sizeof(epoch->latency_exact) ); - uchar is_voter_default = FD_GUI_IS_VOTER_NO; - if( FD_UNLIKELY( gui->summary.is_alpenglow ) ) is_voter_default = FD_GUI_IS_VOTER_UNKNOWN; - memset( epoch->is_voter, is_voter_default, sizeof(epoch->is_voter) ); + memset( epoch->is_voter, FD_GUI_IS_VOTER_UNKNOWN, sizeof(epoch->is_voter) ); memset( epoch->skipped, 0, sizeof(epoch->skipped) ); memset( epoch->vote_rewarded, FD_GUI_VOTE_REWARDED_UNKNOWN, sizeof(epoch->vote_rewarded) ); memset( epoch->vote_count, UCHAR_MAX, sizeof(epoch->vote_count) ); @@ -2519,7 +2489,7 @@ fd_gui_handle_leader_fec( fd_gui_t * gui, long tsorig, long now ) { /* Abandoned block detected */ - if( FD_UNLIKELY( gui->summary.is_alpenglow && gui->shreds.leader_shred_slot!=slot ) ) { + if( FD_UNLIKELY( gui->shreds.leader_shred_slot!=slot ) ) { gui->shreds.leader_shred_cnt = 0UL; gui->shreds.leader_shred_slot = slot; } @@ -2565,61 +2535,6 @@ fd_gui_handle_exec_txn_done( fd_gui_t * gui, } } -/* fd_gui_compute_vote_latency walks the fork whose landing block is - (landed_slot, landed_bank_seq) down toward voted_slot. On success - (voted_slot is an ancestor on this fork) returns 1 and writes - voted_slot's bank_seq on this fork to *out_voted_bank_seq and the - skip-discounted latency to *out_exact. Returns 0 if voted_slot is - not an ancestor on this fork, or the ancestry is not yet - replayed/linked. */ - -static int -fd_gui_compute_vote_latency( fd_gui_t * gui, - ulong landed_slot, - ulong landed_bank_seq, - ulong voted_slot, - ulong * out_voted_bank_seq, - ulong * out_exact ) { - ulong on_fork_between = 0UL; - ulong cur_slot = landed_slot; - ulong cur_bank_seq = landed_bank_seq; - for( ulong steps=0UL; steps<1024UL; steps++ ) { - fd_gui_slot_t const * c = fd_gui_slot_get( gui, cur_slot, cur_bank_seq ); - if( FD_UNLIKELY( !c ) ) return 0; - ulong pslot = c->parent_slot; - ulong pseq = c->parent_bank_seq; - if( FD_UNLIKELY( pslot==ULONG_MAX ) ) return 0; - if( FD_UNLIKELY( pslotvote_latency_exact ) ) { - slot->vote_latency_exact = vote_latency_exact; - fd_gui_printf_slot( gui, voted_slot, slot ); - fd_http_server_ws_broadcast( gui->http ); - } -} - static void fd_gui_persist_slot_skip( fd_gui_t * gui, ulong slot, @@ -2654,36 +2569,18 @@ fd_gui_handle_root_advanced( fd_gui_t * gui, c->level = FD_GUI_SLOT_LEVEL_ROOTED; fd_gui_persist_slot_skip( gui, cslot, cbank_seq ); - if( FD_UNLIKELY( gui->summary.is_alpenglow && c->finalization_kind==FD_GUI_AG_FINAL_NONE ) ) { + if( FD_UNLIKELY( c->finalization_kind==FD_GUI_AG_FINAL_NONE ) ) { c->finalization_kind = FD_GUI_AG_FINAL_IMPLICIT; } fd_gui_printf_slot( gui, cslot, c ); fd_http_server_ws_broadcast( gui->http ); - /* Finalize vote latencies from votes that landed in this block. */ - for( ulong r=0UL; rlanded_vote_cnt; r++ ) { - if( FD_UNLIKELY( gui->landed_votes[ r ].landed_slot!=cslot || gui->landed_votes[ r ].landed_bank_seq!=cbank_seq ) ) continue; - ulong voted_slot = gui->landed_votes[ r ].voted_slot; - ulong voted_bank_seq, exact; - if( FD_UNLIKELY( !fd_gui_compute_vote_latency( gui, cslot, cbank_seq, voted_slot, &voted_bank_seq, &exact ) ) ) continue; - exact = (uchar)fd_ulong_min( exact, FD_GUI_VOTE_LATENCY_MAX ); - fd_gui_record_vote_latency( gui, voted_slot, voted_bank_seq, (uchar)exact ); - - fd_gui_epoch_t * vepoch = fd_gui_get_epoch_by_slot( gui, voted_slot ); - if( FD_UNLIKELY( !vepoch ) ) continue; - ulong vidx = voted_slot - vepoch->start_slot; - if( FD_UNLIKELY( vidx>=vepoch->slot_cnt ) ) continue; - if( FD_LIKELY( exactlatency_exact[ vidx ] ) ) { - vepoch->latency_exact[ vidx ] = (uchar)exact; - } - } - fd_gui_epoch_t * epoch = fd_gui_get_epoch_by_slot( gui, cslot ); if( FD_LIKELY( epoch ) ) { ulong cidx = cslot - epoch->start_slot; if( FD_LIKELY( cidxslot_cnt ) ) { - if( FD_UNLIKELY( gui->summary.is_alpenglow && epoch->is_voter[ cidx ]!=FD_GUI_IS_VOTER_UNKNOWN ) ) + if( FD_UNLIKELY( epoch->is_voter[ cidx ]!=FD_GUI_IS_VOTER_UNKNOWN ) ) fd_gui_slot_set_voter_state( c, epoch->is_voter[ cidx ] ); epoch->is_voter[ cidx ] = c->is_voter; epoch->vote_count[ cidx ] = c->vote_count; @@ -2719,13 +2616,6 @@ fd_gui_handle_root_advanced( fd_gui_t * gui, cslot = pslot; cbank_seq = pseq; } - ulong rooted_slot = gui->summary.slot_rooted; - ulong w = 0UL; - for( ulong r=0UL; rlanded_vote_cnt; r++ ) { - if( FD_UNLIKELY( gui->landed_votes[ r ].landed_slot<=rooted_slot ) ) continue; - gui->landed_votes[ w++ ] = gui->landed_votes[ r ]; - } - gui->landed_vote_cnt = w; } static fd_gui_ag_slot_t * @@ -2807,7 +2697,7 @@ fd_gui_ag_register_block( fd_gui_t * gui, ulong slot, fd_hash_t const * block_id, ulong bank_seq ) { - if( FD_UNLIKELY( !gui->summary.is_alpenglow || !block_id ) ) return; + if( FD_UNLIKELY( !block_id ) ) return; fd_gui_ag_slot_t * ent = fd_gui_ag_slot_entry( gui, slot ); if( FD_UNLIKELY( fd_gui_ag_slot_has_proof( ent ) && !fd_hash_eq( &ent->block_id, block_id ) ) ) return; @@ -2822,7 +2712,7 @@ fd_gui_handle_ag_notarized( fd_gui_t * gui, ulong slot, fd_hash_t const * block_id, uchar notarization_kind ) { - if( FD_UNLIKELY( !gui->summary.is_alpenglow || !block_id ) ) return; + if( FD_UNLIKELY( !block_id ) ) return; fd_gui_ag_slot_t * ent = fd_gui_ag_slot_for_certificate( gui, slot, block_id ); ent->notarization_kind = fd_uchar_if( notarization_kind>ent->notarization_kind, notarization_kind, ent->notarization_kind ); @@ -2835,7 +2725,7 @@ fd_gui_handle_ag_finalized( fd_gui_t * gui, ulong slot, fd_hash_t const * block_id, uchar finalization_kind ) { - if( FD_UNLIKELY( !gui->summary.is_alpenglow || !block_id ) ) return; + if( FD_UNLIKELY( !block_id ) ) return; fd_gui_ag_slot_t * ent = fd_gui_ag_slot_for_certificate( gui, slot, block_id ); ent->finalization_kind = fd_uchar_if( finalization_kind>ent->finalization_kind, finalization_kind, ent->finalization_kind ); @@ -2856,7 +2746,6 @@ fd_gui_handle_ag_finalized( fd_gui_t * gui, void fd_gui_handle_ag_skip_cert( fd_gui_t * gui, ulong slot ) { - if( FD_UNLIKELY( !gui->summary.is_alpenglow ) ) return; fd_gui_ag_slot_t * ent = fd_gui_ag_slot_entry( gui, slot ); if( FD_LIKELY( ent->skip>=FD_GUI_SKIP_STATUS_NOTARIZED ) ) return; @@ -2870,7 +2759,6 @@ fd_gui_handle_ag_skip_cert( fd_gui_t * gui, void fd_gui_handle_ag_leader( fd_gui_t * gui, ulong parent_slot ) { - if( FD_UNLIKELY( !gui->summary.is_alpenglow ) ) return; if( FD_UNLIKELY( gui->summary.slot_reset==parent_slot ) ) return; gui->summary.slot_reset = parent_slot; @@ -2881,7 +2769,7 @@ fd_gui_handle_ag_leader( fd_gui_t * gui, int fd_gui_ag_slot_is_skip_notarized( fd_gui_t const * gui, ulong slot ) { - if( FD_UNLIKELY( !gui->summary.is_alpenglow || slot==ULONG_MAX ) ) return 0; + if( FD_UNLIKELY( slot==ULONG_MAX ) ) return 0; fd_gui_ag_slot_t const * ent = &gui->ag.slot[ slot % gui->ag.slot_cnt ]; return ent->slot==slot && ent->skip>=FD_GUI_SKIP_STATUS_NOTARIZED; @@ -2892,7 +2780,6 @@ fd_gui_handle_ag_reward( fd_gui_t * gui, ulong slot, int voted, ushort voted_rank ) { - if( FD_UNLIKELY( !gui->summary.is_alpenglow ) ) return; uchar vote_rewarded = fd_uchar_if( !!voted, FD_GUI_VOTE_REWARDED_YES, FD_GUI_VOTE_REWARDED_NO ); uchar is_voter = fd_uchar_if( voted_rank!=USHORT_MAX, FD_GUI_IS_VOTER_YES, FD_GUI_IS_VOTER_NO ); @@ -3071,61 +2958,6 @@ fd_gui_timeline_handle_txn( fd_gui_t * gui, } } -void -fd_gui_handle_oc_advanced( fd_gui_t * gui, - ulong _slot, - ulong bank_seq, - long now ) { - (void)now; - - if( FD_UNLIKELY( gui->summary.is_alpenglow ) ) return; - - fd_gui_slot_t * live_slot = fd_gui_slot_get( gui, _slot, bank_seq ); - if( FD_UNLIKELY( !live_slot ) ) return; - - fd_gui_slot_t const * slot = fd_gui_slot_get_canon( gui, _slot ); - int on_canonical_fork = ( slot && slot->bank_seq==bank_seq ); - if( FD_UNLIKELY( !on_canonical_fork ) ) return; /* we've since switched forks so this update is invalid */ - - ulong prev_oc = gui->summary.slot_optimistically_confirmed; - - int advanced = 0; - if( FD_LIKELY( _slot!=prev_oc ) ) { - gui->summary.slot_optimistically_confirmed = _slot; - fd_gui_printf_optimistically_confirmed_slot( gui ); - fd_http_server_ws_broadcast( gui->http ); - advanced = ( prev_oc==ULONG_MAX || _slot>prev_oc ); - } - - for( ulong cslot=_slot, cbank_seq=bank_seq; ; ) { - fd_gui_slot_t * c = fd_gui_slot_get( gui, cslot, cbank_seq ); - if( FD_UNLIKELY( !c || c->level>=FD_GUI_SLOT_LEVEL_ROOTED ) ) break; - - if( FD_LIKELY( c->levellevel = FD_GUI_SLOT_LEVEL_OPTIMISTICALLY_CONFIRMED; - fd_gui_printf_slot( gui, cslot, c ); - fd_http_server_ws_broadcast( gui->http ); - } - - ulong pslot = c->parent_slot, pseq = c->parent_bank_seq; - if( FD_UNLIKELY( pslot==ULONG_MAX || pslot>=cslot ) ) break; - - /* Republish skipped slots as well. */ - if( FD_LIKELY( advanced ) ) { - for( ulong s=pslot+1UL; ssummary.slot_rooted!=ULONG_MAX && ssummary.slot_rooted ) ) continue; /* already rooted: handled by root advance */ - fd_gui_slot_t const * skipped = fd_gui_slot_get_canon_safe( gui, s ); - if( FD_UNLIKELY( skipped->skip!=FD_GUI_SKIP_STATUS_FINALIZED ) ) continue; - fd_gui_printf_slot( gui, s, skipped ); - fd_http_server_ws_broadcast( gui->http ); - } - } - - cslot = pslot; cbank_seq = pseq; - } -} - void fd_gui_handle_genesis_hash( fd_gui_t * gui, fd_hash_t const * msg ) { @@ -3253,27 +3085,16 @@ handle_tower_slot( fd_gui_t * gui, ulong reset_slot, ulong reset_bank_seq, long ulong prev_reset_slot = gui->summary.slot_tower; ulong prev_reset_bank_seq = gui->summary.slot_tower_bank_seq; - /* reset_slot is guaranteed present (returnable_frag deferred this - update until replay recorded it). */ - if( FD_UNLIKELY( !fd_gui_slot_get( gui, reset_slot, reset_bank_seq ) ) ) { - /* Alpenglow drives this from replay completion rather than a - consensus-derived reset slot, so the record is not guaranteed. */ - if( FD_UNLIKELY( gui->summary.is_alpenglow ) ) return 0; - FD_LOG_ERR(( "fd_gui_slot_get( gui, slot_tower %lu, slot_tower_bank_seq %lu ) missing", - reset_slot, reset_bank_seq )); - } + /* Alpenglow drives this from replay completion rather than a + consensus-derived reset slot, so the record is not guaranteed. */ + if( FD_UNLIKELY( !fd_gui_slot_get( gui, reset_slot, reset_bank_seq ) ) ) return 0; /* reset_slot has not changed */ if( FD_UNLIKELY( prev_reset_slot!=ULONG_MAX && reset_slot==prev_reset_slot && reset_bank_seq==prev_reset_bank_seq ) ) return 0; - /* slot complete received out of order on the same fork? Check - against the old frontier before replacing it. */ - if( FD_UNLIKELY( prev_reset_slot!=ULONG_MAX && fd_gui_slot_is_ancestor( gui, reset_slot, reset_bank_seq, prev_reset_slot, prev_reset_bank_seq ) ) ) { - /* Alpenglow can complete a descendant before one of its ancestors. - Keep advertising the highest completed block on that fork. */ - if( FD_UNLIKELY( gui->summary.is_alpenglow ) ) return 0; - FD_LOG_ERR(( "slot complete received out of order on the same fork (prev_reset_slot %lu, slot_tower %lu)", prev_reset_slot, reset_slot )); - } + /* Alpenglow can complete a descendant before one of its ancestors. + Keep advertising the highest completed block on that fork. */ + if( FD_UNLIKELY( prev_reset_slot!=ULONG_MAX && fd_gui_slot_is_ancestor( gui, reset_slot, reset_bank_seq, prev_reset_slot, prev_reset_bank_seq ) ) ) return 0; gui->summary.slot_tower = reset_slot; gui->summary.slot_tower_bank_seq = reset_bank_seq; @@ -3332,40 +3153,6 @@ set_vote_state( fd_gui_t * gui, } } -void -fd_gui_stage_landed_vote( fd_gui_t * gui, - ulong landed_slot, - ulong landed_bank_seq, - ulong voted_slot ) { - if( FD_UNLIKELY( voted_slot==ULONG_MAX || voted_slot>=landed_slot ) ) return; - - /* Handle already-rooted edge case. */ - if( FD_UNLIKELY( gui->summary.slot_rooted!=ULONG_MAX && landed_slot<=gui->summary.slot_rooted ) ) { - fd_gui_slot_t const * canon = fd_gui_slot_get_canon( gui, landed_slot ); - if( FD_UNLIKELY( !canon || canon->bank_seq!=landed_bank_seq ) ) return; - ulong voted_bank_seq, exact; - if( FD_UNLIKELY( !fd_gui_compute_vote_latency( gui, landed_slot, landed_bank_seq, voted_slot, &voted_bank_seq, &exact ) ) ) return; - exact = (uchar)fd_ulong_min( exact, FD_GUI_VOTE_LATENCY_MAX ); - fd_gui_record_vote_latency( gui, voted_slot, voted_bank_seq, (uchar)exact ); - - fd_gui_epoch_t * vepoch = fd_gui_get_epoch_by_slot( gui, voted_slot ); - if( FD_UNLIKELY( !vepoch ) ) return; - ulong vidx = voted_slot - vepoch->start_slot; - if( FD_UNLIKELY( vidx>=vepoch->slot_cnt ) ) return; - if( FD_LIKELY( exactlatency_exact[ vidx ] ) ) { - vepoch->latency_exact[ vidx ] = (uchar)exact; - } - return; - } - - if( FD_UNLIKELY( gui->landed_vote_cnt>=FD_GUI_LANDED_VOTE_MAX ) ) return; - - gui->landed_votes[ gui->landed_vote_cnt ].landed_slot = landed_slot; - gui->landed_votes[ gui->landed_vote_cnt ].landed_bank_seq = landed_bank_seq; - gui->landed_votes[ gui->landed_vote_cnt ].voted_slot = voted_slot; - gui->landed_vote_cnt++; -} - static long fd_gui_tick_to_nanos( fd_gui_t const * gui, long now, @@ -3433,7 +3220,6 @@ fd_gui_handle_replay_txn( fd_gui_t * gui, void fd_gui_handle_replay_update( fd_gui_t * gui, fd_replay_slot_completed_t const * slot_completed, - ulong vote_slot, long now ) { if( FD_LIKELY( gui->summary.slot_storage!=slot_completed->storage_slot ) ) { gui->summary.slot_storage = slot_completed->storage_slot; @@ -3466,28 +3252,15 @@ fd_gui_handle_replay_update( fd_gui_t * gui, fd_gui_slot_t const * parent = fd_gui_slot_parent_get( gui, slot ); slot->parent_completed_time = parent ? parent->completed_time : LONG_MAX; - if( FD_UNLIKELY( gui->summary.is_alpenglow ) ) { - if( FD_UNLIKELY( slot_completed->voted && slot_completed->slot>=FD_NUM_SLOTS_FOR_REWARD ) ) { - vote_slot = slot_completed->slot-FD_NUM_SLOTS_FOR_REWARD; - } else { - vote_slot = gui->summary.slot_voted; /* TODO: inherit from parent so it's fork-correct. Requires tracking identity changes to be correct. */ - } + ulong vote_slot; + if( FD_UNLIKELY( slot_completed->voted && slot_completed->slot>=FD_NUM_SLOTS_FOR_REWARD ) ) { + vote_slot = slot_completed->slot-FD_NUM_SLOTS_FOR_REWARD; + } else { + vote_slot = gui->summary.slot_voted; /* TODO: inherit from parent so it's fork-correct. Requires tracking identity changes to be correct. */ } - if( FD_LIKELY( slot->levelsummary.slot_optimistically_confirmed!=ULONG_MAX && slot_completed->slotsummary.slot_optimistically_confirmed ) ) { - /* Cluster might have already optimistically confirmed by the time - we finish replaying it. */ - slot->level = FD_GUI_SLOT_LEVEL_OPTIMISTICALLY_CONFIRMED; - } else { - slot->level = FD_GUI_SLOT_LEVEL_COMPLETED; - } - } + /* A slot may already be notarized by the time we replay it. */ + if( FD_LIKELY( slot->levellevel = FD_GUI_SLOT_LEVEL_COMPLETED; slot->vote_failed = fd_uint_if( slot_completed->vote_failed==ULONG_MAX, slot->vote_failed, (uint)slot_completed->vote_failed ); slot->vote_success = fd_uint_if( slot_completed->vote_success==ULONG_MAX, slot->vote_success, (uint)slot_completed->vote_success ); slot->nonvote_success = fd_uint_if( slot_completed->nonvote_success==ULONG_MAX, slot->nonvote_success, (uint)slot_completed->nonvote_success ); @@ -3514,13 +3287,13 @@ fd_gui_handle_replay_update( fd_gui_t * gui, fd_gui_shred_event_append( gui, slot_completed->slot, USHORT_MAX, FD_GUI_SLOT_SHRED_SHRED_SLOT_COMPLETE, slot_completed->completion_time_nanos, now ); - /* Set skip status based on the current tower-derived canonical fork. */ + /* Set skip status based on the current canonical fork. */ fd_gui_slot_t * canon = fd_gui_slot_get_canon( gui, slot_completed->slot ); slot->skip = fd_uchar_if( canon && canon->bank_seq==slot_completed->bank_seq, FD_GUI_SKIP_STATUS_NOT_SKIPPED, FD_GUI_SKIP_STATUS_UNKNOWN ); /* vote_count is keyed by the cert slot. The epoch copy is written only on rooting, so it never holds a non-canonical bank's count. */ - if( FD_UNLIKELY( gui->summary.is_alpenglow ) ) slot->vote_count = slot_completed->vote_count; + slot->vote_count = slot_completed->vote_count; /* fixes race if we just sample right after replay's SLOT_COMPLETE */ if( FD_LIKELY( gui->summary.slot_caught_up!=ULONG_MAX ) ) fd_gui_sample_repair_slot( gui, now ); @@ -3538,49 +3311,47 @@ fd_gui_handle_replay_update( fd_gui_t * gui, fd_http_server_ws_broadcast( gui->http ); } - if( FD_UNLIKELY( gui->summary.is_alpenglow ) ) { - if( FD_UNLIKELY( slot_completed->voted && slot_completed->slot>=FD_NUM_SLOTS_FOR_REWARD && ( gui->summary.slot_voted==ULONG_MAX || vote_slot>gui->summary.slot_voted ) ) ) { - gui->summary.slot_voted = vote_slot; - fd_gui_printf_vote_slot( gui ); - fd_http_server_ws_broadcast( gui->http ); - } + if( FD_UNLIKELY( slot_completed->voted && slot_completed->slot>=FD_NUM_SLOTS_FOR_REWARD && ( gui->summary.slot_voted==ULONG_MAX || vote_slot>gui->summary.slot_voted ) ) ) { + gui->summary.slot_voted = vote_slot; + fd_gui_printf_vote_slot( gui ); + fd_http_server_ws_broadcast( gui->http ); + } - /* Bind this bank only to certificates for the same block. */ - fd_gui_ag_register_block( gui, slot_completed->slot, &slot_completed->block_id, slot_completed->bank_seq ); + /* Bind this bank only to certificates for the same block. */ + fd_gui_ag_register_block( gui, slot_completed->slot, &slot_completed->block_id, slot_completed->bank_seq ); - if( FD_UNLIKELY( slot_completed->vote_balance!=ULONG_MAX && gui->summary.vote_account_balance!=slot_completed->vote_balance ) ) { - gui->summary.vote_account_balance = slot_completed->vote_balance; - fd_gui_printf_vote_balance( gui ); - fd_http_server_ws_broadcast( gui->http ); - } - if( FD_UNLIKELY( slot_completed->vote_commission!=USHORT_MAX && gui->summary.vote_commission!=slot_completed->vote_commission ) ) { - gui->summary.vote_commission = slot_completed->vote_commission; - fd_gui_printf_vote_commission( gui ); - fd_http_server_ws_broadcast( gui->http ); - } + if( FD_UNLIKELY( slot_completed->vote_balance!=ULONG_MAX && gui->summary.vote_account_balance!=slot_completed->vote_balance ) ) { + gui->summary.vote_account_balance = slot_completed->vote_balance; + fd_gui_printf_vote_balance( gui ); + fd_http_server_ws_broadcast( gui->http ); + } + if( FD_UNLIKELY( slot_completed->vote_commission!=USHORT_MAX && gui->summary.vote_commission!=slot_completed->vote_commission ) ) { + gui->summary.vote_commission = slot_completed->vote_commission; + fd_gui_printf_vote_commission( gui ); + fd_http_server_ws_broadcast( gui->http ); + } - if( FD_LIKELY( slot_completed->slot>=FD_NUM_SLOTS_FOR_REWARD ) ) { - fd_gui_handle_ag_reward( gui, slot_completed->slot-FD_NUM_SLOTS_FOR_REWARD, slot_completed->voted, slot_completed->voted_rank ); - } + if( FD_LIKELY( slot_completed->slot>=FD_NUM_SLOTS_FOR_REWARD ) ) { + fd_gui_handle_ag_reward( gui, slot_completed->slot-FD_NUM_SLOTS_FOR_REWARD, slot_completed->voted, slot_completed->voted_rank ); + } - int frontier_updated = handle_tower_slot( gui, slot_completed->slot, slot_completed->bank_seq, now ); + int frontier_updated = handle_tower_slot( gui, slot_completed->slot, slot_completed->bank_seq, now ); - if( FD_LIKELY( gui->summary.is_voting && slot_completed->slot>=FD_NUM_SLOTS_FOR_REWARD ) ) { - if( FD_UNLIKELY( slot_completed->voted_rank==USHORT_MAX ) ) { - set_vote_state( gui, FD_GUI_VOTE_STATE_NON_VOTING ); - } else { - ulong s = gui->summary.slot_tower; - ulong voted = gui->summary.slot_voted; - int current = voted!=ULONG_MAX && fd_int_if( s>=128UL, voted+128UL>s, voted>0UL ); - set_vote_state( gui, fd_int_if( current, FD_GUI_VOTE_STATE_VOTING, FD_GUI_VOTE_STATE_DELINQUENT ) ); - } + if( FD_LIKELY( gui->summary.is_voting && slot_completed->slot>=FD_NUM_SLOTS_FOR_REWARD ) ) { + if( FD_UNLIKELY( slot_completed->voted_rank==USHORT_MAX ) ) { + set_vote_state( gui, FD_GUI_VOTE_STATE_NON_VOTING ); + } else { + ulong s = gui->summary.slot_tower; + ulong voted = gui->summary.slot_voted; + int current = voted!=ULONG_MAX && fd_int_if( s>=128UL, voted+128UL>s, voted>0UL ); + set_vote_state( gui, fd_int_if( current, FD_GUI_VOTE_STATE_VOTING, FD_GUI_VOTE_STATE_DELINQUENT ) ); } + } - if( FD_UNLIKELY( frontier_updated && gui->summary.slot_estimated!=slot_completed->slot ) ) { - gui->summary.slot_estimated = slot_completed->slot; - fd_gui_printf_estimated_slot( gui ); - fd_http_server_ws_broadcast( gui->http ); - } + if( FD_UNLIKELY( frontier_updated && gui->summary.slot_estimated!=slot_completed->slot ) ) { + gui->summary.slot_estimated = slot_completed->slot; + fd_gui_printf_estimated_slot( gui ); + fd_http_server_ws_broadcast( gui->http ); } } @@ -3711,30 +3482,6 @@ fd_gui_microblock_execution_begin( fd_gui_t * gui, if( FD_LIKELY( lslot ) ) lslot->begin_microblocks += (uint)txn_cnt; } -static void -fd_gui_stage_leader_block_votes( fd_gui_t * gui, - ulong landed_slot, - ulong landed_bank_seq, - fd_txn_p_t * txn_p ) { - if( FD_LIKELY( !(txn_p->flags & FD_TXN_P_FLAGS_EXECUTE_SUCCESS) || (txn_p->flags & (FD_TXN_P_FLAGS_FEES_ONLY | FD_TXN_P_FLAGS_RESULT_MASK)) ) ) return; - - fd_txn_t const * txn = TXN( txn_p ); - uchar const * payload = txn_p->payload; - - fd_compact_tower_sync_serde_t tower_sync[ 1 ]; - if( FD_UNLIKELY( !fd_txn_parse_simple_vote( txn, payload, tower_sync ) ) ) return; - - fd_acct_addr_t const * accs = fd_txn_get_acct_addrs( txn, payload ); - if( FD_UNLIKELY( memcmp( &accs[ 0 ], gui->summary.identity_key->uc, sizeof(fd_pubkey_t) ) ) ) return; /* not our identity */ - if( FD_UNLIKELY( gui->summary.has_vote_key && memcmp( &accs[ txn->signature_cnt==1 ? 1 : 2 ], gui->summary.vote_key->uc, sizeof(fd_pubkey_t) ) ) ) return; /* not our vote account */ - - ulong cur = fd_ulong_if( tower_sync->root==ULONG_MAX, 0UL, tower_sync->root ); - for( ulong i=0UL; ilockouts_cnt; i++ ) { - if( FD_UNLIKELY( __builtin_uaddl_overflow( cur, tower_sync->lockouts[ i ].offset, &cur ) ) ) return; - fd_gui_stage_landed_vote( gui, landed_slot, landed_bank_seq, cur ); - } -} - static inline long fd_gui_ns_dt_float_to_long( float dt ) { return (long)((double)dt+0.5); @@ -3848,9 +3595,6 @@ fd_gui_microblock_execution_end( fd_gui_t * gui, if( FD_LIKELY( gui->db && gui->hist && (txn_p->flags & FD_TXN_P_FLAGS_EXECUTE_SUCCESS) && exec_end_ticks!=LONG_MAX ) ) { fd_gui_record_leader_txn( gui, txn_p, txn_idx, bank_idx, _slot, block_compute_unit_limit, txn_ns_dt, exec_end_ticks, tips, error_code, now ); } - - /* Record our own votes that land in our own leader block. */ - fd_gui_stage_leader_block_votes( gui, _slot, bank_seq, txn_p ); } lslot = fd_gui_slot_leader_get( gui, _slot, bank_seq ); diff --git a/src/disco/gui/fd_gui.h b/src/disco/gui/fd_gui.h index db61943cc83..9da783031b3 100644 --- a/src/disco/gui/fd_gui.h +++ b/src/disco/gui/fd_gui.h @@ -154,9 +154,8 @@ typedef struct fd_gui_rate_entry fd_gui_rate_entry_t; #define FD_GUI_IS_VOTER_YES ((uchar)1) #define FD_GUI_IS_VOTER_NO ((uchar)2) -/* Under Tower, NOTARIZED isn't used and FINALIZED is a catch-all for - skipped. UNKNOWN is used while fork choice has not resolved a - replayed slot. */ +/* UNKNOWN is used while fork choice has not resolved a replayed + slot. */ #define FD_GUI_SKIP_STATUS_UNKNOWN ((uchar)0) #define FD_GUI_SKIP_STATUS_NOT_SKIPPED ((uchar)1) #define FD_GUI_SKIP_STATUS_NOTARIZED ((uchar)2) @@ -172,10 +171,6 @@ typedef struct fd_gui_rate_entry fd_gui_rate_entry_t; #define FD_GUI_VOTE_STATE_VOTING (1) #define FD_GUI_VOTE_STATE_DELINQUENT (2) -#define FD_GUI_MAX_VOTE_DISTANCE (512UL) - -#define FD_GUI_LANDED_VOTE_MAX (4096UL) - #define FD_GUI_HTTP_MIN_SEND_BUFFER_SZ (256UL<<20) #define FD_GUI_TIMELINE_STORED_GRANULARITY_CNT (7UL) @@ -524,7 +519,6 @@ struct __attribute__((packed)) fd_gui_slot { uint vote_failed; /* failed vote txn count (UINT_MAX if unknown) */ uint nonvote_success; /* successful nonvote txn count (UINT_MAX if unknown) */ uint nonvote_failed; /* failed nonvote txn count (UINT_MAX if unknown) */ - uchar vote_latency_exact; /* our vote latency minus skipped slots on the landed fork, or FD_GUI_VOTE_LATENCY_NOT_VOTED if no vote landed. */ uint max_compute_units;/* block compute unit limit (UINT_MAX if unknown) */ uint compute_units; /* block compute units consumed (UINT_MAX if unknown) */ ulong transaction_fee; /* total transaction fee (ULONG_MAX if unknown) */ @@ -597,9 +591,6 @@ typedef struct fd_gui_slot_rankings fd_gui_slot_rankings_t; #define FD_GUI_EPOCH_SCHED_CNT ((MAX_SLOTS_PER_EPOCH+FD_EPOCH_SLOTS_PER_ROTATION-1UL)/FD_EPOCH_SLOTS_PER_ROTATION) #define FD_GUI_EPOCH_PUB_CNT (MAX_STAKE_WEIGHTS) -#define FD_GUI_VOTE_LATENCY_NOT_VOTED ((uchar)(UCHAR_MAX)) /* vote missing */ -#define FD_GUI_VOTE_LATENCY_MAX ((uchar)(UCHAR_MAX-1UL)) /* largest observable vote latency */ - struct fd_gui_epoch { ulong epoch; ulong start_slot; @@ -613,7 +604,6 @@ struct fd_gui_epoch { fd_gui_slot_rankings_t rankings [ 1 ]; /* global slot rankings */ fd_gui_slot_rankings_t my_rankings[ 1 ]; /* my slots only */ - uchar latency_exact[ MAX_SLOTS_PER_EPOCH ]; /* skip-discounted latency or FD_GUI_VOTE_LATENCY_* */ uchar is_voter [ MAX_SLOTS_PER_EPOCH ]; /* Alpenglow: one of FD_GUI_IS_VOTER_* */ uchar skipped [ MAX_SLOTS_PER_EPOCH ]; /* 1 if the slot was skipped on the rooted fork */ uchar vote_rewarded[ MAX_SLOTS_PER_EPOCH ]; /* Alpenglow: one of FD_GUI_VOTE_REWARDED_* */ @@ -861,7 +851,7 @@ typedef struct fd_gui_snapsv_pending fd_gui_snapsv_pending_t; #define FD_GUI_ACCDB_EMA_HALF_LIFE_NS (600L*1000L*1000L*1000L) /* 10 minutes */ /* Tile kinds. Determines which subset of metrics to read. */ -#define FD_GUI_ACCDB_TILE_KIND_RW 0 /* execle, execrp, replay, tower */ +#define FD_GUI_ACCDB_TILE_KIND_RW 0 /* execle, execrp, replay */ #define FD_GUI_ACCDB_TILE_KIND_RO 1 /* rpc, resolv */ #define FD_GUI_ACCDB_TILE_KIND_SNAPIN 2 /* snapin (direct snapshot disk writer) */ #define FD_GUI_ACCDB_TILE_KIND_ACCDB 3 /* accdb tile itself (prewrite + compaction writes) */ @@ -931,7 +921,7 @@ struct fd_gui_accdb_stats { /* Per-tile accdb stats. At init we walk the topology and assign a slot to each tile that uses the account database (execle, execrp, - replay, tower, rpc, resolv, accdb). Each slot keeps cumulative + replay, rpc, resolv, accdb). Each slot keeps cumulative previous values for delta computation and a triangular-weighted delta ring (same cadence / weighting as the aggregate rings). */ ulong accdb_tile_cnt; @@ -1004,7 +994,6 @@ struct fd_gui_summary { char vote_key_base58[ FD_BASE58_ENCODED_32_SZ ]; char identity_key_base58[ FD_BASE58_ENCODED_32_SZ ]; - int is_alpenglow; int is_voting; /* configured to vote (vote account set) */ char const * version; char const * cluster; @@ -1016,7 +1005,6 @@ struct fd_gui_summary { ushort expected_shred_version; int wfs_enabled; - ulong vote_distance; int vote_state; long startup_time_nanos; @@ -1044,7 +1032,6 @@ struct fd_gui_summary { ulong slot_rooted; ulong slot_finalized; ulong slot_notarized; - ulong slot_optimistically_confirmed; ulong slot_estimated; ulong slot_caught_up; ulong slot_repair; @@ -1190,12 +1177,6 @@ struct fd_gui { fd_gui_slot_txn_join_t * joined; /* [max] */ } slot_txn_scratch; - struct { - ulong landed_slot; - ulong landed_bank_seq; - ulong voted_slot; - } landed_votes[ FD_GUI_LANDED_VOTE_MAX ]; - ulong landed_vote_cnt; struct { int has_block_engine; @@ -1325,7 +1306,6 @@ fd_gui_new( void * shmem, uchar const * identity_key, int has_vote_key, uchar const * vote_key, - int is_alpenglow, ulong max_live_slots, ulong max_txn_per_slot, int snapshots_enabled, @@ -1472,15 +1452,8 @@ fd_gui_handle_epoch_info( fd_gui_t * gui, void fd_gui_handle_replay_update( fd_gui_t * gui, fd_replay_slot_completed_t const * slot_completed, - ulong vote_slot, long now ); -void -fd_gui_stage_landed_vote( fd_gui_t * gui, - ulong landed_slot, - ulong landed_bank_seq, - ulong voted_slot ); - void fd_gui_handle_genesis_hash( fd_gui_t * gui, fd_hash_t const * msg ); @@ -1493,14 +1466,6 @@ fd_gui_handle_root_advanced( fd_gui_t * gui, ulong bank_seq, long now ); -/* fd_gui_handle_oc_advanced is invoked on REPLAY_SIG_OC_ADVANCED. It - marks the (slot, bank_seq) fork optimistically confirmed. */ -void -fd_gui_handle_oc_advanced( fd_gui_t * gui, - ulong slot, - ulong bank_seq, - long now ); - void fd_gui_handle_ag_notarized( fd_gui_t * gui, ulong slot, @@ -1656,7 +1621,7 @@ fd_gui_slot_parent_get( fd_gui_t * gui, fd_gui_slot_t const * c ) { /* fd_gui_slot_get_canon returns the DB record for slot number `_slot` on the canonical fork, or NULL if `_slot` is skipped, above the canonical tip, or the tip is not yet resolved. The canonical fork is - the consensus fork chosen by tower fork choice. */ + the fork of the current reset slot. */ static inline fd_gui_slot_t * fd_gui_slot_get_canon( fd_gui_t * gui, ulong _slot ) { @@ -1827,13 +1792,11 @@ fd_gui_slot_get_or_create( fd_gui_t * gui, meta->parent_bank_seq = parent_bank_seq; meta->parent_slot = _parent_slot; meta->vote_slot = ULONG_MAX; - meta->vote_latency_exact = FD_GUI_VOTE_LATENCY_NOT_VOTED; meta->max_compute_units = UINT_MAX; meta->completed_time = LONG_MAX; meta->parent_completed_time = LONG_MAX; meta->mine = (uchar)(mine & 1); - meta->is_voter = FD_GUI_IS_VOTER_NO; - if( FD_UNLIKELY( gui->summary.is_alpenglow ) ) meta->is_voter = FD_GUI_IS_VOTER_UNKNOWN; + meta->is_voter = FD_GUI_IS_VOTER_UNKNOWN; meta->skip = FD_GUI_SKIP_STATUS_UNKNOWN; meta->level = (uchar)( _slot ? FD_GUI_SLOT_LEVEL_INCOMPLETE : FD_GUI_SLOT_LEVEL_ROOTED ); /* slot 0 always rooted */ meta->notarization_kind = FD_GUI_AG_NOTAR_NONE; diff --git a/src/disco/gui/fd_gui_peers.c b/src/disco/gui/fd_gui_peers.c index 4b5b86d62f6..aa4b93dc3fa 100644 --- a/src/disco/gui/fd_gui_peers.c +++ b/src/disco/gui/fd_gui_peers.c @@ -172,8 +172,6 @@ fd_gui_peers_new( void * shmem, ctx->open_ws_conn_cnt = 0UL; ctx->active_ws_conn_id = ULONG_MAX; - ctx->slot_voted = ULONG_MAX; - for( ulong i=0UL; i<2UL; i++ ) { ctx->epochs[ i ].epoch = ULONG_MAX; ctx->epochs[ i ].stakes_cnt = 0UL; @@ -536,11 +534,6 @@ geoip_lookup( fd_gui_ip_db_t const * ip_db, #define SORT_BEFORE(a,b) (memcmp( (a).weight.id_key.uc, (b).weight.id_key.uc, 32UL )>0) #include "../../util/tmpl/fd_sort.c" -#define SORT_NAME fd_gui_peers_voter_idx_sort_vote_desc -#define SORT_KEY_T fd_gui_peers_voter_idx_t -#define SORT_BEFORE(a,b) (memcmp( (a).key.uc, (b).key.uc, 32UL )>0) -#include "../../util/tmpl/fd_sort.c" - static void wfs_handle_contact_info_update( fd_gui_peers_ctx_t * peers, fd_pubkey_t const * identity, @@ -591,12 +584,7 @@ fd_gui_peers_voter_best_for_identity( fd_gui_peers_voter_t const * voters, ulong j = i + 1UL; fd_gui_peers_voter_t const * best = &voters[ i ]; while( jweight.id_key.uc, sizeof(fd_pubkey_t) ) ) { - fd_gui_peers_voter_t const * candidate = &voters[ j ]; - ulong slot_best = fd_ulong_if( best->vote_slot==ULONG_MAX, 0UL, best->vote_slot ); - ulong slot_cand = fd_ulong_if( candidate->vote_slot==ULONG_MAX, 0UL, candidate->vote_slot ); - if( (slot_cand>slot_best) || ((slot_cand==slot_best) && (candidate->weight.stake>best->weight.stake)) ) { - best = candidate; - } + if( voters[ j ].weight.stake>best->weight.stake ) best = &voters[ j ]; j++; } *p_i = j; @@ -688,7 +676,6 @@ fd_gui_peers_handle_gossip_update( fd_gui_peers_ctx_t * peers, fd_gui_ema_init( &peer->row.gossvf_rx_sum.rate_ema, now, FD_GUI_PEERS_EMA_HALF_LIFE_NS ); fd_gui_ema_init( &peer->row.gossip_tx_sum.rate_ema, now, FD_GUI_PEERS_EMA_HALF_LIFE_NS ); peer->row.has_vote_info = 0; - peer->row.delinquent = 0; peer->row.stake = ULONG_MAX; /* Backfill stake from already-received epoch data. This @@ -801,35 +788,6 @@ fd_gui_peers_handle_gossip_update( fd_gui_peers_ctx_t * peers, } } -void -fd_gui_peers_handle_identity_change( fd_gui_peers_ctx_t * peers ) { - peers->slot_voted = ULONG_MAX; - fd_gui_peers_printf_vote_slot( peers ); - fd_http_server_ws_broadcast( peers->http ); -} - -void -fd_gui_peers_handle_vote( fd_gui_peers_ctx_t * peers, - fd_pubkey_t const * vote_account, - ulong vote_slot, - int is_us ) { - if( FD_UNLIKELY( is_us && peers->slot_voted!=vote_slot ) ) { - peers->slot_voted = fd_ulong_if( vote_slot==0UL, ULONG_MAX, vote_slot ); - fd_gui_peers_printf_vote_slot( peers ); - fd_http_server_ws_broadcast( peers->http ); - } - - for( ulong i=0UL; i<2UL; i++ ) { - fd_gui_peers_voter_idx_t * vidx = peers->epochs[ i ].vote_idx; - ulong cnt = peers->epochs[ i ].stakes_cnt; - ulong pos = fd_gui_peers_voter_idx_sort_vote_desc_split( vidx, cnt, (fd_gui_peers_voter_idx_t){ .key = *vote_account } ); - if( FD_UNLIKELY( pos>=cnt || memcmp( vidx[ pos ].key.uc, vote_account->uc, sizeof(fd_pubkey_t) ) ) ) continue; - - fd_gui_peers_voter_t * voter = &peers->epochs[ i ].stakes[ vidx[ pos ].idx ]; - voter->vote_slot = fd_ulong_if( voter->vote_slot==ULONG_MAX, vote_slot, fd_ulong_max( voter->vote_slot, vote_slot ) ); - } -} - void fd_gui_peers_handle_epoch_info( fd_gui_peers_ctx_t * peers, fd_epoch_info_msg_t const * epoch_info, @@ -847,10 +805,7 @@ fd_gui_peers_handle_epoch_info( fd_gui_peers_ctx_t * peers, ulong stakes_cnt = 0UL; for( ulong i=0UL; istaked_vote_cnt; i++ ) { if( FD_UNLIKELY( fd_pubkey_check_zero( &weights[ i ].id_key ) ) ) continue; - peers->epochs[ epoch_idx ].stakes[ stakes_cnt ] = (fd_gui_peers_voter_t){ - .weight = weights[ i ], - .vote_slot = ULONG_MAX, - }; + peers->epochs[ epoch_idx ].stakes[ stakes_cnt ] = (fd_gui_peers_voter_t){ .weight = weights[ i ] }; stakes_cnt++; } peers->epochs[ epoch_idx ].epoch = epoch_info->epoch; @@ -893,88 +848,8 @@ fd_gui_peers_handle_epoch_info( fd_gui_peers_ctx_t * peers, fd_gui_peers_printf_nodes( peers, peers->scratch.actions, peers->scratch.idxs, updated_cnt ); fd_http_server_ws_broadcast( peers->http ); } - - /* Build vote account index for fd_gui_peers_handle_vote */ - for( ulong j=0UL; jepochs[ epoch_idx ].vote_idx[ j ] = (fd_gui_peers_voter_idx_t){ - .key = peers->epochs[ epoch_idx ].stakes[ j ].weight.vote_key, - .idx = j, - }; - } - fd_gui_peers_voter_idx_sort_vote_desc_inplace( peers->epochs[ epoch_idx ].vote_idx, stakes_cnt ); } -#define SORT_NAME fd_gui_peers_voter_sort_slot_stake_desc -#define SORT_KEY_T fd_gui_peers_voter_t -#define SORT_BEFORE(a,b) ((a).vote_slot>(b).vote_slot ? 1 : (a).vote_slot<(b).vote_slot ? 0 : (a).weight.stake>(b).weight.stake) -#include "../../util/tmpl/fd_sort.c" - -void -fd_gui_peers_update_delinquency( fd_gui_peers_ctx_t * peers, - long now FD_PARAM_UNUSED ) { - ulong epoch_t_1 = ULONG_MAX; - for( ulong i=0UL; i<2UL; i++ ) { - ulong epoch = peers->epochs[ i ].epoch; - if( FD_UNLIKELY( epoch==ULONG_MAX ) ) continue; - if( FD_LIKELY( epoch_t_1==ULONG_MAX || epoch>epoch_t_1 ) ) epoch_t_1 = epoch; - } - ulong epoch_idx = epoch_t_1 % 2UL; - - fd_gui_peers_voter_t * voters = peers->epochs[ epoch_idx ].stakes; - ulong voters_cnt = peers->epochs[ epoch_idx ].stakes_cnt; - - /* Copy to scratch and sort for p67 computation. We use a scratch - copy to avoid clobbering the identity sort order of stakes. */ - fd_gui_peers_voter_t * scratch = peers->scratch.voters_scratch; - fd_memcpy( scratch, voters, voters_cnt * sizeof(fd_gui_peers_voter_t) ); - fd_gui_peers_voter_sort_slot_stake_desc_inplace( scratch, voters_cnt ); - - ulong total_stake = 0UL; - for( ulong i=0UL; i 1UL*total_stake ) ) { - last_vote_slot_p33 = scratch[ i ].vote_slot; - break; - } - } - if( FD_UNLIKELY( last_vote_slot_p33==ULONG_MAX ) ) { - return; /* not enough observed votes */ - } - - ulong updated_cnt = 0UL; - for( ulong i=0UL; inode_pubkey_map, &voters[ i ].weight.id_key, ULONG_MAX, - peers->contact_info_table ); - if( FD_UNLIKELY( peer_idx==ULONG_MAX ) ) continue; - - fd_gui_peers_node_t * peer = &peers->contact_info_table[ peer_idx ]; - - /* Only update peers whose vote_account was already set by - handle_epoch_info and matches this voter */ - if( FD_UNLIKELY( !peer->row.has_vote_info ) ) continue; - if( FD_UNLIKELY( memcmp( peer->row.vote_account.uc, voters[ i ].weight.vote_key.uc, sizeof(fd_pubkey_t) ) ) ) continue; - - int is_delinquent = fd_int_if( voters[ i ].vote_slot==ULONG_MAX, 1, ((long)last_vote_slot_p33 - (long)voters[ i ].vote_slot) > 150L ); - if( FD_LIKELY( peer->row.delinquent==is_delinquent ) ) continue; - - peer->row.delinquent = is_delinquent; - - peers->scratch.actions[ updated_cnt ] = FD_GUI_PEERS_NODE_UPDATE; - peers->scratch.idxs [ updated_cnt ] = peer_idx; - updated_cnt++; - } - - if( FD_UNLIKELY( updated_cnt ) ) { - fd_gui_peers_printf_nodes( peers, peers->scratch.actions, peers->scratch.idxs, updated_cnt ); - fd_http_server_ws_broadcast( peers->http ); - } -} void fd_gui_peers_handle_config_account( fd_gui_peers_ctx_t * peers, diff --git a/src/disco/gui/fd_gui_peers.h b/src/disco/gui/fd_gui_peers.h index e5d4de53115..b1fc22825e6 100644 --- a/src/disco/gui/fd_gui_peers.h +++ b/src/disco/gui/fd_gui_peers.h @@ -136,16 +136,9 @@ typedef struct fd_gui_peers_metric_rate fd_gui_peers_metric_rate_t; struct fd_gui_peers_voter { fd_vote_stake_weight_t weight; - ulong vote_slot; }; typedef struct fd_gui_peers_voter fd_gui_peers_voter_t; -struct fd_gui_peers_voter_idx { - fd_pubkey_t key; - ulong idx; -}; -typedef struct fd_gui_peers_voter_idx fd_gui_peers_voter_idx_t; - /* fd_gui_peers_row_t holds all of the per-peer state that is read when formatting websocket viewport messages snapshotted into a client's viewport. */ @@ -164,7 +157,6 @@ struct fd_gui_peers_row { int has_vote_info; fd_pubkey_t vote_account; - int delinquent; ulong stake; uchar country_code_idx; @@ -390,17 +382,12 @@ struct fd_gui_peers_ctx { fd_gui_peers_gossip_stats_t gossip_stats [ 1 ]; fd_gui_peers_node_t contact_info_table[ FD_CONTACT_INFO_TABLE_SIZE ]; - ulong slot_voted; /* last vote slot for this validator */ - - /* stakes is sorted descending by identity pubkey. vote_idx is sorted - descending by vote account pubkey, each entry pointing back into - the stakes array. */ + /* stakes is sorted descending by identity pubkey. */ struct { ulong epoch; ulong stakes_cnt; fd_gui_peers_voter_t stakes [ MAX_STAKE_WEIGHTS ]; - fd_gui_peers_voter_idx_t vote_idx[ MAX_STAKE_WEIGHTS ]; } epochs[ 2 ]; union { @@ -415,7 +402,6 @@ struct fd_gui_peers_ctx { fd_stake_weight_t manifest_id_weights [ FD_RUNTIME_MAX_SNAPSHOT_VOTE_ACCOUNTS ]; fd_vote_stake_weight_t manifest_vote_weights[ FD_RUNTIME_MAX_SNAPSHOT_VOTE_ACCOUNTS ]; }; - fd_gui_peers_voter_t voters_scratch[ MAX_STAKE_WEIGHTS ]; struct { fd_gui_peers_row_t viewport [ FD_GUI_PEERS_WS_VIEWPORT_MAX_SZ ]; /* new rows snapshotted from live_table */ fd_gui_peers_row_t viewport_ref[ FD_GUI_PEERS_WS_VIEWPORT_MAX_SZ ]; /* old baseline, diff reference */ @@ -481,24 +467,6 @@ fd_gui_peers_handle_gossip_update( fd_gui_peers_ctx_t * peers, fd_gossip_update_message_t const * update, long now ); -void -fd_gui_peers_handle_vote( fd_gui_peers_ctx_t * peers, - fd_pubkey_t const * vote_account, - ulong vote_slot, - int is_us ); - -/* fd_gui_peers_handle_identity_change invalidates/resets state from the - previous identity. Namely, it resets the vote slot. */ -void -fd_gui_peers_handle_identity_change( fd_gui_peers_ctx_t * peers ); - -/* fd_gui_peers_update_delinquency is called infrequently (currently, - once per slot) and scans the cluster for any nodes that are - delinquent, publishing delinquency updates to the frontend. */ -void -fd_gui_peers_update_delinquency( fd_gui_peers_ctx_t * peers, - long now ); - /* fd_gui_peers_handle_epoch_info is called at the epoch boundary and publishes updates for peer stake information. */ void diff --git a/src/disco/gui/fd_gui_printf.c b/src/disco/gui/fd_gui_printf.c index 148fde05a6d..afc1a33fccd 100644 --- a/src/disco/gui/fd_gui_printf.c +++ b/src/disco/gui/fd_gui_printf.c @@ -346,7 +346,7 @@ fd_gui_printf_version( fd_gui_t * gui ) { void fd_gui_printf_is_alpenglow( fd_gui_t * gui ) { jsonp_open_envelope( gui->http, "summary", "is_alpenglow" ); - jsonp_bool( gui->http, "value", gui->summary.is_alpenglow ); + jsonp_bool( gui->http, "value", 1 ); jsonp_close_envelope( gui->http ); } @@ -393,13 +393,6 @@ fd_gui_printf_server_time_nanos( fd_gui_t * gui, long now ) { jsonp_close_envelope( gui->http ); } -void -fd_gui_printf_vote_distance( fd_gui_t * gui ) { - jsonp_open_envelope( gui->http, "summary", "vote_distance" ); - jsonp_ulong( gui->http, "value", gui->summary.vote_distance ); - jsonp_close_envelope( gui->http ); -} - void fd_gui_printf_repair_slot( fd_gui_t * gui ) { jsonp_open_envelope( gui->http, "summary", "repair_slot" ); @@ -408,14 +401,6 @@ fd_gui_printf_repair_slot( fd_gui_t * gui ) { jsonp_close_envelope( gui->http ); } -void -fd_gui_peers_printf_vote_slot( fd_gui_peers_ctx_t * peers ) { - jsonp_open_envelope( peers->http, "summary", "vote_slot" ); - if( FD_LIKELY( peers->slot_voted!=ULONG_MAX ) ) jsonp_ulong( peers->http, "value", peers->slot_voted ); - else jsonp_null ( peers->http, "value" ); - jsonp_close_envelope( peers->http ); -} - void fd_gui_printf_vote_slot( fd_gui_t * gui ) { jsonp_open_envelope( gui->http, "summary", "vote_slot" ); @@ -622,56 +607,6 @@ fd_gui_printf_skipped_history_cluster( fd_gui_t * gui, ulong epoch ) { jsonp_close_envelope( gui->http ); } -/* fd_gui_slot_is_late returns 1 if slot s belongs in the - late_votes_history output (voted for late, or canonical but never - voted for), 0 otherwise. */ - -static inline int -fd_gui_slot_is_late( fd_gui_epoch_t const * rec, ulong s ) { - ulong idx = s-rec->start_slot; - uchar exact = rec->latency_exact[ idx ]; - return fd_int_if( rec->is_voter[ idx ]!=FD_GUI_IS_VOTER_YES, 0, fd_int_if( rec->skipped[ idx ], 0, fd_int_if( exact==FD_GUI_VOTE_LATENCY_NOT_VOTED, 1, exact>1 ) ) ); -} - -void -fd_gui_printf_late_votes_history( fd_gui_t * gui ) { - fd_gui_epoch_t * rec = fd_gui_current_epoch( gui ); - ulong first_replay_slot = fd_ulong_if( gui->summary.slot_caught_up!=ULONG_MAX, fd_gui_first_replay_slot( gui ), ULONG_MAX ); - ulong start_slot = FD_LIKELY( rec ) ? fd_ulong_max( rec->start_slot, fd_ulong_if( first_replay_slot!=ULONG_MAX, first_replay_slot, 0UL ) ) : 0UL; - ulong end_slot = FD_LIKELY( rec ) ? fd_ulong_min( rec->start_slot+rec->slot_cnt-1UL, gui->summary.slot_rooted ) : 0UL; - int have_any = rec && gui->summary.slot_rooted!=ULONG_MAX; - - jsonp_open_envelope( gui->http, "slot", "late_votes_history" ); - jsonp_open_object( gui->http, "value" ); - jsonp_open_array( gui->http, "slot" ); - if( FD_LIKELY( have_any ) ) { - ulong s = start_slot; - while( s<=end_slot ) { - if( FD_LIKELY( !fd_gui_slot_is_late( rec, s ) ) ) { s++; continue; } - ulong run_start = s; - uchar run_exact = rec->latency_exact[ s-rec->start_slot ]; - while( s<=end_slot && fd_gui_slot_is_late( rec, s ) && rec->latency_exact[ s-rec->start_slot ]==run_exact ) s++; - jsonp_ulong( gui->http, NULL, run_start ); - jsonp_ulong( gui->http, NULL, s-1UL ); - } - } - jsonp_close_array( gui->http ); - jsonp_open_array( gui->http, "latency_exact" ); - if( FD_LIKELY( have_any ) ) { - ulong s = start_slot; - while( s<=end_slot ) { - if( FD_LIKELY( !fd_gui_slot_is_late( rec, s ) ) ) { s++; continue; } - uchar run_exact = rec->latency_exact[ s-rec->start_slot ]; - while( s<=end_slot && fd_gui_slot_is_late( rec, s ) && rec->latency_exact[ s-rec->start_slot ]==run_exact ) s++; - if( FD_LIKELY( run_exact!=FD_GUI_VOTE_LATENCY_NOT_VOTED ) ) jsonp_ulong( gui->http, NULL, run_exact ); - else jsonp_null ( gui->http, NULL ); - } - } - jsonp_close_array( gui->http ); - jsonp_close_object( gui->http ); - jsonp_close_envelope( gui->http ); -} - void fd_gui_printf_tps_history( fd_gui_t * gui ) { jsonp_open_envelope( gui->http, "summary", "tps_history" ); @@ -679,25 +614,13 @@ fd_gui_printf_tps_history( fd_gui_t * gui ) { for( ulong i=0UL; isummary.estimated_tps_history_idx+i) % FD_GUI_TPS_HISTORY_SAMPLE_CNT; - ulong vote_cnt = gui->summary.estimated_tps_history[ idx ].vote_failed - + gui->summary.estimated_tps_history[ idx ].vote_success; - ulong total_cnt = vote_cnt - + gui->summary.estimated_tps_history[ idx ].nonvote_success - + gui->summary.estimated_tps_history[ idx ].nonvote_failed; jsonp_open_array( gui->http, NULL ); - if( FD_UNLIKELY( gui->summary.is_alpenglow ) ) { - ulong success_cnt = gui->summary.estimated_tps_history[ idx ].nonvote_success - + gui->summary.estimated_tps_history[ idx ].vote_success; - ulong failed_cnt = gui->summary.estimated_tps_history[ idx ].nonvote_failed - + gui->summary.estimated_tps_history[ idx ].vote_failed; - jsonp_double( gui->http, NULL, (double)success_cnt/(double)FD_GUI_TPS_HISTORY_WINDOW_DURATION_SECONDS ); - jsonp_double( gui->http, NULL, (double)failed_cnt /(double)FD_GUI_TPS_HISTORY_WINDOW_DURATION_SECONDS ); - } else { - jsonp_double( gui->http, NULL, (double)total_cnt/(double)FD_GUI_TPS_HISTORY_WINDOW_DURATION_SECONDS ); - jsonp_double( gui->http, NULL, (double)vote_cnt/(double)FD_GUI_TPS_HISTORY_WINDOW_DURATION_SECONDS ); - jsonp_double( gui->http, NULL, (double)gui->summary.estimated_tps_history[ idx ].nonvote_success/(double)FD_GUI_TPS_HISTORY_WINDOW_DURATION_SECONDS ); - jsonp_double( gui->http, NULL, (double)gui->summary.estimated_tps_history[ idx ].nonvote_failed/(double)FD_GUI_TPS_HISTORY_WINDOW_DURATION_SECONDS ); - } + ulong success_cnt = gui->summary.estimated_tps_history[ idx ].nonvote_success + + gui->summary.estimated_tps_history[ idx ].vote_success; + ulong failed_cnt = gui->summary.estimated_tps_history[ idx ].nonvote_failed + + gui->summary.estimated_tps_history[ idx ].vote_failed; + jsonp_double( gui->http, NULL, (double)success_cnt/(double)FD_GUI_TPS_HISTORY_WINDOW_DURATION_SECONDS ); + jsonp_double( gui->http, NULL, (double)failed_cnt /(double)FD_GUI_TPS_HISTORY_WINDOW_DURATION_SECONDS ); jsonp_close_array( gui->http ); } @@ -819,13 +742,6 @@ fd_gui_printf_notarized_slot( fd_gui_t * gui ) { jsonp_close_envelope( gui->http ); } -void -fd_gui_printf_optimistically_confirmed_slot( fd_gui_t * gui ) { - jsonp_open_envelope( gui->http, "summary", "optimistically_confirmed_slot" ); - jsonp_ulong( gui->http, "value", fd_ulong_if( gui->summary.slot_optimistically_confirmed!=ULONG_MAX, gui->summary.slot_optimistically_confirmed, 0UL ) ); - jsonp_close_envelope( gui->http ); -} - void fd_gui_printf_completed_slot( fd_gui_t * gui ) { jsonp_open_envelope( gui->http, "summary", "completed_slot" ); @@ -1206,24 +1122,12 @@ fd_gui_printf_estimated_tps( fd_gui_t * gui ) { jsonp_open_envelope( gui->http, "summary", "estimated_tps" ); jsonp_open_object( gui->http, "value" ); - ulong vote_cnt = gui->summary.estimated_tps_history[ idx ].vote_failed - + gui->summary.estimated_tps_history[ idx ].vote_success; - ulong total_cnt = vote_cnt - + gui->summary.estimated_tps_history[ idx ].nonvote_success - + gui->summary.estimated_tps_history[ idx ].nonvote_failed; - if( FD_UNLIKELY( gui->summary.is_alpenglow ) ) { - ulong success_cnt = gui->summary.estimated_tps_history[ idx ].nonvote_success - + gui->summary.estimated_tps_history[ idx ].vote_success; - ulong failed_cnt = gui->summary.estimated_tps_history[ idx ].nonvote_failed - + gui->summary.estimated_tps_history[ idx ].vote_failed; - jsonp_double( gui->http, "success", (double)success_cnt/(double)FD_GUI_TPS_HISTORY_WINDOW_DURATION_SECONDS ); - jsonp_double( gui->http, "failed", (double)failed_cnt /(double)FD_GUI_TPS_HISTORY_WINDOW_DURATION_SECONDS ); - } else { - jsonp_double( gui->http, "total", (double)total_cnt/(double)FD_GUI_TPS_HISTORY_WINDOW_DURATION_SECONDS ); - jsonp_double( gui->http, "vote", (double)vote_cnt/(double)FD_GUI_TPS_HISTORY_WINDOW_DURATION_SECONDS ); - jsonp_double( gui->http, "nonvote_success", (double)gui->summary.estimated_tps_history[ idx ].nonvote_success/(double)FD_GUI_TPS_HISTORY_WINDOW_DURATION_SECONDS ); - jsonp_double( gui->http, "nonvote_failed", (double)gui->summary.estimated_tps_history[ idx ].nonvote_failed/(double)FD_GUI_TPS_HISTORY_WINDOW_DURATION_SECONDS ); - } + ulong success_cnt = gui->summary.estimated_tps_history[ idx ].nonvote_success + + gui->summary.estimated_tps_history[ idx ].vote_success; + ulong failed_cnt = gui->summary.estimated_tps_history[ idx ].nonvote_failed + + gui->summary.estimated_tps_history[ idx ].vote_failed; + jsonp_double( gui->http, "success", (double)success_cnt/(double)FD_GUI_TPS_HISTORY_WINDOW_DURATION_SECONDS ); + jsonp_double( gui->http, "failed", (double)failed_cnt /(double)FD_GUI_TPS_HISTORY_WINDOW_DURATION_SECONDS ); jsonp_close_object( gui->http ); jsonp_close_envelope( gui->http ); } @@ -2004,7 +1908,7 @@ peers_printf_node( fd_gui_peers_ctx_t * peers, jsonp_ulong( peers->http, "epoch_credits", 0UL ); /* todo: deprecate */ jsonp_ulong( peers->http, "commission", 0UL ); /* todo: deprecate */ jsonp_ulong( peers->http, "root_slot", 0UL ); /* todo: deprecate */ - jsonp_bool( peers->http, "delinquent", peer->row.delinquent ); + jsonp_bool( peers->http, "delinquent", 0 ); jsonp_close_object( peers->http ); jsonp_close_array( peers->http ); } @@ -2143,17 +2047,6 @@ static char const * fd_gui_slot_level_cstr( fd_gui_t const * gui, ulong _slot, fd_gui_slot_t const * slot ) { - if( FD_LIKELY( !gui->summary.is_alpenglow ) ) { - switch( slot->level ) { - case FD_GUI_SLOT_LEVEL_INCOMPLETE: return "incomplete"; - case FD_GUI_SLOT_LEVEL_COMPLETED: return "completed"; - case FD_GUI_SLOT_LEVEL_OPTIMISTICALLY_CONFIRMED: return "optimistically_confirmed"; - case FD_GUI_SLOT_LEVEL_ROOTED: return "rooted"; - case FD_GUI_SLOT_LEVEL_FINALIZED: return "finalized"; - default: return "unknown"; - } - } - if( FD_UNLIKELY( slot->skip==FD_GUI_SKIP_STATUS_FINALIZED && slot->level>=FD_GUI_SLOT_LEVEL_ROOTED ) ) return "skipped"; if( FD_UNLIKELY( slot->levelcompleted_time==LONG_MAX ) ) return "incomplete"; @@ -2173,10 +2066,8 @@ fd_gui_resolve_slot_voter_state( fd_gui_t * gui, ulong _slot, fd_gui_slot_t const * slot ) { uchar state = slot->is_voter; - if( FD_UNLIKELY( gui->summary.is_alpenglow ) ) { - uchar epoch_state = fd_gui_slot_voter_state( gui, _slot ); - if( FD_LIKELY( epoch_state!=FD_GUI_IS_VOTER_UNKNOWN ) ) state = epoch_state; - } + uchar epoch_state = fd_gui_slot_voter_state( gui, _slot ); + if( FD_LIKELY( epoch_state!=FD_GUI_IS_VOTER_UNKNOWN ) ) state = epoch_state; return state; } @@ -2185,15 +2076,13 @@ fd_gui_printf_slot_is_voter( fd_gui_t * gui, ulong _slot, fd_gui_slot_t const * slot ) { uchar state = fd_gui_resolve_slot_voter_state( gui, _slot, slot ); - if( FD_UNLIKELY( gui->summary.is_alpenglow && state==FD_GUI_IS_VOTER_UNKNOWN ) ) jsonp_null( gui->http, "is_voter" ); - else jsonp_bool( gui->http, "is_voter", state==FD_GUI_IS_VOTER_YES ); + if( FD_UNLIKELY( state==FD_GUI_IS_VOTER_UNKNOWN ) ) jsonp_null( gui->http, "is_voter" ); + else jsonp_bool( gui->http, "is_voter", state==FD_GUI_IS_VOTER_YES ); } static void fd_gui_printf_slot_proofs( fd_gui_t * gui, fd_gui_slot_t const * slot ) { - if( FD_LIKELY( !gui->summary.is_alpenglow ) ) return; - switch( slot->notarization_kind ) { case FD_GUI_AG_NOTAR_REGULAR: jsonp_string( gui->http, "notarization_kind", "regular" ); break; case FD_GUI_AG_NOTAR_FALLBACK: jsonp_string( gui->http, "notarization_kind", "fallback" ); break; @@ -2230,40 +2119,12 @@ fd_gui_printf_slot_proofs( fd_gui_t * gui, static void fd_gui_printf_slot_txn_counts( fd_gui_t * gui, fd_gui_slot_t const * slot ) { - if( FD_UNLIKELY( gui->summary.is_alpenglow ) ) { - if( FD_LIKELY( slot->nonvote_success!=UINT_MAX && slot->vote_success!=UINT_MAX ) ) jsonp_ulong( gui->http, "success_transaction_cnt", (ulong)slot->nonvote_success+(ulong)slot->vote_success ); - else jsonp_null ( gui->http, "success_transaction_cnt" ); - if( FD_LIKELY( slot->nonvote_failed!=UINT_MAX && slot->vote_failed!=UINT_MAX ) ) jsonp_ulong( gui->http, "failed_transaction_cnt", (ulong)slot->nonvote_failed+(ulong)slot->vote_failed ); - else jsonp_null ( gui->http, "failed_transaction_cnt" ); - return; - } - - if( FD_UNLIKELY( slot->nonvote_success==UINT_MAX ) ) jsonp_null ( gui->http, "success_nonvote_transaction_cnt" ); - else jsonp_ulong( gui->http, "success_nonvote_transaction_cnt", slot->nonvote_success ); - if( FD_UNLIKELY( slot->nonvote_failed==UINT_MAX ) ) jsonp_null ( gui->http, "failed_nonvote_transaction_cnt" ); - else jsonp_ulong( gui->http, "failed_nonvote_transaction_cnt", slot->nonvote_failed ); - if( FD_UNLIKELY( slot->vote_success==UINT_MAX ) ) jsonp_null ( gui->http, "success_vote_transaction_cnt" ); - else jsonp_ulong( gui->http, "success_vote_transaction_cnt", slot->vote_success ); - if( FD_UNLIKELY( slot->vote_failed==UINT_MAX ) ) jsonp_null ( gui->http, "failed_vote_transaction_cnt" ); - else jsonp_ulong( gui->http, "failed_vote_transaction_cnt", slot->vote_failed ); -} - -static void -fd_gui_printf_slot_skipped( fd_gui_t * gui, - fd_gui_slot_t const * slot ) { - if( FD_UNLIKELY( gui->summary.is_alpenglow ) ) return; - jsonp_bool( gui->http, "skipped", slot->skip==FD_GUI_SKIP_STATUS_FINALIZED ); -} - -static void -fd_gui_printf_slot_vote_latency( fd_gui_t * gui, - fd_gui_slot_t const * slot ) { - if( FD_UNLIKELY( gui->summary.is_alpenglow ) ) return; - if( FD_LIKELY( slot->vote_latency_exact!=FD_GUI_VOTE_LATENCY_NOT_VOTED ) ) jsonp_ulong( gui->http, "vote_latency_exact", slot->vote_latency_exact ); - else jsonp_null ( gui->http, "vote_latency_exact" ); + if( FD_LIKELY( slot->nonvote_success!=UINT_MAX && slot->vote_success!=UINT_MAX ) ) jsonp_ulong( gui->http, "success_transaction_cnt", (ulong)slot->nonvote_success+(ulong)slot->vote_success ); + else jsonp_null ( gui->http, "success_transaction_cnt" ); + if( FD_LIKELY( slot->nonvote_failed!=UINT_MAX && slot->vote_failed!=UINT_MAX ) ) jsonp_ulong( gui->http, "failed_transaction_cnt", (ulong)slot->nonvote_failed+(ulong)slot->vote_failed ); + else jsonp_null ( gui->http, "failed_transaction_cnt" ); } - void fd_gui_printf_missed_vote_history( fd_gui_t * gui, ulong epoch ) { @@ -2272,7 +2133,7 @@ fd_gui_printf_missed_vote_history( fd_gui_t * gui, jsonp_open_array( gui->http, "slot" ); fd_gui_epoch_t const * rec = fd_gui_epoch( gui, epoch ); ulong lo, hi; - if( FD_LIKELY( gui->summary.is_alpenglow && fd_gui_skipped_history_bounds( gui, rec, &lo, &hi ) ) ) { + if( FD_LIKELY( fd_gui_skipped_history_bounds( gui, rec, &lo, &hi ) ) ) { ulong run_start = ULONG_MAX; for( ulong i=lo; ivote_rewarded[ i ]==FD_GUI_VOTE_REWARDED_NO && rec->is_voter[ i ]==FD_GUI_IS_VOTER_YES; @@ -2310,7 +2171,6 @@ fd_gui_printf_slot( fd_gui_t * gui, jsonp_bool( gui->http, "mine", slot->mine ); if( FD_UNLIKELY( slot->vote_slot!=ULONG_MAX ) ) jsonp_ulong( gui->http, "vote_slot", slot->vote_slot ); else jsonp_null( gui->http, "vote_slot" ); - fd_gui_printf_slot_vote_latency( gui, slot ); fd_gui_printf_slot_is_voter( gui, _slot, slot ); if( FD_UNLIKELY( have_lmeta && lmeta->leader_start_time!=LONG_MAX ) ) jsonp_long_as_str( gui->http, "start_timestamp_nanos", lmeta->leader_start_time ); @@ -2318,7 +2178,6 @@ fd_gui_printf_slot( fd_gui_t * gui, if( FD_UNLIKELY( have_lmeta && lmeta->leader_end_time!=LONG_MAX ) ) jsonp_long_as_str( gui->http, "target_end_timestamp_nanos", lmeta->leader_end_time ); else jsonp_null ( gui->http, "target_end_timestamp_nanos" ); - fd_gui_printf_slot_skipped( gui, slot ); if( FD_UNLIKELY( duration_nanos==LONG_MAX ) ) jsonp_null( gui->http, "duration_nanos" ); else jsonp_long( gui->http, "duration_nanos", duration_nanos ); if( FD_UNLIKELY( slot->completed_time==LONG_MAX ) ) jsonp_null( gui->http, "completed_time_nanos" ); @@ -2421,7 +2280,6 @@ fd_gui_printf_slot_request( fd_gui_t * gui, jsonp_bool( gui->http, "mine", slot->mine ); if( FD_UNLIKELY( slot->vote_slot!=ULONG_MAX ) ) jsonp_ulong( gui->http, "vote_slot", slot->vote_slot ); else jsonp_null( gui->http, "vote_slot" ); - fd_gui_printf_slot_vote_latency( gui, slot ); fd_gui_printf_slot_is_voter( gui, _slot, slot ); if( FD_UNLIKELY( have_lmeta && lmeta->leader_start_time!=LONG_MAX ) ) jsonp_long_as_str( gui->http, "start_timestamp_nanos", lmeta->leader_start_time ); @@ -2429,7 +2287,6 @@ fd_gui_printf_slot_request( fd_gui_t * gui, if( FD_UNLIKELY( have_lmeta && lmeta->leader_end_time!=LONG_MAX ) ) jsonp_long_as_str( gui->http, "target_end_timestamp_nanos", lmeta->leader_end_time ); else jsonp_null ( gui->http, "target_end_timestamp_nanos" ); - fd_gui_printf_slot_skipped( gui, slot ); jsonp_string( gui->http, "level", level ); fd_gui_printf_slot_proofs( gui, slot ); if( FD_UNLIKELY( duration_nanos==LONG_MAX ) ) jsonp_null( gui->http, "duration_nanos" ); @@ -2485,7 +2342,6 @@ fd_gui_printf_slot_transactions_request( fd_gui_t * gui, jsonp_bool( gui->http, "mine", slot->mine ); if( FD_UNLIKELY( slot->vote_slot!=ULONG_MAX ) ) jsonp_ulong( gui->http, "vote_slot", slot->vote_slot ); else jsonp_null( gui->http, "vote_slot" ); - fd_gui_printf_slot_vote_latency( gui, slot ); fd_gui_printf_slot_is_voter( gui, _slot, slot ); if( FD_UNLIKELY( have_lmeta && lmeta->leader_start_time!=LONG_MAX ) ) jsonp_long_as_str( gui->http, "start_timestamp_nanos", lmeta->leader_start_time ); @@ -2493,7 +2349,6 @@ fd_gui_printf_slot_transactions_request( fd_gui_t * gui, if( FD_UNLIKELY( have_lmeta && lmeta->leader_end_time!=LONG_MAX ) ) jsonp_long_as_str( gui->http, "target_end_timestamp_nanos", lmeta->leader_end_time ); else jsonp_null ( gui->http, "target_end_timestamp_nanos" ); - fd_gui_printf_slot_skipped( gui, slot ); jsonp_string( gui->http, "level", level ); fd_gui_printf_slot_proofs( gui, slot ); if( FD_UNLIKELY( duration_nanos==LONG_MAX ) ) jsonp_null( gui->http, "duration_nanos" ); @@ -2521,7 +2376,7 @@ fd_gui_printf_slot_transactions_request( fd_gui_t * gui, jsonp_open_object( gui->http, "limits" ); jsonp_ulong( gui->http, "used_total_block_cost", scheduler_stats->limits_usage->block_cost ); - jsonp_ulong( gui->http, "used_total_vote_cost", fd_ulong_if( gui->summary.is_alpenglow, 0UL, scheduler_stats->limits_usage->vote_cost ) ); + jsonp_ulong( gui->http, "used_total_vote_cost", 0UL ); jsonp_ulong( gui->http, "used_total_bytes", scheduler_stats->limits_usage->block_data_bytes ); jsonp_ulong( gui->http, "used_total_microblocks", scheduler_stats->limits_usage->microblocks ); jsonp_open_array( gui->http, "used_account_write_costs" ); @@ -2538,7 +2393,7 @@ fd_gui_printf_slot_transactions_request( fd_gui_t * gui, jsonp_close_array( gui->http ); jsonp_ulong( gui->http, "max_total_block_cost", scheduler_stats->limits->max_cost_per_block ); - jsonp_ulong( gui->http, "max_total_vote_cost", fd_ulong_if( gui->summary.is_alpenglow, 0UL, scheduler_stats->limits->max_vote_cost_per_block ) ); + jsonp_ulong( gui->http, "max_total_vote_cost", 0UL ); jsonp_ulong( gui->http, "max_account_write_cost", scheduler_stats->limits->max_write_cost_per_acct ); jsonp_ulong( gui->http, "max_total_bytes", scheduler_stats->limits->max_data_bytes_per_block ); jsonp_ulong( gui->http, "max_total_microblocks", lmeta->max_microblocks ); @@ -2577,12 +2432,10 @@ fd_gui_printf_slot_transactions_request( fd_gui_t * gui, fd_pack_smallest_t pending_smallest = *scheduler_stats->pending_smallest; fd_pack_smallest_t pending_votes_smallest = *scheduler_stats->pending_votes_smallest; - if( FD_UNLIKELY( gui->summary.is_alpenglow ) ) { - pending_smallest.cus = fd_ulong_min( pending_smallest.cus, pending_votes_smallest.cus ); - pending_smallest.bytes = fd_ulong_min( pending_smallest.bytes, pending_votes_smallest.bytes ); - pending_votes_smallest.cus = ULONG_MAX; - pending_votes_smallest.bytes = ULONG_MAX; - } + pending_smallest.cus = fd_ulong_min( pending_smallest.cus, pending_votes_smallest.cus ); + pending_smallest.bytes = fd_ulong_min( pending_smallest.bytes, pending_votes_smallest.bytes ); + pending_votes_smallest.cus = ULONG_MAX; + pending_votes_smallest.bytes = ULONG_MAX; if( FD_LIKELY( pending_smallest.cus!=ULONG_MAX ) ) jsonp_ulong( gui->http, "pending_smallest_cost", pending_smallest.cus ); else jsonp_null( gui->http, "pending_smallest_cost" ); @@ -2698,12 +2551,6 @@ fd_gui_printf_slot_transactions_request( fd_gui_t * gui, jsonp_open_array( gui->http, "txn_from_bundle" ); for( ulong i=0UL; ihttp, NULL, joined[ i ].start->flags & FD_GUI_TXN_FLAGS_FROM_BUNDLE ); jsonp_close_array( gui->http ); - /* Tower-only: Alpenglow has no vote transactions to flag. */ - if( FD_LIKELY( !gui->summary.is_alpenglow ) ) { - jsonp_open_array( gui->http, "txn_is_simple_vote" ); - for( ulong i=0UL; ihttp, NULL, joined[ i ].start->flags & FD_GUI_TXN_FLAGS_IS_SIMPLE_VOTE ); - jsonp_close_array( gui->http ); - } jsonp_open_array( gui->http, "txn_bank_idx" ); for( ulong i=0UL; ihttp, NULL, joined[ i ].end->bank_idx ); jsonp_close_array( gui->http ); @@ -2813,7 +2660,6 @@ fd_gui_printf_slot_request_detailed( fd_gui_t * gui, jsonp_bool( gui->http, "mine", slot->mine ); if( FD_UNLIKELY( slot->vote_slot!=ULONG_MAX ) ) jsonp_ulong( gui->http, "vote_slot", slot->vote_slot ); else jsonp_null( gui->http, "vote_slot" ); - fd_gui_printf_slot_vote_latency( gui, slot ); fd_gui_printf_slot_is_voter( gui, _slot, slot ); if( FD_UNLIKELY( have_lmeta && lmeta->leader_start_time!=LONG_MAX ) ) jsonp_long_as_str( gui->http, "start_timestamp_nanos", lmeta->leader_start_time ); @@ -2821,7 +2667,6 @@ fd_gui_printf_slot_request_detailed( fd_gui_t * gui, if( FD_UNLIKELY( have_lmeta && lmeta->leader_end_time!=LONG_MAX ) ) jsonp_long_as_str( gui->http, "target_end_timestamp_nanos", lmeta->leader_end_time ); else jsonp_null ( gui->http, "target_end_timestamp_nanos" ); - fd_gui_printf_slot_skipped( gui, slot ); jsonp_string( gui->http, "level", level ); fd_gui_printf_slot_proofs( gui, slot ); if( FD_UNLIKELY( duration_nanos==LONG_MAX ) ) jsonp_null( gui->http, "duration_nanos" ); @@ -2901,16 +2746,11 @@ fd_gui_printf_slot_request_detailed( fd_gui_t * gui, fd_gui_scheduler_counts_t const * cur = (fd_gui_scheduler_counts_t const *)it.rec; if( FD_UNLIKELY( cur->sample_time_nssample_time_ns>leader_end_time ) ) continue; - ulong regular = cur->regular; - ulong votes = cur->votes; - if( FD_UNLIKELY( gui->summary.is_alpenglow ) ) { - regular += votes; - votes = 0UL; - } + ulong regular = cur->regular + cur->votes; jsonp_open_object( gui->http, NULL ); jsonp_long_as_str( gui->http, "timestamp_nanos", cur->sample_time_ns ); jsonp_ulong ( gui->http, "regular", regular ); - jsonp_ulong ( gui->http, "votes", votes ); + jsonp_ulong ( gui->http, "votes", 0UL ); jsonp_ulong ( gui->http, "conflicting", cur->conflicting ); jsonp_ulong ( gui->http, "bundles", cur->bundles ); jsonp_close_object( gui->http ); @@ -3495,16 +3335,13 @@ static int fd_gui_timeline_slot_skipped( fd_gui_t * gui, ulong slot ) { if( gui->summary.slot_rooted!=ULONG_MAX && slotsummary.slot_rooted ) { - /* Root advancement persists skips in both consensus modes. */ fd_gui_epoch_t const * epoch = fd_gui_get_epoch_by_slot( gui, slot ); if( epoch && slot>=epoch->start_slot && slot-epoch->start_slotslot_cnt && epoch->skipped[ slot-epoch->start_slot ] ) return 1; return fd_gui_slot_get_canon_safe( gui, slot )->skip==FD_GUI_SKIP_STATUS_FINALIZED; } - if( gui->summary.is_alpenglow ) return 0; - - return fd_gui_slot_is_skipped( gui, gui->summary.slot_rooted, gui->summary.slot_tower, gui->summary.slot_tower_bank_seq, slot ); + return 0; } #define SORT_NAME fd_gui_timeline_event_sort diff --git a/src/disco/gui/fd_gui_printf.h b/src/disco/gui/fd_gui_printf.h index 259f8f6f51e..a88b69a1951 100644 --- a/src/disco/gui/fd_gui_printf.h +++ b/src/disco/gui/fd_gui_printf.h @@ -16,13 +16,11 @@ void fd_gui_printf_vote_key( fd_gui_t * gui ); void fd_gui_printf_startup_time_nanos( fd_gui_t * gui ); void fd_gui_printf_server_time_nanos( fd_gui_t * gui, long now ); void fd_gui_printf_vote_state( fd_gui_t * gui ); -void fd_gui_printf_vote_distance( fd_gui_t * gui ); void fd_gui_printf_turbine_slot( fd_gui_t * gui ); void fd_gui_printf_repair_slot( fd_gui_t * gui ); void fd_gui_printf_slot_caught_up( fd_gui_t * gui ); void fd_gui_printf_skipped_history( fd_gui_t * gui, ulong epoch ); void fd_gui_printf_skipped_history_cluster( fd_gui_t * gui, ulong epoch ); -void fd_gui_printf_late_votes_history( fd_gui_t * gui ); void fd_gui_printf_missed_vote_history( fd_gui_t * gui, ulong epoch ); void fd_gui_printf_tps_history( fd_gui_t * gui ); void fd_gui_printf_boot_progress( fd_gui_t * gui ); @@ -37,14 +35,12 @@ void fd_gui_printf_estimated_slot_duration_nanos( fd_gui_t * gui ); void fd_gui_printf_root_slot( fd_gui_t * gui ); void fd_gui_printf_finalized_slot( fd_gui_t * gui ); void fd_gui_printf_notarized_slot( fd_gui_t * gui ); -void fd_gui_printf_optimistically_confirmed_slot( fd_gui_t * gui ); void fd_gui_printf_completed_slot( fd_gui_t * gui ); void fd_gui_printf_estimated_slot( fd_gui_t * gui ); void fd_gui_printf_estimated_tps( fd_gui_t * gui ); void fd_gui_printf_shred_updates( fd_gui_t * gui, long after_ns, long before_ns ); void fd_gui_printf_catch_up_history( fd_gui_t * gui ); void fd_gui_printf_vote_slot( fd_gui_t * gui ); -void fd_gui_peers_printf_vote_slot( fd_gui_peers_ctx_t * peers ); void fd_gui_printf_reset_slot( fd_gui_t * gui ); void fd_gui_printf_storage_slot( fd_gui_t * gui ); void fd_gui_printf_active_fork_cnt( fd_gui_t * gui ); diff --git a/src/disco/gui/fd_gui_tile.c b/src/disco/gui/fd_gui_tile.c index c992028e9f8..b9ade1fc77c 100644 --- a/src/disco/gui/fd_gui_tile.c +++ b/src/disco/gui/fd_gui_tile.c @@ -221,7 +221,6 @@ during_housekeeping( fd_gui_ctx_t * ctx ) { if( FD_UNLIKELY( fd_keyswitch_state_query( ctx->keyswitch )==FD_KEYSWITCH_STATE_SWITCH_PENDING ) ) { fd_gui_set_identity( ctx->gui, ctx->keyswitch->bytes ); - if( FD_LIKELY( !ctx->gui->summary.is_alpenglow ) ) fd_gui_peers_handle_identity_change( ctx->peers ); fd_keyswitch_state( ctx->keyswitch, FD_KEYSWITCH_STATE_COMPLETED ); } } @@ -377,7 +376,6 @@ during_frag( fd_gui_ctx_t * ctx, if( FD_LIKELY( sig!=REPLAY_SIG_SLOT_COMPLETED && sig!=REPLAY_SIG_BECAME_LEADER && sig!=REPLAY_SIG_ROOT_ADVANCED && - sig!=REPLAY_SIG_OC_ADVANCED && sig!=REPLAY_SIG_TXN_EXECUTED ) ) return; } @@ -444,21 +442,6 @@ after_frag( fd_gui_ctx_t * ctx, long tsorig_ns = fd_clock_tile_tickcomp_to_wallclock( ctx->clock, tsorig ); long tspub_ns = fd_clock_tile_tickcomp_to_wallclock( ctx->clock, tspub ); fd_gui_handle_exec_txn_done( ctx->gui, msg->txn_exec->slot, msg->txn_exec->start_shred_idx, msg->txn_exec->end_shred_idx, tsorig_ns, tspub_ns, fd_clock_tile_now( ctx->clock ) ); - - int txn_succeeded = msg->txn_exec->is_committable && !msg->txn_exec->is_fees_only && !msg->txn_exec->txn_err; - if( FD_UNLIKELY( msg->txn_exec->vote.slot!=ULONG_MAX && txn_succeeded ) ) { - int vote_acct_is_us = !ctx->gui->summary.has_vote_key || !memcmp( ctx->gui->summary.vote_key->uc, msg->txn_exec->vote.vote_acct->uc, sizeof(fd_pubkey_t) ); - int is_us = !ctx->gui->summary.is_alpenglow && vote_acct_is_us && !memcmp( ctx->gui->summary.identity_key->uc, msg->txn_exec->vote.identity->uc, sizeof(fd_pubkey_t) ); - fd_gui_peers_handle_vote( ctx->peers, - msg->txn_exec->vote.vote_acct, - msg->txn_exec->vote.slot, - is_us ); - if( FD_UNLIKELY( is_us ) ) { - for( ulong i=0UL; itxn_exec->vote.vote_slot_cnt; i++ ) { - fd_gui_stage_landed_vote( ctx->gui, msg->txn_exec->slot, msg->txn_exec->bank_seq, msg->txn_exec->vote.vote_slots[ i ] ); - } - } - } } break; @@ -490,18 +473,13 @@ after_frag( fd_gui_ctx_t * ctx, if( FD_UNLIKELY( sig==REPLAY_SIG_SLOT_COMPLETED ) ) { fd_replay_slot_completed_t const * slot_completed = (fd_replay_slot_completed_t const *)src; - if( FD_LIKELY( !ctx->gui->summary.is_alpenglow ) ) fd_gui_peers_update_delinquency( ctx->peers, fd_clock_tile_now( ctx->clock ) ); - - fd_gui_handle_replay_update( ctx->gui, slot_completed, ctx->peers->slot_voted, fd_clock_tile_now( ctx->clock ) ); + fd_gui_handle_replay_update( ctx->gui, slot_completed, fd_clock_tile_now( ctx->clock ) ); } else if( FD_UNLIKELY( sig==REPLAY_SIG_BECAME_LEADER ) ) { fd_became_leader_t * became_leader = (fd_became_leader_t *)src; fd_gui_became_leader( ctx->gui, became_leader->slot, became_leader->slot_start_ns, became_leader->slot_end_ns, became_leader->limits.slot_max_cost, became_leader->max_microblocks_in_slot, became_leader->bank_seq ); } else if( FD_UNLIKELY( sig==REPLAY_SIG_ROOT_ADVANCED ) ) { fd_replay_root_advanced_t const * ra = (fd_replay_root_advanced_t const *)src; fd_gui_handle_root_advanced( ctx->gui, ra->slot, ra->bank_seq, fd_clock_tile_now( ctx->clock ) ); - } else if( FD_UNLIKELY( sig==REPLAY_SIG_OC_ADVANCED ) ) { - fd_replay_oc_advanced_t const * oc = (fd_replay_oc_advanced_t const *)src; - fd_gui_handle_oc_advanced( ctx->gui, oc->slot, oc->bank_seq, fd_clock_tile_now( ctx->clock ) ); } else if( FD_LIKELY( sig==REPLAY_SIG_TXN_EXECUTED ) ) { if( FD_UNLIKELY( sz!=sizeof(fd_replay_txn_executed_t) ) ) FD_LOG_ERR(( "invalid replay transaction message size %lu", sz )); fd_gui_handle_replay_txn( ctx->gui, (fd_replay_txn_executed_t const *)src, fd_clock_tile_now( ctx->clock ) ); @@ -965,7 +943,7 @@ unprivileged_init( fd_topo_t const * topo, accdb_shmem = fd_accdb_shmem_join( accdb_shmem_raw ); FD_TEST( accdb_shmem ); } - ctx->gui = fd_gui_join( fd_gui_new( _gui, ctx->gui_server, fd_version_cstr, tile->gui.cluster, ctx->identity_key, ctx->has_vote_key, ctx->vote_key->uc, 1, tile->gui.max_live_slots, tile->gui.max_txn_per_slot, ctx->snapshots_enabled, tile->gui.is_voting, tile->gui.schedule_strategy, tile->gui.wfs_bank_hash, tile->gui.expected_shred_version, tile->gui.wfs_slot, tile->gui.accounts_database_path, tile->gui.gui_database_path, ctx->db, ctx->topo, accdb_shmem, fd_clock_tile_now( ctx->clock ) ) ); + ctx->gui = fd_gui_join( fd_gui_new( _gui, ctx->gui_server, fd_version_cstr, tile->gui.cluster, ctx->identity_key, ctx->has_vote_key, ctx->vote_key->uc, tile->gui.max_live_slots, tile->gui.max_txn_per_slot, ctx->snapshots_enabled, tile->gui.is_voting, tile->gui.schedule_strategy, tile->gui.wfs_bank_hash, tile->gui.expected_shred_version, tile->gui.wfs_slot, tile->gui.accounts_database_path, tile->gui.gui_database_path, ctx->db, ctx->topo, accdb_shmem, fd_clock_tile_now( ctx->clock ) ) ); FD_TEST( ctx->gui ); FD_TEST( ctx->db ); diff --git a/src/disco/gui/test_gui_hist_evict.c b/src/disco/gui/test_gui_hist_evict.c index c443fd6c742..3afee891880 100644 --- a/src/disco/gui/test_gui_hist_evict.c +++ b/src/disco/gui/test_gui_hist_evict.c @@ -417,7 +417,7 @@ test_resident_meta_mutation_survives_evict( fd_gui_t * gui ) { FD_TEST( rec ); rec->my_total_slots = 7UL; rec->my_skipped_slots = 3UL; - rec->latency_exact[ 0 ] = 2; + rec->is_voter[ 0 ] = FD_GUI_IS_VOTER_YES; rec->rankings->largest_tips[ 0 ].slot = RM_NEW_START + 4UL; rec->rankings->largest_tips[ 0 ].value = 12345UL; @@ -441,7 +441,7 @@ test_resident_meta_mutation_survives_evict( fd_gui_t * gui ) { FD_TEST( rec3->epoch==key->epoch ); FD_TEST( rec3->my_total_slots==7UL ); FD_TEST( rec3->my_skipped_slots==3UL ); - FD_TEST( rec3->latency_exact[ 0 ]==2 ); + FD_TEST( rec3->is_voter[ 0 ]==FD_GUI_IS_VOTER_YES ); FD_TEST( rec3->rankings->largest_tips[ 0 ].slot==RM_NEW_START + 4UL ); FD_TEST( rec3->rankings->largest_tips[ 0 ].value==12345UL ); @@ -493,6 +493,9 @@ store_open( test_store_t * s, ulong map_bytes, int instance ) { FD_TEST( s->gui ); memset( s->gui, 0, fd_gui_footprint( 1UL, 1UL, 1UL ) ); s->gui->slot_txn_scratch.max = 1UL; + static fd_gui_ag_slot_t ag_slot[ 1 ] = {{ .slot = ULONG_MAX, .bank_seq = ULONG_MAX }}; + s->gui->ag.slot = ag_slot; + s->gui->ag.slot_cnt = 1UL; s->db_mem = aligned_alloc( fd_gui_store_align(), fd_ulong_align_up( fd_gui_store_footprint( map_bytes, fd_gui_hist_db_cnt(), fd_gui_hist_db_descs( TEST_STORE_BYTES ) ), fd_gui_store_align() ) ); @@ -1107,16 +1110,20 @@ test_timeline_shreds( fd_gui_t * gui ) { long lo, hi; FD_TEST( fd_gui_event_bounds( gui, FD_GUI_HIST_SHRED_EVENTS, &lo, &hi ) ); FD_TEST( lo<=event_ns && event_nssummary.slot_rooted = roots[ i ]; FD_TEST( !fd_gui_printf_timeline_query_shreds( gui, lo, hi, 7UL ) ); - check_response( gui, "{\"topic\":\"timeline\",\"key\":\"query_shreds\",\"id\":7,\"value\":{\"granularity\":\"shred\"," - "\"available_start_ns\":\"99000000000\",\"available_end_ns\":\"102000000000\"," - "\"reference_slot\":101,\"reference_ts\":\"101999999999\",\"slot_delta\":[0,1,12,0],\"idx\":[0,0,0,0]," - "\"event\":[0,0,0,0],\"event_ts_delta\":[\"0\",\"0\",\"0\",\"0\"],\"skipped\":[0]}}" ); + char expected[ 512 ]; + fd_cstr_printf_check( expected, sizeof(expected), NULL, + "{\"topic\":\"timeline\",\"key\":\"query_shreds\",\"id\":7,\"value\":{\"granularity\":\"shred\"," + "\"available_start_ns\":\"99000000000\",\"available_end_ns\":\"102000000000\"," + "\"reference_slot\":101,\"reference_ts\":\"101999999999\",\"slot_delta\":[0,1,12,0],\"idx\":[0,0,0,0]," + "\"event\":[0,0,0,0],\"event_ts_delta\":[\"0\",\"0\",\"0\",\"0\"],\"skipped\":%s}}", skipped[ i ] ); + check_response( gui, expected ); } if( !flushed ) FD_TEST( fd_gui_shred_flush( gui, sec_ns( 101UL ) ) ); } From 17068014747f987fdf9960efa4544dbe944db938 Mon Sep 17 00:00:00 2001 From: Richard Patel Date: Wed, 21 Oct 2026 18:45:45 +0000 Subject: [PATCH 12/13] ci: remove solcap test --- .github/workflows/tests.yml | 7 -- config/everything.mk | 5 - contrib/test/run_solcap_tests.sh | 200 ------------------------------- 3 files changed, 212 deletions(-) delete mode 100755 contrib/test/run_solcap_tests.sh diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 6a866726b76..7f3307fe70e 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -113,10 +113,3 @@ jobs: source /opt/${{ matrix.compiler }}/${{ matrix.compiler }}-${{ matrix.compiler-version }}/activate sudo rm -rf "$HOME/.firedancer/fd1" make run-integration-test - - - 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 diff --git a/config/everything.mk b/config/everything.mk index 5eb91106b0b..18859ce09b9 100644 --- a/config/everything.mk +++ b/config/everything.mk @@ -673,11 +673,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' -print0 | xargs -0 -n 1 $(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 815c6195336..00000000000 --- a/contrib/test/run_solcap_tests.sh +++ /dev/null @@ -1,200 +0,0 @@ -#!/bin/bash -set -eou pipefail - -source contrib/test/ledger_common.sh - -DUMP=${DUMP:="./dump"} -OBJDIR=${OBJDIR:-$(make --silent --no-print-directory objdir 2>/dev/null || true)} -: "${OBJDIR:?cannot determine OBJDIR (make objdir failed)}" -SKIP_INGEST=${SKIP_INGEST:-0} - -LEDGER="mainnet-424669000-solcap-v4.2.0-beta.1-vat-untouched" -REDOWNLOAD=1 - -WATCH=( ) -if [[ -n "${CI:-}" ]]; then - WATCH=( "--no-watch" ) -fi - -BACKTEST_LOG="/tmp/ledger_log_solcap" - -# Surface failures: with `set -e` a failing command aborts the script with -# no context, and several steps below capture output to log files. This -# trap prints the failing command, line, and exit code, and dumps the -# backtest log tail so the real error is visible in CI instead of a bare -# non-zero exit. -on_err() { - local ec=$? - echo "::error::run_solcap_tests.sh failed at line ${BASH_LINENO[0]} (exit ${ec}): ${BASH_COMMAND}" >&2 - if [[ -f "$BACKTEST_LOG" ]]; then - echo "----- last 100 lines of ${BACKTEST_LOG} -----" >&2 - tail -n 100 "$BACKTEST_LOG" >&2 || true - echo "---------------------------------------------" >&2 - fi - exit "$ec" -} -trap on_err ERR - -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 ]]; then - download_and_extract_ledger -fi - -rm -f $DUMP/$LEDGER/accounts.db - -# fd requires the snapshots path to not be group/world accessible. -chmod -R 0700 $DUMP/$LEDGER - -# Clone and build solcap-tools. Capture output to a log and only print -# it on failure, so a broken clone/fetch/checkout/build surfaces its -# error in CI instead of failing silently. -echo "Building solcap-tools ..." -ORIG_DIR=$(pwd) -SOLCAP_BUILD_LOG="$(mktemp)" -run_quiet() { - if ! "$@" > "$SOLCAP_BUILD_LOG" 2>&1; then - echo "::error::solcap-tools step failed: $*" >&2 - cat "$SOLCAP_BUILD_LOG" >&2 - exit 1 - fi -} -cd $DUMP -if [ ! -d "solcap-tools" ]; then - run_quiet git clone https://github.com/firedancer-io/solcap-tools.git -fi -cd solcap-tools -run_quiet git fetch -run_quiet git checkout 44dc8e2a5c65435daf57b009c108234f316224f7 -run_quiet cargo build --release -cd "$ORIG_DIR" - -export ledger_dir=$(realpath $DUMP/$LEDGER) -export dump_dir=$(realpath $DUMP) - -cat > "$DUMP/mainnet-424669000-solcap_current.toml" << EOF -telemetry = false - -[snapshots] - incremental_snapshots = false - [snapshots.sources] - servers = [] - [snapshots.sources.gossip] - allow_any = false - allow_list = [] -[layout] - shred_tile_count = 4 - verify_tile_count = 2 - execrp_tile_count = 6 -[tiles] - [tiles.gui] - enabled = false - [tiles.rpc] - enabled = false -[accounts] - max_accounts = 4000000 -[runtime] - max_live_slots = 64 -[log] - level_stderr = "NOTICE" - path = "/tmp/ledger_log_solcap" -[paths] - snapshots = "${ledger_dir}" - accounts = "${ledger_dir}/accounts.db" - genesis = "${ledger_dir}/genesis.bin" -[capture] - solcap_capture = "${dump_dir}/mainnet-424669000.solcap" -[gossip] - entrypoints = [ "0.0.0.0:1" ] -[development.ledger_input] - format = "pcap" - path = "${ledger_dir}/shreds.pcapng.zst" - end_slot = 424669025 -EOF - -echo "Running firedancer-dev configure fini all ..." -$OBJDIR/bin/firedancer-dev configure fini all - -convert_rocksdb_to_shredcap() { - local zst_tmp=$DUMP/$LEDGER/shreds.pcapng.zst.tmp - rm -f "$zst_tmp" - if ! contrib/blockstore/blockstore2shredcap --rocksdb $DUMP/$LEDGER/rocksdb --out "$zst_tmp" --zstd; then - rm -f "$zst_tmp" - return 1 - fi - mv "$zst_tmp" $DUMP/$LEDGER/shreds.pcapng.zst -} - -if [[ ! -e $DUMP/$LEDGER/shreds.pcapng.zst ]]; then - if ! make -B -C contrib/blockstore blockstore2shredcap; then - echo "failed to build contrib/blockstore/blockstore2shredcap" - exit 1 - fi - - echo "Running blockstore2shredcap ..." - if ! convert_rocksdb_to_shredcap; then - # A cached ledger may have a corrupt rocksdb (eg. damaged by a converter - # version that deleted unopened column families); re-download once. - echo "conversion failed; re-downloading ledger and retrying" - rm -rf $DUMP/$LEDGER - download_and_extract_ledger - convert_rocksdb_to_shredcap - fi -fi - -echo "Running backtest (full log at ${BACKTEST_LOG}) ..." -$OBJDIR/bin/firedancer-dev backtest --config $DUMP/mainnet-424669000-solcap_current.toml "${WATCH[@]}" - -# Run solcap-tools diff and check the summary for zero differences -echo "Running solcap-tools diff ..." -DIFF_OUTPUT=$($DUMP/solcap-tools/target/release/solcap-tools diff $DUMP/mainnet-424669000.solcap $DUMP/$LEDGER/ledger_tool/bank_hash_details/ -v 5) -echo "$DIFF_OUTPUT" - -SUMMARY=$(echo "$DIFF_OUTPUT" | tail -3) -DIFFERING_SLOTS=$(echo "$SUMMARY" | grep -ioP 'Differing Slots: \K\d+' || true) -DIFFERING_ACCOUNTS=$(echo "$SUMMARY" | grep -ioP 'Differing Accounts: \K\d+' || true) - -# If the summary did not parse, treat that as a failure rather than -# silently passing (empty != "0"). -if [[ -z "$DIFFERING_SLOTS" || -z "$DIFFERING_ACCOUNTS" ]]; then - echo -e "\033[0;31mFAIL\033[0m Could not parse solcap diff summary (Differing Slots='${DIFFERING_SLOTS}', Differing Accounts='${DIFFERING_ACCOUNTS}'). Full diff output above." >&2 - exit 1 -fi - -if [[ "$DIFFERING_SLOTS" != "0" || "$DIFFERING_ACCOUNTS" != "0" ]]; then - echo -e "\033[0;31mFAIL\033[0m Solcap diff found mismatches! Differing Slots: $DIFFERING_SLOTS, Differing Accounts: $DIFFERING_ACCOUNTS" >&2 - exit 1 -fi - -echo "Solcap diff clean: 0 differing slots, 0 differing accounts" -exit 0 From 71e0366d6d126f458b357efe738ffcea3950829c Mon Sep 17 00:00:00 2001 From: Richard Patel Date: Wed, 21 Oct 2026 22:27:13 +0000 Subject: [PATCH 13/13] remove PoH hashing --- book/api/metrics-generated.md | 3 +- src/app/firedancer/config/default.toml | 25 -- src/app/shared/fd_config.h | 1 - src/app/shared/fd_config_json.c | 3 +- src/app/shared/fd_config_parse.c | 2 +- .../shared_dev/commands/configure/genesis.c | 62 +--- src/ballet/poh/Local.mk | 4 - src/ballet/poh/fd_poh.c | 19 -- src/ballet/poh/fd_poh.h | 34 -- src/ballet/poh/test_poh.c | 213 ------------ src/ballet/sha256/fd_sha256.c | 319 +----------------- src/ballet/sha256/fd_sha256.h | 57 ---- src/ballet/sha256/fd_sha256_arm.c | 173 ---------- src/ballet/sha256/fd_sha256_batch_avx512.c | 152 --------- src/ballet/sha256/fd_sha256_riscv.c | 45 --- src/ballet/sha256/test_sha256.c | 127 ------- src/disco/events/schema/metrics_execrp.json | 4 +- src/disco/metrics/generated/fd_metrics_all.c | 1 - .../metrics/generated/fd_metrics_execrp.h | 10 +- .../metrics/generated/fd_metrics_sample_gen.c | 5 +- src/disco/metrics/metrics.xml | 4 +- src/discof/execrp/fd_execrp_tile.c | 14 - src/discof/execrp/test_execrp_tile.c | 32 -- src/discof/genesis/fd_genesi_tile.c | 4 + src/discof/replay/fd_execrp.h | 18 - src/discof/replay/fd_replay_tile.c | 27 +- src/discof/replay/fd_sched.c | 237 +++---------- src/discof/replay/fd_sched.h | 31 +- src/discof/replay/fuzz_sched_rdisp.c | 125 +------ src/discof/replay/test_sched.c | 55 +-- src/flamenco/runtime/fd_runtime.c | 3 +- 31 files changed, 80 insertions(+), 1729 deletions(-) delete mode 100644 src/ballet/poh/Local.mk delete mode 100644 src/ballet/poh/fd_poh.c delete mode 100644 src/ballet/poh/fd_poh.h delete mode 100644 src/ballet/poh/test_poh.c diff --git a/book/api/metrics-generated.md b/book/api/metrics-generated.md index 6748fd35e06..ce9c3d1574e 100644 --- a/book/api/metrics-generated.md +++ b/book/api/metrics-generated.md @@ -1191,7 +1191,7 @@ | Metric | Type | Description | |--------|------|-------------| -| execrp_​is_​processing | gauge | Whether the tile is currently processing a task (executing, sigverifying, or PoH hashing) | +| execrp_​is_​processing | gauge | Whether the tile is currently processing a task (executing, sigverifying, or verifying certificates) | | execrp_​txn_​processed
    {txn_​result="success"} | counter | Transactions loaded and executed, by result (Transaction executed successfully) | | execrp_​txn_​processed
    {txn_​result="instruction_​error"} | counter | Transactions loaded and executed, by result (An error occurred while processing an instruction) | | execrp_​txn_​processed
    {txn_​result="account_​not_​found"} | counter | Transactions loaded and executed, by result (The transaction fee payer address was not found) | @@ -1224,7 +1224,6 @@ | execrp_​txn_​processed_​by_​version
    {txn_​version="v0"} | counter | Transactions processed, by transaction version (Version 0 transaction format) | | execrp_​txn_​processed_​by_​version
    {txn_​version="v1"} | counter | Transactions processed, by transaction version (Version 1 transaction format) | | execrp_​signature_​checked | counter | Ed25519 signatures checked, including failures | -| execrp_​poh_​hash_​computed | counter | PoH SHA-256 iterations computed | | execrp_​instruction_​executed | counter | Top-level instructions executed | | execrp_​cpi_​executed | counter | Cross-program invocations executed | | execrp_​account_​lthash_​skipped | counter | Committed writable accounts left byte-identical by their transaction, so their lthash update was skipped | diff --git a/src/app/firedancer/config/default.toml b/src/app/firedancer/config/default.toml index 780b0d12050..2c74b3deb03 100644 --- a/src/app/firedancer/config/default.toml +++ b/src/app/firedancer/config/default.toml @@ -2099,31 +2099,6 @@ telemetry = true # Must be in [1, 4055]. max_file_size_mib = 16 - # When creating a new chain from genesis during development, - # this option can be used to specify the number of hashes in - # each tick of the proof history component. - # - # A value of one is the default, and will be used to mean that - # the proof of history component will run in low power mode, - # and use one hash per tick. This is equivalent to a value of - # "sleep" when providing hashes-per-tick to the Agave - # genesis. - # - # A value of zero means the genesis will automatically determine - # the number of hashes in each tick based on how many hashes - # the generating computer can do in the target tick duration - # specified below. - # - # This value specifies the initial value for the chain in the - # genesis, but it might be overridden at runtime if the related - # features which increase this value are enabled. The features - # are named like `update_hashes_per_tick2`. - # - # A value of 62,500 is the same as mainnet-beta, devnet, and - # testnet, following activation of the `update_hashes_per_tick6` - # feature. - hashes_per_tick = 62_500 - # How long each tick of the proof of history component should # take, in microseconds. This value specifies the initial value # of the chain and it will not change at runtime. The default diff --git a/src/app/shared/fd_config.h b/src/app/shared/fd_config.h index 86a4126188f..71bff92424d 100644 --- a/src/app/shared/fd_config.h +++ b/src/app/shared/fd_config.h @@ -296,7 +296,6 @@ struct fd_config { } gossip; struct { - ulong hashes_per_tick; ulong target_tick_duration_micros; ulong ticks_per_slot; ulong fund_initial_accounts; diff --git a/src/app/shared/fd_config_json.c b/src/app/shared/fd_config_json.c index 56129762d1a..b0274a823ae 100644 --- a/src/app/shared/fd_config_json.c +++ b/src/app/shared/fd_config_json.c @@ -12,7 +12,7 @@ or knowingly skipped) before the constant is bumped. String keys of the user's own file are separately forced through the classification lists below. */ -FD_STATIC_ASSERT( sizeof(fd_config_t)==24458368UL, update_fd_config_to_json_for_the_layout_change ); +FD_STATIC_ASSERT( sizeof(fd_config_t)==24458360UL, update_fd_config_to_json_for_the_layout_change ); #define REDACTED "[redacted]" @@ -469,7 +469,6 @@ fd_config_to_json( fd_config_t const * config, jw_bool( &w, "allow_private_address", config->development.gossip.allow_private_address ); jw_obj_close( &w ); jw_obj_open( &w, "genesis" ); - jw_ulong( &w, "hashes_per_tick", config->development.genesis.hashes_per_tick ); jw_ulong( &w, "target_tick_duration_micros", config->development.genesis.target_tick_duration_micros ); jw_ulong( &w, "ticks_per_slot", config->development.genesis.ticks_per_slot ); jw_ulong( &w, "fund_initial_accounts", config->development.genesis.fund_initial_accounts ); diff --git a/src/app/shared/fd_config_parse.c b/src/app/shared/fd_config_parse.c index ad342d3494a..246f0a5a5a4 100644 --- a/src/app/shared/fd_config_parse.c +++ b/src/app/shared/fd_config_parse.c @@ -255,7 +255,6 @@ fd_config_extract_pod( uchar * pod, CFG_POP ( bool, development.gossip.allow_private_address ); - CFG_POP ( ulong, development.genesis.hashes_per_tick ); CFG_POP ( ulong, development.genesis.target_tick_duration_micros ); CFG_POP ( ulong, development.genesis.ticks_per_slot ); CFG_POP ( ulong, development.genesis.fund_initial_accounts ); @@ -338,6 +337,7 @@ fd_config_extract_pod( uchar * pod, CFG_DEPRECATED( tiles.tower.write_vote_history_file ); CFG_DEPRECATED( tiles.txsend.txsend_src_port ); CFG_DEPRECATED( consensus.alpenglow ); + CFG_DEPRECATED( development.genesis.hashes_per_tick ); # undef CFG_DEPRECATED diff --git a/src/app/shared_dev/commands/configure/genesis.c b/src/app/shared_dev/commands/configure/genesis.c index be33ec2f2fd..a7fef0c0c86 100644 --- a/src/app/shared_dev/commands/configure/genesis.c +++ b/src/app/shared_dev/commands/configure/genesis.c @@ -3,7 +3,6 @@ #include "../bench/bench.h" #include "../../../platform/fd_file_util.h" -#include "../../../../ballet/poh/fd_poh.h" #include "../../../../disco/keyguard/fd_keyguard_bls.h" #include "../../../../disco/keyguard/fd_keyload.h" #include "../../../../discof/genesis/genesis_hash.h" @@ -82,35 +81,6 @@ default_enable_features( fd_features_t * features ) { features->disable_fees_sysvar = 0UL; } -/* estimate_hashes_per_tick approximates the PoH hashrate of the current - tile. Spins PoH hashing for estimate_dur_ns nanoseconds. Returns - the hashes per tick achieved, where tick_dur_us is the target tick - duration in microseconds. Assumes that the estimate duration - is larger than the tick duration. */ - -static ulong -estimate_hashes_per_tick( ulong tick_dur_us, - ulong estimate_dur_ns ) { - ulong const batch = 1UL<<20; - long const deadline = fd_log_wallclock() + (long)estimate_dur_ns; - - uchar poh_hash[ 32 ] = {0}; - ulong hash_cnt = 0UL; - do { - fd_poh_append( poh_hash, batch ); - hash_cnt += batch; - } while( fd_log_wallclock() < deadline ); - - double hash_cnt_dbl = (double)hash_cnt; - double tick_cnt_dbl = (double)estimate_dur_ns / ( (double)tick_dur_us * 1000.0 ); - if( tick_cnt_dbl < 1.0 ) return 0UL; - - /* Apply 50% factor to the maximum machine hash rate. */ - double hashes_per_tick = hash_cnt_dbl / tick_cnt_dbl / 2.0; - return (ulong)hashes_per_tick; -} - - /* Create a new genesis.bin file contents into the provided blob buffer and return the size of the buffer. Will abort on error if the provided buffer is not large enough. */ @@ -165,37 +135,7 @@ create_genesis( config_t const * config, options->faucet_balance = 500000000000000000UL; options->vote_account_stake = config->development.genesis.vote_account_stake_lamports; - /* Set up PoH config */ - - if( 0UL==config->development.genesis.hashes_per_tick ) { - - /* set hashes_per_tick to whatever machine is capable of */ - ulong hashes_per_tick = - estimate_hashes_per_tick( config->development.genesis.target_tick_duration_micros, - (ulong)3e9 /* 3 seconds */ ); - - if( hashes_per_tick == 0UL ) { - FD_LOG_WARNING(( "PoH rate estimation failed. Defaulting to %lu", - FD_SYSVAR_CLOCK_DEFAULT_HASHES_PER_TICK )); - hashes_per_tick = FD_SYSVAR_CLOCK_DEFAULT_HASHES_PER_TICK; - } - - options->hashes_per_tick = hashes_per_tick; - - } else if( 1UL==config->development.genesis.hashes_per_tick ) { - - /* set hashes_per_tick field to 0, which means sleep mode */ - options->hashes_per_tick = 0UL; - - } else { - - /* set hashes_per_tick to the specified value */ - options->hashes_per_tick = config->development.genesis.hashes_per_tick; - - } - - options->hashes_per_tick = 0UL; /* PoH is in low power mode */ - + options->hashes_per_tick = 0UL; options->ticks_per_slot = config->development.genesis.ticks_per_slot; options->target_tick_duration_micros = config->development.genesis.target_tick_duration_micros; diff --git a/src/ballet/poh/Local.mk b/src/ballet/poh/Local.mk deleted file mode 100644 index 7cc52cb1e0d..00000000000 --- a/src/ballet/poh/Local.mk +++ /dev/null @@ -1,4 +0,0 @@ -$(call add-hdrs,fd_poh.h) -$(call add-objs,fd_poh,fddev_shared) -$(call make-unit-test,test_poh,test_poh fd_poh,fd_ballet fd_util) -$(call run-unit-test,test_poh) diff --git a/src/ballet/poh/fd_poh.c b/src/ballet/poh/fd_poh.c deleted file mode 100644 index 7d91007fd51..00000000000 --- a/src/ballet/poh/fd_poh.c +++ /dev/null @@ -1,19 +0,0 @@ -#include "fd_poh.h" - -void * -fd_poh_append( void * poh, - ulong n ) { - fd_sha256_hash_32_repeated( poh, poh, n ); - return poh; -} - -void * -fd_poh_mixin( void * FD_RESTRICT poh, - uchar const * FD_RESTRICT mixin ) { - fd_sha256_t sha; - fd_sha256_init( &sha ); - fd_sha256_append( &sha, poh, FD_SHA256_HASH_SZ ); - fd_sha256_append( &sha, mixin, FD_SHA256_HASH_SZ ); - fd_sha256_fini( &sha, poh ); - return poh; -} diff --git a/src/ballet/poh/fd_poh.h b/src/ballet/poh/fd_poh.h deleted file mode 100644 index 04b9acd3eb9..00000000000 --- a/src/ballet/poh/fd_poh.h +++ /dev/null @@ -1,34 +0,0 @@ -#ifndef HEADER_fd_src_ballet_poh_fd_poh_h -#define HEADER_fd_src_ballet_poh_fd_poh_h - -/* fd_poh provides a software-based implementation of the Proof-of-History hashchain. */ - -#include "../sha256/fd_sha256.h" - -FD_PROTOTYPES_BEGIN - -/* fd_poh_append performs n recursive hash operations. - - poh points to a 32 byte memory region which stores the current poh state - - TODO: Improve documentation on documentation day -*/ - -void * -fd_poh_append( void * poh, - ulong n ); - -/* fd_poh_mixin mixes in a 32-byte value. - - poh points to a 32 byte memory region which stores the current poh state - - TODO: Improve documentation on documentation day -*/ - -void * -fd_poh_mixin( void * FD_RESTRICT poh, - uchar const * FD_RESTRICT mixin ); - -FD_PROTOTYPES_END - -#endif /* HEADER_fd_src_ballet_poh_fd_poh_h */ diff --git a/src/ballet/poh/test_poh.c b/src/ballet/poh/test_poh.c deleted file mode 100644 index cd6e3f34311..00000000000 --- a/src/ballet/poh/test_poh.c +++ /dev/null @@ -1,213 +0,0 @@ -#include "fd_poh.h" -#include "../sha256/fd_sha256.h" - -/* Ensure that calling fd_poh_append with zero iterations is nop. */ -static void -test_poh_append_nop( void ) { - uchar poh[FD_SHA256_HASH_SZ] = {0}; - - /* Make up a pattern */ - uchar want[FD_SHA256_HASH_SZ]; - for( ulong i=0UL; ipre, 32UL ); - for( fd_poh_test_step_t const * step = t->steps; step->n >= 0; step++ ) { - if( FD_UNLIKELY( step->n == 0 ) ) { - fd_poh_mixin( &poh, step->mixin ); - } else { - fd_poh_append( &poh, (ulong)step->n ); - } - } - - if( FD_UNLIKELY( memcmp( &poh, &t->post, FD_SHA256_HASH_SZ ) ) ) { - FD_LOG_ERR(( "FAIL (%s)" - "\n\tGot" - "\n\t\t" FD_LOG_HEX16_FMT " " FD_LOG_HEX16_FMT - "\n\tExpected" - "\n\t\t" FD_LOG_HEX16_FMT " " FD_LOG_HEX16_FMT, - t->name, - FD_LOG_HEX16_FMT_ARGS( poh ), FD_LOG_HEX16_FMT_ARGS( poh +16 ), - FD_LOG_HEX16_FMT_ARGS( t->post ), FD_LOG_HEX16_FMT_ARGS( t->post+16 ) )); - } else { - FD_LOG_NOTICE(( "OK (%s)", t->name )); - } -} - -#define _(v) ((uchar)0x##v) - -// Source: https://github.com/solana-foundation/specs/blob/main/core/poh.md - -static fd_poh_test_step_t const solana_mainnet_block_0_steps[] = { - { .n = 800000 }, - { .n = -1 } -}; - -static fd_poh_test_step_t const solana_mainnet_block_1_steps[] = { - { .n = 14612 }, - { .mixin = { _(c9),_(5f),_(2f),_(13),_(a9),_(a7),_(7f),_(32),_(b1),_(43),_(79),_(76),_(c4),_(cf),_(fe),_(30),_(29),_(29),_(8a),_(49),_(bf),_(37),_(00),_(7f),_(8e),_(45),_(d7),_(93),_(a5),_(20),_(f3),_(0b) } }, - { .n = 210347 }, - { .mixin = { _(1a),_(ae),_(eb),_(36),_(61),_(1f),_(48),_(4d),_(98),_(46),_(83),_(a3),_(db),_(92),_(69),_(f2),_(29),_(2d),_(d9),_(bb),_(81),_(bd),_(ab),_(82),_(b2),_(8c),_(45),_(62),_(5d),_(9a),_(bd),_(59) } }, - { .n = 428775 }, - { .mixin = { _(db),_(31),_(e8),_(61),_(b3),_(10),_(f4),_(49),_(54),_(40),_(3e),_(34),_(5b),_(6b),_(ee),_(b3),_(de),_(d3),_(40),_(84),_(b9),_(06),_(94),_(bc),_(ca),_(a2),_(34),_(53),_(06),_(d3),_(66),_(e1) } }, - { .n = 146263 }, - { .n = -1 } -}; - -static fd_poh_test_vector_t const poh_test_vectors[] = { - { - .name = "Solana mainnet block 0", - .pre = { _(45),_(29),_(69),_(98),_(a6),_(f8),_(e2),_(a7),_(84),_(db),_(5d),_(9f),_(95),_(e1),_(8f),_(c2),_(3f),_(70),_(44),_(1a),_(10),_(39),_(44),_(68),_(01),_(08),_(98),_(79),_(b0),_(8c),_(7e),_(f0) }, - .post = { _(39),_(73),_(e3),_(30),_(c2),_(9b),_(83),_(1f),_(3f),_(cb),_(0e),_(49),_(37),_(4e),_(d8),_(d0),_(38),_(8f),_(41),_(0a),_(23),_(e4),_(eb),_(f2),_(33),_(28),_(50),_(50),_(36),_(ef),_(bd),_(03) }, - .steps = solana_mainnet_block_0_steps - }, - { - .name = "Solana mainnet block 1", - .pre = { _(39),_(73),_(e3),_(30),_(c2),_(9b),_(83),_(1f),_(3f),_(cb),_(0e),_(49),_(37),_(4e),_(d8),_(d0),_(38),_(8f),_(41),_(0a),_(23),_(e4),_(eb),_(f2),_(33),_(28),_(50),_(50),_(36),_(ef),_(bd),_(03) }, - .post = { _(8e),_(e2),_(06),_(07),_(dc),_(f1),_(d9),_(39),_(3c),_(f5),_(a2),_(f2),_(c9),_(f7),_(ba),_(be),_(16),_(7d),_(bd),_(d2),_(67),_(49),_(1b),_(51),_(3c),_(73),_(d2),_(cb),_(f8),_(74),_(13),_(f5) }, - .steps = solana_mainnet_block_1_steps - }, - { .name = NULL } -}; - -#undef _ - -static void -bench_poh_sequential( void ) { - uchar poh[FD_SHA256_HASH_SZ] = {0}; - - ulong batch_sz = 1024; - - /* warmup */ - ulong iter = 1000UL; - long dt = fd_log_wallclock(); - for( ulong rem=iter; rem; rem-- ) fd_poh_append( &poh, batch_sz ); - dt = fd_log_wallclock() - dt; - - /* for real */ - iter = 100000UL; - dt = fd_log_wallclock(); - for( ulong rem=iter; rem; rem-- ) fd_poh_append( &poh, batch_sz ); - dt = fd_log_wallclock() - dt; - - ulong hashes = iter*batch_sz; - double secs = (double)dt / 1e9; - FD_LOG_NOTICE(( "PoH sequential: ~%.3f MH/s", ((double)hashes/secs)/1e6 )); -} - -int main( int argc, - char ** argv ) { - fd_boot( &argc, &argv ); - - int bench = fd_env_strip_cmdline_contains( &argc, &argv, "--bench" ); - - test_poh_append_nop(); - test_poh_append_one(); - - test_poh_mixin(); - - for( fd_poh_test_vector_t const * v = poh_test_vectors; v->name; v++ ) { - test_poh_vector( v ); - } - - if( bench ) bench_poh_sequential(); - - FD_LOG_NOTICE(( "pass" )); - fd_halt(); - return 0; -} diff --git a/src/ballet/sha256/fd_sha256.c b/src/ballet/sha256/fd_sha256.c index f6346f4f292..b7a3efc1e98 100644 --- a/src/ballet/sha256/fd_sha256.c +++ b/src/ballet/sha256/fd_sha256.c @@ -2,7 +2,6 @@ #include "fd_sha256_constants.h" #if FD_HAS_SHANI -/* For the optimized repeated hash */ #include "../../util/simd/fd_sse.h" #endif @@ -282,7 +281,7 @@ fd_sha256_core_ref( uint * state, /* Zen 5's sha256rnds2 has an RTP of 2, while Zen 4's has an RTP of 1. We can win some performance by moving the schedule updates earlier in the loop, - which improves the speed by around 1M hashes/s for the repeated hashing. */ + which improves the speed by around 1M hashes/s for 32 byte inputs. */ #ifdef __znver5__ #define FULL_ROUNDS() do { \ vu_t w1013 = NEXT_W( w0003, w0407, w080b, w0c0f ); FOUR_ROUNDS( vu_add( w0003, vu_ld( fd_sha256_K+ 0UL ) ) ); \ @@ -558,319 +557,3 @@ fd_sha256_hash( void const * _data, state[7] = fd_uint_bswap( state[7] ); return memcpy( _hash, state, 32 ); } - - - -#if FD_SHA256_CORE_IMPL==2 -void -fd_sha256_hash_32_repeated_arm( uchar const * data, - uchar * hash, - ulong cnt ); -#elif FD_SHA256_CORE_IMPL==3 -void -fd_sha256_hash_32_repeated_riscv( uchar const * data, - uchar * hash, - ulong cnt ); -#endif - -void * -fd_sha256_hash_32_repeated( void const * _data, - void * _hash, - ulong cnt ) { -#if FD_SHA256_CORE_IMPL==2 - fd_sha256_hash_32_repeated_arm( _data, _hash, cnt ); - return _hash; -#elif FD_SHA256_CORE_IMPL==3 - fd_sha256_hash_32_repeated_riscv( _data, _hash, cnt ); - return _hash; -#elif FD_SHA256_CORE_IMPL==1 - - uchar const * data = (uchar const *)_data; - uchar * hash = (uchar *)_hash; - - vu_t w0003 = vu_bswap( vu_ldu( data ) ); - vu_t w0407 = vu_bswap( vu_ldu( data+16UL ) ); - vb_t const w080b = vb( 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 ); - vb_t const w0c0f = vb( 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00 ); /* 32 bytes */ - - vu_t const initialFEBA = vu( FD_SHA256_INITIAL_F, FD_SHA256_INITIAL_E, FD_SHA256_INITIAL_B, FD_SHA256_INITIAL_A ); - vu_t const initialHGDC = vu( FD_SHA256_INITIAL_H, FD_SHA256_INITIAL_G, FD_SHA256_INITIAL_D, FD_SHA256_INITIAL_C ); - - for( ulong iter=0UL; iter). Zen 4 M hashes/s by - chain count: 1: 31.1, 2: 58.9, 3: 74.8, 4: 80.1, 5: 79.6 (6+ spill - heavily and get slower). */ - -#define EACH2( M, ... ) M( 0, __VA_ARGS__ ) M( 1, __VA_ARGS__ ) -#define EACH3( M, ... ) EACH2( M, __VA_ARGS__ ) M( 2, __VA_ARGS__ ) -#define EACH4( M, ... ) EACH3( M, __VA_ARGS__ ) M( 3, __VA_ARGS__ ) -#define EACH5( M, ... ) EACH4( M, __VA_ARGS__ ) M( 4, __VA_ARGS__ ) - -#define X_LOAD( l, in ) \ - vu_t w0003_##l = vu_bswap( vu_ldu( (in)+32UL*(l) ) ); \ - vu_t w0407_##l = vu_bswap( vu_ldu( (in)+32UL*(l)+16UL ) ); -#define X_INIT( l, _ ) \ - vu_t feba_##l = initialFEBA; vu_t hgdc_##l = initialHGDC; \ - vu_t w080b_##l = w080b; vu_t w0c0f_##l = w0c0f; -#define X_R( l, w, k ) FOUR_ROUNDS_STATE( feba_##l, hgdc_##l, vu_add( w##_##l, vu_ld( fd_sha256_K+(k) ) ) ); -#define X_W( l, d, a, b, c, e ) vu_t d##_##l = NEXT_W( a##_##l, b##_##l, c##_##l, e##_##l ); -#define X_FINI( l, _ ) \ - feba_##l = vu_add( feba_##l, initialFEBA ); hgdc_##l = vu_add( hgdc_##l, initialHGDC ); \ - w0003_##l = vu_permute2( feba_##l, hgdc_##l, 3, 2, 3, 2 ); \ - w0407_##l = vu_permute2( feba_##l, hgdc_##l, 1, 0, 1, 0 ); -#define X_STORE( l, out ) \ - vu_stu( (out)+32UL*(l), vu_bswap( w0003_##l ) ); \ - vu_stu( (out)+32UL*(l)+16UL, vu_bswap( w0407_##l ) ); - -#define REPEATED_MULTI( EACH ) do { \ - vu_t const w080b = vu( 0x80000000U, 0U, 0U, 0U ); \ - vu_t const w0c0f = vu( 0U, 0U, 0U, 256U ); /* 32 bytes */ \ - vu_t const initialFEBA = vu( FD_SHA256_INITIAL_F, FD_SHA256_INITIAL_E, FD_SHA256_INITIAL_B, FD_SHA256_INITIAL_A ); \ - vu_t const initialHGDC = vu( FD_SHA256_INITIAL_H, FD_SHA256_INITIAL_G, FD_SHA256_INITIAL_D, FD_SHA256_INITIAL_C ); \ - EACH( X_LOAD, in ) \ - for( ulong iter=0UL; iterfd_sha256_simd_lane_max() ) ) FD_LOG_CRIT(( "batch_cnt %lu exceeds fd_sha256_simd_lane_max %lu", batch_cnt, fd_sha256_simd_lane_max() )); -#if FD_SHA256_CORE_IMPL==2 - fd_sha256_hash_32_repeated_batch_arm( hash_in, hash_out, cnt, batch_cnt ); - return; -#elif defined(X86_BATCH) -# if FD_HAS_AVX512 - if( batch_cnt>=AVX512_LANE_MIN ) { - fd_sha256_hash_32_repeated_batch_avx512( hash_in, hash_out, cnt, batch_cnt ); - return; - } -# endif -# if FD_SHA256_CORE_IMPL==1 - fd_sha256_hash_32_repeated_shani( hash_in, hash_out, cnt, batch_cnt ); - return; -# endif -#endif - for( ulong i=0UL; i=AVX512_LANE_MIN ) return 1357UL; /* 5.3x on Zen 5: 16 lanes at 96.5 M hashes/s vs 31.7 M hashes/s single lane with SHA-NI */ -# if FD_SHA256_CORE_IMPL==1 - /* Zen 4, per group of k interleaved chains: 256*k*rate(1)/rate(k) */ - static ushort const group_cost_q8[6] = { 0, 256, 273, 322, 398, 495 }; - ulong cost = 0UL; - ulong grp_cnt = (lane_cnt+4UL)/5UL; - while( lane_cnt ) { - ulong k = (lane_cnt+grp_cnt-1UL)/grp_cnt; - cost += group_cost_q8[ k ]; lane_cnt -= k; grp_cnt--; - } - return fd_ulong_max( cost, 256UL ); -# else - return fd_ulong_max( lane_cnt, 1UL )*256UL; -# endif -} - -#undef AVX512_LANE_MIN -#undef X86_BATCH - -#else - -__attribute__((weak)) ulong fd_sha256_simd_lane_min( void ) { return ULONG_MAX; } -__attribute__((weak)) ulong fd_sha256_simd_lane_max( void ) { return 1UL; } -__attribute__((weak)) ulong fd_sha256_simd_iter_cost_q8( ulong lane_cnt ) { return fd_ulong_max( lane_cnt, 1UL )*256UL; } - -#endif diff --git a/src/ballet/sha256/fd_sha256.h b/src/ballet/sha256/fd_sha256.h index 09a174db435..6c716bbba42 100644 --- a/src/ballet/sha256/fd_sha256.h +++ b/src/ballet/sha256/fd_sha256.h @@ -148,63 +148,6 @@ fd_sha256_hash( void const * data, ulong sz, void * hash ); -/* fd_sha256_hash_32_repeated hashes the 32 bytes pointed to by data, - then hashes the hash, and repeats, doing a total of cnt hashes. It - is a streamlined version of: - - uchar temp[32]; - memcpy( temp, data, 32UL ); - for( ulong i=0UL; i=2UL ? 331UL : 256UL; } /* Neoverse V2 with FEAT_SHA256: 46.4 ns/hash single lane vs 60.0 ns/iter at 2 lanes */ - -void -fd_sha256_hash_32_repeated_batch_arm( uchar const * hash_in, - uchar * hash, - ulong cnt, - ulong batch_cnt ) { - - if( FD_UNLIKELY( batch_cnt<2UL ) ) { - for( ulong i=0UL; i>(n)) | ((x)<<(32-(n))) ) -# define SCALAR_sigma0(x) ( SCALAR_ROTR((x), 7) ^ SCALAR_ROTR((x),18) ^ ((x)>> 3) ) -# define SCALAR_sigma1(x) ( SCALAR_ROTR((x),17) ^ SCALAR_ROTR((x),19) ^ ((x)>>10) ) - uint const PAD8 = 0x80000000U; - uint const PADF = 256U; - -# define LOAD_PAIR( i ) _mm512_inserti64x4( _mm512_castsi256_si512( wu_ld( (uint const *)(scratch+32UL*(i)) ) ), \ - wu_ld( (uint const *)(scratch+32UL*((i)+8UL)) ), 1 ) -# define STORE_PAIR( i, s ) do { \ - wu_st( (uint *)(scratch+32UL*(i)), _mm512_extracti32x8_epi32( (s), 0 ) ); \ - wu_st( (uint *)(scratch+32UL*((i)+8UL)), _mm512_extracti32x8_epi32( (s), 1 ) ); \ - } while(0) - -# define Sigma0(x) _mm512_ternarylogic_epi32( wwu_rol(x,30), wwu_rol(x,19), wwu_rol(x,10), 0x96 ) -# define Sigma1(x) _mm512_ternarylogic_epi32( wwu_rol(x,26), wwu_rol(x,21), wwu_rol(x, 7), 0x96 ) -# define sigma0(x) _mm512_ternarylogic_epi32( wwu_rol(x,25), wwu_rol(x,14), wwu_shr(x, 3), 0x96 ) -# define sigma1(x) _mm512_ternarylogic_epi32( wwu_rol(x,15), wwu_rol(x,13), wwu_shr(x,10), 0x96 ) -# define Ch(x,y,z) _mm512_ternarylogic_epi32( (x), (y), (z), 0xCA ) -# define Maj(x,y,z) _mm512_ternarylogic_epi32( (x), (y), (z), 0xE8 ) - -# define SHA_CORE(wk) \ - T1 = wwu_add( (wk), wwu_add( wwu_add( h, Sigma1(e) ), Ch(e, f, g) ) ); \ - T2 = wwu_add( Sigma0(a), Maj(a, b, c) ); \ - h = g; \ - g = f; \ - f = e; \ - e = wwu_add( d, T1 ); \ - d = c; \ - c = b; \ - b = a; \ - a = wwu_add( T1, T2 ) - -# define ROUND(xi,ki) SHA_CORE( wwu_add( xi, wwu_bcast( fd_sha256_K[ki] ) ) ) - -# define EXPAND_ROUNDS(i) \ - x0 = wwu_add( wwu_add( x0, sigma0(x1) ), wwu_add( sigma1(xe), x9 ) ); ROUND( x0, (i) ); \ - x1 = wwu_add( wwu_add( x1, sigma0(x2) ), wwu_add( sigma1(xf), xa ) ); ROUND( x1, (i)+ 1UL ); \ - x2 = wwu_add( wwu_add( x2, sigma0(x3) ), wwu_add( sigma1(x0), xb ) ); ROUND( x2, (i)+ 2UL ); \ - x3 = wwu_add( wwu_add( x3, sigma0(x4) ), wwu_add( sigma1(x1), xc ) ); ROUND( x3, (i)+ 3UL ); \ - x4 = wwu_add( wwu_add( x4, sigma0(x5) ), wwu_add( sigma1(x2), xd ) ); ROUND( x4, (i)+ 4UL ); \ - x5 = wwu_add( wwu_add( x5, sigma0(x6) ), wwu_add( sigma1(x3), xe ) ); ROUND( x5, (i)+ 5UL ); \ - x6 = wwu_add( wwu_add( x6, sigma0(x7) ), wwu_add( sigma1(x4), xf ) ); ROUND( x6, (i)+ 6UL ); \ - x7 = wwu_add( wwu_add( x7, sigma0(x8) ), wwu_add( sigma1(x5), x0 ) ); ROUND( x7, (i)+ 7UL ); \ - x8 = wwu_add( wwu_add( x8, sigma0(x9) ), wwu_add( sigma1(x6), x1 ) ); ROUND( x8, (i)+ 8UL ); \ - x9 = wwu_add( wwu_add( x9, sigma0(xa) ), wwu_add( sigma1(x7), x2 ) ); ROUND( x9, (i)+ 9UL ); \ - xa = wwu_add( wwu_add( xa, sigma0(xb) ), wwu_add( sigma1(x8), x3 ) ); ROUND( xa, (i)+10UL ); \ - xb = wwu_add( wwu_add( xb, sigma0(xc) ), wwu_add( sigma1(x9), x4 ) ); ROUND( xb, (i)+11UL ); \ - xc = wwu_add( wwu_add( xc, sigma0(xd) ), wwu_add( sigma1(xa), x5 ) ); ROUND( xc, (i)+12UL ); \ - xd = wwu_add( wwu_add( xd, sigma0(xe) ), wwu_add( sigma1(xb), x6 ) ); ROUND( xd, (i)+13UL ); \ - xe = wwu_add( wwu_add( xe, sigma0(xf) ), wwu_add( sigma1(xc), x7 ) ); ROUND( xe, (i)+14UL ); \ - xf = wwu_add( wwu_add( xf, sigma0(x0) ), wwu_add( sigma1(xd), x8 ) ); ROUND( xf, (i)+15UL ) - - /* Transpose 16 lanes x 8 words into 8 vectors of 16 lanes each. */ - - wwu_t s0; wwu_t s1; wwu_t s2; wwu_t s3; wwu_t s4; wwu_t s5; wwu_t s6; wwu_t s7; - wwu_transpose_2x8x8( wwu_bswap( LOAD_PAIR( 0 ) ), wwu_bswap( LOAD_PAIR( 1 ) ), - wwu_bswap( LOAD_PAIR( 2 ) ), wwu_bswap( LOAD_PAIR( 3 ) ), - wwu_bswap( LOAD_PAIR( 4 ) ), wwu_bswap( LOAD_PAIR( 5 ) ), - wwu_bswap( LOAD_PAIR( 6 ) ), wwu_bswap( LOAD_PAIR( 7 ) ), - s0, s1, s2, s3, s4, s5, s6, s7 ); - - for( ulong iter=0UL; iter=1UL && lane_max<=REPEATED_BATCH_MAX ); - for( ulong trial=0UL; trial<1000UL; trial++ ) { - ulong batch_cnt = fd_rng_ulong_roll( rng, lane_max+1UL ); /* In [0,lane_max] */ - ulong batch_iter = fd_rng_uint_roll( rng, 4U ) ? 100UL : fd_rng_ulong_roll( rng, 200UL ); - - uchar batch_data[ REPEATED_BATCH_MAX*32UL ]; - uchar batch_hash[ REPEATED_BATCH_MAX*32UL ]; - uchar ref_hash [ REPEATED_BATCH_MAX*32UL ]; - - for( ulong i=0UL; i=256UL ); - if( batch_cnt>1UL ) FD_TEST( cost>=fd_sha256_simd_iter_cost_q8( batch_cnt-1UL ) ); - } - } - if( bench ) { /* do a benchmark on PoH-style hashing */ FD_LOG_NOTICE(( "Benchmarking poh" )); @@ -271,45 +183,6 @@ main( int argc, float hashes_per_sec = ((float)iter * 1e-6f ) / ((float)dt * 1e-9f) ; FD_LOG_NOTICE(( "~%6.3f M poh hashes / sec / core", (double)hashes_per_sec )); } - { - fd_sha256_hash_32_repeated( in_hash, hash, 10UL ); - ulong iter = 1000000UL; - /* Now do it again with hash_32_repeated */ - long dt = -fd_log_wallclock(); - fd_sha256_hash_32_repeated( in_hash, hash, iter ); - dt += fd_log_wallclock(); - float hashes_per_sec = ((float)iter * 1e-6f ) / ((float)dt * 1e-9f) ; - FD_LOG_NOTICE(( "~%6.3f M poh hashes / sec / core with fd_sha256_hash_32_repeated", (double)hashes_per_sec )); - } - { - uchar batch_in [ REPEATED_BATCH_MAX*32UL ]; - uchar batch_out [ REPEATED_BATCH_MAX*32UL ]; - - for( ulong b=0UL; b - + - + diff --git a/src/discof/execrp/fd_execrp_tile.c b/src/discof/execrp/fd_execrp_tile.c index 0ea5ee791f8..afe9848522a 100644 --- a/src/discof/execrp/fd_execrp_tile.c +++ b/src/discof/execrp/fd_execrp_tile.c @@ -78,7 +78,6 @@ struct fd_execrp_tile { struct { ulong sigverify_cnt; - ulong poh_hash_cnt; /* Ticks spent loading txn accounts */ ulong txn_load_cum_ticks; @@ -139,7 +138,6 @@ metrics_write( fd_execrp_tile_t * ctx ) { fd_accdb_flush_metrics( ctx->accdb ); FD_MCNT_SET ( EXECRP, SIGNATURE_CHECKED, ctx->metrics.sigverify_cnt ); - FD_MCNT_SET ( EXECRP, POH_HASH_COMPUTED, ctx->metrics.poh_hash_cnt ); FD_MCNT_ENUM_COPY( EXECRP, TXN_PROCESSED, ctx->metrics.txn_result ); FD_MCNT_ENUM_COPY( EXECRP, TXN_PROCESSED_BY_VERSION, ctx->metrics.txn_version ); @@ -334,18 +332,6 @@ returnable_frag( fd_execrp_tile_t * ctx, ctx->metrics.sigverify_cnt += TXN( msg->txn )->signature_cnt; break; } - case FD_EXECRP_TT_POH_HASH: { - fd_execrp_poh_hash_msg_t * msg = fd_chunk_to_laddr( ctx->replay_in->mem, chunk ); - fd_execrp_task_done_msg_t * out_msg = fd_chunk_to_laddr( ctx->execrp_replay_out->mem, ctx->execrp_replay_out->chunk ); - FD_CHECK_CRIT( msg->cnt && msg->cnt<=FD_EXECRP_POH_PARA, "invalid poh batch" ); - out_msg->bank_idx = msg->bank_idx; - out_msg->poh_hash->cnt = msg->cnt; - ctx->metrics.poh_hash_cnt += msg->hashcnt*msg->cnt; - fd_sha256_hash_32_repeated_batch( msg->hash, out_msg->poh_hash->hash, msg->hashcnt, msg->cnt ); - fd_stem_publish( stem, ctx->execrp_replay_out->idx, (FD_EXECRP_TT_POH_HASH<<32)|ctx->tile_idx, ctx->execrp_replay_out->chunk, sizeof(*out_msg), 0UL, 0UL, 0UL ); - ctx->execrp_replay_out->chunk = fd_dcache_compact_next( ctx->execrp_replay_out->chunk, sizeof(*out_msg), ctx->execrp_replay_out->chunk0, ctx->execrp_replay_out->wmark ); - break; - } case FD_EXECRP_TT_CERT_VERIFY: { fd_execrp_cert_verify_msg_t * msg = fd_chunk_to_laddr( ctx->replay_in->mem, chunk ); fd_bank_t const * bank = fd_banks_bank_query( ctx->banks, msg->bank_idx ); diff --git a/src/discof/execrp/test_execrp_tile.c b/src/discof/execrp/test_execrp_tile.c index 1842c361209..30c200ea883 100644 --- a/src/discof/execrp/test_execrp_tile.c +++ b/src/discof/execrp/test_execrp_tile.c @@ -401,7 +401,6 @@ FD_UNIT_TEST( execrp_metrics_write ) { test_env_t * env = test_env_create(); env->execrp->metrics.sigverify_cnt = 2UL; - env->execrp->metrics.poh_hash_cnt = 3UL; env->execrp->metrics.txn_load_cum_ticks = 5UL; env->execrp->metrics.txn_check_cum_ticks = 7UL; env->execrp->metrics.txn_exec_cum_ticks = 11UL; @@ -449,37 +448,6 @@ FD_UNIT_TEST( execrp_sigverify ) { test_env_destroy( env ); } -FD_UNIT_TEST( execrp_poh_hash ) { - test_env_t * env = test_env_create(); - - ulong in_chunk = env->execrp->replay_in->chunk0; - fd_execrp_poh_hash_msg_t * in_msg = fd_chunk_to_laddr( env->execrp->replay_in->mem, in_chunk ); - fd_memset( in_msg, 0, sizeof(fd_execrp_poh_hash_msg_t) ); - in_msg->bank_idx = env->bank_idx; - in_msg->cnt = fd_ulong_min( FD_EXECRP_POH_PARA, fd_sha256_simd_lane_max() ); - in_msg->hashcnt = 7UL; - fd_hash_t expected[ FD_EXECRP_POH_PARA ]; - for( ulong j=0UL; jcnt; j++ ) { - for( ulong i=0UL; ihash[ j ].uc[ i ] = (uchar)(i+j); - fd_sha256_hash_32_repeated( in_msg->hash+j, expected+j, in_msg->hashcnt ); - } - - fd_stem_context_t stem[1]; - ulong const sig = (FD_EXECRP_TT_POH_HASH<<32) | env->execrp->tile_idx; - FD_TEST( !returnable_frag( env->execrp, env->execrp->replay_in->idx, 0UL, sig, in_chunk, - sizeof(fd_execrp_poh_hash_msg_t), 0UL, 0UL, 0UL, - test_stem( env->execrp, stem ) ) ); - - fd_execrp_task_done_msg_t const * out_msg = test_assert_out_msg( env, 0UL, FD_EXECRP_TT_POH_HASH ); - FD_TEST( out_msg->poh_hash->cnt==in_msg->cnt ); - for( ulong j=0UL; jcnt; j++ ) { - FD_TEST( !memcmp( out_msg->poh_hash->hash+j, expected+j, sizeof(fd_hash_t) ) ); - } - FD_TEST( env->execrp->metrics.poh_hash_cnt==in_msg->hashcnt*in_msg->cnt ); - - test_env_destroy( env ); -} - /* CERT_VERIFY answers with one done frag for the bank: err set iff the cert does not verify. */ diff --git a/src/discof/genesis/fd_genesi_tile.c b/src/discof/genesis/fd_genesi_tile.c index 6227c1887f9..acf792814c5 100644 --- a/src/discof/genesis/fd_genesi_tile.c +++ b/src/discof/genesis/fd_genesi_tile.c @@ -419,6 +419,10 @@ process_local_genesis( fd_genesi_tile_t * ctx, verify_cluster_type( genesis, ctx->genesis_hash, genesis_path ); } + if( ctx->bootstrap ) { + FD_CHECK_ERR( genesis->poh.hashes_per_tick<=1UL, "not an Alpenglow genesis blob" ); + } + if( FD_UNLIKELY( ctx->bootstrap && ctx->expected_shred_version ) ) { ushort xor = 0; for( ulong i=0UL; i<16UL; i++ ) xor ^= ctx->genesis_hash->us[ i ]; diff --git a/src/discof/replay/fd_execrp.h b/src/discof/replay/fd_execrp.h index 451e235b442..023f19e3d36 100644 --- a/src/discof/replay/fd_execrp.h +++ b/src/discof/replay/fd_execrp.h @@ -9,7 +9,6 @@ /* Exec tile task types. */ #define FD_EXECRP_TT_TXN_EXEC (1UL) /* Transaction execution. */ #define FD_EXECRP_TT_TXN_SIGVERIFY (2UL) /* Transaction sigverify. */ -#define FD_EXECRP_TT_POH_HASH (3UL) /* PoH hashing. */ #define FD_EXECRP_TT_CERT_VERIFY (6UL) /* Alpenglow footer cert verify. */ /* Sent from the replay tile to the exec tiles. These describe one of @@ -41,15 +40,6 @@ struct fd_execrp_txn_sigverify_msg { }; typedef struct fd_execrp_txn_sigverify_msg fd_execrp_txn_sigverify_msg_t; -#define FD_EXECRP_POH_PARA 16 -struct fd_execrp_poh_hash_msg { - ulong bank_idx; - ulong cnt; /* In [1,FD_EXECRP_POH_PARA] */ - ulong hashcnt; /* Same for every element of the batch */ - fd_hash_t hash[ FD_EXECRP_POH_PARA ]; -}; -typedef struct fd_execrp_poh_hash_msg fd_execrp_poh_hash_msg_t; - struct fd_execrp_cert_verify_msg { ulong bank_idx; ulong quorum; /* quorum numerator, 0 for a reward cert */ @@ -64,7 +54,6 @@ typedef struct fd_execrp_cert_verify_msg fd_execrp_cert_verify_msg_t; union fd_execrp_task_msg { fd_execrp_txn_exec_msg_t txn_exec; fd_execrp_txn_sigverify_msg_t txn_sigverify; - fd_execrp_poh_hash_msg_t poh_hash; fd_execrp_cert_verify_msg_t cert_verify; }; @@ -140,12 +129,6 @@ struct fd_execrp_txn_sigverify_done_msg { }; typedef struct fd_execrp_txn_sigverify_done_msg fd_execrp_txn_sigverify_done_msg_t; -struct fd_execrp_poh_hash_done_msg { - ulong cnt; - fd_hash_t hash[ FD_EXECRP_POH_PARA ]; -}; -typedef struct fd_execrp_poh_hash_done_msg fd_execrp_poh_hash_done_msg_t; - struct fd_execrp_cert_verify_done_msg { int err; int has_hm; /* hm is the H(m) the tile hashed, for replay's memo */ @@ -158,7 +141,6 @@ struct fd_execrp_task_done_msg { union { fd_execrp_txn_exec_done_msg_t txn_exec[ 1 ]; fd_execrp_txn_sigverify_done_msg_t txn_sigverify[ 1 ]; - fd_execrp_poh_hash_done_msg_t poh_hash[ 1 ]; fd_execrp_cert_verify_done_msg_t cert_verify[ 1 ]; }; }; diff --git a/src/discof/replay/fd_replay_tile.c b/src/discof/replay/fd_replay_tile.c index c9c317872d8..d4bf59dea93 100644 --- a/src/discof/replay/fd_replay_tile.c +++ b/src/discof/replay/fd_replay_tile.c @@ -2759,23 +2759,6 @@ dispatch_task( fd_replay_tile_t * ctx, exec_out->chunk = fd_dcache_compact_next( exec_out->chunk, sizeof(*exec_msg), exec_out->chunk0, exec_out->wmark ); break; }; - case FD_SCHED_TT_POH_HASH: { - fd_bank_t * bank = fd_banks_bank_query( ctx->banks, task->poh_hash->bank_idx ); - FD_TEST( bank ); - bank->refcnt++; - - FD_TEST( task->poh_hash->exec_idxexec_cnt ); - fd_replay_out_link_t * exec_out = &ctx->exec_out[ task->poh_hash->exec_idx ]; - fd_execrp_poh_hash_msg_t * exec_msg = fd_chunk_to_laddr( exec_out->mem, exec_out->chunk ); - exec_msg->bank_idx = task->poh_hash->bank_idx; - exec_msg->cnt = task->poh_hash->cnt; - exec_msg->hashcnt = task->poh_hash->hashcnt; - FD_STATIC_ASSERT( FD_SCHED_POH_PARA==FD_EXECRP_POH_PARA, poh batch width mismatch ); - memcpy( exec_msg->hash, task->poh_hash->hash, task->poh_hash->cnt*sizeof(fd_hash_t) ); - fd_stem_publish( stem, exec_out->idx, (FD_EXECRP_TT_POH_HASH<<32) | task->poh_hash->exec_idx, exec_out->chunk, sizeof(*exec_msg), 0UL, 0UL, 0UL ); - exec_out->chunk = fd_dcache_compact_next( exec_out->chunk, sizeof(*exec_msg), exec_out->chunk0, exec_out->wmark ); - break; - }; default: { FD_LOG_CRIT(( "unexpected task type %lu", task->task_type )); } @@ -2855,8 +2838,7 @@ try_replay( fd_replay_tile_t * ctx, break; } case FD_SCHED_TT_TXN_EXEC: - case FD_SCHED_TT_TXN_SIGVERIFY: - case FD_SCHED_TT_POH_HASH: { + case FD_SCHED_TT_TXN_SIGVERIFY: { /* Common case: we have a transaction we need to execute. */ dispatch_task( ctx, stem, task ); break; @@ -3537,13 +3519,6 @@ process_exec_task_done( fd_replay_tile_t * ctx, } break; } - case FD_EXECRP_TT_POH_HASH: { - int res = fd_sched_task_done( ctx->sched, FD_SCHED_TT_POH_HASH, ULONG_MAX, exec_tile_idx, msg->poh_hash ); - if( FD_UNLIKELY( res && bank->state!=FD_BANK_STATE_DEAD ) ) { - mark_bank_dead( ctx, stem, bank->idx, sched_dead_reason_to_event( res ), FD_EVENT_BLOCK_COMPLETED_ABANDONED_REASON_NOT_ABANDONED, NULL ); - } - break; - } case FD_EXECRP_TT_CERT_VERIFY: { FD_TEST( bank->cert_task_cnt ); bank->cert_task_cnt--; diff --git a/src/discof/replay/fd_sched.c b/src/discof/replay/fd_sched.c index ba890bfba80..9e000e0534a 100644 --- a/src/discof/replay/fd_sched.c +++ b/src/discof/replay/fd_sched.c @@ -3,7 +3,6 @@ #include "fd_sched.h" #include "../../flamenco/alpenglow/fd_block_marker_serde.h" -#include "fd_execrp.h" /* for poh hash value */ #include "../../ballet/sha256/fd_sha256.h" #include "../../disco/fd_disco_base.h" /* for FD_MAX_TXN_PER_SLOT_SHRED */ #include "../../disco/fd_txn_p.h" @@ -17,8 +16,6 @@ #define FD_SCHED_MAX_PRINT_BUF_SZ (2UL<<20) #define FD_SCHED_POISON_MAX_ACCT_PER_SLOT (64UL) -#define FD_SCHED_MAX_POH_HASHES_PER_TASK (4096UL) /* This seems to be the sweet spot. */ - /* 64 ticks per slot, and a single gigantic microblock containing min size transactions. */ FD_STATIC_ASSERT( FD_MAX_TXN_PER_SLOT_SHRED==((FD_SHRED_DATA_PAYLOAD_MAX_PER_SLOT-65UL*sizeof(fd_microblock_hdr_t))/FD_TXN_MIN_SERIALIZED_SZ), max_txn_per_slot_shred ); @@ -51,7 +48,6 @@ struct fd_sched_mblk { ulong end_txn_idx; /* non-inclusive parse idx */ ulong curr_txn_idx; /* next txn to mixin, parse idx in block */ ulong hashcnt; /* number of pure hashes, excluding final mixin */ - ulong curr_hashcnt; fd_hash_t end_hash[ 1 ]; fd_hash_t curr_hash[ 1 ]; uint curr_sig_cnt; @@ -85,8 +81,6 @@ struct fd_sched_block { uint txn_exec_done_cnt; uint txn_sigverify_in_flight_cnt; uint txn_sigverify_done_cnt; - uint poh_hashing_in_flight_cnt; /* number of in-flight PoH batch tasks */ - uint poh_mblk_in_flight_cnt; /* number of individual in-flight PoH jobs across batches */ uint poh_hashing_done_cnt; uint poh_hash_cmp_done_cnt; /* poh_hashing_done_cnt==poh_hash_cmp_done_cnt+len(mixin_in_progress) */ uint cert_in_flight_cnt; /* in-flight footer cert verify tasks, see fd_sched_cert_dispatch */ @@ -95,7 +89,7 @@ struct fd_sched_block { uint shred_scan_idx; /* First shred boundary not before shred_scan_off. */ uint shred_scan_off; /* Block byte offset at the start of shred_scan_idx. */ uint mblk_cnt; /* Total number of microblocks, including ticks and non ticks. - mblk_cnt==len(unhashed)+len(hashing_in_progress)+poh_mblk_in_flight_cnt+len(mixin_in_progress)+hash_cmp_done_cnt */ + mblk_cnt==len(unhashed)+len(mixin_in_progress)+hash_cmp_done_cnt */ uint mblk_tick_cnt; /* Total number of tick microblocks. */ uint mblk_freed_cnt; /* This is ==hash_cmp_done_cnt in most cases, except for aborted blocks, where the freed cnt will catch up to mblk_cnt and surpass @@ -113,11 +107,8 @@ struct fd_sched_block { fd_hash_t poh_hash[ 1 ]; /* running end_hash of last parsed mblk */ int last_mblk_is_tick; mblk_slist_t mblks_unhashed[ 1 ]; /* A microblock, once parsed out, is in one of these queues. It - generally progresses from unhashed to hashing to mixin. When a - microblock is being hashed/in-flight, it'll be transiently out of - any of the queues. Once a microblock progresses through all stages - of work, it'll be immediately freed. */ - mblk_slist_t mblks_hashing_in_progress[ 1 ]; + progresses from unhashed to mixin. Once a microblock progresses + through all stages of work, it'll be immediately freed. */ mblk_slist_t mblks_mixin_in_progress[ 1 ]; uchar bmtree_mem[ FD_BMTREE_COMMIT_FOOTPRINT(0) ] __attribute__((aligned(FD_BMTREE_COMMIT_ALIGN))); fd_bmtree_commit_t * bmtree; @@ -232,9 +223,9 @@ struct fd_sched_block { }; typedef struct fd_sched_block fd_sched_block_t; -FD_STATIC_ASSERT( sizeof(fd_sched_mblk_t)==120UL, fd_sched_mblk ); +FD_STATIC_ASSERT( sizeof(fd_sched_mblk_t)==112UL, fd_sched_mblk ); FD_STATIC_ASSERT( sizeof(fd_sched_txn_info_t)==192UL, fd_sched_txn_info ); -FD_STATIC_ASSERT( sizeof(fd_sched_block_t)==75840UL, fd_sched_block ); +FD_STATIC_ASSERT( sizeof(fd_sched_block_t)==75808UL, fd_sched_block ); FD_STATIC_ASSERT( sizeof(fd_hash_t)==sizeof(((fd_microblock_hdr_t *)0)->hash), unexpected poh hash size ); @@ -303,8 +294,6 @@ struct fd_sched { ulong depth; /* Immutable. */ ulong block_cnt_max; /* Immutable. */ ulong exec_cnt; /* Immutable. */ - ulong poh_simd_min; /* Immutable. */ - ulong poh_simd_max; /* Immutable. */ int bypass_poh_verify; /* Test/fuzz: skip the PoH end_hash compare in maybe_mixin. */ int bypass_alut_resolution; /* Test/fuzz: skip ALUT resolution (no accdb). */ long txn_in_flight_last_tick; @@ -314,7 +303,6 @@ struct fd_sched { fd_rdisp_t * rdisp; ulong txn_exec_ready_bitset[ 1 ]; ulong sigverify_ready_bitset[ 1 ]; - ulong poh_ready_bitset[ 1 ]; ulong cert_ready_bitset[ 1 ]; ulong active_bank_idx; /* Index of the actively replayed block, or ULONG_MAX if no block is actively replayed; has to have a transaction to dispatch; staged @@ -332,7 +320,6 @@ struct fd_sched { ulong mblk_pool_free_cnt; uint mblk_pool_free_head; ulong tile_to_bank_idx[ FD_SCHED_MAX_EXEC_TILE_CNT ]; /* Index of the bank that the exec tile is executing against. */ - fd_sched_poh_hash_t poh_inflight[ FD_SCHED_MAX_EXEC_TILE_CNT ]; /* PoH dispatch metadata, indexed by exec tile. */ txn_bitset_t exec_done_set[ txn_bitset_word_cnt ]; /* Indexed by txn_idx. */ txn_bitset_t sigverify_done_set[ txn_bitset_word_cnt ]; /* Indexed by txn_idx. */ txn_bitset_t poh_mixin_done_set[ txn_bitset_word_cnt ]; /* Indexed by txn_idx. */ @@ -373,22 +360,11 @@ shred_split( fd_sched_t * sched, fd_sched_block_t * block, uint query ); -static void -dispatch_poh( fd_sched_t * sched, fd_sched_block_t * block, ulong bank_idx, int exec_tile_idx, ulong max_cnt, fd_sched_task_t * out ); - static int poh_retire_mblk( fd_sched_t * sched, fd_sched_block_t * block, uint mblk_idx ); static int -poh_retire_ready_mblks( fd_sched_t * sched, fd_sched_block_t * block ); - -static inline ulong -poh_batch_max_cnt( fd_sched_t const * sched, ulong queued_cnt, ulong free_cnt, ulong threshold ) { - ulong tile_cnt = free_cnt-threshold; - ulong per_tile = (queued_cnt+tile_cnt-1UL)/tile_cnt; - ulong max_cnt = fd_ulong_min( fd_ulong_max( per_tile, 1UL ), sched->poh_simd_max ); - return fd_ulong_if( max_cntpoh_simd_min, 1UL, max_cnt ); -} +poh_hash_unhashed( fd_sched_t * sched, fd_sched_block_t * block ); FD_WARN_UNUSED static int maybe_mixin( fd_sched_t * sched, fd_sched_block_t * block ); @@ -475,17 +451,16 @@ static inline int block_is_dispatchable( fd_sched_block_t * block ) { ulong exec_queued_cnt = block->txn_parsed_cnt-block->txn_exec_in_flight_cnt-block->txn_exec_done_cnt; ulong sigverify_queued_cnt = block->txn_parsed_cnt-block->txn_sigverify_in_flight_cnt-block->txn_sigverify_done_cnt; - ulong poh_queued_cnt = block->mblk_cnt-block->poh_mblk_in_flight_cnt-block->poh_hashing_done_cnt; return exec_queued_cnt>0UL || sigverify_queued_cnt>0UL || - poh_queued_cnt>0UL || + block->mblk_unhashed_cnt>0U || !block->block_start_signaled || block_will_signal_end( block ); } static inline int block_is_in_flight( fd_sched_block_t * block ) { - return block->txn_exec_in_flight_cnt || block->txn_sigverify_in_flight_cnt || block->poh_hashing_in_flight_cnt || block->cert_in_flight_cnt || (block->block_end_signaled && !block->block_end_done); + return block->txn_exec_in_flight_cnt || block->txn_sigverify_in_flight_cnt || block->cert_in_flight_cnt || (block->block_end_signaled && !block->block_end_done); } static inline int @@ -616,8 +591,8 @@ print_block_metrics( fd_sched_t * sched, fd_sched_block_t * block ) { FD_FN_UNUSED static void print_block_debug( fd_sched_t * sched, fd_sched_block_t * block ) { - fd_sched_printf( sched, "block idx %lu, block slot %lu, parent_slot %lu, staged %d (lane %lu), dying %d, in_rdisp %d, fec_eos %d, rooted %d, block_start_signaled %d, block_end_signaled %d, block_start_done %d, block_end_done %d, txn_parsed_cnt %u, txn_exec_in_flight_cnt %u, txn_exec_done_cnt %u, txn_sigverify_in_flight_cnt %u, txn_sigverify_done_cnt %u, poh_hashing_in_flight_cnt %u, poh_mblk_in_flight_cnt %u, cert_in_flight_cnt %u, poh_hashing_done_cnt %u, poh_hash_cmp_done_cnt %u, txn_done_cnt %u, shred_cnt %u, mblk_cnt %u, mblk_freed_cnt %u, mblk_tick_cnt %u, mblk_unhashed_cnt %u, hashcnt %lu, txn_pool_max_popcnt %lu/%lu, mblk_pool_max_popcnt %lu/%lu, block_pool_max_popcnt %lu/%lu, tick_hashcnt_wmk %lu, curr_tick_hashcnt %lu, hashes_per_tick %lu, mblks_rem %lu, txns_rem %lu, fec_buf_sz %u, fec_buf_boff %u, fec_buf_soff %u, fec_eob %d, fec_sob %d\n", - block_to_idx( sched, block ), block->slot, block->parent_slot, block->staged, block->staging_lane, block->dying, block->in_rdisp, block->fec_eos, block->rooted, block->block_start_signaled, block->block_end_signaled, block->block_start_done, block->block_end_done, block->txn_parsed_cnt, block->txn_exec_in_flight_cnt, block->txn_exec_done_cnt, block->txn_sigverify_in_flight_cnt, block->txn_sigverify_done_cnt, block->poh_hashing_in_flight_cnt, block->poh_mblk_in_flight_cnt, block->cert_in_flight_cnt, block->poh_hashing_done_cnt, block->poh_hash_cmp_done_cnt, block->txn_done_cnt, block->shred_cnt, block->mblk_cnt, block->mblk_freed_cnt, block->mblk_tick_cnt, block->mblk_unhashed_cnt, block->hashcnt, block->txn_pool_max_popcnt, sched->depth, block->mblk_pool_max_popcnt, sched->depth, block->block_pool_max_popcnt, sched->block_cnt_max, block->tick_hashcnt_wmk, block->curr_tick_hashcnt, block->hashes_per_tick, block->mblks_rem, block->txns_rem, block->fec_buf_sz, block->fec_buf_boff, block->fec_buf_soff, block->fec_eob, block->fec_sob ); + fd_sched_printf( sched, "block idx %lu, block slot %lu, parent_slot %lu, staged %d (lane %lu), dying %d, in_rdisp %d, fec_eos %d, rooted %d, block_start_signaled %d, block_end_signaled %d, block_start_done %d, block_end_done %d, txn_parsed_cnt %u, txn_exec_in_flight_cnt %u, txn_exec_done_cnt %u, txn_sigverify_in_flight_cnt %u, txn_sigverify_done_cnt %u, cert_in_flight_cnt %u, poh_hashing_done_cnt %u, poh_hash_cmp_done_cnt %u, txn_done_cnt %u, shred_cnt %u, mblk_cnt %u, mblk_freed_cnt %u, mblk_tick_cnt %u, mblk_unhashed_cnt %u, hashcnt %lu, txn_pool_max_popcnt %lu/%lu, mblk_pool_max_popcnt %lu/%lu, block_pool_max_popcnt %lu/%lu, tick_hashcnt_wmk %lu, curr_tick_hashcnt %lu, hashes_per_tick %lu, mblks_rem %lu, txns_rem %lu, fec_buf_sz %u, fec_buf_boff %u, fec_buf_soff %u, fec_eob %d, fec_sob %d\n", + block_to_idx( sched, block ), block->slot, block->parent_slot, block->staged, block->staging_lane, block->dying, block->in_rdisp, block->fec_eos, block->rooted, block->block_start_signaled, block->block_end_signaled, block->block_start_done, block->block_end_done, block->txn_parsed_cnt, block->txn_exec_in_flight_cnt, block->txn_exec_done_cnt, block->txn_sigverify_in_flight_cnt, block->txn_sigverify_done_cnt, block->cert_in_flight_cnt, block->poh_hashing_done_cnt, block->poh_hash_cmp_done_cnt, block->txn_done_cnt, block->shred_cnt, block->mblk_cnt, block->mblk_freed_cnt, block->mblk_tick_cnt, block->mblk_unhashed_cnt, block->hashcnt, block->txn_pool_max_popcnt, sched->depth, block->mblk_pool_max_popcnt, sched->depth, block->block_pool_max_popcnt, sched->block_cnt_max, block->tick_hashcnt_wmk, block->curr_tick_hashcnt, block->hashes_per_tick, block->mblks_rem, block->txns_rem, block->fec_buf_sz, block->fec_buf_boff, block->fec_buf_soff, block->fec_eob, block->fec_sob ); } FD_FN_UNUSED static void @@ -635,8 +610,8 @@ print_metrics( fd_sched_t * sched ) { FD_FN_UNUSED static void print_sched( fd_sched_t * sched ) { - fd_sched_printf( sched, "sched canary 0x%lx, exec_cnt %lu, root_idx %lu, txn_exec_ready_bitset[ 0 ] 0x%lx, sigverify_ready_bitset[ 0 ] 0x%lx, poh_ready_bitset[ 0 ] 0x%lx, cert_ready_bitset[ 0 ] 0x%lx, active_idx %lu, staged_bitset %lu, staged_head_idx[0] %lu, staged_head_idx[1] %lu, staged_head_idx[2] %lu, staged_head_idx[3] %lu, staged_popcnt_wmk %lu, txn_pool_free_cnt %lu/%lu, block_pool_popcnt %lu/%lu\n", - sched->canary, sched->exec_cnt, sched->root_idx, sched->txn_exec_ready_bitset[ 0 ], sched->sigverify_ready_bitset[ 0 ], sched->poh_ready_bitset[ 0 ], sched->cert_ready_bitset[ 0 ], sched->active_bank_idx, sched->staged_bitset, sched->staged_head_bank_idx[ 0 ], sched->staged_head_bank_idx[ 1 ], sched->staged_head_bank_idx[ 2 ], sched->staged_head_bank_idx[ 3 ], sched->staged_popcnt_wmk, sched->txn_pool_free_cnt, sched->depth, sched->block_pool_popcnt, sched->block_cnt_max ); + fd_sched_printf( sched, "sched canary 0x%lx, exec_cnt %lu, root_idx %lu, txn_exec_ready_bitset[ 0 ] 0x%lx, sigverify_ready_bitset[ 0 ] 0x%lx, cert_ready_bitset[ 0 ] 0x%lx, active_idx %lu, staged_bitset %lu, staged_head_idx[0] %lu, staged_head_idx[1] %lu, staged_head_idx[2] %lu, staged_head_idx[3] %lu, staged_popcnt_wmk %lu, txn_pool_free_cnt %lu/%lu, block_pool_popcnt %lu/%lu\n", + sched->canary, sched->exec_cnt, sched->root_idx, sched->txn_exec_ready_bitset[ 0 ], sched->sigverify_ready_bitset[ 0 ], sched->cert_ready_bitset[ 0 ], sched->active_bank_idx, sched->staged_bitset, sched->staged_head_bank_idx[ 0 ], sched->staged_head_bank_idx[ 1 ], sched->staged_head_bank_idx[ 2 ], sched->staged_head_bank_idx[ 3 ], sched->staged_popcnt_wmk, sched->txn_pool_free_cnt, sched->depth, sched->block_pool_popcnt, sched->block_cnt_max ); fd_sched_block_t * active_block = block_pool_ele( sched, sched->active_bank_idx ); if( active_block ) print_block_debug( sched, active_block ); for( int l=0; l<(int)FD_SCHED_MAX_STAGING_LANES; l++ ) { @@ -819,7 +794,6 @@ fd_sched_new( void * mem, for( ulong i=0; imax_txn_per_slot = max_txn_per_slot; sched->max_mblk_per_slot = fd_ulong_min( FD_SCHED_MAX_MBLK_PER_SLOT*max_shreds_per_block/FD_SHRED_BLK_MAX, UINT_MAX ); /* mblk_cnt is uint */ sched->exec_cnt = exec_cnt; - sched->poh_simd_max = fd_sha256_simd_lane_max(); FD_CHECK_ERR( sched->poh_simd_max<=FD_SCHED_POH_PARA, "overly wide PoH SHA batch" ); - sched->poh_simd_min = fd_sha256_simd_lane_min(); sched->bypass_poh_verify = 0; sched->bypass_alut_resolution = 0; sched->root_idx = ULONG_MAX; @@ -849,7 +821,6 @@ fd_sched_new( void * mem, sched->txn_exec_ready_bitset[ 0 ] = fd_ulong_mask_lsb( (int)exec_cnt ); sched->sigverify_ready_bitset[ 0 ] = fd_ulong_mask_lsb( (int)exec_cnt ); - sched->poh_ready_bitset[ 0 ] = fd_ulong_mask_lsb( (int)exec_cnt ); sched->cert_ready_bitset[ 0 ] = fd_ulong_mask_lsb( (int)exec_cnt ); sched->txn_pool_free_cnt = depth-1UL; /* -1 because index 0 is unusable as a sentinel reserved by the dispatcher */ @@ -902,7 +873,6 @@ fd_sched_join( void * mem ) { for( ulong i=0; iblock_pool[ i ].mblks_unhashed ); - mblk_slist_join( sched->block_pool[ i ].mblks_hashing_in_progress ); mblk_slist_join( sched->block_pool[ i ].mblks_mixin_in_progress ); } @@ -954,7 +924,7 @@ fd_sched_can_ingest_cnt( fd_sched_t * sched ) { int fd_sched_is_drained( fd_sched_t * sched ) { - int nothing_inflight = sched->exec_cnt==(ulong)fd_ulong_popcnt( sched->txn_exec_ready_bitset[ 0 ]&sched->sigverify_ready_bitset[ 0 ]&sched->poh_ready_bitset[ 0 ]&sched->cert_ready_bitset[ 0 ] ); + int nothing_inflight = sched->exec_cnt==(ulong)fd_ulong_popcnt( sched->txn_exec_ready_bitset[ 0 ]&sched->sigverify_ready_bitset[ 0 ]&sched->cert_ready_bitset[ 0 ] ); int nothing_queued = sched->active_bank_idx==ULONG_MAX; return nothing_inflight && nothing_queued; } @@ -967,7 +937,7 @@ fd_sched_cert_dispatch( fd_sched_t * sched, FD_TEST( bank_idx==sched->active_bank_idx ); fd_sched_block_t * block = block_pool_ele( sched, bank_idx ); FD_TEST( block->block_end_signaled && !block->block_end_done ); - ulong idle = sched->txn_exec_ready_bitset[ 0 ]&sched->sigverify_ready_bitset[ 0 ]&sched->poh_ready_bitset[ 0 ]&sched->cert_ready_bitset[ 0 ]; + ulong idle = sched->txn_exec_ready_bitset[ 0 ]&sched->sigverify_ready_bitset[ 0 ]&sched->cert_ready_bitset[ 0 ]; ulong claimed = 0UL; for( ; idle && cnt; cnt--, idle=fd_ulong_pop_lsb( idle ) ) { int t = fd_ulong_find_lsb( idle ); @@ -1268,8 +1238,8 @@ fd_sched_fec_ingest( fd_sched_t * sched, /* We just received a FEC set, which may have made all transactions in a partially parsed microblock available. If this were a malformed - block that ends in a non-tick microblock, there's not going to be a - hashing task from the missing ending tick to drain the mixin queue. + block that ends in a non-tick microblock, there's not going to be an + ending tick to drain the mixin queue. So we try to drain the mixin queue right here. Another option is to drain it at dispatch time, when we are about to dispatch the end of block signal, right before the check for whether block should @@ -1295,7 +1265,7 @@ fd_sched_task_next_ready( fd_sched_t * sched, fd_sched_task_t * out ) { FD_TEST( ref_q_empty( sched->ref_q ) ); ulong exec_ready_bitset0 = sched->txn_exec_ready_bitset[ 0 ]; - ulong exec_fully_ready_bitset = sched->sigverify_ready_bitset[ 0 ] & sched->poh_ready_bitset[ 0 ] & sched->cert_ready_bitset[ 0 ] & exec_ready_bitset0; + ulong exec_fully_ready_bitset = sched->sigverify_ready_bitset[ 0 ] & sched->cert_ready_bitset[ 0 ] & exec_ready_bitset0; if( FD_UNLIKELY( !exec_fully_ready_bitset ) ) { /* Early exit if no exec tiles available. */ return 0UL; @@ -1338,6 +1308,22 @@ fd_sched_task_next_ready( fd_sched_t * sched, fd_sched_task_t * out ) { return 1UL; } + /* PoH params are set once block start is done. Each microblock is at + most one hash plus a mixin, cheap enough to do inline. */ + if( FD_LIKELY( block->block_start_done && block->mblk_unhashed_cnt ) ) { + int dead_reason = poh_hash_unhashed( sched, block ); + if( FD_UNLIKELY( dead_reason!=FD_SCHED_DEAD_REASON_NONE ) ) { + handle_bad_block( sched, block, dead_reason ); + out->task_type = FD_SCHED_TT_MARK_DEAD; + out->mark_dead->bank_idx = bank_idx; + sched->next_ready_last_tick = fd_tickcount(); + sched->next_ready_last_bank_idx = bank_idx; + return 1UL; + } + maybe_switch_block( sched, bank_idx ); + if( FD_UNLIKELY( sched->active_bank_idx!=bank_idx ) ) return fd_sched_task_next_ready( sched, out ); + } + ulong exec_tile_idx0 = fd_ulong_if( !!exec_fully_ready_bitset, (ulong)fd_ulong_find_lsb( exec_fully_ready_bitset ), ULONG_MAX ); ulong exec_queued_cnt = block->txn_parsed_cnt-block->txn_exec_in_flight_cnt-block->txn_exec_done_cnt; if( FD_LIKELY( exec_queued_cnt>0UL && fd_ulong_popcnt( exec_fully_ready_bitset ) ) ) { /* Optimize for no fork switching. */ @@ -1367,19 +1353,6 @@ fd_sched_task_next_ready( fd_sched_t * sched, fd_sched_task_t * out ) { FD_LOG_CRIT(( "invariant violation: no ready transaction found but block->txn_exec_in_flight_cnt==0" )); } - /* Next up are PoH tasks. Same dispatching policy as sigverify - tasks. */ - ulong poh_ready_bitset = exec_fully_ready_bitset; - ulong poh_hashing_queued_cnt = block->mblk_cnt-block->poh_mblk_in_flight_cnt-block->poh_hashing_done_cnt; - int poh_threshold = fd_int_if( block->txn_exec_in_flight_cnt>0U, 0, 1 ); - if( FD_LIKELY( poh_hashing_queued_cnt>0UL && fd_ulong_popcnt( poh_ready_bitset )>poh_threshold ) ) { - ulong max_cnt = poh_batch_max_cnt( sched, poh_hashing_queued_cnt, (ulong)fd_ulong_popcnt( poh_ready_bitset ), (ulong)poh_threshold ); - dispatch_poh( sched, block, bank_idx, fd_ulong_find_lsb( poh_ready_bitset ), max_cnt, out ); - sched->next_ready_last_tick = fd_tickcount(); - sched->next_ready_last_bank_idx = bank_idx; - return 1UL; - } - /* Dispatch more sigverify tasks only if at least one exec tile is executing transactions or completely idle. Allow at most one sigverify task in-flight per tile, and only dispatch to @@ -1417,9 +1390,9 @@ fd_sched_task_next_ready( fd_sched_t * sched, fd_sched_task_t * out ) { block->txn_exec_in_flight_cnt++; sched->metrics->txn_max_in_flight_cnt = fd_uint_max( sched->metrics->txn_max_in_flight_cnt, block->txn_exec_in_flight_cnt ); - if( FD_UNLIKELY( (~sched->txn_exec_ready_bitset[ 0 ])&(~sched->sigverify_ready_bitset[ 0 ])&(~sched->poh_ready_bitset[ 0 ])&fd_ulong_mask_lsb( (int)sched->exec_cnt ) ) ) FD_LOG_CRIT(( "invariant violation: txn_exec_ready_bitset 0x%lx sigverify_ready_bitset 0x%lx poh_ready_bitset 0x%lx", sched->txn_exec_ready_bitset[ 0 ], sched->sigverify_ready_bitset[ 0 ], sched->poh_ready_bitset[ 0 ] )); - ulong total_exec_busy_cnt = sched->exec_cnt-(ulong)fd_ulong_popcnt( sched->txn_exec_ready_bitset[ 0 ]&sched->sigverify_ready_bitset[ 0 ]&sched->poh_ready_bitset[ 0 ] ); - if( FD_UNLIKELY( block->txn_exec_in_flight_cnt+block->txn_sigverify_in_flight_cnt+block->poh_hashing_in_flight_cnt!=total_exec_busy_cnt ) ) { + if( FD_UNLIKELY( (~sched->txn_exec_ready_bitset[ 0 ])&(~sched->sigverify_ready_bitset[ 0 ])&fd_ulong_mask_lsb( (int)sched->exec_cnt ) ) ) FD_LOG_CRIT(( "invariant violation: txn_exec_ready_bitset 0x%lx sigverify_ready_bitset 0x%lx", sched->txn_exec_ready_bitset[ 0 ], sched->sigverify_ready_bitset[ 0 ] )); + ulong total_exec_busy_cnt = sched->exec_cnt-(ulong)fd_ulong_popcnt( sched->txn_exec_ready_bitset[ 0 ]&sched->sigverify_ready_bitset[ 0 ] ); + if( FD_UNLIKELY( block->txn_exec_in_flight_cnt+block->txn_sigverify_in_flight_cnt!=total_exec_busy_cnt ) ) { /* Ideally we'd simply assert that the two sides of the equation are equal. But abandoned blocks throw a wrench into this. We allow abandoned blocks to have in-flight transactions that are @@ -1443,7 +1416,6 @@ fd_sched_task_next_ready( fd_sched_t * sched, fd_sched_task_t * out ) { } total_in_flight += staged_block->txn_exec_in_flight_cnt; total_in_flight += staged_block->txn_sigverify_in_flight_cnt; - total_in_flight += staged_block->poh_hashing_in_flight_cnt; } } if( FD_UNLIKELY( total_in_flight!=total_exec_busy_cnt ) ) { @@ -1461,18 +1433,6 @@ fd_sched_task_next_ready( fd_sched_t * sched, fd_sched_task_t * out ) { /* At this point txn_queued_cnt==0 */ - /* Next up are PoH tasks. Same dispatching policy as sigverify. */ - ulong poh_ready_bitset = exec_fully_ready_bitset; - ulong poh_hashing_queued_cnt = block->mblk_cnt-block->poh_mblk_in_flight_cnt-block->poh_hashing_done_cnt; - int poh_threshold = fd_int_if( block->fec_eos||block->txn_exec_in_flight_cnt>0U||sched->exec_cnt==1UL, 0, 1 ); - if( FD_LIKELY( poh_hashing_queued_cnt>0UL && fd_ulong_popcnt( poh_ready_bitset )>poh_threshold ) ) { - ulong max_cnt = poh_batch_max_cnt( sched, poh_hashing_queued_cnt, (ulong)fd_ulong_popcnt( poh_ready_bitset ), (ulong)poh_threshold ); - dispatch_poh( sched, block, bank_idx, fd_ulong_find_lsb( poh_ready_bitset ), max_cnt, out ); - sched->next_ready_last_tick = fd_tickcount(); - sched->next_ready_last_bank_idx = bank_idx; - return 1UL; - } - /* Try to dispatch a sigverify task, but leave one exec tile idle for critical path execution, unless there's not going to be any more real transactions for the critical path. In the degenerate case of @@ -1562,7 +1522,6 @@ fd_sched_task_done( fd_sched_t * sched, ulong task_type, ulong txn_idx, ulong ex bank_idx = sched->tile_to_bank_idx[ exec_idx ]; break; } - case FD_SCHED_TT_POH_HASH: case FD_SCHED_TT_CERT_VERIFY: { (void)txn_idx; bank_idx = sched->tile_to_bank_idx[ exec_idx ]; @@ -1662,38 +1621,6 @@ fd_sched_task_done( fd_sched_t * sched, ulong task_type, ulong txn_idx, ulong ex } break; } - case FD_SCHED_TT_POH_HASH: { - block->poh_hashing_in_flight_cnt--; - FD_TEST( !fd_ulong_extract_bit( sched->poh_ready_bitset[ 0 ], exec_tile_idx ) ); - sched->poh_ready_bitset[ 0 ] = fd_ulong_set_bit( sched->poh_ready_bitset[ 0 ], exec_tile_idx ); - fd_execrp_poh_hash_done_msg_t * msg = fd_type_pun( data ); - FD_TEST( msg->cnt && msg->cnt<=FD_EXECRP_POH_PARA ); - fd_sched_poh_hash_t const * task = sched->poh_inflight+exec_tile_idx; - FD_TEST( msg->cnt==task->cnt ); - - /* Once the block is found to be bad, the remaining elements are - put back on a list so that they get freed along with the - block. */ - int dead_reason = FD_SCHED_DEAD_REASON_NONE; - for( ulong i=0UL; icnt; i++ ) { - block->poh_mblk_in_flight_cnt--; - uint mblk_idx = (uint)task->mblk_idx[ i ]; - fd_sched_mblk_t * mblk = sched->mblk_pool+mblk_idx; - mblk->curr_hashcnt += task->hashcnt; - memcpy( mblk->curr_hash, msg->hash+i, sizeof(fd_hash_t) ); - if( FD_UNLIKELY( dead_reason!=FD_SCHED_DEAD_REASON_NONE ) || mblk->curr_hashcnthashcnt ) { - mblk_slist_idx_push_tail( block->mblks_hashing_in_progress, mblk_idx, sched->mblk_pool ); - continue; - } - dead_reason = poh_retire_mblk( sched, block, mblk_idx ); - } - if( FD_LIKELY( dead_reason==FD_SCHED_DEAD_REASON_NONE ) ) dead_reason = poh_retire_ready_mblks( sched, block ); - if( FD_UNLIKELY( dead_reason!=FD_SCHED_DEAD_REASON_NONE ) ) { - handle_bad_block( sched, block, dead_reason ); - return dead_reason; - } - break; - } case FD_SCHED_TT_CERT_VERIFY: { FD_TEST( block->cert_in_flight_cnt ); block->cert_in_flight_cnt--; @@ -2124,8 +2051,6 @@ add_block( fd_sched_t * sched, block->txn_exec_done_cnt = 0U; block->txn_sigverify_in_flight_cnt = 0U; block->txn_sigverify_done_cnt = 0U; - block->poh_hashing_in_flight_cnt = 0U; - block->poh_mblk_in_flight_cnt = 0U; block->poh_hashing_done_cnt = 0U; block->poh_hash_cmp_done_cnt = 0U; block->cert_in_flight_cnt = 0U; @@ -2147,7 +2072,6 @@ add_block( fd_sched_t * sched, block->parse_mblk_idx = UINT_MAX; mblk_slist_remove_all( block->mblks_unhashed, sched->mblk_pool ); - mblk_slist_remove_all( block->mblks_hashing_in_progress, sched->mblk_pool ); mblk_slist_remove_all( block->mblks_mixin_in_progress, sched->mblk_pool ); block->last_mblk_is_tick = 0; block->tick_hashcnt_wmk = 0UL; @@ -2547,7 +2471,6 @@ fd_sched_parse( fd_sched_t * sched, fd_sched_block_t * block, fd_sched_alut_ctx_ mblk->end_txn_idx = mblk->start_txn_idx+hdr->txn_cnt; mblk->curr_txn_idx = mblk->start_txn_idx; mblk->hashcnt = fd_ulong_sat_sub( hdr->hash_cnt, fd_ulong_if( !hdr->txn_cnt, 0UL, 1UL ) ); /* For pure hashing, implement saturating sub for non-tick microblocks. */ - mblk->curr_hashcnt = 0UL; mblk->curr_sig_cnt = 0U; mblk->mixin_txn_idx = 0U; mblk->is_tick = !hdr->txn_cnt; @@ -2947,7 +2870,7 @@ dispatch_sigverify( fd_sched_t * sched, fd_sched_block_t * block, ulong bank_idx sched->tile_to_bank_idx[ exec_tile_idx ] = bank_idx; block->txn_sigverify_in_flight_cnt++; block->txn_sigverify_next_idx = sched->txn_info_pool[ txn_idx ].next_idx; - if( FD_UNLIKELY( (~sched->txn_exec_ready_bitset[ 0 ])&(~sched->sigverify_ready_bitset[ 0 ])&(~sched->poh_ready_bitset[ 0 ])&fd_ulong_mask_lsb( (int)sched->exec_cnt ) ) ) FD_LOG_CRIT(( "invariant violation: txn_exec_ready_bitset 0x%lx sigverify_ready_bitset 0x%lx poh_ready_bitset 0x%lx", sched->txn_exec_ready_bitset[ 0 ], sched->sigverify_ready_bitset[ 0 ], sched->poh_ready_bitset[ 0 ] )); + if( FD_UNLIKELY( (~sched->txn_exec_ready_bitset[ 0 ])&(~sched->sigverify_ready_bitset[ 0 ])&fd_ulong_mask_lsb( (int)sched->exec_cnt ) ) ) FD_LOG_CRIT(( "invariant violation: txn_exec_ready_bitset 0x%lx sigverify_ready_bitset 0x%lx", sched->txn_exec_ready_bitset[ 0 ], sched->sigverify_ready_bitset[ 0 ] )); } /* Retires a microblock whose PoH hashing has completed, and eagerly @@ -2956,7 +2879,6 @@ dispatch_sigverify( fd_sched_t * sched, fd_sched_block_t * block, ulong bank_idx static int poh_retire_mblk( fd_sched_t * sched, fd_sched_block_t * block, uint mblk_idx ) { fd_sched_mblk_t * mblk = sched->mblk_pool+mblk_idx; - FD_TEST( mblk->curr_hashcnt==mblk->hashcnt ); block->poh_hashing_done_cnt++; sched->metrics->mblk_poh_hashed_cnt++; if( FD_LIKELY( !mblk->is_tick ) ) { @@ -2980,83 +2902,21 @@ poh_retire_mblk( fd_sched_t * sched, fd_sched_block_t * block, uint mblk_idx ) { return FD_SCHED_DEAD_REASON_NONE; } -/* Retires microblocks at the head of the unhashed queue that have - nothing left for PoH to hash. Returns FD_SCHED_DEAD_REASON_NONE on - success, else the dead reason. */ +/* Hashes and retires all unhashed microblocks in parse order. Returns + FD_SCHED_DEAD_REASON_NONE on success, else the dead reason. */ static int -poh_retire_ready_mblks( fd_sched_t * sched, fd_sched_block_t * block ) { +poh_hash_unhashed( fd_sched_t * sched, fd_sched_block_t * block ) { while( block->mblk_unhashed_cnt ) { - uint mblk_idx = (uint)mblk_slist_idx_peek_head( block->mblks_unhashed, sched->mblk_pool ); - fd_sched_mblk_t * mblk = sched->mblk_pool+mblk_idx; - if( FD_LIKELY( mblk->curr_hashcnt!=mblk->hashcnt ) ) break; - mblk_slist_idx_pop_head( block->mblks_unhashed, sched->mblk_pool ); + uint mblk_idx = (uint)mblk_slist_idx_pop_head( block->mblks_unhashed, sched->mblk_pool ); block->mblk_unhashed_cnt--; + fd_sched_mblk_t * mblk = sched->mblk_pool+mblk_idx; + for( ulong i=0UL; ihashcnt; i++ ) fd_sha256_hash( mblk->curr_hash, sizeof(fd_hash_t), mblk->curr_hash ); int dead_reason = poh_retire_mblk( sched, block, mblk_idx ); if( FD_UNLIKELY( dead_reason!=FD_SCHED_DEAD_REASON_NONE ) ) return dead_reason; } return verify_ticks_eager( block ); } -/* Assembles a PoH task for the given exec tile. Assumes there is a - PoH task available for dispatching. A microblock with nothing left - to hash is dispatched as a degenerate zero-hashcnt task, so that it - retires through fd_sched_task_done like any other PoH task. */ -static void -dispatch_poh( fd_sched_t * sched, fd_sched_block_t * block, ulong bank_idx, int exec_tile_idx, ulong max_cnt, fd_sched_task_t * out ) { - FD_TEST( max_cnt>=1UL && max_cnt<=FD_SCHED_POH_PARA ); - /* Every element of a batch advances by the same hashcnt, so the - batch hashcnt is the smallest remaining hashcnt of its elements. - Elements with more work left are re-queued on completion. */ - fd_sched_poh_hash_t * poh = out->poh_hash; - ulong cnt = 0UL; - ulong hashcnt = ULONG_MAX; - while( cntmblks_hashing_in_progress, sched->mblk_pool ) ) ) { - /* There's a PoH task in progress, just continue working on that. */ - list = block->mblks_hashing_in_progress; - } else if( FD_LIKELY( block->mblk_unhashed_cnt ) ) { - list = block->mblks_unhashed; - } else { - break; /* Nothing more to batch. */ - } - uint mblk_idx = (uint)mblk_slist_idx_pop_head( list, sched->mblk_pool ); - fd_sched_mblk_t * mblk = sched->mblk_pool+mblk_idx; - ulong hashcnt_todo = mblk->hashcnt-mblk->curr_hashcnt; - /* A microblock with nothing left to hash can't share a batch with - one that has work left. The batch hashcnt would be zero, and the - latter would never make progress. */ - if( FD_UNLIKELY( cnt && (!hashcnt_todo)!=(!hashcnt) ) ) { - mblk_slist_idx_push_head( list, mblk_idx, sched->mblk_pool ); - break; - } - if( list==block->mblks_unhashed ) block->mblk_unhashed_cnt--; - hashcnt = fd_ulong_min( hashcnt, hashcnt_todo ); - poh->mblk_idx[ cnt ] = mblk_idx; - memcpy( poh->hash+cnt, mblk->curr_hash, sizeof(fd_hash_t) ); - cnt++; - block->poh_mblk_in_flight_cnt++; - } - FD_TEST( cnt ); /* poh_hashing_queued_cnt>0 implies at least one queued microblock. */ - - /* See FD_SCHED_MAX_POH_HASHES_PER_TASK. A batch's per iteration - cost depends on its width. */ - ulong iters_max = FD_SCHED_MAX_POH_HASHES_PER_TASK; - if( cnt>=sched->poh_simd_min ) iters_max = fd_ulong_max( (FD_SCHED_MAX_POH_HASHES_PER_TASK<<8)/fd_sha256_simd_iter_cost_q8( cnt ), 1UL ); - hashcnt = fd_ulong_min( hashcnt, iters_max ); - out->task_type = FD_SCHED_TT_POH_HASH; - poh->bank_idx = bank_idx; - poh->exec_idx = (ulong)exec_tile_idx; - poh->cnt = cnt; - poh->hashcnt = hashcnt; - sched->poh_inflight[ exec_tile_idx ] = *poh; - - sched->poh_ready_bitset[ 0 ] = fd_ulong_clear_bit( sched->poh_ready_bitset[ 0 ], exec_tile_idx ); - sched->tile_to_bank_idx[ exec_tile_idx ] = bank_idx; - block->poh_hashing_in_flight_cnt++; - if( FD_UNLIKELY( (~sched->txn_exec_ready_bitset[ 0 ])&(~sched->sigverify_ready_bitset[ 0 ])&(~sched->poh_ready_bitset[ 0 ])&fd_ulong_mask_lsb( (int)sched->exec_cnt ) ) ) FD_LOG_CRIT(( "invariant violation: txn_exec_ready_bitset 0x%lx sigverify_ready_bitset 0x%lx poh_ready_bitset 0x%lx", sched->txn_exec_ready_bitset[ 0 ], sched->sigverify_ready_bitset[ 0 ], sched->poh_ready_bitset[ 0 ] )); -} - /* Does up to one transaction mixin. Returns 1 if one mixin was done, 2 if that mixin also completed a microblock, 0 if no transaction mixin was available, -1 if there is a PoH verify error. */ @@ -3087,7 +2947,7 @@ maybe_mixin( fd_sched_t * sched, fd_sched_block_t * block ) { /* If we've decided to start mixin on a partially parsed microblock, there better be nothing else in-progress. Otherwise, they might clobber the per-block bmtree for mixin. */ - if( FD_UNLIKELY( mblk->curr_txn_idx!=mblk->start_txn_idx && (block->poh_mblk_in_flight_cnt||!mblk_slist_is_empty( block->mblks_hashing_in_progress, sched->mblk_pool )||!mblk_slist_is_empty( block->mblks_mixin_in_progress, sched->mblk_pool )) ) ) { + if( FD_UNLIKELY( mblk->curr_txn_idx!=mblk->start_txn_idx && !mblk_slist_is_empty( block->mblks_mixin_in_progress, sched->mblk_pool ) ) ) { sched->print_buf_sz = 0UL; print_all( sched, block ); FD_LOG_CRIT(( "invariant violation end_txn_idx %lu start_txn_idx %lu curr_txn_idx %lu: %s", mblk->end_txn_idx, mblk->start_txn_idx, mblk->curr_txn_idx, sched->print_buf )); @@ -3108,9 +2968,7 @@ maybe_mixin( fd_sched_t * sched, fd_sched_block_t * block ) { if( FD_UNLIKELY( mblk->curr_txn_idx>=block->txn_parsed_cnt || /* Nothing more to mixin for this microblock. */ (mblk->end_txn_idx>block->txn_parsed_cnt && /* There is something to mixin, but the microblock isn't fully parsed yet ... */ mblk->curr_txn_idx==mblk->start_txn_idx && /* ... and we haven't started mixin on it yet ... */ - (block->poh_mblk_in_flight_cnt || /* ... and another microblock is in-progress and might preempt this microblock and clobber the bmtree, so we shouldn't start the partial microblock just yet. Counting microblocks rather than tasks matters: a batched PoH task is retired one microblock at a time, and the ones not yet retired are off every list. */ - !mblk_slist_is_empty( block->mblks_hashing_in_progress, sched->mblk_pool ) || - !mblk_slist_is_empty( block->mblks_mixin_in_progress, sched->mblk_pool ))) ) ) { + !mblk_slist_is_empty( block->mblks_mixin_in_progress, sched->mblk_pool )) ) ) { /* ... and another microblock is in-progress and might preempt this microblock and clobber the bmtree, so we shouldn't start the partial microblock just yet. */ mblk_slist_idx_push_tail( block->mblks_mixin_in_progress, mblk_idx, sched->mblk_pool ); /* No other microblock in the mixin queue. */ @@ -3577,8 +3435,8 @@ subtree_prune( fd_sched_t * sched, ulong bank_idx, ulong except_idx ) { we're pruning implies that the bank thinks there's nothing more in-flight. */ if( FD_UNLIKELY( block_is_in_flight( head ) ) ) { - FD_LOG_CRIT(( "invariant violation: block has tasks in flight (%u exec %u sigverify %u poh %u cert), slot %lu, parent slot %lu", - head->txn_exec_in_flight_cnt, head->txn_sigverify_in_flight_cnt, head->poh_hashing_in_flight_cnt, head->cert_in_flight_cnt, head->slot, head->parent_slot )); + FD_LOG_CRIT(( "invariant violation: block has tasks in flight (%u exec %u sigverify %u cert), slot %lu, parent slot %lu", + head->txn_exec_in_flight_cnt, head->txn_sigverify_in_flight_cnt, head->cert_in_flight_cnt, head->slot, head->parent_slot )); } if( FD_UNLIKELY( head->in_rdisp ) ) { /* We should have removed it from the dispatcher when we were @@ -3589,7 +3447,6 @@ subtree_prune( fd_sched_t * sched, ulong bank_idx, ulong except_idx ) { /* Return remaining mblk descriptors to the shared pool. */ free_mblk_slist( sched, head, head->mblks_unhashed ); - free_mblk_slist( sched, head, head->mblks_hashing_in_progress ); free_mblk_slist( sched, head, head->mblks_mixin_in_progress ); if( FD_UNLIKELY( !head->block_end_done ) ) { diff --git a/src/discof/replay/fd_sched.h b/src/discof/replay/fd_sched.h index 17135e25aee..0798bbd7e8d 100644 --- a/src/discof/replay/fd_sched.h +++ b/src/discof/replay/fd_sched.h @@ -149,7 +149,6 @@ typedef struct fd_sched_txn_info fd_sched_txn_info_t; #define FD_SCHED_TT_TXN_EXEC (3UL) /* (e) Transaction execution. */ #define FD_SCHED_TT_TXN_SIGVERIFY (4UL) /* (e) Transaction sigverify. */ #define FD_SCHED_TT_LTHASH (5UL) /* (e) Account lthash. */ -#define FD_SCHED_TT_POH_HASH (6UL) /* (e) PoH hashing. */ #define FD_SCHED_TT_MARK_DEAD (7UL) /* (i) Mark the block dead. */ #define FD_SCHED_TT_CERT_VERIFY (8UL) /* (e) Footer cert verify. */ @@ -180,17 +179,6 @@ struct fd_sched_txn_sigverify { }; typedef struct fd_sched_txn_sigverify fd_sched_txn_sigverify_t; -#define FD_SCHED_POH_PARA 16 -struct fd_sched_poh_hash { - ulong bank_idx; - ulong exec_idx; - ulong cnt; /* In [1,FD_SCHED_POH_PARA] */ - ulong hashcnt; /* Same for every element of the batch */ - ulong mblk_idx[ FD_SCHED_POH_PARA ]; - fd_hash_t hash [ FD_SCHED_POH_PARA ]; -}; -typedef struct fd_sched_poh_hash fd_sched_poh_hash_t; - struct fd_sched_mark_dead { ulong bank_idx; }; @@ -203,7 +191,6 @@ struct fd_sched_task { fd_sched_block_end_t block_end[ 1 ]; fd_sched_txn_exec_t txn_exec[ 1 ]; fd_sched_txn_sigverify_t txn_sigverify[ 1 ]; - fd_sched_poh_hash_t poh_hash[ 1 ]; fd_sched_mark_dead_t mark_dead[ 1 ]; }; }; @@ -227,7 +214,7 @@ typedef struct fd_sched_task fd_sched_task_t; #define FD_SCHED_DEAD_REASON_ZERO_HASH_TICK (14) /* Tick advanced zero hashes; PoH params were unknown when the tick parsed. */ #define FD_SCHED_DEAD_REASON_ZERO_HASH_TICK_INGEST (15) /* Tick advanced zero hashes, detected at FEC ingest. */ #define FD_SCHED_DEAD_REASON_TICK_HASH_MISMATCH (16) /* PoH hash of a tick did not verify. */ -#define FD_SCHED_DEAD_REASON_ENTRY_HASH_MISMATCH (17) /* PoH hash of a transaction entry did not verify, detected when the entry's PoH hashing task completed. */ +#define FD_SCHED_DEAD_REASON_ENTRY_HASH_MISMATCH (17) /* PoH hash of a transaction entry did not verify, detected during inline PoH verification. */ #define FD_SCHED_DEAD_REASON_ENTRY_HASH_MISMATCH_INGEST (18) /* PoH hash of a transaction entry did not verify, detected at FEC ingest when a later FEC set completed the entry's transactions. */ #define FD_SCHED_DEAD_REASON_DEAD_ANCESTOR (19) /* The block went down with its lineage. Whether the lineage was discarded or ruled invalid is distinguished by fd_sched_block_is_discarded. */ #define FD_SCHED_DEAD_REASON_BAD_BLOCK_MARKER (20) /* An Alpenglow block marker (header, footer, genesis certificate or update parent) failed to parse or had an unknown kind. */ @@ -391,22 +378,15 @@ fd_sched_cert_dispatch( fd_sched_t * sched, the scheduler tries to exploit idle cycles in the exec tiles during times of low parallelism critical path progression. - This function may also return a PoH hashing task. These tasks are - lower priority than transaction execution, but higher priority than - sigverify. This is because sigverify tasks are generally bite-sized, - whereas PoH hashing can be longer, so we would like to get started on - hashing sooner rather than later. */ + PoH verification is done inline. If it rules the block bad, this + returns a MARK_DEAD task. */ ulong fd_sched_task_next_ready( fd_sched_t * sched, fd_sched_task_t * out ); /* Mark a task as complete. For transaction execution, this means that the effects of the execution are now visible on any core that could execute a subsequent transaction. Returns FD_SCHED_DEAD_REASON_NONE - (0) on success. If, given the result of the task, the block turns - out to be bad, returns the nonzero FD_SCHED_DEAD_REASON_* it was - ruled bad for. Only PoH tasks can rule a block bad, and not only - for a PoH hash mismatch: eager tick verification also runs on this - path. + (0) on success. If a block has been abandoned or marked dead for any reason, it'll be pruned the moment in-flight task count hits 0 due to the last task @@ -460,8 +440,7 @@ fd_sched_block_abandon( fd_sched_t * sched, ulong bank_idx, int cause ); DEAD_ANCESTOR like any other lineage death; fd_sched_block_is_discarded disambiguates discarded from ruled-invalid. The recorded reason is the first one; later failures - on an already-dead block (e.g. an in-flight PoH task draining after - the block was abandoned) do not overwrite it. */ + on an already-dead block do not overwrite it. */ int fd_sched_get_dead_reason( fd_sched_t * sched, ulong bank_idx ); diff --git a/src/discof/replay/fuzz_sched_rdisp.c b/src/discof/replay/fuzz_sched_rdisp.c index decee67e8e2..c5fd8463f12 100644 --- a/src/discof/replay/fuzz_sched_rdisp.c +++ b/src/discof/replay/fuzz_sched_rdisp.c @@ -7,7 +7,6 @@ #include "../../util/fd_util.h" #include "../../util/sanitize/fd_fuzz.h" -#include "fd_execrp.h" #include "fd_rdisp.h" #include "fd_sched.h" #include "../../ballet/bmtree/fd_bmtree.h" @@ -42,7 +41,6 @@ #define TEST_FAIL_NONE 0 #define TEST_FAIL_EXEC 1 #define TEST_FAIL_SIG 2 -#define TEST_FAIL_POH 3 /* Pick any earlier bank, including the root. This gives the fuzzer a wide mix of fork shapes with very little bias. */ @@ -104,12 +102,6 @@ typedef struct { mblk_t mblk[ TEST_MAX_MBLKS_PER_BLK ]; ulong mblk_cnt; - fd_hash_t mblk_start_hash[ TEST_MAX_MBLKS_PER_BLK ]; - fd_hash_t mblk_hashed_hash[ TEST_MAX_MBLKS_PER_BLK ]; - ulong poh_dispatch_mask; /* bit l set once mblk l had a PoH task */ - ulong mblk_pool_idx [ TEST_MAX_MBLKS_PER_BLK ]; - ulong mblk_curr_hashcnt[ TEST_MAX_MBLKS_PER_BLK ]; - fd_hash_t mblk_curr_hash [ TEST_MAX_MBLKS_PER_BLK ]; uchar encoded[ TEST_MAX_BLOCK_BYTES ]; ulong encoded_sz; @@ -151,11 +143,6 @@ typedef struct { ulong txn_idx; ulong exec_idx; ulong local_txn_idx; - ulong poh_cnt; - ulong hashcnt; - ulong mblk_idx [ FD_SCHED_POH_PARA ]; - ulong local_mblk_idx[ FD_SCHED_POH_PARA ]; - fd_hash_t hash [ FD_SCHED_POH_PARA ]; } inflight_t; typedef struct { @@ -340,13 +327,6 @@ verify_block_encoding( block_t const * block ) { FD_TEST( !memcmp( prev_hash, block->end_poh, sizeof(fd_hash_t) ) ); } -static ulong -mblk_task_hashcnt( block_t const * block, ulong local_mblk_idx ) { - FD_TEST( local_mblk_idxmblk_cnt ); - mblk_t const * mblk = block->mblk + local_mblk_idx; - return mblk->txn_cnt ? mblk->wire_hash_cnt-1UL : mblk->wire_hash_cnt; -} - static ulong block_txn_mask( block_t const * block ) { return fd_ulong_mask_lsb( (int)block->txn_cnt ); @@ -560,8 +540,6 @@ build_block( reader_t * r, ulong min_group_txn_cnt = ( txns_remaining + tx_mblk_slots_rem - 1UL ) / tx_mblk_slots_rem; ulong group_txn_cnt = min_group_txn_cnt + read_range( r, txns_remaining - min_group_txn_cnt + 1UL ); - fd_memcpy( block->mblk_start_hash + block->mblk_cnt, prev_hash, sizeof(fd_hash_t) ); - fd_memcpy( block->mblk_hashed_hash + block->mblk_cnt, prev_hash, sizeof(fd_hash_t) ); block->mblk[ block->mblk_cnt ].start_txn = (uint)txn_off; block->mblk[ block->mblk_cnt ].txn_cnt = (uint)group_txn_cnt; block->mblk[ block->mblk_cnt ].wire_hash_cnt = 1UL; @@ -612,7 +590,6 @@ build_block( reader_t * r, /* Alpentick batch: one tick of one hash, so hashes_per_tick is 1. */ block->hashes_per_tick = 1UL; FD_TEST( block->mblk_cntmblk_start_hash + block->mblk_cnt, prev_hash, sizeof(fd_hash_t) ); block->mblk[ block->mblk_cnt ].start_txn = (uint)block->txn_cnt; block->mblk[ block->mblk_cnt ].txn_cnt = 0U; block->mblk[ block->mblk_cnt ].wire_hash_cnt = 1UL; @@ -620,7 +597,6 @@ build_block( reader_t * r, fd_hash_t tick_end[ 1 ]; repeat_hash( tick_end, prev_hash, 1UL ); - fd_memcpy( block->mblk_hashed_hash + block->mblk_cnt-1UL, tick_end, sizeof(fd_hash_t) ); fd_memcpy( block->end_poh, tick_end, sizeof(fd_hash_t) ); fd_microblock_hdr_t tick_hdr = { @@ -663,7 +639,6 @@ build_block( reader_t * r, switch( read_range( r, 6UL ) ) { case 0UL: block->failure_mode = TEST_FAIL_EXEC; break; case 1UL: block->failure_mode = TEST_FAIL_SIG; break; - case 2UL: block->failure_mode = TEST_FAIL_POH; break; default: block->failure_mode = TEST_FAIL_NONE; break; } @@ -894,17 +869,6 @@ ingest_pending_block_idx( case_t * tc, return ULONG_MAX; } -static int -lineage_dead( case_t const * tc, - ulong bank_idx ) { - while( bank_idx>0UL ) { - block_t const * block = tc->block + (bank_idx-1UL); - if( FD_UNLIKELY( block->dead_seen ) ) return 1; - bank_idx = block->parent_idx; - } - return 0; -} - static int descends_from( case_t const * tc, ulong bank_idx, @@ -951,44 +915,6 @@ complete_task( case_t * tc, block->sig_done_mask |= 1UL << task->local_txn_idx; break; } - case FD_SCHED_TT_POH_HASH: { - int is_lineage_dead = lineage_dead( tc, task->bank_idx ); - fd_execrp_poh_hash_done_msg_t msg[ 1 ]; - msg->cnt = task->poh_cnt; - for( ulong i=0UL; ipoh_cnt; i++ ) { - ulong local_mblk_idx = task->local_mblk_idx[ i ]; - FD_TEST( local_mblk_idx < block->mblk_cnt ); - FD_TEST( !memcmp( task->hash+i, block->mblk_curr_hash + local_mblk_idx, sizeof(fd_hash_t) ) ); - repeat_hash( msg->hash+i, task->hash+i, task->hashcnt ); - block->mblk_curr_hashcnt[ local_mblk_idx ] += task->hashcnt; - FD_TEST( block->mblk_curr_hashcnt[ local_mblk_idx ] <= mblk_task_hashcnt( block, local_mblk_idx ) ); - fd_memcpy( block->mblk_curr_hash + local_mblk_idx, msg->hash+i, sizeof(fd_hash_t) ); - if( block->mblk_curr_hashcnt[ local_mblk_idx ] == mblk_task_hashcnt( block, local_mblk_idx ) ) { - FD_TEST( !memcmp( msg->hash+i, block->mblk_hashed_hash + local_mblk_idx, sizeof(fd_hash_t) ) ); - } - } - if( FD_UNLIKELY( block->failure_mode==TEST_FAIL_POH && !block->failure_injected ) ) { - for( ulong i=0UL; ipoh_cnt; i++ ) { - ulong local_mblk_idx = task->local_mblk_idx[ i ]; - if( block->mblk_curr_hashcnt[ local_mblk_idx ] == mblk_task_hashcnt( block, local_mblk_idx ) ) { - block->failure_injected = 1; - msg->hash[ i ].hash[ 0 ] ^= (uchar)0x80; - break; - } - } - } - int rc = fd_sched_task_done( tc->sched, FD_SCHED_TT_POH_HASH, ULONG_MAX, task->exec_idx, msg ); - if( FD_UNLIKELY( block->failure_mode==TEST_FAIL_POH && block->failure_injected ) ) { - FD_TEST( rc>=0 ); - if( FD_UNLIKELY( rc ) ) block->dead_seen = 1; - } else if( FD_UNLIKELY( is_lineage_dead ) ) { - FD_TEST( rc>=0 ); - if( FD_UNLIKELY( rc ) ) block->dead_seen = 1; - } else { - FD_TEST( rc==0 ); - } - break; - } default: FD_LOG_ERR(( "unexpected async task type %lu", task->task_type )); } } @@ -1187,8 +1113,8 @@ run_sched_rdisp_case( uchar const * data, ulong data_sz ) { the same protocol the production replay tile uses: blocks become visible via fd_sched_fec_ingest() and fd_sched_set_poh_params(), replay polls fd_sched_task_next_ready(), BLOCK_START/BLOCK_END and - MARK_DEAD are handled locally, and TXN_EXEC/TXN_SIGVERIFY/POH_HASH - are completed later with fd_sched_task_done() after worker + MARK_DEAD are handled locally, and TXN_EXEC/TXN_SIGVERIFY are completed + later with fd_sched_task_done() after worker responses arrive. */ for( ulong steps=0UL; steps<100000UL; steps++ ) { drain_refs( tc ); @@ -1296,53 +1222,6 @@ run_sched_rdisp_case( uchar const * data, ulong data_sz ) { }; break; } - case FD_SCHED_TT_POH_HASH: { - bank_idx = task->poh_hash->bank_idx; - FD_TEST( bank_idx>0UL && bank_idx<=tc->block_cnt ); - block_t * block = tc->block + (bank_idx-1UL); - FD_TEST( block->start_seen ); - FD_TEST( !block->end_seen ); - FD_TEST( task->poh_hash->cnt && task->poh_hash->cnt<=FD_SCHED_POH_PARA ); - inflight_t * entry = inflight + inflight_cnt++; - *entry = (inflight_t) { - .task_type = FD_SCHED_TT_POH_HASH, - .bank_idx = bank_idx, - .exec_idx = task->poh_hash->exec_idx, - .poh_cnt = task->poh_hash->cnt, - .hashcnt = task->poh_hash->hashcnt, - }; - ulong min_todo = ULONG_MAX; - for( ulong i=0UL; ipoh_hash->cnt; i++ ) { - ulong pool_idx = task->poh_hash->mblk_idx[ i ]; - ulong local_mblk_idx = ULONG_MAX; - for( ulong l=0UL; lmblk_cnt; l++ ) { - if( !fd_ulong_extract_bit( block->poh_dispatch_mask, (int)l ) ) continue; - if( block->mblk_pool_idx[ l ]==pool_idx && block->mblk_curr_hashcnt[ l ]mblk_cnt; l++ ) { - if( fd_ulong_extract_bit( block->poh_dispatch_mask, (int)l ) ) continue; - if( !memcmp( task->poh_hash->hash+i, block->mblk_start_hash + l, sizeof(fd_hash_t) ) ) { local_mblk_idx = l; break; } - } - FD_TEST( local_mblk_idxmblk_cnt ); - block->poh_dispatch_mask = fd_ulong_set_bit( block->poh_dispatch_mask, (int)local_mblk_idx ); - block->mblk_pool_idx [ local_mblk_idx ] = pool_idx; - block->mblk_curr_hashcnt[ local_mblk_idx ] = 0UL; - fd_memcpy( block->mblk_curr_hash + local_mblk_idx, block->mblk_start_hash + local_mblk_idx, sizeof(fd_hash_t) ); - } - FD_TEST( !memcmp( task->poh_hash->hash+i, block->mblk_curr_hash + local_mblk_idx, sizeof(fd_hash_t) ) ); - min_todo = fd_ulong_min( min_todo, mblk_task_hashcnt( block, local_mblk_idx )-block->mblk_curr_hashcnt[ local_mblk_idx ] ); - entry->mblk_idx [ i ] = pool_idx; - entry->local_mblk_idx[ i ] = local_mblk_idx; - fd_memcpy( entry->hash+i, task->poh_hash->hash+i, sizeof(fd_hash_t) ); - } - /* Transaction microblocks declare one hash, which is the mixin, - so their PoH tasks have nothing to hash. */ - FD_TEST( task->poh_hash->hashcnt<=min_todo && ( !min_todo || task->poh_hash->hashcnt ) ); - break; - } default: FD_LOG_ERR(( "unexpected task_type %lu", task->task_type )); } } diff --git a/src/discof/replay/test_sched.c b/src/discof/replay/test_sched.c index 67bd73e5f24..b0fc9c0947a 100644 --- a/src/discof/replay/test_sched.c +++ b/src/discof/replay/test_sched.c @@ -2,7 +2,6 @@ #include #include "../../util/fd_util.h" -#include "fd_execrp.h" #include "fd_sched.h" #include "../../ballet/sha256/fd_sha256.h" #include "../../flamenco/txn/fd_txn_generate.h" @@ -17,10 +16,10 @@ static void test_sched_footprint( void ) { /* Retain the savings from compact shred lengths and 4992-byte transactions under the default scheduler sizing. */ - FD_TEST( fd_sched_footprint( 65536UL, 2048UL, FD_SHRED_BLK_MAX, FD_MAX_TXN_PER_SLOT )==1694540032UL ); + FD_TEST( fd_sched_footprint( 65536UL, 2048UL, FD_SHRED_BLK_MAX, FD_MAX_TXN_PER_SLOT )==1693907200UL ); /* Only the shred length array scales with the shred limit. */ - FD_TEST( fd_sched_footprint( 65536UL, 2048UL, 4UL*FD_SHRED_BLK_MAX, FD_MAX_TXN_PER_SLOT )==1694540032UL+2048UL*3UL*FD_SHRED_BLK_MAX*sizeof(ushort) ); - FD_TEST( fd_sched_footprint( 65536UL, 2048UL, FD_SHRED_BLK_MAX, 5UL*FD_MAX_TXN_PER_SLOT )==1694540032UL ); + FD_TEST( fd_sched_footprint( 65536UL, 2048UL, 4UL*FD_SHRED_BLK_MAX, FD_MAX_TXN_PER_SLOT )==1693907200UL+2048UL*3UL*FD_SHRED_BLK_MAX*sizeof(ushort) ); + FD_TEST( fd_sched_footprint( 65536UL, 2048UL, FD_SHRED_BLK_MAX, 5UL*FD_MAX_TXN_PER_SLOT )==1693907200UL ); FD_TEST( !fd_sched_footprint( 65536UL, 2048UL, 0UL, FD_MAX_TXN_PER_SLOT ) ); FD_TEST( !fd_sched_footprint( 65536UL, 2048UL, FD_SHRED_BLK_MAX, 0UL ) ); } @@ -294,15 +293,6 @@ run_interleaved_fec_residual_case( void ) { case FD_SCHED_TT_TXN_SIGVERIFY: FD_TEST( !fd_sched_task_done( sched, FD_SCHED_TT_TXN_SIGVERIFY, task->txn_sigverify->txn_idx, task->txn_sigverify->exec_idx, NULL ) ); break; - case FD_SCHED_TT_POH_HASH: { - fd_execrp_poh_hash_done_msg_t msg[ 1 ]; - msg->cnt = task->poh_hash->cnt; - for( ulong i=0UL; ipoh_hash->cnt; i++ ) { - repeat_hash( msg->hash+i, task->poh_hash->hash+i, task->poh_hash->hashcnt ); - } - FD_TEST( !fd_sched_task_done( sched, FD_SCHED_TT_POH_HASH, ULONG_MAX, task->poh_hash->exec_idx, msg ) ); - break; - } default: FD_LOG_ERR(( "unexpected task type %lu in interleaved FEC test", task->task_type )); } @@ -395,13 +385,6 @@ run_ag_structure_case( fd_hash_t const * start_poh, case FD_SCHED_TT_MARK_DEAD: FD_TEST( task->mark_dead->bank_idx==2UL ); break; - case FD_SCHED_TT_POH_HASH: { - fd_execrp_poh_hash_done_msg_t msg[ 1 ]; - msg->cnt = task->poh_hash->cnt; - for( ulong i=0UL; ipoh_hash->cnt; i++ ) repeat_hash( msg->hash+i, task->poh_hash->hash+i, task->poh_hash->hashcnt ); - fd_sched_task_done( sched, FD_SCHED_TT_POH_HASH, ULONG_MAX, task->poh_hash->exec_idx, msg ); - break; - } default: FD_LOG_ERR(( "unexpected task_type %lu in alpenglow structure case", task->task_type )); } @@ -751,11 +734,7 @@ run_cert_verify_case( int abandon ) { FD_TEST( !fd_sched_task_done( sched, FD_SCHED_TT_BLOCK_START, ULONG_MAX, ULONG_MAX, NULL ) ); continue; } - FD_TEST( task->task_type==FD_SCHED_TT_POH_HASH ); - fd_execrp_poh_hash_done_msg_t msg[ 1 ]; - msg->cnt = task->poh_hash->cnt; - for( ulong i=0UL; icnt; i++ ) repeat_hash( msg->hash+i, task->poh_hash->hash+i, task->poh_hash->hashcnt ); - FD_TEST( !fd_sched_task_done( sched, FD_SCHED_TT_POH_HASH, ULONG_MAX, task->poh_hash->exec_idx, msg ) ); + FD_LOG_ERR(( "unexpected task type %lu", task->task_type )); } FD_TEST( fd_sched_pruned_block_next( sched )==2UL ); @@ -994,7 +973,6 @@ run_zero_hashcnt_mblk_case( void ) { /* Drain everything the first FEC made available. */ ulong exec_cnt = 0UL; - ulong poh_task_cnt = 0UL; for( ulong step=0UL; step<100UL; step++ ) { fd_sched_task_t task[ 1 ]; if( !fd_sched_task_next_ready( sched, task ) ) break; @@ -1009,21 +987,11 @@ run_zero_hashcnt_mblk_case( void ) { case FD_SCHED_TT_TXN_SIGVERIFY: FD_TEST( !fd_sched_task_done( sched, FD_SCHED_TT_TXN_SIGVERIFY, task->txn_sigverify->txn_idx, task->txn_sigverify->exec_idx, NULL ) ); break; - case FD_SCHED_TT_POH_HASH: { - poh_task_cnt++; - fd_execrp_poh_hash_done_msg_t msg[ 1 ]; - msg->cnt = task->poh_hash->cnt; - for( ulong i=0UL; ipoh_hash->cnt; i++ ) repeat_hash( msg->hash+i, task->poh_hash->hash+i, task->poh_hash->hashcnt ); - FD_TEST( !fd_sched_task_done( sched, FD_SCHED_TT_POH_HASH, ULONG_MAX, task->poh_hash->exec_idx, msg ) ); - break; - } default: FD_LOG_ERR(( "unexpected task type %lu draining first FEC", task->task_type )); } } FD_TEST( exec_cnt==1UL ); - /* The microblock had nothing to hash, but still went out as a task. */ - FD_TEST( poh_task_cnt==1UL ); /* Second FEC: a microblock header declaring two transactions, but only a fragment of the first. No transaction gets parsed out, so @@ -1046,21 +1014,10 @@ run_zero_hashcnt_mblk_case( void ) { FD_TEST( fd_sched_fec_can_ingest( sched, fec ) ); FD_TEST( fd_sched_fec_ingest( sched, fec ) ); - /* The microblock dispatches with nothing to hash, and retiring it - exhausts the block. Deactivation happens in fd_sched_task_done, so - the scheduler simply parks and idles until more of the block shows + /* The microblock has nothing to hash, and retiring it exhausts the + block. The scheduler parks and idles until more of the block shows up. */ fd_sched_task_t task[ 1 ]; - FD_TEST( fd_sched_task_next_ready( sched, task ) ); - FD_TEST( task->task_type==FD_SCHED_TT_POH_HASH ); - FD_TEST( task->poh_hash->cnt==1UL ); - FD_TEST( !task->poh_hash->hashcnt ); - { - fd_execrp_poh_hash_done_msg_t msg[ 1 ]; - msg->cnt = task->poh_hash->cnt; - for( ulong i=0UL; ipoh_hash->cnt; i++ ) repeat_hash( msg->hash+i, task->poh_hash->hash+i, task->poh_hash->hashcnt ); - FD_TEST( !fd_sched_task_done( sched, FD_SCHED_TT_POH_HASH, ULONG_MAX, task->poh_hash->exec_idx, msg ) ); - } FD_TEST( !fd_sched_task_next_ready( sched, task ) ); FD_TEST( fd_sched_is_drained( sched ) ); diff --git a/src/flamenco/runtime/fd_runtime.c b/src/flamenco/runtime/fd_runtime.c index 8b2ec951a62..4ccd3c7081f 100644 --- a/src/flamenco/runtime/fd_runtime.c +++ b/src/flamenco/runtime/fd_runtime.c @@ -1734,7 +1734,8 @@ fd_runtime_process_genesis_block( fd_bank_t * bank, fd_accdb_t * accdb, fd_capture_ctx_t * capture_ctx, fd_runtime_stack_t * runtime_stack ) { - fd_sha256_hash_32_repeated( bank->f.poh.hash, bank->f.poh.hash, bank->f.slot_params.hashes_per_tick * bank->f.ticks_per_slot ); + ulong hash_cnt = bank->f.slot_params.hashes_per_tick * bank->f.ticks_per_slot; + for( ulong i=0UL; if.poh.hash, 32UL, bank->f.poh.hash ); bank->f.execution_fees = 0UL; bank->f.priority_fees = 0UL;

    cIuPsnOs6b^jIt!oT8fBZjH!!h92bxUP4$HOsT-yAcQ!!h8>_^4mSez$I*nEi$AGVud#)Ug0jKt>k<*L=$AC+`-Jl$f z0WTSuAeT8Fjsb5y)LS_m1MWQfnsPV>+~>ScZZi%X18$gco^m(_T&Q!HJmz>f23)Ue zOXYA3xY)`d%DeFNFBSgEoUTmUQ@j6cG`!~W^Uu0{dY&uwrAe*hgxcP#{@y`PUx%qx zE!U1y@p2>@9{a`O6Pto<-$bTh+gE$QZ2KlK#BIR; z*!B%$9uT&Dy-dNjZ)j7n0w2m0G$Wp-U|Zf0bD^_w^#s%DxH``4dz%X#V(~5JuKwdf zhgi6d8bl8`9b&gED|IlO4l(bZ80P+a!08aXz3QSIPKUVl%LV6n%jpoa#xA6PI-Cyi zM&L~4a5}`=hdj(9>H()i+#l?;6>uKMGq?BYcsP&S>dKdt!+BD^*&{JGg0{G-d7=aX#$oGJ09N*#(~Fcy}syk8cgw z)tJ9FZhih^_oxr{RW6i&ZMHPaj$ZQWcCAm$r~$c}?DP3hDVeL=bT$5uW5DkB0sprQ z*wqt^$+-lkrpn>$0XMpNKslT};J z50%6F+Q%VvmGU~v7tS7V+}SIX!`TBaR3nbg=7kTi<0RVri*h)7z-t38DTfcT$DgjB zQ)lzS*#pi#aJ+K(5PN*whuZu=%{sNr^9W}TxLqda>|Z#`ysi2su5FHovj@E7 zWgq2m_JD6Czoi_`9`KZ68Fe-hZYs%<*vcfRB9YsvOQ9aEIK-mBZNs?(UUUXaB<41J0Ihv~oCmz#IG?D2KBL ze51MZy}5AqfEV4HR^QxDID5bY>%3A9XAd~axGFlE7tS7VpH?fC!&%|Gn#H@J86VCH z-@=z2mBU%#OT6KnayTn|qjDGUH{-xr;j2?^hH^M7d?Bxr0p@r(D}0^mHc<{|g|E%@ zeahji@U`EYwo%A19&lFp!X6*49L@?~w_T5w!S~|e#Ol}LG#n- znmyp!&CaQYvj@DsZMqWXcsP5&XWI2v{*~K5!j%i0y%acmz_VB6*NGKy_JCvUa8AU4 zvj_Zs_+L66&K~gnJpnr50?rJ*=KslT};FN`TDu*|-w>y4TGM!if zXAgMP?7qt3E$s2tGTl%PZ)tNTf4I9jaGD2qbKo=&?&iR09{%p;;78d5j78j*7h0*E`q07_h4`W6tQ-yvdsietyRrJP#jvRE)EChD^z~e97{*A4(w6HKS$>NuZ}duc9Aau52g%Vg@iy{qdQ11{D(c4m{qG2j&!IxB}` zz^yx+Q4Ys|C*I4M#f$^TfPIUPQx3;~pMP4Y9F753c==2@90Tt7p}J1cgJZxC3v5sh z$AGh6PpA{|;7oG+=KZQ1jsbf=xuYD80mn`5s}uC#81T>MRw##Kzz2Sft`qU#81T6p z9hJi|;H~}6D2HRfr8?x+33_l0xNxH>%HbIB$41Wg4Z|_u2wVJgf*zbn?x$&+l*2LL zN7obT1U)zgd?x*`%HbGr*6a6_!!h7$g)8X`wYi`bDvnF9F76^ zdh$^@90Lxl*PwtI2aW+3sj*Wz90Oi-rbkJvPdMM4uLH z53|R!f3BV-&o&G0UjEN5QP22=-%gT3Y6aO}# zfArU*>g+#xYRl70zr7ATemgLKmZvk{zf6%e3GIxl<7i`C9rurW;{Gr9BjA1n+>e0! f5pX{O?nl7=2)G{s_aoqb1l*5+`w{qeJOcj<>sieG diff --git a/src/discof/tower/fixtures/voters-398915647.bin b/src/discof/tower/fixtures/voters-398915647.bin deleted file mode 100644 index e1c21e109ea828511d8b171e79144e39e28653c0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 384200 zcmeFa1$10Vf@ZChnL%b|MwwZ*%p_YfGn35B%*@Qp%*|tJEpwIbayfeWqZ2|^-BdWrvGR%ib9D3YSRdI# zZy|#Jf3b@>cSVg{Icvv@hn}u>L`wUc=ix~k9V2R74D&U`(I#^zcX__d)nmGvKWgfz zshuB9@c$>azj7q`bU3#8wE>Q#c96&nlG#B*Ge~X+3Ctjc9mF?-ly(r$3{u%aTr)^* z2XV|GjUAW=bELI{SZ0vU4q}=?dOL_=1{v(Y%M3EwL3A_7WCzjAAhR7rHG?d65XB6# z+JSi%9Nu;i$qcgDK}0jiZU+&}Acq}T2;wJO-a6Mgar_j^Gd9RzK7J4URLhUI93+mPW_g5{H^uSOEic<3xA~-a z;AdDqYSKh;{7lQ=wR!#I_nZ4O;QkD_KLhU1fcrDx{tUQ31Mbg&`!nEi z^3{iMv-&67+yCg~`xRe=_FaChO|B1_^1oVnp_sgceOlsxV)9jJos*we{42G9N*pY zD`CEi<9k?M_e1T+u6Dud%nYIDVkzUQN!6;|Ez@s8xpQW*+=t%OedM zA&wtn`K-Vf;`pJK$7@uvhM5OH%m_?=^A! zD9i6OOi|0sgCA}A#*>4@@nbCaKlE4}Ki2YwWy{ny^Weu>eyq%Dar}78Z@iCM$6Sw} zV0ro!ZN>4NgmrhE5yx{9cHm6rx@I0cCt-IZjS|LgnkDNfj^`w7#;eQX zcuvA{_srMO%!B77toEYW;&@KN9iQ_p5>)a@9V>1t) zldxA^MvCJ(3G0~Xg*cvd;qGwHCT1S|FHgb(ws$FRE}GRmf!za4MDH$+uWNafFjvL#^(;ShFi#mX55B(TJw{Iz z$2WjGK8fQSTHd}x)v{)u9@aOq{N2mV;`qjv&#e%v6mY?*xERJtx zIg3Buy*Y4h9^9J)=jOq^IdETe>3&8pgWV zF=qbP`_C6&`LKHUK;NTnQ$Nc!eE;5bQ}Xv%@?k}rA&1_y&k}dz^&iJuS4sSRbG?%7 zC*ORub#uhD`@i=6^F3f!bB;b1rD5g?JtKGPmgIh)C*!K`h??)W=|hrFpBS~_%235i zba(ZbuI7K|_#5v5y9@Z=zX$B<31+9`>N+2@Vg|pl$VI(2HjNq2xSUz z630`(cZvsz<0;^*&+}(6^Wa(J#;%_$j;DafybG1lT#u)KL&Ogh$5X(*+fR$*Dd2o{ zyfc}3@D%XOJrl(76!4@v@5S*H@Vop~GMjnu6!6(}+r;q{@T_$4vzY7g6!7N(J;m`X za#;edisLEZ!&CBSHS^#p;AW5Kh~p{XC9}hLo9pouaPCH}#qkty!jH$q@f2{TSIM)P zdGHi)+GGR7@f7geZx6)r6ma7=C9|7(@D#BBk(J_j7P-v{Bj+&J<0;^~<>l<0;^f za~_D}Sr;Be_#%$~WdWai(JqO3u+L{>={*HJpy^?a@f7fqf^m|X>+uwDx2i40@f7fY zmcNPPDd4CB(k9b9)>FWz#*Y-oQ@{_8z7)q(z`wt*klf6Jr-1!(trN#nz%O$~OJT0Z zQ@~{}wim}!z!UbI6US4)>uPwXH1pso;DwRKiQ_5YU434O<0;@bFa1)PdGHi)_U3EE z@f2|Gqft_u>+uwD_M>gY@f7ff38$UcTTcPA_~YH11Lx+!y*Y4h9^9J)=jP$B-W>c> z3OMwD!P^VGZFq7})F$CZMtb?}M#zW@k8Rn0@=Ki9;ctCt(&Fx;TN^I)+P7!l)E^D!%C@EeO| z+G?|km~o^D1@xZ9^4jn3HO8}8HV;|F*IbWhu^b#`i#VQ=4)r5;QFA?>lJ=|7MI28_ z*FSwp98XDCFOsvEnFmiv7nwCd98XE7th!7bPf0%=@=+X5N!J@vL$^8blys{@yTtL7 z^!|p)N|=ua&tiEv@nCU0CEcLKQ*k^cy<}Pi-Oj*M(sRmg630{0VP3@6?F>96-PyOB zIG&Pzb?2%$o|0~NE}w2^;3?@(`(}&dDe1ijJj$5gFP@U_l%Rn)p2afg#~yJ!CH?J6 zQr*tLQ_}I14id*x(hn{_6US5174lUoXFeW0CB3HpC~^GY5az1jd+)6_h~p{dFK1)w zdmZpY?e$d;cNND|%y(v97ssmNo zoMrk7aXiKRetcwouLGW9_KVw298WPfj(0&EPcf(2pHtrhfv1?ezL_MBpJ+cn56-+7 z$5YI2=2q7CK;S9n>B%;V<0rQ7{PPLOC3K1 z*ay3@f37&blI6`qzZb_-#&ez**D(T~G7fQUx;UOPE(s2QvGEN$5o;aQ|?sCz?$2=K$%D7X9 zdg6G>IDV2n;&{sVeD_3!%{+L@xcJ3h;&{sVVUZi+c*>Z?9N)w~Uo7T$_hRqd47eA2 z=VrjY*gH1^f7fFFPbuICZxY7Z7$($`-SXT0tzZ1F`fXcRtigy=6ZglcbY{@5lBeI6>GtOWc0Fg}bgNG~ z?ypYL;QQskX6>JpEx)K*iLd*gE-BZh(WjrMTs@|%`F~x&yy!G>@K@bGUKE_j40us+ zLNnk+!3oTO7X`;R16~vy&kT4`a9lIsMZs~*fENYF4j%k*LpYW(yeK%P8StXu7-qnW zg1yXu7X?Q*16~vy%?x-^a8xtkMZr9(%W{~Lr<8{EU zo?v!5uCDV5eh~9UxgJTgHme!8PwA)kl+_En$FrCP_J1#qr(A!gs-b&zc*-@@ z@4LkDlnYkhx8{iBDcUT4 z8jIsuQFZ@y*Ptp1v$*D~L9v|qCI6vtDvRUcgw$5XTcTXX9q3{TM( zdOSrOPtlf)`bHd2(RNNz#@BrOc#3x4)`jBu>GpXaRQQKDeum|z$JWpZ8J+@e+;5vW zo&pXGh^rGXJO$h-elKx61)So^ZE-vW9H)0torK{j;GpG8#PJkx#`WQK;)SPx<6dYb zj;DabS2`n(r-0LT^43Who&r8PdXhMv0)8{(qd1;*q4K*3ItjzG==|yD0rVUUly1&z;&@8;%5V8Hnt1}Pr*spSn;k8}F`_ z$;^YNbPFulB#x(aM}Cf`Z`i?8y6>C^qK6qE`h>^oBDZHPNdACVExCM6U{%=v4s|y(-{;^Cw}sdV<;MxVp|K_%~zT zSmaJG=wP0e;P38>uJ;sh7q1H%<0;_Eb+eg64G%m8oa4iIaXbYaKJbk=o&t_=#4oOy z2TuXtt~y5?PXRA4@=hF20WU3I)f~Tg;3?oxW4DXrDc}z|5}UId4?K(9rZ4@)@f2|M zLXXAq6mb9U<;;PQ2c80skZHX*o&x@KIYvTrJ)Qzil)JMyo&pYc@v=Cc0uJ1t+Z<7O z;3?qbt7nMgDc~8azl-B3;Bz7CCN}fnS>(!|*(r{vfG2w;PGYXdQ^1af{l)PV@YJ|Z z#PJkxl&%$%ntAXPaK)#q#PJkxmV2R-nd|Ws@Pn_7#qkvI=8H$f@f7gMlj)P2dGHkQ zl!ar&@hozA!@d^BQ^1RQ`lm4S;3?p9%QuPRDd1Jte~9BL;O&L#r8M*4Dd3Aa4~XL_ z-~l^QrZU&#Dd3CShl=AV;D-&LIWKcxmDc~g^hKu7V;4DX; zh~p_>&xz#~S9l8eYv6iuJO!LMR4hdlo&s*0qnkLM0v_XgT^vsVXK!3c5rwCK&s>-% zj;Dav#t5m1!c)Kt^EMU7Q@|l|92Lj2F1($0LmdChd%)*^1em4MBjScnk0_P|rXtI91C$5X&x8%HpUxd)yCKHagkIGzH2x#p}mo&tXR z*xT$7Jn$6os!S8a@f2{8yKlwu6ma2TmCd%p15W|Z=(R~4PXRZ~7t`!nJn$57`_LW5 z@f7gBZs*1S@FC299sK(1-@VO7$OBISZ*MYQ98UpHKKe!+PXQk~?r(Ni9(W4)PT+cR zJO#|+k9ThloSO&t=D@jmaBmKrn}@%8bMQ|o;6}ZVIGR*A?AM_5 z9?Wsq4phMR>_7#4-wss359~k%{Ll_mz>n;}e3BiH?LYJ;CfiU0vs6CJlaL^U-AgR`Xl(XtA$~-n04e-xS~czCG}4K4P5d zEskgNk>K7faXgz3uSh=Tlj(tH^D%zTTyZ>`kFKdb%`@SFXY=vtQ+;tfn-A|9`^52V zK9ZJBVxC?PJe!X)%le4pIew29{+2kN&Bv|I1BMs~;T3DP zIG&SR(J>)bN~4oT$)M9M8$bl)Tr(@tjNy?NU&iPCO?QZ{sWw$Fup! zelfH*op??rZpUmXj^|_|lJ{A0JSP+NPUX<16VJ)SfL2q*@tjPQIPh5<&&kBy1~s+m z#B(xHAz-ICo|B0VK8dyI#B(x{p>RKOJSP*irrj0Cv-#L^sgS-@<2ji)8gITho|B2g z%|q$r3D3zy&m^tH@tjOF9ehC?&&kBope#Ch!gDfF?CvmeJSP*yYCjgob21U-QfZw? z;W?RDePoq5o|B0RbE4_w3D4$Z`s4QEcupn~*SsK(=VW5Z@LW2P!gDeau%ov)o|B1H zDQ=77Ihk0R&{ro?cupp~7Az9Sb24#sURa$x;W?RTTQ*P}&&kAu9>>J-oJ_Q;o=zuH zcupqjrW!7eXPwx!?6EkWb>V(m&m`vS7@kEZL-+;F4cFV$D-N@ydCXb3*sNxM6mX8U zmBsNC@b{@p#PJlcW8YVCJO#`mgLf}N&PB?-2ssxi_afw6q}+>;bCLR=wg~-G3OG`! z+q;KW$=Yo1u(nZKPP(*lz=kFrTBlC1tIvqo-_>7lf8++HY-r?gJz38_-XQ(8NYHWbHG zT0sR5h~ru0%1lY3V#ZTiBfqo}$5UEW3m+86Q(ATXQmdr#l-AQ3!^QEG*0`cC#qpHZ zC654=G@jC`o`0)2p3=I|JfVsiPiakx*k2q^X*Jn)UmVXOcRhA-6*HdFim+>mIG)lv zyg00i8Bb|toE#{Qr?fUtIVp~(w9dcG$x zQ>^XccuMO)&!j42Jd0fJc)i8(l-8jvm&Ngvme21wRm^xw%lrH^aXh7!?)W!xJf&4; zZaozNjmiJv+T*Zu^Zh5I}bH(v9EZ;ETn>c=^_elG}qj^%;Ro!<+BpKJNSJ`Yvc_<5EeT3$-UjGu4$wI$@>%lQ*k=+f&@f>58ta?x!&oSopJ1OI8qYE2;<^uXJ)UFCo3V?U z(_;@j$Cv~AEfB|3kg1+~nxkhAd?WiwITP4G9N*aTYbW=M<2lAGdn%b0Lp;ZrTSxX6 z$2YU{uz2F#i>q_-buX^Y#n-*KIv3x+dU5@y6r}kY^xyd{|FBS*s|U{6-T!c%gd-EY z9PQ;5P`l!g*1jP-$1dxgrBKv7F*~H}Q*(UuB(Hv7vtdB~PPHmF&Q53>=m1!0aj< zRqViQRUB3Az&x0vnjM%;m7}^Hm>rp;h8>t~o};E6n7yN;mK~Ulr=zwVnBApY6qt4 z9L?;&tY?nqc3>7mM+-a9ecM1gFbkcdr5%{3)6vQfbl2H@qB|)&D)8KnCtO;gVN|Y{lxK<{g!%n z#PO8<@<>H|%{+L@KH~BP;&{q_(9{q`&GmT7et7wY;&{qFDC&N3JY^rLOVVOy9z11# z@K#@OJY`=o;I=rPvY)lWr?{C1PuV->%@xN}_C0(&N|@{Ml>OET^~CX%eWB6&#PJ-P z|4fstq?rfLvH9B}{lxJco6n4XM;ymIW|w1 zvT$iL51wQ5Y-tyY<2g2O(kM(Bb3LA8^FBWU#qpH=oJps}@f@2cI-R|&nFr6YdGWi` z#PJ-PuW9o`9M7?N*>LsCnR)OWn}4jkUmVY|`O@E$mp9krIW}K%bf7q%WAl=W9*N^Q zHusrX%FoP$=h(dagXQ9Q%6`j~uocYp_`de}I&-y!IKH3dOOl-u$8&5R;&hIRW*$7p z=Cd=*6326Fz9aHgar_`V|JOv>{LMUgj?H`i94n6J*nHT9x8it?&HEp(5@6=Rb8PNE zZM!(0vQK+4ekF4~o@4XHMS6+j353;qXNu#giqXY~G&l47U2nft2>w@q7gosmZTFkZ zB^s=(LEtf0-Q zlI-k})YW6Un*VRT2kd?waPZ>tulImG`8g!2X^!Jn)BJEUW6T?8`>vi~&iY+lm(Mw^ z2aC$&5V}QTsfP!PO7XH)j%QIxvZkm$C3qHcD#qlgEONLYrW#+-Ns4Tg)T^!G%(qT^=b3EgLXHj__ zvWGaHMWsxoo8ovDmA2uEn=>B|Jd27?+g0Lt7L}a?!LkP1?|B7r$m^uTj3wrBoM;&|@G z4!>|p9M8SjVs+D)gGvuP_hRp@8z_#SX+Qq#i|&czXIXx^o3AcKOwmopQr z>yhcRSNh!%(@x!-V8;E2!`B?ky=Yv5hT%QZgpbyFbKctDS9EQiV@|45okPCt|K|dB zHD{rw59=fgnm@Ss-SpQF{G1lE&Fc0mn}i)+C1ClwSh*GigmCqkuI6_a@PG4v4awCL z%nr-dbw2iQ7RR|)PnB2d#=rAi$I=|<9y+o{9M5s?pcRpenwiRr@RwA?ktX{yq7)-630{C1%2}B7#UA_FFrO!98Y=Ii2OwyPkB$ST3g4+ zc*;9p#6#kE%KLu%3_3=}Q{Elp4HL(+$;+Jit~j3ZUV7d~$H;if`&-Wi;&{rtb*Ioe zF2+;dRm!#y$5Y;iR~;9}Q{MfOWYlpnp7K8B=R97mKuo zWEb+A$t&5+PkLp5IKHyw4X$1k$5*lZ+VZrz$BeIPx!>f0;`nNoXIb)49ADk?=Icu7 z9y7j%<%J%v6vx-J{88-4KIY@b*Rs4x?bhP>+Lpf$cUl}@$MWBnWhrdt!Pm8X-L$dd z_f>i)qx zOOtMlHEz&Y$FawID|`wt_ocW8Y<}%(&P>BMA6yx2_^cjlzb1~`w@#THuVQ9yxgzn` z>IuRwx|`%^oB#MO=t#4iKeRV-@T`u=c96&nqS!$~Gl*&j3Ctjx9mF?-=yqW4?K!;c zAg&q2u!A^e5YrCyUC^=YK;H!&+YZcGwj+)mn1gUfTsttQ=#F@HV2L8l$qbU%K}0i1Y6s@7jw6{Jm|H@Q*4>&ym&+%tGOr&JN7e=}2z}=6C&~u@f9r}lW?s#-rw>WQ$wjN@d3=!y`DIpGHzFIqj+iSDdQ7~ zL+iJMr;KkTsv(Z2j4yd?630`h?Ed|G_`sc@ig3apEcCp^v+Z<0<0-m9L89DdSX! z^5{4kPZ^IaFijj!8CRe7SsYIp51d$C$I*DoIC7S4;&{q9U-q~M~!G@de!e`=UGo@4U~kzb1AIW{kzrIL=L z@f@4CJ-k&M&#`%iM)7s*jOW;V-kzS~c#h4#*SIN;=h(cgZy_B=<2g1Doo2o`o@4Wy z$wKPb8Q=CAHo z)p0bQWAj?KCW_-ZHox%lxj3F<^GPsk2s#HSTJR1bMtlP?|Q4ax=Op6^Tu_EnRi1#p*5pR zhDtJY+lG>{np`N~bV-*P6LZ|}(D-JIo(ra|`+R6rf^plL<$F22{?@f0b01k5=}xH6 zYd78~6=#F_i$npNr`Oe-4{{GpyXi^YF^gy9UtT8FNYCoa8cJ%~v*6->%AN#k8#cV@ck9-DA88j!adCy`Ny?K3cJd4?u zS$oCtEM_UPB++IK&tlekS|4#di&?kV*TwNHX5(L`*Cr3oV&;3HuQ;B?tkaKc;&>Lb zWkm{UlZR(9^Sie|9M57_a8Vd-=I|_Lmmanh$FrDK4slK#&tlePR!$%DdBn4r#VIgN z9M57lXvPjp-+3WIa#qlg=<5I=YrVYa@hoNqGB`JR zc@Z19)nY*<( zo_oNa%}$ErC)&qdRy?CNZTLx+SB*7F96#CeewCkzT$4|HX-eP}k=I}EtZ}@q&IDV$(FP}!zW)45g@=^O+iQ{Km9%bxVar_+1KQzms z%^ZHN<;5J+#PRbiU!CKdIDWq6(~8&CW)8o=a{s{n;`oJ@H~Kw=Hf{JtmcL3fL>#}^ z@<%0~I5&URFR|S7SP^aJ@T?2ja#j_`|MDJinBsT6&0qeep9kwbr8{F?&TNL`Dcv12 zhKl1U-F64Ai{mNXl^1hmH}l{r-KVFfiQ_5VQCq%=<0;)DXY1xL^WZ7nUJv(*<0;)c z5tHUL*W)SOZnOG{O84oc zdgAzo_WHe__K4#t-4Klv=P~o(Dc!N_`-tNy-J^4FiQ_5V8{UPq$m5&Y$7S)yyEg~U z&4YV$;M_d8HwVtm!(Y8Q_@~?h-hXd*uEOb81@xX+5a zJOx}K(Is&_1>DY&M`E`tp zr-0{2m?Ms-fXC!@es?sU0#1LsUM2JQ98Ur7jk;GHPXQNxomk)PjHiGD5A+eoQ@~$C z+!DuAz;pBDsbU@%PXR|rJx&}?0UzJ+L>x~6S1DAqs+k8*0e>GiR~%0P5AOF<98Upv zt5&y~nFmh+e@Jvd98Uosf03%XxgJje&l)mZ98UqC-u_%1PXX6EQK5#J2TuXldbL3u zPXT{f9=)cy9!~+cdD2lFPXXtR8YGVYqkzNbt!3uHvo2ijTwdJM`d=1sjhr8KESWI* zOueUorv#SKF(aM=J|Ai3;`1*?;XCb9@EwQzbs(S*Bk1^ zHeXp22mjCU$qo{k!Dl;2Xa-;GAb}ZtwS)L(@XZe5nZb8Eh-(Hv>>!RA{Imn}U=ELs z&IgTU1`a!jX$GEl5W@^Y*nyWBgtUX`W)R8_qM1QxJBVrqVeBA^8HBZi$Yv1E4kDRB zcsqz_1`+Haf*C}#gYae$$qvGqL1a4!YX(v5AdDGAwS&-R5X}xknL%_r2x$ghc3}QA zIAYj=ry0bw1BV&BvY#qr%o{sPS5GiIO;^|XXjTsoHW~NdO-f}@h#%g1&nBZ-w9gvj z*<@r$QX{pw9?vG@rSEoeJe!QWXX2$X*W=k_^se4h9M2{rO~&iucs3dPPUK5#=E1Yc zINfu)IG#<$+^e6(@oX|K=c=C0%!6l>QG3Z&aXg!h#UJ9NH`n9YWDL2`RUFSI!+$`K zIG#;Lv&*?Mn0fGQGRj|^Dvsv_BKL{U;&@IV-lnde(aeMA1Y*#Vt>So2AY#OhlgV6< z=LDi$n@-|*P9W|iJ}8do1R`#PxS7p7cupX`d-W8@a{}?z|CTtugZXnG{C4AwZx-_& z&nBbZ>LucMP9UPbj+oV4kLLv9X6*LjcupWnAG{=v=LDjEgFL#eh35pK^{(mScupXa zR{JK7=LDi_;o7>rh35ofbjH2ncupWzrc0*VSa?n#Qui1jj%Sl`{`GBfd|&%{>z2hg zhxt6+6S! z<2iviTJV`To)d_9S^RW+3(pC}kq{fi@oX~Y`^U&*9v9CE#HRzD#POU!WO^DTj^_j- zdAcOJ?S<>GiwApF)xR7Bz@*ynNH z?LcunClK{_9T&%Q0&!%x^Lt|OoIvC{G+dF1XPpRKbzdCMy3qDTQbi`7Md!fO#m&v{ zOK)R5`=l_xXtF7}-c!I8pP$wkPXT+LOPbPLkEehif9fQTr+|C>JR^>$fLVO-?#0Qu zc)1rR=i=pFoSci7dvS6uUjMHyPXCkw?&KGC_Nbuxhqihw9ndPn?3Ufym07uN%8aJB zA077I*>UreuP+nEx?N%Kri-t;7wzo;-8wFM?M2iw;+{qD6?Z6_1=e~*lrrXeJO!4v zOb2m11=h89kT{+KYq+66Su+ow0`spsU;GbmcfLWMp>z)wPk{y2Zz7Iokz3$#L>y0n z-G7m$ym?$a1(y592yr|GHuctXaXbY!D!ZSbnFmjS`G;C5j;Fxpmx-)GR}YS0M8;fzRCw(kEg&+maXh>=D|~7RibYd$5UW0 z!pGM=R6GS1`fhJ=JOvge(`|7)1-5KX;Y#Ll@f6s&qYK6H6j=J!&&2T**oKScbPp9z zflWKQRvb@(O|0lu#XK&a0()M*vpAjt8=Egk98ZDOZJMj9nFmjSeJnOb98ZB=j`2|( zPl3JluTss-gQvhA^j<5Dr@)2}30K`*kDq4$UbJr3Kpa2a@ Sh~sBi-pwmc4KojZ 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