mirror of
https://github.com/logos-blockchain/logos-execution-zone.git
synced 2026-07-28 00:23:27 +00:00
refactor(indexer): use "stall reason" instead of "chain breaker"
This commit is contained in:
parent
e28cceffab
commit
c49cc7bc29
@ -14,7 +14,7 @@ use logos_blockchain_zone_sdk::Slot;
|
|||||||
use storage::indexer::RocksDBIO;
|
use storage::indexer::RocksDBIO;
|
||||||
use tokio::sync::RwLock;
|
use tokio::sync::RwLock;
|
||||||
|
|
||||||
use crate::{chain_breaker::ChainBreaker, ingest_error::BlockIngestError};
|
use crate::{ingest_error::BlockIngestError, stall_reason::StallReason};
|
||||||
|
|
||||||
struct Tip {
|
struct Tip {
|
||||||
block_id: u64,
|
block_id: u64,
|
||||||
@ -25,7 +25,7 @@ struct Tip {
|
|||||||
pub enum AcceptOutcome {
|
pub enum AcceptOutcome {
|
||||||
/// Chained and applied; tip and L1 read cursor both advance.
|
/// Chained and applied; tip and L1 read cursor both advance.
|
||||||
Applied,
|
Applied,
|
||||||
/// Did not chain or failed to apply; tip stays frozen, breaker recorded.
|
/// Did not chain or failed to apply; tip stays frozen, stall recorded.
|
||||||
Parked(BlockIngestError),
|
Parked(BlockIngestError),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -134,18 +134,18 @@ impl IndexerStore {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_chain_breaker(&self) -> Result<Option<ChainBreaker>> {
|
pub fn get_stall_reason(&self) -> Result<Option<StallReason>> {
|
||||||
let Some(bytes) = self.dbio.get_chain_breaker_bytes()? else {
|
let Some(bytes) = self.dbio.get_stall_reason_bytes()? else {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
};
|
};
|
||||||
let breaker: Option<ChainBreaker> =
|
let stall: Option<StallReason> =
|
||||||
serde_json::from_slice(&bytes).context("Failed to deserialize stored chain breaker")?;
|
serde_json::from_slice(&bytes).context("Failed to deserialize stored stall reason")?;
|
||||||
Ok(breaker)
|
Ok(stall)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_chain_breaker(&self, breaker: &Option<ChainBreaker>) -> Result<()> {
|
pub fn set_stall_reason(&self, stall: &Option<StallReason>) -> Result<()> {
|
||||||
let bytes = serde_json::to_vec(breaker).context("Failed to serialize chain breaker")?;
|
let bytes = serde_json::to_vec(stall).context("Failed to serialize stall reason")?;
|
||||||
self.dbio.put_chain_breaker_bytes(&bytes)?;
|
self.dbio.put_stall_reason_bytes(&bytes)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -224,20 +224,20 @@ impl IndexerStore {
|
|||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Records the chain breaker: the first break is stored verbatim; subsequent
|
/// Records the stall reason: the first break is stored verbatim; subsequent
|
||||||
/// breaks only bump `orphans_since`, preserving the original cause.
|
/// breaks only bump `orphans_since`, preserving the original cause.
|
||||||
fn record_break(
|
fn record_stall(
|
||||||
&self,
|
&self,
|
||||||
header: Option<&BlockHeader>,
|
header: Option<&BlockHeader>,
|
||||||
l1_slot: serde_json::Value,
|
l1_slot: serde_json::Value,
|
||||||
error: BlockIngestError,
|
error: BlockIngestError,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let breaker = match self.get_chain_breaker()? {
|
let stall = match self.get_stall_reason()? {
|
||||||
Some(mut existing) => {
|
Some(mut existing) => {
|
||||||
existing.orphans_since = existing.orphans_since.saturating_add(1);
|
existing.orphans_since = existing.orphans_since.saturating_add(1);
|
||||||
existing
|
existing
|
||||||
}
|
}
|
||||||
None => ChainBreaker {
|
None => StallReason {
|
||||||
block_id: header.map(|h| h.block_id),
|
block_id: header.map(|h| h.block_id),
|
||||||
block_hash: header.map(|h| h.hash),
|
block_hash: header.map(|h| h.hash),
|
||||||
prev_block_hash: header.map(|h| h.prev_block_hash),
|
prev_block_hash: header.map(|h| h.prev_block_hash),
|
||||||
@ -247,35 +247,35 @@ impl IndexerStore {
|
|||||||
orphans_since: 0,
|
orphans_since: 0,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
self.set_chain_breaker(&Some(breaker))
|
self.set_stall_reason(&Some(stall))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Records a breaker for an inscription that could not even be parsed.
|
/// Records a stall for an inscription that could not even be parsed.
|
||||||
pub fn record_deserialize_break(
|
pub fn record_deserialize_stall(
|
||||||
&self,
|
&self,
|
||||||
l1_slot: serde_json::Value,
|
l1_slot: serde_json::Value,
|
||||||
error: String,
|
error: String,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
self.record_break(None, l1_slot, BlockIngestError::Deserialize(error))
|
self.record_stall(None, l1_slot, BlockIngestError::Deserialize(error))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Validates `block` against the tip and, if it chains, applies it atomically
|
/// Validates `block` against the tip and, if it chains, applies it atomically
|
||||||
/// (scratch clone, commit only on full success) and advances the tip. On any
|
/// (scratch clone, commit only on full success) and advances the tip. On any
|
||||||
/// failure records the breaker and returns `Parked` without touching state.
|
/// failure records the stall and returns `Parked` without touching state.
|
||||||
pub async fn accept_block(
|
pub async fn accept_block(
|
||||||
&self,
|
&self,
|
||||||
block: &Block,
|
block: &Block,
|
||||||
l1_slot: serde_json::Value,
|
l1_slot: serde_json::Value,
|
||||||
) -> Result<AcceptOutcome> {
|
) -> Result<AcceptOutcome> {
|
||||||
if let Some(err) = self.acceptance_error(block)? {
|
if let Some(err) = self.acceptance_error(block)? {
|
||||||
self.record_break(Some(&block.header), l1_slot, err.clone())?;
|
self.record_stall(Some(&block.header), l1_slot, err.clone())?;
|
||||||
return Ok(AcceptOutcome::Parked(err));
|
return Ok(AcceptOutcome::Parked(err));
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: we use scratch state to be atomic, but need to revisit how expensive a clone is
|
// 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();
|
let mut scratch = self.current_state.read().await.clone();
|
||||||
if let Err(err) = apply_block_to_scratch(block, &mut scratch) {
|
if let Err(err) = apply_block_to_scratch(block, &mut scratch) {
|
||||||
self.record_break(Some(&block.header), l1_slot, err.clone())?;
|
self.record_stall(Some(&block.header), l1_slot, err.clone())?;
|
||||||
return Ok(AcceptOutcome::Parked(err));
|
return Ok(AcceptOutcome::Parked(err));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -283,13 +283,13 @@ impl IndexerStore {
|
|||||||
stored.bedrock_status = BedrockStatus::Finalized;
|
stored.bedrock_status = BedrockStatus::Finalized;
|
||||||
if let Err(err) = self.dbio.put_block(&stored, [0_u8; 32]) {
|
if let Err(err) = self.dbio.put_block(&stored, [0_u8; 32]) {
|
||||||
let ingest_err = BlockIngestError::Storage(err.to_string());
|
let ingest_err = BlockIngestError::Storage(err.to_string());
|
||||||
self.record_break(Some(&block.header), l1_slot, ingest_err.clone())?;
|
self.record_stall(Some(&block.header), l1_slot, ingest_err.clone())?;
|
||||||
return Ok(AcceptOutcome::Parked(ingest_err));
|
return Ok(AcceptOutcome::Parked(ingest_err));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Commit in-memory state (infallible) only after the DB write succeeded.
|
// Commit in-memory state (infallible) only after the DB write succeeded.
|
||||||
*self.current_state.write().await = scratch;
|
*self.current_state.write().await = scratch;
|
||||||
self.set_chain_breaker(&None)?;
|
self.set_stall_reason(&None)?;
|
||||||
Ok(AcceptOutcome::Applied)
|
Ok(AcceptOutcome::Applied)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -412,20 +412,20 @@ fn apply_block_to_scratch(block: &Block, state: &mut V03State) -> Result<(), Blo
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod chain_breaker_tests {
|
mod stall_reason_tests {
|
||||||
use common::HashType;
|
use common::HashType;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::{chain_breaker::ChainBreaker, ingest_error::BlockIngestError};
|
use crate::{ingest_error::BlockIngestError, stall_reason::StallReason};
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn chain_breaker_roundtrips_and_clears() {
|
async fn stall_reason_roundtrips_and_clears() {
|
||||||
let dir = tempfile::tempdir().expect("tempdir");
|
let dir = tempfile::tempdir().expect("tempdir");
|
||||||
let store = IndexerStore::open_db(dir.path()).expect("open store");
|
let store = IndexerStore::open_db(dir.path()).expect("open store");
|
||||||
|
|
||||||
assert!(store.get_chain_breaker().expect("get").is_none());
|
assert!(store.get_stall_reason().expect("get").is_none());
|
||||||
|
|
||||||
let breaker = ChainBreaker {
|
let stall = StallReason {
|
||||||
block_id: Some(7),
|
block_id: Some(7),
|
||||||
block_hash: Some(HashType([1_u8; 32])),
|
block_hash: Some(HashType([1_u8; 32])),
|
||||||
prev_block_hash: Some(HashType([2_u8; 32])),
|
prev_block_hash: Some(HashType([2_u8; 32])),
|
||||||
@ -434,17 +434,15 @@ mod chain_breaker_tests {
|
|||||||
first_seen: Some(99),
|
first_seen: Some(99),
|
||||||
orphans_since: 3,
|
orphans_since: 3,
|
||||||
};
|
};
|
||||||
store
|
store.set_stall_reason(&Some(stall)).expect("set stall");
|
||||||
.set_chain_breaker(&Some(breaker))
|
|
||||||
.expect("set breaker");
|
|
||||||
|
|
||||||
let got = store.get_chain_breaker().expect("get").expect("present");
|
let got = store.get_stall_reason().expect("get").expect("present");
|
||||||
assert_eq!(got.block_id, Some(7));
|
assert_eq!(got.block_id, Some(7));
|
||||||
assert_eq!(got.orphans_since, 3);
|
assert_eq!(got.orphans_since, 3);
|
||||||
assert!(matches!(got.error, BlockIngestError::StateTransition(_)));
|
assert!(matches!(got.error, BlockIngestError::StateTransition(_)));
|
||||||
|
|
||||||
store.set_chain_breaker(&None).expect("clear");
|
store.set_stall_reason(&None).expect("clear");
|
||||||
assert!(store.get_chain_breaker().expect("get").is_none());
|
assert!(store.get_stall_reason().expect("get").is_none());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -636,9 +634,9 @@ mod accept_tests {
|
|||||||
got: 2
|
got: 2
|
||||||
})
|
})
|
||||||
));
|
));
|
||||||
let breaker = store.get_chain_breaker().expect("get").expect("present");
|
let stall = store.get_stall_reason().expect("get").expect("present");
|
||||||
assert_eq!(breaker.block_id, Some(2));
|
assert_eq!(stall.block_id, Some(2));
|
||||||
assert_eq!(breaker.orphans_since, 0);
|
assert_eq!(stall.orphans_since, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
@ -675,22 +673,22 @@ mod accept_tests {
|
|||||||
.await
|
.await
|
||||||
.expect("accept");
|
.expect("accept");
|
||||||
|
|
||||||
let breaker = store.get_chain_breaker().expect("get").expect("present");
|
let stall = store.get_stall_reason().expect("get").expect("present");
|
||||||
assert_eq!(breaker.block_id, Some(2), "first breaker preserved");
|
assert_eq!(stall.block_id, Some(2), "first stall preserved");
|
||||||
assert_eq!(breaker.orphans_since, 1, "second break counted as orphan");
|
assert_eq!(stall.orphans_since, 1, "second break counted as orphan");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn deserialize_break_records_breaker_without_header() {
|
async fn deserialize_break_records_stall_without_header() {
|
||||||
let dir = tempfile::tempdir().expect("tempdir");
|
let dir = tempfile::tempdir().expect("tempdir");
|
||||||
let store = IndexerStore::open_db(dir.path()).expect("open store");
|
let store = IndexerStore::open_db(dir.path()).expect("open store");
|
||||||
|
|
||||||
store
|
store
|
||||||
.record_deserialize_break(serde_json::Value::Null, "bad bytes".to_owned())
|
.record_deserialize_stall(serde_json::Value::Null, "bad bytes".to_owned())
|
||||||
.expect("record");
|
.expect("record");
|
||||||
|
|
||||||
let breaker = store.get_chain_breaker().expect("get").expect("present");
|
let stall = store.get_stall_reason().expect("get").expect("present");
|
||||||
assert_eq!(breaker.block_id, None);
|
assert_eq!(stall.block_id, None);
|
||||||
assert!(matches!(breaker.error, BlockIngestError::Deserialize(_)));
|
assert!(matches!(stall.error, BlockIngestError::Deserialize(_)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,7 +2,7 @@ use common::HashType;
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
/// Why the indexer could not apply an L2 block from the channel. Stored inside a
|
/// 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.
|
/// [`crate::stall_reason::StallReason`] and surfaced on the status snapshot.
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, thiserror::Error)]
|
#[derive(Debug, Clone, Serialize, Deserialize, thiserror::Error)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub enum BlockIngestError {
|
pub enum BlockIngestError {
|
||||||
|
|||||||
@ -2,7 +2,6 @@ use std::{path::Path, sync::Arc};
|
|||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use arc_swap::ArcSwap;
|
use arc_swap::ArcSwap;
|
||||||
pub use chain_breaker::ChainBreaker;
|
|
||||||
use common::block::Block;
|
use common::block::Block;
|
||||||
// ToDo: Remove after testnet
|
// ToDo: Remove after testnet
|
||||||
use futures::StreamExt as _;
|
use futures::StreamExt as _;
|
||||||
@ -12,6 +11,7 @@ use logos_blockchain_core::header::HeaderId;
|
|||||||
use logos_blockchain_zone_sdk::{
|
use logos_blockchain_zone_sdk::{
|
||||||
CommonHttpClient, ZoneMessage, adapter::NodeHttpClient, indexer::ZoneIndexer,
|
CommonHttpClient, ZoneMessage, adapter::NodeHttpClient, indexer::ZoneIndexer,
|
||||||
};
|
};
|
||||||
|
pub use stall_reason::StallReason;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
block_store::IndexerStore,
|
block_store::IndexerStore,
|
||||||
@ -19,9 +19,9 @@ use crate::{
|
|||||||
status::{IndexerStatus, IndexerSyncStatus},
|
status::{IndexerStatus, IndexerSyncStatus},
|
||||||
};
|
};
|
||||||
pub mod block_store;
|
pub mod block_store;
|
||||||
pub mod chain_breaker;
|
|
||||||
pub mod config;
|
pub mod config;
|
||||||
pub mod ingest_error;
|
pub mod ingest_error;
|
||||||
|
pub mod stall_reason;
|
||||||
pub mod status;
|
pub mod status;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -62,11 +62,11 @@ impl IndexerCore {
|
|||||||
pub fn status(&self) -> IndexerStatus {
|
pub fn status(&self) -> IndexerStatus {
|
||||||
let sync = IndexerSyncStatus::clone(&self.status.load());
|
let sync = IndexerSyncStatus::clone(&self.status.load());
|
||||||
let indexed_block_id = self.store.get_last_block_id().ok().flatten();
|
let indexed_block_id = self.store.get_last_block_id().ok().flatten();
|
||||||
let chain_breaker = self.store.get_chain_breaker().ok().flatten();
|
let stall_reason = self.store.get_stall_reason().ok().flatten();
|
||||||
IndexerStatus {
|
IndexerStatus {
|
||||||
sync,
|
sync,
|
||||||
indexed_block_id,
|
indexed_block_id,
|
||||||
chain_breaker,
|
stall_reason,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -5,19 +5,20 @@ use crate::ingest_error::BlockIngestError;
|
|||||||
|
|
||||||
/// Diagnostic record of the first block that broke the L2 chain.
|
/// Diagnostic record of the first block that broke the L2 chain.
|
||||||
///
|
///
|
||||||
/// Later non-chaining blocks (orphans, since the tip is frozen) only bump `orphans_since`.
|
|
||||||
///
|
|
||||||
/// The block-derived fields are `None` for a deserialize break (no header was
|
/// The block-derived fields are `None` for a deserialize break (no header was
|
||||||
/// ever parsed). `l1_slot` is the zone-sdk cursor position captured as JSON.
|
/// ever parsed). `l1_slot` is the zone-sdk cursor position captured as JSON.
|
||||||
/// `first_seen` is the breaking block's L2 timestamp (`None` for a deserialize break).
|
/// `first_seen` is the breaking block's L2 timestamp (`None` for a deserialize break).
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct ChainBreaker {
|
pub struct StallReason {
|
||||||
pub block_id: Option<u64>,
|
pub block_id: Option<u64>,
|
||||||
pub block_hash: Option<HashType>,
|
pub block_hash: Option<HashType>,
|
||||||
pub prev_block_hash: Option<HashType>,
|
pub prev_block_hash: Option<HashType>,
|
||||||
pub l1_slot: serde_json::Value,
|
pub l1_slot: serde_json::Value,
|
||||||
pub error: BlockIngestError,
|
pub error: BlockIngestError,
|
||||||
pub first_seen: Option<u64>,
|
pub first_seen: Option<u64>,
|
||||||
|
/// Number of later non-chaining blocks (orphans, since the tip is frozen).
|
||||||
|
///
|
||||||
|
/// TODO: We could store a different "branch" of blocks following this sta
|
||||||
pub orphans_since: u64,
|
pub orphans_since: u64,
|
||||||
}
|
}
|
||||||
@ -1,6 +1,6 @@
|
|||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
use crate::chain_breaker::ChainBreaker;
|
use crate::stall_reason::StallReason;
|
||||||
|
|
||||||
/// Coarse lifecycle state of the indexer's ingestion loop, so a client can tell
|
/// Coarse lifecycle state of the indexer's ingestion loop, so a client can tell
|
||||||
/// "still catching up" apart from "something went wrong".
|
/// "still catching up" apart from "something went wrong".
|
||||||
@ -15,8 +15,8 @@ pub enum IndexerSyncState {
|
|||||||
CaughtUp,
|
CaughtUp,
|
||||||
/// The last cycle failed (e.g. the L1 node is unreachable). See `last_error`.
|
/// The last cycle failed (e.g. the L1 node is unreachable). See `last_error`.
|
||||||
Error,
|
Error,
|
||||||
/// Parked on a chain breaker: the validated tip is frozen awaiting a valid
|
/// Parked on a stall reason: the validated tip is frozen awaiting a valid
|
||||||
/// continuation. See `last_error` and the snapshot's `chain_breaker`.
|
/// continuation. See `last_error` and the snapshot's `stall_reason`.
|
||||||
Stalled,
|
Stalled,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -62,8 +62,8 @@ impl IndexerSyncStatus {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parked on a chain breaker; `reason` mirrors the breaker's error message.
|
/// Parked on a stall reason; `reason` mirrors the stall's error message.
|
||||||
/// The full breaker is attached to the [`IndexerStatus`] snapshot.
|
/// The full stall is attached to the [`IndexerStatus`] snapshot.
|
||||||
pub(crate) const fn stalled(reason: String) -> Self {
|
pub(crate) const fn stalled(reason: String) -> Self {
|
||||||
Self {
|
Self {
|
||||||
state: IndexerSyncState::Stalled,
|
state: IndexerSyncState::Stalled,
|
||||||
@ -83,7 +83,7 @@ pub struct IndexerStatus {
|
|||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
pub sync: IndexerSyncStatus,
|
pub sync: IndexerSyncStatus,
|
||||||
pub indexed_block_id: Option<u64>,
|
pub indexed_block_id: Option<u64>,
|
||||||
pub chain_breaker: Option<ChainBreaker>,
|
pub stall_reason: Option<StallReason>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@ -95,7 +95,7 @@ mod tests {
|
|||||||
let status = IndexerStatus {
|
let status = IndexerStatus {
|
||||||
sync: IndexerSyncStatus::error("boom".to_owned()),
|
sync: IndexerSyncStatus::error("boom".to_owned()),
|
||||||
indexed_block_id: Some(7),
|
indexed_block_id: Some(7),
|
||||||
chain_breaker: None,
|
stall_reason: None,
|
||||||
};
|
};
|
||||||
let value = serde_json::to_value(&status).expect("serialize");
|
let value = serde_json::to_value(&status).expect("serialize");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@ -104,7 +104,7 @@ mod tests {
|
|||||||
"state": "error",
|
"state": "error",
|
||||||
"lastError": "boom",
|
"lastError": "boom",
|
||||||
"indexedBlockId": 7,
|
"indexedBlockId": 7,
|
||||||
"chainBreaker": null,
|
"stallReason": null,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -119,13 +119,13 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn stalled_status_serializes_with_breaker() {
|
fn stalled_status_serializes_with_stall_reason() {
|
||||||
use crate::{chain_breaker::ChainBreaker, ingest_error::BlockIngestError};
|
use crate::{ingest_error::BlockIngestError, stall_reason::StallReason};
|
||||||
|
|
||||||
let status = IndexerStatus {
|
let status = IndexerStatus {
|
||||||
sync: IndexerSyncStatus::stalled("broken chain link".to_owned()),
|
sync: IndexerSyncStatus::stalled("broken chain link".to_owned()),
|
||||||
indexed_block_id: Some(41),
|
indexed_block_id: Some(41),
|
||||||
chain_breaker: Some(ChainBreaker {
|
stall_reason: Some(StallReason {
|
||||||
block_id: Some(42),
|
block_id: Some(42),
|
||||||
block_hash: None,
|
block_hash: None,
|
||||||
prev_block_hash: None,
|
prev_block_hash: None,
|
||||||
@ -139,6 +139,6 @@ mod tests {
|
|||||||
assert_eq!(value["state"], serde_json::json!("stalled"));
|
assert_eq!(value["state"], serde_json::json!("stalled"));
|
||||||
assert_eq!(value["lastError"], serde_json::json!("broken chain link"));
|
assert_eq!(value["lastError"], serde_json::json!("broken chain link"));
|
||||||
assert_eq!(value["indexedBlockId"], serde_json::json!(41));
|
assert_eq!(value["indexedBlockId"], serde_json::json!(41));
|
||||||
assert_eq!(value["chainBreaker"]["orphansSince"], serde_json::json!(2));
|
assert_eq!(value["stallReason"]["orphansSince"], serde_json::json!(2));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,8 +7,8 @@ use crate::{
|
|||||||
error::DbError,
|
error::DbError,
|
||||||
indexer::{
|
indexer::{
|
||||||
ACC_NUM_CELL_NAME, BLOCK_HASH_CELL_NAME, BREAKPOINT_CELL_NAME, CF_ACC_META,
|
ACC_NUM_CELL_NAME, BLOCK_HASH_CELL_NAME, BREAKPOINT_CELL_NAME, CF_ACC_META,
|
||||||
CF_BREAKPOINT_NAME, CF_HASH_TO_ID, CF_TX_TO_ID, DB_META_CHAIN_BREAKER_KEY,
|
CF_BREAKPOINT_NAME, CF_HASH_TO_ID, CF_TX_TO_ID, DB_META_LAST_BREAKPOINT_ID,
|
||||||
DB_META_LAST_BREAKPOINT_ID, DB_META_LAST_OBSERVED_L1_LIB_HEADER_ID_IN_DB_KEY,
|
DB_META_LAST_OBSERVED_L1_LIB_HEADER_ID_IN_DB_KEY, DB_META_STALL_REASON_KEY,
|
||||||
DB_META_ZONE_SDK_INDEXER_CURSOR_KEY, TX_HASH_CELL_NAME,
|
DB_META_ZONE_SDK_INDEXER_CURSOR_KEY, TX_HASH_CELL_NAME,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@ -247,36 +247,36 @@ impl SimpleWritableCell for ZoneSdkIndexerCursorCellRef<'_> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Opaque JSON bytes for the indexer's persisted `Option<ChainBreaker>`.
|
/// Opaque JSON bytes for the indexer's persisted `Option<StallReason>`.
|
||||||
/// Serialized via `serde_json` by the caller (mirrors the zone-sdk cursor cell).
|
/// Serialized via `serde_json` by the caller (mirrors the zone-sdk cursor cell).
|
||||||
#[derive(BorshDeserialize)]
|
#[derive(BorshDeserialize)]
|
||||||
pub struct ChainBreakerCellOwned(pub Vec<u8>);
|
pub struct StallReasonCellOwned(pub Vec<u8>);
|
||||||
|
|
||||||
impl SimpleStorableCell for ChainBreakerCellOwned {
|
impl SimpleStorableCell for StallReasonCellOwned {
|
||||||
type KeyParams = ();
|
type KeyParams = ();
|
||||||
|
|
||||||
const CELL_NAME: &'static str = DB_META_CHAIN_BREAKER_KEY;
|
const CELL_NAME: &'static str = DB_META_STALL_REASON_KEY;
|
||||||
const CF_NAME: &'static str = CF_META_NAME;
|
const CF_NAME: &'static str = CF_META_NAME;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SimpleReadableCell for ChainBreakerCellOwned {}
|
impl SimpleReadableCell for StallReasonCellOwned {}
|
||||||
|
|
||||||
#[derive(BorshSerialize)]
|
#[derive(BorshSerialize)]
|
||||||
pub struct ChainBreakerCellRef<'bytes>(pub &'bytes [u8]);
|
pub struct StallReasonCellRef<'bytes>(pub &'bytes [u8]);
|
||||||
|
|
||||||
impl SimpleStorableCell for ChainBreakerCellRef<'_> {
|
impl SimpleStorableCell for StallReasonCellRef<'_> {
|
||||||
type KeyParams = ();
|
type KeyParams = ();
|
||||||
|
|
||||||
const CELL_NAME: &'static str = DB_META_CHAIN_BREAKER_KEY;
|
const CELL_NAME: &'static str = DB_META_STALL_REASON_KEY;
|
||||||
const CF_NAME: &'static str = CF_META_NAME;
|
const CF_NAME: &'static str = CF_META_NAME;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SimpleWritableCell for ChainBreakerCellRef<'_> {
|
impl SimpleWritableCell for StallReasonCellRef<'_> {
|
||||||
fn value_constructor(&self) -> DbResult<Vec<u8>> {
|
fn value_constructor(&self) -> DbResult<Vec<u8>> {
|
||||||
borsh::to_vec(&self).map_err(|err| {
|
borsh::to_vec(&self).map_err(|err| {
|
||||||
DbError::borsh_cast_message(
|
DbError::borsh_cast_message(
|
||||||
err,
|
err,
|
||||||
Some("Failed to serialize chain breaker cell".to_owned()),
|
Some("Failed to serialize stall reason cell".to_owned()),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -24,8 +24,8 @@ pub const DB_META_LAST_OBSERVED_L1_LIB_HEADER_ID_IN_DB_KEY: &str =
|
|||||||
pub const DB_META_LAST_BREAKPOINT_ID: &str = "last_breakpoint_id";
|
pub const DB_META_LAST_BREAKPOINT_ID: &str = "last_breakpoint_id";
|
||||||
/// Key base for storing the zone-sdk indexer cursor (opaque bytes).
|
/// Key base for storing the zone-sdk indexer cursor (opaque bytes).
|
||||||
pub const DB_META_ZONE_SDK_INDEXER_CURSOR_KEY: &str = "zone_sdk_indexer_cursor";
|
pub const DB_META_ZONE_SDK_INDEXER_CURSOR_KEY: &str = "zone_sdk_indexer_cursor";
|
||||||
/// Key base for storing the persisted `Option<ChainBreaker>` diagnostic record (opaque JSON bytes).
|
/// Key base for storing the persisted `Option<StallReason>` diagnostic record (opaque JSON bytes).
|
||||||
pub const DB_META_CHAIN_BREAKER_KEY: &str = "chain_breaker";
|
pub const DB_META_STALL_REASON_KEY: &str = "stall_reason";
|
||||||
|
|
||||||
/// Cell name for a breakpoint.
|
/// Cell name for a breakpoint.
|
||||||
pub const BREAKPOINT_CELL_NAME: &str = "breakpoint";
|
pub const BREAKPOINT_CELL_NAME: &str = "breakpoint";
|
||||||
|
|||||||
@ -3,8 +3,8 @@ use crate::{
|
|||||||
DBIO as _,
|
DBIO as _,
|
||||||
cells::shared_cells::{BlockCell, FirstBlockCell, FirstBlockSetCell, LastBlockCell},
|
cells::shared_cells::{BlockCell, FirstBlockCell, FirstBlockSetCell, LastBlockCell},
|
||||||
indexer::indexer_cells::{
|
indexer::indexer_cells::{
|
||||||
AccNumTxCell, BlockHashToBlockIdMapCell, BreakpointCellOwned, ChainBreakerCellOwned,
|
AccNumTxCell, BlockHashToBlockIdMapCell, BreakpointCellOwned, LastBreakpointIdCell,
|
||||||
LastBreakpointIdCell, LastObservedL1LibHeaderCell, TxHashToBlockIdMapCell,
|
LastObservedL1LibHeaderCell, StallReasonCellOwned, TxHashToBlockIdMapCell,
|
||||||
ZoneSdkIndexerCursorCellOwned,
|
ZoneSdkIndexerCursorCellOwned,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@ -75,9 +75,7 @@ impl RocksDBIO {
|
|||||||
.map(|cell| cell.0))
|
.map(|cell| cell.0))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_chain_breaker_bytes(&self) -> DbResult<Option<Vec<u8>>> {
|
pub fn get_stall_reason_bytes(&self) -> DbResult<Option<Vec<u8>>> {
|
||||||
Ok(self
|
Ok(self.get_opt::<StallReasonCellOwned>(())?.map(|cell| cell.0))
|
||||||
.get_opt::<ChainBreakerCellOwned>(())?
|
|
||||||
.map(|cell| cell.0))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,7 +3,7 @@ use crate::{
|
|||||||
DBIO as _,
|
DBIO as _,
|
||||||
cells::shared_cells::{FirstBlockSetCell, LastBlockCell},
|
cells::shared_cells::{FirstBlockSetCell, LastBlockCell},
|
||||||
indexer::indexer_cells::{
|
indexer::indexer_cells::{
|
||||||
BreakpointCellRef, ChainBreakerCellRef, LastBreakpointIdCell, LastObservedL1LibHeaderCell,
|
BreakpointCellRef, LastBreakpointIdCell, LastObservedL1LibHeaderCell, StallReasonCellRef,
|
||||||
ZoneSdkIndexerCursorCellRef,
|
ZoneSdkIndexerCursorCellRef,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@ -35,8 +35,8 @@ impl RocksDBIO {
|
|||||||
self.put(&ZoneSdkIndexerCursorCellRef(bytes), ())
|
self.put(&ZoneSdkIndexerCursorCellRef(bytes), ())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn put_chain_breaker_bytes(&self, bytes: &[u8]) -> DbResult<()> {
|
pub fn put_stall_reason_bytes(&self, bytes: &[u8]) -> DbResult<()> {
|
||||||
self.put(&ChainBreakerCellRef(bytes), ())
|
self.put(&StallReasonCellRef(bytes), ())
|
||||||
}
|
}
|
||||||
|
|
||||||
// State
|
// State
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user