diff --git a/docs/architecture/README.md b/docs/architecture/README.md index e327a5b64..737217bb2 100644 --- a/docs/architecture/README.md +++ b/docs/architecture/README.md @@ -45,3 +45,4 @@ Please add a entry below in your Pull Request for an ADR. - [ADR 017: Historical Header Module](./adr-017-historical-header-module.md) - [ADR 018: Extendable Voting Periods](./adr-018-extendable-voting-period.md) - [ADR 019: Protocol Buffer State Encoding](./adr-019-protobuf-state-encoding.md) +- [ADR 020: Protocol Buffer Transaction Encoding](./adr-020-protobuf-transaction-encoding.md) diff --git a/docs/architecture/adr-019-protobuf-state-encoding.md b/docs/architecture/adr-019-protobuf-state-encoding.md index d09052866..78a338ccc 100644 --- a/docs/architecture/adr-019-protobuf-state-encoding.md +++ b/docs/architecture/adr-019-protobuf-state-encoding.md @@ -309,8 +309,6 @@ at the application-level. ### Neutral -{neutral consequences} - ## References 1. https://github.com/cosmos/cosmos-sdk/issues/4977 diff --git a/docs/architecture/adr-020-protobuf-transaction-encoding.md b/docs/architecture/adr-020-protobuf-transaction-encoding.md new file mode 100644 index 000000000..39d3b1421 --- /dev/null +++ b/docs/architecture/adr-020-protobuf-transaction-encoding.md @@ -0,0 +1,182 @@ +# ADR 020: Protocol Buffer Transaction Encoding + +## Changelog + +- 2020 March 06: Initial Draft + +## Status + +Proposed + +## Context + +This ADR is a continuation of the motivation, design, and context established in +[ADR 019](./adr-019-protobuf-state-encoding.md), namely, we aim to design the +Protocol Buffer migration path for the client-side of the Cosmos SDK. + +Specifically, the client-side migration path primarily includes tx generation and +signing, message construction and routing, in addition to CLI & REST handlers and +business logic (i.e. queriers). + +With this in mind, we will tackle the migration path via two main areas, txs and +querying. However, this ADR solely focuses on transactions. Querying should be +addressed in a future ADR, but it should build off of these proposals. + +## Decision + +### Transactions + +Since the messages that an application is known and allowed to handle are specific +to the application itself, so must the transactions be specific to the application +itself. Similar to how we described in [ADR 019](./adr-019-protobuf-state-encoding.md), +the concrete types will be defined at the application level via Protobuf `oneof`. + +The application will define a single canonical `Message` Protobuf message +with a single `oneof` that implements the SDK's `Msg` interface. + +Example: + +```protobuf +// app/codec/codec.proto + +message Message { + option (cosmos_proto.interface_type) = "github.com/cosmos/cosmos-sdk/types.Msg"; + + oneof sum { + bank.MsgSend = 1; + staking.MsgCreateValidator = 2; + staking.MsgDelegate = 3; + // ... + } +} +``` + +Because an application needs to define it's unique `Message` Protobuf message, it +will by proxy have to define a `Transaction` Protobuf message that encapsulates this +`Message` type. The `Transaction` message type must implement the SDK's `Tx` interface. + +Example: + +```protobuf +// app/codec/codec.proto + +message Transaction { + option (cosmos_proto.interface_type) = "github.com/cosmos/cosmos-sdk/types.Tx"; + + StdTxBase base = 1; + repeated Message msgs = 2; +} +``` + +Note, the `Transaction` type includes `StdTxBase` which will be defined by the SDK +and includes all the core field members that are common across all transaction types. +Developers do not have to include `StdTxBase` if they wish, so it is meant to be +used as an auxiliary type. + +### Signing + +Signing of a `Transaction` must be canonical across clients and binaries. In order +to provide canonical representation of a `Transaction` to sign over, clients must +obey the following rules: + +- Encode `SignDoc` (see below) via [Protobuf's canonical JSON encoding](https://developers.google.com/protocol-buffers/docs/proto3#json). + - Default and zero values must be stripped from the output (`0`, `“”`, `null`, `false`, `[]`, and `{}`). +- Generate canonical JSON to sign via the [JSON Canonical Form Spec](https://gibson042.github.io/canonicaljson-spec/). + - This spec should be trivial to interpret and implement in any language. + +```Protobuf +// app/codec/codec.proto + +message SignDoc { + StdSignDocBase base = 1; + repeated Message msgs = 2; +} +``` + +### CLI & REST + +Currently, the REST and CLI handlers encode and decode types and txs via Amino +JSON encoding using a concrete Amino codec. Being that some of the types dealt with +in the client can be interfaces, similar to how we described in [ADR 019](./adr-019-protobuf-state-encoding.md), +the client logic will now need to take a codec interface that knows not only how +to handle all the types, but also knows how to generate transactions, signatures, +and messages. + +```go +type TxGenerator interface { + NewTx() ClientTx + SignBytes func(chainID string, num, seq uint64, fee StdFee, msgs []sdk.Msg, memo string) ([]byte, error) +} + +type ClientTx interface { + sdk.Tx + codec.ProtoMarshaler + + SetMsgs(...sdk.Msg) error + GetSignatures() []StdSignature + SetSignatures(...StdSignature) error + GetFee() StdFee + SetFee(StdFee) + GetMemo() string + SetMemo(string) +} +``` + +We then extend `codec.Marshaler` to also require fulfillment of `TxGenerator`. + +```go +type ClientMarshaler interface { + TxGenerator + codec.Marshaler +} +``` + +Then, each module will at the minimum accept a `ClientMarshaler` instead of a concrete +Amino codec. If the module needs to work with any interface types, it will use +the `Codec` interface defined by the module which also extends `ClientMarshaler`. + +## Future Improvements + +Requiring application developers to have to redefine their `Message` Protobuf types +can be extremely tedious and may increase the surface area of bugs by potentially +missing one or more messages in the `oneof`. + +To circumvent this, an optional strategy can be taken that has each module define +it's own `oneof` and then the application-level `Message` simply imports each module's +`oneof`. However, this requires additional tooling and the use of reflection. + +Example: + +```protobuf +// app/codec/codec.proto + +message Message { + option (cosmos_proto.interface_type) = "github.com/cosmos/cosmos-sdk/types.Msg"; + + oneof sum { + bank.Msg = 1; + staking.Msg = 2; + // ... + } +} +``` + +## Consequences + +### Positive + +- Significant performance gains. +- Supports backward and forward type compatibility. +- Better support for cross-language clients. + +### Negative + +- Learning curve required to understand and implement Protobuf messages. +- Less flexibility in cross-module type registration. We now need to define types +at the application-level. +- Client business logic and tx generation become a bit more complex as developers +have to define more types and implement more interfaces. + +### Neutral + +## References