Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP][IBC] Implement ICS-02 Client Semantics #916

Draft
wants to merge 23 commits into
base: ibc/proto-exploration-ics23
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Add VerifyHostClientState method
  • Loading branch information
h5law committed Jul 24, 2023
commit 9f7f2bbdfeca91debab77711c54d18daf8fca16f
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -321,7 +321,7 @@ protogen_local: go_protoc-go-inject-tag ## Generate go structures for all of the
make copy_ics23_proto
$(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"

Expand Down
151 changes: 151 additions & 0 deletions ibc/client/introspect.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
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 {
height, err := c.GetCurrentHeight()
if err != nil {
return err
}
hostState, err := c.GetHostClientState(height)
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(#882): Use actual revision number
func (h *clientManager) GetCurrentHeight() (modules.Height, error) {
currHeight := h.GetBus().GetConsensusModule().CurrentHeight()
rCtx, err := h.GetBus().GetPersistenceModule().NewReadContext(int64(currHeight))
if err != nil {
return nil, err
}
defer rCtx.Release()
revNum := rCtx.GetRevisionNumber(int64(currHeight))
return &types.Height{
RevisionNumber: revNum,
RevisionHeight: currHeight,
}, nil
}
3 changes: 2 additions & 1 deletion ibc/client/light_clients/types/proto/pocket.proto
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand All @@ -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
}
Expand Down
64 changes: 0 additions & 64 deletions ibc/client/queries.go
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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
}
10 changes: 8 additions & 2 deletions ibc/path/keys_ics02.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,18 @@ func FullClientStateKey(clientID string) []byte {
return fullClientKey(clientID, KeyClientState)
}

// ClientStatePath takes a client identifier and returns a Path string where it can be accessed
// clientStatePath takes a client identifier and returns a Path string where it can be accessed
// within the client store
func ClientStatePath(clientID string) string {
func clientStatePath(clientID string) string {
return clientPath(clientID, KeyClientState)
}

// ClientStateKey takes a client identifier and returns a key where it can be accessed
// within the client store
func ClientStateKey(clientID string) []byte {
return []byte(clientStatePath(clientID))
}

// consensusStatePath returns the suffix store key for the consensus state at a
// particular height stored in a client prefixed store.
func consensusStatePath(height string) string {
Expand Down
5 changes: 5 additions & 0 deletions persistence/gov.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ func (p *PostgresContext) GetVersionAtHeight(height int64) (string, error) {
return "", nil
}

// TODO(#882): Implement this function
func (p *PostgresContext) GetRevisionNumber(height int64) uint64 {
return 1
}

// TODO: Implement this function
func (p *PostgresContext) GetSupportedChains(height int64) ([]string, error) {
// This is a placeholder function for the RPC endpoint "v1/query/supportedchains"
Expand Down
14 changes: 14 additions & 0 deletions shared/modules/ibc_client_module.go
Original file line number Diff line number Diff line change
Expand Up @@ -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, error)

// 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
Expand All @@ -69,6 +79,8 @@ type ClientManager interface {
type ClientState interface {
proto.Message

GetData() []byte
GetWasmChecksum() []byte
ClientType() string
GetLatestHeight() Height
Validate() error
Expand Down Expand Up @@ -155,6 +167,7 @@ type ClientState interface {
type ConsensusState interface {
proto.Message

GetData() []byte
ClientType() string
GetTimestamp() uint64
ValidateBasic() error
Expand All @@ -171,6 +184,7 @@ type ConsensusState interface {
type ClientMessage interface {
proto.Message

GetData() []byte
ClientType() string
ValidateBasic() error
}
Expand Down
1 change: 1 addition & 0 deletions shared/modules/persistence_module.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ type PersistenceReadContext interface {

// Version queries
GetVersionAtHeight(height int64) (string, error) // TODO: Implement this
GetRevisionNumber(height int64) uint64 // TODO(#882): Implement this

// Supported Chains Queries
GetSupportedChains(height int64) ([]string, error) // TODO: Implement this
Expand Down