Skip to content

Compare Monero node endpoints when checking duplicates#9382

Closed
omurovch wants to merge 4 commits into
version/0.50from
monero-custom-node
Closed

Compare Monero node endpoints when checking duplicates#9382
omurovch wants to merge 4 commits into
version/0.50from
monero-custom-node

Conversation

@omurovch

@omurovch omurovch commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

#8358

Summary by CodeRabbit

  • New Features

    • Added clearer guidance when entering a Monero node URL, including an example format.
    • Added validation requiring node URLs to include an explicit port.
  • Bug Fixes

    • Improved detection of existing Monero nodes across equivalent URL formats.
    • Standardized secure node endpoints without an explicitly specified port.
    • Added a clear error message explaining that a port is required.

omurovch added 2 commits July 13, 2026 15:39
Add validation to ensure that manually entered Monero node URLs include a port number. If a port is missing, an error message is displayed to the user.

- Update `AddMoneroNodeViewModel` to validate the presence of a port in the RPC URL.
- Update `AddMoneroNodeScreen` input hint to show the expected format (e.g., `https://node.com:port`).
- Add a fallback to port 443 in `MoneroNodeManager` for nodes saved before this validation was implemented.
- Add `AddMoneroNode_Error_PortRequired` string resource.
@coderabbitai

coderabbitai Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Important

Review skipped

Review was skipped due to path filters

⛔ Files ignored due to path filters (1)
  • walletkit/src/main/res/values-fa/strings.xml is excluded by !**/res/values-*/strings.xml

CodeRabbit blocks several paths by default. You can override this behavior by explicitly including those paths in the path filters. For example, including **/dist/** will override the default block on the dist directory, by removing the pattern from both the lists.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 276bf7fe-d5eb-4e27-a97a-c47c8fa7842f

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Monero node endpoints now normalize unspecified ports to 443 for serialization and duplicate checks. Adding a node requires an explicit port, with updated input guidance and a dedicated validation message.

Changes

Monero node validation

Layer / File(s) Summary
Endpoint normalization and lookup
walletkit/src/main/java/io/horizontalsystems/walletkit/core/managers/MoneroNodeManager.kt
Node serialization defaults unspecified ports to 443, canonicalizes endpoint values, and exposes hasNode(url) for normalized duplicate detection.
Add-node port validation
walletkit/src/main/java/io/horizontalsystems/walletkit/modules/moneronetwork/addnode/AddMoneroNodeViewModel.kt, walletkit/src/main/java/io/horizontalsystems/walletkit/modules/moneronetwork/addnode/AddMoneroNodeScreen.kt, walletkit/src/main/res/values/strings.xml
Node addition rejects URLs without ports, uses normalized existing-node detection, and displays port examples and the corresponding error message.

Estimated code review effort: 2 (Simple) | ~10 minutes

Suggested reviewers: abdrasulov

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the main change: duplicate detection now compares normalized Monero node endpoints.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch monero-custom-node

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@walletkit/src/main/java/io/horizontalsystems/walletkit/core/managers/MoneroNodeManager.kt`:
- Around line 157-160: Update endpoint() to lowercase uri.host before composing
the canonical host-and-port string, ensuring hostnames with different input
casing produce the same endpoint key while preserving the existing fallback for
invalid or hostless inputs.
- Around line 162-164: Rename the local String variable in hasNode so it no
longer shadows the endpoint helper function, then compare endpoint(it.host)
against the renamed value while preserving the existing node-matching behavior.

In
`@walletkit/src/main/java/io/horizontalsystems/walletkit/modules/moneronetwork/addnode/AddMoneroNodeScreen.kt`:
- Line 65: Move the hardcoded URL hint from the add-node screen into a localized
string resource, then update the hint assignment in AddMoneroNodeScreen to use
stringResource(...) with that resource. Preserve the displayed text as
“https://node.com:port”.

In
`@walletkit/src/main/java/io/horizontalsystems/walletkit/modules/moneronetwork/addnode/AddMoneroNodeViewModel.kt`:
- Around line 61-65: Update the port validation in the AddMoneroNodeViewModel
flow to accept only values in the range 1..65535, rejecting both the
missing-port sentinel and port 0. Change the Caution message to use the “valid
port” wording while preserving the existing error state emission and early
return.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 049b4baf-3fbc-4761-94f2-7573703b41d6

📥 Commits

Reviewing files that changed from the base of the PR and between 05a42b4 and 42c72cc.

⛔ Files ignored due to path filters (1)
  • walletkit/src/main/res/values-fa/strings.xml is excluded by !**/res/values-*/strings.xml
📒 Files selected for processing (4)
  • walletkit/src/main/java/io/horizontalsystems/walletkit/core/managers/MoneroNodeManager.kt
  • walletkit/src/main/java/io/horizontalsystems/walletkit/modules/moneronetwork/addnode/AddMoneroNodeScreen.kt
  • walletkit/src/main/java/io/horizontalsystems/walletkit/modules/moneronetwork/addnode/AddMoneroNodeViewModel.kt
  • walletkit/src/main/res/values/strings.xml

Comment on lines +157 to +160
private fun endpoint(hostOrUrl: String): String {
val uri = hostOrUrl.toUri()
return uri.host?.let { "$it:${effectivePort(uri)}" } ?: hostOrUrl
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/usr/bin/env bash
rg -n -C 3 'fun endpoint|toUri\(\)|hasNode' walletkit --glob '*Test.kt' --glob '*Spec.kt' || true

Repository: horizontalsystems/unstoppable-wallet-android

Length of output: 182


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

# Inspect the manager implementation and any duplicate-check logic/tests.
git ls-files 'walletkit/src/main/java/io/horizontalsystems/walletkit/core/managers/MoneroNodeManager.kt' \
             'walletkit/src/test/**' 'walletkit/src/androidTest/**' | sed -n '1,200p'

echo '--- MoneroNodeManager outline ---'
ast-grep outline walletkit/src/main/java/io/horizontalsystems/walletkit/core/managers/MoneroNodeManager.kt --view expanded || true

echo '--- Relevant lines ---'
nl -ba walletkit/src/main/java/io/horizontalsystems/walletkit/core/managers/MoneroNodeManager.kt | sed -n '1,260p'

echo '--- Search for endpoint / duplicate logic ---'
rg -n -C 3 'endpoint\(|hasNode|duplicate|node.*key|toUri\(' walletkit/src/main/java walletkit/src/test walletkit/src/androidTest || true

Repository: horizontalsystems/unstoppable-wallet-android

Length of output: 1859


🌐 Web query:

Android Uri host lowercase preserves casing Uri.host case normalization

💡 Result:

The Android android.net.Uri class does not automatically perform case normalization (lowercasing) on the host component when using getHost [1][2][3]. The method returns the encoded host as it appears in the URI's authority component, preserving the original casing [2][4][5]. Key details: - Preservation: If the URI string contains "Example.com", getHost will return "Example.com" [3][5]. - Implementation: The getHost method parses the authority component and extracts the host string, applying decoding if necessary, but it does not modify the casing of the returned string [3][4]. - Comparison: While getHost preserves case, developers should be aware that the Uri class's equals method is case-sensitive, meaning two URIs with different casing in their host components will not be considered equal [2][6]. - Normalization: Because the class is designed to be "very forgiving" and performs little validation, it generally does not attempt to normalize components like the host [1][6][3]. If case-insensitive handling of hostnames is required for your application, you must manually convert the string returned by getHost to lowercase using standard Java methods (e.g., host.toLowerCase(Locale.US)) [2][3].

Citations:


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path

p = Path('walletkit/src/main/java/io/horizontalsystems/walletkit/core/managers/MoneroNodeManager.kt')
lines = p.read_text().splitlines()
for start, end in [(145, 175), (175, 205)]:
    print(f'লines {start}-{end}')
    for i in range(start, min(end, len(lines)) + 1):
        print(f"{i:4d}: {lines[i-1]}")
    print()
PY

echo '--- tests mentioning MoneroNodeManager ---'
rg -n -C 3 'MoneroNodeManager|hasNode\(|addMoneroNode\(|endpoint\(' walletkit/src/test walletkit/src/androidTest || true

Repository: horizontalsystems/unstoppable-wallet-android

Length of output: 2738


Normalize hostname casing in endpoint().

uri.host preserves the input casing, so https://Node.Example:18081 and https://node.example:18081 can produce different duplicate-check keys. Lowercase the host before composing the canonical endpoint.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@walletkit/src/main/java/io/horizontalsystems/walletkit/core/managers/MoneroNodeManager.kt`
around lines 157 - 160, Update endpoint() to lowercase uri.host before composing
the canonical host-and-port string, ensuring hostnames with different input
casing produce the same endpoint key while preserving the existing fallback for
invalid or hostless inputs.

Comment on lines +162 to +164
fun hasNode(url: String): Boolean {
val endpoint = endpoint(url)
return allNodes.any { endpoint(it.host) == endpoint }

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/usr/bin/env bash
rg -n -C 2 'val endpoint = endpoint|endpoint\(it\.host\)' walletkit/src/main/java/io/horizontalsystems/walletkit/core/managers/MoneroNodeManager.kt

Repository: horizontalsystems/unstoppable-wallet-android

Length of output: 353


🏁 Script executed:

sed -n '1,260p' walletkit/src/main/java/io/horizontalsystems/walletkit/core/managers/MoneroNodeManager.kt

Repository: horizontalsystems/unstoppable-wallet-android

Length of output: 8469


🏁 Script executed:

python3 - <<'PY'
import shutil, subprocess, textwrap, tempfile, os, sys

print("kotlinc:", shutil.which("kotlinc"))
print("kotlin:", shutil.which("kotlin"))

code = textwrap.dedent("""
fun endpoint(x: String): String = x

fun test(url: String, items: List<String>): Boolean {
    val endpoint = endpoint(url)
    return items.any { endpoint(it) == endpoint }
}
""")

with tempfile.TemporaryDirectory() as d:
    path = os.path.join(d, "Test.kt")
    with open(path, "w") as f:
        f.write(code)
    if shutil.which("kotlinc"):
        p = subprocess.run(["kotlinc", path, "-d", os.path.join(d, "out.jar")], capture_output=True, text=True)
        print("kotlinc exit:", p.returncode)
        print("stdout:\n", p.stdout)
        print("stderr:\n", p.stderr)
    else:
        print("kotlinc not available")
PY

Repository: horizontalsystems/unstoppable-wallet-android

Length of output: 231


🌐 Web query:

Kotlin name resolution local variable shadows function call same name compiler error official docs

💡 Result:

In Kotlin, it is not a compiler error to declare a local variable that has the same name as a function [1]. Kotlin allows this because local declarations are permitted to shadow declarations from outer scopes [2][3]. When a local variable and a function share the same name in the same scope, the local variable takes precedence (shadows the function) [4][5][2]. This is a standard part of Kotlin's name resolution rules, where "local declaration wins" [4]. If you declare a local variable with the same name as a function in the same scope: 1. The compiler will resolve the name to the local variable [4][5]. 2. If you then attempt to call that name as a function (e.g., name), the compiler will attempt to resolve the property/variable as a function-like value using the invoke convention [4][5]. 3. If the local variable is not a function type (i.e., it does not have an invoke operator), the compiler will report an error because the variable shadows the function, and the variable itself cannot be invoked [5]. To access the shadowed function, you cannot simply use the name directly because it is hidden by the local variable [2]. Instead, you must use a fully qualified name (if applicable) or rename one of the declarations to avoid the collision [2].

Citations:


Rename the local endpoint variable

endpoint(it.host) is resolved against the local String, so this call is invalid. Rename the local value and keep the helper callable.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@walletkit/src/main/java/io/horizontalsystems/walletkit/core/managers/MoneroNodeManager.kt`
around lines 162 - 164, Rename the local String variable in hasNode so it no
longer shadows the endpoint helper function, then compare endpoint(it.host)
against the renamed value while preserving the existing node-matching behavior.

qrScannerEnabled = true,
onValueChange = viewModel::onEnterRpcUrl,
hint = "",
hint = "https://node.com:port",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Move the URL hint into a string resource.

This hardcoded visible string bypasses localization. Add a resource and reference it with stringResource(...).

Proposed fix
-                    hint = "https://node.com:port",
+                    hint = stringResource(R.string.AddMoneroNode_UrlHint),
<string name="AddMoneroNode_UrlHint">https://node.com:port</string>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
hint = "https://node.com:port",
hint = stringResource(R.string.AddMoneroNode_UrlHint),
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@walletkit/src/main/java/io/horizontalsystems/walletkit/modules/moneronetwork/addnode/AddMoneroNodeScreen.kt`
at line 65, Move the hardcoded URL hint from the add-node screen into a
localized string resource, then update the hint assignment in
AddMoneroNodeScreen to use stringResource(...) with that resource. Preserve the
displayed text as “https://node.com:port”.

Comment on lines +61 to +65
if (sourceUri.port == -1) {
urlCaution = Caution(Translator.getString(R.string.AddMoneroNode_Error_PortRequired), Caution.Type.Error)
emitState()
return
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the target file around the cited lines and identify sourceUri type/imports.
FILE='walletkit/src/main/java/io/horizontalsystems/walletkit/modules/moneronetwork/addnode/AddMoneroNodeViewModel.kt'
echo '--- file outline ---'
ast-grep outline "$FILE" --view expanded || true
echo
echo '--- relevant lines ---'
sed -n '1,140p' "$FILE" | cat -n
echo
echo '--- search for sourceUri declarations/usages ---'
rg -n "sourceUri|URI\.port|port == -1|port !in" "$FILE" -n -C 3 || true
echo
echo '--- search for Uri/URI imports in file ---'
rg -n "import .*Uri|import .*URI" "$FILE" || true

Repository: horizontalsystems/unstoppable-wallet-android

Length of output: 4824


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Probe URI semantics in the runtime available in the sandbox.
python3 - <<'PY'
from urllib.parse import urlparse

tests = [
    "https://node.example:0",
    "https://node.example",
    "https://node.example:443",
]
for t in tests:
    u = urlparse(t)
    print(t, "-> port attr?", u.port, "netloc", u.netloc, "hostname", u.hostname)
PY

Repository: horizontalsystems/unstoppable-wallet-android

Length of output: 436


Reject port 0 here. sourceUri.port == -1 still accepts https://...:0, so an unusable node can be saved. Require 1..65535 and update the message to say “valid port.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@walletkit/src/main/java/io/horizontalsystems/walletkit/modules/moneronetwork/addnode/AddMoneroNodeViewModel.kt`
around lines 61 - 65, Update the port validation in the AddMoneroNodeViewModel
flow to accept only values in the range 1..65535, rejecting both the
missing-port sentinel and port 0. Change the Caution message to use the “valid
port” wording while preserving the existing error state emission and early
return.

@omurovch omurovch closed this Jul 13, 2026
@omurovch
omurovch deleted the monero-custom-node branch July 13, 2026 09:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant