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 event0 from '~/fixtures/events/event-0.json' assert { type: 'json' };
import event1 from '~/fixtures/events/event-1.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', () => { Deno.test('getMicroFilters', () => {
const event = event0 as Event<0>; const event = event0 as Event<0>;
@ -35,3 +35,13 @@ Deno.test('getFilterId', () => {
'{"authors":["79c2cae114ea28a981e7559b4fe7854a473521a8d22a66bbab9fa248eb820ff6"],"kinds":[0]}', '{"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 { type Event, type Filter, matchFilters, stringifyStable, z } from '@/deps.ts';
import { nostrIdSchema } from '@/schemas/nostr.ts'; import { nostrIdSchema } from '@/schemas/nostr.ts';
import { type EventData } from '@/types.ts'; import { type EventData } from '@/types.ts';
import { isReplaceableKind } from '@/kinds.ts';
/** Additional properties that may be added by Ditto to events. */ /** Additional properties that may be added by Ditto to events. */
type Relation = 'author' | 'author_stats' | 'event_stats'; type Relation = 'author' | 'author_stats' | 'event_stats';
@ -82,11 +83,34 @@ function isMicrofilter(filter: Filter): filter is MicroFilter {
return microFilterSchema.safeParse(filter).success; 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 { export {
type AuthorMicrofilter, type AuthorMicrofilter,
canFilter,
type DittoFilter, type DittoFilter,
eventToMicroFilter, eventToMicroFilter,
getFilterId, getFilterId,
getFilterLimit,
getMicroFilters, getMicroFilters,
type IdMicrofilter, type IdMicrofilter,
isMicrofilter, isMicrofilter,

View File

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