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

[IBC] Implement the ICS-02 Client interfaces #933

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
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
h5law marked this conversation as resolved.
Show resolved Hide resolved
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
h5law marked this conversation as resolved.
Show resolved Hide resolved
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
h5law marked this conversation as resolved.
Show resolved Hide resolved
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.
Comment on lines +42 to +43
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment is confusing me.

Is the purpose of this to verify the state of pocket on another chain, or the state of another chain on pocket?

Copy link
Contributor Author

@h5law h5law Aug 9, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this function gets the current network state and is used in the verification of a pocket client running on another network. I will try to clarify the comment

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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#705 will likely be done by the time this is merged in

@red-0ne FYI.

blockTime := time.Minute * 15
unbondingPeriod := blockTime * time.Duration(unbondingBlocks) // approx minutes per block * blocks
h5law marked this conversation as resolved.
Show resolved Hide resolved
pocketClient := &light_client_types.PocketClientState{
NetworkId: block.BlockHeader.NetworkId,
TrustLevel: &light_client_types.Fraction{Numerator: 2, Denominator: 3},
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you sure we don't need a +1 here to guarantee a > instead of >=?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean we could just do our check of the trust level to be >

TrustingPeriod: durationpb.New(unbondingPeriod),
UnbondingPeriod: durationpb.New(unbondingPeriod),
MaxClockDrift: durationpb.New(blockTime), // DISCUSS: What is a reasonable MaxClockDrift?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Anything < one block.

I'd just do like 0.5 * blockTime, move 0.5 into a constant and explain.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have actually gone with tendermints approach here and left it at 10s if a header is 7.5 minutes in the future we probably should be treating the client as going bad.

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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TOL(Thinking Out Loud): Can we replace Host with Pocket where appropriate to make this easier to understand or should we maintain the ibc-go standards?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I dont mind either way, but we have the IBCHost submodule which refers to the node running the IBC software. In that context it makes sense to refer to this node's network as the host network. But otherwise Pocket is more verbose.

WDYT swap or stay?

// 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 {
h5law marked this conversation as resolved.
Show resolved Hide resolved
return err
}
poktCounter := new(light_client_types.PocketClientState)
h5law marked this conversation as resolved.
Show resolved Hide resolved
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")
h5law marked this conversation as resolved.
Show resolved Hide resolved
}
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}) ||
h5law marked this conversation as resolved.
Show resolved Hide resolved
poktCounter.TrustLevel.GT(&light_client_types.Fraction{Numerator: 1, Denominator: 1}) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When/how can > 1 happen?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It cant in reality but this just does the check that 2/3 <= x <= 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
h5law marked this conversation as resolved.
Show resolved Hide resolved

return nil
}

// GetCurrentHeight returns the current IBC client height of the network
// TODO_AFTER(#882): Use actual revision number
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cc @0xBigBoss - just FYI

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
h5law marked this conversation as resolved.
Show resolved Hide resolved

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 {
h5law marked this conversation as resolved.
Show resolved Hide resolved
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
}
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,
h5law marked this conversation as resolved.
Show resolved Hide resolved
) (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