Skip to content

Commit

Permalink
Merge PR #5299: Migrate Equivocation Handling to x/evidence
Browse files Browse the repository at this point in the history
  • Loading branch information
alexanderbez authored Dec 3, 2019
1 parent 5be87e4 commit 7953b52
Show file tree
Hide file tree
Showing 47 changed files with 995 additions and 381 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ deprecated and all components removed except the `legacy/` package. This require
genesis state. Namely, `accounts` now exist under `app_state.auth.accounts`. The corresponding migration
logic has been implemented for v0.38 target version. Applications can migrate via:
`$ {appd} migrate v0.38 genesis.json`.
* (modules) [\#5299](https://github.com/cosmos/cosmos-sdk/pull/5299) Handling of `ABCIEvidenceTypeDuplicateVote`
during `BeginBlock` along with the corresponding parameters (`MaxEvidenceAge`) have moved from the
`x/slashing` module to the `x/evidence` module.

### API Breaking Changes

Expand Down Expand Up @@ -71,6 +74,8 @@ if the provided arguments are invalid.
* `StdTx#GetSignatures` will return an array of just signature byte slices `[][]byte` instead of
returning an array of `StdSignature` structs. To replicate the old behavior, use the public field
`StdTx.Signatures` to get back the array of StdSignatures `[]StdSignature`.
* (modules) [\#5299](https://github.com/cosmos/cosmos-sdk/pull/5299) `HandleDoubleSign` along with params `MaxEvidenceAge`
and `DoubleSignJailEndTime` have moved from the `x/slashing` module to the `x/evidence` module.

### Client Breaking Changes

Expand Down
3 changes: 2 additions & 1 deletion simapp/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,7 @@ func NewSimApp(
// create evidence keeper with router
evidenceKeeper := evidence.NewKeeper(
app.cdc, keys[evidence.StoreKey], app.subspaces[evidence.ModuleName], evidence.DefaultCodespace,
&app.StakingKeeper, app.SlashingKeeper,
)
evidenceRouter := evidence.NewRouter()
// TODO: Register evidence routes.
Expand Down Expand Up @@ -237,7 +238,7 @@ func NewSimApp(
// During begin block slashing happens after distr.BeginBlocker so that
// there is nothing left over in the validator fee pool, so as to keep the
// CanWithdrawInvariant invariant.
app.mm.SetOrderBeginBlockers(upgrade.ModuleName, mint.ModuleName, distr.ModuleName, slashing.ModuleName)
app.mm.SetOrderBeginBlockers(upgrade.ModuleName, mint.ModuleName, distr.ModuleName, slashing.ModuleName, evidence.ModuleName)
app.mm.SetOrderEndBlockers(crisis.ModuleName, gov.ModuleName, staking.ModuleName)

// NOTE: The genutils moodule must occur after staking so that pools are
Expand Down
25 changes: 25 additions & 0 deletions x/evidence/abci.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package evidence

import (
"fmt"

sdk "github.com/cosmos/cosmos-sdk/types"

abci "github.com/tendermint/tendermint/abci/types"
tmtypes "github.com/tendermint/tendermint/types"
)

// BeginBlocker iterates through and handles any newly discovered evidence of
// misbehavior submitted by Tendermint. Currently, only equivocation is handled.
func BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock, k Keeper) {
for _, tmEvidence := range req.ByzantineValidators {
switch tmEvidence.Type {
case tmtypes.ABCIEvidenceTypeDuplicateVote:
evidence := ConvertDuplicateVoteEvidence(tmEvidence)
k.HandleDoubleSign(ctx, evidence.(Equivocation))

default:
k.Logger(ctx).Error(fmt.Sprintf("ignored unknown evidence type: %s", tmEvidence.Type))
}
}
}
25 changes: 16 additions & 9 deletions x/evidence/alias.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const (
DefaultParamspace = types.DefaultParamspace
QueryEvidence = types.QueryEvidence
QueryAllEvidence = types.QueryAllEvidence
QueryParameters = types.QueryParameters
CodeNoEvidenceHandlerExists = types.CodeNoEvidenceHandlerExists
CodeInvalidEvidence = types.CodeInvalidEvidence
CodeNoEvidenceExists = types.CodeNoEvidenceExists
Expand All @@ -23,21 +24,26 @@ const (
EventTypeSubmitEvidence = types.EventTypeSubmitEvidence
AttributeValueCategory = types.AttributeValueCategory
AttributeKeyEvidenceHash = types.AttributeKeyEvidenceHash
DefaultMaxEvidenceAge = types.DefaultMaxEvidenceAge
)

var (
NewKeeper = keeper.NewKeeper
NewQuerier = keeper.NewQuerier

NewMsgSubmitEvidence = types.NewMsgSubmitEvidence
NewRouter = types.NewRouter
NewQueryEvidenceParams = types.NewQueryEvidenceParams
NewQueryAllEvidenceParams = types.NewQueryAllEvidenceParams
RegisterCodec = types.RegisterCodec
RegisterEvidenceTypeCodec = types.RegisterEvidenceTypeCodec
ModuleCdc = types.ModuleCdc
NewGenesisState = types.NewGenesisState
DefaultGenesisState = types.DefaultGenesisState
NewMsgSubmitEvidence = types.NewMsgSubmitEvidence
NewRouter = types.NewRouter
NewQueryEvidenceParams = types.NewQueryEvidenceParams
NewQueryAllEvidenceParams = types.NewQueryAllEvidenceParams
RegisterCodec = types.RegisterCodec
RegisterEvidenceTypeCodec = types.RegisterEvidenceTypeCodec
ModuleCdc = types.ModuleCdc
NewGenesisState = types.NewGenesisState
DefaultGenesisState = types.DefaultGenesisState
ConvertDuplicateVoteEvidence = types.ConvertDuplicateVoteEvidence
KeyMaxEvidenceAge = types.KeyMaxEvidenceAge
DoubleSignJailEndTime = types.DoubleSignJailEndTime
ParamKeyTable = types.ParamKeyTable
)

type (
Expand All @@ -47,4 +53,5 @@ type (
MsgSubmitEvidence = types.MsgSubmitEvidence
Handler = types.Handler
Router = types.Router
Equivocation = types.Equivocation
)
33 changes: 32 additions & 1 deletion x/evidence/client/cli/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,38 @@ $ %s query %s --page=2 --limit=50
cmd.Flags().Int(flagPage, 1, "pagination page of evidence to to query for")
cmd.Flags().Int(flagLimit, 100, "pagination limit of evidence to query for")

return cmd
cmd.AddCommand(client.GetCommands(QueryParamsCmd(cdc))...)

return client.GetCommands(cmd)[0]
}

// QueryParamsCmd returns the command handler for evidence parameter querying.
func QueryParamsCmd(cdc *codec.Codec) *cobra.Command {
return &cobra.Command{
Use: "params",
Short: "Query the current evidence parameters",
Args: cobra.NoArgs,
Long: strings.TrimSpace(`Query the current evidence parameters:
$ <appcli> query evidence params
`),
RunE: func(cmd *cobra.Command, args []string) error {
cliCtx := context.NewCLIContext().WithCodec(cdc)

route := fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QueryParameters)
res, _, err := cliCtx.QueryWithData(route, nil)
if err != nil {
return err
}

var params types.Params
if err := cdc.UnmarshalJSON(res, &params); err != nil {
return fmt.Errorf("failed to unmarshal params: %w", err)
}

return cliCtx.PrintOutput(params)
},
}
}

// QueryEvidenceCmd returns the command handler for evidence querying. Evidence
Expand Down
24 changes: 24 additions & 0 deletions x/evidence/client/rest/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ func registerQueryRoutes(cliCtx context.CLIContext, r *mux.Router) {
"/evidence",
queryAllEvidenceHandler(cliCtx),
).Methods(MethodGet)

r.HandleFunc(
"/evidence/params",
queryParamsHandler(cliCtx),
).Methods(MethodGet)
}

func queryEvidenceHandler(cliCtx context.CLIContext) http.HandlerFunc {
Expand Down Expand Up @@ -89,3 +94,22 @@ func queryAllEvidenceHandler(cliCtx context.CLIContext) http.HandlerFunc {
rest.PostProcessResponse(w, cliCtx, res)
}
}

func queryParamsHandler(cliCtx context.CLIContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
cliCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r)
if !ok {
return
}

route := fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QueryParameters)
res, height, err := cliCtx.QueryWithData(route, nil)
if err != nil {
rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
return
}

cliCtx = cliCtx.WithHeight(height)
rest.PostProcessResponse(w, cliCtx, res)
}
}
3 changes: 3 additions & 0 deletions x/evidence/genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,14 @@ func InitGenesis(ctx sdk.Context, k Keeper, gs GenesisState) {

k.SetEvidence(ctx, e)
}

k.SetParams(ctx, gs.Params)
}

// ExportGenesis returns the evidence module's exported genesis.
func ExportGenesis(ctx sdk.Context, k Keeper) GenesisState {
return GenesisState{
Params: k.GetParams(ctx),
Evidence: k.GetAllEvidence(ctx),
}
}
6 changes: 3 additions & 3 deletions x/evidence/genesis_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ func (suite *GenesisTestSuite) SetupTest() {
// recreate keeper in order to use custom testing types
evidenceKeeper := evidence.NewKeeper(
cdc, app.GetKey(evidence.StoreKey), app.GetSubspace(evidence.ModuleName),
evidence.DefaultCodespace,
evidence.DefaultCodespace, app.StakingKeeper, app.SlashingKeeper,
)
router := evidence.NewRouter()
router = router.AddRoute(types.TestEvidenceRouteEquivocation, types.TestEquivocationHandler(*evidenceKeeper))
Expand Down Expand Up @@ -67,7 +67,7 @@ func (suite *GenesisTestSuite) TestInitGenesis_Valid() {
}

suite.NotPanics(func() {
evidence.InitGenesis(suite.ctx, suite.keeper, evidence.NewGenesisState(testEvidence))
evidence.InitGenesis(suite.ctx, suite.keeper, evidence.NewGenesisState(types.DefaultParams(), testEvidence))
})

for _, e := range testEvidence {
Expand Down Expand Up @@ -100,7 +100,7 @@ func (suite *GenesisTestSuite) TestInitGenesis_Invalid() {
}

suite.Panics(func() {
evidence.InitGenesis(suite.ctx, suite.keeper, evidence.NewGenesisState(testEvidence))
evidence.InitGenesis(suite.ctx, suite.keeper, evidence.NewGenesisState(types.DefaultParams(), testEvidence))
})

suite.Empty(suite.keeper.GetAllEvidence(suite.ctx))
Expand Down
2 changes: 1 addition & 1 deletion x/evidence/handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ func (suite *HandlerTestSuite) SetupTest() {
// recreate keeper in order to use custom testing types
evidenceKeeper := evidence.NewKeeper(
cdc, app.GetKey(evidence.StoreKey), app.GetSubspace(evidence.ModuleName),
evidence.DefaultCodespace,
evidence.DefaultCodespace, app.StakingKeeper, app.SlashingKeeper,
)
router := evidence.NewRouter()
router = router.AddRoute(types.TestEvidenceRouteEquivocation, types.TestEquivocationHandler(*evidenceKeeper))
Expand Down
109 changes: 109 additions & 0 deletions x/evidence/internal/keeper/infraction.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package keeper

import (
"fmt"

sdk "github.com/cosmos/cosmos-sdk/types"

"github.com/cosmos/cosmos-sdk/x/evidence/internal/types"
)

// HandleDoubleSign implements an equivocation evidence handler. Assuming the
// evidence is valid, the validator committing the misbehavior will be slashed,
// jailed and tombstoned. Once tombstoned, the validator will not be able to
// recover. Note, the evidence contains the block time and height at the time of
// the equivocation.
//
// The evidence is considered invalid if:
// - the evidence is too old
// - the validator is unbonded or does not exist
// - the signing info does not exist (will panic)
// - is already tombstoned
//
// TODO: Some of the invalid constraints listed above may need to be reconsidered
// in the case of a lunatic attack.
func (k Keeper) HandleDoubleSign(ctx sdk.Context, evidence types.Equivocation) {
logger := k.Logger(ctx)
consAddr := evidence.GetConsensusAddress()
infractionHeight := evidence.GetHeight()

// calculate the age of the evidence
blockTime := ctx.BlockHeader().Time
age := blockTime.Sub(evidence.GetTime())

if _, err := k.slashingKeeper.GetPubkey(ctx, consAddr.Bytes()); 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
}

// 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",
consAddr, infractionHeight, age, k.MaxEvidenceAge(ctx),
),
)
return
}

validator := k.stakingKeeper.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
}

if ok := k.slashingKeeper.HasValidatorSigningInfo(ctx, consAddr); !ok {
panic(fmt.Sprintf("expected signing info for validator %s but not found", consAddr))
}

// ignore if the validator is already tombstoned
if k.slashingKeeper.IsTombstoned(ctx, consAddr) {
logger.Info(
fmt.Sprintf(
"ignored double sign from %s at height %d, validator already tombstoned",
consAddr, infractionHeight,
),
)
return
}

logger.Info(fmt.Sprintf("confirmed double sign from %s at height %d, age of %d", consAddr, 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

// Slash validator. The `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.
k.slashingKeeper.Slash(
ctx,
consAddr,
k.slashingKeeper.SlashFractionDoubleSign(ctx),
evidence.GetValidatorPower(), distributionHeight,
)

// Jail the validator if not already jailed. This will begin unbonding the
// validator if not already unbonding (tombstoned).
if !validator.IsJailed() {
k.slashingKeeper.Jail(ctx, consAddr)
}

k.slashingKeeper.JailUntil(ctx, consAddr, types.DoubleSignJailEndTime)
k.slashingKeeper.Tombstone(ctx, consAddr)
}
Loading

0 comments on commit 7953b52

Please sign in to comment.