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