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    /// Transaction message. The second field is the per-input witness stack.
51    /// A legacy transaction carries an empty stack list.
52    Tx(Arc<Transaction>, Vec<crate::segwit::Witness>),
53    Ping(PingMessage),
54    Pong(PongMessage),
55    MemPool,
56    FeeFilter(FeeFilterMessage),
57    // Additional core P2P messages
58    GetBlocks(GetBlocksMessage),
59    GetAddr,
60    NotFound(NotFoundMessage),
61    Reject(RejectMessage),
62    SendHeaders,
63    // BIP152 Compact Block Relay
64    SendCmpct(SendCmpctMessage),
65    CmpctBlock(CmpctBlockMessage),
66    GetBlockTxn(GetBlockTxnMessage),
67    BlockTxn(BlockTxnMessage),
68    // Commons-specific protocol extensions
69    #[cfg(feature = "utxo-commitments")]
70    GetUTXOSet(commons::GetUTXOSetMessage),
71    #[cfg(feature = "utxo-commitments")]
72    UTXOSet(commons::UTXOSetMessage),
73    #[cfg(feature = "utxo-commitments")]
74    GetFilteredBlock(commons::GetFilteredBlockMessage),
75    #[cfg(feature = "utxo-commitments")]
76    FilteredBlock(commons::FilteredBlockMessage),
77    GetBanList(commons::GetBanListMessage),
78    BanList(commons::BanListMessage),
79}
80
81/// Version message for initial handshake
82#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
83pub struct VersionMessage {
84    pub version: i32,
85    pub services: u64,
86    pub timestamp: i64,
87    pub addr_recv: NetworkAddress,
88    pub addr_from: NetworkAddress,
89    pub nonce: u64,
90    pub user_agent: String,
91    pub start_height: i32,
92    pub relay: bool,
93}
94
95/// Block P2P message: consensus block paired with its segwit witness stack.
96///
97/// The consensus [`Block`] carries transactions without witness data; witnesses are kept
98/// alongside so the node can round-trip the full segwit wire format without altering the
99/// consensus type.
100#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
101pub struct BlockMessage {
102    pub block: Block,
103    /// One `Vec<Witness>` per transaction, one `Witness` per input.
104    #[serde(skip_serializing_if = "Vec::is_empty", default)]
105    pub witnesses: Vec<Vec<blvm_consensus::segwit::Witness>>,
106}
107
108/// Transaction P2P message (thin wrapper for dispatch).
109#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
110pub struct TxMessage {
111    pub transaction: Transaction,
112    /// One witness stack per input. Empty when the wire encoding has no witness flag.
113    #[serde(skip_serializing_if = "Vec::is_empty", default)]
114    pub witnesses: Vec<crate::segwit::Witness>,
115}
116
117/// Compact block P2P message: decoded BIP152 compact block.
118///
119/// Holds the [`crate::bip152::CompactBlock`] abstraction used for efficient block relay.
120/// The node converts between this and the wire [`CmpctBlockMessage`] on ingress/egress.
121#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
122pub struct CompactBlockMessage {
123    pub compact_block: crate::bip152::CompactBlock,
124}
125
126/// Address message containing peer addresses
127#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
128pub struct AddrMessage {
129    pub addresses: Vec<NetworkAddress>,
130}
131
132/// Inventory message listing available objects
133#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
134pub struct InvMessage {
135    pub inventory: Vec<InventoryVector>,
136}
137
138/// GetData message requesting specific objects
139#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
140pub struct GetDataMessage {
141    pub inventory: Vec<InventoryVector>,
142}
143
144/// GetHeaders message requesting block headers
145#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
146pub struct GetHeadersMessage {
147    pub version: u32,
148    pub block_locator_hashes: Vec<Hash>,
149    pub hash_stop: Hash,
150}
151
152/// Headers message containing block headers
153#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
154pub struct HeadersMessage {
155    pub headers: Vec<BlockHeader>,
156}
157
158/// Ping message for connection keepalive
159#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
160pub struct PingMessage {
161    pub nonce: u64,
162}
163
164/// Pong message responding to ping
165#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
166pub struct PongMessage {
167    pub nonce: u64,
168}
169
170/// FeeFilter message setting minimum fee rate
171#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
172pub struct FeeFilterMessage {
173    pub feerate: u64,
174}
175
176/// GetBlocks message requesting blocks by locator
177#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
178pub struct GetBlocksMessage {
179    pub version: u32,
180    pub block_locator_hashes: Vec<Hash>,
181    pub hash_stop: Hash,
182}
183
184/// NotFound message indicating requested object not found
185#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
186pub struct NotFoundMessage {
187    pub inventory: Vec<InventoryVector>,
188}
189
190/// Reject message rejecting a message with reason
191#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
192pub struct RejectMessage {
193    pub message: String,          // Command name of rejected message
194    pub code: u8, // Rejection code (0x01=malformed, 0x10=invalid, 0x11=obsolete, 0x12=duplicate, 0x40=nonstandard, 0x41=dust, 0x42=insufficientfee, 0x43=checkpoint)
195    pub reason: String, // Human-readable reason
196    pub extra_data: Option<Hash>, // Optional hash for rejected object
197}
198
199/// SendCmpct message - Negotiate compact block relay support (BIP152)
200#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
201pub struct SendCmpctMessage {
202    /// Compact block version (1 or 2)
203    pub version: u64,
204    /// Whether to prefer compact blocks (1) or regular blocks (0)
205    pub prefer_cmpct: u8,
206}
207
208/// CmpctBlock message - Compact block data (BIP152)
209#[derive(Debug, Clone, PartialEq, Eq)]
210pub struct CmpctBlockMessage {
211    /// Block header
212    pub header: BlockHeader,
213    /// Nonce for short transaction ID calculation (SipHash key derivation)
214    pub nonce: u64,
215    /// Short transaction IDs (6 bytes each)
216    pub short_ids: Vec<ShortTxId>,
217    /// Prefilled transactions (transactions that are likely missing)
218    pub prefilled_txs: Vec<PrefilledTransaction>,
219}
220
221/// PrefilledTransaction - Transaction included in compact block
222#[derive(Debug, Clone, PartialEq, Eq)]
223pub struct PrefilledTransaction {
224    /// Index in block (0 = coinbase)
225    pub index: u16,
226    /// Transaction data
227    pub tx: Transaction,
228    /// Witness data (one stack per input). If Some and non-empty, serializes with TX_WITH_WITNESS per BIP152/Core.
229    pub witness: Option<Vec<blvm_consensus::segwit::Witness>>,
230}
231
232/// Error converting between [`crate::bip152::CompactBlock`] and wire [`CmpctBlockMessage`].
233#[derive(Debug, Clone, PartialEq, Eq, Error)]
234pub enum CompactBlockWireConvertError {
235    #[error("prefilled transaction index {0} does not fit in BIP152 u16 index")]
236    PrefilledIndexTooLarge(usize),
237    #[error("duplicate prefilled transaction index {0}")]
238    DuplicatePrefilledIndex(usize),
239}
240
241impl TryFrom<crate::bip152::CompactBlock> for CmpctBlockMessage {
242    type Error = CompactBlockWireConvertError;
243
244    /// Builds a cmpctblock-shaped message from the integration [`crate::bip152::CompactBlock`].
245    ///
246    /// Prefilled entries are **sorted by transaction index** (required for BIP152 diff-encoding on the wire).
247    /// Witness stacks stored on the compact block are copied onto each prefilled transaction.
248    fn try_from(mut value: crate::bip152::CompactBlock) -> std::result::Result<Self, Self::Error> {
249        value.prefilled_txs.sort_by_key(|(i, _, _)| *i);
250        let mut prefilled_txs = Vec::with_capacity(value.prefilled_txs.len());
251        let mut prev_idx: Option<usize> = None;
252        for (idx, tx, witness) in value.prefilled_txs {
253            if prev_idx == Some(idx) {
254                return Err(CompactBlockWireConvertError::DuplicatePrefilledIndex(idx));
255            }
256            prev_idx = Some(idx);
257            let index = u16::try_from(idx)
258                .map_err(|_| CompactBlockWireConvertError::PrefilledIndexTooLarge(idx))?;
259            prefilled_txs.push(PrefilledTransaction { index, tx, witness });
260        }
261        Ok(CmpctBlockMessage {
262            header: value.header,
263            nonce: value.nonce,
264            short_ids: value.short_ids,
265            prefilled_txs,
266        })
267    }
268}
269
270impl From<&CmpctBlockMessage> for crate::bip152::CompactBlock {
271    fn from(msg: &CmpctBlockMessage) -> Self {
272        Self {
273            header: msg.header.clone(),
274            nonce: msg.nonce,
275            short_ids: msg.short_ids.clone(),
276            prefilled_txs: msg
277                .prefilled_txs
278                .iter()
279                .map(|p| (usize::from(p.index), p.tx.clone(), p.witness.clone()))
280                .collect(),
281        }
282    }
283}
284
285impl From<CmpctBlockMessage> for crate::bip152::CompactBlock {
286    fn from(msg: CmpctBlockMessage) -> Self {
287        Self {
288            header: msg.header,
289            nonce: msg.nonce,
290            short_ids: msg.short_ids,
291            prefilled_txs: msg
292                .prefilled_txs
293                .into_iter()
294                .map(|p| (usize::from(p.index), p.tx, p.witness))
295                .collect(),
296        }
297    }
298}
299
300/// GetBlockTxn message - Request missing transactions from compact block (BIP152)
301#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
302pub struct GetBlockTxnMessage {
303    /// Block hash for the compact block
304    pub block_hash: Hash,
305    /// Indices of transactions to request (0-indexed)
306    pub indices: Vec<u16>,
307}
308
309/// BlockTxn message - Response with requested transactions (BIP152)
310#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
311pub struct BlockTxnMessage {
312    /// Block hash for the compact block
313    pub block_hash: Hash,
314    /// Requested transactions in order
315    pub transactions: Vec<Transaction>,
316    /// 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.
317    pub witnesses: Option<Vec<Vec<blvm_consensus::segwit::Witness>>>,
318}
319
320/// Network address structure (legacy format)
321#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
322pub struct NetworkAddress {
323    pub services: u64,
324    pub ip: [u8; 16], // IPv6 address (IPv4 mapped to IPv6)
325    pub port: u16,
326    /// Advertised unix time. Zero is stored and is outside the relay horizon.
327    pub time: u32,
328}
329
330/// BIP155: Address type for addrv2 message
331#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
332#[repr(u8)]
333pub enum AddressType {
334    IPv4 = 1,
335    IPv6 = 2,
336    TorV2 = 3,
337    TorV3 = 4,
338    I2P = 5,
339    CJDNS = 6,
340}
341
342impl AddressType {
343    /// Get the expected address length in bytes for this type
344    pub fn address_length(&self) -> usize {
345        match self {
346            AddressType::IPv4 => 4,
347            AddressType::IPv6 => 16,
348            AddressType::TorV2 => 10,
349            AddressType::TorV3 => 32,
350            AddressType::I2P => 32,
351            AddressType::CJDNS => 16,
352        }
353    }
354
355    /// Try to create AddressType from u8
356    pub fn from_u8(value: u8) -> Option<Self> {
357        match value {
358            1 => Some(AddressType::IPv4),
359            2 => Some(AddressType::IPv6),
360            3 => Some(AddressType::TorV2),
361            4 => Some(AddressType::TorV3),
362            5 => Some(AddressType::I2P),
363            6 => Some(AddressType::CJDNS),
364            _ => None,
365        }
366    }
367}
368
369/// BIP155: Extended address message (addrv2)
370#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
371pub struct AddrV2Message {
372    pub addresses: Vec<NetworkAddressV2>,
373}
374
375/// BIP155: Extended network address structure
376#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
377pub struct NetworkAddressV2 {
378    pub time: u32,
379    pub services: u64,
380    pub address_type: AddressType,
381    pub address: Vec<u8>, // Variable length based on address_type
382    pub port: u16,
383}
384
385impl NetworkAddressV2 {
386    /// Create a new NetworkAddressV2
387    pub fn new(
388        time: u32,
389        services: u64,
390        address_type: AddressType,
391        address: Vec<u8>,
392        port: u16,
393    ) -> Result<Self> {
394        let expected_len = address_type.address_length();
395        if address.len() != expected_len {
396            return Err(ProtocolError::Consensus(ConsensusError::Serialization(
397                std::borrow::Cow::Owned(format!(
398                    "Invalid address length for type {:?}: expected {}, got {}",
399                    address_type,
400                    expected_len,
401                    address.len()
402                )),
403            )));
404        }
405        Ok(Self {
406            time,
407            services,
408            address_type,
409            address,
410            port,
411        })
412    }
413
414    /// Convert to legacy NetworkAddress (if possible)
415    pub fn to_legacy(&self) -> Option<NetworkAddress> {
416        match self.address_type {
417            AddressType::IPv4 => {
418                if self.address.len() == 4 {
419                    // Map IPv4 to IPv6-mapped format
420                    let mut ipv6 = [0u8; 16];
421                    ipv6[10] = 0xff;
422                    ipv6[11] = 0xff;
423                    ipv6[12..16].copy_from_slice(&self.address);
424                    Some(NetworkAddress {
425                        time: self.time,
426                        services: self.services,
427                        ip: ipv6,
428                        port: self.port,
429                    })
430                } else {
431                    None
432                }
433            }
434            AddressType::IPv6 => {
435                if self.address.len() == 16 {
436                    let mut ipv6 = [0u8; 16];
437                    ipv6.copy_from_slice(&self.address);
438                    Some(NetworkAddress {
439                        time: self.time,
440                        services: self.services,
441                        ip: ipv6,
442                        port: self.port,
443                    })
444                } else {
445                    None
446                }
447            }
448            _ => None, // Tor, I2P, CJDNS cannot be converted to legacy format
449        }
450    }
451}
452
453/// Inventory vector identifying objects
454#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
455pub struct InventoryVector {
456    pub inv_type: u32,
457    pub hash: Hash,
458}
459
460/// Network response to a message
461#[derive(Debug, Clone)]
462pub enum NetworkResponse {
463    Ok,
464    SendMessage(Box<NetworkMessage>),
465    SendMessages(Vec<NetworkMessage>),
466    Reject(String),
467}
468
469/// Compact block held until `blocktxn` fills the short ids the mempool missed.
470#[derive(Clone)]
471pub(crate) struct PendingCompact {
472    pub header: BlockHeader,
473    pub nonce: u64,
474    /// `sendcmpct` version that selected the short-id preimage. `1` is the txid.
475    /// `2` is the wtxid.
476    pub version: u64,
477    pub slots: Vec<Option<(Transaction, Vec<blvm_consensus::segwit::Witness>)>>,
478    pub missing: Vec<(usize, ShortTxId)>,
479}
480
481/// Peer connection state
482#[derive(Clone)]
483pub struct PeerState {
484    pub version: i32,
485    pub services: u64,
486    pub user_agent: String,
487    pub start_height: i32,
488    pub handshake_complete: bool,
489    pub known_addresses: Vec<NetworkAddress>,
490    pub ping_nonce: Option<u64>,
491    pub last_pong: Option<std::time::SystemTime>,
492    /// BIP133 minimum, in satoshis per 1,000 virtual bytes. `None` means the peer sent no filter.
493    pub min_fee_rate: Option<u64>,
494    /// BIP37 relay byte. `false` means this peer asked not to receive transaction announcements.
495    /// A version message that omits the byte leaves this `true`.
496    pub relay_tx: bool,
497    /// BIP130: peer prefers `headers` over `inv` for block announcements.
498    pub prefer_headers: bool,
499    /// BIP152: negotiated compact-block version (1 or 2), if peer sent sendcmpct.
500    pub compact_block_version: Option<u8>,
501    /// BIP152: peer prefers compact blocks when true.
502    pub prefer_compact_block: bool,
503    /// This peer was sent `sendcmpct` with announce true and still holds that slot.
504    pub high_bandwidth: bool,
505    /// Last header height this peer has accepted from us, or that we accepted from them.
506    /// Version `start_height` is not this value.
507    pub known_header_height: Option<u64>,
508    /// Peer sent `wtxidrelay` after version and before verack.
509    pub wtxid_relay: bool,
510    /// `sendpackages` version word received before verack.
511    pub package_remote: Option<u64>,
512    /// Compact block waiting on `blocktxn` for the short ids the mempool did not fill.
513    pub(crate) pending_compact: Option<PendingCompact>,
514    /// BIP324: v2 encrypted transport state (if enabled)
515    /// Note: V2Transport is not Clone, so we use Option<Box<V2Transport>> for storage
516    #[cfg(all(
517        feature = "bip324",
518        any(target_arch = "x86_64", target_arch = "aarch64")
519    ))]
520    pub v2_transport: Option<std::sync::Arc<crate::v2_transport::V2Transport>>,
521    /// BIP324: Whether v2 transport handshake is in progress
522    #[cfg(all(
523        feature = "bip324",
524        any(target_arch = "x86_64", target_arch = "aarch64")
525    ))]
526    pub v2_handshake: Option<std::sync::Arc<crate::v2_transport::V2Handshake>>,
527}
528
529impl PeerState {
530    pub fn new() -> Self {
531        Self {
532            version: 0,
533            services: 0,
534            user_agent: String::new(),
535            start_height: 0,
536            handshake_complete: false,
537            known_addresses: Vec::new(),
538            ping_nonce: None,
539            last_pong: None,
540            min_fee_rate: None,
541            relay_tx: true,
542            prefer_headers: false,
543            compact_block_version: None,
544            prefer_compact_block: false,
545            high_bandwidth: false,
546            known_header_height: None,
547            wtxid_relay: false,
548            package_remote: None,
549            pending_compact: None,
550            #[cfg(all(
551                feature = "bip324",
552                any(target_arch = "x86_64", target_arch = "aarch64")
553            ))]
554            v2_transport: None,
555            #[cfg(all(
556                feature = "bip324",
557                any(target_arch = "x86_64", target_arch = "aarch64")
558            ))]
559            v2_handshake: None,
560        }
561    }
562
563    /// Check if peer supports BIP324 v2 encrypted transport
564    #[cfg(all(
565        feature = "bip324",
566        any(target_arch = "x86_64", target_arch = "aarch64")
567    ))]
568    pub fn supports_v2_transport(&self) -> bool {
569        use crate::service_flags::{has_flag, standard};
570        has_flag(self.services, standard::NODE_V2_TRANSPORT)
571    }
572
573    /// Check if v2 transport is active
574    #[cfg(all(
575        feature = "bip324",
576        any(target_arch = "x86_64", target_arch = "aarch64")
577    ))]
578    pub fn is_v2_transport_active(&self) -> bool {
579        self.v2_transport.is_some()
580    }
581}
582
583impl Default for PeerState {
584    fn default() -> Self {
585        Self::new()
586    }
587}
588
589/// Chain object (block or transaction)
590#[derive(Debug, Clone)]
591pub enum ChainObject {
592    Block(Arc<Block>),
593    Transaction(Arc<Transaction>),
594}
595
596impl ChainObject {
597    pub fn as_block(&self) -> Option<&Arc<Block>> {
598        match self {
599            ChainObject::Block(block) => Some(block),
600            _ => None,
601        }
602    }
603
604    pub fn as_transaction(&self) -> Option<&Arc<Transaction>> {
605        match self {
606            ChainObject::Transaction(tx) => Some(tx),
607            _ => None,
608        }
609    }
610}
611
612/// Trait for chain state access (node layer implements this)
613///
614/// This trait allows the protocol layer to query chain state without
615/// owning it. The node layer provides real implementations using its
616/// storage modules (BlockStore, TxIndex, MempoolManager).
617pub trait ChainStateAccess {
618    /// Check if we have an object (block or transaction) by hash
619    fn has_object(&self, hash: &Hash) -> bool;
620
621    /// Get an object (block or transaction) by hash
622    fn get_object(&self, hash: &Hash) -> Option<ChainObject>;
623
624    /// Get headers for a block locator (for GetHeaders requests)
625    /// This implements the Bitcoin block locator algorithm
626    fn get_headers_for_locator(&self, locator: &[Hash], stop: &Hash) -> Vec<BlockHeader>;
627
628    /// Get all mempool transactions
629    fn get_mempool_transactions(&self) -> Vec<Transaction>;
630
631    /// Mempool bodies paired with the witness stacks that belong in a BIP152
632    /// version-2 short id. Empty stacks mean the wtxid equals the txid.
633    /// The default has no SegWit stacks. A node that stored them overrides this.
634    fn mempool_for_compact_block(
635        &self,
636    ) -> Vec<(Transaction, Vec<blvm_consensus::segwit::Witness>)> {
637        self.get_mempool_transactions()
638            .into_iter()
639            .map(|tx| (tx, Vec::new()))
640            .collect()
641    }
642
643    /// Witness stacks for a stored block (SegWit relay). Default: not available.
644    fn get_block_witnesses(
645        &self,
646        _hash: &Hash,
647    ) -> Option<Vec<Vec<blvm_consensus::segwit::Witness>>> {
648        None
649    }
650
651    /// Ban entries this node would return from `getbanlist`. Default: none stored.
652    fn ban_entries(&self) -> Vec<crate::commons::BanEntry> {
653        Vec::new()
654    }
655
656    /// Apply one ban the way `setban` does: the address is the IP with port 0, and
657    /// `timestamp` is the absolute unban time (`0` is permanent).
658    fn apply_ban(&self, entry: &crate::commons::BanEntry) -> std::result::Result<(), String> {
659        let _ = entry;
660        Err("ban path is not available".into())
661    }
662
663    /// Stored UTXO commitment for a block, when this node has one.
664    fn stored_utxo_commitment(
665        &self,
666        _block_hash: &Hash,
667    ) -> std::result::Result<Option<crate::commons::UTXOCommitment>, String> {
668        Ok(None)
669    }
670
671    /// Basic block filter for a block, the bytes `getblockfilter` already builds.
672    fn stored_block_filter(
673        &self,
674        _block_hash: &Hash,
675    ) -> std::result::Result<Option<(BlockHeader, Vec<u8>)>, String> {
676        Ok(None)
677    }
678}
679
680/// Process incoming network message
681///
682/// This function handles Bitcoin P2P protocol messages, applying protocol-specific
683/// limits and delegating consensus validation to the protocol engine.
684///
685/// # Arguments
686///
687/// * `engine` - The protocol engine (contains consensus layer)
688/// * `message` - The network message to process
689/// * `peer_state` - Current peer connection state
690/// * `chain_access` - Optional chain state access (node layer provides this)
691/// * `utxo_set` - Optional UTXO set for block validation
692/// * `height` - Optional block height for validation context
693///
694/// # Returns
695///
696/// A `NetworkResponse` indicating the result of processing
697pub fn process_network_message(
698    engine: &BitcoinProtocolEngine,
699    message: &NetworkMessage,
700    peer_state: &mut PeerState,
701    chain_access: Option<&dyn ChainStateAccess>,
702    utxo_set: Option<&UtxoSet>,
703    height: Option<u64>,
704) -> Result<NetworkResponse> {
705    let config = engine.get_config();
706    match message {
707        NetworkMessage::Version(version) => process_version_message(version, peer_state, config),
708        NetworkMessage::VerAck => process_verack_message(peer_state),
709        NetworkMessage::Addr(addr) => process_addr_message(addr, peer_state, config),
710        NetworkMessage::AddrV2(addrv2) => process_addrv2_message(addrv2, peer_state, config),
711        NetworkMessage::Inv(inv) => process_inv_message(inv, chain_access, config),
712        NetworkMessage::GetData(getdata) => process_getdata_message(getdata, chain_access, config),
713        NetworkMessage::GetHeaders(getheaders) => {
714            process_getheaders_message(getheaders, chain_access, config)
715        }
716        NetworkMessage::Headers(headers) => process_headers_message(headers, config),
717        NetworkMessage::Block(block, witnesses) => {
718            process_block_message(engine, block, witnesses, utxo_set, height, config)
719        }
720        NetworkMessage::Tx(tx, witnesses) => process_tx_message(engine, tx, witnesses, height),
721        NetworkMessage::Ping(ping) => process_ping_message(ping, peer_state),
722        NetworkMessage::Pong(pong) => process_pong_message(pong, peer_state),
723        NetworkMessage::MemPool => process_mempool_message(chain_access),
724        NetworkMessage::FeeFilter(feefilter) => process_feefilter_message(feefilter, peer_state),
725        NetworkMessage::GetBlocks(getblocks) => {
726            process_getblocks_message(getblocks, chain_access, config)
727        }
728        NetworkMessage::GetAddr => process_getaddr_message(peer_state, config),
729        NetworkMessage::NotFound(notfound) => process_notfound_message(notfound, config),
730        NetworkMessage::Reject(reject) => process_reject_message(reject, config),
731        NetworkMessage::SendHeaders => process_sendheaders_message(peer_state),
732        NetworkMessage::SendCmpct(sendcmpct) => {
733            process_sendcmpct_message(sendcmpct, peer_state, config)
734        }
735        NetworkMessage::CmpctBlock(cmpctblock) => process_cmpctblock_message(
736            engine,
737            cmpctblock,
738            peer_state,
739            chain_access,
740            utxo_set,
741            height,
742            config,
743        ),
744        NetworkMessage::GetBlockTxn(getblocktxn) => {
745            process_getblocktxn_message(getblocktxn, chain_access, config)
746        }
747        NetworkMessage::BlockTxn(blocktxn) => {
748            process_blocktxn_message(engine, blocktxn, peer_state, utxo_set, height, config)
749        }
750        #[cfg(feature = "utxo-commitments")]
751        NetworkMessage::GetUTXOSet(getutxoset) => {
752            process_getutxoset_message(getutxoset, chain_access)
753        }
754        #[cfg(feature = "utxo-commitments")]
755        NetworkMessage::UTXOSet(utxoset) => process_utxoset_message(utxoset, chain_access),
756        #[cfg(feature = "utxo-commitments")]
757        NetworkMessage::GetFilteredBlock(getfiltered) => {
758            process_getfilteredblock_message(getfiltered, chain_access)
759        }
760        #[cfg(feature = "utxo-commitments")]
761        NetworkMessage::FilteredBlock(filtered) => {
762            process_filteredblock_message(filtered, chain_access)
763        }
764        NetworkMessage::GetBanList(getbanlist) => {
765            process_getbanlist_message(getbanlist, chain_access)
766        }
767        NetworkMessage::BanList(banlist) => process_banlist_message(banlist, chain_access),
768    }
769}
770
771/// Process version message
772fn process_version_message(
773    version: &VersionMessage,
774    peer_state: &mut PeerState,
775    config: &ProtocolConfig,
776) -> Result<NetworkResponse> {
777    // Validate version message
778    if version.version < 70001 {
779        return Ok(NetworkResponse::Reject("Version too old".into()));
780    }
781
782    // Validate user agent length (from config)
783    if version.user_agent.len() > config.network_limits.max_user_agent_length {
784        return Ok(NetworkResponse::Reject(format!(
785            "User agent too long (max {} bytes)",
786            config.network_limits.max_user_agent_length
787        )));
788    }
789
790    // Update peer state
791    peer_state.version = version.version;
792    peer_state.services = version.services;
793    peer_state.user_agent = version.user_agent.clone();
794    peer_state.start_height = version.start_height;
795
796    // BIP324: Check if peer supports v2 transport and we should negotiate it
797    #[cfg(all(
798        feature = "bip324",
799        any(target_arch = "x86_64", target_arch = "aarch64")
800    ))]
801    {
802        use crate::service_flags::{has_flag, standard};
803        let peer_supports_v2 = has_flag(version.services, standard::NODE_V2_TRANSPORT);
804        let we_support_v2 = config.service_flags.node_v2_transport;
805
806        if peer_supports_v2 && we_support_v2 {
807            // Initiate v2 handshake (responder side)
808            let handshake = crate::v2_transport::V2Handshake::new_responder();
809            peer_state.v2_handshake = Some(std::sync::Arc::new(handshake));
810            // Note: Actual handshake happens at connection level, not in version message
811            // This just records that we should use v2 transport
812        }
813    }
814
815    // Send verack response
816    Ok(NetworkResponse::SendMessage(Box::new(
817        NetworkMessage::VerAck,
818    )))
819}
820
821/// Process verack message
822fn process_verack_message(peer_state: &mut PeerState) -> Result<NetworkResponse> {
823    peer_state.handshake_complete = true;
824    Ok(NetworkResponse::Ok)
825}
826
827/// Process addr message
828fn process_addr_message(
829    addr: &AddrMessage,
830    peer_state: &mut PeerState,
831    config: &ProtocolConfig,
832) -> Result<NetworkResponse> {
833    // Validate address count (from config)
834    if addr.addresses.len() > config.network_limits.max_addr_addresses {
835        return Ok(NetworkResponse::Reject(format!(
836            "Too many addresses (max {})",
837            config.network_limits.max_addr_addresses
838        )));
839    }
840
841    // Store addresses for future use
842    peer_state.known_addresses.extend(addr.addresses.clone());
843
844    Ok(NetworkResponse::Ok)
845}
846
847/// Process addrv2 message (BIP155)
848fn process_addrv2_message(
849    addrv2: &AddrV2Message,
850    peer_state: &mut PeerState,
851    config: &ProtocolConfig,
852) -> Result<NetworkResponse> {
853    // Validate address count (from config)
854    if addrv2.addresses.len() > config.network_limits.max_addr_addresses {
855        return Ok(NetworkResponse::Reject(format!(
856            "Too many addresses (max {})",
857            config.network_limits.max_addr_addresses
858        )));
859    }
860
861    // Convert addrv2 addresses to legacy format where possible and store
862    for addr_v2 in &addrv2.addresses {
863        if let Some(legacy_addr) = addr_v2.to_legacy() {
864            peer_state.known_addresses.push(legacy_addr);
865        }
866        // Note: Tor v3, I2P, CJDNS addresses cannot be converted to legacy format
867        // but we still process them (they're stored in addrv2 format in node layer)
868    }
869
870    Ok(NetworkResponse::Ok)
871}
872
873/// Process inv message
874fn process_inv_message(
875    inv: &InvMessage,
876    chain_access: Option<&dyn ChainStateAccess>,
877    config: &ProtocolConfig,
878) -> Result<NetworkResponse> {
879    // Validate inventory count (from config)
880    if inv.inventory.len() > config.network_limits.max_inv_items {
881        return Ok(NetworkResponse::Reject(format!(
882            "Too many inventory items (max {})",
883            config.network_limits.max_inv_items
884        )));
885    }
886
887    // Check which items we need (if chain access provided)
888    if let Some(chain) = chain_access {
889        let mut needed_items = Vec::with_capacity(inv.inventory.len());
890        for item in &inv.inventory {
891            if !chain.has_object(&item.hash) {
892                needed_items.push(item.clone());
893            }
894        }
895
896        if !needed_items.is_empty() {
897            return Ok(NetworkResponse::SendMessage(Box::new(
898                NetworkMessage::GetData(GetDataMessage {
899                    inventory: needed_items,
900                }),
901            )));
902        }
903    }
904
905    Ok(NetworkResponse::Ok)
906}
907
908/// Process getdata message
909fn process_getdata_message(
910    getdata: &GetDataMessage,
911    chain_access: Option<&dyn ChainStateAccess>,
912    config: &ProtocolConfig,
913) -> Result<NetworkResponse> {
914    // Validate request count (from config)
915    if getdata.inventory.len() > config.network_limits.max_inv_items {
916        return Ok(NetworkResponse::Reject(format!(
917            "Too many getdata items (max {})",
918            config.network_limits.max_inv_items
919        )));
920    }
921
922    // Send requested objects (if chain access provided)
923    if let Some(chain) = chain_access {
924        let mut responses = Vec::with_capacity(getdata.inventory.len());
925        for item in &getdata.inventory {
926            if let Some(obj) = chain.get_object(&item.hash) {
927                match item.inv_type {
928                    1 => {
929                        // MSG_TX
930                        if let Some(tx) = obj.as_transaction() {
931                            responses.push(NetworkMessage::Tx(Arc::clone(tx), Vec::new()));
932                        }
933                    }
934                    2 => {
935                        // MSG_BLOCK
936                        if let Some(block) = obj.as_block() {
937                            let Some(witnesses) = chain.get_block_witnesses(&item.hash) else {
938                                // REV-P-08: do not synthesize empty witness stacks when storage
939                                // has no witness data (node layer serves notfound instead).
940                                continue;
941                            };
942                            responses.push(NetworkMessage::Block(Arc::clone(block), witnesses));
943                        }
944                    }
945                    _ => {
946                        // Unknown inventory type - skip
947                    }
948                }
949            }
950        }
951
952        if !responses.is_empty() {
953            return Ok(NetworkResponse::SendMessages(responses));
954        }
955    }
956
957    Ok(NetworkResponse::Ok)
958}
959
960/// Process getheaders message
961fn process_getheaders_message(
962    getheaders: &GetHeadersMessage,
963    chain_access: Option<&dyn ChainStateAccess>,
964    config: &ProtocolConfig,
965) -> Result<NetworkResponse> {
966    // Validate block locator size (from config)
967    if getheaders.block_locator_hashes.len() > config.validation.max_locator_hashes {
968        return Ok(NetworkResponse::Reject(format!(
969            "Too many locator hashes (max {})",
970            config.validation.max_locator_hashes
971        )));
972    }
973
974    // Use chain access to find headers (if provided)
975    if let Some(chain) = chain_access {
976        let headers =
977            chain.get_headers_for_locator(&getheaders.block_locator_hashes, &getheaders.hash_stop);
978        return Ok(NetworkResponse::SendMessage(Box::new(
979            NetworkMessage::Headers(HeadersMessage { headers }),
980        )));
981    }
982
983    Ok(NetworkResponse::Reject("Chain access not available".into()))
984}
985
986/// Process headers message
987fn process_headers_message(
988    headers: &HeadersMessage,
989    config: &ProtocolConfig,
990) -> Result<NetworkResponse> {
991    // Validate header count (from config)
992    if headers.headers.len() > config.network_limits.max_headers {
993        return Ok(NetworkResponse::Reject(format!(
994            "Too many headers (max {})",
995            config.network_limits.max_headers
996        )));
997    }
998
999    // Header validation is consensus logic, not protocol
1000    // Node layer will validate headers using consensus layer
1001    Ok(NetworkResponse::Ok)
1002}
1003
1004/// Process block message
1005fn process_block_message(
1006    engine: &BitcoinProtocolEngine,
1007    block: &Block,
1008    witnesses: &[Vec<blvm_consensus::segwit::Witness>],
1009    utxo_set: Option<&UtxoSet>,
1010    height: Option<u64>,
1011    config: &ProtocolConfig,
1012) -> Result<NetworkResponse> {
1013    // Check protocol limits first (from config)
1014    if block.transactions.len() > config.validation.max_txs_per_block {
1015        return Err(crate::error::ProtocolError::MessageTooLarge {
1016            size: block.transactions.len(),
1017            max: config.validation.max_txs_per_block,
1018        });
1019    }
1020
1021    // Delegate to consensus via protocol engine (requires utxo_set and height)
1022    if let (Some(utxos), Some(h)) = (utxo_set, height) {
1023        let context = ProtocolValidationContext::new(engine.get_protocol_version(), h)?;
1024        // Pass witnesses for full BIP141/Taproot weight and script validation.
1025        let result = engine
1026            .validate_block_with_protocol_and_witnesses(block, witnesses, utxos, h, &context)?;
1027
1028        match result {
1029            ValidationResult::Valid => Ok(NetworkResponse::Ok),
1030            ValidationResult::Invalid(reason) => {
1031                Ok(NetworkResponse::Reject(format!("Invalid block: {reason}")))
1032            }
1033        }
1034    } else {
1035        Err(crate::error::ProtocolError::Configuration(
1036            "Missing validation context (utxo_set and height required)".into(),
1037        ))
1038    }
1039}
1040
1041/// Process transaction message
1042fn process_tx_message(
1043    engine: &BitcoinProtocolEngine,
1044    tx: &Transaction,
1045    _witnesses: &[crate::segwit::Witness],
1046    height: Option<u64>,
1047) -> Result<NetworkResponse> {
1048    // Check protocol limits and validate
1049    let context =
1050        ProtocolValidationContext::new(engine.get_protocol_version(), height.unwrap_or(0))?;
1051    let result = engine.validate_transaction_with_protocol(tx, &context)?;
1052
1053    match result {
1054        ValidationResult::Valid => Ok(NetworkResponse::Ok),
1055        ValidationResult::Invalid(reason) => Ok(NetworkResponse::Reject(format!(
1056            "Invalid transaction: {reason}"
1057        ))),
1058    }
1059}
1060
1061/// Process ping message
1062fn process_ping_message(
1063    ping: &PingMessage,
1064    _peer_state: &mut PeerState,
1065) -> Result<NetworkResponse> {
1066    let pong = NetworkMessage::Pong(PongMessage { nonce: ping.nonce });
1067    Ok(NetworkResponse::SendMessage(Box::new(pong)))
1068}
1069
1070/// Process pong message
1071fn process_pong_message(pong: &PongMessage, peer_state: &mut PeerState) -> Result<NetworkResponse> {
1072    // Validate pong nonce matches our ping
1073    if peer_state.ping_nonce == Some(pong.nonce) {
1074        peer_state.ping_nonce = None;
1075        peer_state.last_pong = Some(std::time::SystemTime::now());
1076    }
1077
1078    Ok(NetworkResponse::Ok)
1079}
1080
1081/// Process mempool message
1082fn process_mempool_message(chain_access: Option<&dyn ChainStateAccess>) -> Result<NetworkResponse> {
1083    // Send all mempool transactions (if chain access provided)
1084    if let Some(chain) = chain_access {
1085        let mempool_txs = chain.get_mempool_transactions();
1086        let mut responses = Vec::with_capacity(mempool_txs.len());
1087
1088        for tx in mempool_txs {
1089            responses.push(NetworkMessage::Tx(Arc::new(tx), Vec::new()));
1090        }
1091
1092        if !responses.is_empty() {
1093            return Ok(NetworkResponse::SendMessages(responses));
1094        }
1095    }
1096
1097    Ok(NetworkResponse::Ok)
1098}
1099
1100/// Process feefilter message
1101fn process_feefilter_message(
1102    feefilter: &FeeFilterMessage,
1103    peer_state: &mut PeerState,
1104) -> Result<NetworkResponse> {
1105    peer_state.min_fee_rate = Some(feefilter.feerate);
1106    Ok(NetworkResponse::Ok)
1107}
1108
1109/// Process getblocks message
1110fn process_getblocks_message(
1111    getblocks: &GetBlocksMessage,
1112    chain_access: Option<&dyn ChainStateAccess>,
1113    config: &ProtocolConfig,
1114) -> Result<NetworkResponse> {
1115    // Validate block locator size (from config)
1116    if getblocks.block_locator_hashes.len() > config.validation.max_locator_hashes {
1117        return Ok(NetworkResponse::Reject(format!(
1118            "Too many locator hashes (max {})",
1119            config.validation.max_locator_hashes
1120        )));
1121    }
1122
1123    if let Some(chain) = chain_access {
1124        use blvm_consensus::block::block_header_hash;
1125
1126        // Core-compatible: walk locator to fork, then advertise block hashes after fork.
1127        const MAX_GETBLOCKS_INV: usize = 500;
1128        let headers =
1129            chain.get_headers_for_locator(&getblocks.block_locator_hashes, &getblocks.hash_stop);
1130        if headers.is_empty() {
1131            return Ok(NetworkResponse::Ok);
1132        }
1133
1134        let inventory: Vec<InventoryVector> = headers
1135            .into_iter()
1136            .take(MAX_GETBLOCKS_INV)
1137            .map(|header| InventoryVector {
1138                inv_type: 2, // MSG_BLOCK
1139                hash: block_header_hash(&header),
1140            })
1141            .collect();
1142
1143        return Ok(NetworkResponse::SendMessage(Box::new(NetworkMessage::Inv(
1144            InvMessage { inventory },
1145        ))));
1146    }
1147
1148    Ok(NetworkResponse::Ok)
1149}
1150
1151/// Process getaddr message
1152fn process_getaddr_message(
1153    peer_state: &mut PeerState,
1154    config: &ProtocolConfig,
1155) -> Result<NetworkResponse> {
1156    // Return known addresses (if any)
1157    if !peer_state.known_addresses.is_empty() {
1158        // Limit to configured max addresses
1159        let max_addrs = config
1160            .network_limits
1161            .max_addr_addresses
1162            .min(peer_state.known_addresses.len());
1163        let mut addresses = Vec::with_capacity(max_addrs);
1164        addresses.extend(peer_state.known_addresses.iter().take(max_addrs).cloned());
1165
1166        return Ok(NetworkResponse::SendMessage(Box::new(
1167            NetworkMessage::Addr(AddrMessage { addresses }),
1168        )));
1169    }
1170
1171    Ok(NetworkResponse::Ok)
1172}
1173
1174/// Process notfound message
1175fn process_notfound_message(
1176    notfound: &NotFoundMessage,
1177    config: &ProtocolConfig,
1178) -> Result<NetworkResponse> {
1179    // Validate inventory count (from config)
1180    if notfound.inventory.len() > config.network_limits.max_inv_items {
1181        return Ok(NetworkResponse::Reject(format!(
1182            "Too many notfound items (max {})",
1183            config.network_limits.max_inv_items
1184        )));
1185    }
1186
1187    // NotFound is informational - just acknowledge
1188    Ok(NetworkResponse::Ok)
1189}
1190
1191/// Process reject message
1192fn process_reject_message(
1193    reject: &RejectMessage,
1194    _config: &ProtocolConfig,
1195) -> Result<NetworkResponse> {
1196    // Validate message name length (Bitcoin protocol limit: 12 bytes)
1197    // This is a fixed protocol limit, not configurable
1198    if reject.message.len() > 12 {
1199        return Ok(NetworkResponse::Reject(
1200            "Invalid reject message name".into(),
1201        ));
1202    }
1203
1204    // Validate reason length (Bitcoin protocol limit: 111 bytes)
1205    // This is a fixed protocol limit, not configurable
1206    if reject.reason.len() > 111 {
1207        return Ok(NetworkResponse::Reject("Reject reason too long".into()));
1208    }
1209
1210    // Reject is informational - log and acknowledge
1211    // In production, this would trigger appropriate handling (ban peer, etc.)
1212    Ok(NetworkResponse::Ok)
1213}
1214
1215/// Process sendheaders message
1216fn process_sendheaders_message(peer_state: &mut PeerState) -> Result<NetworkResponse> {
1217    peer_state.prefer_headers = true;
1218    Ok(NetworkResponse::Ok)
1219}
1220
1221/// Process sendcmpct message (BIP152)
1222fn process_sendcmpct_message(
1223    sendcmpct: &SendCmpctMessage,
1224    peer_state: &mut PeerState,
1225    config: &ProtocolConfig,
1226) -> Result<NetworkResponse> {
1227    // Validate version (must be 1 or 2, or match configured preferred version)
1228    let valid_versions = [1, 2];
1229    if !valid_versions.contains(&sendcmpct.version) {
1230        return Ok(NetworkResponse::Reject(
1231            "Invalid compact block version".into(),
1232        ));
1233    }
1234
1235    // Check if compact blocks are enabled
1236    if !config.compact_blocks.enabled {
1237        return Ok(NetworkResponse::Reject("Compact blocks not enabled".into()));
1238    }
1239
1240    // Store compact block preference in peer state
1241    peer_state.compact_block_version = Some(sendcmpct.version as u8);
1242    peer_state.prefer_compact_block = sendcmpct.prefer_cmpct != 0;
1243    Ok(NetworkResponse::Ok)
1244}
1245
1246fn sha256d(bytes: &[u8]) -> Hash {
1247    use sha2::{Digest, Sha256};
1248    let first = Sha256::digest(bytes);
1249    let second = Sha256::digest(first);
1250    let mut hash = [0u8; 32];
1251    hash.copy_from_slice(&second);
1252    hash
1253}
1254
1255fn txid_of(tx: &Transaction) -> Hash {
1256    let bytes = crate::wire::serialize_tx(tx).unwrap_or_default();
1257    sha256d(&bytes)
1258}
1259
1260/// BIP152 version 1 hashes the txid. Version 2 hashes the wtxid, which is the
1261/// txid when every witness stack is empty.
1262fn short_id_preimage(
1263    tx: &Transaction,
1264    witnesses: &[blvm_consensus::segwit::Witness],
1265    version: u64,
1266) -> Hash {
1267    if version > 1 {
1268        if let Ok(bytes) = crate::wire::serialize_tx_with_witness(tx, witnesses) {
1269            return sha256d(&bytes);
1270        }
1271    }
1272    txid_of(tx)
1273}
1274
1275/// Version 1 blocks carry no witness. Version 2 keeps the stacks that were hashed.
1276fn stacks_for_reconstructed_slot(
1277    version: u64,
1278    witnesses: &[blvm_consensus::segwit::Witness],
1279) -> Vec<blvm_consensus::segwit::Witness> {
1280    if version > 1 && witnesses.iter().any(|stack| !stack.is_empty()) {
1281        witnesses.to_vec()
1282    } else {
1283        Vec::new()
1284    }
1285}
1286
1287fn negotiated_compact_version(peer_state: &PeerState, config: &ProtocolConfig) -> u64 {
1288    peer_state
1289        .compact_block_version
1290        .map(u64::from)
1291        .unwrap_or(config.compact_blocks.preferred_version)
1292}
1293
1294fn header_hash(header: &BlockHeader) -> Hash {
1295    use sha2::{Digest, Sha256};
1296    let bytes = crate::serialization::serialize_block_header(header);
1297    let first = Sha256::digest(&bytes);
1298    let second = Sha256::digest(first);
1299    let mut hash = [0u8; 32];
1300    hash.copy_from_slice(&second);
1301    hash
1302}
1303
1304fn short_id_hex(id: &ShortTxId) -> String {
1305    hex::encode(id)
1306}
1307
1308/// Place prefilled transactions and unique mempool matches. Missing short ids stay empty.
1309fn begin_pending_compact(
1310    msg: &CmpctBlockMessage,
1311    mempool: &[(Transaction, Vec<blvm_consensus::segwit::Witness>)],
1312    version: u64,
1313) -> std::result::Result<PendingCompact, String> {
1314    let n = msg.short_ids.len() + msg.prefilled_txs.len();
1315    let mut slots: Vec<Option<(Transaction, Vec<blvm_consensus::segwit::Witness>)>> = vec![None; n];
1316    let mut prefilled = std::collections::HashSet::new();
1317    for item in &msg.prefilled_txs {
1318        let index = usize::from(item.index);
1319        if index >= n || !prefilled.insert(index) {
1320            return Err(format!(
1321                "prefilled compact-block index {index} is not usable"
1322            ));
1323        }
1324        slots[index] = Some((item.tx.clone(), item.witness.clone().unwrap_or_default()));
1325    }
1326
1327    let mut missing = Vec::new();
1328    let mut next = 0usize;
1329    for short_id in &msg.short_ids {
1330        while prefilled.contains(&next) {
1331            next = next.saturating_add(1);
1332        }
1333        let block_index = next;
1334        next = next.saturating_add(1);
1335        if block_index >= n {
1336            return Err("compact block short ids ran past the transaction list".into());
1337        }
1338        let mut found: Option<(Transaction, Vec<blvm_consensus::segwit::Witness>)> = None;
1339        let mut hits = 0usize;
1340        for (tx, witnesses) in mempool {
1341            let preimage = short_id_preimage(tx, witnesses, version);
1342            let id = crate::bip152::short_tx_id(&msg.header, &preimage, msg.nonce);
1343            if &id == short_id {
1344                hits += 1;
1345                found = Some((
1346                    tx.clone(),
1347                    stacks_for_reconstructed_slot(version, witnesses),
1348                ));
1349            }
1350        }
1351        if hits == 1 {
1352            slots[block_index] = found;
1353        } else {
1354            missing.push((block_index, *short_id));
1355        }
1356    }
1357    Ok(PendingCompact {
1358        header: msg.header.clone(),
1359        nonce: msg.nonce,
1360        version,
1361        slots,
1362        missing,
1363    })
1364}
1365
1366fn finish_compact_block(
1367    engine: &BitcoinProtocolEngine,
1368    pending: PendingCompact,
1369    utxo_set: Option<&UtxoSet>,
1370    height: Option<u64>,
1371    config: &ProtocolConfig,
1372) -> Result<NetworkResponse> {
1373    if !pending.missing.is_empty() {
1374        let id = short_id_hex(&pending.missing[0].1);
1375        return Ok(NetworkResponse::Reject(format!(
1376            "short id {id} matches neither the mempool nor blocktxn"
1377        )));
1378    }
1379    let mut transactions = Vec::with_capacity(pending.slots.len());
1380    let mut witnesses = Vec::with_capacity(pending.slots.len());
1381    for slot in pending.slots {
1382        let Some((tx, witness)) = slot else {
1383            return Ok(NetworkResponse::Reject(
1384                "compact block still has an empty transaction slot".into(),
1385            ));
1386        };
1387        transactions.push(tx);
1388        witnesses.push(witness);
1389    }
1390    let block = Block::from_parts(pending.header, transactions.into_boxed_slice());
1391    match process_block_message(engine, &block, &witnesses, utxo_set, height, config) {
1392        Ok(response) => Ok(response),
1393        Err(err) if err.to_string().contains("Missing validation context") => {
1394            Ok(NetworkResponse::SendMessage(Box::new(
1395                NetworkMessage::Block(Arc::new(block), witnesses),
1396            )))
1397        }
1398        Err(err) => Err(err),
1399    }
1400}
1401
1402/// Process cmpctblock message (BIP152)
1403fn process_cmpctblock_message(
1404    engine: &BitcoinProtocolEngine,
1405    cmpctblock: &CmpctBlockMessage,
1406    peer_state: &mut PeerState,
1407    chain_access: Option<&dyn ChainStateAccess>,
1408    utxo_set: Option<&UtxoSet>,
1409    height: Option<u64>,
1410    config: &ProtocolConfig,
1411) -> Result<NetworkResponse> {
1412    if !config.compact_blocks.enabled {
1413        return Ok(NetworkResponse::Reject("Compact blocks not enabled".into()));
1414    }
1415    let version = negotiated_compact_version(peer_state, config);
1416    let mempool = chain_access
1417        .map(|chain| chain.mempool_for_compact_block())
1418        .unwrap_or_default();
1419    let pending = match begin_pending_compact(cmpctblock, &mempool, version) {
1420        Ok(pending) => pending,
1421        Err(reason) => return Ok(NetworkResponse::Reject(reason)),
1422    };
1423    if pending.missing.is_empty() {
1424        peer_state.pending_compact = None;
1425        return finish_compact_block(engine, pending, utxo_set, height, config);
1426    }
1427    let indices: Vec<u16> = pending
1428        .missing
1429        .iter()
1430        .map(|(index, _)| u16::try_from(*index).unwrap_or(u16::MAX))
1431        .collect();
1432    let block_hash = header_hash(&pending.header);
1433    peer_state.pending_compact = Some(pending);
1434    Ok(NetworkResponse::SendMessage(Box::new(
1435        NetworkMessage::GetBlockTxn(GetBlockTxnMessage {
1436            block_hash,
1437            indices,
1438        }),
1439    )))
1440}
1441
1442/// Process getblocktxn message (BIP152)
1443fn process_getblocktxn_message(
1444    getblocktxn: &GetBlockTxnMessage,
1445    chain_access: Option<&dyn ChainStateAccess>,
1446    config: &ProtocolConfig,
1447) -> Result<NetworkResponse> {
1448    // Validate indices count (from config)
1449    if getblocktxn.indices.len() > config.compact_blocks.max_blocktxn_indices {
1450        return Ok(NetworkResponse::Reject(format!(
1451            "Too many transaction indices (max {})",
1452            config.compact_blocks.max_blocktxn_indices
1453        )));
1454    }
1455
1456    // Use chain access to get requested transactions and witnesses
1457    if let Some(chain) = chain_access {
1458        if let Some(obj) = chain.get_object(&getblocktxn.block_hash) {
1459            if let Some(block) = obj.as_block() {
1460                let stored_witnesses = chain.get_block_witnesses(&getblocktxn.block_hash);
1461                let mut transactions = Vec::with_capacity(getblocktxn.indices.len());
1462                let mut req_witnesses = Vec::with_capacity(getblocktxn.indices.len());
1463
1464                for &index in &getblocktxn.indices {
1465                    let idx = index as usize;
1466                    if idx >= block.transactions.len() {
1467                        return Ok(NetworkResponse::Reject(
1468                            "getblocktxn index past end of block".into(),
1469                        ));
1470                    }
1471                    transactions.push(block.transactions[idx].clone());
1472                    let tx_witness = stored_witnesses
1473                        .as_ref()
1474                        .and_then(|w| w.get(idx).cloned())
1475                        .unwrap_or_else(|| {
1476                            block.transactions[idx]
1477                                .inputs
1478                                .iter()
1479                                .map(|_| Vec::new())
1480                                .collect()
1481                        });
1482                    req_witnesses.push(tx_witness);
1483                }
1484
1485                if !transactions.is_empty() {
1486                    return Ok(NetworkResponse::SendMessage(Box::new(
1487                        NetworkMessage::BlockTxn(BlockTxnMessage {
1488                            block_hash: getblocktxn.block_hash,
1489                            transactions,
1490                            witnesses: Some(req_witnesses),
1491                        }),
1492                    )));
1493                }
1494            }
1495        }
1496    }
1497
1498    Ok(NetworkResponse::Ok)
1499}
1500
1501/// Process blocktxn message (BIP152)
1502fn process_blocktxn_message(
1503    engine: &BitcoinProtocolEngine,
1504    blocktxn: &BlockTxnMessage,
1505    peer_state: &mut PeerState,
1506    utxo_set: Option<&UtxoSet>,
1507    height: Option<u64>,
1508    config: &ProtocolConfig,
1509) -> Result<NetworkResponse> {
1510    let Some(mut pending) = peer_state.pending_compact.clone() else {
1511        return Ok(NetworkResponse::Reject(
1512            "blocktxn does not match a pending compact block".into(),
1513        ));
1514    };
1515    if header_hash(&pending.header) != blocktxn.block_hash {
1516        return Ok(NetworkResponse::Reject(
1517            "blocktxn does not match a pending compact block".into(),
1518        ));
1519    }
1520    if blocktxn.transactions.len() != pending.missing.len() {
1521        let id = pending
1522            .missing
1523            .first()
1524            .map(|(_, short_id)| short_id_hex(short_id))
1525            .unwrap_or_else(|| "none".into());
1526        return Ok(NetworkResponse::Reject(format!(
1527            "short id {id} matches neither the mempool nor blocktxn"
1528        )));
1529    }
1530    for (i, (index, expected)) in pending.missing.iter().enumerate() {
1531        let tx = &blocktxn.transactions[i];
1532        let witness = blocktxn
1533            .witnesses
1534            .as_ref()
1535            .and_then(|all| all.get(i).cloned())
1536            .unwrap_or_default();
1537        let preimage = short_id_preimage(tx, &witness, pending.version);
1538        let got = crate::bip152::short_tx_id(&pending.header, &preimage, pending.nonce);
1539        if &got != expected {
1540            return Ok(NetworkResponse::Reject(format!(
1541                "short id {} matches neither the mempool nor blocktxn",
1542                short_id_hex(expected)
1543            )));
1544        }
1545        pending.slots[*index] = Some((
1546            tx.clone(),
1547            stacks_for_reconstructed_slot(pending.version, &witness),
1548        ));
1549    }
1550    pending.missing.clear();
1551    peer_state.pending_compact = None;
1552    finish_compact_block(engine, pending, utxo_set, height, config)
1553}
1554
1555#[cfg(feature = "utxo-commitments")]
1556/// Process getutxoset message
1557fn process_getutxoset_message(
1558    getutxoset: &commons::GetUTXOSetMessage,
1559    chain_access: Option<&dyn ChainStateAccess>,
1560) -> Result<NetworkResponse> {
1561    let Some(chain) = chain_access else {
1562        return Err(ProtocolError::Validation(
1563            "utxo set is not available".into(),
1564        ));
1565    };
1566    match chain.stored_utxo_commitment(&getutxoset.block_hash) {
1567        Ok(Some(commitment)) => Ok(NetworkResponse::SendMessage(Box::new(
1568            NetworkMessage::UTXOSet(commons::UTXOSetMessage {
1569                request_id: 0,
1570                utxo_count: commitment.utxo_count,
1571                is_complete: true,
1572                chunk_id: None,
1573                commitment,
1574            }),
1575        ))),
1576        Ok(None) => Err(ProtocolError::Validation(
1577            format!(
1578                "no stored UTXO set for block {}",
1579                hex::encode(getutxoset.block_hash)
1580            )
1581            .into(),
1582        )),
1583        Err(reason) => Err(ProtocolError::Validation(reason.into())),
1584    }
1585}
1586
1587#[cfg(feature = "utxo-commitments")]
1588/// Process utxoset message
1589fn process_utxoset_message(
1590    utxoset: &commons::UTXOSetMessage,
1591    chain_access: Option<&dyn ChainStateAccess>,
1592) -> Result<NetworkResponse> {
1593    let Some(chain) = chain_access else {
1594        return Ok(NetworkResponse::Reject(
1595            "utxo commitment does not match the local set".into(),
1596        ));
1597    };
1598    match chain.stored_utxo_commitment(&utxoset.commitment.block_hash) {
1599        Ok(Some(local)) if local == utxoset.commitment => Ok(NetworkResponse::Ok),
1600        Ok(_) => Ok(NetworkResponse::Reject(format!(
1601            "utxo commitment does not match the local set for {}",
1602            hex::encode(utxoset.commitment.block_hash)
1603        ))),
1604        Err(reason) => Ok(NetworkResponse::Reject(reason)),
1605    }
1606}
1607
1608#[cfg(feature = "utxo-commitments")]
1609/// Process getfilteredblock message
1610fn process_getfilteredblock_message(
1611    getfiltered: &commons::GetFilteredBlockMessage,
1612    chain_access: Option<&dyn ChainStateAccess>,
1613) -> Result<NetworkResponse> {
1614    let Some(chain) = chain_access else {
1615        return Err(ProtocolError::Validation(
1616            "block filter is not available".into(),
1617        ));
1618    };
1619    match chain.stored_block_filter(&getfiltered.block_hash) {
1620        Ok(Some((header, filter))) => {
1621            let commitment = chain
1622                .stored_utxo_commitment(&getfiltered.block_hash)
1623                .ok()
1624                .flatten()
1625                .unwrap_or(commons::UTXOCommitment {
1626                    merkle_root: [0u8; 32],
1627                    total_supply: 0,
1628                    utxo_count: 0,
1629                    block_height: 0,
1630                    block_hash: getfiltered.block_hash,
1631                });
1632            Ok(NetworkResponse::SendMessage(Box::new(
1633                NetworkMessage::FilteredBlock(commons::FilteredBlockMessage {
1634                    request_id: getfiltered.request_id,
1635                    header,
1636                    commitment,
1637                    transactions: Vec::new(),
1638                    transaction_indices: Vec::new(),
1639                    spam_summary: commons::SpamSummary {
1640                        filtered_count: 0,
1641                        filtered_value: 0,
1642                        filter_reasons: 0,
1643                    },
1644                    bip158_filter: Some(filter),
1645                }),
1646            )))
1647        }
1648        Ok(None) => Err(ProtocolError::Validation(
1649            format!(
1650                "no block filter for {}",
1651                hex::encode(getfiltered.block_hash)
1652            )
1653            .into(),
1654        )),
1655        Err(reason) => Err(ProtocolError::Validation(reason.into())),
1656    }
1657}
1658
1659#[cfg(feature = "utxo-commitments")]
1660/// Process filteredblock message
1661fn process_filteredblock_message(
1662    filtered: &commons::FilteredBlockMessage,
1663    chain_access: Option<&dyn ChainStateAccess>,
1664) -> Result<NetworkResponse> {
1665    let Some(chain) = chain_access else {
1666        return Ok(NetworkResponse::Reject(
1667            "filtered block does not match the local filter".into(),
1668        ));
1669    };
1670    let block_hash = header_hash(&filtered.header);
1671    match chain.stored_block_filter(&block_hash) {
1672        Ok(Some((_, local))) if filtered.bip158_filter.as_ref() == Some(&local) => {
1673            Ok(NetworkResponse::Ok)
1674        }
1675        Ok(_) => Ok(NetworkResponse::Reject(format!(
1676            "filtered block does not match the local filter for {}",
1677            hex::encode(block_hash)
1678        ))),
1679        Err(reason) => Ok(NetworkResponse::Reject(reason)),
1680    }
1681}
1682
1683/// Process getbanlist message
1684fn process_getbanlist_message(
1685    getbanlist: &commons::GetBanListMessage,
1686    chain_access: Option<&dyn ChainStateAccess>,
1687) -> Result<NetworkResponse> {
1688    let mut entries = chain_access
1689        .map(|chain| chain.ban_entries())
1690        .unwrap_or_default();
1691    if let Some(min_score) = getbanlist.min_score {
1692        entries.retain(|entry| entry.score >= min_score);
1693    }
1694    Ok(NetworkResponse::SendMessage(Box::new(
1695        NetworkMessage::BanList(commons::BanListMessage {
1696            request_id: getbanlist.request_id,
1697            entries,
1698            list_signature: None,
1699        }),
1700    )))
1701}
1702
1703/// Process banlist message
1704fn process_banlist_message(
1705    banlist: &commons::BanListMessage,
1706    chain_access: Option<&dyn ChainStateAccess>,
1707) -> Result<NetworkResponse> {
1708    if banlist.entries.is_empty() {
1709        return Ok(NetworkResponse::Ok);
1710    }
1711    let Some(chain) = chain_access else {
1712        return Ok(NetworkResponse::Reject("ban path is not available".into()));
1713    };
1714    for entry in &banlist.entries {
1715        if let Err(reason) = chain.apply_ban(entry) {
1716            return Ok(NetworkResponse::Reject(reason));
1717        }
1718    }
1719    Ok(NetworkResponse::Ok)
1720}
1721
1722#[cfg(test)]
1723fn assert_getblocktxn_past_end_rejects() {
1724    struct TwoTx;
1725    impl ChainStateAccess for TwoTx {
1726        fn has_object(&self, _hash: &Hash) -> bool {
1727            true
1728        }
1729        fn get_object(&self, hash: &Hash) -> Option<ChainObject> {
1730            let tx = Transaction {
1731                version: 1,
1732                inputs: vec![].into(),
1733                outputs: vec![].into(),
1734                lock_time: 0,
1735            };
1736            let block = Block {
1737                header: BlockHeader {
1738                    version: 1,
1739                    prev_block_hash: [0; 32],
1740                    merkle_root: [0; 32],
1741                    timestamp: 1,
1742                    bits: 1,
1743                    nonce: 1,
1744                },
1745                transactions: vec![tx.clone(), tx].into_boxed_slice(),
1746            };
1747            if hash == &[9u8; 32] {
1748                Some(ChainObject::Block(Arc::new(block)))
1749            } else {
1750                None
1751            }
1752        }
1753        fn get_headers_for_locator(&self, _locator: &[Hash], _stop: &Hash) -> Vec<BlockHeader> {
1754            Vec::new()
1755        }
1756        fn get_mempool_transactions(&self) -> Vec<Transaction> {
1757            Vec::new()
1758        }
1759    }
1760    let msg = GetBlockTxnMessage {
1761        block_hash: [9u8; 32],
1762        indices: vec![5],
1763    };
1764    let response =
1765        process_getblocktxn_message(&msg, Some(&TwoTx), &ProtocolConfig::default()).unwrap();
1766    assert!(
1767        matches!(response, NetworkResponse::Reject(_)),
1768        "expected reject, got {response:?}"
1769    );
1770}
1771
1772#[cfg(test)]
1773#[test]
1774fn getblocktxn_index_past_block_returns_reject_and_no_blocktxn() {
1775    assert_getblocktxn_past_end_rejects();
1776}
1777
1778#[cfg(test)]
1779fn sample_tx(tag: u8) -> Transaction {
1780    Transaction {
1781        version: 1,
1782        inputs: vec![blvm_consensus::types::TransactionInput {
1783            prevout: blvm_consensus::types::OutPoint {
1784                hash: [tag; 32],
1785                index: 0,
1786            },
1787            script_sig: vec![tag],
1788            sequence: 0xffffffff,
1789        }]
1790        .into(),
1791        outputs: vec![blvm_consensus::types::TransactionOutput {
1792            value: 1000,
1793            script_pubkey: vec![0x51],
1794        }]
1795        .into(),
1796        lock_time: 0,
1797    }
1798}
1799
1800#[cfg(test)]
1801fn sample_header() -> BlockHeader {
1802    BlockHeader {
1803        version: 1,
1804        prev_block_hash: [2u8; 32],
1805        merkle_root: [3u8; 32],
1806        timestamp: 1_700_000_000,
1807        bits: 0x1d00ffff,
1808        nonce: 4,
1809    }
1810}
1811
1812#[cfg(test)]
1813struct MempoolChain(Transaction);
1814
1815#[cfg(test)]
1816impl ChainStateAccess for MempoolChain {
1817    fn has_object(&self, _hash: &Hash) -> bool {
1818        false
1819    }
1820    fn get_object(&self, _hash: &Hash) -> Option<ChainObject> {
1821        None
1822    }
1823    fn get_headers_for_locator(&self, _locator: &[Hash], _stop: &Hash) -> Vec<BlockHeader> {
1824        Vec::new()
1825    }
1826    fn get_mempool_transactions(&self) -> Vec<Transaction> {
1827        vec![self.0.clone()]
1828    }
1829}
1830
1831#[cfg(test)]
1832#[test]
1833fn compact_block_rebuilds_from_mempool_and_names_a_miss() {
1834    use crate::{BitcoinProtocolEngine, ProtocolVersion};
1835    let engine = BitcoinProtocolEngine::new(ProtocolVersion::BitcoinV1).unwrap();
1836    let header = sample_header();
1837    let coinbase = sample_tx(0);
1838    let pooled = sample_tx(7);
1839    let nonce = 9u64;
1840    let short = crate::bip152::short_tx_id(&header, &txid_of(&pooled), nonce);
1841    let cmpct = CmpctBlockMessage {
1842        header: header.clone(),
1843        nonce,
1844        short_ids: vec![short],
1845        prefilled_txs: vec![PrefilledTransaction {
1846            index: 0,
1847            tx: coinbase.clone(),
1848            witness: None,
1849        }],
1850    };
1851    let mut peer = PeerState::new();
1852    let response = process_cmpctblock_message(
1853        &engine,
1854        &cmpct,
1855        &mut peer,
1856        Some(&MempoolChain(pooled.clone())),
1857        None,
1858        None,
1859        &ProtocolConfig::default(),
1860    )
1861    .unwrap();
1862    match response {
1863        NetworkResponse::SendMessage(message) => match *message {
1864            NetworkMessage::Block(block, _) => {
1865                assert_eq!(block.header, header);
1866                assert_eq!(block.transactions.len(), 2);
1867                assert_eq!(block.transactions[0], coinbase);
1868                assert_eq!(block.transactions[1], pooled);
1869            }
1870            other => panic!("expected the reconstructed block, got {other:?}"),
1871        },
1872        other => panic!("expected the reconstructed block, got {other:?}"),
1873    }
1874
1875    let missing = [0xabu8; 6];
1876    let cmpct = CmpctBlockMessage {
1877        header: header.clone(),
1878        nonce,
1879        short_ids: vec![missing],
1880        prefilled_txs: vec![PrefilledTransaction {
1881            index: 0,
1882            tx: coinbase,
1883            witness: None,
1884        }],
1885    };
1886    let mut peer = PeerState::new();
1887    let asked = process_cmpctblock_message(
1888        &engine,
1889        &cmpct,
1890        &mut peer,
1891        Some(&MempoolChain(pooled.clone())),
1892        None,
1893        None,
1894        &ProtocolConfig::default(),
1895    )
1896    .unwrap();
1897    assert!(matches!(
1898        asked,
1899        NetworkResponse::SendMessage(ref message) if matches!(**message, NetworkMessage::GetBlockTxn(_))
1900    ));
1901    let wrong = BlockTxnMessage {
1902        block_hash: header_hash(&header),
1903        transactions: vec![pooled],
1904        witnesses: None,
1905    };
1906    let rejected = process_blocktxn_message(
1907        &engine,
1908        &wrong,
1909        &mut peer,
1910        None,
1911        None,
1912        &ProtocolConfig::default(),
1913    )
1914    .unwrap();
1915    match rejected {
1916        NetworkResponse::Reject(reason) => {
1917            assert!(reason.contains(&short_id_hex(&missing)), "{reason}");
1918        }
1919        other => panic!("expected a named short-id reject, got {other:?}"),
1920    }
1921}
1922
1923#[cfg(test)]
1924struct WitnessMempool {
1925    tx: Transaction,
1926    witnesses: Vec<blvm_consensus::segwit::Witness>,
1927}
1928
1929#[cfg(test)]
1930impl ChainStateAccess for WitnessMempool {
1931    fn has_object(&self, _hash: &Hash) -> bool {
1932        false
1933    }
1934    fn get_object(&self, _hash: &Hash) -> Option<ChainObject> {
1935        None
1936    }
1937    fn get_headers_for_locator(&self, _locator: &[Hash], _stop: &Hash) -> Vec<BlockHeader> {
1938        Vec::new()
1939    }
1940    fn get_mempool_transactions(&self) -> Vec<Transaction> {
1941        vec![self.tx.clone()]
1942    }
1943    fn mempool_for_compact_block(
1944        &self,
1945    ) -> Vec<(Transaction, Vec<blvm_consensus::segwit::Witness>)> {
1946        vec![(self.tx.clone(), self.witnesses.clone())]
1947    }
1948}
1949
1950#[cfg(test)]
1951#[test]
1952fn compact_block_version_two_matches_wtxid_and_keeps_the_witness() {
1953    use crate::{BitcoinProtocolEngine, ProtocolVersion};
1954    let engine = BitcoinProtocolEngine::new(ProtocolVersion::BitcoinV1).unwrap();
1955    let header = sample_header();
1956    let coinbase = sample_tx(0);
1957    let pooled = sample_tx(7);
1958    let stacks = vec![vec![vec![0x30, 0x01]]];
1959    let nonce = 11u64;
1960    let wtxid = short_id_preimage(&pooled, &stacks, 2);
1961    let txid = short_id_preimage(&pooled, &stacks, 1);
1962    assert_ne!(wtxid, txid);
1963    let short = crate::bip152::short_tx_id(&header, &wtxid, nonce);
1964    let cmpct = CmpctBlockMessage {
1965        header: header.clone(),
1966        nonce,
1967        short_ids: vec![short],
1968        prefilled_txs: vec![PrefilledTransaction {
1969            index: 0,
1970            tx: coinbase,
1971            witness: None,
1972        }],
1973    };
1974    let mut peer = PeerState::new();
1975    peer.compact_block_version = Some(2);
1976    let response = process_cmpctblock_message(
1977        &engine,
1978        &cmpct,
1979        &mut peer,
1980        Some(&WitnessMempool {
1981            tx: pooled.clone(),
1982            witnesses: stacks.clone(),
1983        }),
1984        None,
1985        None,
1986        &ProtocolConfig::default(),
1987    )
1988    .unwrap();
1989    match response {
1990        NetworkResponse::SendMessage(message) => match *message {
1991            NetworkMessage::Block(block, witnesses) => {
1992                assert_eq!(block.transactions.len(), 2);
1993                assert_eq!(block.transactions[1], pooled);
1994                assert_eq!(witnesses.len(), 2);
1995                assert!(witnesses[0].is_empty());
1996                assert_eq!(witnesses[1], stacks);
1997            }
1998            other => panic!("expected the reconstructed block, got {other:?}"),
1999        },
2000        other => panic!("expected the reconstructed block, got {other:?}"),
2001    }
2002
2003    let txid_short = crate::bip152::short_tx_id(&header, &txid, nonce);
2004    let cmpct = CmpctBlockMessage {
2005        header,
2006        nonce,
2007        short_ids: vec![txid_short],
2008        prefilled_txs: vec![PrefilledTransaction {
2009            index: 0,
2010            tx: sample_tx(0),
2011            witness: None,
2012        }],
2013    };
2014    let mut peer = PeerState::new();
2015    peer.compact_block_version = Some(2);
2016    let asked = process_cmpctblock_message(
2017        &engine,
2018        &cmpct,
2019        &mut peer,
2020        Some(&WitnessMempool {
2021            tx: pooled,
2022            witnesses: stacks,
2023        }),
2024        None,
2025        None,
2026        &ProtocolConfig::default(),
2027    )
2028    .unwrap();
2029    assert!(matches!(
2030        asked,
2031        NetworkResponse::SendMessage(ref message) if matches!(**message, NetworkMessage::GetBlockTxn(_))
2032    ));
2033}
2034
2035#[cfg(test)]
2036struct BanBook {
2037    entries: std::sync::Mutex<Vec<crate::commons::BanEntry>>,
2038}
2039
2040#[cfg(test)]
2041impl ChainStateAccess for BanBook {
2042    fn has_object(&self, _hash: &Hash) -> bool {
2043        false
2044    }
2045    fn get_object(&self, _hash: &Hash) -> Option<ChainObject> {
2046        None
2047    }
2048    fn get_headers_for_locator(&self, _locator: &[Hash], _stop: &Hash) -> Vec<BlockHeader> {
2049        Vec::new()
2050    }
2051    fn get_mempool_transactions(&self) -> Vec<Transaction> {
2052        Vec::new()
2053    }
2054    fn ban_entries(&self) -> Vec<crate::commons::BanEntry> {
2055        self.entries.lock().expect("ban book").clone()
2056    }
2057    fn apply_ban(&self, entry: &crate::commons::BanEntry) -> std::result::Result<(), String> {
2058        self.entries.lock().expect("ban book").push(entry.clone());
2059        Ok(())
2060    }
2061}
2062
2063#[cfg(test)]
2064#[test]
2065fn ban_messages_list_and_apply_without_the_old_reject() {
2066    let book = BanBook {
2067        entries: std::sync::Mutex::new(Vec::new()),
2068    };
2069    let empty = process_getbanlist_message(
2070        &crate::commons::GetBanListMessage {
2071            request_id: 1,
2072            min_score: None,
2073        },
2074        Some(&book),
2075    )
2076    .unwrap();
2077    match empty {
2078        NetworkResponse::SendMessage(message) => match *message {
2079            NetworkMessage::BanList(list) => assert!(list.entries.is_empty()),
2080            other => panic!("expected an empty ban list, got {other:?}"),
2081        },
2082        other => panic!("expected an empty ban list, got {other:?}"),
2083    }
2084    let entry = crate::commons::BanEntry {
2085        ip: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 1, 2, 3, 4],
2086        score: 100,
2087        reason: 1,
2088        timestamp: 1_700_000_000,
2089        signature: None,
2090    };
2091    process_banlist_message(
2092        &crate::commons::BanListMessage {
2093            request_id: 2,
2094            entries: vec![entry.clone()],
2095            list_signature: None,
2096        },
2097        Some(&book),
2098    )
2099    .unwrap();
2100    assert_eq!(book.entries.lock().unwrap().as_slice(), &[entry]);
2101    let none = process_getbanlist_message(
2102        &crate::commons::GetBanListMessage {
2103            request_id: 3,
2104            min_score: None,
2105        },
2106        None,
2107    )
2108    .unwrap();
2109    assert!(matches!(none, NetworkResponse::SendMessage(_)));
2110}
2111
2112#[cfg(all(test, feature = "utxo-commitments"))]
2113struct UtxoBook {
2114    hash: Hash,
2115    commitment: crate::commons::UTXOCommitment,
2116    header: BlockHeader,
2117    filter: Vec<u8>,
2118}
2119
2120#[cfg(all(test, feature = "utxo-commitments"))]
2121impl ChainStateAccess for UtxoBook {
2122    fn has_object(&self, _hash: &Hash) -> bool {
2123        false
2124    }
2125    fn get_object(&self, _hash: &Hash) -> Option<ChainObject> {
2126        None
2127    }
2128    fn get_headers_for_locator(&self, _locator: &[Hash], _stop: &Hash) -> Vec<BlockHeader> {
2129        Vec::new()
2130    }
2131    fn get_mempool_transactions(&self) -> Vec<Transaction> {
2132        Vec::new()
2133    }
2134    fn stored_utxo_commitment(
2135        &self,
2136        block_hash: &Hash,
2137    ) -> std::result::Result<Option<crate::commons::UTXOCommitment>, String> {
2138        if block_hash == &self.hash {
2139            Ok(Some(self.commitment.clone()))
2140        } else {
2141            Ok(None)
2142        }
2143    }
2144    fn stored_block_filter(
2145        &self,
2146        block_hash: &Hash,
2147    ) -> std::result::Result<Option<(BlockHeader, Vec<u8>)>, String> {
2148        if block_hash == &self.hash {
2149            Ok(Some((self.header.clone(), self.filter.clone())))
2150        } else {
2151            Ok(None)
2152        }
2153    }
2154}
2155
2156#[cfg(all(test, feature = "utxo-commitments"))]
2157#[test]
2158fn utxo_and_filter_messages_use_the_stored_set() {
2159    let hash = [9u8; 32];
2160    let commitment = crate::commons::UTXOCommitment {
2161        merkle_root: [4u8; 32],
2162        total_supply: 50,
2163        utxo_count: 2,
2164        block_height: 10,
2165        block_hash: hash,
2166    };
2167    let book = UtxoBook {
2168        hash,
2169        commitment: commitment.clone(),
2170        header: sample_header(),
2171        filter: vec![0x01, 0x02, 0x03],
2172    };
2173    let got = process_getutxoset_message(
2174        &crate::commons::GetUTXOSetMessage {
2175            height: 10,
2176            block_hash: hash,
2177        },
2178        Some(&book),
2179    )
2180    .unwrap();
2181    match got {
2182        NetworkResponse::SendMessage(message) => match *message {
2183            NetworkMessage::UTXOSet(msg) => assert_eq!(msg.commitment, commitment),
2184            other => panic!("expected the stored commitment, got {other:?}"),
2185        },
2186        other => panic!("expected the stored commitment, got {other:?}"),
2187    }
2188    let missing = process_getutxoset_message(
2189        &crate::commons::GetUTXOSetMessage {
2190            height: 11,
2191            block_hash: [1u8; 32],
2192        },
2193        Some(&book),
2194    );
2195    let err = missing.expect_err("a missing set is an error");
2196    assert!(!err.to_string().contains("not implemented"));
2197
2198    let matched = process_utxoset_message(
2199        &crate::commons::UTXOSetMessage {
2200            request_id: 1,
2201            commitment: commitment.clone(),
2202            utxo_count: 2,
2203            is_complete: true,
2204            chunk_id: None,
2205        },
2206        Some(&book),
2207    )
2208    .unwrap();
2209    assert!(matches!(matched, NetworkResponse::Ok));
2210    let mut other_commitment = commitment.clone();
2211    other_commitment.total_supply = 1;
2212    let rejected = process_utxoset_message(
2213        &crate::commons::UTXOSetMessage {
2214            request_id: 1,
2215            commitment: other_commitment,
2216            utxo_count: 2,
2217            is_complete: true,
2218            chunk_id: None,
2219        },
2220        Some(&book),
2221    )
2222    .unwrap();
2223    assert!(matches!(rejected, NetworkResponse::Reject(_)));
2224
2225    let filter_msg = process_getfilteredblock_message(
2226        &crate::commons::GetFilteredBlockMessage {
2227            request_id: 7,
2228            block_hash: hash,
2229            filter_preferences: crate::commons::FilterPreferences {
2230                filter_ordinals: false,
2231                filter_dust: false,
2232                filter_brc20: false,
2233                min_output_value: 0,
2234            },
2235            include_bip158_filter: true,
2236        },
2237        Some(&book),
2238    )
2239    .unwrap();
2240    let sent_filter = match filter_msg {
2241        NetworkResponse::SendMessage(message) => match *message {
2242            NetworkMessage::FilteredBlock(msg) => msg.bip158_filter.expect("filter bytes"),
2243            other => panic!("expected a filter, got {other:?}"),
2244        },
2245        other => panic!("expected a filter, got {other:?}"),
2246    };
2247    assert_eq!(sent_filter, vec![0x01, 0x02, 0x03]);
2248
2249    let header = sample_header();
2250    let stored_hash = header_hash(&header);
2251    let book = UtxoBook {
2252        hash: stored_hash,
2253        commitment,
2254        header: header.clone(),
2255        filter: vec![0x01, 0x02, 0x03],
2256    };
2257    let ok = process_filteredblock_message(
2258        &crate::commons::FilteredBlockMessage {
2259            request_id: 7,
2260            header: header.clone(),
2261            commitment: book.commitment.clone(),
2262            transactions: Vec::new(),
2263            transaction_indices: Vec::new(),
2264            spam_summary: crate::commons::SpamSummary {
2265                filtered_count: 0,
2266                filtered_value: 0,
2267                filter_reasons: 0,
2268            },
2269            bip158_filter: Some(vec![0x01, 0x02, 0x03]),
2270        },
2271        Some(&book),
2272    )
2273    .unwrap();
2274    assert!(matches!(ok, NetworkResponse::Ok));
2275    let bad = process_filteredblock_message(
2276        &crate::commons::FilteredBlockMessage {
2277            request_id: 7,
2278            header,
2279            commitment: book.commitment.clone(),
2280            transactions: Vec::new(),
2281            transaction_indices: Vec::new(),
2282            spam_summary: crate::commons::SpamSummary {
2283                filtered_count: 0,
2284                filtered_value: 0,
2285                filter_reasons: 0,
2286            },
2287            bip158_filter: Some(vec![0xff]),
2288        },
2289        Some(&book),
2290    )
2291    .unwrap();
2292    assert!(matches!(bad, NetworkResponse::Reject(_)));
2293}