mirror of
https://github.com/luxfi/node.git
synced 2026-07-27 03:39:39 +00:00
External (HTTP / JSON-RPC) is the only place JSON is legitimate. Every
existing encoding/json import in node/ moves to github.com/go-json-experiment/json
(v2 root, not the v1 sub-package). NewEncoder/NewDecoder rewrite to
MarshalWrite/UnmarshalRead. MarshalIndent rewrites to Marshal with
jsontext.WithIndent. json.RawMessage rewrites to jsontext.Value.
*json.SyntaxError rewrites to *jsontext.SyntacticError.
81 files migrated. LLM.md captures the rule + v1->v2 delta table.
Known v2 semantic deltas surfaced by existing tests (followups, not regressions):
- [N]byte fields with no MarshalJSON now marshal as base64 string (v1 marshalled
as JSON array of byte numbers). Affects vms/platformvm/txs/*_test.go fixtures
with embedded BLS proofOfPossession.
- time.Duration has no v2 default representation; configs that wire-format
Duration as nanoseconds (vms/{xvm,platformvm}/config, config/spec) need to
switch to string-form Duration or carry an explicit option. v2 root does not
re-export FormatDurationAsNano.
- v2 enforces strict UTF-8 (vms/chainadapter/messaging fixture has non-UTF-8).
- json.MarshalWrite does not append a trailing '\n' (v1 NewEncoder.Encode did);
service/auth/auth_test.go expectation updated.
- nil []byte round-trips to empty (not nil); config_test deep-equal fixtures
surface this.
All affected sites are at the API boundary; ZAP wire envelope already covers
the internal data paths (state, P2P, consensus, MPC, threshold). Internal
JSON sites that should move to ZAP next (separate work):
- vms/da/store.go (DA blob/cert storage as JSON)
- vms/platformvm/airdrop (airdrop claims as JSON in db)
- vms/chainadapter/appchain (SQLite materializer schema/data blobs)
- vms/chainadapter/messaging (conversation codec)
- staking/kms.go (KMS HTTP client — external technically, leave)
- utils/{bimap,ips} (small marshaler shims — low priority)
220 lines
5.6 KiB
Go
220 lines
5.6 KiB
Go
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package tmpnet
|
|
|
|
import (
|
|
"context"
|
|
"github.com/go-json-experiment/json"
|
|
"github.com/go-json-experiment/json/jsontext"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"time"
|
|
|
|
"github.com/luxfi/ids"
|
|
"github.com/luxfi/log"
|
|
)
|
|
|
|
const (
|
|
DefaultNetworkTimeout = 2 * time.Minute
|
|
)
|
|
|
|
// Flags is a map of node flags with helper methods
|
|
type Flags map[string]interface{}
|
|
|
|
// GetStringVal returns the string value for a flag, or empty string if not found or not a string
|
|
func (f Flags) GetStringVal(key string) string {
|
|
if v, ok := f[key]; ok {
|
|
if s, ok := v.(string); ok {
|
|
return s
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// Network represents a local test network
|
|
type Network struct {
|
|
UUID string
|
|
NetworkID uint32
|
|
Owner string
|
|
Dir string
|
|
Nodes []*Node
|
|
DefaultRuntimeConfig NodeRuntimeConfig
|
|
Genesis interface{} // Can be []byte or *genesis.UnparsedConfig
|
|
DefaultFlags Flags
|
|
|
|
// Track nets/chains
|
|
Nets []*Net
|
|
}
|
|
|
|
// Net represents a net (non-primary chain) in the network
|
|
type Net struct {
|
|
ChainID ids.ID
|
|
Chains []*Chain
|
|
ValidatorIDs []ids.NodeID
|
|
}
|
|
|
|
// Chain represents a blockchain in a net
|
|
type Chain struct {
|
|
ChainID ids.ID
|
|
VMID ids.ID
|
|
ChainName string
|
|
Genesis []byte
|
|
}
|
|
|
|
// NodeRuntimeConfig configures how nodes are run
|
|
type NodeRuntimeConfig struct {
|
|
Process *ProcessRuntimeConfig
|
|
}
|
|
|
|
// ProcessRuntimeConfig configures process execution
|
|
type ProcessRuntimeConfig struct {
|
|
LuxdPath string
|
|
LuxNodePath string // Alias for LuxdPath for CLI compatibility
|
|
PluginDir string
|
|
}
|
|
|
|
// ReadNetwork reads a network from its directory
|
|
func ReadNetwork(ctx context.Context, log log.Logger, networkDir string) (*Network, error) {
|
|
networkPath := filepath.Join(networkDir, "network.json")
|
|
data, err := os.ReadFile(networkPath)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to read network config: %w", err)
|
|
}
|
|
|
|
var network Network
|
|
if err := json.Unmarshal(data, &network); err != nil {
|
|
return nil, fmt.Errorf("failed to parse network config: %w", err)
|
|
}
|
|
|
|
network.Dir = networkDir
|
|
|
|
// Read nodes
|
|
nodesDir := filepath.Join(networkDir, "nodes")
|
|
entries, err := os.ReadDir(nodesDir)
|
|
if err != nil {
|
|
// No nodes directory is fine for new networks
|
|
return &network, nil
|
|
}
|
|
|
|
for _, entry := range entries {
|
|
if !entry.IsDir() {
|
|
continue
|
|
}
|
|
nodeDir := filepath.Join(nodesDir, entry.Name())
|
|
node, err := ReadNode(nodeDir)
|
|
if err != nil {
|
|
log.Warn("failed to read node", "dir", nodeDir, "error", err)
|
|
continue
|
|
}
|
|
network.Nodes = append(network.Nodes, node)
|
|
}
|
|
|
|
return &network, nil
|
|
}
|
|
|
|
// Write writes the network configuration to disk
|
|
func (n *Network) Write() error {
|
|
if n.Dir == "" {
|
|
return fmt.Errorf("network directory not set")
|
|
}
|
|
|
|
if err := os.MkdirAll(n.Dir, 0755); err != nil {
|
|
return fmt.Errorf("failed to create network directory: %w", err)
|
|
}
|
|
|
|
data, err := json.Marshal(n, jsontext.WithIndent(" "))
|
|
if err != nil {
|
|
return fmt.Errorf("failed to marshal network config: %w", err)
|
|
}
|
|
|
|
networkPath := filepath.Join(n.Dir, "network.json")
|
|
if err := os.WriteFile(networkPath, data, 0644); err != nil {
|
|
return fmt.Errorf("failed to write network config: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Start starts all nodes in the network
|
|
func (n *Network) Start(ctx context.Context, log log.Logger) error {
|
|
for _, node := range n.Nodes {
|
|
if err := node.Start(ctx, log); err != nil {
|
|
return fmt.Errorf("failed to start node %s: %w", node.NodeID, err)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Stop stops all nodes in the network
|
|
func (n *Network) Stop(ctx context.Context) error {
|
|
var lastErr error
|
|
for _, node := range n.Nodes {
|
|
if err := node.Stop(ctx); err != nil {
|
|
lastErr = err
|
|
}
|
|
}
|
|
return lastErr
|
|
}
|
|
|
|
// GetBootstrapIPsAndIDs returns the bootstrap IPs and IDs for the network
|
|
func (n *Network) GetBootstrapIPsAndIDs() ([]string, []string) {
|
|
var ips, nodeIDs []string
|
|
for _, node := range n.Nodes {
|
|
if node.URI != "" {
|
|
ips = append(ips, fmt.Sprintf("%s:%d", node.URI, node.StakingPort))
|
|
nodeIDs = append(nodeIDs, node.NodeID.String())
|
|
}
|
|
}
|
|
return ips, nodeIDs
|
|
}
|
|
|
|
// EnsureDefaultConfig ensures the network has default configuration
|
|
func (n *Network) EnsureDefaultConfig(log log.Logger) error {
|
|
if n.Dir == "" {
|
|
return fmt.Errorf("network directory not set")
|
|
}
|
|
if err := os.MkdirAll(n.Dir, 0755); err != nil {
|
|
return fmt.Errorf("failed to create network directory: %w", err)
|
|
}
|
|
if n.DefaultFlags == nil {
|
|
n.DefaultFlags = make(map[string]interface{})
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// EnsureNodeConfig ensures a node has proper configuration for this network
|
|
func (n *Network) EnsureNodeConfig(node *Node) error {
|
|
if node.DataDir == "" {
|
|
if n.Dir == "" {
|
|
return fmt.Errorf("network directory not set")
|
|
}
|
|
nodesDir := filepath.Join(n.Dir, "nodes")
|
|
if err := os.MkdirAll(nodesDir, 0755); err != nil {
|
|
return fmt.Errorf("failed to create nodes directory: %w", err)
|
|
}
|
|
node.DataDir = filepath.Join(nodesDir, node.NodeID.String())
|
|
}
|
|
if node.Flags == nil {
|
|
node.Flags = make(map[string]interface{})
|
|
}
|
|
// Copy default flags
|
|
for k, v := range n.DefaultFlags {
|
|
if _, exists := node.Flags[k]; !exists {
|
|
node.Flags[k] = v
|
|
}
|
|
}
|
|
// Set runtime config from network default
|
|
if node.RuntimeConfig == nil && n.DefaultRuntimeConfig.Process != nil {
|
|
node.RuntimeConfig = &NodeRuntimeConfig{
|
|
Process: &ProcessRuntimeConfig{
|
|
LuxdPath: n.DefaultRuntimeConfig.Process.LuxdPath,
|
|
LuxNodePath: n.DefaultRuntimeConfig.Process.LuxNodePath,
|
|
PluginDir: n.DefaultRuntimeConfig.Process.PluginDir,
|
|
},
|
|
}
|
|
}
|
|
return nil
|
|
}
|