iuna

iuna

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

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 }