From ef96586d53a712bf847c1f14d111174bf8cb5248 Mon Sep 17 00:00:00 2001 From: Silberengel Date: Sat, 23 May 2026 07:49:40 +0200 Subject: [PATCH] fix pow --- src/lib/event-pow.test.ts | 19 +++++++++++++++ src/lib/event.ts | 51 +++++++++++++-------------------------- 2 files changed, 36 insertions(+), 34 deletions(-) create mode 100644 src/lib/event-pow.test.ts diff --git a/src/lib/event-pow.test.ts b/src/lib/event-pow.test.ts new file mode 100644 index 00000000..7f2f233f --- /dev/null +++ b/src/lib/event-pow.test.ts @@ -0,0 +1,19 @@ +import { describe, expect, it } from 'vitest' +import { getPow } from 'nostr-tools/nip13' +import { minePow } from '@/lib/event' + +describe('minePow', () => { + it('uses NIP-13 nonce tag and meets requested difficulty', async () => { + const unsigned = { + kind: 1, + content: 'pow test', + tags: [] as string[][], + created_at: 1_700_000_000, + pubkey: 'a'.repeat(64) + } + const mined = await minePow(unsigned, 1) + expect(unsigned.tags).toEqual([]) + expect(mined.tags.some((t) => t[0] === 'nonce' && t[2] === '1')).toBe(true) + expect(getPow(mined.id)).toBeGreaterThanOrEqual(1) + }) +}) diff --git a/src/lib/event.ts b/src/lib/event.ts index d2a078ba..d170978a 100644 --- a/src/lib/event.ts +++ b/src/lib/event.ts @@ -7,7 +7,7 @@ import client from '@/services/client.service' import { TImetaInfo } from '@/types' import { LRUCache } from 'lru-cache' import { Event, getEventHash, kinds, nip19, UnsignedEvent } from 'nostr-tools' -import { getPow } from 'nostr-tools/nip13' +import { minePow as nip13MinePow } from 'nostr-tools/nip13' import { hexPubkeysEqual, normalizeHexPubkey } from './pubkey' import { generateBech32IdFromATag, @@ -727,44 +727,27 @@ export function createFakeEvent(event: Partial): Event { } } +function cloneUnsignedEvent(unsigned: UnsignedEvent): UnsignedEvent { + return { + kind: unsigned.kind, + content: unsigned.content, + tags: unsigned.tags.map((tag) => [...tag]), + created_at: unsigned.created_at, + pubkey: unsigned.pubkey + } +} + +/** NIP-13 PoW via {@link nip13MinePow}; clones input so draft tags are not mutated. */ export async function minePow( unsigned: UnsignedEvent, difficulty: number ): Promise> { - let count = 0 - - const event = unsigned as Omit - const tag = ['nonce', count.toString(), difficulty.toString()] - - event.tags.push(tag) - + const draft = cloneUnsignedEvent(unsigned) return new Promise((resolve) => { - const mine = () => { - let iterations = 0 - - while (iterations < 1000) { - const now = Math.floor(new Date().getTime() / 1000) - - if (now !== event.created_at) { - count = 0 - event.created_at = now - } - - tag[1] = (++count).toString() - event.id = getEventHash(event) - - if (getPow(event.id) >= difficulty) { - resolve(event) - return - } - - iterations++ - } - - setTimeout(mine, 0) - } - - mine() + // Yield once so posting UI can paint before the synchronous mine loop runs. + setTimeout(() => { + resolve(nip13MinePow(draft, difficulty)) + }, 0) }) }