概要

本规范定义了托管区块链间通信协议实现的状态机必须提供的最小接口集合,以及必须满足的属性。

动机

IBC 旨在成为由多种区块链和状态机承载的通用标准,因此必须清晰定义宿主的要求。

定义

期望属性

IBC 应尽可能少地要求底层状态机提供接口,以最大化正确实现的便利性。

技术规范

模块系统

宿主状态机必须支持模块系统,使得自包含、彼此之间可能互不信任的代码包能够在同一账本上安全执行,控制是否以及何时允许其他模块与之通信,并且能够被“主模块”或执行环境识别和操作。 IBC/TAO 规范定义了两个模块的实现:核心的 “IBC handler” 模块和 “IBC relayer” 模块。IBC/APP 规范进一步为特定的数据包处理应用逻辑定义其他模块。IBC 要求能够使用“主模块”或执行环境,向宿主状态机上的其他模块授予访问 IBC handler 模块和/或 IBC routing 模块的权限,但除此之外,不对可能与该状态机共置的任何其他模块的功能或通信能力施加要求。

路径、标识符、分隔符

Identifier 是一个字节串,用作存储在状态中的对象的键,例如连接、通道或轻客户端。 标识符 MUST 为非空(长度为正整数)。 标识符 MUST 仅由以下类别中的字符组成:
  • 字母数字字符
  • ., _, +, -, #
  • [, ], <, >
Path 是一个字节串,用作存储在状态中的对象的键。路径 MUST 仅包含标识符、常量字符串以及分隔符 "/"。 标识符不应被视为有价值的资源。为防止名称抢注,可以实现最小长度要求或伪随机生成,但本规范不施加特定限制。 分隔符 "/" 用于分隔和拼接两个标识符,或一个标识符与一个常量字节串。标识符 MUST NOT 包含 "/" 字符,以防止歧义。 本规范中广泛使用花括号表示的变量插值,作为定义路径格式的简写,例如 client/{clientIdentifier}/consensusState。 除非另有说明,所有标识符以及本规范中列出的所有字符串都必须采用 ASCII 编码。 默认情况下,标识符的最小和最大字符长度如下:
端口标识符客户端标识符连接标识符通道标识符
2 - 1289 - 6410 - 648 - 64

键值存储

宿主状态机 MUST 提供一个键值存储接口, 其包含三个按标准方式行为的函数:
type get = (path: Path) => Value | void
type set = (path: Path, value: Value) => void
type delete = (path: Path) => void
Path 的定义如上。Value 是特定数据结构的任意字节串编码。编码细节留给单独的 ICS 定义。 这些函数 MUST 仅授权给 IBC handler 模块(其实现见其他标准),因此只有 IBC handler 模块可以对可被 get 读取的路径执行 set 或 delete。这可以实现为整个状态机所使用的更大键值存储中的一个子存储(带前缀的键空间)。 宿主状态机 MUST 提供该接口的两个实例: 一个 provableStore,用于存储可被其他链读取(即可向其他链证明)的数据; 以及一个 privateStore,用于宿主本地存储,可在其上调用 get 、set 和 delete,例如 provableStore.set('some/path', 'value')。 provableStore:
  • MUST 写入一个键值存储,其数据可以按照 ICS 23 中定义的向量承诺进行外部证明。
  • MUST 使用这些规范中以 proto3 文件形式提供的规范数据结构编码
privateStore:
  • MAY 支持外部证明,但不是必须的,因为 IBC handler 永远不会向其中写入需要被证明的数据。
  • MAY 使用规范的 proto3 数据结构,但不是必须的;它可以使用 应用环境偏好的任何格式。
注意:任何提供这些方法和属性的键值存储接口都足以支持 IBC。宿主状态机可以实现“代理存储”,其路径和值映射与通过存储接口设置和获取的路径和值对不直接匹配。例如,路径可以被分组到桶中,值可以按页存储并通过单个承诺进行证明;路径空间也可以通过某种双射方式进行非连续重映射,等等。只要 get、set 和 delete 按预期行为运作,并且其他机器能够验证 provable store 中路径和值对(或其不存在)的承诺证明即可。如果适用,存储必须向外部暴露这种映射,以便客户端(包括 relayer)能够确定存储布局以及如何构造证明。使用此类代理存储的机器的客户端也必须理解这种映射,因此这将需要一种新的客户端类型或参数化客户端。 注意:该接口不要求任何特定的存储后端或后端数据布局。状态机可以根据自身需求选择配置存储后端,只要其上层的存储满足规定的接口并提供承诺证明。

路径空间

目前,IBC/TAO 为 provableStore 和 privateStore 推荐以下路径前缀。 未来版本的协议可能会使用新的路径,因此 provable store 中的整个键空间 MUST 为 IBC handler 保留。 只要此处定义的键格式与机器实现中实际使用的键格式之间存在双向映射,provable store 中按每种客户端类型使用不同的键就是安全的。 只要 IBC handler 对所需的特定键拥有独占访问权,private store 的部分空间 MAY 安全地用于其他目的。 只要此处定义的键格式与 private store 实现中实际使用的键格式之间存在双向映射,private store 中使用的键 MAY 安全地变化。 请注意,下方列出的与客户端相关的路径反映的是 ICS 7 中定义的 Tendermint 客户端,对于其他客户端类型可能会有所不同。
存储路径格式值类型定义于
provableStore”clients//clientState”ClientStateICS 2
provableStore”clients//consensusStates/“ConsensusStateICS 7
privateStore”clients//connections[]IdentifierICS 3
provableStore”connections/“ConnectionEndICS 3
privateStore”ports/“CapabilityKeyICS 5
provableStore”channelEnds/ports//channels/“ChannelEndICS 4
provableStore”nextSequenceSend/ports//channels/“uint64ICS 4
provableStore”nextSequenceRecv/ports//channels/“uint64ICS 4
provableStore”nextSequenceAck/ports//channels/“uint64ICS 4
provableStore”commitments/ports//channels//sequences/“bytesICS 4
provableStore”receipts/ports//channels//sequences/“bytesICS 4
provableStore”acks/ports//channels//sequences/“bytesICS 4

模块布局

从空间表示上看,宿主状态机上的模块及其所包含规范的布局如下所示(Aardvark、Betazoid 和 Cephalopod 是任意模块):
+----------------------------------------------------------------------------------+
|                                                                                  |
| Host State Machine                                                               |
|                                                                                  |
| +-------------------+       +--------------------+      +----------------------+ |
| | Module Aardvark   | <-->  | IBC Routing Module |      | IBC Handler Module   | |
| +-------------------+       |                    |      |                      | |
|                             | Implements ICS 26. |      | Implements ICS 2, 3, | |
|                             |                    |      | 4, 5 internally.     | |
| +-------------------+       |                    |      |                      | |
| | Module Betazoid   | <-->  |                    | -->  | Exposes interface    | |
| +-------------------+       |                    |      | defined in ICS 25.   | |
|                             |                    |      |                      | |
| +-------------------+       |                    |      |                      | |
| | Module Cephalopod | <-->  |                    |      |                      | |
| +-------------------+       +--------------------+      +----------------------+ |
|                                                                                  |
+----------------------------------------------------------------------------------+

共识状态自省

主机状态机 MUST 提供自省其当前高度的能力,通过 getCurrentHeight:
type getCurrentHeight = () => Height
主机状态机 MUST 定义一个唯一的 ConsensusState 类型,并满足 ICS 2 的要求,同时具备规范的二进制序列化。 主机状态机 MUST 提供自省其自身共识状态的能力,通过 getConsensusState:
type getConsensusState = (height: Height, proof?: bytes) => ConsensusState
getConsensusState MUST 至少返回某个连续的最近高度数量 n 对应的共识状态,其中 n 对于该主机状态机是常量。早于 n 的高度 MAY 被安全裁剪(这会导致后续针对这些高度的调用失败)。 我们提供一个可选的证明数据,该数据来自 MsgConnectionOpenAck 或 MsgConnectionOpenTry,用于那些无法自省自身 ConsensusState、必须依赖链下数据的主机状态机。
在这种情况下,主机状态机 MUST 维护一个从 n 个区块号到区块头哈希的映射,其中证明将包含完整区块头,可对其进行哈希并与链上记录比较。 主机状态机 MUST 提供自省这一已存储的最近共识状态数量 n 的能力,通过 getStoredRecentConsensusStateCount:
type getStoredRecentConsensusStateCount = () => Height

客户端状态验证

主机状态机 MUST 定义一个唯一的 ClientState 类型,并满足 ICS 2 的要求。 主机状态机 MUST 提供构造其自身状态的 ClientState 表示形式的能力,以用于客户端状态验证,通过 getHostClientState:
type getHostClientState = (height: Height) => ClientState
主机状态机 MUST 提供验证运行在对手链上的轻客户端 ClientState 的能力,通过 validateSelfClient:
type validateSelfClient = (counterpartyClientState: ClientState) => boolean
validateSelfClient 用于验证主机链客户端的客户端参数。例如,下面是 Tendermint 主机的实现,使用 ICS 7 中定义的 ClientState:
function validateSelfClient(counterpartyClientState: ClientState) {
  hostClientState = getHostClientState()

  // assert that the counterparty client is not frozen
  if counterpartyClientState.frozenHeight !== null {
    return false
  }

  // assert that the chain ids are the same
  if counterpartyClientState.chainID !== hostClientState.chainID {
    return false
  }

  // assert that the counterparty client is in the same revision as the host chain
  counterpartyRevisionNumber = parseRevisionNumber(counterpartyClientState.chainID)
  if counterpartyRevisionNumber !== hostClientState.latestHeight.revisionNumber {
    return false
  }

  // assert that the counterparty client has a height less than the host height
  if counterpartyClientState.latestHeight >= hostClientState.latestHeight {
    return false
  }

  // assert that the counterparty client has the same ProofSpec as the host
  if counterpartyClientState.proofSpecs !== hostClientState.proofSpecs {
    return false
  }

  // assert that the trustLevel is within the allowed range. 1/3 is the minimum amount
  // of trust needed which does not break the security model.
  if counterpartyClientState.trustLevel < 1/3 || counterpartyClientState.trustLevel > 1 {
    return false
  }

  // assert that the unbonding periods are the same
  if counterpartyClientState.unbondingPeriod != hostClientState.unbondingPeriod {
    return false
  }

  // assert that the unbonding period is greater than or equal to the trusting period
  if counterpartyClientState.unbondingPeriod < counterpartyClientState.trustingPeriod {
    return false
  }

  // assert that the upgrade paths are the same
  hostUpgradePath = applyPrefix(hostClientState.upgradeCommitmentPrefix, hostClientState.upgradeKey)
  counterpartyUpgradePath = applyPrefix(counterpartyClientState.upgradeCommitmentPrefix, counterpartyClientState.upgradeKey)
  if counterpartyUpgradePath !== hostUpgradePath {
    return false
  }
  
  return true
}

承诺路径自省

主机链 MUST 提供检查其承诺路径的能力,通过 getCommitmentPrefix:
type getCommitmentPrefix = () => CommitmentPrefix
结果 CommitmentPrefix 是主机状态机键值存储所使用的前缀。 对于主机状态机的 CommitmentRoot root 和 CommitmentState state,MUST 保持如下性质:
if provableStore.get(path) === value {
  prefixedPath = applyPrefix(getCommitmentPrefix(), path)
  if value !== nil {
    proof = createMembershipProof(state, prefixedPath, value)
    assert(verifyMembership(root, proof, prefixedPath, value))
  } else {
    proof = createNonMembershipProof(state, prefixedPath)
    assert(verifyNonMembership(root, proof, prefixedPath))
  }
}
对于某个主机状态机,getCommitmentPrefix 的返回值 MUST 为常量。

时间戳访问

主机链 MUST 提供当前 Unix 时间戳,并可通过 currentTimestamp() 访问:
type currentTimestamp = () => uint64
为了使时间戳能够安全地用于超时判断,后续区块头中的时间戳 MUST 是非递减的。

端口系统

主机状态机 MUST 实现端口系统,使 IBC 处理器能够允许主机状态机中的不同模块绑定到具有唯一名称的端口。端口由一个 Identifier 标识。 主机状态机 MUST 通过与 IBC 处理器的权限交互实现以下要求:
  • 一旦某个模块绑定了一个端口,在该模块释放它之前,其他模块都不能使用该端口
  • 单个模块可以绑定多个端口
  • 端口按先到先得的方式分配,而已知模块的“保留”端口可以在状态机首次启动时进行绑定
这种权限控制可以通过为每个端口使用唯一引用(对象能力,类似 Cosmos SDK)、通过源认证(类似 Ethereum),或通过其他访问控制方法来实现,但无论哪种情况都必须由主机状态机强制执行。详见 ICS 5。 希望使用特定 IBC 功能的模块 MAY 实现某些处理器函数,例如在与另一条状态机上的关联模块进行通道握手时添加额外逻辑。

数据报提交

实现了路由模块的主机状态机 MAY 定义一个 submitDatagram 函数,用于将数据报1提交到路由模块(定义见 ICS 26),这些数据报将被纳入交易:
type submitDatagram = (datagram: Datagram) => void
submitDatagram 允许中继进程将 IBC 数据报直接提交到主机状态机上的路由模块。主机状态机 MAY 要求提交该数据报的中继进程拥有一个账户以支付交易手续费、在更大的交易结构中对该数据报进行签名,等等;submitDatagram MUST 定义并构造任何此类所需的封装。

异常系统

主机状态机 MUST 支持异常系统,使得交易能够中止执行并回滚此前所做的任何状态变更(包括同一交易内其他模块发生的状态变更),并在适当情况下排除已消耗的 gas 与手续费支付;同时,系统不变量违反时可以使状态机停止。 这个异常系统 MUST 通过两个函数暴露:abortTransactionUnless 和 abortSystemUnless,前者回滚交易,后者停止状态机。
type abortTransactionUnless = (bool) => void
如果传递给 abortTransactionUnless 的布尔值为 true,主机状态机可以不执行任何操作。如果传递给 abortTransactionUnless 的布尔值为 false,主机状态机 MUST 中止交易并回滚此前所做的任何状态变更,并在适当情况下排除已消耗的 gas 与手续费支付。
type abortSystemUnless = (bool) => void
如果传递给 abortSystemUnless 的布尔值为 true,主机状态机可以不执行任何操作。如果传递给 abortSystemUnless 的布尔值为 false,主机状态机 MUST 停止运行。

数据可用性

为了保证“交付或超时”安全性,主机状态机 MUST 具备最终数据可用性,使得状态中的任意键值对最终都能被中继者检索到。对于“恰好一次”安全性,则不要求数据可用性。 为了保证数据包中继的活性,主机状态机 MUST 具有有界交易活性(因此也必然具有共识活性),即传入交易必须在某个区块高度界限内得到确认(特别是,该界限必须小于分配给数据包的超时时间)。 IBC 数据包数据,以及其他未直接存储在状态向量中但中继者所依赖的数据,MUST 对中继进程可用,并且能被高效计算。 特定共识算法的轻客户端可能具有不同和/或更严格的数据可用性要求。

事件日志系统

主机状态机 MUST 提供事件日志系统,使任意数据都可以在交易执行过程中被记录,并可由执行该状态机的进程存储、索引并在之后查询。这些事件日志被中继者用来读取 IBC 数据包数据和超时信息,这些信息并不直接存储在链状态中(因为假定这种存储成本较高),而是通过简洁的加密承诺进行提交(仅存储该承诺)。 该系统至少应当包含一个用于发出日志条目的函数和一个用于查询历史日志的函数,大致如下。 函数 emitLogEntry 可由状态机在交易执行期间调用,以写入日志条目:
type emitLogEntry = (topic: string, data: []byte) => void
函数 queryByTopic 可由外部进程(例如中继者)调用,以检索在给定高度执行的交易中、与某个给定主题相关联的所有日志条目。
type queryByTopic = (height: Height, topic: string) => []byte
系统 MAY 还支持更复杂的查询功能,并且可能允许中继进程执行更高效的查询,但这不是必需的。

处理升级

主机机器可以安全地升级其状态机的部分内容,而不影响 IBC 功能。为了安全地做到这一点,IBC 处理器逻辑必须持续符合本规范,并且所有与 IBC 相关的状态(包括可证明存储和私有存储中的状态)都必须在升级过程中保留。如果其他链上存在针对正在升级链的客户端,且该升级会改变轻客户端验证算法,则必须在升级之前通知这些客户端,以便它们能够安全地原子切换,并保持连接与通道的连续性。

向后兼容性

不适用。

向前兼容性

键值存储功能和共识状态类型在单个主机状态机的运行期间不太可能发生变化。 submitDatagram 可以随时间变化,因为中继者应当能够更新其进程。

示例实现

历史

2019 年 4 月 29 日 - 初始草案 2019 年 5 月 11 日 - 将 “RootOfTrust” 重命名为 “ConsensusState” 2019 年 6 月 25 日 - 使用 “ports” 替代模块名称 2019 年 8 月 18 日 - 修订模块系统和定义 2022 年 7 月 5 日 - 将通道标识符允许的最小长度下调为 8 2022 年 7 月 27 日 - 将 ClientState 移至 provableStore,并新增“客户端状态验证”章节

版权

本文所有内容均基于 Apache 2.0 许可。
1:数据报是在某种物理网络上传输的不透明字节串,并由账本状态机中实现的 IBC 路由模块处理。在某些实现中,数据报可能是某个账本特定交易或消息数据结构中的一个字段,该结构还包含其他信息(例如用于防止垃圾消息的费用、用于防止重放的 nonce、用于路由到 IBC 处理器的类型标识符等)。所有 IBC 子协议(例如打开连接、创建通道、发送数据包)都是根据数据报集合以及通过路由模块处理这些数据报的协议来定义的。

Synopsis

This specification defines the minimal set of interfaces which must be provided and properties which must be fulfilled by a state machine hosting an implementation of the interblockchain communication protocol.

Motivation

IBC is designed to be a common standard which will be hosted by a variety of blockchains & state machines and must clearly define the requirements of the host.

Definitions

Desired Properties

IBC should require as simple an interface from the underlying state machine as possible to maximise the ease of correct implementation.

Technical Specification

Module system

The host state machine must support a module system, whereby self-contained, potentially mutually distrusted packages of code can safely execute on the same ledger, control how and when they allow other modules to communicate with them, and be identified and manipulated by a “master module” or execution environment. The IBC/TAO specifications define the implementations of two modules: the core “IBC handler” module and the “IBC relayer” module. IBC/APP specifications further define other modules for particular packet handling application logic. IBC requires that the “master module” or execution environment can be used to grant other modules on the host state machine access to the IBC handler module and/or the IBC routing module, but otherwise does not impose requirements on the functionality or communication abilities of any other modules which may be co-located on the state machine.

Paths, identifiers, separators

An Identifier is a bytestring used as a key for an object stored in state, such as a connection, channel, or light client. Identifiers MUST be non-empty (of positive integer length). Identifiers MUST consist of characters in one of the following categories only:
  • Alphanumeric
  • ., _, +, -, #
  • [, ], <, >
A Path is a bytestring used as the key for an object stored in state. Paths MUST contain only identifiers, constant strings, and the separator "/". Identifiers are not intended to be valuable resources — to prevent name squatting, minimum length requirements or pseudorandom generation MAY be implemented, but particular restrictions are not imposed by this specification. The separator "/" is used to separate and concatenate two identifiers or an identifier and a constant bytestring. Identifiers MUST NOT contain the "/" character, which prevents ambiguity. Variable interpolation, denoted by curly braces, is used throughout this specification as shorthand to define path formats, e.g. client/{clientIdentifier}/consensusState. All identifiers, and all strings listed in this specification, must be encoded as ASCII unless otherwise specified. By default, identifiers have the following minimum and maximum lengths in characters:
Port identifierClient identifierConnection identifierChannel identifier
2 - 1289 - 6410 - 648 - 64

Key/value Store

The host state machine MUST provide a key/value store interface with three functions that behave in the standard way:
type get = (path: Path) => Value | void
type set = (path: Path, value: Value) => void
type delete = (path: Path) => void
Path is as defined above. Value is an arbitrary bytestring encoding of a particular data structure. Encoding details are left to separate ICSs. These functions MUST be permissioned to the IBC handler module (the implementation of which is described in separate standards) only, so only the IBC handler module can set or delete the paths that can be read by get. This can possibly be implemented as a sub-store (prefixed key-space) of a larger key/value store used by the entire state machine. Host state machines MUST provide two instances of this interface - a provableStore for storage read by (i.e. proven to) other chains, and a privateStore for storage local to the host, upon which get , set, and delete can be called, e.g. provableStore.set('some/path', 'value'). The provableStore:
  • MUST write to a key/value store whose data can be externally proved with a vector commitment as defined in ICS 23.
  • MUST use canonical data structure encodings provided in these specifications as proto3 files
The privateStore:
  • MAY support external proofs, but is not required to - the IBC handler will never write data to it which needs to be proved.
  • MAY use canonical proto3 data structures, but is not required to - it can use whatever format is preferred by the application environment.
Note: any key/value store interface which provides these methods & properties is sufficient for IBC. Host state machines may implement “proxy stores” with path & value mappings which do not directly match the path & value pairs set and retrieved through the store interface — paths could be grouped into buckets & values stored in pages which could be proved in a single commitment, path-spaces could be remapped non-contiguously in some bijective manner, etc — as long as get, set, and delete behave as expected and other machines can verify commitment proofs of path & value pairs (or their absence) in the provable store. If applicable, the store must expose this mapping externally so that clients (including relayers) can determine the store layout & how to construct proofs. Clients of a machine using such a proxy store must also understand the mapping, so it will require either a new client type or a parameterised client. Note: this interface does not necessitate any particular storage backend or backend data layout. State machines may elect to use a storage backend configured in accordance with their needs, as long as the store on top fulfils the specified interface and provides commitment proofs.

Path-space

At present, IBC/TAO recommends the following path prefixes for the provableStore and privateStore. Future paths may be used in future versions of the protocol, so the entire key-space in the provable store MUST be reserved for the IBC handler. Keys used in the provable store MAY safely vary on a per-client-type basis as long as there exists a bipartite mapping between the key formats defined herein and the ones actually used in the machine’s implementation. Parts of the private store MAY safely be used for other purposes as long as the IBC handler has exclusive access to the specific keys required. Keys used in the private store MAY safely vary as long as there exists a bipartite mapping between the key formats defined herein and the ones actually used in the private store implementation. Note that the client-related paths listed below reflect the Tendermint client as defined in ICS 7 and may vary for other client types.
StorePath formatValue typeDefined in
provableStore”clients//clientState”ClientStateICS 2
provableStore”clients//consensusStates/“ConsensusStateICS 7
privateStore”clients//connections[]IdentifierICS 3
provableStore”connections/“ConnectionEndICS 3
privateStore”ports/“CapabilityKeyICS 5
provableStore”channelEnds/ports//channels/“ChannelEndICS 4
provableStore”nextSequenceSend/ports//channels/“uint64ICS 4
provableStore”nextSequenceRecv/ports//channels/“uint64ICS 4
provableStore”nextSequenceAck/ports//channels/“uint64ICS 4
provableStore”commitments/ports//channels//sequences/“bytesICS 4
provableStore”receipts/ports//channels//sequences/“bytesICS 4
provableStore”acks/ports//channels//sequences/“bytesICS 4

Module layout

Represented spatially, the layout of modules & their included specifications on a host state machine looks like so (Aardvark, Betazoid, and Cephalopod are arbitrary modules):
+----------------------------------------------------------------------------------+
|                                                                                  |
| Host State Machine                                                               |
|                                                                                  |
| +-------------------+       +--------------------+      +----------------------+ |
| | Module Aardvark   | <-->  | IBC Routing Module |      | IBC Handler Module   | |
| +-------------------+       |                    |      |                      | |
|                             | Implements ICS 26. |      | Implements ICS 2, 3, | |
|                             |                    |      | 4, 5 internally.     | |
| +-------------------+       |                    |      |                      | |
| | Module Betazoid   | <-->  |                    | -->  | Exposes interface    | |
| +-------------------+       |                    |      | defined in ICS 25.   | |
|                             |                    |      |                      | |
| +-------------------+       |                    |      |                      | |
| | Module Cephalopod | <-->  |                    |      |                      | |
| +-------------------+       +--------------------+      +----------------------+ |
|                                                                                  |
+----------------------------------------------------------------------------------+

Consensus state introspection

Host state machines MUST provide the ability to introspect their current height, with getCurrentHeight:
type getCurrentHeight = () => Height
Host state machines MUST define a unique ConsensusState type fulfilling the requirements of ICS 2, with a canonical binary serialisation. Host state machines MUST provide the ability to introspect their own consensus state, with getConsensusState:
type getConsensusState = (height: Height, proof?: bytes) => ConsensusState
getConsensusState MUST return the consensus state for at least some number n of contiguous recent heights, where n is constant for the host state machine. Heights older than n MAY be safely pruned (causing future calls to fail for those heights). We provide an optional proof data which comes from the MsgConnectionOpenAck or MsgConnectionOpenTry for host state machines which are unable to introspect their own ConsensusState and must rely on off-chain data.
In this case host state machines MUST maintain a map of n block numbers to header hashes where the proof would contain full header which can be hashed and compared with the on-chain record. Host state machines MUST provide the ability to introspect this stored recent consensus state count n, with getStoredRecentConsensusStateCount:
type getStoredRecentConsensusStateCount = () => Height

Client state validation

Host state machines MUST define a unique ClientState type fulfilling the requirements of ICS 2. Host state machines MUST provide the ability to construct a ClientState representation of their own state for the purposes of client state validation, with getHostClientState:
type getHostClientState = (height: Height) => ClientState
Host state machines MUST provide the ability to validate the ClientState of a light client running on a counterparty chain, with validateSelfClient:
type validateSelfClient = (counterpartyClientState: ClientState) => boolean
validateSelfClient validates the client parameters for a client of the host chain. For example, below is the implementation for Tendermint hosts, using ClientState as defined in ICS 7:
function validateSelfClient(counterpartyClientState: ClientState) {
  hostClientState = getHostClientState()

  // assert that the counterparty client is not frozen
  if counterpartyClientState.frozenHeight !== null {
    return false
  }

  // assert that the chain ids are the same
  if counterpartyClientState.chainID !== hostClientState.chainID {
    return false
  }

  // assert that the counterparty client is in the same revision as the host chain
  counterpartyRevisionNumber = parseRevisionNumber(counterpartyClientState.chainID)
  if counterpartyRevisionNumber !== hostClientState.latestHeight.revisionNumber {
    return false
  }

  // assert that the counterparty client has a height less than the host height
  if counterpartyClientState.latestHeight >= hostClientState.latestHeight {
    return false
  }

  // assert that the counterparty client has the same ProofSpec as the host
  if counterpartyClientState.proofSpecs !== hostClientState.proofSpecs {
    return false
  }

  // assert that the trustLevel is within the allowed range. 1/3 is the minimum amount
  // of trust needed which does not break the security model.
  if counterpartyClientState.trustLevel < 1/3 || counterpartyClientState.trustLevel > 1 {
    return false
  }

  // assert that the unbonding periods are the same
  if counterpartyClientState.unbondingPeriod != hostClientState.unbondingPeriod {
    return false
  }

  // assert that the unbonding period is greater than or equal to the trusting period
  if counterpartyClientState.unbondingPeriod < counterpartyClientState.trustingPeriod {
    return false
  }

  // assert that the upgrade paths are the same
  hostUpgradePath = applyPrefix(hostClientState.upgradeCommitmentPrefix, hostClientState.upgradeKey)
  counterpartyUpgradePath = applyPrefix(counterpartyClientState.upgradeCommitmentPrefix, counterpartyClientState.upgradeKey)
  if counterpartyUpgradePath !== hostUpgradePath {
    return false
  }
  
  return true
}

Commitment path introspection

Host chains MUST provide the ability to inspect their commitment path, with getCommitmentPrefix:
type getCommitmentPrefix = () => CommitmentPrefix
The result CommitmentPrefix is the prefix used by the host state machine’s key-value store. With the CommitmentRoot root and CommitmentState state of the host state machine, the following property MUST be preserved:
if provableStore.get(path) === value {
  prefixedPath = applyPrefix(getCommitmentPrefix(), path)
  if value !== nil {
    proof = createMembershipProof(state, prefixedPath, value)
    assert(verifyMembership(root, proof, prefixedPath, value))
  } else {
    proof = createNonMembershipProof(state, prefixedPath)
    assert(verifyNonMembership(root, proof, prefixedPath))
  }
}
For a host state machine, the return value of getCommitmentPrefix MUST be constant.

Timestamp access

Host chains MUST provide a current Unix timestamp, accessible with currentTimestamp():
type currentTimestamp = () => uint64
In order for timestamps to be used safely in timeouts, timestamps in subsequent headers MUST be non-decreasing.

Port system

Host state machines MUST implement a port system, where the IBC handler can allow different modules in the host state machine to bind to uniquely named ports. Ports are identified by an Identifier. Host state machines MUST implement permission interaction with the IBC handler such that:
  • Once a module has bound to a port, no other modules can use that port until the module releases it
  • A single module can bind to multiple ports
  • Ports are allocated first-come first-serve and “reserved” ports for known modules can be bound when the state machine is first started
This permissioning can be implemented with unique references (object capabilities) for each port (a la the Cosmos SDK), with source authentication (a la Ethereum), or with some other method of access control, in any case enforced by the host state machine. See ICS 5 for details. Modules that wish to make use of particular IBC features MAY implement certain handler functions, e.g. to add additional logic to a channel handshake with an associated module on another state machine.

Datagram submission

Host state machines which implement the routing module MAY define a submitDatagram function to submit datagrams1, which will be included in transactions, directly to the routing module (defined in ICS 26):
type submitDatagram = (datagram: Datagram) => void
submitDatagram allows relayer processes to submit IBC datagrams directly to the routing module on the host state machine. Host state machines MAY require that the relayer process submitting the datagram has an account to pay transaction fees, signs over the datagram in a larger transaction structure, etc — submitDatagram MUST define & construct any such packaging required.

Exception system

Host state machines MUST support an exception system, whereby a transaction can abort execution and revert any previously made state changes (including state changes in other modules happening within the same transaction), excluding gas consumed & fee payments as appropriate, and a system invariant violation can halt the state machine. This exception system MUST be exposed through two functions: abortTransactionUnless and abortSystemUnless, where the former reverts the transaction and the latter halts the state machine.
type abortTransactionUnless = (bool) => void
If the boolean passed to abortTransactionUnless is true, the host state machine need not do anything. If the boolean passed to abortTransactionUnless is false, the host state machine MUST abort the transaction and revert any previously made state changes, excluding gas consumed & fee payments as appropriate.
type abortSystemUnless = (bool) => void
If the boolean passed to abortSystemUnless is true, the host state machine need not do anything. If the boolean passed to abortSystemUnless is false, the host state machine MUST halt.

Data availability

For deliver-or-timeout safety, host state machines MUST have eventual data availability, such that any key/value pairs in state can be eventually retrieved by relayers. For exactly-once safety, data availability is not required. For liveness of packet relay, host state machines MUST have bounded transactional liveness (and thus necessarily consensus liveness), such that incoming transactions are confirmed within a block height bound (in particular, less than the timeouts assign to the packets). IBC packet data, and other data which is not directly stored in the state vector but is relied upon by relayers, MUST be available to & efficiently computable by relayer processes. Light clients of particular consensus algorithms may have different and/or more strict data availability requirements.

Event logging system

The host state machine MUST provide an event logging system whereby arbitrary data can be logged in the course of transaction execution which can be stored, indexed, and later queried by processes executing the state machine. These event logs are utilised by relayers to read IBC packet data & timeouts, which are not stored directly in the chain state (as this storage is presumed to be expensive) but are instead committed to with a succinct cryptographic commitment (only the commitment is stored). This system is expected to have at minimum one function for emitting log entries and one function for querying past logs, approximately as follows. The function emitLogEntry can be called by the state machine during transaction execution to write a log entry:
type emitLogEntry = (topic: string, data: []byte) => void
The function queryByTopic can be called by an external process (such as a relayer) to retrieve all log entries associated with a given topic written by transactions which were executed at a given height.
type queryByTopic = (height: Height, topic: string) => []byte
More complex query functionality MAY also be supported, and may allow for more efficient relayer process queries, but is not required.

Handling upgrades

Host machines may safely upgrade parts of their state machine without disruption to IBC functionality. In order to do this safely, the IBC handler logic must remain compliant with the specification, and all IBC-related state (in both the provable & private stores) must be persisted across the upgrade. If clients exist for an upgrading chain on other chains, and the upgrade will change the light client validation algorithm, these clients must be informed prior to the upgrade so that they can safely switch atomically and preserve continuity of connections & channels.

Backwards Compatibility

Not applicable.

Forwards Compatibility

Key/value store functionality and consensus state type are unlikely to change during operation of a single host state machine. submitDatagram can change over time as relayers should be able to update their processes.

Example Implementations

History

Apr 29, 2019 - Initial draft May 11, 2019 - Rename “RootOfTrust” to “ConsensusState” Jun 25, 2019 - Use “ports” instead of module names Aug 18, 2019 - Revisions to module system, definitions Jul 05, 2022 - Lower the minimal allowed length of a channel identifier to 8 Jul 27, 2022 - Move ClientState to the provableStore, and add “Client state validation” section All content herein is licensed under Apache 2.0.
1: A datagram is an opaque bytestring transmitted over some physical network, and handled by the IBC routing module implemented in the ledger’s state machine. In some implementations, the datagram may be a field in a ledger-specific transaction or message data structure which also contains other information (e.g. a fee for spam prevention, nonce for replay prevention, type identifier to route to the IBC handler, etc.). All IBC sub-protocols (such as opening a connection, creating a channel, sending a packet) are defined in terms of sets of datagrams and protocols for handling them through the routing module.