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/>.
16
use fc_rpc::frontier_backend_client::{self, is_canon};
17

            
18
use jsonrpsee::types::error::ErrorObject;
19
use jsonrpsee::{core::RpcResult, proc_macros::rpc};
20
use sp_blockchain::HeaderBackend;
21
use sp_core::H256;
22
use sp_runtime::traits::Block;
23
use std::ops::Deref;
24
use std::{marker::PhantomData, sync::Arc};
25

            
26
/// An RPC endpoint to check for finality of blocks and transactions in Moonbeam
27
1912
#[rpc(server)]
28
#[async_trait::async_trait]
29
pub 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

            
45
pub 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

            
51
impl<B: Block, C> MoonbeamFinality<B, C> {
52
1868
	pub fn new(client: Arc<C>, backend: Arc<dyn fc_api::Backend<B>>) -> Self {
53
1868
		Self {
54
1868
			backend,
55
1868
			client,
56
1868
			_phdata: Default::default(),
57
1868
		}
58
1868
	}
59
}
60

            
61
#[async_trait::async_trait]
62
impl<B, C> MoonbeamFinalityApiServer for MoonbeamFinality<B, C>
63
where
64
	B: Block<Hash = H256>,
65
	C: HeaderBackend<B> + Send + Sync + 'static,
66
{
67
10
	async fn is_block_finalized(&self, raw_hash: H256) -> RpcResult<bool> {
68
10
		let client = self.client.clone();
69
10
		is_block_finalized_inner::<B, C>(self.backend.as_ref(), &client, raw_hash).await
70
20
	}
71

            
72
10
	async fn is_tx_finalized(&self, tx_hash: H256) -> RpcResult<bool> {
73
10
		let client = self.client.clone();
74

            
75
8
		if let Some((ethereum_block_hash, _ethereum_index)) =
76
10
			frontier_backend_client::load_transactions::<B, C>(
77
10
				&client,
78
10
				self.backend.as_ref(),
79
10
				tx_hash,
80
10
				true,
81
10
			)
82
			.await?
83
		{
84
8
			is_block_finalized_inner::<B, C>(self.backend.as_ref(), &client, ethereum_block_hash)
85
				.await
86
		} else {
87
2
			Ok(false)
88
		}
89
20
	}
90

            
91
2
	async fn get_frontier_sync_block_range(&self) -> RpcResult<(H256, H256)> {
92
		match (
93
2
			self.backend.deref().first_block_hash().await,
94
2
			self.backend.deref().latest_block_hash().await,
95
		) {
96
2
			(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
4
	}
109
}
110

            
111
18
async fn is_block_finalized_inner<B: Block<Hash = H256>, C: HeaderBackend<B> + 'static>(
112
18
	backend: &(dyn fc_api::Backend<B>),
113
18
	client: &C,
114
18
	raw_hash: H256,
115
18
) -> RpcResult<bool> {
116
18
	let substrate_hash =
117
18
		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
8
			Some(hash) => hash,
120
			// Otherwise, we assume this is a Substrate hash.
121
10
			None => raw_hash,
122
		};
123

            
124
	// First check whether the block is in the best chain
125
18
	if !is_canon(client, substrate_hash) {
126
2
		return Ok(false);
127
16
	}
128
16

            
129
16
	// At this point we know the block in question is in the current best chain.
130
16
	// It's just a question of whether it is in the finalized prefix or not
131
16
	let query_height = client
132
16
		.number(substrate_hash)
133
16
		.expect("No sp_blockchain::Error should be thrown when looking up hash")
134
16
		.expect("Block is already known to be canon, so it must be in the chain");
135
16
	let finalized_height = client.info().finalized_number;
136
16

            
137
16
	Ok(query_height <= finalized_height)
138
18
}