变更记录
- 01-08-2022:初始草案
状态
已接受,并已应用于 ibc-go v7背景
在02-client 子模块的初始开发阶段,每个受支持的轻客户端(06-solomachine、07-tendermint、09-localhost)都是通过硬编码方式引用的。
下面是当时存在于 02-client 子模块中的代码示例:
02-client 子模块中添加代码。
显然,随着 IBC 扩展到更多采用初始已支持轻客户端之外共识机制的链,这种方式很可能会带来问题。
SDK 中的议题 #6064 通过创建一个更模块化的 02-client 子模块来解决这个问题。
此后,02-client 子模块将通过接口与各个轻客户端交互。
虽然这一变更对开发是积极的,提高了 IBC 的灵活性和可采纳性,但也为新的问题打开了大门。
一旦需要对这些轻客户端进行变更,轻客户端抽象通用化的困难就变得明显起来。
每个轻客户端都代表一种不同的共识算法,而这些算法可能包含大量复杂性和细微差异。
下面列举了一些已经出现的问题,这些问题并不适用于所有已支持的轻客户端(06-solomachine、07-tendermint、09-localhost):
Tendermint 非零高度升级
在 IBC 发布之前,已经确定 tendermint 的 golang 实现无法支持非零高度升级。 这意味着任何升级都需要变更 chain ID,并将高度重置为 0。 一条链由其 chain-id 和验证者集合唯一标识。 两个不同的 chain ID 可以被视为两条不同的链,因此由验证者集合产生的常规更新不能改变 chain ID。 为了绕过不支持非零高度升级的问题,引入了一个抽象高度类型以及一套升级机制。 该类型会标识 revision number(chain ID 被变更的次数)和 revision height(区块链当前高度)。 参考:Tendermint 在更新期间需要进行 misbehaviour 检测
IBC 模块和07-tendermint 轻客户端实现的初始版本,既不支持在更新期间进行 misbehaviour 检测,也不阻止覆盖先前的更新。
尽管我们设计了 ClientState 接口并开发了 07-tendermint 客户端,但我们甚至未能检测出构成 misbehaviour 的重复更新,而这种情况本应冻结客户端。
这个问题在 PR #141 中得到修复;该修复要求轻客户端实现必须意识到它们需要处理重复更新和 misbehaviour 检测。
更新期间的 misbehaviour 检测并不适用于 solomachine 或 localhost。
而且,CheckHeaderAndUpdateState 需要承担这一职责也并不直观。
Localhost 需要访问整个 client store
localhost 自 IBC 模块最初版本以来一直是损坏状态。 localhost 曾尝试在不做特殊例外的前提下构建在02-client 接口之下,但事实证明这不可能实现。
相关问题在 #27 中进行了说明,并在 #75 中尝试的 ADR 里有进一步讨论。
与其他所有客户端不同,localhost 需要访问整个 IBC store,而不仅仅是带前缀的 client store。
Solomachine 不设置 consensus state
06-solomachine 不会在带前缀的 client store 中设置 consensus state。
它只有一个 consensus state,并且该状态存储在 client state 内部。
这导致在 02-client 层设置 consensus state 会使用不必要的存储空间。
这也会导致超时处理在 solo machine 上失败。
此前,IBC 中的超时逻辑会获取被证明超时的那个高度上的 consensus state。
对于 solo machine 来说,这会带来问题,因为根本没有设置 consensus state。
参见 IBC 规范仓库中的议题 #562。
新客户端可能希望执行批量更新
新的轻客户端未必会以与06-solomachine 和 07-tendermint 相似的方式工作。
它们可能需要在一次更新中设置多个 consensus state。
正如 @seunlanlege 在这里所说:
我支持这些变更,原因有两点:
- 这将允许轻客户端在
CheckHeaderAndUpdateState中处理批量 header 更新。对于11-beefy的特殊场景,为一批 headers 证明终局性,在空间和时间上都比逐个证明该批次中的每个 header 更高效。- 这也允许单个
11-beefy轻客户端实例为连接到中继链(Polkadot/Kusama)的每条平行链证明终局性。我们通过在CheckHeaderAndUpdateState中为各条平行链的 header 设置相应的ConsensusState来实现这一点。
决策
要求轻客户端自行设置 client state 和 consensus state
IBC 规范指出:如果提供的 header 有效,客户端还必须变更内部状态,以存储现已终局化的共识根,并更新未来调用有效性谓词所需的任何签名授权跟踪信息(例如验证者集合的变更)。IBC Go SDK 模块的初始版本并未满足这一要求。 相反,
02-client 子模块要求每个轻客户端返回应写入 client 前缀 store 的 client state 和 consensus state。
这一决策导致了“Solomachine 不设置 consensus state”和“新客户端可能希望执行批量更新”等问题。
应要求每个轻客户端在任何必要的更新中,自行设置其 client state 和 consensus state。
Go 实现应调整为与规范要求保持一致。
这将使轻客户端在管理自身内部存储和执行批量更新方面拥有更高灵活性。
合并 Header/Misbehaviour 接口并重命名为 ClientMessage
从 header 接口中移除 GetHeight()(因为现在由轻客户端自行设置 client/consensus state)。
这样一来,Header/Misbehaviour 接口就变得相同了。
为了降低代码库复杂度,应将 Header/Misbehaviour 接口合并为 ClientMessage。
ClientMessage 将向客户端提供某些经过认证的信息,这些信息可能引发常规更新、misbehaviour 检测、批量更新,或轻客户端所需的其他自定义功能。
将 CheckHeaderAndUpdateState 拆分为 4 个函数
参见 #668。
将 CheckHeaderAndUpdateState 拆分为 4 个函数:
VerifyClientMessageCheckForMisbehaviourUpdateStateOnMisbehaviourUpdateState
VerifyClientMessage 用于检查 ClientMessage 的结构是否正确,以及所提供的所有认证数据是否有效。
CheckForMisbehaviour 用于检查 ClientMessage 是否构成 misbehaviour 证据。
UpdateStateOnMisbehaviour 用于冻结客户端并相应更新其状态。
UpdateState 执行常规更新,或在重复更新时不执行任何操作。
代码大致如下:
向 client state 接口添加 GetTimestampAtHeight
通过向 ClientState 接口添加 GetTimestampAtHeight,我们允许那些采用非传统 consensus state/timestamp 存储方式的轻客户端正确处理超时。
这修复了前文中 solo machine 客户端的问题。
添加通用验证函数
随着复杂度和功能不断增长,将来需要为额外路径引入新的验证函数。 这一点已在规范仓库中的 #684 中说明。 这些通用验证函数将立即对 connection/channel 可升级性中新增加的路径有用,也适用于 IBC 应用定义的自定义路径,例如 Interchain Queries。 旧的验证函数(VerifyClientState、VerifyConnection 等)应被移除,转而采用通用验证函数。
影响
正面
- 为轻客户端实现提供更高灵活性
- 接口及其所需功能定义更加清晰
- 通用验证函数
- 应用了未来 client/connection/channel 可升级功能所需的变更
- solo machine 的超时处理
- 降低代码复杂度
负面
- 此次重构触及 ibc-go 代码库中的敏感区域
- 变更既有命名(
Header/Misbehaviour改为ClientMessage)
中性
没有明显影响参考
议题: PR:Changelog
- 01-08-2022: Initial Draft
Status
Accepted and applied in v7 of ibc-goContext
During the initial development of the 02-client submodule, each light client supported (06-solomachine, 07-tendermint, 09-localhost) was referenced through hardcoding. Here is an example of the code that existed in the 02-client submodule:Tendermint non-zero height upgrades
Before the launch of IBC, it was determined that the golang implementation of tendermint would not be capable of supporting non-zero height upgrades. This implies that any upgrade would require changing of the chain ID and resetting the height to 0. A chain is uniquely identified by its chain-id and validator set. Two different chain ID’s can be viewed as different chains and thus a normal update produced by a validator set cannot change the chain ID. To work around the lack of support for non-zero height upgrades, an abstract height type was created along with an upgrade mechanism. This type would indicate the revision number (the number of times the chain ID has been changed) and revision height (the current height of the blockchain). Refs:- Issue #439 on IBC specification repository.
- Specification changes in #447
- Implementation changes for the abstract height type, SDK#7211
Tendermint requires misbehaviour detection during updates
The initial release of the IBC module and the 07-tendermint light client implementation did not support misbehaviour detection during update nor did it prevent overwriting of previous updates. Despite the fact that we designed theClientState interface and developed the 07-tendermint client, we failed to detect even a duplicate update that constituted misbehaviour and thus should freeze the client.
This was fixed in PR #141 which required light client implementations to be aware that they must handle duplicate updates and misbehaviour detection.
Misbehaviour detection during updates is not applicable to the solomachine nor localhost.
It is also not obvious that CheckHeaderAndUpdateState should be performing this functionality.
Localhost requires access to the entire client store
The localhost has been broken since the initial version of the IBC module. The localhost tried to be developed underneath the 02-client interfaces without special exception, but this proved to be impossible. The issues were outlined in #27 and further discussed in the attempted ADR in #75. Unlike all other clients, the localhost requires access to the entire IBC store and not just the prefixed client store.Solomachine doesn’t set consensus states
The 06-solomachine does not set the consensus states within the prefixed client store. It has a single consensus state that is stored within the client state. This causes setting of the consensus state at the 02-client level to use unnecessary storage. It also causes timeouts to fail with solo machines. Previously, the timeout logic within IBC would obtain the consensus state at the height a timeout is being proved. This is problematic for the solo machine as no consensus state is set. See issue #562 on the IBC specification repo.New clients may want to do batch updates
New light clients may not function in a similar fashion to 06-solomachine and 07-tendermint. They may require setting many consensus states in a single update. As @seunlanlege states:I’m in support of these changes for 2 reasons:
- This would allow light clients to handle batch header updates in CheckHeaderAndUpdateState, for the special case of 11-beefy proving the finality for a batch of headers is much more space and time efficient than the space/time complexity of proving each individual headers in that batch, combined.
- This also allows for a single light client instance of 11-beefy be used to prove finality for every parachain connected to the relay chain (Polkadot/Kusama). We achieve this by setting the appropriate ConsensusState for individual parachain headers in CheckHeaderAndUpdateState
Decision
Require light clients to set client and consensus states
The IBC specification states:If the provided header was valid, the client MUST also mutate internal state to store now-finalised consensus roots and update any necessary signature authority tracking (e.g. changes to the validator set) for future calls to the validity predicate.The initial version of the IBC go SDK based module did not fulfill this requirement. Instead, the 02-client submodule required each light client to return the client and consensus state which should be updated in the client prefixed store. This decision lead to the issues “Solomachine doesn’t set consensus states” and “New clients may want to do batch updates”. Each light client should be required to set its own client and consensus states on any update necessary. The go implementation should be changed to match the specification requirements. This will allow more flexibility for light clients to manage their own internal storage and do batch updates.
Merge Header/Misbehaviour interface and rename to ClientMessage
Remove GetHeight() from the header interface (as light clients now set the client/consensus states).
This results in the Header/Misbehaviour interfaces being the same.
To reduce complexity of the codebase, the Header/Misbehaviour interfaces should be merged into ClientMessage.
ClientMessage will provide the client with some authenticated information which may result in regular updates, misbehaviour detection, batch updates, or other custom functionality a light client requires.
Split CheckHeaderAndUpdateState into 4 functions
See #668.
Split CheckHeaderAndUpdateState into 4 functions:
VerifyClientMessageCheckForMisbehaviourUpdateStateOnMisbehaviourUpdateState
VerifyClientMessage checks the that the structure of a ClientMessage is correct and that all authentication data provided is valid.
CheckForMisbehaviour checks to see if a ClientMessage is evidence of misbehaviour.
UpdateStateOnMisbehaviour freezes the client and updates its state accordingly.
UpdateState performs a regular update or a no-op on duplicate updates.
The code roughly looks like:
Add GetTimestampAtHeight to the client state interface
By adding GetTimestampAtHeight to the ClientState interface, we allow light clients which do non-traditional consensus state/timestamp storage to process timeouts correctly.
This fixes the issues outlined for the solo machine client.
Add generic verification functions
As the complexity and the functionality grows, new verification functions will be required for additional paths. This was explained in #684 on the specification repo. These generic verification functions would be immediately useful for the new paths added in connection/channel upgradability as well as for custom paths defined by IBC applications such as Interchain Queries. The old verification functions (VerifyClientState, VerifyConnection, etc) should be removed in favor of the generic verification functions.
Consequences
Positive
- Flexibility for light client implementations
- Well defined interfaces and their required functionality
- Generic verification functions
- Applies changes necessary for future client/connection/channel upgrabability features
- Timeout processing for solo machines
- Reduced code complexity
Negative
- The refactor touches on sensitive areas of the ibc-go codebase
- Changing of established naming (
Header/MisbehaviourtoClientMessage)