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