mirror of
https://github.com/nbd-wtf/go-nostr.git
synced 2025-03-17 21:32:56 +01:00
add some basic sdk tests, fix saving hints (normalize urls), checkduplicates arg order, kvdb stuff and other things.
This commit is contained in:
parent
46569b6ef4
commit
febf022124
4
pool.go
4
pool.go
@ -287,7 +287,7 @@ func (pool *SimplePool) SubMany(
|
||||
hasAuthed = false
|
||||
|
||||
subscribe:
|
||||
sub, err = relay.Subscribe(ctx, filters, append(opts, WithCheckDuplicate(func(relay, id string) bool {
|
||||
sub, err = relay.Subscribe(ctx, filters, append(opts, WithCheckDuplicate(func(id, relay string) bool {
|
||||
_, exists := seenAlready.Load(id)
|
||||
if exists && pool.duplicateMiddleware != nil {
|
||||
pool.duplicateMiddleware(relay, id)
|
||||
@ -417,7 +417,7 @@ func (pool *SimplePool) SubManyEose(
|
||||
hasAuthed := false
|
||||
|
||||
subscribe:
|
||||
sub, err := relay.Subscribe(ctx, filters, append(opts, WithCheckDuplicate(func(relay, id string) bool {
|
||||
sub, err := relay.Subscribe(ctx, filters, append(opts, WithCheckDuplicate(func(id, relay string) bool {
|
||||
_, exists := seenAlready.Load(id)
|
||||
if exists && pool.duplicateMiddleware != nil {
|
||||
pool.duplicateMiddleware(relay, id)
|
||||
|
2
relay.go
2
relay.go
@ -226,7 +226,7 @@ func (r *Relay) ConnectWithTLS(ctx context.Context, tlsConfig *tls.Config) error
|
||||
subid := extractSubID(message)
|
||||
subscription, ok := r.Subscriptions.Load(subIdToSerial(subid))
|
||||
if ok && subscription.checkDuplicate != nil {
|
||||
if !subscription.checkDuplicate(extractEventID(message[10+len(subid):]), r.URL) {
|
||||
if subscription.checkDuplicate(extractEventID(message[10+len(subid):]), r.URL) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,11 @@
|
||||
package sdk
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"slices"
|
||||
|
||||
"github.com/nbd-wtf/go-nostr/sdk/kvstore"
|
||||
)
|
||||
|
||||
const eventRelayPrefix = byte('r')
|
||||
@ -19,15 +21,18 @@ func makeEventRelayKey(eventID []byte) []byte {
|
||||
func encodeRelayList(relays []string) []byte {
|
||||
totalSize := 0
|
||||
for _, relay := range relays {
|
||||
totalSize += 2 + len(relay) // 2 bytes for length prefix
|
||||
totalSize += 1 + len(relay) // 1 byte for length prefix
|
||||
}
|
||||
|
||||
buf := make([]byte, totalSize)
|
||||
offset := 0
|
||||
|
||||
for _, relay := range relays {
|
||||
binary.LittleEndian.PutUint16(buf[offset:], uint16(len(relay)))
|
||||
offset += 2
|
||||
if len(relay) > 256 {
|
||||
continue
|
||||
}
|
||||
buf[offset] = uint8(len(relay))
|
||||
offset += 1
|
||||
copy(buf[offset:], relay)
|
||||
offset += len(relay)
|
||||
}
|
||||
@ -40,12 +45,12 @@ func decodeRelayList(data []byte) []string {
|
||||
offset := 0
|
||||
|
||||
for offset < len(data) {
|
||||
if offset+2 > len(data) {
|
||||
if offset+1 > len(data) {
|
||||
return nil // malformed
|
||||
}
|
||||
|
||||
length := int(binary.LittleEndian.Uint16(data[offset:]))
|
||||
offset += 2
|
||||
length := int(data[offset])
|
||||
offset += 1
|
||||
|
||||
if offset+length > len(data) {
|
||||
return nil // malformed
|
||||
@ -59,7 +64,7 @@ func decodeRelayList(data []byte) []string {
|
||||
return relays
|
||||
}
|
||||
|
||||
func (sys *System) trackEventRelayCommon(eventID string, relay string) {
|
||||
func (sys *System) trackEventRelayCommon(eventID string, relay string, onlyIfItExists bool) {
|
||||
// decode the event ID hex into bytes
|
||||
idBytes, err := hex.DecodeString(eventID)
|
||||
if err != nil || len(idBytes) < 8 {
|
||||
@ -74,20 +79,22 @@ func (sys *System) trackEventRelayCommon(eventID string, relay string) {
|
||||
var relays []string
|
||||
if data != nil {
|
||||
relays = decodeRelayList(data)
|
||||
} else {
|
||||
relays = make([]string, 0, 1)
|
||||
}
|
||||
|
||||
// check if relay is already in list
|
||||
for _, r := range relays {
|
||||
if r == relay {
|
||||
return data, nil // no change needed
|
||||
// check if relay is already in list
|
||||
if slices.Contains(relays, relay) {
|
||||
return nil, kvstore.NoOp // no change needed
|
||||
}
|
||||
}
|
||||
|
||||
// append new relay
|
||||
relays = append(relays, relay)
|
||||
return encodeRelayList(relays), nil
|
||||
// append new relay
|
||||
relays = append(relays, relay)
|
||||
return encodeRelayList(relays), nil
|
||||
} else if onlyIfItExists {
|
||||
// when this flag exists and nothing was found we won't create anything
|
||||
return nil, kvstore.NoOp
|
||||
} else {
|
||||
// nothing exists, so create it
|
||||
return encodeRelayList([]string{relay}), nil
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -76,7 +76,9 @@ func (s *Store) Update(key []byte, f func([]byte) ([]byte, error)) error {
|
||||
}
|
||||
|
||||
newVal, err := f(val)
|
||||
if err != nil {
|
||||
if err == kvstore.NoOp {
|
||||
return nil
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -104,7 +104,9 @@ func (s *Store) Update(key []byte, f func([]byte) ([]byte, error)) error {
|
||||
}
|
||||
|
||||
newVal, err := f(val)
|
||||
if err != nil {
|
||||
if err == kvstore.NoOp {
|
||||
return nil
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -22,7 +22,7 @@ func NewStore() *Store {
|
||||
func (s *Store) Get(key []byte) ([]byte, error) {
|
||||
s.RLock()
|
||||
defer s.RUnlock()
|
||||
|
||||
|
||||
if val, ok := s.data[string(key)]; ok {
|
||||
// Return a copy to prevent modification of stored data
|
||||
cp := make([]byte, len(val))
|
||||
@ -35,7 +35,7 @@ func (s *Store) Get(key []byte) ([]byte, error) {
|
||||
func (s *Store) Set(key []byte, value []byte) error {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
|
||||
|
||||
// Store a copy to prevent modification of stored data
|
||||
cp := make([]byte, len(value))
|
||||
copy(cp, value)
|
||||
@ -69,7 +69,9 @@ func (s *Store) Update(key []byte, f func([]byte) ([]byte, error)) error {
|
||||
}
|
||||
|
||||
newVal, err := f(val)
|
||||
if err != nil {
|
||||
if err == kvstore.NoOp {
|
||||
return nil
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
5
sdk/kvstore/noop.go
Normal file
5
sdk/kvstore/noop.go
Normal file
@ -0,0 +1,5 @@
|
||||
package kvstore
|
||||
|
||||
import "fmt"
|
||||
|
||||
var NoOp = fmt.Errorf("noop")
|
@ -88,9 +88,8 @@ func (sys System) FetchProfileFromInput(ctx context.Context, nip19OrNip05Code st
|
||||
hintType = hints.LastInNprofile
|
||||
}
|
||||
for _, r := range p.Relays {
|
||||
nm := nostr.NormalizeURL(r)
|
||||
if !IsVirtualRelay(nm) {
|
||||
sys.Hints.Save(p.PublicKey, nm, hintType, nostr.Now())
|
||||
if !IsVirtualRelay(r) {
|
||||
sys.Hints.Save(p.PublicKey, nostr.NormalizeURL(r), hintType, nostr.Now())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3,18 +3,26 @@ package sdk
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
)
|
||||
|
||||
var outboxShortTermCache = [256]ostcEntry{}
|
||||
|
||||
type ostcEntry struct {
|
||||
pubkey string
|
||||
relays []string
|
||||
when time.Time
|
||||
}
|
||||
|
||||
func (sys *System) FetchOutboxRelays(ctx context.Context, pubkey string, n int) []string {
|
||||
if relays, ok := sys.outboxShortTermCache.Get(pubkey); ok {
|
||||
if len(relays) > n {
|
||||
relays = relays[0:n]
|
||||
}
|
||||
return relays
|
||||
ostcIndex, _ := strconv.ParseUint(pubkey[12:14], 16, 8)
|
||||
now := time.Now()
|
||||
if entry := outboxShortTermCache[ostcIndex]; entry.pubkey == pubkey && entry.when.Add(time.Minute*2).After(now) {
|
||||
return entry.relays
|
||||
}
|
||||
|
||||
// if we have it cached that means we have at least tried to fetch recently and it won't be tried again
|
||||
@ -25,7 +33,11 @@ func (sys *System) FetchOutboxRelays(ctx context.Context, pubkey string, n int)
|
||||
return []string{"wss://relay.damus.io", "wss://nos.lol"}
|
||||
}
|
||||
|
||||
sys.outboxShortTermCache.SetWithTTL(pubkey, relays, time.Minute*2)
|
||||
// we save a copy of this slice to this cache (must be a copy otherwise
|
||||
// we will have a reference to a thing that the caller to this function may change at will)
|
||||
relaysCopy := make([]string, len(relays))
|
||||
copy(relaysCopy, relays)
|
||||
outboxShortTermCache[ostcIndex] = ostcEntry{pubkey, relaysCopy, now}
|
||||
|
||||
if len(relays) > n {
|
||||
relays = relays[0:n]
|
||||
|
@ -202,7 +202,7 @@ func (sys *System) batchReplaceableRelayQueries(
|
||||
defer wg.Done()
|
||||
n := len(filter.Authors)
|
||||
|
||||
ctx, cancel := context.WithTimeout(ctx, time.Millisecond*450+time.Millisecond*50*time.Duration(n))
|
||||
ctx, cancel := context.WithTimeout(ctx, time.Millisecond*950+time.Millisecond*50*time.Duration(n))
|
||||
defer cancel()
|
||||
|
||||
received := 0
|
||||
|
35
sdk/sdk_test.go
Normal file
35
sdk/sdk_test.go
Normal file
@ -0,0 +1,35 @@
|
||||
package sdk
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestSystemFiatjaf(t *testing.T) {
|
||||
sys := NewSystem()
|
||||
ctx := context.Background()
|
||||
|
||||
// get metadata
|
||||
meta, err := sys.FetchProfileFromInput(ctx, "nprofile1qyxhwumn8ghj7mn0wvhxcmmvqyd8wumn8ghj7un9d3shjtnhv4ehgetjde38gcewvdhk6qpq80cvv07tjdrrgpa0j7j7tmnyl2yr6yr7l8j4s3evf6u64th6gkwswpnfsn")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "fiatjaf", meta.Name)
|
||||
|
||||
// check outbox relays
|
||||
relays := sys.FetchOutboxRelays(ctx, meta.PubKey, 5)
|
||||
require.Contains(t, relays, "wss://relay.westernbtc.com")
|
||||
require.Contains(t, relays, "wss://pyramid.fiatjaf.com")
|
||||
|
||||
// fetch notes
|
||||
filter := nostr.Filter{
|
||||
Kinds: []int{1},
|
||||
Authors: []string{meta.PubKey},
|
||||
Limit: 5,
|
||||
}
|
||||
events, err := sys.FetchUserEvents(ctx, filter)
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, events[meta.PubKey])
|
||||
require.GreaterOrEqual(t, len(events[meta.PubKey]), 5)
|
||||
}
|
@ -91,7 +91,7 @@ func (sys *System) FetchSpecificEvent(
|
||||
// (we do this after fetching author outbox relays because we are already going to prioritize these hints)
|
||||
now := nostr.Now()
|
||||
for _, relay := range priorityRelays {
|
||||
sys.Hints.Save(author, relay, hints.LastInNevent, now)
|
||||
sys.Hints.Save(author, nostr.NormalizeURL(relay), hints.LastInNevent, now)
|
||||
}
|
||||
|
||||
// arrange these
|
||||
|
@ -43,9 +43,8 @@ type System struct {
|
||||
|
||||
StoreRelay nostr.RelayStore
|
||||
|
||||
replaceableLoaders []*dataloader.Loader[string, *nostr.Event]
|
||||
addressableLoaders []*dataloader.Loader[string, []*nostr.Event]
|
||||
outboxShortTermCache cache.Cache32[[]string]
|
||||
replaceableLoaders []*dataloader.Loader[string, *nostr.Event]
|
||||
addressableLoaders []*dataloader.Loader[string, []*nostr.Event]
|
||||
}
|
||||
|
||||
type SystemModifier func(sys *System)
|
||||
@ -105,14 +104,11 @@ func NewSystem(mods ...SystemModifier) *System {
|
||||
"wss://search.nos.today",
|
||||
),
|
||||
Hints: memoryh.NewHintDB(),
|
||||
|
||||
outboxShortTermCache: cache_memory.New32[[]string](1000),
|
||||
}
|
||||
|
||||
sys.Pool = nostr.NewSimplePool(context.Background(),
|
||||
nostr.WithAuthorKindQueryMiddleware(sys.TrackQueryAttempts),
|
||||
nostr.WithEventMiddleware(sys.TrackEventHints),
|
||||
nostr.WithEventMiddleware(sys.TrackEventRelays),
|
||||
nostr.WithEventMiddleware(sys.TrackEventHintsAndRelays),
|
||||
nostr.WithDuplicateMiddleware(sys.TrackEventRelaysD),
|
||||
nostr.WithPenaltyBox(),
|
||||
)
|
||||
|
@ -20,7 +20,7 @@ func (sys *System) TrackQueryAttempts(relay string, author string, kind int) {
|
||||
sys.Hints.Save(author, relay, hints.LastFetchAttempt, nostr.Now())
|
||||
}
|
||||
|
||||
func (sys *System) TrackEventHints(ie nostr.RelayEvent) {
|
||||
func (sys *System) TrackEventHintsAndRelays(ie nostr.RelayEvent) {
|
||||
if IsVirtualRelay(ie.Relay.URL) {
|
||||
return
|
||||
}
|
||||
@ -28,6 +28,10 @@ func (sys *System) TrackEventHints(ie nostr.RelayEvent) {
|
||||
return
|
||||
}
|
||||
|
||||
if ie.Kind != 0 && ie.Kind != 10002 {
|
||||
sys.trackEventRelayCommon(ie.ID, ie.Relay.URL, false)
|
||||
}
|
||||
|
||||
switch ie.Kind {
|
||||
case nostr.KindProfileMetadata:
|
||||
// this could be anywhere so it doesn't count
|
||||
@ -43,7 +47,7 @@ func (sys *System) TrackEventHints(ie nostr.RelayEvent) {
|
||||
continue
|
||||
}
|
||||
if len(tag) == 2 || (tag[2] == "" || tag[2] == "write") {
|
||||
sys.Hints.Save(ie.PubKey, tag[1], hints.LastInRelayList, ie.CreatedAt)
|
||||
sys.Hints.Save(ie.PubKey, nostr.NormalizeURL(tag[1]), hints.LastInRelayList, ie.CreatedAt)
|
||||
}
|
||||
}
|
||||
case nostr.KindFollowList:
|
||||
@ -59,11 +63,11 @@ func (sys *System) TrackEventHints(ie nostr.RelayEvent) {
|
||||
continue
|
||||
}
|
||||
if tag[0] == "p" && nostr.IsValidPublicKey(tag[1]) {
|
||||
sys.Hints.Save(tag[1], tag[2], hints.LastInTag, ie.CreatedAt)
|
||||
sys.Hints.Save(tag[1], nostr.NormalizeURL(tag[2]), hints.LastInTag, ie.CreatedAt)
|
||||
}
|
||||
}
|
||||
default:
|
||||
// everything else may have hints
|
||||
// everything else we track by relays and also check for hints
|
||||
sys.Hints.Save(ie.PubKey, ie.Relay.URL, hints.MostRecentEventFetched, ie.CreatedAt)
|
||||
|
||||
for _, tag := range ie.Tags {
|
||||
@ -77,7 +81,7 @@ func (sys *System) TrackEventHints(ie nostr.RelayEvent) {
|
||||
continue
|
||||
}
|
||||
if tag[0] == "p" && nostr.IsValidPublicKey(tag[1]) {
|
||||
sys.Hints.Save(tag[1], tag[2], hints.LastInTag, ie.CreatedAt)
|
||||
sys.Hints.Save(tag[1], nostr.NormalizeURL(tag[2]), hints.LastInTag, ie.CreatedAt)
|
||||
}
|
||||
}
|
||||
|
||||
@ -91,7 +95,7 @@ func (sys *System) TrackEventHints(ie nostr.RelayEvent) {
|
||||
continue
|
||||
}
|
||||
if nostr.IsValidPublicKey(ref.Profile.PublicKey) {
|
||||
sys.Hints.Save(ref.Profile.PublicKey, relay, hints.LastInNprofile, ie.CreatedAt)
|
||||
sys.Hints.Save(ref.Profile.PublicKey, nostr.NormalizeURL(relay), hints.LastInNprofile, ie.CreatedAt)
|
||||
}
|
||||
}
|
||||
} else if ref.Event != nil && nostr.IsValidPublicKey(ref.Event.Author) {
|
||||
@ -102,17 +106,16 @@ func (sys *System) TrackEventHints(ie nostr.RelayEvent) {
|
||||
if p, err := url.Parse(relay); err != nil || (p.Scheme != "wss" && p.Scheme != "ws") {
|
||||
continue
|
||||
}
|
||||
sys.Hints.Save(ref.Event.Author, relay, hints.LastInNevent, ie.CreatedAt)
|
||||
sys.Hints.Save(ref.Event.Author, nostr.NormalizeURL(relay), hints.LastInNevent, ie.CreatedAt)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (sys *System) TrackEventRelays(ie nostr.RelayEvent) {
|
||||
sys.trackEventRelayCommon(ie.ID, ie.Relay.URL)
|
||||
}
|
||||
|
||||
func (sys *System) TrackEventRelaysD(relay, id string) {
|
||||
sys.trackEventRelayCommon(id, relay)
|
||||
if IsVirtualRelay(relay) {
|
||||
return
|
||||
}
|
||||
sys.trackEventRelayCommon(id, relay, true /* we pass this flag so we'll skip creating entries for events that didn't pass the checks on the function above -- i.e. ephemeral events */)
|
||||
}
|
||||
|
@ -63,7 +63,7 @@ type WithLabel string
|
||||
func (_ WithLabel) IsSubscriptionOption() {}
|
||||
|
||||
// WithCheckDuplicate sets checkDuplicate on the subscription
|
||||
type WithCheckDuplicate func(relay, id string) bool
|
||||
type WithCheckDuplicate func(id, relay string) bool
|
||||
|
||||
func (_ WithCheckDuplicate) IsSubscriptionOption() {}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user