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