diff --git a/go.mod b/go.mod index 0ffe9fa..8fe7c41 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,6 @@ require ( github.com/tidwall/gjson v1.14.4 github.com/tyler-smith/go-bip32 v1.0.0 github.com/tyler-smith/go-bip39 v1.1.0 - golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 golang.org/x/exp v0.0.0-20221106115401-f9659909a136 golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc ) @@ -32,5 +31,8 @@ require ( github.com/pkg/errors v0.9.1 // indirect github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.0 // indirect + golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 // indirect golang.org/x/sys v0.6.0 // indirect ) + +replace github.com/dgraph-io/ristretto => github.com/aryehlev/ristretto v0.0.0-20230325112030-fd222a1ebb5e diff --git a/go.sum b/go.sum index 07f6f61..de78c96 100644 --- a/go.sum +++ b/go.sum @@ -3,6 +3,8 @@ github.com/FactomProject/basen v0.0.0-20150613233007-fe3947df716e/go.mod h1:kGUq github.com/FactomProject/btcutilecc v0.0.0-20130527213604-d3a63a5752ec h1:1Qb69mGp/UtRPn422BH4/Y4Q3SLUrD9KHuDkm8iodFc= github.com/FactomProject/btcutilecc v0.0.0-20130527213604-d3a63a5752ec/go.mod h1:CD8UlnlLDiqb36L110uqiP2iSflVjx9g/3U9hCI4q2U= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= +github.com/aryehlev/ristretto v0.0.0-20230325112030-fd222a1ebb5e h1:6XE6UUXzL1OTmsHg/mOqTPyER/KiIhTp7lK4YCxiO3k= +github.com/aryehlev/ristretto v0.0.0-20230325112030-fd222a1ebb5e/go.mod h1:CVcbbyUW8c4weQp46o4er/sA83zTXDPV8xc8D85Dgag= github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= github.com/btcsuite/btcd v0.22.0-beta.0.20220111032746-97732e52810c/go.mod h1:tjmYdS6MLJ5/s0Fj4DbLgSbDHbEqLJrtnHecBFkdz5M= github.com/btcsuite/btcd v0.23.0/go.mod h1:0QJIIN1wwIXF/3G/m87gIwGniDMDQqjVn4SZgnFpsYY= @@ -39,10 +41,7 @@ github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= -github.com/dgraph-io/ristretto v0.1.1 h1:6CWw5tJNgpegArSHpNHJKldNeq03FQCwYvfMVWajOK8= -github.com/dgraph-io/ristretto v0.1.1/go.mod h1:S1GPSBCYCIhmVNfcth17y2zZtQT6wzkzgwUve0VDWWA= github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA= -github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= @@ -92,7 +91,6 @@ github.com/puzpuzpuz/xsync/v2 v2.5.0 h1:2k4qrO/orvmEXZ3hmtHqIy9XaQtPTwzMZk1+iErp github.com/puzpuzpuz/xsync/v2 v2.5.0/go.mod h1:gD2H2krq/w52MfPLE+Uy64TzJDVY7lP2znR9qmR35kU= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.1.5-0.20170601210322-f6abca593680/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= @@ -129,7 +127,6 @@ golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -148,7 +145,6 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= diff --git a/sdk/cache/interface.go b/sdk/cache/interface.go new file mode 100644 index 0000000..5153913 --- /dev/null +++ b/sdk/cache/interface.go @@ -0,0 +1,10 @@ +package cache + +import "time" + +type Cache32[V any] interface { + Get(k string) (v V, ok bool) + Delete(k string) + Set(k string, v V) bool + SetWithTTL(k string, v V, d time.Duration) bool +} diff --git a/sdk/cache/memory/cache.go b/sdk/cache/memory/cache.go new file mode 100644 index 0000000..9dc771c --- /dev/null +++ b/sdk/cache/memory/cache.go @@ -0,0 +1,48 @@ +package cache_memory + +import ( + "encoding/binary" + "encoding/hex" + "time" + + "github.com/dgraph-io/ristretto" +) + +type RistrettoCache[V any] struct { + Cache *ristretto.Cache[string, V] +} + +func New32[V any](max int64) *RistrettoCache[V] { + cache, _ := ristretto.NewCache(&ristretto.Config[string, V]{ + NumCounters: max * 10, + MaxCost: max, + BufferItems: 64, + KeyToHash: func(key string) (uint64, uint64) { return h32(key), 0 }, + }) + return &RistrettoCache[V]{Cache: cache} +} + +func (s RistrettoCache[V]) Get(k string) (v V, ok bool) { return s.Cache.Get(k) } +func (s RistrettoCache[V]) Delete(k string) { s.Cache.Del(k) } +func (s RistrettoCache[V]) Set(k string, v V) bool { return s.Cache.Set(k, v, 1) } +func (s RistrettoCache[V]) SetWithTTL(k string, v V, d time.Duration) bool { + return s.Cache.SetWithTTL(k, v, 1, d) +} + +func h32(key string) uint64 { + // we get an event id or pubkey as hex, + // so just extract the last 8 bytes from it and turn them into a uint64 + return shortUint64(key) +} + +func shortUint64(idOrPubkey string) uint64 { + length := len(idOrPubkey) + if length < 8 { + return 0 + } + b, err := hex.DecodeString(idOrPubkey[length-8:]) + if err != nil { + return 0 + } + return uint64(binary.BigEndian.Uint32(b)) +} diff --git a/sdk/follows.go b/sdk/follows.go new file mode 100644 index 0000000..dbb0913 --- /dev/null +++ b/sdk/follows.go @@ -0,0 +1,7 @@ +package sdk + +type Follow struct { + Pubkey string + Relay string + Petname string +} diff --git a/sdk/init.go b/sdk/init.go new file mode 100644 index 0000000..e5c6823 --- /dev/null +++ b/sdk/init.go @@ -0,0 +1,41 @@ +package sdk + +import ( + "context" + "time" + + "github.com/nbd-wtf/go-nostr" + "github.com/nbd-wtf/go-nostr/sdk/cache" +) + +type System struct { + relaysCache cache.Cache32[[]Relay] + followsCache cache.Cache32[[]Follow] + metadataCache cache.Cache32[*ProfileMetadata] + pool *nostr.SimplePool + metadataRelays []string + relayListRelays []string +} + +func (sys System) FetchRelaysForPubkey(ctx context.Context, pubkey string) []Relay { + if v, ok := sys.relaysCache.Get(pubkey); ok { + return v + } + + ctx, cancel := context.WithTimeout(ctx, time.Second*5) + defer cancel() + res := FetchRelaysForPubkey(ctx, sys.pool, pubkey, sys.relayListRelays...) + sys.relaysCache.SetWithTTL(pubkey, res, time.Hour*6) + return res +} + +func (sys System) FetchOutboxRelaysForPubkey(ctx context.Context, pubkey string) []string { + relays := sys.FetchRelaysForPubkey(ctx, pubkey) + result := make([]string, 0, len(relays)) + for _, relay := range relays { + if relay.Outbox { + result = append(result, relay.URL) + } + } + return result +} diff --git a/metadata.go b/sdk/metadata.go similarity index 64% rename from metadata.go rename to sdk/metadata.go index 922f993..2c39461 100644 --- a/metadata.go +++ b/sdk/metadata.go @@ -1,11 +1,17 @@ -package nostr +package sdk import ( + "context" "encoding/json" "fmt" + + "github.com/nbd-wtf/go-nostr" + "github.com/nbd-wtf/go-nostr/nip19" ) type ProfileMetadata struct { + pubkey string + Name string `json:"name,omitempty"` DisplayName string `json:"display_name,omitempty"` About string `json:"about,omitempty"` @@ -16,7 +22,17 @@ type ProfileMetadata struct { LUD16 string `json:"lud16,omitempty"` } -func ParseMetadata(event Event) (*ProfileMetadata, error) { +func (p ProfileMetadata) Npub() string { + v, _ := nip19.EncodePublicKey(p.pubkey) + return v +} + +func (p ProfileMetadata) Nprofile(ctx context.Context, sys *System, nrelays int) string { + v, _ := nip19.EncodeProfile(p.pubkey, sys.FetchOutboxRelaysForPubkey(ctx, p.pubkey)) + return v +} + +func ParseMetadata(event *nostr.Event) (*ProfileMetadata, error) { if event.Kind != 0 { return nil, fmt.Errorf("event %s is kind %d, not 0", event.ID, event.Kind) } diff --git a/sdk/relays.go b/sdk/relays.go index 40e85e3..7ce9185 100644 --- a/sdk/relays.go +++ b/sdk/relays.go @@ -13,18 +13,13 @@ type Relay struct { Outbox bool } -func FetchRelaysForPubkey(ctx context.Context, pool *nostr.SimplePool, pubkey string, extraRelays ...string) []Relay { +func FetchOutboxRelaysForPubkey(ctx context.Context, pool *nostr.SimplePool, pubkey string, n int) { +} + +func FetchRelaysForPubkey(ctx context.Context, pool *nostr.SimplePool, pubkey string, relays ...string) []Relay { ctx, cancel := context.WithCancel(ctx) defer cancel() - relays := append(extraRelays, - "wss://nostr-pub.wellorder.net", - "wss://relay.damus.io", - "wss://nos.lol", - "wss://nostr.mom", - "wss://relay.nostr.bg", - ) - ch := pool.SubManyEose(ctx, relays, nostr.Filters{ { Kinds: []int{