diff --git a/monero-rpc-pool/migrations/20250624073414_remove_redundant_full_url_from_node_table.sql b/monero-rpc-pool/migrations/20250624073414_remove_redundant_full_url_from_node_table.sql new file mode 100644 index 00000000..66c858cb --- /dev/null +++ b/monero-rpc-pool/migrations/20250624073414_remove_redundant_full_url_from_node_table.sql @@ -0,0 +1,33 @@ +-- Remove full_url column from monero_nodes table as it can be derived from scheme, host, and port + +-- Drop the index first +DROP INDEX IF EXISTS idx_nodes_full_url; + +-- Create a new table without the full_url column +CREATE TABLE monero_nodes_new ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + scheme TEXT NOT NULL, + host TEXT NOT NULL, + port INTEGER NOT NULL, + network TEXT NOT NULL, -- mainnet/stagenet/testnet - always known at insertion time + first_seen_at TEXT NOT NULL, + created_at TEXT NOT NULL DEFAULT (datetime('now')), + updated_at TEXT NOT NULL DEFAULT (datetime('now')), + -- Create a unique constraint on scheme, host, and port instead of full_url + UNIQUE(scheme, host, port) +); + +-- Copy data from old table to new table +INSERT INTO monero_nodes_new (id, scheme, host, port, network, first_seen_at, created_at, updated_at) +SELECT id, scheme, host, port, network, first_seen_at, created_at, updated_at +FROM monero_nodes; + +-- Drop the old table +DROP TABLE monero_nodes; + +-- Rename the new table to the original name +ALTER TABLE monero_nodes_new RENAME TO monero_nodes; + +-- Recreate the indexes (excluding the full_url index) +CREATE INDEX IF NOT EXISTS idx_nodes_network ON monero_nodes(network); +CREATE INDEX IF NOT EXISTS idx_nodes_scheme_host_port ON monero_nodes(scheme, host, port); \ No newline at end of file diff --git a/monero-rpc-pool/migrations/20250624073558_default_nodes_remove_ssl_where_not_supported.sql b/monero-rpc-pool/migrations/20250624073558_default_nodes_remove_ssl_where_not_supported.sql new file mode 100644 index 00000000..83f2356a --- /dev/null +++ b/monero-rpc-pool/migrations/20250624073558_default_nodes_remove_ssl_where_not_supported.sql @@ -0,0 +1,12 @@ +-- Fix monerodevs.org nodes: change from https to http +UPDATE monero_nodes +SET scheme = 'http' +WHERE host = 'node.monerodevs.org' AND network = 'stagenet'; + +UPDATE monero_nodes +SET scheme = 'http' +WHERE host = 'node2.monerodevs.org' AND network = 'stagenet'; + +UPDATE monero_nodes +SET scheme = 'http' +WHERE host = 'node3.monerodevs.org' AND network = 'stagenet'; \ No newline at end of file diff --git a/monero-rpc-pool/src/database.rs b/monero-rpc-pool/src/database.rs index 630d689f..c5440275 100644 --- a/monero-rpc-pool/src/database.rs +++ b/monero-rpc-pool/src/database.rs @@ -12,7 +12,6 @@ pub struct MoneroNode { pub scheme: String, // http or https pub host: String, pub port: i64, - pub full_url: String, pub network: String, // mainnet, stagenet, or testnet - always known at insertion time pub first_seen_at: String, // ISO 8601 timestamp when first discovered // Computed fields from health_checks (not stored in monero_nodes table) @@ -49,14 +48,14 @@ pub struct HealthCheck { impl MoneroNode { pub fn new(scheme: String, host: String, port: i64, network: String) -> Self { - let full_url = format!("{}://{}:{}", scheme, host, port); + // TODO: Do this in the database let now = chrono::Utc::now().to_rfc3339(); + Self { id: None, scheme, host, port, - full_url, network, first_seen_at: now, // These are computed from health_checks @@ -73,6 +72,10 @@ impl MoneroNode { } } + pub fn full_url(&self) -> String { + format!("{}://{}:{}", self.scheme, self.host, self.port) + } + pub fn success_rate(&self) -> f64 { let total = self.success_count + self.failure_count; if total == 0 { @@ -318,7 +321,6 @@ impl Database { scheme: row.scheme, host: row.host, port: row.port, - full_url: row.full_url, network: row.network, first_seen_at: row.first_seen_at, success_count: row.success_count, @@ -402,7 +404,6 @@ impl Database { scheme: row.scheme, host: row.host, port: row.port, - full_url: row.full_url, network: row.network, first_seen_at: row.first_seen_at, success_count: row.success_count, @@ -592,7 +593,6 @@ impl Database { scheme: row.scheme, host: row.host, port: row.port, - full_url: row.full_url, network: row.network, first_seen_at: row.first_seen_at, success_count: row.success_count, @@ -696,7 +696,6 @@ impl Database { scheme: row.scheme, host: row.host, port: row.port, - full_url: row.full_url, network: row.network, first_seen_at: row.first_seen_at, success_count: row.success_count, @@ -810,7 +809,6 @@ impl Database { scheme: row.scheme, host: row.host, port: row.port, - full_url: row.full_url, network: row.network, first_seen_at: row.first_seen_at, success_count: row.success_count, @@ -917,7 +915,6 @@ impl Database { scheme: row.scheme, host: row.host, port: row.port, - full_url: row.full_url, network: row.network, first_seen_at: row.first_seen_at, success_count: row.success_count, diff --git a/monero-rpc-pool/src/pool.rs b/monero-rpc-pool/src/pool.rs index 9190b969..9f9cb740 100644 --- a/monero-rpc-pool/src/pool.rs +++ b/monero-rpc-pool/src/pool.rs @@ -54,7 +54,7 @@ impl NodePool { } if candidate_nodes.len() == 1 { - return Ok(Some(candidate_nodes[0].full_url.clone())); + return Ok(Some(candidate_nodes[0].full_url())); } // Power of Two Choices: pick 2 random nodes, select the better one @@ -71,9 +71,10 @@ impl NodePool { debug!( "Selected node using P2C for network {}: {}", - self.network, selected.full_url + self.network, selected.full_url() ); - Ok(Some(selected.full_url.clone())) + + Ok(Some(selected.full_url())) } /// Calculate goodness score based on usage-based recency @@ -127,7 +128,7 @@ impl NodePool { .into_iter() .take(5) .map(|node| ReliableNodeInfo { - url: node.full_url.clone(), + url: node.full_url(), success_rate: node.success_rate(), avg_latency_ms: node.avg_latency_ms, }) diff --git a/monero-rpc-pool/src/simple_handlers.rs b/monero-rpc-pool/src/simple_handlers.rs index 59486e30..f718225a 100644 --- a/monero-rpc-pool/src/simple_handlers.rs +++ b/monero-rpc-pool/src/simple_handlers.rs @@ -5,7 +5,7 @@ use axum::{ response::Response, }; use serde_json::json; -use std::{error::Error, time::Instant}; +use std::time::Instant; use tracing::{debug, error, info_span, Instrument}; use uuid::Uuid; @@ -69,7 +69,7 @@ async fn raw_http_request( let client = reqwest::Client::builder() .timeout(std::time::Duration::from_secs(30)) .build() - .map_err(|e| HandlerError::RequestError(e.to_string()))?; + .map_err(|e| HandlerError::RequestError(format!("{:#?}", e)))?; let url = format!("{}{}", node_url, path); @@ -119,19 +119,14 @@ async fn raw_http_request( let response = request_builder .send() .await - .map_err(|e| HandlerError::RequestError(e.to_string()))?; + .map_err(|e| HandlerError::RequestError(format!("{:#?}", e)))?; // Convert to axum Response preserving everything let status = response.status(); let response_headers = response.headers().clone(); let body_bytes = response.bytes().await.map_err(|e| { - let mut error_msg = format!("Failed to read response body: {}", e); - if let Some(source) = e.source() { - error_msg.push_str(&format!(" (source: {})", source)); - } - - HandlerError::RequestError(error_msg) + HandlerError::RequestError(format!("Failed to read response body: {:#?}", e)) })?; let mut axum_response = Response::new(Body::from(body_bytes)); @@ -189,7 +184,7 @@ async fn single_raw_request( let (parts, body_stream) = response.into_parts(); let body_bytes = axum::body::to_bytes(body_stream, usize::MAX) .await - .map_err(|e| HandlerError::RequestError(e.to_string()))?; + .map_err(|e| HandlerError::RequestError(format!("{:#?}", e)))?; if is_jsonrpc_error(&body_bytes) { record_failure(state, &node_url).await; @@ -253,7 +248,7 @@ async fn race_requests( let pool: Vec = reliable_nodes .into_iter() - .map(|node| node.full_url) + .map(|node| node.full_url()) .collect(); pool