// Package grapevine computes Web-of-Trust scores by BFS traversal // of the follow graph (kind 3 events). package grapevine import ( "git.smesh.lol/moxie/pkg/mxutil" "bytes" "git.smesh.lol/nostr/pkg/event" "git.smesh.lol/nostr/pkg/filter" "git.smesh.lol/nostr/pkg/kind" "git.smesh.lol/nostr/pkg/tag" "git.smesh.lol/morly/pkg/store" ) // Score is a WoT score for a pubkey. type Score struct { Pubkey []byte Value float64 Depth int32 } // WoT computes Web-of-Trust scores from the follow graph. type WoT struct { store *store.Engine } // New creates a WoT scorer. func New(s *store.Engine) (w *WoT) { return &WoT{store: s} } // Compute runs BFS from seed to maxDepth, returning scored pubkeys // sorted by score descending. func (w *WoT) Compute(seed []byte, maxDepth int32) (ss []Score) { scores := map[string]*Score{} visited := map[string]bool{string(seed): true} queue := [][]byte{seed} for depth := 1; depth <= maxDepth && len(queue) > 0; depth++ { decay := 1.0 / float64(int32(1)< 0 && out[j].Value > out[j-1].Value; j-- { out[j], out[j-1] = out[j-1], out[j] } } return out } // IsTrusted returns true if pubkey has score >= threshold. func IsTrusted(scores []Score, pubkey []byte, threshold float64) (ok bool) { for i := range scores { if bytes.Equal(scores[i].Pubkey, pubkey) { return scores[i].Value >= threshold } } return false } // GetFollows returns the set of pubkeys followed by pubkey (kind-3 p-tags). func GetFollows(s *store.Engine, pubkey []byte) (ss [][]byte) { return (&WoT{store: s}).GetFollows(pubkey) } func (w *WoT) GetFollows(pubkey []byte) (ss [][]byte) { kind.Ensure() f := &filter.F{ Kinds: kind.NewS(kind.FollowList), Authors: tag.NewFromBytesSlice(pubkey), } limit := uint32(1) f.Limit = &limit // The result type is spelled out, and event imported, because stage4 // cannot resolve `[]*event.E` through another package's export data when // the caller does not import event itself: the short var came out with no // type, every later use of it did too, and grapevine failed to build from // source (`cannot resolve type of short var ev ... rhs=ssatype=nil`, then // `%t35 defined with type '{ i64, ptr }' but expected '{ ptr, i64, i64 }'`). // It only ever built from a stale cache entry, which is why the relay and // the pipeline tests passed until the store was edited. var events []*event.E var err error events, err = w.store.QueryEvents(f) if err != nil || len(events) == 0 { return nil } ev := events[0] if ev.Tags == nil { return nil } pTags := ev.Tags.GetAll([]byte("p")) out := [][]byte{:0:len(pTags)} for _, pt := range pTags { val := pt.ValueBinary() if val != nil && len(val) == 32 { pk := []byte{:32} copy(pk, val) out = mxutil.Ensure(out, 1) out = push(out, pk) } } return out }