375 lines
11 KiB
Go
375 lines
11 KiB
Go
package types
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"time"
|
|
|
|
"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"
|
|
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"
|
|
ibctypes "github.com/cosmos/cosmos-sdk/x/ibc/types"
|
|
)
|
|
|
|
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"`
|
|
|
|
// 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"`
|
|
}
|
|
|
|
// InitializeFromMsg creates a tendermint client state from a CreateClientMsg
|
|
func InitializeFromMsg(msg MsgCreateClient) (ClientState, error) {
|
|
return Initialize(
|
|
msg.GetClientID(), msg.TrustingPeriod, msg.UnbondingPeriod, msg.MaxClockDrift, msg.Header,
|
|
)
|
|
}
|
|
|
|
// 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, trustingPeriod, ubdPeriod, maxClockDrift time.Duration, header Header,
|
|
) (ClientState, error) {
|
|
|
|
if trustingPeriod >= ubdPeriod {
|
|
return ClientState{}, errors.New("trusting period should be < unbonding period")
|
|
}
|
|
|
|
clientState := NewClientState(id, trustingPeriod, ubdPeriod, maxClockDrift, header)
|
|
return clientState, nil
|
|
}
|
|
|
|
// NewClientState creates a new ClientState instance
|
|
func NewClientState(
|
|
id string, trustingPeriod, ubdPeriod, maxClockDrift time.Duration, header Header,
|
|
) ClientState {
|
|
|
|
return ClientState{
|
|
ID: id,
|
|
TrustingPeriod: trustingPeriod,
|
|
UnbondingPeriod: ubdPeriod,
|
|
MaxClockDrift: maxClockDrift,
|
|
LastHeader: header,
|
|
FrozenHeight: 0,
|
|
}
|
|
}
|
|
|
|
// 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.DefaultClientIdentifierValidator(cs.ID); err != nil {
|
|
return err
|
|
}
|
|
if cs.TrustingPeriod == 0 {
|
|
return errors.New("trusting period cannot be zero")
|
|
}
|
|
if cs.UnbondingPeriod == 0 {
|
|
return errors.New("unbonding period cannot be zero")
|
|
}
|
|
if cs.MaxClockDrift == 0 {
|
|
return errors.New("max clock drift cannot be zero")
|
|
}
|
|
return cs.LastHeader.ValidateBasic(cs.GetChainID())
|
|
}
|
|
|
|
// VerifyClientConsensusState verifies a proof of the consensus state of the
|
|
// Tendermint client stored on the target machine.
|
|
func (cs ClientState) VerifyClientConsensusState(
|
|
cdc *codec.Codec,
|
|
provingRoot commitmentexported.Root,
|
|
height uint64,
|
|
counterpartyClientIdentifier string,
|
|
consensusHeight uint64,
|
|
prefix commitmentexported.Prefix,
|
|
proof commitmentexported.Proof,
|
|
consensusState clientexported.ConsensusState,
|
|
) error {
|
|
clientPrefixedPath := "clients/" + counterpartyClientIdentifier + "/" + ibctypes.ConsensusStatePath(consensusHeight)
|
|
path, err := commitmenttypes.ApplyPrefix(prefix, clientPrefixedPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := validateVerificationArgs(cs, height, proof, consensusState); err != nil {
|
|
return err
|
|
}
|
|
|
|
bz, err := cdc.MarshalBinaryBare(consensusState)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := proof.VerifyMembership(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(
|
|
cdc *codec.Codec,
|
|
height uint64,
|
|
prefix commitmentexported.Prefix,
|
|
proof commitmentexported.Proof,
|
|
connectionID string,
|
|
connectionEnd connectionexported.ConnectionI,
|
|
consensusState clientexported.ConsensusState,
|
|
) error {
|
|
path, err := commitmenttypes.ApplyPrefix(prefix, ibctypes.ConnectionPath(connectionID))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := validateVerificationArgs(cs, height, proof, consensusState); err != nil {
|
|
return err
|
|
}
|
|
|
|
bz, err := cdc.MarshalBinaryBare(connectionEnd)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := proof.VerifyMembership(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(
|
|
cdc *codec.Codec,
|
|
height uint64,
|
|
prefix commitmentexported.Prefix,
|
|
proof commitmentexported.Proof,
|
|
portID,
|
|
channelID string,
|
|
channel channelexported.ChannelI,
|
|
consensusState clientexported.ConsensusState,
|
|
) error {
|
|
path, err := commitmenttypes.ApplyPrefix(prefix, ibctypes.ChannelPath(portID, channelID))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := validateVerificationArgs(cs, height, proof, consensusState); err != nil {
|
|
return err
|
|
}
|
|
|
|
bz, err := cdc.MarshalBinaryBare(channel)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := proof.VerifyMembership(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(
|
|
height uint64,
|
|
prefix commitmentexported.Prefix,
|
|
proof commitmentexported.Proof,
|
|
portID,
|
|
channelID string,
|
|
sequence uint64,
|
|
commitmentBytes []byte,
|
|
consensusState clientexported.ConsensusState,
|
|
) error {
|
|
path, err := commitmenttypes.ApplyPrefix(prefix, ibctypes.PacketCommitmentPath(portID, channelID, sequence))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := validateVerificationArgs(cs, height, proof, consensusState); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := proof.VerifyMembership(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(
|
|
height uint64,
|
|
prefix commitmentexported.Prefix,
|
|
proof commitmentexported.Proof,
|
|
portID,
|
|
channelID string,
|
|
sequence uint64,
|
|
acknowledgement []byte,
|
|
consensusState clientexported.ConsensusState,
|
|
) error {
|
|
path, err := commitmenttypes.ApplyPrefix(prefix, ibctypes.PacketAcknowledgementPath(portID, channelID, sequence))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := validateVerificationArgs(cs, height, proof, consensusState); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := proof.VerifyMembership(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(
|
|
height uint64,
|
|
prefix commitmentexported.Prefix,
|
|
proof commitmentexported.Proof,
|
|
portID,
|
|
channelID string,
|
|
sequence uint64,
|
|
consensusState clientexported.ConsensusState,
|
|
) error {
|
|
path, err := commitmenttypes.ApplyPrefix(prefix, ibctypes.PacketAcknowledgementPath(portID, channelID, sequence))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := validateVerificationArgs(cs, height, proof, consensusState); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := proof.VerifyNonMembership(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(
|
|
height uint64,
|
|
prefix commitmentexported.Prefix,
|
|
proof commitmentexported.Proof,
|
|
portID,
|
|
channelID string,
|
|
nextSequenceRecv uint64,
|
|
consensusState clientexported.ConsensusState,
|
|
) error {
|
|
path, err := commitmenttypes.ApplyPrefix(prefix, ibctypes.NextSequenceRecvPath(portID, channelID))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := validateVerificationArgs(cs, height, proof, consensusState); err != nil {
|
|
return err
|
|
}
|
|
|
|
bz := sdk.Uint64ToBigEndian(nextSequenceRecv)
|
|
|
|
if err := proof.VerifyMembership(consensusState.GetRoot(), path, bz); err != nil {
|
|
return sdkerrors.Wrap(clienttypes.ErrFailedNextSeqRecvVerification, err.Error())
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// validateVerificationArgs perfoms the basic checks on the arguments that are
|
|
// shared between the verification functions.
|
|
func validateVerificationArgs(
|
|
cs ClientState,
|
|
height uint64,
|
|
proof commitmentexported.Proof,
|
|
consensusState clientexported.ConsensusState,
|
|
) error {
|
|
if cs.GetLatestHeight() < height {
|
|
return sdkerrors.Wrap(
|
|
ibctypes.ErrInvalidHeight,
|
|
fmt.Sprintf("client state (%s) height < proof height (%d < %d)", cs.ID, cs.GetLatestHeight(), height),
|
|
)
|
|
}
|
|
|
|
if cs.IsFrozen() && cs.FrozenHeight <= height {
|
|
return clienttypes.ErrClientFrozen
|
|
}
|
|
|
|
if proof == nil {
|
|
return sdkerrors.Wrap(commitmenttypes.ErrInvalidProof, "proof cannot be empty")
|
|
}
|
|
|
|
if consensusState == nil {
|
|
return sdkerrors.Wrap(clienttypes.ErrInvalidConsensus, "consensus state cannot be empty")
|
|
}
|
|
|
|
return nil
|
|
}
|