diff --git a/.envrc b/.envrc index 3a257cb1f3..0ada8850ac 100644 --- a/.envrc +++ b/.envrc @@ -2,10 +2,6 @@ if ! has make ; then echo "make is not installed"; exit 1 fi -if ! has go ; then - echo "go is not installed"; exit 1 -fi - if ! has unzip ; then echo "unzip is not installed"; exit 1 fi @@ -30,7 +26,9 @@ if ! has readlink ; then echo "readlink is not installed"; exit 1 fi -export AKASH_ROOT=$(pwd) +AKASH_ROOT=$(pwd) + +export AKASH_ROOT dotenv diff --git a/.github/workflows/release-dry-run.yaml b/.github/workflows/release-dry-run.yaml index b1f506a114..739ff97920 100644 --- a/.github/workflows/release-dry-run.yaml +++ b/.github/workflows/release-dry-run.yaml @@ -5,11 +5,13 @@ defaults: shell: bash on: + pull_request: push: branches: - master - mainnet/main - pull_request: + tags: + - v* jobs: dry-run: diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index d54a3da0ad..1a191d853a 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -10,8 +10,44 @@ on: - v* jobs: + ensure-dry-run: + runs-on: ubuntu-latest + steps: + - name: Ensure release/dry-run PASS + uses: lewagon/wait-on-check-action@v1.3.1 + with: + ref: ${{ github.ref }} + check-name: 'dry-run' + repo-token: ${{ secrets.GITHUB_TOKEN }} + wait-interval: 10 + allowed-conclusions: success + ensure-tests: + runs-on: ubuntu-latest + steps: + - name: Ensure tests PASS + uses: lewagon/wait-on-check-action@v1.3.1 + with: + ref: ${{ github.ref }} + check-name: 'tests' + repo-token: ${{ secrets.GITHUB_TOKEN }} + wait-interval: 10 + allowed-conclusions: success + ensure-tools: + runs-on: ubuntu-latest + steps: + - name: Ensure tools PASS + uses: lewagon/wait-on-check-action@v1.3.1 + with: + ref: ${{ github.ref }} + check-name: 'tools' + repo-token: ${{ secrets.GITHUB_TOKEN }} + wait-interval: 10 + allowed-conclusions: success publish: runs-on: ubuntu-latest + needs: + - ensure-dry-run + - ensure-tests steps: - uses: actions/checkout@v3 - run: git fetch --prune --unshallow @@ -27,10 +63,7 @@ jobs: uses: docker/setup-buildx-action@v2 - name: release version run: echo "RELEASE_TAG=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV - - if: (github.ref == 'refs/heads/master') || (github.ref == 'refs/heads/mainnet/main') - name: release dry-run GORELEASER_SKIP_VALIDATE=true - run: make release-dry-run GORELEASER_SKIP_VALIDATE=true - - name: release dry-run + - name: release run: make release - name: Login to GHCR uses: docker/login-action@v2 diff --git a/.github/workflows/upgrades-release.yaml b/.github/workflows/upgrades-release.yaml new file mode 100644 index 0000000000..4c2bf8b422 --- /dev/null +++ b/.github/workflows/upgrades-release.yaml @@ -0,0 +1,43 @@ +name: upgrades +defaults: + run: + shell: bash + +on: + push: + tags: + - v* +jobs: + ensure-release: + runs-on: ubuntu-latest + steps: + - name: Ensure release PASS + uses: lewagon/wait-on-check-action@v1.3.1 + with: + ref: ${{ github.ref }} + check-name: 'release' + repo-token: ${{ secrets.GITHUB_TOKEN }} + wait-interval: 10 + allowed-conclusions: success + test: + runs-on: upgrade-tester + needs: + - ensure-release + steps: + - uses: actions/checkout@v3 + - run: git fetch --prune --unshallow + - name: set environment + uses: HatsuneMiku3939/direnv-action@v1 + - uses: actions/setup-go@v3 + with: + go-version: "${{ env.GOLANG_VERSION }}" + - name: configure variables + run: | + test_required=$(./script/upgrades.sh test-required ${{ github.ref }}) + echo "TEST_REQUIRED=$test_required" >> $GITHUB_ENV + echo "RELEASE_TAG=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV + - name: run test + if: env.TEST_REQUIRED == 'true' + run: | + cd tests/upgrade + UPGRADE_BINARY_VERSION=${{ env.RELEASE_TAG }} make test diff --git a/.github/workflows/upgrades.yaml b/.github/workflows/upgrades.yaml new file mode 100644 index 0000000000..4348a3ec46 --- /dev/null +++ b/.github/workflows/upgrades.yaml @@ -0,0 +1,44 @@ +name: tests +defaults: + run: + shell: bash + +on: + pull_request: + push: + branches: + - master + - mainnet/main + +jobs: + upgrade: + runs-on: upgrade-tester + steps: + - uses: actions/checkout@v3 + - run: git fetch --prune --unshallow + - name: set environment + uses: HatsuneMiku3939/direnv-action@v1 + - uses: actions/setup-go@v3 + with: + go-version: "${{ env.GOLANG_VERSION }}" + - name: configure variables + run: | + test_required=$(./script/upgrades.sh test-required ${{ github.ref }}) + echo "TEST_REQUIRED=$test_required" >> $GITHUB_ENV + - name: run test + if: env.TEST_REQUIRED == 'true' + run: | + cd tests/upgrade + make test + - name: upload stderr + if: always() && steps."run test".outcome != 'skipped' + uses: actions/upload-artifact@v3 + with: + name: stderr.log + path: .cache/run/upgrade/stderr.log + - name: upload stdout + if: always() && steps."run test".outcome != 'skipped' + uses: actions/upload-artifact@v3 + with: + name: stdout.log + path: .cache/run/upgrade/stdout.log diff --git a/Makefile b/Makefile index e7c7b720b9..32d261da12 100644 --- a/Makefile +++ b/Makefile @@ -6,9 +6,6 @@ KIND_APP_IP ?= $(shell make -sC _run/kube kind-k8s-ip) KIND_APP_PORT ?= $(shell make -sC _run/kube app-http-port) KIND_VARS ?= KUBE_INGRESS_IP="$(KIND_APP_IP)" KUBE_INGRESS_PORT="$(KIND_APP_PORT)" -UNAME_OS := $(shell uname -s) -UNAME_ARCH := $(shell uname -m) - include make/init.mk .DEFAULT_GOAL := bins diff --git a/app/export.go b/app/export.go index 323c63daca..807d6196bf 100644 --- a/app/export.go +++ b/app/export.go @@ -5,6 +5,8 @@ import ( "log" "math/rand" + "github.com/spf13/viper" + abci "github.com/tendermint/tendermint/abci/types" logger "github.com/tendermint/tendermint/libs/log" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" @@ -17,7 +19,6 @@ import ( slashingtypes "github.com/cosmos/cosmos-sdk/x/slashing/types" "github.com/cosmos/cosmos-sdk/x/staking" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" - "github.com/spf13/viper" ) // ExportAppStateAndValidators exports the state of the application for a genesis diff --git a/go.mod b/go.mod index c0a14f3ce3..5db355203e 100644 --- a/go.mod +++ b/go.mod @@ -9,8 +9,10 @@ require ( github.com/cosmos/cosmos-sdk v0.45.15 github.com/cosmos/ibc-go/v3 v3.4.0 github.com/gogo/protobuf v1.3.3 + github.com/google/go-github/v52 v52.0.0 github.com/gorilla/mux v1.8.0 github.com/gorilla/websocket v1.5.0 + github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 github.com/grpc-ecosystem/grpc-gateway v1.16.0 github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.14.0 @@ -26,6 +28,8 @@ require ( github.com/tendermint/tm-db v0.6.7 github.com/theckman/yacspin v0.13.12 go.step.sm/crypto v0.26.0 + golang.org/x/mod v0.8.0 + golang.org/x/oauth2 v0.7.0 golang.org/x/sync v0.1.0 google.golang.org/grpc v1.53.0 gopkg.in/yaml.v3 v3.0.1 @@ -66,6 +70,7 @@ require ( github.com/ChainSafe/go-schnorrkel v0.0.0-20200405005733-88cbf1b4c40d // indirect github.com/DataDog/zstd v1.5.0 // indirect github.com/HdrHistogram/hdrhistogram-go v1.1.2 // indirect + github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8 // indirect github.com/Workiva/go-datastructures v1.0.53 // indirect github.com/armon/go-metrics v0.4.1 // indirect github.com/beorn7/perks v1.0.1 // indirect @@ -73,6 +78,7 @@ require ( github.com/btcsuite/btcd/btcec/v2 v2.3.2 // indirect github.com/cespare/xxhash v1.1.0 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/cloudflare/circl v1.3.1 // indirect github.com/cockroachdb/errors v1.9.1 // indirect github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect github.com/cockroachdb/pebble v0.0.0-20220817183557-09c6e030a677 // indirect @@ -112,6 +118,7 @@ require ( github.com/golang/snappy v0.0.4 // indirect github.com/google/btree v1.1.2 // indirect github.com/google/go-cmp v0.5.9 // indirect + github.com/google/go-querystring v1.1.0 // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/google/orderedcode v0.0.1 // indirect github.com/gorilla/handlers v1.5.1 // indirect @@ -167,12 +174,13 @@ require ( github.com/zondax/hid v0.9.1 // indirect github.com/zondax/ledger-go v0.14.1 // indirect go.etcd.io/bbolt v1.3.6 // indirect - golang.org/x/crypto v0.6.0 // indirect + golang.org/x/crypto v0.7.0 // indirect golang.org/x/exp v0.0.0-20221019170559-20944726eadf // indirect - golang.org/x/net v0.7.0 // indirect - golang.org/x/sys v0.5.0 // indirect - golang.org/x/term v0.5.0 // indirect - golang.org/x/text v0.7.0 // indirect + golang.org/x/net v0.9.0 // indirect + golang.org/x/sys v0.7.0 // indirect + golang.org/x/term v0.7.0 // indirect + golang.org/x/text v0.9.0 // indirect + google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto v0.0.0-20230209215440-0dfe4f8abfcc // indirect google.golang.org/protobuf v1.28.2-0.20220831092852-f930b1dc76e8 // indirect gopkg.in/inf.v0 v0.9.1 // indirect diff --git a/go.sum b/go.sum index e967423a18..07dfff3571 100644 --- a/go.sum +++ b/go.sum @@ -80,6 +80,8 @@ github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb0 github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8 h1:wPbRQzjjwFc0ih8puEVAOFGELsn1zoIIYdxvML7mDxA= +github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8/go.mod h1:I0gYDMZ6Z5GRU7l58bNFSkPTFN6Yl12dsUlAZ8xy98g= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398/go.mod h1:a1uqRtAwp2Xwc6WNPJEufxJ7fx3npB4UV/JOLmbu5I0= @@ -194,6 +196,9 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMn 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/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= +github.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I= +github.com/cloudflare/circl v1.3.1 h1:4OVCZRL62ijwEwxnF6I7hLwxvIYi3VaZt8TflkqtrtA= +github.com/cloudflare/circl v1.3.1/go.mod h1:+CauBF6R70Jqcyl8N2hC8pAXYbWkGIezuSbuGLtRhnw= github.com/cloudflare/cloudflare-go v0.14.0/go.mod h1:EnwdgGMaFOruiPZRFSgn+TsQ3hQ7C/YWzIGLeu5c304= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cockroachdb/apd/v3 v3.1.0 h1:MK3Ow7LH0W8zkd5GMKA1PvS9qG3bWFI95WaVNfyZJ/w= @@ -477,7 +482,11 @@ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-github/v52 v52.0.0 h1:uyGWOY+jMQ8GVGSX8dkSwCzlehU3WfdxQ7GweO/JP7M= +github.com/google/go-github/v52 v52.0.0/go.mod h1:WJV6VEEUPuMo5pXqqa2ZCZEdbQqua4zAk2MZTIo+m+4= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= +github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -524,6 +533,8 @@ github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/ad github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/graph-gophers/graphql-go v1.3.0/go.mod h1:9CQHMSxwO4MprSdzoIEobiHpoLtHm77vfxsvsIN5Vuc= +github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA= +github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.2.2/go.mod h1:EaizFBKfUKtMIF5iaDEhniwNedqGo9FuLFzppDr3uwI= github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw= @@ -1113,8 +1124,8 @@ golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc= -golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= +golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A= +golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1157,7 +1168,8 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 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.7.0 h1:LapD9S96VoQRhi/GrNTqeBJFrUjs5UHCAtTlgwA5oZA= +golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8= +golang.org/x/mod v0.8.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-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1214,8 +1226,8 @@ golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= -golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM= +golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1227,6 +1239,8 @@ golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= +golang.org/x/oauth2 v0.7.0 h1:qe6s0zUXlPX80/dITx3440hWZ7GwMwgDDyrSGTPJG/g= +golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -1325,13 +1339,13 @@ golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= +golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY= -golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.7.0 h1:BEvjmm5fURWqcfbSKTdpkDXYBrUS1c0m8agp14W48vQ= +golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1341,8 +1355,8 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -1410,7 +1424,7 @@ golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.4.0 h1:7mTAgkunk3fr4GAloyyCasadO6h9zSsQZbwvcaIciV4= +golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= 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= @@ -1450,6 +1464,7 @@ google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7 google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180518175338-11a468237815/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= diff --git a/make/init.mk b/make/init.mk index 5cdd6254e0..b15962bc0c 100644 --- a/make/init.mk +++ b/make/init.mk @@ -1,6 +1,11 @@ UNAME_OS := $(shell uname -s) UNAME_ARCH := $(shell uname -m) +# certain targets need to use bash +# detect where bash is installed +# use akash-node-ready target as example +BASH_PATH := $(shell which bash) + ifeq (, $(shell which direnv)) $(warning "No direnv in $(PATH), consider installing. https://direnv.net") endif @@ -48,6 +53,7 @@ STATIK_VERSION ?= v0.1.7 GIT_CHGLOG_VERSION ?= v0.15.1 MODVENDOR_VERSION ?= v0.3.0 MOCKERY_VERSION ?= 2.24.0 +COSMOVISOR_VERSION ?= v1.4.0 # ==== Build tools version tracking ==== # _VERSION_FILE points to the marker file for the installed version. @@ -56,7 +62,8 @@ MODVENDOR_VERSION_FILE := $(AKASH_DEVCACHE_VERSIONS)/modvendor/$(MODVE GIT_CHGLOG_VERSION_FILE := $(AKASH_DEVCACHE_VERSIONS)/git-chglog/$(GIT_CHGLOG_VERSION) MOCKERY_VERSION_FILE := $(AKASH_DEVCACHE_VERSIONS)/mockery/v$(MOCKERY_VERSION) GOLANGCI_LINT_VERSION_FILE := $(AKASH_DEVCACHE_VERSIONS)/golangci-lint/$(GOLANGCI_LINT_VERSION) -STATIK_VERSION_FILE := $(AKASH_DEVCACHE_VERSIONS)/statik/$(STATIK_VERSION) +STATIK_VERSION_FILE := $(AKASH_DEVCACHE_VERSIONS)/statik/$(STATIK_VERSION) +COSMOVISOR_VERSION_FILE := $(AKASH_DEVCACHE_VERSIONS)/cosmovisor/$(COSMOVISOR_VERSION) # ==== Build tools executables ==== MODVENDOR := $(AKASH_DEVCACHE_BIN)/modvendor @@ -65,5 +72,6 @@ MOCKERY := $(AKASH_DEVCACHE_BIN)/mockery NPM := npm GOLANGCI_LINT := $(AKASH_DEVCACHE_BIN)/golangci-lint STATIK := $(AKASH_DEVCACHE_BIN)/statik +COSMOVISOR := $(AKASH_DEVCACHE_BIN)/cosmovisor include $(AKASH_ROOT)/make/setup-cache.mk diff --git a/make/setup-cache.mk b/make/setup-cache.mk index 84c54ae1a3..de15fb916c 100644 --- a/make/setup-cache.mk +++ b/make/setup-cache.mk @@ -53,5 +53,14 @@ $(STATIK_VERSION_FILE): $(AKASH_DEVCACHE) touch $@ $(STATIK): $(STATIK_VERSION_FILE) +$(COSMOVISOR_VERSION_FILE): $(AKASH_DEVCACHE) + @echo "installing cosmovisor $(COSMOVISOR_VERSION) ..." + rm -f $(COSMOVISOR) + GOBIN=$(AKASH_DEVCACHE_BIN) $(GO) install cosmossdk.io/tools/cosmovisor/cmd/cosmovisor@$(COSMOVISOR_VERSION) + rm -rf "$(dir $@)" + mkdir -p "$(dir $@)" + touch $@ +$(COSMOVISOR): $(COSMOVISOR_VERSION_FILE) + cache-clean: rm -rf $(AKASH_DEVCACHE) diff --git a/make/test-upgrade.mk b/make/test-upgrade.mk new file mode 100644 index 0000000000..a6e7d1158a --- /dev/null +++ b/make/test-upgrade.mk @@ -0,0 +1,120 @@ +AP_RUN_DIR := $(AKASH_RUN)/upgrade + +AKASH_HOME := $(AP_RUN_DIR)/.akash +AKASH_INIT := $(AP_RUN_DIR)/.akash-init + +MNEMONIC := "wild random elephant refuse clock effort menu barely broccoli team mind magnet pretty fashion fame category turtle rug exclude card view civil purity powder" +TEST_PRIV_KEY := '{"address":"06DCDACF975BE69C039C62052F9FE0F3906575D1","pub_key":{"type":"tendermint/PubKeyEd25519","value":"d0sS1j4EdrAkBkpFXb50lkibj7+Kwh9UtGPO5O35Pes="},"priv_key":{"type":"tendermint/PrivKeyEd25519","value":"ZVh/Fsra8CKOuGkBT7/dpdAy/dvfLaPeDZZ1suIw2h53SxLWPgR2sCQGSkVdvnSWSJuPv4rCH1S0Y87k7fk96w=="}}' + +export AKASH_HOME +export AKASH_KEYRING_BACKEND = test +export AKASH_GAS_ADJUSTMENT = 2 +export AKASH_CHAIN_ID = localakash +export AKASH_YES = true +export AKASH_GAS_PRICES = 0.025uakt +export AKASH_GAS = auto +export AKASH_STATESYNC_ENABLE = false +export AKASH_LOG_COLOR = true + +KEY_OPTS := --keyring-backend=$(AKASH_KEYRING_BACKEND) +KEY_NAME ?= validator +UPGRADE_TO ?= v0.24.0 +UPGRADE_FROM := $(shell cat $(ROOT_DIR)/meta.json | jq --arg name $(UPGRADE_TO) '.upgrades[$$name].from_version' | tr -d '\n') +GENESIS_BINARY_VERSION := $(shell cat $(ROOT_DIR)/meta.json | jq --arg name $(UPGRADE_TO) '.upgrades[$$name].from_binary' | tr -d '\n') +UPGRADE_BINARY_VERSION ?= local + +GENESIS_CONFIG_TEMPLATE ?= $(CURDIR)/config-$(UPGRADE_TO).tmpl.json +GENESIS_ORIG ?= https://github.com/akash-network/testnetify/releases/download/$(UPGRADE_FROM)/genesis.json.tar.lz4 +GENESIS_DEST := $(AKASH_HOME)/config/genesis.json +PRIV_VALIDATOR_KEY := $(AKASH_HOME)/config/priv_validator_key.json +KEYRING_DIR := $(AKASH_HOME)/keyring-test +KEY_FILE := $(KEYRING_DIR)/$(KEY_NAME).info +COSMOVISOR_DIR := $(AKASH_HOME)/cosmovisor +GENESIS_BINARY_DIR := $(COSMOVISOR_DIR)/genesis/bin +UPGRADE_BINARY_DIR := $(COSMOVISOR_DIR)/upgrades/$(UPGRADE_TO)/bin +GENESIS_BINARY := $(GENESIS_BINARY_DIR)/akash +UPGRADE_BINARY := $(UPGRADE_BINARY_DIR)/akash + +KEY_NAMES := validator +GENESIS_ACCOUNTS := $(KEY_NAMES) + +$(AKASH_HOME): + mkdir -p $(COSMOVISOR_DIR)/genesis/bin + +$(AKASH_INIT): $(AKASH_HOME) $(COSMOVISOR) binaries node-init keys-init $(GENESIS_DEST) node-init-finalize + touch $@ + +.INTERMEDIATE: init +init: $(AKASH_INIT) + +$(GENESIS_DEST): $(GENESIS_BINARY) $(PRIV_VALIDATOR_KEY) + wget -qO - "$(GENESIS_ORIG)" | lz4 - -d | tar xf - -C $(AKASH_HOME)/config + +.PHONY: genesis +genesis: $(GENESIS_DEST) + +.INTERMEDIATE: genesis-binary +.INTERMEDIATE: upgrade-binary +$(GENESIS_BINARY): + $(ROOT_DIR)/install.sh -b "$(GENESIS_BINARY_DIR)" $(GENESIS_BINARY_VERSION) + chmod +x $(GENESIS_BINARY) + +ifeq ($(UPGRADE_BINARY_VERSION), local) +$(UPGRADE_BINARY): AKASH=$(UPGRADE_BINARY) +$(UPGRADE_BINARY): + mkdir -p $(UPGRADE_BINARY_DIR) + make make -sC $(ROOT_DIR) akash + +.PHONY: clean-upgrade-binary +clean-upgrade-binary: + rm -f $(UPGRADE_BINARY) +upgrade-binary: clean-upgrade-binary $(UPGRADE_BINARY) +endif + +genesis-binary: $(GENESIS_BINARY) +upgrade-binary: + +.INTERMEDIATE: +binaries: genesis-binary upgrade-binary + +.INTERMEDIATE: node-init +node-init: $(PRIV_VALIDATOR_KEY) + +$(PRIV_VALIDATOR_KEY): $(GENESIS_BINARY) + $(GENESIS_BINARY) init --home=$(AKASH_HOME) upgrade-validator >/dev/null 2>&1 + rm $(GENESIS_DEST) + echo $(TEST_PRIV_KEY) > $(PRIV_VALIDATOR_KEY) + +.INTERMEDIATE: keys-init +keys-init: $(patsubst %,$(KEYRING_DIR)/%.info,$(KEY_NAMES)) + +$(KEYRING_DIR)/%.info: + echo $(MNEMONIC) | $(GENESIS_BINARY) --home=$(AKASH_HOME) --keyring-backend=test keys add $(@:$(KEYRING_DIR)/%.info=%) --recover + +.INTERMEDIATE: node-init-finalize +node-init-finalize: + +.PHONY: genesis +genesis: $(GENESIS_DEST) + +.PHONY: test +test: init upgrade-binary + $(GO) test ./... -tags e2e.upgrade -timeout 60m -v -args \ + -home=$(AP_RUN_DIR) \ + -cosmovisor=$(COSMOVISOR) \ + -genesis-binary=$(GENESIS_BINARY) \ + -chain-id="localakash" \ + -upgrade-name=$(UPGRADE_TO) \ + -upgrade-version="$(UPGRADE_BINARY_VERSION)" \ + -test-cases=upgrades-$(UPGRADE_TO).json + +.PHONY: test-reset +test-reset: + rm -rf $(AKASH_HOME)/data/* + rm -rf $(COSMOVISOR_DIR)/current + rm -rf $(COSMOVISOR_DIR)/upgrades/$(UPGRADE_TO)/upgrade-info.json + @echo '{"height":"0","round": 0,"step": 0}' > $(AKASH_HOME)/data/priv_validator_state.json + +.PHONY: clean +clean: + rm -rf $(AP_RUN_DIR) diff --git a/meta.json b/meta.json new file mode 100644 index 0000000000..b4b1bd60cb --- /dev/null +++ b/meta.json @@ -0,0 +1,14 @@ +{ + "releases": { + "revoked": [ + "v0.22.1" + ] + }, + "upgrades": { + "v0.24.0": { + "skipped": false, + "from_binary": "v0.22.6", + "from_version": "v0.22.0" + } + } +} diff --git a/script/upgrades.sh b/script/upgrades.sh new file mode 100755 index 0000000000..1ec7e54033 --- /dev/null +++ b/script/upgrades.sh @@ -0,0 +1,91 @@ +#!/usr/bin/env bash + +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)" +ROOT_DIR=$(realpath "${SCRIPT_DIR}"/../) + +semver=$(printf %q "${SCRIPT_DIR}/semver.sh") + +PROG=upgrades.sh + +USAGE="\ +Usage: + $PROG test-required + $PROG --help +Options: + -h, --help Print this help message. +Commands: + test-required Determine if latest present upgrade needed test run. + Conditions to run test: + - If current reference matches last upgrade in a codebase + - If the codebase has tag matching to the upgrade name, but release is marked as revoked + - If the codebase does not have tag matching upgrade name + Exit codes: + - 0 test required + - 1 something went wrong. check stderr" + +echoerr() { echo "$@" 1>&2; } + +case "$1" in +test-required) + shift + curr_ref=$1 + + upgrades_dir=${ROOT_DIR}/upgrades/software + upgrade_name=$(find "${upgrades_dir}" -mindepth 1 -maxdepth 1 -type d | awk -F/ '{print $NF}' | sort -r | head -n 1) + + meta=$(cat "${ROOT_DIR}/meta.json") + # upgrade under test is always highest semver in the upgrades list + #uut=$(echo "$meta" | jq -er '.upgrades | keys | .[]' | sort -r | head -n 1) + + # shellcheck disable=SC2086 + $semver validate $upgrade_name + + # current git reference is matching upgrade name. looks like release has been cut + # so lets run the last test + if [[ "$curr_ref" == "$upgrade_name" ]]; then + echo -e "true" + exit 0 + fi + + cnt=0 + while :; do + cnt=$((cnt+1)) + if [[ $cnt -gt 100 ]];then + echoerr "unable to determine tag to test upgrade" + exit 1 + fi + + # shellcheck disable=SC2086 + if git tag -v $upgrade_name >/dev/null 2>&1; then + if echo "$meta" | jq -e --arg name $upgrade_name '.revoked_releases[] | contains($name)' >/dev/null 2>&1; then + $semver bump patch $upgrade_name + upgrade_name="v$upgrade_name" + else + upgrade_name="" + break + fi + else + break + fi + done + + if [[ "$upgrade_name" == "" ]]; then + echo -n "false" + else + echo -n "true" + fi + + exit 0 + ;; +--help | -h) + echo -e "$USAGE"; + exit 0 + ;; +*) + echo "unknown command $1" + echo -e "$USAGE"; + exit 1 + ;; +esac diff --git a/tests/upgrade/Makefile b/tests/upgrade/Makefile new file mode 100644 index 0000000000..ff2251c300 --- /dev/null +++ b/tests/upgrade/Makefile @@ -0,0 +1,2 @@ +include $(ROOT_DIR)/make/init.mk +include $(ROOT_DIR)/make/test-upgrade.mk diff --git a/tests/upgrade/config-v0.24.0.tmpl.json b/tests/upgrade/config-v0.24.0.tmpl.json new file mode 100644 index 0000000000..6a67ae1dbc --- /dev/null +++ b/tests/upgrade/config-v0.24.0.tmpl.json @@ -0,0 +1,41 @@ +{ + "chain_id": "localakash", + "accounts": { + "add": [ + { + "address": "{{ (ds "account_address") }}", + "pubkey": {{ (ds "account_pubkey") }}, + "coins": [ + "2000000000000000uakt" + ] + } + ] + }, + "validators": { + "add": [ + { + "name": "upgrade-tester", + "pubkey": {{ (ds "validator_pubkey") }}, + "rates": { + "rate": "0.05", + "maxRate": "0.8", + "maxChangeRate": "0.1" + }, + "bonded": true, + "delegators": [ + { + "address": "{{ (ds "account_address") }}", + "coins": [ + "1950000000000000uakt" + ] + } + ] + } + ] + }, + "gov": { + "voting_params": { + "voting_period": "60s" + } + } +} diff --git a/tests/upgrade/upgrade_test.go b/tests/upgrade/upgrade_test.go new file mode 100644 index 0000000000..b03355716d --- /dev/null +++ b/tests/upgrade/upgrade_test.go @@ -0,0 +1,944 @@ +//go:build e2e.upgrade + +package upgrade + +import ( + "bufio" + "context" + "encoding/json" + "flag" + "fmt" + "io" + "net/http" + "os" + "os/exec" + "os/signal" + "regexp" + "strconv" + "strings" + "syscall" + "testing" + "time" + + "github.com/google/go-github/v52/github" + "github.com/gregjones/httpcache" + "github.com/stretchr/testify/require" + "golang.org/x/mod/semver" + "golang.org/x/oauth2" + "golang.org/x/sync/errgroup" + + sdk "github.com/cosmos/cosmos-sdk/types" + + // init sdk config + _ "github.com/akash-network/akash-api/go/sdkutil" + + "github.com/akash-network/node/pubsub" +) + +const ( + blockTimeWindow = 7 * time.Second +) + +type nodeEvent int +type watchdogCtrl int +type testStage int +type testModuleStatus int + +const ( + nodeEventStart nodeEvent = iota + nodeEventReplayBlocksStart + nodeEventReplayBlocksDone + nodeEventBlockIndexed + nodeEventUpgradeDetected + nodeEventAddedModule + nodeEventRemovedModule + nodeEventModuleMigration +) + +const ( + watchdogCtrlStart watchdogCtrl = iota + watchdogCtrlPause + watchdogCtrlStop + watchdogCtrlBlock +) + +const ( + testStagePreUpgrade testStage = iota + testStageUpgrade + testStagePostUpgrade +) + +const ( + testModuleStatusUnexpected testModuleStatus = iota + testModuleStatusNotChecked + testModuleStatusChecked +) + +type publisher interface { + Publish(pubsub.Event) error +} + +var ( + testStageMapStr = map[testStage]string{ + testStagePreUpgrade: "preupgrade", + testStageUpgrade: "upgrade", + testStagePostUpgrade: "postupgrade", + } + + testModuleStatusMapStr = map[testModuleStatus]string{ + testModuleStatusUnexpected: "unexpected", + testModuleStatusNotChecked: "notchecked", + testModuleStatusChecked: "checked", + } +) + +type eventCtxModule struct { + name string +} + +type eventCtxModuleMigration struct { + name string + from string + to string +} + +type event struct { + id nodeEvent + ctx interface{} +} + +type votingParams struct { + VotingPeriod string `json:"voting_period"` +} + +type depositParams struct { + MinDeposit sdk.Coins `json:"min_deposit"` +} + +type govParams struct { + VotingParams votingParams `json:"voting_params"` + DepositParams depositParams `json:"deposit_params"` +} + +type proposalResp struct { + ProposalID string `json:"proposal_id"` + Content struct { + Title string `json:"title"` + } `json:"content"` +} + +type proposalsResp struct { + Proposals []proposalResp `json:"proposals"` +} + +type wdReq struct { + event watchdogCtrl + resp chan<- struct{} +} + +type nodeStatus struct { + SyncInfo struct { + LatestBlockHeight string `json:"latest_block_height"` + CatchingUp bool `json:"catching_up"` + } `json:"SyncInfo"` +} + +type testCases struct { + Modules struct { + Added []string `json:"added"` + Removed []string `json:"removed"` + Renamed struct { + From string `json:"from"` + To string `json:"to"` + } `json:"renamed"` + } `json:"modules"` + Migrations map[string]struct { + From string `json:"from"` + To string `json:"to"` + } `json:"migrations"` +} + +type launcherParams struct { + home string + homeDir string + chainID string + upgradeName string + upgradeHeight int64 +} +type launcher struct { + t *testing.T + ctx context.Context + cancel context.CancelFunc + group *errgroup.Group + cosmovisor string + upgradeInfo string + params launcherParams + tConfig testCases + upgradeSuccessful chan struct{} + testErrs []string +} + +type upgradeInfo struct { + Binaries map[string]string `json:"binaries"` +} + +var ( + homedir = flag.String("home", "", "akash home") + cosmovisor = flag.String("cosmovisor", "", "path to cosmovisor") + genesisBinary = flag.String("genesis-binary", "", "path to the akash binary with version prior the upgrade") + upgradeVersion = flag.String("upgrade-version", "local", "akash release to download. local if it is built locally") + upgradeName = flag.String("upgrade-name", "", "name of the upgrade") + chainID = flag.String("chain-id", "", "chain-id") + testCasesFile = flag.String("test-cases", "", "") +) + +func TestUpgrade(t *testing.T) { + ctx := context.Background() + + ctx, stop := signal.NotifyContext(ctx, syscall.SIGINT, syscall.SIGKILL, syscall.SIGTERM) + defer stop() + + t.Log("detecting arguments") + + require.NotEqual(t, "", *homedir, "empty homedir flag") + require.NotEqual(t, "", *cosmovisor, "empty cosmovisor flag") + require.NotEqual(t, "", *genesisBinary, "empty genesis-binary flag") + require.NotEqual(t, "", *upgradeName, "empty upgrade-name flag") + require.NotEqual(t, "", *upgradeVersion, "empty upgrade-version flag") + require.NotEqual(t, "", *chainID, "empty chain-id flag") + require.NotEqual(t, "", *testCasesFile, "empty test-cases flag") + + if *upgradeVersion != "local" && !semver.IsValid(*upgradeVersion) { + require.Fail(t, "upgrade-name contains invalid value. expected local|") + } + + info, err := os.Stat(*homedir) + require.NoError(t, err) + require.True(t, info.IsDir(), "homedir flag is not a dir") + + info, err = os.Stat(*cosmovisor) + require.NoError(t, err) + require.False(t, info.IsDir(), "value in cosmovisor flag is not a file") + require.True(t, isOwnerExecutable(info.Mode()), "cosmovisor must be executable file") + + info, err = os.Stat(*genesisBinary) + require.NoError(t, err) + require.False(t, info.IsDir(), "value in genesis-binary flag is not a file") + require.True(t, isOwnerExecutable(info.Mode()), "akash must be executable file") + + var tConfig testCases + // load testcases config + { + tFile, err := os.Open(*testCasesFile) + require.NoError(t, err) + defer func() { + _ = tFile.Close() + }() + + data, err := io.ReadAll(tFile) + require.NoError(t, err) + + err = json.Unmarshal(data, &tConfig) + require.NoError(t, err) + } + + l := newLauncher(ctx, t) + + if *upgradeVersion != "local" { + t.Logf("generating upgradeinfo from release %s", *upgradeVersion) + l.upgradeInfo, err = generateUpgradeInfo(ctx, *upgradeVersion) + require.NoError(t, err) + require.NotEqual(t, "", l.upgradeInfo) + } + + l.cosmovisor = *cosmovisor + l.tConfig = tConfig + l.params = launcherParams{ + home: *homedir, + homeDir: fmt.Sprintf("%s/.akash", *homedir), + chainID: *chainID, + upgradeName: *upgradeName, + upgradeHeight: 0, + } + + group, ctx := errgroup.WithContext(ctx) + + group.Go(func() error { + t.Log("starting cosmovisor") + return l.run() + }) + + err = group.Wait() + require.NoError(t, err) + + if len(l.testErrs) > 0 { + for _, msg := range l.testErrs { + t.Log(msg) + } + + t.Fail() + } +} + +func newLauncher(ctx context.Context, t *testing.T) *launcher { + ctx, cancel := context.WithCancel(ctx) + group, ctx := errgroup.WithContext(ctx) + return &launcher{ + t: t, + ctx: ctx, + cancel: cancel, + group: group, + upgradeSuccessful: make(chan struct{}, 1), + } +} + +func isOwnerExecutable(mode os.FileMode) bool { + return mode&0100 != 0 +} + +func generateUpgradeInfo(ctx context.Context, tag string) (string, error) { + tc := &http.Client{ + Transport: &oauth2.Transport{ + Base: httpcache.NewMemoryCacheTransport(), + Source: oauth2.StaticTokenSource( + &oauth2.Token{AccessToken: os.Getenv("GITHUB_TOKEN")}, + ), + }, + } + + gh := github.NewClient(tc) + + rel, resp, err := gh.Repositories.GetReleaseByTag(ctx, "akash-network", "node", tag) + if err != nil { + return "", err + } + + if resp.StatusCode != http.StatusOK { + return "", fmt.Errorf("no release for tag %s", tag) + } + + sTag := strings.TrimPrefix(tag, "v") + checksumsAsset := fmt.Sprintf("akash_%s_checksums.txt", sTag) + var checksumsID int64 + for _, asset := range rel.Assets { + if asset.GetName() == checksumsAsset { + checksumsID = asset.GetID() + } + } + + body, _, err := gh.Repositories.DownloadReleaseAsset(ctx, "akash-network", "node", checksumsID, http.DefaultClient) + if err != nil { + return "", err + } + defer func() { + _ = body.Close() + }() + + info := &upgradeInfo{ + Binaries: make(map[string]string), + } + + urlBase := fmt.Sprintf("https://github.com/akash-network/node/releases/download/%s", tag) + scanner := bufio.NewScanner(body) + for scanner.Scan() { + tuple := strings.Split(scanner.Text(), " ") + if len(tuple) != 2 { + return "", fmt.Errorf("invalid checksum format") + } + + switch tuple[1] { + case "akash_linux_amd64.zip": + info.Binaries["linux/amd64"] = fmt.Sprintf("%s/%s?checksum=sha256:%s", urlBase, tuple[1], tuple[0]) + case "akash_linux_arm64.zip": + info.Binaries["linux/arm64"] = fmt.Sprintf("%s/%s?checksum=sha256:%s", urlBase, tuple[1], tuple[0]) + case "akash_darwin_all.zip": + info.Binaries["darwin/amd64"] = fmt.Sprintf("%s/%s?checksum=sha256:%s", urlBase, tuple[1], tuple[0]) + info.Binaries["darwin/arm64"] = fmt.Sprintf("%s/%s?checksum=sha256:%s", urlBase, tuple[1], tuple[0]) + } + } + + res, err := json.Marshal(info) + if err != nil { + return "", err + } + + return string(res), nil +} + +func executeCommand(ctx context.Context, env []string, cmd string, args ...string) ([]byte, error) { + c := exec.CommandContext(ctx, cmd, args...) + c.Env = env + + return c.CombinedOutput() +} + +func (l *launcher) submitUpgradeProposal() error { + var err error + + defer func() { + if err != nil { + l.t.Logf("submitUpgradeProposal finished with error: %s", err.Error()) + } + }() + + env := []string{ + fmt.Sprintf("HOME=%s", *homedir), + fmt.Sprintf("AKASH_HOME=%s", l.params.homeDir), + fmt.Sprintf("AKASH_KEYRING_BACKEND=test"), + fmt.Sprintf("AKASH_BROADCAST_MODE=block"), + fmt.Sprintf("AKASH_CHAIN_ID=localakash"), + fmt.Sprintf("AKASH_FROM=validator"), + fmt.Sprintf("AKASH_GAS=auto"), + fmt.Sprintf("AKASH_YES=true"), + } + + cmd := fmt.Sprintf(`%s status`, *genesisBinary) + + var statusResp nodeStatus + + var cmdRes []byte + + for { + cmdRes, err = executeCommand(l.ctx, env, "bash", "-c", cmd) + if err != nil { + l.t.Logf("node status: %s\n", string(cmdRes)) + return err + } + + err = json.Unmarshal(cmdRes, &statusResp) + if err != nil { + return err + } + + if !statusResp.SyncInfo.CatchingUp { + break + } + } + + tm := time.NewTimer(30 * time.Second) + select { + case <-l.ctx.Done(): + if !tm.Stop() { + <-tm.C + } + err = l.ctx.Err() + return err + case <-tm.C: + } + + cmd = fmt.Sprintf("%s query gov params --output=json", *genesisBinary) + l.t.Logf("executing cmd: %s\n", cmd) + cmdRes, err = executeCommand(l.ctx, env, "bash", "-c", cmd) + if err != nil { + l.t.Logf("executing cmd failed: %s\n", string(cmdRes)) + return err + } + + params := govParams{} + + err = json.Unmarshal(cmdRes, ¶ms) + if err != nil { + return err + } + + votePeriod, valid := sdk.NewIntFromString(params.VotingParams.VotingPeriod) + if !valid { + return fmt.Errorf("invalid vote period value (%s)", params.VotingParams.VotingPeriod) + } + + votePeriod = votePeriod.QuoRaw(1e9) + + cmdRes, err = executeCommand(l.ctx, env, "bash", "-c", cmd) + if err != nil { + l.t.Logf("executing cmd failed: %s\n", string(cmdRes)) + return err + } + + err = json.Unmarshal(cmdRes, &statusResp) + if err != nil { + return err + } + + upgradeHeight, err := strconv.ParseUint(statusResp.SyncInfo.LatestBlockHeight, 10, 64) + if err != nil { + return err + } + + upgradeHeight += (votePeriod.Uint64() / 6) + 10 + + l.t.Logf("voting period: %ss, curr height: %s, upgrade height: %d", + votePeriod, + statusResp.SyncInfo.LatestBlockHeight, + upgradeHeight) + + cmd = fmt.Sprintf(`%s tx gov submit-proposal software-upgrade %s --title=%[2]s --description="%[2]s" --upgrade-height=%d --deposit=%s`, + *genesisBinary, + l.params.upgradeName, + upgradeHeight, + params.DepositParams.MinDeposit[0].String(), + ) + + if l.upgradeInfo != "" { + cmd += fmt.Sprintf(` --upgrade-info='%s'`, l.upgradeInfo) + } + + l.t.Logf("executing cmd: %s\n", cmd) + cmdRes, err = executeCommand(l.ctx, env, "bash", "-c", cmd) + if err != nil { + l.t.Logf("executing cmd failed: %s\n", string(cmdRes)) + return err + } + + cmd = fmt.Sprintf(`%s query gov proposals --output=json`, *genesisBinary) + l.t.Logf("executing cmd: %s\n", cmd) + cmdRes, err = executeCommand(l.ctx, env, "bash", "-c", cmd) + if err != nil { + l.t.Logf("executing cmd failed: %s\n", string(cmdRes)) + return err + } + + var proposals proposalsResp + + err = json.Unmarshal(cmdRes, &proposals) + if err != nil { + return err + } + + var propID string + for i := len(proposals.Proposals) - 1; i >= 0; i-- { + if proposals.Proposals[i].Content.Title == l.params.upgradeName { + propID = proposals.Proposals[i].ProposalID + break + } + } + + if propID == "" { + return fmt.Errorf(`unable to find proposal with title "%s"`, l.params.upgradeName) + } + + cmd = fmt.Sprintf(`%s tx gov vote %s yes`, *genesisBinary, propID) + l.t.Logf("executing cmd: %s\n", cmd) + cmdRes, err = executeCommand(l.ctx, env, "bash", "-c", cmd) + if err != nil { + l.t.Logf("executing cmd failed: %s\n", string(cmdRes)) + return err + } + + return nil +} + +func (l *launcher) run() error { + lStdout, err := os.Create(fmt.Sprintf("%s/stdout.log", l.params.home)) + if err != nil { + return err + } + + defer func() { + _ = lStdout.Close() + }() + + lStderr, err := os.Create(fmt.Sprintf("%s/stderr.log", l.params.home)) + if err != nil { + return err + } + + defer func() { + _ = lStderr.Close() + }() + + rStdout, wStdout := io.Pipe() + defer func() { + _ = wStdout.Close() + }() + + cmd := exec.CommandContext(l.ctx, l.cosmovisor, "run", "start", fmt.Sprintf("--home=%s", l.params.homeDir)) + + cmd.Stdout = io.MultiWriter(lStdout, wStdout) + cmd.Stderr = io.MultiWriter(lStderr) + + cmd.Env = []string{ + fmt.Sprintf("HOME=%s", l.params.home), + fmt.Sprintf("DAEMON_NAME=akash"), + fmt.Sprintf("DAEMON_HOME=%s", l.params.homeDir), + fmt.Sprintf("DAEMON_RESTART_AFTER_UPGRADE=true"), + fmt.Sprintf("DAEMON_ALLOW_DOWNLOAD_BINARIES=true"), + fmt.Sprintf("UNSAFE_SKIP_BACKUP=true"), + fmt.Sprintf("AKASH_HOME=%s", l.params.homeDir), + fmt.Sprintf("AKASH_KEYRING_BACKEND=test"), + fmt.Sprintf("AKASH_FAST_SYNC=false"), + fmt.Sprintf("AKASH_P2P_PEX=false"), + fmt.Sprintf("AKASH_LOG_COLOR=false"), + fmt.Sprintf("AKASH_LOG_TIMESTAMP="), + fmt.Sprintf("AKASH_LOG_FORMAT=plain"), + fmt.Sprintf("AKASH_STATESYNC_ENABLE=false"), + fmt.Sprintf("AKASH_CHAIN_ID=%s", l.params.chainID), + fmt.Sprintf("AKASH_TX_INDEX_INDEXER=kv"), + } + + bus := pubsub.NewBus() + + err = cmd.Start() + if err != nil { + return err + } + + l.group.Go(func() error { + return l.scanner(rStdout, bus) + }) + + l.group.Go(func() error { + sub, err := bus.Subscribe() + if err != nil { + return err + } + + return l.watchTestCases(sub) + }) + + l.group.Go(func() error { + <-l.ctx.Done() + _ = rStdout.Close() + bus.Close() + return l.ctx.Err() + }) + + l.group.Go(func() error { + sub, err := bus.Subscribe() + if err != nil { + return err + } + + return l.blocksWatchdog(l.ctx, sub) + }) + + // state machine + l.group.Go(func() error { + return l.stateMachine(bus) + }) + + err = cmd.Wait() + l.t.Log("cosmovisor stopped") + l.cancel() + + l.t.Log("waiting for workers to finish") + _ = l.group.Wait() + + select { + case <-l.upgradeSuccessful: + err = nil + default: + l.t.Log("cosmovisor finished with error. check stderr") + } + + return err +} + +func (l *launcher) stateMachine(bus pubsub.Bus) error { + var err error + + var sub pubsub.Subscriber + + sub, err = bus.Subscribe() + if err != nil { + return err + } + + blocksCount := 0 + replayDone := false + stage := testStagePreUpgrade + + wdCtrl := func(ctx context.Context, ctrl watchdogCtrl) { + resp := make(chan struct{}, 1) + _ = bus.Publish(wdReq{ + event: ctrl, + resp: resp, + }) + + select { + case <-ctx.Done(): + case <-resp: + } + } + +loop: + for { + select { + case <-l.ctx.Done(): + err = l.ctx.Err() + break loop + case ev := <-sub.Events(): + switch evt := ev.(type) { + case event: + switch evt.id { + case nodeEventStart: + l.t.Logf("[%s]: node started", testStageMapStr[stage]) + if stage == testStageUpgrade { + stage = testStagePostUpgrade + blocksCount = 0 + replayDone = false + } + case nodeEventReplayBlocksStart: + l.t.Logf("[%s]: node started replaying blocks", testStageMapStr[stage]) + case nodeEventReplayBlocksDone: + l.t.Logf("[%s]: node done replaying blocks", testStageMapStr[stage]) + wdCtrl(l.ctx, watchdogCtrlStart) + replayDone = true + case nodeEventBlockIndexed: + // ignore index events until replay done + if !replayDone { + break + } + + wdCtrl(l.ctx, watchdogCtrlBlock) + + blocksCount++ + if blocksCount == 1 { + l.t.Logf("[%s]: node started producing blocks", testStageMapStr[stage]) + } + + if stage == testStagePreUpgrade && blocksCount == 1 { + l.group.Go(func() error { + return l.submitUpgradeProposal() + }) + } else if stage == testStagePostUpgrade && blocksCount == 10 { + l.t.Logf("[%s]: counted 10 blocks. signaling to finish the test", testStageMapStr[stage]) + l.upgradeSuccessful <- struct{}{} + l.cancel() + } + case nodeEventUpgradeDetected: + l.t.Logf("[%s]: node detected upgrade", testStageMapStr[stage]) + stage = testStageUpgrade + wdCtrl(l.ctx, watchdogCtrlPause) + } + } + } + } + + return err +} + +type moduleMigrationVersions struct { + from string + to string +} + +type moduleMigrationStatus struct { + status testModuleStatus + expected moduleMigrationVersions + actual moduleMigrationVersions +} + +func (v moduleMigrationVersions) compare(to moduleMigrationVersions) bool { + return (v.from == to.from) && (v.to == v.to) +} + +func (l *launcher) watchTestCases(subs pubsub.Subscriber) error { + added := make(map[string]testModuleStatus) + removed := make(map[string]testModuleStatus) + migrations := make(map[string]*moduleMigrationStatus) + + for _, name := range l.tConfig.Modules.Added { + added[name] = testModuleStatusNotChecked + } + + for _, name := range l.tConfig.Modules.Removed { + removed[name] = testModuleStatusNotChecked + } + + for name, vals := range l.tConfig.Migrations { + migrations[name] = &moduleMigrationStatus{ + status: testModuleStatusNotChecked, + expected: moduleMigrationVersions{ + from: vals.From, + to: vals.To, + }, + } + } + +loop: + for { + select { + case <-l.ctx.Done(): + break loop + case ev := <-subs.Events(): + switch evt := ev.(type) { + case event: + switch evt.id { + case nodeEventAddedModule: + ctx := evt.ctx.(eventCtxModule) + if _, exists := added[ctx.name]; !exists { + added[ctx.name] = testModuleStatusUnexpected + } else { + added[ctx.name] = testModuleStatusChecked + } + case nodeEventRemovedModule: + ctx := evt.ctx.(eventCtxModule) + if _, exists := removed[ctx.name]; !exists { + removed[ctx.name] = testModuleStatusUnexpected + } else { + removed[ctx.name] = testModuleStatusChecked + } + case nodeEventModuleMigration: + ctx := evt.ctx.(eventCtxModuleMigration) + if _, exists := migrations[ctx.name]; !exists { + migrations[ctx.name] = &moduleMigrationStatus{status: testModuleStatusUnexpected} + } else { + m := migrations[ctx.name] + + m.status = testModuleStatusChecked + m.actual.to = ctx.to + m.actual.from = ctx.from + } + } + } + } + } + + errs := make([]string, 0) + + for name, status := range added { + if status != testModuleStatusChecked { + merr := fmt.Sprintf("module to add (%s) was not checked. status %s", name, testModuleStatusMapStr[status]) + errs = append(errs, merr) + } + } + + for name, status := range removed { + if status != testModuleStatusChecked { + merr := fmt.Sprintf("module to remove (%s) was not checked. status %s", name, testModuleStatusMapStr[status]) + errs = append(errs, merr) + } + } + + for name, module := range migrations { + if module.status == testModuleStatusChecked { + if !module.expected.compare(module.actual) { + merr := fmt.Sprintf("migration for module (%s) finished with mismatched versions:\n"+ + "\texpected:\n"+ + "\t\tfrom: %s\n"+ + "\t\tto: %s\n"+ + "\tactual:\n"+ + "\t\tfrom: %s\n"+ + "\t\tto: %s", + name, + module.expected.from, module.expected.to, + module.actual.from, module.actual.to) + + errs = append(errs, merr) + } + } else { + merr := fmt.Sprintf("detected unexpected pmigration in module (%s)", name) + errs = append(errs, merr) + } + } + + l.testErrs = errs + + return nil +} + +func (l *launcher) blocksWatchdog(ctx context.Context, sub pubsub.Subscriber) error { + var err error + + defer func() { + if err != nil { + l.t.Logf("blocksWatchdog finished with error: %s", err.Error()) + } + }() + +loop: + for { + blocksTm := time.NewTicker(blockTimeWindow) + blocksTm.Stop() + + select { + case <-ctx.Done(): + break loop + case <-blocksTm.C: + err = fmt.Errorf("didn't receive block within specified time") + break loop + case evt := <-sub.Events(): + switch req := evt.(type) { + case wdReq: + switch req.event { + case watchdogCtrlStart: + fallthrough + case watchdogCtrlBlock: + blocksTm.Reset(blockTimeWindow) + case watchdogCtrlPause: + blocksTm.Stop() + case watchdogCtrlStop: + blocksTm.Stop() + break loop + } + + req.resp <- struct{}{} + } + } + } + + return err +} + +func (l *launcher) scanner(stdout io.Reader, p publisher) error { + scanner := bufio.NewScanner(stdout) + + serverStart := "INF starting node with ABCI Tendermint in-process" + replayBlocksStart := "INF ABCI Replay Blocks appHeight" + replayBlocksDone := "INF Replay: Done module=consensus" + executedBlock := "INF indexed block " + upgradeNeeded := fmt.Sprintf(`ERR UPGRADE "%s" NEEDED at height:`, l.params.upgradeName) + addingNewModule := "INF adding a new module: " + migratingModule := "INF migrating module " + + rNewModule, err := regexp.Compile(`^` + addingNewModule + `(\w+)$`) + if err != nil { + return err + } + + rModuleMigration, err := regexp.Compile(`^` + migratingModule + `(\w+) from version (\d+) to version (\d+)$`) + if err != nil { + return err + } + +scan: + for scanner.Scan() { + line := scanner.Text() + + evt := event{} + + if strings.Contains(line, upgradeNeeded) { + evt.id = nodeEventUpgradeDetected + } else if strings.Contains(line, serverStart) { + evt.id = nodeEventStart + } else if strings.Contains(line, replayBlocksStart) { + evt.id = nodeEventReplayBlocksStart + } else if strings.Contains(line, replayBlocksDone) { + evt.id = nodeEventReplayBlocksDone + } else if strings.Contains(line, executedBlock) { + evt.id = nodeEventBlockIndexed + } else if strings.Contains(line, addingNewModule) { + evt.id = nodeEventAddedModule + res := rNewModule.FindAllStringSubmatch(line, -1) + evt.ctx = eventCtxModule{ + name: res[0][1], + } + } else if strings.Contains(line, migratingModule) { + evt.id = nodeEventModuleMigration + res := rModuleMigration.FindAllStringSubmatch(line, -1) + evt.ctx = eventCtxModuleMigration{ + name: res[0][1], + from: res[0][2], + to: res[0][3], + } + } else { + continue scan + } + + if err = p.Publish(evt); err != nil { + return err + } + } + + return nil +} diff --git a/tests/upgrade/upgrades-v0.24.0.json b/tests/upgrade/upgrades-v0.24.0.json new file mode 100644 index 0000000000..360d1ce2c8 --- /dev/null +++ b/tests/upgrade/upgrades-v0.24.0.json @@ -0,0 +1,23 @@ +{ + "modules": { + "added": [ + "agov", + "astaking", + "feegrant" + ] + }, + "migrations": { + "deployment": { + "from": "2", + "to": "3" + }, + "market": { + "from": "2", + "to": "3" + }, + "transfer": { + "from": "1", + "to": "2" + } + } +} diff --git a/upgrades/upgrades_test.go b/upgrades/upgrades_test.go new file mode 100644 index 0000000000..1c4acb96f4 --- /dev/null +++ b/upgrades/upgrades_test.go @@ -0,0 +1,29 @@ +package upgrades + +import ( + "fmt" + "strings" + "testing" + + "github.com/stretchr/testify/require" + "golang.org/x/mod/semver" + + utypes "github.com/akash-network/node/upgrades/types" +) + +func TestUpgradesName(t *testing.T) { + upgrades := utypes.GetUpgradesList() + require.NotNil(t, upgrades) + + for name := range upgrades { + // NOTE this is the only exception to the upgrade name + // Rest MUST be compliant with SEMVER + if name == "akash_v0.15.0_cosmos_v0.44.x" { + continue + } + + require.True(t, strings.HasPrefix(name, "v"), "upgrade name must start with \"v\"") + + require.True(t, semver.IsValid(name), fmt.Sprintf("upgrade name \"%s\" must be valid Semver", name)) + } +}