diff --git a/crates/rpc/rpc-builder/tests/it/http.rs b/crates/rpc/rpc-builder/tests/it/http.rs index c709f9d079..10b2d44ca7 100644 --- a/crates/rpc/rpc-builder/tests/it/http.rs +++ b/crates/rpc/rpc-builder/tests/it/http.rs @@ -73,19 +73,11 @@ where EthApiClient::block_transaction_count_by_hash(client, hash).await.unwrap(); EthApiClient::block_uncles_count_by_hash(client, hash).await.unwrap(); EthApiClient::block_uncles_count_by_number(client, block_number).await.unwrap(); - + EthApiClient::uncle_by_block_hash_and_index(client, hash, index).await.unwrap(); + EthApiClient::uncle_by_block_number_and_index(client, block_number, index).await.unwrap(); // Unimplemented assert!(is_unimplemented(EthApiClient::syncing(client).await.err().unwrap())); assert!(is_unimplemented(EthApiClient::author(client).await.err().unwrap())); - assert!(is_unimplemented( - EthApiClient::uncle_by_block_hash_and_index(client, hash, index).await.err().unwrap() - )); - assert!(is_unimplemented( - EthApiClient::uncle_by_block_number_and_index(client, block_number, index) - .await - .err() - .unwrap() - )); assert!(is_unimplemented(EthApiClient::transaction_by_hash(client, hash).await.err().unwrap())); assert!(is_unimplemented( EthApiClient::transaction_by_block_hash_and_index(client, hash, index).await.err().unwrap() diff --git a/crates/rpc/rpc-types/src/eth/block.rs b/crates/rpc/rpc-types/src/eth/block.rs index 5b6c563ffc..aa74d1a63e 100644 --- a/crates/rpc/rpc-types/src/eth/block.rs +++ b/crates/rpc/rpc-types/src/eth/block.rs @@ -8,7 +8,8 @@ use reth_rlp::Encodable; use serde::{ser::Error, Deserialize, Serialize, Serializer}; use std::{collections::BTreeMap, ops::Deref}; -/// Block Transactions depending on the boolean attribute of `eth_getBlockBy*` +/// Block Transactions depending on the boolean attribute of `eth_getBlockBy*`, +/// or if used by `eth_getUncle*` #[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] #[serde(untagged)] pub enum BlockTransactions { @@ -16,8 +17,16 @@ pub enum BlockTransactions { Hashes(Vec), /// Full transactions Full(Vec), + /// Special case for uncle response. + Uncle, +} +impl BlockTransactions { + /// Check if the enum variant is + /// used for an uncle response. + pub fn is_uncle(&self) -> bool { + matches!(self, Self::Uncle) + } } - /// Determines how the `transactions` field of [Block] should be filled. /// /// This essentially represents the `full:bool` argument in RPC calls that determine whether the @@ -55,11 +64,14 @@ pub struct Block { /// Header of the block #[serde(flatten)] pub header: Header, - /// Total difficulty - pub total_difficulty: U256, + /// Total difficulty, this field is None only if representing + /// an Uncle block. + #[serde(skip_serializing_if = "Option::is_none")] + pub total_difficulty: Option, /// Uncles' hashes pub uncles: Vec, /// Transactions + #[serde(skip_serializing_if = "BlockTransactions::is_uncle")] pub transactions: BlockTransactions, /// Integer the size of this block in bytes. pub size: Option, @@ -67,6 +79,7 @@ pub struct Block { #[serde(skip_serializing_if = "Option::is_none")] pub base_fee_per_gas: Option, /// Withdrawals + #[serde(skip_serializing_if = "Option::is_none")] pub withdrawals: Option>, } @@ -160,11 +173,29 @@ impl Block { uncles, transactions, base_fee_per_gas: base_fee_per_gas.map(U256::from), - total_difficulty, + total_difficulty: Some(total_difficulty), size: Some(U256::from(block_length)), withdrawals: block.withdrawals, } } + + /// Build an RPC block response representing + /// an Uncle from its header. + pub fn uncle_block_from_header(header: PrimitiveHeader) -> Self { + let hash = header.hash_slow(); + let rpc_header = Header::from_primitive_with_hash(header.clone(), hash); + let uncle_block = PrimitiveBlock { header, ..Default::default() }; + let size = Some(U256::from(uncle_block.length())); + Self { + uncles: vec![], + header: rpc_header, + transactions: BlockTransactions::Uncle, + base_fee_per_gas: None, + withdrawals: None, + size, + total_difficulty: None, + } + } } /// Block header representation. @@ -189,6 +220,7 @@ pub struct Header { /// Transactions receipts root hash pub receipts_root: H256, /// Withdrawals root hash + #[serde(skip_serializing_if = "Option::is_none")] pub withdrawals_root: Option, /// Block number pub number: Option, @@ -354,7 +386,7 @@ mod tests { mix_hash: H256::from_low_u64_be(14), nonce: Some(H64::from_low_u64_be(15)), }, - total_difficulty: U256::from(100000), + total_difficulty: Some(U256::from(100000)), uncles: vec![H256::from_low_u64_be(17)], transactions: BlockTransactions::Hashes(vec![H256::from_low_u64_be(18)]), size: Some(U256::from(19)), @@ -364,7 +396,7 @@ mod tests { let serialized = serde_json::to_string(&block).unwrap(); assert_eq!( serialized, - r#"{"hash":"0x0000000000000000000000000000000000000000000000000000000000000001","parentHash":"0x0000000000000000000000000000000000000000000000000000000000000002","sha3Uncles":"0x0000000000000000000000000000000000000000000000000000000000000003","author":"0x0000000000000000000000000000000000000004","miner":"0x0000000000000000000000000000000000000004","stateRoot":"0x0000000000000000000000000000000000000000000000000000000000000005","transactionsRoot":"0x0000000000000000000000000000000000000000000000000000000000000006","receiptsRoot":"0x0000000000000000000000000000000000000000000000000000000000000007","withdrawalsRoot":"0x0000000000000000000000000000000000000000000000000000000000000008","number":"0x9","gasUsed":"0xa","gasLimit":"0xb","extraData":"0x010203","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","timestamp":"0xc","difficulty":"0xd","mixHash":"0x000000000000000000000000000000000000000000000000000000000000000e","nonce":"0x000000000000000f","totalDifficulty":"0x186a0","uncles":["0x0000000000000000000000000000000000000000000000000000000000000011"],"transactions":["0x0000000000000000000000000000000000000000000000000000000000000012"],"size":"0x13","baseFeePerGas":"0x14","withdrawals":null}"# + r#"{"hash":"0x0000000000000000000000000000000000000000000000000000000000000001","parentHash":"0x0000000000000000000000000000000000000000000000000000000000000002","sha3Uncles":"0x0000000000000000000000000000000000000000000000000000000000000003","author":"0x0000000000000000000000000000000000000004","miner":"0x0000000000000000000000000000000000000004","stateRoot":"0x0000000000000000000000000000000000000000000000000000000000000005","transactionsRoot":"0x0000000000000000000000000000000000000000000000000000000000000006","receiptsRoot":"0x0000000000000000000000000000000000000000000000000000000000000007","withdrawalsRoot":"0x0000000000000000000000000000000000000000000000000000000000000008","number":"0x9","gasUsed":"0xa","gasLimit":"0xb","extraData":"0x010203","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","timestamp":"0xc","difficulty":"0xd","mixHash":"0x000000000000000000000000000000000000000000000000000000000000000e","nonce":"0x000000000000000f","totalDifficulty":"0x186a0","uncles":["0x0000000000000000000000000000000000000000000000000000000000000011"],"transactions":["0x0000000000000000000000000000000000000000000000000000000000000012"],"size":"0x13","baseFeePerGas":"0x14"}"# ); let deserialized: Block = serde_json::from_str(&serialized).unwrap(); assert_eq!(block, deserialized); diff --git a/crates/rpc/rpc/src/eth/api/block.rs b/crates/rpc/rpc/src/eth/api/block.rs index 158ba31d56..30dd290f46 100644 --- a/crates/rpc/rpc/src/eth/api/block.rs +++ b/crates/rpc/rpc/src/eth/api/block.rs @@ -6,7 +6,7 @@ use crate::{ }; use reth_primitives::BlockId; use reth_provider::{BlockProvider, EvmEnvProvider, StateProviderFactory}; -use reth_rpc_types::{Block, RichBlock}; +use reth_rpc_types::{Block, Index, RichBlock}; impl EthApi where @@ -23,6 +23,20 @@ where Ok(self.client().ommers(block_id)?) } + pub(crate) async fn ommer_by_block_and_index( + &self, + block_id: impl Into, + index: Index, + ) -> EthResult> { + let block_id = block_id.into(); + let index = usize::from(index); + let uncles = self.client().ommers(block_id)?.unwrap_or_default(); + let uncle = uncles + .into_iter() + .nth(index) + .map(|header| Block::uncle_block_from_header(header).into()); + Ok(uncle) + } pub(crate) async fn block_transaction_count( &self, block_id: impl Into, diff --git a/crates/rpc/rpc/src/eth/api/server.rs b/crates/rpc/rpc/src/eth/api/server.rs index 471ff2eb16..b5ce8bdb97 100644 --- a/crates/rpc/rpc/src/eth/api/server.rs +++ b/crates/rpc/rpc/src/eth/api/server.rs @@ -102,19 +102,19 @@ where /// Handler for: `eth_getUncleByBlockHashAndIndex` async fn uncle_by_block_hash_and_index( &self, - _hash: H256, - _index: Index, + hash: H256, + index: Index, ) -> Result> { - Err(internal_rpc_err("unimplemented")) + Ok(EthApi::ommer_by_block_and_index(self, hash, index).await?) } /// Handler for: `eth_getUncleByBlockNumberAndIndex` async fn uncle_by_block_number_and_index( &self, - _number: BlockNumberOrTag, - _index: Index, + number: BlockNumberOrTag, + index: Index, ) -> Result> { - Err(internal_rpc_err("unimplemented")) + Ok(EthApi::ommer_by_block_and_index(self, number, index).await?) } /// Handler for: `eth_getTransactionByHash`