From 78cae13885112d93c5336ec092ceef4afb614cef Mon Sep 17 00:00:00 2001 From: Siddharth Singh Date: Thu, 30 May 2024 00:05:07 +0530 Subject: [PATCH 1/5] fix kind 0 lookups for account search --- .gitignore | 3 ++- src/queries.ts | 14 ++++++++++++++ src/utils/lookup.ts | 14 +++++++------- 3 files changed, 23 insertions(+), 8 deletions(-) diff --git a/.gitignore b/.gitignore index 39dbfbbb..ec391bf6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .env *.cpuprofile *.swp -deno-test.xml \ No newline at end of file +deno-test.xml +*.db \ No newline at end of file diff --git a/src/queries.ts b/src/queries.ts index 1fccb68d..74d32611 100644 --- a/src/queries.ts +++ b/src/queries.ts @@ -48,6 +48,19 @@ const getAuthor = async (pubkey: string, opts: GetEventOpts = {}): Promise event); }; +const getAuthorFallback = async (pubkey: string, opts: GetEventOpts = {}): Promise => { + const author = await getAuthor(pubkey, opts); + const { signal = AbortSignal.timeout(1000) } = opts; + + if (author) return author; + + const pool = await Storages.client(); + return await pool.query([{ authors: [pubkey], kinds: [0], limit: 1 }], { limit: 1, signal }) + .then((events) => hydrateEvents({ events, store: pool, signal })) + .then(([event]) => event); + +}; + /** Get users the given pubkey follows. */ const getFollows = async (pubkey: string, signal?: AbortSignal): Promise => { const store = await Storages.db(); @@ -113,6 +126,7 @@ async function isLocallyFollowed(pubkey: string): Promise { export { getAncestors, getAuthor, + getAuthorFallback, getDescendants, getEvent, getFeedPubkeys, diff --git a/src/utils/lookup.ts b/src/utils/lookup.ts index 90b30c2b..0f163172 100644 --- a/src/utils/lookup.ts +++ b/src/utils/lookup.ts @@ -1,6 +1,6 @@ import { NIP05, NostrEvent, NSchema as n } from '@nostrify/nostrify'; -import { getAuthor } from '@/queries.ts'; +import { getAuthorFallback } from '@/queries.ts'; import { bech32ToPubkey } from '@/utils.ts'; import { nip05Cache } from '@/utils/nip05.ts'; import { Stickynotes } from '@soapbox/stickynotes'; @@ -13,21 +13,21 @@ export async function lookupAccount( const pubkey = await lookupPubkey(value, signal); if (pubkey) { - return getAuthor(pubkey); + return getAuthorFallback(pubkey); } } /** Resolve a bech32 or NIP-05 identifier to a pubkey. */ -export async function lookupPubkey(value: string, signal?: AbortSignal): Promise { +export async function lookupPubkey(identifier: string, signal?: AbortSignal): Promise { const console = new Stickynotes('ditto:lookup'); - if (n.bech32().safeParse(value).success) { - return bech32ToPubkey(value); + if (n.bech32().safeParse(identifier).success) { + return bech32ToPubkey(identifier); } - if (NIP05.regex().test(value)) { + if (NIP05.regex().test(identifier)) { try { - const { pubkey } = await nip05Cache.fetch(value, { signal }); + const { pubkey } = await nip05Cache.fetch(identifier, { signal }); return pubkey; } catch (e) { console.debug(e); From cf5655f14f935da0a83842a56e1e64bcbcb23674 Mon Sep 17 00:00:00 2001 From: Siddharth Singh Date: Thu, 30 May 2024 19:45:28 +0530 Subject: [PATCH 2/5] create special relay pool with popular relays for getting events out of. --- src/queries.ts | 26 +++++++++++++++----------- src/storages.ts | 26 ++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 11 deletions(-) diff --git a/src/queries.ts b/src/queries.ts index 74d32611..9e35277c 100644 --- a/src/queries.ts +++ b/src/queries.ts @@ -1,4 +1,4 @@ -import { NostrEvent, NostrFilter } from '@nostrify/nostrify'; +import { NostrEvent, NostrFilter, NStore } from '@nostrify/nostrify'; import Debug from '@soapbox/stickynotes/debug'; import { Conf } from '@/config.ts'; @@ -38,27 +38,31 @@ const getEvent = async ( .then(([event]) => event); }; +const queryAndUnwrapEvt = (store: NStore, filter: NostrFilter, signal = AbortSignal.timeout(5000), limit = 1) => + store.query([filter], { limit, signal } as any) + .then((events) => hydrateEvents({ events, store, signal })) + .then(([event]) => event); + /** Get a Nostr `set_medatadata` event for a user's pubkey. */ const getAuthor = async (pubkey: string, opts: GetEventOpts = {}): Promise => { const store = await Storages.db(); const { signal = AbortSignal.timeout(1000) } = opts; - - return await store.query([{ authors: [pubkey], kinds: [0], limit: 1 }], { limit: 1, signal }) - .then((events) => hydrateEvents({ events, store, signal })) - .then(([event]) => event); + return await queryAndUnwrapEvt(store, { authors: [pubkey], kinds: [0], limit: 1 }, signal); }; const getAuthorFallback = async (pubkey: string, opts: GetEventOpts = {}): Promise => { const author = await getAuthor(pubkey, opts); - const { signal = AbortSignal.timeout(1000) } = opts; - if (author) return author; - const pool = await Storages.client(); - return await pool.query([{ authors: [pubkey], kinds: [0], limit: 1 }], { limit: 1, signal }) - .then((events) => hydrateEvents({ events, store: pool, signal })) - .then(([event]) => event); + const pool = await Storages.kind0Finder(); + const res = await queryAndUnwrapEvt(pool, { authors: [pubkey], kinds: [0], limit: 1 }, opts.signal); + if (res) { + const store = await Storages.db(); + store.event(res); + } + + return res; }; /** Get users the given pubkey follows. */ diff --git a/src/storages.ts b/src/storages.ts index f8f206d1..6f93ab0f 100644 --- a/src/storages.ts +++ b/src/storages.ts @@ -15,6 +15,7 @@ export class Storages { private static _client: Promise | undefined; private static _pubsub: Promise | undefined; private static _search: Promise | undefined; + private static _kind0Finder: PoolStore | undefined; /** SQLite database to store events this Ditto server cares about. */ public static async db(): Promise { @@ -43,6 +44,31 @@ export class Storages { return this._pubsub; } + public static async kind0Finder(): Promise { + if (this._kind0Finder) return this._kind0Finder; + const worker = new Worker('https://unpkg.com/nostr-relaypool2@0.6.34/lib/nostr-relaypool.worker.js', { + type: 'module', + }); + + const DEFAULT_RELAYS = [ + 'wss://nos.lol', + 'wss://relay.damus.io', + 'wss://purplepag.es', + 'wss://nostr.mom', + 'wss://relay.snort.social', + 'wss://relay.primal.net', + ]; + + // @ts-ignore Wrong types. + const pool = new RelayPoolWorker(worker, DEFAULT_RELAYS, { + autoReconnect: true, + skipVerification: false, + logErrorsAndNotices: false, + }); + + return this._kind0Finder = new PoolStore({ pool, relays: DEFAULT_RELAYS }); + } + /** Relay pool storage. */ public static async client(): Promise { if (!this._client) { From 126e482e0db9f78c65a296fdd466a3235dea1191 Mon Sep 17 00:00:00 2001 From: Siddharth Singh Date: Thu, 30 May 2024 19:59:52 +0530 Subject: [PATCH 3/5] fix nostr-tools version --- deno.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deno.json b/deno.json index ac13eb4b..7886a5c7 100644 --- a/deno.json +++ b/deno.json @@ -51,7 +51,7 @@ "linkifyjs": "npm:linkifyjs@^4.1.1", "lru-cache": "npm:lru-cache@^10.2.2", "nostr-relaypool": "npm:nostr-relaypool2@0.6.34", - "nostr-tools": "npm:nostr-tools@^2.5.1", + "nostr-tools": "npm:nostr-tools@2.5.1", "nostr-wasm": "npm:nostr-wasm@^0.1.0", "tldts": "npm:tldts@^6.0.14", "tseep": "npm:tseep@^1.2.1", From d5135200e847d65363da8c1b7337057a88cd44bf Mon Sep 17 00:00:00 2001 From: Siddharth Singh Date: Sat, 1 Jun 2024 14:21:36 +0530 Subject: [PATCH 4/5] update other controllers to also use the getAuthorFallback method --- src/controllers/api/accounts.ts | 6 +++--- src/controllers/api/statuses.ts | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/controllers/api/accounts.ts b/src/controllers/api/accounts.ts index d9f2292b..b8f75521 100644 --- a/src/controllers/api/accounts.ts +++ b/src/controllers/api/accounts.ts @@ -4,7 +4,7 @@ import { z } from 'zod'; import { type AppController } from '@/app.ts'; import { Conf } from '@/config.ts'; -import { getAuthor, getFollowedPubkeys } from '@/queries.ts'; +import { getAuthor, getAuthorFallback, getFollowedPubkeys } from '@/queries.ts'; import { booleanParamSchema, fileSchema } from '@/schema.ts'; import { Storages } from '@/storages.ts'; import { uploadFile } from '@/utils/upload.ts'; @@ -79,7 +79,7 @@ const verifyCredentialsController: AppController = async (c) => { const accountController: AppController = async (c) => { const pubkey = c.req.param('pubkey'); - const event = await getAuthor(pubkey); + const event = await getAuthorFallback(pubkey); if (event) { return c.json(await renderAccount(event)); } else { @@ -244,7 +244,7 @@ const updateCredentialsController: AppController = async (c) => { return c.json(result.error, 422); } - const author = await getAuthor(pubkey); + const author = await getAuthorFallback(pubkey); const meta = author ? n.json().pipe(n.metadata()).catch({}).parse(author.content) : {}; const { diff --git a/src/controllers/api/statuses.ts b/src/controllers/api/statuses.ts index 4185b960..360e13c8 100644 --- a/src/controllers/api/statuses.ts +++ b/src/controllers/api/statuses.ts @@ -9,7 +9,7 @@ import { type AppController } from '@/app.ts'; import { Conf } from '@/config.ts'; import { DittoDB } from '@/db/DittoDB.ts'; import { getUnattachedMediaByIds } from '@/db/unattached-media.ts'; -import { getAncestors, getAuthor, getDescendants, getEvent } from '@/queries.ts'; +import { getAncestors, getAuthorFallback, getDescendants, getEvent } from '@/queries.ts'; import { renderEventAccounts } from '@/views.ts'; import { renderReblog, renderStatus } from '@/views/mastodon/statuses.ts'; import { Storages } from '@/storages.ts'; @@ -163,7 +163,7 @@ const createStatusController: AppController = async (c) => { tags, }, c); - const author = await getAuthor(event.pubkey); + const author = await getAuthorFallback(event.pubkey); if (data.quote_id) { await hydrateEvents({ @@ -189,7 +189,7 @@ const deleteStatusController: AppController = async (c) => { tags: [['e', id]], }, c); - const author = await getAuthor(event.pubkey); + const author = await getAuthorFallback(event.pubkey); return c.json(await renderStatus({ ...event, author }, { viewerPubkey: pubkey })); } else { return c.json({ error: 'Unauthorized' }, 403); From 46e9ae4d3d663cf91f490a8f288815db3aa038b1 Mon Sep 17 00:00:00 2001 From: Siddharth Singh Date: Fri, 7 Jun 2024 13:25:44 +0530 Subject: [PATCH 5/5] Revert "update other controllers to also use the getAuthorFallback method" This reverts commit d5135200e847d65363da8c1b7337057a88cd44bf. For now let's test it with only accountsLookupController --- src/controllers/api/accounts.ts | 6 +++--- src/controllers/api/statuses.ts | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/controllers/api/accounts.ts b/src/controllers/api/accounts.ts index b8f75521..d9f2292b 100644 --- a/src/controllers/api/accounts.ts +++ b/src/controllers/api/accounts.ts @@ -4,7 +4,7 @@ import { z } from 'zod'; import { type AppController } from '@/app.ts'; import { Conf } from '@/config.ts'; -import { getAuthor, getAuthorFallback, getFollowedPubkeys } from '@/queries.ts'; +import { getAuthor, getFollowedPubkeys } from '@/queries.ts'; import { booleanParamSchema, fileSchema } from '@/schema.ts'; import { Storages } from '@/storages.ts'; import { uploadFile } from '@/utils/upload.ts'; @@ -79,7 +79,7 @@ const verifyCredentialsController: AppController = async (c) => { const accountController: AppController = async (c) => { const pubkey = c.req.param('pubkey'); - const event = await getAuthorFallback(pubkey); + const event = await getAuthor(pubkey); if (event) { return c.json(await renderAccount(event)); } else { @@ -244,7 +244,7 @@ const updateCredentialsController: AppController = async (c) => { return c.json(result.error, 422); } - const author = await getAuthorFallback(pubkey); + const author = await getAuthor(pubkey); const meta = author ? n.json().pipe(n.metadata()).catch({}).parse(author.content) : {}; const { diff --git a/src/controllers/api/statuses.ts b/src/controllers/api/statuses.ts index 360e13c8..4185b960 100644 --- a/src/controllers/api/statuses.ts +++ b/src/controllers/api/statuses.ts @@ -9,7 +9,7 @@ import { type AppController } from '@/app.ts'; import { Conf } from '@/config.ts'; import { DittoDB } from '@/db/DittoDB.ts'; import { getUnattachedMediaByIds } from '@/db/unattached-media.ts'; -import { getAncestors, getAuthorFallback, getDescendants, getEvent } from '@/queries.ts'; +import { getAncestors, getAuthor, getDescendants, getEvent } from '@/queries.ts'; import { renderEventAccounts } from '@/views.ts'; import { renderReblog, renderStatus } from '@/views/mastodon/statuses.ts'; import { Storages } from '@/storages.ts'; @@ -163,7 +163,7 @@ const createStatusController: AppController = async (c) => { tags, }, c); - const author = await getAuthorFallback(event.pubkey); + const author = await getAuthor(event.pubkey); if (data.quote_id) { await hydrateEvents({ @@ -189,7 +189,7 @@ const deleteStatusController: AppController = async (c) => { tags: [['e', id]], }, c); - const author = await getAuthorFallback(event.pubkey); + const author = await getAuthor(event.pubkey); return c.json(await renderStatus({ ...event, author }, { viewerPubkey: pubkey })); } else { return c.json({ error: 'Unauthorized' }, 403);