Add NIP-11 relay synchronization and group management features
- Introduced a new `sync` package for managing NIP-11 relay information and relay group configurations. - Implemented a cache for NIP-11 documents, allowing retrieval of relay public keys and authoritative configurations. - Enhanced the sync manager to update peer lists based on authoritative configurations from relay group events. - Updated event handling to incorporate policy checks during event imports, ensuring compliance with relay rules. - Refactored various components to utilize the new `sha256-simd` package for improved performance. - Added comprehensive tests to validate the new synchronization and group management functionalities. - Bumped version to v0.24.1 to reflect these changes.
This commit is contained in:
159
pkg/sync/relaygroup.go
Normal file
159
pkg/sync/relaygroup.go
Normal file
@@ -0,0 +1,159 @@
|
||||
// Package sync provides relay group configuration management
|
||||
package sync
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/minio/sha256-simd"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"lol.mleku.dev/log"
|
||||
"next.orly.dev/pkg/database"
|
||||
"next.orly.dev/pkg/encoders/bech32encoding"
|
||||
"next.orly.dev/pkg/encoders/event"
|
||||
"next.orly.dev/pkg/encoders/filter"
|
||||
"next.orly.dev/pkg/encoders/kind"
|
||||
"next.orly.dev/pkg/encoders/tag"
|
||||
)
|
||||
|
||||
// RelayGroupConfig represents a relay group configuration event
|
||||
type RelayGroupConfig struct {
|
||||
Relays []string `json:"relays"`
|
||||
}
|
||||
|
||||
// RelayGroupManager handles relay group configuration
|
||||
type RelayGroupManager struct {
|
||||
db *database.D
|
||||
authorizedPubkeys [][]byte
|
||||
}
|
||||
|
||||
// NewRelayGroupManager creates a new relay group manager
|
||||
func NewRelayGroupManager(db *database.D, adminNpubs []string) *RelayGroupManager {
|
||||
var pubkeys [][]byte
|
||||
for _, npub := range adminNpubs {
|
||||
if pk, err := bech32encoding.NpubOrHexToPublicKeyBinary(npub); err == nil {
|
||||
pubkeys = append(pubkeys, pk)
|
||||
}
|
||||
}
|
||||
|
||||
return &RelayGroupManager{
|
||||
db: db,
|
||||
authorizedPubkeys: pubkeys,
|
||||
}
|
||||
}
|
||||
|
||||
// FindAuthoritativeConfig finds the authoritative relay group configuration
|
||||
// by selecting the latest event by timestamp, with hash tie-breaking
|
||||
func (rgm *RelayGroupManager) FindAuthoritativeConfig(ctx context.Context) (*RelayGroupConfig, error) {
|
||||
if len(rgm.authorizedPubkeys) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Query for all relay group config events from authorized pubkeys
|
||||
f := &filter.F{
|
||||
Kinds: kind.NewS(kind.RelayGroupConfig),
|
||||
Authors: tag.NewFromBytesSlice(rgm.authorizedPubkeys...),
|
||||
}
|
||||
|
||||
events, err := rgm.db.QueryEvents(ctx, f)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(events) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Find the authoritative event
|
||||
authEvent := rgm.selectAuthoritativeEvent(events)
|
||||
if authEvent == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Parse the configuration from the event content
|
||||
var config RelayGroupConfig
|
||||
if err := json.Unmarshal([]byte(authEvent.Content), &config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &config, nil
|
||||
}
|
||||
|
||||
// selectAuthoritativeEvent selects the authoritative event using the specified criteria
|
||||
func (rgm *RelayGroupManager) selectAuthoritativeEvent(events []*event.E) *event.E {
|
||||
if len(events) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Sort events by timestamp (newest first), then by hash (smallest first)
|
||||
sort.Slice(events, func(i, j int) bool {
|
||||
// First compare timestamps (newest first)
|
||||
if events[i].CreatedAt != events[j].CreatedAt {
|
||||
return events[i].CreatedAt > events[j].CreatedAt
|
||||
}
|
||||
|
||||
// If timestamps are equal, compare hashes (smallest first)
|
||||
hashI := sha256.Sum256([]byte(events[i].ID))
|
||||
hashJ := sha256.Sum256([]byte(events[j].ID))
|
||||
return strings.Compare(hex.EncodeToString(hashI[:]), hex.EncodeToString(hashJ[:])) < 0
|
||||
})
|
||||
|
||||
return events[0]
|
||||
}
|
||||
|
||||
// IsAuthorizedPublisher checks if a pubkey is authorized to publish relay group configs
|
||||
func (rgm *RelayGroupManager) IsAuthorizedPublisher(pubkey []byte) bool {
|
||||
for _, authPK := range rgm.authorizedPubkeys {
|
||||
if string(authPK) == string(pubkey) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// ValidateRelayGroupEvent validates a relay group configuration event
|
||||
func (rgm *RelayGroupManager) ValidateRelayGroupEvent(ev *event.E) error {
|
||||
// Check if it's the right kind
|
||||
if ev.Kind != kind.RelayGroupConfig.K {
|
||||
return nil // Not our concern
|
||||
}
|
||||
|
||||
// Check if publisher is authorized
|
||||
if !rgm.IsAuthorizedPublisher(ev.Pubkey) {
|
||||
return nil // Not our concern, but won't be considered authoritative
|
||||
}
|
||||
|
||||
// Try to parse the content
|
||||
var config RelayGroupConfig
|
||||
if err := json.Unmarshal([]byte(ev.Content), &config); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Basic validation - at least one relay should be specified
|
||||
if len(config.Relays) == 0 {
|
||||
return nil // Empty config is allowed, just won't be selected
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// HandleRelayGroupEvent processes a relay group configuration event and updates peer lists
|
||||
func (rgm *RelayGroupManager) HandleRelayGroupEvent(ev *event.E, syncManager *Manager) {
|
||||
if ev.Kind != kind.RelayGroupConfig.K {
|
||||
return
|
||||
}
|
||||
|
||||
// Check if this event is the new authoritative configuration
|
||||
authConfig, err := rgm.FindAuthoritativeConfig(context.Background())
|
||||
if err != nil {
|
||||
log.E.F("failed to find authoritative config: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if authConfig != nil {
|
||||
// Update the sync manager's peer list
|
||||
syncManager.UpdatePeers(authConfig.Relays)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user