#[cfg(feature = "utxo-commitments")]
use crate::utxo_commitments::data_structures::{
UtxoCommitment, UtxoCommitmentError, UtxoCommitmentResult,
};
#[cfg(feature = "utxo-commitments")]
use crate::utxo_commitments::network_integration::UtxoCommitmentsNetworkClient;
#[cfg(feature = "utxo-commitments")]
use crate::utxo_commitments::verification::{verify_header_chain, verify_supply};
#[cfg(feature = "utxo-commitments")]
use blvm_consensus::types::{BlockHeader, Hash, Natural};
#[cfg(feature = "utxo-commitments")]
use blvm_spec_lock::spec_locked;
#[cfg(feature = "utxo-commitments")]
use sparse_merkle_tree::MerkleProof;
#[cfg(feature = "utxo-commitments")]
use std::collections::{HashMap, HashSet};
#[cfg(feature = "utxo-commitments")]
use std::net::IpAddr;
#[derive(Debug, Clone)]
pub struct PeerInfo {
pub address: IpAddr,
pub asn: Option<u32>, pub country: Option<String>, pub implementation: Option<String>, pub subnet: u32, }
impl PeerInfo {
pub fn extract_subnet(ip: IpAddr) -> u32 {
match ip {
IpAddr::V4(ipv4) => {
let octets = ipv4.octets();
((octets[0] as u32) << 24) | ((octets[1] as u32) << 16)
}
IpAddr::V6(ipv6) => {
let segments = ipv6.segments();
((segments[0] as u32) << 16) | (segments[1] as u32)
}
}
}
}
#[derive(Debug, Clone)]
pub struct PeerCommitment {
pub peer_info: PeerInfo,
pub commitment: UtxoCommitment,
}
#[derive(Debug, Clone)]
pub struct ConsensusResult {
pub commitment: UtxoCommitment,
pub agreement_count: usize,
pub total_peers: usize,
pub agreement_ratio: f64,
}
#[derive(Debug, Clone)]
pub struct ConsensusConfig {
pub min_peers: usize,
pub target_peers: usize,
pub consensus_threshold: f64,
pub max_peers_per_asn: usize,
pub safety_margin: Natural,
pub shuffle_peers: bool,
}
impl Default for ConsensusConfig {
fn default() -> Self {
Self {
min_peers: 5,
target_peers: 10,
consensus_threshold: 0.8, max_peers_per_asn: 2,
safety_margin: 2016, shuffle_peers: true,
}
}
}
pub struct PeerConsensus {
pub config: ConsensusConfig,
}
impl PeerConsensus {
pub fn new(config: ConsensusConfig) -> Self {
Self { config }
}
#[spec_locked("13.4")]
pub fn discover_diverse_peers(&self, all_peers: Vec<PeerInfo>) -> Vec<PeerInfo> {
use rand::seq::SliceRandom;
let mut peers = all_peers;
if self.config.shuffle_peers {
peers.shuffle(&mut rand::thread_rng());
}
let mut diverse_peers = Vec::new();
let mut seen_asn: HashMap<u32, usize> = HashMap::new();
let mut seen_subnets: HashSet<u32> = HashSet::new();
let _seen_countries: HashSet<String> = HashSet::new();
for peer in peers {
if let Some(asn) = peer.asn {
let asn_count = seen_asn.entry(asn).or_insert(0);
if *asn_count >= self.config.max_peers_per_asn {
continue; }
*asn_count += 1;
}
if seen_subnets.contains(&peer.subnet) {
continue; }
seen_subnets.insert(peer.subnet);
diverse_peers.push(peer);
if diverse_peers.len() >= self.config.target_peers {
break;
}
}
diverse_peers
}
#[spec_locked("13.4")]
pub fn determine_checkpoint_height(&self, peer_tips: Vec<Natural>) -> Natural {
if peer_tips.is_empty() {
return 0;
}
let mut sorted_tips = peer_tips;
sorted_tips.sort();
debug_assert!(
sorted_tips.windows(2).all(|w| w[0] <= w[1]),
"Tips must be sorted in ascending order"
);
let median_tip = if sorted_tips.len() % 2 == 0 {
let mid = sorted_tips.len() / 2;
let lower = sorted_tips[mid - 1];
let upper = sorted_tips[mid];
debug_assert!(
lower <= upper,
"Lower median value ({}) must be <= upper ({})",
lower,
upper
);
(lower + upper) / 2
} else {
sorted_tips[sorted_tips.len() / 2]
};
if let (Some(&min_tip), Some(&max_tip)) = (sorted_tips.first(), sorted_tips.last()) {
debug_assert!(
median_tip >= min_tip && median_tip <= max_tip,
"Median ({}) must be between min ({}) and max ({})",
median_tip,
min_tip,
max_tip
);
}
if median_tip > self.config.safety_margin {
let checkpoint = median_tip - self.config.safety_margin;
debug_assert!(
checkpoint <= median_tip,
"Checkpoint ({}) must be <= median ({})",
checkpoint,
median_tip
);
checkpoint
} else {
0 }
}
pub async fn request_utxo_sets<C: UtxoCommitmentsNetworkClient>(
&self,
network_client: &C,
peers: &[(PeerInfo, String)],
checkpoint_height: Natural,
checkpoint_hash: Hash,
) -> Vec<PeerCommitment> {
let mut results = Vec::new();
for (peer_info, peer_id) in peers {
match network_client
.request_utxo_set(peer_id, checkpoint_height, checkpoint_hash)
.await
{
Ok(commitment) => results.push(PeerCommitment {
peer_info: peer_info.clone(),
commitment,
}),
Err(_) => {
continue;
}
}
}
results
}
#[spec_locked("11.4")]
pub fn find_consensus(
&self,
peer_commitments: Vec<PeerCommitment>,
) -> UtxoCommitmentResult<ConsensusResult> {
let total_peers = peer_commitments.len();
if total_peers < self.config.min_peers {
return Err(UtxoCommitmentError::VerificationFailed(format!(
"Insufficient peers: got {}, need at least {}",
total_peers, self.config.min_peers
)));
}
let mut commitment_groups: HashMap<(Hash, u64, u64, Natural), Vec<PeerCommitment>> =
HashMap::new();
for peer_commitment in peer_commitments {
let key = (
peer_commitment.commitment.merkle_root,
peer_commitment.commitment.total_supply,
peer_commitment.commitment.utxo_count,
peer_commitment.commitment.block_height,
);
commitment_groups
.entry(key)
.or_insert_with(Vec::new)
.push(peer_commitment);
}
let mut best_group: Option<(&(Hash, u64, u64, Natural), Vec<PeerCommitment>)> = None;
let mut best_agreement_count = 0;
for (key, group) in commitment_groups.iter() {
let agreement_count = group.len();
if agreement_count > best_agreement_count {
best_agreement_count = agreement_count;
best_group = Some((key, group.clone()));
}
}
let (_, group) = match best_group {
Some(g) => g,
None => {
return Err(UtxoCommitmentError::VerificationFailed(
"No consensus groups found".to_string(),
));
}
};
let required_agreement_count =
((total_peers as f64) * self.config.consensus_threshold).ceil() as usize;
debug_assert!(
required_agreement_count <= total_peers,
"Required agreement count ({}) cannot exceed total peers ({})",
required_agreement_count,
total_peers
);
debug_assert!(
required_agreement_count >= 1,
"Required agreement count must be at least 1"
);
debug_assert!(
best_agreement_count <= total_peers,
"Best agreement count ({}) cannot exceed total peers ({})",
best_agreement_count,
total_peers
);
if best_agreement_count < required_agreement_count {
let best_ratio = best_agreement_count as f64 / total_peers as f64;
return Err(UtxoCommitmentError::VerificationFailed(format!(
"No consensus: best agreement is {:.1}% ({} of {} peers), need {:.1}% (at least {} peers)",
best_ratio * 100.0,
best_agreement_count,
total_peers,
self.config.consensus_threshold * 100.0,
required_agreement_count
)));
}
let commitment = group[0].commitment.clone();
let agreement_count = group.len();
let agreement_ratio = agreement_count as f64 / total_peers as f64;
debug_assert!(
agreement_count >= required_agreement_count,
"Agreement count ({}) must meet threshold ({})",
agreement_count,
required_agreement_count
);
debug_assert!(
agreement_ratio >= self.config.consensus_threshold,
"Agreement ratio ({:.4}) must be >= threshold ({:.4})",
agreement_ratio,
self.config.consensus_threshold
);
debug_assert!(
agreement_count <= total_peers,
"Agreement count ({}) cannot exceed total peers ({})",
agreement_count,
total_peers
);
debug_assert!(
agreement_ratio >= 0.0 && agreement_ratio <= 1.0,
"Agreement ratio ({:.4}) must be in [0, 1]",
agreement_ratio
);
Ok(ConsensusResult {
commitment,
agreement_count,
total_peers,
agreement_ratio,
})
}
#[spec_locked("11.4")]
pub fn verify_consensus_commitment(
&self,
consensus: &ConsensusResult,
header_chain: &[BlockHeader],
) -> UtxoCommitmentResult<bool> {
verify_header_chain(header_chain)?;
verify_supply(&consensus.commitment)?;
if consensus.commitment.block_height as usize >= header_chain.len() {
return Err(UtxoCommitmentError::VerificationFailed(format!(
"Commitment height {} exceeds header chain length {}",
consensus.commitment.block_height,
header_chain.len()
)));
}
let expected_header = &header_chain[consensus.commitment.block_height as usize];
let expected_hash = compute_block_hash(expected_header);
if consensus.commitment.block_hash != expected_hash {
return Err(UtxoCommitmentError::VerificationFailed(format!(
"Block hash mismatch: commitment has {:?}, header chain has {:?}",
consensus.commitment.block_hash, expected_hash
)));
}
Ok(true)
}
#[spec_locked("13.4")]
pub fn verify_utxo_proofs(
&self,
consensus: &ConsensusResult,
utxos_to_verify: Vec<(crate::types::OutPoint, crate::types::UTXO, MerkleProof)>,
) -> UtxoCommitmentResult<bool> {
use crate::utxo_commitments::merkle_tree::UtxoMerkleTree;
for (outpoint, expected_utxo, proof) in utxos_to_verify {
let is_valid = UtxoMerkleTree::verify_utxo_proof(
&consensus.commitment,
&outpoint,
&expected_utxo,
proof,
)?;
if !is_valid {
return Err(UtxoCommitmentError::VerificationFailed(format!(
"UTXO proof verification failed for outpoint {:?} - possible attack",
outpoint
)));
}
}
Ok(true)
}
#[cfg(feature = "parallel-verification")]
pub fn verify_utxo_proofs_parallel(
&self,
consensus: &ConsensusResult,
utxos_to_verify: Vec<(crate::types::OutPoint, crate::types::UTXO, MerkleProof)>,
) -> UtxoCommitmentResult<bool> {
use crate::utxo_commitments::merkle_tree::UtxoMerkleTree;
use rayon::prelude::*;
let results: Vec<UtxoCommitmentResult<bool>> = utxos_to_verify
.into_par_iter()
.map(|(outpoint, expected_utxo, proof)| {
UtxoMerkleTree::verify_utxo_proof(
&consensus.commitment,
&outpoint,
&expected_utxo,
proof,
)
})
.collect();
for (i, result) in results.iter().enumerate() {
match result {
Ok(true) => continue,
Ok(false) | Err(_) => {
return Err(UtxoCommitmentError::VerificationFailed(format!(
"UTXO proof verification failed at index {} - possible attack",
i
)));
}
}
}
Ok(true)
}
pub fn verify_utxo_proofs_parallel_fallback(
&self,
consensus: &ConsensusResult,
utxos_to_verify: Vec<(crate::types::OutPoint, crate::types::UTXO, MerkleProof)>,
) -> UtxoCommitmentResult<bool> {
self.verify_utxo_proofs(consensus, utxos_to_verify)
}
}
fn compute_block_hash(header: &BlockHeader) -> Hash {
use sha2::{Digest, Sha256};
let mut bytes = Vec::with_capacity(80);
bytes.extend_from_slice(&header.version.to_le_bytes());
bytes.extend_from_slice(&header.prev_block_hash);
bytes.extend_from_slice(&header.merkle_root);
bytes.extend_from_slice(&header.timestamp.to_le_bytes());
bytes.extend_from_slice(&header.bits.to_le_bytes());
bytes.extend_from_slice(&header.nonce.to_le_bytes());
let first_hash = Sha256::digest(&bytes);
let second_hash = Sha256::digest(&first_hash);
let mut hash = [0u8; 32];
hash.copy_from_slice(&second_hash);
hash
}
#[doc(hidden)]
const _PEER_CONSENSUS_SPEC: () = ();