go-p2p/node/message.go
Claude 8f94639ec9
feat: extract P2P networking and UEPS protocol from Mining repo
P2P node layer (peer discovery, WebSocket transport, message protocol,
worker pool, identity management) and Unified Ethical Protocol Stack
(TLV packet builder with HMAC-signed frames).

Ported from github.com/Snider/Mining/pkg/{node,ueps,logging}

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 15:47:10 +00:00

237 lines
6.8 KiB
Go

package node
import (
"encoding/json"
"time"
"github.com/google/uuid"
)
// Protocol version constants
const (
// ProtocolVersion is the current protocol version
ProtocolVersion = "1.0"
// MinProtocolVersion is the minimum supported version
MinProtocolVersion = "1.0"
)
// SupportedProtocolVersions lists all protocol versions this node supports.
// Used for version negotiation during handshake.
var SupportedProtocolVersions = []string{"1.0"}
// IsProtocolVersionSupported checks if a given version is supported.
func IsProtocolVersionSupported(version string) bool {
for _, v := range SupportedProtocolVersions {
if v == version {
return true
}
}
return false
}
// MessageType defines the type of P2P message.
type MessageType string
const (
// Connection lifecycle
MsgHandshake MessageType = "handshake"
MsgHandshakeAck MessageType = "handshake_ack"
MsgPing MessageType = "ping"
MsgPong MessageType = "pong"
MsgDisconnect MessageType = "disconnect"
// Miner operations
MsgGetStats MessageType = "get_stats"
MsgStats MessageType = "stats"
MsgStartMiner MessageType = "start_miner"
MsgStopMiner MessageType = "stop_miner"
MsgMinerAck MessageType = "miner_ack"
// Deployment
MsgDeploy MessageType = "deploy"
MsgDeployAck MessageType = "deploy_ack"
// Logs
MsgGetLogs MessageType = "get_logs"
MsgLogs MessageType = "logs"
// Error response
MsgError MessageType = "error"
)
// Message represents a P2P message between nodes.
type Message struct {
ID string `json:"id"` // UUID
Type MessageType `json:"type"`
From string `json:"from"` // Sender node ID
To string `json:"to"` // Recipient node ID (empty for broadcast)
Timestamp time.Time `json:"ts"`
Payload json.RawMessage `json:"payload"`
ReplyTo string `json:"replyTo,omitempty"` // ID of message being replied to
}
// NewMessage creates a new message with a generated ID and timestamp.
func NewMessage(msgType MessageType, from, to string, payload interface{}) (*Message, error) {
var payloadBytes json.RawMessage
if payload != nil {
data, err := MarshalJSON(payload)
if err != nil {
return nil, err
}
payloadBytes = data
}
return &Message{
ID: uuid.New().String(),
Type: msgType,
From: from,
To: to,
Timestamp: time.Now(),
Payload: payloadBytes,
}, nil
}
// Reply creates a reply message to this message.
func (m *Message) Reply(msgType MessageType, payload interface{}) (*Message, error) {
reply, err := NewMessage(msgType, m.To, m.From, payload)
if err != nil {
return nil, err
}
reply.ReplyTo = m.ID
return reply, nil
}
// ParsePayload unmarshals the payload into the given struct.
func (m *Message) ParsePayload(v interface{}) error {
if m.Payload == nil {
return nil
}
return json.Unmarshal(m.Payload, v)
}
// --- Payload Types ---
// HandshakePayload is sent during connection establishment.
type HandshakePayload struct {
Identity NodeIdentity `json:"identity"`
Challenge []byte `json:"challenge,omitempty"` // Random bytes for auth
Version string `json:"version"` // Protocol version
}
// HandshakeAckPayload is the response to a handshake.
type HandshakeAckPayload struct {
Identity NodeIdentity `json:"identity"`
ChallengeResponse []byte `json:"challengeResponse,omitempty"`
Accepted bool `json:"accepted"`
Reason string `json:"reason,omitempty"` // If not accepted
}
// PingPayload for keepalive/latency measurement.
type PingPayload struct {
SentAt int64 `json:"sentAt"` // Unix timestamp in milliseconds
}
// PongPayload response to ping.
type PongPayload struct {
SentAt int64 `json:"sentAt"` // Echo of ping's sentAt
ReceivedAt int64 `json:"receivedAt"` // When ping was received
}
// StartMinerPayload requests starting a miner.
type StartMinerPayload struct {
MinerType string `json:"minerType"` // Required: miner type (e.g., "xmrig", "tt-miner")
ProfileID string `json:"profileId,omitempty"`
Config json.RawMessage `json:"config,omitempty"` // Override profile config
}
// StopMinerPayload requests stopping a miner.
type StopMinerPayload struct {
MinerName string `json:"minerName"`
}
// MinerAckPayload acknowledges a miner start/stop operation.
type MinerAckPayload struct {
Success bool `json:"success"`
MinerName string `json:"minerName,omitempty"`
Error string `json:"error,omitempty"`
}
// MinerStatsItem represents stats for a single miner.
type MinerStatsItem struct {
Name string `json:"name"`
Type string `json:"type"`
Hashrate float64 `json:"hashrate"`
Shares int `json:"shares"`
Rejected int `json:"rejected"`
Uptime int `json:"uptime"` // Seconds
Pool string `json:"pool"`
Algorithm string `json:"algorithm"`
CPUThreads int `json:"cpuThreads,omitempty"`
}
// StatsPayload contains miner statistics.
type StatsPayload struct {
NodeID string `json:"nodeId"`
NodeName string `json:"nodeName"`
Miners []MinerStatsItem `json:"miners"`
Uptime int64 `json:"uptime"` // Node uptime in seconds
}
// GetLogsPayload requests console logs from a miner.
type GetLogsPayload struct {
MinerName string `json:"minerName"`
Lines int `json:"lines"` // Number of lines to fetch
Since int64 `json:"since,omitempty"` // Unix timestamp, logs after this time
}
// LogsPayload contains console log lines.
type LogsPayload struct {
MinerName string `json:"minerName"`
Lines []string `json:"lines"`
HasMore bool `json:"hasMore"` // More logs available
}
// DeployPayload contains a deployment bundle.
type DeployPayload struct {
BundleType string `json:"type"` // "profile" | "miner" | "full"
Data []byte `json:"data"` // STIM-encrypted bundle
Checksum string `json:"checksum"` // SHA-256 of Data
Name string `json:"name"` // Profile or miner name
}
// DeployAckPayload acknowledges a deployment.
type DeployAckPayload struct {
Success bool `json:"success"`
Name string `json:"name,omitempty"`
Error string `json:"error,omitempty"`
}
// ErrorPayload contains error information.
type ErrorPayload struct {
Code int `json:"code"`
Message string `json:"message"`
Details string `json:"details,omitempty"`
}
// Common error codes
const (
ErrCodeUnknown = 1000
ErrCodeInvalidMessage = 1001
ErrCodeUnauthorized = 1002
ErrCodeNotFound = 1003
ErrCodeOperationFailed = 1004
ErrCodeTimeout = 1005
)
// NewErrorMessage creates an error response message.
func NewErrorMessage(from, to string, code int, message string, replyTo string) (*Message, error) {
msg, err := NewMessage(MsgError, from, to, ErrorPayload{
Code: code,
Message: message,
})
if err != nil {
return nil, err
}
msg.ReplyTo = replyTo
return msg, nil
}