Skip to content

Commit 67b8b08

Browse files
committed
fix
1 parent 5058319 commit 67b8b08

6 files changed

Lines changed: 118 additions & 63 deletions

File tree

monero-rpc-pool/src/discovery.rs

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use std::collections::HashSet;
12
use std::time::{Duration, Instant};
23

34
use anyhow::Result;
@@ -76,10 +77,11 @@ impl NodeDiscovery {
7677
let mut nodes = monero_fail_response.monero.web_compatible;
7778
nodes.extend(monero_fail_response.monero.clear);
7879

79-
// Remove duplicates
80+
// Remove duplicates using HashSet for O(n) complexity
81+
let mut seen = HashSet::new();
8082
let mut unique_nodes = Vec::new();
8183
for node in nodes {
82-
if !unique_nodes.contains(&node) {
84+
if seen.insert(node.clone()) {
8385
unique_nodes.push(node);
8486
}
8587
}
@@ -330,7 +332,21 @@ impl NodeDiscovery {
330332
for node_url in nodes.iter() {
331333
if let Ok(url) = url::Url::parse(node_url) {
332334
let scheme = url.scheme();
333-
let host = url.host_str().unwrap_or("");
335+
336+
// Validate scheme - must be http or https
337+
if !matches!(scheme, "http" | "https") {
338+
continue;
339+
}
340+
341+
// Validate host - must be non-empty
342+
let Some(host) = url.host_str() else {
343+
continue;
344+
};
345+
if host.is_empty() {
346+
continue;
347+
}
348+
349+
// Validate port - must be present
334350
let Some(port) = url.port() else {
335351
continue;
336352
};

src-gui/src/renderer/components/pages/help/SettingsBox.tsx

Lines changed: 82 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ import {
5454
Delete,
5555
Edit,
5656
HourglassEmpty,
57+
Refresh,
5758
} from "@mui/icons-material";
5859

5960
import { getNetwork } from "store/config";
@@ -62,6 +63,8 @@ import InfoBox from "renderer/components/modal/swap/InfoBox";
6263
import { isValidMultiAddressWithPeerId } from "utils/parseUtils";
6364

6465
import { useAppSelector } from "store/hooks";
66+
import { getNodeStatus } from "renderer/rpc";
67+
import { setStatus } from "store/features/nodesSlice";
6568

6669
const PLACEHOLDER_ELECTRUM_RPC_URL = "ssl://blockstream.info:700";
6770
const PLACEHOLDER_MONERO_NODE_URL = "http://xmr-node.cakewallet.com:18081";
@@ -343,15 +346,49 @@ function MoneroRpcPoolSetting() {
343346
}
344347

345348
/**
346-
* A setting that allows you to select the Monero Node URL to use.
347-
* Only shown when RPC pool is disabled.
349+
* A setting that allows you to configure a single Monero Node URL.
350+
* Gets disabled when RPC pool is enabled.
348351
*/
349352
function MoneroNodeUrlSetting() {
350-
const [tableVisible, setTableVisible] = useState(false);
351353
const network = getNetwork();
352354
const useMoneroRpcPool = useSettings((s) => s.useMoneroRpcPool);
355+
const moneroNodeUrl = useSettings((s) => s.nodes[network][Blockchain.Monero][0] || "");
356+
const nodeStatuses = useNodes((s) => s.nodes);
357+
const dispatch = useAppDispatch();
358+
const [isRefreshing, setIsRefreshing] = useState(false);
359+
360+
const currentNodes = useSettings((s) => s.nodes[network][Blockchain.Monero]);
361+
362+
const handleNodeUrlChange = (newUrl: string) => {
363+
// Remove existing nodes and add the new one
364+
currentNodes.forEach(node => {
365+
dispatch(removeNode({ network, type: Blockchain.Monero, node }));
366+
});
367+
368+
if (newUrl.trim()) {
369+
dispatch(addNode({ network, type: Blockchain.Monero, node: newUrl.trim() }));
370+
}
371+
};
372+
373+
const handleRefreshStatus = async () => {
374+
// Don't refresh if pool is enabled or no node URL is configured
375+
if (!moneroNodeUrl || useMoneroRpcPool) return;
376+
377+
setIsRefreshing(true);
378+
try {
379+
const status = await getNodeStatus(moneroNodeUrl, Blockchain.Monero, network);
380+
381+
// Update the status in the store
382+
dispatch(setStatus({ node: moneroNodeUrl, status, blockchain: Blockchain.Monero }));
383+
} catch (error) {
384+
console.error("Failed to refresh node status:", error);
385+
} finally {
386+
setIsRefreshing(false);
387+
}
388+
};
353389

354-
const isValid = (url: string) => isValidUrl(url, ["http"]);
390+
const isValid = (url: string) => url === "" || isValidUrl(url, ["http"]);
391+
const nodeStatus = moneroNodeUrl ? nodeStatuses[Blockchain.Monero][moneroNodeUrl] : null;
355392

356393
return (
357394
<TableRow>
@@ -360,32 +397,55 @@ function MoneroNodeUrlSetting() {
360397
label="Custom Monero Node URL"
361398
tooltip={
362399
useMoneroRpcPool
363-
? "This setting is disabled because Monero RPC pool is enabled. Disable the RPC pool to configure custom nodes."
400+
? "This setting is disabled because Monero RPC pool is enabled. Disable the RPC pool to configure a custom node."
364401
: "This is the URL of the Monero node that the GUI will connect to. It is used to sync Monero transactions. If you leave this field empty, the GUI will choose from a list of known servers at random."
365402
}
366403
disabled={useMoneroRpcPool}
367404
/>
368405
</TableCell>
369406
<TableCell>
370-
<IconButton
371-
onClick={() => setTableVisible(true)}
372-
size="large"
373-
disabled={useMoneroRpcPool}
374-
>
375-
{<Edit />}
376-
</IconButton>
377-
{tableVisible ? (
378-
<NodeTableModal
379-
open={tableVisible}
380-
onClose={() => setTableVisible(false)}
381-
network={network}
382-
blockchain={Blockchain.Monero}
383-
isValid={isValid}
407+
<Box sx={{ display: "flex", alignItems: "center", gap: 1 }}>
408+
<ValidatedTextField
409+
value={moneroNodeUrl}
410+
onValidatedChange={handleNodeUrlChange}
384411
placeholder={PLACEHOLDER_MONERO_NODE_URL}
412+
disabled={useMoneroRpcPool}
413+
fullWidth
414+
isValid={isValid}
415+
variant="outlined"
416+
noErrorWhenEmpty
385417
/>
386-
) : (
387-
<></>
388-
)}
418+
<>
419+
<Tooltip title={
420+
useMoneroRpcPool
421+
? "Node status checking is disabled when using the pool"
422+
: !moneroNodeUrl
423+
? "Enter a node URL to check status"
424+
: "Node status"
425+
}>
426+
<Box sx={{ display: "flex", alignItems: "center" }}>
427+
<Circle
428+
color={useMoneroRpcPool || !moneroNodeUrl ? "gray" : (nodeStatus ? "green" : "red")}
429+
/>
430+
</Box>
431+
</Tooltip>
432+
<Tooltip title={
433+
useMoneroRpcPool
434+
? "Node status refresh is disabled when using the pool"
435+
: !moneroNodeUrl
436+
? "Enter a node URL to refresh status"
437+
: "Refresh node status"
438+
}>
439+
<IconButton
440+
onClick={handleRefreshStatus}
441+
disabled={isRefreshing || useMoneroRpcPool || !moneroNodeUrl}
442+
size="small"
443+
>
444+
{isRefreshing ? <HourglassEmpty /> : <Refresh />}
445+
</IconButton>
446+
</Tooltip>
447+
</>
448+
</Box>
389449
</TableCell>
390450
</TableRow>
391451
);

src-gui/src/renderer/rpc.ts

Lines changed: 7 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -223,43 +223,20 @@ export async function initializeContext() {
223223
const bitcoinNodes =
224224
store.getState().settings.nodes[network][Blockchain.Bitcoin];
225225

226-
// For Monero nodes, check if we should use RPC pool or custom nodes
226+
// For Monero nodes, get the configured node URL and pool setting
227227
const useMoneroRpcPool = store.getState().settings.useMoneroRpcPool;
228-
let moneroNode = null;
229-
230-
if (!useMoneroRpcPool) {
231-
// Using custom nodes - check availability and use the first working one
232-
const moneroNodes =
233-
store.getState().settings.nodes[network][Blockchain.Monero];
234-
235-
if (moneroNodes.length > 0) {
236-
try {
237-
moneroNode = await Promise.any(
238-
moneroNodes.map(async (node) => {
239-
const isAvailable = await getNodeStatus(
240-
node,
241-
Blockchain.Monero,
242-
network,
243-
);
244-
if (isAvailable) {
245-
return node;
246-
}
247-
throw new Error(`Monero node ${node} is not available`);
248-
}),
249-
);
250-
} catch {
251-
// If no Monero node is available, use null
252-
moneroNode = null;
253-
}
254-
}
255-
}
256-
// If useMoneroRpcPool is true, moneroNode stays null and the backend will use RPC pool
228+
const moneroNodes = store.getState().settings.nodes[network][Blockchain.Monero];
229+
230+
// Always pass the first configured monero node URL directly without checking availability
231+
// The backend will handle whether to use the pool or the custom node
232+
const moneroNode = moneroNodes.length > 0 ? moneroNodes[0] : null;
257233

258234
// Initialize Tauri settings
259235
const tauriSettings: TauriSettings = {
260236
electrum_rpc_urls: bitcoinNodes,
261237
monero_node_url: moneroNode,
262238
use_tor: useTor,
239+
use_monero_rpc_pool: useMoneroRpcPool,
263240
};
264241

265242
logger.info("Initializing context with settings", tauriSettings);

src-tauri/src/lib.rs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -368,13 +368,10 @@ async fn initialize_context(
368368
.to_string_result()?;
369369

370370
// Determine which Monero node to use:
371-
// - If a specific node URL is provided in settings, use that
372-
// - If None, start and use the local RPC pool
373-
let monero_node_url = if let Some(provided_url) = settings.monero_node_url.clone() {
374-
// User provided a specific node URL
375-
Some(provided_url)
376-
} else {
377-
// No specific node provided, start RPC pool and use it
371+
// - If using RPC pool, start and use the local RPC pool
372+
// - Otherwise, use the provided node URL directly (even if empty)
373+
let monero_node_url = if settings.use_monero_rpc_pool {
374+
// Start RPC pool and use it
378375
let data_dir = data::data_dir_from(None, testnet).to_string_result()?;
379376
match monero_rpc_pool::start_server_with_random_port(
380377
monero_rpc_pool::config::Config::new_random_port(
@@ -407,6 +404,9 @@ async fn initialize_context(
407404
None
408405
}
409406
}
407+
} else {
408+
// Use the provided node URL directly without checking availability
409+
settings.monero_node_url.clone()
410410
};
411411

412412
// Get app handle and create a Tauri handle

swap/src/cli/api/request.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1192,7 +1192,7 @@ pub async fn monero_recovery(
11921192
}
11931193
}
11941194

1195-
#[tracing::instrument(fields(method = "get_current_swap"), skip(_context))]
1195+
#[tracing::instrument(fields(method = "get_current_swap"), skip(context))]
11961196
pub async fn get_current_swap(context: Arc<Context>) -> Result<serde_json::Value> {
11971197
Ok(json!({
11981198
"swap_id": context.swap_lock.get_current_swap_id().await,

swap/src/cli/api/tauri_bindings.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -714,6 +714,8 @@ pub struct TauriSettings {
714714
pub electrum_rpc_urls: Vec<String>,
715715
/// Whether to initialize and use a tor client.
716716
pub use_tor: bool,
717+
/// Whether to use the Monero RPC pool instead of custom nodes.
718+
pub use_monero_rpc_pool: bool,
717719
}
718720

719721
#[typeshare]

0 commit comments

Comments
 (0)