变更记录

  • 05-01-2022:初始草稿

状态

已接受

背景

IBC 模块最初在 Cosmos SDK 中开发,并在 Stargate 发布系列(v0.42)期间发布。 随后,它被迁移到了独立仓库 ibc-go。 ibc-go 上的首个正式版本是 v1.0.0。 之所以决定使用 v1.0.0 而不是 v0.1.0,主要基于以下原因:
  • 维护与 IBC 规范 v1 的兼容性,需要更强的支持与保证。
  • 使用主版本号、次版本号和补丁版本号,可以更容易地传达某个版本中包含了哪些破坏性变更。
  • IBC 模块已被许多高价值项目使用,这些项目需要稳定性。

问题

Go 模块版本必须递增

当一个 Go 模块以 v1.0.0 发布后,后续所有版本都必须遵循 Go 语义化版本规范。 因此,当 Go API 发生破坏性变更时,Go 模块主版本号必须递增。 例如,将 Go 包版本从 v2 改为 v3,会使导入路径从 github.com/cosmos/ibc-go/v2 变为 github.com/cosmos/ibc-go/v3。 如果 Go 模块版本未递增,那么在没有后缀的情况下尝试 go get 一个 @v3.0.0 的模块时,会得到: invalid version: module contains a go.mod file, so major version must be compatible: should be v0 or v1, not v3 版本校验是在 Go 1.13 中加入的。这意味着,为了发布一个在模块定义中不带 /v3 后缀的 v3.0.0 git tag,该 tag 必须明确不包含 go.mod 文件。 在我们的发布中不包含 go.mod 并不是一个可行的选项。

尝试为 ibc-go 导入多个 Go 模块版本

尝试同时导入两个版本的 ibc-go,例如 github.com/cosmos/ibc-go/v2 和 github.com/cosmos/ibc-go/v3,会导致多个问题。 Cosmos SDK 会对错误类型和治理提案类型进行全局注册。 ibc-go 中使用的错误和提案现在将需要基于 Go 模块版本来注册其命名。 更令人担忧的问题是,protobuf 定义也会发生命名空间冲突。 ibc-go 以及更广泛的 Cosmos SDK 都高度依赖于为 protobuf 定义生成的 Go 结构体提供扩展函数。 这要求 Go 结构体必须定义在与扩展函数相同的包中。 因此,提升导入版本会导致 protobuf 定义在两个位置生成(分别在 v2 和 v3 中)。 在编译期注册这些类型时,Go 编译器将会 panic。 生成的类型需要注册到 proto codec 上,但同一个名称存在两个定义。 可以通过环境变量 GOLANG_PROTOBUF_REGISTRATION_CONFLICT 覆盖 protobuf 冲突策略,但这可能导致各种运行时错误或意外行为(见这里)。 关于 protobuf 版本化中的命名空间冲突,可参见更多信息见这里。

潜在解决方案

修改 protobuf 定义版本

protobuf 定义的类型 URL 中都包含该类型对应的 protobuf 版本。 修改 protobuf 版本可以解决因导入多个 ibc-go 版本而产生的命名空间冲突,但这也会带来新的问题。 在 Cosmos SDK 中,Any 会通过类型 URL 进行解包和解码。 因此,修改类型 URL 就是在创建一个截然不同的新类型。 原先在 proto codec 上的注册将无法用于解包该新类型。 例如: 所有 Cosmos SDK 消息都会被打包到 Any 中。如果我们为 IBC 消息递增 protobuf 版本,那么提交 v1 版 Cosmos SDK 消息的客户端现在会被拒绝,因为旧类型未注册到 codec 上。 客户端必须知道应提交这些消息的 v2 版本。这将把版本管理的负担转移到 relayer 和钱包上。 更严重的问题是,ClientState 和 ConsensusState 也是以 Any 形式打包的。修改这些类型的 protobuf 版本将破坏与 IBC 规范 v1 的兼容性。

将 protobuf 定义移动到独立的 Go 模块

可以将 protobuf 定义迁移到独立的 Go 模块中,并使用 0.x 版本方案且永远不进入 1.0。 这可以防止 Go 模块版本因破坏性变更而递增。 但这也要求所有扩展函数都必须存在于同一个 Go 模块中,从而破坏现有代码结构。 实现这一变更的版本仍然会与之前版本不兼容,但未来版本可以一起导入而不发生命名空间冲突。 例如,假设该方案在 v3 中实现,那么: github.com/cosmos/ibc-go/v2 不能与任何其他 ibc-go 版本一起导入 github.com/cosmos/ibc-go/v3 不能与任何之前的 ibc-go 版本一起导入 github.com/cosmos/ibc-go/v4 可以与 ibc-go v3+ 版本一起导入 github.com/cosmos/ibc-go/v5 可以与 ibc-go v3+ 版本一起导入

决策

支持同时导入多个 ibc-go 版本需要引入相当复杂的机制。 目前尚不清楚 ibc-go 代码的使用者在什么情况下会需要多个 ibc-go 版本。 在没有压倒性理由支持同时导入多个 ibc-go 版本之前: 主版本发布不能同时导入。 发布版本应在合理范围内重点保持对 Go 代码客户端的向后兼容性。 旧功能应标记为已弃用,并且应存在主版本之间的升级路径。 当没有客户端再依赖某项已弃用功能时,可以将其移除。 如何判断这一点,仍有待决定。 错误类型和提案类型的注册不会随着 Go 模块版本递增而改变。 这明确阻止外部客户端尝试导入两个主版本(从而避免因 proto 名称冲突覆盖机制不稳定而潜在引发 bug 的风险)。

影响

这只会影响直接依赖 Go 代码的客户端。

正面

负面

无法导入多个 ibc-go 版本。

中性


Changelog

  • 05-01-2022: initial draft

Status

Accepted

Context

The IBC module was originally developed in the Cosmos SDK and released during the Stargate release series (v0.42). It was subsequently migrated to its own repository, ibc-go. The first official release on ibc-go was v1.0.0. v1.0.0 was decided to be used instead of v0.1.0 primarily for the following reasons:
  • Maintaining compatibility with the IBC specification v1 requires stronger support/guarantees.
  • Using the major, minor, and patch numbers allows for easier communication of what breaking changes are included in a release.
  • The IBC module is being used by numerous high value projects which require stability.

Problems

Go module version must be incremented

When a Go module is released under v1.0.0, all following releases must follow Go semantic versioning. Thus when the go API is broken, the Go module major version must be incremented. For example, changing the go package version from v2 to v3 bumps the import from github.com/cosmos/ibc-go/v2 to github.com/cosmos/ibc-go/v3. If the Go module version is not incremented then attempting to go get a module @v3.0.0 without the suffix results in: invalid version: module contains a go.mod file, so major version must be compatible: should be v0 or v1, not v3 Version validation was added in Go 1.13. This means that in order to release a v3.0.0 git tag without a /v3 suffix on the module definition, the tag must explicitly not contain a go.mod file. Not including a go.mod in our release is not a viable option.

Attempting to import multiple go module versions for ibc-go

Attempting to import two versions of ibc-go, such as github.com/cosmos/ibc-go/v2 and github.com/cosmos/ibc-go/v3, will result in multiple issues. The Cosmos SDK does global registration of error and governance proposal types. The errors and proposals used in ibc-go would need to now register their naming based on the go module version. The more concerning problem is that protobuf definitions will also reach a namespace collision. ibc-go and the Cosmos SDK in general rely heavily on using extended functions for go structs generated from protobuf definitions. This requires the go structs to be defined in the same package as the extended functions. Thus, bumping the import versioning causes the protobuf definitions to be generated in two places (in v2 and v3). When registering these types at compile time, the go compiler will panic. The generated types need to be registered against the proto codec, but there exist two definitions for the same name. The protobuf conflict policy can be overridden via the environment variable GOLANG_PROTOBUF_REGISTRATION_CONFLICT, but it is possible this could lead to various runtime errors or unexpected behaviour (see here). More information here on namespace conflicts for protobuf versioning.

Potential solutions

Changing the protobuf definition version

The protobuf definitions all have a type URL containing the protobuf version for this type. Changing the protobuf version would solve the namespace collision which arise from importing multiple versions of ibc-go, but it leads to new issues. In the Cosmos SDK, Anys are unpacked and decoded using the type URL. Changing the type URL thus is creating a distinctly different type. The same registration on the proto codec cannot be used to unpack the new type. For example: All Cosmos SDK messages are packed into Anys. If we incremented the protobuf version for our IBC messages, clients which submitted the v1 of our Cosmos SDK messages would now be rejected since the old type is not registered on the codec. The clients must know to submit the v2 of these messages. This pushes the burden of versioning onto relayers and wallets. A more serious problem is that the ClientState and ConsensusState are packed as Anys. Changing the protobuf versioning of these types would break compatibility with IBC specification v1.

Moving protobuf definitions to their own go module

The protobuf definitions could be moved to their own go module which uses 0.x versioning and will never go to 1.0. This prevents the Go module version from being incremented with breaking changes. It also requires all extended functions to live in the same Go module, disrupting the existing code structure. The version that implements this change will still be incompatible with previous versions, but future versions could be imported together without namespace collisions. For example, let’s say this solution is implemented in v3. Then github.com/cosmos/ibc-go/v2 cannot be imported with any other ibc-go version github.com/cosmos/ibc-go/v3 cannot be imported with any previous ibc-go versions github.com/cosmos/ibc-go/v4 may be imported with ibc-go versions v3+ github.com/cosmos/ibc-go/v5 may be imported with ibc-go versions v3+

Decision

Supporting importing multiple versions of ibc-go requires a non-trivial amount of complexity. It is unclear when a user of the ibc-go code would need multiple versions of ibc-go. Until there is an overwhelming reason to support importing multiple versions of ibc-go: Major releases cannot be imported simultaneously. Releases should focus on keeping backwards compatibility for go code clients, within reason. Old functionality should be marked as deprecated and there should exist upgrade paths between major versions. Deprecated functionality may be removed when no clients rely on that functionality. How this is determined is to be decided. Error and proposal type registration will not be changed between go module version increments. This explicitly stops external clients from trying to import two major versions (potentially risking a bug due to the instability of proto name collisions override).

Consequences

This only affects clients relying directly on the go code.

Positive

Negative

Multiple ibc-go versions cannot be imported.

Neutral