Files

254 lines
7.3 KiB
Go

// Copyright (C) 2022, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package subnetcmd
import (
"errors"
"fmt"
"unicode"
"github.com/luxfi/cli/v2/pkg/constants"
"github.com/luxfi/cli/v2/pkg/utils"
"github.com/luxfi/cli/v2/pkg/models"
"github.com/luxfi/cli/v2/pkg/ux"
"github.com/luxfi/cli/v2/pkg/vm"
"github.com/spf13/cobra"
"golang.org/x/mod/semver"
)
const (
forceFlag = "force"
latest = "latest"
)
var (
forceCreate bool
useSubnetEvm bool
genesisFile string
vmFile string
useCustom bool
vmVersion string
useLatestVersion bool
// L2/Sequencer flags
sequencer string
enablePreconfirm bool
errIllegalNameCharacter = errors.New(
"illegal name character: only letters, no special characters allowed")
)
// lux subnet create
func newCreateCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "create [subnetName]",
Short: "Create a new subnet configuration",
Long: `The subnet create command builds a new subnet configuration that can be
deployed as an L2 (using any L1 as sequencer) or as a sovereign L1.
Subnets are L2s that can use different sequencing models:
- Based rollups: Use L1 block proposers as sequencers (Ethereum, Lux L1, etc.)
- Centralized: Traditional single sequencer model
- Distributed: Multiple sequencers with consensus
By default, the command runs an interactive wizard. It supports:
- Lux EVM and custom VMs
- Multiple base chains for sequencing
- Pre-confirmations for fast UX
- Migration paths between different models
Use --sequencer to specify the sequencing model (lux, ethereum, lux, op, external).
Use -f to overwrite existing configurations.`,
SilenceUsage: true,
Args: cobra.ExactArgs(1),
RunE: createSubnetConfig,
PersistentPostRun: handlePostRun,
}
cmd.Flags().StringVar(&genesisFile, "genesis", "", "file path of genesis to use")
cmd.Flags().StringVar(&vmFile, "vm", "", "file path of custom vm to use")
cmd.Flags().BoolVar(&useSubnetEvm, "evm", false, "use the Lux EVM as the base template")
cmd.Flags().StringVar(&vmVersion, "vm-version", "", "version of vm template to use")
cmd.Flags().BoolVar(&useCustom, "custom", false, "use a custom VM template")
cmd.Flags().BoolVar(&useLatestVersion, latest, false, "use latest VM version, takes precedence over --vm-version")
cmd.Flags().BoolVarP(&forceCreate, forceFlag, "f", false, "overwrite the existing configuration if one exists")
// L2/Sequencer flags
cmd.Flags().StringVar(&sequencer, "sequencer", "", "sequencer for the L2 (lux, ethereum, lux, op, external)")
cmd.Flags().BoolVar(&enablePreconfirm, "enable-preconfirm", false, "enable pre-confirmations for fast UX")
return cmd
}
func moreThanOneVMSelected() bool {
vmVars := []bool{useSubnetEvm, useCustom}
firstSelect := false
for _, val := range vmVars {
if firstSelect && val {
return true
} else if val {
firstSelect = true
}
}
return false
}
func getVMFromFlag() models.VMType {
if useSubnetEvm {
return models.EVM
}
if useCustom {
return models.CustomVM
}
return ""
}
// override postrun function from root.go, so that we don't double send metrics for the same command
func handlePostRun(_ *cobra.Command, _ []string) {}
func createSubnetConfig(cmd *cobra.Command, args []string) error {
subnetName := args[0]
if app.GenesisExists(subnetName) && !forceCreate {
return errors.New("configuration already exists. Use --" + forceFlag + " parameter to overwrite")
}
if err := checkInvalidSubnetNames(subnetName); err != nil {
return fmt.Errorf("subnet name %q is invalid: %w", subnetName, err)
}
if moreThanOneVMSelected() {
return errors.New("too many VMs selected. Provide at most one VM selection flag")
}
subnetType := getVMFromFlag()
if subnetType == "" {
subnetTypeStr, err := app.Prompt.CaptureList(
"Choose your VM",
[]string{models.EVM, models.CustomVM},
)
if err != nil {
return err
}
subnetType = models.VMTypeFromString(subnetTypeStr)
}
var (
genesisBytes []byte
sc *models.Sidecar
err error
)
if useLatestVersion {
vmVersion = latest
}
if vmVersion != latest && vmVersion != "" && !semver.IsValid(vmVersion) {
return fmt.Errorf("invalid version string, should be semantic version (ex: v1.1.1): %s", vmVersion)
}
switch subnetType {
case models.EVM:
genesisBytes, sc, err = vm.CreateEvmConfig(app, subnetName, genesisFile, vmVersion)
if err != nil {
return err
}
case models.CustomVM:
genesisBytes, sc, err = vm.CreateCustomSubnetConfig(app, subnetName, genesisFile, vmFile)
if err != nil {
return err
}
default:
return errors.New("not implemented")
}
// Configure L2/Sequencer settings
if sequencer == "" && !cmd.Flags().Changed("sequencer") {
// Interactive sequencer selection
sequencerOptions := []string{
"Lux (100ms blocks, lowest cost, based rollup)",
"Ethereum (12s blocks, highest security, based rollup)",
"Lux (2s blocks, fast finality, based rollup)",
"OP Stack (Optimism compatible)",
"External (Traditional sequencer)",
"None (Deploy as sovereign L1)",
}
choice, err := app.Prompt.CaptureList(
"Select sequencer for your L2",
sequencerOptions,
)
if err != nil {
return err
}
switch choice {
case "Lux (100ms blocks, lowest cost, based rollup)":
sequencer = "lux"
case "Ethereum (12s blocks, highest security, based rollup)":
sequencer = "ethereum"
case "Lux (2s blocks, fast finality, based rollup)":
sequencer = "lux"
case "OP Stack (Optimism compatible)":
sequencer = "op"
case "External (Traditional sequencer)":
sequencer = "external"
case "None (Deploy as sovereign L1)":
sc.Sovereign = true
}
}
// Apply L2 configuration
if sequencer != "" {
sc.BaseChain = sequencer
sc.BasedRollup = isBasedRollup(sequencer)
sc.Sovereign = false // L2s are not sovereign
sc.SequencerType = sequencer
sc.L1BlockTime = getBlockTime(sequencer)
sc.PreconfirmEnabled = enablePreconfirm
ux.Logger.PrintToUser("🔧 L2 Configuration:")
ux.Logger.PrintToUser(" Sequencer: %s", sequencer)
if isBasedRollup(sequencer) {
ux.Logger.PrintToUser(" Type: Based rollup (L1-sequenced)")
} else if sequencer == "op" {
ux.Logger.PrintToUser(" Type: OP Stack compatible")
} else {
ux.Logger.PrintToUser(" Type: External sequencer")
}
ux.Logger.PrintToUser(" Block Time: %dms", sc.L1BlockTime)
if enablePreconfirm {
ux.Logger.PrintToUser(" Pre-confirmations: Enabled")
}
} else if sc.Sovereign {
ux.Logger.PrintToUser("🔧 L1 Configuration:")
ux.Logger.PrintToUser(" Type: Sovereign L1")
ux.Logger.PrintToUser(" Validation: Independent")
}
if err = app.WriteGenesisFile(subnetName, genesisBytes); err != nil {
return err
}
sc.ImportedFromLPM = false
if err = app.CreateSidecar(sc); err != nil {
return err
}
flags := make(map[string]string)
flags[constants.SubnetType] = subnetType.RepoName()
utils.HandleTracking(cmd, app, flags)
ux.Logger.PrintToUser("Successfully created subnet configuration")
return nil
}
func checkInvalidSubnetNames(name string) error {
// this is currently exactly the same code as in node/vms/platformvm/create_chain_tx.go
for _, r := range name {
if r > unicode.MaxASCII || !(unicode.IsLetter(r) || unicode.IsNumber(r) || r == ' ') {
return errIllegalNameCharacter
}
}
return nil
}