chore: remove local replace directives and update dependencies

- Update github.com/luxfi/keys v1.0.5 → v1.0.6
- Update github.com/luxfi/genesis v1.5.18 → v1.5.19
- Remove local replace directives for keys and genesis
- Use published package versions for reproducible builds
This commit is contained in:
Zach Kelling
2026-01-03 07:05:17 -08:00
parent 5d4bdf3368
commit 4e2a4e432e
59 changed files with 909 additions and 916 deletions
+38 -101
View File
@@ -1,113 +1,50 @@
# https://golangci-lint.run/usage/configuration/
# golangci-lint v2 configuration
version: "2"
run:
timeout: 10m
# skip auto-generated files.
skip-files:
- ".*\\.pb\\.go$"
- ".*mock.*"
issues:
# Maximum count of issues with the same text. Set to 0 to disable. Default is 3.
max-same-issues: 0
formatters:
enable:
- gofmt
- goimports
linters:
# please, do not use `enable-all`: it's deprecated and will be removed soon.
# inverted configuration with `enable-all` and `disable` is not scalable during updates of golangci-lint
disable-all: true
default: none
enable:
- asciicheck
- depguard
- errcheck
- errorlint
- exportloopref
- goconst
- gocritic
- gofmt
- gofumpt
- goimports
- revive
- gosec
- gosimple
- govet
- ineffassign
- misspell
- nakedret
- nolintlint
- prealloc
- stylecheck
- unconvert
- unparam
- unused
- unconvert
- whitespace
- staticcheck
# - bodyclose
# - structcheck
# - lll
# - gomnd
# - goprintffuncname
# - interfacer
# - typecheck
# - goerr113
# - noctx
- misspell
linters-settings:
errorlint:
# Check for plain type assertions and type switches.
asserts: false
# Check for plain error comparisons.
comparison: false
revive:
settings:
staticcheck:
checks:
- "all"
- "-SA6002"
- "-SA1019"
- "-ST1000"
- "-ST1021"
- "-QF1008"
exclusions:
generated: lax
presets:
- comments
rules:
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#bool-literal-in-expr
- name: bool-literal-in-expr
disabled: false
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#early-return
- name: early-return
disabled: false
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#empty-lines
- name: empty-lines
disabled: false
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#struct-tag
- name: struct-tag
disabled: false
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#unexported-naming
- name: unexported-naming
disabled: false
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#unhandled-error
- name: unhandled-error
disabled: false
arguments:
- "fmt.Fprint"
- "fmt.Fprintf"
- "fmt.Print"
- "fmt.Printf"
- "fmt.Println"
- "rand.Read"
- "sb.WriteString"
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#unused-parameter
- name: unused-parameter
disabled: false
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#unused-receiver
- name: unused-receiver
disabled: false
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#useless-break
- name: useless-break
disabled: false
staticcheck:
go: "1.19"
# https://staticcheck.io/docs/options#checks
checks:
- "all"
- "-SA6002" # argument should be pointer-like to avoid allocation, for sync.Pool
- "-SA1019" # deprecated packages e.g., golang.org/x/crypto/ripemd160
# https://golangci-lint.run/usage/linters#gosec
gosec:
excludes:
- G107 # https://securego.io/docs/rules/g107.html
depguard:
list-type: blacklist
packages-with-error-message:
- io/ioutil: 'io/ioutil is deprecated. Use package io or os instead.'
- github.com/stretchr/testify/assert: 'github.com/stretchr/testify/require should be used instead.'
include-go-root: true
- path: ".*\\.pb\\.go$"
linters:
- all
- path: ".*mock.*"
linters:
- all
- path: _test\.go
linters:
- errcheck
- linters:
- errcheck
text: "Error return value of .*(Close|Flush|Fprintln|Fprintf|Print|Printf|Println|Write|Signal|Kill|RemoveAll|Sync|Sscanf|MarkFlagRequired).*is not checked"
issues:
max-same-issues: 0
+1 -1
View File
@@ -68,7 +68,7 @@ type Client interface {
PChainAPI() *platformvm.Client
XChainAPI() *exchangevm.Client
XChainWalletAPI() *exchangevm.WalletClient
CChainAPI() interface{} // evmclient.Client
CChainAPI() interface{} // evmclient.Client
CChainEthAPI() EthClient // ethclient websocket wrapper that adds mutexed calls, and lazy conn init (on first call)
InfoAPI() *info.Client
HealthAPI() HealthClient
+1 -1
View File
@@ -131,4 +131,4 @@ func (c *ethClientStub) SuggestGasTipCap(ctx context.Context) (*big.Int, error)
// TransactionReceipt stub implementation
func (c *ethClientStub) TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error) {
return nil, fmt.Errorf("TransactionReceipt not implemented in stub")
}
}
-1
View File
@@ -87,7 +87,6 @@ func (_m *EthClient) AcceptedNonceAt(_a0 context.Context, _a1 common.Address) (u
return r0, r1
}
// BalanceAt provides a mock function with given fields: _a0, _a1, _a2
func (_m *EthClient) BalanceAt(_a0 context.Context, _a1 common.Address, _a2 *big.Int) (*big.Int, error) {
ret := _m.Called(_a0, _a1, _a2)
+1 -1
View File
@@ -178,4 +178,4 @@ func (_m *Client) XChainWalletAPI() *exchangevm.WalletClient {
}
return r0
}
}
+21 -21
View File
@@ -12,9 +12,9 @@ import (
"sync"
"time"
"github.com/luxfi/log"
"github.com/luxfi/netrunner/local"
"github.com/luxfi/netrunner/rpcpb"
"github.com/luxfi/log"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/credentials/insecure"
@@ -59,7 +59,7 @@ type Client interface {
}
type client struct {
cfg Config
cfg Config
logger log.Logger
conn *grpc.ClientConn
@@ -88,7 +88,7 @@ func New(cfg Config, logger log.Logger) (Client, error) {
return &client{
cfg: cfg,
logger: logger,
logger: logger,
conn: conn,
pingc: rpcpb.NewPingServiceClient(conn),
controlc: rpcpb.NewControlServiceClient(conn),
@@ -114,11 +114,11 @@ func (c *client) Start(ctx context.Context, execPath string, opts ...OpOption) (
ret.applyOpts(opts)
req := &rpcpb.StartRequest{
ExecPath: execPath,
NumNodes: &ret.numNodes,
ChainConfigs: ret.chainConfigs,
UpgradeConfigs: ret.upgradeConfigs,
ChainConfigFiles: ret.chainConfigs,
ExecPath: execPath,
NumNodes: &ret.numNodes,
ChainConfigs: ret.chainConfigs,
UpgradeConfigs: ret.upgradeConfigs,
ChainConfigFiles: ret.chainConfigs,
}
if ret.trackChains != "" {
req.WhitelistedChains = &ret.trackChains
@@ -271,12 +271,12 @@ func (c *client) AddNode(ctx context.Context, name string, execPath string, opts
ret.applyOpts(opts)
req := &rpcpb.AddNodeRequest{
Name: name,
ExecPath: execPath,
NodeConfig: &ret.globalNodeConfig,
ChainConfigs: ret.chainConfigs,
UpgradeConfigs: ret.upgradeConfigs,
ChainConfigFiles: ret.chainConfigs,
Name: name,
ExecPath: execPath,
NodeConfig: &ret.globalNodeConfig,
ChainConfigs: ret.chainConfigs,
UpgradeConfigs: ret.upgradeConfigs,
ChainConfigFiles: ret.chainConfigs,
}
if ret.pluginDir != "" {
@@ -354,10 +354,10 @@ func (c *client) LoadSnapshot(ctx context.Context, snapshotName string, opts ...
ret := &Op{}
ret.applyOpts(opts)
req := rpcpb.LoadSnapshotRequest{
SnapshotName: snapshotName,
ChainConfigs: ret.chainConfigs,
UpgradeConfigs: ret.upgradeConfigs,
ChainConfigFiles: ret.chainConfigs,
SnapshotName: snapshotName,
ChainConfigs: ret.chainConfigs,
UpgradeConfigs: ret.upgradeConfigs,
ChainConfigFiles: ret.chainConfigs,
}
if ret.execPath != "" {
req.ExecPath = &ret.execPath
@@ -399,13 +399,13 @@ func (c *client) Close() error {
type Op struct {
numNodes uint32
execPath string
trackChains string
trackChains string
globalNodeConfig string
rootDataDir string
pluginDir string
chainSpecs []*rpcpb.BlockchainSpec
chainSpecs []*rpcpb.BlockchainSpec
customNodeConfigs map[string]string
numChains uint32
numChains uint32
chainConfigs map[string]string
upgradeConfigs map[string]string
pChainConfigs map[string]string
+2 -2
View File
@@ -40,7 +40,7 @@ func main() {
return nil
})
})
if err != nil {
fmt.Printf("Error: %v\n", err)
} else {
@@ -54,7 +54,7 @@ func main() {
opts.PrefetchValues = false
it := txn.NewIterator(opts)
defer it.Close()
prefix := []byte("n")
for it.Seek(prefix); it.ValidForPrefix(prefix); it.Next() {
key := it.Item().Key()
+6 -6
View File
@@ -14,14 +14,14 @@ import (
"syscall"
"time"
"github.com/luxfi/log"
"github.com/luxfi/log/level"
"github.com/luxfi/netrunner/client"
"github.com/luxfi/netrunner/local"
"github.com/luxfi/netrunner/rpcpb"
"github.com/luxfi/netrunner/utils"
"github.com/luxfi/netrunner/utils/constants"
"github.com/luxfi/netrunner/ux"
"github.com/luxfi/log"
"github.com/luxfi/log/level"
"github.com/spf13/cobra"
)
@@ -34,11 +34,11 @@ const clientRootDirPrefix = "client"
var (
logLevel string
logDir string
trackChains string
trackChains string
endpoint string
dialTimeout time.Duration
requestTimeout time.Duration
logger log.Logger
logger log.Logger
)
// NOTE: Naming convention for node names is currently `node` + number, i.e. `node1,node2,node3,...node101`
@@ -86,12 +86,12 @@ func NewCommand() *cobra.Command {
}
var (
luxdBinPath string
luxdBinPath string
numNodes uint32
pluginDir string
globalNodeConfig string
addNodeConfig string
chainSpecsStr string
chainSpecsStr string
customNodeConfigs string
rootDataDir string
chainConfigs string
+9 -3
View File
@@ -51,14 +51,14 @@ func main() {
for i, vk := range validators {
name := fmt.Sprintf("node%d", i)
fmt.Printf("--- Validator %d (%s) ---\n", i, name)
fmt.Printf(" NodeID: %s\n", vk.NodeID.String())
fmt.Printf(" P-Chain Addr: %s\n", vk.PChainAddr.String())
fmt.Printf(" C-Chain Addr: %s\n", vk.CChainAddrHex())
fmt.Printf(" BLS PubKey: %s\n", vk.BLSPublicKeyHex())
fmt.Printf(" BLS PoP: %s\n", vk.BLSPoPHex())
// Save to disk
if err := ks.Save(name, vk); err != nil {
fmt.Printf(" ERROR saving: %v\n", err)
@@ -117,6 +117,12 @@ func main() {
"proofOfPossession": "%s"
}
}%s
`, vk.NodeID.String(), vk.BLSPublicKeyHex(), vk.BLSPoPHex(), func() string { if i < 4 { return "," } else { return "" } }())
`, vk.NodeID.String(), vk.BLSPublicKeyHex(), vk.BLSPoPHex(), func() string {
if i < 4 {
return ","
} else {
return ""
}
}())
}
}
+14 -13
View File
@@ -4,9 +4,10 @@
package commands
import (
"github.com/luxfi/log"
"fmt"
"github.com/luxfi/log"
"github.com/spf13/cobra"
)
@@ -16,13 +17,13 @@ func NewBridgeCmd(logger log.Logger) *cobra.Command {
Use: "bridge",
Short: "Manage cross-chain bridges",
}
cmd.AddCommand(
newBridgeCreateCmd(logger),
newBridgeStatusCmd(logger),
newBridgeStopCmd(logger),
)
return cmd
}
@@ -32,16 +33,16 @@ func newBridgeCreateCmd(logger log.Logger) *cobra.Command {
source string
dest string
)
return &cobra.Command{
Use: "create",
Short: "Create a bridge between two chains",
RunE: func(cmd *cobra.Command, args []string) error {
fmt.Printf("🌉 Creating %s bridge from %s to %s...\n", bridgeType, source, dest)
// TODO: Implement bridge creation
fmt.Println("(Bridge creation not yet implemented)")
return nil
},
}
@@ -54,12 +55,12 @@ func newBridgeStatusCmd(logger log.Logger) *cobra.Command {
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
bridgeID := args[0]
fmt.Printf("📊 Status of bridge '%s':\n", bridgeID)
// TODO: Implement bridge status
fmt.Println("(Bridge status not yet implemented)")
return nil
},
}
@@ -72,13 +73,13 @@ func newBridgeStopCmd(logger log.Logger) *cobra.Command {
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
bridgeID := args[0]
fmt.Printf("⏹ Stopping bridge '%s'...\n", bridgeID)
// TODO: Implement bridge stopping
fmt.Println("(Bridge stopping not yet implemented)")
return nil
},
}
}
}
+40 -40
View File
@@ -4,7 +4,6 @@
package commands
import (
"github.com/luxfi/log"
"context"
"fmt"
"os"
@@ -12,8 +11,9 @@ import (
"text/tabwriter"
"time"
"github.com/luxfi/log"
"github.com/luxfi/netrunner/engines"
_ "github.com/luxfi/netrunner/engines/lux"
_ "github.com/luxfi/netrunner/engines/geth"
_ "github.com/luxfi/netrunner/engines/lux"
"github.com/spf13/cobra"
@@ -48,11 +48,11 @@ func newEngineStartCmd(logger log.Logger) *cobra.Command {
dataDir string
binary string
logLevel string
// OP Stack specific
l1RPC string
sequencer bool
// Eth2 specific
consensusClient string
executionClient string
@@ -65,7 +65,7 @@ func newEngineStartCmd(logger log.Logger) *cobra.Command {
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
name := args[0]
// Create engine configuration
config := &engines.NodeConfig{
NetworkID: networkID,
@@ -76,7 +76,7 @@ func newEngineStartCmd(logger log.Logger) *cobra.Command {
LogLevel: logLevel,
Extra: make(map[string]interface{}),
}
// Add engine-specific configuration
switch engineType {
case "op":
@@ -87,39 +87,39 @@ func newEngineStartCmd(logger log.Logger) *cobra.Command {
config.Extra["execution_client"] = executionClient
config.Extra["validator_enabled"] = validator
}
// Create the engine
engine, err := createEngine(engineType, name, binary)
if err != nil {
return fmt.Errorf("failed to create engine: %w", err)
}
// Start the engine
ctx := cmd.Context()
logger.Info("Starting engine",
logger.Info("Starting engine",
log.String("name", name),
log.String("type", engineType),
log.Uint32("network_id", networkID))
if err := engine.Start(ctx, config); err != nil {
return fmt.Errorf("failed to start engine: %w", err)
}
// Wait for engine to be healthy
logger.Info("Waiting for engine to be healthy...")
ticker := time.NewTicker(2 * time.Second)
defer ticker.Stop()
timeout := time.After(2 * time.Minute)
for {
select {
case <-ticker.C:
health, err := engine.Health(ctx)
if err == nil && health.Healthy {
logger.Info("Engine is healthy",
logger.Info("Engine is healthy",
log.String("name", name),
log.String("version", health.Version))
fmt.Printf("✅ Engine '%s' started successfully\n", name)
fmt.Printf(" Type: %s\n", engineType)
fmt.Printf(" RPC: %s\n", engine.RPCEndpoint())
@@ -137,7 +137,7 @@ func newEngineStartCmd(logger log.Logger) *cobra.Command {
}
},
}
// General flags
cmd.Flags().StringVarP(&engineType, "type", "t", "lux", "Engine type (lux, lux, geth, op, eth2)")
cmd.Flags().Uint32Var(&networkID, "network-id", 1337, "Network ID")
@@ -147,16 +147,16 @@ func newEngineStartCmd(logger log.Logger) *cobra.Command {
cmd.Flags().StringVar(&dataDir, "data-dir", "", "Data directory (auto-generated if empty)")
cmd.Flags().StringVar(&binary, "binary", "", "Path to engine binary")
cmd.Flags().StringVar(&logLevel, "log-level", "info", "Log level")
// OP Stack flags
cmd.Flags().StringVar(&l1RPC, "l1-rpc", "", "L1 RPC endpoint (OP Stack only)")
cmd.Flags().BoolVar(&sequencer, "sequencer", false, "Enable sequencer mode (OP Stack only)")
// Eth2 flags
cmd.Flags().StringVar(&consensusClient, "consensus-client", "lighthouse", "Consensus client (eth2 only)")
cmd.Flags().StringVar(&executionClient, "execution-client", "geth", "Execution client (eth2 only)")
cmd.Flags().BoolVar(&validator, "validator", false, "Enable validator (eth2 only)")
return cmd
}
@@ -167,10 +167,10 @@ func newEngineStopCmd(logger log.Logger) *cobra.Command {
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
name := args[0]
// TODO: Implement engine registry to track running engines
fmt.Printf("⏹ Stopping engine '%s'...\n", name)
return nil
},
}
@@ -183,10 +183,10 @@ func newEngineStatusCmd(logger log.Logger) *cobra.Command {
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
name := args[0]
// TODO: Implement engine registry
fmt.Printf("📊 Status of engine '%s':\n", name)
return nil
},
}
@@ -199,7 +199,7 @@ func newEngineListCmd(logger log.Logger) *cobra.Command {
RunE: func(cmd *cobra.Command, args []string) error {
fmt.Println("Available engine types:")
fmt.Println()
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
fmt.Fprintln(w, "TYPE\tDESCRIPTION\tSTATUS")
fmt.Fprintln(w, "----\t-----------\t------")
@@ -209,7 +209,7 @@ func newEngineListCmd(logger log.Logger) *cobra.Command {
fmt.Fprintln(w, "op\tOP Stack L2\t✅ Ready")
fmt.Fprintln(w, "eth2\tEthereum 2.0\t✅ Ready")
w.Flush()
return nil
},
}
@@ -223,7 +223,7 @@ func newEngineTestCmd(logger log.Logger) *cobra.Command {
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
engineType := args[0]
// Test configuration
config := &engines.NodeConfig{
NetworkID: 99999,
@@ -234,7 +234,7 @@ func newEngineTestCmd(logger log.Logger) *cobra.Command {
LogLevel: "debug",
Extra: make(map[string]interface{}),
}
// Add type-specific test config
switch engineType {
case "op":
@@ -246,24 +246,24 @@ func newEngineTestCmd(logger log.Logger) *cobra.Command {
config.Extra["execution_client"] = "geth"
config.Extra["network_name"] = "testnet"
}
// Create test engine
name := fmt.Sprintf("test-%s-%d", engineType, time.Now().Unix())
engine, err := createEngine(engineType, name, "")
if err != nil {
return fmt.Errorf("failed to create engine: %w", err)
}
// Start engine
ctx := cmd.Context()
logger.Info("Starting test engine",
logger.Info("Starting test engine",
log.String("type", engineType),
log.String("name", name))
if err := engine.Start(ctx, config); err != nil {
return fmt.Errorf("failed to start engine: %w", err)
}
// Defer cleanup
defer func() {
logger.Info("Cleaning up test engine")
@@ -272,11 +272,11 @@ func newEngineTestCmd(logger log.Logger) *cobra.Command {
}
os.RemoveAll(config.DataDir)
}()
// Test health check
fmt.Printf("🧪 Testing %s engine...\n", engineType)
fmt.Println(" ⏳ Waiting for engine to be healthy...")
// Poll for health
maxAttempts := 30
for i := 0; i < maxAttempts; i++ {
@@ -287,7 +287,7 @@ func newEngineTestCmd(logger log.Logger) *cobra.Command {
fmt.Printf(" 🔗 RPC: %s\n", engine.RPCEndpoint())
fmt.Printf(" 🌐 WebSocket: %s\n", engine.WSEndpoint())
fmt.Printf(" ⏱️ Uptime: %s\n", engine.Uptime())
// Type-specific tests
switch engineType {
case "lux":
@@ -306,14 +306,14 @@ func newEngineTestCmd(logger log.Logger) *cobra.Command {
fmt.Printf(" ⚙️ Execution: %s\n", ec)
}
}
fmt.Println("\n✅ Test passed!")
return nil
}
time.Sleep(2 * time.Second)
}
return fmt.Errorf("engine failed to become healthy after %d attempts", maxAttempts)
},
}
@@ -336,7 +336,7 @@ func createEngine(engineType, name, binary string) (engines.Engine, error) {
return nil, fmt.Errorf("unknown engine type: %s", engineType)
}
}
// For multi-binary engines, validate format
if strings.Contains(engineType, "op") || strings.Contains(engineType, "eth2") {
parts := strings.Split(binary, ":")
@@ -344,7 +344,7 @@ func createEngine(engineType, name, binary string) (engines.Engine, error) {
return nil, fmt.Errorf("%s requires two binaries separated by colon (e.g., 'op-node:op-geth')", engineType)
}
}
// Create engine using factory
return engines.New(engines.EngineType(engineType), name, binary)
}
}
+9 -8
View File
@@ -4,9 +4,10 @@
package commands
import (
"github.com/luxfi/log"
"fmt"
"github.com/luxfi/log"
"github.com/spf13/cobra"
)
@@ -16,25 +17,25 @@ func NewLogsCmd(logger log.Logger) *cobra.Command {
follow bool
tail int
)
cmd := &cobra.Command{
Use: "logs [engine]",
Short: "View engine logs",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
engineName := args[0]
fmt.Printf("📜 Logs for engine '%s':\n", engineName)
// TODO: Implement log streaming
fmt.Println("(Log streaming not yet implemented)")
return nil
},
}
cmd.Flags().BoolVarP(&follow, "follow", "f", false, "Follow log output")
cmd.Flags().IntVar(&tail, "tail", 100, "Number of lines to show")
return cmd
}
}
+67 -66
View File
@@ -4,13 +4,14 @@
package commands
import (
"github.com/luxfi/log"
"fmt"
"os"
"path/filepath"
"text/tabwriter"
"time"
"github.com/luxfi/log"
"github.com/luxfi/netrunner/orchestrator"
"github.com/spf13/cobra"
)
@@ -47,38 +48,38 @@ func newStackDeployCmd(logger log.Logger) *cobra.Command {
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
stackName := args[0]
// Load manifest
manifest, err := orchestrator.LoadManifest(manifestPath)
if err != nil {
return fmt.Errorf("failed to load manifest: %w", err)
}
// Validate manifest
if err := manifest.Validate(); err != nil {
return fmt.Errorf("invalid manifest: %w", err)
}
// Create orchestrator host
host := orchestrator.NewHost()
ctx := cmd.Context()
logger.Info("Deploying stack",
logger.Info("Deploying stack",
log.String("name", stackName),
log.String("manifest", manifestPath),
log.Int("engines", len(manifest.Engines)))
fmt.Printf("🚀 Deploying stack '%s' with %d engines...\n", stackName, len(manifest.Engines))
// Deploy engines in dependency order
deployed := make(map[string]bool)
remaining := make([]orchestrator.EngineConfig, len(manifest.Engines))
copy(remaining, manifest.Engines)
for len(remaining) > 0 {
progress := false
newRemaining := []orchestrator.EngineConfig{}
for _, engineCfg := range remaining {
// Check if dependencies are satisfied
ready := true
@@ -88,15 +89,15 @@ func newStackDeployCmd(logger log.Logger) *cobra.Command {
break
}
}
if !ready {
newRemaining = append(newRemaining, engineCfg)
continue
}
// Deploy this engine
fmt.Printf(" ⚙️ Starting %s engine '%s'...\n", engineCfg.Type, engineCfg.Name)
if err := host.StartEngine(ctx, engineCfg.Name, engineCfg.Type, &orchestrator.EngineOptions{
NetworkID: engineCfg.NetworkID,
HTTPPort: engineCfg.HTTPPort,
@@ -109,7 +110,7 @@ func newStackDeployCmd(logger log.Logger) *cobra.Command {
}); err != nil {
return fmt.Errorf("failed to start engine %s: %w", engineCfg.Name, err)
}
// Wait for health if requested
if engineCfg.WaitHealthy && wait {
fmt.Printf(" ⏳ Waiting for health check...\n")
@@ -118,43 +119,43 @@ func newStackDeployCmd(logger log.Logger) *cobra.Command {
}
fmt.Printf(" ✅ Healthy!\n")
}
deployed[engineCfg.Name] = true
progress = true
}
if !progress && len(newRemaining) > 0 {
return fmt.Errorf("circular dependency detected or unsatisfied dependencies")
}
remaining = newRemaining
}
// Deploy bridge if configured
if manifest.Bridge != nil {
fmt.Printf(" 🌉 Setting up bridge from %s to %s...\n",
fmt.Printf(" 🌉 Setting up bridge from %s to %s...\n",
manifest.Bridge.Source, manifest.Bridge.Destination)
if err := host.SetupBridge(ctx, manifest.Bridge); err != nil {
return fmt.Errorf("failed to setup bridge: %w", err)
}
}
fmt.Printf("\n✅ Stack '%s' deployed successfully!\n", stackName)
// Print endpoints
fmt.Println("\n📊 Engine Endpoints:")
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
fmt.Fprintln(w, "ENGINE\tTYPE\tRPC\tWEBSOCKET")
fmt.Fprintln(w, "------\t----\t---\t---------")
for _, engine := range manifest.Engines {
rpc := fmt.Sprintf("http://localhost:%d", engine.HTTPPort)
ws := fmt.Sprintf("ws://localhost:%d", engine.WSPort)
fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", engine.Name, engine.Type, rpc, ws)
}
w.Flush()
// Save stack info
stackDir := filepath.Join(os.Getenv("HOME"), ".netrunner", "stacks", stackName)
if err := os.MkdirAll(stackDir, 0755); err != nil {
@@ -165,23 +166,23 @@ func newStackDeployCmd(logger log.Logger) *cobra.Command {
if err := manifest.Save(manifestCopy); err != nil {
logger.Warn("Failed to save manifest copy", log.Err(err))
}
// Save host state
stateFile := filepath.Join(stackDir, "state.json")
if err := host.SaveState(stateFile); err != nil {
logger.Warn("Failed to save host state", log.Err(err))
}
}
return nil
},
}
cmd.Flags().StringVarP(&manifestPath, "manifest", "f", "", "Path to stack manifest file")
cmd.Flags().BoolVarP(&wait, "wait", "w", true, "Wait for engines to be healthy")
cmd.Flags().DurationVar(&timeout, "timeout", 5*time.Minute, "Deployment timeout")
cmd.MarkFlagRequired("manifest")
return cmd
}
@@ -192,29 +193,29 @@ func newStackDestroyCmd(logger log.Logger) *cobra.Command {
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
stackName := args[0]
// Load stack state
stackDir := filepath.Join(os.Getenv("HOME"), ".netrunner", "stacks", stackName)
stateFile := filepath.Join(stackDir, "state.json")
host := orchestrator.NewHost()
if err := host.LoadState(stateFile); err != nil {
return fmt.Errorf("failed to load stack state: %w", err)
}
ctx := cmd.Context()
fmt.Printf("🛑 Destroying stack '%s'...\n", stackName)
// Stop all engines
if err := host.StopAll(ctx); err != nil {
return fmt.Errorf("failed to stop engines: %w", err)
}
// Clean up stack directory
if err := os.RemoveAll(stackDir); err != nil {
logger.Warn("Failed to remove stack directory", log.Err(err))
}
fmt.Printf("✅ Stack '%s' destroyed\n", stackName)
return nil
},
@@ -228,35 +229,35 @@ func newStackStatusCmd(logger log.Logger) *cobra.Command {
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
stackName := args[0]
// Load stack state
stackDir := filepath.Join(os.Getenv("HOME"), ".netrunner", "stacks", stackName)
stateFile := filepath.Join(stackDir, "state.json")
host := orchestrator.NewHost()
if err := host.LoadState(stateFile); err != nil {
return fmt.Errorf("failed to load stack state: %w", err)
}
ctx := cmd.Context()
fmt.Printf("📊 Status of stack '%s':\n\n", stackName)
// Get status of all engines
statuses, err := host.GetAllStatus(ctx)
if err != nil {
return fmt.Errorf("failed to get status: %w", err)
}
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
fmt.Fprintln(w, "ENGINE\tTYPE\tSTATUS\tBLOCK HEIGHT\tPEERS\tVERSION")
fmt.Fprintln(w, "------\t----\t------\t------------\t-----\t-------")
for name, status := range statuses {
health := "❌ Unhealthy"
if status.Health.Healthy {
health = "✅ Healthy"
}
fmt.Fprintf(w, "%s\t%s\t%s\t%d\t%d\t%s\n",
name,
status.Type,
@@ -266,7 +267,7 @@ func newStackStatusCmd(logger log.Logger) *cobra.Command {
status.Health.Version)
}
w.Flush()
// Show metrics
fmt.Println("\n📈 Metrics:")
metrics := host.GetMetrics()
@@ -276,7 +277,7 @@ func newStackStatusCmd(logger log.Logger) *cobra.Command {
fmt.Printf(" %s: %v\n", k, v)
}
}
return nil
},
}
@@ -288,50 +289,50 @@ func newStackListCmd(logger log.Logger) *cobra.Command {
Short: "List all deployed stacks",
RunE: func(cmd *cobra.Command, args []string) error {
stacksDir := filepath.Join(os.Getenv("HOME"), ".netrunner", "stacks")
// Ensure directory exists
if _, err := os.Stat(stacksDir); os.IsNotExist(err) {
fmt.Println("No stacks deployed")
return nil
}
// List stack directories
entries, err := os.ReadDir(stacksDir)
if err != nil {
return fmt.Errorf("failed to list stacks: %w", err)
}
if len(entries) == 0 {
fmt.Println("No stacks deployed")
return nil
}
fmt.Println("Deployed stacks:")
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
fmt.Fprintln(w, "NAME\tCREATED\tENGINES")
fmt.Fprintln(w, "----\t-------\t-------")
for _, entry := range entries {
if !entry.IsDir() {
continue
}
info, _ := entry.Info()
// Try to load manifest to get engine count
manifestPath := filepath.Join(stacksDir, entry.Name(), "manifest.yaml")
engineCount := "?"
if manifest, err := orchestrator.LoadManifest(manifestPath); err == nil {
engineCount = fmt.Sprintf("%d", len(manifest.Engines))
}
fmt.Fprintf(w, "%s\t%s\t%s\n",
fmt.Fprintf(w, "%s\t%s\t%s\n",
entry.Name(),
info.ModTime().Format("2006-01-02 15:04"),
engineCount)
}
w.Flush()
return nil
},
}
@@ -344,39 +345,39 @@ func newStackValidateCmd(logger log.Logger) *cobra.Command {
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
manifestPath := args[0]
// Load manifest
manifest, err := orchestrator.LoadManifest(manifestPath)
if err != nil {
return fmt.Errorf("failed to load manifest: %w", err)
}
// Validate
if err := manifest.Validate(); err != nil {
fmt.Printf("❌ Manifest validation failed: %v\n", err)
return err
}
fmt.Printf("✅ Manifest is valid\n")
fmt.Printf("\n📋 Summary:\n")
fmt.Printf(" Name: %s\n", manifest.Name)
fmt.Printf(" Version: %s\n", manifest.Version)
fmt.Printf(" Engines: %d\n", len(manifest.Engines))
if manifest.Bridge != nil {
fmt.Printf(" Bridge: %s → %s\n", manifest.Bridge.Source, manifest.Bridge.Destination)
}
// Check for dependency cycles
fmt.Printf("\n🔍 Checking dependencies...\n")
visited := make(map[string]bool)
recStack := make(map[string]bool)
var hasCycle func(string) bool
hasCycle = func(name string) bool {
visited[name] = true
recStack[name] = true
// Find engine config
var engine *orchestrator.EngineConfig
for i := range manifest.Engines {
@@ -385,7 +386,7 @@ func newStackValidateCmd(logger log.Logger) *cobra.Command {
break
}
}
if engine != nil {
for _, dep := range engine.DependsOn {
if !visited[dep] {
@@ -397,11 +398,11 @@ func newStackValidateCmd(logger log.Logger) *cobra.Command {
}
}
}
recStack[name] = false
return false
}
for _, engine := range manifest.Engines {
if !visited[engine.Name] {
if hasCycle(engine.Name) {
@@ -410,10 +411,10 @@ func newStackValidateCmd(logger log.Logger) *cobra.Command {
}
}
}
fmt.Printf(" ✅ No circular dependencies\n")
return nil
},
}
}
}
+7 -6
View File
@@ -4,11 +4,12 @@
package commands
import (
"github.com/luxfi/log"
"fmt"
"os"
"text/tabwriter"
"github.com/luxfi/log"
"github.com/spf13/cobra"
)
@@ -20,22 +21,22 @@ func NewStatusCmd(logger log.Logger) *cobra.Command {
RunE: func(cmd *cobra.Command, args []string) error {
fmt.Println("🔍 Netrunner System Status")
fmt.Println()
// Show running engines
fmt.Println("Running Engines:")
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
fmt.Fprintln(w, "NAME\tTYPE\tSTATUS\tUPTIME\tRPC")
fmt.Fprintln(w, "----\t----\t------\t------\t---")
// TODO: Implement global registry
fmt.Fprintln(w, "(No engines running)")
w.Flush()
fmt.Println()
fmt.Println("Deployed Stacks: 0")
fmt.Println("Active Bridges: 0")
return nil
},
}
}
}
+29 -28
View File
@@ -4,11 +4,12 @@
package commands
import (
"github.com/luxfi/log"
"context"
"fmt"
"time"
"github.com/luxfi/log"
"github.com/luxfi/netrunner/engines"
"github.com/spf13/cobra"
)
@@ -20,13 +21,13 @@ func NewTestCmd(logger log.Logger) *cobra.Command {
Short: "Run integration tests",
Long: "Run various integration tests to verify engine functionality",
}
cmd.AddCommand(
newTestAllCmd(logger),
newTestEngineCmd(logger),
newTestStackCmd(logger),
)
return cmd
}
@@ -36,23 +37,23 @@ func newTestAllCmd(logger log.Logger) *cobra.Command {
Short: "Test all engine types",
RunE: func(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()
fmt.Println("🧪 Testing all engine types...")
fmt.Println()
engineTypes := []string{"lux", "lux", "geth", "op", "eth2"}
results := make(map[string]bool)
for _, engineType := range engineTypes {
fmt.Printf("Testing %s...\n", engineType)
// Skip engines that need additional setup
if engineType == "op" {
fmt.Println(" ⏭️ Skipping (requires L1)")
results[engineType] = false
continue
}
if err := testEngine(ctx, logger, engineType); err != nil {
fmt.Printf(" ❌ Failed: %v\n", err)
results[engineType] = false
@@ -62,7 +63,7 @@ func newTestAllCmd(logger log.Logger) *cobra.Command {
}
fmt.Println()
}
// Summary
fmt.Println("📊 Test Summary:")
passed := 0
@@ -74,13 +75,13 @@ func newTestAllCmd(logger log.Logger) *cobra.Command {
}
fmt.Printf(" %s: %s\n", engine, status)
}
fmt.Printf("\nTotal: %d/%d passed\n", passed, len(engineTypes))
if passed < len(engineTypes) {
return fmt.Errorf("some tests failed")
}
return nil
},
}
@@ -94,14 +95,14 @@ func newTestEngineCmd(logger log.Logger) *cobra.Command {
RunE: func(cmd *cobra.Command, args []string) error {
engineType := args[0]
ctx := cmd.Context()
fmt.Printf("🧪 Testing %s engine...\n", engineType)
if err := testEngine(ctx, logger, engineType); err != nil {
fmt.Printf("❌ Test failed: %v\n", err)
return err
}
fmt.Println("✅ Test passed!")
return nil
},
@@ -114,10 +115,10 @@ func newTestStackCmd(logger log.Logger) *cobra.Command {
Short: "Test multi-engine stack deployment",
RunE: func(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()
fmt.Println("🧪 Testing multi-engine stack...")
fmt.Println()
// Test simple two-engine stack
fmt.Println("1. Testing Lux + Geth stack...")
if err := testSimpleStack(ctx, logger); err != nil {
@@ -126,7 +127,7 @@ func newTestStackCmd(logger log.Logger) *cobra.Command {
}
fmt.Println("✅ Passed")
fmt.Println()
// Test dependency ordering
fmt.Println("2. Testing dependency ordering...")
if err := testDependencyStack(ctx, logger); err != nil {
@@ -134,7 +135,7 @@ func newTestStackCmd(logger log.Logger) *cobra.Command {
return err
}
fmt.Println("✅ Passed")
fmt.Println("\n✅ All stack tests passed!")
return nil
},
@@ -153,7 +154,7 @@ func testEngine(ctx context.Context, logger log.Logger, engineType string) error
LogLevel: "info",
Extra: make(map[string]interface{}),
}
// Type-specific config
var binary string
switch engineType {
@@ -172,26 +173,26 @@ func testEngine(ctx context.Context, logger log.Logger, engineType string) error
default:
return fmt.Errorf("unknown engine type: %s", engineType)
}
// Create engine
name := fmt.Sprintf("test-%s", engineType)
engine, err := engines.New(engines.EngineType(engineType), name, binary)
if err != nil {
return fmt.Errorf("failed to create engine: %w", err)
}
// Start engine
if err := engine.Start(ctx, config); err != nil {
return fmt.Errorf("failed to start engine: %w", err)
}
// Defer cleanup
defer func() {
if err := engine.Stop(context.Background()); err != nil {
logger.Error("Failed to stop test engine", log.Err(err))
}
}()
// Wait for health
maxAttempts := 30
for i := 0; i < maxAttempts; i++ {
@@ -207,10 +208,10 @@ func testEngine(ctx context.Context, logger log.Logger, engineType string) error
if engine.NetworkID() != config.NetworkID {
return fmt.Errorf("network ID mismatch")
}
return nil
}
select {
case <-ctx.Done():
return ctx.Err()
@@ -218,7 +219,7 @@ func testEngine(ctx context.Context, logger log.Logger, engineType string) error
// Continue
}
}
return fmt.Errorf("engine failed to become healthy")
}
@@ -232,4 +233,4 @@ func testSimpleStack(ctx context.Context, logger log.Logger) error {
func testDependencyStack(ctx context.Context, logger log.Logger) error {
// TODO: Implement dependency stack test
return nil
}
}
+1 -1
View File
@@ -66,4 +66,4 @@ OP Stack, and more.`,
logger.Error("Command failed", log.Err(err))
os.Exit(1)
}
}
}
+2 -2
View File
@@ -7,11 +7,11 @@ import (
"context"
"time"
"github.com/luxfi/log"
"github.com/luxfi/log/level"
"github.com/luxfi/netrunner/client"
"github.com/luxfi/netrunner/utils/constants"
"github.com/luxfi/netrunner/ux"
"github.com/luxfi/log"
"github.com/luxfi/log/level"
"github.com/spf13/cobra"
)
+2 -2
View File
@@ -11,11 +11,11 @@ import (
"syscall"
"time"
"github.com/luxfi/log"
"github.com/luxfi/log/level"
"github.com/luxfi/netrunner/server"
"github.com/luxfi/netrunner/utils"
"github.com/luxfi/netrunner/utils/constants"
"github.com/luxfi/log"
"github.com/luxfi/log/level"
"github.com/spf13/cobra"
)
+54 -54
View File
@@ -25,15 +25,15 @@ func init() {
// LuxEngine wraps luxd node management
type LuxEngine struct {
name string
binary string
dataDir string
config *engines.NodeConfig
process *exec.Cmd
httpClient *http.Client
rpcEndpoint string
startTime time.Time
name string
binary string
dataDir string
config *engines.NodeConfig
process *exec.Cmd
httpClient *http.Client
rpcEndpoint string
startTime time.Time
// Cached info
networkID uint32
chainID ids.ID
@@ -50,26 +50,26 @@ func NewLuxEngine(name string, binary string) (engines.Engine, error) {
}, nil
}
func (e *LuxEngine) Name() string { return e.name }
func (e *LuxEngine) Type() engines.EngineType { return engines.EngineLux }
func (e *LuxEngine) NetworkID() uint32 { return e.networkID }
func (e *LuxEngine) ChainID() ids.ID { return e.chainID }
func (e *LuxEngine) Name() string { return e.name }
func (e *LuxEngine) Type() engines.EngineType { return engines.EngineLux }
func (e *LuxEngine) NetworkID() uint32 { return e.networkID }
func (e *LuxEngine) ChainID() ids.ID { return e.chainID }
func (e *LuxEngine) ParentChain() *engines.ChainInfo { return nil } // L1
func (e *LuxEngine) Start(ctx context.Context, config *engines.NodeConfig) error {
e.config = config
e.networkID = config.NetworkID
// Setup data directory
if config.DataDir == "" {
config.DataDir = filepath.Join(os.TempDir(), "lux", e.name)
}
e.dataDir = config.DataDir
if err := os.MkdirAll(e.dataDir, 0755); err != nil {
return fmt.Errorf("failed to create data dir: %w", err)
}
// Build command arguments
args := []string{
fmt.Sprintf("--network-id=%s", getLuxNetwork(config.NetworkID)),
@@ -81,23 +81,23 @@ func (e *LuxEngine) Start(ctx context.Context, config *engines.NodeConfig) error
"--http-allowed-hosts=*",
"--http-allowed-origins=*",
}
// Add bootstrap nodes if custom network
if len(config.BootstrapIPs) > 0 {
for _, ip := range config.BootstrapIPs {
args = append(args, fmt.Sprintf("--bootstrap-ips=%s", ip))
}
}
// Add extra configs
for k, v := range config.Extra {
args = append(args, fmt.Sprintf("--%s=%v", k, v))
}
// Start the process
e.process = exec.CommandContext(ctx, e.binary, args...)
e.process.Dir = e.dataDir
// Setup logs
logFile, err := os.Create(filepath.Join(e.dataDir, "node.log"))
if err != nil {
@@ -105,19 +105,19 @@ func (e *LuxEngine) Start(ctx context.Context, config *engines.NodeConfig) error
}
e.process.Stdout = logFile
e.process.Stderr = logFile
if err := e.process.Start(); err != nil {
return fmt.Errorf("failed to start luxd: %w", err)
}
e.startTime = time.Now()
e.rpcEndpoint = e.RPCEndpoint()
e.httpClient = &http.Client{Timeout: 10 * time.Second}
// Wait for node to be responsive
ticker := time.NewTicker(500 * time.Millisecond)
defer ticker.Stop()
timeout := time.After(60 * time.Second) // Lux can take longer
for {
select {
@@ -141,17 +141,17 @@ func (e *LuxEngine) Stop(ctx context.Context) error {
if e.process == nil || e.process.Process == nil {
return nil
}
// Try graceful shutdown first
if err := e.process.Process.Signal(os.Interrupt); err != nil {
return e.process.Process.Kill()
}
done := make(chan error, 1)
go func() {
done <- e.process.Wait()
}()
select {
case <-done:
return nil
@@ -174,23 +174,23 @@ func (e *LuxEngine) Health(ctx context.Context) (*engines.HealthStatus, error) {
if e.httpClient == nil {
return &engines.HealthStatus{Healthy: false}, nil
}
// Get node health
healthy, err := e.getHealth(ctx)
if err != nil {
return &engines.HealthStatus{Healthy: false}, nil
}
// Get peers
peerCount, _ := e.getPeerCount(ctx)
// Get version
version, _ := e.getNodeVersion(ctx)
return &engines.HealthStatus{
Healthy: healthy,
PeerCount: peerCount,
Version: version,
Healthy: healthy,
PeerCount: peerCount,
Version: version,
// BlockHeight from C-Chain would require eth client
}, nil
}
@@ -262,29 +262,29 @@ func (e *LuxEngine) callRPC(ctx context.Context, endpoint string, method string,
"method": method,
"params": params,
}
data, err := json.Marshal(reqBody)
if err != nil {
return nil, err
}
req, err := http.NewRequestWithContext(ctx, "POST", e.rpcEndpoint+endpoint, bytes.NewReader(data))
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/json")
resp, err := e.httpClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
var result struct {
Result json.RawMessage `json:"result"`
Error *struct {
@@ -292,15 +292,15 @@ func (e *LuxEngine) callRPC(ctx context.Context, endpoint string, method string,
Message string `json:"message"`
} `json:"error"`
}
if err := json.Unmarshal(body, &result); err != nil {
return nil, err
}
if result.Error != nil {
return nil, fmt.Errorf("RPC error %d: %s", result.Error.Code, result.Error.Message)
}
return result.Result, nil
}
@@ -309,14 +309,14 @@ func (e *LuxEngine) getNodeID(ctx context.Context) (string, error) {
if err != nil {
return "", err
}
var resp struct {
NodeID string `json:"nodeID"`
}
if err := json.Unmarshal(result, &resp); err != nil {
return "", err
}
return resp.NodeID, nil
}
@@ -325,14 +325,14 @@ func (e *LuxEngine) getBlockchainID(ctx context.Context, alias string) (ids.ID,
if err != nil {
return ids.Empty, err
}
var resp struct {
BlockchainID string `json:"blockchainID"`
}
if err := json.Unmarshal(result, &resp); err != nil {
return ids.Empty, err
}
return ids.FromString(resp.BlockchainID)
}
@@ -341,14 +341,14 @@ func (e *LuxEngine) getHealth(ctx context.Context) (bool, error) {
if err != nil {
return false, err
}
var resp struct {
Healthy bool `json:"healthy"`
}
if err := json.Unmarshal(result, &resp); err != nil {
return false, err
}
return resp.Healthy, nil
}
@@ -357,9 +357,9 @@ func (e *LuxEngine) getPeerCount(ctx context.Context) (int, error) {
if err != nil {
return 0, err
}
var resp struct {
NumPeers int `json:"numPeers"`
NumPeers int `json:"numPeers"`
Peers []interface{} `json:"peers"`
}
if err := json.Unmarshal(result, &resp); err != nil {
@@ -370,7 +370,7 @@ func (e *LuxEngine) getPeerCount(ctx context.Context) (int, error) {
}
return len(peers), nil
}
if resp.NumPeers > 0 {
return resp.NumPeers, nil
}
@@ -382,13 +382,13 @@ func (e *LuxEngine) getNodeVersion(ctx context.Context) (string, error) {
if err != nil {
return "", err
}
var resp struct {
Version string `json:"version"`
}
if err := json.Unmarshal(result, &resp); err != nil {
return "", err
}
return resp.Version, nil
}
}
+17 -15
View File
@@ -53,18 +53,20 @@ type mockEngine struct {
name string
}
func (m *mockEngine) Name() string { return m.name }
func (m *mockEngine) Type() EngineType { return "test" }
func (m *mockEngine) NetworkID() uint32 { return 1337 }
func (m *mockEngine) ChainID() ids.ID { return ids.Empty }
func (m *mockEngine) ParentChain() *ChainInfo { return nil }
func (m *mockEngine) Start(_ context.Context, _ *NodeConfig) error { return nil }
func (m *mockEngine) Stop(_ context.Context) error { return nil }
func (m *mockEngine) Restart(_ context.Context) error { return nil }
func (m *mockEngine) Health(_ context.Context) (*HealthStatus, error) { return &HealthStatus{Healthy: true}, nil }
func (m *mockEngine) IsRunning() bool { return true }
func (m *mockEngine) Uptime() time.Duration { return 0 }
func (m *mockEngine) RPCEndpoint() string { return "http://localhost:8545" }
func (m *mockEngine) WSEndpoint() string { return "ws://localhost:8546" }
func (m *mockEngine) P2PEndpoint() string { return "localhost:30303" }
func (m *mockEngine) Metrics() map[string]interface{} { return map[string]interface{}{} }
func (m *mockEngine) Name() string { return m.name }
func (m *mockEngine) Type() EngineType { return "test" }
func (m *mockEngine) NetworkID() uint32 { return 1337 }
func (m *mockEngine) ChainID() ids.ID { return ids.Empty }
func (m *mockEngine) ParentChain() *ChainInfo { return nil }
func (m *mockEngine) Start(_ context.Context, _ *NodeConfig) error { return nil }
func (m *mockEngine) Stop(_ context.Context) error { return nil }
func (m *mockEngine) Restart(_ context.Context) error { return nil }
func (m *mockEngine) Health(_ context.Context) (*HealthStatus, error) {
return &HealthStatus{Healthy: true}, nil
}
func (m *mockEngine) IsRunning() bool { return true }
func (m *mockEngine) Uptime() time.Duration { return 0 }
func (m *mockEngine) RPCEndpoint() string { return "http://localhost:8545" }
func (m *mockEngine) WSEndpoint() string { return "ws://localhost:8546" }
func (m *mockEngine) P2PEndpoint() string { return "localhost:30303" }
func (m *mockEngine) Metrics() map[string]interface{} { return map[string]interface{}{} }
+38 -38
View File
@@ -23,17 +23,17 @@ import (
// Eth2Engine implements Ethereum 2.0 consensus (Beacon Chain + Execution Layer)
type Eth2Engine struct {
mu sync.RWMutex
name string
beaconBinary string // Consensus client (e.g., Prysm, Lighthouse, Teku)
executionBinary string // Execution client (e.g., Geth, Erigon, Nethermind)
beaconCmd *exec.Cmd
executionCmd *exec.Cmd
config *NodeConfig
startTime time.Time
running bool
dataDir string
mu sync.RWMutex
name string
beaconBinary string // Consensus client (e.g., Prysm, Lighthouse, Teku)
executionBinary string // Execution client (e.g., Geth, Erigon, Nethermind)
beaconCmd *exec.Cmd
executionCmd *exec.Cmd
config *NodeConfig
startTime time.Time
running bool
dataDir string
// Eth2 specific
chainID uint64
networkName string
@@ -66,27 +66,27 @@ const (
// Eth2Config contains Ethereum 2.0 specific configuration
type Eth2Config struct {
ChainID uint64 `json:"chain_id"`
NetworkName string `json:"network_name"`
ConsensusClient ConsensusClient `json:"consensus_client"`
ExecutionClient ExecutionClient `json:"execution_client"`
ValidatorEnabled bool `json:"validator_enabled"`
ValidatorKeys []string `json:"validator_keys,omitempty"`
InitialValidators []string `json:"initial_validators,omitempty"`
TerminalTotalDifficulty string `json:"terminal_total_difficulty"`
GenesisTime uint64 `json:"genesis_time"`
ChainID uint64 `json:"chain_id"`
NetworkName string `json:"network_name"`
ConsensusClient ConsensusClient `json:"consensus_client"`
ExecutionClient ExecutionClient `json:"execution_client"`
ValidatorEnabled bool `json:"validator_enabled"`
ValidatorKeys []string `json:"validator_keys,omitempty"`
InitialValidators []string `json:"initial_validators,omitempty"`
TerminalTotalDifficulty string `json:"terminal_total_difficulty"`
GenesisTime uint64 `json:"genesis_time"`
}
// BeaconNodeStatus represents beacon chain status
type BeaconNodeStatus struct {
HeadSlot uint64 `json:"head_slot"`
SyncDistance uint64 `json:"sync_distance"`
IsSyncing bool `json:"is_syncing"`
IsOptimistic bool `json:"is_optimistic"`
ElOffline bool `json:"el_offline"`
CurrentEpoch uint64 `json:"current_epoch"`
FinalizedEpoch uint64 `json:"finalized_epoch"`
JustifiedEpoch uint64 `json:"justified_epoch"`
HeadSlot uint64 `json:"head_slot"`
SyncDistance uint64 `json:"sync_distance"`
IsSyncing bool `json:"is_syncing"`
IsOptimistic bool `json:"is_optimistic"`
ElOffline bool `json:"el_offline"`
CurrentEpoch uint64 `json:"current_epoch"`
FinalizedEpoch uint64 `json:"finalized_epoch"`
JustifiedEpoch uint64 `json:"justified_epoch"`
}
// NewEth2Engine creates a new Ethereum 2.0 engine
@@ -185,7 +185,7 @@ func (e *Eth2Engine) loadEth2Config() error {
func (e *Eth2Engine) generateJWTSecret() error {
jwtPath := filepath.Join(e.dataDir, "jwt.hex")
// Check if already exists
if _, err := os.Stat(jwtPath); err == nil {
data, err := os.ReadFile(jwtPath)
@@ -201,14 +201,14 @@ func (e *Eth2Engine) generateJWTSecret() error {
if _, err := rand.Read(secret); err != nil {
return fmt.Errorf("failed to generate random JWT secret: %w", err)
}
e.jwtSecret = hex.EncodeToString(secret)
return os.WriteFile(jwtPath, []byte(e.jwtSecret), 0600)
}
func (e *Eth2Engine) startExecutionClient(ctx context.Context) error {
var args []string
switch e.executionClient {
case Geth:
args = e.getGethArgs()
@@ -305,7 +305,7 @@ func (e *Eth2Engine) getBesuArgs() []string {
func (e *Eth2Engine) startConsensusClient(ctx context.Context) error {
var args []string
switch e.consensusClient {
case Prysm:
args = e.getPrysmArgs()
@@ -424,7 +424,7 @@ func (e *Eth2Engine) Stop(ctx context.Context) error {
go func() {
done <- e.beaconCmd.Wait()
}()
select {
case <-done:
case <-time.After(10 * time.Second):
@@ -446,7 +446,7 @@ func (e *Eth2Engine) stopExecutionClient() {
go func() {
done <- e.executionCmd.Wait()
}()
select {
case <-done:
case <-time.After(10 * time.Second):
@@ -499,7 +499,7 @@ func (e *Eth2Engine) Health(ctx context.Context) (*HealthStatus, error) {
func (e *Eth2Engine) getBeaconNodeStatus() (*BeaconNodeStatus, error) {
url := fmt.Sprintf("http://localhost:%d/eth/v1/node/syncing", e.config.HTTPPort+2000)
client := &http.Client{Timeout: 5 * time.Second}
resp, err := client.Get(url)
if err != nil {
@@ -578,7 +578,7 @@ func (e *Eth2Engine) ParentChain() *ChainInfo {
func (e *Eth2Engine) Metrics() map[string]interface{} {
e.mu.RLock()
defer e.mu.RUnlock()
metrics := map[string]interface{}{
"running": e.running,
"uptime": e.Uptime().Seconds(),
@@ -589,7 +589,7 @@ func (e *Eth2Engine) Metrics() map[string]interface{} {
"network": e.networkName,
"validator": e.validatorEnabled,
}
return metrics
}
@@ -605,4 +605,4 @@ func Eth2Factory(name string, binary string) (Engine, error) {
func init() {
Register(EngineEth2, Eth2Factory)
}
}
+29 -29
View File
@@ -33,7 +33,7 @@ type GethEngine struct {
config *engines.NodeConfig
process *exec.Cmd
startTime time.Time
// Cached info
networkID uint32
chainID ids.ID
@@ -50,32 +50,32 @@ func NewGethEngine(name string, binary string) (engines.Engine, error) {
}, nil
}
func (e *GethEngine) Name() string { return e.name }
func (e *GethEngine) Type() engines.EngineType { return engines.EngineGeth }
func (e *GethEngine) NetworkID() uint32 { return e.networkID }
func (e *GethEngine) ChainID() ids.ID { return e.chainID }
func (e *GethEngine) Name() string { return e.name }
func (e *GethEngine) Type() engines.EngineType { return engines.EngineGeth }
func (e *GethEngine) NetworkID() uint32 { return e.networkID }
func (e *GethEngine) ChainID() ids.ID { return e.chainID }
func (e *GethEngine) ParentChain() *engines.ChainInfo { return nil } // L1
func (e *GethEngine) Start(ctx context.Context, config *engines.NodeConfig) error {
e.config = config
e.networkID = config.NetworkID
// Generate chain ID from network ID
chainIDBytes := make([]byte, 32)
chainIDBig := big.NewInt(int64(config.NetworkID))
chainIDBig.FillBytes(chainIDBytes)
copy(e.chainID[:], chainIDBytes)
// Setup data directory
if config.DataDir == "" {
config.DataDir = filepath.Join(os.TempDir(), "geth", e.name)
}
e.dataDir = config.DataDir
if err := os.MkdirAll(e.dataDir, 0755); err != nil {
return fmt.Errorf("failed to create data dir: %w", err)
}
// Initialize genesis if needed
if _, err := os.Stat(filepath.Join(e.dataDir, "geth", "chaindata")); os.IsNotExist(err) {
// Create simple genesis
@@ -99,19 +99,19 @@ func (e *GethEngine) Start(ctx context.Context, config *engines.NodeConfig) erro
"gasLimit": "0x8000000",
"alloc": {}
}`, config.NetworkID)
genesisPath := filepath.Join(e.dataDir, "genesis.json")
if err := os.WriteFile(genesisPath, []byte(genesis), 0644); err != nil {
return fmt.Errorf("failed to write genesis: %w", err)
}
// Initialize geth with genesis
initCmd := exec.CommandContext(ctx, e.binary, "init", genesisPath, "--datadir", e.dataDir)
if err := initCmd.Run(); err != nil {
return fmt.Errorf("failed to init geth: %w", err)
}
}
// Build command arguments
args := []string{
"--datadir", e.dataDir,
@@ -133,17 +133,17 @@ func (e *GethEngine) Start(ctx context.Context, config *engines.NodeConfig) erro
"--syncmode", "full",
"--gcmode", "archive",
}
// Add dev mode for testing
if config.NetworkID > 90000 {
args = append(args, "--dev", "--dev.period", "1")
}
// Add bootstrap nodes
for _, bootnode := range config.BootstrapIPs {
args = append(args, "--bootnodes", bootnode)
}
// Add extra configs
for k, v := range config.Extra {
switch k {
@@ -157,11 +157,11 @@ func (e *GethEngine) Start(ctx context.Context, config *engines.NodeConfig) erro
args = append(args, "--password", v.(string))
}
}
// Start the process
e.process = exec.CommandContext(ctx, e.binary, args...)
e.process.Dir = e.dataDir
// Setup logs
logFile, err := os.Create(filepath.Join(e.dataDir, "geth.log"))
if err != nil {
@@ -169,17 +169,17 @@ func (e *GethEngine) Start(ctx context.Context, config *engines.NodeConfig) erro
}
e.process.Stdout = logFile
e.process.Stderr = logFile
if err := e.process.Start(); err != nil {
return fmt.Errorf("failed to start geth: %w", err)
}
e.startTime = time.Now()
// Wait for node to be responsive
ticker := time.NewTicker(500 * time.Millisecond)
defer ticker.Stop()
timeout := time.After(30 * time.Second)
for {
select {
@@ -200,17 +200,17 @@ func (e *GethEngine) Stop(ctx context.Context) error {
if e.process == nil || e.process.Process == nil {
return nil
}
// Try graceful shutdown first
if err := e.process.Process.Signal(os.Interrupt); err != nil {
return e.process.Process.Kill()
}
done := make(chan error, 1)
go func() {
done <- e.process.Wait()
}()
select {
case <-done:
return nil
@@ -233,10 +233,10 @@ func (e *GethEngine) Health(ctx context.Context) (*engines.HealthStatus, error)
if !e.IsRunning() {
return &engines.HealthStatus{Healthy: false}, nil
}
// Check if RPC is responsive
healthy := e.checkRPC()
return &engines.HealthStatus{
Healthy: healthy,
PeerCount: 0, // Would need JSON-RPC client to get real peer count
@@ -292,11 +292,11 @@ func (e *GethEngine) checkRPC() bool {
if e.process == nil || e.process.Process == nil {
return false
}
// Check if process is still alive
if err := e.process.Process.Signal(os.Signal(nil)); err != nil {
return false
}
return true
}
}
+28 -28
View File
@@ -36,7 +36,7 @@ type LuxEngine struct {
infoClient *info.Client
healthClient *health.Client
startTime time.Time
// Cached info
networkID uint32
chainID ids.ID
@@ -53,26 +53,26 @@ func NewLuxEngine(name string, binary string) (engines.Engine, error) {
}, nil
}
func (e *LuxEngine) Name() string { return e.name }
func (e *LuxEngine) Type() engines.EngineType { return engines.EngineLux }
func (e *LuxEngine) NetworkID() uint32 { return e.networkID }
func (e *LuxEngine) ChainID() ids.ID { return e.chainID }
func (e *LuxEngine) Name() string { return e.name }
func (e *LuxEngine) Type() engines.EngineType { return engines.EngineLux }
func (e *LuxEngine) NetworkID() uint32 { return e.networkID }
func (e *LuxEngine) ChainID() ids.ID { return e.chainID }
func (e *LuxEngine) ParentChain() *engines.ChainInfo { return nil } // L1
func (e *LuxEngine) Start(ctx context.Context, config *engines.NodeConfig) error {
e.config = config
e.networkID = config.NetworkID
// Setup data directory
if config.DataDir == "" {
config.DataDir = filepath.Join(os.TempDir(), "lux", e.name)
}
e.dataDir = config.DataDir
if err := os.MkdirAll(e.dataDir, 0755); err != nil {
return fmt.Errorf("failed to create data dir: %w", err)
}
// Build command arguments
args := []string{
fmt.Sprintf("--network-id=%d", config.NetworkID),
@@ -83,23 +83,23 @@ func (e *LuxEngine) Start(ctx context.Context, config *engines.NodeConfig) error
"--http-host=0.0.0.0",
"--http-allowed-hosts=*",
}
// Add bootstrap nodes
if len(config.BootstrapIPs) > 0 {
for _, ip := range config.BootstrapIPs {
args = append(args, fmt.Sprintf("--bootstrap-ips=%s", ip))
}
}
// Add extra configs
for k, v := range config.Extra {
args = append(args, fmt.Sprintf("--%s=%v", k, v))
}
// Start the process
e.process = exec.CommandContext(ctx, e.binary, args...)
e.process.Dir = e.dataDir
// Setup logs
logFile, err := os.Create(filepath.Join(e.dataDir, "node.log"))
if err != nil {
@@ -107,21 +107,21 @@ func (e *LuxEngine) Start(ctx context.Context, config *engines.NodeConfig) error
}
e.process.Stdout = logFile
e.process.Stderr = logFile
if err := e.process.Start(); err != nil {
return fmt.Errorf("failed to start luxd: %w", err)
}
e.startTime = time.Now()
// Setup RPC clients
e.infoClient = info.NewClient(e.RPCEndpoint())
e.healthClient = health.NewClient(e.RPCEndpoint())
// Wait for node to be responsive
ticker := time.NewTicker(500 * time.Millisecond)
defer ticker.Stop()
timeout := time.After(30 * time.Second)
for {
select {
@@ -145,17 +145,17 @@ func (e *LuxEngine) Stop(ctx context.Context) error {
if e.process == nil || e.process.Process == nil {
return nil
}
// Try graceful shutdown first
if err := e.process.Process.Signal(os.Interrupt); err != nil {
return e.process.Process.Kill()
}
done := make(chan error, 1)
go func() {
done <- e.process.Wait()
}()
select {
case <-done:
return nil
@@ -178,29 +178,29 @@ func (e *LuxEngine) Health(ctx context.Context) (*engines.HealthStatus, error) {
if e.healthClient == nil || e.infoClient == nil {
return &engines.HealthStatus{Healthy: false}, nil
}
// Get node health
healthResp, err := e.healthClient.Health(ctx, nil)
if err != nil {
return &engines.HealthStatus{Healthy: false}, nil
}
// Get peers
peers, err := e.infoClient.Peers(ctx, nil)
if err != nil {
peers = []info.Peer{}
}
// Get version
versions, err := e.infoClient.GetNodeVersion(ctx)
if err != nil {
versions = &info.GetNodeVersionReply{}
}
return &engines.HealthStatus{
Healthy: healthResp.Healthy,
PeerCount: len(peers),
Version: versions.Version,
Healthy: healthResp.Healthy,
PeerCount: len(peers),
Version: versions.Version,
// BlockHeight from C-Chain would require eth client
}, nil
}
@@ -248,4 +248,4 @@ func (e *LuxEngine) Metrics() map[string]interface{} {
"network_id": e.networkID,
"chain_id": e.chainID.String(),
}
}
}
+19 -19
View File
@@ -29,7 +29,7 @@ type OPStackEngine struct {
startTime time.Time
running bool
dataDir string
// OP Stack specific
l1RPC string
sequencer bool
@@ -38,14 +38,14 @@ type OPStackEngine struct {
// RollupConfig contains OP Stack rollup configuration
type RollupConfig struct {
Genesis RollupGenesis `json:"genesis"`
BlockTime uint64 `json:"block_time"`
MaxSequencerDrift uint64 `json:"max_sequencer_drift"`
SeqWindowSize uint64 `json:"seq_window_size"`
ChannelTimeout uint64 `json:"channel_timeout"`
L1ChainID uint64 `json:"l1_chain_id"`
L2ChainID uint64 `json:"l2_chain_id"`
P2PSequencerAddress string `json:"p2p_sequencer_address"`
Genesis RollupGenesis `json:"genesis"`
BlockTime uint64 `json:"block_time"`
MaxSequencerDrift uint64 `json:"max_sequencer_drift"`
SeqWindowSize uint64 `json:"seq_window_size"`
ChannelTimeout uint64 `json:"channel_timeout"`
L1ChainID uint64 `json:"l1_chain_id"`
L2ChainID uint64 `json:"l2_chain_id"`
P2PSequencerAddress string `json:"p2p_sequencer_address"`
}
// RollupGenesis contains OP Stack genesis configuration
@@ -195,7 +195,7 @@ func (e *OPStackEngine) startNode(ctx context.Context) error {
}
if e.sequencer {
args = append(args,
args = append(args,
"--sequencer.enabled",
"--sequencer.l1-confs", "3",
"--verifier.l1-confs", "3",
@@ -233,14 +233,14 @@ func (e *OPStackEngine) loadRollupConfig() error {
L1ChainID: uint64(e.config.NetworkID),
L2ChainID: uint64(e.config.NetworkID) + 10000,
}
// Set genesis
e.rollupConfig.Genesis.L1.Number = 0
e.rollupConfig.Genesis.L1.Hash = "0x0000000000000000000000000000000000000000000000000000000000000000"
e.rollupConfig.Genesis.L2.Number = 0
e.rollupConfig.Genesis.L2.Hash = "0x0000000000000000000000000000000000000000000000000000000000000000"
e.rollupConfig.Genesis.L2Time = uint64(time.Now().Unix())
// Save config
data, err := json.MarshalIndent(e.rollupConfig, "", " ")
if err != nil {
@@ -254,7 +254,7 @@ func (e *OPStackEngine) loadRollupConfig() error {
if err != nil {
return err
}
return json.Unmarshal(data, &e.rollupConfig)
}
@@ -273,7 +273,7 @@ func (e *OPStackEngine) Stop(ctx context.Context) error {
go func() {
done <- e.nodeCmd.Wait()
}()
select {
case <-done:
case <-time.After(10 * time.Second):
@@ -295,7 +295,7 @@ func (e *OPStackEngine) stopGeth() {
go func() {
done <- e.gethCmd.Wait()
}()
select {
case <-done:
case <-time.After(10 * time.Second):
@@ -408,20 +408,20 @@ func (e *OPStackEngine) ParentChain() *ChainInfo {
func (e *OPStackEngine) Metrics() map[string]interface{} {
e.mu.RLock()
defer e.mu.RUnlock()
metrics := map[string]interface{}{
"running": e.running,
"uptime": e.Uptime().Seconds(),
"type": "op-stack",
"sequencer": e.sequencer,
}
if e.rollupConfig != nil {
metrics["l1_chain_id"] = e.rollupConfig.L1ChainID
metrics["l2_chain_id"] = e.rollupConfig.L2ChainID
metrics["block_time"] = e.rollupConfig.BlockTime
}
return metrics
}
@@ -437,4 +437,4 @@ func OPStackFactory(name string, binary string) (Engine, error) {
func init() {
Register(EngineOP, OPStackFactory)
}
}
+9 -9
View File
@@ -15,10 +15,10 @@ import (
type EngineType string
const (
EngineLux EngineType = "lux"
EngineGeth EngineType = "geth"
EngineOP EngineType = "op"
EngineEth2 EngineType = "eth2"
EngineLux EngineType = "lux"
EngineGeth EngineType = "geth"
EngineOP EngineType = "op"
EngineEth2 EngineType = "eth2"
)
// ChainInfo describes a blockchain's network properties
@@ -58,22 +58,22 @@ type Engine interface {
Start(ctx context.Context, config *NodeConfig) error
Stop(ctx context.Context) error
Restart(ctx context.Context) error
// Status
Health(ctx context.Context) (*HealthStatus, error)
IsRunning() bool
Uptime() time.Duration
// Network info
NetworkID() uint32
ChainID() ids.ID
RPCEndpoint() string
WSEndpoint() string
P2PEndpoint() string
// Chain relationships
ParentChain() *ChainInfo // nil for L1s
// Metrics
Metrics() map[string]interface{}
}
@@ -95,4 +95,4 @@ func New(typ EngineType, name string, binary string) (Engine, error) {
return nil, fmt.Errorf("unknown engine type: %s", typ)
}
return factory(name, binary)
}
}
+2 -2
View File
@@ -9,10 +9,10 @@ import (
"syscall"
"time"
"github.com/luxfi/netrunner/local"
"github.com/luxfi/netrunner/network"
"github.com/luxfi/log"
"github.com/luxfi/log/level"
"github.com/luxfi/netrunner/local"
"github.com/luxfi/netrunner/network"
)
const (
+2 -2
View File
@@ -8,13 +8,13 @@ import (
"syscall"
"time"
"github.com/luxfi/log"
"github.com/luxfi/log/level"
"github.com/luxfi/netrunner/local"
"github.com/luxfi/netrunner/network"
"github.com/luxfi/netrunner/network/node"
"github.com/luxfi/node/config"
"github.com/luxfi/node/staking"
"github.com/luxfi/log"
"github.com/luxfi/log/level"
)
const (
+5 -5
View File
@@ -12,11 +12,11 @@ import (
"syscall"
"time"
"github.com/luxfi/log"
"github.com/luxfi/log/level"
"github.com/luxfi/netrunner/local"
"github.com/luxfi/netrunner/network"
"github.com/luxfi/node/config"
"github.com/luxfi/log"
"github.com/luxfi/log/level"
)
const (
@@ -209,9 +209,9 @@ func run(logger log.Logger) error {
createCtx, createCancel := context.WithTimeout(context.Background(), createBlockchainTimeout)
chainSpec := []network.ChainSpec{
{
VMName: chain.VMName,
Genesis: genesis,
Alias: chain.Alias,
VMName: chain.VMName,
Genesis: genesis,
Alias: chain.Alias,
BlockchainName: chain.Alias, // Use unique name for each chain
},
}
+2 -2
View File
@@ -15,11 +15,11 @@ require (
github.com/luxfi/consensus v1.22.47
github.com/luxfi/const v1.4.0
github.com/luxfi/crypto v1.17.32
github.com/luxfi/genesis v1.5.18
github.com/luxfi/genesis v1.5.19
github.com/luxfi/geth v1.16.67
github.com/luxfi/go-bip39 v1.1.2
github.com/luxfi/ids v1.2.6
github.com/luxfi/keys v1.0.5
github.com/luxfi/keys v1.0.6
github.com/luxfi/log v1.2.1
github.com/luxfi/math v1.2.0
github.com/luxfi/metric v1.4.8
+4 -4
View File
@@ -249,8 +249,8 @@ github.com/luxfi/crypto v1.17.32 h1:qEKDETNMeU7T82UMEEGYh0bbJ0ZZdx0sAMRwFXt69aQ=
github.com/luxfi/crypto v1.17.32/go.mod h1:laZTnEq0BD/fnPhd5ke/79WgFhV3TZjmfPxk+b8zbrE=
github.com/luxfi/database v1.2.18 h1:yppRuZf3FKnr3v4o+UHNfSW+cXtUIUibtyki0+EqiIs=
github.com/luxfi/database v1.2.18/go.mod h1:dYxaF30KGJQzuxA3VH7+WGU5/dhwRyzLgzSk9F3wje0=
github.com/luxfi/genesis v1.5.18 h1:DDNFDyYT+AtSX78XBV1nGeuvR4404XPAkXnnrlJSV78=
github.com/luxfi/genesis v1.5.18/go.mod h1:888i1w8e67HIpgLuveIdd+3ESWCXHgnz7cijF6dDAKI=
github.com/luxfi/genesis v1.5.19 h1:q9ETV3NXTlJBhhkVKYDidaQqBDb1jgY3TdaLPfBGu0k=
github.com/luxfi/genesis v1.5.19/go.mod h1:888i1w8e67HIpgLuveIdd+3ESWCXHgnz7cijF6dDAKI=
github.com/luxfi/geth v1.16.67 h1:ILPG59XNnNKIz1YNO1fJFV+doZXBjcQkb4oXQyz1HUM=
github.com/luxfi/geth v1.16.67/go.mod h1:QWPyY3vlGQfm8SX/PE6q4XHTBe7X4Gw+SGDJi5QU1pg=
github.com/luxfi/go-bip32 v1.0.2 h1:7vFbb+Wr4Z499q2tuCLdd7wWjtn8sH+HWBlx76mhH9Y=
@@ -261,8 +261,8 @@ github.com/luxfi/ids v1.2.6 h1:Ru86pltAcWPhCT7FH/MwwuAYXkeZbt9ZwfuR883c8MI=
github.com/luxfi/ids v1.2.6/go.mod h1:svLsj7e6ixJVfRYaQqv9RWjBIW11Vz738+d08BVpeCg=
github.com/luxfi/keychain v1.0.1 h1:8tTzPnZSi0M6UtKRBCypnGFrLNry9BvWfS5ol33yDF0=
github.com/luxfi/keychain v1.0.1/go.mod h1:X7HLk5QRZ1VHJxovAyBL6z7f/nlosoXlveEtvUGhoaw=
github.com/luxfi/keys v1.0.5 h1:ZzE2wHDxSiIcufkYlp7twmTKGB0mczjE67uM3uAP2Ag=
github.com/luxfi/keys v1.0.5/go.mod h1:hVqfOm6n4/ZN2O6AirlLEpgBhW8bsxNo2EbiNvggGUk=
github.com/luxfi/keys v1.0.6 h1:mKB0xjQoNpJxj59UNfdPfuqUoRT48Itt7PvRXU+X3iY=
github.com/luxfi/keys v1.0.6/go.mod h1:hVqfOm6n4/ZN2O6AirlLEpgBhW8bsxNo2EbiNvggGUk=
github.com/luxfi/log v1.2.1 h1:L2JM8MjGPF8rBnY+EXODS9x5OQTGSgeQ1SPFmeB+oPY=
github.com/luxfi/log v1.2.1/go.mod h1:0vJzb4Hl9fvgwWDMKcbCD/SYH3bO0iSmgZMcYdfdl1o=
github.com/luxfi/math v1.2.0 h1:cZVQQj1PrIWh3Oy6xfbBL44xay4QFuAXZBg9+pWPBmE=
+28 -27
View File
@@ -26,26 +26,27 @@ import (
"github.com/luxfi/node/vms/components/verify"
"github.com/luxfi/node/wallet/chain/x"
"maps"
"slices"
luxconfig "github.com/luxfi/config"
constants "github.com/luxfi/const"
"github.com/luxfi/crypto/bls"
"github.com/luxfi/crypto/secp256k1"
"github.com/luxfi/ids"
"github.com/luxfi/log"
"github.com/luxfi/math/set"
"github.com/luxfi/netrunner/network"
"github.com/luxfi/netrunner/network/node"
"github.com/luxfi/netrunner/utils"
luxconfig "github.com/luxfi/config"
"github.com/luxfi/node/api/admin"
"github.com/luxfi/node/config"
"github.com/luxfi/crypto/secp256k1"
"github.com/luxfi/ids"
"github.com/luxfi/const"
"github.com/luxfi/math/set"
"github.com/luxfi/crypto/bls"
"github.com/luxfi/log"
"github.com/luxfi/node/vms/platformvm"
"github.com/luxfi/node/vms/platformvm/signer"
"github.com/luxfi/node/vms/platformvm/txs"
"github.com/luxfi/node/vms/secp256k1fx"
pwallet "github.com/luxfi/node/wallet/chain/p"
pwalletwallet "github.com/luxfi/node/wallet/chain/p/wallet"
"maps"
"slices"
pbuilder "github.com/luxfi/node/wallet/chain/p/builder"
psigner "github.com/luxfi/node/wallet/chain/p/signer"
@@ -73,7 +74,7 @@ const (
// Most P-chain API calls should complete in <5s on localhost
defaultTimeout = 30 * time.Second
stakingMinimumLeadTime = 25 * time.Second
minStakeDuration = 24 * 14 * time.Hour
minStakeDuration = 24 * 14 * time.Hour
// FAIL FAST: Reduced from 45s to 30s for local deployments
// For local networks, validators activate quickly (5-10s). If it takes longer,
// something is wrong - fail fast with clear error rather than waiting forever.
@@ -88,7 +89,7 @@ var (
type blockchainInfo struct {
chainName string
vmID ids.ID
chainID ids.ID
chainID ids.ID
blockchainID ids.ID
}
@@ -963,16 +964,16 @@ func (ln *localNetwork) installCustomChains(
// This ensures P-Chain is synchronized across all nodes after blockchain creation
// TEMPORARILY DISABLED - debugging P-Chain sync issue
/*
blockchainIDs := make([]ids.ID, len(blockchainTxs))
for i, tx := range blockchainTxs {
blockchainIDs[i] = tx.ID()
}
if err := ln.waitForBlockchainOnPChain(ctx, blockchainIDs); err != nil {
ln.logger.Error("installCustomChains: P-Chain sync failed",
"error", err.Error(),
)
return nil, fmt.Errorf("P-Chain sync failed: %w", err)
}
blockchainIDs := make([]ids.ID, len(blockchainTxs))
for i, tx := range blockchainTxs {
blockchainIDs[i] = tx.ID()
}
if err := ln.waitForBlockchainOnPChain(ctx, blockchainIDs); err != nil {
ln.logger.Error("installCustomChains: P-Chain sync failed",
"error", err.Error(),
)
return nil, fmt.Errorf("P-Chain sync failed: %w", err)
}
*/
// Nodes need to be restarted after blockchain creation to discover the new chains
@@ -1032,7 +1033,7 @@ func (ln *localNetwork) installCustomChains(
// as there is no way to recover VM name from VM ID
chainName: chainSpec.VMName,
vmID: vmID,
chainID: chainID,
chainID: chainID,
blockchainID: blockchainTxs[i].ID(),
}
}
@@ -1333,7 +1334,7 @@ func getDefaultKey() (*secp256k1.PrivateKey, error) {
// (keys.DeriveValidatorFromMnemonic uses m/44'/9000'/0'/0/{index})
if mnemonic := os.Getenv("LUX_MNEMONIC"); mnemonic != "" {
fmt.Printf("🔑 getDefaultKey: Using LUX_MNEMONIC (len=%d)\n", len(mnemonic))
// Use the SAME derivation function as genesis allocations
// This ensures wallet operations use keys that have funds allocated in genesis
vk, err := keys.DeriveValidatorFromMnemonic(mnemonic, 0)
@@ -1344,11 +1345,11 @@ func getDefaultKey() (*secp256k1.PrivateKey, error) {
if err != nil {
return nil, fmt.Errorf("failed to convert key: %w", err)
}
pubKey := privKey.PublicKey()
walletAddr := ids.ShortID(pubKey.Address())
fmt.Printf("🔑 Wallet address (from mnemonic m/44'/9000'/0'/0/0): %s\n", walletAddr.String())
return privKey, nil
}
@@ -1435,9 +1436,9 @@ func newWallet(
}
pTXs[id] = tx
}
pUTXOs := common.NewChainUTXOs(constants.PlatformChainID, luxState.UTXOs)
pUTXOs := common.NewChainUTXOs(constants.PlatformChainID, luxState.UTXOs)
xChainID := luxState.XCTX.BlockchainID
xUTXOs := common.NewChainUTXOs(xChainID, luxState.UTXOs)
xUTXOs := common.NewChainUTXOs(xChainID, luxState.UTXOs)
var w wallet
w.addr = privKey.PublicKey().Address()
// TODO: Create owners map instead of pTXs
+71 -34
View File
@@ -13,10 +13,11 @@ import (
"path/filepath"
"time"
"github.com/luxfi/const"
"maps"
constants "github.com/luxfi/const"
"github.com/luxfi/crypto/bls"
"github.com/luxfi/crypto/bls/signer/localsigner"
"github.com/luxfi/node/vms/platformvm/signer"
luxcrypto "github.com/luxfi/crypto/secp256k1"
"github.com/luxfi/genesis/configs"
"github.com/luxfi/ids"
@@ -25,7 +26,7 @@ import (
"github.com/luxfi/netrunner/network/node"
"github.com/luxfi/netrunner/utils"
"github.com/luxfi/node/config"
"maps"
"github.com/luxfi/node/vms/platformvm/signer"
"github.com/luxfi/node/staking"
"github.com/luxfi/node/utils/formatting/address"
@@ -267,12 +268,10 @@ func NewConfigForNetwork(binaryPath string, numNodes uint32, networkID uint32) (
m["allocations"] = mustJSON(newAllocations)
}
// Check if we need to generate initialStakers
var existingStakers []interface{}
if raw, ok := m["initialStakers"]; ok {
json.Unmarshal(raw, &existingStakers)
}
if len(existingStakers) == 0 {
// ALWAYS replace initialStakers with embedded validator keys for local network testing.
// The genesis file may have different validators than netrunner's embedded keys,
// so we must replace them to ensure nodes can actually validate.
{
// Generate initialStakers from embedded validator keys
var generatedStakers []map[string]interface{}
for i := uint32(0); i < numEmbeddedToUse; i++ {
@@ -423,6 +422,10 @@ func NewCanonicalCustomConfig(binaryPath string, numNodes uint32) (network.Confi
// newCanonicalConfig creates a network config with canonical (pre-serialized) genesis bytes
// and loads validator keys from ~/.lux/keys/node{0..n-1}/
//
// IMPORTANT: This function patches the genesis initialStakers with the BLS keys from the
// loaded validator keys. This ensures the validators' BLS Proof of Possession (PoP) in genesis
// matches the actual signer.key files the nodes will use.
func newCanonicalConfig(binaryPath string, numNodes uint32, networkID uint32, portBase int) (network.Config, error) {
// Load CANONICAL genesis bytes - no parsing/re-serialization
originalGenesisBytes, err := configs.GetCanonicalGenesisBytes(networkID)
@@ -430,13 +433,61 @@ func newCanonicalConfig(binaryPath string, numNodes uint32, networkID uint32, po
return network.Config{}, fmt.Errorf("failed to load canonical genesis: %w", err)
}
// For LOCAL TESTING: Update startTime to now so bootstrap starts immediately.
// Load validator keys from ~/.lux/keys/ FIRST so we can patch genesis with them
keysDir := os.Getenv("LUX_KEYS_DIR")
if keysDir == "" {
keysDir = validatorKeysDir()
}
ks := keys.NewKeyStore(keysDir)
// Load all validator keys and build initialStakers
hrp := constants.GetHRP(networkID)
validatorKeys := make([]*keys.ValidatorKey, numNodes)
initialStakers := make([]map[string]interface{}, numNodes)
for i := uint32(0); i < numNodes; i++ {
name := fmt.Sprintf("node%d", i)
vk, err := ks.Load(name)
if err != nil {
return network.Config{}, fmt.Errorf("failed to load validator key %s from %s: %w (run 'lux key generate' first)", name, keysDir, err)
}
validatorKeys[i] = vk
// Build initialStaker entry with BLS keys from loaded validator
rewardAddr, err := address.Format("P", hrp, vk.PChainAddr[:])
if err != nil {
return network.Config{}, fmt.Errorf("couldn't format reward address for node %d: %w", i, err)
}
staker := map[string]interface{}{
"nodeID": vk.NodeID.String(),
"rewardAddress": rewardAddr,
"delegationFee": 20000,
"weight": GigaLux, // 1 billion nLUX = 1000 LUX
}
if len(vk.BLSPublicKey) > 0 && len(vk.BLSPoP) > 0 {
staker["signer"] = map[string]interface{}{
"publicKey": vk.BLSPublicKeyHex(),
"proofOfPossession": vk.BLSPoPHex(),
}
fmt.Printf(" Validator %d: %s (BLS: %s...)\n", i, vk.NodeID.String(), vk.BLSPublicKeyHex()[:20])
} else {
fmt.Printf(" Validator %d: %s (no BLS key)\n", i, vk.NodeID.String())
}
initialStakers[i] = staker
}
// For LOCAL TESTING: Update startTime to now AND patch initialStakers with our keys
// CRITICAL: Use patchGenesisPreservingRaw to preserve cChainGenesis exact bytes.
now := time.Now().Unix()
fmt.Printf("📅 Updated genesis startTime to %d (now) for local testing\n", now)
fmt.Printf("🔑 Patching genesis initialStakers with %d validators from %s\n", numNodes, keysDir)
genesisBytes, err := patchGenesisPreservingRaw(originalGenesisBytes, evmGenesisKeys, func(m map[string]json.RawMessage) error {
m["startTime"] = mustJSON(uint64(now))
m["initialStakers"] = mustJSON(initialStakers)
return nil
})
if err != nil {
@@ -447,22 +498,10 @@ func newCanonicalConfig(binaryPath string, numNodes uint32, networkID uint32, po
netConfig := NewDefaultConfig(binaryPath)
netConfig.Genesis = string(genesisBytes)
// Load validator keys from ~/.lux/keys/
keysDir := os.Getenv("LUX_KEYS_DIR")
if keysDir == "" {
keysDir = validatorKeysDir()
}
ks := keys.NewKeyStore(keysDir)
// Load keys for each node
// Build node configs from loaded keys
nodeConfigs := make([]node.Config, numNodes)
for i := uint32(0); i < numNodes; i++ {
name := fmt.Sprintf("node%d", i)
vk, err := ks.Load(name)
if err != nil {
return network.Config{}, fmt.Errorf("failed to load validator key %s from %s: %w (run 'lux key generate' first)", name, keysDir, err)
}
vk := validatorKeys[i]
port := portBase + int(i)*2
nodeConfigs[i] = node.Config{
Flags: map[string]interface{}{
@@ -477,11 +516,10 @@ func newCanonicalConfig(binaryPath string, numNodes uint32, networkID uint32, po
UpgradeConfigFiles: map[string]string{},
PChainConfigFiles: map[string]string{},
}
fmt.Printf(" Loaded validator %d: %s\n", i, vk.NodeID.String())
}
netConfig.NodeConfigs = nodeConfigs
fmt.Printf("✅ Loaded canonical genesis for network %d with %d validators\n", networkID, numNodes)
fmt.Printf("✅ Loaded canonical genesis for network %d with %d validators (BLS keys patched)\n", networkID, numNodes)
return netConfig, nil
}
@@ -724,13 +762,13 @@ func NewConfigWithPreExistingKeys(binaryPath string, networkID uint32, keysDir s
config.HTTPPortKey: port,
config.StakingPortKey: port + 1,
},
StakingKey: string(vk.StakerKey),
StakingCert: string(vk.StakerCert),
StakingSigningKey: base64.StdEncoding.EncodeToString(vk.BLSSecretKey),
IsBeacon: true,
ChainConfigFiles: map[string]string{},
StakingKey: string(vk.StakerKey),
StakingCert: string(vk.StakerCert),
StakingSigningKey: base64.StdEncoding.EncodeToString(vk.BLSSecretKey),
IsBeacon: true,
ChainConfigFiles: map[string]string{},
UpgradeConfigFiles: map[string]string{},
PChainConfigFiles: map[string]string{},
PChainConfigFiles: map[string]string{},
}
}
@@ -1016,8 +1054,7 @@ func NewConfigFromMnemonic(binaryPath string, networkID uint32, numNodes uint32)
// Add per-node specific flags
nodeFlags[config.HTTPPortKey] = port
nodeFlags[config.StakingPortKey] = port + 1
// Enable sybil protection for mainnet - validators have proper keys
// The genesis validators have matching NodeIDs from deterministic TLS certs
// Sybil protection MUST be enabled - validators come from genesis initialStakers
nodeFlags[config.SybilProtectionEnabledKey] = true
netConfig.NodeConfigs[i] = node.Config{
+1 -1
View File
@@ -11,7 +11,7 @@ import (
"path/filepath"
"time"
"github.com/luxfi/const"
constants "github.com/luxfi/const"
"github.com/luxfi/log"
"github.com/luxfi/netrunner/network/node"
"github.com/luxfi/netrunner/utils"
+1 -1
View File
@@ -13,9 +13,9 @@ import (
"time"
"github.com/luxfi/genesis/configs"
"github.com/luxfi/netrunner/local"
"github.com/luxfi/log"
"github.com/luxfi/log/level"
"github.com/luxfi/netrunner/local"
"github.com/stretchr/testify/require"
)
+65 -65
View File
@@ -1,72 +1,72 @@
package local
import (
"fmt"
"os"
"testing"
"github.com/btcsuite/btcd/btcutil/hdkeychain"
"github.com/btcsuite/btcd/chaincfg"
"github.com/luxfi/go-bip39"
luxcrypto "github.com/luxfi/crypto/secp256k1"
"golang.org/x/crypto/sha3"
"fmt"
"os"
"testing"
"github.com/btcsuite/btcd/btcutil/hdkeychain"
"github.com/btcsuite/btcd/chaincfg"
luxcrypto "github.com/luxfi/crypto/secp256k1"
"github.com/luxfi/go-bip39"
"golang.org/x/crypto/sha3"
)
func TestMnemonicDerivation(t *testing.T) {
mnemonic := os.Getenv("LUX_MNEMONIC")
if mnemonic == "" {
t.Skip("LUX_MNEMONIC not set")
}
fmt.Printf("Mnemonic: %s...\n", mnemonic[:20])
if !bip39.IsMnemonicValid(mnemonic) {
t.Fatal("Invalid mnemonic")
}
seed := bip39.NewSeed(mnemonic, "")
// Derive using BIP44 m/44'/60'/0'/0/0
masterKey, _ := hdkeychain.NewMaster(seed, &chaincfg.MainNetParams)
purpose, _ := masterKey.Derive(hdkeychain.HardenedKeyStart + 44)
coinType, _ := purpose.Derive(hdkeychain.HardenedKeyStart + 60)
account, _ := coinType.Derive(hdkeychain.HardenedKeyStart + 0)
change, _ := account.Derive(0)
addressKey, _ := change.Derive(0)
ecPrivKey, _ := addressKey.ECPrivKey()
privKeyBytes := ecPrivKey.Serialize()
privKey, err := luxcrypto.ToPrivateKey(privKeyBytes)
if err != nil {
t.Fatalf("Error: %v", err)
}
pubKey := privKey.PublicKey()
shortID := pubKey.Address()
// Get Ethereum address
pubKeyBytes := pubKey.Bytes()
hash := sha3.NewLegacyKeccak256()
hash.Write(pubKeyBytes[1:]) // skip the 0x04 prefix
ethAddr := hash.Sum(nil)[12:]
fmt.Printf("\n=== Key Derivation Results (m/44'/60'/0'/0/0) ===\n")
fmt.Printf("Lux Short ID: %s\n", shortID.String())
fmt.Printf("Ethereum Address: 0x%x\n", ethAddr)
fmt.Printf("Expected Treasury: 0x9011E888251AB053B7bD1cdB598Db4f9DEd94714\n")
// Also derive using coin type 9000 for comparison
coinType9000, _ := purpose.Derive(hdkeychain.HardenedKeyStart + 9000)
account9000, _ := coinType9000.Derive(hdkeychain.HardenedKeyStart + 0)
change9000, _ := account9000.Derive(0)
addressKey9000, _ := change9000.Derive(0)
ecPrivKey9000, _ := addressKey9000.ECPrivKey()
privKey9000, _ := luxcrypto.ToPrivateKey(ecPrivKey9000.Serialize())
shortID9000 := privKey9000.PublicKey().Address()
fmt.Printf("\n=== Comparison: Coin Type 9000 (m/44'/9000'/0'/0/0) ===\n")
fmt.Printf("Lux Short ID (9000): %s\n", shortID9000.String())
fmt.Printf("\n=== Genesis Treasury P-chain Address ===\n")
fmt.Printf("Expected: P-lux1jqg73zp9r2c98daarnd4nrd5l80dj3c5eha5fl\n")
mnemonic := os.Getenv("LUX_MNEMONIC")
if mnemonic == "" {
t.Skip("LUX_MNEMONIC not set")
}
fmt.Printf("Mnemonic: %s...\n", mnemonic[:20])
if !bip39.IsMnemonicValid(mnemonic) {
t.Fatal("Invalid mnemonic")
}
seed := bip39.NewSeed(mnemonic, "")
// Derive using BIP44 m/44'/60'/0'/0/0
masterKey, _ := hdkeychain.NewMaster(seed, &chaincfg.MainNetParams)
purpose, _ := masterKey.Derive(hdkeychain.HardenedKeyStart + 44)
coinType, _ := purpose.Derive(hdkeychain.HardenedKeyStart + 60)
account, _ := coinType.Derive(hdkeychain.HardenedKeyStart + 0)
change, _ := account.Derive(0)
addressKey, _ := change.Derive(0)
ecPrivKey, _ := addressKey.ECPrivKey()
privKeyBytes := ecPrivKey.Serialize()
privKey, err := luxcrypto.ToPrivateKey(privKeyBytes)
if err != nil {
t.Fatalf("Error: %v", err)
}
pubKey := privKey.PublicKey()
shortID := pubKey.Address()
// Get Ethereum address
pubKeyBytes := pubKey.Bytes()
hash := sha3.NewLegacyKeccak256()
hash.Write(pubKeyBytes[1:]) // skip the 0x04 prefix
ethAddr := hash.Sum(nil)[12:]
fmt.Printf("\n=== Key Derivation Results (m/44'/60'/0'/0/0) ===\n")
fmt.Printf("Lux Short ID: %s\n", shortID.String())
fmt.Printf("Ethereum Address: 0x%x\n", ethAddr)
fmt.Printf("Expected Treasury: 0x9011E888251AB053B7bD1cdB598Db4f9DEd94714\n")
// Also derive using coin type 9000 for comparison
coinType9000, _ := purpose.Derive(hdkeychain.HardenedKeyStart + 9000)
account9000, _ := coinType9000.Derive(hdkeychain.HardenedKeyStart + 0)
change9000, _ := account9000.Derive(0)
addressKey9000, _ := change9000.Derive(0)
ecPrivKey9000, _ := addressKey9000.ECPrivKey()
privKey9000, _ := luxcrypto.ToPrivateKey(ecPrivKey9000.Serialize())
shortID9000 := privKey9000.PublicKey().Address()
fmt.Printf("\n=== Comparison: Coin Type 9000 (m/44'/9000'/0'/0/0) ===\n")
fmt.Printf("Lux Short ID (9000): %s\n", shortID9000.String())
fmt.Printf("\n=== Genesis Treasury P-chain Address ===\n")
fmt.Printf("Expected: P-lux1jqg73zp9r2c98daarnd4nrd5l80dj3c5eha5fl\n")
}
+23 -22
View File
@@ -19,6 +19,9 @@ import (
"sync"
"time"
"maps"
"slices"
luxconfig "github.com/luxfi/config"
"github.com/luxfi/crypto/bls"
luxcrypto "github.com/luxfi/crypto/secp256k1"
@@ -36,8 +39,6 @@ import (
"github.com/luxfi/node/network/peer"
"github.com/luxfi/node/staking"
"github.com/luxfi/node/utils/beacon"
"maps"
"slices"
"github.com/luxfi/node/utils/formatting/address"
"github.com/luxfi/node/utils/wrappers"
@@ -55,12 +56,12 @@ const (
genesisFileName = "genesis.json"
stopTimeout = 30 * time.Second
// healthCheckFreq reduced from 3s to 1s for faster health polling
healthCheckFreq = 1 * time.Second
DefaultNumNodes = 5
snapshotPrefix = "lux-snapshot-"
networkRootDirPrefix = "network"
defaultDBSubdir = "db"
defaultLogsSubdir = "logs"
healthCheckFreq = 1 * time.Second
DefaultNumNodes = 5
snapshotPrefix = "lux-snapshot-"
networkRootDirPrefix = "network"
defaultDBSubdir = "db"
defaultLogsSubdir = "logs"
// difference between unlock schedule locktime and startime in original genesis
genesisLocktimeStartimeDelta = 2836800
)
@@ -75,8 +76,8 @@ var (
config.BootstrapIPsKey: {},
config.BootstrapIDsKey: {},
}
chainConfigSubDir = "chainConfigs"
pChainConfigSubDir = "pChainConfigs"
chainConfigSubDir = "chainConfigs"
pChainConfigSubDir = "pChainConfigs"
snapshotsRelPath = filepath.Join(".netrunner", "snapshots")
@@ -85,7 +86,7 @@ var (
// network keeps information uses for network management, and accessing all the nodes
type localNetwork struct {
lock sync.RWMutex
lock sync.RWMutex
logger log.Logger
// This network's ID.
networkID uint32
@@ -261,7 +262,7 @@ func init() {
"C": string(cChainConfig),
},
UpgradeConfigFiles: map[string]string{},
PChainConfigFiles: map[string]string{},
PChainConfigFiles: map[string]string{},
}
for i := 0; i < len(defaultNetworkConfig.NodeConfigs); i++ {
@@ -366,16 +367,16 @@ func newNetwork(
}
// Create the network
net := &localNetwork{
nextNodeSuffix: 1,
nodes: map[string]*localNode{},
onStopCh: make(chan struct{}),
logger: logger,
bootstraps: beacon.NewSet(),
newAPIClientF: newAPIClientF,
nodeProcessCreator: nodeProcessCreator,
rootDir: rootDir,
snapshotsDir: snapshotsDir,
reassignPortsIfUsed: reassignPortsIfUsed,
nextNodeSuffix: 1,
nodes: map[string]*localNode{},
onStopCh: make(chan struct{}),
logger: logger,
bootstraps: beacon.NewSet(),
newAPIClientF: newAPIClientF,
nodeProcessCreator: nodeProcessCreator,
rootDir: rootDir,
snapshotsDir: snapshotsDir,
reassignPortsIfUsed: reassignPortsIfUsed,
chainID2ElasticChainID: map[ids.ID]ids.ID{},
}
return net, nil
+14 -14
View File
@@ -13,6 +13,8 @@ import (
"testing"
"time"
"github.com/luxfi/ids"
"github.com/luxfi/log"
"github.com/luxfi/netrunner/api"
apimocks "github.com/luxfi/netrunner/api/mocks"
"github.com/luxfi/netrunner/local/mocks"
@@ -23,10 +25,8 @@ import (
"github.com/luxfi/netrunner/utils"
"github.com/luxfi/node/api/health"
"github.com/luxfi/node/config"
"github.com/luxfi/ids"
"github.com/luxfi/node/message"
"github.com/luxfi/node/network/peer"
"github.com/luxfi/log"
"github.com/luxfi/node/utils/rpc"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
@@ -38,12 +38,12 @@ const (
)
var (
_ NodeProcessCreator = &localTestSuccessfulNodeProcessCreator{}
_ NodeProcessCreator = &localTestFailedStartProcessCreator{}
_ NodeProcessCreator = &localTestProcessUndefNodeProcessCreator{}
_ NodeProcessCreator = &localTestFlagCheckProcessCreator{}
_ api.NewAPIClientF = newMockAPISuccessful
_ api.NewAPIClientF = newMockAPIUnhealthy
_ NodeProcessCreator = &localTestSuccessfulNodeProcessCreator{}
_ NodeProcessCreator = &localTestFailedStartProcessCreator{}
_ NodeProcessCreator = &localTestProcessUndefNodeProcessCreator{}
_ NodeProcessCreator = &localTestFlagCheckProcessCreator{}
_ api.NewAPIClientF = newMockAPISuccessful
_ api.NewAPIClientF = newMockAPIUnhealthy
_ peer.InboundHandler = &noOpInboundHandler{}
)
@@ -1185,9 +1185,9 @@ func TestWriteFiles(t *testing.T) {
config.StakingTLSKeyPathKey: stakingKeyPath,
config.StakingCertPathKey: stakingCertPath,
config.StakingSignerKeyPathKey: stakingSigningKeyPath,
config.GenesisFileKey: genesisPath,
config.GenesisFileKey: genesisPath,
config.ChainConfigDirKey: chainConfigDir,
config.NetConfigDirKey: subnetConfigDir,
config.NetConfigDirKey: subnetConfigDir,
},
},
{
@@ -1203,9 +1203,9 @@ func TestWriteFiles(t *testing.T) {
config.StakingTLSKeyPathKey: stakingKeyPath,
config.StakingCertPathKey: stakingCertPath,
config.StakingSignerKeyPathKey: stakingSigningKeyPath,
config.GenesisFileKey: genesisPath,
config.GenesisFileKey: genesisPath,
config.ChainConfigDirKey: chainConfigDir,
config.NetConfigDirKey: subnetConfigDir,
config.NetConfigDirKey: subnetConfigDir,
config.ConfigFileKey: configFilePath,
},
},
@@ -1223,9 +1223,9 @@ func TestWriteFiles(t *testing.T) {
config.StakingTLSKeyPathKey: stakingKeyPath,
config.StakingCertPathKey: stakingCertPath,
config.StakingSignerKeyPathKey: stakingSigningKeyPath,
config.GenesisFileKey: genesisPath,
config.GenesisFileKey: genesisPath,
config.ChainConfigDirKey: chainConfigDir,
config.NetConfigDirKey: subnetConfigDir,
config.NetConfigDirKey: subnetConfigDir,
config.ConfigFileKey: configFilePath,
},
},
+8 -8
View File
@@ -9,23 +9,23 @@ import (
"net/netip"
"time"
validators "github.com/luxfi/consensus/validator" // package name is validators
constants "github.com/luxfi/const"
"github.com/luxfi/crypto/bls"
"github.com/luxfi/ids"
"github.com/luxfi/log"
"github.com/luxfi/math/set"
"github.com/luxfi/metric"
"github.com/luxfi/netrunner/api"
"github.com/luxfi/netrunner/network/node"
"github.com/luxfi/netrunner/network/node/status"
"github.com/luxfi/const"
"github.com/luxfi/ids"
"github.com/luxfi/node/message"
"github.com/luxfi/node/network/peer"
"github.com/luxfi/node/network/throttling"
"github.com/luxfi/node/network/tracker"
"github.com/luxfi/node/utils/compression"
"github.com/luxfi/consensus/validator" // package name is validators
"github.com/luxfi/node/staking"
"github.com/luxfi/node/utils"
"github.com/luxfi/crypto/bls"
"github.com/luxfi/log"
"github.com/luxfi/math/set"
"github.com/luxfi/metric"
"github.com/luxfi/node/utils/compression"
"github.com/luxfi/node/version"
"github.com/prometheus/client_golang/prometheus"
)
+5 -5
View File
@@ -8,10 +8,10 @@ import (
"os/exec"
"sync"
"github.com/luxfi/log"
"github.com/luxfi/netrunner/network/node"
"github.com/luxfi/netrunner/network/node/status"
"github.com/luxfi/netrunner/utils"
"github.com/luxfi/log"
"github.com/shirou/gopsutil/process"
)
@@ -81,10 +81,10 @@ func (npc *nodeProcessCreator) NewNodeProcess(config node.Config, args ...string
}
type nodeProcess struct {
name string
logger log.Logger
lock sync.RWMutex
cmd *exec.Cmd
name string
logger log.Logger
lock sync.RWMutex
cmd *exec.Cmd
// Process status
state status.Status
// Closed when the process exits.
+3 -5
View File
@@ -15,18 +15,16 @@ import (
"testing"
"time"
"github.com/luxfi/netrunner/network/node"
constants "github.com/luxfi/const"
"github.com/luxfi/geth/metrics"
"github.com/luxfi/ids"
"github.com/luxfi/netrunner/network/node"
"github.com/luxfi/node/message"
"github.com/luxfi/node/network/peer"
"github.com/luxfi/node/staking"
"github.com/luxfi/const"
"github.com/luxfi/node/utils/ips"
luxlog "github.com/luxfi/log"
"github.com/luxfi/node/utils/wrappers"
"github.com/luxfi/node/version"
"github.com/luxfi/metric"
"github.com/prometheus/client_golang/prometheus"
"github.com/stretchr/testify/require"
)
+4 -3
View File
@@ -9,16 +9,17 @@ import (
"path/filepath"
"strings"
"maps"
"slices"
luxconfig "github.com/luxfi/config"
"github.com/luxfi/const"
constants "github.com/luxfi/const"
"github.com/luxfi/ids"
"github.com/luxfi/log"
"github.com/luxfi/netrunner/api"
"github.com/luxfi/netrunner/network"
"github.com/luxfi/netrunner/network/node"
"github.com/luxfi/netrunner/utils"
"maps"
"slices"
"github.com/luxfi/node/config"
dircopy "github.com/otiai10/copy"
+3 -3
View File
@@ -31,11 +31,11 @@ func (w *walletClient) IssueTx(tx *txs.Tx, options ...common.Option) error {
if ctx == nil {
ctx = context.Background()
}
// Serialize the transaction
txBytes := tx.Bytes()
// Issue through platform client
_, err := w.platformClient.IssueTx(ctx, txBytes)
return err
}
}
+30 -30
View File
@@ -9,9 +9,9 @@ import (
"time"
"github.com/dgraph-io/badger/v4"
"github.com/luxfi/netrunner/network"
"github.com/luxfi/ids"
"github.com/luxfi/log"
"github.com/luxfi/netrunner/network"
)
// NetworkType represents the type of network
@@ -19,29 +19,29 @@ type NetworkType string
const (
NetworkTypePrimary NetworkType = "primary" // Mainnet or Testnet
NetworkTypeChain NetworkType = "chain" // L1/L2 chains
NetworkTypeChain NetworkType = "chain" // L1/L2 chains
)
// NetworkConfig defines configuration for a single network
type NetworkConfig struct {
NetworkID uint32 `json:"networkID"`
Name string `json:"name"`
Type NetworkType `json:"type"`
ParentID uint32 `json:"parentID,omitempty"` // For chains
HTTPPort int `json:"httpPort"`
StakingPort int `json:"stakingPort"`
DataDir string `json:"dataDir"`
Validators int `json:"validators"`
NetworkID uint32 `json:"networkID"`
Name string `json:"name"`
Type NetworkType `json:"type"`
ParentID uint32 `json:"parentID,omitempty"` // For chains
HTTPPort int `json:"httpPort"`
StakingPort int `json:"stakingPort"`
DataDir string `json:"dataDir"`
Validators int `json:"validators"`
ChainConfig []ChainConfig `json:"chains,omitempty"`
}
// ChainConfig defines configuration for a chain within a network
type ChainConfig struct {
ChainID string `json:"chainID"`
VMID string `json:"vmID"`
PChainID string `json:"pChainID,omitempty"`
Genesis []byte `json:"genesis,omitempty"`
IsEVM bool `json:"isEVM"`
ChainID string `json:"chainID"`
VMID string `json:"vmID"`
PChainID string `json:"pChainID,omitempty"`
Genesis []byte `json:"genesis,omitempty"`
IsEVM bool `json:"isEVM"`
}
// MultiNetworkManager manages multiple networks running in parallel
@@ -73,7 +73,7 @@ type NetworkInstance struct {
Config NetworkConfig
Network network.Network
Status NetworkStatus
Chains map[string]*ChainInstance
Chains map[string]*ChainInstance
StartTime time.Time
}
@@ -107,13 +107,13 @@ type ConsensusManager struct {
// ConsensusParams defines consensus parameters
type ConsensusParams struct {
K int `json:"k"`
Alpha int `json:"alpha"`
BetaVirtuous int `json:"betaVirtuous"`
BetaRogue int `json:"betaRogue"`
ConcurrentPolls int `json:"concurrentPolls"`
OptimalProcessing int `json:"optimalProcessing"`
MaxProcessing int `json:"maxProcessing"`
K int `json:"k"`
Alpha int `json:"alpha"`
BetaVirtuous int `json:"betaVirtuous"`
BetaRogue int `json:"betaRogue"`
ConcurrentPolls int `json:"concurrentPolls"`
OptimalProcessing int `json:"optimalProcessing"`
MaxProcessing int `json:"maxProcessing"`
MaxTimeProcessing time.Duration `json:"maxTimeProcessing"`
}
@@ -146,10 +146,10 @@ type CrossChainTx struct {
type TxStatus string
const (
TxStatusPending TxStatus = "pending"
TxStatusPending TxStatus = "pending"
TxStatusValidating TxStatus = "validating"
TxStatusCommitted TxStatus = "committed"
TxStatusFailed TxStatus = "failed"
TxStatusCommitted TxStatus = "committed"
TxStatusFailed TxStatus = "failed"
)
// NewMultiNetworkManager creates a new multi-network manager
@@ -170,7 +170,7 @@ func NewMultiNetworkManager(logger log.Logger, sharedDBPath string) (*MultiNetwo
networks: make(map[uint32]*NetworkInstance),
consensusManagers: make(map[uint32]*ConsensusManager),
txCoordinator: NewCrossChainTxCoordinator(sharedDB),
logger: logger,
logger: logger,
ctx: ctx,
cancel: cancel,
}, nil
@@ -195,8 +195,8 @@ func (m *MultiNetworkManager) AddNetwork(config NetworkConfig) error {
}
instance := &NetworkInstance{
Config: config,
Status: NetworkStatusStopped,
Config: config,
Status: NetworkStatusStopped,
Chains: make(map[string]*ChainInstance),
}
@@ -471,4 +471,4 @@ func (m *MultiNetworkManager) Shutdown() error {
m.logger.Info("Multi-network manager shutdown complete")
return nil
}
}
+5 -4
View File
@@ -8,11 +8,12 @@ import (
"strconv"
"time"
"github.com/luxfi/const"
"maps"
constants "github.com/luxfi/const"
"github.com/luxfi/genesis/pkg/genesis"
"github.com/luxfi/ids"
"github.com/luxfi/netrunner/network/node"
"maps"
"github.com/luxfi/netrunner/utils"
"github.com/luxfi/node/utils/formatting/address"
@@ -152,8 +153,8 @@ func NewGenesis(
NetworkID: networkID,
Allocations: []genesis.UnparsedAllocation{
{
ETHAddr: zeroEthAddr, // Zero address as hex string
LUXAddr: genesisVdrStakeAddr, // Bech32 formatted address
ETHAddr: zeroEthAddr, // Zero address as hex string
LUXAddr: genesisVdrStakeAddr, // Bech32 formatted address
InitialAmount: 0,
UnlockSchedule: []genesis.LockedAmount{ // Provides stake to validators
{
+1 -1
View File
@@ -5,8 +5,8 @@ import (
"errors"
"time"
"github.com/luxfi/netrunner/network/node"
"github.com/luxfi/ids"
"github.com/luxfi/netrunner/network/node"
)
var (
+1 -1
View File
@@ -6,10 +6,10 @@ import (
"errors"
"fmt"
"github.com/luxfi/ids"
"github.com/luxfi/netrunner/api"
"github.com/luxfi/netrunner/network/node/status"
"github.com/luxfi/node/config"
"github.com/luxfi/ids"
"github.com/luxfi/node/network/peer"
)
+46 -46
View File
@@ -16,12 +16,12 @@ import (
// Host manages multiple consensus engines
type Host struct {
mu sync.RWMutex
engines map[string]engines.Engine
ports *PortManager
indexer *Indexer
metrics *MetricsCollector
networks map[string]*NetworkInfo
mu sync.RWMutex
engines map[string]engines.Engine
ports *PortManager
indexer *Indexer
metrics *MetricsCollector
networks map[string]*NetworkInfo
}
// NetworkInfo describes a network's properties
@@ -62,12 +62,12 @@ type EngineOptions struct {
func (h *Host) StartEngine(ctx context.Context, name string, typ string, options *EngineOptions) error {
h.mu.Lock()
defer h.mu.Unlock()
// Check if already running
if _, exists := h.engines[name]; exists {
return fmt.Errorf("engine %s already running", name)
}
// Convert options to NodeConfig
config := &engines.NodeConfig{
NetworkID: options.NetworkID,
@@ -79,7 +79,7 @@ func (h *Host) StartEngine(ctx context.Context, name string, typ string, options
BootstrapIPs: options.BootstrapIPs,
Extra: options.Extra,
}
// Allocate ports if not specified
if config.HTTPPort == 0 {
config.HTTPPort = h.ports.AllocateHTTP()
@@ -90,22 +90,22 @@ func (h *Host) StartEngine(ctx context.Context, name string, typ string, options
if config.StakingPort == 0 {
config.StakingPort = h.ports.AllocateP2P()
}
// Create engine using factory
engine, err := engines.New(engines.EngineType(typ), name, options.Binary)
if err != nil {
return fmt.Errorf("failed to create engine: %w", err)
}
// Start engine
if err := engine.Start(ctx, config); err != nil {
h.ports.Release(config.HTTPPort)
h.ports.Release(config.StakingPort)
return fmt.Errorf("failed to start engine: %w", err)
}
h.engines[name] = engine
// Register network info
h.networks[name] = &NetworkInfo{
Name: name,
@@ -115,10 +115,10 @@ func (h *Host) StartEngine(ctx context.Context, name string, typ string, options
RPC: engine.RPCEndpoint(),
WS: engine.WSEndpoint(),
}
// Start metrics collection
go h.collectMetrics(name, engine)
return nil
}
@@ -126,19 +126,19 @@ func (h *Host) StartEngine(ctx context.Context, name string, typ string, options
func (h *Host) StopEngine(ctx context.Context, name string) error {
h.mu.Lock()
defer h.mu.Unlock()
engine, exists := h.engines[name]
if !exists {
return fmt.Errorf("engine %s not found", name)
}
if err := engine.Stop(ctx); err != nil {
return fmt.Errorf("failed to stop engine: %w", err)
}
delete(h.engines, name)
delete(h.networks, name)
return nil
}
@@ -146,7 +146,7 @@ func (h *Host) StopEngine(ctx context.Context, name string) error {
func (h *Host) ListEngines() []EngineInfo {
h.mu.RLock()
defer h.mu.RUnlock()
var infos []EngineInfo
for name, engine := range h.engines {
health, _ := engine.Health(context.Background())
@@ -182,7 +182,7 @@ func (h *Host) StartStack(ctx context.Context, manifest *StackManifest) error {
if err := manifest.Validate(); err != nil {
return fmt.Errorf("invalid manifest: %w", err)
}
// Start engines in order (L1 first, then L2, then L3)
for _, engine := range manifest.Engines {
config := &engines.NodeConfig{
@@ -194,7 +194,7 @@ func (h *Host) StartStack(ctx context.Context, manifest *StackManifest) error {
BootstrapIPs: engine.BootstrapIPs,
Extra: engine.Extra,
}
options := &EngineOptions{
NetworkID: config.NetworkID,
HTTPPort: config.HTTPPort,
@@ -207,26 +207,26 @@ func (h *Host) StartStack(ctx context.Context, manifest *StackManifest) error {
}
if err := h.StartEngine(ctx, engine.Name, engine.Type, options); err != nil {
// Rollback on failure
h.StopStack(ctx, manifest.Name)
_ = h.StopStack(ctx, manifest.Name)
return fmt.Errorf("failed to start %s: %w", engine.Name, err)
}
// Wait for health before starting dependent engines
if engine.WaitHealthy {
if err := h.waitHealthy(ctx, engine.Name, 30*time.Second); err != nil {
h.StopStack(ctx, manifest.Name)
_ = h.StopStack(ctx, manifest.Name)
return fmt.Errorf("engine %s failed health check: %w", engine.Name, err)
}
}
}
// Configure bridges if specified
if manifest.Bridge != nil {
if err := h.configureBridge(ctx, manifest.Bridge); err != nil {
return fmt.Errorf("failed to configure bridge: %w", err)
}
}
return nil
}
@@ -240,7 +240,7 @@ func (h *Host) StopStack(ctx context.Context, stackName string) error {
engines = append(engines, name)
}
h.mu.Unlock()
var lastErr error
for _, name := range engines {
if err := h.StopEngine(ctx, name); err != nil {
@@ -255,15 +255,15 @@ func (h *Host) waitHealthy(ctx context.Context, name string, timeout time.Durati
h.mu.RLock()
engine, exists := h.engines[name]
h.mu.RUnlock()
if !exists {
return fmt.Errorf("engine %s not found", name)
}
deadline := time.Now().Add(timeout)
ticker := time.NewTicker(1 * time.Second)
defer ticker.Stop()
for {
select {
case <-ticker.C:
@@ -294,7 +294,7 @@ func (h *Host) configureBridge(ctx context.Context, bridge *BridgeConfig) error
func (h *Host) collectMetrics(name string, engine engines.Engine) {
ticker := time.NewTicker(10 * time.Second)
defer ticker.Stop()
for range ticker.C {
h.mu.RLock()
if _, exists := h.engines[name]; !exists {
@@ -302,7 +302,7 @@ func (h *Host) collectMetrics(name string, engine engines.Engine) {
return // Engine stopped
}
h.mu.RUnlock()
metrics := engine.Metrics()
h.metrics.Record(name, metrics)
}
@@ -328,7 +328,7 @@ func (h *Host) StopAll(ctx context.Context) error {
engines = append(engines, name)
}
h.mu.Unlock()
var lastErr error
for _, name := range engines {
if err := h.StopEngine(ctx, name); err != nil {
@@ -348,7 +348,7 @@ type EngineStatus struct {
func (h *Host) GetAllStatus(ctx context.Context) (map[string]*EngineStatus, error) {
h.mu.RLock()
defer h.mu.RUnlock()
statuses := make(map[string]*EngineStatus)
for name, engine := range h.engines {
health, err := engine.Health(ctx)
@@ -375,12 +375,12 @@ func (h *Host) GetMetrics() map[string]*EngineMetrics {
func (h *Host) SaveState(path string) error {
h.mu.RLock()
defer h.mu.RUnlock()
state := &HostState{
Engines: make(map[string]*EngineState),
Networks: h.networks,
}
for name, engine := range h.engines {
state.Engines[name] = &EngineState{
Name: name,
@@ -392,12 +392,12 @@ func (h *Host) SaveState(path string) error {
Uptime: engine.Uptime().Seconds(),
}
}
data, err := json.MarshalIndent(state, "", " ")
if err != nil {
return err
}
return os.WriteFile(path, data, 0644)
}
@@ -407,25 +407,25 @@ func (h *Host) LoadState(path string) error {
if err != nil {
return err
}
var state HostState
if err := json.Unmarshal(data, &state); err != nil {
return err
}
h.mu.Lock()
defer h.mu.Unlock()
h.networks = state.Networks
// Note: Engines need to be restarted separately
return nil
}
// HostState represents the persistent state of the host
type HostState struct {
Engines map[string]*EngineState `json:"engines"`
Networks map[string]*NetworkInfo `json:"networks"`
Engines map[string]*EngineState `json:"engines"`
Networks map[string]*NetworkInfo `json:"networks"`
}
// EngineState represents the state of an engine
@@ -437,4 +437,4 @@ type EngineState struct {
RPC string `json:"rpc"`
WS string `json:"ws"`
Uptime float64 `json:"uptime_seconds"`
}
}
+2 -2
View File
@@ -169,7 +169,7 @@ func TestPortManager(t *testing.T) {
t.Run("ReservePorts", func(t *testing.T) {
pm := orchestrator.NewPortManager()
// Reserve specific ports
err := pm.Reserve(20000, 20001, 20002)
require.NoError(t, err)
@@ -231,4 +231,4 @@ func TestMetricsCollector(t *testing.T) {
require.Equal(t, 350.0, summary["total_uptime_seconds"])
require.Equal(t, 2, summary["healthy_engines"])
})
}
}
+7 -7
View File
@@ -10,8 +10,8 @@ import (
// Indexer provides cross-engine blockchain indexing
type Indexer struct {
mu sync.RWMutex
data map[string]*IndexData
mu sync.RWMutex
data map[string]*IndexData
}
// IndexData contains indexed blockchain data
@@ -35,7 +35,7 @@ func NewIndexer() *Indexer {
func (i *Indexer) Update(engine string, data *IndexData) {
i.mu.Lock()
defer i.mu.Unlock()
data.Engine = engine
data.LastUpdate = time.Now()
i.data[engine] = data
@@ -45,7 +45,7 @@ func (i *Indexer) Update(engine string, data *IndexData) {
func (i *Indexer) Get(engine string) *IndexData {
i.mu.RLock()
defer i.mu.RUnlock()
return i.data[engine]
}
@@ -53,7 +53,7 @@ func (i *Indexer) Get(engine string) *IndexData {
func (i *Indexer) GetAll() map[string]*IndexData {
i.mu.RLock()
defer i.mu.RUnlock()
result := make(map[string]*IndexData)
for k, v := range i.data {
result[k] = v
@@ -65,6 +65,6 @@ func (i *Indexer) GetAll() map[string]*IndexData {
func (i *Indexer) Clear(engine string) {
i.mu.Lock()
defer i.mu.Unlock()
delete(i.data, engine)
}
}
+34 -34
View File
@@ -15,12 +15,12 @@ import (
// StackManifest defines a multi-engine stack configuration
type StackManifest struct {
Name string `yaml:"name" json:"name"`
Version string `yaml:"version" json:"version"`
Description string `yaml:"description" json:"description"`
Engines []EngineConfig `yaml:"engines" json:"engines"`
Bridge *BridgeConfig `yaml:"bridge,omitempty" json:"bridge,omitempty"`
Networks []NetworkConfig `yaml:"networks,omitempty" json:"networks,omitempty"`
Name string `yaml:"name" json:"name"`
Version string `yaml:"version" json:"version"`
Description string `yaml:"description" json:"description"`
Engines []EngineConfig `yaml:"engines" json:"engines"`
Bridge *BridgeConfig `yaml:"bridge,omitempty" json:"bridge,omitempty"`
Networks []NetworkConfig `yaml:"networks,omitempty" json:"networks,omitempty"`
}
// EngineConfig defines an engine configuration
@@ -38,23 +38,23 @@ type EngineConfig struct {
Extra map[string]interface{} `yaml:"extra,omitempty" json:"extra,omitempty"`
WaitHealthy bool `yaml:"wait_healthy,omitempty" json:"wait_healthy,omitempty"`
DependsOn []string `yaml:"depends_on,omitempty" json:"depends_on,omitempty"`
// OP Stack specific
L1RPC string `yaml:"l1_rpc,omitempty" json:"l1_rpc,omitempty"`
Sequencer bool `yaml:"sequencer,omitempty" json:"sequencer,omitempty"`
L1RPC string `yaml:"l1_rpc,omitempty" json:"l1_rpc,omitempty"`
Sequencer bool `yaml:"sequencer,omitempty" json:"sequencer,omitempty"`
// Eth2 specific
ConsensusClient string `yaml:"consensus_client,omitempty" json:"consensus_client,omitempty"`
ExecutionClient string `yaml:"execution_client,omitempty" json:"execution_client,omitempty"`
ValidatorEnabled bool `yaml:"validator_enabled,omitempty" json:"validator_enabled,omitempty"`
ConsensusClient string `yaml:"consensus_client,omitempty" json:"consensus_client,omitempty"`
ExecutionClient string `yaml:"execution_client,omitempty" json:"execution_client,omitempty"`
ValidatorEnabled bool `yaml:"validator_enabled,omitempty" json:"validator_enabled,omitempty"`
}
// BridgeConfig defines bridge configuration between engines
type BridgeConfig struct {
Type string `yaml:"type" json:"type"` // awm, ibc, ccip
Source string `yaml:"source" json:"source"`
Destination string `yaml:"destination" json:"destination"`
RelayerKey string `yaml:"relayer_key,omitempty" json:"relayer_key,omitempty"`
Type string `yaml:"type" json:"type"` // awm, ibc, ccip
Source string `yaml:"source" json:"source"`
Destination string `yaml:"destination" json:"destination"`
RelayerKey string `yaml:"relayer_key,omitempty" json:"relayer_key,omitempty"`
Contracts map[string]string `yaml:"contracts,omitempty" json:"contracts,omitempty"`
}
@@ -73,11 +73,11 @@ func (m *StackManifest) Validate() error {
if m.Name == "" {
return fmt.Errorf("manifest name is required")
}
if len(m.Engines) == 0 {
return fmt.Errorf("at least one engine is required")
}
// Check engine names are unique
names := make(map[string]bool)
for _, e := range m.Engines {
@@ -88,7 +88,7 @@ func (m *StackManifest) Validate() error {
return fmt.Errorf("duplicate engine name: %s", e.Name)
}
names[e.Name] = true
// Validate engine type
switch e.Type {
case "lux", "geth", "op", "eth2":
@@ -96,7 +96,7 @@ func (m *StackManifest) Validate() error {
default:
return fmt.Errorf("invalid engine type: %s", e.Type)
}
// Check dependencies exist
for _, dep := range e.DependsOn {
depFound := false
@@ -111,13 +111,13 @@ func (m *StackManifest) Validate() error {
}
}
}
// Validate bridge if present
if m.Bridge != nil {
if m.Bridge.Source == "" || m.Bridge.Destination == "" {
return fmt.Errorf("bridge source and destination are required")
}
// Check source and destination exist
sourceFound := false
destFound := false
@@ -136,7 +136,7 @@ func (m *StackManifest) Validate() error {
return fmt.Errorf("bridge destination %s not found in engines", m.Bridge.Destination)
}
}
return nil
}
@@ -146,19 +146,19 @@ func LoadManifest(path string) (*StackManifest, error) {
if err != nil {
return nil, fmt.Errorf("failed to read manifest: %w", err)
}
var manifest StackManifest
// Try YAML first
if err := yaml.Unmarshal(data, &manifest); err == nil {
return &manifest, nil
}
// Try JSON
if err := json.Unmarshal(data, &manifest); err == nil {
return &manifest, nil
}
return nil, fmt.Errorf("failed to parse manifest as YAML or JSON")
}
@@ -169,11 +169,11 @@ func (m *StackManifest) Save(path string) error {
if err := os.MkdirAll(dir, 0755); err != nil {
return fmt.Errorf("failed to create directory: %w", err)
}
// Marshal based on extension
var data []byte
var err error
ext := filepath.Ext(path)
switch ext {
case ".json":
@@ -184,14 +184,14 @@ func (m *StackManifest) Save(path string) error {
// Default to YAML
data, err = yaml.Marshal(m)
}
if err != nil {
return fmt.Errorf("failed to marshal manifest: %w", err)
}
if err := ioutil.WriteFile(path, data, 0644); err != nil {
return fmt.Errorf("failed to write manifest: %w", err)
}
return nil
}
}
+12 -12
View File
@@ -39,7 +39,7 @@ func NewMetricsCollector() *MetricsCollector {
func (mc *MetricsCollector) Record(engine string, values map[string]interface{}) {
mc.mu.Lock()
defer mc.mu.Unlock()
em, exists := mc.metrics[engine]
if !exists {
em = &EngineMetrics{
@@ -48,10 +48,10 @@ func (mc *MetricsCollector) Record(engine string, values map[string]interface{})
}
mc.metrics[engine] = em
}
em.LastUpdated = time.Now()
em.Values = values
// Add to history (keep last 100 snapshots)
snapshot := MetricSnapshot{
Timestamp: em.LastUpdated,
@@ -67,7 +67,7 @@ func (mc *MetricsCollector) Record(engine string, values map[string]interface{})
func (mc *MetricsCollector) Get(engine string) *EngineMetrics {
mc.mu.RLock()
defer mc.mu.RUnlock()
return mc.metrics[engine]
}
@@ -75,7 +75,7 @@ func (mc *MetricsCollector) Get(engine string) *EngineMetrics {
func (mc *MetricsCollector) GetAll() map[string]*EngineMetrics {
mc.mu.RLock()
defer mc.mu.RUnlock()
result := make(map[string]*EngineMetrics)
for k, v := range mc.metrics {
result[k] = v
@@ -87,7 +87,7 @@ func (mc *MetricsCollector) GetAll() map[string]*EngineMetrics {
func (mc *MetricsCollector) Clear(engine string) {
mc.mu.Lock()
defer mc.mu.Unlock()
delete(mc.metrics, engine)
}
@@ -95,13 +95,13 @@ func (mc *MetricsCollector) Clear(engine string) {
func (mc *MetricsCollector) Summary() map[string]interface{} {
mc.mu.RLock()
defer mc.mu.RUnlock()
summary := make(map[string]interface{})
summary["engines"] = len(mc.metrics)
var totalUptime float64
var healthyCount int
for _, em := range mc.metrics {
if uptime, ok := em.Values["uptime_seconds"].(float64); ok {
totalUptime += uptime
@@ -110,9 +110,9 @@ func (mc *MetricsCollector) Summary() map[string]interface{} {
healthyCount++
}
}
summary["total_uptime_seconds"] = totalUptime
summary["healthy_engines"] = healthyCount
return summary
}
}
+12 -12
View File
@@ -11,10 +11,10 @@ import (
// PortManager manages port allocation for engines
type PortManager struct {
mu sync.Mutex
allocated map[uint16]bool
httpBase uint16
p2pBase uint16
mu sync.Mutex
allocated map[uint16]bool
httpBase uint16
p2pBase uint16
}
// NewPortManager creates a new port manager
@@ -30,12 +30,12 @@ func NewPortManager() *PortManager {
func (pm *PortManager) AllocateHTTP() uint16 {
pm.mu.Lock()
defer pm.mu.Unlock()
port := pm.httpBase
for pm.isInUse(port) || pm.allocated[port] {
port += 10 // Skip by 10 to avoid conflicts with P2P ports
}
pm.allocated[port] = true
return port
}
@@ -44,12 +44,12 @@ func (pm *PortManager) AllocateHTTP() uint16 {
func (pm *PortManager) AllocateP2P() uint16 {
pm.mu.Lock()
defer pm.mu.Unlock()
port := pm.p2pBase
for pm.isInUse(port) || pm.allocated[port] {
port += 10
}
pm.allocated[port] = true
return port
}
@@ -75,7 +75,7 @@ func (pm *PortManager) isInUse(port uint16) bool {
func (pm *PortManager) Reserve(ports ...uint16) error {
pm.mu.Lock()
defer pm.mu.Unlock()
// Check all ports are available first
for _, port := range ports {
if pm.allocated[port] {
@@ -85,11 +85,11 @@ func (pm *PortManager) Reserve(ports ...uint16) error {
return fmt.Errorf("port %d already in use", port)
}
}
// Reserve all ports
for _, port := range ports {
pm.allocated[port] = true
}
return nil
}
}
+4 -3
View File
@@ -7,12 +7,13 @@
package rpcpb
import (
_ "google.golang.org/genproto/googleapis/api/annotations"
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
unsafe "unsafe"
_ "google.golang.org/genproto/googleapis/api/annotations"
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
)
const (
+1
View File
@@ -8,6 +8,7 @@ package rpcpb
import (
context "context"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
+22 -21
View File
@@ -13,6 +13,10 @@ import (
"strings"
"sync"
"maps"
"slices"
"github.com/luxfi/ids"
"github.com/luxfi/netrunner/local"
"github.com/luxfi/netrunner/network"
"github.com/luxfi/netrunner/network/node"
@@ -20,9 +24,6 @@ import (
"github.com/luxfi/netrunner/utils/constants"
"github.com/luxfi/netrunner/ux"
"github.com/luxfi/node/config"
"github.com/luxfi/ids"
"maps"
"slices"
luxd_constants "github.com/luxfi/const"
"github.com/luxfi/log"
@@ -80,7 +81,7 @@ type localNetwork struct {
type chainInfo struct {
info *rpcpb.CustomChainInfo
chainID ids.ID
chainID ids.ID
blockchainID ids.ID
}
@@ -88,7 +89,7 @@ type localNetworkOptions struct {
execPath string
rootDataDir string
numNodes uint32
trackChains string
trackChains string
redirectNodesOutput bool
globalNodeConfig string
@@ -132,7 +133,7 @@ func newLocalNetwork(opts localNetworkOptions) (*localNetwork, error) {
customChainIDToInfo: make(map[ids.ID]chainInfo),
stopCh: make(chan struct{}),
nodeInfos: make(map[string]*rpcpb.NodeInfo),
chains: make(map[string]*rpcpb.ChainInfo),
chains: make(map[string]*rpcpb.ChainInfo),
}, nil
}
@@ -705,12 +706,12 @@ func (lc *localNetwork) updateChainInfo(ctx context.Context) error {
}
lc.customChainIDToInfo[blockchain.ID] = chainInfo{
info: &rpcpb.CustomChainInfo{
ChainName: blockchain.Name,
VmId: blockchain.VMID.String(),
PchainId: blockchain.NetID.String(),
BlockchainId: blockchain.ID.String(),
ChainName: blockchain.Name,
VmId: blockchain.VMID.String(),
PchainId: blockchain.NetID.String(),
BlockchainId: blockchain.ID.String(),
},
chainID: blockchain.NetID,
chainID: blockchain.NetID,
blockchainID: blockchain.ID,
}
}
@@ -758,7 +759,7 @@ func (lc *localNetwork) updateChainInfo(ctx context.Context) error {
}
lc.chains[chainIDStr] = &rpcpb.ChainInfo{
IsElastic: isElastic,
IsElastic: isElastic,
ElasticChainId: elasticChainID.String(),
ChainParticipants: &rpcpb.ChainParticipants{NodeNames: nodeNameList},
}
@@ -858,16 +859,16 @@ func (lc *localNetwork) updateNodeInfo() error {
}
lc.nodeInfos[name] = &rpcpb.NodeInfo{
Name: node.GetName(),
Uri: node.GetURL(),
Id: node.GetNodeID().String(),
ExecPath: node.GetBinaryPath(),
LogDir: node.GetLogsDir(),
DbDir: node.GetDbDir(),
Config: []byte(node.GetConfigFile()),
PluginDir: node.GetPluginDir(),
Name: node.GetName(),
Uri: node.GetURL(),
Id: node.GetNodeID().String(),
ExecPath: node.GetBinaryPath(),
LogDir: node.GetLogsDir(),
DbDir: node.GetDbDir(),
Config: []byte(node.GetConfigFile()),
PluginDir: node.GetPluginDir(),
WhitelistedChains: trackChains,
Paused: node.GetPaused(),
Paused: node.GetPaused(),
}
// update default exec and pluginDir if empty (snapshots started without these params)
+22 -21
View File
@@ -21,6 +21,11 @@ import (
"go.uber.org/multierr"
"maps"
"slices"
"github.com/luxfi/consensus/core"
"github.com/luxfi/ids"
"github.com/luxfi/netrunner/network"
"github.com/luxfi/netrunner/network/node"
"github.com/luxfi/netrunner/rpcpb"
@@ -28,15 +33,11 @@ import (
"github.com/luxfi/netrunner/utils/constants"
"github.com/luxfi/node/config"
"github.com/luxfi/node/message"
"github.com/luxfi/consensus/core"
"github.com/luxfi/node/network/peer"
"github.com/luxfi/ids"
"maps"
"slices"
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"github.com/luxfi/log"
"github.com/luxfi/math/set"
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/credentials/insecure"
@@ -51,8 +52,8 @@ const (
MinNodes uint32 = 1
DefaultNodes uint32 = 5
stopTimeout = 5 * time.Second
defaultStartTimeout = 5 * time.Minute
stopTimeout = 5 * time.Second
defaultStartTimeout = 5 * time.Minute
// waitForHealthyTimeout - 60s for local 5-node network bootstrap
// First startup takes longer as nodes need to establish consensus
// Subsequent restarts are faster (<10s) but initial bootstrap needs time
@@ -75,9 +76,9 @@ var (
ErrNodeNotFound = errors.New("node not found")
ErrPeerNotFound = errors.New("peer not found")
ErrStatusCanceled = errors.New("gRPC stream status canceled")
ErrNoChainSpec = errors.New("no blockchain spec was provided")
ErrNoChainID = errors.New("chainID is missing")
ErrNoElasticChainSpec = errors.New("no elastic chain spec was provided")
ErrNoChainSpec = errors.New("no blockchain spec was provided")
ErrNoChainID = errors.New("chainID is missing")
ErrNoElasticChainSpec = errors.New("no elastic chain spec was provided")
ErrNoValidatorSpec = errors.New("no validator spec was provided")
)
@@ -175,7 +176,7 @@ type Server interface {
type server struct {
mu *sync.RWMutex
cfg Config
cfg Config
logger log.Logger
rootCtx context.Context
@@ -221,7 +222,7 @@ func New(cfg Config, logger log.Logger) (Server, error) {
s := &server{
cfg: cfg,
logger: logger,
logger: logger,
closed: make(chan struct{}),
ln: listener,
gRPCServer: grpc.NewServer(),
@@ -381,7 +382,7 @@ func (s *server) Start(_ context.Context, req *rpcpb.StartRequest) (*rpcpb.Start
var (
execPath = req.GetExecPath()
numNodes = req.GetNumNodes()
trackChains = req.GetWhitelistedChains()
trackChains = req.GetWhitelistedChains()
rootDataDir = req.GetRootDataDir()
pid = int32(os.Getpid())
globalNodeConfig = req.GetGlobalNodeConfig()
@@ -410,7 +411,7 @@ func (s *server) Start(_ context.Context, req *rpcpb.StartRequest) (*rpcpb.Start
} else {
// CLI provided a specific rootDataDir - use it directly
// Trust the CLI to provide a properly structured path
networkName = getNetworkNameFromRootDir(rootDataDir)
_ = getNetworkNameFromRootDir(rootDataDir)
// Ensure the provided directory exists
if err = os.MkdirAll(rootDataDir, os.ModePerm); err != nil {
@@ -432,7 +433,7 @@ func (s *server) Start(_ context.Context, req *rpcpb.StartRequest) (*rpcpb.Start
execPath: execPath,
rootDataDir: rootDataDir,
numNodes: numNodes,
trackChains: trackChains,
trackChains: trackChains,
redirectNodesOutput: s.cfg.RedirectNodesOutput,
pluginDir: pluginDir,
globalNodeConfig: globalNodeConfig,
@@ -1278,7 +1279,7 @@ var _ peer.InboundHandler = &loggingInboundHandler{}
type loggingInboundHandler struct {
nodeName string
logger log.Logger
logger log.Logger
}
func (lh *loggingInboundHandler) HandleInbound(_ context.Context, msg message.InboundMessage) {
@@ -1601,7 +1602,7 @@ func getNetworkElasticChainSpec(
maxStakeDuration := time.Duration(spec.MaxStakeDuration) * time.Hour
elasticParticipantsSpec := network.ElasticChainSpec{
ChainID: &spec.ChainId,
ChainID: &spec.ChainId,
AssetName: spec.AssetName,
AssetSymbol: spec.AssetSymbol,
InitialSupply: spec.InitialSupply,
@@ -1638,7 +1639,7 @@ func getPermissionlessValidatorSpec(
stakeDuration := time.Duration(spec.StakeDuration) * time.Hour
validatorSpec := network.PermissionlessValidatorSpec{
ChainID: spec.ChainId,
ChainID: spec.ChainId,
AssetID: spec.AssetId,
NodeName: spec.NodeName,
StakedAmount: spec.StakedTokenAmount,
@@ -1652,7 +1653,7 @@ func getRemoveChainValidatorSpec(
spec *rpcpb.RemoveChainValidatorSpec,
) network.RemoveChainValidatorSpec {
validatorSpec := network.RemoveChainValidatorSpec{
ChainID: spec.ChainId,
ChainID: spec.ChainId,
NodeNames: spec.GetNodeNames(),
}
return validatorSpec
@@ -1730,13 +1731,13 @@ func getNetworkChainSpec(
PerNodeChainConfig: perNodeChainConfig,
// Use BlockchainAlias as the blockchain name for the P-Chain transaction
// This allows multiple chains to use the same VM (e.g., multiple EVM chains)
BlockchainName: spec.BlockchainAlias,
BlockchainName: spec.BlockchainAlias,
}
if spec.ChainSpec != nil {
participantsSpec := network.ParticipantsSpec{
Participants: spec.ChainSpec.Participants,
ChainConfig: chainConfigBytes,
ChainConfig: chainConfigBytes,
}
chainSpec.ParticipantsSpec = &participantsSpec
}
+16 -15
View File
@@ -17,23 +17,24 @@ import (
"testing"
"time"
"github.com/luxfi/node/api/admin"
"github.com/luxfi/node/message"
luxd_constants "github.com/luxfi/const"
"maps"
"slices"
luxd_constants "github.com/luxfi/const"
"github.com/luxfi/node/api/admin"
"github.com/luxfi/node/message"
"github.com/luxfi/node/vms/platformvm"
"github.com/luxfi/ids"
"github.com/luxfi/log"
"github.com/luxfi/metric"
"github.com/luxfi/netrunner/client"
"github.com/luxfi/netrunner/rpcpb"
"github.com/luxfi/netrunner/server"
"github.com/luxfi/netrunner/utils"
"github.com/luxfi/netrunner/utils/constants"
"github.com/luxfi/netrunner/ux"
"github.com/luxfi/ids"
"github.com/luxfi/log"
"github.com/luxfi/metric"
ginkgo "github.com/onsi/ginkgo/v2"
"github.com/onsi/gomega"
)
@@ -55,7 +56,7 @@ var (
gRPCGatewayEp string
execPath1 string
execPath2 string
evmPath string
evmPath string
genesisPath string
genesisContents string
@@ -332,8 +333,8 @@ var _ = ginkgo.Describe("[Start/Remove/Restart/Add/Stop]", func() {
resp, err := cli.CreateChains(ctx,
[]*rpcpb.BlockchainSpec{
{
VmName: "evm",
Genesis: genesisContents,
VmName: "evm",
Genesis: genesisContents,
ParticipantsSpec: &rpcpb.ParticipantsSpec{Participants: subnetParticipants},
},
},
@@ -364,8 +365,8 @@ var _ = ginkgo.Describe("[Start/Remove/Restart/Add/Stop]", func() {
resp, err := cli.CreateChains(ctx,
[]*rpcpb.BlockchainSpec{
{
VmName: "evm",
Genesis: genesisContents,
VmName: "evm",
Genesis: genesisContents,
ParticipantsSpec: &rpcpb.ParticipantsSpec{Participants: subnetParticipants2},
},
},
@@ -423,13 +424,13 @@ var _ = ginkgo.Describe("[Start/Remove/Restart/Add/Stop]", func() {
resp, err := cli.CreateChains(ctx,
[]*rpcpb.BlockchainSpec{
{
VmName: "evm",
Genesis: genesisContents,
VmName: "evm",
Genesis: genesisContents,
ParticipantsSpec: &rpcpb.ParticipantsSpec{Participants: disjointNewSubnetParticipants[0]},
},
{
VmName: "evm",
Genesis: genesisContents,
VmName: "evm",
Genesis: genesisContents,
ParticipantsSpec: &rpcpb.ParticipantsSpec{Participants: disjointNewSubnetParticipants[1]},
},
},
+7 -7
View File
@@ -12,16 +12,16 @@ const (
// Network run directory structure (flat, no nesting)
// Structure: ~/.lux/runs/<networkName>/run_<timestamp>/node1/, node2/, ...
RunsDir = "runs" // Top-level runs directory
RunDirPrefix = "run" // Prefix for timestamped run directories
DefaultNetwork = "local" // Default network name
RunsDir = "runs" // Top-level runs directory
RunDirPrefix = "run" // Prefix for timestamped run directories
DefaultNetwork = "local" // Default network name
// Unified chain config directory (shared across all nodes)
// Structure: ~/.lux/chains/<chainName>/genesis.json, config.json, upgrade.json
ChainsDir = "chains"
ChainGenesisFile = "genesis.json"
ChainConfigFile = "config.json"
ChainUpgradeFile = "upgrade.json"
ChainsDir = "chains"
ChainGenesisFile = "genesis.json"
ChainConfigFile = "config.json"
ChainUpgradeFile = "upgrade.json"
// Unified plugins directory
// Structure: ~/.lux/plugins/current/<vmid> (symlinks to actual binaries)
+2 -2
View File
@@ -8,11 +8,11 @@ import (
"os"
"time"
"github.com/luxfi/ids"
"github.com/luxfi/log"
rpcb "github.com/luxfi/netrunner/rpcpb"
"github.com/luxfi/netrunner/ux"
"github.com/luxfi/ids"
"github.com/luxfi/node/staking"
"github.com/luxfi/log"
)
const (