From d50d9bd0fe716af33d2c5a9304039a74a57fe2e9 Mon Sep 17 00:00:00 2001 From: joshieDo <93316087+joshieDo@users.noreply.github.com> Date: Tue, 17 Jan 2023 01:50:58 +0800 Subject: [PATCH] chore(net): Add `proptest` roundtrip to `rlp` types (#829) --- Cargo.lock | 5 ++ crates/common/rlp/src/decode.rs | 3 +- crates/common/rlp/src/encode.rs | 3 +- crates/net/eth-wire/Cargo.toml | 20 +++++ crates/net/eth-wire/src/capability.rs | 32 ++++++++ crates/net/eth-wire/src/disconnect.rs | 2 + crates/net/eth-wire/src/hello.rs | 2 + crates/net/eth-wire/src/p2pstream.rs | 3 + crates/net/eth-wire/src/types/blocks.rs | 6 ++ crates/net/eth-wire/src/types/broadcast.rs | 8 ++ crates/net/eth-wire/src/types/receipts.rs | 3 + crates/net/eth-wire/src/types/state.rs | 7 +- crates/net/eth-wire/src/types/status.rs | 2 + crates/net/eth-wire/src/types/transactions.rs | 3 + crates/net/eth-wire/tests/fuzz_roundtrip.rs | 6 +- crates/net/network/src/message.rs | 4 +- crates/primitives/Cargo.toml | 1 + crates/primitives/src/bits.rs | 74 ++++++++++++++++++ crates/primitives/src/block.rs | 2 + crates/primitives/src/bloom.rs | 52 +------------ crates/primitives/src/chain.rs | 39 ++++++++++ crates/primitives/src/forkid.rs | 3 + crates/primitives/src/header.rs | 3 +- crates/primitives/src/hex_bytes.rs | 4 +- crates/primitives/src/lib.rs | 6 +- crates/primitives/src/log.rs | 2 +- crates/primitives/src/peer.rs | 2 +- crates/primitives/src/receipt.rs | 2 +- .../primitives/src/transaction/access_list.rs | 4 +- crates/primitives/src/transaction/mod.rs | 61 ++++++++++++++- crates/primitives/src/transaction/tx_type.rs | 4 +- crates/storage/codecs/derive/src/arbitrary.rs | 73 +++++++++++++++++ crates/storage/codecs/derive/src/lib.rs | 78 ++++++++++++------- crates/storage/db/Cargo.toml | 2 +- 34 files changed, 423 insertions(+), 98 deletions(-) create mode 100644 crates/primitives/src/bits.rs create mode 100644 crates/storage/codecs/derive/src/arbitrary.rs diff --git a/Cargo.lock b/Cargo.lock index 61f97b799..ff621477e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3812,6 +3812,7 @@ dependencies = [ name = "reth-eth-wire" version = "0.1.0" dependencies = [ + "arbitrary", "bytes", "ethers-core", "futures", @@ -3819,7 +3820,10 @@ dependencies = [ "hex-literal", "metrics", "pin-project", + "proptest", + "proptest-derive", "rand 0.8.5", + "reth-codecs", "reth-ecies", "reth-primitives", "reth-rlp", @@ -4049,6 +4053,7 @@ dependencies = [ "serde", "serde_json", "serde_with", + "strum", "sucds", "test-fuzz", "thiserror", diff --git a/crates/common/rlp/src/decode.rs b/crates/common/rlp/src/decode.rs index 945f24bea..35fff0934 100644 --- a/crates/common/rlp/src/decode.rs +++ b/crates/common/rlp/src/decode.rs @@ -252,7 +252,7 @@ decode_integer!(ethnum::U256); mod ethereum_types_support { use super::*; 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 { ($t:ty) => { @@ -337,6 +337,7 @@ mod ethereum_types_support { } fixed_revm_uint_impl!(RU256, 32); + fixed_revm_uint_impl!(RU128, 16); fixed_uint_impl!(U64, 8); fixed_uint_impl!(U128, 16); diff --git a/crates/common/rlp/src/encode.rs b/crates/common/rlp/src/encode.rs index 947a74197..5f09ce8fa 100644 --- a/crates/common/rlp/src/encode.rs +++ b/crates/common/rlp/src/encode.rs @@ -240,7 +240,7 @@ mod ethereum_types_support { use super::*; 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 { ($t:ty) => { @@ -311,6 +311,7 @@ mod ethereum_types_support { }; } + fixed_revm_uint_impl!(RU128, 16); fixed_revm_uint_impl!(RU256, 32); } diff --git a/crates/net/eth-wire/Cargo.toml b/crates/net/eth-wire/Cargo.toml index 111e1eb7c..42551fec2 100644 --- a/crates/net/eth-wire/Cargo.toml +++ b/crates/net/eth-wire/Cargo.toml @@ -14,6 +14,7 @@ thiserror = "1" serde = "1" # reth +reth-codecs = { path = "../../storage/codecs" } reth-primitives = { path = "../../primitives" } 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"] } 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] +reth-primitives = { path = "../../primitives", features = ["arbitrary"] } reth-ecies = { path = "../ecies" } reth-tracing = { path = "../../tracing" } 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" rand = "0.8" 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"] \ No newline at end of file diff --git a/crates/net/eth-wire/src/capability.rs b/crates/net/eth-wire/src/capability.rs index 3463892dc..a0ba57940 100644 --- a/crates/net/eth-wire/src/capability.rs +++ b/crates/net/eth-wire/src/capability.rs @@ -2,10 +2,17 @@ use crate::{version::ParseVersionError, EthMessage, EthVersion}; use bytes::{BufMut, Bytes}; +use reth_codecs::add_arbitrary_tests; use reth_rlp::{Decodable, DecodeError, Encodable, RlpDecodable, RlpEncodable}; use serde::{Deserialize, Serialize}; 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 #[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] pub struct RawCapabilityMessage { @@ -26,6 +33,7 @@ pub enum CapabilityMessage { } /// A message indicating a supported capability and capability version. +#[add_arbitrary_tests(rlp)] #[derive( 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 { + 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; + type Strategy = BoxedStrategy; + + fn arbitrary_with(args: Self::Parameters) -> Self::Strategy { + any_with::(args) // TODO: what possible values? + .prop_flat_map(move |name| { + any_with::(()) // TODO: What's the max? + .prop_map(move |version| Capability { name: name.clone().into(), version }) + }) + .boxed() + } +} + /// Represents all capabilities of a node. #[derive(Debug, Clone, Eq, PartialEq)] pub struct Capabilities { diff --git a/crates/net/eth-wire/src/disconnect.rs b/crates/net/eth-wire/src/disconnect.rs index 3ab34eea7..65829cfaf 100644 --- a/crates/net/eth-wire/src/disconnect.rs +++ b/crates/net/eth-wire/src/disconnect.rs @@ -1,12 +1,14 @@ //! Disconnect use bytes::Buf; +use reth_codecs::derive_arbitrary; use reth_rlp::{Decodable, DecodeError, Encodable, Header}; use serde::{Deserialize, Serialize}; use std::fmt::Display; use thiserror::Error; /// RLPx disconnect reason. +#[derive_arbitrary(rlp)] #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] pub enum DisconnectReason { /// Disconnect requested by the local node or remote peer. diff --git a/crates/net/eth-wire/src/hello.rs b/crates/net/eth-wire/src/hello.rs index eeb5cadbe..416da853a 100644 --- a/crates/net/eth-wire/src/hello.rs +++ b/crates/net/eth-wire/src/hello.rs @@ -1,4 +1,5 @@ use crate::{capability::Capability, EthVersion, ProtocolVersion}; +use reth_codecs::derive_arbitrary; use reth_primitives::PeerId; use reth_rlp::{RlpDecodable, RlpEncodable}; 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 /// Message used in the `p2p` handshake, containing information about the supported RLPx protocol /// version and capabilities. +#[derive_arbitrary(rlp)] #[derive(Clone, Debug, PartialEq, Eq, RlpEncodable, RlpDecodable, Serialize, Deserialize)] pub struct HelloMessage { /// The version of the `p2p` protocol. diff --git a/crates/net/eth-wire/src/p2pstream.rs b/crates/net/eth-wire/src/p2pstream.rs index 48ebc254a..9bbf744c2 100644 --- a/crates/net/eth-wire/src/p2pstream.rs +++ b/crates/net/eth-wire/src/p2pstream.rs @@ -9,6 +9,7 @@ use bytes::{Buf, Bytes, BytesMut}; use futures::{Sink, SinkExt, StreamExt}; use metrics::counter; use pin_project::pin_project; +use reth_codecs::derive_arbitrary; use reth_rlp::{Decodable, DecodeError, Encodable, EMPTY_LIST_CODE}; use serde::{Deserialize, Serialize}; use std::{ @@ -584,6 +585,7 @@ pub fn set_capability_offsets( } /// This represents only the reserved `p2p` subprotocol messages. +#[derive_arbitrary(rlp)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub enum P2PMessage { /// The first packet sent over the connection, and sent once by both sides. @@ -715,6 +717,7 @@ impl TryFrom for P2PMessageID { } /// RLPx `p2p` protocol version +#[derive_arbitrary(rlp)] #[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] pub enum ProtocolVersion { /// `p2p` version 4 diff --git a/crates/net/eth-wire/src/types/blocks.rs b/crates/net/eth-wire/src/types/blocks.rs index e988fd1a5..c734280d8 100644 --- a/crates/net/eth-wire/src/types/blocks.rs +++ b/crates/net/eth-wire/src/types/blocks.rs @@ -1,6 +1,7 @@ //! Implements the `GetBlockHeaders`, `GetBlockBodies`, `BlockHeaders`, and `BlockBodies` message //! types. use super::RawBlockBody; +use reth_codecs::derive_arbitrary; use reth_primitives::{BlockHashOrNumber, Header, HeadersDirection, TransactionSigned, H256}; use reth_rlp::{RlpDecodable, RlpDecodableWrapper, RlpEncodable, RlpEncodableWrapper}; 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 /// in the direction specified by [`reverse`](#structfield.reverse). +#[derive_arbitrary(rlp)] #[derive( 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. +#[derive_arbitrary(rlp)] #[derive( Clone, Debug, @@ -58,6 +61,7 @@ impl From> for BlockHeaders { } /// A request for a peer to return block bodies for the given block hashes. +#[derive_arbitrary(rlp)] #[derive( Clone, Debug, @@ -82,6 +86,7 @@ impl From> for GetBlockBodies { // TODO(onbjerg): We should have this type in primitives /// A response to [`GetBlockBodies`], containing bodies if any bodies were found. +#[derive_arbitrary(rlp, 10)] #[derive( 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 /// any were found. +#[derive_arbitrary(rlp, 1)] #[derive( Clone, Debug, diff --git a/crates/net/eth-wire/src/types/broadcast.rs b/crates/net/eth-wire/src/types/broadcast.rs index c3ea5ef7a..6c1a07b66 100644 --- a/crates/net/eth-wire/src/types/broadcast.rs +++ b/crates/net/eth-wire/src/types/broadcast.rs @@ -1,10 +1,12 @@ //! Types for broadcasting new data. +use reth_codecs::derive_arbitrary; use reth_primitives::{Header, TransactionSigned, H256, U128}; use reth_rlp::{RlpDecodable, RlpDecodableWrapper, RlpEncodable, RlpEncodableWrapper}; use serde::{Deserialize, Serialize}; use std::sync::Arc; /// This informs peers of new blocks that have appeared on the network. +#[derive_arbitrary(rlp)] #[derive( Clone, Debug, @@ -37,6 +39,7 @@ impl NewBlockHashes { } /// A block hash _and_ a block number. +#[derive_arbitrary(rlp)] #[derive( Clone, Debug, PartialEq, Eq, RlpEncodable, RlpDecodable, Serialize, Deserialize, Default, )] @@ -60,6 +63,7 @@ impl From for Vec { } /// A block body, including transactions and uncle headers. +#[derive_arbitrary(rlp, 25)] #[derive( 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 /// block. +#[derive_arbitrary(rlp, 25)] #[derive( 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 /// in a block. +#[derive_arbitrary(rlp, 10)] #[derive( Clone, Debug, @@ -118,6 +124,7 @@ impl From for Vec { /// /// The list of transactions is constructed on per-peers basis, but the underlying transaction /// objects are shared. +#[derive_arbitrary(rlp, 20)] #[derive(Clone, Debug, PartialEq, Eq, RlpEncodableWrapper, RlpDecodableWrapper)] pub struct SharedTransactions( /// 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, /// but have not been included in a block. +#[derive_arbitrary(rlp)] #[derive( Clone, Debug, diff --git a/crates/net/eth-wire/src/types/receipts.rs b/crates/net/eth-wire/src/types/receipts.rs index da3c8f26d..71e6bafdf 100644 --- a/crates/net/eth-wire/src/types/receipts.rs +++ b/crates/net/eth-wire/src/types/receipts.rs @@ -1,9 +1,11 @@ //! Implements the `GetReceipts` and `Receipts` message types. +use reth_codecs::derive_arbitrary; use reth_primitives::{Receipt, H256}; use reth_rlp::{RlpDecodableWrapper, RlpEncodableWrapper}; use serde::{Deserialize, Serialize}; /// A request for transaction receipts from the given block hashes. +#[derive_arbitrary(rlp)] #[derive( Clone, Debug, @@ -22,6 +24,7 @@ pub struct GetReceipts( /// The response to [`GetReceipts`], containing receipt lists that correspond to each block /// requested. +#[derive_arbitrary(rlp, 1)] #[derive( Clone, Debug, diff --git a/crates/net/eth-wire/src/types/state.rs b/crates/net/eth-wire/src/types/state.rs index 3a3255119..c8095d7fb 100644 --- a/crates/net/eth-wire/src/types/state.rs +++ b/crates/net/eth-wire/src/types/state.rs @@ -1,11 +1,13 @@ //! 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 serde::{Deserialize, Serialize}; /// 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. +#[derive_arbitrary(rlp)] #[derive( Clone, Debug, @@ -24,6 +26,7 @@ pub struct GetNodeData(pub Vec); /// /// Not all nodes are guaranteed to be returned by the peer. /// This message was removed in `eth/67`. +#[derive_arbitrary(rlp)] #[derive( Clone, Debug, @@ -35,7 +38,7 @@ pub struct GetNodeData(pub Vec); Deserialize, Default, )] -pub struct NodeData(pub Vec); +pub struct NodeData(pub Vec); #[cfg(test)] mod test { diff --git a/crates/net/eth-wire/src/types/status.rs b/crates/net/eth-wire/src/types/status.rs index 799f56e34..63b469f41 100644 --- a/crates/net/eth-wire/src/types/status.rs +++ b/crates/net/eth-wire/src/types/status.rs @@ -1,5 +1,6 @@ use crate::{EthVersion, StatusBuilder}; +use reth_codecs::derive_arbitrary; use reth_primitives::{Chain, ForkId, Hardfork, H256, MAINNET_GENESIS, U256}; use reth_rlp::{RlpDecodable, RlpEncodable}; 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 /// hash. This information should be treated as untrusted. +#[derive_arbitrary(rlp)] #[derive(Copy, Clone, PartialEq, Eq, RlpEncodable, RlpDecodable, Serialize, Deserialize)] pub struct Status { /// The current protocol version. For example, peers running `eth/66` would have a version of diff --git a/crates/net/eth-wire/src/types/transactions.rs b/crates/net/eth-wire/src/types/transactions.rs index 6e06369bb..ea5415e00 100644 --- a/crates/net/eth-wire/src/types/transactions.rs +++ b/crates/net/eth-wire/src/types/transactions.rs @@ -1,9 +1,11 @@ //! Implements the `GetPooledTransactions` and `PooledTransactions` message types. +use reth_codecs::derive_arbitrary; use reth_primitives::{TransactionSigned, H256}; use reth_rlp::{RlpDecodableWrapper, RlpEncodableWrapper}; use serde::{Deserialize, Serialize}; /// A list of transaction hashes that the peer would like transaction bodies for. +#[derive_arbitrary(rlp)] #[derive( Clone, Debug, @@ -36,6 +38,7 @@ where /// 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 /// included in the response. +#[derive_arbitrary(rlp, 10)] #[derive( Clone, Debug, diff --git a/crates/net/eth-wire/tests/fuzz_roundtrip.rs b/crates/net/eth-wire/tests/fuzz_roundtrip.rs index cc9fab1d5..e57c329c2 100644 --- a/crates/net/eth-wire/tests/fuzz_roundtrip.rs +++ b/crates/net/eth-wire/tests/fuzz_roundtrip.rs @@ -47,13 +47,14 @@ macro_rules! fuzz_type_and_name { #[allow(non_snake_case)] #[cfg(any(test, feature = "bench"))] pub mod fuzz_rlp { + use reth_codecs::derive_arbitrary; use reth_eth_wire::{ BlockBodies, BlockHeaders, DisconnectReason, GetBlockBodies, GetBlockHeaders, GetNodeData, GetPooledTransactions, GetReceipts, HelloMessage, NewBlock, NewBlockHashes, NewPooledTransactionHashes, NodeData, P2PMessage, PooledTransactions, Receipts, Status, Transactions, }; - use reth_primitives::BlockHashOrNumber; + use reth_primitives::{BlockHashOrNumber, TransactionSigned}; use reth_rlp::{RlpDecodableWrapper, RlpEncodableWrapper}; use serde::{Deserialize, Serialize}; 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 // Default impl + #[derive_arbitrary(rlp)] #[derive( Clone, 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 // type. + #[derive_arbitrary(rlp)] #[derive( Clone, Debug, @@ -141,6 +144,7 @@ pub mod fuzz_rlp { fuzz_type_and_name!(NodeData, fuzz_NodeData); fuzz_type_and_name!(GetReceipts, fuzz_GetReceipts); 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 diff --git a/crates/net/network/src/message.rs b/crates/net/network/src/message.rs index b42e9e37e..95934f75a 100644 --- a/crates/net/network/src/message.rs +++ b/crates/net/network/src/message.rs @@ -11,7 +11,7 @@ use reth_eth_wire::{ SharedTransactions, Transactions, }; 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::{ fmt, sync::Arc, @@ -198,7 +198,7 @@ pub enum PeerResponseResult { BlockHeaders(RequestResult>), BlockBodies(RequestResult>), PooledTransactions(RequestResult>), - NodeData(RequestResult>), + NodeData(RequestResult>), Receipts(RequestResult>>), } diff --git a/crates/primitives/Cargo.toml b/crates/primitives/Cargo.toml index a257ab3f1..f0ae1e2f8 100644 --- a/crates/primitives/Cargo.toml +++ b/crates/primitives/Cargo.toml @@ -62,6 +62,7 @@ hash-db = "0.15" arbitrary = { version = "1.1.7", features = ["derive"], optional = true } proptest = { version = "1.0", optional = true } proptest-derive = { version = "0.3", optional = true } +strum = { version = "0.24", features = ["derive"] } [dev-dependencies] serde_json = "1.0" diff --git a/crates/primitives/src/bits.rs b/crates/primitives/src/bits.rs new file mode 100644 index 000000000..2ffd8f3d4 --- /dev/null +++ b/crates/primitives/src/bits.rs @@ -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; + type Strategy = BoxedStrategy<$name>; + + fn arbitrary_with(args: Self::Parameters) -> Self::Strategy { + proptest::collection::vec(any_with::(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)); diff --git a/crates/primitives/src/block.rs b/crates/primitives/src/block.rs index 8191a3a6e..a517beef8 100644 --- a/crates/primitives/src/block.rs +++ b/crates/primitives/src/block.rs @@ -1,4 +1,5 @@ use crate::{Header, SealedHeader, TransactionSigned, H256}; +use reth_codecs::derive_arbitrary; use reth_rlp::{Decodable, DecodeError, Encodable, RlpDecodable, RlpEncodable}; use serde::{Deserialize, Serialize}; use std::ops::Deref; @@ -61,6 +62,7 @@ impl Deref for SealedBlock { } /// 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 diff --git a/crates/primitives/src/bloom.rs b/crates/primitives/src/bloom.rs index f6a16c393..be929af31 100644 --- a/crates/primitives/src/bloom.rs +++ b/crates/primitives/src/bloom.rs @@ -1,45 +1,16 @@ -//! Bloom related utilities. -use crate::{keccak256, Log}; +//! Bloom type. +use crate::{impl_fixed_hash_type, keccak256, Log}; 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}; - -#[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; +use reth_rlp::{RlpDecodableWrapper, RlpEncodableWrapper, RlpMaxEncodedLen}; /// Length of bloom filter used for Ethereum. pub const BLOOM_BYTE_LENGTH: usize = 256; -construct_fixed_hash! { - /// 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; - type Strategy = BoxedStrategy; - - fn arbitrary_with(args: Self::Parameters) -> Self::Strategy { - proptest::collection::vec(any_with::(args), BLOOM_BYTE_LENGTH) - .prop_map(move |vec| Bloom::from_slice(&vec)) - .boxed() - } -} +impl_fixed_hash_type!((Bloom, BLOOM_BYTE_LENGTH)); // See Section 4.3.1 "Transaction Receipt" of the Yellow Paper 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); - }); - } } diff --git a/crates/primitives/src/chain.rs b/crates/primitives/src/chain.rs index 43c167b23..2f2f14012 100644 --- a/crates/primitives/src/chain.rs +++ b/crates/primitives/src/chain.rs @@ -1,10 +1,12 @@ use crate::U256; use ethers_core::types::U64; +use reth_codecs::add_arbitrary_tests; use reth_rlp::{Decodable, Encodable}; use serde::{Deserialize, Serialize}; use std::{fmt, str::FromStr}; /// Either a named or chain id or the actual id value +#[add_arbitrary_tests(rlp)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] pub enum 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 { + 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; + type Strategy = BoxedStrategy; + + fn arbitrary_with(_: Self::Parameters) -> Self::Strategy { + let named = any::() + .prop_map(move |sel| Chain::Named(sel.select(ethers_core::types::Chain::iter()))); + let id = any::().prop_map(Chain::from); + proptest::strategy::Union::new_weighted(vec![(50, named.boxed()), (50, id.boxed())]).boxed() + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/primitives/src/forkid.rs b/crates/primitives/src/forkid.rs index 64e6a4bcb..97ab5590e 100644 --- a/crates/primitives/src/forkid.rs +++ b/crates/primitives/src/forkid.rs @@ -5,6 +5,7 @@ use crate::{BlockNumber, H256}; use crc::crc32; +use reth_codecs::derive_arbitrary; use reth_rlp::*; use serde::{Deserialize, Serialize}; use std::{ @@ -15,6 +16,7 @@ use std::{ use thiserror::Error; /// `CRC32` hash of all previous forks starting from genesis block. +#[derive_arbitrary(rlp)] #[derive( Clone, Copy, @@ -58,6 +60,7 @@ impl Add for ForkHash { /// A fork identifier as defined by EIP-2124. /// Serves as the chain compatibility identifier. +#[derive_arbitrary(rlp)] #[derive( Clone, Copy, diff --git a/crates/primitives/src/header.rs b/crates/primitives/src/header.rs index 129c48def..5e9d04f53 100644 --- a/crates/primitives/src/header.rs +++ b/crates/primitives/src/header.rs @@ -5,7 +5,7 @@ use crate::{ }; use bytes::{BufMut, BytesMut}; 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 serde::{Deserialize, Serialize}; use std::ops::Deref; @@ -290,6 +290,7 @@ impl SealedHeader { /// [`HeadersDirection::Falling`] block numbers for `reverse == 1 == true` /// /// See also +#[derive_arbitrary(rlp)] #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default, Serialize, Deserialize)] pub enum HeadersDirection { /// Falling block number. diff --git a/crates/primitives/src/hex_bytes.rs b/crates/primitives/src/hex_bytes.rs index be6a5323e..4512726a5 100644 --- a/crates/primitives/src/hex_bytes.rs +++ b/crates/primitives/src/hex_bytes.rs @@ -220,7 +220,7 @@ impl proptest::prelude::Arbitrary for Bytes { type Strategy = proptest::prelude::BoxedStrategy; fn arbitrary_with(args: Self::Parameters) -> Self::Strategy { - proptest::collection::vec(proptest::arbitrary::any_with::(args), 0..1000) + proptest::collection::vec(proptest::arbitrary::any_with::(args), 0..80) .prop_map(move |vec| bytes::Bytes::from(vec).into()) .boxed() } @@ -229,7 +229,7 @@ impl proptest::prelude::Arbitrary for Bytes { #[cfg(any(test, feature = "arbitrary"))] impl<'a> arbitrary::Arbitrary<'a> for Bytes { fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { - 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)?))) } } diff --git a/crates/primitives/src/lib.rs b/crates/primitives/src/lib.rs index 229d68174..f6bba5431 100644 --- a/crates/primitives/src/lib.rs +++ b/crates/primitives/src/lib.rs @@ -10,6 +10,7 @@ //! This crate contains Ethereum primitive types and helper functions. mod account; +mod bits; mod block; pub mod bloom; mod chain; @@ -32,6 +33,7 @@ mod transaction; pub mod proofs; pub use account::Account; +pub use bits::H512; pub use block::{Block, BlockHashOrNumber, SealedBlock}; pub use bloom::Bloom; pub use chain::Chain; @@ -77,9 +79,9 @@ pub type TransitionId = u64; pub use ethers_core::{ 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)] mod __reexport { diff --git a/crates/primitives/src/log.rs b/crates/primitives/src/log.rs index 5ff087fd5..2521428fd 100644 --- a/crates/primitives/src/log.rs +++ b/crates/primitives/src/log.rs @@ -3,7 +3,7 @@ use reth_codecs::{main_codec, Compact}; use reth_rlp::{RlpDecodable, RlpEncodable}; /// Ethereum Log -#[main_codec] +#[main_codec(rlp)] #[derive(Clone, Debug, PartialEq, Eq, RlpDecodable, RlpEncodable, Default)] pub struct Log { /// Contract that emitted this log. diff --git a/crates/primitives/src/peer.rs b/crates/primitives/src/peer.rs index e511a5667..69c10a720 100644 --- a/crates/primitives/src/peer.rs +++ b/crates/primitives/src/peer.rs @@ -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 // prevent misuse diff --git a/crates/primitives/src/receipt.rs b/crates/primitives/src/receipt.rs index 1e9fa1727..80761da98 100644 --- a/crates/primitives/src/receipt.rs +++ b/crates/primitives/src/receipt.rs @@ -112,7 +112,7 @@ impl Encodable for Receipt { if matches!(self.tx_type, TxType::EIP1559 | TxType::EIP2930) { payload_len += 1; // 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 diff --git a/crates/primitives/src/transaction/access_list.rs b/crates/primitives/src/transaction/access_list.rs index cdf666054..153dcb35b 100644 --- a/crates/primitives/src/transaction/access_list.rs +++ b/crates/primitives/src/transaction/access_list.rs @@ -5,7 +5,7 @@ use reth_rlp::{RlpDecodable, RlpDecodableWrapper, RlpEncodable, RlpEncodableWrap /// A list of addresses and storage keys that the transaction plans to access. /// Accesses outside the list are possible, but become more expensive. -#[main_codec] +#[main_codec(rlp)] #[derive(Clone, Debug, PartialEq, Eq, Hash, Default, RlpDecodable, RlpEncodable)] pub struct AccessListItem { /// Account addresses that would be loaded at the start of execution @@ -15,6 +15,6 @@ pub struct AccessListItem { } /// AccessList as defined in EIP-2930 -#[main_codec] +#[main_codec(rlp)] #[derive(Clone, Debug, PartialEq, Eq, Hash, Default, RlpDecodableWrapper, RlpEncodableWrapper)] pub struct AccessList(pub Vec); diff --git a/crates/primitives/src/transaction/mod.rs b/crates/primitives/src/transaction/mod.rs index 3ed11ea7d..df4260579 100644 --- a/crates/primitives/src/transaction/mod.rs +++ b/crates/primitives/src/transaction/mod.rs @@ -2,7 +2,7 @@ use crate::{keccak256, Address, Bytes, ChainId, TxHash, H256}; pub use access_list::{AccessList, AccessListItem}; use bytes::{Buf, BytesMut}; 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}; pub use signature::Signature; pub use tx_type::TxType; @@ -187,6 +187,15 @@ impl Transaction { 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. pub fn set_chain_id(&mut self, chain_id: u64) { match self { @@ -525,7 +534,8 @@ impl Decodable for TransactionKind { } /// Signed transaction. -#[main_codec] +#[main_codec(no_arbitrary)] +#[add_arbitrary_tests(rlp, compact)] #[derive(Debug, Clone, PartialEq, Eq, Hash, AsRef, Deref, Default)] pub struct TransactionSigned { /// Transaction hash @@ -538,6 +548,53 @@ pub struct TransactionSigned { 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; + + 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 { + 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 for TransactionSigned { fn from(recovered: TransactionSignedEcRecovered) -> Self { recovered.signed_transaction diff --git a/crates/primitives/src/transaction/tx_type.rs b/crates/primitives/src/transaction/tx_type.rs index b2dd851eb..215ff5194 100644 --- a/crates/primitives/src/transaction/tx_type.rs +++ b/crates/primitives/src/transaction/tx_type.rs @@ -1,8 +1,8 @@ -use reth_codecs::{derive_compact_arbitrary, Compact}; +use reth_codecs::{derive_arbitrary, Compact}; use serde::{Deserialize, Serialize}; /// Transaction Type -#[derive_compact_arbitrary] +#[derive_arbitrary(compact)] #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Default, Serialize, Deserialize)] pub enum TxType { /// Legacy transaction pre EIP-2929 diff --git a/crates/storage/codecs/derive/src/arbitrary.rs b/crates/storage/codecs/derive/src/arbitrary.rs new file mode 100644 index 000000000..96c259b49 --- /dev/null +++ b/crates/storage/codecs/derive/src/arbitrary.rs @@ -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 +} diff --git a/crates/storage/codecs/derive/src/lib.rs b/crates/storage/codecs/derive/src/lib.rs index fa2b60a63..0588bea5a 100644 --- a/crates/storage/codecs/derive/src/lib.rs +++ b/crates/storage/codecs/derive/src/lib.rs @@ -1,7 +1,8 @@ -use proc_macro::{self, TokenStream}; +use proc_macro::{self, TokenStream, TokenTree}; use quote::{format_ident, quote}; use syn::{parse_macro_input, DeriveInput}; +mod arbitrary; mod compact; #[proc_macro_derive(Compact, attributes(maybe_zero))] @@ -9,6 +10,11 @@ pub fn derive(input: TokenStream) -> TokenStream { 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] #[rustfmt::skip] #[allow(unreachable_code)] @@ -70,28 +76,48 @@ pub fn use_postcard(_args: TokenStream, input: TokenStream) -> TokenStream { } #[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 compact = quote! { + #[derive(Compact, serde::Serialize, serde::Deserialize)] + #ast + } + .into(); - derive_compact_arbitrary( - _args, - quote! { - #[derive(Compact, serde::Serialize, serde::Deserialize)] - #ast + if let Some(first_arg) = args.clone().into_iter().next() { + if first_arg.to_string() == "no_arbitrary" { + return compact } - .into(), - ) + } + + let mut args = args.into_iter().collect::>(); + 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] -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 tests = arbitrary::maybe_generate_tests(args, &ast); + // Avoid duplicate names let prop_import = format_ident!("{}PropTestArbitratry", 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! { #[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))] #ast - #[allow(non_snake_case)] - #[cfg(test)] - mod #mod_tests { - use super::Compact; + #tests + } + .into() +} - #[test] - fn proptest() { - proptest::proptest!(|(field: super::#type_ident)| { - let mut buf = vec![]; - - let len = field.clone().to_compact(&mut buf); - let (decoded, _) = super::#type_ident::from_compact(&buf, len); - - assert!(field == decoded); - }); - } - } +/// To be used for types that implement `Arbitrary` manually. See [`derive_arbitrary`] for more. +#[proc_macro_attribute] +pub fn add_arbitrary_tests(args: TokenStream, input: TokenStream) -> TokenStream { + let ast = parse_macro_input!(input as DeriveInput); + let tests = arbitrary::maybe_generate_tests(args, &ast); + quote! { + #ast + #tests } .into() } diff --git a/crates/storage/db/Cargo.toml b/crates/storage/db/Cargo.toml index 7cb98a9c1..59559c012 100644 --- a/crates/storage/db/Cargo.toml +++ b/crates/storage/db/Cargo.toml @@ -43,7 +43,7 @@ proptest-derive = { version = "0.3", optional = true } [dev-dependencies] # reth libs with 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"] } tempfile = "3.3.0"