Node Architecture
The node package provides the high-level P2P mesh for the Lethean network. It handles identity management, encrypted WebSocket transport, peer discovery and selection, and a controller/worker protocol for remote node operations.
Identity
NodeManager manages node identity using X25519 key pairs (via the STMF library):
- Key generation:
GenerateIdentity(name, role)creates an X25519 keypair, derives a node ID from SHA-256 of the public key (first 16 bytes as hex = 32-char ID), and persists the private key with0600permissions - Shared secret:
DeriveSharedSecret(peerPubKeyBase64)performs X25519 ECDH and hashes the result with SHA-256 - Storage: Private key at
~/.local/share/lethean-desktop/node/private.key, identity config at~/.config/lethean-desktop/node.json
NodeIdentity is the public identity struct containing ID, Name, PublicKey (X25519 base64), CreatedAt, and Role.
Node Roles
type NodeRole string
const (
RoleController NodeRole = "controller"
RoleWorker NodeRole = "worker"
RoleDual NodeRole = "dual"
)
Transport
Transport manages encrypted WebSocket connections with SMSG encryption:
- Configuration:
TransportConfigwithListenAddr,WSPath, TLS paths,MaxConns,MaxMessageSize(default 1MB),PingInterval,PongTimeout - TLS hardening: Minimum TLS 1.2, X25519/P256 curves, AEAD cipher suites only
- Connection lifecycle:
Start(),Stop(),Connect(peer),Send(peerID, msg),Broadcast(msg) - Message encryption: SMSG with the shared secret derived from X25519 ECDH
- Deduplication:
MessageDeduplicatorwith configurable TTL (default 5 minutes) prevents amplification attacks - Rate limiting:
PeerRateLimiterimplements token-bucket per peer (default 100 burst, 50/sec refill)
Handshake
The handshake uses challenge-response authentication:
- Initiator sends
MsgHandshakewith itsNodeIdentity, a 32-byte random challenge, and the protocol version - Responder derives shared secret, verifies protocol version compatibility, checks the peer allowlist, signs the challenge with HMAC-SHA256, and sends
MsgHandshakeAck - Initiator verifies the challenge response and stores the shared secret
- All subsequent messages are SMSG-encrypted
Peer Management
PeerRegistry manages known peers with KD-tree-based selection (via the Poindexter library):
- Peer struct:
ID,Name,PublicKey,Address,Role,PingMS,Hops,GeoKM,Score(0--100) - Authentication modes:
PeerAuthOpen(allow all) orPeerAuthAllowlist(pre-registered peers or allowlisted public keys only) - Optimal selection:
SelectOptimalPeer()andSelectNearestPeers(n)use a 4D KD-tree with weighted dimensions (ping, hops, geo distance, inverted score) - Score tracking:
RecordSuccess()(+1),RecordFailure()(-5),RecordTimeout()(-3), clamped to 0--100 - Persistence: Debounced JSON writes (5-second coalesce interval) with atomic rename pattern
Message Protocol
Message is the envelope for all P2P communication:
type Message struct {
ID string // UUID
Type MessageType // e.g. "handshake", "ping", "get_stats"
From string // Sender node ID
To string // Recipient node ID
Payload json.RawMessage // Type-specific payload
ReplyTo string // For request-response correlation
}
Message Types
| Category | Types |
|---|---|
| Connection | handshake, handshake_ack, ping, pong, disconnect |
| Operations | get_stats, stats, start_miner, stop_miner, miner_ack |
| Deployment | deploy, deploy_ack |
| Logs | get_logs, logs |
| Error | error (with code: 1000--1005) |
Disconnect codes: DisconnectNormal (1000), DisconnectGoingAway (1001), DisconnectProtocolErr (1002), DisconnectTimeout (1003), DisconnectShutdown (1004).
Controller
Controller manages remote peer operations with request-response correlation:
GetRemoteStats(peerID)-- fetch miner statisticsStartRemoteMiner(peerID, minerType, profileID, config)-- start a remote minerStopRemoteMiner(peerID, minerName)-- stop a remote minerGetRemoteLogs(peerID, minerName, lines)-- fetch console logs (max 10000 lines)GetAllStats()-- parallel stats fetch from all connected peersPingPeer(peerID)-- measure RTT and update peer metrics- Auto-connects to peers on first request
Worker
Worker handles incoming messages on worker nodes. It implements MinerManager and ProfileManager interfaces for pluggable integration:
type MinerManager interface {
StartMiner(minerType string, config interface{}) (MinerInstance, error)
StopMiner(name string) error
ListMiners() []MinerInstance
GetMiner(name string) (MinerInstance, error)
}
The worker dispatches messages by type: ping, get_stats, start_miner, stop_miner, get_logs, deploy.
Bundle System
Bundle handles encrypted deployment packages for P2P transfer:
- Types:
BundleProfile(JSON config),BundleMiner(binary + config),BundleFull(everything) - Encryption: TIM/STIM format via the Borg library
- Integrity: SHA-256 checksum verification
- Extraction: Tar archives with path traversal protection (Zip Slip prevention), symlink rejection, and 100MB per-file size limit
Buffer Pool
bufpool.go provides sync.Pool-backed byte buffers for JSON encoding in hot paths. Buffers larger than 64KB are not returned to the pool. The MarshalJSON function uses pooled buffers and disables HTML escaping.
Logging
The logging package provides structured logging with four levels (DEBUG, INFO, WARN, ERROR), key-value fields, component scoping, and both instance and global logger APIs.
See UEPS-Protocol for the low-level TLV wire protocol that operates beneath this layer.