{"record":{"id":"7906e25ed0b826a4","repo":"linera-io/linera-protocol","slug":"evm-scan-loop-exited-unexpectedly-result","errorCode":null,"errorMessage":"EVM scan loop exited unexpectedly: {result:?}","messagePattern":"EVM scan loop exited unexpectedly: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"linera-bridge/src/relay/mod.rs","lineNumber":508,"sourceCode":"    );\n\n    // Safety-net inbox drain. Notification delivery can be missed (relay down,\n    // stream hiccup), and a missed `NewIncomingBundle` would otherwise strand\n    // its messages until the next one. Reuse the monitor's scan interval as the\n    // drain cadence; an empty inbox makes `process_inbox` a cheap no-op.\n    let mut inbox_drain_interval = tokio::time::interval(monitor_scan_interval);\n    // Consume the immediate first tick — the startup drain above already ran.\n    inbox_drain_interval.tick().await;\n\n    // ── Main loop: process chain operations + notifications ──\n    tracing::info!(\"Listening for chain operations and notifications...\");\n    loop {\n        tokio::select! {\n            result = &mut chain_listener_handle => {\n                anyhow::bail!(\"Chain listener exited unexpectedly: {result:?}\");\n            }\n            result = &mut evm_scan_handle => {\n                anyhow::bail!(\"EVM scan loop exited unexpectedly: {result:?}\");\n            }\n            result = &mut linera_scan_handle => {\n                anyhow::bail!(\"Linera scan loop exited unexpectedly: {result:?}\");\n            }\n            result = &mut retry_handle => {\n                anyhow::bail!(\"Retry loop exited unexpectedly: {result:?}\");\n            }\n            result = &mut http_server_handle => {\n                anyhow::bail!(\"HTTP server exited unexpectedly: {result:?}\");\n            }\n            result = &mut admin_server_handle => {\n                anyhow::bail!(\"Admin HTTP server exited unexpectedly: {result:?}\");\n            }\n            _ = inbox_drain_interval.tick() => {\n                // Periodic safety net for a missed `NewIncomingBundle`: sync and\n                // drain the inbox so stranded messages (e.g. user burns) are\n                // eventually processed even without a fresh notification.\n                if let Err(e) = chain_client.synchronize_from_validators().await {","sourceCodeStart":490,"sourceCodeEnd":526,"githubUrl":"https://github.com/linera-io/linera-protocol/blob/6c226ddcb332ef55118dc8d0aafbd093d5420899/linera-bridge/src/relay/mod.rs#L490-L526","documentation":"In serve_loop's tokio::select!, the EVM scan loop task exited. This loop periodically scans the EVM chain for deposit events (eth_getLogs within max_log_block_range windows); its exit — clean return or panic — fails the whole relay loop by design, because deposits would otherwise go unprocessed silently.","triggerScenarios":"The EVM scan future returns Err (persistent eth_getLogs failure after its internal retries, e.g. RPC endpoint down or rate-limiting for the whole scan interval) or panics on a code path like log decoding; the scan task also gets woken by scan_notify for burn events, so bugs in that path can kill it.","commonSituations":"Public RPC provider outage or sustained 429 rate limiting; an EVM log that fails decoding after a contract upgrade (ABI drift); misconfigured max_log_block_range exceeding the provider's cap causing repeated RPC errors.","solutions":["Check relay logs for the scan loop's own error before the bail — it logs the underlying RPC/decoding failure.","If RPC rate-limiting: switch to a better provider or raise rate limits; verify max_log_block_range is within the provider's allowed eth_getLogs window.","If a log decoding panic: capture the offending log from the backtrace context and fix the decoder/ABI, then restart the relay.","Restart the relay process; scanning resumes from persisted heights without duplicate processing."],"exampleFix":"// before\nresult = &mut evm_scan_handle => {\n    anyhow::bail!(\"EVM scan loop exited unexpectedly: {result:?}\");\n}\n\n// after: keep the raw error chain visible\nresult = &mut evm_scan_handle => {\n    anyhow::bail!(\"EVM scan loop exited unexpectedly: {result:?}; \\\n                 check RPC availability and eth_getLogs range limits\");\n}","handlingStrategy":"retry","validationCode":"// Before long runs: verify the RPC honors your scan window once at startup:\nlet bn = provider.get_block_number().await?;\nlet _ = provider.get_logs(&Filter::new().from_block(bn - max_log_block_range).to_block(bn)).await?; // fails fast if range is rejected","typeGuard":null,"tryCatchPattern":"// The scan loop's exit is fatal to serve_loop; catch at process level and restart.\n// Distinguish cause for operators:\nmatch result { Err(j) if j.is_panic() => log panic backtrace, _ => log last RPC error }","preventionTips":["Use a rate-limit-tolerant RPC provider and a max_log_block_range within its cap.","Never let the EVM scan task panic on undecodable logs — log and skip instead."],"tags":["linera-bridge","relay","evm","task-supervision","tokio","rust"],"backgroundTag":"background-task-exited","analyzedSha":"6c226ddcb332ef55118dc8d0aafbd093d5420899","analyzedAt":"2026-08-22T22:49:09.787Z","schemaVersion":2},"datasetVersion":"2026-08-23T01:17:44.959Z"}