moonbeam_finality_rpc/
lib.rs

1// Copyright 2019-2025 PureStake Inc.
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/>.
16use fc_rpc::frontier_backend_client::{self, is_canon};
17
18use jsonrpsee::types::error::ErrorObject;
19use jsonrpsee::{core::RpcResult, proc_macros::rpc};
20use sp_blockchain::HeaderBackend;
21use sp_core::H256;
22use sp_runtime::traits::Block;
23use std::ops::Deref;
24use std::{marker::PhantomData, sync::Arc};
25
26/// An RPC endpoint to check for finality of blocks and transactions in Moonbeam
27#[rpc(server)]
28#[async_trait::async_trait]
29pub trait MoonbeamFinalityApi {
30	/// Reports whether a Substrate or Ethereum block is finalized.
31	/// Returns false if the block is not found.
32	#[method(name = "moon_isBlockFinalized")]
33	async fn is_block_finalized(&self, block_hash: H256) -> RpcResult<bool>;
34
35	/// Reports whether an Ethereum transaction is finalized.
36	/// Returns false if the transaction is not found
37	#[method(name = "moon_isTxFinalized")]
38	async fn is_tx_finalized(&self, tx_hash: H256) -> RpcResult<bool>;
39
40	/// Gets the range of blocks that are fully indexed in frontier's backend.
41	#[method(name = "moon_getEthSyncBlockRange")]
42	async fn get_frontier_sync_block_range(&self) -> RpcResult<(H256, H256)>;
43}
44
45pub struct MoonbeamFinality<B: Block, C> {
46	pub backend: Arc<dyn fc_api::Backend<B>>,
47	pub client: Arc<C>,
48	_phdata: PhantomData<B>,
49}
50
51impl<B: Block, C> MoonbeamFinality<B, C> {
52	pub fn new(client: Arc<C>, backend: Arc<dyn fc_api::Backend<B>>) -> Self {
53		Self {
54			backend,
55			client,
56			_phdata: Default::default(),
57		}
58	}
59}
60
61#[async_trait::async_trait]
62impl<B, C> MoonbeamFinalityApiServer for MoonbeamFinality<B, C>
63where
64	B: Block<Hash = H256>,
65	C: HeaderBackend<B> + Send + Sync + 'static,
66{
67	async fn is_block_finalized(&self, raw_hash: H256) -> RpcResult<bool> {
68		let client = self.client.clone();
69		is_block_finalized_inner::<B, C>(self.backend.as_ref(), &client, raw_hash).await
70	}
71
72	async fn is_tx_finalized(&self, tx_hash: H256) -> RpcResult<bool> {
73		let client = self.client.clone();
74
75		if let Some((ethereum_block_hash, _ethereum_index)) =
76			frontier_backend_client::load_transactions::<B, C>(
77				&client,
78				self.backend.as_ref(),
79				tx_hash,
80				true,
81			)
82			.await?
83		{
84			is_block_finalized_inner::<B, C>(self.backend.as_ref(), &client, ethereum_block_hash)
85				.await
86		} else {
87			Ok(false)
88		}
89	}
90
91	async fn get_frontier_sync_block_range(&self) -> RpcResult<(H256, H256)> {
92		match (
93			self.backend.deref().first_block_hash().await,
94			self.backend.deref().latest_block_hash().await,
95		) {
96			(Ok(first), Ok(last)) => Ok((first, last)),
97			(Err(e), _) => Err(ErrorObject::owned(
98				jsonrpsee::types::error::UNKNOWN_ERROR_CODE,
99				"No synced block",
100				Some(e),
101			)),
102			(_, Err(e)) => Err(ErrorObject::owned(
103				jsonrpsee::types::error::UNKNOWN_ERROR_CODE,
104				"No synced block",
105				Some(e),
106			)),
107		}
108	}
109}
110
111async fn is_block_finalized_inner<B: Block<Hash = H256>, C: HeaderBackend<B> + 'static>(
112	backend: &(dyn fc_api::Backend<B>),
113	client: &C,
114	raw_hash: H256,
115) -> RpcResult<bool> {
116	let substrate_hash =
117		match frontier_backend_client::load_hash::<B, C>(client, backend, raw_hash).await? {
118			// If we find this hash in the frontier data base, we know it is an eth hash
119			Some(hash) => hash,
120			// Otherwise, we assume this is a Substrate hash.
121			None => raw_hash,
122		};
123
124	// First check whether the block is in the best chain
125	if !is_canon(client, substrate_hash) {
126		return Ok(false);
127	}
128
129	// At this point we know the block in question is in the current best chain.
130	// It's just a question of whether it is in the finalized prefix or not
131	let query_height = client
132		.number(substrate_hash)
133		.expect("No sp_blockchain::Error should be thrown when looking up hash")
134		.expect("Block is already known to be canon, so it must be in the chain");
135	let finalized_height = client.info().finalized_number;
136
137	Ok(query_height <= finalized_height)
138}