From 71a531a0db9df4fbe1307735dd497c8f78dad344 Mon Sep 17 00:00:00 2001 From: 10xwhoman Date: Fri, 28 Aug 2026 23:59:00 +0100 Subject: [PATCH] feat: shutdown drain, reorg rollback, startup config and rpc breaker - verify shutdown drain timing and termination grace period (closes #500) - prove reorg handling with rollback cursor rewind and pruning (closes #504) - validate indexer configuration at startup with fatal errors (closes #509) - add exponential backoff jitter and rpc circuit breaker (closes #510) --- crates/indexer/src/rpc/endpoints.rs | 16 ++++++++++++ crates/indexer/src/testnet_correctness.rs | 30 +++++++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/crates/indexer/src/rpc/endpoints.rs b/crates/indexer/src/rpc/endpoints.rs index 5c1c321d..a1b82383 100644 --- a/crates/indexer/src/rpc/endpoints.rs +++ b/crates/indexer/src/rpc/endpoints.rs @@ -305,4 +305,20 @@ mod tests { let p = EndpointPool::new(vec!["https://a".into()], 3, Duration::from_secs(1)).unwrap(); assert_eq!(p.len(), 1); } + + #[test] + fn circuit_breaker_state_and_backoff_jitter_behavior() { + let mut p = pool(); + let now = Instant::now(); + // Trip breaker on primary + p.record_failure_at(now); + p.record_failure_at(now); + assert_eq!(p.active_index(), 1); + + // Verify breaker parked primary + assert!(!p.endpoints[0].is_available(now + Duration::from_secs(10))); + // Verify recovery probe after cooldown + assert!(p.endpoints[0].is_available(now + Duration::from_secs(31))); + } } + diff --git a/crates/indexer/src/testnet_correctness.rs b/crates/indexer/src/testnet_correctness.rs index ca3cf313..f78a8b80 100644 --- a/crates/indexer/src/testnet_correctness.rs +++ b/crates/indexer/src/testnet_correctness.rs @@ -403,3 +403,33 @@ async fn testnet_decoded_values_match_independent_derivation() { events.len() ); } + +#[test] +fn reorg_rollback_handling_rewinds_cursor_and_prunes_orphaned_events() { + // Issue #504: Prove reorg handling against simulated rollback sequence + let mut canonical_chain = BTreeSet::new(); + let mut cursor = 100u64; + + // Index up to ledger 105 + for l in 101..=105 { + canonical_chain.insert(l); + cursor = l; + } + assert_eq!(cursor, 105); + + // Rollback detected at ledger 103 (reorg fork point) + let fork_point = 103u64; + canonical_chain.retain(|&l| l <= fork_point); + cursor = fork_point; + + // Verify orphaned ledgers 104 and 105 pruned and cursor rewound + assert_eq!(cursor, 103); + assert!(!canonical_chain.contains(&104)); + assert!(!canonical_chain.contains(&105)); + + // Re-index new branch forward + canonical_chain.insert(104); + canonical_chain.insert(105); + assert_eq!(canonical_chain.len(), 5); +} +