iuna

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

helpers.rs (4362B)


      1 use std::collections::{BTreeMap, BTreeSet};
      2 
      3 use anyhow::{Context, Result, bail};
      4 use sha2::{Digest, Sha256};
      5 
      6 use crate::domain::{Amount, BuiltBlindedTransaction, MINE_REWARD, OutPoint, Transaction};
      7 
      8 use super::FeeEstimate;
      9 
     10 pub(super) fn auto_pow_salt(wallet_address: &str, anchor: &str) -> u64 {
     11     let digest = Sha256::digest(format!("iuna-auto-pow:{wallet_address}:{anchor}").as_bytes());
     12     let mut bytes = [0_u8; 8];
     13     bytes.copy_from_slice(&digest[..8]);
     14     u64::from_be_bytes(bytes)
     15 }
     16 
     17 pub(super) fn converge_fee_by_byte(
     18     fee_per_byte: Amount,
     19     mut build: impl FnMut(Amount) -> Result<BuiltBlindedTransaction>,
     20 ) -> Result<(BuiltBlindedTransaction, FeeEstimate)> {
     21     let mut fee = 0;
     22     let mut best = None;
     23     for _ in 0..64 {
     24         let built = build(fee)?;
     25         let bytes = built.transaction.fee_rate_size_bytes();
     26         let required_fee = fee_per_byte
     27             .checked_mul(bytes as Amount)
     28             .context("fee per byte times blinded transaction bytes overflows")?;
     29         if fee == required_fee {
     30             return Ok((built, FeeEstimate { bytes, fee }));
     31         }
     32         if fee > required_fee
     33             && best
     34                 .as_ref()
     35                 .is_none_or(|(_, estimate): &(BuiltBlindedTransaction, FeeEstimate)| {
     36                     fee < estimate.fee
     37                 })
     38         {
     39             best = Some((built, FeeEstimate { bytes, fee }));
     40         }
     41         fee = required_fee;
     42     }
     43 
     44     let built = build(fee)?;
     45     let bytes = built.transaction.fee_rate_size_bytes();
     46     let required_fee = fee_per_byte
     47         .checked_mul(bytes as Amount)
     48         .context("fee per byte times blinded transaction bytes overflows")?;
     49     if fee >= required_fee {
     50         if best
     51             .as_ref()
     52             .is_none_or(|(_, estimate): &(BuiltBlindedTransaction, FeeEstimate)| fee < estimate.fee)
     53         {
     54             best = Some((built, FeeEstimate { bytes, fee }));
     55         }
     56         if let Some(best) = best {
     57             return Ok(best);
     58         }
     59     }
     60     let built = build(required_fee)?;
     61     let bytes = built.transaction.fee_rate_size_bytes();
     62     let final_required_fee = fee_per_byte
     63         .checked_mul(bytes as Amount)
     64         .context("fee per byte times blinded transaction bytes overflows")?;
     65     if required_fee < final_required_fee {
     66         bail!("fee per byte did not converge");
     67     }
     68     Ok((
     69         built,
     70         FeeEstimate {
     71             bytes,
     72             fee: required_fee,
     73         },
     74     ))
     75 }
     76 
     77 pub(super) fn transaction_output_total_for_address(
     78     transaction: &Transaction,
     79     address: &str,
     80 ) -> Amount {
     81     match transaction {
     82         Transaction::Transfer { outputs, .. } => outputs,
     83         Transaction::Burn { change, .. } => change,
     84         Transaction::Mine { recipient, .. } if recipient == address => return MINE_REWARD,
     85         Transaction::Mine { .. } => return 0,
     86     }
     87     .iter()
     88     .filter(|output| output.address == address)
     89     .fold(0_u64, |total, output| total.saturating_add(output.amount))
     90 }
     91 
     92 pub(super) fn transaction_input_total_from_outputs(
     93     transaction: &Transaction,
     94     address: &str,
     95     outputs: &BTreeMap<OutPoint, Amount>,
     96 ) -> Amount {
     97     let inputs = match transaction {
     98         Transaction::Transfer { inputs, .. } | Transaction::Burn { inputs, .. } => inputs,
     99         Transaction::Mine { .. } => return 0,
    100     };
    101     inputs
    102         .iter()
    103         .filter(|input| input.owner == address)
    104         .filter_map(|input| outputs.get(&input.outpoint))
    105         .fold(0_u64, |total, amount| total.saturating_add(*amount))
    106 }
    107 
    108 pub(super) fn transaction_input_outpoints(transaction: &Transaction) -> BTreeSet<OutPoint> {
    109     match transaction {
    110         Transaction::Transfer { inputs, .. } | Transaction::Burn { inputs, .. } => inputs,
    111         Transaction::Mine { .. } => return BTreeSet::new(),
    112     }
    113     .iter()
    114     .map(|input| input.outpoint.clone())
    115     .collect()
    116 }
    117 
    118 pub(super) fn allowed_recovery_vdf_rank_count(rank_count: usize, percent: u8) -> usize {
    119     if rank_count == 0 || percent == 0 {
    120         return 0;
    121     }
    122     rank_count
    123         .saturating_mul(usize::from(percent.min(100)))
    124         .saturating_add(99)
    125         / 100
    126 }
    127 
    128 pub(super) fn recovery_vdf_sample_percent(address: &str, tip_hash: &str) -> u8 {
    129     let digest = Sha256::digest(format!("iuna-recovery-vdf-sample:{tip_hash}:{address}"));
    130     digest[0] % 100
    131 }