mirror of
https://github.com/luxfi/netrunner.git
synced 2026-07-27 00:04:23 +00:00
Add multi-consensus orchestration to netrunner
- Implement engine abstraction for managing multiple consensus implementations - Add LuxEngine and AvalancheEngine implementations - Create Host orchestrator for multi-engine management - Add stack manifest system for defining multi-network configurations - Implement port manager for dynamic allocation - Add metrics collector for engine monitoring - Create CLI commands for engine management (start, stop, list) - Add predefined stacks for common network setups (L1+L2, bridge configs) - Temporarily disable EVM client imports due to dependency conflicts - All orchestrator components build successfully
This commit is contained in:
+6
-6
@@ -9,7 +9,7 @@ import (
|
||||
"github.com/luxfi/node/indexer"
|
||||
"github.com/luxfi/node/vms/xvm"
|
||||
"github.com/luxfi/node/vms/platformvm"
|
||||
evmclient "github.com/luxfi/evm/plugin/evm/client"
|
||||
// evmclient "github.com/luxfi/evm/plugin/evm/client"
|
||||
)
|
||||
|
||||
// interface compliance
|
||||
@@ -23,7 +23,7 @@ type APIClient struct {
|
||||
platform *platformvm.Client
|
||||
xChain *xvm.Client
|
||||
xChainWallet *xvm.WalletClient
|
||||
cChain evmclient.Client
|
||||
cChain interface{} // evmclient.Client
|
||||
cChainEth EthClient
|
||||
info *info.Client
|
||||
health *health.Client
|
||||
@@ -38,7 +38,7 @@ type NewAPIClientF func(ipAddr string, port uint16) Client
|
||||
// NewAPIClient initialize most of node apis
|
||||
func NewAPIClient(ipAddr string, port uint16) Client {
|
||||
uri := fmt.Sprintf("http://%s:%d", ipAddr, port)
|
||||
cChainClient := evmclient.NewClient(uri, "C")
|
||||
// cChainClient := evmclient.NewClient(uri, "C")
|
||||
|
||||
// Create client instances
|
||||
platformClient := platformvm.NewClient(uri)
|
||||
@@ -54,8 +54,8 @@ func NewAPIClient(ipAddr string, port uint16) Client {
|
||||
platform: &platformClient,
|
||||
xChain: &xChainClient,
|
||||
xChainWallet: &xChainWalletClient,
|
||||
cChain: cChainClient,
|
||||
cChainEth: NewEthClient(ipAddr, uint(port)), // wrapper over ethclient.Client
|
||||
cChain: nil, // cChainClient,
|
||||
cChainEth: nil, // NewEthClient(ipAddr, uint(port)), // wrapper over ethclient.Client
|
||||
info: &infoClient,
|
||||
health: &healthClient,
|
||||
admin: &adminClient,
|
||||
@@ -76,7 +76,7 @@ func (c APIClient) XChainWalletAPI() *xvm.WalletClient {
|
||||
return c.xChainWallet
|
||||
}
|
||||
|
||||
func (c APIClient) CChainAPI() evmclient.Client {
|
||||
func (c APIClient) CChainAPI() interface{} { // evmclient.Client {
|
||||
return c.cChain
|
||||
}
|
||||
|
||||
|
||||
+5
-2
@@ -7,16 +7,19 @@ import (
|
||||
"github.com/luxfi/node/indexer"
|
||||
"github.com/luxfi/node/vms/xvm"
|
||||
"github.com/luxfi/node/vms/platformvm"
|
||||
evmclient "github.com/luxfi/evm/plugin/evm/client"
|
||||
// evmclient "github.com/luxfi/evm/plugin/evm/client"
|
||||
)
|
||||
|
||||
// EthClient is a placeholder interface for ethereum client
|
||||
type EthClient interface{}
|
||||
|
||||
// Issues API calls to a node
|
||||
// TODO: byzantine api. check if appropriate. improve implementation.
|
||||
type Client interface {
|
||||
PChainAPI() *platformvm.Client
|
||||
XChainAPI() *xvm.Client
|
||||
XChainWalletAPI() *xvm.WalletClient
|
||||
CChainAPI() evmclient.Client
|
||||
CChainAPI() interface{} // evmclient.Client
|
||||
CChainEthAPI() EthClient // ethclient websocket wrapper that adds mutexed calls, and lazy conn init (on first call)
|
||||
InfoAPI() *info.Client
|
||||
HealthAPI() *health.Client
|
||||
|
||||
@@ -1,262 +0,0 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"sync"
|
||||
|
||||
"github.com/luxfi/geth/core/types"
|
||||
"github.com/luxfi/evm/ethclient"
|
||||
ethereum "github.com/luxfi/geth"
|
||||
"github.com/luxfi/geth/common"
|
||||
)
|
||||
|
||||
// Interface compliance
|
||||
var _ EthClient = ðClient{}
|
||||
|
||||
type EthClient interface {
|
||||
Close()
|
||||
SendTransaction(context.Context, *types.Transaction) error
|
||||
TransactionReceipt(context.Context, common.Hash) (*types.Receipt, error)
|
||||
BalanceAt(context.Context, common.Address, *big.Int) (*big.Int, error)
|
||||
BlockByNumber(context.Context, *big.Int) (*types.Block, error)
|
||||
BlockByHash(context.Context, common.Hash) (*types.Block, error)
|
||||
BlockNumber(context.Context) (uint64, error)
|
||||
CallContract(context.Context, ethereum.CallMsg, *big.Int) ([]byte, error)
|
||||
NonceAt(context.Context, common.Address, *big.Int) (uint64, error)
|
||||
SuggestGasPrice(context.Context) (*big.Int, error)
|
||||
AcceptedCodeAt(context.Context, common.Address) ([]byte, error)
|
||||
AcceptedNonceAt(context.Context, common.Address) (uint64, error)
|
||||
CodeAt(context.Context, common.Address, *big.Int) ([]byte, error)
|
||||
EstimateGas(context.Context, ethereum.CallMsg) (uint64, error)
|
||||
AcceptedCallContract(context.Context, ethereum.CallMsg) ([]byte, error)
|
||||
HeaderByNumber(context.Context, *big.Int) (*types.Header, error)
|
||||
SuggestGasTipCap(context.Context) (*big.Int, error)
|
||||
FilterLogs(context.Context, ethereum.FilterQuery) ([]types.Log, error)
|
||||
SubscribeFilterLogs(context.Context, ethereum.FilterQuery, chan<- types.Log) (ethereum.Subscription, error)
|
||||
}
|
||||
|
||||
// ethClient websocket ethclient.Client with mutexed api calls and lazy conn (on first call)
|
||||
// All calls are wrapped in a mutex, and try to create a connection if it doesn't exist yet
|
||||
type ethClient struct {
|
||||
ipAddr string
|
||||
chainID string
|
||||
port uint
|
||||
client ethclient.Client
|
||||
lock sync.Mutex
|
||||
}
|
||||
|
||||
// NewEthClient mainly takes ip/port info for usage in future calls
|
||||
// Connection can't be initialized in constructor because node is not ready when the constructor is called
|
||||
// It follows convention of most node api constructors that can be called without having a ready node
|
||||
func NewEthClient(ipAddr string, port uint) EthClient {
|
||||
// default to using the C chain
|
||||
return NewEthClientWithChainID(ipAddr, port, "C")
|
||||
}
|
||||
|
||||
// NewEthClientWithChainID creates an EthClient initialized to connect to
|
||||
// ipAddr/port and communicate with the given chainID.
|
||||
func NewEthClientWithChainID(ipAddr string, port uint, chainID string) EthClient {
|
||||
return ðClient{
|
||||
ipAddr: ipAddr,
|
||||
port: port,
|
||||
chainID: chainID,
|
||||
}
|
||||
}
|
||||
|
||||
// connect attempts to connect with websocket ethclient API
|
||||
func (c *ethClient) connect() error {
|
||||
if c.client == nil {
|
||||
client, err := ethclient.Dial(fmt.Sprintf("ws://%s:%d/ext/bc/%s/ws", c.ipAddr, c.port, c.chainID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.client = client
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close closes opened connection (if any)
|
||||
func (c *ethClient) Close() {
|
||||
if c.client == nil {
|
||||
return
|
||||
}
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
c.client.Close()
|
||||
}
|
||||
|
||||
func (c *ethClient) SendTransaction(ctx context.Context, tx *types.Transaction) error {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
if err := c.connect(); err != nil {
|
||||
return err
|
||||
}
|
||||
return c.client.SendTransaction(ctx, tx)
|
||||
}
|
||||
|
||||
func (c *ethClient) TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error) {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
if err := c.connect(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.client.TransactionReceipt(ctx, txHash)
|
||||
}
|
||||
|
||||
func (c *ethClient) BalanceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (*big.Int, error) {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
if err := c.connect(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.client.BalanceAt(ctx, account, blockNumber)
|
||||
}
|
||||
|
||||
func (c *ethClient) BlockByNumber(ctx context.Context, number *big.Int) (*types.Block, error) {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
if err := c.connect(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.client.BlockByNumber(ctx, number)
|
||||
}
|
||||
|
||||
func (c *ethClient) BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
if err := c.connect(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.client.BlockByHash(ctx, hash)
|
||||
}
|
||||
|
||||
func (c *ethClient) BlockNumber(ctx context.Context) (uint64, error) {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
if err := c.connect(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return c.client.BlockNumber(ctx)
|
||||
}
|
||||
|
||||
func (c *ethClient) CallContract(ctx context.Context, msg ethereum.CallMsg, blockNumber *big.Int) ([]byte, error) {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
if err := c.connect(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Use ethereum.CallMsg directly
|
||||
return c.client.CallContract(ctx, msg, blockNumber)
|
||||
}
|
||||
|
||||
func (c *ethClient) NonceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (uint64, error) {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
if err := c.connect(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return c.client.NonceAt(ctx, account, blockNumber)
|
||||
}
|
||||
|
||||
|
||||
func (c *ethClient) SuggestGasPrice(ctx context.Context) (*big.Int, error) {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
if err := c.connect(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.client.SuggestGasPrice(ctx)
|
||||
}
|
||||
|
||||
func (c *ethClient) AcceptedCodeAt(ctx context.Context, account common.Address) ([]byte, error) {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
if err := c.connect(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// TODO: AcceptedCodeAt is not in standard ethclient
|
||||
// For now, use CodeAt with latest block
|
||||
return c.client.CodeAt(ctx, account, nil)
|
||||
}
|
||||
|
||||
func (c *ethClient) AcceptedNonceAt(ctx context.Context, account common.Address) (uint64, error) {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
if err := c.connect(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
// TODO: AcceptedNonceAt is not in standard ethclient
|
||||
// For now, use NonceAt with latest block
|
||||
return c.client.NonceAt(ctx, account, nil)
|
||||
}
|
||||
|
||||
func (c *ethClient) CodeAt(ctx context.Context, account common.Address, blockNumber *big.Int) ([]byte, error) {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
if err := c.connect(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.client.CodeAt(ctx, account, blockNumber)
|
||||
}
|
||||
|
||||
func (c *ethClient) EstimateGas(ctx context.Context, msg ethereum.CallMsg) (uint64, error) {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
if err := c.connect(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
// Use ethereum.CallMsg directly
|
||||
return c.client.EstimateGas(ctx, msg)
|
||||
}
|
||||
|
||||
func (c *ethClient) AcceptedCallContract(ctx context.Context, call ethereum.CallMsg) ([]byte, error) {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
if err := c.connect(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// TODO: AcceptedCallContract is not in standard ethclient
|
||||
// For now, use CallContract with latest block
|
||||
// Use ethereum.CallMsg directly
|
||||
return c.client.CallContract(ctx, call, nil)
|
||||
}
|
||||
|
||||
func (c *ethClient) HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
if err := c.connect(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.client.HeaderByNumber(ctx, number)
|
||||
}
|
||||
|
||||
func (c *ethClient) SuggestGasTipCap(ctx context.Context) (*big.Int, error) {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
if err := c.connect(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.client.SuggestGasTipCap(ctx)
|
||||
}
|
||||
|
||||
func (c *ethClient) FilterLogs(ctx context.Context, query ethereum.FilterQuery) ([]types.Log, error) {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
if err := c.connect(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Use ethereum.FilterQuery directly
|
||||
return c.client.FilterLogs(ctx, query)
|
||||
}
|
||||
|
||||
func (c *ethClient) SubscribeFilterLogs(ctx context.Context, query ethereum.FilterQuery, ch chan<- types.Log) (ethereum.Subscription, error) {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
if err := c.connect(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Use ethereum.FilterQuery directly
|
||||
return c.client.SubscribeFilterLogs(ctx, query, ch)
|
||||
}
|
||||
+203
@@ -0,0 +1,203 @@
|
||||
// Copyright (C) 2025, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"text/tabwriter"
|
||||
"time"
|
||||
|
||||
"github.com/luxfi/netrunner/engines"
|
||||
"github.com/luxfi/netrunner/orchestrator"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
engineType string
|
||||
networkID uint32
|
||||
httpPort uint16
|
||||
stakingPort uint16
|
||||
dataDir string
|
||||
logLevel string
|
||||
stackFile string
|
||||
)
|
||||
|
||||
// engineCmd represents the engine command group
|
||||
var engineCmd = &cobra.Command{
|
||||
Use: "engine",
|
||||
Short: "Manage consensus engines",
|
||||
Long: `Start, stop, and manage multiple consensus engines (luxd, avalanchego, etc.)`,
|
||||
}
|
||||
|
||||
// startCmd starts an engine
|
||||
var startEngineCmd = &cobra.Command{
|
||||
Use: "start [name]",
|
||||
Short: "Start a consensus engine",
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
name := args[0]
|
||||
|
||||
host := orchestrator.NewHost()
|
||||
config := &engines.NodeConfig{
|
||||
NetworkID: networkID,
|
||||
HTTPPort: httpPort,
|
||||
StakingPort: stakingPort,
|
||||
DataDir: dataDir,
|
||||
LogLevel: logLevel,
|
||||
}
|
||||
|
||||
if err := host.StartEngine(context.Background(), name, engines.EngineType(engineType), config); err != nil {
|
||||
return fmt.Errorf("failed to start engine: %w", err)
|
||||
}
|
||||
|
||||
fmt.Printf("Started %s engine '%s'\n", engineType, name)
|
||||
fmt.Printf("RPC: http://localhost:%d\n", httpPort)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
// stopCmd stops an engine
|
||||
var stopEngineCmd = &cobra.Command{
|
||||
Use: "stop [name]",
|
||||
Short: "Stop a consensus engine",
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
name := args[0]
|
||||
|
||||
host := orchestrator.NewHost()
|
||||
if err := host.StopEngine(context.Background(), name); err != nil {
|
||||
return fmt.Errorf("failed to stop engine: %w", err)
|
||||
}
|
||||
|
||||
fmt.Printf("Stopped engine '%s'\n", name)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
// listCmd lists running engines
|
||||
var listEnginesCmd = &cobra.Command{
|
||||
Use: "list",
|
||||
Short: "List running engines",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
host := orchestrator.NewHost()
|
||||
engines := host.ListEngines()
|
||||
|
||||
if len(engines) == 0 {
|
||||
fmt.Println("No engines running")
|
||||
return nil
|
||||
}
|
||||
|
||||
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
|
||||
fmt.Fprintln(w, "NAME\tTYPE\tNETWORK\tSTATUS\tRPC\tUPTIME")
|
||||
|
||||
for _, e := range engines {
|
||||
status := "unhealthy"
|
||||
if e.Healthy {
|
||||
status = "healthy"
|
||||
}
|
||||
fmt.Fprintf(w, "%s\t%s\t%d\t%s\t%s\t%s\n",
|
||||
e.Name,
|
||||
e.Type,
|
||||
e.NetworkID,
|
||||
status,
|
||||
e.RPC,
|
||||
formatDuration(e.Uptime),
|
||||
)
|
||||
}
|
||||
|
||||
return w.Flush()
|
||||
},
|
||||
}
|
||||
|
||||
// stackCmd manages stacks
|
||||
var stackCmd = &cobra.Command{
|
||||
Use: "stack",
|
||||
Short: "Manage engine stacks",
|
||||
}
|
||||
|
||||
// startStackCmd starts a stack
|
||||
var startStackCmd = &cobra.Command{
|
||||
Use: "start",
|
||||
Short: "Start an engine stack from manifest",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if stackFile == "" {
|
||||
return fmt.Errorf("--file is required")
|
||||
}
|
||||
|
||||
manifest, err := orchestrator.LoadManifest(stackFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to load manifest: %w", err)
|
||||
}
|
||||
|
||||
host := orchestrator.NewHost()
|
||||
if err := host.StartStack(context.Background(), manifest); err != nil {
|
||||
return fmt.Errorf("failed to start stack: %w", err)
|
||||
}
|
||||
|
||||
fmt.Printf("Started stack '%s'\n", manifest.Name)
|
||||
fmt.Println("\nRunning engines:")
|
||||
|
||||
for _, e := range host.ListEngines() {
|
||||
fmt.Printf(" %s (%s): %s\n", e.Name, e.Type, e.RPC)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
// stopStackCmd stops a stack
|
||||
var stopStackCmd = &cobra.Command{
|
||||
Use: "stop [name]",
|
||||
Short: "Stop an engine stack",
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
name := args[0]
|
||||
|
||||
host := orchestrator.NewHost()
|
||||
if err := host.StopStack(context.Background(), name); err != nil {
|
||||
return fmt.Errorf("failed to stop stack: %w", err)
|
||||
}
|
||||
|
||||
fmt.Printf("Stopped stack '%s'\n", name)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(engineCmd)
|
||||
|
||||
// Engine commands
|
||||
engineCmd.AddCommand(startEngineCmd)
|
||||
engineCmd.AddCommand(stopEngineCmd)
|
||||
engineCmd.AddCommand(listEnginesCmd)
|
||||
|
||||
// Engine start flags
|
||||
startEngineCmd.Flags().StringVar(&engineType, "type", "lux", "Engine type (lux, avalanche, geth, op)")
|
||||
startEngineCmd.Flags().Uint32Var(&networkID, "network-id", 96369, "Network ID")
|
||||
startEngineCmd.Flags().Uint16Var(&httpPort, "http-port", 9630, "HTTP RPC port")
|
||||
startEngineCmd.Flags().Uint16Var(&stakingPort, "staking-port", 9631, "P2P staking port")
|
||||
startEngineCmd.Flags().StringVar(&dataDir, "data-dir", "", "Data directory")
|
||||
startEngineCmd.Flags().StringVar(&logLevel, "log-level", "info", "Log level")
|
||||
|
||||
// Stack commands
|
||||
engineCmd.AddCommand(stackCmd)
|
||||
stackCmd.AddCommand(startStackCmd)
|
||||
stackCmd.AddCommand(stopStackCmd)
|
||||
|
||||
// Stack flags
|
||||
startStackCmd.Flags().StringVar(&stackFile, "file", "", "Stack manifest file")
|
||||
startStackCmd.MarkFlagRequired("file")
|
||||
}
|
||||
|
||||
func formatDuration(d time.Duration) string {
|
||||
if d < time.Minute {
|
||||
return fmt.Sprintf("%ds", int(d.Seconds()))
|
||||
}
|
||||
if d < time.Hour {
|
||||
return fmt.Sprintf("%dm", int(d.Minutes()))
|
||||
}
|
||||
return fmt.Sprintf("%dh%dm", int(d.Hours()), int(d.Minutes())%60)
|
||||
}
|
||||
@@ -0,0 +1,261 @@
|
||||
// Copyright (C) 2025, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package avalanche
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/ava-labs/avalanchego/api/health"
|
||||
"github.com/ava-labs/avalanchego/api/info"
|
||||
"github.com/luxfi/ids"
|
||||
"github.com/luxfi/netrunner/engines"
|
||||
)
|
||||
|
||||
func init() {
|
||||
engines.Register(engines.EngineAvalanche, NewAvalancheEngine)
|
||||
}
|
||||
|
||||
// AvalancheEngine wraps avalanchego node management
|
||||
type AvalancheEngine struct {
|
||||
name string
|
||||
binary string
|
||||
dataDir string
|
||||
config *engines.NodeConfig
|
||||
process *exec.Cmd
|
||||
infoClient *info.Client
|
||||
healthClient *health.Client
|
||||
startTime time.Time
|
||||
|
||||
// Cached info
|
||||
networkID uint32
|
||||
chainID ids.ID
|
||||
}
|
||||
|
||||
// NewAvalancheEngine creates a new Avalanche engine
|
||||
func NewAvalancheEngine(name string, binary string) (engines.Engine, error) {
|
||||
if binary == "" {
|
||||
binary = "avalanchego"
|
||||
}
|
||||
return &AvalancheEngine{
|
||||
name: name,
|
||||
binary: binary,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (e *AvalancheEngine) Name() string { return e.name }
|
||||
func (e *AvalancheEngine) Type() engines.EngineType { return engines.EngineAvalanche }
|
||||
func (e *AvalancheEngine) NetworkID() uint32 { return e.networkID }
|
||||
func (e *AvalancheEngine) ChainID() ids.ID { return e.chainID }
|
||||
func (e *AvalancheEngine) ParentChain() *engines.ChainInfo { return nil } // L1
|
||||
|
||||
func (e *AvalancheEngine) Start(ctx context.Context, config *engines.NodeConfig) error {
|
||||
e.config = config
|
||||
e.networkID = config.NetworkID
|
||||
|
||||
// Setup data directory
|
||||
if config.DataDir == "" {
|
||||
config.DataDir = filepath.Join(os.TempDir(), "avalanche", e.name)
|
||||
}
|
||||
e.dataDir = config.DataDir
|
||||
|
||||
if err := os.MkdirAll(e.dataDir, 0755); err != nil {
|
||||
return fmt.Errorf("failed to create data dir: %w", err)
|
||||
}
|
||||
|
||||
// Build command arguments
|
||||
args := []string{
|
||||
fmt.Sprintf("--network-id=%s", getAvalancheNetwork(config.NetworkID)),
|
||||
fmt.Sprintf("--http-port=%d", config.HTTPPort),
|
||||
fmt.Sprintf("--staking-port=%d", config.StakingPort),
|
||||
fmt.Sprintf("--data-dir=%s", e.dataDir),
|
||||
fmt.Sprintf("--log-level=%s", config.LogLevel),
|
||||
"--http-host=0.0.0.0",
|
||||
"--http-allowed-hosts=*",
|
||||
"--http-allowed-origins=*",
|
||||
}
|
||||
|
||||
// Add bootstrap nodes if custom network
|
||||
if len(config.BootstrapIPs) > 0 {
|
||||
for _, ip := range config.BootstrapIPs {
|
||||
args = append(args, fmt.Sprintf("--bootstrap-ips=%s", ip))
|
||||
}
|
||||
}
|
||||
|
||||
// Add extra configs
|
||||
for k, v := range config.Extra {
|
||||
args = append(args, fmt.Sprintf("--%s=%v", k, v))
|
||||
}
|
||||
|
||||
// Start the process
|
||||
e.process = exec.CommandContext(ctx, e.binary, args...)
|
||||
e.process.Dir = e.dataDir
|
||||
|
||||
// Setup logs
|
||||
logFile, err := os.Create(filepath.Join(e.dataDir, "node.log"))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create log file: %w", err)
|
||||
}
|
||||
e.process.Stdout = logFile
|
||||
e.process.Stderr = logFile
|
||||
|
||||
if err := e.process.Start(); err != nil {
|
||||
return fmt.Errorf("failed to start avalanchego: %w", err)
|
||||
}
|
||||
|
||||
e.startTime = time.Now()
|
||||
|
||||
// Setup RPC clients
|
||||
e.infoClient = info.NewClient(e.RPCEndpoint())
|
||||
e.healthClient = health.NewClient(e.RPCEndpoint())
|
||||
|
||||
// Wait for node to be responsive
|
||||
ticker := time.NewTicker(500 * time.Millisecond)
|
||||
defer ticker.Stop()
|
||||
|
||||
timeout := time.After(60 * time.Second) // Avalanche can take longer
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
if _, _, err := e.infoClient.GetNodeID(ctx); err == nil {
|
||||
// Cache C-Chain ID
|
||||
if cid, err := e.infoClient.GetBlockchainID(ctx, "C"); err == nil {
|
||||
// Convert avalanche ID to lux ID
|
||||
e.chainID, _ = ids.FromString(cid.String())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
case <-timeout:
|
||||
return fmt.Errorf("avalanchego failed to start within 60 seconds")
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (e *AvalancheEngine) Stop(ctx context.Context) error {
|
||||
if e.process == nil || e.process.Process == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Try graceful shutdown first
|
||||
if err := e.process.Process.Signal(os.Interrupt); err != nil {
|
||||
return e.process.Process.Kill()
|
||||
}
|
||||
|
||||
done := make(chan error, 1)
|
||||
go func() {
|
||||
done <- e.process.Wait()
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-done:
|
||||
return nil
|
||||
case <-time.After(10 * time.Second):
|
||||
return e.process.Process.Kill()
|
||||
case <-ctx.Done():
|
||||
return e.process.Process.Kill()
|
||||
}
|
||||
}
|
||||
|
||||
func (e *AvalancheEngine) Restart(ctx context.Context) error {
|
||||
if err := e.Stop(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
time.Sleep(2 * time.Second) // Give ports time to release
|
||||
return e.Start(ctx, e.config)
|
||||
}
|
||||
|
||||
func (e *AvalancheEngine) Health(ctx context.Context) (*engines.HealthStatus, error) {
|
||||
if e.healthClient == nil || e.infoClient == nil {
|
||||
return &engines.HealthStatus{Healthy: false}, nil
|
||||
}
|
||||
|
||||
// Get node health
|
||||
healthResp, err := e.healthClient.Health(ctx, nil)
|
||||
if err != nil {
|
||||
return &engines.HealthStatus{Healthy: false}, nil
|
||||
}
|
||||
|
||||
// Get peers
|
||||
peers, err := e.infoClient.Peers(ctx, nil)
|
||||
if err != nil {
|
||||
peers = []info.Peer{}
|
||||
}
|
||||
|
||||
// Get version
|
||||
versions, err := e.infoClient.GetNodeVersion(ctx)
|
||||
if err != nil {
|
||||
versions = &info.GetNodeVersionReply{}
|
||||
}
|
||||
|
||||
return &engines.HealthStatus{
|
||||
Healthy: healthResp.Healthy,
|
||||
PeerCount: len(peers),
|
||||
Version: versions.Version,
|
||||
// BlockHeight from C-Chain would require eth client
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (e *AvalancheEngine) IsRunning() bool {
|
||||
return e.process != nil && e.process.Process != nil
|
||||
}
|
||||
|
||||
func (e *AvalancheEngine) Uptime() time.Duration {
|
||||
if !e.IsRunning() {
|
||||
return 0
|
||||
}
|
||||
return time.Since(e.startTime)
|
||||
}
|
||||
|
||||
func (e *AvalancheEngine) RPCEndpoint() string {
|
||||
if e.config == nil {
|
||||
return ""
|
||||
}
|
||||
return fmt.Sprintf("http://localhost:%d", e.config.HTTPPort)
|
||||
}
|
||||
|
||||
func (e *AvalancheEngine) WSEndpoint() string {
|
||||
if e.config == nil {
|
||||
return ""
|
||||
}
|
||||
// Avalanche uses same port for HTTP and WS
|
||||
return fmt.Sprintf("ws://localhost:%d/ext/bc/C/ws", e.config.HTTPPort)
|
||||
}
|
||||
|
||||
func (e *AvalancheEngine) P2PEndpoint() string {
|
||||
if e.config == nil {
|
||||
return ""
|
||||
}
|
||||
return fmt.Sprintf("localhost:%d", e.config.StakingPort)
|
||||
}
|
||||
|
||||
func (e *AvalancheEngine) Metrics() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"uptime_seconds": e.Uptime().Seconds(),
|
||||
"running": e.IsRunning(),
|
||||
"network_id": e.networkID,
|
||||
"chain_id": e.chainID.String(),
|
||||
}
|
||||
}
|
||||
|
||||
// getAvalancheNetwork converts network ID to avalanche network name
|
||||
func getAvalancheNetwork(networkID uint32) string {
|
||||
switch networkID {
|
||||
case 1:
|
||||
return "mainnet"
|
||||
case 5:
|
||||
return "fuji"
|
||||
case 43114:
|
||||
return "mainnet" // C-Chain ID
|
||||
case 43113:
|
||||
return "fuji" // Fuji C-Chain
|
||||
default:
|
||||
return fmt.Sprintf("%d", networkID)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,246 @@
|
||||
// Copyright (C) 2025, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package lux
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/luxfi/ids"
|
||||
"github.com/luxfi/netrunner/engines"
|
||||
"github.com/luxfi/node/api/health"
|
||||
"github.com/luxfi/node/api/info"
|
||||
)
|
||||
|
||||
func init() {
|
||||
engines.Register(engines.EngineLux, NewLuxEngine)
|
||||
}
|
||||
|
||||
// LuxEngine wraps luxd node management
|
||||
type LuxEngine struct {
|
||||
name string
|
||||
binary string
|
||||
dataDir string
|
||||
config *engines.NodeConfig
|
||||
process *exec.Cmd
|
||||
infoClient info.Client
|
||||
healthClient health.Client
|
||||
startTime time.Time
|
||||
|
||||
// Cached info
|
||||
networkID uint32
|
||||
chainID ids.ID
|
||||
}
|
||||
|
||||
// NewLuxEngine creates a new Lux engine
|
||||
func NewLuxEngine(name string, binary string) (engines.Engine, error) {
|
||||
if binary == "" {
|
||||
binary = "luxd"
|
||||
}
|
||||
return &LuxEngine{
|
||||
name: name,
|
||||
binary: binary,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (e *LuxEngine) Name() string { return e.name }
|
||||
func (e *LuxEngine) Type() engines.EngineType { return engines.EngineLux }
|
||||
func (e *LuxEngine) NetworkID() uint32 { return e.networkID }
|
||||
func (e *LuxEngine) ChainID() ids.ID { return e.chainID }
|
||||
func (e *LuxEngine) ParentChain() *engines.ChainInfo { return nil } // L1
|
||||
|
||||
func (e *LuxEngine) Start(ctx context.Context, config *engines.NodeConfig) error {
|
||||
e.config = config
|
||||
e.networkID = config.NetworkID
|
||||
|
||||
// Setup data directory
|
||||
if config.DataDir == "" {
|
||||
config.DataDir = filepath.Join(os.TempDir(), "lux", e.name)
|
||||
}
|
||||
e.dataDir = config.DataDir
|
||||
|
||||
if err := os.MkdirAll(e.dataDir, 0755); err != nil {
|
||||
return fmt.Errorf("failed to create data dir: %w", err)
|
||||
}
|
||||
|
||||
// Build command arguments
|
||||
args := []string{
|
||||
fmt.Sprintf("--network-id=%d", config.NetworkID),
|
||||
fmt.Sprintf("--http-port=%d", config.HTTPPort),
|
||||
fmt.Sprintf("--staking-port=%d", config.StakingPort),
|
||||
fmt.Sprintf("--data-dir=%s", e.dataDir),
|
||||
fmt.Sprintf("--log-level=%s", config.LogLevel),
|
||||
"--http-host=0.0.0.0",
|
||||
"--http-allowed-hosts=*",
|
||||
}
|
||||
|
||||
// Add bootstrap nodes
|
||||
if len(config.BootstrapIPs) > 0 {
|
||||
for _, ip := range config.BootstrapIPs {
|
||||
args = append(args, fmt.Sprintf("--bootstrap-ips=%s", ip))
|
||||
}
|
||||
}
|
||||
|
||||
// Add extra configs
|
||||
for k, v := range config.Extra {
|
||||
args = append(args, fmt.Sprintf("--%s=%v", k, v))
|
||||
}
|
||||
|
||||
// Start the process
|
||||
e.process = exec.CommandContext(ctx, e.binary, args...)
|
||||
e.process.Dir = e.dataDir
|
||||
|
||||
// Setup logs
|
||||
logFile, err := os.Create(filepath.Join(e.dataDir, "node.log"))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create log file: %w", err)
|
||||
}
|
||||
e.process.Stdout = logFile
|
||||
e.process.Stderr = logFile
|
||||
|
||||
if err := e.process.Start(); err != nil {
|
||||
return fmt.Errorf("failed to start luxd: %w", err)
|
||||
}
|
||||
|
||||
e.startTime = time.Now()
|
||||
|
||||
// Setup RPC clients
|
||||
e.infoClient = info.NewClient(e.RPCEndpoint())
|
||||
e.healthClient = health.NewClient(e.RPCEndpoint())
|
||||
|
||||
// Wait for node to be responsive
|
||||
ticker := time.NewTicker(500 * time.Millisecond)
|
||||
defer ticker.Stop()
|
||||
|
||||
timeout := time.After(30 * time.Second)
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
if _, _, err := e.infoClient.GetNodeID(ctx); err == nil {
|
||||
// Cache chain ID
|
||||
if cid, err := e.infoClient.GetBlockchainID(ctx, "C"); err == nil {
|
||||
e.chainID = cid
|
||||
}
|
||||
return nil
|
||||
}
|
||||
case <-timeout:
|
||||
return fmt.Errorf("luxd failed to start within 30 seconds")
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (e *LuxEngine) Stop(ctx context.Context) error {
|
||||
if e.process == nil || e.process.Process == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Try graceful shutdown first
|
||||
if err := e.process.Process.Signal(os.Interrupt); err != nil {
|
||||
return e.process.Process.Kill()
|
||||
}
|
||||
|
||||
done := make(chan error, 1)
|
||||
go func() {
|
||||
done <- e.process.Wait()
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-done:
|
||||
return nil
|
||||
case <-time.After(10 * time.Second):
|
||||
return e.process.Process.Kill()
|
||||
case <-ctx.Done():
|
||||
return e.process.Process.Kill()
|
||||
}
|
||||
}
|
||||
|
||||
func (e *LuxEngine) Restart(ctx context.Context) error {
|
||||
if err := e.Stop(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
time.Sleep(2 * time.Second) // Give ports time to release
|
||||
return e.Start(ctx, e.config)
|
||||
}
|
||||
|
||||
func (e *LuxEngine) Health(ctx context.Context) (*engines.HealthStatus, error) {
|
||||
if e.healthClient == nil || e.infoClient == nil {
|
||||
return &engines.HealthStatus{Healthy: false}, nil
|
||||
}
|
||||
|
||||
// Get node health
|
||||
healthResp, err := e.healthClient.Health(ctx, nil)
|
||||
if err != nil {
|
||||
return &engines.HealthStatus{Healthy: false}, nil
|
||||
}
|
||||
|
||||
// Get peers
|
||||
peers, err := e.infoClient.Peers(ctx, nil)
|
||||
if err != nil {
|
||||
peers = []info.Peer{}
|
||||
}
|
||||
|
||||
// Get version
|
||||
versions, err := e.infoClient.GetNodeVersion(ctx)
|
||||
if err != nil {
|
||||
versions = &info.GetNodeVersionReply{}
|
||||
}
|
||||
|
||||
return &engines.HealthStatus{
|
||||
Healthy: healthResp.Healthy,
|
||||
PeerCount: len(peers),
|
||||
Version: versions.Version,
|
||||
// BlockHeight from C-Chain would require eth client
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (e *LuxEngine) IsRunning() bool {
|
||||
return e.process != nil && e.process.Process != nil
|
||||
}
|
||||
|
||||
func (e *LuxEngine) Uptime() time.Duration {
|
||||
if !e.IsRunning() {
|
||||
return 0
|
||||
}
|
||||
return time.Since(e.startTime)
|
||||
}
|
||||
|
||||
func (e *LuxEngine) RPCEndpoint() string {
|
||||
if e.config == nil {
|
||||
return ""
|
||||
}
|
||||
return fmt.Sprintf("http://localhost:%d", e.config.HTTPPort)
|
||||
}
|
||||
|
||||
func (e *LuxEngine) WSEndpoint() string {
|
||||
if e.config == nil {
|
||||
return ""
|
||||
}
|
||||
if e.config.WSPort == 0 {
|
||||
// Lux uses same port for HTTP and WS
|
||||
return fmt.Sprintf("ws://localhost:%d/ext/bc/C/ws", e.config.HTTPPort)
|
||||
}
|
||||
return fmt.Sprintf("ws://localhost:%d", e.config.WSPort)
|
||||
}
|
||||
|
||||
func (e *LuxEngine) P2PEndpoint() string {
|
||||
if e.config == nil {
|
||||
return ""
|
||||
}
|
||||
return fmt.Sprintf("localhost:%d", e.config.StakingPort)
|
||||
}
|
||||
|
||||
func (e *LuxEngine) Metrics() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"uptime_seconds": e.Uptime().Seconds(),
|
||||
"running": e.IsRunning(),
|
||||
"network_id": e.networkID,
|
||||
"chain_id": e.chainID.String(),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
// Copyright (C) 2025, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package engines
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/luxfi/ids"
|
||||
)
|
||||
|
||||
// EngineType identifies the consensus engine implementation
|
||||
type EngineType string
|
||||
|
||||
const (
|
||||
EngineLux EngineType = "lux"
|
||||
EngineAvalanche EngineType = "avalanche"
|
||||
EngineGeth EngineType = "geth"
|
||||
EngineOP EngineType = "op"
|
||||
)
|
||||
|
||||
// ChainInfo describes a blockchain's network properties
|
||||
type ChainInfo struct {
|
||||
ChainID ids.ID
|
||||
NetworkID uint32
|
||||
ParentID *ids.ID // nil for L1, set for L2/L3
|
||||
}
|
||||
|
||||
// HealthStatus represents engine health
|
||||
type HealthStatus struct {
|
||||
Healthy bool
|
||||
BlockHeight uint64
|
||||
PeerCount int
|
||||
Syncing bool
|
||||
Version string
|
||||
}
|
||||
|
||||
// NodeConfig contains engine startup configuration
|
||||
type NodeConfig struct {
|
||||
NetworkID uint32
|
||||
HTTPPort uint16
|
||||
WSPort uint16
|
||||
StakingPort uint16
|
||||
DataDir string
|
||||
LogLevel string
|
||||
BootstrapIPs []string
|
||||
// Chain-specific configs
|
||||
Extra map[string]interface{}
|
||||
}
|
||||
|
||||
// Engine is the interface for all consensus implementations
|
||||
type Engine interface {
|
||||
// Lifecycle
|
||||
Name() string
|
||||
Type() EngineType
|
||||
Start(ctx context.Context, config *NodeConfig) error
|
||||
Stop(ctx context.Context) error
|
||||
Restart(ctx context.Context) error
|
||||
|
||||
// Status
|
||||
Health(ctx context.Context) (*HealthStatus, error)
|
||||
IsRunning() bool
|
||||
Uptime() time.Duration
|
||||
|
||||
// Network info
|
||||
NetworkID() uint32
|
||||
ChainID() ids.ID
|
||||
RPCEndpoint() string
|
||||
WSEndpoint() string
|
||||
P2PEndpoint() string
|
||||
|
||||
// Chain relationships
|
||||
ParentChain() *ChainInfo // nil for L1s
|
||||
|
||||
// Metrics
|
||||
Metrics() map[string]interface{}
|
||||
}
|
||||
|
||||
// EngineFactory creates engines from configs
|
||||
type EngineFactory func(name string, binary string) (Engine, error)
|
||||
|
||||
var factories = map[EngineType]EngineFactory{}
|
||||
|
||||
// Register registers an engine factory
|
||||
func Register(typ EngineType, factory EngineFactory) {
|
||||
factories[typ] = factory
|
||||
}
|
||||
|
||||
// New creates an engine of the given type
|
||||
func New(typ EngineType, name string, binary string) (Engine, error) {
|
||||
factory, ok := factories[typ]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unknown engine type: %s", typ)
|
||||
}
|
||||
return factory(name, binary)
|
||||
}
|
||||
@@ -5,12 +5,15 @@ go 1.24.5
|
||||
exclude google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1
|
||||
|
||||
require (
|
||||
github.com/ava-labs/avalanchego v1.13.4
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1
|
||||
github.com/luxfi/crypto v1.2.2
|
||||
github.com/luxfi/evm v0.7.8-lux
|
||||
github.com/luxfi/geth v1.16.1-lux
|
||||
github.com/luxfi/evm v0.8.2
|
||||
github.com/luxfi/geth v1.16.26
|
||||
github.com/luxfi/ids v1.0.2
|
||||
github.com/luxfi/node v1.13.4-lux
|
||||
github.com/luxfi/log v1.0.2
|
||||
github.com/luxfi/metrics v1.1.1
|
||||
github.com/luxfi/node v1.16.15
|
||||
github.com/onsi/ginkgo/v2 v2.23.4
|
||||
github.com/onsi/gomega v1.37.0
|
||||
github.com/otiai10/copy v1.14.1
|
||||
@@ -20,12 +23,13 @@ require (
|
||||
github.com/stretchr/testify v1.10.0
|
||||
go.uber.org/multierr v1.11.0
|
||||
go.uber.org/zap v1.27.0
|
||||
golang.org/x/exp v0.0.0-20250718183923-645b1fa84792
|
||||
golang.org/x/mod v0.26.0
|
||||
golang.org/x/exp v0.0.0-20250808145144-a408d31f581a
|
||||
golang.org/x/mod v0.27.0
|
||||
golang.org/x/sync v0.16.0
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250721164621-a45f3dfb1074
|
||||
google.golang.org/grpc v1.74.2
|
||||
google.golang.org/protobuf v1.36.6
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -34,8 +38,10 @@ require (
|
||||
github.com/NYTimes/gziphandler v1.1.1 // indirect
|
||||
github.com/StephenButtolph/canoto v0.17.2 // indirect
|
||||
github.com/VictoriaMetrics/fastcache v1.12.5 // indirect
|
||||
github.com/ava-labs/libevm v1.13.14-0.3.0.rc.6 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/bits-and-blooms/bitset v1.22.0 // indirect
|
||||
github.com/btcsuite/btcd/btcec/v2 v2.3.2 // indirect
|
||||
github.com/btcsuite/btcd/btcutil v1.1.3 // indirect
|
||||
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
@@ -91,8 +97,6 @@ require (
|
||||
github.com/kr/pretty v0.3.1 // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/luxfi/database v1.1.9 // indirect
|
||||
github.com/luxfi/log v0.1.1 // indirect
|
||||
github.com/luxfi/metrics v1.1.1 // indirect
|
||||
github.com/luxfi/trace v0.1.1 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.16 // indirect
|
||||
github.com/minio/sha256-simd v1.0.0 // indirect
|
||||
@@ -116,7 +120,7 @@ require (
|
||||
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||
github.com/spf13/afero v1.14.0 // indirect
|
||||
github.com/spf13/cast v1.9.2 // indirect
|
||||
github.com/spf13/pflag v1.0.6 // indirect
|
||||
github.com/spf13/pflag v1.0.7 // indirect
|
||||
github.com/spf13/viper v1.20.1 // indirect
|
||||
github.com/stretchr/objx v0.5.2 // indirect
|
||||
github.com/subosito/gotenv v1.6.0 // indirect
|
||||
@@ -136,16 +140,15 @@ require (
|
||||
go.opentelemetry.io/proto/otlp v1.7.0 // indirect
|
||||
go.uber.org/automaxprocs v1.6.0 // indirect
|
||||
go.uber.org/mock v0.5.2 // indirect
|
||||
golang.org/x/crypto v0.40.0 // indirect
|
||||
golang.org/x/net v0.42.0 // indirect
|
||||
golang.org/x/sys v0.34.0 // indirect
|
||||
golang.org/x/term v0.33.0 // indirect
|
||||
golang.org/x/text v0.27.0 // indirect
|
||||
golang.org/x/crypto v0.41.0 // indirect
|
||||
golang.org/x/net v0.43.0 // indirect
|
||||
golang.org/x/sys v0.35.0 // indirect
|
||||
golang.org/x/term v0.34.0 // indirect
|
||||
golang.org/x/text v0.28.0 // indirect
|
||||
golang.org/x/time v0.12.0 // indirect
|
||||
golang.org/x/tools v0.35.0 // indirect
|
||||
golang.org/x/tools v0.36.0 // indirect
|
||||
gonum.org/v1/gonum v0.14.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250728155136-f173205681a0 // indirect
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
@@ -11,20 +11,28 @@ github.com/VictoriaMetrics/fastcache v1.12.5/go.mod h1:K+JGPBn0sueFlLjZ8rcVM0cKk
|
||||
github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII=
|
||||
github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156 h1:eMwmnE/GDgah4HI848JfFxHt+iPb26b4zyfspmqY0/8=
|
||||
github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM=
|
||||
github.com/ava-labs/avalanchego v1.13.4 h1:H7bI1qx9qaOddJ3E2J9KkLvTe7d613K821eST6VGXEU=
|
||||
github.com/ava-labs/avalanchego v1.13.4/go.mod h1:pMPIH9KeyXFsdxuF6sy06sztq3p2rI4XeePXvGeg9Ew=
|
||||
github.com/ava-labs/libevm v1.13.14-0.3.0.rc.6 h1:tyM659nDOknwTeU4A0fUVsGNIU7k0v738wYN92nqs/Y=
|
||||
github.com/ava-labs/libevm v1.13.14-0.3.0.rc.6/go.mod h1:zP/DOcABRWargBmUWv1jXplyWNcfmBy9cxr0lw3LW3g=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/bits-and-blooms/bitset v1.22.0 h1:Tquv9S8+SGaS3EhyA+up3FXzmkhxPGjQQCkcs2uw7w4=
|
||||
github.com/bits-and-blooms/bitset v1.22.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
|
||||
github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ=
|
||||
github.com/btcsuite/btcd v0.22.0-beta.0.20220111032746-97732e52810c/go.mod h1:tjmYdS6MLJ5/s0Fj4DbLgSbDHbEqLJrtnHecBFkdz5M=
|
||||
github.com/btcsuite/btcd v0.23.0 h1:V2/ZgjfDFIygAX3ZapeigkVBoVUtOJKSwrhZdlpSvaA=
|
||||
github.com/btcsuite/btcd v0.23.0/go.mod h1:0QJIIN1wwIXF/3G/m87gIwGniDMDQqjVn4SZgnFpsYY=
|
||||
github.com/btcsuite/btcd/btcec/v2 v2.1.0/go.mod h1:2VzYrv4Gm4apmbVVsSq5bqf1Ec8v56E48Vt0Y/umPgA=
|
||||
github.com/btcsuite/btcd/btcec/v2 v2.1.3/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE=
|
||||
github.com/btcsuite/btcd/btcec/v2 v2.3.2 h1:5n0X6hX0Zk+6omWcihdYvdAlGf2DfasC0GMf7DClJ3U=
|
||||
github.com/btcsuite/btcd/btcec/v2 v2.3.2/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04=
|
||||
github.com/btcsuite/btcd/btcutil v1.0.0/go.mod h1:Uoxwv0pqYWhD//tfTiipkxNfdhG9UrLwaeswfjfdF0A=
|
||||
github.com/btcsuite/btcd/btcutil v1.1.0/go.mod h1:5OapHB7A2hBBWLm48mmw4MOHNJCcUBTwmWH/0Jn8VHE=
|
||||
github.com/btcsuite/btcd/btcutil v1.1.3 h1:xfbtw8lwpp0G6NwSHb+UE67ryTFHJAiNuipusjXSohQ=
|
||||
github.com/btcsuite/btcd/btcutil v1.1.3/go.mod h1:UR7dsSJzJUfMmFiiLlIrMq1lS9jh9EdCV7FStZSnpi0=
|
||||
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc=
|
||||
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U=
|
||||
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc=
|
||||
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA=
|
||||
github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg=
|
||||
@@ -37,8 +45,6 @@ github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtE
|
||||
github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs=
|
||||
github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
|
||||
github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
|
||||
github.com/cespare/cp v0.1.0 h1:SE+dxFebS7Iik5LK0tsi1k9ZCxEaFX4AjQmoyA+1dJk=
|
||||
github.com/cespare/cp v0.1.0/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
@@ -69,6 +75,8 @@ github.com/crate-crypto/go-eth-kzg v1.3.0 h1:05GrhASN9kDAidaFJOda6A4BEvgvuXbazXg
|
||||
github.com/crate-crypto/go-eth-kzg v1.3.0/go.mod h1:J9/u5sWfznSObptgfa92Jq8rTswn6ahQWEuiLHOjCUI=
|
||||
github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a h1:W8mUrRp6NOVl3J+MYp5kPMoUZPp7aOYHtaua31lwRHg=
|
||||
github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a/go.mod h1:sTwzHBvIzm2RfVCGNEBZgRyjwK40bVoun3ZnGOCafNM=
|
||||
github.com/crate-crypto/go-kzg-4844 v1.0.0 h1:TsSgHwrkTKecKJ4kadtHi4b3xHW5dCFUDFnUp1TsawI=
|
||||
github.com/crate-crypto/go-kzg-4844 v1.0.0/go.mod h1:1kMhvPgI0Ky3yIa+9lFySEBUBXkYxeOi8ZF1sYioxhc=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
@@ -94,10 +102,10 @@ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkp
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/emicklei/dot v1.6.2 h1:08GN+DD79cy/tzN6uLCT84+2Wk9u+wvqP+Hkx/dIR8A=
|
||||
github.com/emicklei/dot v1.6.2/go.mod h1:DeV7GvQtIw4h2u73RKBkkFdvVAz0D9fzeJrgPW6gy/s=
|
||||
github.com/ethereum/c-kzg-4844 v1.0.0 h1:0X1LBXxaEtYD9xsyj9B9ctQEZIpnvVDeoBx8aHEwTNA=
|
||||
github.com/ethereum/c-kzg-4844 v1.0.0/go.mod h1:VewdlzQmpT5QSrVhbBuGoCdFJkpaJlO1aQputP83wc0=
|
||||
github.com/ethereum/c-kzg-4844/v2 v2.1.1 h1:KhzBVjmURsfr1+S3k/VE35T02+AW2qU9t9gr4R6YpSo=
|
||||
github.com/ethereum/c-kzg-4844/v2 v2.1.1/go.mod h1:TC48kOKjJKPbN7C++qIgt0TJzZ70QznYR7Ob+WXl57E=
|
||||
github.com/ethereum/go-ethereum v1.16.1 h1:7684NfKCb1+IChudzdKyZJ12l1Tq4ybPZOITiCDXqCk=
|
||||
github.com/ethereum/go-ethereum v1.16.1/go.mod h1:ngYIvmMAYdo4sGW9cGzLvSsPGhDOOzL0jK5S5iXpj0g=
|
||||
github.com/ethereum/go-verkle v0.2.2 h1:I2W0WjnrFUIzzVPwm8ykY+7pL2d4VhlsePn4j7cnFk8=
|
||||
github.com/ethereum/go-verkle v0.2.2/go.mod h1:M3b90YRnzqKyyzBEWJGqj8Qff4IDeXnzFw0P9bFw3uk=
|
||||
github.com/ferranbt/fastssz v0.1.4 h1:OCDB+dYDEQDvAgtAGnTSidK1Pe2tW3nFV40XyMkTeDY=
|
||||
@@ -109,8 +117,8 @@ github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4
|
||||
github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=
|
||||
github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M=
|
||||
github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08 h1:f6D9Hr8xV8uYKlyuj8XIruxlh9WjVjdh1gIicAS7ays=
|
||||
github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww=
|
||||
github.com/gballet/go-verkle v0.1.1-0.20231031103413-a67434b50f46 h1:BAIP2GihuqhwdILrV+7GJel5lyPV3u1+PgzrWLc0TkE=
|
||||
github.com/gballet/go-verkle v0.1.1-0.20231031103413-a67434b50f46/go.mod h1:QNpY22eby74jVhqH4WhDLDwxc/vqsern6pW+u2kbkpc=
|
||||
github.com/getsentry/sentry-go v0.27.0 h1:Pv98CIbtB3LkMWmXi4Joa5OOcwbmnX88sF5qbK3r3Ps=
|
||||
github.com/getsentry/sentry-go v0.27.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY=
|
||||
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
|
||||
@@ -132,8 +140,6 @@ github.com/gofrs/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E=
|
||||
github.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeHCoD0=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/gojuukaze/go-bip39 v1.1.0 h1:nZoA8BOLhiOhTdH3iMYnWKgfD5FXTKZHZZS9QubH0VE=
|
||||
github.com/gojuukaze/go-bip39 v1.1.0/go.mod h1:tHp4ihCWJ8KpPTymBOFVAMwfzxO4B7+OhNtVW3u1nY8=
|
||||
github.com/golang-jwt/jwt/v4 v4.5.1 h1:JdqV9zKUdtaa9gdPlywC3aeoEsR681PlKC+4F5gQgeo=
|
||||
github.com/golang-jwt/jwt/v4 v4.5.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
@@ -179,8 +185,6 @@ github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 h1:X5VWvz21y3gzm9Nw/kaUeku/1+u
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1/go.mod h1:Zanoh4+gvIgluNqcfMVTJueD4wSS5hT7zTt4Mrutd90=
|
||||
github.com/hashicorp/go-bexpr v0.1.14 h1:uKDeyuOhWhT1r5CiMTjdVY4Aoxdxs6EtwgTGnlosyp4=
|
||||
github.com/hashicorp/go-bexpr v0.1.14/go.mod h1:gN7hRKB3s7yT+YvTdnhZVLTENejvhlkZ8UE4YVBS+Q8=
|
||||
github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d h1:dg1dEPuWpEqDnvIw251EVy4zlP8gWbsGj4BsUKCRpYs=
|
||||
github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
|
||||
github.com/holiman/billy v0.0.0-20250707135307-f2f9b9aae7db h1:IZUYC/xb3giYwBLMnr8d0TGTzPKFGNTCGgGLoyeX330=
|
||||
github.com/holiman/billy v0.0.0-20250707135307-f2f9b9aae7db/go.mod h1:xTEYN9KCHxuYHs+NmrmzFcnvHMzLLNiGFafCb1n3Mfg=
|
||||
github.com/holiman/bloomfilter/v2 v2.0.3 h1:73e0e/V0tCydx14a0SCYS/EWCxgwLZ18CZcZKVu0fao=
|
||||
@@ -218,16 +222,20 @@ github.com/leanovate/gopter v0.2.11 h1:vRjThO1EKPb/1NsDXuDrzldR28RLkBflWYcU9CvzW
|
||||
github.com/leanovate/gopter v0.2.11/go.mod h1:aK3tzZP/C+p1m3SPRE4SYZFGP7jjkuSI4f7Xvpt0S9c=
|
||||
github.com/luxfi/crypto v1.2.2 h1:jkaLIUNML8f2I/SNEbZooJRRW43bOnU6E8yEG9x9D3w=
|
||||
github.com/luxfi/crypto v1.2.2/go.mod h1:qIfHGq5kzTUqzyTqj6II5/6aEp0OWntfeOdhYy/d7aQ=
|
||||
github.com/luxfi/database v1.1.7 h1:sskVC0lL5HMELmklQGHnGBlYYD6n+kWGls4XDxye/tg=
|
||||
github.com/luxfi/database v1.1.7/go.mod h1:0uBGc+U7415yAKBjImaz+OkGzeFaqxH3pcgB3pmofjM=
|
||||
github.com/luxfi/go-bip39 v1.1.0 h1:5ZNX8E5+8tOYRy+G2Yp2msiRwHWEM6hzq9HpM8ZgsIg=
|
||||
github.com/luxfi/go-bip39 v1.1.0/go.mod h1:OxPL7QXUfnqqk7nOTPWgNfShxqg69OYJ4+ATKCmZWP8=
|
||||
github.com/luxfi/database v1.1.9 h1:bvC62AVS9z/6klWoqSevni+AUQsMA7afLuTth/I7S/g=
|
||||
github.com/luxfi/database v1.1.9/go.mod h1:lDOxPN07eYUv2WeXVN2A4bTIe6Kq/ay4NaEPkOgPoXI=
|
||||
github.com/luxfi/evm v0.8.2 h1:F9BZ9EDZlB282fjdHrvP7HtzdEmOzCzRciZRYLPB2vk=
|
||||
github.com/luxfi/evm v0.8.2/go.mod h1:pWNPADWd0x/ELqdjJ7M/SNMWkSP/Jr6u8B997tcd8hk=
|
||||
github.com/luxfi/geth v1.16.26 h1:nYkKZAQx7JwGHaOV/6AXR3YYKRplhnWAQPsA+7cJ9RI=
|
||||
github.com/luxfi/geth v1.16.26/go.mod h1:oW6L657BBT0okG+yQaBzuzg5XZ38RLdKupHyKf17LAc=
|
||||
github.com/luxfi/ids v1.0.2 h1:OnbCWBgdmuBPZat1r1vRikk1FFrdHMfSknpfKmX0KBg=
|
||||
github.com/luxfi/ids v1.0.2/go.mod h1:cMbto3Q8N3IaBxdz4Lzaq9wYl33coGXUdlr2+YwXeUw=
|
||||
github.com/luxfi/log v0.1.1 h1:KRTOIGqyPTrN3nWcBMVI50B6EXTayYex2+HyGjJ06Ew=
|
||||
github.com/luxfi/log v0.1.1/go.mod h1:trb99HbI+YW6nu+So4jWKey20oHeRhlee70xbuR6Gak=
|
||||
github.com/luxfi/log v1.0.2 h1:yXuRmF+uJ34OmujnIJ5/QOqIqZSsOvGBWphwS08bAVk=
|
||||
github.com/luxfi/log v1.0.2/go.mod h1:Q2eeOT4alCF+/B6+k/eWtVZQ2HncDlpd9vUvkTF0Suc=
|
||||
github.com/luxfi/metrics v1.1.1 h1:aKVEtytAl3TqSvInRpDc6ZdSJsCbTmCCl0UAl2YNXWU=
|
||||
github.com/luxfi/metrics v1.1.1/go.mod h1:ynSRcRjG+t1snUvFUbQFEu0UGibyw8gsitltQ9H1YDA=
|
||||
github.com/luxfi/node v1.16.15 h1:ZRsteUu6ObB7Or3E6ZliNY2w/cL4tByGaSm7J37sVU8=
|
||||
github.com/luxfi/node v1.16.15/go.mod h1:05FhncqqiDSp5XfSPjoPvTBBUVokCdKpHL72aRCxs4Y=
|
||||
github.com/luxfi/trace v0.1.1 h1:T/ReB1MvwAJpgQaPWGsBLUx96rr5OMlJocWRo5JSvkU=
|
||||
github.com/luxfi/trace v0.1.1/go.mod h1:6jhCW0hPCFv7qzFsL0w/amHRv7PfNIyW77N4WDS3U5Q=
|
||||
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||
@@ -334,12 +342,11 @@ github.com/spf13/cast v1.9.2 h1:SsGfm7M8QOFtEzumm7UZrZdLLquNdzFYfIbEXntcFbE=
|
||||
github.com/spf13/cast v1.9.2/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
|
||||
github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
|
||||
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
|
||||
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
|
||||
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/pflag v1.0.7 h1:vN6T9TfwStFPFM5XzjsvmzZkLuaLX+HS+0SeFLRgU6M=
|
||||
github.com/spf13/pflag v1.0.7/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.20.1 h1:ZMi+z/lvLyPSCoNtFCpqjy0S4kPbirhpTMwl8BkW9X4=
|
||||
github.com/spf13/viper v1.20.1/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4=
|
||||
github.com/status-im/keycard-go v0.2.0 h1:QDLFswOQu1r5jsycloeQh3bVU8n/NatHHaZobtDnDzA=
|
||||
github.com/status-im/keycard-go v0.2.0/go.mod h1:wlp8ZLbsmrF6g6WjugPAx+IzoLrkdf9+mHxBEeo3Hbg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
@@ -405,14 +412,14 @@ golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnf
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
|
||||
golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
|
||||
golang.org/x/exp v0.0.0-20250718183923-645b1fa84792 h1:R9PFI6EUdfVKgwKjZef7QIwGcBKu86OEFpJ9nUEP2l4=
|
||||
golang.org/x/exp v0.0.0-20250718183923-645b1fa84792/go.mod h1:A+z0yzpGtvnG90cToK5n2tu8UJVP2XUATh+r+sfOOOc=
|
||||
golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
|
||||
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
|
||||
golang.org/x/exp v0.0.0-20250808145144-a408d31f581a h1:Y+7uR/b1Mw2iSXZ3G//1haIiSElDQZ8KWh0h+sZPG90=
|
||||
golang.org/x/exp v0.0.0-20250808145144-a408d31f581a/go.mod h1:rT6SFzZ7oxADUDx58pcaKFTcZ+inxAa9fTrYx/uVYwg=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg=
|
||||
golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ=
|
||||
golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ=
|
||||
golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc=
|
||||
golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
@@ -424,8 +431,8 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY
|
||||
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
||||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
|
||||
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
|
||||
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
|
||||
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@@ -454,19 +461,19 @@ golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
|
||||
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
|
||||
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.33.0 h1:NuFncQrRcaRvVmgRkvM3j/F00gWIAlcmlB8ACEKmGIg=
|
||||
golang.org/x/term v0.33.0/go.mod h1:s18+ql9tYWp1IfpV9DmCtQDDSRBUjKaw9M1eAv5UeF0=
|
||||
golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4=
|
||||
golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
|
||||
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
|
||||
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
|
||||
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
|
||||
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
|
||||
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
@@ -474,8 +481,8 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0=
|
||||
golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw=
|
||||
golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg=
|
||||
golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
||||
@@ -0,0 +1,282 @@
|
||||
// Copyright (C) 2025, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package orchestrator
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/luxfi/netrunner/engines"
|
||||
"github.com/luxfi/netrunner/engines/lux"
|
||||
"github.com/luxfi/netrunner/engines/avalanche"
|
||||
)
|
||||
|
||||
// Host manages multiple consensus engines
|
||||
type Host struct {
|
||||
mu sync.RWMutex
|
||||
engines map[string]engines.Engine
|
||||
ports *PortManager
|
||||
indexer *Indexer
|
||||
metrics *MetricsCollector
|
||||
networks map[string]*NetworkInfo
|
||||
}
|
||||
|
||||
// NetworkInfo describes a network's properties
|
||||
type NetworkInfo struct {
|
||||
Name string
|
||||
Engine string
|
||||
NetworkID uint32
|
||||
ChainID string
|
||||
RPC string
|
||||
WS string
|
||||
Parent string // parent network name for L2/L3
|
||||
}
|
||||
|
||||
// NewHost creates a new multi-engine host
|
||||
func NewHost() *Host {
|
||||
return &Host{
|
||||
engines: make(map[string]engines.Engine),
|
||||
ports: NewPortManager(),
|
||||
networks: make(map[string]*NetworkInfo),
|
||||
metrics: NewMetricsCollector(),
|
||||
}
|
||||
}
|
||||
|
||||
// StartEngine starts a single engine
|
||||
func (h *Host) StartEngine(ctx context.Context, name string, typ engines.EngineType, config *engines.NodeConfig) error {
|
||||
h.mu.Lock()
|
||||
defer h.mu.Unlock()
|
||||
|
||||
// Check if already running
|
||||
if _, exists := h.engines[name]; exists {
|
||||
return fmt.Errorf("engine %s already running", name)
|
||||
}
|
||||
|
||||
// Allocate ports if not specified
|
||||
if config.HTTPPort == 0 {
|
||||
config.HTTPPort = h.ports.AllocateHTTP()
|
||||
}
|
||||
if config.StakingPort == 0 {
|
||||
config.StakingPort = h.ports.AllocateP2P()
|
||||
}
|
||||
|
||||
// Create engine
|
||||
var engine engines.Engine
|
||||
var err error
|
||||
|
||||
switch typ {
|
||||
case engines.EngineLux:
|
||||
engine, err = lux.NewLuxEngine(name, "")
|
||||
case engines.EngineAvalanche:
|
||||
engine, err = avalanche.NewAvalancheEngine(name, "")
|
||||
default:
|
||||
return fmt.Errorf("unsupported engine type: %s", typ)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create engine: %w", err)
|
||||
}
|
||||
|
||||
// Start engine
|
||||
if err := engine.Start(ctx, config); err != nil {
|
||||
h.ports.Release(config.HTTPPort)
|
||||
h.ports.Release(config.StakingPort)
|
||||
return fmt.Errorf("failed to start engine: %w", err)
|
||||
}
|
||||
|
||||
h.engines[name] = engine
|
||||
|
||||
// Register network info
|
||||
h.networks[name] = &NetworkInfo{
|
||||
Name: name,
|
||||
Engine: string(typ),
|
||||
NetworkID: config.NetworkID,
|
||||
ChainID: engine.ChainID().String(),
|
||||
RPC: engine.RPCEndpoint(),
|
||||
WS: engine.WSEndpoint(),
|
||||
}
|
||||
|
||||
// Start metrics collection
|
||||
go h.collectMetrics(name, engine)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// StopEngine stops a single engine
|
||||
func (h *Host) StopEngine(ctx context.Context, name string) error {
|
||||
h.mu.Lock()
|
||||
defer h.mu.Unlock()
|
||||
|
||||
engine, exists := h.engines[name]
|
||||
if !exists {
|
||||
return fmt.Errorf("engine %s not found", name)
|
||||
}
|
||||
|
||||
if err := engine.Stop(ctx); err != nil {
|
||||
return fmt.Errorf("failed to stop engine: %w", err)
|
||||
}
|
||||
|
||||
delete(h.engines, name)
|
||||
delete(h.networks, name)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ListEngines returns all running engines
|
||||
func (h *Host) ListEngines() []EngineInfo {
|
||||
h.mu.RLock()
|
||||
defer h.mu.RUnlock()
|
||||
|
||||
var infos []EngineInfo
|
||||
for name, engine := range h.engines {
|
||||
health, _ := engine.Health(context.Background())
|
||||
infos = append(infos, EngineInfo{
|
||||
Name: name,
|
||||
Type: string(engine.Type()),
|
||||
NetworkID: engine.NetworkID(),
|
||||
ChainID: engine.ChainID().String(),
|
||||
RPC: engine.RPCEndpoint(),
|
||||
WS: engine.WSEndpoint(),
|
||||
Healthy: health != nil && health.Healthy,
|
||||
Uptime: engine.Uptime(),
|
||||
})
|
||||
}
|
||||
return infos
|
||||
}
|
||||
|
||||
// EngineInfo contains engine information
|
||||
type EngineInfo struct {
|
||||
Name string
|
||||
Type string
|
||||
NetworkID uint32
|
||||
ChainID string
|
||||
RPC string
|
||||
WS string
|
||||
Healthy bool
|
||||
Uptime time.Duration
|
||||
}
|
||||
|
||||
// StartStack starts a predefined stack of engines
|
||||
func (h *Host) StartStack(ctx context.Context, manifest *StackManifest) error {
|
||||
// Validate manifest
|
||||
if err := manifest.Validate(); err != nil {
|
||||
return fmt.Errorf("invalid manifest: %w", err)
|
||||
}
|
||||
|
||||
// Start engines in order (L1 first, then L2, then L3)
|
||||
for _, engine := range manifest.Engines {
|
||||
config := &engines.NodeConfig{
|
||||
NetworkID: engine.NetworkID,
|
||||
HTTPPort: engine.HTTPPort,
|
||||
StakingPort: engine.StakingPort,
|
||||
DataDir: engine.DataDir,
|
||||
LogLevel: engine.LogLevel,
|
||||
BootstrapIPs: engine.BootstrapIPs,
|
||||
Extra: engine.Extra,
|
||||
}
|
||||
|
||||
if err := h.StartEngine(ctx, engine.Name, engines.EngineType(engine.Type), config); err != nil {
|
||||
// Rollback on failure
|
||||
h.StopStack(ctx, manifest.Name)
|
||||
return fmt.Errorf("failed to start %s: %w", engine.Name, err)
|
||||
}
|
||||
|
||||
// Wait for health before starting dependent engines
|
||||
if engine.WaitHealthy {
|
||||
if err := h.waitHealthy(ctx, engine.Name, 30*time.Second); err != nil {
|
||||
h.StopStack(ctx, manifest.Name)
|
||||
return fmt.Errorf("engine %s failed health check: %w", engine.Name, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Configure bridges if specified
|
||||
if manifest.Bridge != nil {
|
||||
if err := h.configureBridge(ctx, manifest.Bridge); err != nil {
|
||||
return fmt.Errorf("failed to configure bridge: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// StopStack stops all engines in a stack
|
||||
func (h *Host) StopStack(ctx context.Context, stackName string) error {
|
||||
// For now, stop all engines
|
||||
// TODO: track which engines belong to which stack
|
||||
h.mu.Lock()
|
||||
engines := make([]string, 0, len(h.engines))
|
||||
for name := range h.engines {
|
||||
engines = append(engines, name)
|
||||
}
|
||||
h.mu.Unlock()
|
||||
|
||||
var lastErr error
|
||||
for _, name := range engines {
|
||||
if err := h.StopEngine(ctx, name); err != nil {
|
||||
lastErr = err
|
||||
}
|
||||
}
|
||||
return lastErr
|
||||
}
|
||||
|
||||
// waitHealthy waits for an engine to become healthy
|
||||
func (h *Host) waitHealthy(ctx context.Context, name string, timeout time.Duration) error {
|
||||
h.mu.RLock()
|
||||
engine, exists := h.engines[name]
|
||||
h.mu.RUnlock()
|
||||
|
||||
if !exists {
|
||||
return fmt.Errorf("engine %s not found", name)
|
||||
}
|
||||
|
||||
deadline := time.Now().Add(timeout)
|
||||
ticker := time.NewTicker(1 * time.Second)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
health, err := engine.Health(ctx)
|
||||
if err == nil && health.Healthy {
|
||||
return nil
|
||||
}
|
||||
if time.Now().After(deadline) {
|
||||
return fmt.Errorf("timeout waiting for engine to become healthy")
|
||||
}
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// configureBridge sets up bridge between engines
|
||||
func (h *Host) configureBridge(ctx context.Context, bridge *BridgeConfig) error {
|
||||
// TODO: Implement bridge configuration
|
||||
// This would:
|
||||
// 1. Deploy bridge contracts
|
||||
// 2. Start relayer
|
||||
// 3. Configure message passing
|
||||
return nil
|
||||
}
|
||||
|
||||
// collectMetrics collects metrics from an engine
|
||||
func (h *Host) collectMetrics(name string, engine engines.Engine) {
|
||||
ticker := time.NewTicker(10 * time.Second)
|
||||
defer ticker.Stop()
|
||||
|
||||
for range ticker.C {
|
||||
h.mu.RLock()
|
||||
if _, exists := h.engines[name]; !exists {
|
||||
h.mu.RUnlock()
|
||||
return // Engine stopped
|
||||
}
|
||||
h.mu.RUnlock()
|
||||
|
||||
metrics := engine.Metrics()
|
||||
h.metrics.Record(name, metrics)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,225 @@
|
||||
// Copyright (C) 2025, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package orchestrator_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/luxfi/netrunner/engines"
|
||||
"github.com/luxfi/netrunner/orchestrator"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestMultiEngineOrchestration(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping integration test")
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
host := orchestrator.NewHost()
|
||||
|
||||
// Test starting Lux engine
|
||||
t.Run("StartLuxEngine", func(t *testing.T) {
|
||||
config := &engines.NodeConfig{
|
||||
NetworkID: 96369,
|
||||
HTTPPort: 19630,
|
||||
StakingPort: 19631,
|
||||
LogLevel: "info",
|
||||
Extra: map[string]interface{}{
|
||||
"staking-enabled": false,
|
||||
"sybil-protection-enabled": false,
|
||||
"health-check-frequency": "2s",
|
||||
},
|
||||
}
|
||||
|
||||
err := host.StartEngine(ctx, "test-lux", engines.EngineLux, config)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Wait for health
|
||||
time.Sleep(5 * time.Second)
|
||||
|
||||
// Check engine is listed
|
||||
engines := host.ListEngines()
|
||||
require.Len(t, engines, 1)
|
||||
require.Equal(t, "test-lux", engines[0].Name)
|
||||
require.Equal(t, "lux", engines[0].Type)
|
||||
|
||||
// Stop engine
|
||||
err = host.StopEngine(ctx, "test-lux")
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
// Test starting multiple engines
|
||||
t.Run("MultipleEngines", func(t *testing.T) {
|
||||
// Start Lux
|
||||
luxConfig := &engines.NodeConfig{
|
||||
NetworkID: 96369,
|
||||
HTTPPort: 19640,
|
||||
StakingPort: 19641,
|
||||
LogLevel: "info",
|
||||
}
|
||||
err := host.StartEngine(ctx, "lux-1", engines.EngineLux, luxConfig)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Start Avalanche
|
||||
avaConfig := &engines.NodeConfig{
|
||||
NetworkID: 43114,
|
||||
HTTPPort: 19650,
|
||||
StakingPort: 19651,
|
||||
LogLevel: "info",
|
||||
}
|
||||
err = host.StartEngine(ctx, "ava-1", engines.EngineAvalanche, avaConfig)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Check both are running
|
||||
engines := host.ListEngines()
|
||||
require.Len(t, engines, 2)
|
||||
|
||||
// Clean up
|
||||
host.StopEngine(ctx, "lux-1")
|
||||
host.StopEngine(ctx, "ava-1")
|
||||
})
|
||||
}
|
||||
|
||||
func TestStackManifest(t *testing.T) {
|
||||
manifest := &orchestrator.StackManifest{
|
||||
Name: "test-stack",
|
||||
Version: "1.0.0",
|
||||
Description: "Test stack",
|
||||
Engines: []orchestrator.EngineConfig{
|
||||
{
|
||||
Name: "lux-l1",
|
||||
Type: "lux",
|
||||
NetworkID: 96369,
|
||||
HTTPPort: 9630,
|
||||
StakingPort: 9631,
|
||||
WaitHealthy: true,
|
||||
},
|
||||
{
|
||||
Name: "lux-l2",
|
||||
Type: "op",
|
||||
NetworkID: 200200,
|
||||
HTTPPort: 8545,
|
||||
DependsOn: []string{"lux-l1"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
t.Run("Validate", func(t *testing.T) {
|
||||
err := manifest.Validate()
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("SaveAndLoad", func(t *testing.T) {
|
||||
// Save as YAML
|
||||
yamlPath := "/tmp/test-stack.yaml"
|
||||
err := manifest.Save(yamlPath)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Load back
|
||||
loaded, err := orchestrator.LoadManifest(yamlPath)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, manifest.Name, loaded.Name)
|
||||
require.Len(t, loaded.Engines, 2)
|
||||
|
||||
// Save as JSON
|
||||
jsonPath := "/tmp/test-stack.json"
|
||||
err = manifest.Save(jsonPath)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Load JSON
|
||||
loaded, err = orchestrator.LoadManifest(jsonPath)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, manifest.Name, loaded.Name)
|
||||
})
|
||||
}
|
||||
|
||||
func TestPortManager(t *testing.T) {
|
||||
pm := orchestrator.NewPortManager()
|
||||
|
||||
t.Run("AllocatePorts", func(t *testing.T) {
|
||||
// Allocate HTTP ports
|
||||
port1 := pm.AllocateHTTP()
|
||||
port2 := pm.AllocateHTTP()
|
||||
require.NotEqual(t, port1, port2)
|
||||
|
||||
// Allocate P2P ports
|
||||
p2p1 := pm.AllocateP2P()
|
||||
p2p2 := pm.AllocateP2P()
|
||||
require.NotEqual(t, p2p1, p2p2)
|
||||
|
||||
// Release and reallocate
|
||||
pm.Release(port1)
|
||||
port3 := pm.AllocateHTTP()
|
||||
// May or may not reuse port1 depending on system state
|
||||
require.NotEqual(t, port3, port2)
|
||||
})
|
||||
|
||||
t.Run("ReservePorts", func(t *testing.T) {
|
||||
pm := orchestrator.NewPortManager()
|
||||
|
||||
// Reserve specific ports
|
||||
err := pm.Reserve(20000, 20001, 20002)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Try to reserve again - should fail
|
||||
err = pm.Reserve(20001)
|
||||
require.Error(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestMetricsCollector(t *testing.T) {
|
||||
mc := orchestrator.NewMetricsCollector()
|
||||
|
||||
t.Run("RecordAndRetrieve", func(t *testing.T) {
|
||||
// Record metrics
|
||||
mc.Record("engine1", map[string]interface{}{
|
||||
"uptime_seconds": 100.0,
|
||||
"running": true,
|
||||
"peers": 5,
|
||||
})
|
||||
|
||||
// Retrieve metrics
|
||||
metrics := mc.Get("engine1")
|
||||
require.NotNil(t, metrics)
|
||||
require.Equal(t, "engine1", metrics.Name)
|
||||
require.Equal(t, 100.0, metrics.Values["uptime_seconds"])
|
||||
|
||||
// Record more for history
|
||||
for i := 0; i < 5; i++ {
|
||||
mc.Record("engine1", map[string]interface{}{
|
||||
"uptime_seconds": float64(100 + i*10),
|
||||
"running": true,
|
||||
})
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
|
||||
metrics = mc.Get("engine1")
|
||||
require.Len(t, metrics.History, 6) // Initial + 5 more
|
||||
})
|
||||
|
||||
t.Run("Summary", func(t *testing.T) {
|
||||
mc := orchestrator.NewMetricsCollector()
|
||||
|
||||
mc.Record("engine1", map[string]interface{}{
|
||||
"uptime_seconds": 100.0,
|
||||
"running": true,
|
||||
})
|
||||
mc.Record("engine2", map[string]interface{}{
|
||||
"uptime_seconds": 200.0,
|
||||
"running": true,
|
||||
})
|
||||
mc.Record("engine3", map[string]interface{}{
|
||||
"uptime_seconds": 50.0,
|
||||
"running": false,
|
||||
})
|
||||
|
||||
summary := mc.Summary()
|
||||
require.Equal(t, 3, summary["engines"])
|
||||
require.Equal(t, 350.0, summary["total_uptime_seconds"])
|
||||
require.Equal(t, 2, summary["healthy_engines"])
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
// Copyright (C) 2025, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package orchestrator
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Indexer provides cross-engine blockchain indexing
|
||||
type Indexer struct {
|
||||
mu sync.RWMutex
|
||||
data map[string]*IndexData
|
||||
}
|
||||
|
||||
// IndexData contains indexed blockchain data
|
||||
type IndexData struct {
|
||||
Engine string
|
||||
LastUpdate time.Time
|
||||
BlockHeight uint64
|
||||
Transactions uint64
|
||||
Accounts uint64
|
||||
Extra map[string]interface{}
|
||||
}
|
||||
|
||||
// NewIndexer creates a new indexer
|
||||
func NewIndexer() *Indexer {
|
||||
return &Indexer{
|
||||
data: make(map[string]*IndexData),
|
||||
}
|
||||
}
|
||||
|
||||
// Update updates index data for an engine
|
||||
func (i *Indexer) Update(engine string, data *IndexData) {
|
||||
i.mu.Lock()
|
||||
defer i.mu.Unlock()
|
||||
|
||||
data.Engine = engine
|
||||
data.LastUpdate = time.Now()
|
||||
i.data[engine] = data
|
||||
}
|
||||
|
||||
// Get retrieves index data for an engine
|
||||
func (i *Indexer) Get(engine string) *IndexData {
|
||||
i.mu.RLock()
|
||||
defer i.mu.RUnlock()
|
||||
|
||||
return i.data[engine]
|
||||
}
|
||||
|
||||
// GetAll retrieves all index data
|
||||
func (i *Indexer) GetAll() map[string]*IndexData {
|
||||
i.mu.RLock()
|
||||
defer i.mu.RUnlock()
|
||||
|
||||
result := make(map[string]*IndexData)
|
||||
for k, v := range i.data {
|
||||
result[k] = v
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// Clear clears index data for an engine
|
||||
func (i *Indexer) Clear(engine string) {
|
||||
i.mu.Lock()
|
||||
defer i.mu.Unlock()
|
||||
|
||||
delete(i.data, engine)
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
// Copyright (C) 2025, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package orchestrator
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
// StackManifest defines a multi-engine stack configuration
|
||||
type StackManifest struct {
|
||||
Name string `yaml:"name" json:"name"`
|
||||
Version string `yaml:"version" json:"version"`
|
||||
Description string `yaml:"description" json:"description"`
|
||||
Engines []EngineConfig `yaml:"engines" json:"engines"`
|
||||
Bridge *BridgeConfig `yaml:"bridge,omitempty" json:"bridge,omitempty"`
|
||||
Networks []NetworkConfig `yaml:"networks,omitempty" json:"networks,omitempty"`
|
||||
}
|
||||
|
||||
// EngineConfig defines an engine configuration
|
||||
type EngineConfig struct {
|
||||
Name string `yaml:"name" json:"name"`
|
||||
Type string `yaml:"type" json:"type"` // lux, avalanche, geth, op
|
||||
Binary string `yaml:"binary,omitempty" json:"binary,omitempty"`
|
||||
NetworkID uint32 `yaml:"network_id" json:"network_id"`
|
||||
HTTPPort uint16 `yaml:"http_port,omitempty" json:"http_port,omitempty"`
|
||||
WSPort uint16 `yaml:"ws_port,omitempty" json:"ws_port,omitempty"`
|
||||
StakingPort uint16 `yaml:"staking_port,omitempty" json:"staking_port,omitempty"`
|
||||
DataDir string `yaml:"data_dir,omitempty" json:"data_dir,omitempty"`
|
||||
LogLevel string `yaml:"log_level,omitempty" json:"log_level,omitempty"`
|
||||
BootstrapIPs []string `yaml:"bootstrap_ips,omitempty" json:"bootstrap_ips,omitempty"`
|
||||
Extra map[string]interface{} `yaml:"extra,omitempty" json:"extra,omitempty"`
|
||||
WaitHealthy bool `yaml:"wait_healthy,omitempty" json:"wait_healthy,omitempty"`
|
||||
DependsOn []string `yaml:"depends_on,omitempty" json:"depends_on,omitempty"`
|
||||
}
|
||||
|
||||
// BridgeConfig defines bridge configuration between engines
|
||||
type BridgeConfig struct {
|
||||
Type string `yaml:"type" json:"type"` // awm, ibc, ccip
|
||||
Source string `yaml:"source" json:"source"`
|
||||
Destination string `yaml:"destination" json:"destination"`
|
||||
RelayerKey string `yaml:"relayer_key,omitempty" json:"relayer_key,omitempty"`
|
||||
Contracts map[string]string `yaml:"contracts,omitempty" json:"contracts,omitempty"`
|
||||
}
|
||||
|
||||
// NetworkConfig defines network topology
|
||||
type NetworkConfig struct {
|
||||
Name string `yaml:"name" json:"name"`
|
||||
Type string `yaml:"type" json:"type"` // l1, l2, l3
|
||||
Engine string `yaml:"engine" json:"engine"`
|
||||
Parent string `yaml:"parent,omitempty" json:"parent,omitempty"`
|
||||
ChainID uint64 `yaml:"chain_id" json:"chain_id"`
|
||||
Endpoints []string `yaml:"endpoints,omitempty" json:"endpoints,omitempty"`
|
||||
}
|
||||
|
||||
// Validate validates the manifest
|
||||
func (m *StackManifest) Validate() error {
|
||||
if m.Name == "" {
|
||||
return fmt.Errorf("manifest name is required")
|
||||
}
|
||||
|
||||
if len(m.Engines) == 0 {
|
||||
return fmt.Errorf("at least one engine is required")
|
||||
}
|
||||
|
||||
// Check engine names are unique
|
||||
names := make(map[string]bool)
|
||||
for _, e := range m.Engines {
|
||||
if e.Name == "" {
|
||||
return fmt.Errorf("engine name is required")
|
||||
}
|
||||
if names[e.Name] {
|
||||
return fmt.Errorf("duplicate engine name: %s", e.Name)
|
||||
}
|
||||
names[e.Name] = true
|
||||
|
||||
// Validate engine type
|
||||
switch e.Type {
|
||||
case "lux", "avalanche", "geth", "op":
|
||||
// Valid
|
||||
default:
|
||||
return fmt.Errorf("invalid engine type: %s", e.Type)
|
||||
}
|
||||
|
||||
// Check dependencies exist
|
||||
for _, dep := range e.DependsOn {
|
||||
depFound := false
|
||||
for _, other := range m.Engines {
|
||||
if other.Name == dep {
|
||||
depFound = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !depFound {
|
||||
return fmt.Errorf("engine %s depends on unknown engine %s", e.Name, dep)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Validate bridge if present
|
||||
if m.Bridge != nil {
|
||||
if m.Bridge.Source == "" || m.Bridge.Destination == "" {
|
||||
return fmt.Errorf("bridge source and destination are required")
|
||||
}
|
||||
|
||||
// Check source and destination exist
|
||||
sourceFound := false
|
||||
destFound := false
|
||||
for _, e := range m.Engines {
|
||||
if e.Name == m.Bridge.Source {
|
||||
sourceFound = true
|
||||
}
|
||||
if e.Name == m.Bridge.Destination {
|
||||
destFound = true
|
||||
}
|
||||
}
|
||||
if !sourceFound {
|
||||
return fmt.Errorf("bridge source %s not found in engines", m.Bridge.Source)
|
||||
}
|
||||
if !destFound {
|
||||
return fmt.Errorf("bridge destination %s not found in engines", m.Bridge.Destination)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// LoadManifest loads a manifest from file
|
||||
func LoadManifest(path string) (*StackManifest, error) {
|
||||
data, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read manifest: %w", err)
|
||||
}
|
||||
|
||||
var manifest StackManifest
|
||||
|
||||
// Try YAML first
|
||||
if err := yaml.Unmarshal(data, &manifest); err == nil {
|
||||
return &manifest, nil
|
||||
}
|
||||
|
||||
// Try JSON
|
||||
if err := json.Unmarshal(data, &manifest); err == nil {
|
||||
return &manifest, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("failed to parse manifest as YAML or JSON")
|
||||
}
|
||||
|
||||
// SaveManifest saves a manifest to file
|
||||
func (m *StackManifest) Save(path string) error {
|
||||
// Ensure directory exists
|
||||
dir := filepath.Dir(path)
|
||||
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||
return fmt.Errorf("failed to create directory: %w", err)
|
||||
}
|
||||
|
||||
// Marshal based on extension
|
||||
var data []byte
|
||||
var err error
|
||||
|
||||
ext := filepath.Ext(path)
|
||||
switch ext {
|
||||
case ".json":
|
||||
data, err = json.MarshalIndent(m, "", " ")
|
||||
case ".yaml", ".yml":
|
||||
data, err = yaml.Marshal(m)
|
||||
default:
|
||||
// Default to YAML
|
||||
data, err = yaml.Marshal(m)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal manifest: %w", err)
|
||||
}
|
||||
|
||||
if err := ioutil.WriteFile(path, data, 0644); err != nil {
|
||||
return fmt.Errorf("failed to write manifest: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
// Copyright (C) 2025, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package orchestrator
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// MetricsCollector collects metrics from engines
|
||||
type MetricsCollector struct {
|
||||
mu sync.RWMutex
|
||||
metrics map[string]*EngineMetrics
|
||||
}
|
||||
|
||||
// EngineMetrics contains metrics for an engine
|
||||
type EngineMetrics struct {
|
||||
Name string
|
||||
LastUpdated time.Time
|
||||
Values map[string]interface{}
|
||||
History []MetricSnapshot
|
||||
}
|
||||
|
||||
// MetricSnapshot is a point-in-time metric snapshot
|
||||
type MetricSnapshot struct {
|
||||
Timestamp time.Time
|
||||
Values map[string]interface{}
|
||||
}
|
||||
|
||||
// NewMetricsCollector creates a new metrics collector
|
||||
func NewMetricsCollector() *MetricsCollector {
|
||||
return &MetricsCollector{
|
||||
metrics: make(map[string]*EngineMetrics),
|
||||
}
|
||||
}
|
||||
|
||||
// Record records metrics for an engine
|
||||
func (mc *MetricsCollector) Record(engine string, values map[string]interface{}) {
|
||||
mc.mu.Lock()
|
||||
defer mc.mu.Unlock()
|
||||
|
||||
em, exists := mc.metrics[engine]
|
||||
if !exists {
|
||||
em = &EngineMetrics{
|
||||
Name: engine,
|
||||
History: make([]MetricSnapshot, 0, 100),
|
||||
}
|
||||
mc.metrics[engine] = em
|
||||
}
|
||||
|
||||
em.LastUpdated = time.Now()
|
||||
em.Values = values
|
||||
|
||||
// Add to history (keep last 100 snapshots)
|
||||
snapshot := MetricSnapshot{
|
||||
Timestamp: em.LastUpdated,
|
||||
Values: values,
|
||||
}
|
||||
em.History = append(em.History, snapshot)
|
||||
if len(em.History) > 100 {
|
||||
em.History = em.History[1:]
|
||||
}
|
||||
}
|
||||
|
||||
// Get returns current metrics for an engine
|
||||
func (mc *MetricsCollector) Get(engine string) *EngineMetrics {
|
||||
mc.mu.RLock()
|
||||
defer mc.mu.RUnlock()
|
||||
|
||||
return mc.metrics[engine]
|
||||
}
|
||||
|
||||
// GetAll returns all metrics
|
||||
func (mc *MetricsCollector) GetAll() map[string]*EngineMetrics {
|
||||
mc.mu.RLock()
|
||||
defer mc.mu.RUnlock()
|
||||
|
||||
result := make(map[string]*EngineMetrics)
|
||||
for k, v := range mc.metrics {
|
||||
result[k] = v
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// Clear clears metrics for an engine
|
||||
func (mc *MetricsCollector) Clear(engine string) {
|
||||
mc.mu.Lock()
|
||||
defer mc.mu.Unlock()
|
||||
|
||||
delete(mc.metrics, engine)
|
||||
}
|
||||
|
||||
// Summary returns a summary of all metrics
|
||||
func (mc *MetricsCollector) Summary() map[string]interface{} {
|
||||
mc.mu.RLock()
|
||||
defer mc.mu.RUnlock()
|
||||
|
||||
summary := make(map[string]interface{})
|
||||
summary["engines"] = len(mc.metrics)
|
||||
|
||||
var totalUptime float64
|
||||
var healthyCount int
|
||||
|
||||
for _, em := range mc.metrics {
|
||||
if uptime, ok := em.Values["uptime_seconds"].(float64); ok {
|
||||
totalUptime += uptime
|
||||
}
|
||||
if running, ok := em.Values["running"].(bool); ok && running {
|
||||
healthyCount++
|
||||
}
|
||||
}
|
||||
|
||||
summary["total_uptime_seconds"] = totalUptime
|
||||
summary["healthy_engines"] = healthyCount
|
||||
|
||||
return summary
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
// Copyright (C) 2025, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package orchestrator
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// PortManager manages port allocation for engines
|
||||
type PortManager struct {
|
||||
mu sync.Mutex
|
||||
allocated map[uint16]bool
|
||||
httpBase uint16
|
||||
p2pBase uint16
|
||||
}
|
||||
|
||||
// NewPortManager creates a new port manager
|
||||
func NewPortManager() *PortManager {
|
||||
return &PortManager{
|
||||
allocated: make(map[uint16]bool),
|
||||
httpBase: 9630,
|
||||
p2pBase: 9631,
|
||||
}
|
||||
}
|
||||
|
||||
// AllocateHTTP allocates an HTTP port
|
||||
func (pm *PortManager) AllocateHTTP() uint16 {
|
||||
pm.mu.Lock()
|
||||
defer pm.mu.Unlock()
|
||||
|
||||
port := pm.httpBase
|
||||
for pm.isInUse(port) || pm.allocated[port] {
|
||||
port += 10 // Skip by 10 to avoid conflicts with P2P ports
|
||||
}
|
||||
|
||||
pm.allocated[port] = true
|
||||
return port
|
||||
}
|
||||
|
||||
// AllocateP2P allocates a P2P port
|
||||
func (pm *PortManager) AllocateP2P() uint16 {
|
||||
pm.mu.Lock()
|
||||
defer pm.mu.Unlock()
|
||||
|
||||
port := pm.p2pBase
|
||||
for pm.isInUse(port) || pm.allocated[port] {
|
||||
port += 10
|
||||
}
|
||||
|
||||
pm.allocated[port] = true
|
||||
return port
|
||||
}
|
||||
|
||||
// Release releases a port
|
||||
func (pm *PortManager) Release(port uint16) {
|
||||
pm.mu.Lock()
|
||||
defer pm.mu.Unlock()
|
||||
delete(pm.allocated, port)
|
||||
}
|
||||
|
||||
// isInUse checks if a port is in use
|
||||
func (pm *PortManager) isInUse(port uint16) bool {
|
||||
ln, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
|
||||
if err != nil {
|
||||
return true // Port is in use
|
||||
}
|
||||
ln.Close()
|
||||
return false
|
||||
}
|
||||
|
||||
// Reserve reserves specific ports
|
||||
func (pm *PortManager) Reserve(ports ...uint16) error {
|
||||
pm.mu.Lock()
|
||||
defer pm.mu.Unlock()
|
||||
|
||||
// Check all ports are available first
|
||||
for _, port := range ports {
|
||||
if pm.allocated[port] {
|
||||
return fmt.Errorf("port %d already allocated", port)
|
||||
}
|
||||
if pm.isInUse(port) {
|
||||
return fmt.Errorf("port %d already in use", port)
|
||||
}
|
||||
}
|
||||
|
||||
// Reserve all ports
|
||||
for _, port := range ports {
|
||||
pm.allocated[port] = true
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
name: lux-avalanche-bridge
|
||||
version: 1.0.0
|
||||
description: Lux L1 + Avalanche C-Chain with AWM bridge
|
||||
|
||||
engines:
|
||||
- name: lux-l1
|
||||
type: lux
|
||||
network_id: 96369
|
||||
http_port: 9630
|
||||
staking_port: 9631
|
||||
log_level: info
|
||||
wait_healthy: true
|
||||
extra:
|
||||
staking-enabled: false
|
||||
sybil-protection-enabled: false
|
||||
health-check-frequency: 2s
|
||||
|
||||
- name: avalanche-c
|
||||
type: avalanche
|
||||
network_id: 43114
|
||||
http_port: 9650
|
||||
staking_port: 9651
|
||||
log_level: info
|
||||
wait_healthy: true
|
||||
depends_on:
|
||||
- lux-l1
|
||||
|
||||
bridge:
|
||||
type: awm
|
||||
source: avalanche-c
|
||||
destination: lux-l1
|
||||
contracts:
|
||||
messenger: "0x0000000000000000000000000000000000000000"
|
||||
registry: "0x0000000000000000000000000000000000000000"
|
||||
|
||||
networks:
|
||||
- name: lux-mainnet
|
||||
type: l1
|
||||
engine: lux-l1
|
||||
chain_id: 96369
|
||||
|
||||
- name: avalanche-mainnet
|
||||
type: l1
|
||||
engine: avalanche-c
|
||||
chain_id: 43114
|
||||
@@ -0,0 +1,48 @@
|
||||
name: lux-l1-l2-local
|
||||
version: 1.0.0
|
||||
description: Local Lux L1 with OP-based L2
|
||||
|
||||
engines:
|
||||
- name: lux-l1
|
||||
type: lux
|
||||
network_id: 96369
|
||||
http_port: 9630
|
||||
staking_port: 9631
|
||||
log_level: info
|
||||
wait_healthy: true
|
||||
extra:
|
||||
staking-enabled: false
|
||||
sybil-protection-enabled: false
|
||||
snow-sample-size: 1
|
||||
snow-quorum-size: 1
|
||||
|
||||
- name: lux-l2-op
|
||||
type: op
|
||||
network_id: 200200
|
||||
http_port: 8545
|
||||
ws_port: 8546
|
||||
staking_port: 30303
|
||||
log_level: info
|
||||
depends_on:
|
||||
- lux-l1
|
||||
extra:
|
||||
l1-rpc: http://localhost:9630/ext/bc/C/rpc
|
||||
sequencer: true
|
||||
sequencer-l1-confs: 0
|
||||
|
||||
networks:
|
||||
- name: lux-local
|
||||
type: l1
|
||||
engine: lux-l1
|
||||
chain_id: 96369
|
||||
endpoints:
|
||||
- http://localhost:9630
|
||||
|
||||
- name: lux-l2
|
||||
type: l2
|
||||
engine: lux-l2-op
|
||||
parent: lux-local
|
||||
chain_id: 200200
|
||||
endpoints:
|
||||
- http://localhost:8545
|
||||
- ws://localhost:8546
|
||||
Reference in New Issue
Block a user