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 higher
id 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:
.merge(omit(['event_pubkey', 'event_kind', 'event_deduplication'])(row))
.where('events.event_created_at', '<', row.event_created_at)
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 samecreated_attimestamp are processed concurrently (or sequentially), whichever event evaluates against the database row second will have the<condition resolve tofalse. It will silently fail withrowCount=0and be discarded as a duplicate—even if it possesses a lower lexicalidthat should have won the NIP-01 tie-breaker.To Reproduce
Steps to reproduce the behavior:
kind: 10000, or other replaceable kinds) from the samepubkeywith the exact samecreated_atinteger timestamp.idthan Event B.Expected behavior
The relay should correctly identify that both events have an identical timestamp and evaluate their
ids lexicographically. Event B (having the lowerid) should successfully overwrite Event A, and should be broadcasted to subscribers as an update.Screenshots
N/A
System (please complete the following information):
Logs
N/A
Additional context
This bug lives in the Knex query builder logic under
src/repositories/event-repository.tson line 234: