Merge branch 'unstable' into nip-compliance-audit-4843223870475612808

This commit is contained in:
thePR0M3TH3AN authored and GitHub committed 2026-02-11 17:12:07 -05:00
commit 1470434e3d
5 files changed
+159 -17

No files matched your search

+10
View File
@@ -0,0 +1,10 @@
# Initial Baseline
## Metrics
- Login time: TBD
- Decrypt queue sizes: TBD
- Relay latencies: TBD
- Active WebTorrent clients: TBD
## Observations
- Initial scan pending.
+101
View File
@@ -0,0 +1,101 @@
import { NostrClient } from "../js/nostr/client.js";
import { pMap } from "../js/utils/asyncUtils.js";
// Mock globals for Node environment
globalThis.WebSocket = class MockWebSocket {};
globalThis.localStorage = {
getItem: () => null,
setItem: () => {},
removeItem: () => {},
clear: () => {}
};
globalThis.indexedDB = {
open: () => ({
result: {
createObjectStore: () => {},
transaction: () => ({ objectStore: () => ({ get: () => {}, put: () => {} }) })
},
onupgradeneeded: () => {},
onsuccess: () => {}
})
};
// Mock dependencies
class MockPool {
constructor() {
this.activeRequests = 0;
this.maxConcurrent = 0;
}
async list(relays, filters) {
this.activeRequests++;
if (this.activeRequests > this.maxConcurrent) {
this.maxConcurrent = this.activeRequests;
}
// Simulate network delay
await new Promise(resolve => setTimeout(resolve, 50));
this.activeRequests--;
return [];
}
async get(relays, filter) {
return null;
}
}
async function runBenchmark() {
console.log("Starting Relay Concurrency Benchmark...");
const client = new NostrClient();
const mockPool = new MockPool();
client.pool = mockPool;
// Simulate 20 relays
const relays = Array.from({ length: 20 }, (_, i) => `wss://relay${i}.example.com`);
client.relays = relays;
client.readRelays = relays;
// 1. Benchmark fetchAndCacheNip71Metadata
console.log("\nTesting fetchAndCacheNip71Metadata with 20 relays...");
mockPool.maxConcurrent = 0;
const pointerMap = new Map();
pointerMap.set("e:123", { type: "e", value: "123", relays: [] });
await client.fetchAndCacheNip71Metadata(pointerMap, ["e:123"]);
console.log(`Max concurrent requests: ${mockPool.maxConcurrent}`);
if (mockPool.maxConcurrent > 5) {
console.log("FAIL: Concurrency is unbounded!");
} else {
console.log("PASS: Concurrency is bounded.");
}
// 2. Benchmark hydrateVideoHistory
console.log("\nTesting hydrateVideoHistory with 20 relays...");
mockPool.maxConcurrent = 0;
const video = {
id: "v1",
videoRootId: "root1",
tags: [["d", "d1"]],
pubkey: "pub1"
};
// Prime local cache to force relay fetch
// We need to simulate that we found <= 1 version locally
client.allEvents.set("v1", video);
await client.hydrateVideoHistory(video);
console.log(`Max concurrent requests: ${mockPool.maxConcurrent}`);
if (mockPool.maxConcurrent > 5) {
console.log("FAIL: Concurrency is unbounded!");
} else {
console.log("PASS: Concurrency is bounded.");
}
}
runBenchmark().catch(console.error);
+23
View File
@@ -0,0 +1,23 @@
# Daily Performance Report - 2026-02-08
**Summary:** Bounded background relay concurrency to 3 to prevent network saturation and UI freezes. Verified upload documentation accuracy.
## Findings
### P1: Unbounded Relay Concurrency
- **Location:** `js/nostr/client.js` (`fetchAndCacheNip71Metadata`, `hydrateVideoHistory`, `_sendNip17DirectMessage`).
- **Impact:** Simultaneously opening connections to all relays (could be 20+) caused network congestion and potential UI jank.
- **Fix:** Implemented `pMap` with `RELAY_BACKGROUND_CONCURRENCY = 3`.
- **Status:** Fixed.
## Metrics
- **Max Concurrent Relay Requests:** Reduced from N (number of relays) to 3.
- **Login Time:** (Not measured directly today, but expected improvement due to reduced initial load).
## Docs Audit
- **Page:** `content/docs/guides/upload-content.md`
- **Result:** Verified accurate. Matches code behavior for file types, size recommendations, and HTTPS enforcement.
## Artifacts
- [PR #1] perf: bound relay background concurrency
- `benchmarks/relay_concurrency_repro.mjs` created.
+24 -17
View File
@@ -135,7 +135,7 @@ import {
summarizeDmEventForLog,
} from "./dmDecryptDiagnostics.js";
import { devLogger, userLogger } from "../utils/logger.js";
import { withRequestTimeout } from "../utils/asyncUtils.js";
import { withRequestTimeout, pMap } from "../utils/asyncUtils.js";
import { LRUCache } from "../utils/lruCache.js";
import { updateConversationFromMessage, writeMessages } from "../storage/dmDb.js";
import {
@@ -287,6 +287,7 @@ import {
RELAY_FAILURE_WINDOW_MS,
RELAY_FAILURE_WINDOW_THRESHOLD,
RELAY_SUMMARY_LOG_INTERVAL_MS,
RELAY_BACKGROUND_CONCURRENCY,
} from "./relayConstants.js";
const EVENTS_CACHE_STORAGE_KEY = "bitvid:eventsCache:v1";
// We use the policy TTL, but currently the storage backend is hardcoded to IDB (with localStorage fallback).
@@ -1867,10 +1868,10 @@ export class NostrClient {
devLogger.warn("[nostr] Failed to persist outgoing DM.", error);
}
const recipientPublishResults = await Promise.all(
recipientSelection.relays.map((url) =>
publishEventToRelay(this.pool, url, recipientGiftWrap.wrap),
),
const recipientPublishResults = await pMap(
recipientSelection.relays,
(url) => publishEventToRelay(this.pool, url, recipientGiftWrap.wrap),
{ concurrency: RELAY_BACKGROUND_CONCURRENCY },
);
const recipientSuccess = recipientPublishResults.some(
@@ -1901,10 +1902,10 @@ export class NostrClient {
}
if (senderGiftWrap && senderRelayTargets.length) {
const senderPublishResults = await Promise.all(
senderRelayTargets.map((url) =>
publishEventToRelay(this.pool, url, senderGiftWrap.wrap),
),
const senderPublishResults = await pMap(
senderRelayTargets,
(url) => publishEventToRelay(this.pool, url, senderGiftWrap.wrap),
{ concurrency: RELAY_BACKGROUND_CONCURRENCY },
);
if (!senderPublishResults.some((result) => result.success)) {
devLogger.warn("[nostr] Failed to publish sender copy of NIP-17 DM.", {
@@ -2119,8 +2120,10 @@ export class NostrClient {
const relays = recipientRelays.length ? recipientRelays : fallbackRelays;
const publishResults = await Promise.all(
relays.map((url) => publishEventToRelay(this.pool, url, signedEvent))
const publishResults = await pMap(
relays,
(url) => publishEventToRelay(this.pool, url, signedEvent),
{ concurrency: RELAY_BACKGROUND_CONCURRENCY },
);
const success = publishResults.some((result) => result.success);
@@ -3358,8 +3361,9 @@ export class NostrClient {
};
try {
const responses = await Promise.all(
this.relays.map(async (url) => {
const responses = await pMap(
this.relays,
async (url) => {
try {
const events = await this.pool.list([url], [filter]);
return Array.isArray(events) ? events : [];
@@ -3370,7 +3374,8 @@ export class NostrClient {
);
return [];
}
}),
},
{ concurrency: RELAY_BACKGROUND_CONCURRENCY },
);
const deduped = new Map();
@@ -3882,8 +3887,9 @@ export class NostrClient {
}
try {
const perRelay = await Promise.all(
this.getHealthyRelays(this.relays).map(async (url) => {
const perRelay = await pMap(
this.getHealthyRelays(this.relays),
async (url) => {
try {
const events = await this.pool.list([url], [filter]);
return events || [];
@@ -3894,7 +3900,8 @@ export class NostrClient {
);
return [];
}
})
},
{ concurrency: RELAY_BACKGROUND_CONCURRENCY },
);
const merged = perRelay.flat();
+1
View File
@@ -22,3 +22,4 @@ export const RELAY_CIRCUIT_BREAKER_COOLDOWN_MS = TEN_MINUTES_MS;
export const RELAY_FAILURE_WINDOW_MS = FIVE_MINUTES_MS;
export const RELAY_FAILURE_WINDOW_THRESHOLD = 3;
export const RELAY_SUMMARY_LOG_INTERVAL_MS = MEDIUM_TIMEOUT_MS;
export const RELAY_BACKGROUND_CONCURRENCY = 3;