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
48 changed files with 820 additions and 120 deletions
+9
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.
+10
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)
}
@@ -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
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
}
+42
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
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
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")
}
+20
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
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
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
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
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
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
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
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
}
+8
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
+1 -1
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
+25
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
}
@@ -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)
+2 -1
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
+17 -10
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
}
+3
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")
}
+5
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)
}
+7 -4
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
)
+3 -6
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()
-14
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
}
}
+6 -6
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 {
-13
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
}
}
+6
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
@@ -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 {
+5
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)
}