Files
next.orly.dev/pkg/protocol/directory-client/trust.go
2025-11-23 08:15:06 +00:00

244 lines
5.8 KiB
Go

package directory_client
import (
"sync"
"time"
"git.mleku.dev/mleku/nostr/encoders/event"
"next.orly.dev/pkg/protocol/directory"
)
// TrustCalculator computes aggregate trust scores from multiple trust acts.
//
// It maintains a collection of trust acts and provides methods to calculate
// weighted trust scores for relay public keys.
type TrustCalculator struct {
mu sync.RWMutex
acts map[string][]*directory.TrustAct
}
// NewTrustCalculator creates a new trust calculator instance.
func NewTrustCalculator() *TrustCalculator {
return &TrustCalculator{
acts: make(map[string][]*directory.TrustAct),
}
}
// AddAct adds a trust act to the calculator.
func (tc *TrustCalculator) AddAct(act *directory.TrustAct) {
if act == nil {
return
}
tc.mu.Lock()
defer tc.mu.Unlock()
targetPubkey := act.TargetPubkey
tc.acts[targetPubkey] = append(tc.acts[targetPubkey], act)
}
// CalculateTrust calculates an aggregate trust score for a public key.
//
// The score is computed as a weighted average where:
// - high trust = 100
// - medium trust = 50
// - low trust = 25
//
// Expired trust acts are excluded from the calculation.
// Returns a score between 0 and 100.
func (tc *TrustCalculator) CalculateTrust(pubkey string) float64 {
tc.mu.RLock()
defer tc.mu.RUnlock()
acts := tc.acts[pubkey]
if len(acts) == 0 {
return 0
}
now := time.Now()
var total float64
var count int
// Weight mapping
weights := map[directory.TrustLevel]float64{
directory.TrustLevelHigh: 100,
directory.TrustLevelMedium: 50,
directory.TrustLevelLow: 25,
}
for _, act := range acts {
// Skip expired acts
if act.Expiry != nil && act.Expiry.Before(now) {
continue
}
weight, ok := weights[act.TrustLevel]
if !ok {
continue
}
total += weight
count++
}
if count == 0 {
return 0
}
return total / float64(count)
}
// GetActs returns all trust acts for a specific public key.
func (tc *TrustCalculator) GetActs(pubkey string) []*directory.TrustAct {
tc.mu.RLock()
defer tc.mu.RUnlock()
acts := tc.acts[pubkey]
result := make([]*directory.TrustAct, len(acts))
copy(result, acts)
return result
}
// GetActiveTrustActs returns only non-expired trust acts for a public key.
func (tc *TrustCalculator) GetActiveTrustActs(pubkey string) []*directory.TrustAct {
tc.mu.RLock()
defer tc.mu.RUnlock()
acts := tc.acts[pubkey]
now := time.Now()
result := make([]*directory.TrustAct, 0)
for _, act := range acts {
if act.Expiry == nil || act.Expiry.After(now) {
result = append(result, act)
}
}
return result
}
// Clear removes all trust acts from the calculator.
func (tc *TrustCalculator) Clear() {
tc.mu.Lock()
defer tc.mu.Unlock()
tc.acts = make(map[string][]*directory.TrustAct)
}
// GetAllPubkeys returns all public keys that have trust acts.
func (tc *TrustCalculator) GetAllPubkeys() []string {
tc.mu.RLock()
defer tc.mu.RUnlock()
pubkeys := make([]string, 0, len(tc.acts))
for pubkey := range tc.acts {
pubkeys = append(pubkeys, pubkey)
}
return pubkeys
}
// ReplicationFilter manages replication decisions based on trust scores.
//
// It uses a TrustCalculator to compute trust scores and determines which
// relays are trusted enough for replication based on a minimum threshold.
type ReplicationFilter struct {
mu sync.RWMutex
trustCalc *TrustCalculator
minTrustScore float64
trustedRelays map[string]bool
}
// NewReplicationFilter creates a new replication filter with a minimum trust score threshold.
func NewReplicationFilter(minTrustScore float64) *ReplicationFilter {
return &ReplicationFilter{
trustCalc: NewTrustCalculator(),
minTrustScore: minTrustScore,
trustedRelays: make(map[string]bool),
}
}
// AddTrustAct adds a trust act and updates the trusted relays set.
func (rf *ReplicationFilter) AddTrustAct(act *directory.TrustAct) {
if act == nil {
return
}
rf.trustCalc.AddAct(act)
// Update trusted relays based on new trust score
score := rf.trustCalc.CalculateTrust(act.TargetPubkey)
rf.mu.Lock()
defer rf.mu.Unlock()
if score >= rf.minTrustScore {
rf.trustedRelays[act.TargetPubkey] = true
} else {
delete(rf.trustedRelays, act.TargetPubkey)
}
}
// ShouldReplicate checks if a relay is trusted enough for replication.
func (rf *ReplicationFilter) ShouldReplicate(pubkey string) bool {
rf.mu.RLock()
defer rf.mu.RUnlock()
return rf.trustedRelays[pubkey]
}
// GetTrustedRelays returns all trusted relay public keys.
func (rf *ReplicationFilter) GetTrustedRelays() []string {
rf.mu.RLock()
defer rf.mu.RUnlock()
relays := make([]string, 0, len(rf.trustedRelays))
for pubkey := range rf.trustedRelays {
relays = append(relays, pubkey)
}
return relays
}
// GetTrustScore returns the trust score for a relay.
func (rf *ReplicationFilter) GetTrustScore(pubkey string) float64 {
return rf.trustCalc.CalculateTrust(pubkey)
}
// SetMinTrustScore updates the minimum trust score threshold and recalculates trusted relays.
func (rf *ReplicationFilter) SetMinTrustScore(minScore float64) {
rf.mu.Lock()
defer rf.mu.Unlock()
rf.minTrustScore = minScore
// Recalculate trusted relays with new threshold
rf.trustedRelays = make(map[string]bool)
for _, pubkey := range rf.trustCalc.GetAllPubkeys() {
score := rf.trustCalc.CalculateTrust(pubkey)
if score >= rf.minTrustScore {
rf.trustedRelays[pubkey] = true
}
}
}
// GetMinTrustScore returns the current minimum trust score threshold.
func (rf *ReplicationFilter) GetMinTrustScore() float64 {
rf.mu.RLock()
defer rf.mu.RUnlock()
return rf.minTrustScore
}
// FilterEvents filters events to only those from trusted relays.
func (rf *ReplicationFilter) FilterEvents(events []*event.E) []*event.E {
rf.mu.RLock()
defer rf.mu.RUnlock()
filtered := make([]*event.E, 0)
for _, ev := range events {
if rf.trustedRelays[string(ev.Pubkey)] {
filtered = append(filtered, ev)
}
}
return filtered
}