From 25d7fd2d13cd6e4ee353d5c3afbef6d263b31679 Mon Sep 17 00:00:00 2001 From: pasta Date: Wed, 14 Oct 2026 02:23:25 +0000 Subject: [PATCH 1/2] p2p_core%fix: accept up to 8000 headers in `headers2`, export the caps `headers2` replies carry up to 8000 headers. A count the remaining input cannot hold is reported as `Eof` before anything is allocated. --- pkgs/p2p_core/src/msg/headers.rs | 41 +++++++++++++++++++--- pkgs/p2p_core/src/msg/headers2.rs | 56 +++++++++++++++++++++++++++++-- 2 files changed, 90 insertions(+), 7 deletions(-) diff --git a/pkgs/p2p_core/src/msg/headers.rs b/pkgs/p2p_core/src/msg/headers.rs index 92d6183d3..e40eb4a40 100644 --- a/pkgs/p2p_core/src/msg/headers.rs +++ b/pkgs/p2p_core/src/msg/headers.rs @@ -11,12 +11,21 @@ use crate::prelude::*; use crate::version::ProtocolVersion; use dash_primitives::{hash_impl, BlockHash, BlockHeader, MerkleRoot}; -use dash_types::codec::{BaseCodec, DecodeError, EncodeBuf}; +use dash_types::codec::{self, BaseCodec, DecodeError, EncodeBuf}; use dash_types::type_id::TypeId; use dash_types::CompactSize; -/// Maximum headers per message. -const MAX_HEADERS: usize = 2_000; +/// The fewest bytes a `headers` entry takes: a header and its tx count. +const MIN_HEADERS_ENTRY: usize = 81; + +/// Reads an entry count of at most `limit`, reporting one the remaining +/// input cannot hold as [`DecodeError::Eof`] before anything is allocated +/// for it. +pub(super) fn decode_count(data: &mut &[u8], limit: usize, min_entry: usize) -> Result { + let count = CompactSize::decode(data)?.into_len(limit)?; + codec::ensure(data, count * min_entry)?; + Ok(count) +} /// Requests block headers starting from a locator. #[derive(Clone, Debug, Eq, Hash, PartialEq, TypeId)] @@ -51,7 +60,7 @@ impl_p2p!(Headers); impl BaseCodec for Headers { fn decode(data: &mut &[u8]) -> Result { - let count = CompactSize::decode(data)?.into_len(MAX_HEADERS)?; + let count = decode_count(data, Self::MAX_COUNT, MIN_HEADERS_ENTRY)?; let mut headers = Vec::with_capacity(count); for _ in 0..count { headers.push(BlockHeader { @@ -83,3 +92,27 @@ impl BaseCodec for Headers { } hash_impl!(Headers); + +impl Headers { + /// The most headers a `headers` message carries. + pub const MAX_COUNT: usize = 2_000; +} + +#[cfg(test)] +mod tests { + use super::*; + + use rstest::rstest; + + #[rstest] + fn counts_past_the_input_fail_before_any_header() { + let payload = [&[2][..], &[0; MIN_HEADERS_ENTRY]].concat(); + assert_eq!( + Headers::decode(&mut &payload[..]), + Err(DecodeError::Eof { + needed: 2 * MIN_HEADERS_ENTRY, + remaining: MIN_HEADERS_ENTRY, + }) + ); + } +} diff --git a/pkgs/p2p_core/src/msg/headers2.rs b/pkgs/p2p_core/src/msg/headers2.rs index c519821da..f7c6f35d5 100644 --- a/pkgs/p2p_core/src/msg/headers2.rs +++ b/pkgs/p2p_core/src/msg/headers2.rs @@ -6,6 +6,7 @@ //! Compressed header messages. +use super::headers::decode_count; use crate::codec::{codec_p2p, impl_p2p}; use crate::prelude::*; use crate::version::ProtocolVersion; @@ -15,8 +16,9 @@ use dash_types::codec::{BaseCodec, DecodeError, EncodeBuf}; use dash_types::type_id::{TypeId, Unencodable}; use dash_types::{CompactSize, Hashable}; -/// Maximum headers per message. -const MAX_HEADERS: usize = 2_000; +/// The fewest bytes a compressed header takes: flags, merkle root, a time +/// delta and the nonce. +const MIN_HEADERS2_ENTRY: usize = 39; /// Maximum entries in the MRU version cache. const MAX_VERSION_CACHE: usize = 7; @@ -255,7 +257,7 @@ impl_p2p!(Headers2); impl BaseCodec for Headers2 { fn decode(data: &mut &[u8]) -> Result { - let count = CompactSize::decode(data)?.into_len(MAX_HEADERS)?; + let count = decode_count(data, Self::MAX_COUNT, MIN_HEADERS2_ENTRY)?; let mut state = CompressionState::new(); let mut headers = Vec::with_capacity(count); for _ in 0..count { @@ -275,11 +277,17 @@ impl BaseCodec for Headers2 { hash_impl!(Headers2); +impl Headers2 { + /// The most headers a `headers2` message carries. + pub const MAX_COUNT: usize = 8_000; +} + #[cfg(all(test, feature = "serde"))] mod tests { use super::*; use dash_dev::{assert_serde_rt, check_wire, Corpus}; + use dash_types::Numeric; use rstest::rstest; #[rstest] @@ -295,4 +303,46 @@ mod tests { let items = corpus.entries::("headers2", check_wire); assert_serde_rt("headers2", &items); } + + #[rstest] + fn counts_past_2000_decode() { + let headers = (0..2_001) + .map(|i| BlockHeader { + version: 0x2000_0000, + prev_hash: BlockHash::from_lendian([0; 32]), + merkle_root: MerkleRoot::from_lendian([1; 32]), + time: 1_700_000_000 + i, + bits: 0x1e0f_ffff, + nonce: i, + }) + .collect(); + let msg = Headers2 { headers }; + let mut buf = Vec::new(); + msg.encode(&mut buf); + assert_eq!(Headers2::decode(&mut &buf[..]), Ok(msg)); + } + + #[rstest] + fn counts_past_the_limit_fail_before_any_header() { + let payload = [0xfd, 0x41, 0x1f]; + assert_eq!( + Headers2::decode(&mut &payload[..]), + Err(DecodeError::CompactSizeExceedsLimit { + limit: Headers2::MAX_COUNT, + value: 8_001, + }) + ); + } + + #[rstest] + fn counts_past_the_input_fail_before_any_header() { + let payload = [&[2][..], &[0; MIN_HEADERS2_ENTRY]].concat(); + assert_eq!( + Headers2::decode(&mut &payload[..]), + Err(DecodeError::Eof { + needed: 2 * MIN_HEADERS2_ENTRY, + remaining: MIN_HEADERS2_ENTRY, + }) + ); + } } From 77886998766efec3911ffcf95fab371fb18a436a Mon Sep 17 00:00:00 2001 From: pasta Date: Wed, 14 Oct 2026 02:23:47 +0000 Subject: [PATCH 2/2] p2p_core%feat: add `Headers2::max_count` for the negotiated limit `headers2` carries up to 8000 headers only from protocol version 70235, `ProtocolVersion::INCREASE_MAX_HEADERS2`; below it, 2000. --- pkgs/p2p_core/src/msg/headers2.rs | 12 +++++++++++- pkgs/p2p_core/src/version.rs | 2 ++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/pkgs/p2p_core/src/msg/headers2.rs b/pkgs/p2p_core/src/msg/headers2.rs index f7c6f35d5..5bf0ce531 100644 --- a/pkgs/p2p_core/src/msg/headers2.rs +++ b/pkgs/p2p_core/src/msg/headers2.rs @@ -6,7 +6,7 @@ //! Compressed header messages. -use super::headers::decode_count; +use super::headers::{decode_count, Headers}; use crate::codec::{codec_p2p, impl_p2p}; use crate::prelude::*; use crate::version::ProtocolVersion; @@ -280,6 +280,16 @@ hash_impl!(Headers2); impl Headers2 { /// The most headers a `headers2` message carries. pub const MAX_COUNT: usize = 8_000; + + /// The most headers a peer may send in one `headers2` message, given the + /// protocol version both peers share. + pub fn max_count(common: ProtocolVersion) -> usize { + if common >= ProtocolVersion::INCREASE_MAX_HEADERS2 { + Self::MAX_COUNT + } else { + Headers::MAX_COUNT + } + } } #[cfg(all(test, feature = "serde"))] diff --git a/pkgs/p2p_core/src/version.rs b/pkgs/p2p_core/src/version.rs index 3d07d65e9..c32cc9634 100644 --- a/pkgs/p2p_core/src/version.rs +++ b/pkgs/p2p_core/src/version.rs @@ -33,4 +33,6 @@ impl ProtocolVersion { pub const MNLISTDIFF_VERSION_ORDER: Self = Self(70229); /// Chainlock signatures in MN list diff boundary. pub const MNLISTDIFF_CHAINLOCKS: Self = Self(70230); + /// `headers2` limit raised to 8000 boundary. + pub const INCREASE_MAX_HEADERS2: Self = Self(70235); }