diff --git a/.changelog/unreleased/bug-fixes/1570-slashack-bech32.md b/.changelog/unreleased/bug-fixes/1570-slashack-bech32.md new file mode 100644 index 0000000000..a0e9fe9262 --- /dev/null +++ b/.changelog/unreleased/bug-fixes/1570-slashack-bech32.md @@ -0,0 +1,2 @@ +- Fix the validation of VSCPackets to not fail due to marshaling to string using Bech32. + ([\#1570](https://github.com/cosmos/interchain-security/pull/1570)) \ No newline at end of file diff --git a/.changelog/unreleased/state-breaking/1570-slashack-bech32.md b/.changelog/unreleased/state-breaking/1570-slashack-bech32.md new file mode 100644 index 0000000000..a0e9fe9262 --- /dev/null +++ b/.changelog/unreleased/state-breaking/1570-slashack-bech32.md @@ -0,0 +1,2 @@ +- Fix the validation of VSCPackets to not fail due to marshaling to string using Bech32. + ([\#1570](https://github.com/cosmos/interchain-security/pull/1570)) \ No newline at end of file diff --git a/.github/workflows/manual-e2e.yml b/.github/workflows/manual-e2e.yml deleted file mode 100644 index f9ca0ca33d..0000000000 --- a/.github/workflows/manual-e2e.yml +++ /dev/null @@ -1,103 +0,0 @@ -# manually run full E2E test suite -# all tests are run sequentially -name: manual-e2e-main -on: - workflow_dispatch: - -jobs: - happy-path-test: - runs-on: ubuntu-latest - timeout-minutes: 20 - steps: - - uses: actions/setup-go@v5 - with: - go-version: "1.21" - - uses: actions/checkout@v4 - - name: Checkout LFS objects - run: git lfs checkout - - name: Setup Go - uses: actions/setup-go@v5 - with: - go-version: "1.21" # The Go version to download (if necessary) and use. - - name: E2E happy-path test - run: go run ./tests/e2e/... --tc happy-path - changeover-test: - runs-on: ubuntu-latest - timeout-minutes: 20 - steps: - - uses: actions/setup-go@v5 - with: - go-version: "1.21" - - uses: actions/checkout@v4 - - name: Checkout LFS objects - run: git lfs checkout - - name: Setup Go - uses: actions/setup-go@v5 - with: - go-version: "1.21" # The Go version to download (if necessary) and use. - - name: E2E changeover test - run: go run ./tests/e2e/... --tc changeover - democracy-reward-test: - runs-on: ubuntu-latest - timeout-minutes: 20 - steps: - - uses: actions/setup-go@v5 - with: - go-version: "1.21" - - uses: actions/checkout@v4 - - name: Checkout LFS objects - run: git lfs checkout - - name: Setup Go - uses: actions/setup-go@v5 - with: - go-version: "1.21" # The Go version to download (if necessary) and use. - - name: E2E democracy-reward tests - run: go run ./tests/e2e/... --tc democracy-reward - democracy-test: - runs-on: ubuntu-latest - timeout-minutes: 20 - steps: - - uses: actions/setup-go@v5 - with: - go-version: "1.21" - - uses: actions/checkout@v4 - - name: Checkout LFS objects - run: git lfs checkout - - name: Setup Go - uses: actions/setup-go@v5 - with: - go-version: "1.21" # The Go version to download (if necessary) and use. - - name: E2E democracy tests - run: go run ./tests/e2e/... --tc democracy - slash-throttle-test: - runs-on: ubuntu-latest - timeout-minutes: 20 - steps: - - uses: actions/setup-go@v5 - with: - go-version: "1.21" - - uses: actions/checkout@v4 - - name: Checkout LFS objects - run: git lfs checkout - - name: Setup Go - uses: actions/setup-go@v5 - with: - go-version: "1.21" # The Go version to download (if necessary) and use. - - name: E2E slash-throttle tests - run: go run ./tests/e2e/... --tc slash-throttle - multiconsumer-test: - runs-on: ubuntu-latest - timeout-minutes: 40 - steps: - - uses: actions/setup-go@v5 - with: - go-version: "1.21" - - uses: actions/checkout@v4 - - name: Checkout LFS objects - run: git lfs checkout - - name: Setup Go - uses: actions/setup-go@v5 - with: - go-version: "1.21" # The Go version to download (if necessary) and use. - - name: E2E multi-consumer tests - run: go run ./tests/e2e/... --tc multiconsumer diff --git a/.github/workflows/nightly-e2e.yml b/.github/workflows/nightly-e2e.yml index 2e6e60d042..043f19fc1a 100644 --- a/.github/workflows/nightly-e2e.yml +++ b/.github/workflows/nightly-e2e.yml @@ -1,9 +1,7 @@ # Run integration tests nightly on main - -# !! Relevant changes to this file should be propagated manual-integration.yml - name: nightly-e2e-main on: + workflow_dispatch: schedule: # run every day at 03:00 UTC # ┌───────────── minute (0 - 59) @@ -114,6 +112,54 @@ jobs: go-version: "1.21" # The Go version to download (if necessary) and use. - name: E2E multi-consumer tests run: go run ./tests/e2e/... --tc multiconsumer + consumer-misbehaviour-test: + runs-on: ubuntu-latest + timeout-minutes: 20 + steps: + - uses: actions/setup-go@v5 + with: + go-version: "1.21" + - uses: actions/checkout@v4 + - name: Checkout LFS objects + run: git lfs checkout + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version: "1.21" # The Go version to download (if necessary) and use. + - name: E2E consumer-misbehaviour tests + run: go run ./tests/e2e/... --tc consumer-misbehaviour + consumer-double-sign-test: + runs-on: ubuntu-latest + timeout-minutes: 20 + steps: + - uses: actions/setup-go@v5 + with: + go-version: "1.21" + - uses: actions/checkout@v4 + - name: Checkout LFS objects + run: git lfs checkout + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version: "1.21" # The Go version to download (if necessary) and use. + - name: E2E consumer-double-sign tests + run: go run ./tests/e2e/... --tc consumer-double-sign + consumer-double-downtime-test: + runs-on: ubuntu-latest + timeout-minutes: 20 + steps: + - uses: actions/setup-go@v5 + with: + go-version: "1.21" + - uses: actions/checkout@v4 + - name: Checkout LFS objects + run: git lfs checkout + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version: "1.21" # The Go version to download (if necessary) and use. + - name: E2E consumer-double-downtime tests + run: go run ./tests/e2e/... --tc consumer-double-downtime nightly-test-fail: needs: @@ -123,6 +169,9 @@ jobs: - democracy-test - slash-throttle-test - multiconsumer-test + - consumer-misbehaviour-test + - consumer-double-sign-test + - consumer-double-downtime-test if: ${{ failure() }} runs-on: ubuntu-latest steps: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 9670a29182..a03dfdf831 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -39,7 +39,7 @@ jobs: **/go.sum **/Makefile Makefile - - uses: actions/cache@v3.3.3 + - uses: actions/cache@v4.0.0 with: path: | ~/.cache/go-build diff --git a/Dockerfile.combined b/Dockerfile.combined new file mode 100644 index 0000000000..5ab867c89c --- /dev/null +++ b/Dockerfile.combined @@ -0,0 +1,53 @@ +# syntax=docker/dockerfile:1 + +# Dockerfile.combined defines a docker image using different provider/consumer versions +# originated from other docker images. +# This image is used to test different versions of provider and consumer together. +# +# Use docker's build argument --build-arg to specify the consumer/provider image to be used +# e.g. docker build --build-arg CONSUMER_IMAGE=v3.1.0 --build-arg PROVIDER_IMAGE=v3.1.0 + +ARG PROVIDER_IMAGE +ARG CONSUMER_IMAGE + +# The image from where the consumer implementation will be used +# Defaults to +FROM --platform=linux/amd64 ${PROVIDER_IMAGE} AS provider + + +# The image from where the consumer implementation will be used +# Defaults to +FROM --platform=linux/amd64 ${CONSUMER_IMAGE} AS consumer + +# Get Hermes build +FROM --platform=linux/amd64 otacrew/hermes-ics:evidence-cmd AS hermes-builder + + +# Get GoRelayer +FROM ghcr.io/informalsystems/relayer-no-gas-sim:v2.3.0-rc4-no-gas-sim AS gorelayer-builder + + +FROM --platform=linux/amd64 fedora:39 +RUN dnf update -y +RUN dnf install -y which iproute iputils procps-ng vim-minimal tmux net-tools htop jq +USER root + +COPY --from=hermes-builder /usr/bin/hermes /usr/local/bin/ +COPY --from=gorelayer-builder /bin/rly /usr/local/bin/ + +# Copy consumer from specified image +COPY --from=consumer /usr/local/bin/interchain-security-cd /usr/local/bin/interchain-security-cd +COPY --from=consumer /usr/local/bin/interchain-security-cdd /usr/local/bin/interchain-security-cdd +COPY --from=consumer /usr/local/bin/interchain-security-sd /usr/local/bin/interchain-security-sd +COPY --from=consumer /testnet-scripts /consumer/testnet-scripts + + +# Copy provider from specified image +COPY --from=provider /usr/local/bin/interchain-security-pd /usr/local/bin/interchain-security-pd +COPY --from=provider /testnet-scripts /provider/testnet-scripts + +#Copy cometmock from provider image +COPY --from=provider /usr/local/bin/cometmock /usr/local/bin + +# Copy in the hermes config +ADD ./tests/e2e/testnet-scripts/hermes-config.toml /root/.hermes/config.toml diff --git a/Makefile b/Makefile index dfa42b3e82..350c66f8af 100644 --- a/Makefile +++ b/Makefile @@ -1,13 +1,33 @@ #!/usr/bin/make -f +BRANCH := $(shell git rev-parse --abbrev-ref HEAD) +COMMIT := $(shell git log -1 --format='%H') + +# don't override user values +ifeq (,$(VERSION)) + VERSION := $(shell git describe --exact-match 2>/dev/null) + # if VERSION is empty, then populate it with branch's name and raw commit hash + ifeq (,$(VERSION)) + VERSION := $(BRANCH)-$(COMMIT) + endif +endif + +sharedFlags = -X github.com/cosmos/cosmos-sdk/version.Version=$(VERSION) \ + -X github.com/cosmos/cosmos-sdk/version.Commit=$(COMMIT) + +providerFlags := $(sharedFlags) -X github.com/cosmos/cosmos-sdk/version.AppName=interchain-security-pd -X github.com/cosmos/cosmos-sdk/version.Name=interchain-security-pd +consumerFlags := $(sharedFlags) -X github.com/cosmos/cosmos-sdk/version.AppName=interchain-security-cd -X github.com/cosmos/cosmos-sdk/version.Name=interchain-security-cd +democracyFlags := $(sharedFlags) -X github.com/cosmos/cosmos-sdk/version.AppName=interchain-security-cdd -X github.com/cosmos/cosmos-sdk/version.Name=interchain-security-cdd +standaloneFlags := $(sharedFlags) -X github.com/cosmos/cosmos-sdk/version.AppName=interchain-security-sd -X github.com/cosmos/cosmos-sdk/version.Name=interchain-security-sd + install: go.sum export GOFLAGS='-buildmode=pie' export CGO_CPPFLAGS="-D_FORTIFY_SOURCE=2" export CGO_LDFLAGS="-Wl,-z,relro,-z,now -fstack-protector" - go install $(BUILD_FLAGS) ./cmd/interchain-security-pd - go install $(BUILD_FLAGS) ./cmd/interchain-security-cd - go install $(BUILD_FLAGS) ./cmd/interchain-security-cdd - go install $(BUILD_FLAGS) ./cmd/interchain-security-sd + go install -ldflags "$(providerFlags)" ./cmd/interchain-security-pd + go install -ldflags "$(consumerFlags)" ./cmd/interchain-security-cd + go install -ldflags "$(democracyFlags)" ./cmd/interchain-security-cdd + go install -ldflags "$(standaloneFlags)" ./cmd/interchain-security-sd # run all tests: unit, integration, diff, and E2E test: test-unit test-integration test-mbt test-e2e diff --git a/UPGRADING.md b/UPGRADING.md index cc7de42395..1267d3ac66 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -12,9 +12,29 @@ Upgrading a provider from `v3.3.0` to `v4.0.0` will require state migrations, se ### Consumer +***Note that consumer chains can upgrade directly from `v3.1.0` to `v4.0.0`.*** + Upgrading a consumer from `v3.2.0` to `v4.0.0` will not require state migration, however, upgrading directly from `v3.1.0` to `v4.0.0` will require state migrations, see https://github.com/cosmos/interchain-security/blob/release/v4.0.x/x/ccv/consumer/keeper/migrations.go#L22. -Note that consumer chains can upgrade directly from `v3.1.0` to `v4.0.0`. +In addition, the following migration needs to be added to the upgrade handler of the consumer chain: +```golang +func migrateICSOutstandingDowntime(ctx sdk.Context, keepers *upgrades.UpgradeKeepers) error { + ctx.Logger().Info("Migrating ICS outstanding downtime...") + + downtimes := keepers.ConsumerKeeper.GetAllOutstandingDowntimes(ctx) + for _, od := range downtimes { + consAddr, err := sdk.ConsAddressFromBech32(od.ValidatorConsensusAddress) + if err != nil { + return err + } + keepers.ConsumerKeeper.DeleteOutstandingDowntime(ctx, consAddr) + } + + ctx.Logger().Info("Finished ICS outstanding downtime") + + return nil +} +``` ## [v3.3.x](https://github.com/cosmos/interchain-security/tree/release/v3.2.x) diff --git a/app/consumer-democracy/app.go b/app/consumer-democracy/app.go index e740c89406..e90d35760e 100644 --- a/app/consumer-democracy/app.go +++ b/app/consumer-democracy/app.go @@ -103,7 +103,7 @@ import ( "github.com/cometbft/cometbft/libs/log" tmos "github.com/cometbft/cometbft/libs/os" - appparams "github.com/cosmos/interchain-security/v4/app/params" + appencoding "github.com/cosmos/interchain-security/v4/app/encoding" testutil "github.com/cosmos/interchain-security/v4/testutil/integration" consumer "github.com/cosmos/interchain-security/v4/x/ccv/consumer" consumerkeeper "github.com/cosmos/interchain-security/v4/x/ccv/consumer/keeper" @@ -116,7 +116,7 @@ import ( const ( AppName = "interchain-security-cd" upgradeName = "sovereign-changeover" // arbitrary name, define your own appropriately named upgrade - AccountAddressPrefix = "cosmos" + AccountAddressPrefix = "consumer" ) var ( @@ -1005,8 +1005,8 @@ func initParamsKeeper(appCodec codec.BinaryCodec, legacyAmino *codec.LegacyAmino // should be used only in tests or when creating a new app instance (NewApp*()). // App user shouldn't create new codecs - use the app.AppCodec instead. // [DEPRECATED] -func MakeTestEncodingConfig() appparams.EncodingConfig { - encodingConfig := appparams.MakeTestEncodingConfig() +func MakeTestEncodingConfig() appencoding.EncodingConfig { + encodingConfig := appencoding.MakeTestEncodingConfig() std.RegisterLegacyAminoCodec(encodingConfig.Amino) std.RegisterInterfaces(encodingConfig.InterfaceRegistry) ModuleBasics.RegisterLegacyAminoCodec(encodingConfig.Amino) @@ -1014,8 +1014,8 @@ func MakeTestEncodingConfig() appparams.EncodingConfig { return encodingConfig } -func makeEncodingConfig() appparams.EncodingConfig { - encodingConfig := appparams.MakeTestEncodingConfig() +func makeEncodingConfig() appencoding.EncodingConfig { + encodingConfig := appencoding.MakeTestEncodingConfig() std.RegisterLegacyAminoCodec(encodingConfig.Amino) std.RegisterInterfaces(encodingConfig.InterfaceRegistry) ModuleBasics.RegisterLegacyAminoCodec(encodingConfig.Amino) diff --git a/app/consumer/ante/disabled_modules_ante_test.go b/app/consumer/ante/disabled_modules_ante_test.go index 2b12bed63a..7fa95f37c6 100644 --- a/app/consumer/ante/disabled_modules_ante_test.go +++ b/app/consumer/ante/disabled_modules_ante_test.go @@ -13,11 +13,11 @@ import ( slashingtypes "github.com/cosmos/cosmos-sdk/x/slashing/types" "github.com/cosmos/interchain-security/v4/app/consumer/ante" - "github.com/cosmos/interchain-security/v4/app/params" + appencoding "github.com/cosmos/interchain-security/v4/app/encoding" ) func TestDisabledModulesDecorator(t *testing.T) { - txCfg := params.MakeTestEncodingConfig().TxConfig + txCfg := appencoding.MakeTestEncodingConfig().TxConfig authzMsgExecSlashing := authz.NewMsgExec(sdk.AccAddress{}, []sdk.Msg{&slashingtypes.MsgUnjail{}}) authzMsgExecEvidence := authz.NewMsgExec(sdk.AccAddress{}, []sdk.Msg{&evidencetypes.MsgSubmitEvidence{}}) nestedAuthzMsgExecSlashing := authz.NewMsgExec(sdk.AccAddress{}, []sdk.Msg{&authzMsgExecSlashing}) diff --git a/app/consumer/ante/msg_filter_ante_test.go b/app/consumer/ante/msg_filter_ante_test.go index 47538dad1a..bfc1bb0a50 100644 --- a/app/consumer/ante/msg_filter_ante_test.go +++ b/app/consumer/ante/msg_filter_ante_test.go @@ -10,7 +10,7 @@ import ( banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" "github.com/cosmos/interchain-security/v4/app/consumer/ante" - "github.com/cosmos/interchain-security/v4/app/params" + appencoding "github.com/cosmos/interchain-security/v4/app/encoding" ) type consumerKeeper struct { @@ -28,7 +28,7 @@ func noOpAnteDecorator() sdk.AnteHandler { } func TestMsgFilterDecorator(t *testing.T) { - txCfg := params.MakeTestEncodingConfig().TxConfig + txCfg := appencoding.MakeTestEncodingConfig().TxConfig testCases := []struct { name string diff --git a/app/consumer/app.go b/app/consumer/app.go index 220be5ed80..9853145117 100644 --- a/app/consumer/app.go +++ b/app/consumer/app.go @@ -87,7 +87,7 @@ import ( "github.com/cometbft/cometbft/libs/log" tmos "github.com/cometbft/cometbft/libs/os" - appparams "github.com/cosmos/interchain-security/v4/app/params" + appencoding "github.com/cosmos/interchain-security/v4/app/encoding" testutil "github.com/cosmos/interchain-security/v4/testutil/integration" ibcconsumer "github.com/cosmos/interchain-security/v4/x/ccv/consumer" ibcconsumerkeeper "github.com/cosmos/interchain-security/v4/x/ccv/consumer/keeper" @@ -97,7 +97,7 @@ import ( const ( AppName = "interchain-security-c" upgradeName = "ics-v1-to-v2" - AccountAddressPrefix = "cosmos" + AccountAddressPrefix = "consumer" ) var ( @@ -830,8 +830,8 @@ func initParamsKeeper(appCodec codec.BinaryCodec, legacyAmino *codec.LegacyAmino // return encodingConfig // } -func MakeTestEncodingConfig() appparams.EncodingConfig { - encodingConfig := appparams.MakeTestEncodingConfig() +func MakeTestEncodingConfig() appencoding.EncodingConfig { + encodingConfig := appencoding.MakeTestEncodingConfig() std.RegisterLegacyAminoCodec(encodingConfig.Amino) std.RegisterInterfaces(encodingConfig.InterfaceRegistry) ModuleBasics.RegisterLegacyAminoCodec(encodingConfig.Amino) @@ -839,8 +839,8 @@ func MakeTestEncodingConfig() appparams.EncodingConfig { return encodingConfig } -func makeEncodingConfig() appparams.EncodingConfig { - encodingConfig := appparams.MakeTestEncodingConfig() +func makeEncodingConfig() appencoding.EncodingConfig { + encodingConfig := appencoding.MakeTestEncodingConfig() std.RegisterLegacyAminoCodec(encodingConfig.Amino) std.RegisterInterfaces(encodingConfig.InterfaceRegistry) ModuleBasics.RegisterLegacyAminoCodec(encodingConfig.Amino) diff --git a/app/params/proto.go b/app/encoding/encoding.go similarity index 61% rename from app/params/proto.go rename to app/encoding/encoding.go index d11fe8d06c..6897e53634 100644 --- a/app/params/proto.go +++ b/app/encoding/encoding.go @@ -1,11 +1,21 @@ -package params +package encoding import ( + "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/codec/types" "github.com/cosmos/cosmos-sdk/x/auth/tx" ) +// EncodingConfig specifies the concrete encoding types to use for a given app. +// This is provided for compatibility between protobuf and amino implementations. +type EncodingConfig struct { + InterfaceRegistry types.InterfaceRegistry + Codec codec.Codec + TxConfig client.TxConfig + Amino *codec.LegacyAmino +} + // MakeTestEncodingConfig creates an EncodingConfig for an amino based test configuration. func MakeTestEncodingConfig() EncodingConfig { amino := codec.NewLegacyAmino() diff --git a/app/params/config.go b/app/params/config.go index e4f7308498..c4e3cb7e3c 100644 --- a/app/params/config.go +++ b/app/params/config.go @@ -4,32 +4,27 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ) -var ( - Bech32Prefix = "cosmos" - - // Bech32PrefixAccAddr defines the Bech32 prefix of an account's address - Bech32PrefixAccAddr = Bech32Prefix - // Bech32PrefixAccPub defines the Bech32 prefix of an account's public key - Bech32PrefixAccPub = Bech32Prefix + sdk.PrefixPublic - // Bech32PrefixValAddr defines the Bech32 prefix of a validator's operator address - Bech32PrefixValAddr = Bech32Prefix + sdk.PrefixValidator + sdk.PrefixOperator - // Bech32PrefixValPub defines the Bech32 prefix of a validator's operator public key - Bech32PrefixValPub = Bech32Prefix + sdk.PrefixValidator + sdk.PrefixOperator + sdk.PrefixPublic - // Bech32PrefixConsAddr defines the Bech32 prefix of a consensus node address - Bech32PrefixConsAddr = Bech32Prefix + sdk.PrefixValidator + sdk.PrefixConsensus - // Bech32PrefixConsPub defines the Bech32 prefix of a consensus node public key - Bech32PrefixConsPub = Bech32Prefix + sdk.PrefixValidator + sdk.PrefixConsensus + sdk.PrefixPublic -) - // SetAddressPrefixes builds the Config with Bech32 addressPrefix and publKeyPrefix for accounts, validators, and consensus nodes and verifies that addreeses have correct format. // Not sealed yet -func SetAddressPrefixes() { +func SetAddressPrefixes(bech32Prefix string) { cfg := sdk.GetConfig() - cfg.SetBech32PrefixForAccount(Bech32PrefixAccAddr, Bech32PrefixAccPub) - cfg.SetBech32PrefixForValidator(Bech32PrefixValAddr, Bech32PrefixValPub) - cfg.SetBech32PrefixForConsensusNode(Bech32PrefixConsAddr, Bech32PrefixConsPub) -} -func init() { - SetAddressPrefixes() + // bech32PrefixAccAddr defines the Bech32 prefix of an account's address + bech32PrefixAccAddr := bech32Prefix + // bech32PrefixAccPub defines the Bech32 prefix of an account's public key + bech32PrefixAccPub := bech32Prefix + sdk.PrefixPublic + // bech32PrefixValAddr defines the Bech32 prefix of a validator's operator address + bech32PrefixValAddr := bech32Prefix + sdk.PrefixValidator + sdk.PrefixOperator + // bech32PrefixValPub defines the Bech32 prefix of a validator's operator public key + bech32PrefixValPub := bech32Prefix + sdk.PrefixValidator + sdk.PrefixOperator + sdk.PrefixPublic + // bech32PrefixConsAddr defines the Bech32 prefix of a consensus node address + bech32PrefixConsAddr := bech32Prefix + sdk.PrefixValidator + sdk.PrefixConsensus + // bech32PrefixConsPub defines the Bech32 prefix of a consensus node public key + bech32PrefixConsPub := bech32Prefix + sdk.PrefixValidator + sdk.PrefixConsensus + sdk.PrefixPublic + + cfg.SetBech32PrefixForAccount(bech32PrefixAccAddr, bech32PrefixAccPub) + cfg.SetBech32PrefixForValidator(bech32PrefixValAddr, bech32PrefixValPub) + cfg.SetBech32PrefixForConsensusNode(bech32PrefixConsAddr, bech32PrefixConsPub) + + cfg.Seal() } diff --git a/app/params/doc.go b/app/params/doc.go deleted file mode 100644 index 49b5f6d1e7..0000000000 --- a/app/params/doc.go +++ /dev/null @@ -1,19 +0,0 @@ -/* -Package params defines the simulation parameters in the gaia. - -It contains the default weights used for each transaction used on the module's -simulation. These weights define the chance for a transaction to be simulated at -any given operation. - -You can replace the default values for the weights by providing a params.json -file with the weights defined for each of the transaction operations: - - { - "op_weight_msg_send": 60, - "op_weight_msg_delegate": 100, - } - -In the example above, the `MsgSend` has 60% chance to be simulated, while the -`MsgDelegate` will always be simulated. -*/ -package params diff --git a/app/params/encoding.go b/app/params/encoding.go deleted file mode 100644 index 8ff9ea04b3..0000000000 --- a/app/params/encoding.go +++ /dev/null @@ -1,16 +0,0 @@ -package params - -import ( - "github.com/cosmos/cosmos-sdk/client" - "github.com/cosmos/cosmos-sdk/codec" - "github.com/cosmos/cosmos-sdk/codec/types" -) - -// EncodingConfig specifies the concrete encoding types to use for a given app. -// This is provided for compatibility between protobuf and amino implementations. -type EncodingConfig struct { - InterfaceRegistry types.InterfaceRegistry - Codec codec.Codec - TxConfig client.TxConfig - Amino *codec.LegacyAmino -} diff --git a/app/params/params.go b/app/params/params.go deleted file mode 100644 index b6aa5fb55e..0000000000 --- a/app/params/params.go +++ /dev/null @@ -1,7 +0,0 @@ -package params - -// Simulation parameter constants -const ( - StakePerAccount = "stake_per_account" - InitiallyBondedValidators = "initially_bonded_validators" -) diff --git a/app/params/weights.go b/app/params/weights.go deleted file mode 100644 index 4e5452c9b2..0000000000 --- a/app/params/weights.go +++ /dev/null @@ -1,42 +0,0 @@ -package params - -// Default simulation operation weights for messages and gov proposals -const ( - DefaultWeightMsgSend int = 100 - DefaultWeightMsgMultiSend int = 10 - DefaultWeightMsgSetWithdrawAddress int = 50 - DefaultWeightMsgWithdrawDelegationReward int = 50 - DefaultWeightMsgWithdrawValidatorCommission int = 50 - DefaultWeightMsgFundCommunityPool int = 50 - DefaultWeightMsgDeposit int = 100 - DefaultWeightMsgVote int = 67 - DefaultWeightMsgUnjail int = 100 - DefaultWeightMsgCreateValidator int = 100 - DefaultWeightMsgEditValidator int = 5 - DefaultWeightMsgDelegate int = 100 - DefaultWeightMsgUndelegate int = 100 - DefaultWeightMsgBeginRedelegate int = 100 - - DefaultWeightCommunitySpendProposal int = 5 - DefaultWeightTextProposal int = 5 - DefaultWeightParamChangeProposal int = 5 - - DefaultWeightMsgStoreCode int = 50 - DefaultWeightMsgInstantiateContract int = 100 - DefaultWeightMsgExecuteContract int = 100 - DefaultWeightMsgUpdateAdmin int = 25 - DefaultWeightMsgClearAdmin int = 10 - DefaultWeightMsgMigrateContract int = 50 - - DefaultWeightStoreCodeProposal int = 5 - DefaultWeightInstantiateContractProposal int = 5 - DefaultWeightUpdateAdminProposal int = 5 - DefaultWeightExecuteContractProposal int = 5 - DefaultWeightClearAdminProposal int = 5 - DefaultWeightMigrateContractProposal int = 5 - DefaultWeightSudoContractProposal int = 5 - DefaultWeightPinCodesProposal int = 5 - DefaultWeightUnpinCodesProposal int = 5 - DefaultWeightUpdateInstantiateConfigProposal int = 5 - DefaultWeightStoreAndInstantiateContractProposal int = 5 -) diff --git a/app/provider/app.go b/app/provider/app.go index c1f3c4d6ff..7527756d45 100644 --- a/app/provider/app.go +++ b/app/provider/app.go @@ -100,7 +100,7 @@ import ( "github.com/cometbft/cometbft/libs/log" tmos "github.com/cometbft/cometbft/libs/os" - appparams "github.com/cosmos/interchain-security/v4/app/params" + appencoding "github.com/cosmos/interchain-security/v4/app/encoding" testutil "github.com/cosmos/interchain-security/v4/testutil/integration" ibcprovider "github.com/cosmos/interchain-security/v4/x/ccv/provider" ibcproviderclient "github.com/cosmos/interchain-security/v4/x/ccv/provider/client" @@ -930,8 +930,8 @@ func initParamsKeeper(appCodec codec.BinaryCodec, legacyAmino *codec.LegacyAmino // return encodingConfig // } -func MakeTestEncodingConfig() appparams.EncodingConfig { - encodingConfig := appparams.MakeTestEncodingConfig() +func MakeTestEncodingConfig() appencoding.EncodingConfig { + encodingConfig := appencoding.MakeTestEncodingConfig() std.RegisterLegacyAminoCodec(encodingConfig.Amino) std.RegisterInterfaces(encodingConfig.InterfaceRegistry) ModuleBasics.RegisterLegacyAminoCodec(encodingConfig.Amino) @@ -939,8 +939,8 @@ func MakeTestEncodingConfig() appparams.EncodingConfig { return encodingConfig } -func makeEncodingConfig() appparams.EncodingConfig { - encodingConfig := appparams.MakeTestEncodingConfig() +func makeEncodingConfig() appencoding.EncodingConfig { + encodingConfig := appencoding.MakeTestEncodingConfig() std.RegisterLegacyAminoCodec(encodingConfig.Amino) std.RegisterInterfaces(encodingConfig.InterfaceRegistry) ModuleBasics.RegisterLegacyAminoCodec(encodingConfig.Amino) diff --git a/app/sovereign/app.go b/app/sovereign/app.go index 55967df923..4017ca9d4f 100644 --- a/app/sovereign/app.go +++ b/app/sovereign/app.go @@ -106,14 +106,14 @@ import ( "github.com/cometbft/cometbft/libs/log" tmos "github.com/cometbft/cometbft/libs/os" - appparams "github.com/cosmos/interchain-security/v4/app/params" + appencoding "github.com/cosmos/interchain-security/v4/app/encoding" testutil "github.com/cosmos/interchain-security/v4/testutil/integration" ) const ( AppName = "interchain-security-s" upgradeName = "v07-Theta" // arbitrary name, define your own appropriately named upgrade - AccountAddressPrefix = "cosmos" + AccountAddressPrefix = "consumer" ) var ( @@ -864,8 +864,8 @@ func initParamsKeeper(appCodec codec.BinaryCodec, legacyAmino *codec.LegacyAmino // return encodingConfig // } -func MakeTestEncodingConfig() appparams.EncodingConfig { - encodingConfig := appparams.MakeTestEncodingConfig() +func MakeTestEncodingConfig() appencoding.EncodingConfig { + encodingConfig := appencoding.MakeTestEncodingConfig() std.RegisterLegacyAminoCodec(encodingConfig.Amino) std.RegisterInterfaces(encodingConfig.InterfaceRegistry) ModuleBasics.RegisterLegacyAminoCodec(encodingConfig.Amino) @@ -873,8 +873,8 @@ func MakeTestEncodingConfig() appparams.EncodingConfig { return encodingConfig } -func makeEncodingConfig() appparams.EncodingConfig { - encodingConfig := appparams.MakeTestEncodingConfig() +func makeEncodingConfig() appencoding.EncodingConfig { + encodingConfig := appencoding.MakeTestEncodingConfig() std.RegisterLegacyAminoCodec(encodingConfig.Amino) std.RegisterInterfaces(encodingConfig.InterfaceRegistry) ModuleBasics.RegisterLegacyAminoCodec(encodingConfig.Amino) diff --git a/cmd/interchain-security-cd/cmd/root.go b/cmd/interchain-security-cd/cmd/root.go index 64e7b76d3d..215b45317a 100644 --- a/cmd/interchain-security-cd/cmd/root.go +++ b/cmd/interchain-security-cd/cmd/root.go @@ -32,7 +32,7 @@ import ( "github.com/cometbft/cometbft/libs/log" consumer "github.com/cosmos/interchain-security/v4/app/consumer" - "github.com/cosmos/interchain-security/v4/app/params" + appencoding "github.com/cosmos/interchain-security/v4/app/encoding" ) // NewRootCmd creates a new root command for simd. It is called once in the @@ -40,7 +40,7 @@ import ( func NewRootCmd() *cobra.Command { // we "pre"-instantiate the application for getting the injected/configured encoding configuration tempApp := consumer.New(log.NewNopLogger(), dbm.NewMemDB(), nil, true, simtestutil.NewAppOptionsWithFlagHome(consumer.DefaultNodeHome)) - encodingConfig := params.EncodingConfig{ + encodingConfig := appencoding.EncodingConfig{ InterfaceRegistry: tempApp.InterfaceRegistry(), Codec: tempApp.AppCodec(), TxConfig: tempApp.TxConfig(), @@ -186,7 +186,7 @@ lru_size = 0` return customAppTemplate, customAppConfig } -func initRootCmd(rootCmd *cobra.Command, encodingConfig params.EncodingConfig) { +func initRootCmd(rootCmd *cobra.Command, encodingConfig appencoding.EncodingConfig) { cfg := sdk.GetConfig() cfg.Seal() @@ -276,7 +276,7 @@ func addModuleInitFlags(startCmd *cobra.Command) { } // genesisCommand builds genesis-related `simd genesis` command. Users may provide application specific commands as a parameter -func genesisCommand(encodingConfig params.EncodingConfig, cmds ...*cobra.Command) *cobra.Command { +func genesisCommand(encodingConfig appencoding.EncodingConfig, cmds ...*cobra.Command) *cobra.Command { cmd := genutilcli.GenesisCoreCommand(encodingConfig.TxConfig, consumer.ModuleBasics, consumer.DefaultNodeHome) for _, sub_cmd := range cmds { diff --git a/cmd/interchain-security-cd/main.go b/cmd/interchain-security-cd/main.go index 3ecd26f899..a64a2a8645 100644 --- a/cmd/interchain-security-cd/main.go +++ b/cmd/interchain-security-cd/main.go @@ -7,12 +7,13 @@ import ( svrcmd "github.com/cosmos/cosmos-sdk/server/cmd" app "github.com/cosmos/interchain-security/v4/app/consumer" + appparams "github.com/cosmos/interchain-security/v4/app/params" "github.com/cosmos/interchain-security/v4/cmd/interchain-security-cd/cmd" ) func main() { + appparams.SetAddressPrefixes("consumer") rootCmd := cmd.NewRootCmd() - if err := svrcmd.Execute(rootCmd, "", app.DefaultNodeHome); err != nil { switch e := err.(type) { case server.ErrorCode: diff --git a/cmd/interchain-security-cdd/cmd/root.go b/cmd/interchain-security-cdd/cmd/root.go index b69bd57636..6b2e6cc726 100644 --- a/cmd/interchain-security-cdd/cmd/root.go +++ b/cmd/interchain-security-cdd/cmd/root.go @@ -32,7 +32,7 @@ import ( "github.com/cometbft/cometbft/libs/log" cdd "github.com/cosmos/interchain-security/v4/app/consumer-democracy" - "github.com/cosmos/interchain-security/v4/app/params" + appencoding "github.com/cosmos/interchain-security/v4/app/encoding" ) // NewRootCmd creates a new root command for simd. It is called once in the @@ -40,7 +40,7 @@ import ( func NewRootCmd() *cobra.Command { // we "pre"-instantiate the application for getting the injected/configured encoding configuration tempApp := cdd.New(log.NewNopLogger(), dbm.NewMemDB(), nil, true, simtestutil.NewAppOptionsWithFlagHome(cdd.DefaultNodeHome)) - encodingConfig := params.EncodingConfig{ + encodingConfig := appencoding.EncodingConfig{ InterfaceRegistry: tempApp.InterfaceRegistry(), Codec: tempApp.AppCodec(), TxConfig: tempApp.TxConfig(), @@ -186,7 +186,7 @@ lru_size = 0` return customAppTemplate, customAppConfig } -func initRootCmd(rootCmd *cobra.Command, encodingConfig params.EncodingConfig) { +func initRootCmd(rootCmd *cobra.Command, encodingConfig appencoding.EncodingConfig) { cfg := sdk.GetConfig() cfg.Seal() @@ -276,7 +276,7 @@ func addModuleInitFlags(startCmd *cobra.Command) { } // genesisCommand builds genesis-related `simd genesis` command. Users may provide application specific commands as a parameter -func genesisCommand(encodingConfig params.EncodingConfig, cmds ...*cobra.Command) *cobra.Command { +func genesisCommand(encodingConfig appencoding.EncodingConfig, cmds ...*cobra.Command) *cobra.Command { cmd := genutilcli.GenesisCoreCommand(encodingConfig.TxConfig, cdd.ModuleBasics, cdd.DefaultNodeHome) for _, sub_cmd := range cmds { diff --git a/cmd/interchain-security-cdd/main.go b/cmd/interchain-security-cdd/main.go index 430ab60591..9b6aacd759 100644 --- a/cmd/interchain-security-cdd/main.go +++ b/cmd/interchain-security-cdd/main.go @@ -7,12 +7,13 @@ import ( svrcmd "github.com/cosmos/cosmos-sdk/server/cmd" app "github.com/cosmos/interchain-security/v4/app/consumer-democracy" + appparams "github.com/cosmos/interchain-security/v4/app/params" "github.com/cosmos/interchain-security/v4/cmd/interchain-security-cdd/cmd" ) func main() { + appparams.SetAddressPrefixes("consumer") rootCmd := cmd.NewRootCmd() - if err := svrcmd.Execute(rootCmd, "", app.DefaultNodeHome); err != nil { switch e := err.(type) { case server.ErrorCode: diff --git a/cmd/interchain-security-pd/cmd/root.go b/cmd/interchain-security-pd/cmd/root.go index 1f752d758b..8a8f5fec32 100644 --- a/cmd/interchain-security-pd/cmd/root.go +++ b/cmd/interchain-security-pd/cmd/root.go @@ -31,7 +31,7 @@ import ( tmcfg "github.com/cometbft/cometbft/config" "github.com/cometbft/cometbft/libs/log" - "github.com/cosmos/interchain-security/v4/app/params" + appencoding "github.com/cosmos/interchain-security/v4/app/encoding" providerApp "github.com/cosmos/interchain-security/v4/app/provider" ) @@ -40,7 +40,7 @@ import ( func NewRootCmd() *cobra.Command { // we "pre"-instantiate the application for getting the injected/configured encoding configuration tempApp := providerApp.New(log.NewNopLogger(), dbm.NewMemDB(), nil, true, simtestutil.NewAppOptionsWithFlagHome(providerApp.DefaultNodeHome)) - encodingConfig := params.EncodingConfig{ + encodingConfig := appencoding.EncodingConfig{ InterfaceRegistry: tempApp.InterfaceRegistry(), Codec: tempApp.AppCodec(), TxConfig: tempApp.TxConfig(), @@ -186,7 +186,7 @@ lru_size = 0` return customAppTemplate, customAppConfig } -func initRootCmd(rootCmd *cobra.Command, encodingConfig params.EncodingConfig) { +func initRootCmd(rootCmd *cobra.Command, encodingConfig appencoding.EncodingConfig) { cfg := sdk.GetConfig() cfg.Seal() @@ -276,7 +276,7 @@ func addModuleInitFlags(startCmd *cobra.Command) { } // genesisCommand builds genesis-related `simd genesis` command. Users may provide application specific commands as a parameter -func genesisCommand(encodingConfig params.EncodingConfig, cmds ...*cobra.Command) *cobra.Command { +func genesisCommand(encodingConfig appencoding.EncodingConfig, cmds ...*cobra.Command) *cobra.Command { cmd := genutilcli.GenesisCoreCommand(encodingConfig.TxConfig, providerApp.ModuleBasics, providerApp.DefaultNodeHome) for _, sub_cmd := range cmds { cmd.AddCommand(sub_cmd) diff --git a/cmd/interchain-security-pd/main.go b/cmd/interchain-security-pd/main.go index 6091c60156..7788f06bff 100644 --- a/cmd/interchain-security-pd/main.go +++ b/cmd/interchain-security-pd/main.go @@ -6,13 +6,14 @@ import ( "github.com/cosmos/cosmos-sdk/server" svrcmd "github.com/cosmos/cosmos-sdk/server/cmd" + appparams "github.com/cosmos/interchain-security/v4/app/params" app "github.com/cosmos/interchain-security/v4/app/provider" "github.com/cosmos/interchain-security/v4/cmd/interchain-security-pd/cmd" ) func main() { + appparams.SetAddressPrefixes("cosmos") rootCmd := cmd.NewRootCmd() - if err := svrcmd.Execute(rootCmd, "", app.DefaultNodeHome); err != nil { switch e := err.(type) { case server.ErrorCode: diff --git a/cmd/interchain-security-sd/cmd/root.go b/cmd/interchain-security-sd/cmd/root.go index 0862e92836..28520ef1a4 100644 --- a/cmd/interchain-security-sd/cmd/root.go +++ b/cmd/interchain-security-sd/cmd/root.go @@ -31,7 +31,7 @@ import ( tmcfg "github.com/cometbft/cometbft/config" "github.com/cometbft/cometbft/libs/log" - "github.com/cosmos/interchain-security/v4/app/params" + appencoding "github.com/cosmos/interchain-security/v4/app/encoding" sovereignApp "github.com/cosmos/interchain-security/v4/app/sovereign" ) @@ -40,7 +40,7 @@ import ( func NewRootCmd() *cobra.Command { // we "pre"-instantiate the application for getting the injected/configured encoding configuration tempApp := sovereignApp.New(log.NewNopLogger(), dbm.NewMemDB(), nil, true, simtestutil.NewAppOptionsWithFlagHome(sovereignApp.DefaultNodeHome)) - encodingConfig := params.EncodingConfig{ + encodingConfig := appencoding.EncodingConfig{ InterfaceRegistry: tempApp.InterfaceRegistry(), Codec: tempApp.AppCodec(), TxConfig: tempApp.TxConfig(), @@ -186,7 +186,7 @@ lru_size = 0` return customAppTemplate, customAppConfig } -func initRootCmd(rootCmd *cobra.Command, encodingConfig params.EncodingConfig) { +func initRootCmd(rootCmd *cobra.Command, encodingConfig appencoding.EncodingConfig) { cfg := sdk.GetConfig() cfg.Seal() @@ -276,7 +276,7 @@ func addModuleInitFlags(startCmd *cobra.Command) { } // genesisCommand builds genesis-related `simd genesis` command. Users may provide application specific commands as a parameter -func genesisCommand(encodingConfig params.EncodingConfig, cmds ...*cobra.Command) *cobra.Command { +func genesisCommand(encodingConfig appencoding.EncodingConfig, cmds ...*cobra.Command) *cobra.Command { cmd := genutilcli.GenesisCoreCommand(encodingConfig.TxConfig, sovereignApp.ModuleBasics, sovereignApp.DefaultNodeHome) for _, sub_cmd := range cmds { cmd.AddCommand(sub_cmd) diff --git a/cmd/interchain-security-sd/main.go b/cmd/interchain-security-sd/main.go index 239b08022b..2265afad90 100644 --- a/cmd/interchain-security-sd/main.go +++ b/cmd/interchain-security-sd/main.go @@ -6,13 +6,14 @@ import ( "github.com/cosmos/cosmos-sdk/server" svrcmd "github.com/cosmos/cosmos-sdk/server/cmd" + appparams "github.com/cosmos/interchain-security/v4/app/params" app "github.com/cosmos/interchain-security/v4/app/sovereign" "github.com/cosmos/interchain-security/v4/cmd/interchain-security-sd/cmd" ) func main() { + appparams.SetAddressPrefixes("consumer") rootCmd := cmd.NewRootCmd() - if err := svrcmd.Execute(rootCmd, "", app.DefaultNodeHome); err != nil { switch e := err.(type) { case server.ErrorCode: diff --git a/tests/e2e/README.md b/tests/e2e/README.md index 963494a2ee..cc74e57834 100644 --- a/tests/e2e/README.md +++ b/tests/e2e/README.md @@ -17,7 +17,7 @@ End-to-end tests can still be useful, and we need them, but when possible, we should prefer more local tests. At a high-level, every test case consists of the following steps. -* The test starts a docker container, see [the startup script](testnet-scripts/start-docker.sh) +* The test starts a docker container, see [setupEnvironment](test_runner.go) * We run a defined sequence of actions and expected states, see as an example the steps for [testing the democracy module](steps_democracy.go) * Actions are any event that might meaningfully modify the system state, such as submitting transactions to a node, making nodes double-sign, starting a relayer, starting a new chain, etc. * Expected states define the state we expect after the action was taken. @@ -35,7 +35,7 @@ At a high-level, every test case consists of the following steps. If you just want to run the end-to-end tests, see the commands in the Makefile in the repo root. -If you want to run the tests with a bit more control, see the help by running +If you want to run the tests with a bit more control, see the help by running ```go run ./tests/e2e/... --help``` in the repo root to see how to do that. @@ -102,7 +102,7 @@ the actions necessary to start a provider and multiple consumer chains are already "packaged together" and available as `stepsStartChains` in [steps_start_chains.go](steps_start_chains.go). -**Note:** The parts of the state that are *not* defined are just *not checked*. +**Note:** The parts of the state that are *not* defined are just *not checked*. For example, if the balance of a validator is not listed in a state, it means we do not care about the balance of that validator in this particular state. @@ -131,7 +131,7 @@ You can see the basic template for how to do this by looking at the actions in The basic principle is to use `exec.Command` to execute a command inside the docker container. The pattern for this looks something like this: ``` -cmd := exec.Command("docker", "exec", +cmd := exec.Command("docker", "exec", tr.containerConfig.InstanceName, tr.chainConfigs[action.Chain].BinaryName, "the command to execute, for example tx", @@ -145,7 +145,7 @@ if err != nil { // potentially check something in the output, or log something, ... ``` -Don't forget to wire your action into [main.go](main.go):runStep, where +Don't forget to wire your action into [test_driver.go](test_driver.go):runAction, where the action structs and functions to run for each of them are wired together. **Note:** Actions don't need to check that the state was modified correctly, @@ -163,7 +163,7 @@ work and sometimes fail due to gas, as can happen with `--gas=auto` and no `--ga Essentially, sometimes the gas estimation will underestimate gas, but not always - it seems to be non-deterministic, and probably depends on subtle things like block times, or heights, which are not finely controlled in the end-to-end tests -and do not perfectly match each time. +and do not perfectly match each time. To be sure we don't introduce nondeterminism like this, we need to use a sufficient adjustment to make sure there is enough gas for transactions to pass. diff --git a/tests/e2e/actions.go b/tests/e2e/actions.go index e98fd611e3..81bc3c8b76 100644 --- a/tests/e2e/actions.go +++ b/tests/e2e/actions.go @@ -6,6 +6,7 @@ import ( "fmt" "log" "math" + "os" "os/exec" "strconv" "strings" @@ -31,15 +32,35 @@ const done = "done!!!!!!!!" func (tr TestConfig) sendTokens( action SendTokensAction, + target ExecutionTarget, verbose bool, ) { + fromValCfg := tr.validatorConfigs[action.From] + toValCfg := tr.validatorConfigs[action.To] + fromAddress := fromValCfg.DelAddress + toAddress := toValCfg.DelAddress + if action.Chain != ChainID("provi") { + // use binary with Bech32Prefix set to ConsumerAccountPrefix + if fromValCfg.UseConsumerKey { + fromAddress = fromValCfg.ConsumerDelAddress + } else { + // use the same address as on the provider but with different prefix + fromAddress = fromValCfg.DelAddressOnConsumer + } + if toValCfg.UseConsumerKey { + toAddress = toValCfg.ConsumerDelAddress + } else { + // use the same address as on the provider but with different prefix + toAddress = toValCfg.DelAddressOnConsumer + } + } + binaryName := tr.chainConfigs[action.Chain].BinaryName - //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. - cmd := exec.Command("docker", "exec", tr.containerConfig.InstanceName, binaryName, + cmd := target.ExecCommand(binaryName, "tx", "bank", "send", - tr.validatorConfigs[action.From].DelAddress, - tr.validatorConfigs[action.To].DelAddress, + fromAddress, + toAddress, fmt.Sprint(action.Amount)+`stake`, `--chain-id`, string(tr.chainConfigs[action.Chain].ChainId), @@ -76,6 +97,7 @@ type StartChainValidator struct { func (tr *TestConfig) startChain( action StartChainAction, + target ExecutionTarget, verbose bool, ) { chainConfig := tr.chainConfigs[action.Chain] @@ -131,9 +153,9 @@ func (tr *TestConfig) startChain( cometmockArg = "false" } - //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. - cmd := exec.Command("docker", "exec", tr.containerConfig.InstanceName, "/bin/bash", - "/testnet-scripts/start-chain.sh", chainConfig.BinaryName, string(vals), + startChainScript := target.GetTestScriptPath(action.IsConsumer, "start-chain.sh") + cmd := target.ExecCommand("/bin/bash", + startChainScript, chainConfig.BinaryName, string(vals), string(chainConfig.ChainId), chainConfig.IpPrefix, genesisChanges, fmt.Sprint(action.IsConsumer), // override config/config.toml for each node on chain @@ -173,7 +195,7 @@ func (tr *TestConfig) startChain( Chain: action.Chain, Validator: action.Validators[0].Id, IsConsumer: action.IsConsumer, - }, verbose) + }, target, verbose) // store the fact that we started the chain tr.runningChains[action.Chain] = true @@ -194,11 +216,12 @@ type SubmitTextProposalAction struct { func (tr TestConfig) submitTextProposal( action SubmitTextProposalAction, + target ExecutionTarget, verbose bool, ) { // TEXT PROPOSAL - //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. - bz, err := exec.Command("docker", "exec", tr.containerConfig.InstanceName, tr.chainConfigs[action.Chain].BinaryName, + bz, err := target.ExecCommand( + tr.chainConfigs[action.Chain].BinaryName, "tx", "gov", "submit-legacy-proposal", `--title`, action.Title, `--description`, action.Description, @@ -231,6 +254,7 @@ type SubmitConsumerAdditionProposalAction struct { func (tr TestConfig) submitConsumerAdditionProposal( action SubmitConsumerAdditionProposalAction, + target ExecutionTarget, verbose bool, ) { spawnTime := tr.containerConfig.Now.Add(time.Duration(action.SpawnTime) * time.Millisecond) @@ -263,17 +287,18 @@ func (tr TestConfig) submitConsumerAdditionProposal( log.Fatal("prop json contains single quote") } - //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. - bz, err = exec.Command("docker", "exec", tr.containerConfig.InstanceName, - "/bin/bash", "-c", fmt.Sprintf(`echo '%s' > %s`, jsonStr, "/temp-proposal.json")).CombinedOutput() + //#nosec G204 -- bypass unsafe quoting warning (no production code) + bz, err = target.ExecCommand( + "/bin/bash", "-c", fmt.Sprintf(`echo '%s' > %s`, jsonStr, "/temp-proposal.json"), + ).CombinedOutput() if err != nil { log.Fatal(err, "\n", string(bz)) } - //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. // CONSUMER ADDITION PROPOSAL - bz, err = exec.Command("docker", "exec", tr.containerConfig.InstanceName, tr.chainConfigs[action.Chain].BinaryName, + bz, err = target.ExecCommand( + tr.chainConfigs[action.Chain].BinaryName, "tx", "gov", "submit-legacy-proposal", "consumer-addition", "/temp-proposal.json", `--from`, `validator`+fmt.Sprint(action.From), `--chain-id`, string(tr.chainConfigs[action.Chain].ChainId), @@ -302,6 +327,7 @@ type SubmitConsumerRemovalProposalAction struct { func (tr TestConfig) submitConsumerRemovalProposal( action SubmitConsumerRemovalProposalAction, + target ExecutionTarget, verbose bool, ) { stopTime := tr.containerConfig.Now.Add(action.StopTimeOffset) @@ -323,17 +349,15 @@ func (tr TestConfig) submitConsumerRemovalProposal( log.Fatal("prop json contains single quote") } - //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. - bz, err = exec.Command("docker", "exec", tr.containerConfig.InstanceName, + bz, err = target.ExecCommand( "/bin/bash", "-c", fmt.Sprintf(`echo '%s' > %s`, jsonStr, "/temp-proposal.json")).CombinedOutput() if err != nil { log.Fatal(err, "\n", string(bz)) } - //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. - bz, err = exec.Command("docker", "exec", tr.containerConfig.InstanceName, tr.chainConfigs[action.Chain].BinaryName, - + bz, err = target.ExecCommand( + tr.chainConfigs[action.Chain].BinaryName, "tx", "gov", "submit-legacy-proposal", "consumer-removal", "/temp-proposal.json", `--from`, `validator`+fmt.Sprint(action.From), @@ -378,6 +402,7 @@ type paramChangeJSON struct { func (tr TestConfig) submitParamChangeProposal( action SubmitParamChangeLegacyProposalAction, + target ExecutionTarget, verbose bool, ) { prop := paramChangeProposalJSON{ @@ -398,17 +423,17 @@ func (tr TestConfig) submitParamChangeProposal( log.Fatal("prop json contains single quote") } - //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. - bz, err = exec.Command("docker", "exec", tr.containerConfig.InstanceName, - "/bin/bash", "-c", fmt.Sprintf(`echo '%s' > %s`, jsonStr, "/params-proposal.json")).CombinedOutput() + //#nosec G204 -- bypass unsafe quoting warning (no production code) + bz, err = target.ExecCommand( + "/bin/bash", "-c", fmt.Sprintf(`echo '%s' > %s`, jsonStr, "/params-proposal.json"), + ).CombinedOutput() if err != nil { log.Fatal(err, "\n", string(bz)) } - //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. - // PARAM CHANGE PROPOSAL - cmd := exec.Command("docker", "exec", tr.containerConfig.InstanceName, tr.chainConfigs[action.Chain].BinaryName, + cmd := target.ExecCommand( + tr.chainConfigs[action.Chain].BinaryName, "tx", "gov", "submit-legacy-proposal", "param-change", "/params-proposal.json", @@ -439,6 +464,7 @@ type VoteGovProposalAction struct { func (tr *TestConfig) voteGovProposal( action VoteGovProposalAction, + target ExecutionTarget, verbose bool, ) { var wg sync.WaitGroup @@ -447,8 +473,8 @@ func (tr *TestConfig) voteGovProposal( vote := action.Vote[i] go func(val ValidatorID, vote string) { defer wg.Done() - //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. - bz, err := exec.Command("docker", "exec", tr.containerConfig.InstanceName, tr.chainConfigs[action.Chain].BinaryName, + bz, err := target.ExecCommand( + tr.chainConfigs[action.Chain].BinaryName, "tx", "gov", "vote", fmt.Sprint(action.PropNumber), vote, @@ -482,39 +508,88 @@ type StartConsumerChainAction struct { func (tr *TestConfig) startConsumerChain( action StartConsumerChainAction, + target ExecutionTarget, verbose bool, ) { - //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. - cmd := exec.Command("docker", "exec", tr.containerConfig.InstanceName, tr.chainConfigs[action.ProviderChain].BinaryName, + fmt.Println("Starting consumer chain ", action.ConsumerChain) + consumerGenesis := ".app_state.ccvconsumer = " + tr.getConsumerGenesis(action.ProviderChain, action.ConsumerChain, target) + consumerGenesisChanges := tr.chainConfigs[action.ConsumerChain].GenesisChanges + if consumerGenesisChanges != "" { + consumerGenesis = consumerGenesis + " | " + consumerGenesisChanges + " | " + action.GenesisChanges + } + + tr.startChain(StartChainAction{ + Chain: action.ConsumerChain, + Validators: action.Validators, + GenesisChanges: consumerGenesis, + IsConsumer: true, + }, target, verbose) +} + +// Get consumer genesis from provider +func (tr *TestConfig) getConsumerGenesis(providerChain, consumerChain ChainID, target ExecutionTarget) string { + fmt.Println("Exporting consumer genesis from provider") + providerBinaryName := tr.chainConfigs[providerChain].BinaryName + + cmd := target.ExecCommand( + providerBinaryName, "query", "provider", "consumer-genesis", - string(tr.chainConfigs[action.ConsumerChain].ChainId), + string(tr.chainConfigs[consumerChain].ChainId), - `--node`, tr.getQueryNode(action.ProviderChain), + `--node`, tr.getQueryNode(providerChain), `-o`, `json`, ) - if verbose { - log.Println("startConsumerChain cmd: ", cmd.String()) - } - bz, err := cmd.CombinedOutput() if err != nil { log.Fatal(err, "\n", string(bz)) } - consumerGenesis := ".app_state.ccvconsumer = " + string(bz) - consumerGenesisChanges := tr.chainConfigs[action.ConsumerChain].GenesisChanges - if consumerGenesisChanges != "" { - consumerGenesis = consumerGenesis + " | " + consumerGenesisChanges + " | " + action.GenesisChanges + // only needed when consumer is running v3.3.x and later + if tr.transformGenesis { + return string(tr.transformConsumerGenesis(consumerChain, bz, target)) } + return string(bz) +} - tr.startChain(StartChainAction{ - Chain: action.ConsumerChain, - Validators: action.Validators, - GenesisChanges: consumerGenesis, - IsConsumer: true, - }, verbose) +// Transform consumer genesis content from older version +func (tr *TestConfig) transformConsumerGenesis(consumerChain ChainID, genesis []byte, target ExecutionTarget) []byte { + fmt.Println("Transforming consumer genesis") + fmt.Printf("Original ccv genesis: %s\n", string(genesis)) + + fileName := "consumer_genesis.json" + file, err := os.CreateTemp("", fileName) + if err != nil { + panic(fmt.Sprintf("failed writing ccv consumer file : %v", err)) + } + defer file.Close() + err = os.WriteFile(file.Name(), genesis, 0600) + if err != nil { + log.Fatalf("Failed writing consumer genesis to file: %v", err) + } + + containerInstance := tr.containerConfig.InstanceName + targetFile := fmt.Sprintf("/tmp/%s", fileName) + sourceFile := file.Name() + //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. + cmd := exec.Command("docker", "cp", sourceFile, + fmt.Sprintf("%s:%s", containerInstance, targetFile)) + genesis, err = cmd.CombinedOutput() + if err != nil { + log.Fatal(err, "\n", string(genesis)) + } + + consumerBinaryName := tr.chainConfigs[consumerChain].BinaryName + cmd = target.ExecCommand( + consumerBinaryName, + "genesis", "transform", targetFile) + result, err := cmd.CombinedOutput() + if err != nil { + log.Fatal(err, "CCV consumer genesis transformation failed: %s", string(result)) + } + fmt.Printf("Transformed genesis is: %s\n", string(result)) + return result } type ChangeoverChainAction struct { @@ -526,12 +601,13 @@ type ChangeoverChainAction struct { func (tr TestConfig) changeoverChain( action ChangeoverChainAction, + target ExecutionTarget, verbose bool, ) { // sleep until the consumer chain genesis is ready on consumer time.Sleep(5 * time.Second) - //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. - cmd := exec.Command("docker", "exec", tr.containerConfig.InstanceName, tr.chainConfigs[action.ProviderChain].BinaryName, + cmd := target.ExecCommand( + tr.chainConfigs[action.ProviderChain].BinaryName, "query", "provider", "consumer-genesis", string(tr.chainConfigs[action.SovereignChain].ChainId), @@ -558,11 +634,12 @@ func (tr TestConfig) changeoverChain( tr.startChangeover(ChangeoverChainAction{ Validators: action.Validators, GenesisChanges: consumerGenesis, - }, verbose) + }, target, verbose) } func (tr TestConfig) startChangeover( action ChangeoverChainAction, + target ExecutionTarget, verbose bool, ) { chainConfig := tr.chainConfigs[ChainID("sover")] @@ -611,9 +688,11 @@ func (tr TestConfig) startChangeover( genesisChanges = chainConfig.GenesisChanges } - //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. - cmd := exec.Command("docker", "exec", tr.containerConfig.InstanceName, "/bin/bash", - "/testnet-scripts/start-changeover.sh", chainConfig.UpgradeBinary, string(vals), + isConsumer := true + changeoverScript := target.GetTestScriptPath(isConsumer, "start-changeover.sh") + cmd := target.ExecCommand( + "/bin/bash", + changeoverScript, chainConfig.UpgradeBinary, string(vals), "sover", chainConfig.IpPrefix, genesisChanges, tr.tendermintConfigOverride, ) @@ -653,7 +732,7 @@ type AddChainToRelayerAction struct { const hermesChainConfigTemplate = ` [[chains]] -account_prefix = "cosmos" +account_prefix = "%s" clock_drift = "5s" gas_multiplier = 1.1 grpc_addr = "%s" @@ -687,7 +766,7 @@ const gorelayerChainConfigTemplate = ` "key": "default", "chain-id": "%s", "rpc-addr": "%s", - "account-prefix": "cosmos", + "account-prefix": "%s", "keyring-backend": "test", "gas-adjustment": 1.2, "gas-prices": "0.00stake", @@ -700,17 +779,19 @@ const gorelayerChainConfigTemplate = ` func (tr TestConfig) addChainToRelayer( action AddChainToRelayerAction, + target ExecutionTarget, verbose bool, ) { if !tr.useGorelayer { - tr.addChainToHermes(action, verbose) + tr.addChainToHermes(action, target, verbose) } else { - tr.addChainToGorelayer(action, verbose) + tr.addChainToGorelayer(action, target, verbose) } } func (tr TestConfig) addChainToGorelayer( action AddChainToRelayerAction, + target ExecutionTarget, verbose bool, ) { queryNodeIP := tr.getQueryNodeIP(action.Chain) @@ -720,10 +801,11 @@ func (tr TestConfig) addChainToGorelayer( chainConfig := fmt.Sprintf(gorelayerChainConfigTemplate, ChainId, rpcAddr, + tr.chainConfigs[action.Chain].AccountPrefix, ) - //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. - bz, err := exec.Command("docker", "exec", tr.containerConfig.InstanceName, "rly", "config", "init").CombinedOutput() + bz, err := target.ExecCommand( + "rly", "config", "init").CombinedOutput() if err != nil && !strings.Contains(string(bz), "config already exists") { log.Fatal(err, "\n", string(bz)) } @@ -731,24 +813,22 @@ func (tr TestConfig) addChainToGorelayer( chainConfigFileName := fmt.Sprintf("/root/%s_config.json", ChainId) bashCommand := fmt.Sprintf(`echo '%s' >> %s`, chainConfig, chainConfigFileName) - //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. - bz, err = exec.Command("docker", "exec", tr.containerConfig.InstanceName, "bash", "-c", + bz, err = target.ExecCommand("bash", "-c", bashCommand).CombinedOutput() if err != nil { log.Fatal(err, "\n", string(bz)) } - //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. - addChainCommand := exec.Command("docker", "exec", tr.containerConfig.InstanceName, "rly", "chains", "add", "--file", chainConfigFileName, string(ChainId)) + addChainCommand := target.ExecCommand("rly", "chains", "add", "--file", chainConfigFileName, string(ChainId)) executeCommand(addChainCommand, "add chain") - //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. - keyRestoreCommand := exec.Command("docker", "exec", tr.containerConfig.InstanceName, "rly", "keys", "restore", string(ChainId), "default", tr.validatorConfigs[action.Validator].Mnemonic) + keyRestoreCommand := target.ExecCommand("rly", "keys", "restore", string(ChainId), "default", tr.validatorConfigs[action.Validator].Mnemonic) executeCommand(keyRestoreCommand, "restore keys") } func (tr TestConfig) addChainToHermes( action AddChainToRelayerAction, + target ExecutionTarget, verbose bool, ) { queryNodeIP := tr.getQueryNodeIP(action.Chain) @@ -759,6 +839,7 @@ func (tr TestConfig) addChainToHermes( wsAddr := "ws://" + queryNodeIP + ":26658/websocket" chainConfig := fmt.Sprintf(hermesChainConfigTemplate, + tr.chainConfigs[action.Chain].AccountPrefix, grpcAddr, ChainId, keyName, @@ -769,10 +850,7 @@ func (tr TestConfig) addChainToHermes( bashCommand := fmt.Sprintf(`echo '%s' >> %s`, chainConfig, "/root/.hermes/config.toml") - //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. - bz, err := exec.Command("docker", "exec", tr.containerConfig.InstanceName, "bash", "-c", - bashCommand, - ).CombinedOutput() + bz, err := target.ExecCommand("bash", "-c", bashCommand).CombinedOutput() if err != nil { log.Fatal(err, "\n", string(bz)) } @@ -787,16 +865,12 @@ func (tr TestConfig) addChainToHermes( saveMnemonicCommand := fmt.Sprintf(`echo '%s' > %s`, mnemonic, "/root/.hermes/mnemonic.txt") fmt.Println("Add to hermes", action.Validator) - //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. - bz, err = exec.Command("docker", "exec", tr.containerConfig.InstanceName, "bash", "-c", - saveMnemonicCommand, - ).CombinedOutput() + bz, err = target.ExecCommand("bash", "-c", saveMnemonicCommand).CombinedOutput() if err != nil { log.Fatal(err, "\n", string(bz)) } - //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. - bz, err = exec.Command("docker", "exec", tr.containerConfig.InstanceName, "hermes", + bz, err = target.ExecCommand("hermes", "keys", "add", "--chain", string(tr.chainConfigs[action.Chain].ChainId), "--mnemonic-file", "/root/.hermes/mnemonic.txt", @@ -836,10 +910,11 @@ type AddIbcConnectionAction struct { func (tr TestConfig) addIbcConnection( action AddIbcConnectionAction, + target ExecutionTarget, verbose bool, ) { if !tr.useGorelayer { - tr.addIbcConnectionHermes(action, verbose) + tr.addIbcConnectionHermes(action, target, verbose) } else { tr.addIbcConnectionGorelayer(action, verbose) } @@ -905,10 +980,10 @@ type CreateIbcClientsAction struct { // otherwise, it would use client provided as CLI argument (-a-client) func (tr TestConfig) createIbcClientsHermes( action CreateIbcClientsAction, + target ExecutionTarget, verbose bool, ) { - //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. - cmd := exec.Command("docker", "exec", tr.containerConfig.InstanceName, "hermes", + cmd := target.ExecCommand("hermes", "create", "connection", "--a-chain", string(tr.chainConfigs[action.ChainA].ChainId), "--b-chain", string(tr.chainConfigs[action.ChainB].ChainId), @@ -942,10 +1017,10 @@ func (tr TestConfig) createIbcClientsHermes( func (tr TestConfig) addIbcConnectionHermes( action AddIbcConnectionAction, + target ExecutionTarget, verbose bool, ) { - //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. - cmd := exec.Command("docker", "exec", tr.containerConfig.InstanceName, "hermes", + cmd := target.ExecCommand("hermes", "create", "connection", "--a-chain", string(tr.chainConfigs[action.ChainA].ChainId), "--a-client", "07-tendermint-"+fmt.Sprint(action.ClientA), @@ -992,24 +1067,23 @@ type StartRelayerAction struct{} func (tr TestConfig) startRelayer( action StartRelayerAction, + target ExecutionTarget, verbose bool, ) { if tr.useGorelayer { - tr.startGorelayer(action, verbose) + tr.startGorelayer(action, target, verbose) } else { - tr.startHermes(action, verbose) + tr.startHermes(action, target, verbose) } } func (tr TestConfig) startGorelayer( action StartRelayerAction, + target ExecutionTarget, verbose bool, ) { // gorelayer start is running in detached mode - //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. - cmd := exec.Command("docker", "exec", "-d", tr.containerConfig.InstanceName, "rly", - "start", - ) + cmd := target.ExecDetachedCommand("rly", "start") if err := cmd.Start(); err != nil { log.Fatal(err) @@ -1022,13 +1096,11 @@ func (tr TestConfig) startGorelayer( func (tr TestConfig) startHermes( action StartRelayerAction, + target ExecutionTarget, verbose bool, ) { // hermes start is running in detached mode - //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. - cmd := exec.Command("docker", "exec", "-d", tr.containerConfig.InstanceName, "hermes", - "start", - ) + cmd := target.ExecDetachedCommand("hermes", "start") if err := cmd.Start(); err != nil { log.Fatal(err) @@ -1041,22 +1113,23 @@ func (tr TestConfig) startHermes( func (tr TestConfig) addIbcChannel( action AddIbcChannelAction, + target ExecutionTarget, verbose bool, ) { if tr.useGorelayer { - tr.addIbcChannelGorelayer(action, verbose) + tr.addIbcChannelGorelayer(action, target, verbose) } else { - tr.addIbcChannelHermes(action, verbose) + tr.addIbcChannelHermes(action, target, verbose) } } func (tr TestConfig) addIbcChannelGorelayer( action AddIbcChannelAction, + target ExecutionTarget, verbose bool, ) { pathName := tr.GetPathNameForGorelayer(action.ChainA, action.ChainB) - //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. - cmd := exec.Command("docker", "exec", tr.containerConfig.InstanceName, "rly", + cmd := target.ExecCommand("rly", "transact", "channel", pathName, "--src-port", action.PortA, @@ -1070,6 +1143,7 @@ func (tr TestConfig) addIbcChannelGorelayer( func (tr TestConfig) addIbcChannelHermes( action AddIbcChannelAction, + target ExecutionTarget, verbose bool, ) { // if version is not specified, use the default version when creating ccv connections @@ -1079,8 +1153,7 @@ func (tr TestConfig) addIbcChannelHermes( chanVersion = tr.containerConfig.CcvVersion } - //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. - cmd := exec.Command("docker", "exec", tr.containerConfig.InstanceName, "hermes", + cmd := target.ExecCommand("hermes", "create", "channel", "--a-chain", string(tr.chainConfigs[action.ChainA].ChainId), "--a-connection", "connection-"+fmt.Sprint(action.ConnectionA), @@ -1133,14 +1206,14 @@ type TransferChannelCompleteAction struct { func (tr TestConfig) transferChannelComplete( action TransferChannelCompleteAction, + target ExecutionTarget, verbose bool, ) { if tr.useGorelayer { log.Fatal("transferChannelComplete is not implemented for rly") } - //#nosec G204 -- Bypass linter warning for spawning subprocess with chanOpenTryCmd arguments. - chanOpenTryCmd := exec.Command("docker", "exec", tr.containerConfig.InstanceName, "hermes", + chanOpenTryCmd := target.ExecCommand("hermes", "tx", "chan-open-try", "--dst-chain", string(tr.chainConfigs[action.ChainB].ChainId), "--src-chain", string(tr.chainConfigs[action.ChainA].ChainId), @@ -1151,8 +1224,7 @@ func (tr TestConfig) transferChannelComplete( ) executeCommand(chanOpenTryCmd, "transferChanOpenTry") - //#nosec G204 -- Bypass linter warning for spawning subprocess with chanOpenAckCmd arguments. - chanOpenAckCmd := exec.Command("docker", "exec", tr.containerConfig.InstanceName, "hermes", + chanOpenAckCmd := target.ExecCommand("hermes", "tx", "chan-open-ack", "--dst-chain", string(tr.chainConfigs[action.ChainA].ChainId), "--src-chain", string(tr.chainConfigs[action.ChainB].ChainId), @@ -1165,8 +1237,7 @@ func (tr TestConfig) transferChannelComplete( executeCommand(chanOpenAckCmd, "transferChanOpenAck") - //#nosec G204 -- Bypass linter warning for spawning subprocess with chanOpenConfirmCmd arguments. - chanOpenConfirmCmd := exec.Command("docker", "exec", tr.containerConfig.InstanceName, "hermes", + chanOpenConfirmCmd := target.ExecCommand("hermes", "tx", "chan-open-confirm", "--dst-chain", string(tr.chainConfigs[action.ChainB].ChainId), "--src-chain", string(tr.chainConfigs[action.ChainA].ChainId), @@ -1221,24 +1292,25 @@ type RelayPacketsAction struct { func (tr TestConfig) relayPackets( action RelayPacketsAction, + target ExecutionTarget, verbose bool, ) { if tr.useGorelayer { - tr.relayPacketsGorelayer(action, verbose) + tr.relayPacketsGorelayer(action, target, verbose) } else { - tr.relayPacketsHermes(action, verbose) + tr.relayPacketsHermes(action, target, verbose) } } func (tr TestConfig) relayPacketsGorelayer( action RelayPacketsAction, + target ExecutionTarget, verbose bool, ) { pathName := tr.GetPathNameForGorelayer(action.ChainA, action.ChainB) // rly transact relay-packets [path-name] --channel [channel-id] - //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. - cmd := exec.Command("docker", "exec", tr.containerConfig.InstanceName, "rly", "transact", "flush", + cmd := target.ExecCommand("rly", "transact", "flush", pathName, "channel-"+fmt.Sprint(action.Channel), ) @@ -1256,11 +1328,11 @@ func (tr TestConfig) relayPacketsGorelayer( func (tr TestConfig) relayPacketsHermes( action RelayPacketsAction, + target ExecutionTarget, verbose bool, ) { // hermes clear packets ibc0 transfer channel-13 - //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. - cmd := exec.Command("docker", "exec", tr.containerConfig.InstanceName, "hermes", "clear", "packets", + cmd := target.ExecCommand("hermes", "clear", "packets", "--chain", string(tr.chainConfigs[action.ChainA].ChainId), "--port", action.Port, "--channel", "channel-"+fmt.Sprint(action.Channel), @@ -1287,6 +1359,7 @@ type RelayRewardPacketsToProviderAction struct { func (tr TestConfig) relayRewardPacketsToProvider( action RelayRewardPacketsToProviderAction, + target ExecutionTarget, verbose bool, ) { blockPerDistribution, _ := strconv.ParseUint(strings.Trim(tr.getParam(action.ConsumerChain, Param{Subspace: "ccvconsumer", Key: "BlocksPerDistributionTransmission"}), "\""), 10, 64) @@ -1295,7 +1368,7 @@ func (tr TestConfig) relayRewardPacketsToProvider( tr.waitBlocks(action.ConsumerChain, uint(blockPerDistribution-currentBlock+1), 60*time.Second) } - tr.relayPackets(RelayPacketsAction{ChainA: action.ConsumerChain, ChainB: action.ProviderChain, Port: action.Port, Channel: action.Channel}, verbose) + tr.relayPackets(RelayPacketsAction{ChainA: action.ConsumerChain, ChainB: action.ProviderChain, Port: action.Port, Channel: action.Channel}, target, verbose) tr.waitBlocks(action.ProviderChain, 1, 10*time.Second) } @@ -1308,18 +1381,25 @@ type DelegateTokensAction struct { func (tr TestConfig) delegateTokens( action DelegateTokensAction, + target ExecutionTarget, verbose bool, ) { toValCfg := tr.validatorConfigs[action.To] - delegateAddr := toValCfg.ValoperAddress - if action.Chain != ChainID("provi") && toValCfg.UseConsumerKey { - delegateAddr = toValCfg.ConsumerValoperAddress + validatorAddress := toValCfg.ValoperAddress + if action.Chain != ChainID("provi") { + // use binary with Bech32Prefix set to ConsumerAccountPrefix + if toValCfg.UseConsumerKey { + validatorAddress = toValCfg.ConsumerValoperAddress + } else { + // use the same address as on the provider but with different prefix + validatorAddress = toValCfg.ValoperAddressOnConsumer + } } - //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. - cmd := exec.Command("docker", "exec", tr.containerConfig.InstanceName, tr.chainConfigs[action.Chain].BinaryName, + + cmd := target.ExecCommand(tr.chainConfigs[action.Chain].BinaryName, "tx", "staking", "delegate", - delegateAddr, + validatorAddress, fmt.Sprint(action.Amount)+`stake`, `--from`, `validator`+fmt.Sprint(action.From), @@ -1352,18 +1432,25 @@ type UnbondTokensAction struct { func (tr TestConfig) unbondTokens( action UnbondTokensAction, + target ExecutionTarget, verbose bool, ) { - unbondFrom := tr.validatorConfigs[action.UnbondFrom].ValoperAddress - if tr.validatorConfigs[action.UnbondFrom].UseConsumerKey { - unbondFrom = tr.validatorConfigs[action.UnbondFrom].ConsumerValoperAddress + unbondFromValCfg := tr.validatorConfigs[action.UnbondFrom] + validatorAddress := unbondFromValCfg.ValoperAddress + if action.Chain != ChainID("provi") { + // use binary with Bech32Prefix set to ConsumerAccountPrefix + if unbondFromValCfg.UseConsumerKey { + validatorAddress = unbondFromValCfg.ConsumerValoperAddress + } else { + // use the same address as on the provider but with different prefix + validatorAddress = unbondFromValCfg.ValoperAddressOnConsumer + } } - //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. - cmd := exec.Command("docker", "exec", tr.containerConfig.InstanceName, tr.chainConfigs[action.Chain].BinaryName, + cmd := target.ExecCommand(tr.chainConfigs[action.Chain].BinaryName, "tx", "staking", "unbond", - unbondFrom, + validatorAddress, fmt.Sprint(action.Amount)+`stake`, `--from`, `validator`+fmt.Sprint(action.Sender), @@ -1397,19 +1484,34 @@ type CancelUnbondTokensAction struct { func (tr TestConfig) cancelUnbondTokens( action CancelUnbondTokensAction, + target ExecutionTarget, verbose bool, ) { - validator := tr.validatorConfigs[action.Validator].ValoperAddress - if tr.validatorConfigs[action.Validator].UseConsumerKey { - validator = tr.validatorConfigs[action.Validator].ConsumerValoperAddress + valCfg := tr.validatorConfigs[action.Validator] + delCfg := tr.validatorConfigs[action.Delegator] + validatorAddress := valCfg.ValoperAddress + delegatorAddress := delCfg.DelAddress + if action.Chain != ChainID("provi") { + // use binary with Bech32Prefix set to ConsumerAccountPrefix + if valCfg.UseConsumerKey { + validatorAddress = valCfg.ConsumerValoperAddress + } else { + // use the same address as on the provider but with different prefix + validatorAddress = valCfg.ValoperAddressOnConsumer + } + if delCfg.UseConsumerKey { + delegatorAddress = delCfg.ConsumerDelAddress + } else { + // use the same address as on the provider but with different prefix + delegatorAddress = delCfg.DelAddressOnConsumer + } } // get creation-height from state - //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. - cmd := exec.Command("docker", "exec", tr.containerConfig.InstanceName, tr.chainConfigs[action.Chain].BinaryName, + cmd := target.ExecCommand(tr.chainConfigs[action.Chain].BinaryName, "q", "staking", "unbonding-delegation", - tr.validatorConfigs[action.Delegator].DelAddress, - validator, + delegatorAddress, + validatorAddress, `--home`, tr.getValidatorHome(action.Chain, action.Delegator), `--node`, tr.getValidatorNode(action.Chain, action.Delegator), `-o`, `json`, @@ -1427,10 +1529,9 @@ func (tr TestConfig) cancelUnbondTokens( log.Fatal("invalid creation height") } - //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. - cmd = exec.Command("docker", "exec", tr.containerConfig.InstanceName, tr.chainConfigs[action.Chain].BinaryName, + cmd = target.ExecCommand(tr.chainConfigs[action.Chain].BinaryName, "tx", "staking", "cancel-unbond", - validator, + validatorAddress, fmt.Sprint(action.Amount)+`stake`, fmt.Sprint(creationHeight), `--from`, `validator`+fmt.Sprint(action.Delegator), @@ -1464,22 +1565,28 @@ type RedelegateTokensAction struct { Amount uint } -func (tr TestConfig) redelegateTokens(action RedelegateTokensAction, verbose bool) { +func (tr TestConfig) redelegateTokens(action RedelegateTokensAction, target ExecutionTarget, verbose bool) { srcCfg := tr.validatorConfigs[action.Src] dstCfg := tr.validatorConfigs[action.Dst] - redelegateSrc := srcCfg.ValoperAddress - if action.Chain != ChainID("provi") && srcCfg.UseConsumerKey { - redelegateSrc = srcCfg.ConsumerValoperAddress - } - redelegateDst := dstCfg.ValoperAddress - if action.Chain != ChainID("provi") && dstCfg.UseConsumerKey { - redelegateDst = dstCfg.ConsumerValoperAddress + if action.Chain != ChainID("provi") { + // use binary with Bech32Prefix set to ConsumerAccountPrefix + if srcCfg.UseConsumerKey { + redelegateSrc = srcCfg.ConsumerValoperAddress + } else { + // use the same address as on the provider but with different prefix + redelegateSrc = srcCfg.ValoperAddressOnConsumer + } + if dstCfg.UseConsumerKey { + redelegateDst = dstCfg.ConsumerValoperAddress + } else { + // use the same address as on the provider but with different prefix + redelegateDst = dstCfg.ValoperAddressOnConsumer + } } - //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. - cmd := exec.Command("docker", "exec", - tr.containerConfig.InstanceName, + + cmd := target.ExecCommand( tr.chainConfigs[action.Chain].BinaryName, "tx", "staking", "redelegate", @@ -1528,17 +1635,17 @@ func (tr TestConfig) getValidatorKeyAddressFromString(keystring string) string { return key.Address } -func (tr TestConfig) invokeDowntimeSlash(action DowntimeSlashAction, verbose bool) { +func (tr TestConfig) invokeDowntimeSlash(action DowntimeSlashAction, target ExecutionTarget, verbose bool) { // Bring validator down - tr.setValidatorDowntime(action.Chain, action.Validator, true, verbose) + tr.setValidatorDowntime(action.Chain, action.Validator, true, target, verbose) // Wait appropriate amount of blocks for validator to be slashed tr.waitBlocks(action.Chain, 10, 3*time.Minute) // Bring validator back up - tr.setValidatorDowntime(action.Chain, action.Validator, false, verbose) + tr.setValidatorDowntime(action.Chain, action.Validator, false, target, verbose) } // Sets validator downtime by setting the virtual ethernet interface of a node to "up" or "down" -func (tr TestConfig) setValidatorDowntime(chain ChainID, validator ValidatorID, down, verbose bool) { +func (tr TestConfig) setValidatorDowntime(chain ChainID, validator ValidatorID, down bool, target ExecutionTarget, verbose bool) { var lastArg string if down { lastArg = "down" @@ -1559,11 +1666,7 @@ func (tr TestConfig) setValidatorDowntime(chain ChainID, validator ValidatorID, return } - //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. - cmd := exec.Command( - "docker", - "exec", - tr.containerConfig.InstanceName, + cmd := target.ExecCommand( "ip", "link", "set", @@ -1603,13 +1706,11 @@ type UnjailValidatorAction struct { } // Sends an unjail transaction to the provider chain -func (tr TestConfig) unjailValidator(action UnjailValidatorAction, verbose bool) { +func (tr TestConfig) unjailValidator(action UnjailValidatorAction, target ExecutionTarget, verbose bool) { // wait until downtime_jail_duration has elapsed, to make sure the validator can be unjailed tr.WaitTime(61 * time.Second) - //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. - cmd := exec.Command("docker", "exec", - tr.containerConfig.InstanceName, + cmd := target.ExecCommand( tr.chainConfigs[action.Provider].BinaryName, "tx", "slashing", "unjail", // Validator is sender here @@ -1644,6 +1745,7 @@ type RegisterRepresentativeAction struct { func (tr TestConfig) registerRepresentative( action RegisterRepresentativeAction, + target ExecutionTarget, verbose bool, ) { var wg sync.WaitGroup @@ -1653,8 +1755,7 @@ func (tr TestConfig) registerRepresentative( go func(val ValidatorID, stake uint) { defer wg.Done() - //#nosec G204 -- Bypass linter warning for spawning subprocess with pubKeycmd arguments. - pubKeycmd := exec.Command("docker", "exec", tr.containerConfig.InstanceName, tr.chainConfigs[action.Chain].BinaryName, + pubKeycmd := target.ExecCommand(tr.chainConfigs[action.Chain].BinaryName, "tendermint", "show-validator", `--home`, tr.getValidatorHome(action.Chain, val), ) @@ -1664,8 +1765,7 @@ func (tr TestConfig) registerRepresentative( log.Fatal(err, "\n", string(bzPubKey)) } - //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. - bz, err := exec.Command("docker", "exec", tr.containerConfig.InstanceName, tr.chainConfigs[action.Chain].BinaryName, + bz, err := target.ExecCommand(tr.chainConfigs[action.Chain].BinaryName, "tx", "staking", "create-validator", `--amount`, fmt.Sprint(stake)+"stake", `--pubkey`, string(bzPubKey), @@ -1699,7 +1799,7 @@ type SubmitChangeRewardDenomsProposalAction struct { From ValidatorID } -func (tr TestConfig) submitChangeRewardDenomsProposal(action SubmitChangeRewardDenomsProposalAction, verbose bool) { +func (tr TestConfig) submitChangeRewardDenomsProposal(action SubmitChangeRewardDenomsProposalAction, target ExecutionTarget, verbose bool) { providerChain := tr.chainConfigs[ChainID("provi")] prop := client.ChangeRewardDenomsProposalJSON{ @@ -1723,17 +1823,15 @@ func (tr TestConfig) submitChangeRewardDenomsProposal(action SubmitChangeRewardD log.Fatal("prop json contains single quote") } - //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. - bz, err = exec.Command("docker", "exec", tr.containerConfig.InstanceName, + bz, err = target.ExecCommand( "/bin/bash", "-c", fmt.Sprintf(`echo '%s' > %s`, jsonStr, "/change-reward-denoms-proposal.json")).CombinedOutput() if err != nil { log.Fatal(err, "\n", string(bz)) } - //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. // CHANGE REWARDS DENOM PROPOSAL - bz, err = exec.Command("docker", "exec", tr.containerConfig.InstanceName, providerChain.BinaryName, + bz, err = target.ExecCommand(providerChain.BinaryName, "tx", "gov", "submit-legacy-proposal", "change-reward-denoms", "/change-reward-denoms-proposal.json", `--from`, `validator`+fmt.Sprint(action.From), `--chain-id`, string(providerChain.ChainId), @@ -1770,13 +1868,14 @@ type DoublesignSlashAction struct { func (tr TestConfig) invokeDoublesignSlash( action DoublesignSlashAction, + target ExecutionTarget, verbose bool, ) { if !tr.useCometmock { chainConfig := tr.chainConfigs[action.Chain] - //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. - bz, err := exec.Command("docker", "exec", tr.containerConfig.InstanceName, "/bin/bash", - "/testnet-scripts/cause-doublesign.sh", chainConfig.BinaryName, string(action.Validator), + doubleSignScript := target.GetTestScriptPath(false, "cause-doublesign.sh") + bz, err := target.ExecCommand("/bin/bash", + doubleSignScript, chainConfig.BinaryName, string(action.Validator), string(chainConfig.ChainId), chainConfig.IpPrefix).CombinedOutput() if err != nil { log.Fatal(err, "\n", string(bz)) @@ -1882,7 +1981,7 @@ type AssignConsumerPubKeyAction struct { ExpectedError string } -func (tr TestConfig) assignConsumerPubKey(action AssignConsumerPubKeyAction, verbose bool) { +func (tr TestConfig) assignConsumerPubKey(action AssignConsumerPubKeyAction, target ExecutionTarget, verbose bool) { valCfg := tr.validatorConfigs[action.Validator] // Note: to get error response reported back from this command '--gas auto' needs to be set. @@ -1903,9 +2002,7 @@ func (tr TestConfig) assignConsumerPubKey(action AssignConsumerPubKeyAction, ver gas, ) - //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. - cmd := exec.Command("docker", "exec", - tr.containerConfig.InstanceName, + cmd := target.ExecCommand( "/bin/bash", "-c", assignKey, ) @@ -1932,9 +2029,10 @@ func (tr TestConfig) assignConsumerPubKey(action AssignConsumerPubKeyAction, ver // node was started with provider key // we swap the nodes's keys for consumer keys and restart it if action.ReconfigureNode { - //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. - configureNodeCmd := exec.Command("docker", "exec", tr.containerConfig.InstanceName, "/bin/bash", - "/testnet-scripts/reconfigure-node.sh", tr.chainConfigs[action.Chain].BinaryName, + isConsumer := tr.chainConfigs[action.Chain].BinaryName != "interchain-security-pd" + reconfigureScript := target.GetTestScriptPath(isConsumer, "reconfigure-node.sh") + configureNodeCmd := target.ExecCommand("/bin/bash", + reconfigureScript, tr.chainConfigs[action.Chain].BinaryName, string(action.Validator), string(action.Chain), tr.chainConfigs[action.Chain].IpPrefix, valCfg.IpSuffix, valCfg.ConsumerMnemonic, valCfg.ConsumerPrivValidatorKey, @@ -2054,12 +2152,12 @@ type StartConsumerEvidenceDetectorAction struct { func (tc TestConfig) startConsumerEvidenceDetector( action StartConsumerEvidenceDetectorAction, + target ExecutionTarget, verbose bool, ) { chainConfig := tc.chainConfigs[action.Chain] // run in detached mode so it will keep running in the background - //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. - bz, err := exec.Command("docker", "exec", "-d", tc.containerConfig.InstanceName, + bz, err := target.ExecDetachedCommand( "hermes", "evidence", "--chain", string(chainConfig.ChainId)).CombinedOutput() if err != nil { log.Fatal(err, "\n", string(bz)) diff --git a/tests/e2e/actions_sovereign_chain.go b/tests/e2e/actions_sovereign_chain.go index 9a5bb0f182..187c9036cc 100644 --- a/tests/e2e/actions_sovereign_chain.go +++ b/tests/e2e/actions_sovereign_chain.go @@ -5,7 +5,6 @@ import ( "encoding/json" "fmt" "log" - "os/exec" "time" ) @@ -20,6 +19,7 @@ type StartSovereignChainAction struct { // upgrades are simpler with a single validator node since only one node needs to be upgraded func (tr TestConfig) startSovereignChain( action StartSovereignChainAction, + target ExecutionTarget, verbose bool, ) { chainConfig := tr.chainConfigs["sover"] @@ -68,12 +68,11 @@ func (tr TestConfig) startSovereignChain( genesisChanges = chainConfig.GenesisChanges } - //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. - cmd := exec.Command("docker", "exec", tr.containerConfig.InstanceName, "/bin/bash", - "/testnet-scripts/start-sovereign.sh", chainConfig.BinaryName, string(vals), + isConsumer := chainConfig.BinaryName != "interchain-security-pd" + testScriptPath := target.GetTestScriptPath(isConsumer, "start-sovereign.sh") + cmd := target.ExecCommand("/bin/bash", testScriptPath, string(vals), string(chainConfig.ChainId), chainConfig.IpPrefix, genesisChanges, - tr.tendermintConfigOverride, - ) + tr.tendermintConfigOverride) cmdReader, err := cmd.StdoutPipe() if err != nil { @@ -102,7 +101,7 @@ func (tr TestConfig) startSovereignChain( tr.addChainToRelayer(AddChainToRelayerAction{ Chain: action.Chain, Validator: action.Validators[0].Id, - }, verbose) + }, target, verbose) } type LegacyUpgradeProposalAction struct { @@ -112,7 +111,7 @@ type LegacyUpgradeProposalAction struct { UpgradeHeight uint64 } -func (tr *TestConfig) submitLegacyUpgradeProposal(action LegacyUpgradeProposalAction, verbose bool) { +func (tr *TestConfig) submitLegacyUpgradeProposal(action LegacyUpgradeProposalAction, target ExecutionTarget, verbose bool) { submit := fmt.Sprintf( `%s tx gov submit-legacy-proposal software-upgrade %s \ --title %s \ @@ -137,12 +136,7 @@ func (tr *TestConfig) submitLegacyUpgradeProposal(action LegacyUpgradeProposalAc tr.getValidatorHome(ChainID("sover"), action.Proposer), tr.getValidatorNode(ChainID("sover"), action.Proposer), ) - //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. - cmd := exec.Command("docker", "exec", - tr.containerConfig.InstanceName, - "/bin/bash", "-c", - submit, - ) + cmd := target.ExecCommand("/bin/bash", "-c", submit) if verbose { fmt.Println("submitUpgradeProposal cmd:", cmd.String()) diff --git a/tests/e2e/builder.go b/tests/e2e/builder.go new file mode 100644 index 0000000000..63e9961661 --- /dev/null +++ b/tests/e2e/builder.go @@ -0,0 +1,128 @@ +package main + +import ( + "bytes" + "fmt" + "log" + "os" + "os/exec" + "strings" +) + +// setupWorkSpace checks out given revision in a tmp directory +// and returns the path where the workspace is located +func setupWorkSpace(revision string) (string, error) { + workSpace, err := os.MkdirTemp(os.TempDir(), "e2eWorkTree_") + if err != nil { + return "", fmt.Errorf("error creating temp directory %v", err) + } + + fmt.Printf("Setting up worktree in '%s'\n", workSpace) + + //#nosec G204 -- Bypass linter warning for spawning subprocess with variable + cmd := exec.Command("git", "worktree", "add", + "--force", "--checkout", workSpace, revision) + out, err := cmd.CombinedOutput() + fmt.Println("Running: ", cmd.String()) + if err != nil { + log.Printf("Error creating worktree (%v): %s", err, string(out)) + return "", err + } + return workSpace, nil +} + +// cleanupWorkSpace removes the created worktree +func cleanupWorkSpace(workSpacePath string) error { + cmd := exec.Command("git", "worktree", "remove", workSpacePath) + cmd.Stderr = cmd.Stdout + if err := cmd.Start(); err != nil { + log.Printf("Failed removing git worktree '%s' used for docker image creation", workSpacePath) + return err + } + return cmd.Wait() +} + +// Check if docker is running +func dockerIsUp() bool { + cmd := exec.Command("docker", "info") + var errbuf bytes.Buffer + cmd.Stderr = &errbuf + if err := cmd.Run(); err != nil { + log.Printf("Docker engine is not running (%v): %s", err, errbuf.String()) + return false + } + return true +} + +// Build docker image of ICS for a given revision +func buildDockerImage(imageName, revision string, targetCfg TargetConfig, noCache bool) error { + fmt.Printf("Building ICS %s image %s\n", revision, imageName) + if !dockerIsUp() { + return fmt.Errorf("docker engine is not running") + } + + workSpace := "./" + var err error = nil + // if a revision is provided the related version will be staged + // on a separate worktree (note: revision should _not_ be checked out already elsewhere). + // which will be deleted after image creation + if revision != "" { + workSpace, err = setupWorkSpace(revision) + if err != nil { + return err + } + defer cleanupWorkSpace(workSpace) + } + + // Check if workspace exists + _, err = os.Stat(workSpace) + if err != nil { + log.Fatalf("image build failed. invalid workspace: %v", err) + } + + // Use custom SDK setup if required + sdkPath := strings.Join([]string{workSpace, "cosmos-sdk"}, string(os.PathSeparator)) + err = os.RemoveAll(sdkPath) //delete old SDK directory + if err != nil { + return fmt.Errorf("error deleting SDK directory from workspace: %v", err) + } + if targetCfg.localSdkPath != "" { + fmt.Printf("Using local SDK version from %s\n", targetCfg.localSdkPath) + //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. + cmd := exec.Command("cp", "-n", "-r", targetCfg.localSdkPath, sdkPath) + out, err := cmd.CombinedOutput() + if err != nil { + log.Printf("Error running command %v: %s", cmd, string(out)) + return fmt.Errorf("error setting up local SDK: %v, %s", err, string(out)) + } + } else { + fmt.Println("Using default SDK version") + } + + dockerFile := "Dockerfile" + args := []string{"build", "-t", imageName} + if noCache { + args = append(args, "--no-cache") + } + + if targetCfg.useGaia && targetCfg.gaiaTag != "" { + dockerFile = "Dockerfile.gaia" + args = append(args, "--build-arg", fmt.Sprintf("USE_GAIA_TAG=%s", targetCfg.gaiaTag)) + } + args = append(args, "-f", dockerFile, "./") + + //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. + cmd := exec.Command("docker", args...) + cmd.Dir = workSpace + out, err := cmd.CombinedOutput() + if err != nil && !noCache { + // Retry image creation from pristine state by enforcing --no-cache + log.Printf("Image creation failed '%v'. Re-trying without cache!", err) + return buildDockerImage(imageName, revision, targetCfg, true) + } + if err != nil { + return fmt.Errorf("building docker image failed running '%v' (%v): %s", cmd, err, out) + } + + return err +} diff --git a/tests/e2e/config.go b/tests/e2e/config.go index 311ee67c89..307f61d62c 100644 --- a/tests/e2e/config.go +++ b/tests/e2e/config.go @@ -6,6 +6,11 @@ import ( "time" ) +var ( + ProviderAccountPrefix = "cosmos" + ConsumerAccountPrefix = "consumer" +) + // TODO: Determine if user defined type (wrapping a primitive string) is desired in long run type ( ChainID string @@ -15,10 +20,27 @@ type ( // Attributes that are unique to a validator. Allows us to map (part of) // the set of strings defined above to a set of viable validators type ValidatorConfig struct { - Mnemonic string - DelAddress string - ValoperAddress string - ValconsAddress string + // Seed phrase to generate a secp256k1 key used by the validator on the provider + Mnemonic string + // Validator account address on provider marshaled to string using Bech32 + // with Bech32Prefix = ProviderAccountPrefix + DelAddress string + // Validator account address on provider marshaled to string using Bech32 + // with Bech32Prefix = ConsumerAccountPrefix + DelAddressOnConsumer string + // Validator operator address on provider marshaled to string using Bech32 + // with Bech32Prefix = ProviderAccountPrefix + ValoperAddress string + // Validator operator address on provider marshaled to string using Bech32 + // with Bech32Prefix = ConsumerAccountPrefix + ValoperAddressOnConsumer string + // Validator consensus address on provider marshaled to string using Bech32 + // with Bech32Prefix = ProviderAccountPrefix. It matches the PrivValidatorKey below. + ValconsAddress string + // Validator consensus address on provider marshaled to string using Bech32 + // with Bech32Prefix = ConsumerAccountPrefix. + ValconsAddressOnConsumer string + // Key used for consensus on provider PrivValidatorKey string NodeKey string // Must be an integer greater than 0 and less than 253 @@ -26,11 +48,29 @@ type ValidatorConfig struct { // consumer chain key assignment data // keys are used on a new node - ConsumerMnemonic string - ConsumerDelAddress string - ConsumerValoperAddress string - ConsumerValconsAddress string - ConsumerValPubKey string + + // Seed phrase to generate a secp256k1 key used by the validator on the consumer + ConsumerMnemonic string + // Validator account address on consumer marshaled to string using Bech32 + // with Bech32Prefix = ConsumerAccountPrefix + ConsumerDelAddress string + // Validator account address on consumer marshaled to string using Bech32 + // with Bech32Prefix = ProviderAccountPrefix + ConsumerDelAddressOnProvider string + // Validator operator address on consumer marshaled to string using Bech32 + // with Bech32Prefix = ConsumerAccountPrefix + ConsumerValoperAddress string + // Validator operator address on consumer marshaled to string using Bech32 + // with Bech32Prefix = ProviderAccountPrefix + ConsumerValoperAddressOnProvider string + // Validator consensus address on consumer marshaled to string using Bech32 + // with Bech32Prefix = ConsumerAccountPrefix. It matches the PrivValidatorKey below. + ConsumerValconsAddress string + // Validator consensus address on consumer marshaled to string using Bech32 + // with Bech32Prefix = ProviderAccountPrefix. + ConsumerValconsAddressOnProvider string + ConsumerValPubKey string + // Key used for consensus on consumer ConsumerPrivValidatorKey string ConsumerNodeKey string UseConsumerKey bool // if true the validator node will start with consumer key @@ -40,6 +80,8 @@ type ValidatorConfig struct { // the set of strings defined above to a set of viable chains type ChainConfig struct { ChainId ChainID + // The account prefix configured on the chain. For example, on the Hub, this is "cosmos" + AccountPrefix string // Must be unique per chain IpPrefix string VotingWaitTime uint @@ -59,7 +101,14 @@ type ContainerConfig struct { Now time.Time } -// TODO: Split out TestConfig and system wide config like localsdkpath +type TargetConfig struct { + gaiaTag string + localSdkPath string + useGaia bool + providerVersion string + consumerVersion string +} + type TestConfig struct { // These are the non altered values during a typical test run, where multiple test runs can exist // to validate different action sequences and corresponding state checks. @@ -71,17 +120,14 @@ type TestConfig struct { // having shorter timeout_commit reduces the test runtime because blocks are produced faster // lengthening the timeout_commit increases the test runtime because blocks are produced slower but the test is more reliable tendermintConfigOverride string - localSdkPath string - useGaia bool useCometmock bool // if false, nodes run CometBFT useGorelayer bool // if false, Hermes is used as the relayer - gaiaTag string // chains which are running, i.e. producing blocks, at the moment runningChains map[ChainID]bool // Used with CometMock. The time by which chains have been advanced. Used to keep chains in sync: when a new chain is started, advance its time by this value to keep chains in sync. - timeOffset time.Duration - - name string + timeOffset time.Duration + transformGenesis bool + name string } // Initialize initializes the TestConfig instance by setting the runningChains field to an empty map. @@ -92,61 +138,79 @@ func (tr *TestConfig) Initialize() { func getDefaultValidators() map[ValidatorID]ValidatorConfig { return map[ValidatorID]ValidatorConfig{ ValidatorID("alice"): { - Mnemonic: "pave immune ethics wrap gain ceiling always holiday employ earth tumble real ice engage false unable carbon equal fresh sick tattoo nature pupil nuclear", - DelAddress: "cosmos19pe9pg5dv9k5fzgzmsrgnw9rl9asf7ddwhu7lm", - ValoperAddress: "cosmosvaloper19pe9pg5dv9k5fzgzmsrgnw9rl9asf7ddtrgtng", - ValconsAddress: "cosmosvalcons1qmq08eruchr5sf5s3rwz7djpr5a25f7xw4mceq", - PrivValidatorKey: `{"address":"06C0F3E47CC5C748269088DC2F36411D3AAA27C6","pub_key":{"type":"tendermint/PubKeyEd25519","value":"RrclQz9bIhkIy/gfL485g3PYMeiIku4qeo495787X10="},"priv_key":{"type":"tendermint/PrivKeyEd25519","value":"uX+ZpDMg89a6gtqs/+MQpCTSqlkZ0nJQJOhLlCJvwvdGtyVDP1siGQjL+B8vjzmDc9gx6IiS7ip6jj3nvztfXQ=="}}`, - NodeKey: `{"priv_key":{"type":"tendermint/PrivKeyEd25519","value":"fjw4/DAhyRPnwKgXns5SV7QfswRSXMWJpHS7TyULDmJ8ofUc5poQP8dgr8bZRbCV5RV8cPqDq3FPdqwpmUbmdA=="}}`, - IpSuffix: "4", + Mnemonic: "pave immune ethics wrap gain ceiling always holiday employ earth tumble real ice engage false unable carbon equal fresh sick tattoo nature pupil nuclear", + DelAddress: "cosmos19pe9pg5dv9k5fzgzmsrgnw9rl9asf7ddwhu7lm", + DelAddressOnConsumer: "consumer19pe9pg5dv9k5fzgzmsrgnw9rl9asf7ddtz33vu", + ValoperAddress: "cosmosvaloper19pe9pg5dv9k5fzgzmsrgnw9rl9asf7ddtrgtng", + ValoperAddressOnConsumer: "consumervaloper19pe9pg5dv9k5fzgzmsrgnw9rl9asf7ddy6jwzg", + ValconsAddress: "cosmosvalcons1qmq08eruchr5sf5s3rwz7djpr5a25f7xw4mceq", + ValconsAddressOnConsumer: "consumervalcons1qmq08eruchr5sf5s3rwz7djpr5a25f7xpvpagq", + PrivValidatorKey: `{"address":"06C0F3E47CC5C748269088DC2F36411D3AAA27C6","pub_key":{"type":"tendermint/PubKeyEd25519","value":"RrclQz9bIhkIy/gfL485g3PYMeiIku4qeo495787X10="},"priv_key":{"type":"tendermint/PrivKeyEd25519","value":"uX+ZpDMg89a6gtqs/+MQpCTSqlkZ0nJQJOhLlCJvwvdGtyVDP1siGQjL+B8vjzmDc9gx6IiS7ip6jj3nvztfXQ=="}}`, + NodeKey: `{"priv_key":{"type":"tendermint/PrivKeyEd25519","value":"fjw4/DAhyRPnwKgXns5SV7QfswRSXMWJpHS7TyULDmJ8ofUc5poQP8dgr8bZRbCV5RV8cPqDq3FPdqwpmUbmdA=="}}`, + IpSuffix: "4", // consumer chain assigned key - ConsumerMnemonic: "exile install vapor thing little toss immune notable lounge december final easy strike title end program interest quote cloth forget forward job october twenty", - ConsumerDelAddress: "cosmos1eeeggku6dzk3mv7wph3zq035rhtd890sjswszd", - ConsumerValoperAddress: "cosmosvaloper1eeeggku6dzk3mv7wph3zq035rhtd890shy69w7", - ConsumerValconsAddress: "cosmosvalcons1muys5jyqk4xd27e208nym85kn0t4zjcfeu63fe", - ConsumerValPubKey: `{"@type":"/cosmos.crypto.ed25519.PubKey","key":"ujY14AgopV907IYgPAk/5x8c9267S4fQf89nyeCPTes="}`, - ConsumerPrivValidatorKey: `{"address":"DF090A4880B54CD57B2A79E64D9E969BD7514B09","pub_key":{"type":"tendermint/PubKeyEd25519","value":"ujY14AgopV907IYgPAk/5x8c9267S4fQf89nyeCPTes="},"priv_key":{"type":"tendermint/PrivKeyEd25519","value":"TRJgf7lkTjs/sj43pyweEOanyV7H7fhnVivOi0A4yjW6NjXgCCilX3TshiA8CT/nHxz3brtLh9B/z2fJ4I9N6w=="}}`, - ConsumerNodeKey: `{"priv_key":{"type":"tendermint/PrivKeyEd25519","value":"F966RL9pi20aXRzEBe4D0xRQJtZt696Xxz44XUON52cFc83FMn1WXJbP6arvA2JPyn2LA3DLKCFHSgALrCGXGA=="}}`, - UseConsumerKey: false, + ConsumerMnemonic: "exile install vapor thing little toss immune notable lounge december final easy strike title end program interest quote cloth forget forward job october twenty", + ConsumerDelAddress: "consumer1eeeggku6dzk3mv7wph3zq035rhtd890sh9rl32", + ConsumerDelAddressOnProvider: "cosmos1eeeggku6dzk3mv7wph3zq035rhtd890sjswszd", + ConsumerValoperAddress: "consumervaloper1eeeggku6dzk3mv7wph3zq035rhtd890scaqql7", + ConsumerValoperAddressOnProvider: "cosmosvaloper1eeeggku6dzk3mv7wph3zq035rhtd890shy69w7", + ConsumerValconsAddress: "consumervalcons1muys5jyqk4xd27e208nym85kn0t4zjcfk9q5ce", + ConsumerValconsAddressOnProvider: "cosmosvalcons1muys5jyqk4xd27e208nym85kn0t4zjcfeu63fe", + ConsumerValPubKey: `{"@type":"/cosmos.crypto.ed25519.PubKey","key":"ujY14AgopV907IYgPAk/5x8c9267S4fQf89nyeCPTes="}`, + ConsumerPrivValidatorKey: `{"address":"DF090A4880B54CD57B2A79E64D9E969BD7514B09","pub_key":{"type":"tendermint/PubKeyEd25519","value":"ujY14AgopV907IYgPAk/5x8c9267S4fQf89nyeCPTes="},"priv_key":{"type":"tendermint/PrivKeyEd25519","value":"TRJgf7lkTjs/sj43pyweEOanyV7H7fhnVivOi0A4yjW6NjXgCCilX3TshiA8CT/nHxz3brtLh9B/z2fJ4I9N6w=="}}`, + ConsumerNodeKey: `{"priv_key":{"type":"tendermint/PrivKeyEd25519","value":"F966RL9pi20aXRzEBe4D0xRQJtZt696Xxz44XUON52cFc83FMn1WXJbP6arvA2JPyn2LA3DLKCFHSgALrCGXGA=="}}`, + UseConsumerKey: false, }, ValidatorID("bob"): { - Mnemonic: "glass trip produce surprise diamond spin excess gaze wash drum human solve dress minor artefact canoe hard ivory orange dinner hybrid moral potato jewel", - DelAddress: "cosmos1dkas8mu4kyhl5jrh4nzvm65qz588hy9qcz08la", - ValoperAddress: "cosmosvaloper1dkas8mu4kyhl5jrh4nzvm65qz588hy9qakmjnw", - ValconsAddress: "cosmosvalcons1nx7n5uh0ztxsynn4sje6eyq2ud6rc6klc96w39", - PrivValidatorKey: `{"address":"99BD3A72EF12CD024E7584B3AC900AE3743C6ADF","pub_key":{"type":"tendermint/PubKeyEd25519","value":"mAN6RXYxSM4MNGSIriYiS7pHuwAcOHDQAy9/wnlSzOI="},"priv_key":{"type":"tendermint/PrivKeyEd25519","value":"QePcwfWtOavNK7pBJrtoLMzarHKn6iBWfWPFeyV+IdmYA3pFdjFIzgw0ZIiuJiJLuke7ABw4cNADL3/CeVLM4g=="}}`, - NodeKey: `{"priv_key":{"type":"tendermint/PrivKeyEd25519","value":"TQ4vHcO/vKdzGtWpelkX53WdMQd4kTsWGFrdcatdXFvWyO215Rewn5IRP0FszPLWr2DqPzmuH8WvxYGk5aeOXw=="}}`, - IpSuffix: "5", + Mnemonic: "glass trip produce surprise diamond spin excess gaze wash drum human solve dress minor artefact canoe hard ivory orange dinner hybrid moral potato jewel", + DelAddress: "cosmos1dkas8mu4kyhl5jrh4nzvm65qz588hy9qcz08la", + DelAddressOnConsumer: "consumer1dkas8mu4kyhl5jrh4nzvm65qz588hy9qahzgv6", + ValoperAddress: "cosmosvaloper1dkas8mu4kyhl5jrh4nzvm65qz588hy9qakmjnw", + ValoperAddressOnConsumer: "consumervaloper1dkas8mu4kyhl5jrh4nzvm65qz588hy9qj0phzw", + ValconsAddress: "cosmosvalcons1nx7n5uh0ztxsynn4sje6eyq2ud6rc6klc96w39", + ValconsAddressOnConsumer: "consumervalcons1nx7n5uh0ztxsynn4sje6eyq2ud6rc6klhuqtq9", + PrivValidatorKey: `{"address":"99BD3A72EF12CD024E7584B3AC900AE3743C6ADF","pub_key":{"type":"tendermint/PubKeyEd25519","value":"mAN6RXYxSM4MNGSIriYiS7pHuwAcOHDQAy9/wnlSzOI="},"priv_key":{"type":"tendermint/PrivKeyEd25519","value":"QePcwfWtOavNK7pBJrtoLMzarHKn6iBWfWPFeyV+IdmYA3pFdjFIzgw0ZIiuJiJLuke7ABw4cNADL3/CeVLM4g=="}}`, + NodeKey: `{"priv_key":{"type":"tendermint/PrivKeyEd25519","value":"TQ4vHcO/vKdzGtWpelkX53WdMQd4kTsWGFrdcatdXFvWyO215Rewn5IRP0FszPLWr2DqPzmuH8WvxYGk5aeOXw=="}}`, + IpSuffix: "5", // consumer chain assigned key - ConsumerMnemonic: "grunt list hour endless observe better spoil penalty lab duck only layer vague fantasy satoshi record demise topple space shaft solar practice donor sphere", - ConsumerDelAddress: "cosmos1q90l6j6lzzgt460ehjj56azknlt5yrd4s38n97", - ConsumerValoperAddress: "cosmosvaloper1q90l6j6lzzgt460ehjj56azknlt5yrd449nxfd", - ConsumerValconsAddress: "cosmosvalcons1uuec3cjxajv5te08p220usrjhkfhg9wyvqn0tm", - ConsumerValPubKey: `{"@type":"/cosmos.crypto.ed25519.PubKey","key":"QlG+iYe6AyYpvY1z9RNJKCVlH14Q/qSz4EjGdGCru3o="}`, - ConsumerPrivValidatorKey: `{"address":"E73388E246EC9945E5E70A94FE4072BD937415C4","pub_key":{"type":"tendermint/PubKeyEd25519","value":"QlG+iYe6AyYpvY1z9RNJKCVlH14Q/qSz4EjGdGCru3o="},"priv_key":{"type":"tendermint/PrivKeyEd25519","value":"OFR4w+FC6EMw5fAGTrHVexyPrjzQ7QfqgZOMgVf0izlCUb6Jh7oDJim9jXP1E0koJWUfXhD+pLPgSMZ0YKu7eg=="}}`, - ConsumerNodeKey: `{"priv_key":{"type":"tendermint/PrivKeyEd25519","value":"uhPCqnL2KE8m/8OFNLQ5bN3CJr6mds+xfBi0E4umT/s2uWiJhet+vbYx88DHSdof3gGFNTIzAIxSppscBKX96w=="}}`, - UseConsumerKey: false, + ConsumerMnemonic: "grunt list hour endless observe better spoil penalty lab duck only layer vague fantasy satoshi record demise topple space shaft solar practice donor sphere", + ConsumerDelAddress: "consumer1q90l6j6lzzgt460ehjj56azknlt5yrd44y2uke", + ConsumerDelAddressOnProvider: "cosmos1q90l6j6lzzgt460ehjj56azknlt5yrd4s38n97", + ConsumerValoperAddress: "consumervaloper1q90l6j6lzzgt460ehjj56azknlt5yrd46ufrcd", + ConsumerValoperAddressOnProvider: "cosmosvaloper1q90l6j6lzzgt460ehjj56azknlt5yrd449nxfd", + ConsumerValconsAddress: "consumervalcons1uuec3cjxajv5te08p220usrjhkfhg9wyref26m", + ConsumerValconsAddressOnProvider: "cosmosvalcons1uuec3cjxajv5te08p220usrjhkfhg9wyvqn0tm", + ConsumerValPubKey: `{"@type":"/cosmos.crypto.ed25519.PubKey","key":"QlG+iYe6AyYpvY1z9RNJKCVlH14Q/qSz4EjGdGCru3o="}`, + ConsumerPrivValidatorKey: `{"address":"E73388E246EC9945E5E70A94FE4072BD937415C4","pub_key":{"type":"tendermint/PubKeyEd25519","value":"QlG+iYe6AyYpvY1z9RNJKCVlH14Q/qSz4EjGdGCru3o="},"priv_key":{"type":"tendermint/PrivKeyEd25519","value":"OFR4w+FC6EMw5fAGTrHVexyPrjzQ7QfqgZOMgVf0izlCUb6Jh7oDJim9jXP1E0koJWUfXhD+pLPgSMZ0YKu7eg=="}}`, + ConsumerNodeKey: `{"priv_key":{"type":"tendermint/PrivKeyEd25519","value":"uhPCqnL2KE8m/8OFNLQ5bN3CJr6mds+xfBi0E4umT/s2uWiJhet+vbYx88DHSdof3gGFNTIzAIxSppscBKX96w=="}}`, + UseConsumerKey: false, }, ValidatorID("carol"): { - Mnemonic: "sight similar better jar bitter laptop solve fashion father jelly scissors chest uniform play unhappy convince silly clump another conduct behave reunion marble animal", - DelAddress: "cosmos19hz4m226ztankqramvt4a7t0shejv4dc79gp9u", - ValoperAddress: "cosmosvaloper19hz4m226ztankqramvt4a7t0shejv4dcm3u5f0", - ValconsAddress: "cosmosvalcons1ezyrq65s3gshhx5585w6mpusq3xsj3ayzf4uv6", - PrivValidatorKey: `{"address":"C888306A908A217B9A943D1DAD8790044D0947A4","pub_key":{"type":"tendermint/PubKeyEd25519","value":"IHo4QEikWZfIKmM0X+N+BjKttz8HOzGs2npyjiba3Xk="},"priv_key":{"type":"tendermint/PrivKeyEd25519","value":"z08bmSB91uFVpVmR3t2ewd/bDjZ/AzwQpe5rKjWiPG0gejhASKRZl8gqYzRf434GMq23Pwc7MazaenKOJtrdeQ=="}}`, - NodeKey: `{"priv_key":{"type":"tendermint/PrivKeyEd25519","value":"WLTcHEjbwB24Wp3z5oBSYTvtGQonz/7IQabOFw85BN0UkkyY5HDf38o8oHlFxVI26f+DFVeICuLbe9aXKGnUeg=="}}`, - IpSuffix: "6", + Mnemonic: "sight similar better jar bitter laptop solve fashion father jelly scissors chest uniform play unhappy convince silly clump another conduct behave reunion marble animal", + DelAddress: "cosmos19hz4m226ztankqramvt4a7t0shejv4dc79gp9u", + DelAddressOnConsumer: "consumer19hz4m226ztankqramvt4a7t0shejv4dcms9wkm", + ValoperAddress: "cosmosvaloper19hz4m226ztankqramvt4a7t0shejv4dcm3u5f0", + ValoperAddressOnConsumer: "consumervaloper19hz4m226ztankqramvt4a7t0shejv4dc5gx3c0", + ValconsAddress: "cosmosvalcons1ezyrq65s3gshhx5585w6mpusq3xsj3ayzf4uv6", + ValconsAddressOnConsumer: "consumervalcons1ezyrq65s3gshhx5585w6mpusq3xsj3ayds0ea6", + PrivValidatorKey: `{"address":"C888306A908A217B9A943D1DAD8790044D0947A4","pub_key":{"type":"tendermint/PubKeyEd25519","value":"IHo4QEikWZfIKmM0X+N+BjKttz8HOzGs2npyjiba3Xk="},"priv_key":{"type":"tendermint/PrivKeyEd25519","value":"z08bmSB91uFVpVmR3t2ewd/bDjZ/AzwQpe5rKjWiPG0gejhASKRZl8gqYzRf434GMq23Pwc7MazaenKOJtrdeQ=="}}`, + NodeKey: `{"priv_key":{"type":"tendermint/PrivKeyEd25519","value":"WLTcHEjbwB24Wp3z5oBSYTvtGQonz/7IQabOFw85BN0UkkyY5HDf38o8oHlFxVI26f+DFVeICuLbe9aXKGnUeg=="}}`, + IpSuffix: "6", // consumer chain assigned key - ConsumerMnemonic: "clip choose cake west range gun slam cry village receive juice galaxy lend ritual range provide ritual can since verify breeze vacant play dragon", - ConsumerDelAddress: "cosmos1sx6j9g2rh324a342a5f0rnx7me34r9nwgf0mc7", - ConsumerValoperAddress: "cosmosvaloper1sx6j9g2rh324a342a5f0rnx7me34r9nwdamw5d", - ConsumerValconsAddress: "cosmosvalcons1kswr5sq599365kcjmhgufevfps9njf43e4lwdk", - ConsumerValPubKey: `{"@type":"/cosmos.crypto.ed25519.PubKey","key":"Ui5Gf1+mtWUdH8u3xlmzdKID+F3PK0sfXZ73GZ6q6is="}`, - ConsumerPrivValidatorKey: `{"address":"B41C3A40142963AA5B12DDD1C4E5890C0B3926B1","pub_key":{"type":"tendermint/PubKeyEd25519","value":"Ui5Gf1+mtWUdH8u3xlmzdKID+F3PK0sfXZ73GZ6q6is="},"priv_key":{"type":"tendermint/PrivKeyEd25519","value":"3YaBAZLA+sl/E73lLfbFbG0u6DYm33ayr/0UpCt/vFBSLkZ/X6a1ZR0fy7fGWbN0ogP4Xc8rSx9dnvcZnqrqKw=="}}`, - ConsumerNodeKey: `{"priv_key":{"type":"tendermint/PrivKeyEd25519","value":"rxBzFedtD3pqgfJQblbxGusKOr47oBfr8ba0Iz14gobtDRZQZlSZ/UGP4pSHkVf+4vtkrkO1vRHBYJobuiP+7A=="}}`, - UseConsumerKey: true, + ConsumerMnemonic: "clip choose cake west range gun slam cry village receive juice galaxy lend ritual range provide ritual can since verify breeze vacant play dragon", + ConsumerDelAddress: "consumer1sx6j9g2rh324a342a5f0rnx7me34r9nwduz5te", + ConsumerDelAddressOnProvider: "cosmos1sx6j9g2rh324a342a5f0rnx7me34r9nwgf0mc7", + ConsumerValoperAddress: "consumervaloper1sx6j9g2rh324a342a5f0rnx7me34r9nwzypt9d", + ConsumerValoperAddressOnProvider: "cosmosvaloper1sx6j9g2rh324a342a5f0rnx7me34r9nwdamw5d", + ConsumerValconsAddress: "consumervalcons1kswr5sq599365kcjmhgufevfps9njf43kv9tuk", + ConsumerValconsAddressOnProvider: "cosmosvalcons1kswr5sq599365kcjmhgufevfps9njf43e4lwdk", + ConsumerValPubKey: `{"@type":"/cosmos.crypto.ed25519.PubKey","key":"Ui5Gf1+mtWUdH8u3xlmzdKID+F3PK0sfXZ73GZ6q6is="}`, + ConsumerPrivValidatorKey: `{"address":"B41C3A40142963AA5B12DDD1C4E5890C0B3926B1","pub_key":{"type":"tendermint/PubKeyEd25519","value":"Ui5Gf1+mtWUdH8u3xlmzdKID+F3PK0sfXZ73GZ6q6is="},"priv_key":{"type":"tendermint/PrivKeyEd25519","value":"3YaBAZLA+sl/E73lLfbFbG0u6DYm33ayr/0UpCt/vFBSLkZ/X6a1ZR0fy7fGWbN0ogP4Xc8rSx9dnvcZnqrqKw=="}}`, + ConsumerNodeKey: `{"priv_key":{"type":"tendermint/PrivKeyEd25519","value":"rxBzFedtD3pqgfJQblbxGusKOr47oBfr8ba0Iz14gobtDRZQZlSZ/UGP4pSHkVf+4vtkrkO1vRHBYJobuiP+7A=="}}`, + UseConsumerKey: true, }, } } @@ -164,6 +228,7 @@ func SlashThrottleTestConfig() TestConfig { chainConfigs: map[ChainID]ChainConfig{ ChainID("provi"): { ChainId: ChainID("provi"), + AccountPrefix: ProviderAccountPrefix, BinaryName: "interchain-security-pd", IpPrefix: "7.7.7", VotingWaitTime: 20, @@ -179,6 +244,7 @@ func SlashThrottleTestConfig() TestConfig { }, ChainID("consu"): { ChainId: ChainID("consu"), + AccountPrefix: ConsumerAccountPrefix, BinaryName: "interchain-security-cd", IpPrefix: "7.7.8", VotingWaitTime: 20, @@ -210,6 +276,7 @@ func DefaultTestConfig() TestConfig { chainConfigs: map[ChainID]ChainConfig{ ChainID("provi"): { ChainId: ChainID("provi"), + AccountPrefix: ProviderAccountPrefix, BinaryName: "interchain-security-pd", IpPrefix: "7.7.7", VotingWaitTime: 20, @@ -225,6 +292,7 @@ func DefaultTestConfig() TestConfig { }, ChainID("consu"): { ChainId: ChainID("consu"), + AccountPrefix: ConsumerAccountPrefix, BinaryName: "interchain-security-cd", IpPrefix: "7.7.8", VotingWaitTime: 20, @@ -268,6 +336,7 @@ func DemocracyTestConfig(allowReward bool) TestConfig { chainConfigs: map[ChainID]ChainConfig{ ChainID("provi"): { ChainId: ChainID("provi"), + AccountPrefix: ProviderAccountPrefix, BinaryName: "interchain-security-pd", IpPrefix: "7.7.7", VotingWaitTime: 20, @@ -282,6 +351,7 @@ func DemocracyTestConfig(allowReward bool) TestConfig { }, ChainID("democ"): { ChainId: ChainID("democ"), + AccountPrefix: ConsumerAccountPrefix, BinaryName: "interchain-security-cdd", IpPrefix: "7.7.9", VotingWaitTime: 20, @@ -308,6 +378,7 @@ func MultiConsumerTestConfig() TestConfig { chainConfigs: map[ChainID]ChainConfig{ ChainID("provi"): { ChainId: ChainID("provi"), + AccountPrefix: ProviderAccountPrefix, BinaryName: "interchain-security-pd", IpPrefix: "7.7.7", VotingWaitTime: 20, @@ -322,6 +393,7 @@ func MultiConsumerTestConfig() TestConfig { }, ChainID("consu"): { ChainId: ChainID("consu"), + AccountPrefix: ConsumerAccountPrefix, BinaryName: "interchain-security-cd", IpPrefix: "7.7.8", VotingWaitTime: 20, @@ -333,6 +405,7 @@ func MultiConsumerTestConfig() TestConfig { }, ChainID("densu"): { ChainId: ChainID("densu"), + AccountPrefix: ConsumerAccountPrefix, BinaryName: "interchain-security-cd", IpPrefix: "7.7.9", VotingWaitTime: 20, @@ -363,6 +436,7 @@ func ChangeoverTestConfig() TestConfig { chainConfigs: map[ChainID]ChainConfig{ ChainID("provi"): { ChainId: ChainID("provi"), + AccountPrefix: ProviderAccountPrefix, BinaryName: "interchain-security-pd", IpPrefix: "7.7.7", VotingWaitTime: 20, @@ -378,6 +452,7 @@ func ChangeoverTestConfig() TestConfig { }, ChainID("sover"): { ChainId: ChainID("sover"), + AccountPrefix: ConsumerAccountPrefix, BinaryName: "interchain-security-sd", UpgradeBinary: "interchain-security-cdd", IpPrefix: "7.7.8", @@ -408,47 +483,60 @@ func ConsumerMisbehaviourTestConfig() TestConfig { }, validatorConfigs: map[ValidatorID]ValidatorConfig{ ValidatorID("alice"): { - Mnemonic: "pave immune ethics wrap gain ceiling always holiday employ earth tumble real ice engage false unable carbon equal fresh sick tattoo nature pupil nuclear", - DelAddress: "cosmos19pe9pg5dv9k5fzgzmsrgnw9rl9asf7ddwhu7lm", - ValoperAddress: "cosmosvaloper19pe9pg5dv9k5fzgzmsrgnw9rl9asf7ddtrgtng", - ValconsAddress: "cosmosvalcons1qmq08eruchr5sf5s3rwz7djpr5a25f7xw4mceq", - PrivValidatorKey: `{"address":"06C0F3E47CC5C748269088DC2F36411D3AAA27C6","pub_key":{"type":"tendermint/PubKeyEd25519","value":"RrclQz9bIhkIy/gfL485g3PYMeiIku4qeo495787X10="},"priv_key":{"type":"tendermint/PrivKeyEd25519","value":"uX+ZpDMg89a6gtqs/+MQpCTSqlkZ0nJQJOhLlCJvwvdGtyVDP1siGQjL+B8vjzmDc9gx6IiS7ip6jj3nvztfXQ=="}}`, - NodeKey: `{"priv_key":{"type":"tendermint/PrivKeyEd25519","value":"fjw4/DAhyRPnwKgXns5SV7QfswRSXMWJpHS7TyULDmJ8ofUc5poQP8dgr8bZRbCV5RV8cPqDq3FPdqwpmUbmdA=="}}`, - IpSuffix: "4", + Mnemonic: "pave immune ethics wrap gain ceiling always holiday employ earth tumble real ice engage false unable carbon equal fresh sick tattoo nature pupil nuclear", + DelAddress: "cosmos19pe9pg5dv9k5fzgzmsrgnw9rl9asf7ddwhu7lm", + DelAddressOnConsumer: "consumer19pe9pg5dv9k5fzgzmsrgnw9rl9asf7ddtz33vu", + ValoperAddress: "cosmosvaloper19pe9pg5dv9k5fzgzmsrgnw9rl9asf7ddtrgtng", + ValoperAddressOnConsumer: "consumervaloper19pe9pg5dv9k5fzgzmsrgnw9rl9asf7ddy6jwzg", + ValconsAddress: "cosmosvalcons1qmq08eruchr5sf5s3rwz7djpr5a25f7xw4mceq", + ValconsAddressOnConsumer: "consumervalcons1qmq08eruchr5sf5s3rwz7djpr5a25f7xpvpagq", + PrivValidatorKey: `{"address":"06C0F3E47CC5C748269088DC2F36411D3AAA27C6","pub_key":{"type":"tendermint/PubKeyEd25519","value":"RrclQz9bIhkIy/gfL485g3PYMeiIku4qeo495787X10="},"priv_key":{"type":"tendermint/PrivKeyEd25519","value":"uX+ZpDMg89a6gtqs/+MQpCTSqlkZ0nJQJOhLlCJvwvdGtyVDP1siGQjL+B8vjzmDc9gx6IiS7ip6jj3nvztfXQ=="}}`, + NodeKey: `{"priv_key":{"type":"tendermint/PrivKeyEd25519","value":"fjw4/DAhyRPnwKgXns5SV7QfswRSXMWJpHS7TyULDmJ8ofUc5poQP8dgr8bZRbCV5RV8cPqDq3FPdqwpmUbmdA=="}}`, + IpSuffix: "4", // consumer chain assigned key - ConsumerMnemonic: "exile install vapor thing little toss immune notable lounge december final easy strike title end program interest quote cloth forget forward job october twenty", - ConsumerDelAddress: "cosmos1eeeggku6dzk3mv7wph3zq035rhtd890sjswszd", - ConsumerValoperAddress: "cosmosvaloper1eeeggku6dzk3mv7wph3zq035rhtd890shy69w7", - ConsumerValconsAddress: "cosmosvalcons1muys5jyqk4xd27e208nym85kn0t4zjcfeu63fe", - ConsumerValPubKey: `{"@type":"/cosmos.crypto.ed25519.PubKey","key":"ujY14AgopV907IYgPAk/5x8c9267S4fQf89nyeCPTes="}`, - ConsumerPrivValidatorKey: `{"address":"DF090A4880B54CD57B2A79E64D9E969BD7514B09","pub_key":{"type":"tendermint/PubKeyEd25519","value":"ujY14AgopV907IYgPAk/5x8c9267S4fQf89nyeCPTes="},"priv_key":{"type":"tendermint/PrivKeyEd25519","value":"TRJgf7lkTjs/sj43pyweEOanyV7H7fhnVivOi0A4yjW6NjXgCCilX3TshiA8CT/nHxz3brtLh9B/z2fJ4I9N6w=="}}`, - ConsumerNodeKey: `{"priv_key":{"type":"tendermint/PrivKeyEd25519","value":"F966RL9pi20aXRzEBe4D0xRQJtZt696Xxz44XUON52cFc83FMn1WXJbP6arvA2JPyn2LA3DLKCFHSgALrCGXGA=="}}`, - UseConsumerKey: true, + ConsumerMnemonic: "exile install vapor thing little toss immune notable lounge december final easy strike title end program interest quote cloth forget forward job october twenty", + ConsumerDelAddress: "consumer1eeeggku6dzk3mv7wph3zq035rhtd890sh9rl32", + ConsumerDelAddressOnProvider: "cosmos1eeeggku6dzk3mv7wph3zq035rhtd890sjswszd", + ConsumerValoperAddress: "consumervaloper1eeeggku6dzk3mv7wph3zq035rhtd890scaqql7", + ConsumerValoperAddressOnProvider: "cosmosvaloper1eeeggku6dzk3mv7wph3zq035rhtd890shy69w7", + ConsumerValconsAddress: "consumervalcons1muys5jyqk4xd27e208nym85kn0t4zjcfk9q5ce", + ConsumerValconsAddressOnProvider: "cosmosvalcons1muys5jyqk4xd27e208nym85kn0t4zjcfeu63fe", + ConsumerValPubKey: `{"@type":"/cosmos.crypto.ed25519.PubKey","key":"ujY14AgopV907IYgPAk/5x8c9267S4fQf89nyeCPTes="}`, + ConsumerPrivValidatorKey: `{"address":"DF090A4880B54CD57B2A79E64D9E969BD7514B09","pub_key":{"type":"tendermint/PubKeyEd25519","value":"ujY14AgopV907IYgPAk/5x8c9267S4fQf89nyeCPTes="},"priv_key":{"type":"tendermint/PrivKeyEd25519","value":"TRJgf7lkTjs/sj43pyweEOanyV7H7fhnVivOi0A4yjW6NjXgCCilX3TshiA8CT/nHxz3brtLh9B/z2fJ4I9N6w=="}}`, + ConsumerNodeKey: `{"priv_key":{"type":"tendermint/PrivKeyEd25519","value":"F966RL9pi20aXRzEBe4D0xRQJtZt696Xxz44XUON52cFc83FMn1WXJbP6arvA2JPyn2LA3DLKCFHSgALrCGXGA=="}}`, + UseConsumerKey: true, }, ValidatorID("bob"): { - Mnemonic: "glass trip produce surprise diamond spin excess gaze wash drum human solve dress minor artefact canoe hard ivory orange dinner hybrid moral potato jewel", - DelAddress: "cosmos1dkas8mu4kyhl5jrh4nzvm65qz588hy9qcz08la", - ValoperAddress: "cosmosvaloper1dkas8mu4kyhl5jrh4nzvm65qz588hy9qakmjnw", - ValconsAddress: "cosmosvalcons1nx7n5uh0ztxsynn4sje6eyq2ud6rc6klc96w39", - PrivValidatorKey: `{"address":"99BD3A72EF12CD024E7584B3AC900AE3743C6ADF","pub_key":{"type":"tendermint/PubKeyEd25519","value":"mAN6RXYxSM4MNGSIriYiS7pHuwAcOHDQAy9/wnlSzOI="},"priv_key":{"type":"tendermint/PrivKeyEd25519","value":"QePcwfWtOavNK7pBJrtoLMzarHKn6iBWfWPFeyV+IdmYA3pFdjFIzgw0ZIiuJiJLuke7ABw4cNADL3/CeVLM4g=="}}`, - NodeKey: `{"priv_key":{"type":"tendermint/PrivKeyEd25519","value":"TQ4vHcO/vKdzGtWpelkX53WdMQd4kTsWGFrdcatdXFvWyO215Rewn5IRP0FszPLWr2DqPzmuH8WvxYGk5aeOXw=="}}`, - IpSuffix: "5", + Mnemonic: "glass trip produce surprise diamond spin excess gaze wash drum human solve dress minor artefact canoe hard ivory orange dinner hybrid moral potato jewel", + DelAddress: "cosmos1dkas8mu4kyhl5jrh4nzvm65qz588hy9qcz08la", + DelAddressOnConsumer: "consumer1dkas8mu4kyhl5jrh4nzvm65qz588hy9qahzgv6", + ValoperAddress: "cosmosvaloper1dkas8mu4kyhl5jrh4nzvm65qz588hy9qakmjnw", + ValoperAddressOnConsumer: "consumervaloper1dkas8mu4kyhl5jrh4nzvm65qz588hy9qj0phzw", + ValconsAddress: "cosmosvalcons1nx7n5uh0ztxsynn4sje6eyq2ud6rc6klc96w39", + ValconsAddressOnConsumer: "consumervalcons1nx7n5uh0ztxsynn4sje6eyq2ud6rc6klhuqtq9", + PrivValidatorKey: `{"address":"99BD3A72EF12CD024E7584B3AC900AE3743C6ADF","pub_key":{"type":"tendermint/PubKeyEd25519","value":"mAN6RXYxSM4MNGSIriYiS7pHuwAcOHDQAy9/wnlSzOI="},"priv_key":{"type":"tendermint/PrivKeyEd25519","value":"QePcwfWtOavNK7pBJrtoLMzarHKn6iBWfWPFeyV+IdmYA3pFdjFIzgw0ZIiuJiJLuke7ABw4cNADL3/CeVLM4g=="}}`, + NodeKey: `{"priv_key":{"type":"tendermint/PrivKeyEd25519","value":"TQ4vHcO/vKdzGtWpelkX53WdMQd4kTsWGFrdcatdXFvWyO215Rewn5IRP0FszPLWr2DqPzmuH8WvxYGk5aeOXw=="}}`, + IpSuffix: "5", // consumer chain assigned key - ConsumerMnemonic: "grunt list hour endless observe better spoil penalty lab duck only layer vague fantasy satoshi record demise topple space shaft solar practice donor sphere", - ConsumerDelAddress: "cosmos1q90l6j6lzzgt460ehjj56azknlt5yrd4s38n97", - ConsumerValoperAddress: "cosmosvaloper1q90l6j6lzzgt460ehjj56azknlt5yrd449nxfd", - ConsumerValconsAddress: "cosmosvalcons1uuec3cjxajv5te08p220usrjhkfhg9wyvqn0tm", - ConsumerValPubKey: `{"@type":"/cosmos.crypto.ed25519.PubKey","key":"QlG+iYe6AyYpvY1z9RNJKCVlH14Q/qSz4EjGdGCru3o="}`, - ConsumerPrivValidatorKey: `{"address":"E73388E246EC9945E5E70A94FE4072BD937415C4","pub_key":{"type":"tendermint/PubKeyEd25519","value":"QlG+iYe6AyYpvY1z9RNJKCVlH14Q/qSz4EjGdGCru3o="},"priv_key":{"type":"tendermint/PrivKeyEd25519","value":"OFR4w+FC6EMw5fAGTrHVexyPrjzQ7QfqgZOMgVf0izlCUb6Jh7oDJim9jXP1E0koJWUfXhD+pLPgSMZ0YKu7eg=="}}`, - ConsumerNodeKey: `{"priv_key":{"type":"tendermint/PrivKeyEd25519","value":"uhPCqnL2KE8m/8OFNLQ5bN3CJr6mds+xfBi0E4umT/s2uWiJhet+vbYx88DHSdof3gGFNTIzAIxSppscBKX96w=="}}`, - UseConsumerKey: false, + ConsumerMnemonic: "grunt list hour endless observe better spoil penalty lab duck only layer vague fantasy satoshi record demise topple space shaft solar practice donor sphere", + ConsumerDelAddress: "consumer1q90l6j6lzzgt460ehjj56azknlt5yrd44y2uke", + ConsumerDelAddressOnProvider: "cosmos1q90l6j6lzzgt460ehjj56azknlt5yrd4s38n97", + ConsumerValoperAddress: "consumervaloper1q90l6j6lzzgt460ehjj56azknlt5yrd46ufrcd", + ConsumerValoperAddressOnProvider: "cosmosvaloper1q90l6j6lzzgt460ehjj56azknlt5yrd449nxfd", + ConsumerValconsAddress: "consumervalcons1uuec3cjxajv5te08p220usrjhkfhg9wyref26m", + ConsumerValconsAddressOnProvider: "cosmosvalcons1uuec3cjxajv5te08p220usrjhkfhg9wyvqn0tm", + ConsumerValPubKey: `{"@type":"/cosmos.crypto.ed25519.PubKey","key":"QlG+iYe6AyYpvY1z9RNJKCVlH14Q/qSz4EjGdGCru3o="}`, + ConsumerPrivValidatorKey: `{"address":"E73388E246EC9945E5E70A94FE4072BD937415C4","pub_key":{"type":"tendermint/PubKeyEd25519","value":"QlG+iYe6AyYpvY1z9RNJKCVlH14Q/qSz4EjGdGCru3o="},"priv_key":{"type":"tendermint/PrivKeyEd25519","value":"OFR4w+FC6EMw5fAGTrHVexyPrjzQ7QfqgZOMgVf0izlCUb6Jh7oDJim9jXP1E0koJWUfXhD+pLPgSMZ0YKu7eg=="}}`, + ConsumerNodeKey: `{"priv_key":{"type":"tendermint/PrivKeyEd25519","value":"uhPCqnL2KE8m/8OFNLQ5bN3CJr6mds+xfBi0E4umT/s2uWiJhet+vbYx88DHSdof3gGFNTIzAIxSppscBKX96w=="}}`, + UseConsumerKey: false, }, }, chainConfigs: map[ChainID]ChainConfig{ ChainID("provi"): { ChainId: ChainID("provi"), + AccountPrefix: ProviderAccountPrefix, BinaryName: "interchain-security-pd", IpPrefix: "7.7.7", VotingWaitTime: 20, @@ -464,6 +552,7 @@ func ConsumerMisbehaviourTestConfig() TestConfig { }, ChainID("consu"): { ChainId: ChainID("consu"), + AccountPrefix: ConsumerAccountPrefix, BinaryName: "interchain-security-cd", IpPrefix: "7.7.8", VotingWaitTime: 20, @@ -490,10 +579,6 @@ func (s *TestConfig) SetDockerConfig(localSdkPath string, useGaia bool, gaiaTag if useGaia { fmt.Println("USING GAIA INSTEAD OF ICS provider app", gaiaTag) } - - s.useGaia = useGaia - s.gaiaTag = gaiaTag - s.localSdkPath = localSdkPath } func (s *TestConfig) SetCometMockConfig(useCometmock bool) { diff --git a/tests/e2e/main.go b/tests/e2e/main.go index 13e3fae1e7..0df5c909cb 100644 --- a/tests/e2e/main.go +++ b/tests/e2e/main.go @@ -1,18 +1,12 @@ package main import ( - "bufio" "flag" "fmt" "log" - "os/exec" - "reflect" - "strconv" "strings" "sync" "time" - - "github.com/kylelemons/godebug/pretty" ) // The list of test cases to be executed @@ -33,6 +27,21 @@ func (t *TestSet) String() string { return fmt.Sprint(*t) } +type VersionSet map[string]bool + +func (vs *VersionSet) Set(value string) error { + (*vs)[value] = true + return nil +} + +func (vs *VersionSet) String() string { + keys := []string{} + for k, _ := range *vs { + keys = append(keys, k) + } + return fmt.Sprint(keys) +} + var ( verbose = flag.Bool("verbose", false, "turn verbose logging on/off") includeMultiConsumer = flag.Bool("include-multi-consumer", false, "include multiconsumer tests in run") @@ -48,20 +57,27 @@ var ( gaiaTag = flag.String("gaia-tag", "", "gaia tag to use - default is latest") ) +var ( + consumerVersions VersionSet = VersionSet{} + providerVersions VersionSet = VersionSet{} + transformGenesis = flag.Bool("transform-genesis", false, "enforces a consumer app to perform genesis transformation of exported ccv genesis data. For details see compatibility notes (RELEASES.md) of used versions") +) + var ( selectedTests TestSet // map the test config names to their structs to allow for easy selection of test configs, // and also to programmatically set parameters, i.e. see DemocracyTestConfig testConfigs = map[string]TestConfig{ - "default": DefaultTestConfig(), - "changeover": ChangeoverTestConfig(), - "democracy": DemocracyTestConfig(false), - "democracy-reward": DemocracyTestConfig(true), - "slash-throttle": SlashThrottleTestConfig(), - "multiconsumer": MultiConsumerTestConfig(), - "consumer-misbehaviour": ConsumerMisbehaviourTestConfig(), - "consumer-double-sign": DefaultTestConfig(), + "default": DefaultTestConfig(), + "changeover": ChangeoverTestConfig(), + "democracy": DemocracyTestConfig(false), + "democracy-reward": DemocracyTestConfig(true), + "slash-throttle": SlashThrottleTestConfig(), + "multiconsumer": MultiConsumerTestConfig(), + "consumer-misbehaviour": ConsumerMisbehaviourTestConfig(), + "consumer-double-sign": DefaultTestConfig(), + "consumer-double-downtime": DefaultTestConfig(), } ) @@ -128,31 +144,12 @@ var stepChoices = map[string]StepChoice{ description: "consumer double signing tests", testConfig: DefaultTestConfig(), }, -} - -func executeTests(tests []testStepsWithConfig) (err error) { - if parallel != nil && *parallel { - fmt.Println("=============== running all tests in parallel ===============") - } - - var wg sync.WaitGroup - for _, testCase := range tests { - if parallel != nil && *parallel { - wg.Add(1) - go func(run testStepsWithConfig) { - defer wg.Done() - run.testRun.Run(run.steps, *localSdkPath, *useGaia, *gaiaTag) - }(testCase) - } else { - log.Printf("=============== running %s ===============\n", testCase.testRun.name) - testCase.testRun.Run(testCase.steps, *localSdkPath, *useGaia, *gaiaTag) - } - } - - if parallel != nil && *parallel { - wg.Wait() - } - return + "consumer-double-downtime": { + name: "consumer-double-downtime", + steps: consumerDoubleDowntimeSteps, + description: "jail a validator for two (different) downtime infractions on consumer", + testConfig: DefaultTestConfig(), + }, } func getTestCaseUsageString() string { @@ -208,6 +205,10 @@ func parseArguments() (err error) { flag.Var(&selectedTestfiles, "test-file", getTestFileUsageString()) + + flag.Var(&consumerVersions, "cv", "Version (git tag, revision, branch) of the consumer to be tested. Tests will be run against combinations of all defined provider versions (-pv) with this consumer version. Default: consumer implementation of local workspace") + flag.Var(&providerVersions, "pv", "Version (git tag, revision, branch) of the provider to be tested. Tests will be run against combinations of all defined consumer versions (-cv) with this provider version. Default: provider implementation of local workspace") + flag.Parse() // Enforce go-relayer in case of cometmock as hermes is not yet supported @@ -232,6 +233,7 @@ func getTestCases(selectedPredefinedTests, selectedTestFiles TestSet) (tests []t "changeover", "happy-path", "democracy-reward", "democracy", "slash-throttle", "consumer-double-sign", "consumer-misbehaviour", + "consumer-double-downtime", } if includeMultiConsumer != nil && *includeMultiConsumer { selectedPredefinedTests = append(selectedPredefinedTests, "multiconsumer") @@ -287,226 +289,128 @@ func getTestCases(selectedPredefinedTests, selectedTestFiles TestSet) (tests []t return tests } -// runs E2E tests -// all docker containers are built sequentially to avoid race conditions when using local cosmos-sdk -// after building docker containers, all tests are run in parallel using their respective docker containers -func main() { - if err := parseArguments(); err != nil { - flag.Usage() - log.Fatalf("Error parsing command arguments %s\n", err) - } - - testCases := getTestCases(selectedTests, selectedTestfiles) - - start := time.Now() - err := executeTests(testCases) - if err != nil { - log.Fatalf("Test execution failed '%s'", err) +// delete all test targets +func deleteTargets(targets []ExecutionTarget) { + for _, target := range targets { + if err := target.Delete(); err != nil { + log.Println("error deleting target: ", err) + } } - log.Printf("TOTAL TIME ELAPSED: %v\n", time.Since(start)) } -// Run sets up docker container and executes the steps in the test run. -// Docker containers are torn down after the test run is complete. -func (tr *TestConfig) Run(steps []Step, localSdkPath string, useGaia bool, gaiaTag string) { - tr.SetDockerConfig(localSdkPath, useGaia, gaiaTag) - tr.SetCometMockConfig(*useCometmock) - tr.SetRelayerConfig(*useGorelayer) - - tr.validateStringLiterals() - tr.startDocker() - tr.executeSteps(steps) - tr.teardownDocker() -} +// Create targets where test cases should be executed on +// For each combination of provider & consumer versions an ExecutionTarget +// is created. +func createTargets(providerVersions, consumerVersions VersionSet) ([]ExecutionTarget, error) { + targetCfg := TargetConfig{useGaia: *useGaia, localSdkPath: *localSdkPath, gaiaTag: *gaiaTag} + var targets []ExecutionTarget -type StepChoice struct { - name string - steps []Step - description string - testConfig TestConfig -} - -func (tr *TestConfig) runStep(step Step, verbose bool) { - switch action := step.Action.(type) { - case StartChainAction: - tr.startChain(action, verbose) - case StartSovereignChainAction: - tr.startSovereignChain(action, verbose) - case LegacyUpgradeProposalAction: - tr.submitLegacyUpgradeProposal(action, verbose) - case WaitUntilBlockAction: - tr.waitUntilBlockOnChain(action) - case ChangeoverChainAction: - tr.changeoverChain(action, verbose) - case SendTokensAction: - tr.sendTokens(action, verbose) - case SubmitTextProposalAction: - tr.submitTextProposal(action, verbose) - case SubmitConsumerAdditionProposalAction: - tr.submitConsumerAdditionProposal(action, verbose) - case SubmitConsumerRemovalProposalAction: - tr.submitConsumerRemovalProposal(action, verbose) - case SubmitParamChangeLegacyProposalAction: - tr.submitParamChangeProposal(action, verbose) - case VoteGovProposalAction: - tr.voteGovProposal(action, verbose) - case StartConsumerChainAction: - tr.startConsumerChain(action, verbose) - case AddChainToRelayerAction: - tr.addChainToRelayer(action, verbose) - case CreateIbcClientsAction: - tr.createIbcClientsHermes(action, verbose) - case AddIbcConnectionAction: - tr.addIbcConnection(action, verbose) - case AddIbcChannelAction: - tr.addIbcChannel(action, verbose) - case TransferChannelCompleteAction: - tr.transferChannelComplete(action, verbose) - case RelayPacketsAction: - tr.relayPackets(action, verbose) - case RelayRewardPacketsToProviderAction: - tr.relayRewardPacketsToProvider(action, verbose) - case DelegateTokensAction: - tr.delegateTokens(action, verbose) - case UnbondTokensAction: - tr.unbondTokens(action, verbose) - case CancelUnbondTokensAction: - tr.cancelUnbondTokens(action, verbose) - case RedelegateTokensAction: - tr.redelegateTokens(action, verbose) - case DowntimeSlashAction: - tr.invokeDowntimeSlash(action, verbose) - case UnjailValidatorAction: - tr.unjailValidator(action, verbose) - case DoublesignSlashAction: - tr.invokeDoublesignSlash(action, verbose) - case LightClientAmnesiaAttackAction: - tr.lightClientAmnesiaAttack(action, verbose) - case LightClientEquivocationAttackAction: - tr.lightClientEquivocationAttack(action, verbose) - case LightClientLunaticAttackAction: - tr.lightClientLunaticAttack(action, verbose) - case RegisterRepresentativeAction: - tr.registerRepresentative(action, verbose) - case AssignConsumerPubKeyAction: - tr.assignConsumerPubKey(action, verbose) - case SlashMeterReplenishmentAction: - tr.waitForSlashMeterReplenishment(action, verbose) - case WaitTimeAction: - tr.waitForTime(action, verbose) - case StartRelayerAction: - tr.startRelayer(action, verbose) - case ForkConsumerChainAction: - tr.forkConsumerChain(action, verbose) - case UpdateLightClientAction: - tr.updateLightClient(action, verbose) - case StartConsumerEvidenceDetectorAction: - tr.startConsumerEvidenceDetector(action, verbose) - case SubmitChangeRewardDenomsProposalAction: - tr.submitChangeRewardDenomsProposal(action, verbose) - default: - log.Fatalf("unknown action in testRun %s: %#v", tr.name, action) + if len(consumerVersions) == 0 { + consumerVersions[""] = true } - - modelState := step.State - actualState := tr.getState(step.State) - - // Check state - if !reflect.DeepEqual(actualState, modelState) { - fmt.Printf("=============== %s FAILED ===============\n", tr.name) - fmt.Println("FAILED action", reflect.TypeOf(step.Action).Name()) - pretty.Print("actual state", actualState) - pretty.Print("model state", modelState) - log.Fatal(`actual state (-) not equal to model state (+): ` + pretty.Compare(actualState, modelState)) + if len(providerVersions) == 0 { + providerVersions[""] = true } -} - -// executeSteps sequentially runs steps. -func (tr *TestConfig) executeSteps(steps []Step) { - fmt.Printf("=============== started %s tests ===============\n", tr.name) - start := time.Now() - for i, step := range steps { - // print something the show the test is alive - fmt.Printf("running %s: step %d == %s \n", - tr.name, i+1, reflect.TypeOf(step.Action).Name()) - tr.runStep(step, *verbose) + for provider, _ := range providerVersions { + for consumer, _ := range consumerVersions { + targetCfg.consumerVersion = consumer + targetCfg.providerVersion = provider + target := DockerContainer{targetConfig: targetCfg} + err := target.Build() + if err != nil { + log.Println("@@@ failed creating target") + deleteTargets(targets) + return nil, err + } + targets = append(targets, &target) + } } - - fmt.Printf("=============== finished %s tests in %v ===============\n", tr.name, time.Since(start)) + return targets, nil } -func (tr *TestConfig) startDocker() { - fmt.Printf("=============== building %s testRun ===============\n", tr.name) - localSdk := tr.localSdkPath - if localSdk == "" { - localSdk = "default" - } - useGaia := "false" - gaiaTag := "" - if tr.useGaia { - useGaia = "true" - if tr.gaiaTag != "" { - majVersion, err := strconv.Atoi(tr.gaiaTag[1:strings.Index(tr.gaiaTag, ".")]) - if err != nil { - panic(fmt.Sprintf("invalid gaia version %s", tr.gaiaTag)) +func createTestRunners(targets []ExecutionTarget, testCases []testStepsWithConfig) []TestRunner { + runners := []TestRunner{} + for _, target := range targets { + for _, tc := range testCases { + tr := TestRunner{ + config: tc.testRun, + steps: tc.steps, + target: target, + verbose: *verbose, } - if majVersion < 9 { - panic(fmt.Sprintf("gaia version %s is not supported - supporting only v9.x.x and newer", tr.gaiaTag)) - } - gaiaTag = tr.gaiaTag + //TODO: refactor this target specific setting + tr.target.(*DockerContainer).containerCfg = tc.testRun.containerConfig + tr.config.transformGenesis = *transformGenesis + tr.config.SetCometMockConfig(*useCometmock) + tr.config.SetRelayerConfig(*useGorelayer) + runners = append(runners, tr) } } - scriptStr := fmt.Sprintf( - "tests/e2e/testnet-scripts/start-docker.sh %s %s %s %s %s", - tr.containerConfig.ContainerName, - tr.containerConfig.InstanceName, - localSdk, - useGaia, - gaiaTag, - ) - - //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. - cmd := exec.Command("/bin/bash", "-c", scriptStr) - - cmdReader, err := cmd.StdoutPipe() - if err != nil { - log.Fatal(err) - } - cmd.Stderr = cmd.Stdout + return runners +} - if err := cmd.Start(); err != nil { - log.Fatal(err) +func executeTests(runners []TestRunner) error { + if parallel != nil && *parallel { + fmt.Println("=============== running all tests in parallel ===============") } - scanner := bufio.NewScanner(cmdReader) + var wg sync.WaitGroup + var err error = nil - for scanner.Scan() { - out := scanner.Text() - if verbose != nil && *verbose { - fmt.Println("startDocker: " + out) - } - if out == "beacon!!!!!!!!!!" { - return + for _, runner := range runners { + if parallel != nil && *parallel { + wg.Add(1) + go func(runner TestRunner) { + defer wg.Done() + result := runner.Run() + if result != nil { + log.Printf("Test '%s' failed", runner.config.name) + err = result + } + }(runner) + } else { + fmt.Printf("=============== running %s ===============\n", runner.config.name) + err = runner.Run() } } - if err := scanner.Err(); err != nil { - log.Fatal(err) + + if parallel != nil && *parallel { + wg.Wait() } - err = cmd.Wait() - log.Fatalf("StartDocker exited with error: %v", err) + return err } -// remove docker container to reduce resource usage -// otherwise the chain will keep running in the background -func (tr *TestConfig) teardownDocker() { - fmt.Printf("=============== tearing down %s testRun ===============\n", tr.name) - //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. - cmd := exec.Command("docker", "kill", tr.containerConfig.InstanceName) +// runs E2E tests +// all docker containers are built sequentially to avoid race conditions when using local cosmos-sdk +// after building docker containers, all tests are run in parallel using their respective docker containers +func main() { + if err := parseArguments(); err != nil { + flag.Usage() + log.Fatalf("Error parsing command arguments %s\n", err) + } + + testCases := getTestCases(selectedTests, selectedTestfiles) + targets, err := createTargets(providerVersions, consumerVersions) + if err != nil { + log.Fatal("failed creating test targets: ", err) + } + defer func() { deleteTargets(targets) }() - bz, err := cmd.CombinedOutput() + testRunners := createTestRunners(targets, testCases) + start := time.Now() + err = executeTests(testRunners) if err != nil { - log.Fatal(err, "\n", string(bz)) + log.Fatalf("Test execution failed '%s'", err) } + log.Printf("TOTAL TIME ELAPSED: %v\n", time.Since(start)) + +} + +type StepChoice struct { + name string + steps []Step + description string + testConfig TestConfig } diff --git a/tests/e2e/state.go b/tests/e2e/state.go index 6578aad03a..27d63187ba 100644 --- a/tests/e2e/state.go +++ b/tests/e2e/state.go @@ -101,10 +101,12 @@ type Param struct { Value string } -func (tr TestConfig) getState(modelState State) State { +func (tr TestConfig) getState(modelState State, verbose bool) State { systemState := State{} for k, modelState := range modelState { - log.Println("Getting model state for chain: ", k) + if verbose { + fmt.Println("Getting model state for chain: ", k) + } systemState[k] = tr.getChainState(k, modelState) } @@ -316,9 +318,16 @@ func (tr TestConfig) getRewards(chain ChainID, modelState Rewards) Rewards { } func (tr TestConfig) getReward(chain ChainID, validator ValidatorID, blockHeight uint, isNativeDenom bool) float64 { - delAddresss := tr.validatorConfigs[validator].DelAddress - if chain != ChainID("provi") && tr.validatorConfigs[validator].UseConsumerKey { - delAddresss = tr.validatorConfigs[validator].ConsumerDelAddress + valCfg := tr.validatorConfigs[validator] + delAddresss := valCfg.DelAddress + if chain != ChainID("provi") { + // use binary with Bech32Prefix set to ConsumerAccountPrefix + if valCfg.UseConsumerKey { + delAddresss = valCfg.ConsumerDelAddress + } else { + // use the same address as on the provider but with different prefix + delAddresss = valCfg.DelAddressOnConsumer + } } //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. @@ -345,9 +354,16 @@ func (tr TestConfig) getReward(chain ChainID, validator ValidatorID, blockHeight func (tr TestConfig) getBalance(chain ChainID, validator ValidatorID) uint { //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. - valDelAddress := tr.validatorConfigs[validator].DelAddress - if chain != ChainID("provi") && tr.validatorConfigs[validator].UseConsumerKey { - valDelAddress = tr.validatorConfigs[validator].ConsumerDelAddress + valCfg := tr.validatorConfigs[validator] + valDelAddress := valCfg.DelAddress + if chain != ChainID("provi") { + // use binary with Bech32Prefix set to ConsumerAccountPrefix + if valCfg.UseConsumerKey { + valDelAddress = valCfg.ConsumerDelAddress + } else { + // use the same address as on the provider but with different prefix + valDelAddress = valCfg.DelAddressOnConsumer + } } //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. @@ -485,6 +501,7 @@ type ValPubKey struct { Value string `yaml:"value"` } +// TODO (mpoke) Return powers for multiple validators func (tr TestConfig) getValPower(chain ChainID, validator ValidatorID) uint { if *verbose { log.Println("getting validator power for chain: ", chain, " validator: ", validator) @@ -519,16 +536,25 @@ func (tr TestConfig) getValPower(chain ChainID, validator ValidatorID) uint { } for _, val := range valset.Validators { - if val.Address == tr.validatorConfigs[validator].ValconsAddress || - val.Address == tr.validatorConfigs[validator].ConsumerValconsAddress { - - votingPower, err := strconv.Atoi(val.VotingPower) - if err != nil { - log.Fatalf("strconv.Atoi returned an error while converting validator voting power: %v, voting power string: %s, validator set: %s", err, val.VotingPower, pretty.Sprint(valset)) + if chain == ChainID("provi") { + // use binary with Bech32Prefix set to ProviderAccountPrefix + if val.Address != tr.validatorConfigs[validator].ValconsAddress { + continue + } + } else { + // use binary with Bech32Prefix set to ConsumerAccountPrefix + if val.Address != tr.validatorConfigs[validator].ValconsAddressOnConsumer && + val.Address != tr.validatorConfigs[validator].ConsumerValconsAddress { + continue } + } - return uint(votingPower) + votingPower, err := strconv.Atoi(val.VotingPower) + if err != nil { + log.Fatalf("strconv.Atoi returned an error while converting validator voting power: %v, voting power string: %s, validator set: %s", err, val.VotingPower, pretty.Sprint(valset)) } + + return uint(votingPower) } // Validator not in set, its validator power is zero. @@ -536,11 +562,22 @@ func (tr TestConfig) getValPower(chain ChainID, validator ValidatorID) uint { } func (tr TestConfig) getValStakedTokens(chain ChainID, validator ValidatorID) uint { + valoperAddress := tr.validatorConfigs[validator].ValoperAddress + if chain != ChainID("provi") { + // use binary with Bech32Prefix set to ConsumerAccountPrefix + if tr.validatorConfigs[validator].UseConsumerKey { + valoperAddress = tr.validatorConfigs[validator].ConsumerValoperAddress + } else { + // use the same address as on the provider but with different prefix + valoperAddress = tr.validatorConfigs[validator].ValoperAddressOnConsumer + } + } + //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. bz, err := exec.Command("docker", "exec", tr.containerConfig.InstanceName, tr.chainConfigs[chain].BinaryName, "query", "staking", "validator", - tr.validatorConfigs[validator].ValoperAddress, + valoperAddress, `--node`, tr.getQueryNode(chain), `-o`, `json`, @@ -641,7 +678,7 @@ func (tr TestConfig) getProviderAddressFromConsumer(consumerChain ChainID, valid cmd := exec.Command("docker", "exec", tr.containerConfig.InstanceName, tr.chainConfigs[ChainID("provi")].BinaryName, "query", "provider", "validator-provider-key", - string(consumerChain), tr.validatorConfigs[validator].ConsumerValconsAddress, + string(consumerChain), tr.validatorConfigs[validator].ConsumerValconsAddressOnProvider, `--node`, tr.getQueryNode(ChainID("provi")), `-o`, `json`, ) diff --git a/tests/e2e/steps.go b/tests/e2e/steps.go index 783d976a3a..1626df0de2 100644 --- a/tests/e2e/steps.go +++ b/tests/e2e/steps.go @@ -114,3 +114,11 @@ var consumerDoubleSignSteps = concatSteps( // make a consumer validator double sign and get jailed stepsCauseDoubleSignOnConsumer("consu", "provi"), ) + +var consumerDoubleDowntimeSteps = concatSteps( + stepsStartChains([]string{"consu"}, false), + stepsDelegate("consu"), + stepsUnbond("consu"), + stepsRedelegateShort("consu"), + stepsDoubleDowntime("consu"), +) diff --git a/tests/e2e/steps_consumer_misbehaviour.go b/tests/e2e/steps_consumer_misbehaviour.go index 4df7b9daec..3cb4ce0f1b 100644 --- a/tests/e2e/steps_consumer_misbehaviour.go +++ b/tests/e2e/steps_consumer_misbehaviour.go @@ -61,7 +61,7 @@ func stepsStartChainsWithSoftOptOut(consumerName string) []Step { Action: AssignConsumerPubKeyAction{ Chain: ChainID(consumerName), Validator: ValidatorID("alice"), - ConsumerPubkey: `{"@type":"/cosmos.crypto.ed25519.PubKey","key":"ujY14AgopV907IYgPAk/5x8c9267S4fQf89nyeCPTes="}`, + ConsumerPubkey: getDefaultValidators()[ValidatorID("alice")].ConsumerValPubKey, // consumer chain has not started // we don't need to reconfigure the node // since it will start with consumer key @@ -70,10 +70,10 @@ func stepsStartChainsWithSoftOptOut(consumerName string) []Step { State: State{ ChainID(consumerName): ChainState{ AssignedKeys: &map[ValidatorID]string{ - ValidatorID("alice"): "cosmosvalcons1muys5jyqk4xd27e208nym85kn0t4zjcfeu63fe", + ValidatorID("alice"): getDefaultValidators()[ValidatorID("alice")].ConsumerValconsAddressOnProvider, }, ProviderKeys: &map[ValidatorID]string{ - ValidatorID("alice"): "cosmosvalcons1qmq08eruchr5sf5s3rwz7djpr5a25f7xw4mceq", + ValidatorID("alice"): getDefaultValidators()[ValidatorID("alice")].ValconsAddress, }, }, }, diff --git a/tests/e2e/steps_downtime.go b/tests/e2e/steps_downtime.go index f63d0a31c9..a8506bf518 100644 --- a/tests/e2e/steps_downtime.go +++ b/tests/e2e/steps_downtime.go @@ -211,6 +211,204 @@ func stepsDowntime(consumerName string) []Step { } } +// stepsDowstepsDoubleDowntime time tests that a validator can get jailed twice +// on a consumer. +// These are the steps: +// - a validator is down on a consumer +// - the validator gets jailed on the provider (when the SlashPacket is received) +// - the validator gets removed from the consumer (when the VSCPacket is received) +// - the validator gets unjailed on the provider +// - the validator is added to the consumer (when the VSCPacket is received) +// - the validator is down again on the consumer +// - the validator gets jailed on the provider (when the SlashPacket is received) +// - the validator gets removed from the consumer (when the VSCPacket is received) +func stepsDoubleDowntime(consumerName string) []Step { + return []Step{ + { + Action: DowntimeSlashAction{ + Chain: ChainID(consumerName), + Validator: ValidatorID("bob"), + }, + State: State{ + // validator should be slashed on consumer, powers not affected on either chain yet + ChainID("provi"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 509, + ValidatorID("bob"): 500, + ValidatorID("carol"): 501, + }, + }, + ChainID(consumerName): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 509, + ValidatorID("bob"): 500, + ValidatorID("carol"): 501, + }, + }, + }, + }, + { + Action: RelayPacketsAction{ + ChainA: ChainID("provi"), + ChainB: ChainID(consumerName), + Port: "provider", + Channel: 0, + }, + State: State{ + ChainID("provi"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 509, + // Downtime jailing and corresponding voting power change are processed by provider + ValidatorID("bob"): 0, + ValidatorID("carol"): 501, + }, + }, + ChainID(consumerName): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 509, + // Bob's stake may or may not be slashed at this point depending on comet vs cometmock + // See https://github.com/cosmos/interchain-security/issues/1304 + ValidatorID("carol"): 501, + }, + }, + }, + }, + // A block is incremented each action, hence why VSC is committed on provider, + // and can now be relayed as packet to consumer + { + Action: RelayPacketsAction{ + ChainA: ChainID("provi"), + ChainB: ChainID(consumerName), + Port: "provider", + Channel: 0, + }, + State: State{ + ChainID(consumerName): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 509, + // VSC now seen on consumer + ValidatorID("bob"): 0, + ValidatorID("carol"): 501, + }, + }, + }, + }, + { + Action: UnjailValidatorAction{ + Provider: ChainID("provi"), + Validator: ValidatorID("bob"), + }, + State: State{ + ChainID("provi"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 509, + // bob's stake should not be slashed + // since the slash was initiated from consumer + ValidatorID("bob"): 500, + ValidatorID("carol"): 501, + }, + }, + ChainID(consumerName): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 509, + ValidatorID("bob"): 0, + ValidatorID("carol"): 501, + }, + }, + }, + }, + { + Action: RelayPacketsAction{ + ChainA: ChainID("provi"), + ChainB: ChainID(consumerName), + Port: "provider", + Channel: 0, + }, + State: State{ + ChainID(consumerName): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 509, + // bob's stake should not be slashed + // since the slash was initiated from consumer + ValidatorID("bob"): 500, + ValidatorID("carol"): 501, + }, + }, + }, + }, + // Try to jail bob again on the consumer + { + Action: DowntimeSlashAction{ + Chain: ChainID(consumerName), + Validator: ValidatorID("bob"), + }, + State: State{ + // validator should be slashed on consumer, powers not affected on either chain yet + ChainID("provi"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 509, + ValidatorID("bob"): 500, + ValidatorID("carol"): 501, + }, + }, + ChainID(consumerName): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 509, + ValidatorID("bob"): 500, + ValidatorID("carol"): 501, + }, + }, + }, + }, + { + Action: RelayPacketsAction{ + ChainA: ChainID("provi"), + ChainB: ChainID(consumerName), + Port: "provider", + Channel: 0, + }, + State: State{ + ChainID("provi"): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 509, + // Downtime jailing and corresponding voting power change are processed by provider + ValidatorID("bob"): 0, + ValidatorID("carol"): 501, + }, + }, + ChainID(consumerName): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 509, + // Bob's stake may or may not be slashed at this point depending on comet vs cometmock + // See https://github.com/cosmos/interchain-security/issues/1304 + ValidatorID("carol"): 501, + }, + }, + }, + }, + // A block is incremented each action, hence why VSC is committed on provider, + // and can now be relayed as packet to consumer + { + Action: RelayPacketsAction{ + ChainA: ChainID("provi"), + ChainB: ChainID(consumerName), + Port: "provider", + Channel: 0, + }, + State: State{ + ChainID(consumerName): ChainState{ + ValPowers: &map[ValidatorID]uint{ + ValidatorID("alice"): 509, + // VSC now seen on consumer + ValidatorID("bob"): 0, + ValidatorID("carol"): 501, + }, + }, + }, + }, + } +} + // stepsDowntimeWithOptOut returns steps validating that alice can incur downtime // and not be slashed/jailed, since her voting power is less than 5% of the total. // diff --git a/tests/e2e/steps_start_chains.go b/tests/e2e/steps_start_chains.go index b1fdb65df5..08732e3f37 100644 --- a/tests/e2e/steps_start_chains.go +++ b/tests/e2e/steps_start_chains.go @@ -64,7 +64,7 @@ func stepsStartConsumerChain(consumerName string, proposalIndex, chainIndex uint Action: AssignConsumerPubKeyAction{ Chain: ChainID(consumerName), Validator: ValidatorID("carol"), - ConsumerPubkey: `{"@type":"/cosmos.crypto.ed25519.PubKey","key":"Ui5Gf1+mtWUdH8u3xlmzdKID+F3PK0sfXZ73GZ6q6is="}`, + ConsumerPubkey: getDefaultValidators()[ValidatorID("carol")].ConsumerValPubKey, // consumer chain has not started // we don't need to reconfigure the node // since it will start with consumer key @@ -73,10 +73,10 @@ func stepsStartConsumerChain(consumerName string, proposalIndex, chainIndex uint State: State{ ChainID(consumerName): ChainState{ AssignedKeys: &map[ValidatorID]string{ - ValidatorID("carol"): "cosmosvalcons1kswr5sq599365kcjmhgufevfps9njf43e4lwdk", + ValidatorID("carol"): getDefaultValidators()[ValidatorID("carol")].ConsumerValconsAddressOnProvider, }, ProviderKeys: &map[ValidatorID]string{ - ValidatorID("carol"): "cosmosvalcons1ezyrq65s3gshhx5585w6mpusq3xsj3ayzf4uv6", + ValidatorID("carol"): getDefaultValidators()[ValidatorID("carol")].ValconsAddress, }, }, }, @@ -86,7 +86,7 @@ func stepsStartConsumerChain(consumerName string, proposalIndex, chainIndex uint Action: AssignConsumerPubKeyAction{ Chain: ChainID(consumerName), Validator: ValidatorID("carol"), - ConsumerPubkey: `{"@type":"/cosmos.crypto.ed25519.PubKey","key":"Ui5Gf1+mtWUdH8u3xlmzdKID+F3PK0sfXZ73GZ6q6is="}`, + ConsumerPubkey: getDefaultValidators()[ValidatorID("carol")].ConsumerValPubKey, ReconfigureNode: false, ExpectError: true, ExpectedError: "a validator has assigned the consumer key already: consumer key is already in use by a validator", @@ -99,7 +99,7 @@ func stepsStartConsumerChain(consumerName string, proposalIndex, chainIndex uint Chain: ChainID(consumerName), Validator: ValidatorID("bob"), // same pub key as carol - ConsumerPubkey: `{"@type":"/cosmos.crypto.ed25519.PubKey","key":"Ui5Gf1+mtWUdH8u3xlmzdKID+F3PK0sfXZ73GZ6q6is="}`, + ConsumerPubkey: getDefaultValidators()[ValidatorID("carol")].ConsumerValPubKey, ReconfigureNode: false, ExpectError: true, ExpectedError: "a validator has assigned the consumer key already: consumer key is already in use by a validator", @@ -107,11 +107,11 @@ func stepsStartConsumerChain(consumerName string, proposalIndex, chainIndex uint State: State{ ChainID(consumerName): ChainState{ AssignedKeys: &map[ValidatorID]string{ - ValidatorID("carol"): "cosmosvalcons1kswr5sq599365kcjmhgufevfps9njf43e4lwdk", + ValidatorID("carol"): getDefaultValidators()[ValidatorID("carol")].ConsumerValconsAddressOnProvider, ValidatorID("bob"): "", }, ProviderKeys: &map[ValidatorID]string{ - ValidatorID("carol"): "cosmosvalcons1ezyrq65s3gshhx5585w6mpusq3xsj3ayzf4uv6", + ValidatorID("carol"): getDefaultValidators()[ValidatorID("carol")].ValconsAddress, }, }, }, @@ -235,7 +235,7 @@ func stepsAssignConsumerKeyOnStartedChain(consumerName, validator string) []Step Validator: ValidatorID("bob"), // reconfigure the node -> validator was using provider key // until this point -> key matches config.consumerValPubKey for "bob" - ConsumerPubkey: `{"@type":"/cosmos.crypto.ed25519.PubKey","key":"QlG+iYe6AyYpvY1z9RNJKCVlH14Q/qSz4EjGdGCru3o="}`, + ConsumerPubkey: getDefaultValidators()[ValidatorID("bob")].ConsumerValPubKey, ReconfigureNode: true, }, State: State{ @@ -257,12 +257,12 @@ func stepsAssignConsumerKeyOnStartedChain(consumerName, validator string) []Step ValidatorID("carol"): 500, }, AssignedKeys: &map[ValidatorID]string{ - ValidatorID("bob"): "cosmosvalcons1uuec3cjxajv5te08p220usrjhkfhg9wyvqn0tm", - ValidatorID("carol"): "cosmosvalcons1kswr5sq599365kcjmhgufevfps9njf43e4lwdk", + ValidatorID("bob"): getDefaultValidators()[ValidatorID("bob")].ConsumerValconsAddressOnProvider, + ValidatorID("carol"): getDefaultValidators()[ValidatorID("carol")].ConsumerValconsAddressOnProvider, }, ProviderKeys: &map[ValidatorID]string{ - ValidatorID("bob"): "cosmosvalcons1nx7n5uh0ztxsynn4sje6eyq2ud6rc6klc96w39", - ValidatorID("carol"): "cosmosvalcons1ezyrq65s3gshhx5585w6mpusq3xsj3ayzf4uv6", + ValidatorID("bob"): getDefaultValidators()[ValidatorID("bob")].ValconsAddress, + ValidatorID("carol"): getDefaultValidators()[ValidatorID("carol")].ValconsAddress, }, }, }, @@ -293,12 +293,12 @@ func stepsAssignConsumerKeyOnStartedChain(consumerName, validator string) []Step ValidatorID("carol"): 500, }, AssignedKeys: &map[ValidatorID]string{ - ValidatorID("bob"): "cosmosvalcons1uuec3cjxajv5te08p220usrjhkfhg9wyvqn0tm", - ValidatorID("carol"): "cosmosvalcons1kswr5sq599365kcjmhgufevfps9njf43e4lwdk", + ValidatorID("bob"): getDefaultValidators()[ValidatorID("bob")].ConsumerValconsAddressOnProvider, + ValidatorID("carol"): getDefaultValidators()[ValidatorID("carol")].ConsumerValconsAddressOnProvider, }, ProviderKeys: &map[ValidatorID]string{ - ValidatorID("bob"): "cosmosvalcons1nx7n5uh0ztxsynn4sje6eyq2ud6rc6klc96w39", - ValidatorID("carol"): "cosmosvalcons1ezyrq65s3gshhx5585w6mpusq3xsj3ayzf4uv6", + ValidatorID("bob"): getDefaultValidators()[ValidatorID("bob")].ValconsAddress, + ValidatorID("carol"): getDefaultValidators()[ValidatorID("carol")].ValconsAddress, }, }, }, diff --git a/tests/e2e/test_driver.go b/tests/e2e/test_driver.go new file mode 100644 index 0000000000..26f44d9600 --- /dev/null +++ b/tests/e2e/test_driver.go @@ -0,0 +1,150 @@ +package main + +import ( + "fmt" + "log" + "reflect" + "time" + + "github.com/kylelemons/godebug/pretty" +) + +// TestCaseDriver knows how different TC can be executed +type TestCaseDriver interface { + Run(steps []Step, target ExecutionTarget, verbose bool) error +} + +func GetTestCaseDriver(testCfg TestConfig) TestCaseDriver { + return &DefaultDriver{testCfg: testCfg} +} + +type DefaultDriver struct { + testCfg TestConfig + target ExecutionTarget + verbose bool +} + +// Execute tests +func (td *DefaultDriver) Run(steps []Step, target ExecutionTarget, verbose bool) error { + td.target = target + td.verbose = verbose + + fmt.Printf("=============== started %s tests ===============\n", td.testCfg.name) + + start := time.Now() + for i, step := range steps { + fmt.Printf("running %s: step %d/%d == %s \n", + td.testCfg.name, i+1, len(steps), reflect.TypeOf(step.Action).Name()) + + err := td.runStep(step) + if err != nil { + return err + } + } + fmt.Printf("=============== finished %s tests in %v ===============\n", td.testCfg.name, time.Since(start)) + return nil +} + +// runStep executes an action and evaluates the result against expected state +func (td *DefaultDriver) runStep(step Step) error { + err := td.runAction(step.Action) + if err != nil { + return err + } + modelState := step.State + actualState := td.testCfg.getState(modelState, td.verbose) + + // Check state + if !reflect.DeepEqual(actualState, modelState) { + fmt.Printf("=============== %s FAILED ===============\n", td.testCfg.name) + fmt.Println("FAILED action", reflect.TypeOf(step.Action).Name()) + pretty.Print("actual state", actualState) + pretty.Print("model state", modelState) + log.Fatal(`actual state (-) not equal to model state (+): ` + pretty.Compare(actualState, modelState)) + } + return nil +} + +func (td *DefaultDriver) runAction(action interface{}) error { + switch action := action.(type) { + case StartChainAction: + td.testCfg.startChain(action, td.target, td.verbose) + case StartSovereignChainAction: + td.testCfg.startSovereignChain(action, td.target, td.verbose) + case LegacyUpgradeProposalAction: + td.testCfg.submitLegacyUpgradeProposal(action, td.target, td.verbose) + case WaitUntilBlockAction: + td.testCfg.waitUntilBlockOnChain(action) + case ChangeoverChainAction: + td.testCfg.changeoverChain(action, td.target, td.verbose) + case SendTokensAction: + td.testCfg.sendTokens(action, td.target, td.verbose) + case SubmitTextProposalAction: + td.testCfg.submitTextProposal(action, td.target, td.verbose) + case SubmitConsumerAdditionProposalAction: + td.testCfg.submitConsumerAdditionProposal(action, td.target, td.verbose) + case SubmitConsumerRemovalProposalAction: + td.testCfg.submitConsumerRemovalProposal(action, td.target, td.verbose) + case SubmitParamChangeLegacyProposalAction: + td.testCfg.submitParamChangeProposal(action, td.target, td.verbose) + case VoteGovProposalAction: + td.testCfg.voteGovProposal(action, td.target, td.verbose) + case StartConsumerChainAction: + td.testCfg.startConsumerChain(action, td.target, td.verbose) + case AddChainToRelayerAction: + td.testCfg.addChainToRelayer(action, td.target, td.verbose) + case CreateIbcClientsAction: + td.testCfg.createIbcClientsHermes(action, td.target, td.verbose) + case AddIbcConnectionAction: + td.testCfg.addIbcConnection(action, td.target, td.verbose) + case AddIbcChannelAction: + td.testCfg.addIbcChannel(action, td.target, td.verbose) + case TransferChannelCompleteAction: + td.testCfg.transferChannelComplete(action, td.target, td.verbose) + case RelayPacketsAction: + td.testCfg.relayPackets(action, td.target, td.verbose) + case RelayRewardPacketsToProviderAction: + td.testCfg.relayRewardPacketsToProvider(action, td.target, td.verbose) + case DelegateTokensAction: + td.testCfg.delegateTokens(action, td.target, td.verbose) + case UnbondTokensAction: + td.testCfg.unbondTokens(action, td.target, td.verbose) + case CancelUnbondTokensAction: + td.testCfg.cancelUnbondTokens(action, td.target, td.verbose) + case RedelegateTokensAction: + td.testCfg.redelegateTokens(action, td.target, td.verbose) + case DowntimeSlashAction: + td.testCfg.invokeDowntimeSlash(action, td.target, td.verbose) + case UnjailValidatorAction: + td.testCfg.unjailValidator(action, td.target, td.verbose) + case DoublesignSlashAction: + td.testCfg.invokeDoublesignSlash(action, td.target, td.verbose) + case LightClientAmnesiaAttackAction: + td.testCfg.lightClientAmnesiaAttack(action, td.verbose) + case LightClientEquivocationAttackAction: + td.testCfg.lightClientEquivocationAttack(action, td.verbose) + case LightClientLunaticAttackAction: + td.testCfg.lightClientLunaticAttack(action, td.verbose) + case RegisterRepresentativeAction: + td.testCfg.registerRepresentative(action, td.target, td.verbose) + case AssignConsumerPubKeyAction: + td.testCfg.assignConsumerPubKey(action, td.target, td.verbose) + case SlashMeterReplenishmentAction: + td.testCfg.waitForSlashMeterReplenishment(action, td.verbose) + case WaitTimeAction: + td.testCfg.waitForTime(action, td.verbose) + case StartRelayerAction: + td.testCfg.startRelayer(action, td.target, td.verbose) + case ForkConsumerChainAction: + td.testCfg.forkConsumerChain(action, td.verbose) + case UpdateLightClientAction: + td.testCfg.updateLightClient(action, td.verbose) + case StartConsumerEvidenceDetectorAction: + td.testCfg.startConsumerEvidenceDetector(action, td.target, td.verbose) + case SubmitChangeRewardDenomsProposalAction: + td.testCfg.submitChangeRewardDenomsProposal(action, td.target, td.verbose) + default: + log.Fatalf("unknown action in testRun %s: %#v", td.testCfg.name, action) + } + return nil +} diff --git a/tests/e2e/test_runner.go b/tests/e2e/test_runner.go new file mode 100644 index 0000000000..adcf31d592 --- /dev/null +++ b/tests/e2e/test_runner.go @@ -0,0 +1,52 @@ +package main + +import "fmt" + +// A test runner drives the execution of test cases +// It sets up the test environment and the test driver to run the tests +type TestRunner struct { + config TestConfig + steps []Step + testDriver TestCaseDriver + target ExecutionTarget + verbose bool +} + +// Run will set up the test environment and executes the tests +func (tr *TestRunner) Run() error { + err := tr.checkConfig() + if err != nil { + return err + } + + err = tr.setupEnvironment(tr.target) + if err != nil { + return fmt.Errorf("error setting up test environment: %v", err) + } + + tr.testDriver = GetTestCaseDriver(tr.config) + err = tr.testDriver.Run(tr.steps, tr.target, tr.verbose) + if err != nil { + // not tearing down environment for troubleshooting reasons on container + return fmt.Errorf("test run '%s' failed: %v", tr.config.name, err) + } + return tr.teardownEnvironment() +} + +func (tr *TestRunner) checkConfig() error { + tr.config.validateStringLiterals() + return nil +} +func (tr *TestRunner) setupEnvironment(target ExecutionTarget) error { + tr.target = target + return target.Start() +} + +func (tr *TestRunner) teardownEnvironment() error { + return tr.target.Stop() +} + +func (tr *TestRunner) Setup(testCfg TestConfig) error { + tr.config = testCfg + return nil +} diff --git a/tests/e2e/test_target.go b/tests/e2e/test_target.go new file mode 100644 index 0000000000..23de16127f --- /dev/null +++ b/tests/e2e/test_target.go @@ -0,0 +1,228 @@ +package main + +import ( + "bufio" + "fmt" + "log" + "os" + "os/exec" + "strings" +) + +type ExecutionTarget interface { + GetTargetType() string + GetTestScriptPath(isConsumer bool, script string) string + // ExecCommand: when executed the command will run and return after completion + ExecCommand(name string, arg ...string) *exec.Cmd + // ExecDetachedCommand: when executed the command will be run in the background and call will return immediately + ExecDetachedCommand(name string, args ...string) *exec.Cmd + Start() error + Stop() error + Build() error + Delete() error +} + +type DockerContainer struct { + targetConfig TargetConfig + containerCfg ContainerConfig + images []string //images needed to build the target container + ImageName string +} + +func (dc *DockerContainer) GetTargetType() string { + return "docker" +} + +func generateImageName(version string, cfg TargetConfig) (string, error) { + // identify a tag name + tagName := "" + if version == "" { + tagName = "local" // this refers to build from local workspace + } else { + // use git hash of rev as docker image tag + //cmd := exec.Command("git", "rev-parse", "--verify", "--short", version) + cmd := exec.Command("git", "log", "--pretty=format:%h", "-n", "1", version) + out, err := cmd.CombinedOutput() + if err != nil { + return "", fmt.Errorf("error getting hash for revision: %v, '%s'", err, out) + } + tagName = strings.TrimSpace(string(out)) + } + + imageName := "cosmos-ics" + if cfg.useGaia { + imageName += "_gaia" + tagName += "-" + cfg.gaiaTag + } + if cfg.localSdkPath != "" { + imageName += "_sdk" + } + + return fmt.Sprintf("%s:%s", imageName, tagName), nil +} + +// Build the docker image for the target container +func (dc *DockerContainer) Build() error { + consumerVersion := dc.targetConfig.consumerVersion + providerVersion := dc.targetConfig.providerVersion + + consumerImageName, err := generateImageName(consumerVersion, dc.targetConfig) + if err != nil { + return fmt.Errorf("failed building docker image: %v", err) + } + err = buildDockerImage(consumerImageName, consumerVersion, dc.targetConfig, false) + if err != nil { + return err + } + dc.images = append(dc.images, consumerImageName) + + if consumerVersion == "" && consumerVersion == providerVersion { + dc.ImageName = consumerImageName + return nil + } + + // build image for provider as it's a different version to be run + providerImageName, err := generateImageName(providerVersion, dc.targetConfig) + if err != nil { + return fmt.Errorf("failed building docker image: %v", err) + } + err = buildDockerImage(providerImageName, providerVersion, dc.targetConfig, false) + if err != nil { + return err + } + dc.images = append(dc.images, providerImageName) + + // build combined image using provider/consumer versions from images built above + combinedImageName := fmt.Sprintf("cosmos-ics-combined:%s_%s", + strings.Split(providerImageName, ":")[1], + strings.Split(consumerImageName, ":")[1]) + //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. + cmd := exec.Command("docker", "build", "-f", "Dockerfile.combined", + "--build-arg", fmt.Sprintf("PROVIDER_IMAGE=%s", providerImageName), + "--build-arg", fmt.Sprintf("CONSUMER_IMAGE=%s", consumerImageName), + "-t", combinedImageName, + ".") + out, err := cmd.CombinedOutput() + if err != nil { + log.Printf("Failed running: %v", cmd) + return fmt.Errorf("error building combined docker image: %v, %s", err, string(out)) + } + dc.images = append(dc.images, combinedImageName) + dc.ImageName = combinedImageName + return err +} + +func (dc *DockerContainer) Delete() error { + for _, img := range dc.images { + //#nosec G204 -- Bypass linter warning for spawning subprocess with variable + cmd := exec.Command("docker", "image", "rm", img) + out, err := cmd.CombinedOutput() + //TODO: ignore errors related to non-existing images + if err != nil { + log.Printf("failed deleting image '%v' (%v): %v", cmd, err, string(out)) + } + } + return nil +} + +// ExecCommand returns the command struct to execute the named program with +// given arguments on the current target (docker container) +func (dc *DockerContainer) ExecCommand(name string, arg ...string) *exec.Cmd { + args := []string{"exec", dc.containerCfg.InstanceName, name} + args = append(args, arg...) + //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. + return exec.Command("docker", args...) +} + +// ExecDetachedCommand returns the command struct to execute the named program with +// given arguments on the current target (docker container) in _detached_ mode +func (dc *DockerContainer) ExecDetachedCommand(name string, arg ...string) *exec.Cmd { + args := []string{"exec", "-d", dc.containerCfg.InstanceName, name} + args = append(args, arg...) + //#nosec G204 -- Bypass linter warning for spawning subprocess with variable + return exec.Command("docker", args...) +} + +// Get path to testnet-script on target for a specific chain type +// Needed for different consumer/provider versions staged in one container +func (dc *DockerContainer) GetTestScriptPath(isConsumer bool, script string) string { + path := "/testnet-scripts" + // in case the provider and consumer version differ the test-scripts are in dedicated directories on the target + // for each of them (see Docker.combined) + if dc.targetConfig.providerVersion != dc.targetConfig.consumerVersion { + if !isConsumer { + fmt.Printf("Using script path for provider version '%s'\n", dc.targetConfig.providerVersion) + path = "/provider/testnet-scripts" + } else { + fmt.Printf("Using script path for consumer version '%s'\n", dc.targetConfig.consumerVersion) + path = "/consumer/testnet-scripts" + } + } + // no combined image (see Dockerfile) + return strings.Join([]string{path, script}, string(os.PathSeparator)) +} + +// Startup the container +func (dc *DockerContainer) Start() error { + fmt.Println("Starting container: ", dc.containerCfg.InstanceName) + // Remove existing containers from previous runs + if err := dc.Stop(); err != nil { + return err + } + + // Run new test container instance with extended privileges. + // Extended privileges are granted to the container here to allow for network namespace manipulation (bringing a node up/down) + // See: https://docs.docker.com/engine/reference/run/#runtime-privilege-and-linux-capabilities + beaconScript := dc.GetTestScriptPath(false, "beacon.sh") + //#nosec G204 -- subprocess launched with potential tainted input (no production code) + cmd := exec.Command("docker", "run", "--name", dc.containerCfg.InstanceName, + "--cap-add=NET_ADMIN", "--privileged", dc.ImageName, + "/bin/bash", beaconScript) + + cmdReader, err := cmd.StdoutPipe() + if err != nil { + log.Fatal(err) + } + cmd.Stderr = cmd.Stdout + + if err := cmd.Start(); err != nil { + log.Fatal(err) + } + scanner := bufio.NewScanner(cmdReader) + + // Wait until container is up + for scanner.Scan() { + out := scanner.Text() + if verbose != nil && *verbose { + fmt.Println("startDocker: " + out) + } + if out == "beacon!!!!!!!!!!" { + return nil + } + } + if err := scanner.Err(); err != nil { + return fmt.Errorf("error bringing up container: %v", err) + } + + err = cmd.Wait() + return fmt.Errorf("starting container exited with error: %v", err) +} + +// Stop will stop the container and remove it +func (dc *DockerContainer) Stop() error { + fmt.Println("Stopping existing containers: ", dc.containerCfg.InstanceName) + //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. + cmd := exec.Command("docker", "stop", dc.containerCfg.InstanceName) + bz, err := cmd.CombinedOutput() + if err != nil && !strings.Contains(string(bz), "No such container") { + return fmt.Errorf("error stopping docker container: %v, %s", err, string(bz)) + } + + //#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments. + cmd = exec.Command("docker", "rm", dc.containerCfg.InstanceName) + bz, err = cmd.CombinedOutput() + if err != nil && !strings.Contains(string(bz), "No such container") { + return fmt.Errorf("error removing docker container: %v, %s", err, string(bz)) + } + return nil +} diff --git a/tests/e2e/testnet-scripts/fork-consumer.sh b/tests/e2e/testnet-scripts/fork-consumer.sh index 7c12438b71..db8199d192 100644 --- a/tests/e2e/testnet-scripts/fork-consumer.sh +++ b/tests/e2e/testnet-scripts/fork-consumer.sh @@ -53,7 +53,7 @@ enabled = true [[chains]] id = "consu" ccv_consumer_chain = true -account_prefix = "cosmos" +account_prefix = "consumer" clock_drift = "5s" gas_multiplier = 1.1 grpc_addr = "tcp://$CONS_CHAIN_PREFIX.252:9091" diff --git a/tests/e2e/testnet-scripts/sovereign-genesis.json b/tests/e2e/testnet-scripts/sovereign-genesis.json index c3ae9da36c..ba95b2fbce 100644 --- a/tests/e2e/testnet-scripts/sovereign-genesis.json +++ b/tests/e2e/testnet-scripts/sovereign-genesis.json @@ -35,7 +35,7 @@ "accounts": [ { "@type": "/cosmos.auth.v1beta1.BaseAccount", - "address": "cosmos19pe9pg5dv9k5fzgzmsrgnw9rl9asf7ddwhu7lm", + "address": "consumer19pe9pg5dv9k5fzgzmsrgnw9rl9asf7ddtz33vu", "pub_key": null, "account_number": "0", "sequence": "0" @@ -52,7 +52,7 @@ }, "balances": [ { - "address": "cosmos19pe9pg5dv9k5fzgzmsrgnw9rl9asf7ddwhu7lm", + "address": "consumer19pe9pg5dv9k5fzgzmsrgnw9rl9asf7ddtz33vu", "coins": [ { "denom": "stake", @@ -125,8 +125,8 @@ "max_change_rate": "0.010000000000000000" }, "min_self_delegation": "1", - "delegator_address": "cosmos19pe9pg5dv9k5fzgzmsrgnw9rl9asf7ddwhu7lm", - "validator_address": "cosmosvaloper19pe9pg5dv9k5fzgzmsrgnw9rl9asf7ddtrgtng", + "delegator_address": "consumer19pe9pg5dv9k5fzgzmsrgnw9rl9asf7ddtz33vu", + "validator_address": "consumervaloper19pe9pg5dv9k5fzgzmsrgnw9rl9asf7ddy6jwzg", "pubkey": { "@type": "/cosmos.crypto.ed25519.PubKey", "key": "RrclQz9bIhkIy/gfL485g3PYMeiIku4qeo495787X10=" @@ -166,7 +166,7 @@ "tip": null }, "signatures": [ - "c7aD9dWzb85fn+Aq0ijMdhyJNJSOsOcFLvJt8ctvdxAAbwdrzKPVFbq9IYf1qCwKmfmQUrlFy40qiuQeXaZ8pg==" + "AZROMEeaBL9cDOWQJYYdAG3KDl+w37SK0XP88ecS+WwQQLj8rXuEKNDl1PXpZR0AFIJ8coSwhFEtbpV44j6uVQ==" ] } ] diff --git a/tests/e2e/testnet-scripts/start-docker.sh b/tests/e2e/testnet-scripts/start-docker.sh deleted file mode 100755 index a1d46e1f4d..0000000000 --- a/tests/e2e/testnet-scripts/start-docker.sh +++ /dev/null @@ -1,49 +0,0 @@ -#!/bin/bash - -# If -e is not set then if the build fails, it will use the old container, resulting in a very confusing debugging situation -# Setting -e makes it error out if the build fails -set -eux - -CONTAINER_NAME=$1 -INSTANCE_NAME=$2 -LOCAL_SDK_PATH=${3:-"default"} # Sets this var to default if null or unset -USE_GAIA_PROVIDER=${4:-"false"} # if true, use gaia as provider; if false, use ICS app -USE_GAIA_TAG=${5:-""} # gaia tag to use if using gaia as provider; by default the latest tag is used - -# Remove existing container instance -set +e -docker rm -f "$INSTANCE_NAME" -set -e - -# Delete old sdk directory if it exists -if [ -d "./cosmos-sdk" ]; then - rm -rf ./cosmos-sdk/ -fi - -# Copy sdk directory to working dir if path was specified -if [[ "$LOCAL_SDK_PATH" != "default" ]] -then - cp -n -r "$LOCAL_SDK_PATH" ./cosmos-sdk - printf "\n\nUsing local sdk version from %s\n\n\n" "$LOCAL_SDK_PATH" -else - printf "\n\nUsing default sdk version\n\n\n" -fi - -# Build the Docker container -if [[ "$USE_GAIA_PROVIDER" = "true" ]] -then - printf "\n\nUsing gaia as provider\n\n" - printf "\n\nUsing gaia tag %s\n\n" "$USE_GAIA_TAG" - docker build -f Dockerfile.gaia -t "$CONTAINER_NAME" --build-arg USE_GAIA_TAG="$USE_GAIA_TAG" . -else - printf "\n\nUsing ICS provider app as provider\n\n\n" - docker build -f Dockerfile -t "$CONTAINER_NAME" . -fi - -# Remove copied sdk directory -rm -rf ./cosmos-sdk/ - -# Run new test container instance with extended privileges. -# Extended privileges are granted to the container here to allow for network namespace manipulation (bringing a node up/down) -# See: https://docs.docker.com/engine/reference/run/#runtime-privilege-and-linux-capabilities -docker run --name "$INSTANCE_NAME" --cap-add=NET_ADMIN --privileged "$CONTAINER_NAME" /bin/bash /testnet-scripts/beacon.sh & diff --git a/x/ccv/consumer/keeper/keeper.go b/x/ccv/consumer/keeper/keeper.go index a43a773862..b8751344b8 100644 --- a/x/ccv/consumer/keeper/keeper.go +++ b/x/ccv/consumer/keeper/keeper.go @@ -501,13 +501,9 @@ func (k Keeper) SetOutstandingDowntime(ctx sdk.Context, address sdk.ConsAddress) } // DeleteOutstandingDowntime deletes the outstanding downtime flag for the given validator consensus address -func (k Keeper) DeleteOutstandingDowntime(ctx sdk.Context, consAddress string) { - consAddr, err := sdk.ConsAddressFromBech32(consAddress) - if err != nil { - return // TODO: this should panic with appropriate tests to validate the panic won't happen in normal cases. - } +func (k Keeper) DeleteOutstandingDowntime(ctx sdk.Context, address sdk.ConsAddress) { store := ctx.KVStore(k.storeKey) - store.Delete(types.OutstandingDowntimeKey(consAddr)) + store.Delete(types.OutstandingDowntimeKey(address)) } // GetAllOutstandingDowntimes gets an array of the validator addresses of outstanding downtime flags diff --git a/x/ccv/consumer/keeper/relay.go b/x/ccv/consumer/keeper/relay.go index 0de4b0c35f..2d4e16510a 100644 --- a/x/ccv/consumer/keeper/relay.go +++ b/x/ccv/consumer/keeper/relay.go @@ -82,8 +82,19 @@ func (k Keeper) OnRecvVSCPacket(ctx sdk.Context, packet channeltypes.Packet, new // remove outstanding slashing flags of the validators // for which the slashing was acknowledged by the provider chain - for _, addr := range newChanges.GetSlashAcks() { - k.DeleteOutstandingDowntime(ctx, addr) + for _, ack := range newChanges.GetSlashAcks() { + // get consensus address from bech32 address + consAddr, err := ccv.GetConsAddrFromBech32(ack) + if err != nil { + // Do not return an error as it would lead to the consumer being + // removed by the provider + k.Logger(ctx).Error("invalid consensus address in VSCPacket.SlashAcks", + "vscID", newChanges.ValsetUpdateId, + "SlashAck", ack, + "error", err) + continue + } + k.DeleteOutstandingDowntime(ctx, consAddr) } k.Logger(ctx).Info("finished receiving/handling VSCPacket", diff --git a/x/ccv/consumer/keeper/relay_test.go b/x/ccv/consumer/keeper/relay_test.go index 7c30c3e4f0..46a805a85a 100644 --- a/x/ccv/consumer/keeper/relay_test.go +++ b/x/ccv/consumer/keeper/relay_test.go @@ -123,8 +123,8 @@ func TestOnRecvVSCPacket(t *testing.T) { }}, }, { - "failure on packet with invalid slash acks", - true, + "success on packet with invalid slash acks", + false, channeltypes.NewPacket(pd3.GetBytes(), 4, types.ProviderPortID, providerCCVChannelID, types.ConsumerPortID, consumerCCVChannelID, clienttypes.NewHeight(1, 0), 0), types.ValidatorSetChangePacketData{ValidatorUpdates: []abci.ValidatorUpdate{ diff --git a/x/ccv/consumer/keeper/validators.go b/x/ccv/consumer/keeper/validators.go index 31e803bad4..24a1c5a57c 100644 --- a/x/ccv/consumer/keeper/validators.go +++ b/x/ccv/consumer/keeper/validators.go @@ -59,6 +59,10 @@ func (k Keeper) ApplyCCValidatorChanges(ctx sdk.Context, changes []abci.Validato // AfterValidatorBonded is called by the Slashing module and should not return an error. panic(err) } + // Sanity check: making sure the outstanding downtime flag is not + // set for this new validator. This is especially useful to deal with + // https://github.com/cosmos/interchain-security/issues/1569. + k.DeleteOutstandingDowntime(ctx, consAddr) } else { // edge case: we received an update for 0 power // but the validator is already deleted. Do not forward diff --git a/x/ccv/types/utils.go b/x/ccv/types/utils.go index f6e7ffc3aa..ae85240256 100644 --- a/x/ccv/types/utils.go +++ b/x/ccv/types/utils.go @@ -1,8 +1,10 @@ package types import ( + "errors" "reflect" "sort" + "strings" "time" clienttypes "github.com/cosmos/ibc-go/v7/modules/core/02-client/types" @@ -13,6 +15,7 @@ import ( cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/bech32" abci "github.com/cometbft/cometbft/abci/types" tmprotocrypto "github.com/cometbft/cometbft/proto/tendermint/crypto" @@ -106,3 +109,17 @@ func PanicIfZeroOrNil(x interface{}, nameForPanicMsg string) { panic("zero or nil value for " + nameForPanicMsg) } } + +// GetConsAddrFromBech32 returns a ConsAddress from a Bech32 with an arbitrary prefix +func GetConsAddrFromBech32(bech32str string) (sdk.ConsAddress, error) { + bech32Addr := strings.TrimSpace(bech32str) + if len(bech32Addr) == 0 { + return nil, errors.New("couldn't parse empty input") + } + // remove bech32 prefix + _, addr, err := bech32.DecodeAndConvert(bech32Addr) + if err != nil { + return nil, errors.New("couldn't find valid bech32") + } + return sdk.ConsAddress(addr), nil +} diff --git a/x/ccv/types/wire.go b/x/ccv/types/wire.go index c7cbe9e126..9c22522b74 100644 --- a/x/ccv/types/wire.go +++ b/x/ccv/types/wire.go @@ -30,13 +30,6 @@ func (vsc ValidatorSetChangePacketData) Validate() error { if vsc.ValsetUpdateId == 0 { return errorsmod.Wrap(ErrInvalidPacketData, "valset update id cannot be equal to zero") } - // Validate the slash acks - must be consensus addresses - for _, ack := range vsc.SlashAcks { - _, err := sdk.ConsAddressFromBech32(ack) - if err != nil { - return err - } - } return nil } diff --git a/x/ccv/types/wire_test.go b/x/ccv/types/wire_test.go index e8ba966c8c..ab6692912e 100644 --- a/x/ccv/types/wire_test.go +++ b/x/ccv/types/wire_test.go @@ -17,93 +17,40 @@ import ( ) func TestPacketDataValidateBasic(t *testing.T) { - pk1, err := cryptocodec.ToTmProtoPublicKey(ed25519.GenPrivKey().PubKey()) - require.NoError(t, err) - pk2, err := cryptocodec.ToTmProtoPublicKey(ed25519.GenPrivKey().PubKey()) + pk, err := cryptocodec.ToTmProtoPublicKey(ed25519.GenPrivKey().PubKey()) require.NoError(t, err) - cId := crypto.NewCryptoIdentityFromIntSeed(4732894342) - validSlashAck := cId.SDKValConsAddress().String() - tooLongSlashAck := string(make([]byte, 1024)) - cases := []struct { name string expError bool packetData types.ValidatorSetChangePacketData }{ { - "invalid: nil packet data", + "invalid: zero ValsetUpdateId", true, - types.NewValidatorSetChangePacketData(nil, 1, nil), + types.NewValidatorSetChangePacketData([]abci.ValidatorUpdate{}, 0, nil), }, { - "valid: empty packet data", - false, - types.NewValidatorSetChangePacketData([]abci.ValidatorUpdate{}, 2, nil), - }, - { - "invalid: slash ack not consensus address", + "invalid: nil ValidatorUpdates", true, - types.NewValidatorSetChangePacketData( - []abci.ValidatorUpdate{ - { - PubKey: pk1, - Power: 30, - }, - }, - 3, - []string{ - "some_string", - }, - ), + types.NewValidatorSetChangePacketData(nil, 1, nil), }, { - "valid: packet data with valid slash ack", + "valid: empty ValidatorUpdates", false, - types.NewValidatorSetChangePacketData( - []abci.ValidatorUpdate{ - { - PubKey: pk2, - Power: 20, - }, - }, - 4, - []string{ - validSlashAck, - }, - ), - }, - { - "invalid: slash ack is too long", - true, - types.NewValidatorSetChangePacketData( - []abci.ValidatorUpdate{ - { - PubKey: pk2, - Power: 20, - }, - }, - 5, - []string{ - tooLongSlashAck, - }, - ), + types.NewValidatorSetChangePacketData([]abci.ValidatorUpdate{}, 2, nil), }, { - "valid: packet data with nil slash ack", + "valid: one validator update", false, types.NewValidatorSetChangePacketData( []abci.ValidatorUpdate{ { - PubKey: pk1, + PubKey: pk, Power: 30, }, - { - PubKey: pk2, - Power: 20, - }, }, - 6, + 3, nil, ), },