PR: #32 feat/07-block-listener
File: crates/charon-scanner/src/listener.rs — BlockListener::run_once, inside while let Some(header) loop
Problem
if self.tx.send(event).await.is_err() {
return Ok(());
}
mpsc::Sender::send is async; suspends task until receiver pops a slot. If downstream scanner stalls — even briefly — WS drain loop suspends here. alloy WebSocket does not drain server frames while future pending. After server send-buffer fills, connection dropped, triggering reconnect with exponential backoff. Blocks produced during reconnect never seen.
BSC ~3s per block: 1s scanner stall = one missed block. Liquidation opportunity can appear and be gone within single block.
Impact
Silent missed blocks during downstream congestion. No observable error; reconnect counter increments but root cause (slow consumer) invisible.
Fix
Replace blocking send with non-blocking variant; log drops:
match self.tx.try_send(event) {
Ok(()) => {}
Err(mpsc::error::TrySendError::Full(_)) => {
warn!(chain = %self.name, block = number, "channel full — block dropped");
}
Err(mpsc::error::TrySendError::Closed(_)) => return Ok(()),
}
Alternatively send_timeout(event, Duration::from_millis(200)). Invariant: WS drain loop must never block indefinitely.
PR: #32 feat/07-block-listener
File: crates/charon-scanner/src/listener.rs —
BlockListener::run_once, insidewhile let Some(header)loopProblem
mpsc::Sender::sendis async; suspends task until receiver pops a slot. If downstream scanner stalls — even briefly — WS drain loop suspends here. alloy WebSocket does not drain server frames while future pending. After server send-buffer fills, connection dropped, triggering reconnect with exponential backoff. Blocks produced during reconnect never seen.BSC ~3s per block: 1s scanner stall = one missed block. Liquidation opportunity can appear and be gone within single block.
Impact
Silent missed blocks during downstream congestion. No observable error; reconnect counter increments but root cause (slow consumer) invisible.
Fix
Replace blocking send with non-blocking variant; log drops:
Alternatively
send_timeout(event, Duration::from_millis(200)). Invariant: WS drain loop must never block indefinitely.