Files
zeekay 7cf87b230b node-unified is the node
The module already called itself github.com/luxfi/node while containing no node
in Go: cmd/luxd was twenty-one lines that printed the size of a few types, and
the Go column of the matrix measured a VM plugin. A repo that fetches the thing
it claims to be has not replaced it, so the Go sources move in — 1515 files,
the daemon and everything under it — and the build reaches outside this module
for nothing.

Rust and C++ sit beside it unchanged and are still measured against it by the
differential. That is the point of one repo: the reference implementation and
the two ports are the same checkout, so parity is a thing you can run rather
than a thing you assert.

Verified: builds, and the closure carries no lux-private — checked at build time
rather than believed. The daemon starts twelve chains on a local network
(P X C Q Z A B G K M F, plus D through its plugin) and reports healthy.
2026-09-07 16:15:25 -07:00

138 lines
4.1 KiB
Go

// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
// Operating a node.
//
// The service is a value with a registry of typed operations on it — see ops.go,
// which is where every operation and its doc live. This file is the value: what
// the service is built from, what it holds, and the two things more than one
// operation needs.
package admin
import (
"errors"
"path/filepath"
"sync"
apiadmin "github.com/luxfi/api/admin"
"github.com/luxfi/database"
"github.com/luxfi/ids"
"github.com/luxfi/log"
"github.com/luxfi/math/set"
"github.com/luxfi/node/chains"
nodeconfig "github.com/luxfi/node/config/node"
server "github.com/luxfi/node/server/http"
"github.com/luxfi/node/service/backup"
"github.com/luxfi/node/utils/profiler"
"github.com/luxfi/node/vms"
"github.com/luxfi/node/vms/registry"
)
const (
maxAliasLength = 512
// Name of file that stacktraces are written to.
stacktraceFile = "stacktrace.txt"
)
var (
errAliasTooLong = errors.New("alias length is too long")
errNoLogLevel = errors.New("need to specify either displayLevel or logLevel")
errNoLoggerName = errors.New("need to specify loggerName: loggers are addressed by name and cannot be enumerated")
)
// ChainTracker is the interface for tracking chains at runtime.
type ChainTracker interface {
TrackChain(chainID ids.ID) error
TrackedChains() set.Set[ids.ID]
}
type Config struct {
Log log.Logger
ProfileDir string
LogFactory log.Factory
// NodeConfig is the configuration this node booted with. It is the node's
// own type rather than an `any` because an operation's reply is its type:
// an untyped one has no schema, no MCP shape and no generated client. What
// may be DISCLOSED of it is a separate question with its own answer —
// `json:"-"` on the fields that hold key material, in config/node.
NodeConfig *nodeconfig.Config
DB database.Database
ChainManager chains.Manager
HTTPServer server.PathAdderWithReadLock
VMRegistry registry.VMRegistry
VMManager vms.Manager
PluginDir string
Network ChainTracker
// DataDir is the node's data directory, used for backup metadata.
DataDir string
}
// Service is the node's operator API. Its operations are registered by
// [Service.Ops].
type Service struct {
Config
lock sync.RWMutex
profiler profiler.Profiler
backupService *backup.Service
}
func New(config Config) *Service {
svc := &Service{
Config: config,
profiler: profiler.New(config.ProfileDir),
}
// Initialize backup service if data directory is configured
if config.DataDir != "" && config.DB != nil {
backupSvc, err := backup.New(backup.Config{
DB: config.DB,
MetadataDir: filepath.Join(config.DataDir, "backup"),
Log: config.Log,
})
if err != nil {
config.Log.Warn("failed to initialize backup service", "error", err)
} else {
svc.backupService = backupSvc
}
}
return svc
}
// loggerNames resolves the loggerName argument to the loggers to act on — the one
// place either logger-level operation decides what it is addressing.
//
// log.Factory addresses loggers BY NAME and exposes no enumeration, so the "every
// logger" form (an empty name) cannot be served. Refuse it explicitly: answering 200 OK
// while doing nothing is what made these endpoints unusable.
func (a *Service) loggerNames(loggerName string) ([]string, error) {
if loggerName == "" {
return nil, errNoLoggerName
}
return []string{loggerName}, nil
}
// logLevels reads the two levels each named logger is set to.
func (a *Service) logLevels(loggerNames []string) (apiadmin.LoggerLevels, error) {
levels := make(apiadmin.LoggerLevels, 0, len(loggerNames))
for _, name := range loggerNames {
logLevel, err := a.LogFactory.GetLogLevel(name)
if err != nil {
return nil, err
}
displayLevel, err := a.LogFactory.GetDisplayLevel(name)
if err != nil {
return nil, err
}
levels = append(levels, apiadmin.LoggerLevel{
Logger: name,
Levels: apiadmin.LogAndDisplayLevels{
LogLevel: logLevel.String(),
DisplayLevel: displayLevel.String(),
},
})
}
return levels, nil
}