diff --git a/applications/minotari_merge_mining_proxy/Cargo.toml b/applications/minotari_merge_mining_proxy/Cargo.toml index ab563a987f..1849557146 100644 --- a/applications/minotari_merge_mining_proxy/Cargo.toml +++ b/applications/minotari_merge_mining_proxy/Cargo.toml @@ -42,6 +42,7 @@ hex = "0.4.2" hyper = { version ="0.14.12", features = ["default"] } jsonrpc = "0.12.0" log = { version = "0.4.8", features = ["std"] } +markup5ever = "0.11.0" monero = { version = "0.21.0" } reqwest = { version = "0.11.4", features = ["json"] } serde = { version = "1.0.136", features = ["derive"] } @@ -57,5 +58,4 @@ scraper = "0.19.0" tari_features = { path = "../../common/tari_features", version = "1.7.0-pre.2" } [dev-dependencies] -markup5ever = "0.11.0" hyper = { version ="0.14.12", features = ["full"] } diff --git a/applications/minotari_merge_mining_proxy/src/config.rs b/applications/minotari_merge_mining_proxy/src/config.rs index 44058daa8f..3e9268e752 100644 --- a/applications/minotari_merge_mining_proxy/src/config.rs +++ b/applications/minotari_merge_mining_proxy/src/config.rs @@ -104,7 +104,40 @@ impl Default for MergeMiningProxyConfig { override_from: None, use_dynamic_fail_data: true, monero_fail_url: MONERO_FAIL_MAINNET_URL.into(), - monerod_url: StringList::default(), + monerod_url: StringList::from(vec![ + "http://node.c3pool.org:18081".to_string(), + "http://xmr-full.p2pool.uk:18089".to_string(), + "http://monero.stackwallet.com:18081".to_string(), + "http://xmr.support:18081".to_string(), + "https://xmr-01.tari.com".to_string(), + "http://node1.xmr-tw.org:18081".to_string(), + "http://monero-g2.hexhex.online:18081".to_string(), + "http://137.220.120.19:18089".to_string(), + "http://185.218.124.120:18489".to_string(), + "http://185.218.124.120:18789".to_string(), + "https://xmr-de-2.boldsuck.org:18081".to_string(), + "http://46.32.46.171:18081".to_string(), + "http://185.218.124.120:18089".to_string(), + "http://185.218.124.120:18589".to_string(), + "http://xmr-de-1.boldsuck.org:18081".to_string(), + "http://185.218.124.120:18889".to_string(), + "http://pinodexmr.hopto.org:18081".to_string(), + "http://node.tincloud.eu:18081".to_string(), + "http://183.6.24.33:18081".to_string(), + "http://147.45.196.232:18089".to_string(), + "http://h-helix.com:18089".to_string(), + "http://185.218.124.120:18689".to_string(), + "http://185.218.124.120:18289".to_string(), + "https://node.tincloud.eu".to_string(), + "https://xmr-de.boldsuck.org:18081".to_string(), + "https://monero.booze.org".to_string(), + "https://xmr.mailia.be:18088".to_string(), + "https://xmr.lolfox.au".to_string(), + "https://xmr1.doggett.tech:18089".to_string(), + "https://node.icefiles.nz:18081".to_string(), + "http://45.8.132.220:18089".to_string(), + "http://82.147.85.13:18089".to_string(), + ]), monerod_username: String::new(), monerod_password: String::new(), monerod_use_auth: false, diff --git a/applications/minotari_merge_mining_proxy/src/error.rs b/applications/minotari_merge_mining_proxy/src/error.rs index 4aab962b3a..f1cf96df2d 100644 --- a/applications/minotari_merge_mining_proxy/src/error.rs +++ b/applications/minotari_merge_mining_proxy/src/error.rs @@ -96,10 +96,10 @@ pub enum MmProxyError { InvalidHeaderValue(#[from] InvalidHeaderValue), #[error("Block was lost due to a failed precondition, and should be retried")] FailedPreconditionBlockLostRetry, - #[error("Could not convert data:{0}")] + #[error("Could not convert data: {0}")] ConversionError(String), - #[error("No reachable servers in configuration")] - ServersUnavailable, + #[error("No reachable servers in configuration: {0}")] + ServersUnavailable(String), #[error("Invalid difficulty: {0}")] DifficultyError(#[from] DifficultyError), #[error("TLS connection error: {0}")] diff --git a/applications/minotari_merge_mining_proxy/src/monero_fail.rs b/applications/minotari_merge_mining_proxy/src/monero_fail.rs index 8e6c224243..c77ca692a0 100644 --- a/applications/minotari_merge_mining_proxy/src/monero_fail.rs +++ b/applications/minotari_merge_mining_proxy/src/monero_fail.rs @@ -20,9 +20,10 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -use std::time::Duration; +use std::{ops::Deref, time::Duration}; use log::*; +use markup5ever::{local_name, namespace_url, ns, QualName}; use scraper::{Html, Selector}; use tokio::time::timeout; use url::Url; @@ -32,7 +33,7 @@ use crate::error::MmProxyError; const LOG_TARGET: &str = "minotari_mm_proxy::monero_detect"; /// Monero public server information -#[derive(Debug)] +#[derive(Debug, Clone, Default)] pub struct MonerodEntry { /// The type of address pub address_type: String, @@ -66,6 +67,14 @@ pub async fn get_monerod_info( monero_fail_url: &str, ) -> Result, MmProxyError> { let document = get_monerod_html(monero_fail_url).await?; + let table_structure = extract_table_structure(&document); + let expected_structure = get_expected_table_structure(); + if table_structure != expected_structure { + return Err(MmProxyError::HtmlParseError(format!( + "Unexpected table structure: {:?}, expected: {:?}", + table_structure, expected_structure + ))); + } // The HTML table definition and an example entry looks like this: // @@ -211,16 +220,42 @@ pub async fn get_monerod_info( entries.sort_by(|a, b| b.height.cmp(&a.height)); // Determine connection times - use slightly more nodes than requested entries.truncate(number_of_entries + 10); + + let entries = order_and_select_monerod_info(number_of_entries, connection_test_timeout, &entries).await?; + + if entries.is_empty() { + return Err(MmProxyError::HtmlParseError( + "No public monero servers available".to_string(), + )); + } + Ok(entries) +} + +pub async fn order_and_select_monerod_info( + number_of_entries: usize, + connection_test_timeout: Duration, + entries: &[MonerodEntry], +) -> Result, MmProxyError> { + let mut entries = entries.to_vec(); for entry in &mut entries { - let uri = format!("{}/getheight", entry.url).parse::()?; - let start = std::time::Instant::now(); - if (timeout(connection_test_timeout, reqwest::get(uri.clone())).await).is_ok() { - entry.response_time = Some(start.elapsed()); - debug!(target: LOG_TARGET, "Response time '{:.2?}' for Monerod server at: {}", entry.response_time, uri.as_str()); - } else { - debug!(target: LOG_TARGET, "Response time 'n/a' for Monerod server at: {}, timed out", uri.as_str()); + if let Ok(url) = format!("{}/getheight", entry.url).parse::() { + let start = std::time::Instant::now(); + if (timeout(connection_test_timeout, reqwest::get(url.clone())).await).is_ok() { + entry.response_time = Some(start.elapsed()); + debug!( + target: LOG_TARGET, "Response time '{:.2?}' for Monerod server at: {}", + entry.response_time, url.as_str() + ); + } else { + debug!( + target: LOG_TARGET, "Response time 'n/a' for Monerod server at: {}, timed out", + url.as_str() + ); + } } } + // Remove entries with no response time + entries.retain(|entry| entry.response_time.is_some()); // Sort by response time entries.sort_by(|a, b| { a.response_time @@ -229,12 +264,6 @@ pub async fn get_monerod_info( }); // Truncate to the requested number of entries entries.truncate(number_of_entries); - - if entries.is_empty() { - return Err(MmProxyError::HtmlParseError( - "No public monero servers available".to_string(), - )); - } Ok(entries) } @@ -256,38 +285,99 @@ async fn get_monerod_html(url: &str) -> Result { Ok(Html::parse_document(&body)) } +// Function to extract table structure from the document +fn extract_table_structure(html_document: &Html) -> Vec<&str> { + let mut table_structure = Vec::new(); + if let Some(table) = html_document.tree.root().descendants().find(|n| { + n.value().is_element() && + n.value().as_element().unwrap().name == QualName::new(None, ns!(html), local_name!("table")) + }) { + if let Some(thead) = table.descendants().find(|n| { + n.value().is_element() && + n.value().as_element().unwrap().name == QualName::new(None, ns!(html), local_name!("thead")) + }) { + if let Some(tr) = thead.descendants().find(|n| { + n.value().is_element() && + n.value().as_element().unwrap().name == QualName::new(None, ns!(html), local_name!("tr")) + }) { + for th in tr.descendants().filter(|n| { + n.value().is_element() && + n.value().as_element().unwrap().name == QualName::new(None, ns!(html), local_name!("th")) + }) { + for child in th.children() { + if let Some(text) = child.value().as_text() { + table_structure.push(text.deref().trim()); + } + } + } + } + } + } + table_structure +} + +fn get_expected_table_structure<'a>() -> Vec<&'a str> { + vec![ + "Type", + "URL", + "Height", + "Up", + "Web", + "Compatible", + "Network", + "Last Checked", + "History", + ] +} + #[cfg(test)] mod test { - use std::{ops::Deref, time::Duration}; - - use markup5ever::{local_name, namespace_url, ns, QualName}; - use scraper::Html; + use std::time::Duration; use crate::{ config::MergeMiningProxyConfig, error::MmProxyError::HtmlParseError, - monero_fail::{get_monerod_html, get_monerod_info}, + monero_fail::{ + extract_table_structure, + get_expected_table_structure, + get_monerod_html, + get_monerod_info, + order_and_select_monerod_info, + MonerodEntry, + }, }; - #[tokio::test] - async fn test_get_monerod_info() { - // Monero mainnet - let config = MergeMiningProxyConfig::default(); - let entries = match get_monerod_info(5, Duration::from_secs(2), &config.monero_fail_url).await { + async fn get_monerod_info_if_online( + number_of_entries: usize, + connection_test_timeout: Duration, + monero_fail_url: &str, + ) -> Vec { + match get_monerod_info(number_of_entries, connection_test_timeout, monero_fail_url).await { Ok(val) => val, Err(HtmlParseError(val)) => { if val.contains("No public monero servers available") { - return; + println!("Cannot complete test: {}", val); + vec![] + } else { + panic!("Unexpected error: {}", val); } - vec![] }, Err(err) => { if err.to_string().contains("Failed to send request to monerod") { - return; + println!("Cannot complete test: {}", err); + vec![] + } else { + panic!("Unexpected error: {}", err); } - panic!("Unexpected error: {}", err); }, - }; + } + } + + #[tokio::test] + async fn test_get_monerod_info() { + // Monero mainnet + let config = MergeMiningProxyConfig::default(); + let entries = get_monerod_info_if_online(5, Duration::from_secs(2), &config.monero_fail_url).await; for (i, entry) in entries.iter().enumerate() { assert!(entry.up && entry.up_history.iter().all(|&v| v)); if i > 0 { @@ -296,14 +386,13 @@ mod test { entries[i - 1].response_time.unwrap_or_else(|| Duration::from_secs(100)) ); } + assert_eq!(entry.network, "mainnet"); println!("{}: {:?}", i, entry); } // Monero stagenet const MONERO_FAIL_STAGNET_URL: &str = "https://monero.fail/?chain=monero&network=stagenet&all=true"; - let entries = get_monerod_info(5, Duration::from_secs(2), MONERO_FAIL_STAGNET_URL) - .await - .unwrap(); + let entries = get_monerod_info_if_online(5, Duration::from_secs(2), MONERO_FAIL_STAGNET_URL).await; for (i, entry) in entries.iter().enumerate() { assert!(entry.up && entry.up_history.iter().all(|&v| v)); if i > 0 { @@ -317,9 +406,7 @@ mod test { // Monero testnet const MONERO_FAIL_TESTNET_URL: &str = "https://monero.fail/?chain=monero&network=testnet&all=true"; - let entries = get_monerod_info(5, Duration::from_secs(2), MONERO_FAIL_TESTNET_URL) - .await - .unwrap(); + let entries = get_monerod_info_if_online(5, Duration::from_secs(2), MONERO_FAIL_TESTNET_URL).await; for (i, entry) in entries.iter().enumerate() { assert!(entry.up && entry.up_history.iter().all(|&v| v)); if i > 0 { @@ -332,6 +419,67 @@ mod test { } } + #[tokio::test] + async fn test_default_monerod_list() { + let config = MergeMiningProxyConfig::default(); + + // Use the default monerod list + let mut entries = Vec::new(); + for url in config.monerod_url.clone().into_vec() { + entries.push(MonerodEntry { + url, + ..Default::default() + }); + } + let ordered_entries = order_and_select_monerod_info(10, Duration::from_secs(5), &entries) + .await + .unwrap(); + for (i, entry) in ordered_entries.iter().enumerate() { + if i > 0 { + assert!( + entry.response_time.unwrap_or_else(|| Duration::from_secs(100)) >= + ordered_entries[i - 1] + .response_time + .unwrap_or_else(|| Duration::from_secs(100)) + ); + } + println!("{}: {:?}", i, entry); + } + + // Use the previously qualified monerod list, but invalidate some URLs + let ordered_entries = ordered_entries + .iter() + .filter(|entry| entry.url.contains(":18")) + .cloned() + .collect::>(); + let mut entries = Vec::new(); + for (i, entry) in ordered_entries.iter().take(5).enumerate() { + entries.push(MonerodEntry { + url: if i >= 2 { + entry.url.replace(":18", ":123") + } else { + entry.url.clone() + }, + ..Default::default() + }); + } + let ordered_entries = order_and_select_monerod_info(15, Duration::from_secs(5), &entries) + .await + .unwrap(); + for (i, entry) in ordered_entries.iter().enumerate() { + if i > 0 { + assert!( + entry.response_time.unwrap_or_else(|| Duration::from_secs(100)) >= + ordered_entries[i - 1] + .response_time + .unwrap_or_else(|| Duration::from_secs(100)) + ); + } + println!("{}: {:?}", i, entry); + } + assert_eq!(ordered_entries.len(), 2); + } + #[tokio::test] async fn test_table_structure() { let config = MergeMiningProxyConfig::default(); @@ -339,6 +487,7 @@ mod test { Ok(val) => val, Err(err) => { if err.to_string().contains("Failed to send request to monerod") { + println!("Cannot complete test: {}", err); return; } panic!("Unexpected error: {}", err); @@ -346,51 +495,16 @@ mod test { }; let table_structure = extract_table_structure(&html_content); - - let expected_structure = vec![ - "Type", - "URL", - "Height", - "Up", - "Web", - "Compatible", - "Network", - "Last Checked", - "History", - ]; + let expected_structure = get_expected_table_structure(); // Compare the actual and expected table structures - assert_eq!(table_structure, expected_structure); - } - - // Function to extract table structure from the document - fn extract_table_structure(html_document: &Html) -> Vec<&str> { - let mut table_structure = Vec::new(); - if let Some(table) = html_document.tree.root().descendants().find(|n| { - n.value().is_element() && - n.value().as_element().unwrap().name == QualName::new(None, ns!(html), local_name!("table")) - }) { - if let Some(thead) = table.descendants().find(|n| { - n.value().is_element() && - n.value().as_element().unwrap().name == QualName::new(None, ns!(html), local_name!("thead")) - }) { - if let Some(tr) = thead.descendants().find(|n| { - n.value().is_element() && - n.value().as_element().unwrap().name == QualName::new(None, ns!(html), local_name!("tr")) - }) { - for th in tr.descendants().filter(|n| { - n.value().is_element() && - n.value().as_element().unwrap().name == QualName::new(None, ns!(html), local_name!("th")) - }) { - for child in th.children() { - if let Some(text) = child.value().as_text() { - table_structure.push(text.deref().trim()); - } - } - } - } - } + if table_structure.is_empty() { + println!( + "Cannot complete test, 'https://monero.fail' seems to be down ({:?})", + html_content.errors + ); + } else { + assert_eq!(table_structure, expected_structure); } - table_structure } } diff --git a/applications/minotari_merge_mining_proxy/src/proxy.rs b/applications/minotari_merge_mining_proxy/src/proxy.rs index 7952ffee4e..fea9e8774d 100644 --- a/applications/minotari_merge_mining_proxy/src/proxy.rs +++ b/applications/minotari_merge_mining_proxy/src/proxy.rs @@ -66,6 +66,9 @@ const LOG_TARGET: &str = "minotari_mm_proxy::proxy"; pub(crate) const MMPROXY_AUX_KEY_NAME: &str = "_aux"; /// The identifier used to identify the tari aux chain data const TARI_CHAIN_ID: &str = "xtr"; +/// The timeout duration for connecting to monerod +pub(crate) const MONEROD_CONNECTION_TIMEOUT: Duration = Duration::from_secs(5); +pub(crate) const NUMBER_OF_MONEROD_SERVERS: usize = 15; #[derive(Debug, Clone)] pub struct MergeMiningProxyService { @@ -93,7 +96,7 @@ impl MergeMiningProxyService { p2pool_client, initial_sync_achieved: Arc::new(AtomicBool::new(false)), current_monerod_server: Arc::new(RwLock::new(None)), - last_assigned_monerod_server: Arc::new(RwLock::new(None)), + last_assigned_monerod_url: Arc::new(RwLock::new(None)), randomx_factory, consensus_manager, wallet_payment_address, @@ -136,10 +139,7 @@ impl Service> for MergeMiningProxyService { match inner.handle(&method_name, request).await { Ok(resp) => Ok(resp), Err(err) => { - error!( - target: LOG_TARGET, - "Method \"{}\" failed handling request: {:?}", method_name, err - ); + error!(target: LOG_TARGET, "Method \"{}\" failed handling request: {:?}", method_name, err); Ok(proxy::json_response( StatusCode::INTERNAL_SERVER_ERROR, &json_rpc::standard_error_response( @@ -166,12 +166,14 @@ struct InnerService { p2pool_client: Option, initial_sync_achieved: Arc, current_monerod_server: Arc>>, - last_assigned_monerod_server: Arc>>, + last_assigned_monerod_url: Arc>>, randomx_factory: RandomXFactory, consensus_manager: ConsensusManager, wallet_payment_address: TariAddress, } +const BUSY_QUALIFYING: &str = "BusyQualifyingMonerodUrl"; + impl InnerService { #[allow(clippy::cast_possible_wrap)] async fn handle_get_height(&self, monerod_resp: Response) -> Result, MmProxyError> { @@ -642,7 +644,39 @@ impl InnerService { Ok(proxy::into_response(parts, &resp)) } - async fn get_fully_qualified_monerod_url(&self, uri: &Uri) -> Result { + fn clear_current_monerod_server_lock(&self) { + let mut lock = self.current_monerod_server.write().expect("Write lock should not fail"); + *lock = None; + trace!( + target: LOG_TARGET, "Monerod status - Current: 'None', Last assigned: {}", + self.last_assigned_monerod_url.read().expect("Read lock should not fail").clone().unwrap_or_default() + ); + } + + fn set_current_monerod_server_lock_busy(&self) { + let mut lock = self.current_monerod_server.write().expect("Write lock should not fail"); + *lock = Some(BUSY_QUALIFYING.to_string()); + trace!( + target: LOG_TARGET, "Monerod status - Current: '{}', Last assigned: {}", + BUSY_QUALIFYING, + self.last_assigned_monerod_url.read().expect("Read lock should not fail").clone().unwrap_or_default() + ); + } + + fn update_monerod_server_locks(&self, server: &str) { + let mut lock = self.current_monerod_server.write().expect("Write lock should not fail"); + *lock = Some(server.to_string()); + let mut lock = self + .last_assigned_monerod_url + .write() + .expect("Write lock should not fail"); + *lock = Some(server.to_string()); + trace!(target: LOG_TARGET, "Monerod status - Current: {}, Last assigned: {}", server, server); + } + + async fn get_fully_qualified_monerod_url(&self, request_uri: &Uri) -> Result { + // Return the previously qualified monerod URL if it exists + let mut parse_error = None; { let lock = self .current_monerod_server @@ -650,62 +684,88 @@ impl InnerService { .expect("Read lock should not fail") .clone(); if let Some(server) = lock { - let uri = format!("{}{}", server, uri.path()).parse::()?; - return Ok(uri); + if server == BUSY_QUALIFYING { + return Err(MmProxyError::ServersUnavailable(BUSY_QUALIFYING.to_string())); + } + match format!("{}{}", server, request_uri.path()).parse::() { + Ok(url) => return Ok(url), + Err(e) => parse_error = Some(e), + } } } + if let Some(e) = parse_error { + self.clear_current_monerod_server_lock(); + return Err(e.into()); + } + // Set the "busy qualifying" state + self.set_current_monerod_server_lock_busy(); + + // Create an iterator to query the list twice before giving up, starting after the last used entry let last_used_url = { let lock = self - .last_assigned_monerod_server + .last_assigned_monerod_url .read() .expect("Read lock should not fail") .clone(); - match lock { - Some(url) => url, - None => "".to_string(), - } + lock.unwrap_or_default() }; - - // Query the list twice before giving up, starting after the last used entry let pos = self .config .monerod_url .iter() .position(|x| x == &last_used_url) .unwrap_or(0); - - let (left, right) = self - .config - .monerod_url - .split_at_checked(pos) - .ok_or(MmProxyError::ConversionError("Invalid utf 8 url".to_string()))?; + let (left, right) = self.config.monerod_url.split_at_checked(pos).ok_or_else(|| { + self.clear_current_monerod_server_lock(); + MmProxyError::ConversionError("Invalid utf 8 url".to_string()) + })?; let left = left.to_vec(); let right = right.to_vec(); let iter = right.iter().chain(left.iter()).chain(right.iter()).chain(left.iter()); - for next_url in iter { - let uri = format!("{}{}", next_url, uri.path()).parse::()?; - debug!(target: LOG_TARGET, "Trying to connect to Monerod server at: {}", uri.as_str()); - match timeout(Duration::from_secs(10), reqwest::get(uri.clone())).await { - Ok(_) => { - let mut lock = self.current_monerod_server.write().expect("Write lock should not fail"); - *lock = Some(next_url.to_string()); - let mut lock = self - .last_assigned_monerod_server - .write() - .expect("Write lock should not fail"); - *lock = Some(next_url.to_string()); - info!(target: LOG_TARGET, "Monerod server available: {}", uri.as_str()); - return Ok(uri); + // Lock the current and last monerod server into the first available server + for server in iter { + let start = Instant::now(); + let url = match format!("{}{}", server, request_uri.path()).parse::() { + Ok(uri) => uri, + Err(e) => { + self.clear_current_monerod_server_lock(); + return Err(e.into()); + }, + }; + let pos = self.config.monerod_url.iter().position(|x| x == server).unwrap_or(0); + debug!( + target: LOG_TARGET, "Trying to connect to Monerod server at: {} (entry {} of {})", + url.as_str(), pos + 1, self.config.monerod_url.len() + ); + match timeout(MONEROD_CONNECTION_TIMEOUT, reqwest::get(url.clone())).await { + Ok(response) => { + self.update_monerod_server_locks(server); + let data_len = match response { + Ok(data) => data.content_length().unwrap_or_default(), + Err(_) => 0, + }; + info!( + target: LOG_TARGET, + "Monerod server available (response in {:.2?}, {} bytes): {}", + start.elapsed(), data_len, url.as_str() + ); + return Ok(url); }, Err(_) => { - warn!(target: LOG_TARGET, "Monerod server unavailable: {}", uri.as_str()); + warn!( + target: LOG_TARGET, + "Monerod server unavailable (timeout in {:.2?}): {}", + start.elapsed(), url.as_str() + ); }, } } - Err(MmProxyError::ServersUnavailable) + // Clear the "busy qualifying" state + self.clear_current_monerod_server_lock(); + Err(MmProxyError::ServersUnavailable(format!("{}", self.config.monerod_url))) } /// Proxy a request received by this server to Monerod @@ -903,16 +963,14 @@ impl InnerService { }, Err(e) => { // Monero Server encountered a problem processing the request, reset the current monerod server - let mut lock = self.current_monerod_server.write().expect("Write lock should not fail"); - *lock = None; + self.clear_current_monerod_server_lock(); Err(e) }, } }, Err(e) => { // Monero Server encountered a problem processing the request, reset the current monerod server - let mut lock = self.current_monerod_server.write().expect("Write lock should not fail"); - *lock = None; + self.clear_current_monerod_server_lock(); Err(e) }, } diff --git a/applications/minotari_merge_mining_proxy/src/run_merge_miner.rs b/applications/minotari_merge_mining_proxy/src/run_merge_miner.rs index f9b14f63e5..d2b60d6ebc 100644 --- a/applications/minotari_merge_mining_proxy/src/run_merge_miner.rs +++ b/applications/minotari_merge_mining_proxy/src/run_merge_miner.rs @@ -45,22 +45,62 @@ use crate::{ block_template_data::BlockTemplateRepository, config::MergeMiningProxyConfig, error::MmProxyError, - monero_fail::get_monerod_info, - proxy::MergeMiningProxyService, + monero_fail::{get_monerod_info, order_and_select_monerod_info, MonerodEntry}, + proxy::{MergeMiningProxyService, MONEROD_CONNECTION_TIMEOUT, NUMBER_OF_MONEROD_SERVERS}, Cli, }; const LOG_TARGET: &str = "minotari_mm_proxy::proxy"; +#[allow(clippy::too_many_lines)] pub async fn start_merge_miner(cli: Cli) -> Result<(), anyhow::Error> { let config_path = cli.common.config_path(); let cfg = load_configuration(&config_path, true, cli.non_interactive_mode, &cli, cli.common.network)?; let mut config = MergeMiningProxyConfig::load_from(&cfg)?; config.set_base_path(cli.common.get_base_path()); + + // Get reputable monerod URLs + let mut assigned_dynamic_fail = false; if config.use_dynamic_fail_data { - let entries = get_monerod_info(15, Duration::from_secs(5), &config.monero_fail_url).await?; - if !entries.is_empty() { - config.monerod_url = StringList::from(entries.into_iter().map(|entry| entry.url).collect::>()); + if let Ok(entries) = get_monerod_info( + NUMBER_OF_MONEROD_SERVERS, + MONEROD_CONNECTION_TIMEOUT, + &config.monero_fail_url, + ) + .await + { + if !entries.is_empty() { + let entries_len = entries.len(); + config.monerod_url = StringList::from(entries.into_iter().map(|entry| entry.url).collect::>()); + assigned_dynamic_fail = true; + debug!( + target: LOG_TARGET, + "Using {} vetted monerod servers from the Monero website at '{}'", + entries_len, config.monero_fail_url + ); + } + } + } + if !assigned_dynamic_fail { + let mut entries = Vec::new(); + for url in config.monerod_url.clone().into_vec() { + entries.push(MonerodEntry { + url, + ..Default::default() + }); + } + if let Ok(entries) = + order_and_select_monerod_info(NUMBER_OF_MONEROD_SERVERS, MONEROD_CONNECTION_TIMEOUT, &entries).await + { + if !entries.is_empty() { + let entries_len = entries.len(); + config.monerod_url = StringList::from(entries.into_iter().map(|entry| entry.url).collect::>()); + debug!( + target: LOG_TARGET, + "Using {} vetted monerod servers from the config list'", + entries_len + ); + } } } diff --git a/base_layer/wallet_ffi/wallet.h b/base_layer/wallet_ffi/wallet.h index b2461d9b5c..cd74fec466 100644 --- a/base_layer/wallet_ffi/wallet.h +++ b/base_layer/wallet_ffi/wallet.h @@ -3968,7 +3968,7 @@ bool wallet_is_recovery_in_progress(struct TariWallet *wallet, * * ## Arguments * `wallet` - The TariWallet pointer. - * `base_node_public_key` - The TariPublicKey pointer of the Base Node the recovery process will use + * `base_node_public_keys` - An optional TariPublicKeys pointer of the Base Nodes the recovery process must use * `recovery_progress_callback` - The callback function pointer that will be used to asynchronously communicate * progress to the client. The first argument of the callback is an event enum encoded as a u8 as follows: * ``` diff --git a/common/config/presets/f_merge_mining_proxy.toml b/common/config/presets/f_merge_mining_proxy.toml index 39c01d9bc6..61c0b40a7b 100644 --- a/common/config/presets/f_merge_mining_proxy.toml +++ b/common/config/presets/f_merge_mining_proxy.toml @@ -19,7 +19,8 @@ #monero_fail_url = "https://monero.fail/?chain=monero&network=mainnet&all=true" # URL to monerod (you can add your own server here or use public nodes from https://monero.fail/), only if -# 'use_dynamic_fail_data = false' (default = "") +# 'use_dynamic_fail_data = false' or if the server at 'monero_fail_url' is not available +# (default: The 'monerod_url = [ # mainnet ...' list below) #monerod_url = [# stagenet # "http://stagenet.xmr-tw.org:38081", @@ -29,17 +30,40 @@ # "http://singapore.node.xmr.pm:38081", #] -monerod_url = [ # mainnet - "http://node1.xmr-tw.org:18081", - "https://monero.homeqloud.com:443", - "http://monero1.com:18089", - "http://node.c3pool.org:18081", - "http://xmr-full.p2pool.uk:18089", - "https://monero.stackwallet.com:18081", - "http://xmr.support:18081", - "http://xmr.nthrow.nyc:18081", - "https://xmr-01.tari.com", -] +#monerod_url = [ # mainnet +# "http://node.c3pool.org:18081", +# "http://xmr-full.p2pool.uk:18089", +# "http://monero.stackwallet.com:18081", +# "http://xmr.support:18081", +# "https://xmr-01.tari.com", +# "http://node1.xmr-tw.org:18081", +# "http://monero-g2.hexhex.online:18081", +# "http://137.220.120.19:18089", +# "http://185.218.124.120:18489", +# "http://185.218.124.120:18789", +# "https://xmr-de-2.boldsuck.org:18081", +# "http://46.32.46.171:18081", +# "http://185.218.124.120:18089", +# "http://185.218.124.120:18589", +# "http://xmr-de-1.boldsuck.org:18081", +# "http://185.218.124.120:18889", +# "http://pinodexmr.hopto.org:18081", +# "http://node.tincloud.eu:18081", +# "http://183.6.24.33:18081", +# "http://147.45.196.232:18089", +# "http://h-helix.com:18089", +# "http://185.218.124.120:18689", +# "http://185.218.124.120:18289", +# "https://node.tincloud.eu", +# "https://xmr-de.boldsuck.org:18081", +# "https://monero.booze.org", +# "https://xmr.mailia.be:18088", +# "https://xmr.lolfox.au", +# "https://xmr1.doggett.tech:18089", +# "https://node.icefiles.nz:18081", +# "http://45.8.132.220:18089", +# "http://82.147.85.13:18089", +#] # Username for curl. (default = "") #monerod_username = ""