From 76e751eef4fb9cd16a81e088ebc2df123911c7dd Mon Sep 17 00:00:00 2001 From: evalir Date: Sat, 28 Oct 2023 17:00:47 +0900 Subject: [PATCH] feat: Completely decouple `rpc-types` to standalone crate (#5193) Co-authored-by: Matthias Seitz --- Cargo.lock | 11 +- crates/interfaces/Cargo.toml | 1 + crates/payload/builder/src/payload.rs | 8 +- crates/primitives/Cargo.toml | 3 +- crates/primitives/src/block.rs | 533 +--------------- crates/primitives/src/lib.rs | 2 +- crates/primitives/src/net.rs | 170 +----- crates/primitives/src/peer.rs | 9 +- crates/rpc/rpc-engine-api/src/engine_api.rs | 5 +- crates/rpc/rpc-types-compat/src/block.rs | 79 ++- .../rpc-types-compat/src/engine/payload.rs | 41 ++ crates/rpc/rpc-types-compat/src/log.rs | 23 + crates/rpc/rpc-types-compat/src/proof.rs | 3 +- .../rpc-types-compat/src/transaction/mod.rs | 36 +- .../rpc-types-compat/src/transaction/typed.rs | 70 +++ crates/rpc/rpc-types/Cargo.toml | 17 +- crates/rpc/rpc-types/src/admin.rs | 4 +- crates/rpc/rpc-types/src/eth/account.rs | 2 +- crates/rpc/rpc-types/src/eth/block.rs | 576 ++++++++++++++++-- crates/rpc/rpc-types/src/eth/call.rs | 3 +- .../rpc/rpc-types/src/eth/engine/payload.rs | 37 +- crates/rpc/rpc-types/src/eth/filter.rs | 5 +- crates/rpc/rpc-types/src/eth/mod.rs | 2 + crates/rpc/rpc-types/src/eth/raw_log.rs | 30 + crates/rpc/rpc-types/src/eth/trace/filter.rs | 2 +- .../rpc/rpc-types/src/eth/trace/geth/call.rs | 2 +- .../rpc/rpc-types/src/eth/trace/geth/mod.rs | 2 +- .../rpc-types/src/eth/trace/geth/pre_state.rs | 2 +- .../rpc-types/src/eth/trace/tracerequest.rs | 6 +- crates/rpc/rpc-types/src/eth/tracerequest.rs | 37 -- .../src/eth/transaction/access_list.rs | 2 +- .../rpc-types/src/eth/transaction/request.rs | 10 +- .../rpc-types/src/eth/transaction/typed.rs | 78 +-- crates/rpc/rpc-types/src/eth/withdrawal.rs | 19 +- crates/rpc/rpc-types/src/lib.rs | 6 + crates/rpc/rpc-types/src/mev.rs | 2 +- crates/rpc/rpc-types/src/net.rs | 300 +++++++++ crates/rpc/rpc-types/src/peer.rs | 4 + .../rpc-types/src/serde_helpers/json_u256.rs | 93 +++ crates/rpc/rpc-types/src/serde_helpers/mod.rs | 30 + crates/rpc/rpc-types/src/serde_helpers/num.rs | 139 +++++ .../rpc-types/src/serde_helpers/storage.rs | 102 ++++ .../rpc-types/src/serde_helpers/u64_hex.rs | 18 + crates/rpc/rpc/src/debug.rs | 3 +- crates/rpc/rpc/src/eth/api/call.rs | 8 +- crates/rpc/rpc/src/eth/bundle.rs | 5 +- crates/rpc/rpc/src/eth/logs_utils.rs | 11 +- crates/rpc/rpc/src/eth/pubsub.rs | 4 +- crates/rpc/rpc/src/eth/revm_utils.rs | 6 +- crates/rpc/rpc/src/eth/signer.rs | 4 +- crates/rpc/rpc/src/trace.rs | 4 +- examples/Cargo.toml | 1 + examples/db-access.rs | 4 +- 53 files changed, 1617 insertions(+), 957 deletions(-) create mode 100644 crates/rpc/rpc-types-compat/src/transaction/typed.rs create mode 100644 crates/rpc/rpc-types/src/eth/raw_log.rs delete mode 100644 crates/rpc/rpc-types/src/eth/tracerequest.rs create mode 100644 crates/rpc/rpc-types/src/net.rs create mode 100644 crates/rpc/rpc-types/src/peer.rs create mode 100644 crates/rpc/rpc-types/src/serde_helpers/json_u256.rs create mode 100644 crates/rpc/rpc-types/src/serde_helpers/mod.rs create mode 100644 crates/rpc/rpc-types/src/serde_helpers/num.rs create mode 100644 crates/rpc/rpc-types/src/serde_helpers/storage.rs create mode 100644 crates/rpc/rpc-types/src/serde_helpers/u64_hex.rs diff --git a/Cargo.lock b/Cargo.lock index 8b683aa07f..fda101b2b2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2618,6 +2618,7 @@ dependencies = [ "reth-revm", "reth-rpc-builder", "reth-rpc-types", + "reth-rpc-types-compat", "reth-tasks", "reth-transaction-pool", "tokio", @@ -6019,6 +6020,7 @@ dependencies = [ "reth-nippy-jar", "reth-primitives", "reth-rpc-types", + "reth-rpc-types-compat", "revm-primitives", "secp256k1 0.27.0", "thiserror", @@ -6262,6 +6264,7 @@ dependencies = [ "rayon", "reth-codecs", "reth-primitives", + "reth-rpc-types", "revm", "revm-primitives", "secp256k1 0.27.0", @@ -6498,15 +6501,21 @@ version = "0.1.0-alpha.10" dependencies = [ "alloy-primitives", "alloy-rlp", + "arbitrary", + "bytes", + "c-kzg", "itertools 0.11.0", "jsonrpsee-types", + "proptest", + "proptest-derive", "rand 0.8.5", - "reth-primitives", + "secp256k1 0.27.0", "serde", "serde_json", "serde_with", "similar-asserts", "thiserror", + "url", ] [[package]] diff --git a/crates/interfaces/Cargo.toml b/crates/interfaces/Cargo.toml index 75e304f78d..2c767d3c9f 100644 --- a/crates/interfaces/Cargo.toml +++ b/crates/interfaces/Cargo.toml @@ -12,6 +12,7 @@ reth-codecs = { path = "../storage/codecs" } reth-nippy-jar = { path = "../storage/nippy-jar" } reth-primitives.workspace = true reth-rpc-types.workspace = true +reth-rpc-types-compat.workspace = true reth-network-api.workspace = true # TODO(onbjerg): We only need this for [BlockBody] reth-eth-wire = { path = "../net/eth-wire" } diff --git a/crates/payload/builder/src/payload.rs b/crates/payload/builder/src/payload.rs index f3d0f1f99c..e0142091a9 100644 --- a/crates/payload/builder/src/payload.rs +++ b/crates/payload/builder/src/payload.rs @@ -11,7 +11,7 @@ use reth_rpc_types::engine::{ }; use reth_rpc_types_compat::engine::payload::{ block_to_payload_v3, convert_block_to_payload_field_v2, - convert_standalone_withdraw_to_withdrawal, try_block_to_payload_v1, + convert_standalone_withdraw_to_withdrawal, from_primitive_sidecar, try_block_to_payload_v1, }; use revm_primitives::{BlobExcessGasAndPrice, BlockEnv, CfgEnv, SpecId}; /// Contains the built payload. @@ -111,7 +111,11 @@ impl From for ExecutionPayloadEnvelopeV3 { // Spec: // should_override_builder: false, - blobs_bundle: sidecars.into(), + blobs_bundle: sidecars + .into_iter() + .map(from_primitive_sidecar) + .collect::>() + .into(), } } } diff --git a/crates/primitives/Cargo.toml b/crates/primitives/Cargo.toml index 2f290cefdb..0e0e82d085 100644 --- a/crates/primitives/Cargo.toml +++ b/crates/primitives/Cargo.toml @@ -11,6 +11,7 @@ description = "Commonly used types in reth." [dependencies] # reth reth-codecs = { path = "../storage/codecs" } +reth-rpc-types.workspace = true revm-primitives = { workspace = true, features = ["serde"] } # ethereum @@ -89,7 +90,7 @@ pprof = { version = "0.12", features = ["flamegraph", "frame-pointer", "criterio [features] default = [] -arbitrary = ["revm-primitives/arbitrary", "dep:arbitrary", "dep:proptest", "dep:proptest-derive"] +arbitrary = ["revm-primitives/arbitrary", "reth-rpc-types/arbitrary", "dep:arbitrary", "dep:proptest", "dep:proptest-derive"] test-utils = ["dep:plain_hasher", "dep:hash-db", "dep:ethers-core"] # value-256 controls whether transaction Value fields are DB-encoded as 256 bits instead of the # default of 128 bits. diff --git a/crates/primitives/src/block.rs b/crates/primitives/src/block.rs index 3bd3b53023..9e7fb81bdf 100644 --- a/crates/primitives/src/block.rs +++ b/crates/primitives/src/block.rs @@ -1,14 +1,12 @@ -use crate::{ - Address, BlockHash, BlockNumber, Header, SealedHeader, TransactionSigned, Withdrawal, B256, U64, -}; -use alloy_rlp::{Decodable, Encodable, Error as RlpError, RlpDecodable, RlpEncodable}; +use crate::{Address, Header, SealedHeader, TransactionSigned, Withdrawal, B256}; +use alloy_rlp::{RlpDecodable, RlpEncodable}; use reth_codecs::derive_arbitrary; -use serde::{ - de::{MapAccess, Visitor}, - ser::SerializeStruct, - Deserialize, Deserializer, Serialize, Serializer, +use serde::{Deserialize, Serialize}; +use std::ops::Deref; + +pub use reth_rpc_types::{ + BlockHashOrNumber, BlockId, BlockNumHash, BlockNumberOrTag, ForkBlock, RpcBlockHash, }; -use std::{fmt, fmt::Formatter, num::ParseIntError, ops::Deref, str::FromStr}; /// Ethereum full block. /// @@ -269,520 +267,6 @@ impl std::ops::DerefMut for SealedBlockWithSenders { } } -/// Either a block hash _or_ a block number -#[derive_arbitrary(rlp)] -#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] -pub enum BlockHashOrNumber { - /// A block hash - Hash(B256), - /// A block number - Number(u64), -} - -// === impl BlockHashOrNumber === - -impl BlockHashOrNumber { - /// Returns the block number if it is a [`BlockHashOrNumber::Number`]. - #[inline] - pub fn as_number(self) -> Option { - match self { - BlockHashOrNumber::Hash(_) => None, - BlockHashOrNumber::Number(num) => Some(num), - } - } -} - -impl From for BlockHashOrNumber { - fn from(value: B256) -> Self { - BlockHashOrNumber::Hash(value) - } -} - -impl From for BlockHashOrNumber { - fn from(value: u64) -> Self { - BlockHashOrNumber::Number(value) - } -} - -/// Allows for RLP encoding of either a block hash or block number -impl Encodable for BlockHashOrNumber { - fn encode(&self, out: &mut dyn bytes::BufMut) { - match self { - Self::Hash(block_hash) => block_hash.encode(out), - Self::Number(block_number) => block_number.encode(out), - } - } - fn length(&self) -> usize { - match self { - Self::Hash(block_hash) => block_hash.length(), - Self::Number(block_number) => block_number.length(), - } - } -} - -/// Allows for RLP decoding of a block hash or block number -impl Decodable for BlockHashOrNumber { - fn decode(buf: &mut &[u8]) -> alloy_rlp::Result { - let header: u8 = *buf.first().ok_or(RlpError::InputTooShort)?; - // if the byte string is exactly 32 bytes, decode it into a Hash - // 0xa0 = 0x80 (start of string) + 0x20 (32, length of string) - if header == 0xa0 { - // strip the first byte, parsing the rest of the string. - // If the rest of the string fails to decode into 32 bytes, we'll bubble up the - // decoding error. - let hash = B256::decode(buf)?; - Ok(Self::Hash(hash)) - } else { - // a block number when encoded as bytes ranges from 0 to any number of bytes - we're - // going to accept numbers which fit in less than 64 bytes. - // Any data larger than this which is not caught by the Hash decoding should error and - // is considered an invalid block number. - Ok(Self::Number(u64::decode(buf)?)) - } - } -} - -#[derive(Debug, thiserror::Error)] -#[error("failed to parse {input:?} as a number: {parse_int_error} or hash: {hex_error}")] -pub struct ParseBlockHashOrNumberError { - input: String, - parse_int_error: ParseIntError, - hex_error: crate::hex::FromHexError, -} - -impl FromStr for BlockHashOrNumber { - type Err = ParseBlockHashOrNumberError; - - fn from_str(s: &str) -> Result { - match u64::from_str(s) { - Ok(val) => Ok(val.into()), - Err(pares_int_error) => match B256::from_str(s) { - Ok(val) => Ok(val.into()), - Err(hex_error) => Err(ParseBlockHashOrNumberError { - input: s.to_string(), - parse_int_error: pares_int_error, - hex_error, - }), - }, - } - } -} - -/// A Block Identifier -/// -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub enum BlockId { - /// A block hash and an optional bool that defines if it's canonical - Hash(RpcBlockHash), - /// A block number - Number(BlockNumberOrTag), -} - -// === impl BlockId === - -impl BlockId { - /// Returns the block hash if it is [BlockId::Hash] - pub fn as_block_hash(&self) -> Option { - match self { - BlockId::Hash(hash) => Some(hash.block_hash), - BlockId::Number(_) => None, - } - } - - /// Returns true if this is [BlockNumberOrTag::Latest] - pub fn is_latest(&self) -> bool { - matches!(self, BlockId::Number(BlockNumberOrTag::Latest)) - } - - /// Returns true if this is [BlockNumberOrTag::Pending] - pub fn is_pending(&self) -> bool { - matches!(self, BlockId::Number(BlockNumberOrTag::Pending)) - } -} - -impl From for BlockId { - fn from(num: u64) -> Self { - BlockNumberOrTag::Number(num).into() - } -} - -impl From for BlockId { - fn from(num: BlockNumberOrTag) -> Self { - BlockId::Number(num) - } -} - -impl From for BlockId { - fn from(block_hash: B256) -> Self { - BlockId::Hash(RpcBlockHash { block_hash, require_canonical: None }) - } -} - -impl From<(B256, Option)> for BlockId { - fn from(hash_can: (B256, Option)) -> Self { - BlockId::Hash(RpcBlockHash { block_hash: hash_can.0, require_canonical: hash_can.1 }) - } -} - -impl From for BlockId { - fn from(hash_can: RpcBlockHash) -> Self { - BlockId::Hash(hash_can) - } -} - -impl From for BlockId { - fn from(value: BlockHashOrNumber) -> Self { - match value { - BlockHashOrNumber::Hash(hash) => B256::from(hash.0).into(), - BlockHashOrNumber::Number(number) => number.into(), - } - } -} - -impl Serialize for BlockId { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - match *self { - BlockId::Hash(RpcBlockHash { ref block_hash, ref require_canonical }) => { - let mut s = serializer.serialize_struct("BlockIdEip1898", 1)?; - s.serialize_field("blockHash", block_hash)?; - if let Some(require_canonical) = require_canonical { - s.serialize_field("requireCanonical", require_canonical)?; - } - s.end() - } - BlockId::Number(ref num) => num.serialize(serializer), - } - } -} - -impl<'de> Deserialize<'de> for BlockId { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - struct BlockIdVisitor; - - impl<'de> Visitor<'de> for BlockIdVisitor { - type Value = BlockId; - - fn expecting(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result { - formatter.write_str("Block identifier following EIP-1898") - } - - fn visit_str(self, v: &str) -> Result - where - E: serde::de::Error, - { - // Since there is no way to clearly distinguish between a DATA parameter and a QUANTITY parameter. A str is therefor deserialized into a Block Number: - // However, since the hex string should be a QUANTITY, we can safely assume that if the len is 66 bytes, it is in fact a hash, ref - if v.len() == 66 { - Ok(BlockId::Hash(v.parse::().map_err(serde::de::Error::custom)?.into())) - } else { - // quantity hex string or tag - Ok(BlockId::Number(v.parse().map_err(serde::de::Error::custom)?)) - } - } - - fn visit_map(self, mut map: A) -> Result - where - A: MapAccess<'de>, - { - let mut number = None; - let mut block_hash = None; - let mut require_canonical = None; - while let Some(key) = map.next_key::()? { - match key.as_str() { - "blockNumber" => { - if number.is_some() || block_hash.is_some() { - return Err(serde::de::Error::duplicate_field("blockNumber")) - } - if require_canonical.is_some() { - return Err(serde::de::Error::custom( - "Non-valid require_canonical field", - )) - } - number = Some(map.next_value::()?) - } - "blockHash" => { - if number.is_some() || block_hash.is_some() { - return Err(serde::de::Error::duplicate_field("blockHash")) - } - - block_hash = Some(map.next_value::()?); - } - "requireCanonical" => { - if number.is_some() || require_canonical.is_some() { - return Err(serde::de::Error::duplicate_field("requireCanonical")) - } - - require_canonical = Some(map.next_value::()?) - } - key => { - return Err(serde::de::Error::unknown_field( - key, - &["blockNumber", "blockHash", "requireCanonical"], - )) - } - } - } - - if let Some(number) = number { - Ok(BlockId::Number(number)) - } else if let Some(block_hash) = block_hash { - Ok(BlockId::Hash(RpcBlockHash { block_hash, require_canonical })) - } else { - Err(serde::de::Error::custom( - "Expected `blockNumber` or `blockHash` with `requireCanonical` optionally", - )) - } - } - } - - deserializer.deserialize_any(BlockIdVisitor) - } -} - -/// A block Number (or tag - "latest", "earliest", "pending") -#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)] -pub enum BlockNumberOrTag { - /// Latest block - #[default] - Latest, - /// Finalized block accepted as canonical - Finalized, - /// Safe head block - Safe, - /// Earliest block (genesis) - Earliest, - /// Pending block (not yet part of the blockchain) - Pending, - /// Block by number from canon chain - Number(u64), -} - -impl BlockNumberOrTag { - /// Returns the numeric block number if explicitly set - pub fn as_number(&self) -> Option { - match *self { - BlockNumberOrTag::Number(num) => Some(num), - _ => None, - } - } - - /// Returns `true` if a numeric block number is set - pub fn is_number(&self) -> bool { - matches!(self, BlockNumberOrTag::Number(_)) - } - - /// Returns `true` if it's "latest" - pub fn is_latest(&self) -> bool { - matches!(self, BlockNumberOrTag::Latest) - } - - /// Returns `true` if it's "finalized" - pub fn is_finalized(&self) -> bool { - matches!(self, BlockNumberOrTag::Finalized) - } - - /// Returns `true` if it's "safe" - pub fn is_safe(&self) -> bool { - matches!(self, BlockNumberOrTag::Safe) - } - - /// Returns `true` if it's "pending" - pub fn is_pending(&self) -> bool { - matches!(self, BlockNumberOrTag::Pending) - } - - /// Returns `true` if it's "earliest" - pub fn is_earliest(&self) -> bool { - matches!(self, BlockNumberOrTag::Earliest) - } -} - -impl From for BlockNumberOrTag { - fn from(num: u64) -> Self { - BlockNumberOrTag::Number(num) - } -} - -impl From for BlockNumberOrTag { - fn from(num: U64) -> Self { - num.into_limbs()[0].into() - } -} - -impl Serialize for BlockNumberOrTag { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - match *self { - BlockNumberOrTag::Number(ref x) => serializer.serialize_str(&format!("0x{x:x}")), - BlockNumberOrTag::Latest => serializer.serialize_str("latest"), - BlockNumberOrTag::Finalized => serializer.serialize_str("finalized"), - BlockNumberOrTag::Safe => serializer.serialize_str("safe"), - BlockNumberOrTag::Earliest => serializer.serialize_str("earliest"), - BlockNumberOrTag::Pending => serializer.serialize_str("pending"), - } - } -} - -impl<'de> Deserialize<'de> for BlockNumberOrTag { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let s = String::deserialize(deserializer)?.to_lowercase(); - s.parse().map_err(serde::de::Error::custom) - } -} - -impl FromStr for BlockNumberOrTag { - type Err = ParseBlockNumberError; - - fn from_str(s: &str) -> Result { - let block = match s { - "latest" => Self::Latest, - "finalized" => Self::Finalized, - "safe" => Self::Safe, - "earliest" => Self::Earliest, - "pending" => Self::Pending, - _number => { - if let Some(hex_val) = s.strip_prefix("0x") { - let number = u64::from_str_radix(hex_val, 16); - BlockNumberOrTag::Number(number?) - } else { - return Err(HexStringMissingPrefixError::default().into()) - } - } - }; - Ok(block) - } -} - -impl fmt::Display for BlockNumberOrTag { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - BlockNumberOrTag::Number(ref x) => format!("0x{x:x}").fmt(f), - BlockNumberOrTag::Latest => f.write_str("latest"), - BlockNumberOrTag::Finalized => f.write_str("finalized"), - BlockNumberOrTag::Safe => f.write_str("safe"), - BlockNumberOrTag::Earliest => f.write_str("earliest"), - BlockNumberOrTag::Pending => f.write_str("pending"), - } - } -} - -/// Error variants when parsing a [BlockNumberOrTag] -#[derive(Debug, thiserror::Error)] -pub enum ParseBlockNumberError { - /// Failed to parse hex value - #[error(transparent)] - ParseIntErr(#[from] ParseIntError), - /// Block numbers should be 0x-prefixed - #[error(transparent)] - MissingPrefix(#[from] HexStringMissingPrefixError), -} - -/// Thrown when a 0x-prefixed hex string was expected -#[derive(Debug, Default, thiserror::Error)] -#[non_exhaustive] -#[error("hex string without 0x prefix")] -pub struct HexStringMissingPrefixError; - -/// A block hash which may have -/// a boolean requireCanonical field. -/// If false, an RPC call should raise if a block -/// matching the hash is not found. -/// If true, an RPC call should additionaly raise if -/// the block is not in the canonical chain. -/// -#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize)] -pub struct RpcBlockHash { - /// A block hash - pub block_hash: B256, - /// Whether the block must be a canonical block - pub require_canonical: Option, -} - -impl RpcBlockHash { - pub fn from_hash(block_hash: B256, require_canonical: Option) -> Self { - RpcBlockHash { block_hash, require_canonical } - } -} - -impl From for RpcBlockHash { - fn from(value: B256) -> Self { - Self::from_hash(value, None) - } -} - -impl From for B256 { - fn from(value: RpcBlockHash) -> Self { - value.block_hash - } -} - -impl AsRef for RpcBlockHash { - fn as_ref(&self) -> &B256 { - &self.block_hash - } -} - -/// Block number and hash. -#[derive(Clone, Copy, Hash, Default, PartialEq, Eq)] -pub struct BlockNumHash { - /// Block number - pub number: BlockNumber, - /// Block hash - pub hash: BlockHash, -} - -/// Block number and hash of the forked block. -pub type ForkBlock = BlockNumHash; - -impl std::fmt::Debug for BlockNumHash { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_tuple("").field(&self.number).field(&self.hash).finish() - } -} - -impl BlockNumHash { - /// Creates a new `BlockNumHash` from a block number and hash. - pub fn new(number: BlockNumber, hash: BlockHash) -> Self { - Self { number, hash } - } - - /// Consumes `Self` and returns [`BlockNumber`], [`BlockHash`] - pub fn into_components(self) -> (BlockNumber, BlockHash) { - (self.number, self.hash) - } - - /// Returns whether or not the block matches the given [BlockHashOrNumber]. - pub fn matches_block_or_num(&self, block: &BlockHashOrNumber) -> bool { - match block { - BlockHashOrNumber::Hash(hash) => self.hash == *hash, - BlockHashOrNumber::Number(number) => self.number == *number, - } - } -} - -impl From<(BlockNumber, BlockHash)> for BlockNumHash { - fn from(val: (BlockNumber, BlockHash)) -> Self { - BlockNumHash { number: val.0, hash: val.1 } - } -} - -impl From<(BlockHash, BlockNumber)> for BlockNumHash { - fn from(val: (BlockHash, BlockNumber)) -> Self { - BlockNumHash { hash: val.0, number: val.1 } - } -} - /// A response to `GetBlockBodies`, containing bodies if any bodies were found. /// /// Withdrawals can be optionally included at the end of the RLP encoded message. @@ -887,6 +371,9 @@ pub struct BlockBodyRoots { mod test { use super::{BlockId, BlockNumberOrTag::*, *}; use crate::hex_literal::hex; + use alloy_rlp::{Decodable, Encodable}; + use reth_rpc_types::HexStringMissingPrefixError; + use std::str::FromStr; /// Check parsing according to EIP-1898. #[test] diff --git a/crates/primitives/src/lib.rs b/crates/primitives/src/lib.rs index b82286c1c4..957d310657 100644 --- a/crates/primitives/src/lib.rs +++ b/crates/primitives/src/lib.rs @@ -51,7 +51,7 @@ mod withdrawal; pub use account::{Account, Bytecode}; pub use block::{ Block, BlockBody, BlockBodyRoots, BlockHashOrNumber, BlockId, BlockNumHash, BlockNumberOrTag, - BlockWithSenders, ForkBlock, SealedBlock, SealedBlockWithSenders, + BlockWithSenders, ForkBlock, RpcBlockHash, SealedBlock, SealedBlockWithSenders, }; pub use bytes::{Buf, BufMut, BytesMut}; pub use chain::{ diff --git a/crates/primitives/src/net.rs b/crates/primitives/src/net.rs index 8b4503b289..afb43c871e 100644 --- a/crates/primitives/src/net.rs +++ b/crates/primitives/src/net.rs @@ -1,118 +1,4 @@ -use crate::PeerId; -use alloy_rlp::{RlpDecodable, RlpEncodable}; -use secp256k1::{SecretKey, SECP256K1}; -use serde_with::{DeserializeFromStr, SerializeDisplay}; -use std::{ - fmt, - fmt::Write, - net::{IpAddr, Ipv4Addr, SocketAddr}, - num::ParseIntError, - str::FromStr, -}; -use url::{Host, Url}; - -/// Represents a ENR in discv4. -/// -/// Note: this is only an excerpt of the [`NodeRecord`] data structure. -#[derive( - Clone, - Copy, - Debug, - Eq, - PartialEq, - Hash, - SerializeDisplay, - DeserializeFromStr, - RlpEncodable, - RlpDecodable, -)] -pub struct NodeRecord { - /// The Address of a node. - pub address: IpAddr, - /// TCP port of the port that accepts connections. - pub tcp_port: u16, - /// UDP discovery port. - pub udp_port: u16, - /// Public key of the discovery service - pub id: PeerId, -} - -impl NodeRecord { - /// Derive the [`NodeRecord`] from the secret key and addr - pub fn from_secret_key(addr: SocketAddr, sk: &SecretKey) -> Self { - let pk = secp256k1::PublicKey::from_secret_key(SECP256K1, sk); - let id = PeerId::from_slice(&pk.serialize_uncompressed()[1..]); - Self::new(addr, id) - } - - /// Converts the `address` into an [`Ipv4Addr`] if the `address` is a mapped - /// [Ipv6Addr](std::net::Ipv6Addr). - /// - /// Returns `true` if the address was converted. - /// - /// See also [std::net::Ipv6Addr::to_ipv4_mapped] - pub fn convert_ipv4_mapped(&mut self) -> bool { - // convert IPv4 mapped IPv6 address - if let IpAddr::V6(v6) = self.address { - if let Some(v4) = v6.to_ipv4_mapped() { - self.address = v4.into(); - return true - } - } - false - } - - /// Same as [Self::convert_ipv4_mapped] but consumes the type - pub fn into_ipv4_mapped(mut self) -> Self { - self.convert_ipv4_mapped(); - self - } - - /// Creates a new record from a socket addr and peer id. - #[allow(unused)] - pub fn new(addr: SocketAddr, id: PeerId) -> Self { - Self { address: addr.ip(), tcp_port: addr.port(), udp_port: addr.port(), id } - } - - /// The TCP socket address of this node - #[must_use] - pub fn tcp_addr(&self) -> SocketAddr { - SocketAddr::new(self.address, self.tcp_port) - } - - /// The UDP socket address of this node - #[must_use] - pub fn udp_addr(&self) -> SocketAddr { - SocketAddr::new(self.address, self.udp_port) - } -} - -impl fmt::Display for NodeRecord { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("enode://")?; - crate::hex::encode(self.id.as_slice()).fmt(f)?; - f.write_char('@')?; - match self.address { - IpAddr::V4(ip) => { - ip.fmt(f)?; - } - IpAddr::V6(ip) => { - // encapsulate with brackets - f.write_char('[')?; - ip.fmt(f)?; - f.write_char(']')?; - } - } - f.write_char(':')?; - self.tcp_port.fmt(f)?; - if self.tcp_port != self.udp_port { - f.write_str("?discport=")?; - self.udp_port.fmt(f)?; - } - - Ok(()) - } -} +pub use reth_rpc_types::NodeRecord; // @@ -180,60 +66,18 @@ fn parse_nodes(nodes: impl IntoIterator>) -> Vec Result { - let url = Url::parse(s).map_err(|e| NodeRecordParseError::InvalidUrl(e.to_string()))?; - - let address = match url.host() { - Some(Host::Ipv4(ip)) => IpAddr::V4(ip), - Some(Host::Ipv6(ip)) => IpAddr::V6(ip), - Some(Host::Domain(ip)) => IpAddr::V4( - Ipv4Addr::from_str(ip) - .map_err(|e| NodeRecordParseError::InvalidUrl(e.to_string()))?, - ), - _ => return Err(NodeRecordParseError::InvalidUrl(format!("invalid host: {url:?}"))), - }; - let port = url - .port() - .ok_or_else(|| NodeRecordParseError::InvalidUrl("no port specified".to_string()))?; - - let udp_port = if let Some(discovery_port) = url - .query_pairs() - .find_map(|(maybe_disc, port)| (maybe_disc.as_ref() == "discport").then_some(port)) - { - discovery_port.parse::().map_err(NodeRecordParseError::Discport)? - } else { - port - }; - - let id = url - .username() - .parse::() - .map_err(|e| NodeRecordParseError::InvalidId(e.to_string()))?; - - Ok(Self { address, id, tcp_port: port, udp_port }) - } -} - #[cfg(test)] mod tests { + use std::{ + net::{IpAddr, Ipv4Addr}, + str::FromStr, + }; + use super::*; use alloy_rlp::{Decodable, Encodable}; use bytes::BytesMut; use rand::{thread_rng, Rng, RngCore}; + use reth_rpc_types::PeerId; #[test] fn test_mapped_ipv6() { diff --git a/crates/primitives/src/peer.rs b/crates/primitives/src/peer.rs index af2f6c9d83..852f9e01c5 100644 --- a/crates/primitives/src/peer.rs +++ b/crates/primitives/src/peer.rs @@ -1,10 +1,5 @@ -use crate::B512; - -// TODO: should we use `PublicKey` for this? Even when dealing with public keys we should try to -// prevent misuse -/// This represents an uncompressed secp256k1 public key. -/// This encodes the concatenation of the x and y components of the affine point in bytes. -pub type PeerId = B512; +// Re-export PeerId for ease of use. +pub use reth_rpc_types::PeerId; /// Generic wrapper with peer id #[derive(Debug)] diff --git a/crates/rpc/rpc-engine-api/src/engine_api.rs b/crates/rpc/rpc-engine-api/src/engine_api.rs index 5218e1b963..4b6f1453c2 100644 --- a/crates/rpc/rpc-engine-api/src/engine_api.rs +++ b/crates/rpc/rpc-engine-api/src/engine_api.rs @@ -805,6 +805,7 @@ mod tests { use reth_payload_builder::test_utils::spawn_test_payload_service; use reth_primitives::{SealedBlock, B256, MAINNET}; use reth_provider::test_utils::MockEthProvider; + use reth_rpc_types_compat::engine::payload::execution_payload_from_sealed_block; use reth_tasks::TokioTaskExecutor; use std::sync::Arc; use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver}; @@ -837,7 +838,9 @@ mod tests { let (mut handle, api) = setup_engine_api(); tokio::spawn(async move { - api.new_payload_v1(SealedBlock::default().into()).await.unwrap(); + api.new_payload_v1(execution_payload_from_sealed_block(SealedBlock::default())) + .await + .unwrap(); }); assert_matches!(handle.from_api.recv().await, Some(BeaconEngineMessage::NewPayload { .. })); } diff --git a/crates/rpc/rpc-types-compat/src/block.rs b/crates/rpc/rpc-types-compat/src/block.rs index a5d5d94769..3a4fc86aec 100644 --- a/crates/rpc/rpc-types-compat/src/block.rs +++ b/crates/rpc/rpc-types-compat/src/block.rs @@ -2,7 +2,7 @@ use crate::transaction::from_recovered_with_block_context; use alloy_rlp::Encodable; -use reth_primitives::{Block as PrimitiveBlock, Header as PrimitiveHeader, B256, U256}; +use reth_primitives::{Block as PrimitiveBlock, Header as PrimitiveHeader, B256, U256, U64}; use reth_rpc_types::{Block, BlockError, BlockTransactions, BlockTransactionsKind, Header}; /// Converts the given primitive block into a [Block] response with the given @@ -85,6 +85,71 @@ pub fn from_block_full( )) } +/// Converts from a [reth_primitives::SealedHeader] to a [reth_rpc_types::BlockNumberOrTag] +pub fn from_primitive_with_hash(primitive_header: reth_primitives::SealedHeader) -> Header { + let reth_primitives::SealedHeader { + header: + PrimitiveHeader { + parent_hash, + ommers_hash, + beneficiary, + state_root, + transactions_root, + receipts_root, + logs_bloom, + difficulty, + number, + gas_limit, + gas_used, + timestamp, + mix_hash, + nonce, + base_fee_per_gas, + extra_data, + withdrawals_root, + blob_gas_used, + excess_blob_gas, + parent_beacon_block_root, + }, + hash, + } = primitive_header; + + Header { + hash: Some(hash), + parent_hash, + uncles_hash: ommers_hash, + miner: beneficiary, + state_root, + transactions_root, + receipts_root, + withdrawals_root, + number: Some(U256::from(number)), + gas_used: U256::from(gas_used), + gas_limit: U256::from(gas_limit), + extra_data, + logs_bloom, + timestamp: U256::from(timestamp), + difficulty, + mix_hash, + nonce: Some(nonce.to_be_bytes().into()), + base_fee_per_gas: base_fee_per_gas.map(U256::from), + blob_gas_used: blob_gas_used.map(U64::from), + excess_blob_gas: excess_blob_gas.map(U64::from), + parent_beacon_block_root, + } +} + +fn from_primitive_withdrawal( + withdrawal: reth_primitives::Withdrawal, +) -> reth_rpc_types::Withdrawal { + reth_rpc_types::Withdrawal { + index: withdrawal.validator_index, + address: withdrawal.address, + validator_index: withdrawal.validator_index, + amount: withdrawal.amount, + } +} + #[inline] fn from_block_with_transactions( block_length: usize, @@ -94,8 +159,14 @@ fn from_block_with_transactions( transactions: BlockTransactions, ) -> Block { let uncles = block.ommers.into_iter().map(|h| h.hash_slow()).collect(); - let header = Header::from_primitive_with_hash(block.header.seal(block_hash)); - let withdrawals = if header.withdrawals_root.is_some() { block.withdrawals } else { None }; + let header = from_primitive_with_hash(block.header.seal(block_hash)); + let withdrawals = if header.withdrawals_root.is_some() { + block + .withdrawals + .map(|withdrawals| withdrawals.into_iter().map(from_primitive_withdrawal).collect()) + } else { + None + }; Block { header, uncles, @@ -110,7 +181,7 @@ fn from_block_with_transactions( /// an Uncle from its header. pub fn uncle_block_from_header(header: PrimitiveHeader) -> Block { let hash = header.hash_slow(); - let rpc_header = Header::from_primitive_with_hash(header.clone().seal(hash)); + let rpc_header = from_primitive_with_hash(header.clone().seal(hash)); let uncle_block = PrimitiveBlock { header, ..Default::default() }; let size = Some(U256::from(uncle_block.length())); Block { diff --git a/crates/rpc/rpc-types-compat/src/engine/payload.rs b/crates/rpc/rpc-types-compat/src/engine/payload.rs index 0ebe8b6baf..7b0dbdb987 100644 --- a/crates/rpc/rpc-types-compat/src/engine/payload.rs +++ b/crates/rpc/rpc-types-compat/src/engine/payload.rs @@ -360,6 +360,47 @@ pub fn convert_to_payload_body_v1(value: Block) -> ExecutionPayloadBodyV1 { ExecutionPayloadBodyV1 { transactions: transactions.collect(), withdrawals: withdraw } } +/// Transforms a [reth_primitives::BlobTransactionSidecar] into a +/// [reth_rpc_types::BlobTransactionSidecar] +pub fn from_primitive_sidecar( + sidecar: reth_primitives::BlobTransactionSidecar, +) -> reth_rpc_types::BlobTransactionSidecar { + reth_rpc_types::BlobTransactionSidecar { + blobs: sidecar.blobs, + commitments: sidecar.commitments, + proofs: sidecar.proofs, + } +} + +/// Transforms a [SealedBlock] into a [ExecutionPayloadV1] +pub fn execution_payload_from_sealed_block(value: SealedBlock) -> ExecutionPayloadV1 { + let transactions = value + .body + .iter() + .map(|tx| { + let mut encoded = Vec::new(); + tx.encode_enveloped(&mut encoded); + encoded.into() + }) + .collect(); + ExecutionPayloadV1 { + parent_hash: value.parent_hash, + fee_recipient: value.beneficiary, + state_root: value.state_root, + receipts_root: value.receipts_root, + logs_bloom: value.logs_bloom, + prev_randao: value.mix_hash, + block_number: U64::from(value.number), + gas_limit: U64::from(value.gas_limit), + gas_used: U64::from(value.gas_used), + timestamp: U64::from(value.timestamp), + extra_data: value.extra_data.clone(), + base_fee_per_gas: U256::from(value.base_fee_per_gas.unwrap_or_default()), + block_hash: value.hash(), + transactions, + } +} + #[cfg(test)] mod tests { use reth_primitives::{hex, Bytes, U256, U64}; diff --git a/crates/rpc/rpc-types-compat/src/log.rs b/crates/rpc/rpc-types-compat/src/log.rs index fed647571a..2d2ebb2e98 100644 --- a/crates/rpc/rpc-types-compat/src/log.rs +++ b/crates/rpc/rpc-types-compat/src/log.rs @@ -15,6 +15,13 @@ pub fn from_primitive_log(log: reth_primitives::Log) -> reth_rpc_types::Log { removed: false, } } + +/// Converts from a [reth_rpc_types::Log] to a [reth_primitives::Log] +#[inline] +pub fn to_primitive_log(log: reth_rpc_types::Log) -> reth_primitives::Log { + reth_primitives::Log { address: log.address, topics: log.topics, data: log.data } +} + /// Converts a primitive `AccessList` structure from the `reth_primitives` module into the /// corresponding RPC type. #[inline] @@ -30,3 +37,19 @@ pub fn from_primitive_access_list(list: reth_primitives::AccessList) -> reth_rpc reth_rpc_types::AccessList(converted_list) } + +/// Converts a primitive `AccessList` structure from the `reth_primitives` module into the +/// corresponding RPC type. +#[inline] +pub fn to_primitive_access_list(list: reth_rpc_types::AccessList) -> reth_primitives::AccessList { + let converted_list: Vec = list + .0 + .into_iter() + .map(|item| reth_primitives::AccessListItem { + address: item.address, + storage_keys: item.storage_keys, + }) + .collect(); + + reth_primitives::AccessList(converted_list) +} diff --git a/crates/rpc/rpc-types-compat/src/proof.rs b/crates/rpc/rpc-types-compat/src/proof.rs index aea17f0c79..32c2cf9ac6 100644 --- a/crates/rpc/rpc-types-compat/src/proof.rs +++ b/crates/rpc/rpc-types-compat/src/proof.rs @@ -1,11 +1,10 @@ //! Compatibility functions for rpc proof related types. use reth_primitives::{ - serde_helper::JsonStorageKey, trie::{AccountProof, StorageProof}, U64, }; -use reth_rpc_types::{EIP1186AccountProofResponse, EIP1186StorageProof}; +use reth_rpc_types::{storage::JsonStorageKey, EIP1186AccountProofResponse, EIP1186StorageProof}; /// Creates a new rpc storage proof from a primitive storage proof type. pub fn from_primitive_storage_proof(proof: StorageProof) -> EIP1186StorageProof { diff --git a/crates/rpc/rpc-types-compat/src/transaction/mod.rs b/crates/rpc/rpc-types-compat/src/transaction/mod.rs index 53ae7cd203..cbbce7f68f 100644 --- a/crates/rpc/rpc-types-compat/src/transaction/mod.rs +++ b/crates/rpc/rpc-types-compat/src/transaction/mod.rs @@ -1,11 +1,13 @@ //! Compatibility functions for rpc `Transaction` type. mod signature; +mod typed; use reth_primitives::{ BlockNumber, Transaction as PrimitiveTransaction, TransactionKind as PrimitiveTransactionKind, TransactionSignedEcRecovered, TxType, B256, U128, U256, U64, }; use reth_rpc_types::{AccessListItem, CallInput, CallRequest, Transaction}; use signature::from_primitive_signature; +pub use typed::*; /// Create a new rpc transaction result for a mined transaction, using the given block hash, /// number, and tx index fields to populate the corresponding fields in the rpc result. /// @@ -132,6 +134,38 @@ fn fill( } } +/// Convert [reth_primitives::AccessList] to [reth_rpc_types::AccessList] +pub fn from_primitive_access_list( + access_list: reth_primitives::AccessList, +) -> reth_rpc_types::AccessList { + reth_rpc_types::AccessList( + access_list + .0 + .into_iter() + .map(|item| reth_rpc_types::AccessListItem { + address: item.address.0.into(), + storage_keys: item.storage_keys.into_iter().map(|key| key.0.into()).collect(), + }) + .collect(), + ) +} + +/// Convert [reth_rpc_types::AccessList] to [reth_primitives::AccessList] +pub fn to_primitive_access_list( + access_list: reth_rpc_types::AccessList, +) -> reth_primitives::AccessList { + reth_primitives::AccessList( + access_list + .0 + .into_iter() + .map(|item| reth_primitives::AccessListItem { + address: item.address.0.into(), + storage_keys: item.storage_keys.into_iter().map(|key| key.0.into()).collect(), + }) + .collect(), + ) +} + /// Convert [TransactionSignedEcRecovered] to [CallRequest] pub fn transaction_to_call_request(tx: TransactionSignedEcRecovered) -> CallRequest { let from = tx.signer(); @@ -141,7 +175,7 @@ pub fn transaction_to_call_request(tx: TransactionSignedEcRecovered) -> CallRequ let input = tx.transaction.input().clone(); let nonce = tx.transaction.nonce(); let chain_id = tx.transaction.chain_id(); - let access_list = tx.transaction.access_list().cloned(); + let access_list = tx.transaction.access_list().cloned().map(from_primitive_access_list); let max_fee_per_blob_gas = tx.transaction.max_fee_per_blob_gas(); let blob_versioned_hashes = tx.transaction.blob_versioned_hashes(); let tx_type = tx.transaction.tx_type(); diff --git a/crates/rpc/rpc-types-compat/src/transaction/typed.rs b/crates/rpc/rpc-types-compat/src/transaction/typed.rs new file mode 100644 index 0000000000..19adf6652c --- /dev/null +++ b/crates/rpc/rpc-types-compat/src/transaction/typed.rs @@ -0,0 +1,70 @@ +use crate::log::to_primitive_access_list; + +/// Converts a typed transaction request into a primitive transaction. +/// +/// Returns `None` if any of the following are true: +/// - `nonce` is greater than [`u64::MAX`] +/// - `gas_limit` is greater than [`u64::MAX`] +/// - `value` is greater than [`u128::MAX`] +pub fn to_primitive_transaction( + tx_request: reth_rpc_types::TypedTransactionRequest, +) -> Option { + use reth_primitives::{Transaction, TxEip1559, TxEip2930, TxEip4844, TxLegacy}; + use reth_rpc_types::TypedTransactionRequest; + + Some(match tx_request { + TypedTransactionRequest::Legacy(tx) => Transaction::Legacy(TxLegacy { + chain_id: tx.chain_id, + nonce: tx.nonce.to(), + gas_price: tx.gas_price.to(), + gas_limit: tx.gas_limit.try_into().ok()?, + to: to_primitive_transaction_kind(tx.kind), + value: tx.value.into(), + input: tx.input, + }), + TypedTransactionRequest::EIP2930(tx) => Transaction::Eip2930(TxEip2930 { + chain_id: tx.chain_id, + nonce: tx.nonce.to(), + gas_price: tx.gas_price.to(), + gas_limit: tx.gas_limit.try_into().ok()?, + to: to_primitive_transaction_kind(tx.kind), + value: tx.value.into(), + input: tx.input, + access_list: to_primitive_access_list(tx.access_list), + }), + TypedTransactionRequest::EIP1559(tx) => Transaction::Eip1559(TxEip1559 { + chain_id: tx.chain_id, + nonce: tx.nonce.to(), + max_fee_per_gas: tx.max_fee_per_gas.to(), + gas_limit: tx.gas_limit.try_into().ok()?, + to: to_primitive_transaction_kind(tx.kind), + value: tx.value.into(), + input: tx.input, + access_list: to_primitive_access_list(tx.access_list), + max_priority_fee_per_gas: tx.max_priority_fee_per_gas.to(), + }), + TypedTransactionRequest::EIP4844(tx) => Transaction::Eip4844(TxEip4844 { + chain_id: tx.chain_id, + nonce: tx.nonce.to(), + gas_limit: tx.gas_limit.to(), + max_fee_per_gas: tx.max_fee_per_gas.to(), + max_priority_fee_per_gas: tx.max_priority_fee_per_gas.to(), + to: to_primitive_transaction_kind(tx.kind), + value: tx.value.into(), + access_list: to_primitive_access_list(tx.access_list), + blob_versioned_hashes: tx.blob_versioned_hashes, + max_fee_per_blob_gas: tx.max_fee_per_blob_gas.to(), + input: tx.input, + }), + }) +} + +/// Transforms a [reth_rpc_types::TransactionKind] into a [reth_primitives::TransactionKind] +pub fn to_primitive_transaction_kind( + kind: reth_rpc_types::TransactionKind, +) -> reth_primitives::TransactionKind { + match kind { + reth_rpc_types::TransactionKind::Call(to) => reth_primitives::TransactionKind::Call(to), + reth_rpc_types::TransactionKind::Create => reth_primitives::TransactionKind::Create, + } +} diff --git a/crates/rpc/rpc-types/Cargo.toml b/crates/rpc/rpc-types/Cargo.toml index 98d1c95034..861cf817c7 100644 --- a/crates/rpc/rpc-types/Cargo.toml +++ b/crates/rpc/rpc-types/Cargo.toml @@ -11,11 +11,8 @@ Reth RPC types """ [dependencies] -# reth -reth-primitives.workspace = true - # # ethereum -alloy-rlp = { workspace = true, features = ["arrayvec"] } +alloy-rlp = { workspace = true, features = ["arrayvec", "derive"] } # misc thiserror.workspace = true @@ -24,11 +21,21 @@ serde = { workspace = true, features = ["derive"] } serde_with = "3.3" serde_json.workspace = true jsonrpsee-types = { workspace = true, optional = true } -alloy-primitives = { workspace = true, features = ["rand", "rlp"] } +alloy-primitives = { workspace = true, features = ["rand", "rlp", "serde"] } +c-kzg = { workspace = true, features = ["serde"] } +url = "2.3" +# necessary so we don't hit a "undeclared 'std'": +# https://github.com/paradigmxyz/reth/pull/177#discussion_r1021172198 +secp256k1.workspace = true +bytes.workspace = true +arbitrary = { workspace = true, features = ["derive"], optional = true } +proptest = { workspace = true, optional = true } +proptest-derive = { workspace = true, optional = true } [features] default = ["jsonrpsee-types"] +arbitrary = ["dep:arbitrary", "dep:proptest-derive", "dep:proptest", "alloy-primitives/arbitrary"] [dev-dependencies] # misc diff --git a/crates/rpc/rpc-types/src/admin.rs b/crates/rpc/rpc-types/src/admin.rs index b250e39c7c..03ddb17ef6 100644 --- a/crates/rpc/rpc-types/src/admin.rs +++ b/crates/rpc/rpc-types/src/admin.rs @@ -1,5 +1,5 @@ +use crate::{NodeRecord, PeerId}; use alloy_primitives::{B256, U256}; -use reth_primitives::{NodeRecord, PeerId}; use serde::{Deserialize, Serialize}; use std::{ collections::BTreeMap, @@ -79,7 +79,7 @@ pub struct NetworkStatus { #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct EthProtocolInfo { /// The current difficulty at the head of the chain. - #[serde(deserialize_with = "reth_primitives::serde_helper::deserialize_json_u256")] + #[serde(deserialize_with = "crate::serde_helpers::json_u256::deserialize_json_u256")] pub difficulty: U256, /// The block hash of the head of the chain. pub head: B256, diff --git a/crates/rpc/rpc-types/src/eth/account.rs b/crates/rpc/rpc-types/src/eth/account.rs index 567285e6fa..7188dc98ab 100644 --- a/crates/rpc/rpc-types/src/eth/account.rs +++ b/crates/rpc/rpc-types/src/eth/account.rs @@ -1,6 +1,6 @@ #![allow(missing_docs)] +use crate::serde_helpers::storage::JsonStorageKey; use alloy_primitives::{Address, Bytes, B256, B512, U256, U64}; -use reth_primitives::serde_helper::JsonStorageKey; use serde::{Deserialize, Serialize}; /// Account information. diff --git a/crates/rpc/rpc-types/src/eth/block.rs b/crates/rpc/rpc-types/src/eth/block.rs index afd98f0554..c9028e4011 100644 --- a/crates/rpc/rpc-types/src/eth/block.rs +++ b/crates/rpc/rpc-types/src/eth/block.rs @@ -1,9 +1,19 @@ -//! Contains types that represent ethereum types in [reth_primitives] when used in RPC -use crate::Transaction; -use alloy_primitives::{Address, Bloom, Bytes, B256, B64, U256, U64}; -use reth_primitives::{Header as PrimitiveHeader, SealedHeader, Withdrawal}; -use serde::{ser::Error, Deserialize, Serialize, Serializer}; -use std::{collections::BTreeMap, ops::Deref}; +//! Contains types that represent ethereum types when used in RPC +use crate::{Transaction, Withdrawal}; +use alloy_primitives::{Address, BlockHash, BlockNumber, Bloom, Bytes, B256, B64, U256, U64}; +use alloy_rlp::{bytes, Decodable, Encodable, Error as RlpError}; +use serde::{ + de::{MapAccess, Visitor}, + ser::{Error, SerializeStruct}, + Deserialize, Deserializer, Serialize, Serializer, +}; +use std::{ + collections::BTreeMap, + fmt::{self, Formatter}, + num::ParseIntError, + ops::Deref, + str::FromStr, +}; /// Block Transactions depending on the boolean attribute of `eth_getBlockBy*`, /// or if used by `eth_getUncle*` #[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] @@ -120,7 +130,7 @@ pub struct Block { } /// Block header representation. -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)] #[serde(rename_all = "camelCase")] pub struct Header { /// Hash of the block @@ -173,62 +183,506 @@ pub struct Header { pub parent_beacon_block_root: Option, } -// === impl Header === +/// A block hash which may have +/// a boolean requireCanonical field. +/// If false, an RPC call should raise if a block +/// matching the hash is not found. +/// If true, an RPC call should additionaly raise if +/// the block is not in the canonical chain. +/// +#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize)] +pub struct RpcBlockHash { + /// A block hash + pub block_hash: B256, + /// Whether the block must be a canonical block + pub require_canonical: Option, +} -impl Header { - /// Converts the primitive header type to this RPC type - /// - /// CAUTION: this takes the header's hash as is and does _not_ calculate the hash. - pub fn from_primitive_with_hash(primitive_header: SealedHeader) -> Self { - let SealedHeader { - header: - PrimitiveHeader { - parent_hash, - ommers_hash, - beneficiary, - state_root, - transactions_root, - receipts_root, - logs_bloom, - difficulty, - number, - gas_limit, - gas_used, - timestamp, - mix_hash, - nonce, - base_fee_per_gas, - extra_data, - withdrawals_root, - blob_gas_used, - excess_blob_gas, - parent_beacon_block_root, - }, - hash, - } = primitive_header; +impl RpcBlockHash { + /// Returns an [RpcBlockHash] from a [B256]. + pub const fn from_hash(block_hash: B256, require_canonical: Option) -> Self { + RpcBlockHash { block_hash, require_canonical } + } +} - Header { - hash: Some(hash), - parent_hash, - uncles_hash: ommers_hash, - miner: beneficiary, - state_root, - transactions_root, - receipts_root, - withdrawals_root, - number: Some(U256::from(number)), - gas_used: U256::from(gas_used), - gas_limit: U256::from(gas_limit), - extra_data, - logs_bloom, - timestamp: U256::from(timestamp), - difficulty, - mix_hash, - nonce: Some(nonce.to_be_bytes().into()), - base_fee_per_gas: base_fee_per_gas.map(U256::from), - blob_gas_used: blob_gas_used.map(U64::from), - excess_blob_gas: excess_blob_gas.map(U64::from), - parent_beacon_block_root, +impl From for RpcBlockHash { + fn from(value: B256) -> Self { + Self::from_hash(value, None) + } +} + +impl From for B256 { + fn from(value: RpcBlockHash) -> Self { + value.block_hash + } +} + +impl AsRef for RpcBlockHash { + fn as_ref(&self) -> &B256 { + &self.block_hash + } +} + +/// A block Number (or tag - "latest", "earliest", "pending") +#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)] +pub enum BlockNumberOrTag { + /// Latest block + #[default] + Latest, + /// Finalized block accepted as canonical + Finalized, + /// Safe head block + Safe, + /// Earliest block (genesis) + Earliest, + /// Pending block (not yet part of the blockchain) + Pending, + /// Block by number from canon chain + Number(u64), +} + +impl BlockNumberOrTag { + /// Returns the numeric block number if explicitly set + pub const fn as_number(&self) -> Option { + match *self { + BlockNumberOrTag::Number(num) => Some(num), + _ => None, + } + } + + /// Returns `true` if a numeric block number is set + pub const fn is_number(&self) -> bool { + matches!(self, BlockNumberOrTag::Number(_)) + } + + /// Returns `true` if it's "latest" + pub const fn is_latest(&self) -> bool { + matches!(self, BlockNumberOrTag::Latest) + } + + /// Returns `true` if it's "finalized" + pub const fn is_finalized(&self) -> bool { + matches!(self, BlockNumberOrTag::Finalized) + } + + /// Returns `true` if it's "safe" + pub const fn is_safe(&self) -> bool { + matches!(self, BlockNumberOrTag::Safe) + } + + /// Returns `true` if it's "pending" + pub const fn is_pending(&self) -> bool { + matches!(self, BlockNumberOrTag::Pending) + } + + /// Returns `true` if it's "earliest" + pub const fn is_earliest(&self) -> bool { + matches!(self, BlockNumberOrTag::Earliest) + } +} + +impl From for BlockNumberOrTag { + fn from(num: u64) -> Self { + BlockNumberOrTag::Number(num) + } +} + +impl From for BlockNumberOrTag { + fn from(num: U64) -> Self { + num.into_limbs()[0].into() + } +} + +impl Serialize for BlockNumberOrTag { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + match *self { + BlockNumberOrTag::Number(ref x) => serializer.serialize_str(&format!("0x{x:x}")), + BlockNumberOrTag::Latest => serializer.serialize_str("latest"), + BlockNumberOrTag::Finalized => serializer.serialize_str("finalized"), + BlockNumberOrTag::Safe => serializer.serialize_str("safe"), + BlockNumberOrTag::Earliest => serializer.serialize_str("earliest"), + BlockNumberOrTag::Pending => serializer.serialize_str("pending"), + } + } +} + +impl<'de> Deserialize<'de> for BlockNumberOrTag { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let s = String::deserialize(deserializer)?.to_lowercase(); + s.parse().map_err(serde::de::Error::custom) + } +} + +impl FromStr for BlockNumberOrTag { + type Err = ParseBlockNumberError; + + fn from_str(s: &str) -> Result { + let block = match s { + "latest" => Self::Latest, + "finalized" => Self::Finalized, + "safe" => Self::Safe, + "earliest" => Self::Earliest, + "pending" => Self::Pending, + _number => { + if let Some(hex_val) = s.strip_prefix("0x") { + let number = u64::from_str_radix(hex_val, 16); + BlockNumberOrTag::Number(number?) + } else { + return Err(HexStringMissingPrefixError::default().into()); + } + } + }; + Ok(block) + } +} + +impl fmt::Display for BlockNumberOrTag { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + BlockNumberOrTag::Number(ref x) => format!("0x{x:x}").fmt(f), + BlockNumberOrTag::Latest => f.write_str("latest"), + BlockNumberOrTag::Finalized => f.write_str("finalized"), + BlockNumberOrTag::Safe => f.write_str("safe"), + BlockNumberOrTag::Earliest => f.write_str("earliest"), + BlockNumberOrTag::Pending => f.write_str("pending"), + } + } +} + +/// Error variants when parsing a [BlockNumberOrTag] +#[derive(Debug, thiserror::Error)] +pub enum ParseBlockNumberError { + /// Failed to parse hex value + #[error(transparent)] + ParseIntErr(#[from] ParseIntError), + /// Block numbers should be 0x-prefixed + #[error(transparent)] + MissingPrefix(#[from] HexStringMissingPrefixError), +} + +/// Thrown when a 0x-prefixed hex string was expected +#[derive(Copy, Clone, Debug, Default, thiserror::Error)] +#[non_exhaustive] +#[error("hex string without 0x prefix")] +pub struct HexStringMissingPrefixError; + +/// A Block Identifier +/// +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum BlockId { + /// A block hash and an optional bool that defines if it's canonical + Hash(RpcBlockHash), + /// A block number + Number(BlockNumberOrTag), +} + +// === impl BlockId === + +impl BlockId { + /// Returns the block hash if it is [BlockId::Hash] + pub const fn as_block_hash(&self) -> Option { + match self { + BlockId::Hash(hash) => Some(hash.block_hash), + BlockId::Number(_) => None, + } + } + + /// Returns true if this is [BlockNumberOrTag::Latest] + pub const fn is_latest(&self) -> bool { + matches!(self, BlockId::Number(BlockNumberOrTag::Latest)) + } + + /// Returns true if this is [BlockNumberOrTag::Pending] + pub const fn is_pending(&self) -> bool { + matches!(self, BlockId::Number(BlockNumberOrTag::Pending)) + } +} + +impl From for BlockId { + fn from(num: u64) -> Self { + BlockNumberOrTag::Number(num).into() + } +} + +impl From for BlockId { + fn from(num: BlockNumberOrTag) -> Self { + BlockId::Number(num) + } +} + +impl From for BlockId { + fn from(block_hash: B256) -> Self { + BlockId::Hash(RpcBlockHash { block_hash, require_canonical: None }) + } +} + +impl From<(B256, Option)> for BlockId { + fn from(hash_can: (B256, Option)) -> Self { + BlockId::Hash(RpcBlockHash { block_hash: hash_can.0, require_canonical: hash_can.1 }) + } +} + +impl Serialize for BlockId { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + match *self { + BlockId::Hash(RpcBlockHash { ref block_hash, ref require_canonical }) => { + let mut s = serializer.serialize_struct("BlockIdEip1898", 1)?; + s.serialize_field("blockHash", block_hash)?; + if let Some(require_canonical) = require_canonical { + s.serialize_field("requireCanonical", require_canonical)?; + } + s.end() + } + BlockId::Number(ref num) => num.serialize(serializer), + } + } +} + +impl<'de> Deserialize<'de> for BlockId { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct BlockIdVisitor; + + impl<'de> Visitor<'de> for BlockIdVisitor { + type Value = BlockId; + + fn expecting(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result { + formatter.write_str("Block identifier following EIP-1898") + } + + fn visit_str(self, v: &str) -> Result + where + E: serde::de::Error, + { + // Since there is no way to clearly distinguish between a DATA parameter and a QUANTITY parameter. A str is therefor deserialized into a Block Number: + // However, since the hex string should be a QUANTITY, we can safely assume that if the len is 66 bytes, it is in fact a hash, ref + if v.len() == 66 { + Ok(BlockId::Hash(v.parse::().map_err(serde::de::Error::custom)?.into())) + } else { + // quantity hex string or tag + Ok(BlockId::Number(v.parse().map_err(serde::de::Error::custom)?)) + } + } + + fn visit_map(self, mut map: A) -> Result + where + A: MapAccess<'de>, + { + let mut number = None; + let mut block_hash = None; + let mut require_canonical = None; + while let Some(key) = map.next_key::()? { + match key.as_str() { + "blockNumber" => { + if number.is_some() || block_hash.is_some() { + return Err(serde::de::Error::duplicate_field("blockNumber")); + } + if require_canonical.is_some() { + return Err(serde::de::Error::custom( + "Non-valid require_canonical field", + )); + } + number = Some(map.next_value::()?) + } + "blockHash" => { + if number.is_some() || block_hash.is_some() { + return Err(serde::de::Error::duplicate_field("blockHash")); + } + + block_hash = Some(map.next_value::()?); + } + "requireCanonical" => { + if number.is_some() || require_canonical.is_some() { + return Err(serde::de::Error::duplicate_field("requireCanonical")); + } + + require_canonical = Some(map.next_value::()?) + } + key => { + return Err(serde::de::Error::unknown_field( + key, + &["blockNumber", "blockHash", "requireCanonical"], + )) + } + } + } + + if let Some(number) = number { + Ok(BlockId::Number(number)) + } else if let Some(block_hash) = block_hash { + Ok(BlockId::Hash(RpcBlockHash { block_hash, require_canonical })) + } else { + Err(serde::de::Error::custom( + "Expected `blockNumber` or `blockHash` with `requireCanonical` optionally", + )) + } + } + } + + deserializer.deserialize_any(BlockIdVisitor) + } +} + +/// Block number and hash. +#[derive(Clone, Copy, Hash, Default, PartialEq, Eq)] +pub struct BlockNumHash { + /// Block number + pub number: BlockNumber, + /// Block hash + pub hash: BlockHash, +} + +/// Block number and hash of the forked block. +pub type ForkBlock = BlockNumHash; + +impl std::fmt::Debug for BlockNumHash { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_tuple("").field(&self.number).field(&self.hash).finish() + } +} + +impl BlockNumHash { + /// Creates a new `BlockNumHash` from a block number and hash. + pub fn new(number: BlockNumber, hash: BlockHash) -> Self { + Self { number, hash } + } + + /// Consumes `Self` and returns [`BlockNumber`], [`BlockHash`] + pub fn into_components(self) -> (BlockNumber, BlockHash) { + (self.number, self.hash) + } + + /// Returns whether or not the block matches the given [BlockHashOrNumber]. + pub fn matches_block_or_num(&self, block: &BlockHashOrNumber) -> bool { + match block { + BlockHashOrNumber::Hash(hash) => self.hash == *hash, + BlockHashOrNumber::Number(number) => self.number == *number, + } + } +} + +impl From<(BlockNumber, BlockHash)> for BlockNumHash { + fn from(val: (BlockNumber, BlockHash)) -> Self { + BlockNumHash { number: val.0, hash: val.1 } + } +} + +impl From<(BlockHash, BlockNumber)> for BlockNumHash { + fn from(val: (BlockHash, BlockNumber)) -> Self { + BlockNumHash { hash: val.0, number: val.1 } + } +} + +/// Either a block hash _or_ a block number +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[cfg_attr( + any(test, feature = "arbitrary"), + derive(proptest_derive::Arbitrary, arbitrary::Arbitrary) +)] +pub enum BlockHashOrNumber { + /// A block hash + Hash(B256), + /// A block number + Number(u64), +} + +// === impl BlockHashOrNumber === + +impl BlockHashOrNumber { + /// Returns the block number if it is a [`BlockHashOrNumber::Number`]. + #[inline] + pub fn as_number(self) -> Option { + match self { + BlockHashOrNumber::Hash(_) => None, + BlockHashOrNumber::Number(num) => Some(num), + } + } +} + +impl From for BlockHashOrNumber { + fn from(value: B256) -> Self { + BlockHashOrNumber::Hash(value) + } +} + +impl From for BlockHashOrNumber { + fn from(value: u64) -> Self { + BlockHashOrNumber::Number(value) + } +} + +/// Allows for RLP encoding of either a block hash or block number +impl Encodable for BlockHashOrNumber { + fn encode(&self, out: &mut dyn bytes::BufMut) { + match self { + Self::Hash(block_hash) => block_hash.encode(out), + Self::Number(block_number) => block_number.encode(out), + } + } + fn length(&self) -> usize { + match self { + Self::Hash(block_hash) => block_hash.length(), + Self::Number(block_number) => block_number.length(), + } + } +} + +/// Allows for RLP decoding of a block hash or block number +impl Decodable for BlockHashOrNumber { + fn decode(buf: &mut &[u8]) -> alloy_rlp::Result { + let header: u8 = *buf.first().ok_or(RlpError::InputTooShort)?; + // if the byte string is exactly 32 bytes, decode it into a Hash + // 0xa0 = 0x80 (start of string) + 0x20 (32, length of string) + if header == 0xa0 { + // strip the first byte, parsing the rest of the string. + // If the rest of the string fails to decode into 32 bytes, we'll bubble up the + // decoding error. + let hash = B256::decode(buf)?; + Ok(Self::Hash(hash)) + } else { + // a block number when encoded as bytes ranges from 0 to any number of bytes - we're + // going to accept numbers which fit in less than 64 bytes. + // Any data larger than this which is not caught by the Hash decoding should error and + // is considered an invalid block number. + Ok(Self::Number(u64::decode(buf)?)) + } + } +} + +/// Error thrown when parsing a [BlockHashOrNumber] from a string. +#[derive(Debug, thiserror::Error)] +#[error("failed to parse {input:?} as a number: {parse_int_error} or hash: {hex_error}")] +pub struct ParseBlockHashOrNumberError { + input: String, + parse_int_error: ParseIntError, + hex_error: alloy_primitives::hex::FromHexError, +} + +impl FromStr for BlockHashOrNumber { + type Err = ParseBlockHashOrNumberError; + + fn from_str(s: &str) -> Result { + match u64::from_str(s) { + Ok(val) => Ok(val.into()), + Err(pares_int_error) => match B256::from_str(s) { + Ok(val) => Ok(val.into()), + Err(hex_error) => Err(ParseBlockHashOrNumberError { + input: s.to_string(), + parse_int_error: pares_int_error, + hex_error, + }), + }, } } } diff --git a/crates/rpc/rpc-types/src/eth/call.rs b/crates/rpc/rpc-types/src/eth/call.rs index e984776ae1..2c41dcf282 100644 --- a/crates/rpc/rpc-types/src/eth/call.rs +++ b/crates/rpc/rpc-types/src/eth/call.rs @@ -1,7 +1,6 @@ //use crate::access_list::AccessList; -use crate::BlockOverrides; +use crate::{AccessList, BlockId, BlockOverrides}; use alloy_primitives::{Address, Bytes, B256, U256, U64, U8}; -use reth_primitives::{AccessList, BlockId}; use serde::{Deserialize, Deserializer, Serialize, Serializer}; /// Bundle of transactions #[derive(Debug, Clone, Default, Eq, PartialEq, Serialize, Deserialize)] diff --git a/crates/rpc/rpc-types/src/eth/engine/payload.rs b/crates/rpc/rpc-types/src/eth/engine/payload.rs index 07aef13cf5..fc6c28a803 100644 --- a/crates/rpc/rpc-types/src/eth/engine/payload.rs +++ b/crates/rpc/rpc-types/src/eth/engine/payload.rs @@ -1,10 +1,7 @@ -use crate::eth::withdrawal::BeaconAPIWithdrawal; +use crate::eth::{transaction::BlobTransactionSidecar, withdrawal::BeaconAPIWithdrawal}; pub use crate::Withdrawal; use alloy_primitives::{Address, Bloom, Bytes, B256, B64, U256, U64}; -use reth_primitives::{ - kzg::{Blob, Bytes48}, - BlobTransactionSidecar, SealedBlock, -}; +use c_kzg::{Blob, Bytes48}; use serde::{ser::SerializeMap, Deserialize, Serialize, Serializer}; use serde_with::{serde_as, DisplayFromStr}; @@ -140,36 +137,6 @@ pub struct ExecutionPayloadV1 { pub transactions: Vec, } -impl From for ExecutionPayloadV1 { - fn from(value: SealedBlock) -> Self { - let transactions = value - .body - .iter() - .map(|tx| { - let mut encoded = Vec::new(); - tx.encode_enveloped(&mut encoded); - encoded.into() - }) - .collect(); - ExecutionPayloadV1 { - parent_hash: value.parent_hash, - fee_recipient: value.beneficiary, - state_root: value.state_root, - receipts_root: value.receipts_root, - logs_bloom: value.logs_bloom, - prev_randao: value.mix_hash, - block_number: U64::from(value.number), - gas_limit: U64::from(value.gas_limit), - gas_used: U64::from(value.gas_used), - timestamp: U64::from(value.timestamp), - extra_data: value.extra_data.clone(), - base_fee_per_gas: U256::from(value.base_fee_per_gas.unwrap_or_default()), - block_hash: value.hash(), - transactions, - } - } -} - /// This structure maps on the ExecutionPayloadV2 structure of the beacon chain spec. /// /// See also: diff --git a/crates/rpc/rpc-types/src/eth/filter.rs b/crates/rpc/rpc-types/src/eth/filter.rs index 863a528880..c56365941c 100644 --- a/crates/rpc/rpc-types/src/eth/filter.rs +++ b/crates/rpc/rpc-types/src/eth/filter.rs @@ -1,7 +1,6 @@ -use crate::Log as RpcLog; +use crate::{eth::log::Log as RpcLog, BlockNumberOrTag, Log}; use alloy_primitives::{keccak256, Address, Bloom, BloomInput, B256, U256, U64}; use itertools::{EitherOrBoth::*, Itertools}; -use reth_primitives::{BlockNumberOrTag, Log}; use serde::{ de::{DeserializeOwned, MapAccess, Visitor}, ser::SerializeStruct, @@ -283,7 +282,7 @@ impl Filter { /// Match the latest block only /// /// ```rust - /// # use reth_primitives::BlockNumberOrTag; + /// # use reth_rpc_types::BlockNumberOrTag; /// # use reth_rpc_types::Filter; /// # fn main() { /// let filter = Filter::new().select(BlockNumberOrTag::Latest); diff --git a/crates/rpc/rpc-types/src/eth/mod.rs b/crates/rpc/rpc-types/src/eth/mod.rs index 0e02a8e503..20cdf102f6 100644 --- a/crates/rpc/rpc-types/src/eth/mod.rs +++ b/crates/rpc/rpc-types/src/eth/mod.rs @@ -10,6 +10,7 @@ mod filter; mod index; mod log; pub mod pubsub; +pub mod raw_log; pub mod state; mod syncing; pub mod trace; @@ -26,6 +27,7 @@ pub use fee::{FeeHistory, TxGasAndReward}; pub use filter::*; pub use index::Index; pub use log::Log; +pub use raw_log::{logs_bloom, Log as RawLog}; pub use syncing::*; pub use transaction::*; pub use withdrawal::Withdrawal; diff --git a/crates/rpc/rpc-types/src/eth/raw_log.rs b/crates/rpc/rpc-types/src/eth/raw_log.rs new file mode 100644 index 0000000000..2a12903ec0 --- /dev/null +++ b/crates/rpc/rpc-types/src/eth/raw_log.rs @@ -0,0 +1,30 @@ +//! Ethereum log object. + +use alloy_primitives::{Address, Bloom, Bytes, B256}; +use alloy_rlp::{RlpDecodable, RlpEncodable}; + +/// Ethereum Log +#[derive(Clone, Debug, PartialEq, Eq, RlpDecodable, RlpEncodable, Default)] +pub struct Log { + /// Contract that emitted this log. + pub address: Address, + /// Topics of the log. The number of logs depend on what `LOG` opcode is used. + pub topics: Vec, + /// Arbitrary length data. + pub data: Bytes, +} + +/// Calculate receipt logs bloom. +pub fn logs_bloom<'a, It>(logs: It) -> Bloom +where + It: IntoIterator, +{ + let mut bloom = Bloom::ZERO; + for log in logs { + bloom.m3_2048(log.address.as_slice()); + for topic in &log.topics { + bloom.m3_2048(topic.as_slice()); + } + } + bloom +} diff --git a/crates/rpc/rpc-types/src/eth/trace/filter.rs b/crates/rpc/rpc-types/src/eth/trace/filter.rs index 17497bff75..032a180286 100644 --- a/crates/rpc/rpc-types/src/eth/trace/filter.rs +++ b/crates/rpc/rpc-types/src/eth/trace/filter.rs @@ -1,6 +1,6 @@ //! `trace_filter` types and support +use crate::serde_helpers::num::u64_hex_or_decimal_opt; use alloy_primitives::Address; -use reth_primitives::serde_helper::num::u64_hex_or_decimal_opt; use serde::{Deserialize, Serialize}; use std::collections::HashSet; diff --git a/crates/rpc/rpc-types/src/eth/trace/geth/call.rs b/crates/rpc/rpc-types/src/eth/trace/geth/call.rs index 92869f4df2..1d2d754194 100644 --- a/crates/rpc/rpc-types/src/eth/trace/geth/call.rs +++ b/crates/rpc/rpc-types/src/eth/trace/geth/call.rs @@ -1,5 +1,5 @@ +use crate::serde_helpers::num::from_int_or_hex; use alloy_primitives::{Address, Bytes, B256, U256}; -use reth_primitives::serde_helper::num::from_int_or_hex; use serde::{Deserialize, Serialize}; /// The response object for `debug_traceTransaction` with `"tracer": "callTracer"` diff --git a/crates/rpc/rpc-types/src/eth/trace/geth/mod.rs b/crates/rpc/rpc-types/src/eth/trace/geth/mod.rs index f9b03bbfcc..adc2f7a8e3 100644 --- a/crates/rpc/rpc-types/src/eth/trace/geth/mod.rs +++ b/crates/rpc/rpc-types/src/eth/trace/geth/mod.rs @@ -45,7 +45,7 @@ pub struct BlockTraceResult { pub struct DefaultFrame { pub failed: bool, pub gas: u64, - #[serde(serialize_with = "reth_primitives::serde_helper::serialize_hex_string_no_prefix")] + #[serde(serialize_with = "crate::serde_helpers::serialize_hex_string_no_prefix")] pub return_value: Bytes, pub struct_logs: Vec, } diff --git a/crates/rpc/rpc-types/src/eth/trace/geth/pre_state.rs b/crates/rpc/rpc-types/src/eth/trace/geth/pre_state.rs index 3a67bba678..04cb7c812b 100644 --- a/crates/rpc/rpc-types/src/eth/trace/geth/pre_state.rs +++ b/crates/rpc/rpc-types/src/eth/trace/geth/pre_state.rs @@ -1,5 +1,5 @@ +use crate::serde_helpers::num::from_int_or_hex_opt; use alloy_primitives::{Address, Bytes, B256, U256}; -use reth_primitives::serde_helper::num::from_int_or_hex_opt; use serde::{Deserialize, Serialize}; use std::collections::{btree_map, BTreeMap}; diff --git a/crates/rpc/rpc-types/src/eth/trace/tracerequest.rs b/crates/rpc/rpc-types/src/eth/trace/tracerequest.rs index 7b6b509eb4..abf4e87777 100644 --- a/crates/rpc/rpc-types/src/eth/trace/tracerequest.rs +++ b/crates/rpc/rpc-types/src/eth/trace/tracerequest.rs @@ -1,7 +1,9 @@ //! Builder style functions for `trace_call` -use crate::{state::StateOverride, trace::parity::TraceType, BlockOverrides, CallRequest}; -use reth_primitives::BlockId; +use crate::{ + eth::block::BlockId, state::StateOverride, trace::parity::TraceType, BlockOverrides, + CallRequest, +}; use serde::{Deserialize, Serialize}; use std::collections::HashSet; diff --git a/crates/rpc/rpc-types/src/eth/tracerequest.rs b/crates/rpc/rpc-types/src/eth/tracerequest.rs deleted file mode 100644 index d895c2367b..0000000000 --- a/crates/rpc/rpc-types/src/eth/tracerequest.rs +++ /dev/null @@ -1,37 +0,0 @@ -use crate::{CallRequest,state::StateOverride,BlockOverrides}; -use std::{collections::HashSet}; -use reth_primitives::{BlockId}; -use reth_rpc::{types::tracerequest::parity::TraceType}; - -pub struct TraceRequest{ - call: CallRequest, - trace_types: HashSet, - block_id: Option, - state_overrides: Option, - block_overrides: Option> -} - -impl TraceRequest{ - - pub fn new(call:CallRequest,trace_types:HashSet) -> Self{ - - Self { call,trace_types,block_id:None, state_overrides: None, block_overrides:None } - - } - - pub fn with_block_id(mut self, block_id: BlockId) -> Self{ - self.block_id = Some(block_id); - self - } - - pub fn with_state_override(mut self, state_overrides:StateOverride) -> Self{ - self.state_overrides = Some(state_overrides); - self - } - - pub fn with_block_overrides(mut self, block_overrides:Box) -> Self{ - self.block_overrides = Some(block_overrides); - self - } - -} \ No newline at end of file diff --git a/crates/rpc/rpc-types/src/eth/transaction/access_list.rs b/crates/rpc/rpc-types/src/eth/transaction/access_list.rs index a5a14ab867..f6fd892a90 100644 --- a/crates/rpc/rpc-types/src/eth/transaction/access_list.rs +++ b/crates/rpc/rpc-types/src/eth/transaction/access_list.rs @@ -1,4 +1,4 @@ -use reth_primitives::{Address, B256, U256}; +use alloy_primitives::{Address, B256, U256}; use serde::{Deserialize, Serialize}; /// A list of addresses and storage keys that the transaction plans to access. diff --git a/crates/rpc/rpc-types/src/eth/transaction/request.rs b/crates/rpc/rpc-types/src/eth/transaction/request.rs index a7420ae036..716c3960b2 100644 --- a/crates/rpc/rpc-types/src/eth/transaction/request.rs +++ b/crates/rpc/rpc-types/src/eth/transaction/request.rs @@ -1,9 +1,11 @@ -use crate::eth::transaction::typed::{ - EIP1559TransactionRequest, EIP2930TransactionRequest, LegacyTransactionRequest, - TransactionKind, TypedTransactionRequest, +use crate::eth::transaction::{ + typed::{ + EIP1559TransactionRequest, EIP2930TransactionRequest, LegacyTransactionRequest, + TransactionKind, TypedTransactionRequest, + }, + AccessList, }; use alloy_primitives::{Address, Bytes, U128, U256, U64, U8}; -use reth_primitives::AccessList; use serde::{Deserialize, Serialize}; /// Represents _all_ transaction requests received from RPC diff --git a/crates/rpc/rpc-types/src/eth/transaction/typed.rs b/crates/rpc/rpc-types/src/eth/transaction/typed.rs index 9cfbf21e9f..6988872e7b 100644 --- a/crates/rpc/rpc-types/src/eth/transaction/typed.rs +++ b/crates/rpc/rpc-types/src/eth/transaction/typed.rs @@ -3,13 +3,12 @@ //! transaction deserialized from the json input of an RPC call. Depending on what fields are set, //! it can be converted into the container type [`TypedTransactionRequest`]. +use crate::eth::transaction::AccessList; use alloy_primitives::{Address, Bytes, B256, U128, U256, U64}; -use alloy_rlp::{BufMut, Decodable, Encodable, Error as RlpError, RlpDecodable, RlpEncodable}; -use reth_primitives::{ - kzg::{Blob, Bytes48}, - AccessList, Transaction, TxEip1559, TxEip2930, TxEip4844, TxLegacy, -}; +use alloy_rlp::{BufMut, Decodable, Encodable, Error as RlpError}; +use c_kzg::{Blob, Bytes48}; use serde::{Deserialize, Serialize}; + /// Container type for various Ethereum transaction requests /// /// Its variants correspond to specific allowed transactions: @@ -24,62 +23,6 @@ pub enum TypedTransactionRequest { EIP4844(Eip4844TransactionRequest), } -impl TypedTransactionRequest { - /// Converts a typed transaction request into a primitive transaction. - /// - /// Returns `None` if any of the following are true: - /// - `nonce` is greater than [`u64::MAX`] - /// - `gas_limit` is greater than [`u64::MAX`] - /// - `value` is greater than [`u128::MAX`] - pub fn into_transaction(self) -> Option { - Some(match self { - TypedTransactionRequest::Legacy(tx) => Transaction::Legacy(TxLegacy { - chain_id: tx.chain_id, - nonce: tx.nonce.to(), - gas_price: tx.gas_price.to(), - gas_limit: tx.gas_limit.try_into().ok()?, - to: tx.kind.into(), - value: tx.value.into(), - input: tx.input, - }), - TypedTransactionRequest::EIP2930(tx) => Transaction::Eip2930(TxEip2930 { - chain_id: tx.chain_id, - nonce: tx.nonce.to(), - gas_price: tx.gas_price.to(), - gas_limit: tx.gas_limit.try_into().ok()?, - to: tx.kind.into(), - value: tx.value.into(), - input: tx.input, - access_list: tx.access_list, - }), - TypedTransactionRequest::EIP1559(tx) => Transaction::Eip1559(TxEip1559 { - chain_id: tx.chain_id, - nonce: tx.nonce.to(), - max_fee_per_gas: tx.max_fee_per_gas.to(), - gas_limit: tx.gas_limit.try_into().ok()?, - to: tx.kind.into(), - value: tx.value.into(), - input: tx.input, - access_list: tx.access_list, - max_priority_fee_per_gas: tx.max_priority_fee_per_gas.to(), - }), - TypedTransactionRequest::EIP4844(tx) => Transaction::Eip4844(TxEip4844 { - chain_id: tx.chain_id, - nonce: tx.nonce.to(), - gas_limit: tx.gas_limit.to(), - max_fee_per_gas: tx.max_fee_per_gas.to(), - max_priority_fee_per_gas: tx.max_priority_fee_per_gas.to(), - to: tx.kind.into(), - value: tx.value.into(), - access_list: tx.access_list, - blob_versioned_hashes: tx.blob_versioned_hashes, - max_fee_per_blob_gas: tx.max_fee_per_blob_gas.to(), - input: tx.input, - }), - }) - } -} - /// Represents a legacy transaction request #[derive(Debug, Clone, PartialEq, Eq)] pub struct LegacyTransactionRequest { @@ -93,7 +36,7 @@ pub struct LegacyTransactionRequest { } /// Represents an EIP-2930 transaction request -#[derive(Debug, Clone, PartialEq, Eq, RlpEncodable, RlpDecodable)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct EIP2930TransactionRequest { pub chain_id: u64, pub nonce: U64, @@ -106,7 +49,7 @@ pub struct EIP2930TransactionRequest { } /// Represents an EIP-1559 transaction request -#[derive(Debug, Clone, PartialEq, Eq, RlpEncodable, RlpDecodable)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct EIP1559TransactionRequest { pub chain_id: u64, pub nonce: U64, @@ -191,15 +134,6 @@ impl Decodable for TransactionKind { } } -impl From for reth_primitives::TransactionKind { - fn from(kind: TransactionKind) -> Self { - match kind { - TransactionKind::Call(to) => reth_primitives::TransactionKind::Call(to), - TransactionKind::Create => reth_primitives::TransactionKind::Create, - } - } -} - /// This represents a set of blobs, and its corresponding commitments and proofs. #[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)] pub struct BlobTransactionSidecar { diff --git a/crates/rpc/rpc-types/src/eth/withdrawal.rs b/crates/rpc/rpc-types/src/eth/withdrawal.rs index d989794d89..0ea2322b7d 100644 --- a/crates/rpc/rpc-types/src/eth/withdrawal.rs +++ b/crates/rpc/rpc-types/src/eth/withdrawal.rs @@ -1,13 +1,20 @@ //! Withdrawal type and serde helpers. +use std::mem; + +use crate::serde_helpers::u64_hex; use alloy_primitives::{Address, U256}; -use alloy_rlp::RlpEncodable; -use reth_primitives::{constants::GWEI_TO_WEI, serde_helper::u64_hex}; +use alloy_rlp::{RlpDecodable, RlpEncodable}; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use serde_with::{serde_as, DeserializeAs, DisplayFromStr, SerializeAs}; +/// Multiplier for converting gwei to wei. +pub const GWEI_TO_WEI: u64 = 1_000_000_000; + /// Withdrawal represents a validator withdrawal from the consensus layer. -#[derive(Debug, Clone, PartialEq, Eq, Default, Hash, RlpEncodable, Serialize, Deserialize)] +#[derive( + Debug, Clone, PartialEq, Eq, Default, Hash, RlpEncodable, RlpDecodable, Serialize, Deserialize, +)] pub struct Withdrawal { /// Monotonically increasing identifier issued by consensus layer. #[serde(with = "u64_hex")] @@ -27,6 +34,12 @@ impl Withdrawal { pub fn amount_wei(&self) -> U256 { U256::from(self.amount) * U256::from(GWEI_TO_WEI) } + + /// Calculate a heuristic for the in-memory size of the [Withdrawal]. + #[inline] + pub fn size(&self) -> usize { + mem::size_of::() + } } /// Same as [Withdrawal] but respects the Beacon API format which uses snake-case and quoted diff --git a/crates/rpc/rpc-types/src/lib.rs b/crates/rpc/rpc-types/src/lib.rs index 5e59041b0f..d655fbcce8 100644 --- a/crates/rpc/rpc-types/src/lib.rs +++ b/crates/rpc/rpc-types/src/lib.rs @@ -14,11 +14,17 @@ mod admin; mod eth; mod mev; +mod net; mod otterscan; +mod peer; mod rpc; +mod serde_helpers; pub use admin::*; pub use eth::*; pub use mev::*; +pub use net::*; pub use otterscan::*; +pub use peer::*; pub use rpc::*; +pub use serde_helpers::*; diff --git a/crates/rpc/rpc-types/src/mev.rs b/crates/rpc/rpc-types/src/mev.rs index 8b082927a4..a37dc8afef 100644 --- a/crates/rpc/rpc-types/src/mev.rs +++ b/crates/rpc/rpc-types/src/mev.rs @@ -1,8 +1,8 @@ //! MEV bundle type bindings #![allow(missing_docs)] +use crate::{BlockId, BlockNumberOrTag, Log}; use alloy_primitives::{Address, Bytes, TxHash, B256, U256, U64}; -use reth_primitives::{BlockId, BlockNumberOrTag, Log}; use serde::{ ser::{SerializeSeq, Serializer}, Deserialize, Deserializer, Serialize, diff --git a/crates/rpc/rpc-types/src/net.rs b/crates/rpc/rpc-types/src/net.rs new file mode 100644 index 0000000000..291241ea3c --- /dev/null +++ b/crates/rpc/rpc-types/src/net.rs @@ -0,0 +1,300 @@ +use crate::PeerId; +use alloy_rlp::{RlpDecodable, RlpEncodable}; +use secp256k1::{SecretKey, SECP256K1}; +use serde_with::{DeserializeFromStr, SerializeDisplay}; +use std::{ + fmt, + fmt::Write, + net::{IpAddr, Ipv4Addr, SocketAddr}, + num::ParseIntError, + str::FromStr, +}; +use url::{Host, Url}; + +/// Represents a ENR in discovery. +/// +/// Note: this is only an excerpt of the [`NodeRecord`] data structure. +#[derive( + Clone, + Copy, + Debug, + Eq, + PartialEq, + Hash, + SerializeDisplay, + DeserializeFromStr, + RlpEncodable, + RlpDecodable, +)] +pub struct NodeRecord { + /// The Address of a node. + pub address: IpAddr, + /// TCP port of the port that accepts connections. + pub tcp_port: u16, + /// UDP discovery port. + pub udp_port: u16, + /// Public key of the discovery service + pub id: PeerId, +} + +impl NodeRecord { + /// Derive the [`NodeRecord`] from the secret key and addr + pub fn from_secret_key(addr: SocketAddr, sk: &SecretKey) -> Self { + let pk = secp256k1::PublicKey::from_secret_key(SECP256K1, sk); + let id = PeerId::from_slice(&pk.serialize_uncompressed()[1..]); + Self::new(addr, id) + } + + /// Converts the `address` into an [`Ipv4Addr`] if the `address` is a mapped + /// [Ipv6Addr](std::net::Ipv6Addr). + /// + /// Returns `true` if the address was converted. + /// + /// See also [std::net::Ipv6Addr::to_ipv4_mapped] + pub fn convert_ipv4_mapped(&mut self) -> bool { + // convert IPv4 mapped IPv6 address + if let IpAddr::V6(v6) = self.address { + if let Some(v4) = v6.to_ipv4_mapped() { + self.address = v4.into(); + return true + } + } + false + } + + /// Same as [Self::convert_ipv4_mapped] but consumes the type + pub fn into_ipv4_mapped(mut self) -> Self { + self.convert_ipv4_mapped(); + self + } + + /// Creates a new record from a socket addr and peer id. + #[allow(unused)] + pub fn new(addr: SocketAddr, id: PeerId) -> Self { + Self { address: addr.ip(), tcp_port: addr.port(), udp_port: addr.port(), id } + } + + /// The TCP socket address of this node + #[must_use] + pub fn tcp_addr(&self) -> SocketAddr { + SocketAddr::new(self.address, self.tcp_port) + } + + /// The UDP socket address of this node + #[must_use] + pub fn udp_addr(&self) -> SocketAddr { + SocketAddr::new(self.address, self.udp_port) + } +} + +impl fmt::Display for NodeRecord { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("enode://")?; + alloy_primitives::hex::encode(self.id.as_slice()).fmt(f)?; + f.write_char('@')?; + match self.address { + IpAddr::V4(ip) => { + ip.fmt(f)?; + } + IpAddr::V6(ip) => { + // encapsulate with brackets + f.write_char('[')?; + ip.fmt(f)?; + f.write_char(']')?; + } + } + f.write_char(':')?; + self.tcp_port.fmt(f)?; + if self.tcp_port != self.udp_port { + f.write_str("?discport=")?; + self.udp_port.fmt(f)?; + } + + Ok(()) + } +} + +/// Possible error types when parsing a `NodeRecord` +#[derive(Debug, thiserror::Error)] +pub enum NodeRecordParseError { + /// Invalid url + #[error("Failed to parse url: {0}")] + InvalidUrl(String), + /// Invalid id + #[error("Failed to parse id")] + InvalidId(String), + /// Invalid discport + #[error("Failed to discport query: {0}")] + Discport(ParseIntError), +} + +impl FromStr for NodeRecord { + type Err = NodeRecordParseError; + + fn from_str(s: &str) -> Result { + let url = Url::parse(s).map_err(|e| NodeRecordParseError::InvalidUrl(e.to_string()))?; + + let address = match url.host() { + Some(Host::Ipv4(ip)) => IpAddr::V4(ip), + Some(Host::Ipv6(ip)) => IpAddr::V6(ip), + Some(Host::Domain(ip)) => IpAddr::V4( + Ipv4Addr::from_str(ip) + .map_err(|e| NodeRecordParseError::InvalidUrl(e.to_string()))?, + ), + _ => return Err(NodeRecordParseError::InvalidUrl(format!("invalid host: {url:?}"))), + }; + let port = url + .port() + .ok_or_else(|| NodeRecordParseError::InvalidUrl("no port specified".to_string()))?; + + let udp_port = if let Some(discovery_port) = url + .query_pairs() + .find_map(|(maybe_disc, port)| (maybe_disc.as_ref() == "discport").then_some(port)) + { + discovery_port.parse::().map_err(NodeRecordParseError::Discport)? + } else { + port + }; + + let id = url + .username() + .parse::() + .map_err(|e| NodeRecordParseError::InvalidId(e.to_string()))?; + + Ok(Self { address, id, tcp_port: port, udp_port }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use alloy_rlp::{Decodable, Encodable}; + use bytes::BytesMut; + use rand::{thread_rng, Rng, RngCore}; + + #[test] + fn test_mapped_ipv6() { + let mut rng = thread_rng(); + + let v4: Ipv4Addr = "0.0.0.0".parse().unwrap(); + let v6 = v4.to_ipv6_mapped(); + + let record = NodeRecord { + address: v6.into(), + tcp_port: rng.gen(), + udp_port: rng.gen(), + id: rng.gen(), + }; + + assert!(record.clone().convert_ipv4_mapped()); + assert_eq!(record.into_ipv4_mapped().address, IpAddr::from(v4)); + } + + #[test] + fn test_mapped_ipv4() { + let mut rng = thread_rng(); + let v4: Ipv4Addr = "0.0.0.0".parse().unwrap(); + + let record = NodeRecord { + address: v4.into(), + tcp_port: rng.gen(), + udp_port: rng.gen(), + id: rng.gen(), + }; + + assert!(!record.clone().convert_ipv4_mapped()); + assert_eq!(record.into_ipv4_mapped().address, IpAddr::from(v4)); + } + + #[test] + fn test_noderecord_codec_ipv4() { + let mut rng = thread_rng(); + for _ in 0..100 { + let mut ip = [0u8; 4]; + rng.fill_bytes(&mut ip); + let record = NodeRecord { + address: IpAddr::V4(ip.into()), + tcp_port: rng.gen(), + udp_port: rng.gen(), + id: rng.gen(), + }; + + let mut buf = BytesMut::new(); + record.encode(&mut buf); + + let decoded = NodeRecord::decode(&mut buf.as_ref()).unwrap(); + assert_eq!(record, decoded); + } + } + + #[test] + fn test_noderecord_codec_ipv6() { + let mut rng = thread_rng(); + for _ in 0..100 { + let mut ip = [0u8; 16]; + rng.fill_bytes(&mut ip); + let record = NodeRecord { + address: IpAddr::V6(ip.into()), + tcp_port: rng.gen(), + udp_port: rng.gen(), + id: rng.gen(), + }; + + let mut buf = BytesMut::new(); + record.encode(&mut buf); + + let decoded = NodeRecord::decode(&mut buf.as_ref()).unwrap(); + assert_eq!(record, decoded); + } + } + + #[test] + fn test_url_parse() { + let url = "enode://6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0@10.3.58.6:30303?discport=30301"; + let node: NodeRecord = url.parse().unwrap(); + assert_eq!(node, NodeRecord { + address: IpAddr::V4([10,3,58,6].into()), + tcp_port: 30303, + udp_port: 30301, + id: "6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0".parse().unwrap(), + }) + } + + #[test] + fn test_node_display() { + let url = "enode://6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0@10.3.58.6:30303"; + let node: NodeRecord = url.parse().unwrap(); + assert_eq!(url, &format!("{node}")); + } + + #[test] + fn test_node_display_discport() { + let url = "enode://6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0@10.3.58.6:30303?discport=30301"; + let node: NodeRecord = url.parse().unwrap(); + assert_eq!(url, &format!("{node}")); + } + + #[test] + fn test_node_serialize() { + let node = NodeRecord{ + address: IpAddr::V4([10, 3, 58, 6].into()), + tcp_port: 30303u16, + udp_port: 30301u16, + id: PeerId::from_str("6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0").unwrap(), + }; + let ser = serde_json::to_string::(&node).expect("couldn't serialize"); + assert_eq!(ser, "\"enode://6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0@10.3.58.6:30303?discport=30301\"") + } + + #[test] + fn test_node_deserialize() { + let url = "\"enode://6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0@10.3.58.6:30303?discport=30301\""; + let node: NodeRecord = serde_json::from_str(url).expect("couldn't deserialize"); + assert_eq!(node, NodeRecord{ + address: IpAddr::V4([10, 3, 58, 6].into()), + tcp_port: 30303u16, + udp_port: 30301u16, + id: PeerId::from_str("6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0").unwrap(), + }) + } +} diff --git a/crates/rpc/rpc-types/src/peer.rs b/crates/rpc/rpc-types/src/peer.rs new file mode 100644 index 0000000000..a07e61d002 --- /dev/null +++ b/crates/rpc/rpc-types/src/peer.rs @@ -0,0 +1,4 @@ +use alloy_primitives::B512; + +/// Alias for a peer identifier +pub type PeerId = B512; diff --git a/crates/rpc/rpc-types/src/serde_helpers/json_u256.rs b/crates/rpc/rpc-types/src/serde_helpers/json_u256.rs new file mode 100644 index 0000000000..162d55a821 --- /dev/null +++ b/crates/rpc/rpc-types/src/serde_helpers/json_u256.rs @@ -0,0 +1,93 @@ +//! Json U256 serde helpers. + +use alloy_primitives::U256; +use serde::{ + de::{Error, Visitor}, + Deserialize, Deserializer, Serialize, Serializer, +}; +use std::{fmt, str::FromStr}; + +/// Wrapper around primitive U256 type that also supports deserializing numbers +#[derive(Default, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)] +pub struct JsonU256(pub U256); + +impl From for U256 { + fn from(value: JsonU256) -> Self { + value.0 + } +} + +impl From for JsonU256 { + fn from(value: U256) -> Self { + JsonU256(value) + } +} + +impl Serialize for JsonU256 { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + self.0.serialize(serializer) + } +} + +impl<'a> Deserialize<'a> for JsonU256 { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'a>, + { + deserializer.deserialize_any(JsonU256Visitor) + } +} + +struct JsonU256Visitor; + +impl<'a> Visitor<'a> for JsonU256Visitor { + type Value = JsonU256; + + fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(formatter, "a hex encoding or decimal number") + } + + fn visit_u64(self, value: u64) -> Result + where + E: Error, + { + Ok(JsonU256(U256::from(value))) + } + + fn visit_str(self, value: &str) -> Result + where + E: Error, + { + let value = match value.len() { + 0 => U256::ZERO, + 2 if value.starts_with("0x") => U256::ZERO, + _ if value.starts_with("0x") => U256::from_str(value).map_err(|e| { + Error::custom(format!("Parsing JsonU256 as hex failed {value}: {e}")) + })?, + _ => U256::from_str_radix(value, 10).map_err(|e| { + Error::custom(format!("Parsing JsonU256 as decimal failed {value}: {e:?}")) + })?, + }; + + Ok(JsonU256(value)) + } + + fn visit_string(self, value: String) -> Result + where + E: Error, + { + self.visit_str(value.as_ref()) + } +} + +/// Supports parsing `U256` numbers as strings via [JsonU256] +pub fn deserialize_json_u256<'de, D>(deserializer: D) -> Result +where + D: Deserializer<'de>, +{ + let num = JsonU256::deserialize(deserializer)?; + Ok(num.into()) +} diff --git a/crates/rpc/rpc-types/src/serde_helpers/mod.rs b/crates/rpc/rpc-types/src/serde_helpers/mod.rs new file mode 100644 index 0000000000..1c45b0d56d --- /dev/null +++ b/crates/rpc/rpc-types/src/serde_helpers/mod.rs @@ -0,0 +1,30 @@ +//! Serde helpers for primitive types. + +use alloy_primitives::U256; +use serde::{Deserialize, Deserializer, Serializer}; + +pub mod json_u256; +pub mod num; +/// Storage related helpers. +pub mod storage; +pub mod u64_hex; + +/// Deserializes the input into a U256, accepting both 0x-prefixed hex and decimal strings with +/// arbitrary precision, defined by serde_json's [`Number`](serde_json::Number). +pub fn from_int_or_hex<'de, D>(deserializer: D) -> Result +where + D: Deserializer<'de>, +{ + num::NumberOrHexU256::deserialize(deserializer)?.try_into_u256() +} + +/// Serialize a byte vec as a hex string _without_ the "0x" prefix. +/// +/// This behaves the same as [`hex::encode`](alloy_primitives::hex::encode). +pub fn serialize_hex_string_no_prefix(x: T, s: S) -> Result +where + S: Serializer, + T: AsRef<[u8]>, +{ + s.serialize_str(&alloy_primitives::hex::encode(x.as_ref())) +} diff --git a/crates/rpc/rpc-types/src/serde_helpers/num.rs b/crates/rpc/rpc-types/src/serde_helpers/num.rs new file mode 100644 index 0000000000..d1e6959065 --- /dev/null +++ b/crates/rpc/rpc-types/src/serde_helpers/num.rs @@ -0,0 +1,139 @@ +//! Numeric serde helpers. + +use alloy_primitives::{U256, U64}; +use serde::{de, Deserialize, Deserializer, Serialize}; +use std::str::FromStr; + +/// A `u64` wrapper type that deserializes from hex or a u64 and serializes as hex. +/// +/// +/// ```rust +/// use reth_rpc_types::num::U64HexOrNumber; +/// let number_json = "100"; +/// let hex_json = "\"0x64\""; +/// +/// let number: U64HexOrNumber = serde_json::from_str(number_json).unwrap(); +/// let hex: U64HexOrNumber = serde_json::from_str(hex_json).unwrap(); +/// assert_eq!(number, hex); +/// assert_eq!(hex.to(), 100); +/// ``` +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)] +pub struct U64HexOrNumber(U64); + +impl U64HexOrNumber { + /// Returns the wrapped u64 + pub fn to(self) -> u64 { + self.0.to() + } +} + +impl From for U64HexOrNumber { + fn from(value: u64) -> Self { + Self(U64::from(value)) + } +} + +impl From for U64HexOrNumber { + fn from(value: U64) -> Self { + Self(value) + } +} + +impl From for u64 { + fn from(value: U64HexOrNumber) -> Self { + value.to() + } +} + +impl From for U64 { + fn from(value: U64HexOrNumber) -> Self { + value.0 + } +} + +impl<'de> Deserialize<'de> for U64HexOrNumber { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + #[derive(Deserialize)] + #[serde(untagged)] + enum NumberOrHexU64 { + Hex(U64), + Int(u64), + } + match NumberOrHexU64::deserialize(deserializer)? { + NumberOrHexU64::Int(val) => Ok(val.into()), + NumberOrHexU64::Hex(val) => Ok(val.into()), + } + } +} + +/// serde functions for handling primitive optional `u64` as [U64] +pub mod u64_hex_or_decimal_opt { + use crate::serde_helpers::num::U64HexOrNumber; + use serde::{Deserialize, Deserializer, Serialize, Serializer}; + + /// Deserializes an `u64` accepting a hex quantity string with optional 0x prefix or + /// a number + pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> + where + D: Deserializer<'de>, + { + match Option::::deserialize(deserializer)? { + Some(val) => Ok(Some(val.into())), + None => Ok(None), + } + } + + /// Serializes u64 as hex string + pub fn serialize(value: &Option, s: S) -> Result { + match value { + Some(val) => U64HexOrNumber::from(*val).serialize(s), + None => s.serialize_none(), + } + } +} + +/// Deserializes the input into an `Option`, using [`from_int_or_hex`] to deserialize the +/// inner value. +pub fn from_int_or_hex_opt<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + match Option::::deserialize(deserializer)? { + Some(val) => val.try_into_u256().map(Some), + None => Ok(None), + } +} + +/// An enum that represents either a [serde_json::Number] integer, or a hex [U256]. +#[derive(Debug, Deserialize)] +#[serde(untagged)] +pub enum NumberOrHexU256 { + /// An integer + Int(serde_json::Number), + /// A hex U256 + Hex(U256), +} + +impl NumberOrHexU256 { + /// Tries to convert this into a [U256]]. + pub fn try_into_u256(self) -> Result { + match self { + NumberOrHexU256::Int(num) => { + U256::from_str(num.to_string().as_str()).map_err(E::custom) + } + NumberOrHexU256::Hex(val) => Ok(val), + } + } +} + +/// Deserializes the input into a U256, accepting both 0x-prefixed hex and decimal strings with +/// arbitrary precision, defined by serde_json's [`Number`](serde_json::Number). +pub fn from_int_or_hex<'de, D>(deserializer: D) -> Result +where + D: Deserializer<'de>, +{ + NumberOrHexU256::deserialize(deserializer)?.try_into_u256() +} diff --git a/crates/rpc/rpc-types/src/serde_helpers/storage.rs b/crates/rpc/rpc-types/src/serde_helpers/storage.rs new file mode 100644 index 0000000000..952f5ebe0e --- /dev/null +++ b/crates/rpc/rpc-types/src/serde_helpers/storage.rs @@ -0,0 +1,102 @@ +use alloy_primitives::{Bytes, B256, U256}; +use serde::{Deserialize, Deserializer, Serialize}; +use std::{collections::HashMap, fmt::Write}; + +/// A storage key type that can be serialized to and from a hex string up to 32 bytes. Used for +/// `eth_getStorageAt` and `eth_getProof` RPCs. +/// +/// This is a wrapper type meant to mirror geth's serialization and deserialization behavior for +/// storage keys. +/// +/// In `eth_getStorageAt`, this is used for deserialization of the `index` field. Internally, the +/// index is a [B256], but in `eth_getStorageAt` requests, its serialization can be _up to_ 32 +/// bytes. To support this, the storage key is deserialized first as a U256, and converted to a +/// B256 for use internally. +/// +/// `eth_getProof` also takes storage keys up to 32 bytes as input, so the `keys` field is +/// similarly deserialized. However, geth populates the storage proof `key` fields in the response +/// by mirroring the `key` field used in the input. +/// * See how `storageKey`s (the input) are populated in the `StorageResult` (the output): +/// +/// +/// The contained [B256] and From implementation for String are used to preserve the input and +/// implement this behavior from geth. +#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)] +#[serde(from = "U256", into = "String")] +pub struct JsonStorageKey(pub B256); + +impl From for JsonStorageKey { + fn from(value: U256) -> Self { + // SAFETY: Address (B256) and U256 have the same number of bytes + JsonStorageKey(B256::from(value.to_be_bytes())) + } +} + +impl From for String { + fn from(value: JsonStorageKey) -> Self { + // SAFETY: Address (B256) and U256 have the same number of bytes + let uint = U256::from_be_bytes(value.0 .0); + + // serialize byte by byte + // + // this is mainly so we can return an output that hive testing expects, because the + // `eth_getProof` implementation in geth simply mirrors the input + // + // see the use of `hexKey` in the `eth_getProof` response: + // + let bytes = uint.to_be_bytes_trimmed_vec(); + let mut hex = String::with_capacity(2 + bytes.len() * 2); + hex.push_str("0x"); + for byte in bytes { + write!(hex, "{:02x}", byte).unwrap(); + } + hex + } +} + +/// Converts a Bytes value into a B256, accepting inputs that are less than 32 bytes long. These +/// inputs will be left padded with zeros. +pub fn from_bytes_to_b256<'de, D>(bytes: Bytes) -> Result +where + D: Deserializer<'de>, +{ + if bytes.0.len() > 32 { + return Err(serde::de::Error::custom("input too long to be a B256")); + } + + // left pad with zeros to 32 bytes + let mut padded = [0u8; 32]; + padded[32 - bytes.0.len()..].copy_from_slice(&bytes.0); + + // then convert to B256 without a panic + Ok(B256::from_slice(&padded)) +} + +/// Deserializes the input into an Option>, using [from_bytes_to_b256] which +/// allows cropped values: +/// +/// ```json +/// { +/// "0x0000000000000000000000000000000000000000000000000000000000000001": "0x22" +/// } +/// ``` +pub fn deserialize_storage_map<'de, D>( + deserializer: D, +) -> Result>, D::Error> +where + D: Deserializer<'de>, +{ + let map = Option::>::deserialize(deserializer)?; + match map { + Some(mut map) => { + let mut res_map = HashMap::with_capacity(map.len()); + for (k, v) in map.drain() { + let k_deserialized = from_bytes_to_b256::<'de, D>(k)?; + let v_deserialized = from_bytes_to_b256::<'de, D>(v)?; + res_map.insert(k_deserialized, v_deserialized); + } + Ok(Some(res_map)) + } + None => Ok(None), + } +} diff --git a/crates/rpc/rpc-types/src/serde_helpers/u64_hex.rs b/crates/rpc/rpc-types/src/serde_helpers/u64_hex.rs new file mode 100644 index 0000000000..dc8f9a96ef --- /dev/null +++ b/crates/rpc/rpc-types/src/serde_helpers/u64_hex.rs @@ -0,0 +1,18 @@ +//! Helper to deserialize an `u64` from [U64] accepting a hex quantity string with optional 0x +//! prefix + +use alloy_primitives::U64; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; + +/// Deserializes an `u64` from [U64] accepting a hex quantity string with optional 0x prefix +pub fn deserialize<'de, D>(deserializer: D) -> Result +where + D: Deserializer<'de>, +{ + U64::deserialize(deserializer).map(|val| val.to()) +} + +/// Serializes u64 as hex string +pub fn serialize(value: &u64, s: S) -> Result { + U64::from(*value).serialize(s) +} diff --git a/crates/rpc/rpc/src/debug.rs b/crates/rpc/rpc/src/debug.rs index 6c79e4ed21..7bad7e8c4e 100644 --- a/crates/rpc/rpc/src/debug.rs +++ b/crates/rpc/rpc/src/debug.rs @@ -354,7 +354,8 @@ where let StateContext { transaction_index, block_number } = state_context.unwrap_or_default(); let transaction_index = transaction_index.unwrap_or_default(); - let target_block = block_number.unwrap_or(BlockId::Number(BlockNumberOrTag::Latest)); + let target_block = block_number + .unwrap_or(reth_rpc_types::BlockId::Number(reth_rpc_types::BlockNumberOrTag::Latest)); let ((cfg, block_env, _), block) = futures::try_join!( self.inner.eth_api.evm_env_at(target_block), self.inner.eth_api.block_by_id(target_block), diff --git a/crates/rpc/rpc/src/eth/api/call.rs b/crates/rpc/rpc/src/eth/api/call.rs index 9c364263fb..e579ce37a8 100644 --- a/crates/rpc/rpc/src/eth/api/call.rs +++ b/crates/rpc/rpc/src/eth/api/call.rs @@ -21,7 +21,7 @@ use reth_rpc_types::{ state::StateOverride, AccessListWithGasUsed, BlockError, Bundle, CallRequest, EthCallResponse, StateContext, }; -use reth_rpc_types_compat::log::from_primitive_access_list; +use reth_rpc_types_compat::log::{from_primitive_access_list, to_primitive_access_list}; use reth_transaction_pool::TransactionPool; use revm::{ db::{CacheDB, DatabaseRef}, @@ -87,6 +87,7 @@ where let transaction_index = transaction_index.unwrap_or_default(); let target_block = block_number.unwrap_or(BlockId::Number(BlockNumberOrTag::Latest)); + let ((cfg, block_env, _), block) = futures::try_join!(self.evm_env_at(target_block), self.block_by_id(target_block))?; @@ -386,7 +387,8 @@ where let initial = request.access_list.take().unwrap_or_default(); let precompiles = get_precompiles(env.cfg.spec_id); - let mut inspector = AccessListInspector::new(initial, from, to, precompiles); + let mut inspector = + AccessListInspector::new(to_primitive_access_list(initial), from, to, precompiles); let (result, env) = inspect(&mut db, env, &mut inspector)?; match result.result { @@ -403,7 +405,7 @@ where let access_list = inspector.into_access_list(); // calculate the gas used using the access list - request.access_list = Some(access_list.clone()); + request.access_list = Some(from_primitive_access_list(access_list.clone())); let gas_used = self.estimate_gas_with(env.cfg, env.block, request, db.db.state())?; Ok(AccessListWithGasUsed { access_list: from_primitive_access_list(access_list), gas_used }) diff --git a/crates/rpc/rpc/src/eth/bundle.rs b/crates/rpc/rpc/src/eth/bundle.rs index c70075ede5..495efb003e 100644 --- a/crates/rpc/rpc/src/eth/bundle.rs +++ b/crates/rpc/rpc/src/eth/bundle.rs @@ -55,9 +55,8 @@ where let transactions = txs.into_iter().map(recover_raw_transaction).collect::, _>>()?; - - let (cfg, mut block_env, at) = - self.inner.eth_api.evm_env_at(state_block_number.into()).await?; + let block_id: reth_rpc_types::BlockId = state_block_number.into(); + let (cfg, mut block_env, at) = self.inner.eth_api.evm_env_at(block_id).await?; // need to adjust the timestamp for the next block if let Some(timestamp) = timestamp { diff --git a/crates/rpc/rpc/src/eth/logs_utils.rs b/crates/rpc/rpc/src/eth/logs_utils.rs index 6090657764..2fdd711356 100644 --- a/crates/rpc/rpc/src/eth/logs_utils.rs +++ b/crates/rpc/rpc/src/eth/logs_utils.rs @@ -1,5 +1,6 @@ use reth_primitives::{BlockNumHash, ChainInfo, Receipt, TxHash, U256}; use reth_rpc_types::{FilteredParams, Log}; +use reth_rpc_types_compat::log::from_primitive_log; /// Returns all matching logs of a block's receipts grouped with the hash of their transaction. pub(crate) fn matching_block_logs( @@ -60,8 +61,8 @@ pub(crate) fn log_matches_filter( if params.filter.is_some() && (!params.filter_block_range(block.number) || !params.filter_block_hash(block.hash) || - !params.filter_address(log) || - !params.filter_topics(log)) + !params.filter_address(&from_primitive_log(log.clone())) || + !params.filter_topics(&from_primitive_log(log.clone()))) { return false } @@ -97,7 +98,7 @@ pub(crate) fn get_filter_block_range( #[cfg(test)] mod tests { use super::*; - use reth_primitives::BlockNumberOrTag; + use reth_rpc_types::Filter; #[test] @@ -159,8 +160,8 @@ mod tests { let start_block = info.best_number; let (from_block_number, to_block_number) = get_filter_block_range( - from_block.and_then(BlockNumberOrTag::as_number), - to_block.and_then(BlockNumberOrTag::as_number), + from_block.and_then(reth_rpc_types::BlockNumberOrTag::as_number), + to_block.and_then(reth_rpc_types::BlockNumberOrTag::as_number), start_block, info, ); diff --git a/crates/rpc/rpc/src/eth/pubsub.rs b/crates/rpc/rpc/src/eth/pubsub.rs index 3f3c671e71..b720ebec16 100644 --- a/crates/rpc/rpc/src/eth/pubsub.rs +++ b/crates/rpc/rpc/src/eth/pubsub.rs @@ -294,7 +294,9 @@ where .committed() .map(|chain| chain.headers().collect::>()) .unwrap_or_default(); - futures::stream::iter(headers.into_iter().map(Header::from_primitive_with_hash)) + futures::stream::iter( + headers.into_iter().map(reth_rpc_types_compat::block::from_primitive_with_hash), + ) }) } diff --git a/crates/rpc/rpc/src/eth/revm_utils.rs b/crates/rpc/rpc/src/eth/revm_utils.rs index 4b1911599b..8e8bac970c 100644 --- a/crates/rpc/rpc/src/eth/revm_utils.rs +++ b/crates/rpc/rpc/src/eth/revm_utils.rs @@ -3,7 +3,7 @@ use crate::eth::error::{EthApiError, EthResult, RpcInvalidTransactionError}; use reth_primitives::{ revm::env::{fill_tx_env, fill_tx_env_with_recovered}, - AccessList, Address, TransactionSigned, TransactionSignedEcRecovered, TxHash, B256, U256, + Address, TransactionSigned, TransactionSignedEcRecovered, TxHash, B256, U256, }; use reth_rpc_types::{ state::{AccountOverride, StateOverride}, @@ -309,7 +309,9 @@ pub(crate) fn create_txn_env(block_env: &BlockEnv, request: CallRequest) -> EthR value: value.unwrap_or_default(), data: input.try_into_unique_input()?.unwrap_or_default(), chain_id: chain_id.map(|c| c.to()), - access_list: access_list.map(AccessList::into_flattened).unwrap_or_default(), + access_list: access_list + .map(reth_rpc_types::AccessList::into_flattened) + .unwrap_or_default(), // EIP-4844 fields blob_hashes: blob_versioned_hashes.unwrap_or_default(), max_fee_per_blob_gas, diff --git a/crates/rpc/rpc/src/eth/signer.rs b/crates/rpc/rpc/src/eth/signer.rs index a271c9ca52..e3c911aa7a 100644 --- a/crates/rpc/rpc/src/eth/signer.rs +++ b/crates/rpc/rpc/src/eth/signer.rs @@ -7,6 +7,7 @@ use reth_primitives::{ }; use reth_rpc_types::TypedTransactionRequest; +use reth_rpc_types_compat::transaction::to_primitive_transaction; use secp256k1::SecretKey; use std::collections::HashMap; @@ -78,7 +79,8 @@ impl EthSigner for DevSigner { address: &Address, ) -> Result { // convert to primitive transaction - let transaction = request.into_transaction().ok_or(SignError::InvalidTransactionRequest)?; + let transaction = + to_primitive_transaction(request).ok_or(SignError::InvalidTransactionRequest)?; let tx_signature_hash = transaction.signature_hash(); let signature = self.sign_hash(tx_signature_hash, *address)?; diff --git a/crates/rpc/rpc/src/trace.rs b/crates/rpc/rpc/src/trace.rs index f562b45556..5239c8ec60 100644 --- a/crates/rpc/rpc/src/trace.rs +++ b/crates/rpc/rpc/src/trace.rs @@ -66,7 +66,9 @@ where { /// Executes the given call and returns a number of possible traces for it. pub async fn trace_call(&self, trace_request: TraceRequest) -> EthResult { - let at = trace_request.block_id.unwrap_or(BlockId::Number(BlockNumberOrTag::Latest)); + let at = trace_request + .block_id + .unwrap_or(reth_rpc_types::BlockId::Number(reth_rpc_types::BlockNumberOrTag::Latest)); let config = tracing_config(&trace_request.trace_types); let overrides = EvmOverrides::new(trace_request.state_overrides, trace_request.block_overrides); diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 89f3443881..d3a2fd9da8 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -13,6 +13,7 @@ reth-provider.workspace = true reth-rpc-builder.workspace = true reth-rpc-types.workspace = true +reth-rpc-types-compat.workspace = true reth-revm.workspace = true reth-blockchain-tree.workspace = true diff --git a/examples/db-access.rs b/examples/db-access.rs index faed7fc1d4..235193a38f 100644 --- a/examples/db-access.rs +++ b/examples/db-access.rs @@ -5,6 +5,7 @@ use reth_provider::{ StateProvider, TransactionsProvider, }; use reth_rpc_types::{Filter, FilteredParams}; +use reth_rpc_types_compat::log::from_primitive_log; use std::path::Path; @@ -197,7 +198,8 @@ fn receipts_provider_example