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

R4R: Implement slashing period #2122

Merged
merged 34 commits into from
Sep 1, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
cb2fa04
Update PENDING.md
cwgoes Aug 22, 2018
fedb717
SlashingPeriod struct
cwgoes Aug 22, 2018
23f8887
Seperate keys.go, constant prefixes
cwgoes Aug 22, 2018
ddce1cf
Make linter happy
cwgoes Aug 22, 2018
aee492d
Merge branch 'develop' into cwgoes/implement-slashing-period
cwgoes Aug 23, 2018
1a2f1bf
Merge branch 'develop' into cwgoes/implement-slashing-period
cwgoes Aug 23, 2018
8a66088
Merge branch 'develop' into cwgoes/implement-slashing-period
cwgoes Aug 24, 2018
3c59d43
Update Gopkg.lock
cwgoes Aug 24, 2018
bc392cf
Seek slashing period by infraction height
cwgoes Aug 24, 2018
fa8faad
Slashing period hooks
cwgoes Aug 24, 2018
23fbba7
Slashing period unit tests; bugfix
cwgoes Aug 27, 2018
8d2c74b
Add simple hook tests
cwgoes Aug 27, 2018
b111c75
Add sdk.ValidatorHooks interface
cwgoes Aug 27, 2018
dac51aa
No-op hooks
cwgoes Aug 27, 2018
a0f706a
Real hooks
cwgoes Aug 27, 2018
668c1ef
Merge branch 'develop' into cwgoes/implement-slashing-period
cwgoes Aug 27, 2018
35e8990
Fix iteration direction & duplicate key, update Gaia
cwgoes Aug 27, 2018
3534a99
Correctly simulate past validator set signatures
cwgoes Aug 27, 2018
111754a
Tiny rename
cwgoes Aug 27, 2018
5071897
Update dep; 'make format'
cwgoes Aug 27, 2018
d3ba71f
Add quick slashing period functionality test
cwgoes Aug 27, 2018
97cdfd8
Additional unit tests
cwgoes Aug 27, 2018
527bbbd
Use current validators when selected
cwgoes Aug 27, 2018
6ed552a
Panic in the right place
cwgoes Aug 27, 2018
3277257
Merge branch 'develop' into cwgoes/implement-slashing-period
cwgoes Aug 28, 2018
5ba559c
Merge branch 'develop' into cwgoes/implement-slashing-period
cwgoes Aug 28, 2018
14cb956
Merge branch 'develop' into cwgoes/implement-slashing-period
cwgoes Aug 29, 2018
d9ad52f
Address @rigelrozanski comments
cwgoes Aug 29, 2018
225cb34
Fix linter errors
cwgoes Aug 29, 2018
a349376
Address @melekes suggestion
cwgoes Aug 29, 2018
468aad6
Rename hook
cwgoes Aug 31, 2018
54d78b3
Merge branch 'develop' into cwgoes/implement-slashing-period
cwgoes Aug 31, 2018
05ede6c
Update for new bech32 types
cwgoes Aug 31, 2018
c257ac8
'make format'
cwgoes Aug 31, 2018
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
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
14 changes: 7 additions & 7 deletions server/export_test.go
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
package server

import (
"testing"
"github.com/stretchr/testify/require"
"bytes"
"github.com/cosmos/cosmos-sdk/server/mock"
"github.com/cosmos/cosmos-sdk/wire"
"github.com/tendermint/tendermint/libs/log"
"github.com/stretchr/testify/require"
tcmd "github.com/tendermint/tendermint/cmd/tendermint/commands"
"os"
"bytes"
"github.com/tendermint/tendermint/libs/log"
"io"
"github.com/cosmos/cosmos-sdk/server/mock"
)
"os"
"testing"
)

func TestEmptyState(t *testing.T) {
defer setupViper(t)()
Expand Down
2 changes: 1 addition & 1 deletion server/mock/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ func AppGenStateEmpty(_ *wire.Codec, _ []json.RawMessage) (appState json.RawMess

// Return a validator, not much else
func AppGenTx(_ *wire.Codec, pk crypto.PubKey, genTxConfig gc.GenTx) (
appGenTx, cliPrint json.RawMessage, validator tmtypes.GenesisValidator, err error) {
appGenTx, cliPrint json.RawMessage, validator tmtypes.GenesisValidator, err error) {

validator = tmtypes.GenesisValidator{
PubKey: pk,
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 @@ -95,3 +95,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, 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 @@ -30,7 +30,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())

// Signing info must exist
info, found := k.getValidatorSigningInfo(ctx, addr)
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 @@ -19,7 +19,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
}
rigelrozanski marked this conversation as resolved.
Show resolved Hide resolved

// 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