Merge pull request #354 from logos-blockchain/Pravdyvy/indexer-explorer-integration-fixes

Indexer<->Explorer integration fixes
This commit is contained in:
Pravdyvy 2026-02-26 21:39:15 +02:00 committed by GitHub
commit 18d9d80105
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 136 additions and 31 deletions

1
Cargo.lock generated
View File

@ -2733,6 +2733,7 @@ dependencies = [
"env_logger",
"indexer_service_protocol",
"indexer_service_rpc",
"itertools 0.14.0",
"jsonrpsee",
"leptos",
"leptos_axum",

View File

@ -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 = [

View File

@ -83,6 +83,17 @@ pub async fn get_block_by_id(block_id: BlockId) -> Result<Block, ServerFnError>
.map_err(|e| ServerFnError::ServerError(format!("RPC error: {}", e)))
}
/// Get latest block ID
#[server]
pub async fn get_latest_block_id() -> Result<BlockId, ServerFnError> {
use indexer_service_rpc::RpcClient as _;
let client = expect_context::<IndexerRpcClient>();
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<Block, ServerFnError> {

View File

@ -28,7 +28,7 @@ pub fn App() -> impl IntoView {
view! {
<Stylesheet id="leptos" href="/explorer.css" />
<Title text="LEE Blockchain Explorer" />
<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>

View File

@ -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 {

View File

@ -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">

View File

@ -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>

View File

@ -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())?)
}
}

View File

@ -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,
})
}

View File

@ -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>>,
}

View File

@ -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": {

View File

@ -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

View File

@ -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(())
}

View File

@ -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
);