use crate::network::transport::TransportType;
use anyhow::Result;
use blvm_protocol::{Block, BlockHeader, Hash, Transaction};
use sha2::{Digest, Sha256};
use std::collections::{HashMap, HashSet};
use std::hash::Hasher;
pub use blvm_protocol::bip152::{CompactBlock, ShortTxId};
pub fn calculate_tx_hash(tx: &Transaction) -> Hash {
let mut data = Vec::new();
data.extend_from_slice(&(tx.version as u32).to_le_bytes());
data.extend_from_slice(&encode_varint(tx.inputs.len() as u64));
for input in &tx.inputs {
data.extend_from_slice(&input.prevout.hash);
data.extend_from_slice(&input.prevout.index.to_le_bytes());
data.extend_from_slice(&encode_varint(input.script_sig.len() as u64));
data.extend_from_slice(&input.script_sig);
data.extend_from_slice(&(input.sequence as u32).to_le_bytes());
}
data.extend_from_slice(&encode_varint(tx.outputs.len() as u64));
for output in &tx.outputs {
data.extend_from_slice(&(output.value as u64).to_le_bytes());
data.extend_from_slice(&encode_varint(output.script_pubkey.len() as u64));
data.extend_from_slice(&output.script_pubkey);
}
data.extend_from_slice(&(tx.lock_time as u32).to_le_bytes());
let hash1 = Sha256::digest(&data);
let hash2 = Sha256::digest(hash1);
let mut result = [0u8; 32];
result.copy_from_slice(&hash2);
result
}
fn encode_varint(value: u64) -> Vec<u8> {
if value < 0xfd {
vec![value as u8]
} else if value <= 0xffff {
let mut result = vec![0xfd];
result.extend_from_slice(&(value as u16).to_le_bytes());
result
} else if value <= 0xffffffff {
let mut result = vec![0xfe];
result.extend_from_slice(&(value as u32).to_le_bytes());
result
} else {
let mut result = vec![0xff];
result.extend_from_slice(&value.to_le_bytes());
result
}
}
pub fn calculate_short_tx_id(tx_hash: &Hash, nonce: u64) -> ShortTxId {
let k0 = nonce;
let k1 = nonce.wrapping_add(1);
use siphasher::sip::SipHasher24;
let mut hasher = SipHasher24::new_with_keys(k0, k1);
hasher.write(tx_hash);
let hash_result = hasher.finish();
let mut short_id = [0u8; 6];
short_id.copy_from_slice(&hash_result.to_le_bytes()[..6]);
short_id
}
pub fn reconstruct_block(
compact_block: &CompactBlock,
mempool_txs: &HashMap<Hash, Transaction>,
) -> Result<Vec<usize>> {
let mut missing_indices = Vec::new();
let mut reconstructed_txs = Vec::new();
for (index, &short_id) in compact_block.short_ids.iter().enumerate() {
let mut matched = false;
for (tx_hash, tx) in mempool_txs {
let calculated_short_id = calculate_short_tx_id(tx_hash, compact_block.nonce);
if calculated_short_id == short_id {
let has_conflict = reconstructed_txs
.iter()
.any(|(_, existing_tx)| has_conflict_with_tx(tx, existing_tx));
if !has_conflict {
reconstructed_txs.push((index, tx.clone()));
matched = true;
break;
} else {
matched = false; break;
}
}
}
if !matched {
missing_indices.push(index);
}
}
Ok(missing_indices)
}
fn has_conflict_with_tx(tx1: &Transaction, tx2: &Transaction) -> bool {
for input1 in &tx1.inputs {
for input2 in &tx2.inputs {
if input1.prevout == input2.prevout {
return true;
}
}
}
false
}
pub fn create_compact_block(
block: &Block,
nonce: u64,
prefilled_indices: &HashSet<usize>,
) -> CompactBlock {
let mut short_ids = Vec::new();
let mut prefilled_txs = Vec::new();
for (index, tx) in block.transactions.iter().enumerate() {
if prefilled_indices.contains(&index) {
prefilled_txs.push((index, tx.clone()));
} else {
let tx_hash = calculate_tx_hash(tx);
let short_id = calculate_short_tx_id(&tx_hash, nonce);
short_ids.push(short_id);
}
}
CompactBlock {
header: block.header.clone(),
nonce,
short_ids,
prefilled_txs,
}
}
pub fn should_prefer_compact_blocks(transport_type: TransportType) -> bool {
match transport_type {
#[cfg(feature = "quinn")]
TransportType::Quinn => true, #[cfg(feature = "iroh")]
TransportType::Iroh => true, #[cfg(any(feature = "quinn", feature = "iroh"))]
TransportType::Tcp => false, #[cfg(not(any(feature = "quinn", feature = "iroh")))]
TransportType::Tcp => false, }
}
pub fn negotiate_optimizations(
transport_type: TransportType,
peer_services: u64,
) -> (u64, bool, bool) {
use blvm_protocol::bip157::NODE_COMPACT_FILTERS;
let compact_version = recommended_compact_block_version(transport_type);
let prefer_compact = should_prefer_compact_blocks(transport_type);
let supports_filters = (peer_services & NODE_COMPACT_FILTERS) != 0;
(compact_version, prefer_compact, supports_filters)
}
pub fn create_optimized_sendcmpct(
transport_type: TransportType,
peer_services: u64,
) -> crate::network::protocol::SendCmpctMessage {
use crate::network::protocol::SendCmpctMessage;
let (version, prefer_cmpct, _supports_filters) =
negotiate_optimizations(transport_type, peer_services);
SendCmpctMessage {
version,
prefer_cmpct: if prefer_cmpct { 1 } else { 0 },
}
}
pub fn recommended_compact_block_version(transport_type: TransportType) -> u64 {
match transport_type {
TransportType::Tcp => 1, #[cfg(feature = "quinn")]
TransportType::Quinn => 2, #[cfg(feature = "iroh")]
TransportType::Iroh => 2, }
}
pub fn is_quic_transport(transport_type: TransportType) -> bool {
match transport_type {
#[cfg(feature = "quinn")]
TransportType::Quinn => true,
#[cfg(feature = "iroh")]
TransportType::Iroh => true,
#[cfg(any(feature = "quinn", feature = "iroh"))]
TransportType::Tcp => false,
#[cfg(not(any(feature = "quinn", feature = "iroh")))]
TransportType::Tcp => false,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::network::transport::TransportType;
#[test]
fn test_calculate_short_tx_id() {
let tx_hash = [0u8; 32];
let nonce = 12345u64;
let short_id = calculate_short_tx_id(&tx_hash, nonce);
assert_eq!(short_id.len(), 6);
}
#[test]
fn test_reconstruct_block_empty_mempool() {
let compact_block = CompactBlock {
header: BlockHeader {
version: 1,
prev_block_hash: [0; 32],
merkle_root: [0; 32],
timestamp: 0,
bits: 0,
nonce: 0,
},
nonce: 0,
short_ids: vec![[0u8; 6]],
prefilled_txs: vec![],
};
let mempool_txs = HashMap::new();
let missing = reconstruct_block(&compact_block, &mempool_txs).unwrap();
assert_eq!(missing.len(), 1);
}
#[test]
fn test_should_prefer_compact_blocks_tcp() {
assert!(!should_prefer_compact_blocks(TransportType::Tcp));
}
#[cfg(feature = "quinn")]
#[test]
fn test_should_prefer_compact_blocks_quinn() {
assert_eq!(should_prefer_compact_blocks(TransportType::Quinn), true);
}
#[cfg(feature = "iroh")]
#[test]
fn test_should_prefer_compact_blocks_iroh() {
assert_eq!(should_prefer_compact_blocks(TransportType::Iroh), true);
}
#[test]
fn test_recommended_compact_block_version_tcp() {
assert_eq!(recommended_compact_block_version(TransportType::Tcp), 1);
}
#[cfg(feature = "quinn")]
#[test]
fn test_recommended_compact_block_version_quinn() {
assert_eq!(recommended_compact_block_version(TransportType::Quinn), 2);
}
#[cfg(feature = "iroh")]
#[test]
fn test_recommended_compact_block_version_iroh() {
assert_eq!(recommended_compact_block_version(TransportType::Iroh), 2);
}
#[test]
fn test_is_quic_transport_tcp() {
assert!(!is_quic_transport(TransportType::Tcp));
}
#[cfg(feature = "quinn")]
#[test]
fn test_is_quic_transport_quinn() {
assert_eq!(is_quic_transport(TransportType::Quinn), true);
}
#[cfg(feature = "iroh")]
#[test]
fn test_is_quic_transport_iroh() {
assert_eq!(is_quic_transport(TransportType::Iroh), true);
}
}