Skip to main content

blvm_protocol/
network.rs

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