diff --git a/Makefile b/Makefile index 1a3c2594f..536b045bf 100644 --- a/Makefile +++ b/Makefile @@ -311,7 +311,7 @@ protogen_local: go_protoc-go-inject-tag ## Generate go structures for all of the # IBC $(PROTOC_SHARED) -I=./ibc/types/proto --go_out=./ibc/types ./ibc/types/proto/*.proto $(PROTOC_SHARED) -I=./ibc/client/types/proto --go_out=./ibc/client/types ./ibc/client/types/proto/*.proto - $(PROTOC_SHARED) -I=./ibc/client/types/proto -I=./ibc/client/light_clients/types/proto -I=./shared/core/types/proto --go_out=./ibc/client/light_clients/types ./ibc/client/light_clients/types/proto/*.proto + $(PROTOC_SHARED) -I=./ibc/client/types/proto -I=./ibc/client/light_clients/types/proto -I=./shared/core/types/proto -I=./ibc/types/proto --go_out=./ibc/client/light_clients/types ./ibc/client/light_clients/types/proto/*.proto # echo "View generated proto files by running: make protogen_show" diff --git a/ibc/client/introspect.go b/ibc/client/introspect.go new file mode 100644 index 000000000..f4497b17b --- /dev/null +++ b/ibc/client/introspect.go @@ -0,0 +1,140 @@ +package client + +import ( + "errors" + "time" + + light_client_types "github.com/pokt-network/pocket/ibc/client/light_clients/types" + "github.com/pokt-network/pocket/ibc/client/types" + ibc_types "github.com/pokt-network/pocket/ibc/types" + "github.com/pokt-network/pocket/shared/codec" + "github.com/pokt-network/pocket/shared/modules" + util_types "github.com/pokt-network/pocket/utility/types" + "google.golang.org/protobuf/types/known/durationpb" +) + +// GetHostConsensusState returns the ConsensusState at the given height for the +// host chain, the Pocket network. It then serialises this and packs it into a +// ConsensusState object for use in a WASM client +func (c *clientManager) GetHostConsensusState(height modules.Height) (modules.ConsensusState, error) { + blockStore := c.GetBus().GetPersistenceModule().GetBlockStore() + block, err := blockStore.GetBlock(height.GetRevisionHeight()) + if err != nil { + return nil, err + } + pocketConsState := &light_client_types.PocketConsensusState{ + Timestamp: block.BlockHeader.Timestamp, + StateHash: block.BlockHeader.StateHash, + StateTreeHashes: block.BlockHeader.StateTreeHashes, + NextValSetHash: block.BlockHeader.NextValSetHash, + } + consBz, err := codec.GetCodec().Marshal(pocketConsState) + if err != nil { + return nil, err + } + return types.NewConsensusState(consBz, uint64(pocketConsState.Timestamp.AsTime().UnixNano())), nil +} + +// GetHostClientState returns the ClientState at the given height for the host +// chain, the Pocket network. +// +// This function is used to validate the state of a client running on a +// counterparty chain. +func (c *clientManager) GetHostClientState(height modules.Height) (modules.ClientState, error) { + blockStore := c.GetBus().GetPersistenceModule().GetBlockStore() + block, err := blockStore.GetBlock(height.GetRevisionHeight()) + if err != nil { + return nil, err + } + rCtx, err := c.GetBus().GetPersistenceModule().NewReadContext(int64(height.GetRevisionHeight())) + if err != nil { + return nil, err + } + defer rCtx.Release() + unbondingBlocks, err := rCtx.GetIntParam(util_types.ValidatorUnstakingBlocksParamName, int64(height.GetRevisionHeight())) + if err != nil { + return nil, err + } + // TODO_AFTER(#705): use the actual MinimumBlockTime once set + blockTime := time.Minute * 15 + unbondingPeriod := blockTime * time.Duration(unbondingBlocks) // approx minutes per block * blocks + pocketClient := &light_client_types.PocketClientState{ + NetworkId: block.BlockHeader.NetworkId, + TrustLevel: &light_client_types.Fraction{Numerator: 2, Denominator: 3}, + TrustingPeriod: durationpb.New(unbondingPeriod), + UnbondingPeriod: durationpb.New(unbondingPeriod), + MaxClockDrift: durationpb.New(blockTime), // DISCUSS: What is a reasonable MaxClockDrift? + LatestHeight: &types.Height{ + RevisionNumber: height.GetRevisionNumber(), + RevisionHeight: height.GetRevisionHeight(), + }, + ProofSpec: ibc_types.SmtSpec, + } + clientBz, err := codec.GetCodec().Marshal(pocketClient) + if err != nil { + return nil, err + } + return &types.ClientState{ + Data: clientBz, + RecentHeight: pocketClient.LatestHeight, + }, nil +} + +// VerifyHostClientState verifies that a ClientState for a light client running +// on a counterparty chain is valid, by checking it against the result of +// GetHostClientState(counterpartyClientState.GetLatestHeight()) +func (c *clientManager) VerifyHostClientState(counterparty modules.ClientState) error { + hostState, err := c.GetHostClientState(c.GetCurrentHeight()) + if err != nil { + return err + } + poktHost := new(light_client_types.PocketClientState) + err = codec.GetCodec().Unmarshal(hostState.GetData(), poktHost) + if err != nil { + return err + } + poktCounter := new(light_client_types.PocketClientState) + err = codec.GetCodec().Unmarshal(counterparty.GetData(), poktCounter) + if err != nil { + return errors.New("counterparty client state is not a PocketClientState") + } + + if poktCounter.FrozenHeight > 0 { + return errors.New("counterparty client state is frozen") + } + if poktCounter.NetworkId != poktHost.NetworkId { + return errors.New("counterparty client state has different network id") + } + if poktCounter.LatestHeight.RevisionNumber != poktHost.LatestHeight.RevisionNumber { + return errors.New("counterparty client state has different revision number") + } + if poktCounter.GetLatestHeight().GTE(poktHost.GetLatestHeight()) { + return errors.New("counterparty client state has a height greater than or equal to the host client state") + } + if poktCounter.TrustLevel.LT(&light_client_types.Fraction{Numerator: 2, Denominator: 3}) || + poktCounter.TrustLevel.GT(&light_client_types.Fraction{Numerator: 1, Denominator: 1}) { + return errors.New("counterparty client state trust level is not in the accepted range") + } + if !poktCounter.ProofSpec.ConvertToIcs23ProofSpec().SpecEquals(poktHost.ProofSpec.ConvertToIcs23ProofSpec()) { + return errors.New("counterparty client state has different proof spec") + } + if poktCounter.UnbondingPeriod != poktHost.UnbondingPeriod { + return errors.New("counterparty client state has different unbonding period") + } + if poktCounter.UnbondingPeriod.AsDuration().Nanoseconds() < poktHost.TrustingPeriod.AsDuration().Nanoseconds() { + return errors.New("counterparty client state unbonding period is less than trusting period") + } + + // RESEARCH: Look into upgrade paths, their use and if they should just be equal + + return nil +} + +// GetCurrentHeight returns the current IBC client height of the network +// TODO_AFTER(#705): Use actual revision number +func (h *clientManager) GetCurrentHeight() modules.Height { + return &types.Height{ + RevisionNumber: 1, + RevisionHeight: h.GetBus().GetConsensusModule().CurrentHeight(), + } +} diff --git a/ibc/client/light_clients/types/proto/pocket.proto b/ibc/client/light_clients/types/proto/pocket.proto index 94f17a7e4..f6acd3d1b 100644 --- a/ibc/client/light_clients/types/proto/pocket.proto +++ b/ibc/client/light_clients/types/proto/pocket.proto @@ -6,6 +6,7 @@ option go_package = "github.com/pokt-network/pocket/ibc/client/light_client/type import "google/protobuf/timestamp.proto"; import "google/protobuf/duration.proto"; +import "proofs.proto"; import "wasm.proto"; import "block.proto"; @@ -26,7 +27,7 @@ message PocketClientState { google.protobuf.Duration max_clock_drift = 5; // the max duration a new header's time can be in the future Height latest_height = 6; // the latest height the client was updated to uint64 frozen_height = 7; // the height at which the client was frozen due to a misbehaviour - bytes proof_specs = 8; // ics23 proof spec used in verifying proofs + ProofSpec proof_spec = 8; // ics23 proof spec used in verifying proofs // RESEARCH: Figure out exactly what this is for in tendermint, why it is needed and if we need it also // repeated string upgrade_path = 9; // the upgrade path for the new client state } diff --git a/ibc/client/queries.go b/ibc/client/queries.go index c9f426ca3..2ea5b36dc 100644 --- a/ibc/client/queries.go +++ b/ibc/client/queries.go @@ -1,16 +1,10 @@ package client import ( - "time" - - light_client_types "github.com/pokt-network/pocket/ibc/client/light_clients/types" "github.com/pokt-network/pocket/ibc/client/types" "github.com/pokt-network/pocket/ibc/path" - "github.com/pokt-network/pocket/shared/codec" core_types "github.com/pokt-network/pocket/shared/core/types" "github.com/pokt-network/pocket/shared/modules" - util_types "github.com/pokt-network/pocket/utility/types" - "google.golang.org/protobuf/types/known/durationpb" ) // GetConsensusState returns the ConsensusState at the given height for the @@ -38,61 +32,3 @@ func (c *clientManager) GetClientState(identifier string) (modules.ClientState, return types.GetClientState(clientStore, identifier) } - -// GetHostConsensusState returns the ConsensusState at the given height for the -// host chain, the Pocket network. It then serialises this and packs it into a -// ConsensusState object for use in a WASM client -func (c *clientManager) GetHostConsensusState(height modules.Height) (modules.ConsensusState, error) { - blockStore := c.GetBus().GetPersistenceModule().GetBlockStore() - block, err := blockStore.GetBlock(height.GetRevisionHeight()) - if err != nil { - return nil, err - } - pocketConsState := &light_client_types.PocketConsensusState{ - Timestamp: block.BlockHeader.Timestamp, - StateHash: block.BlockHeader.StateHash, - StateTreeHashes: block.BlockHeader.StateTreeHashes, - NextValSetHash: block.BlockHeader.NextValSetHash, - } - consBz, err := codec.GetCodec().Marshal(pocketConsState) - if err != nil { - return nil, err - } - return types.NewConsensusState(consBz, uint64(pocketConsState.Timestamp.AsTime().UnixNano())), nil -} - -// GetHostClientState returns the ClientState at the given height for the host -// chain, the Pocket network. -// -// This function is used to validate the state of a client running on a -// counterparty chain. -func (c *clientManager) GetHostClientState(height modules.Height) (*light_client_types.PocketClientState, error) { - blockStore := c.GetBus().GetPersistenceModule().GetBlockStore() - block, err := blockStore.GetBlock(height.GetRevisionHeight()) - if err != nil { - return nil, err - } - rCtx, err := c.GetBus().GetPersistenceModule().NewReadContext(int64(height.GetRevisionHeight())) - if err != nil { - return nil, err - } - defer rCtx.Release() - unbondingBlocks, err := rCtx.GetIntParam(util_types.ValidatorUnstakingBlocksParamName, int64(height.GetRevisionHeight())) - if err != nil { - return nil, err - } - // TODO_AFTER(#705): use the actual MinimumBlockTime once set - unbondingPeriod := time.Minute * 15 * time.Duration(unbondingBlocks) // approx minutes per block * blocks - maxDrift := time.Minute * 15 // maximum 15 minutes future - return &light_client_types.PocketClientState{ - NetworkId: block.BlockHeader.NetworkId, - TrustLevel: &light_client_types.Fraction{Numerator: 2, Denominator: 3}, - TrustingPeriod: durationpb.New(unbondingPeriod), - UnbondingPeriod: durationpb.New(unbondingPeriod), - MaxClockDrift: durationpb.New(maxDrift), - LatestHeight: &types.Height{ - RevisionNumber: height.GetRevisionNumber(), - RevisionHeight: height.GetRevisionHeight(), - }, - }, nil -} diff --git a/ibc/types/proofs.go b/ibc/types/proofs.go new file mode 100644 index 000000000..9b0a5efb2 --- /dev/null +++ b/ibc/types/proofs.go @@ -0,0 +1,107 @@ +package types + +import ics23 "github.com/cosmos/ics23/go" + +var SmtSpec = &ProofSpec{ + LeafSpec: &LeafOp{ + Hash: HashOp_SHA256, + PrehashKey: HashOp_SHA256, + PrehashValue: HashOp_SHA256, + Length: LengthOp_NO_PREFIX, + Prefix: []byte{0}, + }, + InnerSpec: &InnerSpec{ + ChildOrder: []int32{0, 1}, + ChildSize: 32, + MinPrefixLength: 1, + MaxPrefixLength: 1, + EmptyChild: make([]byte, 32), + Hash: HashOp_SHA256, + }, + MaxDepth: 256, + PrehashKeyBeforeComparison: true, +} + +func (p *ProofSpec) ConvertToIcs23ProofSpec() *ics23.ProofSpec { + ics := new(ics23.ProofSpec) + ics.LeafSpec = p.LeafSpec.ConvertToIcs23LeafOp() + ics.InnerSpec = p.InnerSpec.ConvertToIcs23InnerSpec() + ics.MaxDepth = p.MaxDepth + ics.MinDepth = p.MinDepth + ics.PrehashKeyBeforeComparison = p.PrehashKeyBeforeComparison + return ics +} + +func (l *LeafOp) ConvertToIcs23LeafOp() *ics23.LeafOp { + ics := new(ics23.LeafOp) + ics.Hash = l.Hash.ConvertToIcs23HashOp() + ics.PrehashKey = l.PrehashKey.ConvertToIcs23HashOp() + ics.PrehashValue = l.PrehashValue.ConvertToIcs23HashOp() + ics.Length = l.Length.ConvertToIcs23LenthOp() + ics.Prefix = l.Prefix + return ics +} + +func (i *InnerSpec) ConvertToIcs23InnerSpec() *ics23.InnerSpec { + ics := new(ics23.InnerSpec) + ics.ChildOrder = i.ChildOrder + ics.MinPrefixLength = i.MinPrefixLength + ics.MaxPrefixLength = i.MaxPrefixLength + ics.EmptyChild = i.EmptyChild + ics.Hash = i.Hash.ConvertToIcs23HashOp() + return ics +} + +func (h HashOp) ConvertToIcs23HashOp() ics23.HashOp { + switch h { + case HashOp_NO_HASH: + return ics23.HashOp_NO_HASH + case HashOp_SHA256: + return ics23.HashOp_SHA256 + case HashOp_SHA512: + return ics23.HashOp_SHA512 + case HashOp_KECCAK: + return ics23.HashOp_KECCAK + case HashOp_RIPEMD160: + return ics23.HashOp_RIPEMD160 + case HashOp_BITCOIN: + return ics23.HashOp_BITCOIN + case HashOp_SHA512_256: + return ics23.HashOp_SHA512_256 + default: + panic("unknown hash op") + } +} + +func (l LengthOp) ConvertToIcs23LenthOp() ics23.LengthOp { + switch l { + case LengthOp_NO_PREFIX: + return ics23.LengthOp_NO_PREFIX + case LengthOp_VAR_PROTO: + return ics23.LengthOp_VAR_PROTO + case LengthOp_VAR_RLP: + return ics23.LengthOp_VAR_RLP + case LengthOp_FIXED32_BIG: + return ics23.LengthOp_FIXED32_BIG + case LengthOp_FIXED32_LITTLE: + return ics23.LengthOp_FIXED32_LITTLE + case LengthOp_FIXED64_BIG: + return ics23.LengthOp_FIXED64_BIG + case LengthOp_FIXED64_LITTLE: + return ics23.LengthOp_FIXED64_LITTLE + case LengthOp_REQUIRE_32_BYTES: + return ics23.LengthOp_REQUIRE_32_BYTES + case LengthOp_REQUIRE_64_BYTES: + return ics23.LengthOp_REQUIRE_64_BYTES + default: + panic("unknown length op") + } +} + +func (i *InnerOp) ConvertToIcs23InnerOp() *ics23.InnerOp { + ics := new(ics23.InnerOp) + ics.Hash = i.Hash.ConvertToIcs23HashOp() + ics.Prefix = i.Prefix + ics.Suffix = i.Suffix + return ics +} diff --git a/ibc/types/proto/proofs.proto b/ibc/types/proto/proofs.proto new file mode 100644 index 000000000..e79881ed4 --- /dev/null +++ b/ibc/types/proto/proofs.proto @@ -0,0 +1,222 @@ +syntax = "proto3"; + +// This file is a clone from the github.com/cosmos/ics23 repo, it has been +// cloned to be compiled with protoc generic. As such it will produce valid +// protobuf messages that can be serialised and cloned + +option go_package = "github.com/pokt-network/pocket/ibc/types"; + +enum HashOp { + // NO_HASH is the default if no data passed. Note this is an illegal argument some places. + NO_HASH = 0; + SHA256 = 1; + SHA512 = 2; + KECCAK = 3; + RIPEMD160 = 4; + BITCOIN = 5; // ripemd160(sha256(x)) + SHA512_256 = 6; +} + +// LengthOp defines how to process the key and value of the LeafOp +// to include length information. After encoding the length with the given +// algorithm, the length will be prepended to the key and value bytes. +// (Each one with it's own encoded length) +enum LengthOp { + // NO_PREFIX don't include any length info + NO_PREFIX = 0; + // VAR_PROTO uses protobuf (and go-amino) varint encoding of the length + VAR_PROTO = 1; + // VAR_RLP uses rlp int encoding of the length + VAR_RLP = 2; + // FIXED32_BIG uses big-endian encoding of the length as a 32 bit integer + FIXED32_BIG = 3; + // FIXED32_LITTLE uses little-endian encoding of the length as a 32 bit integer + FIXED32_LITTLE = 4; + // FIXED64_BIG uses big-endian encoding of the length as a 64 bit integer + FIXED64_BIG = 5; + // FIXED64_LITTLE uses little-endian encoding of the length as a 64 bit integer + FIXED64_LITTLE = 6; + // REQUIRE_32_BYTES is like NONE, but will fail if the input is not exactly 32 bytes (sha256 output) + REQUIRE_32_BYTES = 7; + // REQUIRE_64_BYTES is like NONE, but will fail if the input is not exactly 64 bytes (sha512 output) + REQUIRE_64_BYTES = 8; +} + +// ExistenceProof takes a key and a value and a set of steps to perform on it. +// The result of peforming all these steps will provide a "root hash", which can +// be compared to the value in a header. +// +// Since it is computationally infeasible to produce a hash collission for any of the used +// cryptographic hash functions, if someone can provide a series of operations to transform +// a given key and value into a root hash that matches some trusted root, these key and values +// must be in the referenced merkle tree. +// +// The only possible issue is maliablity in LeafOp, such as providing extra prefix data, +// which should be controlled by a spec. Eg. with lengthOp as NONE, +// prefix = FOO, key = BAR, value = CHOICE +// and +// prefix = F, key = OOBAR, value = CHOICE +// would produce the same value. +// +// With LengthOp this is tricker but not impossible. Which is why the "leafPrefixEqual" field +// in the ProofSpec is valuable to prevent this mutability. And why all trees should +// length-prefix the data before hashing it. +message ExistenceProof { + bytes key = 1; + bytes value = 2; + LeafOp leaf = 3; + repeated InnerOp path = 4; +} + +// NonExistenceProof takes a proof of two neighbors, one left of the desired key, +// one right of the desired key. If both proofs are valid AND they are neighbors, +// then there is no valid proof for the given key. +message NonExistenceProof { + bytes key = 1; // TODO: remove this as unnecessary??? we prove a range + ExistenceProof left = 2; + ExistenceProof right = 3; +} + +// CommitmentProof is either an ExistenceProof or a NonExistenceProof, or a Batch of such messages +message CommitmentProof { + oneof proof { + ExistenceProof exist = 1; + NonExistenceProof nonexist = 2; + BatchProof batch = 3; + CompressedBatchProof compressed = 4; + } +} + +// LeafOp represents the raw key-value data we wish to prove, and +// must be flexible to represent the internal transformation from +// the original key-value pairs into the basis hash, for many existing +// merkle trees. +// +// key and value are passed in. So that the signature of this operation is: +// leafOp(key, value) -> output +// +// To process this, first prehash the keys and values if needed (ANY means no hash in this case): +// hkey = prehashKey(key) +// hvalue = prehashValue(value) +// +// Then combine the bytes, and hash it +// output = hash(prefix || length(hkey) || hkey || length(hvalue) || hvalue) +message LeafOp { + HashOp hash = 1; + HashOp prehash_key = 2; + HashOp prehash_value = 3; + LengthOp length = 4; + // prefix is a fixed bytes that may optionally be included at the beginning to differentiate + // a leaf node from an inner node. + bytes prefix = 5; +} + +// InnerOp represents a merkle-proof step that is not a leaf. +// It represents concatenating two children and hashing them to provide the next result. +// +// The result of the previous step is passed in, so the signature of this op is: +// innerOp(child) -> output +// +// The result of applying InnerOp should be: +// output = op.hash(op.prefix || child || op.suffix) +// +// where the || operator is concatenation of binary data, +// and child is the result of hashing all the tree below this step. +// +// Any special data, like prepending child with the length, or prepending the entire operation with +// some value to differentiate from leaf nodes, should be included in prefix and suffix. +// If either of prefix or suffix is empty, we just treat it as an empty string +message InnerOp { + HashOp hash = 1; + bytes prefix = 2; + bytes suffix = 3; +} + +// ProofSpec defines what the expected parameters are for a given proof type. +// This can be stored in the client and used to validate any incoming proofs. +// +// verify(ProofSpec, Proof) -> Proof | Error +// +// As demonstrated in tests, if we don't fix the algorithm used to calculate the +// LeafHash for a given tree, there are many possible key-value pairs that can +// generate a given hash (by interpretting the preimage differently). +// We need this for proper security, requires client knows a priori what +// tree format server uses. But not in code, rather a configuration object. +message ProofSpec { + // any field in the ExistenceProof must be the same as in this spec. + // except Prefix, which is just the first bytes of prefix (spec can be longer) + LeafOp leaf_spec = 1; + InnerSpec inner_spec = 2; + // max_depth (if > 0) is the maximum number of InnerOps allowed (mainly for fixed-depth tries) + int32 max_depth = 3; + // min_depth (if > 0) is the minimum number of InnerOps allowed (mainly for fixed-depth tries) + int32 min_depth = 4; + // prehash_key_before_comparison is a flag that indicates whether to use the + // prehash_key specified by LeafOp to compare lexical ordering of keys for + // non-existence proofs. + bool prehash_key_before_comparison = 5; +} + +// InnerSpec contains all store-specific structure info to determine if two proofs from a +// given store are neighbors. +// +// This enables: +// +// isLeftMost(spec: InnerSpec, op: InnerOp) +// isRightMost(spec: InnerSpec, op: InnerOp) +// isLeftNeighbor(spec: InnerSpec, left: InnerOp, right: InnerOp) +message InnerSpec { + // Child order is the ordering of the children node, must count from 0 + // iavl tree is [0, 1] (left then right) + // merk is [0, 2, 1] (left, right, here) + repeated int32 child_order = 1; + int32 child_size = 2; + int32 min_prefix_length = 3; + int32 max_prefix_length = 4; + // empty child is the prehash image that is used when one child is nil (eg. 20 bytes of 0) + bytes empty_child = 5; + // hash is the algorithm that must be used for each InnerOp + HashOp hash = 6; +} + +// BatchProof is a group of multiple proof types than can be compressed +message BatchProof { + repeated BatchEntry entries = 1; +} + +// Use BatchEntry not CommitmentProof, to avoid recursion +message BatchEntry { + oneof proof { + ExistenceProof exist = 1; + NonExistenceProof nonexist = 2; + } +} + +// ====== all items here are compressed forms ======= + +message CompressedBatchProof { + repeated CompressedBatchEntry entries = 1; + repeated InnerOp lookup_inners = 2; +} + +// Use BatchEntry not CommitmentProof, to avoid recursion +message CompressedBatchEntry { + oneof proof { + CompressedExistenceProof exist = 1; + CompressedNonExistenceProof nonexist = 2; + } +} + +message CompressedExistenceProof { + bytes key = 1; + bytes value = 2; + LeafOp leaf = 3; + // these are indexes into the lookup_inners table in CompressedBatchProof + repeated int32 path = 4; +} + +message CompressedNonExistenceProof { + bytes key = 1; // TODO: remove this as unnecessary??? we prove a range + CompressedExistenceProof left = 2; + CompressedExistenceProof right = 3; +} diff --git a/shared/modules/ibc_client_module.go b/shared/modules/ibc_client_module.go index 1f1db8f13..80ab7d7e2 100644 --- a/shared/modules/ibc_client_module.go +++ b/shared/modules/ibc_client_module.go @@ -59,6 +59,16 @@ type ClientManager interface { // GetHostConsensusState returns the ConsensusState at the given height for the host chain GetHostConsensusState(height Height) (ConsensusState, error) + + // GetHostClientState returns the ClientState at the provieded height for the host chain + GetHostClientState(height Height) (ClientState, error) + + // GetCurrentHeight returns the current IBC client height of the network + GetCurrentHeight() Height + + // VerifyHostClientState verifies the client state for a client running on a + // counterparty chain is valid, checking against the current host client state + VerifyHostClientState(ClientState) error } // ClientState is an interface that defines the methods required by a clients @@ -69,6 +79,7 @@ type ClientManager interface { type ClientState interface { proto.Message + GetData() []byte ClientType() string GetLatestHeight() Height Validate() error