diff --git a/Makefile b/Makefile index a88a4b943..96407cbf6 100644 --- a/Makefile +++ b/Makefile @@ -423,6 +423,10 @@ test_p2p_raintree: ## Run all p2p raintree related tests test_p2p_raintree_addrbook: ## Run all p2p raintree addr book related tests go test ${VERBOSE_TEST} -count=1 -tags=test -run RainTreeAddrBook -count=1 ./p2p/... +.PHONY: test_ibc +test_ibc: ## Run all go unit tests in the IBC module + go test ${VERBOSE_TEST} -count=1 -tags=test -p=1 ./ibc/... + # TIP: For benchmarks, consider appending `-run=^#` to avoid running unit tests in the same package .PHONY: benchmark_persistence_state_hash diff --git a/build/config/config.validator1.json b/build/config/config.validator1.json index 67051df18..702ad4232 100644 --- a/build/config/config.validator1.json +++ b/build/config/config.validator1.json @@ -57,5 +57,8 @@ "servicer": { "enabled": true, "chains": ["0001"] + }, + "ibc": { + "enabled": true } } diff --git a/build/config/config.validator2.json b/build/config/config.validator2.json index 25b952ecc..3e2ef6f6e 100644 --- a/build/config/config.validator2.json +++ b/build/config/config.validator2.json @@ -50,5 +50,8 @@ "port": "50832", "timeout": 30000, "use_cors": false + }, + "ibc": { + "enabled": true } } diff --git a/build/config/config.validator3.json b/build/config/config.validator3.json index 648ef2a5a..5cd45e544 100644 --- a/build/config/config.validator3.json +++ b/build/config/config.validator3.json @@ -50,5 +50,8 @@ "port": "50832", "timeout": 30000, "use_cors": false + }, + "ibc": { + "enabled": true } } diff --git a/build/config/config.validator4.json b/build/config/config.validator4.json index 65887d398..2eb7747d7 100644 --- a/build/config/config.validator4.json +++ b/build/config/config.validator4.json @@ -50,5 +50,8 @@ "port": "50832", "timeout": 30000, "use_cors": false + }, + "ibc": { + "enabled": true } } diff --git a/consensus/e2e_tests/utils_test.go b/consensus/e2e_tests/utils_test.go index bd2a400f7..94294af26 100644 --- a/consensus/e2e_tests/utils_test.go +++ b/consensus/e2e_tests/utils_test.go @@ -15,6 +15,7 @@ import ( "github.com/pokt-network/pocket/consensus" typesCons "github.com/pokt-network/pocket/consensus/types" "github.com/pokt-network/pocket/internal/testutil" + ibcUtils "github.com/pokt-network/pocket/internal/testutil/ibc" persistenceMocks "github.com/pokt-network/pocket/persistence/types/mocks" "github.com/pokt-network/pocket/runtime" "github.com/pokt-network/pocket/runtime/configs" @@ -24,7 +25,6 @@ import ( "github.com/pokt-network/pocket/shared" "github.com/pokt-network/pocket/shared/codec" coreTypes "github.com/pokt-network/pocket/shared/core/types" - "github.com/pokt-network/pocket/shared/crypto" cryptoPocket "github.com/pokt-network/pocket/shared/crypto" "github.com/pokt-network/pocket/shared/messaging" "github.com/pokt-network/pocket/shared/modules" @@ -122,6 +122,7 @@ func CreateTestConsensusPocketNode( telemetryMock := baseTelemetryMock(t, eventsChannel) loggerMock := baseLoggerMock(t, eventsChannel) rpcMock := baseRpcMock(t, eventsChannel) + ibcMock := ibcUtils.IbcMockWithHost(t, eventsChannel) for _, module := range []modules.Module{ p2pMock, @@ -129,6 +130,7 @@ func CreateTestConsensusPocketNode( telemetryMock, loggerMock, rpcMock, + ibcMock, } { bus.RegisterModule(module) } @@ -669,7 +671,6 @@ func waitForProposalMsgs( maxWaitTime time.Duration, failOnExtraMessages bool, ) ([]*anypb.Any, error) { - proposalMsgs, err := WaitForNetworkConsensusEvents(t, clck, eventsChannel, typesCons.HotstuffStep(step), consensus.Propose, numExpectedMsgs, maxWaitTime, failOnExtraMessages) if err != nil { return nil, err @@ -751,7 +752,6 @@ func waitForNodeToRequestMissingBlock( startingHeight uint64, targetHeight uint64, ) (*anypb.Any, error) { - return &anypb.Any{}, nil } @@ -765,7 +765,6 @@ func waitForNodeToReceiveMissingBlock( allNodes IdToNodeMapping, blockReq *anypb.Any, ) (*anypb.Any, error) { - return &anypb.Any{}, nil } @@ -779,11 +778,10 @@ func waitForNodeToCatchUp( blockResponse *anypb.Any, targetHeight uint64, ) error { - return nil } -func generatePlaceholderBlock(height uint64, leaderAddrr crypto.Address) *coreTypes.Block { +func generatePlaceholderBlock(height uint64, leaderAddrr cryptoPocket.Address) *coreTypes.Block { blockHeader := &coreTypes.BlockHeader{ Height: height, StateHash: stateHash, diff --git a/ibc/docs/README.md b/ibc/docs/README.md new file mode 100644 index 000000000..26de85af7 --- /dev/null +++ b/ibc/docs/README.md @@ -0,0 +1,111 @@ +# IBC + +- [Definitions](#definitions) + - ["host"](#host) + - ["light client"](#light-client) +- [Overview](#overview) +- [IBC Module](#ibc-module) + - [Node Configuration](#node-configuration) + - [Persistence](#persistence) +- [Components](#components) + - [ICS-24 Host Requirements](#ics-24-host-requirements) + +## Definitions + +### "host" + +An IBC host refers to the node (host machine) that is running the IBC module. Relayers will interact with the hosts on each chain in order to call any IBC related functions. The IBC host is responsible for storing and interfacing with the IBC state and handling any IBC related transactions. + +### "light client" + +An IBC light client refers to a consensus state verification algorithm. This is different from the traditional meaning of the term. An IBC light client will be only used for state verification and will lack many of the other features commonly found in traditional light clients. + +## Overview + +![IBC High Level](./high-level-ibc.png) + +Inter-Blockchain Communication (IBC) is a protocol that enables trustless communication between two chains. It allows these chains to interact by relaying IBC packets. The process involves two IBC-enabled chains, referred to as **chain A** and **chain B**, each running a light client for the other chain on their network. + +To transfer native tokens from **chain A** to **chain B**, certain prerequisites must be met. First, a connection between the two chains must be established. Then, a channel and port need to be opened on this connection. Additionally, a light client for the opposing chain should be set up on both ends of the connection. Finally, a relayer is required to facilitate the actual transfer of the packet. + +Once these components are in place, **chain A** can commit an IBC packet to its state, which contains transaction information. It also generates a proof that specifies the inclusion of the packet in the state at a particular height. The relayer submits this proof to **chain B**, where it is verified. If the proof is valid, **chain B** can respond accordingly, such as by sending an IBC token from **chain A** to the designated address on **chain B**. + +## IBC Module + +Pocket's IBC module is split into numerous components detailed below. The overall module layout is as follows: + +**Note:** Not all of the different ICS components have been fully implemented yet, this is a work in progress. + +```mermaid +flowchart TB + subgraph IBC[IBC Module] + subgraph 23[ICS-23] + VC[Vector Commitments] + end + subgraph 24[ICS-24] + HR[Host Requirements] + end + subgraph I[IBC Interface] + IF[ICS-25 Handler Interface] + IR[ICS-26 Routing Module] + end + end + subgraph 2[ICS-02] + CL[Client Semantics] + end + subgraph 3[ICS-03] + CO[Connection Semantics] + end + subgraph 5[ICS-05] + PA[Port Allocation] + end + subgraph 4[ICS-04] + CP[Channel & Packet Semantics] + end + subgraph 20[ICS-20] + FT[Fungible Token Transfer] + end + IBC ---> 2 + IBC ---> 3 + IBC ---> 5 + IBC ---> 4 + IBC ---> 20 +``` + +### Node Configuration + +Part of the node configurations relating to the IBC module is as follows: + +```json +"ibc": { + "enabled": bool, +} +``` + +If a node enables the IBC module, and is a validator, then the IBC module will automatically create an IBC host on startup. As the host defines the connections, channels and ports - which must stay persistent, the node should be a validator with little risk of suddenly closing any of these while open. Any tokens transferred over a connection/channel/port are unique to that combination they can only be returned over the same combination. + +**If the channel is to close without warning then tokens will be unable to be returned to their source. It is for this reason that only validators are able to become IBC hosts.** + +INVESTIGATE(M7): Look into on-chain POKT slashing/incentive conditions based on the QoS of an IBC host. + +_Note_: Connections, Channels and Ports in IBC are not the same as networking connections, channels and ports. They are stored in the chain state and are used by relayers to signify where each IBC packet should go when being relayed. When closing a channel the IBC host must submit to the state a `ChanCloseInit` IBC packet. If this happens without warning, the funds transferred on this channel will become unrecoverable. + +### Persistence + +[ICS24][ics24] defines the IBC stores and these must be a part of the Pocket networks consensus state. As such the `ibcTree` is defined as one of the state trees used to generate the root hash. This tree contains the relevant information the hosts/relayers need to be able to use IBC, in accordance with ICS-24 and the other ICS components. + +TODO([#854](https://github.com/pokt-network/pocket/issues/854)): Add a local cache for changes to the state for use in the event of the node crashing. + +## Components + +The [IBC specification][ibc-spec] details numerous Interchain Standards (ICSs) that together form the IBC protocol. The following gives an overview of the different components implemented in Pocket's IBC module. + +### ICS-24 Host Requirements + +[ICS-24][ics24] defines the requirements for a host chain to be IBC compatible. This includes the definition of a store system to hold IBC related data in a provable (and also a private) fashion. This implementation uses the [SMT][smt] rather than the IAVL tree used by `cosmos-sdk` for its provable stores. ICS-24 also defines the Event Logging system that is used to store and query IBC related events for the relayers to read packet data and timeouts, as only the proofs of these are stored in the chain state. + +See: [ICS-24](./ics24.md) for more details on the specifics of the ICS-24 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 +[smt]: https://github.com/pokt-network/smt diff --git a/ibc/docs/high-level-ibc.png b/ibc/docs/high-level-ibc.png new file mode 100644 index 000000000..a4b71d23e Binary files /dev/null and b/ibc/docs/high-level-ibc.png differ diff --git a/ibc/docs/ics24.md b/ibc/docs/ics24.md new file mode 100644 index 000000000..8834c4b88 --- /dev/null +++ b/ibc/docs/ics24.md @@ -0,0 +1,32 @@ +# ICS-24 Host Requirements + +- [Overview](#overview) +- [Implementation](#implementation) + - [Paths and Identifiers](#paths-and-identifiers) + - [Timestamps](#timestamps) + +## Overview + +[ICS-24][ics24] details the requirements of the host chain, in order for it to be compatible with IBC. A host is defined as a node on a chain that runs the IBC software. A host has the ability to create connections with counterparty chains, open channels, and ports as well as commit proofs to the consensus state of its own chain for the relayer to submit to another chain. The host is responsible to managing and creating clients and all other aspects of the IBC module. + +As token transfers as defined in [ICS-20][ics20] work on a lock and mint pattern, any tokens sent from **chain A** to **chain B** will have a denomination unique to the connection/channel/port combination that the packet was sent over. This means that if a host where to shutdown a connection or channel without warning any tokens yet to be returned to the host chain would be lost. For this reason, only validator nodes are able to become hosts, as they provide the most reliability out of the different node types. + +## Implementation + +**Note**: The ICS-24 implementation is still a work in progress and is not yet fully implemented. + +ICS-24 has numerous sub components that must be implemented in order for the host to be fully functional. These range from type definitions for identifiers, paths and stores as well as the methods to interact with them. Alongside these ICS-24 also defines the Event Logging system which is used to store the packet data and timeouts for the relayers to read, as only the `CommitmentProof` objects are committed to the chain state. In addition to these numerous other features are part of ICS-24 that are closely linked to other ICS components such as consensus state introspection and client state validation. + +### Paths and Identifiers + +Paths are defined as bytestrings that are used to access the elements in the different stores. They are built with the function `ApplyPrefix()` which takes a store key as a prefix and a path string and will return the key to access an element in the specific store. The logic for paths can be found in [host/keys.go](../host/keys.go) and [host/prefix.go](../host/prefix.go) + +Identifiers are bytestrings constrained to specific characters and lengths depending on their usages. They are used to identify: channels, clients, connections and ports. Although the minimum length of the identifiers is much less we use a minimum length of 32 bytes and a maximum length that varies depending on the use case to randomly generate identifiers. This allows for an extremely low chance of collision between identifiers. Identifiers have no significance beyond their use to store different elements in the IBC stores and as such there is no need for non-random identifiers. The logic for identifiers can be found in [host/identifiers.go](../host/identifiers.go). + +### Timestamps + +The `GetTimestamp()` function returns the current unix timestamp of the host machine and is used to calculate timeout periods for packets + +[ics24]: https://github.com/cosmos/ibc/blob/main/spec/core/ics-024-host-requirements/README.md +[ics20]: https://github.com/cosmos/ibc/blob/main/spec/app/ics-020-fungible-token-transfer/README.md +[smt]: https://github.com/pokt-network/smt diff --git a/ibc/host.go b/ibc/host.go new file mode 100644 index 000000000..603580cac --- /dev/null +++ b/ibc/host.go @@ -0,0 +1,18 @@ +package ibc + +import ( + "time" + + "github.com/pokt-network/pocket/shared/modules" +) + +var _ modules.IBCHost = &host{} + +type host struct { + logger *modules.Logger +} + +// GetTimestamp returns the current unix timestamp +func (h *host) GetTimestamp() uint64 { + return uint64(time.Now().Unix()) +} diff --git a/ibc/host/identifiers.go b/ibc/host/identifiers.go new file mode 100644 index 000000000..f6b4a046d --- /dev/null +++ b/ibc/host/identifiers.go @@ -0,0 +1,133 @@ +package host + +import ( + "fmt" + "math/rand" + "strings" + "time" + + coreTypes "github.com/pokt-network/pocket/shared/core/types" +) + +const ( + identifierPrefix = "#" + identifierCharset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789._+-#[]<>" + invalidIdChars = "/" + + // lengths for identifiers are measured in bytes + defaultMinIdLength = 32 // use 32 bytes as a default minimum length to avoid collisions + defaultMaxIdLength = 64 + minClientIdLength = 9 + minConnectionIdLength = 10 + minChannelIdLength = 8 + minPortIdLength = 2 + maxPortIdLength = 128 +) + +var ( + invalidIdMap map[rune]struct{} + validIdMap map[rune]struct{} +) + +func init() { + invalidIdMap = make(map[rune]struct{}, 0) + for _, c := range invalidIdChars { + invalidIdMap[c] = struct{}{} + } + validIdMap = make(map[rune]struct{}, 0) + for _, c := range identifierCharset { + validIdMap[c] = struct{}{} + } +} + +// ValidateClientID validates the client identifier string +func ValidateClientID(id string) error { + return basicValidation(id, minClientIdLength, defaultMaxIdLength) +} + +// ValidateConnectionID validates the connection identifier string +func ValidateConnectionID(id string) error { + return basicValidation(id, minConnectionIdLength, defaultMaxIdLength) +} + +// ValidateChannelID validates the channel identifier string +func ValidateChannelID(id string) error { + return basicValidation(id, minChannelIdLength, defaultMaxIdLength) +} + +// ValidatePortID validates the port identifier string +func ValidatePortID(id string) error { + return basicValidation(id, minPortIdLength, maxPortIdLength) +} + +// GenerateClientIdentifier generates a new client identifier +func GenerateClientIdentifier() string { + return generateNewIdentifier(minClientIdLength, defaultMaxIdLength) +} + +// GenerateConnectionIdentifier generates a new connection identifier +func GenerateConnectionIdentifier() string { + return generateNewIdentifier(minConnectionIdLength, defaultMaxIdLength) +} + +// GenerateChannelIdentifier generates a new channel identifier +func GenerateChannelIdentifier() string { + return generateNewIdentifier(minChannelIdLength, defaultMaxIdLength) +} + +// GeneratePortIdentifier generates a new port identifier +func GeneratePortIdentifier() string { + return generateNewIdentifier(minPortIdLength, maxPortIdLength) +} + +// generateNewIdentifier generates a new identifier in the given range +func generateNewIdentifier(min, max int) string { + return generateNewIdentifierWithSeed(min, max, time.Now().UnixNano()) +} + +// generateNewIdentifierWithSeed generates a new identifier in the given range with the identifier prefix +// If the random seed provided is 0 it will use the current unix timestamp as the seed +func generateNewIdentifierWithSeed(min, max int, seed int64) string { + //nolint:gosec // weak random source okay - cryptographically secure randomness not required + r := rand.New(rand.NewSource(seed)) + localMin := defaultMinIdLength - min + size := r.Intn(max-len(identifierPrefix)-localMin) + localMin + + b := make([]byte, size) + + for i := range b { + b[i] = identifierCharset[r.Intn(len(identifierCharset))] + } + + return identifierPrefix + string(b) +} + +// basicValidation performs basic validation on the given identifier +func basicValidation(id string, minLength, maxLength int) error { + if strings.TrimSpace(id) == "" { + return coreTypes.ErrIBCInvalidID(id, "cannot be blank") + } + + length := len(id) + if length < minLength || length > maxLength { + return coreTypes.ErrIBCInvalidID(id, fmt.Sprintf("length (%d) must be between %d and %d", length, minLength, maxLength)) + } + + if !strings.HasPrefix(id, identifierPrefix) { + return coreTypes.ErrIBCInvalidID(id, fmt.Sprintf("must start with '%s'", identifierPrefix)) + } + + for _, c := range id { + if _, ok := invalidIdMap[c]; ok { + return coreTypes.ErrIBCInvalidID(id, fmt.Sprintf("cannot contain '%s'", string(c))) + } + } + + for _, c := range id { + if _, ok := validIdMap[c]; !ok { + return coreTypes.ErrIBCInvalidID(id, fmt.Sprintf("contains invalid character '%c'", c)) + } + } + + return nil +} diff --git a/ibc/host/keys.go b/ibc/host/keys.go new file mode 100644 index 000000000..c5a62ff34 --- /dev/null +++ b/ibc/host/keys.go @@ -0,0 +1,47 @@ +package host + +import ( + "fmt" + "strings" +) + +// Store key prefixes for IBC as defined in ICS-24 +// https://github.com/cosmos/ibc/blob/main/spec/core/ics-024-host-requirements/README.md +const ( + KeyClientStorePrefix = "clients" + KeyClientState = "clientState" + KeyConsensusStatePrefix = "consensusStates" + KeyConnectionPrefix = "connections" + KeyChannelEndPrefix = "channelEnds" + KeyChannelPrefix = "channels" + KeyPortPrefix = "ports" + KeySequencePrefix = "sequences" + KeyChannelCapabilityPrefix = "capabilities" + KeyNextSeqSendPrefix = "nextSequenceSend" + KeyNextSeqRecvPrefix = "nextSequenceRecv" + KeyNextSeqAckPrefix = "nextSequenceAck" + KeyPacketCommitmentPrefix = "commitments" + KeyPacketAckPrefix = "acks" + KeyPacketReceiptPrefix = "receipts" +) + +// DISCUSSION: Do we need both paths and keys with the ApplyPrefix and RemovePrefix functions? +// These seem to be redundant and could be removed, but are included in the cosmos/ibc-go repo + +// fullClientPath returns the full path of a specific client path in the format: +// "clients/{clientID}/{key}" as a string. +func fullClientPath(clientID, key string) string { + return fmt.Sprintf("%s/%s/%s", KeyClientStorePrefix, clientID, key) +} + +// clientPath returns the path of a specific client within the client store in the format: +// "{clientID}/{key}" as a string +func clientPath(clientID, key string) string { + return strings.TrimPrefix(fullClientPath(clientID, key), KeyClientStorePrefix+"/") +} + +// FullClientKey returns the full path of specific client path in the format: +// "clients/{clientID}/{key}" as a byte array. +func fullClientKey(clientID, key string) []byte { + return []byte(fullClientPath(clientID, key)) +} diff --git a/ibc/host/keys_ics02.go b/ibc/host/keys_ics02.go new file mode 100644 index 000000000..a114f2bf4 --- /dev/null +++ b/ibc/host/keys_ics02.go @@ -0,0 +1,44 @@ +package host + +import "fmt" + +//////////////////////////////////////////////////////////////////////////////// +// ICS02 +// The following paths are the keys to the store as defined in +// https://github.com/cosmos/ibc/tree/master/spec/core/ics-002-client-semantics#path-space +//////////////////////////////////////////////////////////////////////////////// + +// FullClientStateKey takes a client identifier and returns a Key under which to store a +// particular client state. +func FullClientStateKey(clientID string) []byte { + return fullClientKey(clientID, KeyClientState) +} + +// ClientStatePath takes a client identifier and returns a Path string where it can be accessed +// within the client store +func ClientStatePath(clientID string) string { + return clientPath(clientID, KeyClientState) +} + +// 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) +} + +// 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 { + 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 { + 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 { + return clientPath(clientID, consensusStatePath(height)) +} diff --git a/ibc/host/keys_ics03.go b/ibc/host/keys_ics03.go new file mode 100644 index 000000000..505d9e0e2 --- /dev/null +++ b/ibc/host/keys_ics03.go @@ -0,0 +1,39 @@ +package host + +import "fmt" + +//////////////////////////////////////////////////////////////////////////////// +// ICS03 +// The following paths are the keys to the store as defined in: +// https://github.com/cosmos/ibc/blob/master/spec/core/ics-003-connection-semantics#store-paths +//////////////////////////////////////////////////////////////////////////////// + +// clientConnectionsPath defines a reverse mapping from clients to a set of connections +func clientConnectionsPath(clientID string) string { + return fullClientPath(clientID, KeyConnectionPrefix) +} + +// ClientConnectionsKey returns the store key for the connections of a given client +func ClientConnectionsKey(clientID string) []byte { + return []byte(clientConnectionsPath(clientID)) +} + +// ClientConnectionPath defines the path under which the connections are stored in the client store +func ClientConnectionPath(clientID string) string { + return clientPath(clientID, KeyConnectionPrefix) +} + +// connectionPath defines the path under which connection paths are stored +func connectionPath(connectionID string) string { + return fmt.Sprintf("%s/%s", KeyConnectionPrefix, connectionID) +} + +// ConnectionKey returns the store key for a particular connection +func ConnectionKey(connectionID string) []byte { + return []byte(connectionPath(connectionID)) +} + +// ConnectionPath returns the path for a particular connection in the connections store +func ConnectionPath(connectionID string) string { + return connectionID +} diff --git a/ibc/host/keys_ics04.go b/ibc/host/keys_ics04.go new file mode 100644 index 000000000..1cabd8c7c --- /dev/null +++ b/ibc/host/keys_ics04.go @@ -0,0 +1,155 @@ +package host + +import ( + "fmt" + "strings" +) + +//////////////////////////////////////////////////////////////////////////////// +// ICS04 +// The following paths are the keys to the store as defined in: +// https://github.com/cosmos/ibc/tree/master/spec/core/ics-004-channel-and-packet-semantics#store-paths +//////////////////////////////////////////////////////////////////////////////// + +func channelPath(portID, channelID string) string { + return fmt.Sprintf("%s/%s/%s/%s", KeyPortPrefix, portID, KeyChannelPrefix, channelID) +} + +// fullChannelPath defines the path under which channels are stored +func fullChannelPath(portID, channelID string) string { + return fmt.Sprintf("%s/%s", KeyChannelEndPrefix, channelPath(portID, channelID)) +} + +// ChannelKey returns the store key for a particular channel +func ChannelKey(portID, channelID string) []byte { + return []byte(fullChannelPath(portID, channelID)) +} + +// ChannelPath returns the path under which a particular channel is stored in the ChannelEnd store +func ChannelPath(portID, channelID string) string { + return channelPath(portID, channelID) +} + +// channelCapabilityPath defines the path under which capability keys associated +// with a channel are stored +func channelCapabilityPath(portID, channelID string) string { + return fmt.Sprintf("%s/%s", KeyChannelCapabilityPrefix, channelPath(portID, channelID)) +} + +// ChannelCapabilityKey returns the store key for the capability associated with a channel +func ChannelCapabilityKey(portID, channelID string) []byte { + return []byte(channelCapabilityPath(portID, channelID)) +} + +// ChannelCapabilityPath returns the path under which a particular channel capability is stored +// in the channel capability store +func ChannelCapabilityPath(portID, channelID string) string { + return strings.TrimPrefix(channelCapabilityPath(portID, channelID), KeyChannelCapabilityPrefix+"/") +} + +// nextSequenceSendPath defines the next send sequence counter store path +func nextSequenceSendPath(portID, channelID string) string { + return fmt.Sprintf("%s/%s", KeyNextSeqSendPrefix, channelPath(portID, channelID)) +} + +// NextSequenceSendKey returns the store key for the send sequence of a particular +// channel binded to a specific port. +func NextSequenceSendKey(portID, channelID string) []byte { + return []byte(nextSequenceSendPath(portID, channelID)) +} + +// NextSequenceSendPath returns the path under which the NextSequenceSend is stored in the NextSequenceSend store +func NextSequenceSendPath(portID, channelID string) string { + return channelPath(portID, channelID) +} + +// nextSequenceRecvPath defines the next receive sequence counter store path. +func nextSequenceRecvPath(portID, channelID string) string { + return fmt.Sprintf("%s/%s", KeyNextSeqRecvPrefix, channelPath(portID, channelID)) +} + +// NextSequenceRecvKey returns the store key for the receive sequence of a particular +// channel binded to a specific port +func NextSequenceRecvKey(portID, channelID string) []byte { + return []byte(nextSequenceRecvPath(portID, channelID)) +} + +// NextSequenceRecvPath returns the path under which the NextSequenceRecv is stored in the NextSequenceRecv store +func NextSequenceRecvPath(portID, channelID string) string { + return channelPath(portID, channelID) +} + +// nextSequenceAckPath defines the next acknowledgement sequence counter store path +func nextSequenceAckPath(portID, channelID string) string { + return fmt.Sprintf("%s/%s", KeyNextSeqAckPrefix, channelPath(portID, channelID)) +} + +// NextSequenceAckKey returns the store key for the acknowledgement sequence of +// a particular channel binded to a specific port. +func NextSequenceAckKey(portID, channelID string) []byte { + return []byte(nextSequenceAckPath(portID, channelID)) +} + +// NextSequenceAckPath returns the path under which the NextSequenceAck is stored in the NextSequenceAck store +func NextSequenceAckPath(portID, channelID string) string { + return channelPath(portID, channelID) +} + +// packetCommitmentPrefixPath defines the prefix for commitments to packet data fields store path. +func packetCommitmentPrefixPath(portID, channelID string) string { + return fmt.Sprintf("%s/%s/%s", KeyPacketCommitmentPrefix, channelPath(portID, channelID), KeySequencePrefix) +} + +// packetCommitmentPath defines the commitments to packet data fields store path +func packetCommitmentPath(portID, channelID string, sequence uint64) string { + return fmt.Sprintf("%s/%d", packetCommitmentPrefixPath(portID, channelID), sequence) +} + +// PacketCommitmentKey returns the store key of under which a packet commitment is stored +func PacketCommitmentKey(portID, channelID string, sequence uint64) []byte { + return []byte(packetCommitmentPath(portID, channelID, sequence)) +} + +// PacketCommitmentPath returns the path under which the PacketCommitment is stored in the PacketCommitment store +func PacketCommitmentPath(portID, channelID string, sequence uint64) string { + return strings.TrimPrefix(packetCommitmentPath(portID, channelID, sequence), KeyPacketCommitmentPrefix+"/") +} + +// packetAcknowledgementPrefixPath defines the prefix for commitments to packet data fields store path. +func packetAcknowledgementPrefixPath(portID, channelID string) string { + return fmt.Sprintf("%s/%s/%s", KeyPacketAckPrefix, channelPath(portID, channelID), KeySequencePrefix) +} + +// packetAcknowledgementPath defines the packet acknowledgement store path +func packetAcknowledgementPath(portID, channelID string, sequence uint64) string { + return fmt.Sprintf("%s/%d", packetAcknowledgementPrefixPath(portID, channelID), sequence) +} + +// PacketAcknowledgementKey returns the store key of under which a packet acknowledgement is stored +func PacketAcknowledgementKey(portID, channelID string, sequence uint64) []byte { + return []byte(packetAcknowledgementPath(portID, channelID, sequence)) +} + +// PacketAcknowledgementPath returns the path under which the PacketAcknowledgement is stored in the PacketAcknowledgement store +func PacketAcknowledgementPath(portID, channelID string, sequence uint64) string { + return strings.TrimPrefix(packetAcknowledgementPath(portID, channelID, sequence), KeyPacketAckPrefix+"/") +} + +func sequencePath(sequence uint64) string { + return fmt.Sprintf("%s/%d", KeySequencePrefix, sequence) +} + +// packetReceiptPath defines the packet receipt store path +func packetReceiptPath(portID, channelID string, sequence uint64) string { + return fmt.Sprintf("%s/%s/%s", KeyPacketReceiptPrefix, channelPath(portID, channelID), sequencePath(sequence)) +} + +// PacketReceiptKey returns the store key of under which a packet receipt is stored +func PacketReceiptKey(portID, channelID string, sequence uint64) []byte { + return []byte(packetReceiptPath(portID, channelID, sequence)) +} + +// PacketReceiptPath returns the path under which the PacketReceipt is stored in the PacketReceipt store +func PacketReceiptPath(portID, channelID string, sequence uint64) string { + return strings.TrimPrefix(packetReceiptPath(portID, channelID, sequence), KeyPacketReceiptPrefix+"/") +} diff --git a/ibc/host/keys_ics05.go b/ibc/host/keys_ics05.go new file mode 100644 index 000000000..9ff1f1448 --- /dev/null +++ b/ibc/host/keys_ics05.go @@ -0,0 +1,24 @@ +package host + +import "fmt" + +//////////////////////////////////////////////////////////////////////////////// +// ICS05 +// The following paths are the keys to the store as defined in +// https://github.com/cosmos/ibc/tree/master/spec/core/ics-005-port-allocation#store-paths +//////////////////////////////////////////////////////////////////////////////// + +// portPath defines the path under which ports paths are stored on the capability module +func portPath(portID string) string { + return fmt.Sprintf("%s/%s", KeyPortPrefix, portID) +} + +// PortKey returns the store key for a port in the capability module +func PortKey(portID string) []byte { + return []byte(portPath(portID)) +} + +// PortPath returns the path under which the Port is stored in the Port store +func PortPath(portID string) string { + return portID +} diff --git a/ibc/host/path_test.go b/ibc/host/path_test.go new file mode 100644 index 000000000..1c1c8d367 --- /dev/null +++ b/ibc/host/path_test.go @@ -0,0 +1,94 @@ +package host + +import ( + "testing" + + coreTypes "github.com/pokt-network/pocket/shared/core/types" + "github.com/stretchr/testify/require" +) + +func FuzzIdentifiers_GenerateValidIdentifiers(f *testing.F) { + for i := 0; i < 100; i++ { + switch i % 4 { + case 0: + f.Add("client") + case 1: + f.Add("connection") + case 2: + f.Add("channel") + case 3: + f.Add("port") + } + } + f.Fuzz(func(t *testing.T, idType string) { + switch idType { + case "client": + id := GenerateClientIdentifier() + require.NotEmpty(t, id) + require.GreaterOrEqual(t, len(id), 9) + require.LessOrEqual(t, len(id), 64) + err := ValidateClientID(id) + require.NoError(t, err) + case "connection": + id := GenerateConnectionIdentifier() + require.NotEmpty(t, id) + require.GreaterOrEqual(t, len(id), 10) + require.LessOrEqual(t, len(id), 64) + err := ValidateConnectionID(id) + require.NoError(t, err) + case "channel": + id := GenerateChannelIdentifier() + require.NotEmpty(t, id) + require.GreaterOrEqual(t, len(id), 8) + require.LessOrEqual(t, len(id), 64) + err := ValidateChannelID(id) + require.NoError(t, err) + case "port": + id := GeneratePortIdentifier() + require.NotEmpty(t, id) + require.GreaterOrEqual(t, len(id), 2) + require.LessOrEqual(t, len(id), 128) + err := ValidatePortID(id) + require.NoError(t, err) + } + }) +} + +func TestPaths_CommitmentPrefix(t *testing.T) { + prefix := coreTypes.CommitmentPrefix([]byte("test")) + + testCases := []struct { + name string + path string + prefix coreTypes.CommitmentPrefix + expected []byte + result string + }{ + { + name: "Successfully applies and removes prefix to produce the same path", + path: "path", + prefix: coreTypes.CommitmentPrefix([]byte("test")), + expected: []byte("test/path"), + result: "path", + }, + { + name: "Fails to produce input path when given a different prefix", + path: "path", + prefix: coreTypes.CommitmentPrefix([]byte("test2")), + expected: []byte("test/path"), + result: "ath", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + commitment := ApplyPrefix(prefix, tc.path) + require.NotNil(t, commitment) + require.Equal(t, []byte(commitment), tc.expected) + + path := RemovePrefix(tc.prefix, commitment) + require.NotNil(t, path) + require.Equal(t, path, tc.result) + }) + } +} diff --git a/ibc/host/prefix.go b/ibc/host/prefix.go new file mode 100644 index 000000000..86510bbfd --- /dev/null +++ b/ibc/host/prefix.go @@ -0,0 +1,21 @@ +package host + +import ( + coreTypes "github.com/pokt-network/pocket/shared/core/types" +) + +const delimiter = "/" + +// ApplyPrefix applies the prefix to the provided path returning a CommitmentPath +func ApplyPrefix(prefix coreTypes.CommitmentPrefix, path string) coreTypes.CommitmentPath { + bz := make([]byte, 0, len(prefix)+len(delimiter)+len([]byte(path))) + bz = append(bz, prefix...) + bz = append(bz, []byte(delimiter)...) + bz = append(bz, []byte(path)...) + return coreTypes.CommitmentPath(bz) +} + +// RemovePrefix removes the prefix from the provided CommitmentPath returning a path string +func RemovePrefix(prefix coreTypes.CommitmentPrefix, path coreTypes.CommitmentPath) string { + return string(path[len(prefix)+len(delimiter):]) +} diff --git a/ibc/module.go b/ibc/module.go new file mode 100644 index 000000000..c4f4c9c56 --- /dev/null +++ b/ibc/module.go @@ -0,0 +1,88 @@ +package ibc + +import ( + "github.com/pokt-network/pocket/logger" + "github.com/pokt-network/pocket/runtime/configs" + coreTypes "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.IBCModule = &ibcModule{} + +type ibcModule struct { + base_modules.IntegratableModule + + cfg *configs.IBCConfig + logger *modules.Logger + + // Only a single host is allowed at a time + host *host +} + +func Create(bus modules.Bus, options ...modules.ModuleOption) (modules.Module, error) { + return new(ibcModule).Create(bus, options...) +} + +func (m *ibcModule) Create(bus modules.Bus, options ...modules.ModuleOption) (modules.Module, error) { + *m = ibcModule{ + cfg: bus.GetRuntimeMgr().GetConfig().IBC, + logger: logger.Global.CreateLoggerForModule(modules.IBCModuleName), + } + m.logger.Info().Msg("🪐 creating IBC module 🪐") + + for _, option := range options { + option(m) + } + + bus.RegisterModule(m) + + // Only validators can be an IBC host due to the need for reliability + isValidator := false + if _, err := m.GetBus().GetUtilityModule().GetValidatorModule(); err == nil { + isValidator = true + } + if isValidator && m.cfg.Enabled { + m.logger.Info().Msg("🛰️ creating IBC host 🛰️") + if err := m.newHost(); err != nil { + m.logger.Error().Err(err).Msg("❌ failed to create IBC host") + return nil, err + } + } + + return m, nil +} + +func (m *ibcModule) Start() error { + if !m.cfg.Enabled { + m.logger.Info().Msg("🚫 IBC module disabled 🚫") + return nil + } + m.logger.Info().Msg("🪐 starting IBC module 🪐") + // TODO: start the host logic + return nil +} + +func (m *ibcModule) Stop() error { + return nil +} + +func (m *ibcModule) GetHost() modules.IBCHost { + return m.host +} + +func (m *ibcModule) GetModuleName() string { + return modules.IBCModuleName +} + +// newHost returns a new IBC host instance if one is not already created +func (m *ibcModule) newHost() error { + if m.host != nil { + return coreTypes.ErrHostAlreadyExists() + } + host := &host{ + logger: m.logger, + } + m.host = host + return nil +} diff --git a/internal/testutil/ibc/mock.go b/internal/testutil/ibc/mock.go new file mode 100644 index 000000000..b4a931d77 --- /dev/null +++ b/internal/testutil/ibc/mock.go @@ -0,0 +1,43 @@ +package ibc + +import ( + "time" + + "github.com/golang/mock/gomock" + "github.com/pokt-network/pocket/shared/modules" + mockModules "github.com/pokt-network/pocket/shared/modules/mocks" + "github.com/regen-network/gocuke" +) + +// BaseIbcMock returns a mock IBC module without a Host +func BaseIbcMock(t gocuke.TestingT, busMock *mockModules.MockBus) *mockModules.MockIBCModule { + ctrl := gomock.NewController(t) + ibcMock := mockModules.NewMockIBCModule(ctrl) + + ibcMock.EXPECT().Start().Return(nil).AnyTimes() + ibcMock.EXPECT().SetBus(busMock).Return().AnyTimes() + ibcMock.EXPECT().GetBus().Return(busMock).AnyTimes() + ibcMock.EXPECT().GetModuleName().Return(modules.IBCModuleName).AnyTimes() + + return ibcMock +} + +// IbcMockWithHost returns a mock IBC module with a Host +func IbcMockWithHost(t gocuke.TestingT, _ modules.EventsChannel) *mockModules.MockIBCModule { + ctrl := gomock.NewController(t) + ibcMock := mockModules.NewMockIBCModule(ctrl) + + ibcMock.EXPECT().Start().Return(nil).AnyTimes() + ibcMock.EXPECT().SetBus(gomock.Any()).Return().AnyTimes() + ibcMock.EXPECT().GetModuleName().Return(modules.IBCModuleName).AnyTimes() + + hostMock := mockModules.NewMockIBCHost(ctrl) + hostMock.EXPECT().GetTimestamp().DoAndReturn(func() uint64 { + timestamp := time.Now().Unix() + return uint64(timestamp) + }).AnyTimes() + + ibcMock.EXPECT().GetHost().Return(hostMock).AnyTimes() + + return ibcMock +} diff --git a/runtime/bus.go b/runtime/bus.go index cea228733..0b343a8a7 100644 --- a/runtime/bus.go +++ b/runtime/bus.go @@ -119,6 +119,10 @@ func (m *bus) GetStateMachineModule() modules.StateMachineModule { return getModuleFromRegistry[modules.StateMachineModule](m, modules.StateMachineModuleName) } +func (m *bus) GetIBCModule() modules.IBCModule { + return getModuleFromRegistry[modules.IBCModule](m, modules.IBCModuleName) +} + // getModuleFromRegistry is a helper function to get a module from the registry that handles errors and casting via generics func getModuleFromRegistry[T modules.Module](m *bus, moduleName string) T { mod, err := m.modulesRegistry.GetModule(moduleName) diff --git a/runtime/configs/config.go b/runtime/configs/config.go index d9a9791fd..3e387a8c8 100644 --- a/runtime/configs/config.go +++ b/runtime/configs/config.go @@ -32,6 +32,7 @@ type Config struct { Validator *ValidatorConfig `json:"validator"` Servicer *ServicerConfig `json:"servicer"` Fisherman *FishermanConfig `json:"fisherman"` + IBC *IBCConfig `json:"ibc"` } // ParseConfig parses the config file and returns a Config struct @@ -97,7 +98,6 @@ func ParseConfig(cfgFile string) *Config { func setViperDefaults(cfg *Config) { // convert the config struct to a map with the json tags as keys cfgData, err := json.Marshal(cfg) - if err != nil { log.Fatalf("[ERROR] failed to marshal config %s", err.Error()) } @@ -159,6 +159,9 @@ func NewDefaultConfig(options ...func(*Config)) *Config { Validator: &ValidatorConfig{}, Servicer: &ServicerConfig{}, Fisherman: &FishermanConfig{}, + IBC: &IBCConfig{ + Enabled: defaults.DefaultIBCEnabled, + }, } for _, option := range options { diff --git a/runtime/configs/proto/ibc_config.proto b/runtime/configs/proto/ibc_config.proto new file mode 100644 index 000000000..389e19528 --- /dev/null +++ b/runtime/configs/proto/ibc_config.proto @@ -0,0 +1,15 @@ +syntax = "proto3"; + +package configs; + +option go_package = "github.com/pokt-network/pocket/runtime/configs"; + +message IBCConfig { + // If IBC is enabled by a node there are two possible states depending on the + // node's actor type: + // 1. The node is a validator and thus when IBC enabled is automatically + // an IBC host. + // 2. The node is a servicer and thus when IBC enabled is they are enabled + // to relay IBC packets using an IBC relayer binary + bool enabled = 1; +} diff --git a/runtime/defaults/defaults.go b/runtime/defaults/defaults.go index 586628cf9..ce02b2a05 100644 --- a/runtime/defaults/defaults.go +++ b/runtime/defaults/defaults.go @@ -71,6 +71,9 @@ var ( DefaultKeybaseVaultAddr = "" DefaultKeybaseVaultToken = "" DefaultKeybaseVaultMountPath = "" + + // ibc + DefaultIBCEnabled = false ) var ( diff --git a/runtime/manager_test.go b/runtime/manager_test.go index a56add64c..8c3ce360f 100644 --- a/runtime/manager_test.go +++ b/runtime/manager_test.go @@ -1823,6 +1823,7 @@ func TestNewManagerFromReaders(t *testing.T) { }, Validator: &configs.ValidatorConfig{Enabled: true}, Fisherman: defaultCfg.Fisherman, + IBC: &configs.IBCConfig{Enabled: true}, }, genesisState: expectedGenesis, clock: clock.New(), diff --git a/shared/core/types/commitments.go b/shared/core/types/commitments.go new file mode 100644 index 000000000..cac0b1775 --- /dev/null +++ b/shared/core/types/commitments.go @@ -0,0 +1,17 @@ +package types + +// These types are specific to the IBC module and how they store commitments in the +// IBC store. These types are used to implement the pre-defined paths from ICS-24. +// Ref: https://github.com/cosmos/ibc/blob/main/spec/core/ics-024-host-requirements/README.md +type ( + // CommitmentPrefix are the prefix bytes used in conjunction with a path string + // to create a CommitmentPath. + // The prefix represents the store in which the path is stored, for example: + // => []byte("clients/{clientID}/consensusStates/{height}") + // In the above example the prefix is []byte("clients") which represents the + // client store, and the full byteslice is the CommitmentPath, the full key + // under which the commitment is stored. + CommitmentPrefix []byte + // CommitmentPath is the path bytes used to store a commitment in a store + CommitmentPath []byte +) diff --git a/shared/core/types/error.go b/shared/core/types/error.go index ac71a48e3..4de0bc6ef 100644 --- a/shared/core/types/error.go +++ b/shared/core/types/error.go @@ -38,7 +38,7 @@ func NewError(code Code, msg string) Error { } } -// NextCode: 138 +// NextCode: 141 type Code float64 // CONSIDERATION: Should these be a proto enum or a golang iota? //nolint:gosec // G101 - Not hard-coded credentials @@ -177,6 +177,9 @@ const ( CodeUnknownActorType Code = 130 CodeUnknownMessageType Code = 131 CodeProposalBlockNotSet Code = 133 + CodeHostAlreadyExists Code = 138 + CodeIBCInvalidID Code = 139 + CodeIBCInvalidPath Code = 140 ) const ( @@ -312,6 +315,9 @@ const ( NegativeAmountError = "the amount is negative" UnknownActorTypeError = "the actor type is not recognized" UnknownMessageTypeError = "the message being by the utility message is not recognized" + HostAlreadyExistsError = "an ibc host already exists" + IBCInvalidIDError = "invalid ibc identifier" + IBCInvalidPathError = "invalid ibc path" ) func ErrUnknownParam(paramName string) Error { @@ -684,7 +690,6 @@ func ErrMissingRequiredArg(value string) error { func ErrSocketRequestTimedOut(addr string, nonce uint32) error { return NewError(CodeSocketRequestTimedOutError, fmt.Sprintf("%s: %s, %d", SocketRequestTimedOutError, addr, nonce)) - } func ErrUndefinedSocketType(socketType string) error { @@ -847,3 +852,15 @@ func ErrUnknownActorType(actorType string) Error { func ErrUnknownMessageType(messageType any) Error { return NewError(CodeUnknownMessageType, fmt.Sprintf("%s: %v", UnknownMessageTypeError, messageType)) } + +func ErrHostAlreadyExists() Error { + return NewError(CodeHostAlreadyExists, HostAlreadyExistsError) +} + +func ErrIBCInvalidID(identifier, msg string) Error { + return NewError(CodeIBCInvalidID, fmt.Sprintf("%s: %s (%s)", IBCInvalidIDError, identifier, msg)) +} + +func ErrIBCInvalidPath(path string) Error { + return NewError(CodeIBCInvalidPath, fmt.Sprintf("%s: %s", IBCInvalidPathError, path)) +} diff --git a/shared/modules/bus_module.go b/shared/modules/bus_module.go index 8699b756f..195d13dfd 100644 --- a/shared/modules/bus_module.go +++ b/shared/modules/bus_module.go @@ -32,6 +32,7 @@ type Bus interface { GetLoggerModule() LoggerModule GetRPCModule() RPCModule GetStateMachineModule() StateMachineModule + GetIBCModule() IBCModule // Runtime GetRuntimeMgr() RuntimeMgr diff --git a/shared/modules/ibc_module.go b/shared/modules/ibc_module.go new file mode 100644 index 000000000..d006ee194 --- /dev/null +++ b/shared/modules/ibc_module.go @@ -0,0 +1,198 @@ +package modules + +//go:generate mockgen -destination=./mocks/ibc_module_mock.go github.com/pokt-network/pocket/shared/modules IBCModule,IBCHost,IBCHandler + +const IBCModuleName = "ibc" + +type IBCModule interface { + Module + + // GetHost returns the IBC host of the modules + GetHost() IBCHost +} + +// IBCHost is the interface used by the host machine (a Pocket node) to interact with the IBC module +// the host is responsible for managing the IBC state and interacting with consensus in order for +// any IBC packets to be sent to another host on a different chain (via an IBC relayer). The hosts +// are also responsible for receiving any IBC packets from another chain and verifying them through +// the light clients they manage +// https://github.com/cosmos/ibc/tree/main/spec/core/ics-024-host-requirements +type IBCHost interface { + IBCHandler + + // GetTimestamp returns the current unix timestamp for the host machine + GetTimestamp() uint64 +} + +// TODO: Uncomment interface functions as they are defined and potentially change their signatures +// where necessary +// 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/node.go b/shared/node.go index aa4292bfb..ccdb806f7 100644 --- a/shared/node.go +++ b/shared/node.go @@ -5,6 +5,7 @@ import ( "time" "github.com/pokt-network/pocket/consensus" + "github.com/pokt-network/pocket/ibc" "github.com/pokt-network/pocket/logger" "github.com/pokt-network/pocket/p2p" "github.com/pokt-network/pocket/persistence" @@ -47,6 +48,7 @@ func (m *Node) Create(bus modules.Bus, options ...modules.ModuleOption) (modules logger.Create, rpc.Create, p2p.Create, + ibc.Create, } { if _, err := mod(bus); err != nil { return nil, err @@ -97,6 +99,10 @@ func (node *Node) Start() error { return err } + if err := node.GetBus().GetIBCModule().Start(); err != nil { + return err + } + // The first event signaling that the node has started signalNodeStartedEvent, err := messaging.PackMessage(&messaging.NodeStartedEvent{}) if err != nil {