ledger_queries.rs (14777B)
1 use std::collections::{BTreeMap, BTreeSet}; 2 3 use anyhow::{Context, Result, bail}; 4 5 use super::blinded::{ 6 ActiveBlindedTransaction, blinded_envelope_fee_for_transaction, decrypt_blinded_transaction, 7 }; 8 use super::genesis::balances_from_utxos; 9 use super::mine_policy::mine_anchor; 10 use super::ticket::{ 11 BurnTicket, apply_finalizer_ticket_effects, genesis_tickets, ranked_tickets_for_height, 12 tickets_created_by_block, tickets_created_by_transactions, 13 }; 14 use super::{ 15 Amount, BlindedReveal, BlindedTransaction, Block, BurnLeaderRank, ChainSnapshot, ChainStatus, 16 LaunchProfile, Ledger, OutPoint, RevealCommitteeMember, RevealedBlindedTransaction, 17 Transaction, TxOutput, reveal_committee_slot_count, 18 }; 19 20 fn apply_historical_ticket_block( 21 parent: &Block, 22 block: &Block, 23 launch_profile: &LaunchProfile, 24 tickets: &mut Vec<BurnTicket>, 25 active_blinded: &mut BTreeMap<String, ActiveBlindedTransaction>, 26 ) -> Result<()> { 27 apply_finalizer_ticket_effects(parent, block, tickets)?; 28 tickets.extend(tickets_created_by_block(block, launch_profile)?); 29 let mut revealed_transactions = Vec::new(); 30 for reveal in block.all_blinded_reveals() { 31 let active = active_blinded.get(&reveal.commitment).with_context(|| { 32 format!( 33 "block {} reveals unknown blinded transaction {}", 34 block.height, reveal.commitment 35 ) 36 })?; 37 let transaction = decrypt_blinded_transaction(&active.transaction, reveal)?; 38 if matches!(transaction, Transaction::Mine { .. }) { 39 bail!("mine actions are public and cannot be blinded"); 40 } 41 if blinded_envelope_fee_for_transaction(&transaction) != active.transaction.fee { 42 bail!( 43 "block {} blinded reveal fee does not match envelope", 44 block.height 45 ); 46 } 47 revealed_transactions.push(transaction); 48 active_blinded.remove(&reveal.commitment); 49 } 50 tickets.extend(tickets_created_by_transactions( 51 block.height, 52 &revealed_transactions, 53 launch_profile, 54 )?); 55 active_blinded.retain(|_, active| block.height < active.transaction.expires_at_height); 56 for transaction in &block.blinded_transactions { 57 active_blinded.insert( 58 transaction.commitment.clone(), 59 ActiveBlindedTransaction { 60 transaction: transaction.clone(), 61 locked_outputs: Vec::new(), 62 included_height: block.height, 63 included_by: block.miner.clone(), 64 }, 65 ); 66 } 67 Ok(()) 68 } 69 70 impl Ledger { 71 pub fn snapshot(&self) -> ChainSnapshot { 72 ChainSnapshot { 73 genesis_allocations: self.genesis_allocations.clone(), 74 vdf_rounds: self.initial_vdf_rounds, 75 launch_profile: self.launch_profile.clone(), 76 blocks: self.chain.clone(), 77 } 78 } 79 80 pub fn status(&self) -> ChainStatus { 81 self.status_with_balances(true) 82 } 83 84 pub fn light_status(&self) -> ChainStatus { 85 self.status_with_balances(false) 86 } 87 88 fn status_with_balances(&self, include_balances: bool) -> ChainStatus { 89 ChainStatus { 90 height: self.tip().height, 91 tip_hash: self.tip().hash.clone(), 92 next_leader: self.expected_leader_for_next_block(), 93 launch_profile_hash: self.launch_profile.hash(), 94 mine_reward: self.mine_reward, 95 current_mine_difficulty_bits: self.current_mine_difficulty_bits(), 96 balances: include_balances 97 .then(|| balances_from_utxos(&self.utxos)) 98 .unwrap_or_default(), 99 pending_transactions: self.pending.len() 100 + self.pending_blinded.len() 101 + self.pending_reveals.len(), 102 } 103 } 104 105 pub fn tip_hash(&self) -> &str { 106 &self.tip().hash 107 } 108 109 pub fn chain(&self) -> &[Block] { 110 &self.chain 111 } 112 113 pub fn burn_leader_ranks_for_block(&self, height: u64) -> Result<Vec<BurnLeaderRank>> { 114 Ok(self 115 .burn_leader_ranks_for_blocks([height])? 116 .remove(&height) 117 .unwrap_or_default()) 118 } 119 120 pub fn burn_leader_ranks_for_blocks<I>( 121 &self, 122 heights: I, 123 ) -> Result<BTreeMap<u64, Vec<BurnLeaderRank>>> 124 where 125 I: IntoIterator<Item = u64>, 126 { 127 let mut requested = heights.into_iter().collect::<BTreeSet<_>>(); 128 let mut ranks_by_height = BTreeMap::new(); 129 if requested.remove(&0) { 130 ranks_by_height.insert(0, Vec::new()); 131 } 132 if requested.is_empty() { 133 return Ok(ranks_by_height); 134 } 135 136 let mut tickets = genesis_tickets( 137 &self.genesis_allocations, 138 &self.chain[0], 139 &self.launch_profile, 140 )?; 141 let mut active_blinded = BTreeMap::<String, ActiveBlindedTransaction>::new(); 142 let mut next_block_index = 1; 143 144 for height in requested { 145 let parent_index = height.checked_sub(1).context("block height underflows")? as usize; 146 let parent = self 147 .chain 148 .get(parent_index) 149 .with_context(|| format!("missing parent block for height {height}"))?; 150 while let Some(block) = self.chain.get(next_block_index) { 151 if block.height >= height { 152 break; 153 } 154 let block_parent = self 155 .chain 156 .get(next_block_index - 1) 157 .with_context(|| format!("missing parent block for height {}", block.height))?; 158 apply_historical_ticket_block( 159 block_parent, 160 block, 161 &self.launch_profile, 162 &mut tickets, 163 &mut active_blinded, 164 )?; 165 next_block_index += 1; 166 } 167 168 ranks_by_height.insert( 169 height, 170 ranked_tickets_for_height(parent, height, &tickets) 171 .into_iter() 172 .enumerate() 173 .map(|(rank, ticket)| BurnLeaderRank { 174 rank: rank as u32, 175 ticket_id: ticket.id, 176 owner: ticket.owner, 177 amount: ticket.amount, 178 eligible_from_height: ticket.eligible_from_height, 179 eligible_until_height: ticket.eligible_until_height, 180 }) 181 .collect(), 182 ); 183 } 184 185 Ok(ranks_by_height) 186 } 187 188 pub fn reveal_committee_for_next_block(&self) -> Vec<RevealCommitteeMember> { 189 self.reveal_committee_for_height(self.tip().height + 1) 190 } 191 192 pub fn reveal_committee_for_height(&self, height: u64) -> Vec<RevealCommitteeMember> { 193 let ranked = ranked_tickets_for_height(self.tip(), height, &self.tickets); 194 let mut selected = Vec::new(); 195 if !ranked.is_empty() { 196 selected.push(0); 197 } 198 for index in (0..ranked.len()).rev() { 199 if selected.len() >= reveal_committee_slot_count(ranked.len()) { 200 break; 201 } 202 if !selected.contains(&index) { 203 selected.push(index); 204 } 205 } 206 selected 207 .into_iter() 208 .enumerate() 209 .filter_map(|(slot, rank)| { 210 let ticket = ranked.get(rank)?.clone(); 211 Some(RevealCommitteeMember { 212 slot: u8::try_from(slot).ok()?, 213 rank: u32::try_from(rank).ok()?, 214 ticket_id: ticket.id, 215 owner: ticket.owner, 216 amount: ticket.amount, 217 }) 218 }) 219 .collect() 220 } 221 222 pub fn genesis_hash(&self) -> &str { 223 &self.chain[0].hash 224 } 225 226 pub fn is_setup_placeholder(&self) -> bool { 227 self.height() == 0 228 && self.genesis_allocations.is_empty() 229 && self.chain[0].transactions.is_empty() 230 && self.pending.is_empty() 231 } 232 233 pub fn height(&self) -> u64 { 234 self.tip().height 235 } 236 237 pub fn recent_blocks(&self, limit: usize) -> Vec<Block> { 238 self.chain.iter().rev().take(limit).cloned().collect() 239 } 240 241 pub fn blocks_before(&self, before_height: u64, limit: usize) -> Vec<Block> { 242 self.chain 243 .iter() 244 .rev() 245 .filter(|block| block.height < before_height) 246 .take(limit) 247 .cloned() 248 .collect() 249 } 250 251 pub fn blocks_from(&self, from_height: u64, limit: usize) -> Vec<Block> { 252 if limit == 0 { 253 return Vec::new(); 254 } 255 self.chain 256 .iter() 257 .filter(|block| block.height >= from_height) 258 .take(limit) 259 .cloned() 260 .collect() 261 } 262 263 pub fn block_by_hash(&self, hash: &str) -> Option<Block> { 264 self.chain.iter().find(|block| block.hash == hash).cloned() 265 } 266 267 pub fn has_block(&self, hash: &str) -> bool { 268 self.chain.iter().any(|block| block.hash == hash) 269 } 270 271 pub fn pending(&self) -> &[Transaction] { 272 &self.pending 273 } 274 275 pub fn pending_blinded_transactions(&self) -> &[BlindedTransaction] { 276 &self.pending_blinded 277 } 278 279 pub fn pending_blinded_reveals(&self) -> &[BlindedReveal] { 280 &self.pending_reveals 281 } 282 283 pub fn pending_revealed_blinded_transactions(&self) -> Vec<RevealedBlindedTransaction> { 284 self.pending_reveals 285 .iter() 286 .filter_map(|reveal| { 287 let active = self.active_blinded.get(&reveal.commitment)?; 288 let transaction = self.pending_reveal_transaction(reveal).ok()?; 289 Some(RevealedBlindedTransaction { 290 height: self.height().saturating_add(1), 291 commitment: reveal.commitment.clone(), 292 included_by: active.included_by.clone(), 293 transaction, 294 }) 295 }) 296 .collect() 297 } 298 299 pub(crate) fn drop_pending_blinded_conflicting_with_transaction( 300 &mut self, 301 transaction: &Transaction, 302 ) { 303 let spent = transaction 304 .inputs() 305 .iter() 306 .map(|input| input.outpoint.clone()) 307 .collect::<BTreeSet<_>>(); 308 self.pending_blinded.retain(|blinded| { 309 !blinded 310 .inputs 311 .iter() 312 .any(|input| spent.contains(&input.outpoint)) 313 }); 314 } 315 316 pub(crate) fn clear_pending_blinded_transactions(&mut self) { 317 self.pending_blinded.clear(); 318 } 319 320 pub(crate) fn clear_pending_transactions(&mut self) { 321 self.pending.clear(); 322 } 323 324 pub fn orphan_transactions(&self) -> &[Transaction] { 325 &self.orphans 326 } 327 328 pub fn transaction_by_signature(&self, signature: &str) -> Option<Transaction> { 329 self.pending 330 .iter() 331 .chain(self.orphans.iter()) 332 .chain( 333 self.chain 334 .iter() 335 .flat_map(|block| block.transactions.iter()), 336 ) 337 .find(|tx| tx.signature() == signature) 338 .cloned() 339 } 340 341 pub fn has_transaction(&self, signature: &str) -> bool { 342 self.transaction_by_signature(signature).is_some() 343 } 344 345 pub fn pending_mine_count_for_anchor(&self, anchor: &str) -> usize { 346 self.pending 347 .iter() 348 .filter(|tx| mine_anchor(tx) == Some(anchor)) 349 .count() 350 } 351 352 pub fn has_blinded_transaction(&self, commitment: &str) -> bool { 353 self.pending_blinded 354 .iter() 355 .any(|transaction| transaction.commitment == commitment) 356 || self.active_blinded.contains_key(commitment) 357 || self.chain.iter().any(|block| { 358 block 359 .blinded_transactions 360 .iter() 361 .any(|tx| tx.commitment == commitment) 362 }) 363 } 364 365 pub fn has_unrevealed_blinded_transaction(&self, commitment: &str) -> bool { 366 self.pending_blinded 367 .iter() 368 .any(|transaction| transaction.commitment == commitment) 369 || self.active_blinded.contains_key(commitment) 370 } 371 372 pub fn has_active_blinded_transaction(&self, commitment: &str) -> bool { 373 self.active_blinded.contains_key(commitment) 374 } 375 376 pub fn has_blinded_reveal(&self, commitment: &str) -> bool { 377 self.pending_reveals 378 .iter() 379 .any(|reveal| reveal.commitment == commitment) 380 || self.chain.iter().any(|block| { 381 block 382 .all_blinded_reveals() 383 .iter() 384 .any(|reveal| reveal.commitment == commitment) 385 }) 386 } 387 388 pub fn vdf_rounds(&self) -> u64 { 389 self.vdf_rounds 390 } 391 392 pub fn launch_profile(&self) -> &LaunchProfile { 393 &self.launch_profile 394 } 395 396 pub fn current_mine_difficulty_bits(&self) -> u32 { 397 self.mine_difficulty_bits_for_anchor_height(self.tip().height) 398 } 399 400 pub fn mine_difficulty_bits_at_height(&self, height: u64) -> u32 { 401 self.mine_difficulty_bits_for_anchor_height(height.min(self.tip().height)) 402 } 403 404 pub fn balance_of(&self, address: &str) -> Amount { 405 self.utxos 406 .values() 407 .filter(|output| output.address == address) 408 .map(|output| output.amount) 409 .sum() 410 } 411 412 pub fn utxos_for_address(&self, address: &str) -> Vec<(OutPoint, TxOutput)> { 413 self.utxos 414 .iter() 415 .filter(|(_, output)| output.address == address) 416 .map(|(outpoint, output)| (outpoint.clone(), output.clone())) 417 .collect() 418 } 419 420 pub fn all_utxos(&self) -> Vec<(OutPoint, TxOutput)> { 421 self.utxos 422 .iter() 423 .map(|(outpoint, output)| (outpoint.clone(), output.clone())) 424 .collect() 425 } 426 427 pub fn available_utxos_for_address(&self, address: &str) -> Result<Vec<(OutPoint, TxOutput)>> { 428 Ok(self 429 .utxos_after_spendable_pending()? 430 .into_iter() 431 .filter(|(_, output)| output.address == address) 432 .collect()) 433 } 434 435 pub fn next_nonce(&self, address: &str) -> u64 { 436 self.utxos 437 .keys() 438 .chain( 439 self.pending 440 .iter() 441 .flat_map(|tx| tx.inputs().iter().map(|input| &input.outpoint)), 442 ) 443 .filter(|outpoint| outpoint.txid.contains(address)) 444 .count() as u64 445 + 1 446 } 447 }