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(crate) struct PendingCompact {
470 pub header: BlockHeader,
471 pub nonce: u64,
472 pub slots: Vec<Option<(Transaction, Vec<blvm_consensus::segwit::Witness>)>>,
473 pub missing: Vec<(usize, ShortTxId)>,
474}
475
476#[derive(Clone)]
478pub struct PeerState {
479 pub version: u32,
480 pub services: u64,
481 pub user_agent: String,
482 pub start_height: i32,
483 pub handshake_complete: bool,
484 pub known_addresses: Vec<NetworkAddress>,
485 pub ping_nonce: Option<u64>,
486 pub last_pong: Option<std::time::SystemTime>,
487 pub min_fee_rate: Option<u64>,
489 pub relay_tx: bool,
492 pub prefer_headers: bool,
494 pub compact_block_version: Option<u8>,
496 pub prefer_compact_block: bool,
498 pub(crate) pending_compact: Option<PendingCompact>,
500 #[cfg(all(
503 feature = "bip324",
504 any(target_arch = "x86_64", target_arch = "aarch64")
505 ))]
506 pub v2_transport: Option<std::sync::Arc<crate::v2_transport::V2Transport>>,
507 #[cfg(all(
509 feature = "bip324",
510 any(target_arch = "x86_64", target_arch = "aarch64")
511 ))]
512 pub v2_handshake: Option<std::sync::Arc<crate::v2_transport::V2Handshake>>,
513}
514
515impl PeerState {
516 pub fn new() -> Self {
517 Self {
518 version: 0,
519 services: 0,
520 user_agent: String::new(),
521 start_height: 0,
522 handshake_complete: false,
523 known_addresses: Vec::new(),
524 ping_nonce: None,
525 last_pong: None,
526 min_fee_rate: None,
527 relay_tx: true,
528 prefer_headers: false,
529 compact_block_version: None,
530 prefer_compact_block: false,
531 pending_compact: None,
532 #[cfg(all(
533 feature = "bip324",
534 any(target_arch = "x86_64", target_arch = "aarch64")
535 ))]
536 v2_transport: None,
537 #[cfg(all(
538 feature = "bip324",
539 any(target_arch = "x86_64", target_arch = "aarch64")
540 ))]
541 v2_handshake: None,
542 }
543 }
544
545 #[cfg(all(
547 feature = "bip324",
548 any(target_arch = "x86_64", target_arch = "aarch64")
549 ))]
550 pub fn supports_v2_transport(&self) -> bool {
551 use crate::service_flags::{has_flag, standard};
552 has_flag(self.services, standard::NODE_V2_TRANSPORT)
553 }
554
555 #[cfg(all(
557 feature = "bip324",
558 any(target_arch = "x86_64", target_arch = "aarch64")
559 ))]
560 pub fn is_v2_transport_active(&self) -> bool {
561 self.v2_transport.is_some()
562 }
563}
564
565impl Default for PeerState {
566 fn default() -> Self {
567 Self::new()
568 }
569}
570
571#[derive(Debug, Clone)]
573pub enum ChainObject {
574 Block(Arc<Block>),
575 Transaction(Arc<Transaction>),
576}
577
578impl ChainObject {
579 pub fn as_block(&self) -> Option<&Arc<Block>> {
580 match self {
581 ChainObject::Block(block) => Some(block),
582 _ => None,
583 }
584 }
585
586 pub fn as_transaction(&self) -> Option<&Arc<Transaction>> {
587 match self {
588 ChainObject::Transaction(tx) => Some(tx),
589 _ => None,
590 }
591 }
592}
593
594pub trait ChainStateAccess {
600 fn has_object(&self, hash: &Hash) -> bool;
602
603 fn get_object(&self, hash: &Hash) -> Option<ChainObject>;
605
606 fn get_headers_for_locator(&self, locator: &[Hash], stop: &Hash) -> Vec<BlockHeader>;
609
610 fn get_mempool_transactions(&self) -> Vec<Transaction>;
612
613 fn get_block_witnesses(
615 &self,
616 _hash: &Hash,
617 ) -> Option<Vec<Vec<blvm_consensus::segwit::Witness>>> {
618 None
619 }
620
621 fn ban_entries(&self) -> Vec<crate::commons::BanEntry> {
623 Vec::new()
624 }
625
626 fn apply_ban(&self, entry: &crate::commons::BanEntry) -> std::result::Result<(), String> {
629 let _ = entry;
630 Err("ban path is not available".into())
631 }
632
633 fn stored_utxo_commitment(
635 &self,
636 _block_hash: &Hash,
637 ) -> std::result::Result<Option<crate::commons::UTXOCommitment>, String> {
638 Ok(None)
639 }
640
641 fn stored_block_filter(
643 &self,
644 _block_hash: &Hash,
645 ) -> std::result::Result<Option<(BlockHeader, Vec<u8>)>, String> {
646 Ok(None)
647 }
648}
649
650pub fn process_network_message(
668 engine: &BitcoinProtocolEngine,
669 message: &NetworkMessage,
670 peer_state: &mut PeerState,
671 chain_access: Option<&dyn ChainStateAccess>,
672 utxo_set: Option<&UtxoSet>,
673 height: Option<u64>,
674) -> Result<NetworkResponse> {
675 let config = engine.get_config();
676 match message {
677 NetworkMessage::Version(version) => process_version_message(version, peer_state, config),
678 NetworkMessage::VerAck => process_verack_message(peer_state),
679 NetworkMessage::Addr(addr) => process_addr_message(addr, peer_state, config),
680 NetworkMessage::AddrV2(addrv2) => process_addrv2_message(addrv2, peer_state, config),
681 NetworkMessage::Inv(inv) => process_inv_message(inv, chain_access, config),
682 NetworkMessage::GetData(getdata) => process_getdata_message(getdata, chain_access, config),
683 NetworkMessage::GetHeaders(getheaders) => {
684 process_getheaders_message(getheaders, chain_access, config)
685 }
686 NetworkMessage::Headers(headers) => process_headers_message(headers, config),
687 NetworkMessage::Block(block, witnesses) => {
688 process_block_message(engine, block, witnesses, utxo_set, height, config)
689 }
690 NetworkMessage::Tx(tx) => process_tx_message(engine, tx, height),
691 NetworkMessage::Ping(ping) => process_ping_message(ping, peer_state),
692 NetworkMessage::Pong(pong) => process_pong_message(pong, peer_state),
693 NetworkMessage::MemPool => process_mempool_message(chain_access),
694 NetworkMessage::FeeFilter(feefilter) => process_feefilter_message(feefilter, peer_state),
695 NetworkMessage::GetBlocks(getblocks) => {
696 process_getblocks_message(getblocks, chain_access, config)
697 }
698 NetworkMessage::GetAddr => process_getaddr_message(peer_state, config),
699 NetworkMessage::NotFound(notfound) => process_notfound_message(notfound, config),
700 NetworkMessage::Reject(reject) => process_reject_message(reject, config),
701 NetworkMessage::SendHeaders => process_sendheaders_message(peer_state),
702 NetworkMessage::SendCmpct(sendcmpct) => {
703 process_sendcmpct_message(sendcmpct, peer_state, config)
704 }
705 NetworkMessage::CmpctBlock(cmpctblock) => process_cmpctblock_message(
706 engine,
707 cmpctblock,
708 peer_state,
709 chain_access,
710 utxo_set,
711 height,
712 config,
713 ),
714 NetworkMessage::GetBlockTxn(getblocktxn) => {
715 process_getblocktxn_message(getblocktxn, chain_access, config)
716 }
717 NetworkMessage::BlockTxn(blocktxn) => {
718 process_blocktxn_message(engine, blocktxn, peer_state, utxo_set, height, config)
719 }
720 #[cfg(feature = "utxo-commitments")]
721 NetworkMessage::GetUTXOSet(getutxoset) => {
722 process_getutxoset_message(getutxoset, chain_access)
723 }
724 #[cfg(feature = "utxo-commitments")]
725 NetworkMessage::UTXOSet(utxoset) => process_utxoset_message(utxoset, chain_access),
726 #[cfg(feature = "utxo-commitments")]
727 NetworkMessage::GetFilteredBlock(getfiltered) => {
728 process_getfilteredblock_message(getfiltered, chain_access)
729 }
730 #[cfg(feature = "utxo-commitments")]
731 NetworkMessage::FilteredBlock(filtered) => {
732 process_filteredblock_message(filtered, chain_access)
733 }
734 NetworkMessage::GetBanList(getbanlist) => {
735 process_getbanlist_message(getbanlist, chain_access)
736 }
737 NetworkMessage::BanList(banlist) => process_banlist_message(banlist, chain_access),
738 }
739}
740
741fn process_version_message(
743 version: &VersionMessage,
744 peer_state: &mut PeerState,
745 config: &ProtocolConfig,
746) -> Result<NetworkResponse> {
747 if version.version < 70001 {
749 return Ok(NetworkResponse::Reject("Version too old".into()));
750 }
751
752 if version.user_agent.len() > config.network_limits.max_user_agent_length {
754 return Ok(NetworkResponse::Reject(format!(
755 "User agent too long (max {} bytes)",
756 config.network_limits.max_user_agent_length
757 )));
758 }
759
760 peer_state.version = version.version;
762 peer_state.services = version.services;
763 peer_state.user_agent = version.user_agent.clone();
764 peer_state.start_height = version.start_height;
765
766 #[cfg(all(
768 feature = "bip324",
769 any(target_arch = "x86_64", target_arch = "aarch64")
770 ))]
771 {
772 use crate::service_flags::{has_flag, standard};
773 let peer_supports_v2 = has_flag(version.services, standard::NODE_V2_TRANSPORT);
774 let we_support_v2 = config.service_flags.node_v2_transport;
775
776 if peer_supports_v2 && we_support_v2 {
777 let handshake = crate::v2_transport::V2Handshake::new_responder();
779 peer_state.v2_handshake = Some(std::sync::Arc::new(handshake));
780 }
783 }
784
785 Ok(NetworkResponse::SendMessage(Box::new(
787 NetworkMessage::VerAck,
788 )))
789}
790
791fn process_verack_message(peer_state: &mut PeerState) -> Result<NetworkResponse> {
793 peer_state.handshake_complete = true;
794 Ok(NetworkResponse::Ok)
795}
796
797fn process_addr_message(
799 addr: &AddrMessage,
800 peer_state: &mut PeerState,
801 config: &ProtocolConfig,
802) -> Result<NetworkResponse> {
803 if addr.addresses.len() > config.network_limits.max_addr_addresses {
805 return Ok(NetworkResponse::Reject(format!(
806 "Too many addresses (max {})",
807 config.network_limits.max_addr_addresses
808 )));
809 }
810
811 peer_state.known_addresses.extend(addr.addresses.clone());
813
814 Ok(NetworkResponse::Ok)
815}
816
817fn process_addrv2_message(
819 addrv2: &AddrV2Message,
820 peer_state: &mut PeerState,
821 config: &ProtocolConfig,
822) -> Result<NetworkResponse> {
823 if addrv2.addresses.len() > config.network_limits.max_addr_addresses {
825 return Ok(NetworkResponse::Reject(format!(
826 "Too many addresses (max {})",
827 config.network_limits.max_addr_addresses
828 )));
829 }
830
831 for addr_v2 in &addrv2.addresses {
833 if let Some(legacy_addr) = addr_v2.to_legacy() {
834 peer_state.known_addresses.push(legacy_addr);
835 }
836 }
839
840 Ok(NetworkResponse::Ok)
841}
842
843fn process_inv_message(
845 inv: &InvMessage,
846 chain_access: Option<&dyn ChainStateAccess>,
847 config: &ProtocolConfig,
848) -> Result<NetworkResponse> {
849 if inv.inventory.len() > config.network_limits.max_inv_items {
851 return Ok(NetworkResponse::Reject(format!(
852 "Too many inventory items (max {})",
853 config.network_limits.max_inv_items
854 )));
855 }
856
857 if let Some(chain) = chain_access {
859 let mut needed_items = Vec::with_capacity(inv.inventory.len());
860 for item in &inv.inventory {
861 if !chain.has_object(&item.hash) {
862 needed_items.push(item.clone());
863 }
864 }
865
866 if !needed_items.is_empty() {
867 return Ok(NetworkResponse::SendMessage(Box::new(
868 NetworkMessage::GetData(GetDataMessage {
869 inventory: needed_items,
870 }),
871 )));
872 }
873 }
874
875 Ok(NetworkResponse::Ok)
876}
877
878fn process_getdata_message(
880 getdata: &GetDataMessage,
881 chain_access: Option<&dyn ChainStateAccess>,
882 config: &ProtocolConfig,
883) -> Result<NetworkResponse> {
884 if getdata.inventory.len() > config.network_limits.max_inv_items {
886 return Ok(NetworkResponse::Reject(format!(
887 "Too many getdata items (max {})",
888 config.network_limits.max_inv_items
889 )));
890 }
891
892 if let Some(chain) = chain_access {
894 let mut responses = Vec::with_capacity(getdata.inventory.len());
895 for item in &getdata.inventory {
896 if let Some(obj) = chain.get_object(&item.hash) {
897 match item.inv_type {
898 1 => {
899 if let Some(tx) = obj.as_transaction() {
901 responses.push(NetworkMessage::Tx(Arc::clone(tx)));
902 }
903 }
904 2 => {
905 if let Some(block) = obj.as_block() {
907 let Some(witnesses) = chain.get_block_witnesses(&item.hash) else {
908 continue;
911 };
912 responses.push(NetworkMessage::Block(Arc::clone(block), witnesses));
913 }
914 }
915 _ => {
916 }
918 }
919 }
920 }
921
922 if !responses.is_empty() {
923 return Ok(NetworkResponse::SendMessages(responses));
924 }
925 }
926
927 Ok(NetworkResponse::Ok)
928}
929
930fn process_getheaders_message(
932 getheaders: &GetHeadersMessage,
933 chain_access: Option<&dyn ChainStateAccess>,
934 config: &ProtocolConfig,
935) -> Result<NetworkResponse> {
936 if getheaders.block_locator_hashes.len() > config.validation.max_locator_hashes {
938 return Ok(NetworkResponse::Reject(format!(
939 "Too many locator hashes (max {})",
940 config.validation.max_locator_hashes
941 )));
942 }
943
944 if let Some(chain) = chain_access {
946 let headers =
947 chain.get_headers_for_locator(&getheaders.block_locator_hashes, &getheaders.hash_stop);
948 return Ok(NetworkResponse::SendMessage(Box::new(
949 NetworkMessage::Headers(HeadersMessage { headers }),
950 )));
951 }
952
953 Ok(NetworkResponse::Reject("Chain access not available".into()))
954}
955
956fn process_headers_message(
958 headers: &HeadersMessage,
959 config: &ProtocolConfig,
960) -> Result<NetworkResponse> {
961 if headers.headers.len() > config.network_limits.max_headers {
963 return Ok(NetworkResponse::Reject(format!(
964 "Too many headers (max {})",
965 config.network_limits.max_headers
966 )));
967 }
968
969 Ok(NetworkResponse::Ok)
972}
973
974fn process_block_message(
976 engine: &BitcoinProtocolEngine,
977 block: &Block,
978 witnesses: &[Vec<blvm_consensus::segwit::Witness>],
979 utxo_set: Option<&UtxoSet>,
980 height: Option<u64>,
981 config: &ProtocolConfig,
982) -> Result<NetworkResponse> {
983 if block.transactions.len() > config.validation.max_txs_per_block {
985 return Err(crate::error::ProtocolError::MessageTooLarge {
986 size: block.transactions.len(),
987 max: config.validation.max_txs_per_block,
988 });
989 }
990
991 if let (Some(utxos), Some(h)) = (utxo_set, height) {
993 let context = ProtocolValidationContext::new(engine.get_protocol_version(), h)?;
994 let result = engine
996 .validate_block_with_protocol_and_witnesses(block, witnesses, utxos, h, &context)?;
997
998 match result {
999 ValidationResult::Valid => Ok(NetworkResponse::Ok),
1000 ValidationResult::Invalid(reason) => {
1001 Ok(NetworkResponse::Reject(format!("Invalid block: {reason}")))
1002 }
1003 }
1004 } else {
1005 Err(crate::error::ProtocolError::Configuration(
1006 "Missing validation context (utxo_set and height required)".into(),
1007 ))
1008 }
1009}
1010
1011fn process_tx_message(
1013 engine: &BitcoinProtocolEngine,
1014 tx: &Transaction,
1015 height: Option<u64>,
1016) -> Result<NetworkResponse> {
1017 let context =
1019 ProtocolValidationContext::new(engine.get_protocol_version(), height.unwrap_or(0))?;
1020 let result = engine.validate_transaction_with_protocol(tx, &context)?;
1021
1022 match result {
1023 ValidationResult::Valid => Ok(NetworkResponse::Ok),
1024 ValidationResult::Invalid(reason) => Ok(NetworkResponse::Reject(format!(
1025 "Invalid transaction: {reason}"
1026 ))),
1027 }
1028}
1029
1030fn process_ping_message(
1032 ping: &PingMessage,
1033 _peer_state: &mut PeerState,
1034) -> Result<NetworkResponse> {
1035 let pong = NetworkMessage::Pong(PongMessage { nonce: ping.nonce });
1036 Ok(NetworkResponse::SendMessage(Box::new(pong)))
1037}
1038
1039fn process_pong_message(pong: &PongMessage, peer_state: &mut PeerState) -> Result<NetworkResponse> {
1041 if peer_state.ping_nonce == Some(pong.nonce) {
1043 peer_state.ping_nonce = None;
1044 peer_state.last_pong = Some(std::time::SystemTime::now());
1045 }
1046
1047 Ok(NetworkResponse::Ok)
1048}
1049
1050fn process_mempool_message(chain_access: Option<&dyn ChainStateAccess>) -> Result<NetworkResponse> {
1052 if let Some(chain) = chain_access {
1054 let mempool_txs = chain.get_mempool_transactions();
1055 let mut responses = Vec::with_capacity(mempool_txs.len());
1056
1057 for tx in mempool_txs {
1058 responses.push(NetworkMessage::Tx(Arc::new(tx)));
1059 }
1060
1061 if !responses.is_empty() {
1062 return Ok(NetworkResponse::SendMessages(responses));
1063 }
1064 }
1065
1066 Ok(NetworkResponse::Ok)
1067}
1068
1069fn process_feefilter_message(
1071 feefilter: &FeeFilterMessage,
1072 peer_state: &mut PeerState,
1073) -> Result<NetworkResponse> {
1074 peer_state.min_fee_rate = Some(feefilter.feerate);
1075 Ok(NetworkResponse::Ok)
1076}
1077
1078fn process_getblocks_message(
1080 getblocks: &GetBlocksMessage,
1081 chain_access: Option<&dyn ChainStateAccess>,
1082 config: &ProtocolConfig,
1083) -> Result<NetworkResponse> {
1084 if getblocks.block_locator_hashes.len() > config.validation.max_locator_hashes {
1086 return Ok(NetworkResponse::Reject(format!(
1087 "Too many locator hashes (max {})",
1088 config.validation.max_locator_hashes
1089 )));
1090 }
1091
1092 if let Some(chain) = chain_access {
1093 use blvm_consensus::block::block_header_hash;
1094
1095 const MAX_GETBLOCKS_INV: usize = 500;
1097 let headers =
1098 chain.get_headers_for_locator(&getblocks.block_locator_hashes, &getblocks.hash_stop);
1099 if headers.is_empty() {
1100 return Ok(NetworkResponse::Ok);
1101 }
1102
1103 let inventory: Vec<InventoryVector> = headers
1104 .into_iter()
1105 .take(MAX_GETBLOCKS_INV)
1106 .map(|header| InventoryVector {
1107 inv_type: 2, hash: block_header_hash(&header),
1109 })
1110 .collect();
1111
1112 return Ok(NetworkResponse::SendMessage(Box::new(NetworkMessage::Inv(
1113 InvMessage { inventory },
1114 ))));
1115 }
1116
1117 Ok(NetworkResponse::Ok)
1118}
1119
1120fn process_getaddr_message(
1122 peer_state: &mut PeerState,
1123 config: &ProtocolConfig,
1124) -> Result<NetworkResponse> {
1125 if !peer_state.known_addresses.is_empty() {
1127 let max_addrs = config
1129 .network_limits
1130 .max_addr_addresses
1131 .min(peer_state.known_addresses.len());
1132 let mut addresses = Vec::with_capacity(max_addrs);
1133 addresses.extend(peer_state.known_addresses.iter().take(max_addrs).cloned());
1134
1135 return Ok(NetworkResponse::SendMessage(Box::new(
1136 NetworkMessage::Addr(AddrMessage { addresses }),
1137 )));
1138 }
1139
1140 Ok(NetworkResponse::Ok)
1141}
1142
1143fn process_notfound_message(
1145 notfound: &NotFoundMessage,
1146 config: &ProtocolConfig,
1147) -> Result<NetworkResponse> {
1148 if notfound.inventory.len() > config.network_limits.max_inv_items {
1150 return Ok(NetworkResponse::Reject(format!(
1151 "Too many notfound items (max {})",
1152 config.network_limits.max_inv_items
1153 )));
1154 }
1155
1156 Ok(NetworkResponse::Ok)
1158}
1159
1160fn process_reject_message(
1162 reject: &RejectMessage,
1163 _config: &ProtocolConfig,
1164) -> Result<NetworkResponse> {
1165 if reject.message.len() > 12 {
1168 return Ok(NetworkResponse::Reject(
1169 "Invalid reject message name".into(),
1170 ));
1171 }
1172
1173 if reject.reason.len() > 111 {
1176 return Ok(NetworkResponse::Reject("Reject reason too long".into()));
1177 }
1178
1179 Ok(NetworkResponse::Ok)
1182}
1183
1184fn process_sendheaders_message(peer_state: &mut PeerState) -> Result<NetworkResponse> {
1186 peer_state.prefer_headers = true;
1187 Ok(NetworkResponse::Ok)
1188}
1189
1190fn process_sendcmpct_message(
1192 sendcmpct: &SendCmpctMessage,
1193 peer_state: &mut PeerState,
1194 config: &ProtocolConfig,
1195) -> Result<NetworkResponse> {
1196 let valid_versions = [1, 2];
1198 if !valid_versions.contains(&sendcmpct.version) {
1199 return Ok(NetworkResponse::Reject(
1200 "Invalid compact block version".into(),
1201 ));
1202 }
1203
1204 if !config.compact_blocks.enabled {
1206 return Ok(NetworkResponse::Reject("Compact blocks not enabled".into()));
1207 }
1208
1209 peer_state.compact_block_version = Some(sendcmpct.version as u8);
1211 peer_state.prefer_compact_block = sendcmpct.prefer_cmpct != 0;
1212 Ok(NetworkResponse::Ok)
1213}
1214
1215fn txid_of(tx: &Transaction) -> Hash {
1216 use sha2::{Digest, Sha256};
1217 let bytes = crate::wire::serialize_tx(tx).unwrap_or_default();
1218 let first = Sha256::digest(&bytes);
1219 let second = Sha256::digest(first);
1220 let mut hash = [0u8; 32];
1221 hash.copy_from_slice(&second);
1222 hash
1223}
1224
1225fn header_hash(header: &BlockHeader) -> Hash {
1226 use sha2::{Digest, Sha256};
1227 let bytes = crate::serialization::serialize_block_header(header);
1228 let first = Sha256::digest(&bytes);
1229 let second = Sha256::digest(first);
1230 let mut hash = [0u8; 32];
1231 hash.copy_from_slice(&second);
1232 hash
1233}
1234
1235fn short_id_hex(id: &ShortTxId) -> String {
1236 hex::encode(id)
1237}
1238
1239fn begin_pending_compact(
1241 msg: &CmpctBlockMessage,
1242 mempool: &[Transaction],
1243) -> std::result::Result<PendingCompact, String> {
1244 let n = msg.short_ids.len() + msg.prefilled_txs.len();
1245 let mut slots: Vec<Option<(Transaction, Vec<blvm_consensus::segwit::Witness>)>> = vec![None; n];
1246 let mut prefilled = std::collections::HashSet::new();
1247 for item in &msg.prefilled_txs {
1248 let index = usize::from(item.index);
1249 if index >= n || !prefilled.insert(index) {
1250 return Err(format!(
1251 "prefilled compact-block index {index} is not usable"
1252 ));
1253 }
1254 slots[index] = Some((item.tx.clone(), item.witness.clone().unwrap_or_default()));
1255 }
1256
1257 let mut missing = Vec::new();
1258 let mut next = 0usize;
1259 for short_id in &msg.short_ids {
1260 while prefilled.contains(&next) {
1261 next = next.saturating_add(1);
1262 }
1263 let block_index = next;
1264 next = next.saturating_add(1);
1265 if block_index >= n {
1266 return Err("compact block short ids ran past the transaction list".into());
1267 }
1268 let mut found: Option<(Transaction, Vec<blvm_consensus::segwit::Witness>)> = None;
1269 let mut hits = 0usize;
1270 for tx in mempool {
1271 let id = crate::bip152::short_tx_id(&msg.header, &txid_of(tx), msg.nonce);
1272 if &id == short_id {
1273 hits += 1;
1274 found = Some((tx.clone(), Vec::new()));
1275 }
1276 }
1277 if hits == 1 {
1278 slots[block_index] = found;
1279 } else {
1280 missing.push((block_index, *short_id));
1281 }
1282 }
1283 Ok(PendingCompact {
1284 header: msg.header.clone(),
1285 nonce: msg.nonce,
1286 slots,
1287 missing,
1288 })
1289}
1290
1291fn finish_compact_block(
1292 engine: &BitcoinProtocolEngine,
1293 pending: PendingCompact,
1294 utxo_set: Option<&UtxoSet>,
1295 height: Option<u64>,
1296 config: &ProtocolConfig,
1297) -> Result<NetworkResponse> {
1298 if !pending.missing.is_empty() {
1299 let id = short_id_hex(&pending.missing[0].1);
1300 return Ok(NetworkResponse::Reject(format!(
1301 "short id {id} matches neither the mempool nor blocktxn"
1302 )));
1303 }
1304 let mut transactions = Vec::with_capacity(pending.slots.len());
1305 let mut witnesses = Vec::with_capacity(pending.slots.len());
1306 for slot in pending.slots {
1307 let Some((tx, witness)) = slot else {
1308 return Ok(NetworkResponse::Reject(
1309 "compact block still has an empty transaction slot".into(),
1310 ));
1311 };
1312 transactions.push(tx);
1313 witnesses.push(witness);
1314 }
1315 let block = Block::from_parts(pending.header, transactions.into_boxed_slice());
1316 match process_block_message(engine, &block, &witnesses, utxo_set, height, config) {
1317 Ok(response) => Ok(response),
1318 Err(err) if err.to_string().contains("Missing validation context") => {
1319 Ok(NetworkResponse::SendMessage(Box::new(
1320 NetworkMessage::Block(Arc::new(block), witnesses),
1321 )))
1322 }
1323 Err(err) => Err(err),
1324 }
1325}
1326
1327fn process_cmpctblock_message(
1329 engine: &BitcoinProtocolEngine,
1330 cmpctblock: &CmpctBlockMessage,
1331 peer_state: &mut PeerState,
1332 chain_access: Option<&dyn ChainStateAccess>,
1333 utxo_set: Option<&UtxoSet>,
1334 height: Option<u64>,
1335 config: &ProtocolConfig,
1336) -> Result<NetworkResponse> {
1337 if !config.compact_blocks.enabled {
1338 return Ok(NetworkResponse::Reject("Compact blocks not enabled".into()));
1339 }
1340 let mempool = chain_access
1341 .map(|chain| chain.get_mempool_transactions())
1342 .unwrap_or_default();
1343 let pending = match begin_pending_compact(cmpctblock, &mempool) {
1344 Ok(pending) => pending,
1345 Err(reason) => return Ok(NetworkResponse::Reject(reason)),
1346 };
1347 if pending.missing.is_empty() {
1348 peer_state.pending_compact = None;
1349 return finish_compact_block(engine, pending, utxo_set, height, config);
1350 }
1351 let indices: Vec<u16> = pending
1352 .missing
1353 .iter()
1354 .map(|(index, _)| u16::try_from(*index).unwrap_or(u16::MAX))
1355 .collect();
1356 let block_hash = header_hash(&pending.header);
1357 peer_state.pending_compact = Some(pending);
1358 Ok(NetworkResponse::SendMessage(Box::new(
1359 NetworkMessage::GetBlockTxn(GetBlockTxnMessage {
1360 block_hash,
1361 indices,
1362 }),
1363 )))
1364}
1365
1366fn process_getblocktxn_message(
1368 getblocktxn: &GetBlockTxnMessage,
1369 chain_access: Option<&dyn ChainStateAccess>,
1370 config: &ProtocolConfig,
1371) -> Result<NetworkResponse> {
1372 if getblocktxn.indices.len() > config.compact_blocks.max_blocktxn_indices {
1374 return Ok(NetworkResponse::Reject(format!(
1375 "Too many transaction indices (max {})",
1376 config.compact_blocks.max_blocktxn_indices
1377 )));
1378 }
1379
1380 if let Some(chain) = chain_access {
1382 if let Some(obj) = chain.get_object(&getblocktxn.block_hash) {
1383 if let Some(block) = obj.as_block() {
1384 let stored_witnesses = chain.get_block_witnesses(&getblocktxn.block_hash);
1385 let mut transactions = Vec::with_capacity(getblocktxn.indices.len());
1386 let mut req_witnesses = Vec::with_capacity(getblocktxn.indices.len());
1387
1388 for &index in &getblocktxn.indices {
1389 let idx = index as usize;
1390 if idx >= block.transactions.len() {
1391 return Ok(NetworkResponse::Reject(
1392 "getblocktxn index past end of block".into(),
1393 ));
1394 }
1395 transactions.push(block.transactions[idx].clone());
1396 let tx_witness = stored_witnesses
1397 .as_ref()
1398 .and_then(|w| w.get(idx).cloned())
1399 .unwrap_or_else(|| {
1400 block.transactions[idx]
1401 .inputs
1402 .iter()
1403 .map(|_| Vec::new())
1404 .collect()
1405 });
1406 req_witnesses.push(tx_witness);
1407 }
1408
1409 if !transactions.is_empty() {
1410 return Ok(NetworkResponse::SendMessage(Box::new(
1411 NetworkMessage::BlockTxn(BlockTxnMessage {
1412 block_hash: getblocktxn.block_hash,
1413 transactions,
1414 witnesses: Some(req_witnesses),
1415 }),
1416 )));
1417 }
1418 }
1419 }
1420 }
1421
1422 Ok(NetworkResponse::Ok)
1423}
1424
1425fn process_blocktxn_message(
1427 engine: &BitcoinProtocolEngine,
1428 blocktxn: &BlockTxnMessage,
1429 peer_state: &mut PeerState,
1430 utxo_set: Option<&UtxoSet>,
1431 height: Option<u64>,
1432 config: &ProtocolConfig,
1433) -> Result<NetworkResponse> {
1434 let Some(mut pending) = peer_state.pending_compact.clone() else {
1435 return Ok(NetworkResponse::Reject(
1436 "blocktxn does not match a pending compact block".into(),
1437 ));
1438 };
1439 if header_hash(&pending.header) != blocktxn.block_hash {
1440 return Ok(NetworkResponse::Reject(
1441 "blocktxn does not match a pending compact block".into(),
1442 ));
1443 }
1444 if blocktxn.transactions.len() != pending.missing.len() {
1445 let id = pending
1446 .missing
1447 .first()
1448 .map(|(_, short_id)| short_id_hex(short_id))
1449 .unwrap_or_else(|| "none".into());
1450 return Ok(NetworkResponse::Reject(format!(
1451 "short id {id} matches neither the mempool nor blocktxn"
1452 )));
1453 }
1454 for (i, (index, expected)) in pending.missing.iter().enumerate() {
1455 let tx = &blocktxn.transactions[i];
1456 let got = crate::bip152::short_tx_id(&pending.header, &txid_of(tx), pending.nonce);
1457 if &got != expected {
1458 return Ok(NetworkResponse::Reject(format!(
1459 "short id {} matches neither the mempool nor blocktxn",
1460 short_id_hex(expected)
1461 )));
1462 }
1463 let witness = blocktxn
1464 .witnesses
1465 .as_ref()
1466 .and_then(|all| all.get(i).cloned())
1467 .unwrap_or_default();
1468 pending.slots[*index] = Some((tx.clone(), witness));
1469 }
1470 pending.missing.clear();
1471 peer_state.pending_compact = None;
1472 finish_compact_block(engine, pending, utxo_set, height, config)
1473}
1474
1475#[cfg(feature = "utxo-commitments")]
1476fn process_getutxoset_message(
1478 getutxoset: &commons::GetUTXOSetMessage,
1479 chain_access: Option<&dyn ChainStateAccess>,
1480) -> Result<NetworkResponse> {
1481 let Some(chain) = chain_access else {
1482 return Err(ProtocolError::Validation(
1483 "utxo set is not available".into(),
1484 ));
1485 };
1486 match chain.stored_utxo_commitment(&getutxoset.block_hash) {
1487 Ok(Some(commitment)) => Ok(NetworkResponse::SendMessage(Box::new(
1488 NetworkMessage::UTXOSet(commons::UTXOSetMessage {
1489 request_id: 0,
1490 utxo_count: commitment.utxo_count,
1491 is_complete: true,
1492 chunk_id: None,
1493 commitment,
1494 }),
1495 ))),
1496 Ok(None) => Err(ProtocolError::Validation(
1497 format!(
1498 "no stored UTXO set for block {}",
1499 hex::encode(getutxoset.block_hash)
1500 )
1501 .into(),
1502 )),
1503 Err(reason) => Err(ProtocolError::Validation(reason.into())),
1504 }
1505}
1506
1507#[cfg(feature = "utxo-commitments")]
1508fn process_utxoset_message(
1510 utxoset: &commons::UTXOSetMessage,
1511 chain_access: Option<&dyn ChainStateAccess>,
1512) -> Result<NetworkResponse> {
1513 let Some(chain) = chain_access else {
1514 return Ok(NetworkResponse::Reject(
1515 "utxo commitment does not match the local set".into(),
1516 ));
1517 };
1518 match chain.stored_utxo_commitment(&utxoset.commitment.block_hash) {
1519 Ok(Some(local)) if local == utxoset.commitment => Ok(NetworkResponse::Ok),
1520 Ok(_) => Ok(NetworkResponse::Reject(format!(
1521 "utxo commitment does not match the local set for {}",
1522 hex::encode(utxoset.commitment.block_hash)
1523 ))),
1524 Err(reason) => Ok(NetworkResponse::Reject(reason)),
1525 }
1526}
1527
1528#[cfg(feature = "utxo-commitments")]
1529fn process_getfilteredblock_message(
1531 getfiltered: &commons::GetFilteredBlockMessage,
1532 chain_access: Option<&dyn ChainStateAccess>,
1533) -> Result<NetworkResponse> {
1534 let Some(chain) = chain_access else {
1535 return Err(ProtocolError::Validation(
1536 "block filter is not available".into(),
1537 ));
1538 };
1539 match chain.stored_block_filter(&getfiltered.block_hash) {
1540 Ok(Some((header, filter))) => {
1541 let commitment = chain
1542 .stored_utxo_commitment(&getfiltered.block_hash)
1543 .ok()
1544 .flatten()
1545 .unwrap_or(commons::UTXOCommitment {
1546 merkle_root: [0u8; 32],
1547 total_supply: 0,
1548 utxo_count: 0,
1549 block_height: 0,
1550 block_hash: getfiltered.block_hash,
1551 });
1552 Ok(NetworkResponse::SendMessage(Box::new(
1553 NetworkMessage::FilteredBlock(commons::FilteredBlockMessage {
1554 request_id: getfiltered.request_id,
1555 header,
1556 commitment,
1557 transactions: Vec::new(),
1558 transaction_indices: Vec::new(),
1559 spam_summary: commons::SpamSummary {
1560 filtered_count: 0,
1561 filtered_value: 0,
1562 filter_reasons: 0,
1563 },
1564 bip158_filter: Some(filter),
1565 }),
1566 )))
1567 }
1568 Ok(None) => Err(ProtocolError::Validation(
1569 format!(
1570 "no block filter for {}",
1571 hex::encode(getfiltered.block_hash)
1572 )
1573 .into(),
1574 )),
1575 Err(reason) => Err(ProtocolError::Validation(reason.into())),
1576 }
1577}
1578
1579#[cfg(feature = "utxo-commitments")]
1580fn process_filteredblock_message(
1582 filtered: &commons::FilteredBlockMessage,
1583 chain_access: Option<&dyn ChainStateAccess>,
1584) -> Result<NetworkResponse> {
1585 let Some(chain) = chain_access else {
1586 return Ok(NetworkResponse::Reject(
1587 "filtered block does not match the local filter".into(),
1588 ));
1589 };
1590 let block_hash = header_hash(&filtered.header);
1591 match chain.stored_block_filter(&block_hash) {
1592 Ok(Some((_, local))) if filtered.bip158_filter.as_ref() == Some(&local) => {
1593 Ok(NetworkResponse::Ok)
1594 }
1595 Ok(_) => Ok(NetworkResponse::Reject(format!(
1596 "filtered block does not match the local filter for {}",
1597 hex::encode(block_hash)
1598 ))),
1599 Err(reason) => Ok(NetworkResponse::Reject(reason)),
1600 }
1601}
1602
1603fn process_getbanlist_message(
1605 getbanlist: &commons::GetBanListMessage,
1606 chain_access: Option<&dyn ChainStateAccess>,
1607) -> Result<NetworkResponse> {
1608 let mut entries = chain_access
1609 .map(|chain| chain.ban_entries())
1610 .unwrap_or_default();
1611 if let Some(min_score) = getbanlist.min_score {
1612 entries.retain(|entry| entry.score >= min_score);
1613 }
1614 Ok(NetworkResponse::SendMessage(Box::new(
1615 NetworkMessage::BanList(commons::BanListMessage {
1616 request_id: getbanlist.request_id,
1617 entries,
1618 list_signature: None,
1619 }),
1620 )))
1621}
1622
1623fn process_banlist_message(
1625 banlist: &commons::BanListMessage,
1626 chain_access: Option<&dyn ChainStateAccess>,
1627) -> Result<NetworkResponse> {
1628 if banlist.entries.is_empty() {
1629 return Ok(NetworkResponse::Ok);
1630 }
1631 let Some(chain) = chain_access else {
1632 return Ok(NetworkResponse::Reject("ban path is not available".into()));
1633 };
1634 for entry in &banlist.entries {
1635 if let Err(reason) = chain.apply_ban(entry) {
1636 return Ok(NetworkResponse::Reject(reason));
1637 }
1638 }
1639 Ok(NetworkResponse::Ok)
1640}
1641
1642#[cfg(test)]
1643fn assert_getblocktxn_past_end_rejects() {
1644 struct TwoTx;
1645 impl ChainStateAccess for TwoTx {
1646 fn has_object(&self, _hash: &Hash) -> bool {
1647 true
1648 }
1649 fn get_object(&self, hash: &Hash) -> Option<ChainObject> {
1650 let tx = Transaction {
1651 version: 1,
1652 inputs: vec![].into(),
1653 outputs: vec![].into(),
1654 lock_time: 0,
1655 };
1656 let block = Block {
1657 header: BlockHeader {
1658 version: 1,
1659 prev_block_hash: [0; 32],
1660 merkle_root: [0; 32],
1661 timestamp: 1,
1662 bits: 1,
1663 nonce: 1,
1664 },
1665 transactions: vec![tx.clone(), tx].into_boxed_slice(),
1666 };
1667 if hash == &[9u8; 32] {
1668 Some(ChainObject::Block(Arc::new(block)))
1669 } else {
1670 None
1671 }
1672 }
1673 fn get_headers_for_locator(&self, _locator: &[Hash], _stop: &Hash) -> Vec<BlockHeader> {
1674 Vec::new()
1675 }
1676 fn get_mempool_transactions(&self) -> Vec<Transaction> {
1677 Vec::new()
1678 }
1679 }
1680 let msg = GetBlockTxnMessage {
1681 block_hash: [9u8; 32],
1682 indices: vec![5],
1683 };
1684 let response =
1685 process_getblocktxn_message(&msg, Some(&TwoTx), &ProtocolConfig::default()).unwrap();
1686 assert!(
1687 matches!(response, NetworkResponse::Reject(_)),
1688 "expected reject, got {response:?}"
1689 );
1690}
1691
1692#[cfg(test)]
1693#[test]
1694fn getblocktxn_index_past_block_returns_reject_and_no_blocktxn() {
1695 assert_getblocktxn_past_end_rejects();
1696}
1697
1698#[cfg(test)]
1699fn sample_tx(tag: u8) -> Transaction {
1700 Transaction {
1701 version: 1,
1702 inputs: vec![blvm_consensus::types::TransactionInput {
1703 prevout: blvm_consensus::types::OutPoint {
1704 hash: [tag; 32],
1705 index: 0,
1706 },
1707 script_sig: vec![tag],
1708 sequence: 0xffffffff,
1709 }]
1710 .into(),
1711 outputs: vec![blvm_consensus::types::TransactionOutput {
1712 value: 1000,
1713 script_pubkey: vec![0x51],
1714 }]
1715 .into(),
1716 lock_time: 0,
1717 }
1718}
1719
1720#[cfg(test)]
1721fn sample_header() -> BlockHeader {
1722 BlockHeader {
1723 version: 1,
1724 prev_block_hash: [2u8; 32],
1725 merkle_root: [3u8; 32],
1726 timestamp: 1_700_000_000,
1727 bits: 0x1d00ffff,
1728 nonce: 4,
1729 }
1730}
1731
1732#[cfg(test)]
1733struct MempoolChain(Transaction);
1734
1735#[cfg(test)]
1736impl ChainStateAccess for MempoolChain {
1737 fn has_object(&self, _hash: &Hash) -> bool {
1738 false
1739 }
1740 fn get_object(&self, _hash: &Hash) -> Option<ChainObject> {
1741 None
1742 }
1743 fn get_headers_for_locator(&self, _locator: &[Hash], _stop: &Hash) -> Vec<BlockHeader> {
1744 Vec::new()
1745 }
1746 fn get_mempool_transactions(&self) -> Vec<Transaction> {
1747 vec![self.0.clone()]
1748 }
1749}
1750
1751#[cfg(test)]
1752#[test]
1753fn compact_block_rebuilds_from_mempool_and_names_a_miss() {
1754 use crate::{BitcoinProtocolEngine, ProtocolVersion};
1755 let engine = BitcoinProtocolEngine::new(ProtocolVersion::BitcoinV1).unwrap();
1756 let header = sample_header();
1757 let coinbase = sample_tx(0);
1758 let pooled = sample_tx(7);
1759 let nonce = 9u64;
1760 let short = crate::bip152::short_tx_id(&header, &txid_of(&pooled), nonce);
1761 let cmpct = CmpctBlockMessage {
1762 header: header.clone(),
1763 nonce,
1764 short_ids: vec![short],
1765 prefilled_txs: vec![PrefilledTransaction {
1766 index: 0,
1767 tx: coinbase.clone(),
1768 witness: None,
1769 }],
1770 };
1771 let mut peer = PeerState::new();
1772 let response = process_cmpctblock_message(
1773 &engine,
1774 &cmpct,
1775 &mut peer,
1776 Some(&MempoolChain(pooled.clone())),
1777 None,
1778 None,
1779 &ProtocolConfig::default(),
1780 )
1781 .unwrap();
1782 match response {
1783 NetworkResponse::SendMessage(message) => match *message {
1784 NetworkMessage::Block(block, _) => {
1785 assert_eq!(block.header, header);
1786 assert_eq!(block.transactions.len(), 2);
1787 assert_eq!(block.transactions[0], coinbase);
1788 assert_eq!(block.transactions[1], pooled);
1789 }
1790 other => panic!("expected the reconstructed block, got {other:?}"),
1791 },
1792 other => panic!("expected the reconstructed block, got {other:?}"),
1793 }
1794
1795 let missing = [0xabu8; 6];
1796 let cmpct = CmpctBlockMessage {
1797 header: header.clone(),
1798 nonce,
1799 short_ids: vec![missing],
1800 prefilled_txs: vec![PrefilledTransaction {
1801 index: 0,
1802 tx: coinbase,
1803 witness: None,
1804 }],
1805 };
1806 let mut peer = PeerState::new();
1807 let asked = process_cmpctblock_message(
1808 &engine,
1809 &cmpct,
1810 &mut peer,
1811 Some(&MempoolChain(pooled.clone())),
1812 None,
1813 None,
1814 &ProtocolConfig::default(),
1815 )
1816 .unwrap();
1817 assert!(matches!(
1818 asked,
1819 NetworkResponse::SendMessage(ref message) if matches!(**message, NetworkMessage::GetBlockTxn(_))
1820 ));
1821 let wrong = BlockTxnMessage {
1822 block_hash: header_hash(&header),
1823 transactions: vec![pooled],
1824 witnesses: None,
1825 };
1826 let rejected = process_blocktxn_message(
1827 &engine,
1828 &wrong,
1829 &mut peer,
1830 None,
1831 None,
1832 &ProtocolConfig::default(),
1833 )
1834 .unwrap();
1835 match rejected {
1836 NetworkResponse::Reject(reason) => {
1837 assert!(reason.contains(&short_id_hex(&missing)), "{reason}");
1838 }
1839 other => panic!("expected a named short-id reject, got {other:?}"),
1840 }
1841}
1842
1843#[cfg(test)]
1844struct BanBook {
1845 entries: std::sync::Mutex<Vec<crate::commons::BanEntry>>,
1846}
1847
1848#[cfg(test)]
1849impl ChainStateAccess for BanBook {
1850 fn has_object(&self, _hash: &Hash) -> bool {
1851 false
1852 }
1853 fn get_object(&self, _hash: &Hash) -> Option<ChainObject> {
1854 None
1855 }
1856 fn get_headers_for_locator(&self, _locator: &[Hash], _stop: &Hash) -> Vec<BlockHeader> {
1857 Vec::new()
1858 }
1859 fn get_mempool_transactions(&self) -> Vec<Transaction> {
1860 Vec::new()
1861 }
1862 fn ban_entries(&self) -> Vec<crate::commons::BanEntry> {
1863 self.entries.lock().expect("ban book").clone()
1864 }
1865 fn apply_ban(&self, entry: &crate::commons::BanEntry) -> std::result::Result<(), String> {
1866 self.entries.lock().expect("ban book").push(entry.clone());
1867 Ok(())
1868 }
1869}
1870
1871#[cfg(test)]
1872#[test]
1873fn ban_messages_list_and_apply_without_the_old_reject() {
1874 let book = BanBook {
1875 entries: std::sync::Mutex::new(Vec::new()),
1876 };
1877 let empty = process_getbanlist_message(
1878 &crate::commons::GetBanListMessage {
1879 request_id: 1,
1880 min_score: None,
1881 },
1882 Some(&book),
1883 )
1884 .unwrap();
1885 match empty {
1886 NetworkResponse::SendMessage(message) => match *message {
1887 NetworkMessage::BanList(list) => assert!(list.entries.is_empty()),
1888 other => panic!("expected an empty ban list, got {other:?}"),
1889 },
1890 other => panic!("expected an empty ban list, got {other:?}"),
1891 }
1892 let entry = crate::commons::BanEntry {
1893 ip: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 1, 2, 3, 4],
1894 score: 100,
1895 reason: 1,
1896 timestamp: 1_700_000_000,
1897 signature: None,
1898 };
1899 process_banlist_message(
1900 &crate::commons::BanListMessage {
1901 request_id: 2,
1902 entries: vec![entry.clone()],
1903 list_signature: None,
1904 },
1905 Some(&book),
1906 )
1907 .unwrap();
1908 assert_eq!(book.entries.lock().unwrap().as_slice(), &[entry]);
1909 let none = process_getbanlist_message(
1910 &crate::commons::GetBanListMessage {
1911 request_id: 3,
1912 min_score: None,
1913 },
1914 None,
1915 )
1916 .unwrap();
1917 assert!(matches!(none, NetworkResponse::SendMessage(_)));
1918}
1919
1920#[cfg(all(test, feature = "utxo-commitments"))]
1921struct UtxoBook {
1922 hash: Hash,
1923 commitment: crate::commons::UTXOCommitment,
1924 header: BlockHeader,
1925 filter: Vec<u8>,
1926}
1927
1928#[cfg(all(test, feature = "utxo-commitments"))]
1929impl ChainStateAccess for UtxoBook {
1930 fn has_object(&self, _hash: &Hash) -> bool {
1931 false
1932 }
1933 fn get_object(&self, _hash: &Hash) -> Option<ChainObject> {
1934 None
1935 }
1936 fn get_headers_for_locator(&self, _locator: &[Hash], _stop: &Hash) -> Vec<BlockHeader> {
1937 Vec::new()
1938 }
1939 fn get_mempool_transactions(&self) -> Vec<Transaction> {
1940 Vec::new()
1941 }
1942 fn stored_utxo_commitment(
1943 &self,
1944 block_hash: &Hash,
1945 ) -> std::result::Result<Option<crate::commons::UTXOCommitment>, String> {
1946 if block_hash == &self.hash {
1947 Ok(Some(self.commitment.clone()))
1948 } else {
1949 Ok(None)
1950 }
1951 }
1952 fn stored_block_filter(
1953 &self,
1954 block_hash: &Hash,
1955 ) -> std::result::Result<Option<(BlockHeader, Vec<u8>)>, String> {
1956 if block_hash == &self.hash {
1957 Ok(Some((self.header.clone(), self.filter.clone())))
1958 } else {
1959 Ok(None)
1960 }
1961 }
1962}
1963
1964#[cfg(all(test, feature = "utxo-commitments"))]
1965#[test]
1966fn utxo_and_filter_messages_use_the_stored_set() {
1967 let hash = [9u8; 32];
1968 let commitment = crate::commons::UTXOCommitment {
1969 merkle_root: [4u8; 32],
1970 total_supply: 50,
1971 utxo_count: 2,
1972 block_height: 10,
1973 block_hash: hash,
1974 };
1975 let book = UtxoBook {
1976 hash,
1977 commitment: commitment.clone(),
1978 header: sample_header(),
1979 filter: vec![0x01, 0x02, 0x03],
1980 };
1981 let got = process_getutxoset_message(
1982 &crate::commons::GetUTXOSetMessage {
1983 height: 10,
1984 block_hash: hash,
1985 },
1986 Some(&book),
1987 )
1988 .unwrap();
1989 match got {
1990 NetworkResponse::SendMessage(message) => match *message {
1991 NetworkMessage::UTXOSet(msg) => assert_eq!(msg.commitment, commitment),
1992 other => panic!("expected the stored commitment, got {other:?}"),
1993 },
1994 other => panic!("expected the stored commitment, got {other:?}"),
1995 }
1996 let missing = process_getutxoset_message(
1997 &crate::commons::GetUTXOSetMessage {
1998 height: 11,
1999 block_hash: [1u8; 32],
2000 },
2001 Some(&book),
2002 );
2003 let err = missing.expect_err("a missing set is an error");
2004 assert!(!err.to_string().contains("not implemented"));
2005
2006 let matched = process_utxoset_message(
2007 &crate::commons::UTXOSetMessage {
2008 request_id: 1,
2009 commitment: commitment.clone(),
2010 utxo_count: 2,
2011 is_complete: true,
2012 chunk_id: None,
2013 },
2014 Some(&book),
2015 )
2016 .unwrap();
2017 assert!(matches!(matched, NetworkResponse::Ok));
2018 let mut other_commitment = commitment.clone();
2019 other_commitment.total_supply = 1;
2020 let rejected = process_utxoset_message(
2021 &crate::commons::UTXOSetMessage {
2022 request_id: 1,
2023 commitment: other_commitment,
2024 utxo_count: 2,
2025 is_complete: true,
2026 chunk_id: None,
2027 },
2028 Some(&book),
2029 )
2030 .unwrap();
2031 assert!(matches!(rejected, NetworkResponse::Reject(_)));
2032
2033 let filter_msg = process_getfilteredblock_message(
2034 &crate::commons::GetFilteredBlockMessage {
2035 request_id: 7,
2036 block_hash: hash,
2037 filter_preferences: crate::commons::FilterPreferences {
2038 filter_ordinals: false,
2039 filter_dust: false,
2040 filter_brc20: false,
2041 min_output_value: 0,
2042 },
2043 include_bip158_filter: true,
2044 },
2045 Some(&book),
2046 )
2047 .unwrap();
2048 let sent_filter = match filter_msg {
2049 NetworkResponse::SendMessage(message) => match *message {
2050 NetworkMessage::FilteredBlock(msg) => msg.bip158_filter.expect("filter bytes"),
2051 other => panic!("expected a filter, got {other:?}"),
2052 },
2053 other => panic!("expected a filter, got {other:?}"),
2054 };
2055 assert_eq!(sent_filter, vec![0x01, 0x02, 0x03]);
2056
2057 let header = sample_header();
2058 let stored_hash = header_hash(&header);
2059 let book = UtxoBook {
2060 hash: stored_hash,
2061 commitment,
2062 header: header.clone(),
2063 filter: vec![0x01, 0x02, 0x03],
2064 };
2065 let ok = process_filteredblock_message(
2066 &crate::commons::FilteredBlockMessage {
2067 request_id: 7,
2068 header: header.clone(),
2069 commitment: book.commitment.clone(),
2070 transactions: Vec::new(),
2071 transaction_indices: Vec::new(),
2072 spam_summary: crate::commons::SpamSummary {
2073 filtered_count: 0,
2074 filtered_value: 0,
2075 filter_reasons: 0,
2076 },
2077 bip158_filter: Some(vec![0x01, 0x02, 0x03]),
2078 },
2079 Some(&book),
2080 )
2081 .unwrap();
2082 assert!(matches!(ok, NetworkResponse::Ok));
2083 let bad = process_filteredblock_message(
2084 &crate::commons::FilteredBlockMessage {
2085 request_id: 7,
2086 header,
2087 commitment: book.commitment.clone(),
2088 transactions: Vec::new(),
2089 transaction_indices: Vec::new(),
2090 spam_summary: crate::commons::SpamSummary {
2091 filtered_count: 0,
2092 filtered_value: 0,
2093 filter_reasons: 0,
2094 },
2095 bip158_filter: Some(vec![0xff]),
2096 },
2097 Some(&book),
2098 )
2099 .unwrap();
2100 assert!(matches!(bad, NetworkResponse::Reject(_)));
2101}