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 }