From 58a78c1ccbb5cdc043b0af733ef80082df70c917 Mon Sep 17 00:00:00 2001 From: harry <53987565+h5law@users.noreply.github.com> Date: Tue, 11 Jul 2023 09:26:28 +0100 Subject: [PATCH 01/23] Create ClientManager interface and Client interface definitions --- ibc/client/submodule.go | 84 +++++++++++ ibc/client/types/events.go | 17 +++ ibc/host/submodule.go | 16 +- ibc/path/keys_ics02.go | 10 +- runtime/bus.go | 4 + shared/core/types/proto/ibc_events.proto | 13 ++ shared/modules/bus_module.go | 1 + shared/modules/ibc_client_module.go | 172 ++++++++++++++++++++++ shared/modules/ibc_host_module.go | 177 +---------------------- shared/modules/ibc_module.go | 153 +++++++++++++++++++- 10 files changed, 457 insertions(+), 190 deletions(-) create mode 100644 ibc/client/submodule.go create mode 100644 ibc/client/types/events.go create mode 100644 shared/modules/ibc_client_module.go diff --git a/ibc/client/submodule.go b/ibc/client/submodule.go new file mode 100644 index 000000000..9c244b8c1 --- /dev/null +++ b/ibc/client/submodule.go @@ -0,0 +1,84 @@ +package client + +import ( + "github.com/pokt-network/pocket/ibc/path" + core_types "github.com/pokt-network/pocket/shared/core/types" + "github.com/pokt-network/pocket/shared/modules" + "github.com/pokt-network/pocket/shared/modules/base_modules" +) + +var _ modules.ClientManager = &clientManager{} + +type clientManager struct { + base_modules.IntegrableModule + + logger *modules.Logger +} + +func Create(bus modules.Bus, options ...modules.ClientManagerOption) (modules.ClientManager, error) { + return new(clientManager).Create(bus, options...) +} + +// WithLogger sets the logger for the clientManager +func WithLogger(logger *modules.Logger) modules.ClientManagerOption { + return func(m modules.ClientManager) { + if mod, ok := m.(*clientManager); ok { + mod.logger = logger + } + } +} + +func (*clientManager) Create(bus modules.Bus, options ...modules.ClientManagerOption) (modules.ClientManager, error) { + c := &clientManager{} + + for _, option := range options { + option(c) + } + + c.logger.Info().Msg("👨 Creating Client Manager 👨") + + bus.RegisterModule(c) + + return c, nil +} + +func (c *clientManager) GetModuleName() string { return modules.ClientManagerModuleName } + +// CreateClient creates a new client with the given client state and initial +// consensus state and initialises it with a unique identifier in the IBC client +// store and emits an event to the Event Logger +func (c *clientManager) CreateClient( + clientState modules.ClientState, consensusState modules.ConsensusState, +) (string, error) { + identifier := path.GenerateClientIdentifier() + return identifier, nil +} + +// UpdateClient updates an existing client with the given identifer using the +// ClientMessage provided +func (c *clientManager) UpdateClient( + identifier string, clientMessage modules.ClientMessage, +) error { + return nil +} + +// QueryConsensusState returns the ConsensusState at the given height for the +// stored client with the given identifier +func (c *clientManager) QueryConsensusState( + identifier string, height *core_types.Height, +) (modules.ConsensusState, error) { + return nil, nil +} + +// QueryClientState returns the ClientState for the stored client with the given identifier +func (c *clientManager) QueryClientState(identifier string) (modules.ClientState, error) { + return nil, nil +} + +// SubmitMisbehaviour submits evidence for a misbehaviour to the client, possibly +// invalidating previously valid state roots and thus preventing future updates +func (c *clientManager) SubmitMisbehaviour( + identifier string, clientMessage modules.ClientMessage, +) error { + return nil +} diff --git a/ibc/client/types/events.go b/ibc/client/types/events.go new file mode 100644 index 000000000..0900003c3 --- /dev/null +++ b/ibc/client/types/events.go @@ -0,0 +1,17 @@ +package types + +const ( + // Event topics for the events emitted by the Client submodule + EventTopicCreateClient = "create_client" + EventTopicUpdateClient = "update_client" + EventTopicUpgradeClient = "upgrade_client" + EventTopicSubmitMisbehaviour = "client_misbehaviour" +) + +var ( + // Attribute keys for the events emitted by the Client submodule + AttributeKeyClientID = []byte("client_id") + AttributeKeyClientType = []byte("client_type") + AttributeKeyConsensusHeight = []byte("consensus_height") + AttributeKeyHeader = []byte("header") +) diff --git a/ibc/host/submodule.go b/ibc/host/submodule.go index 655985b73..ad95e72a9 100644 --- a/ibc/host/submodule.go +++ b/ibc/host/submodule.go @@ -4,6 +4,7 @@ import ( "errors" "time" + "github.com/pokt-network/pocket/ibc/client" "github.com/pokt-network/pocket/ibc/events" "github.com/pokt-network/pocket/ibc/store" "github.com/pokt-network/pocket/runtime/configs" @@ -20,10 +21,6 @@ type ibcHost struct { cfg *configs.IBCHostConfig logger *modules.Logger storesDir string - - // only a single bulk store cacher and event logger are allowed - bsc modules.BulkStoreCacher - em modules.EventLogger } func Create(bus modules.Bus, config *configs.IBCHostConfig, options ...modules.IBCHostOption) (modules.IBCHostSubmodule, error) { @@ -59,7 +56,7 @@ func (*ibcHost) Create(bus modules.Bus, config *configs.IBCHostConfig, options . bus.RegisterModule(h) - bsc, err := store.Create(h.GetBus(), + _, err := store.Create(h.GetBus(), h.cfg.BulkStoreCacher, store.WithLogger(h.logger), store.WithStoresDir(h.storesDir), @@ -68,13 +65,16 @@ func (*ibcHost) Create(bus modules.Bus, config *configs.IBCHostConfig, options . if err != nil { return nil, err } - h.bsc = bsc - em, err := events.Create(h.GetBus(), events.WithLogger(h.logger)) + _, err = events.Create(h.GetBus(), events.WithLogger(h.logger)) + if err != nil { + return nil, err + } + + _, err = client.Create(h.GetBus(), client.WithLogger(h.logger)) if err != nil { return nil, err } - h.em = em return h, nil } diff --git a/ibc/path/keys_ics02.go b/ibc/path/keys_ics02.go index 5cce681be..d829f8e0c 100644 --- a/ibc/path/keys_ics02.go +++ b/ibc/path/keys_ics02.go @@ -22,23 +22,23 @@ func ClientStatePath(clientID string) string { // consensusStatePath returns the suffix store key for the consensus state at a // particular height stored in a client prefixed store. -func consensusStatePath(height uint64) string { - return fmt.Sprintf("%s/%d", KeyConsensusStatePrefix, height) +func consensusStatePath(height string) string { + return fmt.Sprintf("%s/%s", KeyConsensusStatePrefix, height) } // fullConsensusStatePath takes a client identifier and returns a Path under which to // store the consensus state of a client. -func fullConsensusStatePath(clientID string, height uint64) string { +func fullConsensusStatePath(clientID, height string) string { return fullClientPath(clientID, consensusStatePath(height)) } // FullConsensusStateKey returns the store key for the consensus state of a particular client. -func FullConsensusStateKey(clientID string, height uint64) []byte { +func FullConsensusStateKey(clientID, height string) []byte { return []byte(fullConsensusStatePath(clientID, height)) } // ConsensusStatePath takes a client identifier and height and returns the Path where the consensus // state can be accessed in the client store -func ConsensusStatePath(clientID string, height uint64) string { +func ConsensusStatePath(clientID, height string) string { return clientPath(clientID, consensusStatePath(height)) } diff --git a/runtime/bus.go b/runtime/bus.go index 9d7a6e05f..7052e287f 100644 --- a/runtime/bus.go +++ b/runtime/bus.go @@ -143,6 +143,10 @@ func (m *bus) GetEventLogger() modules.EventLogger { return getModuleFromRegistry[modules.EventLogger](m, modules.EventLoggerModuleName) } +func (m *bus) GetClientManager() modules.ClientManager { + return getModuleFromRegistry[modules.ClientManager](m, modules.ClientManagerModuleName) +} + func (m *bus) GetCurrentHeightProvider() modules.CurrentHeightProvider { return getModuleFromRegistry[modules.CurrentHeightProvider](m, modules.CurrentHeightProviderSubmoduleName) } diff --git a/shared/core/types/proto/ibc_events.proto b/shared/core/types/proto/ibc_events.proto index 15041214a..7809bb4a0 100644 --- a/shared/core/types/proto/ibc_events.proto +++ b/shared/core/types/proto/ibc_events.proto @@ -19,3 +19,16 @@ message IBCEvent { uint64 height = 2; repeated Attribute attributes = 3; } + +// Height represents the height of an IBC client's state +message Height { + uint64 revision_number = 1; + uint64 revision_height = 2; +} + +// Ord is an enum representing the equality of two heights +enum Ord { + LT = 0; + EQ = 1; + GT = 2; +} diff --git a/shared/modules/bus_module.go b/shared/modules/bus_module.go index 3981f9f8c..14b329643 100644 --- a/shared/modules/bus_module.go +++ b/shared/modules/bus_module.go @@ -45,4 +45,5 @@ type Bus interface { GetIBCHost() IBCHostSubmodule GetBulkStoreCacher() BulkStoreCacher GetEventLogger() EventLogger + GetClientManager() ClientManager } diff --git a/shared/modules/ibc_client_module.go b/shared/modules/ibc_client_module.go new file mode 100644 index 000000000..10a0f77ff --- /dev/null +++ b/shared/modules/ibc_client_module.go @@ -0,0 +1,172 @@ +package modules + +//go:generate mockgen -destination=./mocks/ibc_client_module_mock.go github.com/pokt-network/pocket/shared/modules ClientManager + +import ( + "google.golang.org/protobuf/proto" +) + +type ClientStatus string + +const ( + ClientManagerModuleName = "client_manager" + + // Client Status types + ActiveStatus ClientStatus = "active" + ExpiredStatus ClientStatus = "expired" + FrozenStatus ClientStatus = "frozen" + UnauthorizedStatus ClientStatus = "unauthorized" +) + +type ClientManagerOption func(ClientManager) + +type clientManagerFactory = FactoryWithOptions[ClientManager, ClientManagerOption] + +// ClientManager is the interface that defines the methods needed to interact with an IBC light client +// it manages the different lifecycle methods for the different clients +// https://github.com/cosmos/ibc/tree/main/spec/core/ics-002-client-semantics +type ClientManager interface { + Submodule + clientManagerFactory + + // === Client Lifecycle Management === + + // CreateClient creates a new client with the given client state and initial consensus state + // and initialises its unique identifier in the IBC store + CreateClient(ClientState, ConsensusState) (string, error) + + // UpdateClient updates an existing client with the given ClientMessage, given that + // the ClientMessage can be verified using the existing ClientState and ConsensusState + UpdateClient(identifier string, clientMessage ClientMessage) error + + // QueryConsensusState returns the ConsensusState at the given height for the given client + QueryConsensusState(identifier string, height Height) (ConsensusState, error) + + // QueryClientState returns the ClientState for the given client + QueryClientState(identifier string) (ClientState, error) + + // SubmitMisbehaviour submits evidence for a misbehaviour to the client, possibly invalidating + // previously valid state roots and thus preventing future updates + SubmitMisbehaviour(identifier string, clientMessage ClientMessage) error +} + +// ClientState is an interface that defines the methods required by a clients +// implementation of their own client state object +// +// ClientState is an opaque data structure defined by a client type. It may keep +// arbitrary internal state to track verified roots and past misbehaviours. +type ClientState interface { + proto.Message + + ClientType() string + GetLatestHeight() Height + Validate() error + + // Status returns the status of the client. Only Active clients are allowed + // to process packets. + Status(clientStore ProvableStore) ClientStatus + + // GetTimestampAtHeight must return the timestamp for the consensus state + // associated with the provided height. + GetTimestampAtHeight(clientStore ProvableStore, height Height) (uint64, error) + + // Initialise is called upon client creation, it allows the client to perform + // validation on the initial consensus state and set the client state, + // consensus state and any client-specific metadata necessary for correct + // light client operation in the provided client store. + Initialise(clientStore ProvableStore, consensusState ConsensusState) error + + // VerifyMembership is a generic proof verification method which verifies a + // proof of the existence of a value at a given CommitmentPath at the + // specified height. The path is expected to be the full CommitmentPath + VerifyMembership( + clientStore ProvableStore, + height Height, + delayTimePeriod, delayBlockPeriod uint64, + proof, path, value []byte, + ) error + + // VerifyNonMembership is a generic proof verification method which verifies + // the absence of a given CommitmentPath at a specified height. The path is + // expected to be the full CommitmentPath + VerifyNonMembership( + clientStore ProvableStore, + height Height, + delayTimePeriod, delayBlockPeriod uint64, + proof, path []byte, + ) error + + // VerifyClientMessage verifies a ClientMessage. A ClientMessage could be a + // Header, Misbehaviour, or batch update. It must handle each type of + // ClientMessage appropriately. Calls to CheckForMisbehaviour, UpdateState, + // and UpdateStateOnMisbehaviour will assume that the content of the + // ClientMessage has been verified and can be trusted. An error should be + // returned if the ClientMessage fails to verify. + VerifyClientMessage(clientStore ProvableStore, clientMsg ClientMessage) error + + // Checks for evidence of a misbehaviour in Header or Misbehaviour type. + // It assumes the ClientMessage has already been verified. + CheckForMisbehaviour(clientStore ProvableStore, clientMsg ClientMessage) bool + + // UpdateStateOnMisbehaviour should perform appropriate state changes on a + // client state given that misbehaviour has been detected and verified + UpdateStateOnMisbehaviour(clientStore ProvableStore, clientMsg ClientMessage) + + // UpdateState updates and stores as necessary any associated information + // for an IBC client, such as the ClientState and corresponding ConsensusState. + // Upon successful update, a list of consensus heights is returned. + // It assumes the ClientMessage has already been verified. + UpdateState(clientStore ProvableStore, clientMsg ClientMessage) []Height +} + +// ConsensusState is an interface that defines the methods required by a clients +// implementation of their own consensus state object +// +// ConsensusState is an opaque data structure defined by a client type, used by the +// validity predicate to verify new commits & state roots. Likely the structure will +// contain the last commit produced by the consensus process, including signatures +// and validator set metadata. +type ConsensusState interface { + proto.Message + + ClientType() string + GetTimestamp() uint64 + ValidateBasic() error +} + +// ClientMessage is an interface that defines the methods required by a clients +// implementation of their own client message object +// +// A ClientMessage is an opaque data structure defined by a client type which +// provides information to update the client. ClientMessages can be submitted +// to an associated client to add new ConsensusState(s) and/or update the +// ClientState. They likely contain a height, a proof, a commitment root, and +// possibly updates to the validity predicate. +type ClientMessage interface { + proto.Message + + ClientType() string + ValidateBasic() error +} + +// Height is an interface that defines the methods required by a clients +// implementation of their own height object +// +// Heights usually have two components: revision number and revision height. +type Height interface { + IsZero() bool + LT(Height) bool + LTE(Height) bool + EQ(Height) bool + GT(Height) bool + GTE(Height) bool + Increment() Height + Decrement() Height + GetRevisionNumber() uint64 + GetRevisionHeight() uint64 + ToString() string // must define a determinstic `String()` method not the generated protobuf method +} + +func (s ClientStatus) String() string { + return string(s) +} diff --git a/shared/modules/ibc_host_module.go b/shared/modules/ibc_host_module.go index 9a94d5b0f..c009337cf 100644 --- a/shared/modules/ibc_host_module.go +++ b/shared/modules/ibc_host_module.go @@ -4,7 +4,7 @@ import ( "github.com/pokt-network/pocket/runtime/configs" ) -//go:generate mockgen -destination=./mocks/ibc_host_module_mock.go github.com/pokt-network/pocket/shared/modules IBCHostSubmodule,IBCHandler +//go:generate mockgen -destination=./mocks/ibc_host_module_mock.go github.com/pokt-network/pocket/shared/modules IBCHostSubmodule const IBCHostSubmoduleName = "ibc_host" @@ -22,184 +22,9 @@ type IBCHostSubmodule interface { Submodule ibcHostFactory - // IBC related operations - IBCHandler - // GetTimestamp returns the current unix timestamp for the host machine GetTimestamp() uint64 // GetProvableStore returns an instance of a ProvableStore managed by the StoreManager GetProvableStore(name string) (ProvableStore, error) } - -// INCOMPLETE: Split into multiple interfaces per ICS component and embed in the handler -// IBCHandler is the interface through which the different IBC sub-modules can be interacted with -// https://github.com/cosmos/ibc/tree/main/spec/core/ics-025-handler-interface -type IBCHandler interface { - // === Client Lifecycle Management === - // https://github.com/cosmos/ibc/tree/main/spec/core/ics-002-client-semantics - - // CreateClient creates a new client with the given client state and initial consensus state - // and initialises its unique identifier in the IBC store - // CreateClient(clientState clientState, consensusState consensusState) error - - // UpdateClient updates an existing client with the given ClientMessage, given that - // the ClientMessage can be verified using the existing ClientState and ConsensusState - // UpdateClient(identifier Identifier, clientMessage ClientMessage) error - - // QueryConsensusState returns the ConsensusState at the given height for the given client - // QueryConsensusState(identifier Identifier, height Height) ConsensusState - - // QueryClientState returns the ClientState for the given client - // QueryClientState(identifier Identifier) ClientState - - // SubmitMisbehaviour submits evidence for a misbehaviour to the client, possibly invalidating - // previously valid state roots and thus preventing future updates - // SubmitMisbehaviour(identifier Identifier, clientMessage ClientMessage) error - - // === Connection Lifecycle Management === - // https://github.com/cosmos/ibc/tree/main/spec/core/ics-003-connection-semantics - - // ConnOpenInit attempts to initialise a connection to a given counterparty chain (executed on source chain) - /** - ConnOpenInit( - counterpartyPrefix CommitmentPrefix, - clientIdentifier, counterpartyClientIdentifier Identifier, - version: string, // Optional: If version is included, the handshake must fail if the version is not the same - delayPeriodTime, delayPeriodBlocks uint64, - ) error - **/ - - // ConnOpenTry relays a notice of a connection attempt to a counterpaty chain (executed on destination chain) - /** - ConnOpenTry( - counterpartyPrefix CommitmentPrefix, - counterpartyConnectionIdentifier, counterpartyClientIdentifier, clientIdentifier Identifier, - clientState ClientState, - counterpartyVersions []string, - delayPeriodTime, delayPeriodBlocks uint64, - proofInit, proofClient, proofConsensus ics23.CommitmentProof, - proofHeight, consensusHeight Height, - hostConsensusStateProof bytes, - ) error - **/ - - // ConnOpenAck relays the acceptance of a connection open attempt from counterparty chain (executed on source chain) - /** - ConnOpenAck( - identifier, counterpartyIdentifier Identifier, - clientState ClientState, - version string, - proofTry, proofClient, proofConsensus ics23.CommitmentProof, - proofHeight, consensusHeight Height, - hostConsensusStateProof bytes, - ) error - **/ - - // ConnOpenConfirm confirms opening of a connection to the counterparty chain after which the - // connection is open to both chains (executed on destination chain) - // ConnOpenConfirm(identifier Identifier, proofAck ics23.CommitmentProof, proofHeight Height) error - - // QueryConnection returns the ConnectionEnd for the given connection identifier - // QueryConnection(identifier Identifier) (ConnectionEnd, error) - - // QueryClientConnections returns the list of connection identifiers associated with a given client - // QueryClientConnections(clientIdentifier Identifier) ([]Identifier, error) - - // === Channel Lifecycle Management === - // https://github.com/cosmos/ibc/tree/main/spec/core/ics-004-channel-and-packet-semantics - - // ChanOpenInit initialises a channel opening handshake with a counterparty chain (executed on source chain) - /** - ChanOpenInit( - order ChannelOrder, - connectionHops []Identifier, - portIdentifier, counterpartyPortIdentifier Identifier, - version string, - ) (channelIdentifier Identifier, channelCapability CapabilityKey, err Error) - **/ - - // ChanOpenTry attempts to accept the channel opening handshake from a counterparty chain (executed on destination chain) - /** - ChanOpenTry( - order ChannelOrder, - connectionHops []Identifier, - portIdentifier, counterpartyPortIdentifier, counterpartyChannelIdentifier Identifier, - version, counterpartyVersion string, - proofInit ics23.CommitmentProof, - ) (channelIdentifier Identifier, channelCapability CapabilityKey, err Error) - **/ - - // ChanOpenAck relays acceptance of a channel opening handshake from a counterparty chain (executed on source chain) - /** - ChanOpenAck( - portIdentifier, channelIdentifier, counterpartyChannelIdentifier Identifier, - counterpartyVersion string, - proofTry ics23.CommitmentProof, - proofHeight Height, - ) error - **/ - - // ChanOpenConfirm acknowledges the acknowledgment of the channel opening hanshake on the counterparty - // chain after which the channel opening handshake is complete (executed on destination chain) - // ChanOpenConfirm(portIdentifier, channelIdentifier Identifier, proofAck ics23.CommitmentProof, proofHeight Height) error - - // ChanCloseInit is called to close the ChannelEnd with the given identifier on the host machine - // ChanCloseInit(portIdentifier, channelIdentifier Identifier) error - - // ChanCloseConfirm is called to close the ChannelEnd on the counterparty chain as the other end is closed - // ChanCloseConfirm(portIdentifier, channelIdentifier Identifier, proofInit ics23.CommitmentProof, proofHeight Height) error - - // === Packet Relaying === - - // SendPacket is called to send an IBC packet on the channel with the given identifier - /** - SendPacket( - capability CapabilityKey, - sourcePort Identifier, - sourceChannel Identifier, - timeoutHeight Height, - timeoutTimestamp uint64, - data []byte, - ) (sequence uint64, err error) - **/ - - // RecvPacket is called in order to receive an IBC packet on the corresponding channel end - // on the counterpaty chain - // RecvPacket(packet OpaquePacket, proof ics23.CommitmentProof, proofHeight Height, relayer string) (Packet, error) - - // AcknowledgePacket is called to acknowledge the receipt of an IBC packet to the corresponding chain - /** - AcknowledgePacket( - packet OpaquePacket, - acknowledgement []byte, - proof ics23.CommitmentProof, - proofHeight Height, - relayer string, - ) (Packet, error) - **/ - - // TimeoutPacket is called to timeout an IBC packet on the corresponding channel end after the - // timeout height or timeout timestamp has passed and the packet has not been committed - /** - TimeoutPacket( - packet OpaquePacket, - proof ics23.CommitmentProof, - proofHeight Height, - nextSequenceRecv *uint64, - relayer string, - ) (Packet, error) - **/ - - // TimeoutOnClose is called to prove to the counterparty chain that the channel end has been - // closed and that the packet sent over this channel will not be received - /** - TimeoutOnClose( - packet OpaquePacket, - proof, proofClosed ics23.CommitmentProof, - proofHeight Height, - nextSequenceRecv *uint64, - relayer string, - ) (Packet, error) - **/ -} diff --git a/shared/modules/ibc_module.go b/shared/modules/ibc_module.go index 20ed2fa65..ab66fb1b1 100644 --- a/shared/modules/ibc_module.go +++ b/shared/modules/ibc_module.go @@ -2,7 +2,7 @@ package modules import "google.golang.org/protobuf/types/known/anypb" -//go:generate mockgen -destination=./mocks/ibc_module_mock.go github.com/pokt-network/pocket/shared/modules IBCModule +//go:generate mockgen -destination=./mocks/ibc_module_mock.go github.com/pokt-network/pocket/shared/modules IBCModule,IBCHandler const IBCModuleName = "ibc" @@ -11,3 +11,154 @@ type IBCModule interface { HandleEvent(*anypb.Any) error } + +// INCOMPLETE: Split into multiple interfaces per ICS component and embed in the handler +// IBCHandler is the interface through which the different IBC sub-modules can be interacted with +// https://github.com/cosmos/ibc/tree/main/spec/core/ics-025-handler-interface +type IBCHandler interface { + // === Connection Lifecycle Management === + // https://github.com/cosmos/ibc/tree/main/spec/core/ics-003-connection-semantics + + // ConnOpenInit attempts to initialise a connection to a given counterparty chain (executed on source chain) + /** + ConnOpenInit( + counterpartyPrefix CommitmentPrefix, + clientIdentifier, counterpartyClientIdentifier Identifier, + version: string, // Optional: If version is included, the handshake must fail if the version is not the same + delayPeriodTime, delayPeriodBlocks uint64, + ) error + **/ + + // ConnOpenTry relays a notice of a connection attempt to a counterpaty chain (executed on destination chain) + /** + ConnOpenTry( + counterpartyPrefix CommitmentPrefix, + counterpartyConnectionIdentifier, counterpartyClientIdentifier, clientIdentifier Identifier, + clientState ClientState, + counterpartyVersions []string, + delayPeriodTime, delayPeriodBlocks uint64, + proofInit, proofClient, proofConsensus ics23.CommitmentProof, + proofHeight, consensusHeight Height, + hostConsensusStateProof bytes, + ) error + **/ + + // ConnOpenAck relays the acceptance of a connection open attempt from counterparty chain (executed on source chain) + /** + ConnOpenAck( + identifier, counterpartyIdentifier Identifier, + clientState ClientState, + version string, + proofTry, proofClient, proofConsensus ics23.CommitmentProof, + proofHeight, consensusHeight Height, + hostConsensusStateProof bytes, + ) error + **/ + + // ConnOpenConfirm confirms opening of a connection to the counterparty chain after which the + // connection is open to both chains (executed on destination chain) + // ConnOpenConfirm(identifier Identifier, proofAck ics23.CommitmentProof, proofHeight Height) error + + // QueryConnection returns the ConnectionEnd for the given connection identifier + // QueryConnection(identifier Identifier) (ConnectionEnd, error) + + // QueryClientConnections returns the list of connection identifiers associated with a given client + // QueryClientConnections(clientIdentifier Identifier) ([]Identifier, error) + + // === Channel Lifecycle Management === + // https://github.com/cosmos/ibc/tree/main/spec/core/ics-004-channel-and-packet-semantics + + // ChanOpenInit initialises a channel opening handshake with a counterparty chain (executed on source chain) + /** + ChanOpenInit( + order ChannelOrder, + connectionHops []Identifier, + portIdentifier, counterpartyPortIdentifier Identifier, + version string, + ) (channelIdentifier Identifier, channelCapability CapabilityKey, err Error) + **/ + + // ChanOpenTry attempts to accept the channel opening handshake from a counterparty chain (executed on destination chain) + /** + ChanOpenTry( + order ChannelOrder, + connectionHops []Identifier, + portIdentifier, counterpartyPortIdentifier, counterpartyChannelIdentifier Identifier, + version, counterpartyVersion string, + proofInit ics23.CommitmentProof, + ) (channelIdentifier Identifier, channelCapability CapabilityKey, err Error) + **/ + + // ChanOpenAck relays acceptance of a channel opening handshake from a counterparty chain (executed on source chain) + /** + ChanOpenAck( + portIdentifier, channelIdentifier, counterpartyChannelIdentifier Identifier, + counterpartyVersion string, + proofTry ics23.CommitmentProof, + proofHeight Height, + ) error + **/ + + // ChanOpenConfirm acknowledges the acknowledgment of the channel opening hanshake on the counterparty + // chain after which the channel opening handshake is complete (executed on destination chain) + // ChanOpenConfirm(portIdentifier, channelIdentifier Identifier, proofAck ics23.CommitmentProof, proofHeight Height) error + + // ChanCloseInit is called to close the ChannelEnd with the given identifier on the host machine + // ChanCloseInit(portIdentifier, channelIdentifier Identifier) error + + // ChanCloseConfirm is called to close the ChannelEnd on the counterparty chain as the other end is closed + // ChanCloseConfirm(portIdentifier, channelIdentifier Identifier, proofInit ics23.CommitmentProof, proofHeight Height) error + + // === Packet Relaying === + + // SendPacket is called to send an IBC packet on the channel with the given identifier + /** + SendPacket( + capability CapabilityKey, + sourcePort Identifier, + sourceChannel Identifier, + timeoutHeight Height, + timeoutTimestamp uint64, + data []byte, + ) (sequence uint64, err error) + **/ + + // RecvPacket is called in order to receive an IBC packet on the corresponding channel end + // on the counterpaty chain + // RecvPacket(packet OpaquePacket, proof ics23.CommitmentProof, proofHeight Height, relayer string) (Packet, error) + + // AcknowledgePacket is called to acknowledge the receipt of an IBC packet to the corresponding chain + /** + AcknowledgePacket( + packet OpaquePacket, + acknowledgement []byte, + proof ics23.CommitmentProof, + proofHeight Height, + relayer string, + ) (Packet, error) + **/ + + // TimeoutPacket is called to timeout an IBC packet on the corresponding channel end after the + // timeout height or timeout timestamp has passed and the packet has not been committed + /** + TimeoutPacket( + packet OpaquePacket, + proof ics23.CommitmentProof, + proofHeight Height, + nextSequenceRecv *uint64, + relayer string, + ) (Packet, error) + **/ + + // TimeoutOnClose is called to prove to the counterparty chain that the channel end has been + // closed and that the packet sent over this channel will not be received + /** + TimeoutOnClose( + packet OpaquePacket, + proof, proofClosed ics23.CommitmentProof, + proofHeight Height, + nextSequenceRecv *uint64, + relayer string, + ) (Packet, error) + **/ +} From fec79d19ec91fe09ae9b7e7cef5a2f52bbfa4f2b Mon Sep 17 00:00:00 2001 From: harry <53987565+h5law@users.noreply.github.com> Date: Fri, 14 Jul 2023 10:59:56 +0100 Subject: [PATCH 02/23] Use current height for read context and pass uint64 heights into getters --- ibc/events/event_manager.go | 9 +++++---- ibc/store/provable_store.go | 4 ++-- ibc/store/provable_store_test.go | 2 +- persistence/ibc.go | 6 +++--- persistence/types/ibc.go | 6 +++--- 5 files changed, 14 insertions(+), 13 deletions(-) diff --git a/ibc/events/event_manager.go b/ibc/events/event_manager.go index 19e48cb95..0b233e139 100644 --- a/ibc/events/event_manager.go +++ b/ibc/events/event_manager.go @@ -1,7 +1,7 @@ package events import ( - coreTypes "github.com/pokt-network/pocket/shared/core/types" + core_types "github.com/pokt-network/pocket/shared/core/types" "github.com/pokt-network/pocket/shared/modules" "github.com/pokt-network/pocket/shared/modules/base_modules" ) @@ -42,14 +42,15 @@ func (*EventManager) Create(bus modules.Bus, options ...modules.EventLoggerOptio func (e *EventManager) GetModuleName() string { return modules.EventLoggerModuleName } -func (e *EventManager) EmitEvent(event *coreTypes.IBCEvent) error { +func (e *EventManager) EmitEvent(event *core_types.IBCEvent) error { wCtx := e.GetBus().GetPersistenceModule().NewWriteContext() defer wCtx.Release() return wCtx.SetIBCEvent(event) } -func (e *EventManager) QueryEvents(topic string, height uint64) ([]*coreTypes.IBCEvent, error) { - rCtx, err := e.GetBus().GetPersistenceModule().NewReadContext(int64(height)) +func (e *EventManager) QueryEvents(topic string, height uint64) ([]*core_types.IBCEvent, error) { + currHeight := e.GetBus().GetConsensusModule().CurrentHeight() + rCtx, err := e.GetBus().GetPersistenceModule().NewReadContext(int64(currHeight)) if err != nil { return nil, err } diff --git a/ibc/store/provable_store.go b/ibc/store/provable_store.go index c3ff8a171..cc600843c 100644 --- a/ibc/store/provable_store.go +++ b/ibc/store/provable_store.go @@ -67,8 +67,8 @@ func newProvableStore(bus modules.Bus, prefix coreTypes.CommitmentPrefix, privat // keys are automatically prefixed with the CommitmentPrefix if not present func (p *provableStore) Get(key []byte) ([]byte, error) { prefixed := applyPrefix(p.prefix, key) - currHeight := int64(p.bus.GetConsensusModule().CurrentHeight()) - rCtx, err := p.bus.GetPersistenceModule().NewReadContext(currHeight) + currHeight := p.bus.GetConsensusModule().CurrentHeight() + rCtx, err := p.bus.GetPersistenceModule().NewReadContext(int64(currHeight)) if err != nil { return nil, err } diff --git a/ibc/store/provable_store_test.go b/ibc/store/provable_store_test.go index 10fdaf3c0..383441ae5 100644 --- a/ibc/store/provable_store_test.go +++ b/ibc/store/provable_store_test.go @@ -432,7 +432,7 @@ func newPersistenceMock(t *testing.T, EXPECT(). GetIBCStoreEntry(gomock.Any(), gomock.Any()). DoAndReturn( - func(key []byte, _ int64) ([]byte, error) { + func(key []byte, _ uint64) ([]byte, error) { value, ok := dbMap[hex.EncodeToString(key)] if !ok { return nil, coreTypes.ErrIBCKeyDoesNotExist(string(key)) diff --git a/persistence/ibc.go b/persistence/ibc.go index fc9affea4..0544d7197 100644 --- a/persistence/ibc.go +++ b/persistence/ibc.go @@ -14,14 +14,14 @@ import ( // SetIBCStoreEntry sets the key value pair in the IBC store postgres table at the current height func (p *PostgresContext) SetIBCStoreEntry(key, value []byte) error { ctx, tx := p.getCtxAndTx() - if _, err := tx.Exec(ctx, pTypes.InsertIBCStoreEntryQuery(p.Height, key, value)); err != nil { + if _, err := tx.Exec(ctx, pTypes.InsertIBCStoreEntryQuery(uint64(p.Height), key, value)); err != nil { return err } return nil } // GetIBCStoreEntry returns the stored value for the key at the height provided from the IBC store table -func (p *PostgresContext) GetIBCStoreEntry(key []byte, height int64) ([]byte, error) { +func (p *PostgresContext) GetIBCStoreEntry(key []byte, height uint64) ([]byte, error) { ctx, tx := p.getCtxAndTx() row := tx.QueryRow(ctx, pTypes.GetIBCStoreEntryQuery(height, key)) var valueHex string @@ -50,7 +50,7 @@ func (p *PostgresContext) SetIBCEvent(event *coreTypes.IBCEvent) error { return err } eventHex := hex.EncodeToString(eventBz) - if _, err := tx.Exec(ctx, pTypes.InsertIBCEventQuery(p.Height, typeStr, eventHex)); err != nil { + if _, err := tx.Exec(ctx, pTypes.InsertIBCEventQuery(uint64(p.Height), typeStr, eventHex)); err != nil { return err } return nil diff --git a/persistence/types/ibc.go b/persistence/types/ibc.go index a783bcf82..7b0d01ee5 100644 --- a/persistence/types/ibc.go +++ b/persistence/types/ibc.go @@ -23,7 +23,7 @@ const ( ) // InsertIBCStoreEntryQuery returns the query to insert a key/value pair into the ibc_entries table -func InsertIBCStoreEntryQuery(height int64, key, value []byte) string { +func InsertIBCStoreEntryQuery(height uint64, key, value []byte) string { return fmt.Sprintf( `INSERT INTO %s(height, key, value) VALUES(%d, '%s', '%s')`, IBCStoreTableName, @@ -34,7 +34,7 @@ func InsertIBCStoreEntryQuery(height int64, key, value []byte) string { } // InsertIBCEventQuery returns the query to insert an event into the ibc_events table -func InsertIBCEventQuery(height int64, topic, eventHex string) string { +func InsertIBCEventQuery(height uint64, topic, eventHex string) string { return fmt.Sprintf( `INSERT INTO %s(height, topic, event) VALUES(%d, '%s', '%s')`, IBCEventLogTableName, @@ -45,7 +45,7 @@ func InsertIBCEventQuery(height int64, topic, eventHex string) string { } // GetIBCStoreEntryQuery returns the latest value for the key at the height provided or at the last updated height -func GetIBCStoreEntryQuery(height int64, key []byte) string { +func GetIBCStoreEntryQuery(height uint64, key []byte) string { return fmt.Sprintf( `SELECT value FROM %s WHERE height <= %d AND key = '%s' ORDER BY height DESC LIMIT 1`, IBCStoreTableName, From 9c2af753fcb0fe208ccf6a886cc74b36a9030499 Mon Sep 17 00:00:00 2001 From: harry <53987565+h5law@users.noreply.github.com> Date: Fri, 14 Jul 2023 11:03:59 +0100 Subject: [PATCH 03/23] Remove Height field from IBCEvent proto definition --- ibc/client/submodule.go | 3 +-- persistence/test/ibc_test.go | 20 +++++++----------- shared/core/types/proto/ibc_events.proto | 27 ++++-------------------- shared/modules/ibc_event_module.go | 6 +++--- shared/modules/persistence_module.go | 2 +- 5 files changed, 17 insertions(+), 41 deletions(-) diff --git a/ibc/client/submodule.go b/ibc/client/submodule.go index 9c244b8c1..346d5fcc3 100644 --- a/ibc/client/submodule.go +++ b/ibc/client/submodule.go @@ -2,7 +2,6 @@ package client import ( "github.com/pokt-network/pocket/ibc/path" - core_types "github.com/pokt-network/pocket/shared/core/types" "github.com/pokt-network/pocket/shared/modules" "github.com/pokt-network/pocket/shared/modules/base_modules" ) @@ -65,7 +64,7 @@ func (c *clientManager) UpdateClient( // QueryConsensusState returns the ConsensusState at the given height for the // stored client with the given identifier func (c *clientManager) QueryConsensusState( - identifier string, height *core_types.Height, + identifier string, height modules.Height, ) (modules.ConsensusState, error) { return nil, nil } diff --git a/persistence/test/ibc_test.go b/persistence/test/ibc_test.go index 2fcf86f4e..c12533ec4 100644 --- a/persistence/test/ibc_test.go +++ b/persistence/test/ibc_test.go @@ -75,7 +75,7 @@ func TestIBC_GetIBCStoreEntry(t *testing.T) { testCases := []struct { name string - height int64 + height uint64 key []byte expectedValue []byte expectedErr error @@ -133,13 +133,12 @@ var ( baseAttributeValue = []byte("testValue") ) -func TestIBCSetEvent(t *testing.T) { +func TestIBC_SetIBCEvent(t *testing.T) { // Setup database db := NewTestPostgresContext(t, 1) // Add a single event at height 1 event := new(coreTypes.IBCEvent) event.Topic = "test" - event.Height = 1 event.Attributes = append(event.Attributes, &coreTypes.Attribute{ Key: baseAttributeKey, Value: baseAttributeValue, @@ -216,7 +215,6 @@ func TestIBCSetEvent(t *testing.T) { db.Height = int64(tc.height) event := new(coreTypes.IBCEvent) event.Topic = tc.topic - event.Height = tc.height for _, attr := range tc.attributes { event.Attributes = append(event.Attributes, &coreTypes.Attribute{ Key: attr.key, @@ -233,7 +231,7 @@ func TestIBCSetEvent(t *testing.T) { } } -func TestGetIBCEvent(t *testing.T) { +func TestIBC_GetIBCEvent(t *testing.T) { // Setup database db := NewTestPostgresContext(t, 1) // Add events "testKey0", "testKey1", "testKey2", "testKey3" @@ -242,10 +240,6 @@ func TestGetIBCEvent(t *testing.T) { for i := 0; i < 4; i++ { event := new(coreTypes.IBCEvent) event.Topic = "test" - event.Height = uint64(i + 1) - if i == 3 { - event.Height = uint64(i) // add a second event at height 3 - } s := strconv.Itoa(i) event.Attributes = append(event.Attributes, &coreTypes.Attribute{ Key: []byte("testKey" + s), @@ -253,8 +247,11 @@ func TestGetIBCEvent(t *testing.T) { }) events = append(events, event) } - for _, event := range events { - db.Height = int64(event.Height) + for i, event := range events { + db.Height = int64(i + 1) + if i == 3 { + db.Height = int64(i) + } require.NoError(t, db.SetIBCEvent(event)) } @@ -301,7 +298,6 @@ func TestGetIBCEvent(t *testing.T) { require.NoError(t, err) require.Len(t, got, tc.expectedLength) for i, index := range tc.eventsIndexes { - require.Equal(t, events[index].Height, got[i].Height) require.Equal(t, events[index].Topic, got[i].Topic) require.Equal(t, events[index].Attributes[0].Key, got[i].Attributes[0].Key) require.Equal(t, events[index].Attributes[0].Value, got[i].Attributes[0].Value) diff --git a/shared/core/types/proto/ibc_events.proto b/shared/core/types/proto/ibc_events.proto index 7809bb4a0..0ad47774b 100644 --- a/shared/core/types/proto/ibc_events.proto +++ b/shared/core/types/proto/ibc_events.proto @@ -4,31 +4,12 @@ package core; option go_package = "github.com/pokt-network/pocket/shared/core/types"; -// Attribute represents a key-value pair in an IBC event -message Attribute { - bytes key = 1; - bytes value = 2; -} - -// IBCEvent are used after a series of insertions/updates/deletions to the IBC store -// they capture the type of changes made, such as creating a new light client, or -// opening a connection. They also capture the height at which the change was made -// and the different key-value pairs that were modified in the attributes field. message IBCEvent { string topic = 1; - uint64 height = 2; - repeated Attribute attributes = 3; + repeated Attribute attributes = 2; } -// Height represents the height of an IBC client's state -message Height { - uint64 revision_number = 1; - uint64 revision_height = 2; -} - -// Ord is an enum representing the equality of two heights -enum Ord { - LT = 0; - EQ = 1; - GT = 2; +message Attribute { + bytes key = 1; + bytes value = 2; } diff --git a/shared/modules/ibc_event_module.go b/shared/modules/ibc_event_module.go index 56a80f07f..d3f628770 100644 --- a/shared/modules/ibc_event_module.go +++ b/shared/modules/ibc_event_module.go @@ -3,7 +3,7 @@ package modules //go:generate mockgen -destination=./mocks/ibc_event_module_mock.go github.com/pokt-network/pocket/shared/modules EventLogger import ( - coreTypes "github.com/pokt-network/pocket/shared/core/types" + core_types "github.com/pokt-network/pocket/shared/core/types" ) const EventLoggerModuleName = "event_logger" @@ -16,6 +16,6 @@ type EventLogger interface { Submodule eventLoggerFactory - EmitEvent(event *coreTypes.IBCEvent) error - QueryEvents(topic string, height uint64) ([]*coreTypes.IBCEvent, error) + EmitEvent(event *core_types.IBCEvent) error + QueryEvents(topic string, height uint64) ([]*core_types.IBCEvent, error) } diff --git a/shared/modules/persistence_module.go b/shared/modules/persistence_module.go index b510d835b..7b76802b3 100644 --- a/shared/modules/persistence_module.go +++ b/shared/modules/persistence_module.go @@ -245,7 +245,7 @@ type PersistenceReadContext interface { // IBC Queries // GetIBCStoreEntry returns the value of the key at the given height from the ibc_entries table - GetIBCStoreEntry(key []byte, height int64) ([]byte, error) + GetIBCStoreEntry(key []byte, height uint64) ([]byte, error) // GetIBCEvent returns the matching IBC events for any topic at the height provied GetIBCEvents(height uint64, topic string) ([]*coreTypes.IBCEvent, error) } From 227e5269bfdaa5dad3299a20248ed17a7728a563 Mon Sep 17 00:00:00 2001 From: harry <53987565+h5law@users.noreply.github.com> Date: Fri, 14 Jul 2023 13:12:14 +0100 Subject: [PATCH 04/23] Add emit event functions for the client submodule --- ibc/client/events.go | 74 +++++++++++++++++++++++++++++++++ shared/core/types/ibc_events.go | 5 +++ 2 files changed, 79 insertions(+) create mode 100644 ibc/client/events.go create mode 100644 shared/core/types/ibc_events.go diff --git a/ibc/client/events.go b/ibc/client/events.go new file mode 100644 index 000000000..613e73c04 --- /dev/null +++ b/ibc/client/events.go @@ -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().String())), + }, + }, + ) +} + +// 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.String())), + 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().String())), + }, + }, + ) +} + +// 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())), + }, + }, + ) +} diff --git a/shared/core/types/ibc_events.go b/shared/core/types/ibc_events.go new file mode 100644 index 000000000..3c3c7580a --- /dev/null +++ b/shared/core/types/ibc_events.go @@ -0,0 +1,5 @@ +package types + +func NewAttribute(key, value []byte) *Attribute { + return &Attribute{Key: key, Value: value} +} From ec954e765f41d3dfdb9669418955c92763df89b4 Mon Sep 17 00:00:00 2001 From: harry <53987565+h5law@users.noreply.github.com> Date: Fri, 14 Jul 2023 13:13:07 +0100 Subject: [PATCH 05/23] Implement CreateClient method --- ibc/client/submodule.go | 45 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/ibc/client/submodule.go b/ibc/client/submodule.go index 346d5fcc3..45cc963b2 100644 --- a/ibc/client/submodule.go +++ b/ibc/client/submodule.go @@ -1,12 +1,21 @@ package client import ( + "fmt" + "github.com/pokt-network/pocket/ibc/path" "github.com/pokt-network/pocket/shared/modules" "github.com/pokt-network/pocket/shared/modules/base_modules" ) -var _ modules.ClientManager = &clientManager{} +var ( + _ modules.ClientManager = &clientManager{} + allowedClientTypes = make(map[string]struct{}, 0) +) + +func init() { + allowedClientTypes["08-wasm"] = struct{}{} +} type clientManager struct { base_modules.IntegrableModule @@ -49,7 +58,34 @@ func (c *clientManager) GetModuleName() string { return modules.ClientManagerMod func (c *clientManager) CreateClient( clientState modules.ClientState, consensusState modules.ConsensusState, ) (string, error) { + // Check if the client type is allowed + if !isAllowedClientType(clientState.ClientType()) { + return "", fmt.Errorf("client type %s is not supported", clientState.ClientType()) + } + + // Generate a unique identifier for the client identifier := path.GenerateClientIdentifier() + + // Retrieve the client store + clientStore, err := c.GetBus().GetIBCHost().GetProvableStore(path.KeyClientStorePrefix) + if err != nil { + return "", err + } + + // Initialise the client with the clientState provided + if err := clientState.Initialise(clientStore, consensusState); err != nil { + c.logger.Error().Err(err).Str("identifier", identifier).Msg("failed to initialize client") + return "", err + } + + c.logger.Info().Str("identifier", identifier).Str("height", clientState.GetLatestHeight().String()).Msg("client created at height") + + // Emit the create client event to the event logger + if err := c.emitCreateClientEvent(identifier, clientState); err != nil { + c.logger.Error().Err(err).Str("identifier", identifier).Msg("failed to emit client created event") + return "", err + } + return identifier, nil } @@ -81,3 +117,10 @@ func (c *clientManager) SubmitMisbehaviour( ) error { return nil } + +func isAllowedClientType(clientType string) bool { + if _, ok := allowedClientTypes[clientType]; ok { + return true + } + return false +} From 04d969d797ae3baba7e8efbaecf5eb60766e22e1 Mon Sep 17 00:00:00 2001 From: harry <53987565+h5law@users.noreply.github.com> Date: Fri, 14 Jul 2023 17:44:34 +0100 Subject: [PATCH 06/23] Implement UpdateClient method --- Makefile | 1 + ibc/client/events.go | 6 +-- ibc/client/queries.go | 56 +++++++++++++++++++++++++++ ibc/client/submodule.go | 60 ++++++++++++++++++++++------- ibc/path/keys_ics02.go | 4 +- shared/core/types/error.go | 8 +++- shared/modules/ibc_client_module.go | 12 +++--- 7 files changed, 123 insertions(+), 24 deletions(-) create mode 100644 ibc/client/queries.go diff --git a/Makefile b/Makefile index 192cd67a6..817dfde78 100644 --- a/Makefile +++ b/Makefile @@ -320,6 +320,7 @@ 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 # echo "View generated proto files by running: make protogen_show" diff --git a/ibc/client/events.go b/ibc/client/events.go index 613e73c04..7ea984c5b 100644 --- a/ibc/client/events.go +++ b/ibc/client/events.go @@ -15,7 +15,7 @@ func (c *clientManager) emitCreateClientEvent(clientId string, clientState modul 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().String())), + core_types.NewAttribute(client_types.AttributeKeyConsensusHeight, []byte(clientState.GetLatestHeight().ToString())), }, }, ) @@ -39,7 +39,7 @@ func (c *clientManager) emitUpdateClientEvent( 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.String())), + core_types.NewAttribute(client_types.AttributeKeyConsensusHeight, []byte(consensusHeight.ToString())), core_types.NewAttribute(client_types.AttributeKeyHeader, clientMsgBz), }, }, @@ -54,7 +54,7 @@ func (c *clientManager) emitUpgradeClientEvent(clientId string, clientState modu 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().String())), + core_types.NewAttribute(client_types.AttributeKeyConsensusHeight, []byte(clientState.GetLatestHeight().ToString())), }, }, ) diff --git a/ibc/client/queries.go b/ibc/client/queries.go new file mode 100644 index 000000000..fa2814753 --- /dev/null +++ b/ibc/client/queries.go @@ -0,0 +1,56 @@ +package client + +import ( + "github.com/pokt-network/pocket/ibc/path" + "github.com/pokt-network/pocket/shared/codec" + "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 client store + clientStore, err := c.GetBus().GetIBCHost().GetProvableStore(path.KeyClientStorePrefix) + if err != nil { + return nil, err + } + + // Retrieve the consensus state bytes from the client store + consStateBz, err := clientStore.Get(path.FullConsensusStateKey(identifier, height.ToString())) + if err != nil { + return nil, err + } + + // Unmarshal into a ConsensusState interface + var consState modules.ConsensusState + if err := codec.GetInterfaceRegistry().UnmarshalInterface(consStateBz, &consState); err != nil { + return nil, err + } + + return consState, nil +} + +// 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 + } + + // Retrieve the client state bytes from the client store + clientStateBz, err := clientStore.Get(path.FullClientStateKey(identifier)) + if err != nil { + return nil, err + } + + // Unmarshal into a ClientState interface + var clientState modules.ClientState + if err := codec.GetInterfaceRegistry().UnmarshalInterface(clientStateBz, &clientState); err != nil { + return nil, err + } + + return clientState, nil +} diff --git a/ibc/client/submodule.go b/ibc/client/submodule.go index 45cc963b2..13676b6bd 100644 --- a/ibc/client/submodule.go +++ b/ibc/client/submodule.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/pokt-network/pocket/ibc/path" + core_types "github.com/pokt-network/pocket/shared/core/types" "github.com/pokt-network/pocket/shared/modules" "github.com/pokt-network/pocket/shared/modules/base_modules" ) @@ -78,7 +79,7 @@ func (c *clientManager) CreateClient( return "", err } - c.logger.Info().Str("identifier", identifier).Str("height", clientState.GetLatestHeight().String()).Msg("client created at height") + c.logger.Info().Str("identifier", identifier).Str("height", clientState.GetLatestHeight().ToString()).Msg("client created at height") // Emit the create client event to the event logger if err := c.emitCreateClientEvent(identifier, clientState); err != nil { @@ -94,20 +95,53 @@ func (c *clientManager) CreateClient( func (c *clientManager) UpdateClient( identifier string, clientMessage modules.ClientMessage, ) error { - return nil -} + // Get the client state + clientState, err := c.GetClientState(identifier) + if err != nil { + return err + } -// QueryConsensusState returns the ConsensusState at the given height for the -// stored client with the given identifier -func (c *clientManager) QueryConsensusState( - identifier string, height modules.Height, -) (modules.ConsensusState, error) { - return nil, nil -} + // Get the client store + clientStore, err := c.GetBus().GetIBCHost().GetProvableStore(path.KeyClientStorePrefix) + if err != nil { + return err + } + + // Check the state is active + if clientState.Status(clientStore) != modules.ActiveStatus { + return core_types.ErrIBCClientNotActive() + } -// QueryClientState returns the ClientState for the stored client with the given identifier -func (c *clientManager) QueryClientState(identifier string) (modules.ClientState, error) { - return nil, nil + // Verify the client message + if err := clientState.VerifyClientMessage(clientStore, clientMessage); err != nil { + return err + } + + // Check for misbehaviour on the source chain + misbehaved := clientState.CheckForMisbehaviour(clientStore, clientMessage) + if misbehaved { + clientState.UpdateStateOnMisbehaviour(clientStore, clientMessage) + c.logger.Info().Str("identifier", identifier).Msg("client frozen for misbehaviour") + + // emit the submit misbehaviour event to the event logger + if err := c.emitSubmitMisbehaviourEvent(identifier, clientState); err != nil { + c.logger.Error().Err(err).Str("identifier", identifier).Msg("failed to emit client submit misbehaviour event") + return err + } + return nil + } + + // Update the client + consensusHeight := clientState.UpdateState(clientStore, clientMessage) + c.logger.Info().Str("identifier", identifier).Str("height", consensusHeight.ToString()).Msg("client state updated") + + // emit the update client event to the event logger + if err := c.emitUpdateClientEvent(identifier, clientState.ClientType(), consensusHeight, clientMessage); err != nil { + c.logger.Error().Err(err).Str("identifier", identifier).Msg("failed to emit client update event") + return err + } + + return nil } // SubmitMisbehaviour submits evidence for a misbehaviour to the client, possibly diff --git a/ibc/path/keys_ics02.go b/ibc/path/keys_ics02.go index d829f8e0c..6015513be 100644 --- a/ibc/path/keys_ics02.go +++ b/ibc/path/keys_ics02.go @@ -1,6 +1,8 @@ package path -import "fmt" +import ( + "fmt" +) //////////////////////////////////////////////////////////////////////////////// // ICS02 diff --git a/shared/core/types/error.go b/shared/core/types/error.go index 69369cced..af315a2cc 100644 --- a/shared/core/types/error.go +++ b/shared/core/types/error.go @@ -48,7 +48,7 @@ func NewError(code Code, msg string) Error { } } -// NextCode: 149 +// NextCode: 150 type Code float64 // CONSIDERATION: Should these be a proto enum or a golang iota? //nolint:gosec // G101 - Not hard-coded credentials @@ -198,6 +198,7 @@ const ( CodeIBCStoreAlreadyExistsError Code = 146 CodeIBCStoreDoesNotExistError Code = 147 CodeIBCKeyDoesNotExistError Code = 148 + CodeIBCClientNotActiveError Code = 149 ) const ( @@ -344,6 +345,7 @@ const ( IBCStoreAlreadyExistsError = "ibc store already exists in the store manager" IBCStoreDoesNotExistError = "ibc store does not exist in the store manager" IBCKeyDoesNotExistError = "key does not exist in the ibc store" + IBCClientNotActiveError = "ibc client is not active" ) func ErrUnknownParam(paramName string) Error { @@ -922,3 +924,7 @@ func ErrIBCStoreDoesNotExist(name string) Error { func ErrIBCKeyDoesNotExist(key string) Error { return NewError(CodeIBCKeyDoesNotExistError, fmt.Sprintf("%s: %s", IBCKeyDoesNotExistError, key)) } + +func ErrIBCClientNotActive() Error { + return NewError(CodeIBCClientNotActiveError, IBCClientNotActiveError) +} diff --git a/shared/modules/ibc_client_module.go b/shared/modules/ibc_client_module.go index 10a0f77ff..f5f52b752 100644 --- a/shared/modules/ibc_client_module.go +++ b/shared/modules/ibc_client_module.go @@ -39,11 +39,11 @@ type ClientManager interface { // the ClientMessage can be verified using the existing ClientState and ConsensusState UpdateClient(identifier string, clientMessage ClientMessage) error - // QueryConsensusState returns the ConsensusState at the given height for the given client - QueryConsensusState(identifier string, height Height) (ConsensusState, error) + // GetConsensusState returns the ConsensusState at the given height for the given client + GetConsensusState(identifier string, height Height) (ConsensusState, error) - // QueryClientState returns the ClientState for the given client - QueryClientState(identifier string) (ClientState, error) + // GetClientState returns the ClientState for the given client + GetClientState(identifier string) (ClientState, error) // SubmitMisbehaviour submits evidence for a misbehaviour to the client, possibly invalidating // previously valid state roots and thus preventing future updates @@ -114,9 +114,9 @@ type ClientState interface { // UpdateState updates and stores as necessary any associated information // for an IBC client, such as the ClientState and corresponding ConsensusState. - // Upon successful update, a list of consensus heights is returned. + // Upon successful update, a consensus height is returned. // It assumes the ClientMessage has already been verified. - UpdateState(clientStore ProvableStore, clientMsg ClientMessage) []Height + UpdateState(clientStore ProvableStore, clientMsg ClientMessage) Height } // ConsensusState is an interface that defines the methods required by a clients From 37e2ee365d7503e428b2de9bd50cfab9c2e4a033 Mon Sep 17 00:00:00 2001 From: harry <53987565+h5law@users.noreply.github.com> Date: Mon, 17 Jul 2023 18:05:47 +0100 Subject: [PATCH 07/23] Implement UpgradeClient method --- ibc/client/submodule.go | 46 ++++++++++++++++++++++++++--- shared/modules/ibc_client_module.go | 26 ++++++++++++++-- 2 files changed, 65 insertions(+), 7 deletions(-) diff --git a/ibc/client/submodule.go b/ibc/client/submodule.go index 13676b6bd..37016a1a4 100644 --- a/ibc/client/submodule.go +++ b/ibc/client/submodule.go @@ -144,11 +144,49 @@ func (c *clientManager) UpdateClient( return nil } -// SubmitMisbehaviour submits evidence for a misbehaviour to the client, possibly -// invalidating previously valid state roots and thus preventing future updates -func (c *clientManager) SubmitMisbehaviour( - identifier string, clientMessage modules.ClientMessage, +// UpgradeClient upgrades an existing client with the given identifier using the +// ClientState and ConsentusState provided. It can only do so if the new client +// was committed to by the old client at the specified upgrade height +func (c *clientManager) UpgradeClient( + identifier string, + upgradedClient modules.ClientState, upgradedConsState modules.ConsensusState, + proofUpgradeClient, proofUpgradeConsState []byte, ) error { + // Get the client state + clientState, err := c.GetClientState(identifier) + if err != nil { + return err + } + + // Get the client store + clientStore, err := c.GetBus().GetIBCHost().GetProvableStore(path.KeyClientStorePrefix) + if err != nil { + return err + } + + // Check the state is active + if clientState.Status(clientStore) != modules.ActiveStatus { + return core_types.ErrIBCClientNotActive() + } + + // Verify the upgrade + if err := clientState.VerifyUpgradeAndUpdateState( + clientStore, + upgradedClient, upgradedConsState, + proofUpgradeClient, proofUpgradeConsState, + ); err != nil { + c.logger.Error().Err(err).Str("identifier", identifier).Msg("failed to verify upgrade") + return err + } + + c.logger.Info().Str("identifier", identifier).Str("height", upgradedClient.GetLatestHeight().ToString()).Msg("client upgraded") + + // emit the upgrade client event to the event logger + if err := c.emitUpgradeClientEvent(identifier, upgradedClient); err != nil { + c.logger.Error().Err(err).Str("identifier", identifier).Msg("failed to emit client upgrade event") + return err + } + return nil } diff --git a/shared/modules/ibc_client_module.go b/shared/modules/ibc_client_module.go index f5f52b752..142991ebf 100644 --- a/shared/modules/ibc_client_module.go +++ b/shared/modules/ibc_client_module.go @@ -45,9 +45,14 @@ type ClientManager interface { // GetClientState returns the ClientState for the given client GetClientState(identifier string) (ClientState, error) - // SubmitMisbehaviour submits evidence for a misbehaviour to the client, possibly invalidating - // previously valid state roots and thus preventing future updates - SubmitMisbehaviour(identifier string, clientMessage ClientMessage) error + // UpgradeClient upgrades an existing client with the given identifier using the + // ClientState and ConsentusState provided. It can only do so if the new client + // was committed to by the old client at the specified upgrade height + UpgradeClient( + identifier string, + clientState ClientState, consensusState ConsensusState, + proofUpgradeClient, proofUpgradeConsState []byte, + ) error } // ClientState is an interface that defines the methods required by a clients @@ -117,6 +122,21 @@ type ClientState interface { // Upon successful update, a consensus height is returned. // It assumes the ClientMessage has already been verified. UpdateState(clientStore ProvableStore, clientMsg ClientMessage) Height + + // Upgrade functions + // NOTE: proof heights are not included as upgrade to a new revision is expected to pass only on the last + // height committed by the current revision. Clients are responsible for ensuring that the planned last + // height of the current revision is somehow encoded in the proof verification process. + // This is to ensure that no premature upgrades occur, since upgrade plans committed to by the counterparty + // may be cancelled or modified before the last planned height. + // If the upgrade is verified, the upgraded client and consensus states must be set in the client store. + VerifyUpgradeAndUpdateState( + clientStore ProvableStore, + newClient ClientState, + newConsState ConsensusState, + proofUpgradeClient, + proofUpgradeConsState []byte, + ) error } // ConsensusState is an interface that defines the methods required by a clients From 8425f8352933bdac10533c4633b5029e2dd9b67d Mon Sep 17 00:00:00 2001 From: harry <53987565+h5law@users.noreply.github.com> Date: Mon, 17 Jul 2023 18:46:11 +0100 Subject: [PATCH 08/23] Shorted log lines --- ibc/client/submodule.go | 33 +++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/ibc/client/submodule.go b/ibc/client/submodule.go index 37016a1a4..5902443d8 100644 --- a/ibc/client/submodule.go +++ b/ibc/client/submodule.go @@ -75,15 +75,19 @@ func (c *clientManager) CreateClient( // Initialise the client with the clientState provided if err := clientState.Initialise(clientStore, consensusState); err != nil { - c.logger.Error().Err(err).Str("identifier", identifier).Msg("failed to initialize client") + c.logger.Error().Err(err).Str("identifier", identifier). + Msg("failed to initialize client") return "", err } - c.logger.Info().Str("identifier", identifier).Str("height", clientState.GetLatestHeight().ToString()).Msg("client created at height") + c.logger.Info().Str("identifier", identifier). + Str("height", clientState.GetLatestHeight().ToString()). + Msg("client created at height") // Emit the create client event to the event logger if err := c.emitCreateClientEvent(identifier, clientState); err != nil { - c.logger.Error().Err(err).Str("identifier", identifier).Msg("failed to emit client created event") + c.logger.Error().Err(err).Str("identifier", identifier). + Msg("failed to emit client created event") return "", err } @@ -121,11 +125,13 @@ func (c *clientManager) UpdateClient( misbehaved := clientState.CheckForMisbehaviour(clientStore, clientMessage) if misbehaved { clientState.UpdateStateOnMisbehaviour(clientStore, clientMessage) - c.logger.Info().Str("identifier", identifier).Msg("client frozen for misbehaviour") + c.logger.Info().Str("identifier", identifier). + Msg("client frozen for misbehaviour") // emit the submit misbehaviour event to the event logger if err := c.emitSubmitMisbehaviourEvent(identifier, clientState); err != nil { - c.logger.Error().Err(err).Str("identifier", identifier).Msg("failed to emit client submit misbehaviour event") + c.logger.Error().Err(err).Str("identifier", identifier). + Msg("failed to emit client submit misbehaviour event") return err } return nil @@ -133,11 +139,14 @@ func (c *clientManager) UpdateClient( // Update the client consensusHeight := clientState.UpdateState(clientStore, clientMessage) - c.logger.Info().Str("identifier", identifier).Str("height", consensusHeight.ToString()).Msg("client state updated") + c.logger.Info().Str("identifier", identifier). + Str("height", consensusHeight.ToString()). + Msg("client state updated") // emit the update client event to the event logger if err := c.emitUpdateClientEvent(identifier, clientState.ClientType(), consensusHeight, clientMessage); err != nil { - c.logger.Error().Err(err).Str("identifier", identifier).Msg("failed to emit client update event") + c.logger.Error().Err(err).Str("identifier", identifier). + Msg("failed to emit client update event") return err } @@ -175,15 +184,19 @@ func (c *clientManager) UpgradeClient( upgradedClient, upgradedConsState, proofUpgradeClient, proofUpgradeConsState, ); err != nil { - c.logger.Error().Err(err).Str("identifier", identifier).Msg("failed to verify upgrade") + c.logger.Error().Err(err).Str("identifier", identifier). + Msg("failed to verify upgrade") return err } - c.logger.Info().Str("identifier", identifier).Str("height", upgradedClient.GetLatestHeight().ToString()).Msg("client upgraded") + c.logger.Info().Str("identifier", identifier). + Str("height", upgradedClient.GetLatestHeight().ToString()). + Msg("client upgraded") // emit the upgrade client event to the event logger if err := c.emitUpgradeClientEvent(identifier, upgradedClient); err != nil { - c.logger.Error().Err(err).Str("identifier", identifier).Msg("failed to emit client upgrade event") + c.logger.Error().Err(err).Str("identifier", identifier). + Msg("failed to emit client upgrade event") return err } From 3eff80b23e2d7c4a56e92e338ab65ecc52ab4208 Mon Sep 17 00:00:00 2001 From: harry <53987565+h5law@users.noreply.github.com> Date: Mon, 17 Jul 2023 23:22:41 +0100 Subject: [PATCH 09/23] Add WASM specific implementations for client interfaces --- ibc/client/queries.go | 9 ++++---- ibc/client/types/proto/client.go | 0 ibc/client/types/proto/wasm.proto | 35 +++++++++++++++++++++++++++++++ 3 files changed, 40 insertions(+), 4 deletions(-) create mode 100644 ibc/client/types/proto/client.go create mode 100644 ibc/client/types/proto/wasm.proto diff --git a/ibc/client/queries.go b/ibc/client/queries.go index fa2814753..ca5e34487 100644 --- a/ibc/client/queries.go +++ b/ibc/client/queries.go @@ -1,6 +1,7 @@ package client import ( + "github.com/pokt-network/pocket/ibc/client/types" "github.com/pokt-network/pocket/ibc/path" "github.com/pokt-network/pocket/shared/codec" "github.com/pokt-network/pocket/shared/modules" @@ -24,8 +25,8 @@ func (c *clientManager) GetConsensusState( } // Unmarshal into a ConsensusState interface - var consState modules.ConsensusState - if err := codec.GetInterfaceRegistry().UnmarshalInterface(consStateBz, &consState); err != nil { + consState := new(types.ConsensusState) + if err := codec.GetCodec().Unmarshal(consStateBz, consState); err != nil { return nil, err } @@ -47,8 +48,8 @@ func (c *clientManager) GetClientState(identifier string) (modules.ClientState, } // Unmarshal into a ClientState interface - var clientState modules.ClientState - if err := codec.GetInterfaceRegistry().UnmarshalInterface(clientStateBz, &clientState); err != nil { + clientState := new(types.ClientState) + if err := codec.GetCodec().Unmarshal(clientStateBz, clientState); err != nil { return nil, err } diff --git a/ibc/client/types/proto/client.go b/ibc/client/types/proto/client.go new file mode 100644 index 000000000..e69de29bb diff --git a/ibc/client/types/proto/wasm.proto b/ibc/client/types/proto/wasm.proto new file mode 100644 index 000000000..fb8b5251b --- /dev/null +++ b/ibc/client/types/proto/wasm.proto @@ -0,0 +1,35 @@ +syntax = "proto3"; + +package core; + +option go_package = "github.com/pokt-network/pocket/ibc/client/types"; + +// ClientState for a Wasm light client +message ClientState { + bytes data = 1; // opaque data passed to the wasm client + bytes wasm_checksum = 2; // checksum of the wasm client code + Height latest_height = 3; // latest height of the client +} + +// ConsensusState for a Wasm light client +message ConsensusState { + bytes data = 1; // opaque data passed to the wasm client + uint64 timestamp = 2; // unix nano timestamp of the block +} + +// Header for a Wasm light client +message Header { + bytes data = 1; // opaque data passed to the wasm client + Height height = 2; // height of the header +} + +// Misbehaviour for a Wasm light client +message Misbehaviour { + bytes data = 1; // opaque data passed to the wasm client +} + +// Height represents the height of a client +message Height { + uint64 revision_number = 1; + uint64 revision_height = 2; +} From 99423a882606617c6a86749f89b93747de2be48b Mon Sep 17 00:00:00 2001 From: harry <53987565+h5law@users.noreply.github.com> Date: Tue, 18 Jul 2023 01:12:23 +0100 Subject: [PATCH 10/23] Allow setting nil keys --- ibc/store/provable_store.go | 4 +++- ibc/store/provable_store_test.go | 6 ++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/ibc/store/provable_store.go b/ibc/store/provable_store.go index cc600843c..519388c33 100644 --- a/ibc/store/provable_store.go +++ b/ibc/store/provable_store.go @@ -233,5 +233,7 @@ func applyPrefix(prefix coreTypes.CommitmentPrefix, key []byte) coreTypes.Commit if len(prefix) > len(slashed) && bytes.Equal(prefix[:len(slashed)], slashed) { return key } - return path.ApplyPrefix(prefix, string(key)) + prefixed := path.ApplyPrefix(prefix, string(key)) + trimmed := strings.TrimSuffix(string(prefixed), "/") + return coreTypes.CommitmentPath(trimmed) } diff --git a/ibc/store/provable_store_test.go b/ibc/store/provable_store_test.go index 383441ae5..b6f416729 100644 --- a/ibc/store/provable_store_test.go +++ b/ibc/store/provable_store_test.go @@ -45,6 +45,12 @@ func TestProvableStore_Get(t *testing.T) { expectedValue: nil, expectedError: coreTypes.ErrIBCKeyDoesNotExist("test/key2"), }, + { + name: "key is nil", + key: nil, + expectedValue: nil, + expectedError: coreTypes.ErrIBCKeyDoesNotExist("test"), + }, } provableStore := newTestProvableStore(t) From 4bd3d41173d0137f950ca11e2a31e92433ca217c Mon Sep 17 00:00:00 2001 From: harry <53987565+h5law@users.noreply.github.com> Date: Tue, 18 Jul 2023 01:13:10 +0100 Subject: [PATCH 11/23] Add error returns --- ibc/client/types/proto/client.go | 0 shared/modules/ibc_client_module.go | 5 +++-- 2 files changed, 3 insertions(+), 2 deletions(-) delete mode 100644 ibc/client/types/proto/client.go diff --git a/ibc/client/types/proto/client.go b/ibc/client/types/proto/client.go deleted file mode 100644 index e69de29bb..000000000 diff --git a/shared/modules/ibc_client_module.go b/shared/modules/ibc_client_module.go index 142991ebf..816e579c2 100644 --- a/shared/modules/ibc_client_module.go +++ b/shared/modules/ibc_client_module.go @@ -16,6 +16,7 @@ const ( ExpiredStatus ClientStatus = "expired" FrozenStatus ClientStatus = "frozen" UnauthorizedStatus ClientStatus = "unauthorized" + UnknownStatus ClientStatus = "unknown" ) type ClientManagerOption func(ClientManager) @@ -115,13 +116,13 @@ type ClientState interface { // UpdateStateOnMisbehaviour should perform appropriate state changes on a // client state given that misbehaviour has been detected and verified - UpdateStateOnMisbehaviour(clientStore ProvableStore, clientMsg ClientMessage) + UpdateStateOnMisbehaviour(clientStore ProvableStore, clientMsg ClientMessage) error // UpdateState updates and stores as necessary any associated information // for an IBC client, such as the ClientState and corresponding ConsensusState. // Upon successful update, a consensus height is returned. // It assumes the ClientMessage has already been verified. - UpdateState(clientStore ProvableStore, clientMsg ClientMessage) Height + UpdateState(clientStore ProvableStore, clientMsg ClientMessage) (Height, error) // Upgrade functions // NOTE: proof heights are not included as upgrade to a new revision is expected to pass only on the last From 7718b9c7759346d9c957f80a3389824940a50ce7 Mon Sep 17 00:00:00 2001 From: harry <53987565+h5law@users.noreply.github.com> Date: Tue, 18 Jul 2023 01:14:19 +0100 Subject: [PATCH 12/23] Use clientID prefixed client store --- ibc/client/submodule.go | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/ibc/client/submodule.go b/ibc/client/submodule.go index 5902443d8..1cb395814 100644 --- a/ibc/client/submodule.go +++ b/ibc/client/submodule.go @@ -3,6 +3,7 @@ package client import ( "fmt" + "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" @@ -15,7 +16,7 @@ var ( ) func init() { - allowedClientTypes["08-wasm"] = struct{}{} + allowedClientTypes[types.WasmClientType] = struct{}{} } type clientManager struct { @@ -67,8 +68,9 @@ func (c *clientManager) CreateClient( // Generate a unique identifier for the client identifier := path.GenerateClientIdentifier() - // Retrieve the client store - clientStore, err := c.GetBus().GetIBCHost().GetProvableStore(path.KeyClientStorePrefix) + // Retrieve the client store prefixed with the client identifier + prefixed := path.ApplyPrefix(core_types.CommitmentPrefix(path.KeyClientStorePrefix), identifier) + clientStore, err := c.GetBus().GetIBCHost().GetProvableStore(string(prefixed)) if err != nil { return "", err } @@ -105,8 +107,9 @@ func (c *clientManager) UpdateClient( return err } - // Get the client store - clientStore, err := c.GetBus().GetIBCHost().GetProvableStore(path.KeyClientStorePrefix) + // Retrieve the client store prefixed with the client identifier + prefixed := path.ApplyPrefix(core_types.CommitmentPrefix(path.KeyClientStorePrefix), identifier) + clientStore, err := c.GetBus().GetIBCHost().GetProvableStore(string(prefixed)) if err != nil { return err } @@ -138,7 +141,13 @@ func (c *clientManager) UpdateClient( } // Update the client - consensusHeight := clientState.UpdateState(clientStore, clientMessage) + consensusHeight, err := clientState.UpdateState(clientStore, clientMessage) + if err != nil { + c.logger.Error().Err(err).Str("identifier", identifier). + Str("height", consensusHeight.ToString()). + Msg("failed to update client state") + return err + } c.logger.Info().Str("identifier", identifier). Str("height", consensusHeight.ToString()). Msg("client state updated") @@ -167,8 +176,9 @@ func (c *clientManager) UpgradeClient( return err } - // Get the client store - clientStore, err := c.GetBus().GetIBCHost().GetProvableStore(path.KeyClientStorePrefix) + // Retrieve the client store prefixed with the client identifier + prefixed := path.ApplyPrefix(core_types.CommitmentPrefix(path.KeyClientStorePrefix), identifier) + clientStore, err := c.GetBus().GetIBCHost().GetProvableStore(string(prefixed)) if err != nil { return err } From fcc11ad8669f4a1a848a33458f7d574a981ca15e Mon Sep 17 00:00:00 2001 From: harry <53987565+h5law@users.noreply.github.com> Date: Tue, 18 Jul 2023 01:14:47 +0100 Subject: [PATCH 13/23] Add Wasm Client protobuf implementations --- ibc/client/queries.go | 35 +---- ibc/client/submodule.go | 8 +- ibc/client/types/client.go | 238 ++++++++++++++++++++++++++++++ ibc/client/types/consensus.go | 33 +++++ ibc/client/types/header.go | 23 +++ ibc/client/types/height.go | 73 +++++++++ ibc/client/types/misbehaviour.go | 22 +++ ibc/client/types/proto/wasm.proto | 2 +- ibc/client/types/queries.go | 63 ++++++++ ibc/client/types/update.go | 120 +++++++++++++++ ibc/client/types/upgrade.go | 71 +++++++++ ibc/path/keys_ics02.go | 6 + 12 files changed, 662 insertions(+), 32 deletions(-) create mode 100644 ibc/client/types/client.go create mode 100644 ibc/client/types/consensus.go create mode 100644 ibc/client/types/header.go create mode 100644 ibc/client/types/height.go create mode 100644 ibc/client/types/misbehaviour.go create mode 100644 ibc/client/types/queries.go create mode 100644 ibc/client/types/update.go create mode 100644 ibc/client/types/upgrade.go diff --git a/ibc/client/queries.go b/ibc/client/queries.go index ca5e34487..2ea5b36dc 100644 --- a/ibc/client/queries.go +++ b/ibc/client/queries.go @@ -3,7 +3,7 @@ package client import ( "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" ) @@ -12,25 +12,14 @@ import ( func (c *clientManager) GetConsensusState( identifier string, height modules.Height, ) (modules.ConsensusState, error) { - // Retrieve the client store - clientStore, err := c.GetBus().GetIBCHost().GetProvableStore(path.KeyClientStorePrefix) - if err != nil { - return nil, err - } - - // Retrieve the consensus state bytes from the client store - consStateBz, err := clientStore.Get(path.FullConsensusStateKey(identifier, height.ToString())) + // 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 } - // Unmarshal into a ConsensusState interface - consState := new(types.ConsensusState) - if err := codec.GetCodec().Unmarshal(consStateBz, consState); err != nil { - return nil, err - } - - return consState, nil + return types.GetConsensusState(clientStore, height) } // GetClientState returns the ClientState for the stored client with the given identifier @@ -41,17 +30,5 @@ func (c *clientManager) GetClientState(identifier string) (modules.ClientState, return nil, err } - // Retrieve the client state bytes from the client store - clientStateBz, err := clientStore.Get(path.FullClientStateKey(identifier)) - if err != nil { - return nil, err - } - - // Unmarshal into a ClientState interface - clientState := new(types.ClientState) - if err := codec.GetCodec().Unmarshal(clientStateBz, clientState); err != nil { - return nil, err - } - - return clientState, nil + return types.GetClientState(clientStore, identifier) } diff --git a/ibc/client/submodule.go b/ibc/client/submodule.go index 1cb395814..8428f8ecd 100644 --- a/ibc/client/submodule.go +++ b/ibc/client/submodule.go @@ -96,7 +96,7 @@ func (c *clientManager) CreateClient( return identifier, nil } -// UpdateClient updates an existing client with the given identifer using the +// UpdateClient updates an existing client with the given identifier using the // ClientMessage provided func (c *clientManager) UpdateClient( identifier string, clientMessage modules.ClientMessage, @@ -127,7 +127,11 @@ func (c *clientManager) UpdateClient( // Check for misbehaviour on the source chain misbehaved := clientState.CheckForMisbehaviour(clientStore, clientMessage) if misbehaved { - clientState.UpdateStateOnMisbehaviour(clientStore, clientMessage) + if err := clientState.UpdateStateOnMisbehaviour(clientStore, clientMessage); err != nil { + c.logger.Error().Err(err).Str("identifier", identifier). + Msg("failed to freeze client for misbehaviour") + return err + } c.logger.Info().Str("identifier", identifier). Msg("client frozen for misbehaviour") diff --git a/ibc/client/types/client.go b/ibc/client/types/client.go new file mode 100644 index 000000000..fb74383f1 --- /dev/null +++ b/ibc/client/types/client.go @@ -0,0 +1,238 @@ +package types + +import ( + "errors" + "fmt" + + core_types "github.com/pokt-network/pocket/shared/core/types" + "github.com/pokt-network/pocket/shared/modules" +) + +const ( + // https://github.com/cosmos/ibc/blob/main/spec/client/ics-008-wasm-client/README.md + WasmClientType = "08-wasm" +) + +var _ modules.ClientState = &ClientState{} + +// ClientType returns the client type. +func (cs *ClientState) ClientType() string { return WasmClientType } + +// GetLatestHeight returns the latest height stored. +func (cs *ClientState) GetLatestHeight() modules.Height { return cs.RecentHeight } + +// Validate performs a basic validation of the client state fields. +func (cs *ClientState) Validate() error { + if len(cs.Data) == 0 { + return errors.New("data cannot be empty") + } + + lenWasmChecksum := len(cs.WasmChecksum) + if lenWasmChecksum == 0 { + return errors.New("wasm checksum cannot be empty") + } + if lenWasmChecksum > 32 { // sha256 output is 256 bits long + return fmt.Errorf("expected 32, got %d", lenWasmChecksum) + } + + return nil +} + +type ( + statusInnerPayload struct{} + statusPayload struct { + Status statusInnerPayload `json:"status"` + } +) + +// Status returns the status of the wasm client. +// The client may be: +// - Active: frozen height is zero and client is not expired +// - Frozen: frozen height is not zero +// - Expired: the latest consensus state timestamp + trusting period <= current time +// - Unauthorized: the client type is not registered as an allowed client type +// +// A frozen client will become expired, so the Frozen status +// has higher precedence. +func (cs *ClientState) Status(clientStore modules.ProvableStore) modules.ClientStatus { + /* + payload := &statusPayload{Status: statusInnerPayload{}} + encodedData, err := json.Marshal(payload) + if err != nil { + return modules.UnknownStatus + } + + // TODO(#912): implement WASM contract querying + */ + return modules.ActiveStatus +} + +// GetTimestampAtHeight returns the timestamp of the consensus state at the given height. +func (cs *ClientState) GetTimestampAtHeight(clientStore modules.ProvableStore, height modules.Height) (uint64, error) { + consState, err := GetConsensusState(clientStore, height) + if err != nil { + return 0, err + } + return consState.GetTimestamp(), nil +} + +// Initialise checks that the initial consensus state is an 08-wasm consensus +// state and sets the client state, consensus state in the provided client store. +// It also initializes the wasm contract for the client. +func (cs *ClientState) Initialise(clientStore modules.ProvableStore, consensusState modules.ConsensusState) error { + consState, ok := consensusState.(*ConsensusState) + if !ok { + return errors.New("invalid consensus state type") + } + if err := setClientState(clientStore, cs); err != nil { + return fmt.Errorf("failed to set client state: %w", err) + } + if err := setConsensusState(clientStore, consState, cs.GetLatestHeight()); err != nil { + return fmt.Errorf("failed to set consensus state: %w", err) + } + // TODO(#912): implement WASM contract initialisation + return nil +} + +type ( + verifyMembershipInnerPayload struct { + Height modules.Height `json:"height"` + DelayTimePeriod uint64 `json:"delay_time_period"` + DelayBlockPeriod uint64 `json:"delay_block_period"` + Proof []byte `json:"proof"` + Path core_types.CommitmentPath `json:"path"` + Value []byte `json:"value"` + } + verifyMembershipPayload struct { + VerifyMembership verifyMembershipInnerPayload `json:"verify_membership"` + } +) + +// VerifyMembership is a generic proof verification method which verifies a proof +// of the existence of a value at a given CommitmentPath at the specified height. +// The caller is expected to construct the full CommitmentPath from a CommitmentPrefix +// and a standardized path (as defined in ICS 24). +// +// If a zero proof height is passed in, it will fail to retrieve the associated consensus state. +func (cs *ClientState) VerifyMembership( + clientStore modules.ProvableStore, + height modules.Height, + delayTimePeriod, delayBlockPeriod uint64, + proof, key, value []byte, +) error { + if cs.GetLatestHeight().LT(height) { + return fmt.Errorf("client state height < proof height (%d < %d)", cs.GetLatestHeight(), height) + } + + if _, err := GetConsensusState(clientStore, height); err != nil { + return errors.New("consensus state not found for proof height") + } + + /* + payload := verifyMembershipPayload{ + VerifyMembership: verifyMembershipInnerPayload{ + Height: height, + DelayTimePeriod: delayTimePeriod, + DelayBlockPeriod: delayBlockPeriod, + Proof: proof, + Path: key, + Value: value, + }, + } + + // TODO(#912): implement WASM contract method calls + */ + + return nil +} + +type ( + verifyNonMembershipInnerPayload struct { + Height modules.Height `json:"height"` + DelayTimePeriod uint64 `json:"delay_time_period"` + DelayBlockPeriod uint64 `json:"delay_block_period"` + Proof []byte `json:"proof"` + Path core_types.CommitmentPath `json:"path"` + } + verifyNonMembershipPayload struct { + VerifyNonMembership verifyNonMembershipInnerPayload `json:"verify_non_membership"` + } +) + +// VerifyNonMembership is a generic proof verification method which verifies +// the absence of a given CommitmentPath at a specified height. +// The caller is expected to construct the full CommitmentPath from a +// CommitmentPrefix and a standardized path (as defined in ICS 24). +// +// If a zero proof height is passed in, it will fail to retrieve the associated consensus state. +func (cs *ClientState) VerifyNonMembership( + clientStore modules.ProvableStore, + height modules.Height, + delayTimePeriod, delayBlockPeriod uint64, + proof, key []byte, +) error { + if cs.GetLatestHeight().LT(height) { + return fmt.Errorf("client state height < proof height (%d < %d)", cs.GetLatestHeight(), height) + } + + if _, err := GetConsensusState(clientStore, height); err != nil { + return errors.New("consensus state not found for proof height") + } + + /* + payload := verifyNonMembershipPayload{ + VerifyNonMembership: verifyNonMembershipInnerPayload{ + Height: height, + DelayTimePeriod: delayTimePeriod, + DelayBlockPeriod: delayBlockPeriod, + Proof: proof, + Path: key, + }, + } + + // TODO(#912): implement WASM contract method calls + */ + + return nil +} + +type ( + checkForMisbehaviourInnerPayload struct { + ClientMessage clientMessage `json:"client_message"` + } + checkForMisbehaviourPayload struct { + CheckForMisbehaviour checkForMisbehaviourInnerPayload `json:"check_for_misbehaviour"` + } +) + +// CheckForMisbehaviour detects misbehaviour in a submitted Header message and +// verifies the correctness of a submitted Misbehaviour ClientMessage +func (cs *ClientState) CheckForMisbehaviour(clientStore modules.ProvableStore, clientMsg modules.ClientMessage) bool { + clientMsgConcrete := clientMessage{ + Header: nil, + Misbehaviour: nil, + } + switch msg := clientMsg.(type) { + case *Header: + clientMsgConcrete.Header = msg + case *Misbehaviour: + clientMsgConcrete.Misbehaviour = msg + } + + if clientMsgConcrete.Header == nil && clientMsgConcrete.Misbehaviour == nil { + return false + } + + /* + inner := checkForMisbehaviourInnerPayload{ + ClientMessage: clientMsgConcrete, + } + payload := checkForMisbehaviourPayload{ + CheckForMisbehaviour: inner, + } + + // TODO(#912): implement WASM contract method calls + */ + + return true +} diff --git a/ibc/client/types/consensus.go b/ibc/client/types/consensus.go new file mode 100644 index 000000000..5e089cbd5 --- /dev/null +++ b/ibc/client/types/consensus.go @@ -0,0 +1,33 @@ +package types + +import ( + "errors" + + "github.com/pokt-network/pocket/shared/modules" +) + +var _ modules.ConsensusState = &ConsensusState{} + +// NewConsensusState creates a new ConsensusState instance. +func NewConsensusState(data []byte, timestamp uint64) *ConsensusState { + return &ConsensusState{ + Data: data, + Timestamp: timestamp, + } +} + +// ClientType returns the Wasm client type. +func (cs *ConsensusState) ClientType() string { + return WasmClientType +} + +// ValidateBasic defines a basic validation for the wasm client consensus state. +func (cs *ConsensusState) ValidateBasic() error { + if cs.Timestamp == 0 { + return errors.New("timestamp must be a positive Unix time") + } + if len(cs.Data) == 0 { + return errors.New("data cannot be empty") + } + return nil +} diff --git a/ibc/client/types/header.go b/ibc/client/types/header.go new file mode 100644 index 000000000..1522c49db --- /dev/null +++ b/ibc/client/types/header.go @@ -0,0 +1,23 @@ +package types + +import ( + "errors" + + "github.com/pokt-network/pocket/shared/modules" +) + +var _ modules.ClientMessage = &Header{} + +// ClientType defines that the Header is a Wasm client consensus algorithm +func (h *Header) ClientType() string { + return WasmClientType +} + +// ValidateBasic defines a basic validation for the wasm client header. +func (h *Header) ValidateBasic() error { + if len(h.Data) == 0 { + return errors.New("data cannot be empty") + } + + return nil +} diff --git a/ibc/client/types/height.go b/ibc/client/types/height.go new file mode 100644 index 000000000..21b4ec405 --- /dev/null +++ b/ibc/client/types/height.go @@ -0,0 +1,73 @@ +package types + +import ( + "fmt" + + "github.com/pokt-network/pocket/shared/modules" +) + +type ord int + +const ( + lt ord = iota - 1 + eq + gt +) + +func (h *Height) ToString() string { + return fmt.Sprintf("%d-%d", h.RevisionNumber, h.RevisionHeight) +} + +func (h *Height) IsZero() bool { + return h.RevisionNumber == 0 && h.RevisionHeight == 0 +} + +func (h *Height) LT(other modules.Height) bool { + return h.compare(other) == lt +} + +func (h *Height) LTE(other modules.Height) bool { + return h.compare(other) != gt +} + +func (h *Height) GT(other modules.Height) bool { + return h.compare(other) == gt +} + +func (h *Height) GTE(other modules.Height) bool { + return h.compare(other) != lt +} + +func (h *Height) EQ(other modules.Height) bool { + return h.compare(other) == eq +} + +func (h *Height) Increment() modules.Height { + return &Height{ + RevisionNumber: h.RevisionNumber, + RevisionHeight: h.RevisionHeight + 1, + } +} + +func (h *Height) Decrement() modules.Height { + return &Height{ + RevisionNumber: h.RevisionNumber, + RevisionHeight: h.RevisionHeight - 1, + } +} + +func (h *Height) compare(other modules.Height) ord { + if h.RevisionNumber > other.GetRevisionNumber() { + return gt + } + if h.RevisionNumber < other.GetRevisionNumber() { + return lt + } + if h.RevisionHeight > other.GetRevisionHeight() { + return gt + } + if h.RevisionHeight < other.GetRevisionHeight() { + return lt + } + return eq +} diff --git a/ibc/client/types/misbehaviour.go b/ibc/client/types/misbehaviour.go new file mode 100644 index 000000000..823ab6f28 --- /dev/null +++ b/ibc/client/types/misbehaviour.go @@ -0,0 +1,22 @@ +package types + +import ( + "errors" + + "github.com/pokt-network/pocket/shared/modules" +) + +var _ modules.ClientMessage = (*Misbehaviour)(nil) + +// ClientType is Wasm light client +func (m *Misbehaviour) ClientType() string { + return WasmClientType +} + +// ValidateBasic implements Misbehaviour interface +func (m *Misbehaviour) ValidateBasic() error { + if len(m.Data) == 0 { + return errors.New("data cannot be empty") + } + return nil +} diff --git a/ibc/client/types/proto/wasm.proto b/ibc/client/types/proto/wasm.proto index fb8b5251b..ebe6ce5e1 100644 --- a/ibc/client/types/proto/wasm.proto +++ b/ibc/client/types/proto/wasm.proto @@ -8,7 +8,7 @@ option go_package = "github.com/pokt-network/pocket/ibc/client/types"; message ClientState { bytes data = 1; // opaque data passed to the wasm client bytes wasm_checksum = 2; // checksum of the wasm client code - Height latest_height = 3; // latest height of the client + Height recent_height = 3; // latest height of the client } // ConsensusState for a Wasm light client diff --git a/ibc/client/types/queries.go b/ibc/client/types/queries.go new file mode 100644 index 000000000..40353dc9b --- /dev/null +++ b/ibc/client/types/queries.go @@ -0,0 +1,63 @@ +package types + +import ( + "github.com/pokt-network/pocket/ibc/path" + "github.com/pokt-network/pocket/shared/codec" + "github.com/pokt-network/pocket/shared/modules" +) + +// GetConsensusState returns the consensus state at the given height from a +// prefixed client store, in the format: "clients/{clientID}" +func GetConsensusState(clientStore modules.ProvableStore, height modules.Height) (modules.ConsensusState, error) { + // Retrieve the consensus state bytes from the client store + consStateBz, err := clientStore.Get(path.ConsensusStateKey(height.ToString())) + if err != nil { + return nil, err + } + + // Unmarshal into a ConsensusState interface + consState := new(ConsensusState) + if err := codec.GetCodec().Unmarshal(consStateBz, consState); err != nil { + return nil, err + } + + return consState, nil +} + +// GetClientState returns the client state from a prefixed client store, +// in the format: "clients" using the clientID provided +func GetClientState(clientStore modules.ProvableStore, identifier string) (modules.ClientState, error) { + // Retrieve the client state bytes from the client store + clientStateBz, err := clientStore.Get(path.FullClientStateKey(identifier)) + if err != nil { + return nil, err + } + + // Unmarshal into a ClientState interface + clientState := new(ClientState) + if err := codec.GetCodec().Unmarshal(clientStateBz, clientState); err != nil { + return nil, err + } + + return clientState, nil +} + +// setClientState stores the client state +// clientStore must be a prefixed client store: "clients/{clientID}" +func setClientState(clientStore modules.ProvableStore, clientState *ClientState) error { + val, err := codec.GetCodec().Marshal(clientState) + if err != nil { + return err + } + return clientStore.Set(nil, val) // key == nil ==> key == "clients/{clientID}" +} + +// setConsensusState stores the consensus state at the given height. +// clientStore must be a prefixed client store: "clients/{clientID}" +func setConsensusState(clientStore modules.ProvableStore, consensusState *ConsensusState, height modules.Height) error { + val, err := codec.GetCodec().Marshal(consensusState) + if err != nil { + return err + } + return clientStore.Set(path.ConsensusStateKey(height.ToString()), val) +} diff --git a/ibc/client/types/update.go b/ibc/client/types/update.go new file mode 100644 index 000000000..a9349d211 --- /dev/null +++ b/ibc/client/types/update.go @@ -0,0 +1,120 @@ +package types + +import ( + "github.com/pokt-network/pocket/shared/modules" +) + +type ( + clientMessage struct { + Header *Header `json:"header,omitempty"` + Misbehaviour *Misbehaviour `json:"misbehaviour,omitempty"` + } + verifyClientMessageInnerPayload struct { + ClientMessage clientMessage `json:"client_message"` + } + verifyClientMessagePayload struct { + VerifyClientMessage verifyClientMessageInnerPayload `json:"verify_client_message"` + } +) + +// VerifyClientMessage must verify a ClientMessage. A ClientMessage could be a Header, +// Misbehaviour, or batch update. It must handle each type of ClientMessage appropriately. +// +// Calls to CheckForMisbehaviour, UpdateState, and UpdateStateOnMisbehaviour will +// assume that the content of the ClientMessage has been verified and can be trusted +func (cs *ClientState) VerifyClientMessage(clientStore modules.ProvableStore, clientMsg modules.ClientMessage) error { + clientMsgConcrete := clientMessage{ + Header: nil, + Misbehaviour: nil, + } + switch clientMsg := clientMsg.(type) { + case *Header: + clientMsgConcrete.Header = clientMsg + case *Misbehaviour: + clientMsgConcrete.Misbehaviour = clientMsg + } + + /* + inner := verifyClientMessageInnerPayload{ + ClientMessage: clientMsgConcrete, + } + payload := verifyClientMessagePayload{ + VerifyClientMessage: inner, + } + + // TODO(#912): implement WASM method calls + */ + + return nil +} + +type ( + updateStateInnerPayload struct { + ClientMessage clientMessage `json:"client_message"` + } + updateStatePayload struct { + UpdateState updateStateInnerPayload `json:"update_state"` + } +) + +// UpdateState updates and stores as necessary any associated information for an +// IBC client. Upon successful update, a consensus height is returned. +// +// Client state and new consensus states are updated in the store by the contract +// Assumes the ClientMessage has already been verified +func (cs *ClientState) UpdateState(clientStore modules.ProvableStore, clientMsg modules.ClientMessage) (modules.Height, error) { + /* + header, ok := clientMsg.(*Header) + if !ok { + return nil, errors.New("client message must be a header") + } + + payload := updateStatePayload{ + UpdateState: updateStateInnerPayload{ + ClientMessage: clientMessage{ + Header: header, + }, + }, + } + + // TODO(#912): implement WASM method calls + */ + + return clientMsg.(*Header).Height, nil +} + +type ( + updateStateOnMisbehaviourInnerPayload struct { + ClientMessage clientMessage `json:"client_message"` + } + updateStateOnMisbehaviourPayload struct { + UpdateStateOnMisbehaviour updateStateOnMisbehaviourInnerPayload `json:"update_state_on_misbehaviour"` + } +) + +// UpdateStateOnMisbehaviour should perform appropriate state changes on a +// client state given that misbehaviour has been detected and verified +// Client state is updated in the store by contract. +func (cs *ClientState) UpdateStateOnMisbehaviour(clientStore modules.ProvableStore, clientMsg modules.ClientMessage) error { + var clientMsgConcrete clientMessage + switch clientMsg := clientMsg.(type) { + case *Header: + clientMsgConcrete.Header = clientMsg + case *Misbehaviour: + clientMsgConcrete.Misbehaviour = clientMsg + } + + /* + inner := updateStateOnMisbehaviourInnerPayload{ + ClientMessage: clientMsgConcrete, + } + + payload := updateStateOnMisbehaviourPayload{ + UpdateStateOnMisbehaviour: inner, + } + + // TODO(#912): implement WASM method calls + */ + + return nil +} diff --git a/ibc/client/types/upgrade.go b/ibc/client/types/upgrade.go new file mode 100644 index 000000000..d8ab9015b --- /dev/null +++ b/ibc/client/types/upgrade.go @@ -0,0 +1,71 @@ +package types + +import ( + "fmt" + + "github.com/pokt-network/pocket/shared/modules" +) + +type ( + verifyUpgradeAndUpdateStateInnerPayload struct { + UpgradeClientState modules.ClientState `json:"upgrade_client_state"` + UpgradeConsensusState modules.ConsensusState `json:"upgrade_consensus_state"` + ProofUpgradeClient []byte `json:"proof_upgrade_client"` + ProofUpgradeConsensusState []byte `json:"proof_upgrade_consensus_state"` + } + verifyUpgradeAndUpdateStatePayload struct { + VerifyUpgradeAndUpdateState verifyUpgradeAndUpdateStateInnerPayload `json:"verify_upgrade_and_update_state"` + } +) + +// VerifyUpgradeAndUpdateState, on a successful verification expects the contract +// to update the new client state, consensus state, and any other client metadata. +func (cs *ClientState) VerifyUpgradeAndUpdateState( + clientStore modules.ProvableStore, + upgradedClient modules.ClientState, + upgradedConsState modules.ConsensusState, + proofUpgradeClient, proofUpgradeConsState []byte, +) error { + /* + wasmUpgradeClientState, ok := upgradedClient.(*ClientState) + if !ok { + return errors.New("upgraded client state must be Wasm ClientState") + } + + wasmUpgradeConsState, ok := upgradedConsState.(*ConsensusState) + if !ok { + return errors.New("upgraded consensus state must be Wasm ConsensusState") + } + */ + + // last height of current counterparty chain must be client's latest height + lastHeight := cs.GetLatestHeight() + + if !upgradedClient.GetLatestHeight().GT(lastHeight) { + return fmt.Errorf("upgraded client height %s must be greater than current client height %s", + upgradedClient.GetLatestHeight(), lastHeight, + ) + } + + // Must prove against latest consensus state to ensure we are verifying + // against latest upgrade plan. + _, err := GetConsensusState(clientStore, lastHeight) + if err != nil { + return fmt.Errorf("could not retrieve consensus state for height %s", lastHeight) + } + + /* + payload := verifyUpgradeAndUpdateStatePayload{ + VerifyUpgradeAndUpdateState: verifyUpgradeAndUpdateStateInnerPayload{ + UpgradeClientState: upgradedClient, + UpgradeConsensusState: upgradedConsState, + ProofUpgradeClient: proofUpgradeClient, + ProofUpgradeConsensusState: proofUpgradeConsState, + }, + } + + // TODO(#912): implement WASM contract initialisation + */ + + return nil +} diff --git a/ibc/path/keys_ics02.go b/ibc/path/keys_ics02.go index 6015513be..8a98ded5c 100644 --- a/ibc/path/keys_ics02.go +++ b/ibc/path/keys_ics02.go @@ -28,6 +28,12 @@ func consensusStatePath(height string) string { return fmt.Sprintf("%s/%s", KeyConsensusStatePrefix, height) } +// ConsensusStateKey returns the store key for the consensus state of a particular client +// in a prefixed client store +func ConsensusStateKey(height string) []byte { + return []byte(consensusStatePath(height)) +} + // fullConsensusStatePath takes a client identifier and returns a Path under which to // store the consensus state of a client. func fullConsensusStatePath(clientID, height string) string { From 1a29904173e2cd32ca9a2610e64bbfc598b79b30 Mon Sep 17 00:00:00 2001 From: harry <53987565+h5law@users.noreply.github.com> Date: Tue, 18 Jul 2023 10:57:11 +0100 Subject: [PATCH 14/23] Add Pocket specific Client and Consensus State types --- Makefile | 1 + ibc/client/light_clients/types/fraction.go | 42 ++++++++++++++ .../light_clients/types/proto/pocket.proto | 57 +++++++++++++++++++ 3 files changed, 100 insertions(+) create mode 100644 ibc/client/light_clients/types/fraction.go create mode 100644 ibc/client/light_clients/types/proto/pocket.proto diff --git a/Makefile b/Makefile index 817dfde78..06d12e38b 100644 --- a/Makefile +++ b/Makefile @@ -321,6 +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 # echo "View generated proto files by running: make protogen_show" diff --git a/ibc/client/light_clients/types/fraction.go b/ibc/client/light_clients/types/fraction.go new file mode 100644 index 000000000..75a2471ab --- /dev/null +++ b/ibc/client/light_clients/types/fraction.go @@ -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 +} diff --git a/ibc/client/light_clients/types/proto/pocket.proto b/ibc/client/light_clients/types/proto/pocket.proto new file mode 100644 index 000000000..94f17a7e4 --- /dev/null +++ b/ibc/client/light_clients/types/proto/pocket.proto @@ -0,0 +1,57 @@ +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 "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 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 + bytes proof_specs = 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 +} From 92236b3d819d77dc85cc490e614508bd69134f25 Mon Sep 17 00:00:00 2001 From: harry <53987565+h5law@users.noreply.github.com> Date: Tue, 18 Jul 2023 20:24:58 +0100 Subject: [PATCH 15/23] Allow host introspection of its own ConsensusState --- ibc/client/queries.go | 24 ++++++++++++++++++++++++ shared/modules/ibc_client_module.go | 21 +++++++++++++-------- 2 files changed, 37 insertions(+), 8 deletions(-) diff --git a/ibc/client/queries.go b/ibc/client/queries.go index 2ea5b36dc..412e993a0 100644 --- a/ibc/client/queries.go +++ b/ibc/client/queries.go @@ -1,8 +1,10 @@ package client import ( + 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" ) @@ -32,3 +34,25 @@ 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 +} diff --git a/shared/modules/ibc_client_module.go b/shared/modules/ibc_client_module.go index 816e579c2..1f1db8f13 100644 --- a/shared/modules/ibc_client_module.go +++ b/shared/modules/ibc_client_module.go @@ -23,8 +23,8 @@ type ClientManagerOption func(ClientManager) type clientManagerFactory = FactoryWithOptions[ClientManager, ClientManagerOption] -// ClientManager is the interface that defines the methods needed to interact with an IBC light client -// it manages the different lifecycle methods for the different clients +// ClientManager is the interface that defines the methods needed to interact with an +// IBC light client it manages the different lifecycle methods for the different clients // https://github.com/cosmos/ibc/tree/main/spec/core/ics-002-client-semantics type ClientManager interface { Submodule @@ -40,12 +40,6 @@ type ClientManager interface { // the ClientMessage can be verified using the existing ClientState and ConsensusState UpdateClient(identifier string, clientMessage ClientMessage) error - // GetConsensusState returns the ConsensusState at the given height for the given client - GetConsensusState(identifier string, height Height) (ConsensusState, error) - - // GetClientState returns the ClientState for the given client - GetClientState(identifier string) (ClientState, error) - // UpgradeClient upgrades an existing client with the given identifier using the // ClientState and ConsentusState provided. It can only do so if the new client // was committed to by the old client at the specified upgrade height @@ -54,6 +48,17 @@ type ClientManager interface { clientState ClientState, consensusState ConsensusState, proofUpgradeClient, proofUpgradeConsState []byte, ) error + + // === Client Queries === + + // GetConsensusState returns the ConsensusState at the given height for the given client + GetConsensusState(identifier string, height Height) (ConsensusState, error) + + // GetClientState returns the ClientState for the given client + GetClientState(identifier string) (ClientState, error) + + // GetHostConsensusState returns the ConsensusState at the given height for the host chain + GetHostConsensusState(height Height) (ConsensusState, error) } // ClientState is an interface that defines the methods required by a clients From ddccdd0d88b618fe283262dc63a7323836c498a2 Mon Sep 17 00:00:00 2001 From: harry <53987565+h5law@users.noreply.github.com> Date: Wed, 19 Jul 2023 22:10:50 +0100 Subject: [PATCH 16/23] Add GetHostClientState() --- ibc/client/queries.go | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/ibc/client/queries.go b/ibc/client/queries.go index 412e993a0..c9f426ca3 100644 --- a/ibc/client/queries.go +++ b/ibc/client/queries.go @@ -1,12 +1,16 @@ 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 @@ -56,3 +60,39 @@ func (c *clientManager) GetHostConsensusState(height modules.Height) (modules.Co } 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 +} From 9f7f2bbdfeca91debab77711c54d18daf8fca16f Mon Sep 17 00:00:00 2001 From: harry <53987565+h5law@users.noreply.github.com> Date: Thu, 20 Jul 2023 12:40:22 +0100 Subject: [PATCH 17/23] Add VerifyHostClientState method --- Makefile | 2 +- ibc/client/introspect.go | 151 ++++++++++++++++++ .../light_clients/types/proto/pocket.proto | 3 +- ibc/client/queries.go | 64 -------- ibc/path/keys_ics02.go | 10 +- persistence/gov.go | 5 + shared/modules/ibc_client_module.go | 14 ++ shared/modules/persistence_module.go | 1 + 8 files changed, 182 insertions(+), 68 deletions(-) create mode 100644 ibc/client/introspect.go diff --git a/Makefile b/Makefile index 06d12e38b..d9ab7c815 100644 --- a/Makefile +++ b/Makefile @@ -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" diff --git a/ibc/client/introspect.go b/ibc/client/introspect.go new file mode 100644 index 000000000..9d57bc3f8 --- /dev/null +++ b/ibc/client/introspect.go @@ -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 +} 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/path/keys_ics02.go b/ibc/path/keys_ics02.go index 8a98ded5c..7f06fd089 100644 --- a/ibc/path/keys_ics02.go +++ b/ibc/path/keys_ics02.go @@ -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 { diff --git a/persistence/gov.go b/persistence/gov.go index 5ddec2884..73694ac6e 100644 --- a/persistence/gov.go +++ b/persistence/gov.go @@ -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" diff --git a/shared/modules/ibc_client_module.go b/shared/modules/ibc_client_module.go index 1f1db8f13..c4e57e514 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, 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 @@ -69,6 +79,8 @@ type ClientManager interface { type ClientState interface { proto.Message + GetData() []byte + GetWasmChecksum() []byte ClientType() string GetLatestHeight() Height Validate() error @@ -155,6 +167,7 @@ type ClientState interface { type ConsensusState interface { proto.Message + GetData() []byte ClientType() string GetTimestamp() uint64 ValidateBasic() error @@ -171,6 +184,7 @@ type ConsensusState interface { type ClientMessage interface { proto.Message + GetData() []byte ClientType() string ValidateBasic() error } diff --git a/shared/modules/persistence_module.go b/shared/modules/persistence_module.go index 7b76802b3..6ee724d63 100644 --- a/shared/modules/persistence_module.go +++ b/shared/modules/persistence_module.go @@ -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 From 93e71e874a98db4027db98a021196b82bb1f4ac0 Mon Sep 17 00:00:00 2001 From: harry <53987565+h5law@users.noreply.github.com> Date: Tue, 18 Jul 2023 23:55:40 +0100 Subject: [PATCH 18/23] Add nolint lines for unused types --- ibc/client/types/client.go | 4 ++++ ibc/client/types/update.go | 3 +++ ibc/client/types/upgrade.go | 2 ++ 3 files changed, 9 insertions(+) diff --git a/ibc/client/types/client.go b/ibc/client/types/client.go index fb74383f1..27cb74171 100644 --- a/ibc/client/types/client.go +++ b/ibc/client/types/client.go @@ -38,6 +38,7 @@ func (cs *ClientState) Validate() error { return nil } +//nolint:unused // types defined for future use type ( statusInnerPayload struct{} statusPayload struct { @@ -94,6 +95,7 @@ func (cs *ClientState) Initialise(clientStore modules.ProvableStore, consensusSt return nil } +//nolint:unused // types defined for future use type ( verifyMembershipInnerPayload struct { Height modules.Height `json:"height"` @@ -146,6 +148,7 @@ func (cs *ClientState) VerifyMembership( return nil } +//nolint:unused // types defined for future use type ( verifyNonMembershipInnerPayload struct { Height modules.Height `json:"height"` @@ -196,6 +199,7 @@ func (cs *ClientState) VerifyNonMembership( return nil } +//nolint:unused // types defined for future use type ( checkForMisbehaviourInnerPayload struct { ClientMessage clientMessage `json:"client_message"` diff --git a/ibc/client/types/update.go b/ibc/client/types/update.go index a9349d211..1fefaea0a 100644 --- a/ibc/client/types/update.go +++ b/ibc/client/types/update.go @@ -4,6 +4,7 @@ import ( "github.com/pokt-network/pocket/shared/modules" ) +//nolint:unused // types defined for future use type ( clientMessage struct { Header *Header `json:"header,omitempty"` @@ -48,6 +49,7 @@ func (cs *ClientState) VerifyClientMessage(clientStore modules.ProvableStore, cl return nil } +//nolint:unused // types defined for future use type ( updateStateInnerPayload struct { ClientMessage clientMessage `json:"client_message"` @@ -83,6 +85,7 @@ func (cs *ClientState) UpdateState(clientStore modules.ProvableStore, clientMsg return clientMsg.(*Header).Height, nil } +//nolint:unused // types defined for future use type ( updateStateOnMisbehaviourInnerPayload struct { ClientMessage clientMessage `json:"client_message"` diff --git a/ibc/client/types/upgrade.go b/ibc/client/types/upgrade.go index d8ab9015b..9f89e06da 100644 --- a/ibc/client/types/upgrade.go +++ b/ibc/client/types/upgrade.go @@ -6,6 +6,7 @@ import ( "github.com/pokt-network/pocket/shared/modules" ) +//nolint:unused // types defined for future use type ( verifyUpgradeAndUpdateStateInnerPayload struct { UpgradeClientState modules.ClientState `json:"upgrade_client_state"` @@ -26,6 +27,7 @@ func (cs *ClientState) VerifyUpgradeAndUpdateState( upgradedConsState modules.ConsensusState, proofUpgradeClient, proofUpgradeConsState []byte, ) error { + //nolint:gocritic // Commented out code is for future us /* wasmUpgradeClientState, ok := upgradedClient.(*ClientState) if !ok { From f3a4b56cb968d43737e2721c76edb6bb74744320 Mon Sep 17 00:00:00 2001 From: harry <53987565+h5law@users.noreply.github.com> Date: Thu, 20 Jul 2023 13:52:52 +0100 Subject: [PATCH 19/23] Add ICS-02 documentation --- ibc/docs/README.md | 9 ++ ibc/docs/ics02.md | 156 ++++++++++++++++++++++++++++ shared/modules/ibc_client_module.go | 4 +- 3 files changed, 167 insertions(+), 2 deletions(-) create mode 100644 ibc/docs/ics02.md diff --git a/ibc/docs/README.md b/ibc/docs/README.md index 28160a08b..be84e362e 100644 --- a/ibc/docs/README.md +++ b/ibc/docs/README.md @@ -10,6 +10,7 @@ - [Components](#components) - [ICS-24 Host Requirements](#ics-24-host-requirements) - [ICS-23 Vector Commitments](#ics-23-vector-commitments) + - [ICS-02 Client Semantics](#ics-02-client-semantics) ## Definitions @@ -115,7 +116,15 @@ See: [ICS-24](./ics24.md) for more details on the specifics of the ICS-24 implem See: [ICS-23](./ics23.md) for more details on the specifics of the ICS-23 implementation for Pocket. +### ICS-02 Client Semantics + +[ICS-02][ics02] defines the methods, and interfaces through which the IBC host will interact with and manage the different clients it uses. This includes the creation of clients, their updates and upgrades as well as verifying any proofs with the counterparty client's state. The following interfaces must be defined: `ClientState`, `ConsensusState`, `ClientMessage`, `Height` each of these will potentially have a different implementation for each client type. In order to improve client upgradeability Pocket uses [ICS-08][ics08] WASM clients, which use a generic implementation of each interface, passing in opaque serialised data to the WASM client to be deserialised and used internally. + +See [ICS-02](./ics02.md) for more details on the specifics of the ICS-02 implementation for Pocket. + [ibc-spec]: https://github.com/cosmos/ibc [ics24]: https://github.com/cosmos/ibc/blob/main/spec/core/ics-024-host-requirements/README.md [ics23]: https://github.com/cosmos/ibc/blob/main/spec/core/ics-023-vector-commitments/README.md [smt]: https://github.com/pokt-network/smt +[ics02]: https://github.com/cosmos/ibc/blob/main/spec/core/ics-002-client-semantics/README.md +[ics08]: https://github.com/cosmos/ibc/blob/main/spec/client/ics-008-wasm-client/README.md diff --git a/ibc/docs/ics02.md b/ibc/docs/ics02.md new file mode 100644 index 000000000..dfa64d2a7 --- /dev/null +++ b/ibc/docs/ics02.md @@ -0,0 +1,156 @@ +# ICS-02 Client Semantics + +- [Definitions](#definitions) + - ["light client"](#light-client) +- [Overview](#overview) +- [Implementation](#implementation) + - [Client Manager](#client-manager) + - [Lifecycle Management](#lifecycle-management) + - [Client Queries](#client-queries) +- [Types](#types) +- [Provable Stores](#provable-stores) + +## Definitions + +### "light client" + +In the context of IBC a light client differs from a traditional "light client." An IBC light client is simply a state verification algorithm. It does not sync with the network, it does not download headers. Instead the updates/new headers for a client are provided by an IBC relayer. + +## Overview + +IBC utilises light clients to verify the correctness of the state of a counterparty chain. This allows for an IBC packet to be committed to in the state of the network on a source chain and then validated through the light client on the counterparty chain. + +[ICS-02][ics02] defines the interfaces and types through which the host machine can interact with the light clients it manages. This includes: client creation, client updates and upgrades as well as submitting misbehaviour from the chain the client is tracking. In addition to this, ICS-02 also defines numerous interfaces that are used by the different client implementations in order to carry out the previous actions as well as verify the state of the chain they represent via a proof. + +## Implementation + +[ICS-02][ics02] is implemented according to the specification. However as the Pocket protocol will utilise [ICS-08][ics08] WASM clients for the improvements to client upgradeability; the implementations of the `ClientState`, `ConsensusState` and other interfaces are specific to a WASM client. + +The implementation details are explored below, the code for ICS-02 can be found in [ibc/client](../client/) + +### Client Manager + +The `ClientManager` is the submodule that governs the light client implementations and implements the [ICS-02][ics02] interface. It is defined in [shared/modules/ibc_client_module.go](../../shared/modules/ibc_client_module.go). The `ClientManager` exposed the following methods: + +```go +// === Client Lifecycle Management === + +// CreateClient creates a new client with the given client state and initial consensus state +// and initialises its unique identifier in the IBC store +CreateClient(ClientState, ConsensusState) (string, error) + +// UpdateClient updates an existing client with the given ClientMessage, given that +// the ClientMessage can be verified using the existing ClientState and ConsensusState +UpdateClient(identifier string, clientMessage ClientMessage) error + +// UpgradeClient upgrades an existing client with the given identifier using the +// ClientState and ConsenusState provided. It can only do so if the new client +// was committed to by the old client at the specified upgrade height +UpgradeClient( + identifier string, + clientState ClientState, consensusState ConsensusState, + proofUpgradeClient, proofUpgradeConsState []byte, +) error + +// === Client Queries === + +// GetConsensusState returns the ConsensusState at the given height for the given client +GetConsensusState(identifier string, height Height) (ConsensusState, error) + +// GetClientState returns the ClientState for the given client +GetClientState(identifier string) (ClientState, error) + +// GetHostConsensusState returns the ConsensusState at the given height for the host chain +GetHostConsensusState(height Height) (ConsensusState, error) + +// GetHostClientState returns the ClientState at the provided 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 +``` + +#### Lifecycle Management + +The `ClientManager` handles the creation, updates and upgrades for a light client. It does so by utilising the following interfaces: + +```go +type ClientState interface +type ConsensusState interface +type ClientMessage interface +``` + +These interfaces are generic but have unique implementations for each client type. As Pocket utilises WASM light clients each implementation contains a `data []byte` field which contains a serialised, opaque data structure for use within the WASM client. + +The `data` field is a JSON serialised payload that contains the data required for the client to carry out the desired operation, as well as the operation name to carry out. For example, a verify membership payload is constructed using the following `struct`s: + +```go +type ( + verifyMembershipInnerPayload struct { + Height modules.Height `json:"height"` + DelayTimePeriod uint64 `json:"delay_time_period"` + DelayBlockPeriod uint64 `json:"delay_block_period"` + Proof []byte `json:"proof"` + Path core_types.CommitmentPath `json:"path"` + Value []byte `json:"value"` + } + verifyMembershipPayload struct { + VerifyMembership verifyMembershipInnerPayload `json:"verify_membership"` + } +) +``` + +By utilising this pattern of JSON payloads the WASM client itself is able to unmarshal the opaque payload into their own internal protobuf definitions for the implementation of the `ClientState` for example. This allows them to have a much simpler implementation and focus solely on the logic around verification and utilising simple storage. + +See: [Types](#types) for more information on the interfaces and types used in the ICS-02 implementation + +#### Client Queries + +[ICS-24](./ics24.md) instructs that a host must allow for the introspection of both its own `ConsensusState` and `ClientState`. This is done through the `ClientManager`'s `GetHostConsensusState` and `GetHostClientState` methods. These are then used by relayers to: + +1. Provide light clients running on counterparty chains the `ConsensusState` and `ClientState` objects they need. +2. Verify the state of a light client running on a counterparty chain, against the host chain's current `ClientState` + +The other queries used by the `ClientManager` involve querying the [ICS-24](./ics24.md) stores to retrieve the `ClientState` and `ConsensusState` stored objects on a per-client basis. + +See [Provable Stores](#provable-stores) for more information on how the `ProvableStore`s are used in ICS-02. + +## Types + +The [ICS-02 specification][ics02] defines the need for numerous interfaces: + +1. `ClientState` + - `ClientState` is an opaque data structure defined by a client type. It may keep arbitrary internal state to track verified roots and past misbehaviours. +2. `ConsensusState` + - `ConsensusState` is an opaque data structure defined by a client type, used by the + validity predicate to verify new commits & state roots. Likely the structure will contain the last commit produced by the consensus process, including signatures and validator set metadata. +3. `ClientMessage` + - `ClientMessage` is an opaque data structure defined by a client type which provides information to update the client. `ClientMessage`s can be submitted to an associated client to add new `ConsensusState`(s) and/or update the `ClientState`. They likely contain a height, a proof, a commitment root, and possibly updates to the validity predicate. +4. `Height` + - `Height` is an interface that defines the methods required by a clients implementation of their own height object `Height`s usually have two components: revision number and revision height. + +As previously mentioned these interfaces have different implementations for each light client type. This is due to the different light clients representing different networks, consensus types and chains altogether. The implementation of these interfaces can be found in [ibc/client/types/proto/wasm.proto](../client/types/proto/wasm.proto). + +The `data` field in these messages represents the opaque data structure that is internal to the WASM client. This is a part of the JSON serialised payload that is passed into the WASM client, and is used to carry out any relevant operations. This enables the WASM client to define its own internal data structures that can unmarshal the JSON payload into its own internal protobuf definitions. + +See: [shared/modules/ibc_client_module.go](../../shared/modules/ibc_client_module.go) for the details on the interfaces and their methods. + +## Provable Stores + +ICS-02 requires a lot of data to be stored in the IBC stores (defined in [ICS-24](./ics24.md)). In order to do this the provable stores must be initialised on a per client ID basis. This means that any operation using the provable store does not require the use of the `clientID`. This is done as follows: + +```go +prefixed := path.ApplyPrefix(core_types.CommitmentPrefix(path.KeyClientStorePrefix), identifier) +clientStore, err := c.GetBus().GetIBCHost().GetProvableStore(string(prefixed)) +``` + +This allows the `clientStore` to be used by the WASM clients without them needing to keep track of their unique identifiers. + +See: [ibc/client/submodule.go](../client/submodule.go) for more details on how this is used. + +[ics02]: https://github.com/cosmos/ibc/blob/main/spec/core/ics-002-client-semantics/README.md +[ics08]: https://github.com/cosmos/ibc/blob/main/spec/client/ics-008-wasm-client/README.md diff --git a/shared/modules/ibc_client_module.go b/shared/modules/ibc_client_module.go index c4e57e514..d4dc1d716 100644 --- a/shared/modules/ibc_client_module.go +++ b/shared/modules/ibc_client_module.go @@ -41,7 +41,7 @@ type ClientManager interface { UpdateClient(identifier string, clientMessage ClientMessage) error // UpgradeClient upgrades an existing client with the given identifier using the - // ClientState and ConsentusState provided. It can only do so if the new client + // ClientState and ConsenusState provided. It can only do so if the new client // was committed to by the old client at the specified upgrade height UpgradeClient( identifier string, @@ -60,7 +60,7 @@ 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 returns the ClientState at the provided height for the host chain GetHostClientState(height Height) (ClientState, error) // GetCurrentHeight returns the current IBC client height of the network From 6380ee142cab4c57aa464a60d6a528fb50401aff Mon Sep 17 00:00:00 2001 From: harry <53987565+h5law@users.noreply.github.com> Date: Fri, 21 Jul 2023 16:47:31 +0100 Subject: [PATCH 20/23] Add client query tests --- ibc/client/types/queries_test.go | 411 +++++++++++++++++++++++++++++++ ibc/store/bulk_store_cache.go | 2 +- ibc/store/provable_store.go | 4 +- ibc/store/provable_store_test.go | 2 +- 4 files changed, 415 insertions(+), 4 deletions(-) create mode 100644 ibc/client/types/queries_test.go diff --git a/ibc/client/types/queries_test.go b/ibc/client/types/queries_test.go new file mode 100644 index 000000000..0e58c741d --- /dev/null +++ b/ibc/client/types/queries_test.go @@ -0,0 +1,411 @@ +package types + +import ( + "bytes" + "crypto/sha256" + "encoding/hex" + "testing" + + "github.com/golang/mock/gomock" + "github.com/pokt-network/pocket/ibc/store" + "github.com/pokt-network/pocket/persistence/kvstore" + "github.com/pokt-network/pocket/runtime" + "github.com/pokt-network/pocket/runtime/configs" + "github.com/pokt-network/pocket/runtime/test_artifacts" + "github.com/pokt-network/pocket/shared/codec" + core_types "github.com/pokt-network/pocket/shared/core/types" + "github.com/pokt-network/pocket/shared/modules" + mock_modules "github.com/pokt-network/pocket/shared/modules/mocks" + "github.com/pokt-network/smt" + "github.com/stretchr/testify/require" +) + +func TestClientState_Set(t *testing.T) { + // get provable store prefixed with clients/123 + provableStore := newTestProvableStore(t, "123") + + // create a client state + clientState := &ClientState{ + Data: []byte("data"), + WasmChecksum: make([]byte, 32), + } + bz, err := codec.GetCodec().Marshal(clientState) + require.NoError(t, err) + + // set the client state + require.NoError(t, setClientState(provableStore, clientState)) + + // check cache + cache := kvstore.NewMemKVStore() + + // flush cache + require.NoError(t, provableStore.FlushEntries(cache)) + + // get all from cache + keys, vals, err := cache.GetAll([]byte{}, false) + require.NoError(t, err) + require.Len(t, keys, 1) + require.Len(t, vals, 1) + + // check key and value set correctly + require.Equal(t, []byte("clients/123/1/clients/123/clientState"), keys[0]) + require.Equal(t, vals[0], bz) +} + +func TestConsensusState_Set(t *testing.T) { + // get provable store prefixed with clients/123 + provableStore := newTestProvableStore(t, "123") + + // create a consensus state + consensusState := &ConsensusState{ + Data: []byte("data"), + Timestamp: 1, + } + height := &Height{ + RevisionNumber: 1, + RevisionHeight: 1, + } + bz, err := codec.GetCodec().Marshal(consensusState) + require.NoError(t, err) + + // set the client state + require.NoError(t, setConsensusState(provableStore, consensusState, height)) + + // check cache + cache := kvstore.NewMemKVStore() + + // flush cache + require.NoError(t, provableStore.FlushEntries(cache)) + + // get all from cache + keys, vals, err := cache.GetAll([]byte{}, false) + require.NoError(t, err) + require.Len(t, keys, 1) + require.Len(t, vals, 1) + + // check key and value set correctly + require.Equal(t, []byte("clients/123/1/clients/123/consensusStates/1-1"), keys[0]) + require.Equal(t, vals[0], bz) +} + +func TestClientState_Get(t *testing.T) { + clientStore := newTestProvableStore(t, "") + + testCases := []struct { + name string + clientId string + data []byte + checksum []byte + expectedErr error + }{ + { + name: "client state not found", + clientId: "124", + data: nil, + checksum: nil, + expectedErr: core_types.ErrIBCKeyDoesNotExist("clients/124/clientState"), + }, + { + name: "client state found", + clientId: "123", + data: []byte("data"), + checksum: make([]byte, 32), + expectedErr: nil, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + clientState, err := GetClientState(clientStore, tc.clientId) + require.ErrorIs(t, err, tc.expectedErr) + if tc.expectedErr == nil { + require.Equal(t, clientState.GetData(), tc.data) + require.Equal(t, clientState.GetWasmChecksum(), tc.checksum) + } + }) + } +} + +func TestConsensusState_Get(t *testing.T) { + clientStore := newTestProvableStore(t, "123") + + testCases := []struct { + name string + height *Height + data []byte + timestamp uint64 + expectedErr error + }{ + { + name: "consensus state not found - wrong height", + height: &Height{RevisionNumber: 1, RevisionHeight: 2}, + data: nil, + timestamp: 0, + expectedErr: core_types.ErrIBCKeyDoesNotExist("clients/123/consensusStates/1-2"), + }, + { + name: "consensus state found", + height: &Height{RevisionNumber: 1, RevisionHeight: 1}, + data: []byte("data"), + timestamp: 1, + expectedErr: nil, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + consensusState, err := GetConsensusState(clientStore, tc.height) + require.ErrorIs(t, err, tc.expectedErr) + if tc.expectedErr == nil { + require.Equal(t, consensusState.GetData(), tc.data) + require.Equal(t, consensusState.GetTimestamp(), tc.timestamp) + } + }) + } +} + +func newTestProvableStore(t *testing.T, clientId string) modules.ProvableStore { + t.Helper() + + tree, nodeStore, dbMap := setupDB(t) + + runtimeCfg := newTestRuntimeConfig(t) + bus, err := runtime.CreateBus(runtimeCfg) + require.NoError(t, err) + + persistenceMock := newPersistenceMock(t, bus, dbMap) + bus.RegisterModule(persistenceMock) + consensusMock := newConsensusMock(t, bus) + bus.RegisterModule(consensusMock) + treeStoreMock := newTreeStoreMock(t, bus, tree, nodeStore) + bus.RegisterModule(treeStoreMock) + p2pMock := newTestP2PModule(t, bus) + bus.RegisterModule(p2pMock) + utilityMock := newUtilityMock(t, bus) + bus.RegisterModule(utilityMock) + + privKey := runtimeCfg.GetConfig().IBC.Host.PrivateKey + + t.Cleanup(func() { + err := persistenceMock.Stop() + require.NoError(t, err) + err = consensusMock.Stop() + require.NoError(t, err) + err = p2pMock.Stop() + require.NoError(t, err) + }) + + if clientId != "" { + clientId = "/" + clientId + } + + return store.NewProvableStore(bus, []byte("clients"+clientId), privKey) +} + +func setupDB(t *testing.T) (*smt.SMT, kvstore.KVStore, map[string]string) { + dbMap := make(map[string]string, 0) + nodeStore := kvstore.NewMemKVStore() + tree := smt.NewSparseMerkleTree(nodeStore, sha256.New()) + + clientState := &ClientState{ + Data: []byte("data"), + WasmChecksum: make([]byte, 32), + } + cliBz, err := codec.GetCodec().Marshal(clientState) + require.NoError(t, err) + consensusState := &ConsensusState{ + Data: []byte("data"), + Timestamp: 1, + } + conBz, err := codec.GetCodec().Marshal(consensusState) + require.NoError(t, err) + + keys := [][]byte{ + []byte("clients/123/consensusStates/1-1"), + []byte("clients/123/clientState"), + } + values := [][]byte{ + conBz, + cliBz, + } + + for i, key := range keys { + dbMap[hex.EncodeToString(key)] = hex.EncodeToString(values[i]) + err := tree.Update(key, values[i]) + require.NoError(t, err) + } + + require.NoError(t, tree.Commit()) + + t.Cleanup(func() { + err := nodeStore.Stop() + require.NoError(t, err) + }) + + return tree, nodeStore, dbMap +} + +func newConsensusMock(t *testing.T, bus modules.Bus) *mock_modules.MockConsensusModule { + t.Helper() + + ctrl := gomock.NewController(t) + consensusMock := mock_modules.NewMockConsensusModule(ctrl) + consensusMock.EXPECT().GetModuleName().Return(modules.ConsensusModuleName).AnyTimes() + consensusMock.EXPECT().Start().Return(nil).AnyTimes() + consensusMock.EXPECT().Stop().Return(nil).AnyTimes() + consensusMock.EXPECT().SetBus(gomock.Any()).Return().AnyTimes() + consensusMock.EXPECT().GetBus().Return(bus).AnyTimes() + consensusMock.EXPECT().CurrentHeight().Return(uint64(1)).AnyTimes() + + return consensusMock +} + +func newUtilityMock(t *testing.T, bus modules.Bus) *mock_modules.MockUtilityModule { + t.Helper() + + ctrl := gomock.NewController(t) + utilityMock := mock_modules.NewMockUtilityModule(ctrl) + utilityMock.EXPECT().GetModuleName().Return(modules.UtilityModuleName).AnyTimes() + utilityMock.EXPECT().Start().Return(nil).AnyTimes() + utilityMock.EXPECT().Stop().Return(nil).AnyTimes() + utilityMock.EXPECT().SetBus(bus).Return().AnyTimes() + utilityMock.EXPECT().GetBus().Return(bus).AnyTimes() + utilityMock.EXPECT().HandleTransaction(gomock.Any()).Return(nil).AnyTimes() + + return utilityMock +} + +func newPersistenceMock(t *testing.T, + bus modules.Bus, + dbMap map[string]string, +) *mock_modules.MockPersistenceModule { + t.Helper() + + ctrl := gomock.NewController(t) + persistenceMock := mock_modules.NewMockPersistenceModule(ctrl) + persistenceReadContextMock := mock_modules.NewMockPersistenceReadContext(ctrl) + + persistenceMock.EXPECT().GetModuleName().Return(modules.PersistenceModuleName).AnyTimes() + persistenceMock.EXPECT().Start().Return(nil).AnyTimes() + persistenceMock.EXPECT().Stop().Return(nil).AnyTimes() + persistenceMock.EXPECT().SetBus(gomock.Any()).Return().AnyTimes() + persistenceMock.EXPECT().GetBus().Return(bus).AnyTimes() + persistenceMock.EXPECT().NewReadContext(gomock.Any()).Return(persistenceReadContextMock, nil).AnyTimes() + + persistenceMock.EXPECT().ReleaseWriteContext().Return(nil).AnyTimes() + persistenceReadContextMock. + EXPECT(). + GetIBCStoreEntry(gomock.Any(), gomock.Any()). + DoAndReturn( + func(key []byte, _ uint64) ([]byte, error) { + value, ok := dbMap[hex.EncodeToString(key)] + if !ok { + return nil, core_types.ErrIBCKeyDoesNotExist(string(key)) + } + bz, err := hex.DecodeString(value) + if err != nil { + return nil, err + } + if bytes.Equal(bz, nil) { + return nil, core_types.ErrIBCKeyDoesNotExist(string(key)) + } + return bz, nil + }). + AnyTimes() + + persistenceReadContextMock. + EXPECT(). + Release(). + AnyTimes() + + return persistenceMock +} + +func newTreeStoreMock(t *testing.T, + bus modules.Bus, + tree *smt.SMT, + nodeStore kvstore.KVStore, +) *mock_modules.MockTreeStoreModule { + t.Helper() + + ctrl := gomock.NewController(t) + treeStoreMock := mock_modules.NewMockTreeStoreModule(ctrl) + treeStoreMock.EXPECT().GetModuleName().Return(modules.TreeStoreSubmoduleName).AnyTimes() + treeStoreMock.EXPECT().SetBus(gomock.Any()).Return().AnyTimes() + treeStoreMock.EXPECT().GetBus().Return(bus).AnyTimes() + + treeStoreMock. + EXPECT(). + GetTree(gomock.Any()). + DoAndReturn( + func(_ string) ([]byte, kvstore.KVStore) { + return tree.Root(), nodeStore + }). + AnyTimes() + + return treeStoreMock +} + +func newTestP2PModule(t *testing.T, bus modules.Bus) modules.P2PModule { + t.Helper() + + ctrl := gomock.NewController(t) + p2pMock := mock_modules.NewMockP2PModule(ctrl) + + p2pMock.EXPECT().Start().Return(nil).AnyTimes() + p2pMock.EXPECT().Stop().Return(nil).AnyTimes() + p2pMock.EXPECT().SetBus(gomock.Any()).Return().AnyTimes() + p2pMock.EXPECT().GetBus().Return(bus).AnyTimes() + p2pMock.EXPECT(). + Broadcast(gomock.Any()). + Return(nil). + AnyTimes() + p2pMock.EXPECT(). + Send(gomock.Any(), gomock.Any()). + Return(nil). + AnyTimes() + p2pMock.EXPECT().GetModuleName().Return(modules.P2PModuleName).AnyTimes() + p2pMock.EXPECT().HandleEvent(gomock.Any()).Return(nil).AnyTimes() + + return p2pMock +} + +// TECHDEBT: centralise these helper functions in internal/testutils +func newTestRuntimeConfig(t *testing.T) *runtime.Manager { + t.Helper() + cfg, err := configs.CreateTempConfig(&configs.Config{ + Consensus: &configs.ConsensusConfig{ + PrivateKey: "0ca1a40ddecdab4f5b04fa0bfed1d235beaa2b8082e7554425607516f0862075dfe357de55649e6d2ce889acf15eb77e94ab3c5756fe46d3c7538d37f27f115e", + }, + Utility: &configs.UtilityConfig{ + MaxMempoolTransactionBytes: 1000000, + MaxMempoolTransactions: 1000, + }, + Persistence: &configs.PersistenceConfig{ + PostgresUrl: "", + NodeSchema: "test_schema", + BlockStorePath: ":memory:", + TxIndexerPath: ":memory:", + TreesStoreDir: ":memory:", + MaxConnsCount: 50, + MinConnsCount: 1, + MaxConnLifetime: "5m", + MaxConnIdleTime: "1m", + HealthCheckPeriod: "30s", + }, + Validator: &configs.ValidatorConfig{Enabled: true}, + IBC: &configs.IBCConfig{ + Enabled: true, + StoresDir: ":memory:", + Host: &configs.IBCHostConfig{ + PrivateKey: "0ca1a40ddecdab4f5b04fa0bfed1d235beaa2b8082e7554425607516f0862075dfe357de55649e6d2ce889acf15eb77e94ab3c5756fe46d3c7538d37f27f115e", + }, + }, + }) + if err != nil { + t.Fatalf("Error creating config: %s", err) + } + genesisState, _ := test_artifacts.NewGenesisState(0, 0, 0, 0) + runtimeCfg := runtime.NewManager(cfg, genesisState) + return runtimeCfg +} diff --git a/ibc/store/bulk_store_cache.go b/ibc/store/bulk_store_cache.go index 953e9ca3b..4e5e1bd13 100644 --- a/ibc/store/bulk_store_cache.go +++ b/ibc/store/bulk_store_cache.go @@ -92,7 +92,7 @@ func (s *bulkStoreCache) AddStore(name string) error { if _, ok := s.ls.stores[name]; ok { return coreTypes.ErrIBCStoreAlreadyExists(name) } - store := newProvableStore(s.GetBus(), coreTypes.CommitmentPrefix(name), s.privateKey) + store := NewProvableStore(s.GetBus(), coreTypes.CommitmentPrefix(name), s.privateKey) s.ls.stores[store.name] = store return nil } diff --git a/ibc/store/provable_store.go b/ibc/store/provable_store.go index 519388c33..fb13e74c6 100644 --- a/ibc/store/provable_store.go +++ b/ibc/store/provable_store.go @@ -51,8 +51,8 @@ type provableStore struct { privateKey string } -// newProvableStore returns a new instance of provableStore with the bus and prefix provided -func newProvableStore(bus modules.Bus, prefix coreTypes.CommitmentPrefix, privateKey string) *provableStore { +// NewProvableStore returns a new instance of provableStore with the bus and prefix provided +func NewProvableStore(bus modules.Bus, prefix coreTypes.CommitmentPrefix, privateKey string) *provableStore { return &provableStore{ m: sync.Mutex{}, bus: bus, diff --git a/ibc/store/provable_store_test.go b/ibc/store/provable_store_test.go index b6f416729..3cb5f808f 100644 --- a/ibc/store/provable_store_test.go +++ b/ibc/store/provable_store_test.go @@ -350,7 +350,7 @@ func newTestProvableStore(t *testing.T) modules.ProvableStore { require.NoError(t, err) }) - return newProvableStore(bus, []byte("test"), privKey) + return NewProvableStore(bus, []byte("test"), privKey) } func setupDB(t *testing.T) (*smt.SMT, kvstore.KVStore, map[string]string) { From 8cfd9af39e1893030ba4024a5d66c6d883163250 Mon Sep 17 00:00:00 2001 From: harry <53987565+h5law@users.noreply.github.com> Date: Fri, 21 Jul 2023 16:48:07 +0100 Subject: [PATCH 21/23] Add basic type tests --- ibc/client/types/client.go | 2 +- ibc/client/types/height.go | 3 + ibc/client/types/height_test.go | 430 ++++++++++++++++++++++++++++++ ibc/client/types/queries.go | 4 +- ibc/client/types/validate_test.go | 172 ++++++++++++ 5 files changed, 608 insertions(+), 3 deletions(-) create mode 100644 ibc/client/types/height_test.go create mode 100644 ibc/client/types/validate_test.go diff --git a/ibc/client/types/client.go b/ibc/client/types/client.go index 27cb74171..e438eedfb 100644 --- a/ibc/client/types/client.go +++ b/ibc/client/types/client.go @@ -31,7 +31,7 @@ func (cs *ClientState) Validate() error { if lenWasmChecksum == 0 { return errors.New("wasm checksum cannot be empty") } - if lenWasmChecksum > 32 { // sha256 output is 256 bits long + if lenWasmChecksum != 32 { // sha256 output is 256 bits long return fmt.Errorf("expected 32, got %d", lenWasmChecksum) } diff --git a/ibc/client/types/height.go b/ibc/client/types/height.go index 21b4ec405..2c57bc359 100644 --- a/ibc/client/types/height.go +++ b/ibc/client/types/height.go @@ -50,6 +50,9 @@ func (h *Height) Increment() modules.Height { } func (h *Height) Decrement() modules.Height { + if h.RevisionHeight == 0 { + return h + } return &Height{ RevisionNumber: h.RevisionNumber, RevisionHeight: h.RevisionHeight - 1, diff --git a/ibc/client/types/height_test.go b/ibc/client/types/height_test.go new file mode 100644 index 000000000..4b8c34eb8 --- /dev/null +++ b/ibc/client/types/height_test.go @@ -0,0 +1,430 @@ +package types + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/require" +) + +func FuzzHeight_ToStringDeterministic(f *testing.F) { + for i := 0; i < 100; i++ { + f.Add(uint64(i)) + } + f.Fuzz(func(t *testing.T, i uint64) { + height := &Height{ + RevisionNumber: i, + RevisionHeight: i, + } + str := height.ToString() + require.Equal(t, str, fmt.Sprintf("%d-%d", i, i)) + }) +} + +func TestHeight_IsZero(t *testing.T) { + testCases := []struct { + name string + height *Height + expected bool + }{ + { + name: "zero height", + height: &Height{ + RevisionNumber: 0, + RevisionHeight: 0, + }, + expected: true, + }, + { + name: "non-zero height: zero revision number", + height: &Height{ + RevisionNumber: 0, + RevisionHeight: 1, + }, + expected: false, + }, + { + name: "non-zero height: zero revision height", + height: &Height{ + RevisionNumber: 1, + RevisionHeight: 0, + }, + expected: false, + }, + { + name: "non-zero height", + height: &Height{ + RevisionNumber: 1, + RevisionHeight: 1, + }, + expected: false, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + require.Equal(t, tc.expected, tc.height.IsZero()) + }) + } +} + +func TestHeight_Increment(t *testing.T) { + height := &Height{ + RevisionNumber: 1, + RevisionHeight: 1, + } + newHeight := height.Increment() + require.Equal(t, uint64(1), height.GetRevisionNumber()) + require.Equal(t, uint64(2), newHeight.GetRevisionHeight()) + + newHeight = newHeight.Increment() + require.Equal(t, uint64(1), height.GetRevisionNumber()) + require.Equal(t, uint64(3), newHeight.GetRevisionHeight()) +} + +func TestHeight_Decrement(t *testing.T) { + height := &Height{ + RevisionNumber: 1, + RevisionHeight: 2, + } + newHeight := height.Decrement() + require.Equal(t, uint64(1), height.GetRevisionNumber()) + require.Equal(t, uint64(1), newHeight.GetRevisionHeight()) + + newHeight = newHeight.Decrement() + require.Equal(t, uint64(1), height.GetRevisionNumber()) + require.Equal(t, uint64(0), newHeight.GetRevisionHeight()) + + newHeight = newHeight.Decrement() + require.Equal(t, uint64(1), height.GetRevisionNumber()) + require.Equal(t, uint64(0), newHeight.GetRevisionHeight()) +} + +func TestHeight_Comparisons(t *testing.T) { + testCases := []struct { + name string + op string + height *Height + other *Height + expected bool + }{ + { + name: "LT: height < other", + op: "LT", + height: &Height{ + RevisionNumber: 1, + RevisionHeight: 1, + }, + other: &Height{ + RevisionNumber: 2, + RevisionHeight: 2, + }, + expected: true, + }, + { + name: "LT: height == other", + op: "LT", + height: &Height{ + RevisionNumber: 1, + RevisionHeight: 1, + }, + other: &Height{ + RevisionNumber: 1, + RevisionHeight: 1, + }, + expected: false, + }, + { + name: "LT: height > other", + op: "LT", + height: &Height{ + RevisionNumber: 2, + RevisionHeight: 2, + }, + other: &Height{ + RevisionNumber: 1, + RevisionHeight: 1, + }, + expected: false, + }, + { + name: "LT: height < other (same revision number)", + op: "LT", + height: &Height{ + RevisionNumber: 1, + RevisionHeight: 1, + }, + other: &Height{ + RevisionNumber: 1, + RevisionHeight: 2, + }, + expected: true, + }, + { + name: "LT: height > other (same revision number)", + op: "LT", + height: &Height{ + RevisionNumber: 1, + RevisionHeight: 2, + }, + other: &Height{ + RevisionNumber: 1, + RevisionHeight: 1, + }, + expected: false, + }, + { + name: "LT: height > other (same revision height)", + op: "LT", + height: &Height{ + RevisionNumber: 2, + RevisionHeight: 1, + }, + other: &Height{ + RevisionNumber: 1, + RevisionHeight: 1, + }, + expected: false, + }, + { + name: "LT: height < other (same revision height)", + op: "LT", + height: &Height{ + RevisionNumber: 1, + RevisionHeight: 1, + }, + other: &Height{ + RevisionNumber: 2, + RevisionHeight: 1, + }, + expected: true, + }, + { + name: "LTE: height < other", + op: "LTE", + height: &Height{ + RevisionNumber: 1, + RevisionHeight: 1, + }, + other: &Height{ + RevisionNumber: 2, + RevisionHeight: 2, + }, + expected: true, + }, + { + name: "LTE: height == other", + op: "LTE", + height: &Height{ + RevisionNumber: 1, + RevisionHeight: 1, + }, + other: &Height{ + RevisionNumber: 1, + RevisionHeight: 1, + }, + expected: true, + }, + { + name: "LTE: height > other", + op: "LTE", + height: &Height{ + RevisionNumber: 2, + RevisionHeight: 2, + }, + other: &Height{ + RevisionNumber: 1, + RevisionHeight: 1, + }, + expected: false, + }, + { + name: "GT: height < other", + op: "GT", + height: &Height{ + RevisionNumber: 1, + RevisionHeight: 1, + }, + other: &Height{ + RevisionNumber: 2, + RevisionHeight: 2, + }, + expected: false, + }, + { + name: "GT: height == other", + op: "GT", + height: &Height{ + RevisionNumber: 1, + RevisionHeight: 1, + }, + other: &Height{ + RevisionNumber: 1, + RevisionHeight: 1, + }, + expected: false, + }, + { + name: "GT: height > other", + op: "GT", + height: &Height{ + RevisionNumber: 2, + RevisionHeight: 2, + }, + other: &Height{ + RevisionNumber: 1, + RevisionHeight: 1, + }, + expected: true, + }, + { + name: "GT: height < other (same revision number)", + op: "GT", + height: &Height{ + RevisionNumber: 1, + RevisionHeight: 1, + }, + other: &Height{ + RevisionNumber: 1, + RevisionHeight: 2, + }, + expected: false, + }, + { + name: "GT: height > other (same revision number)", + op: "GT", + height: &Height{ + RevisionNumber: 1, + RevisionHeight: 2, + }, + other: &Height{ + RevisionNumber: 1, + RevisionHeight: 1, + }, + expected: true, + }, + { + name: "GT: height > other (same revision height)", + op: "GT", + height: &Height{ + RevisionNumber: 2, + RevisionHeight: 1, + }, + other: &Height{ + RevisionNumber: 1, + RevisionHeight: 1, + }, + expected: true, + }, + { + name: "GT: height < other (same revision height)", + op: "GT", + height: &Height{ + RevisionNumber: 1, + RevisionHeight: 1, + }, + other: &Height{ + RevisionNumber: 2, + RevisionHeight: 1, + }, + expected: false, + }, + { + name: "GTE: height < other", + op: "GTE", + height: &Height{ + RevisionNumber: 1, + RevisionHeight: 1, + }, + other: &Height{ + RevisionNumber: 2, + RevisionHeight: 2, + }, + expected: false, + }, + { + name: "GTE: height == other", + op: "GTE", + height: &Height{ + RevisionNumber: 1, + RevisionHeight: 1, + }, + other: &Height{ + RevisionNumber: 1, + RevisionHeight: 1, + }, + expected: true, + }, + { + name: "GTE: height > other", + op: "GTE", + height: &Height{ + RevisionNumber: 2, + RevisionHeight: 2, + }, + other: &Height{ + RevisionNumber: 1, + RevisionHeight: 1, + }, + expected: true, + }, + { + name: "EQ: height < other", + op: "EQ", + height: &Height{ + RevisionNumber: 1, + RevisionHeight: 1, + }, + other: &Height{ + RevisionNumber: 2, + RevisionHeight: 2, + }, + expected: false, + }, + { + name: "EQ: height == other", + op: "EQ", + height: &Height{ + RevisionNumber: 1, + RevisionHeight: 1, + }, + other: &Height{ + RevisionNumber: 1, + RevisionHeight: 1, + }, + expected: true, + }, + { + name: "EQ: height > other", + op: "EQ", + height: &Height{ + RevisionNumber: 2, + RevisionHeight: 2, + }, + other: &Height{ + RevisionNumber: 1, + RevisionHeight: 1, + }, + expected: false, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + switch tc.op { + case "LT": + require.Equal(t, tc.expected, tc.height.LT(tc.other)) + case "LTE": + require.Equal(t, tc.expected, tc.height.LTE(tc.other)) + case "GT": + require.Equal(t, tc.expected, tc.height.GT(tc.other)) + case "GTE": + require.Equal(t, tc.expected, tc.height.GTE(tc.other)) + case "EQ": + require.Equal(t, tc.expected, tc.height.EQ(tc.other)) + default: + panic(fmt.Sprintf("invalid comparison op: %s", tc.op)) + } + }) + } +} diff --git a/ibc/client/types/queries.go b/ibc/client/types/queries.go index 40353dc9b..fd7a8d8be 100644 --- a/ibc/client/types/queries.go +++ b/ibc/client/types/queries.go @@ -28,7 +28,7 @@ func GetConsensusState(clientStore modules.ProvableStore, height modules.Height) // in the format: "clients" using the clientID provided func GetClientState(clientStore modules.ProvableStore, identifier string) (modules.ClientState, error) { // Retrieve the client state bytes from the client store - clientStateBz, err := clientStore.Get(path.FullClientStateKey(identifier)) + clientStateBz, err := clientStore.Get(path.ClientStateKey(identifier)) if err != nil { return nil, err } @@ -49,7 +49,7 @@ func setClientState(clientStore modules.ProvableStore, clientState *ClientState) if err != nil { return err } - return clientStore.Set(nil, val) // key == nil ==> key == "clients/{clientID}" + return clientStore.Set([]byte(path.KeyClientState), val) // key == nil ==> key == "clients/{clientID}" } // setConsensusState stores the consensus state at the given height. diff --git a/ibc/client/types/validate_test.go b/ibc/client/types/validate_test.go new file mode 100644 index 000000000..80a1fa4bf --- /dev/null +++ b/ibc/client/types/validate_test.go @@ -0,0 +1,172 @@ +package types + +import ( + "errors" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestClientState_Validate(t *testing.T) { + testCases := []struct { + name string + clientState *ClientState + expectedErr error + }{ + { + name: "valid client state", + clientState: &ClientState{ + Data: []byte("data"), + WasmChecksum: make([]byte, 32), + }, + expectedErr: nil, + }, + { + name: "invalid client state: empty data", + clientState: &ClientState{ + Data: nil, + WasmChecksum: make([]byte, 32), + }, + expectedErr: errors.New("data cannot be empty"), + }, + { + name: "invalid client state: empty wasm checksum", + clientState: &ClientState{ + Data: []byte("data"), + WasmChecksum: nil, + }, + expectedErr: errors.New("wasm checksum cannot be empty"), + }, + { + name: "invalid client state: invalid wasm checksum", + clientState: &ClientState{ + Data: []byte("data"), + WasmChecksum: []byte("invalid"), + }, + expectedErr: errors.New("expected 32, got 7"), + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + err := tc.clientState.Validate() + if tc.expectedErr != nil { + require.ErrorAs(t, err, &tc.expectedErr) + return + } + require.NoError(t, err) + }) + } +} + +func TestConsensusState_ValidateBasic(t *testing.T) { + testCases := []struct { + name string + consensusState *ConsensusState + expectedErr error + }{ + { + name: "valid consensus state", + consensusState: &ConsensusState{ + Timestamp: 1, + Data: []byte("data"), + }, + expectedErr: nil, + }, + { + name: "invalid consensus state: zero timestamp", + consensusState: &ConsensusState{ + Timestamp: 0, + Data: []byte("data"), + }, + expectedErr: errors.New("timestamp must be a positive Unix time"), + }, + { + name: "invalid consensus state: empty data", + consensusState: &ConsensusState{ + Timestamp: 1, + Data: nil, + }, + expectedErr: errors.New("data cannot be empty"), + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + err := tc.consensusState.ValidateBasic() + if tc.expectedErr != nil { + require.ErrorAs(t, err, &tc.expectedErr) + return + } + require.NoError(t, err) + }) + } +} + +func TestHeader_ValidateBasic(t *testing.T) { + testCases := []struct { + name string + header *Header + expectedErr error + }{ + { + name: "valid header", + header: &Header{ + Data: []byte("data"), + }, + expectedErr: nil, + }, + { + name: "invalid header: empty data", + header: &Header{ + Data: nil, + }, + expectedErr: errors.New("data cannot be empty"), + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + err := tc.header.ValidateBasic() + if tc.expectedErr != nil { + require.ErrorAs(t, err, &tc.expectedErr) + return + } + require.NoError(t, err) + }) + } +} + +func TestMisbehaviour_ValidateBasic(t *testing.T) { + testCases := []struct { + name string + misbehaviour *Misbehaviour + expectedErr error + }{ + { + name: "valid misbehaviour", + misbehaviour: &Misbehaviour{ + Data: []byte("data"), + }, + expectedErr: nil, + }, + { + name: "invalid misbehaviour: empty data", + misbehaviour: &Misbehaviour{ + Data: nil, + }, + expectedErr: errors.New("data cannot be empty"), + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + err := tc.misbehaviour.ValidateBasic() + if tc.expectedErr != nil { + require.ErrorAs(t, err, &tc.expectedErr) + return + } + require.NoError(t, err) + }) + } +} From f61c97ac295db5e9846f75ab55ed24fcaa872646 Mon Sep 17 00:00:00 2001 From: harry <53987565+h5law@users.noreply.github.com> Date: Fri, 21 Jul 2023 18:26:44 +0100 Subject: [PATCH 22/23] Add host client introspection tests --- ibc/client/introspect.go | 3 +- ibc/host_introspection_test.go | 271 +++++++++++++++++++++++++++++++++ ibc/main_test.go | 1 - 3 files changed, 273 insertions(+), 2 deletions(-) create mode 100644 ibc/host_introspection_test.go diff --git a/ibc/client/introspect.go b/ibc/client/introspect.go index 9d57bc3f8..80b0bf2b8 100644 --- a/ibc/client/introspect.go +++ b/ibc/client/introspect.go @@ -10,6 +10,7 @@ import ( "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" ) @@ -119,7 +120,7 @@ func (c *clientManager) VerifyHostClientState(counterparty modules.ClientState) 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()) { + if !proto.Equal(poktCounter.ProofSpec, poktHost.ProofSpec) { return errors.New("counterparty client state has different proof spec") } if poktCounter.UnbondingPeriod != poktHost.UnbondingPeriod { diff --git a/ibc/host_introspection_test.go b/ibc/host_introspection_test.go new file mode 100644 index 000000000..35f0af6b5 --- /dev/null +++ b/ibc/host_introspection_test.go @@ -0,0 +1,271 @@ +package ibc + +import ( + "errors" + "testing" + "time" + + ics23 "github.com/cosmos/ics23/go" + light_client_types "github.com/pokt-network/pocket/ibc/client/light_clients/types" + client_types "github.com/pokt-network/pocket/ibc/client/types" + "github.com/pokt-network/pocket/ibc/types" + "github.com/pokt-network/pocket/shared/codec" + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/types/known/durationpb" +) + +func TestHost_GetCurrentHeight(t *testing.T) { + _, _, _, _, ibcMod := prepareEnvironment(t, 1, 0, 0, 0) + cm := ibcMod.GetBus().GetClientManager() + + // get the current height + height, err := cm.GetCurrentHeight() + require.NoError(t, err) + require.Equal(t, uint64(1), height.GetRevisionNumber()) + require.Equal(t, uint64(0), height.GetRevisionHeight()) + + // increment the height + publishNewHeightEvent(t, ibcMod.GetBus(), 1) + + height, err = cm.GetCurrentHeight() + require.NoError(t, err) + require.Equal(t, uint64(1), height.GetRevisionNumber()) + require.Equal(t, uint64(1), height.GetRevisionHeight()) +} + +func TestHost_GetHostConsensusState(t *testing.T) { + _, _, _, _, ibcMod := prepareEnvironment(t, 1, 0, 0, 0) + cm := ibcMod.GetBus().GetClientManager() + + consState, err := cm.GetHostConsensusState(&client_types.Height{RevisionNumber: 1, RevisionHeight: 0}) + require.NoError(t, err) + + require.Equal(t, "08-wasm", consState.ClientType()) + require.NoError(t, consState.ValidateBasic()) + require.Less(t, consState.GetTimestamp(), uint64(time.Now().UnixNano())) + + pocketConState := new(light_client_types.PocketConsensusState) + err = codec.GetCodec().Unmarshal(consState.GetData(), pocketConState) + require.NoError(t, err) + + blockstore := ibcMod.GetBus().GetPersistenceModule().GetBlockStore() + block, err := blockstore.GetBlock(0) + require.NoError(t, err) + + require.Equal(t, block.BlockHeader.Timestamp, pocketConState.Timestamp) + require.Equal(t, block.BlockHeader.StateHash, pocketConState.StateHash) + require.Equal(t, block.BlockHeader.StateTreeHashes, pocketConState.StateTreeHashes) + require.Equal(t, block.BlockHeader.NextValSetHash, pocketConState.NextValSetHash) +} + +func TestHost_GetHostClientState(t *testing.T) { + _, _, _, _, ibcMod := prepareEnvironment(t, 1, 0, 0, 0) + cm := ibcMod.GetBus().GetClientManager() + + clientState, err := cm.GetHostClientState(&client_types.Height{RevisionNumber: 1, RevisionHeight: 0}) + require.NoError(t, err) + require.Equal(t, "08-wasm", clientState.ClientType()) + + pocketClientState := new(light_client_types.PocketClientState) + err = codec.GetCodec().Unmarshal(clientState.GetData(), pocketClientState) + require.NoError(t, err) + + blockstore := ibcMod.GetBus().GetPersistenceModule().GetBlockStore() + block, err := blockstore.GetBlock(0) + require.NoError(t, err) + + require.Equal(t, pocketClientState.NetworkId, block.BlockHeader.NetworkId) + require.Equal(t, pocketClientState.TrustLevel, &light_client_types.Fraction{Numerator: 2, Denominator: 3}) + require.Equal(t, pocketClientState.TrustingPeriod.AsDuration().Nanoseconds(), int64(1814400000000000)) + require.Equal(t, pocketClientState.UnbondingPeriod.AsDuration().Nanoseconds(), int64(1814400000000000)) + require.Equal(t, pocketClientState.MaxClockDrift.AsDuration().Nanoseconds(), int64(900000000000)) + require.Equal(t, pocketClientState.LatestHeight, &client_types.Height{RevisionNumber: 1, RevisionHeight: 0}) + require.True(t, pocketClientState.ProofSpec.ConvertToIcs23ProofSpec().SpecEquals(ics23.SmtSpec)) +} + +func TestHost_VerifyHostClientState(t *testing.T) { + _, _, _, persistenceMod, ibcMod := prepareEnvironment(t, 1, 0, 0, 0) + cm := ibcMod.GetBus().GetClientManager() + + approxTime := time.Minute * 15 + unbondingPeriod := time.Duration(1814400000000000) * approxTime + blockstore := ibcMod.GetBus().GetPersistenceModule().GetBlockStore() + block, err := blockstore.GetBlock(0) + require.NoError(t, err) + + publishNewHeightEvent(t, ibcMod.GetBus(), 1) + + rwCtx, err := persistenceMod.NewRWContext(1) + require.NoError(t, err) + defer rwCtx.Release() + err = rwCtx.Commit(nil, nil) + require.NoError(t, err) + + testCases := []struct { + name string + pcs *light_client_types.PocketClientState + expectedErr error + }{ + { + name: "invalid: frozen client", + pcs: &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(approxTime), + LatestHeight: &client_types.Height{ + RevisionNumber: 1, + RevisionHeight: 0, + }, + ProofSpec: types.SmtSpec, + FrozenHeight: 1, + }, + expectedErr: errors.New("counterparty client state is frozen"), + }, + { + name: "invalid: different network id", + pcs: &light_client_types.PocketClientState{ + NetworkId: "not correct", + TrustLevel: &light_client_types.Fraction{Numerator: 2, Denominator: 3}, + TrustingPeriod: durationpb.New(unbondingPeriod), + UnbondingPeriod: durationpb.New(unbondingPeriod), + MaxClockDrift: durationpb.New(approxTime), + LatestHeight: &client_types.Height{ + RevisionNumber: 1, + RevisionHeight: 0, + }, + ProofSpec: types.SmtSpec, + }, + expectedErr: errors.New("counterparty client state has a different network id"), + }, + { + name: "invalid: different revision number", + pcs: &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(approxTime), + LatestHeight: &client_types.Height{ + RevisionNumber: 0, + RevisionHeight: 0, + }, + ProofSpec: types.SmtSpec, + }, + expectedErr: errors.New("counterparty client state has a different revision number"), + }, + { + name: "invalid: equal height", + pcs: &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(approxTime), + LatestHeight: &client_types.Height{ + RevisionNumber: 1, + RevisionHeight: 1, + }, + ProofSpec: types.SmtSpec, + }, + expectedErr: errors.New("counterparty client state has a height greater than or equal to the host client state"), + }, + { + name: "invalid: wrong trust level", + pcs: &light_client_types.PocketClientState{ + NetworkId: block.BlockHeader.NetworkId, + TrustLevel: &light_client_types.Fraction{Numerator: 1, Denominator: 4}, + TrustingPeriod: durationpb.New(unbondingPeriod), + UnbondingPeriod: durationpb.New(unbondingPeriod), + MaxClockDrift: durationpb.New(approxTime), + LatestHeight: &client_types.Height{ + RevisionNumber: 1, + RevisionHeight: 0, + }, + ProofSpec: types.SmtSpec, + }, + expectedErr: errors.New("counterparty client state trust level is not in the accepted range"), + }, + { + name: "invalid: different proof spec", + pcs: &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(approxTime), + LatestHeight: &client_types.Height{ + RevisionNumber: 1, + RevisionHeight: 0, + }, + ProofSpec: types.ConvertFromIcs23ProofSpec(ics23.IavlSpec), + }, + expectedErr: errors.New("counterparty client state has different proof spec"), + }, + { + name: "invalid: different unbonding period", + pcs: &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 + 1), + MaxClockDrift: durationpb.New(approxTime), + LatestHeight: &client_types.Height{ + RevisionNumber: 1, + RevisionHeight: 0, + }, + ProofSpec: types.SmtSpec, + }, + expectedErr: errors.New("counterparty client state has different unbonding period"), + }, + { + name: "invalid: unbonding period less than trusting period", + pcs: &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 - 1), + MaxClockDrift: durationpb.New(approxTime), + LatestHeight: &client_types.Height{ + RevisionNumber: 1, + RevisionHeight: 0, + }, + ProofSpec: types.SmtSpec, + }, + expectedErr: errors.New("counterparty client state unbonding period is less than trusting period"), + }, + { + name: "valid client state", + pcs: &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(approxTime), + LatestHeight: &client_types.Height{ + RevisionNumber: 1, + RevisionHeight: 0, + }, + ProofSpec: types.SmtSpec, + }, + expectedErr: nil, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + bz, err := codec.GetCodec().Marshal(tc.pcs) + require.NoError(t, err) + clientState := &client_types.ClientState{ + Data: bz, + RecentHeight: &client_types.Height{ + RevisionNumber: 1, + RevisionHeight: 0, + }, + } + err = cm.VerifyHostClientState(clientState) + require.ErrorAs(t, err, &tc.expectedErr) + }) + } +} diff --git a/ibc/main_test.go b/ibc/main_test.go index 51fa0c63f..5dcc083b9 100644 --- a/ibc/main_test.go +++ b/ibc/main_test.go @@ -57,7 +57,6 @@ func newTestP2PModule(t *testing.T, bus modules.Bus) modules.P2PModule { AnyTimes() p2pMock.EXPECT().GetModuleName().Return(modules.P2PModuleName).AnyTimes() p2pMock.EXPECT().HandleEvent(gomock.Any()).Return(nil).AnyTimes() - bus.RegisterModule(p2pMock) return p2pMock } From 7e61c5d3cce47d34c635c19a10e469adf8c18d27 Mon Sep 17 00:00:00 2001 From: harry <53987565+h5law@users.noreply.github.com> Date: Sun, 23 Jul 2023 14:53:54 +0100 Subject: [PATCH 23/23] fix linter error --- ibc/main_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ibc/main_test.go b/ibc/main_test.go index 5dcc083b9..bf93e685f 100644 --- a/ibc/main_test.go +++ b/ibc/main_test.go @@ -39,7 +39,7 @@ func newTestConsensusModule(t *testing.T, bus modules.Bus) modules.ConsensusModu return consensusMod.(modules.ConsensusModule) } -func newTestP2PModule(t *testing.T, bus modules.Bus) modules.P2PModule { +func newTestP2PModule(t *testing.T) modules.P2PModule { t.Helper() ctrl := gomock.NewController(t) @@ -116,7 +116,7 @@ func prepareEnvironment( require.NoError(t, err) bus.RegisterModule(testConsensusMod) - testP2PMock := newTestP2PModule(t, bus) + testP2PMock := newTestP2PModule(t) err = testP2PMock.Start() require.NoError(t, err) bus.RegisterModule(testP2PMock)