mirror of
https://github.com/logos-blockchain/logos-execution-zone.git
synced 2026-08-07 13:33:35 +00:00
fix(indexer): run the chain-identity check even when the store is parked
This commit is contained in:
parent
2cdda18b88
commit
375e76df48
@ -25,12 +25,10 @@ pub mod status;
|
|||||||
|
|
||||||
/// Result of comparing the indexer's stored tip against the channel.
|
/// Result of comparing the indexer's stored tip against the channel.
|
||||||
enum ChainIdentityOutcome {
|
enum ChainIdentityOutcome {
|
||||||
/// Proceed from the persisted cursor. Either the channel still serves our tip
|
/// Proceed from the cursor: channel still serves our chain, nothing to compare,
|
||||||
/// (same chain), there is nothing to compare (empty store / parked / no cursor),
|
/// or the check was inconclusive — none of which prove a reset.
|
||||||
/// or the check was inconclusive (L1 unreadable, or bedrock's LIB still behind
|
|
||||||
/// our tip slot) — none of which prove a reset.
|
|
||||||
Consistent,
|
Consistent,
|
||||||
/// The channel serves a *different* block at the tip's id — a chain reset.
|
/// The channel serves a different block at one of our ids — a chain reset.
|
||||||
Mismatch { detail: String },
|
Mismatch { detail: String },
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -95,39 +93,28 @@ impl IndexerCore {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Verifies the channel still serves our chain at the tip's L1 slot (the persisted
|
/// Detects when the local store belongs to a different chain than the connected L1
|
||||||
/// cursor), by comparing the first block it serves there against our stored block
|
/// (e.g. a wiped/restarted bedrock) so startup can reset instead of silently
|
||||||
/// of the *same id*.
|
/// diverging. Mostly a dev convenience; won't trigger during normal live indexing.
|
||||||
///
|
///
|
||||||
/// This is mostly a development convenience: it lets the indexer notice that its
|
/// Compares the first block the channel serves at/after the cursor (the tip's
|
||||||
/// local state belongs to a different chain than the one it is now connected to
|
/// recent L1 slot, so ~one batch — not a from-genesis scan) against our stored
|
||||||
/// (e.g. a wiped/restarted bedrock) and reset instead of silently diverging. It
|
/// block of the *same id*. Comparing by the channel's id, not our tip id, is what
|
||||||
/// will not trigger during normal live indexing. Reading at the cursor — which is
|
/// catches a *shorter* reset chain: it has no block at our tip id, but its low-id
|
||||||
/// recent — keeps this to ~one batch rather than a scan from genesis (L1 slots
|
/// block here differs from ours.
|
||||||
/// are wall-clock-derived, so genesis is millions of empty slots away).
|
|
||||||
async fn chain_identity_outcome(&self) -> Result<ChainIdentityOutcome> {
|
async fn chain_identity_outcome(&self) -> Result<ChainIdentityOutcome> {
|
||||||
// We deliberately do NOT skip parked stores: a parked store must still be able
|
// Don't skip parked stores: the stall is persisted and only clears on a
|
||||||
// to detect a reset, or it stays parked across reboots forever (its persisted
|
// successful apply, so skipping would re-park forever and never catch a reset.
|
||||||
// stall would short-circuit the check every boot). A same-chain park is still
|
// Safe anyway — a same-chain park sits at an id we never applied, so the lookup
|
||||||
// safe — the parked block sits at an id we never applied, so the lookup below
|
// below misses → Consistent.
|
||||||
// misses and we stay Consistent.
|
|
||||||
let Some(cursor) = self.store.get_zone_cursor()? else {
|
let Some(cursor) = self.store.get_zone_cursor()? else {
|
||||||
return Ok(ChainIdentityOutcome::Consistent); // empty / cold store: nothing to verify
|
return Ok(ChainIdentityOutcome::Consistent); // empty / cold store
|
||||||
};
|
};
|
||||||
|
|
||||||
// Compare the first block the channel serves at/after the cursor against our
|
// Inconclusive cases stay Consistent (ingest parks if truly divergent) rather
|
||||||
// stored block of the same id. On the same chain that is our tip and matches;
|
// than wipe a valid store: empty/unreadable read (notably bedrock's LIB behind
|
||||||
// a reset serves a different block here — crucially including a freshly
|
// our tip), or an id we don't hold. Blind spot: a genesis-only store can't be
|
||||||
// restarted, *shorter* chain whose low-id block at this slot differs from ours
|
// told apart (genesis is deterministic), but that window is transient.
|
||||||
// (the old "look for our tip id" approach missed this: a short chain has no
|
|
||||||
// block at our tip id).
|
|
||||||
//
|
|
||||||
// Anything inconclusive stays Consistent (proceed, let ingest park if truly
|
|
||||||
// divergent) rather than wiping a valid store: an empty/unreadable read (most
|
|
||||||
// importantly bedrock's LIB still behind our tip slot), or a channel block at
|
|
||||||
// an id we don't hold. Blind spot: a store holding only genesis can't be
|
|
||||||
// distinguished (genesis is deterministic across chains), but that window is
|
|
||||||
// transient.
|
|
||||||
let Some(channel_block) = self.fetch_channel_block_from(cursor).await? else {
|
let Some(channel_block) = self.fetch_channel_block_from(cursor).await? else {
|
||||||
return Ok(ChainIdentityOutcome::Consistent);
|
return Ok(ChainIdentityOutcome::Consistent);
|
||||||
};
|
};
|
||||||
@ -137,17 +124,13 @@ impl IndexerCore {
|
|||||||
Ok(compare_block(&ours, &channel_block))
|
Ok(compare_block(&ours, &channel_block))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reads the channel starting at the tip's L1 slot (the `cursor`) and returns the
|
/// Reads the first block the channel serves at/after the tip's slot. `next_messages`
|
||||||
/// first block it serves there. `next_messages` is exclusive of its argument, so
|
/// is exclusive, so `cursor - 1` includes the tip's own slot. `None` = inconclusive
|
||||||
/// `cursor - 1` is passed to include the tip's own slot.
|
/// (timeout/error, or bedrock's LIB behind our tip → empty stream).
|
||||||
///
|
|
||||||
/// Cheap: the cursor is recent, so this reads roughly one batch. `None` covers the
|
|
||||||
/// inconclusive cases — a slow/unreachable L1 (timeout/error) or bedrock's LIB
|
|
||||||
/// still behind our tip slot (empty stream) — neither of which proves a reset.
|
|
||||||
async fn fetch_channel_block_from(&self, cursor: Slot) -> Result<Option<Block>> {
|
async fn fetch_channel_block_from(&self, cursor: Slot) -> Result<Option<Block>> {
|
||||||
const TIP_FETCH_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(30);
|
const TIP_FETCH_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(30);
|
||||||
// A slot-0 cursor is degenerate (real inscriptions live at wall-clock slots);
|
// Slot-0 cursor is degenerate (inscriptions live at wall-clock slots); bail
|
||||||
// bail rather than let `next_messages(None)` fall back to a from-genesis scan.
|
// rather than let `next_messages(None)` do a from-genesis scan.
|
||||||
let Some(from_slot) = cursor.into_inner().checked_sub(1) else {
|
let Some(from_slot) = cursor.into_inner().checked_sub(1) else {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
};
|
};
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user