cosmos-sdk/x/ibc/07-tendermint/misbehaviour.go

136 lines
5.8 KiB
Go

package tendermint
import (
"time"
abci "github.com/tendermint/tendermint/abci/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
clientexported "github.com/cosmos/cosmos-sdk/x/ibc/02-client/exported"
clienttypes "github.com/cosmos/cosmos-sdk/x/ibc/02-client/types"
"github.com/cosmos/cosmos-sdk/x/ibc/07-tendermint/types"
)
// CheckMisbehaviourAndUpdateState determines whether or not two conflicting
// headers at the same height would have convinced the light client.
//
// NOTE: consensusState1 is the trusted consensus state that corresponds to the TrustedHeight
// of misbehaviour.Header1
// Similarly, consensusState2 is the trusted consensus state that corresponds
// to misbehaviour.Header2
func CheckMisbehaviourAndUpdateState(
clientState clientexported.ClientState,
consensusState1, consensusState2 clientexported.ConsensusState,
misbehaviour clientexported.Misbehaviour,
currentTimestamp time.Time,
consensusParams *abci.ConsensusParams,
) (clientexported.ClientState, error) {
// cast the interface to specific types before checking for misbehaviour
tmClientState, ok := clientState.(*types.ClientState)
if !ok {
return nil, sdkerrors.Wrapf(clienttypes.ErrInvalidClientType, "expected type %T, got %T", &types.ClientState{}, clientState)
}
// If client is already frozen at earlier height than evidence, return with error
if tmClientState.IsFrozen() && tmClientState.FrozenHeight <= uint64(misbehaviour.GetHeight()) {
return nil, sdkerrors.Wrapf(clienttypes.ErrInvalidEvidence,
"client is already frozen at earlier height %d than misbehaviour height %d", tmClientState.FrozenHeight, misbehaviour.GetHeight())
}
tmConsensusState1, ok := consensusState1.(*types.ConsensusState)
if !ok {
return nil, sdkerrors.Wrapf(clienttypes.ErrInvalidClientType, "invalid consensus state type for first header: expected type %T, got %T", &types.ConsensusState{}, consensusState1)
}
tmConsensusState2, ok := consensusState2.(*types.ConsensusState)
if !ok {
return nil, sdkerrors.Wrapf(clienttypes.ErrInvalidClientType, "invalid consensus state for second header: expected type %T, got %T", &types.ConsensusState{}, consensusState2)
}
tmEvidence, ok := misbehaviour.(types.Evidence)
if !ok {
return nil, sdkerrors.Wrapf(clienttypes.ErrInvalidClientType, "expected type %T, got %T", misbehaviour, types.Evidence{})
}
// use earliest height of trusted consensus states to verify ageBlocks
var height uint64
if tmConsensusState1.Height < tmConsensusState2.Height {
height = tmConsensusState1.Height
} else {
height = tmConsensusState2.Height
}
// calculate the age of the misbehaviour evidence
infractionHeight := tmEvidence.GetHeight()
infractionTime := tmEvidence.GetTime()
ageDuration := currentTimestamp.Sub(infractionTime)
ageBlocks := uint64(infractionHeight) - height
// Reject misbehaviour if the age is too old. Evidence is considered stale
// if the difference in time and number of blocks is greater than the allowed
// parameters defined.
//
// NOTE: The first condition is a safety check as the consensus params cannot
// be nil since the previous param values will be used in case they can't be
// retrieved. If they are not set during initialization, Tendermint will always
// use the default values.
if consensusParams != nil &&
consensusParams.Evidence != nil &&
ageDuration > consensusParams.Evidence.MaxAgeDuration &&
ageBlocks > uint64(consensusParams.Evidence.MaxAgeNumBlocks) {
return nil, sdkerrors.Wrapf(clienttypes.ErrInvalidEvidence,
"age duration (%s) and age blocks (%d) are greater than max consensus params for duration (%s) and block (%d)",
ageDuration, ageBlocks, consensusParams.Evidence.MaxAgeDuration, consensusParams.Evidence.MaxAgeNumBlocks,
)
}
// Check the validity of the two conflicting headers against their respective
// trusted consensus states
// NOTE: header height and commitment root assertions are checked in
// evidence.ValidateBasic by the client keeper and msg.ValidateBasic
// by the base application.
if err := checkMisbehaviourHeader(
tmClientState, tmConsensusState1, tmEvidence.Header1, currentTimestamp,
); err != nil {
return nil, sdkerrors.Wrap(err, "verifying Header1 in Evidence failed")
}
if err := checkMisbehaviourHeader(
tmClientState, tmConsensusState2, tmEvidence.Header2, currentTimestamp,
); err != nil {
return nil, sdkerrors.Wrap(err, "verifying Header2 in Evidence failed")
}
tmClientState.FrozenHeight = uint64(tmEvidence.GetHeight())
return tmClientState, nil
}
// checkMisbehaviourHeader checks that a Header in Misbehaviour is valid evidence given
// a trusted ConsensusState
func checkMisbehaviourHeader(
clientState *types.ClientState, consState *types.ConsensusState, header types.Header, currentTimestamp time.Time,
) error {
// check the trusted fields for the header against ConsensusState
if err := checkTrustedHeader(header, consState); err != nil {
return err
}
// assert that the timestamp is not from more than an unbonding period ago
if currentTimestamp.Sub(consState.Timestamp) >= clientState.UnbondingPeriod {
return sdkerrors.Wrapf(
types.ErrUnbondingPeriodExpired,
"current timestamp minus the latest consensus state timestamp is greater than or equal to the unbonding period (%s >= %s)",
currentTimestamp.Sub(consState.Timestamp), clientState.UnbondingPeriod,
)
}
// - ValidatorSet must have 2/3 similarity with trusted FromValidatorSet
// - ValidatorSets on both headers are valid given the last trusted ValidatorSet
if err := header.TrustedValidators.VerifyCommitLightTrusting(
clientState.GetChainID(), header.Commit.BlockID, header.Height,
header.Commit, clientState.TrustLevel.ToTendermint(),
); err != nil {
return sdkerrors.Wrapf(clienttypes.ErrInvalidEvidence, "validator set in header has too much change from trusted validator set: %v", err)
}
return nil
}