ethl 0.1.22

Tools for capturing, processing, archiving, and replaying Ethereum events
Documentation
use alloy::rpc::types::Log;
use thiserror::Error;

use crate::storage::codec::decoder::DecodedEventWithHeader;

pub mod backfill;
pub mod config;
pub(crate) mod cursor;
pub mod error_tracking;
pub mod events;
pub mod heads;
pub mod provider;
pub mod stream;

#[derive(Debug, Clone, Error)]
pub enum RpcError {
    #[error("Connection Error: {0}")]
    ConnectionError(String),
    #[error("Subscription Error: {0}")]
    SubscriptionError(String),
    #[error("Log Fetch Error: {0}")]
    LogFetchError(String),
    #[error("Transport Error: {0}")]
    TransportError(String),
    #[error("All Providers Suspended: {0}")]
    AllProvidersSuspended(String),
    /// Internal-only signal — produced by `next_cursor_after_batch`, intercepted at
    /// the three call sites, never yielded to consumers. See `ProviderStalled` for the
    /// terminal variant that consumers receive when a provider tip is genuinely stalled.
    #[error(
        "Cursor past tip: refusing to advance to block {to_block}; provider reports tip {reported_tip}"
    )]
    CursorPastTip { to_block: u64, reported_tip: u64 },
    #[error(
        "provider stalled: tip {reported_tip} did not reach block {to_block} after {stalled_cycles} cycles"
    )]
    ProviderStalled {
        to_block: u64,
        reported_tip: u64,
        stalled_cycles: u32,
    },
    /// A tip reorg detected by the live `parent_hash` linkage. `common_ancestor`
    /// is the highest block the new chain still shares with what we recorded — the
    /// single value a consumer's rollback acts on. `depth` (detection point minus
    /// ancestor) and the competing hashes drive no decision, so they are not carried.
    #[error("reorg: common ancestor at block {common_ancestor}")]
    Reorg { common_ancestor: u64 },
    /// The divergence is older than the detector ring (`> finality`): no common
    /// ancestor exists within the finality window. A totality branch — unreachable
    /// under a finality-deep ring plus economic finality; it fires only on a genuine
    /// `> finality` catastrophe or a misconfigured too-shallow ring. Field-less: a
    /// consumer routes it straight to terminate / operator, never to a rollback, and
    /// a number measuring how far the walk got would drive no decision.
    #[error(
        "reorg beyond finality: no common ancestor within the detector ring (consensus invariant violated)"
    )]
    ReorgBeyondFinality,
    #[error("Other Error: {0}")]
    Other(String),
}

pub type LogRange = (u64, u64, Vec<Log>);
pub type DecodedEventRange = (u64, u64, Vec<DecodedEventWithHeader>);