chore(net): Add proptest roundtrip to rlp types (#829)

This commit is contained in:
joshieDo
2023-01-17 01:50:58 +08:00
committed by GitHub
parent 3cd8fb5748
commit d50d9bd0fe
34 changed files with 423 additions and 98 deletions

5
Cargo.lock generated
View File

@ -3812,6 +3812,7 @@ dependencies = [
name = "reth-eth-wire" name = "reth-eth-wire"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"arbitrary",
"bytes", "bytes",
"ethers-core", "ethers-core",
"futures", "futures",
@ -3819,7 +3820,10 @@ dependencies = [
"hex-literal", "hex-literal",
"metrics", "metrics",
"pin-project", "pin-project",
"proptest",
"proptest-derive",
"rand 0.8.5", "rand 0.8.5",
"reth-codecs",
"reth-ecies", "reth-ecies",
"reth-primitives", "reth-primitives",
"reth-rlp", "reth-rlp",
@ -4049,6 +4053,7 @@ dependencies = [
"serde", "serde",
"serde_json", "serde_json",
"serde_with", "serde_with",
"strum",
"sucds", "sucds",
"test-fuzz", "test-fuzz",
"thiserror", "thiserror",

View File

@ -252,7 +252,7 @@ decode_integer!(ethnum::U256);
mod ethereum_types_support { mod ethereum_types_support {
use super::*; use super::*;
use ethereum_types::*; use ethereum_types::*;
use revm_interpreter::{B160, B256, U256 as RU256}; use revm_interpreter::{ruint::aliases::U128 as RU128, B160, B256, U256 as RU256};
macro_rules! fixed_hash_impl { macro_rules! fixed_hash_impl {
($t:ty) => { ($t:ty) => {
@ -337,6 +337,7 @@ mod ethereum_types_support {
} }
fixed_revm_uint_impl!(RU256, 32); fixed_revm_uint_impl!(RU256, 32);
fixed_revm_uint_impl!(RU128, 16);
fixed_uint_impl!(U64, 8); fixed_uint_impl!(U64, 8);
fixed_uint_impl!(U128, 16); fixed_uint_impl!(U128, 16);

View File

@ -240,7 +240,7 @@ mod ethereum_types_support {
use super::*; use super::*;
use ethereum_types::*; use ethereum_types::*;
use revm_interpreter::{B160, B256, U256 as RU256}; use revm_interpreter::{ruint::aliases::U128 as RU128, B160, B256, U256 as RU256};
macro_rules! fixed_hash_impl { macro_rules! fixed_hash_impl {
($t:ty) => { ($t:ty) => {
@ -311,6 +311,7 @@ mod ethereum_types_support {
}; };
} }
fixed_revm_uint_impl!(RU128, 16);
fixed_revm_uint_impl!(RU256, 32); fixed_revm_uint_impl!(RU256, 32);
} }

View File

@ -14,6 +14,7 @@ thiserror = "1"
serde = "1" serde = "1"
# reth # reth
reth-codecs = { path = "../../storage/codecs" }
reth-primitives = { path = "../../primitives" } reth-primitives = { path = "../../primitives" }
reth-rlp = { path = "../../common/rlp", features = ["alloc", "derive", "std", "ethereum-types", "smol_str"] } reth-rlp = { path = "../../common/rlp", features = ["alloc", "derive", "std", "ethereum-types", "smol_str"] }
@ -29,7 +30,13 @@ snap = "1.0.5"
smol_str = { version = "0.1", features = ["serde"] } smol_str = { version = "0.1", features = ["serde"] }
metrics = "0.20.1" metrics = "0.20.1"
# arbitrary utils
arbitrary = { version = "1.1.7", features = ["derive"], optional = true }
proptest = { version = "1.0", optional = true }
proptest-derive = { version = "0.3", optional = true }
[dev-dependencies] [dev-dependencies]
reth-primitives = { path = "../../primitives", features = ["arbitrary"] }
reth-ecies = { path = "../ecies" } reth-ecies = { path = "../ecies" }
reth-tracing = { path = "../../tracing" } reth-tracing = { path = "../../tracing" }
ethers-core = { git = "https://github.com/gakonst/ethers-rs", default-features = false } ethers-core = { git = "https://github.com/gakonst/ethers-rs", default-features = false }
@ -39,3 +46,16 @@ tokio-util = { version = "0.7.4", features = ["io", "codec"] }
hex-literal = "0.3" hex-literal = "0.3"
rand = "0.8" rand = "0.8"
secp256k1 = { version = "0.24.2", features = ["global-context", "rand-std", "recovery"] } secp256k1 = { version = "0.24.2", features = ["global-context", "rand-std", "recovery"] }
arbitrary = { version = "1.1.7", features = ["derive"] }
proptest = { version = "1.0" }
proptest-derive = "0.3"
[features]
default = []
arbitrary = ["reth-primitives/arbitrary", "dep:arbitrary", "dep:proptest", "dep:proptest-derive"]
[[test]]
name = "fuzz_roundtrip"
path = "tests/fuzz_roundtrip.rs"
required-features = ["arbitrary"]

View File

@ -2,10 +2,17 @@
use crate::{version::ParseVersionError, EthMessage, EthVersion}; use crate::{version::ParseVersionError, EthMessage, EthVersion};
use bytes::{BufMut, Bytes}; use bytes::{BufMut, Bytes};
use reth_codecs::add_arbitrary_tests;
use reth_rlp::{Decodable, DecodeError, Encodable, RlpDecodable, RlpEncodable}; use reth_rlp::{Decodable, DecodeError, Encodable, RlpDecodable, RlpEncodable};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use smol_str::SmolStr; use smol_str::SmolStr;
#[cfg(any(test, feature = "arbitrary"))]
use proptest::{
arbitrary::{any_with, ParamsFor},
strategy::{BoxedStrategy, Strategy},
};
/// A Capability message consisting of the message-id and the payload /// A Capability message consisting of the message-id and the payload
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] #[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
pub struct RawCapabilityMessage { pub struct RawCapabilityMessage {
@ -26,6 +33,7 @@ pub enum CapabilityMessage {
} }
/// A message indicating a supported capability and capability version. /// A message indicating a supported capability and capability version.
#[add_arbitrary_tests(rlp)]
#[derive( #[derive(
Clone, Debug, PartialEq, Eq, RlpEncodable, RlpDecodable, Serialize, Deserialize, Default, Hash, Clone, Debug, PartialEq, Eq, RlpEncodable, RlpDecodable, Serialize, Deserialize, Default, Hash,
)] )]
@ -55,6 +63,30 @@ impl Capability {
} }
} }
#[cfg(any(test, feature = "arbitrary"))]
impl<'a> arbitrary::Arbitrary<'a> for Capability {
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
let version = u.int_in_range(0..=32)?; // TODO: What's the max?
let name: SmolStr = String::arbitrary(u)?.into(); // TODO: what possible values?
Ok(Self { name, version })
}
}
#[cfg(any(test, feature = "arbitrary"))]
impl proptest::arbitrary::Arbitrary for Capability {
type Parameters = ParamsFor<String>;
type Strategy = BoxedStrategy<Capability>;
fn arbitrary_with(args: Self::Parameters) -> Self::Strategy {
any_with::<String>(args) // TODO: what possible values?
.prop_flat_map(move |name| {
any_with::<usize>(()) // TODO: What's the max?
.prop_map(move |version| Capability { name: name.clone().into(), version })
})
.boxed()
}
}
/// Represents all capabilities of a node. /// Represents all capabilities of a node.
#[derive(Debug, Clone, Eq, PartialEq)] #[derive(Debug, Clone, Eq, PartialEq)]
pub struct Capabilities { pub struct Capabilities {

View File

@ -1,12 +1,14 @@
//! Disconnect //! Disconnect
use bytes::Buf; use bytes::Buf;
use reth_codecs::derive_arbitrary;
use reth_rlp::{Decodable, DecodeError, Encodable, Header}; use reth_rlp::{Decodable, DecodeError, Encodable, Header};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::fmt::Display; use std::fmt::Display;
use thiserror::Error; use thiserror::Error;
/// RLPx disconnect reason. /// RLPx disconnect reason.
#[derive_arbitrary(rlp)]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
pub enum DisconnectReason { pub enum DisconnectReason {
/// Disconnect requested by the local node or remote peer. /// Disconnect requested by the local node or remote peer.

View File

@ -1,4 +1,5 @@
use crate::{capability::Capability, EthVersion, ProtocolVersion}; use crate::{capability::Capability, EthVersion, ProtocolVersion};
use reth_codecs::derive_arbitrary;
use reth_primitives::PeerId; use reth_primitives::PeerId;
use reth_rlp::{RlpDecodable, RlpEncodable}; use reth_rlp::{RlpDecodable, RlpEncodable};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -9,6 +10,7 @@ pub(crate) const DEFAULT_CLIENT_VERSION: &str = concat!("reth/v", env!("CARGO_PK
// TODO: determine if we should allow for the extra fields at the end like EIP-706 suggests // TODO: determine if we should allow for the extra fields at the end like EIP-706 suggests
/// Message used in the `p2p` handshake, containing information about the supported RLPx protocol /// Message used in the `p2p` handshake, containing information about the supported RLPx protocol
/// version and capabilities. /// version and capabilities.
#[derive_arbitrary(rlp)]
#[derive(Clone, Debug, PartialEq, Eq, RlpEncodable, RlpDecodable, Serialize, Deserialize)] #[derive(Clone, Debug, PartialEq, Eq, RlpEncodable, RlpDecodable, Serialize, Deserialize)]
pub struct HelloMessage { pub struct HelloMessage {
/// The version of the `p2p` protocol. /// The version of the `p2p` protocol.

View File

@ -9,6 +9,7 @@ use bytes::{Buf, Bytes, BytesMut};
use futures::{Sink, SinkExt, StreamExt}; use futures::{Sink, SinkExt, StreamExt};
use metrics::counter; use metrics::counter;
use pin_project::pin_project; use pin_project::pin_project;
use reth_codecs::derive_arbitrary;
use reth_rlp::{Decodable, DecodeError, Encodable, EMPTY_LIST_CODE}; use reth_rlp::{Decodable, DecodeError, Encodable, EMPTY_LIST_CODE};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::{ use std::{
@ -584,6 +585,7 @@ pub fn set_capability_offsets(
} }
/// This represents only the reserved `p2p` subprotocol messages. /// This represents only the reserved `p2p` subprotocol messages.
#[derive_arbitrary(rlp)]
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum P2PMessage { pub enum P2PMessage {
/// The first packet sent over the connection, and sent once by both sides. /// The first packet sent over the connection, and sent once by both sides.
@ -715,6 +717,7 @@ impl TryFrom<u8> for P2PMessageID {
} }
/// RLPx `p2p` protocol version /// RLPx `p2p` protocol version
#[derive_arbitrary(rlp)]
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] #[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
pub enum ProtocolVersion { pub enum ProtocolVersion {
/// `p2p` version 4 /// `p2p` version 4

View File

@ -1,6 +1,7 @@
//! Implements the `GetBlockHeaders`, `GetBlockBodies`, `BlockHeaders`, and `BlockBodies` message //! Implements the `GetBlockHeaders`, `GetBlockBodies`, `BlockHeaders`, and `BlockBodies` message
//! types. //! types.
use super::RawBlockBody; use super::RawBlockBody;
use reth_codecs::derive_arbitrary;
use reth_primitives::{BlockHashOrNumber, Header, HeadersDirection, TransactionSigned, H256}; use reth_primitives::{BlockHashOrNumber, Header, HeadersDirection, TransactionSigned, H256};
use reth_rlp::{RlpDecodable, RlpDecodableWrapper, RlpEncodable, RlpEncodableWrapper}; use reth_rlp::{RlpDecodable, RlpDecodableWrapper, RlpEncodable, RlpEncodableWrapper};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -14,6 +15,7 @@ use serde::{Deserialize, Serialize};
/// ///
/// If the [`skip`](#structfield.skip) field is non-zero, the peer must skip that amount of headers /// If the [`skip`](#structfield.skip) field is non-zero, the peer must skip that amount of headers
/// in the direction specified by [`reverse`](#structfield.reverse). /// in the direction specified by [`reverse`](#structfield.reverse).
#[derive_arbitrary(rlp)]
#[derive( #[derive(
Copy, Clone, Debug, PartialEq, Eq, Hash, RlpEncodable, RlpDecodable, Serialize, Deserialize, Copy, Clone, Debug, PartialEq, Eq, Hash, RlpEncodable, RlpDecodable, Serialize, Deserialize,
)] )]
@ -35,6 +37,7 @@ pub struct GetBlockHeaders {
} }
/// The response to [`GetBlockHeaders`], containing headers if any headers were found. /// The response to [`GetBlockHeaders`], containing headers if any headers were found.
#[derive_arbitrary(rlp)]
#[derive( #[derive(
Clone, Clone,
Debug, Debug,
@ -58,6 +61,7 @@ impl From<Vec<Header>> for BlockHeaders {
} }
/// A request for a peer to return block bodies for the given block hashes. /// A request for a peer to return block bodies for the given block hashes.
#[derive_arbitrary(rlp)]
#[derive( #[derive(
Clone, Clone,
Debug, Debug,
@ -82,6 +86,7 @@ impl From<Vec<H256>> for GetBlockBodies {
// TODO(onbjerg): We should have this type in primitives // TODO(onbjerg): We should have this type in primitives
/// A response to [`GetBlockBodies`], containing bodies if any bodies were found. /// A response to [`GetBlockBodies`], containing bodies if any bodies were found.
#[derive_arbitrary(rlp, 10)]
#[derive( #[derive(
Clone, Debug, PartialEq, Eq, RlpEncodable, RlpDecodable, Serialize, Deserialize, Default, Clone, Debug, PartialEq, Eq, RlpEncodable, RlpDecodable, Serialize, Deserialize, Default,
)] )]
@ -105,6 +110,7 @@ impl BlockBody {
/// The response to [`GetBlockBodies`], containing the block bodies that the peer knows about if /// The response to [`GetBlockBodies`], containing the block bodies that the peer knows about if
/// any were found. /// any were found.
#[derive_arbitrary(rlp, 1)]
#[derive( #[derive(
Clone, Clone,
Debug, Debug,

View File

@ -1,10 +1,12 @@
//! Types for broadcasting new data. //! Types for broadcasting new data.
use reth_codecs::derive_arbitrary;
use reth_primitives::{Header, TransactionSigned, H256, U128}; use reth_primitives::{Header, TransactionSigned, H256, U128};
use reth_rlp::{RlpDecodable, RlpDecodableWrapper, RlpEncodable, RlpEncodableWrapper}; use reth_rlp::{RlpDecodable, RlpDecodableWrapper, RlpEncodable, RlpEncodableWrapper};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::sync::Arc; use std::sync::Arc;
/// This informs peers of new blocks that have appeared on the network. /// This informs peers of new blocks that have appeared on the network.
#[derive_arbitrary(rlp)]
#[derive( #[derive(
Clone, Clone,
Debug, Debug,
@ -37,6 +39,7 @@ impl NewBlockHashes {
} }
/// A block hash _and_ a block number. /// A block hash _and_ a block number.
#[derive_arbitrary(rlp)]
#[derive( #[derive(
Clone, Debug, PartialEq, Eq, RlpEncodable, RlpDecodable, Serialize, Deserialize, Default, Clone, Debug, PartialEq, Eq, RlpEncodable, RlpDecodable, Serialize, Deserialize, Default,
)] )]
@ -60,6 +63,7 @@ impl From<NewBlockHashes> for Vec<BlockHashNumber> {
} }
/// A block body, including transactions and uncle headers. /// A block body, including transactions and uncle headers.
#[derive_arbitrary(rlp, 25)]
#[derive( #[derive(
Debug, Clone, PartialEq, Eq, Default, RlpEncodable, RlpDecodable, Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default, RlpEncodable, RlpDecodable, Serialize, Deserialize,
)] )]
@ -74,6 +78,7 @@ pub struct RawBlockBody {
/// A new block with the current total difficulty, which includes the difficulty of the returned /// A new block with the current total difficulty, which includes the difficulty of the returned
/// block. /// block.
#[derive_arbitrary(rlp, 25)]
#[derive( #[derive(
Clone, Debug, PartialEq, Eq, RlpEncodable, RlpDecodable, Serialize, Deserialize, Default, Clone, Debug, PartialEq, Eq, RlpEncodable, RlpDecodable, Serialize, Deserialize, Default,
)] )]
@ -86,6 +91,7 @@ pub struct NewBlock {
/// This informs peers of transactions that have appeared on the network and are not yet included /// This informs peers of transactions that have appeared on the network and are not yet included
/// in a block. /// in a block.
#[derive_arbitrary(rlp, 10)]
#[derive( #[derive(
Clone, Clone,
Debug, Debug,
@ -118,6 +124,7 @@ impl From<Transactions> for Vec<TransactionSigned> {
/// ///
/// The list of transactions is constructed on per-peers basis, but the underlying transaction /// The list of transactions is constructed on per-peers basis, but the underlying transaction
/// objects are shared. /// objects are shared.
#[derive_arbitrary(rlp, 20)]
#[derive(Clone, Debug, PartialEq, Eq, RlpEncodableWrapper, RlpDecodableWrapper)] #[derive(Clone, Debug, PartialEq, Eq, RlpEncodableWrapper, RlpDecodableWrapper)]
pub struct SharedTransactions( pub struct SharedTransactions(
/// New transactions for the peer to include in its mempool. /// New transactions for the peer to include in its mempool.
@ -126,6 +133,7 @@ pub struct SharedTransactions(
/// This informs peers of transaction hashes for transactions that have appeared on the network, /// This informs peers of transaction hashes for transactions that have appeared on the network,
/// but have not been included in a block. /// but have not been included in a block.
#[derive_arbitrary(rlp)]
#[derive( #[derive(
Clone, Clone,
Debug, Debug,

View File

@ -1,9 +1,11 @@
//! Implements the `GetReceipts` and `Receipts` message types. //! Implements the `GetReceipts` and `Receipts` message types.
use reth_codecs::derive_arbitrary;
use reth_primitives::{Receipt, H256}; use reth_primitives::{Receipt, H256};
use reth_rlp::{RlpDecodableWrapper, RlpEncodableWrapper}; use reth_rlp::{RlpDecodableWrapper, RlpEncodableWrapper};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
/// A request for transaction receipts from the given block hashes. /// A request for transaction receipts from the given block hashes.
#[derive_arbitrary(rlp)]
#[derive( #[derive(
Clone, Clone,
Debug, Debug,
@ -22,6 +24,7 @@ pub struct GetReceipts(
/// The response to [`GetReceipts`], containing receipt lists that correspond to each block /// The response to [`GetReceipts`], containing receipt lists that correspond to each block
/// requested. /// requested.
#[derive_arbitrary(rlp, 1)]
#[derive( #[derive(
Clone, Clone,
Debug, Debug,

View File

@ -1,11 +1,13 @@
//! Implements the `GetNodeData` and `NodeData` message types. //! Implements the `GetNodeData` and `NodeData` message types.
use reth_primitives::H256; use reth_codecs::derive_arbitrary;
use reth_primitives::{Bytes, H256};
use reth_rlp::{RlpDecodableWrapper, RlpEncodableWrapper}; use reth_rlp::{RlpDecodableWrapper, RlpEncodableWrapper};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
/// A request for state tree nodes corresponding to the given hashes. /// A request for state tree nodes corresponding to the given hashes.
/// This message was removed in `eth/67`, only clients running `eth/66` or earlier will respond to /// This message was removed in `eth/67`, only clients running `eth/66` or earlier will respond to
/// this message. /// this message.
#[derive_arbitrary(rlp)]
#[derive( #[derive(
Clone, Clone,
Debug, Debug,
@ -24,6 +26,7 @@ pub struct GetNodeData(pub Vec<H256>);
/// ///
/// Not all nodes are guaranteed to be returned by the peer. /// Not all nodes are guaranteed to be returned by the peer.
/// This message was removed in `eth/67`. /// This message was removed in `eth/67`.
#[derive_arbitrary(rlp)]
#[derive( #[derive(
Clone, Clone,
Debug, Debug,
@ -35,7 +38,7 @@ pub struct GetNodeData(pub Vec<H256>);
Deserialize, Deserialize,
Default, Default,
)] )]
pub struct NodeData(pub Vec<bytes::Bytes>); pub struct NodeData(pub Vec<Bytes>);
#[cfg(test)] #[cfg(test)]
mod test { mod test {

View File

@ -1,5 +1,6 @@
use crate::{EthVersion, StatusBuilder}; use crate::{EthVersion, StatusBuilder};
use reth_codecs::derive_arbitrary;
use reth_primitives::{Chain, ForkId, Hardfork, H256, MAINNET_GENESIS, U256}; use reth_primitives::{Chain, ForkId, Hardfork, H256, MAINNET_GENESIS, U256};
use reth_rlp::{RlpDecodable, RlpEncodable}; use reth_rlp::{RlpDecodable, RlpEncodable};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -10,6 +11,7 @@ use std::fmt::{Debug, Display};
/// ///
/// When performing a handshake, the total difficulty is not guaranteed to correspond to the block /// When performing a handshake, the total difficulty is not guaranteed to correspond to the block
/// hash. This information should be treated as untrusted. /// hash. This information should be treated as untrusted.
#[derive_arbitrary(rlp)]
#[derive(Copy, Clone, PartialEq, Eq, RlpEncodable, RlpDecodable, Serialize, Deserialize)] #[derive(Copy, Clone, PartialEq, Eq, RlpEncodable, RlpDecodable, Serialize, Deserialize)]
pub struct Status { pub struct Status {
/// The current protocol version. For example, peers running `eth/66` would have a version of /// The current protocol version. For example, peers running `eth/66` would have a version of

View File

@ -1,9 +1,11 @@
//! Implements the `GetPooledTransactions` and `PooledTransactions` message types. //! Implements the `GetPooledTransactions` and `PooledTransactions` message types.
use reth_codecs::derive_arbitrary;
use reth_primitives::{TransactionSigned, H256}; use reth_primitives::{TransactionSigned, H256};
use reth_rlp::{RlpDecodableWrapper, RlpEncodableWrapper}; use reth_rlp::{RlpDecodableWrapper, RlpEncodableWrapper};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
/// A list of transaction hashes that the peer would like transaction bodies for. /// A list of transaction hashes that the peer would like transaction bodies for.
#[derive_arbitrary(rlp)]
#[derive( #[derive(
Clone, Clone,
Debug, Debug,
@ -36,6 +38,7 @@ where
/// as the request's hashes. Hashes may be skipped, and the client should ensure that each body /// as the request's hashes. Hashes may be skipped, and the client should ensure that each body
/// corresponds to a requested hash. Hashes may need to be re-requested if the bodies are not /// corresponds to a requested hash. Hashes may need to be re-requested if the bodies are not
/// included in the response. /// included in the response.
#[derive_arbitrary(rlp, 10)]
#[derive( #[derive(
Clone, Clone,
Debug, Debug,

View File

@ -47,13 +47,14 @@ macro_rules! fuzz_type_and_name {
#[allow(non_snake_case)] #[allow(non_snake_case)]
#[cfg(any(test, feature = "bench"))] #[cfg(any(test, feature = "bench"))]
pub mod fuzz_rlp { pub mod fuzz_rlp {
use reth_codecs::derive_arbitrary;
use reth_eth_wire::{ use reth_eth_wire::{
BlockBodies, BlockHeaders, DisconnectReason, GetBlockBodies, GetBlockHeaders, GetNodeData, BlockBodies, BlockHeaders, DisconnectReason, GetBlockBodies, GetBlockHeaders, GetNodeData,
GetPooledTransactions, GetReceipts, HelloMessage, NewBlock, NewBlockHashes, GetPooledTransactions, GetReceipts, HelloMessage, NewBlock, NewBlockHashes,
NewPooledTransactionHashes, NodeData, P2PMessage, PooledTransactions, Receipts, Status, NewPooledTransactionHashes, NodeData, P2PMessage, PooledTransactions, Receipts, Status,
Transactions, Transactions,
}; };
use reth_primitives::BlockHashOrNumber; use reth_primitives::{BlockHashOrNumber, TransactionSigned};
use reth_rlp::{RlpDecodableWrapper, RlpEncodableWrapper}; use reth_rlp::{RlpDecodableWrapper, RlpEncodableWrapper};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use test_fuzz::test_fuzz; use test_fuzz::test_fuzz;
@ -64,6 +65,7 @@ pub mod fuzz_rlp {
// see message below for why wrapper types are necessary for fuzzing types that do not have a // see message below for why wrapper types are necessary for fuzzing types that do not have a
// Default impl // Default impl
#[derive_arbitrary(rlp)]
#[derive( #[derive(
Clone, Clone,
Debug, Debug,
@ -105,6 +107,7 @@ pub mod fuzz_rlp {
// //
// We just provide a default value here so test-fuzz can auto-generate a corpus file for the // We just provide a default value here so test-fuzz can auto-generate a corpus file for the
// type. // type.
#[derive_arbitrary(rlp)]
#[derive( #[derive(
Clone, Clone,
Debug, Debug,
@ -141,6 +144,7 @@ pub mod fuzz_rlp {
fuzz_type_and_name!(NodeData, fuzz_NodeData); fuzz_type_and_name!(NodeData, fuzz_NodeData);
fuzz_type_and_name!(GetReceipts, fuzz_GetReceipts); fuzz_type_and_name!(GetReceipts, fuzz_GetReceipts);
fuzz_type_and_name!(Receipts, fuzz_Receipts); fuzz_type_and_name!(Receipts, fuzz_Receipts);
fuzz_type_and_name!(TransactionSigned, fuzz_TransactionSigned);
// manually test Ping and Pong which are not covered by the above // manually test Ping and Pong which are not covered by the above

View File

@ -11,7 +11,7 @@ use reth_eth_wire::{
SharedTransactions, Transactions, SharedTransactions, Transactions,
}; };
use reth_interfaces::p2p::error::{RequestError, RequestResult}; use reth_interfaces::p2p::error::{RequestError, RequestResult};
use reth_primitives::{Header, PeerId, Receipt, TransactionSigned, H256}; use reth_primitives::{Bytes, Header, PeerId, Receipt, TransactionSigned, H256};
use std::{ use std::{
fmt, fmt,
sync::Arc, sync::Arc,
@ -198,7 +198,7 @@ pub enum PeerResponseResult {
BlockHeaders(RequestResult<Vec<Header>>), BlockHeaders(RequestResult<Vec<Header>>),
BlockBodies(RequestResult<Vec<BlockBody>>), BlockBodies(RequestResult<Vec<BlockBody>>),
PooledTransactions(RequestResult<Vec<TransactionSigned>>), PooledTransactions(RequestResult<Vec<TransactionSigned>>),
NodeData(RequestResult<Vec<bytes::Bytes>>), NodeData(RequestResult<Vec<Bytes>>),
Receipts(RequestResult<Vec<Vec<Receipt>>>), Receipts(RequestResult<Vec<Vec<Receipt>>>),
} }

View File

@ -62,6 +62,7 @@ hash-db = "0.15"
arbitrary = { version = "1.1.7", features = ["derive"], optional = true } arbitrary = { version = "1.1.7", features = ["derive"], optional = true }
proptest = { version = "1.0", optional = true } proptest = { version = "1.0", optional = true }
proptest-derive = { version = "0.3", optional = true } proptest-derive = { version = "0.3", optional = true }
strum = { version = "0.24", features = ["derive"] }
[dev-dependencies] [dev-dependencies]
serde_json = "1.0" serde_json = "1.0"

View File

@ -0,0 +1,74 @@
//! Fixed hash types
use bytes::Buf;
use derive_more::{AsRef, Deref};
use fixed_hash::construct_fixed_hash;
use impl_serde::impl_fixed_hash_serde;
use reth_codecs::{impl_hash_compact, Compact};
use reth_rlp::{RlpDecodableWrapper, RlpEncodableWrapper, RlpMaxEncodedLen};
/// Implements a fixed hash type (eg. H512) with `serde`, `Arbitrary`, `proptest::Arbitrary` and
/// `Compact` support.
#[macro_export]
macro_rules! impl_fixed_hash_type {
($(($name:tt, $size:expr)),+) => {
#[cfg(any(test, feature = "arbitrary"))]
use proptest::{
arbitrary::{any_with, ParamsFor},
strategy::{BoxedStrategy, Strategy},
};
#[cfg(any(test, feature = "arbitrary"))]
use arbitrary::Arbitrary;
$(
construct_fixed_hash! {
#[cfg_attr(any(test, feature = "arbitrary"), derive(Arbitrary))]
#[derive(AsRef, Deref, RlpEncodableWrapper, RlpDecodableWrapper, RlpMaxEncodedLen)]
#[doc = concat!(stringify!($name), " fixed hash type.")]
pub struct $name($size);
}
impl_hash_compact!($name);
impl_fixed_hash_serde!($name, $size);
#[cfg(any(test, feature = "arbitrary"))]
impl proptest::arbitrary::Arbitrary for $name {
type Parameters = ParamsFor<u8>;
type Strategy = BoxedStrategy<$name>;
fn arbitrary_with(args: Self::Parameters) -> Self::Strategy {
proptest::collection::vec(any_with::<u8>(args), $size)
.prop_map(move |vec| $name::from_slice(&vec))
.boxed()
}
}
)+
#[cfg(test)]
mod hash_tests {
use super::*;
#[test]
fn arbitrary() {
$(
proptest::proptest!(|(field: $name)| {
let mut buf = vec![];
field.to_compact(&mut buf);
// Add noise. We want to make sure that only $size bytes get consumed.
buf.push(1);
let (decoded, remaining_buf) = $name::from_compact(&buf, buf.len());
assert!(field == decoded);
assert!(remaining_buf.len() == 1);
});
)+
}
}
};
}
impl_fixed_hash_type!((H64, 8), (H512, 64));

View File

@ -1,4 +1,5 @@
use crate::{Header, SealedHeader, TransactionSigned, H256}; use crate::{Header, SealedHeader, TransactionSigned, H256};
use reth_codecs::derive_arbitrary;
use reth_rlp::{Decodable, DecodeError, Encodable, RlpDecodable, RlpEncodable}; use reth_rlp::{Decodable, DecodeError, Encodable, RlpDecodable, RlpEncodable};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::ops::Deref; use std::ops::Deref;
@ -61,6 +62,7 @@ impl Deref for SealedBlock {
} }
/// Either a block hash _or_ a block number /// Either a block hash _or_ a block number
#[derive_arbitrary(rlp)]
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum BlockHashOrNumber { pub enum BlockHashOrNumber {
/// A block hash /// A block hash

View File

@ -1,45 +1,16 @@
//! Bloom related utilities. //! Bloom type.
use crate::{keccak256, Log}; use crate::{impl_fixed_hash_type, keccak256, Log};
use bytes::Buf; use bytes::Buf;
use derive_more::{AsRef, Deref}; use derive_more::{AsRef, Deref};
use fixed_hash::construct_fixed_hash; use fixed_hash::construct_fixed_hash;
use impl_serde::impl_fixed_hash_serde; use impl_serde::impl_fixed_hash_serde;
use reth_codecs::{impl_hash_compact, Compact}; use reth_codecs::{impl_hash_compact, Compact};
use reth_rlp::{RlpDecodableWrapper, RlpEncodableWrapper}; use reth_rlp::{RlpDecodableWrapper, RlpEncodableWrapper, RlpMaxEncodedLen};
#[cfg(any(test, feature = "arbitrary"))]
use proptest::{
arbitrary::{any_with, Arbitrary as PropTestArbitrary, ParamsFor},
strategy::{BoxedStrategy, Strategy},
};
#[cfg(any(test, feature = "arbitrary"))]
use arbitrary::Arbitrary;
/// Length of bloom filter used for Ethereum. /// Length of bloom filter used for Ethereum.
pub const BLOOM_BYTE_LENGTH: usize = 256; pub const BLOOM_BYTE_LENGTH: usize = 256;
construct_fixed_hash! { impl_fixed_hash_type!((Bloom, BLOOM_BYTE_LENGTH));
/// 2048 bits type.
#[cfg_attr(any(test, feature = "arbitrary"), derive(Arbitrary))]
#[derive(AsRef, Deref, RlpEncodableWrapper, RlpDecodableWrapper)]
pub struct Bloom(BLOOM_BYTE_LENGTH);
}
impl_hash_compact!(Bloom);
impl_fixed_hash_serde!(Bloom, BLOOM_BYTE_LENGTH);
#[cfg(any(test, feature = "arbitrary"))]
impl PropTestArbitrary for Bloom {
type Parameters = ParamsFor<u8>;
type Strategy = BoxedStrategy<Bloom>;
fn arbitrary_with(args: Self::Parameters) -> Self::Strategy {
proptest::collection::vec(any_with::<u8>(args), BLOOM_BYTE_LENGTH)
.prop_map(move |vec| Bloom::from_slice(&vec))
.boxed()
}
}
// See Section 4.3.1 "Transaction Receipt" of the Yellow Paper // See Section 4.3.1 "Transaction Receipt" of the Yellow Paper
fn m3_2048(bloom: &mut Bloom, x: &[u8]) { fn m3_2048(bloom: &mut Bloom, x: &[u8]) {
@ -105,19 +76,4 @@ mod tests {
)) ))
); );
} }
#[test]
fn arbitrary() {
proptest::proptest!(|(bloom: Bloom)| {
let mut buf = vec![];
bloom.to_compact(&mut buf);
// Add noise
buf.push(1);
let (decoded, remaining_buf) = Bloom::from_compact(&buf, buf.len());
assert!(bloom == decoded);
assert!(remaining_buf.len() == 1);
});
}
} }

View File

@ -1,10 +1,12 @@
use crate::U256; use crate::U256;
use ethers_core::types::U64; use ethers_core::types::U64;
use reth_codecs::add_arbitrary_tests;
use reth_rlp::{Decodable, Encodable}; use reth_rlp::{Decodable, Encodable};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::{fmt, str::FromStr}; use std::{fmt, str::FromStr};
/// Either a named or chain id or the actual id value /// Either a named or chain id or the actual id value
#[add_arbitrary_tests(rlp)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Chain { pub enum Chain {
/// Contains a known chain /// Contains a known chain
@ -168,6 +170,43 @@ impl Default for Chain {
} }
} }
#[cfg(any(test, feature = "arbitrary"))]
impl<'a> arbitrary::Arbitrary<'a> for Chain {
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
if u.ratio(1, 2)? {
let chain = u.int_in_range(0..=(ethers_core::types::Chain::COUNT - 1))?;
return Ok(Chain::Named(ethers_core::types::Chain::iter().nth(chain).expect("in range")))
}
Ok(Self::Id(u64::arbitrary(u)?))
}
}
#[cfg(any(test, feature = "arbitrary"))]
use strum::{EnumCount, IntoEnumIterator};
#[cfg(any(test, feature = "arbitrary"))]
use proptest::{
arbitrary::ParamsFor,
prelude::{any, Strategy},
sample::Selector,
strategy::BoxedStrategy,
};
#[cfg(any(test, feature = "arbitrary"))]
impl proptest::arbitrary::Arbitrary for Chain {
type Parameters = ParamsFor<u32>;
type Strategy = BoxedStrategy<Chain>;
fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
let named = any::<Selector>()
.prop_map(move |sel| Chain::Named(sel.select(ethers_core::types::Chain::iter())));
let id = any::<u64>().prop_map(Chain::from);
proptest::strategy::Union::new_weighted(vec![(50, named.boxed()), (50, id.boxed())]).boxed()
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;

View File

@ -5,6 +5,7 @@
use crate::{BlockNumber, H256}; use crate::{BlockNumber, H256};
use crc::crc32; use crc::crc32;
use reth_codecs::derive_arbitrary;
use reth_rlp::*; use reth_rlp::*;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::{ use std::{
@ -15,6 +16,7 @@ use std::{
use thiserror::Error; use thiserror::Error;
/// `CRC32` hash of all previous forks starting from genesis block. /// `CRC32` hash of all previous forks starting from genesis block.
#[derive_arbitrary(rlp)]
#[derive( #[derive(
Clone, Clone,
Copy, Copy,
@ -58,6 +60,7 @@ impl Add<BlockNumber> for ForkHash {
/// A fork identifier as defined by EIP-2124. /// A fork identifier as defined by EIP-2124.
/// Serves as the chain compatibility identifier. /// Serves as the chain compatibility identifier.
#[derive_arbitrary(rlp)]
#[derive( #[derive(
Clone, Clone,
Copy, Copy,

View File

@ -5,7 +5,7 @@ use crate::{
}; };
use bytes::{BufMut, BytesMut}; use bytes::{BufMut, BytesMut};
use ethers_core::types::H64; use ethers_core::types::H64;
use reth_codecs::{main_codec, Compact}; use reth_codecs::{derive_arbitrary, main_codec, Compact};
use reth_rlp::{length_of_length, Decodable, Encodable}; use reth_rlp::{length_of_length, Decodable, Encodable};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::ops::Deref; use std::ops::Deref;
@ -290,6 +290,7 @@ impl SealedHeader {
/// [`HeadersDirection::Falling`] block numbers for `reverse == 1 == true` /// [`HeadersDirection::Falling`] block numbers for `reverse == 1 == true`
/// ///
/// See also <https://github.com/ethereum/devp2p/blob/master/caps/eth.md#getblockheaders-0x03> /// See also <https://github.com/ethereum/devp2p/blob/master/caps/eth.md#getblockheaders-0x03>
#[derive_arbitrary(rlp)]
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default, Serialize, Deserialize)] #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default, Serialize, Deserialize)]
pub enum HeadersDirection { pub enum HeadersDirection {
/// Falling block number. /// Falling block number.

View File

@ -220,7 +220,7 @@ impl proptest::prelude::Arbitrary for Bytes {
type Strategy = proptest::prelude::BoxedStrategy<Bytes>; type Strategy = proptest::prelude::BoxedStrategy<Bytes>;
fn arbitrary_with(args: Self::Parameters) -> Self::Strategy { fn arbitrary_with(args: Self::Parameters) -> Self::Strategy {
proptest::collection::vec(proptest::arbitrary::any_with::<u8>(args), 0..1000) proptest::collection::vec(proptest::arbitrary::any_with::<u8>(args), 0..80)
.prop_map(move |vec| bytes::Bytes::from(vec).into()) .prop_map(move |vec| bytes::Bytes::from(vec).into())
.boxed() .boxed()
} }
@ -229,7 +229,7 @@ impl proptest::prelude::Arbitrary for Bytes {
#[cfg(any(test, feature = "arbitrary"))] #[cfg(any(test, feature = "arbitrary"))]
impl<'a> arbitrary::Arbitrary<'a> for Bytes { impl<'a> arbitrary::Arbitrary<'a> for Bytes {
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> { fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
let size = u.int_in_range(0..=1000)?; let size = u.int_in_range(0..=80)?;
Ok(Self(bytes::Bytes::copy_from_slice(u.bytes(size)?))) Ok(Self(bytes::Bytes::copy_from_slice(u.bytes(size)?)))
} }
} }

View File

@ -10,6 +10,7 @@
//! This crate contains Ethereum primitive types and helper functions. //! This crate contains Ethereum primitive types and helper functions.
mod account; mod account;
mod bits;
mod block; mod block;
pub mod bloom; pub mod bloom;
mod chain; mod chain;
@ -32,6 +33,7 @@ mod transaction;
pub mod proofs; pub mod proofs;
pub use account::Account; pub use account::Account;
pub use bits::H512;
pub use block::{Block, BlockHashOrNumber, SealedBlock}; pub use block::{Block, BlockHashOrNumber, SealedBlock};
pub use bloom::Bloom; pub use bloom::Bloom;
pub use chain::Chain; pub use chain::Chain;
@ -77,9 +79,9 @@ pub type TransitionId = u64;
pub use ethers_core::{ pub use ethers_core::{
types as rpc, types as rpc,
types::{BigEndianHash, H128, H512, H64, U128, U64}, types::{BigEndianHash, H128, H64, U64},
}; };
pub use revm_interpreter::{B160 as H160, B256 as H256, U256}; pub use revm_interpreter::{ruint::aliases::U128, B160 as H160, B256 as H256, U256};
#[doc(hidden)] #[doc(hidden)]
mod __reexport { mod __reexport {

View File

@ -3,7 +3,7 @@ use reth_codecs::{main_codec, Compact};
use reth_rlp::{RlpDecodable, RlpEncodable}; use reth_rlp::{RlpDecodable, RlpEncodable};
/// Ethereum Log /// Ethereum Log
#[main_codec] #[main_codec(rlp)]
#[derive(Clone, Debug, PartialEq, Eq, RlpDecodable, RlpEncodable, Default)] #[derive(Clone, Debug, PartialEq, Eq, RlpDecodable, RlpEncodable, Default)]
pub struct Log { pub struct Log {
/// Contract that emitted this log. /// Contract that emitted this log.

View File

@ -1,4 +1,4 @@
use ethers_core::types::H512; use crate::H512;
// TODO: should we use `PublicKey` for this? Even when dealing with public keys we should try to // TODO: should we use `PublicKey` for this? Even when dealing with public keys we should try to
// prevent misuse // prevent misuse

View File

@ -112,7 +112,7 @@ impl Encodable for Receipt {
if matches!(self.tx_type, TxType::EIP1559 | TxType::EIP2930) { if matches!(self.tx_type, TxType::EIP1559 | TxType::EIP2930) {
payload_len += 1; payload_len += 1;
// we include a string header for typed receipts, so include the length here // we include a string header for typed receipts, so include the length here
payload_len = length_of_length(payload_len); payload_len += length_of_length(payload_len);
} }
payload_len payload_len

View File

@ -5,7 +5,7 @@ use reth_rlp::{RlpDecodable, RlpDecodableWrapper, RlpEncodable, RlpEncodableWrap
/// A list of addresses and storage keys that the transaction plans to access. /// A list of addresses and storage keys that the transaction plans to access.
/// Accesses outside the list are possible, but become more expensive. /// Accesses outside the list are possible, but become more expensive.
#[main_codec] #[main_codec(rlp)]
#[derive(Clone, Debug, PartialEq, Eq, Hash, Default, RlpDecodable, RlpEncodable)] #[derive(Clone, Debug, PartialEq, Eq, Hash, Default, RlpDecodable, RlpEncodable)]
pub struct AccessListItem { pub struct AccessListItem {
/// Account addresses that would be loaded at the start of execution /// Account addresses that would be loaded at the start of execution
@ -15,6 +15,6 @@ pub struct AccessListItem {
} }
/// AccessList as defined in EIP-2930 /// AccessList as defined in EIP-2930
#[main_codec] #[main_codec(rlp)]
#[derive(Clone, Debug, PartialEq, Eq, Hash, Default, RlpDecodableWrapper, RlpEncodableWrapper)] #[derive(Clone, Debug, PartialEq, Eq, Hash, Default, RlpDecodableWrapper, RlpEncodableWrapper)]
pub struct AccessList(pub Vec<AccessListItem>); pub struct AccessList(pub Vec<AccessListItem>);

View File

@ -2,7 +2,7 @@ use crate::{keccak256, Address, Bytes, ChainId, TxHash, H256};
pub use access_list::{AccessList, AccessListItem}; pub use access_list::{AccessList, AccessListItem};
use bytes::{Buf, BytesMut}; use bytes::{Buf, BytesMut};
use derive_more::{AsRef, Deref}; use derive_more::{AsRef, Deref};
use reth_codecs::{main_codec, Compact}; use reth_codecs::{add_arbitrary_tests, main_codec, Compact};
use reth_rlp::{length_of_length, Decodable, DecodeError, Encodable, Header, EMPTY_STRING_CODE}; use reth_rlp::{length_of_length, Decodable, DecodeError, Encodable, Header, EMPTY_STRING_CODE};
pub use signature::Signature; pub use signature::Signature;
pub use tx_type::TxType; pub use tx_type::TxType;
@ -187,6 +187,15 @@ impl Transaction {
keccak256(&buf) keccak256(&buf)
} }
/// Get chain_id.
pub fn chain_id(&self) -> Option<&u64> {
match self {
Transaction::Legacy(TxLegacy { chain_id, .. }) => chain_id.as_ref(),
Transaction::Eip2930(TxEip2930 { chain_id, .. }) => Some(chain_id),
Transaction::Eip1559(TxEip1559 { chain_id, .. }) => Some(chain_id),
}
}
/// Sets the transaction's chain id to the provided value. /// Sets the transaction's chain id to the provided value.
pub fn set_chain_id(&mut self, chain_id: u64) { pub fn set_chain_id(&mut self, chain_id: u64) {
match self { match self {
@ -525,7 +534,8 @@ impl Decodable for TransactionKind {
} }
/// Signed transaction. /// Signed transaction.
#[main_codec] #[main_codec(no_arbitrary)]
#[add_arbitrary_tests(rlp, compact)]
#[derive(Debug, Clone, PartialEq, Eq, Hash, AsRef, Deref, Default)] #[derive(Debug, Clone, PartialEq, Eq, Hash, AsRef, Deref, Default)]
pub struct TransactionSigned { pub struct TransactionSigned {
/// Transaction hash /// Transaction hash
@ -538,6 +548,53 @@ pub struct TransactionSigned {
pub transaction: Transaction, pub transaction: Transaction,
} }
#[cfg(any(test, feature = "arbitrary"))]
use proptest::{
prelude::{any, Strategy},
strategy::BoxedStrategy,
};
#[cfg(any(test, feature = "arbitrary"))]
impl proptest::arbitrary::Arbitrary for TransactionSigned {
type Parameters = ();
type Strategy = BoxedStrategy<TransactionSigned>;
fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
any::<(Transaction, Signature)>()
.prop_map(move |(mut transaction, sig)| {
if let Some(chain_id) = transaction.chain_id().cloned() {
// Otherwise we might overflow when calculating `v` on `recalculate_hash`
transaction.set_chain_id(chain_id % (u64::MAX / 2 - 36));
}
let mut tx =
TransactionSigned { hash: Default::default(), signature: sig, transaction };
tx.hash = tx.recalculate_hash();
tx
})
.boxed()
}
}
#[cfg(any(test, feature = "arbitrary"))]
impl<'a> arbitrary::Arbitrary<'a> for TransactionSigned {
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
let mut transaction = Transaction::arbitrary(u)?;
if let Some(chain_id) = transaction.chain_id().cloned() {
// Otherwise we might overflow when calculating `v` on `recalculate_hash`
transaction.set_chain_id(chain_id % (u64::MAX / 2 - 36));
}
let mut tx = TransactionSigned {
hash: Default::default(),
signature: Signature::arbitrary(u)?,
transaction,
};
tx.hash = tx.recalculate_hash();
Ok(tx)
}
}
impl From<TransactionSignedEcRecovered> for TransactionSigned { impl From<TransactionSignedEcRecovered> for TransactionSigned {
fn from(recovered: TransactionSignedEcRecovered) -> Self { fn from(recovered: TransactionSignedEcRecovered) -> Self {
recovered.signed_transaction recovered.signed_transaction

View File

@ -1,8 +1,8 @@
use reth_codecs::{derive_compact_arbitrary, Compact}; use reth_codecs::{derive_arbitrary, Compact};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
/// Transaction Type /// Transaction Type
#[derive_compact_arbitrary] #[derive_arbitrary(compact)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Default, Serialize, Deserialize)] #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Default, Serialize, Deserialize)]
pub enum TxType { pub enum TxType {
/// Legacy transaction pre EIP-2929 /// Legacy transaction pre EIP-2929

View File

@ -0,0 +1,73 @@
use proc_macro::{self, TokenStream};
use proc_macro2::TokenStream as TokenStream2;
use quote::{format_ident, quote};
use syn::DeriveInput;
/// If `compact` or `rlp` is passed to `derive_arbitrary`, this function will generate the
/// corresponding proptest roundtrip tests.
///
/// It accepts an optional integer number for the number of proptest cases. Otherwise, it will set
/// it at 1000.
pub fn maybe_generate_tests(args: TokenStream, ast: &DeriveInput) -> TokenStream2 {
let type_ident = ast.ident.clone();
// Same as proptest
let mut default_cases = 256;
let mut traits = vec![];
let mut roundtrips = vec![];
for arg in args {
if arg.to_string() == "compact" {
traits.push(quote! { use super::Compact; });
roundtrips.push(quote! {
{
let mut buf = vec![];
let len = field.clone().to_compact(&mut buf);
let (decoded, _) = super::#type_ident::from_compact(&buf, len);
assert!(field == decoded);
}
});
} else if arg.to_string() == "rlp" {
traits.push(quote! { use reth_rlp::{Encodable, Decodable}; });
roundtrips.push(quote! {
{
let mut buf = vec![];
let len = field.clone().encode(&mut buf);
let decoded = super::#type_ident::decode(&mut buf.as_slice()).unwrap();
assert!(field == decoded);
}
});
} else if let Ok(num) = arg.to_string().parse() {
default_cases = num;
}
}
let mut tests = TokenStream2::default();
if !roundtrips.is_empty() {
let mod_tests = format_ident!("{}Tests", ast.ident);
tests = quote! {
#[allow(non_snake_case)]
#[cfg(test)]
mod #mod_tests {
#(#traits)*
#[test]
fn proptest() {
let mut config = proptest::prelude::ProptestConfig::with_cases(#default_cases as u32);
proptest::proptest!(config, |(field: super::#type_ident)| {
#(#roundtrips)*
});
}
}
}
}
tests
}

View File

@ -1,7 +1,8 @@
use proc_macro::{self, TokenStream}; use proc_macro::{self, TokenStream, TokenTree};
use quote::{format_ident, quote}; use quote::{format_ident, quote};
use syn::{parse_macro_input, DeriveInput}; use syn::{parse_macro_input, DeriveInput};
mod arbitrary;
mod compact; mod compact;
#[proc_macro_derive(Compact, attributes(maybe_zero))] #[proc_macro_derive(Compact, attributes(maybe_zero))]
@ -9,6 +10,11 @@ pub fn derive(input: TokenStream) -> TokenStream {
compact::derive(input) compact::derive(input)
} }
/// Implements the main codec. If the codec supports it, it will call `derive_arbitrary(..)`.
///
/// Example usage:
/// * `#[main_codec(rlp)]`: will implement `derive_arbitrary(rlp)` or `derive_arbitrary(compact, rlp)`, if `compact` is the `main_codec`.
/// * `#[main_codec(no_arbitrary)]`: will skip `derive_arbitrary`
#[proc_macro_attribute] #[proc_macro_attribute]
#[rustfmt::skip] #[rustfmt::skip]
#[allow(unreachable_code)] #[allow(unreachable_code)]
@ -70,28 +76,48 @@ pub fn use_postcard(_args: TokenStream, input: TokenStream) -> TokenStream {
} }
#[proc_macro_attribute] #[proc_macro_attribute]
pub fn use_compact(_args: TokenStream, input: TokenStream) -> TokenStream { pub fn use_compact(args: TokenStream, input: TokenStream) -> TokenStream {
let ast = parse_macro_input!(input as DeriveInput); let ast = parse_macro_input!(input as DeriveInput);
let compact = quote! {
#[derive(Compact, serde::Serialize, serde::Deserialize)]
#ast
}
.into();
derive_compact_arbitrary( if let Some(first_arg) = args.clone().into_iter().next() {
_args, if first_arg.to_string() == "no_arbitrary" {
quote! { return compact
#[derive(Compact, serde::Serialize, serde::Deserialize)]
#ast
} }
.into(), }
)
let mut args = args.into_iter().collect::<Vec<_>>();
args.push(TokenTree::Ident(proc_macro::Ident::new("compact", proc_macro::Span::call_site())));
derive_arbitrary(TokenStream::from_iter(args.into_iter()), compact)
} }
/// Adds `Arbitrary` and `proptest::Arbitrary` imports into scope and derives the struct/enum.
///
/// If `compact` or `rlp` is passed to `derive_arbitrary`, there will be proptest roundtrip tests
/// generated. An integer value passed will limit the number of proptest cases generated (default:
/// 256).
///
/// Examples:
/// * `#[derive_arbitrary]`: will derive arbitrary with no tests.
/// * `#[derive_arbitrary(rlp)]`: will derive arbitrary and generate rlp roundtrip proptests.
/// * `#[derive_arbitrary(rlp, 10)]`: will derive arbitrary and generate rlp roundtrip proptests.
/// Limited to 10 cases.
/// * `#[derive_arbitrary(compact, rlp)]`. will derive arbitrary and generate rlp and compact
/// roundtrip proptests.
#[proc_macro_attribute] #[proc_macro_attribute]
pub fn derive_compact_arbitrary(_args: TokenStream, input: TokenStream) -> TokenStream { pub fn derive_arbitrary(args: TokenStream, input: TokenStream) -> TokenStream {
let ast = parse_macro_input!(input as DeriveInput); let ast = parse_macro_input!(input as DeriveInput);
let tests = arbitrary::maybe_generate_tests(args, &ast);
// Avoid duplicate names // Avoid duplicate names
let prop_import = format_ident!("{}PropTestArbitratry", ast.ident); let prop_import = format_ident!("{}PropTestArbitratry", ast.ident);
let arb_import = format_ident!("{}Arbitratry", ast.ident); let arb_import = format_ident!("{}Arbitratry", ast.ident);
let mod_tests = format_ident!("{}Tests", ast.ident);
let type_ident = ast.ident.clone();
quote! { quote! {
#[cfg(any(test, feature = "arbitrary"))] #[cfg(any(test, feature = "arbitrary"))]
@ -103,23 +129,19 @@ pub fn derive_compact_arbitrary(_args: TokenStream, input: TokenStream) -> Token
#[cfg_attr(any(test, feature = "arbitrary"), derive(#prop_import, #arb_import))] #[cfg_attr(any(test, feature = "arbitrary"), derive(#prop_import, #arb_import))]
#ast #ast
#[allow(non_snake_case)] #tests
#[cfg(test)] }
mod #mod_tests { .into()
use super::Compact; }
#[test] /// To be used for types that implement `Arbitrary` manually. See [`derive_arbitrary`] for more.
fn proptest() { #[proc_macro_attribute]
proptest::proptest!(|(field: super::#type_ident)| { pub fn add_arbitrary_tests(args: TokenStream, input: TokenStream) -> TokenStream {
let mut buf = vec![]; let ast = parse_macro_input!(input as DeriveInput);
let tests = arbitrary::maybe_generate_tests(args, &ast);
let len = field.clone().to_compact(&mut buf); quote! {
let (decoded, _) = super::#type_ident::from_compact(&buf, len); #ast
#tests
assert!(field == decoded);
});
}
}
} }
.into() .into()
} }

View File

@ -43,7 +43,7 @@ proptest-derive = { version = "0.3", optional = true }
[dev-dependencies] [dev-dependencies]
# reth libs with arbitrary # reth libs with arbitrary
reth-primitives = { path = "../../primitives", features = ["arbitrary"]} reth-primitives = { path = "../../primitives", features = ["arbitrary"]}
reth-codecs = { path = "../codecs",features = ["arbitrary"] } reth-codecs = { path = "../codecs", features = ["arbitrary"] }
reth-interfaces = { path = "../../interfaces", features = ["bench"] } reth-interfaces = { path = "../../interfaces", features = ["bench"] }
tempfile = "3.3.0" tempfile = "3.3.0"