iuna

iuna

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

ledger_prepare.rs (8670B)


      1 use anyhow::{Result, bail};
      2 
      3 use super::{
      4     BurnBundle, FinalizerMode, Ledger, PreparedBlock, RECOVERY_BLOCK_DELAY_MS, Wallet,
      5     ensure_block_has_burn, ensure_block_has_burn_from, recovery_vdf_seed_for_child, run_vdf,
      6     ticket_block_min_timestamp, vdf_content_commitment, vdf_seed_for_child,
      7 };
      8 
      9 impl Ledger {
     10     pub fn mine_next_block(&self, wallet: &Wallet, timestamp_ms: u64) -> Result<super::Block> {
     11         let next_height = self.height().saturating_add(1);
     12         let reward_address = (next_height >= super::HYBRID_REWARD_ACTIVATION_HEIGHT)
     13             .then(|| self.wallet_reward_address(wallet, next_height));
     14         let prepared = self.prepare_next_block_with_required_burn_and_burn_bundles(
     15             wallet.address(),
     16             reward_address.as_deref(),
     17             timestamp_ms,
     18             Vec::new(),
     19             None,
     20         )?;
     21         let vdf_output = run_vdf(prepared.vdf_seed(), prepared.vdf_rounds());
     22         Ok(prepared.finish(wallet, vdf_output))
     23     }
     24 
     25     pub fn mine_recovery_block(&self, wallet: &Wallet, timestamp_ms: u64) -> Result<super::Block> {
     26         let next_height = self.height().saturating_add(1);
     27         let reward_address = (next_height >= super::HYBRID_REWARD_ACTIVATION_HEIGHT)
     28             .then(|| self.wallet_reward_address(wallet, next_height));
     29         let prepared = self.prepare_recovery_block_with_required_burn_and_burn_bundles(
     30             wallet.address(),
     31             reward_address.as_deref(),
     32             timestamp_ms,
     33             Vec::new(),
     34             None,
     35         )?;
     36         let vdf_output = run_vdf(prepared.vdf_seed(), prepared.vdf_rounds());
     37         Ok(prepared.finish(wallet, vdf_output))
     38     }
     39 
     40     pub fn prepare_next_block(&self, miner: &str, timestamp_ms: u64) -> Result<PreparedBlock> {
     41         self.prepare_next_block_with_burn_bundles(miner, timestamp_ms, Vec::new())
     42     }
     43 
     44     pub fn prepare_next_block_with_burn_bundles(
     45         &self,
     46         miner: &str,
     47         timestamp_ms: u64,
     48         burn_bundles: Vec<BurnBundle>,
     49     ) -> Result<PreparedBlock> {
     50         self.prepare_next_block_with_required_burn_and_burn_bundles(
     51             miner,
     52             None,
     53             timestamp_ms,
     54             burn_bundles,
     55             None,
     56         )
     57     }
     58 
     59     pub(crate) fn prepare_next_block_with_required_burn_and_burn_bundles(
     60         &self,
     61         miner: &str,
     62         reward_address: Option<&str>,
     63         timestamp_ms: u64,
     64         burn_bundles: Vec<BurnBundle>,
     65         required_burn_signature: Option<&str>,
     66     ) -> Result<PreparedBlock> {
     67         let height = self.tip().height + 1;
     68         self.validate_reward_address(height, reward_address, "finalizer")?;
     69         let Some((finalizer_rank, leader_ticket)) = self.finalizer_ticket_for_miner(height, miner)
     70         else {
     71             bail!("cannot mine block without a mature burn ticket");
     72         };
     73         if self.expected_leader_for_next_block().is_none() {
     74             bail!("no selected leader for block {height}");
     75         }
     76 
     77         let burn_bundles =
     78             self.validate_next_block_burn_bundles_for_finalizer_rank(finalizer_rank, burn_bundles)?;
     79         let burn_bundle_section = self.burn_bundle_section_from_bundles(burn_bundles)?;
     80         let selection = self.select_block_transactions_with_burn_section(
     81             miner,
     82             required_burn_signature,
     83             &burn_bundle_section,
     84         )?;
     85         ensure_block_has_burn(&selection.transactions, &selection.transactions_v2)?;
     86 
     87         let tip = self.tip();
     88         let prev_hash = tip.hash.clone();
     89         let timestamp_ms = timestamp_ms.max(ticket_block_min_timestamp(tip, finalizer_rank)?);
     90         let bundle_hashes = burn_bundle_section.burn_bundle_hashes(height, &prev_hash, miner);
     91         let reward = self.expected_reward_for_next_block(
     92             &selection.transactions,
     93             &selection.transactions_v2,
     94             &burn_bundle_section,
     95         )?;
     96         let vdf_rounds = self.vdf_rounds_for_finalizer_rank(finalizer_rank)?;
     97         let content_commitment = vdf_content_commitment(
     98             height,
     99             &prev_hash,
    100             miner,
    101             reward_address,
    102             FinalizerMode::Ticket,
    103             finalizer_rank,
    104             reward,
    105             vdf_rounds,
    106             Some(&leader_ticket.id),
    107             &burn_bundle_section,
    108             &selection.transactions,
    109             &selection.transactions_v2,
    110         );
    111         let vdf_seed = vdf_seed_for_child(&prev_hash, height, &bundle_hashes, &content_commitment);
    112         Ok(PreparedBlock {
    113             height,
    114             prev_hash,
    115             timestamp_ms,
    116             miner: miner.to_string(),
    117             reward_address: reward_address.map(str::to_string),
    118             finalizer_mode: FinalizerMode::Ticket,
    119             reward,
    120             vdf_rounds,
    121             vdf_seed,
    122             finalizer_rank,
    123             leader_ticket: Some(leader_ticket),
    124             burn_bundle_section,
    125             transactions: selection.transactions,
    126             transactions_v2: selection.transactions_v2,
    127         })
    128     }
    129 
    130     pub fn recovery_block_available_at(&self, timestamp_ms: u64) -> bool {
    131         timestamp_ms >= self.recovery_block_min_timestamp()
    132     }
    133 
    134     pub fn recovery_block_min_timestamp(&self) -> u64 {
    135         self.tip()
    136             .timestamp_ms
    137             .saturating_add(RECOVERY_BLOCK_DELAY_MS)
    138     }
    139 
    140     pub fn prepare_recovery_block(&self, miner: &str, timestamp_ms: u64) -> Result<PreparedBlock> {
    141         self.prepare_recovery_block_with_burn_bundles(miner, timestamp_ms, Vec::new())
    142     }
    143 
    144     pub fn prepare_recovery_block_with_burn_bundles(
    145         &self,
    146         miner: &str,
    147         timestamp_ms: u64,
    148         burn_bundles: Vec<BurnBundle>,
    149     ) -> Result<PreparedBlock> {
    150         self.prepare_recovery_block_with_required_burn_and_burn_bundles(
    151             miner,
    152             None,
    153             timestamp_ms,
    154             burn_bundles,
    155             None,
    156         )
    157     }
    158 
    159     pub(crate) fn prepare_recovery_block_with_required_burn_and_burn_bundles(
    160         &self,
    161         miner: &str,
    162         reward_address: Option<&str>,
    163         timestamp_ms: u64,
    164         burn_bundles: Vec<BurnBundle>,
    165         required_burn_signature: Option<&str>,
    166     ) -> Result<PreparedBlock> {
    167         let height = self.tip().height + 1;
    168         self.validate_reward_address(height, reward_address, "finalizer")?;
    169         let min_timestamp = self.recovery_block_min_timestamp();
    170         if timestamp_ms < min_timestamp {
    171             bail!("recovery block is not available before timestamp {min_timestamp}");
    172         }
    173 
    174         let burn_bundles = self.validate_next_block_burn_bundles(burn_bundles)?;
    175         let burn_bundle_section = self.burn_bundle_section_from_bundles(burn_bundles)?;
    176         let selection = self.select_recovery_block_transactions_with_burn_section(
    177             miner,
    178             required_burn_signature,
    179             &burn_bundle_section,
    180         )?;
    181         ensure_block_has_burn(&selection.transactions, &selection.transactions_v2)?;
    182         ensure_block_has_burn_from(&selection.transactions, &selection.transactions_v2, miner)?;
    183 
    184         let tip = self.tip();
    185         let prev_hash = tip.hash.clone();
    186         let timestamp_ms = timestamp_ms.max(tip.timestamp_ms + 1);
    187         let bundle_hashes = burn_bundle_section.burn_bundle_hashes(height, &prev_hash, miner);
    188         let reward = self.expected_reward_for_next_block(
    189             &selection.transactions,
    190             &selection.transactions_v2,
    191             &burn_bundle_section,
    192         )?;
    193         let vdf_rounds = self.recovery_vdf_rounds()?;
    194         let content_commitment = vdf_content_commitment(
    195             height,
    196             &prev_hash,
    197             miner,
    198             reward_address,
    199             FinalizerMode::Recovery,
    200             0,
    201             reward,
    202             vdf_rounds,
    203             None,
    204             &burn_bundle_section,
    205             &selection.transactions,
    206             &selection.transactions_v2,
    207         );
    208         let vdf_seed = recovery_vdf_seed_for_child(
    209             &prev_hash,
    210             height,
    211             timestamp_ms,
    212             &bundle_hashes,
    213             &content_commitment,
    214         );
    215         Ok(PreparedBlock {
    216             height,
    217             prev_hash,
    218             timestamp_ms,
    219             miner: miner.to_string(),
    220             reward_address: reward_address.map(str::to_string),
    221             finalizer_mode: FinalizerMode::Recovery,
    222             finalizer_rank: 0,
    223             reward,
    224             vdf_rounds,
    225             vdf_seed,
    226             leader_ticket: None,
    227             burn_bundle_section,
    228             transactions: selection.transactions,
    229             transactions_v2: selection.transactions_v2,
    230         })
    231     }
    232 }