moonbeam_service/lazy_loading/
frontier_backend.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
// Copyright 2025 Moonbeam foundation
// This file is part of Moonbeam.

// Moonbeam is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// Moonbeam is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with Moonbeam.  If not, see <http://www.gnu.org/licenses/>.

//! Lazy loading implementation for Frontier backend.
//!
//! This module provides a wrapper around the standard Frontier backend that enables
//! lazy loading functionality. It intercepts Ethereum-specific queries and routes them
//! through the RPC client to fetch data on-demand from a remote node.

use fc_api::{LogIndexerBackend, TransactionMetadata};
use serde::de::DeserializeOwned;
use sp_core::H256;
use sp_runtime::traits::{Block as BlockT, Header};
use std::sync::Arc;

/// A wrapper around the Frontier backend that supports lazy loading.
///
/// This backend intercepts Ethereum-specific queries (block hash lookups, transaction metadata)
/// and fetches the data from a remote RPC node when needed, while delegating other operations
/// to the underlying Frontier backend.
#[derive(Clone)]
pub struct LazyLoadingFrontierBackend<Block: BlockT> {
	pub(crate) rpc_client: Arc<super::rpc_client::RPC>,
	pub(crate) frontier_backend: Arc<dyn fc_api::Backend<Block> + Send + Sync>,
}

impl<Block: BlockT + DeserializeOwned> LazyLoadingFrontierBackend<Block>
where
	<Block::Header as Header>::Number: From<u32>,
{
	fn get_substrate_block_hash(
		&self,
		block_number: <Block::Header as Header>::Number,
	) -> Result<Option<Block::Hash>, String> {
		self.rpc_client
			.block_hash::<Block>(Some(block_number))
			.map_err(|e| format!("failed to get substrate block hash: {:?}", e))
	}
}

#[async_trait::async_trait]
impl<Block: BlockT + DeserializeOwned> fc_api::Backend<Block> for LazyLoadingFrontierBackend<Block>
where
	<Block::Header as Header>::Number: From<u32>,
{
	async fn block_hash(&self, eth_block_hash: &H256) -> Result<Option<Vec<Block::Hash>>, String> {
		let block = self
			.rpc_client
			.block_by_hash(eth_block_hash, false)
			.map_err(|e| format!("failed to get block by hash: {:?}", e))?;

		match block {
			Some(block) => {
				let block_number = block.header.number.as_u32().into();
				let substrate_block_hash = self.get_substrate_block_hash(block_number)?;
				Ok(substrate_block_hash.map(|h| vec![h]))
			}
			None => Ok(None),
		}
	}

	async fn transaction_metadata(
		&self,
		eth_transaction_hash: &H256,
	) -> Result<Vec<TransactionMetadata<Block>>, String> {
		let transaction = self
			.rpc_client
			.transaction_by_hash(eth_transaction_hash)
			.map_err(|e| format!("failed to get transaction by hash: {:?}", e))?;

		match transaction {
			Some(tx) => {
				let block_number = tx.block_number.unwrap_or_default().as_u32().into();
				let substrate_block_hash = self.get_substrate_block_hash(block_number)?;

				Ok(vec![TransactionMetadata::<Block> {
					ethereum_index: tx.transaction_index.unwrap_or_default().as_u32(),
					ethereum_block_hash: tx.block_hash.unwrap_or_default(),
					substrate_block_hash: substrate_block_hash.unwrap_or_default(),
				}])
			}
			None => Ok(vec![]),
		}
	}

	fn log_indexer(&self) -> &dyn LogIndexerBackend<Block> {
		self.frontier_backend.log_indexer()
	}

	async fn first_block_hash(&self) -> Result<Block::Hash, String> {
		self.frontier_backend.first_block_hash().await
	}

	async fn latest_block_hash(&self) -> Result<Block::Hash, String> {
		self.frontier_backend.latest_block_hash().await
	}
}