You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[BUG] Missing NIP-01 tie-breaker in eventRepository.upsert causes identically-timestamped events to be dropped #411
Describe the bug
Nostream fails to adhere to the core NIP-01 tie-breaking rule for replaceable events (this behavior was originally defined in the now-deprecated NIP-16). The NIP-01 specification explicitly states: "In case of replaceable events with the same timestamp, the event with the lowest id (first in lexical order) should be retained, and the other discarded."
Currently, in src/repositories/event-repository.ts, the upsert method handles state updates correctly for differing timestamps, but strictly enforces .where('events.event_created_at', '<', row.event_created_at). If two replaceable events with the exact same created_at timestamp are processed concurrently (or sequentially), whichever event evaluates against the database row second will have the < condition resolve to false. It will silently fail with rowCount=0 and be discarded as a duplicate—even if it possesses a lower lexical id that should have won the NIP-01 tie-breaker.
To Reproduce
Steps to reproduce the behavior:
Construct two valid replaceable events (e.g., kind: 10000, or other replaceable kinds) from the same pubkey with the exact same created_at integer timestamp.
Ensure Event A has a lexicographically higherid than Event B.
Publish Event A to the relay first. Wait for it to be accepted.
Publish Event B to the relay.
See error: Event B is silently dropped as a duplicate, and Event A remains persisted, violating the NIP-01 tie-breaker requirement.
Expected behavior
The relay should correctly identify that both events have an identical timestamp and evaluate their ids lexicographically. Event B (having the lower id) should successfully overwrite Event A, and should be broadcasted to subscribers as an update.
Screenshots
N/A
System (please complete the following information):
OS: [e.g. Ubuntu]
Platform: [e.g. docker, standalone]
Version: Latest / Master
Logs
N/A
Additional context
This bug lives in the Knex query builder logic under src/repositories/event-repository.ts on line 234:
To fix this missing NIP-01 tie-breaker, we can update the WHERE clause in src/repositories/event-repository.ts (around line 234) to evaluate the lexical id fallback when timestamps are perfectly identical.
Suggested Fix:
Replace the current .where('events.event_created_at', '<', row.event_created_at) with the following:
.where(function(){this.where('events.event_created_at','<',row.event_created_at).orWhere(function(){this.where('events.event_created_at','=',row.event_created_at).andWhere('events.event_id','>',row.event_id)// Larger existing ID means the new lower ID wins})})
Thanks @YashIIT0909 for the fix! PR #416 has been merged to properly implement the NIP-01 tie-breaker for replaceable events with identical timestamps.
Activity
YashIIT0909 commentedon Apr 9, 2026
To fix this missing NIP-01 tie-breaker, we can update the
WHEREclause in src/repositories/event-repository.ts (around line 234) to evaluate the lexicalidfallback when timestamps are perfectly identical.Suggested Fix:
Replace the current
.where('events.event_created_at', '<', row.event_created_at)with the following:chappie-daemon commentedon Apr 10, 2026
Thanks @YashIIT0909 for the fix! PR #416 has been merged to properly implement the NIP-01 tie-breaker for replaceable events with identical timestamps.