Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: api updates required for aminojson encoder #15019

Merged
merged 11 commits into from
Feb 16, 2023

Conversation

kocubinski
Copy link
Member

@kocubinski kocubinski commented Feb 13, 2023

Description

These are all the protobuf changes required to ship #10993. This patch is a subset of the feature complete PR #14877, and an attempt to make it more reviewable.

Changes

  • Introduce amino options for handling oneof types.
  • Specify numerous message and field encoders with amino.message_encoding for whole messages and amino.encoding for fields.
  • Add amino.dont_omityempty usages where needed.
  • Update amino.name usages to correlate with legacy amino type registration.

More nuanced descriptions are provided as comments closer to the source location in this PR.


Author Checklist

All items are required. Please add a note to the item if the item is not applicable and
please add links to any relevant follow up issues.

I have...

  • included the correct type prefix in the PR title
  • added ! to the type prefix if API or client breaking change
  • targeted the correct branch (see PR Targeting)
  • provided a link to the relevant issue or specification
  • followed the guidelines for building modules
  • included the necessary unit and integration tests
  • added a changelog entry to CHANGELOG.md
  • included comments for documenting Go code
  • updated the relevant documentation or specification
  • reviewed "Files changed" and left comments if necessary
  • confirmed all CI checks have passed

Reviewers Checklist

All items are required. Please add a note if the item is not applicable and please add
your handle next to the items reviewed if you only reviewed selected items.

I have...

  • confirmed the correct type prefix in the PR title
  • confirmed ! in the type prefix if API or client breaking change
  • confirmed all author checklist items have been addressed
  • reviewed state machine logic
  • reviewed API design and naming
  • reviewed documentation is accurate
  • reviewed tests and test coverage
  • manually tested (if applicable)

@@ -76,4 +76,10 @@ extend google.protobuf.FieldOptions {
// out := AminoJSONEncoder(&f);
// out == {"baz":""}
bool dont_omitempty = 11110005;

Copy link
Member Author

@kocubinski kocubinski Feb 13, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These options were added to achieve marshal parity for messages with a oneof field, e.g. StakeAuthorization:

oneof validators {
option (amino.oneof_field_name) = "Validators";
// allow_list specifies list of validator addresses to whom grantee can delegate tokens on behalf of granter's
// account.
Validators allow_list = 2 [(amino.oneof_type_name) = "cosmos-sdk/StakeAuthorization/AllowList"];
// deny_list specifies list of validator addresses to whom grantee can not delegate tokens.
Validators deny_list = 3 [(amino.oneof_type_name) = "cosmos-sdk/StakeAuthorization/DenyList"];
}

Since the legacy encoder uses golang reflection it produces JSON shaped like below.

{"type":"cosmos-sdk/StakeAuthorization","value":{"Validators":{"type":"cosmos-sdk/StakeAuthorization/AllowList","value":{"allow_list":{"address":["foo"]}}}}}

The protoreflect default field traversal differs:

{"type":"cosmos-sdk/StakeAuthorization","value":{"allow_list":{"address":["foo"]}}}

The options are introspected and used in the encoder to bring the JSON output into equivalence.

oneof := f.ContainingOneof()
isOneOf := oneof != nil
oneofFieldName, oneofTypeName, err := getOneOfNames(f)

uint64 account_number = 3;
uint64 sequence = 4;
}

// ModuleAccount defines an account for modules that holds coins on a pool.
message ModuleAccount {
option (amino.name) = "cosmos-sdk/ModuleAccount";
option (amino.message_encoding) = "module_account";
Copy link
Member Author

@kocubinski kocubinski Feb 13, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The legacy encoder features a custom encoding for ModuleAccount

func (ma ModuleAccount) MarshalJSON() ([]byte, error) {
accAddr, err := sdk.AccAddressFromBech32(ma.Address)
if err != nil {
return nil, err
}
return json.Marshal(moduleAccountPretty{
Address: accAddr,
PubKey: "",
AccountNumber: ma.AccountNumber,
Sequence: ma.Sequence,
Name: ma.Name,
Permissions: ma.Permissions,
})
}

The protoreflect encoder provides this by registering message encoding overrides by key. This encoding corresponds to this registration:

"module_account": moduleAccountEncoder,

and this implementation:
func moduleAccountEncoder(msg protoreflect.Message, w io.Writer) error {
ma := msg.Interface().(*authapi.ModuleAccount)
pretty := moduleAccountPretty{
PubKey: "",
Name: ma.Name,
Permissions: ma.Permissions,
}
if ma.BaseAccount != nil {
pretty.Address = ma.BaseAccount.Address
pretty.AccountNumber = ma.BaseAccount.AccountNumber
pretty.Sequence = ma.BaseAccount.Sequence
} else {
pretty.Address = ""
pretty.AccountNumber = 0
pretty.Sequence = 0
}
bz, err := json.Marshal(pretty)
if err != nil {
return err
}
_, err = w.Write(bz)
return err
}

@@ -41,18 +41,21 @@ message Params {
bytes min_signed_per_window = 2 [
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec",
(gogoproto.nullable) = false,
(amino.encoding) = "cosmos_dec_bytes",
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

encoder implementation:

func cosmosDecBytesEncoder(_ AminoJSON, v protoreflect.Value, w io.Writer) error {
switch bz := v.Interface().(type) {
case []byte:
if len(bz) == 0 {
return jsonMarshal(w, "0")
}
var dec math.LegacyDec
err := dec.Unmarshal(bz)
if err != nil {
return err
}
return jsonMarshal(w, dec.String())
default:
return fmt.Errorf("unsupported type %T", bz)
}
}

@kocubinski kocubinski marked this pull request as ready for review February 13, 2023 22:41
@kocubinski kocubinski requested a review from a team as a code owner February 13, 2023 22:41
Copy link
Member

@tac0turtle tac0turtle left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generally looks good. Worried about the null_as_empty stuff. This looks really easy to forget.

@kocubinski
Copy link
Member Author

Generally looks good. Worried about the null_as_empty stuff. This looks really easy to forget.

It's a fair point, we could also special case on all usages of []v1beta1.Coin in protoreflect messages if you think that'd be a better option. I'm generally in in favor of configuration over convention but maybe that's not the right thought here.

@tac0turtle
Copy link
Member

Lets keep it as is. Sorry it wasn't a suggestion to change anything. Don't want to over engineer it since hopefully amino will die soon.

@kocubinski
Copy link
Member Author

Lets keep it as is. Sorry it wasn't a suggestion to change anything. Don't want to over engineer it since hopefully amino will die soon.

If this will make upgrading easier I'm not sure its over engineering. Otherwise developers will need to remember to tag any usage of repeated Coin with (amino.encoding) = "null_slice_as_empty", right?

@kocubinski
Copy link
Member Author

@AmauryM or @aaronc Any insight here on how this will impact adoption?

Copy link
Contributor

@amaury1093 amaury1093 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since coins do have a special encoding, I prefer to add an annotation to all coins (instead of hard coding a convention). However, I see there's already a amino.dont_omit_empty, so I'm wondering if we need null_slice_as_empty.

proto/amino/amino.proto Outdated Show resolved Hide resolved
proto/cosmos/bank/v1beta1/authz.proto Outdated Show resolved Hide resolved
kocubinski added a commit that referenced this pull request Feb 14, 2023
note 3 broken tests. could revert.
ref: #15019 (comment)
@amaury1093 amaury1093 self-assigned this Feb 15, 2023
@kocubinski kocubinski changed the title feat: protobuf changes required for aminojson encoder feat: api updates required for aminojson encoder Feb 15, 2023
Copy link
Contributor

@amaury1093 amaury1093 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@kocubinski kocubinski merged commit 407c757 into main Feb 16, 2023
@kocubinski kocubinski deleted the kocubinski/aminojson/just-proto branch February 16, 2023 03:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants