Skip to content

Commit

Permalink
feat: add EIP-1898 blockHash option to JSON-RPC methods (#1101)
Browse files Browse the repository at this point in the history
Co-authored-by: liya2017 <32123500+liya2017@users.noreply.github.com>
  • Loading branch information
ahonn and liya2017 authored Mar 22, 2023
1 parent 30c7a2c commit 98a8216
Show file tree
Hide file tree
Showing 8 changed files with 137 additions and 24 deletions.
8 changes: 8 additions & 0 deletions core/api/src/adapter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,14 @@ where
}
}

async fn get_block_number_by_hash(
&self,
ctx: Context,
hash: Hash,
) -> ProtocolResult<Option<BlockNumber>> {
self.storage.get_block_number_by_hash(ctx, &hash).await
}

async fn get_receipt_by_tx_hash(
&self,
ctx: Context,
Expand Down
14 changes: 7 additions & 7 deletions core/api/src/jsonrpc/impl/filter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use protocol::async_trait;
use protocol::tokio::sync::mpsc::{channel, Receiver, Sender};
use protocol::tokio::{self, select, sync::oneshot, time::interval};
use protocol::traits::{APIAdapter, Context};
use protocol::types::{BlockNumber, Hash, Receipt, H160, H256, U256};
use protocol::types::{BlockNumber, Hash, Receipt, H160, H256, U256, U64};

use crate::jsonrpc::web3_types::{BlockId, FilterChanges, RawLoggerFilter, Web3Log};
use crate::jsonrpc::{r#impl::from_receipt_to_web3_log, RpcResult, Web3FilterServer};
Expand Down Expand Up @@ -63,7 +63,7 @@ impl Web3FilterServer for AxonWeb3RpcFilter {
));
}
match filter.to_block {
Some(BlockId::Earliest) | Some(BlockId::Num(0)) => {
Some(BlockId::Earliest) | Some(BlockId::Num(U64([0]))) => {
return Err(Error::Custom("Invalid to_block".to_string()))
}
_ => (),
Expand Down Expand Up @@ -198,11 +198,11 @@ where

match from {
BlockId::Num(n) => {
if n < &header.number {
filter.from_block = Some(BlockId::Num(header.number + 1));
if n.as_u64() < header.number {
filter.from_block = Some(BlockId::Num(U64::from(header.number + 1)));
}
}
_ => filter.from_block = Some(BlockId::Num(header.number + 1)),
_ => filter.from_block = Some(BlockId::Num(U64::from(header.number + 1))),
}

self.logs_hub
Expand Down Expand Up @@ -301,7 +301,7 @@ where
let (start, end) = {
let convert = |id: &BlockId| -> BlockNumber {
match id {
BlockId::Num(n) => *n,
BlockId::Num(n) => n.as_u64(),
BlockId::Earliest => 0,
_ => latest_number,
}
Expand Down Expand Up @@ -376,7 +376,7 @@ where
}

if let Some(BlockId::Num(ref mut n)) = filter.from_block {
*n = end + 1
*n = U64::from(end + 1)
}
*time = Instant::now();
Ok(all_logs)
Expand Down
59 changes: 47 additions & 12 deletions core/api/src/jsonrpc/impl/web3.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,20 @@ impl<Adapter: APIAdapter> Web3RpcImpl<Adapter> {
}
}

async fn get_block_number_by_id(
&self,
block_id: Option<BlockId>,
) -> Result<Option<BlockNumber>, Error> {
match block_id {
Some(BlockId::Hash(ref hash)) => Ok(self
.adapter
.get_block_number_by_hash(Context::new(), *hash)
.await
.map_err(|e| Error::Custom(e.to_string()))?),
_ => Ok(block_id.unwrap_or_default().into()),
}
}

async fn call_evm(
&self,
req: Web3CallRequest,
Expand Down Expand Up @@ -234,9 +248,9 @@ impl<Adapter: APIAdapter + 'static> AxonWeb3RpcServer for Web3RpcImpl<Adapter> {
async fn get_transaction_count(
&self,
address: H160,
number: Option<BlockId>,
block_id: Option<BlockId>,
) -> RpcResult<U256> {
match number.unwrap_or_default() {
match block_id.unwrap_or_default() {
BlockId::Pending => {
let pending_tx_count = self
.adapter
Expand All @@ -250,6 +264,19 @@ impl<Adapter: APIAdapter + 'static> AxonWeb3RpcServer for Web3RpcImpl<Adapter> {
.map(|account| account.nonce + pending_tx_count)
.unwrap_or_default())
}
BlockId::Hash(ref hash) => {
let number = self
.adapter
.get_block_number_by_hash(Context::new(), *hash)
.await
.map_err(|e| Error::Custom(e.to_string()))?;
Ok(self
.adapter
.get_account(Context::new(), address, number)
.await
.map(|account| account.nonce)
.unwrap_or_default())
}
b => Ok(self
.adapter
.get_account(Context::new(), address, b.into())
Expand All @@ -270,16 +297,18 @@ impl<Adapter: APIAdapter + 'static> AxonWeb3RpcServer for Web3RpcImpl<Adapter> {
}

#[metrics_rpc("eth_getBalance")]
async fn get_balance(&self, address: H160, number: Option<BlockId>) -> RpcResult<U256> {
async fn get_balance(&self, address: H160, block_id: Option<BlockId>) -> RpcResult<U256> {
let number = self.get_block_number_by_id(block_id).await?;

Ok(self
.adapter
.get_account(Context::new(), address, number.unwrap_or_default().into())
.get_account(Context::new(), address, number)
.await
.map_or(U256::zero(), |account| account.balance))
}

#[metrics_rpc("eth_call")]
async fn call(&self, req: Web3CallRequest, number: Option<BlockId>) -> RpcResult<Hex> {
async fn call(&self, req: Web3CallRequest, block_id: Option<BlockId>) -> RpcResult<Hex> {
if req.gas_price.unwrap_or_default() > U256::from(u64::MAX) {
return Err(Error::Custom("The gas price is too large".to_string()));
}
Expand All @@ -288,13 +317,15 @@ impl<Adapter: APIAdapter + 'static> AxonWeb3RpcServer for Web3RpcImpl<Adapter> {
return Err(Error::Custom("The gas limit is too large".to_string()));
}

let number = self.get_block_number_by_id(block_id).await?;

let data_bytes = req
.data
.as_ref()
.map(|hex| hex.as_bytes())
.unwrap_or_default();
let resp = self
.call_evm(req, data_bytes, number.unwrap_or_default().into())
.call_evm(req, data_bytes, number)
.await
.map_err(|e| Error::Custom(e.to_string()))?;

Expand All @@ -321,7 +352,7 @@ impl<Adapter: APIAdapter + 'static> AxonWeb3RpcServer for Web3RpcImpl<Adapter> {
}

let num = match number {
Some(BlockId::Num(n)) => Some(n),
Some(BlockId::Num(n)) => Some(n.as_u64()),
_ => None,
};
let data_bytes = req
Expand All @@ -342,10 +373,12 @@ impl<Adapter: APIAdapter + 'static> AxonWeb3RpcServer for Web3RpcImpl<Adapter> {
}

#[metrics_rpc("eth_getCode")]
async fn get_code(&self, address: H160, number: Option<BlockId>) -> RpcResult<Hex> {
async fn get_code(&self, address: H160, block_id: Option<BlockId>) -> RpcResult<Hex> {
let number = self.get_block_number_by_id(block_id).await?;

let account = self
.adapter
.get_account(Context::new(), address, number.unwrap_or_default().into())
.get_account(Context::new(), address, number)
.await
.map_err(|e| Error::Custom(e.to_string()))?;

Expand Down Expand Up @@ -547,7 +580,7 @@ impl<Adapter: APIAdapter + 'static> AxonWeb3RpcServer for Web3RpcImpl<Adapter> {
let (start, end) = {
let convert = |id: BlockId| -> BlockNumber {
match id {
BlockId::Num(n) => n,
BlockId::Num(n) => n.as_u64(),
BlockId::Earliest => 0,
_ => latest_number,
}
Expand Down Expand Up @@ -702,11 +735,13 @@ impl<Adapter: APIAdapter + 'static> AxonWeb3RpcServer for Web3RpcImpl<Adapter> {
&self,
address: H160,
position: U256,
number: Option<BlockId>,
block_id: Option<BlockId>,
) -> RpcResult<Hex> {
let number = self.get_block_number_by_id(block_id).await?;

let block = self
.adapter
.get_block_by_number(Context::new(), number.unwrap_or_default().into())
.get_block_by_number(Context::new(), number)
.await
.map_err(|e| Error::Custom(e.to_string()))?
.ok_or_else(|| Error::Custom("Can't find this block".to_string()))?;
Expand Down
24 changes: 19 additions & 5 deletions core/api/src/jsonrpc/web3_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -338,7 +338,8 @@ pub struct Web3CallRequest {

#[derive(Default, Clone, Debug, PartialEq, Eq, Hash)]
pub enum BlockId {
Num(u64),
Num(U64),
Hash(H256),
#[default]
Latest,
Earliest,
Expand All @@ -348,7 +349,7 @@ pub enum BlockId {
impl From<BlockId> for Option<u64> {
fn from(id: BlockId) -> Self {
match id {
BlockId::Num(num) => Some(num),
BlockId::Num(num) => Some(num.as_u64()),
BlockId::Earliest => Some(0),
_ => None,
}
Expand All @@ -370,7 +371,8 @@ impl Serialize for BlockId {
S: Serializer,
{
match *self {
BlockId::Num(ref x) => serializer.serialize_str(&format!("0x{:x}", x)),
BlockId::Num(ref x) => serializer.serialize_str(&format!("{:x}", x)),
BlockId::Hash(ref hash) => serializer.serialize_str(&format!("{:x}", hash)),
BlockId::Latest => serializer.serialize_str("latest"),
BlockId::Earliest => serializer.serialize_str("earliest"),
BlockId::Pending => serializer.serialize_str("pending"),
Expand All @@ -393,6 +395,7 @@ impl<'a> Visitor<'a> for BlockIdVisitor {
V: MapAccess<'a>,
{
let mut block_number = None;
let mut block_hash = None;

loop {
let key_str: Option<String> = visitor.next_key()?;
Expand All @@ -406,14 +409,21 @@ impl<'a> Visitor<'a> for BlockIdVisitor {
Error::custom(format!("Invalid block number: {}", e))
})?;

block_number = Some(number);
block_number = Some(U64::from(number));
break;
} else {
return Err(Error::custom(
"Invalid block number: missing 0x prefix".to_string(),
));
}
}
"blockHash" => {
let value: String = visitor.next_value()?;
if value.len() != 32 {
return Err(Error::custom(format!("Invalid block hash: {}", value)));
}
block_hash = Some(H256::from_slice(value.as_bytes()))
}
key => return Err(Error::custom(format!("Unknown key: {}", key))),
},
None => break,
Expand All @@ -424,6 +434,10 @@ impl<'a> Visitor<'a> for BlockIdVisitor {
return Ok(BlockId::Num(number));
}

if let Some(hash) = block_hash {
return Ok(BlockId::Hash(hash));
}

Err(Error::custom("Invalid input"))
}

Expand All @@ -436,7 +450,7 @@ impl<'a> Visitor<'a> for BlockIdVisitor {
"earliest" => Ok(BlockId::Earliest),
"pending" => Ok(BlockId::Pending),
_ if value.starts_with("0x") => u64::from_str_radix(&value[2..], 16)
.map(BlockId::Num)
.map(|n| BlockId::Num(U64::from(n)))
.map_err(|e| Error::custom(format!("Invalid block number: {}", e))),
_ => Err(Error::custom(
"Invalid block number: missing 0x prefix".to_string(),
Expand Down
8 changes: 8 additions & 0 deletions core/storage/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,14 @@ impl<Adapter: StorageAdapter> Storage for ImplStorage<Adapter> {
Ok(None)
}

async fn get_block_number_by_hash(
&self,
_: Context,
block_hash: &Hash,
) -> ProtocolResult<Option<u64>> {
self.get_block_number_by_hash(block_hash).await
}

#[trace_span(kind = "storage")]
async fn get_transactions(
&self,
Expand Down
6 changes: 6 additions & 0 deletions protocol/src/traits/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ pub trait APIAdapter: Send + Sync {
height: Option<u64>,
) -> ProtocolResult<Option<Header>>;

async fn get_block_number_by_hash(
&self,
ctx: Context,
hash: Hash,
) -> ProtocolResult<Option<BlockNumber>>;

async fn get_receipt_by_tx_hash(
&self,
ctx: Context,
Expand Down
6 changes: 6 additions & 0 deletions protocol/src/traits/storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,12 @@ pub trait Storage: CommonStorage {
block_hash: &Hash,
) -> ProtocolResult<Option<Block>>;

async fn get_block_number_by_hash(
&self,
ctx: Context,
hash: &Hash,
) -> ProtocolResult<Option<u64>>;

async fn get_transactions(
&self,
ctx: Context,
Expand Down
36 changes: 36 additions & 0 deletions tests/e2e/src/eth_getStorageAt.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -151,4 +151,40 @@ describe("eth_getStorageAt", () => {
await param3.type(`0x${testDataInfo.blockNumber.toString(16)}`);
await goto.check(page, "0x0000000000000000000000000000000000000000000000000000000000000012");
});

/**
* param1: real address
* param2: 0x0
* param3: real block number
*/
it("eth_getStorageAt_10", async () => {
const testType = await page.$(goto.pageIds.testTypeId);
const param1 = await page.$(goto.pageIds.param1Id);
const param2 = await page.$(goto.pageIds.param2Id);
const param3 = await page.$(goto.pageIds.param3Id);
await testType.type("1"); // 0: none params 1: common params to request 2: more params
await param1.type(testDataInfo.contractAddress);
await param2.type("0x02");
await param3.type(`{ "blockNumber": "0x${testDataInfo.blockNumber.toString(16)}" }`);
// await goto.check(page, "0x0422ca8b0a00a425000000");
await goto.check(page, "0x");
});

/**
* param1: real address
* param2: 0x0
* param3: real block hash
*/
it("eth_getStorageAt_11", async () => {
const testType = await page.$(goto.pageIds.testTypeId);
const param1 = await page.$(goto.pageIds.param1Id);
const param2 = await page.$(goto.pageIds.param2Id);
const param3 = await page.$(goto.pageIds.param3Id);
await testType.type("1"); // 0: none params 1: common params to request 2: more params
await param1.type(testDataInfo.contractAddress);
await param2.type("0x02");
await param3.type(`{ "blockHash": "${testDataInfo.blockHash}" }`);
// await goto.check(page, "0x0422ca8b0a00a425000000");
await goto.check(page, "0x");
});
});

0 comments on commit 98a8216

Please sign in to comment.