1import { describe, expect, it } from "vitest";2import { hexToBytes } from "@noble/hashes/utils.js";3import { Client, item, newEvent, newKey, now, RELAY_URL } from "./helpers.ts";45const secondary = process.env.RELAY_SECONDARY_URL;67describe.skipIf(!secondary)("cross-instance reconciliation", () => {8 it("reconciles overlapping actor histories and converges both relays", async () => {9 if (!process.env.CLAIM_SK || !secondary)10 throw new Error(11 "explicit disposable relay URLs and CLAIM_SK are required",12 );13 const keys = [14 hexToBytes(process.env.CLAIM_SK),15 newKey(),16 newKey(),17 newKey(),18 ];19 const tag = `cross-relay-${Date.now()}-${Math.random().toString(16).slice(2)}`;20 const events = keys.flatMap((key, actor) =>21 Array.from({ length: 48 }, (_, n) =>22 newEvent(23 key,24 1,25 `actor ${actor} event ${n}`,26 [["t", tag]],27 now() - 100 + n,28 ),29 ),30 );31 const local = await Client.connect(RELAY_URL);32 const remote = await Client.connect(secondary);33 try {34 for (const key of keys) {35 await local.auth(key, RELAY_URL);36 await remote.auth(key, secondary);37 }38 const left = events.filter((_, n) => n % 3 !== 0);39 const right = events.filter((_, n) => n % 3 !== 1);40 await Promise.all([41 (async () => {42 for (const event of left) await local.publish(event);43 })(),44 (async () => {45 for (const event of right) await remote.publish(event);46 })(),47 ]);48 const filter = { kinds: [1], "#t": [tag] };49 const diff = await remote.sync(left.map(item), filter);50 const leftIDs = new Set(left.map((event) => event.id));51 const rightIDs = new Set(right.map((event) => event.id));52 expect(diff.have).toEqual(53 [...leftIDs].filter((id) => !rightIDs.has(id)).sort(),54 );55 expect(diff.need).toEqual(56 [...rightIDs].filter((id) => !leftIDs.has(id)).sort(),57 );58 const missingHere = await remote.req({ ids: diff.need });59 const missingThere = await local.req({ ids: diff.have });60 for (const event of missingHere.events) await local.publish(event);61 for (const event of missingThere.events) await remote.publish(event);62 const expected = events.map((event) => event.id).sort();63 for (const client of [local, remote]) {64 const result = await client.req(filter);65 expect(result.events.map((event) => event.id).sort()).toEqual(expected);66 }67 const converged = await remote.sync(68 events.map(item),69 filter,70 "converged",71 );72 expect(converged.have).toEqual([]);73 expect(converged.need).toEqual([]);74 console.log(75 JSON.stringify({76 actors: keys.length,77 events: events.length,78 transferredEachWay: diff.have.length,79 converged: true,80 }),81 );82 } finally {83 local.close();84 remote.close();85 }86 }, 90_000);87});