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