Stop using bloom filters for deletes, false positives do happen

This commit is contained in:
Jonathan Staab 2023-09-26 12:17:17 -07:00
parent 5c15c5462e
commit eddab7010c
11 changed files with 58 additions and 47 deletions

View File

@ -45,7 +45,6 @@
"@nostr-dev-kit/ndk": "^0.7.0",
"@scure/base": "^1.1.1",
"@tsconfig/svelte": "^3.0.0",
"bloom-filters": "^3.0.0",
"classnames": "^2.3.2",
"compressorjs": "^1.1.1",
"fuse.js": "^6.6.2",

View File

@ -18,7 +18,6 @@
env,
load,
processZap,
deriveMuted,
derivePerson,
getReplyHints,
isEventMuted,
@ -46,6 +45,7 @@
let event = note
let reply = null
let replyIsActive = false
let showMuted = false
let actions = null
let collapsed = false
let replies = sortBy(
@ -59,7 +59,6 @@
const borderColor = invertColors ? "gray-6" : "gray-7"
const showEntire = anchorId === event.id
const interactive = !anchorId || !showEntire
const muted = deriveMuted(event.id)
let interval, border, childrenContainer, noteContainer
@ -101,6 +100,8 @@
}
}
$: muted = !showMuted && $isEventMuted(event)
// Show only notes that were passed in by the parent unless we want to show all
$: visibleNotes = ((showContext ? replies : note.replies) || []).filter(
e => !feedRelay || e.seen_on.includes(feedRelay.url)
@ -219,20 +220,25 @@
</small>
{/if}
</div>
{#if $muted}
{#if muted}
<p class="border-l-2 border-solid border-gray-6 pl-4 text-gray-1">
You have muted this note.
<Anchor
theme="anchor"
on:click={() => {
showMuted = true
}}>Show</Anchor>
</p>
{:else}
<NoteContent {anchorId} note={event} {showEntire} />
{/if}
<NoteActions
note={event}
muted={$muted}
bind:this={actions}
bind:replies
bind:likes
bind:zaps
{muted}
{reply}
{setFeedRelay}
{showEntire} />
@ -241,7 +247,7 @@
</Card>
</div>
{#if !replyIsActive && visibleNotes.length > 0 && !showEntire && depth > 0 && !$muted}
{#if !replyIsActive && visibleNotes.length > 0 && !showEntire && depth > 0 && !muted}
<div class="relative">
<div
class="absolute right-0 top-0 z-10 -mr-2 -mt-4 flex h-6 w-6 cursor-pointer items-center
@ -276,7 +282,7 @@
}}
{borderColor} />
{#if !collapsed && visibleNotes.length > 0 && depth > 0 && !$muted}
{#if !collapsed && visibleNotes.length > 0 && depth > 0 && !muted}
<div class="relative mt-4">
<div class={`absolute w-px bg-${borderColor} z-10 -mt-4 ml-4 h-0`} bind:this={border} />
<div class="note-children relative ml-8 flex flex-col gap-4" bind:this={childrenContainer}>

View File

@ -37,7 +37,9 @@
let limit = 20
$: groupedNotifications = groupNotifications($throttledNotifications).slice(0, limit)
$: tabKinds = activeTab === tabs[0] ? noteKinds : reactionKinds.concat(9734)
$: groupedNotifications = groupNotifications($throttledNotifications, tabKinds).slice(0, limit)
$: tabNotifications =
activeTab === tabs[0]

View File

@ -1,27 +1,7 @@
import {prop} from "ramda"
import {batch} from "hurdak"
import {Worker} from "src/engine/core/utils"
import type {Event} from "src/engine/events/model"
import {sessions} from "src/engine/session/state"
import {_events} from "src/engine/events/state"
export const projections = new Worker<Event>({
getKey: prop("kind"),
})
projections.addGlobalHandler(
batch(500, (chunk: Event[]) => {
const $sessions = sessions.get()
const userEvents = chunk.filter(e => $sessions[e.pubkey])
if (userEvents.length > 0) {
_events.mapStore.update($events => {
for (const e of userEvents) {
$events.set(e.id, e)
}
return $events
})
}
})
)

View File

@ -270,15 +270,25 @@ export class Collection<T extends R> implements Readable<T[]> {
map = (f: (v: T) => T) => this.update(map(f))
}
export class DerivedCollection<T extends R> extends Derived<T[]> {
export class DerivedCollection<T extends R> implements Readable<T[]> {
readonly listStore: Derived<T[]>
readonly mapStore: Readable<M<T>>
constructor(readonly pk: string, stores: Derivable, getValue: (values: any) => T[], t = 0) {
super(stores, getValue, t)
this.mapStore = new Derived(this, xs => new Map(xs.map(x => [x[pk], x])), t)
this.listStore = new Derived(stores, getValue, t)
this.mapStore = new Derived(this.listStore, xs => new Map(xs.map(x => [x[pk], x])))
}
get = () => this.listStore.get()
getMap = () => this.mapStore.get()
subscribe = (f: Subscriber<T[]>) => this.listStore.subscribe(f)
derived = <U>(f: (v: T[]) => U) => this.listStore.derived<U>(f)
throttle = (t: number) => this.listStore.throttle(t)
key = (k: string) => new DerivedKey(this.mapStore, this.pk, k)
}

View File

@ -11,9 +11,9 @@ export const events = new DerivedCollection<Event>("id", [_events, deletes], ([$
$e.filter(e => !$d.has(e.id))
)
export const userEvents = new DerivedCollection<Event>("id", [events, pubkey], ([$e, $pk]) =>
$pk ? $e.filter(whereEq({pubkey: $pk})) : []
)
export const userEvents = new DerivedCollection<Event>("id", [events, pubkey], ([$e, $pk]) => {
return $pk ? $e.filter(whereEq({pubkey: $pk})) : []
})
export const isEventMuted = derived([mutes, settings], ([$mutes, $settings]) => {
const words = $settings.muted_words
@ -27,7 +27,7 @@ export const isEventMuted = derived([mutes, settings], ([$mutes, $settings]) =>
return true
}
if (regex && e.content.toLowerCase().match(regex)) {
if (regex && e.content?.toLowerCase().match(regex)) {
return true
}

View File

@ -1,9 +1,22 @@
import {batch} from "hurdak"
import {Tags} from "src/util/nostr"
import {projections} from "src/engine/core/projections"
import type {Event} from "src/engine/events/model"
import {sessions} from "src/engine/session/state"
import {nip59} from "src/engine/session/derived"
import {EventKind} from "./model"
import {deletes, deletesLastUpdated} from "./state"
import {_events, deletes, deletesLastUpdated} from "./state"
projections.addGlobalHandler(
batch(500, (chunk: Event[]) => {
const $sessions = sessions.get()
const userEvents = chunk.filter(e => $sessions[e.pubkey])
if (userEvents.length > 0) {
_events.update($events => $events.concat(userEvents))
}
})
)
projections.addHandler(EventKind.Delete, e => {
const values = Tags.from(e).type(["a", "e"]).values().all()

View File

@ -1,7 +1,6 @@
import {ScalableBloomFilter} from "bloom-filters"
import {Collection, Writable, writable} from "src/engine/core/utils"
import type {Event} from "./model"
export const _events = new Collection<Event>("id", 1000)
export const deletes = new Writable(new ScalableBloomFilter(), 1000)
export const deletes = new Writable(new Set(), 1000)
export const deletesLastUpdated = writable(0)

View File

@ -1,6 +1,4 @@
import {ScalableBloomFilter} from "bloom-filters"
import {prop, sortBy} from "ramda"
import {tryFunc} from "hurdak"
import {Storage, LocalStorageAdapter, IndexedDBAdapter, sortByPubkeyWhitelist} from "./core"
import {_lists} from "./lists"
import {people} from "./people"
@ -36,11 +34,11 @@ export * from "./zaps"
export const storage = new Storage([
new LocalStorageAdapter("pubkey", pubkey),
new LocalStorageAdapter("sessions", sessions),
new LocalStorageAdapter("deletes", deletes, {
dump: f => f.saveAsJSON(),
load: d => tryFunc(() => ScalableBloomFilter.fromJSON(d)) || new ScalableBloomFilter(),
new LocalStorageAdapter("deletes2", deletes, {
dump: s => Array.from(s),
load: a => new Set(a || []),
}),
new LocalStorageAdapter("deletesLastUpdated", deletesLastUpdated),
new LocalStorageAdapter("deletesLastUpdated2", deletesLastUpdated),
new LocalStorageAdapter("notificationsLastChecked", notificationsLastChecked),
new LocalStorageAdapter("nip04ChannelsLastChecked", nip04ChannelsLastChecked),
new LocalStorageAdapter("nip24ChannelsLastChecked", nip24ChannelsLastChecked),

View File

@ -44,7 +44,7 @@ export const hasNewNotifications = derived(
}
)
export const groupNotifications = $notifications => {
export const groupNotifications = ($notifications, kinds) => {
const $userEvents = userEvents.mapStore.get()
// Convert zaps to zap requests
@ -62,6 +62,10 @@ export const groupNotifications = $notifications => {
for (const ix of $notifications) {
const event = $userEvents.get(findReplyId(ix))
if (!kinds.includes(ix.kind)) {
continue
}
if (reactionKinds.includes(ix.kind) && !event) {
continue
}
@ -78,7 +82,7 @@ export const groupNotifications = $notifications => {
Object.values(groups).map((group: any) => {
const {event, interactions} = group
const timestamp = interactions
.map(ix => ix.created_at)
.map(e => e.created_at)
.concat(event?.created_at || 0)
.reduce(max, 0)

BIN
yarn.lock

Binary file not shown.