Skip to main content

blvm_protocol/
network.rs

1//! Bitcoin P2P Network Protocol (Orange Paper Section 10)
2//!
3//! This module provides Bitcoin P2P protocol message types and processing.
4//! Protocol-specific limits and validation are handled here, with consensus
5//! validation delegated to the consensus layer.
6
7use crate::bip152::ShortTxId;
8use crate::error::ProtocolError;
9use crate::validation::ProtocolValidationContext;
10use crate::{BitcoinProtocolEngine, ProtocolConfig, Result};
11use blvm_consensus::error::ConsensusError;
12use blvm_consensus::types::UtxoSet;
13use blvm_consensus::types::{Block, BlockHeader, Hash, Transaction, ValidationResult};
14use std::sync::Arc;
15use thiserror::Error;
16
17// Commons module is always available (ban list sharing doesn't require utxo-commitments)
18pub mod commons {
19    pub use crate::commons::*;
20}
21
22// BIP324: v2 encrypted transport
23#[cfg(all(
24    feature = "bip324",
25    any(target_arch = "x86_64", target_arch = "aarch64")
26))]
27pub mod v2_transport {
28    pub use crate::v2_transport::*;
29}
30
31#[cfg(test)]
32mod bip155_tests;
33
34/// NetworkMessage: Bitcoin P2P protocol message types
35///
36/// Network message types for Bitcoin P2P protocol
37#[derive(Debug, Clone, PartialEq)]
38pub enum NetworkMessage {
39    Version(VersionMessage),
40    VerAck,
41    Addr(AddrMessage),
42    AddrV2(AddrV2Message), // BIP155: Extended address format
43    Inv(InvMessage),
44    GetData(GetDataMessage),
45    GetHeaders(GetHeadersMessage),
46    Headers(HeadersMessage),
47    /// Full block message.  Witnesses are required for correct SegWit/Taproot validation and
48    /// wire round-tripping.  The second field is the per-tx, per-input witness stack.
49    Block(Arc<Block>, Vec<Vec<blvm_consensus::segwit::Witness>>),
50    Tx(Arc<Transaction>),
51    Ping(PingMessage),
52    Pong(PongMessage),
53    MemPool,
54    FeeFilter(FeeFilterMessage),
55    // Additional core P2P messages
56    GetBlocks(GetBlocksMessage),
57    GetAddr,
58    NotFound(NotFoundMessage),
59    Reject(RejectMessage),
60    SendHeaders,
61    // BIP152 Compact Block Relay
62    SendCmpct(SendCmpctMessage),
63    CmpctBlock(CmpctBlockMessage),
64    GetBlockTxn(GetBlockTxnMessage),
65    BlockTxn(BlockTxnMessage),
66    // Commons-specific protocol extensions
67    #[cfg(feature = "utxo-commitments")]
68    GetUTXOSet(commons::GetUTXOSetMessage),
69    #[cfg(feature = "utxo-commitments")]
70    UTXOSet(commons::UTXOSetMessage),
71    #[cfg(feature = "utxo-commitments")]
72    GetFilteredBlock(commons::GetFilteredBlockMessage),
73    #[cfg(feature = "utxo-commitments")]
74    FilteredBlock(commons::FilteredBlockMessage),
75    GetBanList(commons::GetBanListMessage),
76    BanList(commons::BanListMessage),
77}
78
79/// Version message for initial handshake
80#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
81pub struct VersionMessage {
82    pub version: u32,
83    pub services: u64,
84    pub timestamp: i64,
85    pub addr_recv: NetworkAddress,
86    pub addr_from: NetworkAddress,
87    pub nonce: u64,
88    pub user_agent: String,
89    pub start_height: i32,
90    pub relay: bool,
91}
92
93/// Block P2P message: consensus block paired with its segwit witness stack.
94///
95/// The consensus [`Block`] carries transactions without witness data; witnesses are kept
96/// alongside so the node can round-trip the full segwit wire format without altering the
97/// consensus type.
98#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
99pub struct BlockMessage {
100    pub block: Block,
101    /// One `Vec<Witness>` per transaction, one `Witness` per input.
102    #[serde(skip_serializing_if = "Vec::is_empty", default)]
103    pub witnesses: Vec<Vec<blvm_consensus::segwit::Witness>>,
104}
105
106/// Transaction P2P message (thin wrapper for dispatch).
107#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
108pub struct TxMessage {
109    pub transaction: Transaction,
110}
111
112/// Compact block P2P message: decoded BIP152 compact block.
113///
114/// Holds the [`crate::bip152::CompactBlock`] abstraction used for efficient block relay.
115/// The node converts between this and the wire [`CmpctBlockMessage`] on ingress/egress.
116#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
117pub struct CompactBlockMessage {
118    pub compact_block: crate::bip152::CompactBlock,
119}
120
121/// Address message containing peer addresses
122#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
123pub struct AddrMessage {
124    pub addresses: Vec<NetworkAddress>,
125}
126
127/// Inventory message listing available objects
128#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
129pub struct InvMessage {
130    pub inventory: Vec<InventoryVector>,
131}
132
133/// GetData message requesting specific objects
134#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
135pub struct GetDataMessage {
136    pub inventory: Vec<InventoryVector>,
137}
138
139/// GetHeaders message requesting block headers
140#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
141pub struct GetHeadersMessage {
142    pub version: u32,
143    pub block_locator_hashes: Vec<Hash>,
144    pub hash_stop: Hash,
145}
146
147/// Headers message containing block headers
148#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
149pub struct HeadersMessage {
150    pub headers: Vec<BlockHeader>,
151}
152
153/// Ping message for connection keepalive
154#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
155pub struct PingMessage {
156    pub nonce: u64,
157}
158
159/// Pong message responding to ping
160#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
161pub struct PongMessage {
162    pub nonce: u64,
163}
164
165/// FeeFilter message setting minimum fee rate
166#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
167pub struct FeeFilterMessage {
168    pub feerate: u64,
169}
170
171/// GetBlocks message requesting blocks by locator
172#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
173pub struct GetBlocksMessage {
174    pub version: u32,
175    pub block_locator_hashes: Vec<Hash>,
176    pub hash_stop: Hash,
177}
178
179/// NotFound message indicating requested object not found
180#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
181pub struct NotFoundMessage {
182    pub inventory: Vec<InventoryVector>,
183}
184
185/// Reject message rejecting a message with reason
186#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
187pub struct RejectMessage {
188    pub message: String,          // Command name of rejected message
189    pub code: u8, // Rejection code (0x01=malformed, 0x10=invalid, 0x11=obsolete, 0x12=duplicate, 0x40=nonstandard, 0x41=dust, 0x42=insufficientfee, 0x43=checkpoint)
190    pub reason: String, // Human-readable reason
191    pub extra_data: Option<Hash>, // Optional hash for rejected object
192}
193
194/// SendCmpct message - Negotiate compact block relay support (BIP152)
195#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
196pub struct SendCmpctMessage {
197    /// Compact block version (1 or 2)
198    pub version: u64,
199    /// Whether to prefer compact blocks (1) or regular blocks (0)
200    pub prefer_cmpct: u8,
201}
202
203/// CmpctBlock message - Compact block data (BIP152)
204#[derive(Debug, Clone, PartialEq, Eq)]
205pub struct CmpctBlockMessage {
206    /// Block header
207    pub header: BlockHeader,
208    /// Nonce for short transaction ID calculation (SipHash key derivation)
209    pub nonce: u64,
210    /// Short transaction IDs (6 bytes each)
211    pub short_ids: Vec<ShortTxId>,
212    /// Prefilled transactions (transactions that are likely missing)
213    pub prefilled_txs: Vec<PrefilledTransaction>,
214}
215
216/// PrefilledTransaction - Transaction included in compact block
217#[derive(Debug, Clone, PartialEq, Eq)]
218pub struct PrefilledTransaction {
219    /// Index in block (0 = coinbase)
220    pub index: u16,
221    /// Transaction data
222    pub tx: Transaction,
223    /// Witness data (one stack per input). If Some and non-empty, serializes with TX_WITH_WITNESS per BIP152/Core.
224    pub witness: Option<Vec<blvm_consensus::segwit::Witness>>,
225}
226
227/// Error converting between [`crate::bip152::CompactBlock`] and wire [`CmpctBlockMessage`].
228#[derive(Debug, Clone, PartialEq, Eq, Error)]
229pub enum CompactBlockWireConvertError {
230    #[error("prefilled transaction index {0} does not fit in BIP152 u16 index")]
231    PrefilledIndexTooLarge(usize),
232    #[error("duplicate prefilled transaction index {0}")]
233    DuplicatePrefilledIndex(usize),
234}
235
236impl TryFrom<crate::bip152::CompactBlock> for CmpctBlockMessage {
237    type Error = CompactBlockWireConvertError;
238
239    /// Builds a cmpctblock-shaped message from the integration [`crate::bip152::CompactBlock`].
240    ///
241    /// Prefilled entries are **sorted by transaction index** (required for BIP152 diff-encoding on the wire).
242    /// Witness stacks are not represented on [`crate::bip152::CompactBlock`]; prefilled txs use `witness: None`.
243    fn try_from(mut value: crate::bip152::CompactBlock) -> std::result::Result<Self, Self::Error> {
244        value.prefilled_txs.sort_by_key(|(i, _)| *i);
245        let mut prefilled_txs = Vec::with_capacity(value.prefilled_txs.len());
246        let mut prev_idx: Option<usize> = None;
247        for (idx, tx) in value.prefilled_txs {
248            if prev_idx == Some(idx) {
249                return Err(CompactBlockWireConvertError::DuplicatePrefilledIndex(idx));
250            }
251            prev_idx = Some(idx);
252            let index = u16::try_from(idx)
253                .map_err(|_| CompactBlockWireConvertError::PrefilledIndexTooLarge(idx))?;
254            prefilled_txs.push(PrefilledTransaction {
255                index,
256                tx,
257                witness: None,
258            });
259        }
260        Ok(CmpctBlockMessage {
261            header: value.header,
262            nonce: value.nonce,
263            short_ids: value.short_ids,
264            prefilled_txs,
265        })
266    }
267}
268
269impl From<&CmpctBlockMessage> for crate::bip152::CompactBlock {
270    fn from(msg: &CmpctBlockMessage) -> Self {
271        Self {
272            header: msg.header.clone(),
273            nonce: msg.nonce,
274            short_ids: msg.short_ids.clone(),
275            prefilled_txs: msg
276                .prefilled_txs
277                .iter()
278                .map(|p| (usize::from(p.index), p.tx.clone()))
279                .collect(),
280        }
281    }
282}
283
284impl From<CmpctBlockMessage> for crate::bip152::CompactBlock {
285    fn from(msg: CmpctBlockMessage) -> Self {
286        Self {
287            header: msg.header,
288            nonce: msg.nonce,
289            short_ids: msg.short_ids,
290            prefilled_txs: msg
291                .prefilled_txs
292                .into_iter()
293                .map(|p| (usize::from(p.index), p.tx))
294                .collect(),
295        }
296    }
297}
298
299/// GetBlockTxn message - Request missing transactions from compact block (BIP152)
300#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
301pub struct GetBlockTxnMessage {
302    /// Block hash for the compact block
303    pub block_hash: Hash,
304    /// Indices of transactions to request (0-indexed)
305    pub indices: Vec<u16>,
306}
307
308/// BlockTxn message - Response with requested transactions (BIP152)
309#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
310pub struct BlockTxnMessage {
311    /// Block hash for the compact block
312    pub block_hash: Hash,
313    /// Requested transactions in order
314    pub transactions: Vec<Transaction>,
315    /// Witness data (one Vec<Witness> per tx, one Witness per input). If Some and len matches transactions, serializes with TX_WITH_WITNESS per BIP152/Core.
316    pub witnesses: Option<Vec<Vec<blvm_consensus::segwit::Witness>>>,
317}
318
319/// Network address structure (legacy format)
320#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
321pub struct NetworkAddress {
322    pub services: u64,
323    pub ip: [u8; 16], // IPv6 address (IPv4 mapped to IPv6)
324    pub port: u16,
325}
326
327/// BIP155: Address type for addrv2 message
328#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
329#[repr(u8)]
330pub enum AddressType {
331    IPv4 = 1,
332    IPv6 = 2,
333    TorV2 = 3,
334    TorV3 = 4,
335    I2P = 5,
336    CJDNS = 6,
337}
338
339impl AddressType {
340    /// Get the expected address length in bytes for this type
341    pub fn address_length(&self) -> usize {
342        match self {
343            AddressType::IPv4 => 4,
344            AddressType::IPv6 => 16,
345            AddressType::TorV2 => 10,
346            AddressType::TorV3 => 32,
347            AddressType::I2P => 32,
348            AddressType::CJDNS => 16,
349        }
350    }
351
352    /// Try to create AddressType from u8
353    pub fn from_u8(value: u8) -> Option<Self> {
354        match value {
355            1 => Some(AddressType::IPv4),
356            2 => Some(AddressType::IPv6),
357            3 => Some(AddressType::TorV2),
358            4 => Some(AddressType::TorV3),
359            5 => Some(AddressType::I2P),
360            6 => Some(AddressType::CJDNS),
361            _ => None,
362        }
363    }
364}
365
366/// BIP155: Extended address message (addrv2)
367#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
368pub struct AddrV2Message {
369    pub addresses: Vec<NetworkAddressV2>,
370}
371
372/// BIP155: Extended network address structure
373#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
374pub struct NetworkAddressV2 {
375    pub time: u32,
376    pub services: u64,
377    pub address_type: AddressType,
378    pub address: Vec<u8>, // Variable length based on address_type
379    pub port: u16,
380}
381
382impl NetworkAddressV2 {
383    /// Create a new NetworkAddressV2
384    pub fn new(
385        time: u32,
386        services: u64,
387        address_type: AddressType,
388        address: Vec<u8>,
389        port: u16,
390    ) -> Result<Self> {
391        let expected_len = address_type.address_length();
392        if address.len() != expected_len {
393            return Err(ProtocolError::Consensus(ConsensusError::Serialization(
394                std::borrow::Cow::Owned(format!(
395                    "Invalid address length for type {:?}: expected {}, got {}",
396                    address_type,
397                    expected_len,
398                    address.len()
399                )),
400            )));
401        }
402        Ok(Self {
403            time,
404            services,
405            address_type,
406            address,
407            port,
408        })
409    }
410
411    /// Convert to legacy NetworkAddress (if possible)
412    pub fn to_legacy(&self) -> Option<NetworkAddress> {
413        match self.address_type {
414            AddressType::IPv4 => {
415                if self.address.len() == 4 {
416                    // Map IPv4 to IPv6-mapped format
417                    let mut ipv6 = [0u8; 16];
418                    ipv6[10] = 0xff;
419                    ipv6[11] = 0xff;
420                    ipv6[12..16].copy_from_slice(&self.address);
421                    Some(NetworkAddress {
422                        services: self.services,
423                        ip: ipv6,
424                        port: self.port,
425                    })
426                } else {
427                    None
428                }
429            }
430            AddressType::IPv6 => {
431                if self.address.len() == 16 {
432                    let mut ipv6 = [0u8; 16];
433                    ipv6.copy_from_slice(&self.address);
434                    Some(NetworkAddress {
435                        services: self.services,
436                        ip: ipv6,
437                        port: self.port,
438                    })
439                } else {
440                    None
441                }
442            }
443            _ => None, // Tor, I2P, CJDNS cannot be converted to legacy format
444        }
445    }
446}
447
448/// Inventory vector identifying objects
449#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
450pub struct InventoryVector {
451    pub inv_type: u32,
452    pub hash: Hash,
453}
454
455/// Network response to a message
456#[derive(Debug, Clone)]
457pub enum NetworkResponse {
458    Ok,
459    SendMessage(Box<NetworkMessage>),
460    SendMessages(Vec<NetworkMessage>),
461    Reject(String),
462}
463
464/// Peer connection state
465#[derive(Clone)]
466pub struct PeerState {
467    pub version: u32,
468    pub services: u64,
469    pub user_agent: String,
470    pub start_height: i32,
471    pub handshake_complete: bool,
472    pub known_addresses: Vec<NetworkAddress>,
473    pub ping_nonce: Option<u64>,
474    pub last_pong: Option<std::time::SystemTime>,
475    pub min_fee_rate: Option<u64>,
476    /// BIP324: v2 encrypted transport state (if enabled)
477    /// Note: V2Transport is not Clone, so we use Option<Box<V2Transport>> for storage
478    #[cfg(all(
479        feature = "bip324",
480        any(target_arch = "x86_64", target_arch = "aarch64")
481    ))]
482    pub v2_transport: Option<std::sync::Arc<crate::v2_transport::V2Transport>>,
483    /// BIP324: Whether v2 transport handshake is in progress
484    #[cfg(all(
485        feature = "bip324",
486        any(target_arch = "x86_64", target_arch = "aarch64")
487    ))]
488    pub v2_handshake: Option<std::sync::Arc<crate::v2_transport::V2Handshake>>,
489}
490
491impl PeerState {
492    pub fn new() -> Self {
493        Self {
494            version: 0,
495            services: 0,
496            user_agent: String::new(),
497            start_height: 0,
498            handshake_complete: false,
499            known_addresses: Vec::new(),
500            ping_nonce: None,
501            last_pong: None,
502            min_fee_rate: None,
503            #[cfg(all(
504                feature = "bip324",
505                any(target_arch = "x86_64", target_arch = "aarch64")
506            ))]
507            v2_transport: None,
508            #[cfg(all(
509                feature = "bip324",
510                any(target_arch = "x86_64", target_arch = "aarch64")
511            ))]
512            v2_handshake: None,
513        }
514    }
515
516    /// Check if peer supports BIP324 v2 encrypted transport
517    #[cfg(all(
518        feature = "bip324",
519        any(target_arch = "x86_64", target_arch = "aarch64")
520    ))]
521    pub fn supports_v2_transport(&self) -> bool {
522        use crate::service_flags::{has_flag, standard};
523        has_flag(self.services, standard::NODE_V2_TRANSPORT)
524    }
525
526    /// Check if v2 transport is active
527    #[cfg(all(
528        feature = "bip324",
529        any(target_arch = "x86_64", target_arch = "aarch64")
530    ))]
531    pub fn is_v2_transport_active(&self) -> bool {
532        self.v2_transport.is_some()
533    }
534}
535
536impl Default for PeerState {
537    fn default() -> Self {
538        Self::new()
539    }
540}
541
542/// Chain object (block or transaction)
543#[derive(Debug, Clone)]
544pub enum ChainObject {
545    Block(Arc<Block>),
546    Transaction(Arc<Transaction>),
547}
548
549impl ChainObject {
550    pub fn as_block(&self) -> Option<&Arc<Block>> {
551        match self {
552            ChainObject::Block(block) => Some(block),
553            _ => None,
554        }
555    }
556
557    pub fn as_transaction(&self) -> Option<&Arc<Transaction>> {
558        match self {
559            ChainObject::Transaction(tx) => Some(tx),
560            _ => None,
561        }
562    }
563}
564
565/// Trait for chain state access (node layer implements this)
566///
567/// This trait allows the protocol layer to query chain state without
568/// owning it. The node layer provides real implementations using its
569/// storage modules (BlockStore, TxIndex, MempoolManager).
570pub trait ChainStateAccess {
571    /// Check if we have an object (block or transaction) by hash
572    fn has_object(&self, hash: &Hash) -> bool;
573
574    /// Get an object (block or transaction) by hash
575    fn get_object(&self, hash: &Hash) -> Option<ChainObject>;
576
577    /// Get headers for a block locator (for GetHeaders requests)
578    /// This implements the Bitcoin block locator algorithm
579    fn get_headers_for_locator(&self, locator: &[Hash], stop: &Hash) -> Vec<BlockHeader>;
580
581    /// Get all mempool transactions
582    fn get_mempool_transactions(&self) -> Vec<Transaction>;
583}
584
585/// Process incoming network message
586///
587/// This function handles Bitcoin P2P protocol messages, applying protocol-specific
588/// limits and delegating consensus validation to the protocol engine.
589///
590/// # Arguments
591///
592/// * `engine` - The protocol engine (contains consensus layer)
593/// * `message` - The network message to process
594/// * `peer_state` - Current peer connection state
595/// * `chain_access` - Optional chain state access (node layer provides this)
596/// * `utxo_set` - Optional UTXO set for block validation
597/// * `height` - Optional block height for validation context
598///
599/// # Returns
600///
601/// A `NetworkResponse` indicating the result of processing
602pub fn process_network_message(
603    engine: &BitcoinProtocolEngine,
604    message: &NetworkMessage,
605    peer_state: &mut PeerState,
606    chain_access: Option<&dyn ChainStateAccess>,
607    utxo_set: Option<&UtxoSet>,
608    height: Option<u64>,
609) -> Result<NetworkResponse> {
610    let config = engine.get_config();
611    match message {
612        NetworkMessage::Version(version) => process_version_message(version, peer_state, config),
613        NetworkMessage::VerAck => process_verack_message(peer_state),
614        NetworkMessage::Addr(addr) => process_addr_message(addr, peer_state, config),
615        NetworkMessage::AddrV2(addrv2) => process_addrv2_message(addrv2, peer_state, config),
616        NetworkMessage::Inv(inv) => process_inv_message(inv, chain_access, config),
617        NetworkMessage::GetData(getdata) => process_getdata_message(getdata, chain_access, config),
618        NetworkMessage::GetHeaders(getheaders) => {
619            process_getheaders_message(getheaders, chain_access, config)
620        }
621        NetworkMessage::Headers(headers) => process_headers_message(headers, config),
622        NetworkMessage::Block(block, witnesses) => {
623            process_block_message(engine, block, witnesses, utxo_set, height, config)
624        }
625        NetworkMessage::Tx(tx) => process_tx_message(engine, tx, height),
626        NetworkMessage::Ping(ping) => process_ping_message(ping, peer_state),
627        NetworkMessage::Pong(pong) => process_pong_message(pong, peer_state),
628        NetworkMessage::MemPool => process_mempool_message(chain_access),
629        NetworkMessage::FeeFilter(feefilter) => process_feefilter_message(feefilter, peer_state),
630        NetworkMessage::GetBlocks(getblocks) => {
631            process_getblocks_message(getblocks, chain_access, config)
632        }
633        NetworkMessage::GetAddr => process_getaddr_message(peer_state, config),
634        NetworkMessage::NotFound(notfound) => process_notfound_message(notfound, config),
635        NetworkMessage::Reject(reject) => process_reject_message(reject, config),
636        NetworkMessage::SendHeaders => process_sendheaders_message(peer_state),
637        NetworkMessage::SendCmpct(sendcmpct) => {
638            process_sendcmpct_message(sendcmpct, peer_state, config)
639        }
640        NetworkMessage::CmpctBlock(cmpctblock) => process_cmpctblock_message(cmpctblock),
641        NetworkMessage::GetBlockTxn(getblocktxn) => {
642            process_getblocktxn_message(getblocktxn, chain_access, config)
643        }
644        NetworkMessage::BlockTxn(blocktxn) => process_blocktxn_message(blocktxn),
645        #[cfg(feature = "utxo-commitments")]
646        NetworkMessage::GetUTXOSet(getutxoset) => process_getutxoset_message(getutxoset),
647        #[cfg(feature = "utxo-commitments")]
648        NetworkMessage::UTXOSet(utxoset) => process_utxoset_message(utxoset),
649        #[cfg(feature = "utxo-commitments")]
650        NetworkMessage::GetFilteredBlock(getfiltered) => {
651            process_getfilteredblock_message(getfiltered)
652        }
653        #[cfg(feature = "utxo-commitments")]
654        NetworkMessage::FilteredBlock(filtered) => process_filteredblock_message(filtered),
655        NetworkMessage::GetBanList(getbanlist) => process_getbanlist_message(getbanlist),
656        NetworkMessage::BanList(banlist) => process_banlist_message(banlist),
657    }
658}
659
660/// Process version message
661fn process_version_message(
662    version: &VersionMessage,
663    peer_state: &mut PeerState,
664    config: &ProtocolConfig,
665) -> Result<NetworkResponse> {
666    // Validate version message
667    if version.version < 70001 {
668        return Ok(NetworkResponse::Reject("Version too old".into()));
669    }
670
671    // Validate user agent length (from config)
672    if version.user_agent.len() > config.network_limits.max_user_agent_length {
673        return Ok(NetworkResponse::Reject(format!(
674            "User agent too long (max {} bytes)",
675            config.network_limits.max_user_agent_length
676        )));
677    }
678
679    // Update peer state
680    peer_state.version = version.version;
681    peer_state.services = version.services;
682    peer_state.user_agent = version.user_agent.clone();
683    peer_state.start_height = version.start_height;
684
685    // BIP324: Check if peer supports v2 transport and we should negotiate it
686    #[cfg(all(
687        feature = "bip324",
688        any(target_arch = "x86_64", target_arch = "aarch64")
689    ))]
690    {
691        use crate::service_flags::{has_flag, standard};
692        let peer_supports_v2 = has_flag(version.services, standard::NODE_V2_TRANSPORT);
693        let we_support_v2 = config.service_flags.node_v2_transport;
694
695        if peer_supports_v2 && we_support_v2 {
696            // Initiate v2 handshake (responder side)
697            let handshake = crate::v2_transport::V2Handshake::new_responder();
698            peer_state.v2_handshake = Some(std::sync::Arc::new(handshake));
699            // Note: Actual handshake happens at connection level, not in version message
700            // This just records that we should use v2 transport
701        }
702    }
703
704    // Send verack response
705    Ok(NetworkResponse::SendMessage(Box::new(
706        NetworkMessage::VerAck,
707    )))
708}
709
710/// Process verack message
711fn process_verack_message(peer_state: &mut PeerState) -> Result<NetworkResponse> {
712    peer_state.handshake_complete = true;
713    Ok(NetworkResponse::Ok)
714}
715
716/// Process addr message
717fn process_addr_message(
718    addr: &AddrMessage,
719    peer_state: &mut PeerState,
720    config: &ProtocolConfig,
721) -> Result<NetworkResponse> {
722    // Validate address count (from config)
723    if addr.addresses.len() > config.network_limits.max_addr_addresses {
724        return Ok(NetworkResponse::Reject(format!(
725            "Too many addresses (max {})",
726            config.network_limits.max_addr_addresses
727        )));
728    }
729
730    // Store addresses for future use
731    peer_state.known_addresses.extend(addr.addresses.clone());
732
733    Ok(NetworkResponse::Ok)
734}
735
736/// Process addrv2 message (BIP155)
737fn process_addrv2_message(
738    addrv2: &AddrV2Message,
739    peer_state: &mut PeerState,
740    config: &ProtocolConfig,
741) -> Result<NetworkResponse> {
742    // Validate address count (from config)
743    if addrv2.addresses.len() > config.network_limits.max_addr_addresses {
744        return Ok(NetworkResponse::Reject(format!(
745            "Too many addresses (max {})",
746            config.network_limits.max_addr_addresses
747        )));
748    }
749
750    // Convert addrv2 addresses to legacy format where possible and store
751    for addr_v2 in &addrv2.addresses {
752        if let Some(legacy_addr) = addr_v2.to_legacy() {
753            peer_state.known_addresses.push(legacy_addr);
754        }
755        // Note: Tor v3, I2P, CJDNS addresses cannot be converted to legacy format
756        // but we still process them (they're stored in addrv2 format in node layer)
757    }
758
759    Ok(NetworkResponse::Ok)
760}
761
762/// Process inv message
763fn process_inv_message(
764    inv: &InvMessage,
765    chain_access: Option<&dyn ChainStateAccess>,
766    config: &ProtocolConfig,
767) -> Result<NetworkResponse> {
768    // Validate inventory count (from config)
769    if inv.inventory.len() > config.network_limits.max_inv_items {
770        return Ok(NetworkResponse::Reject(format!(
771            "Too many inventory items (max {})",
772            config.network_limits.max_inv_items
773        )));
774    }
775
776    // Check which items we need (if chain access provided)
777    if let Some(chain) = chain_access {
778        let mut needed_items = Vec::with_capacity(inv.inventory.len());
779        for item in &inv.inventory {
780            if !chain.has_object(&item.hash) {
781                needed_items.push(item.clone());
782            }
783        }
784
785        if !needed_items.is_empty() {
786            return Ok(NetworkResponse::SendMessage(Box::new(
787                NetworkMessage::GetData(GetDataMessage {
788                    inventory: needed_items,
789                }),
790            )));
791        }
792    }
793
794    Ok(NetworkResponse::Ok)
795}
796
797/// Process getdata message
798fn process_getdata_message(
799    getdata: &GetDataMessage,
800    chain_access: Option<&dyn ChainStateAccess>,
801    config: &ProtocolConfig,
802) -> Result<NetworkResponse> {
803    // Validate request count (from config)
804    if getdata.inventory.len() > config.network_limits.max_inv_items {
805        return Ok(NetworkResponse::Reject(format!(
806            "Too many getdata items (max {})",
807            config.network_limits.max_inv_items
808        )));
809    }
810
811    // Send requested objects (if chain access provided)
812    if let Some(chain) = chain_access {
813        let mut responses = Vec::with_capacity(getdata.inventory.len());
814        for item in &getdata.inventory {
815            if let Some(obj) = chain.get_object(&item.hash) {
816                match item.inv_type {
817                    1 => {
818                        // MSG_TX
819                        if let Some(tx) = obj.as_transaction() {
820                            responses.push(NetworkMessage::Tx(Arc::clone(tx)));
821                        }
822                    }
823                    2 => {
824                        // MSG_BLOCK
825                        if let Some(block) = obj.as_block() {
826                            // ChainObject doesn't carry witnesses; relay with empty witnesses
827                            // (pre-SegWit peers or blocks fetched without witness data).
828                            responses.push(NetworkMessage::Block(Arc::clone(block), vec![]));
829                        }
830                    }
831                    _ => {
832                        // Unknown inventory type - skip
833                    }
834                }
835            }
836        }
837
838        if !responses.is_empty() {
839            return Ok(NetworkResponse::SendMessages(responses));
840        }
841    }
842
843    Ok(NetworkResponse::Ok)
844}
845
846/// Process getheaders message
847fn process_getheaders_message(
848    getheaders: &GetHeadersMessage,
849    chain_access: Option<&dyn ChainStateAccess>,
850    config: &ProtocolConfig,
851) -> Result<NetworkResponse> {
852    // Validate block locator size (from config)
853    if getheaders.block_locator_hashes.len() > config.validation.max_locator_hashes {
854        return Ok(NetworkResponse::Reject(format!(
855            "Too many locator hashes (max {})",
856            config.validation.max_locator_hashes
857        )));
858    }
859
860    // Use chain access to find headers (if provided)
861    if let Some(chain) = chain_access {
862        let headers =
863            chain.get_headers_for_locator(&getheaders.block_locator_hashes, &getheaders.hash_stop);
864        return Ok(NetworkResponse::SendMessage(Box::new(
865            NetworkMessage::Headers(HeadersMessage { headers }),
866        )));
867    }
868
869    Ok(NetworkResponse::Reject("Chain access not available".into()))
870}
871
872/// Process headers message
873fn process_headers_message(
874    headers: &HeadersMessage,
875    config: &ProtocolConfig,
876) -> Result<NetworkResponse> {
877    // Validate header count (from config)
878    if headers.headers.len() > config.network_limits.max_headers {
879        return Ok(NetworkResponse::Reject(format!(
880            "Too many headers (max {})",
881            config.network_limits.max_headers
882        )));
883    }
884
885    // Header validation is consensus logic, not protocol
886    // Node layer will validate headers using consensus layer
887    Ok(NetworkResponse::Ok)
888}
889
890/// Process block message
891fn process_block_message(
892    engine: &BitcoinProtocolEngine,
893    block: &Block,
894    witnesses: &[Vec<blvm_consensus::segwit::Witness>],
895    utxo_set: Option<&UtxoSet>,
896    height: Option<u64>,
897    config: &ProtocolConfig,
898) -> Result<NetworkResponse> {
899    // Check protocol limits first (from config)
900    if block.transactions.len() > config.validation.max_txs_per_block {
901        return Err(crate::error::ProtocolError::MessageTooLarge {
902            size: block.transactions.len(),
903            max: config.validation.max_txs_per_block,
904        });
905    }
906
907    // Delegate to consensus via protocol engine (requires utxo_set and height)
908    if let (Some(utxos), Some(h)) = (utxo_set, height) {
909        let context = ProtocolValidationContext::new(engine.get_protocol_version(), h)?;
910        // Pass witnesses for full BIP141/Taproot weight and script validation.
911        let result = engine
912            .validate_block_with_protocol_and_witnesses(block, witnesses, utxos, h, &context)?;
913
914        match result {
915            ValidationResult::Valid => Ok(NetworkResponse::Ok),
916            ValidationResult::Invalid(reason) => {
917                Ok(NetworkResponse::Reject(format!("Invalid block: {reason}")))
918            }
919        }
920    } else {
921        Err(crate::error::ProtocolError::Configuration(
922            "Missing validation context (utxo_set and height required)".into(),
923        ))
924    }
925}
926
927/// Process transaction message
928fn process_tx_message(
929    engine: &BitcoinProtocolEngine,
930    tx: &Transaction,
931    height: Option<u64>,
932) -> Result<NetworkResponse> {
933    // Check protocol limits and validate
934    let context =
935        ProtocolValidationContext::new(engine.get_protocol_version(), height.unwrap_or(0))?;
936    let result = engine.validate_transaction_with_protocol(tx, &context)?;
937
938    match result {
939        ValidationResult::Valid => Ok(NetworkResponse::Ok),
940        ValidationResult::Invalid(reason) => Ok(NetworkResponse::Reject(format!(
941            "Invalid transaction: {reason}"
942        ))),
943    }
944}
945
946/// Process ping message
947fn process_ping_message(
948    ping: &PingMessage,
949    _peer_state: &mut PeerState,
950) -> Result<NetworkResponse> {
951    let pong = NetworkMessage::Pong(PongMessage { nonce: ping.nonce });
952    Ok(NetworkResponse::SendMessage(Box::new(pong)))
953}
954
955/// Process pong message
956fn process_pong_message(pong: &PongMessage, peer_state: &mut PeerState) -> Result<NetworkResponse> {
957    // Validate pong nonce matches our ping
958    if peer_state.ping_nonce == Some(pong.nonce) {
959        peer_state.ping_nonce = None;
960        peer_state.last_pong = Some(std::time::SystemTime::now());
961    }
962
963    Ok(NetworkResponse::Ok)
964}
965
966/// Process mempool message
967fn process_mempool_message(chain_access: Option<&dyn ChainStateAccess>) -> Result<NetworkResponse> {
968    // Send all mempool transactions (if chain access provided)
969    if let Some(chain) = chain_access {
970        let mempool_txs = chain.get_mempool_transactions();
971        let mut responses = Vec::with_capacity(mempool_txs.len());
972
973        for tx in mempool_txs {
974            responses.push(NetworkMessage::Tx(Arc::new(tx)));
975        }
976
977        if !responses.is_empty() {
978            return Ok(NetworkResponse::SendMessages(responses));
979        }
980    }
981
982    Ok(NetworkResponse::Ok)
983}
984
985/// Process feefilter message
986fn process_feefilter_message(
987    feefilter: &FeeFilterMessage,
988    peer_state: &mut PeerState,
989) -> Result<NetworkResponse> {
990    peer_state.min_fee_rate = Some(feefilter.feerate);
991    Ok(NetworkResponse::Ok)
992}
993
994/// Process getblocks message
995fn process_getblocks_message(
996    getblocks: &GetBlocksMessage,
997    chain_access: Option<&dyn ChainStateAccess>,
998    config: &ProtocolConfig,
999) -> Result<NetworkResponse> {
1000    // Validate block locator size (from config)
1001    if getblocks.block_locator_hashes.len() > config.validation.max_locator_hashes {
1002        return Ok(NetworkResponse::Reject(format!(
1003            "Too many locator hashes (max {})",
1004            config.validation.max_locator_hashes
1005        )));
1006    }
1007
1008    // Use chain access to find blocks (if provided)
1009    // Note: GetBlocks is similar to GetHeaders but returns full blocks
1010    // For now, we'll delegate to GetHeaders logic or return inv message
1011    if let Some(chain) = chain_access {
1012        // Find blocks using locator and return inv message
1013        let mut inventory = Vec::with_capacity(getblocks.block_locator_hashes.len());
1014        for hash in &getblocks.block_locator_hashes {
1015            if chain.has_object(hash) {
1016                inventory.push(InventoryVector {
1017                    inv_type: 2, // MSG_BLOCK
1018                    hash: *hash,
1019                });
1020            }
1021        }
1022
1023        if !inventory.is_empty() {
1024            return Ok(NetworkResponse::SendMessage(Box::new(NetworkMessage::Inv(
1025                InvMessage { inventory },
1026            ))));
1027        }
1028    }
1029
1030    Ok(NetworkResponse::Ok)
1031}
1032
1033/// Process getaddr message
1034fn process_getaddr_message(
1035    peer_state: &mut PeerState,
1036    config: &ProtocolConfig,
1037) -> Result<NetworkResponse> {
1038    // Return known addresses (if any)
1039    if !peer_state.known_addresses.is_empty() {
1040        // Limit to configured max addresses
1041        let max_addrs = config
1042            .network_limits
1043            .max_addr_addresses
1044            .min(peer_state.known_addresses.len());
1045        let mut addresses = Vec::with_capacity(max_addrs);
1046        addresses.extend(peer_state.known_addresses.iter().take(max_addrs).cloned());
1047
1048        return Ok(NetworkResponse::SendMessage(Box::new(
1049            NetworkMessage::Addr(AddrMessage { addresses }),
1050        )));
1051    }
1052
1053    Ok(NetworkResponse::Ok)
1054}
1055
1056/// Process notfound message
1057fn process_notfound_message(
1058    notfound: &NotFoundMessage,
1059    config: &ProtocolConfig,
1060) -> Result<NetworkResponse> {
1061    // Validate inventory count (from config)
1062    if notfound.inventory.len() > config.network_limits.max_inv_items {
1063        return Ok(NetworkResponse::Reject(format!(
1064            "Too many notfound items (max {})",
1065            config.network_limits.max_inv_items
1066        )));
1067    }
1068
1069    // NotFound is informational - just acknowledge
1070    Ok(NetworkResponse::Ok)
1071}
1072
1073/// Process reject message
1074fn process_reject_message(
1075    reject: &RejectMessage,
1076    _config: &ProtocolConfig,
1077) -> Result<NetworkResponse> {
1078    // Validate message name length (Bitcoin protocol limit: 12 bytes)
1079    // This is a fixed protocol limit, not configurable
1080    if reject.message.len() > 12 {
1081        return Ok(NetworkResponse::Reject(
1082            "Invalid reject message name".into(),
1083        ));
1084    }
1085
1086    // Validate reason length (Bitcoin protocol limit: 111 bytes)
1087    // This is a fixed protocol limit, not configurable
1088    if reject.reason.len() > 111 {
1089        return Ok(NetworkResponse::Reject("Reject reason too long".into()));
1090    }
1091
1092    // Reject is informational - log and acknowledge
1093    // In production, this would trigger appropriate handling (ban peer, etc.)
1094    Ok(NetworkResponse::Ok)
1095}
1096
1097/// Process sendheaders message
1098fn process_sendheaders_message(_peer_state: &mut PeerState) -> Result<NetworkResponse> {
1099    // Enable headers-only mode for this peer
1100    // This is a flag that affects future GetHeaders responses
1101    // For now, we just acknowledge (actual implementation would set a flag)
1102    Ok(NetworkResponse::Ok)
1103}
1104
1105/// Process sendcmpct message (BIP152)
1106fn process_sendcmpct_message(
1107    sendcmpct: &SendCmpctMessage,
1108    _peer_state: &mut PeerState,
1109    config: &ProtocolConfig,
1110) -> Result<NetworkResponse> {
1111    // Validate version (must be 1 or 2, or match configured preferred version)
1112    let valid_versions = [1, 2];
1113    if !valid_versions.contains(&sendcmpct.version) {
1114        return Ok(NetworkResponse::Reject(
1115            "Invalid compact block version".into(),
1116        ));
1117    }
1118
1119    // Check if compact blocks are enabled
1120    if !config.compact_blocks.enabled {
1121        return Ok(NetworkResponse::Reject("Compact blocks not enabled".into()));
1122    }
1123
1124    // Store compact block preference in peer state
1125    // (actual implementation would store this)
1126    let _ = (sendcmpct.version, sendcmpct.prefer_cmpct);
1127    Ok(NetworkResponse::Ok)
1128}
1129
1130/// Process cmpctblock message (BIP152)
1131fn process_cmpctblock_message(_cmpctblock: &CmpctBlockMessage) -> Result<NetworkResponse> {
1132    // Validate compact block and reconstruct full block
1133    // For now, just acknowledge (actual implementation would validate and reconstruct)
1134    Ok(NetworkResponse::Ok)
1135}
1136
1137/// Process getblocktxn message (BIP152)
1138fn process_getblocktxn_message(
1139    getblocktxn: &GetBlockTxnMessage,
1140    chain_access: Option<&dyn ChainStateAccess>,
1141    config: &ProtocolConfig,
1142) -> Result<NetworkResponse> {
1143    // Validate indices count (from config)
1144    if getblocktxn.indices.len() > config.compact_blocks.max_blocktxn_indices {
1145        return Ok(NetworkResponse::Reject(format!(
1146            "Too many transaction indices (max {})",
1147            config.compact_blocks.max_blocktxn_indices
1148        )));
1149    }
1150
1151    // Use chain access to get requested transactions
1152    if let Some(chain) = chain_access {
1153        let mut transactions = Vec::new();
1154        for &index in &getblocktxn.indices {
1155            // Get block and extract transaction at index
1156            // (simplified - actual implementation would get block first)
1157            if let Some(obj) = chain.get_object(&getblocktxn.block_hash) {
1158                if let Some(block) = obj.as_block() {
1159                    if (index as usize) < block.transactions.len() {
1160                        transactions.push(block.transactions[index as usize].clone());
1161                    }
1162                }
1163            }
1164        }
1165
1166        if !transactions.is_empty() {
1167            return Ok(NetworkResponse::SendMessage(Box::new(
1168                NetworkMessage::BlockTxn(BlockTxnMessage {
1169                    block_hash: getblocktxn.block_hash,
1170                    transactions,
1171                    witnesses: None, // Chain access returns tx only; no witness data
1172                }),
1173            )));
1174        }
1175    }
1176
1177    Ok(NetworkResponse::Ok)
1178}
1179
1180/// Process blocktxn message (BIP152)
1181fn process_blocktxn_message(_blocktxn: &BlockTxnMessage) -> Result<NetworkResponse> {
1182    // Validate transactions and use to reconstruct block
1183    // For now, just acknowledge (actual implementation would validate and reconstruct)
1184    Ok(NetworkResponse::Ok)
1185}
1186
1187#[cfg(feature = "utxo-commitments")]
1188/// Process getutxoset message
1189fn process_getutxoset_message(_getutxoset: &commons::GetUTXOSetMessage) -> Result<NetworkResponse> {
1190    // Request UTXO set at specific height
1191    // For now, just acknowledge (actual implementation would fetch and return UTXO set)
1192    Ok(NetworkResponse::Ok)
1193}
1194
1195#[cfg(feature = "utxo-commitments")]
1196/// Process utxoset message
1197fn process_utxoset_message(_utxoset: &commons::UTXOSetMessage) -> Result<NetworkResponse> {
1198    // Receive UTXO set commitment
1199    // For now, just acknowledge (actual implementation would validate and store)
1200    Ok(NetworkResponse::Ok)
1201}
1202
1203#[cfg(feature = "utxo-commitments")]
1204/// Process getfilteredblock message
1205fn process_getfilteredblock_message(
1206    _getfiltered: &commons::GetFilteredBlockMessage,
1207) -> Result<NetworkResponse> {
1208    // Request filtered block (spam-filtered)
1209    // For now, just acknowledge (actual implementation would filter and return)
1210    Ok(NetworkResponse::Ok)
1211}
1212
1213#[cfg(feature = "utxo-commitments")]
1214/// Process filteredblock message
1215fn process_filteredblock_message(
1216    _filtered: &commons::FilteredBlockMessage,
1217) -> Result<NetworkResponse> {
1218    // Receive filtered block
1219    // For now, just acknowledge (actual implementation would validate and process)
1220    Ok(NetworkResponse::Ok)
1221}
1222
1223/// Process getbanlist message
1224fn process_getbanlist_message(_getbanlist: &commons::GetBanListMessage) -> Result<NetworkResponse> {
1225    // Request ban list from peer
1226    // For now, just acknowledge (actual implementation would return ban list)
1227    Ok(NetworkResponse::Ok)
1228}
1229
1230/// Process banlist message
1231fn process_banlist_message(_banlist: &commons::BanListMessage) -> Result<NetworkResponse> {
1232    // Receive ban list from peer
1233    // For now, just acknowledge (actual implementation would validate and merge)
1234    Ok(NetworkResponse::Ok)
1235}