p2p: add version message encoder/decoder
What changed, and why it matters
This commit adds a new way to encode and decode Bitcoin peer-to-peer 'version' messages in the rust-bitcoin library. It is a routine refactoring and feature addition: it introduces a new encoder/decoder implementation and updates one test to use the new code. There is no indication of a security bug being fixed.
No security action required. Review as normal code-quality/functional change.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The change implements push-based Encodable/Decodable traits for VersionMessage by composing existing encoders (Encoder3 + Encoder6 via Encoder2, and matching decoders). It also updates the existing unit test to use encoding::decode_from_slice and encoding::encode_to_vec instead of the older consensus serialize/deserialize functions. The diff is purely additive and test-updating; no bounds-check, validation, or parsing logic changes are visible.
Changed components
p2p/src/message_network.rsVersionMessage encoder/decoderVersionMessage unit testInspect captured patch +144 / −3
diff --git a/p2p/src/message_network.rs b/p2p/src/message_network.rs
index 7df457d3..f4f7c3c8 100644
--- a/p2p/src/message_network.rs
+++ b/p2p/src/message_network.rs
@@ -23,7 +23,7 @@ use hashes::sha256d;
use internals::write_err;
use io::{BufRead, Write};
-use crate::address::Address;
+use crate::address::{Address, AddressDecoder};
use crate::consensus::{impl_consensus_encoding, impl_vec_wrapper};
use crate::{ProtocolVersion, ServiceFlags};
@@ -88,6 +88,147 @@ impl VersionMessage {
}
}
+encoding::encoder_newtype! {
+ /// The encoder for the [`VersionMessage`] type.
+ pub struct VersionMessageEncoder<'e>(
+ encoding::Encoder2<
+ encoding::Encoder3<
+ crate::ProtocolVersionEncoder<'e>,
+ crate::ServiceFlagsEncoder<'e>,
+ encoding::ArrayEncoder<8>
+ >,
+ encoding::Encoder6<
+ crate::address::AddressEncoder<'e>,
+ crate::address::AddressEncoder<'e>,
+ encoding::ArrayEncoder<8>,
+ UserAgentEncoder<'e>,
+ encoding::ArrayEncoder<4>,
+ encoding::ArrayEncoder<1>
+ >
+ >
+ );
+}
+
+impl encoding::Encodable for VersionMessage {
+ type Encoder<'e>
+ = VersionMessageEncoder<'e>
+ where
+ Self: 'e;
+
+ #[inline]
+ fn encoder(&self) -> Self::Encoder<'_> {
+ VersionMessageEncoder::new(encoding::Encoder2::new(
+ encoding::Encoder3::new(
+ self.version.encoder(),
+ self.services.encoder(),
+ encoding::ArrayEncoder::without_length_prefix(self.timestamp.to_le_bytes()),
+ ),
+ encoding::Encoder6::new(
+ self.receiver.encoder(),
+ self.sender.encoder(),
+ encoding::ArrayEncoder::without_length_prefix(self.nonce.to_le_bytes()),
+ self.user_agent.encoder(),
+ encoding::ArrayEncoder::without_length_prefix(self.start_height.to_le_bytes()),
+ encoding::ArrayEncoder::without_length_prefix([u8::from(self.relay)]),
+ ),
+ ))
+ }
+}
+
+impl encoding::Decodable for VersionMessage {
+ type Decoder = VersionMessageDecoder;
+
+ #[inline]
+ fn decoder() -> Self::Decoder {
+ VersionMessageDecoder(encoding::Decoder2::new(
+ encoding::Decoder3::new(
+ crate::ProtocolVersion::decoder(),
+ crate::ServiceFlags::decoder(),
+ encoding::ArrayDecoder::<8>::new(),
+ ),
+ encoding::Decoder6::new(
+ crate::address::Address::decoder(),
+ crate::address::Address::decoder(),
+ encoding::ArrayDecoder::<8>::new(),
+ UserAgent::decoder(),
+ encoding::ArrayDecoder::<4>::new(),
+ encoding::ArrayDecoder::<1>::new(),
+ ),
+ ))
+ }
+}
+
+impl encoding::Decoder for VersionMessageDecoder {
+ type Output = VersionMessage;
+ type Error = VersionMessageDecoderError;
+
+ #[inline]
+ fn push_bytes(&mut self, bytes: &mut &[u8]) -> Result<bool, Self::Error> {
+ self.0.push_bytes(bytes).map_err(VersionMessageDecoderError)
+ }
+
+ #[inline]
+ fn end(self) -> Result<Self::Output, Self::Error> {
+ let (
+ (version, services, timestamp),
+ (receiver, sender, nonce, user_agent, start_height, relay),
+ ) = self.0.end().map_err(VersionMessageDecoderError)?;
+
+ Ok(VersionMessage {
+ version,
+ services,
+ timestamp: i64::from_le_bytes(timestamp),
+ receiver,
+ sender,
+ nonce: u64::from_le_bytes(nonce),
+ user_agent,
+ start_height: i32::from_le_bytes(start_height),
+ relay: relay[0] != 0,
+ })
+ }
+
+ #[inline]
+ fn read_limit(&self) -> usize { self.0.read_limit() }
+}
+
+type VersionMessageInnerDecoder = encoding::Decoder2<
+ encoding::Decoder3<
+ crate::ProtocolVersionDecoder,
+ crate::ServiceFlagsDecoder,
+ encoding::ArrayDecoder<8>,
+ >,
+ encoding::Decoder6<
+ AddressDecoder,
+ AddressDecoder,
+ encoding::ArrayDecoder<8>,
+ UserAgentDecoder,
+ encoding::ArrayDecoder<4>,
+ encoding::ArrayDecoder<1>,
+ >,
+>;
+
+/// The Decoder for [`VersionMessage`].
+pub struct VersionMessageDecoder(VersionMessageInnerDecoder);
+
+/// An error consensus decoding a [`VersionMessage`].
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct VersionMessageDecoderError(<VersionMessageInnerDecoder as encoding::Decoder>::Error);
+
+impl From<Infallible> for VersionMessageDecoderError {
+ fn from(never: Infallible) -> Self { match never {} }
+}
+
+impl fmt::Display for VersionMessageDecoderError {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write_err!(f, "version message decoder error"; self.0)
+ }
+}
+
+#[cfg(feature = "std")]
+impl std::error::Error for VersionMessageDecoderError {
+ fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { Some(&self.0) }
+}
+
impl_consensus_encoding!(
VersionMessage,
version,
@@ -769,7 +910,7 @@ mod tests {
// This message is from my satoshi node, morning of May 27 2014
let from_sat = hex!("721101000100000000000000e6e0845300000000010000000000000000000000000000000000ffff0000000000000100000000000000fd87d87eeb4364f22cf54dca59412db7208d47d920cffce83ee8102f5361746f7368693a302e392e39392f2c9f040001");
- let decode: Result<VersionMessage, _> = deserialize(&from_sat);
+ let decode: Result<VersionMessage, _> = encoding::decode_from_slice(&from_sat);
assert!(decode.is_ok());
let real_decode = decode.unwrap();
assert_eq!(real_decode.version.0, 70002);
@@ -791,7 +932,7 @@ mod tests {
assert_eq!(real_decode.start_height, 302_892);
assert!(real_decode.relay);
- assert_eq!(serialize(&real_decode), from_sat);
+ assert_eq!(encoding::encode_to_vec(&real_decode), from_sat);
}
#[test]
Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.