diff --git a/core/src/api.rs b/core/src/api.rs index d9465979..56223cbd 100644 --- a/core/src/api.rs +++ b/core/src/api.rs @@ -128,7 +128,11 @@ async fn pool_status(oracle_pool: Arc) -> Result) -> Result, ApiError> { - let node_api = NodeApi::new(ORACLE_SECRETS.node_api_key.clone(), &ORACLE_CONFIG.node_url); + let node_api = NodeApi::new( + ORACLE_SECRETS.node_api_key.clone(), + ORACLE_SECRETS.wallet_password.clone(), + &ORACLE_CONFIG.node_url, + ); let current_height = node_api.node.current_block_height()? as u32; let pool_box = oracle_pool.get_pool_box_source().get_pool_box()?; let epoch_length = POOL_CONFIG @@ -156,7 +160,11 @@ fn pool_status_sync(oracle_pool: Arc) -> Result Result { let current_height = task::spawn_blocking(move || { - let node_api = NodeApi::new(ORACLE_SECRETS.node_api_key.clone(), &ORACLE_CONFIG.node_url); + let node_api = NodeApi::new( + ORACLE_SECRETS.node_api_key.clone(), + ORACLE_SECRETS.wallet_password.clone(), + &ORACLE_CONFIG.node_url, + ); node_api.node.current_block_height() }) .await @@ -197,7 +205,11 @@ async fn oracle_health(oracle_pool: Arc) -> impl IntoResponse { } fn oracle_health_sync(oracle_pool: Arc) -> Result { - let node_api = NodeApi::new(ORACLE_SECRETS.node_api_key.clone(), &ORACLE_CONFIG.node_url); + let node_api = NodeApi::new( + ORACLE_SECRETS.node_api_key.clone(), + ORACLE_SECRETS.wallet_password.clone(), + &ORACLE_CONFIG.node_url, + ); let current_height = (node_api.node.current_block_height()? as u32).into(); let epoch_length = POOL_CONFIG .refresh_box_wrapper_inputs @@ -239,7 +251,11 @@ async fn pool_health(oracle_pool: Arc) -> impl IntoResponse { } fn pool_health_sync(oracle_pool: Arc) -> Result { - let node_api = NodeApi::new(ORACLE_SECRETS.node_api_key.clone(), &ORACLE_CONFIG.node_url); + let node_api = NodeApi::new( + ORACLE_SECRETS.node_api_key.clone(), + ORACLE_SECRETS.wallet_password.clone(), + &ORACLE_CONFIG.node_url, + ); let current_height = (node_api.node.current_block_height()? as u32).into(); let pool_box = &oracle_pool.get_pool_box_source().get_pool_box()?; let pool_box_height = pool_box.get_box().creation_height.into(); diff --git a/core/src/cli_commands/bootstrap.rs b/core/src/cli_commands/bootstrap.rs index c87d5c34..17280587 100644 --- a/core/src/cli_commands/bootstrap.rs +++ b/core/src/cli_commands/bootstrap.rs @@ -43,9 +43,8 @@ use crate::{ }, explorer_api::wait_for_txs_confirmation, node_interface::{ - assert_wallet_unlocked, node_api::{NodeApi, NodeApiError}, - SignTransactionWithInputs, SubmitTransaction, + try_ensure_wallet_unlocked, SignTransactionWithInputs, SubmitTransaction, }, oracle_config::{BASE_FEE, ORACLE_CONFIG, ORACLE_SECRETS}, oracle_types::{BlockHeight, EpochCounter}, @@ -69,8 +68,12 @@ pub fn bootstrap(config_file_name: String) -> Result<(), anyhow::Error> { let s = std::fs::read_to_string(config_file_name)?; let config: BootstrapConfig = serde_yaml::from_str(&s)?; - let node_api = NodeApi::new(ORACLE_SECRETS.node_api_key.clone(), &oracle_config.node_url); - assert_wallet_unlocked(&node_api.node); + let node_api = NodeApi::new( + ORACLE_SECRETS.node_api_key.clone(), + ORACLE_SECRETS.wallet_password.clone(), + &oracle_config.node_url, + ); + try_ensure_wallet_unlocked(&node_api); let change_address = node_api.get_change_address()?; debug!("Change address: {:?}", change_address); let erg_value_per_box = config.oracle_contract_parameters.min_storage_rent; diff --git a/core/src/main.rs b/core/src/main.rs index c90e0a27..be506993 100644 --- a/core/src/main.rs +++ b/core/src/main.rs @@ -66,8 +66,8 @@ use log::error; use log::LevelFilter; use metrics::start_metrics_server; use metrics::update_metrics; -use node_interface::assert_wallet_unlocked; use node_interface::node_api::NodeApi; +use node_interface::try_ensure_wallet_unlocked; use oracle_config::ORACLE_CONFIG; use oracle_config::ORACLE_SECRETS; use oracle_state::OraclePool; @@ -282,8 +282,12 @@ fn main() { Arc::new(RwLock::new(ActionReportStorage::new())); log_on_launch(); - let node_api = NodeApi::new(ORACLE_SECRETS.node_api_key.clone(), &ORACLE_CONFIG.node_url); - assert_wallet_unlocked(&node_api.node); + let node_api = NodeApi::new( + ORACLE_SECRETS.node_api_key.clone(), + ORACLE_SECRETS.wallet_password.clone(), + &ORACLE_CONFIG.node_url, + ); + try_ensure_wallet_unlocked(&node_api); wait_for_node_rescan(&node_api).unwrap(); let pool_config = &POOL_CONFIG; diff --git a/core/src/metrics.rs b/core/src/metrics.rs index d34acae3..324f24b5 100644 --- a/core/src/metrics.rs +++ b/core/src/metrics.rs @@ -271,7 +271,11 @@ fn update_reward_tokens_in_buyback_box(oracle_pool: Arc) { } pub fn update_metrics(oracle_pool: Arc) -> Result<(), anyhow::Error> { - let node_api = NodeApi::new(ORACLE_SECRETS.node_api_key.clone(), &ORACLE_CONFIG.node_url); + let node_api = NodeApi::new( + ORACLE_SECRETS.node_api_key.clone(), + ORACLE_SECRETS.wallet_password.clone(), + &ORACLE_CONFIG.node_url, + ); let current_height = (node_api.node.current_block_height()? as u32).into(); let network_prefix = node_api.get_change_address()?.network(); let pool_box = &oracle_pool.get_pool_box_source().get_pool_box()?; diff --git a/core/src/node_interface.rs b/core/src/node_interface.rs index 6ba031c6..ed49771f 100644 --- a/core/src/node_interface.rs +++ b/core/src/node_interface.rs @@ -1,3 +1,4 @@ +use crate::node_interface::node_api::NodeApi; use ergo_lib::{ chain::transaction::{unsigned::UnsignedTransaction, Transaction, TxId, TxIoVec}, ergotree_ir::chain::ergo_box::ErgoBox, @@ -62,11 +63,19 @@ impl SignTransactionWithInputs for NodeInterface { } } -pub fn assert_wallet_unlocked(node: &NodeInterface) { - let unlocked = node.wallet_status().unwrap().unlocked; +pub fn try_ensure_wallet_unlocked(node: &NodeApi) { + let unlocked = node.node.wallet_status().unwrap().unlocked; + if !unlocked { - error!("Wallet must be unlocked for node operations"); - std::process::exit(exitcode::SOFTWARE); + if let Some(wallet_pass) = &node.wallet_pass { + if let Err(e) = node.wallet_unlock(wallet_pass) { + error!("Failed to unlock wallet. Wallet must be unlocked for node operations. error: {:?}", e); + std::process::exit(exitcode::SOFTWARE); + } + } else { + error!("Wallet must be unlocked for node operations"); + std::process::exit(exitcode::SOFTWARE); + } } else { debug!("Wallet unlocked"); } diff --git a/core/src/node_interface/node_api.rs b/core/src/node_interface/node_api.rs index 859647cb..b0598f4f 100644 --- a/core/src/node_interface/node_api.rs +++ b/core/src/node_interface/node_api.rs @@ -18,12 +18,13 @@ use crate::wallet::WalletDataSource; pub struct NodeApi { pub node: NodeInterface, + pub wallet_pass: Option, } impl NodeApi { - pub fn new(api_key: String, node_url: &Url) -> Self { + pub fn new(api_key: String, wallet_pass: Option, node_url: &Url) -> Self { let node = NodeInterface::from_url(&api_key, node_url.clone()); - Self { node } + Self { node, wallet_pass } } pub fn get_change_address(&self) -> Result { @@ -95,6 +96,25 @@ impl NodeApi { ); Ok(self.node.submit_transaction(&signed_tx)?) } + + /// Unlock wallet + pub fn wallet_unlock(&self, password: &str) -> Result { + let endpoint = "/wallet/unlock"; + let body = json! ({ + "pass": password, + }); + + let res = self.node.send_post_req(endpoint, body.to_string())?; + + if res.status().is_success() { + Ok(true) + } else { + let json = self.node.parse_response_to_json(Ok(res))?; + Err(NodeApiError::NodeInterfaceError(NodeError::BadRequest( + json["error"].to_string(), + ))) + } + } } impl WalletDataSource for NodeApi { diff --git a/core/src/oracle_config.rs b/core/src/oracle_config.rs index 29d5c1e2..5abbde63 100644 --- a/core/src/oracle_config.rs +++ b/core/src/oracle_config.rs @@ -16,7 +16,7 @@ use ergo_lib::{ }, wallet::tx_builder::{self, SUGGESTED_TX_FEE}, }; -use log::LevelFilter; +use log::{warn, LevelFilter}; use once_cell::sync; use reqwest::Url; use serde::{Deserialize, Serialize}; @@ -41,15 +41,24 @@ pub struct OracleConfig { pub struct OracleSecrets { pub node_api_key: String, + pub wallet_password: Option, } impl OracleSecrets { pub fn load() -> Self { - std::env::var("ORACLE_NODE_API_KEY") - .map(|node_api_key| Self { node_api_key }) - .unwrap_or_else(|_| { - panic!("ORACLE_NODE_API_KEY environment variable for node API key is not set") - }) + let api_key = std::env::var("ORACLE_NODE_API_KEY").unwrap_or_else(|_| { + panic!("ORACLE_NODE_API_KEY environment variable for node API key is not set") + }); + + let wallet_pass = std::env::var("ORACLE_NODE_WALLET_PASSWORD").ok(); + if wallet_pass.is_none() { + warn!("ORACLE_NODE_WALLET_PASSWORD environment variable for automatic unlock of node wallet is not set"); + } + + Self { + node_api_key: api_key, + wallet_password: wallet_pass, + } } } diff --git a/core/src/scans.rs b/core/src/scans.rs index f974f44a..ac1f855c 100644 --- a/core/src/scans.rs +++ b/core/src/scans.rs @@ -41,7 +41,11 @@ pub trait NodeScanId { pub trait ScanGetBoxes: NodeScanId { fn get_boxes(&self) -> Result, ScanError> { - let node_api = NodeApi::new(ORACLE_SECRETS.node_api_key.clone(), &ORACLE_CONFIG.node_url); + let node_api = NodeApi::new( + ORACLE_SECRETS.node_api_key.clone(), + ORACLE_SECRETS.wallet_password.clone(), + &ORACLE_CONFIG.node_url, + ); let boxes = node_api.node.scan_boxes(self.scan_id())?; Ok(boxes) }