blvm_protocol/utxo_commitments/
peer_consensus.rs1#[cfg(feature = "utxo-commitments")]
8use crate::utxo_commitments::data_structures::{
9 UtxoCommitment, UtxoCommitmentError, UtxoCommitmentResult,
10};
11#[cfg(feature = "utxo-commitments")]
12use crate::utxo_commitments::network_integration::UtxoCommitmentsNetworkClient;
13#[cfg(feature = "utxo-commitments")]
14use crate::utxo_commitments::verification::{verify_header_chain, verify_supply};
15#[cfg(feature = "utxo-commitments")]
16use blvm_consensus::types::{BlockHeader, Hash, Natural};
17#[cfg(feature = "utxo-commitments")]
18use blvm_spec_lock::spec_locked;
19#[cfg(feature = "utxo-commitments")]
20use sparse_merkle_tree::MerkleProof;
21#[cfg(feature = "utxo-commitments")]
22use std::collections::{HashMap, HashSet};
23#[cfg(feature = "utxo-commitments")]
24use std::net::IpAddr;
25
26#[derive(Debug, Clone)]
28pub struct PeerInfo {
29 pub address: IpAddr,
30 pub asn: Option<u32>, pub country: Option<String>, pub implementation: Option<String>, pub subnet: u32, }
35
36impl PeerInfo {
37 pub fn extract_subnet(ip: IpAddr) -> u32 {
39 match ip {
40 IpAddr::V4(ipv4) => {
41 let octets = ipv4.octets();
42 ((octets[0] as u32) << 24) | ((octets[1] as u32) << 16)
43 }
44 IpAddr::V6(ipv6) => {
45 let segments = ipv6.segments();
47 ((segments[0] as u32) << 16) | (segments[1] as u32)
48 }
49 }
50 }
51}
52
53#[derive(Debug, Clone)]
55pub struct PeerCommitment {
56 pub peer_info: PeerInfo,
57 pub commitment: UtxoCommitment,
58}
59
60#[derive(Debug, Clone)]
62pub struct ConsensusResult {
63 pub commitment: UtxoCommitment,
65 pub agreement_count: usize,
67 pub total_peers: usize,
68 pub agreement_ratio: f64,
70}
71
72#[derive(Debug, Clone)]
74pub struct ConsensusConfig {
75 pub min_peers: usize,
77 pub target_peers: usize,
79 pub consensus_threshold: f64,
81 pub max_peers_per_asn: usize,
83 pub safety_margin: Natural,
85 pub shuffle_peers: bool,
88}
89
90impl Default for ConsensusConfig {
91 fn default() -> Self {
92 Self {
93 min_peers: 5,
94 target_peers: 10,
95 consensus_threshold: 0.8, max_peers_per_asn: 2,
97 safety_margin: 2016, shuffle_peers: true,
99 }
100 }
101}
102
103pub struct PeerConsensus {
105 pub config: ConsensusConfig,
106}
107
108impl PeerConsensus {
109 pub fn new(config: ConsensusConfig) -> Self {
111 Self { config }
112 }
113
114 #[spec_locked("13.4")]
122 pub fn discover_diverse_peers(&self, all_peers: Vec<PeerInfo>) -> Vec<PeerInfo> {
123 use rand::seq::SliceRandom;
124
125 let mut peers = all_peers;
127 if self.config.shuffle_peers {
128 peers.shuffle(&mut rand::thread_rng());
129 }
130
131 let mut diverse_peers = Vec::new();
132 let mut seen_asn: HashMap<u32, usize> = HashMap::new();
133 let mut seen_subnets: HashSet<u32> = HashSet::new();
134 let _seen_countries: HashSet<String> = HashSet::new();
135
136 for peer in peers {
137 if let Some(asn) = peer.asn {
139 let asn_count = seen_asn.entry(asn).or_insert(0);
140 if *asn_count >= self.config.max_peers_per_asn {
141 continue; }
143 *asn_count += 1;
144 }
145
146 if seen_subnets.contains(&peer.subnet) {
148 continue; }
150 seen_subnets.insert(peer.subnet);
151
152 diverse_peers.push(peer);
154
155 if diverse_peers.len() >= self.config.target_peers {
157 break;
158 }
159 }
160
161 diverse_peers
162 }
163
164 #[spec_locked("13.4")]
173 pub fn determine_checkpoint_height(&self, peer_tips: Vec<Natural>) -> Natural {
174 if peer_tips.is_empty() {
175 return 0;
176 }
177
178 let mut sorted_tips = peer_tips;
180 sorted_tips.sort();
181
182 debug_assert!(
184 sorted_tips.windows(2).all(|w| w[0] <= w[1]),
185 "Tips must be sorted in ascending order"
186 );
187
188 let median_tip = if sorted_tips.len() % 2 == 0 {
189 let mid = sorted_tips.len() / 2;
191 let lower = sorted_tips[mid - 1];
192 let upper = sorted_tips[mid];
193
194 debug_assert!(
196 lower <= upper,
197 "Lower median value ({}) must be <= upper ({})",
198 lower,
199 upper
200 );
201
202 (lower + upper) / 2
204 } else {
205 sorted_tips[sorted_tips.len() / 2]
207 };
208
209 if let (Some(&min_tip), Some(&max_tip)) = (sorted_tips.first(), sorted_tips.last()) {
211 debug_assert!(
212 median_tip >= min_tip && median_tip <= max_tip,
213 "Median ({}) must be between min ({}) and max ({})",
214 median_tip,
215 min_tip,
216 max_tip
217 );
218 }
219
220 if median_tip > self.config.safety_margin {
222 let checkpoint = median_tip - self.config.safety_margin;
223
224 debug_assert!(
226 checkpoint <= median_tip,
227 "Checkpoint ({}) must be <= median ({})",
228 checkpoint,
229 median_tip
230 );
231
232 checkpoint
233 } else {
234 0 }
236 }
237
238 pub async fn request_utxo_sets<C: UtxoCommitmentsNetworkClient>(
247 &self,
248 network_client: &C,
249 peers: &[(PeerInfo, String)],
250 checkpoint_height: Natural,
251 checkpoint_hash: Hash,
252 ) -> Vec<PeerCommitment> {
253 let mut results = Vec::new();
254
255 for (peer_info, peer_id) in peers {
256 match network_client
257 .request_utxo_set(peer_id, checkpoint_height, checkpoint_hash)
258 .await
259 {
260 Ok(commitment) => results.push(PeerCommitment {
261 peer_info: peer_info.clone(),
262 commitment,
263 }),
264 Err(_) => {
265 continue;
267 }
268 }
269 }
270
271 results
272 }
273
274 #[spec_locked("11.4")]
279 pub fn find_consensus(
280 &self,
281 peer_commitments: Vec<PeerCommitment>,
282 ) -> UtxoCommitmentResult<ConsensusResult> {
283 let total_peers = peer_commitments.len();
284 if total_peers < self.config.min_peers {
285 return Err(UtxoCommitmentError::VerificationFailed(format!(
286 "Insufficient peers: got {}, need at least {}",
287 total_peers, self.config.min_peers
288 )));
289 }
290
291 let mut commitment_groups: HashMap<(Hash, u64, u64, Natural), Vec<PeerCommitment>> =
293 HashMap::new();
294
295 for peer_commitment in peer_commitments {
296 let key = (
297 peer_commitment.commitment.merkle_root,
298 peer_commitment.commitment.total_supply,
299 peer_commitment.commitment.utxo_count,
300 peer_commitment.commitment.block_height,
301 );
302 commitment_groups
303 .entry(key)
304 .or_insert_with(Vec::new)
305 .push(peer_commitment);
306 }
307
308 let mut best_group: Option<(&(Hash, u64, u64, Natural), Vec<PeerCommitment>)> = None;
310 let mut best_agreement_count = 0;
311
312 for (key, group) in commitment_groups.iter() {
313 let agreement_count = group.len();
314
315 if agreement_count > best_agreement_count {
316 best_agreement_count = agreement_count;
317 best_group = Some((key, group.clone()));
318 }
319 }
320
321 let (_, group) = match best_group {
323 Some(g) => g,
324 None => {
325 return Err(UtxoCommitmentError::VerificationFailed(
326 "No consensus groups found".to_string(),
327 ));
328 }
329 };
330
331 let required_agreement_count =
340 ((total_peers as f64) * self.config.consensus_threshold).ceil() as usize;
341
342 debug_assert!(
344 required_agreement_count <= total_peers,
345 "Required agreement count ({}) cannot exceed total peers ({})",
346 required_agreement_count,
347 total_peers
348 );
349 debug_assert!(
350 required_agreement_count >= 1,
351 "Required agreement count must be at least 1"
352 );
353 debug_assert!(
354 best_agreement_count <= total_peers,
355 "Best agreement count ({}) cannot exceed total peers ({})",
356 best_agreement_count,
357 total_peers
358 );
359
360 if best_agreement_count < required_agreement_count {
361 let best_ratio = best_agreement_count as f64 / total_peers as f64;
362 return Err(UtxoCommitmentError::VerificationFailed(format!(
363 "No consensus: best agreement is {:.1}% ({} of {} peers), need {:.1}% (at least {} peers)",
364 best_ratio * 100.0,
365 best_agreement_count,
366 total_peers,
367 self.config.consensus_threshold * 100.0,
368 required_agreement_count
369 )));
370 }
371
372 let commitment = group[0].commitment.clone();
374 let agreement_count = group.len();
375 let agreement_ratio = agreement_count as f64 / total_peers as f64;
376
377 debug_assert!(
379 agreement_count >= required_agreement_count,
380 "Agreement count ({}) must meet threshold ({})",
381 agreement_count,
382 required_agreement_count
383 );
384 debug_assert!(
385 agreement_ratio >= self.config.consensus_threshold,
386 "Agreement ratio ({:.4}) must be >= threshold ({:.4})",
387 agreement_ratio,
388 self.config.consensus_threshold
389 );
390 debug_assert!(
391 agreement_count <= total_peers,
392 "Agreement count ({}) cannot exceed total peers ({})",
393 agreement_count,
394 total_peers
395 );
396 debug_assert!(
397 agreement_ratio >= 0.0 && agreement_ratio <= 1.0,
398 "Agreement ratio ({:.4}) must be in [0, 1]",
399 agreement_ratio
400 );
401
402 Ok(ConsensusResult {
403 commitment,
404 agreement_count,
405 total_peers,
406 agreement_ratio,
407 })
408 }
409
410 #[spec_locked("11.4")]
417 pub fn verify_consensus_commitment(
418 &self,
419 consensus: &ConsensusResult,
420 header_chain: &[BlockHeader],
421 ) -> UtxoCommitmentResult<bool> {
422 verify_header_chain(header_chain)?;
424
425 verify_supply(&consensus.commitment)?;
427
428 if consensus.commitment.block_height as usize >= header_chain.len() {
430 return Err(UtxoCommitmentError::VerificationFailed(format!(
431 "Commitment height {} exceeds header chain length {}",
432 consensus.commitment.block_height,
433 header_chain.len()
434 )));
435 }
436
437 let expected_header = &header_chain[consensus.commitment.block_height as usize];
438 let expected_hash = compute_block_hash(expected_header);
439
440 if consensus.commitment.block_hash != expected_hash {
441 return Err(UtxoCommitmentError::VerificationFailed(format!(
442 "Block hash mismatch: commitment has {:?}, header chain has {:?}",
443 consensus.commitment.block_hash, expected_hash
444 )));
445 }
446
447 Ok(true)
448 }
449
450 #[spec_locked("13.4")]
478 pub fn verify_utxo_proofs(
479 &self,
480 consensus: &ConsensusResult,
481 utxos_to_verify: Vec<(crate::types::OutPoint, crate::types::UTXO, MerkleProof)>,
482 ) -> UtxoCommitmentResult<bool> {
483 use crate::utxo_commitments::merkle_tree::UtxoMerkleTree;
484
485 for (outpoint, expected_utxo, proof) in utxos_to_verify {
487 let is_valid = UtxoMerkleTree::verify_utxo_proof(
488 &consensus.commitment,
489 &outpoint,
490 &expected_utxo,
491 proof,
492 )?;
493
494 if !is_valid {
495 return Err(UtxoCommitmentError::VerificationFailed(format!(
496 "UTXO proof verification failed for outpoint {:?} - possible attack",
497 outpoint
498 )));
499 }
500 }
501
502 Ok(true)
503 }
504
505 #[cfg(feature = "parallel-verification")]
521 pub fn verify_utxo_proofs_parallel(
522 &self,
523 consensus: &ConsensusResult,
524 utxos_to_verify: Vec<(crate::types::OutPoint, crate::types::UTXO, MerkleProof)>,
525 ) -> UtxoCommitmentResult<bool> {
526 use crate::utxo_commitments::merkle_tree::UtxoMerkleTree;
527 use rayon::prelude::*;
528
529 let results: Vec<UtxoCommitmentResult<bool>> = utxos_to_verify
531 .into_par_iter()
532 .map(|(outpoint, expected_utxo, proof)| {
533 UtxoMerkleTree::verify_utxo_proof(
534 &consensus.commitment,
535 &outpoint,
536 &expected_utxo,
537 proof,
538 )
539 })
540 .collect();
541
542 for (i, result) in results.iter().enumerate() {
544 match result {
545 Ok(true) => continue,
546 Ok(false) | Err(_) => {
547 return Err(UtxoCommitmentError::VerificationFailed(format!(
548 "UTXO proof verification failed at index {} - possible attack",
549 i
550 )));
551 }
552 }
553 }
554
555 Ok(true)
556 }
557
558 pub fn verify_utxo_proofs_parallel_fallback(
562 &self,
563 consensus: &ConsensusResult,
564 utxos_to_verify: Vec<(crate::types::OutPoint, crate::types::UTXO, MerkleProof)>,
565 ) -> UtxoCommitmentResult<bool> {
566 self.verify_utxo_proofs(consensus, utxos_to_verify)
568 }
569}
570
571fn compute_block_hash(header: &BlockHeader) -> Hash {
573 use sha2::{Digest, Sha256};
574
575 let mut bytes = Vec::with_capacity(80);
577 bytes.extend_from_slice(&header.version.to_le_bytes());
578 bytes.extend_from_slice(&header.prev_block_hash);
579 bytes.extend_from_slice(&header.merkle_root);
580 bytes.extend_from_slice(&header.timestamp.to_le_bytes());
581 bytes.extend_from_slice(&header.bits.to_le_bytes());
582 bytes.extend_from_slice(&header.nonce.to_le_bytes());
583
584 let first_hash = Sha256::digest(&bytes);
586 let second_hash = Sha256::digest(&first_hash);
587
588 let mut hash = [0u8; 32];
589 hash.copy_from_slice(&second_hash);
590 hash
591}
592
593#[doc(hidden)]
608const _PEER_CONSENSUS_SPEC: () = ();
609
610