diff --git a/mobile/native/src/api.rs b/mobile/native/src/api.rs index c6aed37c9..0c5070b25 100644 --- a/mobile/native/src/api.rs +++ b/mobile/native/src/api.rs @@ -239,7 +239,7 @@ pub enum IncludeBacktraceOnPanic { } pub fn set_config(config: Config, app_dir: String, seed_dir: String) -> Result<()> { - config::set(config, app_dir, seed_dir); + crate::state::set_config((config, app_dir, seed_dir).into()); Ok(()) } diff --git a/mobile/native/src/config/mod.rs b/mobile/native/src/config/mod.rs index 0c2600fc1..cf49b9727 100644 --- a/mobile/native/src/config/mod.rs +++ b/mobile/native/src/config/mod.rs @@ -1,18 +1,14 @@ pub mod api; -use crate::config::api::Config; use bdk::bitcoin; use bdk::bitcoin::secp256k1::PublicKey; use bdk::bitcoin::XOnlyPublicKey; use ln_dlc_node::node::NodeInfo; use ln_dlc_node::node::OracleInfo; -use state::Storage; use std::net::SocketAddr; use std::path::Path; use std::time::Duration; -static CONFIG: Storage = Storage::new(); - #[derive(Clone)] pub struct ConfigInternal { coordinator_pubkey: PublicKey, @@ -27,22 +23,17 @@ pub struct ConfigInternal { seed_dir: String, } -pub fn set(config: Config, app_dir: String, seed_dir: String) { - CONFIG.set((config, app_dir, seed_dir).into()); -} - pub fn coordinator_health_endpoint() -> String { - let config = CONFIG.get(); + let config = crate::state::get_config(); format!("http://{}/health", config.http_endpoint) } pub fn health_check_interval() -> Duration { - let config = CONFIG.get(); - config.health_check_interval + crate::state::get_config().health_check_interval } pub fn get_coordinator_info() -> NodeInfo { - let config = CONFIG.get(); + let config = crate::state::get_config(); NodeInfo { pubkey: config.coordinator_pubkey, address: config.p2p_endpoint, @@ -50,11 +41,11 @@ pub fn get_coordinator_info() -> NodeInfo { } pub fn get_esplora_endpoint() -> String { - CONFIG.get().esplora_endpoint.clone() + crate::state::get_config().esplora_endpoint } pub fn get_oracle_info() -> OracleInfo { - let config = CONFIG.get(); + let config = crate::state::get_config(); OracleInfo { endpoint: config.oracle_endpoint.clone(), public_key: config.oracle_pubkey, @@ -62,19 +53,19 @@ pub fn get_oracle_info() -> OracleInfo { } pub fn get_http_endpoint() -> SocketAddr { - CONFIG.get().http_endpoint + crate::state::get_config().http_endpoint } pub fn get_network() -> bitcoin::Network { - CONFIG.get().network + crate::state::get_config().network } pub fn get_data_dir() -> String { - CONFIG.get().data_dir.clone() + crate::state::get_config().data_dir } pub fn get_seed_dir() -> String { - CONFIG.get().seed_dir.clone() + crate::state::get_config().seed_dir } pub fn get_backup_dir() -> String { diff --git a/mobile/native/src/lib.rs b/mobile/native/src/lib.rs index d2117be8d..e149dfee2 100644 --- a/mobile/native/src/lib.rs +++ b/mobile/native/src/lib.rs @@ -11,6 +11,7 @@ pub mod event; pub mod health; pub mod logger; pub mod schema; +pub mod state; mod backup; mod orderbook; diff --git a/mobile/native/src/ln_dlc/mod.rs b/mobile/native/src/ln_dlc/mod.rs index a6d1ffb09..c351e5577 100644 --- a/mobile/native/src/ln_dlc/mod.rs +++ b/mobile/native/src/ln_dlc/mod.rs @@ -99,7 +99,7 @@ use tokio::task::spawn_blocking; use trade::ContractSymbol; mod lightning_subscriber; -mod node; +pub mod node; mod recover_rollover; mod sync_position_to_dlc; @@ -122,16 +122,12 @@ const ON_CHAIN_SYNC_INTERVAL: Duration = Duration::from_secs(300); /// exact fee will be know. pub const FUNDING_TX_WEIGHT_ESTIMATE: u64 = 220; -static NODE: Storage> = Storage::new(); -static SEED: Storage = Storage::new(); -static STORAGE: Storage = Storage::new(); - /// Trigger an on-chain sync followed by an update to the wallet balance and history. /// /// We do not wait for the triggered task to finish, because the effect will be reflected /// asynchronously on the UI. pub async fn refresh_wallet_info() -> Result<()> { - let node = NODE.try_get().context("failed to get ln dlc node")?; + let node = crate::state::get_node(); let wallet = node.inner.wallet(); // Spawn into the blocking thread pool of the dedicated backend runtime to avoid blocking the UI @@ -146,7 +142,7 @@ pub async fn refresh_wallet_info() -> Result<()> { tracing::error!("Manually triggered Lightning wallet sync failed: {e:#}"); } - if let Err(e) = keep_wallet_balance_and_history_up_to_date(node) { + if let Err(e) = keep_wallet_balance_and_history_up_to_date(&node) { tracing::error!("Failed to keep wallet history up to date: {e:#}"); } @@ -157,16 +153,14 @@ pub async fn refresh_wallet_info() -> Result<()> { } pub fn get_seed_phrase() -> Vec { - SEED.try_get() - .expect("SEED to be initialised") - .get_seed_phrase() + crate::state::get_seed().get_seed_phrase() } /// Gets the seed from the storage or from disk. However it will panic if the seed can not be found. /// No new seed will be created. fn get_seed() -> Bip39Seed { - match SEED.try_get() { - Some(seed) => seed.clone(), + match crate::state::try_get_seed() { + Some(seed) => seed, None => { let seed_dir = config::get_seed_dir(); @@ -175,7 +169,7 @@ fn get_seed() -> Bip39Seed { assert!(seed_path.exists()); let seed = Bip39Seed::initialize(&seed_path).expect("to read seed file"); - SEED.set(seed.clone()); + crate::state::set_seed(seed.clone()); seed } } @@ -199,16 +193,16 @@ pub fn get_node_pubkey() -> PublicKey { } pub async fn update_node_settings(settings: LnDlcNodeSettings) { - let node = NODE.get(); + let node = crate::state::get_node(); node.inner.update_settings(settings).await; } pub fn get_oracle_pubkey() -> XOnlyPublicKey { - NODE.get().inner.oracle_pk() + crate::state::get_node().inner.oracle_pk() } pub fn get_funding_transaction(channel_id: &ChannelId) -> Result { - let node = NODE.get(); + let node = crate::state::get_node(); let channel_details = node.inner.channel_manager.get_channel_details(channel_id); let funding_transaction = match channel_details { @@ -243,8 +237,8 @@ pub fn get_or_create_tokio_runtime() -> Result<&'static Runtime> { /// Gets the 10101 node storage, initializes the storage if not found yet. pub fn get_storage() -> TenTenOneNodeStorage { - match STORAGE.try_get() { - Some(storage) => storage.clone(), + match crate::state::try_get_storage() { + Some(storage) => storage, None => { // storage is only initialized before the node is started if a new wallet is created // or restored. @@ -254,7 +248,7 @@ pub fn get_storage() -> TenTenOneNodeStorage { get_node_key(), ); tracing::info!("Initialized 10101 storage!"); - STORAGE.set(storage.clone()); + crate::state::set_storage(storage.clone()); storage } } @@ -285,7 +279,7 @@ pub fn run(seed_dir: String, runtime: &Runtime) -> Result<()> { let seed_dir = Path::new(&seed_dir).join(network.to_string()); let seed_path = seed_dir.join("seed"); let seed = Bip39Seed::initialize(&seed_path)?; - SEED.set(seed.clone()); + crate::state::set_seed(seed.clone()); let (event_sender, event_receiver) = watch::channel::>(None); @@ -407,7 +401,7 @@ pub fn run(seed_dir: String, runtime: &Runtime) -> Result<()> { ); } - NODE.set(node); + crate::state::set_node(node); event::publish(&EventInternal::Init("10101 is ready.".to_string())); @@ -417,14 +411,14 @@ pub fn run(seed_dir: String, runtime: &Runtime) -> Result<()> { pub fn init_new_mnemonic(target_seed_file: &Path) -> Result<()> { let seed = Bip39Seed::initialize(target_seed_file)?; - SEED.set(seed); + crate::state::set_seed(seed); Ok(()) } #[tokio::main(flavor = "current_thread")] pub async fn restore_from_mnemonic(seed_words: &str, target_seed_file: &Path) -> Result<()> { let seed = Bip39Seed::restore_from_mnemonic(seed_words, target_seed_file)?; - SEED.set(seed); + crate::state::set_seed(seed); let storage = get_storage(); storage.client.restore(storage.dlc_storage).await @@ -727,11 +721,14 @@ fn compute_relative_contracts(order: &Order) -> Decimal { } pub fn get_unused_address() -> String { - NODE.get().inner.get_unused_address().to_string() + crate::state::get_node() + .inner + .get_unused_address() + .to_string() } pub fn close_channel(is_force_close: bool) -> Result<()> { - let node = NODE.try_get().context("failed to get ln dlc node")?; + let node = crate::state::try_get_node().context("failed to get ln dlc node")?; let channels = node.inner.list_channels(); let channel_details = channels.first().context("No channel to close")?; @@ -749,7 +746,7 @@ pub fn collaborative_revert_channel( trader_amount: Amount, execution_price: Decimal, ) -> Result<()> { - let node = NODE.try_get().context("Failed to get Node")?; + let node = crate::state::try_get_node().context("Failed to get Node")?; let node = node.inner.clone(); let channel_id_hex = hex::encode(channel_id); @@ -883,7 +880,7 @@ fn update_state_after_collab_revert( sub_channel: SubChannel, execution_price: Decimal, ) -> Result<()> { - let node = NODE.try_get().context("failed to get ln dlc node")?; + let node = crate::state::try_get_node().context("failed to get ln dlc node")?; let positions = db::get_positions()?; let position = match positions.first() { @@ -953,14 +950,14 @@ fn update_state_after_collab_revert( } pub fn get_usable_channel_details() -> Result> { - let node = NODE.try_get().context("failed to get ln dlc node")?; + let node = crate::state::try_get_node().context("failed to get ln dlc node")?; let channels = node.inner.list_usable_channels(); Ok(channels) } pub fn get_fee_rate() -> Result { - let node = NODE.try_get().context("failed to get ln dlc node")?; + let node = crate::state::try_get_node().context("failed to get ln dlc node")?; Ok(node.inner.wallet().get_fee_rate(CONFIRMATION_TARGET)) } @@ -968,7 +965,7 @@ pub fn get_fee_rate() -> Result { /// /// This is used when checking max tradeable amount pub fn max_channel_value() -> Result { - let node = NODE.try_get().context("failed to get ln dlc node")?; + let node = crate::state::try_get_node().context("failed to get ln dlc node")?; if let Some(existing_channel) = node .inner .list_channels() @@ -1009,7 +1006,7 @@ fn fetch_lsp_config() -> Result { } pub fn contract_tx_fee_rate() -> Result { - let node = NODE.try_get().context("failed to get ln dlc node")?; + let node = crate::state::try_get_node().context("failed to get ln dlc node")?; if let Some(fee_rate_per_vb) = node .inner .list_dlc_channels()? @@ -1041,7 +1038,7 @@ pub fn create_onboarding_invoice( let runtime = get_or_create_tokio_runtime()?; runtime.block_on(async { - let node = NODE.get(); + let node = crate::state::get_node(); let client = reqwest_client(); // check if we have already announced a channel before. If so we can reuse the `user_channel_id` @@ -1107,7 +1104,7 @@ pub fn create_onboarding_invoice( } pub fn create_invoice(amount_sats: Option) -> Result { - let node = NODE.get(); + let node = crate::state::get_node(); let final_route_hint_hop = node .inner @@ -1125,11 +1122,15 @@ pub fn send_payment(payment: SendPayment) -> Result<()> { match payment { SendPayment::Lightning { invoice, amount } => { let invoice = Bolt11Invoice::from_str(&invoice)?; - NODE.get().inner.pay_invoice(&invoice, amount)?; + crate::state::get_node() + .inner + .pay_invoice(&invoice, amount)?; } SendPayment::OnChain { address, amount } => { let address = Address::from_str(&address)?; - NODE.get().inner.send_to_address(&address, amount)?; + crate::state::get_node() + .inner + .send_to_address(&address, amount)?; } } Ok(()) @@ -1165,7 +1166,7 @@ pub async fn trade(trade_params: TradeParams) -> Result<(), (FailureReason, Erro /// initiates the rollover protocol with the coordinator pub async fn rollover(contract_id: Option) -> Result<()> { - let node = NODE.get(); + let node = crate::state::get_node(); let dlc_channels = node .inner diff --git a/mobile/native/src/state.rs b/mobile/native/src/state.rs new file mode 100644 index 000000000..49ace1ab6 --- /dev/null +++ b/mobile/native/src/state.rs @@ -0,0 +1,101 @@ +use crate::config::ConfigInternal; +use crate::ln_dlc::node::Node; +use crate::storage::TenTenOneNodeStorage; +use ln_dlc_node::seed::Bip39Seed; +use state::Storage; +use std::sync::Arc; + +// FIXME(holzeis): mutability is only required for tests, but somehow annotating them with +// #[cfg(test)] and #[cfg(not(test))] did not work. The tests are always compiled with +// #[cfg(not(test))] + +/// For testing we need the state to be mutable as otherwise we can't start another app after +/// stopping the first one. Note, running two apps at the same time will not work as the states +/// below are static and will be used for both apps. +/// TODO(holzeis): Check if there is a way to bind the state to the lifetime of the app (node). + +static mut CONFIG: TenTenOneState = TenTenOneState::new(); +static mut NODE: TenTenOneState> = TenTenOneState::new(); +static mut SEED: TenTenOneState = TenTenOneState::new(); +static mut STORAGE: TenTenOneState = TenTenOneState::new(); + +pub struct TenTenOneState { + inner: Storage, +} + +impl TenTenOneState { + pub const fn new() -> TenTenOneState { + Self { + inner: Storage::new(), + } + } + + fn set(&mut self, state: T) { + match self.inner.try_get_mut() { + Some(inner_state) => *inner_state = state, + None => { + self.inner = Storage::from(state); + } + } + } + + fn get(&self) -> T { + self.inner.get().clone() + } + + fn try_get(&self) -> Option { + self.inner.try_get().cloned() + } +} + +pub fn set_config(config: ConfigInternal) { + unsafe { + CONFIG.set(config); + } +} + +pub fn get_config() -> ConfigInternal { + unsafe { CONFIG.get() } +} + +pub fn set_node(node: Arc) { + unsafe { + NODE.set(node); + } +} + +pub fn get_node() -> Arc { + unsafe { NODE.get() } +} + +pub fn try_get_node() -> Option> { + unsafe { NODE.try_get() } +} + +pub fn set_seed(seed: Bip39Seed) { + unsafe { + SEED.set(seed); + } +} + +pub fn get_seed() -> Bip39Seed { + unsafe { SEED.get() } +} + +pub fn try_get_seed() -> Option { + unsafe { SEED.try_get() } +} + +pub fn set_storage(storage: TenTenOneNodeStorage) { + unsafe { + STORAGE.set(storage); + } +} + +pub fn get_storage() -> TenTenOneNodeStorage { + unsafe { STORAGE.get() } +} + +pub fn try_get_storage() -> Option { + unsafe { STORAGE.try_get() } +}