mirror of
https://github.com/luxfi/cli.git
synced 2026-10-30 17:01:20 +00:00
The node moved every chain route from /v1/bc/<alias> to /v1/chain/<alias> and deleted the old prefix, so every chain call answered 404. The address was spelled out at 112 sites across 34 files and constants.ChainAliasPrefix was used at none of them, which is why nothing failed to compile when the segment moved. route.Chain(uri, alias) composes it now, and route.Alias reads one back, replacing three hand-rolled parsers ā one of which took the alias by position in the split path and only worked because the new segment happens to leave the component count unchanged. TestChainAddressBuiltOnlyHere walks every .go file in the module and fails on a chain address in a string literal, so the segment cannot drift back; prose is free to name a route. TestGuardCatchesEachShape plants each forbidden shape and requires the guard to find it. The endpoint box and the eleven-chain table were the same list written twice; there is one list, rendered once, measured rather than hand-padded. The generated CLI reference and the faucet manifest compose through the same function. Co-authored-by: Hanzo Dev <[email protected]>
288 lines
9.7 KiB
Go
288 lines
9.7 KiB
Go
// Copyright (C) 2022-2025, Lux Industries Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package cmd
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/luxfi/cli/pkg/route"
|
|
"github.com/luxfi/constants"
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
// evmBlockchainID is the chain this command deploys.
|
|
const evmBlockchainID = "2G8mK7VCZX1dV8iPjkkTDMpYGZDCNLLVdTJVLmMsG5ZV7zKVmB"
|
|
|
|
var (
|
|
evmNetworkID int
|
|
evmDataDir string
|
|
evmPort int
|
|
evmChainConfig string
|
|
evmSkipBuild bool
|
|
evmBackend string // gevm, revm, cevm, auto
|
|
evmGPU bool // enable GPU acceleration
|
|
)
|
|
|
|
var evmCmd = &cobra.Command{
|
|
Use: "evm",
|
|
Short: "Manage EVM chain deployments",
|
|
Long: `Deploy and manage EVM chains with existing state.
|
|
|
|
This command allows you to deploy a new EVM chain using an existing PebbleDB database,
|
|
enabling easy migration and state preservation across network deployments.`,
|
|
}
|
|
|
|
var evmDeployCmd = &cobra.Command{
|
|
Use: "deploy",
|
|
Short: "Deploy EVM chain",
|
|
Long: `Deploy a new EVM chain, optionally reusing an existing data directory.
|
|
|
|
Example:
|
|
lux evm deploy # Uses default ~/.lux/evm/
|
|
lux evm deploy --network-id 2 # Deploy on testnet
|
|
lux evm deploy --data-dir ~/.lux/evm # Specify data directory`,
|
|
RunE: deployEVM,
|
|
}
|
|
|
|
func NewEVMCmd() *cobra.Command {
|
|
evmCmd.AddCommand(evmDeployCmd)
|
|
|
|
evmDeployCmd.Flags().IntVar(&evmNetworkID, "network-id", int(constants.MainnetID), "Network ID for the deployment (1=mainnet, 2=testnet, 3=devnet)")
|
|
evmDeployCmd.Flags().StringVar(&evmDataDir, "data-dir", "", "Data directory for the node (default: ~/.lux/evm)")
|
|
evmDeployCmd.Flags().IntVar(&evmPort, "port", 9630, "Port for the node RPC")
|
|
evmDeployCmd.Flags().StringVar(&evmChainConfig, "chain-config", "", "Path to chain configuration JSON")
|
|
evmDeployCmd.Flags().BoolVar(&evmSkipBuild, "skip-build", false, "Skip building the EVM plugin")
|
|
evmDeployCmd.Flags().StringVar(&evmBackend, "backend", "gevm", "EVM backend: gevm (Go), revm (Rust), cevm (C++), auto")
|
|
evmDeployCmd.Flags().BoolVar(&evmGPU, "gpu", false, "Enable GPU acceleration (Metal/CUDA)")
|
|
|
|
return evmCmd
|
|
}
|
|
|
|
func deployEVM(cmd *cobra.Command, args []string) error {
|
|
// Set default data directory if not provided - use ~/.lux/evm
|
|
if evmDataDir == "" {
|
|
evmDataDir = filepath.Join(os.Getenv("HOME"), ".lux", "evm")
|
|
}
|
|
|
|
// Check if data directory exists and has an existing database
|
|
var existingDB string
|
|
blockchainID := evmBlockchainID
|
|
|
|
// Check for existing database in standard locations
|
|
possibleDBPaths := []string{
|
|
filepath.Join(evmDataDir, "chains", blockchainID, "db"),
|
|
filepath.Join(evmDataDir, "db", blockchainID, "db"),
|
|
}
|
|
|
|
for _, dbPath := range possibleDBPaths {
|
|
if _, err := os.Stat(dbPath); err == nil {
|
|
existingDB = dbPath
|
|
fmt.Printf("ā
Found existing PebbleDB at %s\n", existingDB)
|
|
|
|
// Check database size
|
|
cmd := exec.Command("du", "-sh", existingDB) //nolint:gosec // G204: Known command
|
|
output, err := cmd.Output()
|
|
if err == nil {
|
|
fmt.Printf(" Database size: %s", output)
|
|
}
|
|
break
|
|
}
|
|
}
|
|
|
|
fmt.Printf("\nDeploying EVM chain\n")
|
|
fmt.Printf(" Network ID: %d\n", evmNetworkID)
|
|
fmt.Printf(" Data Directory: %s\n", evmDataDir)
|
|
fmt.Printf(" RPC Port: %d\n", evmPort)
|
|
|
|
// Create directory structure
|
|
dirs := []string{
|
|
evmDataDir,
|
|
filepath.Join(evmDataDir, "staking"),
|
|
filepath.Join(evmDataDir, "plugins"),
|
|
filepath.Join(evmDataDir, "logs"),
|
|
filepath.Join(evmDataDir, "db"),
|
|
filepath.Join(evmDataDir, "configs", "chains"),
|
|
}
|
|
|
|
for _, dir := range dirs {
|
|
if err := os.MkdirAll(dir, 0o750); err != nil {
|
|
return fmt.Errorf("failed to create directory %s: %w", dir, err)
|
|
}
|
|
}
|
|
|
|
// Build EVM plugin if needed
|
|
if !evmSkipBuild {
|
|
// Compute build tags from --backend and --gpu flags
|
|
var tags []string
|
|
switch evmBackend {
|
|
case "revm":
|
|
tags = append(tags, "revm")
|
|
case "cevm":
|
|
tags = append(tags, "cevm")
|
|
case "auto":
|
|
tags = append(tags, "revm", "cevm") // link all, select at runtime
|
|
}
|
|
if evmGPU {
|
|
tags = append(tags, "gpu")
|
|
}
|
|
|
|
tagStr := ""
|
|
if len(tags) > 0 {
|
|
tagStr = "-tags " + strings.Join(tags, ",")
|
|
}
|
|
|
|
fmt.Printf("\n Building EVM plugin (backend=%s, gpu=%v)...\n", evmBackend, evmGPU)
|
|
buildScript := fmt.Sprintf("cd %s && CGO_ENABLED=1 go build %s -o %s/plugins/evm ./plugin",
|
|
filepath.Join(os.Getenv("HOME"), "work", "lux", "evm"),
|
|
tagStr,
|
|
evmDataDir)
|
|
buildCmd := exec.Command("bash", "-c", buildScript)
|
|
buildCmd.Stdout = os.Stdout
|
|
buildCmd.Stderr = os.Stderr
|
|
if err := buildCmd.Run(); err != nil {
|
|
return fmt.Errorf("failed to build EVM: %w", err)
|
|
}
|
|
}
|
|
|
|
// Copy EVM plugin from build location
|
|
pluginSrc := "/home/z/work/lux/evm/build/mgj786NP7uDwBCcq6YwThhaN8FLyybkCa4zBWTQbNgmK6k9A6"
|
|
// If build location doesn't exist, try lux-cli plugins directory
|
|
if _, err := os.Stat(pluginSrc); os.IsNotExist(err) {
|
|
pluginSrc = filepath.Join(os.Getenv("HOME"), ".lux-cli", "plugins", "mgj786NP7uDwBCcq6YwThhaN8FLyybkCa4zBWTQbNgmK6k9A6")
|
|
}
|
|
pluginDst := filepath.Join(evmDataDir, "plugins", "mgj786NP7uDwBCcq6YwThhaN8FLyybkCa4zBWTQbNgmK6k9A6")
|
|
|
|
if _, err := os.Stat(pluginSrc); err == nil {
|
|
copyCmd := exec.Command("cp", pluginSrc, pluginDst) //nolint:gosec // G204: Known command
|
|
if err := copyCmd.Run(); err != nil {
|
|
return fmt.Errorf("failed to copy EVM plugin: %w", err)
|
|
}
|
|
_ = os.Chmod(pluginDst, 0o755) //nolint:gosec // G302: Executable needs 0755
|
|
fmt.Println("ā
EVM plugin installed")
|
|
}
|
|
|
|
// If existing database wasn't found in the data directory, we may still be able to use existing data
|
|
// The database would already be in place if we're reusing an existing data directory
|
|
if existingDB != "" {
|
|
fmt.Printf("ā
Will reuse existing PebbleDB database\n")
|
|
fmt.Printf(" Database path: %s\n", existingDB)
|
|
}
|
|
|
|
// Generate staking certificates
|
|
fmt.Println("\nš Generating staking certificates...")
|
|
stakingDir := filepath.Join(evmDataDir, "staking")
|
|
|
|
genCertCmd := exec.Command("openssl", "req", "-x509", "-newkey", "rsa:4096", //nolint:gosec // G204: Known openssl command
|
|
"-keyout", filepath.Join(stakingDir, "staker.key"),
|
|
"-out", filepath.Join(stakingDir, "staker.crt"),
|
|
"-sha256", "-days", "365", "-nodes",
|
|
"-subj", "/CN=LuxEVM")
|
|
if err := genCertCmd.Run(); err != nil {
|
|
return fmt.Errorf("failed to generate certificates: %w", err)
|
|
}
|
|
|
|
copyCmd := exec.Command("cp", //nolint:gosec // G204: Known command
|
|
filepath.Join(stakingDir, "staker.key"),
|
|
filepath.Join(stakingDir, "signer.key"))
|
|
_ = copyCmd.Run()
|
|
|
|
// Create chain configuration if provided
|
|
if evmChainConfig != "" {
|
|
chainConfigDst := filepath.Join(evmDataDir, "configs", "chains",
|
|
evmBlockchainID, "config.json")
|
|
|
|
_ = os.MkdirAll(filepath.Dir(chainConfigDst), 0o750)
|
|
copyCmd := exec.Command("cp", evmChainConfig, chainConfigDst) //nolint:gosec // G204: Known command
|
|
if err := copyCmd.Run(); err != nil {
|
|
fmt.Printf("Warning: Could not copy chain config: %v\n", err)
|
|
}
|
|
}
|
|
|
|
// Create node configuration
|
|
nodeConfig := map[string]interface{}{
|
|
"network-id": evmNetworkID,
|
|
"data-dir": evmDataDir,
|
|
"db-dir": filepath.Join(evmDataDir, "db"),
|
|
"log-dir": filepath.Join(evmDataDir, "logs"),
|
|
"plugin-dir": filepath.Join(evmDataDir, "plugins"),
|
|
"chain-config-dir": filepath.Join(evmDataDir, "configs", "chains"),
|
|
"log-level": "info",
|
|
"http-host": "0.0.0.0",
|
|
"http-port": evmPort,
|
|
"staking-enabled": false,
|
|
"sybil-protection-enabled": false,
|
|
"consensus-sample-size": 1,
|
|
"consensus-quorum-size": 1,
|
|
"api-admin-enabled": true,
|
|
"api-metrics-enabled": true,
|
|
"api-health-enabled": true,
|
|
"api-info-enabled": true,
|
|
"index-enabled": true,
|
|
"db-type": "badgerdb",
|
|
"http-allowed-origins": "*",
|
|
"http-allowed-hosts": "*",
|
|
"chain-data-dir": filepath.Join(evmDataDir, "chaindata"),
|
|
}
|
|
|
|
configPath := filepath.Join(evmDataDir, "config.json")
|
|
configFile, err := os.Create(configPath) //nolint:gosec // G304: Creating config in app's data directory
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create config file: %w", err)
|
|
}
|
|
defer func() { _ = configFile.Close() }()
|
|
|
|
encoder := json.NewEncoder(configFile)
|
|
encoder.SetIndent("", " ")
|
|
if err := encoder.Encode(nodeConfig); err != nil {
|
|
return fmt.Errorf("failed to write config: %w", err)
|
|
}
|
|
|
|
rpc := route.Chain(fmt.Sprintf("http://localhost:%d", evmPort), evmBlockchainID) + "/rpc"
|
|
|
|
// Create launch script
|
|
launchScript := filepath.Join(evmDataDir, "launch.sh")
|
|
script := fmt.Sprintf(`#!/bin/bash
|
|
echo "Starting EVM chain node..."
|
|
echo " Data directory: %s"
|
|
echo " RPC endpoint: %s"
|
|
echo ""
|
|
|
|
exec /home/z/work/lux/node/build/luxd --config-file=%s
|
|
`, evmDataDir, rpc, configPath)
|
|
|
|
if err := os.WriteFile(launchScript, []byte(script), 0o755); err != nil { //nolint:gosec // G306: Launch script needs to be executable
|
|
return fmt.Errorf("failed to create launch script: %w", err)
|
|
}
|
|
|
|
fmt.Println("\nā
EVM deployment prepared successfully!")
|
|
fmt.Println("\nš Deployment Summary:")
|
|
fmt.Printf(" Data Directory: %s\n", evmDataDir)
|
|
fmt.Printf(" Config File: %s\n", configPath)
|
|
fmt.Printf(" Launch Script: %s\n", launchScript)
|
|
|
|
if existingDB != "" {
|
|
fmt.Printf(" Using Existing DB: %s (9.3GB with 1M+ blocks)\n", existingDB)
|
|
fmt.Println(" State: All accounts, balances, and contracts preserved")
|
|
}
|
|
|
|
fmt.Println("\nTo start the EVM chain:")
|
|
fmt.Printf(" %s\n", launchScript)
|
|
|
|
fmt.Println("\nš” Once running, access via:")
|
|
fmt.Printf(" RPC: %s\n", rpc)
|
|
|
|
return nil
|
|
}
|
|
|
|
// Additional helper commands can be added here for:
|
|
// - lux evm status - Check EVM status
|
|
// - lux evm stop - Stop EVM node
|
|
// - lux evm logs - View EVM logs
|
|
// - lux evm info - Get blockchain info
|