iuna

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

status.rs (8444B)


      1 use std::collections::BTreeMap;
      2 
      3 use anyhow::Result;
      4 
      5 use crate::{
      6     adapters::config_store::{MAX_POW_MINING_WORKERS, clamp_pow_mining_workers},
      7     domain::{Amount, MINE_FINALIZER_FEE, Transaction, VDF_TARGET_BLOCK_MS},
      8 };
      9 
     10 use super::{
     11     LaunchProfileStatus, MiningStatus, NodeCore, NodeStatus, StratumStatus,
     12     helpers::{transaction_input_total_from_outputs, transaction_output_total_for_address},
     13     now_ms,
     14 };
     15 
     16 impl NodeCore {
     17     pub fn status(&self) -> NodeStatus {
     18         let chain = self.ledger.light_status();
     19         let launch_profile = self.ledger.launch_profile();
     20         let current_leader = self.ledger.expected_leader_for_next_block();
     21         let wallet_is_current_leader = current_leader
     22             .as_deref()
     23             .is_none_or(|leader| leader == self.wallet.address());
     24 
     25         NodeStatus {
     26             app_version: env!("CARGO_PKG_VERSION").to_string(),
     27             wallet_address: self.wallet.address().to_string(),
     28             wallet_balance: self.wallet_projected_balance(),
     29             wallet_locked: self.wallet.is_locked(),
     30             launch_profile: LaunchProfileStatus {
     31                 profile_id: launch_profile.profile_id.clone(),
     32                 profile_hash: chain.launch_profile_hash.clone(),
     33                 ticket_maturity_delay_heights: launch_profile.ticket_maturity_delay_heights,
     34                 ticket_expiry_window_heights: launch_profile.ticket_expiry_window_heights,
     35                 mine_difficulty_bits: launch_profile.mine_difficulty_bits,
     36             },
     37             mining: MiningStatus {
     38                 automatic: self.automatic_mining_enabled,
     39                 pow_mining_enabled: self.pow_mining_enabled,
     40                 pow_mining_workers: self.pow_mining_workers,
     41                 max_pow_mining_workers: MAX_POW_MINING_WORKERS,
     42                 burn_per_block: self.burn_per_block,
     43                 automatic_burn_fee: self.burn_fee,
     44                 automatic_pow_mine_fee: MINE_FINALIZER_FEE,
     45                 last_auto_pow_mine_anchor: self.last_auto_pow_mine_anchor.clone(),
     46                 last_auto_pow_mine_status: if self.pow_mining_enabled && !self.has_real_chain() {
     47                     Some("waiting for a real chain before PoW mining can start".to_string())
     48                 } else {
     49                     self.last_auto_pow_mine_status.clone()
     50                 },
     51                 vdf_rounds: self.ledger.vdf_rounds(),
     52                 vdf_target_block_ms: VDF_TARGET_BLOCK_MS,
     53                 current_leader,
     54                 wallet_is_current_leader,
     55                 last_auto_burn_height: self.last_auto_burn_height,
     56                 recovery_vdf_top_rank_percent: self.recovery_vdf_top_rank_percent,
     57             },
     58             stratum: StratumStatus {
     59                 enabled: false,
     60                 listen_addr: None,
     61             },
     62             chain,
     63         }
     64     }
     65 
     66     fn wallet_projected_balance(&self) -> Amount {
     67         let address = self.wallet.address();
     68         let mut balance = self.ledger.balance_of(address);
     69         let confirmed_outputs = self
     70             .ledger
     71             .utxos_for_address(address)
     72             .into_iter()
     73             .map(|(outpoint, output)| (outpoint, output.amount))
     74             .collect::<BTreeMap<_, _>>();
     75 
     76         for (commitment, payload) in &self.owned_blinded_payloads {
     77             if !self.ledger.has_unrevealed_blinded_transaction(commitment) {
     78                 continue;
     79             }
     80             let output_total = transaction_output_total_for_address(payload, address);
     81             if self.ledger.has_active_blinded_transaction(commitment) {
     82                 balance = balance.saturating_add(output_total);
     83             } else {
     84                 let input_total =
     85                     transaction_input_total_from_outputs(payload, address, &confirmed_outputs);
     86                 balance = balance
     87                     .saturating_sub(input_total)
     88                     .saturating_add(output_total);
     89             }
     90         }
     91 
     92         if let Some((height, burn)) = &self.local_block_anchor_burn {
     93             if *height == self.ledger.height() && !self.ledger.has_transaction(burn.signature()) {
     94                 let output_total = transaction_output_total_for_address(burn, address);
     95                 let input_total =
     96                     transaction_input_total_from_outputs(burn, address, &confirmed_outputs);
     97                 balance = balance
     98                     .saturating_sub(input_total)
     99                     .saturating_add(output_total);
    100             }
    101         }
    102 
    103         balance
    104     }
    105 
    106     pub fn set_burn_per_block(&mut self, amount: Amount) -> Result<Option<Transaction>> {
    107         self.set_automatic_burn(amount, self.burn_fee)
    108     }
    109 
    110     pub fn set_automatic_burn(
    111         &mut self,
    112         amount: Amount,
    113         fee: Amount,
    114     ) -> Result<Option<Transaction>> {
    115         self.set_automatic_burn_settings(amount > 0, amount, fee)
    116     }
    117 
    118     pub fn set_automatic_burn_settings(
    119         &mut self,
    120         enabled: bool,
    121         amount: Amount,
    122         fee: Amount,
    123     ) -> Result<Option<Transaction>> {
    124         let was_disabled = !self.automatic_mining_enabled || self.burn_per_block == 0;
    125         self.automatic_mining_enabled = enabled;
    126         self.burn_per_block = amount;
    127         self.burn_fee = fee;
    128         if was_disabled && enabled && amount > 0 {
    129             self.last_auto_burn_height = None;
    130             self.last_auto_anchor_burn_height = None;
    131         }
    132         self.prepare_automatic_burn(now_ms())
    133     }
    134 
    135     pub fn set_pow_mining_enabled(&mut self, enabled: bool) {
    136         self.pow_mining_enabled = enabled;
    137         self.auto_pow_mine_cursor = None;
    138         if !enabled {
    139             self.last_auto_pow_mine_anchor = None;
    140             self.last_auto_pow_mine_status = None;
    141         } else {
    142             self.last_auto_pow_mine_status =
    143                 Some("waiting for next automatic PoW mining tick".to_string());
    144         }
    145     }
    146 
    147     pub fn pow_mining_enabled(&self) -> bool {
    148         self.pow_mining_enabled
    149     }
    150 
    151     pub fn set_pow_mining_workers(&mut self, workers: u8) {
    152         let workers = clamp_pow_mining_workers(workers);
    153         if self.pow_mining_workers != workers {
    154             self.pow_mining_workers = workers;
    155             self.auto_pow_mine_cursor = None;
    156             if self.pow_mining_enabled {
    157                 self.last_auto_pow_mine_status =
    158                     Some("waiting for next automatic PoW mining tick".to_string());
    159             }
    160         }
    161     }
    162 
    163     pub fn pow_mining_workers(&self) -> u8 {
    164         self.pow_mining_workers
    165     }
    166 
    167     pub fn set_recovery_vdf_top_rank_percent(&mut self, percent: u8) {
    168         self.recovery_vdf_top_rank_percent = percent.min(100);
    169     }
    170 }
    171 
    172 #[cfg(test)]
    173 mod tests {
    174     use std::collections::BTreeMap;
    175 
    176     use crate::{
    177         app::{NodeConfig, NodeCore},
    178         domain::{Ledger, Wallet},
    179     };
    180 
    181     #[test]
    182     fn status_reports_package_version() {
    183         let wallet = Wallet::from_seed("status-version-wallet");
    184         let node = NodeCore::new(NodeConfig {
    185             wallet,
    186             genesis_allocations: BTreeMap::new(),
    187             vdf_rounds: 1,
    188             burn_per_block: 0,
    189             burn_fee: 0,
    190             pow_mining_workers: 1,
    191             recovery_vdf_top_rank_percent: 100,
    192         });
    193 
    194         assert_eq!(node.status().app_version, env!("CARGO_PKG_VERSION"));
    195     }
    196 
    197     #[test]
    198     fn status_omits_full_balance_map_for_ui_polling() {
    199         let wallet = Wallet::from_seed("status-light-wallet");
    200         let mut allocations = BTreeMap::new();
    201         allocations.insert(wallet.address().to_string(), 10);
    202         allocations.insert(
    203             Wallet::from_seed("status-light-peer").address().to_string(),
    204             5,
    205         );
    206         let ledger = Ledger::new(allocations, 1);
    207         let node = NodeCore::from_ledger(wallet, ledger, 0);
    208 
    209         let status = node.status();
    210 
    211         assert_eq!(status.wallet_balance, 10);
    212         assert!(status.chain.balances.is_empty());
    213     }
    214 
    215     #[test]
    216     fn automatic_pow_status_reports_setup_placeholder_wait() {
    217         let wallet = Wallet::from_seed("automatic-pow-setup-placeholder-wallet");
    218         let ledger = Ledger::new(BTreeMap::new(), 1);
    219         let mut node = NodeCore::from_ledger(wallet, ledger, 0);
    220 
    221         node.set_pow_mining_enabled(true);
    222 
    223         assert_eq!(
    224             node.status().mining.last_auto_pow_mine_status.as_deref(),
    225             Some("waiting for a real chain before PoW mining can start")
    226         );
    227     }
    228 }