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

fix: verify equivocation using validator pubkey in SubmitConsumerDoubleVoting msg #1264

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
25 changes: 25 additions & 0 deletions tests/integration/double_vote.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package integration

import (
cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
testutil "github.com/cosmos/interchain-security/v2/testutil/crypto"
"github.com/cosmos/interchain-security/v2/x/ccv/provider/types"
"github.com/tendermint/tendermint/crypto"
tmtypes "github.com/tendermint/tendermint/types"
)

Expand Down Expand Up @@ -78,6 +80,7 @@ func (s *CCVTestSuite) TestHandleConsumerDoubleVoting() {
name string
ev *tmtypes.DuplicateVoteEvidence
chainID string
pubkey crypto.PubKey
expPass bool
}{
{
Expand All @@ -90,6 +93,20 @@ func (s *CCVTestSuite) TestHandleConsumerDoubleVoting() {
Timestamp: s.consumerCtx().BlockTime(),
},
"chainID",
consuVal.PubKey,
false,
},
{
"wrong public key - shouldn't pass",
&tmtypes.DuplicateVoteEvidence{
VoteA: consuVote,
VoteB: consuVote,
ValidatorPower: consuVal.VotingPower,
TotalVotingPower: consuVal.VotingPower,
Timestamp: s.consumerCtx().BlockTime(),
},
s.consumerChain.ChainID,
provVal.PubKey,
false,
},
{
Expand All @@ -103,6 +120,7 @@ func (s *CCVTestSuite) TestHandleConsumerDoubleVoting() {
Timestamp: s.consumerCtx().BlockTime(),
},
s.consumerChain.ChainID,
consuVal.PubKey,
false,
},
{
Expand All @@ -119,6 +137,7 @@ func (s *CCVTestSuite) TestHandleConsumerDoubleVoting() {
Timestamp: s.consumerCtx().BlockTime(),
},
s.consumerChain.ChainID,
consuVal.PubKey,
true,
},
{
Expand All @@ -132,6 +151,7 @@ func (s *CCVTestSuite) TestHandleConsumerDoubleVoting() {
Timestamp: s.consumerCtx().BlockTime(),
},
s.consumerChain.ChainID,
provVal.PubKey,
true,
},
}
Expand All @@ -151,10 +171,15 @@ func (s *CCVTestSuite) TestHandleConsumerDoubleVoting() {
s.providerApp.GetProviderKeeper().DeleteKeyAssignments(provCtx, s.consumerChain.ChainID)
}

// convert validator public key
pk, err := cryptocodec.FromTmPubKeyInterface(tc.pubkey)
s.Require().NoError(err)

err = s.providerApp.GetProviderKeeper().HandleConsumerDoubleVoting(
provCtx,
tc.ev,
tc.chainID,
pk,
)

if tc.expPass {
Expand Down
57 changes: 13 additions & 44 deletions x/ccv/provider/keeper/double_vote.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"bytes"
"fmt"

cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec"
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"

sdk "github.com/cosmos/cosmos-sdk/types"
Expand All @@ -14,27 +13,26 @@ import (
tmtypes "github.com/tendermint/tendermint/types"
)

// HandleConsumerDoubleVoting verifies a double voting evidence for a given a consumer chain and,
// if successful, executes the jailing of the malicious validator.
func (k Keeper) HandleConsumerDoubleVoting(ctx sdk.Context, evidence *tmtypes.DuplicateVoteEvidence, chainID string) error {
// HandleConsumerDoubleVoting verifies a double voting evidence for a given a consumer chain ID
// and a public key and, if successful, executes the jailing of the malicious validator.
func (k Keeper) HandleConsumerDoubleVoting(
ctx sdk.Context,
evidence *tmtypes.DuplicateVoteEvidence,
chainID string,
pubkey cryptotypes.PubKey,
) error {
// verifies the double voting evidence using the consumer chain public key
if err := k.VerifyDoubleVotingEvidence(ctx, *evidence, chainID, pubkey); err != nil {
return err
}

// get the validator's consensus address on the provider
providerAddr := k.GetProviderAddrFromConsumerAddr(
ctx,
chainID,
types.NewConsumerConsAddress(sdk.ConsAddress(evidence.VoteA.ValidatorAddress.Bytes())),
)

// get validator pubkey used on the consumer chain
pubkey, err := k.getValidatorPubkeyOnConsumer(ctx, chainID, providerAddr)
if err != nil {
return err
}

// verifies the double voting evidence using the consumer chain public key
if err := k.VerifyDoubleVotingEvidence(ctx, *evidence, chainID, pubkey); err != nil {
return err
}

// execute the jailing
k.JailValidator(ctx, providerAddr)

Expand Down Expand Up @@ -107,32 +105,3 @@ func (k Keeper) VerifyDoubleVotingEvidence(

return nil
}

// getValidatorPubkeyOnConsumer returns the public key a validator used on a given consumer chain.
// Note that it can either be an assigned public key or the same public key the validator uses
// on the provider chain.
func (k Keeper) getValidatorPubkeyOnConsumer(
ctx sdk.Context,
chainID string,
providerAddr types.ProviderConsAddress,
) (pubkey cryptotypes.PubKey, err error) {
tmPK, ok := k.GetValidatorConsumerPubKey(ctx, chainID, providerAddr)
if ok {
pubkey, err = cryptocodec.FromTmProtoPublicKey(tmPK)
if err != nil {
return
}
} else {
val, ok := k.stakingKeeper.GetValidatorByConsAddr(ctx, providerAddr.ToSdkConsAddr())
if !ok {
err = fmt.Errorf("cannot find validator %s", providerAddr.String())
return
}
pubkey, err = val.ConsPubKey()
if err != nil {
return
}
}

return
}
31 changes: 29 additions & 2 deletions x/ccv/provider/keeper/msg_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
"context"
"encoding/base64"

cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec"

errorsmod "cosmossdk.io/errors"
sdk "github.com/cosmos/cosmos-sdk/types"
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
Expand Down Expand Up @@ -156,8 +158,33 @@ func (k msgServer) SubmitConsumerDoubleVoting(goCtx context.Context, msg *types.
return nil, err
}

if err := k.Keeper.HandleConsumerDoubleVoting(ctx, evidence, msg.InfractionBlockHeader.Header.ChainID); err != nil {
return &types.MsgSubmitConsumerDoubleVotingResponse{}, err
// parse the validator set of the infraction block header in order
// to find the public key of the validator who double voted

// get validator set
valset, err := tmtypes.ValidatorSetFromProto(msg.InfractionBlockHeader.ValidatorSet)
if err != nil {
return nil, err
}

// look for the malicious validator in the validator set
_, validator := valset.GetByAddress(evidence.VoteA.ValidatorAddress)
if validator == nil {
return nil, errorsmod.Wrapf(
ccvtypes.ErrInvalidEvidence,
"misbehaving validator %s cannot be found in the infraction block header validator set",
evidence.VoteA.ValidatorAddress)
}

pubkey, err := cryptocodec.FromTmPubKeyInterface(validator.PubKey)
if err != nil {
return nil, err
}

// handle the double voting evidence using the chain ID of the infraction block header
// and the malicious validator's public key
if err := k.Keeper.HandleConsumerDoubleVoting(ctx, evidence, msg.InfractionBlockHeader.Header.ChainID, pubkey); err != nil {
return nil, err
}

ctx.EventManager().EmitEvents(sdk.Events{
Expand Down