Surfaced by #386, which fixed the API startup crash that previously killed the E2E contract events job before it reached this point. The job now gets all the way to the polling step and fails there. No indexer code is involved in #386 — this is a pre-existing bug that was simply unreachable while the job died earlier.
Symptom
E2E contract events fails at "Poll /v1/events for the decoded mint event". The contract deploys and the mint is invoked successfully, but nothing is ever indexed. The indexer logs the same error every poll until the bounded wait expires:
{"cursor":0,"message":"Resuming from ledger cursor"}
{"error":"RPC error: getEvents: RPC error -32600: startLedger must be within the ledger range: 7 - 457","message":"Transient poll failure, will retry next interval"}
{"error":"... startLedger must be within the ledger range: 7 - 470", ...}
{"error":"... startLedger must be within the ledger range: 7 - 484", ...}
The upper bound advances each poll; the lower bound stays at 7. Every request is rejected, so total is always 0 and the mint never lands.
Cause
crates/indexer/src/streamer/mod.rs:499-514. On a fresh database get_cursor returns 0, and poll_once computes:
let effective_cursor = if *cursor == 0 {
match &self.contract_filter {
Some(filter) => { /* min index_from - 1 */ }
None => 0,
}
} else { *cursor };
With no contract allowlist — which is exactly what the e2e stack runs, per "No contract allowlist; requesting all events (index-all mode)" — effective_cursor stays 0 and page_request_params sends startLedger = 1.
The code comment states "A fresh index (cursor 0) starts at ledger 1", which assumes ledger 1 is always retrievable. The Soroban RPC only retains a recent window, so on the CI quickstart network the valid floor is ledger 7, and on a real network with retention it will be far higher. The indexer never recovers on its own because the error is classified Retryable and the cursor is never advanced past the floor.
Suggested fix
Clamp a fresh cursor to the RPC's advertised lower bound instead of assuming ledger 1. getLatestLedger / the getEvents error already expose the retained range, so on a fresh index the streamer can start at the oldest retained ledger (or latest - N) rather than 1. Worth also considering whether a persistently out-of-range startLedger should escalate out of Retryable, since retrying an out-of-range request unchanged can never succeed.
Scope note
Deliberately not fixed in #386 — that PR is CI configuration and a dependency bump, and this is an indexer behaviour change that deserves its own review and test. #386 leaves E2E contract events red for this reason; E2E smoke test passes there.
Surfaced by #386, which fixed the API startup crash that previously killed the
E2E contract eventsjob before it reached this point. The job now gets all the way to the polling step and fails there. No indexer code is involved in #386 — this is a pre-existing bug that was simply unreachable while the job died earlier.Symptom
E2E contract eventsfails at "Poll /v1/events for the decoded mint event". The contract deploys and the mint is invoked successfully, but nothing is ever indexed. The indexer logs the same error every poll until the bounded wait expires:The upper bound advances each poll; the lower bound stays at 7. Every request is rejected, so
totalis always 0 and the mint never lands.Cause
crates/indexer/src/streamer/mod.rs:499-514. On a fresh databaseget_cursorreturns 0, andpoll_oncecomputes:With no contract allowlist — which is exactly what the e2e stack runs, per
"No contract allowlist; requesting all events (index-all mode)"—effective_cursorstays 0 andpage_request_paramssendsstartLedger = 1.The code comment states "A fresh index (cursor 0) starts at ledger 1", which assumes ledger 1 is always retrievable. The Soroban RPC only retains a recent window, so on the CI quickstart network the valid floor is ledger 7, and on a real network with retention it will be far higher. The indexer never recovers on its own because the error is classified
Retryableand the cursor is never advanced past the floor.Suggested fix
Clamp a fresh cursor to the RPC's advertised lower bound instead of assuming ledger 1.
getLatestLedger/ thegetEventserror already expose the retained range, so on a fresh index the streamer can start at the oldest retained ledger (orlatest - N) rather than 1. Worth also considering whether a persistently out-of-rangestartLedgershould escalate out ofRetryable, since retrying an out-of-range request unchanged can never succeed.Scope note
Deliberately not fixed in #386 — that PR is CI configuration and a dependency bump, and this is an indexer behaviour change that deserves its own review and test. #386 leaves
E2E contract eventsred for this reason;E2E smoke testpasses there.