Files

814 lines
27 KiB
Go

// / Copyright (C) 2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package blockchaincmd
import (
"fmt"
"math/big"
"os"
"time"
"github.com/luxfi/cli/v2/cmd/flags"
"github.com/luxfi/cli/v2/pkg/blockchain"
"github.com/luxfi/cli/v2/pkg/cobrautils"
"github.com/luxfi/cli/v2/pkg/constants"
"github.com/luxfi/cli/v2/pkg/contract"
"github.com/luxfi/cli/v2/pkg/dependencies"
"github.com/luxfi/cli/v2/pkg/keychain"
"github.com/luxfi/cli/v2/pkg/localnet"
"github.com/luxfi/cli/v2/pkg/models"
"github.com/luxfi/cli/v2/pkg/networkoptions"
"github.com/luxfi/cli/v2/pkg/node"
"github.com/luxfi/cli/v2/pkg/prompts"
"github.com/luxfi/cli/v2/pkg/signatureaggregator"
"github.com/luxfi/cli/v2/pkg/subnet"
"github.com/luxfi/cli/v2/pkg/txutils"
"github.com/luxfi/cli/v2/pkg/utils"
"github.com/luxfi/cli/v2/pkg/ux"
"github.com/luxfi/cli/v2/pkg/validatormanager"
blockchainSDK "github.com/luxfi/cli/v2/sdk/blockchain"
"github.com/luxfi/cli/v2/sdk/evm"
validatormanagerSDK "github.com/luxfi/cli/v2/sdk/validatormanager"
"github.com/luxfi/cli/v2/sdk/validatormanager/validatormanagertypes"
"github.com/luxfi/node/v2/v2/config"
"github.com/luxfi/ids"
"github.com/luxfi/node/v2/v2/utils/logging"
"github.com/luxfi/node/v2/v2/utils/units"
"github.com/luxfi/node/v2/v2/vms/platformvm/txs"
"github.com/ethereum/go-ethereum/common"
"github.com/spf13/cobra"
)
var (
doStrongInputChecks bool
convertFlags BlockchainConvertFlags
)
type BlockchainConvertFlags struct {
SigAggFlags flags.SignatureAggregatorFlags
LocalMachineFlags flags.LocalMachineFlags
ProofOfStakeFlags flags.POSFlags
BootstrapValidatorFlags flags.BootstrapValidatorFlags
ConvertOnly bool
}
// lux blockchain convert
func newConvertCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "convert [blockchainName]",
Short: "Converts a Subnet into a sovereign L1",
Long: `The blockchain convert command converts a Subnet into sovereign L1.
Sovereign L1s require bootstrap validators. lux blockchain convert command gives the option of:
- either using local machine as bootstrap validators (set the number of bootstrap validators using
--num-bootstrap-validators flag, default is set to 1)
- or using remote nodes (we require the node's Node-ID and BLS info)`,
RunE: convertBlockchain,
PersistentPostRun: handlePostRun,
PreRunE: cobrautils.ExactArgs(1),
}
networkGroup := networkoptions.GetNetworkFlagsGroup(cmd, &globalNetworkFlags, true, networkoptions.DefaultSupportedNetworkOptions)
sigAggGroup := flags.AddSignatureAggregatorFlagsToCmd(cmd, &convertFlags.SigAggFlags)
cmd.Flags().StringVarP(&keyName, "key", "k", "", "select the key to use to authorize ConvertSubnetTol1 Tx")
cmd.Flags().StringSliceVar(&subnetAuthKeys, "auth-keys", nil, "control keys that will be used to authenticate ConvertSubnetTol1")
cmd.Flags().StringVar(&outputTxPath, "output-tx-path", "", "file path of the convert to L1 tx (for multi-sig)")
cmd.Flags().BoolVarP(&useLedger, "ledger", "g", false, "use ledger instead of key")
cmd.Flags().StringSliceVar(&ledgerAddresses, "ledger-addrs", []string{}, "use the given ledger addresses")
cmd.Flags().BoolVar(&convertFlags.ConvertOnly, "convert-only", false, "avoid node track, restart and poa manager setup")
localMachineGroup := flags.AddLocalMachineFlagsToCmd(cmd, &convertFlags.LocalMachineFlags)
posGroup := flags.AddProofOfStakeToCmd(cmd, &convertFlags.ProofOfStakeFlags)
bootstrapValidatorGroup := flags.AddBootstrapValidatorFlagsToCmd(cmd, &convertFlags.BootstrapValidatorFlags)
cmd.Flags().BoolVar(&createFlags.proofOfAuthority, "proof-of-authority", false, "use proof of authority(PoA) for validator management")
cmd.Flags().BoolVar(&createFlags.proofOfStake, "proof-of-stake", false, "use proof of stake(PoS) for validator management")
cmd.Flags().StringVar(&createFlags.validatorManagerOwner, "validator-manager-owner", "", "EVM address that controls Validator Manager Owner")
cmd.Flags().StringVar(&createFlags.proxyContractOwner, "proxy-contract-owner", "", "EVM address that controls ProxyAdmin for TransparentProxy of ValidatorManager contract")
cmd.Flags().StringVar(&validatorManagerAddress, "validator-manager-address", "", "validator manager address")
cmd.Flags().BoolVar(&doStrongInputChecks, "verify-input", true, "check for input confirmation")
cmd.SetHelpFunc(flags.WithGroupedHelp([]flags.GroupedFlags{networkGroup, bootstrapValidatorGroup, localMachineGroup, posGroup, sigAggGroup}))
return cmd
}
func StartLocalMachine(
network models.Network,
sidecar models.Sidecar,
blockchainName string,
deployBalance,
availableBalance uint64,
localMachineFlags *flags.LocalMachineFlags,
bootstrapValidatorFlags *flags.BootstrapValidatorFlags,
) (bool, error) {
var err error
if network.Kind == models.Local &&
!bootstrapValidatorFlags.GenerateNodeID &&
bootstrapValidatorFlags.BootstrapEndpoints == nil &&
bootstrapValidatorFlags.BootstrapValidatorsJSONFilePath == "" {
localMachineFlags.UseLocalMachine = true
}
clusterName := localnet.LocalClusterName(network, blockchainName)
if clusterNameFlagValue != "" {
clusterName = clusterNameFlagValue
if localnet.LocalClusterExists(app, clusterName) {
localMachineFlags.UseLocalMachine = true
if len(bootstrapValidatorFlags.BootstrapEndpoints) == 0 {
bootstrapValidatorFlags.BootstrapEndpoints, err = localnet.GetLocalClusterURIs(app, clusterName)
if err != nil {
return false, fmt.Errorf("error getting local host bootstrap endpoints: %w, "+
"please create your local node again and call blockchain deploy command again", err)
}
}
network = models.ConvertClusterToNetwork(network)
}
}
// ask user if we want to use local machine if cluster is not provided
if !localMachineFlags.UseLocalMachine && clusterNameFlagValue == "" {
ux.Logger.PrintToUser("You can use your local machine as a bootstrap validator on the blockchain")
ux.Logger.PrintToUser("This means that you don't have to to set up a remote server on a cloud service (e.g. AWS / GCP) to be a validator on the blockchain.")
localMachineFlags.UseLocalMachine, err = app.Prompt.CaptureYesNo("Do you want to use your local machine as a bootstrap validator?")
if err != nil {
return false, err
}
}
// default number of local machine nodes to be 1
if localMachineFlags.UseLocalMachine && bootstrapValidatorFlags.NumBootstrapValidators == 0 {
bootstrapValidatorFlags.NumBootstrapValidators = constants.DefaultNumberOfLocalMachineNodes
}
// if no cluster provided - we create one with fmt.Sprintf("%s-local-node-%s", blockchainName, networkNameComponent) name
if localMachineFlags.UseLocalMachine && clusterNameFlagValue == "" {
if localnet.LocalClusterExists(app, clusterName) {
ux.Logger.PrintToUser("")
ux.Logger.PrintToUser(
logging.Red.Wrap("A local machine L1 deploy already exists for %s L1 and network %s"),
blockchainName,
network.Name(),
)
yes, err := app.Prompt.CaptureNoYes(
fmt.Sprintf("Do you want to overwrite the current local L1 deploy for %s?", blockchainName),
)
if err != nil {
return false, err
}
if !yes {
return true, nil
}
_ = localnet.LocalClusterRemove(app, clusterName)
ux.Logger.GreenCheckmarkToUser("Local node %s cleaned up.", clusterName)
}
requiredBalance := deployBalance * uint64(bootstrapValidatorFlags.NumBootstrapValidators)
if availableBalance < requiredBalance {
return false, fmt.Errorf(
"required balance for %d validators dynamic fee on PChain is %d but the given key has %d",
bootstrapValidatorFlags.NumBootstrapValidators,
requiredBalance,
availableBalance,
)
}
luxdVersionSettings := dependencies.LuxdVersionSettings{}
// setup (install if needed) luxd binary
luxdVersion := localMachineFlags.UserProvidedLuxdVersion
if localMachineFlags.UserProvidedLuxdVersion == constants.DefaultLuxdVersion && localMachineFlags.LuxdBinaryPath == "" {
// nothing given: get luxd version from RPC compat using latest.json defined in
// https://raw.githubusercontent.com/luxfi/lux-cli/control-default-version/versions/latest.json
luxdVersion, err = dependencies.GetLatestCLISupportedDependencyVersion(app, constants.LuxdRepoName, network, &sidecar.RPCVersion)
if err != nil {
if err != dependencies.ErrNoLuxdVersion {
return false, err
}
luxdVersion = constants.LatestPreReleaseVersionTag
}
}
localMachineFlags.LuxdBinaryPath, err = localnet.SetupLuxdBinary(app, luxdVersion, localMachineFlags.LuxdBinaryPath)
if err != nil {
return false, err
}
nodeConfig := map[string]interface{}{}
if partialSync {
nodeConfig[config.PartialSyncPrimaryNetworkKey] = true
}
if network.Kind == models.Testnet {
globalNetworkFlags.UseTestnet = true
}
if network.Kind == models.Mainnet {
globalNetworkFlags.UseMainnet = true
}
nodeSettingsLen := max(len(localMachineFlags.StakingSignerKeyPaths), len(localMachineFlags.HTTPPorts), len(localMachineFlags.StakingPorts))
nodeSettings := make([]localnet.NodeSetting, nodeSettingsLen)
for i := range nodeSettingsLen {
nodeSetting := localnet.NodeSetting{}
if i < len(localMachineFlags.StakingSignerKeyPaths) {
stakingSignerKey, err := os.ReadFile(localMachineFlags.StakingSignerKeyPaths[i])
if err != nil {
return false, fmt.Errorf("could not read staking signer key at %s: %w", localMachineFlags.StakingSignerKeyPaths[i], err)
}
stakingCertKey, err := os.ReadFile(localMachineFlags.StakingCertKeyPaths[i])
if err != nil {
return false, fmt.Errorf("could not read staking cert key at %s: %w", localMachineFlags.StakingCertKeyPaths[i], err)
}
stakingTLSKey, err := os.ReadFile(localMachineFlags.StakingTLSKeyPaths[i])
if err != nil {
return false, fmt.Errorf("could not read staking TLS key at %s: %w", localMachineFlags.StakingTLSKeyPaths[i], err)
}
nodeSetting.StakingSignerKey = stakingSignerKey
nodeSetting.StakingCertKey = stakingCertKey
nodeSetting.StakingTLSKey = stakingTLSKey
}
if i < len(localMachineFlags.HTTPPorts) {
nodeSetting.HTTPPort = uint64(localMachineFlags.HTTPPorts[i])
}
if i < len(localMachineFlags.StakingPorts) {
nodeSetting.StakingPort = uint64(localMachineFlags.StakingPorts[i])
}
nodeSettings[i] = nodeSetting
}
// anrSettings, luxdVersionSettings, globalNetworkFlags are empty
if err = node.StartLocalNode(
app,
clusterName,
localMachineFlags.LuxdBinaryPath,
uint32(bootstrapValidatorFlags.NumBootstrapValidators),
nodeConfig,
localnet.ConnectionSettings{},
nodeSettings,
luxdVersionSettings,
network,
); err != nil {
return false, err
}
clusterNameFlagValue = clusterName
if len(bootstrapValidatorFlags.BootstrapEndpoints) == 0 {
bootstrapValidatorFlags.BootstrapEndpoints, err = localnet.GetLocalClusterURIs(app, clusterName)
if err != nil {
return false, fmt.Errorf("error getting local host bootstrap endpoints: %w, "+
"please create your local node again and call blockchain deploy command again", err)
}
}
}
return false, nil
}
func InitializeValidatorManager(
blockchainName,
validatorManagerOwner string,
subnetID ids.ID,
blockchainID ids.ID,
network models.Network,
luxdBootstrapValidators []*txs.ConvertSubnetToL1Validator,
pos bool,
managerAddress string,
proxyContractOwner string,
useACP99 bool,
useLocalMachine bool,
signatureAggregatorFlags flags.SignatureAggregatorFlags,
proofOfStakeFlags flags.POSFlags,
) (bool, error) {
if useACP99 {
ux.Logger.PrintToUser(logging.Yellow.Wrap("Validator Manager Protocol: V2"))
} else {
ux.Logger.PrintToUser(logging.Yellow.Wrap("Validator Manager Protocol: v1.0.0"))
}
var err error
clusterName := clusterNameFlagValue
switch {
case useLocalMachine:
if err := localnet.LocalClusterTrackSubnet(
app,
ux.Logger.PrintToUser,
clusterName,
blockchainName,
); err != nil {
return false, err
}
default:
if clusterName != "" {
if err = node.SyncSubnet(app, clusterName, blockchainName, true, nil); err != nil {
return false, err
}
if err := node.WaitForHealthyCluster(app, clusterName, node.HealthCheckTimeout, node.HealthCheckPoolTime); err != nil {
return false, err
}
}
}
tracked := true
chainSpec := contract.ChainSpec{
BlockchainName: blockchainName,
}
_, genesisPrivateKey, err := contract.GetEVMSubnetPrefundedKey(
app,
network,
chainSpec,
)
if err != nil {
return tracked, err
}
rpcURL, _, err := contract.GetBlockchainEndpoints(
app,
network,
chainSpec,
true,
false,
)
if err != nil {
return tracked, err
}
client, err := evm.GetClient(rpcURL)
if err != nil {
return tracked, err
}
if err := client.WaitForEVMBootstrapped(0); err != nil {
return tracked, err
}
ownerAddress := common.HexToAddress(validatorManagerOwner)
if pos {
deployed, err := validatormanager.ValidatorProxyHasImplementationSet(rpcURL)
if err != nil {
return tracked, err
}
if !deployed {
// it is not in genesis
ux.Logger.PrintToUser("Deploying Proof of Stake Validator Manager contract on blockchain %s ...", blockchainName)
proxyOwnerPrivateKey, err := GetProxyOwnerPrivateKey(
app,
network,
proxyContractOwner,
ux.Logger.PrintToUser,
)
if err != nil {
return tracked, err
}
if useACP99 {
_, err := validatormanager.DeployAndRegisterValidatorManagerV2_0_0Contract(
rpcURL,
genesisPrivateKey,
proxyOwnerPrivateKey,
)
if err != nil {
return tracked, err
}
_, err = validatormanager.DeployAndRegisterPoSValidatorManagerV2_0_0Contract(
rpcURL,
genesisPrivateKey,
proxyOwnerPrivateKey,
)
if err != nil {
return tracked, err
}
} else {
if _, err := validatormanager.DeployAndRegisterPoSValidatorManagerV1_0_0Contract(
rpcURL,
genesisPrivateKey,
proxyOwnerPrivateKey,
); err != nil {
return tracked, err
}
}
}
}
extraAggregatorPeers, err := blockchain.GetAggregatorExtraPeers(app, clusterName)
if err != nil {
return tracked, err
}
subnetSDK := blockchainSDK.Subnet{
SubnetID: subnetID,
BlockchainID: blockchainID,
OwnerAddress: &ownerAddress,
RPC: rpcURL,
BootstrapValidators: luxdBootstrapValidators,
}
aggregatorLogger, err := signatureaggregator.NewSignatureAggregatorLogger(
signatureAggregatorFlags.AggregatorLogLevel,
signatureAggregatorFlags.AggregatorLogToStdout,
app.GetAggregatorLogDir(clusterName),
)
if err != nil {
return tracked, err
}
// TODO: replace latest below with sig agg version in flags for convert and deploy
err = signatureaggregator.CreateSignatureAggregatorInstance(app, subnetID.String(), network, extraAggregatorPeers, aggregatorLogger, "latest")
if err != nil {
return tracked, err
}
signatureAggregatorEndpoint, err := signatureaggregator.GetSignatureAggregatorEndpoint(app, network)
if err != nil {
return tracked, err
}
if pos {
ux.Logger.PrintToUser("Initializing Native Token Proof of Stake Validator Manager contract on blockchain %s ...", blockchainName)
found, _, _, managerOwnerPrivateKey, err := contract.SearchForManagedKey(
app,
network,
ownerAddress,
true,
)
if err != nil {
return tracked, err
}
if !found {
return tracked, fmt.Errorf("could not find validator manager owner private key")
}
if err := subnetSDK.InitializeProofOfStake(
app.Log,
network.SDKNetwork(),
genesisPrivateKey,
aggregatorLogger,
validatormanagerSDK.PoSParams{
MinimumStakeAmount: big.NewInt(int64(proofOfStakeFlags.MinimumStakeAmount)),
MaximumStakeAmount: big.NewInt(int64(proofOfStakeFlags.MaximumStakeAmount)),
MinimumStakeDuration: proofOfStakeFlags.MinimumStakeDuration,
MinimumDelegationFee: proofOfStakeFlags.MinimumDelegationFee,
MaximumStakeMultiplier: proofOfStakeFlags.MaximumStakeMultiplier,
WeightToValueFactor: big.NewInt(int64(proofOfStakeFlags.WeightToValueFactor)),
RewardCalculatorAddress: validatormanagerSDK.RewardCalculatorAddress,
UptimeBlockchainID: blockchainID,
},
managerAddress,
validatormanagerSDK.SpecializationProxyContractAddress,
managerOwnerPrivateKey,
useACP99,
signatureAggregatorEndpoint,
); err != nil {
return tracked, err
}
ux.Logger.GreenCheckmarkToUser("Proof of Stake Validator Manager contract successfully initialized on blockchain %s", blockchainName)
} else {
ux.Logger.PrintToUser("Initializing Proof of Authority Validator Manager contract on blockchain %s ...", blockchainName)
if err := subnetSDK.InitializeProofOfAuthority(
app.Log,
network.SDKNetwork(),
genesisPrivateKey,
aggregatorLogger,
managerAddress,
useACP99,
signatureAggregatorEndpoint,
); err != nil {
return tracked, err
}
ux.Logger.GreenCheckmarkToUser("Proof of Authority Validator Manager contract successfully initialized on blockchain %s", blockchainName)
}
return tracked, nil
}
func convertSubnetToL1(
bootstrapValidators []models.SubnetValidator,
deployer *subnet.PublicDeployer,
subnetID, blockchainID ids.ID,
network models.Network,
chain string,
sidecar models.Sidecar,
controlKeysList,
subnetAuthKeysList []string,
validatorManagerAddressStr string,
doStrongInputsCheck bool,
) ([]*txs.ConvertSubnetToL1Validator, bool, bool, error) {
if subnetID == ids.Empty {
return nil, false, false, constants.ErrNoSubnetID
}
if blockchainID == ids.Empty {
return nil, false, false, constants.ErrNoBlockchainID
}
if !common.IsHexAddress(validatorManagerAddressStr) {
return nil, false, false, constants.ErrInvalidValidatorManagerAddress
}
luxdBootstrapValidators, err := ConvertToLuxdSubnetValidator(bootstrapValidators)
if err != nil {
return luxdBootstrapValidators, false, false, err
}
managerAddress := common.HexToAddress(validatorManagerAddressStr)
if doStrongInputsCheck {
ux.Logger.PrintToUser("You are about to create a ConvertSubnetToL1Tx on %s with the following content:", network.Name())
ux.Logger.PrintToUser(" Subnet ID: %s", subnetID)
ux.Logger.PrintToUser(" Blockchain ID: %s", blockchainID)
ux.Logger.PrintToUser(" Manager Address: %s", managerAddress.Hex())
ux.Logger.PrintToUser(" Validators:")
for _, val := range bootstrapValidators {
ux.Logger.PrintToUser(" Node ID: %s", val.NodeID)
ux.Logger.PrintToUser(" Weight: %d", val.Weight)
ux.Logger.PrintToUser(" Balance: %.5f", float64(val.Balance)/float64(units.Lux))
}
ux.Logger.PrintToUser("")
ux.Logger.PrintToUser("Please review the details of the ConvertSubnetToL1 Transaction")
ux.Logger.PrintToUser("")
if doContinue, err := app.Prompt.CaptureYesNo("Do you want to create the transaction?"); err != nil {
return luxdBootstrapValidators, false, false, err
} else if !doContinue {
return luxdBootstrapValidators, true, false, nil
}
}
isFullySigned, convertL1TxID, tx, remainingSubnetAuthKeys, err := deployer.ConvertL1(
controlKeysList,
subnetAuthKeysList,
subnetID,
blockchainID,
managerAddress,
luxdBootstrapValidators,
)
if err != nil {
ux.Logger.RedXToUser("error converting blockchain: %s. fix the issue and try again with a new convert cmd", err)
return luxdBootstrapValidators, false, false, err
}
savePartialTx := !isFullySigned && err == nil
if savePartialTx {
if err := SaveNotFullySignedTx(
"ConvertSubnetToL1Tx",
tx,
chain,
subnetAuthKeys,
remainingSubnetAuthKeys,
outputTxPath,
false,
); err != nil {
return luxdBootstrapValidators, false, savePartialTx, err
}
} else {
ux.Logger.PrintToUser("ConvertSubnetToL1Tx ID: %s", convertL1TxID)
_, err = ux.TimedProgressBar(
30*time.Second,
"Waiting for the Subnet to be converted into a sovereign L1 ...",
0,
)
if err != nil {
return luxdBootstrapValidators, false, savePartialTx, err
}
}
ux.Logger.PrintToUser("")
setBootstrapValidatorValidationID(luxdBootstrapValidators, bootstrapValidators, subnetID)
return luxdBootstrapValidators, false, savePartialTx, app.UpdateSidecarNetworks(
&sidecar,
network,
subnetID,
blockchainID,
"",
"",
bootstrapValidators,
clusterNameFlagValue,
validatorManagerAddressStr,
)
}
// convertBlockchain is the cobra command run for converting subnets into sovereign L1
func convertBlockchain(cmd *cobra.Command, args []string) error {
blockchainName := args[0]
chains, err := ValidateSubnetNameAndGetChains(args)
if err != nil {
return err
}
var bootstrapValidators []models.SubnetValidator
if convertFlags.BootstrapValidatorFlags.BootstrapValidatorsJSONFilePath != "" {
bootstrapValidators, err = LoadBootstrapValidator(convertFlags.BootstrapValidatorFlags)
if err != nil {
return err
}
convertFlags.BootstrapValidatorFlags.NumBootstrapValidators = len(bootstrapValidators)
}
chain := chains[0]
sidecar, err := app.LoadSidecar(chain)
if err != nil {
return fmt.Errorf("failed to load sidecar for later update: %w", err)
}
if outputTxPath != "" {
if _, err := os.Stat(outputTxPath); err == nil {
return fmt.Errorf("outputTxPath %q already exists", outputTxPath)
}
}
network, err := networkoptions.GetNetworkFromCmdLineFlags(
app,
"",
globalNetworkFlags,
true,
false,
networkoptions.GetNetworkFromSidecar(sidecar, networkoptions.DefaultSupportedNetworkOptions),
"",
)
if err != nil {
return err
}
if err = validateConvertOnlyFlag(cmd, convertFlags.BootstrapValidatorFlags, &convertFlags.ConvertOnly, convertFlags.LocalMachineFlags.UseLocalMachine); err != nil {
return err
}
clusterNameFlagValue = globalNetworkFlags.ClusterName
subnetID := sidecar.Networks[network.Name()].SubnetID
blockchainID := sidecar.Networks[network.Name()].BlockchainID
if doStrongInputChecks && subnetID != ids.Empty {
ux.Logger.PrintToUser("Subnet ID to be used is %s", subnetID)
if acceptValue, err := app.Prompt.CaptureYesNo("Is this value correct?"); err != nil {
return err
} else if !acceptValue {
subnetID = ids.Empty
}
}
if subnetID == ids.Empty {
subnetID, err = app.Prompt.CaptureID("What is the subnet ID?")
if err != nil {
return err
}
}
if doStrongInputChecks && blockchainID != ids.Empty {
ux.Logger.PrintToUser("Blockchain ID to be used is %s", blockchainID)
if acceptValue, err := app.Prompt.CaptureYesNo("Is this value correct?"); err != nil {
return err
} else if !acceptValue {
blockchainID = ids.Empty
}
}
if blockchainID == ids.Empty {
blockchainID, err = app.Prompt.CaptureID("What is the blockchain ID?")
if err != nil {
return err
}
}
if validatorManagerAddress == "" {
validatorManagerAddressAddrFmt, err := app.Prompt.CaptureAddress("What is the address of the Validator Manager?")
if err != nil {
return err
}
validatorManagerAddress = validatorManagerAddressAddrFmt.String()
}
if !convertFlags.ConvertOnly {
if err = promptValidatorManagementType(app, &sidecar); err != nil {
return err
}
if err := setSidecarValidatorManageOwner(&sidecar, createFlags); err != nil {
return err
}
sidecar.UpdateValidatorManagerAddress(network.Name(), validatorManagerAddress)
}
sidecar.Sovereign = true
fee := uint64(0)
kc, err := keychain.GetKeychainFromCmdLineFlags(
app,
constants.PayTxsFeesMsg,
network,
keyName,
useEwoq,
useLedger,
ledgerAddresses,
fee,
)
if err != nil {
return err
}
availableBalance, err := utils.GetNetworkBalance(kc.Addresses().List(), network.Endpoint)
if err != nil {
return err
}
deployBalance := uint64(convertFlags.BootstrapValidatorFlags.DeployBalanceLUX * float64(units.Lux))
err = prepareBootstrapValidators(&bootstrapValidators, network, sidecar, *kc, blockchainName, deployBalance, availableBalance, &convertFlags.LocalMachineFlags, &convertFlags.BootstrapValidatorFlags)
if err != nil {
return err
}
requiredBalance := deployBalance * uint64(len(bootstrapValidators))
if availableBalance < requiredBalance {
return fmt.Errorf(
"required balance for %d validators dynamic fee on PChain is %d but the given key has %d",
len(bootstrapValidators),
requiredBalance,
availableBalance,
)
}
kcKeys, err := kc.PChainFormattedStrAddresses()
if err != nil {
return err
}
// get keys for blockchain tx signing
_, controlKeys, threshold, err = txutils.GetOwners(network, subnetID)
if err != nil {
return err
}
// get keys for convertL1 tx signing
if subnetAuthKeys != nil {
if err := prompts.CheckSubnetAuthKeys(kcKeys, subnetAuthKeys, controlKeys, threshold); err != nil {
return err
}
} else {
subnetAuthKeys, err = prompts.GetSubnetAuthKeys(app.Prompt, kcKeys, controlKeys, threshold)
if err != nil {
return err
}
}
ux.Logger.PrintToUser("Your auth keys for add validator tx creation: %s", subnetAuthKeys)
// deploy to public network
deployer := subnet.NewPublicDeployer(app, kc, network)
luxdBootstrapValidators, cancel, savePartialTx, err := convertSubnetToL1(
bootstrapValidators,
deployer,
subnetID,
blockchainID,
network,
chain,
sidecar,
controlKeys,
subnetAuthKeys,
validatorManagerAddress,
doStrongInputChecks,
)
if err != nil {
return err
}
if cancel {
return nil
}
if savePartialTx {
return nil
}
if !convertFlags.ConvertOnly && !convertFlags.BootstrapValidatorFlags.GenerateNodeID {
if _, err = InitializeValidatorManager(
blockchainName,
sidecar.ValidatorManagerOwner,
subnetID,
blockchainID,
network,
luxdBootstrapValidators,
sidecar.ValidatorManagement == validatormanagertypes.ProofOfStake,
validatorManagerAddress,
sidecar.ProxyContractOwner,
sidecar.UseACP99,
convertFlags.LocalMachineFlags.UseLocalMachine,
convertFlags.SigAggFlags,
convertFlags.ProofOfStakeFlags,
); err != nil {
return err
}
if sidecar.UseACP99 && sidecar.ValidatorManagement == validatormanagertypes.ProofOfStake {
sidecar, err := app.LoadSidecar(chain)
if err != nil {
return err
}
networkInfo := sidecar.Networks[network.Name()]
networkInfo.ValidatorManagerAddress = validatormanagerSDK.SpecializationProxyContractAddress
sidecar.Networks[network.Name()] = networkInfo
if err := app.UpdateSidecar(&sidecar); err != nil {
return err
}
}
} else {
printSuccessfulConvertOnlyOutput(blockchainName, subnetID.String(), convertFlags.BootstrapValidatorFlags.GenerateNodeID)
}
ux.Logger.PrintToUser("")
ux.Logger.PrintToUser(logging.Green.Wrap("Your L1 is ready for on-chain interactions."))
ux.Logger.PrintToUser("")
ux.Logger.GreenCheckmarkToUser("Subnet is successfully converted to sovereign L1")
return nil
}
func printSuccessfulConvertOnlyOutput(blockchainName, subnetID string, generateNodeID bool) {
ux.Logger.GreenCheckmarkToUser("Converted blockchain successfully generated")
ux.Logger.PrintToUser("Next, we need to:")
if generateNodeID {
ux.Logger.PrintToUser("- Create the corresponding Lux node(s) with the provided Node ID and BLS Info")
}
ux.Logger.PrintToUser("- Have the Lux node(s) track the blockchain")
ux.Logger.PrintToUser("- Call `lux contract initValidatorManager %s`", blockchainName)
ux.Logger.PrintToUser("==================================================")
if generateNodeID {
ux.Logger.PrintToUser("To create the Lux node(s) with the provided Node ID and BLS Info:")
ux.Logger.PrintToUser("- Created Node ID and BLS Info can be found at %s", app.GetSidecarPath(blockchainName))
ux.Logger.PrintToUser("")
}
ux.Logger.PrintToUser("To enable the nodes to track the L1:")
ux.Logger.PrintToUser("- Set '%s' as the value for 'track-subnets' configuration in ~/.luxd/config.json", subnetID)
ux.Logger.PrintToUser("- Ensure that the P2P port is exposed and 'public-ip' config value is set")
}