mirror of
https://github.com/logos-blockchain/logos-execution-zone.git
synced 2026-07-09 23:39:34 +00:00
fix(indexer): do not park immediately on possibly-transient apply failures
This commit is contained in:
parent
c9f7beb95f
commit
93182f0545
@ -29,6 +29,11 @@ pub enum AcceptOutcome {
|
||||
AlreadyApplied,
|
||||
/// Did not chain or failed to apply; tip stays frozen, stall recorded.
|
||||
Parked(BlockIngestError),
|
||||
/// Chained but failed to apply, possibly transiently
|
||||
/// ([`BlockIngestError::is_retryable`]); nothing recorded, tip and state
|
||||
/// untouched. The caller retries and parks via
|
||||
/// [`IndexerStore::record_stall`] once it gives up.
|
||||
ApplyFailed(BlockIngestError),
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
@ -254,6 +259,9 @@ impl IndexerStore {
|
||||
// TODO: we use scratch state to be atomic, but need to revisit how expensive a clone is
|
||||
let mut scratch = self.current_state.read().await.clone();
|
||||
if let Err(err) = apply_block_to_scratch(block, &mut scratch) {
|
||||
if err.is_retryable() {
|
||||
return Ok(AcceptOutcome::ApplyFailed(err));
|
||||
}
|
||||
self.record_stall(Some(&block.header), l1_slot, err.clone())?;
|
||||
return Ok(AcceptOutcome::Parked(err));
|
||||
}
|
||||
@ -876,4 +884,44 @@ mod accept_tests {
|
||||
let reopened = IndexerStore::open_db(dir.path()).expect("reopen");
|
||||
assert_eq!(reopened.last_block().unwrap(), Some(101));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn transient_apply_failure_returns_apply_failed_without_stall() {
|
||||
use testnet_initial_state::initial_pub_accounts_private_keys;
|
||||
|
||||
let dir = tempfile::tempdir().expect("tempdir");
|
||||
let store = IndexerStore::open_db(dir.path()).expect("open store");
|
||||
|
||||
let accounts = initial_pub_accounts_private_keys();
|
||||
let from = accounts[0].account_id;
|
||||
let to = accounts[1].account_id;
|
||||
let sign_key = accounts[0].pub_sign_key.clone();
|
||||
|
||||
let genesis = produce_dummy_block(1, None, vec![]);
|
||||
store
|
||||
.accept_block(&genesis, Slot::from(0))
|
||||
.await
|
||||
.expect("accept genesis");
|
||||
|
||||
// Overdraft: rejected during execution → StateTransition → retryable.
|
||||
let tx = common::test_utils::create_transaction_native_token_transfer(
|
||||
from,
|
||||
0,
|
||||
to,
|
||||
1_000_000_000,
|
||||
&sign_key,
|
||||
);
|
||||
let block = produce_dummy_block(2, Some(genesis.header.hash), vec![tx]);
|
||||
let outcome = store.accept_block(&block, Slot::from(0)).await.unwrap();
|
||||
|
||||
assert!(matches!(
|
||||
outcome,
|
||||
AcceptOutcome::ApplyFailed(BlockIngestError::StateTransition { .. })
|
||||
));
|
||||
assert!(
|
||||
store.get_stall_reason().unwrap().is_none(),
|
||||
"retryable failure must not persist a stall"
|
||||
);
|
||||
assert_eq!(store.get_last_block_id().unwrap(), Some(1), "tip frozen");
|
||||
}
|
||||
}
|
||||
|
||||
@ -40,6 +40,19 @@ pub enum BlockIngestError {
|
||||
},
|
||||
}
|
||||
|
||||
impl BlockIngestError {
|
||||
/// Whether the failure may be transient rather than a property of the block.
|
||||
///
|
||||
/// FIXME: `StateTransition` is too coarse — its `reason` string mixes genuine
|
||||
/// state-transition rejections with infra failures (risc0 executor teardown,
|
||||
/// storage errors). Once it carries a structured cause, narrow this so only
|
||||
/// infra failures retry.
|
||||
#[must_use]
|
||||
pub const fn is_retryable(&self) -> bool {
|
||||
matches!(self, Self::StateTransition { .. })
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
@ -236,6 +236,15 @@ impl IndexerCore {
|
||||
// L1 proceeds regardless
|
||||
self.advance_cursor(&mut cursor, slot);
|
||||
}
|
||||
Ok(AcceptOutcome::ApplyFailed(ingest_err)) => {
|
||||
error!(
|
||||
"Apply failed at block {}: {ingest_err}",
|
||||
block.header.block_id
|
||||
);
|
||||
self.set_status(IndexerSyncStatus::error(ingest_err.to_string()));
|
||||
had_cycle_error = true;
|
||||
break;
|
||||
}
|
||||
Err(err) => {
|
||||
// Infrastructure error (DB read/write), not a bad block.
|
||||
// will re-poll from the same cursor next cycle.
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user