From 774af235210ac5dbb7964e41c184dc0e191e852c Mon Sep 17 00:00:00 2001 From: erhant Date: Fri, 26 Jun 2026 17:44:00 +0300 Subject: [PATCH] feat(indexer): expose status over RPC and add integration coverage test `RUST_LOG=info RISC0_DEV_MODE=1 cargo test -p integration_tests --test bridge --indexer_status_rpc_reports_caught_up_with_no_stall --exact --nocapture --include-ignored` --- integration_tests/tests/bridge.rs | 32 +++++++++++++++++++++++++ lez/indexer/service/rpc/src/lib.rs | 3 +++ lez/indexer/service/src/mock_service.rs | 18 ++++++++++++++ lez/indexer/service/src/service.rs | 5 ++++ 4 files changed, 58 insertions(+) diff --git a/integration_tests/tests/bridge.rs b/integration_tests/tests/bridge.rs index 9c2fa8c5..a24817c0 100644 --- a/integration_tests/tests/bridge.rs +++ b/integration_tests/tests/bridge.rs @@ -536,6 +536,38 @@ fn create_zone_indexer_observer( )) } +/// Test that the indexer status RPC reports caught-up with no stall after a clean run. +/// +/// TODO: Integration-level park testing (publishing a bad block to force a stall) is a follow-up +/// needing fault injection support in the test harness. +#[ignore = "requires the full local stack (bedrock + sequencer + indexer)"] +#[test] +async fn indexer_status_rpc_reports_caught_up_with_no_stall() -> anyhow::Result<()> { + use indexer_service_rpc::RpcClient as _; + + let ctx = TestContext::new().await?; + + let indexer_tip = wait_for_indexer_to_catch_up(&ctx).await?; + + let status = ctx.indexer_client().get_status().await?; + assert_eq!( + status["state"], + serde_json::json!("caught_up"), + "indexer should be caught up, got {status}" + ); + assert!( + status["stallReason"].is_null(), + "indexer should have no stall reason after a clean run, got {status}" + ); + assert_eq!( + status["indexedBlockId"], + serde_json::json!(indexer_tip), + "status indexedBlockId should equal the caught-up tip" + ); + + Ok(()) +} + async fn wait_for_finalized_withdraw_op( observer: &ZoneIndexer, expected_amount: u64, diff --git a/lez/indexer/service/rpc/src/lib.rs b/lez/indexer/service/rpc/src/lib.rs index 5763fe82..108ee6c2 100644 --- a/lez/indexer/service/rpc/src/lib.rs +++ b/lez/indexer/service/rpc/src/lib.rs @@ -69,6 +69,9 @@ pub trait Rpc { limit: u64, ) -> Result, ErrorObjectOwned>; + #[method(name = "getStatus")] + async fn get_status(&self) -> Result; + // ToDo: expand healthcheck response into some kind of report #[method(name = "checkHealth")] async fn healthcheck(&self) -> Result<(), ErrorObjectOwned>; diff --git a/lez/indexer/service/src/mock_service.rs b/lez/indexer/service/src/mock_service.rs index d9ab9484..a8e62c5c 100644 --- a/lez/indexer/service/src/mock_service.rs +++ b/lez/indexer/service/src/mock_service.rs @@ -325,6 +325,24 @@ impl indexer_service_rpc::RpcServer for MockIndexerService { .collect()) } + async fn get_status(&self) -> Result { + let indexed_block_id = self + .state + .read() + .await + .blocks + .iter() + .rev() + .find(|block| block.bedrock_status == BedrockStatus::Finalized) + .map(|block| block.header.block_id); + Ok(serde_json::json!({ + "state": "caught_up", + "lastError": null, + "indexedBlockId": indexed_block_id, + "stallReason": null, + })) + } + async fn healthcheck(&self) -> Result<(), ErrorObjectOwned> { Ok(()) } diff --git a/lez/indexer/service/src/service.rs b/lez/indexer/service/src/service.rs index 7a8ed90f..7abab18f 100644 --- a/lez/indexer/service/src/service.rs +++ b/lez/indexer/service/src/service.rs @@ -149,6 +149,11 @@ impl indexer_service_rpc::RpcServer for IndexerService { Ok(tx_res) } + async fn get_status(&self) -> Result { + Ok(serde_json::to_value(self.indexer.status()) + .expect("IndexerStatus serialization should not fail")) + } + async fn healthcheck(&self) -> Result<(), ErrorObjectOwned> { // Checking, that indexer can calculate last state let _ = self