Skip to content

Commit

Permalink
Merge PR #2122: Implement slashing period
Browse files Browse the repository at this point in the history
* Update PENDING.md

* SlashingPeriod struct

* Seperate keys.go, constant prefixes

* Make linter happy

* Update Gopkg.lock

* Seek slashing period by infraction height

* Slashing period hooks

* Slashing period unit tests; bugfix

* Add simple hook tests

* Add sdk.ValidatorHooks interface

* No-op hooks

* Real hooks

* Fix iteration direction & duplicate key, update Gaia

* Correctly simulate past validator set signatures

* Tiny rename

* Update dep; 'make format'

* Add quick slashing period functionality test

* Additional unit tests

* Use current validators when selected

* Panic in the right place

* Address @rigelrozanski comments

* Fix linter errors

* Address @melekes suggestion

* Rename hook

* Update for new bech32 types

* 'make format'
  • Loading branch information
cwgoes authored and rigelrozanski committed Sep 1, 2018
1 parent e1981d4 commit 1204857
Show file tree
Hide file tree
Showing 25 changed files with 494 additions and 85 deletions.
1 change: 1 addition & 0 deletions PENDING.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ BREAKING CHANGES
* SDK
* [core] \#1807 Switch from use of rational to decimal
* [types] \#1901 Validator interface's GetOwner() renamed to GetOperator()
* [x/slashing] [#2122](https://github.com/cosmos/cosmos-sdk/pull/2122) - Implement slashing period
* [types] \#2119 Parsed error messages and ABCI log errors to make them more human readable.
* [simulation] Rename TestAndRunTx to Operation [#2153](https://github.com/cosmos/cosmos-sdk/pull/2153)

Expand Down
3 changes: 2 additions & 1 deletion cmd/gaia/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,9 +93,10 @@ func NewGaiaApp(logger log.Logger, db dbm.DB, traceStore io.Writer, baseAppOptio
app.ibcMapper = ibc.NewMapper(app.cdc, app.keyIBC, app.RegisterCodespace(ibc.DefaultCodespace))
app.paramsKeeper = params.NewKeeper(app.cdc, app.keyParams)
app.stakeKeeper = stake.NewKeeper(app.cdc, app.keyStake, app.coinKeeper, app.RegisterCodespace(stake.DefaultCodespace))
app.slashingKeeper = slashing.NewKeeper(app.cdc, app.keySlashing, app.stakeKeeper, app.paramsKeeper.Getter(), app.RegisterCodespace(slashing.DefaultCodespace))
app.stakeKeeper = app.stakeKeeper.WithValidatorHooks(app.slashingKeeper.ValidatorHooks())
app.govKeeper = gov.NewKeeper(app.cdc, app.keyGov, app.paramsKeeper.Setter(), app.coinKeeper, app.stakeKeeper, app.RegisterCodespace(gov.DefaultCodespace))
app.feeCollectionKeeper = auth.NewFeeCollectionKeeper(app.cdc, app.keyFeeCollection)
app.slashingKeeper = slashing.NewKeeper(app.cdc, app.keySlashing, app.stakeKeeper, app.paramsKeeper.Getter(), app.RegisterCodespace(slashing.DefaultCodespace))

// register message routes
app.Router().
Expand Down
2 changes: 1 addition & 1 deletion server/tm_cmds.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ import (
"github.com/spf13/cobra"
"github.com/spf13/viper"

"github.com/cosmos/cosmos-sdk/client"
sdk "github.com/cosmos/cosmos-sdk/types"
tcmd "github.com/tendermint/tendermint/cmd/tendermint/commands"
"github.com/tendermint/tendermint/p2p"
pvm "github.com/tendermint/tendermint/privval"
"github.com/cosmos/cosmos-sdk/client"
)

// ShowNodeIDCmd - ported from Tendermint, dump node ID to stdout
Expand Down
8 changes: 8 additions & 0 deletions types/decimal.go
Original file line number Diff line number Diff line change
Expand Up @@ -415,6 +415,14 @@ func MinDec(d1, d2 Dec) Dec {
return d2
}

// maximum decimal between two
func MaxDec(d1, d2 Dec) Dec {
if d1.LT(d2) {
return d2
}
return d1
}

// intended to be used with require/assert: require.True(DecEq(...))
func DecEq(t *testing.T, exp, got Dec) (*testing.T, bool, string, Dec, Dec) {
return t, exp.Equal(got), "expected:\t%v\ngot:\t\t%v", exp, got
Expand Down
10 changes: 10 additions & 0 deletions types/stake.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,3 +99,13 @@ type DelegationSet interface {
IterateDelegations(ctx Context, delegator AccAddress,
fn func(index int64, delegation Delegation) (stop bool))
}

// validator event hooks
// These can be utilized to communicate between a staking keeper
// and another keeper which must take particular actions when
// validators are bonded and unbonded. The second keeper must implement
// this interface, which then the staking keeper can call.
type ValidatorHooks interface {
OnValidatorBonded(ctx Context, address ConsAddress) // Must be called when a validator is bonded
OnValidatorBeginUnbonding(ctx Context, address ConsAddress) // Must be called when a validator begins unbonding
}
54 changes: 30 additions & 24 deletions x/mock/simulation/random_simulate_blocks.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,16 +67,18 @@ func SimulateFromSeed(
header := abci.Header{Height: 0, Time: timestamp}
opCount := 0

request := abci.RequestBeginBlock{Header: header}

var pastTimes []time.Time
var pastSigningValidators [][]abci.SigningValidator

request := RandomRequestBeginBlock(t, r, validators, livenessTransitionMatrix, evidenceFraction, pastTimes, pastSigningValidators, event, header, log)
// These are operations which have been queued by previous operations
operationQueue := make(map[int][]Operation)

for i := 0; i < numBlocks; i++ {

// Log the header time for future lookup
pastTimes = append(pastTimes, header.Time)
pastSigningValidators = append(pastSigningValidators, request.LastCommitInfo.Validators)

// Run the BeginBlock handler
app.BeginBlock(request)
Expand Down Expand Up @@ -131,7 +133,7 @@ func SimulateFromSeed(
}

// Generate a random RequestBeginBlock with the current validator set for the next block
request = RandomRequestBeginBlock(t, r, validators, livenessTransitionMatrix, evidenceFraction, pastTimes, event, header, log)
request = RandomRequestBeginBlock(t, r, validators, livenessTransitionMatrix, evidenceFraction, pastTimes, pastSigningValidators, event, header, log)

// Update the validator set
validators = updateValidators(t, r, validators, res.ValidatorUpdates, event)
Expand Down Expand Up @@ -187,13 +189,12 @@ func getKeys(validators map[string]mockValidator) []string {

// RandomRequestBeginBlock generates a list of signing validators according to the provided list of validators, signing fraction, and evidence fraction
func RandomRequestBeginBlock(t *testing.T, r *rand.Rand, validators map[string]mockValidator, livenessTransitions TransitionMatrix, evidenceFraction float64,
pastTimes []time.Time, event func(string), header abci.Header, log string) abci.RequestBeginBlock {
pastTimes []time.Time, pastSigningValidators [][]abci.SigningValidator, event func(string), header abci.Header, log string) abci.RequestBeginBlock {
if len(validators) == 0 {
return abci.RequestBeginBlock{Header: header}
}
signingValidators := make([]abci.SigningValidator, len(validators))
i := 0

for _, key := range getKeys(validators) {
mVal := validators[key]
mVal.livenessState = livenessTransitions.NextState(r, mVal.livenessState)
Expand All @@ -220,26 +221,31 @@ func RandomRequestBeginBlock(t *testing.T, r *rand.Rand, validators map[string]m
i++
}
evidence := make([]abci.Evidence, 0)
for r.Float64() < evidenceFraction {
height := header.Height
time := header.Time
if r.Float64() < pastEvidenceFraction {
height = int64(r.Intn(int(header.Height)))
time = pastTimes[height]
}
validator := signingValidators[r.Intn(len(signingValidators))].Validator
var currentTotalVotingPower int64
for _, mVal := range validators {
currentTotalVotingPower += mVal.val.Power
// Anything but the first block
if len(pastTimes) > 0 {
for r.Float64() < evidenceFraction {
height := header.Height
time := header.Time
vals := signingValidators
if r.Float64() < pastEvidenceFraction {
height = int64(r.Intn(int(header.Height)))
time = pastTimes[height]
vals = pastSigningValidators[height]
}
validator := vals[r.Intn(len(vals))].Validator
var totalVotingPower int64
for _, val := range vals {
totalVotingPower += val.Validator.Power
}
evidence = append(evidence, abci.Evidence{
Type: tmtypes.ABCIEvidenceTypeDuplicateVote,
Validator: validator,
Height: height,
Time: time,
TotalVotingPower: totalVotingPower,
})
event("beginblock/evidence")
}
evidence = append(evidence, abci.Evidence{
Type: tmtypes.ABCIEvidenceTypeDuplicateVote,
Validator: validator,
Height: height,
Time: time,
TotalVotingPower: currentTotalVotingPower,
})
event("beginblock/evidence")
}
return abci.RequestBeginBlock{
Header: header,
Expand Down
4 changes: 2 additions & 2 deletions x/slashing/app_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ func checkValidator(t *testing.T, mapp *mock.App, keeper stake.Keeper,
}

func checkValidatorSigningInfo(t *testing.T, mapp *mock.App, keeper Keeper,
addr sdk.ValAddress, expFound bool) ValidatorSigningInfo {
addr sdk.ConsAddress, expFound bool) ValidatorSigningInfo {
ctxCheck := mapp.BaseApp.NewContext(true, abci.Header{})
signingInfo, found := keeper.getValidatorSigningInfo(ctxCheck, addr)
require.Equal(t, expFound, found)
Expand Down Expand Up @@ -113,7 +113,7 @@ func TestSlashingMsgs(t *testing.T) {
unjailMsg := MsgUnjail{ValidatorAddr: sdk.ValAddress(validator.PubKey.Address())}

// no signing info yet
checkValidatorSigningInfo(t, mapp, keeper, sdk.ValAddress(addr1), false)
checkValidatorSigningInfo(t, mapp, keeper, sdk.ConsAddress(addr1), false)

// unjail should fail with unknown validator
res := mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{unjailMsg}, []int64{0}, []int64{1}, false, false, priv1)
Expand Down
2 changes: 1 addition & 1 deletion x/slashing/client/cli/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ func GetCmdQuerySigningInfo(storeName string, cdc *wire.Codec) *cobra.Command {
return err
}

key := slashing.GetValidatorSigningInfoKey(sdk.ValAddress(pk.Address()))
key := slashing.GetValidatorSigningInfoKey(sdk.ConsAddress(pk.Address()))
cliCtx := context.NewCLIContext().WithCodec(cdc)

res, err := cliCtx.QueryStore(key, storeName)
Expand Down
2 changes: 1 addition & 1 deletion x/slashing/client/rest/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ func signingInfoHandlerFn(cliCtx context.CLIContext, storeName string, cdc *wire
return
}

key := slashing.GetValidatorSigningInfoKey(sdk.ValAddress(pk.Address()))
key := slashing.GetValidatorSigningInfoKey(sdk.ConsAddress(pk.Address()))

res, err := cliCtx.QueryStore(key, storeName)
if err != nil {
Expand Down
2 changes: 1 addition & 1 deletion x/slashing/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ func handleMsgUnjail(ctx sdk.Context, msg MsgUnjail, k Keeper) sdk.Result {
return ErrValidatorNotJailed(k.codespace).Result()
}

addr := sdk.ValAddress(validator.GetPubKey().Address())
addr := sdk.ConsAddress(validator.GetPubKey().Address())

info, found := k.getValidatorSigningInfo(ctx, addr)
if !found {
Expand Down
2 changes: 1 addition & 1 deletion x/slashing/handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ func TestCannotUnjailUnlessJailed(t *testing.T) {
got := stake.NewHandler(sk)(ctx, msg)
require.True(t, got.IsOK())
stake.EndBlocker(ctx, sk)
require.Equal(t, ck.GetCoins(ctx, addr), sdk.Coins{{sk.GetParams(ctx).BondDenom, initCoins.Sub(amt)}})
require.Equal(t, ck.GetCoins(ctx, sdk.AccAddress(addr)), sdk.Coins{{sk.GetParams(ctx).BondDenom, initCoins.Sub(amt)}})
require.True(t, sdk.NewDecFromInt(amt).Equal(sk.Validator(ctx, sdk.ValAddress(addr)).GetPower()))

// assert non-jailed validator can't be unjailed
Expand Down
46 changes: 46 additions & 0 deletions x/slashing/hooks.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package slashing

import (
sdk "github.com/cosmos/cosmos-sdk/types"
)

// Create a new slashing period when a validator is bonded
func (k Keeper) onValidatorBonded(ctx sdk.Context, address sdk.ConsAddress) {
slashingPeriod := ValidatorSlashingPeriod{
ValidatorAddr: address,
StartHeight: ctx.BlockHeight(),
EndHeight: 0,
SlashedSoFar: sdk.ZeroDec(),
}
k.addOrUpdateValidatorSlashingPeriod(ctx, slashingPeriod)
}

// Mark the slashing period as having ended when a validator begins unbonding
func (k Keeper) onValidatorBeginUnbonding(ctx sdk.Context, address sdk.ConsAddress) {
slashingPeriod := k.getValidatorSlashingPeriodForHeight(ctx, address, ctx.BlockHeight())
slashingPeriod.EndHeight = ctx.BlockHeight()
k.addOrUpdateValidatorSlashingPeriod(ctx, slashingPeriod)
}

// Wrapper struct for sdk.ValidatorHooks
type ValidatorHooks struct {
k Keeper
}

// Assert implementation
var _ sdk.ValidatorHooks = ValidatorHooks{}

// Return a sdk.ValidatorHooks interface over the wrapper struct
func (k Keeper) ValidatorHooks() sdk.ValidatorHooks {
return ValidatorHooks{k}
}

// Implements sdk.ValidatorHooks
func (v ValidatorHooks) OnValidatorBonded(ctx sdk.Context, address sdk.ConsAddress) {
v.k.onValidatorBonded(ctx, address)
}

// Implements sdk.ValidatorHooks
func (v ValidatorHooks) OnValidatorBeginUnbonding(ctx sdk.Context, address sdk.ConsAddress) {
v.k.onValidatorBeginUnbonding(ctx, address)
}
26 changes: 26 additions & 0 deletions x/slashing/hooks_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package slashing

import (
"testing"

"github.com/stretchr/testify/require"

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

func TestHookOnValidatorBonded(t *testing.T) {
ctx, _, _, _, keeper := createTestInput(t)
addr := sdk.ConsAddress(addrs[0])
keeper.onValidatorBonded(ctx, addr)
period := keeper.getValidatorSlashingPeriodForHeight(ctx, addr, ctx.BlockHeight())
require.Equal(t, ValidatorSlashingPeriod{addr, ctx.BlockHeight(), 0, sdk.ZeroDec()}, period)
}

func TestHookOnValidatorBeginUnbonding(t *testing.T) {
ctx, _, _, _, keeper := createTestInput(t)
addr := sdk.ConsAddress(addrs[0])
keeper.onValidatorBonded(ctx, addr)
keeper.onValidatorBeginUnbonding(ctx, addr)
period := keeper.getValidatorSlashingPeriodForHeight(ctx, addr, ctx.BlockHeight())
require.Equal(t, ValidatorSlashingPeriod{addr, ctx.BlockHeight(), ctx.BlockHeight(), sdk.ZeroDec()}, period)
}
16 changes: 9 additions & 7 deletions x/slashing/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ func (k Keeper) handleDoubleSign(ctx sdk.Context, addr crypto.Address, infractio
logger := ctx.Logger().With("module", "x/slashing")
time := ctx.BlockHeader().Time
age := time.Sub(timestamp)
address := sdk.ValAddress(addr)
address := sdk.ConsAddress(addr)
pubkey, err := k.getPubkey(ctx, addr)
if err != nil {
panic(fmt.Sprintf("Validator address %v not found", addr))
Expand All @@ -56,8 +56,14 @@ func (k Keeper) handleDoubleSign(ctx sdk.Context, addr crypto.Address, infractio
// Double sign confirmed
logger.Info(fmt.Sprintf("Confirmed double sign from %s at height %d, age of %d less than max age of %d", pubkey.Address(), infractionHeight, age, maxEvidenceAge))

// Cap the amount slashed to the penalty for the worst infraction
// within the slashing period when this infraction was committed
fraction := k.SlashFractionDoubleSign(ctx)
revisedFraction := k.capBySlashingPeriod(ctx, address, fraction, infractionHeight)
logger.Info(fmt.Sprintf("Fraction slashed capped by slashing period from %v to %v", fraction, revisedFraction))

// Slash validator
k.validatorSet.Slash(ctx, pubkey, infractionHeight, power, k.SlashFractionDoubleSign(ctx))
k.validatorSet.Slash(ctx, pubkey, infractionHeight, power, revisedFraction)

// Jail validator
k.validatorSet.Jail(ctx, pubkey)
Expand All @@ -76,7 +82,7 @@ func (k Keeper) handleDoubleSign(ctx sdk.Context, addr crypto.Address, infractio
func (k Keeper) handleValidatorSignature(ctx sdk.Context, addr crypto.Address, power int64, signed bool) {
logger := ctx.Logger().With("module", "x/slashing")
height := ctx.BlockHeight()
address := sdk.ValAddress(addr)
address := sdk.ConsAddress(addr)
pubkey, err := k.getPubkey(ctx, addr)
if err != nil {
panic(fmt.Sprintf("Validator address %v not found", addr))
Expand Down Expand Up @@ -169,7 +175,3 @@ func (k Keeper) deleteAddrPubkeyRelation(ctx sdk.Context, addr crypto.Address) {
store := ctx.KVStore(k.storeKey)
store.Delete(getAddrPubkeyRelationKey(addr))
}

func getAddrPubkeyRelationKey(address []byte) []byte {
return append([]byte{0x03}, address...)
}
Loading

0 comments on commit 1204857

Please sign in to comment.