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>, Vec<crate::segwit::Witness>),
53 Ping(PingMessage),
54 Pong(PongMessage),
55 MemPool,
56 FeeFilter(FeeFilterMessage),
57 GetBlocks(GetBlocksMessage),
59 GetAddr,
60 NotFound(NotFoundMessage),
61 Reject(RejectMessage),
62 SendHeaders,
63 SendCmpct(SendCmpctMessage),
65 CmpctBlock(CmpctBlockMessage),
66 GetBlockTxn(GetBlockTxnMessage),
67 BlockTxn(BlockTxnMessage),
68 #[cfg(feature = "utxo-commitments")]
70 GetUTXOSet(commons::GetUTXOSetMessage),
71 #[cfg(feature = "utxo-commitments")]
72 UTXOSet(commons::UTXOSetMessage),
73 #[cfg(feature = "utxo-commitments")]
74 GetFilteredBlock(commons::GetFilteredBlockMessage),
75 #[cfg(feature = "utxo-commitments")]
76 FilteredBlock(commons::FilteredBlockMessage),
77 GetBanList(commons::GetBanListMessage),
78 BanList(commons::BanListMessage),
79}
80
81#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
83pub struct VersionMessage {
84 pub version: i32,
85 pub services: u64,
86 pub timestamp: i64,
87 pub addr_recv: NetworkAddress,
88 pub addr_from: NetworkAddress,
89 pub nonce: u64,
90 pub user_agent: String,
91 pub start_height: i32,
92 pub relay: bool,
93}
94
95#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
101pub struct BlockMessage {
102 pub block: Block,
103 #[serde(skip_serializing_if = "Vec::is_empty", default)]
105 pub witnesses: Vec<Vec<blvm_consensus::segwit::Witness>>,
106}
107
108#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
110pub struct TxMessage {
111 pub transaction: Transaction,
112 #[serde(skip_serializing_if = "Vec::is_empty", default)]
114 pub witnesses: Vec<crate::segwit::Witness>,
115}
116
117#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
122pub struct CompactBlockMessage {
123 pub compact_block: crate::bip152::CompactBlock,
124}
125
126#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
128pub struct AddrMessage {
129 pub addresses: Vec<NetworkAddress>,
130}
131
132#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
134pub struct InvMessage {
135 pub inventory: Vec<InventoryVector>,
136}
137
138#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
140pub struct GetDataMessage {
141 pub inventory: Vec<InventoryVector>,
142}
143
144#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
146pub struct GetHeadersMessage {
147 pub version: u32,
148 pub block_locator_hashes: Vec<Hash>,
149 pub hash_stop: Hash,
150}
151
152#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
154pub struct HeadersMessage {
155 pub headers: Vec<BlockHeader>,
156}
157
158#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
160pub struct PingMessage {
161 pub nonce: u64,
162}
163
164#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
166pub struct PongMessage {
167 pub nonce: u64,
168}
169
170#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
172pub struct FeeFilterMessage {
173 pub feerate: u64,
174}
175
176#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
178pub struct GetBlocksMessage {
179 pub version: u32,
180 pub block_locator_hashes: Vec<Hash>,
181 pub hash_stop: Hash,
182}
183
184#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
186pub struct NotFoundMessage {
187 pub inventory: Vec<InventoryVector>,
188}
189
190#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
192pub struct RejectMessage {
193 pub message: String, pub code: u8, pub reason: String, pub extra_data: Option<Hash>, }
198
199#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
201pub struct SendCmpctMessage {
202 pub version: u64,
204 pub prefer_cmpct: u8,
206}
207
208#[derive(Debug, Clone, PartialEq, Eq)]
210pub struct CmpctBlockMessage {
211 pub header: BlockHeader,
213 pub nonce: u64,
215 pub short_ids: Vec<ShortTxId>,
217 pub prefilled_txs: Vec<PrefilledTransaction>,
219}
220
221#[derive(Debug, Clone, PartialEq, Eq)]
223pub struct PrefilledTransaction {
224 pub index: u16,
226 pub tx: Transaction,
228 pub witness: Option<Vec<blvm_consensus::segwit::Witness>>,
230}
231
232#[derive(Debug, Clone, PartialEq, Eq, Error)]
234pub enum CompactBlockWireConvertError {
235 #[error("prefilled transaction index {0} does not fit in BIP152 u16 index")]
236 PrefilledIndexTooLarge(usize),
237 #[error("duplicate prefilled transaction index {0}")]
238 DuplicatePrefilledIndex(usize),
239}
240
241impl TryFrom<crate::bip152::CompactBlock> for CmpctBlockMessage {
242 type Error = CompactBlockWireConvertError;
243
244 fn try_from(mut value: crate::bip152::CompactBlock) -> std::result::Result<Self, Self::Error> {
249 value.prefilled_txs.sort_by_key(|(i, _, _)| *i);
250 let mut prefilled_txs = Vec::with_capacity(value.prefilled_txs.len());
251 let mut prev_idx: Option<usize> = None;
252 for (idx, tx, witness) in value.prefilled_txs {
253 if prev_idx == Some(idx) {
254 return Err(CompactBlockWireConvertError::DuplicatePrefilledIndex(idx));
255 }
256 prev_idx = Some(idx);
257 let index = u16::try_from(idx)
258 .map_err(|_| CompactBlockWireConvertError::PrefilledIndexTooLarge(idx))?;
259 prefilled_txs.push(PrefilledTransaction { index, tx, witness });
260 }
261 Ok(CmpctBlockMessage {
262 header: value.header,
263 nonce: value.nonce,
264 short_ids: value.short_ids,
265 prefilled_txs,
266 })
267 }
268}
269
270impl From<&CmpctBlockMessage> for crate::bip152::CompactBlock {
271 fn from(msg: &CmpctBlockMessage) -> Self {
272 Self {
273 header: msg.header.clone(),
274 nonce: msg.nonce,
275 short_ids: msg.short_ids.clone(),
276 prefilled_txs: msg
277 .prefilled_txs
278 .iter()
279 .map(|p| (usize::from(p.index), p.tx.clone(), p.witness.clone()))
280 .collect(),
281 }
282 }
283}
284
285impl From<CmpctBlockMessage> for crate::bip152::CompactBlock {
286 fn from(msg: CmpctBlockMessage) -> Self {
287 Self {
288 header: msg.header,
289 nonce: msg.nonce,
290 short_ids: msg.short_ids,
291 prefilled_txs: msg
292 .prefilled_txs
293 .into_iter()
294 .map(|p| (usize::from(p.index), p.tx, p.witness))
295 .collect(),
296 }
297 }
298}
299
300#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
302pub struct GetBlockTxnMessage {
303 pub block_hash: Hash,
305 pub indices: Vec<u16>,
307}
308
309#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
311pub struct BlockTxnMessage {
312 pub block_hash: Hash,
314 pub transactions: Vec<Transaction>,
316 pub witnesses: Option<Vec<Vec<blvm_consensus::segwit::Witness>>>,
318}
319
320#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
322pub struct NetworkAddress {
323 pub services: u64,
324 pub ip: [u8; 16], pub port: u16,
326 pub time: u32,
328}
329
330#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
332#[repr(u8)]
333pub enum AddressType {
334 IPv4 = 1,
335 IPv6 = 2,
336 TorV2 = 3,
337 TorV3 = 4,
338 I2P = 5,
339 CJDNS = 6,
340}
341
342impl AddressType {
343 pub fn address_length(&self) -> usize {
345 match self {
346 AddressType::IPv4 => 4,
347 AddressType::IPv6 => 16,
348 AddressType::TorV2 => 10,
349 AddressType::TorV3 => 32,
350 AddressType::I2P => 32,
351 AddressType::CJDNS => 16,
352 }
353 }
354
355 pub fn from_u8(value: u8) -> Option<Self> {
357 match value {
358 1 => Some(AddressType::IPv4),
359 2 => Some(AddressType::IPv6),
360 3 => Some(AddressType::TorV2),
361 4 => Some(AddressType::TorV3),
362 5 => Some(AddressType::I2P),
363 6 => Some(AddressType::CJDNS),
364 _ => None,
365 }
366 }
367}
368
369#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
371pub struct AddrV2Message {
372 pub addresses: Vec<NetworkAddressV2>,
373}
374
375#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
377pub struct NetworkAddressV2 {
378 pub time: u32,
379 pub services: u64,
380 pub address_type: AddressType,
381 pub address: Vec<u8>, pub port: u16,
383}
384
385impl NetworkAddressV2 {
386 pub fn new(
388 time: u32,
389 services: u64,
390 address_type: AddressType,
391 address: Vec<u8>,
392 port: u16,
393 ) -> Result<Self> {
394 let expected_len = address_type.address_length();
395 if address.len() != expected_len {
396 return Err(ProtocolError::Consensus(ConsensusError::Serialization(
397 std::borrow::Cow::Owned(format!(
398 "Invalid address length for type {:?}: expected {}, got {}",
399 address_type,
400 expected_len,
401 address.len()
402 )),
403 )));
404 }
405 Ok(Self {
406 time,
407 services,
408 address_type,
409 address,
410 port,
411 })
412 }
413
414 pub fn to_legacy(&self) -> Option<NetworkAddress> {
416 match self.address_type {
417 AddressType::IPv4 => {
418 if self.address.len() == 4 {
419 let mut ipv6 = [0u8; 16];
421 ipv6[10] = 0xff;
422 ipv6[11] = 0xff;
423 ipv6[12..16].copy_from_slice(&self.address);
424 Some(NetworkAddress {
425 time: self.time,
426 services: self.services,
427 ip: ipv6,
428 port: self.port,
429 })
430 } else {
431 None
432 }
433 }
434 AddressType::IPv6 => {
435 if self.address.len() == 16 {
436 let mut ipv6 = [0u8; 16];
437 ipv6.copy_from_slice(&self.address);
438 Some(NetworkAddress {
439 time: self.time,
440 services: self.services,
441 ip: ipv6,
442 port: self.port,
443 })
444 } else {
445 None
446 }
447 }
448 _ => None, }
450 }
451}
452
453#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
455pub struct InventoryVector {
456 pub inv_type: u32,
457 pub hash: Hash,
458}
459
460#[derive(Debug, Clone)]
462pub enum NetworkResponse {
463 Ok,
464 SendMessage(Box<NetworkMessage>),
465 SendMessages(Vec<NetworkMessage>),
466 Reject(String),
467}
468
469#[derive(Clone)]
471pub(crate) struct PendingCompact {
472 pub header: BlockHeader,
473 pub nonce: u64,
474 pub version: u64,
477 pub slots: Vec<Option<(Transaction, Vec<blvm_consensus::segwit::Witness>)>>,
478 pub missing: Vec<(usize, ShortTxId)>,
479}
480
481#[derive(Clone)]
483pub struct PeerState {
484 pub version: i32,
485 pub services: u64,
486 pub user_agent: String,
487 pub start_height: i32,
488 pub handshake_complete: bool,
489 pub known_addresses: Vec<NetworkAddress>,
490 pub ping_nonce: Option<u64>,
491 pub last_pong: Option<std::time::SystemTime>,
492 pub min_fee_rate: Option<u64>,
494 pub relay_tx: bool,
497 pub prefer_headers: bool,
499 pub compact_block_version: Option<u8>,
501 pub prefer_compact_block: bool,
503 pub high_bandwidth: bool,
505 pub known_header_height: Option<u64>,
508 pub wtxid_relay: bool,
510 pub package_remote: Option<u64>,
512 pub(crate) pending_compact: Option<PendingCompact>,
514 #[cfg(all(
517 feature = "bip324",
518 any(target_arch = "x86_64", target_arch = "aarch64")
519 ))]
520 pub v2_transport: Option<std::sync::Arc<crate::v2_transport::V2Transport>>,
521 #[cfg(all(
523 feature = "bip324",
524 any(target_arch = "x86_64", target_arch = "aarch64")
525 ))]
526 pub v2_handshake: Option<std::sync::Arc<crate::v2_transport::V2Handshake>>,
527}
528
529impl PeerState {
530 pub fn new() -> Self {
531 Self {
532 version: 0,
533 services: 0,
534 user_agent: String::new(),
535 start_height: 0,
536 handshake_complete: false,
537 known_addresses: Vec::new(),
538 ping_nonce: None,
539 last_pong: None,
540 min_fee_rate: None,
541 relay_tx: true,
542 prefer_headers: false,
543 compact_block_version: None,
544 prefer_compact_block: false,
545 high_bandwidth: false,
546 known_header_height: None,
547 wtxid_relay: false,
548 package_remote: None,
549 pending_compact: None,
550 #[cfg(all(
551 feature = "bip324",
552 any(target_arch = "x86_64", target_arch = "aarch64")
553 ))]
554 v2_transport: None,
555 #[cfg(all(
556 feature = "bip324",
557 any(target_arch = "x86_64", target_arch = "aarch64")
558 ))]
559 v2_handshake: None,
560 }
561 }
562
563 #[cfg(all(
565 feature = "bip324",
566 any(target_arch = "x86_64", target_arch = "aarch64")
567 ))]
568 pub fn supports_v2_transport(&self) -> bool {
569 use crate::service_flags::{has_flag, standard};
570 has_flag(self.services, standard::NODE_V2_TRANSPORT)
571 }
572
573 #[cfg(all(
575 feature = "bip324",
576 any(target_arch = "x86_64", target_arch = "aarch64")
577 ))]
578 pub fn is_v2_transport_active(&self) -> bool {
579 self.v2_transport.is_some()
580 }
581}
582
583impl Default for PeerState {
584 fn default() -> Self {
585 Self::new()
586 }
587}
588
589#[derive(Debug, Clone)]
591pub enum ChainObject {
592 Block(Arc<Block>),
593 Transaction(Arc<Transaction>),
594}
595
596impl ChainObject {
597 pub fn as_block(&self) -> Option<&Arc<Block>> {
598 match self {
599 ChainObject::Block(block) => Some(block),
600 _ => None,
601 }
602 }
603
604 pub fn as_transaction(&self) -> Option<&Arc<Transaction>> {
605 match self {
606 ChainObject::Transaction(tx) => Some(tx),
607 _ => None,
608 }
609 }
610}
611
612pub trait ChainStateAccess {
618 fn has_object(&self, hash: &Hash) -> bool;
620
621 fn get_object(&self, hash: &Hash) -> Option<ChainObject>;
623
624 fn get_headers_for_locator(&self, locator: &[Hash], stop: &Hash) -> Vec<BlockHeader>;
627
628 fn get_mempool_transactions(&self) -> Vec<Transaction>;
630
631 fn mempool_for_compact_block(
635 &self,
636 ) -> Vec<(Transaction, Vec<blvm_consensus::segwit::Witness>)> {
637 self.get_mempool_transactions()
638 .into_iter()
639 .map(|tx| (tx, Vec::new()))
640 .collect()
641 }
642
643 fn get_block_witnesses(
645 &self,
646 _hash: &Hash,
647 ) -> Option<Vec<Vec<blvm_consensus::segwit::Witness>>> {
648 None
649 }
650
651 fn ban_entries(&self) -> Vec<crate::commons::BanEntry> {
653 Vec::new()
654 }
655
656 fn apply_ban(&self, entry: &crate::commons::BanEntry) -> std::result::Result<(), String> {
659 let _ = entry;
660 Err("ban path is not available".into())
661 }
662
663 fn stored_utxo_commitment(
665 &self,
666 _block_hash: &Hash,
667 ) -> std::result::Result<Option<crate::commons::UTXOCommitment>, String> {
668 Ok(None)
669 }
670
671 fn stored_block_filter(
673 &self,
674 _block_hash: &Hash,
675 ) -> std::result::Result<Option<(BlockHeader, Vec<u8>)>, String> {
676 Ok(None)
677 }
678}
679
680pub fn process_network_message(
698 engine: &BitcoinProtocolEngine,
699 message: &NetworkMessage,
700 peer_state: &mut PeerState,
701 chain_access: Option<&dyn ChainStateAccess>,
702 utxo_set: Option<&UtxoSet>,
703 height: Option<u64>,
704) -> Result<NetworkResponse> {
705 let config = engine.get_config();
706 match message {
707 NetworkMessage::Version(version) => process_version_message(version, peer_state, config),
708 NetworkMessage::VerAck => process_verack_message(peer_state),
709 NetworkMessage::Addr(addr) => process_addr_message(addr, peer_state, config),
710 NetworkMessage::AddrV2(addrv2) => process_addrv2_message(addrv2, peer_state, config),
711 NetworkMessage::Inv(inv) => process_inv_message(inv, chain_access, config),
712 NetworkMessage::GetData(getdata) => process_getdata_message(getdata, chain_access, config),
713 NetworkMessage::GetHeaders(getheaders) => {
714 process_getheaders_message(getheaders, chain_access, config)
715 }
716 NetworkMessage::Headers(headers) => process_headers_message(headers, config),
717 NetworkMessage::Block(block, witnesses) => {
718 process_block_message(engine, block, witnesses, utxo_set, height, config)
719 }
720 NetworkMessage::Tx(tx, witnesses) => process_tx_message(engine, tx, witnesses, height),
721 NetworkMessage::Ping(ping) => process_ping_message(ping, peer_state),
722 NetworkMessage::Pong(pong) => process_pong_message(pong, peer_state),
723 NetworkMessage::MemPool => process_mempool_message(chain_access),
724 NetworkMessage::FeeFilter(feefilter) => process_feefilter_message(feefilter, peer_state),
725 NetworkMessage::GetBlocks(getblocks) => {
726 process_getblocks_message(getblocks, chain_access, config)
727 }
728 NetworkMessage::GetAddr => process_getaddr_message(peer_state, config),
729 NetworkMessage::NotFound(notfound) => process_notfound_message(notfound, config),
730 NetworkMessage::Reject(reject) => process_reject_message(reject, config),
731 NetworkMessage::SendHeaders => process_sendheaders_message(peer_state),
732 NetworkMessage::SendCmpct(sendcmpct) => {
733 process_sendcmpct_message(sendcmpct, peer_state, config)
734 }
735 NetworkMessage::CmpctBlock(cmpctblock) => process_cmpctblock_message(
736 engine,
737 cmpctblock,
738 peer_state,
739 chain_access,
740 utxo_set,
741 height,
742 config,
743 ),
744 NetworkMessage::GetBlockTxn(getblocktxn) => {
745 process_getblocktxn_message(getblocktxn, chain_access, config)
746 }
747 NetworkMessage::BlockTxn(blocktxn) => {
748 process_blocktxn_message(engine, blocktxn, peer_state, utxo_set, height, config)
749 }
750 #[cfg(feature = "utxo-commitments")]
751 NetworkMessage::GetUTXOSet(getutxoset) => {
752 process_getutxoset_message(getutxoset, chain_access)
753 }
754 #[cfg(feature = "utxo-commitments")]
755 NetworkMessage::UTXOSet(utxoset) => process_utxoset_message(utxoset, chain_access),
756 #[cfg(feature = "utxo-commitments")]
757 NetworkMessage::GetFilteredBlock(getfiltered) => {
758 process_getfilteredblock_message(getfiltered, chain_access)
759 }
760 #[cfg(feature = "utxo-commitments")]
761 NetworkMessage::FilteredBlock(filtered) => {
762 process_filteredblock_message(filtered, chain_access)
763 }
764 NetworkMessage::GetBanList(getbanlist) => {
765 process_getbanlist_message(getbanlist, chain_access)
766 }
767 NetworkMessage::BanList(banlist) => process_banlist_message(banlist, chain_access),
768 }
769}
770
771fn process_version_message(
773 version: &VersionMessage,
774 peer_state: &mut PeerState,
775 config: &ProtocolConfig,
776) -> Result<NetworkResponse> {
777 if version.version < 70001 {
779 return Ok(NetworkResponse::Reject("Version too old".into()));
780 }
781
782 if version.user_agent.len() > config.network_limits.max_user_agent_length {
784 return Ok(NetworkResponse::Reject(format!(
785 "User agent too long (max {} bytes)",
786 config.network_limits.max_user_agent_length
787 )));
788 }
789
790 peer_state.version = version.version;
792 peer_state.services = version.services;
793 peer_state.user_agent = version.user_agent.clone();
794 peer_state.start_height = version.start_height;
795
796 #[cfg(all(
798 feature = "bip324",
799 any(target_arch = "x86_64", target_arch = "aarch64")
800 ))]
801 {
802 use crate::service_flags::{has_flag, standard};
803 let peer_supports_v2 = has_flag(version.services, standard::NODE_V2_TRANSPORT);
804 let we_support_v2 = config.service_flags.node_v2_transport;
805
806 if peer_supports_v2 && we_support_v2 {
807 let handshake = crate::v2_transport::V2Handshake::new_responder();
809 peer_state.v2_handshake = Some(std::sync::Arc::new(handshake));
810 }
813 }
814
815 Ok(NetworkResponse::SendMessage(Box::new(
817 NetworkMessage::VerAck,
818 )))
819}
820
821fn process_verack_message(peer_state: &mut PeerState) -> Result<NetworkResponse> {
823 peer_state.handshake_complete = true;
824 Ok(NetworkResponse::Ok)
825}
826
827fn process_addr_message(
829 addr: &AddrMessage,
830 peer_state: &mut PeerState,
831 config: &ProtocolConfig,
832) -> Result<NetworkResponse> {
833 if addr.addresses.len() > config.network_limits.max_addr_addresses {
835 return Ok(NetworkResponse::Reject(format!(
836 "Too many addresses (max {})",
837 config.network_limits.max_addr_addresses
838 )));
839 }
840
841 peer_state.known_addresses.extend(addr.addresses.clone());
843
844 Ok(NetworkResponse::Ok)
845}
846
847fn process_addrv2_message(
849 addrv2: &AddrV2Message,
850 peer_state: &mut PeerState,
851 config: &ProtocolConfig,
852) -> Result<NetworkResponse> {
853 if addrv2.addresses.len() > config.network_limits.max_addr_addresses {
855 return Ok(NetworkResponse::Reject(format!(
856 "Too many addresses (max {})",
857 config.network_limits.max_addr_addresses
858 )));
859 }
860
861 for addr_v2 in &addrv2.addresses {
863 if let Some(legacy_addr) = addr_v2.to_legacy() {
864 peer_state.known_addresses.push(legacy_addr);
865 }
866 }
869
870 Ok(NetworkResponse::Ok)
871}
872
873fn process_inv_message(
875 inv: &InvMessage,
876 chain_access: Option<&dyn ChainStateAccess>,
877 config: &ProtocolConfig,
878) -> Result<NetworkResponse> {
879 if inv.inventory.len() > config.network_limits.max_inv_items {
881 return Ok(NetworkResponse::Reject(format!(
882 "Too many inventory items (max {})",
883 config.network_limits.max_inv_items
884 )));
885 }
886
887 if let Some(chain) = chain_access {
889 let mut needed_items = Vec::with_capacity(inv.inventory.len());
890 for item in &inv.inventory {
891 if !chain.has_object(&item.hash) {
892 needed_items.push(item.clone());
893 }
894 }
895
896 if !needed_items.is_empty() {
897 return Ok(NetworkResponse::SendMessage(Box::new(
898 NetworkMessage::GetData(GetDataMessage {
899 inventory: needed_items,
900 }),
901 )));
902 }
903 }
904
905 Ok(NetworkResponse::Ok)
906}
907
908fn process_getdata_message(
910 getdata: &GetDataMessage,
911 chain_access: Option<&dyn ChainStateAccess>,
912 config: &ProtocolConfig,
913) -> Result<NetworkResponse> {
914 if getdata.inventory.len() > config.network_limits.max_inv_items {
916 return Ok(NetworkResponse::Reject(format!(
917 "Too many getdata items (max {})",
918 config.network_limits.max_inv_items
919 )));
920 }
921
922 if let Some(chain) = chain_access {
924 let mut responses = Vec::with_capacity(getdata.inventory.len());
925 for item in &getdata.inventory {
926 if let Some(obj) = chain.get_object(&item.hash) {
927 match item.inv_type {
928 1 => {
929 if let Some(tx) = obj.as_transaction() {
931 responses.push(NetworkMessage::Tx(Arc::clone(tx), Vec::new()));
932 }
933 }
934 2 => {
935 if let Some(block) = obj.as_block() {
937 let Some(witnesses) = chain.get_block_witnesses(&item.hash) else {
938 continue;
941 };
942 responses.push(NetworkMessage::Block(Arc::clone(block), witnesses));
943 }
944 }
945 _ => {
946 }
948 }
949 }
950 }
951
952 if !responses.is_empty() {
953 return Ok(NetworkResponse::SendMessages(responses));
954 }
955 }
956
957 Ok(NetworkResponse::Ok)
958}
959
960fn process_getheaders_message(
962 getheaders: &GetHeadersMessage,
963 chain_access: Option<&dyn ChainStateAccess>,
964 config: &ProtocolConfig,
965) -> Result<NetworkResponse> {
966 if getheaders.block_locator_hashes.len() > config.validation.max_locator_hashes {
968 return Ok(NetworkResponse::Reject(format!(
969 "Too many locator hashes (max {})",
970 config.validation.max_locator_hashes
971 )));
972 }
973
974 if let Some(chain) = chain_access {
976 let headers =
977 chain.get_headers_for_locator(&getheaders.block_locator_hashes, &getheaders.hash_stop);
978 return Ok(NetworkResponse::SendMessage(Box::new(
979 NetworkMessage::Headers(HeadersMessage { headers }),
980 )));
981 }
982
983 Ok(NetworkResponse::Reject("Chain access not available".into()))
984}
985
986fn process_headers_message(
988 headers: &HeadersMessage,
989 config: &ProtocolConfig,
990) -> Result<NetworkResponse> {
991 if headers.headers.len() > config.network_limits.max_headers {
993 return Ok(NetworkResponse::Reject(format!(
994 "Too many headers (max {})",
995 config.network_limits.max_headers
996 )));
997 }
998
999 Ok(NetworkResponse::Ok)
1002}
1003
1004fn process_block_message(
1006 engine: &BitcoinProtocolEngine,
1007 block: &Block,
1008 witnesses: &[Vec<blvm_consensus::segwit::Witness>],
1009 utxo_set: Option<&UtxoSet>,
1010 height: Option<u64>,
1011 config: &ProtocolConfig,
1012) -> Result<NetworkResponse> {
1013 if block.transactions.len() > config.validation.max_txs_per_block {
1015 return Err(crate::error::ProtocolError::MessageTooLarge {
1016 size: block.transactions.len(),
1017 max: config.validation.max_txs_per_block,
1018 });
1019 }
1020
1021 if let (Some(utxos), Some(h)) = (utxo_set, height) {
1023 let context = ProtocolValidationContext::new(engine.get_protocol_version(), h)?;
1024 let result = engine
1026 .validate_block_with_protocol_and_witnesses(block, witnesses, utxos, h, &context)?;
1027
1028 match result {
1029 ValidationResult::Valid => Ok(NetworkResponse::Ok),
1030 ValidationResult::Invalid(reason) => {
1031 Ok(NetworkResponse::Reject(format!("Invalid block: {reason}")))
1032 }
1033 }
1034 } else {
1035 Err(crate::error::ProtocolError::Configuration(
1036 "Missing validation context (utxo_set and height required)".into(),
1037 ))
1038 }
1039}
1040
1041fn process_tx_message(
1043 engine: &BitcoinProtocolEngine,
1044 tx: &Transaction,
1045 _witnesses: &[crate::segwit::Witness],
1046 height: Option<u64>,
1047) -> Result<NetworkResponse> {
1048 let context =
1050 ProtocolValidationContext::new(engine.get_protocol_version(), height.unwrap_or(0))?;
1051 let result = engine.validate_transaction_with_protocol(tx, &context)?;
1052
1053 match result {
1054 ValidationResult::Valid => Ok(NetworkResponse::Ok),
1055 ValidationResult::Invalid(reason) => Ok(NetworkResponse::Reject(format!(
1056 "Invalid transaction: {reason}"
1057 ))),
1058 }
1059}
1060
1061fn process_ping_message(
1063 ping: &PingMessage,
1064 _peer_state: &mut PeerState,
1065) -> Result<NetworkResponse> {
1066 let pong = NetworkMessage::Pong(PongMessage { nonce: ping.nonce });
1067 Ok(NetworkResponse::SendMessage(Box::new(pong)))
1068}
1069
1070fn process_pong_message(pong: &PongMessage, peer_state: &mut PeerState) -> Result<NetworkResponse> {
1072 if peer_state.ping_nonce == Some(pong.nonce) {
1074 peer_state.ping_nonce = None;
1075 peer_state.last_pong = Some(std::time::SystemTime::now());
1076 }
1077
1078 Ok(NetworkResponse::Ok)
1079}
1080
1081fn process_mempool_message(chain_access: Option<&dyn ChainStateAccess>) -> Result<NetworkResponse> {
1083 if let Some(chain) = chain_access {
1085 let mempool_txs = chain.get_mempool_transactions();
1086 let mut responses = Vec::with_capacity(mempool_txs.len());
1087
1088 for tx in mempool_txs {
1089 responses.push(NetworkMessage::Tx(Arc::new(tx), Vec::new()));
1090 }
1091
1092 if !responses.is_empty() {
1093 return Ok(NetworkResponse::SendMessages(responses));
1094 }
1095 }
1096
1097 Ok(NetworkResponse::Ok)
1098}
1099
1100fn process_feefilter_message(
1102 feefilter: &FeeFilterMessage,
1103 peer_state: &mut PeerState,
1104) -> Result<NetworkResponse> {
1105 peer_state.min_fee_rate = Some(feefilter.feerate);
1106 Ok(NetworkResponse::Ok)
1107}
1108
1109fn process_getblocks_message(
1111 getblocks: &GetBlocksMessage,
1112 chain_access: Option<&dyn ChainStateAccess>,
1113 config: &ProtocolConfig,
1114) -> Result<NetworkResponse> {
1115 if getblocks.block_locator_hashes.len() > config.validation.max_locator_hashes {
1117 return Ok(NetworkResponse::Reject(format!(
1118 "Too many locator hashes (max {})",
1119 config.validation.max_locator_hashes
1120 )));
1121 }
1122
1123 if let Some(chain) = chain_access {
1124 use blvm_consensus::block::block_header_hash;
1125
1126 const MAX_GETBLOCKS_INV: usize = 500;
1128 let headers =
1129 chain.get_headers_for_locator(&getblocks.block_locator_hashes, &getblocks.hash_stop);
1130 if headers.is_empty() {
1131 return Ok(NetworkResponse::Ok);
1132 }
1133
1134 let inventory: Vec<InventoryVector> = headers
1135 .into_iter()
1136 .take(MAX_GETBLOCKS_INV)
1137 .map(|header| InventoryVector {
1138 inv_type: 2, hash: block_header_hash(&header),
1140 })
1141 .collect();
1142
1143 return Ok(NetworkResponse::SendMessage(Box::new(NetworkMessage::Inv(
1144 InvMessage { inventory },
1145 ))));
1146 }
1147
1148 Ok(NetworkResponse::Ok)
1149}
1150
1151fn process_getaddr_message(
1153 peer_state: &mut PeerState,
1154 config: &ProtocolConfig,
1155) -> Result<NetworkResponse> {
1156 if !peer_state.known_addresses.is_empty() {
1158 let max_addrs = config
1160 .network_limits
1161 .max_addr_addresses
1162 .min(peer_state.known_addresses.len());
1163 let mut addresses = Vec::with_capacity(max_addrs);
1164 addresses.extend(peer_state.known_addresses.iter().take(max_addrs).cloned());
1165
1166 return Ok(NetworkResponse::SendMessage(Box::new(
1167 NetworkMessage::Addr(AddrMessage { addresses }),
1168 )));
1169 }
1170
1171 Ok(NetworkResponse::Ok)
1172}
1173
1174fn process_notfound_message(
1176 notfound: &NotFoundMessage,
1177 config: &ProtocolConfig,
1178) -> Result<NetworkResponse> {
1179 if notfound.inventory.len() > config.network_limits.max_inv_items {
1181 return Ok(NetworkResponse::Reject(format!(
1182 "Too many notfound items (max {})",
1183 config.network_limits.max_inv_items
1184 )));
1185 }
1186
1187 Ok(NetworkResponse::Ok)
1189}
1190
1191fn process_reject_message(
1193 reject: &RejectMessage,
1194 _config: &ProtocolConfig,
1195) -> Result<NetworkResponse> {
1196 if reject.message.len() > 12 {
1199 return Ok(NetworkResponse::Reject(
1200 "Invalid reject message name".into(),
1201 ));
1202 }
1203
1204 if reject.reason.len() > 111 {
1207 return Ok(NetworkResponse::Reject("Reject reason too long".into()));
1208 }
1209
1210 Ok(NetworkResponse::Ok)
1213}
1214
1215fn process_sendheaders_message(peer_state: &mut PeerState) -> Result<NetworkResponse> {
1217 peer_state.prefer_headers = true;
1218 Ok(NetworkResponse::Ok)
1219}
1220
1221fn process_sendcmpct_message(
1223 sendcmpct: &SendCmpctMessage,
1224 peer_state: &mut PeerState,
1225 config: &ProtocolConfig,
1226) -> Result<NetworkResponse> {
1227 let valid_versions = [1, 2];
1229 if !valid_versions.contains(&sendcmpct.version) {
1230 return Ok(NetworkResponse::Reject(
1231 "Invalid compact block version".into(),
1232 ));
1233 }
1234
1235 if !config.compact_blocks.enabled {
1237 return Ok(NetworkResponse::Reject("Compact blocks not enabled".into()));
1238 }
1239
1240 peer_state.compact_block_version = Some(sendcmpct.version as u8);
1242 peer_state.prefer_compact_block = sendcmpct.prefer_cmpct != 0;
1243 Ok(NetworkResponse::Ok)
1244}
1245
1246fn sha256d(bytes: &[u8]) -> Hash {
1247 use sha2::{Digest, Sha256};
1248 let first = Sha256::digest(bytes);
1249 let second = Sha256::digest(first);
1250 let mut hash = [0u8; 32];
1251 hash.copy_from_slice(&second);
1252 hash
1253}
1254
1255fn txid_of(tx: &Transaction) -> Hash {
1256 let bytes = crate::wire::serialize_tx(tx).unwrap_or_default();
1257 sha256d(&bytes)
1258}
1259
1260fn short_id_preimage(
1263 tx: &Transaction,
1264 witnesses: &[blvm_consensus::segwit::Witness],
1265 version: u64,
1266) -> Hash {
1267 if version > 1 {
1268 if let Ok(bytes) = crate::wire::serialize_tx_with_witness(tx, witnesses) {
1269 return sha256d(&bytes);
1270 }
1271 }
1272 txid_of(tx)
1273}
1274
1275fn stacks_for_reconstructed_slot(
1277 version: u64,
1278 witnesses: &[blvm_consensus::segwit::Witness],
1279) -> Vec<blvm_consensus::segwit::Witness> {
1280 if version > 1 && witnesses.iter().any(|stack| !stack.is_empty()) {
1281 witnesses.to_vec()
1282 } else {
1283 Vec::new()
1284 }
1285}
1286
1287fn negotiated_compact_version(peer_state: &PeerState, config: &ProtocolConfig) -> u64 {
1288 peer_state
1289 .compact_block_version
1290 .map(u64::from)
1291 .unwrap_or(config.compact_blocks.preferred_version)
1292}
1293
1294fn header_hash(header: &BlockHeader) -> Hash {
1295 use sha2::{Digest, Sha256};
1296 let bytes = crate::serialization::serialize_block_header(header);
1297 let first = Sha256::digest(&bytes);
1298 let second = Sha256::digest(first);
1299 let mut hash = [0u8; 32];
1300 hash.copy_from_slice(&second);
1301 hash
1302}
1303
1304fn short_id_hex(id: &ShortTxId) -> String {
1305 hex::encode(id)
1306}
1307
1308fn begin_pending_compact(
1310 msg: &CmpctBlockMessage,
1311 mempool: &[(Transaction, Vec<blvm_consensus::segwit::Witness>)],
1312 version: u64,
1313) -> std::result::Result<PendingCompact, String> {
1314 let n = msg.short_ids.len() + msg.prefilled_txs.len();
1315 let mut slots: Vec<Option<(Transaction, Vec<blvm_consensus::segwit::Witness>)>> = vec![None; n];
1316 let mut prefilled = std::collections::HashSet::new();
1317 for item in &msg.prefilled_txs {
1318 let index = usize::from(item.index);
1319 if index >= n || !prefilled.insert(index) {
1320 return Err(format!(
1321 "prefilled compact-block index {index} is not usable"
1322 ));
1323 }
1324 slots[index] = Some((item.tx.clone(), item.witness.clone().unwrap_or_default()));
1325 }
1326
1327 let mut missing = Vec::new();
1328 let mut next = 0usize;
1329 for short_id in &msg.short_ids {
1330 while prefilled.contains(&next) {
1331 next = next.saturating_add(1);
1332 }
1333 let block_index = next;
1334 next = next.saturating_add(1);
1335 if block_index >= n {
1336 return Err("compact block short ids ran past the transaction list".into());
1337 }
1338 let mut found: Option<(Transaction, Vec<blvm_consensus::segwit::Witness>)> = None;
1339 let mut hits = 0usize;
1340 for (tx, witnesses) in mempool {
1341 let preimage = short_id_preimage(tx, witnesses, version);
1342 let id = crate::bip152::short_tx_id(&msg.header, &preimage, msg.nonce);
1343 if &id == short_id {
1344 hits += 1;
1345 found = Some((
1346 tx.clone(),
1347 stacks_for_reconstructed_slot(version, witnesses),
1348 ));
1349 }
1350 }
1351 if hits == 1 {
1352 slots[block_index] = found;
1353 } else {
1354 missing.push((block_index, *short_id));
1355 }
1356 }
1357 Ok(PendingCompact {
1358 header: msg.header.clone(),
1359 nonce: msg.nonce,
1360 version,
1361 slots,
1362 missing,
1363 })
1364}
1365
1366fn finish_compact_block(
1367 engine: &BitcoinProtocolEngine,
1368 pending: PendingCompact,
1369 utxo_set: Option<&UtxoSet>,
1370 height: Option<u64>,
1371 config: &ProtocolConfig,
1372) -> Result<NetworkResponse> {
1373 if !pending.missing.is_empty() {
1374 let id = short_id_hex(&pending.missing[0].1);
1375 return Ok(NetworkResponse::Reject(format!(
1376 "short id {id} matches neither the mempool nor blocktxn"
1377 )));
1378 }
1379 let mut transactions = Vec::with_capacity(pending.slots.len());
1380 let mut witnesses = Vec::with_capacity(pending.slots.len());
1381 for slot in pending.slots {
1382 let Some((tx, witness)) = slot else {
1383 return Ok(NetworkResponse::Reject(
1384 "compact block still has an empty transaction slot".into(),
1385 ));
1386 };
1387 transactions.push(tx);
1388 witnesses.push(witness);
1389 }
1390 let block = Block::from_parts(pending.header, transactions.into_boxed_slice());
1391 match process_block_message(engine, &block, &witnesses, utxo_set, height, config) {
1392 Ok(response) => Ok(response),
1393 Err(err) if err.to_string().contains("Missing validation context") => {
1394 Ok(NetworkResponse::SendMessage(Box::new(
1395 NetworkMessage::Block(Arc::new(block), witnesses),
1396 )))
1397 }
1398 Err(err) => Err(err),
1399 }
1400}
1401
1402fn process_cmpctblock_message(
1404 engine: &BitcoinProtocolEngine,
1405 cmpctblock: &CmpctBlockMessage,
1406 peer_state: &mut PeerState,
1407 chain_access: Option<&dyn ChainStateAccess>,
1408 utxo_set: Option<&UtxoSet>,
1409 height: Option<u64>,
1410 config: &ProtocolConfig,
1411) -> Result<NetworkResponse> {
1412 if !config.compact_blocks.enabled {
1413 return Ok(NetworkResponse::Reject("Compact blocks not enabled".into()));
1414 }
1415 let version = negotiated_compact_version(peer_state, config);
1416 let mempool = chain_access
1417 .map(|chain| chain.mempool_for_compact_block())
1418 .unwrap_or_default();
1419 let pending = match begin_pending_compact(cmpctblock, &mempool, version) {
1420 Ok(pending) => pending,
1421 Err(reason) => return Ok(NetworkResponse::Reject(reason)),
1422 };
1423 if pending.missing.is_empty() {
1424 peer_state.pending_compact = None;
1425 return finish_compact_block(engine, pending, utxo_set, height, config);
1426 }
1427 let indices: Vec<u16> = pending
1428 .missing
1429 .iter()
1430 .map(|(index, _)| u16::try_from(*index).unwrap_or(u16::MAX))
1431 .collect();
1432 let block_hash = header_hash(&pending.header);
1433 peer_state.pending_compact = Some(pending);
1434 Ok(NetworkResponse::SendMessage(Box::new(
1435 NetworkMessage::GetBlockTxn(GetBlockTxnMessage {
1436 block_hash,
1437 indices,
1438 }),
1439 )))
1440}
1441
1442fn process_getblocktxn_message(
1444 getblocktxn: &GetBlockTxnMessage,
1445 chain_access: Option<&dyn ChainStateAccess>,
1446 config: &ProtocolConfig,
1447) -> Result<NetworkResponse> {
1448 if getblocktxn.indices.len() > config.compact_blocks.max_blocktxn_indices {
1450 return Ok(NetworkResponse::Reject(format!(
1451 "Too many transaction indices (max {})",
1452 config.compact_blocks.max_blocktxn_indices
1453 )));
1454 }
1455
1456 if let Some(chain) = chain_access {
1458 if let Some(obj) = chain.get_object(&getblocktxn.block_hash) {
1459 if let Some(block) = obj.as_block() {
1460 let stored_witnesses = chain.get_block_witnesses(&getblocktxn.block_hash);
1461 let mut transactions = Vec::with_capacity(getblocktxn.indices.len());
1462 let mut req_witnesses = Vec::with_capacity(getblocktxn.indices.len());
1463
1464 for &index in &getblocktxn.indices {
1465 let idx = index as usize;
1466 if idx >= block.transactions.len() {
1467 return Ok(NetworkResponse::Reject(
1468 "getblocktxn index past end of block".into(),
1469 ));
1470 }
1471 transactions.push(block.transactions[idx].clone());
1472 let tx_witness = stored_witnesses
1473 .as_ref()
1474 .and_then(|w| w.get(idx).cloned())
1475 .unwrap_or_else(|| {
1476 block.transactions[idx]
1477 .inputs
1478 .iter()
1479 .map(|_| Vec::new())
1480 .collect()
1481 });
1482 req_witnesses.push(tx_witness);
1483 }
1484
1485 if !transactions.is_empty() {
1486 return Ok(NetworkResponse::SendMessage(Box::new(
1487 NetworkMessage::BlockTxn(BlockTxnMessage {
1488 block_hash: getblocktxn.block_hash,
1489 transactions,
1490 witnesses: Some(req_witnesses),
1491 }),
1492 )));
1493 }
1494 }
1495 }
1496 }
1497
1498 Ok(NetworkResponse::Ok)
1499}
1500
1501fn process_blocktxn_message(
1503 engine: &BitcoinProtocolEngine,
1504 blocktxn: &BlockTxnMessage,
1505 peer_state: &mut PeerState,
1506 utxo_set: Option<&UtxoSet>,
1507 height: Option<u64>,
1508 config: &ProtocolConfig,
1509) -> Result<NetworkResponse> {
1510 let Some(mut pending) = peer_state.pending_compact.clone() else {
1511 return Ok(NetworkResponse::Reject(
1512 "blocktxn does not match a pending compact block".into(),
1513 ));
1514 };
1515 if header_hash(&pending.header) != blocktxn.block_hash {
1516 return Ok(NetworkResponse::Reject(
1517 "blocktxn does not match a pending compact block".into(),
1518 ));
1519 }
1520 if blocktxn.transactions.len() != pending.missing.len() {
1521 let id = pending
1522 .missing
1523 .first()
1524 .map(|(_, short_id)| short_id_hex(short_id))
1525 .unwrap_or_else(|| "none".into());
1526 return Ok(NetworkResponse::Reject(format!(
1527 "short id {id} matches neither the mempool nor blocktxn"
1528 )));
1529 }
1530 for (i, (index, expected)) in pending.missing.iter().enumerate() {
1531 let tx = &blocktxn.transactions[i];
1532 let witness = blocktxn
1533 .witnesses
1534 .as_ref()
1535 .and_then(|all| all.get(i).cloned())
1536 .unwrap_or_default();
1537 let preimage = short_id_preimage(tx, &witness, pending.version);
1538 let got = crate::bip152::short_tx_id(&pending.header, &preimage, pending.nonce);
1539 if &got != expected {
1540 return Ok(NetworkResponse::Reject(format!(
1541 "short id {} matches neither the mempool nor blocktxn",
1542 short_id_hex(expected)
1543 )));
1544 }
1545 pending.slots[*index] = Some((
1546 tx.clone(),
1547 stacks_for_reconstructed_slot(pending.version, &witness),
1548 ));
1549 }
1550 pending.missing.clear();
1551 peer_state.pending_compact = None;
1552 finish_compact_block(engine, pending, utxo_set, height, config)
1553}
1554
1555#[cfg(feature = "utxo-commitments")]
1556fn process_getutxoset_message(
1558 getutxoset: &commons::GetUTXOSetMessage,
1559 chain_access: Option<&dyn ChainStateAccess>,
1560) -> Result<NetworkResponse> {
1561 let Some(chain) = chain_access else {
1562 return Err(ProtocolError::Validation(
1563 "utxo set is not available".into(),
1564 ));
1565 };
1566 match chain.stored_utxo_commitment(&getutxoset.block_hash) {
1567 Ok(Some(commitment)) => Ok(NetworkResponse::SendMessage(Box::new(
1568 NetworkMessage::UTXOSet(commons::UTXOSetMessage {
1569 request_id: 0,
1570 utxo_count: commitment.utxo_count,
1571 is_complete: true,
1572 chunk_id: None,
1573 commitment,
1574 }),
1575 ))),
1576 Ok(None) => Err(ProtocolError::Validation(
1577 format!(
1578 "no stored UTXO set for block {}",
1579 hex::encode(getutxoset.block_hash)
1580 )
1581 .into(),
1582 )),
1583 Err(reason) => Err(ProtocolError::Validation(reason.into())),
1584 }
1585}
1586
1587#[cfg(feature = "utxo-commitments")]
1588fn process_utxoset_message(
1590 utxoset: &commons::UTXOSetMessage,
1591 chain_access: Option<&dyn ChainStateAccess>,
1592) -> Result<NetworkResponse> {
1593 let Some(chain) = chain_access else {
1594 return Ok(NetworkResponse::Reject(
1595 "utxo commitment does not match the local set".into(),
1596 ));
1597 };
1598 match chain.stored_utxo_commitment(&utxoset.commitment.block_hash) {
1599 Ok(Some(local)) if local == utxoset.commitment => Ok(NetworkResponse::Ok),
1600 Ok(_) => Ok(NetworkResponse::Reject(format!(
1601 "utxo commitment does not match the local set for {}",
1602 hex::encode(utxoset.commitment.block_hash)
1603 ))),
1604 Err(reason) => Ok(NetworkResponse::Reject(reason)),
1605 }
1606}
1607
1608#[cfg(feature = "utxo-commitments")]
1609fn process_getfilteredblock_message(
1611 getfiltered: &commons::GetFilteredBlockMessage,
1612 chain_access: Option<&dyn ChainStateAccess>,
1613) -> Result<NetworkResponse> {
1614 let Some(chain) = chain_access else {
1615 return Err(ProtocolError::Validation(
1616 "block filter is not available".into(),
1617 ));
1618 };
1619 match chain.stored_block_filter(&getfiltered.block_hash) {
1620 Ok(Some((header, filter))) => {
1621 let commitment = chain
1622 .stored_utxo_commitment(&getfiltered.block_hash)
1623 .ok()
1624 .flatten()
1625 .unwrap_or(commons::UTXOCommitment {
1626 merkle_root: [0u8; 32],
1627 total_supply: 0,
1628 utxo_count: 0,
1629 block_height: 0,
1630 block_hash: getfiltered.block_hash,
1631 });
1632 Ok(NetworkResponse::SendMessage(Box::new(
1633 NetworkMessage::FilteredBlock(commons::FilteredBlockMessage {
1634 request_id: getfiltered.request_id,
1635 header,
1636 commitment,
1637 transactions: Vec::new(),
1638 transaction_indices: Vec::new(),
1639 spam_summary: commons::SpamSummary {
1640 filtered_count: 0,
1641 filtered_value: 0,
1642 filter_reasons: 0,
1643 },
1644 bip158_filter: Some(filter),
1645 }),
1646 )))
1647 }
1648 Ok(None) => Err(ProtocolError::Validation(
1649 format!(
1650 "no block filter for {}",
1651 hex::encode(getfiltered.block_hash)
1652 )
1653 .into(),
1654 )),
1655 Err(reason) => Err(ProtocolError::Validation(reason.into())),
1656 }
1657}
1658
1659#[cfg(feature = "utxo-commitments")]
1660fn process_filteredblock_message(
1662 filtered: &commons::FilteredBlockMessage,
1663 chain_access: Option<&dyn ChainStateAccess>,
1664) -> Result<NetworkResponse> {
1665 let Some(chain) = chain_access else {
1666 return Ok(NetworkResponse::Reject(
1667 "filtered block does not match the local filter".into(),
1668 ));
1669 };
1670 let block_hash = header_hash(&filtered.header);
1671 match chain.stored_block_filter(&block_hash) {
1672 Ok(Some((_, local))) if filtered.bip158_filter.as_ref() == Some(&local) => {
1673 Ok(NetworkResponse::Ok)
1674 }
1675 Ok(_) => Ok(NetworkResponse::Reject(format!(
1676 "filtered block does not match the local filter for {}",
1677 hex::encode(block_hash)
1678 ))),
1679 Err(reason) => Ok(NetworkResponse::Reject(reason)),
1680 }
1681}
1682
1683fn process_getbanlist_message(
1685 getbanlist: &commons::GetBanListMessage,
1686 chain_access: Option<&dyn ChainStateAccess>,
1687) -> Result<NetworkResponse> {
1688 let mut entries = chain_access
1689 .map(|chain| chain.ban_entries())
1690 .unwrap_or_default();
1691 if let Some(min_score) = getbanlist.min_score {
1692 entries.retain(|entry| entry.score >= min_score);
1693 }
1694 Ok(NetworkResponse::SendMessage(Box::new(
1695 NetworkMessage::BanList(commons::BanListMessage {
1696 request_id: getbanlist.request_id,
1697 entries,
1698 list_signature: None,
1699 }),
1700 )))
1701}
1702
1703fn process_banlist_message(
1705 banlist: &commons::BanListMessage,
1706 chain_access: Option<&dyn ChainStateAccess>,
1707) -> Result<NetworkResponse> {
1708 if banlist.entries.is_empty() {
1709 return Ok(NetworkResponse::Ok);
1710 }
1711 let Some(chain) = chain_access else {
1712 return Ok(NetworkResponse::Reject("ban path is not available".into()));
1713 };
1714 for entry in &banlist.entries {
1715 if let Err(reason) = chain.apply_ban(entry) {
1716 return Ok(NetworkResponse::Reject(reason));
1717 }
1718 }
1719 Ok(NetworkResponse::Ok)
1720}
1721
1722#[cfg(test)]
1723fn assert_getblocktxn_past_end_rejects() {
1724 struct TwoTx;
1725 impl ChainStateAccess for TwoTx {
1726 fn has_object(&self, _hash: &Hash) -> bool {
1727 true
1728 }
1729 fn get_object(&self, hash: &Hash) -> Option<ChainObject> {
1730 let tx = Transaction {
1731 version: 1,
1732 inputs: vec![].into(),
1733 outputs: vec![].into(),
1734 lock_time: 0,
1735 };
1736 let block = Block {
1737 header: BlockHeader {
1738 version: 1,
1739 prev_block_hash: [0; 32],
1740 merkle_root: [0; 32],
1741 timestamp: 1,
1742 bits: 1,
1743 nonce: 1,
1744 },
1745 transactions: vec![tx.clone(), tx].into_boxed_slice(),
1746 };
1747 if hash == &[9u8; 32] {
1748 Some(ChainObject::Block(Arc::new(block)))
1749 } else {
1750 None
1751 }
1752 }
1753 fn get_headers_for_locator(&self, _locator: &[Hash], _stop: &Hash) -> Vec<BlockHeader> {
1754 Vec::new()
1755 }
1756 fn get_mempool_transactions(&self) -> Vec<Transaction> {
1757 Vec::new()
1758 }
1759 }
1760 let msg = GetBlockTxnMessage {
1761 block_hash: [9u8; 32],
1762 indices: vec![5],
1763 };
1764 let response =
1765 process_getblocktxn_message(&msg, Some(&TwoTx), &ProtocolConfig::default()).unwrap();
1766 assert!(
1767 matches!(response, NetworkResponse::Reject(_)),
1768 "expected reject, got {response:?}"
1769 );
1770}
1771
1772#[cfg(test)]
1773#[test]
1774fn getblocktxn_index_past_block_returns_reject_and_no_blocktxn() {
1775 assert_getblocktxn_past_end_rejects();
1776}
1777
1778#[cfg(test)]
1779fn sample_tx(tag: u8) -> Transaction {
1780 Transaction {
1781 version: 1,
1782 inputs: vec![blvm_consensus::types::TransactionInput {
1783 prevout: blvm_consensus::types::OutPoint {
1784 hash: [tag; 32],
1785 index: 0,
1786 },
1787 script_sig: vec![tag],
1788 sequence: 0xffffffff,
1789 }]
1790 .into(),
1791 outputs: vec![blvm_consensus::types::TransactionOutput {
1792 value: 1000,
1793 script_pubkey: vec![0x51],
1794 }]
1795 .into(),
1796 lock_time: 0,
1797 }
1798}
1799
1800#[cfg(test)]
1801fn sample_header() -> BlockHeader {
1802 BlockHeader {
1803 version: 1,
1804 prev_block_hash: [2u8; 32],
1805 merkle_root: [3u8; 32],
1806 timestamp: 1_700_000_000,
1807 bits: 0x1d00ffff,
1808 nonce: 4,
1809 }
1810}
1811
1812#[cfg(test)]
1813struct MempoolChain(Transaction);
1814
1815#[cfg(test)]
1816impl ChainStateAccess for MempoolChain {
1817 fn has_object(&self, _hash: &Hash) -> bool {
1818 false
1819 }
1820 fn get_object(&self, _hash: &Hash) -> Option<ChainObject> {
1821 None
1822 }
1823 fn get_headers_for_locator(&self, _locator: &[Hash], _stop: &Hash) -> Vec<BlockHeader> {
1824 Vec::new()
1825 }
1826 fn get_mempool_transactions(&self) -> Vec<Transaction> {
1827 vec![self.0.clone()]
1828 }
1829}
1830
1831#[cfg(test)]
1832#[test]
1833fn compact_block_rebuilds_from_mempool_and_names_a_miss() {
1834 use crate::{BitcoinProtocolEngine, ProtocolVersion};
1835 let engine = BitcoinProtocolEngine::new(ProtocolVersion::BitcoinV1).unwrap();
1836 let header = sample_header();
1837 let coinbase = sample_tx(0);
1838 let pooled = sample_tx(7);
1839 let nonce = 9u64;
1840 let short = crate::bip152::short_tx_id(&header, &txid_of(&pooled), nonce);
1841 let cmpct = CmpctBlockMessage {
1842 header: header.clone(),
1843 nonce,
1844 short_ids: vec![short],
1845 prefilled_txs: vec![PrefilledTransaction {
1846 index: 0,
1847 tx: coinbase.clone(),
1848 witness: None,
1849 }],
1850 };
1851 let mut peer = PeerState::new();
1852 let response = process_cmpctblock_message(
1853 &engine,
1854 &cmpct,
1855 &mut peer,
1856 Some(&MempoolChain(pooled.clone())),
1857 None,
1858 None,
1859 &ProtocolConfig::default(),
1860 )
1861 .unwrap();
1862 match response {
1863 NetworkResponse::SendMessage(message) => match *message {
1864 NetworkMessage::Block(block, _) => {
1865 assert_eq!(block.header, header);
1866 assert_eq!(block.transactions.len(), 2);
1867 assert_eq!(block.transactions[0], coinbase);
1868 assert_eq!(block.transactions[1], pooled);
1869 }
1870 other => panic!("expected the reconstructed block, got {other:?}"),
1871 },
1872 other => panic!("expected the reconstructed block, got {other:?}"),
1873 }
1874
1875 let missing = [0xabu8; 6];
1876 let cmpct = CmpctBlockMessage {
1877 header: header.clone(),
1878 nonce,
1879 short_ids: vec![missing],
1880 prefilled_txs: vec![PrefilledTransaction {
1881 index: 0,
1882 tx: coinbase,
1883 witness: None,
1884 }],
1885 };
1886 let mut peer = PeerState::new();
1887 let asked = process_cmpctblock_message(
1888 &engine,
1889 &cmpct,
1890 &mut peer,
1891 Some(&MempoolChain(pooled.clone())),
1892 None,
1893 None,
1894 &ProtocolConfig::default(),
1895 )
1896 .unwrap();
1897 assert!(matches!(
1898 asked,
1899 NetworkResponse::SendMessage(ref message) if matches!(**message, NetworkMessage::GetBlockTxn(_))
1900 ));
1901 let wrong = BlockTxnMessage {
1902 block_hash: header_hash(&header),
1903 transactions: vec![pooled],
1904 witnesses: None,
1905 };
1906 let rejected = process_blocktxn_message(
1907 &engine,
1908 &wrong,
1909 &mut peer,
1910 None,
1911 None,
1912 &ProtocolConfig::default(),
1913 )
1914 .unwrap();
1915 match rejected {
1916 NetworkResponse::Reject(reason) => {
1917 assert!(reason.contains(&short_id_hex(&missing)), "{reason}");
1918 }
1919 other => panic!("expected a named short-id reject, got {other:?}"),
1920 }
1921}
1922
1923#[cfg(test)]
1924struct WitnessMempool {
1925 tx: Transaction,
1926 witnesses: Vec<blvm_consensus::segwit::Witness>,
1927}
1928
1929#[cfg(test)]
1930impl ChainStateAccess for WitnessMempool {
1931 fn has_object(&self, _hash: &Hash) -> bool {
1932 false
1933 }
1934 fn get_object(&self, _hash: &Hash) -> Option<ChainObject> {
1935 None
1936 }
1937 fn get_headers_for_locator(&self, _locator: &[Hash], _stop: &Hash) -> Vec<BlockHeader> {
1938 Vec::new()
1939 }
1940 fn get_mempool_transactions(&self) -> Vec<Transaction> {
1941 vec![self.tx.clone()]
1942 }
1943 fn mempool_for_compact_block(
1944 &self,
1945 ) -> Vec<(Transaction, Vec<blvm_consensus::segwit::Witness>)> {
1946 vec![(self.tx.clone(), self.witnesses.clone())]
1947 }
1948}
1949
1950#[cfg(test)]
1951#[test]
1952fn compact_block_version_two_matches_wtxid_and_keeps_the_witness() {
1953 use crate::{BitcoinProtocolEngine, ProtocolVersion};
1954 let engine = BitcoinProtocolEngine::new(ProtocolVersion::BitcoinV1).unwrap();
1955 let header = sample_header();
1956 let coinbase = sample_tx(0);
1957 let pooled = sample_tx(7);
1958 let stacks = vec![vec![vec![0x30, 0x01]]];
1959 let nonce = 11u64;
1960 let wtxid = short_id_preimage(&pooled, &stacks, 2);
1961 let txid = short_id_preimage(&pooled, &stacks, 1);
1962 assert_ne!(wtxid, txid);
1963 let short = crate::bip152::short_tx_id(&header, &wtxid, nonce);
1964 let cmpct = CmpctBlockMessage {
1965 header: header.clone(),
1966 nonce,
1967 short_ids: vec![short],
1968 prefilled_txs: vec![PrefilledTransaction {
1969 index: 0,
1970 tx: coinbase,
1971 witness: None,
1972 }],
1973 };
1974 let mut peer = PeerState::new();
1975 peer.compact_block_version = Some(2);
1976 let response = process_cmpctblock_message(
1977 &engine,
1978 &cmpct,
1979 &mut peer,
1980 Some(&WitnessMempool {
1981 tx: pooled.clone(),
1982 witnesses: stacks.clone(),
1983 }),
1984 None,
1985 None,
1986 &ProtocolConfig::default(),
1987 )
1988 .unwrap();
1989 match response {
1990 NetworkResponse::SendMessage(message) => match *message {
1991 NetworkMessage::Block(block, witnesses) => {
1992 assert_eq!(block.transactions.len(), 2);
1993 assert_eq!(block.transactions[1], pooled);
1994 assert_eq!(witnesses.len(), 2);
1995 assert!(witnesses[0].is_empty());
1996 assert_eq!(witnesses[1], stacks);
1997 }
1998 other => panic!("expected the reconstructed block, got {other:?}"),
1999 },
2000 other => panic!("expected the reconstructed block, got {other:?}"),
2001 }
2002
2003 let txid_short = crate::bip152::short_tx_id(&header, &txid, nonce);
2004 let cmpct = CmpctBlockMessage {
2005 header,
2006 nonce,
2007 short_ids: vec![txid_short],
2008 prefilled_txs: vec![PrefilledTransaction {
2009 index: 0,
2010 tx: sample_tx(0),
2011 witness: None,
2012 }],
2013 };
2014 let mut peer = PeerState::new();
2015 peer.compact_block_version = Some(2);
2016 let asked = process_cmpctblock_message(
2017 &engine,
2018 &cmpct,
2019 &mut peer,
2020 Some(&WitnessMempool {
2021 tx: pooled,
2022 witnesses: stacks,
2023 }),
2024 None,
2025 None,
2026 &ProtocolConfig::default(),
2027 )
2028 .unwrap();
2029 assert!(matches!(
2030 asked,
2031 NetworkResponse::SendMessage(ref message) if matches!(**message, NetworkMessage::GetBlockTxn(_))
2032 ));
2033}
2034
2035#[cfg(test)]
2036struct BanBook {
2037 entries: std::sync::Mutex<Vec<crate::commons::BanEntry>>,
2038}
2039
2040#[cfg(test)]
2041impl ChainStateAccess for BanBook {
2042 fn has_object(&self, _hash: &Hash) -> bool {
2043 false
2044 }
2045 fn get_object(&self, _hash: &Hash) -> Option<ChainObject> {
2046 None
2047 }
2048 fn get_headers_for_locator(&self, _locator: &[Hash], _stop: &Hash) -> Vec<BlockHeader> {
2049 Vec::new()
2050 }
2051 fn get_mempool_transactions(&self) -> Vec<Transaction> {
2052 Vec::new()
2053 }
2054 fn ban_entries(&self) -> Vec<crate::commons::BanEntry> {
2055 self.entries.lock().expect("ban book").clone()
2056 }
2057 fn apply_ban(&self, entry: &crate::commons::BanEntry) -> std::result::Result<(), String> {
2058 self.entries.lock().expect("ban book").push(entry.clone());
2059 Ok(())
2060 }
2061}
2062
2063#[cfg(test)]
2064#[test]
2065fn ban_messages_list_and_apply_without_the_old_reject() {
2066 let book = BanBook {
2067 entries: std::sync::Mutex::new(Vec::new()),
2068 };
2069 let empty = process_getbanlist_message(
2070 &crate::commons::GetBanListMessage {
2071 request_id: 1,
2072 min_score: None,
2073 },
2074 Some(&book),
2075 )
2076 .unwrap();
2077 match empty {
2078 NetworkResponse::SendMessage(message) => match *message {
2079 NetworkMessage::BanList(list) => assert!(list.entries.is_empty()),
2080 other => panic!("expected an empty ban list, got {other:?}"),
2081 },
2082 other => panic!("expected an empty ban list, got {other:?}"),
2083 }
2084 let entry = crate::commons::BanEntry {
2085 ip: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 1, 2, 3, 4],
2086 score: 100,
2087 reason: 1,
2088 timestamp: 1_700_000_000,
2089 signature: None,
2090 };
2091 process_banlist_message(
2092 &crate::commons::BanListMessage {
2093 request_id: 2,
2094 entries: vec![entry.clone()],
2095 list_signature: None,
2096 },
2097 Some(&book),
2098 )
2099 .unwrap();
2100 assert_eq!(book.entries.lock().unwrap().as_slice(), &[entry]);
2101 let none = process_getbanlist_message(
2102 &crate::commons::GetBanListMessage {
2103 request_id: 3,
2104 min_score: None,
2105 },
2106 None,
2107 )
2108 .unwrap();
2109 assert!(matches!(none, NetworkResponse::SendMessage(_)));
2110}
2111
2112#[cfg(all(test, feature = "utxo-commitments"))]
2113struct UtxoBook {
2114 hash: Hash,
2115 commitment: crate::commons::UTXOCommitment,
2116 header: BlockHeader,
2117 filter: Vec<u8>,
2118}
2119
2120#[cfg(all(test, feature = "utxo-commitments"))]
2121impl ChainStateAccess for UtxoBook {
2122 fn has_object(&self, _hash: &Hash) -> bool {
2123 false
2124 }
2125 fn get_object(&self, _hash: &Hash) -> Option<ChainObject> {
2126 None
2127 }
2128 fn get_headers_for_locator(&self, _locator: &[Hash], _stop: &Hash) -> Vec<BlockHeader> {
2129 Vec::new()
2130 }
2131 fn get_mempool_transactions(&self) -> Vec<Transaction> {
2132 Vec::new()
2133 }
2134 fn stored_utxo_commitment(
2135 &self,
2136 block_hash: &Hash,
2137 ) -> std::result::Result<Option<crate::commons::UTXOCommitment>, String> {
2138 if block_hash == &self.hash {
2139 Ok(Some(self.commitment.clone()))
2140 } else {
2141 Ok(None)
2142 }
2143 }
2144 fn stored_block_filter(
2145 &self,
2146 block_hash: &Hash,
2147 ) -> std::result::Result<Option<(BlockHeader, Vec<u8>)>, String> {
2148 if block_hash == &self.hash {
2149 Ok(Some((self.header.clone(), self.filter.clone())))
2150 } else {
2151 Ok(None)
2152 }
2153 }
2154}
2155
2156#[cfg(all(test, feature = "utxo-commitments"))]
2157#[test]
2158fn utxo_and_filter_messages_use_the_stored_set() {
2159 let hash = [9u8; 32];
2160 let commitment = crate::commons::UTXOCommitment {
2161 merkle_root: [4u8; 32],
2162 total_supply: 50,
2163 utxo_count: 2,
2164 block_height: 10,
2165 block_hash: hash,
2166 };
2167 let book = UtxoBook {
2168 hash,
2169 commitment: commitment.clone(),
2170 header: sample_header(),
2171 filter: vec![0x01, 0x02, 0x03],
2172 };
2173 let got = process_getutxoset_message(
2174 &crate::commons::GetUTXOSetMessage {
2175 height: 10,
2176 block_hash: hash,
2177 },
2178 Some(&book),
2179 )
2180 .unwrap();
2181 match got {
2182 NetworkResponse::SendMessage(message) => match *message {
2183 NetworkMessage::UTXOSet(msg) => assert_eq!(msg.commitment, commitment),
2184 other => panic!("expected the stored commitment, got {other:?}"),
2185 },
2186 other => panic!("expected the stored commitment, got {other:?}"),
2187 }
2188 let missing = process_getutxoset_message(
2189 &crate::commons::GetUTXOSetMessage {
2190 height: 11,
2191 block_hash: [1u8; 32],
2192 },
2193 Some(&book),
2194 );
2195 let err = missing.expect_err("a missing set is an error");
2196 assert!(!err.to_string().contains("not implemented"));
2197
2198 let matched = process_utxoset_message(
2199 &crate::commons::UTXOSetMessage {
2200 request_id: 1,
2201 commitment: commitment.clone(),
2202 utxo_count: 2,
2203 is_complete: true,
2204 chunk_id: None,
2205 },
2206 Some(&book),
2207 )
2208 .unwrap();
2209 assert!(matches!(matched, NetworkResponse::Ok));
2210 let mut other_commitment = commitment.clone();
2211 other_commitment.total_supply = 1;
2212 let rejected = process_utxoset_message(
2213 &crate::commons::UTXOSetMessage {
2214 request_id: 1,
2215 commitment: other_commitment,
2216 utxo_count: 2,
2217 is_complete: true,
2218 chunk_id: None,
2219 },
2220 Some(&book),
2221 )
2222 .unwrap();
2223 assert!(matches!(rejected, NetworkResponse::Reject(_)));
2224
2225 let filter_msg = process_getfilteredblock_message(
2226 &crate::commons::GetFilteredBlockMessage {
2227 request_id: 7,
2228 block_hash: hash,
2229 filter_preferences: crate::commons::FilterPreferences {
2230 filter_ordinals: false,
2231 filter_dust: false,
2232 filter_brc20: false,
2233 min_output_value: 0,
2234 },
2235 include_bip158_filter: true,
2236 },
2237 Some(&book),
2238 )
2239 .unwrap();
2240 let sent_filter = match filter_msg {
2241 NetworkResponse::SendMessage(message) => match *message {
2242 NetworkMessage::FilteredBlock(msg) => msg.bip158_filter.expect("filter bytes"),
2243 other => panic!("expected a filter, got {other:?}"),
2244 },
2245 other => panic!("expected a filter, got {other:?}"),
2246 };
2247 assert_eq!(sent_filter, vec![0x01, 0x02, 0x03]);
2248
2249 let header = sample_header();
2250 let stored_hash = header_hash(&header);
2251 let book = UtxoBook {
2252 hash: stored_hash,
2253 commitment,
2254 header: header.clone(),
2255 filter: vec![0x01, 0x02, 0x03],
2256 };
2257 let ok = process_filteredblock_message(
2258 &crate::commons::FilteredBlockMessage {
2259 request_id: 7,
2260 header: header.clone(),
2261 commitment: book.commitment.clone(),
2262 transactions: Vec::new(),
2263 transaction_indices: Vec::new(),
2264 spam_summary: crate::commons::SpamSummary {
2265 filtered_count: 0,
2266 filtered_value: 0,
2267 filter_reasons: 0,
2268 },
2269 bip158_filter: Some(vec![0x01, 0x02, 0x03]),
2270 },
2271 Some(&book),
2272 )
2273 .unwrap();
2274 assert!(matches!(ok, NetworkResponse::Ok));
2275 let bad = process_filteredblock_message(
2276 &crate::commons::FilteredBlockMessage {
2277 request_id: 7,
2278 header,
2279 commitment: book.commitment.clone(),
2280 transactions: Vec::new(),
2281 transaction_indices: Vec::new(),
2282 spam_summary: crate::commons::SpamSummary {
2283 filtered_count: 0,
2284 filtered_value: 0,
2285 filter_reasons: 0,
2286 },
2287 bip158_filter: Some(vec![0xff]),
2288 },
2289 Some(&book),
2290 )
2291 .unwrap();
2292 assert!(matches!(bad, NetworkResponse::Reject(_)));
2293}