package types import ( "time" ics23 "github.com/confio/ics23/go" tmmath "github.com/tendermint/tendermint/libs/math" lite "github.com/tendermint/tendermint/lite2" "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/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" connectionexported "github.com/cosmos/cosmos-sdk/x/ibc/03-connection/exported" connectiontypes "github.com/cosmos/cosmos-sdk/x/ibc/03-connection/types" channelexported "github.com/cosmos/cosmos-sdk/x/ibc/04-channel/exported" channeltypes "github.com/cosmos/cosmos-sdk/x/ibc/04-channel/types" commitmentexported "github.com/cosmos/cosmos-sdk/x/ibc/23-commitment/exported" commitmenttypes "github.com/cosmos/cosmos-sdk/x/ibc/23-commitment/types" host "github.com/cosmos/cosmos-sdk/x/ibc/24-host" ) var _ clientexported.ClientState = ClientState{} // ClientState from Tendermint tracks the current validator set, latest height, // and a possible frozen height. type ClientState struct { // Client ID ID string `json:"id" yaml:"id"` TrustLevel tmmath.Fraction `json:"trust_level" yaml:"trust_level"` // Duration of the period since the LastestTimestamp during which the // submitted headers are valid for upgrade TrustingPeriod time.Duration `json:"trusting_period" yaml:"trusting_period"` // Duration of the staking unbonding period UnbondingPeriod time.Duration `json:"unbonding_period" yaml:"unbonding_period"` // MaxClockDrift defines how much new (untrusted) header's Time can drift into // the future. MaxClockDrift time.Duration // Block height when the client was frozen due to a misbehaviour FrozenHeight uint64 `json:"frozen_height" yaml:"frozen_height"` // Last Header that was stored by client LastHeader Header `json:"last_header" yaml:"last_header"` ProofSpecs []*ics23.ProofSpec `json:"proof_specs" yaml:"proof_specs"` } // InitializeFromMsg creates a tendermint client state from a CreateClientMsg func InitializeFromMsg(msg MsgCreateClient) (ClientState, error) { return Initialize( msg.GetClientID(), msg.TrustLevel, msg.TrustingPeriod, msg.UnbondingPeriod, msg.MaxClockDrift, msg.Header, msg.ProofSpecs, ) } // Initialize creates a client state and validates its contents, checking that // the provided consensus state is from the same client type. func Initialize( id string, trustLevel tmmath.Fraction, trustingPeriod, ubdPeriod, maxClockDrift time.Duration, header Header, specs []*ics23.ProofSpec, ) (ClientState, error) { clientState := NewClientState(id, trustLevel, trustingPeriod, ubdPeriod, maxClockDrift, header, specs) return clientState, nil } // NewClientState creates a new ClientState instance func NewClientState( id string, trustLevel tmmath.Fraction, trustingPeriod, ubdPeriod, maxClockDrift time.Duration, header Header, specs []*ics23.ProofSpec, ) ClientState { return ClientState{ ID: id, TrustLevel: trustLevel, TrustingPeriod: trustingPeriod, UnbondingPeriod: ubdPeriod, MaxClockDrift: maxClockDrift, LastHeader: header, FrozenHeight: 0, ProofSpecs: specs, } } // GetID returns the tendermint client state identifier. func (cs ClientState) GetID() string { return cs.ID } // GetChainID returns the chain-id from the last header func (cs ClientState) GetChainID() string { if cs.LastHeader.SignedHeader.Header == nil { return "" } return cs.LastHeader.SignedHeader.Header.ChainID } // ClientType is tendermint. func (cs ClientState) ClientType() clientexported.ClientType { return clientexported.Tendermint } // GetLatestHeight returns latest block height. func (cs ClientState) GetLatestHeight() uint64 { return uint64(cs.LastHeader.Height) } // GetLatestTimestamp returns latest block time. func (cs ClientState) GetLatestTimestamp() time.Time { return cs.LastHeader.Time } // IsFrozen returns true if the frozen height has been set. func (cs ClientState) IsFrozen() bool { return cs.FrozenHeight != 0 } // Validate performs a basic validation of the client state fields. func (cs ClientState) Validate() error { if err := host.ClientIdentifierValidator(cs.ID); err != nil { return err } if err := lite.ValidateTrustLevel(cs.TrustLevel); err != nil { return err } if cs.TrustingPeriod == 0 { return sdkerrors.Wrap(ErrInvalidTrustingPeriod, "trusting period cannot be zero") } if cs.UnbondingPeriod == 0 { return sdkerrors.Wrap(ErrInvalidUnbondingPeriod, "unbonding period cannot be zero") } if cs.MaxClockDrift == 0 { return sdkerrors.Wrap(ErrInvalidMaxClockDrift, "max clock drift cannot be zero") } if cs.TrustingPeriod >= cs.UnbondingPeriod { return sdkerrors.Wrapf( ErrInvalidTrustingPeriod, "trusting period (%s) should be < unbonding period (%s)", cs.TrustingPeriod, cs.UnbondingPeriod, ) } // Validate ProofSpecs if cs.ProofSpecs == nil { return sdkerrors.Wrap(ErrInvalidProofSpecs, "proof specs cannot be nil for tm client") } for _, spec := range cs.ProofSpecs { if spec == nil { return sdkerrors.Wrap(ErrInvalidProofSpecs, "proof spec cannot be nil") } } return cs.LastHeader.ValidateBasic(cs.GetChainID()) } // GetProofSpecs returns the format the client expects for proof verification // as a string array specifying the proof type for each position in chained proof func (cs ClientState) GetProofSpecs() []*ics23.ProofSpec { return cs.ProofSpecs } // VerifyClientConsensusState verifies a proof of the consensus state of the // Tendermint client stored on the target machine. func (cs ClientState) VerifyClientConsensusState( _ sdk.KVStore, cdc codec.Marshaler, aminoCdc *codec.Codec, provingRoot commitmentexported.Root, height uint64, counterpartyClientIdentifier string, consensusHeight uint64, prefix commitmentexported.Prefix, proof []byte, consensusState clientexported.ConsensusState, ) error { merkleProof, err := sanitizeVerificationArgs(cdc, cs, height, prefix, proof, consensusState) if err != nil { return err } clientPrefixedPath := "clients/" + counterpartyClientIdentifier + "/" + host.ConsensusStatePath(consensusHeight) path, err := commitmenttypes.ApplyPrefix(prefix, clientPrefixedPath) if err != nil { return err } bz, err := aminoCdc.MarshalBinaryBare(consensusState) if err != nil { return err } if err := merkleProof.VerifyMembership(cs.ProofSpecs, provingRoot, path, bz); err != nil { return sdkerrors.Wrap(clienttypes.ErrFailedClientConsensusStateVerification, err.Error()) } return nil } // VerifyConnectionState verifies a proof of the connection state of the // specified connection end stored on the target machine. func (cs ClientState) VerifyConnectionState( _ sdk.KVStore, cdc codec.Marshaler, height uint64, prefix commitmentexported.Prefix, proof []byte, connectionID string, connectionEnd connectionexported.ConnectionI, consensusState clientexported.ConsensusState, ) error { merkleProof, err := sanitizeVerificationArgs(cdc, cs, height, prefix, proof, consensusState) if err != nil { return err } path, err := commitmenttypes.ApplyPrefix(prefix, host.ConnectionPath(connectionID)) if err != nil { return err } connection, ok := connectionEnd.(connectiontypes.ConnectionEnd) if !ok { return sdkerrors.Wrapf(sdkerrors.ErrInvalidType, "invalid connection type %T", connectionEnd) } bz, err := cdc.MarshalBinaryBare(&connection) if err != nil { return err } if err := merkleProof.VerifyMembership(cs.ProofSpecs, consensusState.GetRoot(), path, bz); err != nil { return sdkerrors.Wrap(clienttypes.ErrFailedConnectionStateVerification, err.Error()) } return nil } // VerifyChannelState verifies a proof of the channel state of the specified // channel end, under the specified port, stored on the target machine. func (cs ClientState) VerifyChannelState( _ sdk.KVStore, cdc codec.Marshaler, height uint64, prefix commitmentexported.Prefix, proof []byte, portID, channelID string, channel channelexported.ChannelI, consensusState clientexported.ConsensusState, ) error { merkleProof, err := sanitizeVerificationArgs(cdc, cs, height, prefix, proof, consensusState) if err != nil { return err } path, err := commitmenttypes.ApplyPrefix(prefix, host.ChannelPath(portID, channelID)) if err != nil { return err } channelEnd, ok := channel.(channeltypes.Channel) if !ok { return sdkerrors.Wrapf(sdkerrors.ErrInvalidType, "invalid channel type %T", channel) } bz, err := cdc.MarshalBinaryBare(&channelEnd) if err != nil { return err } if err := merkleProof.VerifyMembership(cs.ProofSpecs, consensusState.GetRoot(), path, bz); err != nil { return sdkerrors.Wrap(clienttypes.ErrFailedChannelStateVerification, err.Error()) } return nil } // VerifyPacketCommitment verifies a proof of an outgoing packet commitment at // the specified port, specified channel, and specified sequence. func (cs ClientState) VerifyPacketCommitment( _ sdk.KVStore, cdc codec.Marshaler, height uint64, prefix commitmentexported.Prefix, proof []byte, portID, channelID string, sequence uint64, commitmentBytes []byte, consensusState clientexported.ConsensusState, ) error { merkleProof, err := sanitizeVerificationArgs(cdc, cs, height, prefix, proof, consensusState) if err != nil { return err } path, err := commitmenttypes.ApplyPrefix(prefix, host.PacketCommitmentPath(portID, channelID, sequence)) if err != nil { return err } if err := merkleProof.VerifyMembership(cs.ProofSpecs, consensusState.GetRoot(), path, commitmentBytes); err != nil { return sdkerrors.Wrap(clienttypes.ErrFailedPacketCommitmentVerification, err.Error()) } return nil } // VerifyPacketAcknowledgement verifies a proof of an incoming packet // acknowledgement at the specified port, specified channel, and specified sequence. func (cs ClientState) VerifyPacketAcknowledgement( _ sdk.KVStore, cdc codec.Marshaler, height uint64, prefix commitmentexported.Prefix, proof []byte, portID, channelID string, sequence uint64, acknowledgement []byte, consensusState clientexported.ConsensusState, ) error { merkleProof, err := sanitizeVerificationArgs(cdc, cs, height, prefix, proof, consensusState) if err != nil { return err } path, err := commitmenttypes.ApplyPrefix(prefix, host.PacketAcknowledgementPath(portID, channelID, sequence)) if err != nil { return err } if err := merkleProof.VerifyMembership(cs.ProofSpecs, consensusState.GetRoot(), path, channeltypes.CommitAcknowledgement(acknowledgement)); err != nil { return sdkerrors.Wrap(clienttypes.ErrFailedPacketAckVerification, err.Error()) } return nil } // VerifyPacketAcknowledgementAbsence verifies a proof of the absence of an // incoming packet acknowledgement at the specified port, specified channel, and // specified sequence. func (cs ClientState) VerifyPacketAcknowledgementAbsence( _ sdk.KVStore, cdc codec.Marshaler, height uint64, prefix commitmentexported.Prefix, proof []byte, portID, channelID string, sequence uint64, consensusState clientexported.ConsensusState, ) error { merkleProof, err := sanitizeVerificationArgs(cdc, cs, height, prefix, proof, consensusState) if err != nil { return err } path, err := commitmenttypes.ApplyPrefix(prefix, host.PacketAcknowledgementPath(portID, channelID, sequence)) if err != nil { return err } if err := merkleProof.VerifyNonMembership(cs.ProofSpecs, consensusState.GetRoot(), path); err != nil { return sdkerrors.Wrap(clienttypes.ErrFailedPacketAckAbsenceVerification, err.Error()) } return nil } // VerifyNextSequenceRecv verifies a proof of the next sequence number to be // received of the specified channel at the specified port. func (cs ClientState) VerifyNextSequenceRecv( _ sdk.KVStore, cdc codec.Marshaler, height uint64, prefix commitmentexported.Prefix, proof []byte, portID, channelID string, nextSequenceRecv uint64, consensusState clientexported.ConsensusState, ) error { merkleProof, err := sanitizeVerificationArgs(cdc, cs, height, prefix, proof, consensusState) if err != nil { return err } path, err := commitmenttypes.ApplyPrefix(prefix, host.NextSequenceRecvPath(portID, channelID)) if err != nil { return err } bz := sdk.Uint64ToBigEndian(nextSequenceRecv) if err := merkleProof.VerifyMembership(cs.ProofSpecs, consensusState.GetRoot(), path, bz); err != nil { return sdkerrors.Wrap(clienttypes.ErrFailedNextSeqRecvVerification, err.Error()) } return nil } // sanitizeVerificationArgs perfoms the basic checks on the arguments that are // shared between the verification functions and returns the unmarshalled // merkle proof and an error if one occurred. func sanitizeVerificationArgs( cdc codec.Marshaler, cs ClientState, height uint64, prefix commitmentexported.Prefix, proof []byte, consensusState clientexported.ConsensusState, ) (merkleProof commitmenttypes.MerkleProof, err error) { if cs.GetLatestHeight() < height { return commitmenttypes.MerkleProof{}, sdkerrors.Wrapf( sdkerrors.ErrInvalidHeight, "client state (%s) height < proof height (%d < %d)", cs.ID, cs.GetLatestHeight(), height, ) } if cs.IsFrozen() && cs.FrozenHeight <= height { return commitmenttypes.MerkleProof{}, clienttypes.ErrClientFrozen } if prefix == nil { return commitmenttypes.MerkleProof{}, sdkerrors.Wrap(commitmenttypes.ErrInvalidPrefix, "prefix cannot be empty") } _, ok := prefix.(*commitmenttypes.MerklePrefix) if !ok { return commitmenttypes.MerkleProof{}, sdkerrors.Wrapf(commitmenttypes.ErrInvalidPrefix, "invalid prefix type %T, expected *MerklePrefix", prefix) } if proof == nil { return commitmenttypes.MerkleProof{}, sdkerrors.Wrap(commitmenttypes.ErrInvalidProof, "proof cannot be empty") } if err = cdc.UnmarshalBinaryBare(proof, &merkleProof); err != nil { return commitmenttypes.MerkleProof{}, sdkerrors.Wrap(commitmenttypes.ErrInvalidProof, "failed to unmarshal proof into commitment merkle proof") } if consensusState == nil { return commitmenttypes.MerkleProof{}, sdkerrors.Wrap(clienttypes.ErrInvalidConsensus, "consensus state cannot be empty") } _, ok = consensusState.(ConsensusState) if !ok { return commitmenttypes.MerkleProof{}, sdkerrors.Wrapf(clienttypes.ErrInvalidConsensus, "invalid consensus type %T, expected %T", consensusState, ConsensusState{}) } return merkleProof, nil }