cosmos-sdk/x/ibc/07-tendermint/types/evidence.go

136 lines
4.4 KiB
Go

package types
import (
"math"
yaml "gopkg.in/yaml.v2"
"github.com/tendermint/tendermint/crypto/tmhash"
tmbytes "github.com/tendermint/tendermint/libs/bytes"
tmtypes "github.com/tendermint/tendermint/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
evidenceexported "github.com/cosmos/cosmos-sdk/x/evidence/exported"
clientexported "github.com/cosmos/cosmos-sdk/x/ibc/02-client/exported"
clienttypes "github.com/cosmos/cosmos-sdk/x/ibc/02-client/types"
host "github.com/cosmos/cosmos-sdk/x/ibc/24-host"
)
var (
_ evidenceexported.Evidence = Evidence{}
_ clientexported.Misbehaviour = Evidence{}
)
// Evidence is a wrapper over tendermint's DuplicateVoteEvidence
// that implements Evidence interface expected by ICS-02
type Evidence struct {
ClientID string `json:"client_id" yaml:"client_id"`
Header1 Header `json:"header1" yaml:"header1"`
Header2 Header `json:"header2" yaml:"header2"`
ChainID string `json:"chain_id" yaml:"chain_id"`
}
// ClientType is Tendermint light client
func (ev Evidence) ClientType() clientexported.ClientType {
return clientexported.Tendermint
}
// GetClientID returns the ID of the client that committed a misbehaviour.
func (ev Evidence) GetClientID() string {
return ev.ClientID
}
// Route implements Evidence interface
func (ev Evidence) Route() string {
return clienttypes.SubModuleName
}
// Type implements Evidence interface
func (ev Evidence) Type() string {
return "client_misbehaviour"
}
// String implements Evidence interface
func (ev Evidence) String() string {
// FIXME: implement custom marshaller
bz, err := yaml.Marshal(ev)
if err != nil {
panic(err)
}
return string(bz)
}
// Hash implements Evidence interface
func (ev Evidence) Hash() tmbytes.HexBytes {
bz := SubModuleCdc.MustMarshalBinaryBare(ev)
return tmhash.Sum(bz)
}
// GetHeight returns the height at which misbehaviour occurred
//
// NOTE: assumes that evidence headers have the same height
func (ev Evidence) GetHeight() int64 {
return int64(math.Min(float64(ev.Header1.Height), float64(ev.Header2.Height)))
}
// ValidateBasic implements Evidence interface
func (ev Evidence) ValidateBasic() error {
if err := host.DefaultClientIdentifierValidator(ev.ClientID); err != nil {
return sdkerrors.Wrap(clienttypes.ErrInvalidEvidence, err.Error())
}
// ValidateBasic on both validators
if err := ev.Header1.ValidateBasic(ev.ChainID); err != nil {
return sdkerrors.Wrap(
clienttypes.ErrInvalidEvidence,
sdkerrors.Wrap(err, "header 1 failed validation").Error(),
)
}
if err := ev.Header2.ValidateBasic(ev.ChainID); err != nil {
return sdkerrors.Wrap(
clienttypes.ErrInvalidEvidence,
sdkerrors.Wrap(err, "header 2 failed validation").Error(),
)
}
// Ensure that Heights are the same
if ev.Header1.Height != ev.Header2.Height {
return sdkerrors.Wrapf(clienttypes.ErrInvalidEvidence, "headers in evidence are on different heights (%d ≠ %d)", ev.Header1.Height, ev.Header2.Height)
}
// Ensure that Commit Hashes are different
if ev.Header1.Commit.BlockID.Equals(ev.Header2.Commit.BlockID) {
return sdkerrors.Wrap(clienttypes.ErrInvalidEvidence, "headers commit to same blockID")
}
if err := ValidCommit(ev.ChainID, ev.Header1.Commit, ev.Header1.ValidatorSet); err != nil {
return err
}
if err := ValidCommit(ev.ChainID, ev.Header2.Commit, ev.Header2.ValidatorSet); err != nil {
return err
}
return nil
}
// ValidCommit checks if the given commit is a valid commit from the passed-in validatorset
//
// CommitToVoteSet will panic if the commit cannot be converted to a valid voteset given the validatorset
// This implies that someone tried to submit evidence that wasn't actually committed by the validatorset
// thus we should return an error here and reject the evidence rather than panicing.
func ValidCommit(chainID string, commit *tmtypes.Commit, valSet *tmtypes.ValidatorSet) (err error) {
defer func() {
if r := recover(); r != nil {
err = sdkerrors.Wrapf(clienttypes.ErrInvalidEvidence, "invalid commit: %v", r)
}
}()
// Convert commits to vote-sets given the validator set so we can check if they both have 2/3 power
voteSet := tmtypes.CommitToVoteSet(chainID, commit, valSet)
blockID, ok := voteSet.TwoThirdsMajority()
// Check that ValidatorSet did indeed commit to blockID in Commit
if !ok || !blockID.Equals(commit.BlockID) {
return sdkerrors.Wrap(clienttypes.ErrInvalidEvidence, "validator set did not commit to header 1")
}
return nil
}