mirror of
https://github.com/hl-archive-node/nanoreth.git
synced 2025-12-06 19:09:54 +00:00
feat: Completely decouple rpc-types to standalone crate (#5193)
Co-authored-by: Matthias Seitz <matthias.seitz@outlook.de>
This commit is contained in:
11
Cargo.lock
generated
11
Cargo.lock
generated
@ -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]]
|
||||
|
||||
@ -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" }
|
||||
|
||||
@ -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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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]
|
||||
|
||||
@ -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::{
|
||||
|
||||
@ -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() {
|
||||
|
||||
@ -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)]
|
||||
|
||||
@ -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 { .. }));
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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};
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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();
|
||||
|
||||
70
crates/rpc/rpc-types-compat/src/transaction/typed.rs
Normal file
70
crates/rpc/rpc-types-compat/src/transaction/typed.rs
Normal 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,
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)]
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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;
|
||||
|
||||
30
crates/rpc/rpc-types/src/eth/raw_log.rs
Normal file
30
crates/rpc/rpc-types/src/eth/raw_log.rs
Normal 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
|
||||
}
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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"`
|
||||
|
||||
@ -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>,
|
||||
}
|
||||
|
||||
@ -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};
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
@ -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.
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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::*;
|
||||
|
||||
@ -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,
|
||||
|
||||
300
crates/rpc/rpc-types/src/net.rs
Normal file
300
crates/rpc/rpc-types/src/net.rs
Normal 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(),
|
||||
})
|
||||
}
|
||||
}
|
||||
4
crates/rpc/rpc-types/src/peer.rs
Normal file
4
crates/rpc/rpc-types/src/peer.rs
Normal file
@ -0,0 +1,4 @@
|
||||
use alloy_primitives::B512;
|
||||
|
||||
/// Alias for a peer identifier
|
||||
pub type PeerId = B512;
|
||||
93
crates/rpc/rpc-types/src/serde_helpers/json_u256.rs
Normal file
93
crates/rpc/rpc-types/src/serde_helpers/json_u256.rs
Normal 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())
|
||||
}
|
||||
30
crates/rpc/rpc-types/src/serde_helpers/mod.rs
Normal file
30
crates/rpc/rpc-types/src/serde_helpers/mod.rs
Normal 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()))
|
||||
}
|
||||
139
crates/rpc/rpc-types/src/serde_helpers/num.rs
Normal file
139
crates/rpc/rpc-types/src/serde_helpers/num.rs
Normal 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()
|
||||
}
|
||||
102
crates/rpc/rpc-types/src/serde_helpers/storage.rs
Normal file
102
crates/rpc/rpc-types/src/serde_helpers/storage.rs
Normal 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),
|
||||
}
|
||||
}
|
||||
18
crates/rpc/rpc-types/src/serde_helpers/u64_hex.rs
Normal file
18
crates/rpc/rpc-types/src/serde_helpers/u64_hex.rs
Normal 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)
|
||||
}
|
||||
@ -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),
|
||||
|
||||
@ -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 })
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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,
|
||||
);
|
||||
|
||||
@ -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),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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)?;
|
||||
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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:?}")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user