feat(eth-wire): fuzzing wire encoding roundtrip (#350)

* move hello to separate file

* cargo fmt

* wip: actual fuzz test

 * should probably also take advantage of test-fuzz to generate
   benchmarks like impl_fuzzer_with_input

* impl generic roundtrip method

* generate test with macro

* change testname to fuzzname

* add reth-eth-wire to fuzz in ci

* add other message types to fuzz

* remove unused_crate_dependencies

 * was causing test issues, may want to revisit whether or not we can
   include this warning and still use test_fuzz

* more afl debugging ci

* use more explicit imports in fuzz_rlp

* impl Default for types and fuzz ping/pong

 * Default is necessary for test-fuzz to auto generate a corpus for each
   type we are fuzz testing

* enable AFL_I_DONT_CARE_ABOUT_MISSING_CRASHES

 * not sure if we should do this in the workflow instead:
   echo core >/proc/sys/kernel/core_pattern

   we may miss crashes if we keep this enabled?

* remove reth-interfaces from fuzzing

* add secp256k1 to reth-db dev deps
This commit is contained in:
Dan Cline
2022-12-13 12:10:52 -05:00
committed by GitHub
parent dff3936b29
commit 1e38ffa5ad
18 changed files with 402 additions and 141 deletions

View File

@ -8,6 +8,7 @@ env:
RUSTFLAGS: -D warnings RUSTFLAGS: -D warnings
CARGO_TERM_COLOR: always CARGO_TERM_COLOR: always
GETH_BUILD: 1.10.26-e5eb32ac GETH_BUILD: 1.10.26-e5eb32ac
AFL_I_DONT_CARE_ABOUT_MISSING_CRASHES: 1
concurrency: concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
@ -70,11 +71,16 @@ jobs:
command: install command: install
args: cargo-test-fuzz afl args: cargo-test-fuzz afl
- name: check for cargo afl
run: |
cargo install --force afl
cargo afl --version
- name: Run fuzz tests - name: Run fuzz tests
run: | run: |
./.github/scripts/fuzz.sh reth-primitives ./.github/scripts/fuzz.sh reth-primitives
./.github/scripts/fuzz.sh reth-db ./.github/scripts/fuzz.sh reth-db
./.github/scripts/fuzz.sh reth-interfaces ./.github/scripts/fuzz.sh reth-eth-wire
./.github/scripts/fuzz.sh reth-codecs ./.github/scripts/fuzz.sh reth-codecs
lint: lint:

2
Cargo.lock generated
View File

@ -3342,6 +3342,7 @@ dependencies = [
"serde", "serde",
"smol_str", "smol_str",
"snap", "snap",
"test-fuzz",
"thiserror", "thiserror",
"tokio", "tokio",
"tokio-stream", "tokio-stream",
@ -3394,7 +3395,6 @@ dependencies = [
"reth-rpc-types", "reth-rpc-types",
"secp256k1", "secp256k1",
"serde", "serde",
"test-fuzz",
"thiserror", "thiserror",
"tokio", "tokio",
"tokio-stream", "tokio-stream",

View File

@ -33,7 +33,6 @@ modular-bitfield = "0.11.2"
[dev-dependencies] [dev-dependencies]
reth-db = { path = "../storage/db", features = ["test-utils"] } reth-db = { path = "../storage/db", features = ["test-utils"] }
test-fuzz = "3.0.4"
tokio = { version = "1.21.2", features = ["full"] } tokio = { version = "1.21.2", features = ["full"] }
tokio-stream = { version = "0.1.11", features = ["sync"] } tokio-stream = { version = "0.1.11", features = ["sync"] }
arbitrary = { version = "1.1.7", features = ["derive"]} arbitrary = { version = "1.1.7", features = ["derive"]}

View File

@ -29,6 +29,7 @@ snap = "1.0.5"
smol_str = { version = "0.1", features = ["serde"] } smol_str = { version = "0.1", features = ["serde"] }
[dev-dependencies] [dev-dependencies]
test-fuzz = "3.0.4"
reth-ecies = { path = "../ecies" } reth-ecies = { path = "../ecies" }
ethers-core = { git = "https://github.com/gakonst/ethers-rs", default-features = false } ethers-core = { git = "https://github.com/gakonst/ethers-rs", default-features = false }

View File

@ -2,9 +2,7 @@
//! messages. //! messages.
use crate::{ use crate::{
capability::Capability, capability::Capability, hello::HelloMessage, p2pstream::ProtocolVersion, EthVersion, Status,
p2pstream::{HelloMessage, ProtocolVersion},
EthVersion, Status,
}; };
use reth_primitives::{Chain, ForkId, PeerId, H256, U256}; use reth_primitives::{Chain, ForkId, PeerId, H256, U256};

View File

@ -26,7 +26,9 @@ pub enum CapabilityMessage {
} }
/// A message indicating a supported capability and capability version. /// A message indicating a supported capability and capability version.
#[derive(Clone, Debug, PartialEq, Eq, RlpEncodable, RlpDecodable, Serialize, Deserialize)] #[derive(
Clone, Debug, PartialEq, Eq, RlpEncodable, RlpDecodable, Serialize, Deserialize, Default,
)]
pub struct Capability { pub struct Capability {
/// The name of the subprotocol /// The name of the subprotocol
pub name: SmolStr, pub name: SmolStr,

View File

@ -65,6 +65,12 @@ impl Display for DisconnectReason {
} }
} }
impl Default for DisconnectReason {
fn default() -> Self {
DisconnectReason::DisconnectRequested
}
}
/// This represents an unknown disconnect reason with the given code. /// This represents an unknown disconnect reason with the given code.
#[derive(Debug, Clone, Error)] #[derive(Debug, Clone, Error)]
#[error("unknown disconnect reason: {0}")] #[error("unknown disconnect reason: {0}")]

View File

@ -243,7 +243,8 @@ mod tests {
use super::UnauthedEthStream; use super::UnauthedEthStream;
use crate::{ use crate::{
capability::Capability, capability::Capability,
p2pstream::{HelloMessage, ProtocolVersion, UnauthedP2PStream}, hello::HelloMessage,
p2pstream::{ProtocolVersion, UnauthedP2PStream},
types::{broadcast::BlockHashNumber, EthMessage, EthVersion, Status}, types::{broadcast::BlockHashNumber, EthMessage, EthVersion, Status},
EthStream, PassthroughCodec, EthStream, PassthroughCodec,
}; };

View File

@ -0,0 +1,131 @@
use crate::{capability::Capability, ProtocolVersion};
use reth_primitives::PeerId;
use reth_rlp::{RlpDecodable, RlpEncodable};
use serde::{Deserialize, Serialize};
// 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(
Clone, Debug, PartialEq, Eq, RlpEncodable, RlpDecodable, Serialize, Deserialize, Default,
)]
pub struct HelloMessage {
/// The version of the `p2p` protocol.
pub protocol_version: ProtocolVersion,
/// Specifies the client software identity, as a human-readable string (e.g.
/// "Ethereum(++)/1.0.0").
pub client_version: String,
/// The list of supported capabilities and their versions.
pub capabilities: Vec<Capability>,
/// The port that the client is listening on, zero indicates the client is not listening.
pub port: u16,
/// The secp256k1 public key corresponding to the node's private key.
pub id: PeerId,
}
#[cfg(test)]
mod tests {
use reth_ecies::util::pk2id;
use reth_rlp::{Decodable, Encodable, EMPTY_STRING_CODE};
use secp256k1::{SecretKey, SECP256K1};
use crate::{
capability::Capability,
p2pstream::{P2PMessage, P2PMessageID},
EthVersion, HelloMessage, ProtocolVersion,
};
#[test]
fn test_pong_snappy_encoding_parity() {
// encode pong using our `Encodable` implementation
let pong = P2PMessage::Pong;
let mut pong_encoded = Vec::new();
pong.encode(&mut pong_encoded);
// the definition of pong is 0x80 (an empty rlp string)
let pong_raw = vec![EMPTY_STRING_CODE];
let mut snappy_encoder = snap::raw::Encoder::new();
let pong_compressed = snappy_encoder.compress_vec(&pong_raw).unwrap();
let mut pong_expected = vec![P2PMessageID::Pong as u8];
pong_expected.extend(&pong_compressed);
// ensure that the two encodings are equal
assert_eq!(
pong_expected, pong_encoded,
"left: {pong_expected:#x?}, right: {pong_encoded:#x?}"
);
// also ensure that the length is correct
assert_eq!(pong_expected.len(), P2PMessage::Pong.length());
// try to decode using Decodable
let p2p_message = P2PMessage::decode(&mut &pong_expected[..]).unwrap();
assert_eq!(p2p_message, P2PMessage::Pong);
// finally decode the encoded message with snappy
let mut snappy_decoder = snap::raw::Decoder::new();
// the message id is not compressed, only compress the latest bits
let decompressed = snappy_decoder.decompress_vec(&pong_encoded[1..]).unwrap();
assert_eq!(decompressed, pong_raw);
}
#[test]
fn test_hello_encoding_round_trip() {
let secret_key = SecretKey::new(&mut rand::thread_rng());
let id = pk2id(&secret_key.public_key(SECP256K1));
let hello = P2PMessage::Hello(HelloMessage {
protocol_version: ProtocolVersion::V5,
client_version: "reth/0.1.0".to_string(),
capabilities: vec![Capability::new("eth".into(), EthVersion::Eth67 as usize)],
port: 30303,
id,
});
let mut hello_encoded = Vec::new();
hello.encode(&mut hello_encoded);
let hello_decoded = P2PMessage::decode(&mut &hello_encoded[..]).unwrap();
assert_eq!(hello, hello_decoded);
}
#[test]
fn hello_encoding_length() {
let secret_key = SecretKey::new(&mut rand::thread_rng());
let id = pk2id(&secret_key.public_key(SECP256K1));
let hello = P2PMessage::Hello(HelloMessage {
protocol_version: ProtocolVersion::V5,
client_version: "reth/0.1.0".to_string(),
capabilities: vec![Capability::new("eth".into(), EthVersion::Eth67 as usize)],
port: 30303,
id,
});
let mut hello_encoded = Vec::new();
hello.encode(&mut hello_encoded);
assert_eq!(hello_encoded.len(), hello.length());
}
#[test]
fn hello_message_id_prefix() {
// ensure that the hello message id is prefixed
let secret_key = SecretKey::new(&mut rand::thread_rng());
let id = pk2id(&secret_key.public_key(SECP256K1));
let hello = P2PMessage::Hello(HelloMessage {
protocol_version: ProtocolVersion::V5,
client_version: "reth/0.1.0".to_string(),
capabilities: vec![Capability::new("eth".into(), EthVersion::Eth67 as usize)],
port: 30303,
id,
});
let mut hello_encoded = Vec::new();
hello.encode(&mut hello_encoded);
// zero is encoded as 0x80, the empty string code in RLP
assert_eq!(hello_encoded[0], EMPTY_STRING_CODE);
}
}

View File

@ -1,4 +1,4 @@
#![warn(missing_docs, unreachable_pub, unused_crate_dependencies)] #![warn(missing_docs, unreachable_pub)]
#![deny(unused_must_use, rust_2018_idioms)] #![deny(unused_must_use, rust_2018_idioms)]
#![doc(test( #![doc(test(
no_crate_inject, no_crate_inject,
@ -11,6 +11,7 @@ pub mod capability;
mod disconnect; mod disconnect;
pub mod error; pub mod error;
mod ethstream; mod ethstream;
mod hello;
mod p2pstream; mod p2pstream;
mod pinger; mod pinger;
pub use builder::*; pub use builder::*;
@ -25,5 +26,6 @@ pub use tokio_util::codec::{
pub use crate::{ pub use crate::{
disconnect::DisconnectReason, disconnect::DisconnectReason,
ethstream::{EthStream, UnauthedEthStream, MAX_MESSAGE_SIZE}, ethstream::{EthStream, UnauthedEthStream, MAX_MESSAGE_SIZE},
p2pstream::{HelloMessage, P2PStream, ProtocolVersion, UnauthedP2PStream}, hello::HelloMessage,
p2pstream::{P2PMessage, P2PMessageID, P2PStream, ProtocolVersion, UnauthedP2PStream},
}; };

View File

@ -3,13 +3,12 @@ use crate::{
capability::{Capability, SharedCapability}, capability::{Capability, SharedCapability},
error::{P2PHandshakeError, P2PStreamError}, error::{P2PHandshakeError, P2PStreamError},
pinger::{Pinger, PingerEvent}, pinger::{Pinger, PingerEvent},
DisconnectReason, DisconnectReason, HelloMessage,
}; };
use bytes::{Buf, Bytes, BytesMut}; use bytes::{Buf, Bytes, BytesMut};
use futures::{Sink, SinkExt, StreamExt}; use futures::{Sink, SinkExt, StreamExt};
use pin_project::pin_project; use pin_project::pin_project;
use reth_primitives::H512 as PeerId; use reth_rlp::{Decodable, DecodeError, Encodable, EMPTY_STRING_CODE};
use reth_rlp::{Decodable, DecodeError, Encodable, RlpDecodable, RlpEncodable, EMPTY_STRING_CODE};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::{ use std::{
collections::{BTreeSet, HashMap, VecDeque}, collections::{BTreeSet, HashMap, VecDeque},
@ -524,7 +523,7 @@ pub fn set_capability_offsets(
} }
/// This represents only the reserved `p2p` subprotocol messages. /// This represents only the reserved `p2p` subprotocol messages.
#[derive(Debug, 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.
Hello(HelloMessage), Hello(HelloMessage),
@ -653,24 +652,6 @@ impl TryFrom<u8> for P2PMessageID {
} }
} }
// 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(Clone, Debug, PartialEq, Eq, RlpEncodable, RlpDecodable, Serialize, Deserialize)]
pub struct HelloMessage {
/// The version of the `p2p` protocol.
pub protocol_version: ProtocolVersion,
/// Specifies the client software identity, as a human-readable string (e.g.
/// "Ethereum(++)/1.0.0").
pub client_version: String,
/// The list of supported capabilities and their versions.
pub capabilities: Vec<Capability>,
/// The port that the client is listening on, zero indicates the client is not listening.
pub port: u16,
/// The secp256k1 public key corresponding to the node's private key.
pub id: PeerId,
}
/// RLPx `p2p` protocol version /// RLPx `p2p` protocol version
#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum ProtocolVersion { pub enum ProtocolVersion {
@ -701,6 +682,12 @@ impl Decodable for ProtocolVersion {
} }
} }
impl Default for ProtocolVersion {
fn default() -> Self {
ProtocolVersion::V5
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@ -842,98 +829,4 @@ mod tests {
assert_eq!(decompressed, ping_raw); assert_eq!(decompressed, ping_raw);
} }
#[test]
fn test_pong_snappy_encoding_parity() {
// encode pong using our `Encodable` implementation
let pong = P2PMessage::Pong;
let mut pong_encoded = Vec::new();
pong.encode(&mut pong_encoded);
// the definition of pong is 0x80 (an empty rlp string)
let pong_raw = vec![EMPTY_STRING_CODE];
let mut snappy_encoder = snap::raw::Encoder::new();
let pong_compressed = snappy_encoder.compress_vec(&pong_raw).unwrap();
let mut pong_expected = vec![P2PMessageID::Pong as u8];
pong_expected.extend(&pong_compressed);
// ensure that the two encodings are equal
assert_eq!(
pong_expected, pong_encoded,
"left: {pong_expected:#x?}, right: {pong_encoded:#x?}"
);
// also ensure that the length is correct
assert_eq!(pong_expected.len(), P2PMessage::Pong.length());
// try to decode using Decodable
let p2p_message = P2PMessage::decode(&mut &pong_expected[..]).unwrap();
assert_eq!(p2p_message, P2PMessage::Pong);
// finally decode the encoded message with snappy
let mut snappy_decoder = snap::raw::Decoder::new();
// the message id is not compressed, only compress the latest bits
let decompressed = snappy_decoder.decompress_vec(&pong_encoded[1..]).unwrap();
assert_eq!(decompressed, pong_raw);
}
#[test]
fn test_hello_encoding_round_trip() {
let secret_key = SecretKey::new(&mut rand::thread_rng());
let id = pk2id(&secret_key.public_key(SECP256K1));
let hello = P2PMessage::Hello(HelloMessage {
protocol_version: ProtocolVersion::V5,
client_version: "reth/0.1.0".to_string(),
capabilities: vec![Capability::new("eth".into(), EthVersion::Eth67 as usize)],
port: 30303,
id,
});
let mut hello_encoded = Vec::new();
hello.encode(&mut hello_encoded);
let hello_decoded = P2PMessage::decode(&mut &hello_encoded[..]).unwrap();
assert_eq!(hello, hello_decoded);
}
#[test]
fn hello_encoding_length() {
let secret_key = SecretKey::new(&mut rand::thread_rng());
let id = pk2id(&secret_key.public_key(SECP256K1));
let hello = P2PMessage::Hello(HelloMessage {
protocol_version: ProtocolVersion::V5,
client_version: "reth/0.1.0".to_string(),
capabilities: vec![Capability::new("eth".into(), EthVersion::Eth67 as usize)],
port: 30303,
id,
});
let mut hello_encoded = Vec::new();
hello.encode(&mut hello_encoded);
assert_eq!(hello_encoded.len(), hello.length());
}
#[test]
fn hello_message_id_prefix() {
// ensure that the hello message id is prefixed
let secret_key = SecretKey::new(&mut rand::thread_rng());
let id = pk2id(&secret_key.public_key(SECP256K1));
let hello = P2PMessage::Hello(HelloMessage {
protocol_version: ProtocolVersion::V5,
client_version: "reth/0.1.0".to_string(),
capabilities: vec![Capability::new("eth".into(), EthVersion::Eth67 as usize)],
port: 30303,
id,
});
let mut hello_encoded = Vec::new();
hello.encode(&mut hello_encoded);
// zero is encoded as 0x80, the empty string code in RLP
assert_eq!(hello_encoded[0], EMPTY_STRING_CODE);
}
} }

View File

@ -36,7 +36,15 @@ 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( #[derive(
Clone, Debug, PartialEq, Eq, RlpEncodableWrapper, RlpDecodableWrapper, Serialize, Deserialize, Clone,
Debug,
PartialEq,
Eq,
RlpEncodableWrapper,
RlpDecodableWrapper,
Serialize,
Deserialize,
Default,
)] )]
pub struct BlockHeaders( pub struct BlockHeaders(
/// The requested headers. /// The requested headers.
@ -51,7 +59,15 @@ 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( #[derive(
Clone, Debug, PartialEq, Eq, RlpEncodableWrapper, RlpDecodableWrapper, Serialize, Deserialize, Clone,
Debug,
PartialEq,
Eq,
RlpEncodableWrapper,
RlpDecodableWrapper,
Serialize,
Deserialize,
Default,
)] )]
pub struct GetBlockBodies( pub struct GetBlockBodies(
/// The block hashes to request bodies for. /// The block hashes to request bodies for.
@ -66,7 +82,9 @@ 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(Clone, Debug, PartialEq, Eq, RlpEncodable, RlpDecodable, Serialize, Deserialize)] #[derive(
Clone, Debug, PartialEq, Eq, RlpEncodable, RlpDecodable, Serialize, Deserialize, Default,
)]
pub struct BlockBody { pub struct BlockBody {
/// Transactions in the block /// Transactions in the block
pub transactions: Vec<TransactionSigned>, pub transactions: Vec<TransactionSigned>,
@ -88,7 +106,15 @@ 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( #[derive(
Clone, Debug, PartialEq, Eq, RlpEncodableWrapper, RlpDecodableWrapper, Serialize, Deserialize, Clone,
Debug,
PartialEq,
Eq,
RlpEncodableWrapper,
RlpDecodableWrapper,
Serialize,
Deserialize,
Default,
)] )]
pub struct BlockBodies( pub struct BlockBodies(
/// The requested block bodies, each of which should correspond to a hash in the request. /// The requested block bodies, each of which should correspond to a hash in the request.

View File

@ -6,7 +6,15 @@ 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( #[derive(
Clone, Debug, PartialEq, Eq, RlpEncodableWrapper, RlpDecodableWrapper, Serialize, Deserialize, Clone,
Debug,
PartialEq,
Eq,
RlpEncodableWrapper,
RlpDecodableWrapper,
Serialize,
Deserialize,
Default,
)] )]
pub struct NewBlockHashes( pub struct NewBlockHashes(
/// New block hashes and the block number for each blockhash. /// New block hashes and the block number for each blockhash.
@ -29,7 +37,9 @@ impl NewBlockHashes {
} }
/// A block hash _and_ a block number. /// A block hash _and_ a block number.
#[derive(Clone, Debug, PartialEq, Eq, RlpEncodable, RlpDecodable, Serialize, Deserialize)] #[derive(
Clone, Debug, PartialEq, Eq, RlpEncodable, RlpDecodable, Serialize, Deserialize, Default,
)]
pub struct BlockHashNumber { pub struct BlockHashNumber {
/// The block hash /// The block hash
pub hash: H256, pub hash: H256,
@ -64,7 +74,9 @@ 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(Clone, Debug, PartialEq, Eq, RlpEncodable, RlpDecodable, Serialize, Deserialize)] #[derive(
Clone, Debug, PartialEq, Eq, RlpEncodable, RlpDecodable, Serialize, Deserialize, Default,
)]
pub struct NewBlock { pub struct NewBlock {
/// A new block. /// A new block.
pub block: RawBlockBody, pub block: RawBlockBody,
@ -75,7 +87,15 @@ 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( #[derive(
Clone, Debug, PartialEq, Eq, RlpEncodableWrapper, RlpDecodableWrapper, Serialize, Deserialize, Clone,
Debug,
PartialEq,
Eq,
RlpEncodableWrapper,
RlpDecodableWrapper,
Serialize,
Deserialize,
Default,
)] )]
pub struct Transactions( pub struct Transactions(
/// New transactions for the peer to include in its mempool. /// New transactions for the peer to include in its mempool.
@ -107,7 +127,15 @@ 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( #[derive(
Clone, Debug, PartialEq, Eq, RlpEncodableWrapper, RlpDecodableWrapper, Serialize, Deserialize, Clone,
Debug,
PartialEq,
Eq,
RlpEncodableWrapper,
RlpDecodableWrapper,
Serialize,
Deserialize,
Default,
)] )]
pub struct NewPooledTransactionHashes( pub struct NewPooledTransactionHashes(
/// Transaction hashes for new transactions that have appeared on the network. /// Transaction hashes for new transactions that have appeared on the network.

View File

@ -5,7 +5,15 @@ 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( #[derive(
Clone, Debug, PartialEq, Eq, RlpEncodableWrapper, RlpDecodableWrapper, Serialize, Deserialize, Clone,
Debug,
PartialEq,
Eq,
RlpEncodableWrapper,
RlpDecodableWrapper,
Serialize,
Deserialize,
Default,
)] )]
pub struct GetReceipts( pub struct GetReceipts(
/// The block hashes to request receipts for. /// The block hashes to request receipts for.
@ -15,7 +23,15 @@ 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( #[derive(
Clone, Debug, PartialEq, Eq, RlpEncodableWrapper, RlpDecodableWrapper, Serialize, Deserialize, Clone,
Debug,
PartialEq,
Eq,
RlpEncodableWrapper,
RlpDecodableWrapper,
Serialize,
Deserialize,
Default,
)] )]
pub struct Receipts( pub struct Receipts(
/// Each receipt hash should correspond to a block hash in the request. /// Each receipt hash should correspond to a block hash in the request.

View File

@ -7,7 +7,15 @@ use serde::{Deserialize, Serialize};
/// 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( #[derive(
Clone, Debug, PartialEq, Eq, RlpEncodableWrapper, RlpDecodableWrapper, Serialize, Deserialize, Clone,
Debug,
PartialEq,
Eq,
RlpEncodableWrapper,
RlpDecodableWrapper,
Serialize,
Deserialize,
Default,
)] )]
pub struct GetNodeData(pub Vec<H256>); pub struct GetNodeData(pub Vec<H256>);
@ -17,7 +25,15 @@ 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( #[derive(
Clone, Debug, PartialEq, Eq, RlpEncodableWrapper, RlpDecodableWrapper, Serialize, Deserialize, Clone,
Debug,
PartialEq,
Eq,
RlpEncodableWrapper,
RlpDecodableWrapper,
Serialize,
Deserialize,
Default,
)] )]
pub struct NodeData(pub Vec<bytes::Bytes>); pub struct NodeData(pub Vec<bytes::Bytes>);

View File

@ -5,7 +5,15 @@ 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( #[derive(
Clone, Debug, PartialEq, Eq, RlpEncodableWrapper, RlpDecodableWrapper, Serialize, Deserialize, Clone,
Debug,
PartialEq,
Eq,
RlpEncodableWrapper,
RlpDecodableWrapper,
Serialize,
Deserialize,
Default,
)] )]
pub struct GetPooledTransactions( pub struct GetPooledTransactions(
/// The transaction hashes to request transaction bodies for. /// The transaction hashes to request transaction bodies for.
@ -29,7 +37,15 @@ where
/// 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( #[derive(
Clone, Debug, PartialEq, Eq, RlpEncodableWrapper, RlpDecodableWrapper, Serialize, Deserialize, Clone,
Debug,
PartialEq,
Eq,
RlpEncodableWrapper,
RlpDecodableWrapper,
Serialize,
Deserialize,
Default,
)] )]
pub struct PooledTransactions( pub struct PooledTransactions(
/// The transaction bodies, each of which should correspond to a requested hash. /// The transaction bodies, each of which should correspond to a requested hash.

View File

@ -0,0 +1,116 @@
//! Round-trip encoding fuzzing for the `eth-wire` crate.
use reth_rlp::{Decodable, Encodable};
use serde::Serialize;
use std::fmt::Debug;
/// Creates a fuzz test for a type that should be [`Encodable`](reth_rlp::Encodable) and
/// [`Decodable`](reth_rlp::Decodable).
///
/// The test will create a random instance of the type, encode it, and then decode it.
fn roundtrip_encoding<T>(thing: T)
where
T: Encodable + Decodable + Clone + Serialize + Debug + PartialEq + Eq,
{
let mut encoded = Vec::new();
thing.encode(&mut encoded);
let decoded = T::decode(&mut &encoded[..]).unwrap();
assert_eq!(thing, decoded, "expected: {thing:?}, got: {decoded:?}");
}
/// Creates a fuzz test for a rlp encodable and decodable type.
macro_rules! fuzz_type_and_name {
( $x:ty, $fuzzname:ident ) => {
/// Fuzzes the round-trip encoding of the type.
#[test_fuzz]
#[allow(non_snake_case)]
fn $fuzzname(thing: $x) {
roundtrip_encoding::<$x>(thing)
}
};
}
#[allow(non_snake_case)]
#[cfg(any(test, feature = "bench"))]
pub mod fuzz_rlp {
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_rlp::{RlpDecodableWrapper, RlpEncodableWrapper};
use serde::{Deserialize, Serialize};
use test_fuzz::test_fuzz;
use crate::roundtrip_encoding;
// p2p subprotocol messages
fuzz_type_and_name!(HelloMessage, fuzz_HelloMessage);
fuzz_type_and_name!(DisconnectReason, fuzz_DisconnectReason);
// eth subprotocol messages
fuzz_type_and_name!(Status, fuzz_Status);
fuzz_type_and_name!(NewBlockHashes, fuzz_NewBlockHashes);
fuzz_type_and_name!(Transactions, fuzz_Transactions);
// GetBlockHeaders implements all the traits required for roundtrip_encoding, so why is this
// wrapper type needed?
//
// While GetBlockHeaders implements all traits needed to work for test-fuzz, it does not have
// an obvious Default implementation since BlockHashOrNumber can be either a hash or number,
// and the default value of BlockHashOrNumber is not obvious.
//
// We just provide a default value here so test-fuzz can auto-generate a corpus file for the
// type.
#[derive(
Clone,
Debug,
PartialEq,
Eq,
Serialize,
Deserialize,
RlpEncodableWrapper,
RlpDecodableWrapper,
)]
struct GetBlockHeadersWrapper(pub GetBlockHeaders);
impl Default for GetBlockHeadersWrapper {
fn default() -> Self {
GetBlockHeadersWrapper(GetBlockHeaders {
start_block: BlockHashOrNumber::Number(0),
limit: Default::default(),
skip: Default::default(),
direction: Default::default(),
})
}
}
fuzz_type_and_name!(GetBlockHeadersWrapper, fuzz_GetBlockHeaders);
fuzz_type_and_name!(BlockHeaders, fuzz_BlockHeaders);
fuzz_type_and_name!(GetBlockBodies, fuzz_GetBlockBodies);
fuzz_type_and_name!(BlockBodies, fuzz_BlockBodies);
fuzz_type_and_name!(NewBlock, fuzz_NewBlock);
fuzz_type_and_name!(NewPooledTransactionHashes, fuzz_NewPooledTransactionHashes);
fuzz_type_and_name!(GetPooledTransactions, fuzz_GetPooledTransactions);
fuzz_type_and_name!(PooledTransactions, fuzz_PooledTransactions);
fuzz_type_and_name!(GetNodeData, fuzz_GetNodeData);
fuzz_type_and_name!(NodeData, fuzz_NodeData);
fuzz_type_and_name!(GetReceipts, fuzz_GetReceipts);
fuzz_type_and_name!(Receipts, fuzz_Receipts);
// manually test Ping and Pong which are not covered by the above
/// Tests the round-trip encoding of Ping
#[test]
fn roundtrip_ping() {
roundtrip_encoding::<P2PMessage>(P2PMessage::Ping)
}
/// Tests the round-trip encoding of Pong
#[test]
fn roundtrip_pong() {
roundtrip_encoding::<P2PMessage>(P2PMessage::Pong)
}
}

View File

@ -37,12 +37,16 @@ eyre = "0.6.8"
[dev-dependencies] [dev-dependencies]
tempfile = "3.3.0" tempfile = "3.3.0"
test-fuzz = "3.0.4" test-fuzz = "3.0.4"
criterion = "0.4.0" criterion = "0.4.0"
iai = "0.1.1" iai = "0.1.1"
tokio = { version = "1.21.2", features = ["full"] } tokio = { version = "1.21.2", features = ["full"] }
arbitrary = { version = "1.1.7", features = ["derive"]} arbitrary = { version = "1.1.7", features = ["derive"]}
reth-db = { path = ".", features = ["test-utils","bench"]} reth-db = { path = ".", features = ["test-utils","bench"]}
# needed for test-fuzz to work properly, see https://github.com/paradigmxyz/reth/pull/177#discussion_r1021172198
secp256k1 = "0.24.0"
reth-interfaces = { path = "../../interfaces",features=["bench"] } reth-interfaces = { path = "../../interfaces",features=["bench"] }
async-trait = "0.1.58" async-trait = "0.1.58"