Skip to content

indexer: fresh cursor starts at ledger 1 and is rejected when the RPC has pruned early ledgers (E2E contract events) #388

Description

@Depo-dev

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions