Merge PR #5757: ADR 020 - Protocol Buffer Transaction Encoding
This commit is contained in:
commit
9d49fe9c5e
|
@ -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)
|
||||
|
|
|
@ -309,8 +309,6 @@ at the application-level.
|
|||
|
||||
### Neutral
|
||||
|
||||
{neutral consequences}
|
||||
|
||||
## References
|
||||
|
||||
1. https://github.com/cosmos/cosmos-sdk/issues/4977
|
||||
|
|
|
@ -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
|
Loading…
Reference in New Issue