diff --git a/Cargo.lock b/Cargo.lock index 914a4dd1..94880644 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2733,6 +2733,7 @@ dependencies = [ "env_logger", "indexer_service_protocol", "indexer_service_rpc", + "itertools 0.14.0", "jsonrpsee", "leptos", "leptos_axum", diff --git a/explorer_service/Cargo.toml b/explorer_service/Cargo.toml index 219f2bc0..1dc989d0 100644 --- a/explorer_service/Cargo.toml +++ b/explorer_service/Cargo.toml @@ -50,6 +50,9 @@ clap = { workspace = true, features = ["derive"], optional = true } url = { workspace = true, optional = true } env_logger = { workspace = true, optional = true } +# Mandatory server side dependencies +itertools.workspace = true + [features] hydrate = ["leptos/hydrate"] ssr = [ diff --git a/explorer_service/src/api.rs b/explorer_service/src/api.rs index d0785949..4dfd75cd 100644 --- a/explorer_service/src/api.rs +++ b/explorer_service/src/api.rs @@ -83,6 +83,17 @@ pub async fn get_block_by_id(block_id: BlockId) -> Result .map_err(|e| ServerFnError::ServerError(format!("RPC error: {}", e))) } +/// Get latest block ID +#[server] +pub async fn get_latest_block_id() -> Result { + use indexer_service_rpc::RpcClient as _; + let client = expect_context::(); + client + .get_last_finalized_block_id() + .await + .map_err(|e| ServerFnError::ServerError(format!("RPC error: {}", e))) +} + /// Get block by hash #[server] pub async fn get_block_by_hash(block_hash: HashType) -> Result { diff --git a/explorer_service/src/lib.rs b/explorer_service/src/lib.rs index 489636fd..e2b2291e 100644 --- a/explorer_service/src/lib.rs +++ b/explorer_service/src/lib.rs @@ -28,7 +28,7 @@ pub fn App() -> impl IntoView { view! { - + <Title text="LEZ Block Explorer" /> <Meta name="description" content="Explore the blockchain - view blocks, transactions, and accounts" /> <Router> @@ -36,7 +36,7 @@ pub fn App() -> impl IntoView { <header class="app-header"> <nav class="app-nav"> <a href="/" class="nav-logo"> - "LEE Blockchain Explorer" + "LEZ Block Explorer" </a> </nav> </header> @@ -69,7 +69,7 @@ pub fn App() -> impl IntoView { </main> <footer class="app-footer"> - <p>"LEE Blockchain Explorer © 2026"</p> + <p>"LEZ Block Explorer © 2026"</p> </footer> </div> </Router> diff --git a/explorer_service/src/main.rs b/explorer_service/src/main.rs index 63d54d70..6cc4a9a4 100644 --- a/explorer_service/src/main.rs +++ b/explorer_service/src/main.rs @@ -10,7 +10,7 @@ async fn main() { env_logger::init(); - /// LEE Blockchain Explorer Server CLI arguments. + /// LEZ Block Explorer Server CLI arguments. #[derive(Parser, Debug)] #[command(version, about, long_about = None)] struct Args { diff --git a/explorer_service/src/pages/main_page.rs b/explorer_service/src/pages/main_page.rs index ffd625c8..3cfb832d 100644 --- a/explorer_service/src/pages/main_page.rs +++ b/explorer_service/src/pages/main_page.rs @@ -7,6 +7,8 @@ use crate::{ components::{AccountPreview, BlockPreview, TransactionPreview}, }; +const RECENT_BLOCKS_LIMIT: u64 = 10; + /// Main page component #[component] pub fn MainPage() -> impl IntoView { @@ -38,7 +40,21 @@ pub fn MainPage() -> impl IntoView { }); // Load recent blocks on mount - let recent_blocks_resource = Resource::new(|| (), |_| async { api::get_blocks(0, 10).await }); + let recent_blocks_resource = Resource::new( + || (), + |_| async { + match api::get_latest_block_id().await { + Ok(last_id) => { + api::get_blocks( + std::cmp::max(last_id.saturating_sub(RECENT_BLOCKS_LIMIT) as u32, 1), + (RECENT_BLOCKS_LIMIT + 1) as u32, + ) + .await + } + Err(err) => Err(err), + } + }, + ); // Handle search - update URL parameter let on_search = move |ev: SubmitEvent| { @@ -58,7 +74,7 @@ pub fn MainPage() -> impl IntoView { view! { <div class="main-page"> <div class="page-header"> - <h1>"LEE Blockchain Explorer"</h1> + <h1>"LEZ Block Explorer"</h1> </div> <div class="search-section"> diff --git a/explorer_service/src/pages/transaction_page.rs b/explorer_service/src/pages/transaction_page.rs index d0f1b4da..2859719f 100644 --- a/explorer_service/src/pages/transaction_page.rs +++ b/explorer_service/src/pages/transaction_page.rs @@ -4,6 +4,7 @@ use indexer_service_protocol::{ HashType, PrivacyPreservingMessage, PrivacyPreservingTransaction, ProgramDeploymentMessage, ProgramDeploymentTransaction, PublicMessage, PublicTransaction, Transaction, WitnessSet, }; +use itertools::{EitherOrBoth, Itertools}; use leptos::prelude::*; use leptos_router::{components::A, hooks::use_params_map}; @@ -65,7 +66,8 @@ pub fn TransactionPage() -> impl IntoView { </div> </div> - {match tx { + { + match tx { Transaction::Public(ptx) => { let PublicTransaction { hash: _, @@ -115,9 +117,11 @@ pub fn TransactionPage() -> impl IntoView { <div class="accounts-list"> {account_ids .into_iter() - .zip(nonces.into_iter()) - .map(|(account_id, nonce)| { - let account_id_str = account_id.to_string(); + .zip_longest(nonces.into_iter()) + .map(|maybe_pair| { + match maybe_pair { + EitherOrBoth::Both(account_id, nonce) => { + let account_id_str = account_id.to_string(); view! { <div class="account-item"> <A href=format!("/account/{}", account_id_str)> @@ -128,6 +132,33 @@ pub fn TransactionPage() -> impl IntoView { </span> </div> } + } + EitherOrBoth::Left(account_id) => { + let account_id_str = account_id.to_string(); + view! { + <div class="account-item"> + <A href=format!("/account/{}", account_id_str)> + <span class="hash">{account_id_str}</span> + </A> + <span class="nonce"> + " (nonce: "{"Not affected by this transaction".to_string()}" )" + </span> + </div> + } + } + EitherOrBoth::Right(_) => { + view! { + <div class="account-item"> + <A href=format!("/account/{}", "Account not found")> + <span class="hash">{"Account not found"}</span> + </A> + <span class="nonce"> + " (nonce: "{"Account not found".to_string()}" )" + </span> + </div> + } + } + } }) .collect::<Vec<_>>()} </div> @@ -189,9 +220,11 @@ pub fn TransactionPage() -> impl IntoView { <div class="accounts-list"> {public_account_ids .into_iter() - .zip(nonces.into_iter()) - .map(|(account_id, nonce)| { - let account_id_str = account_id.to_string(); + .zip_longest(nonces.into_iter()) + .map(|maybe_pair| { + match maybe_pair { + EitherOrBoth::Both(account_id, nonce) => { + let account_id_str = account_id.to_string(); view! { <div class="account-item"> <A href=format!("/account/{}", account_id_str)> @@ -202,6 +235,33 @@ pub fn TransactionPage() -> impl IntoView { </span> </div> } + } + EitherOrBoth::Left(account_id) => { + let account_id_str = account_id.to_string(); + view! { + <div class="account-item"> + <A href=format!("/account/{}", account_id_str)> + <span class="hash">{account_id_str}</span> + </A> + <span class="nonce"> + " (nonce: "{"Not affected by this transaction".to_string()}" )" + </span> + </div> + } + } + EitherOrBoth::Right(_) => { + view! { + <div class="account-item"> + <A href=format!("/account/{}", "Account not found")> + <span class="hash">{"Account not found"}</span> + </A> + <span class="nonce"> + " (nonce: "{"Account not found".to_string()}" )" + </span> + </div> + } + } + } }) .collect::<Vec<_>>()} </div> diff --git a/indexer/core/src/block_store.rs b/indexer/core/src/block_store.rs index fc7641a1..ce3881bd 100644 --- a/indexer/core/src/block_store.rs +++ b/indexer/core/src/block_store.rs @@ -2,7 +2,10 @@ use std::{path::Path, sync::Arc}; use anyhow::Result; use bedrock_client::HeaderId; -use common::{block::Block, transaction::NSSATransaction}; +use common::{ + block::{BedrockStatus, Block}, + transaction::NSSATransaction, +}; use nssa::{Account, AccountId, V02State}; use storage::indexer::RocksDBIO; @@ -100,7 +103,7 @@ impl IndexerStore { Ok(self.final_state()?.get_account_by_id(*account_id)) } - pub fn put_block(&self, block: Block, l1_header: HeaderId) -> Result<()> { + pub fn put_block(&self, mut block: Block, l1_header: HeaderId) -> Result<()> { let mut final_state = self.dbio.final_state()?; for transaction in &block.body.transactions { @@ -110,6 +113,11 @@ impl IndexerStore { .execute_check_on_state(&mut final_state)?; } + // ToDo: Currently we are fetching only finalized blocks + // if it changes, the following lines need to be updated + // to represent correct block finality + block.bedrock_status = BedrockStatus::Finalized; + Ok(self.dbio.put_block(block, l1_header.into())?) } } diff --git a/indexer/core/src/lib.rs b/indexer/core/src/lib.rs index b24cb1bd..6d56eb18 100644 --- a/indexer/core/src/lib.rs +++ b/indexer/core/src/lib.rs @@ -15,7 +15,6 @@ use crate::{block_store::IndexerStore, config::IndexerConfig}; pub mod block_store; pub mod config; -pub mod state; #[derive(Clone)] pub struct IndexerCore { @@ -123,6 +122,10 @@ impl IndexerCore { l2_blocks: l2_block_vec, l1_header, } in start_buff { + let mut l2_blocks_parsed_ids: Vec<_> = l2_block_vec.iter().map(|block| block.header.block_id).collect(); + l2_blocks_parsed_ids.sort(); + info!("Parsed {} L2 blocks with ids {:?}", l2_block_vec.len(), l2_blocks_parsed_ids); + for l2_block in l2_block_vec { self.store.put_block(l2_block.clone(), l1_header)?; @@ -153,6 +156,10 @@ impl IndexerCore { l2_blocks: l2_block_vec, l1_header: header, } in buff { + let mut l2_blocks_parsed_ids: Vec<_> = l2_block_vec.iter().map(|block| block.header.block_id).collect(); + l2_blocks_parsed_ids.sort(); + info!("Parsed {} L2 blocks with ids {:?}", l2_block_vec.len(), l2_blocks_parsed_ids); + for l2_block in l2_block_vec { self.store.put_block(l2_block.clone(), header)?; @@ -260,7 +267,7 @@ impl IndexerCore { Ok(BackfillData { block_data: block_buffer, - curr_fin_l1_lib_header: backfill_limit, + curr_fin_l1_lib_header: curr_last_l1_lib_header, }) } diff --git a/indexer/core/src/state.rs b/indexer/core/src/state.rs deleted file mode 100644 index bd05971f..00000000 --- a/indexer/core/src/state.rs +++ /dev/null @@ -1,9 +0,0 @@ -use std::sync::Arc; - -use tokio::sync::RwLock; - -#[derive(Debug, Clone)] -pub struct IndexerState { - // Only one field for now, for testing. - pub latest_seen_block: Arc<RwLock<u64>>, -} diff --git a/indexer/service/configs/indexer_config.json b/indexer/service/configs/indexer_config.json index 7d7a317c..247caa8e 100644 --- a/indexer/service/configs/indexer_config.json +++ b/indexer/service/configs/indexer_config.json @@ -1,6 +1,6 @@ { "home": "./indexer/service", - "consensus_info_polling_interval": "1s", + "consensus_info_polling_interval": "60s", "bedrock_client_config": { "addr": "http://localhost:8080", "backoff": { diff --git a/indexer/service/rpc/Cargo.toml b/indexer/service/rpc/Cargo.toml index 2bed63ae..b2194882 100644 --- a/indexer/service/rpc/Cargo.toml +++ b/indexer/service/rpc/Cargo.toml @@ -5,7 +5,7 @@ edition = "2024" license = { workspace = true } [dependencies] -indexer_service_protocol = { workspace = true } +indexer_service_protocol = { workspace = true, features = ["convert"] } jsonrpsee = { workspace = true, features = ["macros"] } serde_json.workspace = true diff --git a/sequencer_core/src/block_settlement_client.rs b/sequencer_core/src/block_settlement_client.rs index 075ebd11..15612835 100644 --- a/sequencer_core/src/block_settlement_client.rs +++ b/sequencer_core/src/block_settlement_client.rs @@ -28,7 +28,7 @@ pub trait BlockSettlementClientTrait: Clone { /// Create and sign a transaction for inscribing data. fn create_inscribe_tx(&self, block: &Block) -> Result<(SignedMantleTx, MsgId)> { let inscription_data = borsh::to_vec(block)?; - log::info!( + log::debug!( "The size of the block {} is {} bytes", block.header.block_id, inscription_data.len() @@ -104,7 +104,7 @@ impl BlockSettlementClientTrait for BlockSettlementClient { .await .context("Failed to post transaction to Bedrock")?; - log::info!("Posted block to Bedrock with parent id {parent_id:?} and msg id: {msg_id:?}"); + log::debug!("Posted block to Bedrock with parent id {parent_id:?} and msg id: {msg_id:?}"); Ok(()) } diff --git a/sequencer_runner/src/lib.rs b/sequencer_runner/src/lib.rs index 7a02bfc3..d74792c8 100644 --- a/sequencer_runner/src/lib.rs +++ b/sequencer_runner/src/lib.rs @@ -182,8 +182,16 @@ async fn retry_pending_blocks(seq_core: &Arc<Mutex<SequencerCore>>) -> Result<() (pending_blocks, client) }; - for block in pending_blocks.iter() { + if !pending_blocks.is_empty() { info!( + "Resubmitting blocks from {} to {}", + pending_blocks.first().unwrap().header.block_id, + pending_blocks.last().unwrap().header.block_id + ); + } + + for block in pending_blocks.iter() { + debug!( "Resubmitting pending block with id {}", block.header.block_id );