From bb6704861858a44decbb7c2ba97495323166ccff Mon Sep 17 00:00:00 2001 From: tgmichel Date: Fri, 23 Jun 2023 13:39:39 +0200 Subject: [PATCH] Add `pending` support for `eth_getBlockByNumber` (#1048) * Add `pending` support for `eth_getBlockByNumber` * header not needed * cleanup * prettier * update some fields to be optional on pending * update test * cleanup --- client/rpc-core/src/types/block.rs | 4 +- client/rpc/src/eth/block.rs | 112 ++++++++++++++++++++--------- client/rpc/src/eth/mod.rs | 21 ++++-- client/rpc/src/eth_pubsub.rs | 2 +- primitives/rpc/src/lib.rs | 4 ++ template/runtime/src/lib.rs | 17 ++++- ts-tests/tests/test-block.ts | 54 +++++++++++++- 7 files changed, 170 insertions(+), 44 deletions(-) diff --git a/client/rpc-core/src/types/block.rs b/client/rpc-core/src/types/block.rs index 0ab435e253..1ce10681c8 100644 --- a/client/rpc-core/src/types/block.rs +++ b/client/rpc-core/src/types/block.rs @@ -52,7 +52,7 @@ pub struct Block { #[serde(flatten)] pub header: Header, /// Total difficulty - pub total_difficulty: U256, + pub total_difficulty: Option, /// Uncles' hashes pub uncles: Vec, /// Transactions @@ -78,7 +78,7 @@ pub struct Header { /// Authors address pub author: H160, /// Alias of `author` - pub miner: H160, + pub miner: Option, /// State root hash pub state_root: H256, /// Transactions root hash diff --git a/client/rpc/src/eth/block.rs b/client/rpc/src/eth/block.rs index 871c52d429..ac65b9b80a 100644 --- a/client/rpc/src/eth/block.rs +++ b/client/rpc/src/eth/block.rs @@ -23,6 +23,7 @@ use jsonrpsee::core::RpcResult; // Substrate use sc_client_api::backend::{Backend, StorageProvider}; use sc_transaction_pool::ChainApi; +use sc_transaction_pool_api::InPoolTransaction; use sp_api::ProvideRuntimeApi; use sp_blockchain::HeaderBackend; use sp_core::hashing::keccak_256; @@ -43,6 +44,7 @@ where C::Api: EthereumRuntimeRPCApi, C: HeaderBackend + StorageProvider + 'static, BE: Backend, + A: ChainApi + 'static, { pub async fn block_by_hash(&self, hash: H256, full: bool) -> RpcResult> { let client = Arc::clone(&self.client); @@ -77,6 +79,7 @@ where Some(hash), full, base_fee, + false, ); let substrate_hash = H256::from_slice(substrate_hash.as_ref()); @@ -102,52 +105,97 @@ where let client = Arc::clone(&self.client); let block_data_cache = Arc::clone(&self.block_data_cache); let backend = Arc::clone(&self.backend); + let graph = Arc::clone(&self.graph); - let id = match frontier_backend_client::native_block_id::( + match frontier_backend_client::native_block_id::( client.as_ref(), backend.as_ref(), Some(number), )? { - Some(id) => id, - None => return Ok(None), - }; - let substrate_hash = client - .expect_block_hash_from_id(&id) - .map_err(|_| internal_err(format!("Expect block number from id: {}", id)))?; + Some(id) => { + let substrate_hash = 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(client.as_ref(), substrate_hash); + let schema = fc_storage::onchain_storage_schema(client.as_ref(), substrate_hash); - let block = block_data_cache.current_block(schema, substrate_hash).await; - let statuses = block_data_cache - .current_transaction_statuses(schema, substrate_hash) - .await; + let block = block_data_cache.current_block(schema, substrate_hash).await; + let statuses = block_data_cache + .current_transaction_statuses(schema, substrate_hash) + .await; - let base_fee = client.runtime_api().gas_price(substrate_hash).ok(); + let base_fee = client.runtime_api().gas_price(substrate_hash).ok(); - match (block, statuses) { - (Some(block), Some(statuses)) => { - let hash = H256::from(keccak_256(&rlp::encode(&block.header))); + match (block, statuses) { + (Some(block), Some(statuses)) => { + let hash = H256::from(keccak_256(&rlp::encode(&block.header))); + let mut rich_block = rich_block_build( + block, + statuses.into_iter().map(Option::Some).collect(), + Some(hash), + full, + base_fee, + false, + ); - let mut rich_block = rich_block_build( - block, - statuses.into_iter().map(Option::Some).collect(), - Some(hash), - full, - base_fee, - ); + let substrate_hash = H256::from_slice(substrate_hash.as_ref()); + if let Some(parent_hash) = self + .forced_parent_hashes + .as_ref() + .and_then(|parent_hashes| parent_hashes.get(&substrate_hash).cloned()) + { + rich_block.inner.header.parent_hash = parent_hash + } - let substrate_hash = H256::from_slice(substrate_hash.as_ref()); - if let Some(parent_hash) = self - .forced_parent_hashes - .as_ref() - .and_then(|parent_hashes| parent_hashes.get(&substrate_hash).cloned()) - { - rich_block.inner.header.parent_hash = parent_hash + Ok(Some(rich_block)) + } + _ => Ok(None), } + } + None if number == BlockNumber::Pending => { + let api = client.runtime_api(); + let best_hash = client.info().best_hash; - Ok(Some(rich_block)) + // Get current in-pool transactions + let mut xts: Vec<::Extrinsic> = Vec::new(); + // ready validated pool + xts.extend( + graph + .validated_pool() + .ready() + .map(|in_pool_tx| in_pool_tx.data().clone()) + .collect::::Extrinsic>>(), + ); + + // future validated pool + xts.extend( + graph + .validated_pool() + .futures() + .iter() + .map(|(_hash, extrinsic)| extrinsic.clone()) + .collect::::Extrinsic>>(), + ); + + let (block, statuses) = api + .pending_block(best_hash, xts) + .map_err(|_| internal_err(format!("Runtime access error at {}", best_hash)))?; + + let base_fee = api.gas_price(best_hash).ok(); + + match (block, statuses) { + (Some(block), Some(statuses)) => Ok(Some(rich_block_build( + block, + statuses.into_iter().map(Option::Some).collect(), + None, + full, + base_fee, + true, + ))), + _ => Ok(None), + } } - _ => Ok(None), + None => Ok(None), } } diff --git a/client/rpc/src/eth/mod.rs b/client/rpc/src/eth/mod.rs index e74b92e219..1ca8c7480e 100644 --- a/client/rpc/src/eth/mod.rs +++ b/client/rpc/src/eth/mod.rs @@ -396,17 +396,26 @@ fn rich_block_build( hash: Option, full_transactions: bool, base_fee: Option, + is_pending: bool, ) -> RichBlock { + let (hash, miner, nonce, total_difficulty) = if !is_pending { + ( + Some(hash.unwrap_or_else(|| H256::from(keccak_256(&rlp::encode(&block.header))))), + Some(block.header.beneficiary), + Some(block.header.nonce), + Some(U256::zero()), + ) + } else { + (None, None, None, None) + }; Rich { inner: Block { header: Header { - hash: Some( - hash.unwrap_or_else(|| H256::from(keccak_256(&rlp::encode(&block.header)))), - ), + hash, parent_hash: block.header.parent_hash, uncles_hash: block.header.ommers_hash, author: block.header.beneficiary, - miner: block.header.beneficiary, + miner, state_root: block.header.state_root, transactions_root: block.header.transactions_root, receipts_root: block.header.receipts_root, @@ -417,10 +426,10 @@ fn rich_block_build( logs_bloom: block.header.logs_bloom, timestamp: U256::from(block.header.timestamp / 1000), difficulty: block.header.difficulty, - nonce: Some(block.header.nonce), + nonce, size: Some(U256::from(rlp::encode(&block.header).len() as u32)), }, - total_difficulty: U256::zero(), + total_difficulty, uncles: vec![], transactions: { if full_transactions { diff --git a/client/rpc/src/eth_pubsub.rs b/client/rpc/src/eth_pubsub.rs index 8f08934c39..9466983b95 100644 --- a/client/rpc/src/eth_pubsub.rs +++ b/client/rpc/src/eth_pubsub.rs @@ -107,7 +107,7 @@ impl EthSubscriptionResult { parent_hash: block.header.parent_hash, uncles_hash: block.header.ommers_hash, author: block.header.beneficiary, - miner: block.header.beneficiary, + miner: Some(block.header.beneficiary), state_root: block.header.state_root, transactions_root: block.header.transactions_root, receipts_root: block.header.receipts_root, diff --git a/primitives/rpc/src/lib.rs b/primitives/rpc/src/lib.rs index e00d447dd1..910b0d6c75 100644 --- a/primitives/rpc/src/lib.rs +++ b/primitives/rpc/src/lib.rs @@ -207,6 +207,10 @@ sp_api::decl_runtime_apis! { /// Used to determine if gas limit multiplier for non-transactional calls (eth_call/estimateGas) /// is supported. fn gas_limit_multiplier_support(); + /// Return the pending block. + fn pending_block( + xts: Vec<::Extrinsic>, + ) -> (Option, Option>); } #[api_version(2)] diff --git a/template/runtime/src/lib.rs b/template/runtime/src/lib.rs index 59981a59a2..5ec33a9a9b 100644 --- a/template/runtime/src/lib.rs +++ b/template/runtime/src/lib.rs @@ -35,7 +35,7 @@ use frame_support::weights::constants::ParityDbWeight as RuntimeDbWeight; use frame_support::weights::constants::RocksDbWeight as RuntimeDbWeight; use frame_support::{ construct_runtime, parameter_types, - traits::{ConstU32, ConstU8, FindAuthor, OnTimestampSet}, + traits::{ConstU32, ConstU8, FindAuthor, OnFinalize, OnTimestampSet}, weights::{constants::WEIGHT_REF_TIME_PER_MILLIS, ConstantMultiplier, IdentityFee, Weight}, }; use pallet_grandpa::{ @@ -759,6 +759,21 @@ impl_runtime_apis! { } fn gas_limit_multiplier_support() {} + + fn pending_block( + xts: Vec<::Extrinsic>, + ) -> (Option, Option>) { + for ext in xts.into_iter() { + let _ = Executive::apply_extrinsic(ext); + } + + Ethereum::on_finalize(System::block_number() + 1); + + ( + pallet_ethereum::CurrentBlock::::get(), + pallet_ethereum::CurrentTransactionStatuses::::get() + ) + } } impl fp_rpc::ConvertTransactionRuntimeApi for Runtime { diff --git a/ts-tests/tests/test-block.ts b/ts-tests/tests/test-block.ts index 8fd331874c..3ec80a545b 100644 --- a/ts-tests/tests/test-block.ts +++ b/ts-tests/tests/test-block.ts @@ -1,8 +1,8 @@ import { expect } from "chai"; import { step } from "mocha-steps"; -import { BLOCK_TIMESTAMP, ETH_BLOCK_GAS_LIMIT } from "./config"; -import { createAndFinalizeBlock, describeWithFrontier } from "./util"; +import { BLOCK_TIMESTAMP, ETH_BLOCK_GAS_LIMIT, GENESIS_ACCOUNT, GENESIS_ACCOUNT_PRIVATE_KEY } from "./config"; +import { createAndFinalizeBlock, describeWithFrontier, customRequest } from "./util"; describeWithFrontier("Frontier RPC (Block)", (context) => { let previousBlock; @@ -145,3 +145,53 @@ describeWithFrontier("Frontier RPC (Block)", (context) => { expect(block.parentHash).to.equal(previousBlock.hash); }); }); + +describeWithFrontier("Frontier RPC (Pending Block)", (context) => { + const TEST_ACCOUNT = "0x1111111111111111111111111111111111111111"; + + it("should return pending block", async function () { + var nonce = 0; + let sendTransaction = async () => { + const tx = await context.web3.eth.accounts.signTransaction( + { + from: GENESIS_ACCOUNT, + to: TEST_ACCOUNT, + value: "0x200", // Must be higher than ExistentialDeposit + gasPrice: "0x3B9ACA00", + gas: "0x100000", + nonce: nonce, + }, + GENESIS_ACCOUNT_PRIVATE_KEY + ); + nonce = nonce + 1; + return (await customRequest(context.web3, "eth_sendRawTransaction", [tx.rawTransaction])).result; + }; + + // block 1 send 5 transactions + const expectedXtsNumber = 5; + for (var _ of Array(expectedXtsNumber)) { + await sendTransaction(); + } + + // test still invalid future transactions can be safely applied (they are applied, just not overlayed) + nonce = nonce + 100; + await sendTransaction(); + + // do not seal, get pendign block + let pending_transactions = []; + { + const pending = (await customRequest(context.web3, "eth_getBlockByNumber", ["pending", false])).result; + expect(pending.hash).to.be.null; + expect(pending.miner).to.be.null; + expect(pending.nonce).to.be.null; + expect(pending.totalDifficulty).to.be.null; + pending_transactions = pending.transactions; + expect(pending_transactions.length).to.be.eq(expectedXtsNumber); + } + + // seal and compare latest blocks transactions with the previously pending + await createAndFinalizeBlock(context.web3); + const latest_block = await context.web3.eth.getBlock("latest", false); + expect(pending_transactions).to.be.deep.eq(latest_block.transactions); + }); +});