1use crate::bip152::ShortTxId;
8use crate::error::ProtocolError;
9use crate::validation::ProtocolValidationContext;
10use crate::{BitcoinProtocolEngine, ProtocolConfig, Result};
11use blvm_consensus::error::ConsensusError;
12use blvm_consensus::types::UtxoSet;
13use blvm_consensus::types::{Block, BlockHeader, Hash, Transaction, ValidationResult};
14use std::sync::Arc;
15use thiserror::Error;
16
17pub mod commons {
19 pub use crate::commons::*;
20}
21
22#[cfg(all(
24 feature = "bip324",
25 any(target_arch = "x86_64", target_arch = "aarch64")
26))]
27pub mod v2_transport {
28 pub use crate::v2_transport::*;
29}
30
31#[cfg(test)]
32mod bip155_tests;
33
34#[derive(Debug, Clone, PartialEq)]
38pub enum NetworkMessage {
39 Version(VersionMessage),
40 VerAck,
41 Addr(AddrMessage),
42 AddrV2(AddrV2Message), Inv(InvMessage),
44 GetData(GetDataMessage),
45 GetHeaders(GetHeadersMessage),
46 Headers(HeadersMessage),
47 Block(Arc<Block>, Vec<Vec<blvm_consensus::segwit::Witness>>),
50 Tx(Arc<Transaction>),
51 Ping(PingMessage),
52 Pong(PongMessage),
53 MemPool,
54 FeeFilter(FeeFilterMessage),
55 GetBlocks(GetBlocksMessage),
57 GetAddr,
58 NotFound(NotFoundMessage),
59 Reject(RejectMessage),
60 SendHeaders,
61 SendCmpct(SendCmpctMessage),
63 CmpctBlock(CmpctBlockMessage),
64 GetBlockTxn(GetBlockTxnMessage),
65 BlockTxn(BlockTxnMessage),
66 #[cfg(feature = "utxo-commitments")]
68 GetUTXOSet(commons::GetUTXOSetMessage),
69 #[cfg(feature = "utxo-commitments")]
70 UTXOSet(commons::UTXOSetMessage),
71 #[cfg(feature = "utxo-commitments")]
72 GetFilteredBlock(commons::GetFilteredBlockMessage),
73 #[cfg(feature = "utxo-commitments")]
74 FilteredBlock(commons::FilteredBlockMessage),
75 GetBanList(commons::GetBanListMessage),
76 BanList(commons::BanListMessage),
77}
78
79#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
81pub struct VersionMessage {
82 pub version: u32,
83 pub services: u64,
84 pub timestamp: i64,
85 pub addr_recv: NetworkAddress,
86 pub addr_from: NetworkAddress,
87 pub nonce: u64,
88 pub user_agent: String,
89 pub start_height: i32,
90 pub relay: bool,
91}
92
93#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
99pub struct BlockMessage {
100 pub block: Block,
101 #[serde(skip_serializing_if = "Vec::is_empty", default)]
103 pub witnesses: Vec<Vec<blvm_consensus::segwit::Witness>>,
104}
105
106#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
108pub struct TxMessage {
109 pub transaction: Transaction,
110 #[serde(skip_serializing_if = "Vec::is_empty", default)]
112 pub witnesses: Vec<crate::segwit::Witness>,
113}
114
115#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
120pub struct CompactBlockMessage {
121 pub compact_block: crate::bip152::CompactBlock,
122}
123
124#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
126pub struct AddrMessage {
127 pub addresses: Vec<NetworkAddress>,
128}
129
130#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
132pub struct InvMessage {
133 pub inventory: Vec<InventoryVector>,
134}
135
136#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
138pub struct GetDataMessage {
139 pub inventory: Vec<InventoryVector>,
140}
141
142#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
144pub struct GetHeadersMessage {
145 pub version: u32,
146 pub block_locator_hashes: Vec<Hash>,
147 pub hash_stop: Hash,
148}
149
150#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
152pub struct HeadersMessage {
153 pub headers: Vec<BlockHeader>,
154}
155
156#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
158pub struct PingMessage {
159 pub nonce: u64,
160}
161
162#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
164pub struct PongMessage {
165 pub nonce: u64,
166}
167
168#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
170pub struct FeeFilterMessage {
171 pub feerate: u64,
172}
173
174#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
176pub struct GetBlocksMessage {
177 pub version: u32,
178 pub block_locator_hashes: Vec<Hash>,
179 pub hash_stop: Hash,
180}
181
182#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
184pub struct NotFoundMessage {
185 pub inventory: Vec<InventoryVector>,
186}
187
188#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
190pub struct RejectMessage {
191 pub message: String, pub code: u8, pub reason: String, pub extra_data: Option<Hash>, }
196
197#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
199pub struct SendCmpctMessage {
200 pub version: u64,
202 pub prefer_cmpct: u8,
204}
205
206#[derive(Debug, Clone, PartialEq, Eq)]
208pub struct CmpctBlockMessage {
209 pub header: BlockHeader,
211 pub nonce: u64,
213 pub short_ids: Vec<ShortTxId>,
215 pub prefilled_txs: Vec<PrefilledTransaction>,
217}
218
219#[derive(Debug, Clone, PartialEq, Eq)]
221pub struct PrefilledTransaction {
222 pub index: u16,
224 pub tx: Transaction,
226 pub witness: Option<Vec<blvm_consensus::segwit::Witness>>,
228}
229
230#[derive(Debug, Clone, PartialEq, Eq, Error)]
232pub enum CompactBlockWireConvertError {
233 #[error("prefilled transaction index {0} does not fit in BIP152 u16 index")]
234 PrefilledIndexTooLarge(usize),
235 #[error("duplicate prefilled transaction index {0}")]
236 DuplicatePrefilledIndex(usize),
237}
238
239impl TryFrom<crate::bip152::CompactBlock> for CmpctBlockMessage {
240 type Error = CompactBlockWireConvertError;
241
242 fn try_from(mut value: crate::bip152::CompactBlock) -> std::result::Result<Self, Self::Error> {
247 value.prefilled_txs.sort_by_key(|(i, _, _)| *i);
248 let mut prefilled_txs = Vec::with_capacity(value.prefilled_txs.len());
249 let mut prev_idx: Option<usize> = None;
250 for (idx, tx, witness) in value.prefilled_txs {
251 if prev_idx == Some(idx) {
252 return Err(CompactBlockWireConvertError::DuplicatePrefilledIndex(idx));
253 }
254 prev_idx = Some(idx);
255 let index = u16::try_from(idx)
256 .map_err(|_| CompactBlockWireConvertError::PrefilledIndexTooLarge(idx))?;
257 prefilled_txs.push(PrefilledTransaction { index, tx, witness });
258 }
259 Ok(CmpctBlockMessage {
260 header: value.header,
261 nonce: value.nonce,
262 short_ids: value.short_ids,
263 prefilled_txs,
264 })
265 }
266}
267
268impl From<&CmpctBlockMessage> for crate::bip152::CompactBlock {
269 fn from(msg: &CmpctBlockMessage) -> Self {
270 Self {
271 header: msg.header.clone(),
272 nonce: msg.nonce,
273 short_ids: msg.short_ids.clone(),
274 prefilled_txs: msg
275 .prefilled_txs
276 .iter()
277 .map(|p| (usize::from(p.index), p.tx.clone(), p.witness.clone()))
278 .collect(),
279 }
280 }
281}
282
283impl From<CmpctBlockMessage> for crate::bip152::CompactBlock {
284 fn from(msg: CmpctBlockMessage) -> Self {
285 Self {
286 header: msg.header,
287 nonce: msg.nonce,
288 short_ids: msg.short_ids,
289 prefilled_txs: msg
290 .prefilled_txs
291 .into_iter()
292 .map(|p| (usize::from(p.index), p.tx, p.witness))
293 .collect(),
294 }
295 }
296}
297
298#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
300pub struct GetBlockTxnMessage {
301 pub block_hash: Hash,
303 pub indices: Vec<u16>,
305}
306
307#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
309pub struct BlockTxnMessage {
310 pub block_hash: Hash,
312 pub transactions: Vec<Transaction>,
314 pub witnesses: Option<Vec<Vec<blvm_consensus::segwit::Witness>>>,
316}
317
318#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
320pub struct NetworkAddress {
321 pub services: u64,
322 pub ip: [u8; 16], pub port: u16,
324 pub time: u32,
326}
327
328#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
330#[repr(u8)]
331pub enum AddressType {
332 IPv4 = 1,
333 IPv6 = 2,
334 TorV2 = 3,
335 TorV3 = 4,
336 I2P = 5,
337 CJDNS = 6,
338}
339
340impl AddressType {
341 pub fn address_length(&self) -> usize {
343 match self {
344 AddressType::IPv4 => 4,
345 AddressType::IPv6 => 16,
346 AddressType::TorV2 => 10,
347 AddressType::TorV3 => 32,
348 AddressType::I2P => 32,
349 AddressType::CJDNS => 16,
350 }
351 }
352
353 pub fn from_u8(value: u8) -> Option<Self> {
355 match value {
356 1 => Some(AddressType::IPv4),
357 2 => Some(AddressType::IPv6),
358 3 => Some(AddressType::TorV2),
359 4 => Some(AddressType::TorV3),
360 5 => Some(AddressType::I2P),
361 6 => Some(AddressType::CJDNS),
362 _ => None,
363 }
364 }
365}
366
367#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
369pub struct AddrV2Message {
370 pub addresses: Vec<NetworkAddressV2>,
371}
372
373#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
375pub struct NetworkAddressV2 {
376 pub time: u32,
377 pub services: u64,
378 pub address_type: AddressType,
379 pub address: Vec<u8>, pub port: u16,
381}
382
383impl NetworkAddressV2 {
384 pub fn new(
386 time: u32,
387 services: u64,
388 address_type: AddressType,
389 address: Vec<u8>,
390 port: u16,
391 ) -> Result<Self> {
392 let expected_len = address_type.address_length();
393 if address.len() != expected_len {
394 return Err(ProtocolError::Consensus(ConsensusError::Serialization(
395 std::borrow::Cow::Owned(format!(
396 "Invalid address length for type {:?}: expected {}, got {}",
397 address_type,
398 expected_len,
399 address.len()
400 )),
401 )));
402 }
403 Ok(Self {
404 time,
405 services,
406 address_type,
407 address,
408 port,
409 })
410 }
411
412 pub fn to_legacy(&self) -> Option<NetworkAddress> {
414 match self.address_type {
415 AddressType::IPv4 => {
416 if self.address.len() == 4 {
417 let mut ipv6 = [0u8; 16];
419 ipv6[10] = 0xff;
420 ipv6[11] = 0xff;
421 ipv6[12..16].copy_from_slice(&self.address);
422 Some(NetworkAddress {
423 time: self.time,
424 services: self.services,
425 ip: ipv6,
426 port: self.port,
427 })
428 } else {
429 None
430 }
431 }
432 AddressType::IPv6 => {
433 if self.address.len() == 16 {
434 let mut ipv6 = [0u8; 16];
435 ipv6.copy_from_slice(&self.address);
436 Some(NetworkAddress {
437 time: self.time,
438 services: self.services,
439 ip: ipv6,
440 port: self.port,
441 })
442 } else {
443 None
444 }
445 }
446 _ => None, }
448 }
449}
450
451#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
453pub struct InventoryVector {
454 pub inv_type: u32,
455 pub hash: Hash,
456}
457
458#[derive(Debug, Clone)]
460pub enum NetworkResponse {
461 Ok,
462 SendMessage(Box<NetworkMessage>),
463 SendMessages(Vec<NetworkMessage>),
464 Reject(String),
465}
466
467#[derive(Clone)]
469pub struct PeerState {
470 pub version: u32,
471 pub services: u64,
472 pub user_agent: String,
473 pub start_height: i32,
474 pub handshake_complete: bool,
475 pub known_addresses: Vec<NetworkAddress>,
476 pub ping_nonce: Option<u64>,
477 pub last_pong: Option<std::time::SystemTime>,
478 pub min_fee_rate: Option<u64>,
479 pub prefer_headers: bool,
481 pub compact_block_version: Option<u8>,
483 pub prefer_compact_block: bool,
485 #[cfg(all(
488 feature = "bip324",
489 any(target_arch = "x86_64", target_arch = "aarch64")
490 ))]
491 pub v2_transport: Option<std::sync::Arc<crate::v2_transport::V2Transport>>,
492 #[cfg(all(
494 feature = "bip324",
495 any(target_arch = "x86_64", target_arch = "aarch64")
496 ))]
497 pub v2_handshake: Option<std::sync::Arc<crate::v2_transport::V2Handshake>>,
498}
499
500impl PeerState {
501 pub fn new() -> Self {
502 Self {
503 version: 0,
504 services: 0,
505 user_agent: String::new(),
506 start_height: 0,
507 handshake_complete: false,
508 known_addresses: Vec::new(),
509 ping_nonce: None,
510 last_pong: None,
511 min_fee_rate: None,
512 prefer_headers: false,
513 compact_block_version: None,
514 prefer_compact_block: false,
515 #[cfg(all(
516 feature = "bip324",
517 any(target_arch = "x86_64", target_arch = "aarch64")
518 ))]
519 v2_transport: None,
520 #[cfg(all(
521 feature = "bip324",
522 any(target_arch = "x86_64", target_arch = "aarch64")
523 ))]
524 v2_handshake: None,
525 }
526 }
527
528 #[cfg(all(
530 feature = "bip324",
531 any(target_arch = "x86_64", target_arch = "aarch64")
532 ))]
533 pub fn supports_v2_transport(&self) -> bool {
534 use crate::service_flags::{has_flag, standard};
535 has_flag(self.services, standard::NODE_V2_TRANSPORT)
536 }
537
538 #[cfg(all(
540 feature = "bip324",
541 any(target_arch = "x86_64", target_arch = "aarch64")
542 ))]
543 pub fn is_v2_transport_active(&self) -> bool {
544 self.v2_transport.is_some()
545 }
546}
547
548impl Default for PeerState {
549 fn default() -> Self {
550 Self::new()
551 }
552}
553
554#[derive(Debug, Clone)]
556pub enum ChainObject {
557 Block(Arc<Block>),
558 Transaction(Arc<Transaction>),
559}
560
561impl ChainObject {
562 pub fn as_block(&self) -> Option<&Arc<Block>> {
563 match self {
564 ChainObject::Block(block) => Some(block),
565 _ => None,
566 }
567 }
568
569 pub fn as_transaction(&self) -> Option<&Arc<Transaction>> {
570 match self {
571 ChainObject::Transaction(tx) => Some(tx),
572 _ => None,
573 }
574 }
575}
576
577pub trait ChainStateAccess {
583 fn has_object(&self, hash: &Hash) -> bool;
585
586 fn get_object(&self, hash: &Hash) -> Option<ChainObject>;
588
589 fn get_headers_for_locator(&self, locator: &[Hash], stop: &Hash) -> Vec<BlockHeader>;
592
593 fn get_mempool_transactions(&self) -> Vec<Transaction>;
595
596 fn get_block_witnesses(
598 &self,
599 _hash: &Hash,
600 ) -> Option<Vec<Vec<blvm_consensus::segwit::Witness>>> {
601 None
602 }
603}
604
605pub fn process_network_message(
623 engine: &BitcoinProtocolEngine,
624 message: &NetworkMessage,
625 peer_state: &mut PeerState,
626 chain_access: Option<&dyn ChainStateAccess>,
627 utxo_set: Option<&UtxoSet>,
628 height: Option<u64>,
629) -> Result<NetworkResponse> {
630 let config = engine.get_config();
631 match message {
632 NetworkMessage::Version(version) => process_version_message(version, peer_state, config),
633 NetworkMessage::VerAck => process_verack_message(peer_state),
634 NetworkMessage::Addr(addr) => process_addr_message(addr, peer_state, config),
635 NetworkMessage::AddrV2(addrv2) => process_addrv2_message(addrv2, peer_state, config),
636 NetworkMessage::Inv(inv) => process_inv_message(inv, chain_access, config),
637 NetworkMessage::GetData(getdata) => process_getdata_message(getdata, chain_access, config),
638 NetworkMessage::GetHeaders(getheaders) => {
639 process_getheaders_message(getheaders, chain_access, config)
640 }
641 NetworkMessage::Headers(headers) => process_headers_message(headers, config),
642 NetworkMessage::Block(block, witnesses) => {
643 process_block_message(engine, block, witnesses, utxo_set, height, config)
644 }
645 NetworkMessage::Tx(tx) => process_tx_message(engine, tx, height),
646 NetworkMessage::Ping(ping) => process_ping_message(ping, peer_state),
647 NetworkMessage::Pong(pong) => process_pong_message(pong, peer_state),
648 NetworkMessage::MemPool => process_mempool_message(chain_access),
649 NetworkMessage::FeeFilter(feefilter) => process_feefilter_message(feefilter, peer_state),
650 NetworkMessage::GetBlocks(getblocks) => {
651 process_getblocks_message(getblocks, chain_access, config)
652 }
653 NetworkMessage::GetAddr => process_getaddr_message(peer_state, config),
654 NetworkMessage::NotFound(notfound) => process_notfound_message(notfound, config),
655 NetworkMessage::Reject(reject) => process_reject_message(reject, config),
656 NetworkMessage::SendHeaders => process_sendheaders_message(peer_state),
657 NetworkMessage::SendCmpct(sendcmpct) => {
658 process_sendcmpct_message(sendcmpct, peer_state, config)
659 }
660 NetworkMessage::CmpctBlock(cmpctblock) => process_cmpctblock_message(cmpctblock),
661 NetworkMessage::GetBlockTxn(getblocktxn) => {
662 process_getblocktxn_message(getblocktxn, chain_access, config)
663 }
664 NetworkMessage::BlockTxn(blocktxn) => process_blocktxn_message(blocktxn),
665 #[cfg(feature = "utxo-commitments")]
666 NetworkMessage::GetUTXOSet(getutxoset) => process_getutxoset_message(getutxoset),
667 #[cfg(feature = "utxo-commitments")]
668 NetworkMessage::UTXOSet(utxoset) => process_utxoset_message(utxoset),
669 #[cfg(feature = "utxo-commitments")]
670 NetworkMessage::GetFilteredBlock(getfiltered) => {
671 process_getfilteredblock_message(getfiltered)
672 }
673 #[cfg(feature = "utxo-commitments")]
674 NetworkMessage::FilteredBlock(filtered) => process_filteredblock_message(filtered),
675 NetworkMessage::GetBanList(getbanlist) => process_getbanlist_message(getbanlist),
676 NetworkMessage::BanList(banlist) => process_banlist_message(banlist),
677 }
678}
679
680fn process_version_message(
682 version: &VersionMessage,
683 peer_state: &mut PeerState,
684 config: &ProtocolConfig,
685) -> Result<NetworkResponse> {
686 if version.version < 70001 {
688 return Ok(NetworkResponse::Reject("Version too old".into()));
689 }
690
691 if version.user_agent.len() > config.network_limits.max_user_agent_length {
693 return Ok(NetworkResponse::Reject(format!(
694 "User agent too long (max {} bytes)",
695 config.network_limits.max_user_agent_length
696 )));
697 }
698
699 peer_state.version = version.version;
701 peer_state.services = version.services;
702 peer_state.user_agent = version.user_agent.clone();
703 peer_state.start_height = version.start_height;
704
705 #[cfg(all(
707 feature = "bip324",
708 any(target_arch = "x86_64", target_arch = "aarch64")
709 ))]
710 {
711 use crate::service_flags::{has_flag, standard};
712 let peer_supports_v2 = has_flag(version.services, standard::NODE_V2_TRANSPORT);
713 let we_support_v2 = config.service_flags.node_v2_transport;
714
715 if peer_supports_v2 && we_support_v2 {
716 let handshake = crate::v2_transport::V2Handshake::new_responder();
718 peer_state.v2_handshake = Some(std::sync::Arc::new(handshake));
719 }
722 }
723
724 Ok(NetworkResponse::SendMessage(Box::new(
726 NetworkMessage::VerAck,
727 )))
728}
729
730fn process_verack_message(peer_state: &mut PeerState) -> Result<NetworkResponse> {
732 peer_state.handshake_complete = true;
733 Ok(NetworkResponse::Ok)
734}
735
736fn process_addr_message(
738 addr: &AddrMessage,
739 peer_state: &mut PeerState,
740 config: &ProtocolConfig,
741) -> Result<NetworkResponse> {
742 if addr.addresses.len() > config.network_limits.max_addr_addresses {
744 return Ok(NetworkResponse::Reject(format!(
745 "Too many addresses (max {})",
746 config.network_limits.max_addr_addresses
747 )));
748 }
749
750 peer_state.known_addresses.extend(addr.addresses.clone());
752
753 Ok(NetworkResponse::Ok)
754}
755
756fn process_addrv2_message(
758 addrv2: &AddrV2Message,
759 peer_state: &mut PeerState,
760 config: &ProtocolConfig,
761) -> Result<NetworkResponse> {
762 if addrv2.addresses.len() > config.network_limits.max_addr_addresses {
764 return Ok(NetworkResponse::Reject(format!(
765 "Too many addresses (max {})",
766 config.network_limits.max_addr_addresses
767 )));
768 }
769
770 for addr_v2 in &addrv2.addresses {
772 if let Some(legacy_addr) = addr_v2.to_legacy() {
773 peer_state.known_addresses.push(legacy_addr);
774 }
775 }
778
779 Ok(NetworkResponse::Ok)
780}
781
782fn process_inv_message(
784 inv: &InvMessage,
785 chain_access: Option<&dyn ChainStateAccess>,
786 config: &ProtocolConfig,
787) -> Result<NetworkResponse> {
788 if inv.inventory.len() > config.network_limits.max_inv_items {
790 return Ok(NetworkResponse::Reject(format!(
791 "Too many inventory items (max {})",
792 config.network_limits.max_inv_items
793 )));
794 }
795
796 if let Some(chain) = chain_access {
798 let mut needed_items = Vec::with_capacity(inv.inventory.len());
799 for item in &inv.inventory {
800 if !chain.has_object(&item.hash) {
801 needed_items.push(item.clone());
802 }
803 }
804
805 if !needed_items.is_empty() {
806 return Ok(NetworkResponse::SendMessage(Box::new(
807 NetworkMessage::GetData(GetDataMessage {
808 inventory: needed_items,
809 }),
810 )));
811 }
812 }
813
814 Ok(NetworkResponse::Ok)
815}
816
817fn process_getdata_message(
819 getdata: &GetDataMessage,
820 chain_access: Option<&dyn ChainStateAccess>,
821 config: &ProtocolConfig,
822) -> Result<NetworkResponse> {
823 if getdata.inventory.len() > config.network_limits.max_inv_items {
825 return Ok(NetworkResponse::Reject(format!(
826 "Too many getdata items (max {})",
827 config.network_limits.max_inv_items
828 )));
829 }
830
831 if let Some(chain) = chain_access {
833 let mut responses = Vec::with_capacity(getdata.inventory.len());
834 for item in &getdata.inventory {
835 if let Some(obj) = chain.get_object(&item.hash) {
836 match item.inv_type {
837 1 => {
838 if let Some(tx) = obj.as_transaction() {
840 responses.push(NetworkMessage::Tx(Arc::clone(tx)));
841 }
842 }
843 2 => {
844 if let Some(block) = obj.as_block() {
846 let Some(witnesses) = chain.get_block_witnesses(&item.hash) else {
847 continue;
850 };
851 responses.push(NetworkMessage::Block(Arc::clone(block), witnesses));
852 }
853 }
854 _ => {
855 }
857 }
858 }
859 }
860
861 if !responses.is_empty() {
862 return Ok(NetworkResponse::SendMessages(responses));
863 }
864 }
865
866 Ok(NetworkResponse::Ok)
867}
868
869fn process_getheaders_message(
871 getheaders: &GetHeadersMessage,
872 chain_access: Option<&dyn ChainStateAccess>,
873 config: &ProtocolConfig,
874) -> Result<NetworkResponse> {
875 if getheaders.block_locator_hashes.len() > config.validation.max_locator_hashes {
877 return Ok(NetworkResponse::Reject(format!(
878 "Too many locator hashes (max {})",
879 config.validation.max_locator_hashes
880 )));
881 }
882
883 if let Some(chain) = chain_access {
885 let headers =
886 chain.get_headers_for_locator(&getheaders.block_locator_hashes, &getheaders.hash_stop);
887 return Ok(NetworkResponse::SendMessage(Box::new(
888 NetworkMessage::Headers(HeadersMessage { headers }),
889 )));
890 }
891
892 Ok(NetworkResponse::Reject("Chain access not available".into()))
893}
894
895fn process_headers_message(
897 headers: &HeadersMessage,
898 config: &ProtocolConfig,
899) -> Result<NetworkResponse> {
900 if headers.headers.len() > config.network_limits.max_headers {
902 return Ok(NetworkResponse::Reject(format!(
903 "Too many headers (max {})",
904 config.network_limits.max_headers
905 )));
906 }
907
908 Ok(NetworkResponse::Ok)
911}
912
913fn process_block_message(
915 engine: &BitcoinProtocolEngine,
916 block: &Block,
917 witnesses: &[Vec<blvm_consensus::segwit::Witness>],
918 utxo_set: Option<&UtxoSet>,
919 height: Option<u64>,
920 config: &ProtocolConfig,
921) -> Result<NetworkResponse> {
922 if block.transactions.len() > config.validation.max_txs_per_block {
924 return Err(crate::error::ProtocolError::MessageTooLarge {
925 size: block.transactions.len(),
926 max: config.validation.max_txs_per_block,
927 });
928 }
929
930 if let (Some(utxos), Some(h)) = (utxo_set, height) {
932 let context = ProtocolValidationContext::new(engine.get_protocol_version(), h)?;
933 let result = engine
935 .validate_block_with_protocol_and_witnesses(block, witnesses, utxos, h, &context)?;
936
937 match result {
938 ValidationResult::Valid => Ok(NetworkResponse::Ok),
939 ValidationResult::Invalid(reason) => {
940 Ok(NetworkResponse::Reject(format!("Invalid block: {reason}")))
941 }
942 }
943 } else {
944 Err(crate::error::ProtocolError::Configuration(
945 "Missing validation context (utxo_set and height required)".into(),
946 ))
947 }
948}
949
950fn process_tx_message(
952 engine: &BitcoinProtocolEngine,
953 tx: &Transaction,
954 height: Option<u64>,
955) -> Result<NetworkResponse> {
956 let context =
958 ProtocolValidationContext::new(engine.get_protocol_version(), height.unwrap_or(0))?;
959 let result = engine.validate_transaction_with_protocol(tx, &context)?;
960
961 match result {
962 ValidationResult::Valid => Ok(NetworkResponse::Ok),
963 ValidationResult::Invalid(reason) => Ok(NetworkResponse::Reject(format!(
964 "Invalid transaction: {reason}"
965 ))),
966 }
967}
968
969fn process_ping_message(
971 ping: &PingMessage,
972 _peer_state: &mut PeerState,
973) -> Result<NetworkResponse> {
974 let pong = NetworkMessage::Pong(PongMessage { nonce: ping.nonce });
975 Ok(NetworkResponse::SendMessage(Box::new(pong)))
976}
977
978fn process_pong_message(pong: &PongMessage, peer_state: &mut PeerState) -> Result<NetworkResponse> {
980 if peer_state.ping_nonce == Some(pong.nonce) {
982 peer_state.ping_nonce = None;
983 peer_state.last_pong = Some(std::time::SystemTime::now());
984 }
985
986 Ok(NetworkResponse::Ok)
987}
988
989fn process_mempool_message(chain_access: Option<&dyn ChainStateAccess>) -> Result<NetworkResponse> {
991 if let Some(chain) = chain_access {
993 let mempool_txs = chain.get_mempool_transactions();
994 let mut responses = Vec::with_capacity(mempool_txs.len());
995
996 for tx in mempool_txs {
997 responses.push(NetworkMessage::Tx(Arc::new(tx)));
998 }
999
1000 if !responses.is_empty() {
1001 return Ok(NetworkResponse::SendMessages(responses));
1002 }
1003 }
1004
1005 Ok(NetworkResponse::Ok)
1006}
1007
1008fn process_feefilter_message(
1010 feefilter: &FeeFilterMessage,
1011 peer_state: &mut PeerState,
1012) -> Result<NetworkResponse> {
1013 peer_state.min_fee_rate = Some(feefilter.feerate);
1014 Ok(NetworkResponse::Ok)
1015}
1016
1017fn process_getblocks_message(
1019 getblocks: &GetBlocksMessage,
1020 chain_access: Option<&dyn ChainStateAccess>,
1021 config: &ProtocolConfig,
1022) -> Result<NetworkResponse> {
1023 if getblocks.block_locator_hashes.len() > config.validation.max_locator_hashes {
1025 return Ok(NetworkResponse::Reject(format!(
1026 "Too many locator hashes (max {})",
1027 config.validation.max_locator_hashes
1028 )));
1029 }
1030
1031 if let Some(chain) = chain_access {
1032 use blvm_consensus::block::block_header_hash;
1033
1034 const MAX_GETBLOCKS_INV: usize = 500;
1036 let headers =
1037 chain.get_headers_for_locator(&getblocks.block_locator_hashes, &getblocks.hash_stop);
1038 if headers.is_empty() {
1039 return Ok(NetworkResponse::Ok);
1040 }
1041
1042 let inventory: Vec<InventoryVector> = headers
1043 .into_iter()
1044 .take(MAX_GETBLOCKS_INV)
1045 .map(|header| InventoryVector {
1046 inv_type: 2, hash: block_header_hash(&header),
1048 })
1049 .collect();
1050
1051 return Ok(NetworkResponse::SendMessage(Box::new(NetworkMessage::Inv(
1052 InvMessage { inventory },
1053 ))));
1054 }
1055
1056 Ok(NetworkResponse::Ok)
1057}
1058
1059fn process_getaddr_message(
1061 peer_state: &mut PeerState,
1062 config: &ProtocolConfig,
1063) -> Result<NetworkResponse> {
1064 if !peer_state.known_addresses.is_empty() {
1066 let max_addrs = config
1068 .network_limits
1069 .max_addr_addresses
1070 .min(peer_state.known_addresses.len());
1071 let mut addresses = Vec::with_capacity(max_addrs);
1072 addresses.extend(peer_state.known_addresses.iter().take(max_addrs).cloned());
1073
1074 return Ok(NetworkResponse::SendMessage(Box::new(
1075 NetworkMessage::Addr(AddrMessage { addresses }),
1076 )));
1077 }
1078
1079 Ok(NetworkResponse::Ok)
1080}
1081
1082fn process_notfound_message(
1084 notfound: &NotFoundMessage,
1085 config: &ProtocolConfig,
1086) -> Result<NetworkResponse> {
1087 if notfound.inventory.len() > config.network_limits.max_inv_items {
1089 return Ok(NetworkResponse::Reject(format!(
1090 "Too many notfound items (max {})",
1091 config.network_limits.max_inv_items
1092 )));
1093 }
1094
1095 Ok(NetworkResponse::Ok)
1097}
1098
1099fn process_reject_message(
1101 reject: &RejectMessage,
1102 _config: &ProtocolConfig,
1103) -> Result<NetworkResponse> {
1104 if reject.message.len() > 12 {
1107 return Ok(NetworkResponse::Reject(
1108 "Invalid reject message name".into(),
1109 ));
1110 }
1111
1112 if reject.reason.len() > 111 {
1115 return Ok(NetworkResponse::Reject("Reject reason too long".into()));
1116 }
1117
1118 Ok(NetworkResponse::Ok)
1121}
1122
1123fn process_sendheaders_message(peer_state: &mut PeerState) -> Result<NetworkResponse> {
1125 peer_state.prefer_headers = true;
1126 Ok(NetworkResponse::Ok)
1127}
1128
1129fn process_sendcmpct_message(
1131 sendcmpct: &SendCmpctMessage,
1132 peer_state: &mut PeerState,
1133 config: &ProtocolConfig,
1134) -> Result<NetworkResponse> {
1135 let valid_versions = [1, 2];
1137 if !valid_versions.contains(&sendcmpct.version) {
1138 return Ok(NetworkResponse::Reject(
1139 "Invalid compact block version".into(),
1140 ));
1141 }
1142
1143 if !config.compact_blocks.enabled {
1145 return Ok(NetworkResponse::Reject("Compact blocks not enabled".into()));
1146 }
1147
1148 peer_state.compact_block_version = Some(sendcmpct.version as u8);
1150 peer_state.prefer_compact_block = sendcmpct.prefer_cmpct != 0;
1151 Ok(NetworkResponse::Ok)
1152}
1153
1154fn process_cmpctblock_message(_cmpctblock: &CmpctBlockMessage) -> Result<NetworkResponse> {
1156 Ok(NetworkResponse::Reject(
1157 "Compact block reconstruction not implemented".into(),
1158 ))
1159}
1160
1161fn process_getblocktxn_message(
1163 getblocktxn: &GetBlockTxnMessage,
1164 chain_access: Option<&dyn ChainStateAccess>,
1165 config: &ProtocolConfig,
1166) -> Result<NetworkResponse> {
1167 if getblocktxn.indices.len() > config.compact_blocks.max_blocktxn_indices {
1169 return Ok(NetworkResponse::Reject(format!(
1170 "Too many transaction indices (max {})",
1171 config.compact_blocks.max_blocktxn_indices
1172 )));
1173 }
1174
1175 if let Some(chain) = chain_access {
1177 if let Some(obj) = chain.get_object(&getblocktxn.block_hash) {
1178 if let Some(block) = obj.as_block() {
1179 let stored_witnesses = chain.get_block_witnesses(&getblocktxn.block_hash);
1180 let mut transactions = Vec::with_capacity(getblocktxn.indices.len());
1181 let mut req_witnesses = Vec::with_capacity(getblocktxn.indices.len());
1182
1183 for &index in &getblocktxn.indices {
1184 let idx = index as usize;
1185 if idx >= block.transactions.len() {
1186 return Ok(NetworkResponse::Reject(
1187 "getblocktxn index past end of block".into(),
1188 ));
1189 }
1190 transactions.push(block.transactions[idx].clone());
1191 let tx_witness = stored_witnesses
1192 .as_ref()
1193 .and_then(|w| w.get(idx).cloned())
1194 .unwrap_or_else(|| {
1195 block.transactions[idx]
1196 .inputs
1197 .iter()
1198 .map(|_| Vec::new())
1199 .collect()
1200 });
1201 req_witnesses.push(tx_witness);
1202 }
1203
1204 if !transactions.is_empty() {
1205 return Ok(NetworkResponse::SendMessage(Box::new(
1206 NetworkMessage::BlockTxn(BlockTxnMessage {
1207 block_hash: getblocktxn.block_hash,
1208 transactions,
1209 witnesses: Some(req_witnesses),
1210 }),
1211 )));
1212 }
1213 }
1214 }
1215 }
1216
1217 Ok(NetworkResponse::Ok)
1218}
1219
1220fn process_blocktxn_message(_blocktxn: &BlockTxnMessage) -> Result<NetworkResponse> {
1222 Ok(NetworkResponse::Reject(
1223 "Compact block reconstruction not implemented".into(),
1224 ))
1225}
1226
1227#[cfg(feature = "utxo-commitments")]
1228fn process_getutxoset_message(_getutxoset: &commons::GetUTXOSetMessage) -> Result<NetworkResponse> {
1230 Ok(NetworkResponse::Reject(
1231 "UTXO set relay not implemented".into(),
1232 ))
1233}
1234
1235#[cfg(feature = "utxo-commitments")]
1236fn process_utxoset_message(_utxoset: &commons::UTXOSetMessage) -> Result<NetworkResponse> {
1238 Ok(NetworkResponse::Reject(
1239 "UTXO set relay not implemented".into(),
1240 ))
1241}
1242
1243#[cfg(feature = "utxo-commitments")]
1244fn process_getfilteredblock_message(
1246 _getfiltered: &commons::GetFilteredBlockMessage,
1247) -> Result<NetworkResponse> {
1248 Ok(NetworkResponse::Reject(
1249 "Filtered block relay not implemented".into(),
1250 ))
1251}
1252
1253#[cfg(feature = "utxo-commitments")]
1254fn process_filteredblock_message(
1256 _filtered: &commons::FilteredBlockMessage,
1257) -> Result<NetworkResponse> {
1258 Ok(NetworkResponse::Reject(
1259 "Filtered block relay not implemented".into(),
1260 ))
1261}
1262
1263fn process_getbanlist_message(_getbanlist: &commons::GetBanListMessage) -> Result<NetworkResponse> {
1265 Ok(NetworkResponse::Reject(
1266 "Ban list relay not implemented".into(),
1267 ))
1268}
1269
1270fn process_banlist_message(_banlist: &commons::BanListMessage) -> Result<NetworkResponse> {
1272 Ok(NetworkResponse::Reject(
1273 "Ban list relay not implemented".into(),
1274 ))
1275}
1276
1277#[cfg(test)]
1278fn assert_getblocktxn_past_end_rejects() {
1279 struct TwoTx;
1280 impl ChainStateAccess for TwoTx {
1281 fn has_object(&self, _hash: &Hash) -> bool {
1282 true
1283 }
1284 fn get_object(&self, hash: &Hash) -> Option<ChainObject> {
1285 let tx = Transaction {
1286 version: 1,
1287 inputs: vec![].into(),
1288 outputs: vec![].into(),
1289 lock_time: 0,
1290 };
1291 let block = Block {
1292 header: BlockHeader {
1293 version: 1,
1294 prev_block_hash: [0; 32],
1295 merkle_root: [0; 32],
1296 timestamp: 1,
1297 bits: 1,
1298 nonce: 1,
1299 },
1300 transactions: vec![tx.clone(), tx].into_boxed_slice(),
1301 };
1302 if hash == &[9u8; 32] {
1303 Some(ChainObject::Block(Arc::new(block)))
1304 } else {
1305 None
1306 }
1307 }
1308 fn get_headers_for_locator(&self, _locator: &[Hash], _stop: &Hash) -> Vec<BlockHeader> {
1309 Vec::new()
1310 }
1311 fn get_mempool_transactions(&self) -> Vec<Transaction> {
1312 Vec::new()
1313 }
1314 }
1315 let msg = GetBlockTxnMessage {
1316 block_hash: [9u8; 32],
1317 indices: vec![5],
1318 };
1319 let response =
1320 process_getblocktxn_message(&msg, Some(&TwoTx), &ProtocolConfig::default()).unwrap();
1321 assert!(
1322 matches!(response, NetworkResponse::Reject(_)),
1323 "expected reject, got {response:?}"
1324 );
1325}
1326
1327#[cfg(test)]
1328#[test]
1329fn getblocktxn_index_past_block_returns_reject_and_no_blocktxn() {
1330 assert_getblocktxn_past_end_rejects();
1331}