Complete BZZZ functionality port to CHORUS

🎭 CHORUS now contains full BZZZ functionality adapted for containers

Core systems ported:
- P2P networking (libp2p with DHT and PubSub)
- Task coordination (COOEE protocol)
- HMMM collaborative reasoning
- SHHH encryption and security
- SLURP admin election system
- UCXL content addressing
- UCXI server integration
- Hypercore logging system
- Health monitoring and graceful shutdown
- License validation with KACHING

Container adaptations:
- Environment variable configuration (no YAML files)
- Container-optimized logging to stdout/stderr
- Auto-generated agent IDs for container deployments
- Docker-first architecture

All proven BZZZ P2P protocols, AI integration, and collaboration
features are now available in containerized form.

Next: Build and test container deployment.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
anthonyrawlins
2025-09-02 20:02:37 +10:00
parent 7c6cbd562a
commit 543ab216f9
224 changed files with 86331 additions and 186 deletions

View File

@@ -0,0 +1,682 @@
// Package distribution provides gossip protocol for metadata synchronization
package distribution
import (
"context"
"encoding/json"
"fmt"
"math/rand"
"sync"
"time"
"chorus.services/bzzz/pkg/dht"
"chorus.services/bzzz/pkg/config"
"chorus.services/bzzz/pkg/ucxl"
)
// GossipProtocolImpl implements GossipProtocol interface for metadata synchronization
type GossipProtocolImpl struct {
mu sync.RWMutex
dht *dht.DHT
config *config.Config
running bool
gossipInterval time.Duration
maxGossipPeers int
compressionEnabled bool
messageBuffer chan *GossipMessage
state *GossipState
stats *GossipStatistics
metadataCache map[string]*ContextMetadata
vectorClock map[string]int64
failureDetector *FailureDetector
}
// GossipMessage represents a message in the gossip protocol
type GossipMessage struct {
MessageID string `json:"message_id"`
MessageType GossipMessageType `json:"message_type"`
SenderID string `json:"sender_id"`
Timestamp time.Time `json:"timestamp"`
TTL int `json:"ttl"`
VectorClock map[string]int64 `json:"vector_clock"`
Payload map[string]interface{} `json:"payload"`
Metadata *GossipMessageMetadata `json:"metadata"`
}
// GossipMessageType represents different types of gossip messages
type GossipMessageType string
const (
GossipMessageHeartbeat GossipMessageType = "heartbeat"
GossipMessageMetadataSync GossipMessageType = "metadata_sync"
GossipMessageContextUpdate GossipMessageType = "context_update"
GossipMessagePeerDiscovery GossipMessageType = "peer_discovery"
GossipMessageConflictAlert GossipMessageType = "conflict_alert"
GossipMessageHealthCheck GossipMessageType = "health_check"
)
// GossipMessageMetadata contains metadata about gossip messages
type GossipMessageMetadata struct {
Priority Priority `json:"priority"`
Reliability bool `json:"reliability"`
Encrypted bool `json:"encrypted"`
Compressed bool `json:"compressed"`
OriginalSize int `json:"original_size"`
CompressionType string `json:"compression_type"`
}
// ContextMetadata represents metadata about a distributed context
type ContextMetadata struct {
Address ucxl.Address `json:"address"`
Version int64 `json:"version"`
LastUpdated time.Time `json:"last_updated"`
UpdatedBy string `json:"updated_by"`
Roles []string `json:"roles"`
Size int64 `json:"size"`
Checksum string `json:"checksum"`
ReplicationNodes []string `json:"replication_nodes"`
VectorClock map[string]int64 `json:"vector_clock"`
Status MetadataStatus `json:"status"`
}
// MetadataStatus represents the status of context metadata
type MetadataStatus string
const (
MetadataStatusActive MetadataStatus = "active"
MetadataStatusDeprecated MetadataStatus = "deprecated"
MetadataStatusDeleted MetadataStatus = "deleted"
MetadataStatusConflicted MetadataStatus = "conflicted"
)
// FailureDetector detects failed nodes in the network
type FailureDetector struct {
mu sync.RWMutex
suspectedNodes map[string]time.Time
failedNodes map[string]time.Time
heartbeatTimeout time.Duration
failureThreshold time.Duration
}
// NewGossipProtocolImpl creates a new gossip protocol implementation
func NewGossipProtocolImpl(dht *dht.DHT, config *config.Config) (*GossipProtocolImpl, error) {
if dht == nil {
return nil, fmt.Errorf("DHT instance is required")
}
if config == nil {
return nil, fmt.Errorf("config is required")
}
gp := &GossipProtocolImpl{
dht: dht,
config: config,
running: false,
gossipInterval: 30 * time.Second,
maxGossipPeers: 5,
compressionEnabled: true,
messageBuffer: make(chan *GossipMessage, 1000),
state: &GossipState{
Running: false,
CurrentRound: 0,
RoundStartTime: time.Now(),
RoundDuration: 0,
ActiveConnections: 0,
PendingMessages: 0,
NextRoundTime: time.Now().Add(30 * time.Second),
ProtocolVersion: "v1.0",
State: "stopped",
},
stats: &GossipStatistics{
LastUpdated: time.Now(),
},
metadataCache: make(map[string]*ContextMetadata),
vectorClock: make(map[string]int64),
failureDetector: &FailureDetector{
suspectedNodes: make(map[string]time.Time),
failedNodes: make(map[string]time.Time),
heartbeatTimeout: 60 * time.Second,
failureThreshold: 120 * time.Second,
},
}
return gp, nil
}
// StartGossip begins gossip protocol for metadata synchronization
func (gp *GossipProtocolImpl) StartGossip(ctx context.Context) error {
gp.mu.Lock()
if gp.running {
gp.mu.Unlock()
return fmt.Errorf("gossip protocol already running")
}
gp.running = true
gp.state.Running = true
gp.state.State = "running"
gp.mu.Unlock()
// Start background workers
go gp.gossipWorker(ctx)
go gp.messageProcessor(ctx)
go gp.heartbeatSender(ctx)
go gp.failureDetectorWorker(ctx)
return nil
}
// StopGossip stops gossip protocol
func (gp *GossipProtocolImpl) StopGossip(ctx context.Context) error {
gp.mu.Lock()
defer gp.mu.Unlock()
if !gp.running {
return fmt.Errorf("gossip protocol not running")
}
gp.running = false
gp.state.Running = false
gp.state.State = "stopped"
close(gp.messageBuffer)
return nil
}
// GossipMetadata exchanges metadata with peer nodes
func (gp *GossipProtocolImpl) GossipMetadata(ctx context.Context, peer string) error {
if !gp.running {
return fmt.Errorf("gossip protocol not running")
}
// Create metadata sync message
message := &GossipMessage{
MessageID: gp.generateMessageID(),
MessageType: GossipMessageMetadataSync,
SenderID: gp.config.Agent.ID,
Timestamp: time.Now(),
TTL: 3, // Max 3 hops
VectorClock: gp.getVectorClock(),
Payload: map[string]interface{}{
"metadata_cache": gp.getMetadataCacheSnapshot(),
"request_sync": true,
},
Metadata: &GossipMessageMetadata{
Priority: PriorityNormal,
Reliability: true,
Encrypted: false,
Compressed: gp.compressionEnabled,
},
}
// Send to specific peer
return gp.sendMessage(ctx, message, peer)
}
// GetGossipState returns current gossip protocol state
func (gp *GossipProtocolImpl) GetGossipState() (*GossipState, error) {
gp.mu.RLock()
defer gp.mu.RUnlock()
// Update dynamic state
gp.state.ActiveConnections = len(gp.dht.GetConnectedPeers())
gp.state.PendingMessages = len(gp.messageBuffer)
return gp.state, nil
}
// SetGossipInterval configures gossip frequency
func (gp *GossipProtocolImpl) SetGossipInterval(interval time.Duration) error {
if interval < time.Second {
return fmt.Errorf("gossip interval too short (minimum 1 second)")
}
if interval > time.Hour {
return fmt.Errorf("gossip interval too long (maximum 1 hour)")
}
gp.mu.Lock()
gp.gossipInterval = interval
gp.state.NextRoundTime = time.Now().Add(interval)
gp.mu.Unlock()
return nil
}
// GetGossipStats returns gossip protocol statistics
func (gp *GossipProtocolImpl) GetGossipStats() (*GossipStatistics, error) {
gp.mu.RLock()
defer gp.mu.RUnlock()
// Update real-time stats
gp.stats.ActivePeers = len(gp.dht.GetConnectedPeers())
gp.stats.LastGossipTime = time.Now()
gp.stats.LastUpdated = time.Now()
return gp.stats, nil
}
// Background workers
func (gp *GossipProtocolImpl) gossipWorker(ctx context.Context) {
ticker := time.NewTicker(gp.gossipInterval)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return
case <-ticker.C:
if gp.running {
gp.performGossipRound(ctx)
}
}
}
}
func (gp *GossipProtocolImpl) messageProcessor(ctx context.Context) {
for {
select {
case <-ctx.Done():
return
case message := <-gp.messageBuffer:
if message == nil {
return // Channel closed
}
gp.processIncomingMessage(ctx, message)
}
}
}
func (gp *GossipProtocolImpl) heartbeatSender(ctx context.Context) {
ticker := time.NewTicker(30 * time.Second)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return
case <-ticker.C:
if gp.running {
gp.sendHeartbeat(ctx)
}
}
}
}
func (gp *GossipProtocolImpl) failureDetectorWorker(ctx context.Context) {
ticker := time.NewTicker(60 * time.Second)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return
case <-ticker.C:
if gp.running {
gp.detectFailures()
}
}
}
}
// Core gossip operations
func (gp *GossipProtocolImpl) performGossipRound(ctx context.Context) {
start := time.Now()
gp.mu.Lock()
gp.state.CurrentRound++
gp.state.RoundStartTime = start
gp.stats.GossipRounds++
gp.mu.Unlock()
// Select random peers for gossip
peers := gp.selectGossipPeers()
// Perform gossip with selected peers
for _, peer := range peers {
go func(peerID string) {
if err := gp.GossipMetadata(ctx, peerID); err != nil {
gp.mu.Lock()
gp.stats.NetworkErrors++
gp.mu.Unlock()
}
}(peer)
}
// Update round duration
gp.mu.Lock()
gp.state.RoundDuration = time.Since(start)
gp.state.NextRoundTime = time.Now().Add(gp.gossipInterval)
gp.stats.AverageRoundTime = (gp.stats.AverageRoundTime + gp.state.RoundDuration) / 2
gp.mu.Unlock()
}
func (gp *GossipProtocolImpl) selectGossipPeers() []string {
connectedPeers := gp.dht.GetConnectedPeers()
if len(connectedPeers) == 0 {
return []string{}
}
// Randomly select up to maxGossipPeers
selectedCount := min(len(connectedPeers), gp.maxGossipPeers)
selected := make([]string, 0, selectedCount)
// Simple random selection
perm := rand.Perm(len(connectedPeers))
for i := 0; i < selectedCount; i++ {
selected = append(selected, connectedPeers[perm[i]].String())
}
return selected
}
func (gp *GossipProtocolImpl) processIncomingMessage(ctx context.Context, message *GossipMessage) {
// Update vector clock
gp.updateVectorClock(message.VectorClock)
// Process based on message type
switch message.MessageType {
case GossipMessageHeartbeat:
gp.processHeartbeat(message)
case GossipMessageMetadataSync:
gp.processMetadataSync(ctx, message)
case GossipMessageContextUpdate:
gp.processContextUpdate(message)
case GossipMessagePeerDiscovery:
gp.processPeerDiscovery(message)
case GossipMessageConflictAlert:
gp.processConflictAlert(message)
case GossipMessageHealthCheck:
gp.processHealthCheck(message)
default:
gp.mu.Lock()
gp.stats.ProtocolErrors++
gp.mu.Unlock()
}
// Update statistics
gp.mu.Lock()
gp.stats.MessagesReceived++
gp.mu.Unlock()
}
func (gp *GossipProtocolImpl) sendMessage(ctx context.Context, message *GossipMessage, peer string) error {
// Serialize message
messageBytes, err := json.Marshal(message)
if err != nil {
return fmt.Errorf("failed to serialize message: %w", err)
}
// Compress if enabled
if gp.compressionEnabled && message.Metadata != nil {
compressedBytes, err := gp.compressMessage(messageBytes)
if err == nil {
message.Metadata.Compressed = true
message.Metadata.OriginalSize = len(messageBytes)
message.Metadata.CompressionType = "gzip"
messageBytes = compressedBytes
}
}
// Send via DHT (in a real implementation, this would use direct peer connections)
key := fmt.Sprintf("gossip:%s:%s", peer, message.MessageID)
if err := gp.dht.PutValue(ctx, key, messageBytes); err != nil {
gp.mu.Lock()
gp.stats.MessagesDropped++
gp.mu.Unlock()
return fmt.Errorf("failed to send gossip message: %w", err)
}
gp.mu.Lock()
gp.stats.MessagesSent++
gp.mu.Unlock()
return nil
}
func (gp *GossipProtocolImpl) sendHeartbeat(ctx context.Context) {
message := &GossipMessage{
MessageID: gp.generateMessageID(),
MessageType: GossipMessageHeartbeat,
SenderID: gp.config.Agent.ID,
Timestamp: time.Now(),
TTL: 1, // Heartbeats don't propagate
VectorClock: gp.getVectorClock(),
Payload: map[string]interface{}{
"status": "alive",
"load": gp.calculateNodeLoad(),
"version": "1.0.0",
"capabilities": []string{"context_distribution", "replication"},
},
Metadata: &GossipMessageMetadata{
Priority: PriorityHigh,
Reliability: false, // Heartbeats can be lost
Encrypted: false,
Compressed: false,
},
}
// Send to all connected peers
peers := gp.selectGossipPeers()
for _, peer := range peers {
go func(peerID string) {
gp.sendMessage(ctx, message, peerID)
}(peer)
}
}
func (gp *GossipProtocolImpl) detectFailures() {
now := time.Now()
gp.failureDetector.mu.Lock()
defer gp.failureDetector.mu.Unlock()
// Check for suspected nodes that haven't responded
for nodeID, suspectedTime := range gp.failureDetector.suspectedNodes {
if now.Sub(suspectedTime) > gp.failureDetector.failureThreshold {
// Mark as failed
gp.failureDetector.failedNodes[nodeID] = now
delete(gp.failureDetector.suspectedNodes, nodeID)
}
}
// Clean up old failure records
for nodeID, failedTime := range gp.failureDetector.failedNodes {
if now.Sub(failedTime) > 24*time.Hour {
delete(gp.failureDetector.failedNodes, nodeID)
}
}
}
// Message processing handlers
func (gp *GossipProtocolImpl) processHeartbeat(message *GossipMessage) {
// Remove from suspected/failed lists if present
gp.failureDetector.mu.Lock()
delete(gp.failureDetector.suspectedNodes, message.SenderID)
delete(gp.failureDetector.failedNodes, message.SenderID)
gp.failureDetector.mu.Unlock()
// Update peer information
if load, ok := message.Payload["load"].(float64); ok {
// Store peer load information
_ = load
}
}
func (gp *GossipProtocolImpl) processMetadataSync(ctx context.Context, message *GossipMessage) {
// Extract metadata cache from payload
if metadataCache, ok := message.Payload["metadata_cache"].(map[string]interface{}); ok {
gp.mergeMetadataCache(metadataCache)
}
// If this is a sync request, respond with our metadata
if requestSync, ok := message.Payload["request_sync"].(bool); ok && requestSync {
responseMessage := &GossipMessage{
MessageID: gp.generateMessageID(),
MessageType: GossipMessageMetadataSync,
SenderID: gp.config.Agent.ID,
Timestamp: time.Now(),
TTL: 1,
VectorClock: gp.getVectorClock(),
Payload: map[string]interface{}{
"metadata_cache": gp.getMetadataCacheSnapshot(),
"request_sync": false,
},
Metadata: &GossipMessageMetadata{
Priority: PriorityNormal,
Reliability: true,
Encrypted: false,
Compressed: gp.compressionEnabled,
},
}
go func() {
gp.sendMessage(ctx, responseMessage, message.SenderID)
}()
}
}
func (gp *GossipProtocolImpl) processContextUpdate(message *GossipMessage) {
// Handle context update notifications
if address, ok := message.Payload["address"].(string); ok {
if version, ok := message.Payload["version"].(float64); ok {
gp.updateContextMetadata(address, int64(version), message.SenderID)
}
}
}
func (gp *GossipProtocolImpl) processPeerDiscovery(message *GossipMessage) {
// Handle peer discovery messages
if peers, ok := message.Payload["peers"].([]interface{}); ok {
for _, peerData := range peers {
if peer, ok := peerData.(string); ok {
// Add discovered peer to our peer list
_ = peer
}
}
}
}
func (gp *GossipProtocolImpl) processConflictAlert(message *GossipMessage) {
// Handle conflict alert messages
if address, ok := message.Payload["address"].(string); ok {
// Mark context as conflicted in our metadata cache
gp.mu.Lock()
if metadata, exists := gp.metadataCache[address]; exists {
metadata.Status = MetadataStatusConflicted
}
gp.mu.Unlock()
}
}
func (gp *GossipProtocolImpl) processHealthCheck(message *GossipMessage) {
// Respond to health check with our status
// Implementation would send back health information
}
// Helper methods
func (gp *GossipProtocolImpl) generateMessageID() string {
return fmt.Sprintf("%s-%d", gp.config.Agent.ID, time.Now().UnixNano())
}
func (gp *GossipProtocolImpl) getVectorClock() map[string]int64 {
gp.mu.RLock()
defer gp.mu.RUnlock()
clock := make(map[string]int64)
for nodeID, timestamp := range gp.vectorClock {
clock[nodeID] = timestamp
}
clock[gp.config.Agent.ID] = time.Now().Unix()
return clock
}
func (gp *GossipProtocolImpl) updateVectorClock(remoteClock map[string]int64) {
gp.mu.Lock()
defer gp.mu.Unlock()
for nodeID, timestamp := range remoteClock {
if existingTimestamp, exists := gp.vectorClock[nodeID]; !exists || timestamp > existingTimestamp {
gp.vectorClock[nodeID] = timestamp
}
}
}
func (gp *GossipProtocolImpl) getMetadataCacheSnapshot() map[string]*ContextMetadata {
gp.mu.RLock()
defer gp.mu.RUnlock()
snapshot := make(map[string]*ContextMetadata)
for address, metadata := range gp.metadataCache {
// Deep copy metadata
snapshot[address] = &ContextMetadata{
Address: metadata.Address,
Version: metadata.Version,
LastUpdated: metadata.LastUpdated,
UpdatedBy: metadata.UpdatedBy,
Roles: append([]string{}, metadata.Roles...),
Size: metadata.Size,
Checksum: metadata.Checksum,
ReplicationNodes: append([]string{}, metadata.ReplicationNodes...),
VectorClock: make(map[string]int64),
Status: metadata.Status,
}
for k, v := range metadata.VectorClock {
snapshot[address].VectorClock[k] = v
}
}
return snapshot
}
func (gp *GossipProtocolImpl) mergeMetadataCache(remoteCache map[string]interface{}) {
gp.mu.Lock()
defer gp.mu.Unlock()
// Simplified merge logic - in production would be more sophisticated
for address, metadataInterface := range remoteCache {
if metadataMap, ok := metadataInterface.(map[string]interface{}); ok {
// Convert map to ContextMetadata struct
// This is simplified - production code would use proper deserialization
if version, ok := metadataMap["version"].(float64); ok {
if existing, exists := gp.metadataCache[address]; !exists || int64(version) > existing.Version {
// Update with newer version
// Implementation would properly deserialize the metadata
}
}
}
}
}
func (gp *GossipProtocolImpl) updateContextMetadata(address string, version int64, updatedBy string) {
gp.mu.Lock()
defer gp.mu.Unlock()
if existing, exists := gp.metadataCache[address]; exists && version > existing.Version {
existing.Version = version
existing.LastUpdated = time.Now()
existing.UpdatedBy = updatedBy
}
}
func (gp *GossipProtocolImpl) calculateNodeLoad() float64 {
// Calculate current node load (simplified)
gp.mu.RLock()
metadataCount := len(gp.metadataCache)
gp.mu.RUnlock()
return float64(metadataCount) / 100.0 // Normalize to [0,1] range
}
func (gp *GossipProtocolImpl) compressMessage(data []byte) ([]byte, error) {
// Simplified compression - would use actual compression in production
return data, nil
}
// min returns the minimum of two integers
func min(a, b int) int {
if a < b {
return a
}
return b
}