mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-31 09:52:24 +00:00
Profiling the live app showed the single biggest CPU cost was the relay pool verifying every incoming event's schnorr signature synchronously on the main thread: ~1.2s of secp256k1 work ([email protected] 855ms) during feed load and tab switches, dropping to ~16ms when skipped. The pool was constructed as `new SimplePool()` with no options, so nostr-tools used its default verifying verifyEvent; the `verifyEvent: () => true` that looked like it disabled this only existed on a mock tools object and was never wired to the real pool. Move verification off the main thread instead of dropping it: - Construct the pool with `verifyEvent: () => true` so it never blocks. - Add signatureVerifyWorker (+ client) that verifies event batches in a Web Worker and returns the valid ids; falls back to main-thread verification only when Workers are unavailable (never silently trusts). - VideoEventBuffer verifies each batch via the worker before committing, and drops events that fail (fails closed). The verifier is injectable so tests can virtualize the worker; subscribeVideos passes it through and returns the flush promise from EOSE so commits are awaitable. Tests: new signatureVerifyWorkerClient.test.mjs (worker protocol + drop-invalid + empty/no-id handling); videoEventBuffer.test.mjs and client.test.mjs adapted to the async, verified commit path with a passthrough verifier. Note: tests/nostr-delete-flow.test.mjs has a PRE-EXISTING failure on `should not trigger subscription callbacks` (fails on clean unstable, and is a standalone script not run by the unit-test runner); its edits here only adapt it to the now-async flush. Co-Authored-By: Claude Opus 4.8 <[email protected]>
57 lines
1.8 KiB
JavaScript
57 lines
1.8 KiB
JavaScript
// Web Worker: verifies Nostr event signatures off the main thread.
|
|
//
|
|
// The relay SimplePool is constructed with `verifyEvent: () => true` so it never
|
|
// blocks the main thread verifying incoming events (profiling showed ~1.2s of
|
|
// schnorr work during feed load). Instead, batches of events are verified here,
|
|
// in parallel with the UI, and the caller commits only the ids reported valid.
|
|
//
|
|
// Message protocol:
|
|
// in: { id, events: Array<NostrEvent> }
|
|
// out: { id, ok: true, validIds: string[] } | { id, ok: false, error }
|
|
|
|
import { ensureNostrTools, getCachedNostrTools } from "./toolkit.js";
|
|
|
|
async function resolveTools() {
|
|
const cached = getCachedNostrTools();
|
|
if (cached && typeof cached.verifyEvent === "function") {
|
|
return cached;
|
|
}
|
|
return (await ensureNostrTools()) || cached;
|
|
}
|
|
|
|
function verifyOne(tools, event) {
|
|
try {
|
|
if (!event || typeof event !== "object") return false;
|
|
if (typeof tools.validateEvent === "function" && !tools.validateEvent(event)) {
|
|
return false;
|
|
}
|
|
return tools.verifyEvent(event) === true;
|
|
} catch (_) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
self.addEventListener("message", async (msg) => {
|
|
const data = msg?.data || {};
|
|
const { id, events } = data;
|
|
try {
|
|
const tools = await resolveTools();
|
|
if (!tools || typeof tools.verifyEvent !== "function") {
|
|
throw new Error("signature-verification-unavailable");
|
|
}
|
|
const validIds = [];
|
|
for (const event of Array.isArray(events) ? events : []) {
|
|
if (event && event.id && verifyOne(tools, event)) {
|
|
validIds.push(event.id);
|
|
}
|
|
}
|
|
self.postMessage({ id, ok: true, validIds });
|
|
} catch (error) {
|
|
self.postMessage({
|
|
id,
|
|
ok: false,
|
|
error: { name: error?.name || "Error", message: error?.message || "verify-worker-error" },
|
|
});
|
|
}
|
|
});
|