Skip to content

Fix/fetched ranges recording race - #703

Open
nogringo wants to merge 4 commits into
masterfrom
fix/fetched-ranges-recording-race
Open

Fix/fetched ranges recording race#703
nogringo wants to merge 4 commits into
masterfrom
fix/fetched-ranges-recording-race

Conversation

@nogringo

@nogringo nogringo commented Aug 8, 2026

Copy link
Copy Markdown
Collaborator

Summary by CodeRabbit

  • Bug Fixes

    • Improved fetched-range tracking to reflect events actually delivered by the network.
    • Corrected range calculations for limited queries and responses capped by relays.
    • Preserved gaps for older events that were not fetched, with accurate oldest-event boundaries.
  • Tests

    • Added integration coverage for limited queries and relay response limits.
    • Updated expectations to validate returned event counts and accurate fetched-range status.

@nogringo
nogringo requested review from 1-leo and frnandu August 8, 2026 09:53
@nogringo nogringo self-assigned this Aug 8, 2026
@coderabbitai

coderabbitai Bot commented Aug 8, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Request processing records fetched ranges from network-delivered events after response completion. Range bounds use the oldest returned event. Integration tests cover limited queries and relay-capped responses.

Changes

Fetched range tracking

Layer / File(s) Summary
Network event coverage
packages/ndk/lib/domain_layer/usecases/requests/requests.dart
requestNostrEvent tracks network events without buffering full event lists. It records ranges after the response stream closes and uses relay-specific oldest event timestamps.
Coverage integration validation
packages/ndk/test/usecases/fetched_ranges/fetched_ranges_integration_test.dart
Integration tests verify returned counts, fetched ranges, reachedOldest, and gaps for limited queries and relay responses capped below the requested limit.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

  • relaystr/ndk#689: Both PRs modify Requests.requestNostrEvent cache and network request lifecycle behavior.

Suggested reviewers: frnandu, 1-leo

🚥 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 clearly identifies the main change: fixing a race in fetched-range recording.
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 fix/fetched-ranges-recording-race

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.

@codecov

codecov Bot commented Aug 8, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 71.35%. Comparing base (4e28d2e) to head (525453e).

Additional details and impacted files
@@            Coverage Diff             @@
##           master     #703      +/-   ##
==========================================
+ Coverage   71.32%   71.35%   +0.03%     
==========================================
  Files         225      225              
  Lines       13201    13198       -3     
==========================================
+ Hits         9416     9418       +2     
+ Misses       3785     3780       -5     

☔ View full report in Codecov by Harness.
📢 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/usecases/requests/requests.dart
@nogringo
nogringo requested a review from 1-leo August 8, 2026 14:43

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 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 `@packages/ndk/lib/domain_layer/usecases/requests/requests.dart`:
- Around line 582-602: Update the range-recording logic around the EOSE response
handling to avoid using callback-time DateTime.now() as an unbounded until
value. Track the newest received event per relay and use its created-at
timestamp for filters without until; for empty responses, reuse a fixed
request-dispatch cutoff captured when the request was sent. Add a
delayed-response test covering an event published after dispatch and verify gap
detection still reports it.
🪄 Autofix

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 Plus

Run ID: baea84e6-62ce-4542-835f-0acccfa75ce3

📥 Commits

Reviewing files that changed from the base of the PR and between 989216f and 525453e.

📒 Files selected for processing (2)
  • packages/ndk/lib/domain_layer/usecases/requests/requests.dart
  • packages/ndk/test/usecases/fetched_ranges/fetched_ranges_integration_test.dart

Comment on lines 582 to +602
final now = DateTime.now().millisecondsSinceEpoch ~/ 1000;

// Get all events from the replay subject
final events = state.controller.values.toList();

// Group events by source relay
final eventsByRelay = <String, List<Nip01Event>>{};
for (final event in events) {
for (final source in event.sources) {
eventsByRelay.putIfAbsent(source, () => []).add(event);
}
}

for (final entry in state.requests.entries) {
final relayUrl = entry.key;
final relayState = entry.value;

if (!relayState.receivedEOSE) continue;

final relayEvents = eventsByRelay[relayUrl];
final oldestEvent = oldestEventByRelay[relayUrl];

// Record fetched range for each filter sent to this relay
for (final filter in relayState.filters) {
int since;
int until;

if (relayEvents != null && relayEvents.isNotEmpty) {
// Use oldest event timestamp for since, filter.until or now for until
// EOSE means relay has no more events, so fetched range extends to query end
final timestamps = relayEvents.map((e) => e.createdAt).toList();
since = timestamps.reduce((a, b) => a < b ? a : b);
until = filter.until ?? now;
} else if (filter.since != null || filter.until != null) {
// No events but filter has explicit bounds
since = filter.since ?? 0;
until = filter.until ?? now;
} else {
// No events, no bounds - relay has nothing, record 0 to now
since = 0;
until = now;
int since = filter.since ?? 0;
final int until = filter.until ?? now;

if (oldestEvent != null) {
// A relay can cap a response below the requested limit, or with no
// limit in the filter at all (NIP-11 max_limit, which we don't read),
// so a full response is indistinguishable from a truncated one. Only
// claim coverage down to the oldest event received.
since = oldestEvent;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Do not use response-completion time as an unbounded upper bound.

Line 582 captures now after the relay response has completed. For a filter without until, an event can be created after the relay evaluated the request but before this callback runs. The recorded range then claims that event was fetched. Later gap detection can skip the event.

Track the newest received event per relay and use it as until when the filter has no upper bound. For an empty response, use a fixed request-dispatch cutoff. Add a delayed-response test that publishes an event after request dispatch and verifies that it remains a gap.

🤖 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 `@packages/ndk/lib/domain_layer/usecases/requests/requests.dart` around lines
582 - 602, Update the range-recording logic around the EOSE response handling to
avoid using callback-time DateTime.now() as an unbounded until value. Track the
newest received event per relay and use its created-at timestamp for filters
without until; for empty responses, reuse a fixed request-dispatch cutoff
captured when the request was sent. Add a delayed-response test covering an
event published after dispatch and verify gap detection still reports it.

Comment thread packages/ndk/lib/domain_layer/usecases/requests/requests.dart
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.

2 participants