Add a function to calculate the intrinsic limit of a filter

This commit is contained in:
Alex Gleason 2024-01-03 16:35:40 -06:00
parent 80e6147927
commit 5bffffe07b
No known key found for this signature in database
GPG Key ID: 7211D1F99744FBB7
3 changed files with 39 additions and 3 deletions

View File

@ -4,7 +4,7 @@ import { assertEquals } from '@/deps-test.ts';
import event0 from '~/fixtures/events/event-0.json' assert { type: 'json' };
import event1 from '~/fixtures/events/event-1.json' assert { type: 'json' };
import { eventToMicroFilter, getFilterId, getMicroFilters, isMicrofilter } from './filter.ts';
import { eventToMicroFilter, getFilterId, getFilterLimit, getMicroFilters, isMicrofilter } from './filter.ts';
Deno.test('getMicroFilters', () => {
const event = event0 as Event<0>;
@ -35,3 +35,13 @@ Deno.test('getFilterId', () => {
'{"authors":["79c2cae114ea28a981e7559b4fe7854a473521a8d22a66bbab9fa248eb820ff6"],"kinds":[0]}',
);
});
Deno.test('getFilterLimit', () => {
assertEquals(getFilterLimit({ ids: [event0.id] }), 1);
assertEquals(getFilterLimit({ ids: [event0.id], limit: 2 }), 1);
assertEquals(getFilterLimit({ ids: [event0.id], limit: 0 }), 0);
assertEquals(getFilterLimit({ ids: [event0.id], limit: -1 }), 0);
assertEquals(getFilterLimit({ kinds: [0], authors: [event0.pubkey] }), 1);
assertEquals(getFilterLimit({ kinds: [1], authors: [event0.pubkey] }), Infinity);
assertEquals(getFilterLimit({}), Infinity);
});

View File

@ -2,6 +2,7 @@ import { Conf } from '@/config.ts';
import { type Event, type Filter, matchFilters, stringifyStable, z } from '@/deps.ts';
import { nostrIdSchema } from '@/schemas/nostr.ts';
import { type EventData } from '@/types.ts';
import { isReplaceableKind } from '@/kinds.ts';
/** Additional properties that may be added by Ditto to events. */
type Relation = 'author' | 'author_stats' | 'event_stats';
@ -82,11 +83,34 @@ function isMicrofilter(filter: Filter): filter is MicroFilter {
return microFilterSchema.safeParse(filter).success;
}
/** Calculate the intrinsic limit of a filter. */
function getFilterLimit(filter: Filter): number {
if (filter.ids && !filter.ids.length) return 0;
if (filter.kinds && !filter.kinds.length) return 0;
if (filter.authors && !filter.authors.length) return 0;
return Math.min(
Math.max(0, filter.limit ?? Infinity),
filter.ids?.length ?? Infinity,
filter.authors?.length &&
filter.kinds?.every((kind) => isReplaceableKind(kind))
? filter.authors.length * filter.kinds.length
: Infinity,
);
}
/** Returns true if the filter could potentially return any stored events at all. */
function canFilter(filter: Filter): boolean {
return getFilterLimit(filter) > 0;
}
export {
type AuthorMicrofilter,
canFilter,
type DittoFilter,
eventToMicroFilter,
getFilterId,
getFilterLimit,
getMicroFilters,
type IdMicrofilter,
isMicrofilter,

View File

@ -1,4 +1,5 @@
import { Debug, type Event, type Filter, LRUCache, matchFilter, matchFilters } from '@/deps.ts';
import { canFilter, getFilterLimit } from '@/filter.ts';
import { type EventStore, type GetEventsOpts } from '@/store.ts';
/** In-memory data store for events. */
@ -27,6 +28,7 @@ class Memorelay implements EventStore {
/** Get events from memory. */
getEvents<K extends number>(filters: Filter<K>[], opts: GetEventsOpts = {}): Promise<Event<K>[]> {
if (opts.signal?.aborted) return Promise.resolve([]);
filters = filters.filter(canFilter);
if (!filters.length) return Promise.resolve([]);
this.#debug('REQ', JSON.stringify(filters));
@ -37,7 +39,7 @@ class Memorelay implements EventStore {
let index = 0;
for (const filter of filters) {
const limit = filter.limit ?? Infinity;
const limit = getFilterLimit(filter);
const usage = usages[index] ?? 0;
if (usage >= limit) {
@ -50,7 +52,7 @@ class Memorelay implements EventStore {
index++;
}
if (filters.every((filter, index) => usages[index] >= (filter.limit ?? Infinity))) {
if (filters.every((filter, index) => usages[index] >= getFilterLimit(filter))) {
break;
}
}