Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrate Equivocation Handling to x/evidence #5299

Merged
merged 36 commits into from
Dec 3, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
ffaf86e
start migration of equivocation handling
alexanderbez Nov 8, 2019
3abff13
Migrate max evidence params from slashing to evidence module
alexanderbez Nov 27, 2019
ee1de08
Move DoubleSignJailEndTime to the evidence module
alexanderbez Nov 27, 2019
0b29de6
Update Equivocation type
alexanderbez Nov 27, 2019
cb627f2
Add expected keepers to evidence module
alexanderbez Nov 27, 2019
cb2f029
Move HandleDoubleSign from the slashing to the evidence module
alexanderbez Nov 27, 2019
94c44c3
Implement auxiliary slashing keeper methods
alexanderbez Nov 27, 2019
a745897
Add JailUntil to the expected slashing keeper
alexanderbez Nov 27, 2019
f58d5a6
Update evidence Keeper along with constructor
alexanderbez Nov 27, 2019
e7d7fbe
Update evidence BeginBlocker
alexanderbez Nov 27, 2019
8099290
Update evidence BeginBlocker
alexanderbez Nov 27, 2019
994a196
Call BeginBlocker in AppModule#BeginBlock
alexanderbez Nov 27, 2019
e74bc38
Add evidence to SimApp's SetOrderBeginBlockers
alexanderbez Nov 27, 2019
ecf15a1
linting...
alexanderbez Nov 27, 2019
32b7fe9
remove err check MarshalYAML
alexanderbez Nov 27, 2019
c76608c
update params module to support HasKeyTable
alexanderbez Nov 27, 2019
14cd46a
update evidence keeper constructor usage
alexanderbez Nov 27, 2019
e96c0d2
add params test
alexanderbez Nov 27, 2019
d90e037
fix slashing module build
alexanderbez Nov 27, 2019
f073c17
linting and alias and updates
alexanderbez Nov 27, 2019
1fafe7d
migrate unit tests
alexanderbez Nov 28, 2019
fb7d80b
fix build & tests
alexanderbez Nov 28, 2019
2a49151
add signing info unit tests
alexanderbez Nov 28, 2019
982035b
deference pointer
alexanderbez Nov 28, 2019
0770aab
Merge branch 'master' into bez/5296-evidence-slashing
alexanderbez Nov 28, 2019
4f5d7d3
add changelog entry
alexanderbez Nov 28, 2019
781320e
update specs
alexanderbez Nov 28, 2019
6b39d43
add support for params querying
alexanderbez Nov 28, 2019
02ea53c
Merge branch 'master' into bez/5296-evidence-slashing
alexanderbez Dec 1, 2019
9a0bf93
add check for time in ValidateBasic
alexanderbez Dec 2, 2019
4381437
move methods around
alexanderbez Dec 2, 2019
2afa9b2
Add godoc for BeginBlocker
alexanderbez Dec 2, 2019
3acc0ce
Update Slash to take a fraction
alexanderbez Dec 2, 2019
e915ac4
Merge branch 'master' into bez/5296-evidence-slashing
alexanderbez Dec 2, 2019
69c3953
Update x/evidence/internal/types/evidence.go
alexanderbez Dec 2, 2019
0ab897c
Update x/evidence/client/rest/query.go
alexanderbez Dec 2, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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) {
alexanderbez marked this conversation as resolved.
Show resolved Hide resolved
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) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

q: shouldn't every evidence type define its own evidence age instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think so because all evidence must be within the unbonding period. Perhaps certain evidence can have even smaller valid periods (e.g. 1/2 unbonding period), but I don't see any immediate use-cases or evidence types that need this.

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