Merge PR #5103: Migration Testing

This commit is contained in:
Alexander Bezobchuk 2019-09-26 09:07:15 -07:00 committed by GitHub
parent 9d0bed8f4f
commit fbdf0e778e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 353 additions and 140 deletions

View File

@ -15,6 +15,7 @@ linters:
- errcheck - errcheck
- scopelint - scopelint
- varcheck - varcheck
- godox
issues: issues:
exclude-rules: exclude-rules:

View File

@ -92,5 +92,5 @@ func ExportCmd(ctx *Context, cdc *codec.Codec, appExporter AppExporter) *cobra.C
} }
func isEmptyState(db dbm.DB) bool { func isEmptyState(db dbm.DB) bool {
return db.Stats()["leveldb.sstables"] != "" return db.Stats()["leveldb.sstables"] == ""
} }

123
simapp/sim_bench_test.go Normal file
View File

@ -0,0 +1,123 @@
package simapp
import (
"fmt"
"io/ioutil"
"os"
"testing"
abci "github.com/tendermint/tendermint/abci/types"
"github.com/tendermint/tendermint/libs/log"
dbm "github.com/tendermint/tm-db"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/simulation"
)
// Profile with:
// /usr/local/go/bin/go test -benchmem -run=^$ github.com/cosmos/cosmos-sdk/simapp -bench ^BenchmarkFullAppSimulation$ -Commit=true -cpuprofile cpu.out
func BenchmarkFullAppSimulation(b *testing.B) {
logger := log.NewNopLogger()
config := NewConfigFromFlags()
var db dbm.DB
dir, _ := ioutil.TempDir("", "goleveldb-app-sim")
db, _ = sdk.NewLevelDB("Simulation", dir)
defer func() {
db.Close()
os.RemoveAll(dir)
}()
app := NewSimApp(logger, db, nil, true, FlagPeriodValue, interBlockCacheOpt())
// Run randomized simulation
// TODO: parameterize numbers, save for a later PR
_, simParams, simErr := simulation.SimulateFromSeed(
b, os.Stdout, app.BaseApp, AppStateFn(app.Codec(), app.sm),
testAndRunTxs(app, config), app.ModuleAccountAddrs(), config,
)
// export state and params before the simulation error is checked
if config.ExportStatePath != "" {
if err := ExportStateToJSON(app, config.ExportStatePath); err != nil {
fmt.Println(err)
b.Fail()
}
}
if config.ExportParamsPath != "" {
if err := ExportParamsToJSON(simParams, config.ExportParamsPath); err != nil {
fmt.Println(err)
b.Fail()
}
}
if simErr != nil {
fmt.Println(simErr)
b.FailNow()
}
if config.Commit {
fmt.Println("\nGoLevelDB Stats")
fmt.Println(db.Stats()["leveldb.stats"])
fmt.Println("GoLevelDB cached block size", db.Stats()["leveldb.cachedblock"])
}
}
func BenchmarkInvariants(b *testing.B) {
logger := log.NewNopLogger()
config := NewConfigFromFlags()
config.AllInvariants = false
dir, _ := ioutil.TempDir("", "goleveldb-app-invariant-bench")
db, _ := sdk.NewLevelDB("simulation", dir)
defer func() {
db.Close()
os.RemoveAll(dir)
}()
app := NewSimApp(logger, db, nil, true, FlagPeriodValue, interBlockCacheOpt())
// 2. Run parameterized simulation (w/o invariants)
_, simParams, simErr := simulation.SimulateFromSeed(
b, ioutil.Discard, app.BaseApp, AppStateFn(app.Codec(), app.sm),
testAndRunTxs(app, config), app.ModuleAccountAddrs(), config,
)
// export state and params before the simulation error is checked
if config.ExportStatePath != "" {
if err := ExportStateToJSON(app, config.ExportStatePath); err != nil {
fmt.Println(err)
b.Fail()
}
}
if config.ExportParamsPath != "" {
if err := ExportParamsToJSON(simParams, config.ExportParamsPath); err != nil {
fmt.Println(err)
b.Fail()
}
}
if simErr != nil {
fmt.Println(simErr)
b.FailNow()
}
ctx := app.NewContext(true, abci.Header{Height: app.LastBlockHeight() + 1})
// 3. Benchmark each invariant separately
//
// NOTE: We use the crisis keeper as it has all the invariants registered with
// their respective metadata which makes it useful for testing/benchmarking.
for _, cr := range app.CrisisKeeper.Routes() {
b.Run(fmt.Sprintf("%s/%s", cr.ModuleName, cr.Route), func(b *testing.B) {
if res, stop := cr.Invar(ctx); stop {
fmt.Printf("broken invariant at block %d of %d\n%s", ctx.BlockHeight()-1, config.NumBlocks, res)
b.FailNow()
}
})
}
}

View File

@ -9,7 +9,6 @@ import (
"testing" "testing"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
abci "github.com/tendermint/tendermint/abci/types" abci "github.com/tendermint/tendermint/abci/types"
"github.com/tendermint/tendermint/libs/log" "github.com/tendermint/tendermint/libs/log"
dbm "github.com/tendermint/tm-db" dbm "github.com/tendermint/tm-db"
@ -292,56 +291,6 @@ func interBlockCacheOpt() func(*baseapp.BaseApp) {
return baseapp.SetInterBlockCache(store.NewCommitKVStoreCacheManager()) return baseapp.SetInterBlockCache(store.NewCommitKVStoreCacheManager())
} }
// Profile with:
// /usr/local/go/bin/go test -benchmem -run=^$ github.com/cosmos/cosmos-sdk/simapp -bench ^BenchmarkFullAppSimulation$ -Commit=true -cpuprofile cpu.out
func BenchmarkFullAppSimulation(b *testing.B) {
logger := log.NewNopLogger()
config := NewConfigFromFlags()
var db dbm.DB
dir, _ := ioutil.TempDir("", "goleveldb-app-sim")
db, _ = sdk.NewLevelDB("Simulation", dir)
defer func() {
db.Close()
os.RemoveAll(dir)
}()
app := NewSimApp(logger, db, nil, true, FlagPeriodValue, interBlockCacheOpt())
// Run randomized simulation
// TODO: parameterize numbers, save for a later PR
_, simParams, simErr := simulation.SimulateFromSeed(
b, os.Stdout, app.BaseApp, AppStateFn(app.Codec(), app.sm),
testAndRunTxs(app, config), app.ModuleAccountAddrs(), config,
)
// export state and params before the simulation error is checked
if config.ExportStatePath != "" {
if err := ExportStateToJSON(app, config.ExportStatePath); err != nil {
fmt.Println(err)
b.Fail()
}
}
if config.ExportParamsPath != "" {
if err := ExportParamsToJSON(simParams, config.ExportParamsPath); err != nil {
fmt.Println(err)
b.Fail()
}
}
if simErr != nil {
fmt.Println(simErr)
b.FailNow()
}
if config.Commit {
fmt.Println("\nGoLevelDB Stats")
fmt.Println(db.Stats()["leveldb.stats"])
fmt.Println("GoLevelDB cached block size", db.Stats()["leveldb.cachedblock"])
}
}
func TestFullAppSimulation(t *testing.T) { func TestFullAppSimulation(t *testing.T) {
if !FlagEnabledValue { if !FlagEnabledValue {
t.Skip("skipping application simulation") t.Skip("skipping application simulation")
@ -652,61 +601,3 @@ func TestAppStateDeterminism(t *testing.T) {
} }
} }
} }
func BenchmarkInvariants(b *testing.B) {
logger := log.NewNopLogger()
config := NewConfigFromFlags()
config.AllInvariants = false
dir, _ := ioutil.TempDir("", "goleveldb-app-invariant-bench")
db, _ := sdk.NewLevelDB("simulation", dir)
defer func() {
db.Close()
os.RemoveAll(dir)
}()
app := NewSimApp(logger, db, nil, true, FlagPeriodValue, interBlockCacheOpt())
// 2. Run parameterized simulation (w/o invariants)
_, simParams, simErr := simulation.SimulateFromSeed(
b, ioutil.Discard, app.BaseApp, AppStateFn(app.Codec(), app.sm),
testAndRunTxs(app, config), app.ModuleAccountAddrs(), config,
)
// export state and params before the simulation error is checked
if config.ExportStatePath != "" {
if err := ExportStateToJSON(app, config.ExportStatePath); err != nil {
fmt.Println(err)
b.Fail()
}
}
if config.ExportParamsPath != "" {
if err := ExportParamsToJSON(simParams, config.ExportParamsPath); err != nil {
fmt.Println(err)
b.Fail()
}
}
if simErr != nil {
fmt.Println(simErr)
b.FailNow()
}
ctx := app.NewContext(true, abci.Header{Height: app.LastBlockHeight() + 1})
// 3. Benchmark each invariant separately
//
// NOTE: We use the crisis keeper as it has all the invariants registered with
// their respective metadata which makes it useful for testing/benchmarking.
for _, cr := range app.CrisisKeeper.Routes() {
b.Run(fmt.Sprintf("%s/%s", cr.ModuleName, cr.Route), func(b *testing.B) {
if res, stop := cr.Invar(ctx); stop {
fmt.Printf("broken invariant at block %d of %d\n%s", ctx.BlockHeight()-1, config.NumBlocks, res)
b.FailNow()
}
})
}
}

View File

@ -2,10 +2,10 @@ package cli
import ( import (
"fmt" "fmt"
"sort"
"time" "time"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/tendermint/tendermint/types" "github.com/tendermint/tendermint/types"
@ -18,19 +18,40 @@ import (
v038 "github.com/cosmos/cosmos-sdk/x/genutil/legacy/v0_38" v038 "github.com/cosmos/cosmos-sdk/x/genutil/legacy/v0_38"
) )
// Allow applications to extend and modify the migration process.
//
// Ref: https://github.com/cosmos/cosmos-sdk/issues/5041
var migrationMap = extypes.MigrationMap{
"v0.36": v036.Migrate,
"v0.38": v038.Migrate,
}
const ( const (
flagGenesisTime = "genesis-time" flagGenesisTime = "genesis-time"
flagChainID = "chain-id" flagChainID = "chain-id"
) )
// Allow applications to extend and modify the migration process.
//
// Ref: https://github.com/cosmos/cosmos-sdk/issues/5041
var migrationMap = extypes.MigrationMap{
"v0.36": v036.Migrate,
"v0.38": v038.Migrate, // NOTE: v0.37 and v0.38 are genesis compatible
}
// GetMigrationCallback returns a MigrationCallback for a given version.
func GetMigrationCallback(version string) extypes.MigrationCallback {
return migrationMap[version]
}
// GetMigrationVersions get all migration version in a sorted slice.
func GetMigrationVersions() []string {
versions := make([]string, len(migrationMap))
var i int
for version := range migrationMap {
versions[i] = version
i++
}
sort.Strings(versions)
return versions
}
// MigrateGenesisCmd returns a command to execute genesis state migration.
// nolint: funlen
func MigrateGenesisCmd(_ *server.Context, cdc *codec.Codec) *cobra.Command { func MigrateGenesisCmd(_ *server.Context, cdc *codec.Codec) *cobra.Command {
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "migrate [target-version] [genesis-file]", Use: "migrate [target-version] [genesis-file]",
@ -42,6 +63,8 @@ $ %s migrate v0.36 /path/to/genesis.json --chain-id=cosmoshub-3 --genesis-time=2
`, version.ServerName), `, version.ServerName),
Args: cobra.ExactArgs(2), Args: cobra.ExactArgs(2),
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
var err error
target := args[0] target := args[0]
importGenesis := args[1] importGenesis := args[1]
@ -51,14 +74,22 @@ $ %s migrate v0.36 /path/to/genesis.json --chain-id=cosmoshub-3 --genesis-time=2
} }
var initialState extypes.AppMap var initialState extypes.AppMap
cdc.MustUnmarshalJSON(genDoc.AppState, &initialState) if err := cdc.UnmarshalJSON(genDoc.AppState, &initialState); err != nil {
return errors.Wrap(err, "failed to JSON unmarshal initial genesis state")
if migrationMap[target] == nil {
return fmt.Errorf("unknown migration function version: %s", target)
} }
newGenState := migrationMap[target](initialState) migrationFunc := GetMigrationCallback(target)
genDoc.AppState = cdc.MustMarshalJSON(newGenState) if migrationFunc == nil {
return fmt.Errorf("unknown migration function for version: %s", target)
}
// TODO: handler error from migrationFunc call
newGenState := migrationFunc(initialState)
genDoc.AppState, err = cdc.MarshalJSON(newGenState)
if err != nil {
return errors.Wrap(err, "failed to JSON marshal migrated genesis state")
}
genesisTime := cmd.Flag(flagGenesisTime).Value.String() genesisTime := cmd.Flag(flagGenesisTime).Value.String()
if genesisTime != "" { if genesisTime != "" {
@ -77,18 +108,23 @@ $ %s migrate v0.36 /path/to/genesis.json --chain-id=cosmoshub-3 --genesis-time=2
genDoc.ChainID = chainID genDoc.ChainID = chainID
} }
out, err := cdc.MarshalJSONIndent(genDoc, "", " ") bz, err := cdc.MarshalJSONIndent(genDoc, "", " ")
if err != nil { if err != nil {
return errors.Wrap(err, "failed to marshal genesis doc") return errors.Wrap(err, "failed to marshal genesis doc")
} }
fmt.Println(string(sdk.MustSortJSON(out))) sortedBz, err := sdk.SortJSON(bz)
if err != nil {
return errors.Wrap(err, "failed to sort JSON genesis doc")
}
fmt.Println(string(sortedBz))
return nil return nil
}, },
} }
cmd.Flags().String(flagGenesisTime, "", "Override genesis_time with this flag") cmd.Flags().String(flagGenesisTime, "", "override genesis_time with this flag")
cmd.Flags().String(flagChainID, "", "Override chain_id with this flag") cmd.Flags().String(flagChainID, "", "override chain_id with this flag")
return cmd return cmd
} }

View File

@ -30,6 +30,12 @@ func setupCmd(genesisTime string, chainID string) *cobra.Command {
return c return c
} }
func TestGetMigrationCallback(t *testing.T) {
for _, version := range GetMigrationVersions() {
require.NotNil(t, GetMigrationCallback(version))
}
}
func TestMigrateGenesis(t *testing.T) { func TestMigrateGenesis(t *testing.T) {
home, cleanup := tests.NewTestCaseDir(t) home, cleanup := tests.NewTestCaseDir(t)
viper.Set(cli.HomeFlag, home) viper.Set(cli.HomeFlag, home)

View File

@ -6,6 +6,8 @@ import (
v038auth "github.com/cosmos/cosmos-sdk/x/auth/legacy/v0_38" v038auth "github.com/cosmos/cosmos-sdk/x/auth/legacy/v0_38"
v036genaccounts "github.com/cosmos/cosmos-sdk/x/genaccounts/legacy/v0_36" v036genaccounts "github.com/cosmos/cosmos-sdk/x/genaccounts/legacy/v0_36"
"github.com/cosmos/cosmos-sdk/x/genutil" "github.com/cosmos/cosmos-sdk/x/genutil"
v036staking "github.com/cosmos/cosmos-sdk/x/staking/legacy/v0_36"
v038staking "github.com/cosmos/cosmos-sdk/x/staking/legacy/v0_38"
) )
// Migrate migrates exported state from v0.34 to a v0.36 genesis state. // Migrate migrates exported state from v0.34 to a v0.36 genesis state.
@ -35,5 +37,14 @@ func Migrate(appState genutil.AppMap) genutil.AppMap {
delete(appState, v036genaccounts.ModuleName) delete(appState, v036genaccounts.ModuleName)
} }
// migrate staking state
if appState[v036staking.ModuleName] != nil {
var stakingGenState v036staking.GenesisState
v036Codec.MustUnmarshalJSON(appState[v036staking.ModuleName], &stakingGenState)
delete(appState, v036staking.ModuleName) // delete old key in case the name changed
appState[v038staking.ModuleName] = v038Codec.MustMarshalJSON(v038staking.Migrate(stakingGenState))
}
return appState return appState
} }

View File

@ -0,0 +1,142 @@
package v038_test
import (
"testing"
v036auth "github.com/cosmos/cosmos-sdk/x/auth/legacy/v0_36"
v036genaccounts "github.com/cosmos/cosmos-sdk/x/genaccounts/legacy/v0_36"
"github.com/cosmos/cosmos-sdk/x/genutil"
v038 "github.com/cosmos/cosmos-sdk/x/genutil/legacy/v0_38"
v036staking "github.com/cosmos/cosmos-sdk/x/staking/legacy/v0_36"
"github.com/stretchr/testify/require"
)
var genAccountsState = []byte(`[
{
"account_number": "0",
"address": "cosmos1q7380u26f7ntke3facjmynajs4umlr329vr4ja",
"coins": [
{
"amount": "1000000000",
"denom": "node0token"
},
{
"amount": "400000198",
"denom": "stake"
}
],
"delegated_free": [],
"delegated_vesting": [],
"end_time": "0",
"module_name": "",
"module_permissions": [],
"original_vesting": [],
"sequence_number": "1",
"start_time": "0"
},
{
"account_number": "0",
"address": "cosmos1tygms3xhhs3yv487phx3dw4a95jn7t7lpm470r",
"coins": [],
"delegated_free": [],
"delegated_vesting": [],
"end_time": "0",
"module_name": "not_bonded_tokens_pool",
"module_permissions": [
"burner",
"staking"
],
"original_vesting": [],
"sequence_number": "0",
"start_time": "0"
},
{
"account_number": "0",
"address": "cosmos1m3h30wlvsf8llruxtpukdvsy0km2kum8g38c8q",
"coins": [],
"delegated_free": [],
"delegated_vesting": [],
"end_time": "0",
"module_name": "mint",
"module_permissions": [
"minter"
],
"original_vesting": [],
"sequence_number": "0",
"start_time": "0"
}
]`)
var genAuthState = []byte(`{
"params": {
"max_memo_characters": "256",
"sig_verify_cost_ed25519": "590",
"sig_verify_cost_secp256k1": "1000",
"tx_sig_limit": "7",
"tx_size_cost_per_byte": "10"
}
}`)
var genStakingState = []byte(`{
"delegations": [
{
"delegator_address": "cosmos1q7380u26f7ntke3facjmynajs4umlr329vr4ja",
"shares": "100000000.000000000000000000",
"validator_address": "cosmosvaloper1q7380u26f7ntke3facjmynajs4umlr32qchq7w"
}
],
"exported": true,
"last_total_power": "400",
"last_validator_powers": [
{
"Address": "cosmosvaloper1q7380u26f7ntke3facjmynajs4umlr32qchq7w",
"Power": "100"
}
],
"params": {
"bond_denom": "stake",
"max_entries": 7,
"max_validators": 100,
"unbonding_time": "259200000000000"
},
"redelegations": null,
"unbonding_delegations": null,
"validators": [
{
"commission": {
"commission_rates": {
"max_change_rate": "0.000000000000000000",
"max_rate": "0.000000000000000000",
"rate": "0.000000000000000000"
},
"update_time": "2019-09-24T23:11:22.9692177Z"
},
"consensus_pubkey": "cosmosvalconspub1zcjduepqygqrt0saxf76lhsmp56rx52j0acdxyjvcdkq3tqvwrsmmm0ke28q36kh9h",
"delegator_shares": "100000000.000000000000000000",
"description": {
"details": "",
"identity": "",
"moniker": "node0",
"website": ""
},
"jailed": false,
"min_self_delegation": "1",
"operator_address": "cosmosvaloper1q7380u26f7ntke3facjmynajs4umlr32qchq7w",
"status": 2,
"tokens": "100000000",
"unbonding_height": "0",
"unbonding_time": "1970-01-01T00:00:00Z"
}
]
}`)
func TestMigrate(t *testing.T) {
genesis := genutil.AppMap{
v036auth.ModuleName: genAuthState,
v036genaccounts.ModuleName: genAccountsState,
v036staking.ModuleName: genStakingState,
}
require.NotPanics(t, func() { v038.Migrate(genesis) })
}

View File

@ -9,11 +9,16 @@ import (
// DONTCOVER // DONTCOVER
type ( type (
// AppMap map modules names with their json raw representation // AppMap map modules names with their json raw representation.
AppMap map[string]json.RawMessage AppMap map[string]json.RawMessage
// MigrationCallback converts a genesis map from the previous version to the targeted one
// MigrationCallback converts a genesis map from the previous version to the
// targeted one.
//
// TODO: MigrationCallback should also return an error upon failure.
MigrationCallback func(AppMap) AppMap MigrationCallback func(AppMap) AppMap
// MigrationMap defines a mapping from a version to a MigrationCallback
// MigrationMap defines a mapping from a version to a MigrationCallback.
MigrationMap map[string]MigrationCallback MigrationMap map[string]MigrationCallback
) )

View File

@ -1,14 +1,14 @@
// DONTCOVER // DONTCOVER
// nolint // nolint
package v0_37 package v0_38
import ( import (
v036staking "github.com/cosmos/cosmos-sdk/x/staking/legacy/v0_36" v036staking "github.com/cosmos/cosmos-sdk/x/staking/legacy/v0_36"
) )
// Migrate accepts exported genesis state from v0.34 and migrates it to v0.36 // Migrate accepts exported genesis state from v0.36 or v0.37 and migrates it to
// genesis state. All entries are identical except for validator slashing events // v0.38 genesis state. All entries are identical except for validator descriptions
// which now include the period. // which now include a security contact.
func Migrate(oldGenState v036staking.GenesisState) GenesisState { func Migrate(oldGenState v036staking.GenesisState) GenesisState {
return NewGenesisState( return NewGenesisState(
oldGenState.Params, oldGenState.Params,

View File

@ -1,6 +1,6 @@
// DONTCOVER // DONTCOVER
// nolint // nolint
package v0_37 package v0_38
import ( import (
"time" "time"
@ -69,9 +69,7 @@ type (
) )
// NewDescription creates a new Description object // NewDescription creates a new Description object
func NewDescription(moniker, identity, website, func NewDescription(moniker, identity, website, securityContact, details string) Description {
securityContact, details string) Description {
return Description{ return Description{
Moniker: moniker, Moniker: moniker,
Identity: identity, Identity: identity,