Skip to main content

blvm_protocol/
node_tcp.rs

1//! Bitcoin **TCP v1** framed messages: standard header (magic, command, length, checksum) plus payload.
2//!
3//! This is the full [`ProtocolMessage`] union and [`TcpFramedParser`] (parse/serialize) used by the
4//! full node (`blvm-node`). **BIP324** v2 encrypted transport is the separate `v2_transport`
5//! module (enable `feature = "bip324"`), not this stack.
6//!
7//! The embedding binary supplies which command strings are accepted (e.g. node `ALLOWED_COMMANDS`).
8
9#[cfg(feature = "protocol-verification")]
10use blvm_spec_lock::spec_locked;
11
12use crate::wire::{
13    deserialize_addrv2, deserialize_cmpctblock, deserialize_feefilter, deserialize_getblocks,
14    deserialize_getdata, deserialize_getheaders, deserialize_headers, deserialize_inv,
15    deserialize_notfound, deserialize_reject, deserialize_sendcmpct, deserialize_tx,
16    serialize_addrv2, serialize_cmpctblock, serialize_feefilter, serialize_getblocks,
17    serialize_getdata, serialize_getheaders, serialize_inv, serialize_notfound, serialize_reject,
18    serialize_sendcmpct, serialize_tx,
19};
20use crate::{BlockHeader, Hash, Transaction};
21use anyhow::Result;
22use serde::{Deserialize, Serialize};
23
24pub use crate::p2p_framing::{
25    BITCOIN_MAGIC_MAINNET, BITCOIN_MAGIC_REGTEST, BITCOIN_MAGIC_TESTNET,
26    BITCOIN_P2P_MAGIC_MAINNET_LE, MAX_ADDR_TO_SEND, MAX_HEADERS_RESULTS, MAX_INV_SZ,
27    MAX_PROTOCOL_MESSAGE_LENGTH,
28};
29
30pub use crate::service_flags::commons::{
31    NODE_BAN_LIST_SHARING, NODE_FIBRE, NODE_GOVERNANCE, NODE_PACKAGE_RELAY,
32};
33
34#[cfg(feature = "dandelion")]
35pub use crate::service_flags::commons::NODE_DANDELION;
36
37#[cfg(feature = "utxo-commitments")]
38pub use crate::service_flags::commons::NODE_UTXO_COMMITMENTS;
39
40#[cfg(feature = "erlay")]
41pub use crate::service_flags::commons::NODE_ERLAY;
42
43pub use crate::p2p_commands::cmd;
44
45/// Bitcoin protocol message types
46#[derive(Debug, Clone, Serialize, Deserialize)]
47pub enum ProtocolMessage {
48    Version(VersionMessage),
49    Verack,
50    Ping(PingMessage),
51    Pong(PongMessage),
52    GetHeaders(GetHeadersMessage),
53    Headers(HeadersMessage),
54    GetBlocks(GetBlocksMessage),
55    Block(BlockMessage),
56    GetData(GetDataMessage),
57    Inv(InvMessage),
58    NotFound(NotFoundMessage),
59    /// BIP61 reject (informational)
60    Reject(RejectMessage),
61    Tx(TxMessage),
62    /// BIP133 FeeFilter - peer's minimum fee rate for tx relay (we accept, no response)
63    FeeFilter(FeeFilterMessage),
64    /// Request for mempool tx announcements (empty payload)
65    MemPool,
66    /// BIP130 — prefer `headers` for block announcements (empty payload)
67    SendHeaders,
68    // Compact Block Relay (BIP152)
69    SendCmpct(SendCmpctMessage),
70    CmpctBlock(CompactBlockMessage),
71    GetBlockTxn(GetBlockTxnMessage),
72    BlockTxn(BlockTxnMessage),
73    // UTXO commitment protocol extensions
74    GetUTXOSet(GetUTXOSetMessage),
75    UTXOSet(UTXOSetMessage),
76    GetUTXOProof(GetUTXOProofMessage),
77    UTXOProof(UTXOProofMessage),
78    GetFilteredBlock(GetFilteredBlockMessage),
79    FilteredBlock(FilteredBlockMessage),
80    // Block Filtering (BIP157)
81    GetCfilters(GetCfiltersMessage),
82    Cfilter(CfilterMessage),
83    GetCfheaders(GetCfheadersMessage),
84    Cfheaders(CfheadersMessage),
85    GetCfcheckpt(GetCfcheckptMessage),
86    Cfcheckpt(CfcheckptMessage),
87    // Payment Protocol (BIP70) - P2P variant
88    GetPaymentRequest(GetPaymentRequestMessage),
89    PaymentRequest(PaymentRequestMessage),
90    Payment(PaymentMessage),
91    PaymentACK(PaymentACKMessage),
92    // CTV Payment Proof messages (for instant proof)
93    #[cfg(feature = "ctv")]
94    PaymentProof(PaymentProofMessage),
95    SettlementNotification(SettlementNotificationMessage),
96    // Package Relay (BIP 331)
97    SendPkgTxn(SendPkgTxnMessage),
98    PkgTxn(PkgTxnMessage),
99    PkgTxnReject(PkgTxnRejectMessage),
100    // Ban List Sharing
101    GetBanList(GetBanListMessage),
102    BanList(BanListMessage),
103    // Mesh networking packets (payment-gated routing)
104    MeshPacket(Vec<u8>), // Serialized mesh packet (handled by mesh module)
105    // Address relay
106    GetAddr,
107    Addr(AddrMessage),
108    /// BIP155 extended addresses
109    AddrV2(AddrV2Message),
110    // Module Registry
111    GetModule(GetModuleMessage),
112    Module(ModuleMessage),
113    GetModuleByHash(GetModuleByHashMessage),
114    ModuleByHash(ModuleByHashMessage),
115    ModuleInv(ModuleInvMessage),
116    GetModuleList(GetModuleListMessage),
117    ModuleList(ModuleListMessage),
118    /// Erlay (BIP330) — enable with `feature = "erlay"` on `blvm-protocol`.
119    #[cfg(feature = "erlay")]
120    SendTxRcncl(SendTxRcnclMessage),
121    #[cfg(feature = "erlay")]
122    ReqRecon(ReqReconMessage),
123    #[cfg(feature = "erlay")]
124    ReqSkt(ReqSktMessage),
125    #[cfg(feature = "erlay")]
126    Sketch(SketchMessage),
127}
128
129pub use crate::network::NetworkAddress;
130
131// Pull shared types from blvm-protocol (single source of truth — field-identical to the
132// former local definitions, so bincode wire format is preserved verbatim).
133pub use crate::{
134    BlockMessage, CompactBlockMessage, FilterPreferences, GetFilteredBlockMessage,
135    GetUTXOProofMessage, GetUTXOSetMessage, TxMessage, UTXOCommitment, UTXOProofMessage,
136    UTXOSetMessage,
137};
138
139/// Version message
140#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
141pub struct VersionMessage {
142    pub version: i32,
143    pub services: u64,
144    pub timestamp: i64,
145    pub addr_recv: NetworkAddress,
146    pub addr_from: NetworkAddress,
147    pub nonce: u64,
148    pub user_agent: String,
149    pub start_height: i32,
150    pub relay: bool,
151}
152
153impl VersionMessage {
154    /// Check if peer supports UTXO commitments
155    #[cfg(feature = "utxo-commitments")]
156    pub fn supports_utxo_commitments(&self) -> bool {
157        (self.services & NODE_UTXO_COMMITMENTS) != 0
158    }
159
160    /// Check if peer supports ban list sharing
161    pub fn supports_ban_list_sharing(&self) -> bool {
162        (self.services & NODE_BAN_LIST_SHARING) != 0
163    }
164
165    /// Check if peer supports BIP157 compact block filters
166    pub fn supports_compact_filters(&self) -> bool {
167        use crate::bip157::NODE_COMPACT_FILTERS;
168        (self.services & NODE_COMPACT_FILTERS) != 0
169    }
170
171    /// Check if peer supports package relay (BIP331)
172    pub fn supports_package_relay(&self) -> bool {
173        (self.services & NODE_PACKAGE_RELAY) != 0
174    }
175
176    /// Check if peer supports FIBRE
177    pub fn supports_fibre(&self) -> bool {
178        (self.services & NODE_FIBRE) != 0
179    }
180
181    #[cfg(feature = "dandelion")]
182    /// Check if peer supports Dandelion
183    pub fn supports_dandelion(&self) -> bool {
184        (self.services & NODE_DANDELION) != 0
185    }
186}
187
188// Re-export inventory and shared P2P payload types from blvm-protocol (single source of truth)
189pub use crate::network::{
190    AddrMessage, AddrV2Message, BlockTxnMessage, FeeFilterMessage, GetBlockTxnMessage,
191    GetBlocksMessage, GetDataMessage, GetHeadersMessage, HeadersMessage, InvMessage,
192    InventoryVector, NotFoundMessage, PingMessage, PongMessage, RejectMessage, SendCmpctMessage,
193};
194
195/// FilteredBlock message - Response with filtered transactions
196#[derive(Debug, Clone, Serialize, Deserialize)]
197pub struct FilteredBlockMessage {
198    /// Request ID (echo from GetFilteredBlock for matching)
199    pub request_id: u64,
200    /// Block header
201    pub header: BlockHeader,
202    /// UTXO commitment for this block
203    pub commitment: UTXOCommitment,
204    /// Filtered transactions (only non-spam)
205    pub transactions: Vec<Transaction>,
206    /// Transaction indices in original block (for verification)
207    pub transaction_indices: Vec<u32>,
208    /// Summary of filtered spam
209    pub spam_summary: SpamSummary,
210    /// Optional BIP158 compact block filter (if requested and available)
211    ///
212    /// This allows clients to get both spam-filtered transactions (UTXO commitments)
213    /// and BIP158 filters (light client discovery) in a single response.
214    /// When present, clients can use the filter for efficient transaction matching
215    /// while still receiving the commitment data for verification.
216    pub bip158_filter: Option<Bip158FilterData>,
217}
218
219/// BIP158 filter data (embedded in FilteredBlock message)
220#[derive(Debug, Clone, Serialize, Deserialize)]
221pub struct Bip158FilterData {
222    /// Filter type (0 = Basic)
223    pub filter_type: u8,
224    /// Compact block filter data
225    pub filter_data: Vec<u8>,
226    /// Number of elements in filter
227    pub num_elements: u32,
228}
229
230// Block Filtering (BIP157) messages
231
232/// getcfilters message - Request filters for block range
233#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
234pub struct GetCfiltersMessage {
235    /// Filter type (0 = Basic)
236    pub filter_type: u8,
237    /// Start block height
238    pub start_height: u32,
239    /// Stop block hash
240    pub stop_hash: Hash,
241}
242
243/// cfilter message - Compact block filter response
244#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
245pub struct CfilterMessage {
246    /// Filter type (0 = Basic)
247    pub filter_type: u8,
248    /// Block hash
249    pub block_hash: Hash,
250    /// Compact block filter data
251    pub filter_data: Vec<u8>,
252    /// Number of elements in filter
253    pub num_elements: u32,
254}
255
256/// getcfheaders message - Request filter headers
257#[derive(Debug, Clone, Serialize, Deserialize)]
258pub struct GetCfheadersMessage {
259    /// Filter type (0 = Basic)
260    pub filter_type: u8,
261    /// Start block height
262    pub start_height: u32,
263    /// Stop block hash
264    pub stop_hash: Hash,
265}
266
267/// cfheaders message - Filter headers response
268#[derive(Debug, Clone, Serialize, Deserialize)]
269pub struct CfheadersMessage {
270    /// Filter type (0 = Basic)
271    pub filter_type: u8,
272    /// Stop block hash
273    pub stop_hash: Hash,
274    /// Previous filter header
275    pub prev_header: FilterHeaderData,
276    /// Filter headers (one per block in range)
277    pub filter_headers: Vec<Hash>,
278}
279
280/// Filter header data (for serialization)
281#[derive(Debug, Clone, Serialize, Deserialize)]
282pub struct FilterHeaderData {
283    /// Filter hash
284    pub filter_hash: Hash,
285    /// Previous filter header hash
286    pub prev_header_hash: Hash,
287}
288
289/// getcfcheckpt message - Request filter checkpoints
290#[derive(Debug, Clone, Serialize, Deserialize)]
291pub struct GetCfcheckptMessage {
292    /// Filter type (0 = Basic)
293    pub filter_type: u8,
294    /// Stop block hash
295    pub stop_hash: Hash,
296}
297
298/// cfcheckpt message - Filter checkpoint response
299#[derive(Debug, Clone, Serialize, Deserialize)]
300pub struct CfcheckptMessage {
301    /// Filter type (0 = Basic)
302    pub filter_type: u8,
303    /// Stop block hash
304    pub stop_hash: Hash,
305    /// Filter header hashes at checkpoint intervals
306    pub filter_header_hashes: Vec<Hash>,
307}
308
309// Payment Protocol (BIP70) - P2P variant messages
310
311/// getpaymentrequest message - Request payment details from merchant
312#[derive(Debug, Clone, Serialize, Deserialize)]
313pub struct GetPaymentRequestMessage {
314    /// Merchant's Bitcoin public key (compressed, 33 bytes)
315    #[serde(with = "serde_bytes")]
316    pub merchant_pubkey: Vec<u8>,
317    /// Unique payment identifier (32-byte hash)
318    #[serde(with = "serde_bytes")]
319    pub payment_id: Vec<u8>,
320    /// Network identifier ("main", "test", "regtest")
321    pub network: String,
322}
323
324/// paymentrequest message - Merchant payment request response
325#[derive(Debug, Clone, Serialize, Deserialize)]
326pub struct PaymentRequestMessage {
327    /// Payment request details (from bip70 module)
328    pub payment_request: crate::payment::PaymentRequest,
329    /// Signature over payment_request by merchant's Bitcoin key
330    #[serde(with = "serde_bytes")]
331    pub merchant_signature: Vec<u8>,
332    /// Merchant's public key (compressed, 33 bytes)
333    #[serde(with = "serde_bytes")]
334    pub merchant_pubkey: Vec<u8>,
335    /// Payment ID (echo from GetPaymentRequest)
336    #[serde(with = "serde_bytes")]
337    pub payment_id: Vec<u8>,
338    /// Optional CTV covenant proof (for instant proof)
339    #[cfg(feature = "ctv")]
340    #[serde(default)]
341    pub covenant_proof: Option<crate::payment::CovenantProof>,
342}
343
344/// payment message - Customer payment transaction(s)
345#[derive(Debug, Clone, Serialize, Deserialize)]
346pub struct PaymentMessage {
347    /// Payment details (from payment protocol module)
348    pub payment: crate::payment::Payment,
349    /// Payment ID (echo from PaymentRequest)
350    #[serde(with = "serde_bytes")]
351    pub payment_id: Vec<u8>,
352    /// Optional customer signature (for authenticated payments)
353    #[serde(with = "serde_bytes")]
354    pub customer_signature: Option<Vec<u8>>,
355}
356
357/// paymentack message - Merchant payment confirmation
358#[derive(Debug, Clone, Serialize, Deserialize)]
359pub struct PaymentACKMessage {
360    /// Payment acknowledgment (from payment protocol module)
361    pub payment_ack: crate::payment::PaymentACK,
362    /// Payment ID (echo from Payment)
363    #[serde(with = "serde_bytes")]
364    pub payment_id: Vec<u8>,
365    /// Merchant signature confirming receipt
366    #[serde(with = "serde_bytes")]
367    pub merchant_signature: Vec<u8>,
368}
369
370// CTV Payment Proof messages (for instant proof, not instant settlement)
371
372/// paymentproof message - CTV covenant proof for payment commitment
373#[cfg(feature = "ctv")]
374#[derive(Debug, Clone, Serialize, Deserialize)]
375pub struct PaymentProofMessage {
376    /// Request ID for async request-response matching
377    pub request_id: u64,
378    /// Payment request ID this proof commits to
379    pub payment_request_id: String,
380    /// CTV covenant proof
381    pub covenant_proof: crate::payment::CovenantProof,
382    /// Optional full transaction template (for verification)
383    pub transaction_template: Option<crate::payment::TransactionTemplate>,
384}
385
386/// settlementnotification message - Settlement status update
387#[derive(Debug, Clone, Serialize, Deserialize)]
388pub struct SettlementNotificationMessage {
389    /// Payment request ID
390    pub payment_request_id: String,
391    /// Transaction hash (if in mempool or confirmed)
392    pub transaction_hash: Option<Hash>,
393    /// Confirmation count (0 = in mempool, >0 = confirmed)
394    pub confirmation_count: u32,
395    /// Block hash (if confirmed)
396    pub block_hash: Option<Hash>,
397    /// Settlement status
398    pub status: String, // "mempool", "confirmed", "failed"
399}
400
401// Package Relay (BIP 331) messages
402
403/// sendpkgtxn message - Request to send package of transactions
404#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
405pub struct SendPkgTxnMessage {
406    /// Package ID (combined hash of all transactions)
407    #[serde(with = "serde_bytes")]
408    pub package_id: Vec<u8>,
409    /// Transaction hashes in package (ordered: parents first)
410    pub tx_hashes: Vec<Hash>,
411}
412
413/// pkgtxn message - Package of transactions
414#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
415pub struct PkgTxnMessage {
416    /// Package ID (echo from SendPkgTxn)
417    #[serde(with = "serde_bytes")]
418    pub package_id: Vec<u8>,
419    /// Transactions in package (ordered: parents first)
420    /// Using Vec<u8> for serialized transactions (matches BIP 331 spec)
421    pub transactions: Vec<Vec<u8>>,
422}
423
424/// pkgtxnreject message - Package rejection
425#[derive(Debug, Clone, Serialize, Deserialize)]
426pub struct PkgTxnRejectMessage {
427    /// Package ID that was rejected
428    #[serde(with = "serde_bytes")]
429    pub package_id: Vec<u8>,
430    /// Rejection reason code
431    pub reason: u8,
432    /// Optional rejection reason text
433    pub reason_text: Option<String>,
434}
435
436// Module Registry messages
437
438/// getmodule message - Request module by name
439#[derive(Debug, Clone, Serialize, Deserialize)]
440pub struct GetModuleMessage {
441    /// Request ID for async request-response matching
442    pub request_id: u64,
443    /// Module name
444    pub name: String,
445    /// Optional version (if not specified, get latest)
446    pub version: Option<String>,
447    /// Optional payment ID (required if module requires payment)
448    /// This is the payment_id from a completed PaymentACK
449    pub payment_id: Option<String>,
450}
451
452/// module message - Module response
453#[derive(Debug, Clone, Serialize, Deserialize)]
454pub struct ModuleMessage {
455    /// Request ID (echo from GetModule for matching)
456    pub request_id: u64,
457    /// Module name
458    pub name: String,
459    /// Module version
460    pub version: String,
461    /// Module hash (content-addressable identifier)
462    pub hash: Hash,
463    /// Manifest hash
464    pub manifest_hash: Hash,
465    /// Binary hash
466    pub binary_hash: Hash,
467    /// Manifest content (TOML)
468    pub manifest: Vec<u8>,
469    /// Binary content (optional - may be fetched separately via getmodulebyhash)
470    pub binary: Option<Vec<u8>>,
471}
472
473/// getmodulebyhash message - Request module by hash (content-addressable)
474#[derive(Debug, Clone, Serialize, Deserialize)]
475pub struct GetModuleByHashMessage {
476    /// Request ID for async request-response matching
477    pub request_id: u64,
478    /// Module hash
479    pub hash: Hash,
480    /// Request binary (if false, only manifest is returned)
481    pub include_binary: bool,
482}
483
484/// modulebyhash message - Module response by hash
485#[derive(Debug, Clone, Serialize, Deserialize)]
486pub struct ModuleByHashMessage {
487    /// Request ID (echo from GetModuleByHash for matching)
488    pub request_id: u64,
489    /// Module hash (echo from request)
490    pub hash: Hash,
491    /// Manifest content
492    pub manifest: Vec<u8>,
493    /// Binary content (if requested)
494    pub binary: Option<Vec<u8>>,
495}
496
497/// moduleinv message - Module inventory (announce available modules)
498#[derive(Debug, Clone, Serialize, Deserialize)]
499pub struct ModuleInvMessage {
500    /// List of available modules
501    pub modules: Vec<ModuleInventoryItem>,
502}
503
504/// Module inventory item
505#[derive(Debug, Clone, Serialize, Deserialize)]
506pub struct ModuleInventoryItem {
507    /// Module name
508    pub name: String,
509    /// Module version
510    pub version: String,
511    /// Module hash
512    pub hash: Hash,
513}
514
515/// getmodulelist message - Request list of available modules
516#[derive(Debug, Clone, Serialize, Deserialize)]
517pub struct GetModuleListMessage {
518    /// Optional filter by name prefix
519    pub name_prefix: Option<String>,
520    /// Maximum number of modules to return
521    pub max_count: Option<u32>,
522}
523
524/// modulelist message - List of available modules
525#[derive(Debug, Clone, Serialize, Deserialize)]
526pub struct ModuleListMessage {
527    /// List of available modules
528    pub modules: Vec<ModuleInventoryItem>,
529}
530
531// Erlay (BIP330) transaction relay messages
532
533/// sendtxrcncl message - Announce Erlay support and negotiate parameters
534#[cfg(feature = "erlay")]
535#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
536pub struct SendTxRcnclMessage {
537    /// Erlay version (currently 1)
538    pub version: u16,
539    /// Initial reconciliation salt (for privacy)
540    #[serde(with = "serde_bytes")]
541    pub salt: [u8; 16],
542    /// Minimum field size in bits (32 or 64)
543    pub min_field_size: u8,
544    /// Maximum field size in bits (32 or 64)
545    pub max_field_size: u8,
546}
547
548/// reqrecon message - Request reconciliation
549#[cfg(feature = "erlay")]
550#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
551pub struct ReqReconMessage {
552    /// Reconciliation salt (for privacy)
553    #[serde(with = "serde_bytes")]
554    pub salt: [u8; 16],
555    /// Local transaction set size
556    pub local_set_size: u32,
557    /// Field size in bits (32 or 64)
558    pub field_size: u8,
559}
560
561/// reqskt message - Request sketch
562#[cfg(feature = "erlay")]
563#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
564pub struct ReqSktMessage {
565    /// Reconciliation salt (echo from ReqRecon)
566    #[serde(with = "serde_bytes")]
567    pub salt: [u8; 16],
568    /// Remote transaction set size
569    pub remote_set_size: u32,
570    /// Field size in bits (32 or 64)
571    pub field_size: u8,
572}
573
574/// sketch message - Send reconciliation sketch
575#[cfg(feature = "erlay")]
576#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
577pub struct SketchMessage {
578    /// Reconciliation salt (echo from ReqRecon)
579    #[serde(with = "serde_bytes")]
580    pub salt: [u8; 16],
581    /// Reconciliation sketch (minisketch serialized data)
582    #[serde(with = "serde_bytes")]
583    pub sketch: Vec<u8>,
584    /// Field size in bits (32 or 64)
585    pub field_size: u8,
586}
587
588/// SpamSummary - Summary of filtered spam transactions
589#[derive(Debug, Clone, Serialize, Deserialize)]
590pub struct SpamSummary {
591    /// Number of transactions filtered
592    pub filtered_count: u32,
593    /// Total size of filtered transactions (bytes)
594    pub filtered_size: u64,
595    /// Breakdown by spam type
596    pub by_type: SpamBreakdown,
597}
598
599/// SpamBreakdown - Breakdown of spam by category
600#[derive(Debug, Clone, Serialize, Deserialize)]
601pub struct SpamBreakdown {
602    pub ordinals: u32,
603    pub inscriptions: u32,
604    pub dust: u32,
605    pub brc20: u32,
606}
607
608/// Bitcoin P2P framed message parser (TCP path). Pass allowed commands from the node.
609pub struct TcpFramedParser;
610
611impl TcpFramedParser {
612    /// Parse a raw message into a protocol message
613    /// Orange Paper 10.1.1: ParseMessage, size bounds, checksum rejection
614    #[cfg_attr(feature = "protocol-verification", spec_locked("10.1.1"))]
615    pub fn parse_message(data: &[u8], allowed_commands: &[&str]) -> Result<ProtocolMessage> {
616        use tracing::{debug, warn};
617
618        if data.len() >= 4 {
619            let magic = u32::from_le_bytes([data[0], data[1], data[2], data[3]]);
620            debug!(
621                "Parsing message: magic=0x{:08x}, total_len={}",
622                magic,
623                data.len()
624            );
625            if magic != BITCOIN_P2P_MAGIC_MAINNET_LE {
626                let header_hex: String = data.iter().take(24).map(|b| format!("{b:02x}")).collect();
627                warn!(
628                    "Invalid magic number 0x{:08x}, expected 0x{:08x}. Header hex: {}",
629                    magic, BITCOIN_P2P_MAGIC_MAINNET_LE, header_hex
630                );
631            }
632        }
633
634        let (command, payload) =
635            crate::p2p_frame::parse_p2p_frame(data, BITCOIN_P2P_MAGIC_MAINNET_LE, |c| {
636                allowed_commands.contains(&c)
637            })
638            .map_err(|e| anyhow::anyhow!("{}", e))?;
639
640        debug!("Message command: '{}', data_len={}", command, data.len());
641
642        // Parse payload based on command
643        match command {
644            cmd::VERSION => {
645                // Use proper Bitcoin wire format deserialization for version messages
646                use crate::wire::deserialize_version;
647
648                let version_msg = deserialize_version(payload)?;
649
650                Ok(ProtocolMessage::Version(VersionMessage {
651                    version: version_msg.version as i32, // blvm-node uses i32, blvm-protocol uses u32
652                    services: version_msg.services,
653                    timestamp: version_msg.timestamp,
654                    addr_recv: version_msg.addr_recv,
655                    addr_from: version_msg.addr_from,
656                    nonce: version_msg.nonce,
657                    user_agent: version_msg.user_agent,
658                    start_height: version_msg.start_height,
659                    relay: version_msg.relay,
660                }))
661            }
662            cmd::VERACK => Ok(ProtocolMessage::Verack),
663            cmd::SENDHEADERS => Ok(ProtocolMessage::SendHeaders),
664            cmd::PING => {
665                // Use proper Bitcoin wire format (8-byte nonce)
666                let wire_msg = crate::wire::deserialize_ping(payload)
667                    .map_err(|e| anyhow::anyhow!("Failed to deserialize ping: {}", e))?;
668                Ok(ProtocolMessage::Ping(PingMessage {
669                    nonce: wire_msg.nonce,
670                }))
671            }
672            cmd::PONG => {
673                // Use proper Bitcoin wire format (8-byte nonce)
674                let wire_msg = crate::wire::deserialize_pong(payload)
675                    .map_err(|e| anyhow::anyhow!("Failed to deserialize pong: {}", e))?;
676                Ok(ProtocolMessage::Pong(PongMessage {
677                    nonce: wire_msg.nonce,
678                }))
679            }
680            cmd::GETHEADERS => {
681                let wire_msg = deserialize_getheaders(payload)
682                    .map_err(|e| anyhow::anyhow!("Failed to deserialize getheaders: {}", e))?;
683                Ok(ProtocolMessage::GetHeaders(wire_msg))
684            }
685            cmd::HEADERS => {
686                let wire_msg = deserialize_headers(payload)
687                    .map_err(|e| anyhow::anyhow!("Failed to deserialize headers: {}", e))?;
688                Ok(ProtocolMessage::Headers(wire_msg))
689            }
690            cmd::GETBLOCKS => {
691                let wire_msg = deserialize_getblocks(payload)
692                    .map_err(|e| anyhow::anyhow!("Failed to deserialize getblocks: {}", e))?;
693                Ok(ProtocolMessage::GetBlocks(wire_msg))
694            }
695            cmd::BLOCK => {
696                // Use consensus wire format (Bitcoin block + witness structure)
697                let (block, witnesses) =
698                    crate::serialization::deserialize_block_with_witnesses(payload)
699                        .map_err(|e| anyhow::anyhow!("Failed to deserialize block: {}", e))?;
700                Ok(ProtocolMessage::Block(BlockMessage { block, witnesses }))
701            }
702            cmd::GETDATA => {
703                let msg = deserialize_getdata(payload)
704                    .map_err(|e| anyhow::anyhow!("Failed to deserialize getdata: {}", e))?;
705                Ok(ProtocolMessage::GetData(msg))
706            }
707            cmd::INV => {
708                let msg = deserialize_inv(payload)
709                    .map_err(|e| anyhow::anyhow!("Failed to deserialize inv: {}", e))?;
710                Ok(ProtocolMessage::Inv(msg))
711            }
712            cmd::NOTFOUND => {
713                let msg = deserialize_notfound(payload)
714                    .map_err(|e| anyhow::anyhow!("Failed to deserialize notfound: {}", e))?;
715                Ok(ProtocolMessage::NotFound(msg))
716            }
717            cmd::REJECT => {
718                let msg = deserialize_reject(payload)
719                    .map_err(|e| anyhow::anyhow!("Failed to deserialize reject: {}", e))?;
720                Ok(ProtocolMessage::Reject(msg))
721            }
722            cmd::TX => {
723                let transaction = deserialize_tx(payload)
724                    .map_err(|e| anyhow::anyhow!("Failed to deserialize tx: {}", e))?;
725                Ok(ProtocolMessage::Tx(TxMessage { transaction }))
726            }
727            cmd::MEMPOOL => Ok(ProtocolMessage::MemPool),
728            cmd::FEEFILTER => {
729                let msg = deserialize_feefilter(payload)
730                    .map_err(|e| anyhow::anyhow!("Failed to deserialize feefilter: {}", e))?;
731                Ok(ProtocolMessage::FeeFilter(msg))
732            }
733            // Compact Block Relay (BIP152)
734            cmd::SENDCMPCT => {
735                let msg = deserialize_sendcmpct(payload)
736                    .map_err(|e| anyhow::anyhow!("Failed to deserialize sendcmpct: {}", e))?;
737                Ok(ProtocolMessage::SendCmpct(msg))
738            }
739            cmd::CMPCTBLOCK => {
740                let wire_msg = deserialize_cmpctblock(payload)
741                    .map_err(|e| anyhow::anyhow!("Failed to deserialize cmpctblock: {}", e))?;
742                let compact_block = crate::bip152::CompactBlock::from(&wire_msg);
743                Ok(ProtocolMessage::CmpctBlock(CompactBlockMessage {
744                    compact_block,
745                }))
746            }
747            cmd::GETBLOCKTXN => {
748                let wire_msg = crate::wire::deserialize_getblocktxn(payload)
749                    .map_err(|e| anyhow::anyhow!("Failed to deserialize getblocktxn: {}", e))?;
750                Ok(ProtocolMessage::GetBlockTxn(wire_msg))
751            }
752            cmd::BLOCKTXN => {
753                let wire_msg = crate::wire::deserialize_blocktxn(payload)
754                    .map_err(|e| anyhow::anyhow!("Failed to deserialize blocktxn: {}", e))?;
755                Ok(ProtocolMessage::BlockTxn(wire_msg))
756            }
757            // UTXO commitment protocol extensions
758            cmd::GETUTXOSET => Ok(ProtocolMessage::GetUTXOSet(bincode::deserialize(payload)?)),
759            cmd::UTXOSET => Ok(ProtocolMessage::UTXOSet(bincode::deserialize(payload)?)),
760            cmd::GETUTXOPROOF => Ok(ProtocolMessage::GetUTXOProof(bincode::deserialize(
761                payload,
762            )?)),
763            cmd::UTXOPROOF => Ok(ProtocolMessage::UTXOProof(bincode::deserialize(payload)?)),
764            cmd::GETFILTEREDBLOCK => Ok(ProtocolMessage::GetFilteredBlock(bincode::deserialize(
765                payload,
766            )?)),
767            cmd::FILTEREDBLOCK => Ok(ProtocolMessage::FilteredBlock(bincode::deserialize(
768                payload,
769            )?)),
770            // Block Filtering (BIP157)
771            cmd::GETCFILTERS => Ok(ProtocolMessage::GetCfilters(bincode::deserialize(payload)?)),
772            cmd::CFILTER => Ok(ProtocolMessage::Cfilter(bincode::deserialize(payload)?)),
773            cmd::GETCFHEADERS => Ok(ProtocolMessage::GetCfheaders(bincode::deserialize(
774                payload,
775            )?)),
776            cmd::CFHEADERS => Ok(ProtocolMessage::Cfheaders(bincode::deserialize(payload)?)),
777            cmd::GETCFCHECKPT => Ok(ProtocolMessage::GetCfcheckpt(bincode::deserialize(
778                payload,
779            )?)),
780            cmd::CFCHECKPT => Ok(ProtocolMessage::Cfcheckpt(bincode::deserialize(payload)?)),
781            // Payment Protocol (BIP70) - P2P variant
782            cmd::GETPAYMENTREQUEST => Ok(ProtocolMessage::GetPaymentRequest(bincode::deserialize(
783                payload,
784            )?)),
785            cmd::PAYMENTREQUEST => Ok(ProtocolMessage::PaymentRequest(bincode::deserialize(
786                payload,
787            )?)),
788            cmd::PAYMENT => Ok(ProtocolMessage::Payment(bincode::deserialize(payload)?)),
789            cmd::PAYMENTACK => Ok(ProtocolMessage::PaymentACK(bincode::deserialize(payload)?)),
790            #[cfg(feature = "ctv")]
791            cmd::PAYMENTPROOF => Ok(ProtocolMessage::PaymentProof(bincode::deserialize(
792                payload,
793            )?)),
794            cmd::SETTLEMENTNOTIFICATION => Ok(ProtocolMessage::SettlementNotification(
795                bincode::deserialize(payload)?,
796            )),
797            // Package Relay (BIP 331)
798            cmd::SENDPKGTXN => Ok(ProtocolMessage::SendPkgTxn(bincode::deserialize(payload)?)),
799            cmd::PKGTXN => Ok(ProtocolMessage::PkgTxn(bincode::deserialize(payload)?)),
800            cmd::PKGTXNREJECT => Ok(ProtocolMessage::PkgTxnReject(bincode::deserialize(
801                payload,
802            )?)),
803            // Ban List Sharing
804            cmd::GETBANLIST => Ok(ProtocolMessage::GetBanList(bincode::deserialize(payload)?)),
805            cmd::BANLIST => Ok(ProtocolMessage::BanList(bincode::deserialize(payload)?)),
806            cmd::GETADDR => Ok(ProtocolMessage::GetAddr),
807            cmd::ADDR => {
808                let wire_msg = crate::wire::deserialize_addr(payload)
809                    .map_err(|e| anyhow::anyhow!("Failed to deserialize addr: {}", e))?;
810                Ok(ProtocolMessage::Addr(wire_msg))
811            }
812            cmd::ADDRV2 => {
813                let wire_msg = deserialize_addrv2(payload)
814                    .map_err(|e| anyhow::anyhow!("Failed to deserialize addrv2: {}", e))?;
815                Ok(ProtocolMessage::AddrV2(wire_msg))
816            }
817            // Module Registry
818            cmd::GETMODULE => Ok(ProtocolMessage::GetModule(bincode::deserialize(payload)?)),
819            cmd::MODULE => Ok(ProtocolMessage::Module(bincode::deserialize(payload)?)),
820            cmd::GETMODULEBYHASH => Ok(ProtocolMessage::GetModuleByHash(bincode::deserialize(
821                payload,
822            )?)),
823            cmd::MODULEBYHASH => Ok(ProtocolMessage::ModuleByHash(bincode::deserialize(
824                payload,
825            )?)),
826            cmd::MODULEINV => Ok(ProtocolMessage::ModuleInv(bincode::deserialize(payload)?)),
827            cmd::GETMODULELIST => Ok(ProtocolMessage::GetModuleList(bincode::deserialize(
828                payload,
829            )?)),
830            cmd::MODULELIST => Ok(ProtocolMessage::ModuleList(bincode::deserialize(payload)?)),
831            // Mesh networking packets
832            cmd::MESH => Ok(ProtocolMessage::MeshPacket(payload.to_vec())),
833            #[cfg(feature = "erlay")]
834            cmd::SENDTXRCNCL => Ok(ProtocolMessage::SendTxRcncl(bincode::deserialize(payload)?)),
835            #[cfg(feature = "erlay")]
836            cmd::REQRECON => Ok(ProtocolMessage::ReqRecon(bincode::deserialize(payload)?)),
837            #[cfg(feature = "erlay")]
838            cmd::REQSKT => Ok(ProtocolMessage::ReqSkt(bincode::deserialize(payload)?)),
839            #[cfg(feature = "erlay")]
840            cmd::SKETCH => Ok(ProtocolMessage::Sketch(bincode::deserialize(payload)?)),
841            _ => Err(anyhow::anyhow!("Unknown command: {}", command)),
842        }
843    }
844
845    /// Serialize a protocol message to bytes
846    pub fn serialize_message(message: &ProtocolMessage) -> Result<Vec<u8>> {
847        let (command, payload) = match message {
848            ProtocolMessage::Version(msg) => {
849                // Use proper Bitcoin wire format for version messages
850                use crate::network::VersionMessage as WireVersionMessage;
851                use crate::wire::serialize_version;
852
853                let version_msg = WireVersionMessage {
854                    version: msg.version as u32,
855                    services: msg.services,
856                    timestamp: msg.timestamp,
857                    addr_recv: msg.addr_recv.clone(),
858                    addr_from: msg.addr_from.clone(),
859                    nonce: msg.nonce,
860                    user_agent: msg.user_agent.clone(),
861                    start_height: msg.start_height,
862                    relay: msg.relay,
863                };
864
865                // Serialize payload using proper Bitcoin wire format
866                // This uses the serialize_version function from blvm-protocol wire.rs
867                // which implements the exact Bitcoin protocol format
868                let payload = serialize_version(&version_msg)?;
869                (cmd::VERSION, payload)
870            }
871            ProtocolMessage::Verack => (cmd::VERACK, vec![]),
872            ProtocolMessage::SendHeaders => (cmd::SENDHEADERS, vec![]),
873            ProtocolMessage::Ping(msg) => (
874                cmd::PING,
875                crate::wire::serialize_ping(msg).map_err(|e| anyhow::anyhow!("{}", e))?,
876            ),
877            ProtocolMessage::Pong(msg) => (
878                cmd::PONG,
879                crate::wire::serialize_pong(msg).map_err(|e| anyhow::anyhow!("{}", e))?,
880            ),
881            ProtocolMessage::GetHeaders(msg) => (
882                cmd::GETHEADERS,
883                serialize_getheaders(msg).map_err(|e| anyhow::anyhow!("{}", e))?,
884            ),
885            ProtocolMessage::Headers(msg) => (
886                cmd::HEADERS,
887                crate::wire::serialize_headers(msg).map_err(|e| anyhow::anyhow!("{}", e))?,
888            ),
889            ProtocolMessage::GetBlocks(msg) => (
890                cmd::GETBLOCKS,
891                serialize_getblocks(msg).map_err(|e| anyhow::anyhow!("{}", e))?,
892            ),
893            ProtocolMessage::Block(msg) => {
894                // Must match `parse_message` cmd::BLOCK: consensus block+witness wire bytes, not bincode.
895                let payload = crate::serialization::serialize_block_with_witnesses(
896                    &msg.block,
897                    &msg.witnesses,
898                    true,
899                );
900                (cmd::BLOCK, payload)
901            }
902            ProtocolMessage::GetData(msg) => (
903                cmd::GETDATA,
904                serialize_getdata(msg).map_err(|e| anyhow::anyhow!("{}", e))?,
905            ),
906            ProtocolMessage::Inv(msg) => (
907                cmd::INV,
908                serialize_inv(msg).map_err(|e| anyhow::anyhow!("{}", e))?,
909            ),
910            ProtocolMessage::NotFound(msg) => (
911                cmd::NOTFOUND,
912                serialize_notfound(msg).map_err(|e| anyhow::anyhow!("{}", e))?,
913            ),
914            ProtocolMessage::Reject(msg) => (
915                cmd::REJECT,
916                serialize_reject(msg).map_err(|e| anyhow::anyhow!("{}", e))?,
917            ),
918            ProtocolMessage::Tx(msg) => (
919                cmd::TX,
920                serialize_tx(&msg.transaction).map_err(|e| anyhow::anyhow!("{}", e))?,
921            ),
922            ProtocolMessage::FeeFilter(msg) => (
923                cmd::FEEFILTER,
924                serialize_feefilter(msg).map_err(|e| anyhow::anyhow!("{}", e))?,
925            ),
926            ProtocolMessage::MemPool => (cmd::MEMPOOL, vec![]),
927            // Compact Block Relay (BIP152)
928            ProtocolMessage::SendCmpct(msg) => (
929                cmd::SENDCMPCT,
930                serialize_sendcmpct(msg).map_err(|e| anyhow::anyhow!("{}", e))?,
931            ),
932            ProtocolMessage::CmpctBlock(msg) => {
933                use crate::network::CmpctBlockMessage;
934                let wire_msg = CmpctBlockMessage::try_from(msg.compact_block.clone())
935                    .map_err(|e| anyhow::anyhow!("{}", e))?;
936                let payload =
937                    serialize_cmpctblock(&wire_msg).map_err(|e| anyhow::anyhow!("{}", e))?;
938                (cmd::CMPCTBLOCK, payload)
939            }
940            ProtocolMessage::GetBlockTxn(msg) => (
941                cmd::GETBLOCKTXN,
942                crate::wire::serialize_getblocktxn(msg).map_err(|e| anyhow::anyhow!("{}", e))?,
943            ),
944            ProtocolMessage::BlockTxn(msg) => (
945                cmd::BLOCKTXN,
946                crate::wire::serialize_blocktxn(msg).map_err(|e| anyhow::anyhow!("{}", e))?,
947            ),
948            // UTXO commitment protocol extensions
949            ProtocolMessage::GetUTXOSet(msg) => (cmd::GETUTXOSET, bincode::serialize(msg)?),
950            ProtocolMessage::UTXOSet(msg) => (cmd::UTXOSET, bincode::serialize(msg)?),
951            ProtocolMessage::GetUTXOProof(msg) => (cmd::GETUTXOPROOF, bincode::serialize(msg)?),
952            ProtocolMessage::UTXOProof(msg) => (cmd::UTXOPROOF, bincode::serialize(msg)?),
953            ProtocolMessage::GetFilteredBlock(msg) => {
954                (cmd::GETFILTEREDBLOCK, bincode::serialize(msg)?)
955            }
956            ProtocolMessage::FilteredBlock(msg) => (cmd::FILTEREDBLOCK, bincode::serialize(msg)?),
957            // Block Filtering (BIP157)
958            ProtocolMessage::GetCfilters(msg) => (cmd::GETCFILTERS, bincode::serialize(msg)?),
959            ProtocolMessage::Cfilter(msg) => (cmd::CFILTER, bincode::serialize(msg)?),
960            ProtocolMessage::GetCfheaders(msg) => (cmd::GETCFHEADERS, bincode::serialize(msg)?),
961            ProtocolMessage::Cfheaders(msg) => (cmd::CFHEADERS, bincode::serialize(msg)?),
962            ProtocolMessage::GetCfcheckpt(msg) => (cmd::GETCFCHECKPT, bincode::serialize(msg)?),
963            ProtocolMessage::Cfcheckpt(msg) => (cmd::CFCHECKPT, bincode::serialize(msg)?),
964            // Payment Protocol (BIP70) - P2P variant
965            ProtocolMessage::GetPaymentRequest(msg) => {
966                (cmd::GETPAYMENTREQUEST, bincode::serialize(msg)?)
967            }
968            ProtocolMessage::PaymentRequest(msg) => (cmd::PAYMENTREQUEST, bincode::serialize(msg)?),
969            ProtocolMessage::Payment(msg) => (cmd::PAYMENT, bincode::serialize(msg)?),
970            ProtocolMessage::PaymentACK(msg) => (cmd::PAYMENTACK, bincode::serialize(msg)?),
971            // CTV Payment Proof messages
972            #[cfg(feature = "ctv")]
973            ProtocolMessage::PaymentProof(msg) => (cmd::PAYMENTPROOF, bincode::serialize(msg)?),
974            ProtocolMessage::SettlementNotification(msg) => {
975                (cmd::SETTLEMENTNOTIFICATION, bincode::serialize(msg)?)
976            }
977            // Package Relay (BIP 331)
978            ProtocolMessage::SendPkgTxn(msg) => (cmd::SENDPKGTXN, bincode::serialize(msg)?),
979            ProtocolMessage::PkgTxn(msg) => (cmd::PKGTXN, bincode::serialize(msg)?),
980            ProtocolMessage::PkgTxnReject(msg) => (cmd::PKGTXNREJECT, bincode::serialize(msg)?),
981            // Ban List Sharing
982            ProtocolMessage::GetBanList(msg) => (cmd::GETBANLIST, bincode::serialize(msg)?),
983            ProtocolMessage::BanList(msg) => (cmd::BANLIST, bincode::serialize(msg)?),
984            // Address relay
985            ProtocolMessage::GetAddr => (cmd::GETADDR, vec![]),
986            ProtocolMessage::Addr(msg) => (
987                cmd::ADDR,
988                crate::wire::serialize_addr(msg).map_err(|e| anyhow::anyhow!("{}", e))?,
989            ),
990            ProtocolMessage::AddrV2(msg) => (
991                cmd::ADDRV2,
992                serialize_addrv2(msg).map_err(|e| anyhow::anyhow!("{}", e))?,
993            ),
994            // Module Registry
995            ProtocolMessage::GetModule(msg) => (cmd::GETMODULE, bincode::serialize(msg)?),
996            ProtocolMessage::Module(msg) => (cmd::MODULE, bincode::serialize(msg)?),
997            ProtocolMessage::GetModuleByHash(msg) => {
998                (cmd::GETMODULEBYHASH, bincode::serialize(msg)?)
999            }
1000            ProtocolMessage::ModuleByHash(msg) => (cmd::MODULEBYHASH, bincode::serialize(msg)?),
1001            ProtocolMessage::ModuleInv(msg) => (cmd::MODULEINV, bincode::serialize(msg)?),
1002            ProtocolMessage::GetModuleList(msg) => (cmd::GETMODULELIST, bincode::serialize(msg)?),
1003            ProtocolMessage::ModuleList(msg) => (cmd::MODULELIST, bincode::serialize(msg)?),
1004            ProtocolMessage::MeshPacket(_) => {
1005                return Err(anyhow::anyhow!("MeshPacket handled separately"))
1006            }
1007            #[cfg(feature = "erlay")]
1008            ProtocolMessage::SendTxRcncl(msg) => (cmd::SENDTXRCNCL, bincode::serialize(msg)?),
1009            #[cfg(feature = "erlay")]
1010            ProtocolMessage::ReqRecon(msg) => (cmd::REQRECON, bincode::serialize(msg)?),
1011            #[cfg(feature = "erlay")]
1012            ProtocolMessage::ReqSkt(msg) => (cmd::REQSKT, bincode::serialize(msg)?),
1013            #[cfg(feature = "erlay")]
1014            ProtocolMessage::Sketch(msg) => (cmd::SKETCH, bincode::serialize(msg)?),
1015        };
1016
1017        crate::p2p_frame::build_p2p_frame(BITCOIN_MAGIC_MAINNET, command, &payload)
1018            .map_err(|e| anyhow::anyhow!("{}", e))
1019    }
1020
1021    /// Calculate message checksum
1022    ///
1023    /// Computes double SHA256 of payload and returns first 4 bytes.
1024    /// Orange Paper 10.1.1: CalculateChecksum, |result| = 4
1025    #[cfg_attr(feature = "protocol-verification", spec_locked("10.1.1"))]
1026    pub fn calculate_checksum(payload: &[u8]) -> [u8; 4] {
1027        crate::p2p_frame::bitcoin_p2p_payload_checksum(payload)
1028    }
1029}
1030
1031// Ban List Sharing messages
1032
1033/// GetBanList message - Request peer's ban list (or hashed version)
1034#[derive(Debug, Clone, Serialize, Deserialize)]
1035pub struct GetBanListMessage {
1036    /// Request full ban list (true) or just hash (false)
1037    pub request_full: bool,
1038    /// Minimum ban duration to include (seconds, 0 = all)
1039    pub min_ban_duration: u64,
1040}
1041
1042/// BanList message - Response with ban list or hash
1043#[derive(Debug, Clone, Serialize, Deserialize)]
1044pub struct BanListMessage {
1045    /// If false, only ban_list_hash is valid
1046    pub is_full: bool,
1047    /// Hash of full ban list (SHA256 of sorted entries)
1048    pub ban_list_hash: Hash,
1049    /// Full ban list entries (only if is_full = true)
1050    pub ban_entries: Vec<BanEntry>,
1051    /// Timestamp when ban list was generated
1052    pub timestamp: u64,
1053}
1054
1055/// Single ban entry
1056#[derive(Debug, Clone, Serialize, Deserialize)]
1057pub struct BanEntry {
1058    /// Banned peer address
1059    pub addr: NetworkAddress,
1060    /// Unix timestamp when ban expires (u64::MAX = permanent)
1061    pub unban_timestamp: u64,
1062    /// Reason for ban (optional)
1063    pub reason: Option<String>,
1064}