#[cfg(feature = "protocol-verification")]
use blvm_spec_lock::spec_locked;
use crate::network::transport::TransportType;
use anyhow::Result;
use blvm_protocol::segwit::Witness;
use blvm_protocol::wire::{
deserialize_addr, deserialize_blocktxn, deserialize_cmpctblock, deserialize_getblocktxn,
deserialize_getdata, deserialize_headers, deserialize_inv, deserialize_notfound,
deserialize_ping, deserialize_pong, deserialize_tx_with_witness, serialize_addr,
serialize_blocktxn, serialize_cmpctblock, serialize_getblocktxn, serialize_getdata,
serialize_getheaders, serialize_inv, serialize_notfound, serialize_ping, serialize_pong,
serialize_tx_with_witness,
};
use blvm_protocol::{Block, BlockHeader, Hash, Transaction};
use serde::{Deserialize, Serialize};
pub use blvm_protocol::network::{AddrV2Message, RejectMessage};
pub const BITCOIN_MAGIC_MAINNET: [u8; 4] = [0xf9, 0xbe, 0xb4, 0xd9];
pub const BITCOIN_MAGIC_TESTNET: [u8; 4] = [0x0b, 0x11, 0x09, 0x07];
pub const BITCOIN_MAGIC_REGTEST: [u8; 4] = [0xfa, 0xbf, 0xb5, 0xda];
pub const BITCOIN_MAGIC_TESTNET4: [u8; 4] = [0x1c, 0x16, 0x3f, 0x28];
pub const BITCOIN_MAGIC_SIGNET: [u8; 4] = [0x0f, 0x1b, 0xf2, 0xe1];
pub(crate) static ACTIVE_MAGIC: std::sync::atomic::AtomicU32 =
std::sync::atomic::AtomicU32::new(u32::from_le_bytes(BITCOIN_MAGIC_MAINNET));
pub const MAX_PROTOCOL_MESSAGE_LENGTH: usize = 32 * 1024 * 1024;
pub const MAX_ADDR_TO_SEND: usize = 1000;
pub const MAX_INV_SZ: usize = 50000;
pub const MAX_HEADERS_RESULTS: usize = 2000;
#[cfg(feature = "dandelion")]
pub const NODE_DANDELION: u64 = 1 << 24;
pub const NODE_PACKAGE_RELAY: u64 = 1 << 25;
pub const NODE_FIBRE: u64 = 1 << 26;
#[cfg(feature = "utxo-commitments")]
pub const NODE_UTXO_COMMITMENTS: u64 = 1 << 27;
pub const NODE_BAN_LIST_SHARING: u64 = 1 << 28;
pub const NODE_GOVERNANCE: u64 = 1 << 29;
#[cfg(feature = "erlay")]
pub const NODE_ERLAY: u64 = 1 << 30;
pub mod cmd {
pub const ADDR: &str = "addr";
pub const BANLIST: &str = "banlist";
pub const BLOCK: &str = "block";
pub const BLOCKTXN: &str = "blocktxn";
pub const CFCHECKPT: &str = "cfcheckpt";
pub const CFHEADERS: &str = "cfheaders";
pub const CFILTER: &str = "cfilter";
pub const CMPCTBLOCK: &str = "cmpctblock";
pub const FEEFILTER: &str = "feefilter";
pub const FILTEREDBLOCK: &str = "filteredblock";
pub const GETADDR: &str = "getaddr";
pub const GETBANLIST: &str = "getbanlist";
pub const GETBLOCKTXN: &str = "getblocktxn";
pub const GETCFCHECKPT: &str = "getcfcheckpt";
pub const GETCFHEADERS: &str = "getcfheaders";
pub const GETCFILTERS: &str = "getcfilters";
pub const GETDATA: &str = "getdata";
pub const GETFILTEREDBLOCK: &str = "getfilteredblock";
pub const GETHEADERS: &str = "getheaders";
pub const GETMODULE: &str = "getmodule";
pub const GETMODULEBYHASH: &str = "getmodulebyhash";
pub const GETMODULELIST: &str = "getmodulelist";
pub const GETPAYMENTREQUEST: &str = "getpaymentrequest";
pub const GETUTXOPROOF: &str = "getutxoproof";
pub const GETUTXOSET: &str = "getutxoset";
pub const GETBLOCKS: &str = "getblocks";
pub const HEADERS: &str = "headers";
pub const INV: &str = "inv";
pub const MESH: &str = "mesh";
pub const MODULE: &str = "module";
pub const MODULEBYHASH: &str = "modulebyhash";
pub const MODULEINV: &str = "moduleinv";
pub const MODULELIST: &str = "modulelist";
pub const NOTFOUND: &str = "notfound";
pub const PAYMENT: &str = "payment";
pub const PAYMENTACK: &str = "paymentack";
pub const PAYMENTPROOF: &str = "paymentproof";
pub const PAYMENTREQUEST: &str = "paymentrequest";
pub const PING: &str = "ping";
pub const PKGTXN: &str = "pkgtxn";
pub const PKGTXNREJECT: &str = "pkgtxnreject";
pub const PONG: &str = "pong";
pub const REQRECON: &str = "reqrecon";
pub const REQSKT: &str = "reqskt";
pub const SENDCMPCT: &str = "sendcmpct";
pub const SENDPKGTXN: &str = "sendpkgtxn";
pub const SENDTXRCNCL: &str = "sendtxrcncl";
pub const SETTLEMENTNOTIFICATION: &str = "settlementnotification";
pub const SKETCH: &str = "sketch";
pub const TX: &str = "tx";
pub const UTXOPROOF: &str = "utxoproof";
pub const UTXOSET: &str = "utxoset";
pub const VERACK: &str = "verack";
pub const VERSION: &str = "version";
pub const SENDHEADERS: &str = "sendheaders";
pub const REJECT: &str = "reject";
pub const MEMPOOL: &str = "mempool";
pub const ADDRV2: &str = "addrv2";
}
pub const ALLOWED_COMMANDS: &[&str] = &[
cmd::VERSION,
cmd::VERACK,
cmd::PING,
cmd::PONG,
cmd::GETHEADERS,
cmd::HEADERS,
cmd::GETBLOCKS,
cmd::BLOCK,
cmd::GETDATA,
cmd::INV,
cmd::TX,
cmd::NOTFOUND,
cmd::GETADDR,
cmd::ADDR,
cmd::ADDRV2,
cmd::SENDHEADERS,
cmd::MEMPOOL,
cmd::REJECT,
cmd::FEEFILTER,
cmd::SENDCMPCT,
cmd::CMPCTBLOCK,
cmd::GETBLOCKTXN,
cmd::BLOCKTXN,
cmd::GETUTXOSET,
cmd::UTXOSET,
cmd::GETFILTEREDBLOCK,
cmd::FILTEREDBLOCK,
cmd::GETCFILTERS,
cmd::CFILTER,
cmd::GETCFHEADERS,
cmd::CFHEADERS,
cmd::GETCFCHECKPT,
cmd::CFCHECKPT,
cmd::GETPAYMENTREQUEST,
cmd::PAYMENTREQUEST,
cmd::PAYMENT,
cmd::PAYMENTACK,
cmd::SENDPKGTXN,
cmd::PKGTXN,
cmd::PKGTXNREJECT,
cmd::GETBANLIST,
cmd::BANLIST,
cmd::GETMODULE,
cmd::MODULE,
cmd::GETMODULEBYHASH,
cmd::MODULEBYHASH,
cmd::MODULEINV,
cmd::GETMODULELIST,
cmd::MODULELIST,
cmd::MESH,
#[cfg(feature = "erlay")]
cmd::SENDTXRCNCL,
#[cfg(feature = "erlay")]
cmd::REQRECON,
#[cfg(feature = "erlay")]
cmd::REQSKT,
#[cfg(feature = "erlay")]
cmd::SKETCH,
];
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ProtocolMessage {
Version(VersionMessage),
Verack,
Ping(PingMessage),
Pong(PongMessage),
GetHeaders(GetHeadersMessage),
Headers(HeadersMessage),
GetBlocks(GetBlocksMessage),
Block(BlockMessage),
GetData(GetDataMessage),
Inv(InvMessage),
NotFound(NotFoundMessage),
Tx(TxMessage),
FeeFilter(FeeFilterMessage),
SendCmpct(SendCmpctMessage),
CmpctBlock(CompactBlockMessage),
GetBlockTxn(GetBlockTxnMessage),
BlockTxn(BlockTxnMessage),
GetUTXOSet(GetUTXOSetMessage),
UTXOSet(UTXOSetMessage),
GetUTXOProof(GetUTXOProofMessage),
UTXOProof(UTXOProofMessage),
GetFilteredBlock(GetFilteredBlockMessage),
FilteredBlock(FilteredBlockMessage),
GetCfilters(GetCfiltersMessage),
Cfilter(CfilterMessage),
GetCfheaders(GetCfheadersMessage),
Cfheaders(CfheadersMessage),
GetCfcheckpt(GetCfcheckptMessage),
Cfcheckpt(CfcheckptMessage),
GetPaymentRequest(GetPaymentRequestMessage),
PaymentRequest(PaymentRequestMessage),
Payment(PaymentMessage),
PaymentACK(PaymentACKMessage),
#[cfg(feature = "ctv")]
PaymentProof(PaymentProofMessage),
SettlementNotification(SettlementNotificationMessage),
SendPkgTxn(SendPkgTxnMessage),
PkgTxn(PkgTxnMessage),
PkgTxnReject(PkgTxnRejectMessage),
GetBanList(GetBanListMessage),
BanList(BanListMessage),
MeshPacket(Vec<u8>), GetAddr,
Addr(AddrMessage),
AddrV2(AddrV2Message),
SendHeaders,
Reject(RejectMessage),
MemPool,
GetModule(GetModuleMessage),
Module(ModuleMessage),
GetModuleByHash(GetModuleByHashMessage),
ModuleByHash(ModuleByHashMessage),
ModuleInv(ModuleInvMessage),
GetModuleList(GetModuleListMessage),
ModuleList(ModuleListMessage),
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct VersionMessage {
pub version: i32,
pub services: u64,
pub timestamp: i64,
pub addr_recv: NetworkAddress,
pub addr_from: NetworkAddress,
pub nonce: u64,
pub user_agent: String,
pub start_height: i32,
pub relay: bool,
}
impl VersionMessage {
#[cfg(feature = "utxo-commitments")]
pub fn supports_utxo_commitments(&self) -> bool {
(self.services & NODE_UTXO_COMMITMENTS) != 0
}
pub fn supports_ban_list_sharing(&self) -> bool {
(self.services & NODE_BAN_LIST_SHARING) != 0
}
pub fn supports_compact_filters(&self) -> bool {
use blvm_protocol::bip157::NODE_COMPACT_FILTERS;
(self.services & NODE_COMPACT_FILTERS) != 0
}
pub fn supports_package_relay(&self) -> bool {
(self.services & NODE_PACKAGE_RELAY) != 0
}
pub fn supports_fibre(&self) -> bool {
(self.services & NODE_FIBRE) != 0
}
#[cfg(feature = "dandelion")]
pub fn supports_dandelion(&self) -> bool {
(self.services & NODE_DANDELION) != 0
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct NetworkAddress {
pub services: u64,
pub ip: [u8; 16],
pub port: u16,
pub time: u32,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct PingMessage {
pub nonce: u64,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct PongMessage {
pub nonce: u64,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct GetHeadersMessage {
pub version: i32,
pub block_locator_hashes: Vec<Hash>,
pub hash_stop: Hash,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct HeadersMessage {
pub headers: Vec<BlockHeader>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GetBlocksMessage {
pub version: i32,
pub block_locator_hashes: Vec<Hash>,
pub hash_stop: Hash,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct BlockMessage {
pub block: Block,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub witnesses: Vec<Vec<Witness>>,
}
pub use blvm_protocol::network::{GetDataMessage, InvMessage, InventoryVector, NotFoundMessage};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct TxMessage {
pub transaction: Transaction,
#[serde(skip_serializing_if = "Vec::is_empty", default)]
pub witnesses: Vec<Witness>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct FeeFilterMessage {
pub feerate: u64,
}
use crate::network::compact_blocks::CompactBlock;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SendCmpctMessage {
pub version: u64,
pub prefer_cmpct: u8,
}
impl SendCmpctMessage {
pub fn for_transport(transport: TransportType, prefer_cmpct: bool) -> Self {
use crate::network::compact_blocks::recommended_compact_block_version;
Self {
version: recommended_compact_block_version(transport),
prefer_cmpct: if prefer_cmpct { 1 } else { 0 },
}
}
pub fn supports_filters(&self, peer_services: u64) -> bool {
use blvm_protocol::bip157::NODE_COMPACT_FILTERS;
(peer_services & NODE_COMPACT_FILTERS) != 0
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct CompactBlockMessage {
pub compact_block: CompactBlock,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct GetBlockTxnMessage {
pub block_hash: Hash,
pub indices: Vec<u16>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct BlockTxnMessage {
pub block_hash: Hash,
pub transactions: Vec<Transaction>,
pub witnesses: Option<Vec<Vec<blvm_protocol::segwit::Witness>>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GetUTXOSetMessage {
pub height: u64,
pub block_hash: Hash,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UTXOSetMessage {
pub request_id: u64,
pub commitment: UTXOCommitment,
pub utxo_count: u64,
pub is_complete: bool,
pub chunk_id: Option<u32>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UTXOCommitment {
pub merkle_root: Hash,
pub total_supply: u64,
pub utxo_count: u64,
pub block_height: u64,
pub block_hash: Hash,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GetUTXOProofMessage {
pub request_id: u64,
pub tx_hash: Hash,
pub output_index: u32,
pub block_height: u64,
pub block_hash: Hash,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UTXOProofMessage {
pub request_id: u64,
pub tx_hash: Hash,
pub output_index: u32,
pub value: i64,
pub script_pubkey: Vec<u8>,
pub height: u64,
pub is_coinbase: bool,
pub proof: Vec<u8>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GetFilteredBlockMessage {
pub request_id: u64,
pub block_hash: Hash,
pub filter_preferences: FilterPreferences,
pub include_bip158_filter: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FilterPreferences {
pub filter_ordinals: bool,
pub filter_dust: bool,
pub filter_brc20: bool,
pub min_output_value: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FilteredBlockMessage {
pub request_id: u64,
pub header: BlockHeader,
pub commitment: UTXOCommitment,
pub transactions: Vec<Transaction>,
pub transaction_indices: Vec<u32>,
pub spam_summary: SpamSummary,
pub bip158_filter: Option<Bip158FilterData>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Bip158FilterData {
pub filter_type: u8,
pub filter_data: Vec<u8>,
pub num_elements: u32,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct GetCfiltersMessage {
pub filter_type: u8,
pub start_height: u32,
pub stop_hash: Hash,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct CfilterMessage {
pub filter_type: u8,
pub block_hash: Hash,
pub filter_data: Vec<u8>,
pub num_elements: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GetCfheadersMessage {
pub filter_type: u8,
pub start_height: u32,
pub stop_hash: Hash,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CfheadersMessage {
pub filter_type: u8,
pub stop_hash: Hash,
pub prev_header: FilterHeaderData,
pub filter_headers: Vec<Hash>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FilterHeaderData {
pub filter_hash: Hash,
pub prev_header_hash: Hash,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GetCfcheckptMessage {
pub filter_type: u8,
pub stop_hash: Hash,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CfcheckptMessage {
pub filter_type: u8,
pub stop_hash: Hash,
pub filter_header_hashes: Vec<Hash>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GetPaymentRequestMessage {
#[serde(with = "serde_bytes")]
pub merchant_pubkey: Vec<u8>,
#[serde(with = "serde_bytes")]
pub payment_id: Vec<u8>,
pub network: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PaymentRequestMessage {
pub payment_request: blvm_protocol::payment::PaymentRequest,
#[serde(with = "serde_bytes")]
pub merchant_signature: Vec<u8>,
#[serde(with = "serde_bytes")]
pub merchant_pubkey: Vec<u8>,
#[serde(with = "serde_bytes")]
pub payment_id: Vec<u8>,
#[cfg(feature = "ctv")]
#[serde(default)]
pub covenant_proof: Option<crate::payment::covenant::CovenantProof>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PaymentMessage {
pub payment: blvm_protocol::payment::Payment,
#[serde(with = "serde_bytes")]
pub payment_id: Vec<u8>,
#[serde(with = "serde_bytes")]
pub customer_signature: Option<Vec<u8>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PaymentACKMessage {
pub payment_ack: blvm_protocol::payment::PaymentACK,
#[serde(with = "serde_bytes")]
pub payment_id: Vec<u8>,
#[serde(with = "serde_bytes")]
pub merchant_signature: Vec<u8>,
}
#[cfg(feature = "ctv")]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PaymentProofMessage {
pub request_id: u64,
pub payment_request_id: String,
pub covenant_proof: crate::payment::covenant::CovenantProof,
pub transaction_template: Option<crate::payment::covenant::TransactionTemplate>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SettlementNotificationMessage {
pub payment_request_id: String,
pub transaction_hash: Option<Hash>,
pub confirmation_count: u32,
pub block_hash: Option<Hash>,
pub status: String, }
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SendPkgTxnMessage {
#[serde(with = "serde_bytes")]
pub package_id: Vec<u8>,
pub tx_hashes: Vec<Hash>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct PkgTxnMessage {
#[serde(with = "serde_bytes")]
pub package_id: Vec<u8>,
pub transactions: Vec<Vec<u8>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PkgTxnRejectMessage {
#[serde(with = "serde_bytes")]
pub package_id: Vec<u8>,
pub reason: u8,
pub reason_text: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GetModuleMessage {
pub request_id: u64,
pub name: String,
pub version: Option<String>,
pub payment_id: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ModuleMessage {
pub request_id: u64,
pub name: String,
pub version: String,
pub hash: Hash,
pub manifest_hash: Hash,
pub binary_hash: Hash,
pub manifest: Vec<u8>,
pub binary: Option<Vec<u8>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GetModuleByHashMessage {
pub request_id: u64,
pub hash: Hash,
pub include_binary: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ModuleByHashMessage {
pub request_id: u64,
pub hash: Hash,
pub manifest: Vec<u8>,
pub binary: Option<Vec<u8>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ModuleInvMessage {
pub modules: Vec<ModuleInventoryItem>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ModuleInventoryItem {
pub name: String,
pub version: String,
pub hash: Hash,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GetModuleListMessage {
pub name_prefix: Option<String>,
pub max_count: Option<u32>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ModuleListMessage {
pub modules: Vec<ModuleInventoryItem>,
}
#[cfg(feature = "erlay")]
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SendTxRcnclMessage {
pub version: u16,
#[serde(with = "serde_bytes")]
pub salt: [u8; 16],
pub min_field_size: u8,
pub max_field_size: u8,
}
#[cfg(feature = "erlay")]
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ReqReconMessage {
#[serde(with = "serde_bytes")]
pub salt: [u8; 16],
pub local_set_size: u32,
pub field_size: u8,
}
#[cfg(feature = "erlay")]
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ReqSktMessage {
#[serde(with = "serde_bytes")]
pub salt: [u8; 16],
pub remote_set_size: u32,
pub field_size: u8,
}
#[cfg(feature = "erlay")]
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SketchMessage {
#[serde(with = "serde_bytes")]
pub salt: [u8; 16],
#[serde(with = "serde_bytes")]
pub sketch: Vec<u8>,
pub field_size: u8,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SpamSummary {
pub filtered_count: u32,
pub filtered_size: u64,
pub by_type: SpamBreakdown,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SpamBreakdown {
pub ordinals: u32,
pub inscriptions: u32,
pub dust: u32,
pub brc20: u32,
}
pub struct ProtocolParser;
impl ProtocolParser {
pub fn set_network_magic(magic_bytes: [u8; 4]) {
ACTIVE_MAGIC.store(
u32::from_le_bytes(magic_bytes),
std::sync::atomic::Ordering::Relaxed,
);
}
pub fn dns_seed_network() -> (&'static str, u16) {
let magic = ACTIVE_MAGIC
.load(std::sync::atomic::Ordering::Relaxed)
.to_le_bytes();
if magic == BITCOIN_MAGIC_TESTNET {
("testnet", 18333)
} else if magic == BITCOIN_MAGIC_TESTNET4 {
("testnet4", 48333)
} else if magic == BITCOIN_MAGIC_SIGNET {
("signet", 38333)
} else if magic == BITCOIN_MAGIC_REGTEST {
("regtest", 18444)
} else {
("mainnet", 8333)
}
}
#[cfg_attr(feature = "protocol-verification", spec_locked("10.1.1"))]
pub fn parse_message(data: &[u8]) -> Result<ProtocolMessage> {
use tracing::{debug, warn};
if data.len() < 24 {
return Err(anyhow::anyhow!("Message too short: {} bytes", data.len()));
}
if data.len() > MAX_PROTOCOL_MESSAGE_LENGTH {
return Err(anyhow::anyhow!("Message too large"));
}
let magic = u32::from_le_bytes([data[0], data[1], data[2], data[3]]);
let expected_magic = ACTIVE_MAGIC.load(std::sync::atomic::Ordering::Relaxed);
debug!(
"Parsing message: magic=0x{:08x}, total_len={}",
magic,
data.len()
);
if magic != expected_magic {
let header_hex: String = data.iter().take(24).map(|b| format!("{b:02x}")).collect();
warn!(
"Invalid magic number 0x{:08x}, expected 0x{:08x}. Header hex: {}",
magic, expected_magic, header_hex
);
return Err(anyhow::anyhow!("Invalid magic number 0x{:08x}", magic));
}
let command = String::from_utf8_lossy(&data[4..16])
.trim_end_matches('\0')
.to_string();
debug!("Message command: '{}', data_len={}", command, data.len());
if !ALLOWED_COMMANDS.contains(&command.as_str()) {
return Err(anyhow::anyhow!("Unknown command: {}", command));
}
let payload_length = u32::from_le_bytes([data[16], data[17], data[18], data[19]]);
let checksum = &data[20..24];
if payload_length as usize > MAX_PROTOCOL_MESSAGE_LENGTH - 24 {
return Err(anyhow::anyhow!("Payload too large"));
}
if data.len() < 24 + payload_length as usize {
return Err(anyhow::anyhow!("Incomplete message"));
}
let payload = &data[24..24 + payload_length as usize];
let calculated_checksum = Self::calculate_checksum(payload);
if calculated_checksum != checksum {
return Err(anyhow::anyhow!("Invalid checksum"));
}
match command.as_str() {
cmd::VERSION => {
use blvm_protocol::wire::deserialize_version;
let version_msg = deserialize_version(payload)?;
Ok(ProtocolMessage::Version(VersionMessage {
version: version_msg.version as i32, services: version_msg.services,
timestamp: version_msg.timestamp,
addr_recv: NetworkAddress {
time: 0,
services: version_msg.addr_recv.services,
ip: version_msg.addr_recv.ip,
port: version_msg.addr_recv.port,
},
addr_from: NetworkAddress {
time: 0,
services: version_msg.addr_from.services,
ip: version_msg.addr_from.ip,
port: version_msg.addr_from.port,
},
nonce: version_msg.nonce,
user_agent: version_msg.user_agent,
start_height: version_msg.start_height,
relay: version_msg.relay,
}))
}
cmd::VERACK => Ok(ProtocolMessage::Verack),
cmd::PING => {
let wire_msg = blvm_protocol::wire::deserialize_ping(payload)
.map_err(|e| anyhow::anyhow!("Failed to deserialize ping: {}", e))?;
Ok(ProtocolMessage::Ping(PingMessage {
nonce: wire_msg.nonce,
}))
}
cmd::PONG => {
let wire_msg = blvm_protocol::wire::deserialize_pong(payload)
.map_err(|e| anyhow::anyhow!("Failed to deserialize pong: {}", e))?;
Ok(ProtocolMessage::Pong(PongMessage {
nonce: wire_msg.nonce,
}))
}
cmd::GETHEADERS => {
let wire_msg = blvm_protocol::wire::deserialize_getheaders(payload)
.map_err(|e| anyhow::anyhow!("Failed to deserialize getheaders: {}", e))?;
Ok(ProtocolMessage::GetHeaders(GetHeadersMessage {
version: wire_msg.version as i32,
block_locator_hashes: wire_msg.block_locator_hashes,
hash_stop: wire_msg.hash_stop,
}))
}
cmd::HEADERS => {
let wire_msg = deserialize_headers(payload)
.map_err(|e| anyhow::anyhow!("Failed to deserialize headers: {}", e))?;
Ok(ProtocolMessage::Headers(HeadersMessage {
headers: wire_msg.headers,
}))
}
cmd::GETBLOCKS => Ok(ProtocolMessage::GetBlocks(decode_getblocks(payload)?)),
cmd::BLOCK => {
let (block, witnesses) =
blvm_protocol::serialization::deserialize_block_with_witnesses(payload)
.map_err(|e| anyhow::anyhow!("Failed to deserialize block: {}", e))?;
Ok(ProtocolMessage::Block(BlockMessage { block, witnesses }))
}
cmd::GETDATA => {
let msg = deserialize_getdata(payload)
.map_err(|e| anyhow::anyhow!("Failed to deserialize getdata: {}", e))?;
Ok(ProtocolMessage::GetData(msg))
}
cmd::INV => {
let msg = deserialize_inv(payload)
.map_err(|e| anyhow::anyhow!("Failed to deserialize inv: {}", e))?;
Ok(ProtocolMessage::Inv(msg))
}
cmd::NOTFOUND => {
let msg = deserialize_notfound(payload)
.map_err(|e| anyhow::anyhow!("Failed to deserialize notfound: {}", e))?;
Ok(ProtocolMessage::NotFound(msg))
}
cmd::TX => {
let (transaction, witnesses) = deserialize_tx_with_witness(payload)
.map_err(|e| anyhow::anyhow!("Failed to deserialize tx: {}", e))?;
Ok(ProtocolMessage::Tx(TxMessage {
transaction,
witnesses,
}))
}
cmd::FEEFILTER => {
if payload.len() < 8 {
return Err(anyhow::anyhow!("FeeFilter message too short"));
}
let feerate = u64::from_le_bytes(payload[0..8].try_into().unwrap());
Ok(ProtocolMessage::FeeFilter(FeeFilterMessage { feerate }))
}
cmd::SENDCMPCT => {
let sc = blvm_protocol::wire::deserialize_sendcmpct(payload)
.map_err(|e| anyhow::anyhow!("Failed to deserialize sendcmpct: {}", e))?;
Ok(ProtocolMessage::SendCmpct(SendCmpctMessage {
prefer_cmpct: sc.prefer_cmpct,
version: sc.version,
}))
}
cmd::CMPCTBLOCK => {
let wire = deserialize_cmpctblock(payload)
.map_err(|e| anyhow::anyhow!("Failed to deserialize cmpctblock: {}", e))?;
Ok(ProtocolMessage::CmpctBlock(CompactBlockMessage {
compact_block: blvm_protocol::bip152::CompactBlock::from(&wire),
}))
}
cmd::GETBLOCKTXN => {
let wire = deserialize_getblocktxn(payload)
.map_err(|e| anyhow::anyhow!("Failed to deserialize getblocktxn: {}", e))?;
Ok(ProtocolMessage::GetBlockTxn(GetBlockTxnMessage {
block_hash: wire.block_hash,
indices: wire.indices,
}))
}
cmd::BLOCKTXN => {
let wire = deserialize_blocktxn(payload)
.map_err(|e| anyhow::anyhow!("Failed to deserialize blocktxn: {}", e))?;
Ok(ProtocolMessage::BlockTxn(BlockTxnMessage {
block_hash: wire.block_hash,
transactions: wire.transactions,
witnesses: wire.witnesses,
}))
}
cmd::GETUTXOSET => Ok(ProtocolMessage::GetUTXOSet(bincode::deserialize(payload)?)),
cmd::UTXOSET => Ok(ProtocolMessage::UTXOSet(bincode::deserialize(payload)?)),
cmd::GETUTXOPROOF => Ok(ProtocolMessage::GetUTXOProof(bincode::deserialize(
payload,
)?)),
cmd::UTXOPROOF => Ok(ProtocolMessage::UTXOProof(bincode::deserialize(payload)?)),
cmd::GETFILTEREDBLOCK => Ok(ProtocolMessage::GetFilteredBlock(bincode::deserialize(
payload,
)?)),
cmd::FILTEREDBLOCK => Ok(ProtocolMessage::FilteredBlock(bincode::deserialize(
payload,
)?)),
cmd::GETCFILTERS => Ok(ProtocolMessage::GetCfilters(decode_filter_request(
payload,
)?)),
cmd::CFILTER => Ok(ProtocolMessage::Cfilter(decode_cfilter(payload)?)),
cmd::GETCFHEADERS => {
let req = decode_filter_request(payload)?;
Ok(ProtocolMessage::GetCfheaders(GetCfheadersMessage {
filter_type: req.filter_type,
start_height: req.start_height,
stop_hash: req.stop_hash,
}))
}
cmd::CFHEADERS => Ok(ProtocolMessage::Cfheaders(decode_cfheaders(payload)?)),
cmd::GETCFCHECKPT => Ok(ProtocolMessage::GetCfcheckpt(decode_getcfcheckpt(payload)?)),
cmd::CFCHECKPT => Ok(ProtocolMessage::Cfcheckpt(decode_cfcheckpt(payload)?)),
cmd::GETPAYMENTREQUEST => Ok(ProtocolMessage::GetPaymentRequest(bincode::deserialize(
payload,
)?)),
cmd::PAYMENTREQUEST => Ok(ProtocolMessage::PaymentRequest(bincode::deserialize(
payload,
)?)),
cmd::PAYMENT => Ok(ProtocolMessage::Payment(bincode::deserialize(payload)?)),
cmd::PAYMENTACK => Ok(ProtocolMessage::PaymentACK(bincode::deserialize(payload)?)),
cmd::SENDPKGTXN => Ok(ProtocolMessage::SendPkgTxn(bincode::deserialize(payload)?)),
cmd::PKGTXN => Ok(ProtocolMessage::PkgTxn(bincode::deserialize(payload)?)),
cmd::PKGTXNREJECT => Ok(ProtocolMessage::PkgTxnReject(bincode::deserialize(
payload,
)?)),
cmd::GETBANLIST => Ok(ProtocolMessage::GetBanList(bincode::deserialize(payload)?)),
cmd::BANLIST => Ok(ProtocolMessage::BanList(bincode::deserialize(payload)?)),
cmd::GETADDR => Ok(ProtocolMessage::GetAddr),
cmd::ADDR => {
let wire_msg = deserialize_addr(payload)
.map_err(|e| anyhow::anyhow!("Failed to deserialize addr: {}", e))?;
Ok(ProtocolMessage::Addr(AddrMessage {
addresses: wire_msg
.addresses
.into_iter()
.map(|a| NetworkAddress {
services: a.services,
ip: a.ip,
port: a.port,
time: a.time,
})
.collect(),
}))
}
cmd::ADDRV2 => {
let msg = blvm_protocol::wire::deserialize_addrv2(payload)
.map_err(|e| anyhow::anyhow!("Failed to deserialize addrv2: {e}"))?;
Ok(ProtocolMessage::AddrV2(msg))
}
cmd::SENDHEADERS => Ok(ProtocolMessage::SendHeaders),
cmd::MEMPOOL => Ok(ProtocolMessage::MemPool),
cmd::REJECT => {
let msg = blvm_protocol::wire::deserialize_reject(payload)
.map_err(|e| anyhow::anyhow!("Failed to deserialize reject: {e}"))?;
Ok(ProtocolMessage::Reject(msg))
}
cmd::GETMODULE => Ok(ProtocolMessage::GetModule(bincode::deserialize(payload)?)),
cmd::MODULE => Ok(ProtocolMessage::Module(bincode::deserialize(payload)?)),
cmd::GETMODULEBYHASH => Ok(ProtocolMessage::GetModuleByHash(bincode::deserialize(
payload,
)?)),
cmd::MODULEBYHASH => Ok(ProtocolMessage::ModuleByHash(bincode::deserialize(
payload,
)?)),
cmd::MODULEINV => Ok(ProtocolMessage::ModuleInv(bincode::deserialize(payload)?)),
cmd::GETMODULELIST => Ok(ProtocolMessage::GetModuleList(bincode::deserialize(
payload,
)?)),
cmd::MODULELIST => Ok(ProtocolMessage::ModuleList(bincode::deserialize(payload)?)),
cmd::MESH => Ok(ProtocolMessage::MeshPacket(payload.to_vec())),
#[cfg(feature = "erlay")]
cmd::SENDTXRCNCL => Ok(ProtocolMessage::SendTxRcncl(bincode::deserialize(payload)?)),
#[cfg(feature = "erlay")]
cmd::REQRECON => Ok(ProtocolMessage::ReqRecon(bincode::deserialize(payload)?)),
#[cfg(feature = "erlay")]
cmd::REQSKT => Ok(ProtocolMessage::ReqSkt(bincode::deserialize(payload)?)),
#[cfg(feature = "erlay")]
cmd::SKETCH => Ok(ProtocolMessage::Sketch(bincode::deserialize(payload)?)),
_ => Err(anyhow::anyhow!("Unknown command: {}", command)),
}
}
pub fn serialize_command_payload(command: &str, payload: &[u8]) -> Result<Vec<u8>> {
if command.len() > 12 {
return Err(anyhow::anyhow!("command too long: {command}"));
}
let mut message = Vec::with_capacity(24 + payload.len());
let active_magic = ACTIVE_MAGIC.load(std::sync::atomic::Ordering::Relaxed);
message.extend_from_slice(&active_magic.to_le_bytes());
let mut command_bytes = [0u8; 12];
command_bytes[..command.len()].copy_from_slice(command.as_bytes());
message.extend_from_slice(&command_bytes);
message.extend_from_slice(&(payload.len() as u32).to_le_bytes());
let checksum = Self::calculate_checksum(payload);
message.extend_from_slice(&checksum);
message.extend_from_slice(payload);
Ok(message)
}
pub fn serialize_message(message: &ProtocolMessage) -> Result<Vec<u8>> {
let (command, payload) = match message {
ProtocolMessage::Version(msg) => {
use blvm_protocol::network::{NetworkAddress, VersionMessage};
use blvm_protocol::wire::serialize_version;
let version_msg = VersionMessage {
version: msg.version as u32,
services: msg.services,
timestamp: msg.timestamp,
addr_recv: NetworkAddress {
time: 0,
services: msg.addr_recv.services,
ip: msg.addr_recv.ip,
port: msg.addr_recv.port,
},
addr_from: NetworkAddress {
time: 0,
services: msg.addr_from.services,
ip: msg.addr_from.ip,
port: msg.addr_from.port,
},
nonce: msg.nonce,
user_agent: msg.user_agent.clone(),
start_height: msg.start_height,
relay: msg.relay,
};
let payload = serialize_version(&version_msg)?;
(cmd::VERSION, payload)
}
ProtocolMessage::Verack => (cmd::VERACK, vec![]),
ProtocolMessage::SendHeaders => (cmd::SENDHEADERS, vec![]),
ProtocolMessage::MemPool => (cmd::MEMPOOL, vec![]),
ProtocolMessage::Reject(msg) => (
cmd::REJECT,
blvm_protocol::wire::serialize_reject(msg).map_err(|e| anyhow::anyhow!("{e}"))?,
),
ProtocolMessage::Ping(msg) => {
let wire = blvm_protocol::network::PingMessage { nonce: msg.nonce };
(
cmd::PING,
serialize_ping(&wire).map_err(|e| anyhow::anyhow!("{e}"))?,
)
}
ProtocolMessage::Pong(msg) => {
let wire = blvm_protocol::network::PongMessage { nonce: msg.nonce };
(
cmd::PONG,
serialize_pong(&wire).map_err(|e| anyhow::anyhow!("{e}"))?,
)
}
ProtocolMessage::GetHeaders(msg) => {
let wire_msg = blvm_protocol::network::GetHeadersMessage {
version: msg.version as u32,
block_locator_hashes: msg.block_locator_hashes.clone(),
hash_stop: msg.hash_stop,
};
(
cmd::GETHEADERS,
serialize_getheaders(&wire_msg).map_err(|e| anyhow::anyhow!("{}", e))?,
)
}
ProtocolMessage::Headers(msg) => {
let wire_msg = blvm_protocol::network::HeadersMessage {
headers: msg.headers.clone(),
};
(
cmd::HEADERS,
blvm_protocol::wire::serialize_headers(&wire_msg)
.map_err(|e| anyhow::anyhow!("{}", e))?,
)
}
ProtocolMessage::GetBlocks(msg) => (cmd::GETBLOCKS, encode_getblocks(msg)?),
ProtocolMessage::Block(msg) => {
let payload = blvm_protocol::serialization::serialize_block_with_witnesses(
&msg.block,
&msg.witnesses,
true,
);
(cmd::BLOCK, payload)
}
ProtocolMessage::GetData(msg) => (
cmd::GETDATA,
serialize_getdata(msg).map_err(|e| anyhow::anyhow!("{}", e))?,
),
ProtocolMessage::Inv(msg) => (
cmd::INV,
serialize_inv(msg).map_err(|e| anyhow::anyhow!("{}", e))?,
),
ProtocolMessage::NotFound(msg) => (
cmd::NOTFOUND,
serialize_notfound(msg).map_err(|e| anyhow::anyhow!("{}", e))?,
),
ProtocolMessage::Tx(msg) => (
cmd::TX,
serialize_tx_with_witness(&msg.transaction, &msg.witnesses)
.map_err(|e| anyhow::anyhow!("{e}"))?,
),
ProtocolMessage::FeeFilter(msg) => (cmd::FEEFILTER, msg.feerate.to_le_bytes().to_vec()),
ProtocolMessage::SendCmpct(msg) => {
let wire = blvm_protocol::network::SendCmpctMessage {
prefer_cmpct: msg.prefer_cmpct,
version: msg.version,
};
(
cmd::SENDCMPCT,
blvm_protocol::wire::serialize_sendcmpct(&wire)
.map_err(|e| anyhow::anyhow!("{e}"))?,
)
}
ProtocolMessage::CmpctBlock(msg) => {
let wire =
blvm_protocol::network::CmpctBlockMessage::try_from(msg.compact_block.clone())
.map_err(|e| anyhow::anyhow!("CmpctBlock conversion error: {}", e))?;
(
cmd::CMPCTBLOCK,
serialize_cmpctblock(&wire).map_err(|e| anyhow::anyhow!("{e}"))?,
)
}
ProtocolMessage::GetBlockTxn(msg) => {
let wire = blvm_protocol::network::GetBlockTxnMessage {
block_hash: msg.block_hash,
indices: msg.indices.clone(),
};
(
cmd::GETBLOCKTXN,
serialize_getblocktxn(&wire).map_err(|e| anyhow::anyhow!("{e}"))?,
)
}
ProtocolMessage::BlockTxn(msg) => {
let wire = blvm_protocol::network::BlockTxnMessage {
block_hash: msg.block_hash,
transactions: msg.transactions.clone(),
witnesses: msg.witnesses.clone(),
};
(
cmd::BLOCKTXN,
serialize_blocktxn(&wire).map_err(|e| anyhow::anyhow!("{e}"))?,
)
}
ProtocolMessage::GetUTXOSet(msg) => (cmd::GETUTXOSET, bincode::serialize(msg)?),
ProtocolMessage::UTXOSet(msg) => (cmd::UTXOSET, bincode::serialize(msg)?),
ProtocolMessage::GetUTXOProof(msg) => (cmd::GETUTXOPROOF, bincode::serialize(msg)?),
ProtocolMessage::UTXOProof(msg) => (cmd::UTXOPROOF, bincode::serialize(msg)?),
ProtocolMessage::GetFilteredBlock(msg) => {
(cmd::GETFILTEREDBLOCK, bincode::serialize(msg)?)
}
ProtocolMessage::FilteredBlock(msg) => (cmd::FILTEREDBLOCK, bincode::serialize(msg)?),
ProtocolMessage::GetCfilters(msg) => (cmd::GETCFILTERS, encode_filter_request(msg)),
ProtocolMessage::Cfilter(msg) => (cmd::CFILTER, encode_cfilter(msg)?),
ProtocolMessage::GetCfheaders(msg) => (
cmd::GETCFHEADERS,
encode_filter_request(&GetCfiltersMessage {
filter_type: msg.filter_type,
start_height: msg.start_height,
stop_hash: msg.stop_hash,
}),
),
ProtocolMessage::Cfheaders(msg) => (cmd::CFHEADERS, encode_cfheaders(msg)?),
ProtocolMessage::GetCfcheckpt(msg) => (cmd::GETCFCHECKPT, encode_getcfcheckpt(msg)),
ProtocolMessage::Cfcheckpt(msg) => (cmd::CFCHECKPT, encode_cfcheckpt(msg)?),
ProtocolMessage::GetPaymentRequest(msg) => {
(cmd::GETPAYMENTREQUEST, bincode::serialize(msg)?)
}
ProtocolMessage::PaymentRequest(msg) => (cmd::PAYMENTREQUEST, bincode::serialize(msg)?),
ProtocolMessage::Payment(msg) => (cmd::PAYMENT, bincode::serialize(msg)?),
ProtocolMessage::PaymentACK(msg) => (cmd::PAYMENTACK, bincode::serialize(msg)?),
#[cfg(feature = "ctv")]
ProtocolMessage::PaymentProof(msg) => (cmd::PAYMENTPROOF, bincode::serialize(msg)?),
ProtocolMessage::SettlementNotification(msg) => {
(cmd::SETTLEMENTNOTIFICATION, bincode::serialize(msg)?)
}
ProtocolMessage::SendPkgTxn(msg) => (cmd::SENDPKGTXN, bincode::serialize(msg)?),
ProtocolMessage::PkgTxn(msg) => (cmd::PKGTXN, bincode::serialize(msg)?),
ProtocolMessage::PkgTxnReject(msg) => (cmd::PKGTXNREJECT, bincode::serialize(msg)?),
ProtocolMessage::GetBanList(msg) => (cmd::GETBANLIST, bincode::serialize(msg)?),
ProtocolMessage::BanList(msg) => (cmd::BANLIST, bincode::serialize(msg)?),
ProtocolMessage::GetAddr => (cmd::GETADDR, vec![]),
ProtocolMessage::Addr(msg) => {
let wire = blvm_protocol::network::AddrMessage {
addresses: msg
.addresses
.iter()
.map(|a| blvm_protocol::network::NetworkAddress {
services: a.services,
ip: a.ip,
port: a.port,
time: a.time,
})
.collect(),
};
(
cmd::ADDR,
serialize_addr(&wire).map_err(|e| anyhow::anyhow!("{e}"))?,
)
}
ProtocolMessage::AddrV2(msg) => (
cmd::ADDRV2,
blvm_protocol::wire::serialize_addrv2(msg).map_err(|e| anyhow::anyhow!("{e}"))?,
),
ProtocolMessage::GetModule(msg) => (cmd::GETMODULE, bincode::serialize(msg)?),
ProtocolMessage::Module(msg) => (cmd::MODULE, bincode::serialize(msg)?),
ProtocolMessage::GetModuleByHash(msg) => {
(cmd::GETMODULEBYHASH, bincode::serialize(msg)?)
}
ProtocolMessage::ModuleByHash(msg) => (cmd::MODULEBYHASH, bincode::serialize(msg)?),
ProtocolMessage::ModuleInv(msg) => (cmd::MODULEINV, bincode::serialize(msg)?),
ProtocolMessage::GetModuleList(msg) => (cmd::GETMODULELIST, bincode::serialize(msg)?),
ProtocolMessage::ModuleList(msg) => (cmd::MODULELIST, bincode::serialize(msg)?),
ProtocolMessage::MeshPacket(_) => {
return Err(anyhow::anyhow!("MeshPacket handled separately"));
}
#[cfg(feature = "erlay")]
ProtocolMessage::SendTxRcncl(msg) => (cmd::SENDTXRCNCL, bincode::serialize(msg)?),
#[cfg(feature = "erlay")]
ProtocolMessage::ReqRecon(msg) => (cmd::REQRECON, bincode::serialize(msg)?),
#[cfg(feature = "erlay")]
ProtocolMessage::ReqSkt(msg) => (cmd::REQSKT, bincode::serialize(msg)?),
#[cfg(feature = "erlay")]
ProtocolMessage::Sketch(msg) => (cmd::SKETCH, bincode::serialize(msg)?),
};
let mut message = Vec::new();
let active_magic = ACTIVE_MAGIC.load(std::sync::atomic::Ordering::Relaxed);
message.extend_from_slice(&active_magic.to_le_bytes());
let mut command_bytes = [0u8; 12];
command_bytes[..command.len()].copy_from_slice(command.as_bytes());
message.extend_from_slice(&command_bytes);
message.extend_from_slice(&(payload.len() as u32).to_le_bytes());
let checksum = Self::calculate_checksum(&payload);
message.extend_from_slice(&checksum);
message.extend_from_slice(&payload);
Ok(message)
}
#[cfg_attr(
feature = "protocol-verification",
spec_locked("10.1.1", "CalculateChecksum")
)]
#[cfg_attr(
feature = "protocol-verification",
blvm_spec_lock::ensures(result.len() == 4)
)]
pub fn calculate_checksum(payload: &[u8]) -> [u8; 4] {
use sha2::{Digest, Sha256};
let hash1 = Sha256::digest(payload);
let hash2 = Sha256::digest(hash1);
let mut checksum = [0u8; 4];
checksum.copy_from_slice(&hash2[..4]);
checksum
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GetBanListMessage {
pub request_full: bool,
pub min_ban_duration: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BanListMessage {
pub is_full: bool,
pub ban_list_hash: Hash,
pub ban_entries: Vec<BanEntry>,
pub timestamp: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BanEntry {
pub addr: NetworkAddress,
pub unban_timestamp: u64,
pub reason: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AddrMessage {
pub addresses: Vec<NetworkAddress>,
}
fn read_hash(cursor: &mut std::io::Cursor<&[u8]>) -> anyhow::Result<Hash> {
let mut hash = [0u8; 32];
std::io::Read::read_exact(cursor, &mut hash)?;
Ok(hash)
}
fn decode_filter_request(payload: &[u8]) -> anyhow::Result<GetCfiltersMessage> {
if payload.len() < 37 {
anyhow::bail!("filter request too short");
}
let mut stop_hash = [0u8; 32];
stop_hash.copy_from_slice(&payload[5..37]);
Ok(GetCfiltersMessage {
filter_type: payload[0],
start_height: u32::from_le_bytes(payload[1..5].try_into().unwrap()),
stop_hash,
})
}
fn encode_filter_request(msg: &GetCfiltersMessage) -> Vec<u8> {
let mut buf = Vec::with_capacity(37);
buf.push(msg.filter_type);
buf.extend_from_slice(&msg.start_height.to_le_bytes());
buf.extend_from_slice(&msg.stop_hash);
buf
}
fn decode_cfilter(payload: &[u8]) -> anyhow::Result<CfilterMessage> {
if payload.len() < 34 {
anyhow::bail!("cfilter too short");
}
let mut block_hash = [0u8; 32];
block_hash.copy_from_slice(&payload[1..33]);
let mut cursor = std::io::Cursor::new(&payload[33..]);
let len = blvm_protocol::varint::read_varint(&mut cursor).map_err(|e| anyhow::anyhow!("{e}"))?
as usize;
let start = 33 + cursor.position() as usize;
if payload.len() < start + len {
anyhow::bail!("cfilter truncated");
}
Ok(CfilterMessage {
filter_type: payload[0],
block_hash,
filter_data: payload[start..start + len].to_vec(),
num_elements: 0,
})
}
fn encode_cfilter(msg: &CfilterMessage) -> anyhow::Result<Vec<u8>> {
let mut buf = Vec::new();
buf.push(msg.filter_type);
buf.extend_from_slice(&msg.block_hash);
blvm_protocol::varint::write_varint(&mut buf, msg.filter_data.len() as u64)
.map_err(|e| anyhow::anyhow!("{e}"))?;
buf.extend_from_slice(&msg.filter_data);
Ok(buf)
}
fn decode_cfheaders(payload: &[u8]) -> anyhow::Result<CfheadersMessage> {
if payload.len() < 66 {
anyhow::bail!("cfheaders too short");
}
let mut stop_hash = [0u8; 32];
stop_hash.copy_from_slice(&payload[1..33]);
let mut filter_hash = [0u8; 32];
filter_hash.copy_from_slice(&payload[33..65]);
let mut cursor = std::io::Cursor::new(&payload[65..]);
let count = blvm_protocol::varint::read_varint(&mut cursor)
.map_err(|e| anyhow::anyhow!("{e}"))? as usize;
if count > 2000 {
anyhow::bail!("cfheaders count {count}");
}
let base = 65 + cursor.position() as usize;
if payload.len() < base + count * 32 {
anyhow::bail!("cfheaders truncated");
}
let mut filter_headers = Vec::with_capacity(count);
for index in 0..count {
let off = base + index * 32;
let mut hash = [0u8; 32];
hash.copy_from_slice(&payload[off..off + 32]);
filter_headers.push(hash);
}
Ok(CfheadersMessage {
filter_type: payload[0],
stop_hash,
prev_header: FilterHeaderData {
filter_hash,
prev_header_hash: [0u8; 32],
},
filter_headers,
})
}
fn encode_cfheaders(msg: &CfheadersMessage) -> anyhow::Result<Vec<u8>> {
let mut buf = Vec::new();
buf.push(msg.filter_type);
buf.extend_from_slice(&msg.stop_hash);
buf.extend_from_slice(&msg.prev_header.filter_hash);
blvm_protocol::varint::write_varint(&mut buf, msg.filter_headers.len() as u64)
.map_err(|e| anyhow::anyhow!("{e}"))?;
for hash in &msg.filter_headers {
buf.extend_from_slice(hash);
}
Ok(buf)
}
fn decode_getcfcheckpt(payload: &[u8]) -> anyhow::Result<GetCfcheckptMessage> {
if payload.len() < 33 {
anyhow::bail!("getcfcheckpt too short");
}
let mut stop_hash = [0u8; 32];
stop_hash.copy_from_slice(&payload[1..33]);
Ok(GetCfcheckptMessage {
filter_type: payload[0],
stop_hash,
})
}
fn encode_getcfcheckpt(msg: &GetCfcheckptMessage) -> Vec<u8> {
let mut buf = Vec::with_capacity(33);
buf.push(msg.filter_type);
buf.extend_from_slice(&msg.stop_hash);
buf
}
fn decode_cfcheckpt(payload: &[u8]) -> anyhow::Result<CfcheckptMessage> {
if payload.len() < 34 {
anyhow::bail!("cfcheckpt too short");
}
let mut stop_hash = [0u8; 32];
stop_hash.copy_from_slice(&payload[1..33]);
let mut cursor = std::io::Cursor::new(&payload[33..]);
let count = blvm_protocol::varint::read_varint(&mut cursor)
.map_err(|e| anyhow::anyhow!("{e}"))? as usize;
if count > 2000 {
anyhow::bail!("cfcheckpt count {count}");
}
let base = 33 + cursor.position() as usize;
if payload.len() < base + count * 32 {
anyhow::bail!("cfcheckpt truncated");
}
let mut filter_header_hashes = Vec::with_capacity(count);
for index in 0..count {
let off = base + index * 32;
let mut hash = [0u8; 32];
hash.copy_from_slice(&payload[off..off + 32]);
filter_header_hashes.push(hash);
}
Ok(CfcheckptMessage {
filter_type: payload[0],
stop_hash,
filter_header_hashes,
})
}
fn encode_cfcheckpt(msg: &CfcheckptMessage) -> anyhow::Result<Vec<u8>> {
let mut buf = Vec::new();
buf.push(msg.filter_type);
buf.extend_from_slice(&msg.stop_hash);
blvm_protocol::varint::write_varint(&mut buf, msg.filter_header_hashes.len() as u64)
.map_err(|e| anyhow::anyhow!("{e}"))?;
for hash in &msg.filter_header_hashes {
buf.extend_from_slice(hash);
}
Ok(buf)
}
fn decode_getblocks(payload: &[u8]) -> anyhow::Result<GetBlocksMessage> {
let mut cursor = std::io::Cursor::new(payload);
let mut version_bytes = [0u8; 4];
std::io::Read::read_exact(&mut cursor, &mut version_bytes)?;
let version = i32::from_le_bytes(version_bytes);
let hash_count = blvm_protocol::varint::read_varint(&mut cursor)
.map_err(|e| anyhow::anyhow!("{e}"))? as usize;
if hash_count > 2000 {
anyhow::bail!("getblocks locator count {hash_count}");
}
let mut block_locator_hashes = Vec::with_capacity(hash_count);
for _ in 0..hash_count {
block_locator_hashes.push(read_hash(&mut cursor)?);
}
Ok(GetBlocksMessage {
version,
block_locator_hashes,
hash_stop: read_hash(&mut cursor)?,
})
}
fn encode_getblocks(msg: &GetBlocksMessage) -> anyhow::Result<Vec<u8>> {
let mut buf = Vec::new();
buf.extend_from_slice(&msg.version.to_le_bytes());
blvm_protocol::varint::write_varint(&mut buf, msg.block_locator_hashes.len() as u64)
.map_err(|e| anyhow::anyhow!("{e}"))?;
for hash in &msg.block_locator_hashes {
buf.extend_from_slice(hash);
}
buf.extend_from_slice(&msg.hash_stop);
Ok(buf)
}
#[cfg(test)]
mod tests {
use super::{ProtocolMessage, ProtocolParser, TxMessage};
use blvm_protocol::{
OutPoint, Transaction, TransactionInput, TransactionOutput, tx_inputs, tx_outputs,
};
fn one_input_tx() -> Transaction {
Transaction {
version: 2,
inputs: tx_inputs![TransactionInput {
prevout: OutPoint {
hash: [4u8; 32],
index: 0,
},
script_sig: Vec::new(),
sequence: 0xfffffffe,
}],
outputs: tx_outputs![TransactionOutput {
value: 1000,
script_pubkey: Vec::new(),
}],
lock_time: 0,
}
}
#[test]
fn tx_message_keeps_witness_stacks() {
let witnesses = vec![vec![vec![1u8, 2, 3]]];
let message = ProtocolMessage::Tx(TxMessage {
transaction: one_input_tx(),
witnesses: witnesses.clone(),
});
let bytes = ProtocolParser::serialize_message(&message).unwrap();
match ProtocolParser::parse_message(&bytes).unwrap() {
ProtocolMessage::Tx(parsed) => {
assert_eq!(parsed.witnesses, witnesses);
assert_eq!(parsed.transaction.inputs.len(), 1);
}
other => panic!("expected a tx message, got {other:?}"),
}
let legacy = ProtocolMessage::Tx(TxMessage {
transaction: one_input_tx(),
witnesses: vec![Vec::new()],
});
let legacy_bytes = ProtocolParser::serialize_message(&legacy).unwrap();
match ProtocolParser::parse_message(&legacy_bytes).unwrap() {
ProtocolMessage::Tx(parsed) => {
assert_eq!(parsed.witnesses.len(), 1);
assert!(parsed.witnesses.iter().all(|stack| stack.is_empty()));
}
other => panic!("expected a tx message, got {other:?}"),
}
}
#[test]
fn filter_lock_getcfilters_and_cfilter_use_wire_bytes() {
use super::{CfilterMessage, GetCfiltersMessage, ProtocolMessage, ProtocolParser};
let stop = [0xab; 32];
let request = GetCfiltersMessage {
filter_type: 0,
start_height: 0x0102_0304,
stop_hash: stop,
};
let framed =
ProtocolParser::serialize_message(&ProtocolMessage::GetCfilters(request)).unwrap();
let payload = &framed[24..];
assert_eq!(payload.len(), 37);
match ProtocolParser::parse_message(&framed).unwrap() {
ProtocolMessage::GetCfilters(parsed) => {
assert_eq!(parsed.filter_type, 0);
assert_eq!(parsed.start_height, 0x0102_0304);
assert_eq!(parsed.stop_hash, stop);
}
other => panic!("expected getcfilters, got {other:?}"),
}
let reply = CfilterMessage {
filter_type: 0,
block_hash: stop,
filter_data: vec![0x09, 0x08, 0x07],
num_elements: 3,
};
let framed = ProtocolParser::serialize_message(&ProtocolMessage::Cfilter(reply)).unwrap();
let payload = &framed[24..];
assert_eq!(payload.len(), 37, "no trailing element count");
assert_eq!(payload[0], 0);
assert_eq!(&payload[1..33], &stop);
assert_eq!(payload[33], 3);
assert_eq!(&payload[34..], &[0x09, 0x08, 0x07]);
match ProtocolParser::parse_message(&framed).unwrap() {
ProtocolMessage::Cfilter(parsed) => {
assert_eq!(parsed.filter_data, vec![0x09, 0x08, 0x07]);
assert_eq!(parsed.block_hash, stop);
}
other => panic!("expected cfilter, got {other:?}"),
}
}
#[test]
fn getblocks_lock_69_byte_request_decodes() {
let mut payload = vec![0x80, 0x11, 0x01, 0x00, 0x01];
payload.extend(std::iter::repeat(0x11).take(32));
payload.extend(std::iter::repeat(0x22).take(32));
assert_eq!(payload.len(), 69);
let msg = super::decode_getblocks(&payload).unwrap();
assert_eq!(msg.version, 70016);
assert_eq!(msg.block_locator_hashes, vec![[0x11; 32]]);
assert_eq!(msg.hash_stop, [0x22; 32]);
}
}