p2p: Implement `encoding` traits for `GetBlocks/HeadersMessage`
What changed, and why it matters
This commit adds new ways to encode and decode two Bitcoin peer-to-peer network messages, `GetBlocks` and `GetHeaders`. It does not change the existing encoding logic; it only introduces an alternative implementation using newer internal traits. There is no indication this fixes a security bug or introduces a vulnerability.
No security action required. Review as normal code-quality change.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The patch implements the newer encoding::Encodable/Decodable traits for GetBlocksMessage and GetHeadersMessage in p2p/src/message_blockdata.rs. It adds new encoder/decoder newtypes and error types, while keeping the existing impl_consensus_encoding! macro implementations in place. The new code mirrors the old structure: a protocol version, a compact-size-prefixed vector of block hashes, and a stop hash. No bounds checks, resource limits, or parsing semantics appear to change.
Changed components
p2p/src/message_blockdata.rsGetBlocksMessageGetHeadersMessageInspect captured patch +161 / −2
diff --git a/p2p/src/message_blockdata.rs b/p2p/src/message_blockdata.rs
index 113dba15..9659227e 100644
--- a/p2p/src/message_blockdata.rs
+++ b/p2p/src/message_blockdata.rs
@@ -12,14 +12,15 @@ use core::fmt;
#[cfg(feature = "arbitrary")]
use arbitrary::{Arbitrary, Unstructured};
use bitcoin::consensus::encode::{self, Decodable, Encodable};
-use encoding::{ArrayDecoder, ArrayEncoder, Decoder2, Encoder2};
+use encoding::{ArrayDecoder, ArrayEncoder, CompactSizeEncoder, Decoder2, Decoder3, Encoder2, Encoder3, SliceEncoder, VecDecoder};
use internals::write_err;
use io::{BufRead, Write};
+use primitives::block::{BlockHashDecoder, BlockHashEncoder};
use primitives::transaction::{Txid, Wtxid};
use primitives::BlockHash;
use crate::consensus::impl_consensus_encoding;
-use crate::ProtocolVersion;
+use crate::{ProtocolVersion, ProtocolVersionDecoder, ProtocolVersionEncoder};
/// An inventory item.
#[derive(PartialEq, Eq, Clone, Debug, Copy, Hash, PartialOrd, Ord)]
@@ -219,6 +220,164 @@ pub struct GetHeadersMessage {
pub stop_hash: BlockHash,
}
+type GetBlocksOrHeadersInnerEncoder<'e> = Encoder3<ProtocolVersionEncoder, Encoder2<CompactSizeEncoder, SliceEncoder<'e, BlockHash>>, BlockHashEncoder>;
+
+encoding::encoder_newtype! {
+ /// The encoder for [`GetBlocksMessage`].
+ pub struct GetBlocksEncoder<'e>(GetBlocksOrHeadersInnerEncoder<'e>);
+}
+
+encoding::encoder_newtype! {
+ /// The encoder for [`GetHeadersMessage`].
+ pub struct GetHeadersEncoder<'e>(GetBlocksOrHeadersInnerEncoder<'e>);
+}
+
+impl encoding::Encodable for GetHeadersMessage {
+ type Encoder<'e> = GetHeadersEncoder<'e>
+ where
+ Self: 'e;
+
+ fn encoder(&self) -> Self::Encoder<'_> {
+ GetHeadersEncoder(
+ Encoder3::new(
+ self.version.encoder(),
+ Encoder2::new(
+ CompactSizeEncoder::new(self.locator_hashes.len()),
+ SliceEncoder::without_length_prefix(&self.locator_hashes),
+ ),
+ self.stop_hash.encoder(),
+ ),
+ )
+ }
+}
+
+impl encoding::Encodable for GetBlocksMessage {
+ type Encoder<'e> = GetBlocksEncoder<'e>
+ where
+ Self: 'e;
+
+ fn encoder(&self) -> Self::Encoder<'_> {
+ GetBlocksEncoder(
+ Encoder3::new(
+ self.version.encoder(),
+ Encoder2::new(
+ CompactSizeEncoder::new(self.locator_hashes.len()),
+ SliceEncoder::without_length_prefix(&self.locator_hashes),
+ ),
+ self.stop_hash.encoder(),
+ ),
+ )
+ }
+}
+
+type GetBlocksOrHeadersInnerDecoder = Decoder3<ProtocolVersionDecoder, VecDecoder<BlockHash>, BlockHashDecoder>;
+
+/// Decoder type for [`GetBlocksMessage`].
+pub struct GetBlocksMessageDecoder(GetBlocksOrHeadersInnerDecoder);
+
+/// Decoder type for [`GetHeadersMessage`].
+pub struct GetHeadersMessageDecoder(GetBlocksOrHeadersInnerDecoder);
+
+impl encoding::Decoder for GetHeadersMessageDecoder {
+ type Output = GetHeadersMessage;
+ type Error = GetHeadersMessageDecoderError;
+
+ #[inline]
+ fn push_bytes(&mut self, bytes: &mut &[u8]) -> Result<bool, Self::Error> {
+ self.0.push_bytes(bytes).map_err(GetHeadersMessageDecoderError)
+ }
+
+ #[inline]
+ fn end(self) -> Result<Self::Output, Self::Error> {
+ let (version, locator_hashes, stop_hash) = self.0.end().map_err(GetHeadersMessageDecoderError)?;
+ Ok(GetHeadersMessage { version, locator_hashes, stop_hash })
+ }
+
+ #[inline]
+ fn read_limit(&self) -> usize { self.0.read_limit() }
+}
+
+impl encoding::Decoder for GetBlocksMessageDecoder {
+ type Output = GetBlocksMessage;
+ type Error = GetBlocksMessageDecoderError;
+
+ #[inline]
+ fn push_bytes(&mut self, bytes: &mut &[u8]) -> Result<bool, Self::Error> {
+ self.0.push_bytes(bytes).map_err(GetBlocksMessageDecoderError)
+ }
+
+ #[inline]
+ fn end(self) -> Result<Self::Output, Self::Error> {
+ let (version, locator_hashes, stop_hash) = self.0.end().map_err(GetBlocksMessageDecoderError)?;
+ Ok(GetBlocksMessage { version, locator_hashes, stop_hash })
+ }
+
+ #[inline]
+ fn read_limit(&self) -> usize { self.0.read_limit() }
+}
+
+impl encoding::Decodable for GetBlocksMessage {
+ type Decoder = GetBlocksMessageDecoder;
+ fn decoder() -> Self::Decoder {
+ GetBlocksMessageDecoder(Decoder3::new(
+ ProtocolVersionDecoder::new(),
+ VecDecoder::<BlockHash>::new(),
+ BlockHashDecoder::new(),
+ )
+ )
+ }
+}
+
+impl encoding::Decodable for GetHeadersMessage {
+ type Decoder = GetHeadersMessageDecoder;
+ fn decoder() -> Self::Decoder {
+ GetHeadersMessageDecoder(Decoder3::new(
+ ProtocolVersionDecoder::new(),
+ VecDecoder::<BlockHash>::new(),
+ BlockHashDecoder::new(),
+ )
+ )
+ }
+}
+
+/// An error consensus decoding a [`GetBlocksMessage`].
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct GetBlocksMessageDecoderError(<GetBlocksOrHeadersInnerDecoder as encoding::Decoder>::Error);
+
+impl From<Infallible> for GetBlocksMessageDecoderError {
+ fn from(never: Infallible) -> Self { match never {} }
+}
+
+impl fmt::Display for GetBlocksMessageDecoderError {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write_err!(f, "getblocks decoder error"; self.0)
+ }
+}
+
+#[cfg(feature = "std")]
+impl std::error::Error for GetBlocksMessageDecoderError {
+ fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { Some(&self.0) }
+}
+
+/// An error consensus decoding a [`GetHeadersMessage`].
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct GetHeadersMessageDecoderError(<GetBlocksOrHeadersInnerDecoder as encoding::Decoder>::Error);
+
+impl From<Infallible> for GetHeadersMessageDecoderError {
+ fn from(never: Infallible) -> Self { match never {} }
+}
+
+impl fmt::Display for GetHeadersMessageDecoderError {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write_err!(f, "getheaders decoder error"; self.0)
+ }
+}
+
+#[cfg(feature = "std")]
+impl std::error::Error for GetHeadersMessageDecoderError {
+ fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { Some(&self.0) }
+}
+
impl_consensus_encoding!(GetBlocksMessage, version, locator_hashes, stop_hash);
impl_consensus_encoding!(GetHeadersMessage, version, locator_hashes, stop_hash);
Why this scored 12/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.