Merge branch 'fetch-authors' into 'main'

Automatically fetch missing authors from pool

See merge request soapbox-pub/ditto!710
This commit is contained in:
Alex Gleason 2025-03-06 21:00:39 +00:00
commit da3bee11e6
3 changed files with 43 additions and 8 deletions

View file

@ -58,7 +58,7 @@ export class DittoPostgres implements DittoDB {
} }
async [Symbol.asyncDispose](): Promise<void> { async [Symbol.asyncDispose](): Promise<void> {
await this.pg.end(); await this.pg.end({ timeout: 0 }); // force-close the connections
await this.kysely.destroy(); await this.kysely.destroy();
} }
} }

View file

@ -156,6 +156,24 @@ Deno.test('fetchRelated', async () => {
}, 3000); }, 3000);
}); });
Deno.test('event author is fetched', async () => {
await using test = setupTest();
const { pool, store } = test;
const sk = generateSecretKey();
const pubkey = getPublicKey(sk);
const post = genEvent({ kind: 1 }, sk);
const author = genEvent({ kind: 0 }, sk);
await pool.event(author);
await store.event(post);
const [result] = await store.query([{ kinds: [0], authors: [pubkey] }]);
assertEquals(result?.id, author.id);
});
function setupTest(cb?: (req: Request) => Response | Promise<Response>) { function setupTest(cb?: (req: Request) => Response | Promise<Response>) {
const conf = new DittoConf(Deno.env); const conf = new DittoConf(Deno.env);
const db = new DittoPolyPg(conf.databaseUrl); const db = new DittoPolyPg(conf.databaseUrl);

View file

@ -55,6 +55,7 @@ interface DittoRelayStoreOpts {
export class DittoRelayStore implements NRelay { export class DittoRelayStore implements NRelay {
private push: DittoPush; private push: DittoPush;
private encounters = new LRUCache<string, true>({ max: 5000 }); private encounters = new LRUCache<string, true>({ max: 5000 });
private authorEncounters = new LRUCache<string, true>({ max: 5000, ttl: Time.hours(4) });
private controller = new AbortController(); private controller = new AbortController();
private policyWorker: PolicyWorker; private policyWorker: PolicyWorker;
@ -130,8 +131,8 @@ export class DittoRelayStore implements NRelay {
* Common pipeline function to process (and maybe store) events. * Common pipeline function to process (and maybe store) events.
* It is idempotent, so it can be called multiple times for the same event. * It is idempotent, so it can be called multiple times for the same event.
*/ */
async event(event: DittoEvent, opts: { publish?: boolean; signal?: AbortSignal } = {}): Promise<void> { async event(event: DittoEvent, opts: { signal?: AbortSignal } = {}): Promise<void> {
const { conf, relay } = this.opts; const { conf, relay, pool } = this.opts;
const { signal } = opts; const { signal } = opts;
// Skip events that have already been encountered. // Skip events that have already been encountered.
@ -177,15 +178,31 @@ export class DittoRelayStore implements NRelay {
await relay.event(event, { signal }); await relay.event(event, { signal });
} }
// Ensure the event doesn't violate the policy.
if (event.pubkey !== await conf.signer.getPublicKey()) {
await this.policyFilter(event, signal);
}
// Prepare the event for additional checks. // Prepare the event for additional checks.
// FIXME: This is kind of hacky. Should be reorganized to fetch only what's needed for each stage. // FIXME: This is kind of hacky. Should be reorganized to fetch only what's needed for each stage.
await this.hydrateEvent(event, signal); await this.hydrateEvent(event, signal);
// Try to fetch a kind 0 for the user if we don't have one yet.
// TODO: Create a more elaborate system to refresh all replaceable events by addr.
if (event.kind !== 0 && !event.author?.sig && !this.authorEncounters.get(event.pubkey)) {
this.authorEncounters.set(event.pubkey, true);
const [author] = await pool.query(
[{ kinds: [0], authors: [event.pubkey], limit: 1 }],
{ signal: AbortSignal.timeout(1000) },
);
if (author) {
// await because it's important to have the kind 0 before the policy filter.
await this.event(author, { signal });
}
}
// Ensure the event doesn't violate the policy.
if (event.pubkey !== await conf.signer.getPublicKey()) {
await this.policyFilter(purifyEvent(event), signal);
}
// Ensure that the author is not banned. // Ensure that the author is not banned.
const n = getTagSet(event.user?.tags ?? [], 'n'); const n = getTagSet(event.user?.tags ?? [], 'n');
if (n.has('disabled')) { if (n.has('disabled')) {