From bff27a4154a0dc45cb5b4277991c5c1026e4f29f Mon Sep 17 00:00:00 2001 From: Roman Krasiuk Date: Thu, 3 Nov 2022 12:04:09 +0200 Subject: [PATCH] fix(rpc): type encoding (#155) * fix(rpc): type encoding * copy bytes from ethers --- Cargo.lock | 13 +- crates/net/rpc-types/Cargo.toml | 4 +- .../rpc-types/src/eth/transaction/request.rs | 4 +- .../rpc-types/src/eth/transaction/typed.rs | 20 +- crates/primitives/src/hex_bytes.rs | 226 ++++++++++++++++++ crates/primitives/src/lib.rs | 5 +- .../primitives/src/transaction/access_list.rs | 18 +- crates/primitives/src/transaction/mod.rs | 4 +- 8 files changed, 261 insertions(+), 33 deletions(-) create mode 100644 crates/primitives/src/hex_bytes.rs diff --git a/Cargo.lock b/Cargo.lock index 5066715df..3f41faa4d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1261,17 +1261,6 @@ dependencies = [ "instant", ] -[[package]] -name = "fastrlp" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "089263294bb1c38ac73649a6ad563dd9a5142c8dc0482be15b8b9acb22a1611e" -dependencies = [ - "arrayvec", - "auto_impl", - "bytes", -] - [[package]] name = "ff" version = "0.12.0" @@ -3342,8 +3331,8 @@ dependencies = [ name = "reth-rpc-types" version = "0.1.0" dependencies = [ - "fastrlp", "reth-primitives", + "reth-rlp", "serde", "serde_json", ] diff --git a/crates/net/rpc-types/Cargo.toml b/crates/net/rpc-types/Cargo.toml index 2fdbda370..ac528b5b3 100644 --- a/crates/net/rpc-types/Cargo.toml +++ b/crates/net/rpc-types/Cargo.toml @@ -11,9 +11,7 @@ Reth RPC types [dependencies] # reth reth-primitives = { path = "../../primitives" } - -# eth -fastrlp = { version = "0.1" } +reth-rlp = {path = "../../common/rlp"} # misc serde = { version = "1.0", features = ["derive"] } diff --git a/crates/net/rpc-types/src/eth/transaction/request.rs b/crates/net/rpc-types/src/eth/transaction/request.rs index 3027649ba..f84ac9b84 100644 --- a/crates/net/rpc-types/src/eth/transaction/request.rs +++ b/crates/net/rpc-types/src/eth/transaction/request.rs @@ -2,7 +2,7 @@ use crate::eth::transaction::typed::{ EIP1559TransactionRequest, EIP2930TransactionRequest, LegacyTransactionRequest, TransactionKind, TypedTransactionRequest, }; -use reth_primitives::{rpc::transaction::eip2930::AccessListItem, Address, Bytes, U256}; +use reth_primitives::{AccessList, Address, Bytes, U256}; use serde::{Deserialize, Serialize}; /// Represents _all_ transaction requests received from RPC @@ -33,7 +33,7 @@ pub struct TransactionRequest { pub nonce: Option, /// warm storage access pre-payment #[serde(default)] - pub access_list: Option>, + pub access_list: Option, /// EIP-2718 type #[serde(rename = "type")] pub transaction_type: Option, diff --git a/crates/net/rpc-types/src/eth/transaction/typed.rs b/crates/net/rpc-types/src/eth/transaction/typed.rs index c7e20e7d4..2d71acf5e 100644 --- a/crates/net/rpc-types/src/eth/transaction/typed.rs +++ b/crates/net/rpc-types/src/eth/transaction/typed.rs @@ -3,8 +3,8 @@ //! json input of an RPC call. Depending on what fields are set, it can be converted into the //! container type [`TypedTransactionRequest`]. -use fastrlp::{RlpDecodable, RlpEncodable}; -use reth_primitives::{rpc::transaction::eip2930::AccessListItem, Address, Bytes, U256}; +use reth_primitives::{AccessList, Address, Bytes, U256}; +use reth_rlp::{BufMut, Decodable, DecodeError, Encodable, RlpDecodable, RlpEncodable}; use serde::{Deserialize, Serialize}; /// Container type for various Ethereum transaction requests @@ -42,7 +42,7 @@ pub struct EIP2930TransactionRequest { pub kind: TransactionKind, pub value: U256, pub input: Bytes, - pub access_list: Vec, + pub access_list: AccessList, } /// Represents an EIP-1559 transaction request @@ -56,7 +56,7 @@ pub struct EIP1559TransactionRequest { pub kind: TransactionKind, pub value: U256, pub input: Bytes, - pub access_list: Vec, + pub access_list: AccessList, } /// Represents the `to` field of a transaction request @@ -82,14 +82,14 @@ impl TransactionKind { } } -impl fastrlp::Encodable for TransactionKind { +impl Encodable for TransactionKind { fn length(&self) -> usize { match self { TransactionKind::Call(to) => to.length(), TransactionKind::Create => ([]).length(), } } - fn encode(&self, out: &mut dyn fastrlp::BufMut) { + fn encode(&self, out: &mut dyn BufMut) { match self { TransactionKind::Call(to) => to.encode(out), TransactionKind::Create => ([]).encode(out), @@ -97,18 +97,18 @@ impl fastrlp::Encodable for TransactionKind { } } -impl fastrlp::Decodable for TransactionKind { - fn decode(buf: &mut &[u8]) -> Result { +impl Decodable for TransactionKind { + fn decode(buf: &mut &[u8]) -> Result { if let Some(&first) = buf.first() { if first == 0x80 { *buf = &buf[1..]; Ok(TransactionKind::Create) } else { - let addr =
::decode(buf)?; + let addr =
::decode(buf)?; Ok(TransactionKind::Call(addr)) } } else { - Err(fastrlp::DecodeError::InputTooShort) + Err(DecodeError::InputTooShort) } } } diff --git a/crates/primitives/src/hex_bytes.rs b/crates/primitives/src/hex_bytes.rs new file mode 100644 index 000000000..c0bf3aa54 --- /dev/null +++ b/crates/primitives/src/hex_bytes.rs @@ -0,0 +1,226 @@ +use reth_rlp::{Decodable, DecodeError, Encodable}; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use std::{ + borrow::Borrow, + clone::Clone, + fmt::{Debug, Display, Formatter, LowerHex, Result as FmtResult}, + ops::Deref, + str::FromStr, +}; +use thiserror::Error; + +/// Wrapper type around Bytes to deserialize/serialize "0x" prefixed ethereum hex strings +#[derive(Clone, Default, PartialEq, Eq, Hash, Serialize, Deserialize, Ord, PartialOrd)] +pub struct Bytes( + #[serde(serialize_with = "serialize_bytes", deserialize_with = "deserialize_bytes")] + pub bytes::Bytes, +); + +fn bytes_to_hex(b: &Bytes) -> String { + hex::encode(b.0.as_ref()) +} + +impl Debug for Bytes { + fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { + write!(f, "Bytes(0x{})", bytes_to_hex(self)) + } +} + +impl Display for Bytes { + fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { + write!(f, "0x{}", bytes_to_hex(self)) + } +} + +impl LowerHex for Bytes { + fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { + write!(f, "0x{}", bytes_to_hex(self)) + } +} + +impl Bytes { + /// Return bytes as [Vec::] + pub fn to_vec(&self) -> Vec { + self.as_ref().to_vec() + } +} + +impl Deref for Bytes { + type Target = [u8]; + + #[inline] + fn deref(&self) -> &[u8] { + self.as_ref() + } +} + +impl AsRef<[u8]> for Bytes { + fn as_ref(&self) -> &[u8] { + self.0.as_ref() + } +} + +impl Borrow<[u8]> for Bytes { + fn borrow(&self) -> &[u8] { + self.as_ref() + } +} + +impl IntoIterator for Bytes { + type Item = u8; + type IntoIter = bytes::buf::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } +} + +impl<'a> IntoIterator for &'a Bytes { + type Item = &'a u8; + type IntoIter = core::slice::Iter<'a, u8>; + + fn into_iter(self) -> Self::IntoIter { + self.as_ref().iter() + } +} + +impl From for Bytes { + fn from(src: bytes::Bytes) -> Self { + Self(src) + } +} + +impl From> for Bytes { + fn from(src: Vec) -> Self { + Self(src.into()) + } +} + +impl From<[u8; N]> for Bytes { + fn from(src: [u8; N]) -> Self { + src.to_vec().into() + } +} + +impl<'a, const N: usize> From<&'a [u8; N]> for Bytes { + fn from(src: &'a [u8; N]) -> Self { + src.to_vec().into() + } +} + +impl PartialEq<[u8]> for Bytes { + fn eq(&self, other: &[u8]) -> bool { + self.as_ref() == other + } +} + +impl PartialEq for [u8] { + fn eq(&self, other: &Bytes) -> bool { + *other == *self + } +} + +impl PartialEq> for Bytes { + fn eq(&self, other: &Vec) -> bool { + self.as_ref() == &other[..] + } +} + +impl PartialEq for Vec { + fn eq(&self, other: &Bytes) -> bool { + *other == *self + } +} + +impl PartialEq for Bytes { + fn eq(&self, other: &bytes::Bytes) -> bool { + other == self.as_ref() + } +} + +impl Encodable for Bytes { + fn length(&self) -> usize { + self.0.length() + } + fn encode(&self, out: &mut dyn bytes::BufMut) { + self.0.encode(out) + } +} + +impl Decodable for Bytes { + fn decode(buf: &mut &[u8]) -> Result { + Ok(Self(bytes::Bytes::decode(buf)?)) + } +} + +#[derive(Debug, Clone, Error)] +#[error("Failed to parse bytes: {0}")] +pub struct ParseBytesError(String); + +impl FromStr for Bytes { + type Err = ParseBytesError; + + fn from_str(value: &str) -> Result { + if let Some(value) = value.strip_prefix("0x") { + hex::decode(value) + } else { + hex::decode(value) + } + .map(Into::into) + .map_err(|e| ParseBytesError(format!("Invalid hex: {}", e))) + } +} + +fn serialize_bytes(x: T, s: S) -> Result +where + S: Serializer, + T: AsRef<[u8]>, +{ + s.serialize_str(&format!("0x{}", hex::encode(x.as_ref()))) +} + +fn deserialize_bytes<'de, D>(d: D) -> Result +where + D: Deserializer<'de>, +{ + let value = String::deserialize(d)?; + if let Some(value) = value.strip_prefix("0x") { + hex::decode(value) + } else { + hex::decode(&value) + } + .map(Into::into) + .map_err(|e| serde::de::Error::custom(e.to_string())) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn hex_formatting() { + let b = Bytes::from(vec![1, 35, 69, 103, 137, 171, 205, 239]); + let expected = String::from("0x0123456789abcdef"); + assert_eq!(format!("{:x}", b), expected); + assert_eq!(format!("{}", b), expected); + } + + #[test] + fn test_from_str() { + let b = Bytes::from_str("0x1213"); + assert!(b.is_ok()); + let b = b.unwrap(); + assert_eq!(b.as_ref(), hex::decode("1213").unwrap()); + + let b = Bytes::from_str("1213"); + let b = b.unwrap(); + assert_eq!(b.as_ref(), hex::decode("1213").unwrap()); + } + + #[test] + fn test_debug_formatting() { + let b = Bytes::from(vec![1, 35, 69, 103, 137, 171, 205, 239]); + assert_eq!(format!("{:?}", b), "Bytes(0x0123456789abcdef)"); + assert_eq!(format!("{:#?}", b), "Bytes(0x0123456789abcdef)"); + } +} diff --git a/crates/primitives/src/lib.rs b/crates/primitives/src/lib.rs index bec355f4c..fa90c780d 100644 --- a/crates/primitives/src/lib.rs +++ b/crates/primitives/src/lib.rs @@ -12,6 +12,7 @@ mod block; mod chain; mod error; mod header; +mod hex_bytes; mod integer_list; mod jsonu256; mod log; @@ -23,6 +24,7 @@ pub use account::Account; pub use block::{Block, BlockLocked}; pub use chain::Chain; pub use header::{Header, HeaderLocked}; +pub use hex_bytes::Bytes; pub use integer_list::IntegerList; pub use jsonu256::JsonU256; pub use log::Log; @@ -51,10 +53,9 @@ pub type StorageKey = H256; /// Storage value pub type StorageValue = H256; -// NOTE: There is a benefit of using wrapped Bytes as it gives us serde and debug pub use ethers_core::{ types as rpc, - types::{BigEndianHash, Bloom, Bytes, H128, H160, H256, H512, H64, U128, U256, U64}, + types::{BigEndianHash, Bloom, H128, H160, H256, H512, H64, U128, U256, U64}, }; #[doc(hidden)] diff --git a/crates/primitives/src/transaction/access_list.rs b/crates/primitives/src/transaction/access_list.rs index 119e25880..2ca16ade2 100644 --- a/crates/primitives/src/transaction/access_list.rs +++ b/crates/primitives/src/transaction/access_list.rs @@ -1,10 +1,13 @@ use reth_rlp::{RlpDecodable, RlpDecodableWrapper, RlpEncodable, RlpEncodableWrapper}; +use serde::{Deserialize, Serialize}; use crate::{Address, H256}; /// A list of addresses and storage keys that the transaction plans to access. /// Accesses outside the list are possible, but become more expensive. -#[derive(Clone, Debug, PartialEq, Eq, Hash, Default, RlpDecodable, RlpEncodable)] +#[derive( + Clone, Debug, PartialEq, Eq, Hash, Default, RlpDecodable, RlpEncodable, Serialize, Deserialize, +)] pub struct AccessListItem { /// Account addresses that would be loaded at the start of execution pub address: Address, @@ -13,5 +16,16 @@ pub struct AccessListItem { } /// AccessList as defined in EIP-2930 -#[derive(Clone, Debug, PartialEq, Eq, Hash, Default, RlpDecodableWrapper, RlpEncodableWrapper)] +#[derive( + Clone, + Debug, + PartialEq, + Eq, + Hash, + Default, + RlpDecodableWrapper, + RlpEncodableWrapper, + Serialize, + Deserialize, +)] pub struct AccessList(pub Vec); diff --git a/crates/primitives/src/transaction/mod.rs b/crates/primitives/src/transaction/mod.rs index 78fe80bb2..f995c6cd6 100644 --- a/crates/primitives/src/transaction/mod.rs +++ b/crates/primitives/src/transaction/mod.rs @@ -632,10 +632,10 @@ mod tests { use crate::{ transaction::{signature::Signature, TransactionKind}, - Address, Transaction, TransactionSigned, H256, U256, + Address, Bytes, Transaction, TransactionSigned, H256, U256, }; use bytes::BytesMut; - use ethers_core::{types::Bytes, utils::hex}; + use ethers_core::utils::hex; use reth_rlp::{Decodable, Encodable}; #[test]