iuna

iuna

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

ui_index.rs (6538B)


      1 use std::collections::BTreeMap;
      2 
      3 use crate::domain::{
      4     AddressNetwork, Block, BurnCommitteeMember, BurnLeaderRank, ChainSnapshot, Ledger, OutPoint,
      5     Transaction, TransactionV2, TxOutput, decode_hex, encode_versioned_address,
      6     genesis_allocation_outpoint, hex_encode, reward_outputs_for_block,
      7 };
      8 
      9 #[derive(Clone, Debug, Default, Eq, PartialEq)]
     10 pub(crate) struct UiChainIndex {
     11     pub(crate) tip_hash: Option<String>,
     12     pub(crate) outputs: BTreeMap<OutPoint, TxOutput>,
     13     pub(crate) burn_leader_ranks_by_hash: BTreeMap<String, Vec<BurnLeaderRank>>,
     14 }
     15 
     16 #[cfg(test)]
     17 pub(crate) fn build_ui_chain_index(snapshot: &ChainSnapshot) -> UiChainIndex {
     18     let Ok(ledger) = Ledger::from_preverified_snapshot(snapshot.clone()) else {
     19         return UiChainIndex::default();
     20     };
     21     build_ui_chain_index_for_blocks(snapshot, &ledger, &snapshot.blocks, true)
     22 }
     23 
     24 pub(crate) fn build_ui_chain_index_for_blocks(
     25     snapshot: &ChainSnapshot,
     26     ledger: &Ledger,
     27     blocks: &[Block],
     28     include_genesis_allocations: bool,
     29 ) -> UiChainIndex {
     30     UiChainIndex {
     31         tip_hash: snapshot.blocks.last().map(|block| block.hash.clone()),
     32         outputs: known_chain_output_index(snapshot, blocks, include_genesis_allocations),
     33         burn_leader_ranks_by_hash: burn_leader_ranks_for_blocks(ledger, blocks),
     34     }
     35 }
     36 
     37 pub(crate) fn burn_leader_ranks_for_blocks(
     38     ledger: &Ledger,
     39     blocks: &[Block],
     40 ) -> BTreeMap<String, Vec<BurnLeaderRank>> {
     41     let Ok(ranks_by_height) =
     42         ledger.burn_leader_ranks_for_blocks(blocks.iter().map(|block| block.height))
     43     else {
     44         return BTreeMap::new();
     45     };
     46 
     47     blocks
     48         .iter()
     49         .filter_map(|block| {
     50             ranks_by_height
     51                 .get(&block.height)
     52                 .cloned()
     53                 .map(|ranks| (block.hash.clone(), ranks))
     54         })
     55         .collect()
     56 }
     57 
     58 fn known_chain_output_index(
     59     snapshot: &ChainSnapshot,
     60     blocks: &[Block],
     61     include_genesis_allocations: bool,
     62 ) -> BTreeMap<OutPoint, TxOutput> {
     63     let mut outputs = BTreeMap::new();
     64     let network = AddressNetwork::from_profile_id(&snapshot.launch_profile.profile_id);
     65     if include_genesis_allocations {
     66         for (address, amount) in &snapshot.genesis_allocations {
     67             if *amount == 0 {
     68                 continue;
     69             }
     70             outputs.insert(
     71                 genesis_allocation_outpoint(address),
     72                 TxOutput {
     73                     address: address.clone(),
     74                     amount: *amount,
     75                 },
     76             );
     77         }
     78     }
     79     for block in blocks {
     80         for transaction in &block.transactions {
     81             index_transaction_outputs(&mut outputs, transaction);
     82         }
     83         for envelope in &block.transactions_v2 {
     84             let Ok(bytes) = decode_hex(envelope) else {
     85                 continue;
     86             };
     87             let Ok((domain, transaction)) = TransactionV2::decode(&bytes) else {
     88                 continue;
     89             };
     90             index_transaction_v2_outputs(&mut outputs, &transaction, &domain, network);
     91         }
     92         for (outpoint, output) in projected_reward_outputs(block) {
     93             outputs.insert(outpoint, output);
     94         }
     95     }
     96     outputs
     97 }
     98 
     99 pub(crate) fn projected_reward_outputs(block: &Block) -> Vec<(OutPoint, TxOutput)> {
    100     let committee = block
    101         .burn_bundle_section
    102         .signatures
    103         .iter()
    104         .map(|signature| BurnCommitteeMember {
    105             slot: signature.slot,
    106             root: block.hash.clone(),
    107             owner: signature.member.clone(),
    108             weight: 0,
    109         })
    110         .collect::<Vec<_>>();
    111     reward_outputs_for_block(block, &committee)
    112 }
    113 
    114 fn index_transaction_v2_outputs(
    115     outputs: &mut BTreeMap<OutPoint, TxOutput>,
    116     transaction: &TransactionV2,
    117     domain: &crate::domain::TransactionV2Domain,
    118     network: AddressNetwork,
    119 ) {
    120     let transaction_outputs = match transaction {
    121         TransactionV2::Migration { outputs, .. } | TransactionV2::Transfer { outputs, .. } => {
    122             outputs.as_slice()
    123         }
    124         TransactionV2::Burn { change, .. } => change.as_slice(),
    125         TransactionV2::Mine { .. } => return,
    126     };
    127     let Ok(transaction_id) = transaction.transaction_id(domain).map(hex_encode) else {
    128         return;
    129     };
    130     for (index, output) in transaction_outputs.iter().enumerate() {
    131         let Ok(address) = encode_versioned_address(output.address, network) else {
    132             continue;
    133         };
    134         let Ok(index) = u32::try_from(index) else {
    135             continue;
    136         };
    137         outputs.insert(
    138             OutPoint {
    139                 txid: transaction_id.clone(),
    140                 index,
    141             },
    142             TxOutput {
    143                 address,
    144                 amount: output.amount,
    145             },
    146         );
    147     }
    148 }
    149 
    150 fn index_transaction_outputs(
    151     outputs: &mut BTreeMap<OutPoint, TxOutput>,
    152     transaction: &Transaction,
    153 ) {
    154     let tx_outputs = match transaction {
    155         Transaction::Transfer { outputs, .. } => outputs.clone(),
    156         Transaction::Burn { change, .. } => change.clone(),
    157         Transaction::Mine { recipient, .. } => vec![TxOutput {
    158             address: recipient.clone(),
    159             amount: crate::domain::MINE_REWARD,
    160         }],
    161     };
    162     for (index, output) in tx_outputs.into_iter().enumerate() {
    163         outputs.insert(
    164             OutPoint {
    165                 txid: transaction.signature().to_string(),
    166                 index: index as u32,
    167             },
    168             output,
    169         );
    170     }
    171 }
    172 
    173 #[cfg(test)]
    174 mod tests {
    175     use std::collections::BTreeMap;
    176 
    177     use crate::domain::{ChainSnapshot, Ledger, MICRO_IUNA, Wallet};
    178 
    179     use super::{build_ui_chain_index, genesis_allocation_outpoint};
    180 
    181     #[test]
    182     fn ui_chain_index_keeps_genesis_allocation_outputs() {
    183         let wallet = Wallet::from_seed("ui-genesis-output-wallet");
    184         let mut allocations = BTreeMap::new();
    185         allocations.insert(wallet.address().to_string(), MICRO_IUNA);
    186         let ledger = Ledger::new(allocations.clone(), 1);
    187         let snapshot = ChainSnapshot {
    188             genesis_allocations: allocations,
    189             vdf_rounds: ledger.vdf_rounds(),
    190             launch_profile: ledger.launch_profile().clone(),
    191             blocks: ledger.chain().to_vec(),
    192         };
    193 
    194         let index = build_ui_chain_index(&snapshot);
    195         let outpoint = genesis_allocation_outpoint(wallet.address());
    196 
    197         assert_eq!(
    198             index.outputs.get(&outpoint).map(|output| output.amount),
    199             Some(MICRO_IUNA)
    200         );
    201     }
    202 }