Merge PR #3656: Broken-Invar Tx - aka. Crisis module

* beginning thinking on issue

* ...

* working

* working

* working fee pool distribution

* spek outline

* spec update

* gas refund calculations

* simulation saved to ~/.gaiad/simulations/

* lean simulation output

int

* cleanup bank simulation messages

* operation messges

int

* lint

* move simulation to its own module

* move simulation log code to log.go

* logger overhaul

int

* distribution comments

* fix compiling

* cleanup modifications to x/distribution/keeper/allocation.go

int

int

int

* gov bug

* result.IsOK() minimization

* importExport typo bug

* pending

* address @alexanderbez comments

* simple @cwgoes comments addressed

* event logging unified approach

* distr module name constant

* implementing

* compiles

* gaia integration

* proper constant fee removal

* crisis genesis

* go.sum update

* ...

* debugging

* fix sum errors

* missing err checks

* working implementing CLI

* remove query command

* crisis expected keepers in other modules

* crisis testing infrastructure

* working

* tests complete

* modify handler to still panic if not enough pool coins, docs working

* spec tags

* docs complete

* CL

* assert invariants on a blockly basis gaiad functionality

* gaiad CL

* transaction details in runtime invariance panic

* Apply suggestions from code review

Co-Authored-By: rigelrozanski <rigel.rozanski@gmail.com>

* sender tags

* @mossid suggestions

int

* @cwgoes comments final

* Apply suggestions from code review

Co-Authored-By: rigelrozanski <rigel.rozanski@gmail.com>

* bug seems fixed (#3998)

* delete unused line in zero height export bug
This commit is contained in:
frog power 4000 2019-03-28 19:27:47 -04:00 committed by GitHub
parent b9e6df3851
commit df4394185e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
48 changed files with 820 additions and 120 deletions

View File

@ -0,0 +1 @@
#2935 Optionally assert invariants on a blockly basis using `gaiad --assert-invariants-blockly`

View File

@ -0,0 +1 @@
#2935 New module Crisis which can test broken invariant with messages

View File

@ -220,7 +220,7 @@ func InitializeTestLCD(t *testing.T, nValidators int, initAddrs []sdk.AccAddress
privVal.Reset()
db := dbm.NewMemDB()
app := gapp.NewGaiaApp(logger, db, nil, true)
app := gapp.NewGaiaApp(logger, db, nil, true, false)
cdc = gapp.MakeCodec()
genesisFile := config.GenesisFile()
@ -299,6 +299,9 @@ func InitializeTestLCD(t *testing.T, nValidators int, initAddrs []sdk.AccAddress
genesisState.MintData.Minter.Inflation = inflationMin
genesisState.MintData.Params.InflationMin = inflationMin
// initialize crisis data
genesisState.CrisisData.ConstantFee = sdk.NewInt64Coin(sdk.DefaultBondDenom, 1000)
// double check inflation is set according to the minting boolean flag
if minting {
require.Equal(t, sdk.MustNewDecFromStr("15000.0"),

View File

@ -123,7 +123,9 @@ func EnrichWithGas(txBldr authtxb.TxBuilder, cliCtx context.CLIContext, msgs []s
// CalculateGas simulates the execution of a transaction and returns
// both the estimate obtained by the query and the adjusted amount.
func CalculateGas(queryFunc func(string, common.HexBytes) ([]byte, error), cdc *amino.Codec, txBytes []byte, adjustment float64) (estimate, adjusted uint64, err error) {
func CalculateGas(queryFunc func(string, common.HexBytes) ([]byte, error),
cdc *amino.Codec, txBytes []byte, adjustment float64) (estimate, adjusted uint64, err error) {
// run a simulation (via /app/simulate query) to
// estimate gas and update TxBuilder accordingly
rawRes, err := queryFunc("/app/simulate", txBytes)

View File

@ -19,6 +19,7 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/auth"
"github.com/cosmos/cosmos-sdk/x/bank"
"github.com/cosmos/cosmos-sdk/x/crisis"
distr "github.com/cosmos/cosmos-sdk/x/distribution"
"github.com/cosmos/cosmos-sdk/x/gov"
"github.com/cosmos/cosmos-sdk/x/mint"
@ -44,6 +45,8 @@ type GaiaApp struct {
*bam.BaseApp
cdc *codec.Codec
assertInvariantsBlockly bool
// keys to access the substores
keyMain *sdk.KVStoreKey
keyAccount *sdk.KVStoreKey
@ -67,11 +70,14 @@ type GaiaApp struct {
mintKeeper mint.Keeper
distrKeeper distr.Keeper
govKeeper gov.Keeper
crisisKeeper crisis.Keeper
paramsKeeper params.Keeper
}
// NewGaiaApp returns a reference to an initialized GaiaApp.
func NewGaiaApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest bool, baseAppOptions ...func(*bam.BaseApp)) *GaiaApp {
func NewGaiaApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest, assertInvariantsBlockly bool,
baseAppOptions ...func(*bam.BaseApp)) *GaiaApp {
cdc := MakeCodec()
bApp := bam.NewBaseApp(appName, logger, db, auth.DefaultTxDecoder(cdc), baseAppOptions...)
@ -143,6 +149,12 @@ func NewGaiaApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest b
app.paramsKeeper, app.paramsKeeper.Subspace(gov.DefaultParamspace), app.bankKeeper, &stakingKeeper,
gov.DefaultCodespace,
)
app.crisisKeeper = crisis.NewKeeper(
app.paramsKeeper.Subspace(crisis.DefaultParamspace),
app.distrKeeper,
app.bankKeeper,
app.feeCollectionKeeper,
)
// register the staking hooks
// NOTE: The stakingKeeper above is passed by reference, so that it can be
@ -151,6 +163,11 @@ func NewGaiaApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest b
NewStakingHooks(app.distrKeeper.Hooks(), app.slashingKeeper.Hooks()),
)
// register the crisis routes
bank.RegisterInvariants(&app.crisisKeeper, app.accountKeeper)
distr.RegisterInvariants(&app.crisisKeeper, app.distrKeeper, app.stakingKeeper)
staking.RegisterInvariants(&app.crisisKeeper, app.stakingKeeper, app.feeCollectionKeeper, app.distrKeeper, app.accountKeeper)
// register message routes
//
// TODO: Use standard bank router once transfers are enabled.
@ -159,7 +176,8 @@ func NewGaiaApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest b
AddRoute(staking.RouterKey, staking.NewHandler(app.stakingKeeper)).
AddRoute(distr.RouterKey, distr.NewHandler(app.distrKeeper)).
AddRoute(slashing.RouterKey, slashing.NewHandler(app.slashingKeeper)).
AddRoute(gov.RouterKey, gov.NewHandler(app.govKeeper))
AddRoute(gov.RouterKey, gov.NewHandler(app.govKeeper)).
AddRoute(crisis.RouterKey, crisis.NewHandler(app.crisisKeeper))
app.QueryRouter().
AddRoute(auth.QuerierRoute, auth.NewQuerier(app.accountKeeper)).
@ -197,6 +215,7 @@ func MakeCodec() *codec.Codec {
slashing.RegisterCodec(cdc)
gov.RegisterCodec(cdc)
auth.RegisterCodec(cdc)
crisis.RegisterCodec(cdc)
sdk.RegisterCodec(cdc)
codec.RegisterCrypto(cdc)
return cdc
@ -229,7 +248,9 @@ func (app *GaiaApp) EndBlocker(ctx sdk.Context, req abci.RequestEndBlock) abci.R
validatorUpdates, endBlockerTags := staking.EndBlocker(ctx, app.stakingKeeper)
tags = append(tags, endBlockerTags...)
app.assertRuntimeInvariants()
if app.assertInvariantsBlockly {
app.assertRuntimeInvariants()
}
return abci.ResponseEndBlock{
ValidatorUpdates: validatorUpdates,
@ -262,6 +283,7 @@ func (app *GaiaApp) initFromGenesisState(ctx sdk.Context, genesisState GenesisSt
bank.InitGenesis(ctx, app.bankKeeper, genesisState.BankData)
slashing.InitGenesis(ctx, app.slashingKeeper, genesisState.SlashingData, genesisState.StakingData.Validators.ToSDKValidators())
gov.InitGenesis(ctx, app.govKeeper, genesisState.GovData)
crisis.InitGenesis(ctx, app.crisisKeeper, genesisState.CrisisData)
mint.InitGenesis(ctx, app.mintKeeper, genesisState.MintData)
// validate genesis state

View File

@ -5,6 +5,7 @@ import (
"testing"
"github.com/cosmos/cosmos-sdk/x/bank"
"github.com/cosmos/cosmos-sdk/x/crisis"
"github.com/stretchr/testify/require"
"github.com/tendermint/tendermint/libs/db"
@ -35,6 +36,7 @@ func setGenesis(gapp *GaiaApp, accs ...*auth.BaseAccount) error {
mint.DefaultGenesisState(),
distr.DefaultGenesisState(),
gov.DefaultGenesisState(),
crisis.DefaultGenesisState(),
slashing.DefaultGenesisState(),
)
@ -53,11 +55,11 @@ func setGenesis(gapp *GaiaApp, accs ...*auth.BaseAccount) error {
func TestGaiadExport(t *testing.T) {
db := db.NewMemDB()
gapp := NewGaiaApp(log.NewTMLogger(log.NewSyncWriter(os.Stdout)), db, nil, true)
gapp := NewGaiaApp(log.NewTMLogger(log.NewSyncWriter(os.Stdout)), db, nil, true, false)
setGenesis(gapp)
// Making a new app object with the db, so that initchain hasn't been called
newGapp := NewGaiaApp(log.NewTMLogger(log.NewSyncWriter(os.Stdout)), db, nil, true)
newGapp := NewGaiaApp(log.NewTMLogger(log.NewSyncWriter(os.Stdout)), db, nil, true, false)
_, _, err := newGapp.ExportAppStateAndValidators(false, []string{})
require.NoError(t, err, "ExportAppStateAndValidators should not have an error")
}

View File

@ -11,6 +11,7 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/auth"
"github.com/cosmos/cosmos-sdk/x/bank"
"github.com/cosmos/cosmos-sdk/x/crisis"
distr "github.com/cosmos/cosmos-sdk/x/distribution"
"github.com/cosmos/cosmos-sdk/x/gov"
"github.com/cosmos/cosmos-sdk/x/mint"
@ -46,6 +47,7 @@ func (app *GaiaApp) ExportAppStateAndValidators(forZeroHeight bool, jailWhiteLis
mint.ExportGenesis(ctx, app.mintKeeper),
distr.ExportGenesis(ctx, app.distrKeeper),
gov.ExportGenesis(ctx, app.govKeeper),
crisis.ExportGenesis(ctx, app.crisisKeeper),
slashing.ExportGenesis(ctx, app.slashingKeeper),
)
appState, err = codec.MarshalJSONIndent(app.cdc, genState)
@ -118,6 +120,7 @@ func (app *GaiaApp) prepForZeroHeightGenesis(ctx sdk.Context, jailWhiteList []st
// reinitialize all delegations
for _, del := range dels {
app.distrKeeper.Hooks().BeforeDelegationCreated(ctx, del.DelegatorAddress, del.ValidatorAddress)
app.distrKeeper.Hooks().AfterDelegationModified(ctx, del.DelegatorAddress, del.ValidatorAddress)
}
// reset context height

View File

@ -17,6 +17,7 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/auth"
"github.com/cosmos/cosmos-sdk/x/bank"
"github.com/cosmos/cosmos-sdk/x/crisis"
distr "github.com/cosmos/cosmos-sdk/x/distribution"
"github.com/cosmos/cosmos-sdk/x/gov"
"github.com/cosmos/cosmos-sdk/x/mint"
@ -39,6 +40,7 @@ type GenesisState struct {
MintData mint.GenesisState `json:"mint"`
DistrData distr.GenesisState `json:"distr"`
GovData gov.GenesisState `json:"gov"`
CrisisData crisis.GenesisState `json:"crisis"`
SlashingData slashing.GenesisState `json:"slashing"`
GenTxs []json.RawMessage `json:"gentxs"`
}
@ -46,7 +48,7 @@ type GenesisState struct {
func NewGenesisState(accounts []GenesisAccount, authData auth.GenesisState,
bankData bank.GenesisState,
stakingData staking.GenesisState, mintData mint.GenesisState,
distrData distr.GenesisState, govData gov.GenesisState,
distrData distr.GenesisState, govData gov.GenesisState, crisisData crisis.GenesisState,
slashingData slashing.GenesisState) GenesisState {
return GenesisState{
@ -57,6 +59,7 @@ func NewGenesisState(accounts []GenesisAccount, authData auth.GenesisState,
MintData: mintData,
DistrData: distrData,
GovData: govData,
CrisisData: crisisData,
SlashingData: slashingData,
}
}
@ -209,6 +212,7 @@ func NewDefaultGenesisState() GenesisState {
MintData: mint.DefaultGenesisState(),
DistrData: distr.DefaultGenesisState(),
GovData: gov.DefaultGenesisState(),
CrisisData: crisis.DefaultGenesisState(),
SlashingData: slashing.DefaultGenesisState(),
GenTxs: nil,
}
@ -246,6 +250,9 @@ func GaiaValidateGenesisState(genesisState GenesisState) error {
if err := gov.ValidateGenesis(genesisState.GovData); err != nil {
return err
}
if err := crisis.ValidateGenesis(genesisState.CrisisData); err != nil {
return err
}
return slashing.ValidateGenesis(genesisState.SlashingData)
}

View File

@ -7,20 +7,8 @@ import (
abci "github.com/tendermint/tendermint/abci/types"
sdk "github.com/cosmos/cosmos-sdk/types"
banksim "github.com/cosmos/cosmos-sdk/x/bank/simulation"
distrsim "github.com/cosmos/cosmos-sdk/x/distribution/simulation"
stakingsim "github.com/cosmos/cosmos-sdk/x/staking/simulation"
)
func (app *GaiaApp) runtimeInvariants() []sdk.Invariant {
return []sdk.Invariant{
banksim.NonnegativeBalanceInvariant(app.accountKeeper),
distrsim.NonNegativeOutstandingInvariant(app.distrKeeper),
stakingsim.SupplyInvariants(app.stakingKeeper, app.feeCollectionKeeper, app.distrKeeper, app.accountKeeper),
stakingsim.NonNegativePowerInvariant(app.stakingKeeper),
}
}
func (app *GaiaApp) assertRuntimeInvariants() {
ctx := app.NewContext(false, abci.Header{Height: app.LastBlockHeight() + 1})
app.assertRuntimeInvariantsOnContext(ctx)
@ -28,10 +16,12 @@ func (app *GaiaApp) assertRuntimeInvariants() {
func (app *GaiaApp) assertRuntimeInvariantsOnContext(ctx sdk.Context) {
start := time.Now()
invariants := app.runtimeInvariants()
for _, inv := range invariants {
if err := inv(ctx); err != nil {
panic(fmt.Errorf("invariant broken: %s", err))
invarRoutes := app.crisisKeeper.Routes()
for _, ir := range invarRoutes {
if err := ir.Invar(ctx); err != nil {
panic(fmt.Errorf("invariant broken: %s\n"+
"\tCRITICAL please submit the following transaction:\n"+
"\t\t gaiacli tx crisis invariant-broken %v %v", err, ir.ModuleName, ir.Route))
}
}
end := time.Now()

View File

@ -294,12 +294,10 @@ func testAndRunTxs(app *GaiaApp) []simulation.WeightedOperation {
func invariants(app *GaiaApp) []sdk.Invariant {
return []sdk.Invariant{
simulation.PeriodicInvariant(banksim.NonnegativeBalanceInvariant(app.accountKeeper), period, 0),
simulation.PeriodicInvariant(govsim.AllInvariants(), period, 0),
simulation.PeriodicInvariant(distrsim.AllInvariants(app.distrKeeper, app.stakingKeeper), period, 0),
simulation.PeriodicInvariant(stakingsim.AllInvariants(app.stakingKeeper, app.feeCollectionKeeper,
simulation.PeriodicInvariant(bank.NonnegativeBalanceInvariant(app.accountKeeper), period, 0),
simulation.PeriodicInvariant(distr.AllInvariants(app.distrKeeper, app.stakingKeeper), period, 0),
simulation.PeriodicInvariant(staking.AllInvariants(app.stakingKeeper, app.feeCollectionKeeper,
app.distrKeeper, app.accountKeeper), period, 0),
simulation.PeriodicInvariant(slashingsim.AllInvariants(), period, 0),
}
}
@ -321,7 +319,7 @@ func BenchmarkFullGaiaSimulation(b *testing.B) {
db.Close()
os.RemoveAll(dir)
}()
app := NewGaiaApp(logger, db, nil, true)
app := NewGaiaApp(logger, db, nil, true, false)
// Run randomized simulation
// TODO parameterize numbers, save for a later PR
@ -356,7 +354,7 @@ func TestFullGaiaSimulation(t *testing.T) {
db.Close()
os.RemoveAll(dir)
}()
app := NewGaiaApp(logger, db, nil, true, fauxMerkleModeOpt)
app := NewGaiaApp(logger, db, nil, true, false, fauxMerkleModeOpt)
require.Equal(t, "GaiaApp", app.Name())
// Run randomized simulation
@ -390,7 +388,7 @@ func TestGaiaImportExport(t *testing.T) {
db.Close()
os.RemoveAll(dir)
}()
app := NewGaiaApp(logger, db, nil, true, fauxMerkleModeOpt)
app := NewGaiaApp(logger, db, nil, true, false, fauxMerkleModeOpt)
require.Equal(t, "GaiaApp", app.Name())
// Run randomized simulation
@ -417,7 +415,7 @@ func TestGaiaImportExport(t *testing.T) {
newDB.Close()
os.RemoveAll(newDir)
}()
newApp := NewGaiaApp(log.NewNopLogger(), newDB, nil, true, fauxMerkleModeOpt)
newApp := NewGaiaApp(log.NewNopLogger(), newDB, nil, true, false, fauxMerkleModeOpt)
require.Equal(t, "GaiaApp", newApp.Name())
var genesisState GenesisState
err = app.cdc.UnmarshalJSON(appState, &genesisState)
@ -480,7 +478,7 @@ func TestGaiaSimulationAfterImport(t *testing.T) {
db.Close()
os.RemoveAll(dir)
}()
app := NewGaiaApp(logger, db, nil, true, fauxMerkleModeOpt)
app := NewGaiaApp(logger, db, nil, true, false, fauxMerkleModeOpt)
require.Equal(t, "GaiaApp", app.Name())
// Run randomized simulation
@ -516,7 +514,7 @@ func TestGaiaSimulationAfterImport(t *testing.T) {
newDB.Close()
os.RemoveAll(newDir)
}()
newApp := NewGaiaApp(log.NewNopLogger(), newDB, nil, true, fauxMerkleModeOpt)
newApp := NewGaiaApp(log.NewNopLogger(), newDB, nil, true, false, fauxMerkleModeOpt)
require.Equal(t, "GaiaApp", newApp.Name())
newApp.InitChain(abci.RequestInitChain{
AppStateBytes: appState,
@ -544,7 +542,7 @@ func TestAppStateDeterminism(t *testing.T) {
for j := 0; j < numTimesToRunPerSeed; j++ {
logger := log.NewNopLogger()
db := dbm.NewMemDB()
app := NewGaiaApp(logger, db, nil, true)
app := NewGaiaApp(logger, db, nil, true, false)
// Run randomized simulation
simulation.SimulateFromSeed(

View File

@ -35,6 +35,7 @@ import (
authcmd "github.com/cosmos/cosmos-sdk/x/auth/client/cli"
bankcmd "github.com/cosmos/cosmos-sdk/x/bank/client/cli"
crisisclient "github.com/cosmos/cosmos-sdk/x/crisis/client"
distcmd "github.com/cosmos/cosmos-sdk/x/distribution"
distClient "github.com/cosmos/cosmos-sdk/x/distribution/client"
govClient "github.com/cosmos/cosmos-sdk/x/gov/client"
@ -69,6 +70,7 @@ func main() {
distClient.NewModuleClient(distcmd.StoreKey, cdc),
stakingClient.NewModuleClient(st.StoreKey, cdc),
slashingClient.NewModuleClient(sl.StoreKey, cdc),
crisisclient.NewModuleClient(sl.StoreKey, cdc),
}
rootCmd := &cobra.Command{
@ -124,7 +126,10 @@ func queryCmd(cdc *amino.Codec, mc []sdk.ModuleClients) *cobra.Command {
)
for _, m := range mc {
queryCmd.AddCommand(m.GetQueryCmd())
mQueryCmd := m.GetQueryCmd()
if mQueryCmd != nil {
queryCmd.AddCommand(mQueryCmd)
}
}
return queryCmd

View File

@ -22,6 +22,11 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types"
)
// gaiad custom flags
const flagAssertInvariantsBlockly = "assert-invariants-blockly"
var assertInvariantsBlockly bool
func main() {
cdc := app.MakeCodec()
@ -50,6 +55,8 @@ func main() {
// prepare and add flags
executor := cli.PrepareBaseCmd(rootCmd, "GA", app.DefaultNodeHome)
rootCmd.Flags().BoolVar(&assertInvariantsBlockly, flagAssertInvariantsBlockly,
false, "Assert registered invariants on a blockly basis")
err := executor.Execute()
if err != nil {
// handle with #870
@ -59,7 +66,7 @@ func main() {
func newApp(logger log.Logger, db dbm.DB, traceStore io.Writer) abci.Application {
return app.NewGaiaApp(
logger, db, traceStore, true,
logger, db, traceStore, true, assertInvariantsBlockly,
baseapp.SetPruning(store.NewPruningOptionsFromString(viper.GetString("pruning"))),
baseapp.SetMinGasPrices(viper.GetString(server.FlagMinGasPrices)),
)
@ -68,14 +75,15 @@ func newApp(logger log.Logger, db dbm.DB, traceStore io.Writer) abci.Application
func exportAppStateAndTMValidators(
logger log.Logger, db dbm.DB, traceStore io.Writer, height int64, forZeroHeight bool, jailWhiteList []string,
) (json.RawMessage, []tmtypes.GenesisValidator, error) {
if height != -1 {
gApp := app.NewGaiaApp(logger, db, traceStore, false)
gApp := app.NewGaiaApp(logger, db, traceStore, false, false)
err := gApp.LoadHeight(height)
if err != nil {
return nil, nil, err
}
return gApp.ExportAppStateAndValidators(forZeroHeight, jailWhiteList)
}
gApp := app.NewGaiaApp(logger, db, traceStore, true)
gApp := app.NewGaiaApp(logger, db, traceStore, true, false)
return gApp.ExportAppStateAndValidators(forZeroHeight, jailWhiteList)
}

View File

@ -107,7 +107,7 @@ func run(rootDir string) {
// Application
fmt.Println("Creating application")
myapp := app.NewGaiaApp(
ctx.Logger, appDB, traceStoreWriter, true,
ctx.Logger, appDB, traceStoreWriter, true, true,
baseapp.SetPruning(store.PruneEverything), // nothing
)

View File

@ -0,0 +1,14 @@
# State
## ConstantFee
Due to the anticipated large gas cost requirement to verify an invariant (and
potential to exceed the maximum allowable block gas limit) a constant fee is
used instead of the standard gas consumption method. The constant fee is
intended to be larger than the anticipated gas cost of running the invariant
with the standard gas consumption method.
The ConstantFee param is held in the global params store.
- Params: `mint/params -> amino(sdk.Coin)`

View File

@ -0,0 +1,25 @@
# Messages
In this section we describe the processing of the crisis messages and the
corresponding updates to the state.
## MsgVerifyInvariant
Blockchain invariants can be checked using the `MsgVerifyInvariant` message.
```golang
type MsgVerifyInvariant struct {
Sender sdk.AccAddress
InvariantRoute string
}
```
This message is expected to fail if:
- the sender does not have enough coins for the constant fee
- the invariant route is not registered
This message checks the invariant provided, and if the invariant is broken it
panics, halting the blockchain. If the invariant is broken, the constant fee is
never deducted as the transaction is never committed to a block (equivalent to
being refunded). However, if the invariant is not broken, the constant fee will
not be refunded.

View File

@ -0,0 +1,13 @@
# Tags
The crisis module emits the following events/tags:
## Handlers
### MsgVerifyInvariance
| Key | Value |
|-----------|---------------------|
| action | verify_invariant |
| sender | {message-sender} |
| invariant | {invariant-route} |

View File

@ -0,0 +1,16 @@
# Crisis
## Overview
The crisis module halts the blockchain under the circumstance that a blockchain
invariant is broken. Invariants can be registered with the application during the
application initialization process.
## Contents
1. **[State](01_state.md)**
- [ConstantFee](01_state.md#constantfee)
2. **[Messages](02_messages.md)**
- [MsgVerifyInvariant](02_messages.md#msgverifyinvariant)
3. **[Tags](03_tags.md)**
- [Handlers](03_tags.md#handlers)

View File

@ -150,6 +150,15 @@ func (fee StdFee) Bytes() []byte {
return bz
}
// GasPrices returns the gas prices for a StdFee.
//
// NOTE: The gas prices returned are not the true gas prices that were
// originally part of the submitted transaction because the fee is computed
// as fee = ceil(gasWanted * gasPrices).
func (fee StdFee) GasPrices() sdk.DecCoins {
return sdk.NewDecCoins(fee.Amount).QuoDec(sdk.NewDec(int64(fee.Gas)))
}
//__________________________________________________________
// StdSignDoc is replay-prevention structure.

View File

@ -0,0 +1,10 @@
package bank
import (
sdk "github.com/cosmos/cosmos-sdk/types"
)
// expected crisis keeper
type CrisisKeeper interface {
RegisterRoute(moduleName, route string, invar sdk.Invariant)
}

View File

@ -1,4 +1,4 @@
package simulation
package bank
import (
"errors"
@ -8,6 +8,12 @@ import (
"github.com/cosmos/cosmos-sdk/x/auth"
)
// register bank invariants
func RegisterInvariants(c CrisisKeeper, ak auth.AccountKeeper) {
c.RegisterRoute("bank", "nonnegative-outstanding",
NonnegativeBalanceInvariant(ak))
}
// NonnegativeBalanceInvariant checks that all accounts in the application have non-negative balances
func NonnegativeBalanceInvariant(ak auth.AccountKeeper) sdk.Invariant {
return func(ctx sdk.Context) error {

33
x/crisis/client/cli/tx.go Normal file
View File

@ -0,0 +1,33 @@
// nolint
package cli
import (
"github.com/spf13/cobra"
"github.com/cosmos/cosmos-sdk/client/context"
"github.com/cosmos/cosmos-sdk/client/utils"
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
authtxb "github.com/cosmos/cosmos-sdk/x/auth/client/txbuilder"
"github.com/cosmos/cosmos-sdk/x/crisis"
)
// command to replace a delegator's withdrawal address
func GetCmdInvariantBroken(cdc *codec.Codec) *cobra.Command {
cmd := &cobra.Command{
Use: "invariant-broken [module-name] [invariant-route]",
Short: "submit proof that an invariant broken to halt the chain",
Args: cobra.ExactArgs(2),
RunE: func(cmd *cobra.Command, args []string) error {
txBldr := authtxb.NewTxBuilderFromCLI().WithTxEncoder(utils.GetTxEncoder(cdc))
cliCtx := context.NewCLIContext().WithCodec(cdc).WithAccountDecoder(cdc)
senderAddr := cliCtx.GetFromAddress()
moduleName, route := args[0], args[1]
msg := crisis.NewMsgVerifyInvariant(senderAddr, moduleName, route)
return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg}, false)
},
}
return cmd
}

View File

@ -0,0 +1,42 @@
package client
import (
"github.com/spf13/cobra"
amino "github.com/tendermint/go-amino"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/x/crisis"
"github.com/cosmos/cosmos-sdk/x/crisis/client/cli"
)
// ModuleClient exports all client functionality from this module
type ModuleClient struct {
storeKey string
cdc *amino.Codec
}
// NewModuleClient creates a new ModuleClient object
func NewModuleClient(storeKey string, cdc *amino.Codec) ModuleClient {
return ModuleClient{
storeKey: storeKey,
cdc: cdc,
}
}
// GetQueryCmd returns the cli query commands for this module
func (ModuleClient) GetQueryCmd() *cobra.Command {
return nil
}
// GetTxCmd returns the transaction commands for this module
func (mc ModuleClient) GetTxCmd() *cobra.Command {
txCmd := &cobra.Command{
Use: crisis.ModuleName,
Short: "crisis transactions subcommands",
}
txCmd.AddCommand(client.PostCommands(
cli.GetCmdInvariantBroken(mc.cdc),
)...)
return txCmd
}

20
x/crisis/codec.go Normal file
View File

@ -0,0 +1,20 @@
package crisis
import (
"github.com/cosmos/cosmos-sdk/codec"
)
// Register concrete types on codec codec
func RegisterCodec(cdc *codec.Codec) {
cdc.RegisterConcrete(MsgVerifyInvariant{}, "cosmos-sdk/MsgVerifyInvariant", nil)
}
// generic sealed codec to be used throughout module
var MsgCdc *codec.Codec
func init() {
cdc := codec.New()
RegisterCodec(cdc)
codec.RegisterCrypto(cdc)
MsgCdc = cdc.Seal()
}

23
x/crisis/errors.go Normal file
View File

@ -0,0 +1,23 @@
package crisis
import (
sdk "github.com/cosmos/cosmos-sdk/types"
)
const (
// default codespace for crisis module
DefaultCodespace sdk.CodespaceType = ModuleName
// CodeInvalidInput is the codetype for invalid input for the crisis module
CodeInvalidInput sdk.CodeType = 103
)
// ErrNilSender - no sender provided for the input
func ErrNilSender(codespace sdk.CodespaceType) sdk.Error {
return sdk.NewError(codespace, CodeInvalidInput, "sender address is nil")
}
// ErrUnknownInvariant - unknown invariant provided
func ErrUnknownInvariant(codespace sdk.CodespaceType) sdk.Error {
return sdk.NewError(codespace, CodeInvalidInput, "unknown invariant")
}

View File

@ -0,0 +1,20 @@
package crisis
import (
sdk "github.com/cosmos/cosmos-sdk/types"
)
// expected bank keeper
type DistrKeeper interface {
DistributeFeePool(ctx sdk.Context, amount sdk.Coins, receiveAddr sdk.AccAddress) sdk.Error
}
// expected fee collection keeper
type FeeCollectionKeeper interface {
AddCollectedFees(ctx sdk.Context, coins sdk.Coins) sdk.Coins
}
// expected bank keeper
type BankKeeper interface {
SubtractCoins(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins) (sdk.Coins, sdk.Tags, sdk.Error)
}

40
x/crisis/genesis.go Normal file
View File

@ -0,0 +1,40 @@
package crisis
import (
sdk "github.com/cosmos/cosmos-sdk/types"
)
// GenesisState - crisis genesis state
type GenesisState struct {
ConstantFee sdk.Coin `json:"constant_fee"`
}
// NewGenesisState creates a new GenesisState object
func NewGenesisState(constantFee sdk.Coin) GenesisState {
return GenesisState{
ConstantFee: constantFee,
}
}
// DefaultGenesisState creates a default GenesisState object
func DefaultGenesisState() GenesisState {
return GenesisState{
ConstantFee: sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(1000)),
}
}
// new crisis genesis
func InitGenesis(ctx sdk.Context, keeper Keeper, data GenesisState) {
keeper.SetConstantFee(ctx, data.ConstantFee)
}
// ExportGenesis returns a GenesisState for a given context and keeper.
func ExportGenesis(ctx sdk.Context, keeper Keeper) GenesisState {
constantFee := keeper.GetConstantFee(ctx)
return NewGenesisState(constantFee)
}
// ValidateGenesis - placeholder function
func ValidateGenesis(data GenesisState) error {
return nil
}

80
x/crisis/handler.go Normal file
View File

@ -0,0 +1,80 @@
package crisis
import (
sdk "github.com/cosmos/cosmos-sdk/types"
)
// ModuleName is the module name for this module
const (
ModuleName = "crisis"
RouterKey = ModuleName
)
func NewHandler(k Keeper) sdk.Handler {
return func(ctx sdk.Context, msg sdk.Msg) sdk.Result {
switch msg := msg.(type) {
case MsgVerifyInvariant:
return handleMsgVerifyInvariant(ctx, msg, k)
default:
return sdk.ErrTxDecode("invalid message parse in crisis module").Result()
}
}
}
func handleMsgVerifyInvariant(ctx sdk.Context, msg MsgVerifyInvariant, k Keeper) sdk.Result {
// remove the constant fee
constantFee := sdk.NewCoins(k.GetConstantFee(ctx))
_, _, err := k.bankKeeper.SubtractCoins(ctx, msg.Sender, constantFee)
if err != nil {
return err.Result()
}
_ = k.feeCollectionKeeper.AddCollectedFees(ctx, constantFee)
// use a cached context to avoid gas costs during invariants
cacheCtx, _ := ctx.CacheContext()
found := false
var invarianceErr error
msgFullRoute := msg.FullInvariantRoute()
for _, invarRoute := range k.routes {
if invarRoute.FullRoute() == msgFullRoute {
invarianceErr = invarRoute.Invar(cacheCtx)
found = true
break
}
}
if !found {
return ErrUnknownInvariant(DefaultCodespace).Result()
}
if invarianceErr != nil {
// NOTE currently, because the chain halts here, this transaction will never be included
// in the blockchain thus the constant fee will have never been deducted. Thus no
// refund is required.
// TODO uncomment the following code block with implementation of the circuit breaker
//// refund constant fee
//err := k.distrKeeper.DistributeFeePool(ctx, constantFee, msg.Sender)
//if err != nil {
//// if there are insufficient coins to refund, log the error,
//// but still halt the chain.
//logger := ctx.Logger().With("module", "x/crisis")
//logger.Error(fmt.Sprintf(
//"WARNING: insufficient funds to allocate to sender from fee pool, err: %s", err))
//}
// TODO replace with circuit breaker
panic(invarianceErr)
}
tags := sdk.NewTags(
"sender", msg.Sender.String(),
"invariant", msg.InvariantRoute,
)
return sdk.Result{
Tags: tags,
}
}

100
x/crisis/handler_test.go Normal file
View File

@ -0,0 +1,100 @@
package crisis
import (
"errors"
"fmt"
"testing"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/auth"
distr "github.com/cosmos/cosmos-sdk/x/distribution"
"github.com/stretchr/testify/require"
)
var (
testModuleName = "dummy"
dummyRouteWhichPasses = NewInvarRoute(testModuleName, "which-passes", func(_ sdk.Context) error { return nil })
dummyRouteWhichFails = NewInvarRoute(testModuleName, "which-fails", func(_ sdk.Context) error { return errors.New("whoops") })
addrs = distr.TestAddrs
)
func CreateTestInput(t *testing.T) (sdk.Context, Keeper, auth.AccountKeeper, distr.Keeper) {
communityTax := sdk.NewDecWithPrec(2, 2)
ctx, accKeeper, bankKeeper, distrKeeper, _, feeCollectionKeeper, paramsKeeper :=
distr.CreateTestInputAdvanced(t, false, 10, communityTax)
paramSpace := paramsKeeper.Subspace(DefaultParamspace)
crisisKeeper := NewKeeper(paramSpace, distrKeeper, bankKeeper, feeCollectionKeeper)
constantFee := sdk.NewInt64Coin("stake", 10000000)
crisisKeeper.SetConstantFee(ctx, constantFee)
crisisKeeper.RegisterRoute(testModuleName, dummyRouteWhichPasses.Route, dummyRouteWhichPasses.Invar)
crisisKeeper.RegisterRoute(testModuleName, dummyRouteWhichFails.Route, dummyRouteWhichFails.Invar)
// set the community pool to pay back the constant fee
feePool := distr.InitialFeePool()
feePool.CommunityPool = sdk.NewDecCoins(sdk.NewCoins(constantFee))
distrKeeper.SetFeePool(ctx, feePool)
return ctx, crisisKeeper, accKeeper, distrKeeper
}
//____________________________________________________________________________
func TestHandleMsgVerifyInvariantWithNotEnoughSenderCoins(t *testing.T) {
ctx, crisisKeeper, accKeeper, _ := CreateTestInput(t)
sender := addrs[0]
coin := accKeeper.GetAccount(ctx, sender).GetCoins()[0]
excessCoins := sdk.NewCoin(coin.Denom, coin.Amount.AddRaw(1))
crisisKeeper.SetConstantFee(ctx, excessCoins)
msg := NewMsgVerifyInvariant(sender, testModuleName, dummyRouteWhichPasses.Route)
res := handleMsgVerifyInvariant(ctx, msg, crisisKeeper)
require.False(t, res.IsOK())
}
func TestHandleMsgVerifyInvariantWithBadInvariant(t *testing.T) {
ctx, crisisKeeper, _, _ := CreateTestInput(t)
sender := addrs[0]
msg := NewMsgVerifyInvariant(sender, testModuleName, "route-that-doesnt-exist")
res := handleMsgVerifyInvariant(ctx, msg, crisisKeeper)
require.False(t, res.IsOK())
}
func TestHandleMsgVerifyInvariantWithInvariantBroken(t *testing.T) {
ctx, crisisKeeper, _, _ := CreateTestInput(t)
sender := addrs[0]
msg := NewMsgVerifyInvariant(sender, testModuleName, dummyRouteWhichFails.Route)
var res sdk.Result
require.Panics(t, func() {
res = handleMsgVerifyInvariant(ctx, msg, crisisKeeper)
}, fmt.Sprintf("%v", res))
}
func TestHandleMsgVerifyInvariantWithInvariantBrokenAndNotEnoughPoolCoins(t *testing.T) {
ctx, crisisKeeper, _, distrKeeper := CreateTestInput(t)
sender := addrs[0]
// set the community pool to empty
feePool := distrKeeper.GetFeePool(ctx)
feePool.CommunityPool = sdk.DecCoins{}
distrKeeper.SetFeePool(ctx, feePool)
msg := NewMsgVerifyInvariant(sender, testModuleName, dummyRouteWhichFails.Route)
var res sdk.Result
require.Panics(t, func() {
res = handleMsgVerifyInvariant(ctx, msg, crisisKeeper)
}, fmt.Sprintf("%v", res))
}
func TestHandleMsgVerifyInvariantWithInvariantNotBroken(t *testing.T) {
ctx, crisisKeeper, _, _ := CreateTestInput(t)
sender := addrs[0]
msg := NewMsgVerifyInvariant(sender, testModuleName, dummyRouteWhichPasses.Route)
res := handleMsgVerifyInvariant(ctx, msg, crisisKeeper)
require.True(t, res.IsOK())
}

41
x/crisis/keeper.go Normal file
View File

@ -0,0 +1,41 @@
package crisis
import (
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/params"
)
// Keeper - crisis keeper
type Keeper struct {
routes []InvarRoute
paramSpace params.Subspace
distrKeeper DistrKeeper
bankKeeper BankKeeper
feeCollectionKeeper FeeCollectionKeeper
}
// NewKeeper creates a new Keeper object
func NewKeeper(paramSpace params.Subspace,
distrKeeper DistrKeeper, bankKeeper BankKeeper,
feeCollectionKeeper FeeCollectionKeeper) Keeper {
return Keeper{
routes: []InvarRoute{},
paramSpace: paramSpace.WithKeyTable(ParamKeyTable()),
distrKeeper: distrKeeper,
bankKeeper: bankKeeper,
feeCollectionKeeper: feeCollectionKeeper,
}
}
// register routes for the
func (k *Keeper) RegisterRoute(moduleName, route string, invar sdk.Invariant) {
invarRoute := NewInvarRoute(moduleName, route, invar)
k.routes = append(k.routes, invarRoute)
}
// Routes - return the keeper's invariant routes
func (k Keeper) Routes() []InvarRoute {
return k.routes
}

52
x/crisis/msg.go Normal file
View File

@ -0,0 +1,52 @@
package crisis
import (
sdk "github.com/cosmos/cosmos-sdk/types"
)
// MsgVerifyInvariant - message struct to verify a particular invariance
type MsgVerifyInvariant struct {
Sender sdk.AccAddress `json:"sender"`
InvariantModuleName string `json:"invariant_module_name"`
InvariantRoute string `json:"invariant_route"`
}
// ensure Msg interface compliance at compile time
var _ sdk.Msg = &MsgVerifyInvariant{}
// NewMsgVerifyInvariant creates a new MsgVerifyInvariant object
func NewMsgVerifyInvariant(sender sdk.AccAddress, invariantModuleName,
invariantRoute string) MsgVerifyInvariant {
return MsgVerifyInvariant{
Sender: sender,
InvariantModuleName: invariantModuleName,
InvariantRoute: invariantRoute,
}
}
//nolint
func (msg MsgVerifyInvariant) Route() string { return ModuleName }
func (msg MsgVerifyInvariant) Type() string { return "verify_invariant" }
// get the bytes for the message signer to sign on
func (msg MsgVerifyInvariant) GetSigners() []sdk.AccAddress { return []sdk.AccAddress{msg.Sender} }
// GetSignBytes gets the sign bytes for the msg MsgVerifyInvariant
func (msg MsgVerifyInvariant) GetSignBytes() []byte {
bz := MsgCdc.MustMarshalJSON(msg)
return sdk.MustSortJSON(bz)
}
// quick validity check
func (msg MsgVerifyInvariant) ValidateBasic() sdk.Error {
if msg.Sender.Empty() {
return ErrNilSender(DefaultCodespace)
}
return nil
}
// FullInvariantRoute - get the messages full invariant route
func (msg MsgVerifyInvariant) FullInvariantRoute() string {
return msg.InvariantModuleName + "/" + msg.InvariantRoute
}

34
x/crisis/params.go Normal file
View File

@ -0,0 +1,34 @@
package crisis
import (
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/params"
)
// Default parameter namespace
const (
DefaultParamspace = ModuleName
)
var (
// key for constant fee parameter
ParamStoreKeyConstantFee = []byte("ConstantFee")
)
// type declaration for parameters
func ParamKeyTable() params.KeyTable {
return params.NewKeyTable(
ParamStoreKeyConstantFee, sdk.Coin{},
)
}
// GetConstantFee get's the constant fee from the paramSpace
func (k Keeper) GetConstantFee(ctx sdk.Context) (constantFee sdk.Coin) {
k.paramSpace.Get(ctx, ParamStoreKeyConstantFee, &constantFee)
return
}
// GetConstantFee set's the constant fee in the paramSpace
func (k Keeper) SetConstantFee(ctx sdk.Context, constantFee sdk.Coin) {
k.paramSpace.Set(ctx, ParamStoreKeyConstantFee, constantFee)
}

26
x/crisis/route.go Normal file
View File

@ -0,0 +1,26 @@
package crisis
import (
sdk "github.com/cosmos/cosmos-sdk/types"
)
// invariant route
type InvarRoute struct {
ModuleName string
Route string
Invar sdk.Invariant
}
// NewInvarRoute - create an InvarRoute object
func NewInvarRoute(moduleName, route string, invar sdk.Invariant) InvarRoute {
return InvarRoute{
ModuleName: moduleName,
Route: route,
Invar: invar,
}
}
// get the full invariance route
func (i InvarRoute) FullRoute() string {
return i.ModuleName + "/" + i.Route
}

View File

@ -59,6 +59,14 @@ var (
NewQueryDelegatorParams = keeper.NewQueryDelegatorParams
NewQueryDelegatorWithdrawAddrParams = keeper.NewQueryDelegatorWithdrawAddrParams
DefaultParamspace = keeper.DefaultParamspace
RegisterInvariants = keeper.RegisterInvariants
AllInvariants = keeper.AllInvariants
NonNegativeOutstandingInvariant = keeper.NonNegativeOutstandingInvariant
CanWithdrawInvariant = keeper.CanWithdrawInvariant
ReferenceCountInvariant = keeper.ReferenceCountInvariant
CreateTestInputDefault = keeper.CreateTestInputDefault
CreateTestInputAdvanced = keeper.CreateTestInputAdvanced
TestAddrs = keeper.TestAddrs
RegisterCodec = types.RegisterCodec
DefaultGenesisState = types.DefaultGenesisState

View File

@ -105,7 +105,7 @@ func TestAllocateTokensToManyValidators(t *testing.T) {
func TestAllocateTokensTruncation(t *testing.T) {
communityTax := sdk.NewDec(0)
ctx, _, k, sk, fck := CreateTestInputAdvanced(t, false, 1000000, communityTax)
ctx, _, _, k, sk, fck, _ := CreateTestInputAdvanced(t, false, 1000000, communityTax)
sh := staking.NewHandler(sk)
// create validator with 10% commission

View File

@ -0,0 +1,25 @@
package keeper
import (
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/distribution/types"
)
// DistributeFeePool distributes funds from the the community pool to a receiver address
func (k Keeper) DistributeFeePool(ctx sdk.Context, amount sdk.Coins, receiveAddr sdk.AccAddress) sdk.Error {
feePool := k.GetFeePool(ctx)
poolTruncated, _ := feePool.CommunityPool.TruncateDecimal()
if !poolTruncated.IsAllGTE(amount) {
return types.ErrBadDistribution(k.codespace)
}
feePool.CommunityPool.Sub(sdk.NewDecCoins(amount))
_, _, err := k.bankKeeper.AddCoins(ctx, receiveAddr, amount)
if err != nil {
return err
}
k.SetFeePool(ctx, feePool)
return nil
}

View File

@ -1,25 +1,34 @@
package simulation
package keeper
import (
"fmt"
sdk "github.com/cosmos/cosmos-sdk/types"
distr "github.com/cosmos/cosmos-sdk/x/distribution"
"github.com/cosmos/cosmos-sdk/x/distribution/types"
)
// register all distribution invariants
func RegisterInvariants(c types.CrisisKeeper, k Keeper, stk types.StakingKeeper) {
c.RegisterRoute(types.ModuleName, "nonnegative-outstanding",
NonNegativeOutstandingInvariant(k))
c.RegisterRoute(types.ModuleName, "can-withdraw",
CanWithdrawInvariant(k, stk))
c.RegisterRoute(types.ModuleName, "reference-count",
ReferenceCountInvariant(k, stk))
}
// AllInvariants runs all invariants of the distribution module
func AllInvariants(d distr.Keeper, stk types.StakingKeeper) sdk.Invariant {
func AllInvariants(k Keeper, stk types.StakingKeeper) sdk.Invariant {
return func(ctx sdk.Context) error {
err := CanWithdrawInvariant(d, stk)(ctx)
err := CanWithdrawInvariant(k, stk)(ctx)
if err != nil {
return err
}
err = NonNegativeOutstandingInvariant(d)(ctx)
err = NonNegativeOutstandingInvariant(k)(ctx)
if err != nil {
return err
}
err = ReferenceCountInvariant(d, stk)(ctx)
err = ReferenceCountInvariant(k, stk)(ctx)
if err != nil {
return err
}
@ -28,7 +37,7 @@ func AllInvariants(d distr.Keeper, stk types.StakingKeeper) sdk.Invariant {
}
// NonNegativeOutstandingInvariant checks that outstanding unwithdrawn fees are never negative
func NonNegativeOutstandingInvariant(k distr.Keeper) sdk.Invariant {
func NonNegativeOutstandingInvariant(k Keeper) sdk.Invariant {
return func(ctx sdk.Context) error {
var outstanding sdk.DecCoins
@ -51,7 +60,7 @@ func NonNegativeOutstandingInvariant(k distr.Keeper) sdk.Invariant {
}
// CanWithdrawInvariant checks that current rewards can be completely withdrawn
func CanWithdrawInvariant(k distr.Keeper, sk types.StakingKeeper) sdk.Invariant {
func CanWithdrawInvariant(k Keeper, sk types.StakingKeeper) sdk.Invariant {
return func(ctx sdk.Context) error {
// cache, we don't want to write changes
@ -95,7 +104,7 @@ func CanWithdrawInvariant(k distr.Keeper, sk types.StakingKeeper) sdk.Invariant
}
// ReferenceCountInvariant checks that the number of historical rewards records is correct
func ReferenceCountInvariant(k distr.Keeper, sk types.StakingKeeper) sdk.Invariant {
func ReferenceCountInvariant(k Keeper, sk types.StakingKeeper) sdk.Invariant {
return func(ctx sdk.Context) error {
valCount := uint64(0)

View File

@ -4,11 +4,12 @@ import (
"encoding/binary"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/distribution/types"
)
const (
// default paramspace for params keeper
DefaultParamspace = "distr"
DefaultParamspace = types.ModuleName
)
// keys

View File

@ -47,7 +47,8 @@ var (
valConsAddr2 = sdk.ConsAddress(valConsPk2.Address())
valConsAddr3 = sdk.ConsAddress(valConsPk3.Address())
addrs = []sdk.AccAddress{
// test addresses
TestAddrs = []sdk.AccAddress{
delAddr1, delAddr2, delAddr3,
valAccAddr1, valAccAddr2, valAccAddr3,
}
@ -75,13 +76,15 @@ func CreateTestInputDefault(t *testing.T, isCheckTx bool, initPower int64) (
sdk.Context, auth.AccountKeeper, Keeper, staking.Keeper, DummyFeeCollectionKeeper) {
communityTax := sdk.NewDecWithPrec(2, 2)
return CreateTestInputAdvanced(t, isCheckTx, initPower, communityTax)
ctx, ak, _, dk, sk, fck, _ := CreateTestInputAdvanced(t, isCheckTx, initPower, communityTax)
return ctx, ak, dk, sk, fck
}
// hogpodge of all sorts of input required for testing
func CreateTestInputAdvanced(t *testing.T, isCheckTx bool, initPower int64,
communityTax sdk.Dec) (
sdk.Context, auth.AccountKeeper, Keeper, staking.Keeper, DummyFeeCollectionKeeper) {
communityTax sdk.Dec) (sdk.Context, auth.AccountKeeper, bank.Keeper,
Keeper, staking.Keeper, DummyFeeCollectionKeeper, params.Keeper) {
initCoins := sdk.TokensFromTendermintPower(initPower)
@ -112,15 +115,15 @@ func CreateTestInputAdvanced(t *testing.T, isCheckTx bool, initPower int64,
ctx := sdk.NewContext(ms, abci.Header{ChainID: "foochainid"}, isCheckTx, log.NewNopLogger())
accountKeeper := auth.NewAccountKeeper(cdc, keyAcc, pk.Subspace(auth.DefaultParamspace), auth.ProtoBaseAccount)
ck := bank.NewBaseKeeper(accountKeeper, pk.Subspace(bank.DefaultParamspace), bank.DefaultCodespace)
sk := staking.NewKeeper(cdc, keyStaking, tkeyStaking, ck, pk.Subspace(staking.DefaultParamspace), staking.DefaultCodespace)
bankKeeper := bank.NewBaseKeeper(accountKeeper, pk.Subspace(bank.DefaultParamspace), bank.DefaultCodespace)
sk := staking.NewKeeper(cdc, keyStaking, tkeyStaking, bankKeeper, pk.Subspace(staking.DefaultParamspace), staking.DefaultCodespace)
sk.SetPool(ctx, staking.InitialPool())
sk.SetParams(ctx, staking.DefaultParams())
// fill all the addresses with some coins, set the loose pool tokens simultaneously
for _, addr := range addrs {
for _, addr := range TestAddrs {
pool := sk.GetPool(ctx)
_, _, err := ck.AddCoins(ctx, addr, sdk.Coins{
_, _, err := bankKeeper.AddCoins(ctx, addr, sdk.Coins{
sdk.NewCoin(sk.GetParams(ctx).BondDenom, initCoins),
})
require.Nil(t, err)
@ -129,7 +132,7 @@ func CreateTestInputAdvanced(t *testing.T, isCheckTx bool, initPower int64,
}
fck := DummyFeeCollectionKeeper{}
keeper := NewKeeper(cdc, keyDistr, pk.Subspace(DefaultParamspace), ck, sk, fck, types.DefaultCodespace)
keeper := NewKeeper(cdc, keyDistr, pk.Subspace(DefaultParamspace), bankKeeper, sk, fck, types.DefaultCodespace)
// set the distribution hooks on staking
sk.SetHooks(keeper.Hooks())
@ -140,7 +143,7 @@ func CreateTestInputAdvanced(t *testing.T, isCheckTx bool, initPower int64,
keeper.SetBaseProposerReward(ctx, sdk.NewDecWithPrec(1, 2))
keeper.SetBonusProposerReward(ctx, sdk.NewDecWithPrec(4, 2))
return ctx, accountKeeper, keeper, sk, fck
return ctx, accountKeeper, bankKeeper, keeper, sk, fck, pk
}
//__________________________________________________________________________________
@ -151,6 +154,10 @@ var heldFees sdk.Coins
var _ types.FeeCollectionKeeper = DummyFeeCollectionKeeper{}
// nolint
func (fck DummyFeeCollectionKeeper) AddCollectedFees(_ sdk.Context, in sdk.Coins) sdk.Coins {
fck.SetCollectedFees(heldFees.Add(in))
return heldFees
}
func (fck DummyFeeCollectionKeeper) GetCollectedFees(_ sdk.Context) sdk.Coins {
return heldFees
}

View File

@ -36,3 +36,6 @@ func ErrNoValidatorCommission(codespace sdk.CodespaceType) sdk.Error {
func ErrSetWithdrawAddrDisabled(codespace sdk.CodespaceType) sdk.Error {
return sdk.NewError(codespace, CodeSetWithdrawAddrDisabled, "set withdraw address disabled")
}
func ErrBadDistribution(codespace sdk.CodespaceType) sdk.Error {
return sdk.NewError(codespace, CodeInvalidInput, "community pool does not have sufficient coins to distribute")
}

View File

@ -28,3 +28,8 @@ type FeeCollectionKeeper interface {
GetCollectedFees(ctx sdk.Context) sdk.Coins
ClearCollectedFees(ctx sdk.Context)
}
// expected crisis keeper
type CrisisKeeper interface {
RegisterRoute(moduleName, route string, invar sdk.Invariant)
}

View File

@ -1,15 +1,18 @@
package types
const (
// ModuleName is the module name constant used in many places
ModuleName = "distr"
// StoreKey is the store key string for distribution
StoreKey = "distr"
StoreKey = ModuleName
// TStoreKey is the transient store key for distribution
TStoreKey = "transient_distr"
TStoreKey = "transient_" + ModuleName
// RouterKey is the message route for distribution
RouterKey = "distr"
RouterKey = ModuleName
// QuerierRoute is the querier route for distribution
QuerierRoute = "distr"
QuerierRoute = ModuleName
)

View File

@ -5,9 +5,6 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types"
)
// name to identify transaction types
const MsgRoute = "distr"
// Verify interface at compile time
var _, _, _ sdk.Msg = &MsgSetWithdrawAddress{}, &MsgWithdrawDelegatorReward{}, &MsgWithdrawValidatorCommission{}
@ -24,7 +21,7 @@ func NewMsgSetWithdrawAddress(delAddr, withdrawAddr sdk.AccAddress) MsgSetWithdr
}
}
func (msg MsgSetWithdrawAddress) Route() string { return MsgRoute }
func (msg MsgSetWithdrawAddress) Route() string { return ModuleName }
func (msg MsgSetWithdrawAddress) Type() string { return "set_withdraw_address" }
// Return address that must sign over msg.GetSignBytes()
@ -62,7 +59,7 @@ func NewMsgWithdrawDelegatorReward(delAddr sdk.AccAddress, valAddr sdk.ValAddres
}
}
func (msg MsgWithdrawDelegatorReward) Route() string { return MsgRoute }
func (msg MsgWithdrawDelegatorReward) Route() string { return ModuleName }
func (msg MsgWithdrawDelegatorReward) Type() string { return "withdraw_delegator_reward" }
// Return address that must sign over msg.GetSignBytes()
@ -98,7 +95,7 @@ func NewMsgWithdrawValidatorCommission(valAddr sdk.ValAddress) MsgWithdrawValida
}
}
func (msg MsgWithdrawValidatorCommission) Route() string { return MsgRoute }
func (msg MsgWithdrawValidatorCommission) Route() string { return ModuleName }
func (msg MsgWithdrawValidatorCommission) Type() string { return "withdraw_validator_rewards_all" }
// Return address that must sign over msg.GetSignBytes()

View File

@ -1,14 +0,0 @@
package simulation
import (
sdk "github.com/cosmos/cosmos-sdk/types"
)
// AllInvariants tests all governance invariants
func AllInvariants() sdk.Invariant {
return func(ctx sdk.Context) error {
// TODO Add some invariants!
// Checking proposal queues, no passed-but-unexecuted proposals, etc.
return nil
}
}

View File

@ -4,12 +4,13 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types"
)
// GenesisState - all distribution state that must be provided at genesis
// GenesisState - minter state
type GenesisState struct {
Minter Minter `json:"minter"` // minter object
Params Params `json:"params"` // inflation params
}
// NewGenesisState creates a new GenesisState object
func NewGenesisState(minter Minter, params Params) GenesisState {
return GenesisState{
Minter: minter,
@ -17,7 +18,7 @@ func NewGenesisState(minter Minter, params Params) GenesisState {
}
}
// get raw genesis raw message for testing
// DefaultGenesisState creates a default GenesisState object
func DefaultGenesisState() GenesisState {
return GenesisState{
Minter: DefaultInitialMinter(),
@ -31,8 +32,7 @@ func InitGenesis(ctx sdk.Context, keeper Keeper, data GenesisState) {
keeper.SetParams(ctx, data.Params)
}
// ExportGenesis returns a GenesisState for a given context and keeper. The
// GenesisState will contain the pool, and validator/delegator distribution info's
// ExportGenesis returns a GenesisState for a given context and keeper.
func ExportGenesis(ctx sdk.Context, keeper Keeper) GenesisState {
minter := keeper.GetMinter(ctx)
@ -40,8 +40,8 @@ func ExportGenesis(ctx sdk.Context, keeper Keeper) GenesisState {
return NewGenesisState(minter, params)
}
// ValidateGenesis validates the provided staking genesis state to ensure the
// expected invariants holds. (i.e. params in correct bounds, no duplicate validators)
// ValidateGenesis validates the provided genesis state to ensure the
// expected invariants holds.
func ValidateGenesis(data GenesisState) error {
err := validateParams(data.Params)
if err != nil {

View File

@ -1,13 +0,0 @@
package simulation
import (
sdk "github.com/cosmos/cosmos-sdk/types"
)
// TODO Any invariants to check here?
// AllInvariants tests all slashing invariants
func AllInvariants() sdk.Invariant {
return func(_ sdk.Context) error {
return nil
}
}

View File

@ -68,6 +68,12 @@ var (
UnbondingQueueKey = keeper.UnbondingQueueKey
RedelegationQueueKey = keeper.RedelegationQueueKey
ValidatorQueueKey = keeper.ValidatorQueueKey
RegisterInvariants = keeper.RegisterInvariants
AllInvariants = keeper.AllInvariants
SupplyInvariants = keeper.SupplyInvariants
NonNegativePowerInvariant = keeper.NonNegativePowerInvariant
PositiveDelegationInvariant = keeper.PositiveDelegationInvariant
DelegatorSharesInvariant = keeper.DelegatorSharesInvariant
DefaultParamspace = keeper.DefaultParamspace
KeyUnbondingTime = types.KeyUnbondingTime

View File

@ -1,4 +1,4 @@
package simulation
package keeper
import (
"bytes"
@ -6,15 +6,26 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/auth"
"github.com/cosmos/cosmos-sdk/x/staking"
"github.com/cosmos/cosmos-sdk/x/staking/keeper"
"github.com/cosmos/cosmos-sdk/x/staking/types"
)
// register all staking invariants
func RegisterInvariants(c types.CrisisKeeper, k Keeper, f types.FeeCollectionKeeper,
d types.DistributionKeeper, am auth.AccountKeeper) {
c.RegisterRoute(types.ModuleName, "supply",
SupplyInvariants(k, f, d, am))
c.RegisterRoute(types.ModuleName, "nonnegative-power",
NonNegativePowerInvariant(k))
c.RegisterRoute(types.ModuleName, "positive-delegation",
PositiveDelegationInvariant(k))
c.RegisterRoute(types.ModuleName, "delegator-shares",
DelegatorSharesInvariant(k))
}
// AllInvariants runs all invariants of the staking module.
// Currently: total supply, positive power
func AllInvariants(k staking.Keeper,
f staking.FeeCollectionKeeper, d staking.DistributionKeeper,
am auth.AccountKeeper) sdk.Invariant {
func AllInvariants(k Keeper, f types.FeeCollectionKeeper,
d types.DistributionKeeper, am auth.AccountKeeper) sdk.Invariant {
return func(ctx sdk.Context) error {
err := SupplyInvariants(k, f, d, am)(ctx)
@ -43,8 +54,9 @@ func AllInvariants(k staking.Keeper,
// SupplyInvariants checks that the total supply reflects all held not-bonded tokens, bonded tokens, and unbonding delegations
// nolint: unparam
func SupplyInvariants(k staking.Keeper,
f staking.FeeCollectionKeeper, d staking.DistributionKeeper, am auth.AccountKeeper) sdk.Invariant {
func SupplyInvariants(k Keeper, f types.FeeCollectionKeeper,
d types.DistributionKeeper, am auth.AccountKeeper) sdk.Invariant {
return func(ctx sdk.Context) error {
pool := k.GetPool(ctx)
@ -54,7 +66,7 @@ func SupplyInvariants(k staking.Keeper,
loose = loose.Add(acc.GetCoins().AmountOf(k.BondDenom(ctx)).ToDec())
return false
})
k.IterateUnbondingDelegations(ctx, func(_ int64, ubd staking.UnbondingDelegation) bool {
k.IterateUnbondingDelegations(ctx, func(_ int64, ubd types.UnbondingDelegation) bool {
for _, entry := range ubd.Entries {
loose = loose.Add(entry.Balance.ToDec())
}
@ -98,7 +110,7 @@ func SupplyInvariants(k staking.Keeper,
}
// NonNegativePowerInvariant checks that all stored validators have >= 0 power.
func NonNegativePowerInvariant(k staking.Keeper) sdk.Invariant {
func NonNegativePowerInvariant(k Keeper) sdk.Invariant {
return func(ctx sdk.Context) error {
iterator := k.ValidatorsPowerStoreIterator(ctx)
@ -108,7 +120,7 @@ func NonNegativePowerInvariant(k staking.Keeper) sdk.Invariant {
panic(fmt.Sprintf("validator record not found for address: %X\n", iterator.Value()))
}
powerKey := keeper.GetValidatorsByPowerIndexKey(validator)
powerKey := GetValidatorsByPowerIndexKey(validator)
if !bytes.Equal(iterator.Key(), powerKey) {
return fmt.Errorf("power store invariance:\n\tvalidator.Power: %v"+
@ -126,7 +138,7 @@ func NonNegativePowerInvariant(k staking.Keeper) sdk.Invariant {
}
// PositiveDelegationInvariant checks that all stored delegations have > 0 shares.
func PositiveDelegationInvariant(k staking.Keeper) sdk.Invariant {
func PositiveDelegationInvariant(k Keeper) sdk.Invariant {
return func(ctx sdk.Context) error {
delegations := k.GetAllDelegations(ctx)
for _, delegation := range delegations {
@ -145,7 +157,7 @@ func PositiveDelegationInvariant(k staking.Keeper) sdk.Invariant {
// DelegatorSharesInvariant checks whether all the delegator shares which persist
// in the delegator object add up to the correct total delegator shares
// amount stored in each validator
func DelegatorSharesInvariant(k staking.Keeper) sdk.Invariant {
func DelegatorSharesInvariant(k Keeper) sdk.Invariant {
return func(ctx sdk.Context) error {
validators := k.GetAllValidators(ctx)
for _, validator := range validators {

View File

@ -18,3 +18,8 @@ type BankKeeper interface {
DelegateCoins(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins) (sdk.Tags, sdk.Error)
UndelegateCoins(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins) (sdk.Tags, sdk.Error)
}
// expected crisis keeper
type CrisisKeeper interface {
RegisterRoute(moduleName, route string, invar sdk.Invariant)
}