net-rs: contiguity walk falls back to block_cache
select_chain_once's contiguity guard called chain_tree.ancestors(last_hash) to verify that a peer's replay chain reaches the picked common ancestor. The walk terminates at the first block whose parent is not in chain_tree. On_block_received inserts every fetched block into both chain_tree and block_cache — but the chain_tree insert is skipped when the header has no parsed info AND chain_tree doesn't already know the block (so block_no=0). That leaves the block in block_cache without a chain_tree entry, and the next contiguity walk terminates early, firing `fork mismatch (replay doesn't reach ancestor)` → OrphanCandidate. With the cooldown cap this no longer infects other peers, but individual nodes under sustained fork load can slowly get stuck on this false mismatch. Fix: add a hybrid walker that follows prev_hash links using chain_tree first and block_cache as a fallback. The walk terminates at a genuine gap (neither store has the parent) or at a genesis child (prev_hash=None) — both distinguished via a new HybridWalk.reached_origin flag so the genesis-reached check in select_chain_once still works. The walk is a new private method on PraosConsensus in selection.rs: walk_ancestors_hybrid(start_hash) -> HybridWalk. 4 new unit tests exercise: - chain_tree-only case (back-compat with pre-fix behaviour) - block_cache fallback (tree has tip + anchor, middle only in cache) - gap termination (parent in neither store → reached_origin=false) - start_only_in_cache (start block only in block_cache) Cluster verification at p=0.2: 24/25 nodes stayed healthy for ~55 min (vs previous build which had 4 stuck by T+60min). The one stuck node (node-4) hit a separate mux-level ingress-overflow bug during catch-up fetches, not the contiguity walk — tracked separately. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>