iuna

iuna - experimental devnet protocol
git clone https://iuna.jhx.app/git/iuna.git
Log | Files | Refs | README | LICENSE

ledger_consensus.rs (4389B)


      1 use anyhow::Result;
      2 
      3 use super::mine_policy::{MINE_RETARGET_WINDOW_BLOCKS, retarget_mine_difficulty_bits};
      4 use super::ticket::{
      5     BurnTicket, base_vdf_rounds_for_finalizer_rank, mine_action_count, ranked_tickets_for_height,
      6     vdf_rounds_for_finalizer_rank,
      7 };
      8 use super::vdf::{VDF_RETARGET_WINDOW_BLOCKS, retarget_vdf_rounds, vdf_retarget_observed_block_ms};
      9 use super::{Block, FinalizerMode, Ledger};
     10 
     11 impl Ledger {
     12     pub(super) fn next_vdf_rounds_after_tip(&self) -> u64 {
     13         let Some(tip) = self.chain.last() else {
     14             return self.vdf_rounds;
     15         };
     16         if tip.height < 2 {
     17             return self.vdf_rounds;
     18         }
     19 
     20         let mut total_observed_ms = 0_u128;
     21         let mut observed_blocks = 0_u128;
     22         for pair in self
     23             .chain
     24             .windows(2)
     25             .rev()
     26             .filter(|pair| pair[0].height > 0)
     27             .take(VDF_RETARGET_WINDOW_BLOCKS)
     28         {
     29             let Some(observed_ms) = vdf_retarget_observed_block_ms(&pair[0], &pair[1]) else {
     30                 continue;
     31             };
     32             total_observed_ms += u128::from(observed_ms);
     33             observed_blocks += 1;
     34         }
     35         if observed_blocks == 0 {
     36             return self.vdf_rounds;
     37         }
     38 
     39         let average_observed_ms = (total_observed_ms / observed_blocks) as u64;
     40         let base_rounds = base_vdf_rounds_for_finalizer_rank(tip.vdf_rounds, tip.finalizer_rank);
     41         retarget_vdf_rounds(base_rounds, average_observed_ms)
     42     }
     43 
     44     pub fn expected_leader_for_next_block(&self) -> Option<String> {
     45         self.selected_ticket_for_height(self.tip().height + 1)
     46             .map(|ticket| ticket.owner)
     47     }
     48 
     49     pub fn finalizer_rank_for_next_block(&self, miner: &str) -> Option<u32> {
     50         self.finalizer_ticket_for_miner(self.tip().height + 1, miner)
     51             .map(|(rank, _)| rank)
     52     }
     53 
     54     pub fn finalizer_rank_count_for_next_block(&self) -> usize {
     55         ranked_tickets_for_height(self.tip(), self.tip().height + 1, &self.tickets).len()
     56     }
     57 
     58     pub(super) fn selected_ticket_for_height(&self, height: u64) -> Option<BurnTicket> {
     59         self.ticket_for_finalizer_rank(height, 0)
     60     }
     61 
     62     pub(super) fn ticket_for_finalizer_rank(&self, height: u64, rank: u32) -> Option<BurnTicket> {
     63         ranked_tickets_for_height(self.tip(), height, &self.tickets)
     64             .get(rank as usize)
     65             .cloned()
     66     }
     67 
     68     pub(super) fn finalizer_ticket_for_miner(
     69         &self,
     70         height: u64,
     71         miner: &str,
     72     ) -> Option<(u32, BurnTicket)> {
     73         ranked_tickets_for_height(self.tip(), height, &self.tickets)
     74             .into_iter()
     75             .enumerate()
     76             .find(|(_, ticket)| ticket.owner == miner)
     77             .and_then(|(rank, ticket)| {
     78                 let rank = u32::try_from(rank).ok()?;
     79                 Some((rank, ticket))
     80             })
     81     }
     82 
     83     pub(super) fn vdf_rounds_for_finalizer_rank(&self, rank: u32) -> Result<u64> {
     84         vdf_rounds_for_finalizer_rank(self.vdf_rounds, rank)
     85     }
     86 
     87     pub(super) fn recovery_vdf_rounds(&self) -> Result<u64> {
     88         vdf_rounds_for_finalizer_rank(self.vdf_rounds, 0)
     89     }
     90 
     91     pub(super) fn expected_vdf_rounds_for_block(&self, block: &Block) -> Result<u64> {
     92         match block.finalizer_mode {
     93             FinalizerMode::Ticket => self.vdf_rounds_for_finalizer_rank(block.finalizer_rank),
     94             FinalizerMode::Recovery => self.recovery_vdf_rounds(),
     95         }
     96     }
     97 
     98     pub(super) fn mine_difficulty_bits_for_anchor_height(&self, anchor_height: u64) -> u32 {
     99         let mut difficulty = self.launch_profile.mine_difficulty_bits;
    100         let mut window_end = MINE_RETARGET_WINDOW_BLOCKS;
    101         while window_end <= anchor_height {
    102             let window_start = window_end + 1 - MINE_RETARGET_WINDOW_BLOCKS;
    103             let mine_actions = self
    104                 .chain
    105                 .iter()
    106                 .filter(|block| window_start <= block.height && block.height <= window_end)
    107                 .map(mine_action_count)
    108                 .sum::<u64>();
    109             difficulty = retarget_mine_difficulty_bits(difficulty, mine_actions);
    110             window_end = window_end.saturating_add(MINE_RETARGET_WINDOW_BLOCKS);
    111         }
    112         difficulty
    113     }
    114 
    115     pub(super) fn tip(&self) -> &Block {
    116         self.chain
    117             .last()
    118             .expect("ledger is always initialized with genesis")
    119     }
    120 }