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