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 all commits
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
2 changes: 2 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,8 @@ protogen_local: go_protoc-go-inject-tag ## Generate go structures for all of the
# IBC
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 -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
74 changes: 74 additions & 0 deletions ibc/client/events.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package client

import (
client_types "github.com/pokt-network/pocket/ibc/client/types"
"github.com/pokt-network/pocket/shared/codec"
core_types "github.com/pokt-network/pocket/shared/core/types"
"github.com/pokt-network/pocket/shared/modules"
)

// emitCreateClientEvent emits a create client event
func (c *clientManager) emitCreateClientEvent(clientId string, clientState modules.ClientState) error {
return c.GetBus().GetEventLogger().EmitEvent(
&core_types.IBCEvent{
Topic: client_types.EventTopicCreateClient,
Attributes: []*core_types.Attribute{
core_types.NewAttribute(client_types.AttributeKeyClientID, []byte(clientId)),
core_types.NewAttribute(client_types.AttributeKeyClientType, []byte(clientState.ClientType())),
core_types.NewAttribute(client_types.AttributeKeyConsensusHeight, []byte(clientState.GetLatestHeight().ToString())),
},
},
)
}

// emitUpdateClientEvent emits an update client event
func (c *clientManager) emitUpdateClientEvent(
clientId, clientType string,
consensusHeight modules.Height,
clientMessage modules.ClientMessage,
) error {
// Marshall the client message
clientMsgBz, err := codec.GetCodec().Marshal(clientMessage)
if err != nil {
return err
}

return c.GetBus().GetEventLogger().EmitEvent(
&core_types.IBCEvent{
Topic: client_types.EventTopicUpdateClient,
Attributes: []*core_types.Attribute{
core_types.NewAttribute(client_types.AttributeKeyClientID, []byte(clientId)),
core_types.NewAttribute(client_types.AttributeKeyClientType, []byte(clientType)),
core_types.NewAttribute(client_types.AttributeKeyConsensusHeight, []byte(consensusHeight.ToString())),
core_types.NewAttribute(client_types.AttributeKeyHeader, clientMsgBz),
},
},
)
}

// emitUpgradeClientEvent emits an upgrade client event
func (c *clientManager) emitUpgradeClientEvent(clientId string, clientState modules.ClientState) error {
return c.GetBus().GetEventLogger().EmitEvent(
&core_types.IBCEvent{
Topic: client_types.EventTopicUpdateClient,
Attributes: []*core_types.Attribute{
core_types.NewAttribute(client_types.AttributeKeyClientID, []byte(clientId)),
core_types.NewAttribute(client_types.AttributeKeyClientType, []byte(clientState.ClientType())),
core_types.NewAttribute(client_types.AttributeKeyConsensusHeight, []byte(clientState.GetLatestHeight().ToString())),
},
},
)
}

// emitSubmitMisbehaviourEvent emits a submit misbehaviour event
func (c *clientManager) emitSubmitMisbehaviourEvent(clientId string, clientState modules.ClientState) error {
return c.GetBus().GetEventLogger().EmitEvent(
&core_types.IBCEvent{
Topic: client_types.EventTopicSubmitMisbehaviour,
Attributes: []*core_types.Attribute{
core_types.NewAttribute(client_types.AttributeKeyClientID, []byte(clientId)),
core_types.NewAttribute(client_types.AttributeKeyClientType, []byte(clientState.ClientType())),
},
},
)
}
152 changes: 152 additions & 0 deletions ibc/client/introspect.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
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/proto"
"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 !proto.Equal(poktCounter.ProofSpec, poktHost.ProofSpec) {
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
}
42 changes: 42 additions & 0 deletions ibc/client/light_clients/types/fraction.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package types

type ord int

const (
lt ord = iota
eq
gt
)

func (f *Fraction) LT(other *Fraction) bool {
return f.compare(other) == lt
}

func (f *Fraction) GT(other *Fraction) bool {
return f.compare(other) == gt
}

func (f *Fraction) EQ(other *Fraction) bool {
return f.compare(other) == eq
}

func (f *Fraction) LTE(other *Fraction) bool {
return f.compare(other) != gt
}

func (f *Fraction) GTE(other *Fraction) bool {
return f.compare(other) != lt
}

func (f *Fraction) compare(other *Fraction) ord {
comDenom := f.Denominator * other.Denominator
aNum := f.Numerator * (comDenom / f.Denominator)
bNum := other.Numerator * (comDenom / other.Denominator)
if aNum < bNum {
return lt
}
if aNum > bNum {
return gt
}
return eq
}
58 changes: 58 additions & 0 deletions ibc/client/light_clients/types/proto/pocket.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
syntax = "proto3";

package core;

option go_package = "github.com/pokt-network/pocket/ibc/client/light_client/types";

import "google/protobuf/timestamp.proto";
import "google/protobuf/duration.proto";
import "proofs.proto";
import "wasm.proto";
import "block.proto";

// PocketConsensusState defines the ibc client consensus state for Pocket
message PocketConsensusState {
google.protobuf.Timestamp timestamp = 1; // unixnano timestamp of the block
string state_hash = 2; // hex encoded root state tree hash
map<string, string> state_tree_hashes = 3; // map of state tree hashes; map[TreeName]hex(TreeRootHash)
string next_val_set_hash = 4; // hex encoded sha3_256 hash of the next validator set
}

// PocketClientState defines the ibc client state for Pocket
message PocketClientState {
string network_id = 1; // network identifier string
Fraction trust_level = 2; // fraction of the validator set that is required to sign off on new blocks
google.protobuf.Duration trusting_period = 3; // the duration of the period since the LastestTimestamp where the state can be upgraded
google.protobuf.Duration unbonding_period = 4; // the duration of the staking unbonding period
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
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
}

// Fraction defines a positive rational number
message Fraction {
uint64 numerator = 1;
uint64 denominator = 2;
}

// PocketHeader defines the ibc client header for the Pocket network
message PocketHeader {
BlockHeader block_header = 1; // pocket consensus block header
ValidatorSet validator_set = 2; // new validator set for the updating client
// the consensus state at trusted_height must be within the unbonding_period to correctly verify the new header
Height trusted_height = 3; // height of the ConsensusState stored used to verify the new header
// trusted_validators must hash to the ConsensusState.NextValSetHash as this is the last trusted validator set
ValidatorSet trusted_validators = 4; // already stored validator set used to verify the update
}

// PocketMisbehaviour defines the ibc client misbehaviour for the Pocket network
//
// The two conflicting headers are submitted as evidence to verify the Pocket
// network has misbehaved.
message PocketMisbehaviour {
PocketHeader header_1 = 1; // the first header
PocketHeader header_2 = 2; // the second header
}
34 changes: 34 additions & 0 deletions ibc/client/queries.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package client

import (
"github.com/pokt-network/pocket/ibc/client/types"
"github.com/pokt-network/pocket/ibc/path"
core_types "github.com/pokt-network/pocket/shared/core/types"
"github.com/pokt-network/pocket/shared/modules"
)

// GetConsensusState returns the ConsensusState at the given height for the
// stored client with the given identifier
func (c *clientManager) GetConsensusState(
identifier string, height modules.Height,
) (modules.ConsensusState, error) {
// Retrieve the clientId prefixed client store
prefixed := path.ApplyPrefix(core_types.CommitmentPrefix(path.KeyClientStorePrefix), identifier)
clientStore, err := c.GetBus().GetIBCHost().GetProvableStore(string(prefixed))
if err != nil {
return nil, err
}

return types.GetConsensusState(clientStore, height)
}

// GetClientState returns the ClientState for the stored client with the given identifier
func (c *clientManager) GetClientState(identifier string) (modules.ClientState, error) {
// Retrieve the client store
clientStore, err := c.GetBus().GetIBCHost().GetProvableStore(path.KeyClientStorePrefix)
if err != nil {
return nil, err
}

return types.GetClientState(clientStore, identifier)
}
Loading