iuna

iuna

iuna - experimental mainnet-candidate protocol
git clone https://getiuna.org/git/iuna.git
Log | Files | Refs | README | LICENSE

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 }