mine_policy.rs (4707B)
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(crate) 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 14 pub(crate) fn retarget_mine_difficulty_bits(current: u32, mine_actions: u64) -> u32 { 15 let target = MINE_RETARGET_WINDOW_BLOCKS.saturating_mul(MINE_TARGET_ACTIONS_PER_BLOCK); 16 if target == 0 || mine_actions == target { 17 return current.max(MINE_MIN_DIFFICULTY_BITS); 18 } 19 20 let step = if mine_actions > target { 21 floor_log2_ratio(mine_actions, target).min(MINE_MAX_RETARGET_STEP_BITS) 22 } else if mine_actions == 0 { 23 MINE_MAX_RETARGET_STEP_BITS 24 } else { 25 floor_log2_ratio(target, mine_actions).min(MINE_MAX_RETARGET_STEP_BITS) 26 }; 27 28 if step == 0 { 29 return current.max(MINE_MIN_DIFFICULTY_BITS); 30 } 31 if mine_actions > target { 32 current.saturating_add(step).max(MINE_MIN_DIFFICULTY_BITS) 33 } else { 34 current.saturating_sub(step).max(MINE_MIN_DIFFICULTY_BITS) 35 } 36 } 37 38 fn floor_log2_ratio(numerator: u64, denominator: u64) -> u32 { 39 if denominator == 0 || numerator <= denominator { 40 return 0; 41 } 42 let mut step = 0_u32; 43 let mut threshold = denominator; 44 while threshold <= numerator / 2 { 45 threshold = threshold.saturating_mul(2); 46 step = step.saturating_add(1); 47 } 48 step 49 } 50 51 pub(super) fn ensure_mine_anchor_limit(_height: u64, transactions: &[Transaction]) -> Result<()> { 52 let mut anchor_counts = BTreeMap::new(); 53 for transaction in transactions { 54 let Some(anchor) = mine_anchor(transaction) else { 55 continue; 56 }; 57 let count = anchor_counts.entry(anchor).or_insert(0usize); 58 *count += 1; 59 if *count > MINE_ACTIONS_PER_ANCHOR_LIMIT { 60 bail!("block exceeds mine actions per anchor limit"); 61 } 62 } 63 Ok(()) 64 } 65 66 pub(super) fn mine_anchor(transaction: &Transaction) -> Option<&str> { 67 match transaction { 68 Transaction::Mine { anchor, .. } => Some(anchor.as_str()), 69 _ => None, 70 } 71 } 72 73 pub(super) fn mine_anchor_count_before_height(chain: &[Block], anchor: &str, height: u64) -> usize { 74 chain 75 .iter() 76 .take_while(|block| block.height <= height) 77 .map(|block| { 78 block 79 .transactions 80 .iter() 81 .filter(|transaction| mine_anchor(transaction) == Some(anchor)) 82 .count() 83 }) 84 .sum() 85 } 86 87 #[cfg(test)] 88 mod tests { 89 use super::*; 90 use crate::domain::MINE_DIFFICULTY_BITS; 91 92 #[test] 93 fn retarget_keeps_protocol_minimum() { 94 assert_eq!( 95 retarget_mine_difficulty_bits(MINE_DIFFICULTY_BITS, 0), 96 MINE_DIFFICULTY_BITS - MINE_MAX_RETARGET_STEP_BITS 97 ); 98 assert_eq!( 99 retarget_mine_difficulty_bits(1, 0), 100 MINE_MIN_DIFFICULTY_BITS 101 ); 102 } 103 104 #[test] 105 fn retarget_does_not_cap_difficulty_at_32_bits() { 106 assert_eq!(retarget_mine_difficulty_bits(33, 20), 34); 107 assert_eq!(retarget_mine_difficulty_bits(40, 10), 40); 108 } 109 110 #[test] 111 fn pow_retarget_parameters_and_action_targets_match_the_protocol() { 112 assert_eq!(MINE_RETARGET_WINDOW_BLOCKS, 10); 113 assert_eq!(MINE_TARGET_ACTIONS_PER_BLOCK, 1); 114 assert_eq!(MINE_MAX_RETARGET_STEP_BITS, 2); 115 assert_eq!(MINE_MIN_DIFFICULTY_BITS, 10); 116 assert_eq!(MINE_MAX_ANCHOR_AGE_BLOCKS, 10); 117 118 assert_eq!(retarget_mine_difficulty_bits(12, 0), 10); 119 assert_eq!(retarget_mine_difficulty_bits(12, 5), 11); 120 assert_eq!(retarget_mine_difficulty_bits(12, 9), 12); 121 assert_eq!(retarget_mine_difficulty_bits(12, 10), 12); 122 assert_eq!(retarget_mine_difficulty_bits(12, 20), 13); 123 assert_eq!(retarget_mine_difficulty_bits(12, 40), 14); 124 assert_eq!(retarget_mine_difficulty_bits(12, 1_000), 14); 125 } 126 127 #[test] 128 fn block_rejects_a_third_mine_action_for_the_same_anchor() { 129 let mine = |signature: &str| Transaction::Mine { 130 recipient: "recipient".to_string(), 131 anchor: "a".repeat(64), 132 salt: 1, 133 nonce: 1, 134 difficulty_bits: 10, 135 proof_header: None, 136 signature: signature.repeat(64), 137 }; 138 139 assert!(ensure_mine_anchor_limit(1, &[mine("1"), mine("2")]).is_ok()); 140 assert!(ensure_mine_anchor_limit(1, &[mine("1"), mine("2"), mine("3")]).is_err()); 141 } 142 }