From 2f9fd08fcfc754f003a040b5352ad31b5a332f40 Mon Sep 17 00:00:00 2001 From: Vaibhav Jindal Date: Thu, 12 Oct 2023 13:02:30 +0530 Subject: [PATCH] Cosmos 0.37.5 Modules --- params/alias.go | 56 + params/client/cli/flags.go | 5 - params/client/cli/tx.go | 37 +- params/client/proposal_handler.go | 6 +- params/client/rest/rest.go | 25 +- params/client/utils/utils.go | 40 +- params/client/utils/utils_test.go | 13 +- params/keeper.go | 18 +- params/keeper_test.go | 37 +- params/module.go | 30 +- params/proposal_handler.go | 30 +- params/proposal_handler_test.go | 60 +- params/simulation/msgs.go | 185 ++++ params/subspace/subspace.go | 84 +- params/subspace/table.go | 7 +- params/subspace/table_test.go | 2 - params/subspace/test_common.go | 34 + params/test_common.go | 49 + params/types/codec.go | 4 +- params/types/errors.go | 4 +- params/types/keys.go | 18 +- params/types/proposal.go | 25 +- params/types/proposal_test.go | 17 +- slashing/abci.go | 45 +- slashing/abci_test.go | 91 ++ slashing/alias.go | 96 ++ slashing/app_test.go | 156 +++ slashing/client/cli/flags.go | 14 +- slashing/client/cli/query.go | 389 +------ slashing/client/cli/tx.go | 167 +-- slashing/client/rest/query.go | 485 +-------- slashing/client/rest/rest.go | 18 +- slashing/client/rest/tx.go | 292 +----- slashing/genesis.go | 83 +- slashing/handler.go | 185 +--- slashing/handler_test.go | 134 +++ slashing/hooks.go | 76 ++ slashing/infractions.go | 151 --- slashing/keeper.go | 737 ++++--------- slashing/keeper_test.go | 474 +++++++++ slashing/module.go | 173 +-- slashing/msg_test.go | 29 - slashing/params.go | 55 + slashing/querier.go | 226 +--- slashing/querier_test.go | 37 + slashing/side_handler.go | 357 ------- slashing/signing_info.go | 93 ++ slashing/signing_info_test.go | 40 + slashing/simulation/decoder.go | 40 - slashing/simulation/decoder_test.go | 61 -- slashing/simulation/genesis.go | 141 --- slashing/simulation/msgs.go | 32 + slashing/simulation/params.go | 40 - slashing/slashinfo_test.go | 64 -- slashing/test_common.go | 155 +++ slashing/types/codec.go | 17 +- slashing/types/errors.go | 51 + slashing/types/events.go | 27 +- slashing/types/evidence.go | 132 --- slashing/types/expected_keepers.go | 42 + slashing/types/genesis.go | 72 +- slashing/types/infohash.go | 109 -- slashing/types/keys.go | 61 +- slashing/types/msg.go | 184 +--- slashing/types/msg_test.go | 20 + slashing/types/params.go | 92 +- slashing/types/querier.go | 69 +- slashing/types/signing_info.go | 47 + staking/alias.go | 245 +++++ staking/app_test.go | 188 ++++ staking/client/cli/flags.go | 84 +- staking/client/cli/query.go | 616 +++++++---- staking/client/cli/tx.go | 594 +++++------ staking/client/cli/tx_test.go | 84 ++ staking/client/cli/utils.go | 32 + staking/client/rest/query.go | 696 ++++-------- staking/client/rest/rest.go | 13 +- staking/client/rest/tx.go | 459 ++------ staking/client/rest/utils.go | 148 +++ staking/exported/exported.go | 37 + staking/genesis.go | 238 ++++- staking/genesis_test.go | 172 ++- staking/handler.go | 422 ++++---- staking/handler_test.go | 1512 ++++++++++++++++++++++----- staking/integration_test.go | 47 - staking/keeper.go | 577 ---------- staking/keeper/alias_functions.go | 139 +++ staking/keeper/delegation.go | 826 +++++++++++++++ staking/keeper/delegation_test.go | 946 +++++++++++++++++ staking/keeper/hooks.go | 79 ++ staking/keeper/invariants.go | 181 ++++ staking/keeper/keeper.go | 99 ++ staking/keeper/keeper_test.go | 24 + staking/keeper/params.go | 59 ++ staking/keeper/pool.go | 77 ++ staking/keeper/querier.go | 405 +++++++ staking/keeper/querier_test.go | 553 ++++++++++ staking/keeper/query_utils.go | 111 ++ staking/keeper/slash.go | 294 ++++++ staking/keeper/slash_test.go | 552 ++++++++++ staking/keeper/test_common.go | 304 ++++++ staking/keeper/val_state_change.go | 298 ++++++ staking/keeper/validator.go | 446 ++++++++ staking/keeper/validator_test.go | 1086 +++++++++++++++++++ staking/keeper_test.go | 479 --------- staking/legacy/v0_34/types.go | 173 +++ staking/legacy/v0_36/migrate.go | 52 + staking/legacy/v0_36/types.go | 134 +++ staking/module.go | 232 ++-- staking/querier.go | 235 ----- staking/querier_test.go | 257 ----- staking/side_handler.go | 669 ------------ staking/side_handler_test.go | 1356 ------------------------ staking/simulation/genesis.go | 45 - staking/simulation/msgs.go | 222 ++++ staking/simulation/utils.go | 44 - staking/test_common.go | 57 + staking/types/codec.go | 22 +- staking/types/commission.go | 122 +++ staking/types/commission_test.go | 72 ++ staking/types/delegation.go | 471 +++++++++ staking/types/delegation_test.go | 141 +++ staking/types/errors.go | 214 ++++ staking/types/events.go | 32 +- staking/types/expected_keepers.go | 101 ++ staking/types/genesis.go | 104 +- staking/types/hooks.go | 64 ++ staking/types/keys.go | 277 ++++- staking/types/keys_test.go | 90 ++ staking/types/msg.go | 546 +++++----- staking/types/msg_test.go | 156 +++ staking/types/params.go | 117 ++- staking/types/params_test.go | 21 + staking/types/pool.go | 39 + staking/types/querier.go | 115 +- staking/types/test_utils.go | 24 + staking/types/validator.go | 496 +++++++++ staking/types/validator_test.go | 304 ++++++ topup/client/cli/flags.go | 15 - topup/client/cli/query.go | 325 ------ topup/client/cli/tx.go | 155 --- topup/client/rest/query.go | 382 ------- topup/client/rest/rest.go | 22 - topup/client/rest/tx.go | 238 ----- topup/genesis.go | 28 - topup/genesis_test.go | 57 - topup/handler.go | 114 -- topup/handler_test.go | 198 ---- topup/intergration_test.go | 33 - topup/keeper.go | 234 ----- topup/keeper_test.go | 101 -- topup/module.go | 186 ---- topup/querier.go | 170 --- topup/querier_test.go | 219 ---- topup/side_handler.go | 161 --- topup/side_handler_test.go | 471 --------- topup/simulation/genesis.go | 59 -- topup/types/codec.go | 18 - topup/types/errors.go | 43 - topup/types/events.go | 16 - topup/types/genesis.go | 61 -- topup/types/keys.go | 25 - topup/types/msg.go | 146 --- topup/types/params.go | 1 - topup/types/querier.go | 53 - 165 files changed, 16588 insertions(+), 13914 deletions(-) create mode 100644 params/alias.go delete mode 100644 params/client/cli/flags.go create mode 100644 params/simulation/msgs.go create mode 100644 params/test_common.go create mode 100644 slashing/abci_test.go create mode 100644 slashing/alias.go create mode 100644 slashing/app_test.go create mode 100644 slashing/handler_test.go create mode 100644 slashing/hooks.go delete mode 100644 slashing/infractions.go create mode 100644 slashing/keeper_test.go delete mode 100644 slashing/msg_test.go create mode 100644 slashing/params.go create mode 100644 slashing/querier_test.go delete mode 100644 slashing/side_handler.go create mode 100644 slashing/signing_info.go create mode 100644 slashing/signing_info_test.go delete mode 100644 slashing/simulation/decoder.go delete mode 100644 slashing/simulation/decoder_test.go delete mode 100644 slashing/simulation/genesis.go create mode 100644 slashing/simulation/msgs.go delete mode 100644 slashing/simulation/params.go delete mode 100644 slashing/slashinfo_test.go create mode 100644 slashing/test_common.go create mode 100644 slashing/types/errors.go delete mode 100644 slashing/types/evidence.go create mode 100644 slashing/types/expected_keepers.go delete mode 100644 slashing/types/infohash.go create mode 100644 slashing/types/msg_test.go create mode 100644 slashing/types/signing_info.go create mode 100644 staking/alias.go create mode 100644 staking/app_test.go create mode 100644 staking/client/cli/tx_test.go create mode 100644 staking/client/cli/utils.go create mode 100644 staking/client/rest/utils.go create mode 100644 staking/exported/exported.go delete mode 100644 staking/integration_test.go delete mode 100644 staking/keeper.go create mode 100644 staking/keeper/alias_functions.go create mode 100644 staking/keeper/delegation.go create mode 100644 staking/keeper/delegation_test.go create mode 100644 staking/keeper/hooks.go create mode 100644 staking/keeper/invariants.go create mode 100644 staking/keeper/keeper.go create mode 100644 staking/keeper/keeper_test.go create mode 100644 staking/keeper/params.go create mode 100644 staking/keeper/pool.go create mode 100644 staking/keeper/querier.go create mode 100644 staking/keeper/querier_test.go create mode 100644 staking/keeper/query_utils.go create mode 100644 staking/keeper/slash.go create mode 100644 staking/keeper/slash_test.go create mode 100644 staking/keeper/test_common.go create mode 100644 staking/keeper/val_state_change.go create mode 100644 staking/keeper/validator.go create mode 100644 staking/keeper/validator_test.go delete mode 100644 staking/keeper_test.go create mode 100644 staking/legacy/v0_34/types.go create mode 100644 staking/legacy/v0_36/migrate.go create mode 100644 staking/legacy/v0_36/types.go delete mode 100644 staking/querier.go delete mode 100644 staking/querier_test.go delete mode 100644 staking/side_handler.go delete mode 100644 staking/side_handler_test.go delete mode 100644 staking/simulation/genesis.go create mode 100644 staking/simulation/msgs.go delete mode 100644 staking/simulation/utils.go create mode 100644 staking/test_common.go create mode 100644 staking/types/commission.go create mode 100644 staking/types/commission_test.go create mode 100644 staking/types/delegation.go create mode 100644 staking/types/delegation_test.go create mode 100644 staking/types/errors.go create mode 100644 staking/types/expected_keepers.go create mode 100644 staking/types/hooks.go create mode 100644 staking/types/keys_test.go create mode 100644 staking/types/msg_test.go create mode 100644 staking/types/params_test.go create mode 100644 staking/types/pool.go create mode 100644 staking/types/test_utils.go create mode 100644 staking/types/validator.go create mode 100644 staking/types/validator_test.go delete mode 100644 topup/client/cli/flags.go delete mode 100644 topup/client/cli/query.go delete mode 100644 topup/client/cli/tx.go delete mode 100644 topup/client/rest/query.go delete mode 100644 topup/client/rest/rest.go delete mode 100644 topup/client/rest/tx.go delete mode 100644 topup/genesis.go delete mode 100644 topup/genesis_test.go delete mode 100644 topup/handler.go delete mode 100644 topup/handler_test.go delete mode 100644 topup/intergration_test.go delete mode 100644 topup/keeper.go delete mode 100644 topup/keeper_test.go delete mode 100644 topup/module.go delete mode 100644 topup/querier.go delete mode 100644 topup/querier_test.go delete mode 100644 topup/side_handler.go delete mode 100644 topup/side_handler_test.go delete mode 100644 topup/simulation/genesis.go delete mode 100644 topup/types/codec.go delete mode 100644 topup/types/errors.go delete mode 100644 topup/types/events.go delete mode 100644 topup/types/genesis.go delete mode 100644 topup/types/keys.go delete mode 100644 topup/types/msg.go delete mode 100644 topup/types/params.go delete mode 100644 topup/types/querier.go diff --git a/params/alias.go b/params/alias.go new file mode 100644 index 000000000..00a7729b1 --- /dev/null +++ b/params/alias.go @@ -0,0 +1,56 @@ +// nolint +// autogenerated code using github.com/rigelrozanski/multitool +// aliases generated for the following subdirectories: +// ALIASGEN: github.com/cosmos/cosmos-sdk/x/params/subspace +// ALIASGEN: github.com/cosmos/cosmos-sdk/x/params/types +package params + +import ( + "github.com/cosmos/cosmos-sdk/x/params/subspace" + "github.com/cosmos/cosmos-sdk/x/params/types" +) + +const ( + StoreKey = subspace.StoreKey + TStoreKey = subspace.TStoreKey + TestParamStore = subspace.TestParamStore + DefaultCodespace = types.DefaultCodespace + CodeUnknownSubspace = types.CodeUnknownSubspace + CodeSettingParameter = types.CodeSettingParameter + CodeEmptyData = types.CodeEmptyData + ModuleName = types.ModuleName + RouterKey = types.RouterKey + ProposalTypeChange = types.ProposalTypeChange +) + +var ( + // functions aliases + NewSubspace = subspace.NewSubspace + NewKeyTable = subspace.NewKeyTable + DefaultTestComponents = subspace.DefaultTestComponents + RegisterCodec = types.RegisterCodec + ErrUnknownSubspace = types.ErrUnknownSubspace + ErrSettingParameter = types.ErrSettingParameter + ErrEmptyChanges = types.ErrEmptyChanges + ErrEmptySubspace = types.ErrEmptySubspace + ErrEmptyKey = types.ErrEmptyKey + ErrEmptyValue = types.ErrEmptyValue + NewParameterChangeProposal = types.NewParameterChangeProposal + NewParamChange = types.NewParamChange + NewParamChangeWithSubkey = types.NewParamChangeWithSubkey + ValidateChanges = types.ValidateChanges + + // variable aliases + ModuleCdc = types.ModuleCdc +) + +type ( + ParamSetPair = subspace.ParamSetPair + ParamSetPairs = subspace.ParamSetPairs + ParamSet = subspace.ParamSet + Subspace = subspace.Subspace + ReadOnlySubspace = subspace.ReadOnlySubspace + KeyTable = subspace.KeyTable + ParameterChangeProposal = types.ParameterChangeProposal + ParamChange = types.ParamChange +) diff --git a/params/client/cli/flags.go b/params/client/cli/flags.go deleted file mode 100644 index f26abee20..000000000 --- a/params/client/cli/flags.go +++ /dev/null @@ -1,5 +0,0 @@ -package cli - -const ( - FlagValidatorID = "validator-id" -) diff --git a/params/client/cli/tx.go b/params/client/cli/tx.go index abf13165e..40aeaae83 100644 --- a/params/client/cli/tx.go +++ b/params/client/cli/tx.go @@ -4,23 +4,19 @@ import ( "fmt" "strings" - "github.com/maticnetwork/heimdall/helper" "github.com/spf13/cobra" - "github.com/spf13/viper" "github.com/cosmos/cosmos-sdk/client/context" "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/version" - - govTypes "github.com/maticnetwork/heimdall/gov/types" - paramscutils "github.com/maticnetwork/heimdall/params/client/utils" - "github.com/maticnetwork/heimdall/params/types" - hmTypes "github.com/maticnetwork/heimdall/types" + "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/cosmos/cosmos-sdk/x/auth/client/utils" + "github.com/cosmos/cosmos-sdk/x/gov" + paramscutils "github.com/cosmos/cosmos-sdk/x/params/client/utils" + "github.com/cosmos/cosmos-sdk/x/params/types" ) -var logger = helper.Logger.With("module", "params/client/cli") - // GetCmdSubmitProposal implements a command handler for submitting a parameter // change proposal transaction. func GetCmdSubmitProposal(cdc *codec.Codec) *cobra.Command { @@ -58,8 +54,8 @@ Where proposal.json contains: ], "deposit": [ { - "denom": "matic", - "amount": "1000000000000000000" + "denom": "stake", + "amount": "10000" } ] } @@ -68,6 +64,7 @@ Where proposal.json contains: ), ), RunE: func(cmd *cobra.Command, args []string) error { + txBldr := auth.NewTxBuilderFromCLI().WithTxEncoder(utils.GetTxEncoder(cdc)) cliCtx := context.NewCLIContext().WithCodec(cdc) proposal, err := paramscutils.ParseParamChangeProposalJSON(cdc, args[0]) @@ -75,29 +72,17 @@ Where proposal.json contains: return err } - validatorID := viper.GetUint64(FlagValidatorID) - if validatorID == 0 { - return fmt.Errorf("Valid validator ID required") - } - - from := helper.GetFromAddress(cliCtx) + from := cliCtx.GetFromAddress() content := types.NewParameterChangeProposal(proposal.Title, proposal.Description, proposal.Changes.ToParamChanges()) - // create submit proposal - msg := govTypes.NewMsgSubmitProposal(content, proposal.Deposit, from, hmTypes.NewValidatorID(validatorID)) + msg := gov.NewMsgSubmitProposal(content, proposal.Deposit, from) if err := msg.ValidateBasic(); err != nil { return err } - return helper.BroadcastMsgsWithCLI(cliCtx, []sdk.Msg{msg}) + return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg}) }, } - cmd.Flags().Int(FlagValidatorID, 0, "--validator-id=") - - if err := cmd.MarkFlagRequired(FlagValidatorID); err != nil { - logger.Error("GetCmdSubmitProposal | MarkFlagRequired | FlagValidatorID", "Error", err) - } - return cmd } diff --git a/params/client/proposal_handler.go b/params/client/proposal_handler.go index 9f771d96a..040bebdf4 100644 --- a/params/client/proposal_handler.go +++ b/params/client/proposal_handler.go @@ -1,9 +1,9 @@ package client import ( - govclient "github.com/maticnetwork/heimdall/gov/client" - "github.com/maticnetwork/heimdall/params/client/cli" - "github.com/maticnetwork/heimdall/params/client/rest" + govclient "github.com/cosmos/cosmos-sdk/x/gov/client" + "github.com/cosmos/cosmos-sdk/x/params/client/cli" + "github.com/cosmos/cosmos-sdk/x/params/client/rest" ) // param change proposal handler diff --git a/params/client/rest/rest.go b/params/client/rest/rest.go index 038d26087..4d95932b1 100644 --- a/params/client/rest/rest.go +++ b/params/client/rest/rest.go @@ -5,19 +5,18 @@ import ( "github.com/cosmos/cosmos-sdk/client/context" sdk "github.com/cosmos/cosmos-sdk/types" - - restClient "github.com/maticnetwork/heimdall/client/rest" - govRest "github.com/maticnetwork/heimdall/gov/client/rest" - govTypes "github.com/maticnetwork/heimdall/gov/types" - paramsUtils "github.com/maticnetwork/heimdall/params/client/utils" - paramsTypes "github.com/maticnetwork/heimdall/params/types" - "github.com/maticnetwork/heimdall/types/rest" + "github.com/cosmos/cosmos-sdk/types/rest" + "github.com/cosmos/cosmos-sdk/x/auth/client/utils" + "github.com/cosmos/cosmos-sdk/x/gov" + govrest "github.com/cosmos/cosmos-sdk/x/gov/client/rest" + "github.com/cosmos/cosmos-sdk/x/params" + paramscutils "github.com/cosmos/cosmos-sdk/x/params/client/utils" ) // ProposalRESTHandler returns a ProposalRESTHandler that exposes the param // change REST handler with a given sub-route. -func ProposalRESTHandler(cliCtx context.CLIContext) govRest.ProposalRESTHandler { - return govRest.ProposalRESTHandler{ +func ProposalRESTHandler(cliCtx context.CLIContext) govrest.ProposalRESTHandler { + return govrest.ProposalRESTHandler{ SubRoute: "param_change", Handler: postProposalHandlerFn(cliCtx), } @@ -25,7 +24,7 @@ func ProposalRESTHandler(cliCtx context.CLIContext) govRest.ProposalRESTHandler func postProposalHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - var req paramsUtils.ParamChangeProposalReq + var req paramscutils.ParamChangeProposalReq if !rest.ReadRESTReq(w, r, cliCtx.Codec, &req) { return } @@ -35,14 +34,14 @@ func postProposalHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { return } - content := paramsTypes.NewParameterChangeProposal(req.Title, req.Description, req.Changes.ToParamChanges()) + content := params.NewParameterChangeProposal(req.Title, req.Description, req.Changes.ToParamChanges()) - msg := govTypes.NewMsgSubmitProposal(content, req.Deposit, req.Proposer, req.Validator) + msg := gov.NewMsgSubmitProposal(content, req.Deposit, req.Proposer) if err := msg.ValidateBasic(); err != nil { rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } - restClient.WriteGenerateStdTxResponse(w, cliCtx, req.BaseReq, []sdk.Msg{msg}) + utils.WriteGenerateStdTxResponse(w, cliCtx, req.BaseReq, []sdk.Msg{msg}) } } diff --git a/params/client/utils/utils.go b/params/client/utils/utils.go index 341c93fc9..6bcbeb43a 100644 --- a/params/client/utils/utils.go +++ b/params/client/utils/utils.go @@ -6,10 +6,8 @@ import ( "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" - - "github.com/maticnetwork/heimdall/params/types" - hmTypes "github.com/maticnetwork/heimdall/types" - "github.com/maticnetwork/heimdall/types/rest" + "github.com/cosmos/cosmos-sdk/types/rest" + "github.com/cosmos/cosmos-sdk/x/params" ) type ( @@ -22,49 +20,47 @@ type ( ParamChangeJSON struct { Subspace string `json:"subspace" yaml:"subspace"` Key string `json:"key" yaml:"key"` + Subkey string `json:"subkey,omitempty" yaml:"subkey,omitempty"` Value json.RawMessage `json:"value" yaml:"value"` } // ParamChangeProposalJSON defines a ParameterChangeProposal with a deposit used // to parse parameter change proposals from a JSON file. ParamChangeProposalJSON struct { - Title string `json:"title" yaml:"title"` - Description string `json:"description" yaml:"description"` - Changes ParamChangesJSON `json:"changes" yaml:"changes"` - Deposit sdk.Coins `json:"deposit" yaml:"deposit"` - Validator hmTypes.ValidatorID `json:"validator" yaml:"validator"` + Title string `json:"title" yaml:"title"` + Description string `json:"description" yaml:"description"` + Changes ParamChangesJSON `json:"changes" yaml:"changes"` + Deposit sdk.Coins `json:"deposit" yaml:"deposit"` } // ParamChangeProposalReq defines a parameter change proposal request body. ParamChangeProposalReq struct { BaseReq rest.BaseReq `json:"base_req" yaml:"base_req"` - Title string `json:"title" yaml:"title"` - Description string `json:"description" yaml:"description"` - Changes ParamChangesJSON `json:"changes" yaml:"changes"` - Proposer hmTypes.HeimdallAddress `json:"proposer" yaml:"proposer"` - Deposit sdk.Coins `json:"deposit" yaml:"deposit"` - Validator hmTypes.ValidatorID `json:"validator" yaml:"validator"` + Title string `json:"title" yaml:"title"` + Description string `json:"description" yaml:"description"` + Changes ParamChangesJSON `json:"changes" yaml:"changes"` + Proposer sdk.AccAddress `json:"proposer" yaml:"proposer"` + Deposit sdk.Coins `json:"deposit" yaml:"deposit"` } ) -func NewParamChangeJSON(subspace string, key string, value json.RawMessage) ParamChangeJSON { - return ParamChangeJSON{subspace, key, value} +func NewParamChangeJSON(subspace, key, subkey string, value json.RawMessage) ParamChangeJSON { + return ParamChangeJSON{subspace, key, subkey, value} } // ToParamChange converts a ParamChangeJSON object to ParamChange. -func (pcj ParamChangeJSON) ToParamChange() types.ParamChange { - return types.NewParamChange(pcj.Subspace, pcj.Key, string(pcj.Value)) +func (pcj ParamChangeJSON) ToParamChange() params.ParamChange { + return params.NewParamChangeWithSubkey(pcj.Subspace, pcj.Key, pcj.Subkey, string(pcj.Value)) } // ToParamChanges converts a slice of ParamChangeJSON objects to a slice of // ParamChange. -func (pcj ParamChangesJSON) ToParamChanges() []types.ParamChange { - res := make([]types.ParamChange, len(pcj)) +func (pcj ParamChangesJSON) ToParamChanges() []params.ParamChange { + res := make([]params.ParamChange, len(pcj)) for i, pc := range pcj { res[i] = pc.ToParamChange() } - return res } diff --git a/params/client/utils/utils_test.go b/params/client/utils/utils_test.go index e5f22e9ba..1953d11ff 100644 --- a/params/client/utils/utils_test.go +++ b/params/client/utils/utils_test.go @@ -8,19 +8,16 @@ import ( ) func TestNewParamChangeJSON(t *testing.T) { - t.Parallel() - - pcj := NewParamChangeJSON("subspace", "key", json.RawMessage(`{}`)) + pcj := NewParamChangeJSON("subspace", "key", "subkey", json.RawMessage(`{}`)) require.Equal(t, "subspace", pcj.Subspace) require.Equal(t, "key", pcj.Key) + require.Equal(t, "subkey", pcj.Subkey) require.Equal(t, json.RawMessage(`{}`), pcj.Value) } func TestToParamChanges(t *testing.T) { - t.Parallel() - - pcj1 := NewParamChangeJSON("subspace", "key1", json.RawMessage(`{}`)) - pcj2 := NewParamChangeJSON("subspace", "key2", json.RawMessage(`{}`)) + pcj1 := NewParamChangeJSON("subspace", "key1", "", json.RawMessage(`{}`)) + pcj2 := NewParamChangeJSON("subspace", "key2", "", json.RawMessage(`{}`)) pcjs := ParamChangesJSON{pcj1, pcj2} paramChanges := pcjs.ToParamChanges() @@ -28,9 +25,11 @@ func TestToParamChanges(t *testing.T) { require.Equal(t, paramChanges[0].Subspace, pcj1.Subspace) require.Equal(t, paramChanges[0].Key, pcj1.Key) + require.Equal(t, paramChanges[0].Subkey, pcj1.Subkey) require.Equal(t, paramChanges[0].Value, string(pcj1.Value)) require.Equal(t, paramChanges[1].Subspace, pcj2.Subspace) require.Equal(t, paramChanges[1].Key, pcj2.Key) + require.Equal(t, paramChanges[1].Subkey, pcj2.Subkey) require.Equal(t, paramChanges[1].Value, string(pcj2.Value)) } diff --git a/params/keeper.go b/params/keeper.go index 53267b3b3..a3dc5c077 100644 --- a/params/keeper.go +++ b/params/keeper.go @@ -5,9 +5,9 @@ import ( "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/params/subspace" + "github.com/cosmos/cosmos-sdk/x/params/types" - "github.com/maticnetwork/heimdall/params/subspace" - "github.com/maticnetwork/heimdall/params/types" "github.com/tendermint/tendermint/libs/log" ) @@ -17,7 +17,7 @@ type Keeper struct { key sdk.StoreKey tkey sdk.StoreKey codespace sdk.CodespaceType - spaces map[string]*subspace.Subspace + spaces map[string]*Subspace } // NewKeeper constructs a params keeper @@ -27,7 +27,7 @@ func NewKeeper(cdc *codec.Codec, key *sdk.KVStoreKey, tkey *sdk.TransientStoreKe key: key, tkey: tkey, codespace: codespace, - spaces: make(map[string]*subspace.Subspace), + spaces: make(map[string]*Subspace), } return k @@ -38,8 +38,8 @@ func (k Keeper) Logger(ctx sdk.Context) log.Logger { return ctx.Logger().With("module", fmt.Sprintf("x/%s", types.ModuleName)) } -// Subspace allocate subspace used for keepers -func (k Keeper) Subspace(s string) subspace.Subspace { +// Allocate subspace used for keepers +func (k Keeper) Subspace(s string) Subspace { _, ok := k.spaces[s] if ok { panic("subspace already occupied") @@ -55,11 +55,11 @@ func (k Keeper) Subspace(s string) subspace.Subspace { return space } -// GetSubspace existing substore from keeper -func (k Keeper) GetSubspace(s string) (subspace.Subspace, bool) { +// Get existing substore from keeper +func (k Keeper) GetSubspace(s string) (Subspace, bool) { space, ok := k.spaces[s] if !ok { - return subspace.Subspace{}, false + return Subspace{}, false } return *space, ok } diff --git a/params/keeper_test.go b/params/keeper_test.go index 16da895bf..1d4b4823a 100644 --- a/params/keeper_test.go +++ b/params/keeper_test.go @@ -4,16 +4,13 @@ import ( "reflect" "testing" - "github.com/cosmos/cosmos-sdk/store/prefix" - sdk "github.com/cosmos/cosmos-sdk/types" "github.com/stretchr/testify/require" - subspace "github.com/maticnetwork/heimdall/params/subspace" + "github.com/cosmos/cosmos-sdk/store/prefix" + sdk "github.com/cosmos/cosmos-sdk/types" ) func TestKeeper(t *testing.T) { - t.Parallel() - kvs := []struct { key string param int64 @@ -27,7 +24,7 @@ func TestKeeper(t *testing.T) { {"key7", 9058701}, } - table := subspace.NewKeyTable( + table := NewKeyTable( []byte("key1"), int64(0), []byte("key2"), int64(0), []byte("key3"), int64(0), @@ -106,18 +103,6 @@ func indirect(ptr interface{}) interface{} { return reflect.ValueOf(ptr).Elem().Interface() } -func TestSubspaceCreation(t *testing.T) { - _, _, _, _, keeper := testComponents() - require.Panics(t, func() { keeper.Subspace("") }, "creating subspace with empty should panic") - - // create keeper - _ = keeper.Subspace("test") - require.Panics(t, func() { keeper.Subspace("test") }, "creating subspace with same key should panic") - - _, ok := keeper.GetSubspace("test1") - require.False(t, ok, "getting subspace with no key not return not-ok result") -} - func TestSubspace(t *testing.T) { cdc, ctx, key, _, keeper := testComponents() @@ -141,7 +126,7 @@ func TestSubspace(t *testing.T) { {"struct", s{1}, s{0}, new(s)}, } - table := subspace.NewKeyTable( + table := NewKeyTable( []byte("string"), string(""), []byte("bool"), bool(false), []byte("int16"), int16(0), @@ -205,27 +190,23 @@ func TestJSONUpdate(t *testing.T) { key := []byte("key") - space := keeper.Subspace("test").WithKeyTable(subspace.NewKeyTable(key, paramJSON{})) + space := keeper.Subspace("test").WithKeyTable(NewKeyTable(key, paramJSON{})) var param paramJSON - err := space.Update(ctx, key, []byte(`{"param1": "10241024"}`)) - require.NoError(t, err) + space.Update(ctx, key, []byte(`{"param1": "10241024"}`)) space.Get(ctx, key, ¶m) require.Equal(t, paramJSON{10241024, ""}, param) - err = space.Update(ctx, key, []byte(`{"param2": "helloworld"}`)) - require.NoError(t, err) + space.Update(ctx, key, []byte(`{"param2": "helloworld"}`)) space.Get(ctx, key, ¶m) require.Equal(t, paramJSON{10241024, "helloworld"}, param) - err = space.Update(ctx, key, []byte(`{"param1": "20482048"}`)) - require.NoError(t, err) + space.Update(ctx, key, []byte(`{"param1": "20482048"}`)) space.Get(ctx, key, ¶m) require.Equal(t, paramJSON{20482048, "helloworld"}, param) - err = space.Update(ctx, key, []byte(`{"param1": "40964096", "param2": "goodbyeworld"}`)) - require.NoError(t, err) + space.Update(ctx, key, []byte(`{"param1": "40964096", "param2": "goodbyeworld"}`)) space.Get(ctx, key, ¶m) require.Equal(t, paramJSON{40964096, "goodbyeworld"}, param) } diff --git a/params/module.go b/params/module.go index 8d49108fb..e339d9549 100644 --- a/params/module.go +++ b/params/module.go @@ -9,39 +9,39 @@ import ( "github.com/cosmos/cosmos-sdk/client/context" "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/types/module" - - "github.com/maticnetwork/heimdall/params/types" + "github.com/cosmos/cosmos-sdk/x/params/types" ) var ( _ module.AppModuleBasic = AppModuleBasic{} ) -// AppModuleBasic app module basics object +const moduleName = "params" + +// app module basics object type AppModuleBasic struct{} -// Name module name +// module name func (AppModuleBasic) Name() string { - return types.ModuleName + return moduleName } -// RegisterCodec register module codec -func (AppModuleBasic) RegisterCodec(cdc *codec.Codec) { types.RegisterCodec(cdc) } +// register module codec +func (AppModuleBasic) RegisterCodec(cdc *codec.Codec) { + types.RegisterCodec(cdc) +} -// DefaultGenesis default genesis state +// default genesis state func (AppModuleBasic) DefaultGenesis() json.RawMessage { return nil } -// ValidateGenesis module validate genesis +// module validate genesis func (AppModuleBasic) ValidateGenesis(_ json.RawMessage) error { return nil } -// VerifyGenesis module -func (AppModuleBasic) VerifyGenesis(bz map[string]json.RawMessage) error { return nil } - -// RegisterRESTRoutes register rest routes +// register rest routes func (AppModuleBasic) RegisterRESTRoutes(_ context.CLIContext, _ *mux.Router) {} -// GetTxCmd get the root tx command of this module +// get the root tx command of this module func (AppModuleBasic) GetTxCmd(_ *codec.Codec) *cobra.Command { return nil } -// GetQueryCmd get the root query command of this module +// get the root query command of this module func (AppModuleBasic) GetQueryCmd(_ *codec.Codec) *cobra.Command { return nil } diff --git a/params/proposal_handler.go b/params/proposal_handler.go index 3493f1c35..a40d8df83 100644 --- a/params/proposal_handler.go +++ b/params/proposal_handler.go @@ -4,15 +4,13 @@ import ( "fmt" sdk "github.com/cosmos/cosmos-sdk/types" - govtypes "github.com/maticnetwork/heimdall/gov/types" - "github.com/maticnetwork/heimdall/params/types" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" ) -// NewParamChangeProposalHandler new param changes proposal handler func NewParamChangeProposalHandler(k Keeper) govtypes.Handler { return func(ctx sdk.Context, content govtypes.Content) sdk.Error { switch c := content.(type) { - case types.ParameterChangeProposal: + case ParameterChangeProposal: return handleParameterChangeProposal(ctx, k, c) default: @@ -22,19 +20,29 @@ func NewParamChangeProposalHandler(k Keeper) govtypes.Handler { } } -func handleParameterChangeProposal(ctx sdk.Context, k Keeper, p types.ParameterChangeProposal) sdk.Error { +func handleParameterChangeProposal(ctx sdk.Context, k Keeper, p ParameterChangeProposal) sdk.Error { for _, c := range p.Changes { ss, ok := k.GetSubspace(c.Subspace) if !ok { - return types.ErrUnknownSubspace(k.codespace, c.Subspace) + return ErrUnknownSubspace(k.codespace, c.Subspace) } - k.Logger(ctx).Info( - fmt.Sprintf("setting new parameter; key: %s, value: %s", c.Key, c.Value), - ) + var err error + if len(c.Subkey) == 0 { + k.Logger(ctx).Info( + fmt.Sprintf("setting new parameter; key: %s, value: %s", c.Key, c.Value), + ) + + err = ss.Update(ctx, []byte(c.Key), []byte(c.Value)) + } else { + k.Logger(ctx).Info( + fmt.Sprintf("setting new parameter; key: %s, subkey: %s, value: %s", c.Key, c.Subspace, c.Value), + ) + err = ss.UpdateWithSubkey(ctx, []byte(c.Key), []byte(c.Subkey), []byte(c.Value)) + } - if err := ss.Update(ctx, []byte(c.Key), []byte(c.Value)); err != nil { - return types.ErrSettingParameter(k.codespace, c.Key, c.Value, err.Error()) + if err != nil { + return ErrSettingParameter(k.codespace, c.Key, c.Subkey, c.Value, err.Error()) } } diff --git a/params/proposal_handler_test.go b/params/proposal_handler_test.go index 04140b1aa..6797929a0 100644 --- a/params/proposal_handler_test.go +++ b/params/proposal_handler_test.go @@ -3,18 +3,18 @@ package params_test import ( "testing" - "github.com/cosmos/cosmos-sdk/codec" - "github.com/cosmos/cosmos-sdk/store" - sdk "github.com/cosmos/cosmos-sdk/types" "github.com/stretchr/testify/require" abci "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/tendermint/libs/log" + dbm "github.com/tendermint/tm-db" - "github.com/maticnetwork/heimdall/params" - "github.com/maticnetwork/heimdall/params/subspace" - "github.com/maticnetwork/heimdall/params/types" - paramTypes "github.com/maticnetwork/heimdall/params/types" + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/store" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/params" + "github.com/cosmos/cosmos-sdk/x/params/subspace" + "github.com/cosmos/cosmos-sdk/x/params/types" ) type testInput struct { @@ -43,22 +43,13 @@ type testParams struct { func (tp *testParams) ParamSetPairs() subspace.ParamSetPairs { return subspace.ParamSetPairs{ - {Key: []byte(keyMaxValidators), Value: &tp.MaxValidators}, - {Key: []byte(keySlashingRate), Value: &tp.SlashingRate}, + {[]byte(keyMaxValidators), &tp.MaxValidators}, + {[]byte(keySlashingRate), &tp.SlashingRate}, } } -type invalidParamProposal struct{} - -func (invalidParamProposal) GetTitle() string { return "" } -func (invalidParamProposal) GetDescription() string { return "" } -func (invalidParamProposal) ProposalRoute() string { return "" } -func (invalidParamProposal) ProposalType() string { return "" } -func (invalidParamProposal) ValidateBasic() sdk.Error { return nil } -func (invalidParamProposal) String() string { return "" } - -func testProposal(changes ...paramTypes.ParamChange) paramTypes.ParameterChangeProposal { - return paramTypes.NewParameterChangeProposal( +func testProposal(changes ...params.ParamChange) params.ParameterChangeProposal { + return params.NewParameterChangeProposal( "Test", "description", changes, @@ -66,8 +57,6 @@ func testProposal(changes ...paramTypes.ParamChange) paramTypes.ParameterChangeP } func newTestInput(t *testing.T) testInput { - t.Helper() - cdc := codec.New() types.RegisterCodec(cdc) @@ -83,7 +72,7 @@ func newTestInput(t *testing.T) testInput { err := cms.LoadLatestVersion() require.Nil(t, err) - keeper := params.NewKeeper(cdc, keyParams, tKeyParams, paramTypes.DefaultCodespace) + keeper := params.NewKeeper(cdc, keyParams, tKeyParams, params.DefaultCodespace) ctx := sdk.NewContext(cms, abci.Header{}, false, log.NewNopLogger()) return testInput{ctx, cdc, keeper} @@ -92,10 +81,10 @@ func newTestInput(t *testing.T) testInput { func TestProposalHandlerPassed(t *testing.T) { input := newTestInput(t) ss := input.keeper.Subspace(testSubspace).WithKeyTable( - subspace.NewKeyTable().RegisterParamSet(&testParams{}), + params.NewKeyTable().RegisterParamSet(&testParams{}), ) - tp := testProposal(paramTypes.NewParamChange(testSubspace, keyMaxValidators, "1")) + tp := testProposal(params.NewParamChange(testSubspace, keyMaxValidators, "1")) hdlr := params.NewParamChangeProposalHandler(input.keeper) require.NoError(t, hdlr(input.ctx, tp)) @@ -107,43 +96,32 @@ func TestProposalHandlerPassed(t *testing.T) { func TestProposalHandlerFailed(t *testing.T) { input := newTestInput(t) ss := input.keeper.Subspace(testSubspace).WithKeyTable( - subspace.NewKeyTable().RegisterParamSet(&testParams{}), + params.NewKeyTable().RegisterParamSet(&testParams{}), ) - tp := testProposal(paramTypes.NewParamChange(testSubspace, keyMaxValidators, "invalidType")) + tp := testProposal(params.NewParamChange(testSubspace, keyMaxValidators, "invalidType")) hdlr := params.NewParamChangeProposalHandler(input.keeper) require.Error(t, hdlr(input.ctx, tp)) require.False(t, ss.Has(input.ctx, []byte(keyMaxValidators))) - - require.Error(t, hdlr(input.ctx, invalidParamProposal{})) -} - -func TestProposalHandlerSubspaceFailed(t *testing.T) { - input := newTestInput(t) - - // without subspace - tp := testProposal(paramTypes.NewParamChange(testSubspace, keySlashingRate, `{"downtime": 7}`)) - hdlr := params.NewParamChangeProposalHandler(input.keeper) - require.Error(t, hdlr(input.ctx, tp)) } func TestProposalHandlerUpdateOmitempty(t *testing.T) { input := newTestInput(t) ss := input.keeper.Subspace(testSubspace).WithKeyTable( - subspace.NewKeyTable().RegisterParamSet(&testParams{}), + params.NewKeyTable().RegisterParamSet(&testParams{}), ) hdlr := params.NewParamChangeProposalHandler(input.keeper) var param testParamsSlashingRate - tp := testProposal(paramTypes.NewParamChange(testSubspace, keySlashingRate, `{"downtime": 7}`)) + tp := testProposal(params.NewParamChange(testSubspace, keySlashingRate, `{"downtime": 7}`)) require.NoError(t, hdlr(input.ctx, tp)) ss.Get(input.ctx, []byte(keySlashingRate), ¶m) require.Equal(t, testParamsSlashingRate{0, 7}, param) - tp = testProposal(paramTypes.NewParamChange(testSubspace, keySlashingRate, `{"double_sign": 10}`)) + tp = testProposal(params.NewParamChange(testSubspace, keySlashingRate, `{"double_sign": 10}`)) require.NoError(t, hdlr(input.ctx, tp)) ss.Get(input.ctx, []byte(keySlashingRate), ¶m) diff --git a/params/simulation/msgs.go b/params/simulation/msgs.go new file mode 100644 index 000000000..df303a8b2 --- /dev/null +++ b/params/simulation/msgs.go @@ -0,0 +1,185 @@ +package simulation + +import ( + "encoding/json" + "fmt" + "math/rand" + "time" + + "github.com/cosmos/cosmos-sdk/baseapp" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/gov" + "github.com/cosmos/cosmos-sdk/x/params" + "github.com/cosmos/cosmos-sdk/x/simulation" +) + +type simParamChange struct { + subspace string + key string + subkey string + simValue func(r *rand.Rand) string +} + +func (spc simParamChange) compKey() string { + return fmt.Sprintf("%s/%s/%s", spc.subkey, spc.key, spc.subkey) +} + +// paramChangePool defines a static slice of possible simulated parameter changes +// where each simParamChange corresponds to a ParamChange with a simValue +// function to generate a simulated new value. +var paramChangePool = []simParamChange{ + // staking parameters + { + "staking", + "MaxValidators", + "", + func(r *rand.Rand) string { + return fmt.Sprintf("%d", simulation.ModuleParamSimulator[simulation.MaxValidators](r).(uint16)) + }, + }, + { + "staking", + "UnbondingTime", + "", + func(r *rand.Rand) string { + return fmt.Sprintf("\"%d\"", simulation.ModuleParamSimulator[simulation.UnbondingTime](r).(time.Duration)) + }, + }, + // slashing parameters + { + "slashing", + "SignedBlocksWindow", + "", + func(r *rand.Rand) string { + return fmt.Sprintf("\"%d\"", simulation.ModuleParamSimulator[simulation.SignedBlocksWindow](r).(int64)) + }, + }, + { + "slashing", + "MinSignedPerWindow", + "", + func(r *rand.Rand) string { + return fmt.Sprintf("\"%s\"", simulation.ModuleParamSimulator[simulation.MinSignedPerWindow](r).(sdk.Dec)) + }, + }, + { + "slashing", + "SlashFractionDowntime", + "", + func(r *rand.Rand) string { + return fmt.Sprintf("\"%s\"", simulation.ModuleParamSimulator[simulation.SlashFractionDowntime](r).(sdk.Dec)) + }, + }, + // minting parameters + { + "mint", + "InflationRateChange", + "", + func(r *rand.Rand) string { + return fmt.Sprintf("\"%s\"", simulation.ModuleParamSimulator[simulation.InflationRateChange](r).(sdk.Dec)) + }, + }, + // gov parameters + { + "gov", + "votingparams", + "", + func(r *rand.Rand) string { + return fmt.Sprintf(`{"voting_period": "%d"}`, simulation.ModuleParamSimulator[simulation.VotingParamsVotingPeriod](r).(time.Duration)) + }, + }, + { + "gov", + "depositparams", + "", + func(r *rand.Rand) string { + return fmt.Sprintf(`{"max_deposit_period": "%d"}`, simulation.ModuleParamSimulator[simulation.VotingParamsVotingPeriod](r).(time.Duration)) + }, + }, + { + "gov", + "tallyparams", + "", + func(r *rand.Rand) string { + changes := []struct { + key string + value sdk.Dec + }{ + {"quorum", simulation.ModuleParamSimulator[simulation.TallyParamsQuorum](r).(sdk.Dec)}, + {"threshold", simulation.ModuleParamSimulator[simulation.TallyParamsThreshold](r).(sdk.Dec)}, + {"veto", simulation.ModuleParamSimulator[simulation.TallyParamsVeto](r).(sdk.Dec)}, + } + + pc := make(map[string]string) + numChanges := simulation.RandIntBetween(r, 1, len(changes)) + for i := 0; i < numChanges; i++ { + c := changes[r.Intn(len(changes))] + + _, ok := pc[c.key] + for ok { + c := changes[r.Intn(len(changes))] + _, ok = pc[c.key] + } + + pc[c.key] = c.value.String() + } + + bz, _ := json.Marshal(pc) + return string(bz) + }, + }, + // auth parameters + { + "auth", + "MaxMemoCharacters", + "", + func(r *rand.Rand) string { + return fmt.Sprintf("\"%d\"", simulation.ModuleParamSimulator[simulation.MaxMemoChars](r).(uint64)) + }, + }, + { + "auth", + "TxSigLimit", + "", + func(r *rand.Rand) string { + return fmt.Sprintf("\"%d\"", simulation.ModuleParamSimulator[simulation.TxSigLimit](r).(uint64)) + }, + }, + { + "auth", + "TxSizeCostPerByte", + "", + func(r *rand.Rand) string { + return fmt.Sprintf("\"%d\"", simulation.ModuleParamSimulator[simulation.TxSizeCostPerByte](r).(uint64)) + }, + }, +} + +// SimulateParamChangeProposalContent returns random parameter change content. +// It will generate a ParameterChangeProposal object with anywhere between 1 and +// 3 parameter changes all of which have random, but valid values. +func SimulateParamChangeProposalContent(r *rand.Rand, _ *baseapp.BaseApp, _ sdk.Context, _ []simulation.Account) gov.Content { + numChanges := simulation.RandIntBetween(r, 1, len(paramChangePool)/2) + paramChanges := make([]params.ParamChange, numChanges, numChanges) + paramChangesKeys := make(map[string]struct{}) + + for i := 0; i < numChanges; i++ { + spc := paramChangePool[r.Intn(len(paramChangePool))] + + // do not include duplicate parameter changes for a given subspace/key + _, ok := paramChangesKeys[spc.compKey()] + for ok { + spc = paramChangePool[r.Intn(len(paramChangePool))] + _, ok = paramChangesKeys[spc.compKey()] + } + + paramChangesKeys[spc.compKey()] = struct{}{} + paramChanges[i] = params.NewParamChangeWithSubkey(spc.subspace, spc.key, spc.subkey, spc.simValue(r)) + } + + return params.NewParameterChangeProposal( + simulation.RandStringOfLength(r, 140), + simulation.RandStringOfLength(r, 5000), + paramChanges, + ) +} diff --git a/params/subspace/subspace.go b/params/subspace/subspace.go index 486a67eb2..457c01333 100644 --- a/params/subspace/subspace.go +++ b/params/subspace/subspace.go @@ -1,6 +1,7 @@ package subspace import ( + "errors" "reflect" "github.com/cosmos/cosmos-sdk/codec" @@ -50,7 +51,6 @@ func (s Subspace) WithKeyTable(table KeyTable) Subspace { if table.m == nil { panic("SetKeyTable() called with nil KeyTable") } - if len(s.table.m) != 0 { panic("SetKeyTable() called on already initialized Subspace") } @@ -82,12 +82,20 @@ func (s Subspace) transientStore(ctx sdk.Context) sdk.KVStore { return prefix.NewStore(ctx.TransientStore(s.tkey), append(s.name, '/')) } +func concatKeys(key, subkey []byte) (res []byte) { + res = make([]byte, len(key)+1+len(subkey)) + copy(res, key) + res[len(key)] = '/' + copy(res[len(key)+1:], subkey) + return +} + // Get parameter from store func (s Subspace) Get(ctx sdk.Context, key []byte, ptr interface{}) { store := s.kvStore(ctx) bz := store.Get(key) - - if err := s.cdc.UnmarshalJSON(bz, ptr); err != nil { + err := s.cdc.UnmarshalJSON(bz, ptr) + if err != nil { panic(err) } } @@ -95,18 +103,28 @@ func (s Subspace) Get(ctx sdk.Context, key []byte, ptr interface{}) { // GetIfExists do not modify ptr if the stored parameter is nil func (s Subspace) GetIfExists(ctx sdk.Context, key []byte, ptr interface{}) { store := s.kvStore(ctx) - bz := store.Get(key) if bz == nil { return } - - if err := s.cdc.UnmarshalJSON(bz, ptr); err != nil { + err := s.cdc.UnmarshalJSON(bz, ptr) + if err != nil { panic(err) } } -// GetRaw returns raw bytes of parameter from store +// GetWithSubkey returns a parameter with a given key and a subkey. +func (s Subspace) GetWithSubkey(ctx sdk.Context, key, subkey []byte, ptr interface{}) { + s.Get(ctx, concatKeys(key, subkey), ptr) +} + +// GetWithSubkeyIfExists returns a parameter with a given key and a subkey but does not +// modify ptr if the stored parameter is nil. +func (s Subspace) GetWithSubkeyIfExists(ctx sdk.Context, key, subkey []byte, ptr interface{}) { + s.GetIfExists(ctx, concatKeys(key, subkey), ptr) +} + +// Get raw bytes of parameter from store func (s Subspace) GetRaw(ctx sdk.Context, key []byte) []byte { store := s.kvStore(ctx) return store.Get(key) @@ -131,7 +149,6 @@ func (s Subspace) checkType(store sdk.KVStore, key []byte, param interface{}) { } ty := attr.ty - pty := reflect.TypeOf(param) if pty.Kind() == reflect.Ptr { pty = pty.Elem() @@ -153,11 +170,11 @@ func (s Subspace) Set(ctx sdk.Context, key []byte, param interface{}) { if err != nil { panic(err) } - store.Set(key, bz) tstore := s.transientStore(ctx) tstore.Set(key, []byte{}) + } // Update stores raw parameter bytes. It returns error if the stored parameter @@ -171,10 +188,9 @@ func (s Subspace) Update(ctx sdk.Context, key []byte, param []byte) error { ty := attr.ty dest := reflect.New(ty).Interface() - s.GetIfExists(ctx, key, dest) - - if err := s.cdc.UnmarshalJSON(param, dest); err != nil { + err := s.cdc.UnmarshalJSON(param, dest) + if err != nil { return err } @@ -185,6 +201,50 @@ func (s Subspace) Update(ctx sdk.Context, key []byte, param []byte) error { return nil } +// SetWithSubkey set a parameter with a key and subkey +// Checks parameter type only over the key +func (s Subspace) SetWithSubkey(ctx sdk.Context, key []byte, subkey []byte, param interface{}) { + store := s.kvStore(ctx) + + s.checkType(store, key, param) + + newkey := concatKeys(key, subkey) + + bz, err := s.cdc.MarshalJSON(param) + if err != nil { + panic(err) + } + store.Set(newkey, bz) + + tstore := s.transientStore(ctx) + tstore.Set(newkey, []byte{}) +} + +// UpdateWithSubkey stores raw parameter bytes with a key and subkey. It checks +// the parameter type only over the key. +func (s Subspace) UpdateWithSubkey(ctx sdk.Context, key []byte, subkey []byte, param []byte) error { + concatkey := concatKeys(key, subkey) + + attr, ok := s.table.m[string(concatkey)] + if !ok { + return errors.New("parameter not registered") + } + + ty := attr.ty + dest := reflect.New(ty).Interface() + s.GetWithSubkeyIfExists(ctx, key, subkey, dest) + err := s.cdc.UnmarshalJSON(param, dest) + if err != nil { + return err + } + + s.SetWithSubkey(ctx, key, subkey, dest) + tStore := s.transientStore(ctx) + tStore.Set(concatkey, []byte{}) + + return nil +} + // Get to ParamSet func (s Subspace) GetParamSet(ctx sdk.Context, ps ParamSet) { for _, pair := range ps.ParamSetPairs() { diff --git a/params/subspace/table.go b/params/subspace/table.go index d6c886755..75269cc06 100644 --- a/params/subspace/table.go +++ b/params/subspace/table.go @@ -38,7 +38,6 @@ func isAlphaNumeric(key []byte) bool { return false } } - return true } @@ -47,11 +46,9 @@ func (t KeyTable) RegisterType(key []byte, ty interface{}) KeyTable { if len(key) == 0 { panic("cannot register empty key") } - if !isAlphaNumeric(key) { panic("non alphanumeric parameter key") } - keystr := string(key) if _, ok := t.m[keystr]; ok { panic("duplicate parameter key") @@ -71,12 +68,11 @@ func (t KeyTable) RegisterType(key []byte, ty interface{}) KeyTable { return t } -// RegisterParamSet registers multiple pairs from ParamSet +// Register multiple pairs from ParamSet func (t KeyTable) RegisterParamSet(ps ParamSet) KeyTable { for _, kvp := range ps.ParamSetPairs() { t = t.RegisterType(kvp.Key, kvp.Value) } - return t } @@ -87,6 +83,5 @@ func (t KeyTable) maxKeyLength() (res int) { res = l } } - return } diff --git a/params/subspace/table_test.go b/params/subspace/table_test.go index ffb456682..f1485b6e2 100644 --- a/params/subspace/table_test.go +++ b/params/subspace/table_test.go @@ -19,8 +19,6 @@ func (tp *testparams) ParamSetPairs() ParamSetPairs { } func TestKeyTable(t *testing.T) { - t.Parallel() - table := NewKeyTable() require.Panics(t, func() { table.RegisterType([]byte(""), nil) }) diff --git a/params/subspace/test_common.go b/params/subspace/test_common.go index 6b4b4b307..2b1d817be 100644 --- a/params/subspace/test_common.go +++ b/params/subspace/test_common.go @@ -1,6 +1,40 @@ package subspace +import ( + "os" + "testing" + + "github.com/stretchr/testify/require" + + abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/libs/log" + dbm "github.com/tendermint/tm-db" + + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/store" + sdk "github.com/cosmos/cosmos-sdk/types" +) + // Keys for parameter access const ( TestParamStore = "ParamsTest" ) + +// Returns components for testing +func DefaultTestComponents(t *testing.T) (sdk.Context, Subspace, func() sdk.CommitID) { + cdc := codec.New() + key := sdk.NewKVStoreKey(StoreKey) + tkey := sdk.NewTransientStoreKey(TStoreKey) + db := dbm.NewMemDB() + ms := store.NewCommitMultiStore(db) + ms.SetTracer(os.Stdout) + ms.SetTracingContext(sdk.TraceContext{}) + ms.MountStoreWithDB(key, sdk.StoreTypeIAVL, db) + ms.MountStoreWithDB(tkey, sdk.StoreTypeTransient, db) + err := ms.LoadLatestVersion() + require.Nil(t, err) + ctx := sdk.NewContext(ms, abci.Header{}, false, log.NewTMLogger(os.Stdout)) + subspace := NewSubspace(cdc, key, tkey, TestParamStore) + + return ctx, subspace, ms.Commit +} diff --git a/params/test_common.go b/params/test_common.go new file mode 100644 index 000000000..59f718103 --- /dev/null +++ b/params/test_common.go @@ -0,0 +1,49 @@ +// nolint: deadcode unused +package params + +import ( + abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/libs/log" + dbm "github.com/tendermint/tm-db" + + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/store" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +type invalid struct{} + +type s struct { + I int +} + +func createTestCodec() *codec.Codec { + cdc := codec.New() + sdk.RegisterCodec(cdc) + cdc.RegisterConcrete(s{}, "test/s", nil) + cdc.RegisterConcrete(invalid{}, "test/invalid", nil) + return cdc +} + +func defaultContext(key sdk.StoreKey, tkey sdk.StoreKey) sdk.Context { + db := dbm.NewMemDB() + cms := store.NewCommitMultiStore(db) + cms.MountStoreWithDB(key, sdk.StoreTypeIAVL, db) + cms.MountStoreWithDB(tkey, sdk.StoreTypeTransient, db) + err := cms.LoadLatestVersion() + if err != nil { + panic(err) + } + ctx := sdk.NewContext(cms, abci.Header{}, false, log.NewNopLogger()) + return ctx +} + +func testComponents() (*codec.Codec, sdk.Context, sdk.StoreKey, sdk.StoreKey, Keeper) { + cdc := createTestCodec() + mkey := sdk.NewKVStoreKey("test") + tkey := sdk.NewTransientStoreKey("transient_test") + ctx := defaultContext(mkey, tkey) + keeper := NewKeeper(cdc, mkey, tkey, DefaultCodespace) + + return cdc, ctx, mkey, tkey, keeper +} diff --git a/params/types/codec.go b/params/types/codec.go index cbb847e6f..525836346 100644 --- a/params/types/codec.go +++ b/params/types/codec.go @@ -4,7 +4,7 @@ import ( "github.com/cosmos/cosmos-sdk/codec" ) -// ModuleCdc module codec +// module codec var ModuleCdc *codec.Codec func init() { @@ -15,5 +15,5 @@ func init() { // RegisterCodec registers all necessary param module types with a given codec. func RegisterCodec(cdc *codec.Codec) { - cdc.RegisterConcrete(ParameterChangeProposal{}, "heimdall/ParameterChangeProposal", nil) + cdc.RegisterConcrete(ParameterChangeProposal{}, "cosmos-sdk/ParameterChangeProposal", nil) } diff --git a/params/types/errors.go b/params/types/errors.go index 12726b032..bbf8f5a2b 100644 --- a/params/types/errors.go +++ b/params/types/errors.go @@ -21,8 +21,8 @@ func ErrUnknownSubspace(codespace sdk.CodespaceType, space string) sdk.Error { } // ErrSettingParameter returns an error for failing to set a parameter. -func ErrSettingParameter(codespace sdk.CodespaceType, key, value, msg string) sdk.Error { - return sdk.NewError(codespace, CodeSettingParameter, fmt.Sprintf("error setting parameter %s on %s: %s", value, key, msg)) +func ErrSettingParameter(codespace sdk.CodespaceType, key, subkey, value, msg string) sdk.Error { + return sdk.NewError(codespace, CodeSettingParameter, fmt.Sprintf("error setting parameter %s on %s (%s): %s", value, key, subkey, msg)) } // ErrEmptyChanges returns an error for empty parameter changes. diff --git a/params/types/keys.go b/params/types/keys.go index d8779558b..e6b1e301c 100644 --- a/params/types/keys.go +++ b/params/types/keys.go @@ -1,21 +1,9 @@ package types const ( - // ModuleName defines the name of the module + // ModuleKey defines the name of the module ModuleName = "params" - // StoreKey is the store key string for bor - StoreKey = ModuleName - - // RouterKey is the message route for bor - RouterKey = ModuleName - - // QuerierRoute is the querier route for bor - QuerierRoute = ModuleName - - // DefaultParamspace default name for parameter store - DefaultParamspace = ModuleName - - // TStoreKey is the string store key for the param transient store - TStoreKey = "transient_params" + // RouterKey defines the routing key for a ParameterChangeProposal + RouterKey = "params" ) diff --git a/params/types/proposal.go b/params/types/proposal.go index e565e409c..1039a082e 100644 --- a/params/types/proposal.go +++ b/params/types/proposal.go @@ -5,7 +5,7 @@ import ( "strings" sdk "github.com/cosmos/cosmos-sdk/types" - govTypes "github.com/maticnetwork/heimdall/gov/types" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" ) const ( @@ -14,11 +14,11 @@ const ( ) // Assert ParameterChangeProposal implements govtypes.Content at compile-time -var _ govTypes.Content = ParameterChangeProposal{} +var _ govtypes.Content = ParameterChangeProposal{} func init() { - govTypes.RegisterProposalType(ProposalTypeChange) - govTypes.RegisterProposalTypeCodec(ParameterChangeProposal{}, "heimdall/ParameterChangeProposal") + govtypes.RegisterProposalType(ProposalTypeChange) + govtypes.RegisterProposalTypeCodec(ParameterChangeProposal{}, "cosmos-sdk/ParameterChangeProposal") } // ParameterChangeProposal defines a proposal which contains multiple parameter @@ -47,7 +47,7 @@ func (pcp ParameterChangeProposal) ProposalType() string { return ProposalTypeCh // ValidateBasic validates the parameter change proposal func (pcp ParameterChangeProposal) ValidateBasic() sdk.Error { - err := govTypes.ValidateAbstract(DefaultCodespace, pcp) + err := govtypes.ValidateAbstract(DefaultCodespace, pcp) if err != nil { return err } @@ -69,8 +69,9 @@ func (pcp ParameterChangeProposal) String() string { b.WriteString(fmt.Sprintf(` Param Change: Subspace: %s Key: %s + Subkey: %X Value: %X -`, pc.Subspace, pc.Key, pc.Value)) +`, pc.Subspace, pc.Key, pc.Subkey, pc.Value)) } return b.String() @@ -80,11 +81,16 @@ func (pcp ParameterChangeProposal) String() string { type ParamChange struct { Subspace string `json:"subspace" yaml:"subspace"` Key string `json:"key" yaml:"key"` + Subkey string `json:"subkey,omitempty" yaml:"subkey,omitempty"` Value string `json:"value" yaml:"value"` } func NewParamChange(subspace, key, value string) ParamChange { - return ParamChange{subspace, key, value} + return ParamChange{subspace, key, "", value} +} + +func NewParamChangeWithSubkey(subspace, key, subkey, value string) ParamChange { + return ParamChange{subspace, key, subkey, value} } // String implements the Stringer interface. @@ -92,8 +98,9 @@ func (pc ParamChange) String() string { return fmt.Sprintf(`Param Change: Subspace: %s Key: %s + Subkey: %X Value: %X -`, pc.Subspace, pc.Key, pc.Value) +`, pc.Subspace, pc.Key, pc.Subkey, pc.Value) } // ValidateChange performs basic validation checks over a set of ParamChange. It @@ -107,11 +114,9 @@ func ValidateChanges(changes []ParamChange) sdk.Error { if len(pc.Subspace) == 0 { return ErrEmptySubspace(DefaultCodespace) } - if len(pc.Key) == 0 { return ErrEmptyKey(DefaultCodespace) } - if len(pc.Value) == 0 { return ErrEmptyValue(DefaultCodespace) } diff --git a/params/types/proposal_test.go b/params/types/proposal_test.go index e82a28e14..e4865aad7 100644 --- a/params/types/proposal_test.go +++ b/params/types/proposal_test.go @@ -7,14 +7,25 @@ import ( ) func TestParameterChangeProposal(t *testing.T) { - t.Parallel() - pc1 := NewParamChange("sub", "foo", "baz") - pcp := NewParameterChangeProposal("test title", "test description", []ParamChange{pc1}) + pc2 := NewParamChangeWithSubkey("sub", "bar", "cat", "dog") + pcp := NewParameterChangeProposal("test title", "test description", []ParamChange{pc1, pc2}) require.Equal(t, "test title", pcp.GetTitle()) require.Equal(t, "test description", pcp.GetDescription()) require.Equal(t, RouterKey, pcp.ProposalRoute()) require.Equal(t, ProposalTypeChange, pcp.ProposalType()) require.Nil(t, pcp.ValidateBasic()) + + pc3 := NewParamChangeWithSubkey("", "bar", "cat", "dog") + pcp = NewParameterChangeProposal("test title", "test description", []ParamChange{pc3}) + require.Error(t, pcp.ValidateBasic()) + + pc4 := NewParamChangeWithSubkey("sub", "", "cat", "dog") + pcp = NewParameterChangeProposal("test title", "test description", []ParamChange{pc4}) + require.Error(t, pcp.ValidateBasic()) + + pc5 := NewParamChangeWithSubkey("sub", "foo", "cat", "") + pcp = NewParameterChangeProposal("test title", "test description", []ParamChange{pc5}) + require.Error(t, pcp.ValidateBasic()) } diff --git a/slashing/abci.go b/slashing/abci.go index 23019878c..ef21e4477 100644 --- a/slashing/abci.go +++ b/slashing/abci.go @@ -4,44 +4,29 @@ import ( "fmt" abci "github.com/tendermint/tendermint/abci/types" + tmtypes "github.com/tendermint/tendermint/types" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/maticnetwork/heimdall/slashing/types" - tmtypes "github.com/tendermint/tendermint/types" ) -func BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock, k Keeper) { - - if !k.GetParams(ctx).EnableSlashing { - k.Logger(ctx).Debug("slashing is not enabled. To enable, send a proposal via governance") - return - } - - // BeginBlocker iterates through and handles any newly discovered evidence of - // misbehavior submitted by Tendermint. Currently, only equivocation is handled. - for _, tmEvidence := range req.ByzantineValidators { - switch tmEvidence.Type { - case tmtypes.ABCIEvidenceTypeDuplicateVote: - evidence := types.ConvertDuplicateVoteEvidence(tmEvidence) - if err := k.HandleDoubleSign(ctx, evidence.(types.Equivocation)); err != nil { - k.Logger(ctx).Error("Failed to handle double sign", "Error", err) - } - default: - k.Logger(ctx).Error(fmt.Sprintf("ignored unknown evidence type: %s", tmEvidence.Type)) - } - } - +// slashing begin block functionality +func BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock, sk Keeper) { // Iterate over all the validators which *should* have signed this block // store whether or not they have actually signed it and slash/unbond any // which have missed too many blocks in a row (downtime slashing) for _, voteInfo := range req.LastCommitInfo.GetVotes() { - if err := k.HandleValidatorSignature( - ctx, - voteInfo.Validator.Address, - voteInfo.Validator.Power, - voteInfo.SignedLastBlock, - ); err != nil { - k.Logger(ctx).Error("Failed to handle validator signature", "Error", err, "address", voteInfo.Validator) + sk.HandleValidatorSignature(ctx, voteInfo.Validator.Address, voteInfo.Validator.Power, voteInfo.SignedLastBlock) + } + + // Iterate through any newly discovered evidence of infraction + // Slash any validators (and since-unbonded stake within the unbonding period) + // who contributed to valid infractions + for _, evidence := range req.ByzantineValidators { + switch evidence.Type { + case tmtypes.ABCIEvidenceTypeDuplicateVote: + sk.HandleDoubleSign(ctx, evidence.Validator.Address, evidence.Height, evidence.Time, evidence.Validator.Power) + default: + sk.Logger(ctx).Error(fmt.Sprintf("ignored unknown evidence type: %s", evidence.Type)) } } } diff --git a/slashing/abci_test.go b/slashing/abci_test.go new file mode 100644 index 000000000..5edea6309 --- /dev/null +++ b/slashing/abci_test.go @@ -0,0 +1,91 @@ +package slashing + +import ( + "testing" + "time" + + "github.com/stretchr/testify/require" + + abci "github.com/tendermint/tendermint/abci/types" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/staking" +) + +func TestBeginBlocker(t *testing.T) { + ctx, ck, sk, _, keeper := createTestInput(t, DefaultParams()) + power := int64(100) + amt := sdk.TokensFromConsensusPower(power) + addr, pk := addrs[2], pks[2] + + // bond the validator + got := staking.NewHandler(sk)(ctx, NewTestMsgCreateValidator(addr, pk, amt)) + require.True(t, got.IsOK()) + staking.EndBlocker(ctx, sk) + require.Equal( + t, ck.GetCoins(ctx, sdk.AccAddress(addr)), + sdk.NewCoins(sdk.NewCoin(sk.GetParams(ctx).BondDenom, initTokens.Sub(amt))), + ) + require.Equal(t, amt, sk.Validator(ctx, addr).GetBondedTokens()) + + val := abci.Validator{ + Address: pk.Address(), + Power: amt.Int64(), + } + + // mark the validator as having signed + req := abci.RequestBeginBlock{ + LastCommitInfo: abci.LastCommitInfo{ + Votes: []abci.VoteInfo{{ + Validator: val, + SignedLastBlock: true, + }}, + }, + } + BeginBlocker(ctx, req, keeper) + + info, found := keeper.getValidatorSigningInfo(ctx, sdk.ConsAddress(pk.Address())) + require.True(t, found) + require.Equal(t, ctx.BlockHeight(), info.StartHeight) + require.Equal(t, int64(1), info.IndexOffset) + require.Equal(t, time.Unix(0, 0).UTC(), info.JailedUntil) + require.Equal(t, int64(0), info.MissedBlocksCounter) + + height := int64(0) + + // for 1000 blocks, mark the validator as having signed + for ; height < keeper.SignedBlocksWindow(ctx); height++ { + ctx = ctx.WithBlockHeight(height) + req = abci.RequestBeginBlock{ + LastCommitInfo: abci.LastCommitInfo{ + Votes: []abci.VoteInfo{{ + Validator: val, + SignedLastBlock: true, + }}, + }, + } + BeginBlocker(ctx, req, keeper) + } + + // for 500 blocks, mark the validator as having not signed + for ; height < ((keeper.SignedBlocksWindow(ctx) * 2) - keeper.MinSignedPerWindow(ctx) + 1); height++ { + ctx = ctx.WithBlockHeight(height) + req = abci.RequestBeginBlock{ + LastCommitInfo: abci.LastCommitInfo{ + Votes: []abci.VoteInfo{{ + Validator: val, + SignedLastBlock: false, + }}, + }, + } + BeginBlocker(ctx, req, keeper) + } + + // end block + staking.EndBlocker(ctx, sk) + + // validator should be jailed + validator, found := sk.GetValidatorByConsAddr(ctx, sdk.GetConsAddress(pk)) + require.True(t, found) + require.Equal(t, sdk.Unbonding, validator.GetStatus()) +} diff --git a/slashing/alias.go b/slashing/alias.go new file mode 100644 index 000000000..3ce0c3c71 --- /dev/null +++ b/slashing/alias.go @@ -0,0 +1,96 @@ +// nolint +// autogenerated code using github.com/rigelrozanski/multitool +// aliases generated for the following subdirectories: +// ALIASGEN: github.com/cosmos/cosmos-sdk/x/slashing/types +package slashing + +import ( + "github.com/cosmos/cosmos-sdk/x/slashing/types" +) + +const ( + DefaultCodespace = types.DefaultCodespace + CodeInvalidValidator = types.CodeInvalidValidator + CodeValidatorJailed = types.CodeValidatorJailed + CodeValidatorNotJailed = types.CodeValidatorNotJailed + CodeMissingSelfDelegation = types.CodeMissingSelfDelegation + CodeSelfDelegationTooLow = types.CodeSelfDelegationTooLow + CodeMissingSigningInfo = types.CodeMissingSigningInfo + ModuleName = types.ModuleName + StoreKey = types.StoreKey + RouterKey = types.RouterKey + QuerierRoute = types.QuerierRoute + QueryParameters = types.QueryParameters + QuerySigningInfo = types.QuerySigningInfo + QuerySigningInfos = types.QuerySigningInfos + DefaultParamspace = types.DefaultParamspace + DefaultMaxEvidenceAge = types.DefaultMaxEvidenceAge + DefaultSignedBlocksWindow = types.DefaultSignedBlocksWindow + DefaultDowntimeJailDuration = types.DefaultDowntimeJailDuration +) + +var ( + // functions aliases + RegisterCodec = types.RegisterCodec + ErrNoValidatorForAddress = types.ErrNoValidatorForAddress + ErrBadValidatorAddr = types.ErrBadValidatorAddr + ErrValidatorJailed = types.ErrValidatorJailed + ErrValidatorNotJailed = types.ErrValidatorNotJailed + ErrMissingSelfDelegation = types.ErrMissingSelfDelegation + ErrSelfDelegationTooLowToUnjail = types.ErrSelfDelegationTooLowToUnjail + ErrNoSigningInfoFound = types.ErrNoSigningInfoFound + NewGenesisState = types.NewGenesisState + DefaultGenesisState = types.DefaultGenesisState + ValidateGenesis = types.ValidateGenesis + GetValidatorSigningInfoKey = types.GetValidatorSigningInfoKey + GetValidatorSigningInfoAddress = types.GetValidatorSigningInfoAddress + GetValidatorMissedBlockBitArrayPrefixKey = types.GetValidatorMissedBlockBitArrayPrefixKey + GetValidatorMissedBlockBitArrayKey = types.GetValidatorMissedBlockBitArrayKey + GetAddrPubkeyRelationKey = types.GetAddrPubkeyRelationKey + NewMsgUnjail = types.NewMsgUnjail + ParamKeyTable = types.ParamKeyTable + NewParams = types.NewParams + DefaultParams = types.DefaultParams + NewQuerySigningInfoParams = types.NewQuerySigningInfoParams + NewQuerySigningInfosParams = types.NewQuerySigningInfosParams + NewValidatorSigningInfo = types.NewValidatorSigningInfo + + // variable aliases + ModuleCdc = types.ModuleCdc + ValidatorSigningInfoKey = types.ValidatorSigningInfoKey + ValidatorMissedBlockBitArrayKey = types.ValidatorMissedBlockBitArrayKey + AddrPubkeyRelationKey = types.AddrPubkeyRelationKey + DoubleSignJailEndTime = types.DoubleSignJailEndTime + DefaultMinSignedPerWindow = types.DefaultMinSignedPerWindow + DefaultSlashFractionDoubleSign = types.DefaultSlashFractionDoubleSign + DefaultSlashFractionDowntime = types.DefaultSlashFractionDowntime + KeyMaxEvidenceAge = types.KeyMaxEvidenceAge + KeySignedBlocksWindow = types.KeySignedBlocksWindow + KeyMinSignedPerWindow = types.KeyMinSignedPerWindow + KeyDowntimeJailDuration = types.KeyDowntimeJailDuration + KeySlashFractionDoubleSign = types.KeySlashFractionDoubleSign + KeySlashFractionDowntime = types.KeySlashFractionDowntime + + EventTypeSlash = types.EventTypeSlash + EventTypeLiveness = types.EventTypeLiveness + AttributeKeyAddress = types.AttributeKeyAddress + AttributeKeyHeight = types.AttributeKeyHeight + AttributeKeyPower = types.AttributeKeyPower + AttributeKeyReason = types.AttributeKeyReason + AttributeKeyJailed = types.AttributeKeyJailed + AttributeKeyMissedBlocks = types.AttributeKeyMissedBlocks + AttributeValueDoubleSign = types.AttributeValueDoubleSign + AttributeValueMissingSignature = types.AttributeValueMissingSignature + AttributeValueCategory = types.AttributeValueCategory +) + +type ( + CodeType = types.CodeType + GenesisState = types.GenesisState + MissedBlock = types.MissedBlock + MsgUnjail = types.MsgUnjail + Params = types.Params + QuerySigningInfoParams = types.QuerySigningInfoParams + QuerySigningInfosParams = types.QuerySigningInfosParams + ValidatorSigningInfo = types.ValidatorSigningInfo +) diff --git a/slashing/app_test.go b/slashing/app_test.go new file mode 100644 index 000000000..5ff7d0436 --- /dev/null +++ b/slashing/app_test.go @@ -0,0 +1,156 @@ +package slashing + +import ( + "testing" + + "github.com/stretchr/testify/require" + abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/crypto/secp256k1" + + 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/mock" + "github.com/cosmos/cosmos-sdk/x/staking" + "github.com/cosmos/cosmos-sdk/x/staking/types" + "github.com/cosmos/cosmos-sdk/x/supply" + supplyexported "github.com/cosmos/cosmos-sdk/x/supply/exported" +) + +var ( + priv1 = secp256k1.GenPrivKey() + addr1 = sdk.AccAddress(priv1.PubKey().Address()) + coins = sdk.Coins{sdk.NewInt64Coin("foocoin", 10)} +) + +// initialize the mock application for this module +func getMockApp(t *testing.T) (*mock.App, staking.Keeper, Keeper) { + mapp := mock.NewApp() + + RegisterCodec(mapp.Cdc) + staking.RegisterCodec(mapp.Cdc) + supply.RegisterCodec(mapp.Cdc) + + keyStaking := sdk.NewKVStoreKey(staking.StoreKey) + tkeyStaking := sdk.NewTransientStoreKey(staking.TStoreKey) + keySlashing := sdk.NewKVStoreKey(StoreKey) + keySupply := sdk.NewKVStoreKey(supply.StoreKey) + + feeCollector := supply.NewEmptyModuleAccount(auth.FeeCollectorName) + notBondedPool := supply.NewEmptyModuleAccount(types.NotBondedPoolName, supply.Burner, supply.Staking) + bondPool := supply.NewEmptyModuleAccount(types.BondedPoolName, supply.Burner, supply.Staking) + + blacklistedAddrs := make(map[string]bool) + blacklistedAddrs[feeCollector.String()] = true + blacklistedAddrs[notBondedPool.String()] = true + blacklistedAddrs[bondPool.String()] = true + + bankKeeper := bank.NewBaseKeeper(mapp.AccountKeeper, mapp.ParamsKeeper.Subspace(bank.DefaultParamspace), bank.DefaultCodespace, blacklistedAddrs) + maccPerms := map[string][]string{ + auth.FeeCollectorName: nil, + staking.NotBondedPoolName: []string{supply.Burner, supply.Staking}, + staking.BondedPoolName: []string{supply.Burner, supply.Staking}, + } + supplyKeeper := supply.NewKeeper(mapp.Cdc, keySupply, mapp.AccountKeeper, bankKeeper, maccPerms) + stakingKeeper := staking.NewKeeper(mapp.Cdc, keyStaking, tkeyStaking, supplyKeeper, mapp.ParamsKeeper.Subspace(staking.DefaultParamspace), staking.DefaultCodespace) + keeper := NewKeeper(mapp.Cdc, keySlashing, stakingKeeper, mapp.ParamsKeeper.Subspace(DefaultParamspace), DefaultCodespace) + mapp.Router().AddRoute(staking.RouterKey, staking.NewHandler(stakingKeeper)) + mapp.Router().AddRoute(RouterKey, NewHandler(keeper)) + + mapp.SetEndBlocker(getEndBlocker(stakingKeeper)) + mapp.SetInitChainer(getInitChainer(mapp, stakingKeeper, mapp.AccountKeeper, supplyKeeper, + []supplyexported.ModuleAccountI{feeCollector, notBondedPool, bondPool})) + + require.NoError(t, mapp.CompleteSetup(keyStaking, tkeyStaking, keySupply, keySlashing)) + + return mapp, stakingKeeper, keeper +} + +// staking endblocker +func getEndBlocker(keeper staking.Keeper) sdk.EndBlocker { + return func(ctx sdk.Context, req abci.RequestEndBlock) abci.ResponseEndBlock { + validatorUpdates := staking.EndBlocker(ctx, keeper) + return abci.ResponseEndBlock{ + ValidatorUpdates: validatorUpdates, + } + } +} + +// overwrite the mock init chainer +func getInitChainer(mapp *mock.App, keeper staking.Keeper, accountKeeper types.AccountKeeper, supplyKeeper types.SupplyKeeper, + blacklistedAddrs []supplyexported.ModuleAccountI) sdk.InitChainer { + return func(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain { + // set module accounts + for _, macc := range blacklistedAddrs { + supplyKeeper.SetModuleAccount(ctx, macc) + } + + mapp.InitChainer(ctx, req) + stakingGenesis := staking.DefaultGenesisState() + validators := staking.InitGenesis(ctx, keeper, accountKeeper, supplyKeeper, stakingGenesis) + return abci.ResponseInitChain{ + Validators: validators, + } + } +} + +func checkValidator(t *testing.T, mapp *mock.App, keeper staking.Keeper, + addr sdk.AccAddress, expFound bool) staking.Validator { + ctxCheck := mapp.BaseApp.NewContext(true, abci.Header{}) + validator, found := keeper.GetValidator(ctxCheck, sdk.ValAddress(addr1)) + require.Equal(t, expFound, found) + return validator +} + +func checkValidatorSigningInfo(t *testing.T, mapp *mock.App, keeper Keeper, + addr sdk.ConsAddress, expFound bool) ValidatorSigningInfo { + ctxCheck := mapp.BaseApp.NewContext(true, abci.Header{}) + signingInfo, found := keeper.getValidatorSigningInfo(ctxCheck, addr) + require.Equal(t, expFound, found) + return signingInfo +} + +func TestSlashingMsgs(t *testing.T) { + mapp, stakingKeeper, keeper := getMockApp(t) + + genTokens := sdk.TokensFromConsensusPower(42) + bondTokens := sdk.TokensFromConsensusPower(10) + genCoin := sdk.NewCoin(sdk.DefaultBondDenom, genTokens) + bondCoin := sdk.NewCoin(sdk.DefaultBondDenom, bondTokens) + + acc1 := &auth.BaseAccount{ + Address: addr1, + Coins: sdk.Coins{genCoin}, + } + accs := []auth.Account{acc1} + mock.SetGenesis(mapp, accs) + + description := staking.NewDescription("foo_moniker", "", "", "") + commission := staking.NewCommissionRates(sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec()) + + createValidatorMsg := staking.NewMsgCreateValidator( + sdk.ValAddress(addr1), priv1.PubKey(), bondCoin, description, commission, sdk.OneInt(), + ) + + header := abci.Header{Height: mapp.LastBlockHeight() + 1} + mock.SignCheckDeliver(t, mapp.Cdc, mapp.BaseApp, header, []sdk.Msg{createValidatorMsg}, []uint64{0}, []uint64{0}, true, true, priv1) + mock.CheckBalance(t, mapp, addr1, sdk.Coins{genCoin.Sub(bondCoin)}) + + header = abci.Header{Height: mapp.LastBlockHeight() + 1} + mapp.BeginBlock(abci.RequestBeginBlock{Header: header}) + + validator := checkValidator(t, mapp, stakingKeeper, addr1, true) + require.Equal(t, sdk.ValAddress(addr1), validator.OperatorAddress) + require.Equal(t, sdk.Bonded, validator.Status) + require.True(sdk.IntEq(t, bondTokens, validator.BondedTokens())) + unjailMsg := MsgUnjail{ValidatorAddr: sdk.ValAddress(validator.ConsPubKey.Address())} + + // no signing info yet + checkValidatorSigningInfo(t, mapp, keeper, sdk.ConsAddress(addr1), false) + + // unjail should fail with unknown validator + header = abci.Header{Height: mapp.LastBlockHeight() + 1} + res := mock.SignCheckDeliver(t, mapp.Cdc, mapp.BaseApp, header, []sdk.Msg{unjailMsg}, []uint64{0}, []uint64{1}, false, false, priv1) + require.EqualValues(t, CodeValidatorNotJailed, res.Code) + require.EqualValues(t, DefaultCodespace, res.Codespace) +} diff --git a/slashing/client/cli/flags.go b/slashing/client/cli/flags.go index edc433d6e..09db8e825 100644 --- a/slashing/client/cli/flags.go +++ b/slashing/client/cli/flags.go @@ -1,16 +1,6 @@ package cli +// nolint const ( - FlagProposerAddress = "proposer" - FlagValidatorAddress = "validator" - FlagValidatorID = "id" - FlagTxHash = "tx-hash" - FlagLogIndex = "log-index" - FlagAmount = "slashed-amount" - FlagSlashInfoBytes = "slashinfo-bytes" - FlagTickID = "tick-id" - FlagBlockNumber = "block-number" - FlagId = "id" - FlagPage = "page" - FlagLimit = "limit" + FlagAddressValidator = "validator" ) diff --git a/slashing/client/cli/query.go b/slashing/client/cli/query.go index ef986f96f..07b55458b 100644 --- a/slashing/client/cli/query.go +++ b/slashing/client/cli/query.go @@ -1,22 +1,21 @@ package cli import ( - "encoding/json" "fmt" "strings" + "github.com/spf13/cobra" + "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/context" "github.com/cosmos/cosmos-sdk/codec" - "github.com/spf13/cobra" - "github.com/spf13/viper" + sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/maticnetwork/heimdall/slashing/types" - hmTypes "github.com/maticnetwork/heimdall/types" + "github.com/cosmos/cosmos-sdk/x/slashing/types" ) // GetQueryCmd returns the cli query commands for this module -func GetQueryCmd(cdc *codec.Codec) *cobra.Command { +func GetQueryCmd(queryRoute string, cdc *codec.Codec) *cobra.Command { // Group slashing queries under a subcommand slashingQueryCmd := &cobra.Command{ Use: types.ModuleName, @@ -26,305 +25,52 @@ func GetQueryCmd(cdc *codec.Codec) *cobra.Command { RunE: client.ValidateCmd, } - // slashingQueryCmd query command slashingQueryCmd.AddCommand( client.GetCommands( - // GetCmdQuerySigningInfo(cdc), + GetCmdQuerySigningInfo(queryRoute, cdc), GetCmdQueryParams(cdc), - GetSigningInfo(cdc), - GetSigningInfos(cdc), - GetLatestSlashInfo(cdc), - GetLatestSlashingInfos(cdc), - GetTickSlashingInfos(cdc), - GetLatestSlashInfoBytes(cdc), - GetTickCount(cdc), - IsOldTx(cdc), )..., ) + return slashingQueryCmd } -/* // GetCmdQuerySigningInfo implements the command to query signing info. -func GetCmdQuerySigningInfo(cdc *codec.Codec) *cobra.Command { +// GetCmdQuerySigningInfo implements the command to query signing info. +func GetCmdQuerySigningInfo(storeName string, cdc *codec.Codec) *cobra.Command { return &cobra.Command{ - Use: "signing-info [validator-id]", + Use: "signing-info [validator-conspub]", Short: "Query a validator's signing information", - Long: strings.TrimSpace(`Use a validators' id to find the signing-info for that validator: + Long: strings.TrimSpace(`Use a validators' consensus public key to find the signing-info for that validator: -$ query slashing signing-info {valID} +$ query slashing signing-info cosmosvalconspub1zcjduepqfhvwcmt7p06fvdgexxhmz0l8c7sgswl7ulv7aulk364x4g5xsw7sr0k2g5 `), Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { cliCtx := context.NewCLIContext().WithCodec(cdc) - validatorID := viper.GetUint64(FlagValidatorID) - - if validatorID == 0 { - return fmt.Errorf("validator ID is required") - } - - key := types.GetValidatorSigningInfoKey(hmTypes.NewValidatorID(validatorID).Bytes()) - - res, _, err := cliCtx.QueryStore(key, storeName) - if err != nil { - return err - } - - if len(res) == 0 { - return fmt.Errorf("validator %s not found in slashing store", validatorID) - } - - var signingInfo hmTypes.ValidatorSigningInfo - signingInfo, err = hmTypes.UnmarshallValSigningInfo(types.ModuleCdc, res) - if err != nil { - return err - } - - return cliCtx.PrintOutput(signingInfo) - }, - } -} */ - -//Give signing info by id -func GetSigningInfo(cdc *codec.Codec) *cobra.Command { - cmd := &cobra.Command{ - Use: "signing-info", - Short: "show signing-info by id", - RunE: func(cmd *cobra.Command, args []string) error { - cliCtx := context.NewCLIContext().WithCodec(cdc) - - id := viper.GetUint64(FlagId) - - params := types.NewQuerySigningInfoParams(hmTypes.ValidatorID(id)) - - bz, err := cliCtx.Codec.MarshalJSON(params) - if err != nil { - return err - } - - route := fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QuerySigningInfo) - res, _, err := cliCtx.QueryWithData(route, bz) - if err != nil { - return err - } - - fmt.Println(string(res)) - return nil - }, - } - - cmd.Flags().String(FlagId, "", "--id=") - - if err := cmd.MarkFlagRequired(FlagId); err != nil { - logger.Error("GetSigningInfo | MarkFlagRequired | FlagId", "Error", err) - } - - return cmd -} - -//Give signing info by id -func GetSigningInfos(cdc *codec.Codec) *cobra.Command { - cmd := &cobra.Command{ - Use: "signing-infos", - Short: "show signing-info by id", - RunE: func(cmd *cobra.Command, args []string) error { - cliCtx := context.NewCLIContext().WithCodec(cdc) - - page := viper.GetInt(FlagPage) - - limit := viper.GetInt(FlagLimit) - - params := types.NewQuerySigningInfosParams(page, limit) - - bz, err := cliCtx.Codec.MarshalJSON(params) - if err != nil { - return err - } - - route := fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QuerySigningInfos) - res, _, err := cliCtx.QueryWithData(route, bz) - if err != nil { - return err - } - - fmt.Println(string(res)) - return nil - }, - } - - cmd.Flags().Uint64(FlagPage, 0, "--page=") - cmd.Flags().Uint64(FlagLimit, 0, "--id=") - - if err := cmd.MarkFlagRequired(FlagPage); err != nil { - logger.Error("GetSigningInfos | MarkFlagRequired | FlagPage", "Error", err) - } - - if err := cmd.MarkFlagRequired(FlagLimit); err != nil { - logger.Error("GetSigningInfos | MarkFlagRequired | FlagLimit", "Error", err) - } - - return cmd -} - -func GetLatestSlashInfo(cdc *codec.Codec) *cobra.Command { - cmd := &cobra.Command{ - Use: "slashing-info", - Short: "show latest slash info by id", - RunE: func(cmd *cobra.Command, args []string) error { - cliCtx := context.NewCLIContext().WithCodec(cdc) - - id := viper.GetUint64(FlagId) - - params := types.NewQuerySlashingInfoParams(hmTypes.ValidatorID(id)) - - bz, err := cliCtx.Codec.MarshalJSON(params) - if err != nil { - return err - } - - route := fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QuerySlashingInfo) - res, _, err := cliCtx.QueryWithData(route, bz) - if err != nil { - return err - } - - fmt.Println(string(res)) - return nil - }, - } - - cmd.Flags().String(FlagId, "", "--id=") - - if err := cmd.MarkFlagRequired(FlagId); err != nil { - logger.Error("GetLatestSlashInfo | MarkFlagRequired | FlagId", "Error", err) - } - - return cmd -} - -//Give signing info by id -func GetLatestSlashingInfos(cdc *codec.Codec) *cobra.Command { - cmd := &cobra.Command{ - Use: "slashing-infos", - Short: "show slashing infos", - RunE: func(cmd *cobra.Command, args []string) error { - cliCtx := context.NewCLIContext().WithCodec(cdc) - - page := viper.GetInt(FlagPage) - - limit := viper.GetInt(FlagLimit) - - params := types.NewQuerySlashingInfosParams(page, limit) - - bz, err := cliCtx.Codec.MarshalJSON(params) - if err != nil { - return err - } - - route := fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QuerySlashingInfos) - res, _, err := cliCtx.QueryWithData(route, bz) - if err != nil { - return err - } - - fmt.Println(string(res)) - return nil - }, - } - - cmd.Flags().Uint64(FlagPage, 0, "--page=") - cmd.Flags().Uint64(FlagLimit, 0, "--id=") - - if err := cmd.MarkFlagRequired(FlagPage); err != nil { - logger.Error("GetLatestSlashingInfos | MarkFlagRequired | FlagPage", "Error", err) - } - - if err := cmd.MarkFlagRequired(FlagLimit); err != nil { - logger.Error("GetLatestSlashingInfos | MarkFlagRequired | FlagLimit", "Error", err) - } - - return cmd -} - -//Give tick slash infos -func GetTickSlashingInfos(cdc *codec.Codec) *cobra.Command { - cmd := &cobra.Command{ - Use: "tick-slash-infos", - Short: "show tick slash infos", - RunE: func(cmd *cobra.Command, args []string) error { - cliCtx := context.NewCLIContext().WithCodec(cdc) - - page := viper.GetInt(FlagPage) - - limit := viper.GetInt(FlagLimit) - - params := types.NewQueryTickSlashingInfosParams(page, limit) - bz, err := cliCtx.Codec.MarshalJSON(params) + pk, err := sdk.GetConsPubKeyBech32(args[0]) if err != nil { return err } - route := fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QueryTickSlashingInfos) - res, _, err := cliCtx.QueryWithData(route, bz) - if err != nil { - return err - } - - fmt.Println(string(res)) - return nil - }, - } - - cmd.Flags().Uint64(FlagPage, 0, "--page=") - cmd.Flags().Uint64(FlagLimit, 0, "--id=") - - if err := cmd.MarkFlagRequired(FlagPage); err != nil { - logger.Error("GetTickSlashingInfos | MarkFlagRequired | FlagPage", "Error", err) - } - - if err := cmd.MarkFlagRequired(FlagLimit); err != nil { - logger.Error("GetTickSlashingInfos | MarkFlagRequired | FlagLimit", "Error", err) - } - - return cmd -} - -func GetLatestSlashInfoBytes(cdc *codec.Codec) *cobra.Command { - cmd := &cobra.Command{ - Use: "latest-slash-info-bytes", - Short: "Give the latest slash info bytes", - Args: cobra.NoArgs, - - RunE: func(cmd *cobra.Command, args []string) error { - cliCtx := context.NewCLIContext().WithCodec(cdc) - - res, _, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QuerySlashingInfoBytes), nil) + consAddr := sdk.ConsAddress(pk.Address()) + key := types.GetValidatorSigningInfoKey(consAddr) + res, _, err := cliCtx.QueryStore(key, storeName) if err != nil { return err } - // error if no slashInfoBytes found if len(res) == 0 { - fmt.Printf("no slashing Bytes found") - return nil - } - - var slashInfoBytes = hmTypes.BytesToHexBytes(res) - - result, err := json.Marshal(&slashInfoBytes) - if err != nil { - return err + return fmt.Errorf("Validator %s not found in slashing store", consAddr) } - fmt.Println(string(result)) - return nil - + var signingInfo types.ValidatorSigningInfo + cdc.MustUnmarshalBinaryLengthPrefixed(res, &signingInfo) + return cliCtx.PrintOutput(signingInfo) }, } - - return cmd } // GetCmdQueryParams implements a command to fetch slashing parameters. @@ -352,98 +98,3 @@ $ query slashing params }, } } - -// Check whether the transaction is old -func IsOldTx(cdc *codec.Codec) *cobra.Command { - cmd := &cobra.Command{ - Use: "is-old-tx", - Short: "Check whether the transaction is old", - RunE: func(cmd *cobra.Command, args []string) error { - cliCtx := context.NewCLIContext().WithCodec(cdc) - - // tx hash - txHash := viper.GetString(FlagTxHash) - if txHash == "" { - return fmt.Errorf("tx hash cannot be empty") - } - - // log index - logIndex := viper.GetUint64(FlagLogIndex) - - // get query params - queryParams, err := cliCtx.Codec.MarshalJSON(types.NewQuerySlashingSequenceParams(txHash, logIndex)) - if err != nil { - return err - } - - seqNo, _, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QuerySlashingSequence), queryParams) - if err != nil { - return err - } - - // error if no tx status found - if len(seqNo) == 0 { - fmt.Printf("false") - return nil - } - - res := true - - fmt.Println(res) - return nil - }, - } - - cmd.Flags().Uint64(FlagLogIndex, 0, "--log-index=") - cmd.Flags().Uint64(FlagTxHash, 0, "--tx-hash=") - - if err := cmd.MarkFlagRequired(FlagLogIndex); err != nil { - logger.Error("IsOldTx | MarkFlagRequired | FlagLogIndex", "Error", err) - } - - if err := cmd.MarkFlagRequired(FlagTxHash); err != nil { - logger.Error("IsOldTx | MarkFlagRequired | FlagTxHash", "Error", err) - } - - return cmd -} - -func GetTickCount(cdc *codec.Codec) *cobra.Command { - cmd := &cobra.Command{ - Use: "tick-count", - Short: "Give the tick-count", - Args: cobra.NoArgs, - - RunE: func(cmd *cobra.Command, args []string) error { - cliCtx := context.NewCLIContext().WithCodec(cdc) - - res, _, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QueryTickCount), nil) - - if err != nil { - return err - } - - // error if no slashInfoBytes found - if len(res) == 0 { - fmt.Printf("no slashing Bytes found") - return nil - } - - var tickCount uint64 - if err := json.Unmarshal(res, &tickCount); err != nil { - return err - } - - result, err := json.Marshal(&tickCount) - if err != nil { - return err - } - - fmt.Println(string(result)) - return nil - - }, - } - - return cmd -} diff --git a/slashing/client/cli/tx.go b/slashing/client/cli/tx.go index 024ffa2d8..c8967e482 100644 --- a/slashing/client/cli/tx.go +++ b/slashing/client/cli/tx.go @@ -1,23 +1,18 @@ package cli import ( - "fmt" + "github.com/spf13/cobra" "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/context" - "github.com/cosmos/cosmos-sdk/client/flags" "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/spf13/cobra" - "github.com/spf13/viper" - - "github.com/maticnetwork/heimdall/helper" - "github.com/maticnetwork/heimdall/slashing/types" - hmTypes "github.com/maticnetwork/heimdall/types" + "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/cosmos/cosmos-sdk/x/auth/client/utils" + "github.com/cosmos/cosmos-sdk/x/slashing/types" ) -var logger = helper.Logger.With("module", "slashing/client/cli") - +// GetTxCmd returns the transaction commands for this module func GetTxCmd(cdc *codec.Codec) *cobra.Command { slashingTxCmd := &cobra.Command{ Use: types.ModuleName, @@ -27,167 +22,31 @@ func GetTxCmd(cdc *codec.Codec) *cobra.Command { RunE: client.ValidateCmd, } - slashingTxCmd.AddCommand(flags.PostCommands( + slashingTxCmd.AddCommand(client.PostCommands( GetCmdUnjail(cdc), - GetCmdTick(cdc), - GetCmdTickAck(cdc), )...) return slashingTxCmd } +// GetCmdUnjail implements the create unjail validator command. func GetCmdUnjail(cdc *codec.Codec) *cobra.Command { - cmd := &cobra.Command{ + return &cobra.Command{ Use: "unjail", Args: cobra.NoArgs, - Short: "unjail validator previously jailed", + Short: "unjail validator previously jailed for downtime", Long: `unjail a jailed validator: $ tx slashing unjail --from mykey `, RunE: func(cmd *cobra.Command, args []string) error { + txBldr := auth.NewTxBuilderFromCLI().WithTxEncoder(utils.GetTxEncoder(cdc)) cliCtx := context.NewCLIContext().WithCodec(cdc) - // get proposer - proposer := hmTypes.HexToHeimdallAddress(viper.GetString(FlagProposerAddress)) - if proposer.Empty() { - proposer = helper.GetFromAddress(cliCtx) - } - - validator := viper.GetInt64(FlagValidatorID) - if validator == 0 { - return fmt.Errorf("validator ID cannot be 0") - } - - txHash := viper.GetString(FlagTxHash) - if txHash == "" { - return fmt.Errorf("transaction hash is required") - } - - msg := types.NewMsgUnjail( - proposer, - uint64(validator), - hmTypes.HexToHeimdallHash(txHash), - uint64(viper.GetInt64(FlagLogIndex)), - viper.GetUint64(FlagBlockNumber), - ) - - // broadcast messages - return helper.BroadcastMsgsWithCLI(cliCtx, []sdk.Msg{msg}) - }, - } - cmd.Flags().StringP(FlagProposerAddress, "p", "", "--proposer=") - cmd.Flags().String(FlagTxHash, "", "--tx-hash=") - cmd.Flags().Uint64(FlagBlockNumber, 0, "--block-number=") - if err := cmd.MarkFlagRequired(FlagTxHash); err != nil { - logger.Error("SendValidatorJoinTx | MarkFlagRequired | FlagTxHash", "Error", err) - } - if err := cmd.MarkFlagRequired(FlagBlockNumber); err != nil { - logger.Error("SendValidatorJoinTx | MarkFlagRequired | FlagBlockNumber", "Error", err) - } - return cmd -} - -func GetCmdTick(cdc *codec.Codec) *cobra.Command { - cmd := &cobra.Command{ - Use: "tick", - Short: "send slash tick when total slashedamount exceeds limit", - Long: "", - - RunE: func(cmd *cobra.Command, args []string) error { - cliCtx := context.NewCLIContext().WithCodec(cdc) - - // get proposer - proposer := hmTypes.HexToHeimdallAddress(viper.GetString(FlagProposerAddress)) - if proposer.Empty() { - proposer = helper.GetFromAddress(cliCtx) - } - - slashInfoBytes := viper.GetString(FlagSlashInfoBytes) - if slashInfoBytes == "" { - return fmt.Errorf("slashinfo bytes has to be supplied") - } - - msg := types.NewMsgTick( - viper.GetUint64(FlagTickID), - proposer, - hmTypes.HexToHexBytes(slashInfoBytes), - ) - - // broadcast messages - return helper.BroadcastMsgsWithCLI(cliCtx, []sdk.Msg{msg}) - }, - } - - cmd.Flags().StringP(FlagProposerAddress, "p", "", "--proposer=") - cmd.Flags().String(FlagSlashInfoBytes, "", "--slashinfo-bytes=") - cmd.Flags().Uint64(FlagTickID, 1, "--tick-id=") - - if err := cmd.MarkFlagRequired(FlagSlashInfoBytes); err != nil { - logger.Error("GetCmdTick | MarkFlagRequired | FlagSlashInfoBytes", "Error", err) - } - if err := cmd.MarkFlagRequired(FlagTickID); err != nil { - logger.Error("GetCmdTick | MarkFlagRequired | FlagTickID", "Error", err) - } - - return cmd -} - -func GetCmdTickAck(cdc *codec.Codec) *cobra.Command { - - cmd := &cobra.Command{ - Use: "tick-ack", - Short: "send tick ack", - RunE: func(cmd *cobra.Command, args []string) error { - cliCtx := context.NewCLIContext().WithCodec(cdc) - - // get proposer - proposer := hmTypes.HexToHeimdallAddress(viper.GetString(FlagProposerAddress)) - if proposer.Empty() { - proposer = helper.GetFromAddress(cliCtx) - } - - txHash := viper.GetString(FlagTxHash) - if txHash == "" { - return fmt.Errorf("transaction hash is required") - } - - msg := types.NewMsgTickAck( - proposer, - viper.GetUint64(FlagTickID), - viper.GetUint64(FlagAmount), - hmTypes.HexToHeimdallHash(txHash), - uint64(viper.GetInt64(FlagLogIndex)), - viper.GetUint64(FlagBlockNumber), - ) + valAddr := cliCtx.GetFromAddress() - // broadcast messages - return helper.BroadcastMsgsWithCLI(cliCtx, []sdk.Msg{msg}) + msg := types.NewMsgUnjail(sdk.ValAddress(valAddr)) + return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg}) }, } - - cmd.Flags().StringP(FlagProposerAddress, "p", "", "--proposer=") - cmd.Flags().String(FlagTxHash, "", "--tx-hash=") - cmd.Flags().Uint64(FlagBlockNumber, 0, "--block-number=") - cmd.Flags().String(FlagLogIndex, "", "--log-index=") - cmd.Flags().Uint64(FlagAmount, 0, "--amount=") - cmd.Flags().Uint64(FlagTickID, 1, "--tick-id=") - - if err := cmd.MarkFlagRequired(FlagBlockNumber); err != nil { - logger.Error("SendTickAckTx | MarkFlagRequired | FlagBlockNumber", "Error", err) - } - if err := cmd.MarkFlagRequired(FlagTxHash); err != nil { - logger.Error("SendTickAckTx | MarkFlagRequired | FlagTxHash", "Error", err) - } - if err := cmd.MarkFlagRequired(FlagLogIndex); err != nil { - logger.Error("SendTickAckTx | MarkFlagRequired | FlagLogIndex", "Error", err) - } - if err := cmd.MarkFlagRequired(FlagAmount); err != nil { - logger.Error("SendTickAckTx | MarkFlagRequired | FlagAmount", "Error", err) - } - if err := cmd.MarkFlagRequired(FlagTickID); err != nil { - logger.Error("SendTickAckTx | MarkFlagRequired | FlagTickID", "Error", err) - } - - return cmd } diff --git a/slashing/client/rest/query.go b/slashing/client/rest/query.go index a73d0cff4..30a3e6683 100644 --- a/slashing/client/rest/query.go +++ b/slashing/client/rest/query.go @@ -1,138 +1,20 @@ -//nolint package rest import ( "fmt" "net/http" - "github.com/cosmos/cosmos-sdk/client/context" - "github.com/cosmos/cosmos-sdk/types/rest" "github.com/gorilla/mux" - jsoniter "github.com/json-iterator/go" - "github.com/maticnetwork/heimdall/slashing/types" - hmTypes "github.com/maticnetwork/heimdall/types" - hmRest "github.com/maticnetwork/heimdall/types/rest" + "github.com/cosmos/cosmos-sdk/client/context" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/rest" + "github.com/cosmos/cosmos-sdk/x/slashing/types" ) -//swagger:response slashingSigningInfoByIdResponse -type slashingSigningInfoByIdResponse struct { - //in:body - Output slashingSigningInfoByIdStructure `json:"output"` -} - -type slashingSigningInfoByIdStructure struct { - Height string `json:"height"` - Result slashingSigningInfo `json:"result"` -} - -//swagger:response slashingInfosResponse -type slashingInfosResponse struct { - //in:body - Output slashingInfosStructure `json:"output"` -} - -type slashingInfosStructure struct { - Height string `json:"height"` - Result []slashingSigningInfo `json:"result"` -} - -type slashingSigningInfo struct { - ValID int64 `json:"valID"` - StartHeight int64 `json:"startHeight"` - IndexOffset int64 `json:"indexOffset"` -} - -//swagger:response slashingLatestInfoByIdResponse -type slashingLatestInfoByIdResponse struct { - //in:body - Output slashingLatestInfoByIdStructure `json:"output"` -} - -type slashingLatestInfoByIdStructure struct { - Height string `json:"height"` - Result ValidatorSlashingInfo `json:"result"` -} - -//swagger:response slashingLatestInfosResponse -type slashingLatestInfosResponse struct { - //in:body - Output slashingLatestInfosStructure `json:"output"` -} - -type slashingLatestInfosStructure struct { - Height string `json:"height"` - Result []ValidatorSlashingInfo `json:"result"` -} - -type ValidatorSlashingInfo struct { - ID int64 `json:"ID"` - SlashedAmount int64 `json:"SlashedAmount"` - IsJailed bool `json:"IsJailed"` -} - -//It represents the slashing parameters -//swagger:response slashingParametersResponse -type slashingParametersResponse struct { - //in:body - Output slashingParametersStructure `json:"output"` -} - -type slashingParametersStructure struct { - Height string `json:"height"` - Result params `json:"result"` -} - -type params struct { - SignedBlockWindow int64 `json:"signed_blocks_window"` - MinSignedPerWindow string `json:"min_signed_per_window"` - DowntimeJailDuration int64 `json:"downtime_jail_duration"` - SlashFunctionDoubleSign string `json:"slash_fraction_double_sign"` - SlashFractionDowntime string `json:"slash_fraction_downtime"` - SlashFractionLimit string `json:"slash_fraction_limit"` - JailFractionLimit string `json:"jail_fraction_limit"` - MaxEvidenceAge string `json:"max_evidence_age"` - EnableSlashing bool `json:"enable_slashing"` -} - -//It represents the slashing count -//swagger:response slashingCountResponse -type slashingCountResponse struct { - //in:body - Output slashingCountStructure `json:"output"` -} - -type slashingCountStructure struct { - Height string `json:"height"` - Result int64 `json:"result"` -} - -//It represents the slashing count -//swagger:response slashingInfosBytesResponse -type slashingInfosBytesResponse struct { - //in:body - Output slashingInfosBytesStructure `json:"output"` -} - -type slashingInfosBytesStructure struct { - Height string `json:"height"` - Result string `json:"result"` -} - -//swagger:response slashingIsOldTxResponse -type slashingIsOldTxResponse struct { - //in:body - Output isOldTx `json:"output"` -} - -type isOldTx struct { - Height string `json:"height"` - Result bool `json:"result"` -} - func registerQueryRoutes(cliCtx context.CLIContext, r *mux.Router) { r.HandleFunc( - "/slashing/validators/{id}/signing_info", + "/slashing/validators/{validatorPubKey}/signing_info", signingInfoHandlerFn(cliCtx), ).Methods("GET") @@ -141,72 +23,28 @@ func registerQueryRoutes(cliCtx context.CLIContext, r *mux.Router) { signingInfoHandlerListFn(cliCtx), ).Methods("GET") - r.HandleFunc( - "/slashing/validators/{id}/latest_slash_info", - latestSlashInfoHandlerFn(cliCtx), - ).Methods("GET") - - r.HandleFunc( - "/slashing/latest_slash_infos", - latestSlashInfoHandlerListFn(cliCtx), - ).Methods("GET") - - r.HandleFunc( - "/slashing/tick_slash_infos", - tickSlashInfoHandlerListFn(cliCtx), - ).Methods("GET") - - r.HandleFunc( - "/slashing/latest_slash_info_bytes", - latestSlashInfoBytesHandlerFn(cliCtx), - ).Methods("GET") - r.HandleFunc( "/slashing/parameters", queryParamsHandlerFn(cliCtx), ).Methods("GET") - - r.HandleFunc( - "/slashing/isoldtx", - SlashingTxStatusHandlerFn(cliCtx), - ).Methods("GET") - - r.HandleFunc( - "/slashing/tick-count", - tickCountHandlerFn(cliCtx), - ).Methods("GET") } -//swagger:parameters slashingSigningInfoById -type validatorID struct { - - //ID of the validator - //required:true - //in:path - Id int64 `json:"id"` -} - -// swagger:route GET /slashing/validators/{id}/signing_info slashing slashingSigningInfoById -// It returns the signing infos of the validator based on Id -// responses: -// 200: slashingSigningInfoByIdResponse // http request handler to query signing info func signingInfoHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) - - cliCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r) - if !ok { + pk, err := sdk.GetConsPubKeyBech32(vars["validatorPubKey"]) + if err != nil { + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } - // get id - id, ok := rest.ParseUint64OrReturnBadRequest(w, vars["id"]) + cliCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r) if !ok { return } - params := types.NewQuerySigningInfoParams(hmTypes.ValidatorID(id)) + params := types.NewQuerySigningInfoParams(sdk.ConsAddress(pk.Address())) bz, err := cliCtx.Codec.MarshalJSON(params) if err != nil { @@ -226,10 +64,6 @@ func signingInfoHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { } } -// swagger:route GET /slashing/signing_infos slashing slashingInfos -// It returns the signing infos. -// responses: -// 200: slashingInfosResponse // http request handler to query signing info func signingInfoHandlerListFn(cliCtx context.CLIContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { @@ -263,139 +97,6 @@ func signingInfoHandlerListFn(cliCtx context.CLIContext) http.HandlerFunc { } } -//swagger:parameters slashingLatestInfoById -type ID struct { - - //ID of the validator - //required:true - //in:path - Id int64 `json:"id"` -} - -// swagger:route GET /slashing/validators/{id}/latest_slash_info slashing slashingLatestInfoById -// It returns the latest signing infos of the validator based on Id -// responses: -// 200: slashingLatestInfoByIdResponse -// http request handler to query slashing info -func latestSlashInfoHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - vars := mux.Vars(r) - - cliCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r) - if !ok { - return - } - - // get id - id, ok := rest.ParseUint64OrReturnBadRequest(w, vars["id"]) - if !ok { - return - } - - params := types.NewQuerySlashingInfoParams(hmTypes.ValidatorID(id)) - - bz, err := cliCtx.Codec.MarshalJSON(params) - if err != nil { - rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) - return - } - - route := fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QuerySlashingInfo) - res, height, err := cliCtx.QueryWithData(route, bz) - if err != nil { - rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) - return - } - - cliCtx = cliCtx.WithHeight(height) - rest.PostProcessResponse(w, cliCtx, res) - } -} - -// swagger:route GET /slashing/latest_slash_infos slashing slashingLatestInfos -// It returns the latest signing infos -// responses: -// 200: slashingLatestInfosResponse -// http request handler to query signing info -func latestSlashInfoHandlerListFn(cliCtx context.CLIContext) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - _, page, limit, err := rest.ParseHTTPArgsWithLimit(r, 0) - if err != nil { - rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) - return - } - - cliCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r) - if !ok { - return - } - - params := types.NewQuerySlashingInfosParams(page, limit) - bz, err := cliCtx.Codec.MarshalJSON(params) - if err != nil { - rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) - return - } - - route := fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QuerySlashingInfos) - res, height, err := cliCtx.QueryWithData(route, bz) - if err != nil { - rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) - return - } - - cliCtx = cliCtx.WithHeight(height) - rest.PostProcessResponse(w, cliCtx, res) - } -} - -// swagger:route GET /slashing/latest_slash_info_bytes slashing slashingLatestSlashInfoBytes -// It returns the latest signing info byte -// responses: -// 200:slashingInfosBytesResponse -// http request handler to query signing info -func latestSlashInfoBytesHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - cliCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r) - if !ok { - return - } - - res, _, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QuerySlashingInfoBytes), nil) - RestLogger.Debug("slashInfoBytes querier response", "res", res) - - if err != nil { - RestLogger.Error("Error while calculating slashInfoBytes ", "Error", err.Error()) - rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) - return - } - - // error if no slashInfoBytes found - if ok := hmRest.ReturnNotFoundIfNoContent(w, res, "SlashInfoBytes not found"); !ok { - RestLogger.Error("SlashInfoBytes not found ", "Error", err.Error()) - return - } - - var slashInfoBytes = hmTypes.BytesToHexBytes(res) - RestLogger.Debug("Fetched slashInfoBytes ", "SlashInfoBytes", slashInfoBytes.String()) - - result, err := jsoniter.ConfigFastest.Marshal(&slashInfoBytes) - if err != nil { - RestLogger.Error("Error while marshalling response to Json", "error", err) - rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) - return - } - - // return result - rest.PostProcessResponse(w, cliCtx, result) - - } -} - -// swagger:route GET /slashing/parameters slashing slashingParameters -// It returns the slashing parameters -// responses: -// 200: slashingParametersResponse func queryParamsHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { cliCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r) @@ -415,169 +116,3 @@ func queryParamsHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { rest.PostProcessResponse(w, cliCtx, res) } } - -//swagger:parameters slashingTickInfos -type slashingTickInfosParams struct { - - //Page number - //required:true - //in:query - Page int64 `json:"page"` - - //Limit per page - //required:true - //in:query - Limit int64 `json:"limit"` -} - -// swagger:route GET /slashing/tick_slash_infos slashing slashingTickInfos -// It returns the tick slash infos -// responses: -// 200: slashingLatestInfosResponse -// http request handler to query tick slashing info -func tickSlashInfoHandlerListFn(cliCtx context.CLIContext) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - _, page, limit, err := rest.ParseHTTPArgsWithLimit(r, 0) - if err != nil { - rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) - return - } - - cliCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r) - if !ok { - return - } - - params := types.NewQueryTickSlashingInfosParams(page, limit) - bz, err := cliCtx.Codec.MarshalJSON(params) - if err != nil { - rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) - return - } - - route := fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QueryTickSlashingInfos) - res, height, err := cliCtx.QueryWithData(route, bz) - if err != nil { - rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) - return - } - - cliCtx = cliCtx.WithHeight(height) - rest.PostProcessResponse(w, cliCtx, res) - } -} - -//swagger:parameters slashingIsOldTx -type slashingTxParams struct { - //Log Index of the transaction - //required:true - //in:query - LogIndex int64 `json:"logindex"` - - //Hash of the transaction - //required:true - //in:query - Txhash string `json:"txhash"` -} - -// swagger:route GET /slashing/isoldtx slashing slashingIsOldTx -// It returns whether the transaction is old -// responses: -// 200: slashingIsOldTxResponse -// Returns slashing tx status information -func SlashingTxStatusHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - vars := r.URL.Query() - - cliCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r) - if !ok { - return - } - - // get logIndex - logindex, ok := rest.ParseUint64OrReturnBadRequest(w, vars.Get("logindex")) - if !ok { - return - } - - txHash := vars.Get("txhash") - - if txHash == "" { - return - } - - // get query params - queryParams, err := cliCtx.Codec.MarshalJSON(types.NewQuerySlashingSequenceParams(txHash, logindex)) - if err != nil { - hmRest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) - return - } - - seqNo, height, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QuerySlashingSequence), queryParams) - if err != nil { - RestLogger.Error("Error while fetching staking sequence", "Error", err.Error()) - hmRest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) - return - } - - // error if no tx status found - if ok := hmRest.ReturnNotFoundIfNoContent(w, seqNo, "No sequence found"); !ok { - return - } - - res := true - - // return result - cliCtx = cliCtx.WithHeight(height) - rest.PostProcessResponse(w, cliCtx, res) - } -} - -// swagger:route GET /slashing/tick-count slashing slashingTickCount -// It returns the slashing tick count -// responses: -// 200: slashingCountResponse -func tickCountHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - cliCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r) - if !ok { - return - } - - RestLogger.Debug("Fetching number of ticks from state") - tickCountBytes, height, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QueryTickCount), nil) - if err != nil { - hmRest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) - return - } - - // check content - if ok := hmRest.ReturnNotFoundIfNoContent(w, tickCountBytes, "No tick count found"); !ok { - return - } - - var tickCount uint64 - if err := jsoniter.ConfigFastest.Unmarshal(tickCountBytes, &tickCount); err != nil { - hmRest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) - return - } - - result, err := jsoniter.ConfigFastest.Marshal(&tickCount) - if err != nil { - RestLogger.Error("Error while marshalling response to Json", "error", err) - hmRest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) - return - } - - cliCtx = cliCtx.WithHeight(height) - rest.PostProcessResponse(w, cliCtx, result) - } -} - -//swagger:parameters slashingTickCount slashingIsOldTx slashingTickInfos slashingParameters slashingLatestSlashInfoBytes slashingLatestInfos slashingLatestInfoById slashingSigningInfoById slashingInfos -type Height struct { - - //Block Height - //in:query - Height string `json:"height"` -} diff --git a/slashing/client/rest/rest.go b/slashing/client/rest/rest.go index 489aad3b4..4237d59e2 100644 --- a/slashing/client/rest/rest.go +++ b/slashing/client/rest/rest.go @@ -1,26 +1,12 @@ package rest import ( - "github.com/cosmos/cosmos-sdk/client/context" "github.com/gorilla/mux" - tmLog "github.com/tendermint/tendermint/libs/log" - "github.com/maticnetwork/heimdall/helper" + "github.com/cosmos/cosmos-sdk/client/context" ) -// RestLogger for slashing module logger -var RestLogger tmLog.Logger - -func init() { - RestLogger = helper.Logger.With("module", "slashing/rest") -} - -// func RegisterHandlers(ctx context.CLIContext, m codec.Marshaler, txg tx.Generator, r *mux.Router) { -// registerQueryRoutes(ctx, r) -// registerTxHandlers(ctx, m, txg, r) -// } - -// RegisterRoutes registers slashing-related REST handlers to a router +// RegisterRoutes registers staking-related REST handlers to a router func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router) { registerQueryRoutes(cliCtx, r) registerTxRoutes(cliCtx, r) diff --git a/slashing/client/rest/tx.go b/slashing/client/rest/tx.go index 35417d5d6..16186d35d 100644 --- a/slashing/client/rest/tx.go +++ b/slashing/client/rest/tx.go @@ -1,199 +1,37 @@ -//nolint package rest import ( + "bytes" "net/http" - "github.com/cosmos/cosmos-sdk/client/context" - sdk "github.com/cosmos/cosmos-sdk/types" "github.com/gorilla/mux" - "github.com/maticnetwork/heimdall/slashing/types" - hmTypes "github.com/maticnetwork/heimdall/types" - "github.com/maticnetwork/heimdall/types/rest" - - restClient "github.com/maticnetwork/heimdall/client/rest" + "github.com/cosmos/cosmos-sdk/client/context" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/rest" + "github.com/cosmos/cosmos-sdk/x/auth/client/utils" + "github.com/cosmos/cosmos-sdk/x/slashing/types" ) -//It represents unjail msg. -//swagger:response slashingUnjailResponse -type slashingUnjailResponse struct { - //in:body - Output slashingUnjailOutput `json:"output"` -} - -type slashingUnjailOutput struct { - Type string `json:"type"` - Value slashingUnjailValue `json:"value"` -} - -type slashingUnjailValue struct { - Msg slashingUnjailMsg `json:"msg"` - Signature string `json:"signature"` - Memo string `json:"memo"` -} - -type slashingUnjailMsg struct { - Type string `json:"type"` - Value slashingUnjailVal `json:"value"` -} - -type slashingUnjailVal struct { - From string `json:"from"` - ID uint64 `json:"id"` - TxHash string `json:"tx_hash"` - LogIndex uint64 `json:"log_index"` - BlockNumber uint64 `json:"block_number"` -} - -//It represents Propose Span msg. -//swagger:response slashingNewTickResponse -type slashingNewTickResponse struct { - //in:body - Output slashingNewTickOutput `json:"output"` -} - -type slashingNewTickOutput struct { - Type string `json:"type"` - Value slashingNewTickValue `json:"value"` -} - -type slashingNewTickValue struct { - Msg slashingNewTickMsg `json:"msg"` - Signature string `json:"signature"` - Memo string `json:"memo"` -} - -type slashingNewTickMsg struct { - Type string `json:"type"` - Value slashingNewTickVal `json:"value"` -} - -type slashingNewTickVal struct { - ID uint64 `json:"id"` - Proposer string `json:"proposer"` - SlashingInfoBytes string `json:"slashinginfobytes"` -} - -//It represents Propose Span msg. -//swagger:response slashingTickAckResponse -type slashingTickAckResponse struct { - //in:body - Output slashingTickAckOutput `json:"output"` -} - -type slashingTickAckOutput struct { - Type string `json:"type"` - Value slashingTickAckValue `json:"value"` -} - -type slashingTickAckValue struct { - Msg slashingTickAckMsg `json:"msg"` - Signature string `json:"signature"` - Memo string `json:"memo"` -} - -type slashingTickAckMsg struct { - Type string `json:"type"` - Value slashingTickAckVal `json:"value"` -} - -type slashingTickAckVal struct { - From string `json:"from"` - ID uint64 `json:"tick_id"` - Amount uint64 `json:"slashed_amount"` - TxHash string `json:"tx_hash"` - LogIndex uint64 `json:"log_index"` - BlockNumber uint64 `json:"block_number"` -} - func registerTxRoutes(cliCtx context.CLIContext, r *mux.Router) { r.HandleFunc( "/slashing/validators/{validatorAddr}/unjail", - newUnjailRequestHandlerFn(cliCtx), - ).Methods("POST") - - r.HandleFunc( - "/slashing/tick", - newTickRequestHandlerFn(cliCtx), - ).Methods("POST") - - r.HandleFunc( - "/slashing/tick-ack", - newTickAckHandler(cliCtx), + unjailRequestHandlerFn(cliCtx), ).Methods("POST") } // Unjail TX body type UnjailReq struct { - BaseReq rest.BaseReq `json:"base_req"` - - ID uint64 `json:"ID"` - TxHash string `json:"tx_hash"` - LogIndex uint64 `json:"log_index"` - BlockNumber uint64 `json:"block_number" yaml:"block_number"` -} - -type TickReq struct { - BaseReq rest.BaseReq `json:"base_req"` - ID uint64 `json:"ID"` - Proposer string `json:"proposer"` - SlashingInfoBytes string `json:"slashing_info_bytes"` -} - -type TickAckReq struct { - BaseReq rest.BaseReq `json:"base_req"` - ID uint64 `json:"ID"` - Amount uint64 `json:"amount"` - TxHash string `json:"tx_hash"` - LogIndex uint64 `json:"log_index"` - BlockNumber uint64 `json:"block_number" yaml:"block_number"` -} - -//swagger:parameters slashingUnjail -type slashingUnjailParam struct { - - //Body - //required:true - //in:body - Input slashingUnjailInput `json:"input"` - - //Validator Address - //required:true - //in:path - ValidatorAddr string `json:"validatorAddr"` + BaseReq rest.BaseReq `json:"base_req" yaml:"base_req"` } -type slashingUnjailInput struct { - BaseReq BaseReq `json:"base_req"` - ID uint64 `json:"ID"` - TxHash string `json:"tx_hash"` - LogIndex uint64 `json:"log_index"` - BlockNumber uint64 `json:"block_number" yaml:"block_number"` -} - -type BaseReq struct { - - //Address of the sender - //required:true - //in:body - From string `json:"address"` +func unjailRequestHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) - //Chain ID of Heimdall - //required:true - //in:body - ChainID string `json:"chain_id"` -} + bech32validator := vars["validatorAddr"] -// swagger:route POST /slashing/validators/{validatorAddr}/unjail slashing slashingUnjail -// It returns the prepared msg for unjail -// responses: -// 200: slashingUnjailResponse -func newUnjailRequestHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - // read req from Request var req UnjailReq - if !rest.ReadRESTReq(w, r, cliCtx.Codec, &req) { return } @@ -203,112 +41,30 @@ func newUnjailRequestHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { return } - msg := types.NewMsgUnjail( - hmTypes.HexToHeimdallAddress(req.BaseReq.From), - req.ID, - hmTypes.HexToHeimdallHash(req.TxHash), - req.LogIndex, - req.BlockNumber, - ) - err := msg.ValidateBasic() + valAddr, err := sdk.ValAddressFromBech32(bech32validator) if err != nil { - rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } - restClient.WriteGenerateStdTxResponse(w, cliCtx, req.BaseReq, []sdk.Msg{msg}) - } -} - -//swagger:parameters slashingNewTick -type slashingNewTickParam struct { - - //Body - //required:true - //in:body - Input slashingNewTickInput `json:"input"` -} - -type slashingNewTickInput struct { - BaseReq BaseReq `json:"base_req"` - ID uint64 `json:"ID"` - Proposer string `json:"proposer"` - SlashingInfoBytes string `json:"slashing_info_bytes"` -} - -// swagger:route POST /slashing/tick slashing slashingNewTick -// It returns the prepared msg for new tick -// responses: -// 200: slashingNewTickResponse -func newTickRequestHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { - - return func(w http.ResponseWriter, r *http.Request) { - // read req from Request - var req TickReq - if !rest.ReadRESTReq(w, r, cliCtx.Codec, &req) { - return - } - - req.BaseReq = req.BaseReq.Sanitize() - if !req.BaseReq.ValidateBasic(w) { + fromAddr, err := sdk.AccAddressFromBech32(req.BaseReq.From) + if err != nil { + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } - msg := types.NewMsgTick( - req.ID, - hmTypes.HexToHeimdallAddress(req.Proposer), - hmTypes.HexToHexBytes(req.SlashingInfoBytes), - ) - - restClient.WriteGenerateStdTxResponse(w, cliCtx, req.BaseReq, []sdk.Msg{msg}) - - } -} - -//swagger:parameters slashingTickAck -type slashingTickAckParam struct { - - //Body - //required:true - //in:body - Input slashingTickAckInput `json:"input"` -} - -type slashingTickAckInput struct { - BaseReq BaseReq `json:"base_req"` - ID uint64 `json:"ID"` - Amount uint64 `json:"amount"` - TxHash string `json:"tx_hash"` - LogIndex uint64 `json:"log_index"` - BlockNumber uint64 `json:"block_number" yaml:"block_number"` -} - -// swagger:route POST slashing/tick-ack slashing slashingTickAck -// It returns the prepared msg for tick-ack -// responses: -// 200: stakingTickAckResponse -func newTickAckHandler(cliCtx context.CLIContext) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - - var req TickAckReq - if !rest.ReadRESTReq(w, r, cliCtx.Codec, &req) { + if !bytes.Equal(fromAddr, valAddr) { + rest.WriteErrorResponse(w, http.StatusUnauthorized, "must use own validator address") return } - req.BaseReq = req.BaseReq.Sanitize() - if !req.BaseReq.ValidateBasic(w) { + msg := types.NewMsgUnjail(valAddr) + err = msg.ValidateBasic() + if err != nil { + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } - msg := types.NewMsgTickAck( - hmTypes.HexToHeimdallAddress(req.BaseReq.From), - req.ID, - req.Amount, - hmTypes.HexToHeimdallHash(req.TxHash), - req.LogIndex, - req.BlockNumber, - ) - - restClient.WriteGenerateStdTxResponse(w, cliCtx, req.BaseReq, []sdk.Msg{msg}) + utils.WriteGenerateStdTxResponse(w, cliCtx, req.BaseReq, []sdk.Msg{msg}) } } diff --git a/slashing/genesis.go b/slashing/genesis.go index 0f520b973..c39010c67 100644 --- a/slashing/genesis.go +++ b/slashing/genesis.go @@ -1,75 +1,68 @@ package slashing import ( - "strconv" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/maticnetwork/heimdall/slashing/types" - hmTypes "github.com/maticnetwork/heimdall/types" + "github.com/cosmos/cosmos-sdk/x/slashing/types" + "github.com/cosmos/cosmos-sdk/x/staking/exported" ) // InitGenesis initialize default parameters // and the keeper's address to pubkey map -func InitGenesis(ctx sdk.Context, keeper Keeper, data types.GenesisState) { - // stakingKeeper.IterateValidators(ctx, - // func(index int64, validator exported.ValidatorI) bool { - // keeper.AddPubkey(ctx, validator.GetConsPubKey()) - // return false - // }, - // ) +func InitGenesis(ctx sdk.Context, keeper Keeper, stakingKeeper types.StakingKeeper, data types.GenesisState) { + stakingKeeper.IterateValidators(ctx, + func(index int64, validator exported.ValidatorI) bool { + keeper.addPubkey(ctx, validator.GetConsPubKey()) + return false + }, + ) - for _, info := range data.SigningInfos { - keeper.SetValidatorSigningInfo(ctx, info.ValID, info) + for addr, info := range data.SigningInfos { + address, err := sdk.ConsAddressFromBech32(addr) + if err != nil { + panic(err) + } + keeper.SetValidatorSigningInfo(ctx, address, info) } - for valIDStr, array := range data.MissedBlocks { + for addr, array := range data.MissedBlocks { + address, err := sdk.ConsAddressFromBech32(addr) + if err != nil { + panic(err) + } for _, missed := range array { - valID, _ := strconv.ParseUint(valIDStr, 10, 64) - keeper.SetValidatorMissedBlockBitArray(ctx, hmTypes.ValidatorID(valID), missed.Index, missed.Missed) + keeper.setValidatorMissedBlockBitArray(ctx, address, missed.Index, missed.Missed) } } - for _, valSlashInfo := range data.BufferValSlashingInfo { - keeper.SetBufferValSlashingInfo(ctx, valSlashInfo.ID, *valSlashInfo) - } - - for _, tickValSlashInfo := range data.TickValSlashingInfo { - keeper.SetTickValSlashingInfo(ctx, tickValSlashInfo.ID, *tickValSlashInfo) - } - - keeper.SetParams(ctx, data.Params) - - // Set initial tick count - keeper.UpdateTickCountWithValue(ctx, data.TickCount) - + keeper.paramspace.SetParamSet(ctx, &data.Params) } // ExportGenesis writes the current store values // to a genesis file, which can be imported again // with InitGenesis func ExportGenesis(ctx sdk.Context, keeper Keeper) (data types.GenesisState) { - params := keeper.GetParams(ctx) - signingInfos := make(map[string]hmTypes.ValidatorSigningInfo) + var params types.Params + keeper.paramspace.GetParamSet(ctx, ¶ms) + + signingInfos := make(map[string]types.ValidatorSigningInfo) missedBlocks := make(map[string][]types.MissedBlock) - keeper.IterateValidatorSigningInfos(ctx, func(valID hmTypes.ValidatorID, info hmTypes.ValidatorSigningInfo) (stop bool) { - signingInfos[valID.String()] = info + keeper.IterateValidatorSigningInfos(ctx, func(address sdk.ConsAddress, info types.ValidatorSigningInfo) (stop bool) { + bechAddr := address.String() + signingInfos[bechAddr] = info localMissedBlocks := []types.MissedBlock{} - keeper.IterateValidatorMissedBlockBitArray(ctx, valID, func(index int64, missed bool) (stop bool) { - localMissedBlocks = append(localMissedBlocks, types.NewMissedBlock(index, missed)) + keeper.IterateValidatorMissedBlockBitArray(ctx, address, func(index int64, missed bool) (stop bool) { + localMissedBlocks = append(localMissedBlocks, types.MissedBlock{index, missed}) return false }) - missedBlocks[valID.String()] = localMissedBlocks + missedBlocks[bechAddr] = localMissedBlocks + return false }) - bufSlashInfos, _ := keeper.GetBufferValSlashingInfos(ctx) - tickSlashInfos, _ := keeper.GetTickValSlashingInfos(ctx) - return types.NewGenesisState( - params, - signingInfos, - missedBlocks, - bufSlashInfos, - tickSlashInfos, - keeper.GetTickCount(ctx)) + return types.GenesisState{ + Params: params, + SigningInfos: signingInfos, + MissedBlocks: missedBlocks, + } } diff --git a/slashing/handler.go b/slashing/handler.go index 25a07e218..be7885885 100644 --- a/slashing/handler.go +++ b/slashing/handler.go @@ -1,179 +1,76 @@ package slashing import ( - "bytes" - "encoding/hex" - "math/big" + "fmt" sdk "github.com/cosmos/cosmos-sdk/types" - - "github.com/maticnetwork/heimdall/common" - "github.com/maticnetwork/heimdall/helper" - "github.com/maticnetwork/heimdall/slashing/types" - hmTypes "github.com/maticnetwork/heimdall/types" - - hmCommon "github.com/maticnetwork/heimdall/common" + "github.com/cosmos/cosmos-sdk/x/slashing/types" ) -// NewHandler creates an sdk.Handler for all the slashing type messages -func NewHandler(k Keeper, contractCaller helper.IContractCaller) sdk.Handler { +func NewHandler(k Keeper) sdk.Handler { return func(ctx sdk.Context, msg sdk.Msg) sdk.Result { ctx = ctx.WithEventManager(sdk.NewEventManager()) switch msg := msg.(type) { - case types.MsgTick: - return handlerMsgTick(ctx, msg, k, contractCaller) - case types.MsgTickAck: - return handleMsgTickAck(ctx, msg, k, contractCaller) - case types.MsgUnjail: - return handleMsgUnjail(ctx, msg, k, contractCaller) + case MsgUnjail: + return handleMsgUnjail(ctx, msg, k) + default: - return sdk.ErrTxDecode("Invalid message in slashing module").Result() + errMsg := fmt.Sprintf("unrecognized slashing message type: %T", msg) + return sdk.ErrUnknownRequest(errMsg).Result() } } } -// handlerMsgTick - handles slashing of validators -// 0. check if slashLimit is exceeded or not. -// 1. Validate input slashing info hash data -// 2. If hash matches, copy slashBuffer into latestTickData -// 3. flushes slashBuffer, totalSlashedAmount -// 4. iterate and reduce the power of slashed validators. -// 5. Also update the jailStatus of Validator -// 6. emit event TickConfirmation -func handlerMsgTick(ctx sdk.Context, msg types.MsgTick, k Keeper, contractCaller helper.IContractCaller) sdk.Result { - - k.Logger(ctx).Debug("✅ Validating tick msg", - "msgID", msg.ID, - "SlashInfoBytes", msg.SlashingInfoBytes.String(), - ) - - // check if slash limit is exceeded or not - totalSlashedAmount := k.GetTotalSlashedAmount(ctx) - if totalSlashedAmount == 0 { - k.Logger(ctx).Error("Slashed amount is zero") - return hmCommon.ErrInvalidMsg(k.Codespace(), "Slashed amount is zero").Result() - } - - // check if tick msgs are in continuity - tickCount := k.GetTickCount(ctx) - if msg.ID != tickCount+1 { - k.Logger(ctx).Error("Tick not in continuity", "msgID", msg.ID, "expectedMsgID", tickCount+1) - return hmCommon.ErrTickNotInContinuity(k.Codespace()).Result() - } - - valSlashingInfos, err := k.GetBufferValSlashingInfos(ctx) - if err != nil { - k.Logger(ctx).Error("Error fetching slash Info list from buffer", "error", err) - return hmCommon.ErrSlashInfoDetails(k.Codespace()).Result() +// Validators must submit a transaction to unjail itself after +// having been jailed (and thus unbonded) for downtime +func handleMsgUnjail(ctx sdk.Context, msg MsgUnjail, k Keeper) sdk.Result { + validator := k.sk.Validator(ctx, msg.ValidatorAddr) + if validator == nil { + return ErrNoValidatorForAddress(k.codespace).Result() } - slashingInfoBytes, err := types.SortAndRLPEncodeSlashInfos(valSlashingInfos) - if err != nil { - k.Logger(ctx).Info("Error generating slashing info bytes", "error", err) - return hmCommon.ErrSlashInfoDetails(k.Codespace()).Result() + // cannot be unjailed if no self-delegation exists + selfDel := k.sk.Delegation(ctx, sdk.AccAddress(msg.ValidatorAddr), msg.ValidatorAddr) + if selfDel == nil { + return ErrMissingSelfDelegation(k.codespace).Result() } - // compare slashingInfoHash with msg hash - k.Logger(ctx).Info("SlashInfo bytes generated", "SlashInfoBytes", hex.EncodeToString(slashingInfoBytes)) - - if !bytes.Equal(slashingInfoBytes, msg.SlashingInfoBytes) { - k.Logger(ctx).Error("slashingInfoBytes of current buffer state", "bufferSlashingInfoBytes", hex.EncodeToString(slashingInfoBytes), - "doesn't match with slashingInfoBytes of msg", "msgSlashInfoBytes", msg.SlashingInfoBytes.String()) - return hmCommon.ErrSlashInfoDetails(k.Codespace()).Result() + if validator.TokensFromShares(selfDel.GetShares()).TruncateInt().LT(validator.GetMinSelfDelegation()) { + return ErrSelfDelegationTooLowToUnjail(k.codespace).Result() } - k.Logger(ctx).Debug("SlashInfoHash matches") - - // ensure latestTickData is empty - tickSlashingInfos, err := k.GetTickValSlashingInfos(ctx) - if err != nil { - k.Logger(ctx).Error("Error fetching slash Info list from tick", "error", err) - return common.ErrSlashInfoDetails(k.Codespace()).Result() + // cannot be unjailed if not jailed + if !validator.IsJailed() { + return ErrValidatorNotJailed(k.codespace).Result() } - if len(tickSlashingInfos) > 0 { - k.Logger(ctx).Error("Waiting for tick data to be pushed to contract", "tickSlashingInfo", tickSlashingInfos) - return common.ErrSlashInfoDetails(k.Codespace()).Result() - } + consAddr := sdk.ConsAddress(validator.GetConsPubKey().Address()) - return sdk.Result{ - Events: ctx.EventManager().Events(), + info, found := k.getValidatorSigningInfo(ctx, consAddr) + if !found { + return ErrNoValidatorForAddress(k.codespace).Result() } -} -// Validators must submit a transaction to unjail itself after -// having been jailed (and thus unbonded) for downtime -func handleMsgUnjail(ctx sdk.Context, msg types.MsgUnjail, k Keeper, contractCaller helper.IContractCaller) sdk.Result { - - k.Logger(ctx).Debug("✅ Validating unjail msg", - "validatorId", msg.ID, - "txHash", hmTypes.BytesToHeimdallHash(msg.TxHash.Bytes()), - "logIndex", uint64(msg.LogIndex), - "blockNumber", msg.BlockNumber, - ) - - // sequence id - blockNumber := new(big.Int).SetUint64(msg.BlockNumber) - sequence := new(big.Int).Mul(blockNumber, big.NewInt(hmTypes.DefaultLogIndexUnit)) - sequence.Add(sequence, new(big.Int).SetUint64(msg.LogIndex)) - - // check if incoming tx is older - if k.HasSlashingSequence(ctx, sequence.String()) { - k.Logger(ctx).Error("Older invalid tx found") - return hmCommon.ErrOldTx(k.Codespace()).Result() + // cannot be unjailed if tombstoned + if info.Tombstoned { + return ErrValidatorJailed(k.codespace).Result() } - // pull validator from store - validator, ok := k.sk.GetValidatorFromValID(ctx, msg.ID) - if !ok { - k.Logger(ctx).Error("Fetching of validator from store failed", "validatorId", msg.ID) - return hmCommon.ErrNoValidator(k.Codespace()).Result() + // cannot be unjailed until out of jail + if ctx.BlockHeader().Time.Before(info.JailedUntil) { + return ErrValidatorJailed(k.codespace).Result() } - if !validator.Jailed { - k.Logger(ctx).Error("Fetching of validator from store failed", "validatorId", msg.ID) - return hmCommon.ErrNoValidator(k.Codespace()).Result() - } - return sdk.Result{ - Events: ctx.EventManager().Events(), - } -} + k.sk.Unjail(ctx, consAddr) -/* - handleMsgTickAck - handle msg tick ack event - 1. validate the tx hash in the event - 2. flush the last tick slashing info -*/ -func handleMsgTickAck(ctx sdk.Context, msg types.MsgTickAck, k Keeper, contractCaller helper.IContractCaller) sdk.Result { - - k.Logger(ctx).Debug("✅ Validating TickAck msg", - "ID", msg.ID, - "SlashedAmount", msg.SlashedAmount, - "txHash", hmTypes.BytesToHeimdallHash(msg.TxHash.Bytes()), - "logIndex", uint64(msg.LogIndex), - "blockNumber", msg.BlockNumber, + ctx.EventManager().EmitEvent( + sdk.NewEvent( + sdk.EventTypeMessage, + sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory), + sdk.NewAttribute(sdk.AttributeKeySender, msg.ValidatorAddr.String()), + ), ) - // sequence id - blockNumber := new(big.Int).SetUint64(msg.BlockNumber) - sequence := new(big.Int).Mul(blockNumber, big.NewInt(hmTypes.DefaultLogIndexUnit)) - sequence.Add(sequence, new(big.Int).SetUint64(msg.LogIndex)) - - // check if incoming tx is older - if k.HasSlashingSequence(ctx, sequence.String()) { - k.Logger(ctx).Error("Older invalid tx found") - return hmCommon.ErrOldTx(k.Codespace()).Result() - } - - // check if tick ack msgs are in continuity - tickCount := k.GetTickCount(ctx) - if msg.ID != tickCount { - k.Logger(ctx).Error("Tick-ack not in continuity", "msgID", msg.ID, "expectedMsgID", tickCount) - return hmCommon.ErrTickAckNotInContinuity(k.Codespace()).Result() - } - - return sdk.Result{ - Events: ctx.EventManager().Events(), - } + return sdk.Result{Events: ctx.EventManager().Events()} } diff --git a/slashing/handler_test.go b/slashing/handler_test.go new file mode 100644 index 000000000..854a1d140 --- /dev/null +++ b/slashing/handler_test.go @@ -0,0 +1,134 @@ +package slashing + +import ( + "strings" + "testing" + "time" + + "github.com/stretchr/testify/require" + abci "github.com/tendermint/tendermint/abci/types" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/staking" +) + +func TestCannotUnjailUnlessJailed(t *testing.T) { + // initial setup + ctx, ck, sk, _, keeper := createTestInput(t, DefaultParams()) + slh := NewHandler(keeper) + amt := sdk.TokensFromConsensusPower(100) + addr, val := addrs[0], pks[0] + msg := NewTestMsgCreateValidator(addr, val, amt) + got := staking.NewHandler(sk)(ctx, msg) + require.True(t, got.IsOK(), "%v", got) + staking.EndBlocker(ctx, sk) + + require.Equal( + t, ck.GetCoins(ctx, sdk.AccAddress(addr)), + sdk.Coins{sdk.NewCoin(sk.GetParams(ctx).BondDenom, initTokens.Sub(amt))}, + ) + require.Equal(t, amt, sk.Validator(ctx, addr).GetBondedTokens()) + + // assert non-jailed validator can't be unjailed + got = slh(ctx, NewMsgUnjail(addr)) + require.False(t, got.IsOK(), "allowed unjail of non-jailed validator") + require.EqualValues(t, CodeValidatorNotJailed, got.Code) + require.EqualValues(t, DefaultCodespace, got.Codespace) +} + +func TestCannotUnjailUnlessMeetMinSelfDelegation(t *testing.T) { + // initial setup + ctx, ck, sk, _, keeper := createTestInput(t, DefaultParams()) + slh := NewHandler(keeper) + amtInt := int64(100) + addr, val, amt := addrs[0], pks[0], sdk.TokensFromConsensusPower(amtInt) + msg := NewTestMsgCreateValidator(addr, val, amt) + msg.MinSelfDelegation = amt + got := staking.NewHandler(sk)(ctx, msg) + require.True(t, got.IsOK()) + staking.EndBlocker(ctx, sk) + + require.Equal( + t, ck.GetCoins(ctx, sdk.AccAddress(addr)), + sdk.Coins{sdk.NewCoin(sk.GetParams(ctx).BondDenom, initTokens.Sub(amt))}, + ) + + unbondAmt := sdk.NewCoin(sk.GetParams(ctx).BondDenom, sdk.OneInt()) + undelegateMsg := staking.NewMsgUndelegate(sdk.AccAddress(addr), addr, unbondAmt) + got = staking.NewHandler(sk)(ctx, undelegateMsg) + + require.True(t, sk.Validator(ctx, addr).IsJailed()) + + // assert non-jailed validator can't be unjailed + got = slh(ctx, NewMsgUnjail(addr)) + require.False(t, got.IsOK(), "allowed unjail of validator with less than MinSelfDelegation") + require.EqualValues(t, CodeValidatorNotJailed, got.Code) + require.EqualValues(t, DefaultCodespace, got.Codespace) +} + +func TestJailedValidatorDelegations(t *testing.T) { + ctx, _, stakingKeeper, _, slashingKeeper := createTestInput(t, DefaultParams()) + + stakingParams := stakingKeeper.GetParams(ctx) + stakingParams.UnbondingTime = 0 + stakingKeeper.SetParams(ctx, stakingParams) + + // create a validator + bondAmount := sdk.TokensFromConsensusPower(10) + valPubKey := pks[0] + valAddr, consAddr := addrs[1], sdk.ConsAddress(addrs[0]) + + msgCreateVal := NewTestMsgCreateValidator(valAddr, valPubKey, bondAmount) + got := staking.NewHandler(stakingKeeper)(ctx, msgCreateVal) + require.True(t, got.IsOK(), "expected create validator msg to be ok, got: %v", got) + + // end block + staking.EndBlocker(ctx, stakingKeeper) + + // set dummy signing info + newInfo := NewValidatorSigningInfo(consAddr, 0, 0, time.Unix(0, 0), false, 0) + slashingKeeper.SetValidatorSigningInfo(ctx, consAddr, newInfo) + + // delegate tokens to the validator + delAddr := sdk.AccAddress(addrs[2]) + msgDelegate := newTestMsgDelegate(delAddr, valAddr, bondAmount) + got = staking.NewHandler(stakingKeeper)(ctx, msgDelegate) + require.True(t, got.IsOK(), "expected delegation to be ok, got %v", got) + + unbondAmt := sdk.NewCoin(stakingKeeper.GetParams(ctx).BondDenom, bondAmount) + + // unbond validator total self-delegations (which should jail the validator) + msgUndelegate := staking.NewMsgUndelegate(sdk.AccAddress(valAddr), valAddr, unbondAmt) + got = staking.NewHandler(stakingKeeper)(ctx, msgUndelegate) + require.True(t, got.IsOK(), "expected begin unbonding validator msg to be ok, got: %v", got) + + err := stakingKeeper.CompleteUnbonding(ctx, sdk.AccAddress(valAddr), valAddr) + require.Nil(t, err, "expected complete unbonding validator to be ok, got: %v", err) + + // verify validator still exists and is jailed + validator, found := stakingKeeper.GetValidator(ctx, valAddr) + require.True(t, found) + require.True(t, validator.IsJailed()) + + // verify the validator cannot unjail itself + got = NewHandler(slashingKeeper)(ctx, NewMsgUnjail(valAddr)) + require.False(t, got.IsOK(), "expected jailed validator to not be able to unjail, got: %v", got) + + // self-delegate to validator + msgSelfDelegate := newTestMsgDelegate(sdk.AccAddress(valAddr), valAddr, bondAmount) + got = staking.NewHandler(stakingKeeper)(ctx, msgSelfDelegate) + require.True(t, got.IsOK(), "expected delegation to not be ok, got %v", got) + + // verify the validator can now unjail itself + got = NewHandler(slashingKeeper)(ctx, NewMsgUnjail(valAddr)) + require.True(t, got.IsOK(), "expected jailed validator to be able to unjail, got: %v", got) +} + +func TestInvalidMsg(t *testing.T) { + k := Keeper{} + h := NewHandler(k) + + res := h(sdk.NewContext(nil, abci.Header{}, false, nil), sdk.NewTestMsg()) + require.False(t, res.IsOK()) + require.True(t, strings.Contains(res.Log, "unrecognized slashing message type")) +} diff --git a/slashing/hooks.go b/slashing/hooks.go new file mode 100644 index 000000000..7535ab4ac --- /dev/null +++ b/slashing/hooks.go @@ -0,0 +1,76 @@ +// nolint +package slashing + +import ( + "time" + + "github.com/tendermint/tendermint/crypto" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/slashing/types" +) + +func (k Keeper) AfterValidatorBonded(ctx sdk.Context, address sdk.ConsAddress, _ sdk.ValAddress) { + // Update the signing info start height or create a new signing info + _, found := k.getValidatorSigningInfo(ctx, address) + if !found { + signingInfo := types.NewValidatorSigningInfo( + address, + ctx.BlockHeight(), + 0, + time.Unix(0, 0), + false, + 0, + ) + k.SetValidatorSigningInfo(ctx, address, signingInfo) + } +} + +// When a validator is created, add the address-pubkey relation. +func (k Keeper) AfterValidatorCreated(ctx sdk.Context, valAddr sdk.ValAddress) { + validator := k.sk.Validator(ctx, valAddr) + k.addPubkey(ctx, validator.GetConsPubKey()) +} + +// When a validator is removed, delete the address-pubkey relation. +func (k Keeper) AfterValidatorRemoved(ctx sdk.Context, address sdk.ConsAddress) { + k.deleteAddrPubkeyRelation(ctx, crypto.Address(address)) +} + +//_________________________________________________________________________________________ + +// Hooks wrapper struct for slashing keeper +type Hooks struct { + k Keeper +} + +var _ types.StakingHooks = Hooks{} + +// Return the wrapper struct +func (k Keeper) Hooks() Hooks { + return Hooks{k} +} + +// Implements sdk.ValidatorHooks +func (h Hooks) AfterValidatorBonded(ctx sdk.Context, consAddr sdk.ConsAddress, valAddr sdk.ValAddress) { + h.k.AfterValidatorBonded(ctx, consAddr, valAddr) +} + +// Implements sdk.ValidatorHooks +func (h Hooks) AfterValidatorRemoved(ctx sdk.Context, consAddr sdk.ConsAddress, _ sdk.ValAddress) { + h.k.AfterValidatorRemoved(ctx, consAddr) +} + +// Implements sdk.ValidatorHooks +func (h Hooks) AfterValidatorCreated(ctx sdk.Context, valAddr sdk.ValAddress) { + h.k.AfterValidatorCreated(ctx, valAddr) +} + +// nolint - unused hooks +func (h Hooks) AfterValidatorBeginUnbonding(_ sdk.Context, _ sdk.ConsAddress, _ sdk.ValAddress) {} +func (h Hooks) BeforeValidatorModified(_ sdk.Context, _ sdk.ValAddress) {} +func (h Hooks) BeforeDelegationCreated(_ sdk.Context, _ sdk.AccAddress, _ sdk.ValAddress) {} +func (h Hooks) BeforeDelegationSharesModified(_ sdk.Context, _ sdk.AccAddress, _ sdk.ValAddress) {} +func (h Hooks) BeforeDelegationRemoved(_ sdk.Context, _ sdk.AccAddress, _ sdk.ValAddress) {} +func (h Hooks) AfterDelegationModified(_ sdk.Context, _ sdk.AccAddress, _ sdk.ValAddress) {} +func (h Hooks) BeforeValidatorSlashed(_ sdk.Context, _ sdk.ValAddress, _ sdk.Dec) {} diff --git a/slashing/infractions.go b/slashing/infractions.go deleted file mode 100644 index c688fbf15..000000000 --- a/slashing/infractions.go +++ /dev/null @@ -1,151 +0,0 @@ -package slashing - -import ( - "errors" - "fmt" - - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/maticnetwork/heimdall/slashing/types" - hmTypes "github.com/maticnetwork/heimdall/types" -) - -// HandleValidatorSignature handles a validator signature, must be called once per validator per block. -func (k *Keeper) HandleValidatorSignature(ctx sdk.Context, addr []byte, power int64, signed bool) error { - height := ctx.BlockHeight() - signerAddress := hmTypes.BytesToHeimdallAddress(addr) - k.Logger(ctx).Debug("Processing downtime request for validator", "address", signerAddress, "signed", signed, "power", power) - - // fetch validator Info - validator, err := k.sk.GetValidatorInfo(ctx, signerAddress.Bytes()) - if err != nil { - k.Logger(ctx).Error("validator info not found", "address", signerAddress) - return err - } - - // fetch signing info - signInfo, found := k.GetValidatorSigningInfo(ctx, validator.ID) - if !found { - panic(fmt.Sprintf("Expected signing info for validator %s but not found", addr)) - } - - k.Logger(ctx).Debug("validator signing info", "valID", validator.ID, "address", signerAddress, "signingInfo", signInfo) - - params := k.GetParams(ctx) - // this is a relative index, so it counts blocks the validator *should* have signed - // will use the 0-value default signing info if not present, except for start height - index := signInfo.IndexOffset % params.SignedBlocksWindow - signInfo.IndexOffset++ - - // Update signed block bit array & counter - // This counter just tracks the sum of the bit array - // That way we avoid needing to read/write the whole array each time - previous := k.GetValidatorMissedBlockBitArray(ctx, validator.ID, index) - k.Logger(ctx).Debug("validator signing status", "valID", validator.ID, "address", signerAddress, "previous", previous, "current", signed) - missed := !signed - switch { - case !previous && missed: - // Array value has changed from not missed to missed, increment counter - k.SetValidatorMissedBlockBitArray(ctx, validator.ID, index, true) - signInfo.MissedBlocksCounter++ - k.Logger(ctx).Debug("Array value has changed from not missed to missed, increment counter", "missedBlocksCounter", signInfo.MissedBlocksCounter) - case previous && !missed: - // Array value has changed from missed to not missed, decrement counter - k.SetValidatorMissedBlockBitArray(ctx, validator.ID, index, false) - signInfo.MissedBlocksCounter-- - k.Logger(ctx).Debug("Array value has changed from missed to not missed, decrement counter", "missedBlocksCounter", signInfo.MissedBlocksCounter) - default: - // Array value at this index has not changed, no need to update counter - k.Logger(ctx).Debug("Array value has not changed. missedBlocksCounter remains same", "signingInfo", signInfo) - } - - if missed { - k.Logger(ctx).Info( - fmt.Sprintf("Absent validator %s at height %d, %d missed, threshold %d", validator.ID, height, signInfo.MissedBlocksCounter, k.MinSignedPerWindow(ctx))) - } - - minHeight := signInfo.StartHeight + params.SignedBlocksWindow - maxMissed := params.SignedBlocksWindow - k.MinSignedPerWindow(ctx) - - // SLASH - if we are past the minimum height and the validator has missed too many blocks, punish them - if height > minHeight && signInfo.MissedBlocksCounter > maxMissed { - - valSlashInfo, found := k.GetBufferValSlashingInfo(ctx, validator.ID) - // if val is already in jailed state(in buffer or fixed), don't slash him anymore. - if validator.Jailed || (found && valSlashInfo.IsJailed) { - // Validator was (a) not found or (b) already jailed, don't slash - k.Logger(ctx).Info(fmt.Sprintf("Validator %s would have been slashed for downtime, but was either not found in store or already jailed", validator.ID)) - } else { - // Downtime confirmed: slash and jail the validator - k.Logger(ctx).Info(fmt.Sprintf("Validator %s past min height of %d and below signed blocks threshold of %d", - validator.ID, minHeight, k.MinSignedPerWindow(ctx))) - - slashedAmount := k.SlashInterim(ctx, validator.ID, params.SlashFractionDowntime) - k.Logger(ctx).Debug("Interim uptime slashing successful", "valID", validator.ID, "slashedAmount", slashedAmount) - - // We need to reset the counter & array so that the validator won't be immediately slashed for downtime upon rebonding. - signInfo.MissedBlocksCounter = 0 - signInfo.IndexOffset = 0 - k.clearValidatorMissedBlockBitArray(ctx, validator.ID) - - } - } - - // Set the updated signing info - k.SetValidatorSigningInfo(ctx, validator.ID, signInfo) - return nil -} - -// HandleDoubleSign implements an equivocation evidence handler. Assuming the -// evidence is valid, the validator committing the misbehavior will be slashed, -// jailed -// -// The evidence is considered invalid if: -// - the evidence is too old -// - the validator does not exist -// - the signing info does not exist (will panic) -// - is already jailed -func (k *Keeper) HandleDoubleSign(ctx sdk.Context, evidence types.Equivocation) error { - consAddr := evidence.GetConsensusAddress() - signerAddress := hmTypes.BytesToHeimdallAddress(consAddr) - - validator, err := k.sk.GetValidatorInfo(ctx, signerAddress.Bytes()) - if err != nil { - k.Logger(ctx).Error("Error fetching validator", "signerAddress", signerAddress) - return err - } - - infractionHeight := evidence.GetHeight() - k.Logger(ctx).Debug("Processing doubleSign request for validator", "address", signerAddress, "height", infractionHeight) - - // calculate the age of the evidence - blockTime := ctx.BlockHeader().Time - age := blockTime.Sub(evidence.GetTime()) - params := k.GetParams(ctx) - - // reject evidence if the double-sign is too old - if age > params.MaxEvidenceAge { - k.Logger(ctx).Error("Ignored double sign from %s at height %d, age of %d past max age of %d", - signerAddress, infractionHeight, age, params.MaxEvidenceAge) - return errors.New("double sign too old") - } - - if ok := k.HasValidatorSigningInfo(ctx, validator.ID); !ok { - panic(fmt.Sprintf("expected signing info for validator %s but not found", validator.ID)) - } - - k.Logger(ctx).Info(fmt.Sprintf("confirmed double sign from %s at height %d, age of %d", validator.ID, infractionHeight, age)) - - // Slash validator. The `power` is the int64 power of the validator as provided - // to/by Tendermint. - valSlashInfo, found := k.GetBufferValSlashingInfo(ctx, validator.ID) - // if val is already in jailed state(in buffer or fixed), don't slash him anymore. - if validator.Jailed || (found && valSlashInfo.IsJailed) { - // Validator was (a) not found or (b) already jailed, don't slash - k.Logger(ctx).Info(fmt.Sprintf("Validator %s would have been slashed for double time, but was either not found in store or already jailed", validator.ID)) - } else { - slashedAmount := k.SlashInterim(ctx, validator.ID, params.SlashFractionDoubleSign) - k.Logger(ctx).Debug("Interim uptime slashing successful", "valID", validator.ID, "slashedAmount", slashedAmount) - } - - return nil -} diff --git a/slashing/keeper.go b/slashing/keeper.go index c9882a58b..52b186857 100644 --- a/slashing/keeper.go +++ b/slashing/keeper.go @@ -2,48 +2,38 @@ package slashing import ( "fmt" - "strconv" + "time" + "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/libs/log" "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" - gogotypes "github.com/gogo/protobuf/types" - "github.com/maticnetwork/heimdall/chainmanager" - "github.com/maticnetwork/heimdall/params/subspace" - "github.com/maticnetwork/heimdall/slashing/types" - "github.com/maticnetwork/heimdall/staking" - hmTypes "github.com/maticnetwork/heimdall/types" + "github.com/cosmos/cosmos-sdk/x/params" + "github.com/cosmos/cosmos-sdk/x/slashing/types" ) // Keeper of the slashing store type Keeper struct { - cdc *codec.Codec - storeKey sdk.StoreKey - sk staking.Keeper - // codespace - codespace sdk.CodespaceType - paramSpace subspace.Subspace + storeKey sdk.StoreKey + cdc *codec.Codec + sk types.StakingKeeper + paramspace params.Subspace - // chain manager keeper - chainKeeper chainmanager.Keeper + // codespace + codespace sdk.CodespaceType } // NewKeeper creates a slashing keeper -func NewKeeper(cdc *codec.Codec, key sdk.StoreKey, sk staking.Keeper, paramSpace subspace.Subspace, codespace sdk.CodespaceType, chainKeeper chainmanager.Keeper) Keeper { - return Keeper{ - storeKey: key, - cdc: cdc, - sk: sk, - paramSpace: paramSpace.WithKeyTable(types.ParamKeyTable()), - codespace: codespace, - chainKeeper: chainKeeper, +func NewKeeper(cdc *codec.Codec, key sdk.StoreKey, sk types.StakingKeeper, paramspace params.Subspace, codespace sdk.CodespaceType) Keeper { + keeper := Keeper{ + storeKey: key, + cdc: cdc, + sk: sk, + paramspace: paramspace.WithKeyTable(ParamKeyTable()), + codespace: codespace, } -} - -// Codespace returns the codespace -func (k Keeper) Codespace() sdk.CodespaceType { - return k.codespace + return keeper } // Logger returns a module-specific logger. @@ -51,558 +41,233 @@ func (k Keeper) Logger(ctx sdk.Context) log.Logger { return ctx.Logger().With("module", fmt.Sprintf("x/%s", types.ModuleName)) } -// GetValidatorSigningInfo returns the ValidatorSigningInfo for a specific validator -// ConsAddress -func (k *Keeper) GetValidatorSigningInfo(ctx sdk.Context, valID hmTypes.ValidatorID) (info hmTypes.ValidatorSigningInfo, found bool) { - store := ctx.KVStore(k.storeKey) - bz := store.Get(types.GetValidatorSigningInfoKey(valID.Bytes())) - if bz == nil { - found = false +// handle a validator signing two blocks at the same height +// power: power of the double-signing validator at the height of infraction +func (k Keeper) HandleDoubleSign(ctx sdk.Context, addr crypto.Address, infractionHeight int64, timestamp time.Time, power int64) { + logger := k.Logger(ctx) + + // calculate the age of the evidence + time := ctx.BlockHeader().Time + age := time.Sub(timestamp) + + // fetch the validator public key + consAddr := sdk.ConsAddress(addr) + pubkey, err := k.getPubkey(ctx, addr) + if err != nil { + // Ignore evidence that cannot be handled. + // NOTE: + // We used to panic with: + // `panic(fmt.Sprintf("Validator consensus-address %v not found", consAddr))`, + // but this couples the expectations of the app to both Tendermint and + // the simulator. Both are expected to provide the full range of + // allowable but none of the disallowed evidence types. Instead of + // getting this coordination right, it is easier to relax the + // constraints and ignore evidence that cannot be handled. return } - k.cdc.MustUnmarshalBinaryBare(bz, &info) - found = true - return -} - -// HasValidatorSigningInfo returns if a given validator has signing information -// persisted. -func (k *Keeper) HasValidatorSigningInfo(ctx sdk.Context, valID hmTypes.ValidatorID) bool { - _, ok := k.GetValidatorSigningInfo(ctx, valID) - return ok -} - -// SetValidatorSigningInfo sets the validator signing info to a consensus address key -func (k *Keeper) SetValidatorSigningInfo(ctx sdk.Context, valID hmTypes.ValidatorID, info hmTypes.ValidatorSigningInfo) { - store := ctx.KVStore(k.storeKey) - bz := k.cdc.MustMarshalBinaryBare(&info) - store.Set(types.GetValidatorSigningInfoKey(valID.Bytes()), bz) -} - -// IterateValidatorSigningInfos iterates over the stored ValidatorSigningInfo -func (k *Keeper) IterateValidatorSigningInfos(ctx sdk.Context, - handler func(valID hmTypes.ValidatorID, info hmTypes.ValidatorSigningInfo) (stop bool)) { - - store := ctx.KVStore(k.storeKey) - iter := sdk.KVStorePrefixIterator(store, types.ValidatorSigningInfoKey) - defer iter.Close() - for ; iter.Valid(); iter.Next() { - var info hmTypes.ValidatorSigningInfo - k.cdc.MustUnmarshalBinaryBare(iter.Value(), &info) - if handler(info.ValID, info) { - break - } - } -} - -// signing info bit array - -// GetValidatorMissedBlockBitArray gets the bit for the missed blocks array -func (k *Keeper) GetValidatorMissedBlockBitArray(ctx sdk.Context, valID hmTypes.ValidatorID, index int64) bool { - store := ctx.KVStore(k.storeKey) - bz := store.Get(types.GetValidatorMissedBlockBitArrayKey(valID.Bytes(), index)) - var missed gogotypes.BoolValue - if bz == nil { - // lazy: treat empty key as not missed - return false - } - k.cdc.MustUnmarshalBinaryBare(bz, &missed) - - return missed.Value -} - -// IterateValidatorMissedBlockBitArray iterates over the signed blocks window -// and performs a callback function -func (k *Keeper) IterateValidatorMissedBlockBitArray(ctx sdk.Context, - valID hmTypes.ValidatorID, handler func(index int64, missed bool) (stop bool)) { - - store := ctx.KVStore(k.storeKey) - index := int64(0) - params := k.GetParams(ctx) - // Array may be sparse - for ; index < params.SignedBlocksWindow; index++ { - var missed gogotypes.BoolValue - bz := store.Get(types.GetValidatorMissedBlockBitArrayKey(valID.Bytes(), index)) - if bz == nil { - continue - } - - k.cdc.MustUnmarshalBinaryBare(bz, &missed) - if handler(index, missed.Value) { - break - } - } -} - -// SetValidatorMissedBlockBitArray sets the bit that checks if the validator has -// missed a block in the current window -func (k *Keeper) SetValidatorMissedBlockBitArray(ctx sdk.Context, valID hmTypes.ValidatorID, index int64, missed bool) { - store := ctx.KVStore(k.storeKey) - bz := k.cdc.MustMarshalBinaryBare(&gogotypes.BoolValue{Value: missed}) - store.Set(types.GetValidatorMissedBlockBitArrayKey(valID.Bytes(), index), bz) -} - -// clearValidatorMissedBlockBitArray deletes every instance of ValidatorMissedBlockBitArray in the store -func (k *Keeper) clearValidatorMissedBlockBitArray(ctx sdk.Context, valID hmTypes.ValidatorID) { - store := ctx.KVStore(k.storeKey) - iter := sdk.KVStorePrefixIterator(store, types.GetValidatorMissedBlockBitArrayPrefixKey(valID.Bytes())) - defer iter.Close() - for ; iter.Valid(); iter.Next() { - store.Delete(iter.Key()) - } -} - -// MinSignedPerWindow - minimum blocks signed per window -func (k *Keeper) MinSignedPerWindow(ctx sdk.Context) int64 { - var minSignedPerWindow sdk.Dec - params := k.GetParams(ctx) - // minSignedPerWindow = percent - minSignedPerWindow = params.MinSignedPerWindow - signedBlocksWindow := params.SignedBlocksWindow - - // NOTE: RoundInt64 will never panic as minSignedPerWindow is - // less than 1. - return minSignedPerWindow.MulInt64(signedBlocksWindow).RoundInt64() -} -// ----------------------------------------------------------------------------- -// Params - -// SetParams sets the slashing module's parameters. -func (k *Keeper) SetParams(ctx sdk.Context, params types.Params) { - k.paramSpace.SetParamSet(ctx, ¶ms) -} - -// GetParams gets the slashing module's parameters. -func (k *Keeper) GetParams(ctx sdk.Context) (params types.Params) { - k.paramSpace.GetParamSet(ctx, ¶ms) - return -} - -// -// Tick count -// - -// GetTickCount returns current Tick count -func (k Keeper) GetTickCount(ctx sdk.Context) uint64 { - store := ctx.KVStore(k.storeKey) - // check if tick count is there - if store.Has(types.TickCountKey) { - // get current tick count - tickCount, err := strconv.ParseUint(string(store.Get(types.TickCountKey)), 10, 64) - if err != nil { - k.Logger(ctx).Error("Unable to convert key to int") - } else { - return tickCount - } + // Reject evidence if the double-sign is too old + if age > k.MaxEvidenceAge(ctx) { + logger.Info(fmt.Sprintf("Ignored double sign from %s at height %d, age of %d past max age of %d", + sdk.ConsAddress(pubkey.Address()), infractionHeight, age, k.MaxEvidenceAge(ctx))) + return } - return 0 -} - -// UpdateTickCountWithValue updates TickCount with value -func (k Keeper) UpdateTickCountWithValue(ctx sdk.Context, value uint64) { - store := ctx.KVStore(k.storeKey) - - // convert - tickCount := []byte(strconv.FormatUint(value, 10)) - - // update - store.Set(types.TickCountKey, tickCount) -} - -// IncrementTickCount updates Tick count by 1 -func (k Keeper) IncrementTickCount(ctx sdk.Context) { - store := ctx.KVStore(k.storeKey) - - // get current tick Count - tickCount := k.GetTickCount(ctx) - - // increment by 1 - tickCounts := []byte(strconv.FormatUint(tickCount+1, 10)) - - // update - store.Set(types.TickCountKey, tickCounts) -} - -// Slashing Info api's - -// SlashInterim - Add slash amounts to a buffer and emit event if exceeded -func (k *Keeper) SlashInterim(ctx sdk.Context, valID hmTypes.ValidatorID, slashPercent sdk.Dec) uint64 { - if slashPercent.IsNegative() { - panic(fmt.Errorf("attempted to slash with a negative slash factor: %v", slashPercent)) + // Get validator and signing info + validator := k.sk.ValidatorByConsAddr(ctx, consAddr) + if validator == nil || validator.IsUnbonded() { + // Defensive. + // Simulation doesn't take unbonding periods into account, and + // Tendermint might break this assumption at some point. + return } - validator, found := k.sk.GetValidatorFromValID(ctx, valID) + // fetch the validator signing info + signInfo, found := k.getValidatorSigningInfo(ctx, consAddr) if !found { - k.Logger(ctx).Error("Interim slashing the validator. Validator not found", "valID", valID) - return uint64(0) - } - valPower := validator.VotingPower - - slashAmountDec := sdk.NewDec(valPower).Mul(slashPercent) - slashAmountInt := slashAmountDec.TruncateInt().Int64() - - k.Logger(ctx).Info("Interim slashing the validator", "valID", valID, "valPower", valPower, "slashPercent", slashPercent, "slashAmountDec", slashAmountDec, "slashAmountInt", slashAmountInt) - - // Add slash to buffer - valSlashingInfo, found := k.GetBufferValSlashingInfo(ctx, valID) - if found { - // Add or Update Slash Amount - prevAmount := valSlashingInfo.SlashedAmount - updatedSlashAmount := prevAmount + uint64(slashAmountInt) - valSlashingInfo.SlashedAmount = updatedSlashAmount - } else { - // create slashing info - valSlashingInfo = hmTypes.NewValidatorSlashingInfo(valID, uint64(slashAmountInt), false) + panic(fmt.Sprintf("Expected signing info for validator %s but not found", consAddr)) } - // Check if jailLimit is exceeded and update the jail status. - if k.IsJailLimitExceeded(ctx, valSlashingInfo) { - valSlashingInfo.IsJailed = true + // validator is already tombstoned + if signInfo.Tombstoned { + logger.Info(fmt.Sprintf("Ignored double sign from %s at height %d, validator already tombstoned", sdk.ConsAddress(pubkey.Address()), infractionHeight)) + return } - k.Logger(ctx).Debug("After interim slashing the validator status", "valID", valID, "updatedSlashAmount", valSlashingInfo.SlashedAmount, "jailStatus", valSlashingInfo.IsJailed) - - // Update buffer with val slashing info - k.SetBufferValSlashingInfo(ctx, valID, valSlashingInfo) - - // Update total slashed amount - k.UpdateTotalSlashedAmount(ctx, uint64(slashAmountInt)) - - totalSlashedAmount := k.GetTotalSlashedAmount(ctx) - // Check if slash limit is exceeded and emit `slash-limit` event - if k.IsSlashedLimitExceeded(ctx) { - k.Logger(ctx).Info("TotalSlashedAmount exceeded SlashLimit, Emitting event", types.EventTypeSlashLimit) + // double sign confirmed + logger.Info(fmt.Sprintf("Confirmed double sign from %s at height %d, age of %d", sdk.ConsAddress(pubkey.Address()), infractionHeight, age)) + + // We need to retrieve the stake distribution which signed the block, so we subtract ValidatorUpdateDelay from the evidence height. + // Note that this *can* result in a negative "distributionHeight", up to -ValidatorUpdateDelay, + // i.e. at the end of the pre-genesis block (none) = at the beginning of the genesis block. + // That's fine since this is just used to filter unbonding delegations & redelegations. + distributionHeight := infractionHeight - sdk.ValidatorUpdateDelay + + // get the percentage slash penalty fraction + fraction := k.SlashFractionDoubleSign(ctx) + + // Slash validator + // `power` is the int64 power of the validator as provided to/by + // Tendermint. This value is validator.Tokens as sent to Tendermint via + // ABCI, and now received as evidence. + // The fraction is passed in to separately to slash unbonding and rebonding delegations. + ctx.EventManager().EmitEvent( + sdk.NewEvent( + types.EventTypeSlash, + sdk.NewAttribute(types.AttributeKeyAddress, consAddr.String()), + sdk.NewAttribute(types.AttributeKeyPower, fmt.Sprintf("%d", power)), + sdk.NewAttribute(types.AttributeKeyReason, types.AttributeValueDoubleSign), + ), + ) + k.sk.Slash(ctx, consAddr, distributionHeight, power, fraction) + + // Jail validator if not already jailed + // begin unbonding validator if not already unbonding (tombstone) + if !validator.IsJailed() { ctx.EventManager().EmitEvent( sdk.NewEvent( - types.EventTypeSlashLimit, - sdk.NewAttribute(types.AttributeKeySlashedAmount, fmt.Sprintf("%d", totalSlashedAmount)), + types.EventTypeSlash, + sdk.NewAttribute(types.AttributeKeyJailed, consAddr.String()), ), ) - k.Logger(ctx).Info("Emitted SlashLimit event", "slashedAmountAttr", totalSlashedAmount) + k.sk.Jail(ctx, consAddr) } - return uint64(slashAmountInt) -} - -func (k *Keeper) GetTotalSlashedAmount(ctx sdk.Context) uint64 { - store := ctx.KVStore(k.storeKey) - if store.Has(types.TotalSlashedAmountKey) { - // get current Total slashed amount - totalSlashedAmountKey, err := strconv.ParseUint(string(store.Get(types.TotalSlashedAmountKey)), 10, 64) - if err != nil { - k.Logger(ctx).Error("Unable to convert key to int") - } else { - return totalSlashedAmountKey - } - } - - return 0 -} - -// IsSlashedLimitExceeded - if total slashed amount exceeded slash limit or not -func (k *Keeper) IsSlashedLimitExceeded(ctx sdk.Context) bool { - params := k.GetParams(ctx) - slashedAmount := k.GetTotalSlashedAmount(ctx) - totalPower := k.sk.GetTotalPower(ctx) + // Set tombstoned to be true + signInfo.Tombstoned = true - slashLimitDec := sdk.NewDec(totalPower).Mul(params.SlashFractionLimit) - slashLimit := slashLimitDec.TruncateInt().Int64() + // Set jailed until to be forever (max time) + signInfo.JailedUntil = types.DoubleSignJailEndTime - k.Logger(ctx).Info("checking if slash-limit exceeded", "totalPower", totalPower, "totalSlashedAmount", slashedAmount, "slashlimit", slashLimit) - if slashedAmount >= uint64(slashLimit) { - k.Logger(ctx).Debug("slash-limit exceeded", "totalPower", totalPower, "totalSlashedAmount", slashedAmount, "slashlimit", slashLimit) - return true - } - k.Logger(ctx).Debug("slash-limit not exceeded", "totalPower", totalPower, "totalSlashedAmount", slashedAmount, "slashlimit", slashLimit) - return false + // Set validator signing info + k.SetValidatorSigningInfo(ctx, consAddr, signInfo) } -// IsJailLimitExceeded - if jail limit is exceeded or not -func (k *Keeper) IsJailLimitExceeded(ctx sdk.Context, valSlashingInfo hmTypes.ValidatorSlashingInfo) bool { - params := k.GetParams(ctx) - valID := valSlashingInfo.ID - - slashedAmount := valSlashingInfo.SlashedAmount - val, _ := k.sk.GetValidatorFromValID(ctx, valID) - - jailLimitDec := sdk.NewDec(val.VotingPower).Mul(params.JailFractionLimit) - jailLimit := jailLimitDec.TruncateInt().Int64() - - k.Logger(ctx).Info("Checking if jail limit is exceeded", "valId", valID, "power", val.VotingPower, "slashedAmount", slashedAmount, "jailLimit", jailLimit, "jailLimitDec", jailLimitDec) - if slashedAmount >= uint64(jailLimit) { - k.Logger(ctx).Debug("Jail limit exceeded", "valId", valID, "power", val.VotingPower, "slashedAmount", slashedAmount, "jailLimit", jailLimit, "jailLimitDec", jailLimitDec) - return true +// handle a validator signature, must be called once per validator per block +// TODO refactor to take in a consensus address, additionally should maybe just take in the pubkey too +func (k Keeper) HandleValidatorSignature(ctx sdk.Context, addr crypto.Address, power int64, signed bool) { + logger := k.Logger(ctx) + height := ctx.BlockHeight() + consAddr := sdk.ConsAddress(addr) + pubkey, err := k.getPubkey(ctx, addr) + if err != nil { + panic(fmt.Sprintf("Validator consensus-address %s not found", consAddr)) } - k.Logger(ctx).Debug("Jail limit not exceeded", "valId", valID, "power", val.VotingPower, "slashedAmount", slashedAmount, "jailLimit", jailLimit, "jailLimitDec", jailLimitDec) - return false -} -// GetBufferValSlashingInfo gets the validator slashing info for a validator ID key -func (k *Keeper) GetBufferValSlashingInfo(ctx sdk.Context, valId hmTypes.ValidatorID) (info hmTypes.ValidatorSlashingInfo, found bool) { - store := ctx.KVStore(k.storeKey) - - bz := store.Get(types.GetBufferValSlashingInfoKey(valId.Bytes())) - if bz == nil { - found = false - return + // fetch signing info + signInfo, found := k.getValidatorSigningInfo(ctx, consAddr) + if !found { + panic(fmt.Sprintf("Expected signing info for validator %s but not found", consAddr)) } - k.cdc.MustUnmarshalBinaryBare(bz, &info) - found = true - return -} -// SetBufferValSlashingInfo sets the validator slashing info to a validator ID key -func (k *Keeper) SetBufferValSlashingInfo(ctx sdk.Context, valID hmTypes.ValidatorID, info hmTypes.ValidatorSlashingInfo) { - store := ctx.KVStore(k.storeKey) - bz := k.cdc.MustMarshalBinaryBare(&info) - store.Set(types.GetBufferValSlashingInfoKey(valID.Bytes()), bz) -} - -// RemoveBufferValSlashingInfo removes the validator slashing info for a validator ID key -func (k *Keeper) RemoveBufferValSlashingInfo(ctx sdk.Context, valID hmTypes.ValidatorID) { - store := ctx.KVStore(k.storeKey) - store.Delete(types.GetBufferValSlashingInfoKey(valID.Bytes())) -} - -// IterateBufferValSlashingInfos iterates over the stored ValidatorSlashingInfo -func (k *Keeper) IterateBufferValSlashingInfos(ctx sdk.Context, - handler func(slashingInfo hmTypes.ValidatorSlashingInfo) (stop bool)) { - - store := ctx.KVStore(k.storeKey) - iter := sdk.KVStorePrefixIterator(store, types.BufferValSlashingInfoKey) - defer iter.Close() - for ; iter.Valid(); iter.Next() { - var slashingInfo hmTypes.ValidatorSlashingInfo - k.cdc.MustUnmarshalBinaryBare(iter.Value(), &slashingInfo) - if handler(slashingInfo) { - break - } + // this is a relative index, so it counts blocks the validator *should* have signed + // will use the 0-value default signing info if not present, except for start height + index := signInfo.IndexOffset % k.SignedBlocksWindow(ctx) + signInfo.IndexOffset++ + + // Update signed block bit array & counter + // This counter just tracks the sum of the bit array + // That way we avoid needing to read/write the whole array each time + previous := k.getValidatorMissedBlockBitArray(ctx, consAddr, index) + missed := !signed + switch { + case !previous && missed: + // Array value has changed from not missed to missed, increment counter + k.setValidatorMissedBlockBitArray(ctx, consAddr, index, true) + signInfo.MissedBlocksCounter++ + case previous && !missed: + // Array value has changed from missed to not missed, decrement counter + k.setValidatorMissedBlockBitArray(ctx, consAddr, index, false) + signInfo.MissedBlocksCounter-- + default: + // Array value at this index has not changed, no need to update counter } -} -// FlushBufferValSlashingInfos removes all validator slashing infos in buffer -func (k *Keeper) FlushBufferValSlashingInfos(ctx sdk.Context) error { - // iterate through validator slashing info and create validator slashing info update array - err := k.IterateBufferValSlashingInfosAndApplyFn(ctx, func(valSlashingInfo hmTypes.ValidatorSlashingInfo) error { - // remove from buffer data - k.RemoveBufferValSlashingInfo(ctx, valSlashingInfo.ID) - return nil - }) - return err -} + if missed { + ctx.EventManager().EmitEvent( + sdk.NewEvent( + types.EventTypeLiveness, + sdk.NewAttribute(types.AttributeKeyAddress, consAddr.String()), + sdk.NewAttribute(types.AttributeKeyMissedBlocks, fmt.Sprintf("%d", signInfo.MissedBlocksCounter)), + sdk.NewAttribute(types.AttributeKeyHeight, fmt.Sprintf("%d", height)), + ), + ) -// FlushBufferValSlashingInfos removes all validator slashing infos in buffer -func (k *Keeper) FlushTotalSlashedAmount(ctx sdk.Context) { - store := ctx.KVStore(k.storeKey) - // remove from store - if store.Has(types.TotalSlashedAmountKey) { - store.Delete(types.TotalSlashedAmountKey) + logger.Info( + fmt.Sprintf("Absent validator %s (%s) at height %d, %d missed, threshold %d", consAddr, pubkey, height, signInfo.MissedBlocksCounter, k.MinSignedPerWindow(ctx))) } -} - -// IterateBufferValSlashingInfosAndApplyFn iterate ValidatorSlashingInfo and apply the given function. -func (k *Keeper) IterateBufferValSlashingInfosAndApplyFn(ctx sdk.Context, f func(slashingInfo hmTypes.ValidatorSlashingInfo) error) error { - store := ctx.KVStore(k.storeKey) - - // get validator iterator - iterator := sdk.KVStorePrefixIterator(store, types.BufferValSlashingInfoKey) - defer iterator.Close() - - // loop through validators to get valid validators - for ; iterator.Valid(); iterator.Next() { - // unmarshall validator - slashingInfo, err := hmTypes.UnmarshallValSlashingInfo(k.cdc, iterator.Value()) - if err != nil { - k.Logger(ctx).Error("Error unmarshalling val slashing info") - return err - } - // call function and return if required - if err := f(slashingInfo); err != nil { - return err + minHeight := signInfo.StartHeight + k.SignedBlocksWindow(ctx) + maxMissed := k.SignedBlocksWindow(ctx) - k.MinSignedPerWindow(ctx) + + // if we are past the minimum height and the validator has missed too many blocks, punish them + if height > minHeight && signInfo.MissedBlocksCounter > maxMissed { + validator := k.sk.ValidatorByConsAddr(ctx, consAddr) + if validator != nil && !validator.IsJailed() { + + // Downtime confirmed: slash and jail the validator + logger.Info(fmt.Sprintf("Validator %s past min height of %d and below signed blocks threshold of %d", + consAddr, minHeight, k.MinSignedPerWindow(ctx))) + + // We need to retrieve the stake distribution which signed the block, so we subtract ValidatorUpdateDelay from the evidence height, + // and subtract an additional 1 since this is the LastCommit. + // Note that this *can* result in a negative "distributionHeight" up to -ValidatorUpdateDelay-1, + // i.e. at the end of the pre-genesis block (none) = at the beginning of the genesis block. + // That's fine since this is just used to filter unbonding delegations & redelegations. + distributionHeight := height - sdk.ValidatorUpdateDelay - 1 + + ctx.EventManager().EmitEvent( + sdk.NewEvent( + types.EventTypeSlash, + sdk.NewAttribute(types.AttributeKeyAddress, consAddr.String()), + sdk.NewAttribute(types.AttributeKeyPower, fmt.Sprintf("%d", power)), + sdk.NewAttribute(types.AttributeKeyReason, types.AttributeValueMissingSignature), + sdk.NewAttribute(types.AttributeKeyJailed, consAddr.String()), + ), + ) + k.sk.Slash(ctx, consAddr, distributionHeight, power, k.SlashFractionDowntime(ctx)) + k.sk.Jail(ctx, consAddr) + + signInfo.JailedUntil = ctx.BlockHeader().Time.Add(k.DowntimeJailDuration(ctx)) + + // We need to reset the counter & array so that the validator won't be immediately slashed for downtime upon rebonding. + signInfo.MissedBlocksCounter = 0 + signInfo.IndexOffset = 0 + k.clearValidatorMissedBlockBitArray(ctx, consAddr) + } else { + // Validator was (a) not found or (b) already jailed, don't slash + logger.Info( + fmt.Sprintf("Validator %s would have been slashed for downtime, but was either not found in store or already jailed", consAddr), + ) } } - return nil -} - -// GetBufferValSlashingInfos returns all validator slashing infos in buffer -func (k *Keeper) GetBufferValSlashingInfos(ctx sdk.Context) (valSlashingInfos []*hmTypes.ValidatorSlashingInfo, err error) { - // iterate through validators and create validator update array - err = k.IterateBufferValSlashingInfosAndApplyFn(ctx, func(valSlashingInfo hmTypes.ValidatorSlashingInfo) error { - // append to list of valSlashingInfos - valSlashingInfos = append(valSlashingInfos, &valSlashingInfo) - return nil - }) - - return + // Set the updated signing info + k.SetValidatorSigningInfo(ctx, consAddr, signInfo) } -func (k *Keeper) UpdateTotalSlashedAmount(ctx sdk.Context, slashedAmount uint64) { - store := ctx.KVStore(k.storeKey) - current := k.GetTotalSlashedAmount(ctx) - updated := current + slashedAmount - - // convert - totalSlashedAmount := []byte(strconv.FormatUint(updated, 10)) - store.Set(types.TotalSlashedAmountKey, totalSlashedAmount) - k.Logger(ctx).Debug("Updated Total Slashed Amount ", "oldAmount", current, "newAmount", updated) +func (k Keeper) addPubkey(ctx sdk.Context, pubkey crypto.PubKey) { + addr := pubkey.Address() + k.setAddrPubkeyRelation(ctx, addr, pubkey) } -// GetTickValSlashingInfo gets the validator slashing info for a validator ID key -func (k *Keeper) GetTickValSlashingInfo(ctx sdk.Context, valId hmTypes.ValidatorID) (info hmTypes.ValidatorSlashingInfo, found bool) { +func (k Keeper) getPubkey(ctx sdk.Context, address crypto.Address) (crypto.PubKey, error) { store := ctx.KVStore(k.storeKey) - - bz := store.Get(types.GetTickValSlashingInfoKey(valId.Bytes())) - if bz == nil { - found = false - return + var pubkey crypto.PubKey + err := k.cdc.UnmarshalBinaryLengthPrefixed(store.Get(types.GetAddrPubkeyRelationKey(address)), &pubkey) + if err != nil { + return nil, fmt.Errorf("address %s not found", sdk.ConsAddress(address)) } - k.cdc.MustUnmarshalBinaryBare(bz, &info) - found = true - return + return pubkey, nil } -// GetTickValSlashingInfos returns all validator slashing infos in tick -func (k *Keeper) GetTickValSlashingInfos(ctx sdk.Context) (valSlashingInfos []*hmTypes.ValidatorSlashingInfo, err error) { - // iterate through validators and create slashing info update array - err = k.IterateTickValSlashingInfosAndApplyFn(ctx, func(valSlashingInfo hmTypes.ValidatorSlashingInfo) error { - // append to list of valSlashingInfos - valSlashingInfos = append(valSlashingInfos, &valSlashingInfo) - return nil - }) - - return -} - -// SetTickValSlashingInfo sets the validator slashing info to a validator ID key -func (k *Keeper) SetTickValSlashingInfo(ctx sdk.Context, valID hmTypes.ValidatorID, info hmTypes.ValidatorSlashingInfo) { - store := ctx.KVStore(k.storeKey) - bz := k.cdc.MustMarshalBinaryBare(&info) - store.Set(types.GetTickValSlashingInfoKey(valID.Bytes()), bz) -} - -// RemoveTickValSlashingInfo removes the validator slashing info for a validator ID key -func (k *Keeper) RemoveTickValSlashingInfo(ctx sdk.Context, valID hmTypes.ValidatorID) { +func (k Keeper) setAddrPubkeyRelation(ctx sdk.Context, addr crypto.Address, pubkey crypto.PubKey) { store := ctx.KVStore(k.storeKey) - store.Delete(types.GetTickValSlashingInfoKey(valID.Bytes())) -} - -// IterateTickValSlashingInfos iterates over the stored ValidatorSlashingInfo -func (k *Keeper) IterateTickValSlashingInfos(ctx sdk.Context, - handler func(slashingInfo hmTypes.ValidatorSlashingInfo) (stop bool)) { - - store := ctx.KVStore(k.storeKey) - iter := sdk.KVStorePrefixIterator(store, types.TickValSlashingInfoKey) - defer iter.Close() - for ; iter.Valid(); iter.Next() { - var slashingInfo hmTypes.ValidatorSlashingInfo - k.cdc.MustUnmarshalBinaryBare(iter.Value(), &slashingInfo) - if handler(slashingInfo) { - break - } - } + bz := k.cdc.MustMarshalBinaryLengthPrefixed(pubkey) + store.Set(types.GetAddrPubkeyRelationKey(addr), bz) } -// CopyValSlashingInfosToTickData copies all validator slashing infos in buffer to tickdata -func (k *Keeper) CopyBufferValSlashingInfosToTickData(ctx sdk.Context) error { - // iterate through validators and create validator slashing info update array - err := k.IterateBufferValSlashingInfosAndApplyFn(ctx, func(valSlashingInfo hmTypes.ValidatorSlashingInfo) error { - // store to tick data - k.SetTickValSlashingInfo(ctx, valSlashingInfo.ID, valSlashingInfo) - return nil - }) - - return err -} - -// IterateTickValSlashingInfosAndApplyFn iterate ValidatorSlashingInfo and apply the given function. -func (k *Keeper) IterateTickValSlashingInfosAndApplyFn(ctx sdk.Context, f func(slashingInfo hmTypes.ValidatorSlashingInfo) error) error { +func (k Keeper) deleteAddrPubkeyRelation(ctx sdk.Context, addr crypto.Address) { store := ctx.KVStore(k.storeKey) - - // get validator iterator - iterator := sdk.KVStorePrefixIterator(store, types.TickValSlashingInfoKey) - defer iterator.Close() - - // loop through validators to get valid validators - for ; iterator.Valid(); iterator.Next() { - // unmarshall validator - slashingInfo, _ := hmTypes.UnmarshallValSlashingInfo(k.cdc, iterator.Value()) - k.Logger(ctx).Debug("slashing the validator", "slashingInfo", slashingInfo) - // call function and return if required - if err := f(slashingInfo); err != nil { - // Error slashing validator - k.Logger(ctx).Error("Error slashing the validator", "error", err) - return err - } - } - return nil -} - -// SlashAndJailTickValSlashingInfos reduces power of all validator slashing infos in tick data -func (k *Keeper) SlashAndJailTickValSlashingInfos(ctx sdk.Context) error { - // iterate through validator slashing info and create validator slashing info update array - err := k.IterateTickValSlashingInfosAndApplyFn(ctx, func(valSlashingInfo hmTypes.ValidatorSlashingInfo) error { - err := k.sk.Slash(ctx, valSlashingInfo) - return err - }) - return err -} - -// FlushTickValSlashingInfos removes all validator slashing infos in last Tick -func (k *Keeper) FlushTickValSlashingInfos(ctx sdk.Context) error { - // iterate through validator slashing info and create validator slashing info update array - err := k.IterateTickValSlashingInfosAndApplyFn(ctx, func(valSlashingInfo hmTypes.ValidatorSlashingInfo) error { - // remove from tick data - k.RemoveTickValSlashingInfo(ctx, valSlashingInfo.ID) - return nil - }) - return err -} - -// -// Slashing sequence -// - -// SetSlashingSequence sets Slashing sequence -func (k *Keeper) SetSlashingSequence(ctx sdk.Context, sequence string) { - store := ctx.KVStore(k.storeKey) - - store.Set(types.GetSlashingSequenceKey(sequence), types.DefaultValue) -} - -// HasSlashingSequence checks if Slashing sequence already exists -func (k *Keeper) HasSlashingSequence(ctx sdk.Context, sequence string) bool { - store := ctx.KVStore(k.storeKey) - return store.Has(types.GetSlashingSequenceKey(sequence)) -} - -// GetSlashingSequences checks if Slashing already exists -func (k *Keeper) GetSlashingSequences(ctx sdk.Context) (sequences []string) { - k.IterateSlashingSequencesAndApplyFn(ctx, func(sequence string) error { - sequences = append(sequences, sequence) - return nil - }) - return -} - -// IterateSlashingSequencesAndApplyFn iterate validators and apply the given function. -func (k *Keeper) IterateSlashingSequencesAndApplyFn(ctx sdk.Context, f func(sequence string) error) { - store := ctx.KVStore(k.storeKey) - - // get sequence iterator - iterator := sdk.KVStorePrefixIterator(store, types.SlashingSequenceKey) - defer iterator.Close() - - // loop through validators to get valid validators - for ; iterator.Valid(); iterator.Next() { - sequence := string(iterator.Key()[len(types.SlashingSequenceKey):]) - - // call function and return if required - if err := f(sequence); err != nil { - return - } - } + store.Delete(types.GetAddrPubkeyRelationKey(addr)) } diff --git a/slashing/keeper_test.go b/slashing/keeper_test.go new file mode 100644 index 000000000..523cc6485 --- /dev/null +++ b/slashing/keeper_test.go @@ -0,0 +1,474 @@ +package slashing + +import ( + "testing" + "time" + + "github.com/stretchr/testify/require" + abci "github.com/tendermint/tendermint/abci/types" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/slashing/types" + "github.com/cosmos/cosmos-sdk/x/staking" +) + +// Have to change these parameters for tests +// lest the tests take forever +func keeperTestParams() types.Params { + params := types.DefaultParams() + params.SignedBlocksWindow = 1000 + params.DowntimeJailDuration = 60 * 60 + return params +} + +// ______________________________________________________________ + +// Test that a validator is slashed correctly +// when we discover evidence of infraction +func TestHandleDoubleSign(t *testing.T) { + + // initial setup + ctx, ck, sk, _, keeper := createTestInput(t, keeperTestParams()) + // validator added pre-genesis + ctx = ctx.WithBlockHeight(-1) + power := int64(100) + amt := sdk.TokensFromConsensusPower(power) + operatorAddr, val := addrs[0], pks[0] + got := staking.NewHandler(sk)(ctx, NewTestMsgCreateValidator(operatorAddr, val, amt)) + require.True(t, got.IsOK()) + staking.EndBlocker(ctx, sk) + require.Equal( + t, ck.GetCoins(ctx, sdk.AccAddress(operatorAddr)), + sdk.NewCoins(sdk.NewCoin(sk.GetParams(ctx).BondDenom, initTokens.Sub(amt))), + ) + require.Equal(t, amt, sk.Validator(ctx, operatorAddr).GetBondedTokens()) + + // handle a signature to set signing info + keeper.HandleValidatorSignature(ctx, val.Address(), amt.Int64(), true) + + oldTokens := sk.Validator(ctx, operatorAddr).GetTokens() + + // double sign less than max age + keeper.HandleDoubleSign(ctx, val.Address(), 0, time.Unix(0, 0), power) + + // should be jailed + require.True(t, sk.Validator(ctx, operatorAddr).IsJailed()) + + // tokens should be decreased + newTokens := sk.Validator(ctx, operatorAddr).GetTokens() + require.True(t, newTokens.LT(oldTokens)) + + // New evidence + keeper.HandleDoubleSign(ctx, val.Address(), 0, time.Unix(0, 0), power) + + // tokens should be the same (capped slash) + require.True(t, sk.Validator(ctx, operatorAddr).GetTokens().Equal(newTokens)) + + // Jump to past the unbonding period + ctx = ctx.WithBlockHeader(abci.Header{Time: time.Unix(1, 0).Add(sk.GetParams(ctx).UnbondingTime)}) + + // Still shouldn't be able to unjail + msgUnjail := types.NewMsgUnjail(operatorAddr) + res := handleMsgUnjail(ctx, msgUnjail, keeper) + require.False(t, res.IsOK()) + + // Should be able to unbond now + del, _ := sk.GetDelegation(ctx, sdk.AccAddress(operatorAddr), operatorAddr) + validator, _ := sk.GetValidator(ctx, operatorAddr) + + totalBond := validator.TokensFromShares(del.GetShares()).TruncateInt() + msgUnbond := staking.NewMsgUndelegate(sdk.AccAddress(operatorAddr), operatorAddr, sdk.NewCoin(sk.GetParams(ctx).BondDenom, totalBond)) + res = staking.NewHandler(sk)(ctx, msgUnbond) + require.True(t, res.IsOK()) +} + +// ______________________________________________________________ + +// Test that a validator is slashed correctly +// when we discover evidence of infraction +func TestPastMaxEvidenceAge(t *testing.T) { + + // initial setup + ctx, ck, sk, _, keeper := createTestInput(t, keeperTestParams()) + // validator added pre-genesis + ctx = ctx.WithBlockHeight(-1) + power := int64(100) + amt := sdk.TokensFromConsensusPower(power) + operatorAddr, val := addrs[0], pks[0] + got := staking.NewHandler(sk)(ctx, NewTestMsgCreateValidator(operatorAddr, val, amt)) + require.True(t, got.IsOK()) + staking.EndBlocker(ctx, sk) + require.Equal( + t, ck.GetCoins(ctx, sdk.AccAddress(operatorAddr)), + sdk.NewCoins(sdk.NewCoin(sk.GetParams(ctx).BondDenom, initTokens.Sub(amt))), + ) + require.Equal(t, amt, sk.Validator(ctx, operatorAddr).GetBondedTokens()) + + // handle a signature to set signing info + keeper.HandleValidatorSignature(ctx, val.Address(), power, true) + + ctx = ctx.WithBlockHeader(abci.Header{Time: time.Unix(1, 0).Add(keeper.MaxEvidenceAge(ctx))}) + + oldPower := sk.Validator(ctx, operatorAddr).GetConsensusPower() + + // double sign past max age + keeper.HandleDoubleSign(ctx, val.Address(), 0, time.Unix(0, 0), power) + + // should still be bonded + require.True(t, sk.Validator(ctx, operatorAddr).IsBonded()) + + // should still have same power + require.Equal(t, oldPower, sk.Validator(ctx, operatorAddr).GetConsensusPower()) +} + +// Test a validator through uptime, downtime, revocation, +// unrevocation, starting height reset, and revocation again +func TestHandleAbsentValidator(t *testing.T) { + + // initial setup + ctx, ck, sk, _, keeper := createTestInput(t, keeperTestParams()) + power := int64(100) + amt := sdk.TokensFromConsensusPower(power) + addr, val := addrs[0], pks[0] + sh := staking.NewHandler(sk) + slh := NewHandler(keeper) + got := sh(ctx, NewTestMsgCreateValidator(addr, val, amt)) + require.True(t, got.IsOK()) + staking.EndBlocker(ctx, sk) + + require.Equal( + t, ck.GetCoins(ctx, sdk.AccAddress(addr)), + sdk.NewCoins(sdk.NewCoin(sk.GetParams(ctx).BondDenom, initTokens.Sub(amt))), + ) + require.Equal(t, amt, sk.Validator(ctx, addr).GetBondedTokens()) + + // will exist since the validator has been bonded + info, found := keeper.getValidatorSigningInfo(ctx, sdk.ConsAddress(val.Address())) + require.True(t, found) + require.Equal(t, int64(0), info.StartHeight) + require.Equal(t, int64(0), info.IndexOffset) + require.Equal(t, int64(0), info.MissedBlocksCounter) + require.Equal(t, time.Unix(0, 0).UTC(), info.JailedUntil) + height := int64(0) + + // 1000 first blocks OK + for ; height < keeper.SignedBlocksWindow(ctx); height++ { + ctx = ctx.WithBlockHeight(height) + keeper.HandleValidatorSignature(ctx, val.Address(), power, true) + } + info, found = keeper.getValidatorSigningInfo(ctx, sdk.ConsAddress(val.Address())) + require.True(t, found) + require.Equal(t, int64(0), info.StartHeight) + require.Equal(t, int64(0), info.MissedBlocksCounter) + + // 500 blocks missed + for ; height < keeper.SignedBlocksWindow(ctx)+(keeper.SignedBlocksWindow(ctx)-keeper.MinSignedPerWindow(ctx)); height++ { + ctx = ctx.WithBlockHeight(height) + keeper.HandleValidatorSignature(ctx, val.Address(), power, false) + } + info, found = keeper.getValidatorSigningInfo(ctx, sdk.ConsAddress(val.Address())) + require.True(t, found) + require.Equal(t, int64(0), info.StartHeight) + require.Equal(t, keeper.SignedBlocksWindow(ctx)-keeper.MinSignedPerWindow(ctx), info.MissedBlocksCounter) + + // validator should be bonded still + validator, _ := sk.GetValidatorByConsAddr(ctx, sdk.GetConsAddress(val)) + require.Equal(t, sdk.Bonded, validator.GetStatus()) + bondPool := sk.GetBondedPool(ctx) + require.True(sdk.IntEq(t, amt, bondPool.GetCoins().AmountOf(sk.BondDenom(ctx)))) + + // 501st block missed + ctx = ctx.WithBlockHeight(height) + keeper.HandleValidatorSignature(ctx, val.Address(), power, false) + info, found = keeper.getValidatorSigningInfo(ctx, sdk.ConsAddress(val.Address())) + require.True(t, found) + require.Equal(t, int64(0), info.StartHeight) + // counter now reset to zero + require.Equal(t, int64(0), info.MissedBlocksCounter) + + // end block + staking.EndBlocker(ctx, sk) + + // validator should have been jailed + validator, _ = sk.GetValidatorByConsAddr(ctx, sdk.GetConsAddress(val)) + require.Equal(t, sdk.Unbonding, validator.GetStatus()) + + slashAmt := amt.ToDec().Mul(keeper.SlashFractionDowntime(ctx)).RoundInt64() + + // validator should have been slashed + require.Equal(t, amt.Int64()-slashAmt, validator.GetTokens().Int64()) + + // 502nd block *also* missed (since the LastCommit would have still included the just-unbonded validator) + height++ + ctx = ctx.WithBlockHeight(height) + keeper.HandleValidatorSignature(ctx, val.Address(), power, false) + info, found = keeper.getValidatorSigningInfo(ctx, sdk.ConsAddress(val.Address())) + require.True(t, found) + require.Equal(t, int64(0), info.StartHeight) + require.Equal(t, int64(1), info.MissedBlocksCounter) + + // end block + staking.EndBlocker(ctx, sk) + + // validator should not have been slashed any more, since it was already jailed + validator, _ = sk.GetValidatorByConsAddr(ctx, sdk.GetConsAddress(val)) + require.Equal(t, amt.Int64()-slashAmt, validator.GetTokens().Int64()) + + // unrevocation should fail prior to jail expiration + got = slh(ctx, NewMsgUnjail(addr)) + require.False(t, got.IsOK()) + + // unrevocation should succeed after jail expiration + ctx = ctx.WithBlockHeader(abci.Header{Time: time.Unix(1, 0).Add(keeper.DowntimeJailDuration(ctx))}) + got = slh(ctx, NewMsgUnjail(addr)) + require.True(t, got.IsOK()) + + // end block + staking.EndBlocker(ctx, sk) + + // validator should be rebonded now + validator, _ = sk.GetValidatorByConsAddr(ctx, sdk.GetConsAddress(val)) + require.Equal(t, sdk.Bonded, validator.GetStatus()) + + // validator should have been slashed + bondPool = sk.GetBondedPool(ctx) + require.Equal(t, amt.Int64()-slashAmt, bondPool.GetCoins().AmountOf(sk.BondDenom(ctx)).Int64()) + + // Validator start height should not have been changed + info, found = keeper.getValidatorSigningInfo(ctx, sdk.ConsAddress(val.Address())) + require.True(t, found) + require.Equal(t, int64(0), info.StartHeight) + // we've missed 2 blocks more than the maximum, so the counter was reset to 0 at 1 block more and is now 1 + require.Equal(t, int64(1), info.MissedBlocksCounter) + + // validator should not be immediately jailed again + height++ + ctx = ctx.WithBlockHeight(height) + keeper.HandleValidatorSignature(ctx, val.Address(), power, false) + validator, _ = sk.GetValidatorByConsAddr(ctx, sdk.GetConsAddress(val)) + require.Equal(t, sdk.Bonded, validator.GetStatus()) + + // 500 signed blocks + nextHeight := height + keeper.MinSignedPerWindow(ctx) + 1 + for ; height < nextHeight; height++ { + ctx = ctx.WithBlockHeight(height) + keeper.HandleValidatorSignature(ctx, val.Address(), power, false) + } + + // end block + staking.EndBlocker(ctx, sk) + + // validator should be jailed again after 500 unsigned blocks + nextHeight = height + keeper.MinSignedPerWindow(ctx) + 1 + for ; height <= nextHeight; height++ { + ctx = ctx.WithBlockHeight(height) + keeper.HandleValidatorSignature(ctx, val.Address(), power, false) + } + + // end block + staking.EndBlocker(ctx, sk) + + validator, _ = sk.GetValidatorByConsAddr(ctx, sdk.GetConsAddress(val)) + require.Equal(t, sdk.Unbonding, validator.GetStatus()) +} + +// Test a new validator entering the validator set +// Ensure that SigningInfo.StartHeight is set correctly +// and that they are not immediately jailed +func TestHandleNewValidator(t *testing.T) { + // initial setup + ctx, ck, sk, _, keeper := createTestInput(t, keeperTestParams()) + addr, val := addrs[0], pks[0] + amt := sdk.TokensFromConsensusPower(100) + sh := staking.NewHandler(sk) + + // 1000 first blocks not a validator + ctx = ctx.WithBlockHeight(keeper.SignedBlocksWindow(ctx) + 1) + + // Validator created + got := sh(ctx, NewTestMsgCreateValidator(addr, val, amt)) + require.True(t, got.IsOK()) + staking.EndBlocker(ctx, sk) + + require.Equal( + t, ck.GetCoins(ctx, sdk.AccAddress(addr)), + sdk.NewCoins(sdk.NewCoin(sk.GetParams(ctx).BondDenom, initTokens.Sub(amt))), + ) + require.Equal(t, amt, sk.Validator(ctx, addr).GetBondedTokens()) + + // Now a validator, for two blocks + keeper.HandleValidatorSignature(ctx, val.Address(), 100, true) + ctx = ctx.WithBlockHeight(keeper.SignedBlocksWindow(ctx) + 2) + keeper.HandleValidatorSignature(ctx, val.Address(), 100, false) + + info, found := keeper.getValidatorSigningInfo(ctx, sdk.ConsAddress(val.Address())) + require.True(t, found) + require.Equal(t, keeper.SignedBlocksWindow(ctx)+1, info.StartHeight) + require.Equal(t, int64(2), info.IndexOffset) + require.Equal(t, int64(1), info.MissedBlocksCounter) + require.Equal(t, time.Unix(0, 0).UTC(), info.JailedUntil) + + // validator should be bonded still, should not have been jailed or slashed + validator, _ := sk.GetValidatorByConsAddr(ctx, sdk.GetConsAddress(val)) + require.Equal(t, sdk.Bonded, validator.GetStatus()) + bondPool := sk.GetBondedPool(ctx) + expTokens := sdk.TokensFromConsensusPower(100) + require.Equal(t, expTokens.Int64(), bondPool.GetCoins().AmountOf(sk.BondDenom(ctx)).Int64()) +} + +// Test a jailed validator being "down" twice +// Ensure that they're only slashed once +func TestHandleAlreadyJailed(t *testing.T) { + + // initial setup + ctx, _, sk, _, keeper := createTestInput(t, DefaultParams()) + power := int64(100) + amt := sdk.TokensFromConsensusPower(power) + addr, val := addrs[0], pks[0] + sh := staking.NewHandler(sk) + got := sh(ctx, NewTestMsgCreateValidator(addr, val, amt)) + require.True(t, got.IsOK()) + staking.EndBlocker(ctx, sk) + + // 1000 first blocks OK + height := int64(0) + for ; height < keeper.SignedBlocksWindow(ctx); height++ { + ctx = ctx.WithBlockHeight(height) + keeper.HandleValidatorSignature(ctx, val.Address(), power, true) + } + + // 501 blocks missed + for ; height < keeper.SignedBlocksWindow(ctx)+(keeper.SignedBlocksWindow(ctx)-keeper.MinSignedPerWindow(ctx))+1; height++ { + ctx = ctx.WithBlockHeight(height) + keeper.HandleValidatorSignature(ctx, val.Address(), power, false) + } + + // end block + staking.EndBlocker(ctx, sk) + + // validator should have been jailed and slashed + validator, _ := sk.GetValidatorByConsAddr(ctx, sdk.GetConsAddress(val)) + require.Equal(t, sdk.Unbonding, validator.GetStatus()) + + // validator should have been slashed + resultingTokens := amt.Sub(sdk.TokensFromConsensusPower(1)) + require.Equal(t, resultingTokens, validator.GetTokens()) + + // another block missed + ctx = ctx.WithBlockHeight(height) + keeper.HandleValidatorSignature(ctx, val.Address(), power, false) + + // validator should not have been slashed twice + validator, _ = sk.GetValidatorByConsAddr(ctx, sdk.GetConsAddress(val)) + require.Equal(t, resultingTokens, validator.GetTokens()) + +} + +// Test a validator dipping in and out of the validator set +// Ensure that missed blocks are tracked correctly and that +// the start height of the signing info is reset correctly +func TestValidatorDippingInAndOut(t *testing.T) { + + // initial setup + // keeperTestParams set the SignedBlocksWindow to 1000 and MaxMissedBlocksPerWindow to 500 + ctx, _, sk, _, keeper := createTestInput(t, keeperTestParams()) + params := sk.GetParams(ctx) + params.MaxValidators = 1 + sk.SetParams(ctx, params) + power := int64(100) + amt := sdk.TokensFromConsensusPower(power) + addr, val := addrs[0], pks[0] + consAddr := sdk.ConsAddress(addr) + sh := staking.NewHandler(sk) + got := sh(ctx, NewTestMsgCreateValidator(addr, val, amt)) + require.True(t, got.IsOK()) + staking.EndBlocker(ctx, sk) + + // 100 first blocks OK + height := int64(0) + for ; height < int64(100); height++ { + ctx = ctx.WithBlockHeight(height) + keeper.HandleValidatorSignature(ctx, val.Address(), power, true) + } + + // kick first validator out of validator set + newAmt := sdk.TokensFromConsensusPower(101) + got = sh(ctx, NewTestMsgCreateValidator(addrs[1], pks[1], newAmt)) + require.True(t, got.IsOK()) + validatorUpdates := staking.EndBlocker(ctx, sk) + require.Equal(t, 2, len(validatorUpdates)) + validator, _ := sk.GetValidator(ctx, addr) + require.Equal(t, sdk.Unbonding, validator.Status) + + // 600 more blocks happened + height = int64(700) + ctx = ctx.WithBlockHeight(height) + + // validator added back in + delTokens := sdk.TokensFromConsensusPower(50) + got = sh(ctx, newTestMsgDelegate(sdk.AccAddress(addrs[2]), addrs[0], delTokens)) + require.True(t, got.IsOK()) + validatorUpdates = staking.EndBlocker(ctx, sk) + require.Equal(t, 2, len(validatorUpdates)) + validator, _ = sk.GetValidator(ctx, addr) + require.Equal(t, sdk.Bonded, validator.Status) + newPower := int64(150) + + // validator misses a block + keeper.HandleValidatorSignature(ctx, val.Address(), newPower, false) + height++ + + // shouldn't be jailed/kicked yet + validator, _ = sk.GetValidator(ctx, addr) + require.Equal(t, sdk.Bonded, validator.Status) + + // validator misses 500 more blocks, 501 total + latest := height + for ; height < latest+500; height++ { + ctx = ctx.WithBlockHeight(height) + keeper.HandleValidatorSignature(ctx, val.Address(), newPower, false) + } + + // should now be jailed & kicked + staking.EndBlocker(ctx, sk) + validator, _ = sk.GetValidator(ctx, addr) + require.Equal(t, sdk.Unbonding, validator.Status) + + // check all the signing information + signInfo, found := keeper.getValidatorSigningInfo(ctx, consAddr) + require.True(t, found) + require.Equal(t, int64(0), signInfo.MissedBlocksCounter) + require.Equal(t, int64(0), signInfo.IndexOffset) + // array should be cleared + for offset := int64(0); offset < keeper.SignedBlocksWindow(ctx); offset++ { + missed := keeper.getValidatorMissedBlockBitArray(ctx, consAddr, offset) + require.False(t, missed) + } + + // some blocks pass + height = int64(5000) + ctx = ctx.WithBlockHeight(height) + + // validator rejoins and starts signing again + sk.Unjail(ctx, consAddr) + keeper.HandleValidatorSignature(ctx, val.Address(), newPower, true) + height++ + + // validator should not be kicked since we reset counter/array when it was jailed + staking.EndBlocker(ctx, sk) + validator, _ = sk.GetValidator(ctx, addr) + require.Equal(t, sdk.Bonded, validator.Status) + + // validator misses 501 blocks + latest = height + for ; height < latest+501; height++ { + ctx = ctx.WithBlockHeight(height) + keeper.HandleValidatorSignature(ctx, val.Address(), newPower, false) + } + + // validator should now be jailed & kicked + staking.EndBlocker(ctx, sk) + validator, _ = sk.GetValidator(ctx, addr) + require.Equal(t, sdk.Unbonding, validator.Status) + +} diff --git a/slashing/module.go b/slashing/module.go index d78a43b33..3538f96d6 100644 --- a/slashing/module.go +++ b/slashing/module.go @@ -2,217 +2,136 @@ package slashing import ( "encoding/json" - "fmt" - "math/rand" "github.com/gorilla/mux" "github.com/spf13/cobra" + abci "github.com/tendermint/tendermint/abci/types" + "github.com/cosmos/cosmos-sdk/client/context" "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/module" - abci "github.com/tendermint/tendermint/abci/types" - - chainmanagerTypes "github.com/maticnetwork/heimdall/chainmanager/types" - "github.com/maticnetwork/heimdall/helper" - slashingCli "github.com/maticnetwork/heimdall/slashing/client/cli" - "github.com/maticnetwork/heimdall/slashing/client/rest" - "github.com/maticnetwork/heimdall/slashing/simulation" - "github.com/maticnetwork/heimdall/slashing/types" - "github.com/maticnetwork/heimdall/staking" - hmTypes "github.com/maticnetwork/heimdall/types" - hmModule "github.com/maticnetwork/heimdall/types/module" - simTypes "github.com/maticnetwork/heimdall/types/simulation" + "github.com/cosmos/cosmos-sdk/x/slashing/client/cli" + "github.com/cosmos/cosmos-sdk/x/slashing/client/rest" + "github.com/cosmos/cosmos-sdk/x/slashing/types" ) var ( - _ module.AppModule = AppModule{} - _ module.AppModuleBasic = AppModuleBasic{} - _ hmModule.HeimdallModuleBasic = AppModule{} - _ hmModule.AppModuleSimulation = AppModule{} + _ module.AppModule = AppModule{} + _ module.AppModuleBasic = AppModuleBasic{} ) -// AppModuleBasic defines the basic application module used by the slashing module. +// app module basics object type AppModuleBasic struct{} var _ module.AppModuleBasic = AppModuleBasic{} -// Name returns the slashing module's name. +// module name func (AppModuleBasic) Name() string { return types.ModuleName } -// RegisterCodec registers the slashing module's types for the given codec. +// register module codec func (AppModuleBasic) RegisterCodec(cdc *codec.Codec) { - types.RegisterCodec(cdc) + RegisterCodec(cdc) } -// DefaultGenesis returns default genesis state as raw bytes for the slashing -// module. +// default genesis state func (AppModuleBasic) DefaultGenesis() json.RawMessage { - return types.ModuleCdc.MustMarshalJSON(types.DefaultGenesisState()) + return ModuleCdc.MustMarshalJSON(DefaultGenesisState()) } -// ValidateGenesis performs genesis state validation for the slashing module. +// module validate genesis func (AppModuleBasic) ValidateGenesis(bz json.RawMessage) error { - var data types.GenesisState - if err := types.ModuleCdc.UnmarshalJSON(bz, &data); err != nil { - return fmt.Errorf("failed to unmarshal %s genesis state: %w", types.ModuleName, err) - } - - return types.ValidateGenesis(data) -} - -// VerifyGenesis performs verification on slashing module state. -func (AppModuleBasic) VerifyGenesis(bz map[string]json.RawMessage) error { - var chainManagertData chainmanagerTypes.GenesisState - errcm := chainmanagerTypes.ModuleCdc.UnmarshalJSON(bz[chainmanagerTypes.ModuleName], &chainManagertData) - if errcm != nil { - return errcm - } - - var data types.GenesisState - err := types.ModuleCdc.UnmarshalJSON(bz[types.ModuleName], &data) - + var data GenesisState + err := ModuleCdc.UnmarshalJSON(bz, &data) if err != nil { return err } - return nil + return ValidateGenesis(data) } -// RegisterRESTRoutes registers the REST routes for the slashing module. +// register rest routes func (AppModuleBasic) RegisterRESTRoutes(ctx context.CLIContext, rtr *mux.Router) { rest.RegisterRoutes(ctx, rtr) } -// GetTxCmd returns the root tx command for the slashing module. +// get the root tx command of this module func (AppModuleBasic) GetTxCmd(cdc *codec.Codec) *cobra.Command { - return slashingCli.GetTxCmd(cdc) + return cli.GetTxCmd(cdc) } -// GetQueryCmd returns no root query command for the slashing module. +// get the root query command of this module func (AppModuleBasic) GetQueryCmd(cdc *codec.Codec) *cobra.Command { - return slashingCli.GetQueryCmd(cdc) - + return cli.GetQueryCmd(StoreKey, cdc) } -//____________________________________________________________________________ - -// AppModule implements an application module for the slashing module. +//___________________________ +// app module type AppModule struct { AppModuleBasic - - keeper Keeper - stakingKeeper staking.Keeper - contractCaller helper.IContractCaller + keeper Keeper + stakingKeeper types.StakingKeeper } // NewAppModule creates a new AppModule object -func NewAppModule(keeper Keeper, sk staking.Keeper, contractCaller helper.IContractCaller) AppModule { +func NewAppModule(keeper Keeper, stakingKeeper types.StakingKeeper) AppModule { return AppModule{ AppModuleBasic: AppModuleBasic{}, keeper: keeper, - stakingKeeper: sk, - contractCaller: contractCaller, + stakingKeeper: stakingKeeper, } } -// Name returns the slashing module's name. +// module name func (AppModule) Name() string { - return types.ModuleName + return ModuleName } -// RegisterInvariants registers the slashing module invariants. +// register invariants func (am AppModule) RegisterInvariants(_ sdk.InvariantRegistry) {} -// Route returns the message routing key for the slashing module. +// module message route name func (AppModule) Route() string { - return types.RouterKey + return RouterKey } -// NewHandler returns an sdk.Handler for the slashing module. +// module handler func (am AppModule) NewHandler() sdk.Handler { - return NewHandler(am.keeper, am.contractCaller) + return NewHandler(am.keeper) } -// QuerierRoute returns the slashing module's querier route name. +// module querier route name func (AppModule) QuerierRoute() string { - return types.QuerierRoute + return QuerierRoute } -// NewQuerierHandler returns the slashing module sdk.Querier. +// module querier func (am AppModule) NewQuerierHandler() sdk.Querier { return NewQuerier(am.keeper) } -// InitGenesis performs genesis initialization for the slashing module. It returns -// no validator updates. +// module init-genesis func (am AppModule) InitGenesis(ctx sdk.Context, data json.RawMessage) []abci.ValidatorUpdate { - var genesisState types.GenesisState - types.ModuleCdc.MustUnmarshalJSON(data, &genesisState) - InitGenesis(ctx, am.keeper, genesisState) + var genesisState GenesisState + ModuleCdc.MustUnmarshalJSON(data, &genesisState) + InitGenesis(ctx, am.keeper, am.stakingKeeper, genesisState) return []abci.ValidatorUpdate{} - } -// ExportGenesis returns the exported genesis state as raw bytes for the auth -// module. +// module export genesis func (am AppModule) ExportGenesis(ctx sdk.Context) json.RawMessage { gs := ExportGenesis(ctx, am.keeper) - return types.ModuleCdc.MustMarshalJSON(gs) + return ModuleCdc.MustMarshalJSON(gs) } -// BeginBlock returns the begin blocker for the slashing module. +// module begin-block func (am AppModule) BeginBlock(ctx sdk.Context, req abci.RequestBeginBlock) { BeginBlocker(ctx, req, am.keeper) } -// EndBlock returns the end blocker for the slashing module. It returns no validator -// updates. +// module end-block func (AppModule) EndBlock(_ sdk.Context, _ abci.RequestEndBlock) []abci.ValidatorUpdate { return []abci.ValidatorUpdate{} } - -//____________________________________________________________________________ - -// AppModuleSimulation functions - -// GenerateGenesisState creates a randomized GenState of the auth module -func (AppModule) GenerateGenesisState(simState *hmModule.SimulationState) { - simulation.RandomizedGenState(simState) -} - -// ProposalContents doesn't return any content functions for governance proposals. -func (AppModule) ProposalContents(simState hmModule.SimulationState) []simTypes.WeightedProposalContent { - return nil -} - -// RandomizedParams creates randomized auth param changes for the simulator. -func (AppModule) RandomizedParams(r *rand.Rand) []simTypes.ParamChange { - return simulation.ParamChanges(r) -} - -// RegisterStoreDecoder registers a decoder for auth module's types -func (AppModule) RegisterStoreDecoder(sdr hmModule.StoreDecoderRegistry) { - sdr[types.StoreKey] = simulation.DecodeStore -} - -// WeightedOperations doesn't return any auth module operation. -func (AppModule) WeightedOperations(_ hmModule.SimulationState) []simTypes.WeightedOperation { - return nil -} - -// -// Side module -// - -// NewSideTxHandler side tx handler -func (am AppModule) NewSideTxHandler() hmTypes.SideTxHandler { - return NewSideTxHandler(am.keeper, am.contractCaller) -} - -// NewPostTxHandler side tx handler -func (am AppModule) NewPostTxHandler() hmTypes.PostTxHandler { - return NewPostTxHandler(am.keeper, am.contractCaller) -} diff --git a/slashing/msg_test.go b/slashing/msg_test.go deleted file mode 100644 index 424d72f3b..000000000 --- a/slashing/msg_test.go +++ /dev/null @@ -1,29 +0,0 @@ -package slashing_test - -import ( - "encoding/hex" - "testing" - - "github.com/maticnetwork/heimdall/helper" - slashingTypes "github.com/maticnetwork/heimdall/slashing/types" - hmTypes "github.com/maticnetwork/heimdall/types" -) - -func TestMsgTick(t *testing.T) { - // create msg Tick message - msg := slashingTypes.NewMsgTick( - uint64(2), - hmTypes.BytesToHeimdallAddress(helper.GetAddress()), - hmTypes.HexToHexBytes("0xdacc01893635c9adc5dea0000080cc02890caf6700370168000001"), - ) - t.Log(hmTypes.BytesToHeimdallAddress(helper.GetAddress())) - t.Log(hmTypes.HexToHexBytes("0xdacc01893635c9adc5dea0000080cc02890caf6700370168000001")) - - t.Log(msg.Proposer) - t.Log(msg.SlashingInfoBytes) - - t.Log(msg.Proposer.String()) - t.Log(msg.SlashingInfoBytes.String()) - - t.Log(hex.EncodeToString(msg.GetSideSignBytes())) -} diff --git a/slashing/params.go b/slashing/params.go new file mode 100644 index 000000000..dfe676a88 --- /dev/null +++ b/slashing/params.go @@ -0,0 +1,55 @@ +package slashing + +import ( + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/slashing/types" +) + +// MaxEvidenceAge - max age for evidence +func (k Keeper) MaxEvidenceAge(ctx sdk.Context) (res time.Duration) { + k.paramspace.Get(ctx, types.KeyMaxEvidenceAge, &res) + return +} + +// SignedBlocksWindow - sliding window for downtime slashing +func (k Keeper) SignedBlocksWindow(ctx sdk.Context) (res int64) { + k.paramspace.Get(ctx, types.KeySignedBlocksWindow, &res) + return +} + +// Downtime slashing threshold +func (k Keeper) MinSignedPerWindow(ctx sdk.Context) int64 { + var minSignedPerWindow sdk.Dec + k.paramspace.Get(ctx, types.KeyMinSignedPerWindow, &minSignedPerWindow) + signedBlocksWindow := k.SignedBlocksWindow(ctx) + + // NOTE: RoundInt64 will never panic as minSignedPerWindow is + // less than 1. + return minSignedPerWindow.MulInt64(signedBlocksWindow).RoundInt64() +} + +// Downtime unbond duration +func (k Keeper) DowntimeJailDuration(ctx sdk.Context) (res time.Duration) { + k.paramspace.Get(ctx, types.KeyDowntimeJailDuration, &res) + return +} + +// SlashFractionDoubleSign +func (k Keeper) SlashFractionDoubleSign(ctx sdk.Context) (res sdk.Dec) { + k.paramspace.Get(ctx, types.KeySlashFractionDoubleSign, &res) + return +} + +// SlashFractionDowntime +func (k Keeper) SlashFractionDowntime(ctx sdk.Context) (res sdk.Dec) { + k.paramspace.Get(ctx, types.KeySlashFractionDowntime, &res) + return +} + +// GetParams returns the total set of slashing parameters. +func (k Keeper) GetParams(ctx sdk.Context) (params types.Params) { + k.paramspace.GetParamSet(ctx, ¶ms) + return params +} diff --git a/slashing/querier.go b/slashing/querier.go index 5afa8e043..32be853f9 100644 --- a/slashing/querier.go +++ b/slashing/querier.go @@ -2,250 +2,88 @@ package slashing import ( "fmt" - "math/big" + + abci "github.com/tendermint/tendermint/abci/types" "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" - jsoniter "github.com/json-iterator/go" - abci "github.com/tendermint/tendermint/abci/types" - - "github.com/maticnetwork/heimdall/helper" - "github.com/maticnetwork/heimdall/slashing/types" - hmTypes "github.com/maticnetwork/heimdall/types" ) // NewQuerier creates a new querier for slashing clients. func NewQuerier(k Keeper) sdk.Querier { return func(ctx sdk.Context, path []string, req abci.RequestQuery) ([]byte, sdk.Error) { switch path[0] { - case types.QueryParameters: + case QueryParameters: return queryParams(ctx, k) - - case types.QueryTickCount: - return handleQueryTickCount(ctx, req, k) - - case types.QuerySigningInfo: + case QuerySigningInfo: return querySigningInfo(ctx, req, k) - - case types.QuerySigningInfos: + case QuerySigningInfos: return querySigningInfos(ctx, req, k) - - case types.QuerySlashingInfo: - return querySlashingInfo(ctx, req, k) - - case types.QuerySlashingInfos: - return querySlashingInfos(ctx, req, k) - - case types.QuerySlashingInfoBytes: - return querySlashingInfoBytes(ctx, req, k) - - case types.QueryTickSlashingInfos: - return queryTickSlashingInfos(ctx, req, k) - - case types.QuerySlashingSequence: - return querySlashingSequence(ctx, req, k) - default: - return nil, sdk.ErrUnknownRequest("unknown slashing query endpoint") + return nil, sdk.ErrUnknownRequest("unknown staking query endpoint") } } } -func queryParams(ctx sdk.Context, keeper Keeper) ([]byte, sdk.Error) { - bz, err := jsoniter.ConfigFastest.Marshal(keeper.GetParams(ctx)) - if err != nil { - return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", err.Error())) - } - return bz, nil -} - -func querySigningInfo(ctx sdk.Context, req abci.RequestQuery, k Keeper) ([]byte, sdk.Error) { - var params types.QuerySigningInfoParams +func queryParams(ctx sdk.Context, k Keeper) ([]byte, sdk.Error) { + params := k.GetParams(ctx) - if err := types.ModuleCdc.UnmarshalJSON(req.Data, ¶ms); err != nil { - return nil, sdk.ErrInternal(fmt.Sprintf("failed to parse params: %s", err)) - } - - // get validator signing info - signingInfo, found := k.GetValidatorSigningInfo(ctx, params.ValidatorID) - if !found { - return nil, sdk.ErrInternal("Error while getting validator signing info") - } - - // json record - bz, err := jsoniter.ConfigFastest.Marshal(signingInfo) - if err != nil { - return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", err.Error())) - } - return bz, nil -} - -func querySigningInfos(ctx sdk.Context, req abci.RequestQuery, k Keeper) ([]byte, sdk.Error) { - var params types.QuerySigningInfosParams - - if err := types.ModuleCdc.UnmarshalJSON(req.Data, ¶ms); err != nil { - return nil, sdk.ErrInternal(fmt.Sprintf("failed to parse params: %s", err)) - } - - var signingInfos []hmTypes.ValidatorSigningInfo - - k.IterateValidatorSigningInfos(ctx, func(valID hmTypes.ValidatorID, info hmTypes.ValidatorSigningInfo) (stop bool) { - signingInfos = append(signingInfos, info) - return false - }) - - start, end := client.Paginate(len(signingInfos), params.Page, params.Limit, len(signingInfos)) - if start < 0 || end < 0 { - signingInfos = []hmTypes.ValidatorSigningInfo{} - } else { - signingInfos = signingInfos[start:end] - } - - // json record - bz, err := jsoniter.ConfigFastest.Marshal(signingInfos) + res, err := codec.MarshalJSONIndent(ModuleCdc, params) if err != nil { - return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", err.Error())) + return nil, sdk.ErrInternal(sdk.AppendMsgToErr("failed to marshal JSON", err.Error())) } - return bz, nil -} -func handleQueryTickCount(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) ([]byte, sdk.Error) { - bz, err := jsoniter.ConfigFastest.Marshal(keeper.GetTickCount(ctx)) - if err != nil { - return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", err.Error())) - } - return bz, nil + return res, nil } -func querySlashingInfo(ctx sdk.Context, req abci.RequestQuery, k Keeper) ([]byte, sdk.Error) { - var params types.QuerySlashingInfoParams +func querySigningInfo(ctx sdk.Context, req abci.RequestQuery, k Keeper) ([]byte, sdk.Error) { + var params QuerySigningInfoParams - if err := types.ModuleCdc.UnmarshalJSON(req.Data, ¶ms); err != nil { + err := ModuleCdc.UnmarshalJSON(req.Data, ¶ms) + if err != nil { return nil, sdk.ErrInternal(fmt.Sprintf("failed to parse params: %s", err)) } - // get validator slashing info - slashingInfo, found := k.GetBufferValSlashingInfo(ctx, params.ValidatorID) + signingInfo, found := k.getValidatorSigningInfo(ctx, params.ConsAddress) if !found { - return nil, sdk.ErrInternal(" slashing info not found for given val") + return nil, ErrNoSigningInfoFound(DefaultCodespace, params.ConsAddress) } - // json record - bz, err := jsoniter.ConfigFastest.Marshal(slashingInfo) + res, err := codec.MarshalJSONIndent(ModuleCdc, signingInfo) if err != nil { - return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", err.Error())) - } - return bz, nil -} - -func querySlashingInfos(ctx sdk.Context, req abci.RequestQuery, k Keeper) ([]byte, sdk.Error) { - var params types.QuerySlashingInfosParams - - if err := types.ModuleCdc.UnmarshalJSON(req.Data, ¶ms); err != nil { - return nil, sdk.ErrInternal(fmt.Sprintf("failed to parse params: %s", err)) + return nil, sdk.ErrInternal(sdk.AppendMsgToErr("failed to JSON marshal result: %s", err.Error())) } - var slashingInfos []hmTypes.ValidatorSlashingInfo - - k.IterateBufferValSlashingInfos(ctx, func(info hmTypes.ValidatorSlashingInfo) (stop bool) { - slashingInfos = append(slashingInfos, info) - return false - }) - - start, end := client.Paginate(len(slashingInfos), params.Page, params.Limit, len(slashingInfos)) - if start < 0 || end < 0 { - slashingInfos = []hmTypes.ValidatorSlashingInfo{} - } else { - slashingInfos = slashingInfos[start:end] - } - - // json record - bz, err := jsoniter.ConfigFastest.Marshal(slashingInfos) - if err != nil { - return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", err.Error())) - } - return bz, nil + return res, nil } -func querySlashingInfoBytes(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) ([]byte, sdk.Error) { - // Calculate new slashInfo bytes - slashingInfos, err := keeper.GetBufferValSlashingInfos(ctx) - if err != nil { - return nil, sdk.ErrInternal(sdk.AppendMsgToErr("no slash infos in buffer", err.Error())) - } +func querySigningInfos(ctx sdk.Context, req abci.RequestQuery, k Keeper) ([]byte, sdk.Error) { + var params QuerySigningInfosParams - slashingInfoBytes, err := types.SortAndRLPEncodeSlashInfos(slashingInfos) + err := ModuleCdc.UnmarshalJSON(req.Data, ¶ms) if err != nil { - return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not fetch slashingInfoBytes ", err.Error())) - } - return slashingInfoBytes, nil -} - -func queryTickSlashingInfos(ctx sdk.Context, req abci.RequestQuery, k Keeper) ([]byte, sdk.Error) { - var params types.QueryTickSlashingInfosParams - - if err := types.ModuleCdc.UnmarshalJSON(req.Data, ¶ms); err != nil { return nil, sdk.ErrInternal(fmt.Sprintf("failed to parse params: %s", err)) } - var slashingInfos []hmTypes.ValidatorSlashingInfo + var signingInfos []ValidatorSigningInfo - k.IterateTickValSlashingInfos(ctx, func(info hmTypes.ValidatorSlashingInfo) (stop bool) { - slashingInfos = append(slashingInfos, info) + k.IterateValidatorSigningInfos(ctx, func(consAddr sdk.ConsAddress, info ValidatorSigningInfo) (stop bool) { + signingInfos = append(signingInfos, info) return false }) - start, end := client.Paginate(len(slashingInfos), params.Page, params.Limit, len(slashingInfos)) + start, end := client.Paginate(len(signingInfos), params.Page, params.Limit, int(k.sk.MaxValidators(ctx))) if start < 0 || end < 0 { - slashingInfos = []hmTypes.ValidatorSlashingInfo{} + signingInfos = []ValidatorSigningInfo{} } else { - slashingInfos = slashingInfos[start:end] - } - - // json record - bz, err := jsoniter.ConfigFastest.Marshal(slashingInfos) - if err != nil { - return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", err.Error())) - } - return bz, nil -} - -func querySlashingSequence(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) ([]byte, sdk.Error) { - var params types.QuerySlashingSequenceParams - - if err := types.ModuleCdc.UnmarshalJSON(req.Data, ¶ms); err != nil { - return nil, sdk.ErrInternal(fmt.Sprintf("failed to parse params: %s", err)) - } - - chainParams := keeper.chainKeeper.GetParams(ctx) - - contractCallerObj, err := helper.NewContractCaller() - if err != nil { - return nil, sdk.ErrInternal(err.Error()) - } - - // get main tx receipt - receipt, err := contractCallerObj.GetConfirmedTxReceipt(hmTypes.HexToHeimdallHash(params.TxHash).EthHash(), chainParams.MainchainTxConfirmations) - if err != nil || receipt == nil { - return nil, sdk.ErrInternal("Transaction is not confirmed yet. Please wait for sometime and try again") - } - - // sequence id - - sequence := new(big.Int).Mul(receipt.BlockNumber, big.NewInt(hmTypes.DefaultLogIndexUnit)) - sequence.Add(sequence, new(big.Int).SetUint64(params.LogIndex)) - - // check if incoming tx already exists - if !keeper.HasSlashingSequence(ctx, sequence.String()) { - keeper.Logger(ctx).Error("No slashing sequence exist: %s %s", params.TxHash, params.LogIndex) - return nil, nil + signingInfos = signingInfos[start:end] } - bz, err := codec.MarshalJSONIndent(types.ModuleCdc, sequence) + res, err := codec.MarshalJSONIndent(ModuleCdc, signingInfos) if err != nil { - return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", err.Error())) + return nil, sdk.ErrInternal(sdk.AppendMsgToErr("failed to JSON marshal result: %s", err.Error())) } - return bz, nil + return res, nil } diff --git a/slashing/querier_test.go b/slashing/querier_test.go new file mode 100644 index 000000000..a4941e0a9 --- /dev/null +++ b/slashing/querier_test.go @@ -0,0 +1,37 @@ +package slashing + +import ( + "testing" + + "github.com/stretchr/testify/require" + abci "github.com/tendermint/tendermint/abci/types" + + "github.com/cosmos/cosmos-sdk/codec" +) + +func TestNewQuerier(t *testing.T) { + ctx, _, _, _, keeper := createTestInput(t, keeperTestParams()) + querier := NewQuerier(keeper) + + query := abci.RequestQuery{ + Path: "", + Data: []byte{}, + } + + _, err := querier(ctx, []string{"parameters"}, query) + require.NoError(t, err) +} + +func TestQueryParams(t *testing.T) { + cdc := codec.New() + ctx, _, _, _, keeper := createTestInput(t, keeperTestParams()) + + var params Params + + res, errRes := queryParams(ctx, keeper) + require.NoError(t, errRes) + + err := cdc.UnmarshalJSON(res, ¶ms) + require.NoError(t, err) + require.Equal(t, keeper.GetParams(ctx), params) +} diff --git a/slashing/side_handler.go b/slashing/side_handler.go deleted file mode 100644 index 75c320126..000000000 --- a/slashing/side_handler.go +++ /dev/null @@ -1,357 +0,0 @@ -package slashing - -import ( - "bytes" - "encoding/hex" - "math/big" - - sdk "github.com/cosmos/cosmos-sdk/types" - abci "github.com/tendermint/tendermint/abci/types" - - "github.com/maticnetwork/heimdall/common" - hmCommon "github.com/maticnetwork/heimdall/common" - "github.com/maticnetwork/heimdall/helper" - "github.com/maticnetwork/heimdall/slashing/types" - hmTypes "github.com/maticnetwork/heimdall/types" - tmTypes "github.com/tendermint/tendermint/types" -) - -// NewSideTxHandler returns a side handler for "topup" type messages. -func NewSideTxHandler(k Keeper, contractCaller helper.IContractCaller) hmTypes.SideTxHandler { - return func(ctx sdk.Context, msg sdk.Msg) abci.ResponseDeliverSideTx { - ctx = ctx.WithEventManager(sdk.NewEventManager()) - switch msg := msg.(type) { - case types.MsgTick: - return SideHandleMsgTick(ctx, k, msg, contractCaller) - case types.MsgTickAck: - return SideHandleMsgTickAck(ctx, k, msg, contractCaller) - case types.MsgUnjail: - return SideHandleMsgUnjail(ctx, k, msg, contractCaller) - default: - return abci.ResponseDeliverSideTx{ - Code: uint32(sdk.CodeUnknownRequest), - } - } - } -} - -// NewPostTxHandler returns a side handler for "bank" type messages. -func NewPostTxHandler(k Keeper, contractCaller helper.IContractCaller) hmTypes.PostTxHandler { - return func(ctx sdk.Context, msg sdk.Msg, sideTxResult abci.SideTxResultType) sdk.Result { - ctx = ctx.WithEventManager(sdk.NewEventManager()) - switch msg := msg.(type) { - case types.MsgTick: - return PostHandleMsgTick(ctx, k, msg, sideTxResult) - case types.MsgTickAck: - return PostHandleMsgTickAck(ctx, k, msg, sideTxResult) - case types.MsgUnjail: - return PostHandleMsgUnjail(ctx, k, msg, sideTxResult) - default: - errMsg := "Unrecognized slash Msg type: %s" + msg.Type() - return sdk.ErrUnknownRequest(errMsg).Result() - } - } -} - -// SideHandleMsgTick handles MsgTick message for external call -func SideHandleMsgTick(ctx sdk.Context, k Keeper, msg types.MsgTick, contractCaller helper.IContractCaller) (result abci.ResponseDeliverSideTx) { - k.Logger(ctx).Debug("✅ Validating External call for tick msg") - k.Logger(ctx).Debug("✅ Successfully validated External call for tick msg") - result.Result = abci.SideTxResultType_Yes - return -} - -// SideHandleMsgTick handles MsgTick message for external call -func SideHandleMsgTickAck(ctx sdk.Context, k Keeper, msg types.MsgTickAck, contractCaller helper.IContractCaller) (result abci.ResponseDeliverSideTx) { - k.Logger(ctx).Debug("✅ Validating External call for tick-ack msg", - "txHash", hmTypes.BytesToHeimdallHash(msg.TxHash.Bytes()), - "logIndex", uint64(msg.LogIndex), - "blockNumber", msg.BlockNumber, - ) - - // chainManager params - params := k.chainKeeper.GetParams(ctx) - chainParams := params.ChainParams - - // get main tx receipt - receipt, err := contractCaller.GetConfirmedTxReceipt(msg.TxHash.EthHash(), params.MainchainTxConfirmations) - if err != nil || receipt == nil { - return hmCommon.ErrorSideTx(k.Codespace(), common.CodeWaitFrConfirmation) - } - - // get event log for slashed event - eventLog, err := contractCaller.DecodeSlashedEvent(chainParams.StakingInfoAddress.EthAddress(), receipt, msg.LogIndex) - if err != nil || eventLog == nil { - k.Logger(ctx).Error("Error fetching log from txhash") - return hmCommon.ErrorSideTx(k.Codespace(), common.CodeErrDecodeEvent) - } - - if receipt.BlockNumber.Uint64() != msg.BlockNumber { - k.Logger(ctx).Error("BlockNumber in message doesn't match blocknumber in receipt", "MsgBlockNumber", msg.BlockNumber, "ReceiptBlockNumber", receipt.BlockNumber.Uint64) - return hmCommon.ErrorSideTx(k.Codespace(), common.CodeInvalidMsg) - } - - if eventLog.Amount.Uint64() != msg.SlashedAmount { - k.Logger(ctx).Error("SlashedAmount in message doesn't match SlashedAmount in event logs", "MsgSlashedAmount", msg.SlashedAmount, "SlashedAmountFromEvent", eventLog.Amount) - return hmCommon.ErrorSideTx(k.Codespace(), common.CodeInvalidMsg) - } - - k.Logger(ctx).Debug("✅ Successfully validated External call for tick-ack msg") - result.Result = abci.SideTxResultType_Yes - return -} - -// SideHandleMsgUnjail handles MsgUnjail message for external call -func SideHandleMsgUnjail(ctx sdk.Context, k Keeper, msg types.MsgUnjail, contractCaller helper.IContractCaller) (result abci.ResponseDeliverSideTx) { - k.Logger(ctx).Debug("✅ Validating External call for unjail msg", - "txHash", hmTypes.BytesToHeimdallHash(msg.TxHash.Bytes()), - "logIndex", uint64(msg.LogIndex), - "blockNumber", msg.BlockNumber, - "validatorID", msg.ID, - ) - - // chainManager params - params := k.chainKeeper.GetParams(ctx) - chainParams := params.ChainParams - - // get main tx receipt - receipt, err := contractCaller.GetConfirmedTxReceipt(msg.TxHash.EthHash(), params.MainchainTxConfirmations) - if err != nil || receipt == nil { - return hmCommon.ErrorSideTx(k.Codespace(), common.CodeWaitFrConfirmation) - } - - // get unjail event - eventLog, err := contractCaller.DecodeUnJailedEvent(chainParams.StakingInfoAddress.EthAddress(), receipt, msg.LogIndex) - if err != nil || eventLog == nil { - k.Logger(ctx).Error("Error fetching log from txhash") - return hmCommon.ErrorSideTx(k.Codespace(), common.CodeErrDecodeEvent) - } - - if receipt.BlockNumber.Uint64() != msg.BlockNumber { - k.Logger(ctx).Error("BlockNumber in message doesn't match blocknumber in receipt", "MsgBlockNumber", msg.BlockNumber, "ReceiptBlockNumber", receipt.BlockNumber.Uint64) - return hmCommon.ErrorSideTx(k.Codespace(), common.CodeInvalidMsg) - } - - if eventLog.ValidatorId.Uint64() != msg.ID.Uint64() { - k.Logger(ctx).Error("ID in message doesn't match id in logs", "MsgID", msg.ID, "IdFromTx", eventLog.ValidatorId) - return hmCommon.ErrorSideTx(k.Codespace(), common.CodeInvalidMsg) - } - - k.Logger(ctx).Debug("✅ Successfully validated External call for tick msg") - result.Result = abci.SideTxResultType_Yes - return -} - -// PostHandleMsgTick - handles slashing of validators -// 1. copy slashBuffer into latestTickData -// 2. flush slashBuffer, totalSlashedAmount -// 3. iterate and reduce the power of slashed validators. -// 4. Also update the jailStatus of Validator -// 5. emit event TickConfirmation -func PostHandleMsgTick(ctx sdk.Context, k Keeper, msg types.MsgTick, sideTxResult abci.SideTxResultType) sdk.Result { - - // Skip handler if tick is not approved - if sideTxResult != abci.SideTxResultType_Yes { - k.Logger(ctx).Debug("Skipping new tick since side-tx didn't get yes votes") - return common.ErrSideTxValidation(k.Codespace()).Result() - } - - // check for replay - tick should be in conitunity - tickCount := k.GetTickCount(ctx) - if msg.ID != tickCount+1 { - k.Logger(ctx).Error("Tick not in continuity. may be due to replay", "msgID", msg.ID, "expectedMsgID", tickCount+1) - return hmCommon.ErrTickNotInContinuity(k.Codespace()).Result() - } - - // check if state has changed between handler and side-handler blocks - valSlashingInfos, err := k.GetBufferValSlashingInfos(ctx) - if err != nil { - k.Logger(ctx).Error("Error fetching slash Info list from buffer", "error", err) - return hmCommon.ErrSlashInfoDetails(k.Codespace()).Result() - } - slashingInfoBytes, err := types.SortAndRLPEncodeSlashInfos(valSlashingInfos) - if err != nil { - k.Logger(ctx).Info("Error generating slashing info bytes", "error", err) - return hmCommon.ErrSlashInfoDetails(k.Codespace()).Result() - } - k.Logger(ctx).Info("SlashInfo bytes generated", "SlashInfoBytes", hex.EncodeToString(slashingInfoBytes)) - // compare slashingInfoHash with msg hash - if !bytes.Equal(slashingInfoBytes, msg.SlashingInfoBytes) { - k.Logger(ctx).Error("slashingInfoBytes of current buffer state", "bufferSlashingInfoBytes", hex.EncodeToString(slashingInfoBytes), - "doesn't match with slashingInfoBytes of msg", "msgSlashInfoBytes", msg.SlashingInfoBytes.String()) - return hmCommon.ErrSlashInfoDetails(k.Codespace()).Result() - } - - k.Logger(ctx).Debug("Persisting tick state", "sideTxResult", sideTxResult) - - // copy slashBuffer into latestTickData - if err := k.CopyBufferValSlashingInfosToTickData(ctx); err != nil { - k.Logger(ctx).Error("Error copying bufferSlashInfo to tickSlashInfo", "error", err) - return common.ErrSlashInfoDetails(k.Codespace()).Result() - } - - // Flush slashBuffer - if err := k.FlushBufferValSlashingInfos(ctx); err != nil { - k.Logger(ctx).Error("Error flushing buffer slash info in tick handler", "error", err) - return common.ErrSlashInfoDetails(k.Codespace()).Result() - } - - // Flush TotalSlashedAmount - k.FlushTotalSlashedAmount(ctx) - - // Update Tick count - k.IncrementTickCount(ctx) - - k.Logger(ctx).Debug("Successfully slashed and jailed") - // TX bytes - txBytes := ctx.TxBytes() - hash := tmTypes.Tx(txBytes).Hash() - - ctx.EventManager().EmitEvents(sdk.Events{ - sdk.NewEvent( - types.EventTypeTickConfirm, - sdk.NewAttribute(sdk.AttributeKeyAction, msg.Type()), // action - sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory), // module name - sdk.NewAttribute(hmTypes.AttributeKeyTxHash, hmTypes.BytesToHeimdallHash(hash).Hex()), // tx hash - sdk.NewAttribute(hmTypes.AttributeKeySideTxResult, sideTxResult.String()), // result - sdk.NewAttribute(types.AttributeKeyProposer, msg.Proposer.String()), - sdk.NewAttribute(types.AttributeKeySlashInfoBytes, msg.SlashingInfoBytes.String()), - ), - }) - - return sdk.Result{ - Events: ctx.EventManager().Events(), - } -} - -// PostHandleMsgTickAck - handles slashing of validators -/* - handleMsgTickAck - handle msg tick ack event - 1. validate the tx hash in the event - 2. flush the last tick slashing info -*/ -func PostHandleMsgTickAck(ctx sdk.Context, k Keeper, msg types.MsgTickAck, sideTxResult abci.SideTxResultType) sdk.Result { - - // Skip handler if topup is not approved - if sideTxResult != abci.SideTxResultType_Yes { - k.Logger(ctx).Debug("Skipping new topup since side-tx didn't get yes votes") - return common.ErrSideTxValidation(k.Codespace()).Result() - } - - // check if tick ack msgs are in continuity. - tickCount := k.GetTickCount(ctx) - if msg.ID != tickCount { - k.Logger(ctx).Error("Tick-ack not in continuity.", "msgID", msg.ID, "expectedMsgID", tickCount) - return hmCommon.ErrTickAckNotInContinuity(k.Codespace()).Result() - } - - // check for replay - check if incoming tx is older - blockNumber := new(big.Int).SetUint64(msg.BlockNumber) - sequence := new(big.Int).Mul(blockNumber, big.NewInt(hmTypes.DefaultLogIndexUnit)) - sequence.Add(sequence, new(big.Int).SetUint64(msg.LogIndex)) - - if k.HasSlashingSequence(ctx, sequence.String()) { - k.Logger(ctx).Error("Older invalid tx found") - return hmCommon.ErrOldTx(k.Codespace()).Result() - } - - tickSlashInfos, err := k.GetTickValSlashingInfos(ctx) - if err != nil { - k.Logger(ctx).Error("Error fetching tick slash infos", "error", err) - return common.ErrSlashInfoDetails(k.Codespace()).Result() - } - - if len(tickSlashInfos) == 0 { - k.Logger(ctx).Error("tick ack already processed", "error", err) - return common.ErrSlashInfoDetails(k.Codespace()).Result() - } - - // slash validator - Iterate lastTickData and reduce power of each validator along with jailing if needed - if err = k.SlashAndJailTickValSlashingInfos(ctx); err != nil { - k.Logger(ctx).Error("Error slashing and jailing validator in tick handler", "error", err) - return common.ErrSlashInfoDetails(k.Codespace()).Result() - } - - // remove validator slashing infos from tick data - if err = k.FlushTickValSlashingInfos(ctx); err != nil { - k.Logger(ctx).Error("Error flushing tick slash info in tick-ack handler", "error", err) - return common.ErrSlashInfoDetails(k.Codespace()).Result() - } - - k.Logger(ctx).Debug("Successfully flushed tick slash info in tick-ack handler") - - // save staking sequence - k.SetSlashingSequence(ctx, sequence.String()) - - // TX bytes - txBytes := ctx.TxBytes() - hash := tmTypes.Tx(txBytes).Hash() - - ctx.EventManager().EmitEvent( - sdk.NewEvent( - types.EventTypeTickAck, - sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory), - sdk.NewAttribute(sdk.AttributeKeyAction, msg.Type()), // action - sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory), // module name - sdk.NewAttribute(hmTypes.AttributeKeyTxHash, hmTypes.BytesToHeimdallHash(hash).Hex()), // tx hash - sdk.NewAttribute(hmTypes.AttributeKeySideTxResult, sideTxResult.String()), // result - ), - ) - - return sdk.Result{ - Events: ctx.EventManager().Events(), - } -} - -// PostHandleMsgUnjail must submit a transaction to unjail itself after -// having been jailed (and thus unbonded) for downtime -func PostHandleMsgUnjail(ctx sdk.Context, k Keeper, msg types.MsgUnjail, sideTxResult abci.SideTxResultType) sdk.Result { - // Skip handler if topup is not approved - if sideTxResult != abci.SideTxResultType_Yes { - k.Logger(ctx).Debug("Skipping new topup since side-tx didn't get yes votes") - return common.ErrSideTxValidation(k.Codespace()).Result() - } - - // check if incoming tx is older - blockNumber := new(big.Int).SetUint64(msg.BlockNumber) - sequence := new(big.Int).Mul(blockNumber, big.NewInt(hmTypes.DefaultLogIndexUnit)) - sequence.Add(sequence, new(big.Int).SetUint64(msg.LogIndex)) - - if k.HasSlashingSequence(ctx, sequence.String()) { - k.Logger(ctx).Error("Older invalid tx found") - return hmCommon.ErrOldTx(k.Codespace()).Result() - } - - k.Logger(ctx).Debug("Persisting jail status", "sideTxResult", sideTxResult) - - // unjail validator - k.sk.Unjail(ctx, msg.ID) - - // check if unjail is successful or not - val, _ := k.sk.GetValidatorFromValID(ctx, msg.ID) - if val.Jailed { - k.Logger(ctx).Error("Error unjailing validator", "validatorId", msg.ID, "jailStatus", val.Jailed) - return hmCommon.ErrUnjailValidator(k.Codespace()).Result() - } - - // save staking sequence - k.SetSlashingSequence(ctx, sequence.String()) - - // TX bytes - txBytes := ctx.TxBytes() - hash := tmTypes.Tx(txBytes).Hash() - - ctx.EventManager().EmitEvent( - sdk.NewEvent( - types.EventTypeUnjail, - sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory), - sdk.NewAttribute(sdk.AttributeKeyAction, msg.Type()), // action - sdk.NewAttribute(hmTypes.AttributeKeyTxHash, hmTypes.BytesToHeimdallHash(hash).Hex()), // tx hash - sdk.NewAttribute(hmTypes.AttributeKeySideTxResult, sideTxResult.String()), // result - ), - ) - - return sdk.Result{ - Events: ctx.EventManager().Events(), - } -} diff --git a/slashing/signing_info.go b/slashing/signing_info.go new file mode 100644 index 000000000..f9cce43ac --- /dev/null +++ b/slashing/signing_info.go @@ -0,0 +1,93 @@ +package slashing + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/slashing/types" +) + +// Stored by *validator* address (not operator address) +func (k Keeper) getValidatorSigningInfo(ctx sdk.Context, address sdk.ConsAddress) (info types.ValidatorSigningInfo, found bool) { + store := ctx.KVStore(k.storeKey) + bz := store.Get(types.GetValidatorSigningInfoKey(address)) + if bz == nil { + found = false + return + } + k.cdc.MustUnmarshalBinaryLengthPrefixed(bz, &info) + found = true + return +} + +// Stored by *validator* address (not operator address) +func (k Keeper) IterateValidatorSigningInfos(ctx sdk.Context, + handler func(address sdk.ConsAddress, info types.ValidatorSigningInfo) (stop bool)) { + + store := ctx.KVStore(k.storeKey) + iter := sdk.KVStorePrefixIterator(store, types.ValidatorSigningInfoKey) + defer iter.Close() + for ; iter.Valid(); iter.Next() { + address := types.GetValidatorSigningInfoAddress(iter.Key()) + var info types.ValidatorSigningInfo + k.cdc.MustUnmarshalBinaryLengthPrefixed(iter.Value(), &info) + if handler(address, info) { + break + } + } +} + +// Stored by *validator* address (not operator address) +func (k Keeper) SetValidatorSigningInfo(ctx sdk.Context, address sdk.ConsAddress, info types.ValidatorSigningInfo) { + store := ctx.KVStore(k.storeKey) + bz := k.cdc.MustMarshalBinaryLengthPrefixed(info) + store.Set(types.GetValidatorSigningInfoKey(address), bz) +} + +// Stored by *validator* address (not operator address) +func (k Keeper) getValidatorMissedBlockBitArray(ctx sdk.Context, address sdk.ConsAddress, index int64) (missed bool) { + store := ctx.KVStore(k.storeKey) + bz := store.Get(types.GetValidatorMissedBlockBitArrayKey(address, index)) + if bz == nil { + // lazy: treat empty key as not missed + missed = false + return + } + k.cdc.MustUnmarshalBinaryLengthPrefixed(bz, &missed) + return +} + +// Stored by *validator* address (not operator address) +func (k Keeper) IterateValidatorMissedBlockBitArray(ctx sdk.Context, + address sdk.ConsAddress, handler func(index int64, missed bool) (stop bool)) { + + store := ctx.KVStore(k.storeKey) + index := int64(0) + // Array may be sparse + for ; index < k.SignedBlocksWindow(ctx); index++ { + var missed bool + bz := store.Get(types.GetValidatorMissedBlockBitArrayKey(address, index)) + if bz == nil { + continue + } + k.cdc.MustUnmarshalBinaryLengthPrefixed(bz, &missed) + if handler(index, missed) { + break + } + } +} + +// Stored by *validator* address (not operator address) +func (k Keeper) setValidatorMissedBlockBitArray(ctx sdk.Context, address sdk.ConsAddress, index int64, missed bool) { + store := ctx.KVStore(k.storeKey) + bz := k.cdc.MustMarshalBinaryLengthPrefixed(missed) + store.Set(types.GetValidatorMissedBlockBitArrayKey(address, index), bz) +} + +// Stored by *validator* address (not operator address) +func (k Keeper) clearValidatorMissedBlockBitArray(ctx sdk.Context, address sdk.ConsAddress) { + store := ctx.KVStore(k.storeKey) + iter := sdk.KVStorePrefixIterator(store, types.GetValidatorMissedBlockBitArrayPrefixKey(address)) + defer iter.Close() + for ; iter.Valid(); iter.Next() { + store.Delete(iter.Key()) + } +} diff --git a/slashing/signing_info_test.go b/slashing/signing_info_test.go new file mode 100644 index 000000000..d6803af8d --- /dev/null +++ b/slashing/signing_info_test.go @@ -0,0 +1,40 @@ +package slashing + +import ( + "testing" + "time" + + "github.com/stretchr/testify/require" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +func TestGetSetValidatorSigningInfo(t *testing.T) { + ctx, _, _, _, keeper := createTestInput(t, DefaultParams()) + info, found := keeper.getValidatorSigningInfo(ctx, sdk.ConsAddress(addrs[0])) + require.False(t, found) + newInfo := NewValidatorSigningInfo( + sdk.ConsAddress(addrs[0]), + int64(4), + int64(3), + time.Unix(2, 0), + false, + int64(10), + ) + keeper.SetValidatorSigningInfo(ctx, sdk.ConsAddress(addrs[0]), newInfo) + info, found = keeper.getValidatorSigningInfo(ctx, sdk.ConsAddress(addrs[0])) + require.True(t, found) + require.Equal(t, info.StartHeight, int64(4)) + require.Equal(t, info.IndexOffset, int64(3)) + require.Equal(t, info.JailedUntil, time.Unix(2, 0).UTC()) + require.Equal(t, info.MissedBlocksCounter, int64(10)) +} + +func TestGetSetValidatorMissedBlockBitArray(t *testing.T) { + ctx, _, _, _, keeper := createTestInput(t, DefaultParams()) + missed := keeper.getValidatorMissedBlockBitArray(ctx, sdk.ConsAddress(addrs[0]), 0) + require.False(t, missed) // treat empty key as not missed + keeper.setValidatorMissedBlockBitArray(ctx, sdk.ConsAddress(addrs[0]), 0, true) + missed = keeper.getValidatorMissedBlockBitArray(ctx, sdk.ConsAddress(addrs[0]), 0) + require.True(t, missed) // now should be missed +} diff --git a/slashing/simulation/decoder.go b/slashing/simulation/decoder.go deleted file mode 100644 index cdf78f7ae..000000000 --- a/slashing/simulation/decoder.go +++ /dev/null @@ -1,40 +0,0 @@ -package simulation - -import ( - "bytes" - "fmt" - - "github.com/cosmos/cosmos-sdk/codec" - sdk "github.com/cosmos/cosmos-sdk/types" - gogotypes "github.com/gogo/protobuf/types" - "github.com/maticnetwork/heimdall/slashing/types" - hmTpyes "github.com/maticnetwork/heimdall/types" -) - -// DecodeStore unmarshals the KVPair's Value to the corresponding slashing type -func DecodeStore(cdc *codec.Codec, kvA, kvB sdk.KVPair) string { - switch { - case bytes.Equal(kvA.Key[:1], types.ValidatorSigningInfoKey): - var infoA, infoB hmTpyes.ValidatorSigningInfo - cdc.MustUnmarshalBinaryBare(kvA.Value, &infoA) - cdc.MustUnmarshalBinaryBare(kvB.Value, &infoB) - return fmt.Sprintf("%v\n%v", infoA, infoB) - - case bytes.Equal(kvA.Key[:1], types.ValidatorMissedBlockBitArrayKey): - var missedA, missedB gogotypes.BoolValue - cdc.MustUnmarshalBinaryBare(kvA.Value, &missedA) - cdc.MustUnmarshalBinaryBare(kvB.Value, &missedB) - return fmt.Sprintf("missedA: %v\nmissedB: %v", missedA.Value, missedB.Value) - - /* case bytes.Equal(kvA.Key[:1], types.AddrPubkeyRelationKey): - var pubKeyA, pubKeyB crypto.PubKey - cdc.MustUnmarshalBinaryBare(kvA.Value, &pubKeyA) - cdc.MustUnmarshalBinaryBare(kvB.Value, &pubKeyB) - bechPKA := sdk.MustBech32ifyPubKey(sdk.Bech32PubKeyTypeAccPub, pubKeyA) - bechPKB := sdk.MustBech32ifyPubKey(sdk.Bech32PubKeyTypeAccPub, pubKeyB) - return fmt.Sprintf("PubKeyA: %s\nPubKeyB: %s", bechPKA, bechPKB) - */ - default: - panic(fmt.Sprintf("invalid slashing key prefix %X", kvA.Key[:1])) - } -} diff --git a/slashing/simulation/decoder_test.go b/slashing/simulation/decoder_test.go deleted file mode 100644 index 189c35b2e..000000000 --- a/slashing/simulation/decoder_test.go +++ /dev/null @@ -1,61 +0,0 @@ -package simulation - -import ( - "fmt" - "testing" - - "github.com/cosmos/cosmos-sdk/codec" - sdk "github.com/cosmos/cosmos-sdk/types" - gogotypes "github.com/gogo/protobuf/types" - "github.com/maticnetwork/heimdall/slashing/types" - hmTypes "github.com/maticnetwork/heimdall/types" - "github.com/stretchr/testify/require" - "github.com/tendermint/tendermint/crypto/secp256k1" -) - -// nolint:deadcode,unused,varcheck -var ( - delPk1 = secp256k1.GenPrivKey().PubKey() - consAddr1 = hmTypes.BytesToHeimdallAddress(delPk1.Address().Bytes()) -) - -func makeTestCodec() (cdc *codec.Codec) { - cdc = codec.New() - sdk.RegisterCodec(cdc) - codec.RegisterCrypto(cdc) - types.RegisterCodec(cdc) - return -} - -func TestDecodeStore(t *testing.T) { - cdc := makeTestCodec() - - info := hmTypes.NewValidatorSigningInfo(1, 0, 1, 0) - missed := gogotypes.BoolValue{Value: true} - - kvPairs := []sdk.KVPair{ - {Key: types.GetValidatorSigningInfoKey(consAddr1.Bytes()), Value: cdc.MustMarshalBinaryBare(info)}, - {Key: types.GetValidatorMissedBlockBitArrayKey(consAddr1.Bytes(), 6), Value: cdc.MustMarshalBinaryBare(&missed)}, - {Key: []byte{0x99}, Value: []byte{0x99}}, - } - - tests := []struct { - name string - expectedLog string - }{ - {"ValidatorSigningInfo", fmt.Sprintf("%v\n%v", info, info)}, - {"ValidatorMissedBlockBitArray", fmt.Sprintf("missedA: %v\nmissedB: %v", missed.Value, missed.Value)}, - {"other", ""}, - } - for i, tt := range tests { - i, tt := i, tt - t.Run(tt.name, func(t *testing.T) { - switch i { - case len(tests) - 1: - require.Panics(t, func() { DecodeStore(cdc, kvPairs[i], kvPairs[i]) }, tt.name) - default: - require.Equal(t, tt.expectedLog, DecodeStore(cdc, kvPairs[i], kvPairs[i]), tt.name) - } - }) - } -} diff --git a/slashing/simulation/genesis.go b/slashing/simulation/genesis.go deleted file mode 100644 index 1cd7195ce..000000000 --- a/slashing/simulation/genesis.go +++ /dev/null @@ -1,141 +0,0 @@ -package simulation - -// DONTCOVER - -import ( - "fmt" - "math/rand" - "time" - - "github.com/cosmos/cosmos-sdk/codec" - - sdk "github.com/cosmos/cosmos-sdk/types" - - "github.com/maticnetwork/heimdall/slashing/types" - "github.com/maticnetwork/heimdall/types/module" - "github.com/maticnetwork/heimdall/types/simulation" -) - -// Simulation parameter constants -const ( - SignedBlocksWindow = "signed_blocks_window" - MinSignedPerWindow = "min_signed_per_window" - DowntimeJailDuration = "downtime_jail_duration" - SlashFractionDoubleSign = "slash_fraction_double_sign" - SlashFractionDowntime = "slash_fraction_downtime" - SlashFractionLimit = "slash_fraction_limit" - JailFractionLimit = "jail_fraction_limit" - MaxEvidenceAge = "max_evidence_age" -) - -// GenSignedBlocksWindow randomized SignedBlocksWindow -func GenSignedBlocksWindow(r *rand.Rand) int64 { - return int64(simulation.RandIntBetween(r, 10, 1000)) -} - -// GenMinSignedPerWindow randomized MinSignedPerWindow -func GenMinSignedPerWindow(r *rand.Rand) sdk.Dec { - return sdk.NewDecWithPrec(int64(r.Intn(10)), 1) -} - -// GenDowntimeJailDuration randomized DowntimeJailDuration -func GenDowntimeJailDuration(r *rand.Rand) time.Duration { - return time.Duration(simulation.RandIntBetween(r, 60, 60*60*24)) * time.Second -} - -// GenSlashFractionDoubleSign randomized SlashFractionDoubleSign -func GenSlashFractionDoubleSign(r *rand.Rand) sdk.Dec { - return sdk.NewDec(1).Quo(sdk.NewDec(int64(r.Intn(50) + 1))) -} - -// GenSlashFractionDowntime randomized SlashFractionDowntime -func GenSlashFractionDowntime(r *rand.Rand) sdk.Dec { - return sdk.NewDec(1).Quo(sdk.NewDec(int64(r.Intn(200) + 1))) -} - -// GenSlashFractionLimit randomized SlashFractionLimit -func GenSlashFractionLimit(r *rand.Rand) sdk.Dec { - return sdk.NewDec(1).Quo(sdk.NewDec(int64(r.Intn(200) + 1))) -} - -// GenJailFractionLimit randomized JailFractionLimit -func GenJailFractionLimit(r *rand.Rand) sdk.Dec { - return sdk.NewDec(1).Quo(sdk.NewDec(int64(r.Intn(200) + 1))) -} - -// GenMaxEvidenceAge randomized MaxEvidenceAge -func GenMaxEvidenceAge(r *rand.Rand) time.Duration { - return time.Duration(simulation.RandIntBetween(r, 60, 60*60*24)) * time.Second -} - -// GenMaxEvidenceAge randomized MaxEvidenceAge -func GenEnableslashing(r *rand.Rand) bool { - return (r.Intn(200)%10 == 0) -} - -// RandomizedGenState generates a random GenesisState for slashing -func RandomizedGenState(simState *module.SimulationState) { - var signedBlocksWindow int64 - simState.AppParams.GetOrGenerate( - simState.Cdc, SignedBlocksWindow, &signedBlocksWindow, simState.Rand, - func(r *rand.Rand) { signedBlocksWindow = GenSignedBlocksWindow(r) }, - ) - - var minSignedPerWindow sdk.Dec - simState.AppParams.GetOrGenerate( - simState.Cdc, MinSignedPerWindow, &minSignedPerWindow, simState.Rand, - func(r *rand.Rand) { minSignedPerWindow = GenMinSignedPerWindow(r) }, - ) - - var downtimeJailDuration time.Duration - simState.AppParams.GetOrGenerate( - simState.Cdc, DowntimeJailDuration, &downtimeJailDuration, simState.Rand, - func(r *rand.Rand) { downtimeJailDuration = GenDowntimeJailDuration(r) }, - ) - - var slashFractionDoubleSign sdk.Dec - simState.AppParams.GetOrGenerate( - simState.Cdc, SlashFractionDoubleSign, &slashFractionDoubleSign, simState.Rand, - func(r *rand.Rand) { slashFractionDoubleSign = GenSlashFractionDoubleSign(r) }, - ) - - var slashFractionDowntime sdk.Dec - simState.AppParams.GetOrGenerate( - simState.Cdc, SlashFractionDowntime, &slashFractionDowntime, simState.Rand, - func(r *rand.Rand) { slashFractionDowntime = GenSlashFractionDowntime(r) }, - ) - - var slashFractionLimit sdk.Dec - simState.AppParams.GetOrGenerate( - simState.Cdc, SlashFractionLimit, &slashFractionLimit, simState.Rand, - func(r *rand.Rand) { slashFractionLimit = GenSlashFractionLimit(r) }, - ) - - var jailFractionLimit sdk.Dec - simState.AppParams.GetOrGenerate( - simState.Cdc, JailFractionLimit, &jailFractionLimit, simState.Rand, - func(r *rand.Rand) { jailFractionLimit = GenJailFractionLimit(r) }, - ) - - var maxEvidenceAge time.Duration - simState.AppParams.GetOrGenerate( - simState.Cdc, MaxEvidenceAge, &maxEvidenceAge, simState.Rand, - func(r *rand.Rand) { maxEvidenceAge = GenMaxEvidenceAge(r) }, - ) - - var enableSlashing bool - simState.AppParams.GetOrGenerate( - simState.Cdc, MaxEvidenceAge, &maxEvidenceAge, simState.Rand, - func(r *rand.Rand) { enableSlashing = GenEnableslashing(r) }, - ) - - params := types.NewParams( - signedBlocksWindow, minSignedPerWindow, downtimeJailDuration, - slashFractionDoubleSign, slashFractionDowntime, slashFractionLimit, jailFractionLimit, maxEvidenceAge, enableSlashing, - ) - - slashingGenesis := types.NewGenesisState(params, nil, nil, nil, nil, uint64(0)) - - fmt.Printf("Selected randomly generated slashing parameters:\n%s\n", codec.MustMarshalJSONIndent(simState.Cdc, slashingGenesis.Params)) - simState.GenState[types.ModuleName] = simState.Cdc.MustMarshalJSON(slashingGenesis) -} diff --git a/slashing/simulation/msgs.go b/slashing/simulation/msgs.go new file mode 100644 index 000000000..01acf4a81 --- /dev/null +++ b/slashing/simulation/msgs.go @@ -0,0 +1,32 @@ +package simulation + +import ( + "fmt" + "math/rand" + + "github.com/cosmos/cosmos-sdk/baseapp" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/simulation" + "github.com/cosmos/cosmos-sdk/x/slashing" +) + +// SimulateMsgUnjail generates a MsgUnjail with random values +func SimulateMsgUnjail(k slashing.Keeper) simulation.Operation { + return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, + accs []simulation.Account) (opMsg simulation.OperationMsg, fOps []simulation.FutureOperation, err error) { + + acc := simulation.RandomAcc(r, accs) + address := sdk.ValAddress(acc.Address) + msg := slashing.NewMsgUnjail(address) + if msg.ValidateBasic() != nil { + return simulation.NoOpMsg(slashing.ModuleName), nil, fmt.Errorf("expected msg to pass ValidateBasic: %s", msg.GetSignBytes()) + } + ctx, write := ctx.CacheContext() + ok := slashing.NewHandler(k)(ctx, msg).IsOK() + if ok { + write() + } + opMsg = simulation.NewOperationMsg(msg, ok, "") + return opMsg, nil, nil + } +} diff --git a/slashing/simulation/params.go b/slashing/simulation/params.go deleted file mode 100644 index 407cddc10..000000000 --- a/slashing/simulation/params.go +++ /dev/null @@ -1,40 +0,0 @@ -package simulation - -// DONTCOVER - -import ( - "fmt" - "math/rand" - - "github.com/maticnetwork/heimdall/simulation" - "github.com/maticnetwork/heimdall/slashing/types" - simtypes "github.com/maticnetwork/heimdall/types/simulation" -) - -const ( - keySignedBlocksWindow = "SignedBlocksWindow" - keyMinSignedPerWindow = "MinSignedPerWindow" - keySlashFractionDowntime = "SlashFractionDowntime" -) - -// ParamChanges defines the parameters that can be modified by param change proposals -// on the simulation -func ParamChanges(r *rand.Rand) []simtypes.ParamChange { - return []simtypes.ParamChange{ - simulation.NewSimParamChange(types.ModuleName, keySignedBlocksWindow, - func(r *rand.Rand) string { - return fmt.Sprintf("\"%d\"", GenSignedBlocksWindow(r)) - }, - ), - simulation.NewSimParamChange(types.ModuleName, keyMinSignedPerWindow, - func(r *rand.Rand) string { - return fmt.Sprintf("\"%s\"", GenMinSignedPerWindow(r)) - }, - ), - simulation.NewSimParamChange(types.ModuleName, keySlashFractionDowntime, - func(r *rand.Rand) string { - return fmt.Sprintf("\"%s\"", GenSlashFractionDowntime(r)) - }, - ), - } -} diff --git a/slashing/slashinfo_test.go b/slashing/slashinfo_test.go deleted file mode 100644 index 6babaeefa..000000000 --- a/slashing/slashinfo_test.go +++ /dev/null @@ -1,64 +0,0 @@ -package slashing_test - -import ( - "encoding/hex" - "testing" - - slashingTypes "github.com/maticnetwork/heimdall/slashing/types" - "github.com/stretchr/testify/require" - - hmTypes "github.com/maticnetwork/heimdall/types" -) - -func TestSlashingInfoRLP(t *testing.T) { - var slashingInfoList []*hmTypes.ValidatorSlashingInfo - - // Input data - slashingInfo1 := hmTypes.NewValidatorSlashingInfo(1, uint64(1000), false) // on contract, "false" value decoded as "0" - slashingInfo2 := hmTypes.NewValidatorSlashingInfo(2, uint64(234), true) // on contract, "true" value decoded as "1" - slashingInfoList = append(slashingInfoList, &slashingInfo1) - slashingInfoList = append(slashingInfoList, &slashingInfo2) - - // Encoding - encodedSlashInfos, err := slashingTypes.SortAndRLPEncodeSlashInfos(slashingInfoList) - t.Log("RLP encoded", "encodedSlashInfos", hex.EncodeToString(encodedSlashInfos), "error", err) - require.Empty(t, err) - - // Decoding - decodedSlashInfoList, err := slashingTypes.RLPDecodeSlashInfos(encodedSlashInfos) - require.Empty(t, err) - t.Log("RLP Decoded data", "valID", decodedSlashInfoList[0].ID, "amount", decodedSlashInfoList[0].SlashedAmount, "isJailed", decodedSlashInfoList[0].IsJailed) - t.Log("RLP Decoded data", "valID", decodedSlashInfoList[1].ID, "amount", decodedSlashInfoList[1].SlashedAmount, "isJailed", decodedSlashInfoList[1].IsJailed) - - // Assertions - for i := 0; i < len(slashingInfoList); i++ { - require.Equal(t, slashingInfoList[i].ID, decodedSlashInfoList[i].ID, "ID mismatch between slashInfoList and decodedSlashInfoList") - require.Equal(t, slashingInfoList[i].SlashedAmount, decodedSlashInfoList[i].SlashedAmount, "Amount mismatch between slashInfoList and decodedSlashInfoList") - require.Equal(t, slashingInfoList[i].IsJailed, decodedSlashInfoList[i].IsJailed, "JailStatus mismatch between slashInfoList and decodedSlashInfoList") - } -} - -func TestSlashingInfoRLPEncoding(t *testing.T) { - var slashingInfoList []*hmTypes.ValidatorSlashingInfo - - // Input data - slashingInfo2 := hmTypes.NewValidatorSlashingInfo(2, uint64(120), false) - slashingInfoList = append(slashingInfoList, &slashingInfo2) - - // Encoding - encodedSlashInfos, err := slashingTypes.SortAndRLPEncodeSlashInfos(slashingInfoList) - t.Log("RLP encoded", "encodedSlashInfos", hex.EncodeToString(encodedSlashInfos), "error", err) - require.Empty(t, err) -} - -func TestSlashingInfoRLPDecoding(t *testing.T) { - // input data - slashInfoEncodedBytesStr := "cdcc0289068155a43676e0000000" - slashInfoEncodedBytes, err := hex.DecodeString(slashInfoEncodedBytesStr) - require.Empty(t, err) - - // decoding input - slashInfos, err := slashingTypes.RLPDecodeSlashInfos(slashInfoEncodedBytes) - require.Empty(t, err) - t.Log("RLP decoded data", "slashInfos - ", slashInfos) -} diff --git a/slashing/test_common.go b/slashing/test_common.go new file mode 100644 index 000000000..224052857 --- /dev/null +++ b/slashing/test_common.go @@ -0,0 +1,155 @@ +// nolint:deadcode unused +package slashing + +import ( + "encoding/hex" + "testing" + "time" + + "github.com/stretchr/testify/require" + + abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/crypto" + "github.com/tendermint/tendermint/crypto/ed25519" + "github.com/tendermint/tendermint/libs/log" + dbm "github.com/tendermint/tm-db" + + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/store" + 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/params" + "github.com/cosmos/cosmos-sdk/x/staking" + "github.com/cosmos/cosmos-sdk/x/supply" +) + +// TODO remove dependencies on staking (should only refer to validator set type from sdk) + +var ( + pks = []crypto.PubKey{ + newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB50"), + newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB51"), + newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB52"), + } + addrs = []sdk.ValAddress{ + sdk.ValAddress(pks[0].Address()), + sdk.ValAddress(pks[1].Address()), + sdk.ValAddress(pks[2].Address()), + } + initTokens = sdk.TokensFromConsensusPower(200) + initCoins = sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, initTokens)) +) + +func createTestCodec() *codec.Codec { + cdc := codec.New() + sdk.RegisterCodec(cdc) + auth.RegisterCodec(cdc) + supply.RegisterCodec(cdc) + bank.RegisterCodec(cdc) + staking.RegisterCodec(cdc) + codec.RegisterCrypto(cdc) + return cdc +} + +func createTestInput(t *testing.T, defaults Params) (sdk.Context, bank.Keeper, staking.Keeper, params.Subspace, Keeper) { + keyAcc := sdk.NewKVStoreKey(auth.StoreKey) + keyStaking := sdk.NewKVStoreKey(staking.StoreKey) + tkeyStaking := sdk.NewTransientStoreKey(staking.TStoreKey) + keySlashing := sdk.NewKVStoreKey(StoreKey) + keySupply := sdk.NewKVStoreKey(supply.StoreKey) + keyParams := sdk.NewKVStoreKey(params.StoreKey) + tkeyParams := sdk.NewTransientStoreKey(params.TStoreKey) + + db := dbm.NewMemDB() + + ms := store.NewCommitMultiStore(db) + ms.MountStoreWithDB(keyAcc, sdk.StoreTypeIAVL, db) + ms.MountStoreWithDB(tkeyStaking, sdk.StoreTypeTransient, nil) + ms.MountStoreWithDB(keyStaking, sdk.StoreTypeIAVL, db) + ms.MountStoreWithDB(keySupply, sdk.StoreTypeIAVL, db) + ms.MountStoreWithDB(keySlashing, sdk.StoreTypeIAVL, db) + ms.MountStoreWithDB(keyParams, sdk.StoreTypeIAVL, db) + ms.MountStoreWithDB(tkeyParams, sdk.StoreTypeTransient, db) + + err := ms.LoadLatestVersion() + require.Nil(t, err) + + ctx := sdk.NewContext(ms, abci.Header{Time: time.Unix(0, 0)}, false, log.NewNopLogger()) + cdc := createTestCodec() + + feeCollectorAcc := supply.NewEmptyModuleAccount(auth.FeeCollectorName) + notBondedPool := supply.NewEmptyModuleAccount(staking.NotBondedPoolName, supply.Burner, supply.Staking) + bondPool := supply.NewEmptyModuleAccount(staking.BondedPoolName, supply.Burner, supply.Staking) + + blacklistedAddrs := make(map[string]bool) + blacklistedAddrs[feeCollectorAcc.String()] = true + blacklistedAddrs[notBondedPool.String()] = true + blacklistedAddrs[bondPool.String()] = true + + paramsKeeper := params.NewKeeper(cdc, keyParams, tkeyParams, params.DefaultCodespace) + accountKeeper := auth.NewAccountKeeper(cdc, keyAcc, paramsKeeper.Subspace(auth.DefaultParamspace), auth.ProtoBaseAccount) + + bk := bank.NewBaseKeeper(accountKeeper, paramsKeeper.Subspace(bank.DefaultParamspace), bank.DefaultCodespace, blacklistedAddrs) + maccPerms := map[string][]string{ + auth.FeeCollectorName: nil, + staking.NotBondedPoolName: []string{supply.Burner, supply.Staking}, + staking.BondedPoolName: []string{supply.Burner, supply.Staking}, + } + supplyKeeper := supply.NewKeeper(cdc, keySupply, accountKeeper, bk, maccPerms) + + totalSupply := sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, initTokens.MulRaw(int64(len(addrs))))) + supplyKeeper.SetSupply(ctx, supply.NewSupply(totalSupply)) + + sk := staking.NewKeeper(cdc, keyStaking, tkeyStaking, supplyKeeper, paramsKeeper.Subspace(staking.DefaultParamspace), staking.DefaultCodespace) + genesis := staking.DefaultGenesisState() + + // set module accounts + supplyKeeper.SetModuleAccount(ctx, feeCollectorAcc) + supplyKeeper.SetModuleAccount(ctx, bondPool) + supplyKeeper.SetModuleAccount(ctx, notBondedPool) + + _ = staking.InitGenesis(ctx, sk, accountKeeper, supplyKeeper, genesis) + + for _, addr := range addrs { + _, err = bk.AddCoins(ctx, sdk.AccAddress(addr), initCoins) + } + require.Nil(t, err) + paramstore := paramsKeeper.Subspace(DefaultParamspace) + keeper := NewKeeper(cdc, keySlashing, &sk, paramstore, DefaultCodespace) + sk.SetHooks(keeper.Hooks()) + + require.NotPanics(t, func() { + InitGenesis(ctx, keeper, sk, GenesisState{defaults, nil, nil}) + }) + + return ctx, bk, sk, paramstore, keeper +} + +func newPubKey(pk string) (res crypto.PubKey) { + pkBytes, err := hex.DecodeString(pk) + if err != nil { + panic(err) + } + var pkEd ed25519.PubKeyEd25519 + copy(pkEd[:], pkBytes[:]) + return pkEd +} + +func testAddr(addr string) sdk.AccAddress { + res := []byte(addr) + return res +} + +func NewTestMsgCreateValidator(address sdk.ValAddress, pubKey crypto.PubKey, amt sdk.Int) staking.MsgCreateValidator { + commission := staking.NewCommissionRates(sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec()) + return staking.NewMsgCreateValidator( + address, pubKey, sdk.NewCoin(sdk.DefaultBondDenom, amt), + staking.Description{}, commission, sdk.OneInt(), + ) +} + +func newTestMsgDelegate(delAddr sdk.AccAddress, valAddr sdk.ValAddress, delAmount sdk.Int) staking.MsgDelegate { + amount := sdk.NewCoin(sdk.DefaultBondDenom, delAmount) + return staking.NewMsgDelegate(delAddr, valAddr, amount) +} diff --git a/slashing/types/codec.go b/slashing/types/codec.go index d53c20848..1a4d62c4e 100644 --- a/slashing/types/codec.go +++ b/slashing/types/codec.go @@ -4,20 +4,17 @@ import ( "github.com/cosmos/cosmos-sdk/codec" ) -// RegisterCodec registers concrete types on codec +// Register concrete types on codec codec func RegisterCodec(cdc *codec.Codec) { - cdc.RegisterConcrete(MsgUnjail{}, "slashing/MsgUnjail", nil) - cdc.RegisterConcrete(MsgTick{}, "slashing/MsgTick", nil) - cdc.RegisterConcrete(MsgTickAck{}, "slashing/MsgTickAck", nil) - + cdc.RegisterConcrete(MsgUnjail{}, "cosmos-sdk/MsgUnjail", nil) } -// ModuleCdc generic sealed codec to be used throughout module +// module codec var ModuleCdc *codec.Codec func init() { - cdc := codec.New() - codec.RegisterCrypto(cdc) - RegisterCodec(cdc) - ModuleCdc = cdc.Seal() + ModuleCdc = codec.New() + RegisterCodec(ModuleCdc) + codec.RegisterCrypto(ModuleCdc) + ModuleCdc.Seal() } diff --git a/slashing/types/errors.go b/slashing/types/errors.go new file mode 100644 index 000000000..2e0fa8349 --- /dev/null +++ b/slashing/types/errors.go @@ -0,0 +1,51 @@ +//nolint +package types + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// Local code type +type CodeType = sdk.CodeType + +const ( + // Default slashing codespace + DefaultCodespace sdk.CodespaceType = ModuleName + + CodeInvalidValidator CodeType = 101 + CodeValidatorJailed CodeType = 102 + CodeValidatorNotJailed CodeType = 103 + CodeMissingSelfDelegation CodeType = 104 + CodeSelfDelegationTooLow CodeType = 105 + CodeMissingSigningInfo CodeType = 106 +) + +func ErrNoValidatorForAddress(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeInvalidValidator, "that address is not associated with any known validator") +} + +func ErrBadValidatorAddr(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeInvalidValidator, "validator does not exist for that address") +} + +func ErrValidatorJailed(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeValidatorJailed, "validator still jailed, cannot yet be unjailed") +} + +func ErrValidatorNotJailed(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeValidatorNotJailed, "validator not jailed, cannot be unjailed") +} + +func ErrMissingSelfDelegation(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeMissingSelfDelegation, "validator has no self-delegation; cannot be unjailed") +} + +func ErrSelfDelegationTooLowToUnjail(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeValidatorNotJailed, "validator's self delegation less than MinSelfDelegation, cannot be unjailed") +} + +func ErrNoSigningInfoFound(codespace sdk.CodespaceType, consAddr sdk.ConsAddress) sdk.Error { + return sdk.NewError(codespace, CodeMissingSigningInfo, fmt.Sprintf("no signing info found for address: %s", consAddr)) +} diff --git a/slashing/types/events.go b/slashing/types/events.go index 6d62f7f6b..2ba4020bd 100644 --- a/slashing/types/events.go +++ b/slashing/types/events.go @@ -1,25 +1,16 @@ -//noalias package types // Slashing module event types -const ( - EventTypeSlash = "slash" - EventTypeSlashLimit = "slash-limit" - EventTypeTickConfirm = "tick-confirm" - EventTypeTickAck = "tick-ack" - EventTypeUnjail = "unjail" - EventTypeLiveness = "liveness" +var ( + EventTypeSlash = "slash" + EventTypeLiveness = "liveness" - AttributeKeyAddress = "address" - AttributeKeyValID = "valid" - AttributeKeyHeight = "height" - AttributeKeyPower = "power" - AttributeKeySlashedAmount = "slashed-amount" - AttributeKeySlashInfoBytes = "slash-info-bytes" - AttributeKeyProposer = "proposer" - AttributeKeyReason = "reason" - AttributeKeyJailed = "jailed" - AttributeKeyMissedBlocks = "missed_blocks" + AttributeKeyAddress = "address" + AttributeKeyHeight = "height" + AttributeKeyPower = "power" + AttributeKeyReason = "reason" + AttributeKeyJailed = "jailed" + AttributeKeyMissedBlocks = "missed_blocks" AttributeValueDoubleSign = "double_sign" AttributeValueMissingSignature = "missing_signature" diff --git a/slashing/types/evidence.go b/slashing/types/evidence.go deleted file mode 100644 index 66a0bdc59..000000000 --- a/slashing/types/evidence.go +++ /dev/null @@ -1,132 +0,0 @@ -package types - -import ( - "fmt" - "time" - - yaml "gopkg.in/yaml.v3" - - sdk "github.com/cosmos/cosmos-sdk/types" - - abci "github.com/tendermint/tendermint/abci/types" - "github.com/tendermint/tendermint/crypto/tmhash" -) - -// Evidence defines the contract which concrete evidence types of misbehavior -// must implement. -type Evidence interface { - Route() string - Type() string - String() string - Hash() []byte - ValidateBasic() error - - // The consensus address of the malicious validator at time of infraction - GetConsensusAddress() sdk.ConsAddress - - // Height at which the infraction occurred - GetHeight() int64 - - // The total power of the malicious validator at time of infraction - GetValidatorPower() int64 - - // The total validator set power at time of infraction - GetTotalPower() int64 -} - -// MsgSubmitEvidence defines the specific interface a concrete message must -// implement in order to process submitted evidence. The concrete MsgSubmitEvidence -// must be defined at the application-level. -type MsgSubmitEvidence interface { - sdk.Msg - - GetEvidence() Evidence - GetSubmitter() sdk.AccAddress -} - -// Equivocation implements the Evidence interface and defines evidence of double -// signing misbehavior. -type Equivocation struct { - Height int64 `json:"height,omitempty"` - Time time.Time `json:"time"` - Power int64 `json:"power,omitempty"` - ConsensusAddress []byte `json:"address,omitempty"` -} - -// Evidence type constants -const ( - RouteEquivocation = "equivocation" - TypeEquivocation = "equivocation" -) - -// var _ exported.Evidence = (*Equivocation)(nil) - -// Route returns the Evidence Handler route for an Equivocation type. -func (e Equivocation) Route() string { return RouteEquivocation } - -// Type returns the Evidence Handler type for an Equivocation type. -func (e Equivocation) Type() string { return TypeEquivocation } - -func (e Equivocation) String() string { - bz, _ := yaml.Marshal(e) - return string(bz) -} - -// Hash returns the hash of an Equivocation object. -func (e Equivocation) Hash() []byte { - return tmhash.Sum(ModuleCdc.MustMarshalBinaryBare(&e)) -} - -// ValidateBasic performs basic stateless validation checks on an Equivocation object. -func (e Equivocation) ValidateBasic() error { - if e.Time.IsZero() { - return fmt.Errorf("invalid equivocation time: %s", e.Time) - } - if e.Height < 1 { - return fmt.Errorf("invalid equivocation height: %d", e.Height) - } - if e.Power < 1 { - return fmt.Errorf("invalid equivocation validator power: %d", e.Power) - } - if e.ConsensusAddress == nil { - return fmt.Errorf("invalid equivocation validator consensus address: %s", e.ConsensusAddress) - } - - return nil -} - -// GetConsensusAddress returns the validator's consensus address at time of the -// Equivocation infraction. -func (e Equivocation) GetConsensusAddress() sdk.ConsAddress { - return e.ConsensusAddress -} - -// GetHeight returns the height at time of the Equivocation infraction. -func (e Equivocation) GetHeight() int64 { - return e.Height -} - -// GetTime returns the time at time of the Equivocation infraction. -func (e Equivocation) GetTime() time.Time { - return e.Time -} - -// GetValidatorPower returns the validator's power at time of the Equivocation -// infraction. -func (e Equivocation) GetValidatorPower() int64 { - return e.Power -} - -// GetTotalPower is a no-op for the Equivocation type. -func (e Equivocation) GetTotalPower() int64 { return 0 } - -// ConvertDuplicateVoteEvidence converts a Tendermint concrete Evidence type to -// SDK Evidence using Equivocation as the concrete type. -func ConvertDuplicateVoteEvidence(dupVote abci.Evidence) Evidence { - return Equivocation{ - Height: dupVote.Height, - Power: dupVote.Validator.Power, - ConsensusAddress: sdk.ConsAddress(dupVote.Validator.Address), - Time: dupVote.Time, - } -} diff --git a/slashing/types/expected_keepers.go b/slashing/types/expected_keepers.go new file mode 100644 index 000000000..4626e079b --- /dev/null +++ b/slashing/types/expected_keepers.go @@ -0,0 +1,42 @@ +package types // noalias + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/cosmos/cosmos-sdk/x/staking/exported" +) + +// AccountKeeper expected account keeper +type AccountKeeper interface { + IterateAccounts(ctx sdk.Context, process func(auth.Account) (stop bool)) +} + +// StakingKeeper expected staking keeper +type StakingKeeper interface { + // iterate through validators by operator address, execute func for each validator + IterateValidators(sdk.Context, + func(index int64, validator exported.ValidatorI) (stop bool)) + + Validator(sdk.Context, sdk.ValAddress) exported.ValidatorI // get a particular validator by operator address + ValidatorByConsAddr(sdk.Context, sdk.ConsAddress) exported.ValidatorI // get a particular validator by consensus address + + // slash the validator and delegators of the validator, specifying offence height, offence power, and slash fraction + Slash(sdk.Context, sdk.ConsAddress, int64, int64, sdk.Dec) + Jail(sdk.Context, sdk.ConsAddress) // jail a validator + Unjail(sdk.Context, sdk.ConsAddress) // unjail a validator + + // Delegation allows for getting a particular delegation for a given validator + // and delegator outside the scope of the staking module. + Delegation(sdk.Context, sdk.AccAddress, sdk.ValAddress) exported.DelegationI + + // MaxValidators returns the maximum amount of bonded validators + MaxValidators(sdk.Context) uint16 +} + +// StakingHooks event hooks for staking validator object +type StakingHooks interface { + AfterValidatorCreated(ctx sdk.Context, valAddr sdk.ValAddress) // Must be called when a validator is created + AfterValidatorRemoved(ctx sdk.Context, consAddr sdk.ConsAddress, valAddr sdk.ValAddress) // Must be called when a validator is deleted + + AfterValidatorBonded(ctx sdk.Context, consAddr sdk.ConsAddress, valAddr sdk.ValAddress) // Must be called when a validator is bonded +} diff --git a/slashing/types/genesis.go b/slashing/types/genesis.go index ed014a3e8..b72c0637b 100644 --- a/slashing/types/genesis.go +++ b/slashing/types/genesis.go @@ -1,42 +1,28 @@ package types import ( - "encoding/json" "fmt" "time" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/maticnetwork/heimdall/bor/types" - hmTypes "github.com/maticnetwork/heimdall/types" ) // GenesisState - all slashing state that must be provided at genesis type GenesisState struct { - Params Params `json:"params" yaml:"params"` - SigningInfos map[string]hmTypes.ValidatorSigningInfo `json:"signing_infos" yaml:"signing_infos"` - MissedBlocks map[string][]MissedBlock `json:"missed_blocks" yaml:"missed_blocks"` - BufferValSlashingInfo []*hmTypes.ValidatorSlashingInfo `json:"buffer_val_slash_info" yaml:"buffer_val_slash_info"` - TickValSlashingInfo []*hmTypes.ValidatorSlashingInfo `json:"tick_val_slash_info" yaml:"tick_val_slash_info"` - TickCount uint64 `json:"tick_count" yaml:"tick_count"` + Params Params `json:"params" yaml:"params"` + SigningInfos map[string]ValidatorSigningInfo `json:"signing_infos" yaml:"signing_infos"` + MissedBlocks map[string][]MissedBlock `json:"missed_blocks" yaml:"missed_blocks"` } // NewGenesisState creates a new GenesisState object func NewGenesisState( - params Params, - signingInfos map[string]hmTypes.ValidatorSigningInfo, - missedBlocks map[string][]MissedBlock, - bufferValSlashingInfo []*hmTypes.ValidatorSlashingInfo, - tickValSlashingInfo []*hmTypes.ValidatorSlashingInfo, - tickCount uint64, + params Params, signingInfos map[string]ValidatorSigningInfo, missedBlocks map[string][]MissedBlock, ) GenesisState { return GenesisState{ - Params: params, - SigningInfos: signingInfos, - MissedBlocks: missedBlocks, - BufferValSlashingInfo: bufferValSlashingInfo, - TickValSlashingInfo: tickValSlashingInfo, - TickCount: tickCount, + Params: params, + SigningInfos: signingInfos, + MissedBlocks: missedBlocks, } } @@ -46,19 +32,11 @@ type MissedBlock struct { Missed bool `json:"missed" yaml:"missed"` } -// NewMissedBlock creates a new MissedBlock instance -func NewMissedBlock(index int64, missed bool) MissedBlock { - return MissedBlock{ - Index: index, - Missed: missed, - } -} - // DefaultGenesisState - default GenesisState used by Cosmos Hub func DefaultGenesisState() GenesisState { return GenesisState{ Params: DefaultParams(), - SigningInfos: make(map[string]hmTypes.ValidatorSigningInfo), + SigningInfos: make(map[string]ValidatorSigningInfo), MissedBlocks: make(map[string][]MissedBlock), } } @@ -67,47 +45,33 @@ func DefaultGenesisState() GenesisState { func ValidateGenesis(data GenesisState) error { downtime := data.Params.SlashFractionDowntime if downtime.IsNegative() || downtime.GT(sdk.OneDec()) { - return fmt.Errorf("slashing fraction downtime should be less than or equal to one and greater than zero, is %s", downtime.String()) + return fmt.Errorf("Slashing fraction downtime should be less than or equal to one and greater than zero, is %s", downtime.String()) } dblSign := data.Params.SlashFractionDoubleSign if dblSign.IsNegative() || dblSign.GT(sdk.OneDec()) { - return fmt.Errorf("slashing fraction double sign should be less than or equal to one and greater than zero, is %s", dblSign.String()) + return fmt.Errorf("Slashing fraction double sign should be less than or equal to one and greater than zero, is %s", dblSign.String()) } minSign := data.Params.MinSignedPerWindow if minSign.IsNegative() || minSign.GT(sdk.OneDec()) { - return fmt.Errorf("min signed per window should be less than or equal to one and greater than zero, is %s", minSign.String()) + return fmt.Errorf("Min signed per window should be less than or equal to one and greater than zero, is %s", minSign.String()) + } + + maxEvidence := data.Params.MaxEvidenceAge + if maxEvidence < 1*time.Minute { + return fmt.Errorf("Max evidence age must be at least 1 minute, is %s", maxEvidence.String()) } downtimeJail := data.Params.DowntimeJailDuration if downtimeJail < 1*time.Minute { - return fmt.Errorf("downtime unblond duration must be at least 1 minute, is %s", downtimeJail.String()) + return fmt.Errorf("Downtime unblond duration must be at least 1 minute, is %s", downtimeJail.String()) } signedWindow := data.Params.SignedBlocksWindow if signedWindow < 10 { - return fmt.Errorf("signed blocks window must be at least 10, is %d", signedWindow) + return fmt.Errorf("Signed blocks window must be at least 10, is %d", signedWindow) } return nil } - -// GetGenesisStateFromAppState returns staking GenesisState given raw application genesis state -func GetGenesisStateFromAppState(appState map[string]json.RawMessage) GenesisState { - var genesisState GenesisState - if appState[ModuleName] != nil { - types.ModuleCdc.MustUnmarshalJSON(appState[ModuleName], &genesisState) - } - return genesisState -} - -// SetGenesisStateToAppState sets state into app state -func SetGenesisStateToAppState(appState map[string]json.RawMessage, valSigningInfo map[string]hmTypes.ValidatorSigningInfo) (map[string]json.RawMessage, error) { - // set state to staking state - slashingState := GetGenesisStateFromAppState(appState) - slashingState.SigningInfos = valSigningInfo - - appState[ModuleName] = types.ModuleCdc.MustMarshalJSON(slashingState) - return appState, nil -} diff --git a/slashing/types/infohash.go b/slashing/types/infohash.go deleted file mode 100644 index c9b2c586b..000000000 --- a/slashing/types/infohash.go +++ /dev/null @@ -1,109 +0,0 @@ -package types - -import ( - "bytes" - "math/big" - "sort" - - "github.com/ethereum/go-ethereum/rlp" - "github.com/maticnetwork/heimdall/helper" - hmTypes "github.com/maticnetwork/heimdall/types" -) - -type ModifiedSlashInfo struct { - ID hmTypes.ValidatorID `json:"ID"` - SlashedAmount *big.Int `json:"SlashedAmount"` - IsJailed []byte `json:"IsJailed"` -} - -// SortModifiedSlashInfoByID - Sorts ModifiedSlashInfo By ID -func sortModifiedSlashInfoByID(modifiedSlashInfos []*ModifiedSlashInfo) []*ModifiedSlashInfo { - sort.Slice(modifiedSlashInfos, func(i, j int) bool { return modifiedSlashInfos[i].ID < modifiedSlashInfos[j].ID }) - return modifiedSlashInfos -} - -// SortAndRLPEncodeSlashInfos - RLP encoded slashing infos -func SortAndRLPEncodeSlashInfos(slashingInfos []*hmTypes.ValidatorSlashingInfo) ([]byte, error) { - - // convert slashingInfos to modifiedSlashingInfos - var updatedslashInfos []*ModifiedSlashInfo - for _, slashInfo := range slashingInfos { - modifiedSlashInfo, err := slashInfoToModified(slashInfo) - if err != nil { - return nil, err - } - updatedslashInfos = append(updatedslashInfos, modifiedSlashInfo) - } - - // Sort the slashingInfos by ID - updatedslashInfos = sortModifiedSlashInfoByID(updatedslashInfos) - - // Encode encodedSlashInfos - encodedSlashInfos, err := rlp.EncodeToBytes(updatedslashInfos) - - return encodedSlashInfos, err -} - -func slashInfoToModified(slashInfo *hmTypes.ValidatorSlashingInfo) (modifiedSlashInfo *ModifiedSlashInfo, err error) { - amount, err := helper.GetAmountFromPower(int64(slashInfo.SlashedAmount)) - if err != nil { - return modifiedSlashInfo, err - } - - // converting jailed from boolean to Byte. as boolean rlp is incompatible Issue - https://github.com/hamdiallam/Solidity-RLP/issues/5 - jailedByte := []byte{0x00} - if slashInfo.IsJailed { - jailedByte = []byte{0x01} - } - - // convert slashing amount to 10^18. required for contracts. - modifiedSlashInfo = &ModifiedSlashInfo{ - ID: slashInfo.ID, - SlashedAmount: amount, - IsJailed: jailedByte, - } - - return modifiedSlashInfo, err -} - -func RLPDecodeSlashInfos(encodedSlashInfo []byte) ([]*hmTypes.ValidatorSlashingInfo, error) { - var modifiedSlashInfoList []*ModifiedSlashInfo - err := rlp.DecodeBytes(encodedSlashInfo, &modifiedSlashInfoList) - if err != nil { - return nil, err - } - // convert modifiedSlashingInfos to slashingInfos - var updatedslashInfos []*hmTypes.ValidatorSlashingInfo - for _, modifiedSlashInfo := range modifiedSlashInfoList { - slashInfo, err := modifiedToSlashInfo(modifiedSlashInfo) - if err != nil { - return nil, err - } - updatedslashInfos = append(updatedslashInfos, slashInfo) - } - - return updatedslashInfos, err -} - -func modifiedToSlashInfo(modifiedSlashInfo *ModifiedSlashInfo) (slashInfo *hmTypes.ValidatorSlashingInfo, err error) { - amount := modifiedSlashInfo.SlashedAmount - power, err := helper.GetPowerFromAmount(amount) - if err != nil { - return slashInfo, err - } - - // converting jailed from boolean to bytes. as boolean RLP is incompatible Issue - https://github.com/hamdiallam/Solidity-RLP/issues/5 - var jailedBool bool - if bytes.Equal(modifiedSlashInfo.IsJailed, []byte{0x01}) { - jailedBool = true - } - - // convert slashing amount to 10^18. required for contracts. - slashInfo = &hmTypes.ValidatorSlashingInfo{ - ID: modifiedSlashInfo.ID, - SlashedAmount: power.Uint64(), - IsJailed: jailedBool, - } - - return slashInfo, err -} diff --git a/slashing/types/keys.go b/slashing/types/keys.go index 6acf85866..294b9c9a1 100644 --- a/slashing/types/keys.go +++ b/slashing/types/keys.go @@ -2,10 +2,12 @@ package types import ( "encoding/binary" + + sdk "github.com/cosmos/cosmos-sdk/types" ) const ( - // ModuleName is the name of the module + // module name ModuleName = "slashing" // StoreKey is the store key string for slashing @@ -18,6 +20,13 @@ const ( QuerierRoute = ModuleName ) +// Query endpoints supported by the slashing querier +const ( + QueryParameters = "parameters" + QuerySigningInfo = "signingInfo" + QuerySigningInfos = "signingInfos" +) + // Keys for slashing store // Items are stored with the following key: values // @@ -27,44 +36,38 @@ const ( // // - 0x03: crypto.PubKey var ( - DefaultValue = []byte{0x01} // Value to store for slashing sequence - ValidatorSigningInfoKey = []byte{0x01} // Prefix for signing info ValidatorMissedBlockBitArrayKey = []byte{0x02} // Prefix for missed block bit array - TotalSlashedAmountKey = []byte{0x04} // Prefix for total slashed amount stored in buffer - BufferValSlashingInfoKey = []byte{0x05} // Prefix for Slashing Info stored in buffer - TickValSlashingInfoKey = []byte{0x06} // Prefix for Slashing Info stored after tick tx - SlashingSequenceKey = []byte{0x07} // prefix for each key for slashing sequence map - TickCountKey = []byte{0x08} // key to store Tick counts + AddrPubkeyRelationKey = []byte{0x03} // Prefix for address-pubkey relation ) -// GetValidatorSigningInfoKey - stored by *valID* -func GetValidatorSigningInfoKey(valID []byte) []byte { - return append(ValidatorSigningInfoKey, valID...) +// stored by *Consensus* address (not operator address) +func GetValidatorSigningInfoKey(v sdk.ConsAddress) []byte { + return append(ValidatorSigningInfoKey, v.Bytes()...) } -// GetValidatorMissedBlockBitArrayPrefixKey - stored by *Consensus* address (not operator address) -func GetValidatorMissedBlockBitArrayPrefixKey(valID []byte) []byte { - return append(ValidatorMissedBlockBitArrayKey, valID...) +// extract the address from a validator signing info key +func GetValidatorSigningInfoAddress(key []byte) (v sdk.ConsAddress) { + addr := key[1:] + if len(addr) != sdk.AddrLen { + panic("unexpected key length") + } + return sdk.ConsAddress(addr) } -// GetValidatorMissedBlockBitArrayKey - stored by *Consensus* address (not operator address) -func GetValidatorMissedBlockBitArrayKey(valID []byte, i int64) []byte { - b := make([]byte, 8) - binary.LittleEndian.PutUint64(b, uint64(i)) - return append(GetValidatorMissedBlockBitArrayPrefixKey(valID), b...) -} - -// GetBufferValSlashingInfoKey - gets buffer val slashing info key -func GetBufferValSlashingInfoKey(id []byte) []byte { - return append(BufferValSlashingInfoKey, id...) +// stored by *Consensus* address (not operator address) +func GetValidatorMissedBlockBitArrayPrefixKey(v sdk.ConsAddress) []byte { + return append(ValidatorMissedBlockBitArrayKey, v.Bytes()...) } -func GetTickValSlashingInfoKey(id []byte) []byte { - return append(TickValSlashingInfoKey, id...) +// stored by *Consensus* address (not operator address) +func GetValidatorMissedBlockBitArrayKey(v sdk.ConsAddress, i int64) []byte { + b := make([]byte, 8) + binary.LittleEndian.PutUint64(b, uint64(i)) + return append(GetValidatorMissedBlockBitArrayPrefixKey(v), b...) } -// GetSlashingSequenceKey returns slashing sequence key -func GetSlashingSequenceKey(sequence string) []byte { - return append(SlashingSequenceKey, []byte(sequence)...) +// get pubkey relation key used to get the pubkey from the address +func GetAddrPubkeyRelationKey(address []byte) []byte { + return append(AddrPubkeyRelationKey, address...) } diff --git a/slashing/types/msg.go b/slashing/types/msg.go index 153e447ea..a167ae44b 100644 --- a/slashing/types/msg.go +++ b/slashing/types/msg.go @@ -1,13 +1,7 @@ package types import ( - "math/big" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/ethereum/go-ethereum/accounts/abi" - hmCommon "github.com/maticnetwork/heimdall/common" - "github.com/maticnetwork/heimdall/types" - hmTypes "github.com/maticnetwork/heimdall/types" ) // verify interface at compile time @@ -15,20 +9,12 @@ var _ sdk.Msg = &MsgUnjail{} // MsgUnjail - struct for unjailing jailed validator type MsgUnjail struct { - From types.HeimdallAddress `json:"from"` - ID hmTypes.ValidatorID `json:"id"` - TxHash types.HeimdallHash `json:"tx_hash"` - LogIndex uint64 `json:"log_index"` - BlockNumber uint64 `json:"block_number"` + ValidatorAddr sdk.ValAddress `json:"address" yaml:"address"` // address of the validator operator } -func NewMsgUnjail(from types.HeimdallAddress, id uint64, txHash types.HeimdallHash, logIndex uint64, blockNumber uint64) MsgUnjail { +func NewMsgUnjail(validatorAddr sdk.ValAddress) MsgUnjail { return MsgUnjail{ - From: from, - ID: hmTypes.NewValidatorID(id), - TxHash: txHash, - LogIndex: logIndex, - BlockNumber: blockNumber, + ValidatorAddr: validatorAddr, } } @@ -36,169 +22,19 @@ func NewMsgUnjail(from types.HeimdallAddress, id uint64, txHash types.HeimdallHa func (msg MsgUnjail) Route() string { return RouterKey } func (msg MsgUnjail) Type() string { return "unjail" } func (msg MsgUnjail) GetSigners() []sdk.AccAddress { - return []sdk.AccAddress{hmTypes.HeimdallAddressToAccAddress(msg.From)} + return []sdk.AccAddress{sdk.AccAddress(msg.ValidatorAddr)} } -// GetSignBytes gets the bytes for the message signer to sign on +// get the bytes for the message signer to sign on func (msg MsgUnjail) GetSignBytes() []byte { - b, err := ModuleCdc.MarshalJSON(msg) - if err != nil { - panic(err) - } - return sdk.MustSortJSON(b) + bz := ModuleCdc.MustMarshalJSON(msg) + return sdk.MustSortJSON(bz) } -// ValidateBasic validity check for the AnteHandler +// quick validity check func (msg MsgUnjail) ValidateBasic() sdk.Error { - if msg.ID <= 0 { - return hmCommon.ErrInvalidMsg(hmCommon.DefaultCodespace, "Invalid validator ID %v", msg.ID) - } - - if msg.From.Empty() { - return hmCommon.ErrInvalidMsg(hmCommon.DefaultCodespace, "Invalid proposer %v", msg.From.String()) - } - return nil -} - -// Tick Msg - -// TickMsg - struct for unjailing jailed validator -type MsgTick struct { - ID uint64 `json:"id"` - Proposer types.HeimdallAddress `json:"proposer"` - SlashingInfoBytes types.HexBytes `json:"slashinginfobytes"` -} - -func NewMsgTick(id uint64, proposer types.HeimdallAddress, slashingInfoBytes types.HexBytes) MsgTick { - return MsgTick{ - ID: id, - Proposer: proposer, - SlashingInfoBytes: slashingInfoBytes, - } -} - -// Type returns message type -func (msg MsgTick) Type() string { - return "tick" -} - -func (msg MsgTick) Route() string { - return RouterKey -} - -// GetSigners returns address of the signer -func (msg MsgTick) GetSigners() []sdk.AccAddress { - return []sdk.AccAddress{types.HeimdallAddressToAccAddress(msg.Proposer)} -} - -func (msg MsgTick) GetSignBytes() []byte { - b, err := ModuleCdc.MarshalJSON(msg) - if err != nil { - panic(err) - } - return sdk.MustSortJSON(b) -} - -func (msg MsgTick) ValidateBasic() sdk.Error { - - if msg.Proposer.Empty() { - return hmCommon.ErrInvalidMsg(hmCommon.DefaultCodespace, "Invalid proposer %v", msg.Proposer.String()) - } - return nil -} - -// GetSideSignBytes returns side sign bytes -func (msg MsgTick) GetSideSignBytes() []byte { - uintType, _ := abi.NewType("uint256", "", nil) - addressType, _ := abi.NewType("address", "", nil) - bytesType, _ := abi.NewType("bytes", "", nil) - - arguments := abi.Arguments{ - { - Type: uintType, - }, - { - Type: addressType, - }, - { - Type: bytesType, - }, - } - - bytes, _ := arguments.Pack( - new(big.Int).SetUint64(msg.ID), - msg.Proposer, - msg.SlashingInfoBytes, - ) - return bytes -} - -// -// Msg Tick Ack -// - -var _ sdk.Msg = &MsgTickAck{} - -type MsgTickAck struct { - From types.HeimdallAddress `json:"from"` - ID uint64 `json:"tick_id"` - SlashedAmount uint64 `json:"slashed_amount"` - TxHash types.HeimdallHash `json:"tx_hash"` - LogIndex uint64 `json:"log_index"` - BlockNumber uint64 `json:"block_number"` -} - -func NewMsgTickAck(from types.HeimdallAddress, id uint64, slashedAmount uint64, txHash types.HeimdallHash, logIndex uint64, blockNumber uint64) MsgTickAck { - return MsgTickAck{ - From: from, - ID: id, - SlashedAmount: slashedAmount, - TxHash: txHash, - BlockNumber: blockNumber, - LogIndex: logIndex, - } -} - -// Type returns message type -func (msg MsgTickAck) Type() string { - return "tick-ack" -} - -func (msg MsgTickAck) Route() string { - return RouterKey -} - -// GetSigners returns address of the signer -func (msg MsgTickAck) GetSigners() []sdk.AccAddress { - return []sdk.AccAddress{types.HeimdallAddressToAccAddress(msg.From)} -} - -func (msg MsgTickAck) GetSignBytes() []byte { - b, err := ModuleCdc.MarshalJSON(msg) - if err != nil { - panic(err) - } - return sdk.MustSortJSON(b) -} - -func (msg MsgTickAck) ValidateBasic() sdk.Error { - if msg.From.Empty() { - return hmCommon.ErrInvalidMsg(hmCommon.DefaultCodespace, "Invalid from %v", msg.From.String()) + if msg.ValidatorAddr.Empty() { + return ErrBadValidatorAddr(DefaultCodespace) } return nil } - -// GetTxHash Returns tx hash -func (msg MsgTickAck) GetTxHash() types.HeimdallHash { - return msg.TxHash -} - -// GetLogIndex Returns log index -func (msg MsgTickAck) GetLogIndex() uint64 { - return msg.LogIndex -} - -// GetSideSignBytes returns side sign bytes -func (msg MsgTickAck) GetSideSignBytes() []byte { - return nil -} diff --git a/slashing/types/msg_test.go b/slashing/types/msg_test.go new file mode 100644 index 000000000..31f7a70e7 --- /dev/null +++ b/slashing/types/msg_test.go @@ -0,0 +1,20 @@ +package types + +import ( + "testing" + + "github.com/stretchr/testify/require" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +func TestMsgUnjailGetSignBytes(t *testing.T) { + addr := sdk.AccAddress("abcd") + msg := NewMsgUnjail(sdk.ValAddress(addr)) + bytes := msg.GetSignBytes() + require.Equal( + t, + `{"type":"cosmos-sdk/MsgUnjail","value":{"address":"cosmosvaloper1v93xxeqhg9nn6"}}`, + string(bytes), + ) +} diff --git a/slashing/types/params.go b/slashing/types/params.go index 7f70071d3..532324a28 100644 --- a/slashing/types/params.go +++ b/slashing/types/params.go @@ -5,114 +5,98 @@ import ( "time" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/maticnetwork/heimdall/params/subspace" + "github.com/cosmos/cosmos-sdk/x/params" ) // Default parameter namespace const ( DefaultParamspace = ModuleName + DefaultMaxEvidenceAge = 60 * 2 * time.Second DefaultSignedBlocksWindow = int64(100) DefaultDowntimeJailDuration = 60 * 10 * time.Second ) +// The Double Sign Jail period ends at Max Time supported by Amino (Dec 31, 9999 - 23:59:59 GMT) var ( + DoubleSignJailEndTime = time.Unix(253402300799, 0) DefaultMinSignedPerWindow = sdk.NewDecWithPrec(5, 1) DefaultSlashFractionDoubleSign = sdk.NewDec(1).Quo(sdk.NewDec(20)) DefaultSlashFractionDowntime = sdk.NewDec(1).Quo(sdk.NewDec(100)) - DefaultSlashFractionLimit = sdk.NewDec(1).Quo(sdk.NewDec(3)) - DefaultJailFractionLimit = sdk.NewDec(1).Quo(sdk.NewDec(3)) - DefaultMaxEvidenceAge = 60 * 2 * time.Second - DefaultEnableSlashing = false ) // Parameter store keys var ( + KeyMaxEvidenceAge = []byte("MaxEvidenceAge") KeySignedBlocksWindow = []byte("SignedBlocksWindow") KeyMinSignedPerWindow = []byte("MinSignedPerWindow") KeyDowntimeJailDuration = []byte("DowntimeJailDuration") KeySlashFractionDoubleSign = []byte("SlashFractionDoubleSign") KeySlashFractionDowntime = []byte("SlashFractionDowntime") - KeySlashFractionLimit = []byte("SlashFractionLimit") - KeyJailFractionLimit = []byte("JailFractionLimit") - KeyMaxEvidenceAge = []byte("MaxEvidenceAge") - KeyEnableSlashing = []byte("EnableSlashing") ) -var _ subspace.ParamSet = &Params{} +// ParamKeyTable for slashing module +func ParamKeyTable() params.KeyTable { + return params.NewKeyTable().RegisterParamSet(&Params{}) +} // Params - used for initializing default parameter for slashing at genesis type Params struct { + MaxEvidenceAge time.Duration `json:"max_evidence_age" yaml:"max_evidence_age"` SignedBlocksWindow int64 `json:"signed_blocks_window" yaml:"signed_blocks_window"` MinSignedPerWindow sdk.Dec `json:"min_signed_per_window" yaml:"min_signed_per_window"` DowntimeJailDuration time.Duration `json:"downtime_jail_duration" yaml:"downtime_jail_duration"` - SlashFractionDoubleSign sdk.Dec `json:"slash_fraction_double_sign" yaml:"slash_fraction_double_sign"` // fraction amount to slash on double sign - SlashFractionDowntime sdk.Dec `json:"slash_fraction_downtime" yaml:"slash_fraction_downtime"` // fraction amount to slash on downtime - SlashFractionLimit sdk.Dec `json:"slash_fraction_limit" yaml:"slash_fraction_limit"` // if totalSlashedAmount crossed SlashFraction of totalValidatorPower, emit Slash-limit event - JailFractionLimit sdk.Dec `json:"jail_fraction_limit" yaml:"jail_fraction_limit"` // if slashedAmount crossed JailFraction of validatorPower, Jail him - MaxEvidenceAge time.Duration `json:"max_evidence_age" yaml:"max_evidence_age"` - EnableSlashing bool `json:"enable_slashing" yaml:"enable_slashing"` + SlashFractionDoubleSign sdk.Dec `json:"slash_fraction_double_sign" yaml:"slash_fraction_double_sign"` + SlashFractionDowntime sdk.Dec `json:"slash_fraction_downtime" yaml:"slash_fraction_downtime"` } // NewParams creates a new Params object -func NewParams( - signedBlocksWindow int64, minSignedPerWindow sdk.Dec, downtimeJailDuration time.Duration, - slashFractionDoubleSign, slashFractionDowntime sdk.Dec, slashFractionLimit sdk.Dec, jailFractionLimit sdk.Dec, maxEvidenceAge time.Duration, enableSlashing bool, -) Params { +func NewParams(maxEvidenceAge time.Duration, signedBlocksWindow int64, + minSignedPerWindow sdk.Dec, downtimeJailDuration time.Duration, + slashFractionDoubleSign sdk.Dec, slashFractionDowntime sdk.Dec) Params { return Params{ + MaxEvidenceAge: maxEvidenceAge, SignedBlocksWindow: signedBlocksWindow, MinSignedPerWindow: minSignedPerWindow, DowntimeJailDuration: downtimeJailDuration, SlashFractionDoubleSign: slashFractionDoubleSign, SlashFractionDowntime: slashFractionDowntime, - MaxEvidenceAge: maxEvidenceAge, - SlashFractionLimit: slashFractionLimit, - JailFractionLimit: jailFractionLimit, - EnableSlashing: enableSlashing, } } -// ParamKeyTable for slashing module -func ParamKeyTable() subspace.KeyTable { - return subspace.NewKeyTable().RegisterParamSet(&Params{}) -} - -// String implements the stringer interface for Params func (p Params) String() string { return fmt.Sprintf(`Slashing Params: + MaxEvidenceAge: %s SignedBlocksWindow: %d MinSignedPerWindow: %s DowntimeJailDuration: %s SlashFractionDoubleSign: %s - MaxEvidenceAge: %s - SlashFractionDowntime: %s - SlashFractionLimit: %s - JailFractionDowntime: %s - EnableSlashing: %t`, + SlashFractionDowntime: %s`, p.MaxEvidenceAge, p.SignedBlocksWindow, p.MinSignedPerWindow, - p.DowntimeJailDuration, p.SlashFractionDoubleSign, p.MaxEvidenceAge, - p.SlashFractionDowntime, p.SlashFractionLimit, p.JailFractionLimit, p.EnableSlashing) + p.DowntimeJailDuration, p.SlashFractionDoubleSign, + p.SlashFractionDowntime) } -// ParamSetPairs - Implements params.ParamSet -func (p *Params) ParamSetPairs() subspace.ParamSetPairs { - return subspace.ParamSetPairs{ - {Key: KeySignedBlocksWindow, Value: &p.SignedBlocksWindow}, - {Key: KeyMinSignedPerWindow, Value: &p.MinSignedPerWindow}, - {Key: KeyDowntimeJailDuration, Value: &p.DowntimeJailDuration}, - {Key: KeySlashFractionDoubleSign, Value: &p.SlashFractionDoubleSign}, - {Key: KeySlashFractionDowntime, Value: &p.SlashFractionDowntime}, - {Key: KeySlashFractionLimit, Value: &p.SlashFractionLimit}, - {Key: KeyJailFractionLimit, Value: &p.JailFractionLimit}, - {Key: KeyMaxEvidenceAge, Value: &p.MaxEvidenceAge}, - {Key: KeyEnableSlashing, Value: &p.EnableSlashing}, +// Implements params.ParamSet +func (p *Params) ParamSetPairs() params.ParamSetPairs { + return params.ParamSetPairs{ + {KeyMaxEvidenceAge, &p.MaxEvidenceAge}, + {KeySignedBlocksWindow, &p.SignedBlocksWindow}, + {KeyMinSignedPerWindow, &p.MinSignedPerWindow}, + {KeyDowntimeJailDuration, &p.DowntimeJailDuration}, + {KeySlashFractionDoubleSign, &p.SlashFractionDoubleSign}, + {KeySlashFractionDowntime, &p.SlashFractionDowntime}, } } -// DefaultParams defines the parameters for this module +// Default parameters for this module func DefaultParams() Params { - return NewParams( - DefaultSignedBlocksWindow, DefaultMinSignedPerWindow, DefaultDowntimeJailDuration, - DefaultSlashFractionDoubleSign, DefaultSlashFractionDowntime, DefaultSlashFractionLimit, DefaultJailFractionLimit, DefaultMaxEvidenceAge, DefaultEnableSlashing, - ) + return Params{ + MaxEvidenceAge: DefaultMaxEvidenceAge, + SignedBlocksWindow: DefaultSignedBlocksWindow, + MinSignedPerWindow: DefaultMinSignedPerWindow, + DowntimeJailDuration: DefaultDowntimeJailDuration, + SlashFractionDoubleSign: DefaultSlashFractionDoubleSign, + SlashFractionDowntime: DefaultSlashFractionDowntime, + } } diff --git a/slashing/types/querier.go b/slashing/types/querier.go index 9ccae94e7..d8850b0b2 100644 --- a/slashing/types/querier.go +++ b/slashing/types/querier.go @@ -1,33 +1,17 @@ package types import ( - hmTypes "github.com/maticnetwork/heimdall/types" -) - -// DONTCOVER - -// Query endpoints supported by the slashing querier -const ( - QueryParameters = "parameters" - QuerySigningInfo = "signingInfo" - QuerySigningInfos = "signingInfos" - QuerySlashingInfo = "slashingInfo" - QuerySlashingInfos = "slashingInfos" - QuerySlashingInfoBytes = "slashingInfoBytes" - QueryTickSlashingInfos = "tickSlashingInfos" - QuerySlashingSequence = "slashing-sequence" - QueryTickCount = "tick-count" + sdk "github.com/cosmos/cosmos-sdk/types" ) // QuerySigningInfoParams defines the params for the following queries: // - 'custom/slashing/signingInfo' type QuerySigningInfoParams struct { - ValidatorID hmTypes.ValidatorID + ConsAddress sdk.ConsAddress } -// NewQuerySigningInfoParams creates a new QuerySigningInfoParams instance -func NewQuerySigningInfoParams(valID hmTypes.ValidatorID) QuerySigningInfoParams { - return QuerySigningInfoParams{valID} +func NewQuerySigningInfoParams(consAddr sdk.ConsAddress) QuerySigningInfoParams { + return QuerySigningInfoParams{consAddr} } // QuerySigningInfosParams defines the params for the following queries: @@ -36,51 +20,6 @@ type QuerySigningInfosParams struct { Page, Limit int } -// NewQuerySigningInfosParams creates a new QuerySigningInfosParams instance func NewQuerySigningInfosParams(page, limit int) QuerySigningInfosParams { return QuerySigningInfosParams{page, limit} } - -// QuerySlashingInfoParams defines the params for the following queries: -// - 'custom/slashing/slashingInfo' -type QuerySlashingInfoParams struct { - ValidatorID hmTypes.ValidatorID -} - -// NewQuerySlashingInfoParams creates a new QuerySlashingInfoParams instance -func NewQuerySlashingInfoParams(valID hmTypes.ValidatorID) QuerySlashingInfoParams { - return QuerySlashingInfoParams{valID} -} - -// QuerySlashingInfosParams defines the params for the following queries: -// - 'custom/slashing/slashingInfos' -type QuerySlashingInfosParams struct { - Page, Limit int -} - -// NewQuerySlashingInfosParams creates a new QuerySlashingInfosParams instance -func NewQuerySlashingInfosParams(page, limit int) QuerySlashingInfosParams { - return QuerySlashingInfosParams{page, limit} -} - -// QueryTickSlashingInfosParams defines the params for the following queries: -// - 'custom/slashing/tick_slash_infos' -type QueryTickSlashingInfosParams struct { - Page, Limit int -} - -// NewQueryTickSlashingInfosParams creates a new QueryTickSlashingInfosParams instance -func NewQueryTickSlashingInfosParams(page, limit int) QueryTickSlashingInfosParams { - return QueryTickSlashingInfosParams{page, limit} -} - -// QuerySlashingSequenceParams defines the params for querying an account Sequence. -type QuerySlashingSequenceParams struct { - TxHash string - LogIndex uint64 -} - -// NewQuerySlashingSequenceParams creates a new instance of QuerySlashingSequenceParams. -func NewQuerySlashingSequenceParams(txHash string, logIndex uint64) QuerySlashingSequenceParams { - return QuerySlashingSequenceParams{TxHash: txHash, LogIndex: logIndex} -} diff --git a/slashing/types/signing_info.go b/slashing/types/signing_info.go new file mode 100644 index 000000000..804db7b97 --- /dev/null +++ b/slashing/types/signing_info.go @@ -0,0 +1,47 @@ +package types + +import ( + "fmt" + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// Signing info for a validator +type ValidatorSigningInfo struct { + Address sdk.ConsAddress `json:"address" yaml:"address"` // validator consensus address + StartHeight int64 `json:"start_height" yaml:"start_height"` // height at which validator was first a candidate OR was unjailed + IndexOffset int64 `json:"index_offset" yaml:"index_offset"` // index offset into signed block bit array + JailedUntil time.Time `json:"jailed_until" yaml:"jailed_until"` // timestamp validator cannot be unjailed until + Tombstoned bool `json:"tombstoned" yaml:"tombstoned"` // whether or not a validator has been tombstoned (killed out of validator set) + MissedBlocksCounter int64 `json:"missed_blocks_counter" yaml:"missed_blocks_counter"` // missed blocks counter (to avoid scanning the array every time) +} + +// Construct a new `ValidatorSigningInfo` struct +func NewValidatorSigningInfo( + condAddr sdk.ConsAddress, startHeight, indexOffset int64, + jailedUntil time.Time, tombstoned bool, missedBlocksCounter int64, +) ValidatorSigningInfo { + + return ValidatorSigningInfo{ + Address: condAddr, + StartHeight: startHeight, + IndexOffset: indexOffset, + JailedUntil: jailedUntil, + Tombstoned: tombstoned, + MissedBlocksCounter: missedBlocksCounter, + } +} + +// Return human readable signing info +func (i ValidatorSigningInfo) String() string { + return fmt.Sprintf(`Validator Signing Info: + Address: %s + Start Height: %d + Index Offset: %d + Jailed Until: %v + Tombstoned: %t + Missed Blocks Counter: %d`, + i.Address, i.StartHeight, i.IndexOffset, i.JailedUntil, + i.Tombstoned, i.MissedBlocksCounter) +} diff --git a/staking/alias.go b/staking/alias.go new file mode 100644 index 000000000..019fa0298 --- /dev/null +++ b/staking/alias.go @@ -0,0 +1,245 @@ +// nolint +// autogenerated code using github.com/rigelrozanski/multitool +// aliases generated for the following subdirectories: +// ALIASGEN: github.com/cosmos/cosmos-sdk/x/staking/keeper +// ALIASGEN: github.com/cosmos/cosmos-sdk/x/staking/types +// ALIASGEN: github.com/cosmos/cosmos-sdk/x/staking/exported +package staking + +import ( + "github.com/cosmos/cosmos-sdk/x/staking/exported" + "github.com/cosmos/cosmos-sdk/x/staking/keeper" + "github.com/cosmos/cosmos-sdk/x/staking/types" +) + +const ( + DefaultParamspace = keeper.DefaultParamspace + DefaultCodespace = types.DefaultCodespace + CodeInvalidValidator = types.CodeInvalidValidator + CodeInvalidDelegation = types.CodeInvalidDelegation + CodeInvalidInput = types.CodeInvalidInput + CodeValidatorJailed = types.CodeValidatorJailed + CodeInvalidAddress = types.CodeInvalidAddress + CodeUnauthorized = types.CodeUnauthorized + CodeInternal = types.CodeInternal + CodeUnknownRequest = types.CodeUnknownRequest + ModuleName = types.ModuleName + StoreKey = types.StoreKey + TStoreKey = types.TStoreKey + QuerierRoute = types.QuerierRoute + RouterKey = types.RouterKey + DefaultUnbondingTime = types.DefaultUnbondingTime + DefaultMaxValidators = types.DefaultMaxValidators + DefaultMaxEntries = types.DefaultMaxEntries + NotBondedPoolName = types.NotBondedPoolName + BondedPoolName = types.BondedPoolName + QueryValidators = types.QueryValidators + QueryValidator = types.QueryValidator + QueryDelegatorDelegations = types.QueryDelegatorDelegations + QueryDelegatorUnbondingDelegations = types.QueryDelegatorUnbondingDelegations + QueryRedelegations = types.QueryRedelegations + QueryValidatorDelegations = types.QueryValidatorDelegations + QueryValidatorRedelegations = types.QueryValidatorRedelegations + QueryValidatorUnbondingDelegations = types.QueryValidatorUnbondingDelegations + QueryDelegation = types.QueryDelegation + QueryUnbondingDelegation = types.QueryUnbondingDelegation + QueryDelegatorValidators = types.QueryDelegatorValidators + QueryDelegatorValidator = types.QueryDelegatorValidator + QueryPool = types.QueryPool + QueryParameters = types.QueryParameters + MaxMonikerLength = types.MaxMonikerLength + MaxIdentityLength = types.MaxIdentityLength + MaxWebsiteLength = types.MaxWebsiteLength + MaxDetailsLength = types.MaxDetailsLength + DoNotModifyDesc = types.DoNotModifyDesc +) + +var ( + // functions aliases + RegisterInvariants = keeper.RegisterInvariants + AllInvariants = keeper.AllInvariants + ModuleAccountInvariants = keeper.ModuleAccountInvariants + NonNegativePowerInvariant = keeper.NonNegativePowerInvariant + PositiveDelegationInvariant = keeper.PositiveDelegationInvariant + DelegatorSharesInvariant = keeper.DelegatorSharesInvariant + NewKeeper = keeper.NewKeeper + ParamKeyTable = keeper.ParamKeyTable + NewQuerier = keeper.NewQuerier + RegisterCodec = types.RegisterCodec + NewCommissionRates = types.NewCommissionRates + NewCommission = types.NewCommission + NewCommissionWithTime = types.NewCommissionWithTime + NewDelegation = types.NewDelegation + MustMarshalDelegation = types.MustMarshalDelegation + MustUnmarshalDelegation = types.MustUnmarshalDelegation + UnmarshalDelegation = types.UnmarshalDelegation + NewUnbondingDelegation = types.NewUnbondingDelegation + NewUnbondingDelegationEntry = types.NewUnbondingDelegationEntry + MustMarshalUBD = types.MustMarshalUBD + MustUnmarshalUBD = types.MustUnmarshalUBD + UnmarshalUBD = types.UnmarshalUBD + NewRedelegation = types.NewRedelegation + NewRedelegationEntry = types.NewRedelegationEntry + MustMarshalRED = types.MustMarshalRED + MustUnmarshalRED = types.MustUnmarshalRED + UnmarshalRED = types.UnmarshalRED + NewDelegationResp = types.NewDelegationResp + NewRedelegationResponse = types.NewRedelegationResponse + NewRedelegationEntryResponse = types.NewRedelegationEntryResponse + ErrNilValidatorAddr = types.ErrNilValidatorAddr + ErrBadValidatorAddr = types.ErrBadValidatorAddr + ErrNoValidatorFound = types.ErrNoValidatorFound + ErrValidatorOwnerExists = types.ErrValidatorOwnerExists + ErrValidatorPubKeyExists = types.ErrValidatorPubKeyExists + ErrValidatorPubKeyTypeNotSupported = types.ErrValidatorPubKeyTypeNotSupported + ErrValidatorJailed = types.ErrValidatorJailed + ErrBadRemoveValidator = types.ErrBadRemoveValidator + ErrDescriptionLength = types.ErrDescriptionLength + ErrCommissionNegative = types.ErrCommissionNegative + ErrCommissionHuge = types.ErrCommissionHuge + ErrCommissionGTMaxRate = types.ErrCommissionGTMaxRate + ErrCommissionUpdateTime = types.ErrCommissionUpdateTime + ErrCommissionChangeRateNegative = types.ErrCommissionChangeRateNegative + ErrCommissionChangeRateGTMaxRate = types.ErrCommissionChangeRateGTMaxRate + ErrCommissionGTMaxChangeRate = types.ErrCommissionGTMaxChangeRate + ErrSelfDelegationBelowMinimum = types.ErrSelfDelegationBelowMinimum + ErrMinSelfDelegationInvalid = types.ErrMinSelfDelegationInvalid + ErrMinSelfDelegationDecreased = types.ErrMinSelfDelegationDecreased + ErrNilDelegatorAddr = types.ErrNilDelegatorAddr + ErrBadDenom = types.ErrBadDenom + ErrBadDelegationAddr = types.ErrBadDelegationAddr + ErrBadDelegationAmount = types.ErrBadDelegationAmount + ErrNoDelegation = types.ErrNoDelegation + ErrBadDelegatorAddr = types.ErrBadDelegatorAddr + ErrNoDelegatorForAddress = types.ErrNoDelegatorForAddress + ErrInsufficientShares = types.ErrInsufficientShares + ErrDelegationValidatorEmpty = types.ErrDelegationValidatorEmpty + ErrNotEnoughDelegationShares = types.ErrNotEnoughDelegationShares + ErrBadSharesAmount = types.ErrBadSharesAmount + ErrBadSharesPercent = types.ErrBadSharesPercent + ErrNotMature = types.ErrNotMature + ErrNoUnbondingDelegation = types.ErrNoUnbondingDelegation + ErrMaxUnbondingDelegationEntries = types.ErrMaxUnbondingDelegationEntries + ErrBadRedelegationAddr = types.ErrBadRedelegationAddr + ErrNoRedelegation = types.ErrNoRedelegation + ErrSelfRedelegation = types.ErrSelfRedelegation + ErrVerySmallRedelegation = types.ErrVerySmallRedelegation + ErrBadRedelegationDst = types.ErrBadRedelegationDst + ErrTransitiveRedelegation = types.ErrTransitiveRedelegation + ErrMaxRedelegationEntries = types.ErrMaxRedelegationEntries + ErrDelegatorShareExRateInvalid = types.ErrDelegatorShareExRateInvalid + ErrBothShareMsgsGiven = types.ErrBothShareMsgsGiven + ErrNeitherShareMsgsGiven = types.ErrNeitherShareMsgsGiven + ErrMissingSignature = types.ErrMissingSignature + NewGenesisState = types.NewGenesisState + DefaultGenesisState = types.DefaultGenesisState + NewMultiStakingHooks = types.NewMultiStakingHooks + GetValidatorKey = types.GetValidatorKey + GetValidatorByConsAddrKey = types.GetValidatorByConsAddrKey + AddressFromLastValidatorPowerKey = types.AddressFromLastValidatorPowerKey + GetValidatorsByPowerIndexKey = types.GetValidatorsByPowerIndexKey + GetLastValidatorPowerKey = types.GetLastValidatorPowerKey + ParseValidatorPowerRankKey = types.ParseValidatorPowerRankKey + GetValidatorQueueTimeKey = types.GetValidatorQueueTimeKey + GetDelegationKey = types.GetDelegationKey + GetDelegationsKey = types.GetDelegationsKey + GetUBDKey = types.GetUBDKey + GetUBDByValIndexKey = types.GetUBDByValIndexKey + GetUBDKeyFromValIndexKey = types.GetUBDKeyFromValIndexKey + GetUBDsKey = types.GetUBDsKey + GetUBDsByValIndexKey = types.GetUBDsByValIndexKey + GetUnbondingDelegationTimeKey = types.GetUnbondingDelegationTimeKey + GetREDKey = types.GetREDKey + GetREDByValSrcIndexKey = types.GetREDByValSrcIndexKey + GetREDByValDstIndexKey = types.GetREDByValDstIndexKey + GetREDKeyFromValSrcIndexKey = types.GetREDKeyFromValSrcIndexKey + GetREDKeyFromValDstIndexKey = types.GetREDKeyFromValDstIndexKey + GetRedelegationTimeKey = types.GetRedelegationTimeKey + GetREDsKey = types.GetREDsKey + GetREDsFromValSrcIndexKey = types.GetREDsFromValSrcIndexKey + GetREDsToValDstIndexKey = types.GetREDsToValDstIndexKey + GetREDsByDelToValDstIndexKey = types.GetREDsByDelToValDstIndexKey + NewMsgCreateValidator = types.NewMsgCreateValidator + NewMsgEditValidator = types.NewMsgEditValidator + NewMsgDelegate = types.NewMsgDelegate + NewMsgBeginRedelegate = types.NewMsgBeginRedelegate + NewMsgUndelegate = types.NewMsgUndelegate + NewParams = types.NewParams + DefaultParams = types.DefaultParams + MustUnmarshalParams = types.MustUnmarshalParams + UnmarshalParams = types.UnmarshalParams + NewPool = types.NewPool + NewQueryDelegatorParams = types.NewQueryDelegatorParams + NewQueryValidatorParams = types.NewQueryValidatorParams + NewQueryBondsParams = types.NewQueryBondsParams + NewQueryRedelegationParams = types.NewQueryRedelegationParams + NewQueryValidatorsParams = types.NewQueryValidatorsParams + NewValidator = types.NewValidator + MustMarshalValidator = types.MustMarshalValidator + MustUnmarshalValidator = types.MustUnmarshalValidator + UnmarshalValidator = types.UnmarshalValidator + NewDescription = types.NewDescription + + // variable aliases + ModuleCdc = types.ModuleCdc + LastValidatorPowerKey = types.LastValidatorPowerKey + LastTotalPowerKey = types.LastTotalPowerKey + ValidatorsKey = types.ValidatorsKey + ValidatorsByConsAddrKey = types.ValidatorsByConsAddrKey + ValidatorsByPowerIndexKey = types.ValidatorsByPowerIndexKey + DelegationKey = types.DelegationKey + UnbondingDelegationKey = types.UnbondingDelegationKey + UnbondingDelegationByValIndexKey = types.UnbondingDelegationByValIndexKey + RedelegationKey = types.RedelegationKey + RedelegationByValSrcIndexKey = types.RedelegationByValSrcIndexKey + RedelegationByValDstIndexKey = types.RedelegationByValDstIndexKey + UnbondingQueueKey = types.UnbondingQueueKey + RedelegationQueueKey = types.RedelegationQueueKey + ValidatorQueueKey = types.ValidatorQueueKey + KeyUnbondingTime = types.KeyUnbondingTime + KeyMaxValidators = types.KeyMaxValidators + KeyMaxEntries = types.KeyMaxEntries + KeyBondDenom = types.KeyBondDenom +) + +type ( + Keeper = keeper.Keeper + Commission = types.Commission + CommissionRates = types.CommissionRates + DVPair = types.DVPair + DVVTriplet = types.DVVTriplet + Delegation = types.Delegation + Delegations = types.Delegations + UnbondingDelegation = types.UnbondingDelegation + UnbondingDelegationEntry = types.UnbondingDelegationEntry + UnbondingDelegations = types.UnbondingDelegations + Redelegation = types.Redelegation + RedelegationEntry = types.RedelegationEntry + Redelegations = types.Redelegations + DelegationResponse = types.DelegationResponse + DelegationResponses = types.DelegationResponses + RedelegationResponse = types.RedelegationResponse + RedelegationEntryResponse = types.RedelegationEntryResponse + RedelegationResponses = types.RedelegationResponses + CodeType = types.CodeType + GenesisState = types.GenesisState + LastValidatorPower = types.LastValidatorPower + MultiStakingHooks = types.MultiStakingHooks + MsgCreateValidator = types.MsgCreateValidator + MsgEditValidator = types.MsgEditValidator + MsgDelegate = types.MsgDelegate + MsgBeginRedelegate = types.MsgBeginRedelegate + MsgUndelegate = types.MsgUndelegate + Params = types.Params + Pool = types.Pool + QueryDelegatorParams = types.QueryDelegatorParams + QueryValidatorParams = types.QueryValidatorParams + QueryBondsParams = types.QueryBondsParams + QueryRedelegationParams = types.QueryRedelegationParams + QueryValidatorsParams = types.QueryValidatorsParams + Validator = types.Validator + Validators = types.Validators + Description = types.Description + DelegationI = exported.DelegationI + ValidatorI = exported.ValidatorI +) diff --git a/staking/app_test.go b/staking/app_test.go new file mode 100644 index 000000000..c931c5410 --- /dev/null +++ b/staking/app_test.go @@ -0,0 +1,188 @@ +package staking + +import ( + "testing" + + "github.com/stretchr/testify/require" + abci "github.com/tendermint/tendermint/abci/types" + + 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/mock" + "github.com/cosmos/cosmos-sdk/x/staking/types" + "github.com/cosmos/cosmos-sdk/x/supply" + supplyexported "github.com/cosmos/cosmos-sdk/x/supply/exported" +) + +// getMockApp returns an initialized mock application for this module. +func getMockApp(t *testing.T) (*mock.App, Keeper) { + mApp := mock.NewApp() + + RegisterCodec(mApp.Cdc) + supply.RegisterCodec(mApp.Cdc) + + keyStaking := sdk.NewKVStoreKey(StoreKey) + tkeyStaking := sdk.NewTransientStoreKey(TStoreKey) + keySupply := sdk.NewKVStoreKey(supply.StoreKey) + + feeCollector := supply.NewEmptyModuleAccount(auth.FeeCollectorName) + notBondedPool := supply.NewEmptyModuleAccount(types.NotBondedPoolName, supply.Burner, supply.Staking) + bondPool := supply.NewEmptyModuleAccount(types.BondedPoolName, supply.Burner, supply.Staking) + + blacklistedAddrs := make(map[string]bool) + blacklistedAddrs[feeCollector.String()] = true + blacklistedAddrs[notBondedPool.String()] = true + blacklistedAddrs[bondPool.String()] = true + + bankKeeper := bank.NewBaseKeeper(mApp.AccountKeeper, mApp.ParamsKeeper.Subspace(bank.DefaultParamspace), bank.DefaultCodespace, blacklistedAddrs) + maccPerms := map[string][]string{ + auth.FeeCollectorName: nil, + types.NotBondedPoolName: []string{supply.Burner, supply.Staking}, + types.BondedPoolName: []string{supply.Burner, supply.Staking}, + } + supplyKeeper := supply.NewKeeper(mApp.Cdc, keySupply, mApp.AccountKeeper, bankKeeper, maccPerms) + keeper := NewKeeper(mApp.Cdc, keyStaking, tkeyStaking, supplyKeeper, mApp.ParamsKeeper.Subspace(DefaultParamspace), DefaultCodespace) + + mApp.Router().AddRoute(RouterKey, NewHandler(keeper)) + mApp.SetEndBlocker(getEndBlocker(keeper)) + mApp.SetInitChainer(getInitChainer(mApp, keeper, mApp.AccountKeeper, supplyKeeper, + []supplyexported.ModuleAccountI{feeCollector, notBondedPool, bondPool})) + + require.NoError(t, mApp.CompleteSetup(keyStaking, tkeyStaking, keySupply)) + return mApp, keeper +} + +// getEndBlocker returns a staking endblocker. +func getEndBlocker(keeper Keeper) sdk.EndBlocker { + return func(ctx sdk.Context, req abci.RequestEndBlock) abci.ResponseEndBlock { + validatorUpdates := EndBlocker(ctx, keeper) + + return abci.ResponseEndBlock{ + ValidatorUpdates: validatorUpdates, + } + } +} + +// getInitChainer initializes the chainer of the mock app and sets the genesis +// state. It returns an empty ResponseInitChain. +func getInitChainer(mapp *mock.App, keeper Keeper, accountKeeper types.AccountKeeper, supplyKeeper types.SupplyKeeper, + blacklistedAddrs []supplyexported.ModuleAccountI) sdk.InitChainer { + return func(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain { + mapp.InitChainer(ctx, req) + + // set module accounts + for _, macc := range blacklistedAddrs { + supplyKeeper.SetModuleAccount(ctx, macc) + } + + stakingGenesis := DefaultGenesisState() + validators := InitGenesis(ctx, keeper, accountKeeper, supplyKeeper, stakingGenesis) + return abci.ResponseInitChain{ + Validators: validators, + } + } +} + +//__________________________________________________________________________________________ + +func checkValidator(t *testing.T, mapp *mock.App, keeper Keeper, + addr sdk.ValAddress, expFound bool) Validator { + + ctxCheck := mapp.BaseApp.NewContext(true, abci.Header{}) + validator, found := keeper.GetValidator(ctxCheck, addr) + + require.Equal(t, expFound, found) + return validator +} + +func checkDelegation( + t *testing.T, mapp *mock.App, keeper Keeper, delegatorAddr sdk.AccAddress, + validatorAddr sdk.ValAddress, expFound bool, expShares sdk.Dec, +) { + + ctxCheck := mapp.BaseApp.NewContext(true, abci.Header{}) + delegation, found := keeper.GetDelegation(ctxCheck, delegatorAddr, validatorAddr) + if expFound { + require.True(t, found) + require.True(sdk.DecEq(t, expShares, delegation.Shares)) + + return + } + + require.False(t, found) +} + +func TestStakingMsgs(t *testing.T) { + mApp, keeper := getMockApp(t) + + genTokens := sdk.TokensFromConsensusPower(42) + bondTokens := sdk.TokensFromConsensusPower(10) + genCoin := sdk.NewCoin(sdk.DefaultBondDenom, genTokens) + bondCoin := sdk.NewCoin(sdk.DefaultBondDenom, bondTokens) + + acc1 := &auth.BaseAccount{ + Address: addr1, + Coins: sdk.Coins{genCoin}, + } + acc2 := &auth.BaseAccount{ + Address: addr2, + Coins: sdk.Coins{genCoin}, + } + accs := []auth.Account{acc1, acc2} + + mock.SetGenesis(mApp, accs) + mock.CheckBalance(t, mApp, addr1, sdk.Coins{genCoin}) + mock.CheckBalance(t, mApp, addr2, sdk.Coins{genCoin}) + + // create validator + description := NewDescription("foo_moniker", "", "", "") + createValidatorMsg := NewMsgCreateValidator( + sdk.ValAddress(addr1), priv1.PubKey(), bondCoin, description, commissionRates, sdk.OneInt(), + ) + + header := abci.Header{Height: mApp.LastBlockHeight() + 1} + mock.SignCheckDeliver(t, mApp.Cdc, mApp.BaseApp, header, []sdk.Msg{createValidatorMsg}, []uint64{0}, []uint64{0}, true, true, priv1) + mock.CheckBalance(t, mApp, addr1, sdk.Coins{genCoin.Sub(bondCoin)}) + + header = abci.Header{Height: mApp.LastBlockHeight() + 1} + mApp.BeginBlock(abci.RequestBeginBlock{Header: header}) + + validator := checkValidator(t, mApp, keeper, sdk.ValAddress(addr1), true) + require.Equal(t, sdk.ValAddress(addr1), validator.OperatorAddress) + require.Equal(t, sdk.Bonded, validator.Status) + require.True(sdk.IntEq(t, bondTokens, validator.BondedTokens())) + + header = abci.Header{Height: mApp.LastBlockHeight() + 1} + mApp.BeginBlock(abci.RequestBeginBlock{Header: header}) + + // edit the validator + description = NewDescription("bar_moniker", "", "", "") + editValidatorMsg := NewMsgEditValidator(sdk.ValAddress(addr1), description, nil, nil) + + header = abci.Header{Height: mApp.LastBlockHeight() + 1} + mock.SignCheckDeliver(t, mApp.Cdc, mApp.BaseApp, header, []sdk.Msg{editValidatorMsg}, []uint64{0}, []uint64{1}, true, true, priv1) + + validator = checkValidator(t, mApp, keeper, sdk.ValAddress(addr1), true) + require.Equal(t, description, validator.Description) + + // delegate + mock.CheckBalance(t, mApp, addr2, sdk.Coins{genCoin}) + delegateMsg := NewMsgDelegate(addr2, sdk.ValAddress(addr1), bondCoin) + + header = abci.Header{Height: mApp.LastBlockHeight() + 1} + mock.SignCheckDeliver(t, mApp.Cdc, mApp.BaseApp, header, []sdk.Msg{delegateMsg}, []uint64{1}, []uint64{0}, true, true, priv2) + mock.CheckBalance(t, mApp, addr2, sdk.Coins{genCoin.Sub(bondCoin)}) + checkDelegation(t, mApp, keeper, addr2, sdk.ValAddress(addr1), true, bondTokens.ToDec()) + + // begin unbonding + beginUnbondingMsg := NewMsgUndelegate(addr2, sdk.ValAddress(addr1), bondCoin) + header = abci.Header{Height: mApp.LastBlockHeight() + 1} + mock.SignCheckDeliver(t, mApp.Cdc, mApp.BaseApp, header, []sdk.Msg{beginUnbondingMsg}, []uint64{1}, []uint64{1}, true, true, priv2) + + // delegation should exist anymore + checkDelegation(t, mApp, keeper, addr2, sdk.ValAddress(addr1), false, sdk.Dec{}) + + // balance should be the same because bonding not yet complete + mock.CheckBalance(t, mApp, addr2, sdk.Coins{genCoin.Sub(bondCoin)}) +} diff --git a/staking/client/cli/flags.go b/staking/client/cli/flags.go index 83d0df64b..563598409 100644 --- a/staking/client/cli/flags.go +++ b/staking/client/cli/flags.go @@ -1,22 +1,70 @@ package cli +import ( + flag "github.com/spf13/pflag" + + "github.com/cosmos/cosmos-sdk/x/staking/types" +) + +// nolint const ( - FlagProposerAddress = "proposer" - FlagValidatorAddress = "validator" - FlagValidatorID = "id" - FlagSignerAddress = "signer" - FlagSignerPubkey = "signer-pubkey" - FlagNewSignerPubkey = "new-pubkey" - FlagAmount = "staked-amount" - FlagAcceptDelegation = "accept-delegation" - FlagTxHash = "tx-hash" - FlagLogIndex = "log-index" - FlagActivationEpoch = "activation-epoch" - FlagDeactivationEpoch = "deactivation-epoch" - FlagFeeAmount = "fee-amount" - FlagBlockNumber = "block-number" - FlagNonce = "nonce" - FlagStartEpoch = "start-epoch" - FlagEndEpoch = "end-epoch" - FlagTimes = "times" + FlagAddressValidator = "validator" + FlagAddressValidatorSrc = "addr-validator-source" + FlagAddressValidatorDst = "addr-validator-dest" + FlagPubKey = "pubkey" + FlagAmount = "amount" + FlagSharesAmount = "shares-amount" + FlagSharesFraction = "shares-fraction" + + FlagMoniker = "moniker" + FlagIdentity = "identity" + FlagWebsite = "website" + FlagDetails = "details" + + FlagCommissionRate = "commission-rate" + FlagCommissionMaxRate = "commission-max-rate" + FlagCommissionMaxChangeRate = "commission-max-change-rate" + + FlagMinSelfDelegation = "min-self-delegation" + + FlagGenesisFormat = "genesis-format" + FlagNodeID = "node-id" + FlagIP = "ip" ) + +// common flagsets to add to various functions +var ( + FsPk = flag.NewFlagSet("", flag.ContinueOnError) + FsAmount = flag.NewFlagSet("", flag.ContinueOnError) + fsShares = flag.NewFlagSet("", flag.ContinueOnError) + fsDescriptionCreate = flag.NewFlagSet("", flag.ContinueOnError) + FsCommissionCreate = flag.NewFlagSet("", flag.ContinueOnError) + fsCommissionUpdate = flag.NewFlagSet("", flag.ContinueOnError) + FsMinSelfDelegation = flag.NewFlagSet("", flag.ContinueOnError) + fsDescriptionEdit = flag.NewFlagSet("", flag.ContinueOnError) + fsValidator = flag.NewFlagSet("", flag.ContinueOnError) + fsRedelegation = flag.NewFlagSet("", flag.ContinueOnError) +) + +func init() { + FsPk.String(FlagPubKey, "", "The Bech32 encoded PubKey of the validator") + FsAmount.String(FlagAmount, "", "Amount of coins to bond") + fsShares.String(FlagSharesAmount, "", "Amount of source-shares to either unbond or redelegate as a positive integer or decimal") + fsShares.String(FlagSharesFraction, "", "Fraction of source-shares to either unbond or redelegate as a positive integer or decimal >0 and <=1") + fsDescriptionCreate.String(FlagMoniker, "", "The validator's name") + fsDescriptionCreate.String(FlagIdentity, "", "The optional identity signature (ex. UPort or Keybase)") + fsDescriptionCreate.String(FlagWebsite, "", "The validator's (optional) website") + fsDescriptionCreate.String(FlagDetails, "", "The validator's (optional) details") + fsCommissionUpdate.String(FlagCommissionRate, "", "The new commission rate percentage") + FsCommissionCreate.String(FlagCommissionRate, "", "The initial commission rate percentage") + FsCommissionCreate.String(FlagCommissionMaxRate, "", "The maximum commission rate percentage") + FsCommissionCreate.String(FlagCommissionMaxChangeRate, "", "The maximum commission change rate percentage (per day)") + FsMinSelfDelegation.String(FlagMinSelfDelegation, "", "The minimum self delegation required on the validator") + fsDescriptionEdit.String(FlagMoniker, types.DoNotModifyDesc, "The validator's name") + fsDescriptionEdit.String(FlagIdentity, types.DoNotModifyDesc, "The (optional) identity signature (ex. UPort or Keybase)") + fsDescriptionEdit.String(FlagWebsite, types.DoNotModifyDesc, "The validator's (optional) website") + fsDescriptionEdit.String(FlagDetails, types.DoNotModifyDesc, "The validator's (optional) details") + fsValidator.String(FlagAddressValidator, "", "The Bech32 address of the validator") + fsRedelegation.String(FlagAddressValidatorSrc, "", "The Bech32 address of the source validator") + fsRedelegation.String(FlagAddressValidatorDst, "", "The Bech32 address of the destination validator") +} diff --git a/staking/client/cli/query.go b/staking/client/cli/query.go index 35fb52639..9e0352f93 100644 --- a/staking/client/cli/query.go +++ b/staking/client/cli/query.go @@ -1,326 +1,592 @@ package cli import ( - "encoding/json" "fmt" + "strings" + + "github.com/spf13/cobra" "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/context" "github.com/cosmos/cosmos-sdk/codec" - "github.com/spf13/cobra" - "github.com/spf13/viper" - - "github.com/ethereum/go-ethereum/common" - hmClient "github.com/maticnetwork/heimdall/client" - "github.com/maticnetwork/heimdall/staking/types" - hmTypes "github.com/maticnetwork/heimdall/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/version" + "github.com/cosmos/cosmos-sdk/x/staking/types" ) // GetQueryCmd returns the cli query commands for this module -func GetQueryCmd(cdc *codec.Codec) *cobra.Command { - // Group supply queries under a subcommand - supplyQueryCmd := &cobra.Command{ +func GetQueryCmd(queryRoute string, cdc *codec.Codec) *cobra.Command { + stakingQueryCmd := &cobra.Command{ Use: types.ModuleName, Short: "Querying commands for the staking module", DisableFlagParsing: true, SuggestionsMinimumDistance: 2, - RunE: hmClient.ValidateCmd, + RunE: client.ValidateCmd, } + stakingQueryCmd.AddCommand(client.GetCommands( + GetCmdQueryDelegation(queryRoute, cdc), + GetCmdQueryDelegations(queryRoute, cdc), + GetCmdQueryUnbondingDelegation(queryRoute, cdc), + GetCmdQueryUnbondingDelegations(queryRoute, cdc), + GetCmdQueryRedelegation(queryRoute, cdc), + GetCmdQueryRedelegations(queryRoute, cdc), + GetCmdQueryValidator(queryRoute, cdc), + GetCmdQueryValidators(queryRoute, cdc), + GetCmdQueryValidatorDelegations(queryRoute, cdc), + GetCmdQueryValidatorUnbondingDelegations(queryRoute, cdc), + GetCmdQueryValidatorRedelegations(queryRoute, cdc), + GetCmdQueryParams(queryRoute, cdc), + GetCmdQueryPool(queryRoute, cdc))...) + + return stakingQueryCmd - // supply query command - supplyQueryCmd.AddCommand( - client.GetCommands( - GetValidatorInfo(cdc), - GetCurrentValSet(cdc), - GetTotalStakingPower(cdc), - GetValidatorStatus(cdc), - GetProposer(cdc), - GetCurentProposer(cdc), - IsOldTx(cdc), - )..., - ) - - return supplyQueryCmd } -// GetValidatorInfo validator information via id or address -func GetValidatorInfo(cdc *codec.Codec) *cobra.Command { - cmd := &cobra.Command{ - Use: "validator-info", - Short: "show validator information via validator id or validator address", +// GetCmdQueryValidator implements the validator query command. +func GetCmdQueryValidator(storeName string, cdc *codec.Codec) *cobra.Command { + return &cobra.Command{ + Use: "validator [validator-addr]", + Short: "Query a validator", + Long: strings.TrimSpace( + fmt.Sprintf(`Query details about an individual validator. + +Example: +$ %s query staking validator cosmosvaloper1gghjut3ccd8ay0zduzj64hwre2fxs9ldmqhffj +`, + version.ClientName, + ), + ), + Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { cliCtx := context.NewCLIContext().WithCodec(cdc) - validatorID := viper.GetInt64(FlagValidatorID) - validatorAddressStr := viper.GetString(FlagValidatorAddress) - if validatorID == 0 && validatorAddressStr == "" { - return fmt.Errorf("validator ID or validator address required") - } - var queryParams []byte - var err error - var t string = "" - if validatorAddressStr != "" { - queryParams, err = cliCtx.Codec.MarshalJSON(types.NewQuerySignerParams(common.FromHex(validatorAddressStr))) - if err != nil { - return err - } - t = types.QuerySigner - } else if validatorID != 0 { - queryParams, err = cliCtx.Codec.MarshalJSON(types.NewQueryValidatorParams(hmTypes.ValidatorID(validatorID))) - if err != nil { - return err - } - t = types.QueryValidator + addr, err := sdk.ValAddressFromBech32(args[0]) + if err != nil { + return err } - // get validator - res, _, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/%s", types.QuerierRoute, t), queryParams) + res, _, err := cliCtx.QueryStore(types.GetValidatorKey(addr), storeName) if err != nil { return err } - fmt.Println(string(res)) - return nil + if len(res) == 0 { + return fmt.Errorf("No validator found with address %s", addr) + } + + return cliCtx.PrintOutput(types.MustUnmarshalValidator(cdc, res)) }, } +} - cmd.Flags().Int(FlagValidatorID, 0, "--id=") - cmd.Flags().String(FlagValidatorAddress, "", "--validator=") +// GetCmdQueryValidators implements the query all validators command. +func GetCmdQueryValidators(storeName string, cdc *codec.Codec) *cobra.Command { + return &cobra.Command{ + Use: "validators", + Short: "Query for all validators", + Args: cobra.NoArgs, + Long: strings.TrimSpace( + fmt.Sprintf(`Query details about all validators on a network. + +Example: +$ %s query staking validators +`, + version.ClientName, + ), + ), + RunE: func(cmd *cobra.Command, args []string) error { + cliCtx := context.NewCLIContext().WithCodec(cdc) + + resKVs, _, err := cliCtx.QuerySubspace(types.ValidatorsKey, storeName) + if err != nil { + return err + } + + var validators types.Validators + for _, kv := range resKVs { + validators = append(validators, types.MustUnmarshalValidator(cdc, kv.Value)) + } - return cmd + return cliCtx.PrintOutput(validators) + }, + } } -// GetCurrentValSet validator information via address -func GetCurrentValSet(cdc *codec.Codec) *cobra.Command { - cmd := &cobra.Command{ - Use: "current-validator-set", - Short: "show current validator set", +// GetCmdQueryValidatorUnbondingDelegations implements the query all unbonding delegatations from a validator command. +func GetCmdQueryValidatorUnbondingDelegations(queryRoute string, cdc *codec.Codec) *cobra.Command { + return &cobra.Command{ + Use: "unbonding-delegations-from [validator-addr]", + Short: "Query all unbonding delegatations from a validator", + Long: strings.TrimSpace( + fmt.Sprintf(`Query delegations that are unbonding _from_ a validator. + +Example: +$ %s query staking unbonding-delegations-from cosmosvaloper1gghjut3ccd8ay0zduzj64hwre2fxs9ldmqhffj +`, + version.ClientName, + ), + ), + Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { cliCtx := context.NewCLIContext().WithCodec(cdc) - // get validator set - res, _, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QueryCurrentValidatorSet), nil) + valAddr, err := sdk.ValAddressFromBech32(args[0]) + if err != nil { + return err + } + + bz, err := cdc.MarshalJSON(types.NewQueryValidatorParams(valAddr)) + if err != nil { + return err + } + + route := fmt.Sprintf("custom/%s/%s", queryRoute, types.QueryValidatorUnbondingDelegations) + res, _, err := cliCtx.QueryWithData(route, bz) if err != nil { return err } - fmt.Println(string(res)) - return nil + var ubds types.UnbondingDelegations + cdc.MustUnmarshalJSON(res, &ubds) + return cliCtx.PrintOutput(ubds) }, } - - return cmd } -// Get total staking power -func GetTotalStakingPower(cdc *codec.Codec) *cobra.Command { - cmd := &cobra.Command{ - Use: "staking-power", - Short: "show the current staking power", +// GetCmdQueryValidatorRedelegations implements the query all redelegatations +// from a validator command. +func GetCmdQueryValidatorRedelegations(queryRoute string, cdc *codec.Codec) *cobra.Command { + return &cobra.Command{ + Use: "redelegations-from [validator-addr]", + Short: "Query all outgoing redelegatations from a validator", + Long: strings.TrimSpace( + fmt.Sprintf(`Query delegations that are redelegating _from_ a validator. + +Example: +$ %s query staking redelegations-from cosmosvaloper1gghjut3ccd8ay0zduzj64hwre2fxs9ldmqhffj +`, + version.ClientName, + ), + ), + Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { cliCtx := context.NewCLIContext().WithCodec(cdc) - totalPowerBytes, _, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QueryTotalValidatorPower), nil) + valSrcAddr, err := sdk.ValAddressFromBech32(args[0]) if err != nil { return err } - // check content - if len(totalPowerBytes) == 0 { - fmt.Printf("Total power not found") - return nil + bz, err := cdc.MarshalJSON(types.QueryRedelegationParams{SrcValidatorAddr: valSrcAddr}) + if err != nil { + return err } - var totalPower uint64 - if err := json.Unmarshal(totalPowerBytes, &totalPower); err != nil { + route := fmt.Sprintf("custom/%s/%s", queryRoute, types.QueryRedelegations) + res, _, err := cliCtx.QueryWithData(route, bz) + if err != nil { return err } - res, err := json.Marshal(map[string]interface{}{"result": totalPower}) - if err != nil { + var resp types.RedelegationResponses + if err := cdc.UnmarshalJSON(res, &resp); err != nil { return err } - fmt.Println(string(res)) - return nil + return cliCtx.PrintOutput(resp) }, } - - return cmd } -// GetValidatorInfo validator status via address -func GetValidatorStatus(cdc *codec.Codec) *cobra.Command { - cmd := &cobra.Command{ - Use: "validator-status", - Short: "show validator status by validator address", +// GetCmdQueryDelegation the query delegation command. +func GetCmdQueryDelegation(queryRoute string, cdc *codec.Codec) *cobra.Command { + return &cobra.Command{ + Use: "delegation [delegator-addr] [validator-addr]", + Short: "Query a delegation based on address and validator address", + Long: strings.TrimSpace( + fmt.Sprintf(`Query delegations for an individual delegator on an individual validator. + +Example: +$ %s query staking delegation cosmos1gghjut3ccd8ay0zduzj64hwre2fxs9ld75ru9p cosmosvaloper1gghjut3ccd8ay0zduzj64hwre2fxs9ldmqhffj +`, + version.ClientName, + ), + ), + Args: cobra.ExactArgs(2), RunE: func(cmd *cobra.Command, args []string) error { cliCtx := context.NewCLIContext().WithCodec(cdc) - validatorAddressStr := viper.GetString(FlagValidatorAddress) - if validatorAddressStr == "" { - return fmt.Errorf("validator ID or validator address required") + delAddr, err := sdk.AccAddressFromBech32(args[0]) + if err != nil { + return err } - queryParams, err := cliCtx.Codec.MarshalJSON(types.NewQuerySignerParams(common.FromHex(validatorAddressStr))) + valAddr, err := sdk.ValAddressFromBech32(args[1]) if err != nil { return err } - statusBytes, _, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QueryValidatorStatus), queryParams) + bz, err := cdc.MarshalJSON(types.NewQueryBondsParams(delAddr, valAddr)) if err != nil { return err } - // error if no checkpoint found - if len(statusBytes) == 0 { - fmt.Printf("Not Found") - return nil + route := fmt.Sprintf("custom/%s/%s", queryRoute, types.QueryDelegation) + res, _, err := cliCtx.QueryWithData(route, bz) + if err != nil { + return err } - var status bool - if err = json.Unmarshal(statusBytes, &status); err != nil { + var resp types.DelegationResponse + if err := cdc.UnmarshalJSON(res, &resp); err != nil { return err } - res, err := json.Marshal(map[string]interface{}{"result": status}) + return cliCtx.PrintOutput(resp) + }, + } +} + +// GetCmdQueryDelegations implements the command to query all the delegations +// made from one delegator. +func GetCmdQueryDelegations(queryRoute string, cdc *codec.Codec) *cobra.Command { + return &cobra.Command{ + Use: "delegations [delegator-addr]", + Short: "Query all delegations made by one delegator", + Long: strings.TrimSpace( + fmt.Sprintf(`Query delegations for an individual delegator on all validators. + +Example: +$ %s query staking delegations cosmos1gghjut3ccd8ay0zduzj64hwre2fxs9ld75ru9p +`, + version.ClientName, + ), + ), + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + cliCtx := context.NewCLIContext().WithCodec(cdc) + + delAddr, err := sdk.AccAddressFromBech32(args[0]) if err != nil { return err } - fmt.Println(string(res)) - return nil - }, - } + bz, err := cdc.MarshalJSON(types.NewQueryDelegatorParams(delAddr)) + if err != nil { + return err + } - cmd.Flags().String(FlagValidatorAddress, "", "--validator=") + route := fmt.Sprintf("custom/%s/%s", queryRoute, types.QueryDelegatorDelegations) + res, _, err := cliCtx.QueryWithData(route, bz) + if err != nil { + return err + } - if err := cmd.MarkFlagRequired(FlagValidatorAddress); err != nil { - logger.Error("GetValidatorStatus | MarkFlagRequired | FlagValidatorAddress", "Error", err) - } + var resp types.DelegationResponses + if err := cdc.UnmarshalJSON(res, &resp); err != nil { + return err + } - return cmd + return cliCtx.PrintOutput(resp) + }, + } } -// GetValidatorInfo validator status via address -func GetProposer(cdc *codec.Codec) *cobra.Command { - cmd := &cobra.Command{ - Use: "proposer", - Short: "show proposer info by times", +// GetCmdQueryValidatorDelegations implements the command to query all the +// delegations to a specific validator. +func GetCmdQueryValidatorDelegations(queryRoute string, cdc *codec.Codec) *cobra.Command { + return &cobra.Command{ + Use: "delegations-to [validator-addr]", + Short: "Query all delegations made to one validator", + Long: strings.TrimSpace( + fmt.Sprintf(`Query delegations on an individual validator. + +Example: +$ %s query staking delegations-to cosmosvaloper1gghjut3ccd8ay0zduzj64hwre2fxs9ldmqhffj +`, + version.ClientName, + ), + ), + Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { cliCtx := context.NewCLIContext().WithCodec(cdc) - times := viper.GetUint64(FlagTimes) + valAddr, err := sdk.ValAddressFromBech32(args[0]) + if err != nil { + return err + } - // get query params - queryParams, err := cliCtx.Codec.MarshalJSON(types.NewQueryProposerParams(times)) + bz, err := cdc.MarshalJSON(types.NewQueryValidatorParams(valAddr)) if err != nil { return err } - res, _, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QueryProposer), queryParams) + route := fmt.Sprintf("custom/%s/%s", queryRoute, types.QueryValidatorDelegations) + res, _, err := cliCtx.QueryWithData(route, bz) if err != nil { return err } - // error if no checkpoint found - if len(res) == 0 { - fmt.Printf("No Proposer found") - return nil + var resp types.DelegationResponses + if err := cdc.UnmarshalJSON(res, &resp); err != nil { + return err } - fmt.Println(string(res)) - return nil + return cliCtx.PrintOutput(resp) }, } +} - cmd.Flags().String(FlagTimes, "", "--times=