iuna

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

genesis.rs (5979B)


      1 use std::collections::BTreeMap;
      2 
      3 use anyhow::{Result, bail};
      4 
      5 use super::{
      6     Amount, BLOCK_REWARD, Block, FinalizerMode, OutPoint, RevealBundleSection, Transaction,
      7     TxOutput, apply_transaction, credit_reward_output, hex_hash, validate_genesis_burn_transaction,
      8 };
      9 
     10 pub(super) fn build_genesis_block(
     11     genesis_allocations: &BTreeMap<String, Amount>,
     12     transactions: Vec<Transaction>,
     13 ) -> Block {
     14     let miner = genesis_miner(genesis_allocations, &transactions);
     15     let reward = genesis_reward(genesis_allocations, &transactions);
     16     let txs = transactions
     17         .iter()
     18         .map(Transaction::canonical)
     19         .collect::<Vec<_>>()
     20         .join("|");
     21     let vdf_output = hex_hash(format!("iuna-genesis-vdf:{genesis_allocations:?}:{txs}"));
     22     let mut genesis = Block {
     23         height: 0,
     24         prev_hash: "0".repeat(64),
     25         timestamp_ms: 0,
     26         miner,
     27         finalizer_mode: FinalizerMode::Ticket,
     28         finalizer_rank: 0,
     29         reward,
     30         vdf_rounds: 0,
     31         vdf_output,
     32         leader_proof: None,
     33         blinded_transactions: Vec::new(),
     34         reveal_bundle_section: RevealBundleSection::default(),
     35         transactions,
     36         hash: String::new(),
     37     };
     38     genesis.hash = genesis.compute_hash();
     39     genesis
     40 }
     41 
     42 pub(super) fn utxos_after_genesis(
     43     genesis_allocations: &BTreeMap<String, Amount>,
     44     genesis: &Block,
     45 ) -> Result<BTreeMap<OutPoint, TxOutput>> {
     46     let mut utxos = genesis_allocation_utxos(genesis_allocations);
     47     for transaction in &genesis.transactions {
     48         match transaction {
     49             Transaction::Burn { .. } => {
     50                 validate_genesis_burn_transaction(transaction)?;
     51                 apply_transaction(transaction, &mut utxos)?;
     52             }
     53             Transaction::Transfer { .. } | Transaction::Mine { .. } => {
     54                 bail!("genesis only supports burn transactions")
     55             }
     56         }
     57     }
     58     credit_reward_output(&mut utxos, genesis)?;
     59     Ok(utxos)
     60 }
     61 
     62 fn genesis_allocation_utxos(
     63     genesis_allocations: &BTreeMap<String, Amount>,
     64 ) -> BTreeMap<OutPoint, TxOutput> {
     65     genesis_allocations
     66         .iter()
     67         .filter(|(_, amount)| **amount > 0)
     68         .map(|(address, amount)| {
     69             (
     70                 genesis_allocation_outpoint(address),
     71                 TxOutput {
     72                     address: address.clone(),
     73                     amount: *amount,
     74                 },
     75             )
     76         })
     77         .collect()
     78 }
     79 
     80 pub(super) fn balances_from_utxos(
     81     utxos: &BTreeMap<OutPoint, TxOutput>,
     82 ) -> BTreeMap<String, Amount> {
     83     let mut balances = BTreeMap::new();
     84     for output in utxos.values() {
     85         let balance = balances.entry(output.address.clone()).or_insert(0_u64);
     86         *balance = balance.saturating_add(output.amount);
     87     }
     88     balances
     89 }
     90 
     91 pub(super) fn genesis_allocation_outpoint(address: &str) -> OutPoint {
     92     OutPoint {
     93         txid: hex_hash(format!("iuna-genesis-allocation:{address}")),
     94         index: 0,
     95     }
     96 }
     97 
     98 pub(super) fn validate_genesis_block(block: &Block) -> Result<()> {
     99     if block.height != 0 {
    100         bail!("genesis block height must be 0");
    101     }
    102     if block.prev_hash != "0".repeat(64) {
    103         bail!("genesis block prev_hash must be all zeroes");
    104     }
    105     if block.timestamp_ms != 0 {
    106         bail!("genesis block timestamp must be 0");
    107     }
    108     if block.miner == "genesis" && block.reward != 0 {
    109         bail!("genesis placeholder miner must not receive a reward");
    110     }
    111     if block.miner != "genesis" && block.reward != 0 && block.reward != BLOCK_REWARD {
    112         bail!("genesis block reward is invalid");
    113     }
    114     if block.vdf_rounds != 0 {
    115         bail!("genesis block VDF rounds must be 0");
    116     }
    117     if block.leader_proof.is_some() {
    118         bail!("genesis block must not carry a leader proof");
    119     }
    120     if !block.blinded_transactions.is_empty() || !block.reveal_bundle_section.is_empty() {
    121         bail!("genesis block must not carry blinded transactions");
    122     }
    123     if block.compute_hash() != block.hash {
    124         bail!("genesis block hash is invalid");
    125     }
    126     Ok(())
    127 }
    128 
    129 fn genesis_miner(
    130     genesis_allocations: &BTreeMap<String, Amount>,
    131     transactions: &[Transaction],
    132 ) -> String {
    133     transactions
    134         .iter()
    135         .filter_map(|transaction| match transaction {
    136             Transaction::Burn { inputs, .. } => inputs.first().map(|input| input.owner.as_str()),
    137             Transaction::Transfer { .. } | Transaction::Mine { .. } => None,
    138         })
    139         .find(|from| genesis_allocations.contains_key(*from))
    140         .or_else(|| genesis_allocations.keys().next().map(String::as_str))
    141         .unwrap_or("genesis")
    142         .to_string()
    143 }
    144 
    145 fn genesis_reward(
    146     genesis_allocations: &BTreeMap<String, Amount>,
    147     transactions: &[Transaction],
    148 ) -> Amount {
    149     if genesis_allocations.is_empty() || transactions.is_empty() {
    150         0
    151     } else {
    152         BLOCK_REWARD
    153     }
    154 }
    155 
    156 #[cfg(test)]
    157 mod tests {
    158     use std::collections::BTreeMap;
    159 
    160     use super::{build_genesis_block, genesis_allocation_outpoint, validate_genesis_block};
    161     use crate::domain::{MICRO_IUNA, Transaction, Wallet};
    162 
    163     #[test]
    164     fn genesis_allocation_outpoint_is_address_bound() {
    165         let alice = Wallet::from_seed("genesis-outpoint-alice");
    166         let bob = Wallet::from_seed("genesis-outpoint-bob");
    167 
    168         assert_ne!(
    169             genesis_allocation_outpoint(alice.address()),
    170             genesis_allocation_outpoint(bob.address())
    171         );
    172         assert_eq!(genesis_allocation_outpoint(alice.address()).index, 0);
    173     }
    174 
    175     #[test]
    176     fn built_genesis_block_validates() {
    177         let wallet = Wallet::from_seed("genesis-module-validates");
    178         let mut allocations = BTreeMap::new();
    179         allocations.insert(wallet.address().to_string(), MICRO_IUNA);
    180         let burn = Transaction::genesis_burn(wallet.address(), MICRO_IUNA);
    181 
    182         let genesis = build_genesis_block(&allocations, vec![burn]);
    183 
    184         validate_genesis_block(&genesis).unwrap();
    185     }
    186 }