From 4cf9631c28e08b3e091462b2eea8ef0079f39bf8 Mon Sep 17 00:00:00 2001 From: fiatjaf Date: Thu, 16 Jan 2025 10:25:00 -0300 Subject: [PATCH] sdk: use a prefix iterator on kvdb for storing relay urls associated with ids. --- sdk/kvstore/badger/store.go | 25 +++++++++++++++++++++++++ sdk/kvstore/interface.go | 4 ++++ sdk/kvstore/lmdb/store.go | 20 ++++++++++++++++++++ sdk/kvstore/memory/store.go | 15 +++++++++++++++ sdk/system.go | 27 +++++++++++++++++++++++++++ sdk/tracker.go | 27 +++++++++++++++++---------- 6 files changed, 108 insertions(+), 10 deletions(-) diff --git a/sdk/kvstore/badger/store.go b/sdk/kvstore/badger/store.go index d4e6667..db957ea 100644 --- a/sdk/kvstore/badger/store.go +++ b/sdk/kvstore/badger/store.go @@ -57,3 +57,28 @@ func (s *Store) Delete(key []byte) error { func (s *Store) Close() error { return s.db.Close() } + +func (s *Store) Scan(prefix []byte, fn func(key []byte, value []byte) bool) error { + return s.db.View(func(txn *badger.Txn) error { + it := txn.NewIterator(badger.DefaultIteratorOptions) + defer it.Close() + + for it.Seek(prefix); it.ValidForPrefix(prefix); it.Next() { + item := it.Item() + err := item.Value(func(v []byte) error { + k := item.Key() + if !fn(k, v) { + return badger.ErrStopIteration + } + return nil + }) + if err == badger.ErrStopIteration { + break + } + if err != nil { + return err + } + } + return nil + }) +} diff --git a/sdk/kvstore/interface.go b/sdk/kvstore/interface.go index 6573886..c963a4c 100644 --- a/sdk/kvstore/interface.go +++ b/sdk/kvstore/interface.go @@ -13,4 +13,8 @@ type KVStore interface { // Close releases any resources held by the store Close() error + + // Scan iterates through all keys with the given prefix. + // For each key-value pair, fn is called. If fn returns false, iteration stops. + Scan(prefix []byte, fn func(key []byte, value []byte) bool) error } diff --git a/sdk/kvstore/lmdb/store.go b/sdk/kvstore/lmdb/store.go index 90919f6..f8374d6 100644 --- a/sdk/kvstore/lmdb/store.go +++ b/sdk/kvstore/lmdb/store.go @@ -90,3 +90,23 @@ func (s *Store) Close() error { s.env.Close() return nil } + +func (s *Store) Scan(prefix []byte, fn func(key []byte, value []byte) bool) error { + return s.env.View(func(txn *lmdb.Txn) error { + cursor, err := txn.OpenCursor(s.dbi) + if err != nil { + return err + } + defer cursor.Close() + + for k, v, err := cursor.Get(prefix, nil, lmdb.SetRange); err == nil; k, v, err = cursor.Get(nil, nil, lmdb.Next) { + if !bytes.HasPrefix(k, prefix) { + break + } + if !fn(k, v) { + break + } + } + return nil + }) +} diff --git a/sdk/kvstore/memory/store.go b/sdk/kvstore/memory/store.go index f88f55b..524083a 100644 --- a/sdk/kvstore/memory/store.go +++ b/sdk/kvstore/memory/store.go @@ -56,3 +56,18 @@ func (s *Store) Close() error { s.data = nil return nil } + +func (s *Store) Scan(prefix []byte, fn func(key []byte, value []byte) bool) error { + s.RLock() + defer s.RUnlock() + + prefixStr := string(prefix) + for k, v := range s.data { + if strings.HasPrefix(k, prefixStr) { + if !fn([]byte(k), v) { + break + } + } + } + return nil +} diff --git a/sdk/system.go b/sdk/system.go index 541b191..7976db5 100644 --- a/sdk/system.go +++ b/sdk/system.go @@ -139,6 +139,33 @@ func (sys *System) Close() { } } +// GetEventRelays returns all known relay URLs that have been seen to carry the given event. +func (sys *System) GetEventRelays(eventID string) ([]string, error) { + // decode the event ID hex into bytes + idBytes, err := hex.DecodeString(eventID) + if err != nil || len(idBytes) < 8 { + return nil, fmt.Errorf("invalid event id") + } + + // create prefix for scanning: 'r' + first 8 bytes of event ID + prefix := make([]byte, 9) + prefix[0] = eventRelayPrefix + copy(prefix[1:], idBytes[:8]) + + relays := make([]string, 0) + err = sys.KVStore.Scan(prefix, func(key []byte, value []byte) bool { + // extract relay URL from key (everything after prefix) + relay := string(key[9:]) + relays = append(relays, relay) + return true + }) + if err != nil { + return nil, err + } + + return relays, nil +} + func WithHintsDB(hdb hints.HintsDB) SystemModifier { return func(sys *System) { sys.Hints = hdb diff --git a/sdk/tracker.go b/sdk/tracker.go index eac395b..ce32047 100644 --- a/sdk/tracker.go +++ b/sdk/tracker.go @@ -110,6 +110,17 @@ func (sys *System) TrackEventHints(ie nostr.RelayEvent) { } } +const eventRelayPrefix = byte('r') + +func makeEventRelayKey(eventID []byte, relay string) []byte { + // Format: 'r' + first 8 bytes of event ID + relay URL + key := make([]byte, 1+8+len(relay)) + key[0] = eventRelayPrefix + copy(key[1:], eventID[:8]) + copy(key[9:], relay) + return key +} + func (sys *System) TrackEventRelays(ie nostr.RelayEvent) { // decode the event ID hex into bytes idBytes, err := hex.DecodeString(ie.ID) @@ -117,11 +128,9 @@ func (sys *System) TrackEventRelays(ie nostr.RelayEvent) { return } - // store only first 8 bytes of event ID as key - key := idBytes[:8] - - // store the relay URL as value - sys.KVStore.Set(key, []byte(ie.Relay.URL)) + // store with prefix + eventid + relay format + key := makeEventRelayKey(idBytes, ie.Relay.URL) + sys.KVStore.Set(key, nil) // value is not needed since relay is in key } func (sys *System) TrackEventRelaysD(relay, id string) { @@ -131,9 +140,7 @@ func (sys *System) TrackEventRelaysD(relay, id string) { return } - // store only first 8 bytes of event ID as key - key := idBytes[:8] - - // store the relay URL as value - sys.KVStore.Set(key, []byte(relay)) + // store with prefix + eventid + relay format + key := makeEventRelayKey(idBytes, relay) + sys.KVStore.Set(key, nil) // value is not needed since relay is in key }