Synopsis
While encoding in the Cosmos SDK used to be mainly handled by the
go-amino codec, the Cosmos SDK is moving toward using gogoprotobuf for both state and client-side encoding.Prerequisite Readings
Encoding
The Cosmos SDK utilizes two binary wire encoding protocols: Amino, which is an object encoding specification, and Protocol Buffers, a subset of Proto3 with an extension for interface support. See the Proto3 spec for more information on Proto3, which Amino is largely compatible with (but not with Proto2). Due to Amino having significant performance drawbacks, being reflection-based, and not having any meaningful cross-language/client support, Protocol Buffers, specifically gogoprotobuf, is being used in place of Amino. Note that this process of using Protocol Buffers over Amino is still ongoing. Binary wire encoding of types in the Cosmos SDK can be broken down into two main categories: client encoding and store encoding. Client encoding mainly revolves around transaction processing and signing, whereas store encoding revolves around types used in state-machine transitions and what is ultimately stored in the Merkle tree. For store encoding, protobuf definitions can exist for any type and will typically have an Amino-based “intermediary” type. Specifically, the protobuf-based type definition is used for serialization and persistence, whereas the Amino-based type is used for business logic in the state-machine where they may convert back and forth. Note that the Amino-based types may slowly be phased out in the future, so developers should take note to use the protobuf message definitions where possible. In thecodec package, there exist two core interfaces, BinaryCodec and JSONCodec, where the former encapsulates the current Amino interface except it operates on types implementing the latter instead of generic interface{} types.
The ProtoCodec handles both binary and JSON serialization via Protobuf. This means that modules may use Protobuf encoding, but the types must implement ProtoMarshaler. If modules wish to avoid implementing this interface for their types, it can be autogenerated via buf.
If modules use Collections, encoding and decoding are handled automatically; marshal and unmarshal should not be handled manually unless for specific cases identified by the developer.
Gogoproto
Modules are encouraged to utilize Protobuf encoding for their respective types. In the Cosmos SDK, we use the Gogoproto specific implementation of the Protobuf spec that offers speed and developer experience improvements compared to the official Google protobuf implementation.Guidelines for protobuf message definitions
In addition to following official Protocol Buffer guidelines, we recommend using these annotations in.proto files when dealing with interfaces:
- Use
cosmos_proto.accepts_interfaceto annotateAnyfields that accept interfaces:- Pass the same fully qualified name as
protoNametoInterfaceRegistry.RegisterInterface. - Example:
(cosmos_proto.accepts_interface) = "cosmos.gov.v1beta1.Content"(not justContent).
- Pass the same fully qualified name as
- Annotate interface implementations with
cosmos_proto.implements_interface:- Pass the same fully qualified name as
protoNametoInterfaceRegistry.RegisterInterface. - Example:
(cosmos_proto.implements_interface) = "cosmos.authz.v1beta1.Authorization"(not justAuthorization).
- Pass the same fully qualified name as
accepts_interface and implements_interface annotations to determine whether some Protobuf messages are allowed to be packed in a given Any field.
Transaction Encoding
Another important use of Protobuf is the encoding and decoding of transactions. Transactions are defined by the application or the Cosmos SDK but are then passed to the underlying consensus engine to be relayed to other peers. Since the underlying consensus engine is agnostic to the application, the consensus engine accepts only transactions in the form of raw bytes.- The
TxEncoderobject performs the encoding. - The
TxDecoderobject performs the decoding.
auth/tx module:
Interface Encoding and Usage of Any
The Protobuf DSL is strongly typed, which can make inserting variable-typed fields difficult. Imagine we want to create a Profile protobuf message that serves as a wrapper over an account:
Profile example, we hardcoded account as a BaseAccount. However, there are several other types of user accounts related to vesting, such as BaseVestingAccount or ContinuousVestingAccount. All of these accounts are different, but they all implement the AccountI interface. How would you create a Profile that allows all these types of accounts with an account field that accepts an AccountI interface?
Any to encode interfaces in protobuf. An Any contains an arbitrary serialized message as bytes, along with a URL that acts as a globally unique identifier for and resolves to that message’s type. This strategy allows us to pack arbitrary Go types inside protobuf messages. Our new Profile then looks like:
Any first, using codectypes.NewAnyWithValue:
Any and 2) marshal the Any. For convenience, the Cosmos SDK provides a MarshalInterface method to bundle these two steps. Have a look at a real-life example in the x/auth module.
The reverse operation of retrieving the concrete Go type from inside an Any, called “unpacking,” is done with the GetCachedValue() method on Any.
GetCachedValue() to work, Profile (and any other structs embedding Profile) must implement the UnpackInterfaces method:
UnpackInterfaces method gets called recursively on all structs implementing this method, to allow all Anys to have their GetCachedValue() correctly populated.
For more information about interface encoding, and especially on UnpackInterfaces and how the Any’s type_url gets resolved using the InterfaceRegistry, please refer to ADR-019.
Any Encoding in the Cosmos SDK
The above Profile example is a fictitious example used for educational purposes. In the Cosmos SDK, we use Any encoding in several places (non-exhaustive list):
- the
cryptotypes.PubKeyinterface for encoding different types of public keys, - the
sdk.Msginterface for encoding differentMsgs in a transaction, - the
AccountIinterface for encoding different types of accounts (similar to the above example) in the x/auth query responses, - the
EvidenceIinterface for encoding different types of evidence in the x/evidence module, - the
AuthorizationIinterface for encoding different types of x/authz authorizations, - the
Validatorstruct that contains information about a validator.
Any inside the Validator struct in x/staking is shown in the following example:
Any’s TypeURL
When packing a protobuf message inside an Any, the message’s type is uniquely defined by its type URL, which is the message’s fully qualified name prefixed by a / (slash) character. In some implementations of Any, like the gogoproto one, there’s generally a resolvable prefix, e.g. type.googleapis.com. However, in the Cosmos SDK, we made the decision not to include such a prefix, to have shorter type URLs. The Cosmos SDK’s own Any implementation can be found in github.com/cosmos/cosmos-sdk/codec/types.
The Cosmos SDK is also transitioning away from gogoproto to the official google.golang.org/protobuf (known as the Protobuf API v2). Its default Any implementation also contains the type.googleapis.com prefix. To maintain compatibility with the SDK, the following methods from "google.golang.org/protobuf/types/known/anypb" should not be used:
anypb.Newanypb.MarshalFromanypb.Any#MarshalFrom
"github.com/cosmos/cosmos-proto/anyutil", which create an official anypb.Any without inserting the prefixes:
anyutil.Newanyutil.MarshalFrom
sdk.Msg called internalMsg, use:
FAQ
How to create modules using protobuf encoding
Defining module types
Protobuf types can be defined to encode:- state
Msgs- Query services
- genesis
Naming and conventions
We encourage developers to follow industry guidelines: Protocol Buffers style guide and Buf. See more details in ADR 023.How to update modules to protobuf encoding
If modules do not contain any interfaces (e.g.Account or Content), then they
may simply migrate any existing types that are encoded and persisted via their concrete Amino codec to Protobuf (see 1. for further guidelines) and accept a Marshaler as the codec, which is implemented via the ProtoCodec without any further customization.
However, if a module type composes an interface, it must wrap it in the sdk.Any (from the /types package) type. To do that, a module-level .proto file must use google.protobuf.Any for respective message type interface types.
For example, the x/evidence module defines an Evidence interface, which is used by MsgSubmitEvidence. The structure definition must use sdk.Any to wrap the evidence file. In the proto file we define it as follows:
codec.Codec interface provides support methods MarshalInterface and UnmarshalInterface to easily encode state to Any.
Modules should register interfaces using InterfaceRegistry, which provides a mechanism for registering interfaces: RegisterInterface(protoName string, iface interface{}, impls ...proto.Message) and implementations: RegisterImplementations(iface interface{}, impls ...proto.Message) that can be safely unpacked from Any, similarly to type registration with Amino:
UnpackInterfaces phase should be introduced to deserialization to unpack interfaces before they’re needed. Protobuf types that contain a protobuf Any either directly or via one of their members should implement the UnpackInterfacesMessage interface: