mirror of
https://github.com/luxfi/cli.git
synced 2026-10-30 17:01:20 +00:00
295 lines
10 KiB
Go
295 lines
10 KiB
Go
// Copyright (C) 2022-2025, Lux Industries Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package networkcmd
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/luxfi/cli/pkg/binutils"
|
|
"github.com/luxfi/cli/pkg/snapshot"
|
|
"github.com/luxfi/cli/pkg/ux"
|
|
"github.com/luxfi/constants"
|
|
"github.com/luxfi/netrunner/local"
|
|
"github.com/luxfi/netrunner/server"
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
const networkTypeLocal = "local"
|
|
|
|
var (
|
|
stopNetworkType string
|
|
stopNetworkID uint32 // Custom network ID for non-standard networks
|
|
forceStop bool
|
|
stopCleanupLogs bool // Clean up old logs when stopping
|
|
// Network type flags (same as start command for consistency)
|
|
stopMainnet bool
|
|
stopTestnet bool
|
|
stopDevnet bool
|
|
)
|
|
|
|
func newStopCmd() *cobra.Command {
|
|
cmd := &cobra.Command{
|
|
Use: "stop",
|
|
Short: "Stop the running network and save a snapshot",
|
|
Long: `The network stop command gracefully shuts down the running network and saves state.
|
|
|
|
SNAPSHOT BEHAVIOR:
|
|
|
|
By default, the network saves its state to a snapshot when stopping. This includes:
|
|
- Blockchain state (C-Chain, P-Chain, X-Chain, deployed chains)
|
|
- Validator state
|
|
- Database contents
|
|
|
|
The snapshot allows you to resume exactly where you left off with:
|
|
lux network start --<type> --snapshot-name <name>
|
|
|
|
OPTIONS:
|
|
|
|
--mainnet Stop mainnet network (network-id=1)
|
|
--testnet Stop testnet network (network-id=2)
|
|
--devnet Stop devnet network (network-id=3)
|
|
--network-id Stop network by ID (for custom networks)
|
|
--snapshot-name Name for the snapshot (default: default-snapshot)
|
|
--force Force stop without confirmation (use with caution)
|
|
|
|
SAFETY CHECKS:
|
|
|
|
If multiple networks are running, you MUST specify which one to stop:
|
|
lux network stop --devnet
|
|
lux network stop --testnet
|
|
|
|
Stopping mainnet or testnet requires explicit --mainnet/--testnet flag.
|
|
This prevents accidental disruption of production deployments.
|
|
|
|
EXAMPLES:
|
|
|
|
# Stop the running network (when only one is running)
|
|
lux network stop
|
|
|
|
# Stop specific network type (required when multiple running)
|
|
lux network stop --devnet
|
|
lux network stop --testnet
|
|
|
|
# Stop with named snapshot
|
|
lux network stop --devnet --snapshot-name my-snapshot
|
|
|
|
# Resume from snapshot later
|
|
lux network start --devnet --snapshot-name my-snapshot
|
|
|
|
NOTES:
|
|
|
|
- Snapshots preserve ALL network state including deployed chains
|
|
- Chain configurations (in ~/.lux/chains/) are NOT affected
|
|
- Use 'lux network clean' to wipe runtime data completely
|
|
- Only the specified network type is stopped (others remain running)
|
|
- Use 'lux dev stop' for the dev mode node (separate from network command)
|
|
|
|
SNAPSHOT vs CLEAN:
|
|
|
|
lux network stop - Saves state for resuming later
|
|
lux network clean - Deletes runtime data, preserves chain configs`,
|
|
|
|
RunE: StopNetwork,
|
|
Args: cobra.ExactArgs(0),
|
|
SilenceUsage: true,
|
|
}
|
|
cmd.Flags().StringVar(&snapshotName, "snapshot-name", constants.DefaultSnapshotName, "name of snapshot to use to save network state into")
|
|
cmd.Flags().BoolVar(&forceStop, "force", false, "force stop without confirmation (use with caution for mainnet/testnet)")
|
|
cmd.Flags().BoolVar(&stopCleanupLogs, "cleanup", false, "clean up old log files and stale run directories")
|
|
// Network type flags (same pattern as start command for consistency)
|
|
cmd.Flags().BoolVar(&stopMainnet, "mainnet", false, "stop mainnet network (network-id=1)")
|
|
cmd.Flags().BoolVar(&stopTestnet, "testnet", false, "stop testnet network (network-id=2)")
|
|
cmd.Flags().BoolVar(&stopDevnet, "devnet", false, "stop devnet network (network-id=3)")
|
|
cmd.Flags().Uint32Var(&stopNetworkID, "network-id", 0, "stop network by ID (for custom networks)")
|
|
return cmd
|
|
}
|
|
|
|
// StopNetwork stops the local network.
|
|
func StopNetwork(*cobra.Command, []string) error {
|
|
// Handle --mainnet, --testnet, --devnet flags (same pattern as start command)
|
|
if stopMainnet {
|
|
stopNetworkType = "mainnet"
|
|
} else if stopTestnet {
|
|
stopNetworkType = "testnet"
|
|
} else if stopDevnet {
|
|
stopNetworkType = "devnet"
|
|
} else if stopNetworkID > 0 {
|
|
// Map network ID to network type
|
|
switch stopNetworkID {
|
|
case 1:
|
|
stopNetworkType = "mainnet"
|
|
case 2:
|
|
stopNetworkType = "testnet"
|
|
case 3:
|
|
stopNetworkType = "devnet"
|
|
default:
|
|
stopNetworkType = fmt.Sprintf("custom-%d", stopNetworkID)
|
|
}
|
|
}
|
|
|
|
// Get all running networks
|
|
runningNetworks := app.GetAllRunningNetworks()
|
|
devRunning := isDevModeRunning()
|
|
|
|
// If network type not specified, apply safety checks
|
|
if stopNetworkType == "" {
|
|
// If multiple networks are running, require explicit --network-type
|
|
if len(runningNetworks) > 1 {
|
|
ux.Logger.PrintToUser("Multiple networks are running: %s", strings.Join(runningNetworks, ", "))
|
|
if devRunning {
|
|
ux.Logger.PrintToUser("Dev mode is also running (use 'lux dev stop' to stop it)")
|
|
}
|
|
ux.Logger.PrintToUser("")
|
|
ux.Logger.PrintToUser("Please specify which network to stop:")
|
|
for _, net := range runningNetworks {
|
|
ux.Logger.PrintToUser(" lux network stop --%s", net)
|
|
}
|
|
return fmt.Errorf("ambiguous: multiple networks running. Use --%s to specify which one to stop", runningNetworks[0])
|
|
}
|
|
|
|
// If dev mode + one network, warn but allow stopping the network
|
|
if devRunning && len(runningNetworks) == 1 {
|
|
ux.Logger.PrintToUser("Note: Dev mode is also running. Use 'lux dev stop' to stop it separately.")
|
|
}
|
|
|
|
// Auto-detect the single running network
|
|
if len(runningNetworks) == 1 {
|
|
stopNetworkType = runningNetworks[0]
|
|
} else if len(runningNetworks) == 0 {
|
|
// No network running
|
|
if devRunning {
|
|
return fmt.Errorf("no network running. Dev mode is running - use 'lux dev stop' to stop it")
|
|
}
|
|
ux.Logger.PrintToUser("No network is currently running.")
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// Normalize "local" to "custom"
|
|
if stopNetworkType == networkTypeLocal {
|
|
stopNetworkType = networkTypeCustom
|
|
}
|
|
|
|
// Safety check for mainnet/testnet: require explicit flag or force
|
|
if (stopNetworkType == "mainnet" || stopNetworkType == "testnet") && !forceStop {
|
|
// Check if this is a production-like network that needs protection
|
|
ux.Logger.PrintToUser("WARNING: You are about to stop %s network.", strings.ToUpper(stopNetworkType))
|
|
ux.Logger.PrintToUser("")
|
|
ux.Logger.PrintToUser("This could disrupt production services. Are you sure?")
|
|
ux.Logger.PrintToUser("")
|
|
ux.Logger.PrintToUser("To confirm, run:")
|
|
ux.Logger.PrintToUser(" lux network stop --%s --force", stopNetworkType)
|
|
return fmt.Errorf("stopping %s requires --force flag for safety", stopNetworkType)
|
|
}
|
|
|
|
// Check if the specified network is actually running
|
|
isRunning := false
|
|
for _, net := range runningNetworks {
|
|
if net == stopNetworkType {
|
|
isRunning = true
|
|
break
|
|
}
|
|
}
|
|
if !isRunning {
|
|
ux.Logger.PrintToUser("Network '%s' is not currently running.", stopNetworkType)
|
|
if len(runningNetworks) > 0 {
|
|
ux.Logger.PrintToUser("Running networks: %s", strings.Join(runningNetworks, ", "))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
ux.Logger.PrintToUser("Stopping network: %s", stopNetworkType)
|
|
|
|
// Create hot snapshot via gRPC → admin.snapshot API while network is still running
|
|
// This is the primary method - uses native BadgerDB incremental backup
|
|
if err := saveNetworkForType(stopNetworkType); err != nil {
|
|
ux.Logger.PrintToUser("Warning: failed to save snapshot: %v", err)
|
|
}
|
|
|
|
if killErr := binutils.KillgRPCServerProcessForNetwork(app, stopNetworkType); killErr != nil {
|
|
app.Log.Warn("failed killing server process", "error", killErr)
|
|
ux.Logger.PrintToUser("Warning: failed to shutdown server gracefully: %v", killErr)
|
|
} else {
|
|
ux.Logger.PrintToUser("Server (%s) shutdown gracefully", stopNetworkType)
|
|
}
|
|
|
|
// Clear network-specific state when stopping
|
|
if clearErr := app.ClearNetworkStateForType(stopNetworkType); clearErr != nil {
|
|
app.Log.Warn("failed to clear network state", "error", clearErr)
|
|
}
|
|
|
|
// Cleanup old logs and stale runs if requested
|
|
if stopCleanupLogs {
|
|
ux.Logger.PrintToUser("Cleaning up old logs and stale run directories...")
|
|
sm := snapshot.NewSnapshotManager(app.GetBaseDir())
|
|
cfg := snapshot.DefaultCleanupConfig()
|
|
cfg.Verbose = true
|
|
result := sm.Cleanup(cfg)
|
|
if result.TotalBytesFreed() > 0 {
|
|
ux.Logger.PrintToUser("Freed %s (%d logs, %d backups, %d stale runs)",
|
|
snapshot.FormatBytes(result.TotalBytesFreed()),
|
|
result.LogsDeleted, result.BackupsDeleted, result.StaleRunsDeleted)
|
|
}
|
|
for _, err := range result.Errors {
|
|
ux.Logger.PrintToUser("Warning: %v", err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// isDevModeRunning checks if a dev mode node is currently running
|
|
func isDevModeRunning() bool {
|
|
pidFile := filepath.Join(os.Getenv("HOME"), constants.BaseDirName, constants.DevDir, "luxd.pid")
|
|
pidData, err := os.ReadFile(pidFile) //nolint:gosec // G304: Reading PID file from app's directory
|
|
if err != nil {
|
|
return false
|
|
}
|
|
|
|
pid, err := strconv.Atoi(strings.TrimSpace(string(pidData)))
|
|
if err != nil {
|
|
return false
|
|
}
|
|
|
|
// Check if process exists using os.FindProcess
|
|
// Note: On Unix, FindProcess always succeeds, but we verify by checking /proc
|
|
// On Windows, we check if we can open the process
|
|
return isProcessRunning(pid)
|
|
}
|
|
|
|
func saveNetworkForType(networkType string) error {
|
|
cli, err := binutils.NewGRPCClient(binutils.WithAvoidRPCVersionCheck(true), binutils.WithNetworkType(networkType))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer func() { _ = cli.Close() }()
|
|
|
|
ctx := binutils.GetAsyncContext()
|
|
|
|
_, err = cli.RemoveSnapshot(ctx, snapshotName)
|
|
if err != nil {
|
|
if server.IsServerError(err, server.ErrNotBootstrapped) {
|
|
ux.Logger.PrintToUser("Network already stopped.")
|
|
return nil
|
|
}
|
|
// it we try to stop a network with a new snapshot name, remove snapshot
|
|
// will fail, so we cover here that expected case
|
|
if !server.IsServerError(err, local.ErrSnapshotNotFound) {
|
|
return fmt.Errorf("failed stop network with a snapshot: %w", err)
|
|
}
|
|
}
|
|
|
|
_, err = cli.SaveSnapshot(ctx, snapshotName)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to stop network with a snapshot: %w", err)
|
|
}
|
|
ux.Logger.PrintToUser("Network stopped successfully.")
|
|
|
|
return nil
|
|
}
|