From 94bcff28b19715061c38af418afd8f0b979792a8 Mon Sep 17 00:00:00 2001 From: rustaceanrob Date: Tue, 27 May 2025 13:55:21 +0100 Subject: [PATCH 1/2] p2p: Add wrappers for messages that use `Vec` In preparation to move `p2p` to its own crate, any `Vec` that satisfies `T: Encodable` will not trivially implement `Encodable` because of the orphan rule. A type defined within p2p may implement `Encodable`, however, and the simplest possible type is one that simply wraps the vector. Three types that will implement `Encodable` within `p2p` are added here. --- bitcoin/src/p2p/message.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/bitcoin/src/p2p/message.rs b/bitcoin/src/p2p/message.rs index bb69ec86d..b464623ef 100644 --- a/bitcoin/src/p2p/message.rs +++ b/bitcoin/src/p2p/message.rs @@ -163,6 +163,18 @@ pub struct V2NetworkMessage { payload: NetworkMessage, } +/// A list of inventory items. +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct InventoryPayload(pub Vec); + +/// A list of legacy p2p address messages. +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct AddrPayload(pub Vec<(u32, Address)>); + +/// A list of v2 address messages. +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct AddrV2Payload(pub Vec); + /// A Network message payload. Proper documentation is available on at /// [Bitcoin Wiki: Protocol Specification](https://en.bitcoin.it/wiki/Protocol_specification) #[derive(Clone, PartialEq, Eq, Debug)] From 20779bdbc8ebcac2365d122e857c23b3d8e8d1e7 Mon Sep 17 00:00:00 2001 From: rustaceanrob Date: Tue, 27 May 2025 14:24:18 +0100 Subject: [PATCH 2/2] Move `p2p` encodings out of `encode` All of the `Encodable` implementations are defined within `p2p` with the exception of `p2p` types that are a `Vec`. To fully decouple `p2p` from `encode` we can define these in `p2p` like the others. This is the final step in removing anything `p2p` related from the rest of `bitcoin`. --- bitcoin/src/consensus/encode.rs | 18 --------------- bitcoin/src/p2p/address.rs | 6 ++--- bitcoin/src/p2p/deser.rs | 41 +++++++++++++++++++++++++++++++++ bitcoin/src/p2p/message.rs | 33 +++++++++++++++----------- bitcoin/src/p2p/mod.rs | 2 ++ 5 files changed, 65 insertions(+), 35 deletions(-) create mode 100644 bitcoin/src/p2p/deser.rs diff --git a/bitcoin/src/consensus/encode.rs b/bitcoin/src/consensus/encode.rs index 3620c1867..4e6cd660f 100644 --- a/bitcoin/src/consensus/encode.rs +++ b/bitcoin/src/consensus/encode.rs @@ -26,11 +26,6 @@ use crate::bip152::{PrefilledTransaction, ShortId}; use crate::bip158::{FilterHash, FilterHeader}; use crate::block::{self, BlockHash}; use crate::merkle_tree::TxMerkleNode; -#[cfg(feature = "std")] -use crate::p2p::{ - address::{AddrV2Message, Address}, - message_blockdata::Inventory, -}; use crate::prelude::{rc, sync, Box, Cow, String, Vec}; use crate::taproot::TapLeafHash; use crate::transaction::{Transaction, TxIn, TxOut}; @@ -552,13 +547,6 @@ impl_vec!(TapLeafHash); impl_vec!(ShortId); impl_vec!(PrefilledTransaction); -#[cfg(feature = "std")] -impl_vec!(Inventory); -#[cfg(feature = "std")] -impl_vec!((u32, Address)); -#[cfg(feature = "std")] -impl_vec!(AddrV2Message); - pub(crate) fn consensus_encode_with_size( data: &[u8], w: &mut W, @@ -765,8 +753,6 @@ mod tests { use core::mem::discriminant; use super::*; - #[cfg(feature = "std")] - use crate::p2p::{message_blockdata::Inventory, Address}; #[test] fn serialize_int() { @@ -1049,10 +1035,6 @@ mod tests { test_len_is_max_vec::(); test_len_is_max_vec::>(); test_len_is_max_vec::(); - #[cfg(feature = "std")] - test_len_is_max_vec::<(u32, Address)>(); - #[cfg(feature = "std")] - test_len_is_max_vec::(); } fn test_len_is_max_vec() diff --git a/bitcoin/src/p2p/address.rs b/bitcoin/src/p2p/address.rs index 0a3809bfd..927cdcd34 100644 --- a/bitcoin/src/p2p/address.rs +++ b/bitcoin/src/p2p/address.rs @@ -493,7 +493,7 @@ mod test { use hex_lit::hex; use super::*; - use crate::consensus::encode::{deserialize, serialize}; + use crate::{consensus::encode::{deserialize, serialize}, p2p::message::AddrV2Payload}; #[test] fn serialize_address() { @@ -706,10 +706,10 @@ mod test { #[test] fn addrv2message() { let raw = hex!("0261bc6649019902abab208d79627683fd4804010409090909208d"); - let addresses: Vec = deserialize(&raw).unwrap(); + let addresses: AddrV2Payload = deserialize(&raw).unwrap(); assert_eq!( - addresses, + addresses.0, vec![ AddrV2Message { services: ServiceFlags::NETWORK, diff --git a/bitcoin/src/p2p/deser.rs b/bitcoin/src/p2p/deser.rs new file mode 100644 index 000000000..09a18f23b --- /dev/null +++ b/bitcoin/src/p2p/deser.rs @@ -0,0 +1,41 @@ +macro_rules! impl_vec_wrapper { + ($wrapper: ident, $type: ty) => { + impl crate::consensus::encode::Encodable for $wrapper { + #[inline] + fn consensus_encode( + &self, + w: &mut W, + ) -> core::result::Result { + let mut len = 0; + len += w.emit_compact_size(self.0.len())?; + for c in self.0.iter() { + len += c.consensus_encode(w)?; + } + Ok(len) + } + } + + impl crate::consensus::encode::Decodable for $wrapper { + #[inline] + fn consensus_decode_from_finite_reader( + r: &mut R, + ) -> core::result::Result<$wrapper, crate::consensus::encode::Error> { + let len = r.read_compact_size()?; + // Do not allocate upfront more items than if the sequence of type + // occupied roughly quarter a block. This should never be the case + // for normal data, but even if that's not true - `push` will just + // reallocate. + // Note: OOM protection relies on reader eventually running out of + // data to feed us. + let max_capacity = crate::consensus::encode::MAX_VEC_SIZE / 4 / core::mem::size_of::<$type>(); + let mut ret = Vec::with_capacity(core::cmp::min(len as usize, max_capacity)); + for _ in 0..len { + ret.push(Decodable::consensus_decode_from_finite_reader(r)?); + } + Ok($wrapper(ret)) + } + } + }; +} + +pub(crate) use impl_vec_wrapper; diff --git a/bitcoin/src/p2p/message.rs b/bitcoin/src/p2p/message.rs index b464623ef..3bfc8513b 100644 --- a/bitcoin/src/p2p/message.rs +++ b/bitcoin/src/p2p/message.rs @@ -19,6 +19,7 @@ use crate::p2p::{ Magic, }; use crate::prelude::{Box, Cow, String, ToOwned, Vec}; +use crate::p2p::deser::impl_vec_wrapper; use crate::{block, consensus, transaction}; /// The maximum number of [super::message_blockdata::Inventory] items in an `inv` message. @@ -175,6 +176,10 @@ pub struct AddrPayload(pub Vec<(u32, Address)>); #[derive(Clone, PartialEq, Eq, Debug)] pub struct AddrV2Payload(pub Vec); +impl_vec_wrapper!(InventoryPayload, message_blockdata::Inventory); +impl_vec_wrapper!(AddrPayload, (u32, Address)); +impl_vec_wrapper!(AddrV2Payload, AddrV2Message); + /// A Network message payload. Proper documentation is available on at /// [Bitcoin Wiki: Protocol Specification](https://en.bitcoin.it/wiki/Protocol_specification) #[derive(Clone, PartialEq, Eq, Debug)] @@ -184,13 +189,13 @@ pub enum NetworkMessage { /// `verack` Verack, /// `addr` - Addr(Vec<(u32, Address)>), + Addr(AddrPayload), /// `inv` - Inv(Vec), + Inv(InventoryPayload), /// `getdata` - GetData(Vec), + GetData(InventoryPayload), /// `notfound` - NotFound(Vec), + NotFound(InventoryPayload), /// `getblocks` GetBlocks(message_blockdata::GetBlocksMessage), /// `getheaders` @@ -248,7 +253,7 @@ pub enum NetworkMessage { /// `wtxidrelay` WtxidRelay, /// `addrv2` - AddrV2(Vec), + AddrV2(AddrV2Payload), /// `sendaddrv2` SendAddrV2, @@ -746,17 +751,17 @@ mod test { let msgs = [ NetworkMessage::Version(version_msg), NetworkMessage::Verack, - NetworkMessage::Addr(vec![( + NetworkMessage::Addr(AddrPayload(vec![( 45, Address::new(&([123, 255, 000, 100], 833).into(), ServiceFlags::NETWORK), - )]), - NetworkMessage::Inv(vec![Inventory::Block(BlockHash::from_byte_array( + )])), + NetworkMessage::Inv(InventoryPayload(vec![Inventory::Block(BlockHash::from_byte_array( hash([8u8; 32]).to_byte_array(), - ))]), - NetworkMessage::GetData(vec![Inventory::Transaction(Txid::from_byte_array( + ))])), + NetworkMessage::GetData(InventoryPayload(vec![Inventory::Transaction(Txid::from_byte_array( hash([45u8; 32]).to_byte_array(), - ))]), - NetworkMessage::NotFound(vec![Inventory::Error([0u8; 32])]), + ))])), + NetworkMessage::NotFound(InventoryPayload(vec![Inventory::Error([0u8; 32])])), NetworkMessage::GetBlocks(GetBlocksMessage::new( vec![ BlockHash::from_byte_array(hash([1u8; 32]).to_byte_array()), @@ -838,12 +843,12 @@ mod test { }), NetworkMessage::FeeFilter(1000), NetworkMessage::WtxidRelay, - NetworkMessage::AddrV2(vec![AddrV2Message { + NetworkMessage::AddrV2(AddrV2Payload(vec![AddrV2Message { addr: AddrV2::Ipv4(Ipv4Addr::new(127, 0, 0, 1)), port: 0, services: ServiceFlags::NONE, time: 0, - }]), + }])), NetworkMessage::SendAddrV2, NetworkMessage::CmpctBlock(cmptblock), NetworkMessage::GetBlockTxn(GetBlockTxn { diff --git a/bitcoin/src/p2p/mod.rs b/bitcoin/src/p2p/mod.rs index cb9c1f5a8..31cbdd6ae 100644 --- a/bitcoin/src/p2p/mod.rs +++ b/bitcoin/src/p2p/mod.rs @@ -19,6 +19,8 @@ pub mod message_compact_blocks; pub mod message_filter; #[cfg(feature = "std")] pub mod message_network; +#[cfg(feature = "std")] +mod deser; use core::str::FromStr; use core::{fmt, ops};