Replies: 3 comments 31 replies
-
In reference to #10779 (comment), I want to write up why my thinking has changed from the base/core module design articulated in #9011 to small feature-focused modules like The advantages of smaller module scopes are:
Advantages of base/core:
If there are other strong reasons to go back to base/core, I'm willing to go back in that direction, but so far most of the advantages seem to be in the independent functional modules which do one thing well. If there are strong arguments in the other direction I'm not seeing, please post here. |
Beta Was this translation helpful? Give feedback.
-
>Root
|
Beta Was this translation helpful? Give feedback.
-
Issues with API Module ParadigmIn working with the API module paradigm for a few months in the SDK and at Regen, I've identified a few complex issues with it. Proto File VersioningFor one versioning of proto files vs the state machine is complicated. The idea with the This introduces a bit of a complex versioning scenario where we have a Also it requires state machines to solidify a v1 in a beta or alpha package and then a migration of 1) all the proto files to v1 and 2) all the state machine code to now reference v1 in two steps at the end of development. If we call Essentially this makes state machine development more complicated because it prematurely leaks WIP designs to client libraries. Pinned proto imagesAs is described in this refactoring proposal, we would need to pin proto images for state machines in the codec registry to ensure proper unknown field filtering. This isn't a deal breaker, but it is a bit complex for people to understand and requires both:
Complex refactoring of interfacesThis is already described quite a bit in #10368. It is also not a deal breaker, but it is a complex refactoring especially of Potential limitations to generated codeIn doing benchmarking of ORM generated code several potential optimizations have emerged and obviously the most performant optimization would be to generate as much code as possible. But because ORM table descriptors may evolve from one version to the next with new indexes and fields being added, this makes generated code state machine breaking. Recall that the API module paradigm requires no state machine breaking logic in generated code. AlternativesSkip global registry registration for internal codegenThe API module paradigm I believe is still a useful approach for client developers, but it may not be ideal to use the same API module for state machine implementations. One alternative is to generate all proto files needed for a state machine in an Since we control the code generator, an alternative is to skip global registration either with a protoc flag and/or by default when the generated code is in an This will require explicit registration of all generated code in the codec registry. But we are sort of already requiring that with the pinned proto images, but with this alternate approach we a) wouldn’t need a separate build step and b) generated code would align 100% with state machine versions as there would be different generated code per state machine version. Separate API module/buf schema registry module per go moduleThe issue related to prematurely leaking WIP versions of proto files to clients could be mitigated by:
OR
|
Beta Was this translation helpful? Give feedback.
-
This proposal attempts to address long-term technical debt in the SDK in order to make it more maintainable and developer friendly with three big refactoring goals which could be completed together or independently, listed in no particular order:
NOTE: These are big changes. We will need to think through the impact carefully. Comments, concerns, and alternate ideas welcome.
Goal: Basic Semantic Versioning
Protobuf API Module (
sdk.cosmos.network/api
)sdk.cosmos.network/api
will be created which contains just the generated protobuf code from the Cosmos SDKproto/
folder plus a few essential dependenciessdk.cosmos.network/api/cosmos/bank/v1
, etc. and existing SDK code will need to be refactored although we may choose to add type aliases to reduce the refactoring overheadgov.Content
andAccountI
)sdk.Msg
refactoringmath
anderrors
will be included in this API module or be dependencies of ittypes.Int
,types.Dec
andtypes/errors
can have aliases introduced to reduce refactoring complexityCoin
&DecCoin
will move to thecosmos.coin.v1
proto package[]*Coin
and[]*DecCoin
instead ofCoins
andDecCoins
because the new code generator doesn't allow for this sort of customizationCoins
andDecCoins
(i.eAddCoins(...*Coin)
)v1beta1
protobuf packages will be refactored to referencecosmos.coin.v1
- this is considered an acceptable breaking change because these types are binary compatible. If we don't do this we end up with two golangCoin
types which is undesirablesdk.cosmos.network/api
will be forever onv1.x.x
and breaking changes to existing code will be prohibited except in extenuating circumstancesSDK Refactoring and Versioning
Note: the API module above could be created first and used by clients and the SDK could be refactored second
codec
to be compatible with both gogo and the golang v2 APIs so that gogo and v2 types can be used in the same code-base and the migration can happen piece by piecesdk.cosmos.network/api
as described above, type aliases may be introduced to ease the transition if there is broad enough community demandx/staking
will require a bump fromv2
->v3
why force all other code which hasn't changed to change its import path?Pinning State Machine API Compatibility
One consequence of bundling protobuf types separate from state machine logic is how it affects API forwards compatibility. By default, protobuf as we're using it is forwards compatible - meaning that newer clients can talk to older state machines. This can cause problems, however, if fields are added to older messages and clients try to use these new fields against older state machines.
For example, say someone adds a field to a message to set an optional expiration time on some operation. If the newer client sends a message an older state machine with this new expiration field set, the state machine will reject it based on ADR 020 Unknown Field Filtering.
This will break down, however, if we package API types separately because an app developer may use an API version that's newer that the state machine version and then clients can send a message with an expiration time, the state machine will accept it but ignore it, which is a bug. This isn't a problem now because the protobuf types are codegen'ed directly into the state machine code so there can be no discrepancy.
If we migrate to an API module, we will need to pin the API compatibility version in the state machine. Here are two potential ways to do this:
"Since" Annotations
In the Protobuf package versioning discussion we agreed to take approach 2: annotations using "Since" comments to help clients deal with forwards compatibility gracefully by only enabling newer API features when they know they're talking to a newer chain. We could use these same annotations in state machines and instruct unknown field filtering to reject fields in newer API versions if they are present in protobuf generated code.
Embed raw proto files in state machines
This would involve using golang embed to force state machines to embed the proto files they intended to use in the binary and registering them with the
InterfaceRegistry
which will use them for unknown field filtering. By using theembed.FS
type we can force module developers to copy .proto files into the module path and update them when they want to support newer APIs. A warning could be emitted if an older API verison is registered and a newer one is available in generated code, alerting modular developers and/or apps to upgrade.This second approach is probably less fragile than the "Since" annotations because it could be easy to mess up API versions with annotations. While these annotations are a good solution for clients like CosmJs which want to support multiple chains, we need things to be a little stricter inside state machines.
Alternativate approach: go module per protobuf package
An alternate approach to
sdk.cosmos.network/api
would be to have a go module for each protobuf package. i.e.cosmos.bank.v1
would live in thegithub.com/cosmos/cosmos-sdk/x/bank/types
go modulePros of this approach are:
github.com/cosmos/cosmos-sdk/x/bank/types
rather thansdk.cosmos.network/api/cosmos/bank/v1
Cons:
cosmos.bank.v1
go to a go package ending inx/bank/types
?Goal: Modular SDK
Note: this work could happen before or after the semantic versioning refactor or at the same time
The goal of this work is to:
The key ways to accomplish this are:
app.yaml
/dependency injection (DI) app wiring design that has been designed for months and is basically ready to be implemented - this will allow modules to quickly spin-up "simapps" for testing without a massively bloated app.go file. Note: adding theapp.yaml
/DI module wiring can be done in a non-breaking way and is only blocked on resourcesHere are some packages which could possibly be refactored out to independent modules in the near-term:
types/errors
to a standaloneerrors
module so this can be a common dependency of other modulescodec
crypto
store
Goal: No Globals
Note: this should probably be done together with the semantic versioning refactor described above
There are a few globals in the
types/
package, namely:sdk.Config
which mainly contains bech32 prefixesbaseDenom
anddenomUnits
Plan:
baseDenom
anddenomUnits
should be refactored into bankMsg.GetSigners
and replace it with protobuf annotations (cosmos.msg.v1.signer
), ex:cosmos.msg.v1.signer
annotations inside tx middleware and signing logic and use theaddress.Codec
(implemented by the auth module) for conversion to/from bech32address.Codec
to all other modules which need itaddress.Codec
to all places where it is needed using theapp.yaml
/DI app wiring described above as modules will simply need to defineaddress.Codec
as a dependency and the DI container will resolve itBeta Was this translation helpful? Give feedback.
All reactions