feat: [ENG-792] Implement E2E Testing Framework (#107)

This commit is contained in:
Aleksandr Bezobchuk 2023-05-08 14:45:17 -04:00 committed by GitHub
parent e913bee3cd
commit cc74b9e027
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 3051 additions and 22 deletions

View File

@ -33,3 +33,34 @@ jobs:
if: env.GIT_DIFF
run: |
make test
test-e2e:
runs-on: ubuntu-latest
timeout-minutes: 25
steps:
- uses: actions/checkout@v3
- uses: technote-space/get-diff-action@v6.1.2
with:
PATTERNS: |
**/**.go
go.mod
go.sum
- uses: actions/setup-go@v4
if: env.GIT_DIFF
with:
go-version: "1.20"
cache: true
# In this step, this action saves a list of existing images, the cache is
# created without them in the post run. It also restores the cache if it
# exists.
- name: cache docker layer
uses: satackey/action-docker-layer-caching@v0.0.11
if: env.GIT_DIFF
# Ignore the failure of a step and avoid terminating the job.
continue-on-error: true
- name: Test E2E
if: env.GIT_DIFF
run: |
make test-e2e

1
.gitignore vendored
View File

@ -12,6 +12,7 @@ profile.out
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
build/
# Dependency directories (remove the comment below to include it)
# vendor/

View File

@ -1,8 +1,8 @@
#!/usr/bin/make -f
export VERSION := $(shell echo $(shell git describe --always --match "v*") | sed 's/^v//')
# export TM_VERSION := $(shell go list -m github.com/cometbft/cometbft | sed 's:.* ::')
export COMMIT := $(shell git log -1 --format='%H')
export COMETBFT_VERSION := $(shell go list -m github.com/cometbft/cometbft | sed 's:.* ::')
BIN_DIR ?= $(GOPATH)/bin
BUILD_DIR ?= $(CURDIR)/build
@ -10,14 +10,91 @@ PROJECT_NAME = $(shell git remote get-url origin | xargs basename -s .git)
HTTPS_GIT := https://github.com/skip-mev/pob.git
DOCKER := $(shell which docker)
###############################################################################
### Test App ###
###############################################################################
whitespace :=
whitespace += $(whitespace)
comma := ,
build_tags_comma_sep := $(subst $(whitespace),$(comma),$(build_tags))
ldflags = -X github.com/cosmos/cosmos-sdk/version.Name=testapp \
-X github.com/cosmos/cosmos-sdk/version.AppName=testappd \
-X github.com/cosmos/cosmos-sdk/version.Version=$(VERSION) \
-X github.com/cosmos/cosmos-sdk/version.Commit=$(COMMIT) \
-X "github.com/cosmos/cosmos-sdk/version.BuildTags=$(build_tags_comma_sep)" \
-X github.com/cometbft/cometbft/version.TMCoreSemVer=$(COMETBFT_VERSION)
# DB backend selection
ifeq (cleveldb,$(findstring cleveldb,$(COSMOS_BUILD_OPTIONS)))
build_tags += gcc
endif
ifeq (badgerdb,$(findstring badgerdb,$(COSMOS_BUILD_OPTIONS)))
build_tags += badgerdb
endif
# handle rocksdb
ifeq (rocksdb,$(findstring rocksdb,$(COSMOS_BUILD_OPTIONS)))
CGO_ENABLED=1
build_tags += rocksdb
endif
# handle boltdb
ifeq (boltdb,$(findstring boltdb,$(COSMOS_BUILD_OPTIONS)))
build_tags += boltdb
endif
ifeq (,$(findstring nostrip,$(COSMOS_BUILD_OPTIONS)))
ldflags += -w -s
endif
ldflags += $(LDFLAGS)
ldflags := $(strip $(ldflags))
build_tags += $(BUILD_TAGS)
build_tags := $(strip $(build_tags))
BUILD_FLAGS := -tags "$(build_tags)" -ldflags '$(ldflags)'
# check for nostrip option
ifeq (,$(findstring nostrip,$(COSMOS_BUILD_OPTIONS)))
BUILD_FLAGS += -trimpath
endif
BUILD_TARGETS := build-test-app
build-test-app: BUILD_ARGS=-o $(BUILD_DIR)/
$(BUILD_TARGETS): $(BUILD_DIR)/
cd $(CURDIR)/tests/app && go build -mod=readonly $(BUILD_FLAGS) $(BUILD_ARGS) ./...
$(BUILD_DIR)/:
mkdir -p $(BUILD_DIR)/
.PHONY: build-test-app
###############################################################################
## Docker ##
###############################################################################
docker-build:
@echo "Building E2E Docker image..."
@DOCKER_BUILDKIT=1 docker build -t skip-mev/pob-e2e -f contrib/images/pob.e2e.Dockerfile .
###############################################################################
### Tests ###
###############################################################################
TEST_E2E_TAGS = e2e
TEST_E2E_DEPS = docker-build
test-e2e: $(TEST_E2E_DEPS)
@echo "Running E2E tests..."
@go test ./tests/e2e/... -mod=readonly -timeout 30m -race -v -tags='$(TEST_E2E_TAGS)'
test:
@go test -v ./...
.PHONY: test
.PHONY: test test-e2e
###############################################################################
### Protobuf ###

View File

@ -0,0 +1,17 @@
FROM golang:1.20-bullseye AS builder
WORKDIR /src/pob
COPY go.mod go.sum ./
RUN go mod download
WORKDIR /src/pob
COPY . .
RUN make build-test-app
## Prepare the final clear binary
FROM ubuntu:rolling
EXPOSE 26656 26657 1317 9090 7171
ENTRYPOINT ["testappd", "start"]
COPY --from=builder /src/pob/build/* /usr/local/bin/
RUN apt-get update && apt-get install ca-certificates -y

81
go.mod
View File

@ -3,21 +3,25 @@ module github.com/skip-mev/pob
go 1.20
require (
cosmossdk.io/api v0.4.1
cosmossdk.io/core v0.6.1
cosmossdk.io/api v0.3.1
cosmossdk.io/core v0.5.1
cosmossdk.io/depinject v1.0.0-alpha.3
cosmossdk.io/errors v1.0.0-beta.7
cosmossdk.io/math v1.0.0
github.com/cometbft/cometbft v0.37.1
github.com/cosmos/cosmos-proto v1.0.0-beta.3
github.com/cometbft/cometbft-db v0.7.0
github.com/cosmos/cosmos-proto v1.0.0-beta.2
github.com/cosmos/cosmos-sdk v0.47.2
github.com/cosmos/go-bip39 v1.0.0
github.com/cosmos/gogoproto v1.4.9
github.com/golang/mock v1.6.0
github.com/golang/protobuf v1.5.3
github.com/gorilla/mux v1.8.0
github.com/grpc-ecosystem/grpc-gateway v1.16.0
github.com/huandu/skiplist v1.2.0
github.com/spf13/cobra v1.7.0
github.com/ory/dockertest/v3 v3.10.0
github.com/spf13/cobra v1.6.1
github.com/spf13/viper v1.14.0
github.com/stretchr/testify v1.8.2
google.golang.org/genproto v0.0.0-20230320184635-7606e756e683
google.golang.org/grpc v1.55.0
@ -25,59 +29,106 @@ require (
)
require (
cloud.google.com/go v0.110.0 // indirect
cloud.google.com/go/compute v1.18.0 // indirect
cloud.google.com/go/compute/metadata v0.2.3 // indirect
cloud.google.com/go/iam v0.12.0 // indirect
cloud.google.com/go/storage v1.29.0 // indirect
cosmossdk.io/tools/rosetta v0.2.1 // indirect
filippo.io/edwards25519 v1.0.0 // 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 v0.0.0-20200405005733-88cbf1b4c40d // indirect
github.com/Microsoft/go-winio v0.6.0 // indirect
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect
github.com/armon/go-metrics v0.4.1 // indirect
github.com/aws/aws-sdk-go v1.44.203 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect
github.com/bgentry/speakeasy v0.1.1-0.20220910012023-760eaf8b6816 // indirect
github.com/btcsuite/btcd/btcec/v2 v2.3.2 // indirect
github.com/cenkalti/backoff/v4 v4.1.3 // indirect
github.com/cespare/xxhash v1.1.0 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/cometbft/cometbft-db v0.7.0 // indirect
github.com/chzyer/readline v1.5.1 // indirect
github.com/cockroachdb/apd/v2 v2.0.2 // indirect
github.com/coinbase/rosetta-sdk-go/types v1.0.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/go-bip39 v1.0.0 // indirect
github.com/cosmos/gogogateway v1.2.0 // indirect
github.com/cosmos/iavl v0.20.0 // indirect
github.com/cosmos/ledger-cosmos-go v0.12.1 // indirect
github.com/cosmos/rosetta-sdk-go v0.10.0 // indirect
github.com/creachadair/taskgroup v0.3.2 // indirect
github.com/danieljoos/wincred v1.1.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 // indirect
github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f // indirect
github.com/dgraph-io/badger/v2 v2.2007.4 // indirect
github.com/dgraph-io/ristretto v0.1.1 // indirect
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 // indirect
github.com/docker/cli v20.10.17+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 // indirect
github.com/dvsekhvalnov/jose2go v1.5.0 // indirect
github.com/felixge/httpsnoop v1.0.2 // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/go-kit/kit v0.12.0 // indirect
github.com/go-kit/log v0.2.1 // indirect
github.com/go-logfmt/logfmt v0.5.1 // indirect
github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect
github.com/gogo/googleapis v1.4.1 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/glog v1.1.0 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
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/orderedcode v0.0.1 // 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.7.0 // indirect
github.com/gorilla/handlers v1.5.1 // indirect
github.com/gorilla/websocket v1.5.0 // indirect
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect
github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c // indirect
github.com/gtank/merlin v0.1.1 // indirect
github.com/gtank/ristretto255 v0.1.2 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-getter v1.7.1 // indirect
github.com/hashicorp/go-immutable-radix v1.3.1 // indirect
github.com/hashicorp/go-safetemp v1.0.0 // indirect
github.com/hashicorp/go-version v1.6.0 // indirect
github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/hdevalence/ed25519consensus v0.1.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
github.com/jmhodges/levigo v1.0.0 // indirect
github.com/klauspost/compress v1.16.3 // indirect
github.com/lib/pq v1.10.7 // indirect
github.com/libp2p/go-buffer-pool v0.1.0 // indirect
github.com/magiconair/properties v1.8.6 // indirect
github.com/mattn/go-isatty v0.0.16 // indirect
github.com/manifoldco/promptui v0.9.0 // indirect
github.com/mattn/go-isatty v0.0.18 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
github.com/mimoo/StrobeGo v0.0.0-20210601165009-122bf33a46e0 // indirect
github.com/minio/highwayhash v1.0.2 // indirect
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 v1.9.5 // indirect
github.com/pelletier/go-toml/v2 v2.0.5 // indirect
github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 // indirect
@ -87,30 +138,44 @@ require (
github.com/prometheus/client_model v0.3.0 // indirect
github.com/prometheus/common v0.42.0 // indirect
github.com/prometheus/procfs v0.9.0 // indirect
github.com/rakyll/statik v0.1.7 // indirect
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect
github.com/rs/cors v1.8.2 // 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.2 // indirect
github.com/spf13/cast v1.5.0 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/spf13/viper v1.14.0 // indirect
github.com/subosito/gotenv v1.4.1 // 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.6.0 // indirect
github.com/ulikunitz/xz v0.5.11 // 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
github.com/zondax/ledger-go v0.14.0 // indirect
go.etcd.io/bbolt v1.3.7 // indirect
go.opencensus.io v0.24.0 // indirect
golang.org/x/crypto v0.7.0 // indirect
golang.org/x/exp v0.0.0-20230321023759-10a507213a29 // indirect
golang.org/x/mod v0.9.0 // indirect
golang.org/x/net v0.9.0 // indirect
golang.org/x/oauth2 v0.6.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
golang.org/x/tools v0.7.0 // indirect
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
google.golang.org/api v0.110.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
nhooyr.io/websocket v1.8.6 // indirect
pgregory.net/rapid v0.5.5 // indirect
sigs.k8s.io/yaml v1.3.0 // indirect
)

813
go.sum

File diff suppressed because it is too large Load Diff

384
tests/app/app.go Normal file
View File

@ -0,0 +1,384 @@
package app
//nolint:revive
import (
_ "embed"
"io"
"os"
"path/filepath"
"cosmossdk.io/depinject"
dbm "github.com/cometbft/cometbft-db"
"github.com/cometbft/cometbft/libs/log"
"github.com/cosmos/cosmos-sdk/baseapp"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/codec"
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
"github.com/cosmos/cosmos-sdk/runtime"
"github.com/cosmos/cosmos-sdk/server"
"github.com/cosmos/cosmos-sdk/server/api"
"github.com/cosmos/cosmos-sdk/server/config"
servertypes "github.com/cosmos/cosmos-sdk/server/types"
"github.com/cosmos/cosmos-sdk/store/streaming"
storetypes "github.com/cosmos/cosmos-sdk/store/types"
"github.com/cosmos/cosmos-sdk/testutil/testdata_pulsar"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/module"
"github.com/cosmos/cosmos-sdk/x/auth"
authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper"
_ "github.com/cosmos/cosmos-sdk/x/auth/tx/config" // import for side-effects
"github.com/cosmos/cosmos-sdk/x/auth/vesting"
authzkeeper "github.com/cosmos/cosmos-sdk/x/authz/keeper"
authzmodule "github.com/cosmos/cosmos-sdk/x/authz/module"
"github.com/cosmos/cosmos-sdk/x/bank"
bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper"
"github.com/cosmos/cosmos-sdk/x/capability"
capabilitykeeper "github.com/cosmos/cosmos-sdk/x/capability/keeper"
consensus "github.com/cosmos/cosmos-sdk/x/consensus"
consensuskeeper "github.com/cosmos/cosmos-sdk/x/consensus/keeper"
"github.com/cosmos/cosmos-sdk/x/crisis"
crisiskeeper "github.com/cosmos/cosmos-sdk/x/crisis/keeper"
distr "github.com/cosmos/cosmos-sdk/x/distribution"
distrkeeper "github.com/cosmos/cosmos-sdk/x/distribution/keeper"
"github.com/cosmos/cosmos-sdk/x/evidence"
evidencekeeper "github.com/cosmos/cosmos-sdk/x/evidence/keeper"
feegrantkeeper "github.com/cosmos/cosmos-sdk/x/feegrant/keeper"
feegrantmodule "github.com/cosmos/cosmos-sdk/x/feegrant/module"
"github.com/cosmos/cosmos-sdk/x/genutil"
genutiltypes "github.com/cosmos/cosmos-sdk/x/genutil/types"
"github.com/cosmos/cosmos-sdk/x/gov"
govclient "github.com/cosmos/cosmos-sdk/x/gov/client"
govkeeper "github.com/cosmos/cosmos-sdk/x/gov/keeper"
groupkeeper "github.com/cosmos/cosmos-sdk/x/group/keeper"
groupmodule "github.com/cosmos/cosmos-sdk/x/group/module"
"github.com/cosmos/cosmos-sdk/x/mint"
mintkeeper "github.com/cosmos/cosmos-sdk/x/mint/keeper"
nftmodule "github.com/cosmos/cosmos-sdk/x/nft/module"
"github.com/cosmos/cosmos-sdk/x/params"
paramsclient "github.com/cosmos/cosmos-sdk/x/params/client"
paramskeeper "github.com/cosmos/cosmos-sdk/x/params/keeper"
paramstypes "github.com/cosmos/cosmos-sdk/x/params/types"
"github.com/cosmos/cosmos-sdk/x/slashing"
slashingkeeper "github.com/cosmos/cosmos-sdk/x/slashing/keeper"
"github.com/cosmos/cosmos-sdk/x/staking"
stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper"
"github.com/cosmos/cosmos-sdk/x/upgrade"
upgradeclient "github.com/cosmos/cosmos-sdk/x/upgrade/client"
upgradekeeper "github.com/cosmos/cosmos-sdk/x/upgrade/keeper"
)
var (
BondDenom = sdk.DefaultBondDenom
// DefaultNodeHome default home directories for the application daemon
DefaultNodeHome string
// ModuleBasics defines the module BasicManager is in charge of setting up basic,
// non-dependant module elements, such as codec registration
// and genesis verification.
ModuleBasics = module.NewBasicManager(
auth.AppModuleBasic{},
genutil.NewAppModuleBasic(genutiltypes.DefaultMessageValidator),
bank.AppModuleBasic{},
capability.AppModuleBasic{},
staking.AppModuleBasic{},
mint.AppModuleBasic{},
distr.AppModuleBasic{},
gov.NewAppModuleBasic(
[]govclient.ProposalHandler{
paramsclient.ProposalHandler,
upgradeclient.LegacyProposalHandler,
upgradeclient.LegacyCancelProposalHandler,
},
),
params.AppModuleBasic{},
crisis.AppModuleBasic{},
slashing.AppModuleBasic{},
feegrantmodule.AppModuleBasic{},
upgrade.AppModuleBasic{},
evidence.AppModuleBasic{},
authzmodule.AppModuleBasic{},
groupmodule.AppModuleBasic{},
vesting.AppModuleBasic{},
nftmodule.AppModuleBasic{},
consensus.AppModuleBasic{},
)
)
var (
_ runtime.AppI = (*TestApp)(nil)
_ servertypes.Application = (*TestApp)(nil)
)
type TestApp struct {
*runtime.App
legacyAmino *codec.LegacyAmino
appCodec codec.Codec
txConfig client.TxConfig
interfaceRegistry codectypes.InterfaceRegistry
// keepers
AccountKeeper authkeeper.AccountKeeper
BankKeeper bankkeeper.Keeper
CapabilityKeeper *capabilitykeeper.Keeper
StakingKeeper *stakingkeeper.Keeper
SlashingKeeper slashingkeeper.Keeper
MintKeeper mintkeeper.Keeper
DistrKeeper distrkeeper.Keeper
GovKeeper *govkeeper.Keeper
CrisisKeeper *crisiskeeper.Keeper
UpgradeKeeper *upgradekeeper.Keeper
ParamsKeeper paramskeeper.Keeper
AuthzKeeper authzkeeper.Keeper
EvidenceKeeper evidencekeeper.Keeper
FeeGrantKeeper feegrantkeeper.Keeper
GroupKeeper groupkeeper.Keeper
ConsensusParamsKeeper consensuskeeper.Keeper
}
func init() {
userHomeDir, err := os.UserHomeDir()
if err != nil {
panic(err)
}
DefaultNodeHome = filepath.Join(userHomeDir, ".testapp")
}
func New(
logger log.Logger,
db dbm.DB,
traceStore io.Writer,
loadLatest bool,
appOpts servertypes.AppOptions,
baseAppOptions ...func(*baseapp.BaseApp),
) *TestApp {
var (
app = &TestApp{}
appBuilder *runtime.AppBuilder
// merge the AppConfig and other configuration in one config
appConfig = depinject.Configs(
AppConfig,
depinject.Supply(
// supply the application options
appOpts,
// ADVANCED CONFIGURATION
//
// AUTH
//
// For providing a custom function required in auth to generate custom account types
// add it below. By default the auth module uses simulation.RandomGenesisAccounts.
//
// authtypes.RandomGenesisAccountsFn(simulation.RandomGenesisAccounts),
// For providing a custom a base account type add it below.
// By default the auth module uses authtypes.ProtoBaseAccount().
//
// func() authtypes.AccountI { return authtypes.ProtoBaseAccount() },
//
// MINT
//
// For providing a custom inflation function for x/mint add here your
// custom function that implements the minttypes.InflationCalculationFn
// interface.
),
)
)
if err := depinject.Inject(appConfig,
&appBuilder,
&app.appCodec,
&app.legacyAmino,
&app.txConfig,
&app.interfaceRegistry,
&app.AccountKeeper,
&app.BankKeeper,
&app.CapabilityKeeper,
&app.StakingKeeper,
&app.SlashingKeeper,
&app.MintKeeper,
&app.DistrKeeper,
&app.GovKeeper,
&app.CrisisKeeper,
&app.UpgradeKeeper,
&app.ParamsKeeper,
&app.AuthzKeeper,
&app.EvidenceKeeper,
&app.FeeGrantKeeper,
&app.GroupKeeper,
&app.ConsensusParamsKeeper,
); err != nil {
panic(err)
}
// Below we could construct and set an application specific mempool and
// ABCI 1.0 PrepareProposal and ProcessProposal handlers. These defaults are
// already set in the SDK's BaseApp, this shows an example of how to override
// them.
//
// Example:
//
// app.App = appBuilder.Build(...)
// nonceMempool := mempool.NewSenderNonceMempool()
// abciPropHandler := NewDefaultProposalHandler(nonceMempool, app.App.BaseApp)
//
// app.App.BaseApp.SetMempool(nonceMempool)
// app.App.BaseApp.SetPrepareProposal(abciPropHandler.PrepareProposalHandler())
// app.App.BaseApp.SetProcessProposal(abciPropHandler.ProcessProposalHandler())
//
// Alternatively, you can construct BaseApp options, append those to
// baseAppOptions and pass them to the appBuilder.
//
// Example:
//
// prepareOpt = func(app *baseapp.BaseApp) {
// abciPropHandler := baseapp.NewDefaultProposalHandler(nonceMempool, app)
// app.SetPrepareProposal(abciPropHandler.PrepareProposalHandler())
// }
// baseAppOptions = append(baseAppOptions, prepareOpt)
app.App = appBuilder.Build(logger, db, traceStore, baseAppOptions...)
// load state streaming if enabled
if _, _, err := streaming.LoadStreamingServices(app.App.BaseApp, appOpts, app.appCodec, logger, app.kvStoreKeys()); err != nil {
logger.Error("failed to load state streaming", "err", err)
os.Exit(1)
}
/**** Module Options ****/
app.ModuleManager.RegisterInvariants(app.CrisisKeeper)
// RegisterUpgradeHandlers is used for registering any on-chain upgrades.
// app.RegisterUpgradeHandlers()
// add test gRPC service for testing gRPC queries in isolation
testdata_pulsar.RegisterQueryServer(app.GRPCQueryRouter(), testdata_pulsar.QueryImpl{})
// A custom InitChainer can be set if extra pre-init-genesis logic is required.
// By default, when using app wiring enabled module, this is not required.
// For instance, the upgrade module will set automatically the module version map in its init genesis thanks to app wiring.
// However, when registering a module manually (i.e. that does not support app wiring), the module version map
// must be set manually as follow. The upgrade module will de-duplicate the module version map.
//
// app.SetInitChainer(func(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain {
// app.UpgradeKeeper.SetModuleVersionMap(ctx, app.ModuleManager.GetVersionMap())
// return app.App.InitChainer(ctx, req)
// })
if err := app.Load(loadLatest); err != nil {
panic(err)
}
return app
}
// Name returns the name of the App
func (app *TestApp) Name() string { return app.BaseApp.Name() }
// LegacyAmino returns SimApp's amino codec.
//
// NOTE: This is solely to be used for testing purposes as it may be desirable
// for modules to register their own custom testing types.
func (app *TestApp) LegacyAmino() *codec.LegacyAmino {
return app.legacyAmino
}
// AppCodec returns SimApp's app codec.
//
// NOTE: This is solely to be used for testing purposes as it may be desirable
// for modules to register their own custom testing types.
func (app *TestApp) AppCodec() codec.Codec {
return app.appCodec
}
// InterfaceRegistry returns SimApp's InterfaceRegistry
func (app *TestApp) InterfaceRegistry() codectypes.InterfaceRegistry {
return app.interfaceRegistry
}
// TxConfig returns SimApp's TxConfig
func (app *TestApp) TxConfig() client.TxConfig {
return app.txConfig
}
// GetKey returns the KVStoreKey for the provided store key.
//
// NOTE: This is solely to be used for testing purposes.
func (app *TestApp) GetKey(storeKey string) *storetypes.KVStoreKey {
sk := app.UnsafeFindStoreKey(storeKey)
kvStoreKey, ok := sk.(*storetypes.KVStoreKey)
if !ok {
return nil
}
return kvStoreKey
}
func (app *TestApp) kvStoreKeys() map[string]*storetypes.KVStoreKey {
keys := make(map[string]*storetypes.KVStoreKey)
for _, k := range app.GetStoreKeys() {
if kv, ok := k.(*storetypes.KVStoreKey); ok {
keys[kv.Name()] = kv
}
}
return keys
}
// GetSubspace returns a param subspace for a given module name.
//
// NOTE: This is solely to be used for testing purposes.
func (app *TestApp) GetSubspace(moduleName string) paramstypes.Subspace {
subspace, _ := app.ParamsKeeper.GetSubspace(moduleName)
return subspace
}
// SimulationManager implements the SimulationApp interface
func (app *TestApp) SimulationManager() *module.SimulationManager {
return nil
}
// RegisterAPIRoutes registers all application module routes with the provided
// API server.
func (app *TestApp) RegisterAPIRoutes(apiSvr *api.Server, apiConfig config.APIConfig) {
app.App.RegisterAPIRoutes(apiSvr, apiConfig)
// register swagger API in app.go so that other applications can override easily
if err := server.RegisterSwaggerAPI(apiSvr.ClientCtx, apiSvr.Router, apiConfig.Swagger); err != nil {
panic(err)
}
}
// GetMaccPerms returns a copy of the module account permissions
//
// NOTE: This is solely to be used for testing purposes.
func GetMaccPerms() map[string][]string {
dup := make(map[string][]string)
for _, perms := range moduleAccPerms {
dup[perms.Account] = perms.Permissions
}
return dup
}
// BlockedAddresses returns all the app's blocked account addresses.
func BlockedAddresses() map[string]bool {
result := make(map[string]bool)
if len(blockAccAddrs) > 0 {
for _, addr := range blockAccAddrs {
result[addr] = true
}
} else {
for addr := range GetMaccPerms() {
result[addr] = true
}
}
return result
}

243
tests/app/config.go Normal file
View File

@ -0,0 +1,243 @@
package app
import (
"time"
runtimev1alpha1 "cosmossdk.io/api/cosmos/app/runtime/v1alpha1"
appv1alpha1 "cosmossdk.io/api/cosmos/app/v1alpha1"
authmodulev1 "cosmossdk.io/api/cosmos/auth/module/v1"
authzmodulev1 "cosmossdk.io/api/cosmos/authz/module/v1"
bankmodulev1 "cosmossdk.io/api/cosmos/bank/module/v1"
capabilitymodulev1 "cosmossdk.io/api/cosmos/capability/module/v1"
consensusmodulev1 "cosmossdk.io/api/cosmos/consensus/module/v1"
crisismodulev1 "cosmossdk.io/api/cosmos/crisis/module/v1"
distrmodulev1 "cosmossdk.io/api/cosmos/distribution/module/v1"
evidencemodulev1 "cosmossdk.io/api/cosmos/evidence/module/v1"
feegrantmodulev1 "cosmossdk.io/api/cosmos/feegrant/module/v1"
genutilmodulev1 "cosmossdk.io/api/cosmos/genutil/module/v1"
govmodulev1 "cosmossdk.io/api/cosmos/gov/module/v1"
groupmodulev1 "cosmossdk.io/api/cosmos/group/module/v1"
mintmodulev1 "cosmossdk.io/api/cosmos/mint/module/v1"
paramsmodulev1 "cosmossdk.io/api/cosmos/params/module/v1"
slashingmodulev1 "cosmossdk.io/api/cosmos/slashing/module/v1"
stakingmodulev1 "cosmossdk.io/api/cosmos/staking/module/v1"
txconfigv1 "cosmossdk.io/api/cosmos/tx/config/v1"
upgrademodulev1 "cosmossdk.io/api/cosmos/upgrade/module/v1"
vestingmodulev1 "cosmossdk.io/api/cosmos/vesting/module/v1"
"cosmossdk.io/core/appconfig"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
vestingtypes "github.com/cosmos/cosmos-sdk/x/auth/vesting/types"
"github.com/cosmos/cosmos-sdk/x/authz"
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
capabilitytypes "github.com/cosmos/cosmos-sdk/x/capability/types"
consensustypes "github.com/cosmos/cosmos-sdk/x/consensus/types"
crisistypes "github.com/cosmos/cosmos-sdk/x/crisis/types"
distrtypes "github.com/cosmos/cosmos-sdk/x/distribution/types"
evidencetypes "github.com/cosmos/cosmos-sdk/x/evidence/types"
"github.com/cosmos/cosmos-sdk/x/feegrant"
genutiltypes "github.com/cosmos/cosmos-sdk/x/genutil/types"
govtypes "github.com/cosmos/cosmos-sdk/x/gov/types"
"github.com/cosmos/cosmos-sdk/x/group"
minttypes "github.com/cosmos/cosmos-sdk/x/mint/types"
paramstypes "github.com/cosmos/cosmos-sdk/x/params/types"
slashingtypes "github.com/cosmos/cosmos-sdk/x/slashing/types"
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types"
"google.golang.org/protobuf/types/known/durationpb"
)
var (
// NOTE: The genutils module must occur after staking so that pools are
// properly initialized with tokens from genesis accounts.
// NOTE: The genutils module must also occur after auth so that it can access the params from auth.
// NOTE: Capability module must occur first so that it can initialize any capabilities
// so that other modules that want to create or claim capabilities afterwards in InitChain
// can do so safely.
genesisModuleOrder = []string{
capabilitytypes.ModuleName, authtypes.ModuleName, banktypes.ModuleName,
distrtypes.ModuleName, stakingtypes.ModuleName, slashingtypes.ModuleName, govtypes.ModuleName,
minttypes.ModuleName, crisistypes.ModuleName, genutiltypes.ModuleName, evidencetypes.ModuleName, authz.ModuleName,
feegrant.ModuleName, group.ModuleName, paramstypes.ModuleName, upgradetypes.ModuleName,
vestingtypes.ModuleName, consensustypes.ModuleName,
}
// module account permissions
moduleAccPerms = []*authmodulev1.ModuleAccountPermission{
{Account: authtypes.FeeCollectorName},
{Account: distrtypes.ModuleName},
{Account: minttypes.ModuleName, Permissions: []string{authtypes.Minter}},
{Account: stakingtypes.BondedPoolName, Permissions: []string{authtypes.Burner, stakingtypes.ModuleName}},
{Account: stakingtypes.NotBondedPoolName, Permissions: []string{authtypes.Burner, stakingtypes.ModuleName}},
{Account: govtypes.ModuleName, Permissions: []string{authtypes.Burner}},
}
// blocked account addresses
blockAccAddrs = []string{
authtypes.FeeCollectorName,
distrtypes.ModuleName,
minttypes.ModuleName,
stakingtypes.BondedPoolName,
stakingtypes.NotBondedPoolName,
// We allow the following module accounts to receive funds:
// govtypes.ModuleName
}
// application configuration (used by depinject)
AppConfig = appconfig.Compose(&appv1alpha1.Config{
Modules: []*appv1alpha1.ModuleConfig{
{
Name: "runtime",
Config: appconfig.WrapAny(&runtimev1alpha1.Module{
AppName: "TestApp",
// During begin block slashing happens after distr.BeginBlocker so that
// there is nothing left over in the validator fee pool, so as to keep the
// CanWithdrawInvariant invariant.
// NOTE: staking module is required if HistoricalEntries param > 0
// NOTE: capability module's beginblocker must come before any modules using capabilities (e.g. IBC)
BeginBlockers: []string{
upgradetypes.ModuleName,
capabilitytypes.ModuleName,
minttypes.ModuleName,
distrtypes.ModuleName,
slashingtypes.ModuleName,
evidencetypes.ModuleName,
stakingtypes.ModuleName,
authtypes.ModuleName,
banktypes.ModuleName,
govtypes.ModuleName,
crisistypes.ModuleName,
genutiltypes.ModuleName,
authz.ModuleName,
feegrant.ModuleName,
group.ModuleName,
paramstypes.ModuleName,
vestingtypes.ModuleName,
consensustypes.ModuleName,
},
EndBlockers: []string{
crisistypes.ModuleName,
govtypes.ModuleName,
stakingtypes.ModuleName,
capabilitytypes.ModuleName,
authtypes.ModuleName,
banktypes.ModuleName,
distrtypes.ModuleName,
slashingtypes.ModuleName,
minttypes.ModuleName,
genutiltypes.ModuleName,
evidencetypes.ModuleName,
authz.ModuleName,
feegrant.ModuleName,
group.ModuleName,
paramstypes.ModuleName,
consensustypes.ModuleName,
upgradetypes.ModuleName,
vestingtypes.ModuleName,
},
OverrideStoreKeys: []*runtimev1alpha1.StoreKeyConfig{
{
ModuleName: authtypes.ModuleName,
KvStoreKey: "acc",
},
},
InitGenesis: genesisModuleOrder,
// When ExportGenesis is not specified, the export genesis module order
// is equal to the init genesis order
// ExportGenesis: genesisModuleOrder,
// Uncomment if you want to set a custom migration order here.
// OrderMigrations: nil,
}),
},
{
Name: authtypes.ModuleName,
Config: appconfig.WrapAny(&authmodulev1.Module{
Bech32Prefix: "cosmos",
ModuleAccountPermissions: moduleAccPerms,
// By default modules authority is the governance module. This is configurable with the following:
// Authority: "group", // A custom module authority can be set using a module name
// Authority: "cosmos1cwwv22j5ca08ggdv9c2uky355k908694z577tv", // or a specific address
}),
},
{
Name: vestingtypes.ModuleName,
Config: appconfig.WrapAny(&vestingmodulev1.Module{}),
},
{
Name: banktypes.ModuleName,
Config: appconfig.WrapAny(&bankmodulev1.Module{
BlockedModuleAccountsOverride: blockAccAddrs,
}),
},
{
Name: stakingtypes.ModuleName,
Config: appconfig.WrapAny(&stakingmodulev1.Module{}),
},
{
Name: slashingtypes.ModuleName,
Config: appconfig.WrapAny(&slashingmodulev1.Module{}),
},
{
Name: paramstypes.ModuleName,
Config: appconfig.WrapAny(&paramsmodulev1.Module{}),
},
{
Name: "tx",
Config: appconfig.WrapAny(&txconfigv1.Config{}),
},
{
Name: genutiltypes.ModuleName,
Config: appconfig.WrapAny(&genutilmodulev1.Module{}),
},
{
Name: authz.ModuleName,
Config: appconfig.WrapAny(&authzmodulev1.Module{}),
},
{
Name: upgradetypes.ModuleName,
Config: appconfig.WrapAny(&upgrademodulev1.Module{}),
},
{
Name: distrtypes.ModuleName,
Config: appconfig.WrapAny(&distrmodulev1.Module{}),
},
{
Name: capabilitytypes.ModuleName,
Config: appconfig.WrapAny(&capabilitymodulev1.Module{
SealKeeper: true,
}),
},
{
Name: evidencetypes.ModuleName,
Config: appconfig.WrapAny(&evidencemodulev1.Module{}),
},
{
Name: minttypes.ModuleName,
Config: appconfig.WrapAny(&mintmodulev1.Module{}),
},
{
Name: group.ModuleName,
Config: appconfig.WrapAny(&groupmodulev1.Module{
MaxExecutionPeriod: durationpb.New(time.Second * 1209600),
MaxMetadataLen: 255,
}),
},
{
Name: feegrant.ModuleName,
Config: appconfig.WrapAny(&feegrantmodulev1.Module{}),
},
{
Name: govtypes.ModuleName,
Config: appconfig.WrapAny(&govmodulev1.Module{}),
},
{
Name: crisistypes.ModuleName,
Config: appconfig.WrapAny(&crisismodulev1.Module{}),
},
{
Name: consensustypes.ModuleName,
Config: appconfig.WrapAny(&consensusmodulev1.Module{}),
},
},
})
)

201
tests/app/export.go Normal file
View File

@ -0,0 +1,201 @@
package app
import (
"encoding/json"
"fmt"
"log"
tmproto "github.com/cometbft/cometbft/proto/tendermint/types"
servertypes "github.com/cosmos/cosmos-sdk/server/types"
sdk "github.com/cosmos/cosmos-sdk/types"
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"
)
// ExportAppStateAndValidators exports the state of the application for a genesis
// file.
func (app *TestApp) ExportAppStateAndValidators(forZeroHeight bool, jailAllowedAddrs []string, modulesToExport []string) (servertypes.ExportedApp, error) {
// as if they could withdraw from the start of the next block
ctx := app.NewContext(true, tmproto.Header{Height: app.LastBlockHeight()})
// We export at last height + 1, because that's the height at which
// Tendermint will start InitChain.
height := app.LastBlockHeight() + 1
if forZeroHeight {
height = 0
app.prepForZeroHeightGenesis(ctx, jailAllowedAddrs)
}
genState := app.ModuleManager.ExportGenesisForModules(ctx, app.appCodec, modulesToExport)
appState, err := json.MarshalIndent(genState, "", " ")
if err != nil {
return servertypes.ExportedApp{}, err
}
validators, err := staking.WriteValidators(ctx, app.StakingKeeper)
return servertypes.ExportedApp{
AppState: appState,
Validators: validators,
Height: height,
ConsensusParams: app.BaseApp.GetConsensusParams(ctx),
}, err
}
// prepare for fresh start at zero height
// NOTE zero height genesis is a temporary feature which will be deprecated
//
// in favour of export at a block height
func (app *TestApp) prepForZeroHeightGenesis(ctx sdk.Context, jailAllowedAddrs []string) {
applyAllowedAddrs := false
// check if there is a allowed address list
if len(jailAllowedAddrs) > 0 {
applyAllowedAddrs = true
}
allowedAddrsMap := make(map[string]bool)
for _, addr := range jailAllowedAddrs {
_, err := sdk.ValAddressFromBech32(addr)
if err != nil {
log.Fatal(err)
}
allowedAddrsMap[addr] = true
}
/* Just to be safe, assert the invariants on current state. */
app.CrisisKeeper.AssertInvariants(ctx)
/* Handle fee distribution state. */
// withdraw all validator commission
app.StakingKeeper.IterateValidators(ctx, func(_ int64, val stakingtypes.ValidatorI) (stop bool) {
_, _ = app.DistrKeeper.WithdrawValidatorCommission(ctx, val.GetOperator())
return false
})
// withdraw all delegator rewards
dels := app.StakingKeeper.GetAllDelegations(ctx)
for _, delegation := range dels {
valAddr, err := sdk.ValAddressFromBech32(delegation.ValidatorAddress)
if err != nil {
panic(err)
}
delAddr := sdk.MustAccAddressFromBech32(delegation.DelegatorAddress)
_, _ = app.DistrKeeper.WithdrawDelegationRewards(ctx, delAddr, valAddr)
}
// clear validator slash events
app.DistrKeeper.DeleteAllValidatorSlashEvents(ctx)
// clear validator historical rewards
app.DistrKeeper.DeleteAllValidatorHistoricalRewards(ctx)
// set context height to zero
height := ctx.BlockHeight()
ctx = ctx.WithBlockHeight(0)
// reinitialize all validators
app.StakingKeeper.IterateValidators(ctx, func(_ int64, val stakingtypes.ValidatorI) (stop bool) {
// donate any unwithdrawn outstanding reward fraction tokens to the community pool
scraps := app.DistrKeeper.GetValidatorOutstandingRewardsCoins(ctx, val.GetOperator())
feePool := app.DistrKeeper.GetFeePool(ctx)
feePool.CommunityPool = feePool.CommunityPool.Add(scraps...)
app.DistrKeeper.SetFeePool(ctx, feePool)
if err := app.DistrKeeper.Hooks().AfterValidatorCreated(ctx, val.GetOperator()); err != nil {
panic(err)
}
return false
})
// reinitialize all delegations
for _, del := range dels {
valAddr, err := sdk.ValAddressFromBech32(del.ValidatorAddress)
if err != nil {
panic(err)
}
delAddr := sdk.MustAccAddressFromBech32(del.DelegatorAddress)
if err := app.DistrKeeper.Hooks().BeforeDelegationCreated(ctx, delAddr, valAddr); err != nil {
// never called as BeforeDelegationCreated always returns nil
panic(fmt.Errorf("error while incrementing period: %w", err))
}
if err := app.DistrKeeper.Hooks().AfterDelegationModified(ctx, delAddr, valAddr); err != nil {
// never called as AfterDelegationModified always returns nil
panic(fmt.Errorf("error while creating a new delegation period record: %w", err))
}
}
// reset context height
ctx = ctx.WithBlockHeight(height)
/* Handle staking state. */
// iterate through redelegations, reset creation height
app.StakingKeeper.IterateRedelegations(ctx, func(_ int64, red stakingtypes.Redelegation) (stop bool) {
for i := range red.Entries {
red.Entries[i].CreationHeight = 0
}
app.StakingKeeper.SetRedelegation(ctx, red)
return false
})
// iterate through unbonding delegations, reset creation height
app.StakingKeeper.IterateUnbondingDelegations(ctx, func(_ int64, ubd stakingtypes.UnbondingDelegation) (stop bool) {
for i := range ubd.Entries {
ubd.Entries[i].CreationHeight = 0
}
app.StakingKeeper.SetUnbondingDelegation(ctx, ubd)
return false
})
// Iterate through validators by power descending, reset bond heights, and
// update bond intra-tx counters.
store := ctx.KVStore(app.GetKey(stakingtypes.StoreKey))
iter := sdk.KVStoreReversePrefixIterator(store, stakingtypes.ValidatorsKey)
counter := int16(0)
for ; iter.Valid(); iter.Next() {
addr := sdk.ValAddress(stakingtypes.AddressFromValidatorsKey(iter.Key()))
validator, found := app.StakingKeeper.GetValidator(ctx, addr)
if !found {
panic("expected validator, not found")
}
validator.UnbondingHeight = 0
if applyAllowedAddrs && !allowedAddrsMap[addr.String()] {
validator.Jailed = true
}
app.StakingKeeper.SetValidator(ctx, validator)
counter++
}
if err := iter.Close(); err != nil {
app.Logger().Error("error while closing the key-value store reverse prefix iterator: ", err)
return
}
_, err := app.StakingKeeper.ApplyAndReturnValidatorSetUpdates(ctx)
if err != nil {
log.Fatal(err)
}
/* Handle slashing state. */
// reset start height on signing infos
app.SlashingKeeper.IterateValidatorSigningInfos(
ctx,
func(addr sdk.ConsAddress, info slashingtypes.ValidatorSigningInfo) (stop bool) {
info.StartHeight = 0
app.SlashingKeeper.SetValidatorSigningInfo(ctx, addr, info)
return false
},
)
}

View File

@ -0,0 +1,16 @@
package params
import (
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/codec/types"
)
// EncodingConfig specifies the concrete encoding types to use for a given app.
// This is provided for compatibility between protobuf and amino implementations.
type EncodingConfig struct {
InterfaceRegistry types.InterfaceRegistry
Codec codec.Codec
TxConfig client.TxConfig
Amino *codec.LegacyAmino
}

View File

@ -0,0 +1,299 @@
package cmd
import (
"errors"
"io"
"os"
dbm "github.com/cometbft/cometbft-db"
tmcfg "github.com/cometbft/cometbft/config"
"github.com/cometbft/cometbft/libs/log"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/config"
"github.com/cosmos/cosmos-sdk/client/debug"
"github.com/cosmos/cosmos-sdk/client/flags"
"github.com/cosmos/cosmos-sdk/client/keys"
"github.com/cosmos/cosmos-sdk/client/pruning"
"github.com/cosmos/cosmos-sdk/client/rpc"
"github.com/cosmos/cosmos-sdk/server"
serverconfig "github.com/cosmos/cosmos-sdk/server/config"
servertypes "github.com/cosmos/cosmos-sdk/server/types"
simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims"
sdk "github.com/cosmos/cosmos-sdk/types"
authcmd "github.com/cosmos/cosmos-sdk/x/auth/client/cli"
"github.com/cosmos/cosmos-sdk/x/auth/types"
"github.com/cosmos/cosmos-sdk/x/crisis"
genutilcli "github.com/cosmos/cosmos-sdk/x/genutil/client/cli"
"github.com/skip-mev/pob/tests/app"
"github.com/skip-mev/pob/tests/app/params"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
func NewRootCmd() *cobra.Command {
// we "pre"-instantiate the application for getting the injected/configured encoding configuration
testApp := app.New(log.NewNopLogger(), dbm.NewMemDB(), nil, true, simtestutil.NewAppOptionsWithFlagHome(app.DefaultNodeHome))
encodingConfig := params.EncodingConfig{
InterfaceRegistry: testApp.InterfaceRegistry(),
Codec: testApp.AppCodec(),
TxConfig: testApp.TxConfig(),
Amino: testApp.LegacyAmino(),
}
initClientCtx := client.Context{}.
WithCodec(encodingConfig.Codec).
WithInterfaceRegistry(encodingConfig.InterfaceRegistry).
WithTxConfig(encodingConfig.TxConfig).
WithLegacyAmino(encodingConfig.Amino).
WithInput(os.Stdin).
WithAccountRetriever(types.AccountRetriever{}).
WithHomeDir(app.DefaultNodeHome).
WithViper("")
rootCmd := &cobra.Command{
Use: "testappd",
Short: "POB testing application",
PersistentPreRunE: func(cmd *cobra.Command, _ []string) error {
// set the default command outputs
cmd.SetOut(cmd.OutOrStdout())
cmd.SetErr(cmd.ErrOrStderr())
initClientCtx, err := client.ReadPersistentCommandFlags(initClientCtx, cmd.Flags())
if err != nil {
return err
}
initClientCtx, err = config.ReadFromClientConfig(initClientCtx)
if err != nil {
return err
}
if err := client.SetCmdClientContextHandler(initClientCtx, cmd); err != nil {
return err
}
customAppTemplate, customAppConfig := initAppConfig()
customTMConfig := initTendermintConfig()
return server.InterceptConfigsPreRunHandler(cmd, customAppTemplate, customAppConfig, customTMConfig)
},
}
initRootCmd(rootCmd, encodingConfig)
return rootCmd
}
// initTendermintConfig helps to override default Tendermint Config values.
// return tmcfg.DefaultConfig if no custom configuration is required for the application.
func initTendermintConfig() *tmcfg.Config {
cfg := tmcfg.DefaultConfig()
// these values put a higher strain on node memory
// cfg.P2P.MaxNumInboundPeers = 100
// cfg.P2P.MaxNumOutboundPeers = 40
return cfg
}
// initAppConfig helps to override default appConfig template and configs.
// return "", nil if no custom configuration is required for the application.
func initAppConfig() (string, interface{}) {
// The following code snippet is just for reference.
// WASMConfig defines configuration for the wasm module.
type WASMConfig struct {
// This is the maximum sdk gas (wasm and storage) that we allow for any x/wasm "smart" queries
QueryGasLimit uint64 `mapstructure:"query_gas_limit"`
// Address defines the gRPC-web server to listen on
LruSize uint64 `mapstructure:"lru_size"`
}
type CustomAppConfig struct {
serverconfig.Config
WASM WASMConfig `mapstructure:"wasm"`
}
// Optionally allow the chain developer to overwrite the SDK's default
// server config.
srvCfg := serverconfig.DefaultConfig()
// The SDK's default minimum gas price is set to "" (empty value) inside
// app.toml. If left empty by validators, the node will halt on startup.
// However, the chain developer can set a default app.toml value for their
// validators here.
//
// In summary:
// - if you leave srvCfg.MinGasPrices = "", all validators MUST tweak their
// own app.toml config,
// - if you set srvCfg.MinGasPrices non-empty, validators CAN tweak their
// own app.toml to override, or use this default value.
//
// In testapp, we set the min gas prices to 0.
srvCfg.MinGasPrices = "0stake"
// srvCfg.BaseConfig.IAVLDisableFastNode = true // disable fastnode by default
customAppConfig := CustomAppConfig{
Config: *srvCfg,
WASM: WASMConfig{
LruSize: 1,
QueryGasLimit: 300000,
},
}
customAppTemplate := serverconfig.DefaultConfigTemplate + `
[wasm]
# This is the maximum sdk gas (wasm and storage) that we allow for any x/wasm "smart" queries
query_gas_limit = 300000
# This is the number of wasm vm instances we keep cached in memory for speed-up
# Warning: this is currently unstable and may lead to crashes, best to keep for 0 unless testing locally
lru_size = 0`
return customAppTemplate, customAppConfig
}
func initRootCmd(rootCmd *cobra.Command, encodingConfig params.EncodingConfig) {
cfg := sdk.GetConfig()
cfg.Seal()
rootCmd.AddCommand(
genutilcli.InitCmd(app.ModuleBasics, app.DefaultNodeHome),
debug.Cmd(),
config.Cmd(),
pruning.PruningCmd(newApp),
)
server.AddCommands(rootCmd, app.DefaultNodeHome, newApp, appExport, addModuleInitFlags)
// add keybase, auxiliary RPC, query, genesis, and tx child commands
rootCmd.AddCommand(
rpc.StatusCommand(),
genesisCommand(encodingConfig),
queryCommand(),
txCommand(),
keys.Commands(app.DefaultNodeHome),
)
}
func addModuleInitFlags(startCmd *cobra.Command) {
crisis.AddModuleInitFlags(startCmd)
}
func genesisCommand(encodingConfig params.EncodingConfig, cmds ...*cobra.Command) *cobra.Command {
cmd := genutilcli.GenesisCoreCommand(encodingConfig.TxConfig, app.ModuleBasics, app.DefaultNodeHome)
for _, subCmd := range cmds {
cmd.AddCommand(subCmd)
}
return cmd
}
func queryCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "query",
Aliases: []string{"q"},
Short: "Querying subcommands",
DisableFlagParsing: false,
SuggestionsMinimumDistance: 2,
RunE: client.ValidateCmd,
}
cmd.AddCommand(
authcmd.GetAccountCmd(),
rpc.ValidatorCommand(),
rpc.BlockCommand(),
authcmd.QueryTxsByEventsCmd(),
authcmd.QueryTxCmd(),
)
app.ModuleBasics.AddQueryCommands(cmd)
return cmd
}
func txCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "tx",
Short: "Transactions subcommands",
DisableFlagParsing: false,
SuggestionsMinimumDistance: 2,
RunE: client.ValidateCmd,
}
cmd.AddCommand(
authcmd.GetSignCommand(),
authcmd.GetSignBatchCommand(),
authcmd.GetMultiSignCommand(),
authcmd.GetMultiSignBatchCmd(),
authcmd.GetValidateSignaturesCommand(),
authcmd.GetBroadcastCommand(),
authcmd.GetEncodeCommand(),
authcmd.GetDecodeCommand(),
authcmd.GetAuxToFeeCommand(),
)
app.ModuleBasics.AddTxCommands(cmd)
return cmd
}
func newApp(
logger log.Logger,
db dbm.DB,
traceStore io.Writer,
appOpts servertypes.AppOptions,
) servertypes.Application {
baseAppOpts := server.DefaultBaseappOptions(appOpts)
return app.New(
logger,
db,
traceStore,
true,
appOpts,
baseAppOpts...,
)
}
func appExport(
logger log.Logger,
db dbm.DB,
traceStore io.Writer,
height int64,
forZeroHeight bool,
jailAllowedAddrs []string,
appOpts servertypes.AppOptions,
modulesToExport []string,
) (servertypes.ExportedApp, error) {
var testApp *app.TestApp
// this check is necessary as we use the flag in x/upgrade.
// we can exit more gracefully by checking the flag here.
homePath, ok := appOpts.Get(flags.FlagHome).(string)
if !ok || homePath == "" {
return servertypes.ExportedApp{}, errors.New("application home not set")
}
viperAppOpts, ok := appOpts.(*viper.Viper)
if !ok {
return servertypes.ExportedApp{}, errors.New("appOpts is not viper.Viper")
}
// overwrite the FlagInvCheckPeriod
viperAppOpts.Set(server.FlagInvCheckPeriod, 1)
appOpts = viperAppOpts
if height != -1 {
testApp = app.New(logger, db, traceStore, false, appOpts)
if err := testApp.LoadHeight(height); err != nil {
return servertypes.ExportedApp{}, err
}
} else {
testApp = app.New(logger, db, traceStore, true, appOpts)
}
return testApp.ExportAppStateAndValidators(forZeroHeight, jailAllowedAddrs, modulesToExport)
}

View File

@ -0,0 +1,23 @@
package main
import (
"os"
"github.com/cosmos/cosmos-sdk/server"
svrcmd "github.com/cosmos/cosmos-sdk/server/cmd"
"github.com/skip-mev/pob/tests/app"
"github.com/skip-mev/pob/tests/app/testappd/cmd"
)
func main() {
rootCmd := cmd.NewRootCmd()
if err := svrcmd.Execute(rootCmd, "", app.DefaultNodeHome); err != nil {
switch e := err.(type) {
case server.ErrorCode:
os.Exit(e.Code)
default:
os.Exit(1)
}
}
}

91
tests/e2e/chain.go Normal file
View File

@ -0,0 +1,91 @@
package e2e
import (
"fmt"
"os"
dbm "github.com/cometbft/cometbft-db"
"github.com/cometbft/cometbft/libs/log"
cometrand "github.com/cometbft/cometbft/libs/rand"
"github.com/cosmos/cosmos-sdk/codec"
simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims"
"github.com/skip-mev/pob/tests/app"
"github.com/skip-mev/pob/tests/app/params"
)
const (
keyringPassphrase = "testpassphrase"
keyringAppName = "testnet"
)
var (
encodingConfig params.EncodingConfig
cdc codec.Codec
)
func init() {
testApp := app.New(log.NewNopLogger(), dbm.NewMemDB(), nil, true, simtestutil.NewAppOptionsWithFlagHome(app.DefaultNodeHome))
encodingConfig = params.EncodingConfig{
InterfaceRegistry: testApp.InterfaceRegistry(),
Codec: testApp.AppCodec(),
TxConfig: testApp.TxConfig(),
Amino: testApp.LegacyAmino(),
}
cdc = encodingConfig.Codec
}
type chain struct {
dataDir string
id string
validators []*validator
}
func newChain() (*chain, error) {
tmpDir, err := os.MkdirTemp("", "pob-e2e-testnet-")
if err != nil {
return nil, err
}
return &chain{
id: "chain-" + cometrand.NewRand().Str(6),
dataDir: tmpDir,
}, nil
}
func (c *chain) configDir() string {
return fmt.Sprintf("%s/%s", c.dataDir, c.id)
}
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: "testapp",
}
}

297
tests/e2e/e2e_setup_test.go Normal file
View File

@ -0,0 +1,297 @@
package e2e
import (
"context"
"encoding/json"
"fmt"
"os"
"path/filepath"
"strconv"
"strings"
"testing"
"time"
cometcfg "github.com/cometbft/cometbft/config"
cometjson "github.com/cometbft/cometbft/libs/json"
rpchttp "github.com/cometbft/cometbft/rpc/client/http"
"github.com/cosmos/cosmos-sdk/server"
srvconfig "github.com/cosmos/cosmos-sdk/server/config"
sdk "github.com/cosmos/cosmos-sdk/types"
genutiltypes "github.com/cosmos/cosmos-sdk/x/genutil/types"
govtypes "github.com/cosmos/cosmos-sdk/x/gov/types"
govtypesv1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1"
"github.com/ory/dockertest/v3"
"github.com/ory/dockertest/v3/docker"
"github.com/skip-mev/pob/tests/app"
"github.com/spf13/viper"
"github.com/stretchr/testify/suite"
)
var (
numValidators = 3
minGasPrice = sdk.NewDecCoinFromDec(app.BondDenom, sdk.MustNewDecFromStr("0.02")).String()
initBalanceStr = sdk.NewInt64Coin(app.BondDenom, 510000000000).String()
stakeAmount, _ = sdk.NewIntFromString("100000000000")
stakeAmountCoin = sdk.NewCoin(app.BondDenom, stakeAmount)
)
type IntegrationTestSuite struct {
suite.Suite
tmpDirs []string
chain *chain
dkrPool *dockertest.Pool
dkrNet *dockertest.Network
valResources []*dockertest.Resource
}
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.chain, err = newChain()
s.Require().NoError(err)
s.T().Logf("starting e2e infrastructure; chain-id: %s; datadir: %s", s.chain.id, s.chain.dataDir)
s.dkrPool, err = dockertest.NewPool("")
s.Require().NoError(err)
s.dkrNet, err = s.dkrPool.CreateNetwork(fmt.Sprintf("%s-testnet", s.chain.id))
s.Require().NoError(err)
// The bootstrapping phase is as follows:
//
// 1. Initialize TestApp validator nodes.
// 2. Create and initialize TestApp validator genesis files, i.e. setting
// delegate keys for validators.
// 3. Start TestApp network.
s.initNodes()
s.initGenesis()
s.initValidatorConfigs()
s.runValidators()
}
func (s *IntegrationTestSuite) TearDownSuite() {
if str := os.Getenv("POB_E2E_SKIP_CLEANUP"); len(str) > 0 {
skipCleanup, err := strconv.ParseBool(str)
s.Require().NoError(err)
if skipCleanup {
return
}
}
s.T().Log("tearing down e2e integration test suite...")
for _, vc := range s.valResources {
s.Require().NoError(s.dkrPool.Purge(vc))
}
s.Require().NoError(s.dkrPool.RemoveNetwork(s.dkrNet))
os.RemoveAll(s.chain.dataDir)
for _, td := range s.tmpDirs {
os.RemoveAll(td)
}
}
func (s *IntegrationTestSuite) initNodes() {
s.Require().NoError(s.chain.createAndInitValidators(numValidators))
// initialize a genesis file for the first validator
val0ConfigDir := s.chain.validators[0].configDir()
for _, val := range s.chain.validators {
valAddr, err := val.keyInfo.GetAddress()
s.Require().NoError(err)
s.Require().NoError(addGenesisAccount(val0ConfigDir, "", initBalanceStr, valAddr))
}
// copy the genesis file to the remaining validators
for _, val := range s.chain.validators[1:] {
_, err := copyFile(
filepath.Join(val0ConfigDir, "config", "genesis.json"),
filepath.Join(val.configDir(), "config", "genesis.json"),
)
s.Require().NoError(err)
}
}
func (s *IntegrationTestSuite) initGenesis() {
serverCtx := server.NewDefaultContext()
config := serverCtx.Config
config.SetRoot(s.chain.validators[0].configDir())
config.Moniker = s.chain.validators[0].moniker
genFilePath := config.GenesisFile()
appGenState, genDoc, err := genutiltypes.GenesisStateFromGenFile(genFilePath)
s.T().Log("starting e2e infrastructure; validator_0 config:", genFilePath)
s.Require().NoError(err)
// x/gov
var govGenState govtypesv1.GenesisState
s.Require().NoError(cdc.UnmarshalJSON(appGenState[govtypes.ModuleName], &govGenState))
votingPeriod := 5 * time.Second
govGenState.Params.VotingPeriod = &votingPeriod
govGenState.Params.MinDeposit = sdk.NewCoins(sdk.NewCoin(app.BondDenom, sdk.NewInt(100)))
bz, err := cdc.MarshalJSON(&govGenState)
s.Require().NoError(err)
appGenState[govtypes.ModuleName] = bz
var genUtilGenState genutiltypes.GenesisState
s.Require().NoError(cdc.UnmarshalJSON(appGenState[genutiltypes.ModuleName], &genUtilGenState))
// x/genutil genesis txs
genTxs := make([]json.RawMessage, len(s.chain.validators))
for i, val := range s.chain.validators {
createValMsg, err := val.buildCreateValidatorMsg(stakeAmountCoin)
s.Require().NoError(err)
signedTx, err := val.signMsg(createValMsg)
s.Require().NoError(err)
txRaw, err := cdc.MarshalJSON(signedTx)
s.Require().NoError(err)
genTxs[i] = txRaw
}
genUtilGenState.GenTxs = genTxs
bz, err = cdc.MarshalJSON(&genUtilGenState)
s.Require().NoError(err)
appGenState[genutiltypes.ModuleName] = bz
bz, err = json.MarshalIndent(appGenState, "", " ")
s.Require().NoError(err)
genDoc.AppState = bz
bz, err = cometjson.MarshalIndent(genDoc, "", " ")
s.Require().NoError(err)
// write the updated genesis file to each validator
for _, val := range s.chain.validators {
writeFile(filepath.Join(val.configDir(), "config", "genesis.json"), bz)
}
}
func (s *IntegrationTestSuite) initValidatorConfigs() {
for i, val := range s.chain.validators {
tmCfgPath := filepath.Join(val.configDir(), "config", "config.toml")
vpr := viper.New()
vpr.SetConfigFile(tmCfgPath)
s.Require().NoError(vpr.ReadInConfig())
valConfig := cometcfg.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(s.chain.validators); j++ {
if i == j {
continue
}
peer := s.chain.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, ",")
cometcfg.WriteConfigFile(tmCfgPath, valConfig)
// set application configuration
appCfgPath := filepath.Join(val.configDir(), "config", "app.toml")
appConfig := srvconfig.DefaultConfig()
appConfig.API.Enable = true
appConfig.MinGasPrices = minGasPrice
srvconfig.WriteConfigFile(appCfgPath, appConfig)
}
}
func (s *IntegrationTestSuite) runValidators() {
s.T().Log("starting POB TestApp validator containers...")
s.valResources = make([]*dockertest.Resource, len(s.chain.validators))
for i, val := range s.chain.validators {
runOpts := &dockertest.RunOptions{
Name: val.instanceName(),
NetworkID: s.dkrNet.Network.ID,
Mounts: []string{
fmt.Sprintf("%s/:/root/.testapp", val.configDir()),
},
Repository: "docker.io/skip-mev/pob-e2e",
}
// expose the first validator for debugging and communication
if val.index == 0 {
runOpts.PortBindings = map[docker.Port][]docker.PortBinding{
"1317/tcp": {{HostIP: "", HostPort: "1317"}},
"6060/tcp": {{HostIP: "", HostPort: "6060"}},
"6061/tcp": {{HostIP: "", HostPort: "6061"}},
"6062/tcp": {{HostIP: "", HostPort: "6062"}},
"6063/tcp": {{HostIP: "", HostPort: "6063"}},
"6064/tcp": {{HostIP: "", HostPort: "6064"}},
"6065/tcp": {{HostIP: "", HostPort: "6065"}},
"9090/tcp": {{HostIP: "", HostPort: "9090"}},
"26656/tcp": {{HostIP: "", HostPort: "26656"}},
"26657/tcp": {{HostIP: "", HostPort: "26657"}},
}
}
resource, err := s.dkrPool.RunWithOptions(runOpts, noRestart)
s.Require().NoError(err)
s.valResources[i] = resource
s.T().Logf("started POB TestApp validator container: %s", 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,
"POB TestApp 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",
}
}

7
tests/e2e/e2e_test.go Normal file
View File

@ -0,0 +1,7 @@
//go:build e2e
package e2e
func (s *IntegrationTestSuite) TestTmp() {
s.Require().True(true)
}

110
tests/e2e/genesis.go Normal file
View File

@ -0,0 +1,110 @@
package e2e
import (
"encoding/json"
"fmt"
"os"
comettypes "github.com/cometbft/cometbft/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"
)
func getGenDoc(path string) (*comettypes.GenesisDoc, error) {
serverCtx := server.NewDefaultContext()
config := serverCtx.Config
config.SetRoot(path)
genFile := config.GenesisFile()
doc := &comettypes.GenesisDoc{}
if _, err := os.Stat(genFile); err != nil {
if !os.IsNotExist(err) {
return nil, err
}
} else {
var err error
doc, err = comettypes.GenesisDocFromFile(genFile)
if err != nil {
return nil, fmt.Errorf("failed to read genesis doc from file: %w", err)
}
}
return doc, nil
}
func addGenesisAccount(path, moniker, amountStr string, accAddr sdk.AccAddress) 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)
}
balances := banktypes.Balance{Address: accAddr.String(), Coins: coins.Sort()}
genAccount := authtypes.NewBaseAccount(accAddr, nil, 0, 0)
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)
}
if accs.Contains(accAddr) {
return fmt.Errorf("failed to add account to genesis state; account already exists: %s", accAddr)
}
// Add the new account to the set of genesis accounts and sanitize the
// accounts afterwards.
accs = append(accs, genAccount)
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
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)
}

42
tests/e2e/io.go Normal file
View File

@ -0,0 +1,42 @@
package e2e
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)
}

19
tests/e2e/keys.go Normal file
View File

@ -0,0 +1,19 @@
package e2e
import (
"github.com/cosmos/go-bip39"
)
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
}

45
tests/e2e/util.go Normal file
View File

@ -0,0 +1,45 @@
package e2e
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
}

272
tests/e2e/validator.go Normal file
View File

@ -0,0 +1,272 @@
package e2e
import (
"encoding/json"
"fmt"
"os"
"path"
"path/filepath"
cometcfg "github.com/cometbft/cometbft/config"
"github.com/cometbft/cometbft/p2p"
"github.com/cometbft/cometbft/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"
"github.com/skip-mev/pob/tests/app"
)
type validator struct {
chain *chain
index int
moniker string
mnemonic string
keyInfo keyring.Record
privateKey cryptotypes.PrivKey
consensusKey privval.FilePVKey
nodeKey p2p.NodeKey
}
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(app.ModuleBasics.DefaultGenesis(cdc), "", " ")
if err != nil {
return fmt.Errorf("failed to JSON encode app genesis state: %w", err)
}
genDoc.ChainID = v.chain.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)
}
cometcfg.WriteConfigFile(filepath.Join(config.RootDir, "config", "config.toml"), config)
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 := os.MkdirAll(filepath.Dir(pvKeyFile), 0o777); err != nil {
return fmt.Errorf("could not create directory %q: %w", filepath.Dir(pvKeyFile), err)
}
pvStateFile := config.PrivValidatorStateFile()
if err := os.MkdirAll(filepath.Dir(pvStateFile), 0o777); err != nil {
return fmt.Errorf("could not create directory %q: %w", filepath.Dir(pvStateFile), err)
}
filePV := privval.LoadOrGenFilePV(pvKeyFile, pvStateFile)
v.consensusKey = filePV.Key
return nil
}
func (v *validator) createKeyFromMnemonic(name, mnemonic string) error {
kb, err := keyring.New(keyringAppName, keyring.BackendTest, v.configDir(), 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) createKey(name string) error {
mnemonic, err := createMnemonic()
if err != nil {
return err
}
return v.createKeyFromMnemonic(name, mnemonic)
}
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"),
}
// get the initial validator min self delegation
minSelfDelegation, _ := sdk.NewIntFromString("1")
valPubKey, err := cryptocodec.FromTmPubKeyInterface(v.consensusKey.PubKey)
if err != nil {
return nil, err
}
valAddr, err := v.keyInfo.GetAddress()
if err != nil {
return nil, err
}
return stakingtypes.NewMsgCreateValidator(
sdk.ValAddress(valAddr),
valPubKey,
amount,
description,
commissionRates,
minSelfDelegation,
)
}
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(200_000)
signerData := authsigning.SignerData{
ChainID: v.chain.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)
}