sdk/hints: add badger implementation.

This commit is contained in:
fiatjaf 2025-03-25 17:00:37 -03:00
parent 1544d90354
commit a1e2a46b5b
3 changed files with 229 additions and 0 deletions

168
sdk/hints/badgerh/db.go Normal file
View File

@ -0,0 +1,168 @@
package badgerh
import (
"encoding/hex"
"fmt"
"math"
"slices"
"github.com/dgraph-io/badger/v4"
"github.com/nbd-wtf/go-nostr"
"github.com/nbd-wtf/go-nostr/sdk/hints"
)
var _ hints.HintsDB = (*BadgerHints)(nil)
type BadgerHints struct {
db *badger.DB
}
func NewBadgerHints(path string) (*BadgerHints, error) {
opts := badger.DefaultOptions(path)
db, err := badger.Open(opts)
if err != nil {
return nil, err
}
return &BadgerHints{db: db}, nil
}
func (bh *BadgerHints) Close() {
bh.db.Close()
}
func (bh *BadgerHints) Save(pubkey string, relay string, hintkey hints.HintKey, ts nostr.Timestamp) {
if now := nostr.Now(); ts > now {
ts = now
}
err := bh.db.Update(func(txn *badger.Txn) error {
k := encodeKey(pubkey, relay)
var tss timestamps
item, err := txn.Get(k)
if err == nil {
err = item.Value(func(val []byte) error {
// there is a value, so we may update it or not
tss = parseValue(val)
return nil
})
if err != nil {
return err
}
} else if err != badger.ErrKeyNotFound {
return err
}
if tss[hintkey] < ts {
tss[hintkey] = ts
return txn.Set(k, encodeValue(tss))
}
return nil
})
if err != nil {
nostr.InfoLogger.Printf("[sdk/hints/badger] unexpected error on save: %s\n", err)
}
}
func (bh *BadgerHints) TopN(pubkey string, n int) []string {
type relayScore struct {
relay string
score int64
}
scores := make([]relayScore, 0, n)
err := bh.db.View(func(txn *badger.Txn) error {
opts := badger.DefaultIteratorOptions
opts.Prefix, _ = hex.DecodeString(pubkey)
it := txn.NewIterator(opts)
defer it.Close()
for it.Seek(opts.Prefix); it.Valid(); it.Next() {
item := it.Item()
k := item.Key()
relay := string(k[32:])
err := item.Value(func(val []byte) error {
tss := parseValue(val)
scores = append(scores, relayScore{relay, tss.sum()})
return nil
})
if err != nil {
continue
}
}
return nil
})
if err != nil {
nostr.InfoLogger.Printf("[sdk/hints/badger] unexpected error on topn: %s\n", err)
return nil
}
slices.SortFunc(scores, func(a, b relayScore) int {
return int(b.score - a.score)
})
result := make([]string, 0, n)
for i, rs := range scores {
if i >= n {
break
}
result = append(result, rs.relay)
}
return result
}
func (bh *BadgerHints) PrintScores() {
fmt.Println("= print scores")
err := bh.db.View(func(txn *badger.Txn) error {
it := txn.NewIterator(badger.DefaultIteratorOptions)
defer it.Close()
var lastPubkey string
i := 0
for it.Seek(nil); it.Valid(); it.Next() {
item := it.Item()
k := item.Key()
pubkey, relay := parseKey(k)
if pubkey != lastPubkey {
fmt.Println("== relay scores for", pubkey)
lastPubkey = pubkey
i = 0
} else {
i++
}
err := item.Value(func(val []byte) error {
tss := parseValue(val)
fmt.Printf(" %3d :: %30s ::> %12d\n", i, relay, tss.sum())
return nil
})
if err != nil {
continue
}
}
return nil
})
if err != nil {
nostr.InfoLogger.Printf("[sdk/hints/badger] unexpected error on print: %s\n", err)
}
}
type timestamps [4]nostr.Timestamp
func (tss timestamps) sum() int64 {
now := nostr.Now() + 24*60*60
var sum int64
for i, ts := range tss {
if ts == 0 {
continue
}
value := float64(hints.HintKey(i).BasePoints()) * 10000000000 / math.Pow(float64(max(now-ts, 1)), 1.3)
// fmt.Println(" ", i, "value:", value)
sum += int64(value)
}
return sum
}

40
sdk/hints/badgerh/keys.go Normal file
View File

@ -0,0 +1,40 @@
package badgerh
import (
"encoding/binary"
"encoding/hex"
"unsafe"
"github.com/nbd-wtf/go-nostr"
)
func encodeKey(pubhintkey, relay string) []byte {
k := make([]byte, 32+len(relay))
hex.Decode(k[0:32], []byte(pubhintkey))
copy(k[32:], unsafe.Slice(unsafe.StringData(relay), len(relay)))
return k
}
func parseKey(k []byte) (pubkey string, relay string) {
pubkey = hex.EncodeToString(k[0:32])
relay = string(k[32:])
return
}
func encodeValue(tss timestamps) []byte {
v := make([]byte, 16)
binary.LittleEndian.PutUint32(v[0:], uint32(tss[0]))
binary.LittleEndian.PutUint32(v[4:], uint32(tss[1]))
binary.LittleEndian.PutUint32(v[8:], uint32(tss[2]))
binary.LittleEndian.PutUint32(v[12:], uint32(tss[3]))
return v
}
func parseValue(v []byte) timestamps {
return timestamps{
nostr.Timestamp(binary.LittleEndian.Uint32(v[0:])),
nostr.Timestamp(binary.LittleEndian.Uint32(v[4:])),
nostr.Timestamp(binary.LittleEndian.Uint32(v[8:])),
nostr.Timestamp(binary.LittleEndian.Uint32(v[12:])),
}
}

View File

@ -0,0 +1,21 @@
package test
import (
"os"
"testing"
"github.com/nbd-wtf/go-nostr/sdk/hints/badgerh"
)
func TestBadgerHints(t *testing.T) {
path := "/tmp/tmpsdkhintsbadger"
os.RemoveAll(path)
hdb, err := badgerh.NewBadgerHints(path)
if err != nil {
t.Fatal(err)
}
defer hdb.Close()
runTestWith(t, hdb)
}