# Encoding
While encoding in the SDK used to be mainly handled by go-amino
codec, the SDK is moving towards using gogoprotobuf
for both state and client-side encoding.
# Pre-requisite Readings
# Encoding
The Cosmos SDK utilizes two binary wire encoding protocols, Amino (opens new window) which is an object encoding specification and Protocol Buffers (opens new window), a subset of Proto3 with an extension for interface support. See the Proto3 spec (opens new window) 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 (opens new window), is being used in place of Amino. Note, this process of using Protocol Buffers over Amino is still an ongoing process.
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 converted back-n-forth. Note, 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 the codec
package, there exists two core interfaces, Marshaler
and ProtoMarshaler
,
where the former encapsulates the current Amino interface except it operates on
types implementing the latter instead of generic interface{}
types.
In addition, there exists two implementations of Marshaler
. The first being
AminoCodec
, where both binary and JSON serialization is handled via Amino. The
second being ProtoCodec
, where both binary and JSON serialization is handled
via Protobuf.
This means that modules may use Amino or Protobuf encoding but the types must
implement ProtoMarshaler
. If modules wish to avoid implementing this interface
for their types, they may use an Amino codec directly.
# Amino
Every module uses an Amino codec to serialize types and interfaces. This codec typically
has types and interfaces registered in that module's domain only (e.g. messages),
but there are exceptions like x/gov
. Each module exposes a RegisterLegacyAminoCodec
function
that allows a user to provide a codec and have all the types registered. An application
will call this method for each necessary module.
Where there is no protobuf-based type definition for a module (see below), Amino is used to encode and decode raw wire bytes to the concrete type or interface:
Note, there are length-prefixed variants of the above functionality and this is
typically used for when the data needs to be streamed or grouped together
(e.g. ResponseDeliverTx.Data
)
# Gogoproto
Modules are encouraged to utilize Protobuf encoding for their respective types. In the SDK, we use the Gogoproto (opens new window) specific implementation of the Protobuf spec that offers speed and DX improvements compared to the official Google protobuf implementation (opens new window).
# Guidelines for protobuf message definitions
In addition to following official Protocol Buffer guidelines (opens new window), we recommend using these annotations in .proto files when dealing with interfaces:
- use
cosmos_proto.accepts_interface
to annote fields that accept interfaces - pass the same fully qualified name as
protoName
toInterfaceRegistry.RegisterInterface
- annotate interface implementations with
cosmos_proto.implements_interface
- pass the same fully qualified name as
protoName
toInterfaceRegistry.RegisterInterface
# Transaction Encoding
Another important use of Protobuf is the encoding and decoding of transactions. Transactions are defined by the application or the 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
TxEncoder
object performs the encoding. - The
TxDecoder
object performs the decoding.
A standard implementation of both these objects can be found in the auth
module:
See ADR-020 for details of how a transaction is encoded.
# 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:
In this 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?
In ADR-019, it has been decided to use Any
(opens new window)s 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:
To add an account inside a profile, we need to "pack" it inside an Any
first, using codectypes.NewAnyWithValue
:
To summarize, to encode an interface, you must 1/ pack the interface into an Any
and 2/ marshal the Any
. For convenience, the SDK provides a MarshalInterface
method to bundle these two steps. Have a look at a real-life example in the x/auth module (opens new window).
The reverse operation of retrieving the concrete Go type from inside an Any
, called "unpacking", is done with the GetCachedValue()
on Any
.
It is important to note that for GetCachedValue()
to work, Profile
(and any other structs embedding Profile
) must implement the UnpackInterfaces
method:
The UnpackInterfaces
gets called recursively on all structs implementing this method, to allow all Any
s 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 SDK
The above Profile
example is a fictive example used for educational purposes. In the SDK, we use Any
encoding in several places (non-exhaustive list):
- the
cryptotypes.PubKey
interface for encoding different types of public keys, - the
sdk.Msg
interface for encoding differentMsg
s in a transaction, - the
AccountI
interface for encodinig different types of accounts (similar to the above example) in the x/auth query responses, - the
Evidencei
interface for encoding different types of evidences in the x/evidence module, - the
AuthorizationI
interface for encoding different types of x/authz authorizations, - the
Validator
(opens new window) struct that contains information about a validator.
A real-life example of encoding the pubkey as Any
inside the Validator struct in x/staking is shown in the following example:
# FAQ
- How to create modules using protobuf encoding?
Defining module types
Protobuf types can be defined to encode:
- state
Msg
s- Query services
- genesis
Naming and conventions
We encourage developers to follow industry guidelines: Protocol Buffers style guide (opens new window) and Buf (opens new window), 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 skd.Any
(from /types
package) type. To do that, a module-level .proto file must use google.protobuf.Any
(opens new window) for respective message type interface types.
For example, in the x/evidence
module defines an Evidence
interface, which is used by the MsgSubmitEvidence
. The structure definition must use sdk.Any
to wrap the evidence file. In the proto file we define it as follows:
The SDK codec.Codec
interface provides support methods MarshalInterface
and UnmarshalInterface
to easy encoding of state to Any
.
Module should register interfaces using InterfaceRegistry
which provides a mechanism for registering interfaces: RegisterInterface(protoName string, iface interface{})
and implementations: RegisterImplementations(iface interface{}, impls ...proto.Message)
that can be safely unpacked from Any, similarly to type registration with Amino:
In addition, an 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:
# Next
Learn about gRPC, REST and other endpoints