iuna

iuna

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

status.rs (8698B)


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