chore: bring in v0.13.x x/tx in release/v0.50.x (#21158)

This commit is contained in:
Julien Robert 2024-08-02 13:23:53 +02:00 committed by GitHub
parent 31ef899f4c
commit a565daad48
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
49 changed files with 6490 additions and 637 deletions

View File

@ -25,83 +25,56 @@ Types of changes (Stanzas):
"Bug Fixes" for any bug fixes.
"API Breaking" for breaking exported APIs used by developers building on SDK.
Ref: https://keepachangelog.com/en/1.0.0/
Since v0.13.0, x/tx follows Cosmos SDK semver: https://github.com/cosmos/cosmos-sdk/blob/main/RELEASES.md
-->
# Changelog
## [Unreleased]
## v0.8.0
## [v0.13.4](https://github.com/cosmos/cosmos-sdk/releases/tag/x/tx/v0.13.4) - 2024-08-02
### Improvements
* [#16340](https://github.com/cosmos/cosmos-sdk/pull/16340): add `DefineCustomGetSigners` API function.
* [#21073](https://github.com/cosmos/cosmos-sdk/pull/21073) In Context use sync.Map `getSignersFuncs` map from concurrent writes, we also call Validate when creating the Context.
## v0.7.0
### API Breaking
* [#16044](https://github.com/cosmos/cosmos-sdk/pull/16044): rename aminojson.NewAminoJSON -> aminojson.NewEncoder.
* [#16047](https://github.com/cosmos/cosmos-sdk/pull/16047): aminojson.NewEncoder now takes EncoderOptions as an argument.
* [#16254](https://github.com/cosmos/cosmos-sdk/pull/16254): aminojson.Encoder.Marshal now sorts all fields like encoding/json.Marshal does, hence no more need for sdk.\*SortJSON.
## v0.6.2
## [v0.13.3](https://github.com/cosmos/cosmos-sdk/releases/tag/x/tx/v0.13.3) - 2024-04-22
### Improvements
* [#15873](https://github.com/cosmos/cosmos-sdk/pull/15873): add `Validate` method and only check for errors when `Validate` is explicitly called.
* [#20049](https://github.com/cosmos/cosmos-sdk/pull/20049) Sort JSON attributes for `inline_json` encoder.
## v0.6.1
### Improvements
* [#15871](https://github.com/cosmos/cosmos-sdk/pull/15871)
* `HandlerMap` now has a `DefaultMode()` getter method
* Textual types use `signing.ProtoFileResolver` instead of `protoregistry.Files`
## v0.6.0
### API Breaking
* [#15709](https://github.com/cosmos/cosmos-sdk/pull/15709):
* `GetSignersContext` has been renamed to `signing.Context`
* `GetSigners` now returns `[][]byte` instead of `[]string`
* `GetSignersOptions` has been renamed to `signing.Options` and requires `address.Codec`s for account and validator addresses
* `GetSignersOptions.ProtoFiles` has been renamed to `signing.Options.FileResolver`
### Bug Fixes
* [#15849](https://github.com/cosmos/cosmos-sdk/pull/15849) Fix int64 usage for 32 bit platforms.
## v0.5.1
## [v0.13.2](https://github.com/cosmos/cosmos-sdk/releases/tag/x/tx/v0.13.2) - 2024-04-12
### Features
* [#15414](https://github.com/cosmos/cosmos-sdk/pull/15414) Add basic transaction decoding support.
## v0.5.0
### API Breaking
* [#15581](https://github.com/cosmos/cosmos-sdk/pull/15581) `GetSignersOptions` and `directaux.SignModeHandlerOptions` now
require a `signing.ProtoFileResolver` interface instead of `protodesc.Resolver`.
* [#15742](https://github.com/cosmos/cosmos-sdk/pull/15742) The `direct_aux` package has been renamed to `directaux` in line with Go conventions. No other types were changed during the package rename.
* [#15748](https://github.com/cosmos/cosmos-sdk/pull/15748) Rename signing.SignerData.ChainId to .ChainID, in line with Go conventions.
### Bug Fixes
* (signing/textual) [#15730](https://github.com/cosmos/cosmos-sdk/pull/15730) make IntValueRenderer.Parse: gracefully handle "" + fuzz
## v0.4.0
### API Breaking
* [#13793](https://github.com/cosmos/cosmos-sdk/pull/13793) `direct_aux.NewSignModeHandler` constructor function now returns an additional error argument.
* [#15278](https://github.com/cosmos/cosmos-sdk/pull/15278) Move `x/tx/{textual,aminojson}` into `x/tx/signing`.
* [#15302](https://github.com/cosmos/cosmos-sdk/pull/15302) `textual.NewSignModeHandler` now takes an options struct instead of a simple coin querier argument. It also returns an error.
* [#19786](https://github.com/cosmos/cosmos-sdk/pull/19786)/[#19919](https://github.com/cosmos/cosmos-sdk/pull/19919) Add "inline_json" option to Amino JSON encoder.
### Improvements
* [#15302](https://github.com/cosmos/cosmos-sdk/pull/15302) Add support for a custom registry (e.g. gogo's MergedRegistry) to be plugged into SIGN_MODE_TEXTUAL.
* [#15557](https://github.com/cosmos/cosmos-sdk/pull/15557) Implement unknown field filtering.
* [#15515](https://github.com/cosmos/cosmos-sdk/pull/15515) Implement SIGN_MODE_LEGACY_AMINO_JSON handler.
* [#19845](https://github.com/cosmos/cosmos-sdk/pull/19845) Use hybrid resolver instead of only protov2 registry
### Bug Fixes
* [#19955](https://github.com/cosmos/cosmos-sdk/pull/19955) Don't shadow Amino marshalling error messages
## [v0.13.1](https://github.com/cosmos/cosmos-sdk/releases/tag/x/tx/v0.13.1) - 2024-03-05
### Features
* [#19618](https://github.com/cosmos/cosmos-sdk/pull/19618) Add enum as string option to encoder.
### Improvements
* [#18857](https://github.com/cosmos/cosmos-sdk/pull/18857) Moved `FormatCoins` from `core/coins` to this package under `signing/textual`.
### Bug Fixes
* [#19265](https://github.com/cosmos/cosmos-sdk/pull/19265) Reject denoms that contain a comma.
## [v0.13.0](https://github.com/cosmos/cosmos-sdk/releases/tag/x/tx/v0.13.0) - 2023-12-19
### Improvements
* [#18740](https://github.com/cosmos/cosmos-sdk/pull/18740) Support nested messages when fetching signers up to a default depth of 32.

View File

@ -24,6 +24,14 @@ func rejectNonADR027TxRaw(txBytes []byte) error {
if m < 0 {
return fmt.Errorf("invalid length; %w", protowire.ParseError(m))
}
// Paranoia from possible varint decoding which can trivially
// be wrong due to the precarious nature of the format being tricked:
// https://cyber.orijtech.com/advisory/varint-decode-limitless
if m > len(txBytes) {
return fmt.Errorf("invalid length from decoding (%d) > len(txBytes) (%d)", m, len(txBytes))
}
// TxRaw only has bytes fields.
if wireType != protowire.BytesType {
return fmt.Errorf("expected %d wire type, got %d", protowire.BytesType, wireType)

View File

@ -1,13 +1,13 @@
package decode
import (
"fmt"
"errors"
"github.com/cosmos/cosmos-proto/anyutil"
"google.golang.org/protobuf/proto"
v1beta1 "cosmossdk.io/api/cosmos/tx/v1beta1"
"cosmossdk.io/errors"
errorsmod "cosmossdk.io/errors"
"cosmossdk.io/x/tx/signing"
)
@ -33,7 +33,7 @@ type Options struct {
// NewDecoder creates a new Decoder for decoding transactions.
func NewDecoder(options Options) (*Decoder, error) {
if options.SigningContext == nil {
return nil, fmt.Errorf("signing context is required")
return nil, errors.New("signing context is required")
}
return &Decoder{
@ -46,7 +46,7 @@ func (d *Decoder) Decode(txBytes []byte) (*DecodedTx, error) {
// Make sure txBytes follow ADR-027.
err := rejectNonADR027TxRaw(txBytes)
if err != nil {
return nil, errors.Wrap(ErrTxDecode, err.Error())
return nil, errorsmod.Wrap(ErrTxDecode, err.Error())
}
var raw v1beta1.TxRaw
@ -55,7 +55,7 @@ func (d *Decoder) Decode(txBytes []byte) (*DecodedTx, error) {
fileResolver := d.signingCtx.FileResolver()
err = RejectUnknownFieldsStrict(txBytes, raw.ProtoReflect().Descriptor(), fileResolver)
if err != nil {
return nil, errors.Wrap(ErrTxDecode, err.Error())
return nil, errorsmod.Wrap(ErrTxDecode, err.Error())
}
err = proto.Unmarshal(txBytes, &raw)
@ -68,12 +68,12 @@ func (d *Decoder) Decode(txBytes []byte) (*DecodedTx, error) {
// allow non-critical unknown fields in TxBody
txBodyHasUnknownNonCriticals, err := RejectUnknownFields(raw.BodyBytes, body.ProtoReflect().Descriptor(), true, fileResolver)
if err != nil {
return nil, errors.Wrap(ErrTxDecode, err.Error())
return nil, errorsmod.Wrap(ErrTxDecode, err.Error())
}
err = proto.Unmarshal(raw.BodyBytes, &body)
if err != nil {
return nil, errors.Wrap(ErrTxDecode, err.Error())
return nil, errorsmod.Wrap(ErrTxDecode, err.Error())
}
var authInfo v1beta1.AuthInfo
@ -81,12 +81,12 @@ func (d *Decoder) Decode(txBytes []byte) (*DecodedTx, error) {
// reject all unknown proto fields in AuthInfo
err = RejectUnknownFieldsStrict(raw.AuthInfoBytes, authInfo.ProtoReflect().Descriptor(), fileResolver)
if err != nil {
return nil, errors.Wrap(ErrTxDecode, err.Error())
return nil, errorsmod.Wrap(ErrTxDecode, err.Error())
}
err = proto.Unmarshal(raw.AuthInfoBytes, &authInfo)
if err != nil {
return nil, errors.Wrap(ErrTxDecode, err.Error())
return nil, errorsmod.Wrap(ErrTxDecode, err.Error())
}
theTx := &v1beta1.Tx{
@ -97,17 +97,25 @@ func (d *Decoder) Decode(txBytes []byte) (*DecodedTx, error) {
var signers [][]byte
var msgs []proto.Message
seenSigners := map[string]struct{}{}
for _, anyMsg := range body.Messages {
msg, signerErr := anyutil.Unpack(anyMsg, fileResolver, d.signingCtx.TypeResolver())
if signerErr != nil {
return nil, errors.Wrap(ErrTxDecode, signerErr.Error())
return nil, errorsmod.Wrap(ErrTxDecode, signerErr.Error())
}
msgs = append(msgs, msg)
ss, signerErr := d.signingCtx.GetSigners(msg)
if signerErr != nil {
return nil, errors.Wrap(ErrTxDecode, signerErr.Error())
return nil, errorsmod.Wrap(ErrTxDecode, signerErr.Error())
}
for _, s := range ss {
_, seen := seenSigners[string(s)]
if seen {
continue
}
signers = append(signers, s)
seenSigners[string(s)] = struct{}{}
}
signers = append(signers, ss...)
}
return &DecodedTx{

View File

@ -3,6 +3,7 @@ package decode_test
import (
"encoding/hex"
"fmt"
"strings"
"testing"
"github.com/cosmos/cosmos-proto/anyutil"
@ -85,10 +86,6 @@ func TestDecode(t *testing.T) {
Payer: "payer",
Granter: "",
},
Tip: &txv1beta1.Tip{ //nolint:staticcheck // we still need this deprecated struct
Amount: []*basev1beta1.Coin{{Amount: "100", Denom: "denom"}},
Tipper: "tipper",
},
},
Signatures: nil,
}
@ -118,3 +115,31 @@ func (d dummyAddressCodec) StringToBytes(text string) ([]byte, error) {
func (d dummyAddressCodec) BytesToString(bz []byte) (string, error) {
return hex.EncodeToString(bz), nil
}
func TestDecodeTxBodyPanic(t *testing.T) {
crashVector := []byte{
0x0a, 0x0a, 0x09, 0xe7, 0xbf, 0xba, 0xe6, 0x82, 0x9a, 0xe6, 0xaa, 0x30,
}
cdc := new(dummyAddressCodec)
signingCtx, err := signing.NewContext(signing.Options{
AddressCodec: cdc,
ValidatorAddressCodec: cdc,
})
if err != nil {
t.Fatal(err)
}
dec, err := decode.NewDecoder(decode.Options{
SigningContext: signingCtx,
})
if err != nil {
t.Fatal(err)
}
_, err = dec.Decode(crashVector)
if err == nil {
t.Fatal("expected a non-nil error")
}
if g, w := err.Error(), "could not consume length prefix"; !strings.Contains(g, w) {
t.Fatalf("error mismatch\n%s\nodes not contain\n\t%q", g, w)
}
}

140
x/tx/decode/fuzz_test.go Normal file
View File

@ -0,0 +1,140 @@
package decode
import (
"encoding/hex"
"testing"
"github.com/cosmos/cosmos-proto/anyutil"
fuzz "github.com/google/gofuzz"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/known/anypb"
bankv1beta1 "cosmossdk.io/api/cosmos/bank/v1beta1"
basev1beta1 "cosmossdk.io/api/cosmos/base/v1beta1"
"cosmossdk.io/api/cosmos/crypto/secp256k1"
signingv1beta1 "cosmossdk.io/api/cosmos/tx/signing/v1beta1"
txv1beta1 "cosmossdk.io/api/cosmos/tx/v1beta1"
"cosmossdk.io/x/tx/signing"
)
var (
accSeq = uint64(2)
signerInfo = []*txv1beta1.SignerInfo{
{
PublicKey: pkAny,
ModeInfo: &txv1beta1.ModeInfo{
Sum: &txv1beta1.ModeInfo_Single_{
Single: &txv1beta1.ModeInfo_Single{
Mode: signingv1beta1.SignMode_SIGN_MODE_DIRECT,
},
},
},
Sequence: accSeq,
},
}
anyMsg, _ = anyutil.New(&bankv1beta1.MsgSend{})
pkAny, _ = anyutil.New(&secp256k1.PubKey{Key: []byte("foo")})
)
func generateAndAddSeedsFromTx(f *testing.F) {
f.Helper()
// 1. Add some seeds.
tx := &txv1beta1.Tx{
Body: &txv1beta1.TxBody{
Messages: []*anypb.Any{anyMsg},
Memo: "memo",
TimeoutHeight: 0,
},
AuthInfo: &txv1beta1.AuthInfo{
SignerInfos: signerInfo,
Fee: &txv1beta1.Fee{
Amount: []*basev1beta1.Coin{{Amount: "100", Denom: "denom"}},
GasLimit: 100,
Payer: "payer",
Granter: "",
},
},
Signatures: nil,
}
f.Add(mustMarshal(f, tx))
fz := fuzz.New()
// 1.1. Mutate tx as much and add those as seeds.
for i := 0; i < 1e4; i++ {
func() {
defer func() {
_ = recover() // Catch any panics and continue
}()
fz.Fuzz(tx)
f.Add(mustMarshal(f, tx))
}()
}
}
func FuzzInternal_rejectNonADR027TxRaw(f *testing.F) {
if testing.Short() {
f.Skip("Skipping in -short mode")
}
// 1. Add some seeds.
generateAndAddSeedsFromTx(f)
// 2. Now run the fuzzer.
f.Fuzz(func(t *testing.T, in []byte) {
// Just ensure it doesn't crash.
_ = rejectNonADR027TxRaw(in)
})
}
func FuzzDecode(f *testing.F) {
if testing.Short() {
f.Skip("Skipping in -short mode")
}
// 1. Add some seeds.
generateAndAddSeedsFromTx(f)
// 2. Now fuzz it.
cdc := new(asHexCodec)
signingCtx, err := signing.NewContext(signing.Options{
AddressCodec: cdc,
ValidatorAddressCodec: cdc,
})
if err != nil {
return
}
dec, err := NewDecoder(Options{
SigningContext: signingCtx,
})
if err != nil {
return
}
f.Fuzz(func(t *testing.T, in []byte) {
txr, err := dec.Decode(in)
if err == nil && txr == nil {
t.Fatal("inconsistency: err==nil yet tx==nil")
}
})
}
func mustMarshal(f *testing.F, m proto.Message) []byte {
f.Helper()
blob, err := proto.Marshal(m)
if err != nil {
f.Fatal(err)
}
return blob
}
type asHexCodec int
func (d asHexCodec) StringToBytes(text string) ([]byte, error) {
return hex.DecodeString(text)
}
func (d asHexCodec) BytesToString(bz []byte) (string, error) {
return hex.EncodeToString(bz), nil
}

View File

@ -85,6 +85,15 @@ func RejectUnknownFields(bz []byte, desc protoreflect.MessageDescriptor, allowUn
// consume length prefix of nested message
_, o := protowire.ConsumeVarint(fieldBytes)
if o < 0 {
err = fmt.Errorf("could not consume length prefix fieldBytes for nested message: %v: %w",
fieldMessage, protowire.ParseError(o))
return hasUnknownNonCriticals, err
} else if o > len(fieldBytes) {
err = fmt.Errorf("length prefix > len(fieldBytes) for nested message: %v", fieldMessage)
return hasUnknownNonCriticals, err
}
fieldBytes = fieldBytes[o:]
var err error

View File

@ -3,13 +3,15 @@ module cosmossdk.io/x/tx
go 1.21
require (
cosmossdk.io/api v0.7.5
cosmossdk.io/api v0.7.4
cosmossdk.io/core v0.11.0
cosmossdk.io/errors v1.0.0-beta.7
cosmossdk.io/errors v1.0.1
cosmossdk.io/math v1.3.0
github.com/cosmos/cosmos-proto v1.0.0-beta.5
github.com/cosmos/gogoproto v1.4.12
github.com/google/go-cmp v0.6.0
github.com/iancoleman/strcase v0.2.0
github.com/google/gofuzz v1.2.0
github.com/iancoleman/strcase v0.3.0
github.com/pkg/errors v0.9.1
github.com/stretchr/testify v1.9.0
github.com/tendermint/go-amino v0.16.0
@ -19,17 +21,19 @@ require (
)
require (
github.com/cosmos/gogoproto v1.5.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 // indirect
golang.org/x/net v0.20.0 // indirect
golang.org/x/sys v0.16.0 // indirect
golang.org/x/net v0.24.0 // indirect
golang.org/x/sys v0.19.0 // indirect
golang.org/x/text v0.14.0 // indirect
google.golang.org/genproto v0.0.0-20240123012728-ef4313101c80 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20240123012728-ef4313101c80 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80 // indirect
google.golang.org/grpc v1.62.1 // indirect
google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda // indirect
google.golang.org/grpc v1.63.2 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
// NOTE: we do not want to replace to the development version of cosmossdk.io/api yet
// Until https://github.com/cosmos/cosmos-sdk/issues/19228 is resolved
// We are tagging x/tx v0.14+ from main and v0.13 from release/v0.50.x and must keep using released versions of x/tx dependencies

View File

@ -1,28 +1,26 @@
cosmossdk.io/api v0.7.5 h1:eMPTReoNmGUm8DeiQL9DyM8sYDjEhWzL1+nLbI9DqtQ=
cosmossdk.io/api v0.7.5/go.mod h1:IcxpYS5fMemZGqyYtErK7OqvdM0C8kdW3dq8Q/XIG38=
cosmossdk.io/api v0.7.4 h1:sPo8wKwCty1lht8kgL3J7YL1voJywP3YWuA5JKkBz30=
cosmossdk.io/api v0.7.4/go.mod h1:IcxpYS5fMemZGqyYtErK7OqvdM0C8kdW3dq8Q/XIG38=
cosmossdk.io/core v0.11.0 h1:vtIafqUi+1ZNAE/oxLOQQ7Oek2n4S48SWLG8h/+wdbo=
cosmossdk.io/core v0.11.0/go.mod h1:LaTtayWBSoacF5xNzoF8tmLhehqlA9z1SWiPuNC6X1w=
cosmossdk.io/errors v1.0.0-beta.7 h1:gypHW76pTQGVnHKo6QBkb4yFOJjC+sUGRc5Al3Odj1w=
cosmossdk.io/errors v1.0.0-beta.7/go.mod h1:mz6FQMJRku4bY7aqS/Gwfcmr/ue91roMEKAmDUDpBfE=
cosmossdk.io/errors v1.0.1 h1:bzu+Kcr0kS/1DuPBtUFdWjzLqyUuCiyHjyJB6srBV/0=
cosmossdk.io/errors v1.0.1/go.mod h1:MeelVSZThMi4bEakzhhhE/CKqVv3nOJDA25bIqRDu/U=
cosmossdk.io/math v1.3.0 h1:RC+jryuKeytIiictDslBP9i1fhkVm6ZDmZEoNP316zE=
cosmossdk.io/math v1.3.0/go.mod h1:vnRTxewy+M7BtXBNFybkuhSH4WfedVAAnERHgVFhp3k=
github.com/cosmos/cosmos-proto v1.0.0-beta.5 h1:eNcayDLpip+zVLRLYafhzLvQlSmyab+RC5W7ZfmxJLA=
github.com/cosmos/cosmos-proto v1.0.0-beta.5/go.mod h1:hQGLpiIUloJBMdQMMWb/4wRApmI9hjHH05nefC0Ojec=
github.com/cosmos/gogoproto v1.5.0 h1:SDVwzEqZDDBoslaeZg+dGE55hdzHfgUA40pEanMh52o=
github.com/cosmos/gogoproto v1.5.0/go.mod h1:iUM31aofn3ymidYG6bUR5ZFrk+Om8p5s754eMUcyp8I=
github.com/cosmos/gogoproto v1.4.12 h1:vB6Lbe/rtnYGjQuFxkPiPYiCybqFT8QvLipDZP8JpFE=
github.com/cosmos/gogoproto v1.4.12/go.mod h1:LnZob1bXRdUoqMMtwYlcR3wjiElmlC+FkjaZRv1/eLY=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/golang/protobuf v1.3.0/go.mod h1:Qd/q+1AKNOZr9uGQzbzCmRO6sUih6GTPZv6a1/R87v0=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/iancoleman/strcase v0.2.0 h1:05I4QRnGpI0m37iZQRuskXh+w77mr6Z41lwQzuHLwW0=
github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=
github.com/iancoleman/strcase v0.3.0 h1:nTXanmYxhfFAMjZL34Ov6gkzEsSJZ5DbhxWjvSASxEI=
github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
@ -42,23 +40,23 @@ github.com/tendermint/go-amino v0.16.0/go.mod h1:TQU0M1i/ImAo+tYpZi73AU3V/dKeCoM
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ=
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20240123012728-ef4313101c80 h1:KAeGQVN3M9nD0/bQXnr/ClcEMJ968gUXJQ9pwfSynuQ=
google.golang.org/genproto v0.0.0-20240123012728-ef4313101c80/go.mod h1:cc8bqMqtv9gMOr0zHg2Vzff5ULhhL2IXP4sbcn32Dro=
google.golang.org/genproto/googleapis/api v0.0.0-20240123012728-ef4313101c80 h1:Lj5rbfG876hIAYFjqiJnPHfhXbv+nzTWfm04Fg/XSVU=
google.golang.org/genproto/googleapis/api v0.0.0-20240123012728-ef4313101c80/go.mod h1:4jWUdICTdgc3Ibxmr8nAJiiLHwQBY0UI0XZcEMaFKaA=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80 h1:AjyfHzEPEFp/NpvfN5g+KDla3EMojjhRVZc1i7cj+oM=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80/go.mod h1:PAREbraiVEVGVdTZsVWjSbbTtSyGbAgIIvni8a8CD5s=
google.golang.org/grpc v1.62.1 h1:B4n+nfKzOICUXMgyrNd19h/I9oH0L1pizfk1d4zSgTk=
google.golang.org/grpc v1.62.1/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE=
google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de h1:F6qOa9AZTYJXOUEr4jDysRDLrm4PHePlge4v4TGAlxY=
google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:VUhTRKeHn9wwcdrk73nvdC9gF178Tzhmt/qyaFcPLSo=
google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de h1:jFNzHPIeuzhdRwVhbZdiym9q0ory/xY3sA+v2wPg8I0=
google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:5iCWqnniDlqZHrd3neWVTOwvh/v6s3232omMecelax8=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda h1:LI5DOvAxUPMv/50agcLLoo+AdWc1irS9Rzz4vPuD1V4=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY=
google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM=
google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA=
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

View File

@ -55,6 +55,41 @@ message RepeatedNestedRepeatedSigner {
}
}
message DeeplyNestedSigner {
option (cosmos.msg.v1.signer) = "inner_one";
InnerOne inner_one = 1;
message InnerOne {
option (cosmos.msg.v1.signer) = "inner_two";
InnerTwo inner_two = 1;
message InnerTwo {
option (cosmos.msg.v1.signer) = "signer";
string signer = 1;
}
}
}
message DeeplyNestedRepeatedSigner {
option (cosmos.msg.v1.signer) = "inner";
repeated Inner inner = 1;
message Inner {
option (cosmos.msg.v1.signer) = "inner";
repeated Inner inner = 1;
message Inner {
option (cosmos.msg.v1.signer) = "inner";
repeated Bottom inner = 1;
message Bottom {
option (cosmos.msg.v1.signer) = "signer";
repeated string signer = 1;
}
}
}
}
message BadSigner {
option (cosmos.msg.v1.signer) = "signer";
bytes signer = 1;

File diff suppressed because it is too large Load Diff

View File

@ -2,8 +2,10 @@ package aminojson
import (
"context"
"errors"
"fmt"
gogoproto "github.com/cosmos/gogoproto/proto"
"google.golang.org/protobuf/reflect/protoregistry"
signingv1beta1 "cosmossdk.io/api/cosmos/tx/signing/v1beta1"
@ -22,7 +24,7 @@ type SignModeHandler struct {
// SignModeHandlerOptions are the options for the SignModeHandler.
type SignModeHandlerOptions struct {
FileResolver signing.ProtoFileResolver
TypeResolver protoregistry.MessageTypeResolver
TypeResolver signing.TypeResolver
Encoder *Encoder
}
@ -30,7 +32,7 @@ type SignModeHandlerOptions struct {
func NewSignModeHandler(options SignModeHandlerOptions) *SignModeHandler {
h := &SignModeHandler{}
if options.FileResolver == nil {
h.fileResolver = protoregistry.GlobalFiles
h.fileResolver = gogoproto.HybridResolver
} else {
h.fileResolver = options.FileResolver
}
@ -43,6 +45,7 @@ func NewSignModeHandler(options SignModeHandlerOptions) *SignModeHandler {
h.encoder = NewEncoder(EncoderOptions{
FileResolver: options.FileResolver,
TypeResolver: options.TypeResolver,
EnumAsString: false, // ensure enum as string is disabled
})
} else {
h.encoder = *options.Encoder
@ -78,7 +81,7 @@ func (h SignModeHandler) GetSignBytes(_ context.Context, signerData signing.Sign
f := txData.AuthInfo.Fee
if f == nil {
return nil, fmt.Errorf("fee cannot be nil when tipper is not signer")
return nil, errors.New("fee cannot be nil when tipper is not signer")
}
fee = &aminojsonpb.AminoSignFee{
Amount: f.Amount,

View File

@ -11,12 +11,12 @@ import (
"google.golang.org/protobuf/types/known/anypb"
)
func (enc Encoder) marshalAny(message protoreflect.Message, writer io.Writer) error {
func marshalAny(enc *Encoder, message protoreflect.Message, writer io.Writer) error {
// when a message contains a nested any field, and the top-level message has been unmarshalled into a dyanmicpb,
// the nested any field will also be a dynamicpb. In this case, we must use the dynamicpb API.
_, ok := message.Interface().(*dynamicpb.Message)
if ok {
return enc.marshalDynamic(message, writer)
return marshalDynamic(enc, message, writer)
}
anyMsg, ok := message.Interface().(*anypb.Any)
@ -50,13 +50,7 @@ func (enc Encoder) marshalAny(message protoreflect.Message, writer io.Writer) er
protoMessage = valueMsg.ProtoReflect()
}
_, named := getMessageAminoName(protoMessage.Descriptor().Options())
if !named {
return fmt.Errorf("message %s is packed into an any field, so requires an amino.name annotation",
anyMsg.TypeUrl)
}
return enc.beginMarshal(protoMessage, writer)
return enc.beginMarshal(protoMessage, writer, true)
}
const (
@ -64,7 +58,7 @@ const (
anyValueFieldName = "value"
)
func (enc Encoder) marshalDynamic(message protoreflect.Message, writer io.Writer) error {
func marshalDynamic(enc *Encoder, message protoreflect.Message, writer io.Writer) error {
msgName := message.Get(message.Descriptor().Fields().ByName(anyTypeURLFieldName)).String()[1:]
msgBytes := message.Get(message.Descriptor().Fields().ByName(anyValueFieldName)).Bytes()
@ -73,17 +67,11 @@ func (enc Encoder) marshalDynamic(message protoreflect.Message, writer io.Writer
return errors.Wrapf(err, "can't resolve type URL %s", msgName)
}
_, named := getMessageAminoName(desc.Options())
if !named {
return fmt.Errorf("message %s is packed into an any field, so requires an amino.name annotation",
msgName)
}
valueMsg := dynamicpb.NewMessageType(desc.(protoreflect.MessageDescriptor)).New().Interface()
err = proto.Unmarshal(msgBytes, valueMsg)
if err != nil {
return err
}
return enc.beginMarshal(valueMsg.ProtoReflect(), writer)
return enc.beginMarshal(valueMsg.ProtoReflect(), writer, true)
}

View File

@ -40,6 +40,7 @@ func BenchmarkAminoJSONDefaultSort(b *testing.B) {
}
func benchmarkAminoJSON(b *testing.B, addNaiveSort bool) {
b.Helper()
enc := aminojson.NewEncoder(aminojson.EncoderOptions{DoNotSortFields: addNaiveSort})
b.ReportAllocs()
b.ResetTimer()
@ -54,6 +55,7 @@ func benchmarkAminoJSON(b *testing.B, addNaiveSort bool) {
}
func runAminoJSON(b *testing.B, enc aminojson.Encoder, addNaiveSort bool) []byte {
b.Helper()
bz, err := enc.Marshal(msg)
if err != nil {
b.Fatal(err)

View File

@ -5,6 +5,7 @@ import (
"encoding/json"
"fmt"
"io"
"sort"
"github.com/pkg/errors"
"google.golang.org/protobuf/reflect/protoreflect"
@ -72,15 +73,37 @@ func nullSliceAsEmptyEncoder(enc *Encoder, v protoreflect.Value, w io.Writer) er
switch list := v.Interface().(type) {
case protoreflect.List:
if list.Len() == 0 {
_, err := w.Write([]byte("[]"))
_, err := io.WriteString(w, "[]")
return err
}
return enc.marshalList(list, w)
return enc.marshalList(list, nil /* no field descriptor available here */, w)
default:
return fmt.Errorf("unsupported type %T", list)
}
}
// cosmosInlineJSON takes bytes and inlines them into a JSON document.
//
// This requires the bytes contain valid JSON since otherwise the resulting document would be invalid.
// Invalid JSON will result in an error.
//
// This replicates the behavior of JSON messages embedded in protobuf bytes
// required for CosmWasm, e.g.:
// https://github.com/CosmWasm/wasmd/blob/08567ff20e372e4f4204a91ca64a371538742bed/x/wasm/types/tx.go#L20-L22
func cosmosInlineJSON(_ *Encoder, v protoreflect.Value, w io.Writer) error {
switch bz := v.Interface().(type) {
case []byte:
json, err := sortedJSONStringify(bz)
if err != nil {
return errors.Wrap(err, "could not normalize JSON")
}
_, err = w.Write(json)
return err
default:
return fmt.Errorf("unsupported type %T", bz)
}
}
// keyFieldEncoder replicates the behavior at described at:
// https://github.com/cosmos/cosmos-sdk/blob/b49f948b36bc991db5be431607b475633aed697e/proto/cosmos/crypto/secp256k1/keys.proto#L16
// The message is treated if it were bytes directly without the key field specified.
@ -129,6 +152,7 @@ func moduleAccountEncoder(_ *Encoder, msg protoreflect.Message, w io.Writer) err
pretty.Sequence = 0
}
// we do not want to use the json encoder here because it adds a newline
bz, err := json.Marshal(pretty)
if err != nil {
return err
@ -146,13 +170,13 @@ func thresholdStringEncoder(enc *Encoder, msg protoreflect.Message, w io.Writer)
if !ok {
return errors.New("thresholdStringEncoder: msg not a multisig.LegacyAminoPubKey")
}
_, err := w.Write([]byte(fmt.Sprintf(`{"threshold":"%d","pubkeys":`, pk.Threshold)))
_, err := fmt.Fprintf(w, `{"threshold":"%d","pubkeys":`, pk.Threshold)
if err != nil {
return err
}
if len(pk.PublicKeys) == 0 {
_, err = w.Write([]byte(`[]}`))
_, err = io.WriteString(w, `[]}`)
return err
}
@ -160,10 +184,49 @@ func thresholdStringEncoder(enc *Encoder, msg protoreflect.Message, w io.Writer)
pubkeysField := fields.ByName("public_keys")
pubkeys := msg.Get(pubkeysField).List()
err = enc.marshalList(pubkeys, w)
err = enc.marshalList(pubkeys, pubkeysField, w)
if err != nil {
return err
}
_, err = w.Write([]byte(`}`))
_, err = io.WriteString(w, `}`)
return err
}
// sortedObject returns a new object that mirrors the structure of the original
// but with all maps having their keys sorted.
func sortedObject(obj interface{}) interface{} {
switch v := obj.(type) {
case map[string]interface{}:
sortedKeys := make([]string, 0, len(v))
for key := range v {
sortedKeys = append(sortedKeys, key)
}
sort.Strings(sortedKeys)
result := make(map[string]interface{})
for _, key := range sortedKeys {
result[key] = sortedObject(v[key])
}
return result
case []interface{}:
for i, val := range v {
v[i] = sortedObject(val)
}
return v
default:
return obj
}
}
// sortedJSONStringify returns a JSON with objects sorted by key.
func sortedJSONStringify(jsonBytes []byte) ([]byte, error) {
var obj interface{}
if err := json.Unmarshal(jsonBytes, &obj); err != nil {
return nil, errors.New("invalid JSON bytes")
}
sorted := sortedObject(obj)
jsonData, err := json.Marshal(sorted)
if err != nil {
return nil, err
}
return jsonData, nil
}

View File

@ -0,0 +1,174 @@
package aminojson
import (
"bytes"
"testing"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/reflect/protoreflect"
"gotest.tools/v3/assert"
)
func TestCosmosInlineJSON(t *testing.T) {
cases := map[string]struct {
value protoreflect.Value
wantErr bool
wantOutput string
}{
"supported type - valid JSON object": {
value: protoreflect.ValueOfBytes([]byte(`{"test":"value"}`)),
wantErr: false,
wantOutput: `{"test":"value"}`,
},
"supported type - valid JSON array": {
// spaces are normalized away
value: protoreflect.ValueOfBytes([]byte(`[1,2,3]`)),
wantErr: false,
wantOutput: `[1,2,3]`,
},
"supported type - valid JSON is normalized": {
value: protoreflect.ValueOfBytes([]byte(`[1, 2, 3]`)),
wantErr: false,
wantOutput: `[1,2,3]`,
},
"supported type - valid JSON array (empty)": {
value: protoreflect.ValueOfBytes([]byte(`[]`)),
wantErr: false,
wantOutput: `[]`,
},
"supported type - valid JSON number": {
value: protoreflect.ValueOfBytes([]byte(`43.72`)),
wantErr: false,
wantOutput: `43.72`,
},
"supported type - valid JSON boolean": {
value: protoreflect.ValueOfBytes([]byte(`true`)),
wantErr: false,
wantOutput: `true`,
},
"supported type - valid JSON null": {
value: protoreflect.ValueOfBytes([]byte(`null`)),
wantErr: false,
wantOutput: `null`,
},
"supported type - valid JSON string": {
value: protoreflect.ValueOfBytes([]byte(`"hey yo"`)),
wantErr: false,
wantOutput: `"hey yo"`,
},
"supported type - invalid JSON": {
value: protoreflect.ValueOfBytes([]byte(`foo`)),
wantErr: true,
},
"supported type - invalid JSON (empty)": {
value: protoreflect.ValueOfBytes([]byte(``)),
wantErr: true,
},
"supported type - invalid JSON (nil bytes)": {
value: protoreflect.ValueOfBytes(nil),
wantErr: true,
},
"unsupported type - bool": {
value: protoreflect.ValueOfBool(true),
wantErr: true,
},
"unsupported type - int64": {
value: protoreflect.ValueOfInt64(1),
wantErr: true,
},
}
for name, tc := range cases {
t.Run(name, func(t *testing.T) {
var buf bytes.Buffer
err := cosmosInlineJSON(nil, tc.value, &buf)
if tc.wantErr {
require.Error(t, err)
return
}
require.NoError(t, err)
assert.Equal(t, tc.wantOutput, buf.String())
})
}
}
func TestSortedJSONStringify(t *testing.T) {
tests := map[string]struct {
input []byte
wantOutput string
}{
"leaves true unchanged": {
input: []byte(`true`),
wantOutput: "true",
},
"leaves false unchanged": {
input: []byte(`false`),
wantOutput: "false",
},
"leaves string unchanged": {
input: []byte(`"aabbccdd"`),
wantOutput: `"aabbccdd"`,
},
"leaves number unchanged": {
input: []byte(`75`),
wantOutput: "75",
},
"leaves nil unchanged": {
input: []byte(`null`),
wantOutput: "null",
},
"leaves simple array unchanged": {
input: []byte(`[5, 6, 7, 1]`),
wantOutput: "[5,6,7,1]",
},
"leaves complex array unchanged": {
input: []byte(`[5, ["a", "b"], true, null, 1]`),
wantOutput: `[5,["a","b"],true,null,1]`,
},
"sorts empty object": {
input: []byte(`{}`),
wantOutput: `{}`,
},
"sorts single key object": {
input: []byte(`{"a": 3}`),
wantOutput: `{"a":3}`,
},
"sorts multiple keys object": {
input: []byte(`{"a": 3, "b": 2, "c": 1}`),
wantOutput: `{"a":3,"b":2,"c":1}`,
},
"sorts unsorted object": {
input: []byte(`{"b": 2, "a": 3, "c": 1}`),
wantOutput: `{"a":3,"b":2,"c":1}`,
},
"sorts unsorted complex object": {
input: []byte(`{"aaa": true, "aa": true, "a": true}`),
wantOutput: `{"a":true,"aa":true,"aaa":true}`,
},
"sorts nested objects": {
input: []byte(`{"x": {"y": {"z": null}}}`),
wantOutput: `{"x":{"y":{"z":null}}}`,
},
"sorts deeply nested unsorted objects": {
input: []byte(`{"b": {"z": true, "x": true, "y": true}, "a": true, "c": true}`),
wantOutput: `{"a":true,"b":{"x":true,"y":true,"z":true},"c":true}`,
},
"sorts objects in array sorted": {
input: []byte(`[1, 2, {"x": {"y": {"z": null}}}, 4]`),
wantOutput: `[1,2,{"x":{"y":{"z":null}}},4]`,
},
"sorts objects in array unsorted": {
input: []byte(`[1, 2, {"b": {"z": true, "x": true, "y": true}, "a": true, "c": true}, 4]`),
wantOutput: `[1,2,{"a":true,"b":{"x":true,"y":true,"z":true},"c":true},4]`,
},
}
for name, tc := range tests {
t.Run(name, func(t *testing.T) {
got, err := sortedJSONStringify(tc.input)
require.NoError(t, err)
assert.Equal(t, tc.wantOutput, string(got))
})
}
}

View File

@ -0,0 +1,75 @@
package aminojson
import (
"context"
"encoding/json"
"testing"
fuzz "github.com/google/gofuzz"
bankv1beta1 "cosmossdk.io/api/cosmos/bank/v1beta1"
basev1beta1 "cosmossdk.io/api/cosmos/base/v1beta1"
txv1beta1 "cosmossdk.io/api/cosmos/tx/v1beta1"
"cosmossdk.io/x/tx/signing/testutil"
)
func FuzzSignModeGetSignBytes(f *testing.F) {
if testing.Short() {
f.Skip("not running in -short mode")
}
// 1. Create seeds.
fee := &txv1beta1.Fee{
Amount: []*basev1beta1.Coin{{Denom: "uatom", Amount: "1000"}},
}
seed := &testutil.HandlerArgumentOptions{
ChainID: "test-chain",
Memo: "sometestmemo",
Msg: &bankv1beta1.MsgSend{
FromAddress: "foo",
ToAddress: "bar",
Amount: []*basev1beta1.Coin{{Denom: "demon", Amount: "100"}},
},
AccNum: 1,
AccSeq: 2,
SignerAddress: "signerAddress",
Fee: fee,
}
gf := fuzz.New()
for i := 0; i < 1e4; i++ {
blob, err := json.Marshal(seed)
if err != nil {
f.Fatal(err)
}
f.Add(blob)
// 1.5. Mutate the seed for the next iteration.
// gofuzz cannot handle mutating "&bankv1beta1.MsgSend",
// hence why we are mutating fields individually.
gf.Fuzz(&seed.ChainID)
gf.Fuzz(&seed.Memo)
gf.Fuzz(&seed.AccNum)
gf.Fuzz(&seed.AccSeq)
gf.Fuzz(seed.Fee)
gf.Fuzz(&seed.SignerAddress)
}
ctx := context.Background()
handler := NewSignModeHandler(SignModeHandlerOptions{})
// 2. Now run the fuzzers.
f.Fuzz(func(t *testing.T, in []byte) {
opts := new(testutil.HandlerArgumentOptions)
if err := json.Unmarshal(in, opts); err != nil {
return
}
signerData, txData, err := testutil.MakeHandlerArguments(*opts)
if err != nil {
return
}
_, _ = handler.GetSignBytes(ctx, signerData, txData)
})
}

View File

@ -1,10 +1,8 @@
syntax = "proto3";
import "cosmos_proto/cosmos.proto";
import "amino/amino.proto";
import "cosmos/base/v1beta1/coin.proto";
import "google/protobuf/any.proto";
import "cosmos/tx/v1beta1/tx.proto";
// AminoSignFee is the legacy amino json sign mode compatible version of txv1beta1.Fee, and differs from that message
// by the name of the Gas field (GasLimit in txv1beta.Fee).
@ -27,5 +25,4 @@ message AminoSignDoc {
string memo = 5 [(amino.dont_omitempty) = true];
AminoSignFee fee = 6 [(amino.dont_omitempty) = true];
repeated google.protobuf.Any msgs = 7 [(amino.dont_omitempty) = true];
cosmos.tx.v1beta1.Tip tip = 8;
}
}

View File

@ -4,9 +4,7 @@ package aminojsonpb
import (
_ "cosmossdk.io/api/amino"
v1beta1 "cosmossdk.io/api/cosmos/base/v1beta1"
v1beta11 "cosmossdk.io/api/cosmos/tx/v1beta1"
fmt "fmt"
_ "github.com/cosmos/cosmos-proto"
runtime "github.com/cosmos/cosmos-proto/runtime"
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoiface "google.golang.org/protobuf/runtime/protoiface"
@ -747,7 +745,6 @@ var (
fd_AminoSignDoc_memo protoreflect.FieldDescriptor
fd_AminoSignDoc_fee protoreflect.FieldDescriptor
fd_AminoSignDoc_msgs protoreflect.FieldDescriptor
fd_AminoSignDoc_tip protoreflect.FieldDescriptor
)
func init() {
@ -760,7 +757,6 @@ func init() {
fd_AminoSignDoc_memo = md_AminoSignDoc.Fields().ByName("memo")
fd_AminoSignDoc_fee = md_AminoSignDoc.Fields().ByName("fee")
fd_AminoSignDoc_msgs = md_AminoSignDoc.Fields().ByName("msgs")
fd_AminoSignDoc_tip = md_AminoSignDoc.Fields().ByName("tip")
}
var _ protoreflect.Message = (*fastReflection_AminoSignDoc)(nil)
@ -870,12 +866,6 @@ func (x *fastReflection_AminoSignDoc) Range(f func(protoreflect.FieldDescriptor,
return
}
}
if x.Tip != nil {
value := protoreflect.ValueOfMessage(x.Tip.ProtoReflect())
if !f(fd_AminoSignDoc_tip, value) {
return
}
}
}
// Has reports whether a field is populated.
@ -905,8 +895,6 @@ func (x *fastReflection_AminoSignDoc) Has(fd protoreflect.FieldDescriptor) bool
return x.Fee != nil
case "AminoSignDoc.msgs":
return len(x.Msgs) != 0
case "AminoSignDoc.tip":
return x.Tip != nil
default:
if fd.IsExtension() {
panic(fmt.Errorf("proto3 declared messages do not support extensions: AminoSignDoc"))
@ -937,8 +925,6 @@ func (x *fastReflection_AminoSignDoc) Clear(fd protoreflect.FieldDescriptor) {
x.Fee = nil
case "AminoSignDoc.msgs":
x.Msgs = nil
case "AminoSignDoc.tip":
x.Tip = nil
default:
if fd.IsExtension() {
panic(fmt.Errorf("proto3 declared messages do not support extensions: AminoSignDoc"))
@ -979,9 +965,6 @@ func (x *fastReflection_AminoSignDoc) Get(descriptor protoreflect.FieldDescripto
}
listValue := &_AminoSignDoc_7_list{list: &x.Msgs}
return protoreflect.ValueOfList(listValue)
case "AminoSignDoc.tip":
value := x.Tip
return protoreflect.ValueOfMessage(value.ProtoReflect())
default:
if descriptor.IsExtension() {
panic(fmt.Errorf("proto3 declared messages do not support extensions: AminoSignDoc"))
@ -1018,8 +1001,6 @@ func (x *fastReflection_AminoSignDoc) Set(fd protoreflect.FieldDescriptor, value
lv := value.List()
clv := lv.(*_AminoSignDoc_7_list)
x.Msgs = *clv.list
case "AminoSignDoc.tip":
x.Tip = value.Message().Interface().(*v1beta11.Tip)
default:
if fd.IsExtension() {
panic(fmt.Errorf("proto3 declared messages do not support extensions: AminoSignDoc"))
@ -1051,11 +1032,6 @@ func (x *fastReflection_AminoSignDoc) Mutable(fd protoreflect.FieldDescriptor) p
}
value := &_AminoSignDoc_7_list{list: &x.Msgs}
return protoreflect.ValueOfList(value)
case "AminoSignDoc.tip":
if x.Tip == nil {
x.Tip = new(v1beta11.Tip)
}
return protoreflect.ValueOfMessage(x.Tip.ProtoReflect())
case "AminoSignDoc.account_number":
panic(fmt.Errorf("field account_number of message AminoSignDoc is not mutable"))
case "AminoSignDoc.sequence":
@ -1095,9 +1071,6 @@ func (x *fastReflection_AminoSignDoc) NewField(fd protoreflect.FieldDescriptor)
case "AminoSignDoc.msgs":
list := []*anypb.Any{}
return protoreflect.ValueOfList(&_AminoSignDoc_7_list{list: &list})
case "AminoSignDoc.tip":
m := new(v1beta11.Tip)
return protoreflect.ValueOfMessage(m.ProtoReflect())
default:
if fd.IsExtension() {
panic(fmt.Errorf("proto3 declared messages do not support extensions: AminoSignDoc"))
@ -1194,10 +1167,6 @@ func (x *fastReflection_AminoSignDoc) ProtoMethods() *protoiface.Methods {
n += 1 + l + runtime.Sov(uint64(l))
}
}
if x.Tip != nil {
l = options.Size(x.Tip)
n += 1 + l + runtime.Sov(uint64(l))
}
if x.unknownFields != nil {
n += len(x.unknownFields)
}
@ -1227,20 +1196,6 @@ func (x *fastReflection_AminoSignDoc) ProtoMethods() *protoiface.Methods {
i -= len(x.unknownFields)
copy(dAtA[i:], x.unknownFields)
}
if x.Tip != nil {
encoded, err := options.Marshal(x.Tip)
if err != nil {
return protoiface.MarshalOutput{
NoUnkeyedLiterals: input.NoUnkeyedLiterals,
Buf: input.Buf,
}, err
}
i -= len(encoded)
copy(dAtA[i:], encoded)
i = runtime.EncodeVarint(dAtA, i, uint64(len(encoded)))
i--
dAtA[i] = 0x42
}
if len(x.Msgs) > 0 {
for iNdEx := len(x.Msgs) - 1; iNdEx >= 0; iNdEx-- {
encoded, err := options.Marshal(x.Msgs[iNdEx])
@ -1540,42 +1495,6 @@ func (x *fastReflection_AminoSignDoc) ProtoMethods() *protoiface.Methods {
return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, err
}
iNdEx = postIndex
case 8:
if wireType != 2 {
return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, fmt.Errorf("proto: wrong wireType = %d for field Tip", wireType)
}
var msglen int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrIntOverflow
}
if iNdEx >= l {
return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
msglen |= int(b&0x7F) << shift
if b < 0x80 {
break
}
}
if msglen < 0 {
return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrInvalidLength
}
postIndex := iNdEx + msglen
if postIndex < 0 {
return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrInvalidLength
}
if postIndex > l {
return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF
}
if x.Tip == nil {
x.Tip = &v1beta11.Tip{}
}
if err := options.Unmarshal(dAtA[iNdEx:postIndex], x.Tip); err != nil {
return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, err
}
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := runtime.Skip(dAtA[iNdEx:])
@ -1701,7 +1620,6 @@ type AminoSignDoc struct {
Memo string `protobuf:"bytes,5,opt,name=memo,proto3" json:"memo,omitempty"`
Fee *AminoSignFee `protobuf:"bytes,6,opt,name=fee,proto3" json:"fee,omitempty"`
Msgs []*anypb.Any `protobuf:"bytes,7,rep,name=msgs,proto3" json:"msgs,omitempty"`
Tip *v1beta11.Tip `protobuf:"bytes,8,opt,name=tip,proto3" json:"tip,omitempty"`
}
func (x *AminoSignDoc) Reset() {
@ -1773,63 +1691,50 @@ func (x *AminoSignDoc) GetMsgs() []*anypb.Any {
return nil
}
func (x *AminoSignDoc) GetTip() *v1beta11.Tip {
if x != nil {
return x.Tip
}
return nil
}
var File_aminojsonpb_aminojson_proto protoreflect.FileDescriptor
var file_aminojsonpb_aminojson_proto_rawDesc = []byte{
0x0a, 0x1b, 0x61, 0x6d, 0x69, 0x6e, 0x6f, 0x6a, 0x73, 0x6f, 0x6e, 0x70, 0x62, 0x2f, 0x61, 0x6d,
0x69, 0x6e, 0x6f, 0x6a, 0x73, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x19, 0x63,
0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x63, 0x6f, 0x73, 0x6d,
0x6f, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x11, 0x61, 0x6d, 0x69, 0x6e, 0x6f, 0x2f,
0x61, 0x6d, 0x69, 0x6e, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1e, 0x63, 0x6f, 0x73,
0x6d, 0x6f, 0x73, 0x2f, 0x62, 0x61, 0x73, 0x65, 0x2f, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31,
0x2f, 0x63, 0x6f, 0x69, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x19, 0x67, 0x6f, 0x6f,
0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x61, 0x6e, 0x79,
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1a, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2f, 0x74,
0x78, 0x2f, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2f, 0x74, 0x78, 0x2e, 0x70, 0x72, 0x6f,
0x74, 0x6f, 0x22, 0xa2, 0x01, 0x0a, 0x0c, 0x41, 0x6d, 0x69, 0x6e, 0x6f, 0x53, 0x69, 0x67, 0x6e,
0x46, 0x65, 0x65, 0x12, 0x49, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20,
0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2e, 0x62, 0x61, 0x73,
0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x43, 0x6f, 0x69, 0x6e, 0x42, 0x16,
0x9a, 0xe7, 0xb0, 0x2a, 0x0c, 0x6c, 0x65, 0x67, 0x61, 0x63, 0x79, 0x5f, 0x63, 0x6f, 0x69, 0x6e,
0x73, 0xa8, 0xe7, 0xb0, 0x2a, 0x01, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x17,
0x0a, 0x03, 0x67, 0x61, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x42, 0x05, 0xa8, 0xe7, 0xb0,
0x2a, 0x01, 0x52, 0x03, 0x67, 0x61, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x70, 0x61, 0x79, 0x65, 0x72,
0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x70, 0x61, 0x79, 0x65, 0x72, 0x12, 0x18, 0x0a,
0x07, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x65, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07,
0x67, 0x72, 0x61, 0x6e, 0x74, 0x65, 0x72, 0x22, 0xc6, 0x02, 0x0a, 0x0c, 0x41, 0x6d, 0x69, 0x6e,
0x6f, 0x53, 0x69, 0x67, 0x6e, 0x44, 0x6f, 0x63, 0x12, 0x2c, 0x0a, 0x0e, 0x61, 0x63, 0x63, 0x6f,
0x75, 0x6e, 0x74, 0x5f, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04,
0x42, 0x05, 0xa8, 0xe7, 0xb0, 0x2a, 0x01, 0x52, 0x0d, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74,
0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x21, 0x0a, 0x08, 0x73, 0x65, 0x71, 0x75, 0x65, 0x6e,
0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x42, 0x05, 0xa8, 0xe7, 0xb0, 0x2a, 0x01, 0x52,
0x08, 0x73, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x74, 0x69, 0x6d,
0x65, 0x6f, 0x75, 0x74, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28,
0x04, 0x52, 0x0d, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74,
0x12, 0x20, 0x0a, 0x08, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01,
0x28, 0x09, 0x42, 0x05, 0xa8, 0xe7, 0xb0, 0x2a, 0x01, 0x52, 0x07, 0x63, 0x68, 0x61, 0x69, 0x6e,
0x49, 0x64, 0x12, 0x19, 0x0a, 0x04, 0x6d, 0x65, 0x6d, 0x6f, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09,
0x42, 0x05, 0xa8, 0xe7, 0xb0, 0x2a, 0x01, 0x52, 0x04, 0x6d, 0x65, 0x6d, 0x6f, 0x12, 0x26, 0x0a,
0x03, 0x66, 0x65, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x41, 0x6d, 0x69,
0x6e, 0x6f, 0x53, 0x69, 0x67, 0x6e, 0x46, 0x65, 0x65, 0x42, 0x05, 0xa8, 0xe7, 0xb0, 0x2a, 0x01,
0x52, 0x03, 0x66, 0x65, 0x65, 0x12, 0x2f, 0x0a, 0x04, 0x6d, 0x73, 0x67, 0x73, 0x18, 0x07, 0x20,
0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f,
0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x41, 0x6e, 0x79, 0x42, 0x05, 0xa8, 0xe7, 0xb0, 0x2a, 0x01,
0x52, 0x04, 0x6d, 0x73, 0x67, 0x73, 0x12, 0x28, 0x0a, 0x03, 0x74, 0x69, 0x70, 0x18, 0x08, 0x20,
0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2e, 0x74, 0x78, 0x2e,
0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x54, 0x69, 0x70, 0x52, 0x03, 0x74, 0x69, 0x70,
0x42, 0x4b, 0x42, 0x0e, 0x41, 0x6d, 0x69, 0x6e, 0x6f, 0x6a, 0x73, 0x6f, 0x6e, 0x50, 0x72, 0x6f,
0x74, 0x6f, 0x50, 0x01, 0x5a, 0x37, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x73, 0x64, 0x6b, 0x2e,
0x69, 0x6f, 0x2f, 0x78, 0x2f, 0x74, 0x78, 0x2f, 0x61, 0x6d, 0x69, 0x6e, 0x6f, 0x6a, 0x73, 0x6f,
0x6e, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x74, 0x65, 0x73, 0x74, 0x70,
0x62, 0x2f, 0x61, 0x6d, 0x69, 0x6e, 0x6f, 0x6a, 0x73, 0x6f, 0x6e, 0x70, 0x62, 0x62, 0x06, 0x70,
0x72, 0x6f, 0x74, 0x6f, 0x33,
0x69, 0x6e, 0x6f, 0x6a, 0x73, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x11, 0x61,
0x6d, 0x69, 0x6e, 0x6f, 0x2f, 0x61, 0x6d, 0x69, 0x6e, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
0x1a, 0x1e, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2f, 0x62, 0x61, 0x73, 0x65, 0x2f, 0x76, 0x31,
0x62, 0x65, 0x74, 0x61, 0x31, 0x2f, 0x63, 0x6f, 0x69, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
0x1a, 0x19, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75,
0x66, 0x2f, 0x61, 0x6e, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xa2, 0x01, 0x0a, 0x0c,
0x41, 0x6d, 0x69, 0x6e, 0x6f, 0x53, 0x69, 0x67, 0x6e, 0x46, 0x65, 0x65, 0x12, 0x49, 0x0a, 0x06,
0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x63,
0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2e, 0x62, 0x61, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74,
0x61, 0x31, 0x2e, 0x43, 0x6f, 0x69, 0x6e, 0x42, 0x16, 0x9a, 0xe7, 0xb0, 0x2a, 0x0c, 0x6c, 0x65,
0x67, 0x61, 0x63, 0x79, 0x5f, 0x63, 0x6f, 0x69, 0x6e, 0x73, 0xa8, 0xe7, 0xb0, 0x2a, 0x01, 0x52,
0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x17, 0x0a, 0x03, 0x67, 0x61, 0x73, 0x18, 0x02,
0x20, 0x01, 0x28, 0x04, 0x42, 0x05, 0xa8, 0xe7, 0xb0, 0x2a, 0x01, 0x52, 0x03, 0x67, 0x61, 0x73,
0x12, 0x14, 0x0a, 0x05, 0x70, 0x61, 0x79, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52,
0x05, 0x70, 0x61, 0x79, 0x65, 0x72, 0x12, 0x18, 0x0a, 0x07, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x65,
0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x65, 0x72,
0x22, 0x9c, 0x02, 0x0a, 0x0c, 0x41, 0x6d, 0x69, 0x6e, 0x6f, 0x53, 0x69, 0x67, 0x6e, 0x44, 0x6f,
0x63, 0x12, 0x2c, 0x0a, 0x0e, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x6e, 0x75, 0x6d,
0x62, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x42, 0x05, 0xa8, 0xe7, 0xb0, 0x2a, 0x01,
0x52, 0x0d, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12,
0x21, 0x0a, 0x08, 0x73, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28,
0x04, 0x42, 0x05, 0xa8, 0xe7, 0xb0, 0x2a, 0x01, 0x52, 0x08, 0x73, 0x65, 0x71, 0x75, 0x65, 0x6e,
0x63, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x5f, 0x68, 0x65,
0x69, 0x67, 0x68, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0d, 0x74, 0x69, 0x6d, 0x65,
0x6f, 0x75, 0x74, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x20, 0x0a, 0x08, 0x63, 0x68, 0x61,
0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x42, 0x05, 0xa8, 0xe7, 0xb0,
0x2a, 0x01, 0x52, 0x07, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x04, 0x6d,
0x65, 0x6d, 0x6f, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x42, 0x05, 0xa8, 0xe7, 0xb0, 0x2a, 0x01,
0x52, 0x04, 0x6d, 0x65, 0x6d, 0x6f, 0x12, 0x26, 0x0a, 0x03, 0x66, 0x65, 0x65, 0x18, 0x06, 0x20,
0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x41, 0x6d, 0x69, 0x6e, 0x6f, 0x53, 0x69, 0x67, 0x6e, 0x46,
0x65, 0x65, 0x42, 0x05, 0xa8, 0xe7, 0xb0, 0x2a, 0x01, 0x52, 0x03, 0x66, 0x65, 0x65, 0x12, 0x2f,
0x0a, 0x04, 0x6d, 0x73, 0x67, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67,
0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x41,
0x6e, 0x79, 0x42, 0x05, 0xa8, 0xe7, 0xb0, 0x2a, 0x01, 0x52, 0x04, 0x6d, 0x73, 0x67, 0x73, 0x42,
0x4b, 0x42, 0x0e, 0x41, 0x6d, 0x69, 0x6e, 0x6f, 0x6a, 0x73, 0x6f, 0x6e, 0x50, 0x72, 0x6f, 0x74,
0x6f, 0x50, 0x01, 0x5a, 0x37, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x73, 0x64, 0x6b, 0x2e, 0x69,
0x6f, 0x2f, 0x78, 0x2f, 0x74, 0x78, 0x2f, 0x61, 0x6d, 0x69, 0x6e, 0x6f, 0x6a, 0x73, 0x6f, 0x6e,
0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x74, 0x65, 0x73, 0x74, 0x70, 0x62,
0x2f, 0x61, 0x6d, 0x69, 0x6e, 0x6f, 0x6a, 0x73, 0x6f, 0x6e, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x33,
}
var (
@ -1850,18 +1755,16 @@ var file_aminojsonpb_aminojson_proto_goTypes = []interface{}{
(*AminoSignDoc)(nil), // 1: AminoSignDoc
(*v1beta1.Coin)(nil), // 2: cosmos.base.v1beta1.Coin
(*anypb.Any)(nil), // 3: google.protobuf.Any
(*v1beta11.Tip)(nil), // 4: cosmos.tx.v1beta1.Tip
}
var file_aminojsonpb_aminojson_proto_depIdxs = []int32{
2, // 0: AminoSignFee.amount:type_name -> cosmos.base.v1beta1.Coin
0, // 1: AminoSignDoc.fee:type_name -> AminoSignFee
3, // 2: AminoSignDoc.msgs:type_name -> google.protobuf.Any
4, // 3: AminoSignDoc.tip:type_name -> cosmos.tx.v1beta1.Tip
4, // [4:4] is the sub-list for method output_type
4, // [4:4] is the sub-list for method input_type
4, // [4:4] is the sub-list for extension type_name
4, // [4:4] is the sub-list for extension extendee
0, // [0:4] is the sub-list for field type_name
3, // [3:3] is the sub-list for method output_type
3, // [3:3] is the sub-list for method input_type
3, // [3:3] is the sub-list for extension type_name
3, // [3:3] is the sub-list for extension extendee
0, // [0:3] is the sub-list for field type_name
}
func init() { file_aminojsonpb_aminojson_proto_init() }

View File

@ -9,8 +9,8 @@ deps:
- remote: buf.build
owner: cosmos
repository: cosmos-sdk
commit: a0f17dcd1d6b4a03ab50d377b36da149
digest: shake256:fc36c3c09df4a908270170125aeb559af8424df3efa1179f09b51738b0e95df5c5f31f590e2a6ab8c31af497a0e15056efeddde7ba1e3297383d01badc68d3b3
commit: 1356e87e6d1d4076b8bfb735737a0b63
digest: shake256:574a0e72da9b55e30257557503bd4203513e5ca06dd8bc19fb8bf375fc8525a6bab4d9995e1554db7e9c649bc3778578e0cb96182176206f0653f2a17c073c46
- remote: buf.build
owner: cosmos
repository: gogo-proto

View File

@ -20,6 +20,10 @@ message WithAList {
repeated string list = 2;
}
message WithAJson {
bytes field1 = 1 [(amino.encoding) = "inline_json"];
}
message ABitOfEverything {
option (amino.name) = "ABitOfEverything";
@ -62,6 +66,11 @@ message ABitOfEverything {
// google.protobuf.Duration duration = 24;
}
message Duration {
google.protobuf.Duration duration = 1;
google.protobuf.Timestamp timestamp = 2;
}
message NestedMessage {
option (amino.name) = "NestedMessage";

File diff suppressed because it is too large Load Diff

View File

@ -7,6 +7,7 @@ import (
"io"
"sort"
gogoproto "github.com/cosmos/gogoproto/proto"
"github.com/pkg/errors"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/reflect/protoreflect"
@ -23,10 +24,16 @@ type FieldEncoder func(*Encoder, protoreflect.Value, io.Writer) error
// EncoderOptions are options for creating a new Encoder.
type EncoderOptions struct {
// DonotSortFields when set turns off sorting of field names.
// Indent can only be composed of space or tab characters.
// It defines the indentation used for each level of indentation.
Indent string
// DoNotSortFields when set turns off sorting of field names.
DoNotSortFields bool
// EnumAsString when set will encode enums as strings instead of integers.
// Caution: Enabling this option produce different sign bytes.
EnumAsString bool
// TypeResolver is used to resolve protobuf message types by TypeURL when marshaling any packed messages.
TypeResolver protoregistry.MessageTypeResolver
TypeResolver signing.TypeResolver
// FileResolver is used to resolve protobuf file descriptors TypeURL when TypeResolver fails.
FileResolver signing.ProtoFileResolver
}
@ -34,39 +41,50 @@ type EncoderOptions struct {
// Encoder is a JSON encoder that uses the Amino JSON encoding rules for protobuf messages.
type Encoder struct {
// maps cosmos_proto.scalar -> field encoder
scalarEncoders map[string]FieldEncoder
messageEncoders map[string]MessageEncoder
fieldEncoders map[string]FieldEncoder
fileResolver signing.ProtoFileResolver
typeResolver protoregistry.MessageTypeResolver
doNotSortFields bool
cosmosProtoScalarEncoders map[string]FieldEncoder
aminoMessageEncoders map[string]MessageEncoder
aminoFieldEncoders map[string]FieldEncoder
protoTypeEncoders map[string]MessageEncoder
fileResolver signing.ProtoFileResolver
typeResolver protoregistry.MessageTypeResolver
doNotSortFields bool
indent string
enumsAsString bool
}
// NewEncoder returns a new Encoder capable of serializing protobuf messages to JSON using the Amino JSON encoding
// rules.
func NewEncoder(options EncoderOptions) Encoder {
if options.FileResolver == nil {
options.FileResolver = protoregistry.GlobalFiles
options.FileResolver = gogoproto.HybridResolver
}
if options.TypeResolver == nil {
options.TypeResolver = protoregistry.GlobalTypes
}
enc := Encoder{
scalarEncoders: map[string]FieldEncoder{
cosmosProtoScalarEncoders: map[string]FieldEncoder{
"cosmos.Dec": cosmosDecEncoder,
"cosmos.Int": cosmosIntEncoder,
},
messageEncoders: map[string]MessageEncoder{
aminoMessageEncoders: map[string]MessageEncoder{
"key_field": keyFieldEncoder,
"module_account": moduleAccountEncoder,
"threshold_string": thresholdStringEncoder,
},
fieldEncoders: map[string]FieldEncoder{
aminoFieldEncoders: map[string]FieldEncoder{
"legacy_coins": nullSliceAsEmptyEncoder,
"inline_json": cosmosInlineJSON,
},
protoTypeEncoders: map[string]MessageEncoder{
"google.protobuf.Timestamp": marshalTimestamp,
"google.protobuf.Duration": marshalDuration,
"google.protobuf.Any": marshalAny,
},
fileResolver: options.FileResolver,
typeResolver: options.TypeResolver,
doNotSortFields: options.DoNotSortFields,
indent: options.Indent,
enumsAsString: options.EnumAsString,
}
return enc
}
@ -81,10 +99,10 @@ func NewEncoder(options EncoderOptions) Encoder {
// ...
// }
func (enc Encoder) DefineMessageEncoding(name string, encoder MessageEncoder) Encoder {
if enc.messageEncoders == nil {
enc.messageEncoders = map[string]MessageEncoder{}
if enc.aminoMessageEncoders == nil {
enc.aminoMessageEncoders = map[string]MessageEncoder{}
}
enc.messageEncoders[name] = encoder
enc.aminoMessageEncoders[name] = encoder
return enc
}
@ -102,36 +120,92 @@ func (enc Encoder) DefineMessageEncoding(name string, encoder MessageEncoder) En
// ...
// }
func (enc Encoder) DefineFieldEncoding(name string, encoder FieldEncoder) Encoder {
if enc.fieldEncoders == nil {
enc.fieldEncoders = map[string]FieldEncoder{}
if enc.aminoFieldEncoders == nil {
enc.aminoFieldEncoders = map[string]FieldEncoder{}
}
enc.fieldEncoders[name] = encoder
enc.aminoFieldEncoders[name] = encoder
return enc
}
// DefineScalarEncoding defines a custom encoding for a protobuf scalar field. The `name` field must match a usage of
// an (cosmos_proto.scalar) option in the protobuf message as in the following example. This encoding will be used
// instead of the default encoding for all usages of the tagged field.
//
// message Balance {
// string address = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"];
// ...
// }
func (enc Encoder) DefineScalarEncoding(name string, encoder FieldEncoder) Encoder {
if enc.cosmosProtoScalarEncoders == nil {
enc.cosmosProtoScalarEncoders = map[string]FieldEncoder{}
}
enc.cosmosProtoScalarEncoders[name] = encoder
return enc
}
// DefineTypeEncoding defines a custom encoding for a protobuf message type. The `typeURL` field must match the
// type of the protobuf message as in the following example. This encoding will be used instead of the default
// encoding for all usages of the tagged message.
//
// message Foo {
// google.protobuf.Duration type_url = 1;
// ...
// }
func (enc Encoder) DefineTypeEncoding(typeURL string, encoder MessageEncoder) Encoder {
if enc.protoTypeEncoders == nil {
enc.protoTypeEncoders = map[string]MessageEncoder{}
}
enc.protoTypeEncoders[typeURL] = encoder
return enc
}
// Marshal serializes a protobuf message to JSON.
func (enc Encoder) Marshal(message proto.Message) ([]byte, error) {
buf := &bytes.Buffer{}
err := enc.beginMarshal(message.ProtoReflect(), buf)
return buf.Bytes(), err
err := enc.beginMarshal(message.ProtoReflect(), buf, false)
if err != nil {
return nil, err
}
if enc.indent != "" {
indentBuf := &bytes.Buffer{}
if err := json.Indent(indentBuf, buf.Bytes(), "", enc.indent); err != nil {
return nil, err
}
return indentBuf.Bytes(), nil
}
return buf.Bytes(), nil
}
func (enc Encoder) beginMarshal(msg protoreflect.Message, writer io.Writer) error {
name, named := getMessageAminoName(msg.Descriptor().Options())
func (enc Encoder) beginMarshal(msg protoreflect.Message, writer io.Writer, isAny bool) error {
var (
name string
named bool
)
if isAny {
name, named = getMessageAminoNameAny(msg), true
} else {
name, named = getMessageAminoName(msg)
}
if named {
_, err := writer.Write([]byte(fmt.Sprintf(`{"type":"%s","value":`, name)))
_, err := fmt.Fprintf(writer, `{"type":"%s","value":`, name)
if err != nil {
return err
}
}
err := enc.marshal(protoreflect.ValueOfMessage(msg), writer)
err := enc.marshal(protoreflect.ValueOfMessage(msg), nil /* no field descriptor needed here */, writer)
if err != nil {
return err
}
if named {
_, err = writer.Write([]byte("}"))
_, err = io.WriteString(writer, "}")
if err != nil {
return err
}
@ -140,7 +214,7 @@ func (enc Encoder) beginMarshal(msg protoreflect.Message, writer io.Writer) erro
return nil
}
func (enc Encoder) marshal(value protoreflect.Value, writer io.Writer) error {
func (enc Encoder) marshal(value protoreflect.Value, fd protoreflect.FieldDescriptor, writer io.Writer) error {
switch val := value.Interface().(type) {
case protoreflect.Message:
err := enc.marshalMessage(val, writer)
@ -151,12 +225,23 @@ func (enc Encoder) marshal(value protoreflect.Value, writer io.Writer) error {
case protoreflect.List:
if !val.IsValid() {
_, err := writer.Write([]byte("null"))
_, err := io.WriteString(writer, "null")
return err
}
return enc.marshalList(val, writer)
return enc.marshalList(val, fd, writer)
case string, bool, int32, uint32, []byte:
return jsonMarshal(writer, val)
case protoreflect.EnumNumber:
if enc.enumsAsString && fd != nil {
desc := fd.Enum().Values().ByNumber(val)
if desc != nil {
_, err := io.WriteString(writer, fmt.Sprintf(`"%s"`, desc.Name()))
return err
}
}
case string, bool, int32, uint32, []byte, protoreflect.EnumNumber:
return jsonMarshal(writer, val)
case uint64, int64:
@ -178,14 +263,9 @@ func (enc Encoder) marshalMessage(msg protoreflect.Message, writer io.Writer) er
return errors.New("nil message")
}
switch msg.Descriptor().FullName() {
case timestampFullName:
// replicate https://github.com/tendermint/go-amino/blob/8e779b71f40d175cd1302d3cd41a75b005225a7a/json-encode.go#L45-L51
return marshalTimestamp(msg, writer)
case durationFullName:
return marshalDuration(msg, writer)
case anyFullName:
return enc.marshalAny(msg, writer)
// check if we have a custom type encoder for this type
if typeEnc, ok := enc.protoTypeEncoders[string(msg.Descriptor().FullName())]; ok {
return typeEnc(&enc, msg, writer)
}
if encoder := enc.getMessageEncoder(msg); encoder != nil {
@ -193,7 +273,7 @@ func (enc Encoder) marshalMessage(msg protoreflect.Message, writer io.Writer) er
return err
}
_, err := writer.Write([]byte("{"))
_, err := io.WriteString(writer, "{")
if err != nil {
return err
}
@ -248,15 +328,14 @@ func (enc Encoder) marshalMessage(msg protoreflect.Message, writer io.Writer) er
}
if !first {
_, err = writer.Write([]byte(","))
_, err = io.WriteString(writer, ",")
if err != nil {
return err
}
}
if isOneOf && !writeNil {
_, err = writer.Write([]byte(fmt.Sprintf(`"%s":{"type":"%s","value":{`,
oneofFieldName, oneofTypeName)))
_, err = fmt.Fprintf(writer, `"%s":{"type":"%s","value":{`, oneofFieldName, oneofTypeName)
if err != nil {
return err
}
@ -267,7 +346,7 @@ func (enc Encoder) marshalMessage(msg protoreflect.Message, writer io.Writer) er
return err
}
_, err = writer.Write([]byte(":"))
_, err = io.WriteString(writer, ":")
if err != nil {
return err
}
@ -279,19 +358,19 @@ func (enc Encoder) marshalMessage(msg protoreflect.Message, writer io.Writer) er
return err
}
} else if writeNil {
_, err = writer.Write([]byte("null"))
_, err = io.WriteString(writer, "null")
if err != nil {
return err
}
} else {
err = enc.marshal(v, writer)
err = enc.marshal(v, f, writer)
if err != nil {
return err
}
}
if isOneOf && !writeNil {
_, err = writer.Write([]byte("}}"))
_, err = io.WriteString(writer, "}}")
if err != nil {
return err
}
@ -300,7 +379,7 @@ func (enc Encoder) marshalMessage(msg protoreflect.Message, writer io.Writer) er
first = false
}
_, err = writer.Write([]byte("}"))
_, err = io.WriteString(writer, "}")
return err
}
@ -313,10 +392,10 @@ func jsonMarshal(w io.Writer, v interface{}) error {
return err
}
func (enc Encoder) marshalList(list protoreflect.List, writer io.Writer) error {
func (enc Encoder) marshalList(list protoreflect.List, fd protoreflect.FieldDescriptor, writer io.Writer) error {
n := list.Len()
_, err := writer.Write([]byte("["))
_, err := io.WriteString(writer, "[")
if err != nil {
return err
}
@ -324,25 +403,19 @@ func (enc Encoder) marshalList(list protoreflect.List, writer io.Writer) error {
first := true
for i := 0; i < n; i++ {
if !first {
_, err := writer.Write([]byte(","))
_, err := io.WriteString(writer, ",")
if err != nil {
return err
}
}
first = false
err = enc.marshal(list.Get(i), writer)
err = enc.marshal(list.Get(i), fd, writer)
if err != nil {
return err
}
}
_, err = writer.Write([]byte("]"))
_, err = io.WriteString(writer, "]")
return err
}
const (
timestampFullName protoreflect.FullName = "google.protobuf.Timestamp"
durationFullName protoreflect.FullName = "google.protobuf.Duration"
anyFullName protoreflect.FullName = "google.protobuf.Any"
)

View File

@ -2,9 +2,12 @@ package aminojson_test
import (
"encoding/json"
"errors"
"fmt"
"io"
"reflect"
"testing"
"time"
"github.com/cosmos/cosmos-proto/rapidproto"
"github.com/stretchr/testify/require"
@ -14,6 +17,7 @@ import (
"google.golang.org/protobuf/reflect/protoreflect"
"google.golang.org/protobuf/reflect/protoregistry"
"google.golang.org/protobuf/types/dynamicpb"
"google.golang.org/protobuf/types/known/durationpb"
"gotest.tools/v3/assert"
"pgregory.net/rapid"
@ -117,12 +121,13 @@ func TestAminoJSON(t *testing.T) {
require.Equal(t, string(sortedBz), string(encodedDefaultBz))
}
func naiveSortedJSON(t testing.TB, jsonToSort []byte) []byte {
func naiveSortedJSON(tb testing.TB, jsonToSort []byte) []byte {
tb.Helper()
var c interface{}
err := json.Unmarshal(jsonToSort, &c)
assert.NilError(t, err)
assert.NilError(tb, err)
sortedBz, err := json.Marshal(c)
assert.NilError(t, err)
assert.NilError(tb, err)
return sortedBz
}
@ -173,6 +178,180 @@ func TestDynamicPb(t *testing.T) {
require.NoError(t, err)
dynamicBz, err := encoder.Marshal(dynamicMsg)
require.NoError(t, err)
fmt.Printf("dynamicBz: %s\n", string(dynamicBz))
require.Equal(t, string(bz), string(dynamicBz))
}
func TestMarshalDuration(t *testing.T) {
msg := &testpb.Duration{
Duration: &durationpb.Duration{Seconds: 1},
}
encoder := aminojson.NewEncoder(aminojson.EncoderOptions{})
bz, err := encoder.Marshal(msg)
require.NoError(t, err)
require.Equal(t, `{"duration":"1000000000"}`, string(bz))
// define a custom marshaler for duration
encoder.DefineTypeEncoding("google.protobuf.Duration", func(_ *aminojson.Encoder, msg protoreflect.Message, w io.Writer) error {
var secondsName protoreflect.Name = "seconds"
fields := msg.Descriptor().Fields()
secondsField := fields.ByName(secondsName)
if secondsField == nil {
return errors.New("expected seconds field")
}
seconds := msg.Get(secondsField).Int()
_, err = fmt.Fprint(w, "\"", (time.Duration(seconds) * time.Second).String(), "\"")
return err
})
bz, err = encoder.Marshal(msg)
require.NoError(t, err)
require.Equal(t, `{"duration":"1s"}`, string(bz))
}
func TestWithAJson(t *testing.T) {
encoder := aminojson.NewEncoder(aminojson.EncoderOptions{})
// list
msg := &testpb.WithAJson{
Field1: []byte(`[{"name":"child1"}]`),
}
bz, err := encoder.Marshal(msg)
require.NoError(t, err)
require.Equal(t, `{"field1":[{"name":"child1"}]}`, string(bz))
// string
msg = &testpb.WithAJson{
Field1: []byte(`"hello again"`),
}
bz, err = encoder.Marshal(msg)
require.NoError(t, err)
require.Equal(t, `{"field1":"hello again"}`, string(bz))
// object
msg = &testpb.WithAJson{
Field1: []byte(`{"deeper":{"nesting":1}}`),
}
bz, err = encoder.Marshal(msg)
require.NoError(t, err)
require.Equal(t, `{"field1":{"deeper":{"nesting":1}}}`, string(bz))
}
func TestIndent(t *testing.T) {
encoder := aminojson.NewEncoder(aminojson.EncoderOptions{Indent: " "})
msg := &testpb.ABitOfEverything{
Message: &testpb.NestedMessage{
Foo: "test",
Bar: 0, // this is the default value and should be omitted from output
},
Enum: testpb.AnEnum_ONE,
Repeated: []int32{3, -7, 2, 6, 4},
Str: `abcxyz"foo"def`,
Bool: true,
Bytes: []byte{0, 1, 2, 3},
I32: -15,
F32: 1001,
U32: 1200,
Si32: -376,
Sf32: -1000,
I64: 14578294827584932,
F64: 9572348124213523654,
U64: 4759492485,
Si64: -59268425823934,
Sf64: -659101379604211154,
}
bz, err := encoder.Marshal(msg)
require.NoError(t, err)
fmt.Println(string(bz))
require.Equal(t, `{
"type": "ABitOfEverything",
"value": {
"bool": true,
"bytes": "AAECAw==",
"enum": 1,
"f32": 1001,
"f64": "9572348124213523654",
"i32": -15,
"i64": "14578294827584932",
"message": {
"foo": "test"
},
"repeated": [
3,
-7,
2,
6,
4
],
"sf32": -1000,
"sf64": "-659101379604211154",
"si32": -376,
"si64": "-59268425823934",
"str": "abcxyz\"foo\"def",
"u32": 1200,
"u64": "4759492485"
}
}`, string(bz))
}
func TestEnumAsString(t *testing.T) {
encoder := aminojson.NewEncoder(aminojson.EncoderOptions{Indent: " ", EnumAsString: true})
msg := &testpb.ABitOfEverything{
Message: &testpb.NestedMessage{
Foo: "test",
Bar: 0, // this is the default value and should be omitted from output
},
Enum: testpb.AnEnum_ONE,
Repeated: []int32{3, -7, 2, 6, 4},
Str: `abcxyz"foo"def`,
Bool: true,
Bytes: []byte{0, 1, 2, 3},
I32: -15,
F32: 1001,
U32: 1200,
Si32: -376,
Sf32: -1000,
I64: 14578294827584932,
F64: 9572348124213523654,
U64: 4759492485,
Si64: -59268425823934,
Sf64: -659101379604211154,
}
bz, err := encoder.Marshal(msg)
require.NoError(t, err)
fmt.Println(string(bz))
require.Equal(t, `{
"type": "ABitOfEverything",
"value": {
"bool": true,
"bytes": "AAECAw==",
"enum": "ONE",
"f32": 1001,
"f64": "9572348124213523654",
"i32": -15,
"i64": "14578294827584932",
"message": {
"foo": "test"
},
"repeated": [
3,
-7,
2,
6,
4
],
"sf32": -1000,
"sf64": "-659101379604211154",
"si32": -376,
"si64": "-59268425823934",
"str": "abcxyz\"foo\"def",
"u32": 1200,
"u64": "4759492485"
}
}`, string(bz))
}

View File

@ -2,6 +2,7 @@ package aminojson
import (
cosmos_proto "github.com/cosmos/cosmos-proto"
gogoproto "github.com/cosmos/gogoproto/proto"
"github.com/iancoleman/strcase"
"github.com/pkg/errors"
"google.golang.org/protobuf/proto"
@ -12,14 +13,38 @@ import (
// getMessageAminoName returns the amino name of a message if it has been set by the `amino.name` option.
// If the message does not have an amino name, then the function returns false.
func getMessageAminoName(messageOptions proto.Message) (string, bool) {
func getMessageAminoName(msg protoreflect.Message) (string, bool) {
messageOptions := msg.Descriptor().Options()
if proto.HasExtension(messageOptions, amino.E_Name) {
name := proto.GetExtension(messageOptions, amino.E_Name)
return name.(string), true
}
return "", false
}
// getMessageAminoName returns the amino name of a message if it has been set by the `amino.name` option.
// If the message does not have an amino name, then it returns the msg url.
// If it cannot get the msg url, then it returns false.
func getMessageAminoNameAny(msg protoreflect.Message) string {
messageOptions := msg.Descriptor().Options()
if proto.HasExtension(messageOptions, amino.E_Name) {
name := proto.GetExtension(messageOptions, amino.E_Name)
return name.(string)
}
msgURL := "/" + string(msg.Descriptor().FullName())
if msgURL != "/" {
return msgURL
}
if m, ok := msg.(gogoproto.Message); ok {
return "/" + gogoproto.MessageName(m)
}
return ""
}
// omitEmpty returns true if the field should be omitted if empty. Empty field omission is the default behavior.
func omitEmpty(field protoreflect.FieldDescriptor) bool {
opts := field.Options()
@ -64,7 +89,7 @@ func (enc Encoder) getMessageEncoder(message protoreflect.Message) MessageEncode
opts := message.Descriptor().Options()
if proto.HasExtension(opts, amino.E_MessageEncoding) {
encoding := proto.GetExtension(opts, amino.E_MessageEncoding).(string)
if fn, ok := enc.messageEncoders[encoding]; ok {
if fn, ok := enc.aminoMessageEncoders[encoding]; ok {
return fn
}
}
@ -75,13 +100,13 @@ func (enc Encoder) getFieldEncoding(field protoreflect.FieldDescriptor) FieldEnc
opts := field.Options()
if proto.HasExtension(opts, amino.E_Encoding) {
encoding := proto.GetExtension(opts, amino.E_Encoding).(string)
if fn, ok := enc.fieldEncoders[encoding]; ok {
if fn, ok := enc.aminoFieldEncoders[encoding]; ok {
return fn
}
}
if proto.HasExtension(opts, cosmos_proto.E_Scalar) {
scalar := proto.GetExtension(opts, cosmos_proto.E_Scalar).(string)
if fn, ok := enc.scalarEncoders[scalar]; ok {
if fn, ok := enc.cosmosProtoScalarEncoders[scalar]; ok {
return fn
}
}

View File

@ -0,0 +1,30 @@
package aminojson
import (
"testing"
"github.com/stretchr/testify/require"
"cosmossdk.io/x/tx/signing/aminojson/internal/testpb"
)
func Test_getMessageAminoName(t *testing.T) {
msg := &testpb.ABitOfEverything{}
name, ok := getMessageAminoName(msg.ProtoReflect())
require.True(t, ok)
require.Equal(t, "ABitOfEverything", name)
secondMsg := &testpb.Duration{}
_, ok = getMessageAminoName(secondMsg.ProtoReflect())
require.False(t, ok)
}
func Test_getMessageAminoNameAny(t *testing.T) {
msg := &testpb.ABitOfEverything{}
name := getMessageAminoNameAny(msg.ProtoReflect())
require.Equal(t, "ABitOfEverything", name)
secondMsg := &testpb.Duration{}
name = getMessageAminoNameAny(secondMsg.ProtoReflect())
require.Equal(t, "/testpb.Duration", name)
}

View File

@ -1,6 +1,7 @@
package aminojson
import (
"errors"
"fmt"
"io"
"math"
@ -14,16 +15,17 @@ const (
nanosName protoreflect.Name = "nanos"
)
func marshalTimestamp(message protoreflect.Message, writer io.Writer) error {
// marshalTimestamp replicate https://github.com/tendermint/go-amino/blob/8e779b71f40d175cd1302d3cd41a75b005225a7a/json-encode.go#L45-L51
func marshalTimestamp(_ *Encoder, message protoreflect.Message, writer io.Writer) error {
fields := message.Descriptor().Fields()
secondsField := fields.ByName(secondsName)
if secondsField == nil {
return fmt.Errorf("expected seconds field")
return errors.New("expected seconds field")
}
nanosField := fields.ByName(nanosName)
if nanosField == nil {
return fmt.Errorf("expected nanos field")
return errors.New("expected nanos field")
}
seconds := message.Get(secondsField).Int()
@ -48,11 +50,11 @@ func marshalTimestamp(message protoreflect.Message, writer io.Writer) error {
// gogoproto encodes google.protobuf.Duration as a time.Duration, which is 64-bit signed integer.
const MaxDurationSeconds = int64(math.MaxInt64)/1e9 - 1
func marshalDuration(message protoreflect.Message, writer io.Writer) error {
func marshalDuration(_ *Encoder, message protoreflect.Message, writer io.Writer) error {
fields := message.Descriptor().Fields()
secondsField := fields.ByName(secondsName)
if secondsField == nil {
return fmt.Errorf("expected seconds field")
return errors.New("expected seconds field")
}
// todo
@ -64,11 +66,11 @@ func marshalDuration(message protoreflect.Message, writer io.Writer) error {
nanosField := fields.ByName(nanosName)
if nanosField == nil {
return fmt.Errorf("expected nanos field")
return errors.New("expected nanos field")
}
nanos := message.Get(nanosField).Int()
totalNanos := nanos + (seconds * 1e9)
_, err := writer.Write([]byte(fmt.Sprintf(`"%d"`, totalNanos)))
_, err := fmt.Fprintf(writer, `"%d"`, totalNanos)
return err
}

View File

@ -3,8 +3,10 @@ package signing
import (
"errors"
"fmt"
"sync"
cosmos_proto "github.com/cosmos/cosmos-proto"
gogoproto "github.com/cosmos/gogoproto/proto"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/reflect/protodesc"
"google.golang.org/protobuf/reflect/protoreflect"
@ -14,6 +16,11 @@ import (
"cosmossdk.io/core/address"
)
type TypeResolver interface {
protoregistry.MessageTypeResolver
protoregistry.ExtensionTypeResolver
}
// Context is a context for retrieving the list of signers from a
// message where signers are specified by the cosmos.msg.v1.signer protobuf
// option. It also contains the ProtoFileResolver and address.Codec's used
@ -23,8 +30,9 @@ type Context struct {
typeResolver protoregistry.MessageTypeResolver
addressCodec address.Codec
validatorAddressCodec address.Codec
getSignersFuncs map[protoreflect.FullName]GetSignersFunc
getSignersFuncs sync.Map
customGetSignerFuncs map[protoreflect.FullName]GetSignersFunc
maxRecursionDepth int
}
// Options are options for creating Context which will be used for signing operations.
@ -34,7 +42,7 @@ type Options struct {
FileResolver ProtoFileResolver
// TypeResolver is the protobuf type resolver to use for resolving message types.
TypeResolver protoregistry.MessageTypeResolver
TypeResolver TypeResolver
// AddressCodec is the codec for converting addresses between strings and bytes.
AddressCodec address.Codec
@ -42,7 +50,11 @@ type Options struct {
// ValidatorAddressCodec is the codec for converting validator addresses between strings and bytes.
ValidatorAddressCodec address.Codec
// CustomGetSigners is a map of message types to custom GetSignersFuncs.
CustomGetSigners map[protoreflect.FullName]GetSignersFunc
// MaxRecursionDepth is the maximum depth of nested messages that will be traversed
MaxRecursionDepth int
}
// DefineCustomGetSigners defines a custom GetSigners function for a given
@ -69,7 +81,7 @@ type ProtoFileResolver interface {
func NewContext(options Options) (*Context, error) {
protoFiles := options.FileResolver
if protoFiles == nil {
protoFiles = protoregistry.GlobalFiles
protoFiles = gogoproto.HybridResolver
}
protoTypes := options.TypeResolver
@ -85,6 +97,10 @@ func NewContext(options Options) (*Context, error) {
return nil, errors.New("validator address codec is required")
}
if options.MaxRecursionDepth <= 0 {
options.MaxRecursionDepth = 32
}
customGetSignerFuncs := map[protoreflect.FullName]GetSignersFunc{}
for k := range options.CustomGetSigners {
customGetSignerFuncs[k] = options.CustomGetSigners[k]
@ -95,8 +111,9 @@ func NewContext(options Options) (*Context, error) {
typeResolver: protoTypes,
addressCodec: options.AddressCodec,
validatorAddressCodec: options.ValidatorAddressCodec,
getSignersFuncs: map[protoreflect.FullName]GetSignersFunc{},
getSignersFuncs: sync.Map{},
customGetSignerFuncs: customGetSignerFuncs,
maxRecursionDepth: options.MaxRecursionDepth,
}
return c, nil
@ -203,92 +220,87 @@ func (c *Context) makeGetSignersFunc(descriptor protoreflect.MessageDescriptor)
}
}
case protoreflect.MessageKind:
isList := field.IsList()
nestedMessage := field.Message()
nestedSignersFields, err := getSignersFieldNames(nestedMessage)
if err != nil {
return nil, err
}
if len(nestedSignersFields) != 1 {
return nil, fmt.Errorf("nested cosmos.msg.v1.signer option in message %s must contain only one value", nestedMessage.FullName())
}
nestedFieldName := nestedSignersFields[0]
nestedField := nestedMessage.Fields().ByName(protoreflect.Name(nestedFieldName))
nestedIsList := nestedField.IsList()
if nestedField == nil {
return nil, fmt.Errorf("field %s not found in message %s", nestedFieldName, nestedMessage.FullName())
}
if nestedField.Kind() != protoreflect.StringKind || nestedField.IsMap() || nestedField.HasOptionalKeyword() {
return nil, fmt.Errorf("nested signer field %s in message %s must be a simple string", nestedFieldName, nestedMessage.FullName())
}
addrCdc := c.getAddressCodec(nestedField)
if isList {
if nestedIsList {
fieldGetters[i] = func(msg proto.Message, arr [][]byte) ([][]byte, error) {
msgs := msg.ProtoReflect().Get(field).List()
m := msgs.Len()
for i := 0; i < m; i++ {
signers := msgs.Get(i).Message().Get(nestedField).List()
n := signers.Len()
for j := 0; j < n; j++ {
addrStr := signers.Get(j).String()
addrBz, err := addrCdc.StringToBytes(addrStr)
if err != nil {
return nil, err
}
arr = append(arr, addrBz)
}
}
return arr, nil
}
} else {
fieldGetters[i] = func(msg proto.Message, arr [][]byte) ([][]byte, error) {
msgs := msg.ProtoReflect().Get(field).List()
m := msgs.Len()
for i := 0; i < m; i++ {
addrStr := msgs.Get(i).Message().Get(nestedField).String()
addrBz, err := addrCdc.StringToBytes(addrStr)
if err != nil {
return nil, err
}
arr = append(arr, addrBz)
}
return arr, nil
}
var fieldGetter func(protoreflect.Message, int) ([][]byte, error)
fieldGetter = func(msg protoreflect.Message, depth int) ([][]byte, error) {
if depth > c.maxRecursionDepth {
return nil, errors.New("maximum recursion depth exceeded")
}
} else {
if nestedIsList {
fieldGetters[i] = func(msg proto.Message, arr [][]byte) ([][]byte, error) {
nestedMsg := msg.ProtoReflect().Get(field).Message()
signers := nestedMsg.Get(nestedField).List()
n := signers.Len()
for j := 0; j < n; j++ {
addrStr := signers.Get(j).String()
desc := msg.Descriptor()
signerFields, err := getSignersFieldNames(desc)
if err != nil {
return nil, err
}
if len(signerFields) != 1 {
return nil, fmt.Errorf("nested cosmos.msg.v1.signer option in message %s must contain only one value", desc.FullName())
}
signerFieldName := signerFields[0]
childField := desc.Fields().ByName(protoreflect.Name(signerFieldName))
switch {
case childField.Kind() == protoreflect.MessageKind:
if childField.IsList() {
childMsgs := msg.Get(childField).List()
var arr [][]byte
for i := 0; i < childMsgs.Len(); i++ {
res, err := fieldGetter(childMsgs.Get(i).Message(), depth+1)
if err != nil {
return nil, err
}
arr = append(arr, res...)
}
return arr, nil
}
return fieldGetter(msg.Get(childField).Message(), depth+1)
case childField.IsMap() || childField.HasOptionalKeyword():
return nil, fmt.Errorf("cosmos.msg.v1.signer field %s in message %s must not be a map or optional", signerFieldName, desc.FullName())
case childField.Kind() == protoreflect.StringKind:
addrCdc := c.getAddressCodec(childField)
if childField.IsList() {
childMsgs := msg.Get(childField).List()
n := childMsgs.Len()
var res [][]byte
for i := 0; i < n; i++ {
addrStr := childMsgs.Get(i).String()
addrBz, err := addrCdc.StringToBytes(addrStr)
if err != nil {
return nil, err
}
arr = append(arr, addrBz)
res = append(res, addrBz)
}
return arr, nil
return res, nil
}
} else {
fieldGetters[i] = func(msg proto.Message, arr [][]byte) ([][]byte, error) {
addrStr := msg.ProtoReflect().Get(field).Message().Get(nestedField).String()
addrBz, err := addrCdc.StringToBytes(addrStr)
addrStr := msg.Get(childField).String()
addrBz, err := addrCdc.StringToBytes(addrStr)
if err != nil {
return nil, err
}
return [][]byte{addrBz}, nil
}
return nil, fmt.Errorf("unexpected field type %s for field %s in message %s, only string and message type are supported",
childField.Kind(), signerFieldName, desc.FullName())
}
fieldGetters[i] = func(msg proto.Message, arr [][]byte) ([][]byte, error) {
if field.IsList() {
signers := msg.ProtoReflect().Get(field).List()
n := signers.Len()
for i := 0; i < n; i++ {
res, err := fieldGetter(signers.Get(i).Message(), 0)
if err != nil {
return nil, err
}
return append(arr, addrBz), nil
arr = append(arr, res...)
}
} else {
res, err := fieldGetter(msg.ProtoReflect().Get(field).Message(), 0)
if err != nil {
return nil, err
}
arr = append(arr, res...)
}
return arr, nil
}
default:
return nil, fmt.Errorf("unexpected field type %s for field %s in message %s", field.Kind(), fieldName, descriptor.FullName())
}
@ -323,14 +335,17 @@ func (c *Context) getGetSignersFn(messageDescriptor protoreflect.MessageDescript
if ok {
return f, nil
}
f, ok = c.getSignersFuncs[messageDescriptor.FullName()]
loadedFn, ok := c.getSignersFuncs.Load(messageDescriptor.FullName())
if !ok {
var err error
f, err = c.makeGetSignersFunc(messageDescriptor)
if err != nil {
return nil, err
}
c.getSignersFuncs[messageDescriptor.FullName()] = f
c.getSignersFuncs.Store(messageDescriptor.FullName(), f)
} else {
f = loadedFn.(GetSignersFunc)
}
return f, nil

View File

@ -14,6 +14,58 @@ import (
"cosmossdk.io/x/tx/internal/testpb"
)
var deeplyNestedRepeatedSigner = &testpb.DeeplyNestedRepeatedSigner{
Inner: []*testpb.DeeplyNestedRepeatedSigner_Inner{
{
Inner: []*testpb.DeeplyNestedRepeatedSigner_Inner_Inner{
{
Inner: []*testpb.DeeplyNestedRepeatedSigner_Inner_Inner_Bottom{
{
Signer: []string{hex.EncodeToString([]byte("foo")), hex.EncodeToString([]byte("bar"))},
},
},
},
},
},
{
Inner: []*testpb.DeeplyNestedRepeatedSigner_Inner_Inner{
{
Inner: []*testpb.DeeplyNestedRepeatedSigner_Inner_Inner_Bottom{
{
Signer: []string{hex.EncodeToString([]byte("baz"))},
},
},
},
{
Inner: []*testpb.DeeplyNestedRepeatedSigner_Inner_Inner_Bottom{
{
Signer: []string{hex.EncodeToString([]byte("qux")), hex.EncodeToString([]byte("fuz"))},
},
{
Signer: []string{hex.EncodeToString([]byte("bing")), hex.EncodeToString([]byte("bap"))},
},
},
},
},
},
},
}
func TestGetGetSignersFnConcurrent(t *testing.T) {
ctx, err := NewContext(Options{
AddressCodec: dummyAddressCodec{},
ValidatorAddressCodec: dummyValidatorAddressCodec{},
})
require.NoError(t, err)
desc := (&testpb.RepeatedSigner{}).ProtoReflect().Descriptor()
for i := 0; i < 50; i++ {
go func() {
_, _ = ctx.getGetSignersFn(desc)
}()
}
}
func TestGetSigners(t *testing.T) {
ctx, err := NewContext(Options{
AddressCodec: dummyAddressCodec{},
@ -88,7 +140,18 @@ func TestGetSigners(t *testing.T) {
want: [][]byte{[]byte("foo"), []byte("bar")},
},
{
name: "nested repeated",
name: "deeply nested",
msg: &testpb.DeeplyNestedSigner{
InnerOne: &testpb.DeeplyNestedSigner_InnerOne{
InnerTwo: &testpb.DeeplyNestedSigner_InnerOne_InnerTwo{
Signer: hex.EncodeToString([]byte("foo")),
},
},
},
want: [][]byte{[]byte("foo")},
},
{
name: "nested repeated #1",
msg: &testpb.NestedRepeatedSigner{Inner: &testpb.NestedRepeatedSigner_Inner{
Signer: []string{
hex.EncodeToString([]byte("foo")),
@ -97,6 +160,11 @@ func TestGetSigners(t *testing.T) {
}},
want: [][]byte{[]byte("foo"), []byte("bar")},
},
{
name: "nested repeated #2",
msg: deeplyNestedRepeatedSigner,
want: [][]byte{[]byte("foo"), []byte("bar"), []byte("baz"), []byte("qux"), []byte("fuz"), []byte("bing"), []byte("bap")},
},
{
name: "repeated nested repeated",
msg: &testpb.RepeatedNestedRepeatedSigner{Inner: []*testpb.RepeatedNestedRepeatedSigner_Inner{
@ -145,6 +213,27 @@ func TestGetSigners(t *testing.T) {
}
}
func TestMaxRecursionDepth(t *testing.T) {
ctx, err := NewContext(Options{
AddressCodec: dummyAddressCodec{},
ValidatorAddressCodec: dummyValidatorAddressCodec{},
MaxRecursionDepth: 1,
})
require.NoError(t, err)
_, err = ctx.GetSigners(deeplyNestedRepeatedSigner)
require.ErrorContains(t, err, "maximum recursion depth exceeded")
ctx, err = NewContext(Options{
AddressCodec: dummyAddressCodec{},
ValidatorAddressCodec: dummyValidatorAddressCodec{},
MaxRecursionDepth: 5,
})
require.NoError(t, err)
_, err = ctx.GetSigners(deeplyNestedRepeatedSigner)
require.NoError(t, err)
}
func TestDefineCustomGetSigners(t *testing.T) {
customMsg := &testpb.Ballot{}
signers := [][]byte{[]byte("foo")}

View File

@ -10,6 +10,11 @@ import (
"cosmossdk.io/x/tx/signing"
)
var (
_ signing.SignModeHandler = SignModeHandler{}
protov2MarshalOpts = proto.MarshalOptions{Deterministic: true}
)
// SignModeHandler is the SIGN_MODE_DIRECT implementation of signing.SignModeHandler.
type SignModeHandler struct{}
@ -20,12 +25,10 @@ func (h SignModeHandler) Mode() signingv1beta1.SignMode {
// GetSignBytes implements signing.SignModeHandler.GetSignBytes.
func (SignModeHandler) GetSignBytes(_ context.Context, signerData signing.SignerData, txData signing.TxData) ([]byte, error) {
return proto.Marshal(&txv1beta1.SignDoc{
return protov2MarshalOpts.Marshal(&txv1beta1.SignDoc{
BodyBytes: txData.BodyBytes,
AuthInfoBytes: txData.AuthInfoBytes,
ChainId: signerData.ChainID,
AccountNumber: signerData.AccountNumber,
})
}
var _ signing.SignModeHandler = SignModeHandler{}

View File

@ -2,6 +2,7 @@ package directaux
import (
"context"
"errors"
"fmt"
"github.com/cosmos/cosmos-proto/anyutil"
@ -34,7 +35,7 @@ func NewSignModeHandler(options SignModeHandlerOptions) (SignModeHandler, error)
h := SignModeHandler{}
if options.SignersContext == nil {
return h, fmt.Errorf("signers context is required")
return h, errors.New("signers context is required")
}
h.signersContext = options.SignersContext
@ -60,7 +61,7 @@ func (h SignModeHandler) Mode() signingv1beta1.SignMode {
// https://github.com/cosmos/cosmos-sdk/blob/4a6a1e3cb8de459891cb0495052589673d14ef51/x/auth/tx/builder.go#L142
func (h SignModeHandler) getFirstSigner(txData signing.TxData) ([]byte, error) {
if len(txData.Body.Messages) == 0 {
return nil, fmt.Errorf("no signer found")
return nil, errors.New("no signer found")
}
msg, err := anyutil.Unpack(txData.Body.Messages[0], h.fileResolver, h.typeResolver)
@ -100,7 +101,8 @@ func (h SignModeHandler) GetSignBytes(
ChainId: signerData.ChainID,
AccountNumber: signerData.AccountNumber,
Sequence: signerData.Sequence,
Tip: txData.AuthInfo.Tip, //nolint:staticcheck // keep it for compatibility
}
return proto.Marshal(signDocDirectAux)
protov2MarshalOpts := proto.MarshalOptions{Deterministic: true}
return protov2MarshalOpts.Marshal(signDocDirectAux)
}

View File

@ -54,7 +54,6 @@ func TestDirectAuxHandler(t *testing.T) {
GasLimit: 20000,
Payer: feePayerAddr,
}
tip := &txv1beta1.Tip{Amount: []*basev1beta1.Coin{{Denom: "tip-token", Amount: "10"}}} //nolint:staticcheck // we still need this deprecated struct
txBody := &txv1beta1.TxBody{
Messages: []*anypb.Any{msg},
@ -63,7 +62,6 @@ func TestDirectAuxHandler(t *testing.T) {
authInfo := &txv1beta1.AuthInfo{
Fee: fee,
Tip: tip,
SignerInfos: signerInfo,
}
@ -113,7 +111,6 @@ func TestDirectAuxHandler(t *testing.T) {
}
authInfoWithNoFeePayer := &txv1beta1.AuthInfo{
Fee: feeWithNoPayer,
Tip: tip,
SignerInfos: signerInfo,
}
authInfoWithNoFeePayerBz, err := proto.Marshal(authInfoWithNoFeePayer)
@ -139,7 +136,6 @@ func TestDirectAuxHandler(t *testing.T) {
ChainId: chainID,
AccountNumber: accNum,
Sequence: accSeq,
Tip: tip,
}
expectedSignBytes, err := proto.Marshal(signDocDirectAux)
require.NoError(t, err)

View File

@ -17,7 +17,6 @@ type HandlerArgumentOptions struct {
Msg proto.Message
AccNum uint64
AccSeq uint64
Tip *txv1beta1.Tip //nolint:staticcheck // we still need this deprecated struct
Fee *txv1beta1.Fee
SignerAddress string
}
@ -57,15 +56,16 @@ func MakeHandlerArguments(options HandlerArgumentOptions) (signing.SignerData, s
authInfo := &txv1beta1.AuthInfo{
Fee: options.Fee,
Tip: options.Tip,
SignerInfos: signerInfo,
}
bodyBz, err := proto.Marshal(txBody)
protov2MarshalOpts := proto.MarshalOptions{Deterministic: true}
bodyBz, err := protov2MarshalOpts.Marshal(txBody)
if err != nil {
return signing.SignerData{}, signing.TxData{}, err
}
authInfoBz, err := proto.Marshal(authInfo)
authInfoBz, err := protov2MarshalOpts.Marshal(authInfo)
if err != nil {
return signing.SignerData{}, signing.TxData{}, err
}

View File

@ -2,6 +2,7 @@ package textual
import (
"context"
"errors"
"fmt"
"strings"
@ -74,7 +75,7 @@ func (ar anyValueRenderer) Format(ctx context.Context, v protoreflect.Value) ([]
// Parse implements the ValueRenderer interface.
func (ar anyValueRenderer) Parse(ctx context.Context, screens []Screen) (protoreflect.Value, error) {
if len(screens) == 0 {
return nilValue, fmt.Errorf("expect at least one screen")
return nilValue, errors.New("expect at least one screen")
}
if screens[0].Indent != 0 {
return nilValue, fmt.Errorf("bad indentation: want 0, got %d", screens[0].Indent)
@ -82,7 +83,7 @@ func (ar anyValueRenderer) Parse(ctx context.Context, screens []Screen) (protore
typeURL := screens[0].Content
msgType, err := ar.tr.typeResolver.FindMessageByURL(typeURL)
if err == protoregistry.NotFound {
if errors.Is(err, protoregistry.NotFound) {
// If the proto v2 registry doesn't have this message, then we use
// protoFiles (which can e.g. be initialized to gogo's MergedRegistry)
// to retrieve the message descriptor, and then use dynamicpb on that

View File

@ -3,6 +3,7 @@ package textual_test
import (
"context"
"encoding/json"
"errors"
"fmt"
"os"
"testing"
@ -55,7 +56,7 @@ func TestMetadataQuerier(t *testing.T) {
require.Error(t, err)
// Errors if metadata querier returns an error
expErr := fmt.Errorf("mock error")
expErr := errors.New("mock error")
txt, err := textual.NewSignModeHandler(textual.SignModeOptions{
CoinMetadataQuerier: func(_ context.Context, _ string) (*bankv1beta1.Metadata, error) {
return nil, expErr

View File

@ -2,14 +2,15 @@ package textual
import (
"context"
"errors"
"fmt"
"sort"
"strings"
"google.golang.org/protobuf/reflect/protoreflect"
bankv1beta1 "cosmossdk.io/api/cosmos/bank/v1beta1"
basev1beta1 "cosmossdk.io/api/cosmos/base/v1beta1"
corecoins "cosmossdk.io/core/coins"
"cosmossdk.io/math"
)
@ -32,7 +33,7 @@ var _ RepeatedValueRenderer = coinsValueRenderer{}
func (vr coinsValueRenderer) Format(ctx context.Context, v protoreflect.Value) ([]Screen, error) {
if vr.coinMetadataQuerier == nil {
return nil, fmt.Errorf("expected non-nil coin metadata querier")
return nil, errors.New("expected non-nil coin metadata querier")
}
// Since this value renderer has a FormatRepeated method, the Format one
@ -48,7 +49,7 @@ func (vr coinsValueRenderer) Format(ctx context.Context, v protoreflect.Value) (
return nil, err
}
formatted, err := corecoins.FormatCoins([]*basev1beta1.Coin{coin}, []*bankv1beta1.Metadata{metadata})
formatted, err := FormatCoins([]*basev1beta1.Coin{coin}, []*bankv1beta1.Metadata{metadata})
if err != nil {
return nil, err
}
@ -58,7 +59,7 @@ func (vr coinsValueRenderer) Format(ctx context.Context, v protoreflect.Value) (
func (vr coinsValueRenderer) FormatRepeated(ctx context.Context, v protoreflect.Value) ([]Screen, error) {
if vr.coinMetadataQuerier == nil {
return nil, fmt.Errorf("expected non-nil coin metadata querier")
return nil, errors.New("expected non-nil coin metadata querier")
}
protoCoins := v.List()
@ -76,7 +77,7 @@ func (vr coinsValueRenderer) FormatRepeated(ctx context.Context, v protoreflect.
}
}
formatted, err := corecoins.FormatCoins(coins, metadatas)
formatted, err := FormatCoins(coins, metadatas)
if err != nil {
return nil, err
}
@ -217,3 +218,92 @@ func parseCoin(coinStr string, metadata *bankv1beta1.Metadata) (*basev1beta1.Coi
Denom: baseDenom,
}, nil
}
// formatCoin formats a sdk.Coin into a value-rendered string, using the
// given metadata about the denom. It returns the formatted coin string, the
// display denom, and an optional error.
func formatCoin(coin *basev1beta1.Coin, metadata *bankv1beta1.Metadata) (string, error) {
coinDenom := coin.Denom
// Return early if no display denom or display denom is the current coin denom.
if metadata == nil || metadata.Display == "" || coinDenom == metadata.Display {
vr, err := math.FormatDec(coin.Amount)
return vr + " " + coin.Denom, err
}
dispDenom := metadata.Display
// Find exponents of both denoms.
var coinExp, dispExp uint32
foundCoinExp, foundDispExp := false, false
for _, unit := range metadata.DenomUnits {
if coinDenom == unit.Denom {
coinExp = unit.Exponent
foundCoinExp = true
}
if dispDenom == unit.Denom {
dispExp = unit.Exponent
foundDispExp = true
}
}
// If we didn't find either exponent, then we return early.
if !foundCoinExp || !foundDispExp {
vr, err := math.FormatInt(coin.Amount)
return vr + " " + coin.Denom, err
}
dispAmount, err := math.LegacyNewDecFromStr(coin.Amount)
if err != nil {
return "", err
}
if coinExp > dispExp {
dispAmount = dispAmount.Mul(math.LegacyNewDec(10).Power(uint64(coinExp - dispExp)))
} else {
dispAmount = dispAmount.Quo(math.LegacyNewDec(10).Power(uint64(dispExp - coinExp)))
}
vr, err := math.FormatDec(dispAmount.String())
return vr + " " + dispDenom, err
}
// FormatCoins formats Coins into a value-rendered string, which uses
// `formatCoin` separated by ", " (a comma and a space), and sorted
// alphabetically by value-rendered denoms. It expects an array of metadata
// (optionally nil), where each metadata at index `i` MUST match the coin denom
// at the same index.
func FormatCoins(coins []*basev1beta1.Coin, metadata []*bankv1beta1.Metadata) (string, error) {
if len(coins) != len(metadata) {
return "", fmt.Errorf("formatCoins expect one metadata for each coin; expected %d, got %d", len(coins), len(metadata))
}
formatted := make([]string, len(coins))
for i, coin := range coins {
var err error
formatted[i], err = formatCoin(coin, metadata[i])
if err != nil {
return "", err
}
// If a coin contains a comma, return an error given that the output
// could be misinterpreted by the user as 2 different coins.
if strings.Contains(formatted[i], ",") {
return "", fmt.Errorf("coin %s contains a comma", formatted[i])
}
}
if len(coins) == 0 {
return emptyCoins, nil
}
// Sort the formatted coins by display denom.
sort.SliceStable(formatted, func(i, j int) bool {
denomI := strings.Split(formatted[i], " ")[1]
denomJ := strings.Split(formatted[j], " ")[1]
return denomI < denomJ
})
return strings.Join(formatted, ", "), nil
}

View File

@ -66,12 +66,13 @@ func TestCoinsJSONTestcases(t *testing.T) {
// rendering, so we lose initial Coins ordering. Instead, we just check
// set equality using a map.
func checkCoinsEqual(t *testing.T, l1, l2 protoreflect.List) {
t.Helper()
require.Equal(t, l1.Len(), l2.Len())
coinsMap := make(map[string]*basev1beta1.Coin, l1.Len())
for i := 0; i < l1.Len(); i++ {
coin, ok := l1.Get(i).Message().Interface().(*basev1beta1.Coin)
require.True(t, ok)
require.True(t, ok, "not a *basev1beta1.Coin: %#v", l1.Get(i).Message().Interface())
coinsMap[coin.Denom] = coin
}
@ -85,12 +86,13 @@ func checkCoinsEqual(t *testing.T, l1, l2 protoreflect.List) {
}
func checkCoinEqual(t *testing.T, coin, coin1 *basev1beta1.Coin) {
t.Helper()
require.Equal(t, coin1.Denom, coin.Denom)
v, ok := math.NewIntFromString(coin.Amount)
require.True(t, ok)
v1, ok := math.NewIntFromString(coin1.Amount)
require.True(t, ok)
require.True(t, v.Equal(v1))
require.True(t, v.Equal(v1), "Mismatch\n\tv: %+v\n\tv1: %+v", v, v1)
}
// coinsJSONTest is the type of test cases in the testdata file.
@ -104,3 +106,64 @@ type coinsJSONTest struct {
Text string
Error bool
}
// formatCoinJSONTest is the type of test cases in the coin.json file.
type formatCoinJSONTest struct {
Proto *basev1beta1.Coin
Metadata *bankv1beta1.Metadata
Text string
Error bool
}
func TestFormatCoin(t *testing.T) {
var testcases []formatCoinJSONTest
raw, err := os.ReadFile("./internal/testdata/coin.json")
require.NoError(t, err)
err = json.Unmarshal(raw, &testcases)
require.NoError(t, err)
for _, tc := range testcases {
t.Run(tc.Text, func(t *testing.T) {
if tc.Proto != nil {
out, err := textual.FormatCoins([]*basev1beta1.Coin{tc.Proto}, []*bankv1beta1.Metadata{tc.Metadata})
if tc.Error {
require.Error(t, err)
return
}
require.NoError(t, err)
require.Equal(t, tc.Text, out)
}
})
}
}
func TestFormatCoins(t *testing.T) {
var testcases []coinsJSONTest
raw, err := os.ReadFile("./internal/testdata/coins.json")
require.NoError(t, err)
err = json.Unmarshal(raw, &testcases)
require.NoError(t, err)
for _, tc := range testcases {
t.Run(tc.Text, func(t *testing.T) {
if tc.Proto != nil {
metadata := make([]*bankv1beta1.Metadata, len(tc.Proto))
for i, coin := range tc.Proto {
metadata[i] = tc.Metadata[coin.Denom]
}
out, err := textual.FormatCoins(tc.Proto, metadata)
if tc.Error {
require.Error(t, err)
return
}
require.NoError(t, err)
require.Equal(t, tc.Text, out)
}
})
}
}

View File

@ -38,6 +38,7 @@ func TestDecJSONTestcases(t *testing.T) {
}
func checkDecTest(t *testing.T, r textual.ValueRenderer, pv protoreflect.Value, expected string) {
t.Helper()
screens, err := r.Format(context.Background(), pv)
require.NoError(t, err)
require.Len(t, screens, 1)

View File

@ -71,6 +71,10 @@ func TestE2EJSONTestcases(t *testing.T) {
AuthInfoBytes: authInfoBz,
})
require.NoError(t, err)
decodeWant, err := hex.DecodeString(tc.Cbor)
require.NoError(t, err)
t.Log("got: " + string(signDoc))
t.Log("want " + string(decodeWant))
require.Equal(t, tc.Cbor, hex.EncodeToString(signDoc))
})
}

View File

@ -1,14 +1,20 @@
package textual_test
import (
"bytes"
"context"
"encoding/json"
"os"
"regexp"
"testing"
"github.com/google/go-cmp/cmp"
"google.golang.org/protobuf/reflect/protoreflect"
"google.golang.org/protobuf/testing/protocmp"
tspb "google.golang.org/protobuf/types/known/timestamppb"
basev1beta1 "cosmossdk.io/api/cosmos/base/v1beta1"
"cosmossdk.io/x/tx/internal/testpb"
"cosmossdk.io/x/tx/signing/textual"
)
@ -101,3 +107,195 @@ func FuzzTimestampJSONParseToParseRoundTrip(f *testing.F) {
}
})
}
func FuzzBytesValueRendererParse(f *testing.F) {
// 1. Generate some seeds from testdata.
seed, err := os.ReadFile("./internal/testdata/bytes.json")
if err != nil {
f.Fatal(err)
}
f.Add(seed)
tr, err := textual.NewSignModeHandler(textual.SignModeOptions{CoinMetadataQuerier: EmptyCoinMetadataQuerier})
if err != nil {
f.Fatal(err)
}
ctx := context.Background()
f.Fuzz(func(t *testing.T, input []byte) {
var testCases []bytesTest
if err := json.Unmarshal(input, &testCases); err != nil {
return
}
for _, tc := range testCases {
rend, err := tr.GetFieldValueRenderer(fieldDescriptorFromName("BYTES"))
if err != nil {
t.Fatal(err)
}
screens, err := rend.Format(ctx, protoreflect.ValueOfBytes(tc.base64))
if err != nil {
t.Fatal(err)
}
if g, w := len(screens), 1; g != w {
t.Fatalf("Mismatch screen count: got=%d, want=%d", g, w)
}
// Round trip and test.
val, err := rend.Parse(ctx, screens)
if err != nil {
t.Fatal(err)
}
if g, w := len(tc.base64), 35; g > w {
if len(val.Bytes()) != 0 {
t.Fatalf("val.Bytes() != 0:\n\tGot: % x", val.Bytes())
}
} else if !bytes.Equal(tc.base64, val.Bytes()) {
t.Fatalf("val.Bytes() mismatch:\n\tGot: % x\n\tWant: % x", val.Bytes(), tc.base64)
}
}
})
}
func FuzzMessageValueRendererParse(f *testing.F) {
if testing.Short() {
f.Skip()
}
// 1. Use the seeds from testdata and mutate them.
seed, err := os.ReadFile("./internal/testdata/message.json")
if err != nil {
f.Fatal(err)
}
f.Add(seed)
ctx := context.Background()
tr, err := textual.NewSignModeHandler(textual.SignModeOptions{CoinMetadataQuerier: EmptyCoinMetadataQuerier})
if err != nil {
f.Fatalf("Failed to create SignModeHandler: %v", err)
}
f.Fuzz(func(t *testing.T, input []byte) {
var testCases []messageJSONTest
if err := json.Unmarshal(input, &testCases); err != nil {
return
}
for _, tc := range testCases {
rend := textual.NewMessageValueRenderer(tr, (&testpb.Foo{}).ProtoReflect().Descriptor())
var screens []textual.Screen
var err error
if tc.Proto != nil {
screens, err = rend.Format(ctx, protoreflect.ValueOf(tc.Proto.ProtoReflect()))
if err != nil {
continue
}
}
val, err := rend.Parse(ctx, screens)
if err != nil {
continue
}
msg := val.Message().Interface()
gotMsg, ok := msg.(*testpb.Foo)
if !ok {
t.Fatalf("Wrong type for Foo: %T", msg)
}
diff := cmp.Diff(gotMsg, tc.Proto, protocmp.Transform())
if diff != "" {
t.Fatalf("Roundtrip mismatch\n\tGot: %#v\n\tWant: %#v", gotMsg, tc.Proto)
}
}
})
}
// Copied from types/coin.go but pasted in here so as to avoid any imports
// of that package as has been mandated by team decisions.
var (
reCoinDenom = regexp.MustCompile(`[a-zA-Z][a-zA-Z0-9/:._-]{2,127}`)
reCoinAmount = regexp.MustCompile(`[[:digit:]]+(?:\.[[:digit:]]+)?|\.[[:digit:]]+`)
)
func FuzzCoinsJSONTestcases(f *testing.F) {
f.Skip() // https://github.com/cosmos/cosmos-sdk/pull/16521#issuecomment-1614507574
// Generate some seeds.
seed, err := os.ReadFile("./internal/testdata/coins.json")
if err != nil {
f.Fatal(err)
}
f.Add(seed)
txt, err := textual.NewSignModeHandler(textual.SignModeOptions{CoinMetadataQuerier: mockCoinMetadataQuerier})
if err != nil {
f.Fatal(err)
}
rend, err := txt.GetFieldValueRenderer(fieldDescriptorFromName("COINS"))
if err != nil {
f.Fatal(err)
}
vrr := rend.(textual.RepeatedValueRenderer)
f.Fuzz(func(t *testing.T, input []byte) {
var testCases []coinsJSONTest
if err := json.Unmarshal(input, &testCases); err != nil {
return
}
for _, tc := range testCases {
if tc.Proto == nil {
continue
}
// Create a context.Context containing all coins metadata, to simulate
// that they are in state.
ctx := context.Background()
for _, v := range tc.Metadata {
ctx = addMetadataToContext(ctx, v)
}
listValue := NewGenericList(tc.Proto)
screens, err := vrr.FormatRepeated(ctx, protoreflect.ValueOf(listValue))
if err != nil {
cpt := tc.Proto[0]
likeEmpty := err.Error() == "cannot format empty string" || err.Error() == "decimal string cannot be empty"
if likeEmpty && (!reCoinDenom.MatchString(cpt.Denom) || cpt.Amount == "") {
return
}
if !reCoinDenom.MatchString(cpt.Denom) {
return
}
if !reCoinAmount.MatchString(cpt.Amount) {
return
}
t.Fatalf("%v\n%q\n%#v => %t", err, tc.Text, cpt, cpt.Amount == "")
}
if g, w := len(screens), 1; g != w {
t.Fatalf("Screens mismatch: got=%d want=%d", g, w)
}
wantContent := tc.Text
if wantContent == "" {
wantContent = "zero"
}
if false {
if g, w := screens[0].Content, wantContent; g != w {
t.Fatalf("Content mismatch:\n\tGot: %s\n\tWant: %s", g, w)
}
}
// Round trip.
parsedValue := NewGenericList([]*basev1beta1.Coin{})
if err := vrr.ParseRepeated(ctx, screens, parsedValue); err != nil {
return
}
checkCoinsEqual(t, listValue, parsedValue)
}
})
}

View File

@ -3,10 +3,12 @@ package textual
import (
"bytes"
"context"
"errors"
"fmt"
"reflect"
cosmos_proto "github.com/cosmos/cosmos-proto"
gogoproto "github.com/cosmos/gogoproto/proto"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/reflect/protoreflect"
"google.golang.org/protobuf/reflect/protoregistry"
@ -67,10 +69,10 @@ type SignModeHandler struct {
// NewSignModeHandler returns a new SignModeHandler which generates sign bytes and provides value renderers.
func NewSignModeHandler(o SignModeOptions) (*SignModeHandler, error) {
if o.CoinMetadataQuerier == nil {
return nil, fmt.Errorf("coinMetadataQuerier must be non-empty")
return nil, errors.New("coinMetadataQuerier must be non-empty")
}
if o.FileResolver == nil {
o.FileResolver = protoregistry.GlobalFiles
o.FileResolver = gogoproto.HybridResolver
}
if o.TypeResolver == nil {
o.TypeResolver = protoregistry.GlobalTypes
@ -134,7 +136,7 @@ func (r *SignModeHandler) GetFieldValueRenderer(fd protoreflect.FieldDescriptor)
}
if fd.IsMap() {
return nil, fmt.Errorf("value renderers cannot format value of type map")
return nil, errors.New("value renderers cannot format value of type map")
}
return NewMessageValueRenderer(r, md), nil
case fd.Kind() == protoreflect.BoolKind:

View File

@ -78,6 +78,7 @@ func TestIntJSONTestcases(t *testing.T) {
// checkNumberTest checks that the output of a number value renderer
// matches the expected string. Only use it to test numbers.
func checkNumberTest(t *testing.T, r textual.ValueRenderer, pv protoreflect.Value, expected string) {
t.Helper()
screens, err := r.Format(context.Background(), pv)
require.NoError(t, err)
require.Len(t, screens, 1)

View File

@ -327,5 +327,10 @@
{"text":"", "error": true},
{"text":"1COSM", "error": true},
{"text":"1 COSM", "error": true},
{"text":" 1 COSM", "error": true}
{"text":" 1 COSM", "error": true},
{
"proto": {"amount": "10000000", "denom": "point, 222222 point"},
"metadata": {"display": "POINT", "base": "point", "denom_units": [{"denom": "point", "exponent": 0}, {"denom": "POINT", "exponent": 0}]},
"error": true
}
]

File diff suppressed because one or more lines are too long

View File

@ -299,13 +299,6 @@
"gas_limit": 100000,
"payer": "cosmos1ejrf4cur2wy6kfurg9f2jppp2h3afe5h6pkh5t",
"granter": "cosmos1ulav3hsenupswqfkw2y3sup5kgtqwnvqa8eyhs"
},
"tip": {
"amount": [
{ "amount": "20000", "denom": "uatom" },
{ "amount": "30000", "denom": "uosmo" }
],
"tipper": "cosmos1ejrf4cur2wy6kfurg9f2jppp2h3afe5h6pkh5t"
}
}
},
@ -357,8 +350,6 @@
{ "title": "Fees", "content": "0.002 ATOM" },
{ "title": "Fee payer", "content": "cosmos1ejrf4cur2wy6kfurg9f2jppp2h3afe5h6pkh5t", "expert": true },
{ "title": "Fee granter", "content": "cosmos1ulav3hsenupswqfkw2y3sup5kgtqwnvqa8eyhs", "expert": true },
{ "title": "Tip", "content": "0.02 ATOM, 30'000 uosmo" },
{ "title": "Tipper", "content": "cosmos1ejrf4cur2wy6kfurg9f2jppp2h3afe5h6pkh5t" },
{ "title": "Gas limit", "content": "100'000", "expert": true },
{ "title": "Timeout height", "content": "20", "expert": true },
{ "title": "Other signer", "content": "1 SignerInfo", "expert": true },
@ -394,7 +385,7 @@
{ "title": "Non critical extension options (1/1)", "content": "/cosmos.auth.v1beta1.Params", "indent": 1, "expert": true },
{ "title": "Max memo characters", "content": "10", "indent": 2, "expert": true },
{ "content": "End of Non critical extension options", "expert": true },
{ "title": "Hash of raw bytes", "content": "7ea02e8f0baed2db969e2d9ae4dc51fa31116259bd42897588072faf0ebb4d2e", "expert": true }
{ "title": "Hash of raw bytes", "content": "e7be7808de4985bd609811d2a32805cb233c168c7d247d61d37f4a6dd4cf3a2a", "expert": true }
]
}
]

View File

@ -77,8 +77,6 @@ message Envelope {
repeated cosmos.base.v1beta1.Coin fees = 8;
string fee_payer = 9;
string fee_granter = 10;
repeated cosmos.base.v1beta1.Coin tip = 11;
string tipper = 12;
uint64 gas_limit = 13;
uint64 timeout_height = 14;
repeated cosmos.tx.v1beta1.SignerInfo other_signer = 15;

View File

@ -0,0 +1,2 @@
go test fuzz v1
[]byte("[{\"proto\":[{\"Amount\":\"0\"},{\"Amount\":\"1\"}]}]")

View File

@ -43,6 +43,7 @@ func TestTimestampJsonTestcasesExtraneousNanos(t *testing.T) {
}
func testTimestampJSONTestcases(t *testing.T, raw []byte) {
t.Helper()
var testcases []timestampJSONTest
err := json.Unmarshal(raw, &testcases)
require.NoError(t, err)

View File

@ -83,10 +83,7 @@ func (vr txValueRenderer) Format(ctx context.Context, v protoreflect.Value) ([]S
NonCriticalExtensionOptions: txBody.NonCriticalExtensionOptions,
HashOfRawBytes: getHash(textualData.BodyBytes, textualData.AuthInfoBytes),
}
if txAuthInfo.Tip != nil { //nolint:staticcheck // we still need this deprecated struct
envelope.Tip = txAuthInfo.Tip.Amount //nolint:staticcheck // we still need this deprecated struct
envelope.Tipper = txAuthInfo.Tip.Tipper //nolint:staticcheck // we still need this deprecated struct
}
// Find all other tx signers than the current signer. In the case where our
// Textual signer is one key of a multisig, then otherSigners will include
// the multisig pubkey.
@ -243,12 +240,6 @@ func (vr txValueRenderer) Parse(ctx context.Context, screens []Screen) (protoref
Granter: envelope.FeeGranter,
},
}
if envelope.Tip != nil {
authInfo.Tip = &txv1beta1.Tip{ //nolint:staticcheck // we still need this deprecated struct
Amount: envelope.Tip,
Tipper: envelope.Tipper,
}
}
// Figure out the signers in the correct order.
signers, err := getSigners(txBody, authInfo)
@ -281,11 +272,12 @@ func (vr txValueRenderer) Parse(ctx context.Context, screens []Screen) (protoref
// Note that we might not always get back the exact bodyBz and authInfoBz
// that was passed into, because protobuf is not deterministic.
// In tests, we don't check bytes equality, but protobuf object equality.
bodyBz, err := proto.Marshal(txBody)
protov2MarshalOpts := proto.MarshalOptions{Deterministic: true}
bodyBz, err := protov2MarshalOpts.Marshal(txBody)
if err != nil {
return nilValue, err
}
authInfoBz, err := proto.Marshal(authInfo)
authInfoBz, err := protov2MarshalOpts.Marshal(authInfo)
if err != nil {
return nilValue, err
}

View File

@ -117,6 +117,7 @@ func TestTxJSONTestcases(t *testing.T) {
// createTextualData creates a Textual data give then JSON
// test case.
func createTextualData(t *testing.T, jsonTx txJSONTestTx, jsonSignerData json.RawMessage) (*txv1beta1.TxBody, []byte, *txv1beta1.AuthInfo, []byte, signing.SignerData) {
t.Helper()
body := &txv1beta1.TxBody{}
authInfo := &txv1beta1.AuthInfo{}
protoSignerData := &textualpb.SignerData{}