feat: Completely decouple rpc-types to standalone crate (#5193)

Co-authored-by: Matthias Seitz <matthias.seitz@outlook.de>
This commit is contained in:
evalir
2023-10-28 17:00:47 +09:00
committed by GitHub
parent ffe1a88417
commit 76e751eef4
53 changed files with 1617 additions and 957 deletions

11
Cargo.lock generated
View File

@ -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]]

View File

@ -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" }

View File

@ -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<BuiltPayload> for ExecutionPayloadEnvelopeV3 {
// Spec:
// <https://github.com/ethereum/execution-apis/blob/fe8e13c288c592ec154ce25c534e26cb7ce0530d/src/engine/cancun.md#specification-2>
should_override_builder: false,
blobs_bundle: sidecars.into(),
blobs_bundle: sidecars
.into_iter()
.map(from_primitive_sidecar)
.collect::<Vec<_>>()
.into(),
}
}
}

View File

@ -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.

View File

@ -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<u64> {
match self {
BlockHashOrNumber::Hash(_) => None,
BlockHashOrNumber::Number(num) => Some(num),
}
}
}
impl From<B256> for BlockHashOrNumber {
fn from(value: B256) -> Self {
BlockHashOrNumber::Hash(value)
}
}
impl From<u64> 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<Self> {
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<Self, Self::Err> {
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
/// <https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1898.md>
#[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<B256> {
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<u64> for BlockId {
fn from(num: u64) -> Self {
BlockNumberOrTag::Number(num).into()
}
}
impl From<BlockNumberOrTag> for BlockId {
fn from(num: BlockNumberOrTag) -> Self {
BlockId::Number(num)
}
}
impl From<B256> for BlockId {
fn from(block_hash: B256) -> Self {
BlockId::Hash(RpcBlockHash { block_hash, require_canonical: None })
}
}
impl From<(B256, Option<bool>)> for BlockId {
fn from(hash_can: (B256, Option<bool>)) -> Self {
BlockId::Hash(RpcBlockHash { block_hash: hash_can.0, require_canonical: hash_can.1 })
}
}
impl From<RpcBlockHash> for BlockId {
fn from(hash_can: RpcBlockHash) -> Self {
BlockId::Hash(hash_can)
}
}
impl From<BlockHashOrNumber> 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<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
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<D>(deserializer: D) -> Result<Self, D::Error>
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<E>(self, v: &str) -> Result<Self::Value, E>
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: <https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1898.md>
// 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 <https://github.com/ethereum/go-ethereum/blob/ee530c0d5aa70d2c00ab5691a89ab431b73f8165/rpc/types.go#L184-L184>
if v.len() == 66 {
Ok(BlockId::Hash(v.parse::<B256>().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<A>(self, mut map: A) -> Result<Self::Value, A::Error>
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::<String>()? {
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::<BlockNumberOrTag>()?)
}
"blockHash" => {
if number.is_some() || block_hash.is_some() {
return Err(serde::de::Error::duplicate_field("blockHash"))
}
block_hash = Some(map.next_value::<B256>()?);
}
"requireCanonical" => {
if number.is_some() || require_canonical.is_some() {
return Err(serde::de::Error::duplicate_field("requireCanonical"))
}
require_canonical = Some(map.next_value::<bool>()?)
}
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<u64> {
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<u64> for BlockNumberOrTag {
fn from(num: u64) -> Self {
BlockNumberOrTag::Number(num)
}
}
impl From<U64> for BlockNumberOrTag {
fn from(num: U64) -> Self {
num.into_limbs()[0].into()
}
}
impl Serialize for BlockNumberOrTag {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
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<D>(deserializer: D) -> Result<Self, D::Error>
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<Self, Self::Err> {
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.
/// <https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1898.md#specification>
#[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<bool>,
}
impl RpcBlockHash {
pub fn from_hash(block_hash: B256, require_canonical: Option<bool>) -> Self {
RpcBlockHash { block_hash, require_canonical }
}
}
impl From<B256> for RpcBlockHash {
fn from(value: B256) -> Self {
Self::from_hash(value, None)
}
}
impl From<RpcBlockHash> for B256 {
fn from(value: RpcBlockHash) -> Self {
value.block_hash
}
}
impl AsRef<B256> 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]

View File

@ -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::{

View File

@ -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;
// <https://github.com/ledgerwatch/erigon/blob/610e648dc43ec8cd6563313e28f06f534a9091b3/params/bootnodes.go>
@ -180,60 +66,18 @@ fn parse_nodes(nodes: impl IntoIterator<Item = impl AsRef<str>>) -> Vec<NodeReco
nodes.into_iter().map(|s| s.as_ref().parse().unwrap()).collect()
}
/// Possible error types when parsing a `NodeRecord`
#[derive(Debug, thiserror::Error)]
pub enum NodeRecordParseError {
#[error("failed to parse url: {0}")]
InvalidUrl(String),
#[error("failed to parse id")]
InvalidId(String),
#[error("failed to discport query: {0}")]
Discport(ParseIntError),
}
impl FromStr for NodeRecord {
type Err = NodeRecordParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
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::<u16>().map_err(NodeRecordParseError::Discport)?
} else {
port
};
let id = url
.username()
.parse::<PeerId>()
.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() {

View File

@ -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)]

View File

@ -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 { .. }));
}

View File

@ -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 {

View File

@ -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};

View File

@ -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<reth_primitives::AccessListItem> = list
.0
.into_iter()
.map(|item| reth_primitives::AccessListItem {
address: item.address,
storage_keys: item.storage_keys,
})
.collect();
reth_primitives::AccessList(converted_list)
}

View File

@ -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 {

View File

@ -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();

View File

@ -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<reth_primitives::Transaction> {
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,
}
}

View File

@ -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

View File

@ -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,

View File

@ -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.

View File

@ -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<B256>,
}
// === 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.
/// <https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1898.md#specification>
#[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<bool>,
}
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,
impl RpcBlockHash {
/// Returns an [RpcBlockHash] from a [B256].
pub const fn from_hash(block_hash: B256, require_canonical: Option<bool>) -> Self {
RpcBlockHash { block_hash, require_canonical }
}
}
impl From<B256> for RpcBlockHash {
fn from(value: B256) -> Self {
Self::from_hash(value, None)
}
}
impl From<RpcBlockHash> for B256 {
fn from(value: RpcBlockHash) -> Self {
value.block_hash
}
}
impl AsRef<B256> 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<u64> {
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<u64> for BlockNumberOrTag {
fn from(num: u64) -> Self {
BlockNumberOrTag::Number(num)
}
}
impl From<U64> for BlockNumberOrTag {
fn from(num: U64) -> Self {
num.into_limbs()[0].into()
}
}
impl Serialize for BlockNumberOrTag {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
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<D>(deserializer: D) -> Result<Self, D::Error>
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<Self, Self::Err> {
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
/// <https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1898.md>
#[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<B256> {
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<u64> for BlockId {
fn from(num: u64) -> Self {
BlockNumberOrTag::Number(num).into()
}
}
impl From<BlockNumberOrTag> for BlockId {
fn from(num: BlockNumberOrTag) -> Self {
BlockId::Number(num)
}
}
impl From<B256> for BlockId {
fn from(block_hash: B256) -> Self {
BlockId::Hash(RpcBlockHash { block_hash, require_canonical: None })
}
}
impl From<(B256, Option<bool>)> for BlockId {
fn from(hash_can: (B256, Option<bool>)) -> Self {
BlockId::Hash(RpcBlockHash { block_hash: hash_can.0, require_canonical: hash_can.1 })
}
}
impl Serialize for BlockId {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
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<D>(deserializer: D) -> Result<Self, D::Error>
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<E>(self, v: &str) -> Result<Self::Value, E>
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: <https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1898.md>
// 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 <https://github.com/ethereum/go-ethereum/blob/ee530c0d5aa70d2c00ab5691a89ab431b73f8165/rpc/types.go#L184-L184>
if v.len() == 66 {
Ok(BlockId::Hash(v.parse::<B256>().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<A>(self, mut map: A) -> Result<Self::Value, A::Error>
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::<String>()? {
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::<BlockNumberOrTag>()?)
}
"blockHash" => {
if number.is_some() || block_hash.is_some() {
return Err(serde::de::Error::duplicate_field("blockHash"));
}
block_hash = Some(map.next_value::<B256>()?);
}
"requireCanonical" => {
if number.is_some() || require_canonical.is_some() {
return Err(serde::de::Error::duplicate_field("requireCanonical"));
}
require_canonical = Some(map.next_value::<bool>()?)
}
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<u64> {
match self {
BlockHashOrNumber::Hash(_) => None,
BlockHashOrNumber::Number(num) => Some(num),
}
}
}
impl From<B256> for BlockHashOrNumber {
fn from(value: B256) -> Self {
BlockHashOrNumber::Hash(value)
}
}
impl From<u64> 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<Self> {
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<Self, Self::Err> {
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,
}),
},
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,
}
}
}

View File

@ -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)]

View File

@ -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<Bytes>,
}
impl From<SealedBlock> 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: <https://github.com/ethereum/execution-apis/blob/6709c2a795b707202e93c4f2867fa0bf2640a84f/src/engine/shanghai.md#executionpayloadv2>

View File

@ -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);

View File

@ -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;

View File

@ -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<B256>,
/// Arbitrary length data.
pub data: Bytes,
}
/// Calculate receipt logs bloom.
pub fn logs_bloom<'a, It>(logs: It) -> Bloom
where
It: IntoIterator<Item = &'a Log>,
{
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
}

View File

@ -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;

View File

@ -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"`

View File

@ -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<StructLog>,
}

View File

@ -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};

View File

@ -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;

View File

@ -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<TraceType>,
block_id: Option<BlockId>,
state_overrides: Option<StateOverride>,
block_overrides: Option<Box<BlockOverrides>>
}
impl TraceRequest{
pub fn new(call:CallRequest,trace_types:HashSet<TraceType>) -> 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<BlockOverrides>) -> Self{
self.block_overrides = Some(block_overrides);
self
}
}

View File

@ -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.

View File

@ -1,9 +1,11 @@
use crate::eth::transaction::typed::{
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

View File

@ -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<Transaction> {
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<TransactionKind> 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 {

View File

@ -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::<Self>()
}
}
/// Same as [Withdrawal] but respects the Beacon API format which uses snake-case and quoted

View File

@ -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::*;

View File

@ -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,

View File

@ -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<Self, Self::Err> {
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::<u16>().map_err(NodeRecordParseError::Discport)?
} else {
port
};
let id = url
.username()
.parse::<PeerId>()
.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::<NodeRecord>(&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(),
})
}
}

View File

@ -0,0 +1,4 @@
use alloy_primitives::B512;
/// Alias for a peer identifier
pub type PeerId = B512;

View File

@ -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<JsonU256> for U256 {
fn from(value: JsonU256) -> Self {
value.0
}
}
impl From<U256> for JsonU256 {
fn from(value: U256) -> Self {
JsonU256(value)
}
}
impl Serialize for JsonU256 {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
self.0.serialize(serializer)
}
}
impl<'a> Deserialize<'a> for JsonU256 {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
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<E>(self, value: u64) -> Result<Self::Value, E>
where
E: Error,
{
Ok(JsonU256(U256::from(value)))
}
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
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<E>(self, value: String) -> Result<Self::Value, E>
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<U256, D::Error>
where
D: Deserializer<'de>,
{
let num = JsonU256::deserialize(deserializer)?;
Ok(num.into())
}

View File

@ -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<U256, D::Error>
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<S, T>(x: T, s: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
T: AsRef<[u8]>,
{
s.serialize_str(&alloy_primitives::hex::encode(x.as_ref()))
}

View File

@ -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<u64> for U64HexOrNumber {
fn from(value: u64) -> Self {
Self(U64::from(value))
}
}
impl From<U64> for U64HexOrNumber {
fn from(value: U64) -> Self {
Self(value)
}
}
impl From<U64HexOrNumber> for u64 {
fn from(value: U64HexOrNumber) -> Self {
value.to()
}
}
impl From<U64HexOrNumber> for U64 {
fn from(value: U64HexOrNumber) -> Self {
value.0
}
}
impl<'de> Deserialize<'de> for U64HexOrNumber {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
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<Option<u64>, D::Error>
where
D: Deserializer<'de>,
{
match Option::<U64HexOrNumber>::deserialize(deserializer)? {
Some(val) => Ok(Some(val.into())),
None => Ok(None),
}
}
/// Serializes u64 as hex string
pub fn serialize<S: Serializer>(value: &Option<u64>, s: S) -> Result<S::Ok, S::Error> {
match value {
Some(val) => U64HexOrNumber::from(*val).serialize(s),
None => s.serialize_none(),
}
}
}
/// Deserializes the input into an `Option<U256>`, using [`from_int_or_hex`] to deserialize the
/// inner value.
pub fn from_int_or_hex_opt<'de, D>(deserializer: D) -> Result<Option<U256>, D::Error>
where
D: Deserializer<'de>,
{
match Option::<NumberOrHexU256>::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<E: de::Error>(self) -> Result<U256, E> {
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<U256, D::Error>
where
D: Deserializer<'de>,
{
NumberOrHexU256::deserialize(deserializer)?.try_into_u256()
}

View File

@ -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):
/// <https://github.com/ethereum/go-ethereum/blob/00a73fbcce3250b87fc4160f3deddc44390848f4/internal/ethapi/api.go#L658-L690>
///
/// 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<U256> 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<JsonStorageKey> 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:
// <https://github.com/ethereum/go-ethereum/blob/00a73fbcce3250b87fc4160f3deddc44390848f4/internal/ethapi/api.go#L658-L690>
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<B256, D::Error>
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<HashMap<B256, B256>>, using [from_bytes_to_b256] which
/// allows cropped values:
///
/// ```json
/// {
/// "0x0000000000000000000000000000000000000000000000000000000000000001": "0x22"
/// }
/// ```
pub fn deserialize_storage_map<'de, D>(
deserializer: D,
) -> Result<Option<HashMap<B256, B256>>, D::Error>
where
D: Deserializer<'de>,
{
let map = Option::<HashMap<Bytes, Bytes>>::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),
}
}

View File

@ -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<u64, D::Error>
where
D: Deserializer<'de>,
{
U64::deserialize(deserializer).map(|val| val.to())
}
/// Serializes u64 as hex string
pub fn serialize<S: Serializer>(value: &u64, s: S) -> Result<S::Ok, S::Error> {
U64::from(*value).serialize(s)
}

View File

@ -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),

View File

@ -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 })

View File

@ -55,9 +55,8 @@ where
let transactions =
txs.into_iter().map(recover_raw_transaction).collect::<Result<Vec<_>, _>>()?;
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 {

View File

@ -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<I>(
@ -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,
);

View File

@ -294,7 +294,9 @@ where
.committed()
.map(|chain| chain.headers().collect::<Vec<_>>())
.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),
)
})
}

View File

@ -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,

View File

@ -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<TransactionSigned> {
// 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)?;

View File

@ -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<TraceResults> {
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);

View File

@ -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

View File

@ -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<T: ReceiptProvider + TransactionsProvider + HeaderP
{
let receipts = provider.receipt(header_num)?.ok_or(eyre::eyre!("receipt not found"))?;
for log in &receipts.logs {
if filter_params.filter_address(log) && filter_params.filter_topics(log) {
let log = from_primitive_log(log.clone());
if filter_params.filter_address(&log) && filter_params.filter_topics(&log) {
// Do something with the log e.g. decode it.
println!("Matching log found! {log:?}")
}