-
-| 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