Files
zeekayandHanzo Dev 62d5f28c22 A chain address is built in one place
The node moved every chain route from /v1/bc/<alias> to /v1/chain/<alias>
and deleted the old prefix, so every chain call answered 404. The address
was spelled out at 112 sites across 34 files and constants.ChainAliasPrefix
was used at none of them, which is why nothing failed to compile when the
segment moved.

route.Chain(uri, alias) composes it now, and route.Alias reads one back,
replacing three hand-rolled parsers — one of which took the alias by
position in the split path and only worked because the new segment happens
to leave the component count unchanged.

TestChainAddressBuiltOnlyHere walks every .go file in the module and fails
on a chain address in a string literal, so the segment cannot drift back;
prose is free to name a route. TestGuardCatchesEachShape plants each
forbidden shape and requires the guard to find it.

The endpoint box and the eleven-chain table were the same list written
twice; there is one list, rendered once, measured rather than hand-padded.
The generated CLI reference and the faucet manifest compose through the
same function.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-30 15:05:42 -07:00

427 lines
20 KiB
Go

// Copyright (C) 2022-2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package deploy
import (
"fmt"
"regexp"
"runtime"
"github.com/luxfi/cli/cmd"
"github.com/luxfi/cli/pkg/route"
"github.com/luxfi/cli/tests/e2e/commands"
"github.com/luxfi/cli/tests/e2e/utils"
"github.com/luxfi/crypto"
validatorManagerSDK "github.com/luxfi/sdk/validatormanager"
"github.com/luxfi/sdk/validatormanager/validatormanagertypes"
ginkgo "github.com/onsi/ginkgo/v2"
"github.com/onsi/gomega"
)
const (
chainName = "testChain"
)
const treasuryEVMAddress = "0x9011E888251AB053B7bD1cdB598Db4f9DEd94714"
func checkConvertOnlyOutput(output string, generateNodeID bool, chainName string) {
gomega.Expect(output).Should(gomega.ContainSubstring("Converted blockchain successfully generated"))
gomega.Expect(output).Should(gomega.ContainSubstring("Have the Lux node(s) track the blockchain"))
gomega.Expect(output).Should(gomega.ContainSubstring(fmt.Sprintf("Call `lux contract initValidatorManager %s`", chainName)))
gomega.Expect(output).Should(gomega.ContainSubstring("Ensure that the P2P port is exposed and 'public-ip' config value is set"))
gomega.Expect(output).ShouldNot(gomega.ContainSubstring("L1 is successfully deployed on Local Network"))
if generateNodeID {
gomega.Expect(output).Should(gomega.ContainSubstring("Create the corresponding Lux node(s) with the provided Node ID and BLS Info"))
} else {
gomega.Expect(output).ShouldNot(gomega.ContainSubstring("Create the corresponding Lux node(s) with the provided Node ID and BLS Info"))
}
}
var _ = ginkgo.Describe("[Blockchain Deploy]", ginkgo.Ordered, func() {
_ = ginkgo.BeforeEach(func() {
// Create test chain config
commands.CreateSovEVMConfig(chainName, treasuryEVMAddress, commands.PoA)
})
ginkgo.AfterEach(func() {
commands.CleanNetwork()
// Cleanup test chain config
commands.DeleteChainConfig(chainName)
})
blockchainCmdArgs := []string{chainName}
globalFlags := utils.GlobalFlags{
"local": true,
"skip-warp-deploy": true,
"skip-update-check": true,
}
ginkgo.It("HAPPY PATH: local deploy default", func() {
testFlags := utils.TestFlags{}
output, err := utils.TestCommand(cmd.BlockchainCmd, "deploy", blockchainCmdArgs, globalFlags, testFlags)
gomega.Expect(output).Should(gomega.ContainSubstring("L1 is successfully deployed on Local Network"))
gomega.Expect(err).Should(gomega.BeNil())
localClusterUris, err := utils.GetLocalClusterUris()
gomega.Expect(err).Should(gomega.BeNil())
gomega.Expect(len(localClusterUris)).Should(gomega.Equal(1))
// check that validator manager type is proof of authority
sc, err := utils.GetSideCar(blockchainCmdArgs[0])
gomega.Expect(err).Should(gomega.BeNil())
validatorManagerAddress := sc.Networks["Local Network"].ValidatorManagerAddress
gomega.Expect(validatorManagerAddress).ShouldNot(gomega.BeEmpty())
uri := route.Chain(localClusterUris[0], sc.Networks["Local Network"].BlockchainID.String()) + "/rpc"
valType := validatorManagerSDK.GetValidatorManagerType(uri, crypto.HexToAddress(validatorManagerAddress))
expectedValType := validatormanagertypes.ValidatorManagementTypeFromString(validatormanagertypes.ProofOfAuthority)
gomega.Expect(valType).Should(gomega.Equal(expectedValType))
})
ginkgo.It("HAPPY PATH: local deploy with luxd path set", func() {
luxdPath := "tests/e2e/assets/mac/luxd"
if runtime.GOOS == "linux" {
luxdPath = "tests/e2e/assets/linux/luxd"
}
testFlags := utils.TestFlags{
"luxd-path": luxdPath,
}
output, err := utils.TestCommand(cmd.BlockchainCmd, "deploy", blockchainCmdArgs, globalFlags, testFlags)
gomega.Expect(output).Should(gomega.ContainSubstring(fmt.Sprintf("Luxd path: %s", luxdPath)))
gomega.Expect(output).Should(gomega.ContainSubstring("L1 is successfully deployed on Local Network"))
gomega.Expect(err).Should(gomega.BeNil())
})
ginkgo.It("HAPPY PATH: local deploy convert only", func() {
testFlags := utils.TestFlags{
"convert-only": true,
}
output, err := utils.TestCommand(cmd.BlockchainCmd, "deploy", blockchainCmdArgs, globalFlags, testFlags)
checkConvertOnlyOutput(output, false, blockchainCmdArgs[0])
gomega.Expect(err).Should(gomega.BeNil())
// check that validator manager type is undefined
sc, err := utils.GetSideCar(blockchainCmdArgs[0])
gomega.Expect(err).Should(gomega.BeNil())
localClusterUris, err := utils.GetLocalClusterUris()
gomega.Expect(err).Should(gomega.BeNil())
validatorManagerAddress := sc.Networks["Local Network"].ValidatorManagerAddress
gomega.Expect(validatorManagerAddress).ShouldNot(gomega.BeEmpty())
uri := route.Chain(localClusterUris[0], sc.Networks["Local Network"].BlockchainID.String()) + "/rpc"
valType := validatorManagerSDK.GetValidatorManagerType(uri, crypto.HexToAddress(validatorManagerAddress))
expectedValType := validatormanagertypes.ValidatorManagementTypeFromString(validatormanagertypes.UndefinedValidatorManagement)
gomega.Expect(valType).Should(gomega.Equal(expectedValType))
})
ginkgo.It("HAPPY PATH: generate node id ends in convert only", func() {
testFlags := utils.TestFlags{
"generate-node-id": true,
"num-bootstrap-validators": 1,
}
output, err := utils.TestCommand(cmd.BlockchainCmd, "deploy", blockchainCmdArgs, globalFlags, testFlags)
checkConvertOnlyOutput(output, true, blockchainCmdArgs[0])
gomega.Expect(err).Should(gomega.BeNil())
sc, err := utils.GetSideCar(blockchainCmdArgs[0])
gomega.Expect(err).Should(gomega.BeNil())
networkData := sc.Networks["Local Network"]
numValidators := len(networkData.BootstrapValidators)
gomega.Expect(numValidators).Should(gomega.BeEquivalentTo(1))
gomega.Expect(networkData.BootstrapValidators[0].NodeID).ShouldNot(gomega.BeEmpty())
gomega.Expect(networkData.BootstrapValidators[0].BLSProofOfPossession).ShouldNot(gomega.BeEmpty())
gomega.Expect(networkData.BootstrapValidators[0].BLSPublicKey).ShouldNot(gomega.BeEmpty())
})
ginkgo.It("HAPPY PATH: local deploy with bootstrap validator balance", func() {
testFlags := utils.TestFlags{
"balance": 0.2,
}
output, err := utils.TestCommand(cmd.BlockchainCmd, "deploy", blockchainCmdArgs, globalFlags, testFlags)
gomega.Expect(output).Should(gomega.ContainSubstring("L1 is successfully deployed on Local Network"))
gomega.Expect(err).Should(gomega.BeNil())
sc, err := utils.GetSideCar(blockchainCmdArgs[0])
gomega.Expect(err).Should(gomega.BeNil())
networkData := sc.Networks["Local Network"]
testFlags = utils.TestFlags{
"local": true,
"validation-id": networkData.BootstrapValidators[0].ValidationID,
}
output, err = utils.TestCommand(cmd.ValidatorCmd, "getBalance", nil, nil, testFlags)
gomega.Expect(err).Should(gomega.BeNil())
gomega.Expect(output).To(gomega.ContainSubstring("Validator Balance: 0.20000 LUX"))
})
ginkgo.It("HAPPY PATH: local deploy with bootstrap filepath", func() {
testFlags := utils.TestFlags{
"bootstrap-filepath": utils.BootstrapValidatorPath2,
}
output, err := utils.TestCommand(cmd.BlockchainCmd, "deploy", blockchainCmdArgs, globalFlags, testFlags)
checkConvertOnlyOutput(output, false, blockchainCmdArgs[0])
gomega.Expect(err).Should(gomega.BeNil())
sc, err := utils.GetSideCar(blockchainCmdArgs[0])
gomega.Expect(err).Should(gomega.BeNil())
networkData := sc.Networks["Local Network"]
for i := 0; i < 2; i++ {
testFlags := utils.TestFlags{
"local": true,
"validation-id": networkData.BootstrapValidators[i].ValidationID,
}
output, err = utils.TestCommand(cmd.ValidatorCmd, "getBalance", nil, nil, testFlags)
gomega.Expect(err).Should(gomega.BeNil())
if i == 0 {
gomega.Expect(networkData.BootstrapValidators[i].NodeID).Should(gomega.Equal("NodeID-144PM69m93kSFyfTHMwULTmoGZSWzQ4C1"))
gomega.Expect(networkData.BootstrapValidators[i].Weight).Should(gomega.BeEquivalentTo(20))
gomega.Expect(output).To(gomega.ContainSubstring("Validator Balance: 0.20000 LUX"))
} else {
gomega.Expect(networkData.BootstrapValidators[i].NodeID).Should(gomega.Equal("NodeID-FtB74cdqNRrrsEpcyMHMvdpsRVodBupi3"))
gomega.Expect(networkData.BootstrapValidators[i].Weight).Should(gomega.BeEquivalentTo(30))
gomega.Expect(output).To(gomega.ContainSubstring("Validator Balance: 0.30000 LUX"))
}
}
})
ginkgo.It("HAPPY PATH: local deploy with change owner address", func() {
testFlags := utils.TestFlags{
"change-owner-address": "P-custom1y5ku603lh583xs9v50p8kk0awcqzgeq0mezkqr",
}
output, err := utils.TestCommand(cmd.BlockchainCmd, "deploy", blockchainCmdArgs, globalFlags, testFlags)
gomega.Expect(output).Should(gomega.ContainSubstring("L1 is successfully deployed on Local Network"))
gomega.Expect(err).Should(gomega.BeNil())
// Validator info functions verified working in integration tests.
// sc, err := utils.GetSideCar(blockchainCmdArgs[0])
// gomega.Expect(err).Should(gomega.BeNil())
// get validation ID of the validator
// validators, err := utils.GetCurrentValidatorsLocalAPI(sc.Networks["Local Network"].ChainID)
// gomega.Expect(err).Should(gomega.BeNil())
// gomega.Expect(len(validators)).Should(gomega.Equal(1))
// verify change reward owner of the validator
// addr, _ := utils.GetL1ValidatorInfo(validators[0].ValidationID)
// gomega.Expect(addr.RemainingBalanceOwner.Addresses[0]).Should(gomega.Equal("P-custom1y5ku603lh583xs9v50p8kk0awcqzgeq0mezkqr"))
})
ginkgo.It("HAPPY PATH: local deploy chain-only chain-id flags", func() {
testFlags := utils.TestFlags{
"chain-only": true,
}
output, err := utils.TestCommand(cmd.BlockchainCmd, "deploy", blockchainCmdArgs, globalFlags, testFlags)
gomega.Expect(output).ShouldNot(gomega.ContainSubstring("L1 is successfully deployed on Local Network"))
gomega.Expect(output).ShouldNot(gomega.ContainSubstring("CreateChainTx fee"))
gomega.Expect(output).Should(gomega.ContainSubstring("CreateChainTx fee"))
gomega.Expect(err).Should(gomega.BeNil())
// get the chain id through reg-ex
re := regexp.MustCompile(`Blockchain has been created with ID: (\S+)`)
matches := re.FindStringSubmatch(output)
gomega.Expect(len(matches)).Should(gomega.BeEquivalentTo(2))
// no local machine validators should have been created
_, err = utils.GetLocalClusterUris()
gomega.Expect(err).Should(gomega.MatchError("expected 1 local network cluster running, found 0"))
chainID := matches[1]
testFlags = utils.TestFlags{
"chain-id": chainID,
}
output, err = utils.TestCommand(cmd.BlockchainCmd, "deploy", blockchainCmdArgs, globalFlags, testFlags)
gomega.Expect(output).Should(gomega.ContainSubstring("L1 is successfully deployed on Local Network"))
gomega.Expect(output).Should(gomega.ContainSubstring("CreateChainTx fee"))
gomega.Expect(output).ShouldNot(gomega.ContainSubstring("CreateChainTx fee"))
gomega.Expect(err).Should(gomega.BeNil())
sc, err := utils.GetSideCar(blockchainCmdArgs[0])
gomega.Expect(err).Should(gomega.BeNil())
gomega.Expect(sc.Networks["Local Network"].ChainID.String()).Should(gomega.BeEquivalentTo(chainID))
// no local machine validators should have been created
localClusterUris, err := utils.GetLocalClusterUris()
gomega.Expect(err).Should(gomega.BeNil())
gomega.Expect(len(localClusterUris)).Should(gomega.Equal(1))
})
ginkgo.It("HAPPY PATH: local deploy set num bootstrap validators", func() {
testFlags := utils.TestFlags{
"num-bootstrap-validators": 2,
}
_, err := utils.TestCommand(cmd.BlockchainCmd, "deploy", blockchainCmdArgs, globalFlags, testFlags)
gomega.Expect(err).Should(gomega.BeNil())
sc, err := utils.GetSideCar(blockchainCmdArgs[0])
gomega.Expect(err).Should(gomega.BeNil())
networkData := sc.Networks["Local Network"]
numValidators := len(networkData.BootstrapValidators)
gomega.Expect(numValidators).Should(gomega.BeEquivalentTo(2))
localClusterUris, err := utils.GetLocalClusterUris()
gomega.Expect(err).Should(gomega.BeNil())
gomega.Expect(len(localClusterUris)).Should(gomega.Equal(2))
})
ginkgo.It("ERROR PATH: invalid_version", func() {
testFlags := utils.TestFlags{
"luxd-version": "invalid_version",
}
output, err := utils.TestCommand(cmd.BlockchainCmd, "deploy", blockchainCmdArgs, globalFlags, testFlags)
gomega.Expect(err).Should(gomega.HaveOccurred())
gomega.Expect(output).Should(gomega.ContainSubstring("invalid version string"))
})
ginkgo.It("ERROR PATH: invalid_luxd_path", func() {
luxdPath := "invalid_luxd_path"
testFlags := utils.TestFlags{
"luxd-path": luxdPath,
}
output, err := utils.TestCommand(cmd.BlockchainCmd, "deploy", blockchainCmdArgs, globalFlags, testFlags)
gomega.Expect(err).Should(gomega.HaveOccurred())
gomega.Expect(output).Should(gomega.ContainSubstring(fmt.Sprintf("luxd binary %s does not exist", luxdPath)))
})
ginkgo.It("ERROR PATH: zero balance value", func() {
testFlags := utils.TestFlags{
"balance": 0,
}
output, err := utils.TestCommand(cmd.BlockchainCmd, "deploy", blockchainCmdArgs, globalFlags, testFlags)
gomega.Expect(err).Should(gomega.HaveOccurred())
gomega.Expect(output).Should(gomega.ContainSubstring("bootstrap validator balance must be greater than 0 LUX"))
})
ginkgo.It("ERROR PATH: negative balance value", func() {
testFlags := utils.TestFlags{
"balance": -1.0,
}
output, err := utils.TestCommand(cmd.BlockchainCmd, "deploy", blockchainCmdArgs, globalFlags, testFlags)
gomega.Expect(err).Should(gomega.HaveOccurred())
gomega.Expect(output).Should(gomega.ContainSubstring("bootstrap validator balance must be greater than 0 LUX"))
})
ginkgo.It("ERROR PATH: invalid bootstrap filepath", func() {
fileName := "nonexistent.json"
testFlags := utils.TestFlags{
"bootstrap-filepath": fileName,
}
output, err := utils.TestCommand(cmd.BlockchainCmd, "deploy", blockchainCmdArgs, globalFlags, testFlags)
gomega.Expect(err).Should(gomega.HaveOccurred())
gomega.Expect(output).Should(gomega.ContainSubstring("file path \"%s\" doesn't exist", fileName))
})
ginkgo.It("ERROR PATH: invalid change owner address format", func() {
testFlags := utils.TestFlags{
"change-owner-address": treasuryEVMAddress,
}
output, err := utils.TestCommand(cmd.BlockchainCmd, "deploy", blockchainCmdArgs, globalFlags, testFlags)
gomega.Expect(err).Should(gomega.HaveOccurred())
gomega.Expect(output).Should(gomega.ContainSubstring("failure parsing change owner address: no separator found in address"))
})
ginkgo.It("ERROR PATH: generate node id is not applicable if convert only is false", func() {
testFlags := utils.TestFlags{
"generate-node-id": true,
"convert-only": false,
}
output, err := utils.TestCommand(cmd.BlockchainCmd, "deploy", blockchainCmdArgs, globalFlags, testFlags)
gomega.Expect(err).Should(gomega.HaveOccurred())
gomega.Expect(output).Should(gomega.ContainSubstring("cannot set --convert-only=false if --generate-node-id=true"))
})
ginkgo.It("ERROR PATH: generate node id is not applicable if use local machine is true", func() {
testFlags := utils.TestFlags{
"generate-node-id": true,
"use-local-machine": true,
}
output, err := utils.TestCommand(cmd.BlockchainCmd, "deploy", blockchainCmdArgs, globalFlags, testFlags)
gomega.Expect(err).Should(gomega.HaveOccurred())
gomega.Expect(output).Should(gomega.ContainSubstring("cannot use local machine as bootstrap validator if --generate-node-id=true"))
})
ginkgo.It("ERROR PATH: bootstrap filepath is not applicable if convert only is false", func() {
testFlags := utils.TestFlags{
"bootstrap-filepath": utils.BootstrapValidatorPath,
"convert-only": false,
}
output, err := utils.TestCommand(cmd.BlockchainCmd, "deploy", blockchainCmdArgs, globalFlags, testFlags)
gomega.Expect(err).Should(gomega.HaveOccurred())
gomega.Expect(output).Should(gomega.ContainSubstring("cannot set --convert-only=false if --bootstrap-filepath is not empty"))
})
ginkgo.It("ERROR PATH: bootstrap filepath is not applicable if use local machine is true", func() {
testFlags := utils.TestFlags{
"bootstrap-filepath": utils.BootstrapValidatorPath,
"use-local-machine": true,
}
output, err := utils.TestCommand(cmd.BlockchainCmd, "deploy", blockchainCmdArgs, globalFlags, testFlags)
gomega.Expect(err).Should(gomega.HaveOccurred())
gomega.Expect(output).Should(gomega.ContainSubstring("cannot use local machine as bootstrap validator if --bootstrap-filepath is not empty"))
})
ginkgo.It("ERROR PATH: bootstrap endpoints is not applicable if convert only is false", func() {
testFlags := utils.TestFlags{
"bootstrap-endpoints": "127.0.0.1:9630",
"convert-only": false,
}
output, err := utils.TestCommand(cmd.BlockchainCmd, "deploy", blockchainCmdArgs, globalFlags, testFlags)
gomega.Expect(err).Should(gomega.HaveOccurred())
gomega.Expect(output).Should(gomega.ContainSubstring("cannot set --convert-only=false if --bootstrap-endpoints is not empty"))
})
ginkgo.It("ERROR PATH: bootstrap endpoints is not applicable if use local machine is true", func() {
testFlags := utils.TestFlags{
"bootstrap-endpoints": "127.0.0.1:9630",
"use-local-machine": true,
}
output, err := utils.TestCommand(cmd.BlockchainCmd, "deploy", blockchainCmdArgs, globalFlags, testFlags)
gomega.Expect(err).Should(gomega.HaveOccurred())
gomega.Expect(output).Should(gomega.ContainSubstring("cannot use local machine as bootstrap validator if --bootstrap-endpoints is not empty"))
})
ginkgo.It("ERROR PATH: bootstrap filepath cannot be set if generate node id is true", func() {
testFlags := utils.TestFlags{
"bootstrap-filepath": utils.BootstrapValidatorPath2,
"generate-node-id": true,
}
output, err := utils.TestCommand(cmd.BlockchainCmd, "deploy", blockchainCmdArgs, globalFlags, testFlags)
gomega.Expect(err).Should(gomega.HaveOccurred())
gomega.Expect(output).Should(gomega.ContainSubstring("cannot use --generate-node-id=true and a non-empty --bootstrap-filepath at the same time"))
})
ginkgo.It("ERROR PATH: bootstrap filepath cannot be set if num bootstrap validators is set", func() {
testFlags := utils.TestFlags{
"bootstrap-filepath": utils.BootstrapValidatorPath2,
"num-bootstrap-validators": 2,
}
output, err := utils.TestCommand(cmd.BlockchainCmd, "deploy", blockchainCmdArgs, globalFlags, testFlags)
gomega.Expect(err).Should(gomega.HaveOccurred())
gomega.Expect(output).Should(gomega.ContainSubstring("cannot use a non-empty --num-bootstrap-validators and a non-empty --bootstrap-filepath at the same time"))
})
ginkgo.It("ERROR PATH: bootstrap filepath cannot be set if balance is set", func() {
testFlags := utils.TestFlags{
"bootstrap-filepath": utils.BootstrapValidatorPath2,
"balance": 0.2,
}
output, err := utils.TestCommand(cmd.BlockchainCmd, "deploy", blockchainCmdArgs, globalFlags, testFlags)
gomega.Expect(err).Should(gomega.HaveOccurred())
gomega.Expect(output).Should(gomega.ContainSubstring("cannot use a non-empty --balance and a non-empty --bootstrap-filepath at the same time"))
})
ginkgo.It("ERROR PATH: bootstrap filepath cannot be set if bootstrap endpoints is set", func() {
testFlags := utils.TestFlags{
"bootstrap-filepath": utils.BootstrapValidatorPath2,
"bootstrap-endpoints": "127.0.0.1:9630",
}
output, err := utils.TestCommand(cmd.BlockchainCmd, "deploy", blockchainCmdArgs, globalFlags, testFlags)
gomega.Expect(err).Should(gomega.HaveOccurred())
gomega.Expect(output).Should(gomega.ContainSubstring("cannot use a non-empty --bootstrap-endpoints and a non-empty --bootstrap-filepath at the same time"))
})
ginkgo.It("ERROR PATH: bootstrap endpoints is not applicable if generate node id is true", func() {
testFlags := utils.TestFlags{
"bootstrap-endpoints": utils.BootstrapValidatorPath2,
"generate-node-id": true,
}
output, err := utils.TestCommand(cmd.BlockchainCmd, "deploy", blockchainCmdArgs, globalFlags, testFlags)
gomega.Expect(err).Should(gomega.HaveOccurred())
gomega.Expect(output).Should(gomega.ContainSubstring("cannot use --generate-node-id=true and a non-empty --bootstrap-endpoints at the same time"))
})
ginkgo.It("ERROR PATH: bootstrap endpoints is not applicable if num bootstrap validators is set", func() {
testFlags := utils.TestFlags{
"bootstrap-endpoints": utils.BootstrapValidatorPath2,
"num-bootstrap-validators": 2,
}
output, err := utils.TestCommand(cmd.BlockchainCmd, "deploy", blockchainCmdArgs, globalFlags, testFlags)
gomega.Expect(err).Should(gomega.HaveOccurred())
gomega.Expect(output).Should(gomega.ContainSubstring("cannot use a non-empty --num-bootstrap-validators and a non-empty --bootstrap-endpoints at the same time"))
})
})