mirror of
https://github.com/hl-archive-node/nanoreth.git
synced 2025-12-06 10:59:55 +00:00
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:
8
.github/workflows/ci.yml
vendored
8
.github/workflows/ci.yml
vendored
@ -8,6 +8,7 @@ env:
|
||||
RUSTFLAGS: -D warnings
|
||||
CARGO_TERM_COLOR: always
|
||||
GETH_BUILD: 1.10.26-e5eb32ac
|
||||
AFL_I_DONT_CARE_ABOUT_MISSING_CRASHES: 1
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||
@ -70,11 +71,16 @@ jobs:
|
||||
command: install
|
||||
args: cargo-test-fuzz afl
|
||||
|
||||
- name: check for cargo afl
|
||||
run: |
|
||||
cargo install --force afl
|
||||
cargo afl --version
|
||||
|
||||
- name: Run fuzz tests
|
||||
run: |
|
||||
./.github/scripts/fuzz.sh reth-primitives
|
||||
./.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
|
||||
|
||||
lint:
|
||||
|
||||
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -3342,6 +3342,7 @@ dependencies = [
|
||||
"serde",
|
||||
"smol_str",
|
||||
"snap",
|
||||
"test-fuzz",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
@ -3394,7 +3395,6 @@ dependencies = [
|
||||
"reth-rpc-types",
|
||||
"secp256k1",
|
||||
"serde",
|
||||
"test-fuzz",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
|
||||
@ -33,7 +33,6 @@ modular-bitfield = "0.11.2"
|
||||
|
||||
[dev-dependencies]
|
||||
reth-db = { path = "../storage/db", features = ["test-utils"] }
|
||||
test-fuzz = "3.0.4"
|
||||
tokio = { version = "1.21.2", features = ["full"] }
|
||||
tokio-stream = { version = "0.1.11", features = ["sync"] }
|
||||
arbitrary = { version = "1.1.7", features = ["derive"]}
|
||||
|
||||
@ -29,6 +29,7 @@ snap = "1.0.5"
|
||||
smol_str = { version = "0.1", features = ["serde"] }
|
||||
|
||||
[dev-dependencies]
|
||||
test-fuzz = "3.0.4"
|
||||
reth-ecies = { path = "../ecies" }
|
||||
ethers-core = { git = "https://github.com/gakonst/ethers-rs", default-features = false }
|
||||
|
||||
|
||||
@ -2,9 +2,7 @@
|
||||
//! messages.
|
||||
|
||||
use crate::{
|
||||
capability::Capability,
|
||||
p2pstream::{HelloMessage, ProtocolVersion},
|
||||
EthVersion, Status,
|
||||
capability::Capability, hello::HelloMessage, p2pstream::ProtocolVersion, EthVersion, Status,
|
||||
};
|
||||
use reth_primitives::{Chain, ForkId, PeerId, H256, U256};
|
||||
|
||||
|
||||
@ -26,7 +26,9 @@ pub enum CapabilityMessage {
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
/// The name of the subprotocol
|
||||
pub name: SmolStr,
|
||||
|
||||
@ -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.
|
||||
#[derive(Debug, Clone, Error)]
|
||||
#[error("unknown disconnect reason: {0}")]
|
||||
|
||||
@ -243,7 +243,8 @@ mod tests {
|
||||
use super::UnauthedEthStream;
|
||||
use crate::{
|
||||
capability::Capability,
|
||||
p2pstream::{HelloMessage, ProtocolVersion, UnauthedP2PStream},
|
||||
hello::HelloMessage,
|
||||
p2pstream::{ProtocolVersion, UnauthedP2PStream},
|
||||
types::{broadcast::BlockHashNumber, EthMessage, EthVersion, Status},
|
||||
EthStream, PassthroughCodec,
|
||||
};
|
||||
|
||||
131
crates/net/eth-wire/src/hello.rs
Normal file
131
crates/net/eth-wire/src/hello.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
#![warn(missing_docs, unreachable_pub, unused_crate_dependencies)]
|
||||
#![warn(missing_docs, unreachable_pub)]
|
||||
#![deny(unused_must_use, rust_2018_idioms)]
|
||||
#![doc(test(
|
||||
no_crate_inject,
|
||||
@ -11,6 +11,7 @@ pub mod capability;
|
||||
mod disconnect;
|
||||
pub mod error;
|
||||
mod ethstream;
|
||||
mod hello;
|
||||
mod p2pstream;
|
||||
mod pinger;
|
||||
pub use builder::*;
|
||||
@ -25,5 +26,6 @@ pub use tokio_util::codec::{
|
||||
pub use crate::{
|
||||
disconnect::DisconnectReason,
|
||||
ethstream::{EthStream, UnauthedEthStream, MAX_MESSAGE_SIZE},
|
||||
p2pstream::{HelloMessage, P2PStream, ProtocolVersion, UnauthedP2PStream},
|
||||
hello::HelloMessage,
|
||||
p2pstream::{P2PMessage, P2PMessageID, P2PStream, ProtocolVersion, UnauthedP2PStream},
|
||||
};
|
||||
|
||||
@ -3,13 +3,12 @@ use crate::{
|
||||
capability::{Capability, SharedCapability},
|
||||
error::{P2PHandshakeError, P2PStreamError},
|
||||
pinger::{Pinger, PingerEvent},
|
||||
DisconnectReason,
|
||||
DisconnectReason, HelloMessage,
|
||||
};
|
||||
use bytes::{Buf, Bytes, BytesMut};
|
||||
use futures::{Sink, SinkExt, StreamExt};
|
||||
use pin_project::pin_project;
|
||||
use reth_primitives::H512 as PeerId;
|
||||
use reth_rlp::{Decodable, DecodeError, Encodable, RlpDecodable, RlpEncodable, EMPTY_STRING_CODE};
|
||||
use reth_rlp::{Decodable, DecodeError, Encodable, EMPTY_STRING_CODE};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
collections::{BTreeSet, HashMap, VecDeque},
|
||||
@ -524,7 +523,7 @@ pub fn set_capability_offsets(
|
||||
}
|
||||
|
||||
/// This represents only the reserved `p2p` subprotocol messages.
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum P2PMessage {
|
||||
/// The first packet sent over the connection, and sent once by both sides.
|
||||
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
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum ProtocolVersion {
|
||||
@ -701,6 +682,12 @@ impl Decodable for ProtocolVersion {
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ProtocolVersion {
|
||||
fn default() -> Self {
|
||||
ProtocolVersion::V5
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@ -842,98 +829,4 @@ mod tests {
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -36,7 +36,15 @@ pub struct GetBlockHeaders {
|
||||
|
||||
/// The response to [`GetBlockHeaders`], containing headers if any headers were found.
|
||||
#[derive(
|
||||
Clone, Debug, PartialEq, Eq, RlpEncodableWrapper, RlpDecodableWrapper, Serialize, Deserialize,
|
||||
Clone,
|
||||
Debug,
|
||||
PartialEq,
|
||||
Eq,
|
||||
RlpEncodableWrapper,
|
||||
RlpDecodableWrapper,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
Default,
|
||||
)]
|
||||
pub struct BlockHeaders(
|
||||
/// 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.
|
||||
#[derive(
|
||||
Clone, Debug, PartialEq, Eq, RlpEncodableWrapper, RlpDecodableWrapper, Serialize, Deserialize,
|
||||
Clone,
|
||||
Debug,
|
||||
PartialEq,
|
||||
Eq,
|
||||
RlpEncodableWrapper,
|
||||
RlpDecodableWrapper,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
Default,
|
||||
)]
|
||||
pub struct GetBlockBodies(
|
||||
/// 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
|
||||
/// 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 {
|
||||
/// Transactions in the block
|
||||
pub transactions: Vec<TransactionSigned>,
|
||||
@ -88,7 +106,15 @@ impl BlockBody {
|
||||
/// The response to [`GetBlockBodies`], containing the block bodies that the peer knows about if
|
||||
/// any were found.
|
||||
#[derive(
|
||||
Clone, Debug, PartialEq, Eq, RlpEncodableWrapper, RlpDecodableWrapper, Serialize, Deserialize,
|
||||
Clone,
|
||||
Debug,
|
||||
PartialEq,
|
||||
Eq,
|
||||
RlpEncodableWrapper,
|
||||
RlpDecodableWrapper,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
Default,
|
||||
)]
|
||||
pub struct BlockBodies(
|
||||
/// The requested block bodies, each of which should correspond to a hash in the request.
|
||||
|
||||
@ -6,7 +6,15 @@ use std::sync::Arc;
|
||||
|
||||
/// This informs peers of new blocks that have appeared on the network.
|
||||
#[derive(
|
||||
Clone, Debug, PartialEq, Eq, RlpEncodableWrapper, RlpDecodableWrapper, Serialize, Deserialize,
|
||||
Clone,
|
||||
Debug,
|
||||
PartialEq,
|
||||
Eq,
|
||||
RlpEncodableWrapper,
|
||||
RlpDecodableWrapper,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
Default,
|
||||
)]
|
||||
pub struct NewBlockHashes(
|
||||
/// New block hashes and the block number for each blockhash.
|
||||
@ -29,7 +37,9 @@ impl NewBlockHashes {
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
/// The block hash
|
||||
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
|
||||
/// block.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, RlpEncodable, RlpDecodable, Serialize, Deserialize)]
|
||||
#[derive(
|
||||
Clone, Debug, PartialEq, Eq, RlpEncodable, RlpDecodable, Serialize, Deserialize, Default,
|
||||
)]
|
||||
pub struct NewBlock {
|
||||
/// A new block.
|
||||
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
|
||||
/// in a block.
|
||||
#[derive(
|
||||
Clone, Debug, PartialEq, Eq, RlpEncodableWrapper, RlpDecodableWrapper, Serialize, Deserialize,
|
||||
Clone,
|
||||
Debug,
|
||||
PartialEq,
|
||||
Eq,
|
||||
RlpEncodableWrapper,
|
||||
RlpDecodableWrapper,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
Default,
|
||||
)]
|
||||
pub struct Transactions(
|
||||
/// 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,
|
||||
/// but have not been included in a block.
|
||||
#[derive(
|
||||
Clone, Debug, PartialEq, Eq, RlpEncodableWrapper, RlpDecodableWrapper, Serialize, Deserialize,
|
||||
Clone,
|
||||
Debug,
|
||||
PartialEq,
|
||||
Eq,
|
||||
RlpEncodableWrapper,
|
||||
RlpDecodableWrapper,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
Default,
|
||||
)]
|
||||
pub struct NewPooledTransactionHashes(
|
||||
/// Transaction hashes for new transactions that have appeared on the network.
|
||||
|
||||
@ -5,7 +5,15 @@ use serde::{Deserialize, Serialize};
|
||||
|
||||
/// A request for transaction receipts from the given block hashes.
|
||||
#[derive(
|
||||
Clone, Debug, PartialEq, Eq, RlpEncodableWrapper, RlpDecodableWrapper, Serialize, Deserialize,
|
||||
Clone,
|
||||
Debug,
|
||||
PartialEq,
|
||||
Eq,
|
||||
RlpEncodableWrapper,
|
||||
RlpDecodableWrapper,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
Default,
|
||||
)]
|
||||
pub struct GetReceipts(
|
||||
/// 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
|
||||
/// requested.
|
||||
#[derive(
|
||||
Clone, Debug, PartialEq, Eq, RlpEncodableWrapper, RlpDecodableWrapper, Serialize, Deserialize,
|
||||
Clone,
|
||||
Debug,
|
||||
PartialEq,
|
||||
Eq,
|
||||
RlpEncodableWrapper,
|
||||
RlpDecodableWrapper,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
Default,
|
||||
)]
|
||||
pub struct Receipts(
|
||||
/// Each receipt hash should correspond to a block hash in the request.
|
||||
|
||||
@ -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.
|
||||
#[derive(
|
||||
Clone, Debug, PartialEq, Eq, RlpEncodableWrapper, RlpDecodableWrapper, Serialize, Deserialize,
|
||||
Clone,
|
||||
Debug,
|
||||
PartialEq,
|
||||
Eq,
|
||||
RlpEncodableWrapper,
|
||||
RlpDecodableWrapper,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
Default,
|
||||
)]
|
||||
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.
|
||||
/// This message was removed in `eth/67`.
|
||||
#[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>);
|
||||
|
||||
|
||||
@ -5,7 +5,15 @@ use serde::{Deserialize, Serialize};
|
||||
|
||||
/// A list of transaction hashes that the peer would like transaction bodies for.
|
||||
#[derive(
|
||||
Clone, Debug, PartialEq, Eq, RlpEncodableWrapper, RlpDecodableWrapper, Serialize, Deserialize,
|
||||
Clone,
|
||||
Debug,
|
||||
PartialEq,
|
||||
Eq,
|
||||
RlpEncodableWrapper,
|
||||
RlpDecodableWrapper,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
Default,
|
||||
)]
|
||||
pub struct GetPooledTransactions(
|
||||
/// 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
|
||||
/// included in the response.
|
||||
#[derive(
|
||||
Clone, Debug, PartialEq, Eq, RlpEncodableWrapper, RlpDecodableWrapper, Serialize, Deserialize,
|
||||
Clone,
|
||||
Debug,
|
||||
PartialEq,
|
||||
Eq,
|
||||
RlpEncodableWrapper,
|
||||
RlpDecodableWrapper,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
Default,
|
||||
)]
|
||||
pub struct PooledTransactions(
|
||||
/// The transaction bodies, each of which should correspond to a requested hash.
|
||||
|
||||
116
crates/net/eth-wire/tests/fuzz_roundtrip.rs
Normal file
116
crates/net/eth-wire/tests/fuzz_roundtrip.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
@ -37,12 +37,16 @@ eyre = "0.6.8"
|
||||
[dev-dependencies]
|
||||
tempfile = "3.3.0"
|
||||
test-fuzz = "3.0.4"
|
||||
|
||||
criterion = "0.4.0"
|
||||
iai = "0.1.1"
|
||||
tokio = { version = "1.21.2", features = ["full"] }
|
||||
arbitrary = { version = "1.1.7", features = ["derive"]}
|
||||
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"] }
|
||||
async-trait = "0.1.58"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user