459 lines
14 KiB
Go
459 lines
14 KiB
Go
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
|
|
}
|