Skip to content

Commit

Permalink
Add pending support for eth_getBlockByNumber (#1048)
Browse files Browse the repository at this point in the history
* Add `pending` support for `eth_getBlockByNumber`

* header not needed

* cleanup

* prettier

* update some fields to be optional on pending

* update test

* cleanup
  • Loading branch information
tgmichel authored Jun 23, 2023
1 parent 2cfa1d6 commit 67df5c8
Show file tree
Hide file tree
Showing 7 changed files with 170 additions and 44 deletions.
4 changes: 2 additions & 2 deletions client/rpc-core/src/types/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ pub struct Block {
#[serde(flatten)]
pub header: Header,
/// Total difficulty
pub total_difficulty: U256,
pub total_difficulty: Option<U256>,
/// Uncles' hashes
pub uncles: Vec<H256>,
/// Transactions
Expand All @@ -78,7 +78,7 @@ pub struct Header {
/// Authors address
pub author: H160,
/// Alias of `author`
pub miner: H160,
pub miner: Option<H160>,
/// State root hash
pub state_root: H256,
/// Transactions root hash
Expand Down
112 changes: 80 additions & 32 deletions client/rpc/src/eth/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -43,6 +44,7 @@ where
C::Api: EthereumRuntimeRPCApi<B>,
C: HeaderBackend<B> + StorageProvider<B, BE> + 'static,
BE: Backend<B>,
A: ChainApi<Block = B> + 'static,
{
pub async fn block_by_hash(&self, hash: H256, full: bool) -> RpcResult<Option<RichBlock>> {
let client = Arc::clone(&self.client);
Expand Down Expand Up @@ -78,6 +80,7 @@ where
Some(hash),
full,
base_fee,
false,
);

let substrate_hash = H256::from_slice(substrate_hash.as_ref());
Expand All @@ -103,54 +106,99 @@ 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::<B, C>(
match frontier_backend_client::native_block_id::<B, C>(
client.as_ref(),
backend.as_ref(),
Some(number),
)
.await?
{
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<<B as BlockT>::Extrinsic> = Vec::new();
// ready validated pool
xts.extend(
graph
.validated_pool()
.ready()
.map(|in_pool_tx| in_pool_tx.data().clone())
.collect::<Vec<<B as BlockT>::Extrinsic>>(),
);

// future validated pool
xts.extend(
graph
.validated_pool()
.futures()
.iter()
.map(|(_hash, extrinsic)| extrinsic.clone())
.collect::<Vec<<B as BlockT>::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),
}
}

Expand Down
21 changes: 15 additions & 6 deletions client/rpc/src/eth/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -404,17 +404,26 @@ fn rich_block_build(
hash: Option<H256>,
full_transactions: bool,
base_fee: Option<U256>,
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,
Expand All @@ -425,10 +434,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 {
Expand Down
2 changes: 1 addition & 1 deletion client/rpc/src/eth_pubsub.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
4 changes: 4 additions & 0 deletions primitives/rpc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,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<<Block as BlockT>::Extrinsic>,
) -> (Option<ethereum::BlockV2>, Option<Vec<TransactionStatus>>);
}

#[api_version(2)]
Expand Down
17 changes: 16 additions & 1 deletion template/runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::{
Expand Down Expand Up @@ -768,6 +768,21 @@ impl_runtime_apis! {
}

fn gas_limit_multiplier_support() {}

fn pending_block(
xts: Vec<<Block as BlockT>::Extrinsic>,
) -> (Option<pallet_ethereum::Block>, Option<Vec<TransactionStatus>>) {
for ext in xts.into_iter() {
let _ = Executive::apply_extrinsic(ext);
}

Ethereum::on_finalize(System::block_number() + 1);

(
pallet_ethereum::CurrentBlock::<Runtime>::get(),
pallet_ethereum::CurrentTransactionStatuses::<Runtime>::get()
)
}
}

impl fp_rpc::ConvertTransactionRuntimeApi<Block> for Runtime {
Expand Down
54 changes: 52 additions & 2 deletions ts-tests/tests/test-block.ts
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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);
});
});

0 comments on commit 67df5c8

Please sign in to comment.