iuna

iuna

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

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 }