Skip to content
This repository has been archived by the owner on Nov 30, 2021. It is now read-only.

SDK modules simulations #269

Merged
merged 3 commits into from
Apr 29, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,12 +61,12 @@ Ref: https://keepachangelog.com/en/1.0.0/
* Update uninstallFilter and getFilterChanges accordingly
* uninstallFilter stops the polling goroutine
* getFilterChanges returns the filter's internal list of block hashes and resets it

* (rpc) [\#54](https://github.com/ChainSafe/ethermint/issues/54) [\#55](https://github.com/ChainSafe/ethermint/issues/55)
Implement eth_getFilterLogs and eth_getLogs
* for a given filter, look through each block for transactions. If there are transactions in the block, get the logs from it, and filter using the filterLogs method
* eth_getLogs and eth_getFilterChanges for log filters use the same underlying method as eth_getFilterLogs
* update HandleMsgEthereumTx to store logs using the ethereum hash
* (rpc) [\#54](https://github.com/ChainSafe/ethermint/issues/54), [\#55](https://github.com/ChainSafe/ethermint/issues/55)
Implement `eth_getFilterLogs` and `eth_getLogs`:
* For a given filter, look through each block for transactions. If there are transactions in the block, get the logs from it, and filter using the filterLogs method
* `eth_getLogs` and `eth_getFilterChanges` for log filters use the same underlying method as `eth_getFilterLogs`
* update `HandleMsgEthereumTx` to store logs using the ethereum hash
* (app) [\#187](https://github.com/ChainSafe/ethermint/issues/187) Add support for simulations.

### Bug Fixes

Expand Down
56 changes: 54 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ DOCKER_IMAGE = cosmos/ethermint
ETHERMINT_DAEMON_BINARY = emintd
ETHERMINT_CLI_BINARY = emintcli
GO_MOD=GO111MODULE=on
BINDIR ?= $(GOPATH)/bin
SIMAPP = github.com/cosmos/ethermint/app
RUNSIM = $(BINDIR)/runsim

all: tools verify install

Expand Down Expand Up @@ -77,7 +80,17 @@ MISSPELL_CHECK := $(shell command -v misspell 2> /dev/null)
ERRCHECK_CHECK := $(shell command -v errcheck 2> /dev/null)
UNPARAM_CHECK := $(shell command -v unparam 2> /dev/null)

tools:

# Install the runsim binary with a temporary workaround of entering an outside
# directory as the "go get" command ignores the -mod option and will polute the
# go.{mod, sum} files.
#
# ref: https://github.com/golang/go/issues/30515
$(RUNSIM):
@echo "Installing runsim..."
@(cd /tmp && go get github.com/cosmos/tools/cmd/runsim@v1.0.0)

tools: $(RUNSIM)
ifdef GOLINT_CHECK
@echo "Golint is already installed. Run 'make update-tools' to update."
else
Expand Down Expand Up @@ -238,4 +251,43 @@ proto-update-deps:
@sed -i '' '6 s|x/auth/types/types.proto|third_party/proto/cosmos-sdk/x/auth/types/types.proto|g' $(SUPPLY_PROTO_TYPES)/types.proto


.PHONY: proto-all proto-gen proto-lint proto-check-breaking proto-update-deps
.PHONY: proto-all proto-gen proto-lint proto-check-breaking proto-update-deps

#######################
### Simulations ###
#######################

test-sim-nondeterminism:
@echo "Running non-determinism test..."
@go test -mod=readonly $(SIMAPP) -run TestAppStateDeterminism -Enabled=true \
-NumBlocks=100 -BlockSize=200 -Commit=true -Period=0 -v -timeout 24h

test-sim-custom-genesis-fast:
@echo "Running custom genesis simulation..."
@echo "By default, ${HOME}/.emintd/config/genesis.json will be used."
@go test -mod=readonly $(SIMAPP) -run TestFullAppSimulation -Genesis=${HOME}/.emintd/config/genesis.json \
-Enabled=true -NumBlocks=100 -BlockSize=200 -Commit=true -Seed=99 -Period=5 -v -timeout 24h

test-sim-import-export: runsim
@echo "Running Ethermint import/export simulation. This may take several minutes..."
@$(BINDIR)/runsim -Jobs=4 -SimAppPkg=$(SIMAPP) 25 5 TestAppImportExport

test-sim-after-import: runsim
@echo "Running Ethermint simulation-after-import. This may take several minutes..."
@$(BINDIR)/runsim -Jobs=4 -SimAppPkg=$(SIMAPP) 25 5 TestAppSimulationAfterImport

test-sim-custom-genesis-multi-seed: runsim
@echo "Running multi-seed custom genesis simulation..."
@echo "By default, ${HOME}/.emintd/config/genesis.json will be used."
@$(BINDIR)/runsim -Jobs=4 -Genesis=${HOME}/.emintd/config/genesis.json 400 5 TestFullAppSimulation

test-sim-multi-seed-long: runsim
@echo "Running multi-seed application simulation. This may take awhile!"
@$(BINDIR)/runsim -Jobs=4 -SimAppPkg=$(SIMAPP) 500 50 TestFullAppSimulation

test-sim-multi-seed-short: runsim
@echo "Running multi-seed application simulation. This may take awhile!"
@$(BINDIR)/runsim -Jobs=4 -SimAppPkg=$(SIMAPP) 50 10 TestFullAppSimulation

.PHONY: runsim test-sim-nondeterminism test-sim-custom-genesis-fast test-sim-fast sim-import-export \
test-sim-simulation-after-import test-sim-custom-genesis-multi-seed test-sim-multi-seed \
295 changes: 295 additions & 0 deletions app/simulation_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,295 @@
package app

import (
"encoding/json"
"fmt"
"math/rand"
"os"
"testing"

"github.com/stretchr/testify/require"
abci "github.com/tendermint/tendermint/abci/types"
"github.com/tendermint/tendermint/libs/log"
dbm "github.com/tendermint/tm-db"

"github.com/cosmos/cosmos-sdk/baseapp"
"github.com/cosmos/cosmos-sdk/simapp"
"github.com/cosmos/cosmos-sdk/simapp/helpers"
"github.com/cosmos/cosmos-sdk/store"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/auth"
distr "github.com/cosmos/cosmos-sdk/x/distribution"
"github.com/cosmos/cosmos-sdk/x/gov"
"github.com/cosmos/cosmos-sdk/x/mint"
"github.com/cosmos/cosmos-sdk/x/params"
"github.com/cosmos/cosmos-sdk/x/simulation"
"github.com/cosmos/cosmos-sdk/x/slashing"
"github.com/cosmos/cosmos-sdk/x/staking"
"github.com/cosmos/cosmos-sdk/x/supply"
)

func init() {
simapp.GetSimulatorFlags()
}

type storeKeysPrefixes struct {
A sdk.StoreKey
B sdk.StoreKey
Prefixes [][]byte
}

// fauxMerkleModeOpt returns a BaseApp option to use a dbStoreAdapter instead of
// an IAVLStore for faster simulation speed.
func fauxMerkleModeOpt(bapp *baseapp.BaseApp) {
bapp.SetFauxMerkleMode()
}

// interBlockCacheOpt returns a BaseApp option function that sets the persistent
// inter-block write-through cache.
func interBlockCacheOpt() func(*baseapp.BaseApp) {
return baseapp.SetInterBlockCache(store.NewCommitKVStoreCacheManager())
}

func TestFullAppSimulation(t *testing.T) {
config, db, dir, logger, skip, err := simapp.SetupSimulation("leveldb-app-sim", "Simulation")
if skip {
t.Skip("skipping application simulation")
}
require.NoError(t, err, "simulation setup failed")

defer func() {
db.Close()
require.NoError(t, os.RemoveAll(dir))
}()

app := NewEthermintApp(logger, db, nil, true, simapp.FlagPeriodValue, fauxMerkleModeOpt)
require.Equal(t, appName, app.Name())

// run randomized simulation
_, simParams, simErr := simulation.SimulateFromSeed(
t, os.Stdout, app.BaseApp, simapp.AppStateFn(app.Codec(), app.SimulationManager()),
simapp.SimulationOperations(app, app.Codec(), config),
app.ModuleAccountAddrs(), config,
)

// export state and simParams before the simulation error is checked
err = simapp.CheckExportSimulation(app, config, simParams)
require.NoError(t, err)
require.NoError(t, simErr)

if config.Commit {
simapp.PrintStats(db)
}
}

func TestAppImportExport(t *testing.T) {
config, db, dir, logger, skip, err := simapp.SetupSimulation("leveldb-app-sim", "Simulation")
if skip {
t.Skip("skipping application import/export simulation")
}
require.NoError(t, err, "simulation setup failed")

defer func() {
db.Close()
require.NoError(t, os.RemoveAll(dir))
}()

app := NewEthermintApp(logger, db, nil, true, simapp.FlagPeriodValue, fauxMerkleModeOpt)
require.Equal(t, appName, app.Name())

// Run randomized simulation
_, simParams, simErr := simulation.SimulateFromSeed(
t, os.Stdout, app.BaseApp, simapp.AppStateFn(app.Codec(), app.SimulationManager()),
simapp.SimulationOperations(app, app.Codec(), config),
app.ModuleAccountAddrs(), config,
)

// export state and simParams before the simulation error is checked
err = simapp.CheckExportSimulation(app, config, simParams)
require.NoError(t, err)
require.NoError(t, simErr)

if config.Commit {
simapp.PrintStats(db)
}

fmt.Printf("exporting genesis...\n")

appState, _, err := app.ExportAppStateAndValidators(false, []string{})
require.NoError(t, err)

fmt.Printf("importing genesis...\n")

// nolint: dogsled
_, newDB, newDir, _, _, err := simapp.SetupSimulation("leveldb-app-sim-2", "Simulation-2")
require.NoError(t, err, "simulation setup failed")

defer func() {
newDB.Close()
require.NoError(t, os.RemoveAll(newDir))
}()

newApp := NewEthermintApp(log.NewNopLogger(), newDB, nil, true, simapp.FlagPeriodValue, fauxMerkleModeOpt)
require.Equal(t, appName, newApp.Name())

var genesisState map[string]json.RawMessage
err = app.Codec().UnmarshalJSON(appState, &genesisState)
require.NoError(t, err)

ctxA := app.NewContext(true, abci.Header{Height: app.LastBlockHeight()})
ctxB := newApp.NewContext(true, abci.Header{Height: app.LastBlockHeight()})
newApp.mm.InitGenesis(ctxB, app.Codec(), genesisState)

fmt.Printf("comparing stores...\n")

storeKeysPrefixes := []storeKeysPrefixes{
{app.keys[baseapp.MainStoreKey], newApp.keys[baseapp.MainStoreKey], [][]byte{}},
{app.keys[auth.StoreKey], newApp.keys[auth.StoreKey], [][]byte{}},
{app.keys[staking.StoreKey], newApp.keys[staking.StoreKey],
[][]byte{
staking.UnbondingQueueKey, staking.RedelegationQueueKey, staking.ValidatorQueueKey,
staking.HistoricalInfoKey,
}}, // ordering may change but it doesn't matter
{app.keys[slashing.StoreKey], newApp.keys[slashing.StoreKey], [][]byte{}},
{app.keys[mint.StoreKey], newApp.keys[mint.StoreKey], [][]byte{}},
{app.keys[distr.StoreKey], newApp.keys[distr.StoreKey], [][]byte{}},
{app.keys[supply.StoreKey], newApp.keys[supply.StoreKey], [][]byte{}},
{app.keys[params.StoreKey], newApp.keys[params.StoreKey], [][]byte{}},
{app.keys[gov.StoreKey], newApp.keys[gov.StoreKey], [][]byte{}},
}

for _, skp := range storeKeysPrefixes {
storeA := ctxA.KVStore(skp.A)
storeB := ctxB.KVStore(skp.B)

failedKVAs, failedKVBs := sdk.DiffKVStores(storeA, storeB, skp.Prefixes)
require.Equal(t, len(failedKVAs), len(failedKVBs), "unequal sets of key-values to compare")

fmt.Printf("compared %d key/value pairs between %s and %s\n", len(failedKVAs), skp.A, skp.B)
require.Equal(t, len(failedKVAs), 0, simapp.GetSimulationLog(skp.A.Name(), app.SimulationManager().StoreDecoders, app.Codec(), failedKVAs, failedKVBs))
}
}

func TestAppSimulationAfterImport(t *testing.T) {
config, db, dir, logger, skip, err := simapp.SetupSimulation("leveldb-app-sim", "Simulation")
if skip {
t.Skip("skipping application simulation after import")
}
require.NoError(t, err, "simulation setup failed")

defer func() {
db.Close()
require.NoError(t, os.RemoveAll(dir))
}()

app := NewEthermintApp(logger, db, nil, true, simapp.FlagPeriodValue, fauxMerkleModeOpt)
require.Equal(t, appName, app.Name())

// Run randomized simulation
stopEarly, simParams, simErr := simulation.SimulateFromSeed(
t, os.Stdout, app.BaseApp, simapp.AppStateFn(app.Codec(), app.SimulationManager()),
simapp.SimulationOperations(app, app.Codec(), config),
app.ModuleAccountAddrs(), config,
)

// export state and simParams before the simulation error is checked
err = simapp.CheckExportSimulation(app, config, simParams)
require.NoError(t, err)
require.NoError(t, simErr)

if config.Commit {
simapp.PrintStats(db)
}

if stopEarly {
fmt.Println("can't export or import a zero-validator genesis, exiting test...")
return
}

fmt.Printf("exporting genesis...\n")

appState, _, err := app.ExportAppStateAndValidators(true, []string{})
require.NoError(t, err)

fmt.Printf("importing genesis...\n")

// nolint: dosgsled
_, newDB, newDir, _, _, err := simapp.SetupSimulation("leveldb-app-sim-2", "Simulation-2")
require.NoError(t, err, "simulation setup failed")

defer func() {
newDB.Close()
require.NoError(t, os.RemoveAll(newDir))
}()

newApp := NewEthermintApp(log.NewNopLogger(), newDB, nil, true, simapp.FlagPeriodValue, fauxMerkleModeOpt)
require.Equal(t, appName, newApp.Name())

newApp.InitChain(abci.RequestInitChain{
AppStateBytes: appState,
})

_, _, err = simulation.SimulateFromSeed(
t, os.Stdout, newApp.BaseApp, simapp.AppStateFn(app.Codec(), app.SimulationManager()),
simapp.SimulationOperations(newApp, newApp.Codec(), config),
newApp.ModuleAccountAddrs(), config,
)
require.NoError(t, err)
}

func TestAppStateDeterminism(t *testing.T) {
if !simapp.FlagEnabledValue {
t.Skip("skipping application simulation")
}

config := simapp.NewConfigFromFlags()
config.InitialBlockHeight = 1
config.ExportParamsPath = ""
config.OnOperation = false
config.AllInvariants = false
config.ChainID = helpers.SimAppChainID

numTimesToRunPerSeed := 2
appHashList := make([]json.RawMessage, numTimesToRunPerSeed)

config.Seed = rand.Int63()

for i := 0; i < numTimesToRunPerSeed; i++ {
var logger log.Logger
if simapp.FlagVerboseValue {
logger = log.TestingLogger()
} else {
logger = log.NewNopLogger()
}

db := dbm.NewMemDB()

app := NewEthermintApp(logger, db, nil, true, simapp.FlagPeriodValue, interBlockCacheOpt())

fmt.Printf(
"running non-determinism simulation; seed %d: attempt: %d/%d\n",
config.Seed, i+1, numTimesToRunPerSeed,
)

_, _, err := simulation.SimulateFromSeed(
t, os.Stdout, app.BaseApp, simapp.AppStateFn(app.Codec(), app.SimulationManager()),
simapp.SimulationOperations(app, app.Codec(), config),
app.ModuleAccountAddrs(), config,
)
require.NoError(t, err)

if config.Commit {
simapp.PrintStats(db)
}

appHash := app.LastCommitID().Hash
appHashList[i] = appHash

if i != 0 {
require.Equal(
t, appHashList[0], appHashList[i],
"non-determinism in seed %d: %d/%d, attempt: %d/%d\n", config.Seed, i+1, numTimesToRunPerSeed,
)
}
}
}