Files
CHORUS/internal/logging/hypercore.go
anthonyrawlins 95784822ce fix(logging): resolve duplicate type case compilation error in hypercore.go
@goal: CHORUS-REQ-001 - Fix critical compilation error blocking development

- Remove duplicate type cases for interface{}/any and []interface{}/[]any
- Go 1.18+ treats interface{} and any as identical types
- Standardize on 'any' type for consistency with modern Go practices
- Add proper type conversion for cloneLogMap compatibility
- Include requirement traceability comments

Fixes: CHORUS issue #1
Test: go build ./internal/logging/... passes without errors

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-21 17:16:38 +10:00

437 lines
11 KiB
Go

package logging
import (
"context"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"sync"
"time"
"chorus/pkg/shhh"
"github.com/libp2p/go-libp2p/core/peer"
)
// Logger interface for basic logging operations
type Logger interface {
Info(msg string, args ...interface{})
Warn(msg string, args ...interface{})
Error(msg string, args ...interface{})
}
// HypercoreLog represents a simplified Hypercore-inspired distributed log
type HypercoreLog struct {
entries []LogEntry
mutex sync.RWMutex
peerID peer.ID
// Verification chain
headHash string
// Replication
replicators map[peer.ID]*Replicator
redactor *shhh.Sentinel
}
// LogEntry represents a single entry in the distributed log
type LogEntry struct {
Index uint64 `json:"index"`
Timestamp time.Time `json:"timestamp"`
Author string `json:"author"` // Peer ID of the author
Type LogType `json:"type"` // Type of log entry
Data map[string]interface{} `json:"data"` // Log data
Hash string `json:"hash"` // Hash of this entry
PrevHash string `json:"prev_hash"` // Hash of previous entry
Signature string `json:"signature"` // Digital signature (simplified)
}
// LogType represents different types of log entries
type LogType string
const (
// Bzzz coordination logs
TaskAnnounced LogType = "task_announced"
TaskClaimed LogType = "task_claimed"
TaskProgress LogType = "task_progress"
TaskCompleted LogType = "task_completed"
TaskFailed LogType = "task_failed"
// HMMM meta-discussion logs
PlanProposed LogType = "plan_proposed"
ObjectionRaised LogType = "objection_raised"
Collaboration LogType = "collaboration"
ConsensusReached LogType = "consensus_reached"
Escalation LogType = "escalation"
TaskHelpRequested LogType = "task_help_requested"
TaskHelpOffered LogType = "task_help_offered"
TaskHelpReceived LogType = "task_help_received"
// System logs
PeerJoined LogType = "peer_joined"
PeerLeft LogType = "peer_left"
CapabilityBcast LogType = "capability_broadcast"
NetworkEvent LogType = "network_event"
)
// Replicator handles log replication with other peers
type Replicator struct {
peerID peer.ID
lastSyncIndex uint64
connected bool
}
// NewHypercoreLog creates a new distributed log for a peer
func NewHypercoreLog(peerID peer.ID) *HypercoreLog {
return &HypercoreLog{
entries: make([]LogEntry, 0),
peerID: peerID,
headHash: "",
replicators: make(map[peer.ID]*Replicator),
}
}
// SetRedactor wires the SHHH sentinel so log payloads are sanitized before persistence.
func (h *HypercoreLog) SetRedactor(redactor *shhh.Sentinel) {
h.mutex.Lock()
defer h.mutex.Unlock()
h.redactor = redactor
}
// AppendString is a convenience method for string log types (to match interface)
func (h *HypercoreLog) AppendString(logType string, data map[string]interface{}) error {
_, err := h.Append(LogType(logType), data)
return err
}
// Append adds a new entry to the log
func (h *HypercoreLog) Append(logType LogType, data map[string]interface{}) (*LogEntry, error) {
h.mutex.Lock()
defer h.mutex.Unlock()
index := uint64(len(h.entries))
sanitized := h.redactData(logType, data)
entry := LogEntry{
Index: index,
Timestamp: time.Now(),
Author: h.peerID.String(),
Type: logType,
Data: sanitized,
PrevHash: h.headHash,
}
// Calculate hash
entryHash, err := h.calculateEntryHash(entry)
if err != nil {
return nil, fmt.Errorf("failed to calculate entry hash: %w", err)
}
entry.Hash = entryHash
// Add simple signature (in production, use proper cryptographic signatures)
entry.Signature = h.createSignature(entry)
// Append to log
h.entries = append(h.entries, entry)
h.headHash = entryHash
fmt.Printf("📝 Log entry appended: %s [%d] by %s\n",
logType, index, h.peerID.ShortString())
// Trigger replication to connected peers
go h.replicateEntry(entry)
return &entry, nil
}
// Get retrieves a log entry by index
func (h *HypercoreLog) Get(index uint64) (*LogEntry, error) {
h.mutex.RLock()
defer h.mutex.RUnlock()
if index >= uint64(len(h.entries)) {
return nil, fmt.Errorf("entry %d not found", index)
}
return &h.entries[index], nil
}
// Length returns the number of entries in the log
func (h *HypercoreLog) Length() uint64 {
h.mutex.RLock()
defer h.mutex.RUnlock()
return uint64(len(h.entries))
}
// GetRange retrieves a range of log entries
func (h *HypercoreLog) GetRange(start, end uint64) ([]LogEntry, error) {
h.mutex.RLock()
defer h.mutex.RUnlock()
if start >= uint64(len(h.entries)) {
return nil, fmt.Errorf("start index %d out of range", start)
}
if end > uint64(len(h.entries)) {
end = uint64(len(h.entries))
}
if start > end {
return nil, fmt.Errorf("invalid range: start %d > end %d", start, end)
}
result := make([]LogEntry, end-start)
copy(result, h.entries[start:end])
return result, nil
}
// GetEntriesByType retrieves all entries of a specific type
func (h *HypercoreLog) GetEntriesByType(logType LogType) ([]LogEntry, error) {
h.mutex.RLock()
defer h.mutex.RUnlock()
var result []LogEntry
for _, entry := range h.entries {
if entry.Type == logType {
result = append(result, entry)
}
}
return result, nil
}
// GetEntriesByAuthor retrieves all entries by a specific author
func (h *HypercoreLog) GetEntriesByAuthor(author string) ([]LogEntry, error) {
h.mutex.RLock()
defer h.mutex.RUnlock()
var result []LogEntry
for _, entry := range h.entries {
if entry.Author == author {
result = append(result, entry)
}
}
return result, nil
}
// GetRecentEntries retrieves the most recent N entries from the log
func (h *HypercoreLog) GetRecentEntries(count int) ([]LogEntry, error) {
h.mutex.RLock()
defer h.mutex.RUnlock()
totalEntries := len(h.entries)
if count <= 0 || totalEntries == 0 {
return []LogEntry{}, nil
}
start := 0
if totalEntries > count {
start = totalEntries - count
}
result := make([]LogEntry, totalEntries-start)
copy(result, h.entries[start:])
return result, nil
}
// GetEntriesSince retrieves all entries since a given index
func (h *HypercoreLog) GetEntriesSince(sinceIndex uint64) ([]LogEntry, error) {
h.mutex.RLock()
defer h.mutex.RUnlock()
if sinceIndex >= uint64(len(h.entries)) {
return []LogEntry{}, nil
}
result := make([]LogEntry, len(h.entries)-int(sinceIndex))
copy(result, h.entries[sinceIndex:])
return result, nil
}
// VerifyIntegrity verifies the integrity of the log chain
func (h *HypercoreLog) VerifyIntegrity() error {
h.mutex.RLock()
defer h.mutex.RUnlock()
var prevHash string
for i, entry := range h.entries {
// Verify previous hash link
if entry.PrevHash != prevHash {
return fmt.Errorf("integrity error at entry %d: prev_hash mismatch", i)
}
// Verify entry hash
calculatedHash, err := h.calculateEntryHash(entry)
if err != nil {
return fmt.Errorf("failed to calculate hash for entry %d: %w", i, err)
}
if entry.Hash != calculatedHash {
return fmt.Errorf("integrity error at entry %d: hash mismatch", i)
}
prevHash = entry.Hash
}
return nil
}
// AddReplicator adds a peer for log replication
func (h *HypercoreLog) AddReplicator(peerID peer.ID) {
h.mutex.Lock()
defer h.mutex.Unlock()
h.replicators[peerID] = &Replicator{
peerID: peerID,
lastSyncIndex: 0,
connected: true,
}
fmt.Printf("🔄 Added replicator: %s\n", peerID.ShortString())
}
// RemoveReplicator removes a peer from replication
func (h *HypercoreLog) RemoveReplicator(peerID peer.ID) {
h.mutex.Lock()
defer h.mutex.Unlock()
delete(h.replicators, peerID)
fmt.Printf("🔄 Removed replicator: %s\n", peerID.ShortString())
}
// replicateEntry sends a new entry to all connected replicators
func (h *HypercoreLog) replicateEntry(entry LogEntry) {
h.mutex.RLock()
replicators := make([]*Replicator, 0, len(h.replicators))
for _, replicator := range h.replicators {
if replicator.connected {
replicators = append(replicators, replicator)
}
}
h.mutex.RUnlock()
for _, replicator := range replicators {
// In a real implementation, this would send the entry over the network
fmt.Printf("🔄 Replicating entry %d to %s\n",
entry.Index, replicator.peerID.ShortString())
}
}
// calculateEntryHash calculates the hash of a log entry
func (h *HypercoreLog) calculateEntryHash(entry LogEntry) (string, error) {
// Create a copy without the hash and signature for calculation
entryForHash := LogEntry{
Index: entry.Index,
Timestamp: entry.Timestamp,
Author: entry.Author,
Type: entry.Type,
Data: entry.Data,
PrevHash: entry.PrevHash,
}
entryBytes, err := json.Marshal(entryForHash)
if err != nil {
return "", err
}
hash := sha256.Sum256(entryBytes)
return hex.EncodeToString(hash[:]), nil
}
func (h *HypercoreLog) redactData(logType LogType, data map[string]interface{}) map[string]interface{} {
cloned := cloneLogMap(data)
if cloned == nil {
return nil
}
if h.redactor != nil {
labels := map[string]string{
"source": "hypercore",
"log_type": string(logType),
}
h.redactor.RedactMapWithLabels(context.Background(), cloned, labels)
}
return cloned
}
func cloneLogMap(in map[string]interface{}) map[string]interface{} {
if in == nil {
return nil
}
out := make(map[string]interface{}, len(in))
for k, v := range in {
out[k] = cloneLogValue(v)
}
return out
}
// @goal: CHORUS-REQ-001 - Fix duplicate type case compilation error
// WHY: Go 1.18+ treats interface{} and any as identical types, causing duplicate case errors
func cloneLogValue(v interface{}) interface{} {
switch tv := v.(type) {
case map[string]any:
// @goal: CHORUS-REQ-001 - Convert any to interface{} for cloneLogMap compatibility
converted := make(map[string]interface{}, len(tv))
for k, val := range tv {
converted[k] = val
}
return cloneLogMap(converted)
case []any:
converted := make([]interface{}, len(tv))
for i, val := range tv {
converted[i] = cloneLogValue(val)
}
return converted
case []string:
return append([]string(nil), tv...)
default:
return tv
}
}
func cloneLogSlice(in []interface{}) []interface{} {
out := make([]interface{}, len(in))
for i, val := range in {
out[i] = cloneLogValue(val)
}
return out
}
// createSignature creates a simplified signature for the entry
func (h *HypercoreLog) createSignature(entry LogEntry) string {
// In production, this would use proper cryptographic signatures
// For now, we use a simple hash-based signature
signatureData := fmt.Sprintf("%s:%s:%d", h.peerID.String(), entry.Hash, entry.Index)
hash := sha256.Sum256([]byte(signatureData))
return hex.EncodeToString(hash[:])[:16] // Shortened for display
}
// GetStats returns statistics about the log
func (h *HypercoreLog) GetStats() map[string]interface{} {
h.mutex.RLock()
defer h.mutex.RUnlock()
typeCount := make(map[LogType]int)
authorCount := make(map[string]int)
for _, entry := range h.entries {
typeCount[entry.Type]++
authorCount[entry.Author]++
}
return map[string]interface{}{
"total_entries": len(h.entries),
"head_hash": h.headHash,
"replicators": len(h.replicators),
"entries_by_type": typeCount,
"entries_by_author": authorCount,
"peer_id": h.peerID.String(),
}
}