Skip to content

Commit

Permalink
handle malicious vote slash as double sign
Browse files Browse the repository at this point in the history
  • Loading branch information
NathanBSC committed Apr 18, 2023
1 parent 9ecc0a0 commit 6ffe958
Show file tree
Hide file tree
Showing 6 changed files with 149 additions and 24 deletions.
3 changes: 2 additions & 1 deletion types/stake.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const (
Bonded BondStatus = 0x02
)

//BondStatusToString for pretty prints of Bond Status
// BondStatusToString for pretty prints of Bond Status
func BondStatusToString(b BondStatus) string {
switch b {
case 0x00:
Expand Down Expand Up @@ -73,6 +73,7 @@ type ValidatorSet interface {

Validator(Context, ValAddress) Validator // get a particular validator by operator address
ValidatorByConsAddr(Context, ConsAddress) Validator // get a particular validator by consensus address
ValidatorByVoteAddr(Context, []byte) Validator // get a particular validator by vote address
TotalPower(Context) Dec // total power of the validator set

// slash the validator and delegators of the validator, specifying offence height, offence power, and slash fraction
Expand Down
24 changes: 17 additions & 7 deletions x/slashing/errors.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
//nolint
// nolint
package slashing

import (
"fmt"

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

Expand All @@ -21,18 +22,23 @@ const (
CodeSelfDelegationTooLowToUnjail CodeType = 105
CodeInvalidClaim CodeType = 106

CodeExpiredEvidence CodeType = 201
CodeFailSlash CodeType = 202
CodeHandledEvidence CodeType = 203
CodeInvalidEvidence CodeType = 204
CodeInvalidSideChain CodeType = 205
CodeDuplicateDowntimeClaim CodeType = 206
CodeExpiredEvidence CodeType = 201
CodeFailSlash CodeType = 202
CodeHandledEvidence CodeType = 203
CodeInvalidEvidence CodeType = 204
CodeInvalidSideChain CodeType = 205
CodeDuplicateDowntimeClaim CodeType = 206
CodeDuplicateMalicousVoteClaim CodeType = 207
)

func ErrNoValidatorForAddress(codespace sdk.CodespaceType) sdk.Error {
return sdk.NewError(codespace, CodeInvalidValidator, "that address is not associated with any known validator")
}

func ErrNoValidatorWithVoteAddr(codespace sdk.CodespaceType) sdk.Error {
return sdk.NewError(codespace, CodeInvalidValidator, "that vote 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")
}
Expand Down Expand Up @@ -81,6 +87,10 @@ func ErrDuplicateDowntimeClaim(codespace sdk.CodespaceType) sdk.Error {
return sdk.NewError(codespace, CodeDuplicateDowntimeClaim, "duplicate downtime claim")
}

func ErrDuplicateMalicousVoteClaim(codespace sdk.CodespaceType) sdk.Error {
return sdk.NewError(codespace, CodeDuplicateMalicousVoteClaim, "duplicate malicious vote claim")
}

func ErrInvalidInput(codespace sdk.CodespaceType, msg string) sdk.Error {
return sdk.NewError(codespace, CodeInvalidInput, msg)
}
124 changes: 110 additions & 14 deletions x/slashing/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -237,9 +237,13 @@ func (k *Keeper) SubscribeParamChange(hub types.ParamChangePublisher) {
// implement cross chain app
func (k *Keeper) ExecuteSynPackage(ctx sdk.Context, payload []byte, _ int64) sdk.ExecuteResult {
var resCode uint32
pack, err := k.checkSideDowntimeSlashPackage(payload)
sideSlashPack, err := k.checkSideSlashPackage(payload)
if err == nil {
err = k.slashingSideDowntime(ctx, pack)
if sideSlashPack.addrType == SideConsAddrType {
err = k.slashingSideDowntime(ctx, sideSlashPack)
} else if sideSlashPack.addrType == SideVoteAddrType {
err = k.slashingSideMalicousVote(ctx, sideSlashPack)
}
}
if err != nil {
resCode = uint32(err.ABCICode())
Expand All @@ -263,14 +267,19 @@ func (k *Keeper) ExecuteFailAckPackage(ctx sdk.Context, payload []byte) sdk.Exec
panic("receive unexpected fail ack package")
}

func (k *Keeper) checkSideDowntimeSlashPackage(payload []byte) (*SideDowntimeSlashPackage, sdk.Error) {
var slashEvent SideDowntimeSlashPackage
func (k *Keeper) checkSideSlashPackage(payload []byte) (*SideSlashPackage, sdk.Error) {
var slashEvent SideSlashPackage
err := rlp.DecodeBytes(payload, &slashEvent)
if err != nil {
return nil, ErrInvalidInput(k.Codespace, "failed to parse the payload")
}
if len(slashEvent.SideConsAddr) != sdk.AddrLen {
return nil, ErrInvalidClaim(k.Codespace, fmt.Sprintf("wrong sideConsAddr length, expected=%d", slashEvent.SideConsAddr))

if len(slashEvent.SideAddr) == sdk.AddrLen {
slashEvent.addrType = SideConsAddrType
} else if len(slashEvent.SideAddr) == sdk.VoteAddrLen {
slashEvent.addrType = SideVoteAddrType
} else {
return nil, ErrInvalidClaim(k.Codespace, fmt.Sprintf("wrong sideAddr length:%d, expected:%d or %d", len(slashEvent.SideAddr), sdk.AddrLen, sdk.VoteAddrLen))
}

if slashEvent.SideHeight <= 0 {
Expand All @@ -287,7 +296,8 @@ func (k *Keeper) checkSideDowntimeSlashPackage(payload []byte) (*SideDowntimeSla
return &slashEvent, nil
}

func (k *Keeper) slashingSideDowntime(ctx sdk.Context, pack *SideDowntimeSlashPackage) sdk.Error {
func (k *Keeper) slashingSideDowntime(ctx sdk.Context, pack *SideSlashPackage) sdk.Error {
sideConsAddr := pack.SideAddr
sideChainName, err := k.ScKeeper.GetDestChainName(pack.SideChainId)
if err != nil {
return ErrInvalidSideChainId(DefaultCodespace)
Expand All @@ -303,12 +313,12 @@ func (k *Keeper) slashingSideDowntime(ctx sdk.Context, pack *SideDowntimeSlashPa
return ErrExpiredEvidence(DefaultCodespace)
}

if k.hasSlashRecord(sideCtx, pack.SideConsAddr, Downtime, pack.SideHeight) {
if k.hasSlashRecord(sideCtx, sideConsAddr, Downtime, pack.SideHeight) {
return ErrDuplicateDowntimeClaim(k.Codespace)
}

slashAmt := k.DowntimeSlashAmount(sideCtx)
validator, slashedAmt, err := k.validatorSet.SlashSideChain(ctx, sideChainName, pack.SideConsAddr, sdk.NewDec(slashAmt))
validator, slashedAmt, err := k.validatorSet.SlashSideChain(ctx, sideChainName, sideConsAddr, sdk.NewDec(slashAmt))
if err != nil {
return ErrFailedToSlash(k.Codespace, err.Error())
}
Expand All @@ -327,7 +337,7 @@ func (k *Keeper) slashingSideDowntime(ctx sdk.Context, pack *SideDowntimeSlashPa
var validatorsAllocatedAmt map[string]int64
var found bool
if remaining > 0 {
found, validatorsAllocatedAmt, err = k.validatorSet.AllocateSlashAmtToValidators(sideCtx, pack.SideConsAddr, sdk.NewDec(remaining))
found, validatorsAllocatedAmt, err = k.validatorSet.AllocateSlashAmtToValidators(sideCtx, sideConsAddr, sdk.NewDec(remaining))
if err != nil {
return ErrFailedToSlash(k.Codespace, err.Error())
}
Expand All @@ -340,7 +350,7 @@ func (k *Keeper) slashingSideDowntime(ctx sdk.Context, pack *SideDowntimeSlashPa

jailUntil := header.Time.Add(k.DowntimeUnbondDuration(sideCtx))
sr := SlashRecord{
ConsAddr: pack.SideConsAddr,
ConsAddr: sideConsAddr,
InfractionType: Downtime,
InfractionHeight: pack.SideHeight,
SlashHeight: header.Height,
Expand All @@ -351,12 +361,12 @@ func (k *Keeper) slashingSideDowntime(ctx sdk.Context, pack *SideDowntimeSlashPa
k.setSlashRecord(sideCtx, sr)

// Set or updated validator jail duration
signInfo, found := k.getValidatorSigningInfo(sideCtx, pack.SideConsAddr)
signInfo, found := k.getValidatorSigningInfo(sideCtx, sideConsAddr)
if !found {
return sdk.ErrInternal(fmt.Sprintf("Expected signing info for validator %s but not found", sdk.HexEncode(pack.SideConsAddr)))
return sdk.ErrInternal(fmt.Sprintf("Expected signing info for validator %s but not found", sdk.HexEncode(sideConsAddr)))
}
signInfo.JailedUntil = jailUntil
k.setValidatorSigningInfo(sideCtx, pack.SideConsAddr, signInfo)
k.setValidatorSigningInfo(sideCtx, sideConsAddr, signInfo)

if k.PbsbServer != nil {
event := SideSlashEvent{
Expand All @@ -376,6 +386,92 @@ func (k *Keeper) slashingSideDowntime(ctx sdk.Context, pack *SideDowntimeSlashPa
return nil
}

func (k *Keeper) slashingSideMalicousVote(ctx sdk.Context, pack *SideSlashPackage) sdk.Error {
sideVoteAddr := pack.SideAddr
sideChainName, err := k.ScKeeper.GetDestChainName(pack.SideChainId)
if err != nil {
return ErrInvalidSideChainId(DefaultCodespace)
}
sideCtx, err := k.ScKeeper.PrepareCtxForSideChain(ctx, sideChainName)
if err != nil {
return ErrInvalidSideChainId(DefaultCodespace)
}

header := sideCtx.BlockHeader()
age := uint64(header.Time.Unix()) - pack.SideTimestamp
if age > uint64(k.MaxEvidenceAge(sideCtx).Seconds()) {
return ErrExpiredEvidence(DefaultCodespace)
}

validator := k.validatorSet.ValidatorByVoteAddr(sideCtx, sideVoteAddr)
if validator == nil {
return ErrNoValidatorWithVoteAddr(k.Codespace)
}
sideConsAddr := []byte(validator.GetConsAddr())
if k.hasSlashRecord(sideCtx, sideConsAddr, MalicousVote, pack.SideHeight) {
return ErrDuplicateMalicousVoteClaim(k.Codespace)
}

slashAmt := k.DoubleSignSlashAmount(sideCtx)
validator, slashedAmt, err := k.validatorSet.SlashSideChain(ctx, sideChainName, sideConsAddr, sdk.NewDec(slashAmt))
if err != nil {
return ErrFailedToSlash(k.Codespace, err.Error())
}

bondDenom := k.validatorSet.BondDenom(sideCtx)
var toFeePool int64
var validatorsAllocatedAmt map[string]int64
var found bool
if slashAmt > 0 {
found, validatorsAllocatedAmt, err = k.validatorSet.AllocateSlashAmtToValidators(sideCtx, sideConsAddr, sdk.NewDec(slashAmt))
if err != nil {
return ErrFailedToSlash(k.Codespace, err.Error())
}
if !found && ctx.IsDeliverTx() {
remainingCoin := sdk.NewCoin(bondDenom, slashAmt)
fees.Pool.AddAndCommitFee("side_malicious_vote_slash", sdk.NewFee(sdk.Coins{remainingCoin}, sdk.FeeForAll))
toFeePool += slashAmt
}
}

jailUntil := header.Time.Add(k.DoubleSignUnbondDuration(sideCtx))
sr := SlashRecord{
ConsAddr: sideConsAddr,
InfractionType: MalicousVote,
InfractionHeight: pack.SideHeight,
SlashHeight: header.Height,
JailUntil: jailUntil,
SlashAmt: slashedAmt.RawInt(),
SideChainId: sideChainName,
}
k.setSlashRecord(sideCtx, sr)

// Set or updated validator jail duration
signInfo, found := k.getValidatorSigningInfo(sideCtx, sideConsAddr)
if !found {
return sdk.ErrInternal(fmt.Sprintf("Expected signing info for validator %s but not found", sdk.HexEncode(sideConsAddr)))
}
signInfo.JailedUntil = jailUntil
k.setValidatorSigningInfo(sideCtx, sideConsAddr, signInfo)

if k.PbsbServer != nil {
event := SideSlashEvent{
Validator: validator.GetOperator(),
InfractionType: MalicousVote,
InfractionHeight: int64(pack.SideHeight),
SlashHeight: header.Height,
JailUtil: jailUntil,
SlashAmt: slashedAmt.RawInt(),
ToFeePool: toFeePool,
SideChainId: sideChainName,
ValidatorsCompensation: validatorsAllocatedAmt,
}
k.PbsbServer.Publish(event)
}

return nil
}

// TODO: Make a method to remove the pubkey from the map when a validator is unbonded.
func (k Keeper) addPubkey(ctx sdk.Context, pubkey crypto.PubKey) {
addr := pubkey.Address()
Expand Down
1 change: 1 addition & 0 deletions x/slashing/slash_record.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
const (
DoubleSign byte = iota
Downtime
MalicousVote
)

type SlashRecord struct {
Expand Down
12 changes: 10 additions & 2 deletions x/slashing/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,17 @@ package slashing

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

type SideDowntimeSlashPackage struct {
SideConsAddr []byte `json:"side_cons_addr"`
type AddressType int

const (
SideConsAddrType AddressType = 1
SideVoteAddrType AddressType = 2
)

type SideSlashPackage struct {
SideAddr []byte `json:"side_addr"`
SideHeight uint64 `json:"side_height"`
SideChainId types.ChainID `json:"side_chain_id"`
SideTimestamp uint64 `json:"side_timestamp"`
addrType AddressType `rlp:"-"`
}
9 changes: 9 additions & 0 deletions x/stake/keeper/sdk_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,15 @@ func (k Keeper) ValidatorByConsAddr(ctx sdk.Context, addr sdk.ConsAddress) sdk.V
return val
}

// get the sdk.validator for a particular vote address
func (k Keeper) ValidatorByVoteAddr(ctx sdk.Context, VoteAddress []byte) sdk.Validator {
val, found := k.GetValidatorBySideVoteAddr(ctx, VoteAddress)
if !found {
return nil
}
return val
}

// get the sdk.validator for a particular consensus address
func (k Keeper) ValidatorBySideChainConsAddr(ctx sdk.Context, sideChainConsAddr []byte) sdk.Validator {
val, found := k.GetValidatorBySideConsAddr(ctx, sideChainConsAddr)
Expand Down

0 comments on commit 6ffe958

Please sign in to comment.