Skip to content

Commit 67df5c8

Browse files
authored
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
1 parent 2cfa1d6 commit 67df5c8

File tree

7 files changed

+170
-44
lines changed

7 files changed

+170
-44
lines changed

client/rpc-core/src/types/block.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ pub struct Block {
5252
#[serde(flatten)]
5353
pub header: Header,
5454
/// Total difficulty
55-
pub total_difficulty: U256,
55+
pub total_difficulty: Option<U256>,
5656
/// Uncles' hashes
5757
pub uncles: Vec<H256>,
5858
/// Transactions
@@ -78,7 +78,7 @@ pub struct Header {
7878
/// Authors address
7979
pub author: H160,
8080
/// Alias of `author`
81-
pub miner: H160,
81+
pub miner: Option<H160>,
8282
/// State root hash
8383
pub state_root: H256,
8484
/// Transactions root hash

client/rpc/src/eth/block.rs

Lines changed: 80 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ use jsonrpsee::core::RpcResult;
2323
// Substrate
2424
use sc_client_api::backend::{Backend, StorageProvider};
2525
use sc_transaction_pool::ChainApi;
26+
use sc_transaction_pool_api::InPoolTransaction;
2627
use sp_api::ProvideRuntimeApi;
2728
use sp_blockchain::HeaderBackend;
2829
use sp_core::hashing::keccak_256;
@@ -43,6 +44,7 @@ where
4344
C::Api: EthereumRuntimeRPCApi<B>,
4445
C: HeaderBackend<B> + StorageProvider<B, BE> + 'static,
4546
BE: Backend<B>,
47+
A: ChainApi<Block = B> + 'static,
4648
{
4749
pub async fn block_by_hash(&self, hash: H256, full: bool) -> RpcResult<Option<RichBlock>> {
4850
let client = Arc::clone(&self.client);
@@ -78,6 +80,7 @@ where
7880
Some(hash),
7981
full,
8082
base_fee,
83+
false,
8184
);
8285

8386
let substrate_hash = H256::from_slice(substrate_hash.as_ref());
@@ -103,54 +106,99 @@ where
103106
let client = Arc::clone(&self.client);
104107
let block_data_cache = Arc::clone(&self.block_data_cache);
105108
let backend = Arc::clone(&self.backend);
109+
let graph = Arc::clone(&self.graph);
106110

107-
let id = match frontier_backend_client::native_block_id::<B, C>(
111+
match frontier_backend_client::native_block_id::<B, C>(
108112
client.as_ref(),
109113
backend.as_ref(),
110114
Some(number),
111115
)
112116
.await?
113117
{
114-
Some(id) => id,
115-
None => return Ok(None),
116-
};
117-
let substrate_hash = client
118-
.expect_block_hash_from_id(&id)
119-
.map_err(|_| internal_err(format!("Expect block number from id: {}", id)))?;
118+
Some(id) => {
119+
let substrate_hash = client
120+
.expect_block_hash_from_id(&id)
121+
.map_err(|_| internal_err(format!("Expect block number from id: {}", id)))?;
120122

121-
let schema = fc_storage::onchain_storage_schema(client.as_ref(), substrate_hash);
123+
let schema = fc_storage::onchain_storage_schema(client.as_ref(), substrate_hash);
122124

123-
let block = block_data_cache.current_block(schema, substrate_hash).await;
124-
let statuses = block_data_cache
125-
.current_transaction_statuses(schema, substrate_hash)
126-
.await;
125+
let block = block_data_cache.current_block(schema, substrate_hash).await;
126+
let statuses = block_data_cache
127+
.current_transaction_statuses(schema, substrate_hash)
128+
.await;
127129

128-
let base_fee = client.runtime_api().gas_price(substrate_hash).ok();
130+
let base_fee = client.runtime_api().gas_price(substrate_hash).ok();
129131

130-
match (block, statuses) {
131-
(Some(block), Some(statuses)) => {
132-
let hash = H256::from(keccak_256(&rlp::encode(&block.header)));
132+
match (block, statuses) {
133+
(Some(block), Some(statuses)) => {
134+
let hash = H256::from(keccak_256(&rlp::encode(&block.header)));
135+
let mut rich_block = rich_block_build(
136+
block,
137+
statuses.into_iter().map(Option::Some).collect(),
138+
Some(hash),
139+
full,
140+
base_fee,
141+
false,
142+
);
133143

134-
let mut rich_block = rich_block_build(
135-
block,
136-
statuses.into_iter().map(Option::Some).collect(),
137-
Some(hash),
138-
full,
139-
base_fee,
140-
);
144+
let substrate_hash = H256::from_slice(substrate_hash.as_ref());
145+
if let Some(parent_hash) = self
146+
.forced_parent_hashes
147+
.as_ref()
148+
.and_then(|parent_hashes| parent_hashes.get(&substrate_hash).cloned())
149+
{
150+
rich_block.inner.header.parent_hash = parent_hash
151+
}
141152

142-
let substrate_hash = H256::from_slice(substrate_hash.as_ref());
143-
if let Some(parent_hash) = self
144-
.forced_parent_hashes
145-
.as_ref()
146-
.and_then(|parent_hashes| parent_hashes.get(&substrate_hash).cloned())
147-
{
148-
rich_block.inner.header.parent_hash = parent_hash
153+
Ok(Some(rich_block))
154+
}
155+
_ => Ok(None),
149156
}
157+
}
158+
None if number == BlockNumber::Pending => {
159+
let api = client.runtime_api();
160+
let best_hash = client.info().best_hash;
150161

151-
Ok(Some(rich_block))
162+
// Get current in-pool transactions
163+
let mut xts: Vec<<B as BlockT>::Extrinsic> = Vec::new();
164+
// ready validated pool
165+
xts.extend(
166+
graph
167+
.validated_pool()
168+
.ready()
169+
.map(|in_pool_tx| in_pool_tx.data().clone())
170+
.collect::<Vec<<B as BlockT>::Extrinsic>>(),
171+
);
172+
173+
// future validated pool
174+
xts.extend(
175+
graph
176+
.validated_pool()
177+
.futures()
178+
.iter()
179+
.map(|(_hash, extrinsic)| extrinsic.clone())
180+
.collect::<Vec<<B as BlockT>::Extrinsic>>(),
181+
);
182+
183+
let (block, statuses) = api
184+
.pending_block(best_hash, xts)
185+
.map_err(|_| internal_err(format!("Runtime access error at {}", best_hash)))?;
186+
187+
let base_fee = api.gas_price(best_hash).ok();
188+
189+
match (block, statuses) {
190+
(Some(block), Some(statuses)) => Ok(Some(rich_block_build(
191+
block,
192+
statuses.into_iter().map(Option::Some).collect(),
193+
None,
194+
full,
195+
base_fee,
196+
true,
197+
))),
198+
_ => Ok(None),
199+
}
152200
}
153-
_ => Ok(None),
201+
None => Ok(None),
154202
}
155203
}
156204

client/rpc/src/eth/mod.rs

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -404,17 +404,26 @@ fn rich_block_build(
404404
hash: Option<H256>,
405405
full_transactions: bool,
406406
base_fee: Option<U256>,
407+
is_pending: bool,
407408
) -> RichBlock {
409+
let (hash, miner, nonce, total_difficulty) = if !is_pending {
410+
(
411+
Some(hash.unwrap_or_else(|| H256::from(keccak_256(&rlp::encode(&block.header))))),
412+
Some(block.header.beneficiary),
413+
Some(block.header.nonce),
414+
Some(U256::zero()),
415+
)
416+
} else {
417+
(None, None, None, None)
418+
};
408419
Rich {
409420
inner: Block {
410421
header: Header {
411-
hash: Some(
412-
hash.unwrap_or_else(|| H256::from(keccak_256(&rlp::encode(&block.header)))),
413-
),
422+
hash,
414423
parent_hash: block.header.parent_hash,
415424
uncles_hash: block.header.ommers_hash,
416425
author: block.header.beneficiary,
417-
miner: block.header.beneficiary,
426+
miner,
418427
state_root: block.header.state_root,
419428
transactions_root: block.header.transactions_root,
420429
receipts_root: block.header.receipts_root,
@@ -425,10 +434,10 @@ fn rich_block_build(
425434
logs_bloom: block.header.logs_bloom,
426435
timestamp: U256::from(block.header.timestamp / 1000),
427436
difficulty: block.header.difficulty,
428-
nonce: Some(block.header.nonce),
437+
nonce,
429438
size: Some(U256::from(rlp::encode(&block.header).len() as u32)),
430439
},
431-
total_difficulty: U256::zero(),
440+
total_difficulty,
432441
uncles: vec![],
433442
transactions: {
434443
if full_transactions {

client/rpc/src/eth_pubsub.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ impl EthSubscriptionResult {
107107
parent_hash: block.header.parent_hash,
108108
uncles_hash: block.header.ommers_hash,
109109
author: block.header.beneficiary,
110-
miner: block.header.beneficiary,
110+
miner: Some(block.header.beneficiary),
111111
state_root: block.header.state_root,
112112
transactions_root: block.header.transactions_root,
113113
receipts_root: block.header.receipts_root,

primitives/rpc/src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,10 @@ sp_api::decl_runtime_apis! {
232232
/// Used to determine if gas limit multiplier for non-transactional calls (eth_call/estimateGas)
233233
/// is supported.
234234
fn gas_limit_multiplier_support();
235+
/// Return the pending block.
236+
fn pending_block(
237+
xts: Vec<<Block as BlockT>::Extrinsic>,
238+
) -> (Option<ethereum::BlockV2>, Option<Vec<TransactionStatus>>);
235239
}
236240

237241
#[api_version(2)]

template/runtime/src/lib.rs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ use frame_support::weights::constants::ParityDbWeight as RuntimeDbWeight;
3535
use frame_support::weights::constants::RocksDbWeight as RuntimeDbWeight;
3636
use frame_support::{
3737
construct_runtime, parameter_types,
38-
traits::{ConstU32, ConstU8, FindAuthor, OnTimestampSet},
38+
traits::{ConstU32, ConstU8, FindAuthor, OnFinalize, OnTimestampSet},
3939
weights::{constants::WEIGHT_REF_TIME_PER_MILLIS, ConstantMultiplier, IdentityFee, Weight},
4040
};
4141
use pallet_grandpa::{
@@ -768,6 +768,21 @@ impl_runtime_apis! {
768768
}
769769

770770
fn gas_limit_multiplier_support() {}
771+
772+
fn pending_block(
773+
xts: Vec<<Block as BlockT>::Extrinsic>,
774+
) -> (Option<pallet_ethereum::Block>, Option<Vec<TransactionStatus>>) {
775+
for ext in xts.into_iter() {
776+
let _ = Executive::apply_extrinsic(ext);
777+
}
778+
779+
Ethereum::on_finalize(System::block_number() + 1);
780+
781+
(
782+
pallet_ethereum::CurrentBlock::<Runtime>::get(),
783+
pallet_ethereum::CurrentTransactionStatuses::<Runtime>::get()
784+
)
785+
}
771786
}
772787

773788
impl fp_rpc::ConvertTransactionRuntimeApi<Block> for Runtime {

ts-tests/tests/test-block.ts

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { expect } from "chai";
22
import { step } from "mocha-steps";
33

4-
import { BLOCK_TIMESTAMP, ETH_BLOCK_GAS_LIMIT } from "./config";
5-
import { createAndFinalizeBlock, describeWithFrontier } from "./util";
4+
import { BLOCK_TIMESTAMP, ETH_BLOCK_GAS_LIMIT, GENESIS_ACCOUNT, GENESIS_ACCOUNT_PRIVATE_KEY } from "./config";
5+
import { createAndFinalizeBlock, describeWithFrontier, customRequest } from "./util";
66

77
describeWithFrontier("Frontier RPC (Block)", (context) => {
88
let previousBlock;
@@ -145,3 +145,53 @@ describeWithFrontier("Frontier RPC (Block)", (context) => {
145145
expect(block.parentHash).to.equal(previousBlock.hash);
146146
});
147147
});
148+
149+
describeWithFrontier("Frontier RPC (Pending Block)", (context) => {
150+
const TEST_ACCOUNT = "0x1111111111111111111111111111111111111111";
151+
152+
it("should return pending block", async function () {
153+
var nonce = 0;
154+
let sendTransaction = async () => {
155+
const tx = await context.web3.eth.accounts.signTransaction(
156+
{
157+
from: GENESIS_ACCOUNT,
158+
to: TEST_ACCOUNT,
159+
value: "0x200", // Must be higher than ExistentialDeposit
160+
gasPrice: "0x3B9ACA00",
161+
gas: "0x100000",
162+
nonce: nonce,
163+
},
164+
GENESIS_ACCOUNT_PRIVATE_KEY
165+
);
166+
nonce = nonce + 1;
167+
return (await customRequest(context.web3, "eth_sendRawTransaction", [tx.rawTransaction])).result;
168+
};
169+
170+
// block 1 send 5 transactions
171+
const expectedXtsNumber = 5;
172+
for (var _ of Array(expectedXtsNumber)) {
173+
await sendTransaction();
174+
}
175+
176+
// test still invalid future transactions can be safely applied (they are applied, just not overlayed)
177+
nonce = nonce + 100;
178+
await sendTransaction();
179+
180+
// do not seal, get pendign block
181+
let pending_transactions = [];
182+
{
183+
const pending = (await customRequest(context.web3, "eth_getBlockByNumber", ["pending", false])).result;
184+
expect(pending.hash).to.be.null;
185+
expect(pending.miner).to.be.null;
186+
expect(pending.nonce).to.be.null;
187+
expect(pending.totalDifficulty).to.be.null;
188+
pending_transactions = pending.transactions;
189+
expect(pending_transactions.length).to.be.eq(expectedXtsNumber);
190+
}
191+
192+
// seal and compare latest blocks transactions with the previously pending
193+
await createAndFinalizeBlock(context.web3);
194+
const latest_block = await context.web3.eth.getBlock("latest", false);
195+
expect(pending_transactions).to.be.deep.eq(latest_block.transactions);
196+
});
197+
});

0 commit comments

Comments
 (0)