From 400d132612b9f6f3c928073e6c9fba3690f73788 Mon Sep 17 00:00:00 2001 From: fiatjaf Date: Fri, 21 Nov 2025 19:51:55 -0300 Subject: [PATCH] nip77: negentropy tests and small fixes. --- nip77.test.ts | 114 ++++++++++++++++++++++++++++++++++++++++++++++++++ nip77.ts | 12 ++++-- 2 files changed, 122 insertions(+), 4 deletions(-) create mode 100644 nip77.test.ts diff --git a/nip77.test.ts b/nip77.test.ts new file mode 100644 index 0000000..8693036 --- /dev/null +++ b/nip77.test.ts @@ -0,0 +1,114 @@ +import { describe, test, expect } from 'bun:test' +import { NegentropySync, NegentropyStorageVector } from './nip77.ts' +import { Relay } from './relay.ts' +import { NostrEvent } from './core.ts' + +// const RELAY = 'ws://127.0.0.1:10547' +const RELAY = 'wss://relay.damus.io' + +describe('NegentropySync', () => { + test('syncs events from ' + RELAY, async () => { + const relay = await Relay.connect(RELAY) + + const storage = new NegentropyStorageVector() + storage.seal() + const filter = { + authors: ['3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d'], + kinds: [30617, 30618], + } + + let ids1: string[] = [] + const done1 = Promise.withResolvers() + const sync1 = new NegentropySync(relay, storage, filter, { + onneed: (id: string) => { + ids1.push(id) + }, + onclose: err => { + expect(err).toBeUndefined() + done1.resolve() + }, + }) + + await sync1.start() + await done1.promise + + expect(ids1.length).toBeGreaterThan(10) + + sync1.close() + + // fetch events + const events1: NostrEvent[] = [] + const fetched = Promise.withResolvers() + const sub = relay.subscribe([{ ids: ids1 }], { + onevent(evt) { + events1.push(evt) + }, + oneose() { + sub.close() + fetched.resolve() + }, + }) + await fetched.promise + expect(events1.map(evt => evt.id).sort()).toEqual(ids1.sort()) + + // Second sync with local events + await relay.connect() + + const storage2 = new NegentropyStorageVector() + for (const evt of events1) { + storage2.insert(evt.created_at, evt.id) + } + storage2.seal() + + let ids2: string[] = [] + let done2 = Promise.withResolvers() + const sync2 = new NegentropySync(relay, storage2, filter, { + onneed: (id: string) => { + ids2.push(id) + }, + onclose: err => { + expect(err).toBeUndefined() + done2.resolve() + }, + }) + + await sync2.start() + await done2.promise + + expect(ids2.length).toBe(0) + + sync2.close() + + // third sync with 4 events removed + const storage3 = new NegentropyStorageVector() + + // shuffle + ids1.sort(() => Math.random() - 0.5) + const removedEvents = ids1.slice(0, 1 + Math.floor(Math.random() * ids1.length - 1)) + for (const evt of events1) { + if (!removedEvents.includes(evt.id)) { + storage3.insert(evt.created_at, evt.id) + } + } + storage3.seal() + + let ids3: string[] = [] + const done3 = Promise.withResolvers() + const sync3 = new NegentropySync(relay, storage3, filter, { + onneed: (id: string) => { + ids3.push(id) + }, + onclose: err => { + expect(err).toBeUndefined() + done3.resolve() + }, + }) + + await sync3.start() + await done3.promise + + expect(ids3.sort()).toEqual(removedEvents.sort()) + + sync3.close() + }) +}) diff --git a/nip77.ts b/nip77.ts index a2abdb5..fb0ae6f 100644 --- a/nip77.ts +++ b/nip77.ts @@ -537,6 +537,7 @@ export class NegentropySync { relay: AbstractRelay storage: NegentropyStorageVector private neg: Negentropy + private filter: Filter private subscription: Subscription private onhave?: (id: string) => void private onneed?: (id: string) => void @@ -557,8 +558,10 @@ export class NegentropySync { this.neg = new Negentropy(storage) this.onhave = params.onhave this.onneed = params.onneed + this.filter = filter - this.subscription = this.relay.prepareSubscription([filter], { label: params.label || 'negentropy' }) + // we prepare a subscription with an empty filter, but it will not be used + this.subscription = this.relay.prepareSubscription([{}], { label: params.label || 'negentropy' }) this.subscription.oncustom = (data: string[]) => { switch (data[0]) { case 'NEG-MSG': { @@ -569,6 +572,9 @@ export class NegentropySync { const response = this.neg.reconcile(data[2], this.onhave, this.onneed) if (response) { this.relay.send(`["NEG-MSG", "${this.subscription.id}", "${response}"]`) + } else { + this.close() + params.onclose?.() } } catch (error) { console.error('negentropy reconcile error:', error) @@ -591,9 +597,7 @@ export class NegentropySync { async start(): Promise { const initMsg = this.neg.initiate() - if (initMsg) { - this.relay.send(`["NEG-OPEN","${this.subscription.id}",${initMsg}]`) - } + this.relay.send(`["NEG-OPEN","${this.subscription.id}",${JSON.stringify(this.filter)},"${initMsg}"]`) } close(): void {