- 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.
160 lines
4.3 KiB
Go
160 lines
4.3 KiB
Go
// 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)
|
|
}
|
|
}
|