Merge rust-bitcoin/rust-bitcoin#3792: Add BIP324 V2 p2p network message support

e09bdb5f98 Add BIP324 V2 p2p network message support (Nick Johnson)

Pull request description:

  Migrating over the BIP324's library's V2 network message encoding and decoding. As discussed over in https://github.com/rust-bitcoin/rust-bitcoin/discussions/2959, this is probably the natural home for it and also cleans up some gross copy/pasta of some of the encoding/decoding chain logic.

  This patch adds `V2NetworkMessage` which wraps a `NetworkMessage`, but handles the V2 encoding and decoding. It is a parallel of the existing `RawNetworkMessage` (which mentioned before, may be better described as `V1NetworkMessage` https://github.com/rust-bitcoin/rust-bitcoin/issues/3157). A priority of this patch was to not re-invent any wheels and try to use the existing patterns as much as possible.

ACKs for top commit:
  tcharding:
    ACK e09bdb5f98
  apoelstra:
    ACK e09bdb5f98ea516382a04283373ad97a41d57c2b; successfully ran local tests; nice!

Tree-SHA512: a5078d4d3deb04c2e06ea513bbc8c97d0e6d5da5b029847a97b3f90bf55a263858dd16d88299f853aa3c468f7b9bceb3973c5652a49d3e96df3e91181b455f29
This commit is contained in:
merge-script 2025-01-09 18:46:39 +00:00
commit 653ea702d6
No known key found for this signature in database
GPG Key ID: C588D63CE41B97C1
1 changed files with 258 additions and 3 deletions

View File

@ -148,7 +148,7 @@ impl std::error::Error for CommandStringError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None } fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None }
} }
/// A Network message /// A Network message using the v1 p2p protocol.
#[derive(Clone, Debug, PartialEq, Eq)] #[derive(Clone, Debug, PartialEq, Eq)]
pub struct RawNetworkMessage { pub struct RawNetworkMessage {
magic: Magic, magic: Magic,
@ -157,6 +157,12 @@ pub struct RawNetworkMessage {
checksum: [u8; 4], checksum: [u8; 4],
} }
/// A Network message using the v2 p2p protocol defined in BIP324.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct V2NetworkMessage {
payload: NetworkMessage,
}
/// A Network message payload. Proper documentation is available on at /// A Network message payload. Proper documentation is available on at
/// [Bitcoin Wiki: Protocol Specification](https://en.bitcoin.it/wiki/Protocol_specification) /// [Bitcoin Wiki: Protocol Specification](https://en.bitcoin.it/wiki/Protocol_specification)
#[derive(Clone, PartialEq, Eq, Debug)] #[derive(Clone, PartialEq, Eq, Debug)]
@ -332,6 +338,27 @@ impl RawNetworkMessage {
pub fn command(&self) -> CommandString { self.payload.command() } pub fn command(&self) -> CommandString { self.payload.command() }
} }
impl V2NetworkMessage {
/// Constructs a new [V2NetworkMessage].
pub fn new(payload: NetworkMessage) -> Self { Self { payload } }
/// Consumes the [V2NetworkMessage] instance and returns the inner payload.
pub fn into_payload(self) -> NetworkMessage { self.payload }
/// The actual message data
pub fn payload(&self) -> &NetworkMessage { &self.payload }
/// Return the message command as a static string reference.
///
/// This returns `"unknown"` for [NetworkMessage::Unknown],
/// regardless of the actual command in the unknown message.
/// Use the [Self::command] method to get the command for unknown messages.
pub fn cmd(&self) -> &'static str { self.payload.cmd() }
/// Return the CommandString for the message command.
pub fn command(&self) -> CommandString { self.payload.command() }
}
struct HeaderSerializationWrapper<'a>(&'a Vec<block::Header>); struct HeaderSerializationWrapper<'a>(&'a Vec<block::Header>);
impl Encodable for HeaderSerializationWrapper<'_> { impl Encodable for HeaderSerializationWrapper<'_> {
@ -404,6 +431,62 @@ impl Encodable for RawNetworkMessage {
} }
} }
impl Encodable for V2NetworkMessage {
fn consensus_encode<W: Write + ?Sized>(&self, writer: &mut W) -> Result<usize, io::Error> {
// A subset of message types are optimized to only use one byte to encode the command.
// Non-optimized message types use the zero-byte flag and the following twelve bytes to encode the command.
let (command_byte, full_command) = match self.payload {
NetworkMessage::Addr(_) => (1u8, None),
NetworkMessage::Inv(_) => (14u8, None),
NetworkMessage::GetData(_) => (11u8, None),
NetworkMessage::NotFound(_) => (17u8, None),
NetworkMessage::GetBlocks(_) => (9u8, None),
NetworkMessage::GetHeaders(_) => (12u8, None),
NetworkMessage::MemPool => (15u8, None),
NetworkMessage::Tx(_) => (21u8, None),
NetworkMessage::Block(_) => (2u8, None),
NetworkMessage::Headers(_) => (13u8, None),
NetworkMessage::Ping(_) => (18u8, None),
NetworkMessage::Pong(_) => (19u8, None),
NetworkMessage::MerkleBlock(_) => (16u8, None),
NetworkMessage::FilterLoad(_) => (8u8, None),
NetworkMessage::FilterAdd(_) => (6u8, None),
NetworkMessage::FilterClear => (7u8, None),
NetworkMessage::GetCFilters(_) => (22u8, None),
NetworkMessage::CFilter(_) => (23u8, None),
NetworkMessage::GetCFHeaders(_) => (24u8, None),
NetworkMessage::CFHeaders(_) => (25u8, None),
NetworkMessage::GetCFCheckpt(_) => (26u8, None),
NetworkMessage::CFCheckpt(_) => (27u8, None),
NetworkMessage::SendCmpct(_) => (20u8, None),
NetworkMessage::CmpctBlock(_) => (4u8, None),
NetworkMessage::GetBlockTxn(_) => (10u8, None),
NetworkMessage::BlockTxn(_) => (3u8, None),
NetworkMessage::FeeFilter(_) => (5u8, None),
NetworkMessage::AddrV2(_) => (28u8, None),
NetworkMessage::Version(_)
| NetworkMessage::Verack
| NetworkMessage::SendHeaders
| NetworkMessage::GetAddr
| NetworkMessage::WtxidRelay
| NetworkMessage::SendAddrV2
| NetworkMessage::Alert(_)
| NetworkMessage::Reject(_)
| NetworkMessage::Unknown { .. } => (0u8, Some(self.payload.command())),
};
let mut len = command_byte.consensus_encode(writer)?;
if let Some(cmd) = full_command {
len += cmd.consensus_encode(writer)?;
}
// Encode the payload.
len += self.payload.consensus_encode(writer)?;
Ok(len)
}
}
struct HeaderDeserializationWrapper(Vec<block::Header>); struct HeaderDeserializationWrapper(Vec<block::Header>);
impl Decodable for HeaderDeserializationWrapper { impl Decodable for HeaderDeserializationWrapper {
@ -537,6 +620,79 @@ impl Decodable for RawNetworkMessage {
} }
} }
impl Decodable for V2NetworkMessage {
fn consensus_decode_from_finite_reader<R: BufRead + ?Sized>(
r: &mut R,
) -> Result<Self, encode::Error> {
let short_id: u8 = Decodable::consensus_decode_from_finite_reader(r)?;
let payload = match short_id {
0u8 => {
// Full command encoding.
let cmd = CommandString::consensus_decode_from_finite_reader(r)?;
match &cmd.0[..] {
"version" =>
NetworkMessage::Version(Decodable::consensus_decode_from_finite_reader(r)?),
"verack" => NetworkMessage::Verack,
"sendheaders" => NetworkMessage::SendHeaders,
"getaddr" => NetworkMessage::GetAddr,
"wtxidrelay" => NetworkMessage::WtxidRelay,
"sendaddrv2" => NetworkMessage::SendAddrV2,
"alert" =>
NetworkMessage::Alert(Decodable::consensus_decode_from_finite_reader(r)?),
"reject" =>
NetworkMessage::Reject(Decodable::consensus_decode_from_finite_reader(r)?),
_ => NetworkMessage::Unknown {
command: cmd,
payload: Vec::consensus_decode_from_finite_reader(r)?,
},
}
}
1u8 => NetworkMessage::Addr(Decodable::consensus_decode_from_finite_reader(r)?),
2u8 => NetworkMessage::Block(Decodable::consensus_decode_from_finite_reader(r)?),
3u8 => NetworkMessage::BlockTxn(Decodable::consensus_decode_from_finite_reader(r)?),
4u8 => NetworkMessage::CmpctBlock(Decodable::consensus_decode_from_finite_reader(r)?),
5u8 => NetworkMessage::FeeFilter(Decodable::consensus_decode_from_finite_reader(r)?),
6u8 => NetworkMessage::FilterAdd(Decodable::consensus_decode_from_finite_reader(r)?),
7u8 => NetworkMessage::FilterClear,
8u8 => NetworkMessage::FilterLoad(Decodable::consensus_decode_from_finite_reader(r)?),
9u8 => NetworkMessage::GetBlocks(Decodable::consensus_decode_from_finite_reader(r)?),
10u8 => NetworkMessage::GetBlockTxn(Decodable::consensus_decode_from_finite_reader(r)?),
11u8 => NetworkMessage::GetData(Decodable::consensus_decode_from_finite_reader(r)?),
12u8 => NetworkMessage::GetHeaders(Decodable::consensus_decode_from_finite_reader(r)?),
13u8 => NetworkMessage::Headers(
HeaderDeserializationWrapper::consensus_decode_from_finite_reader(r)?.0,
),
14u8 => NetworkMessage::Inv(Decodable::consensus_decode_from_finite_reader(r)?),
15u8 => NetworkMessage::MemPool,
16u8 => NetworkMessage::MerkleBlock(Decodable::consensus_decode_from_finite_reader(r)?),
17u8 => NetworkMessage::NotFound(Decodable::consensus_decode_from_finite_reader(r)?),
18u8 => NetworkMessage::Ping(Decodable::consensus_decode_from_finite_reader(r)?),
19u8 => NetworkMessage::Pong(Decodable::consensus_decode_from_finite_reader(r)?),
20u8 => NetworkMessage::SendCmpct(Decodable::consensus_decode_from_finite_reader(r)?),
21u8 => NetworkMessage::Tx(Decodable::consensus_decode_from_finite_reader(r)?),
22u8 => NetworkMessage::GetCFilters(Decodable::consensus_decode_from_finite_reader(r)?),
23u8 => NetworkMessage::CFilter(Decodable::consensus_decode_from_finite_reader(r)?),
24u8 =>
NetworkMessage::GetCFHeaders(Decodable::consensus_decode_from_finite_reader(r)?),
25u8 => NetworkMessage::CFHeaders(Decodable::consensus_decode_from_finite_reader(r)?),
26u8 =>
NetworkMessage::GetCFCheckpt(Decodable::consensus_decode_from_finite_reader(r)?),
27u8 => NetworkMessage::CFCheckpt(Decodable::consensus_decode_from_finite_reader(r)?),
28u8 => NetworkMessage::AddrV2(Decodable::consensus_decode_from_finite_reader(r)?),
_ =>
return Err(encode::Error::Parse(encode::ParseError::ParseFailed(
"Unknown short ID",
))),
};
Ok(V2NetworkMessage { payload })
}
#[inline]
fn consensus_decode<R: BufRead + ?Sized>(r: &mut R) -> Result<Self, encode::Error> {
Self::consensus_decode_from_finite_reader(&mut r.take(MAX_MSG_SIZE.to_u64()))
}
}
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use std::net::Ipv4Addr; use std::net::Ipv4Addr;
@ -669,9 +825,14 @@ mod test {
NetworkMessage::SendCmpct(SendCmpct { send_compact: true, version: 8333 }), NetworkMessage::SendCmpct(SendCmpct { send_compact: true, version: 8333 }),
]; ];
for msg in msgs { for msg in &msgs {
let raw_msg = RawNetworkMessage::new(Magic::from_bytes([57, 0, 0, 0]), msg); // V1 messages.
let raw_msg = RawNetworkMessage::new(Magic::from_bytes([57, 0, 0, 0]), msg.clone());
assert_eq!(deserialize::<RawNetworkMessage>(&serialize(&raw_msg)).unwrap(), raw_msg); assert_eq!(deserialize::<RawNetworkMessage>(&serialize(&raw_msg)).unwrap(), raw_msg);
// V2 messages.
let v2_msg = V2NetworkMessage::new(msg.clone());
assert_eq!(deserialize::<V2NetworkMessage>(&serialize(&v2_msg)).unwrap(), v2_msg);
} }
} }
@ -709,6 +870,17 @@ mod test {
0x00, 0x00, 0x00, 0x00, 0x5d, 0xf6, 0xe0, 0xe2]); 0x00, 0x00, 0x00, 0x00, 0x5d, 0xf6, 0xe0, 0xe2]);
} }
#[test]
fn serialize_v2_verack() {
assert_eq!(
serialize(&V2NetworkMessage::new(NetworkMessage::Verack)),
[
0x00, // Full command encoding flag.
0x76, 0x65, 0x72, 0x61, 0x63, 0x6B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
]
);
}
#[test] #[test]
#[rustfmt::skip] #[rustfmt::skip]
fn serialize_ping() { fn serialize_ping() {
@ -719,6 +891,17 @@ mod test {
0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]); 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]);
} }
#[test]
fn serialize_v2_ping() {
assert_eq!(
serialize(&V2NetworkMessage::new(NetworkMessage::Ping(100))),
[
0x12, // Ping command short ID
0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
]
);
}
#[test] #[test]
#[rustfmt::skip] #[rustfmt::skip]
fn serialize_mempool() { fn serialize_mempool() {
@ -728,6 +911,16 @@ mod test {
0x00, 0x00, 0x00, 0x00, 0x5d, 0xf6, 0xe0, 0xe2]); 0x00, 0x00, 0x00, 0x00, 0x5d, 0xf6, 0xe0, 0xe2]);
} }
#[test]
fn serialize_v2_mempool() {
assert_eq!(
serialize(&V2NetworkMessage::new(NetworkMessage::MemPool)),
[
0x0F, // MemPool command short ID
]
);
}
#[test] #[test]
#[rustfmt::skip] #[rustfmt::skip]
fn serialize_getaddr() { fn serialize_getaddr() {
@ -737,6 +930,17 @@ mod test {
0x00, 0x00, 0x00, 0x00, 0x5d, 0xf6, 0xe0, 0xe2]); 0x00, 0x00, 0x00, 0x00, 0x5d, 0xf6, 0xe0, 0xe2]);
} }
#[test]
fn serialize_v2_getaddr() {
assert_eq!(
serialize(&V2NetworkMessage::new(NetworkMessage::GetAddr)),
[
0x00, // Full command encoding flag.
0x67, 0x65, 0x74, 0x61, 0x64, 0x64, 0x72, 0x00, 0x00, 0x00, 0x00, 0x00,
]
);
}
#[test] #[test]
fn deserialize_getaddr() { fn deserialize_getaddr() {
#[rustfmt::skip] #[rustfmt::skip]
@ -752,6 +956,19 @@ mod test {
assert_eq!(preimage.payload, msg.payload); assert_eq!(preimage.payload, msg.payload);
} }
#[test]
fn deserialize_v2_getaddr() {
let msg = deserialize(&[
0x00, // Full command encoding flag
0x67, 0x65, 0x74, 0x61, 0x64, 0x64, 0x72, 0x00, 0x00, 0x00, 0x00, 0x00,
]);
let preimage = V2NetworkMessage::new(NetworkMessage::GetAddr);
assert!(msg.is_ok());
let msg: V2NetworkMessage = msg.unwrap();
assert_eq!(preimage, msg);
}
#[test] #[test]
fn deserialize_version() { fn deserialize_version() {
#[rustfmt::skip] #[rustfmt::skip]
@ -796,6 +1013,44 @@ mod test {
} }
} }
#[test]
fn deserialize_v2_version() {
#[rustfmt::skip]
let msg = deserialize::<V2NetworkMessage>(&[
0x00, // Full command encoding flag
0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x00, 0x00, 0x00, 0x00, 0x00, // "version" command
0x7f, 0x11, 0x01, 0x00, // version: 70015
0x0d, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // services
0xf0, 0x0f, 0x4d, 0x5c, 0x00, 0x00, 0x00, 0x00, // timestamp: 1548554224
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // receiver services: NONE
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x5b, 0xf0, 0x8c, 0x80, 0xb4, 0xbd, // addr_recv
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // sender services: NONE
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // addr_from
0xfa, 0xa9, 0x95, 0x59, 0xcc, 0x68, 0xa1, 0xc1, // nonce
0x10, 0x2f, 0x53, 0x61, 0x74, 0x6f, 0x73, 0x68, 0x69, 0x3a, 0x30, 0x2e, 0x31, 0x37, 0x2e, 0x31, 0x2f, // user_agent: "/Satoshi:0.17.1/"
0x93, 0x8c, 0x08, 0x00, // start_height: 560275
0x01 // relay: true
]).unwrap();
if let NetworkMessage::Version(version_msg) = msg.payload {
assert_eq!(version_msg.version, 70015);
assert_eq!(
version_msg.services,
ServiceFlags::NETWORK
| ServiceFlags::BLOOM
| ServiceFlags::WITNESS
| ServiceFlags::NETWORK_LIMITED
);
assert_eq!(version_msg.timestamp, 1548554224);
assert_eq!(version_msg.nonce, 13952548347456104954);
assert_eq!(version_msg.user_agent, "/Satoshi:0.17.1/");
assert_eq!(version_msg.start_height, 560275);
assert!(version_msg.relay);
} else {
panic!("wrong message type");
}
}
#[test] #[test]
fn deserialize_partial_message() { fn deserialize_partial_message() {
#[rustfmt::skip] #[rustfmt::skip]