Replies: 10 comments 15 replies
-
I support this approach, interfaces are the only amino left-over at codec level and they're not protobuf first class citizens, we have have discussed and trialed different approaches but they all ended up in clunky or unsafe APIs which were unfitting for the Considering the usage of interfaces in the sdk, and considering that most of use-cases could be solved through handlers (pub key being the only real interface), the effort required to create and maintain a custom API and code generator does not seem to be justified for the little benefits it brings. On top of this handlers allow more customisation through extension or overriding. |
Beta Was this translation helpful? Give feedback.
-
Huge fan of this proposal. It should help a lot in ergonomics for client developers. |
Beta Was this translation helpful? Give feedback.
-
These look great, a nice step in the Cosmos SDK. |
Beta Was this translation helpful? Give feedback.
-
Few questions:
|
Beta Was this translation helpful? Give feedback.
-
Alternative: embedded implementers.During the last week architecture call I had the following idea: type MsgA struct {
// proto attributes
Att1 string
// "functional" attributes, won't be serialized:
// we inject the functionality:
msgImplementation func(*MsgA) sdk.Msg
// or, we inject a wrapper:
msgImplementer sdk.Msg
// or through anonymous field:
sdk.Msg // eg MsgA.Msg = MsgAWrapper{*Msg}
} I think the last approach may have the most potential. // MsgSend represents a message to send coins from one account to another.
message MsgSend {
string from_address = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"];
string to_address = 2 [(cosmos_proto.scalar) = "cosmos.AddressString"];
repeated cosmos.base.v1beta1.Coin amount = 3
[(gogoproto.nullable) = false, (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins"];
cosmos.Implementer Msg [(cosmos_proto.implementer = "sdk.Msg")]
} this will generate: type MsgSend struct {
FromAddress string
ToAddress string
Amount github_com_cosmos_cosmos_sdk_types.Coins
sdk.Msg
} And then in the module wiring we will need to have a function (or a provider for DI): func (m BankModule) WireImplementers(r wiring.ImplementerRouter) {
r.Add(proto.MessageName((nil)(&proto.MsgSend), (nil)(&sdk.Msg), newMsgSendMsg)
}
// creates a wrapper for MsgSend to implementing sdk.Msg
func newMsgSendMsg(m *proto.MsgSend) sdk.Msg {
return msgMsgSend{m}
}
type msgMsgSend struct {
m *proto.MsgSend
}
func (m msgMsgSend) GetSigners() []sdk.AccAddress {
fromAddress, _ := sdk.AccAddressFromBech32(m.m.FromAddress)
return []sdk.AccAddress{fromAddress}
} Pros & ConsRelated to the current implementation
|
Beta Was this translation helpful? Give feedback.
-
Alternative: embedded functional implementers .Similar to the above, but instead having an interface type in the proto, we will have a functional type. I will use same example as above: message MsgSend {
string from_address = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"];
string to_address = 2 [(cosmos_proto.scalar) = "cosmos.AddressString"];
repeated cosmos.base.v1beta1.Coin amount = 3
[(gogoproto.nullable) = false, (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins"];
cosmos.Implementer ValidateBasic [(cosmos_proto.implementer = "wiring.ValidateBasic")]
// we can do same with getSigners, but it could have a default implementation as outlined in the Aaron original idea.
// cosmos.Implementer GetSingers [(cosmos_proto.implementer = "wiring.GetSingers")]
} this will generate: import goproto "...google/proto"
type MsgSend struct {
FromAddress string
ToAddress string
Amount github_com_cosmos_cosmos_sdk_types.Coins
ValidateBasic wiring.ValidateBasic // type: func(goproto.Message) error
} We will need a wiring router as above.... func MsgSendValidateBasic(m goproto.Message) error {
m, ok := m.(*proto.MsgSend)
if !ok { return errors.WrongType(...) }
// validate m
} |
Beta Was this translation helpful? Give feedback.
-
Here is one proposal for how interface handlers could be wired up generically using a "plugin host" mechanism: https://github.com/cosmos/cosmos-sdk/pull/10452/files. Basically this plugin host would allow for:
There are other ways this could be done for sure but this is one fairly generic, reusable approach. |
Beta Was this translation helpful? Give feedback.
-
I had previously said that Due to the way that the |
Beta Was this translation helpful? Give feedback.
-
How can we move forward with the removal of interface encoding here? Could someone open an issue/epic on the discussion with potential work phases. Even if we need to do an adr |
Beta Was this translation helpful? Give feedback.
-
Regarding Interfaces I found some in the osmosis codebase that are using registry.RegisterInterface(
"osmosis.swaprouter.v1beta1.PoolI",
(*swaproutertypes.PoolI)(nil),
&Pool{},
)
registry.RegisterInterface(
"osmosis.gamm.v1beta1.PoolI", // N.B.: the old proto-path is preserved for backwards-compatibility.
(*types.CFMMPoolI)(nil),
&Pool{},
) but for implements/accepts annotations, it's not scoped: (cosmos_proto.implements_interface) = "PoolI" or (cosmos_proto.accepts_interface) = "PoolI" Is the I ask because we’re potentially going to create conventions in the SDK-JS group that could impact this, so I’d like to better understand how it’s used |
Beta Was this translation helpful? Give feedback.
-
Recently we have made substantial progress on a code generator (https://github.com/cosmos/cosmos-proto) for the new google.golang.org/protobuf API. We decided it was time for gogo proto to go and that the golang community is by and large migrating to the new API. So far we have implemented fast marshaling and fast reflection and we are planning to support custom scalar types (in particular
sdk.Int
andsdk.Dec
).We have also discussed what to do with protobuf
Any
s as the currentUnpackInterfacesMessage
approach is rather cumbersome but needed for amino compatibility. The initial idea was to simply generate structs that had interface (i.e.sdk.Msg
,auth.AccountI
) fields rather thanAny
s and to handle the marshaling behind the scenes. We worked on a few designs such as this one proposed by @fdymylja which generates wrapper types and uses protobuf services: https://github.com/cosmos/cosmos-proto/pull/26/files.Problems with interfaces on proto types
In our discussions, however, we began to question the value of defining golang interfaces directly on protobuf types at all and identified the following weaknesses with this strategy:
Alternative approach
So our current thinking is that we will actually not support interfaces with the new code generator any more than we already do with the clunky
UnpackInterfacesMessage
. Instead, we are considering deprecating interface usage on generated types generally within the SDK in favor of:gov.Content
andEvidence
Msg
, orValidateBasic
consideringValidateBasic
as a special extension ofproto.Message
that can be used in various scenarios where objects are routed to handlers or even serialized with the ORMRefactoring of Existing Interfaces
Here’s how this would look for the various interface types used in the SDK:
sdk.Msg
GetSigners
would move to metadata in .proto files so that it is also obvious to clients, ex:ValidateBasic
could be handled declaratively in .proto filesValidateBasic
would be handled with a handler (seePubKey
below for more details on handlers)gov.Content
This is all going away anyway in favor of
sdk.Msg
(#9810)PubKey
I propose having a resolver which returns different
PubKey
interface implementations for each protobuf pub key type. The pub key implementations would be registered through constructor functions which take the protobuf type and return the interface type. Ex:This will free different chains to use different crypto libraries or different address implementations as desired (although different concrete proto types may be a better alternative to changing the address format).
auth.AccountI
This is a weird case where the interface has a number of complected concerns.
Address
,PubKey
,AccountNumber
, andSequence
are shared by all implementations so they maybe don’t even really belong in the interface but rather should move up to some concrete type. Then we have module accounts which are getting redesigned in accordance with ADR-028 and ADR-033, and vesting accounts which also are long overdue for a revamp and shouldn’t really be connected to authentication at all. So there is some refactoring that is long overdue here and likely a short-term solution is a handler approach as described above forPubKey
, but a more involved refactor may make sense here.Evidence
,authz.Authorization
,feegrant.FeeAllowanceI
These would be handled similarly to
sdk.Msg
with the routing stuff coming from the proto message name,ValidateBasic
being a separate method, and the things moving to handlers.Roadmap
In the short-term this approach would allow us to make the code generator mostly a drop in replacement for gogo proto because we would support the same
UnpackInterfacesMessage
based code and basically the sameCodec
API. In the mid-term, doing the interface refactorings above will allow us to move generated proto code into isolated semantically-versioned go modules without needing to worry about tight coupling of implementation logic there and all the dependencies that would bring.We’d appreciate any thoughts on this approach as getting alignment on this is crucial to moving the code generator project along and doing some necessary clean-up of the SDK.
Beta Was this translation helpful? Give feedback.
All reactions