Skip to content

fix: broadcast timeout#494

Merged
frnandu merged 4 commits intomasterfrom
test/add-broadcast-timeout-tests
Mar 30, 2026
Merged

fix: broadcast timeout#494
frnandu merged 4 commits intomasterfrom
test/add-broadcast-timeout-tests

Conversation

@nogringo
Copy link
Copy Markdown
Collaborator

@nogringo nogringo commented Mar 14, 2026

Summary by CodeRabbit

  • Tests
    • Added broadcast reliability tests verifying broadcasts finish within 5 seconds, including offline and no-relay scenarios.
  • Refactor
    • Converted callback chains to async/await and run per-relay work concurrently for faster, clearer flows.
  • Bug Fixes
    • Strengthened transport/null checks and improved detection/emission of broadcast completion.
  • Behavior
    • Deduplicates relay targets, batches concurrent relay operations, and closes network handling when no relays remain.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 14, 2026

📝 Walkthrough

Walkthrough

Await and parallelize relay broadcasts across JIT and relay-set flows, improve relay transport null-safety and broadcast completion detection (including no-relay case), add closeIfNoRelays(), and introduce two timing tests for offline or empty-relay broadcast scenarios.

Changes

Cohort / File(s) Summary
Broadcast tests
packages/ndk/test/broadcast/broadcast_test.dart
Adds two async tests that login with a Bip340 key, create a kind-1 event, call ndk.broadcast.broadcast(...), await broadcastDoneFuture, and assert completion <5s for (1) offline bootstrap relay and (2) empty specificRelays / no bootstrap relays.
Broadcast state
packages/ndk/lib/domain_layer/entities/broadcast_state.dart
Detect publish completion when no broadcasts exist by using networkController.isClosed; trigger _checkBroadcastDone() on subscription onDone; emit final state update before dispose; add void closeIfNoRelays() to close networkController.
JIT engine flow
packages/ndk/lib/domain_layer/usecases/jit_engine/jit_engine.dart
Make previously fire-and-forget broadcasts awaited for specific/other strategies and call broadcastState.closeIfNoRelays() after broadcast branches to ensure completion when no relays are present.
Relay JIT strategies
packages/ndk/lib/domain_layer/usecases/jit_engine/relay_jit_broadcast_strategies/...
relay_jit_broadcast_specific.dart, relay_jit_broadcast_own.dart, relay_jit_broadcast_other_read.dart
Deduplicate relay URLs, refactor then()-based loops into async sendToUrl helpers, run per-relay sends concurrently via Future.wait(..., eagerError: false), and add structured try/catch reporting to relayManager.failBroadcast(...) with clearer messages.
Relay manager
packages/ndk/lib/domain_layer/usecases/relay_manager.dart
Remove onTimeout log branch, fix relayTransport null assignment in catch, and add null checks before inspecting transport properties or short-circuiting on connection state.
Relay sets / broadcast orchestration
packages/ndk/lib/domain_layer/usecases/relay_sets_engine.dart
Handle non-empty specificRelays by awaiting concurrent broadcasts; batch outbox/write-relay and other-read broadcasts with Future.wait; preserve Nip65 fallback but continue flow; call broadcastState.closeIfNoRelays() after broadcasts.

Sequence Diagram(s)

sequenceDiagram
participant Client
participant JIT as JitEngine
participant Sets as RelaySetsEngine
participant Manager as RelayManager
participant Relay as RelayTransport
participant Network as NetworkController

Client->>JIT: request broadcast(event, strategy)
JIT->>Sets: handleEventBroadcast(event, strategy)
Sets->>Manager: select/connect relays (parallel)
Manager->>Relay: connect/reconnect
Relay-->>Manager: connected / failed
Manager-->>Sets: connection results
Sets->>Relay: sendToUrl (concurrent sends)
Relay-->>Network: publish result
Network-->>Sets: publish responses / onDone
Sets->>JIT: broadcast completion (calls closeIfNoRelays() if needed)
JIT->>Client: return broadcast outcome
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • frnandu
  • 1-leo

Poem

🐰 I hopped through awaits and tangled threads,

I nudged closed transports and tidied their sheds,
Relays now sprint in parallel, not one by one,
Tests tap their watches — the broadcasts are done,
A carrot for builds that finish with sun! 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive The title 'fix: broadcast timeout' is vague and does not clearly convey the actual changes made in the PR, which involve refactoring broadcast logic, adding deduplication, improving error handling, and adding timeout tests. Provide a more specific title that describes the actual changes, such as 'refactor: improve broadcast completion and error handling with relay deduplication' or 'fix: ensure broadcasts complete when relays are offline or unavailable'.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch test/add-broadcast-timeout-tests

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 and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
packages/ndk/test/broadcast/broadcast_test.dart (1)

17-40: Consider extracting shared setup/assert flow into a helper.

Both tests repeat the same key generation, login, event creation, timing, and threshold assertion. A helper would reduce duplication and make future timeout-case additions easier.

Also applies to: 51-77

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/ndk/test/broadcast/broadcast_test.dart` around lines 17 - 40, Tests
duplicate key generation, login, event creation, timing and timeout assertion;
extract that shared flow into a helper function (e.g.,
runBroadcastAndAssertWithin) that performs Bip340.generatePrivateKey(), calls
ndk.accounts.loginPrivateKey(privkey:..., pubkey:...), constructs the
Nip01Event(pubKey:..., kind:1, content:'', tags:[]), starts a Stopwatch, invokes
ndk.broadcast.broadcast(nostrEvent: event), awaits
broadcast.broadcastDoneFuture, stops the stopwatch and asserts
stopwatch.elapsedMilliseconds is lessThan a given threshold; replace the
repeated blocks in both test locations with calls to this helper and pass the
threshold as a parameter.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/ndk/test/broadcast/broadcast_test.dart`:
- Around line 6-43: The test currently calls await ndk.destroy() only on the
happy path; wrap the test body so Ndk cleanup always runs by ensuring
ndk.destroy() is invoked in a finally block (or use test framework tearDown)
after creating the Ndk instance, so any exceptions from
broadcast.broadcast/broadcastDoneFuture or failed assertions still call
ndk.destroy(); locate the Ndk instantiation (Ndk(...)), the broadcast invocation
(ndk.broadcast.broadcast and broadcastDoneFuture) and ensure destroy() is called
in finally/tearDown.

---

Nitpick comments:
In `@packages/ndk/test/broadcast/broadcast_test.dart`:
- Around line 17-40: Tests duplicate key generation, login, event creation,
timing and timeout assertion; extract that shared flow into a helper function
(e.g., runBroadcastAndAssertWithin) that performs Bip340.generatePrivateKey(),
calls ndk.accounts.loginPrivateKey(privkey:..., pubkey:...), constructs the
Nip01Event(pubKey:..., kind:1, content:'', tags:[]), starts a Stopwatch, invokes
ndk.broadcast.broadcast(nostrEvent: event), awaits
broadcast.broadcastDoneFuture, stops the stopwatch and asserts
stopwatch.elapsedMilliseconds is lessThan a given threshold; replace the
repeated blocks in both test locations with calls to this helper and pass the
threshold as a parameter.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 5faa1b96-231f-4dbc-8479-47488ca37fcf

📥 Commits

Reviewing files that changed from the base of the PR and between f2cdc15 and c0143d8.

📒 Files selected for processing (1)
  • packages/ndk/test/broadcast/broadcast_test.dart

Comment thread packages/ndk/test/broadcast/broadcast_test.dart
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@packages/ndk/lib/domain_layer/usecases/jit_engine/relay_jit_broadcast_strategies/relay_jit_broadcast_other_read.dart`:
- Around line 91-94: The code uses
relayManager.connectedRelays.firstWhere((element) => element.url == relayUrl)
which can throw a StateError if no relay matches; wrap that lookup in a
try-catch (or use firstWhere with orElse) to handle the missing-relay case, and
only call sendToRelay(relay: relay) when a relay is found; ensure the catch logs
or handles the error similarly to relay_jit_broadcast_specific.dart so behavior
is consistent with other strategies.

In
`@packages/ndk/lib/domain_layer/usecases/jit_engine/relay_jit_broadcast_strategies/relay_jit_broadcast_own.dart`:
- Around line 82-85: The relay lookup using
relayManager.connectedRelays.firstWhere (the block that finds a relay by
relayUrl before calling sendToRelay) can throw StateError if no match is found;
wrap that lookup in a try-catch (matching the pattern used in
relay_jit_broadcast_specific.dart) so if the relay isn't present after
connectRelay we catch the error, skip/continue the loop and do not call
sendToRelay, and optionally log the missing-relay case; update the code around
firstWhere and sendToRelay to handle the exception gracefully.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 35785250-d0da-418e-8cdd-e5cdf17196ec

📥 Commits

Reviewing files that changed from the base of the PR and between c0143d8 and 0c2e56d.

📒 Files selected for processing (7)
  • packages/ndk/lib/domain_layer/entities/broadcast_state.dart
  • packages/ndk/lib/domain_layer/usecases/jit_engine/jit_engine.dart
  • packages/ndk/lib/domain_layer/usecases/jit_engine/relay_jit_broadcast_strategies/relay_jit_broadcast_other_read.dart
  • packages/ndk/lib/domain_layer/usecases/jit_engine/relay_jit_broadcast_strategies/relay_jit_broadcast_own.dart
  • packages/ndk/lib/domain_layer/usecases/jit_engine/relay_jit_broadcast_strategies/relay_jit_broadcast_specific.dart
  • packages/ndk/lib/domain_layer/usecases/relay_manager.dart
  • packages/ndk/lib/domain_layer/usecases/relay_sets_engine.dart

@nogringo nogringo changed the title test: add broadcast timeout tests fix: broadcast timeout Mar 18, 2026
@nogringo nogringo requested review from 1-leo and frnandu March 18, 2026 13:36
@codecov
Copy link
Copy Markdown

codecov Bot commented Mar 25, 2026

Codecov Report

❌ Patch coverage is 67.74194% with 30 lines in your changes missing coverage. Please review.
✅ Project coverage is 76.80%. Comparing base (f2cdc15) to head (ba318d8).
⚠️ Report is 421 commits behind head on master.

Files with missing lines Patch % Lines
...ast_strategies/relay_jit_broadcast_other_read.dart 44.44% 10 Missing ⚠️
...k/lib/domain_layer/usecases/relay_sets_engine.dart 71.42% 8 Missing ⚠️
..._broadcast_strategies/relay_jit_broadcast_own.dart 61.11% 7 Missing ⚠️
...dcast_strategies/relay_jit_broadcast_specific.dart 61.53% 5 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master     #494      +/-   ##
==========================================
+ Coverage   76.74%   76.80%   +0.06%     
==========================================
  Files         153      153              
  Lines        6407     6424      +17     
==========================================
+ Hits         4917     4934      +17     
  Misses       1490     1490              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Comment thread packages/ndk/lib/domain_layer/entities/broadcast_state.dart Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
packages/ndk/lib/domain_layer/usecases/jit_engine/relay_jit_broadcast_strategies/relay_jit_broadcast_other_read.dart (1)

14-18: Consider aligning relay_jit_broadcast_specific.dart with this parallel pattern.

The RelayJitBroadcastSpecificRelaysStrategy in relay_jit_broadcast_specific.dart (lines 28-69) still uses a sequential for loop with await inside, while this file and relay_jit_broadcast_own.dart now use parallel Future.wait. For consistent broadcast behavior and latency characteristics across all strategies, consider applying the same parallel pattern there in a follow-up.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/ndk/lib/domain_layer/usecases/jit_engine/relay_jit_broadcast_strategies/relay_jit_broadcast_other_read.dart`
around lines 14 - 18, The broadcast in RelayJitBroadcastSpecificRelaysStrategy
currently iterates relays with a sequential for loop that awaits each send;
change it to run sends in parallel using Future.wait to match
RelayJitBroadcastOtherReadStrategy and RelayJitBroadcastOwnStrategy. Locate the
static broadcast method in RelayJitBroadcastSpecificRelaysStrategy and replace
the sequential await-per-relay logic with collecting Futures for each relay send
(and any onMessage/connect futures) and await them together via Future.wait,
preserving error handling and any per-relay callbacks.
packages/ndk/lib/domain_layer/usecases/relay_sets_engine.dart (1)

332-351: Deduplicate inbox relay URLs before broadcasting.

myWriteRelayUrlsOthers can contain duplicates across multiple pTags, causing redundant publishes to the same relay.

Proposed refactor
-          List<String> myWriteRelayUrlsOthers = [];
+          final Set<String> myWriteRelayUrlsOthers = <String>{};
@@
-            myWriteRelayUrlsOthers.addAll(maxList);
+            myWriteRelayUrlsOthers.addAll(maxList);
@@
-          await Future.wait(myWriteRelayUrlsOthers.map((relayUrl) =>
+          await Future.wait(myWriteRelayUrlsOthers.map((relayUrl) =>
               doRelayBroadcast(relayUrl, workingEvent)));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/ndk/lib/domain_layer/usecases/relay_sets_engine.dart` around lines
332 - 351, The list myWriteRelayUrlsOthers may contain duplicate relay URLs
gathered from iterating nip65Data which causes redundant publishes via
doRelayBroadcast; change the collection/iteration to deduplicate before
broadcasting (e.g., use a Set or LinkedHashSet to collect unique relay URLs
after building maxList or call .toSet() /
LinkedHashSet.from(myWriteRelayUrlsOthers) and then await Future.wait over the
unique relay URLs) while still respecting
BroadcastDefaults.MAX_INBOX_RELAYS_TO_BROADCAST and the existing logic that
builds completeList/maxList.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/ndk/lib/domain_layer/usecases/relay_sets_engine.dart`:
- Around line 315-318: The else branch logs that bootstrap relays will be used
but never actually replaces writeRelaysUrls (currently sourced from
_relayManager.globalState.relays.keys), so outbox broadcasting can be skipped;
update that branch in relay_sets_engine.dart to set writeRelaysUrls to the
module's known default bootstrap list (replace the writeRelaysUrls value taken
from _relayManager.globalState.relays.keys with the default bootstrap relays
constant or property used in your codebase—e.g., defaultBootstrapRelays or
_relayManager.bootstrapRelays) immediately after the Logger.log.w call so the
subsequent broadcast uses the fallback relays.

---

Nitpick comments:
In
`@packages/ndk/lib/domain_layer/usecases/jit_engine/relay_jit_broadcast_strategies/relay_jit_broadcast_other_read.dart`:
- Around line 14-18: The broadcast in RelayJitBroadcastSpecificRelaysStrategy
currently iterates relays with a sequential for loop that awaits each send;
change it to run sends in parallel using Future.wait to match
RelayJitBroadcastOtherReadStrategy and RelayJitBroadcastOwnStrategy. Locate the
static broadcast method in RelayJitBroadcastSpecificRelaysStrategy and replace
the sequential await-per-relay logic with collecting Futures for each relay send
(and any onMessage/connect futures) and await them together via Future.wait,
preserving error handling and any per-relay callbacks.

In `@packages/ndk/lib/domain_layer/usecases/relay_sets_engine.dart`:
- Around line 332-351: The list myWriteRelayUrlsOthers may contain duplicate
relay URLs gathered from iterating nip65Data which causes redundant publishes
via doRelayBroadcast; change the collection/iteration to deduplicate before
broadcasting (e.g., use a Set or LinkedHashSet to collect unique relay URLs
after building maxList or call .toSet() /
LinkedHashSet.from(myWriteRelayUrlsOthers) and then await Future.wait over the
unique relay URLs) while still respecting
BroadcastDefaults.MAX_INBOX_RELAYS_TO_BROADCAST and the existing logic that
builds completeList/maxList.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: d0329a9c-eae0-419f-bb90-5323e0fe9ebf

📥 Commits

Reviewing files that changed from the base of the PR and between 0c2e56d and 4f5096d.

📒 Files selected for processing (6)
  • packages/ndk/lib/domain_layer/entities/broadcast_state.dart
  • packages/ndk/lib/domain_layer/usecases/jit_engine/jit_engine.dart
  • packages/ndk/lib/domain_layer/usecases/jit_engine/relay_jit_broadcast_strategies/relay_jit_broadcast_other_read.dart
  • packages/ndk/lib/domain_layer/usecases/jit_engine/relay_jit_broadcast_strategies/relay_jit_broadcast_own.dart
  • packages/ndk/lib/domain_layer/usecases/relay_sets_engine.dart
  • packages/ndk/test/broadcast/broadcast_test.dart
✅ Files skipped from review due to trivial changes (1)
  • packages/ndk/test/broadcast/broadcast_test.dart
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/ndk/lib/domain_layer/usecases/jit_engine/jit_engine.dart
  • packages/ndk/lib/domain_layer/entities/broadcast_state.dart

Comment on lines +315 to 318
} else {
Logger.log.w(() =>
"could not find user relay list from nip65, using default bootstrap relays");
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Fallback branch says “bootstrap relays” but does not actually set them.

At Line 316, the warning says bootstrap relays are used, but writeRelaysUrls is still sourced from Line 308 (_relayManager.globalState.relays.keys). If that list is empty/stale, outbox broadcast is skipped unexpectedly.

Proposed fix
         if (nip65List.isNotEmpty) {
           writeRelaysUrls = nip65List.first.relays.entries
               .where((element) => element.value.isWrite)
               .map((e) => e.key)
               .toList();
         } else {
           Logger.log.w(() =>
               "could not find user relay list from nip65, using default bootstrap relays");
+          writeRelaysUrls = cleanRelayUrls(_bootstrapRelays.toList());
         }
📝 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
} else {
Logger.log.w(() =>
"could not find user relay list from nip65, using default bootstrap relays");
}
} else {
Logger.log.w(() =>
"could not find user relay list from nip65, using default bootstrap relays");
writeRelaysUrls = cleanRelayUrls(_bootstrapRelays.toList());
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/ndk/lib/domain_layer/usecases/relay_sets_engine.dart` around lines
315 - 318, The else branch logs that bootstrap relays will be used but never
actually replaces writeRelaysUrls (currently sourced from
_relayManager.globalState.relays.keys), so outbox broadcasting can be skipped;
update that branch in relay_sets_engine.dart to set writeRelaysUrls to the
module's known default bootstrap list (replace the writeRelaysUrls value taken
from _relayManager.globalState.relays.keys with the default bootstrap relays
constant or property used in your codebase—e.g., defaultBootstrapRelays or
_relayManager.bootstrapRelays) immediately after the Logger.log.w call so the
subsequent broadcast uses the fallback relays.

@nogringo nogringo requested a review from frnandu March 28, 2026 19:07
Copy link
Copy Markdown
Collaborator

@frnandu frnandu left a comment

Choose a reason for hiding this comment

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

The RelayJitBroadcastSpecificRelaysStrategy in relay_jit_broadcast_specific.dart (lines 28-69) still uses a sequential for loop with await inside, while this file and relay_jit_broadcast_own.dart now use parallel Future.wait. For consistent broadcast behavior and latency characteristics across all strategies, consider applying the same parallel pattern there in a follow-up.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@packages/ndk/lib/domain_layer/usecases/jit_engine/relay_jit_broadcast_strategies/relay_jit_broadcast_specific.dart`:
- Around line 43-54: The catch currently treats any error (including send
failures) as "relay not found" in the block using connectedRelays.firstWhere and
sendToRelay; change the flow in relay_jit_broadcast_specific.dart so you first
determine existence of the relay without wrapping send in the same try/catch
(e.g., use firstWhere with orElse or a simple connectedRelays.any/check to see
if a relay with url == relayUrl exists), if not found call
relayManager.failBroadcast(eventToPublish.id, relayUrl, "relay not found in
connected list"), otherwise call sendToRelay(relay: relay) inside its own
try/catch and on send errors call relayManager.failBroadcast(eventToPublish.id,
relayUrl, "send failed: <error message>"); apply the same separation of lookup
vs send error handling to the similar block around lines 70-80.
- Around line 41-47: The connectivity check uses
relayManager.isRelayConnected(relayUrl) but the relay instance is retrieved from
a possibly stale connectedRelays snapshot (connectedRelays.firstWhere), causing
false failures; after confirming isRelayConnected, fetch the relay from the same
source (relayManager) instead of connectedRelays — e.g., use a relayManager
method to get the connected relay by relayUrl (or add one if missing) and then
call sendToRelay(relay: ...) so both connectivity and the relay instance come
from relayManager (symbols: relayManager.isRelayConnected, connectedRelays,
sendToRelay, relayUrl).
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: c23c1b33-3034-4836-beec-9ebcb8be7d5b

📥 Commits

Reviewing files that changed from the base of the PR and between 4f5096d and ba318d8.

📒 Files selected for processing (1)
  • packages/ndk/lib/domain_layer/usecases/jit_engine/relay_jit_broadcast_strategies/relay_jit_broadcast_specific.dart

Comment on lines +41 to +47
final isConnected = relayManager.isRelayConnected(relayUrl);
if (isConnected) {
try {
final relay = connectedRelays.firstWhere(
(element) => element.url == relayUrl,
);
sendToRelay(relay: relay);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Use one relay source after connectivity check to avoid false failures.

Line 41 checks connectivity from relayManager, but Line 44 looks up from the connectedRelays snapshot. If that snapshot is stale, a connected relay can be incorrectly marked failed.

Suggested fix
-            final relay = connectedRelays.firstWhere(
+            final relay = relayManager.connectedRelays.firstWhere(
               (element) => element.url == relayUrl,
             );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/ndk/lib/domain_layer/usecases/jit_engine/relay_jit_broadcast_strategies/relay_jit_broadcast_specific.dart`
around lines 41 - 47, The connectivity check uses
relayManager.isRelayConnected(relayUrl) but the relay instance is retrieved from
a possibly stale connectedRelays snapshot (connectedRelays.firstWhere), causing
false failures; after confirming isRelayConnected, fetch the relay from the same
source (relayManager) instead of connectedRelays — e.g., use a relayManager
method to get the connected relay by relayUrl (or add one if missing) and then
call sendToRelay(relay: ...) so both connectivity and the relay instance come
from relayManager (symbols: relayManager.isRelayConnected, connectedRelays,
sendToRelay, relayUrl).

Comment on lines +43 to +54
try {
final relay = connectedRelays.firstWhere(
(element) => element.url == relayUrl,
);
sendToRelay(relay: relay);
} catch (e) {
relayManager.failBroadcast(
eventToPublish.id,
relayUrl,
"relay not found in connected list",
);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Narrow catch scope: current error mapping can misclassify send failures.

The catch blocks label all exceptions as “relay not found”, but they also wrap sendToRelay(...). A send exception will be reported with the wrong reason.

Suggested fix pattern
-          try {
-            final relay = relayManager.connectedRelays
-                .firstWhere((element) => element.url == relayUrl);
-            sendToRelay(relay: relay);
-          } catch (e) {
-            relayManager.failBroadcast(
-              eventToPublish.id,
-              relayUrl,
-              "relay not found after connection",
-            );
-          }
+          final matches = relayManager.connectedRelays
+              .where((element) => element.url == relayUrl);
+          if (matches.isEmpty) {
+            relayManager.failBroadcast(
+              eventToPublish.id,
+              relayUrl,
+              "relay not found after connection",
+            );
+            return;
+          }
+          try {
+            sendToRelay(relay: matches.first);
+          } catch (e) {
+            relayManager.failBroadcast(
+              eventToPublish.id,
+              relayUrl,
+              "send error: $e",
+            );
+          }

Also applies to: 70-80

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/ndk/lib/domain_layer/usecases/jit_engine/relay_jit_broadcast_strategies/relay_jit_broadcast_specific.dart`
around lines 43 - 54, The catch currently treats any error (including send
failures) as "relay not found" in the block using connectedRelays.firstWhere and
sendToRelay; change the flow in relay_jit_broadcast_specific.dart so you first
determine existence of the relay without wrapping send in the same try/catch
(e.g., use firstWhere with orElse or a simple connectedRelays.any/check to see
if a relay with url == relayUrl exists), if not found call
relayManager.failBroadcast(eventToPublish.id, relayUrl, "relay not found in
connected list"), otherwise call sendToRelay(relay: relay) inside its own
try/catch and on send errors call relayManager.failBroadcast(eventToPublish.id,
relayUrl, "send failed: <error message>"); apply the same separation of lookup
vs send error handling to the similar block around lines 70-80.

@nogringo nogringo requested a review from frnandu March 30, 2026 15:09
@frnandu frnandu added this to the 0.8 milestone Mar 30, 2026
@frnandu frnandu merged commit f54a856 into master Mar 30, 2026
7 of 8 checks passed
This was referenced Mar 31, 2026
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.

3 participants