From 3428cf10d5e0506d4d125376da56c62ebc2493a0 Mon Sep 17 00:00:00 2001 From: Rafael Tenfen Date: Fri, 26 Jul 2024 14:21:51 -0300 Subject: [PATCH] feat: e2e upgrade software (#727) * chore: try to clean up all and only then return the error * chore: add errgroup to clear resources and new mount path to set proposasl * chore: add upgrade e2e from current branch * feat: add query proposals to container CLI * feat: add tx gov submit prop in container CLI * feat: add query for tx debug * fix: set min deposit to ubbn token * fix: set gov prop time in genstate gov * chore: add vote to gov prop * test: upgrade fails due to BINARY UPDATED BEFORE TRIGGER * chore: mod tidy * feat: add directives to build babylon from specific version and tag as before upgrade * chore: add comment for TODO rmv upgrade for mainnet * chore: add clean docker directive * chore: add fetch for commit hash in babylond docker file versioned * fix: deterministic upgrade * feat: add upgrade and initialization needed for software upgrade prop * chore: removed unecessary secret key at node level * chore: add checker for vanilla upgrade * chore: add status command * chore: wait to purge resources than remove network * feat: add bank multisend * fix: use multisend to avoid sequence error * fix: usage and command bank multisend * fix: fp babylon address * fix: no error required * fix: add typed error that reward gauge was not found for that addr * chore: update error not exactly the same * chore: set linter timeout to 10min * chore: cleanup * fix: used parameter of voting period to update gov genesis * chore: rename from NewSoftwareUpgradeTest to NewSoftwareUpgradeConfigurer * chore: add check for applied plan by verifying the height in which it was executed * chore: add upgrade path as parameter in configurer * fix: set type of expected height to match equal value * chore: address PR comments --- Makefile | 23 +- app/app.go | 8 +- app/upgrades/vanilla/upgrades.go | 43 ++- contrib/images/Makefile | 15 +- contrib/images/babylond/Dockerfile | 5 +- .../images/e2e-initialization/init.Dockerfile | 46 +++ go.mod | 2 +- test/e2e/btc_staking_e2e_test.go | 17 +- test/e2e/configurer/base.go | 17 +- test/e2e/configurer/chain/chain.go | 11 +- test/e2e/configurer/chain/commands.go | 59 ++++ test/e2e/configurer/chain/node.go | 17 ++ test/e2e/configurer/chain/queries.go | 53 ++++ test/e2e/configurer/config/constants.go | 10 + test/e2e/configurer/factory.go | 29 +- test/e2e/configurer/setup.go | 28 +- test/e2e/configurer/upgrade.go | 269 ++++++++++++++++++ test/e2e/containers/config.go | 39 ++- test/e2e/containers/containers.go | 58 +++- test/e2e/e2e_test.go | 5 + test/e2e/initialization/config.go | 23 +- test/e2e/initialization/export.go | 3 - test/e2e/initialization/node.go | 1 - test/e2e/software_upgrade_e2e_test.go | 50 ++++ test/e2e/upgrades/vanilla.json | 14 + 25 files changed, 775 insertions(+), 70 deletions(-) create mode 100644 contrib/images/e2e-initialization/init.Dockerfile create mode 100644 test/e2e/configurer/upgrade.go create mode 100644 test/e2e/software_upgrade_e2e_test.go create mode 100644 test/e2e/upgrades/vanilla.json diff --git a/Makefile b/Makefile index f70e4c026..2c193185c 100644 --- a/Makefile +++ b/Makefile @@ -204,6 +204,17 @@ build-docs: diagrams ## Builds a docs site done < versions ; .PHONY: build-docs +############################################################################### +### E2E build ### +############################################################################### + +# Executed to build the binary for chain initialization, one of +## chain => test/e2e/initialization/chain/main.go +## node => test/e2e/initialization/node/main.go +e2e-build-script: + mkdir -p $(BUILDDIR) + go build -mod=readonly $(BUILD_FLAGS) -o $(BUILDDIR)/ ./test/e2e/initialization/$(E2E_SCRIPT_NAME) + ############################################################################### ### Tests & Simulation ### ############################################################################### @@ -243,7 +254,7 @@ endif .PHONY: run-tests test test-all $(TEST_TARGETS) -test-e2e: build-docker +test-e2e: build-docker-e2e go test -mod=readonly -timeout=25m -v $(PACKAGES_E2E) -count=1 --tags=e2e test-sim-nondeterminism: @@ -411,14 +422,22 @@ proto-lint: ## Lint protobuf files ############################################################################### ### Docker ### ############################################################################### +dockerNetworkList=$($(DOCKER) network ls --filter name=bbn-testnet --format {{.ID}}) build-docker: ## Build babylond Docker image $(MAKE) -C contrib/images babylond +build-docker-e2e: build-docker + $(MAKE) -C contrib/images babylond-before-upgrade + $(MAKE) -C contrib/images e2e-init-chain + build-cosmos-relayer-docker: ## Build Docker image for the Cosmos relayer $(MAKE) -C contrib/images cosmos-relayer -.PHONY: build-docker build-cosmos-relayer-docker +clean-docker-network: + $(DOCKER) network rm ${dockerNetworkList} + +.PHONY: build-docker build-docker-e2e build-cosmos-relayer-docker clean-docker-network ############################################################################### ### Localnet ### diff --git a/app/app.go b/app/app.go index 67b0b7800..1cc9347ca 100644 --- a/app/app.go +++ b/app/app.go @@ -94,6 +94,7 @@ import ( "github.com/spf13/cast" "github.com/babylonchain/babylon/app/upgrades" + "github.com/babylonchain/babylon/app/upgrades/vanilla" bbn "github.com/babylonchain/babylon/types" appkeepers "github.com/babylonchain/babylon/app/keepers" @@ -158,8 +159,11 @@ var ( } // software upgrades and forks - Upgrades = []upgrades.Upgrade{} - Forks = []upgrades.Fork{} + // TODO: REMOVE UPGRADE BEFORE MAINNET, used for e2e testing + Upgrades = []upgrades.Upgrade{ + vanilla.Upgrade, + } + Forks = []upgrades.Fork{} ) func init() { diff --git a/app/upgrades/vanilla/upgrades.go b/app/upgrades/vanilla/upgrades.go index 1cc0c9060..c46b94b63 100644 --- a/app/upgrades/vanilla/upgrades.go +++ b/app/upgrades/vanilla/upgrades.go @@ -32,7 +32,6 @@ func CreateUpgradeHandler( keepers *keepers.AppKeepers, ) upgradetypes.UpgradeHandler { return func(context context.Context, _plan upgradetypes.Plan, fromVM module.VersionMap) (module.VersionMap, error) { - ctx := sdk.UnwrapSDKContext(context) propVanilla(ctx, &keepers.AccountKeeper, &keepers.BTCStakingKeeper) @@ -46,18 +45,48 @@ func propVanilla( accountKeeper *authkeeper.AccountKeeper, bsKeeper *btcstakingkeeper.Keeper, ) { - // remove an account + // remove the account with higher number and the lowest is the new fp addr allAccounts := accountKeeper.GetAllAccounts(ctx) - accountKeeper.RemoveAccount(ctx, allAccounts[len(allAccounts)-1]) - // insert a FP - sk, err := btcec.NewPrivateKey() + var ( + accToRemove sdk.AccountI + accFp sdk.AccountI + ) + heighestAccNumber, lowestAccNumber := uint64(0), uint64(len(allAccounts)) + + for _, acc := range allAccounts { + accNumber := acc.GetAccountNumber() + if accNumber > heighestAccNumber { + heighestAccNumber = accNumber + accToRemove = acc + } + if accNumber < lowestAccNumber { + lowestAccNumber = accNumber + accFp = acc + } + } + + accountKeeper.RemoveAccount(ctx, accToRemove) + + // insert a FP from predefined public key + pk, err := btcec.ParsePubKey( + []byte{0x06, 0x79, 0xbe, 0x66, 0x7e, 0xf9, 0xdc, 0xbb, + 0xac, 0x55, 0xa0, 0x62, 0x95, 0xce, 0x87, 0x0b, 0x07, + 0x02, 0x9b, 0xfc, 0xdb, 0x2d, 0xce, 0x28, 0xd9, 0x59, + 0xf2, 0x81, 0x5b, 0x16, 0xf8, 0x17, 0x98, 0x48, 0x3a, + 0xda, 0x77, 0x26, 0xa3, 0xc4, 0x65, 0x5d, 0xa4, 0xfb, + 0xfc, 0x0e, 0x11, 0x08, 0xa8, 0xfd, 0x17, 0xb4, 0x48, + 0xa6, 0x85, 0x54, 0x19, 0x9c, 0x47, 0xd0, 0x8f, 0xfb, + 0x10, 0xd4, 0xb8, + }, + ) if err != nil { panic(err) } - btcPK := bbn.NewBIP340PubKeyFromBTCPK(sk.PubKey()) + + btcPK := bbn.NewBIP340PubKeyFromBTCPK(pk) fp := &bstypes.FinalityProvider{ - Addr: allAccounts[0].GetAddress().String(), + Addr: accFp.GetAddress().String(), BtcPk: btcPK, } bsKeeper.SetFinalityProvider(ctx, fp) diff --git a/contrib/images/Makefile b/contrib/images/Makefile index 3b9d0818e..b4aaf7467 100644 --- a/contrib/images/Makefile +++ b/contrib/images/Makefile @@ -1,17 +1,26 @@ RELAYER_TAG := $(shell grep '^ENV RELAYER_TAG' cosmos-relayer/Dockerfile | cut -f3 -d\ ) +BABYLON_FULL_PATH := $(shell git rev-parse --show-toplevel) +BABYLON_VERSION_BEFORE_UPGRADE ?= a33a3344bb44bde2f3374d3cbf919abb942c341a all: babylond cosmos-relayer babylond: babylond-rmi - docker build --tag babylonchain/babylond -f babylond/Dockerfile \ - $(shell git rev-parse --show-toplevel) + docker build --tag babylonchain/babylond -f babylond/Dockerfile ${BABYLON_FULL_PATH} + +babylond-before-upgrade: + docker rmi babylonchain/babylond-before-upgrade 2>/dev/null; true && \ + docker build --tag babylonchain/babylond-before-upgrade -f babylond/Dockerfile \ + --build-arg VERSION="${BABYLON_VERSION_BEFORE_UPGRADE}" ${BABYLON_FULL_PATH} babylond-rmi: docker rmi babylonchain/babylond 2>/dev/null; true +e2e-init-chain: + @DOCKER_BUILDKIT=1 docker build -t babylonchain/babylond-e2e-init-chain --build-arg E2E_SCRIPT_NAME=chain --platform=linux/x86_64 -f e2e-initialization/init.Dockerfile ${BABYLON_FULL_PATH} + cosmos-relayer: cosmos-relayer-rmi docker build --tag babylonchain/cosmos-relayer:${RELAYER_TAG} -f cosmos-relayer/Dockerfile \ - $(shell git rev-parse --show-toplevel)/contrib/images/cosmos-relayer + ${BABYLON_FULL_PATH}/contrib/images/cosmos-relayer docker tag babylonchain/cosmos-relayer:${RELAYER_TAG} babylonchain/cosmos-relayer:latest cosmos-relayer-rmi: diff --git a/contrib/images/babylond/Dockerfile b/contrib/images/babylond/Dockerfile index 2e8add214..6759346c8 100644 --- a/contrib/images/babylond/Dockerfile +++ b/contrib/images/babylond/Dockerfile @@ -1,9 +1,5 @@ FROM golang:1.21 AS build-env -# Customize to your build env - -# TARGETPLATFORM should be one of linux/amd64 or linux/arm64. -ARG TARGETPLATFORM="linux/amd64" # Version to build. Default is empty ARG VERSION @@ -23,6 +19,7 @@ RUN go mod download COPY ./ /go/src/github.com/babylonchain/babylon/ # If version is set, then checkout this version RUN if [ -n "${VERSION}" ]; then \ + git fetch origin ${VERSION}; \ git checkout -f ${VERSION}; \ fi diff --git a/contrib/images/e2e-initialization/init.Dockerfile b/contrib/images/e2e-initialization/init.Dockerfile new file mode 100644 index 000000000..0b3095d90 --- /dev/null +++ b/contrib/images/e2e-initialization/init.Dockerfile @@ -0,0 +1,46 @@ +FROM golang:1.21 as build-env + +ARG E2E_SCRIPT_NAME + +# TARGETPLATFORM should be one of linux/amd64 or linux/arm64. +ARG TARGETPLATFORM="linux/amd64" + +# Install cli tools for building and final image +RUN apt-get update && apt-get install -y make git bash gcc curl jq + +WORKDIR /go/src/github.com/babylonchain/babylon + +# First cache dependencies +COPY go.mod go.sum /go/src/github.com/babylonchain/babylon/ +RUN go mod download + +# Copy everything else +COPY ./ /go/src/github.com/babylonchain/babylon/ + +RUN LEDGER_ENABLED=false LINK_STATICALLY=false E2E_SCRIPT_NAME=${E2E_SCRIPT_NAME} make e2e-build-script + +FROM debian:bookworm-slim AS run + +# Create a user +RUN addgroup --gid 1137 --system babylon && adduser --uid 1137 --gid 1137 --system --home /home/babylon babylon +RUN apt-get update && apt-get install -y bash curl jq wget + +COPY --from=build-env /go/src/github.com/babylonchain/babylon/go.mod /tmp +RUN WASMVM_VERSION=$(grep github.com/CosmWasm/wasmvm /tmp/go.mod | cut -d' ' -f2) && \ + wget https://github.com/CosmWasm/wasmvm/releases/download/$WASMVM_VERSION/libwasmvm.$(uname -m).so \ + -O /lib/libwasmvm.$(uname -m).so && \ + # verify checksum + wget https://github.com/CosmWasm/wasmvm/releases/download/$WASMVM_VERSION/checksums.txt -O /tmp/checksums.txt && \ + sha256sum /lib/libwasmvm.$(uname -m).so | grep $(cat /tmp/checksums.txt | grep libwasmvm.$(uname -m) | cut -d ' ' -f 1) + +# Args only last for a single build stage - renew +ARG E2E_SCRIPT_NAME + +COPY --from=build-env /go/src/github.com/babylonchain/babylon/build/${E2E_SCRIPT_NAME} /bin/${E2E_SCRIPT_NAME} + +# Docker ARGs are not expanded in ENTRYPOINT in the exec mode. At the same time, +# it is impossible to add CMD arguments when running a container in the shell mode. +# As a workaround, we create the entrypoint.sh script to bypass these issues. +RUN echo "#!/bin/bash\n${E2E_SCRIPT_NAME} \"\$@\"" >> entrypoint.sh && chmod +x entrypoint.sh + +ENTRYPOINT ["./entrypoint.sh"] diff --git a/go.mod b/go.mod index 98badadb7..54416cadb 100644 --- a/go.mod +++ b/go.mod @@ -64,6 +64,7 @@ require ( github.com/vulpine-io/io-test v1.0.0 go.uber.org/zap v1.26.0 golang.org/x/exp v0.0.0-20240404231335-c0f41cb1a7a0 + golang.org/x/sync v0.7.0 google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de ) @@ -247,7 +248,6 @@ require ( golang.org/x/mod v0.17.0 // indirect golang.org/x/net v0.24.0 // indirect golang.org/x/oauth2 v0.18.0 // indirect - golang.org/x/sync v0.7.0 // indirect golang.org/x/time v0.5.0 // indirect golang.org/x/tools v0.20.0 // indirect google.golang.org/api v0.162.0 // indirect diff --git a/test/e2e/btc_staking_e2e_test.go b/test/e2e/btc_staking_e2e_test.go index e76a83c11..530aa0d06 100644 --- a/test/e2e/btc_staking_e2e_test.go +++ b/test/e2e/btc_staking_e2e_test.go @@ -297,12 +297,13 @@ func (s *BTCStakingTestSuite) Test3CommitPublicRandomnessAndSubmitFinalitySignat s.Equal(prCommitMap[activatedHeight].Commitment, msgCommitPubRandList.Commitment) // no reward gauge for finality provider and delegation yet - fpBabylonAddr := sdk.AccAddress(nonValidatorNode.SecretKey.PubKey().Address().Bytes()) + + fpBabylonAddr, err := sdk.AccAddressFromBech32(cacheFP.Addr) + s.NoError(err) + _, err = nonValidatorNode.QueryRewardGauge(fpBabylonAddr) - s.Error(err) - delBabylonAddr := sdk.AccAddress(nonValidatorNode.SecretKey.PubKey().Address().Bytes()) - _, err = nonValidatorNode.QueryRewardGauge(delBabylonAddr) - s.Error(err) + s.ErrorContains(err, itypes.ErrRewardGaugeNotFound.Error()) + delBabylonAddr := fpBabylonAddr /* submit finality signature @@ -358,8 +359,10 @@ func (s *BTCStakingTestSuite) Test4WithdrawReward() { s.NoError(err) // finality provider balance before withdraw - fpBabylonAddr := sdk.AccAddress(nonValidatorNode.SecretKey.PubKey().Address().Bytes()) - delBabylonAddr := sdk.AccAddress(nonValidatorNode.SecretKey.PubKey().Address().Bytes()) + fpBabylonAddr, err := sdk.AccAddressFromBech32(cacheFP.Addr) + s.NoError(err) + delBabylonAddr := fpBabylonAddr + fpBalance, err := nonValidatorNode.QueryBalances(fpBabylonAddr.String()) s.NoError(err) // finality provider reward gauge should not be fully withdrawn diff --git a/test/e2e/configurer/base.go b/test/e2e/configurer/base.go index 6744fff94..d7d474be1 100644 --- a/test/e2e/configurer/base.go +++ b/test/e2e/configurer/base.go @@ -19,6 +19,7 @@ import ( "github.com/babylonchain/babylon/types" types2 "github.com/babylonchain/babylon/x/btccheckpoint/types" "github.com/stretchr/testify/require" + "golang.org/x/sync/errgroup" ) // baseConfigurer is the base implementation for the @@ -39,16 +40,18 @@ const defaultSyncUntilHeight = 3 func (bc *baseConfigurer) ClearResources() error { bc.t.Log("tearing down e2e integration test suite...") - if err := bc.containerManager.ClearResources(); err != nil { - return err - } + g := new(errgroup.Group) + g.Go(func() error { + return bc.containerManager.ClearResources() + }) for _, chainConfig := range bc.chainConfigs { - if err := os.RemoveAll(chainConfig.DataDir); err != nil { - return err - } + chainConfig := chainConfig + g.Go(func() error { + return os.RemoveAll(chainConfig.DataDir) + }) } - return nil + return g.Wait() } func (bc *baseConfigurer) GetChainConfig(chainIndex int) *chain.Config { diff --git a/test/e2e/configurer/chain/chain.go b/test/e2e/configurer/chain/chain.go index 4d5e1ec03..2c16e9f4b 100644 --- a/test/e2e/configurer/chain/chain.go +++ b/test/e2e/configurer/chain/chain.go @@ -2,10 +2,12 @@ package chain import ( "fmt" - ibctesting "github.com/cosmos/ibc-go/v8/testing" "testing" "time" + govv1 "cosmossdk.io/api/cosmos/gov/v1" + ibctesting "github.com/cosmos/ibc-go/v8/testing" + coretypes "github.com/cometbft/cometbft/rpc/core/types" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/stretchr/testify/require" @@ -173,3 +175,10 @@ func (c *Config) GetNodeAtIndex(nodeIndex int) (*NodeConfig, error) { } return c.NodeConfigs[nodeIndex], nil } + +// TxGovVoteFromAllNodes votes in a gov prop from all nodes wallet. +func (c *Config) TxGovVoteFromAllNodes(propID int, option govv1.VoteOption, overallFlags ...string) { + for _, n := range c.NodeConfigs { + n.TxGovVote(n.WalletName, propID, option, overallFlags...) + } +} diff --git a/test/e2e/configurer/chain/commands.go b/test/e2e/configurer/chain/commands.go index 8d8c59b1a..f49e4d77c 100644 --- a/test/e2e/configurer/chain/commands.go +++ b/test/e2e/configurer/chain/commands.go @@ -12,6 +12,7 @@ import ( "strings" "time" + govv1 "cosmossdk.io/api/cosmos/gov/v1" txformat "github.com/babylonchain/babylon/btctxformatter" "github.com/babylonchain/babylon/test/e2e/containers" "github.com/babylonchain/babylon/test/e2e/initialization" @@ -94,6 +95,10 @@ func (n *NodeConfig) BankSendFromNode(receiveAddress, amount string) { n.BankSend(n.WalletName, receiveAddress, amount) } +func (n *NodeConfig) BankMultiSendFromNode(addresses []string, amount string) { + n.BankMultiSend(n.WalletName, addresses, amount) +} + func (n *NodeConfig) BankSend(fromWallet, to, amount string, overallFlags ...string) { fromAddr := n.GetWallet(fromWallet) n.LogActionF("bank sending %s from wallet %s to %s", amount, fromWallet, to) @@ -103,6 +108,23 @@ func (n *NodeConfig) BankSend(fromWallet, to, amount string, overallFlags ...str n.LogActionF("successfully sent bank sent %s from address %s to %s", amount, fromWallet, to) } +func (n *NodeConfig) BankMultiSend(fromWallet string, receivers []string, amount string, overallFlags ...string) { + if len(receivers) == 0 { + require.Error(n.t, fmt.Errorf("no address to send to")) + } + + fromAddr := n.GetWallet(fromWallet) + n.LogActionF("bank multi-send sending %s from wallet %s to %+v", amount, fromWallet, receivers) + + cmd := []string{"babylond", "tx", "bank", "multi-send", fromAddr} // starts the initial flags + cmd = append(cmd, receivers...) // appends all the receivers + cmd = append(cmd, amount, fmt.Sprintf("--from=%s", fromWallet)) // set amounts and overall + + _, _, err := n.containerManager.ExecTxCmd(n.t, n.chainId, n.Name, append(cmd, overallFlags...)) + require.NoError(n.t, err) + n.LogActionF("successfully sent bank multi-send %s from address %s to %+v", amount, fromWallet, receivers) +} + func (n *NodeConfig) BankSendOutput(fromWallet, to, amount string, overallFlags ...string) (out bytes.Buffer, errBuff bytes.Buffer, err error) { fromAddr := n.GetWallet(fromWallet) n.LogActionF("bank sending %s from wallet %s to %s", amount, fromWallet, to) @@ -349,6 +371,43 @@ func (n *NodeConfig) TxMultisignBroadcast(walletNameMultisig, txFileFullPath str n.TxBroadcast(signedTxToBroadcast) } +// TxGovPropSubmitProposal submits a governance proposal from the file inside the container, +// if the file is local, remind to add it to the mounting point in container. +func (n *NodeConfig) TxGovPropSubmitProposal(proposalJsonFilePath, from string, overallFlags ...string) int { + n.LogActionF("submitting new v1 proposal type %s", proposalJsonFilePath) + + cmd := []string{ + "babylond", "tx", "gov", "submit-proposal", proposalJsonFilePath, + fmt.Sprintf("--from=%s", from), + } + + _, _, err := n.containerManager.ExecTxCmd(n.t, n.chainId, n.Name, append(cmd, overallFlags...)) + require.NoError(n.t, err) + + n.WaitForNextBlock() + + props := n.QueryProposals() + require.GreaterOrEqual(n.t, len(props.Proposals), 1) + + n.LogActionF("successfully submitted new v1 proposal type") + return int(props.Proposals[len(props.Proposals)-1].ProposalId) +} + +// TxGovVote votes in a governance proposal +func (n *NodeConfig) TxGovVote(from string, propID int, option govv1.VoteOption, overallFlags ...string) { + n.LogActionF("submitting vote %s to prop %d", option, propID) + + cmd := []string{ + "babylond", "tx", "gov", "vote", fmt.Sprintf("%d", propID), option.String(), + fmt.Sprintf("--from=%s", from), + } + + _, _, err := n.containerManager.ExecTxCmd(n.t, n.chainId, n.Name, append(cmd, overallFlags...)) + require.NoError(n.t, err) + + n.LogActionF("successfully submitted vote %s to prop %d", option, propID) +} + // WriteFile writes a new file in the config dir of the node where it is volume mounted to the // babylon home inside the container and returns the full file path inside the container. func (n *NodeConfig) WriteFile(fileName, content string) (fullFilePathInContainer string) { diff --git a/test/e2e/configurer/chain/node.go b/test/e2e/configurer/chain/node.go index 8e185ec51..db6f2cbac 100644 --- a/test/e2e/configurer/chain/node.go +++ b/test/e2e/configurer/chain/node.go @@ -9,6 +9,7 @@ import ( "testing" "time" + cmtjson "github.com/cometbft/cometbft/libs/json" rpchttp "github.com/cometbft/cometbft/rpc/client/http" coretypes "github.com/cometbft/cometbft/rpc/core/types" "github.com/stretchr/testify/require" @@ -185,3 +186,19 @@ func (n *NodeConfig) LogActionF(msg string, args ...interface{}) { s := fmt.Sprintf(msg, args...) n.t.Logf("[%s] %s. From container %s", timeSinceStart, s, n.Name) } + +func (n *NodeConfig) Status() (*coretypes.ResultStatus, error) { + cmd := []string{"babylond", "status", "--output=json"} + outBuf, _, err := n.containerManager.ExecCmd(n.t, n.Name, cmd, "") + if err != nil { + return nil, err + } + + var r coretypes.ResultStatus + err = cmtjson.Unmarshal(outBuf.Bytes(), &r) + if err != nil { + return nil, err + } + + return &r, nil +} diff --git a/test/e2e/configurer/chain/queries.go b/test/e2e/configurer/chain/queries.go index c0bb049f8..ebf694c58 100644 --- a/test/e2e/configurer/chain/queries.go +++ b/test/e2e/configurer/chain/queries.go @@ -12,6 +12,7 @@ import ( "time" sdkmath "cosmossdk.io/math" + upgradetypes "cosmossdk.io/x/upgrade/types" wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types" cmtabcitypes "github.com/cometbft/cometbft/abci/types" cmttypes "github.com/cometbft/cometbft/types" @@ -19,6 +20,7 @@ import ( "github.com/cosmos/cosmos-sdk/types/query" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + govtypesv1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1" "github.com/stretchr/testify/require" "github.com/babylonchain/babylon/test/e2e/util" @@ -406,6 +408,41 @@ func (n *NodeConfig) QueryWasmSmart(contract string, msg string, result any) err return nil } +func (n *NodeConfig) QueryProposal(proposalNumber int) govtypesv1.QueryProposalResponse { + path := fmt.Sprintf("cosmos/gov/v1beta1/proposals/%d", proposalNumber) + bz, err := n.QueryGRPCGateway(path, url.Values{}) + require.NoError(n.t, err) + + var resp govtypesv1.QueryProposalResponse + err = util.Cdc.UnmarshalJSON(bz, &resp) + require.NoError(n.t, err) + + return resp +} + +func (n *NodeConfig) QueryProposals() govtypesv1.QueryProposalsResponse { + bz, err := n.QueryGRPCGateway("cosmos/gov/v1beta1/proposals", url.Values{}) + require.NoError(n.t, err) + + var resp govtypesv1.QueryProposalsResponse + err = util.Cdc.UnmarshalJSON(bz, &resp) + require.NoError(n.t, err) + + return resp +} + +func (n *NodeConfig) QueryAppliedPlan(planName string) upgradetypes.QueryAppliedPlanResponse { + path := fmt.Sprintf("cosmos/upgrade/v1beta1/applied_plan/%s", planName) + bz, err := n.QueryGRPCGateway(path, url.Values{}) + require.NoError(n.t, err) + + var resp upgradetypes.QueryAppliedPlanResponse + err = util.Cdc.UnmarshalJSON(bz, &resp) + require.NoError(n.t, err) + + return resp +} + func (n *NodeConfig) QueryWasmSmartObject(contract string, msg string) (resultObject map[string]interface{}, err error) { err = n.QueryWasmSmart(contract, msg, &resultObject) if err != nil { @@ -413,3 +450,19 @@ func (n *NodeConfig) QueryWasmSmartObject(contract string, msg string) (resultOb } return resultObject, nil } + +func (n *NodeConfig) QueryTx(txHash string, overallFlags ...string) sdk.TxResponse { + cmd := []string{ + "babylond", "q", "tx", "--type=hash", txHash, "--output=json", + n.FlagChainID(), + } + + out, _, err := n.containerManager.ExecCmd(n.t, n.Name, append(cmd, overallFlags...), "") + require.NoError(n.t, err) + + var txResp sdk.TxResponse + err = util.Cdc.UnmarshalJSON(out.Bytes(), &txResp) + require.NoError(n.t, err) + + return txResp +} diff --git a/test/e2e/configurer/config/constants.go b/test/e2e/configurer/config/constants.go index 09c584507..72ee9e9b4 100644 --- a/test/e2e/configurer/config/constants.go +++ b/test/e2e/configurer/config/constants.go @@ -7,4 +7,14 @@ const ( PropVoteBlocks float32 = 1.2 // PropBufferBlocks number of blocks used as a calculation buffer PropBufferBlocks float32 = 6 + + // Upgrades + // ForkHeightPreUpgradeOffset how many blocks we allow for fork to run pre upgrade state creation + ForkHeightPreUpgradeOffset int64 = 60 + // MaxRetries for json unmarshalling init chain + MaxRetries = 60 + // PropSubmitBlocks estimated number of blocks it takes to submit for a proposal + PropSubmitBlocks float32 = 1 + // VanillaUpgradeFilePath upgrade vanilla testing + VanillaUpgradeFilePath = "/upgrades/vanilla.json" ) diff --git a/test/e2e/configurer/factory.go b/test/e2e/configurer/factory.go index bcac549e5..a33b0be34 100644 --- a/test/e2e/configurer/factory.go +++ b/test/e2e/configurer/factory.go @@ -114,7 +114,7 @@ var ( // TODO currently only one configuration is available. Consider testing upgrades // when necessary func NewBTCTimestampingConfigurer(t *testing.T, isDebugLogEnabled bool) (Configurer, error) { - containerManager, err := containers.NewManager(isDebugLogEnabled, false) + containerManager, err := containers.NewManager(isDebugLogEnabled, false, false) if err != nil { return nil, err } @@ -130,7 +130,7 @@ func NewBTCTimestampingConfigurer(t *testing.T, isDebugLogEnabled bool) (Configu } func NewIBCTransferConfigurer(t *testing.T, isDebugLogEnabled bool) (Configurer, error) { - containerManager, err := containers.NewManager(isDebugLogEnabled, false) + containerManager, err := containers.NewManager(isDebugLogEnabled, false, false) if err != nil { return nil, err } @@ -147,7 +147,7 @@ func NewIBCTransferConfigurer(t *testing.T, isDebugLogEnabled bool) (Configurer, // NewBTCTimestampingPhase2Configurer returns a new Configurer for BTC timestamping service (phase 2). func NewBTCTimestampingPhase2Configurer(t *testing.T, isDebugLogEnabled bool) (Configurer, error) { - containerManager, err := containers.NewManager(isDebugLogEnabled, false) + containerManager, err := containers.NewManager(isDebugLogEnabled, false, false) if err != nil { return nil, err } @@ -164,7 +164,7 @@ func NewBTCTimestampingPhase2Configurer(t *testing.T, isDebugLogEnabled bool) (C // NewBTCTimestampingPhase2RlyConfigurer returns a new Configurer for BTC timestamping service (phase 2), using the Go relayer (rly). func NewBTCTimestampingPhase2RlyConfigurer(t *testing.T, isDebugLogEnabled bool) (Configurer, error) { - containerManager, err := containers.NewManager(isDebugLogEnabled, true) + containerManager, err := containers.NewManager(isDebugLogEnabled, true, false) if err != nil { return nil, err } @@ -181,7 +181,7 @@ func NewBTCTimestampingPhase2RlyConfigurer(t *testing.T, isDebugLogEnabled bool) // NewBTCStakingConfigurer returns a new Configurer for BTC staking service func NewBTCStakingConfigurer(t *testing.T, isDebugLogEnabled bool) (Configurer, error) { - containerManager, err := containers.NewManager(isDebugLogEnabled, false) + containerManager, err := containers.NewManager(isDebugLogEnabled, false, false) if err != nil { return nil, err } @@ -195,3 +195,22 @@ func NewBTCStakingConfigurer(t *testing.T, isDebugLogEnabled bool) (Configurer, containerManager, ), nil } + +// NewSoftwareUpgradeConfigurer returns a new Configurer for Software Upgrade testing +func NewSoftwareUpgradeConfigurer(t *testing.T, isDebugLogEnabled bool, upgradePath string) (Configurer, error) { + containerManager, err := containers.NewManager(isDebugLogEnabled, false, true) + if err != nil { + return nil, err + } + + return NewUpgradeConfigurer(t, + []*chain.Config{ + // we only need 1 chain for testing upgrade + chain.New(t, containerManager, initialization.ChainAID, validatorConfigsChainA, nil), + }, + withUpgrade(baseSetup), // base set up with upgrade + containerManager, + upgradePath, + 0, + ), nil +} diff --git a/test/e2e/configurer/setup.go b/test/e2e/configurer/setup.go index 21308c458..ae2ce6a1b 100644 --- a/test/e2e/configurer/setup.go +++ b/test/e2e/configurer/setup.go @@ -1,6 +1,9 @@ package configurer -import "time" +import ( + "fmt" + "time" +) type setupFn func(configurer Configurer) error @@ -77,3 +80,26 @@ func withIBCTransferChannel(setupHandler setupFn) setupFn { return nil } } + +func withUpgrade(setupHandler setupFn) setupFn { + return func(configurer Configurer) error { + if err := setupHandler(configurer); err != nil { + return err + } + + upgradeConfigurer, ok := configurer.(*UpgradeConfigurer) + if !ok { + return fmt.Errorf("to run with upgrade, %v must be set during initialization", &UpgradeConfigurer{}) + } + + if err := upgradeConfigurer.CreatePreUpgradeState(); err != nil { + return err + } + + if err := upgradeConfigurer.RunUpgrade(); err != nil { + return err + } + + return nil + } +} diff --git a/test/e2e/configurer/upgrade.go b/test/e2e/configurer/upgrade.go new file mode 100644 index 000000000..8c4e60e0d --- /dev/null +++ b/test/e2e/configurer/upgrade.go @@ -0,0 +1,269 @@ +package configurer + +import ( + "encoding/json" + "fmt" + "os" + "sync" + "testing" + "time" + + govv1 "cosmossdk.io/api/cosmos/gov/v1" + sdkmath "cosmossdk.io/math" + sdk "github.com/cosmos/cosmos-sdk/types" + + appparams "github.com/babylonchain/babylon/app/params" + "github.com/babylonchain/babylon/test/e2e/configurer/chain" + "github.com/babylonchain/babylon/test/e2e/configurer/config" + "github.com/babylonchain/babylon/test/e2e/containers" + "github.com/babylonchain/babylon/test/e2e/initialization" +) + +type UpgradeSettings struct { + IsEnabled bool + Version string + ForkHeight int64 // non-zero height implies that this is a fork upgrade. +} + +type UpgradeConfigurer struct { + baseConfigurer + upgradeJsonFilePath string + forkHeight int64 // forkHeight > 0 implies that this is a fork upgrade. Otherwise, proposal upgrade. +} + +var _ Configurer = (*UpgradeConfigurer)(nil) + +// NewUpgradeConfigurer returns a upgrade configurer, if forkHeight is bigger +// than 0 it implies that it is a fork upgrade that does not pass by a gov prop +// if it is set to zero it runs the upgrade by the gov prop. +func NewUpgradeConfigurer(t *testing.T, chainConfigs []*chain.Config, setupTests setupFn, containerManager *containers.Manager, upgradePlanFilePath string, forkHeight int64) Configurer { + t.Helper() + return &UpgradeConfigurer{ + baseConfigurer: baseConfigurer{ + chainConfigs: chainConfigs, + containerManager: containerManager, + setupTests: setupTests, + syncUntilHeight: forkHeight + defaultSyncUntilHeight, + t: t, + }, + forkHeight: forkHeight, + upgradeJsonFilePath: upgradePlanFilePath, + } +} + +func (uc *UpgradeConfigurer) ConfigureChains() error { + errCh := make(chan error, len(uc.chainConfigs)) + var wg sync.WaitGroup + + for _, chainConfig := range uc.chainConfigs { + wg.Add(1) + go func(cc *chain.Config) { + defer wg.Done() + if err := uc.ConfigureChain(cc); err != nil { + errCh <- err + } + }(chainConfig) + } + + // Wait for all goroutines to complete + wg.Wait() + close(errCh) + + // Check if any of the goroutines returned an error + for err := range errCh { + if err != nil { + return err + } + } + + return nil +} + +func (uc *UpgradeConfigurer) ConfigureChain(chainConfig *chain.Config) error { + uc.t.Logf("starting upgrade e2e infrastructure for chain-id: %s", chainConfig.Id) + tmpDir, err := os.MkdirTemp("", "bbn-e2e-testnet-") + if err != nil { + return err + } + + validatorConfigBytes, err := json.Marshal(chainConfig.ValidatorInitConfigs) + if err != nil { + return err + } + + forkHeight := uc.forkHeight + if forkHeight > 0 { + forkHeight = forkHeight - config.ForkHeightPreUpgradeOffset + } + + chainInitResource, err := uc.containerManager.RunChainInitResource(chainConfig.Id, int(chainConfig.VotingPeriod), int(chainConfig.ExpeditedVotingPeriod), validatorConfigBytes, tmpDir, int(forkHeight)) + if err != nil { + return err + } + + fileName := fmt.Sprintf("%v/%v-encode", tmpDir, chainConfig.Id) + uc.t.Logf("serialized init file for chain-id %v: %v", chainConfig.Id, fileName) + + // loop through the reading and unmarshaling of the init file a total of maxRetries or until error is nil + // without this, test attempts to unmarshal file before docker container is finished writing + var initializedChain initialization.Chain + for i := 0; i < config.MaxRetries; i++ { + initializedChainBytes, _ := os.ReadFile(fileName) + err = json.Unmarshal(initializedChainBytes, &initializedChain) + if err == nil { + break + } + + if i == config.MaxRetries-1 { + return err + } + + if i > 0 { + time.Sleep(1 * time.Second) + } + } + if err := uc.containerManager.PurgeResource(chainInitResource); err != nil { + return err + } + uc.initializeChainConfigFromInitChain(&initializedChain, chainConfig) + return nil +} + +func (uc *UpgradeConfigurer) CreatePreUpgradeState() error { + // send a few bank transfers simulating state data + amountToSend := sdk.NewCoin(appparams.BaseCoinUnit, sdkmath.NewInt(1000000)) // 1bbn + for _, chain := range uc.chainConfigs { + firstNode := chain.NodeConfigs[0] + otherNodes := chain.NodeConfigs[1:] + // first node send to others... + + addresses := make([]string, len(otherNodes)) + for i, node := range otherNodes { + addresses[i] = node.PublicAddress + } + firstNode.BankMultiSendFromNode(addresses, amountToSend.String()) + } + + return nil +} + +func (uc *UpgradeConfigurer) RunSetup() error { + return uc.setupTests(uc) +} + +func (uc *UpgradeConfigurer) RunUpgrade() error { + var err error + if uc.forkHeight > 0 { + uc.runForkUpgrade() + } else { + err = uc.runProposalUpgrade() + } + if err != nil { + return err + } + + // Check if the nodes are running + for chainIndex, chainConfig := range uc.chainConfigs { + chain := uc.baseConfigurer.GetChainConfig(chainIndex) + for validatorIdx := range chainConfig.NodeConfigs { + node := chain.NodeConfigs[validatorIdx] + // Check node status + _, err = node.Status() + if err != nil { + uc.t.Errorf("node is not running after upgrade, chain-id %s, node %s", chainConfig.Id, node.Name) + return err + } + uc.t.Logf("node %s upgraded successfully, address %s", node.Name, node.PublicAddress) + } + } + return nil +} + +func (uc *UpgradeConfigurer) runProposalUpgrade() error { + // submit, deposit, and vote for upgrade proposal + // prop height = current height + voting period + time it takes to submit proposal + small buffer + for _, chainConfig := range uc.chainConfigs { + node, err := chainConfig.GetDefaultNode() + if err != nil { + return err + } + // currentHeight, err := node.QueryCurrentHeight() + // if err != nil { + // return err + // } + // TODO: need to make a way to update proposal height + // chainConfig.UpgradePropHeight = currentHeight + int64(chainConfig.VotingPeriod) + int64(config.PropSubmitBlocks) + int64(config.PropBufferBlocks) + chainConfig.UpgradePropHeight = 25 // at least read from the prop plan file + propID := node.TxGovPropSubmitProposal(uc.upgradeJsonFilePath, node.WalletName) + + chainConfig.TxGovVoteFromAllNodes(propID, govv1.VoteOption_VOTE_OPTION_YES) + } + + // wait till all chains halt at upgrade height + for _, chainConfig := range uc.chainConfigs { + uc.t.Logf("waiting to reach upgrade height on chain %s", chainConfig.Id) + chainConfig.WaitUntilHeight(chainConfig.UpgradePropHeight) + uc.t.Logf("upgrade height reached on chain %s", chainConfig.Id) + } + + // remove all containers so we can upgrade them to the new version + for _, chainConfig := range uc.chainConfigs { + for _, validatorConfig := range chainConfig.NodeConfigs { + err := uc.containerManager.RemoveNodeResource(validatorConfig.Name) + if err != nil { + return err + } + } + } + + // remove all containers so we can upgrade them to the new version + for _, chainConfig := range uc.chainConfigs { + if err := uc.upgradeContainers(chainConfig, chainConfig.UpgradePropHeight); err != nil { + return err + } + } + return nil +} + +func (uc *UpgradeConfigurer) runForkUpgrade() { + for _, chainConfig := range uc.chainConfigs { + uc.t.Logf("waiting to reach fork height on chain %s", chainConfig.Id) + chainConfig.WaitUntilHeight(uc.forkHeight) + uc.t.Logf("fork height reached on chain %s", chainConfig.Id) + } +} + +func (uc *UpgradeConfigurer) upgradeContainers(chainConfig *chain.Config, propHeight int64) error { + // upgrade containers to the locally compiled daemon + uc.t.Logf("starting upgrade for chain-id: %s...", chainConfig.Id) + uc.containerManager.CurrentRepository = containers.BabylonContainerName + + errCh := make(chan error, len(chainConfig.NodeConfigs)) + var wg sync.WaitGroup + + for _, node := range chainConfig.NodeConfigs { + wg.Add(1) + go func(node *chain.NodeConfig) { + defer wg.Done() + if err := node.Run(); err != nil { + errCh <- err + } + }(node) + } + + // Wait for all goroutines to complete + wg.Wait() + close(errCh) + + // Check if any of the goroutines returned an error + for err := range errCh { + if err != nil { + return err + } + } + + uc.t.Logf("waiting to upgrade containers on chain %s", chainConfig.Id) + chainConfig.WaitUntilHeight(propHeight + 1) + uc.t.Logf("upgrade successful on chain %s", chainConfig.Id) + return nil +} diff --git a/test/e2e/containers/config.go b/test/e2e/containers/config.go index 964367871..0c6058ff2 100644 --- a/test/e2e/containers/config.go +++ b/test/e2e/containers/config.go @@ -3,38 +3,51 @@ package containers // ImageConfig contains all images and their respective tags // needed for running e2e tests. type ImageConfig struct { + // IBC relayer for cosmos-SDK RelayerRepository string RelayerTag string + + CurrentRepository string } //nolint:deadcode const ( - // name of babylon container produced by running `make localnet-build-env` + // Images that do not have specified tag, latest will be used by default. + // name of babylon image produced by running `make build-docker` BabylonContainerName = "babylonchain/babylond" + // name of babylon image produced by running `make build-docker-e2e` + BabylonContainerNameBeforeUpgrade = "babylonchain/babylond-before-upgrade" + // name of the image produced by running `make e2e-init-chain` in contrib/images + InitChainContainerE2E = "babylonchain/babylond-e2e-init-chain" hermesRelayerRepository = "informalsystems/hermes" hermesRelayerTag = "v1.8.2" // Built using the `build-cosmos-relayer-docker` target on an Intel (amd64) machine and pushed to ECR cosmosRelayerRepository = "public.ecr.aws/t9e9i3h0/cosmos-relayer" // TODO: Replace with version tag once we have a working version - cosmosRelayerTag = "main" + cosmosRelayerTag = "main" ) // NewImageConfig returns ImageConfig needed for running e2e test. // If isUpgrade is true, returns images for running the upgrade // If isFork is true, utilizes provided fork height to initiate fork logic -func NewImageConfig(isCosmosRelayer bool) ImageConfig { +func NewImageConfig(isCosmosRelayer, isUpgrade bool) (ic ImageConfig) { + ic = ImageConfig{ + CurrentRepository: BabylonContainerName, + } + + if isUpgrade { + // starts at the older version and later upgrades it to current branch... BabylonContainerName + ic.CurrentRepository = BabylonContainerNameBeforeUpgrade + } + if isCosmosRelayer { - config := ImageConfig{ - RelayerRepository: cosmosRelayerRepository, - RelayerTag: cosmosRelayerTag, - } - return config + ic.RelayerRepository = cosmosRelayerRepository + ic.RelayerTag = cosmosRelayerTag + return ic } else { - config := ImageConfig{ - RelayerRepository: hermesRelayerRepository, - RelayerTag: hermesRelayerTag, - } - return config + ic.RelayerRepository = hermesRelayerRepository + ic.RelayerTag = hermesRelayerTag + return ic } } diff --git a/test/e2e/containers/containers.go b/test/e2e/containers/containers.go index 09782d7f1..2de3bc413 100644 --- a/test/e2e/containers/containers.go +++ b/test/e2e/containers/containers.go @@ -13,6 +13,7 @@ import ( "github.com/ory/dockertest/v3" "github.com/ory/dockertest/v3/docker" "github.com/stretchr/testify/require" + "golang.org/x/sync/errgroup" ) const ( @@ -39,9 +40,9 @@ type Manager struct { // NewManager creates a new Manager instance and initializes // all Docker specific utilities. Returns an error if initialization fails. -func NewManager(isDebugLogEnabled bool, isCosmosRelayer bool) (docker *Manager, err error) { +func NewManager(isDebugLogEnabled bool, isCosmosRelayer, isUpgrade bool) (docker *Manager, err error) { docker = &Manager{ - ImageConfig: NewImageConfig(isCosmosRelayer), + ImageConfig: NewImageConfig(isCosmosRelayer, isUpgrade), resources: make(map[string]*dockertest.Resource), isDebugLogEnabled: isDebugLogEnabled, } @@ -252,7 +253,7 @@ func (m *Manager) RunNodeResource(chainId string, containerName, valCondifDir st runOpts := &dockertest.RunOptions{ Name: containerName, - Repository: BabylonContainerName, + Repository: m.CurrentRepository, NetworkID: m.network.Network.ID, User: "root:root", Entrypoint: []string{ @@ -264,6 +265,7 @@ func (m *Manager) RunNodeResource(chainId string, containerName, valCondifDir st Mounts: []string{ fmt.Sprintf("%s/:%s", valCondifDir, BabylonHomePath), fmt.Sprintf("%s/bytecode:/bytecode", pwd), + fmt.Sprintf("%s/upgrades:/upgrades", pwd), }, } @@ -321,17 +323,20 @@ func (m *Manager) RemoveNodeResource(containerName string) error { } // ClearResources removes all outstanding Docker resources created by the Manager. -func (m *Manager) ClearResources() error { +func (m *Manager) ClearResources() (e error) { + g := new(errgroup.Group) for _, resource := range m.resources { - if err := m.pool.Purge(resource); err != nil { - return err - } + resource := resource + g.Go(func() error { + return m.pool.Purge(resource) + }) } - if err := m.pool.RemoveNetwork(m.network); err != nil { + if err := g.Wait(); err != nil { return err } - return nil + + return m.pool.RemoveNetwork(m.network) } func noRestart(config *docker.HostConfig) { @@ -340,3 +345,38 @@ func noRestart(config *docker.HostConfig) { Name: "no", } } + +// RunChainInitResource runs a chain init container to initialize genesis and configs for a chain with chainId. +// The chain is to be configured with chainVotingPeriod and validators deserialized from validatorConfigBytes. +// The genesis and configs are to be mounted on the init container as volume on mountDir path. +// Returns the container resource and error if any. This method does not Purge the container. The caller +// must deal with removing the resource. +func (m *Manager) RunChainInitResource(chainId string, chainVotingPeriod, chainExpeditedVotingPeriod int, validatorConfigBytes []byte, mountDir string, forkHeight int) (*dockertest.Resource, error) { + votingPeriodDuration := time.Duration(chainVotingPeriod * 1000000000) + expeditedVotingPeriodDuration := time.Duration(chainExpeditedVotingPeriod * 1000000000) + + initResource, err := m.pool.RunWithOptions( + &dockertest.RunOptions{ + Name: chainId, + Repository: InitChainContainerE2E, + NetworkID: m.network.Network.ID, + Cmd: []string{ + fmt.Sprintf("--data-dir=%s", mountDir), + fmt.Sprintf("--chain-id=%s", chainId), + fmt.Sprintf("--config=%s", validatorConfigBytes), + fmt.Sprintf("--voting-period=%v", votingPeriodDuration), + fmt.Sprintf("--expedited-voting-period=%v", expeditedVotingPeriodDuration), + fmt.Sprintf("--fork-height=%v", forkHeight), + }, + User: "root:root", + Mounts: []string{ + fmt.Sprintf("%s:%s", mountDir, mountDir), + }, + }, + noRestart, + ) + if err != nil { + return nil, err + } + return initResource, nil +} diff --git a/test/e2e/e2e_test.go b/test/e2e/e2e_test.go index 4c877c6c8..cc78d6b15 100644 --- a/test/e2e/e2e_test.go +++ b/test/e2e/e2e_test.go @@ -35,3 +35,8 @@ func TestBTCTimestampingPhase2RlyTestSuite(t *testing.T) { func TestBTCStakingTestSuite(t *testing.T) { suite.Run(t, new(BTCStakingTestSuite)) } + +// TestSoftwareUpgradeTestSuite tests software upgrade protocol end-to-end +func TestSoftwareUpgradeTestSuite(t *testing.T) { + suite.Run(t, new(SoftwareUpgradeVanillaTestSuite)) +} diff --git a/test/e2e/initialization/config.go b/test/e2e/initialization/config.go index 9e5f95a16..f0adbbbd4 100644 --- a/test/e2e/initialization/config.go +++ b/test/e2e/initialization/config.go @@ -6,7 +6,7 @@ import ( "path/filepath" "time" - "cosmossdk.io/math" + sdkmath "cosmossdk.io/math" cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" @@ -17,6 +17,8 @@ import ( crisistypes "github.com/cosmos/cosmos-sdk/x/crisis/types" "github.com/cosmos/cosmos-sdk/x/genutil" genutiltypes "github.com/cosmos/cosmos-sdk/x/genutil/types" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" + govv1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1" minttypes "github.com/cosmos/cosmos-sdk/x/mint/types" staketypes "github.com/cosmos/cosmos-sdk/x/staking/types" "github.com/cosmos/gogoproto/proto" @@ -67,9 +69,9 @@ const ( ) var ( - StakeAmountIntA = math.NewInt(StakeAmountA) + StakeAmountIntA = sdkmath.NewInt(StakeAmountA) StakeAmountCoinA = sdk.NewCoin(BabylonDenom, StakeAmountIntA) - StakeAmountIntB = math.NewInt(StakeAmountB) + StakeAmountIntB = sdkmath.NewInt(StakeAmountB) StakeAmountCoinB = sdk.NewCoin(BabylonDenom, StakeAmountIntB) InitBalanceStrA = fmt.Sprintf("%d%s", BabylonBalanceA, BabylonDenom) @@ -216,6 +218,11 @@ func initGenesis(chain *internalChain, votingPeriod, expeditedVotingPeriod time. return err } + err = updateModuleGenesis(appGenState, govtypes.ModuleName, &govv1.GenesisState{}, updateGovGenesis(votingPeriod, expeditedVotingPeriod)) + if err != nil { + return err + } + err = updateModuleGenesis(appGenState, minttypes.ModuleName, &minttypes.GenesisState{}, updateMintGenesis) if err != nil { return err @@ -288,6 +295,14 @@ func updateBankGenesis(bankGenState *banktypes.GenesisState) { }) } +func updateGovGenesis(votingPeriod, expeditedVotingPeriod time.Duration) func(govGenState *govv1.GenesisState) { + return func(govGenState *govv1.GenesisState) { + govGenState.Params.MinDeposit = sdk.NewCoins(sdk.NewCoin(BabylonDenom, sdkmath.NewInt(100))) + govGenState.Params.VotingPeriod = &votingPeriod + govGenState.Params.ExpeditedVotingPeriod = &expeditedVotingPeriod + } +} + func updateMintGenesis(mintGenState *minttypes.GenesisState) { mintGenState.Params.MintDenom = BabylonDenom } @@ -299,7 +314,7 @@ func updateStakeGenesis(stakeGenState *staketypes.GenesisState) { MaxEntries: 7, HistoricalEntries: 10000, UnbondingTime: staketypes.DefaultUnbondingTime, - MinCommissionRate: math.LegacyZeroDec(), + MinCommissionRate: sdkmath.LegacyZeroDec(), } } diff --git a/test/e2e/initialization/export.go b/test/e2e/initialization/export.go index 663250876..ebe2c2522 100644 --- a/test/e2e/initialization/export.go +++ b/test/e2e/initialization/export.go @@ -2,8 +2,6 @@ package initialization import ( "fmt" - - cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" ) type ChainMeta struct { @@ -17,7 +15,6 @@ type Node struct { Mnemonic string `json:"mnemonic"` PublicAddress string `json:"publicAddress"` WalletName string `json:"walletName"` - SecretKey cryptotypes.PrivKey PublicKey []byte `json:"publicKey"` PeerId string `json:"peerId"` IsValidator bool `json:"isValidator"` diff --git a/test/e2e/initialization/node.go b/test/e2e/initialization/node.go index d2a8dc1bf..0e940b1a8 100644 --- a/test/e2e/initialization/node.go +++ b/test/e2e/initialization/node.go @@ -248,7 +248,6 @@ func (n *internalNode) export() *Node { Mnemonic: n.mnemonic, PublicAddress: addr.String(), WalletName: n.keyInfo.Name, - SecretKey: n.privateKey, PublicKey: pub.Bytes(), PeerId: n.peerId, IsValidator: n.isValidator, diff --git a/test/e2e/software_upgrade_e2e_test.go b/test/e2e/software_upgrade_e2e_test.go new file mode 100644 index 000000000..d1baf0692 --- /dev/null +++ b/test/e2e/software_upgrade_e2e_test.go @@ -0,0 +1,50 @@ +package e2e + +import ( + "github.com/stretchr/testify/suite" + + "github.com/babylonchain/babylon/test/e2e/configurer" + "github.com/babylonchain/babylon/test/e2e/configurer/config" +) + +type SoftwareUpgradeVanillaTestSuite struct { + suite.Suite + + configurer configurer.Configurer +} + +func (s *SoftwareUpgradeVanillaTestSuite) SetupSuite() { + s.T().Log("setting up e2e integration test suite...") + var err error + + s.configurer, err = configurer.NewSoftwareUpgradeConfigurer(s.T(), false, config.VanillaUpgradeFilePath) + s.NoError(err) + err = s.configurer.ConfigureChains() + s.NoError(err) + err = s.configurer.RunSetup() // upgrade happens at the setup of configurer. + s.NoError(err) +} + +func (s *SoftwareUpgradeVanillaTestSuite) TearDownSuite() { + err := s.configurer.ClearResources() + s.Require().NoError(err) +} + +// TestUpgradeVanilla only checks that new fp was added. +func (s *SoftwareUpgradeVanillaTestSuite) TestUpgradeVanilla() { + // chain is already upgraded, only checks for differences in state are expected + chainA := s.configurer.GetChainConfig(0) + chainA.WaitUntilHeight(30) // five blocks more than upgrade + + n, err := chainA.GetDefaultNode() + s.NoError(err) + + expectedUpgradeHeight := int64(25) + + // makes sure that the upgrade was actually executed + resp := n.QueryAppliedPlan("vanilla") + s.EqualValues(expectedUpgradeHeight, resp.Height, "the plan should be applied at the height 25") + + fps := n.QueryFinalityProviders() + s.Len(fps, 1, "it should have one finality provider, since the vanilla upgrade just added a new one") +} diff --git a/test/e2e/upgrades/vanilla.json b/test/e2e/upgrades/vanilla.json new file mode 100644 index 000000000..f38550fa1 --- /dev/null +++ b/test/e2e/upgrades/vanilla.json @@ -0,0 +1,14 @@ +{ + "title": "any title", + "summary": "any summary", + "messages": [ + { + "@type": "/cosmos.upgrade.v1beta1.MsgSoftwareUpgrade", + "authority": "bbn10d07y265gmmuvt4z0w9aw880jnsr700jduz5f2", + "plan": { "name": "vanilla", "info": "Msg info", "height": 25 } + } + ], + "deposit": "500000000ubbn", + "initial_deposit": "500000000ubbn", + "initialDeposit": "500000000ubbn" +}