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 }