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