mirror of
https://github.com/luxfi/cli.git
synced 2026-10-30 17:01:20 +00:00
1301 lines
45 KiB
Go
1301 lines
45 KiB
Go
// / Copyright (C) 2025, Lux Industries Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
package blockchaincmd
|
|
|
|
import (
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
|
|
"github.com/luxfi/cli/v2/cmd/flags"
|
|
"github.com/luxfi/cli/v2/cmd/interchaincmd/messengercmd"
|
|
"github.com/luxfi/cli/v2/cmd/interchaincmd/relayercmd"
|
|
"github.com/luxfi/cli/v2/cmd/networkcmd"
|
|
"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/interchain/relayer"
|
|
"github.com/luxfi/cli/v2/pkg/keychain"
|
|
"github.com/luxfi/cli/v2/pkg/localnet"
|
|
"github.com/luxfi/cli/v2/pkg/metrics"
|
|
"github.com/luxfi/cli/v2/pkg/models"
|
|
"github.com/luxfi/cli/v2/pkg/networkoptions"
|
|
"github.com/luxfi/cli/v2/pkg/prompts"
|
|
"github.com/luxfi/cli/v2/pkg/prompts/comparator"
|
|
"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/vm"
|
|
sdkutils "github.com/luxfi/cli/v2/sdk/utils"
|
|
validatormanagerSDK "github.com/luxfi/cli/v2/sdk/validatormanager"
|
|
"github.com/luxfi/cli/v2/sdk/validatormanager/validatormanagertypes"
|
|
"github.com/luxfi/node/v2/v2/api/info"
|
|
"github.com/luxfi/ids"
|
|
luxdutils "github.com/luxfi/node/v2/v2/utils"
|
|
"github.com/luxfi/node/v2/v2/utils/formatting/address"
|
|
"github.com/luxfi/node/v2/v2/utils/logging"
|
|
"github.com/luxfi/node/v2/v2/utils/set"
|
|
"github.com/luxfi/node/v2/v2/utils/units"
|
|
"github.com/luxfi/node/v2/v2/vms/platformvm/fx"
|
|
"github.com/luxfi/node/v2/v2/vms/platformvm/signer"
|
|
"github.com/luxfi/node/v2/v2/vms/platformvm/txs"
|
|
"github.com/luxfi/warp"
|
|
|
|
"github.com/jedib0t/go-pretty/v6/table"
|
|
"github.com/spf13/cobra"
|
|
"github.com/spf13/pflag"
|
|
)
|
|
|
|
const skipRelayerFlagName = "skip-relayer"
|
|
|
|
var (
|
|
sameControlKey bool
|
|
keyName string
|
|
threshold uint32
|
|
controlKeys []string
|
|
subnetAuthKeys []string
|
|
outputTxPath string
|
|
useLedger bool
|
|
useEwoq bool
|
|
ledgerAddresses []string
|
|
subnetIDStr string
|
|
mainnetChainID uint32
|
|
skipCreatePrompt bool
|
|
partialSync bool
|
|
subnetOnly bool
|
|
warpSpec subnet.WarpSpec
|
|
numNodes uint32
|
|
relayerAmount float64
|
|
relayerKeyName string
|
|
relayCChain bool
|
|
cChainFundingKey string
|
|
warpKeyName string
|
|
cchainIcmKeyName string
|
|
relayerAllowPrivateIPs bool
|
|
|
|
validatorManagerAddress string
|
|
deployFlags BlockchainDeployFlags
|
|
errMutuallyExlusiveControlKeys = errors.New("--control-keys and --same-control-key are mutually exclusive")
|
|
ErrMutuallyExlusiveKeyLedger = errors.New("key source flags --key, --ledger/--ledger-addrs are mutually exclusive")
|
|
ErrStoredKeyOnMainnet = errors.New("key --key is not available for mainnet operations")
|
|
errMutuallyExlusiveSubnetFlags = errors.New("--subnet-only and --subnet-id are mutually exclusive")
|
|
)
|
|
|
|
type BlockchainDeployFlags struct {
|
|
SigAggFlags flags.SignatureAggregatorFlags
|
|
LocalMachineFlags flags.LocalMachineFlags
|
|
ProofOfStakeFlags flags.POSFlags
|
|
BootstrapValidatorFlags flags.BootstrapValidatorFlags
|
|
ConvertOnly bool
|
|
}
|
|
|
|
// lux blockchain deploy
|
|
func newDeployCmd() *cobra.Command {
|
|
cmd := &cobra.Command{
|
|
Use: "deploy [blockchainName]",
|
|
Short: "Deploys a blockchain configuration",
|
|
Long: `The blockchain deploy command deploys your Blockchain configuration to Local Network, to Testnet, DevNet or to Mainnet.
|
|
|
|
At the end of the call, the command prints the RPC URL you can use to interact with the L1 / Subnet.
|
|
|
|
When deploying an L1, Lux-CLI lets you use your local machine as a bootstrap validator, so you don't need to run separate Lux nodes.
|
|
This is controlled by the --use-local-machine flag (enabled by default on Local Network).
|
|
|
|
If --use-local-machine is set to true:
|
|
- Lux-CLI will call CreateSubnetTx, CreateChainTx, ConvertSubnetToL1Tx, followed by syncing the local machine bootstrap validator to the L1 and initialize
|
|
Validator Manager Contract on the L1
|
|
|
|
If using your own Lux Nodes as bootstrap validators:
|
|
- Lux-CLI will call CreateSubnetTx, CreateChainTx, ConvertSubnetToL1Tx
|
|
- You will have to sync your bootstrap validators to the L1
|
|
- Next, Initialize Validator Manager contract on the L1 using lux contract initValidatorManager [L1_Name]
|
|
|
|
Lux-CLI only supports deploying an individual Blockchain once per network. Subsequent
|
|
attempts to deploy the same Blockchain to the same network (Local Network, Testnet, Mainnet) aren't
|
|
allowed. If you'd like to redeploy a Blockchain locally for testing, you must first call
|
|
lux network clean to reset all deployed chain state. Subsequent local deploys
|
|
redeploy the chain with fresh state. You can deploy the same Blockchain to multiple networks,
|
|
so you can take your locally tested Blockchain and deploy it on Testnet or Mainnet.`,
|
|
RunE: deployBlockchain,
|
|
PersistentPostRun: handlePostRun,
|
|
PreRunE: cobrautils.ExactArgs(1),
|
|
}
|
|
networkGroup := networkoptions.GetNetworkFlagsGroup(cmd, &globalNetworkFlags, true, networkoptions.DefaultSupportedNetworkOptions)
|
|
sigAggGroup := flags.AddSignatureAggregatorFlagsToCmd(cmd, &deployFlags.SigAggFlags)
|
|
|
|
cmd.Flags().StringVarP(&keyName, "key", "k", "", "select the key to use [testnet/devnet deploy only]")
|
|
cmd.Flags().StringVar(&outputTxPath, "output-tx-path", "", "file path of the blockchain creation tx (for multi-sig signing)")
|
|
cmd.Flags().BoolVarP(&useEwoq, "ewoq", "e", false, "use ewoq key [local/devnet deploy only]")
|
|
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().StringVarP(&subnetIDStr, "subnet-id", "u", "", "do not create a subnet, deploy the blockchain into the given subnet id")
|
|
cmd.Flags().Uint32Var(&mainnetChainID, "mainnet-chain-id", 0, "use different ChainID for mainnet deployment")
|
|
cmd.Flags().BoolVar(&subnetOnly, "subnet-only", false, "command stops after CreateSubnetTx and returns SubnetID")
|
|
cmd.Flags().BoolVar(&deployFlags.ConvertOnly, "convert-only", false, "avoid node track, restart and poa manager setup")
|
|
|
|
localNetworkGroup := flags.RegisterFlagGroup(cmd, "Local Network Flags", "show-local-network-flags", true, func(set *pflag.FlagSet) {
|
|
set.Uint32Var(&numNodes, "num-nodes", constants.LocalNetworkNumNodes, "number of nodes to be created on local network deploy")
|
|
set.StringVar(&deployFlags.LocalMachineFlags.LuxdBinaryPath, "luxd-path", "", "use this luxd binary path")
|
|
set.StringVar(
|
|
&deployFlags.LocalMachineFlags.UserProvidedLuxdVersion,
|
|
"luxd-version",
|
|
constants.DefaultLuxdVersion,
|
|
"use this version of luxd (ex: v1.17.12)",
|
|
)
|
|
})
|
|
|
|
nonSovGroup := flags.RegisterFlagGroup(cmd, "Non Subnet-Only-Validators (Non-SOV) Flags", "show-non-sov-flags", true, func(set *pflag.FlagSet) {
|
|
set.BoolVar(&sameControlKey, "same-control-key", false, "use the fee-paying key as control key")
|
|
set.Uint32Var(&threshold, "threshold", 0, "required number of control key signatures to make blockchain changes")
|
|
set.StringSliceVar(&controlKeys, "control-keys", nil, "addresses that may make blockchain changes")
|
|
set.StringSliceVar(&subnetAuthKeys, "auth-keys", nil, "control keys that will be used to authenticate chain creation")
|
|
})
|
|
|
|
bootstrapValidatorGroup := flags.AddBootstrapValidatorFlagsToCmd(cmd, &deployFlags.BootstrapValidatorFlags)
|
|
localMachineGroup := flags.AddLocalMachineFlagsToCmd(cmd, &deployFlags.LocalMachineFlags)
|
|
|
|
warpGroup := flags.RegisterFlagGroup(cmd, "Warp Flags", "show-warp-flags", false, func(set *pflag.FlagSet) {
|
|
set.BoolVar(&warpSpec.SkipWarpDeploy, "skip-warp-deploy", false, "Skip automatic Warp deploy")
|
|
set.BoolVar(&warpSpec.SkipRelayerDeploy, skipRelayerFlagName, false, "skip relayer deploy")
|
|
set.StringVar(&warpSpec.WarpVersion, "warp-version", constants.LatestReleaseVersionTag, "Warp version to deploy")
|
|
set.StringVar(&warpSpec.RelayerVersion, "relayer-version", constants.DefaultRelayerVersion, "relayer version to deploy")
|
|
set.StringVar(&warpSpec.RelayerBinPath, "relayer-path", "", "relayer binary to use")
|
|
set.StringVar(&warpSpec.RelayerLogLevel, "relayer-log-level", "info", "log level to be used for relayer logs")
|
|
set.Float64Var(&relayerAmount, "relayer-amount", 0, "automatically fund relayer fee payments with the given amount")
|
|
set.StringVar(&relayerKeyName, "relayer-key", "", "key to be used by default both for rewards and to pay fees")
|
|
set.StringVar(&warpKeyName, "warp-key", constants.WarpKeyName, "key to be used to pay for Warp deploys")
|
|
set.StringVar(&cchainIcmKeyName, "cchain-warp-key", "", "key to be used to pay for Warp deploys on C-Chain")
|
|
set.BoolVar(&relayCChain, "relay-cchain", true, "relay C-Chain as source and destination")
|
|
set.StringVar(&cChainFundingKey, "cchain-funding-key", "", "key to be used to fund relayer account on cchain")
|
|
set.BoolVar(&relayerAllowPrivateIPs, "relayer-allow-private-ips", true, "allow relayer to connec to private ips")
|
|
set.StringVar(&warpSpec.MessengerContractAddressPath, "teleporter-messenger-contract-address-path", "", "path to an Warp Messenger contract address file")
|
|
set.StringVar(&warpSpec.MessengerDeployerAddressPath, "teleporter-messenger-deployer-address-path", "", "path to an Warp Messenger deployer address file")
|
|
set.StringVar(&warpSpec.MessengerDeployerTxPath, "teleporter-messenger-deployer-tx-path", "", "path to an Warp Messenger deployer tx file")
|
|
set.StringVar(&warpSpec.RegistryBydecodePath, "teleporter-registry-bytecode-path", "", "path to an Warp Registry bytecode file")
|
|
})
|
|
posGroup := flags.AddProofOfStakeToCmd(cmd, &deployFlags.ProofOfStakeFlags)
|
|
|
|
cmd.SetHelpFunc(flags.WithGroupedHelp([]flags.GroupedFlags{networkGroup, bootstrapValidatorGroup, localMachineGroup, localNetworkGroup, nonSovGroup, warpGroup, posGroup, sigAggGroup}))
|
|
return cmd
|
|
}
|
|
|
|
type SubnetValidator struct {
|
|
// Must be Ed25519 NodeID
|
|
NodeID ids.NodeID `json:"nodeID"`
|
|
// Weight of this validator used when sampling
|
|
Weight uint64 `json:"weight"`
|
|
// When this validator will stop validating the Subnet
|
|
EndTime uint64 `json:"endTime"`
|
|
// Initial balance for this validator
|
|
Balance uint64 `json:"balance"`
|
|
// [Signer] is the BLS key for this validator.
|
|
// Note: We do not enforce that the BLS key is unique across all validators.
|
|
// This means that validators can share a key if they so choose.
|
|
// However, a NodeID + Subnet does uniquely map to a BLS key
|
|
Signer signer.Signer `json:"signer"`
|
|
// Leftover $LUX from the [Balance] will be issued to this
|
|
// owner once it is removed from the validator set.
|
|
ChangeOwner fx.Owner `json:"changeOwner"`
|
|
}
|
|
|
|
func CallDeploy(
|
|
cmd *cobra.Command,
|
|
subnetOnlyParam bool,
|
|
blockchainName string,
|
|
networkFlags networkoptions.NetworkFlags,
|
|
keyNameParam string,
|
|
useLedgerParam bool,
|
|
useEwoqParam bool,
|
|
sameControlKeyParam bool,
|
|
) error {
|
|
subnetOnly = subnetOnlyParam
|
|
globalNetworkFlags = networkFlags
|
|
sameControlKey = sameControlKeyParam
|
|
keyName = keyNameParam
|
|
useLedger = useLedgerParam
|
|
useEwoq = useEwoqParam
|
|
return deployBlockchain(cmd, []string{blockchainName})
|
|
}
|
|
|
|
func getChainsInSubnet(blockchainName string) ([]string, error) {
|
|
subnets, err := os.ReadDir(app.GetSubnetDir())
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to read baseDir: %w", err)
|
|
}
|
|
|
|
chains := []string{}
|
|
|
|
for _, s := range subnets {
|
|
if !s.IsDir() {
|
|
continue
|
|
}
|
|
sidecarFile := filepath.Join(app.GetSubnetDir(), s.Name(), constants.SidecarFileName)
|
|
if _, err := os.Stat(sidecarFile); err == nil {
|
|
// read in sidecar file
|
|
jsonBytes, err := os.ReadFile(sidecarFile)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed reading file %s: %w", sidecarFile, err)
|
|
}
|
|
|
|
var sc models.Sidecar
|
|
err = json.Unmarshal(jsonBytes, &sc)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed unmarshaling file %s: %w", sidecarFile, err)
|
|
}
|
|
if sc.Subnet == blockchainName {
|
|
chains = append(chains, sc.Name)
|
|
}
|
|
}
|
|
}
|
|
return chains, nil
|
|
}
|
|
|
|
func checkSubnetEVMDefaultAddressNotInAlloc(network models.Network, chain string) error {
|
|
if network.Kind != models.Local &&
|
|
network.Kind != models.Devnet &&
|
|
!simulatedPublicNetwork() {
|
|
genesis, err := app.LoadEvmGenesis(chain)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
allocAddressMap := genesis.Alloc
|
|
for address := range allocAddressMap {
|
|
if address.String() == vm.PrefundedEwoqAddress.String() {
|
|
return fmt.Errorf("can't airdrop to default address on public networks, please edit the genesis by calling `lux blockchain create %s --force`", chain)
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func runDeploy(cmd *cobra.Command, args []string) error {
|
|
skipCreatePrompt = true
|
|
return deployBlockchain(cmd, args)
|
|
}
|
|
|
|
func updateSubnetEVMGenesisChainID(genesisBytes []byte, newChainID uint) ([]byte, error) {
|
|
var genesisMap map[string]interface{}
|
|
if err := json.Unmarshal(genesisBytes, &genesisMap); err != nil {
|
|
return nil, err
|
|
}
|
|
configI, ok := genesisMap["config"]
|
|
if !ok {
|
|
return nil, fmt.Errorf("config field not found on genesis")
|
|
}
|
|
config, ok := configI.(map[string]interface{})
|
|
if !ok {
|
|
return nil, fmt.Errorf("expected genesis config field to be a map[string]interface, found %T", configI)
|
|
}
|
|
config["chainId"] = float64(newChainID)
|
|
return json.MarshalIndent(genesisMap, "", " ")
|
|
}
|
|
|
|
// updates sidecar with genesis mainnet id to use
|
|
// given either by cmdline flag, original genesis id, or id obtained from the user
|
|
func getSubnetEVMMainnetChainID(sc *models.Sidecar, blockchainName string) error {
|
|
// get original chain id
|
|
evmGenesis, err := app.LoadEvmGenesis(blockchainName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if evmGenesis.Config == nil {
|
|
return fmt.Errorf("invalid subnet evm genesis format: config is nil")
|
|
}
|
|
if evmGenesis.Config.ChainID == nil {
|
|
return fmt.Errorf("invalid subnet evm genesis format: config chain id is nil")
|
|
}
|
|
originalChainID := evmGenesis.Config.ChainID.Uint64()
|
|
// handle cmdline flag if given
|
|
if mainnetChainID != 0 {
|
|
sc.SubnetEVMMainnetChainID = uint(mainnetChainID)
|
|
}
|
|
// prompt the user
|
|
if sc.SubnetEVMMainnetChainID == 0 {
|
|
useSameChainID := "Use same ChainID"
|
|
useNewChainID := "Use new ChainID"
|
|
listOptions := []string{useNewChainID, useSameChainID}
|
|
newChainIDPrompt := "Using the same ChainID for both Testnet and Mainnet could lead to a replay attack. Do you want to use a different ChainID?"
|
|
var (
|
|
err error
|
|
decision string
|
|
)
|
|
decision, err = app.Prompt.CaptureList(newChainIDPrompt, listOptions)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if decision == useSameChainID {
|
|
sc.SubnetEVMMainnetChainID = uint(originalChainID)
|
|
} else {
|
|
ux.Logger.PrintToUser("Enter your blockchain's ChainID. It can be any positive integer != %d.", originalChainID)
|
|
newChainID, err := app.Prompt.CapturePositiveInt(
|
|
"ChainID",
|
|
[]comparator.Comparator{
|
|
{
|
|
Label: "Zero",
|
|
Type: comparator.MoreThan,
|
|
Value: 0,
|
|
},
|
|
{
|
|
Label: "Original Chain ID",
|
|
Type: comparator.NotEq,
|
|
Value: originalChainID,
|
|
},
|
|
},
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
sc.SubnetEVMMainnetChainID = uint(newChainID)
|
|
}
|
|
}
|
|
return app.UpdateSidecar(sc)
|
|
}
|
|
|
|
func deployLocalNetworkPreCheck(cmd *cobra.Command, network models.Network, bootstrapValidatorFlags flags.BootstrapValidatorFlags) error {
|
|
if network.Kind == models.Local {
|
|
if cmd.Flags().Changed("use-local-machine") && !deployFlags.LocalMachineFlags.UseLocalMachine &&
|
|
bootstrapValidatorFlags.BootstrapEndpoints == nil &&
|
|
bootstrapValidatorFlags.BootstrapValidatorsJSONFilePath == "" &&
|
|
!bootstrapValidatorFlags.GenerateNodeID {
|
|
return fmt.Errorf("deploying blockchain on local network requires local machine to be used as bootstrap validator")
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// checks for flags that will conflict if user sets convert only to false or if user sets use-local-machine to true
|
|
// if any of generateNodeID, bootstrapValidatorsJSONFilePath or bootstrapEndpoints is used by user,
|
|
// convertOnly will be set to true
|
|
func validateConvertOnlyFlag(cmd *cobra.Command, bootstrapValidatorFlags flags.BootstrapValidatorFlags, convertOnly *bool, useLocalMachine bool) error {
|
|
if bootstrapValidatorFlags.GenerateNodeID ||
|
|
bootstrapValidatorFlags.BootstrapValidatorsJSONFilePath != "" ||
|
|
bootstrapValidatorFlags.BootstrapEndpoints != nil {
|
|
flagName := ""
|
|
switch {
|
|
case bootstrapValidatorFlags.GenerateNodeID:
|
|
flagName = "--generate-node-id=true"
|
|
case bootstrapValidatorFlags.BootstrapValidatorsJSONFilePath != "":
|
|
flagName = "--bootstrap-filepath is not empty"
|
|
case bootstrapValidatorFlags.BootstrapEndpoints != nil:
|
|
flagName = "--bootstrap-endpoints is not empty"
|
|
}
|
|
if cmd.Flags().Changed("use-local-machine") && useLocalMachine {
|
|
return fmt.Errorf("cannot use local machine as bootstrap validator if %s", flagName)
|
|
}
|
|
if cmd.Flags().Changed("convert-only") && !*convertOnly {
|
|
return fmt.Errorf("cannot set --convert-only=false if %s", flagName)
|
|
}
|
|
*convertOnly = true
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func prepareBootstrapValidators(
|
|
bootstrapValidators *[]models.SubnetValidator,
|
|
network models.Network,
|
|
sidecar models.Sidecar,
|
|
kc keychain.Keychain,
|
|
blockchainName string,
|
|
deployBalance,
|
|
availableBalance uint64,
|
|
localMachineFlags *flags.LocalMachineFlags,
|
|
bootstrapValidatorFlags *flags.BootstrapValidatorFlags,
|
|
) error {
|
|
var err error
|
|
if bootstrapValidatorFlags.ChangeOwnerAddress == "" {
|
|
// use provided key as change owner unless already set
|
|
if pAddr, err := kc.PChainFormattedStrAddresses(); err == nil && len(pAddr) > 0 {
|
|
bootstrapValidatorFlags.ChangeOwnerAddress = pAddr[0]
|
|
ux.Logger.PrintToUser("Using [%s] to be set as a change owner for leftover LUX", bootstrapValidatorFlags.ChangeOwnerAddress)
|
|
}
|
|
}
|
|
if !bootstrapValidatorFlags.GenerateNodeID && bootstrapValidatorFlags.BootstrapEndpoints == nil && bootstrapValidatorFlags.BootstrapValidatorsJSONFilePath == "" {
|
|
if cancel, err := StartLocalMachine(
|
|
network,
|
|
sidecar,
|
|
blockchainName,
|
|
deployBalance,
|
|
availableBalance,
|
|
localMachineFlags,
|
|
bootstrapValidatorFlags,
|
|
); err != nil {
|
|
return err
|
|
} else if cancel {
|
|
return nil
|
|
}
|
|
}
|
|
switch {
|
|
case len(bootstrapValidatorFlags.BootstrapEndpoints) > 0:
|
|
for _, endpoint := range bootstrapValidatorFlags.BootstrapEndpoints {
|
|
infoClient := info.NewClient(endpoint)
|
|
ctx, cancel := utils.GetAPILargeContext()
|
|
defer cancel()
|
|
nodeID, proofOfPossession, err := infoClient.GetNodeID(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
publicKey = "0x" + hex.EncodeToString(proofOfPossession.PublicKey[:])
|
|
pop = "0x" + hex.EncodeToString(proofOfPossession.ProofOfPossession[:])
|
|
|
|
*bootstrapValidators = append(*bootstrapValidators, models.SubnetValidator{
|
|
NodeID: nodeID.String(),
|
|
Weight: constants.BootstrapValidatorWeight,
|
|
Balance: deployBalance,
|
|
BLSPublicKey: publicKey,
|
|
BLSProofOfPossession: pop,
|
|
ChangeOwnerAddr: bootstrapValidatorFlags.ChangeOwnerAddress,
|
|
})
|
|
}
|
|
case clusterNameFlagValue != "":
|
|
// for remote clusters we don't need to ask for bootstrap validators and can read it from filesystem
|
|
*bootstrapValidators, err = getClusterBootstrapValidators(clusterNameFlagValue, network, deployBalance)
|
|
if err != nil {
|
|
return fmt.Errorf("error getting bootstrap validators from cluster %s: %w", clusterNameFlagValue, err)
|
|
}
|
|
|
|
default:
|
|
if len(*bootstrapValidators) == 0 {
|
|
*bootstrapValidators, err = promptBootstrapValidators(
|
|
network,
|
|
deployBalance,
|
|
availableBalance,
|
|
bootstrapValidatorFlags,
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// deployBlockchain is the cobra command run for deploying subnets
|
|
func deployBlockchain(cmd *cobra.Command, args []string) error {
|
|
blockchainName := args[0]
|
|
|
|
if err := CreateBlockchainFirst(cmd, blockchainName, skipCreatePrompt); err != nil {
|
|
return err
|
|
}
|
|
|
|
chains, err := ValidateSubnetNameAndGetChains(args)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if warpSpec.MessengerContractAddressPath != "" || warpSpec.MessengerDeployerAddressPath != "" || warpSpec.MessengerDeployerTxPath != "" || warpSpec.RegistryBydecodePath != "" {
|
|
if warpSpec.MessengerContractAddressPath == "" || warpSpec.MessengerDeployerAddressPath == "" || warpSpec.MessengerDeployerTxPath == "" || warpSpec.RegistryBydecodePath == "" {
|
|
return fmt.Errorf("if setting any Warp asset path, you must set all Warp asset paths")
|
|
}
|
|
}
|
|
|
|
var bootstrapValidators []models.SubnetValidator
|
|
if deployFlags.BootstrapValidatorFlags.BootstrapValidatorsJSONFilePath != "" {
|
|
bootstrapValidators, err = LoadBootstrapValidator(deployFlags.BootstrapValidatorFlags)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
deployFlags.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 sidecar.ImportedFromLPM {
|
|
return errors.New("unable to deploy blockchains imported from a repo")
|
|
}
|
|
|
|
if outputTxPath != "" {
|
|
if _, err := os.Stat(outputTxPath); err == nil {
|
|
return fmt.Errorf("outputTxPath %q already exists", outputTxPath)
|
|
}
|
|
}
|
|
|
|
if !sidecar.Sovereign && deployFlags.BootstrapValidatorFlags.BootstrapValidatorsJSONFilePath != "" {
|
|
return fmt.Errorf("--bootstrap-filepath flag is only applicable to sovereign blockchains")
|
|
}
|
|
|
|
network, err := networkoptions.GetNetworkFromCmdLineFlags(
|
|
app,
|
|
"",
|
|
globalNetworkFlags,
|
|
true,
|
|
false,
|
|
networkoptions.DefaultSupportedNetworkOptions,
|
|
"",
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
clusterNameFlagValue = globalNetworkFlags.ClusterName
|
|
|
|
isEVMGenesis, validationErr, err := app.HasSubnetEVMGenesis(chain)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if sidecar.VM == models.SubnetEvm && !isEVMGenesis {
|
|
return fmt.Errorf("failed to validate SubnetEVM genesis format: %w", validationErr)
|
|
}
|
|
|
|
chainGenesis, err := app.LoadRawGenesis(chain)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if isEVMGenesis {
|
|
// is is a subnet evm or a custom vm based on subnet evm
|
|
if network.Kind == models.Mainnet {
|
|
err = getSubnetEVMMainnetChainID(&sidecar, chain)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
chainGenesis, err = updateSubnetEVMGenesisChainID(chainGenesis, sidecar.SubnetEVMMainnetChainID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
err = checkSubnetEVMDefaultAddressNotInAlloc(network, chain)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if err = validateConvertOnlyFlag(cmd, deployFlags.BootstrapValidatorFlags, &deployFlags.ConvertOnly, deployFlags.LocalMachineFlags.UseLocalMachine); err != nil {
|
|
return err
|
|
}
|
|
|
|
ux.Logger.PrintToUser("Deploying %s to %s", chains, network.Name())
|
|
|
|
if network.Kind == models.Local {
|
|
if err = deployLocalNetworkPreCheck(cmd, network, deployFlags.BootstrapValidatorFlags); err != nil {
|
|
return err
|
|
}
|
|
app.Log.Debug("Deploy local")
|
|
|
|
luxdVersion := deployFlags.LocalMachineFlags.UserProvidedLuxdVersion
|
|
|
|
if luxdVersion == constants.DefaultLuxdVersion && deployFlags.LocalMachineFlags.LuxdBinaryPath == "" {
|
|
luxdVersion, err = dependencies.GetLatestCLISupportedDependencyVersion(app, constants.LuxdRepoName, network, &sidecar.RPCVersion)
|
|
if err != nil {
|
|
if err != dependencies.ErrNoLuxdVersion {
|
|
return err
|
|
}
|
|
luxdVersion = constants.LatestPreReleaseVersionTag
|
|
}
|
|
}
|
|
|
|
ux.Logger.PrintToUser("")
|
|
if err := networkcmd.Start(
|
|
networkcmd.StartFlags{
|
|
UserProvidedLuxdVersion: luxdVersion,
|
|
LuxdBinaryPath: deployFlags.LocalMachineFlags.LuxdBinaryPath,
|
|
NumNodes: numNodes,
|
|
},
|
|
false,
|
|
); err != nil {
|
|
return err
|
|
}
|
|
|
|
// check if blockchain rpc version matches what is currently running
|
|
// for the case version or binary was provided
|
|
_, _, networkRPCVersion, err := localnet.GetLocalNetworkLuxdVersion(app)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if networkRPCVersion != sidecar.RPCVersion {
|
|
return fmt.Errorf(
|
|
"the current local network uses rpc version %d but your blockchain has version %d and is not compatible",
|
|
networkRPCVersion,
|
|
sidecar.RPCVersion,
|
|
)
|
|
}
|
|
|
|
useEwoq = true
|
|
|
|
if !sidecar.Sovereign {
|
|
// sovereign blockchains are deployed into new local clusters,
|
|
// non sovereign blockchains are deployed into the local network itself
|
|
if b, err := localnet.BlockchainAlreadyDeployedOnLocalNetwork(app, blockchainName); err != nil {
|
|
return err
|
|
} else if b {
|
|
return fmt.Errorf("blockchain %s has already been deployed", blockchainName)
|
|
}
|
|
}
|
|
}
|
|
|
|
createSubnet := true
|
|
var subnetID ids.ID
|
|
if subnetIDStr != "" {
|
|
subnetID, err = ids.FromString(subnetIDStr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
createSubnet = false
|
|
} else if !subnetOnly && sidecar.Networks != nil {
|
|
model, ok := sidecar.Networks[network.Name()]
|
|
if ok {
|
|
if model.SubnetID != ids.Empty && model.BlockchainID == ids.Empty {
|
|
subnetID = model.SubnetID
|
|
createSubnet = false
|
|
}
|
|
}
|
|
}
|
|
|
|
// TODO: will estimate fee in subsecuent PR
|
|
// !subnetonly: add blockchain fee
|
|
// createSubnet: add subnet fee
|
|
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(deployFlags.BootstrapValidatorFlags.DeployBalanceLUX * float64(units.Lux))
|
|
// whether user has created Lux Nodes when blockchain deploy command is called
|
|
if sidecar.Sovereign && !subnetOnly {
|
|
err = prepareBootstrapValidators(&bootstrapValidators, network, sidecar, *kc, blockchainName, deployBalance, availableBalance, &deployFlags.LocalMachineFlags, &deployFlags.BootstrapValidatorFlags)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
} else if network.Kind == models.Local {
|
|
sameControlKey = true
|
|
}
|
|
|
|
// from here on we are assuming a public deploy
|
|
if subnetOnly && subnetIDStr != "" {
|
|
return errMutuallyExlusiveSubnetFlags
|
|
}
|
|
|
|
if sidecar.Sovereign {
|
|
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,
|
|
)
|
|
}
|
|
}
|
|
|
|
network.HandlePublicNetworkSimulation()
|
|
|
|
if createSubnet {
|
|
if sidecar.Sovereign {
|
|
sameControlKey = true
|
|
}
|
|
controlKeys, threshold, err = promptOwners(
|
|
kc,
|
|
controlKeys,
|
|
sameControlKey,
|
|
threshold,
|
|
subnetAuthKeys,
|
|
true,
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
ux.Logger.PrintToUser(logging.Blue.Wrap(
|
|
fmt.Sprintf("Deploying into pre-existent subnet ID %s", subnetID.String()),
|
|
))
|
|
var isPermissioned bool
|
|
isPermissioned, controlKeys, threshold, err = txutils.GetOwners(network, subnetID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !isPermissioned {
|
|
return ErrNotPermissionedSubnet
|
|
}
|
|
}
|
|
|
|
// add control keys to the keychain whenever possible
|
|
if err := kc.AddAddresses(controlKeys); err != nil {
|
|
return err
|
|
}
|
|
|
|
kcKeys, err := kc.PChainFormattedStrAddresses()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// get keys for blockchain 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 blockchain auth keys for chain creation: %s", subnetAuthKeys)
|
|
|
|
// deploy to public network
|
|
deployer := subnet.NewPublicDeployer(app, kc, network)
|
|
|
|
if createSubnet {
|
|
subnetID, err = deployer.DeploySubnet(controlKeys, threshold)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// TODO: remove once dynamic fees conf can be updated on wallet
|
|
deployer.CleanCacheWallet()
|
|
// get the control keys in the same order as the tx
|
|
_, controlKeys, threshold, err = txutils.GetOwners(network, subnetID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
var (
|
|
savePartialTx bool
|
|
blockchainID ids.ID
|
|
tx *txs.Tx
|
|
remainingSubnetAuthKeys []string
|
|
isFullySigned bool
|
|
)
|
|
|
|
if !subnetOnly {
|
|
isFullySigned, blockchainID, tx, remainingSubnetAuthKeys, err = deployer.DeployBlockchain(
|
|
controlKeys,
|
|
subnetAuthKeys,
|
|
subnetID,
|
|
chain,
|
|
chainGenesis,
|
|
)
|
|
if err != nil {
|
|
ux.Logger.PrintToUser(logging.Red.Wrap(
|
|
fmt.Sprintf("error deploying blockchain: %s. fix the issue and try again with a new deploy cmd", err),
|
|
))
|
|
return err
|
|
}
|
|
// TODO: remove once dynamic fees conf can be updated on wallet
|
|
deployer.CleanCacheWallet()
|
|
|
|
savePartialTx = !isFullySigned && err == nil
|
|
}
|
|
|
|
if err := PrintDeployResults(chain, subnetID, blockchainID); err != nil {
|
|
return err
|
|
}
|
|
|
|
if savePartialTx {
|
|
if err := SaveNotFullySignedTx(
|
|
"Blockchain Creation",
|
|
tx,
|
|
chain,
|
|
subnetAuthKeys,
|
|
remainingSubnetAuthKeys,
|
|
outputTxPath,
|
|
false,
|
|
); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// stop here if subnetOnly is true
|
|
if subnetOnly {
|
|
return nil
|
|
}
|
|
|
|
// needs to first stop relayer so non sovereign subnets successfully restart
|
|
if sidecar.TeleporterReady && !warpSpec.SkipWarpDeploy && !warpSpec.SkipRelayerDeploy && network.Kind != models.Mainnet {
|
|
_ = relayercmd.CallStop(nil, relayercmd.StopFlags{}, network)
|
|
}
|
|
|
|
tracked := false
|
|
|
|
if sidecar.Sovereign {
|
|
validatorManagerStr := validatormanagerSDK.ValidatorProxyContractAddress
|
|
luxdBootstrapValidators, cancel, savePartialTx, err := convertSubnetToL1(
|
|
bootstrapValidators,
|
|
deployer,
|
|
subnetID,
|
|
blockchainID,
|
|
network,
|
|
chain,
|
|
sidecar,
|
|
controlKeys,
|
|
subnetAuthKeys,
|
|
validatorManagerStr,
|
|
false,
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if cancel {
|
|
return nil
|
|
}
|
|
|
|
if savePartialTx {
|
|
return nil
|
|
}
|
|
if deployFlags.ConvertOnly || (!deployFlags.LocalMachineFlags.UseLocalMachine && clusterNameFlagValue == "") {
|
|
printSuccessfulConvertOnlyOutput(blockchainName, subnetID.String(), deployFlags.BootstrapValidatorFlags.GenerateNodeID)
|
|
return nil
|
|
}
|
|
|
|
tracked, err = InitializeValidatorManager(
|
|
blockchainName,
|
|
sidecar.ValidatorManagerOwner,
|
|
subnetID,
|
|
blockchainID,
|
|
network,
|
|
luxdBootstrapValidators,
|
|
sidecar.ValidatorManagement == validatormanagertypes.ProofOfStake,
|
|
validatorManagerStr,
|
|
sidecar.ProxyContractOwner,
|
|
sidecar.UseACP99,
|
|
deployFlags.LocalMachineFlags.UseLocalMachine,
|
|
deployFlags.SigAggFlags,
|
|
deployFlags.ProofOfStakeFlags,
|
|
)
|
|
if 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 {
|
|
if err := app.UpdateSidecarNetworks(
|
|
&sidecar,
|
|
network,
|
|
subnetID,
|
|
blockchainID,
|
|
"",
|
|
"",
|
|
nil,
|
|
clusterNameFlagValue,
|
|
"",
|
|
); err != nil {
|
|
return err
|
|
}
|
|
if network.Kind == models.Local && !simulatedPublicNetwork() {
|
|
ux.Logger.PrintToUser("")
|
|
if err := localnet.LocalNetworkTrackSubnet(
|
|
app,
|
|
ux.Logger.PrintToUser,
|
|
blockchainName,
|
|
); err != nil {
|
|
return err
|
|
}
|
|
tracked = true
|
|
}
|
|
}
|
|
|
|
if sidecar.Sovereign && tracked {
|
|
ux.Logger.PrintToUser("")
|
|
ux.Logger.PrintToUser(logging.Green.Wrap("Your L1 is ready for on-chain interactions."))
|
|
}
|
|
|
|
var warpErr, relayerErr error
|
|
if sidecar.TeleporterReady && tracked && !warpSpec.SkipWarpDeploy {
|
|
chainSpec := contract.ChainSpec{
|
|
BlockchainName: blockchainName,
|
|
}
|
|
chainSpec.SetEnabled(true, false, false, false, false)
|
|
deployWarpFlags := messengercmd.DeployFlags{
|
|
ChainFlags: chainSpec,
|
|
PrivateKeyFlags: contract.PrivateKeyFlags{
|
|
KeyName: warpKeyName,
|
|
},
|
|
DeployMessenger: true,
|
|
DeployRegistry: true,
|
|
ForceRegistryDeploy: true,
|
|
Version: warpSpec.WarpVersion,
|
|
MessengerContractAddressPath: warpSpec.MessengerContractAddressPath,
|
|
MessengerDeployerAddressPath: warpSpec.MessengerDeployerAddressPath,
|
|
MessengerDeployerTxPath: warpSpec.MessengerDeployerTxPath,
|
|
RegistryBydecodePath: warpSpec.RegistryBydecodePath,
|
|
CChainKeyName: cchainIcmKeyName,
|
|
}
|
|
ux.Logger.PrintToUser("")
|
|
if err := messengercmd.CallDeploy([]string{}, deployWarpFlags, network); err != nil {
|
|
warpErr = err
|
|
ux.Logger.RedXToUser("Interchain Messaging is not deployed due to: %v", warpErr)
|
|
} else {
|
|
ux.Logger.GreenCheckmarkToUser("Warp is successfully deployed")
|
|
if network.Kind != models.Local && !deployFlags.LocalMachineFlags.UseLocalMachine {
|
|
if flag := cmd.Flags().Lookup(skipRelayerFlagName); flag != nil && !flag.Changed {
|
|
ux.Logger.PrintToUser("")
|
|
yes, err := app.Prompt.CaptureYesNo("Do you want to setup local relayer for the messages to be interchanged, as Interchain Messaging was deployed to your blockchain?")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
warpSpec.SkipRelayerDeploy = !yes
|
|
}
|
|
}
|
|
if !warpSpec.SkipRelayerDeploy && network.Kind != models.Mainnet {
|
|
if network.Kind == models.Local && warpSpec.RelayerBinPath == "" && warpSpec.RelayerVersion == constants.DefaultRelayerVersion {
|
|
if b, extraLocalNetworkData, err := localnet.GetExtraLocalNetworkData(app, ""); err != nil {
|
|
return err
|
|
} else if b {
|
|
warpSpec.RelayerBinPath = extraLocalNetworkData.RelayerPath
|
|
}
|
|
}
|
|
deployRelayerFlags := relayercmd.DeployFlags{
|
|
Version: warpSpec.RelayerVersion,
|
|
BinPath: warpSpec.RelayerBinPath,
|
|
LogLevel: warpSpec.RelayerLogLevel,
|
|
RelayCChain: relayCChain,
|
|
CChainFundingKey: cChainFundingKey,
|
|
BlockchainsToRelay: []string{blockchainName},
|
|
Key: relayerKeyName,
|
|
Amount: relayerAmount,
|
|
AllowPrivateIPs: relayerAllowPrivateIPs,
|
|
}
|
|
if network.Kind == models.Local {
|
|
blockchains, err := localnet.GetLocalNetworkBlockchainsInfo(app)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
deployRelayerFlags.BlockchainsToRelay = utils.Unique(sdkutils.Map(blockchains, func(i localnet.BlockchainInfo) string { return i.Name }))
|
|
}
|
|
if network.Kind == models.Local || deployFlags.LocalMachineFlags.UseLocalMachine {
|
|
relayerKeyName, _, _, err := relayer.GetDefaultRelayerKeyInfo(app)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
deployRelayerFlags.Key = relayerKeyName
|
|
deployRelayerFlags.Amount = constants.DefaultRelayerAmount
|
|
deployRelayerFlags.BlockchainFundingKey = constants.WarpKeyName
|
|
}
|
|
if network.Kind == models.Local {
|
|
deployRelayerFlags.CChainFundingKey = "ewoq"
|
|
deployRelayerFlags.CChainAmount = constants.DefaultRelayerAmount
|
|
}
|
|
if err := relayercmd.CallDeploy(nil, deployRelayerFlags, network); err != nil {
|
|
relayerErr = err
|
|
ux.Logger.RedXToUser("Relayer is not deployed due to: %v", relayerErr)
|
|
} else {
|
|
ux.Logger.GreenCheckmarkToUser("Relayer is successfully deployed")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
flags := make(map[string]string)
|
|
flags[constants.MetricsNetwork] = network.Name()
|
|
metrics.HandleTracking(app, flags, nil)
|
|
|
|
if network.Kind == models.Local && !simulatedPublicNetwork() {
|
|
ux.Logger.PrintToUser("")
|
|
_ = PrintSubnetInfo(blockchainName, true)
|
|
}
|
|
if warpErr != nil {
|
|
ux.Logger.PrintToUser("")
|
|
ux.Logger.PrintToUser("Interchain Messaging is not deployed due to: %v", warpErr)
|
|
ux.Logger.PrintToUser("")
|
|
ux.Logger.PrintToUser("To deploy Warp later on, call `lux warp deploy`")
|
|
ux.Logger.PrintToUser("This does not affect L1 operations besides Interchain Messaging")
|
|
}
|
|
if relayerErr != nil {
|
|
ux.Logger.PrintToUser("")
|
|
ux.Logger.PrintToUser("Relayer is not deployed due to: %v", relayerErr)
|
|
ux.Logger.PrintToUser("")
|
|
ux.Logger.PrintToUser("To deploy a local relayer later on, call `lux interchain relayer deploy`")
|
|
ux.Logger.PrintToUser("This does not affect L1 operations besides Interchain Messaging")
|
|
}
|
|
|
|
if tracked {
|
|
if sidecar.Sovereign {
|
|
ux.Logger.GreenCheckmarkToUser("L1 is successfully deployed on %s", network.Name())
|
|
} else {
|
|
ux.Logger.GreenCheckmarkToUser("Subnet is successfully deployed on %s", network.Name())
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func setBootstrapValidatorValidationID(luxdBootstrapValidators []*txs.ConvertSubnetToL1Validator, bootstrapValidators []models.SubnetValidator, subnetID ids.ID) {
|
|
for index, luxdValidator := range luxdBootstrapValidators {
|
|
for bootstrapValidatorIndex, validator := range bootstrapValidators {
|
|
luxdValidatorNodeID, _ := ids.ToNodeID(luxdValidator.NodeID)
|
|
if validator.NodeID == luxdValidatorNodeID.String() {
|
|
validationID := subnetID.Append(uint32(index))
|
|
bootstrapValidators[bootstrapValidatorIndex].ValidationID = validationID.String()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func getClusterBootstrapValidators(
|
|
clusterName string,
|
|
network models.Network,
|
|
deployBalance uint64,
|
|
) ([]models.SubnetValidator, error) {
|
|
clusterConf, err := app.GetClusterConfig(clusterName)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
subnetValidators := []models.SubnetValidator{}
|
|
hostIDs := utils.Filter(clusterConf.GetCloudIDs(), clusterConf.IsLuxdHost)
|
|
changeAddr := ""
|
|
for _, h := range hostIDs {
|
|
nodeID, pub, pop, err := utils.GetNodeParams(app.GetNodeInstanceDirPath(h))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to parse nodeID: %w", err)
|
|
}
|
|
changeAddr, err = blockchain.GetKeyForChangeOwner(app, network)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
ux.Logger.Info("Bootstrap validator info for Host: %s | Node ID: %s | Public Key: %s | Proof of Possession: %s", h, nodeID, hex.EncodeToString(pub), hex.EncodeToString(pop))
|
|
subnetValidators = append(subnetValidators, models.SubnetValidator{
|
|
NodeID: nodeID.String(),
|
|
Weight: constants.BootstrapValidatorWeight,
|
|
Balance: deployBalance,
|
|
BLSPublicKey: fmt.Sprintf("%s%s", "0x", hex.EncodeToString(pub)),
|
|
BLSProofOfPossession: fmt.Sprintf("%s%s", "0x", hex.EncodeToString(pop)),
|
|
ChangeOwnerAddr: changeAddr,
|
|
})
|
|
}
|
|
return subnetValidators, nil
|
|
}
|
|
|
|
// TODO: add deactivation owner?
|
|
func ConvertToLuxdSubnetValidator(subnetValidators []models.SubnetValidator) ([]*txs.ConvertSubnetToL1Validator, error) {
|
|
bootstrapValidators := []*txs.ConvertSubnetToL1Validator{}
|
|
for _, validator := range subnetValidators {
|
|
nodeID, err := ids.NodeIDFromString(validator.NodeID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
blsInfo, err := blockchain.ConvertToBLSProofOfPossession(validator.BLSPublicKey, validator.BLSProofOfPossession)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failure parsing BLS info: %w", err)
|
|
}
|
|
addrs, err := address.ParseToIDs([]string{validator.ChangeOwnerAddr})
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failure parsing change owner address: %w", err)
|
|
}
|
|
bootstrapValidator := &txs.ConvertSubnetToL1Validator{
|
|
NodeID: nodeID[:],
|
|
Weight: validator.Weight,
|
|
Balance: validator.Balance,
|
|
Signer: blsInfo,
|
|
RemainingBalanceOwner: message.PChainOwner{
|
|
Threshold: 1,
|
|
Addresses: addrs,
|
|
},
|
|
}
|
|
bootstrapValidators = append(bootstrapValidators, bootstrapValidator)
|
|
}
|
|
luxdutils.Sort(bootstrapValidators)
|
|
return bootstrapValidators, nil
|
|
}
|
|
|
|
func ValidateSubnetNameAndGetChains(args []string) ([]string, error) {
|
|
// this should not be necessary but some bright guy might just be creating
|
|
// the genesis by hand or something...
|
|
if err := checkInvalidSubnetNames(args[0]); err != nil {
|
|
return nil, fmt.Errorf("blockchain name %s is invalid: %w", args[0], err)
|
|
}
|
|
// Check subnet exists
|
|
// TODO create a file that lists chains by subnet for fast querying
|
|
chains, err := getChainsInSubnet(args[0])
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to getChainsInSubnet: %w", err)
|
|
}
|
|
|
|
if len(chains) == 0 {
|
|
return nil, errors.New("Invalid blockchain " + args[0])
|
|
}
|
|
|
|
return chains, nil
|
|
}
|
|
|
|
func SaveNotFullySignedTx(
|
|
txName string,
|
|
tx *txs.Tx,
|
|
blockchainName string,
|
|
subnetAuthKeys []string,
|
|
remainingSubnetAuthKeys []string,
|
|
outputTxPath string,
|
|
forceOverwrite bool,
|
|
) error {
|
|
signedCount := len(subnetAuthKeys) - len(remainingSubnetAuthKeys)
|
|
ux.Logger.PrintToUser("")
|
|
if signedCount == len(subnetAuthKeys) {
|
|
ux.Logger.PrintToUser("All %d required %s signatures have been signed. "+
|
|
"Saving tx to disk to enable commit.", len(subnetAuthKeys), txName)
|
|
} else {
|
|
ux.Logger.PrintToUser("%d of %d required %s signatures have been signed. "+
|
|
"Saving tx to disk to enable remaining signing.", signedCount, len(subnetAuthKeys), txName)
|
|
}
|
|
if outputTxPath == "" {
|
|
ux.Logger.PrintToUser("")
|
|
var err error
|
|
if forceOverwrite {
|
|
outputTxPath, err = app.Prompt.CaptureString("Path to export partially signed tx to")
|
|
} else {
|
|
outputTxPath, err = app.Prompt.CaptureNewFilepath("Path to export partially signed tx to")
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if forceOverwrite {
|
|
ux.Logger.PrintToUser("")
|
|
ux.Logger.PrintToUser("Overwriting %s", outputTxPath)
|
|
}
|
|
if err := txutils.SaveToDisk(tx, outputTxPath, forceOverwrite); err != nil {
|
|
return err
|
|
}
|
|
if signedCount == len(subnetAuthKeys) {
|
|
PrintReadyToSignMsg(blockchainName, outputTxPath)
|
|
} else {
|
|
PrintRemainingToSignMsg(blockchainName, remainingSubnetAuthKeys, outputTxPath)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func PrintReadyToSignMsg(
|
|
blockchainName string,
|
|
outputTxPath string,
|
|
) {
|
|
ux.Logger.PrintToUser("")
|
|
ux.Logger.PrintToUser("Tx is fully signed, and ready to be committed")
|
|
ux.Logger.PrintToUser("")
|
|
ux.Logger.PrintToUser("Commit command:")
|
|
cmdLine := fmt.Sprintf(" lux transaction commit %s --input-tx-filepath %s", blockchainName, outputTxPath)
|
|
if blockchainName == "" {
|
|
cmdLine = fmt.Sprintf(" lux transaction commit --input-tx-filepath %s", outputTxPath)
|
|
}
|
|
ux.Logger.PrintToUser(cmdLine)
|
|
}
|
|
|
|
func PrintRemainingToSignMsg(
|
|
blockchainName string,
|
|
remainingSubnetAuthKeys []string,
|
|
outputTxPath string,
|
|
) {
|
|
ux.Logger.PrintToUser("")
|
|
ux.Logger.PrintToUser("Addresses remaining to sign the tx")
|
|
for _, subnetAuthKey := range remainingSubnetAuthKeys {
|
|
ux.Logger.PrintToUser(" %s", subnetAuthKey)
|
|
}
|
|
ux.Logger.PrintToUser("")
|
|
ux.Logger.PrintToUser("Connect a ledger with one of the remaining addresses or choose a stored key "+
|
|
"and run the signing command, or send %q to another user for signing.", outputTxPath)
|
|
ux.Logger.PrintToUser("")
|
|
ux.Logger.PrintToUser("Signing command:")
|
|
cmdline := fmt.Sprintf(" lux transaction sign %s --input-tx-filepath %s", blockchainName, outputTxPath)
|
|
if blockchainName == "" {
|
|
cmdline = fmt.Sprintf(" lux transaction sign --input-tx-filepath %s", outputTxPath)
|
|
}
|
|
ux.Logger.PrintToUser(cmdline)
|
|
ux.Logger.PrintToUser("")
|
|
}
|
|
|
|
func PrintDeployResults(blockchainName string, subnetID ids.ID, blockchainID ids.ID) error {
|
|
t := ux.DefaultTable("Deployment results", nil)
|
|
t.SetColumnConfigs([]table.ColumnConfig{
|
|
{Number: 2, AutoMerge: true},
|
|
})
|
|
if blockchainName != "" {
|
|
t.AppendRow(table.Row{"Chain Name", blockchainName})
|
|
}
|
|
t.AppendRow(table.Row{"Subnet ID", subnetID.String()})
|
|
if blockchainName != "" {
|
|
vmID, err := utils.VMID(blockchainName)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create VM ID from %s: %w", blockchainName, err)
|
|
}
|
|
t.AppendRow(table.Row{"VM ID", vmID.String()})
|
|
}
|
|
if blockchainID != ids.Empty {
|
|
t.AppendRow(table.Row{"Blockchain ID", blockchainID.String()})
|
|
t.AppendRow(table.Row{"P-Chain TXID", blockchainID.String()})
|
|
}
|
|
ux.Logger.PrintToUser(t.Render())
|
|
return nil
|
|
}
|
|
|
|
func LoadBootstrapValidator(bootstrapValidatorFlags flags.BootstrapValidatorFlags) ([]models.SubnetValidator, error) {
|
|
if !utils.FileExists(bootstrapValidatorFlags.BootstrapValidatorsJSONFilePath) {
|
|
return nil, fmt.Errorf("file path %q doesn't exist", bootstrapValidatorFlags.BootstrapValidatorsJSONFilePath)
|
|
}
|
|
jsonBytes, err := os.ReadFile(bootstrapValidatorFlags.BootstrapValidatorsJSONFilePath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var subnetValidators []models.SubnetValidator
|
|
if err = json.Unmarshal(jsonBytes, &subnetValidators); err != nil {
|
|
return nil, err
|
|
}
|
|
if err = validateSubnetValidatorsJSON(bootstrapValidatorFlags.GenerateNodeID, subnetValidators); err != nil {
|
|
return nil, err
|
|
}
|
|
return subnetValidators, nil
|
|
}
|
|
|
|
func ConvertURIToPeers(uris []string) ([]info.Peer, error) {
|
|
aggregatorPeers, err := blockchain.UrisToPeers(uris)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
nodeIDs := sdkutils.Map(aggregatorPeers, func(peer info.Peer) ids.NodeID {
|
|
return peer.Info.ID
|
|
})
|
|
nodeIDsSet := set.Of(nodeIDs...)
|
|
for _, uri := range uris {
|
|
infoClient := info.NewClient(uri)
|
|
ctx, cancel := utils.GetAPILargeContext()
|
|
defer cancel()
|
|
peers, err := infoClient.Peers(ctx, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for _, peer := range peers {
|
|
if !nodeIDsSet.Contains(peer.Info.ID) {
|
|
aggregatorPeers = append(aggregatorPeers, peer)
|
|
nodeIDsSet.Add(peer.Info.ID)
|
|
}
|
|
}
|
|
}
|
|
return aggregatorPeers, nil
|
|
}
|
|
|
|
func simulatedPublicNetwork() bool {
|
|
return os.Getenv(constants.SimulatePublicNetwork) != ""
|
|
}
|