From d66aaa16d53fd855d1253a39fc59ae333b6cdf44 Mon Sep 17 00:00:00 2001 From: tgmichel Date: Thu, 4 May 2023 15:59:32 +0200 Subject: [PATCH 1/7] Add `pending` support for `eth_getBlockByNumber` --- client/rpc/src/eth/block.rs | 110 ++++++++++++++++++++++++++--------- primitives/rpc/src/lib.rs | 5 ++ template/runtime/src/lib.rs | 25 +++++++- ts-tests/tests/test-block.ts | 48 ++++++++++++++- 4 files changed, 156 insertions(+), 32 deletions(-) diff --git a/client/rpc/src/eth/block.rs b/client/rpc/src/eth/block.rs index aeb3be6be4..632e445198 100644 --- a/client/rpc/src/eth/block.rs +++ b/client/rpc/src/eth/block.rs @@ -24,7 +24,9 @@ use jsonrpsee::core::RpcResult as Result; use sc_client_api::backend::{Backend, StorageProvider}; use sc_network_common::ExHashT; use sc_transaction_pool::ChainApi; +use sc_transaction_pool_api::InPoolTransaction; use sp_api::ProvideRuntimeApi; +use sp_block_builder::BlockBuilder as BlockBuilderApi; use sp_blockchain::HeaderBackend; use sp_core::hashing::keccak_256; use sp_runtime::traits::Block as BlockT; @@ -41,9 +43,10 @@ impl> Eth, - C::Api: EthereumRuntimeRPCApi, + C::Api: BlockBuilderApi + EthereumRuntimeRPCApi, C: HeaderBackend + StorageProvider + 'static, BE: Backend, + A: ChainApi + 'static, { pub async fn block_by_hash(&self, hash: H256, full: bool) -> Result> { let client = Arc::clone(&self.client); @@ -93,44 +96,93 @@ 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) - .unwrap_or_default(); + let base_fee = client + .runtime_api() + .gas_price(substrate_hash) + .unwrap_or_default(); - match (block, statuses) { - (Some(block), Some(statuses)) => { - let hash = H256::from(keccak_256(&rlp::encode(&block.header))); - - Ok(Some(rich_block_build( - block, - statuses.into_iter().map(Option::Some).collect(), - Some(hash), - full, - Some(base_fee), - ))) + match (block, statuses) { + (Some(block), Some(statuses)) => { + let hash = H256::from(keccak_256(&rlp::encode(&block.header))); + + Ok(Some(rich_block_build( + block, + statuses.into_iter().map(Option::Some).collect(), + Some(hash), + full, + Some(base_fee), + ))) + } + _ => Ok(None), + } } - _ => Ok(None), + None if number == BlockNumber::Pending => { + let api = client.runtime_api(); + let best_hash = client.info().best_hash; + + let parent_header = client + .header(best_hash) + .map_err(|_| internal_err(format!("Runtime access error at {}", best_hash)))? + .ok_or_else(|| internal_err(format!("Block not found at {}", best_hash)))?; + + // 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, &parent_header, xts) + .map_err(|_| internal_err(format!("Runtime access error at {}", best_hash)))?; + + let base_fee = api.gas_price(best_hash).unwrap_or_default(); + + match (block, statuses) { + (Some(block), Some(statuses)) => Ok(Some(rich_block_build( + block, + statuses.into_iter().map(Option::Some).collect(), + None, + full, + Some(base_fee), + ))), + _ => Ok(None), + } + } + None => Ok(None), } } diff --git a/primitives/rpc/src/lib.rs b/primitives/rpc/src/lib.rs index e00d447dd1..8622b45341 100644 --- a/primitives/rpc/src/lib.rs +++ b/primitives/rpc/src/lib.rs @@ -207,6 +207,11 @@ 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( + parent_header: &::Header, + xts: Vec<::Extrinsic>, + ) -> (Option, Option>); } #[api_version(2)] diff --git a/template/runtime/src/lib.rs b/template/runtime/src/lib.rs index 0663a7e922..d05857339c 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, KeyOwnerProofSystem, OnTimestampSet}, + traits::{ConstU32, ConstU8, FindAuthor, KeyOwnerProofSystem, OnFinalize, OnTimestampSet}, weights::{constants::WEIGHT_REF_TIME_PER_MILLIS, ConstantMultiplier, IdentityFee, Weight}, }; use pallet_grandpa::{ @@ -766,6 +766,29 @@ impl_runtime_apis! { } fn gas_limit_multiplier_support() {} + + fn pending_block( + parent_header: &::Header, + xts: Vec<::Extrinsic>, + ) -> (Option, Option>) { + let block_number = parent_header.number + 1; + System::initialize( + &block_number, + &parent_header.hash(), + &parent_header.digest, + ); + + for ext in xts.into_iter() { + let _ = Executive::apply_extrinsic(ext); + } + + Ethereum::on_finalize(block_number); + + ( + 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..941fa76c63 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,47 @@ 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(); + } + + // do not seal, get pendign block + let pending_transactions = []; + { + const pending = ( + await customRequest(context.web3, "eth_getBlockByNumber", ["pending", false]) + ).result; + 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); + }); +}); From 0921b4cab6fa44834746e9060f4b232fef8f1d89 Mon Sep 17 00:00:00 2001 From: tgmichel Date: Thu, 4 May 2023 16:07:02 +0200 Subject: [PATCH 2/7] header not needed --- client/rpc/src/eth/block.rs | 7 +------ primitives/rpc/src/lib.rs | 1 - template/runtime/src/lib.rs | 10 +--------- 3 files changed, 2 insertions(+), 16 deletions(-) diff --git a/client/rpc/src/eth/block.rs b/client/rpc/src/eth/block.rs index 632e445198..a2601baff4 100644 --- a/client/rpc/src/eth/block.rs +++ b/client/rpc/src/eth/block.rs @@ -139,11 +139,6 @@ where let api = client.runtime_api(); let best_hash = client.info().best_hash; - let parent_header = client - .header(best_hash) - .map_err(|_| internal_err(format!("Runtime access error at {}", best_hash)))? - .ok_or_else(|| internal_err(format!("Block not found at {}", best_hash)))?; - // Get current in-pool transactions let mut xts: Vec<::Extrinsic> = Vec::new(); // ready validated pool @@ -166,7 +161,7 @@ where ); let (block, statuses) = api - .pending_block(best_hash, &parent_header, xts) + .pending_block(best_hash, xts) .map_err(|_| internal_err(format!("Runtime access error at {}", best_hash)))?; let base_fee = api.gas_price(best_hash).unwrap_or_default(); diff --git a/primitives/rpc/src/lib.rs b/primitives/rpc/src/lib.rs index 8622b45341..910b0d6c75 100644 --- a/primitives/rpc/src/lib.rs +++ b/primitives/rpc/src/lib.rs @@ -209,7 +209,6 @@ sp_api::decl_runtime_apis! { fn gas_limit_multiplier_support(); /// Return the pending block. fn pending_block( - parent_header: &::Header, xts: Vec<::Extrinsic>, ) -> (Option, Option>); } diff --git a/template/runtime/src/lib.rs b/template/runtime/src/lib.rs index d05857339c..8d050a6d35 100644 --- a/template/runtime/src/lib.rs +++ b/template/runtime/src/lib.rs @@ -768,21 +768,13 @@ impl_runtime_apis! { fn gas_limit_multiplier_support() {} fn pending_block( - parent_header: &::Header, xts: Vec<::Extrinsic>, ) -> (Option, Option>) { - let block_number = parent_header.number + 1; - System::initialize( - &block_number, - &parent_header.hash(), - &parent_header.digest, - ); - for ext in xts.into_iter() { let _ = Executive::apply_extrinsic(ext); } - Ethereum::on_finalize(block_number); + Ethereum::on_finalize(System::block_number() + 1); ( pallet_ethereum::CurrentBlock::::get(), From 8ee21b867349aceaf555407205223b51fdd67216 Mon Sep 17 00:00:00 2001 From: tgmichel Date: Thu, 4 May 2023 16:27:18 +0200 Subject: [PATCH 3/7] cleanup --- client/rpc/src/eth/block.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/client/rpc/src/eth/block.rs b/client/rpc/src/eth/block.rs index a2601baff4..8c3aa00973 100644 --- a/client/rpc/src/eth/block.rs +++ b/client/rpc/src/eth/block.rs @@ -26,7 +26,6 @@ use sc_network_common::ExHashT; use sc_transaction_pool::ChainApi; use sc_transaction_pool_api::InPoolTransaction; use sp_api::ProvideRuntimeApi; -use sp_block_builder::BlockBuilder as BlockBuilderApi; use sp_blockchain::HeaderBackend; use sp_core::hashing::keccak_256; use sp_runtime::traits::Block as BlockT; @@ -43,7 +42,7 @@ impl> Eth, - C::Api: BlockBuilderApi + EthereumRuntimeRPCApi, + C::Api: EthereumRuntimeRPCApi, C: HeaderBackend + StorageProvider + 'static, BE: Backend, A: ChainApi + 'static, From 53494cb49a59c2d7b2bf3e2825fea34b92ba3429 Mon Sep 17 00:00:00 2001 From: tgmichel Date: Thu, 4 May 2023 17:11:49 +0200 Subject: [PATCH 4/7] prettier --- ts-tests/tests/test-block.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/ts-tests/tests/test-block.ts b/ts-tests/tests/test-block.ts index 941fa76c63..74ae2e9723 100644 --- a/ts-tests/tests/test-block.ts +++ b/ts-tests/tests/test-block.ts @@ -176,9 +176,7 @@ describeWithFrontier("Frontier RPC (Pending Block)", (context) => { // do not seal, get pendign block let pending_transactions = []; { - const pending = ( - await customRequest(context.web3, "eth_getBlockByNumber", ["pending", false]) - ).result; + const pending = (await customRequest(context.web3, "eth_getBlockByNumber", ["pending", false])).result; pending_transactions = pending.transactions; expect(pending_transactions.length).to.be.eq(expectedXtsNumber); } From addb94f99449f98540dc5589fe7c9fb6542c0c85 Mon Sep 17 00:00:00 2001 From: tgmichel Date: Fri, 5 May 2023 09:23:45 +0200 Subject: [PATCH 5/7] update some fields to be optional on pending --- client/rpc-core/src/types/block.rs | 4 ++-- client/rpc/src/eth/block.rs | 5 +++++ client/rpc/src/eth/mod.rs | 16 +++++++++++++--- client/rpc/src/eth_pubsub.rs | 2 +- 4 files changed, 21 insertions(+), 6 deletions(-) diff --git a/client/rpc-core/src/types/block.rs b/client/rpc-core/src/types/block.rs index e1fbefd186..f6f4175ca3 100644 --- a/client/rpc-core/src/types/block.rs +++ b/client/rpc-core/src/types/block.rs @@ -51,7 +51,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 @@ -77,7 +77,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 8c3aa00973..a5c98c5c55 100644 --- a/client/rpc/src/eth/block.rs +++ b/client/rpc/src/eth/block.rs @@ -82,6 +82,7 @@ where Some(hash), full, Some(base_fee), + false, ))), _ => Ok(None), } @@ -129,6 +130,7 @@ where Some(hash), full, Some(base_fee), + false, ))) } _ => Ok(None), @@ -159,6 +161,8 @@ where .collect::::Extrinsic>>(), ); + println!("FUTURE LEN ----------> {:?}", xts.len()); + let (block, statuses) = api .pending_block(best_hash, xts) .map_err(|_| internal_err(format!("Runtime access error at {}", best_hash)))?; @@ -172,6 +176,7 @@ where None, full, Some(base_fee), + true, ))), _ => Ok(None), } diff --git a/client/rpc/src/eth/mod.rs b/client/rpc/src/eth/mod.rs index 4bbdca1bb7..eb81f51ff7 100644 --- a/client/rpc/src/eth/mod.rs +++ b/client/rpc/src/eth/mod.rs @@ -381,7 +381,17 @@ fn rich_block_build( hash: Option, full_transactions: bool, base_fee: Option, + is_pending: bool, ) -> RichBlock { + let (miner, nonce, total_difficulty) = if !is_pending { + ( + Some(block.header.beneficiary), + Some(block.header.nonce), + Some(U256::zero()), + ) + } else { + (None, None, None) + }; Rich { inner: Block { header: Header { @@ -391,7 +401,7 @@ fn rich_block_build( 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, @@ -402,10 +412,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 670f653358..08a895aa87 100644 --- a/client/rpc/src/eth_pubsub.rs +++ b/client/rpc/src/eth_pubsub.rs @@ -102,7 +102,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, From 01cb4ccda6212ed94fe196e74678e314dd9bf911 Mon Sep 17 00:00:00 2001 From: tgmichel Date: Fri, 5 May 2023 09:24:40 +0200 Subject: [PATCH 6/7] update test --- ts-tests/tests/test-block.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/ts-tests/tests/test-block.ts b/ts-tests/tests/test-block.ts index 74ae2e9723..7b6b77bc0f 100644 --- a/ts-tests/tests/test-block.ts +++ b/ts-tests/tests/test-block.ts @@ -173,10 +173,17 @@ describeWithFrontier("Frontier RPC (Pending Block)", (context) => { 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.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); } From a6c87c49143979452d31e4b1e3dd2987b4582e82 Mon Sep 17 00:00:00 2001 From: tgmichel Date: Fri, 5 May 2023 09:25:26 +0200 Subject: [PATCH 7/7] cleanup --- client/rpc/src/eth/block.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/client/rpc/src/eth/block.rs b/client/rpc/src/eth/block.rs index a5c98c5c55..aaca846c15 100644 --- a/client/rpc/src/eth/block.rs +++ b/client/rpc/src/eth/block.rs @@ -161,8 +161,6 @@ where .collect::::Extrinsic>>(), ); - println!("FUTURE LEN ----------> {:?}", xts.len()); - let (block, statuses) = api .pending_block(best_hash, xts) .map_err(|_| internal_err(format!("Runtime access error at {}", best_hash)))?;