Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add pending support for eth_getBlockByNumber #1048

Merged
merged 8 commits into from
Jun 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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>>(),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
.collect::<Vec<<B as BlockT>::Extrinsic>>(),
.collect::<Vec<_>>(),

);

// future validated pool
xts.extend(
graph
.validated_pool()
.futures()
.iter()
.map(|(_hash, extrinsic)| extrinsic.clone())
.collect::<Vec<<B as BlockT>::Extrinsic>>(),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
.collect::<Vec<<B as BlockT>::Extrinsic>>(),
.collect::<Vec<_>>(),

);

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,
tgmichel marked this conversation as resolved.
Show resolved Hide resolved
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 @@ -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<<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 @@ -759,6 +759,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);
});
});