Skip to content

test(iroh): verify custom relay round trips - #8482

Merged
azooz2003-bit merged 1 commit into
feat-iroh-final-integrationfrom
feat-iroh-custom-relay-e2e
Jul 19, 2026
Merged

azooz2003-bit merged 1 commit into
feat-iroh-final-integrationfrom
feat-iroh-custom-relay-e2e

Conversation

@azooz2003-bit

@azooz2003-bit azooz2003-bit commented Jul 19, 2026 •

Copy link
Copy Markdown
Collaborator

Adds opt-in live gates that:

  • bind two production FFI endpoints in relay-only mode
  • prove exact custom relay-map selection
  • exchange request and response bytes over one bidirectional stream
  • assert both selected paths are the configured relay
  • support unauthenticated, shared static-token, and endpoint-bound per-device token providers
  • close disposable endpoints on timeout so external failures are bounded

Verification:

  • 46 focused Iroh transport tests pass
  • 10 macOS Iroh settings tests pass
  • 8 iOS Simulator Iroh settings tests pass on isolated iPhone 16 Pro / iOS 18.4
  • user-provided EU relay selection passes
  • full unauthenticated round trip times out, confirming that relay requires provider credentials despite advertising the route

To run authenticated endpoint-bound coverage, provide CMUX_IROH_CUSTOM_RELAY_BOUND_URL, CMUX_IROH_CUSTOM_RELAY_FIRST_SECRET_KEY_HEX, CMUX_IROH_CUSTOM_RELAY_FIRST_TOKEN, CMUX_IROH_CUSTOM_RELAY_SECOND_SECRET_KEY_HEX, and CMUX_IROH_CUSTOM_RELAY_SECOND_TOKEN, plus CMUX_IROH_CUSTOM_RELAY_LIVE=1.


View with Codesmith Autofix with Codesmith
Need help on this PR? Tag /codesmith with what you need. Autofix is disabled.


Summary by cubic

Adds opt-in live tests that verify custom relay round trips in CmuxIrohTransport. Tests assert exact relay selection and a bidirectional request/response over one stream across unauthenticated, static-token, and endpoint-bound modes.

  • New Features

    • Relay-only live tests binding two endpoints via the configured custom relay.
    • Verify path hints and observed path match the exact relay URL.
    • Bidirectional stream round trip with request/response byte checks.
    • Deterministic timeouts with endpoint cleanup; strict hex parsing for endpoint-bound secret keys.
  • Migration

    • Enable: CMUX_IROH_CUSTOM_RELAY_LIVE=1 (optional CMUX_IROH_CUSTOM_RELAY_TIMEOUT, default 10s).
    • Static mode: CMUX_IROH_CUSTOM_RELAY_STATIC_URL, CMUX_IROH_CUSTOM_RELAY_STATIC_TOKEN.
    • Unauthenticated mode: CMUX_IROH_CUSTOM_RELAY_NO_TOKEN_URL.
    • Endpoint-bound mode: CMUX_IROH_CUSTOM_RELAY_BOUND_URL, CMUX_IROH_CUSTOM_RELAY_FIRST_SECRET_KEY_HEX, CMUX_IROH_CUSTOM_RELAY_FIRST_TOKEN, CMUX_IROH_CUSTOM_RELAY_SECOND_SECRET_KEY_HEX, CMUX_IROH_CUSTOM_RELAY_SECOND_TOKEN.

Written for commit b63de38. Summary will update on new commits.

Review in cubic

@cursor

cursor Bot commented Jul 19, 2026

Copy link
Copy Markdown

Bugbot is paused — on-demand spend limit reached

Bugbot uses usage-based billing for this team and has hit its on-demand spend limit.

A team admin can raise the spend limit in the Cursor dashboard, or wait for the next billing cycle to continue.

@coderabbitai

coderabbitai Bot commented Jul 19, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: d8df9d01-25c9-4ce7-b523-8fa8acbcbc4f

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
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat-iroh-custom-relay-e2e

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.

@greptile-apps

greptile-apps Bot commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds three opt-in live integration tests that bind two real Iroh endpoints in relay-only mode and verify bidirectional QUIC stream round trips over a custom relay, plus an environment helper for parsing per-device hex secret keys. All tests are gated behind CMUX_IROH_CUSTOM_RELAY_LIVE=1 and skip automatically in CI.

  • Three new round-trip test scenarios cover unauthenticated, shared static-token, and per-device endpoint-bound token providers; each builds a ConnectionPair via a withThrowingTaskGroup with a configurable timeout that explicitly closes the FFI endpoints to bound external-network hangs.
  • requiredSecretKey hex parser decodes a 64-character hex env var into a 32-byte CmxIrohSecretKey; the current loop uses String.index(_:offsetBy:2) without limitedBy:, which traps instead of throwing on malformed input.
  • Exact pathHints.map(\\.value) == [relayURL] equality asserts each endpoint reports only the configured relay URL; this could fail spuriously if the FFI surfaces any additional hint in relay-only mode.

Confidence Score: 4/5

Test-only additions that are fully opt-in behind an env-var gate; safe to merge with minor robustness improvements worth addressing.

Both files live entirely in the test target and require explicit opt-in to run. The hex key parser can trap instead of throwing cleanly on contrived malformed input, and the strict pathHints equality assertions could produce spurious failures if the FFI reports additional hints in relay-only mode. Neither affects production behaviour.

Both changed files deserve a second look: CmxIrohCustomRelayLiveEnvironment.swift for the limitedBy fix in the hex loop, and CmxIrohCustomRelayLiveTests.swift for the exact path-hint equality assertions and the Date-vs-ContinuousClock inconsistency.

Important Files Changed

Filename Overview
Packages/Shared/CmuxIrohTransport/Tests/CmuxIrohTransportTests/CmxIrohCustomRelayLiveEnvironment.swift Adds hasEndpointBoundTokenRelay gate and requiredSecretKey hex parser; hex parser has a latent crash path when the env var string has fewer than 64 grapheme clusters but exactly 64 UTF-8 bytes
Packages/Shared/CmuxIrohTransport/Tests/CmuxIrohTransportTests/CmxIrohCustomRelayLiveTests.swift Adds three bidirectional round-trip live tests with properly bounded timeouts; exact pathHints equality assertions may be fragile if relay-only mode surfaces any additional hint; Date used for deadlines where ContinuousClock is already used elsewhere

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant Test as assertBidirectionalRoundTrip
    participant E1 as Endpoint first
    participant Relay as Custom Relay
    participant E2 as Endpoint second

    Test->>E1: factory.bind(secretKey, relayProfile)
    Test->>E2: factory.bind(secretKey, relayProfile)
    Test->>E1: relayAddress polls until relay hint appears
    E1-->>Relay: register via relay URL
    Test->>E2: relayAddress polls until relay hint appears
    E2-->>Relay: register via relay URL

    par connectPair withThrowingTaskGroup
        Test->>E2: second.accept()
        Test->>E1: first.connect(to: secondAddress)
        E1->>Relay: QUIC connect relay-only
        Relay->>E2: forward connection
    and timeout task
        Note over Test: ContinuousClock.sleep(timeout)
    end

    Test->>E1: openBidirectionalStream()
    E2->>E2: acceptBidirectionalStream()
    E1->>Relay: send request bytes
    Relay->>E2: forward request
    E2->>Relay: send response bytes
    Relay->>E1: forward response
    Test->>E1: first.close()
    Test->>E2: second.close()
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant Test as assertBidirectionalRoundTrip
    participant E1 as Endpoint first
    participant Relay as Custom Relay
    participant E2 as Endpoint second

    Test->>E1: factory.bind(secretKey, relayProfile)
    Test->>E2: factory.bind(secretKey, relayProfile)
    Test->>E1: relayAddress polls until relay hint appears
    E1-->>Relay: register via relay URL
    Test->>E2: relayAddress polls until relay hint appears
    E2-->>Relay: register via relay URL

    par connectPair withThrowingTaskGroup
        Test->>E2: second.accept()
        Test->>E1: first.connect(to: secondAddress)
        E1->>Relay: QUIC connect relay-only
        Relay->>E2: forward connection
    and timeout task
        Note over Test: ContinuousClock.sleep(timeout)
    end

    Test->>E1: openBidirectionalStream()
    E2->>E2: acceptBidirectionalStream()
    E1->>Relay: send request bytes
    Relay->>E2: forward request
    E2->>Relay: send response bytes
    Relay->>E1: forward response
    Test->>E1: first.close()
    Test->>E2: second.close()
Loading

Reviews (1): Last reviewed commit: "test(iroh): gate custom relay round trip..." | Re-trigger Greptile

Comment on lines +47 to +63
static func requiredSecretKey(_ name: String) throws -> CmxIrohSecretKey {
let value = try required(name)
guard value.utf8.count == 64 else {
throw EnvironmentError.invalid(name)
}
var bytes = Data(capacity: 32)
var index = value.startIndex
while index < value.endIndex {
let next = value.index(index, offsetBy: 2)
guard let byte = UInt8(value[index ..< next], radix: 16) else {
throw EnvironmentError.invalid(name)
}
bytes.append(byte)
index = next
}
return try CmxIrohSecretKey(bytes: bytes)
}

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.

P2 String.index(_:offsetBy:) without limitedBy: traps instead of throwing when the string has fewer than 64 grapheme clusters but exactly 64 UTF-8 bytes (e.g., 62 ASCII hex chars + one two-byte UTF-8 character). The last loop iteration calls value.index(index, offsetBy: 2) past endIndex, producing a fatal error rather than the expected EnvironmentError.invalid. Using limitedBy: lets the guard catch it cleanly.

Suggested change
static func requiredSecretKey(_ name: String) throws -> CmxIrohSecretKey {
let value = try required(name)
guard value.utf8.count == 64 else {
throw EnvironmentError.invalid(name)
}
var bytes = Data(capacity: 32)
var index = value.startIndex
while index < value.endIndex {
let next = value.index(index, offsetBy: 2)
guard let byte = UInt8(value[index ..< next], radix: 16) else {
throw EnvironmentError.invalid(name)
}
bytes.append(byte)
index = next
}
return try CmxIrohSecretKey(bytes: bytes)
}
static func requiredSecretKey(_ name: String) throws -> CmxIrohSecretKey {
let value = try required(name)
guard value.utf8.count == 64 else {
throw EnvironmentError.invalid(name)
}
var bytes = Data(capacity: 32)
var index = value.startIndex
while index < value.endIndex {
guard let next = value.index(index, offsetBy: 2, limitedBy: value.endIndex) else {
throw EnvironmentError.invalid(name)
}
guard let byte = UInt8(value[index ..< next], radix: 16) else {
throw EnvironmentError.invalid(name)
}
bytes.append(byte)
index = next
}
return try CmxIrohSecretKey(bytes: bytes)
}

Comment on lines +179 to +180
#expect(firstAddress.pathHints.map(\.value) == [relayURL])
#expect(secondAddress.pathHints.map(\.value) == [relayURL])

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.

P2 Exact path-hint equality may be fragile in relay-only mode. The assertions check that each endpoint reports exactly one hint equal to relayURL. If the FFI layer surfaces any additional hint alongside the relay URL (a secondary relay URL, a loopback address, etc.), these fail even though relay selection is correct. The polling helper relayAddress already uses contains(where:) to confirm relay presence; the subsequent strict equality may overconstrain the assertion and produce spurious failures in environments where the endpoint discovers extra hints.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment on lines +279 to +292
let deadline = Date().addingTimeInterval(CmxIrohCustomRelayLiveEnvironment.timeout)
while Date() < deadline {
let address = await endpoint.address()
if address.pathHints.contains(where: {
$0.kind == .relayURL && $0.value == relayURL
}) {
return address
}
if !(await endpoint.isHealthy()) {
throw LiveTestError.endpointClosed
}
try await Task.sleep(for: .milliseconds(100))
}
throw LiveTestError.relayAddressTimedOut

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.

P2 Date()-based deadline tracking is inconsistent with the rest of the timeout strategy. relayAddress and relayPath both compute their deadline via Date().addingTimeInterval(...) which reflects wall-clock time and can jump backward on an NTP correction. connectPair already uses ContinuousClock().sleep, which is monotonic. A backward clock jump during a test would cause the loop to exit immediately with a timeout error rather than waiting the intended duration.

@azooz2003-bit
azooz2003-bit merged commit 0470a97 into feat-iroh-final-integration Jul 19, 2026
10 checks passed
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