From eef1d489c1d5d2be9378e69a0da9cd770490a39f Mon Sep 17 00:00:00 2001 From: koushiro Date: Wed, 1 Nov 2023 14:59:56 +0800 Subject: [PATCH] feat: add debug standard rpc - debug_getRawHeader - debug_getRawBlock - debug_getRawTransaction - debug_getRawReceipts - debug_getBadBlocks --- Cargo.lock | 4 +- client/rpc-core/src/debug.rs | 49 +++++ client/rpc-core/src/lib.rs | 2 + client/rpc/src/{eth => }/cache/lru_cache.rs | 0 client/rpc/src/{eth => }/cache/mod.rs | 0 client/rpc/src/debug.rs | 207 ++++++++++++++++++++ client/rpc/src/eth/filter.rs | 2 +- client/rpc/src/eth/mod.rs | 12 +- client/rpc/src/lib.rs | 12 +- template/node/src/rpc/eth.rs | 22 ++- 10 files changed, 289 insertions(+), 21 deletions(-) create mode 100644 client/rpc-core/src/debug.rs rename client/rpc/src/{eth => }/cache/lru_cache.rs (100%) rename client/rpc/src/{eth => }/cache/mod.rs (100%) create mode 100644 client/rpc/src/debug.rs diff --git a/Cargo.lock b/Cargo.lock index 77d720040b..a3412f7401 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1024,9 +1024,9 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "bytes" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" +checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" [[package]] name = "bzip2-sys" diff --git a/client/rpc-core/src/debug.rs b/client/rpc-core/src/debug.rs new file mode 100644 index 0000000000..2dd02d5a03 --- /dev/null +++ b/client/rpc-core/src/debug.rs @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 +// This file is part of Frontier. +// +// Copyright (c) 2023 Parity Technologies (UK) Ltd. +// +// This program 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. +// +// This program 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 this program. If not, see . + +//! Debug rpc interface. + +use ethereum_types::H256; +use jsonrpsee::{core::RpcResult, proc_macros::rpc}; + +use crate::types::{BlockNumberOrHash, Bytes}; + +/// Net rpc interface. +#[rpc(server)] +#[async_trait] +pub trait DebugApi { + /// Returns an RLP-encoded header with the given number or hash. + #[method(name = "debug_getRawHeader")] + async fn raw_header(&self, number: BlockNumberOrHash) -> RpcResult>; + + /// Returns an RLP-encoded block with the given number or hash. + #[method(name = "debug_getRawBlock")] + async fn raw_block(&self, number: BlockNumberOrHash) -> RpcResult>; + + /// Returns a EIP-2718 binary-encoded transaction with the given hash. + #[method(name = "debug_getRawTransaction")] + async fn raw_transaction(&self, hash: H256) -> RpcResult>; + + /// Returns an array of EIP-2718 binary-encoded receipts with the given number of hash. + #[method(name = "debug_getRawReceipts")] + async fn raw_receipts(&self, number: BlockNumberOrHash) -> RpcResult>; + + /// Returns an array of recent bad blocks that the client has seen on the network. + #[method(name = "debug_getBadBlocks")] + fn bad_blocks(&self, number: BlockNumberOrHash) -> RpcResult>; +} diff --git a/client/rpc-core/src/lib.rs b/client/rpc-core/src/lib.rs index 7f29588ba8..d1b927c605 100644 --- a/client/rpc-core/src/lib.rs +++ b/client/rpc-core/src/lib.rs @@ -20,6 +20,7 @@ pub mod types; +mod debug; mod eth; mod eth_pubsub; mod net; @@ -30,6 +31,7 @@ mod web3; #[cfg(feature = "txpool")] pub use self::txpool::TxPoolApiServer; pub use self::{ + debug::DebugApiServer, eth::{EthApiServer, EthFilterApiServer}, eth_pubsub::EthPubSubApiServer, net::NetApiServer, diff --git a/client/rpc/src/eth/cache/lru_cache.rs b/client/rpc/src/cache/lru_cache.rs similarity index 100% rename from client/rpc/src/eth/cache/lru_cache.rs rename to client/rpc/src/cache/lru_cache.rs diff --git a/client/rpc/src/eth/cache/mod.rs b/client/rpc/src/cache/mod.rs similarity index 100% rename from client/rpc/src/eth/cache/mod.rs rename to client/rpc/src/cache/mod.rs diff --git a/client/rpc/src/debug.rs b/client/rpc/src/debug.rs new file mode 100644 index 0000000000..ee25661dbf --- /dev/null +++ b/client/rpc/src/debug.rs @@ -0,0 +1,207 @@ +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 +// This file is part of Frontier. +// +// Copyright (c) 2023 Parity Technologies (UK) Ltd. +// +// This program 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. +// +// This program 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 this program. If not, see . + +use std::{marker::PhantomData, sync::Arc}; + +use ethereum::EnvelopedEncodable; +use ethereum_types::H256; +use jsonrpsee::core::{async_trait, RpcResult}; +use rlp::Encodable; +// Substrate +use sc_client_api::backend::{Backend, StorageProvider}; +use sp_api::ProvideRuntimeApi; +use sp_blockchain::HeaderBackend; +use sp_runtime::traits::Block as BlockT; +// Frontier +use fc_rpc_core::{types::*, DebugApiServer}; +use fc_storage::OverrideHandle; +use fp_rpc::EthereumRuntimeRPCApi; + +use crate::{cache::EthBlockDataCacheTask, frontier_backend_client, internal_err}; + +/// Debug API implementation. +pub struct Debug { + client: Arc, + backend: Arc>, + overrides: Arc>, + block_data_cache: Arc>, + _marker: PhantomData, +} + +impl Debug { + pub fn new( + client: Arc, + backend: Arc>, + overrides: Arc>, + block_data_cache: Arc>, + ) -> Self { + Self { + client, + backend, + overrides, + block_data_cache, + _marker: PhantomData, + } + } + + async fn block_by(&self, number: BlockNumberOrHash) -> RpcResult> + where + C: HeaderBackend + StorageProvider + 'static, + BE: Backend, + { + let id = match frontier_backend_client::native_block_id::( + self.client.as_ref(), + self.backend.as_ref(), + Some(number), + ) + .await? + { + Some(id) => id, + None => return Ok(None), + }; + + let substrate_hash = self + .client + .expect_block_hash_from_id(&id) + .map_err(|_| internal_err(format!("Expect block number from id: {}", id)))?; + let schema = fc_storage::onchain_storage_schema(self.client.as_ref(), substrate_hash); + let block = self + .block_data_cache + .current_block(schema, substrate_hash) + .await; + Ok(block) + } + + async fn transaction_by( + &self, + transaction_hash: H256, + ) -> RpcResult> + where + C: HeaderBackend + StorageProvider + 'static, + BE: Backend, + { + let (eth_block_hash, index) = match frontier_backend_client::load_transactions::( + self.client.as_ref(), + self.backend.as_ref(), + transaction_hash, + true, + ) + .await? + { + Some((hash, index)) => (hash, index as usize), + None => return Ok(None), + }; + + let substrate_hash = match frontier_backend_client::load_hash::( + self.client.as_ref(), + self.backend.as_ref(), + eth_block_hash, + ) + .await? + { + Some(hash) => hash, + None => return Ok(None), + }; + + let schema = fc_storage::onchain_storage_schema(self.client.as_ref(), substrate_hash); + let block = self + .block_data_cache + .current_block(schema, substrate_hash) + .await; + if let Some(block) = block { + Ok(Some(block.transactions[index].clone())) + } else { + Ok(None) + } + } + + async fn receipts_by( + &self, + number: BlockNumberOrHash, + ) -> RpcResult>> + where + C: HeaderBackend + StorageProvider + 'static, + BE: Backend, + { + let id = match frontier_backend_client::native_block_id::( + self.client.as_ref(), + self.backend.as_ref(), + Some(number), + ) + .await? + { + Some(id) => id, + None => return Ok(None), + }; + + let substrate_hash = self + .client + .expect_block_hash_from_id(&id) + .map_err(|_| internal_err(format!("Expect block number from id: {}", id)))?; + + let schema = fc_storage::onchain_storage_schema(self.client.as_ref(), substrate_hash); + let handler = self + .overrides + .schemas + .get(&schema) + .unwrap_or(&self.overrides.fallback); + let receipts = handler.current_receipts(substrate_hash); + Ok(receipts) + } +} + +#[async_trait] +impl DebugApiServer for Debug +where + B: BlockT, + C: ProvideRuntimeApi, + C::Api: EthereumRuntimeRPCApi, + C: HeaderBackend + StorageProvider + 'static, + BE: Backend + 'static, +{ + async fn raw_header(&self, number: BlockNumberOrHash) -> RpcResult> { + let block = self.block_by(number).await?; + Ok(block.map(|block| Bytes::new(block.header.rlp_bytes().to_vec()))) + } + + async fn raw_block(&self, number: BlockNumberOrHash) -> RpcResult> { + let block = self.block_by(number).await?; + Ok(block.map(|block| Bytes::new(block.rlp_bytes().to_vec()))) + } + + async fn raw_transaction(&self, hash: H256) -> RpcResult> { + let transaction = self.transaction_by(hash).await?; + Ok(transaction.map(|transaction| Bytes::new(transaction.encode().to_vec()))) + } + + async fn raw_receipts(&self, number: BlockNumberOrHash) -> RpcResult> { + let receipts = self.receipts_by(number).await?.unwrap_or_default(); + Ok(receipts + .into_iter() + .map(|receipt| Bytes::new(receipt.encode().to_vec())) + .collect::>()) + } + + fn bad_blocks(&self, _number: BlockNumberOrHash) -> RpcResult> { + // `debug_getBadBlocks` wouldn't really be useful in a Substrate context. + // The rationale for that is for debugging multi-client consensus issues, which we'll never face + // (we may have multiple clients in the future, but for runtime it's only "multi-wasm-runtime", never "multi-EVM"). + // We can simply return empty array for this API. + Ok(vec![]) + } +} diff --git a/client/rpc/src/eth/filter.rs b/client/rpc/src/eth/filter.rs index b0a34465ec..f6ac476ca6 100644 --- a/client/rpc/src/eth/filter.rs +++ b/client/rpc/src/eth/filter.rs @@ -41,7 +41,7 @@ use sp_runtime::{ use fc_rpc_core::{types::*, EthFilterApiServer}; use fp_rpc::{EthereumRuntimeRPCApi, TransactionStatus}; -use crate::{eth::cache::EthBlockDataCacheTask, frontier_backend_client, internal_err}; +use crate::{cache::EthBlockDataCacheTask, frontier_backend_client, internal_err}; pub struct EthFilter { client: Arc, diff --git a/client/rpc/src/eth/mod.rs b/client/rpc/src/eth/mod.rs index 1c7bb62ebc..10a3122b9f 100644 --- a/client/rpc/src/eth/mod.rs +++ b/client/rpc/src/eth/mod.rs @@ -17,7 +17,6 @@ // along with this program. If not, see . mod block; -mod cache; mod client; mod execute; mod fee; @@ -53,14 +52,13 @@ use fp_rpc::{ RuntimeStorageOverride, TransactionStatus, }; -use crate::{frontier_backend_client, internal_err, public_key, signer::EthSigner}; - -pub use self::{ - cache::{EthBlockDataCacheTask, EthTask}, - execute::EstimateGasAdapter, - filter::EthFilter, +use crate::{ + cache::EthBlockDataCacheTask, frontier_backend_client, internal_err, public_key, + signer::EthSigner, }; +pub use self::{execute::EstimateGasAdapter, filter::EthFilter}; + // Configuration trait for RPC configuration. pub trait EthConfig: Send + Sync + 'static { type EstimateGasAdapter: EstimateGasAdapter + Send + Sync; diff --git a/client/rpc/src/lib.rs b/client/rpc/src/lib.rs index 2a217a41b5..46f82a3ed1 100644 --- a/client/rpc/src/lib.rs +++ b/client/rpc/src/lib.rs @@ -26,6 +26,8 @@ )] #![deny(unused_crate_dependencies)] +mod cache; +mod debug; mod eth; mod eth_pubsub; mod net; @@ -37,10 +39,9 @@ mod web3; #[cfg(feature = "txpool")] pub use self::txpool::TxPool; pub use self::{ - eth::{ - format, pending, EstimateGasAdapter, Eth, EthBlockDataCacheTask, EthConfig, EthFilter, - EthTask, - }, + cache::{EthBlockDataCacheTask, EthTask}, + debug::Debug, + eth::{format, pending, EstimateGasAdapter, Eth, EthConfig, EthFilter}, eth_pubsub::{EthPubSub, EthereumSubIdProvider}, net::Net, signer::{EthDevSigner, EthSigner}, @@ -50,7 +51,8 @@ pub use ethereum::TransactionV2 as EthereumTransaction; #[cfg(feature = "txpool")] pub use fc_rpc_core::TxPoolApiServer; pub use fc_rpc_core::{ - EthApiServer, EthFilterApiServer, EthPubSubApiServer, NetApiServer, Web3ApiServer, + DebugApiServer, EthApiServer, EthFilterApiServer, EthPubSubApiServer, NetApiServer, + Web3ApiServer, }; pub use fc_storage::{ OverrideHandle, RuntimeApiStorageOverride, SchemaV1Override, SchemaV2Override, diff --git a/template/node/src/rpc/eth.rs b/template/node/src/rpc/eth.rs index caf7e478ef..ff4c80b327 100644 --- a/template/node/src/rpc/eth.rs +++ b/template/node/src/rpc/eth.rs @@ -94,9 +94,9 @@ where EC: EthConfig, { use fc_rpc::{ - pending::AuraConsensusDataProvider, Eth, EthApiServer, EthDevSigner, EthFilter, - EthFilterApiServer, EthPubSub, EthPubSubApiServer, EthSigner, Net, NetApiServer, Web3, - Web3ApiServer, + pending::AuraConsensusDataProvider, Debug, DebugApiServer, Eth, EthApiServer, EthDevSigner, + EthFilter, EthFilterApiServer, EthPubSub, EthPubSubApiServer, EthSigner, Net, NetApiServer, + Web3, Web3ApiServer, }; #[cfg(feature = "txpool")] use fc_rpc::{TxPool, TxPoolApiServer}; @@ -154,12 +154,12 @@ where io.merge( EthFilter::new( client.clone(), - frontier_backend, + frontier_backend.clone(), graph.clone(), filter_pool, 500_usize, // max stored filters max_past_logs, - block_data_cache, + block_data_cache.clone(), ) .into_rpc(), )?; @@ -171,7 +171,7 @@ where client.clone(), sync, subscription_task_executor, - overrides, + overrides.clone(), pubsub_notification_sinks, ) .into_rpc(), @@ -189,6 +189,16 @@ where io.merge(Web3::new(client.clone()).into_rpc())?; + io.merge( + Debug::new( + client.clone(), + frontier_backend, + overrides, + block_data_cache, + ) + .into_rpc(), + )?; + #[cfg(feature = "txpool")] io.merge(TxPool::new(client, graph).into_rpc())?;