Files
node/tests/fixture/tmpnet/network.go
T
Hanzo AI c3b398bc7b json: migrate every encoding/json import to json/v2 (go-json-experiment)
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)
2026-06-06 22:26:02 -07:00

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
}