-
Notifications
You must be signed in to change notification settings - Fork 136
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add handler for consumer double voting (#1232)
* create new endpoint for consumer double voting * add first draft handling logic * first iteration of double voting * draft first mem test * error handling * refactor * add unit test of double voting verification * remove evidence age checks * document * doc * protogen * reformat double voting handling * logger nit * nits * check evidence age duration * move verify double voting evidence to ut * fix nit * nits * fix e2e tests * improve double vote testing coverage * remove TODO * lint * add UT for JailAndTombstoneValidator * nits * nits * remove tombstoning and evidence age check * lint * typo * improve godoc
- Loading branch information
Showing
18 changed files
with
1,417 additions
and
65 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
package integration | ||
|
||
import ( | ||
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" | ||
tmtypes "github.com/tendermint/tendermint/types" | ||
) | ||
|
||
// TestHandleConsumerDoubleVoting verifies that handling a double voting evidence | ||
// of a consumer chain results in the expected jailing of the malicious validator | ||
func (s *CCVTestSuite) TestHandleConsumerDoubleVoting() { | ||
s.SetupCCVChannel(s.path) | ||
// required to have the consumer client revision height greater than 0 | ||
s.SendEmptyVSCPacket() | ||
|
||
// create signing info for all validators | ||
for _, v := range s.providerChain.Vals.Validators { | ||
s.setDefaultValSigningInfo(*v) | ||
} | ||
|
||
valSet, err := tmtypes.ValidatorSetFromProto(s.consumerChain.LastHeader.ValidatorSet) | ||
s.Require().NoError(err) | ||
|
||
val := valSet.Validators[0] | ||
signer := s.consumerChain.Signers[val.Address.String()] | ||
|
||
blockID1 := testutil.MakeBlockID([]byte("blockhash"), 1000, []byte("partshash")) | ||
blockID2 := testutil.MakeBlockID([]byte("blockhash2"), 1000, []byte("partshash")) | ||
|
||
// Note that votes are signed along with the chain ID | ||
// see VoteSignBytes in https://github.com/cometbft/cometbft/blob/main/types/vote.go#L139 | ||
vote1 := testutil.MakeAndSignVote( | ||
blockID1, | ||
s.consumerCtx().BlockHeight(), | ||
s.consumerCtx().BlockTime(), | ||
valSet, | ||
signer, | ||
s.consumerChain.ChainID, | ||
) | ||
|
||
badVote := testutil.MakeAndSignVote( | ||
blockID2, | ||
s.consumerCtx().BlockHeight(), | ||
s.consumerCtx().BlockTime(), | ||
valSet, | ||
signer, | ||
s.consumerChain.ChainID, | ||
) | ||
|
||
testCases := []struct { | ||
name string | ||
ev *tmtypes.DuplicateVoteEvidence | ||
chainID string | ||
expPass bool | ||
}{ | ||
{ | ||
"invalid consumer chain id - shouldn't pass", | ||
&tmtypes.DuplicateVoteEvidence{ | ||
VoteA: vote1, | ||
VoteB: badVote, | ||
ValidatorPower: val.VotingPower, | ||
TotalVotingPower: val.VotingPower, | ||
Timestamp: s.consumerCtx().BlockTime(), | ||
}, | ||
"chainID", | ||
false, | ||
}, | ||
{ | ||
// create an invalid evidence containing two identical votes | ||
"invalid double voting evidence - shouldn't pass", | ||
&tmtypes.DuplicateVoteEvidence{ | ||
VoteA: vote1, | ||
VoteB: vote1, | ||
ValidatorPower: val.VotingPower, | ||
TotalVotingPower: val.VotingPower, | ||
Timestamp: s.consumerCtx().BlockTime(), | ||
}, | ||
s.consumerChain.ChainID, | ||
false, | ||
}, | ||
{ | ||
// In order to create an evidence for a consumer chain, | ||
// we create two votes that only differ by their Block IDs and | ||
// signed them using the same validator private key and chain ID | ||
// of the consumer chain | ||
"valid double voting evidence - should pass", | ||
&tmtypes.DuplicateVoteEvidence{ | ||
VoteA: vote1, | ||
VoteB: badVote, | ||
ValidatorPower: val.VotingPower, | ||
TotalVotingPower: val.VotingPower, | ||
Timestamp: s.consumerCtx().BlockTime(), | ||
}, | ||
s.consumerChain.ChainID, | ||
true, | ||
}, | ||
} | ||
|
||
consuAddr := types.NewConsumerConsAddress(sdk.ConsAddress(val.Address.Bytes())) | ||
provAddr := s.providerApp.GetProviderKeeper().GetProviderAddrFromConsumerAddr(s.providerCtx(), s.consumerChain.ChainID, consuAddr) | ||
|
||
for _, tc := range testCases { | ||
s.Run(tc.name, func() { | ||
err = s.providerApp.GetProviderKeeper().HandleConsumerDoubleVoting( | ||
s.providerCtx(), | ||
tc.ev, | ||
tc.chainID, | ||
) | ||
if tc.expPass { | ||
s.Require().NoError(err) | ||
|
||
// verifies that the jailing has occurred | ||
s.Require().True(s.providerApp.GetTestStakingKeeper().IsValidatorJailed(s.providerCtx(), provAddr.ToSdkConsAddr())) | ||
} else { | ||
s.Require().Error(err) | ||
|
||
// verifies that no jailing and has occurred | ||
s.Require().False(s.providerApp.GetTestStakingKeeper().IsValidatorJailed(s.providerCtx(), provAddr.ToSdkConsAddr())) | ||
} | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
package crypto | ||
|
||
import ( | ||
"time" | ||
|
||
"github.com/tendermint/tendermint/crypto/tmhash" | ||
tmtypes "github.com/tendermint/tendermint/types" | ||
) | ||
|
||
// utility function duplicated from CometBFT | ||
// see https://github.com/cometbft/cometbft/blob/main/evidence/verify_test.go#L554 | ||
func MakeBlockID(hash []byte, partSetSize uint32, partSetHash []byte) tmtypes.BlockID { | ||
var ( | ||
h = make([]byte, tmhash.Size) | ||
psH = make([]byte, tmhash.Size) | ||
) | ||
copy(h, hash) | ||
copy(psH, partSetHash) | ||
return tmtypes.BlockID{ | ||
Hash: h, | ||
PartSetHeader: tmtypes.PartSetHeader{ | ||
Total: partSetSize, | ||
Hash: psH, | ||
}, | ||
} | ||
} | ||
|
||
func MakeAndSignVote( | ||
blockID tmtypes.BlockID, | ||
blockHeight int64, | ||
blockTime time.Time, | ||
valSet *tmtypes.ValidatorSet, | ||
signer tmtypes.PrivValidator, | ||
chainID string, | ||
) *tmtypes.Vote { | ||
vote, err := tmtypes.MakeVote( | ||
blockHeight, | ||
blockID, | ||
valSet, | ||
signer, | ||
chainID, | ||
blockTime, | ||
) | ||
if err != nil { | ||
panic(err) | ||
} | ||
|
||
v := vote.ToProto() | ||
err = signer.SignVote(chainID, v) | ||
if err != nil { | ||
panic(err) | ||
} | ||
|
||
vote.Signature = v.Signature | ||
return vote | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
syntax = "proto3"; | ||
package tendermint.types; | ||
|
||
option go_package = "github.com/tendermint/tendermint/proto/tendermint/types"; | ||
|
||
import "gogoproto/gogo.proto"; | ||
import "google/protobuf/timestamp.proto"; | ||
import "tendermint/types/types.proto"; | ||
import "tendermint/types/validator.proto"; | ||
|
||
message Evidence { | ||
oneof sum { | ||
DuplicateVoteEvidence duplicate_vote_evidence = 1; | ||
LightClientAttackEvidence light_client_attack_evidence = 2; | ||
} | ||
} | ||
|
||
// DuplicateVoteEvidence contains evidence of a validator signed two conflicting votes. | ||
message DuplicateVoteEvidence { | ||
tendermint.types.Vote vote_a = 1; | ||
tendermint.types.Vote vote_b = 2; | ||
int64 total_voting_power = 3; | ||
int64 validator_power = 4; | ||
google.protobuf.Timestamp timestamp = 5 [(gogoproto.nullable) = false, (gogoproto.stdtime) = true]; | ||
} | ||
|
||
// LightClientAttackEvidence contains evidence of a set of validators attempting to mislead a light client. | ||
message LightClientAttackEvidence { | ||
tendermint.types.LightBlock conflicting_block = 1; | ||
int64 common_height = 2; | ||
repeated tendermint.types.Validator byzantine_validators = 3; | ||
int64 total_voting_power = 4; | ||
google.protobuf.Timestamp timestamp = 5 [(gogoproto.nullable) = false, (gogoproto.stdtime) = true]; | ||
} | ||
|
||
message EvidenceList { | ||
repeated Evidence evidence = 1 [(gogoproto.nullable) = false]; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.