moonbeam_service/lazy_loading/
frontier_backend.rs

1// Copyright 2025 Moonbeam foundation
2// This file is part of Moonbeam.
3
4// Moonbeam is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8
9// Moonbeam is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13
14// You should have received a copy of the GNU General Public License
15// along with Moonbeam.  If not, see <http://www.gnu.org/licenses/>.
16
17//! Lazy loading implementation for Frontier backend.
18//!
19//! This module provides a wrapper around the standard Frontier backend that enables
20//! lazy loading functionality. It intercepts Ethereum-specific queries and routes them
21//! through the RPC client to fetch data on-demand from a remote node.
22
23use fc_api::{LogIndexerBackend, TransactionMetadata};
24use serde::de::DeserializeOwned;
25use sp_core::H256;
26use sp_runtime::traits::{Block as BlockT, Header};
27use std::sync::Arc;
28
29/// A wrapper around the Frontier backend that supports lazy loading.
30///
31/// This backend intercepts Ethereum-specific queries (block hash lookups, transaction metadata)
32/// and fetches the data from a remote RPC node when needed, while delegating other operations
33/// to the underlying Frontier backend.
34#[derive(Clone)]
35pub struct LazyLoadingFrontierBackend<Block: BlockT> {
36	pub(crate) rpc_client: Arc<super::rpc_client::RPC>,
37	pub(crate) frontier_backend: Arc<dyn fc_api::Backend<Block> + Send + Sync>,
38}
39
40impl<Block: BlockT + DeserializeOwned> LazyLoadingFrontierBackend<Block>
41where
42	<Block::Header as Header>::Number: From<u32>,
43{
44	fn get_substrate_block_hash(
45		&self,
46		block_number: <Block::Header as Header>::Number,
47	) -> Result<Option<Block::Hash>, String> {
48		self.rpc_client
49			.block_hash::<Block>(Some(block_number))
50			.map_err(|e| format!("failed to get substrate block hash: {:?}", e))
51	}
52}
53
54#[async_trait::async_trait]
55impl<Block: BlockT + DeserializeOwned> fc_api::Backend<Block> for LazyLoadingFrontierBackend<Block>
56where
57	<Block::Header as Header>::Number: From<u32>,
58{
59	async fn block_hash(&self, eth_block_hash: &H256) -> Result<Option<Vec<Block::Hash>>, String> {
60		let block = self
61			.rpc_client
62			.block_by_hash(eth_block_hash, false)
63			.map_err(|e| format!("failed to get block by hash: {:?}", e))?;
64
65		match block {
66			Some(block) => {
67				let block_number = block.header.number.as_u32().into();
68				let substrate_block_hash = self.get_substrate_block_hash(block_number)?;
69				Ok(substrate_block_hash.map(|h| vec![h]))
70			}
71			None => Ok(None),
72		}
73	}
74
75	async fn transaction_metadata(
76		&self,
77		eth_transaction_hash: &H256,
78	) -> Result<Vec<TransactionMetadata<Block>>, String> {
79		let transaction = self
80			.rpc_client
81			.transaction_by_hash(eth_transaction_hash)
82			.map_err(|e| format!("failed to get transaction by hash: {:?}", e))?;
83
84		match transaction {
85			Some(tx) => {
86				let block_number = tx.block_number.unwrap_or_default().as_u32().into();
87				let substrate_block_hash = self.get_substrate_block_hash(block_number)?;
88
89				Ok(vec![TransactionMetadata::<Block> {
90					ethereum_index: tx.transaction_index.unwrap_or_default().as_u32(),
91					ethereum_block_hash: tx.block_hash.unwrap_or_default(),
92					substrate_block_hash: substrate_block_hash.unwrap_or_default(),
93				}])
94			}
95			None => Ok(vec![]),
96		}
97	}
98
99	fn log_indexer(&self) -> &dyn LogIndexerBackend<Block> {
100		self.frontier_backend.log_indexer()
101	}
102
103	async fn first_block_hash(&self) -> Result<Block::Hash, String> {
104		self.frontier_backend.first_block_hash().await
105	}
106
107	async fn latest_block_hash(&self) -> Result<Block::Hash, String> {
108		self.frontier_backend.latest_block_hash().await
109	}
110}