refactor: Remove unused code and fix nil dereference issues
- Remove unused exported functions from pkg/database (session tracking, bulk hashrate inserts, various query helpers) - Remove unused exported functions from pkg/node (identity management, bundle operations, controller methods) - Make internal-only functions unexported in config_manager.go and database.go - Remove unused EventProfile* constants from events.go - Add GetCommit() and GetBuildDate() to expose version.go variables - Fix potential nil dereference issues flagged by Qodana: - Add nil checks for GetIdentity() in controller.go, transport.go, worker.go - Add nil checks for GetPeer() in peer_test.go - Add nil checks in worker_test.go 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
b0297471c6
commit
d61a8aff8b
14 changed files with 148 additions and 636 deletions
|
|
@ -28,8 +28,8 @@ type Config struct {
|
||||||
RetentionDays int `json:"retentionDays,omitempty"`
|
RetentionDays int `json:"retentionDays,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// DefaultConfig returns the default database configuration
|
// defaultConfig returns the default database configuration
|
||||||
func DefaultConfig() Config {
|
func defaultConfig() Config {
|
||||||
return Config{
|
return Config{
|
||||||
Enabled: true,
|
Enabled: true,
|
||||||
Path: "",
|
Path: "",
|
||||||
|
|
@ -99,8 +99,8 @@ func Close() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsInitialized returns true if the database is ready
|
// isInitialized returns true if the database is ready
|
||||||
func IsInitialized() bool {
|
func isInitialized() bool {
|
||||||
dbMu.RLock()
|
dbMu.RLock()
|
||||||
defer dbMu.RUnlock()
|
defer dbMu.RUnlock()
|
||||||
return db != nil
|
return db != nil
|
||||||
|
|
@ -168,8 +168,8 @@ func Cleanup(retentionDays int) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// VacuumDB optimizes the database file size
|
// vacuumDB optimizes the database file size
|
||||||
func VacuumDB() error {
|
func vacuumDB() error {
|
||||||
dbMu.RLock()
|
dbMu.RLock()
|
||||||
defer dbMu.RUnlock()
|
defer dbMu.RUnlock()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,11 @@
|
||||||
package database
|
package database
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// dbOperationTimeout is the maximum time for database operations
|
|
||||||
const dbOperationTimeout = 30 * time.Second
|
|
||||||
|
|
||||||
// parseSQLiteTimestamp parses timestamp strings from SQLite which may use various formats.
|
// parseSQLiteTimestamp parses timestamp strings from SQLite which may use various formats.
|
||||||
// Logs a warning if parsing fails and returns zero time.
|
// Logs a warning if parsing fails and returns zero time.
|
||||||
func parseSQLiteTimestamp(s string) time.Time {
|
func parseSQLiteTimestamp(s string) time.Time {
|
||||||
|
|
@ -68,48 +63,6 @@ func InsertHashratePoint(minerName, minerType string, point HashratePoint, resol
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// InsertHashratePoints stores multiple hashrate measurements in a single transaction
|
|
||||||
func InsertHashratePoints(minerName, minerType string, points []HashratePoint, resolution Resolution) error {
|
|
||||||
dbMu.RLock()
|
|
||||||
defer dbMu.RUnlock()
|
|
||||||
|
|
||||||
if db == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(points) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use context with timeout to prevent hanging on locked database
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), dbOperationTimeout)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
tx, err := db.BeginTx(ctx, nil)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to begin transaction: %w", err)
|
|
||||||
}
|
|
||||||
defer tx.Rollback()
|
|
||||||
|
|
||||||
stmt, err := tx.PrepareContext(ctx, `
|
|
||||||
INSERT INTO hashrate_history (miner_name, miner_type, timestamp, hashrate, resolution)
|
|
||||||
VALUES (?, ?, ?, ?, ?)
|
|
||||||
`)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to prepare statement: %w", err)
|
|
||||||
}
|
|
||||||
defer stmt.Close()
|
|
||||||
|
|
||||||
for _, point := range points {
|
|
||||||
_, err := stmt.ExecContext(ctx, minerName, minerType, point.Timestamp, point.Hashrate, string(resolution))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to insert point: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return tx.Commit()
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetHashrateHistory retrieves hashrate history for a miner within a time range
|
// GetHashrateHistory retrieves hashrate history for a miner within a time range
|
||||||
func GetHashrateHistory(minerName string, resolution Resolution, since, until time.Time) ([]HashratePoint, error) {
|
func GetHashrateHistory(minerName string, resolution Resolution, since, until time.Time) ([]HashratePoint, error) {
|
||||||
dbMu.RLock()
|
dbMu.RLock()
|
||||||
|
|
@ -145,76 +98,6 @@ func GetHashrateHistory(minerName string, resolution Resolution, since, until ti
|
||||||
return points, rows.Err()
|
return points, rows.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetLatestHashrate retrieves the most recent hashrate for a miner
|
|
||||||
func GetLatestHashrate(minerName string) (*HashratePoint, error) {
|
|
||||||
dbMu.RLock()
|
|
||||||
defer dbMu.RUnlock()
|
|
||||||
|
|
||||||
if db == nil {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var point HashratePoint
|
|
||||||
err := db.QueryRow(`
|
|
||||||
SELECT timestamp, hashrate
|
|
||||||
FROM hashrate_history
|
|
||||||
WHERE miner_name = ?
|
|
||||||
ORDER BY timestamp DESC
|
|
||||||
LIMIT 1
|
|
||||||
`, minerName).Scan(&point.Timestamp, &point.Hashrate)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
if err == sql.ErrNoRows {
|
|
||||||
return nil, nil // No data found is not an error
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("failed to get latest hashrate: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &point, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetAverageHashrate calculates the average hashrate for a miner in a time range
|
|
||||||
func GetAverageHashrate(minerName string, since, until time.Time) (int, error) {
|
|
||||||
dbMu.RLock()
|
|
||||||
defer dbMu.RUnlock()
|
|
||||||
|
|
||||||
if db == nil {
|
|
||||||
return 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var avg float64
|
|
||||||
err := db.QueryRow(`
|
|
||||||
SELECT COALESCE(AVG(hashrate), 0)
|
|
||||||
FROM hashrate_history
|
|
||||||
WHERE miner_name = ?
|
|
||||||
AND timestamp >= ?
|
|
||||||
AND timestamp <= ?
|
|
||||||
`, minerName, since, until).Scan(&avg)
|
|
||||||
|
|
||||||
return int(avg), err
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetMaxHashrate retrieves the maximum hashrate for a miner in a time range
|
|
||||||
func GetMaxHashrate(minerName string, since, until time.Time) (int, error) {
|
|
||||||
dbMu.RLock()
|
|
||||||
defer dbMu.RUnlock()
|
|
||||||
|
|
||||||
if db == nil {
|
|
||||||
return 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var max int
|
|
||||||
err := db.QueryRow(`
|
|
||||||
SELECT COALESCE(MAX(hashrate), 0)
|
|
||||||
FROM hashrate_history
|
|
||||||
WHERE miner_name = ?
|
|
||||||
AND timestamp >= ?
|
|
||||||
AND timestamp <= ?
|
|
||||||
`, minerName, since, until).Scan(&max)
|
|
||||||
|
|
||||||
return max, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetHashrateStats retrieves aggregated stats for a miner
|
// GetHashrateStats retrieves aggregated stats for a miner
|
||||||
type HashrateStats struct {
|
type HashrateStats struct {
|
||||||
MinerName string `json:"minerName"`
|
MinerName string `json:"minerName"`
|
||||||
|
|
@ -331,22 +214,3 @@ func GetAllMinerStats() ([]HashrateStats, error) {
|
||||||
|
|
||||||
return allStats, rows.Err()
|
return allStats, rows.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
// CleanupOldData removes hashrate data older than the specified duration
|
|
||||||
func CleanupOldData(resolution Resolution, maxAge time.Duration) error {
|
|
||||||
dbMu.RLock()
|
|
||||||
defer dbMu.RUnlock()
|
|
||||||
|
|
||||||
if db == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
cutoff := time.Now().Add(-maxAge)
|
|
||||||
_, err := db.Exec(`
|
|
||||||
DELETE FROM hashrate_history
|
|
||||||
WHERE resolution = ?
|
|
||||||
AND timestamp < ?
|
|
||||||
`, string(resolution), cutoff)
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,279 +1,5 @@
|
||||||
package database
|
package database
|
||||||
|
|
||||||
import (
|
// This file previously contained session tracking functions.
|
||||||
"fmt"
|
// Session tracking is not currently integrated into the mining manager.
|
||||||
"time"
|
// The database schema still supports sessions for future use.
|
||||||
)
|
|
||||||
|
|
||||||
// MinerSession represents a mining session
|
|
||||||
type MinerSession struct {
|
|
||||||
ID int64 `json:"id"`
|
|
||||||
MinerName string `json:"minerName"`
|
|
||||||
MinerType string `json:"minerType"`
|
|
||||||
StartedAt time.Time `json:"startedAt"`
|
|
||||||
StoppedAt *time.Time `json:"stoppedAt,omitempty"`
|
|
||||||
TotalShares int `json:"totalShares"`
|
|
||||||
RejectedShares int `json:"rejectedShares"`
|
|
||||||
AverageHashrate int `json:"averageHashrate"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// StartSession records the start of a new mining session
|
|
||||||
func StartSession(minerName, minerType string) (int64, error) {
|
|
||||||
dbMu.RLock()
|
|
||||||
defer dbMu.RUnlock()
|
|
||||||
|
|
||||||
if db == nil {
|
|
||||||
return 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
result, err := db.Exec(`
|
|
||||||
INSERT INTO miner_sessions (miner_name, miner_type, started_at)
|
|
||||||
VALUES (?, ?, ?)
|
|
||||||
`, minerName, minerType, time.Now())
|
|
||||||
if err != nil {
|
|
||||||
return 0, fmt.Errorf("failed to start session: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return result.LastInsertId()
|
|
||||||
}
|
|
||||||
|
|
||||||
// EndSession marks a session as complete with final stats
|
|
||||||
func EndSession(sessionID int64, totalShares, rejectedShares, averageHashrate int) error {
|
|
||||||
dbMu.RLock()
|
|
||||||
defer dbMu.RUnlock()
|
|
||||||
|
|
||||||
if db == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err := db.Exec(`
|
|
||||||
UPDATE miner_sessions
|
|
||||||
SET stopped_at = ?,
|
|
||||||
total_shares = ?,
|
|
||||||
rejected_shares = ?,
|
|
||||||
average_hashrate = ?
|
|
||||||
WHERE id = ?
|
|
||||||
`, time.Now(), totalShares, rejectedShares, averageHashrate, sessionID)
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// EndSessionByName marks the most recent session for a miner as complete
|
|
||||||
func EndSessionByName(minerName string, totalShares, rejectedShares, averageHashrate int) error {
|
|
||||||
dbMu.RLock()
|
|
||||||
defer dbMu.RUnlock()
|
|
||||||
|
|
||||||
if db == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err := db.Exec(`
|
|
||||||
UPDATE miner_sessions
|
|
||||||
SET stopped_at = ?,
|
|
||||||
total_shares = ?,
|
|
||||||
rejected_shares = ?,
|
|
||||||
average_hashrate = ?
|
|
||||||
WHERE miner_name = ?
|
|
||||||
AND stopped_at IS NULL
|
|
||||||
`, time.Now(), totalShares, rejectedShares, averageHashrate, minerName)
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetSession retrieves a session by ID
|
|
||||||
func GetSession(sessionID int64) (*MinerSession, error) {
|
|
||||||
dbMu.RLock()
|
|
||||||
defer dbMu.RUnlock()
|
|
||||||
|
|
||||||
if db == nil {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var session MinerSession
|
|
||||||
var stoppedAt *time.Time
|
|
||||||
|
|
||||||
err := db.QueryRow(`
|
|
||||||
SELECT id, miner_name, miner_type, started_at, stopped_at,
|
|
||||||
total_shares, rejected_shares, average_hashrate
|
|
||||||
FROM miner_sessions
|
|
||||||
WHERE id = ?
|
|
||||||
`, sessionID).Scan(
|
|
||||||
&session.ID,
|
|
||||||
&session.MinerName,
|
|
||||||
&session.MinerType,
|
|
||||||
&session.StartedAt,
|
|
||||||
&stoppedAt,
|
|
||||||
&session.TotalShares,
|
|
||||||
&session.RejectedShares,
|
|
||||||
&session.AverageHashrate,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
session.StoppedAt = stoppedAt
|
|
||||||
return &session, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetActiveSessions retrieves all currently active (non-stopped) sessions
|
|
||||||
func GetActiveSessions() ([]MinerSession, error) {
|
|
||||||
dbMu.RLock()
|
|
||||||
defer dbMu.RUnlock()
|
|
||||||
|
|
||||||
if db == nil {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
rows, err := db.Query(`
|
|
||||||
SELECT id, miner_name, miner_type, started_at, stopped_at,
|
|
||||||
total_shares, rejected_shares, average_hashrate
|
|
||||||
FROM miner_sessions
|
|
||||||
WHERE stopped_at IS NULL
|
|
||||||
ORDER BY started_at DESC
|
|
||||||
`)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer rows.Close()
|
|
||||||
|
|
||||||
var sessions []MinerSession
|
|
||||||
for rows.Next() {
|
|
||||||
var session MinerSession
|
|
||||||
var stoppedAt *time.Time
|
|
||||||
if err := rows.Scan(
|
|
||||||
&session.ID,
|
|
||||||
&session.MinerName,
|
|
||||||
&session.MinerType,
|
|
||||||
&session.StartedAt,
|
|
||||||
&stoppedAt,
|
|
||||||
&session.TotalShares,
|
|
||||||
&session.RejectedShares,
|
|
||||||
&session.AverageHashrate,
|
|
||||||
); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
session.StoppedAt = stoppedAt
|
|
||||||
sessions = append(sessions, session)
|
|
||||||
}
|
|
||||||
|
|
||||||
return sessions, rows.Err()
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetRecentSessions retrieves the most recent sessions for a miner
|
|
||||||
func GetRecentSessions(minerName string, limit int) ([]MinerSession, error) {
|
|
||||||
dbMu.RLock()
|
|
||||||
defer dbMu.RUnlock()
|
|
||||||
|
|
||||||
if db == nil {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
rows, err := db.Query(`
|
|
||||||
SELECT id, miner_name, miner_type, started_at, stopped_at,
|
|
||||||
total_shares, rejected_shares, average_hashrate
|
|
||||||
FROM miner_sessions
|
|
||||||
WHERE miner_name = ?
|
|
||||||
ORDER BY started_at DESC
|
|
||||||
LIMIT ?
|
|
||||||
`, minerName, limit)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer rows.Close()
|
|
||||||
|
|
||||||
var sessions []MinerSession
|
|
||||||
for rows.Next() {
|
|
||||||
var session MinerSession
|
|
||||||
var stoppedAt *time.Time
|
|
||||||
if err := rows.Scan(
|
|
||||||
&session.ID,
|
|
||||||
&session.MinerName,
|
|
||||||
&session.MinerType,
|
|
||||||
&session.StartedAt,
|
|
||||||
&stoppedAt,
|
|
||||||
&session.TotalShares,
|
|
||||||
&session.RejectedShares,
|
|
||||||
&session.AverageHashrate,
|
|
||||||
); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
session.StoppedAt = stoppedAt
|
|
||||||
sessions = append(sessions, session)
|
|
||||||
}
|
|
||||||
|
|
||||||
return sessions, rows.Err()
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetSessionStats retrieves aggregated session statistics for a miner
|
|
||||||
type SessionStats struct {
|
|
||||||
MinerName string `json:"minerName"`
|
|
||||||
TotalSessions int `json:"totalSessions"`
|
|
||||||
TotalUptime time.Duration `json:"totalUptime"`
|
|
||||||
TotalShares int `json:"totalShares"`
|
|
||||||
TotalRejected int `json:"totalRejected"`
|
|
||||||
AvgSessionTime time.Duration `json:"avgSessionTime"`
|
|
||||||
AvgHashrate int `json:"avgHashrate"`
|
|
||||||
LastSessionAt time.Time `json:"lastSessionAt"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetSessionStats(minerName string) (*SessionStats, error) {
|
|
||||||
dbMu.RLock()
|
|
||||||
defer dbMu.RUnlock()
|
|
||||||
|
|
||||||
if db == nil {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var stats SessionStats
|
|
||||||
stats.MinerName = minerName
|
|
||||||
|
|
||||||
// Get basic aggregates
|
|
||||||
err := db.QueryRow(`
|
|
||||||
SELECT
|
|
||||||
COUNT(*),
|
|
||||||
COALESCE(SUM(total_shares), 0),
|
|
||||||
COALESCE(SUM(rejected_shares), 0),
|
|
||||||
COALESCE(AVG(average_hashrate), 0),
|
|
||||||
MAX(started_at)
|
|
||||||
FROM miner_sessions
|
|
||||||
WHERE miner_name = ?
|
|
||||||
AND stopped_at IS NOT NULL
|
|
||||||
`, minerName).Scan(
|
|
||||||
&stats.TotalSessions,
|
|
||||||
&stats.TotalShares,
|
|
||||||
&stats.TotalRejected,
|
|
||||||
&stats.AvgHashrate,
|
|
||||||
&stats.LastSessionAt,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate total uptime
|
|
||||||
rows, err := db.Query(`
|
|
||||||
SELECT started_at, stopped_at
|
|
||||||
FROM miner_sessions
|
|
||||||
WHERE miner_name = ?
|
|
||||||
AND stopped_at IS NOT NULL
|
|
||||||
`, minerName)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer rows.Close()
|
|
||||||
|
|
||||||
var totalSeconds int64
|
|
||||||
for rows.Next() {
|
|
||||||
var started, stopped time.Time
|
|
||||||
if err := rows.Scan(&started, &stopped); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
totalSeconds += int64(stopped.Sub(started).Seconds())
|
|
||||||
}
|
|
||||||
|
|
||||||
stats.TotalUptime = time.Duration(totalSeconds) * time.Second
|
|
||||||
if stats.TotalSessions > 0 {
|
|
||||||
stats.AvgSessionTime = stats.TotalUptime / time.Duration(stats.TotalSessions)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &stats, nil
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -28,8 +28,8 @@ type DatabaseConfig struct {
|
||||||
RetentionDays int `json:"retentionDays,omitempty"`
|
RetentionDays int `json:"retentionDays,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// DefaultDatabaseConfig returns the default database configuration.
|
// defaultDatabaseConfig returns the default database configuration.
|
||||||
func DefaultDatabaseConfig() DatabaseConfig {
|
func defaultDatabaseConfig() DatabaseConfig {
|
||||||
return DatabaseConfig{
|
return DatabaseConfig{
|
||||||
Enabled: true,
|
Enabled: true,
|
||||||
RetentionDays: 30,
|
RetentionDays: 30,
|
||||||
|
|
@ -42,8 +42,8 @@ type MinersConfig struct {
|
||||||
Database DatabaseConfig `json:"database"`
|
Database DatabaseConfig `json:"database"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetMinersConfigPath returns the path to the miners configuration file.
|
// getMinersConfigPath returns the path to the miners configuration file.
|
||||||
func GetMinersConfigPath() (string, error) {
|
func getMinersConfigPath() (string, error) {
|
||||||
return xdg.ConfigFile("lethean-desktop/miners/config.json")
|
return xdg.ConfigFile("lethean-desktop/miners/config.json")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -52,7 +52,7 @@ func LoadMinersConfig() (*MinersConfig, error) {
|
||||||
configMu.RLock()
|
configMu.RLock()
|
||||||
defer configMu.RUnlock()
|
defer configMu.RUnlock()
|
||||||
|
|
||||||
configPath, err := GetMinersConfigPath()
|
configPath, err := getMinersConfigPath()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("could not determine miners config path: %w", err)
|
return nil, fmt.Errorf("could not determine miners config path: %w", err)
|
||||||
}
|
}
|
||||||
|
|
@ -63,7 +63,7 @@ func LoadMinersConfig() (*MinersConfig, error) {
|
||||||
// Return empty config with defaults if file doesn't exist
|
// Return empty config with defaults if file doesn't exist
|
||||||
return &MinersConfig{
|
return &MinersConfig{
|
||||||
Miners: []MinerAutostartConfig{},
|
Miners: []MinerAutostartConfig{},
|
||||||
Database: DefaultDatabaseConfig(),
|
Database: defaultDatabaseConfig(),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("failed to read miners config file: %w", err)
|
return nil, fmt.Errorf("failed to read miners config file: %w", err)
|
||||||
|
|
@ -76,7 +76,7 @@ func LoadMinersConfig() (*MinersConfig, error) {
|
||||||
|
|
||||||
// Apply default database config if not set (for backwards compatibility)
|
// Apply default database config if not set (for backwards compatibility)
|
||||||
if cfg.Database.RetentionDays == 0 {
|
if cfg.Database.RetentionDays == 0 {
|
||||||
cfg.Database = DefaultDatabaseConfig()
|
cfg.Database = defaultDatabaseConfig()
|
||||||
}
|
}
|
||||||
|
|
||||||
return &cfg, nil
|
return &cfg, nil
|
||||||
|
|
@ -88,7 +88,7 @@ func SaveMinersConfig(cfg *MinersConfig) error {
|
||||||
configMu.Lock()
|
configMu.Lock()
|
||||||
defer configMu.Unlock()
|
defer configMu.Unlock()
|
||||||
|
|
||||||
configPath, err := GetMinersConfigPath()
|
configPath, err := getMinersConfigPath()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("could not determine miners config path: %w", err)
|
return fmt.Errorf("could not determine miners config path: %w", err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,11 +22,6 @@ const (
|
||||||
EventMinerError EventType = "miner.error"
|
EventMinerError EventType = "miner.error"
|
||||||
EventMinerConnected EventType = "miner.connected"
|
EventMinerConnected EventType = "miner.connected"
|
||||||
|
|
||||||
// Profile events
|
|
||||||
EventProfileCreated EventType = "profile.created"
|
|
||||||
EventProfileUpdated EventType = "profile.updated"
|
|
||||||
EventProfileDeleted EventType = "profile.deleted"
|
|
||||||
|
|
||||||
// System events
|
// System events
|
||||||
EventPong EventType = "pong"
|
EventPong EventType = "pong"
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -10,3 +10,13 @@ var (
|
||||||
func GetVersion() string {
|
func GetVersion() string {
|
||||||
return version
|
return version
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetCommit returns the git commit hash
|
||||||
|
func GetCommit() string {
|
||||||
|
return commit
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBuildDate returns the build date
|
||||||
|
func GetBuildDate() string {
|
||||||
|
return date
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -129,71 +129,6 @@ func CreateMinerBundle(minerPath string, profileJSON []byte, name string, passwo
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateFullBundle creates an encrypted bundle with miners and all profiles.
|
|
||||||
func CreateFullBundle(minerPaths []string, profiles [][]byte, name string, password string) (*Bundle, error) {
|
|
||||||
files := make(map[string][]byte)
|
|
||||||
|
|
||||||
// Add each miner
|
|
||||||
for _, minerPath := range minerPaths {
|
|
||||||
minerData, err := os.ReadFile(minerPath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to read miner %s: %w", minerPath, err)
|
|
||||||
}
|
|
||||||
files["miners/"+filepath.Base(minerPath)] = minerData
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add each profile
|
|
||||||
for i, profile := range profiles {
|
|
||||||
profileName := fmt.Sprintf("profiles/profile_%d.json", i)
|
|
||||||
files[profileName] = profile
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create tarball
|
|
||||||
tarData, err := createTarball(files)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to create tarball: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create DataNode from tarball
|
|
||||||
dn, err := datanode.FromTar(tarData)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to create datanode: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create TIM from DataNode
|
|
||||||
t, err := tim.FromDataNode(dn)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to create TIM: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create manifest as config
|
|
||||||
manifest := BundleManifest{
|
|
||||||
Type: BundleFull,
|
|
||||||
Name: name,
|
|
||||||
ProfileIDs: make([]string, len(profiles)),
|
|
||||||
}
|
|
||||||
manifestJSON, err := json.Marshal(manifest)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to create manifest: %w", err)
|
|
||||||
}
|
|
||||||
t.Config = manifestJSON
|
|
||||||
|
|
||||||
// Encrypt to STIM format
|
|
||||||
stimData, err := t.ToSigil(password)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to encrypt bundle: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
checksum := calculateChecksum(stimData)
|
|
||||||
|
|
||||||
return &Bundle{
|
|
||||||
Type: BundleFull,
|
|
||||||
Name: name,
|
|
||||||
Data: stimData,
|
|
||||||
Checksum: checksum,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExtractProfileBundle decrypts and extracts a profile bundle.
|
// ExtractProfileBundle decrypts and extracts a profile bundle.
|
||||||
func ExtractProfileBundle(bundle *Bundle, password string) ([]byte, error) {
|
func ExtractProfileBundle(bundle *Bundle, password string) ([]byte, error) {
|
||||||
// Verify checksum first
|
// Verify checksum first
|
||||||
|
|
@ -243,39 +178,6 @@ func ExtractMinerBundle(bundle *Bundle, password string, destDir string) (string
|
||||||
return minerPath, t.Config, nil
|
return minerPath, t.Config, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExtractFullBundle decrypts and extracts a full bundle.
|
|
||||||
func ExtractFullBundle(bundle *Bundle, password string, destDir string) (*BundleManifest, error) {
|
|
||||||
// Verify checksum
|
|
||||||
if calculateChecksum(bundle.Data) != bundle.Checksum {
|
|
||||||
return nil, fmt.Errorf("checksum mismatch - bundle may be corrupted")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Decrypt STIM format
|
|
||||||
t, err := tim.FromSigil(bundle.Data, password)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to decrypt bundle: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse manifest
|
|
||||||
var manifest BundleManifest
|
|
||||||
if err := json.Unmarshal(t.Config, &manifest); err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to parse manifest: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert rootfs to tarball and extract
|
|
||||||
tarData, err := t.RootFS.ToTar()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to convert rootfs to tar: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract tarball to destination
|
|
||||||
if _, err := extractTarball(tarData, destDir); err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to extract tarball: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &manifest, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// VerifyBundle checks if a bundle's checksum is valid.
|
// VerifyBundle checks if a bundle's checksum is valid.
|
||||||
func VerifyBundle(bundle *Bundle) bool {
|
func VerifyBundle(bundle *Bundle) bool {
|
||||||
return calculateChecksum(bundle.Data) == bundle.Checksum
|
return calculateChecksum(bundle.Data) == bundle.Checksum
|
||||||
|
|
|
||||||
|
|
@ -111,6 +111,9 @@ func (c *Controller) sendRequest(peerID string, msg *Message, timeout time.Durat
|
||||||
// GetRemoteStats requests miner statistics from a remote peer.
|
// GetRemoteStats requests miner statistics from a remote peer.
|
||||||
func (c *Controller) GetRemoteStats(peerID string) (*StatsPayload, error) {
|
func (c *Controller) GetRemoteStats(peerID string) (*StatsPayload, error) {
|
||||||
identity := c.node.GetIdentity()
|
identity := c.node.GetIdentity()
|
||||||
|
if identity == nil {
|
||||||
|
return nil, fmt.Errorf("node identity not initialized")
|
||||||
|
}
|
||||||
|
|
||||||
msg, err := NewMessage(MsgGetStats, identity.ID, peerID, nil)
|
msg, err := NewMessage(MsgGetStats, identity.ID, peerID, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -145,6 +148,9 @@ func (c *Controller) GetRemoteStats(peerID string) (*StatsPayload, error) {
|
||||||
// StartRemoteMiner requests a remote peer to start a miner with a given profile.
|
// StartRemoteMiner requests a remote peer to start a miner with a given profile.
|
||||||
func (c *Controller) StartRemoteMiner(peerID, profileID string, configOverride json.RawMessage) error {
|
func (c *Controller) StartRemoteMiner(peerID, profileID string, configOverride json.RawMessage) error {
|
||||||
identity := c.node.GetIdentity()
|
identity := c.node.GetIdentity()
|
||||||
|
if identity == nil {
|
||||||
|
return fmt.Errorf("node identity not initialized")
|
||||||
|
}
|
||||||
|
|
||||||
payload := StartMinerPayload{
|
payload := StartMinerPayload{
|
||||||
ProfileID: profileID,
|
ProfileID: profileID,
|
||||||
|
|
@ -188,6 +194,9 @@ func (c *Controller) StartRemoteMiner(peerID, profileID string, configOverride j
|
||||||
// StopRemoteMiner requests a remote peer to stop a miner.
|
// StopRemoteMiner requests a remote peer to stop a miner.
|
||||||
func (c *Controller) StopRemoteMiner(peerID, minerName string) error {
|
func (c *Controller) StopRemoteMiner(peerID, minerName string) error {
|
||||||
identity := c.node.GetIdentity()
|
identity := c.node.GetIdentity()
|
||||||
|
if identity == nil {
|
||||||
|
return fmt.Errorf("node identity not initialized")
|
||||||
|
}
|
||||||
|
|
||||||
payload := StopMinerPayload{
|
payload := StopMinerPayload{
|
||||||
MinerName: minerName,
|
MinerName: minerName,
|
||||||
|
|
@ -230,6 +239,9 @@ func (c *Controller) StopRemoteMiner(peerID, minerName string) error {
|
||||||
// GetRemoteLogs requests console logs from a remote miner.
|
// GetRemoteLogs requests console logs from a remote miner.
|
||||||
func (c *Controller) GetRemoteLogs(peerID, minerName string, lines int) ([]string, error) {
|
func (c *Controller) GetRemoteLogs(peerID, minerName string, lines int) ([]string, error) {
|
||||||
identity := c.node.GetIdentity()
|
identity := c.node.GetIdentity()
|
||||||
|
if identity == nil {
|
||||||
|
return nil, fmt.Errorf("node identity not initialized")
|
||||||
|
}
|
||||||
|
|
||||||
payload := GetLogsPayload{
|
payload := GetLogsPayload{
|
||||||
MinerName: minerName,
|
MinerName: minerName,
|
||||||
|
|
@ -266,51 +278,6 @@ func (c *Controller) GetRemoteLogs(peerID, minerName string, lines int) ([]strin
|
||||||
return logs.Lines, nil
|
return logs.Lines, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeployProfile sends a profile configuration to a remote peer.
|
|
||||||
func (c *Controller) DeployProfile(peerID string, bundleData []byte, name string, checksum string) error {
|
|
||||||
identity := c.node.GetIdentity()
|
|
||||||
|
|
||||||
payload := DeployPayload{
|
|
||||||
BundleType: "profile",
|
|
||||||
Data: bundleData,
|
|
||||||
Checksum: checksum,
|
|
||||||
Name: name,
|
|
||||||
}
|
|
||||||
|
|
||||||
msg, err := NewMessage(MsgDeploy, identity.ID, peerID, payload)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to create message: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := c.sendRequest(peerID, msg, 60*time.Second)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if resp.Type == MsgError {
|
|
||||||
var errPayload ErrorPayload
|
|
||||||
if err := resp.ParsePayload(&errPayload); err != nil {
|
|
||||||
return fmt.Errorf("remote error (unable to parse)")
|
|
||||||
}
|
|
||||||
return fmt.Errorf("remote error: %s", errPayload.Message)
|
|
||||||
}
|
|
||||||
|
|
||||||
if resp.Type != MsgDeployAck {
|
|
||||||
return fmt.Errorf("unexpected response type: %s", resp.Type)
|
|
||||||
}
|
|
||||||
|
|
||||||
var ack DeployAckPayload
|
|
||||||
if err := resp.ParsePayload(&ack); err != nil {
|
|
||||||
return fmt.Errorf("failed to parse ack: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !ack.Success {
|
|
||||||
return fmt.Errorf("deployment failed: %s", ack.Error)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetAllStats fetches stats from all connected peers.
|
// GetAllStats fetches stats from all connected peers.
|
||||||
func (c *Controller) GetAllStats() map[string]*StatsPayload {
|
func (c *Controller) GetAllStats() map[string]*StatsPayload {
|
||||||
peers := c.peers.GetConnectedPeers()
|
peers := c.peers.GetConnectedPeers()
|
||||||
|
|
@ -336,26 +303,12 @@ func (c *Controller) GetAllStats() map[string]*StatsPayload {
|
||||||
return results
|
return results
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetTotalHashrate calculates total hashrate across all connected peers.
|
|
||||||
func (c *Controller) GetTotalHashrate() float64 {
|
|
||||||
allStats := c.GetAllStats()
|
|
||||||
var total float64
|
|
||||||
|
|
||||||
for _, stats := range allStats {
|
|
||||||
if stats == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
for _, miner := range stats.Miners {
|
|
||||||
total += miner.Hashrate
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return total
|
|
||||||
}
|
|
||||||
|
|
||||||
// PingPeer sends a ping to a peer and updates metrics.
|
// PingPeer sends a ping to a peer and updates metrics.
|
||||||
func (c *Controller) PingPeer(peerID string) (float64, error) {
|
func (c *Controller) PingPeer(peerID string) (float64, error) {
|
||||||
identity := c.node.GetIdentity()
|
identity := c.node.GetIdentity()
|
||||||
|
if identity == nil {
|
||||||
|
return 0, fmt.Errorf("node identity not initialized")
|
||||||
|
}
|
||||||
sentAt := time.Now()
|
sentAt := time.Now()
|
||||||
|
|
||||||
payload := PingPayload{
|
payload := PingPayload{
|
||||||
|
|
|
||||||
|
|
@ -171,16 +171,6 @@ func (n *NodeManager) DeriveSharedSecret(peerPubKeyBase64 string) ([]byte, error
|
||||||
return hash[:], nil
|
return hash[:], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetPublicKey returns the node's public key in base64 format.
|
|
||||||
func (n *NodeManager) GetPublicKey() string {
|
|
||||||
n.mu.RLock()
|
|
||||||
defer n.mu.RUnlock()
|
|
||||||
if n.identity == nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return n.identity.PublicKey
|
|
||||||
}
|
|
||||||
|
|
||||||
// savePrivateKey saves the private key to disk with restricted permissions.
|
// savePrivateKey saves the private key to disk with restricted permissions.
|
||||||
func (n *NodeManager) savePrivateKey() error {
|
func (n *NodeManager) savePrivateKey() error {
|
||||||
// Ensure directory exists
|
// Ensure directory exists
|
||||||
|
|
@ -249,32 +239,6 @@ func (n *NodeManager) loadIdentity() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateName updates the node's display name.
|
|
||||||
func (n *NodeManager) UpdateName(name string) error {
|
|
||||||
n.mu.Lock()
|
|
||||||
defer n.mu.Unlock()
|
|
||||||
|
|
||||||
if n.identity == nil {
|
|
||||||
return fmt.Errorf("node identity not initialized")
|
|
||||||
}
|
|
||||||
|
|
||||||
n.identity.Name = name
|
|
||||||
return n.saveIdentity()
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateRole updates the node's operational role.
|
|
||||||
func (n *NodeManager) UpdateRole(role NodeRole) error {
|
|
||||||
n.mu.Lock()
|
|
||||||
defer n.mu.Unlock()
|
|
||||||
|
|
||||||
if n.identity == nil {
|
|
||||||
return fmt.Errorf("node identity not initialized")
|
|
||||||
}
|
|
||||||
|
|
||||||
n.identity.Role = role
|
|
||||||
return n.saveIdentity()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete removes the node identity and keys from disk.
|
// Delete removes the node identity and keys from disk.
|
||||||
func (n *NodeManager) Delete() error {
|
func (n *NodeManager) Delete() error {
|
||||||
n.mu.Lock()
|
n.mu.Lock()
|
||||||
|
|
|
||||||
|
|
@ -168,6 +168,9 @@ func TestPeerRegistry_UpdateMetrics(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
updated := pr.GetPeer("metrics-test")
|
updated := pr.GetPeer("metrics-test")
|
||||||
|
if updated == nil {
|
||||||
|
t.Fatal("expected peer to exist")
|
||||||
|
}
|
||||||
if updated.PingMS != 50.5 {
|
if updated.PingMS != 50.5 {
|
||||||
t.Errorf("expected ping 50.5, got %f", updated.PingMS)
|
t.Errorf("expected ping 50.5, got %f", updated.PingMS)
|
||||||
}
|
}
|
||||||
|
|
@ -197,6 +200,9 @@ func TestPeerRegistry_UpdateScore(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
updated := pr.GetPeer("score-test")
|
updated := pr.GetPeer("score-test")
|
||||||
|
if updated == nil {
|
||||||
|
t.Fatal("expected peer to exist")
|
||||||
|
}
|
||||||
if updated.Score != 85.5 {
|
if updated.Score != 85.5 {
|
||||||
t.Errorf("expected score 85.5, got %f", updated.Score)
|
t.Errorf("expected score 85.5, got %f", updated.Score)
|
||||||
}
|
}
|
||||||
|
|
@ -208,6 +214,9 @@ func TestPeerRegistry_UpdateScore(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
updated = pr.GetPeer("score-test")
|
updated = pr.GetPeer("score-test")
|
||||||
|
if updated == nil {
|
||||||
|
t.Fatal("expected peer to exist")
|
||||||
|
}
|
||||||
if updated.Score != 100 {
|
if updated.Score != 100 {
|
||||||
t.Errorf("expected score clamped to 100, got %f", updated.Score)
|
t.Errorf("expected score clamped to 100, got %f", updated.Score)
|
||||||
}
|
}
|
||||||
|
|
@ -219,6 +228,9 @@ func TestPeerRegistry_UpdateScore(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
updated = pr.GetPeer("score-test")
|
updated = pr.GetPeer("score-test")
|
||||||
|
if updated == nil {
|
||||||
|
t.Fatal("expected peer to exist")
|
||||||
|
}
|
||||||
if updated.Score != 0 {
|
if updated.Score != 0 {
|
||||||
t.Errorf("expected score clamped to 0, got %f", updated.Score)
|
t.Errorf("expected score clamped to 0, got %f", updated.Score)
|
||||||
}
|
}
|
||||||
|
|
@ -239,6 +251,9 @@ func TestPeerRegistry_SetConnected(t *testing.T) {
|
||||||
pr.SetConnected("connect-test", true)
|
pr.SetConnected("connect-test", true)
|
||||||
|
|
||||||
updated := pr.GetPeer("connect-test")
|
updated := pr.GetPeer("connect-test")
|
||||||
|
if updated == nil {
|
||||||
|
t.Fatal("expected peer to exist")
|
||||||
|
}
|
||||||
if !updated.Connected {
|
if !updated.Connected {
|
||||||
t.Error("peer should be connected")
|
t.Error("peer should be connected")
|
||||||
}
|
}
|
||||||
|
|
@ -248,6 +263,9 @@ func TestPeerRegistry_SetConnected(t *testing.T) {
|
||||||
|
|
||||||
pr.SetConnected("connect-test", false)
|
pr.SetConnected("connect-test", false)
|
||||||
updated = pr.GetPeer("connect-test")
|
updated = pr.GetPeer("connect-test")
|
||||||
|
if updated == nil {
|
||||||
|
t.Fatal("expected peer to exist")
|
||||||
|
}
|
||||||
if updated.Connected {
|
if updated.Connected {
|
||||||
t.Error("peer should be disconnected")
|
t.Error("peer should be disconnected")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -327,6 +327,10 @@ func (t *Transport) handleWSUpgrade(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
// Send handshake acknowledgment
|
// Send handshake acknowledgment
|
||||||
identity := t.node.GetIdentity()
|
identity := t.node.GetIdentity()
|
||||||
|
if identity == nil {
|
||||||
|
conn.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
ackPayload := HandshakeAckPayload{
|
ackPayload := HandshakeAckPayload{
|
||||||
Identity: *identity,
|
Identity: *identity,
|
||||||
Accepted: true,
|
Accepted: true,
|
||||||
|
|
@ -380,6 +384,9 @@ func (t *Transport) performHandshake(pc *PeerConnection) error {
|
||||||
}()
|
}()
|
||||||
|
|
||||||
identity := t.node.GetIdentity()
|
identity := t.node.GetIdentity()
|
||||||
|
if identity == nil {
|
||||||
|
return fmt.Errorf("node identity not initialized")
|
||||||
|
}
|
||||||
|
|
||||||
payload := HandshakePayload{
|
payload := HandshakePayload{
|
||||||
Identity: *identity,
|
Identity: *identity,
|
||||||
|
|
|
||||||
|
|
@ -83,14 +83,17 @@ func (w *Worker) HandleMessage(conn *PeerConnection, msg *Message) {
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Send error response
|
// Send error response
|
||||||
|
identity := w.node.GetIdentity()
|
||||||
|
if identity != nil {
|
||||||
errMsg, _ := NewErrorMessage(
|
errMsg, _ := NewErrorMessage(
|
||||||
w.node.GetIdentity().ID,
|
identity.ID,
|
||||||
msg.From,
|
msg.From,
|
||||||
ErrCodeOperationFailed,
|
ErrCodeOperationFailed,
|
||||||
err.Error(),
|
err.Error(),
|
||||||
msg.ID,
|
msg.ID,
|
||||||
)
|
)
|
||||||
conn.Send(errMsg)
|
conn.Send(errMsg)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -122,6 +125,9 @@ func (w *Worker) handlePing(msg *Message) (*Message, error) {
|
||||||
// handleGetStats responds with current miner statistics.
|
// handleGetStats responds with current miner statistics.
|
||||||
func (w *Worker) handleGetStats(msg *Message) (*Message, error) {
|
func (w *Worker) handleGetStats(msg *Message) (*Message, error) {
|
||||||
identity := w.node.GetIdentity()
|
identity := w.node.GetIdentity()
|
||||||
|
if identity == nil {
|
||||||
|
return nil, fmt.Errorf("node identity not initialized")
|
||||||
|
}
|
||||||
|
|
||||||
stats := StatsPayload{
|
stats := StatsPayload{
|
||||||
NodeID: identity.ID,
|
NodeID: identity.ID,
|
||||||
|
|
|
||||||
|
|
@ -127,6 +127,9 @@ func TestWorker_HandlePing(t *testing.T) {
|
||||||
|
|
||||||
// Create a ping message
|
// Create a ping message
|
||||||
identity := nm.GetIdentity()
|
identity := nm.GetIdentity()
|
||||||
|
if identity == nil {
|
||||||
|
t.Fatal("expected identity to be generated")
|
||||||
|
}
|
||||||
pingPayload := PingPayload{SentAt: time.Now().UnixMilli()}
|
pingPayload := PingPayload{SentAt: time.Now().UnixMilli()}
|
||||||
pingMsg, err := NewMessage(MsgPing, "sender-id", identity.ID, pingPayload)
|
pingMsg, err := NewMessage(MsgPing, "sender-id", identity.ID, pingPayload)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -183,6 +186,9 @@ func TestWorker_HandleGetStats(t *testing.T) {
|
||||||
|
|
||||||
// Create a get_stats message
|
// Create a get_stats message
|
||||||
identity := nm.GetIdentity()
|
identity := nm.GetIdentity()
|
||||||
|
if identity == nil {
|
||||||
|
t.Fatal("expected identity to be generated")
|
||||||
|
}
|
||||||
msg, err := NewMessage(MsgGetStats, "sender-id", identity.ID, nil)
|
msg, err := NewMessage(MsgGetStats, "sender-id", identity.ID, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to create get_stats message: %v", err)
|
t.Fatalf("failed to create get_stats message: %v", err)
|
||||||
|
|
@ -238,6 +244,9 @@ func TestWorker_HandleStartMiner_NoManager(t *testing.T) {
|
||||||
|
|
||||||
// Create a start_miner message
|
// Create a start_miner message
|
||||||
identity := nm.GetIdentity()
|
identity := nm.GetIdentity()
|
||||||
|
if identity == nil {
|
||||||
|
t.Fatal("expected identity to be generated")
|
||||||
|
}
|
||||||
payload := StartMinerPayload{ProfileID: "test-profile"}
|
payload := StartMinerPayload{ProfileID: "test-profile"}
|
||||||
msg, err := NewMessage(MsgStartMiner, "sender-id", identity.ID, payload)
|
msg, err := NewMessage(MsgStartMiner, "sender-id", identity.ID, payload)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -273,6 +282,9 @@ func TestWorker_HandleStopMiner_NoManager(t *testing.T) {
|
||||||
|
|
||||||
// Create a stop_miner message
|
// Create a stop_miner message
|
||||||
identity := nm.GetIdentity()
|
identity := nm.GetIdentity()
|
||||||
|
if identity == nil {
|
||||||
|
t.Fatal("expected identity to be generated")
|
||||||
|
}
|
||||||
payload := StopMinerPayload{MinerName: "test-miner"}
|
payload := StopMinerPayload{MinerName: "test-miner"}
|
||||||
msg, err := NewMessage(MsgStopMiner, "sender-id", identity.ID, payload)
|
msg, err := NewMessage(MsgStopMiner, "sender-id", identity.ID, payload)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -308,6 +320,9 @@ func TestWorker_HandleGetLogs_NoManager(t *testing.T) {
|
||||||
|
|
||||||
// Create a get_logs message
|
// Create a get_logs message
|
||||||
identity := nm.GetIdentity()
|
identity := nm.GetIdentity()
|
||||||
|
if identity == nil {
|
||||||
|
t.Fatal("expected identity to be generated")
|
||||||
|
}
|
||||||
payload := GetLogsPayload{MinerName: "test-miner", Lines: 100}
|
payload := GetLogsPayload{MinerName: "test-miner", Lines: 100}
|
||||||
msg, err := NewMessage(MsgGetLogs, "sender-id", identity.ID, payload)
|
msg, err := NewMessage(MsgGetLogs, "sender-id", identity.ID, payload)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -343,6 +358,9 @@ func TestWorker_HandleDeploy_Profile(t *testing.T) {
|
||||||
|
|
||||||
// Create a deploy message for profile
|
// Create a deploy message for profile
|
||||||
identity := nm.GetIdentity()
|
identity := nm.GetIdentity()
|
||||||
|
if identity == nil {
|
||||||
|
t.Fatal("expected identity to be generated")
|
||||||
|
}
|
||||||
payload := DeployPayload{
|
payload := DeployPayload{
|
||||||
BundleType: "profile",
|
BundleType: "profile",
|
||||||
Data: []byte(`{"id": "test", "name": "Test Profile"}`),
|
Data: []byte(`{"id": "test", "name": "Test Profile"}`),
|
||||||
|
|
@ -382,6 +400,9 @@ func TestWorker_HandleDeploy_UnknownType(t *testing.T) {
|
||||||
|
|
||||||
// Create a deploy message with unknown type
|
// Create a deploy message with unknown type
|
||||||
identity := nm.GetIdentity()
|
identity := nm.GetIdentity()
|
||||||
|
if identity == nil {
|
||||||
|
t.Fatal("expected identity to be generated")
|
||||||
|
}
|
||||||
payload := DeployPayload{
|
payload := DeployPayload{
|
||||||
BundleType: "unknown",
|
BundleType: "unknown",
|
||||||
Data: []byte(`{}`),
|
Data: []byte(`{}`),
|
||||||
|
|
|
||||||
46
qodana.yaml
Normal file
46
qodana.yaml
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
#-------------------------------------------------------------------------------#
|
||||||
|
# Qodana analysis is configured by qodana.yaml file #
|
||||||
|
# https://www.jetbrains.com/help/qodana/qodana-yaml.html #
|
||||||
|
#-------------------------------------------------------------------------------#
|
||||||
|
|
||||||
|
#################################################################################
|
||||||
|
# WARNING: Do not store sensitive information in this file, #
|
||||||
|
# as its contents will be included in the Qodana report. #
|
||||||
|
#################################################################################
|
||||||
|
version: "1.0"
|
||||||
|
|
||||||
|
#Specify inspection profile for code analysis
|
||||||
|
profile:
|
||||||
|
name: qodana.starter
|
||||||
|
|
||||||
|
#Enable inspections
|
||||||
|
#include:
|
||||||
|
# - name: <SomeEnabledInspectionId>
|
||||||
|
|
||||||
|
#Disable inspections
|
||||||
|
#exclude:
|
||||||
|
# - name: <SomeDisabledInspectionId>
|
||||||
|
# paths:
|
||||||
|
# - <path/where/not/run/inspection>
|
||||||
|
|
||||||
|
#Execute shell command before Qodana execution (Applied in CI/CD pipeline)
|
||||||
|
#bootstrap: sh ./prepare-qodana.sh
|
||||||
|
|
||||||
|
#Install IDE plugins before Qodana execution (Applied in CI/CD pipeline)
|
||||||
|
#plugins:
|
||||||
|
# - id: <plugin.id> #(plugin id can be found at https://plugins.jetbrains.com)
|
||||||
|
|
||||||
|
# Quality gate. Will fail the CI/CD pipeline if any condition is not met
|
||||||
|
# severityThresholds - configures maximum thresholds for different problem severities
|
||||||
|
# testCoverageThresholds - configures minimum code coverage on a whole project and newly added code
|
||||||
|
# Code Coverage is available in Ultimate and Ultimate Plus plans
|
||||||
|
#failureConditions:
|
||||||
|
# severityThresholds:
|
||||||
|
# any: 15
|
||||||
|
# critical: 5
|
||||||
|
# testCoverageThresholds:
|
||||||
|
# fresh: 70
|
||||||
|
# total: 50
|
||||||
|
|
||||||
|
#Specify Qodana linter for analysis (Applied in CI/CD pipeline)
|
||||||
|
linter: jetbrains/qodana-go:2025.3
|
||||||
Loading…
Add table
Reference in a new issue