From a5421a1d82a1f9db2387e2ed47185ab1e3a72cfb Mon Sep 17 00:00:00 2001 From: icodezjb <8869892+icodezjb@users.noreply.github.com> Date: Wed, 21 Feb 2024 13:12:41 +0800 Subject: [PATCH] Add bevm-finality-rpc finalized tool (#697) Co-authored-by: icodezjb --- Cargo.lock | 18 ++++++ Cargo.toml | 1 + client/rpc/finality/Cargo.toml | 21 +++++++ client/rpc/finality/README.md | 16 +++++ client/rpc/finality/src/lib.rs | 105 +++++++++++++++++++++++++++++++++ rpc/Cargo.toml | 3 + rpc/src/lib.rs | 6 ++ 7 files changed, 170 insertions(+) create mode 100644 client/rpc/finality/Cargo.toml create mode 100644 client/rpc/finality/README.md create mode 100644 client/rpc/finality/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 543ab4428..98c96b529 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -399,6 +399,23 @@ dependencies = [ "serde", ] +[[package]] +name = "bevm-finality-rpc" +version = "5.2.2" +dependencies = [ + "fc-db", + "fc-rpc", + "futures 0.3.21", + "jsonrpc-core", + "jsonrpc-derive", + "parity-scale-codec", + "sp-api", + "sp-blockchain", + "sp-core", + "sp-runtime", + "tokio", +] + [[package]] name = "bimap" version = "0.6.2" @@ -861,6 +878,7 @@ dependencies = [ name = "chainx-rpc" version = "5.2.2" dependencies = [ + "bevm-finality-rpc", "chainx-primitives", "fc-db", "fc-rpc", diff --git a/Cargo.toml b/Cargo.toml index 79e476eef..0dcfef05a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,7 @@ cli = { package = "chainx-cli", path = "cli", features = ["wasmtime"] } [workspace] members = [ "cli", + "client/rpc/finality", "executor", "primitives", "primitives/assets-registrar", diff --git a/client/rpc/finality/Cargo.toml b/client/rpc/finality/Cargo.toml new file mode 100644 index 000000000..59e964987 --- /dev/null +++ b/client/rpc/finality/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "bevm-finality-rpc" +description = "An experimental RPC to check for block and transaction finality in the bevm chain" +version = "5.2.2" +authors = ["The ChainX Authors"] +edition = "2021" + +[dependencies] +futures = { version = "0.3", features = [ "compat" ] } +jsonrpc-core = "18.0.0" +jsonrpc-derive = "18.0.0" +parity-scale-codec = "3.0.0" +tokio = { version = "1.12.0", features = [ "sync", "time" ] } + +fc-db = { git = "https://github.com/chainx-org/frontier", branch = "polkadot-v0.9.18-btc-fix2" } +fc-rpc = { git = "https://github.com/chainx-org/frontier", branch = "polkadot-v0.9.18-btc-fix2" } +sp-api = { git = "https://github.com/chainx-org/substrate", branch = "polkadot-v0.9.18-fix2" } +sp-blockchain = { git = "https://github.com/chainx-org/substrate", branch = "polkadot-v0.9.18-fix2" } +sp-core = { git = "https://github.com/chainx-org/substrate", branch = "polkadot-v0.9.18-fix2" } +sp-runtime = { git = "https://github.com/chainx-org/substrate", branch = "polkadot-v0.9.18-fix2" } + diff --git a/client/rpc/finality/README.md b/client/rpc/finality/README.md new file mode 100644 index 000000000..f00aff118 --- /dev/null +++ b/client/rpc/finality/README.md @@ -0,0 +1,16 @@ +# rpc + +```json +"bevm": { + "isBlockFinalized": { + "description": "Returns whether an Ethereum block is finalized", + "params": [{ "name": "blockHash", "type": "Hash" }], + "type": "bool" + }, + "isTxFinalized": { + "description": "Returns whether an Ethereum transaction is finalized", + "params": [{ "name": "txHash", "type": "Hash" }], + "type": "bool" + } +} +``` \ No newline at end of file diff --git a/client/rpc/finality/src/lib.rs b/client/rpc/finality/src/lib.rs new file mode 100644 index 000000000..866b9255c --- /dev/null +++ b/client/rpc/finality/src/lib.rs @@ -0,0 +1,105 @@ +// Copyright 2019-2023 ChainX Project Authors. Licensed under GPL-3.0. + +use fc_rpc::frontier_backend_client::{self, is_canon}; +use futures::{future::BoxFuture, FutureExt as _}; +use jsonrpc_core::Result as RpcResult; +use jsonrpc_derive::rpc; +use sp_core::H256; +use std::{marker::PhantomData, sync::Arc}; +//TODO ideally we wouldn't depend on BlockId here. Can we change frontier +// so it's load_hash helper returns an H256 instead of wrapping it in a BlockId? +use fc_db::Backend as FrontierBackend; +use sp_api::BlockId; +use sp_blockchain::HeaderBackend; +use sp_runtime::traits::Block; + +/// An RPC endpoint to check for finality of blocks and transactions in Bevm +#[rpc(server)] +pub trait BevmFinalityApi { + /// Reports whether a Substrate or Ethereum block is finalized. + /// Returns false if the block is not found. + #[rpc(name = "bevm_isBlockFinalized")] + fn is_block_finalized(&self, block_hash: H256) -> BoxFuture<'static, RpcResult>; + + /// Reports whether an Ethereum transaction is finalized. + /// Returns false if the transaction is not found + #[rpc(name = "bevm_isTxFinalized")] + fn is_tx_finalized(&self, tx_hash: H256) -> BoxFuture<'static, RpcResult>; +} + +pub struct BevmFinality { + pub backend: Arc>, + pub client: Arc, + _phdata: PhantomData, +} + +impl BevmFinality { + pub fn new(client: Arc, backend: Arc>) -> Self { + Self { + backend, + client, + _phdata: Default::default(), + } + } +} + +impl BevmFinalityApi for BevmFinality +where + B: Block, + C: HeaderBackend + Send + Sync + 'static, +{ + fn is_block_finalized(&self, raw_hash: H256) -> BoxFuture<'static, RpcResult> { + let backend = self.backend.clone(); + let client = self.client.clone(); + async move { is_block_finalized_inner::(&backend, &client, raw_hash) }.boxed() + } + + fn is_tx_finalized(&self, tx_hash: H256) -> BoxFuture<'static, RpcResult> { + let backend = self.backend.clone(); + let client = self.client.clone(); + async move { + if let Some((ethereum_block_hash, _ethereum_index)) = + frontier_backend_client::load_transactions::( + &client, + backend.as_ref(), + tx_hash, + true, + )? + { + is_block_finalized_inner::(&backend, &client, ethereum_block_hash) + } else { + Ok(false) + } + } + .boxed() + } +} + +fn is_block_finalized_inner, C: HeaderBackend + 'static>( + backend: &FrontierBackend, + client: &C, + raw_hash: H256, +) -> RpcResult { + let substrate_hash = match frontier_backend_client::load_hash::(backend, raw_hash)? { + // If we find this hash in the frontier data base, we know it is an eth hash + Some(BlockId::Hash(hash)) => hash, + Some(BlockId::Number(_)) => panic!("is_canon test only works with hashes."), + // Otherwise, we assume this is a Substrate hash. + None => raw_hash, + }; + + // First check whether the block is in the best chain + if !is_canon(client, substrate_hash) { + return Ok(false); + } + + // At this point we know the block in question is in the current best chain. + // It's just a question of whether it is in the finalized prefix or not + let query_height = client + .number(substrate_hash) + .expect("No sp_blockchain::Error should be thrown when looking up hash") + .expect("Block is already known to be canon, so it must be in the chain"); + let finalized_height = client.info().finalized_number; + + Ok(query_height <= finalized_height) +} diff --git a/rpc/Cargo.toml b/rpc/Cargo.toml index a1432356d..85a5013ef 100644 --- a/rpc/Cargo.toml +++ b/rpc/Cargo.toml @@ -43,6 +43,9 @@ substrate-frame-rpc-system = { git = "https://github.com/chainx-org/substrate", chainx-primitives = { path = "../primitives" } xp-runtime = { path = "../primitives/runtime" } +# Finality-rpc +bevm-finality-rpc = { path = "../client/rpc/finality" } + # ChainX pallets xpallet-assets-rpc = { path = "../xpallets/assets/rpc" } xpallet-assets-rpc-runtime-api = { path = "../xpallets/assets/rpc/runtime-api" } diff --git a/rpc/src/lib.rs b/rpc/src/lib.rs index 744f2e09a..dee5b11e2 100644 --- a/rpc/src/lib.rs +++ b/rpc/src/lib.rs @@ -206,6 +206,7 @@ where B::State: sc_client_api::backend::StateBackend>, A: ChainApi + 'static, { + use bevm_finality_rpc::{BevmFinality, BevmFinalityApi}; use pallet_transaction_payment_rpc::{TransactionPayment, TransactionPaymentApi}; use substrate_frame_rpc_system::{FullSystem, SystemApi}; use xpallet_assets_rpc::{Assets, XAssetsApi}; @@ -338,6 +339,11 @@ where fee_history_cache, ))); + io.extend_with(BevmFinalityApi::to_delegate(BevmFinality::new( + client.clone(), + backend.clone(), + ))); + if let Some(filter_pool) = filter_pool { io.extend_with(EthFilterApiServer::to_delegate(EthFilterApi::new( client.clone(),