trust.go raw

   1  package directory_client
   2  
   3  import (
   4  	"sync"
   5  	"time"
   6  
   7  	"next.orly.dev/pkg/nostr/encoders/event"
   8  	"next.orly.dev/pkg/protocol/directory"
   9  )
  10  
  11  // TrustCalculator computes aggregate trust scores from multiple trust acts.
  12  //
  13  // It maintains a collection of trust acts and provides methods to calculate
  14  // weighted trust scores for relay public keys.
  15  type TrustCalculator struct {
  16  	mu   sync.RWMutex
  17  	acts map[string][]*directory.TrustAct
  18  }
  19  
  20  // NewTrustCalculator creates a new trust calculator instance.
  21  func NewTrustCalculator() *TrustCalculator {
  22  	return &TrustCalculator{
  23  		acts: make(map[string][]*directory.TrustAct),
  24  	}
  25  }
  26  
  27  // AddAct adds a trust act to the calculator.
  28  func (tc *TrustCalculator) AddAct(act *directory.TrustAct) {
  29  	if act == nil {
  30  		return
  31  	}
  32  
  33  	tc.mu.Lock()
  34  	defer tc.mu.Unlock()
  35  
  36  	targetPubkey := act.TargetPubkey
  37  	tc.acts[targetPubkey] = append(tc.acts[targetPubkey], act)
  38  }
  39  
  40  // CalculateTrust calculates an aggregate trust score for a public key.
  41  //
  42  // The score is computed as a weighted average where:
  43  //   - high trust = 100
  44  //   - medium trust = 50
  45  //   - low trust = 25
  46  //
  47  // Expired trust acts are excluded from the calculation.
  48  // Returns a score between 0 and 100.
  49  func (tc *TrustCalculator) CalculateTrust(pubkey string) float64 {
  50  	tc.mu.RLock()
  51  	defer tc.mu.RUnlock()
  52  
  53  	acts := tc.acts[pubkey]
  54  	if len(acts) == 0 {
  55  		return 0
  56  	}
  57  
  58  	now := time.Now()
  59  	var total float64
  60  	var count int
  61  
  62  	// Weight mapping
  63  	weights := map[directory.TrustLevel]float64{
  64  		directory.TrustLevelHigh:   100,
  65  		directory.TrustLevelMedium: 50,
  66  		directory.TrustLevelLow:    25,
  67  	}
  68  
  69  	for _, act := range acts {
  70  		// Skip expired acts
  71  		if act.Expiry != nil && act.Expiry.Before(now) {
  72  			continue
  73  		}
  74  
  75  		weight, ok := weights[act.TrustLevel]
  76  		if !ok {
  77  			continue
  78  		}
  79  
  80  		total += weight
  81  		count++
  82  	}
  83  
  84  	if count == 0 {
  85  		return 0
  86  	}
  87  
  88  	return total / float64(count)
  89  }
  90  
  91  // GetActs returns all trust acts for a specific public key.
  92  func (tc *TrustCalculator) GetActs(pubkey string) []*directory.TrustAct {
  93  	tc.mu.RLock()
  94  	defer tc.mu.RUnlock()
  95  
  96  	acts := tc.acts[pubkey]
  97  	result := make([]*directory.TrustAct, len(acts))
  98  	copy(result, acts)
  99  	return result
 100  }
 101  
 102  // GetActiveTrustActs returns only non-expired trust acts for a public key.
 103  func (tc *TrustCalculator) GetActiveTrustActs(pubkey string) []*directory.TrustAct {
 104  	tc.mu.RLock()
 105  	defer tc.mu.RUnlock()
 106  
 107  	acts := tc.acts[pubkey]
 108  	now := time.Now()
 109  	result := make([]*directory.TrustAct, 0)
 110  
 111  	for _, act := range acts {
 112  		if act.Expiry == nil || act.Expiry.After(now) {
 113  			result = append(result, act)
 114  		}
 115  	}
 116  
 117  	return result
 118  }
 119  
 120  // Clear removes all trust acts from the calculator.
 121  func (tc *TrustCalculator) Clear() {
 122  	tc.mu.Lock()
 123  	defer tc.mu.Unlock()
 124  
 125  	tc.acts = make(map[string][]*directory.TrustAct)
 126  }
 127  
 128  // GetAllPubkeys returns all public keys that have trust acts.
 129  func (tc *TrustCalculator) GetAllPubkeys() []string {
 130  	tc.mu.RLock()
 131  	defer tc.mu.RUnlock()
 132  
 133  	pubkeys := make([]string, 0, len(tc.acts))
 134  	for pubkey := range tc.acts {
 135  		pubkeys = append(pubkeys, pubkey)
 136  	}
 137  	return pubkeys
 138  }
 139  
 140  // ReplicationFilter manages replication decisions based on trust scores.
 141  //
 142  // It uses a TrustCalculator to compute trust scores and determines which
 143  // relays are trusted enough for replication based on a minimum threshold.
 144  type ReplicationFilter struct {
 145  	mu            sync.RWMutex
 146  	trustCalc     *TrustCalculator
 147  	minTrustScore float64
 148  	trustedRelays map[string]bool
 149  }
 150  
 151  // NewReplicationFilter creates a new replication filter with a minimum trust score threshold.
 152  func NewReplicationFilter(minTrustScore float64) *ReplicationFilter {
 153  	return &ReplicationFilter{
 154  		trustCalc:     NewTrustCalculator(),
 155  		minTrustScore: minTrustScore,
 156  		trustedRelays: make(map[string]bool),
 157  	}
 158  }
 159  
 160  // AddTrustAct adds a trust act and updates the trusted relays set.
 161  func (rf *ReplicationFilter) AddTrustAct(act *directory.TrustAct) {
 162  	if act == nil {
 163  		return
 164  	}
 165  
 166  	rf.trustCalc.AddAct(act)
 167  
 168  	// Update trusted relays based on new trust score
 169  	score := rf.trustCalc.CalculateTrust(act.TargetPubkey)
 170  
 171  	rf.mu.Lock()
 172  	defer rf.mu.Unlock()
 173  
 174  	if score >= rf.minTrustScore {
 175  		rf.trustedRelays[act.TargetPubkey] = true
 176  	} else {
 177  		delete(rf.trustedRelays, act.TargetPubkey)
 178  	}
 179  }
 180  
 181  // ShouldReplicate checks if a relay is trusted enough for replication.
 182  func (rf *ReplicationFilter) ShouldReplicate(pubkey string) bool {
 183  	rf.mu.RLock()
 184  	defer rf.mu.RUnlock()
 185  
 186  	return rf.trustedRelays[pubkey]
 187  }
 188  
 189  // GetTrustedRelays returns all trusted relay public keys.
 190  func (rf *ReplicationFilter) GetTrustedRelays() []string {
 191  	rf.mu.RLock()
 192  	defer rf.mu.RUnlock()
 193  
 194  	relays := make([]string, 0, len(rf.trustedRelays))
 195  	for pubkey := range rf.trustedRelays {
 196  		relays = append(relays, pubkey)
 197  	}
 198  	return relays
 199  }
 200  
 201  // GetTrustScore returns the trust score for a relay.
 202  func (rf *ReplicationFilter) GetTrustScore(pubkey string) float64 {
 203  	return rf.trustCalc.CalculateTrust(pubkey)
 204  }
 205  
 206  // SetMinTrustScore updates the minimum trust score threshold and recalculates trusted relays.
 207  func (rf *ReplicationFilter) SetMinTrustScore(minScore float64) {
 208  	rf.mu.Lock()
 209  	defer rf.mu.Unlock()
 210  
 211  	rf.minTrustScore = minScore
 212  
 213  	// Recalculate trusted relays with new threshold
 214  	rf.trustedRelays = make(map[string]bool)
 215  	for _, pubkey := range rf.trustCalc.GetAllPubkeys() {
 216  		score := rf.trustCalc.CalculateTrust(pubkey)
 217  		if score >= rf.minTrustScore {
 218  			rf.trustedRelays[pubkey] = true
 219  		}
 220  	}
 221  }
 222  
 223  // GetMinTrustScore returns the current minimum trust score threshold.
 224  func (rf *ReplicationFilter) GetMinTrustScore() float64 {
 225  	rf.mu.RLock()
 226  	defer rf.mu.RUnlock()
 227  
 228  	return rf.minTrustScore
 229  }
 230  
 231  // FilterEvents filters events to only those from trusted relays.
 232  func (rf *ReplicationFilter) FilterEvents(events []*event.E) []*event.E {
 233  	rf.mu.RLock()
 234  	defer rf.mu.RUnlock()
 235  
 236  	filtered := make([]*event.E, 0)
 237  	for _, ev := range events {
 238  		if rf.trustedRelays[string(ev.Pubkey)] {
 239  			filtered = append(filtered, ev)
 240  		}
 241  	}
 242  	return filtered
 243  }
 244