Skip to content
Open
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
8 changes: 7 additions & 1 deletion crates/common/types/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,8 +124,13 @@ pub struct BlockHeader {
pub prev_randao: H256,
#[serde(with = "crate::serde_utils::u64::hex_str_padding")]
pub nonce: u64,
#[serde(default, with = "crate::serde_utils::u64::hex_str_opt")]
#[serde(
skip_serializing_if = "Option::is_none",
with = "crate::serde_utils::u64::hex_str_opt",
default = "Option::default"
)]
pub base_fee_per_gas: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none", default = "Option::default")]
#[rkyv(with=crate::rkyv_utils::OptionH256Wrapper)]
pub withdrawals_root: Option<H256>,
#[serde(
Expand All @@ -140,6 +145,7 @@ pub struct BlockHeader {
default = "Option::default"
)]
pub excess_blob_gas: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none", default = "Option::default")]
#[rkyv(with=crate::rkyv_utils::OptionH256Wrapper)]
pub parent_beacon_block_root: Option<H256>,
#[serde(skip_serializing_if = "Option::is_none", default = "Option::default")]
Expand Down
71 changes: 69 additions & 2 deletions crates/networking/rpc/eth/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ use tracing::debug;
use crate::{
rpc::{RpcApiContext, RpcHandler},
types::{
block::RpcBlock,
block_identifier::{BlockIdentifier, BlockIdentifierOrHash},
block::{RpcBlock, RpcHeader},
block_identifier::{BlockIdentifier, BlockIdentifierOrHash, BlockTag},
receipt::{RpcReceipt, RpcReceiptBlockInfo, RpcReceiptTxInfo},
},
utils::RpcErr,
Expand All @@ -26,6 +26,14 @@ pub struct GetBlockByHashRequest {
pub hydrated: bool,
}

pub struct GetHeaderByNumberRequest {
pub block: BlockIdentifier,
}

pub struct GetHeaderByHashRequest {
pub block: BlockHash,
}

pub struct GetBlockTransactionCountRequest {
pub block: BlockIdentifierOrHash,
}
Expand Down Expand Up @@ -113,6 +121,65 @@ impl RpcHandler for GetBlockByHashRequest {
}
}

impl RpcHandler for GetHeaderByNumberRequest {
fn parse(params: &Option<Vec<Value>>) -> Result<GetHeaderByNumberRequest, RpcErr> {
let params = params
.as_ref()
.ok_or(RpcErr::BadParams("No params provided".to_owned()))?;
if params.len() != 1 {
return Err(RpcErr::BadParams("Expected 1 param".to_owned()));
};
Ok(GetHeaderByNumberRequest {
block: BlockIdentifier::parse(params[0].clone(), 0)?,
})
}
async fn handle(&self, context: RpcApiContext) -> Result<Value, RpcErr> {
let storage = &context.storage;
debug!("Requested header with number: {}", self.block);
// Per the spec, the pending tag returns null instead of aliasing latest.
if matches!(self.block, BlockIdentifier::Tag(BlockTag::Pending)) {
return Ok(Value::Null);
}
let block_number = match self.block.resolve_block_number(storage).await? {
Some(block_number) => block_number,
_ => return Ok(Value::Null),
};
let header = match storage.get_block_header(block_number)? {
Some(header) => header,
_ => return Ok(Value::Null),
};
let hash = header.hash();
serde_json::to_value(RpcHeader { hash, header })
.map_err(|error| RpcErr::Internal(error.to_string()))
}
}

impl RpcHandler for GetHeaderByHashRequest {
fn parse(params: &Option<Vec<Value>>) -> Result<GetHeaderByHashRequest, RpcErr> {
let params = params
.as_ref()
.ok_or(RpcErr::BadParams("No params provided".to_owned()))?;
if params.len() != 1 {
return Err(RpcErr::BadParams("Expected 1 param".to_owned()));
};
Ok(GetHeaderByHashRequest {
block: serde_json::from_value(params[0].clone())?,
})
}
async fn handle(&self, context: RpcApiContext) -> Result<Value, RpcErr> {
let storage = &context.storage;
debug!("Requested header with hash: {:#x}", self.block);
let Some(header) = storage.get_block_header_by_hash(self.block)? else {
return Ok(Value::Null);
};
serde_json::to_value(RpcHeader {
hash: self.block,
header,
})
.map_err(|error| RpcErr::Internal(error.to_string()))
}
}

impl RpcHandler for GetBlockTransactionCountRequest {
fn parse(params: &Option<Vec<Value>>) -> Result<GetBlockTransactionCountRequest, RpcErr> {
let params = params
Expand Down
51 changes: 49 additions & 2 deletions crates/networking/rpc/rpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,9 @@ use crate::eth::{
},
block::{
BlockNumberRequest, GetBlobBaseFee, GetBlockByHashRequest, GetBlockByNumberRequest,
GetBlockReceiptsRequest, GetBlockTransactionCountRequest, GetRawBlockRequest,
GetRawHeaderRequest, GetRawReceipts, GetUncleCountRequest,
GetBlockReceiptsRequest, GetBlockTransactionCountRequest, GetHeaderByHashRequest,
GetHeaderByNumberRequest, GetRawBlockRequest, GetRawHeaderRequest, GetRawReceipts,
GetUncleCountRequest,
},
block_access_list::{BlockAccessListRequest, RawBlockAccessListRequest},
client::{ChainId, Syncing},
Expand Down Expand Up @@ -1399,6 +1400,8 @@ pub async fn map_eth_requests(req: &RpcRequest, context: RpcApiContext) -> Resul
"eth_syncing" => Syncing::call(req, context).await,
"eth_getBlockByNumber" => GetBlockByNumberRequest::call(req, context).await,
"eth_getBlockByHash" => GetBlockByHashRequest::call(req, context).await,
"eth_getHeaderByNumber" => GetHeaderByNumberRequest::call(req, context).await,
"eth_getHeaderByHash" => GetHeaderByHashRequest::call(req, context).await,
"eth_getBalance" => GetBalanceRequest::call(req, context).await,
"eth_getCode" => GetCodeRequest::call(req, context).await,
"eth_getStorageAt" => GetStorageAtRequest::call(req, context).await,
Expand Down Expand Up @@ -1758,6 +1761,50 @@ mod tests {
}
}

/// eth_getHeaderBy* returns the header with its hash and no size or body
/// fields, and null for unknown blocks and the pending tag.
#[tokio::test]
async fn eth_get_header_by_number_and_hash() {
let storage = crate::test_utils::setup_store().await;
crate::test_utils::add_empty_blocks(&storage, 2).await;
let context = default_context_with_storage(storage).await;

let request: RpcRequest = serde_json::from_str(
r#"{"jsonrpc":"2.0","method":"eth_getHeaderByNumber","params":["latest"],"id":1}"#,
)
.unwrap();
let header = map_http_requests(&request, context.clone()).await.unwrap();
assert_eq!(header["number"], "0x2");
assert!(header.get("hash").is_some());
assert!(header.get("size").is_none());
assert!(header.get("transactions").is_none());
assert!(header.get("uncles").is_none());

let hash = header["hash"].as_str().unwrap().to_owned();
let request: RpcRequest = serde_json::from_str(&format!(
r#"{{"jsonrpc":"2.0","method":"eth_getHeaderByHash","params":["{hash}"],"id":1}}"#
))
.unwrap();
let by_hash = map_http_requests(&request, context.clone()).await.unwrap();
assert_eq!(by_hash, header);

for (method, param) in [
("eth_getHeaderByNumber", r#""0x3e8""#),
("eth_getHeaderByNumber", r#""pending""#),
(
"eth_getHeaderByHash",
r#""0x0000000000000000000000000000000000000000000000000000000000000000""#,
),
] {
let request: RpcRequest = serde_json::from_str(&format!(
r#"{{"jsonrpc":"2.0","method":"{method}","params":[{param}],"id":1}}"#
))
.unwrap();
let result = map_http_requests(&request, context.clone()).await.unwrap();
assert_eq!(result, Value::Null, "{method} {param}");
}
}

/// WebSocket subscriptions live in the `eth` namespace and must obey the
/// same `--http.api` allowlist as regular `eth_*` requests. A node started
/// without `eth` in the allowlist must not serve `eth_subscribe` over WS.
Expand Down
8 changes: 8 additions & 0 deletions crates/networking/rpc/types/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,14 @@ pub struct RpcBlock {
pub body: BlockBodyWrapper,
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct RpcHeader {
pub hash: H256,
#[serde(flatten)]
pub header: BlockHeader,
}

#[derive(Debug, Serialize, Deserialize)]
#[serde(untagged)]
pub enum BlockBodyWrapper {
Expand Down
Loading