From 47cd27714a7e069c94900b04f7324137d0619ad1 Mon Sep 17 00:00:00 2001 From: deymosh Date: Sat, 22 Aug 2026 11:29:53 +0000 Subject: [PATCH] feat: explicitly reject not latest replaceable/addressable events --- backup.go | 2 +- blastr.go | 2 +- import.go | 18 ++++++++++------- init.go | 9 +++++++++ pkg/wot/simple_in_memory.go | 12 +++++------ policies.go | 40 +++++++++++++++++++++++++++++++++++++ 6 files changed, 68 insertions(+), 15 deletions(-) diff --git a/backup.go b/backup.go index 186e6422..2d79130f 100644 --- a/backup.go +++ b/backup.go @@ -180,7 +180,7 @@ func runRestore(ctx context.Context) { func startPeriodicCloudBackups(ctx context.Context) { cloudProvider, err := getCloudProvider() if err != nil { - log.Printf("⚠️ Cloud backup disabled: %v", err) + log.Printf("⚠️ Cloud backup disabled: %v", err) return } diff --git a/blastr.go b/blastr.go index 27ac79b3..3b94d41a 100644 --- a/blastr.go +++ b/blastr.go @@ -31,5 +31,5 @@ func blast(ctx context.Context, ev *nostr.Event) { } cancel() } - slog.Info("🔫 blasted event", "id", ev.ID, "relays", successCount) + slog.Info("🔫 blasted event", "kind", ev.Kind, "id", ev.ID, "relays", successCount) } diff --git a/import.go b/import.go index 1765bde2..8b4a0b18 100644 --- a/import.go +++ b/import.go @@ -35,8 +35,8 @@ func ensureImportRelays() { slog.Error("🚫 Unable to connect to any import relays, check your connectivity and relays_import.json file") os.Exit(1) } else { - slog.Warn("⚠️ Some relays failed to connect, proceeding, but this may cause issues") - slog.Info("ℹ️ If you always see this message during startup, consider removing the relays that are not working from your relays_import.json file") + slog.Warn("⚠️ Some relays failed to connect, proceeding, but this may cause issues") + slog.Info("ℹ️ If you always see this message during startup, consider removing the relays that are not working from your relays_import.json file") } } @@ -221,12 +221,18 @@ func subscribeInboxAndChat(ctx context.Context) { for ev := range pool.SubscribeMany(ctx, config.ImportSeedRelays, filter, nostr.SubscriptionOptions{}) { if _, ok := config.BlacklistedPubKeys[ev.PubKey.Hex()]; ok { - slog.Debug("🚫discarding imported note from blacklisted pubkey", "pubkey", ev.PubKey, "id", ev.ID) + slog.Debug("🚫 discarding imported note from blacklisted pubkey", "pubkey", ev.PubKey, "id", ev.ID) continue } if !wot.GetInstance().Has(ctx, ev.PubKey.Hex()) && ev.Kind != nostr.KindGiftWrap { continue } + + // Discard follow list events since they are not relevant for the inbox or chat + if ev.Kind == nostr.KindFollowList { + continue + } + for tag := range ev.Tags.FindAll("p") { if len(tag) < 2 { continue @@ -237,10 +243,10 @@ func subscribeInboxAndChat(ctx context.Context) { dbToPublish = chatDB } - slog.Debug("ℹ️ importing event", "kind", ev.Kind, "id", ev.ID, "relay", ev.Relay.URL) + slog.Debug("ℹ️ importing event", "kind", ev.Kind, "id", ev.ID, "relay", ev.Relay.URL) if isDuplicate(ctx, dbToPublish, ev.Event) { - slog.Debug("ℹ️ skipping duplicate event", "id", ev.ID) + slog.Debug("ℹ️ skipping duplicate event", "id", ev.ID) break // Avoid re-importing duplicates } @@ -262,8 +268,6 @@ func subscribeInboxAndChat(ctx context.Context) { log.Println("🎁🔒️✉️ new gift-wrapped message in your chat relay") case nostr.KindRepost: log.Println("🔁 new repost in your inbox") - case nostr.KindFollowList: - // do nothing default: log.Println("📦 new event kind", ev.Kind, "event in your inbox") } diff --git a/init.go b/init.go index 8a23d44b..7f124b14 100644 --- a/init.go +++ b/init.go @@ -149,6 +149,9 @@ func initRelays(ctx context.Context) { )(ctx, event); reject { return reject, msg } + if reject, msg := EventMustBeLatest(ctx, &event, privateDB); reject { + return reject, msg + } return MustBeWhitelistedToPost(ctx, &event) } @@ -296,6 +299,9 @@ func initRelays(ctx context.Context) { )(ctx, event); reject { return reject, msg } + if reject, msg := EventMustBeLatest(ctx, &event, outboxDB); reject { + return reject, msg + } return MustBeWhitelistedToPost(ctx, &event) } @@ -405,6 +411,9 @@ func initRelays(ctx context.Context) { if reject, msg := OnlyGiftWrappedDMs(ctx, &event); reject { return reject, msg } + if reject, msg := EventMustNotBeFollowList(ctx, &event); reject { + return reject, msg + } if reject, msg := MustNotBeBlacklistedToPost(ctx, &event); reject { return reject, msg } diff --git a/pkg/wot/simple_in_memory.go b/pkg/wot/simple_in_memory.go index e53a65f7..0f81d765 100644 --- a/pkg/wot/simple_in_memory.go +++ b/pkg/wot/simple_in_memory.go @@ -103,7 +103,7 @@ func (wt *SimpleInMemory) Refresh(ctx context.Context) { filter.Authors = append(filter.Authors, nostr.MustPubKeyFromHex(pubkeyHex)) } - slog.Info("🛜 fetching Nostr events to build WoT") + slog.Info("🛜 fetching Nostr events to build WoT") events := wt.Pool.FetchMany(timeoutCtx, wt.SeedRelays, filter, nostr.SubscriptionOptions{}) for ev := range latestEventByKindAndPubkey(timeoutCtx, events, &eventsAnalysed) { @@ -118,13 +118,13 @@ func (wt *SimpleInMemory) Refresh(ctx context.Context) { } if wt.WotDepth == 2 { - slog.Info("🕸️ analysed Nostr events", "count", eventsAnalysed.Load()) + slog.Info("🕸️ analysed Nostr events", "count", eventsAnalysed.Load()) slog.Info("📈 direct followers in import relays", "🫂pubkeys", len(newWot), "🔗relays", len(wt.SeedRelays)) wt.pubkeys.Store(&newWot) return } - slog.Info("🕸️ analysing Nostr events", "count", eventsAnalysed.Load()) + slog.Info("🕸️ analysing Nostr events", "count", eventsAnalysed.Load()) processBatch := func(pubkeys []string) { timeoutCtx, cancel := context.WithTimeout(ctx, timeout) @@ -160,7 +160,7 @@ func (wt *SimpleInMemory) Refresh(ctx context.Context) { select { case <-done: - slog.Info("🕸️ analysing Nostr events", "count", eventsAnalysed.Load()) + slog.Info("🕸️ analysing Nostr events", "count", eventsAnalysed.Load()) case <-timeoutCtx.Done(): slog.Error("🚫 timeout while fetching events, moving to the next batch") } @@ -172,7 +172,7 @@ func (wt *SimpleInMemory) Refresh(ctx context.Context) { processBatch(batch) } - slog.Info("📈 totals", "🫂pubkeys", pubkeyFollowers.Size(), "🔗relays", relaysDiscovered.Size()) + slog.Info("📈 totals", "🫂 pubkeys", pubkeyFollowers.Size(), "🔗 relays", relaysDiscovered.Size()) // Log Top N pubkeys by follower count for debugging purposes if slog.Default().Enabled(ctx, slog.LevelDebug) { @@ -228,7 +228,7 @@ func (wt *SimpleInMemory) Refresh(ctx context.Context) { return true }) - slog.Info("🫥 pruned pubkeys without minimum common followers", "🚧minimum", minimumFollowers, "🫂kept", len(newWot), "🗑️eliminated", pubkeyFollowers.Size()-len(newWot)) + slog.Info("🫥 pruned pubkeys without minimum common followers", "🚧 minimum", minimumFollowers, "🫂 kept", len(newWot), "🗑️ eliminated", pubkeyFollowers.Size()-len(newWot)) wt.pubkeys.Store(&newWot) } diff --git a/policies.go b/policies.go index ef1367ea..d7e88430 100644 --- a/policies.go +++ b/policies.go @@ -6,6 +6,7 @@ import ( "fiatjaf.com/nostr" "fiatjaf.com/nostr/khatru" + "fiatjaf.com/nostr/eventstore" "github.com/barrydeen/haven/pkg/wot" ) @@ -113,6 +114,45 @@ func EventMustBeChatRelated(_ context.Context, event *nostr.Event) (bool, string return true, "only chat related events are allowed" } +func EventMustNotBeFollowList(_ context.Context, event *nostr.Event) (bool, string) { + if event.Kind == nostr.KindFollowList { + return true, "blocked: follow list events are not allowed" + } + return false, "" +} + +func EventMustBeLatest(_ context.Context, event *nostr.Event, db eventstore.Store) (bool, string) { + // if event is not replaceable or addressable kind, we don't need to check for latest + if !event.Kind.IsReplaceable() && !event.Kind.IsAddressable() { + return false, "" + } + + // prepare filter with kind and author + filter := nostr.Filter{ + Kinds: []nostr.Kind{event.Kind}, + Authors: []nostr.PubKey{event.PubKey}, + Limit: 10, // could be just 1 + } + + // when addressable, add the "d" tag to the filter + if event.Kind.IsAddressable() { + filter.Tags = nostr.TagMap{"d": []string{event.Tags.GetD()}} + } + + // query the latest events of the same kind and pubkey (and "d" tag if applicable) + savedEvents := db.QueryEvents(filter, filter.Limit) + + // check if there is a stored event that is newer (or same precedence) than this incoming event + for savedEvent := range savedEvents { + if !nostr.IsOlder(savedEvent, *event) { + slog.Debug("🚫 event rejected: there is a newer event", "kind", event.Kind, "pubkey", event.PubKey) + return true, "replaced: there is a newer event" + } + } + + return false, "" +} + func OnlyGiftWrappedDMs(_ context.Context, event *nostr.Event) (bool, string) { if event.Kind == nostr.KindEncryptedDirectMessage { return true, "only gift wrapped DMs are supported"