Skip to main content

blvm_protocol/wire/
mod.rs

1//! Bitcoin P2P wire format serialization
2//!
3//! Implements Bitcoin P2P protocol message framing:
4//! Format: [magic:4][command:12][length:4][checksum:4][payload:var]
5//!
6//! - Magic bytes: Network identifier (mainnet, testnet, etc.)
7//! - Command: 12-byte ASCII string (null-padded)
8//! - Length: Payload length in bytes (little-endian u32)
9//! - Checksum: First 4 bytes of double SHA256 of payload
10//! - Payload: Message-specific data
11
12use crate::ConsensusError;
13use crate::Result;
14use crate::error::ProtocolError;
15use crate::network::NetworkMessage;
16use std::borrow::Cow;
17use std::io::Read;
18use std::sync::Arc;
19
20mod frame_header;
21pub use frame_header::{MAX_MESSAGE_PAYLOAD, MESSAGE_HEADER_SIZE, calculate_checksum};
22
23/// Serialize a network message to Bitcoin P2P wire format
24pub fn serialize_message(message: &NetworkMessage, magic_bytes: [u8; 4]) -> Result<Vec<u8>> {
25    use crate::network::*;
26
27    // Serialize payload based on message type
28    let (command, payload) = match message {
29        NetworkMessage::Version(v) => ("version", serialize_version(v)?),
30        NetworkMessage::VerAck => ("verack", vec![]),
31        NetworkMessage::Addr(a) => ("addr", serialize_addr(a)?),
32        NetworkMessage::AddrV2(a) => ("addrv2", serialize_addrv2(a)?),
33        NetworkMessage::Inv(i) => ("inv", serialize_inv(i)?),
34        NetworkMessage::GetData(g) => ("getdata", serialize_getdata(g)?),
35        NetworkMessage::GetHeaders(gh) => ("getheaders", serialize_getheaders(gh)?),
36        NetworkMessage::Headers(h) => ("headers", serialize_headers(h)?),
37        NetworkMessage::Block(b, witnesses) => ("block", serialize_block_witnesses(b, witnesses)?),
38        NetworkMessage::Tx(tx) => ("tx", serialize_tx(tx)?),
39        NetworkMessage::Ping(p) => ("ping", serialize_ping(p)?),
40        NetworkMessage::Pong(p) => ("pong", serialize_pong(p)?),
41        NetworkMessage::MemPool => ("mempool", vec![]),
42        NetworkMessage::FeeFilter(f) => ("feefilter", serialize_feefilter(f)?),
43        NetworkMessage::GetBlocks(gb) => ("getblocks", serialize_getblocks(gb)?),
44        NetworkMessage::GetAddr => ("getaddr", vec![]),
45        NetworkMessage::NotFound(nf) => ("notfound", serialize_notfound(nf)?),
46        NetworkMessage::Reject(r) => ("reject", serialize_reject(r)?),
47        NetworkMessage::SendHeaders => ("sendheaders", vec![]),
48        NetworkMessage::SendCmpct(sc) => ("sendcmpct", serialize_sendcmpct(sc)?),
49        NetworkMessage::CmpctBlock(cb) => ("cmpctblock", serialize_cmpctblock(cb)?),
50        NetworkMessage::GetBlockTxn(gbt) => ("getblocktxn", serialize_getblocktxn(gbt)?),
51        NetworkMessage::BlockTxn(bt) => ("blocktxn", serialize_blocktxn(bt)?),
52        #[cfg(feature = "utxo-commitments")]
53        NetworkMessage::GetUTXOSet(gus) => ("getutxoset", serialize_getutxoset(gus)?),
54        #[cfg(feature = "utxo-commitments")]
55        NetworkMessage::UTXOSet(us) => ("utxoset", serialize_utxoset(us)?),
56        #[cfg(feature = "utxo-commitments")]
57        NetworkMessage::GetFilteredBlock(gfb) => {
58            ("getfilteredblock", serialize_getfilteredblock(gfb)?)
59        }
60        #[cfg(feature = "utxo-commitments")]
61        NetworkMessage::FilteredBlock(fb) => ("filteredblock", serialize_filteredblock(fb)?),
62        NetworkMessage::GetBanList(gbl) => ("getbanlist", serialize_getbanlist(gbl)?),
63        NetworkMessage::BanList(bl) => ("banlist", serialize_banlist(bl)?),
64    };
65
66    // Validate payload size
67    if payload.len() > MAX_MESSAGE_PAYLOAD {
68        return Err(ProtocolError::Consensus(ConsensusError::Serialization(
69            Cow::Owned(format!(
70                "Message payload too large: {} bytes",
71                payload.len()
72            )),
73        )));
74    }
75
76    // Calculate checksum
77    let checksum = calculate_checksum(&payload);
78
79    // Build message
80    let mut message_bytes = Vec::with_capacity(MESSAGE_HEADER_SIZE + payload.len());
81
82    // Magic bytes
83    message_bytes.extend_from_slice(&magic_bytes);
84
85    // Command (12 bytes, null-padded)
86    let mut command_bytes = [0u8; 12];
87    let cmd_len = command.len().min(12);
88    command_bytes[..cmd_len].copy_from_slice(&command.as_bytes()[..cmd_len]);
89    message_bytes.extend_from_slice(&command_bytes);
90
91    // Payload length (little-endian u32)
92    message_bytes.extend_from_slice(&(payload.len() as u32).to_le_bytes());
93
94    // Checksum
95    message_bytes.extend_from_slice(&checksum);
96
97    // Payload
98    message_bytes.extend_from_slice(&payload);
99
100    Ok(message_bytes)
101}
102
103/// Deserialize Bitcoin P2P wire format to network message
104pub fn deserialize_message<R: Read>(
105    reader: &mut R,
106    expected_magic: [u8; 4],
107) -> Result<(NetworkMessage, usize)> {
108    use crate::network::*;
109
110    // Read header
111    let mut header = [0u8; MESSAGE_HEADER_SIZE];
112    reader.read_exact(&mut header).map_err(|e| {
113        ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(format!(
114            "IO error: {e}"
115        ))))
116    })?;
117
118    // Check magic bytes
119    let magic = [header[0], header[1], header[2], header[3]];
120    if magic != expected_magic {
121        return Err(ProtocolError::Consensus(ConsensusError::Serialization(
122            Cow::Owned(format!(
123                "Invalid magic bytes: {magic:?}, expected {expected_magic:?}"
124            )),
125        )));
126    }
127
128    // Read command (12 bytes, null-terminated)
129    let command_bytes = &header[4..16];
130    let command_len = command_bytes.iter().position(|&b| b == 0).unwrap_or(12);
131    let command = std::str::from_utf8(&command_bytes[..command_len]).map_err(|e| {
132        ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(format!(
133            "Invalid command: {e}"
134        ))))
135    })?;
136
137    // Read payload length
138    let length_bytes = [header[16], header[17], header[18], header[19]];
139    let payload_length = u32::from_le_bytes(length_bytes) as usize;
140
141    if payload_length > MAX_MESSAGE_PAYLOAD {
142        return Err(ProtocolError::Consensus(ConsensusError::Serialization(
143            Cow::Owned(format!("Payload length too large: {payload_length} bytes")),
144        )));
145    }
146
147    // Read checksum
148    let checksum = [header[20], header[21], header[22], header[23]];
149
150    // Read payload
151    let mut payload = vec![0u8; payload_length];
152    if payload_length > 0 {
153        reader.read_exact(&mut payload).map_err(|e| {
154            ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(format!(
155                "IO error: {e}"
156            ))))
157        })?;
158    }
159
160    // Verify checksum
161    let calculated_checksum = calculate_checksum(&payload);
162    if calculated_checksum != checksum {
163        return Err(ProtocolError::Consensus(ConsensusError::Serialization(
164            Cow::Owned("Checksum mismatch".to_string()),
165        )));
166    }
167
168    // Deserialize message based on command
169    let message = match command {
170        "version" => NetworkMessage::Version(deserialize_version(&payload)?),
171        "verack" => NetworkMessage::VerAck,
172        "addr" => NetworkMessage::Addr(deserialize_addr(&payload)?),
173        "addrv2" => NetworkMessage::AddrV2(deserialize_addrv2(&payload)?),
174        "inv" => NetworkMessage::Inv(deserialize_inv(&payload)?),
175        "getdata" => NetworkMessage::GetData(deserialize_getdata(&payload)?),
176        "getheaders" => NetworkMessage::GetHeaders(deserialize_getheaders(&payload)?),
177        "headers" => NetworkMessage::Headers(deserialize_headers(&payload)?),
178        "block" => {
179            let (block, witnesses) = deserialize_block_with_witnesses_pair(&payload)?;
180            NetworkMessage::Block(Arc::new(block), witnesses)
181        }
182        "tx" => NetworkMessage::Tx(Arc::new(deserialize_tx(&payload)?)),
183        "ping" => NetworkMessage::Ping(deserialize_ping(&payload)?),
184        "pong" => NetworkMessage::Pong(deserialize_pong(&payload)?),
185        "mempool" => NetworkMessage::MemPool,
186        "feefilter" => NetworkMessage::FeeFilter(deserialize_feefilter(&payload)?),
187        "getblocks" => NetworkMessage::GetBlocks(deserialize_getblocks(&payload)?),
188        "getaddr" => NetworkMessage::GetAddr,
189        "notfound" => NetworkMessage::NotFound(deserialize_notfound(&payload)?),
190        "reject" => NetworkMessage::Reject(deserialize_reject(&payload)?),
191        "sendheaders" => NetworkMessage::SendHeaders,
192        "sendcmpct" => NetworkMessage::SendCmpct(deserialize_sendcmpct(&payload)?),
193        "cmpctblock" => NetworkMessage::CmpctBlock(deserialize_cmpctblock(&payload)?),
194        "getblocktxn" => NetworkMessage::GetBlockTxn(deserialize_getblocktxn(&payload)?),
195        "blocktxn" => NetworkMessage::BlockTxn(deserialize_blocktxn(&payload)?),
196        #[cfg(feature = "utxo-commitments")]
197        "getutxoset" => NetworkMessage::GetUTXOSet(deserialize_getutxoset(&payload)?),
198        #[cfg(feature = "utxo-commitments")]
199        "utxoset" => NetworkMessage::UTXOSet(deserialize_utxoset(&payload)?),
200        #[cfg(feature = "utxo-commitments")]
201        "getfilteredblock" => {
202            NetworkMessage::GetFilteredBlock(deserialize_getfilteredblock(&payload)?)
203        }
204        #[cfg(feature = "utxo-commitments")]
205        "filteredblock" => NetworkMessage::FilteredBlock(deserialize_filteredblock(&payload)?),
206        "getbanlist" => NetworkMessage::GetBanList(deserialize_getbanlist(&payload)?),
207        "banlist" => NetworkMessage::BanList(deserialize_banlist(&payload)?),
208        _ => {
209            return Err(ProtocolError::Consensus(ConsensusError::Serialization(
210                Cow::Owned(format!("Unknown command: {command}")),
211            )));
212        }
213    };
214
215    Ok((message, MESSAGE_HEADER_SIZE + payload_length))
216}
217
218// Serialization helpers - Bitcoin P2P wire format encoding
219
220/// Serialize NetworkAddress to Bitcoin wire format (26 bytes)
221/// Format: services (8 bytes LE) + ip (16 bytes) + port (2 bytes BE)
222fn serialize_network_address(addr: &crate::network::NetworkAddress) -> Vec<u8> {
223    let mut buf = Vec::with_capacity(26);
224    // services: u64 little-endian
225    buf.extend_from_slice(&addr.services.to_le_bytes());
226    // ip: [u8; 16] (IPv6 format, IPv4 is 0x0000...0000FFFF + IPv4 bytes)
227    buf.extend_from_slice(&addr.ip);
228    // port: u16 big-endian (network byte order)
229    buf.extend_from_slice(&addr.port.to_be_bytes());
230    buf
231}
232
233/// Deserialize NetworkAddress from Bitcoin wire format (26 bytes)
234fn deserialize_network_address(data: &[u8]) -> Result<crate::network::NetworkAddress> {
235    if data.len() < 26 {
236        return Err(ProtocolError::Consensus(ConsensusError::Serialization(
237            Cow::Owned("NetworkAddress too short".to_string()),
238        )));
239    }
240
241    // services: u64 little-endian (bytes 0-7)
242    let services = u64::from_le_bytes([
243        data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7],
244    ]);
245
246    // ip: [u8; 16] (bytes 8-23)
247    let mut ip = [0u8; 16];
248    ip.copy_from_slice(&data[8..24]);
249
250    // port: u16 big-endian (bytes 24-25)
251    let port = u16::from_be_bytes([data[24], data[25]]);
252
253    Ok(crate::network::NetworkAddress {
254        time: 0,
255        services,
256        ip,
257        port,
258    })
259}
260
261/// Serialize VersionMessage to Bitcoin wire format
262/// Format per Bitcoin protocol specification:
263/// - version: i32 (4 bytes LE)
264/// - services: u64 (8 bytes LE)
265/// - timestamp: i64 (8 bytes LE)
266/// - addr_recv: NetworkAddress (26 bytes)
267/// - addr_from: NetworkAddress (26 bytes)
268/// - nonce: u64 (8 bytes LE)
269/// - user_agent: CompactSize + string bytes
270/// - start_height: i32 (4 bytes LE)
271/// - relay: u8 (1 byte, 0 or 1)
272pub fn serialize_version(v: &crate::network::VersionMessage) -> Result<Vec<u8>> {
273    use crate::varint::write_varint;
274
275    let mut buf = Vec::new();
276
277    // version: i32 (4 bytes, little-endian)
278    // Note: VersionMessage uses u32, but protocol expects i32
279    buf.extend_from_slice(&(v.version as i32).to_le_bytes());
280
281    // services: u64 (8 bytes, little-endian)
282    buf.extend_from_slice(&v.services.to_le_bytes());
283
284    // timestamp: i64 (8 bytes, little-endian)
285    buf.extend_from_slice(&v.timestamp.to_le_bytes());
286
287    // addr_recv: NetworkAddress (26 bytes)
288    buf.extend_from_slice(&serialize_network_address(&v.addr_recv));
289
290    // addr_from: NetworkAddress (26 bytes)
291    buf.extend_from_slice(&serialize_network_address(&v.addr_from));
292
293    // nonce: u64 (8 bytes, little-endian)
294    buf.extend_from_slice(&v.nonce.to_le_bytes());
295
296    // user_agent: CompactSize (varint) + string bytes
297    let user_agent_bytes = v.user_agent.as_bytes();
298    write_varint(&mut buf, user_agent_bytes.len() as u64)?;
299    buf.extend_from_slice(user_agent_bytes);
300
301    // start_height: i32 (4 bytes, little-endian)
302    buf.extend_from_slice(&v.start_height.to_le_bytes());
303
304    // relay: u8 (1 byte, 0 or 1)
305    buf.push(if v.relay { 1 } else { 0 });
306
307    Ok(buf)
308}
309
310/// Deserialize VersionMessage from Bitcoin wire format
311pub fn deserialize_version(data: &[u8]) -> Result<crate::network::VersionMessage> {
312    use crate::varint::read_varint;
313    use std::io::Cursor;
314
315    let mut cursor = Cursor::new(data);
316
317    // version: i32 (4 bytes, little-endian)
318    let mut version_bytes = [0u8; 4];
319    cursor.read_exact(&mut version_bytes).map_err(|e| {
320        ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(format!(
321            "Failed to read version: {e}"
322        ))))
323    })?;
324    let version = i32::from_le_bytes(version_bytes) as u32;
325
326    // services: u64 (8 bytes, little-endian)
327    let mut services_bytes = [0u8; 8];
328    cursor.read_exact(&mut services_bytes).map_err(|e| {
329        ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(format!(
330            "Failed to read services: {e}"
331        ))))
332    })?;
333    let services = u64::from_le_bytes(services_bytes);
334
335    // timestamp: i64 (8 bytes, little-endian)
336    let mut timestamp_bytes = [0u8; 8];
337    cursor.read_exact(&mut timestamp_bytes).map_err(|e| {
338        ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(format!(
339            "Failed to read timestamp: {e}"
340        ))))
341    })?;
342    let timestamp = i64::from_le_bytes(timestamp_bytes);
343
344    // addr_recv: NetworkAddress (26 bytes)
345    let mut addr_recv_bytes = [0u8; 26];
346    cursor.read_exact(&mut addr_recv_bytes).map_err(|e| {
347        ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(format!(
348            "Failed to read addr_recv: {e}"
349        ))))
350    })?;
351    let addr_recv = deserialize_network_address(&addr_recv_bytes)?;
352
353    // addr_from: NetworkAddress (26 bytes)
354    let mut addr_from_bytes = [0u8; 26];
355    cursor.read_exact(&mut addr_from_bytes).map_err(|e| {
356        ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(format!(
357            "Failed to read addr_from: {e}"
358        ))))
359    })?;
360    let addr_from = deserialize_network_address(&addr_from_bytes)?;
361
362    // nonce: u64 (8 bytes, little-endian)
363    let mut nonce_bytes = [0u8; 8];
364    cursor.read_exact(&mut nonce_bytes).map_err(|e| {
365        ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(format!(
366            "Failed to read nonce: {e}"
367        ))))
368    })?;
369    let nonce = u64::from_le_bytes(nonce_bytes);
370
371    // user_agent: CompactSize (varint) + string bytes
372    let user_agent_len = read_varint(&mut cursor)?;
373    if user_agent_len > 10000 {
374        return Err(ProtocolError::Consensus(ConsensusError::Serialization(
375            Cow::Owned("User agent too long".to_string()),
376        )));
377    }
378    let mut user_agent_bytes = vec![0u8; user_agent_len as usize];
379    cursor.read_exact(&mut user_agent_bytes).map_err(|e| {
380        ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(format!(
381            "Failed to read user_agent: {e}"
382        ))))
383    })?;
384    let user_agent = String::from_utf8(user_agent_bytes).map_err(|e| {
385        ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(format!(
386            "Invalid user_agent UTF-8: {e}"
387        ))))
388    })?;
389
390    // start_height: i32 (4 bytes, little-endian)
391    let mut start_height_bytes = [0u8; 4];
392    cursor.read_exact(&mut start_height_bytes).map_err(|e| {
393        ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(format!(
394            "Failed to read start_height: {e}"
395        ))))
396    })?;
397    let start_height = i32::from_le_bytes(start_height_bytes);
398
399    // relay: u8 (1 byte, 0 or 1) - OPTIONAL field (version >= 70001)
400    let relay = {
401        let mut relay_byte = [0u8; 1];
402        match cursor.read_exact(&mut relay_byte) {
403            Ok(_) => relay_byte[0] != 0,
404            Err(_) => true, // Default to relay=true if not present
405        }
406    };
407
408    Ok(crate::network::VersionMessage {
409        version,
410        services,
411        timestamp,
412        addr_recv,
413        addr_from,
414        nonce,
415        user_agent,
416        start_height,
417        relay,
418    })
419}
420
421/// Serialize AddrMessage to Bitcoin wire format (legacy addr)
422/// Format: CompactSize(count) + [timestamp(4) + services(8) + addr(16) + port(2)]*
423pub fn serialize_addr(a: &crate::network::AddrMessage) -> Result<Vec<u8>> {
424    use crate::varint::write_varint;
425
426    let mut buf = Vec::new();
427    write_varint(&mut buf, a.addresses.len() as u64)?;
428
429    for addr in &a.addresses {
430        buf.extend_from_slice(&addr.time.to_le_bytes());
431        // services: u64 (8 bytes, little-endian)
432        buf.extend_from_slice(&addr.services.to_le_bytes());
433        // address: 16 bytes (IPv6, IPv4-mapped)
434        buf.extend_from_slice(&addr.ip);
435        // port: u16 (2 bytes, big-endian)
436        buf.extend_from_slice(&addr.port.to_be_bytes());
437    }
438    Ok(buf)
439}
440pub fn deserialize_addr(data: &[u8]) -> Result<crate::network::AddrMessage> {
441    use crate::varint::read_varint;
442    use std::io::Read;
443
444    let mut cursor = std::io::Cursor::new(data);
445    let count = read_varint(&mut cursor)?;
446    if count > 1000 {
447        return Err(ProtocolError::Consensus(ConsensusError::Serialization(
448            Cow::Owned("Too many addresses in addr".to_string()),
449        )));
450    }
451
452    let mut addresses = Vec::with_capacity(count as usize);
453    for _ in 0..count {
454        let mut time_bytes = [0u8; 4];
455        cursor.read_exact(&mut time_bytes).map_err(|e| {
456            ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(format!(
457                "Addr time: {e}"
458            ))))
459        })?;
460        let time = u32::from_le_bytes(time_bytes);
461
462        let mut services_bytes = [0u8; 8];
463        cursor.read_exact(&mut services_bytes).map_err(|e| {
464            ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(format!(
465                "Addr services: {e}"
466            ))))
467        })?;
468        let services = u64::from_le_bytes(services_bytes);
469
470        let mut ip = [0u8; 16];
471        cursor.read_exact(&mut ip).map_err(|e| {
472            ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(format!(
473                "Addr ip: {e}"
474            ))))
475        })?;
476
477        let mut port_bytes = [0u8; 2];
478        cursor.read_exact(&mut port_bytes).map_err(|e| {
479            ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(format!(
480                "Addr port: {e}"
481            ))))
482        })?;
483        let port = u16::from_be_bytes(port_bytes);
484
485        addresses.push(crate::network::NetworkAddress {
486            services,
487            ip,
488            port,
489            time,
490        });
491    }
492    Ok(crate::network::AddrMessage { addresses })
493}
494
495/// Serialize AddrV2Message to Bitcoin wire format (BIP155)
496/// Format: CompactSize(count) + [time(4) + services(8) + address_type(1) + address(var) + port(2)]*
497pub fn serialize_addrv2(addrv2: &crate::network::AddrV2Message) -> Result<Vec<u8>> {
498    use crate::varint::write_varint;
499
500    let mut buf = Vec::new();
501
502    // CompactSize: number of addresses
503    write_varint(&mut buf, addrv2.addresses.len() as u64)?;
504
505    // Serialize each address
506    for addr in &addrv2.addresses {
507        // time: u32 (4 bytes, little-endian)
508        buf.extend_from_slice(&addr.time.to_le_bytes());
509
510        // services: u64 (8 bytes, little-endian)
511        buf.extend_from_slice(&addr.services.to_le_bytes());
512
513        // address_type: u8 (1 byte)
514        buf.push(addr.address_type as u8);
515
516        // address: variable length based on type
517        buf.extend_from_slice(&addr.address);
518
519        // port: u16 (2 bytes, big-endian)
520        buf.extend_from_slice(&addr.port.to_be_bytes());
521    }
522
523    Ok(buf)
524}
525
526/// Deserialize AddrV2Message from Bitcoin wire format (BIP155)
527pub fn deserialize_addrv2(data: &[u8]) -> Result<crate::network::AddrV2Message> {
528    use crate::varint::read_varint;
529    use std::io::Cursor;
530
531    let mut cursor = Cursor::new(data);
532
533    // CompactSize: number of addresses
534    let count = read_varint(&mut cursor)?;
535    if count > 1000 {
536        return Err(ProtocolError::Consensus(ConsensusError::Serialization(
537            Cow::Owned("Too many addresses in addrv2".to_string()),
538        )));
539    }
540
541    let mut addresses = Vec::with_capacity(count as usize);
542
543    // Deserialize each address
544    for _ in 0..count {
545        // time: u32 (4 bytes, little-endian)
546        let mut time_bytes = [0u8; 4];
547        cursor.read_exact(&mut time_bytes).map_err(|e| {
548            ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(format!(
549                "Failed to read time: {e}"
550            ))))
551        })?;
552        let time = u32::from_le_bytes(time_bytes);
553
554        // services: u64 (8 bytes, little-endian)
555        let mut services_bytes = [0u8; 8];
556        cursor.read_exact(&mut services_bytes).map_err(|e| {
557            ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(format!(
558                "Failed to read services: {e}"
559            ))))
560        })?;
561        let services = u64::from_le_bytes(services_bytes);
562
563        // address_type: u8 (1 byte)
564        let mut addr_type_byte = [0u8; 1];
565        cursor.read_exact(&mut addr_type_byte).map_err(|e| {
566            ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(format!(
567                "Failed to read address_type: {e}"
568            ))))
569        })?;
570        let address_type =
571            crate::network::AddressType::from_u8(addr_type_byte[0]).ok_or_else(|| {
572                ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(format!(
573                    "Invalid address type: {}",
574                    addr_type_byte[0]
575                ))))
576            })?;
577
578        // address: variable length based on type
579        let addr_len = address_type.address_length();
580        let mut address = vec![0u8; addr_len];
581        cursor.read_exact(&mut address).map_err(|e| {
582            ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(format!(
583                "Failed to read address: {e}"
584            ))))
585        })?;
586
587        // port: u16 (2 bytes, big-endian)
588        let mut port_bytes = [0u8; 2];
589        cursor.read_exact(&mut port_bytes).map_err(|e| {
590            ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(format!(
591                "Failed to read port: {e}"
592            ))))
593        })?;
594        let port = u16::from_be_bytes(port_bytes);
595
596        // Create NetworkAddressV2
597        let addr_v2 =
598            crate::network::NetworkAddressV2::new(time, services, address_type, address, port)?;
599
600        addresses.push(addr_v2);
601    }
602
603    Ok(crate::network::AddrV2Message { addresses })
604}
605
606/// Serialize InvMessage to Bitcoin wire format
607/// Format: count (varint) + count * (type u32 LE + hash 32 bytes)
608pub fn serialize_inv(i: &crate::network::InvMessage) -> Result<Vec<u8>> {
609    use crate::varint::write_varint;
610
611    let capacity = 9 + (36 * i.inventory.len()); // varint + (4 + 32) per item
612    let mut buf = Vec::with_capacity(capacity);
613
614    write_varint(&mut buf, i.inventory.len() as u64)?;
615
616    for item in &i.inventory {
617        buf.extend_from_slice(&item.inv_type.to_le_bytes());
618        buf.extend_from_slice(&item.hash);
619    }
620
621    Ok(buf)
622}
623
624/// Deserialize InvMessage from Bitcoin wire format
625pub fn deserialize_inv(data: &[u8]) -> Result<crate::network::InvMessage> {
626    use crate::varint::read_varint;
627    use std::io::Cursor;
628
629    if data.is_empty() {
630        return Err(ProtocolError::Consensus(ConsensusError::Serialization(
631            Cow::Owned("Inv message is empty".to_string()),
632        )));
633    }
634
635    let mut cursor = Cursor::new(data);
636
637    let count = read_varint(&mut cursor)? as usize;
638
639    // Sanity check (max 50000 inventory items per message)
640    if count > 50000 {
641        return Err(ProtocolError::Consensus(ConsensusError::Serialization(
642            Cow::Owned(format!("Too many inventory items: {count}")),
643        )));
644    }
645
646    let mut inventory = Vec::with_capacity(count);
647
648    for _ in 0..count {
649        let mut type_bytes = [0u8; 4];
650        std::io::Read::read_exact(&mut cursor, &mut type_bytes).map_err(|e| {
651            ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(e.to_string())))
652        })?;
653        let inv_type = u32::from_le_bytes(type_bytes);
654
655        let mut hash = [0u8; 32];
656        std::io::Read::read_exact(&mut cursor, &mut hash).map_err(|e| {
657            ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(e.to_string())))
658        })?;
659
660        inventory.push(crate::network::InventoryVector { inv_type, hash });
661    }
662
663    Ok(crate::network::InvMessage { inventory })
664}
665
666/// Serialize GetDataMessage to Bitcoin wire format
667/// Format: identical to Inv - count (varint) + count * (type u32 LE + hash 32 bytes)
668pub fn serialize_getdata(g: &crate::network::GetDataMessage) -> Result<Vec<u8>> {
669    use crate::varint::write_varint;
670
671    let capacity = 9 + (36 * g.inventory.len());
672    let mut buf = Vec::with_capacity(capacity);
673
674    write_varint(&mut buf, g.inventory.len() as u64)?;
675
676    for item in &g.inventory {
677        buf.extend_from_slice(&item.inv_type.to_le_bytes());
678        buf.extend_from_slice(&item.hash);
679    }
680
681    Ok(buf)
682}
683
684/// Deserialize GetDataMessage from Bitcoin wire format
685pub fn deserialize_getdata(data: &[u8]) -> Result<crate::network::GetDataMessage> {
686    use crate::varint::read_varint;
687    use std::io::Cursor;
688
689    if data.is_empty() {
690        return Err(ProtocolError::Consensus(ConsensusError::Serialization(
691            Cow::Owned("GetData message is empty".to_string()),
692        )));
693    }
694
695    let mut cursor = Cursor::new(data);
696
697    let count = read_varint(&mut cursor)? as usize;
698
699    if count > 50000 {
700        return Err(ProtocolError::Consensus(ConsensusError::Serialization(
701            Cow::Owned(format!("Too many inventory items: {count}")),
702        )));
703    }
704
705    let mut inventory = Vec::with_capacity(count);
706
707    for _ in 0..count {
708        let mut type_bytes = [0u8; 4];
709        std::io::Read::read_exact(&mut cursor, &mut type_bytes).map_err(|e| {
710            ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(e.to_string())))
711        })?;
712        let inv_type = u32::from_le_bytes(type_bytes);
713
714        let mut hash = [0u8; 32];
715        std::io::Read::read_exact(&mut cursor, &mut hash).map_err(|e| {
716            ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(e.to_string())))
717        })?;
718
719        inventory.push(crate::network::InventoryVector { inv_type, hash });
720    }
721
722    Ok(crate::network::GetDataMessage { inventory })
723}
724
725/// Serialize GetHeadersMessage to Bitcoin wire format
726/// Format: version (4 bytes LE) + hash_count (varint) + hashes (32 bytes each) + hash_stop (32 bytes)
727pub fn serialize_getheaders(gh: &crate::network::GetHeadersMessage) -> Result<Vec<u8>> {
728    use crate::varint::write_varint;
729    use std::io::Cursor;
730
731    // Estimate capacity: 4 + 1-9 + (32 * hash_count) + 32
732    let capacity = 4 + 9 + (32 * gh.block_locator_hashes.len()) + 32;
733    let mut buf = Vec::with_capacity(capacity);
734
735    // Protocol version (4 bytes, little-endian)
736    buf.extend_from_slice(&(gh.version as i32).to_le_bytes());
737
738    // Hash count (varint)
739    let mut cursor = Cursor::new(&mut buf);
740    cursor.set_position(4);
741    write_varint(&mut buf, gh.block_locator_hashes.len() as u64)?;
742
743    // Block locator hashes (32 bytes each, in internal byte order)
744    for hash in &gh.block_locator_hashes {
745        buf.extend_from_slice(hash);
746    }
747
748    // Hash stop (32 bytes)
749    buf.extend_from_slice(&gh.hash_stop);
750
751    Ok(buf)
752}
753
754/// Deserialize GetHeadersMessage from Bitcoin wire format
755pub fn deserialize_getheaders(data: &[u8]) -> Result<crate::network::GetHeadersMessage> {
756    use crate::varint::read_varint;
757    use std::io::Cursor;
758
759    if data.len() < 4 + 1 + 32 {
760        return Err(ProtocolError::Consensus(ConsensusError::Serialization(
761            Cow::Owned("GetHeaders message too short".to_string()),
762        )));
763    }
764
765    let mut cursor = Cursor::new(data);
766
767    // Protocol version (4 bytes, little-endian)
768    let mut version_bytes = [0u8; 4];
769    std::io::Read::read_exact(&mut cursor, &mut version_bytes).map_err(|e| {
770        ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(e.to_string())))
771    })?;
772    let version = i32::from_le_bytes(version_bytes) as u32;
773
774    // Hash count (varint)
775    let hash_count = read_varint(&mut cursor)? as usize;
776
777    // Sanity check on hash count
778    if hash_count > 2000 {
779        return Err(ProtocolError::Consensus(ConsensusError::Serialization(
780            Cow::Owned(format!("Too many locator hashes: {hash_count}")),
781        )));
782    }
783
784    // Block locator hashes
785    let mut block_locator_hashes = Vec::with_capacity(hash_count);
786    for _ in 0..hash_count {
787        let mut hash = [0u8; 32];
788        std::io::Read::read_exact(&mut cursor, &mut hash).map_err(|e| {
789            ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(e.to_string())))
790        })?;
791        block_locator_hashes.push(hash);
792    }
793
794    // Hash stop (32 bytes)
795    let mut hash_stop = [0u8; 32];
796    std::io::Read::read_exact(&mut cursor, &mut hash_stop).map_err(|e| {
797        ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(e.to_string())))
798    })?;
799
800    Ok(crate::network::GetHeadersMessage {
801        version,
802        block_locator_hashes,
803        hash_stop,
804    })
805}
806
807/// Serialize HeadersMessage to Bitcoin wire format
808/// Format: count (varint) + headers (each: 80 bytes header + varint tx_count which is always 0)
809pub fn serialize_headers(h: &crate::network::HeadersMessage) -> Result<Vec<u8>> {
810    use crate::varint::write_varint;
811
812    // Estimate capacity: 1-9 varint + (81 bytes per header: 80 header + 1 tx_count)
813    let capacity = 9 + (81 * h.headers.len());
814    let mut buf = Vec::with_capacity(capacity);
815
816    // Header count (varint)
817    write_varint(&mut buf, h.headers.len() as u64)?;
818
819    // Headers (80 bytes each + 1 byte tx_count = 0)
820    for header in &h.headers {
821        // version (4 bytes LE)
822        buf.extend_from_slice(&(header.version as i32).to_le_bytes());
823        // prev_block_hash (32 bytes)
824        buf.extend_from_slice(&header.prev_block_hash);
825        // merkle_root (32 bytes)
826        buf.extend_from_slice(&header.merkle_root);
827        // timestamp (4 bytes LE)
828        buf.extend_from_slice(&(header.timestamp as u32).to_le_bytes());
829        // bits (4 bytes LE)
830        buf.extend_from_slice(&(header.bits as u32).to_le_bytes());
831        // nonce (4 bytes LE)
832        buf.extend_from_slice(&(header.nonce as u32).to_le_bytes());
833        // tx_count (varint, always 0 for headers message)
834        buf.push(0);
835    }
836
837    Ok(buf)
838}
839
840/// Deserialize HeadersMessage from Bitcoin wire format
841pub fn deserialize_headers(data: &[u8]) -> Result<crate::network::HeadersMessage> {
842    use crate::varint::read_varint;
843    use std::io::Cursor;
844
845    if data.is_empty() {
846        return Err(ProtocolError::Consensus(ConsensusError::Serialization(
847            Cow::Owned("Headers message is empty".to_string()),
848        )));
849    }
850
851    let mut cursor = Cursor::new(data);
852
853    // Header count (varint)
854    let header_count = read_varint(&mut cursor)? as usize;
855
856    // Sanity check on header count (max 2000 per Bitcoin protocol)
857    if header_count > 2000 {
858        return Err(ProtocolError::Consensus(ConsensusError::Serialization(
859            Cow::Owned(format!("Too many headers: {header_count}")),
860        )));
861    }
862
863    let mut headers = Vec::with_capacity(header_count);
864
865    for _ in 0..header_count {
866        // version (4 bytes LE)
867        let mut version_bytes = [0u8; 4];
868        std::io::Read::read_exact(&mut cursor, &mut version_bytes).map_err(|e| {
869            ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(e.to_string())))
870        })?;
871        let version = i32::from_le_bytes(version_bytes) as i64;
872
873        // prev_block_hash (32 bytes)
874        let mut prev_block_hash = [0u8; 32];
875        std::io::Read::read_exact(&mut cursor, &mut prev_block_hash).map_err(|e| {
876            ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(e.to_string())))
877        })?;
878
879        // merkle_root (32 bytes)
880        let mut merkle_root = [0u8; 32];
881        std::io::Read::read_exact(&mut cursor, &mut merkle_root).map_err(|e| {
882            ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(e.to_string())))
883        })?;
884
885        // timestamp (4 bytes LE)
886        let mut timestamp_bytes = [0u8; 4];
887        std::io::Read::read_exact(&mut cursor, &mut timestamp_bytes).map_err(|e| {
888            ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(e.to_string())))
889        })?;
890        let timestamp = u32::from_le_bytes(timestamp_bytes) as u64;
891
892        // bits (4 bytes LE)
893        let mut bits_bytes = [0u8; 4];
894        std::io::Read::read_exact(&mut cursor, &mut bits_bytes).map_err(|e| {
895            ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(e.to_string())))
896        })?;
897        let bits = u32::from_le_bytes(bits_bytes) as u64;
898
899        // nonce (4 bytes LE)
900        let mut nonce_bytes = [0u8; 4];
901        std::io::Read::read_exact(&mut cursor, &mut nonce_bytes).map_err(|e| {
902            ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(e.to_string())))
903        })?;
904        let nonce = u32::from_le_bytes(nonce_bytes) as u64;
905
906        // tx_count (varint, should be 0 for headers message, but we read and discard)
907        let _tx_count = read_varint(&mut cursor)?;
908
909        headers.push(crate::BlockHeader {
910            version,
911            prev_block_hash,
912            merkle_root,
913            timestamp,
914            bits,
915            nonce,
916        });
917    }
918
919    Ok(crate::network::HeadersMessage { headers })
920}
921
922/// Serialize a block with its witness stacks for P2P relay.
923///
924/// `witnesses` is the per-tx, per-input witness stack (same layout as [`BlockMessage::witnesses`]).
925/// Pass an empty slice to produce a legacy (pre-SegWit) serialization.
926pub fn serialize_block_witnesses(
927    b: &crate::Block,
928    witnesses: &[Vec<blvm_consensus::segwit::Witness>],
929) -> Result<Vec<u8>> {
930    use crate::serialization::serialize_block_with_witnesses;
931
932    let include_witness = !witnesses.is_empty() && witnesses.iter().any(|tx_w| !tx_w.is_empty());
933
934    // Pad witnesses to match transaction count if a non-empty slice was provided but is shorter.
935    let padded;
936    let ws: &[Vec<blvm_consensus::segwit::Witness>] = if witnesses.len() < b.transactions.len() {
937        padded = {
938            let mut v = witnesses.to_vec();
939            v.resize(b.transactions.len(), Vec::new());
940            v
941        };
942        &padded
943    } else {
944        witnesses
945    };
946
947    Ok(serialize_block_with_witnesses(b, ws, include_witness))
948}
949
950/// Deserialize a block from P2P wire bytes, preserving its witness stacks.
951///
952/// Returns `(block, per_tx_witnesses)`.  The witness vec is empty if the message
953/// was serialized in legacy (non-SegWit) format.
954pub fn deserialize_block_with_witnesses_pair(
955    data: &[u8],
956) -> Result<(crate::Block, Vec<Vec<blvm_consensus::segwit::Witness>>)> {
957    use crate::serialization::block::deserialize_block_with_witnesses;
958
959    deserialize_block_with_witnesses(data).map_err(|e| {
960        ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(e.to_string())))
961    })
962}
963
964/// Deserialize only the block, discarding any witness data.
965///
966/// Prefer [`deserialize_block_with_witnesses_pair`] for new call-sites; this
967/// helper is retained for legacy uses where witnesses are not needed.
968pub fn deserialize_block(data: &[u8]) -> Result<crate::Block> {
969    let (block, _witnesses) = deserialize_block_with_witnesses_pair(data)?;
970    Ok(block)
971}
972
973pub fn serialize_tx(tx: &crate::Transaction) -> Result<Vec<u8>> {
974    Ok(crate::serialization::serialize_transaction(tx))
975}
976
977/// Serialize a transaction, including witness stacks when any stack is non-empty.
978pub fn serialize_tx_with_witness(
979    tx: &crate::Transaction,
980    witnesses: &[crate::segwit::Witness],
981) -> Result<Vec<u8>> {
982    let has_witness =
983        witnesses.len() == tx.inputs.len() && witnesses.iter().any(|stack| !stack.is_empty());
984    if has_witness {
985        crate::serialization::serialize_transaction_with_witness(tx, witnesses).map_err(|e| {
986            ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(e.to_string())))
987        })
988    } else {
989        serialize_tx(tx)
990    }
991}
992
993pub fn deserialize_tx(data: &[u8]) -> Result<crate::Transaction> {
994    deserialize_tx_with_witness(data).map(|(tx, _)| tx)
995}
996
997/// Deserialize a transaction and the witness stack for each input.
998pub fn deserialize_tx_with_witness(
999    data: &[u8],
1000) -> Result<(crate::Transaction, Vec<crate::segwit::Witness>)> {
1001    crate::serialization::deserialize_transaction_with_witness(data)
1002        .map(|(tx, witnesses, _)| (tx, witnesses))
1003        .map_err(|e| {
1004            ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(e.to_string())))
1005        })
1006}
1007
1008/// Serialize PingMessage to Bitcoin wire format (8-byte nonce)
1009pub fn serialize_ping(p: &crate::network::PingMessage) -> Result<Vec<u8>> {
1010    Ok(p.nonce.to_le_bytes().to_vec())
1011}
1012
1013/// Deserialize PingMessage from Bitcoin wire format
1014pub fn deserialize_ping(data: &[u8]) -> Result<crate::network::PingMessage> {
1015    if data.len() < 8 {
1016        return Err(ProtocolError::Consensus(ConsensusError::Serialization(
1017            Cow::Owned(format!("Ping message too short: {} bytes", data.len())),
1018        )));
1019    }
1020
1021    let nonce = u64::from_le_bytes([
1022        data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7],
1023    ]);
1024
1025    Ok(crate::network::PingMessage { nonce })
1026}
1027
1028/// Serialize PongMessage to Bitcoin wire format (8-byte nonce)
1029pub fn serialize_pong(p: &crate::network::PongMessage) -> Result<Vec<u8>> {
1030    Ok(p.nonce.to_le_bytes().to_vec())
1031}
1032
1033/// Deserialize PongMessage from Bitcoin wire format
1034pub fn deserialize_pong(data: &[u8]) -> Result<crate::network::PongMessage> {
1035    if data.len() < 8 {
1036        return Err(ProtocolError::Consensus(ConsensusError::Serialization(
1037            Cow::Owned(format!("Pong message too short: {} bytes", data.len())),
1038        )));
1039    }
1040
1041    let nonce = u64::from_le_bytes([
1042        data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7],
1043    ]);
1044
1045    Ok(crate::network::PongMessage { nonce })
1046}
1047
1048/// Serialize FeeFilterMessage per BIP133: 8-byte feerate (LE)
1049pub fn serialize_feefilter(f: &crate::network::FeeFilterMessage) -> Result<Vec<u8>> {
1050    Ok(f.feerate.to_le_bytes().to_vec())
1051}
1052pub fn deserialize_feefilter(data: &[u8]) -> Result<crate::network::FeeFilterMessage> {
1053    if data.len() < 8 {
1054        return Err(ProtocolError::Consensus(ConsensusError::Serialization(
1055            Cow::Owned("FeeFilter message too short".to_string()),
1056        )));
1057    }
1058    let feerate = u64::from_le_bytes([
1059        data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7],
1060    ]);
1061    Ok(crate::network::FeeFilterMessage { feerate })
1062}
1063
1064/// Serialize GetBlocksMessage - same structure as GetHeaders (version + locator + hash_stop)
1065pub fn serialize_getblocks(gb: &crate::network::GetBlocksMessage) -> Result<Vec<u8>> {
1066    use crate::varint::write_varint;
1067
1068    let mut buf = Vec::new();
1069    buf.extend_from_slice(&gb.version.to_le_bytes());
1070    write_varint(&mut buf, gb.block_locator_hashes.len() as u64)?;
1071    for hash in &gb.block_locator_hashes {
1072        buf.extend_from_slice(hash);
1073    }
1074    buf.extend_from_slice(&gb.hash_stop);
1075    Ok(buf)
1076}
1077pub fn deserialize_getblocks(data: &[u8]) -> Result<crate::network::GetBlocksMessage> {
1078    use crate::varint::read_varint;
1079    use std::io::Read;
1080
1081    if data.len() < 4 {
1082        return Err(ProtocolError::Consensus(ConsensusError::Serialization(
1083            Cow::Owned("GetBlocks message too short".to_string()),
1084        )));
1085    }
1086    let mut cursor = std::io::Cursor::new(data);
1087    let version = u32::from_le_bytes([data[0], data[1], data[2], data[3]]);
1088    cursor.set_position(4);
1089
1090    let count = read_varint(&mut cursor)? as usize;
1091    if count > 101 {
1092        return Err(ProtocolError::Consensus(ConsensusError::Serialization(
1093            Cow::Owned("GetBlocks locator too long".to_string()),
1094        )));
1095    }
1096    let mut block_locator_hashes = Vec::with_capacity(count);
1097    for _ in 0..count {
1098        let mut hash = [0u8; 32];
1099        cursor.read_exact(&mut hash).map_err(|e| {
1100            ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(format!(
1101                "GetBlocks: {e}"
1102            ))))
1103        })?;
1104        block_locator_hashes.push(hash);
1105    }
1106    let mut hash_stop = [0u8; 32];
1107    cursor.read_exact(&mut hash_stop).map_err(|e| {
1108        ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(format!(
1109            "GetBlocks hash_stop: {e}"
1110        ))))
1111    })?;
1112
1113    Ok(crate::network::GetBlocksMessage {
1114        version,
1115        block_locator_hashes,
1116        hash_stop,
1117    })
1118}
1119
1120/// Serialize NotFoundMessage to Bitcoin wire format.
1121/// Format: identical to Inv/GetData - count (varint) + count * (type u32 LE + hash 32 bytes)
1122pub fn serialize_notfound(nf: &crate::network::NotFoundMessage) -> Result<Vec<u8>> {
1123    use crate::varint::write_varint;
1124
1125    let capacity = 9 + (36 * nf.inventory.len());
1126    let mut buf = Vec::with_capacity(capacity);
1127
1128    write_varint(&mut buf, nf.inventory.len() as u64)?;
1129
1130    for item in &nf.inventory {
1131        buf.extend_from_slice(&item.inv_type.to_le_bytes());
1132        buf.extend_from_slice(&item.hash);
1133    }
1134
1135    Ok(buf)
1136}
1137
1138/// Deserialize NotFoundMessage from Bitcoin wire format.
1139pub fn deserialize_notfound(data: &[u8]) -> Result<crate::network::NotFoundMessage> {
1140    use crate::varint::read_varint;
1141    use std::io::Cursor;
1142
1143    if data.is_empty() {
1144        return Err(ProtocolError::Consensus(ConsensusError::Serialization(
1145            Cow::Owned("NotFound message is empty".to_string()),
1146        )));
1147    }
1148
1149    let mut cursor = Cursor::new(data);
1150
1151    let count = read_varint(&mut cursor)? as usize;
1152
1153    if count > 50000 {
1154        return Err(ProtocolError::Consensus(ConsensusError::Serialization(
1155            Cow::Owned(format!("Too many inventory items: {count}")),
1156        )));
1157    }
1158
1159    let mut inventory = Vec::with_capacity(count);
1160
1161    for _ in 0..count {
1162        let mut type_bytes = [0u8; 4];
1163        std::io::Read::read_exact(&mut cursor, &mut type_bytes).map_err(|e| {
1164            ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(e.to_string())))
1165        })?;
1166        let inv_type = u32::from_le_bytes(type_bytes);
1167
1168        let mut hash = [0u8; 32];
1169        std::io::Read::read_exact(&mut cursor, &mut hash).map_err(|e| {
1170            ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(e.to_string())))
1171        })?;
1172
1173        inventory.push(crate::network::InventoryVector { inv_type, hash });
1174    }
1175
1176    Ok(crate::network::NotFoundMessage { inventory })
1177}
1178
1179/// Serialize RejectMessage per BIP61: message(12) + ccode(1) + reason(var) + data(32 optional)
1180pub fn serialize_reject(r: &crate::network::RejectMessage) -> Result<Vec<u8>> {
1181    use crate::varint::write_varint;
1182
1183    let mut buf = Vec::with_capacity(12 + 1 + 9 + r.reason.len() + 32);
1184    let msg_bytes = r.message.as_bytes();
1185    if msg_bytes.len() > 12 {
1186        return Err(ProtocolError::Consensus(ConsensusError::Serialization(
1187            Cow::Owned("Reject message field too long".to_string()),
1188        )));
1189    }
1190    buf.extend_from_slice(msg_bytes);
1191    buf.extend_from_slice(&[0u8; 12][msg_bytes.len()..]);
1192
1193    buf.push(r.code);
1194
1195    write_varint(&mut buf, r.reason.len() as u64)?;
1196    buf.extend_from_slice(r.reason.as_bytes());
1197
1198    if let Some(ref h) = r.extra_data {
1199        buf.extend_from_slice(h);
1200    }
1201    Ok(buf)
1202}
1203pub fn deserialize_reject(data: &[u8]) -> Result<crate::network::RejectMessage> {
1204    use crate::varint::read_varint;
1205
1206    if data.len() < 13 {
1207        return Err(ProtocolError::Consensus(ConsensusError::Serialization(
1208            Cow::Owned("Reject message too short".to_string()),
1209        )));
1210    }
1211    let message = String::from_utf8_lossy(&data[0..12])
1212        .trim_end_matches('\0')
1213        .to_string();
1214    let code = data[12];
1215    let mut cursor = std::io::Cursor::new(&data[13..]);
1216    let reason_len_u64 = read_varint(&mut cursor)?;
1217    let pos: usize = 13_usize.saturating_add(cursor.position() as usize);
1218    if reason_len_u64 > (data.len() as u64).saturating_sub(pos as u64) {
1219        return Err(ProtocolError::Consensus(ConsensusError::Serialization(
1220            Cow::Owned("Reject reason truncated".to_string()),
1221        )));
1222    }
1223    let reason_len: usize = reason_len_u64.try_into().map_err(|_| {
1224        ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(
1225            "Reject reason length out of range".to_string(),
1226        )))
1227    })?;
1228    let end = pos.checked_add(reason_len).ok_or_else(|| {
1229        ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(
1230            "Reject reason length out of range".to_string(),
1231        )))
1232    })?;
1233    if end > data.len() {
1234        return Err(ProtocolError::Consensus(ConsensusError::Serialization(
1235            Cow::Owned("Reject reason truncated".to_string()),
1236        )));
1237    }
1238    let reason = String::from_utf8_lossy(&data[pos..end]).to_string();
1239    let pos = end;
1240    let extra_data = if data.len() >= pos + 32 {
1241        Some({
1242            let mut h = [0u8; 32];
1243            h.copy_from_slice(&data[pos..pos + 32]);
1244            h
1245        })
1246    } else {
1247        None
1248    };
1249    Ok(crate::network::RejectMessage {
1250        message,
1251        code,
1252        reason,
1253        extra_data,
1254    })
1255}
1256
1257/// BIP152: sendcmpct - 1 byte prefer_cmpct + 8 bytes version (LE)
1258pub fn serialize_sendcmpct(sc: &crate::network::SendCmpctMessage) -> Result<Vec<u8>> {
1259    let mut buf = Vec::with_capacity(9);
1260    buf.push(sc.prefer_cmpct);
1261    buf.extend_from_slice(&sc.version.to_le_bytes());
1262    Ok(buf)
1263}
1264pub fn deserialize_sendcmpct(data: &[u8]) -> Result<crate::network::SendCmpctMessage> {
1265    if data.len() < 9 {
1266        return Err(ProtocolError::Consensus(ConsensusError::Serialization(
1267            Cow::Owned("SendCmpct message too short".to_string()),
1268        )));
1269    }
1270    Ok(crate::network::SendCmpctMessage {
1271        prefer_cmpct: data[0],
1272        version: u64::from_le_bytes([
1273            data[1], data[2], data[3], data[4], data[5], data[6], data[7], data[8],
1274        ]),
1275    })
1276}
1277
1278/// BIP152: cmpctblock - header(80) + nonce(8) + shortids(varint+6*count) + prefilled(varint+each: diff_index+tx)
1279pub fn serialize_cmpctblock(cb: &crate::network::CmpctBlockMessage) -> Result<Vec<u8>> {
1280    use crate::varint::write_varint;
1281
1282    let mut buf = Vec::new();
1283    buf.extend_from_slice(&crate::serialization::serialize_block_header(&cb.header));
1284    buf.extend_from_slice(&cb.nonce.to_le_bytes());
1285    write_varint(&mut buf, cb.short_ids.len() as u64)?;
1286    for sid in &cb.short_ids {
1287        buf.extend_from_slice(sid);
1288    }
1289    write_varint(&mut buf, cb.prefilled_txs.len() as u64)?;
1290    let mut last_index = -1i64;
1291    for pt in &cb.prefilled_txs {
1292        let diff = (pt.index as i64) - last_index - 1;
1293        write_varint(&mut buf, diff as u64)?;
1294        last_index = pt.index as i64;
1295        let tx_bytes = match &pt.witness {
1296            Some(wit) if wit.iter().any(|w| !w.is_empty()) => {
1297                crate::serialization::serialize_transaction_with_witness(&pt.tx, wit)?
1298            }
1299            _ => crate::serialization::serialize_transaction(&pt.tx),
1300        };
1301        buf.extend_from_slice(&tx_bytes);
1302    }
1303    Ok(buf)
1304}
1305pub fn deserialize_cmpctblock(data: &[u8]) -> Result<crate::network::CmpctBlockMessage> {
1306    use crate::varint::read_varint;
1307    use std::io::Read;
1308
1309    /// BIP152 wire sanity bound (aligns with `getblocktxn` and inventory caps in this module).
1310    const MAX_CMPCT_SHORTIDS: u64 = 50_000;
1311    const MAX_CMPCT_PREFILLED: u64 = 50_000;
1312
1313    if data.len() < 80 + 8 {
1314        return Err(ProtocolError::Consensus(ConsensusError::Serialization(
1315            Cow::Owned("CmpctBlock message too short".to_string()),
1316        )));
1317    }
1318    let header = crate::serialization::deserialize_block_header(&data[0..80]).map_err(|e| {
1319        ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(e.to_string())))
1320    })?;
1321    let nonce = u64::from_le_bytes([
1322        data[80], data[81], data[82], data[83], data[84], data[85], data[86], data[87],
1323    ]);
1324    let mut cursor = std::io::Cursor::new(&data[88..]);
1325    let shortids_len_u64 = read_varint(&mut cursor)?;
1326    if shortids_len_u64 > MAX_CMPCT_SHORTIDS {
1327        return Err(ProtocolError::Consensus(ConsensusError::Serialization(
1328            Cow::Owned("CmpctBlock too many shortids".to_string()),
1329        )));
1330    }
1331    let shortids_len: usize = shortids_len_u64.try_into().map_err(|_| {
1332        ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(
1333            "CmpctBlock shortid count out of range".to_string(),
1334        )))
1335    })?;
1336    let rem_after_count = data.len().saturating_sub(88 + cursor.position() as usize);
1337    let need_shortids = shortids_len.checked_mul(6).ok_or_else(|| {
1338        ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(
1339            "CmpctBlock shortid size overflow".to_string(),
1340        )))
1341    })?;
1342    if need_shortids > rem_after_count {
1343        return Err(ProtocolError::Consensus(ConsensusError::Serialization(
1344            Cow::Owned("CmpctBlock shortids truncated".to_string()),
1345        )));
1346    }
1347    let mut short_ids = Vec::with_capacity(shortids_len);
1348    for _ in 0..shortids_len {
1349        let mut sid = [0u8; 6];
1350        cursor.read_exact(&mut sid).map_err(|e| {
1351            ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(format!(
1352                "CmpctBlock shortid: {e}"
1353            ))))
1354        })?;
1355        short_ids.push(sid);
1356    }
1357    let prefilled_len_u64 = read_varint(&mut cursor)?;
1358    if prefilled_len_u64 > MAX_CMPCT_PREFILLED {
1359        return Err(ProtocolError::Consensus(ConsensusError::Serialization(
1360            Cow::Owned("CmpctBlock too many prefilled txs".to_string()),
1361        )));
1362    }
1363    let prefilled_len: usize = prefilled_len_u64.try_into().map_err(|_| {
1364        ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(
1365            "CmpctBlock prefilled count out of range".to_string(),
1366        )))
1367    })?;
1368    let mut prefilled_txs = Vec::with_capacity(prefilled_len);
1369    let mut last_index: i64 = -1;
1370    let mut pos;
1371    for _ in 0..prefilled_len {
1372        let diff = read_varint(&mut cursor)? as i64;
1373        if diff < 0 {
1374            return Err(ProtocolError::Consensus(ConsensusError::Serialization(
1375                Cow::Owned("CmpctBlock prefilled index diff invalid".to_string()),
1376            )));
1377        }
1378        let index = last_index
1379            .checked_add(diff)
1380            .and_then(|v| v.checked_add(1))
1381            .ok_or_else(|| {
1382                ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(
1383                    "CmpctBlock prefilled index overflow".to_string(),
1384                )))
1385            })?;
1386        if index > 0xffff {
1387            return Err(ProtocolError::Consensus(ConsensusError::Serialization(
1388                Cow::Owned("CmpctBlock prefilled index too large".to_string()),
1389            )));
1390        }
1391        last_index = index;
1392        pos = cursor.position() as usize;
1393        let slice = &data[88..];
1394        if pos >= slice.len() {
1395            return Err(ProtocolError::Consensus(ConsensusError::Serialization(
1396                Cow::Owned("CmpctBlock prefilled tx truncated".to_string()),
1397            )));
1398        }
1399        // Use deserialize_transaction_with_witness: returns actual bytes consumed.
1400        // Core sends prefilled txs with TX_WITH_WITNESS (SegWit); serialize_transaction().len()
1401        // would undercount and corrupt subsequent parsing.
1402        let (tx, witnesses, consumed) = crate::serialization::deserialize_transaction_with_witness(
1403            &slice[pos..],
1404        )
1405        .map_err(|e| {
1406            ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(e.to_string())))
1407        })?;
1408        cursor.set_position((pos + consumed) as u64);
1409        let witness = witnesses.iter().any(|w| !w.is_empty()).then_some(witnesses);
1410        prefilled_txs.push(crate::network::PrefilledTransaction {
1411            index: index as u16,
1412            tx,
1413            witness,
1414        });
1415    }
1416    Ok(crate::network::CmpctBlockMessage {
1417        header,
1418        nonce,
1419        short_ids,
1420        prefilled_txs,
1421    })
1422}
1423
1424/// BIP152: getblocktxn - block_hash(32) + indexes(varint count + diff-encoded varints)
1425pub fn serialize_getblocktxn(gbt: &crate::network::GetBlockTxnMessage) -> Result<Vec<u8>> {
1426    use crate::varint::write_varint;
1427
1428    let mut buf = Vec::new();
1429    buf.extend_from_slice(&gbt.block_hash);
1430    write_varint(&mut buf, gbt.indices.len() as u64)?;
1431    let mut last: i64 = -1;
1432    for &idx in &gbt.indices {
1433        let diff = (idx as i64) - last - 1;
1434        if diff < 0 {
1435            return Err(ProtocolError::Consensus(ConsensusError::Serialization(
1436                Cow::Owned("GetBlockTxn indices must be strictly increasing".to_string()),
1437            )));
1438        }
1439        write_varint(&mut buf, diff as u64)?;
1440        last = idx as i64;
1441    }
1442    Ok(buf)
1443}
1444pub fn deserialize_getblocktxn(data: &[u8]) -> Result<crate::network::GetBlockTxnMessage> {
1445    use crate::varint::read_varint;
1446
1447    if data.len() < 32 {
1448        return Err(ProtocolError::Consensus(ConsensusError::Serialization(
1449            Cow::Owned("GetBlockTxn message too short".to_string()),
1450        )));
1451    }
1452    let mut block_hash = [0u8; 32];
1453    block_hash.copy_from_slice(&data[0..32]);
1454    let mut cursor = std::io::Cursor::new(&data[32..]);
1455    let count = read_varint(&mut cursor)? as usize;
1456    if count > 50000 {
1457        return Err(ProtocolError::Consensus(ConsensusError::Serialization(
1458            Cow::Owned("GetBlockTxn too many indices".to_string()),
1459        )));
1460    }
1461    let mut indices = Vec::with_capacity(count);
1462    let mut last: i64 = -1;
1463    for _ in 0..count {
1464        let diff = read_varint(&mut cursor)? as i64;
1465        if diff < 0 {
1466            return Err(ProtocolError::Consensus(ConsensusError::Serialization(
1467                Cow::Owned("GetBlockTxn index diff invalid".to_string()),
1468            )));
1469        }
1470        last = last
1471            .checked_add(diff)
1472            .and_then(|v| v.checked_add(1))
1473            .ok_or_else(|| {
1474                ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(
1475                    "GetBlockTxn index overflow".to_string(),
1476                )))
1477            })?;
1478        if last > 0xffff {
1479            return Err(ProtocolError::Consensus(ConsensusError::Serialization(
1480                Cow::Owned("GetBlockTxn index too large".to_string()),
1481            )));
1482        }
1483        indices.push(last as u16);
1484    }
1485    Ok(crate::network::GetBlockTxnMessage {
1486        block_hash,
1487        indices,
1488    })
1489}
1490
1491/// BIP152: blocktxn - block_hash(32) + count(varint) + transactions
1492pub fn serialize_blocktxn(bt: &crate::network::BlockTxnMessage) -> Result<Vec<u8>> {
1493    use crate::varint::write_varint;
1494
1495    let mut buf = Vec::new();
1496    buf.extend_from_slice(&bt.block_hash);
1497    write_varint(&mut buf, bt.transactions.len() as u64)?;
1498    match (&bt.witnesses, bt.transactions.len()) {
1499        (Some(witnesses), len) if witnesses.len() == len => {
1500            for (tx, wit) in bt.transactions.iter().zip(witnesses.iter()) {
1501                buf.extend_from_slice(&crate::serialization::serialize_transaction_with_witness(
1502                    tx, wit,
1503                )?);
1504            }
1505        }
1506        _ => {
1507            for tx in &bt.transactions {
1508                buf.extend_from_slice(&crate::serialization::serialize_transaction(tx));
1509            }
1510        }
1511    }
1512    Ok(buf)
1513}
1514pub fn deserialize_blocktxn(data: &[u8]) -> Result<crate::network::BlockTxnMessage> {
1515    use crate::varint::read_varint;
1516
1517    if data.len() < 32 {
1518        return Err(ProtocolError::Consensus(ConsensusError::Serialization(
1519            Cow::Owned("BlockTxn message too short".to_string()),
1520        )));
1521    }
1522    let mut block_hash = [0u8; 32];
1523    block_hash.copy_from_slice(&data[0..32]);
1524    let mut cursor = std::io::Cursor::new(&data[32..]);
1525    let count = read_varint(&mut cursor)? as usize;
1526    // Same ceiling as compact-block short ids and getblocktxn indexes.
1527    if count > 50_000 {
1528        return Err(ProtocolError::Consensus(ConsensusError::Serialization(
1529            Cow::Owned("BlockTxn too many transactions".to_string()),
1530        )));
1531    }
1532    let mut transactions = Vec::with_capacity(count);
1533    let mut all_witnesses = Vec::with_capacity(count);
1534    let mut pos = 32 + (cursor.position() as usize);
1535    for _ in 0..count {
1536        if pos >= data.len() {
1537            return Err(ProtocolError::Consensus(ConsensusError::Serialization(
1538                Cow::Owned("BlockTxn truncated".to_string()),
1539            )));
1540        }
1541        // BIP152: blocktxn txs use same format as block (TX_WITH_WITNESS). Use
1542        // deserialize_transaction_with_witness for correct bytes consumed.
1543        let (tx, witnesses, consumed) = crate::serialization::deserialize_transaction_with_witness(
1544            &data[pos..],
1545        )
1546        .map_err(|e| {
1547            ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(e.to_string())))
1548        })?;
1549        pos += consumed;
1550        transactions.push(tx);
1551        all_witnesses.push(witnesses);
1552    }
1553    let witnesses = all_witnesses
1554        .iter()
1555        .any(|w| w.iter().any(|s| !s.is_empty()))
1556        .then_some(all_witnesses);
1557    Ok(crate::network::BlockTxnMessage {
1558        block_hash,
1559        transactions,
1560        witnesses,
1561    })
1562}
1563
1564#[cfg(feature = "utxo-commitments")]
1565fn serialize_getutxoset(gus: &crate::commons::GetUTXOSetMessage) -> Result<Vec<u8>> {
1566    let mut buf = Vec::with_capacity(40);
1567    buf.extend_from_slice(&gus.height.to_le_bytes());
1568    buf.extend_from_slice(&gus.block_hash);
1569    Ok(buf)
1570}
1571#[cfg(feature = "utxo-commitments")]
1572fn deserialize_getutxoset(data: &[u8]) -> Result<crate::commons::GetUTXOSetMessage> {
1573    if data.len() < 40 {
1574        return Err(ProtocolError::Consensus(ConsensusError::Serialization(
1575            Cow::Owned("GetUTXOSet message too short".to_string()),
1576        )));
1577    }
1578    let height = u64::from_le_bytes([
1579        data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7],
1580    ]);
1581    let mut block_hash = [0u8; 32];
1582    block_hash.copy_from_slice(&data[8..40]);
1583    Ok(crate::commons::GetUTXOSetMessage { height, block_hash })
1584}
1585
1586#[cfg(feature = "utxo-commitments")]
1587fn serialize_utxoset(us: &crate::commons::UTXOSetMessage) -> Result<Vec<u8>> {
1588    bincode::serialize(us).map_err(|e| {
1589        ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(e.to_string())))
1590    })
1591}
1592#[cfg(feature = "utxo-commitments")]
1593fn deserialize_utxoset(data: &[u8]) -> Result<crate::commons::UTXOSetMessage> {
1594    bincode::deserialize(data).map_err(|e| {
1595        ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(e.to_string())))
1596    })
1597}
1598
1599#[cfg(feature = "utxo-commitments")]
1600fn serialize_getfilteredblock(gfb: &crate::commons::GetFilteredBlockMessage) -> Result<Vec<u8>> {
1601    bincode::serialize(gfb).map_err(|e| {
1602        ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(e.to_string())))
1603    })
1604}
1605#[cfg(feature = "utxo-commitments")]
1606fn deserialize_getfilteredblock(data: &[u8]) -> Result<crate::commons::GetFilteredBlockMessage> {
1607    bincode::deserialize(data).map_err(|e| {
1608        ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(e.to_string())))
1609    })
1610}
1611
1612#[cfg(feature = "utxo-commitments")]
1613fn serialize_filteredblock(fb: &crate::commons::FilteredBlockMessage) -> Result<Vec<u8>> {
1614    bincode::serialize(fb).map_err(|e| {
1615        ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(e.to_string())))
1616    })
1617}
1618#[cfg(feature = "utxo-commitments")]
1619fn deserialize_filteredblock(data: &[u8]) -> Result<crate::commons::FilteredBlockMessage> {
1620    bincode::deserialize(data).map_err(|e| {
1621        ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(e.to_string())))
1622    })
1623}
1624
1625fn serialize_getbanlist(gbl: &crate::commons::GetBanListMessage) -> Result<Vec<u8>> {
1626    bincode::serialize(gbl).map_err(|e| {
1627        ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(e.to_string())))
1628    })
1629}
1630fn deserialize_getbanlist(data: &[u8]) -> Result<crate::commons::GetBanListMessage> {
1631    bincode::deserialize(data).map_err(|e| {
1632        ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(e.to_string())))
1633    })
1634}
1635
1636fn serialize_banlist(bl: &crate::commons::BanListMessage) -> Result<Vec<u8>> {
1637    bincode::serialize(bl).map_err(|e| {
1638        ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(e.to_string())))
1639    })
1640}
1641fn deserialize_banlist(data: &[u8]) -> Result<crate::commons::BanListMessage> {
1642    bincode::deserialize(data).map_err(|e| {
1643        ProtocolError::Consensus(ConsensusError::Serialization(Cow::Owned(e.to_string())))
1644    })
1645}
1646
1647#[cfg(test)]
1648mod blocktxn_count {
1649    use super::deserialize_blocktxn;
1650
1651    fn minimal_tx() -> Vec<u8> {
1652        let mut tx = Vec::new();
1653        tx.extend_from_slice(&1i32.to_le_bytes());
1654        tx.push(0);
1655        tx.push(0);
1656        tx.extend_from_slice(&0u32.to_le_bytes());
1657        tx
1658    }
1659
1660    #[test]
1661    fn twelve_thousand_transactions_decode() {
1662        let one = minimal_tx();
1663        let mut payload = vec![0u8; 32];
1664        payload.push(0xfd);
1665        payload.extend_from_slice(&12_239u16.to_le_bytes());
1666        for _ in 0..12_239 {
1667            payload.extend_from_slice(&one);
1668        }
1669        let msg = deserialize_blocktxn(&payload).expect("12239 transactions fit");
1670        assert_eq!(msg.transactions.len(), 12_239);
1671
1672        let mut over = vec![0u8; 32];
1673        over.push(0xfd);
1674        over.extend_from_slice(&50_001u16.to_le_bytes());
1675        let err = deserialize_blocktxn(&over).unwrap_err();
1676        let text = format!("{err:?}");
1677        assert!(text.contains("too many"), "{text}");
1678    }
1679}