mine_policy.rs (3367B)
1 use std::collections::BTreeMap; 2 3 use anyhow::{Result, bail}; 4 5 use super::{Block, MINE_ACTIONS_PER_ANCHOR_LIMIT, Transaction}; 6 7 pub(super) const MINE_RETARGET_WINDOW_BLOCKS: u64 = 10; 8 pub(super) const MINE_MAX_RETARGET_STEP_BITS: u32 = 2; 9 pub(super) const MINE_MIN_DIFFICULTY_BITS: u32 = 10; 10 pub(super) const MINE_MAX_ANCHOR_AGE_BLOCKS: u64 = MINE_RETARGET_WINDOW_BLOCKS; 11 12 const MINE_TARGET_ACTIONS_PER_BLOCK: u64 = 1; 13 const MINE_MAX_DIFFICULTY_BITS: u32 = 32; 14 15 pub(super) fn retarget_mine_difficulty_bits(current: u32, mine_actions: u64) -> u32 { 16 let target = MINE_RETARGET_WINDOW_BLOCKS.saturating_mul(MINE_TARGET_ACTIONS_PER_BLOCK); 17 if target == 0 || mine_actions == target { 18 return current.clamp(MINE_MIN_DIFFICULTY_BITS, MINE_MAX_DIFFICULTY_BITS); 19 } 20 21 let step = if mine_actions > target { 22 floor_log2_ratio(mine_actions, target).min(MINE_MAX_RETARGET_STEP_BITS) 23 } else if mine_actions == 0 { 24 MINE_MAX_RETARGET_STEP_BITS 25 } else { 26 floor_log2_ratio(target, mine_actions).min(MINE_MAX_RETARGET_STEP_BITS) 27 }; 28 29 if step == 0 { 30 return current.clamp(MINE_MIN_DIFFICULTY_BITS, MINE_MAX_DIFFICULTY_BITS); 31 } 32 if mine_actions > target { 33 current 34 .saturating_add(step) 35 .clamp(MINE_MIN_DIFFICULTY_BITS, MINE_MAX_DIFFICULTY_BITS) 36 } else { 37 current 38 .saturating_sub(step) 39 .clamp(MINE_MIN_DIFFICULTY_BITS, MINE_MAX_DIFFICULTY_BITS) 40 } 41 } 42 43 fn floor_log2_ratio(numerator: u64, denominator: u64) -> u32 { 44 if denominator == 0 || numerator <= denominator { 45 return 0; 46 } 47 let mut step = 0_u32; 48 let mut threshold = denominator; 49 while threshold <= numerator / 2 { 50 threshold = threshold.saturating_mul(2); 51 step = step.saturating_add(1); 52 } 53 step 54 } 55 56 pub(super) fn ensure_mine_anchor_limit(_height: u64, transactions: &[Transaction]) -> Result<()> { 57 let mut anchor_counts = BTreeMap::new(); 58 for transaction in transactions { 59 let Some(anchor) = mine_anchor(transaction) else { 60 continue; 61 }; 62 let count = anchor_counts.entry(anchor).or_insert(0usize); 63 *count += 1; 64 if *count > MINE_ACTIONS_PER_ANCHOR_LIMIT { 65 bail!("block exceeds mine actions per anchor limit"); 66 } 67 } 68 Ok(()) 69 } 70 71 pub(super) fn mine_anchor(transaction: &Transaction) -> Option<&str> { 72 match transaction { 73 Transaction::Mine { anchor, .. } => Some(anchor.as_str()), 74 _ => None, 75 } 76 } 77 78 pub(super) fn mine_anchor_count_before_height(chain: &[Block], anchor: &str, height: u64) -> usize { 79 chain 80 .iter() 81 .take_while(|block| block.height <= height) 82 .map(|block| { 83 block 84 .transactions 85 .iter() 86 .filter(|transaction| mine_anchor(transaction) == Some(anchor)) 87 .count() 88 }) 89 .sum() 90 } 91 92 #[cfg(test)] 93 mod tests { 94 use super::*; 95 use crate::domain::MINE_DIFFICULTY_BITS; 96 97 #[test] 98 fn retarget_bounds_match_protocol_constants() { 99 assert_eq!( 100 retarget_mine_difficulty_bits(MINE_DIFFICULTY_BITS, 0), 101 MINE_DIFFICULTY_BITS - MINE_MAX_RETARGET_STEP_BITS 102 ); 103 assert_eq!( 104 retarget_mine_difficulty_bits(1, 0), 105 MINE_MIN_DIFFICULTY_BITS 106 ); 107 } 108 }