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/.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/book/api/metrics-generated.md b/book/api/metrics-generated.md index 4388e9b8d76..ce9c3d1574e 100644 --- a/book/api/metrics-generated.md +++ b/book/api/metrics-generated.md @@ -685,15 +685,6 @@ -## Poh Tile - -
- -| Metric | Type | Description | -|--------|------|-------------| - -
- ## Sign Tile
@@ -960,45 +951,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
@@ -1097,7 +1049,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 +1105,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 | @@ -1241,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) | @@ -1274,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 | @@ -1467,219 +1416,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 | - -
- -## 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/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/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/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_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 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/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 30656e843ee..556564448dd 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, 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/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..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"), !!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"), 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, VAL("shred_tile_cnt") ) ); } fd_topo_obj_callbacks_t fd_obj_cb_store = { 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/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..2c74b3deb03 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. @@ -2127,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/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..669c07f2d7e 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,11 @@ 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.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 +1571,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 +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 = config->firedancer.consensus.alpenglow; 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 +1666,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 +1690,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 +1756,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 +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 = config->firedancer.consensus.alpenglow; 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 +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 = config->firedancer.consensus.alpenglow; 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 +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 = config->firedancer.consensus.alpenglow; 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 +1869,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/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/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..7fabca6d981 100644 --- a/src/app/shared/fd_action.h +++ b/src/app/shared/fd_action.h @@ -2,13 +2,12 @@ #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 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; @@ -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 { @@ -109,18 +106,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; @@ -144,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 { @@ -196,11 +180,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..71bff92424d 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 { @@ -297,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; @@ -379,10 +377,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 +458,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..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)==26553488UL, 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]" @@ -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" ); @@ -470,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 ); @@ -549,9 +547,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 +613,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..246f0a5a5a4 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 ); @@ -260,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 ); @@ -340,6 +334,10 @@ 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 ); + CFG_DEPRECATED( development.genesis.hashes_per_tick ); # 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..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,41 +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; - - } - - /* 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 */ - + 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; @@ -347,9 +283,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/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/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 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 252d3a5a84e..00000000000 Binary files a/src/choreo/eqvoc/fixtures/id.bin and /dev/null differ diff --git a/src/choreo/eqvoc/fixtures/pay1.bin b/src/choreo/eqvoc/fixtures/pay1.bin deleted file mode 100644 index 252d3a5a84e..00000000000 Binary files a/src/choreo/eqvoc/fixtures/pay1.bin and /dev/null differ diff --git a/src/choreo/eqvoc/fixtures/pay2.bin b/src/choreo/eqvoc/fixtures/pay2.bin deleted file mode 100644 index 039bee71bfb..00000000000 Binary files a/src/choreo/eqvoc/fixtures/pay2.bin and /dev/null differ 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/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/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_execrp.json b/src/disco/events/schema/metrics_execrp.json index 4ca3ac68ad5..9b7d80609e1 100644 --- a/src/disco/events/schema/metrics_execrp.json +++ b/src/disco/events/schema/metrics_execrp.json @@ -85,7 +85,7 @@ "device_irq": { "type": "UInt64", "compression": "DoubleDelta, ZSTD", "description": "counter: cumulative since tile start, query deltas. Device IRQs on the tile's dedicated CPU, 0 unless it is the only live tile pinned there" }, "tlb_shootdown": { "type": "UInt64", "compression": "DoubleDelta, ZSTD", "description": "counter: cumulative since tile start, query deltas. TLB shootdowns on the tile's dedicated CPU, 0 unless it is the only live tile pinned there" }, "timer_irq": { "type": "UInt64", "compression": "DoubleDelta, ZSTD", "description": "counter: cumulative since tile start, query deltas. Local timer interrupts (LOC) on the tile's dedicated CPU, 0 unless it is the only live tile pinned there" }, - "is_processing": { "type": "Bool", "description": "Whether the tile is currently processing a task (executing, sigverifying, or PoH hashing)" }, + "is_processing": { "type": "Bool", "description": "Whether the tile is currently processing a task (executing, sigverifying, or verifying certificates)" }, "txn_processed": { "type": "Tuple", "description": "Transactions loaded and executed, by result", @@ -137,7 +137,7 @@ } }, "signature_checked": { "type": "UInt64", "compression": "DoubleDelta, ZSTD", "description": "Ed25519 signatures checked, including failures" }, - "poh_hash_computed": { "type": "UInt64", "compression": "DoubleDelta, ZSTD", "description": "PoH SHA-256 iterations computed" }, + "poh_hash_computed": { "type": "UInt64", "compression": "DoubleDelta, ZSTD", "description": "PoH SHA-256 iterations computed", "deprecated": true }, "instruction_executed": { "type": "UInt64", "compression": "DoubleDelta, ZSTD", "description": "Top-level instructions executed" }, "cpi_executed": { "type": "UInt64", "compression": "DoubleDelta, ZSTD", "description": "Cross-program invocations executed" }, "account_lthash_skipped": { "type": "UInt64", "compression": "DoubleDelta, ZSTD", "description": "Committed writable accounts left byte-identical by their transaction, so their lthash update was skipped" }, 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/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..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 @@ -1061,7 +1036,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; @@ -1511,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 && @@ -1730,26 +1704,18 @@ 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 ); } 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 ]; @@ -2338,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){ @@ -2358,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 ), @@ -2430,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) ); @@ -2528,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; } @@ -2574,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, @@ -2663,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; @@ -2728,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 * @@ -2816,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; @@ -2831,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 ); @@ -2844,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 ); @@ -2865,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; @@ -2879,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; @@ -2890,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; @@ -2901,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 ); @@ -3080,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 ) { @@ -3262,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; @@ -3341,156 +3153,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, - 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, @@ -3558,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; @@ -3591,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 ); @@ -3639,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 ); @@ -3663,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 ); } } @@ -3836,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); @@ -3973,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 65a1748d5cf..9da783031b3 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 */ @@ -155,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) @@ -173,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) @@ -525,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) */ @@ -598,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; @@ -614,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_* */ @@ -862,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) */ @@ -932,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; @@ -1005,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; @@ -1017,7 +1005,6 @@ struct fd_gui_summary { ushort expected_shred_version; int wfs_enabled; - ulong vote_distance; int vote_state; long startup_time_nanos; @@ -1045,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; @@ -1191,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; @@ -1326,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, @@ -1470,23 +1449,11 @@ 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, - 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 ); @@ -1499,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, @@ -1662,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 ) { @@ -1833,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 49c27465a39..b9ade1fc77c 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 */ @@ -222,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 ); } } @@ -357,8 +355,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 ) ) { @@ -380,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; } @@ -439,8 +434,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 ) ) { @@ -449,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; @@ -495,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 ) ); @@ -647,29 +620,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; } @@ -985,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, 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, 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 ); @@ -1014,7 +972,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/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 ) ) ); } 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/disco/metrics/generated/fd_metrics_all.c b/src/disco/metrics/generated/fd_metrics_all.c index 703384c48d6..aacfcf05c28 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 ), @@ -928,7 +895,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 +951,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 ), @@ -1099,7 +1064,6 @@ const fd_metrics_meta_t FD_METRICS_EXECRP[FD_METRICS_EXECRP_TOTAL] = { DECLARE_METRIC_ENUM( EXECRP_TXN_PROCESSED_BY_VERSION, COUNTER, TXN_VERSION, V0 ), DECLARE_METRIC_ENUM( EXECRP_TXN_PROCESSED_BY_VERSION, COUNTER, TXN_VERSION, V1 ), DECLARE_METRIC( EXECRP_SIGNATURE_CHECKED, COUNTER ), - DECLARE_METRIC( EXECRP_POH_HASH_COMPUTED, COUNTER ), DECLARE_METRIC( EXECRP_INSTRUCTION_EXECUTED, COUNTER ), DECLARE_METRIC( EXECRP_CPI_EXECUTED, COUNTER ), DECLARE_METRIC( EXECRP_ACCOUNT_LTHASH_SKIPPED, COUNTER ), @@ -1285,207 +1249,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_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 ), @@ -2182,18 +1945,14 @@ const char * FD_METRICS_TILE_KIND_NAMES[FD_METRICS_TILE_KIND_CNT] = { "resolv", "pack", "execle", - "poh", "sign", "shred", "gossvf", "gossip", - "repair", "rserve", "replay", "execrp", "accdb", - "tower", - "txsend", "diag", "event", "gui", @@ -2232,18 +1991,14 @@ 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, FD_METRICS_GOSSIP_TOTAL, - FD_METRICS_REPAIR_TOTAL, FD_METRICS_RSERVE_TOTAL, FD_METRICS_REPLAY_TOTAL, 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, @@ -2281,18 +2036,14 @@ 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, FD_METRICS_GOSSIP, - FD_METRICS_REPAIR, FD_METRICS_RSERVE, FD_METRICS_REPLAY, 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..7481258cadf 100644 --- a/src/disco/metrics/generated/fd_metrics_all.h +++ b/src/disco/metrics/generated/fd_metrics_all.h @@ -64,18 +64,15 @@ 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" #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" -#include "fd_metrics_repair.h" #include "fd_metrics_rotor.h" #include "fd_metrics_rserve.h" #include "fd_metrics_gossip.h" @@ -97,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" @@ -254,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 47 +#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 731514c35dd..072aa017a82 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) @@ -1354,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_execrp.h b/src/disco/metrics/generated/fd_metrics_execrp.h index 8a9da912bcd..71ff72a1837 100644 --- a/src/disco/metrics/generated/fd_metrics_execrp.h +++ b/src/disco/metrics/generated/fd_metrics_execrp.h @@ -42,7 +42,6 @@ enum { FD_METRICS_COUNTER_EXECRP_TXN_PROCESSED_BY_VERSION_V0_OFF, FD_METRICS_COUNTER_EXECRP_TXN_PROCESSED_BY_VERSION_V1_OFF, FD_METRICS_COUNTER_EXECRP_SIGNATURE_CHECKED_OFF, - FD_METRICS_COUNTER_EXECRP_POH_HASH_COMPUTED_OFF, FD_METRICS_COUNTER_EXECRP_INSTRUCTION_EXECUTED_OFF, FD_METRICS_COUNTER_EXECRP_CPI_EXECUTED_OFF, FD_METRICS_COUNTER_EXECRP_ACCOUNT_LTHASH_SKIPPED_OFF, @@ -161,7 +160,7 @@ enum { #define FD_METRICS_GAUGE_EXECRP_IS_PROCESSING_NAME "execrp_is_processing" #define FD_METRICS_GAUGE_EXECRP_IS_PROCESSING_TYPE (FD_METRICS_TYPE_GAUGE) -#define FD_METRICS_GAUGE_EXECRP_IS_PROCESSING_DESC "Whether the tile is currently processing a task (executing, sigverifying, or PoH hashing)" +#define FD_METRICS_GAUGE_EXECRP_IS_PROCESSING_DESC "Whether the tile is currently processing a task (executing, sigverifying, or verifying certificates)" #define FD_METRICS_GAUGE_EXECRP_IS_PROCESSING_CVT (FD_METRICS_CONVERTER_NONE) #define FD_METRICS_COUNTER_EXECRP_TXN_PROCESSED_NAME "execrp_txn_processed" @@ -181,11 +180,6 @@ enum { #define FD_METRICS_COUNTER_EXECRP_SIGNATURE_CHECKED_DESC "Ed25519 signatures checked, including failures" #define FD_METRICS_COUNTER_EXECRP_SIGNATURE_CHECKED_CVT (FD_METRICS_CONVERTER_NONE) -#define FD_METRICS_COUNTER_EXECRP_POH_HASH_COMPUTED_NAME "execrp_poh_hash_computed" -#define FD_METRICS_COUNTER_EXECRP_POH_HASH_COMPUTED_TYPE (FD_METRICS_TYPE_COUNTER) -#define FD_METRICS_COUNTER_EXECRP_POH_HASH_COMPUTED_DESC "PoH SHA-256 iterations computed" -#define FD_METRICS_COUNTER_EXECRP_POH_HASH_COMPUTED_CVT (FD_METRICS_CONVERTER_NONE) - #define FD_METRICS_COUNTER_EXECRP_INSTRUCTION_EXECUTED_NAME "execrp_instruction_executed" #define FD_METRICS_COUNTER_EXECRP_INSTRUCTION_EXECUTED_TYPE (FD_METRICS_TYPE_COUNTER) #define FD_METRICS_COUNTER_EXECRP_INSTRUCTION_EXECUTED_DESC "Top-level instructions executed" @@ -368,7 +362,7 @@ enum { #define FD_METRICS_COUNTER_EXECRP_ACCDB_COPIED_BYTES_DESC "Account data bytes copied in memory between cache lines, staging buffers and callers; disk reads excluded" #define FD_METRICS_COUNTER_EXECRP_ACCDB_COPIED_BYTES_CVT (FD_METRICS_CONVERTER_NONE) -#define FD_METRICS_EXECRP_TOTAL (136UL) +#define FD_METRICS_EXECRP_TOTAL (135UL) extern const fd_metrics_meta_t FD_METRICS_EXECRP[FD_METRICS_EXECRP_TOTAL]; #endif /* HEADER_fd_src_disco_metrics_generated_fd_metrics_execrp_h */ 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_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_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..889b6649178 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 }, @@ -3225,7 +3223,7 @@ static fd_metrics_sample_field_t const fields_ipecho[ 55 ] = { { FD_METRICS_GAUGE_IPECHO_PORT_CHECK_TCP_IN_FLIGHT_OFF, 31U, FD_METRICS_SAMPLE_U64, NULL, 0UL }, }; -static fd_metrics_sample_field_t const fields_execrp[ 202 ] = { +static fd_metrics_sample_field_t const fields_execrp[ 201 ] = { { 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 }, @@ -3301,7 +3299,6 @@ static fd_metrics_sample_field_t const fields_execrp[ 202 ] = { { FD_METRICS_COUNTER_EXECRP_TXN_PROCESSED_BY_VERSION_V1_OFF, 3U, FD_METRICS_SAMPLE_U64, NULL, 0UL }, { 0, 0U, FD_METRICS_SAMPLE_CLOSE, NULL, 0UL }, { FD_METRICS_COUNTER_EXECRP_SIGNATURE_CHECKED_OFF, 28U, FD_METRICS_SAMPLE_U64, NULL, 0UL }, - { FD_METRICS_COUNTER_EXECRP_POH_HASH_COMPUTED_OFF, 29U, FD_METRICS_SAMPLE_U64, NULL, 0UL }, { FD_METRICS_COUNTER_EXECRP_INSTRUCTION_EXECUTED_OFF, 30U, FD_METRICS_SAMPLE_U64, NULL, 0UL }, { FD_METRICS_COUNTER_EXECRP_CPI_EXECUTED_OFF, 31U, FD_METRICS_SAMPLE_U64, NULL, 0UL }, { FD_METRICS_COUNTER_EXECRP_ACCOUNT_LTHASH_SKIPPED_OFF, 32U, FD_METRICS_SAMPLE_U64, NULL, 0UL }, @@ -4382,7 +4379,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 }, @@ -4401,7 +4398,7 @@ fd_metrics_sample_schema_t const fd_metrics_sample_tiles[] = { { "waker", 50U, fields_waker, 40UL, 536UL }, { "mwaitx", 51U, fields_mwaitx, 44UL, 564UL }, { "ipecho", 52U, fields_ipecho, 55UL, 674UL }, - { "execrp", 53U, fields_execrp, 202UL, 2135UL }, + { "execrp", 53U, fields_execrp, 201UL, 2123UL }, { "votor", 54U, fields_votor, 357UL, 3719UL }, { "gui", 55U, fields_gui, 256UL, 2708UL }, { "event", 56U, fields_event, 54UL, 699UL }, @@ -4413,6 +4410,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", "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/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 e2899321942..64355d0b277 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 - - - @@ -633,7 +580,7 @@ deliberate exception with lint-allow="") - + @@ -837,9 +784,6 @@ deliberate exception with lint-allow="") - - - @@ -998,7 +942,7 @@ deliberate exception with lint-allow="") - + @@ -1109,35 +1053,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 - - - - - - - - - - @@ -1765,12 +1680,12 @@ deliberate exception with lint-allow="") - + - + @@ -1816,144 +1731,6 @@ deliberate exception with lint-allow="") - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 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..24b52aee3a8 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 @@ -245,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]; @@ -428,9 +425,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 +464,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 || szstore ) ) { fd_hash_t const * mr = fd_type_pun_const( merkle_root ); @@ -953,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 @@ -1054,7 +1037,7 @@ after_frag( fd_shred_ctx_t * ctx, return; } - if( FD_UNLIKELY( (ctx->in_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; } @@ -1188,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 && @@ -1337,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, @@ -1426,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 ) ) ); @@ -1461,7 +1441,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/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 8640f8cb5b2..889b8df1021 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; }; @@ -396,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 { @@ -418,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 ]; @@ -460,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 ]; @@ -530,8 +526,6 @@ struct fd_topo_tile { uchar tip_distribution_program_addr[ 32 ]; char vote_account_path[ PATH_MAX ]; } bundle; - - int alpenglow; } replay; struct { @@ -606,15 +600,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; @@ -624,28 +609,9 @@ struct fd_topo_tile { char ledger_path[ PATH_MAX ]; ulong end_slot; ulong root_distance; - int alpenglow; 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 ]; @@ -736,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/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/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; ialpenglow ) ) { - 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/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/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/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/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/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 */ 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/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; -} 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_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 824077ac9b3..d4bf59dea93 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 )); @@ -3150,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 )); } @@ -3185,8 +2777,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 +2791,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 +2809,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. */ @@ -3251,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; @@ -3272,144 +2858,49 @@ 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; - } - - fd_reasm_fec_t * fec; - if( FD_UNLIKELY( (fec = fd_reasm_peek( ctx->reasm ))==NULL ) ) { - ctx->metrics.reasm_empty++; - return 0; + return PROCESS_FEC_WAIT; } - 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. + /* 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 @@ -3564,288 +3055,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 +3198,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 +3213,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 +3231,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 +3282,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 +3328,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 +3351,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 +3369,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 +3393,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, @@ -4482,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--; @@ -4520,156 +3550,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 +3660,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 +3804,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 +4077,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 +4108,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 +4162,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 +4196,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 +4280,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 +4302,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 +4322,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 +4335,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 +4347,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 +4476,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 +4503,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" ) ) ); FD_TEST( ctx->sched ); - FD_TEST( ctx->alpenglow || ctx->reasm ); ctx->in_cnt = tile->in_cnt; ctx->execrp_idle_cnt = 0UL; @@ -5766,7 +4558,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 +4585,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 +4625,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 +4636,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 +4659,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 +4793,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/fd_sched.c b/src/discof/replay/fd_sched.c index 97029221142..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; @@ -215,7 +206,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 @@ -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 ); @@ -299,13 +290,10 @@ 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. */ 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; @@ -315,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 @@ -333,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. */ @@ -374,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 ); @@ -476,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 @@ -617,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 @@ -636,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++ ) { @@ -752,8 +726,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" )); @@ -821,7 +794,6 @@ fd_sched_new( void * mem, for( ulong i=0; inext_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; @@ -840,8 +811,6 @@ fd_sched_new( void * mem, sched->max_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; @@ -852,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 */ @@ -905,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 ); } @@ -957,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; } @@ -970,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 ); @@ -1261,7 +1228,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 ); @@ -1271,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 @@ -1298,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; @@ -1341,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. */ @@ -1370,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 @@ -1420,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 @@ -1446,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 ) ) { @@ -1464,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 @@ -1565,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 ]; @@ -1665,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--; @@ -2127,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; @@ -2150,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; @@ -2459,15 +2380,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; @@ -2554,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; @@ -2581,7 +2497,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 +2513,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 +2524,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; } @@ -2957,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 @@ -2966,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 ) ) { @@ -2990,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. */ @@ -3097,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 )); @@ -3118,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. */ @@ -3587,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 @@ -3599,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 33c148dd0d6..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. */ @@ -303,8 +290,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 ); @@ -392,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 @@ -461,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 4095a45d7ba..c5fd8463f12 100644 --- a/src/discof/replay/fuzz_sched_rdisp.c +++ b/src/discof/replay/fuzz_sched_rdisp.c @@ -7,11 +7,11 @@ #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" #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 +22,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 @@ -40,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. */ @@ -102,18 +102,16 @@ 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_mblk_cnt; - 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; 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; @@ -145,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 { @@ -266,15 +259,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); @@ -329,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 ); @@ -470,26 +461,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 +510,52 @@ 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) ); 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,58 +575,70 @@ 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 ); - fd_memcpy( block->mblk_hashed_hash + block->mblk_cnt-1UL, tick_end, sizeof(fd_hash_t) ); + repeat_hash( tick_end, prev_hash, 1UL ); 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; 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; } @@ -693,7 +700,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 +773,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 @@ -862,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, @@ -919,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 )); } } @@ -1155,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 ); @@ -1264,44 +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; lpoh_dispatch_mblk_cnt; l++ ) { - if( block->mblk_pool_idx[ l ]==pool_idx && block->mblk_curr_hashcnt[ l ]poh_dispatch_mblk_cnt++; - FD_TEST( local_mblk_idxmblk_cnt ); - 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) ); - } - FD_TEST( task->poh_hash->hashcnt && task->poh_hash->hashcnt<=min_todo ); - 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 e268accdfe8..a66fe8457f8 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, 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, 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, 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, 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, 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, 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 ); diff --git a/src/discof/replay/test_sched.c b/src/discof/replay/test_sched.c index 464b7a6049e..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 ) ); } @@ -43,35 +42,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 +69,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 +171,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 +187,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 +207,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 +227,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 +239,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 +253,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 +285,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; @@ -233,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 )); } @@ -255,167 +306,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 +324,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 ); @@ -495,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 )); } @@ -575,11 +458,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 +474,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 +488,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 +508,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 +529,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 +550,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 +574,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 +700,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 +718,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(;;) { @@ -1057,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 ); @@ -1253,7 +926,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 +939,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,15 +967,12 @@ 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; - 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; @@ -1314,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 @@ -1346,27 +1009,15 @@ 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 ) ); - /* 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 ) ); @@ -1405,7 +1056,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 +1095,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 +1118,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 +1134,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 +1155,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 +1173,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/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/rpc/fd_rpc_tile.c b/src/discof/rpc/fd_rpc_tile.c index da56cbafad1..64b045fd6a5 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) @@ -440,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; @@ -756,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; } @@ -990,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: { @@ -1047,14 +1041,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 ); @@ -3204,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; @@ -3263,7 +3248,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 )); 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/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 90496599d6f..00000000000 Binary files a/src/discof/tower/fixtures/voters-398915634.bin and /dev/null differ diff --git a/src/discof/tower/fixtures/voters-398915635.bin b/src/discof/tower/fixtures/voters-398915635.bin deleted file mode 100644 index c8eb8c48905..00000000000 Binary files a/src/discof/tower/fixtures/voters-398915635.bin and /dev/null differ diff --git a/src/discof/tower/fixtures/voters-398915636.bin b/src/discof/tower/fixtures/voters-398915636.bin deleted file mode 100644 index 580d2d49bc0..00000000000 Binary files a/src/discof/tower/fixtures/voters-398915636.bin and /dev/null differ diff --git a/src/discof/tower/fixtures/voters-398915637.bin b/src/discof/tower/fixtures/voters-398915637.bin deleted file mode 100644 index 20694d71edd..00000000000 Binary files a/src/discof/tower/fixtures/voters-398915637.bin and /dev/null differ diff --git a/src/discof/tower/fixtures/voters-398915638.bin b/src/discof/tower/fixtures/voters-398915638.bin deleted file mode 100644 index c86becc57ae..00000000000 Binary files a/src/discof/tower/fixtures/voters-398915638.bin and /dev/null differ diff --git a/src/discof/tower/fixtures/voters-398915639.bin b/src/discof/tower/fixtures/voters-398915639.bin deleted file mode 100644 index 11dd48776b5..00000000000 Binary files a/src/discof/tower/fixtures/voters-398915639.bin and /dev/null differ diff --git a/src/discof/tower/fixtures/voters-398915640.bin b/src/discof/tower/fixtures/voters-398915640.bin deleted file mode 100644 index 2ab0129cf0d..00000000000 Binary files a/src/discof/tower/fixtures/voters-398915640.bin and /dev/null differ diff --git a/src/discof/tower/fixtures/voters-398915641.bin b/src/discof/tower/fixtures/voters-398915641.bin deleted file mode 100644 index 46f9972e085..00000000000 Binary files a/src/discof/tower/fixtures/voters-398915641.bin and /dev/null differ diff --git a/src/discof/tower/fixtures/voters-398915642.bin b/src/discof/tower/fixtures/voters-398915642.bin deleted file mode 100644 index 83cba9a6923..00000000000 Binary files a/src/discof/tower/fixtures/voters-398915642.bin and /dev/null differ diff --git a/src/discof/tower/fixtures/voters-398915643.bin b/src/discof/tower/fixtures/voters-398915643.bin deleted file mode 100644 index ffbe7dace88..00000000000 Binary files a/src/discof/tower/fixtures/voters-398915643.bin and /dev/null differ diff --git a/src/discof/tower/fixtures/voters-398915644.bin b/src/discof/tower/fixtures/voters-398915644.bin deleted file mode 100644 index 34521c0eedf..00000000000 Binary files a/src/discof/tower/fixtures/voters-398915644.bin and /dev/null differ diff --git a/src/discof/tower/fixtures/voters-398915645.bin b/src/discof/tower/fixtures/voters-398915645.bin deleted file mode 100644 index c6dd3eae4fd..00000000000 Binary files a/src/discof/tower/fixtures/voters-398915645.bin and /dev/null differ diff --git a/src/discof/tower/fixtures/voters-398915646.bin b/src/discof/tower/fixtures/voters-398915646.bin deleted file mode 100644 index 75b47331d0b..00000000000 Binary files a/src/discof/tower/fixtures/voters-398915646.bin and /dev/null differ diff --git a/src/discof/tower/fixtures/voters-398915647.bin b/src/discof/tower/fixtures/voters-398915647.bin deleted file mode 100644 index e1c21e109ea..00000000000 Binary files a/src/discof/tower/fixtures/voters-398915647.bin and /dev/null differ diff --git a/src/discof/tower/fixtures/voters-398915648.bin b/src/discof/tower/fixtures/voters-398915648.bin deleted file mode 100644 index 30ab895ad25..00000000000 Binary files a/src/discof/tower/fixtures/voters-398915648.bin and /dev/null differ diff --git a/src/discof/tower/fixtures/voters-398915649.bin b/src/discof/tower/fixtures/voters-398915649.bin deleted file mode 100644 index 0979a9880ea..00000000000 Binary files a/src/discof/tower/fixtures/voters-398915649.bin and /dev/null differ diff --git a/src/discof/tower/fixtures/voters-398915650.bin b/src/discof/tower/fixtures/voters-398915650.bin deleted file mode 100644 index 10a8c79d5bd..00000000000 Binary files a/src/discof/tower/fixtures/voters-398915650.bin and /dev/null differ diff --git a/src/discof/tower/fixtures/voters-398915651.bin b/src/discof/tower/fixtures/voters-398915651.bin deleted file mode 100644 index a6b0ce1f109..00000000000 Binary files a/src/discof/tower/fixtures/voters-398915651.bin and /dev/null differ diff --git a/src/discof/tower/fixtures/voters-398915652.bin b/src/discof/tower/fixtures/voters-398915652.bin deleted file mode 100644 index 43c48069c15..00000000000 Binary files a/src/discof/tower/fixtures/voters-398915652.bin and /dev/null differ diff --git a/src/discof/tower/fixtures/voters-398915653.bin b/src/discof/tower/fixtures/voters-398915653.bin deleted file mode 100644 index 337799c07c5..00000000000 Binary files a/src/discof/tower/fixtures/voters-398915653.bin and /dev/null differ diff --git a/src/discof/tower/fixtures/voters-398915654.bin b/src/discof/tower/fixtures/voters-398915654.bin deleted file mode 100644 index c7f3b9ddc24..00000000000 Binary files a/src/discof/tower/fixtures/voters-398915654.bin and /dev/null differ diff --git a/src/discof/tower/fixtures/voters-398915655.bin b/src/discof/tower/fixtures/voters-398915655.bin deleted file mode 100644 index 888ef8f9d6b..00000000000 Binary files a/src/discof/tower/fixtures/voters-398915655.bin and /dev/null differ diff --git a/src/discof/tower/fixtures/voters-398915656.bin b/src/discof/tower/fixtures/voters-398915656.bin deleted file mode 100644 index 81ac62948c2..00000000000 Binary files a/src/discof/tower/fixtures/voters-398915656.bin and /dev/null differ diff --git a/src/discof/tower/fixtures/voters-398915657.bin b/src/discof/tower/fixtures/voters-398915657.bin deleted file mode 100644 index 5ac57576e28..00000000000 Binary files a/src/discof/tower/fixtures/voters-398915657.bin and /dev/null differ diff --git a/src/discof/tower/fixtures/voters-398915658.bin b/src/discof/tower/fixtures/voters-398915658.bin deleted file mode 100644 index 7098b3aa6f7..00000000000 Binary files a/src/discof/tower/fixtures/voters-398915658.bin and /dev/null differ diff --git a/src/discof/tower/fixtures/voters-398915659.bin b/src/discof/tower/fixtures/voters-398915659.bin deleted file mode 100644 index 4d26255acc5..00000000000 Binary files a/src/discof/tower/fixtures/voters-398915659.bin and /dev/null differ diff --git a/src/discof/tower/fixtures/voters-398915660.bin b/src/discof/tower/fixtures/voters-398915660.bin deleted file mode 100644 index 42377fc4180..00000000000 Binary files a/src/discof/tower/fixtures/voters-398915660.bin and /dev/null differ diff --git a/src/discof/tower/fixtures/voters-398915661.bin b/src/discof/tower/fixtures/voters-398915661.bin deleted file mode 100644 index d1e622b1b9f..00000000000 Binary files a/src/discof/tower/fixtures/voters-398915661.bin and /dev/null differ diff --git a/src/discof/tower/fixtures/voters-398915662.bin b/src/discof/tower/fixtures/voters-398915662.bin deleted file mode 100644 index d0bc3ad27a5..00000000000 Binary files a/src/discof/tower/fixtures/voters-398915662.bin and /dev/null differ diff --git a/src/discof/tower/fixtures/voters-398915663.bin b/src/discof/tower/fixtures/voters-398915663.bin deleted file mode 100644 index e22b504e824..00000000000 Binary files a/src/discof/tower/fixtures/voters-398915663.bin and /dev/null differ diff --git a/src/discof/tower/fixtures/voters-398915664.bin b/src/discof/tower/fixtures/voters-398915664.bin deleted file mode 100644 index d9138156dca..00000000000 Binary files a/src/discof/tower/fixtures/voters-398915664.bin and /dev/null differ diff --git a/src/discof/tower/fixtures/voters-398915665.bin b/src/discof/tower/fixtures/voters-398915665.bin deleted file mode 100644 index dfded4deb05..00000000000 Binary files a/src/discof/tower/fixtures/voters-398915665.bin and /dev/null differ 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/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 */ 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 ); 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; 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/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] 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 );