From 082169c4bba4ad0c114c1a1dfa1b61dd6d0b4b86 Mon Sep 17 00:00:00 2001 From: NathanBSC Date: Mon, 17 Apr 2023 17:41:22 +0800 Subject: [PATCH] handle malicious vote slash as double sign --- types/stake.go | 3 +- x/slashing/errors.go | 24 +++++-- x/slashing/keeper.go | 124 ++++++++++++++++++++++++++++++++---- x/slashing/slash_record.go | 1 + x/slashing/types.go | 12 +++- x/stake/keeper/sdk_types.go | 9 +++ 6 files changed, 149 insertions(+), 24 deletions(-) diff --git a/types/stake.go b/types/stake.go index c1ce835d6..904fcebdc 100644 --- a/types/stake.go +++ b/types/stake.go @@ -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: @@ -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 diff --git a/x/slashing/errors.go b/x/slashing/errors.go index 6bd3145e0..0bbaed59d 100644 --- a/x/slashing/errors.go +++ b/x/slashing/errors.go @@ -1,8 +1,9 @@ -//nolint +// nolint package slashing import ( "fmt" + sdk "github.com/cosmos/cosmos-sdk/types" ) @@ -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") } @@ -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) } diff --git a/x/slashing/keeper.go b/x/slashing/keeper.go index abaf21e95..6e3eb3d6c 100644 --- a/x/slashing/keeper.go +++ b/x/slashing/keeper.go @@ -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()) @@ -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 { @@ -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) @@ -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()) } @@ -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()) } @@ -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, @@ -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{ @@ -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.GetSideChainConsAddr()) + 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()) + } + + var toFeePool int64 + var validatorsCompensation map[string]int64 + var found bool + if slashAmt > 0 { + found, validatorsCompensation, err = k.validatorSet.AllocateSlashAmtToValidators(sideCtx, sideConsAddr, sdk.NewDec(slashAmt)) + if err != nil { + return ErrFailedToSlash(k.Codespace, err.Error()) + } + if !found && ctx.IsDeliverTx() { + bondDenom := k.validatorSet.BondDenom(sideCtx) + toFeePool = slashAmt + remainingCoin := sdk.NewCoin(bondDenom, slashAmt) + fees.Pool.AddAndCommitFee("side_malicious_vote_slash", sdk.NewFee(sdk.Coins{remainingCoin}, sdk.FeeForAll)) + } + } + + 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: validatorsCompensation, + } + 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() diff --git a/x/slashing/slash_record.go b/x/slashing/slash_record.go index 75ae3f86a..cd5fabe95 100644 --- a/x/slashing/slash_record.go +++ b/x/slashing/slash_record.go @@ -13,6 +13,7 @@ import ( const ( DoubleSign byte = iota Downtime + MalicousVote ) type SlashRecord struct { diff --git a/x/slashing/types.go b/x/slashing/types.go index a985610b0..5448f730c 100644 --- a/x/slashing/types.go +++ b/x/slashing/types.go @@ -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 = iota + 1 + SideVoteAddrType +) + +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:"-"` } diff --git a/x/stake/keeper/sdk_types.go b/x/stake/keeper/sdk_types.go index 070f87e29..f8c0b80c4 100644 --- a/x/stake/keeper/sdk_types.go +++ b/x/stake/keeper/sdk_types.go @@ -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)