ledger_prepare.rs (6183B)
1 use anyhow::{Result, bail}; 2 3 use super::{ 4 FinalizerMode, Ledger, PreparedBlock, RECOVERY_BLOCK_DELAY_MS, RevealBundle, Wallet, 5 ensure_block_has_burn, ensure_block_has_burn_from, recovery_vdf_seed_for_child, run_vdf, 6 ticket_block_min_timestamp, 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 prepared = self.prepare_next_block(wallet.address(), timestamp_ms)?; 12 let vdf_output = run_vdf(prepared.vdf_seed(), prepared.vdf_rounds()); 13 Ok(prepared.finish(wallet, vdf_output)) 14 } 15 16 pub fn mine_recovery_block(&self, wallet: &Wallet, timestamp_ms: u64) -> Result<super::Block> { 17 let prepared = self.prepare_recovery_block(wallet.address(), timestamp_ms)?; 18 let vdf_output = run_vdf(prepared.vdf_seed(), prepared.vdf_rounds()); 19 Ok(prepared.finish(wallet, vdf_output)) 20 } 21 22 pub fn prepare_next_block(&self, miner: &str, timestamp_ms: u64) -> Result<PreparedBlock> { 23 self.prepare_next_block_with_reveal_bundles(miner, timestamp_ms, Vec::new()) 24 } 25 26 pub fn prepare_next_block_with_reveal_bundles( 27 &self, 28 miner: &str, 29 timestamp_ms: u64, 30 reveal_bundles: Vec<RevealBundle>, 31 ) -> Result<PreparedBlock> { 32 self.prepare_next_block_with_required_burn_and_reveal_bundles( 33 miner, 34 timestamp_ms, 35 reveal_bundles, 36 None, 37 ) 38 } 39 40 pub(crate) fn prepare_next_block_with_required_burn_and_reveal_bundles( 41 &self, 42 miner: &str, 43 timestamp_ms: u64, 44 reveal_bundles: Vec<RevealBundle>, 45 required_burn_signature: Option<&str>, 46 ) -> Result<PreparedBlock> { 47 let height = self.tip().height + 1; 48 let Some((finalizer_rank, leader_ticket)) = self.finalizer_ticket_for_miner(height, miner) 49 else { 50 bail!("cannot mine block without a mature burn ticket"); 51 }; 52 if self.expected_leader_for_next_block().is_none() { 53 bail!("no selected leader for block {height}"); 54 } 55 56 let reveal_bundles = self.validate_next_block_reveal_bundles(reveal_bundles)?; 57 let reveal_bundle_section = self.reveal_bundle_section_from_bundles(reveal_bundles); 58 let selection = self.select_block_transactions(required_burn_signature)?; 59 ensure_block_has_burn(&selection.transactions)?; 60 61 let tip = self.tip(); 62 let prev_hash = tip.hash.clone(); 63 let timestamp_ms = timestamp_ms.max(ticket_block_min_timestamp(tip, finalizer_rank)?); 64 let bundle_hashes = reveal_bundle_section.reveal_bundle_hashes(height, &prev_hash); 65 let vdf_seed = vdf_seed_for_child(&prev_hash, height, &bundle_hashes); 66 Ok(PreparedBlock { 67 height, 68 prev_hash, 69 timestamp_ms, 70 miner: miner.to_string(), 71 finalizer_mode: FinalizerMode::Ticket, 72 reward: self 73 .expected_reward_for_next_block(&selection.transactions, &reveal_bundle_section)?, 74 vdf_rounds: self.vdf_rounds_for_finalizer_rank(finalizer_rank)?, 75 vdf_seed, 76 finalizer_rank, 77 leader_ticket: Some(leader_ticket), 78 blinded_transactions: selection.blinded_transactions, 79 reveal_bundle_section, 80 transactions: selection.transactions, 81 }) 82 } 83 84 pub fn recovery_block_available_at(&self, timestamp_ms: u64) -> bool { 85 timestamp_ms >= self.recovery_block_min_timestamp() 86 } 87 88 pub fn recovery_block_min_timestamp(&self) -> u64 { 89 self.tip() 90 .timestamp_ms 91 .saturating_add(RECOVERY_BLOCK_DELAY_MS) 92 } 93 94 pub fn prepare_recovery_block(&self, miner: &str, timestamp_ms: u64) -> Result<PreparedBlock> { 95 self.prepare_recovery_block_with_reveal_bundles(miner, timestamp_ms, Vec::new()) 96 } 97 98 pub fn prepare_recovery_block_with_reveal_bundles( 99 &self, 100 miner: &str, 101 timestamp_ms: u64, 102 reveal_bundles: Vec<RevealBundle>, 103 ) -> Result<PreparedBlock> { 104 self.prepare_recovery_block_with_required_burn_and_reveal_bundles( 105 miner, 106 timestamp_ms, 107 reveal_bundles, 108 None, 109 ) 110 } 111 112 pub(crate) fn prepare_recovery_block_with_required_burn_and_reveal_bundles( 113 &self, 114 miner: &str, 115 timestamp_ms: u64, 116 reveal_bundles: Vec<RevealBundle>, 117 required_burn_signature: Option<&str>, 118 ) -> Result<PreparedBlock> { 119 let height = self.tip().height + 1; 120 let min_timestamp = self.recovery_block_min_timestamp(); 121 if timestamp_ms < min_timestamp { 122 bail!("recovery block is not available before timestamp {min_timestamp}"); 123 } 124 125 let reveal_bundles = self.validate_next_block_reveal_bundles(reveal_bundles)?; 126 let reveal_bundle_section = self.reveal_bundle_section_from_bundles(reveal_bundles); 127 let selection = self.select_recovery_block_transactions(miner, required_burn_signature)?; 128 ensure_block_has_burn(&selection.transactions)?; 129 ensure_block_has_burn_from(&selection.transactions, miner)?; 130 131 let tip = self.tip(); 132 let prev_hash = tip.hash.clone(); 133 let timestamp_ms = timestamp_ms.max(tip.timestamp_ms + 1); 134 let bundle_hashes = reveal_bundle_section.reveal_bundle_hashes(height, &prev_hash); 135 let vdf_seed = 136 recovery_vdf_seed_for_child(&prev_hash, height, timestamp_ms, &bundle_hashes); 137 Ok(PreparedBlock { 138 height, 139 prev_hash, 140 timestamp_ms, 141 miner: miner.to_string(), 142 finalizer_mode: FinalizerMode::Recovery, 143 finalizer_rank: 0, 144 reward: self 145 .expected_reward_for_next_block(&selection.transactions, &reveal_bundle_section)?, 146 vdf_rounds: self.recovery_vdf_rounds()?, 147 vdf_seed, 148 leader_ticket: None, 149 blinded_transactions: selection.blinded_transactions, 150 reveal_bundle_section, 151 transactions: selection.transactions, 152 }) 153 } 154 }