fix: publish NIP-52 events with monotonic created_at (#26)
NIP-52 calendar events (31922/31923) are replaceable and republished whenever inventory changes (a ticket sells). build_nip52_event stamped created_at=int(time.time()); relays only push a replacement to OPEN subscriptions when created_at is strictly newer, so two republishes in the same wall-clock second tie and the second is silently dropped for live subscribers — clients' "tickets remaining" badge stalls until a reload. Same root cause as the webapp fix (aiolabs/webapp#122). - Add monotonic_created_at() in nostr_timestamp.py = max(now, last+1), mirroring the webapp helper + docs/nostr-patterns/replaceable-events.md. - Anchor it on the already-persisted Event.nostr_event_created_at (set after each publish in nostr_hooks.py). The kind-5 delete event is not replaceable, so it keeps plain int(time.time()). - Unit tests mirror the webapp's timestamp suite. Concurrent same-second sales reading the same stored anchor can still collide; full hardening (row-level lock) is noted as follow-up in #26. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
fd12476b90
commit
b5c87c60b4
3 changed files with 74 additions and 1 deletions
|
|
@ -19,6 +19,7 @@ from loguru import logger
|
|||
|
||||
from .models import Event
|
||||
from .nostr.event import NostrEvent
|
||||
from .nostr_timestamp import monotonic_created_at
|
||||
|
||||
|
||||
def _has_time(value: str | None) -> bool:
|
||||
|
|
@ -110,9 +111,15 @@ def build_nip52_event(event: Event, pubkey: str) -> NostrEvent:
|
|||
if event.fiat_currency:
|
||||
tags.append(["tickets_fiat_currency", event.fiat_currency])
|
||||
|
||||
# NIP-52 calendar events are replaceable: this d-tag is republished
|
||||
# whenever inventory changes (a ticket sells). Use a strictly-monotonic
|
||||
# created_at anchored on the last published value so a same-second
|
||||
# republish still outranks the prior version and relays push it to open
|
||||
# subscriptions — a bare int(time.time()) can tie and be silently
|
||||
# dropped, stalling clients' live "tickets remaining" badge.
|
||||
nostr_event = NostrEvent(
|
||||
pubkey=pubkey,
|
||||
created_at=int(time.time()),
|
||||
created_at=monotonic_created_at(event.nostr_event_created_at),
|
||||
kind=kind,
|
||||
tags=tags,
|
||||
content=event.info or "",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue