diff --git a/Cargo.lock b/Cargo.lock index bfca56c1f7..152e38dc62 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4564,6 +4564,7 @@ dependencies = [ "bytes", "ethers-core", "futures", + "hex", "hex-literal", "metrics", "pin-project", diff --git a/crates/net/eth-wire/Cargo.toml b/crates/net/eth-wire/Cargo.toml index fb76a352b6..b4cc8118d4 100644 --- a/crates/net/eth-wire/Cargo.toml +++ b/crates/net/eth-wire/Cargo.toml @@ -45,6 +45,7 @@ ethers-core = { git = "https://github.com/gakonst/ethers-rs", default-features = test-fuzz = "3.0.4" tokio-util = { version = "0.7.4", features = ["io", "codec"] } hex-literal = "0.3" +hex = "0.4" rand = "0.8" secp256k1 = { version = "0.24.2", features = ["global-context", "rand-std", "recovery"] } diff --git a/crates/net/eth-wire/src/types/broadcast.rs b/crates/net/eth-wire/src/types/broadcast.rs index c6fef5457f..163f16bf8b 100644 --- a/crates/net/eth-wire/src/types/broadcast.rs +++ b/crates/net/eth-wire/src/types/broadcast.rs @@ -1,8 +1,11 @@ //! Types for broadcasting new data. use crate::{EthMessage, EthVersion}; +use bytes::Bytes; use reth_codecs::derive_arbitrary; use reth_primitives::{Block, TransactionSigned, H256, U128}; -use reth_rlp::{RlpDecodable, RlpDecodableWrapper, RlpEncodable, RlpEncodableWrapper}; +use reth_rlp::{ + Decodable, Encodable, RlpDecodable, RlpDecodableWrapper, RlpEncodable, RlpEncodableWrapper, +}; use std::sync::Arc; #[cfg(feature = "serde")] @@ -193,10 +196,32 @@ impl From> for NewPooledTransactionHashes66 { /// Same as [`NewPooledTransactionHashes66`] but extends that that beside the transaction hashes, /// the node sends the transaction types and their sizes (as defined in EIP-2718) as well. #[derive_arbitrary(rlp)] -#[derive(Clone, Debug, PartialEq, Eq, RlpEncodable, RlpDecodable, Default)] +#[derive(Clone, Debug, PartialEq, Eq, Default)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct NewPooledTransactionHashes68 { /// Transaction types for new transactions that have appeared on the network. + /// + /// ## Note on RLP encoding and decoding + /// + /// In the [eth/68 spec](https://eips.ethereum.org/EIPS/eip-5793#specification) this is defined + /// the following way: + /// * `[type_0: B_1, type_1: B_1, ...]` + /// + /// This would make it seem like the [`Encodable`](reth_rlp::Encodable) and + /// [`Decodable`](reth_rlp::Decodable) implementations should directly use a `Vec` for + /// encoding and decoding, because it looks like this field should be encoded as a _list_ of + /// bytes. + /// + /// However, [this is implemented in geth as a `[]byte` + /// type](https://github.com/ethereum/go-ethereum/blob/82d934b1dd80cdd8190803ea9f73ed2c345e2576/eth/protocols/eth/protocol.go#L308-L313), + /// which [ends up being encoded as a RLP + /// string](https://github.com/ethereum/go-ethereum/blob/82d934b1dd80cdd8190803ea9f73ed2c345e2576/rlp/encode_test.go#L171-L176), + /// **not** a RLP list. + /// + /// Because of this, we do not directly use the `Vec` when encoding and decoding, and + /// instead use the [`Encodable`](reth_rlp::Encodable) and [`Decodable`](reth_rlp::Decodable) + /// implementations for `&[u8]` instead, which encodes into a RLP string, and expects an RLP + /// string when decoding. pub types: Vec, /// Transaction sizes for new transactions that have appeared on the network. pub sizes: Vec, @@ -204,10 +229,80 @@ pub struct NewPooledTransactionHashes68 { pub hashes: Vec, } +impl Encodable for NewPooledTransactionHashes68 { + fn length(&self) -> usize { + #[derive(RlpEncodable)] + struct EncodableNewPooledTransactionHashes68<'a> { + types: &'a [u8], + sizes: &'a Vec, + hashes: &'a Vec, + } + + let encodable = EncodableNewPooledTransactionHashes68 { + types: &self.types[..], + sizes: &self.sizes, + hashes: &self.hashes, + }; + + encodable.length() + } + fn encode(&self, out: &mut dyn bytes::BufMut) { + #[derive(RlpEncodable)] + struct EncodableNewPooledTransactionHashes68<'a> { + types: &'a [u8], + sizes: &'a Vec, + hashes: &'a Vec, + } + + let encodable = EncodableNewPooledTransactionHashes68 { + types: &self.types[..], + sizes: &self.sizes, + hashes: &self.hashes, + }; + + encodable.encode(out); + } +} + +impl Decodable for NewPooledTransactionHashes68 { + fn decode(buf: &mut &[u8]) -> Result { + #[derive(RlpDecodable)] + struct EncodableNewPooledTransactionHashes68 { + types: Bytes, + sizes: Vec, + hashes: Vec, + } + + let encodable = EncodableNewPooledTransactionHashes68::decode(buf)?; + Ok(Self { types: encodable.types.into(), sizes: encodable.sizes, hashes: encodable.hashes }) + } +} + #[cfg(test)] mod tests { + use std::str::FromStr; + + use bytes::BytesMut; + use hex_literal::hex; + use reth_rlp::{Decodable, Encodable}; + use super::*; + /// Takes as input a struct / encoded hex message pair, ensuring that we encode to the exact hex + /// message, and decode to the exact struct. + fn test_encoding_vector( + input: (T, &[u8]), + ) { + let (expected_decoded, expected_encoded) = input; + let mut encoded = BytesMut::new(); + expected_decoded.encode(&mut encoded); + + assert_eq!(hex::encode(&encoded), hex::encode(expected_encoded)); + + let decoded = T::decode(&mut encoded.as_ref()).unwrap(); + assert_eq!(expected_decoded, decoded); + } + #[test] fn can_return_latest_block() { let mut blocks = NewBlockHashes(vec![BlockHashNumber { hash: H256::random(), number: 0 }]); @@ -219,4 +314,125 @@ mod tests { let latest = blocks.latest().unwrap(); assert_eq!(latest.number, 100); } + + #[test] + fn eth_68_tx_hash_roundtrip() { + let vectors = vec![ + ( + NewPooledTransactionHashes68 { types: vec![], sizes: vec![], hashes: vec![] }, + &hex!("c380c0c0")[..], + ), + ( + NewPooledTransactionHashes68 { + types: vec![0x00], + sizes: vec![0x00], + hashes: vec![H256::from_str( + "0x0000000000000000000000000000000000000000000000000000000000000000", + ) + .unwrap()], + }, + &hex!("e500c180e1a00000000000000000000000000000000000000000000000000000000000000000")[..], + ), + ( + NewPooledTransactionHashes68 { + types: vec![0x00, 0x00], + sizes: vec![0x00, 0x00], + hashes: vec![ + H256::from_str( + "0x0000000000000000000000000000000000000000000000000000000000000000", + ) + .unwrap(), + H256::from_str( + "0x0000000000000000000000000000000000000000000000000000000000000000", + ) + .unwrap(), + ], + }, + &hex!("f84a820000c28080f842a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000")[..], + ), + ( + NewPooledTransactionHashes68 { + types: vec![0x02], + sizes: vec![0xb6], + hashes: vec![H256::from_str( + "0xfecbed04c7b88d8e7221a0a3f5dc33f220212347fc167459ea5cc9c3eb4c1124", + ) + .unwrap()], + }, + &hex!("e602c281b6e1a0fecbed04c7b88d8e7221a0a3f5dc33f220212347fc167459ea5cc9c3eb4c1124")[..], + ), + ( + NewPooledTransactionHashes68 { + types: vec![0xff, 0xff], + sizes: vec![0xffffffff, 0xffffffff], + hashes: vec![ + H256::from_str( + "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + ) + .unwrap(), + H256::from_str( + "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + ) + .unwrap(), + ], + }, + &hex!("f85282ffffca84ffffffff84fffffffff842a0ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")[..], + ), + ( + NewPooledTransactionHashes68 { + types: vec![0xff, 0xff], + sizes: vec![0xffffffff, 0xffffffff], + hashes: vec![ + H256::from_str( + "0xbeefcafebeefcafebeefcafebeefcafebeefcafebeefcafebeefcafebeefcafe", + ) + .unwrap(), + H256::from_str( + "0xbeefcafebeefcafebeefcafebeefcafebeefcafebeefcafebeefcafebeefcafe", + ) + .unwrap(), + ], + }, + &hex!("f85282ffffca84ffffffff84fffffffff842a0beefcafebeefcafebeefcafebeefcafebeefcafebeefcafebeefcafebeefcafea0beefcafebeefcafebeefcafebeefcafebeefcafebeefcafebeefcafebeefcafe")[..], + ), + ( + NewPooledTransactionHashes68 { + types: vec![0x10, 0x10], + sizes: vec![0xdeadc0de, 0xdeadc0de], + hashes: vec![ + H256::from_str( + "0x3b9aca00f0671c9a2a1b817a0a78d3fe0c0f776cccb2a8c3c1b412a4f4e4d4e2", + ) + .unwrap(), + H256::from_str( + "0x3b9aca00f0671c9a2a1b817a0a78d3fe0c0f776cccb2a8c3c1b412a4f4e4d4e2", + ) + .unwrap(), + ], + }, + &hex!("f852821010ca84deadc0de84deadc0def842a03b9aca00f0671c9a2a1b817a0a78d3fe0c0f776cccb2a8c3c1b412a4f4e4d4e2a03b9aca00f0671c9a2a1b817a0a78d3fe0c0f776cccb2a8c3c1b412a4f4e4d4e2")[..], + ), + ( + NewPooledTransactionHashes68 { + types: vec![0x6f, 0x6f], + sizes: vec![0x7fffffff, 0x7fffffff], + hashes: vec![ + H256::from_str( + "0x0000000000000000000000000000000000000000000000000000000000000002", + ) + .unwrap(), + H256::from_str( + "0x0000000000000000000000000000000000000000000000000000000000000002", + ) + .unwrap(), + ], + }, + &hex!("f852826f6fca847fffffff847ffffffff842a00000000000000000000000000000000000000000000000000000000000000002a00000000000000000000000000000000000000000000000000000000000000002")[..], + ), + ]; + + for vector in vectors { + test_encoding_vector(vector); + } + } }