diff --git a/Cargo.lock b/Cargo.lock index 3f973541..66916640 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3865,6 +3865,7 @@ dependencies = [ "storage", "tempfile", "testnet_initial_state", + "thiserror 2.0.18", "tokio", "url", ] diff --git a/lez/common/src/block.rs b/lez/common/src/block.rs index 029bcfba..a8149e7c 100644 --- a/lez/common/src/block.rs +++ b/lez/common/src/block.rs @@ -178,7 +178,7 @@ mod tests { } .into_pending_block(&key); - let mut tampered = block.clone(); + let mut tampered = block; tampered.header.timestamp = 99; // header changed; stale hash no longer matches assert_ne!(tampered.recompute_hash(), tampered.header.hash); } diff --git a/lez/indexer/core/Cargo.toml b/lez/indexer/core/Cargo.toml index 758acdd6..2dfdf1bf 100644 --- a/lez/indexer/core/Cargo.toml +++ b/lez/indexer/core/Cargo.toml @@ -25,6 +25,7 @@ futures.workspace = true url.workspace = true logos-blockchain-core.workspace = true serde_json.workspace = true +thiserror.workspace = true async-stream.workspace = true tokio.workspace = true diff --git a/lez/indexer/core/src/block_store.rs b/lez/indexer/core/src/block_store.rs index a1cb71ef..617c4c60 100644 --- a/lez/indexer/core/src/block_store.rs +++ b/lez/indexer/core/src/block_store.rs @@ -168,13 +168,11 @@ impl IndexerStore { ) .context("Failed to execute genesis public transaction")?; } else { - transaction - .clone() - .execute_on_state( - &mut state_guard, - block.header.block_id, - block.header.timestamp, - )?; + transaction.clone().execute_on_state( + &mut state_guard, + block.header.block_id, + block.header.timestamp, + )?; } } diff --git a/lez/indexer/core/src/ingest_error.rs b/lez/indexer/core/src/ingest_error.rs new file mode 100644 index 00000000..4240a84c --- /dev/null +++ b/lez/indexer/core/src/ingest_error.rs @@ -0,0 +1,59 @@ +use common::HashType; +use serde::{Deserialize, Serialize}; + +/// Why the indexer could not apply an L2 block from the channel. Stored inside a +/// [`crate::chain_breaker::ChainBreaker`] and surfaced on the status snapshot. +#[derive(Debug, Clone, Serialize, Deserialize, thiserror::Error)] +#[serde(rename_all = "camelCase")] +pub enum BlockIngestError { + #[error("failed to deserialize L2 block: {0}")] + Deserialize(String), + #[error("unexpected block id: expected {expected}, got {got}")] + UnexpectedBlockId { expected: u64, got: u64 }, + #[error("broken chain link: expected prev {expected_prev}, got {got_prev}")] + BrokenChainLink { + expected_prev: HashType, + got_prev: HashType, + }, + #[error("block hash mismatch: computed {computed}, header {header}")] + HashMismatch { + computed: HashType, + header: HashType, + }, + #[error("state transition failed: {0}")] + StateTransition(String), + #[error("storage error: {0}")] + Storage(String), +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn serializes_and_round_trips_externally_tagged() { + let err = BlockIngestError::UnexpectedBlockId { + expected: 5, + got: 7, + }; + let value = serde_json::to_value(&err).expect("serialize"); + assert_eq!( + value, + serde_json::json!({ "unexpectedBlockId": { "expected": 5, "got": 7 } }) + ); + let back: BlockIngestError = serde_json::from_value(value).expect("deserialize"); + assert!(matches!( + back, + BlockIngestError::UnexpectedBlockId { + expected: 5, + got: 7 + } + )); + } + + #[test] + fn display_is_human_readable() { + let err = BlockIngestError::StateTransition("nonce too low".to_owned()); + assert_eq!(err.to_string(), "state transition failed: nonce too low"); + } +} diff --git a/lez/indexer/core/src/lib.rs b/lez/indexer/core/src/lib.rs index 0d595fc0..35ff3c6b 100644 --- a/lez/indexer/core/src/lib.rs +++ b/lez/indexer/core/src/lib.rs @@ -5,6 +5,7 @@ use arc_swap::ArcSwap; use common::block::Block; // ToDo: Remove after testnet use futures::StreamExt as _; +pub use ingest_error::BlockIngestError; use log::{error, info, warn}; use logos_blockchain_core::header::HeaderId; use logos_blockchain_zone_sdk::{ @@ -16,9 +17,9 @@ use crate::{ config::IndexerConfig, status::{IndexerStatus, IndexerSyncStatus}, }; - pub mod block_store; pub mod config; +pub mod ingest_error; pub mod status; #[derive(Clone)]