From 76358d40fd456e4a47e114405dc42f7f8cb950ed Mon Sep 17 00:00:00 2001 From: Pham Anh Minh <1phamminh0811@gmail.com> Date: Wed, 3 Jan 2024 13:47:21 +0700 Subject: [PATCH 01/32] Init setup for e2e-test --- Makefile | 20 ++++++++++++++------ e2e.mk | 19 +++++++++++++++++++ tests/e2e/e2e_setup_test.go | 12 ++++++++++++ tests/e2e/e2e_test.go | 2 ++ tests/e2e/initialization/chain.go | 1 + 5 files changed, 48 insertions(+), 6 deletions(-) create mode 100644 e2e.mk create mode 100644 tests/e2e/e2e_setup_test.go create mode 100644 tests/e2e/e2e_test.go create mode 100644 tests/e2e/initialization/chain.go diff --git a/Makefile b/Makefile index f7830eb85..c924cdae7 100755 --- a/Makefile +++ b/Makefile @@ -112,7 +112,7 @@ endif # The below include contains the tools and runsim targets. include contrib/devtools/Makefile -all: tools install lint test +all: tools install lint test test-e2e build: go.sum ifeq ($(OS),Windows_NT) @@ -229,22 +229,30 @@ clean: ############################################################################### include sims.mk +include e2e.mk + +PACKAGES_UNIT=$(shell go list ./... | grep -v -e '/tests/e2e') +PACKAGES_E2E=$(shell cd tests/e2e && go list ./... | grep '/e2e') +TEST_PACKAGES=./... test: test-unit -test-all: test-unit test-race test-cover +test-all: test-unit test-race test-cover test-e2e test-unit: - @VERSION=$(VERSION) go test -mod=readonly -tags='ledger test_ledger_mock' ./... + @VERSION=$(VERSION) go test -mod=readonly -tags='ledger test_ledger_mock' $(PACKAGES_UNIT) test-race: - @VERSION=$(VERSION) go test -mod=readonly -race -tags='ledger test_ledger_mock' ./... + @VERSION=$(VERSION) go test -mod=readonly -race -tags='ledger test_ledger_mock' $(PACKAGES_UNIT) test-cover: - @go test -mod=readonly -timeout 30m -race -coverprofile=coverage.txt -covermode=atomic -tags='ledger test_ledger_mock' ./... + @go test -mod=readonly -timeout 30m -race -coverprofile=coverage.txt -covermode=atomic -tags='ledger test_ledger_mock' $(PACKAGES_UNIT) + +test-e2e: + @VERSION=$(VERSION) go test -mod=readonly -timeout 30m $(PACKAGES_E2E) benchmark: - @go test -mod=readonly -bench=. ./... + @go test -mod=readonly -bench=. $(PACKAGES_UNIT) .PHONY: test test-all test-cover test-unit test-race diff --git a/e2e.mk b/e2e.mk new file mode 100644 index 000000000..231b57bf8 --- /dev/null +++ b/e2e.mk @@ -0,0 +1,19 @@ +#!/usr/bin/make -f + +######################################## +### Simulations +e2e-help: + @echo "e2e subcommands" + @echo "" + @echo "Usage:" + @echo " make e2e-[command]" + @echo "" + @echo "Available Commands:" + @echo " e2e-build Build e2e debug Docker image" + @echo " setup Set up e2e environment" + +e2e-setup: e2e-build + @echo Finished e2e environment setup, ready to start the test + +e2e-build: + \ No newline at end of file diff --git a/tests/e2e/e2e_setup_test.go b/tests/e2e/e2e_setup_test.go new file mode 100644 index 000000000..36e297844 --- /dev/null +++ b/tests/e2e/e2e_setup_test.go @@ -0,0 +1,12 @@ +package e2e + +import ( + "github.com/stretchr/testify/suite" + + +) + +type IntegrationTestSuite struct { + suite.Suite + +} diff --git a/tests/e2e/e2e_test.go b/tests/e2e/e2e_test.go new file mode 100644 index 000000000..b70bf6651 --- /dev/null +++ b/tests/e2e/e2e_test.go @@ -0,0 +1,2 @@ +package e2e + diff --git a/tests/e2e/initialization/chain.go b/tests/e2e/initialization/chain.go new file mode 100644 index 000000000..58de3c933 --- /dev/null +++ b/tests/e2e/initialization/chain.go @@ -0,0 +1 @@ +package initialization \ No newline at end of file From 91f347fcf9d791caed8f0caf032c75d97248db82 Mon Sep 17 00:00:00 2001 From: Pham Anh Minh <1phamminh0811@gmail.com> Date: Wed, 3 Jan 2024 14:11:56 +0700 Subject: [PATCH 02/32] Add chain to e2e --- tests/e2e/initialization/chain.go | 41 ++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/tests/e2e/initialization/chain.go b/tests/e2e/initialization/chain.go index 58de3c933..d6c2ee359 100644 --- a/tests/e2e/initialization/chain.go +++ b/tests/e2e/initialization/chain.go @@ -1 +1,40 @@ -package initialization \ No newline at end of file +package initialization + +import ( + "github.com/tendermint/tendermint/p2p" + "github.com/tendermint/tendermint/privval" + + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" +) + +type ChainMeta struct { + DataDir string + Id string +} + +type Validator struct { + Chain *Chain + Index int + Moniker string + Mnemonic string + PublicAddress string + PublicKey cryptotypes.PubKey + PrivateKey cryptotypes.PrivKey + ConsensusKey privval.FilePVKey + ConsensusPrivKey cryptotypes.PrivKey + NodeKey p2p.NodeKey +} + +type Node struct { + Moniker string //nolint:unused + Mnemonic string + PublicAddress string + PublicKey cryptotypes.PubKey + PrivateKey cryptotypes.PrivKey +} + +type Chain struct { + ChainMeta ChainMeta + Validator []*Validator + Node []*Node +} \ No newline at end of file From 253e2c2c6ee7704940addd6146ea46c33b9339d4 Mon Sep 17 00:00:00 2001 From: Dong Lieu Date: Wed, 3 Jan 2024 15:52:00 +0700 Subject: [PATCH 03/32] add io to e2e --- tests/e2e/util/io.go | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 tests/e2e/util/io.go diff --git a/tests/e2e/util/io.go b/tests/e2e/util/io.go new file mode 100644 index 000000000..d1c891608 --- /dev/null +++ b/tests/e2e/util/io.go @@ -0,0 +1,42 @@ +package util + +import ( + "fmt" + "io" + "os" +) + +func CopyFile(src, dst string) (int64, error) { + sourceFileStat, err := os.Stat(src) + if err != nil { + return 0, err + } + + if !sourceFileStat.Mode().IsRegular() { + return 0, fmt.Errorf("%s is not a regular file", src) + } + + source, err := os.Open(src) + if err != nil { + return 0, err + } + defer source.Close() + + destination, err := os.Create(dst) + if err != nil { + return 0, err + } + defer destination.Close() + + nBytes, err := io.Copy(destination, source) + return nBytes, err +} + +func WriteFile(path string, body []byte) error { + _, err := os.Create(path) + if err != nil { + return err + } + + return os.WriteFile(path, body, 0o600) +} From 554eb69b45240e7c232472fb85471ff6c4cc4b40 Mon Sep 17 00:00:00 2001 From: Pham Anh Minh <1phamminh0811@gmail.com> Date: Sat, 6 Jan 2024 10:27:36 +0700 Subject: [PATCH 04/32] Add local.Dockerfile to build e2e SetupSuite rename fix pfm subspace --- app/keepers/keepers.go | 1 + e2e.mk | 1 + go.mod | 24 +- go.sum | 64 ++- local.Dockerfile | 30 ++ tests/e2e/e2e_encode_test.go | 31 ++ tests/e2e/e2e_ibc_test.go | 105 ++++ tests/e2e/e2e_setup_test.go | 721 ++++++++++++++++++++++++++ tests/e2e/e2e_slashing_test.go | 3 + tests/e2e/e2e_vesting_test.go | 58 +++ tests/e2e/initialization/chain.go | 157 +++++- tests/e2e/initialization/decode.go | 45 ++ tests/e2e/initialization/genesis.go | 191 +++++++ tests/e2e/initialization/keys.go | 20 + tests/e2e/initialization/validator.go | 283 ++++++++++ tests/e2e/util/address.go | 33 ++ 16 files changed, 1738 insertions(+), 29 deletions(-) create mode 100644 local.Dockerfile create mode 100644 tests/e2e/e2e_encode_test.go create mode 100644 tests/e2e/e2e_ibc_test.go create mode 100644 tests/e2e/e2e_slashing_test.go create mode 100644 tests/e2e/e2e_vesting_test.go create mode 100644 tests/e2e/initialization/decode.go create mode 100644 tests/e2e/initialization/genesis.go create mode 100644 tests/e2e/initialization/keys.go create mode 100644 tests/e2e/initialization/validator.go create mode 100644 tests/e2e/util/address.go diff --git a/app/keepers/keepers.go b/app/keepers/keepers.go index 5abaf6abd..3b38c320f 100644 --- a/app/keepers/keepers.go +++ b/app/keepers/keepers.go @@ -505,6 +505,7 @@ func initParamsKeeper( paramsKeeper.Subspace(wasmtypes.ModuleName) paramsKeeper.Subspace(dyncommtypes.ModuleName) paramsKeeper.Subspace(ibchooktypes.ModuleName) + paramsKeeper.Subspace(forwardtypes.ModuleName) return paramsKeeper } diff --git a/e2e.mk b/e2e.mk index 231b57bf8..f9a87b905 100644 --- a/e2e.mk +++ b/e2e.mk @@ -16,4 +16,5 @@ e2e-setup: e2e-build @echo Finished e2e environment setup, ready to start the test e2e-build: + @DOCKER_BUILDKIT=1 docker build -t core:local -f local.Dockerfile . \ No newline at end of file diff --git a/go.mod b/go.mod index 3fc5b0dca..bd2a3fdcc 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ require ( github.com/CosmWasm/wasmd v0.30.0 github.com/CosmWasm/wasmvm v1.1.2 github.com/cosmos/cosmos-sdk v0.46.15 + github.com/cosmos/go-bip39 v1.0.0 github.com/cosmos/gogoproto v1.4.11 github.com/cosmos/ibc-apps/middleware/packet-forward-middleware/v6 v6.1.1 github.com/cosmos/ibc-go/v6 v6.2.0 @@ -15,11 +16,13 @@ require ( github.com/google/gofuzz v1.2.0 github.com/gorilla/mux v1.8.0 github.com/grpc-ecosystem/grpc-gateway v1.16.0 + github.com/ory/dockertest/v3 v3.10.0 github.com/pkg/errors v0.9.1 github.com/rakyll/statik v0.1.7 github.com/spf13/cast v1.5.1 github.com/spf13/cobra v1.7.0 github.com/spf13/pflag v1.0.5 + github.com/spf13/viper v1.15.0 github.com/stretchr/testify v1.8.4 github.com/tendermint/tendermint v0.34.29 github.com/tendermint/tm-db v0.6.8-0.20221109095132-774cdfe7e6b0 @@ -39,7 +42,10 @@ require ( filippo.io/edwards25519 v1.0.0-rc.1 // indirect github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect github.com/99designs/keyring v1.2.1 // indirect + github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect github.com/ChainSafe/go-schnorrkel v1.0.0 // indirect + github.com/Microsoft/go-winio v0.6.0 // indirect + github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect github.com/Workiva/go-datastructures v1.0.53 // indirect github.com/armon/go-metrics v0.4.1 // indirect github.com/aws/aws-sdk-go v1.44.122 // indirect @@ -56,9 +62,9 @@ require ( github.com/coinbase/rosetta-sdk-go v0.7.9 // indirect github.com/cometbft/cometbft-db v0.7.0 // indirect github.com/confio/ics23/go v0.9.0 // indirect + github.com/containerd/continuity v0.3.0 // indirect github.com/cosmos/btcutil v1.0.5 // indirect github.com/cosmos/cosmos-proto v1.0.0-beta.3 // indirect - github.com/cosmos/go-bip39 v1.0.0 // indirect github.com/cosmos/gorocksdb v1.2.0 // indirect github.com/cosmos/iavl v0.19.7 // indirect github.com/cosmos/ledger-cosmos-go v0.12.2 // indirect @@ -71,7 +77,11 @@ require ( github.com/dgraph-io/badger/v2 v2.2007.4 // indirect github.com/dgraph-io/ristretto v0.1.0 // indirect github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 // indirect + github.com/docker/cli v20.10.17+incompatible // indirect github.com/docker/distribution v2.8.2+incompatible // indirect + github.com/docker/docker v20.10.19+incompatible // indirect + github.com/docker/go-connections v0.4.0 // indirect + github.com/docker/go-units v0.5.0 // indirect github.com/dustin/go-humanize v1.0.1-0.20200219035652-afde56e7acac // indirect github.com/dvsekhvalnov/jose2go v1.5.0 // indirect github.com/felixge/httpsnoop v1.0.1 // indirect @@ -88,6 +98,7 @@ require ( github.com/google/go-cmp v0.5.9 // indirect github.com/google/orderedcode v0.0.1 // indirect github.com/google/s2a-go v0.1.4 // indirect + github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/google/uuid v1.3.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect github.com/googleapis/gax-go/v2 v2.11.0 // indirect @@ -106,6 +117,7 @@ require ( github.com/hashicorp/hcl v1.0.0 // indirect github.com/hdevalence/ed25519consensus v0.0.0-20220222234857-c00d1f31bab3 // indirect github.com/iancoleman/orderedmap v0.2.0 // indirect + github.com/imdario/mergo v0.3.13 // indirect github.com/improbable-eng/grpc-web v0.15.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect @@ -123,8 +135,11 @@ require ( github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/go-testing-interface v1.14.1 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/moby/term v0.0.0-20220808134915-39b0c02b01ae // indirect github.com/mtibben/percent v0.2.1 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect + github.com/opencontainers/image-spec v1.1.0-rc2 // indirect + github.com/opencontainers/runc v1.1.5 // indirect github.com/pelletier/go-toml/v2 v2.0.7 // indirect github.com/petermattis/goid v0.0.0-20230317030725-371a4b8eda08 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect @@ -137,26 +152,31 @@ require ( github.com/rs/cors v1.8.2 // indirect github.com/rs/zerolog v1.29.1 // indirect github.com/sasha-s/go-deadlock v0.3.1 // indirect + github.com/sirupsen/logrus v1.9.0 // indirect github.com/spf13/afero v1.9.3 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect - github.com/spf13/viper v1.15.0 // indirect github.com/subosito/gotenv v1.4.2 // indirect github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d // indirect github.com/tecbot/gorocksdb v0.0.0-20191217155057-f0fad39f321c // indirect github.com/tendermint/go-amino v0.16.0 // indirect github.com/tidwall/btree v1.5.0 // indirect github.com/ulikunitz/xz v0.5.10 // indirect + github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect + github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect + github.com/xeipuuv/gojsonschema v1.2.0 // indirect github.com/zondax/hid v0.9.1 // indirect go.etcd.io/bbolt v1.3.6 // indirect go.opencensus.io v0.24.0 // indirect golang.org/x/crypto v0.12.0 // indirect golang.org/x/exp v0.0.0-20230811145659-89c5cff77bcb // indirect + golang.org/x/mod v0.11.0 // indirect golang.org/x/net v0.14.0 // indirect golang.org/x/oauth2 v0.8.0 // indirect golang.org/x/sync v0.2.0 // indirect golang.org/x/sys v0.11.0 // indirect golang.org/x/term v0.11.0 // indirect golang.org/x/text v0.12.0 // indirect + golang.org/x/tools v0.7.0 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect google.golang.org/api v0.126.0 // indirect google.golang.org/appengine v1.6.7 // indirect diff --git a/go.sum b/go.sum index fb52449bc..d76d7c8bd 100644 --- a/go.sum +++ b/go.sum @@ -206,6 +206,7 @@ github.com/Azure/azure-sdk-for-go/sdk/azcore v0.21.1/go.mod h1:fBF9PQNqB8scdgpZ3 github.com/Azure/azure-sdk-for-go/sdk/internal v0.8.3/go.mod h1:KLF4gFr6DcKFZwSuH8w8yEK6DpFl3LP5rhdvAb7Yz5I= github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v0.3.0/go.mod h1:tPaiy8S5bQ+S5sOiDlINkp7+Ef339+Nz5L5XO+cnOHo= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= +github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/ChainSafe/go-schnorrkel v1.0.0 h1:3aDA67lAykLaG1y3AOjs88dMxC88PgUuHRrLeDnvGIM= @@ -215,7 +216,9 @@ github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3 github.com/DataDog/zstd v1.5.0/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= github.com/Microsoft/go-winio v0.6.0 h1:slsWYD/zyx7lCXoZVlvQrj0hPTM1HI4+v1sIda2yDvg= +github.com/Microsoft/go-winio v0.6.0/go.mod h1:cTAf44im0RAYeL23bpB+fzCyDH2MJiz2BO69KH/soAE= github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= +github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= @@ -319,6 +322,7 @@ github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghf github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/checkpoint-restore/go-criu/v5 v5.3.0/go.mod h1:E/eQpaFtUKGOOSEBZgmKAcn+zUUwWxqcaKZlF54wK8E= github.com/cheggaaa/pb v1.0.27/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s= github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= @@ -329,6 +333,7 @@ github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5O github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2usCA= github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= github.com/classic-terra/cometbft v0.34.29-terra.0 h1:HnRGt7tijI2n5zSVrg/xh1mYYm4Gb4QFlknq+dRP8Jw= @@ -368,11 +373,14 @@ github.com/consensys/bavard v0.1.8-0.20210406032232-f3452dc9b572/go.mod h1:Bpd0/ github.com/consensys/bavard v0.1.8-0.20210915155054-088da2f7f54a/go.mod h1:9ItSMtA/dXMAiL7BG6bqW2m3NdSEObYWoH223nGHukI= github.com/consensys/gnark-crypto v0.4.1-0.20210426202927-39ac3d4b3f1f/go.mod h1:815PAHg3wvysy0SyIqanF8gZ0Y1wjk/hrDHD/iT88+Q= github.com/consensys/gnark-crypto v0.5.3/go.mod h1:hOdPlWQV1gDLp7faZVeg8Y0iEPFaOUnCc4XeCCk96p0= +github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= github.com/containerd/continuity v0.3.0 h1:nisirsYROK15TAMVukJOUyGJjz4BNQJBVsNvAXZJ/eg= +github.com/containerd/continuity v0.3.0/go.mod h1:wJEAIwKOm/pBZuBd0JmeTvnLquTB1Ag8espWhkykbPM= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cosmos/btcutil v1.0.5 h1:t+ZFcX77LpKtDBhjucvnOH8C2l2ioGsBNEQ3jef8xFk= @@ -405,7 +413,10 @@ github.com/creachadair/taskgroup v0.3.2 h1:zlfutDS+5XG40AOxcHDSThxKzns8Tnr9jnr6V github.com/creachadair/taskgroup v0.3.2/go.mod h1:wieWwecHVzsidg2CsUnFinW1faVN4+kq+TDlRJQ0Wbk= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw= +github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4= +github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= github.com/danieljoos/wincred v1.1.2 h1:QLdCxFs1/Yl4zduvBdcHB8goaYk9RARS2SgLLRuAyr0= github.com/danieljoos/wincred v1.1.2/go.mod h1:GijpziifJoIBfYh+S7BbkdUTU4LfM+QnGqR5Vl2tAx0= github.com/dave/jennifer v1.2.0/go.mod h1:fIb+770HOpJ2fmN9EPPKOqm1vMGhB+TwXKMZhrIygKg= @@ -438,11 +449,18 @@ github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8 github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= github.com/dnaeon/go-vcr v1.1.0/go.mod h1:M7tiix8f0r6mKKJ3Yq/kqU1OYf3MnfmBWVbPx/yU9ko= github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= +github.com/docker/cli v20.10.17+incompatible h1:eO2KS7ZFeov5UJeaDmIs1NFEDRf32PaqRpvoEkKBy5M= +github.com/docker/cli v20.10.17+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8= github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/docker v1.4.2-0.20180625184442-8e610b2b55bf/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v20.10.19+incompatible h1:lzEmjivyNHFHMNAFLXORMBXyGIhw/UP4DvJwvyKYq64= +github.com/docker/docker v20.10.19+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= +github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= +github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/dop251/goja v0.0.0-20211011172007-d99e4b8cbf48/go.mod h1:R9ET47fwRVRPZnOGvHxxhuZcbrMCuiqOz3Rlrh4KSnk= github.com/dop251/goja_nodejs v0.0.0-20210225215109-d91c329300e7/go.mod h1:hn7BA7c8pLvoGndExHudxTDKZ84Pyvv+90pbBjbTz0Y= github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= @@ -480,6 +498,7 @@ github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/ github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= +github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= @@ -524,6 +543,7 @@ github.com/go-playground/validator/v10 v10.11.2/go.mod h1:NieE624vt4SCTJtD87arVL github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee h1:s+21KNqlpePfkah2I+gwHF8xmJWRjooY+5248k6m4A0= github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= @@ -536,6 +556,7 @@ github.com/goccy/go-json v0.10.0/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MG github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 h1:ZpnhV/YsD2/4cESfV5+Hoeu/iUR3ruzNvZ+yQfO03a0= github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/gateway v1.1.0 h1:u0SuhL9+Il+UbjM9VIE3ntfRujKbvVpFvNB4HbjeVQ0= github.com/gogo/gateway v1.1.0/go.mod h1:S7rR8FRQyG3QFESeSv4l2WnsyzlCLG0CzBbUUo/mbic= @@ -640,6 +661,8 @@ github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLe github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/s2a-go v0.1.4 h1:1kZ/sQM3srePvKs3tXAvQzo66XfcReoqFpIpIccE7Oc= github.com/google/s2a-go v0.1.4/go.mod h1:Ej+mSEMGRnqRzjc7VtF+jdBwYG5fuJfiZ8ELkjEwM0A= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -744,6 +767,8 @@ github.com/iancoleman/orderedmap v0.2.0 h1:sq1N/TFpYH++aViPcaKjys3bDClUEU7s5B+z6 github.com/iancoleman/orderedmap v0.2.0/go.mod h1:N0Wam8K1arqPXNWjMo21EXnBPOPp36vB07FNRdD2geA= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= +github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg= github.com/improbable-eng/grpc-web v0.15.0 h1:BN+7z6uNXZ1tQGcNAuaU1YjsLTApzkjt2tzCixLaUPQ= github.com/improbable-eng/grpc-web v0.15.0/go.mod h1:1sy9HKV4Jt9aEs9JSnkWlRJPuPtwNr0l57L4f878wP8= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= @@ -892,6 +917,9 @@ github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RR github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/pointerstructure v1.2.0/go.mod h1:BRAsLI5zgXmw97Lf6s25bs8ohIXc3tViBH44KcwB2g4= +github.com/moby/sys/mountinfo v0.5.0/go.mod h1:3bMD3Rg+zkqx8MRYPi7Pyb0Ie97QEBmdxbhnCLlSvSU= +github.com/moby/term v0.0.0-20220808134915-39b0c02b01ae h1:O4SWKdcHVCvYqyDV+9CJA1fcDN2L11Bule0iFy3YlAI= +github.com/moby/term v0.0.0-20220808134915-39b0c02b01ae/go.mod h1:E2VnQOmVuvZB6UYnnDB0qG5Nq/1tD9acaOpo6xmt0Kw= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -900,6 +928,7 @@ github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3Rllmb github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8= +github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ= github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE+eKJXWVjKXM4ck2QobLqTDytGJbLLhJg= github.com/mtibben/percent v0.2.1 h1:5gssi8Nqo8QU/r2pynCm+hBQHpkB/uNK7BJCFogWdzs= github.com/mtibben/percent v0.2.1/go.mod h1:KG9uO+SZkUp+VkRHsCdYQV3XSZrrSpR3O9ibNBTZrns= @@ -939,7 +968,11 @@ github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWEr github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.0-rc2 h1:2zx/Stx4Wc5pIPDvIxHXvXtQFW/7XWJGmnM7r3wg034= -github.com/opencontainers/runc v1.1.3 h1:vIXrkId+0/J2Ymu2m7VjGvbSlAId9XNRPhn2p4b+d8w= +github.com/opencontainers/image-spec v1.1.0-rc2/go.mod h1:3OVijpioIKYWTqjiG0zfF6wvoJ4fAXGbjdZuI2NgsRQ= +github.com/opencontainers/runc v1.1.5 h1:L44KXEpKmfWDcS02aeGm8QNTFXTo2D+8MYGDIJ/GDEs= +github.com/opencontainers/runc v1.1.5/go.mod h1:1J5XiS+vdZ3wCyZybsuxXZWGrgSr8fFJHLXuG2PsnNg= +github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/selinux v1.10.0/go.mod h1:2i0OySw99QjzBBQByd1Gr9gSjvuho1lHsJxIJ3gGbJI= github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= @@ -950,6 +983,8 @@ github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJ github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= github.com/ory/dockertest v3.3.5+incompatible h1:iLLK6SQwIhcbrG783Dghaaa3WPzGc+4Emza6EbVUUGA= +github.com/ory/dockertest/v3 v3.10.0 h1:4K3z2VMe8Woe++invjaTB7VRyQXQy5UY+loujO4aNE4= +github.com/ory/dockertest/v3 v3.10.0/go.mod h1:nr57ZbRWMqfsdGdFNLHz5jjNdDb7VVFnzAeW1n5N1Lg= github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= @@ -1047,6 +1082,7 @@ github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0 github.com/sasha-s/go-deadlock v0.3.1 h1:sqv7fDNShgjcaxkO0JNcOAlr8B9+cV5Ey/OB71efZx0= github.com/sasha-s/go-deadlock v0.3.1/go.mod h1:F73l+cr82YSh10GxyRI6qZiCgK64VaZjwesgfQ1/iLM= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/seccomp/libseccomp-golang v0.9.2-0.20220502022130-f33da4d89646/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg= github.com/segmentio/fasthash v1.0.3/go.mod h1:waKX8l2N8yckOgmSsXJi7x1ZfdKZ4x7KRMzBtS3oedY= github.com/segmentio/kafka-go v0.1.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfPOCvTvk+EJo= github.com/segmentio/kafka-go v0.2.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfPOCvTvk+EJo= @@ -1057,7 +1093,9 @@ github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPx github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= +github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= @@ -1108,6 +1146,7 @@ github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcU github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= +github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/tecbot/gorocksdb v0.0.0-20191217155057-f0fad39f321c h1:g+WoO5jjkqGAzHWCjJB1zZfXPIAaDpzXIEJ0eS6B5Ok= github.com/tecbot/gorocksdb v0.0.0-20191217155057-f0fad39f321c/go.mod h1:ahpPrc7HpcfEWDQRZEmnXMzHY03mLDYMCxeDzy46i+8= github.com/tendermint/go-amino v0.16.0 h1:GyhmgQKvqF82e2oZeuMSp9JTN0N09emoSZlb2lyGa2E= @@ -1147,9 +1186,17 @@ github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/X github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= +github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= +github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc= github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= github.com/willf/bitset v1.1.3/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= +github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= +github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xlab/treeprint v0.0.0-20180616005107-d6fb6747feb6/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= @@ -1261,6 +1308,7 @@ golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU= +golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1392,6 +1440,7 @@ golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1401,6 +1450,7 @@ golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1457,9 +1507,12 @@ golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210819135213-f52c844e1c1c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1474,6 +1527,7 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220624220833-87e55d714810/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1528,6 +1582,7 @@ golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBn golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -1577,7 +1632,8 @@ golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= +golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4= +golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -1864,9 +1920,13 @@ gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= +gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= +gotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/local.Dockerfile b/local.Dockerfile new file mode 100644 index 000000000..1a6ea76c6 --- /dev/null +++ b/local.Dockerfile @@ -0,0 +1,30 @@ +FROM golang:1.20 AS go-builder + +# Install minimum necessary dependencies, build Cosmos SDK, remove packages +RUN apt update +RUN apt install -y curl git build-essential +# debug: for live editting in the image +RUN apt install -y vim + +WORKDIR /code +COPY . /code/ + +RUN LEDGER_ENABLED=false make build + +RUN cp /go/pkg/mod/github.com/classic-terra/wasmvm@v*/internal/api/libwasmvm.aarch64.so /lib/libwasmvm.aarch64.so + +FROM ubuntu:22.04 + +WORKDIR /root + +COPY --from=go-builder /code/build/terrad /usr/local/bin/terrad +COPY --from=go-builder /lib/libwasmvm.aarch64.so /lib/libwasmvm.aarch64.so + +# rest server +EXPOSE 1317 +# grpc +EXPOSE 9090 +# tendermint p2p +EXPOSE 26656 +# tendermint rpc +EXPOSE 26657 \ No newline at end of file diff --git a/tests/e2e/e2e_encode_test.go b/tests/e2e/e2e_encode_test.go new file mode 100644 index 000000000..f1a8d2c7e --- /dev/null +++ b/tests/e2e/e2e_encode_test.go @@ -0,0 +1,31 @@ +package e2e + +import ( + "encoding/base64" + // "path/filepath" + + "github.com/classic-terra/core/v2/tests/e2e/initialization" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +const ( + rawTxFile = "tx_raw.json" +) + +// buildRawTx build a dummy tx using the TxBuilder and +// return the JSON and encoded tx's +func buildRawTx() ([]byte, string, error) { + builder := initialization.TxConfig.NewTxBuilder() + builder.SetGasLimit(gas) + builder.SetFeeAmount(sdk.NewCoins(standardFees)) + builder.SetMemo("foomemo") + tx, err := initialization.TxConfig.TxJSONEncoder()(builder.GetTx()) + if err != nil { + return nil, "", err + } + txBytes, err := initialization.TxConfig.TxEncoder()(builder.GetTx()) + if err != nil { + return nil, "", err + } + return tx, base64.StdEncoding.EncodeToString(txBytes), err +} diff --git a/tests/e2e/e2e_ibc_test.go b/tests/e2e/e2e_ibc_test.go new file mode 100644 index 000000000..927339a74 --- /dev/null +++ b/tests/e2e/e2e_ibc_test.go @@ -0,0 +1,105 @@ +package e2e + +import ( + "bytes" + "context" + // "encoding/json" + // "fmt" + // "strconv" + // "strings" + "time" + + "github.com/ory/dockertest/v3/docker" + // "github.com/cosmos/cosmos-sdk/client/flags" + // sdk "github.com/cosmos/cosmos-sdk/types" +) + +func (s *IntegrationTestSuite) createConnection() { + s.T().Logf("connecting %s and %s chains via IBC", s.chainA.ChainMeta.Id, s.chainB.ChainMeta.Id) + + ctx, cancel := context.WithTimeout(context.Background(), time.Minute) + defer cancel() + + exec, err := s.dkrPool.Client.CreateExec(docker.CreateExecOptions{ + Context: ctx, + AttachStdout: true, + AttachStderr: true, + Container: s.hermesResource0.Container.ID, + User: "root", + Cmd: []string{ + "hermes", + "create", + "connection", + "--a-chain", + s.chainA.ChainMeta.Id, + "--b-chain", + s.chainB.ChainMeta.Id, + }, + }) + s.Require().NoError(err) + + var ( + outBuf bytes.Buffer + errBuf bytes.Buffer + ) + + err = s.dkrPool.Client.StartExec(exec.ID, docker.StartExecOptions{ + Context: ctx, + Detach: false, + OutputStream: &outBuf, + ErrorStream: &errBuf, + }) + s.Require().NoErrorf( + err, + "failed connect chains; stdout: %s, stderr: %s", outBuf.String(), errBuf.String(), + ) + + s.T().Logf("connected %s and %s chains via IBC", s.chainA.ChainMeta.Id, s.chainB.ChainMeta.Id) +} + +func (s *IntegrationTestSuite) createChannel() { + s.T().Logf("connecting %s and %s chains via IBC", s.chainA.ChainMeta.Id, s.chainB.ChainMeta.Id) + + ctx, cancel := context.WithTimeout(context.Background(), time.Minute) + defer cancel() + + exec, err := s.dkrPool.Client.CreateExec(docker.CreateExecOptions{ + Context: ctx, + AttachStdout: true, + AttachStderr: true, + Container: s.hermesResource0.Container.ID, + User: "root", + Cmd: []string{ + "hermes", + txCommand, + "chan-open-init", + "--dst-chain", + s.chainA.ChainMeta.Id, + "--src-chain", + s.chainB.ChainMeta.Id, + "--dst-connection", + "connection-0", + "--src-port=transfer", + "--dst-port=transfer", + }, + }) + s.Require().NoError(err) + + var ( + outBuf bytes.Buffer + errBuf bytes.Buffer + ) + + err = s.dkrPool.Client.StartExec(exec.ID, docker.StartExecOptions{ + Context: ctx, + Detach: false, + OutputStream: &outBuf, + ErrorStream: &errBuf, + }) + s.Require().NoErrorf( + err, + "failed connect chains; stdout: %s, stderr: %s", outBuf.String(), errBuf.String(), + ) + + s.T().Logf("connected %s and %s chains via IBC", s.chainA.ChainMeta.Id, s.chainB.ChainMeta.Id) +} diff --git a/tests/e2e/e2e_setup_test.go b/tests/e2e/e2e_setup_test.go index 36e297844..b5498fbbd 100644 --- a/tests/e2e/e2e_setup_test.go +++ b/tests/e2e/e2e_setup_test.go @@ -1,12 +1,733 @@ package e2e import ( + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "os" + "os/exec" + "path" + "path/filepath" + "strings" + "testing" + "time" + + "github.com/ory/dockertest/v3" + "github.com/ory/dockertest/v3/docker" + "github.com/spf13/viper" "github.com/stretchr/testify/suite" + tmconfig "github.com/tendermint/tendermint/config" + tmjson "github.com/tendermint/tendermint/libs/json" + "github.com/tendermint/tendermint/libs/rand" + rpchttp "github.com/tendermint/tendermint/rpc/client/http" + + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + "github.com/cosmos/cosmos-sdk/crypto/hd" + "github.com/cosmos/cosmos-sdk/crypto/keyring" + "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" + "github.com/cosmos/cosmos-sdk/server" + srvconfig "github.com/cosmos/cosmos-sdk/server/config" + sdk "github.com/cosmos/cosmos-sdk/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + authvesting "github.com/cosmos/cosmos-sdk/x/auth/vesting/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + distrtypes "github.com/cosmos/cosmos-sdk/x/distribution/types" + evidencetypes "github.com/cosmos/cosmos-sdk/x/evidence/types" + genutiltypes "github.com/cosmos/cosmos-sdk/x/genutil/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + + e2einit "github.com/classic-terra/core/v2/tests/e2e/initialization" + e2eutil "github.com/classic-terra/core/v2/tests/e2e/util" +) + +const ( + terradBinary = "terrad" + txCommand = "tx" + queryCommand = "query" + keysCommand = "keys" + terraHomePath = "/home/nonroot/.terra" + photonDenom = "photon" + ulunaDenom = "uluna" + stakeDenom = "stake" + initBalanceStr = "110000000000stake,100000000000000000photon,100000000000000000uluna" + minGasPrice = "0.00001" + initialGlobalFeeAmt = "0.00001" + lowGlobalFeesAmt = "0.000001" + highGlobalFeeAmt = "0.0001" + maxTotalBypassMinFeeMsgGasUsage = "1" + gas = 200000 + govProposalBlockBuffer = 35 + relayerAccountIndexHermes0 = 0 + relayerAccountIndexHermes1 = 1 + numberOfEvidences = 10 + slashingShares int64 = 10000 + + proposalGlobalFeeFilename = "proposal_globalfee.json" + proposalBypassMsgFilename = "proposal_bypass_msg.json" + proposalMaxTotalBypassFilename = "proposal_max_total_bypass.json" + proposalCommunitySpendFilename = "proposal_community_spend.json" + proposalAddConsumerChainFilename = "proposal_add_consumer.json" + proposalRemoveConsumerChainFilename = "proposal_remove_consumer.json" + proposalLSMParamUpdateFilename = "proposal_lsm_param_update.json" + + hermesBinary = "hermes" + hermesConfigWithGasPrices = "/root/.hermes/config.toml" + hermesConfigNoGasPrices = "/root/.hermes/config-zero.toml" + transferChannel = "channel-0" +) +var ( + terraConfigPath = filepath.Join(terraHomePath, "config") + stakingAmount = sdk.NewInt(100000000000) + stakingAmountCoin = sdk.NewCoin(ulunaDenom, stakingAmount) + tokenAmount = sdk.NewCoin(ulunaDenom, sdk.NewInt(3300000000)) // 3,300uluna + standardFees = sdk.NewCoin(ulunaDenom, sdk.NewInt(330000)) // 0.33uluna + depositAmount = sdk.NewCoin(ulunaDenom, sdk.NewInt(330000000)) // 3,300uluna + distModuleAddress = authtypes.NewModuleAddress(distrtypes.ModuleName).String() + proposalCounter = 0 + HermesResource0Purged = false ) type IntegrationTestSuite struct { suite.Suite + tmpDirs []string + chainA *e2einit.Chain + chainB *e2einit.Chain + + dkrPool *dockertest.Pool + dkrNet *dockertest.Network + hermesResource0 *dockertest.Resource + hermesResource1 *dockertest.Resource + + valResources map[string][]*dockertest.Resource +} + +type AddressResponse struct { + Name string `json:"name"` + Type string `json:"type"` + Address string `json:"address"` + Mnemonic string `json:"mnemonic"` +} + +func TestIntegrationTestSuite(t *testing.T) { + suite.Run(t, new(IntegrationTestSuite)) +} + +func (s *IntegrationTestSuite) SetupSuite() { + s.T().Log("setting up e2e integration test suite...") + + var err error + s.chainA, err = e2einit.NewChain() + s.Require().NoError(err) + + s.chainB, err = e2einit.NewChain() + s.Require().NoError(err) + + s.dkrPool, err = dockertest.NewPool("") + s.Require().NoError(err) + + s.dkrNet, err = s.dkrPool.CreateNetwork(fmt.Sprintf("%s-%s-testnet", s.chainA.ChainMeta.Id, s.chainB.ChainMeta.Id)) + s.Require().NoError(err) + + s.valResources = make(map[string][]*dockertest.Resource) + + vestingMnemonic, err := e2einit.CreateMnemonic() + s.Require().NoError(err) + + jailedValMnemonic, err := e2einit.CreateMnemonic() + s.Require().NoError(err) + + // The boostrapping phase is as follows: + // + // 1. Initialize Terra validator nodes. + // 2. Create and initialize Terra validator genesis files (both chains) + // 3. Start both networks. + // 4. Create and run IBC relayer (Hermes) containers. + + s.T().Logf("starting e2e infrastructure for chain A; chain-id: %s; datadir: %s", s.chainA.ChainMeta.Id, s.chainA.ChainMeta.DataDir) + s.initNodes(s.chainA) + s.initGenesis(s.chainA, vestingMnemonic, jailedValMnemonic) + s.initValidatorConfigs(s.chainA) + s.runValidators(s.chainA, 0) + + s.T().Logf("starting e2e infrastructure for chain B; chain-id: %s; datadir: %s", s.chainB.ChainMeta.Id, s.chainB.ChainMeta.DataDir) + s.initNodes(s.chainB) + s.initGenesis(s.chainB, vestingMnemonic, jailedValMnemonic) + s.initValidatorConfigs(s.chainB) + s.runValidators(s.chainB, 10) + + time.Sleep(10 * time.Second) + s.runIBCRelayer0() + s.runIBCRelayer1() +} + +func (s *IntegrationTestSuite) initNodes(c *e2einit.Chain) { + s.Require().NoError(c.CreateAndInitValidators(2)) + /* Adding 4 accounts to val0 local directory + c.genesisAccounts[0]: Relayer0 Wallet + c.genesisAccounts[1]: ICA Owner + c.genesisAccounts[2]: Test Account 1 + c.genesisAccounts[3]: Test Account 2 + c.genesisAccounts[4]: Relayer1 Wallet + */ + s.Require().NoError(c.AddAccountFromMnemonic(5)) + // Initialize a genesis file for the first validator + val0ConfigDir := c.Validators[0].ConfigDir() + var addrAll []sdk.AccAddress + for _, val := range c.Validators { + address, err := val.KeyInfo.GetAddress() + s.Require().NoError(err) + addrAll = append(addrAll, address) + } + + for _, addr := range c.GenesisAccounts { + acctAddr, err := addr.KeyInfo.GetAddress() + s.Require().NoError(err) + addrAll = append(addrAll, acctAddr) + } + + s.Require().NoError( + e2einit.ModifyGenesis(val0ConfigDir, "", initBalanceStr, addrAll, initialGlobalFeeAmt+ulunaDenom, ulunaDenom), + ) + // copy the genesis file to the remaining validators + for _, val := range c.Validators[1:] { + _, err := e2eutil.CopyFile( + filepath.Join(val0ConfigDir, "config", "genesis.json"), + filepath.Join(val.ConfigDir(), "config", "genesis.json"), + ) + s.Require().NoError(err) + } +} + +func (s *IntegrationTestSuite) initGenesis(c *e2einit.Chain, vestingMnemonic, jailedValMnemonic string) { + var ( + serverCtx = server.NewDefaultContext() + config = serverCtx.Config + validator = c.Validators[0] + ) + + config.SetRoot(validator.ConfigDir()) + config.Moniker = validator.Moniker + + genFilePath := config.GenesisFile() + appGenState, genDoc, err := genutiltypes.GenesisStateFromGenFile(genFilePath) + s.Require().NoError(err) + + appGenState = s.addGenesisVestingAndJailedAccounts( + c, + validator.ConfigDir(), + vestingMnemonic, + jailedValMnemonic, + appGenState, + ) + + var evidenceGenState evidencetypes.GenesisState + s.Require().NoError(e2einit.Cdc.UnmarshalJSON(appGenState[evidencetypes.ModuleName], &evidenceGenState)) + + evidenceGenState.Evidence = make([]*codectypes.Any, numberOfEvidences) + for i := range evidenceGenState.Evidence { + pk := ed25519.GenPrivKey() + evidence := &evidencetypes.Equivocation{ + Height: 1, + Power: 100, + Time: time.Now().UTC(), + ConsensusAddress: sdk.ConsAddress(pk.PubKey().Address().Bytes()).String(), + } + evidenceGenState.Evidence[i], err = codectypes.NewAnyWithValue(evidence) + s.Require().NoError(err) + } + + appGenState[evidencetypes.ModuleName], err = e2einit.Cdc.MarshalJSON(&evidenceGenState) + s.Require().NoError(err) + + var genUtilGenState genutiltypes.GenesisState + s.Require().NoError(e2einit.Cdc.UnmarshalJSON(appGenState[genutiltypes.ModuleName], &genUtilGenState)) + + // generate genesis txs + genTxs := make([]json.RawMessage, len(c.Validators)) + for i, val := range c.Validators { + createValmsg, err := val.BuildCreateValidatorMsg(stakingAmountCoin) + s.Require().NoError(err) + signedTx, err := val.SignMsg(createValmsg) + + s.Require().NoError(err) + + txRaw, err := e2einit.Cdc.MarshalJSON(signedTx) + s.Require().NoError(err) + + genTxs[i] = txRaw + } + + genUtilGenState.GenTxs = genTxs + + appGenState[genutiltypes.ModuleName], err = e2einit.Cdc.MarshalJSON(&genUtilGenState) + s.Require().NoError(err) + + genDoc.AppState, err = json.MarshalIndent(appGenState, "", " ") + s.Require().NoError(err) + + bz, err := tmjson.MarshalIndent(genDoc, "", " ") + s.Require().NoError(err) + + vestingPeriod, err := generateVestingPeriod() + s.Require().NoError(err) + + rawTx, _, err := buildRawTx() + s.Require().NoError(err) + + // write the updated genesis file to each validator. + for _, val := range c.Validators { + err = e2eutil.WriteFile(filepath.Join(val.ConfigDir(), "config", "genesis.json"), bz) + s.Require().NoError(err) + + err = e2eutil.WriteFile(filepath.Join(val.ConfigDir(), vestingPeriodFile), vestingPeriod) + s.Require().NoError(err) + + err = e2eutil.WriteFile(filepath.Join(val.ConfigDir(), rawTxFile), rawTx) + s.Require().NoError(err) + } +} + +// TODO find a better way to manipulate accounts to add genesis accounts +func (s *IntegrationTestSuite) addGenesisVestingAndJailedAccounts( + c *e2einit.Chain, + valConfigDir, + vestingMnemonic, + jailedValMnemonic string, + appGenState map[string]json.RawMessage, +) map[string]json.RawMessage { + var ( + authGenState = authtypes.GetGenesisStateFromAppState(e2einit.Cdc, appGenState) + bankGenState = banktypes.GetGenesisStateFromAppState(e2einit.Cdc, appGenState) + stakingGenState = stakingtypes.GetGenesisStateFromAppState(e2einit.Cdc, appGenState) + ) + + // create genesis vesting accounts keys + kb, err := keyring.New(e2einit.KeyringAppName, keyring.BackendTest, valConfigDir, nil, e2einit.Cdc) + s.Require().NoError(err) + + keyringAlgos, _ := kb.SupportedAlgorithms() + algo, err := keyring.NewSigningAlgoFromString(string(hd.Secp256k1Type), keyringAlgos) + s.Require().NoError(err) + + // create jailed validator account keys + jailedValKey, err := kb.NewAccount(JailedValidatorKey, jailedValMnemonic, "", sdk.FullFundraiserPath, algo) + s.Require().NoError(err) + + // create genesis vesting accounts keys + c.GenesisVestingAccounts = make(map[string]sdk.AccAddress) + for i, key := range genesisVestingKeys { + // Use the first wallet from the same mnemonic by HD path + acc, err := kb.NewAccount(key, vestingMnemonic, "", e2eutil.HDPath(i), algo) + s.Require().NoError(err) + adrr, err := acc.GetAddress() + s.Require().NoError(err) + c.GenesisVestingAccounts[key] = adrr + s.T().Logf("created %s genesis account %s\n", key, c.GenesisVestingAccounts[key].String()) + } + var ( + continuousVestingAcc = c.GenesisVestingAccounts[continuousVestingKey] + delayedVestingAcc = c.GenesisVestingAccounts[delayedVestingKey] + ) + + // add jailed validator to staking store + pubKey, err := jailedValKey.GetPubKey() + s.Require().NoError(err) + jailedValAcc, err := jailedValKey.GetAddress() + s.Require().NoError(err) + jailedValAddr := sdk.ValAddress(jailedValAcc) + val, err := stakingtypes.NewValidator( + jailedValAddr, + pubKey, + stakingtypes.NewDescription("jailed", "", "", "", ""), + ) + s.Require().NoError(err) + val.Jailed = true + val.Tokens = sdk.NewInt(slashingShares) + val.DelegatorShares = sdk.NewDec(slashingShares) + stakingGenState.Validators = append(stakingGenState.Validators, val) + + // add jailed validator delegations + stakingGenState.Delegations = append(stakingGenState.Delegations, stakingtypes.Delegation{ + DelegatorAddress: jailedValAcc.String(), + ValidatorAddress: jailedValAddr.String(), + Shares: sdk.NewDec(slashingShares), + }) + + appGenState[stakingtypes.ModuleName], err = e2einit.Cdc.MarshalJSON(stakingGenState) + s.Require().NoError(err) + + // add jailed account to the genesis + baseJailedAccount := authtypes.NewBaseAccount(jailedValAcc, pubKey, 0, 0) + s.Require().NoError(baseJailedAccount.Validate()) + + // add continuous vesting account to the genesis + baseVestingContinuousAccount := authtypes.NewBaseAccount( + continuousVestingAcc, nil, 0, 0) + vestingContinuousGenAccount := authvesting.NewContinuousVestingAccountRaw( + authvesting.NewBaseVestingAccount( + baseVestingContinuousAccount, + sdk.NewCoins(vestingAmountVested), + time.Now().Add(time.Duration(rand.Intn(80)+150)*time.Second).Unix(), + ), + time.Now().Add(time.Duration(rand.Intn(40)+90)*time.Second).Unix(), + ) + s.Require().NoError(vestingContinuousGenAccount.Validate()) + + // add delayed vesting account to the genesis + baseVestingDelayedAccount := authtypes.NewBaseAccount( + delayedVestingAcc, nil, 0, 0) + vestingDelayedGenAccount := authvesting.NewDelayedVestingAccountRaw( + authvesting.NewBaseVestingAccount( + baseVestingDelayedAccount, + sdk.NewCoins(vestingAmountVested), + time.Now().Add(time.Duration(rand.Intn(40)+90)*time.Second).Unix(), + ), + ) + s.Require().NoError(vestingDelayedGenAccount.Validate()) + + // unpack and append accounts + accs, err := authtypes.UnpackAccounts(authGenState.Accounts) + s.Require().NoError(err) + accs = append(accs, vestingContinuousGenAccount, vestingDelayedGenAccount, baseJailedAccount) + accs = authtypes.SanitizeGenesisAccounts(accs) + genAccs, err := authtypes.PackAccounts(accs) + s.Require().NoError(err) + authGenState.Accounts = genAccs + + // update auth module state + appGenState[authtypes.ModuleName], err = e2einit.Cdc.MarshalJSON(&authGenState) + s.Require().NoError(err) + + // update balances + vestingContinuousBalances := banktypes.Balance{ + Address: continuousVestingAcc.String(), + Coins: vestingBalance, + } + vestingDelayedBalances := banktypes.Balance{ + Address: delayedVestingAcc.String(), + Coins: vestingBalance, + } + jailedValidatorBalances := banktypes.Balance{ + Address: jailedValAcc.String(), + Coins: sdk.NewCoins(tokenAmount), + } + stakingModuleBalances := banktypes.Balance{ + Address: authtypes.NewModuleAddress(stakingtypes.NotBondedPoolName).String(), + Coins: sdk.NewCoins(sdk.NewCoin(ulunaDenom, sdk.NewInt(slashingShares))), + } + bankGenState.Balances = append( + bankGenState.Balances, + vestingContinuousBalances, + vestingDelayedBalances, + jailedValidatorBalances, + stakingModuleBalances, + ) + bankGenState.Balances = banktypes.SanitizeGenesisBalances(bankGenState.Balances) + + // update the denom metadata for the bank module + bankGenState.DenomMetadata = append(bankGenState.DenomMetadata, banktypes.Metadata{ + Description: "An example stable token", + Display: ulunaDenom, + Base: ulunaDenom, + Symbol: ulunaDenom, + Name: ulunaDenom, + DenomUnits: []*banktypes.DenomUnit{ + { + Denom: ulunaDenom, + Exponent: 0, + }, + }, + }) + + // update bank module state + appGenState[banktypes.ModuleName], err = e2einit.Cdc.MarshalJSON(bankGenState) + s.Require().NoError(err) + + return appGenState +} + +// initValidatorConfigs initializes the validator configs for the given chain. +func (s *IntegrationTestSuite) initValidatorConfigs(c *e2einit.Chain) { + for i, val := range c.Validators { + tmCfgPath := filepath.Join(val.ConfigDir(), "config", "config.toml") + + vpr := viper.New() + vpr.SetConfigFile(tmCfgPath) + s.Require().NoError(vpr.ReadInConfig()) + + valConfig := tmconfig.DefaultConfig() + + s.Require().NoError(vpr.Unmarshal(valConfig)) + + valConfig.P2P.ListenAddress = "tcp://0.0.0.0:26656" + valConfig.P2P.AddrBookStrict = false + valConfig.P2P.ExternalAddress = fmt.Sprintf("%s:%d", val.InstanceName(), 26656) + valConfig.RPC.ListenAddress = "tcp://0.0.0.0:26657" + valConfig.StateSync.Enable = false + valConfig.LogLevel = "info" + + var peers []string + + for j := 0; j < len(c.Validators); j++ { + if i == j { + continue + } + + peer := c.Validators[j] + peerID := fmt.Sprintf("%s@%s%d:26656", peer.NodeKey.ID(), peer.Moniker, j) + peers = append(peers, peerID) + } + + valConfig.P2P.PersistentPeers = strings.Join(peers, ",") + + tmconfig.WriteConfigFile(tmCfgPath, valConfig) + + // set application configuration + appCfgPath := filepath.Join(val.ConfigDir(), "config", "app.toml") + + appConfig := srvconfig.DefaultConfig() + appConfig.API.Enable = true + appConfig.MinGasPrices = fmt.Sprintf("%s%s", minGasPrice, ulunaDenom) + + srvconfig.SetConfigTemplate(srvconfig.DefaultConfigTemplate) + srvconfig.WriteConfigFile(appCfgPath, appConfig) + } +} + +// runValidators runs the validators in the chain +func (s *IntegrationTestSuite) runValidators(c *e2einit.Chain, portOffset int) { + s.T().Logf("starting Terra %s validator containers...", c.ChainMeta.Id) + + s.valResources[c.ChainMeta.Id] = make([]*dockertest.Resource, len(c.Validators)) + for i, val := range c.Validators { + runOpts := &dockertest.RunOptions{ + Name: val.InstanceName(), + NetworkID: s.dkrNet.Network.ID, + Mounts: []string{ + fmt.Sprintf("%s/:%s", val.ConfigDir(), terraHomePath), + }, + Repository: "cosmos/terrad-e2e", + } + + s.Require().NoError(exec.Command("chmod", "-R", "0777", val.ConfigDir()).Run()) //nolint:gosec // this is a test + + // expose the first validator for debugging and communication + if val.Index == 0 { + runOpts.PortBindings = map[docker.Port][]docker.PortBinding{ + "1317/tcp": {{HostIP: "", HostPort: fmt.Sprintf("%d", 1317+portOffset)}}, + "6060/tcp": {{HostIP: "", HostPort: fmt.Sprintf("%d", 6060+portOffset)}}, + "6061/tcp": {{HostIP: "", HostPort: fmt.Sprintf("%d", 6061+portOffset)}}, + "6062/tcp": {{HostIP: "", HostPort: fmt.Sprintf("%d", 6062+portOffset)}}, + "6063/tcp": {{HostIP: "", HostPort: fmt.Sprintf("%d", 6063+portOffset)}}, + "6064/tcp": {{HostIP: "", HostPort: fmt.Sprintf("%d", 6064+portOffset)}}, + "6065/tcp": {{HostIP: "", HostPort: fmt.Sprintf("%d", 6065+portOffset)}}, + "9090/tcp": {{HostIP: "", HostPort: fmt.Sprintf("%d", 9090+portOffset)}}, + "26656/tcp": {{HostIP: "", HostPort: fmt.Sprintf("%d", 26656+portOffset)}}, + "26657/tcp": {{HostIP: "", HostPort: fmt.Sprintf("%d", 26657+portOffset)}}, + } + } + + resource, err := s.dkrPool.RunWithOptions(runOpts, noRestart) + s.Require().NoError(err) + + s.valResources[c.ChainMeta.Id][i] = resource + s.T().Logf("started Terra %s validator container: %s", c.ChainMeta.Id, resource.Container.ID) + } + + rpcClient, err := rpchttp.New("tcp://localhost:26657", "/websocket") + s.Require().NoError(err) + + s.Require().Eventually( + func() bool { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) + defer cancel() + + status, err := rpcClient.Status(ctx) + if err != nil { + return false + } + + // let the node produce a few blocks + if status.SyncInfo.CatchingUp || status.SyncInfo.LatestBlockHeight < 3 { + return false + } + + return true + }, + 5*time.Minute, + time.Second, + "terra node failed to produce blocks", + ) +} +func noRestart(config *docker.HostConfig) { + // in this case we don't want the nodes to restart on failure + config.RestartPolicy = docker.RestartPolicy{ + Name: "no", + } +} + +// hermes0 is for ibc and packet-forward-middleware(PFM) test, hermes0 is keep running during the ibc and PFM test. +func (s *IntegrationTestSuite) runIBCRelayer0() { + s.T().Log("starting Hermes relayer container 0...") + + tmpDir, err := os.MkdirTemp("", "terra-e2e-testnet-hermes-") + s.Require().NoError(err) + s.tmpDirs = append(s.tmpDirs, tmpDir) + + terraAVal := s.chainA.Validators[0] + terraBVal := s.chainB.Validators[0] + + terraARly := s.chainA.GenesisAccounts[relayerAccountIndexHermes0] + terraBRly := s.chainB.GenesisAccounts[relayerAccountIndexHermes0] + + hermesCfgPath := path.Join(tmpDir, "hermes") + + s.Require().NoError(os.MkdirAll(hermesCfgPath, 0o755)) + _, err = e2eutil.CopyFile( + filepath.Join("./scripts/", "hermes_bootstrap.sh"), + filepath.Join(hermesCfgPath, "hermes_bootstrap.sh"), + ) + s.Require().NoError(err) + + s.hermesResource0, err = s.dkrPool.RunWithOptions( + &dockertest.RunOptions{ + Name: fmt.Sprintf("%s-%s-relayer-0", s.chainA.ChainMeta.Id, s.chainB.ChainMeta.Id), + Repository: "ghcr.io/cosmos/hermes-e2e", + Tag: "1.0.0", + NetworkID: s.dkrNet.Network.ID, + Mounts: []string{ + fmt.Sprintf("%s/:/root/hermes", hermesCfgPath), + }, + PortBindings: map[docker.Port][]docker.PortBinding{ + "3031/tcp": {{HostIP: "", HostPort: "3031"}}, + }, + Env: []string{ + fmt.Sprintf("terra_A_E2E_CHAIN_ID=%s", s.chainA.ChainMeta.Id), + fmt.Sprintf("terra_B_E2E_CHAIN_ID=%s", s.chainB.ChainMeta.Id), + fmt.Sprintf("terra_A_E2E_VAL_MNEMONIC=%s", terraAVal.Mnemonic), + fmt.Sprintf("terra_B_E2E_VAL_MNEMONIC=%s", terraBVal.Mnemonic), + fmt.Sprintf("terra_A_E2E_RLY_MNEMONIC=%s", terraARly.Mnemonic), + fmt.Sprintf("terra_B_E2E_RLY_MNEMONIC=%s", terraBRly.Mnemonic), + fmt.Sprintf("terra_A_E2E_VAL_HOST=%s", s.valResources[s.chainA.ChainMeta.Id][0].Container.Name[1:]), + fmt.Sprintf("terra_B_E2E_VAL_HOST=%s", s.valResources[s.chainB.ChainMeta.Id][0].Container.Name[1:]), + }, + Entrypoint: []string{ + "sh", + "-c", + "chmod +x /root/hermes/hermes_bootstrap.sh && /root/hermes/hermes_bootstrap.sh", + }, + }, + noRestart, + ) + s.Require().NoError(err) + + endpoint := fmt.Sprintf("http://%s/state", s.hermesResource0.GetHostPort("3031/tcp")) + s.Require().Eventually( + func() bool { + resp, err := http.Get(endpoint) //nolint:gosec // this is a test + if err != nil { + return false + } + + defer resp.Body.Close() + + bz, err := io.ReadAll(resp.Body) + if err != nil { + return false + } + + var respBody map[string]interface{} + if err := json.Unmarshal(bz, &respBody); err != nil { + return false + } + + status := respBody["status"].(string) + result := respBody["result"].(map[string]interface{}) + + return status == "success" && len(result["chains"].([]interface{})) == 2 + }, + 5*time.Minute, + time.Second, + "hermes relayer not healthy", + ) + + s.T().Logf("started Hermes relayer 0 container: %s", s.hermesResource0.Container.ID) + + // XXX: Give time to both networks to start, otherwise we might see gRPC + // transport errors. + time.Sleep(10 * time.Second) + + // create the client, connection and channel between the two terra chains + s.createConnection() + time.Sleep(10 * time.Second) + s.createChannel() +} + +// hermes1 is for bypass-msg test. Hermes1 is to process asynchronous transactions, +// Hermes1 has access to two Hermes configurations: one configuration allows paying fees, while the other does not. +// With Hermes1, better control can be achieved regarding whether fees are paid when clearing transactions. +func (s *IntegrationTestSuite) runIBCRelayer1() { + s.T().Log("starting Hermes relayer container 1...") + + tmpDir, err := os.MkdirTemp("", "terra-e2e-testnet-hermes-") + s.Require().NoError(err) + s.tmpDirs = append(s.tmpDirs, tmpDir) + + terraAVal := s.chainA.Validators[0] + terraBVal := s.chainB.Validators[0] + + terraARly := s.chainA.GenesisAccounts[relayerAccountIndexHermes1] + terraBRly := s.chainB.GenesisAccounts[relayerAccountIndexHermes1] + + hermesCfgPath := path.Join(tmpDir, "hermes") + + s.Require().NoError(os.MkdirAll(hermesCfgPath, 0o755)) + _, err = e2eutil.CopyFile( + filepath.Join("./scripts/", "hermes1_bootstrap.sh"), + filepath.Join(hermesCfgPath, "hermes1_bootstrap.sh"), + ) + s.Require().NoError(err) + + s.hermesResource1, err = s.dkrPool.RunWithOptions( + &dockertest.RunOptions{ + Name: fmt.Sprintf("%s-%s-relayer-1", s.chainA.ChainMeta.Id, s.chainB.ChainMeta.Id), + Repository: "ghcr.io/cosmos/hermes-e2e", + Tag: "1.0.0", + NetworkID: s.dkrNet.Network.ID, + Mounts: []string{ + fmt.Sprintf("%s/:/root/hermes", hermesCfgPath), + }, + PortBindings: map[docker.Port][]docker.PortBinding{ + "3032/tcp": {{HostIP: "", HostPort: "3032"}}, + }, + Env: []string{ + fmt.Sprintf("terra_A_E2E_CHAIN_ID=%s", s.chainA.ChainMeta.Id), + fmt.Sprintf("terra_B_E2E_CHAIN_ID=%s", s.chainB.ChainMeta.Id), + fmt.Sprintf("terra_A_E2E_VAL_MNEMONIC=%s", terraAVal.Mnemonic), + fmt.Sprintf("terra_B_E2E_VAL_MNEMONIC=%s", terraBVal.Mnemonic), + fmt.Sprintf("terra_A_E2E_RLY_MNEMONIC=%s", terraARly.Mnemonic), + fmt.Sprintf("terra_B_E2E_RLY_MNEMONIC=%s", terraBRly.Mnemonic), + fmt.Sprintf("terra_A_E2E_VAL_HOST=%s", s.valResources[s.chainA.ChainMeta.Id][0].Container.Name[1:]), + fmt.Sprintf("terra_B_E2E_VAL_HOST=%s", s.valResources[s.chainB.ChainMeta.Id][0].Container.Name[1:]), + }, + Entrypoint: []string{ + "sh", + "-c", + "chmod +x /root/hermes/hermes1_bootstrap.sh && /root/hermes/hermes1_bootstrap.sh && tail -f /dev/null", + }, + }, + noRestart, + ) + s.Require().NoError(err) + + s.T().Logf("started Hermes relayer 1 container: %s", s.hermesResource1.Container.ID) + + // XXX: Give time to both networks to start, otherwise we might see gRPC + // transport errors. + time.Sleep(10 * time.Second) } diff --git a/tests/e2e/e2e_slashing_test.go b/tests/e2e/e2e_slashing_test.go new file mode 100644 index 000000000..e4afe41de --- /dev/null +++ b/tests/e2e/e2e_slashing_test.go @@ -0,0 +1,3 @@ +package e2e + +const JailedValidatorKey = "jailed" diff --git a/tests/e2e/e2e_vesting_test.go b/tests/e2e/e2e_vesting_test.go new file mode 100644 index 000000000..411792bf9 --- /dev/null +++ b/tests/e2e/e2e_vesting_test.go @@ -0,0 +1,58 @@ +package e2e + +import ( + "encoding/json" + "math/rand" + // "path/filepath" + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +const ( + delayedVestingKey = "delayed_vesting" + continuousVestingKey = "continuous_vesting" + lockedVestingKey = "locker_vesting" + periodicVestingKey = "periodic_vesting" + + vestingPeriodFile = "test_period.json" + vestingTxDelay = 5 +) + +type ( + vestingPeriod struct { + StartTime int64 `json:"start_time"` + Periods []period `json:"periods"` + } + period struct { + Coins string `json:"coins"` + Length int64 `json:"length_seconds"` + } +) + +var ( + genesisVestingKeys = []string{continuousVestingKey, delayedVestingKey, lockedVestingKey, periodicVestingKey} + vestingAmountVested = sdk.NewCoin(ulunaDenom, sdk.NewInt(99900000000)) + vestingAmount = sdk.NewCoin(ulunaDenom, sdk.NewInt(350000)) + vestingBalance = sdk.NewCoins(vestingAmountVested).Add(vestingAmount) + vestingDelegationAmount = sdk.NewCoin(ulunaDenom, sdk.NewInt(500000000)) + vestingDelegationFees = sdk.NewCoin(ulunaDenom, sdk.NewInt(1)) +) + +// generateVestingPeriod generate the vesting period file +func generateVestingPeriod() ([]byte, error) { + p := vestingPeriod{ + StartTime: time.Now().Add(time.Duration(rand.Intn(20)+95) * time.Second).Unix(), + Periods: []period{ + { + Coins: "850000000" + ulunaDenom, + Length: 35, + }, + { + Coins: "2000000000" + ulunaDenom, + Length: 35, + }, + }, + } + return json.Marshal(p) +} diff --git a/tests/e2e/initialization/chain.go b/tests/e2e/initialization/chain.go index d6c2ee359..da8409709 100644 --- a/tests/e2e/initialization/chain.go +++ b/tests/e2e/initialization/chain.go @@ -1,10 +1,33 @@ package initialization import ( - "github.com/tendermint/tendermint/p2p" - "github.com/tendermint/tendermint/privval" + "fmt" + "os" - cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + tmrand "github.com/tendermint/tendermint/libs/rand" + // "github.com/tendermint/tendermint/p2p" + // "github.com/tendermint/tendermint/privval" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/codec" + sdkcrypto "github.com/cosmos/cosmos-sdk/crypto" + "github.com/cosmos/cosmos-sdk/crypto/hd" + "github.com/cosmos/cosmos-sdk/crypto/keyring" + sdk "github.com/cosmos/cosmos-sdk/types" + // cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + + "github.com/classic-terra/core/v2/app/params" +) + +const ( + KeyringPassphrase = "testpassphrase" + KeyringAppName = "testnet" +) + +var ( + EncodingConfig params.EncodingConfig + Cdc codec.Codec + TxConfig client.TxConfig ) type ChainMeta struct { @@ -12,29 +35,113 @@ type ChainMeta struct { Id string } -type Validator struct { - Chain *Chain - Index int - Moniker string - Mnemonic string - PublicAddress string - PublicKey cryptotypes.PubKey - PrivateKey cryptotypes.PrivKey - ConsensusKey privval.FilePVKey - ConsensusPrivKey cryptotypes.PrivKey - NodeKey p2p.NodeKey +// type Node struct { +// Moniker string +// Mnemonic string +// PublicAddress string +// PublicKey cryptotypes.PubKey +// PrivateKey cryptotypes.PrivKey +// } + +type Chain struct { + ChainMeta ChainMeta + Validators []*Validator + // Node []*Node + GenesisAccounts []*Account + GenesisVestingAccounts map[string]sdk.AccAddress } -type Node struct { - Moniker string //nolint:unused - Mnemonic string - PublicAddress string - PublicKey cryptotypes.PubKey - PrivateKey cryptotypes.PrivKey +func NewChain() (*Chain, error) { + tmpDir, err := os.MkdirTemp("", "terra-e2e-testnet-") + if err != nil { + return nil, err + } + + return &Chain{ + ChainMeta: ChainMeta{ + Id: "chain-" + tmrand.Str(6), + DataDir: tmpDir, + }, + }, nil } -type Chain struct { - ChainMeta ChainMeta - Validator []*Validator - Node []*Node -} \ No newline at end of file +func (c *Chain) CreateAndInitValidators(count int) error { + for i := 0; i < count; i++ { + node := c.createValidator(i) + + // generate genesis files + if err := node.init(); err != nil { + return err + } + + c.Validators = append(c.Validators, node) + + // create keys + if err := node.createKey("val"); err != nil { + return err + } + if err := node.createNodeKey(); err != nil { + return err + } + if err := node.createConsensusKey(); err != nil { + return err + } + } + + return nil +} + +func (c *Chain) createValidator(index int) *Validator { + return &Validator{ + Chain: c, + Index: index, + Moniker: fmt.Sprintf("%s-terra-%d", c.ChainMeta.Id, index), + } +} + +func (c *Chain) configDir() string { + return fmt.Sprintf("%s/%s", c.ChainMeta.DataDir, c.ChainMeta.Id) +} + +func (c *Chain) AddAccountFromMnemonic(counts int) error { + val0ConfigDir := c.Validators[0].ConfigDir() + kb, err := keyring.New(KeyringAppName, keyring.BackendTest, val0ConfigDir, nil, Cdc) + if err != nil { + return err + } + + keyringAlgos, _ := kb.SupportedAlgorithms() + algo, err := keyring.NewSigningAlgoFromString(string(hd.Secp256k1Type), keyringAlgos) + if err != nil { + return err + } + + for i := 0; i < counts; i++ { + name := fmt.Sprintf("acct-%d", i) + mnemonic, err := CreateMnemonic() + if err != nil { + return err + } + info, err := kb.NewAccount(name, mnemonic, "", sdk.FullFundraiserPath, algo) + if err != nil { + return err + } + + privKeyArmor, err := kb.ExportPrivKeyArmor(name, KeyringPassphrase) + if err != nil { + return err + } + + privKey, _, err := sdkcrypto.UnarmorDecryptPrivKey(privKeyArmor, KeyringPassphrase) + if err != nil { + return err + } + acct := Account{} + acct.KeyInfo = info + acct.Mnemonic = mnemonic + acct.PrivateKey = privKey + c.GenesisAccounts = append(c.GenesisAccounts, &acct) + } + + return nil +} diff --git a/tests/e2e/initialization/decode.go b/tests/e2e/initialization/decode.go new file mode 100644 index 000000000..b7f6606a8 --- /dev/null +++ b/tests/e2e/initialization/decode.go @@ -0,0 +1,45 @@ +package initialization + +import ( + "fmt" + + "github.com/cosmos/cosmos-sdk/codec/unknownproto" + sdktx "github.com/cosmos/cosmos-sdk/types/tx" +) + +func DecodeTx(txBytes []byte) (*sdktx.Tx, error) { + var raw sdktx.TxRaw + + // reject all unknown proto fields in the root TxRaw + err := unknownproto.RejectUnknownFieldsStrict(txBytes, &raw, EncodingConfig.InterfaceRegistry) + if err != nil { + return nil, fmt.Errorf("failed to reject unknown fields: %w", err) + } + + if err := Cdc.Unmarshal(txBytes, &raw); err != nil { + return nil, err + } + + var body sdktx.TxBody + if err := Cdc.Unmarshal(raw.BodyBytes, &body); err != nil { + return nil, fmt.Errorf("failed to decode tx: %w", err) + } + + var authInfo sdktx.AuthInfo + + // reject all unknown proto fields in AuthInfo + err = unknownproto.RejectUnknownFieldsStrict(raw.AuthInfoBytes, &authInfo, EncodingConfig.InterfaceRegistry) + if err != nil { + return nil, fmt.Errorf("failed to reject unknown fields: %w", err) + } + + if err := Cdc.Unmarshal(raw.AuthInfoBytes, &authInfo); err != nil { + return nil, fmt.Errorf("failed to decode auth info: %w", err) + } + + return &sdktx.Tx{ + Body: &body, + AuthInfo: &authInfo, + Signatures: raw.Signatures, + }, nil +} diff --git a/tests/e2e/initialization/genesis.go b/tests/e2e/initialization/genesis.go new file mode 100644 index 000000000..a64987dfb --- /dev/null +++ b/tests/e2e/initialization/genesis.go @@ -0,0 +1,191 @@ +package initialization + +import ( + "encoding/json" + "fmt" + "os" + "time" + + tmtypes "github.com/tendermint/tendermint/types" + + icagenesistypes "github.com/cosmos/ibc-go/v6/modules/apps/27-interchain-accounts/genesis/types" + icatypes "github.com/cosmos/ibc-go/v6/modules/apps/27-interchain-accounts/types" + + "github.com/cosmos/cosmos-sdk/server" + sdk "github.com/cosmos/cosmos-sdk/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + "github.com/cosmos/cosmos-sdk/x/genutil" + genutiltypes "github.com/cosmos/cosmos-sdk/x/genutil/types" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" + govv1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" +) + +func getGenDoc(path string) (*tmtypes.GenesisDoc, error) { + serverCtx := server.NewDefaultContext() + config := serverCtx.Config + config.SetRoot(path) + + genFile := config.GenesisFile() + doc := &tmtypes.GenesisDoc{} + + if _, err := os.Stat(genFile); err != nil { + if !os.IsNotExist(err) { + return nil, err + } + } else { + var err error + + doc, err = tmtypes.GenesisDocFromFile(genFile) + if err != nil { + return nil, fmt.Errorf("failed to read genesis doc from file: %w", err) + } + } + + return doc, nil +} + +func ModifyGenesis(path, moniker, amountStr string, addrAll []sdk.AccAddress, globfees string, denom string) error { + serverCtx := server.NewDefaultContext() + config := serverCtx.Config + config.SetRoot(path) + config.Moniker = moniker + + coins, err := sdk.ParseCoinsNormalized(amountStr) + if err != nil { + return fmt.Errorf("failed to parse coins: %w", err) + } + + var balances []banktypes.Balance + var genAccounts []*authtypes.BaseAccount + for _, addr := range addrAll { + balance := banktypes.Balance{Address: addr.String(), Coins: coins.Sort()} + balances = append(balances, balance) + genAccount := authtypes.NewBaseAccount(addr, nil, 0, 0) + genAccounts = append(genAccounts, genAccount) + } + + genFile := config.GenesisFile() + appState, genDoc, err := genutiltypes.GenesisStateFromGenFile(genFile) + if err != nil { + return fmt.Errorf("failed to unmarshal genesis state: %w", err) + } + + authGenState := authtypes.GetGenesisStateFromAppState(Cdc, appState) + accs, err := authtypes.UnpackAccounts(authGenState.Accounts) + if err != nil { + return fmt.Errorf("failed to get accounts from any: %w", err) + } + + for _, addr := range addrAll { + if accs.Contains(addr) { + return fmt.Errorf("failed to add account to genesis state; account already exists: %s", addr) + } + } + + // Add the new account to the set of genesis accounts and sanitize the + // accounts afterwards. + for _, genAcct := range genAccounts { + accs = append(accs, genAcct) + accs = authtypes.SanitizeGenesisAccounts(accs) + } + + genAccs, err := authtypes.PackAccounts(accs) + if err != nil { + return fmt.Errorf("failed to convert accounts into any's: %w", err) + } + + authGenState.Accounts = genAccs + + authGenStateBz, err := Cdc.MarshalJSON(&authGenState) + if err != nil { + return fmt.Errorf("failed to marshal auth genesis state: %w", err) + } + appState[authtypes.ModuleName] = authGenStateBz + + bankGenState := banktypes.GetGenesisStateFromAppState(Cdc, appState) + bankGenState.Balances = append(bankGenState.Balances, balances...) + bankGenState.Balances = banktypes.SanitizeGenesisBalances(bankGenState.Balances) + + bankGenStateBz, err := Cdc.MarshalJSON(bankGenState) + if err != nil { + return fmt.Errorf("failed to marshal bank genesis state: %w", err) + } + appState[banktypes.ModuleName] = bankGenStateBz + + // add ica host allowed msg types + var icaGenesisState icagenesistypes.GenesisState + + if appState[icatypes.ModuleName] != nil { + Cdc.MustUnmarshalJSON(appState[icatypes.ModuleName], &icaGenesisState) + } + + icaGenesisState.HostGenesisState.Params.AllowMessages = []string{ + "/cosmos.authz.v1beta1.MsgExec", + "/cosmos.authz.v1beta1.MsgGrant", + "/cosmos.authz.v1beta1.MsgRevoke", + "/cosmos.bank.v1beta1.MsgSend", + "/cosmos.bank.v1beta1.MsgMultiSend", + "/cosmos.distribution.v1beta1.MsgSetWithdrawAddress", + "/cosmos.distribution.v1beta1.MsgWithdrawValidatorCommission", + "/cosmos.distribution.v1beta1.MsgFundCommunityPool", + "/cosmos.distribution.v1beta1.MsgWithdrawDelegatorReward", + "/cosmos.feegrant.v1beta1.MsgGrantAllowance", + "/cosmos.feegrant.v1beta1.MsgRevokeAllowance", + "/cosmos.gov.v1beta1.MsgVoteWeighted", + "/cosmos.gov.v1beta1.MsgSubmitProposal", + "/cosmos.gov.v1beta1.MsgDeposit", + "/cosmos.gov.v1beta1.MsgVote", + "/cosmos.staking.v1beta1.MsgEditValidator", + "/cosmos.staking.v1beta1.MsgDelegate", + "/cosmos.staking.v1beta1.MsgUndelegate", + "/cosmos.staking.v1beta1.MsgBeginRedelegate", + "/cosmos.staking.v1beta1.MsgCreateValidator", + "/cosmos.vesting.v1beta1.MsgCreateVestingAccount", + "/ibc.applications.transfer.v1.MsgTransfer", + "/tendermint.liquidity.v1beta1.MsgCreatePool", + "/tendermint.liquidity.v1beta1.MsgSwapWithinBatch", + "/tendermint.liquidity.v1beta1.MsgDepositWithinBatch", + "/tendermint.liquidity.v1beta1.MsgWithdrawWithinBatch", + } + + icaGenesisStateBz, err := Cdc.MarshalJSON(&icaGenesisState) + if err != nil { + return fmt.Errorf("failed to marshal interchain accounts genesis state: %w", err) + } + appState[icatypes.ModuleName] = icaGenesisStateBz + + stakingGenState := stakingtypes.GetGenesisStateFromAppState(Cdc, appState) + stakingGenState.Params.BondDenom = denom + stakingGenStateBz, err := Cdc.MarshalJSON(stakingGenState) + if err != nil { + return fmt.Errorf("failed to marshal staking genesis state: %s", err) + } + appState[stakingtypes.ModuleName] = stakingGenStateBz + + // Refactor to separate method + amnt := sdk.NewInt(10000) + quorum, _ := sdk.NewDecFromStr("0.000000000000000001") + threshold, _ := sdk.NewDecFromStr("0.000000000000000001") + + govState := govv1.NewGenesisState(1, + govv1.NewDepositParams(sdk.NewCoins(sdk.NewCoin(denom, amnt)), 10*time.Minute), + govv1.NewVotingParams(15*time.Second), + govv1.NewTallyParams(quorum, threshold, govv1.DefaultVetoThreshold), + ) + + govGenStateBz, err := Cdc.MarshalJSON(govState) + if err != nil { + return fmt.Errorf("failed to marshal gov genesis state: %w", err) + } + appState[govtypes.ModuleName] = govGenStateBz + + appStateJSON, err := json.Marshal(appState) + if err != nil { + return fmt.Errorf("failed to marshal application genesis state: %w", err) + } + genDoc.AppState = appStateJSON + + return genutil.ExportGenesisFile(genDoc, genFile) +} diff --git a/tests/e2e/initialization/keys.go b/tests/e2e/initialization/keys.go new file mode 100644 index 000000000..db603e017 --- /dev/null +++ b/tests/e2e/initialization/keys.go @@ -0,0 +1,20 @@ +package initialization + +import ( + "github.com/cosmos/go-bip39" +) + +// createMnemonic creates a random string mnemonic +func CreateMnemonic() (string, error) { + entropySeed, err := bip39.NewEntropy(256) + if err != nil { + return "", err + } + + mnemonic, err := bip39.NewMnemonic(entropySeed) + if err != nil { + return "", err + } + + return mnemonic, nil +} diff --git a/tests/e2e/initialization/validator.go b/tests/e2e/initialization/validator.go new file mode 100644 index 000000000..2e87c768e --- /dev/null +++ b/tests/e2e/initialization/validator.go @@ -0,0 +1,283 @@ +package initialization + +import ( + "cosmossdk.io/math" + "encoding/json" + "fmt" + "os" + "path" + "path/filepath" + + tmcfg "github.com/tendermint/tendermint/config" + tmos "github.com/tendermint/tendermint/libs/os" + "github.com/tendermint/tendermint/p2p" + "github.com/tendermint/tendermint/privval" + + sdkcrypto "github.com/cosmos/cosmos-sdk/crypto" + cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" + "github.com/cosmos/cosmos-sdk/crypto/hd" + "github.com/cosmos/cosmos-sdk/crypto/keyring" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + "github.com/cosmos/cosmos-sdk/server" + sdk "github.com/cosmos/cosmos-sdk/types" + sdktx "github.com/cosmos/cosmos-sdk/types/tx" + txsigning "github.com/cosmos/cosmos-sdk/types/tx/signing" + authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing" + "github.com/cosmos/cosmos-sdk/x/genutil" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + + terraapp "github.com/classic-terra/core/v2/app" +) + +type Validator struct { + Chain *Chain + Index int + Moniker string + Mnemonic string + KeyInfo *keyring.Record + PublicAddress string + PublicKey cryptotypes.PubKey + PrivateKey cryptotypes.PrivKey + ConsensusKey privval.FilePVKey + ConsensusPrivKey cryptotypes.PrivKey + NodeKey p2p.NodeKey +} + +type Account struct { + Moniker string + Mnemonic string + KeyInfo *keyring.Record + PrivateKey cryptotypes.PrivKey +} + +func (v *Validator) InstanceName() string { + return fmt.Sprintf("%s%d", v.Moniker, v.Index) +} + +func (v *Validator) ConfigDir() string { + return fmt.Sprintf("%s/%s", v.Chain.configDir(), v.InstanceName()) +} + +func (v *Validator) createConfig() error { + p := path.Join(v.ConfigDir(), "config") + return os.MkdirAll(p, 0o755) +} + +func (v *Validator) init() error { + if err := v.createConfig(); err != nil { + return err + } + + serverCtx := server.NewDefaultContext() + config := serverCtx.Config + + config.SetRoot(v.ConfigDir()) + config.Moniker = v.Moniker + + genDoc, err := getGenDoc(v.ConfigDir()) + if err != nil { + return err + } + + appState, err := json.MarshalIndent(terraapp.ModuleBasics.DefaultGenesis(Cdc), "", " ") + if err != nil { + return fmt.Errorf("failed to JSON encode app genesis state: %w", err) + } + + genDoc.ChainID = v.Chain.ChainMeta.Id + genDoc.Validators = nil + genDoc.AppState = appState + + if err = genutil.ExportGenesisFile(genDoc, config.GenesisFile()); err != nil { + return fmt.Errorf("failed to export app genesis state: %w", err) + } + + tmcfg.WriteConfigFile(filepath.Join(config.RootDir, "config", "config.toml"), config) + return nil +} + +func (v *Validator) createKey(name string) error { + mnemonic, err := CreateMnemonic() + if err != nil { + return err + } + + return v.createKeyFromMnemonic(name, mnemonic) +} + +func (v *Validator) createKeyFromMnemonic(name, mnemonic string) error { + dir := v.ConfigDir() + kb, err := keyring.New(KeyringAppName, keyring.BackendTest, dir, nil, Cdc) + if err != nil { + return err + } + + keyringAlgos, _ := kb.SupportedAlgorithms() + algo, err := keyring.NewSigningAlgoFromString(string(hd.Secp256k1Type), keyringAlgos) + if err != nil { + return err + } + + info, err := kb.NewAccount(name, mnemonic, "", sdk.FullFundraiserPath, algo) + if err != nil { + return err + } + + privKeyArmor, err := kb.ExportPrivKeyArmor(name, KeyringPassphrase) + if err != nil { + return err + } + + privKey, _, err := sdkcrypto.UnarmorDecryptPrivKey(privKeyArmor, KeyringPassphrase) + if err != nil { + return err + } + + v.KeyInfo = info + v.Mnemonic = mnemonic + v.PrivateKey = privKey + + return nil +} + +func (v *Validator) createNodeKey() error { + serverCtx := server.NewDefaultContext() + config := serverCtx.Config + + config.SetRoot(v.ConfigDir()) + config.Moniker = v.Moniker + + nodeKey, err := p2p.LoadOrGenNodeKey(config.NodeKeyFile()) + if err != nil { + return err + } + + v.NodeKey = *nodeKey + return nil +} + +func (v *Validator) createConsensusKey() error { + serverCtx := server.NewDefaultContext() + config := serverCtx.Config + + config.SetRoot(v.ConfigDir()) + config.Moniker = v.Moniker + + pvKeyFile := config.PrivValidatorKeyFile() + if err := tmos.EnsureDir(filepath.Dir(pvKeyFile), 0o777); err != nil { + return err + } + + pvStateFile := config.PrivValidatorStateFile() + if err := tmos.EnsureDir(filepath.Dir(pvStateFile), 0o777); err != nil { + return err + } + + filePV := privval.LoadOrGenFilePV(pvKeyFile, pvStateFile) + v.ConsensusKey = filePV.Key + + return nil +} + +func (v *Validator) BuildCreateValidatorMsg(amount sdk.Coin) (sdk.Msg, error) { + description := stakingtypes.NewDescription(v.Moniker, "", "", "", "") + commissionRates := stakingtypes.CommissionRates{ + Rate: sdk.MustNewDecFromStr("0.1"), + MaxRate: sdk.MustNewDecFromStr("0.2"), + MaxChangeRate: sdk.MustNewDecFromStr("0.01"), + } + + valPubKey, err := cryptocodec.FromTmPubKeyInterface(v.ConsensusKey.PubKey) + if err != nil { + return nil, err + } + addr, err := v.KeyInfo.GetAddress() + if err != nil { + return nil, err + } + return stakingtypes.NewMsgCreateValidator( + sdk.ValAddress(addr), + valPubKey, + amount, + description, + commissionRates, + math.OneInt(), + ) +} + +func (v *Validator) SignMsg(msgs ...sdk.Msg) (*sdktx.Tx, error) { + txBuilder := EncodingConfig.TxConfig.NewTxBuilder() + + if err := txBuilder.SetMsgs(msgs...); err != nil { + return nil, err + } + + txBuilder.SetMemo(fmt.Sprintf("%s@%s:26656", v.NodeKey.ID(), v.InstanceName())) + txBuilder.SetFeeAmount(sdk.NewCoins()) + txBuilder.SetGasLimit(200000) + + signerData := authsigning.SignerData{ + ChainID: v.Chain.ChainMeta.Id, + AccountNumber: 0, + Sequence: 0, + } + + // For SIGN_MODE_DIRECT, calling SetSignatures calls setSignerInfos on + // TxBuilder under the hood, and SignerInfos is needed to generate the sign + // bytes. This is the reason for setting SetSignatures here, with a nil + // signature. + // + // Note: This line is not needed for SIGN_MODE_LEGACY_AMINO, but putting it + // also doesn't affect its generated sign bytes, so for code's simplicity + // sake, we put it here. + pubkey, err := v.KeyInfo.GetPubKey() + if err != nil { + return nil, err + } + sig := txsigning.SignatureV2{ + PubKey: pubkey, + Data: &txsigning.SingleSignatureData{ + SignMode: txsigning.SignMode_SIGN_MODE_DIRECT, + Signature: nil, + }, + Sequence: 0, + } + + if err := txBuilder.SetSignatures(sig); err != nil { + return nil, err + } + + bytesToSign, err := EncodingConfig.TxConfig.SignModeHandler().GetSignBytes( + txsigning.SignMode_SIGN_MODE_DIRECT, + signerData, + txBuilder.GetTx(), + ) + if err != nil { + return nil, err + } + + sigBytes, err := v.PrivateKey.Sign(bytesToSign) + if err != nil { + return nil, err + } + + sig = txsigning.SignatureV2{ + PubKey: pubkey, + Data: &txsigning.SingleSignatureData{ + SignMode: txsigning.SignMode_SIGN_MODE_DIRECT, + Signature: sigBytes, + }, + Sequence: 0, + } + if err := txBuilder.SetSignatures(sig); err != nil { + return nil, err + } + + signedTx := txBuilder.GetTx() + bz, err := EncodingConfig.TxConfig.TxEncoder()(signedTx) + if err != nil { + return nil, err + } + + return DecodeTx(bz) +} diff --git a/tests/e2e/util/address.go b/tests/e2e/util/address.go new file mode 100644 index 000000000..4b1a60317 --- /dev/null +++ b/tests/e2e/util/address.go @@ -0,0 +1,33 @@ +package util + +import ( + "fmt" + "math/rand" + "strconv" + + "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" + crypto "github.com/cosmos/cosmos-sdk/crypto/types" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// HDPath generates an HD path based on the wallet index +func HDPath(index int) string { + return fmt.Sprintf("m/44'/118'/0'/0/%d", index) +} + +// PubKey returns a sample account PubKey +func PubKey() crypto.PubKey { + seed := []byte(strconv.Itoa(rand.Int())) + return ed25519.GenPrivKeyFromSecret(seed).PubKey() +} + +// AccAddress returns a sample account address +func AccAddress() sdk.AccAddress { + addr := PubKey().Address() + return sdk.AccAddress(addr) +} + +// Address returns a sample string account address +func Address() string { + return AccAddress().String() +} From 8164fdcb40acafc62203a345d1e910915300f2e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=E1=BA=B7c?= Date: Mon, 8 Jan 2024 22:10:29 +0700 Subject: [PATCH 05/32] add localterra and fix local dockerfile workdir --- Makefile | 14 +++ local.Dockerfile | 11 +- scripts/localterra/README.md | 74 ++++++++++++++ scripts/localterra/docker-compose.yml | 21 ++++ scripts/localterra/scripts/add_keys.sh | 13 +++ scripts/localterra/scripts/setup.sh | 134 +++++++++++++++++++++++++ 6 files changed, 264 insertions(+), 3 deletions(-) create mode 100644 scripts/localterra/README.md create mode 100644 scripts/localterra/docker-compose.yml create mode 100755 scripts/localterra/scripts/add_keys.sh create mode 100755 scripts/localterra/scripts/setup.sh diff --git a/Makefile b/Makefile index c924cdae7..e934c3d1c 100755 --- a/Makefile +++ b/Makefile @@ -337,6 +337,20 @@ localnet-stop: rm -rf build/node* rm -rf build/gentxs +localnet-keys: + . scripts/localterra/scripts/add_keys.sh + +localnet-init: localnet-clean localnet-build + +localnet-clean: + @rm -rfI $(HOME)/.terrad-local/ + +localnet-build: + @DOCKER_BUILDKIT=1 COMPOSE_DOCKER_CLI_BUILD=1 docker-compose -f scripts/localterra/docker-compose.yml build + +localnet-starts: + @STATE="" docker-compose -f scripts/localterra/docker-compose.yml up + .PHONY: localnet-start localnet-stop ############################################################################### diff --git a/local.Dockerfile b/local.Dockerfile index 1a6ea76c6..7de8bf712 100644 --- a/local.Dockerfile +++ b/local.Dockerfile @@ -15,11 +15,16 @@ RUN cp /go/pkg/mod/github.com/classic-terra/wasmvm@v*/internal/api/libwasmvm.aar FROM ubuntu:22.04 -WORKDIR /root - COPY --from=go-builder /code/build/terrad /usr/local/bin/terrad COPY --from=go-builder /lib/libwasmvm.aarch64.so /lib/libwasmvm.aarch64.so +RUN apt-get update \ + && apt-get install -y wget \ + && rm -rf /var/lib/apt/lists/* + +ENV HOME /terra +WORKDIR $HOME + # rest server EXPOSE 1317 # grpc @@ -27,4 +32,4 @@ EXPOSE 9090 # tendermint p2p EXPOSE 26656 # tendermint rpc -EXPOSE 26657 \ No newline at end of file +EXPOSE 26657 diff --git a/scripts/localterra/README.md b/scripts/localterra/README.md new file mode 100644 index 000000000..a15b232d5 --- /dev/null +++ b/scripts/localterra/README.md @@ -0,0 +1,74 @@ +# LocalTerra + +LocalTerra is a complete Terra testnet containerized with Docker and orchestrated with a simple docker-compose file. LocalTerra comes preconfigured with opinionated, sensible defaults for a standard testing environment. + +LocalTerra comes in two flavors: + +1. No initial state: brand new testnet with no initial state. +2. With mainnet state: creates a testnet from a mainnet state export + +## Prerequisites + +Ensure you have docker and docker-compose installed: + +```sh +# Docker +sudo apt-get remove docker docker-engine docker.io +sudo apt-get update +sudo apt install docker.io -y + +# Docker compose +sudo apt install docker-compose -y +``` + +## 1. LocalTerra - No Initial State + +The following commands must be executed from the root folder of the Terra repository. + +1. Make any change to the terra code that you want to test + +2. Initialize LocalTerra: + +```bash +make localnet-init +``` + +The command: + +- Builds a local docker image with the latest changes +- Cleans the `$HOME/.terrad-local` folder + +3. Start LocalTerra: + +```bash +make localnet-start +``` + +> Note +> +> You can also start LocalTerra in detach mode with: +> +> `make localnet-startd` + +4. (optional) Add your validator wallet and 9 other preloaded wallets automatically: + +```bash +make localnet-keys +``` + +- These keys are added to your `--keyring-backend test` +- If the keys are already on your keyring, you will get an `"Error: aborted"` +- Ensure you use the name of the account as listed in the table below, as well as ensure you append the `--keyring-backend test` to your txs +- Example: `terrad tx bank send lo-test2 osmo1cyyzpxplxdzkeea7kwsydadg87357qnahakaks --keyring-backend test --chain-id LocalTerra` + +5. You can stop chain, keeping the state with + +```bash +make localnet-stop +``` + +6. When you are done you can clean up the environment with: + +```bash +make localnet-clean +``` diff --git a/scripts/localterra/docker-compose.yml b/scripts/localterra/docker-compose.yml new file mode 100644 index 000000000..ec91225cd --- /dev/null +++ b/scripts/localterra/docker-compose.yml @@ -0,0 +1,21 @@ +version: "3" + +services: + terrad: + image: local:terra + build: + context: ../../ + dockerfile: local.Dockerfile + volumes: + - ./scripts/setup.sh:/terra/setup.sh + - $HOME/.terrad-local/:/terra/.terrad/ + entrypoint: + - /terra/setup.sh + command: + - $STATE + ports: + - 26657:26657 + - 1317:1317 + - 9090:9090 + - 9091:9091 + - 6060:6060 diff --git a/scripts/localterra/scripts/add_keys.sh b/scripts/localterra/scripts/add_keys.sh new file mode 100755 index 000000000..fc6aa9e72 --- /dev/null +++ b/scripts/localterra/scripts/add_keys.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +echo "bottom loan skill merry east cradle onion journey palm apology verb edit desert impose absurd oil bubble sweet glove shallow size build burst effort" | terrad keys add val --recover --keyring-backend test +echo "notice oak worry limit wrap speak medal online prefer cluster roof addict wrist behave treat actual wasp year salad speed social layer crew genius" | terrad keys add lo-test1 --recover --keyring-backend test +echo "quality vacuum heart guard buzz spike sight swarm shove special gym robust assume sudden deposit grid alcohol choice devote leader tilt noodle tide penalty" | terrad keys add lo-test2 --recover --keyring-backend test +echo "symbol force gallery make bulk round subway violin worry mixture penalty kingdom boring survey tool fringe patrol sausage hard admit remember broken alien absorb" | terrad keys add lo-test3 --recover --keyring-backend test +echo "bounce success option birth apple portion aunt rural episode solution hockey pencil lend session cause hedgehog slender journey system canvas decorate razor catch empty" | terrad keys add lo-test4 --recover --keyring-backend test +echo "second render cat sing soup reward cluster island bench diet lumber grocery repeat balcony perfect diesel stumble piano distance caught occur example ozone loyal" | terrad keys add lo-test5 --recover --keyring-backend test +echo "spatial forest elevator battle also spoon fun skirt flight initial nasty transfer glory palm drama gossip remove fan joke shove label dune debate quick" | terrad keys add lo-test6 --recover --keyring-backend test +echo "noble width taxi input there patrol clown public spell aunt wish punch moment will misery eight excess arena pen turtle minimum grain vague inmate" | terrad keys add lo-test7 --recover --keyring-backend test +echo "cream sport mango believe inhale text fish rely elegant below earth april wall rug ritual blossom cherry detail length blind digital proof identify ride" | terrad keys add lo-test8 --recover --keyring-backend test +echo "index light average senior silent limit usual local involve delay update rack cause inmate wall render magnet common feature laundry exact casual resource hundred" | terrad keys add lo-test9 --recover --keyring-backend test +echo "prefer forget visit mistake mixture feel eyebrow autumn shop pair address airport diesel street pass vague innocent poem method awful require hurry unhappy shoulder" | terrad keys add lo-test10 --recover --keyring-backend test diff --git a/scripts/localterra/scripts/setup.sh b/scripts/localterra/scripts/setup.sh new file mode 100755 index 000000000..eeaff768b --- /dev/null +++ b/scripts/localterra/scripts/setup.sh @@ -0,0 +1,134 @@ +#!/bin/sh + +CHAIN_ID=localterra +TERRA_HOME=$HOME/.terrad +CONFIG_FOLDER=$TERRA_HOME/config +MONIKER=val +STATE='false' + +MNEMONIC="bottom loan skill merry east cradle onion journey palm apology verb edit desert impose absurd oil bubble sweet glove shallow size build burst effort" +POOLSMNEMONIC="traffic cool olive pottery elegant innocent aisle dial genuine install shy uncle ride federal soon shift flight program cave famous provide cute pole struggle" + +while getopts s flag +do + case "${flag}" in + s) STATE='true';; + esac +done + +install_prerequisites () { + wget -qO /usr/local/bin/dasel https://github.com/TomWright/dasel/releases/latest/download/dasel_linux_amd64 + chmod a+x /usr/local/bin/dasel + dasel --version +} + +edit_genesis () { + + GENESIS=$CONFIG_FOLDER/genesis.json + + # Update staking module + dasel put -t string -f $GENESIS -v 'uluna' '.app_state.staking.params.bond_denom' + + # Update crisis module + dasel put -t string -f $GENESIS -v 'uluna' '.app_state.crisis.constant_fee.denom' + + # Udpate gov module + dasel put -t string -f $GENESIS -v '60s' '.app_state.gov.voting_params.voting_period' + dasel put -t string -f $GENESIS -v 'uluna' '.app_state.gov.deposit_params.min_deposit.[0].denom' + + # Update mint module + dasel put -t string -f $GENESIS -v 'uluna' '.app_state.mint.params.mint_denom' + + # Update txfee basedenom + dasel put -t string -f $GENESIS -v 'uluna' '.app_state.txfees.basedenom' + + # Update wasm permission (Nobody or Everybody) + dasel put -t string -f $GENESIS -v 'Everybody' '.app_state.wasm.params.code_upload_access.permission' +} + +add_genesis_accounts () { + + terrad add-genesis-account terra1a7fgca0746t9kjz079s0m63eqkczfjp3luesac 100000000000uluna --home $TERRA_HOME + # note such large amounts are set for e2e tests on FE + terrad add-genesis-account terra192xhdwsnc44zz0dsnhf7sq7rhmtuklf7nupy53 9999999999999999999999999999999999999999999999999uluna --home $TERRA_HOME + terrad add-genesis-account terra1x46rqay4d3cssq8gxxvqz8xt6nwlz4td20k38v 100000000000uluna --home $TERRA_HOME + terrad add-genesis-account terra17lmam6zguazs5q5u6z5mmx76uj63gldnse2pdp 100000000000uluna --home $TERRA_HOME + terrad add-genesis-account terra1757tkx08n0cqrw7p86ny9lnxsqeth0wgp0em95 100000000000uluna --home $TERRA_HOME + terrad add-genesis-account terra199vw7724lzkwz6lf2hsx04lrxfkz09tg8dlp6r 100000000000uluna --home $TERRA_HOME + terrad add-genesis-account terra18wlvftxzj6zt0xugy2lr9nxzu402690ltaf4ss 100000000000uluna --home $TERRA_HOME + terrad add-genesis-account terra1e8ryd9ezefuucd4mje33zdms9m2s90m57878v9 100000000000uluna --home $TERRA_HOME + terrad add-genesis-account terra17tv2hvwpg0ukqgd2y5ct2w54fyan7z0zxrm2f9 100000000000uluna --home $TERRA_HOME + terrad add-genesis-account terra1lkccuqgj6sjwjn8gsa9xlklqv4pmrqg9dx2fxc 100000000000uluna --home $TERRA_HOME + terrad add-genesis-account terra1333veey879eeqcff8j3gfcgwt8cfrg9mq20v6f 100000000000uluna --home $TERRA_HOME + terrad add-genesis-account terra1fmcjjt6yc9wqup2r06urnrd928jhrde6gcld6n 1000000000000uluna --home $TERRA_HOME + + echo $MNEMONIC | terrad keys add $MONIKER --recover --keyring-backend=test --home $TERRA_HOME + echo $POOLSMNEMONIC | terrad keys add pools --recover --keyring-backend=test --home $TERRA_HOME + terrad gentx $MONIKER 500000000uluna --keyring-backend=test --chain-id=$CHAIN_ID --home $TERRA_HOME + + terrad collect-gentxs --home $TERRA_HOME +} + +edit_config () { + + # Remove seeds + dasel put -t string -f $CONFIG_FOLDER/config.toml -v '' '.p2p.seeds' + + # Expose the rpc + dasel put -t string -f $CONFIG_FOLDER/config.toml -v "tcp://0.0.0.0:26657" '.rpc.laddr' + + # Expose pprof for debugging + # To make the change enabled locally, make sure to add 'EXPOSE 6060' to the root Dockerfile + # and rebuild the image. + dasel put -t string -f $CONFIG_FOLDER/config.toml -v "0.0.0.0:6060" '.rpc.pprof_laddr' +} + +enable_cors () { + + # Enable cors on RPC + dasel put -t string -f $CONFIG_FOLDER/config.toml -v "*" '.rpc.cors_allowed_origins.[]' + dasel put -t string -f $CONFIG_FOLDER/config.toml -v "Accept-Encoding" '.rpc.cors_allowed_headers.[]' + dasel put -t string -f $CONFIG_FOLDER/config.toml -v "DELETE" '.rpc.cors_allowed_methods.[]' + dasel put -t string -f $CONFIG_FOLDER/config.toml -v "OPTIONS" '.rpc.cors_allowed_methods.[]' + dasel put -t string -f $CONFIG_FOLDER/config.toml -v "PATCH" '.rpc.cors_allowed_methods.[]' + dasel put -t string -f $CONFIG_FOLDER/config.toml -v "PUT" '.rpc.cors_allowed_methods.[]' + + # Enable unsafe cors and swagger on the api + dasel put -t bool -f $CONFIG_FOLDER/app.toml -v "true" '.api.swagger' + dasel put -t bool -f $CONFIG_FOLDER/app.toml -v "true" '.api.enabled-unsafe-cors' + + # Enable cors on gRPC Web + dasel put -t bool -f $CONFIG_FOLDER/app.toml -v "true" '.grpc-web.enable-unsafe-cors' +} + +run_with_retries() { + cmd=$1 + success_msg=$2 + + substring='code: 0' + COUNTER=0 + + while [ $COUNTER -lt 15 ]; do + string=$(eval $cmd 2>&1) + echo $string + + if [ "$string" != "${string%"$substring"*}" ]; then + echo "$success_msg" + break + else + COUNTER=$((COUNTER+1)) + sleep 0.5 + fi + done +} + + echo $MNEMONIC | terrad init -o --chain-id=$CHAIN_ID --home $TERRA_HOME --recover $MONIKER + install_prerequisites + edit_genesis + add_genesis_accounts + edit_config + enable_cors + +terrad start --home $TERRA_HOME & + +wait From e916648c416c90545a27e8cef22e1d986016c552 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=E1=BA=B7c?= Date: Mon, 8 Jan 2024 23:18:34 +0700 Subject: [PATCH 06/32] add localrelayer setup --- scripts/localrelayer/Makefile | 24 ++ scripts/localrelayer/README.md | 222 ++++++++++++++++++ .../localrelayer/config/hermes/config.toml | 107 +++++++++ scripts/localrelayer/docker-compose.yml | 122 ++++++++++ scripts/localrelayer/scripts/setup_chain.sh | 91 +++++++ scripts/localrelayer/scripts/setup_hermes.sh | 71 ++++++ 6 files changed, 637 insertions(+) create mode 100644 scripts/localrelayer/Makefile create mode 100644 scripts/localrelayer/README.md create mode 100644 scripts/localrelayer/config/hermes/config.toml create mode 100644 scripts/localrelayer/docker-compose.yml create mode 100755 scripts/localrelayer/scripts/setup_chain.sh create mode 100755 scripts/localrelayer/scripts/setup_hermes.sh diff --git a/scripts/localrelayer/Makefile b/scripts/localrelayer/Makefile new file mode 100644 index 000000000..8f4d2936e --- /dev/null +++ b/scripts/localrelayer/Makefile @@ -0,0 +1,24 @@ +init: clean build + +build: + @DOCKER_BUILDKIT=1 COMPOSE_DOCKER_CLI_BUILD=1 docker-compose -f docker-compose.yml build + +start: + @docker-compose -f docker-compose.yml up + +startd: + @docker-compose -f docker-compose.yml up -d + +stop: + @docker-compose -f docker-compose.yml down -t 3 + + +restart: stop + @docker-compose -f docker-compose.yml up --force-recreate + +restartd: stop + @docker-compose -f docker-compose.yml up --force-recreate -d + +clean: + @rm -rfI $(HOME)/.terrad-local-a/ + @rm -rfI $(HOME)/.terrad-local-b/ diff --git a/scripts/localrelayer/README.md b/scripts/localrelayer/README.md new file mode 100644 index 000000000..c0e60dc48 --- /dev/null +++ b/scripts/localrelayer/README.md @@ -0,0 +1,222 @@ +# Localrelayer + +Localrelayer is a local testing environment composed of two LocalTerra instances connected by a relayer. + +![Architecture](./assets/architecture.png) + +## Endpoints + +| Chain ID | Component | Endpoint | +|------------------|------------|--------------------------| +| `localterra-a` | `RPC` | | +| `localterra-a` | `REST/LCD` | | +| `localterra-a` | `gRPC` | | +| `localterra-a` | `faucet` | | +| `localterra-b` | `RPC` | | +| `localterra-b` | `REST/LCD` | | +| `localterra-b` | `gRPC` | | +| `localterra-b` | `faucet` | | +| `-` | `hermes` | | + +## Accounts + +By default the following mnemonics are used: + +| Chain ID | Account | Mnemonic | +|------------------|---------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `localterra-a` | `validator-a` | *family album bird seek tilt color pill danger message abuse manual tent almost ridge boost blast high comic core quantum spoon coconut oyster remove* | +| `localterra-a` | `faucet` | *increase bread alpha rigid glide amused approve oblige print asset idea enact lawn proof unfold jeans rabbit audit return chuckle valve rather cactus great* | +| `localterra-a` | `relayer` | *black frequent sponsor nice claim rally hunt suit parent size stumble expire forest avocado mistake agree trend witness lounge shiver image smoke stool chicken* | +| `localterra-b` | `validator-b` | *family album bird seek tilt color pill danger message abuse manual tent almost ridge boost blast high comic core quantum spoon coconut oyster remove* | +| `localterra-b` | `faucet` | *increase bread alpha rigid glide amused approve oblige print asset idea enact lawn proof unfold jeans rabbit audit return chuckle valve rather cactus great* | +| `localterra-b` | `relayer` | *black frequent sponsor nice claim rally hunt suit parent size stumble expire forest avocado mistake agree trend witness lounge shiver image smoke stool chicken* | + + +## Deploy + +Build a local docker image with current changes + +```bash +make build +``` + +Start the testing environment: + +```bash +make start +``` + +The command will: + +1. create a local docker network: + +```bash + ⠿ Network localrelayer_localterra Created +``` + +2. run the following containers: + +```bash + ⠿ Container localrelayer-localterra-b-1 Created + ⠿ Container localrelayer-localterra-a-1 Created + ⠿ Container localrelayer-faucet-a-1 Created + ⠿ Container localrelayer-faucet-b-1 Created + ⠿ Container localrelayer-hermes-1 Created +``` + +> If you don't want the logs, you can start in detached mode with the following command: +> +> `make startd` + +Check that everything is running: + +```bash +docker ps +``` + +Expected output: + +```bash +❯ docker ps +CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES +318c89d3015f informalsystems/hermes:1.1.0 "/home/hermes/setup.…" About a minute ago Up 2 seconds 0.0.0.0:3000->3000/tcp localrelayer-hermes-1 +ff7abb62fdb3 confio/faucet:0.28.11 "/app/packages/fauce…" About a minute ago Up 2 seconds 0.0.0.0:38000->8000/tcp localrelayer-faucet-b-1 +7e7ca3ff8a67 confio/faucet:0.28.11 "/app/packages/fauce…" About a minute ago Up 2 seconds 0.0.0.0:8000->8000/tcp localrelayer-faucet-a-1 +d90ec29c7a6f local:terra "/terra/setup.sh" About a minute ago Up 3 seconds 26656/tcp, 0.0.0.0:31317->1317/tcp, 0.0.0.0:39090->9090/tcp, 0.0.0.0:36657->26657/tcp localrelayer-localterra-b-1 +e36cead49a07 local:terra "/terra/setup.sh" About a minute ago Up 3 seconds 0.0.0.0:1317->1317/tcp, 0.0.0.0:9090->9090/tcp, 0.0.0.0:26657->26657/tcp, 26656/tcp localrelayer-localterra-a-1 +``` + +## Usage + +### Interact with chain + +Check `localterra-a` status: + +```bash +curl -s http://localhost:26657/status +``` + +Check `localterra-b` status: + +```bash +curl -s http://localhost:36657/status +``` + +### Faucet + +The faucet used is `confio/faucet:0.28.11`. The source code and additional documentation are available [here](https://github.com/cosmos/cosmjs/tree/main/packages/faucet). + +Create a new account: + +```bash +❯ terrad keys add my-account --keyring-backend test + +- name: my-account + type: local + address: terra1e8ryd9ezefuucd4mje33zdms9m2s90m57878v9 + pubkey: '{"@type":"/cosmos.crypto.secp256k1.PubKey","key":"AougdpyGftv+BMBXzQWFVJx9ASz/QRoBDM0nRI/xq90Y"}' + mnemonic: "" +``` + +Request founds: + +```bash +FAUCET_ENDPOINT=http://localhost:8080 + +# Use the following endpoint for localterra-b: +# FAUCET_ENDPOINT=http://localhost:38080 + +curl --header "Content-Type: application/json" \ + --request POST \ + --data '{"denom":"uluna","address":"terra1e8ryd9ezefuucd4mje33zdms9m2s90m57878v9"}' \ + http://localhost:8000/credit +``` + +Check balance: + +```bash +LCD_ENDPOINT=\localhost:1317 + +# Use the following endpoint for localterra-b: +# LCD_ENDPOINT=localhost:31317 + +curl -s http://$LCD_ENDPOINT/cosmos/bank/v1beta1/balances/terra1e8ryd9ezefuucd4mje33zdms9m2s90m57878v9 +``` + +### Hermes + +You can test that hermes is working by sending a test IBC transaction. + +Make sure `hermes` is running: + +```bash +❯ docker ps | grep hermes +``` + +Expected output: + +```bash +318c89d3015f informalsystems/hermes:1.1.0 "/home/hermes/setup.…" 23 minutes ago Up 22 minutes 0.0.0.0:3000->3000/tcp +``` + +Exec inside the container: + +```bash +docker exec -ti localrelayer-hermes-1 sh +``` + +Send a transaction: + +```bash +hermes tx ft-transfer --timeout-seconds 1000 \ + --dst-chain localterra-a \ + --src-chain localterra-b \ + --src-port transfer \ + --src-channel channel-0 \ + --amount 100 \ + --denom uluna +``` + +Expected output: + +```bash +2022-12-01T11:41:22.351909Z INFO ThreadId(01) using default configuration from '/root/.hermes/config.toml' +SUCCESS [ + IbcEventWithHeight { + event: SendPacket( + SendPacket { + packet: Packet { + sequence: Sequence( + 1, + ), + source_port: PortId( + "transfer", + ), + source_channel: ChannelId( + "channel-0", + ), + destination_port: PortId( + "transfer", + ), + destination_channel: ChannelId( + "channel-0", + ), + data: [123, 34, 97, 109, 111, 117, 110, 116, 34, 58, 34, 49, 48, 48, 34, 44, 34, 100, 101, 110, 111, 109, 34, 58, 34, 117, 111, 115, 109, 111, 34, 44, 34, 114, 101, 99, 101, 105, 118, 101, 114, 34, 58, 34, 111, 115, 109, 111, 49, 113, 118, 100, 101, 117, 52, 120, 51, 52, 114, 97, 112, 112, 51, 119, 99, 56, 102, 121, 109, 53, 103, 52, 119, 117, 51, 52, 51, 109, 115, 119, 120, 50, 101, 120, 107, 117, 103, 34, 44, 34, 115, 101, 110, 100, 101, 114, 34, 58, 34, 111, 115, 109, 111, 49, 113, 118, 100, 101, 117, 52, 120, 51, 52, 114, 97, 112, 112, 51, 119, 99, 56, 102, 121, 109, 53, 103, 52, 119, 117, 51, 52, 51, 109, 115, 119, 120, 50, 101, 120, 107, 117, 103, 34, 125], + timeout_height: Never, + timeout_timestamp: Timestamp { + time: Some( + Time( + 2022-12-01 11:57:59.365129852, + ), + ), + }, + }, + }, + ), + height: Height { + revision: 0, + height: 1607, + }, + }, +] +``` diff --git a/scripts/localrelayer/config/hermes/config.toml b/scripts/localrelayer/config/hermes/config.toml new file mode 100644 index 000000000..01ce7f70a --- /dev/null +++ b/scripts/localrelayer/config/hermes/config.toml @@ -0,0 +1,107 @@ +[global] +log_level = 'info' + +[mode.clients] +enabled = true +refresh = true +misbehaviour = true + +[mode.connections] +enabled = true + +[mode.channels] +enabled = true + +[mode.packets] +enabled = true +clear_interval = 100 +clear_on_start = true +tx_confirmation = true + +[rest] +enabled = true +host = '0.0.0.0' +port = 3000 + +[telemetry] +enabled = true +host = '0.0.0.0' +port = 3001 + +[[chains]] +id = 'localterra-b' +type = 'CosmosSdk' +rpc_addr = 'http://localterra-b:26657' +grpc_addr = 'http://localterra-b:9090' +websocket_addr = 'ws://localterra-b:26657/websocket' +rpc_timeout = '10s' +account_prefix = 'terra' +key_name = 'localterra-b' +key_store_type = 'Test' +store_prefix = 'ibc' +default_gas = 100000 +max_gas = 4000000 +gas_multiplier = 1.1 +max_msg_num = 30 +max_tx_size = 2097152 +clock_drift = '5s' +max_block_time = '30s' +memo_prefix = '' +sequential_batch_tx = false + +[chains.trust_threshold] +numerator = '1' +denominator = '3' + +[chains.gas_price] +price = 0.1 +denom = 'uluna' + +[chains.packet_filter] +policy = 'allow' +list = [[ + 'transfer', + 'channel-*', +]] + +[chains.address_type] +derivation = 'cosmos' + +[[chains]] +id = 'localterra-a' +type = 'CosmosSdk' +rpc_addr = 'http://localterra-a:26657' +grpc_addr = 'http://localterra-a:9090' +websocket_addr = 'ws://localterra-a:26657/websocket' +rpc_timeout = '10s' +account_prefix = 'terra' +key_name = 'localterra-a' +key_store_type = 'Test' +store_prefix = 'ibc' +default_gas = 100000 +max_gas = 400000 +gas_multiplier = 1.1 +max_msg_num = 30 +max_tx_size = 2097152 +clock_drift = '5s' +max_block_time = '30s' +memo_prefix = '' +sequential_batch_tx = false + +[chains.trust_threshold] +numerator = '1' +denominator = '3' + +[chains.gas_price] +price = 0.1 +denom = 'uluna' + +[chains.packet_filter] +policy = 'allow' +list = [[ + 'transfer', + 'channel-*', +]] + +[chains.address_type] +derivation = 'cosmos' diff --git a/scripts/localrelayer/docker-compose.yml b/scripts/localrelayer/docker-compose.yml new file mode 100644 index 000000000..2eea808f1 --- /dev/null +++ b/scripts/localrelayer/docker-compose.yml @@ -0,0 +1,122 @@ +version: "3" + +services: + localterra-a: + image: local:terra + build: + context: ../../ + dockerfile: local.Dockerfile + volumes: + - ./scripts/setup_chain.sh:/terra/setup.sh + - $HOME/.terrad-local-a/:/terra/.terrad/ + entrypoint: + - /terra/setup.sh + environment: + - CHAIN_ID=localterra-a + - VALIDATOR_MONIKER=validator-a + - VALIDATOR_MNEMONIC=family album bird seek tilt color pill danger message abuse manual tent almost ridge boost blast high comic core quantum spoon coconut oyster remove + - FAUCET_MNEMONIC=increase bread alpha rigid glide amused approve oblige print asset idea enact lawn proof unfold jeans rabbit audit return chuckle valve rather cactus great + - RELAYER_MNEMONIC=black frequent sponsor nice claim rally hunt suit parent size stumble expire forest avocado mistake agree trend witness lounge shiver image smoke stool chicken + ports: + - 26657:26657 + - 1317:1317 + - 9090:9090 + networks: + - localterra + + faucet-a: + command: + - start + - localterra-a:26657 + image: confio/faucet:0.28.11 + environment: + FAUCET_CONCURRENCY: 1 + FAUCET_MNEMONIC: "increase bread alpha rigid glide amused approve oblige print asset idea enact lawn proof unfold jeans rabbit audit return chuckle valve rather cactus great" + FAUCET_PORT: 8000 + FAUCET_GAS_PRICE: 0.025uluna + FAUCET_PATH_PATTERN: "m/44'/118'/0'/0/a" + FAUCET_ADDRESS_PREFIX: luna + FAUCET_TOKENS: uluna,uion + FAUCET_CREDIT_AMOUNT_ULUNA: 10000000 # 10 luna + FAUCET_CREDIT_AMOUNT_UION: 100000 # 0.1 ion + FAUCET_REFILL_FACTOR: 8 + FAUCET_REFILL_THRESHOLD: 20 + FAUCET_COOLDOWN_TIME: 30 # 30s + ports: + - 8000:8000 + depends_on: + - localterra-a + networks: + - localterra + + localterra-b: + image: local:terra + build: + context: ../../ + dockerfile: local.Dockerfile + volumes: + - ./scripts/setup_chain.sh:/terra/setup.sh + - $HOME/.terrad-local-b/:/terra/.terrad/ + entrypoint: + - /terra/setup.sh + environment: + - CHAIN_ID=localterra-b + - VALIDATOR_MONIKER=validator-b + - VALIDATOR_MNEMONIC=family album bird seek tilt color pill danger message abuse manual tent almost ridge boost blast high comic core quantum spoon coconut oyster remove + - FAUCET_MNEMONIC=increase bread alpha rigid glide amused approve oblige print asset idea enact lawn proof unfold jeans rabbit audit return chuckle valve rather cactus great + - RELAYER_MNEMONIC=black frequent sponsor nice claim rally hunt suit parent size stumble expire forest avocado mistake agree trend witness lounge shiver image smoke stool chicken + ports: + # Can't use the same ports + - 36657:26657 + - 31317:1317 + - 39090:9090 + networks: + - localterra + + faucet-b: + command: + - start + - localterra-b:26657 + image: confio/faucet:0.28.11 + environment: + FAUCET_CONCURRENCY: 1 + FAUCET_MNEMONIC: "increase bread alpha rigid glide amused approve oblige print asset idea enact lawn proof unfold jeans rabbit audit return chuckle valve rather cactus great" + FAUCET_PORT: 8000 + FAUCET_GAS_PRICE: 0.025uluna + FAUCET_PATH_PATTERN: "m/44'/118'/0'/0/a" + FAUCET_ADDRESS_PREFIX: luna + FAUCET_TOKENS: uluna + FAUCET_CREDIT_AMOUNT_ULUNA: 10000000 # 10 luna + FAUCET_REFILL_FACTOR: 8 + FAUCET_REFILL_THRESHOLD: 20 + FAUCET_COOLDOWN_TIME: 30 # 30s + ports: + - 38000:8000 + depends_on: + - localterra-b + networks: + - localterra + + hermes: + image: informalsystems/hermes:1.1.0 + user: root:root + volumes: + - ./scripts/setup_hermes.sh:/home/hermes/setup.sh + - ./config/hermes/config.toml:/root/.hermes/config.toml + entrypoint: + - /home/hermes/setup.sh + environment: + - CHAIN_A_ID=localterra-a + - CHAIN_A_MNEMONIC=black frequent sponsor nice claim rally hunt suit parent size stumble expire forest avocado mistake agree trend witness lounge shiver image smoke stool chicken + - CHAIN_B_ID=localterra-b + - CHAIN_B_MNEMONIC=black frequent sponsor nice claim rally hunt suit parent size stumble expire forest avocado mistake agree trend witness lounge shiver image smoke stool chicken + ports: + - 3000:3000 + depends_on: + - localterra-a + - localterra-b + networks: + - localterra + +networks: + localterra: diff --git a/scripts/localrelayer/scripts/setup_chain.sh b/scripts/localrelayer/scripts/setup_chain.sh new file mode 100755 index 000000000..99c31342d --- /dev/null +++ b/scripts/localrelayer/scripts/setup_chain.sh @@ -0,0 +1,91 @@ +#!/bin/sh +set -e pipefail + +DEFAULT_CHAIN_ID="localterra" +DEFAULT_VALIDATOR_MONIKER="validator" +DEFAULT_VALIDATOR_MNEMONIC="bottom loan skill merry east cradle onion journey palm apology verb edit desert impose absurd oil bubble sweet glove shallow size build burst effort" +DEFAULT_FAUCET_MNEMONIC="increase bread alpha rigid glide amused approve oblige print asset idea enact lawn proof unfold jeans rabbit audit return chuckle valve rather cactus great" +DEFAULT_RELAYER_MNEMONIC="black frequent sponsor nice claim rally hunt suit parent size stumble expire forest avocado mistake agree trend witness lounge shiver image smoke stool chicken" + +# Override default values with environment variables +CHAIN_ID=${CHAIN_ID:-$DEFAULT_CHAIN_ID} +VALIDATOR_MONIKER=${VALIDATOR_MONIKER:-$DEFAULT_VALIDATOR_MONIKER} +VALIDATOR_MNEMONIC=${VALIDATOR_MNEMONIC:-$DEFAULT_VALIDATOR_MNEMONIC} +FAUCET_MNEMONIC=${FAUCET_MNEMONIC:-$DEFAULT_FAUCET_MNEMONIC} +RELAYER_MNEMONIC=${RELAYER_MNEMONIC:-$DEFAULT_RELAYER_MNEMONIC} + +TERRA_HOME=$HOME/.terrad +CONFIG_FOLDER=$TERRA_HOME/config + +install_prerequisites () { + wget -qO /usr/local/bin/dasel https://github.com/TomWright/dasel/releases/latest/download/dasel_linux_amd64 + chmod a+x /usr/local/bin/dasel + dasel --version +} + +edit_genesis () { + + GENESIS=$CONFIG_FOLDER/genesis.json + + # Update staking module + dasel put -t string -f $GENESIS -v 'uluna' '.app_state.staking.params.bond_denom' + + # Update crisis module + dasel put -t string -f $GENESIS -v 'uluna' '.app_state.crisis.constant_fee.denom' + + # Udpate gov module + dasel put -t string -f $GENESIS -v '60s' '.app_state.gov.voting_params.voting_period' + dasel put -t string -f $GENESIS -v 'uluna' '.app_state.gov.deposit_params.min_deposit.[0].denom' + + # Update mint module + dasel put -t string -f $GENESIS -v "uluna" '.app_state.mint.params.mint_denom' + + # Update txfee basedenom + dasel put -t string -f $GENESIS -v "uluna" '.app_state.txfees.basedenom' + + # Update wasm permission (Nobody or Everybody) + dasel put -t string -f $GENESIS -v "Everybody" '.app_state.wasm.params.code_upload_access.permission' +} + +add_genesis_accounts () { + + # Validator + echo "⚖️ Add validator account" + echo $VALIDATOR_MNEMONIC | terrad keys add $VALIDATOR_MONIKER --recover --keyring-backend=test --home $TERRA_HOME + VALIDATOR_ACCOUNT=$(terrad keys show -a $VALIDATOR_MONIKER --keyring-backend test --home $TERRA_HOME) + terrad add-genesis-account $VALIDATOR_ACCOUNT 100000000000uluna,100000000000stake --home $TERRA_HOME + + # Faucet + echo "🚰 Add faucet account" + echo $FAUCET_MNEMONIC | terrad keys add faucet --recover --keyring-backend=test --home $TERRA_HOME + FAUCET_ACCOUNT=$(terrad keys show -a faucet --keyring-backend test --home $TERRA_HOME) + terrad add-genesis-account $FAUCET_ACCOUNT 100000000000uluna,100000000000stake --home $TERRA_HOME + + # Relayer + echo "🔗 Add relayer account" + echo $RELAYER_MNEMONIC | terrad keys add relayer --recover --keyring-backend=test --home $TERRA_HOME + RELAYER_ACCOUNT=$(terrad keys show -a relayer --keyring-backend test --home $TERRA_HOME) + terrad add-genesis-account $RELAYER_ACCOUNT 1000000000uluna,1000000000stake --home $TERRA_HOME + + terrad gentx $VALIDATOR_MONIKER 500000000uluna --keyring-backend=test --chain-id=$CHAIN_ID --home $TERRA_HOME + terrad collect-gentxs --home $TERRA_HOME +} + +edit_config () { + # Remove seeds + dasel put -t string -f $CONFIG_FOLDER/config.toml -v '' '.p2p.seeds' + + # Expose the rpc + dasel put -t string -f $CONFIG_FOLDER/config.toml -v "tcp://0.0.0.0:26657" '.rpc.laddr' +} + +install_prerequisites +echo "Creating Terra home for $VALIDATOR_MONIKER" +echo $VALIDATOR_MNEMONIC | terrad init -o --chain-id=$CHAIN_ID --home $TERRA_HOME --recover $VALIDATOR_MONIKER +edit_genesis +add_genesis_accounts +edit_config + + +echo "🏁 Starting $CHAIN_ID..." +terrad start --home $TERRA_HOME diff --git a/scripts/localrelayer/scripts/setup_hermes.sh b/scripts/localrelayer/scripts/setup_hermes.sh new file mode 100755 index 000000000..351f81c2b --- /dev/null +++ b/scripts/localrelayer/scripts/setup_hermes.sh @@ -0,0 +1,71 @@ +#!/bin/sh +set -e + +DEFAULT_CHAIN_A_ID="localterra-a" +DEFAULT_CHAIN_A_MNEMONIC="black frequent sponsor nice claim rally hunt suit parent size stumble expire forest avocado mistake agree trend witness lounge shiver image smoke stool chicken" +DEFAULT_CHAIN_B_ID="localterra-b" +DEFAULT_CHAIN_B_MNEMONIC="black frequent sponsor nice claim rally hunt suit parent size stumble expire forest avocado mistake agree trend witness lounge shiver image smoke stool chicken" + +CHAIN_A_MNEMONIC=${CHAIN_A_MNEMONIC:-$DEFAULT_CHAIN_A_MNEMONIC} +CHAIN_A_ID=${CHAIN_A_ID:-$DEFAULT_CHAIN_A_ID} +CHAIN_B_MNEMONIC=${CHAIN_B_MNEMONIC:-$DEFAULT_CHAIN_B_MNEMONIC} +CHAIN_B_ID=${CHAIN_B_ID:-$DEFAULT_CHAIN_B_ID} + +install_prerequisites(){ + echo "🧰 Install prerequisites" + apt update + apt -y install curl +} + +add_keys(){ + + echo "🔑 Adding key for $CHAIN_A_ID" + mkdir -p /home/hermes/mnemonics/ + echo $CHAIN_A_MNEMONIC > /home/hermes/mnemonics/$CHAIN_A_ID + + hermes keys add \ + --chain $CHAIN_A_ID \ + --mnemonic-file /home/hermes/mnemonics/$CHAIN_A_ID \ + --key-name $CHAIN_A_ID \ + --overwrite + + echo "🔑 Adding key for $CHAIN_B_ID" + echo $CHAIN_B_MNEMONIC > /home/hermes/mnemonics/$CHAIN_B_ID + + hermes keys add \ + --chain $CHAIN_B_ID \ + --mnemonic-file /home/hermes/mnemonics/$CHAIN_B_ID \ + --key-name $CHAIN_B_ID \ + --overwrite +} + +create_channel(){ + echo "🥱 Waiting for $CHAIN_A_ID to start" + COUNTER=0 + until $(curl --output /dev/null --silent --head --fail http://$CHAIN_A_ID:26657/status); do + printf '.' + sleep 2 + done + + echo "🥱 Waiting for $CHAIN_B_ID to start" + COUNTER=0 + until $(curl --output /dev/null --silent --head --fail http://$CHAIN_B_ID:26657/status); do + printf '.' + sleep 5 + done + + echo "📺 Creating channel $CHAIN_A_ID <> $CHAIN_B_ID" + hermes create channel \ + --a-chain $CHAIN_A_ID \ + --b-chain $CHAIN_B_ID \ + --a-port transfer \ + --b-port transfer \ + --new-client-connection --yes +} + +install_prerequisites +add_keys +create_channel + +echo "✉️ Start Hermes" +hermes start From 95572ca642a6bf6544a418a2907d9ea25514b176 Mon Sep 17 00:00:00 2001 From: expertdicer Date: Tue, 9 Jan 2024 16:21:58 +0700 Subject: [PATCH 07/32] support both amd64 and arm64 in local Dockerfile and add right hd-path in hermes keys generate --- local.Dockerfile | 18 ++++++++++++++++-- scripts/localrelayer/scripts/setup_hermes.sh | 2 ++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/local.Dockerfile b/local.Dockerfile index 7de8bf712..be7d71d9e 100644 --- a/local.Dockerfile +++ b/local.Dockerfile @@ -1,5 +1,9 @@ +ARG BUILDPLATFORM=linux/amd64 + FROM golang:1.20 AS go-builder +ARG BUILDPLATFORM + # Install minimum necessary dependencies, build Cosmos SDK, remove packages RUN apt update RUN apt install -y curl git build-essential @@ -11,12 +15,22 @@ COPY . /code/ RUN LEDGER_ENABLED=false make build -RUN cp /go/pkg/mod/github.com/classic-terra/wasmvm@v*/internal/api/libwasmvm.aarch64.so /lib/libwasmvm.aarch64.so + + +RUN if [ ${BUILDPLATFORM} = "linux/amd64" ]; then \ + WASMVM_URL="libwasmvm.x86_64.so"; \ + elif [ ${BUILDPLATFORM} = "linux/arm64" ]; then \ + WASMVM_URL="libwasmvm.aarch64.so"; \ + else \ + echo "Unsupported Build Platfrom ${BUILDPLATFORM}"; \ + exit 1; \ + fi; \ + cp /go/pkg/mod/github.com/classic-terra/wasmvm@v*/internal/api/${WASMVM_URL} /lib/${WASMVM_URL} FROM ubuntu:22.04 COPY --from=go-builder /code/build/terrad /usr/local/bin/terrad -COPY --from=go-builder /lib/libwasmvm.aarch64.so /lib/libwasmvm.aarch64.so +COPY --from=go-builder /lib/${WASMVM_URL} /lib/${WASMVM_URL} RUN apt-get update \ && apt-get install -y wget \ diff --git a/scripts/localrelayer/scripts/setup_hermes.sh b/scripts/localrelayer/scripts/setup_hermes.sh index 351f81c2b..b74546283 100755 --- a/scripts/localrelayer/scripts/setup_hermes.sh +++ b/scripts/localrelayer/scripts/setup_hermes.sh @@ -24,6 +24,7 @@ add_keys(){ echo $CHAIN_A_MNEMONIC > /home/hermes/mnemonics/$CHAIN_A_ID hermes keys add \ + --hd-path "m/44'/330'/0'/0/0" \ --chain $CHAIN_A_ID \ --mnemonic-file /home/hermes/mnemonics/$CHAIN_A_ID \ --key-name $CHAIN_A_ID \ @@ -33,6 +34,7 @@ add_keys(){ echo $CHAIN_B_MNEMONIC > /home/hermes/mnemonics/$CHAIN_B_ID hermes keys add \ + --hd-path "m/44'/330'/0'/0/0" \ --chain $CHAIN_B_ID \ --mnemonic-file /home/hermes/mnemonics/$CHAIN_B_ID \ --key-name $CHAIN_B_ID \ From 4a560eadb86b96d62d8972322847c813632fda7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=E1=BA=B7c?= Date: Tue, 9 Jan 2024 22:35:58 +0700 Subject: [PATCH 08/32] change to ubuntu 23.04 --- local.Dockerfile | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/local.Dockerfile b/local.Dockerfile index be7d71d9e..cfc893bd4 100644 --- a/local.Dockerfile +++ b/local.Dockerfile @@ -15,8 +15,6 @@ COPY . /code/ RUN LEDGER_ENABLED=false make build - - RUN if [ ${BUILDPLATFORM} = "linux/amd64" ]; then \ WASMVM_URL="libwasmvm.x86_64.so"; \ elif [ ${BUILDPLATFORM} = "linux/arm64" ]; then \ @@ -27,7 +25,7 @@ RUN if [ ${BUILDPLATFORM} = "linux/amd64" ]; then \ fi; \ cp /go/pkg/mod/github.com/classic-terra/wasmvm@v*/internal/api/${WASMVM_URL} /lib/${WASMVM_URL} -FROM ubuntu:22.04 +FROM ubuntu:23.04 COPY --from=go-builder /code/build/terrad /usr/local/bin/terrad COPY --from=go-builder /lib/${WASMVM_URL} /lib/${WASMVM_URL} From a367697b2b2a86c02977f48d3431f04a68ea0088 Mon Sep 17 00:00:00 2001 From: expertdicer Date: Wed, 10 Jan 2024 14:36:59 +0700 Subject: [PATCH 09/32] remove faucet and add delay in hermes start --- scripts/localrelayer/docker-compose.yml | 49 -------------------- scripts/localrelayer/scripts/setup_hermes.sh | 1 + 2 files changed, 1 insertion(+), 49 deletions(-) diff --git a/scripts/localrelayer/docker-compose.yml b/scripts/localrelayer/docker-compose.yml index 2eea808f1..f7a447463 100644 --- a/scripts/localrelayer/docker-compose.yml +++ b/scripts/localrelayer/docker-compose.yml @@ -24,31 +24,6 @@ services: networks: - localterra - faucet-a: - command: - - start - - localterra-a:26657 - image: confio/faucet:0.28.11 - environment: - FAUCET_CONCURRENCY: 1 - FAUCET_MNEMONIC: "increase bread alpha rigid glide amused approve oblige print asset idea enact lawn proof unfold jeans rabbit audit return chuckle valve rather cactus great" - FAUCET_PORT: 8000 - FAUCET_GAS_PRICE: 0.025uluna - FAUCET_PATH_PATTERN: "m/44'/118'/0'/0/a" - FAUCET_ADDRESS_PREFIX: luna - FAUCET_TOKENS: uluna,uion - FAUCET_CREDIT_AMOUNT_ULUNA: 10000000 # 10 luna - FAUCET_CREDIT_AMOUNT_UION: 100000 # 0.1 ion - FAUCET_REFILL_FACTOR: 8 - FAUCET_REFILL_THRESHOLD: 20 - FAUCET_COOLDOWN_TIME: 30 # 30s - ports: - - 8000:8000 - depends_on: - - localterra-a - networks: - - localterra - localterra-b: image: local:terra build: @@ -73,30 +48,6 @@ services: networks: - localterra - faucet-b: - command: - - start - - localterra-b:26657 - image: confio/faucet:0.28.11 - environment: - FAUCET_CONCURRENCY: 1 - FAUCET_MNEMONIC: "increase bread alpha rigid glide amused approve oblige print asset idea enact lawn proof unfold jeans rabbit audit return chuckle valve rather cactus great" - FAUCET_PORT: 8000 - FAUCET_GAS_PRICE: 0.025uluna - FAUCET_PATH_PATTERN: "m/44'/118'/0'/0/a" - FAUCET_ADDRESS_PREFIX: luna - FAUCET_TOKENS: uluna - FAUCET_CREDIT_AMOUNT_ULUNA: 10000000 # 10 luna - FAUCET_REFILL_FACTOR: 8 - FAUCET_REFILL_THRESHOLD: 20 - FAUCET_COOLDOWN_TIME: 30 # 30s - ports: - - 38000:8000 - depends_on: - - localterra-b - networks: - - localterra - hermes: image: informalsystems/hermes:1.1.0 user: root:root diff --git a/scripts/localrelayer/scripts/setup_hermes.sh b/scripts/localrelayer/scripts/setup_hermes.sh index b74546283..d9df30414 100755 --- a/scripts/localrelayer/scripts/setup_hermes.sh +++ b/scripts/localrelayer/scripts/setup_hermes.sh @@ -65,6 +65,7 @@ create_channel(){ --new-client-connection --yes } +sleep 10 install_prerequisites add_keys create_channel From ba19b6dffa334cefd4ad1317f66d4549c88ed676 Mon Sep 17 00:00:00 2001 From: expertdicer Date: Wed, 10 Jan 2024 14:37:17 +0700 Subject: [PATCH 10/32] bytecodes and scripts for ibc-hooks --- tests/ibc-hooks/bytecode/counter.wasm | Bin 0 -> 173503 bytes tests/ibc-hooks/bytecode/cw20_base.wasm | Bin 0 -> 318421 bytes tests/ibc-hooks/bytecode/cw20_ics20.wasm | Bin 0 -> 338457 bytes tests/ibc-hooks/bytecode/echo.wasm | Bin 0 -> 167708 bytes tests/ibc-hooks/test_hooks.sh | 52 +++++++++++++++++++++++ 5 files changed, 52 insertions(+) create mode 100644 tests/ibc-hooks/bytecode/counter.wasm create mode 100644 tests/ibc-hooks/bytecode/cw20_base.wasm create mode 100644 tests/ibc-hooks/bytecode/cw20_ics20.wasm create mode 100644 tests/ibc-hooks/bytecode/echo.wasm create mode 100755 tests/ibc-hooks/test_hooks.sh diff --git a/tests/ibc-hooks/bytecode/counter.wasm b/tests/ibc-hooks/bytecode/counter.wasm new file mode 100644 index 0000000000000000000000000000000000000000..e4dca5274bf76e894eddbdcf1066ffca23e2c990 GIT binary patch literal 173503 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 literal 0 HcmV?d00001 diff --git a/tests/ibc-hooks/bytecode/cw20_base.wasm b/tests/ibc-hooks/bytecode/cw20_base.wasm new file mode 100644 index 0000000000000000000000000000000000000000..2fe4d034b7075b580fe94c52dca819ac3559dbb6 GIT binary patch literal 318421 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

^*H7?)>4lr>&7ria6gnx)cswUHu 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