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:
parent
b9e6df3851
commit
df4394185e
|
@ -0,0 +1 @@
|
|||
#2935 Optionally assert invariants on a blockly basis using `gaiad --assert-invariants-blockly`
|
|
@ -0,0 +1 @@
|
|||
#2935 New module Crisis which can test broken invariant with messages
|
|
@ -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"),
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
||||
|
|
|
@ -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)`
|
||||
|
|
@ -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.
|
|
@ -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} |
|
|
@ -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)
|
|
@ -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.
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -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 {
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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()
|
||||
}
|
|
@ -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")
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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,
|
||||
}
|
||||
}
|
|
@ -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())
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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)
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
|
@ -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)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue