fix(x/tx): fix amino json drift from legacy spec (backport #21825) (#22088)

This commit is contained in:
mergify[bot]
2024-12-12 02:13:57 +01:00
committed by GitHub
parent 4a73a1ecef
commit d62bcbdb55
12 changed files with 139 additions and 60 deletions
+1 -1
View File
@@ -11,7 +11,7 @@ require (
cosmossdk.io/log v1.4.1 cosmossdk.io/log v1.4.1
cosmossdk.io/math v1.4.0 cosmossdk.io/math v1.4.0
cosmossdk.io/store v1.1.1 cosmossdk.io/store v1.1.1
cosmossdk.io/x/tx v0.13.5 cosmossdk.io/x/tx v0.13.6-0.20241003112805-ff8789a02871
github.com/99designs/keyring v1.2.1 github.com/99designs/keyring v1.2.1
github.com/bgentry/speakeasy v0.1.1-0.20220910012023-760eaf8b6816 github.com/bgentry/speakeasy v0.1.1-0.20220910012023-760eaf8b6816
github.com/bits-and-blooms/bitset v1.8.0 github.com/bits-and-blooms/bitset v1.8.0
+2 -2
View File
@@ -16,8 +16,8 @@ cosmossdk.io/math v1.4.0 h1:XbgExXFnXmF/CccPPEto40gOO7FpWu9yWNAZPN3nkNQ=
cosmossdk.io/math v1.4.0/go.mod h1:O5PkD4apz2jZs4zqFdTr16e1dcaQCc5z6lkEnrrppuk= cosmossdk.io/math v1.4.0/go.mod h1:O5PkD4apz2jZs4zqFdTr16e1dcaQCc5z6lkEnrrppuk=
cosmossdk.io/store v1.1.1 h1:NA3PioJtWDVU7cHHeyvdva5J/ggyLDkyH0hGHl2804Y= cosmossdk.io/store v1.1.1 h1:NA3PioJtWDVU7cHHeyvdva5J/ggyLDkyH0hGHl2804Y=
cosmossdk.io/store v1.1.1/go.mod h1:8DwVTz83/2PSI366FERGbWSH7hL6sB7HbYp8bqksNwM= cosmossdk.io/store v1.1.1/go.mod h1:8DwVTz83/2PSI366FERGbWSH7hL6sB7HbYp8bqksNwM=
cosmossdk.io/x/tx v0.13.5 h1:FdnU+MdmFWn1pTsbfU0OCf2u6mJ8cqc1H4OMG418MLw= cosmossdk.io/x/tx v0.13.6-0.20241003112805-ff8789a02871 h1:+lRwWQRVvB3jgRgdqrgeFUJ45BoXZh/UeeAV5f/m2Gk=
cosmossdk.io/x/tx v0.13.5/go.mod h1:V6DImnwJMTq5qFjeGWpXNiT/fjgE4HtmclRmTqRVM3w= cosmossdk.io/x/tx v0.13.6-0.20241003112805-ff8789a02871/go.mod h1:V6DImnwJMTq5qFjeGWpXNiT/fjgE4HtmclRmTqRVM3w=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
filippo.io/edwards25519 v1.0.0 h1:0wAIcmJUqRdI8IJ/3eGi5/HwXZWPujYXXlkrQogz0Ek= filippo.io/edwards25519 v1.0.0 h1:0wAIcmJUqRdI8IJ/3eGi5/HwXZWPujYXXlkrQogz0Ek=
filippo.io/edwards25519 v1.0.0/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns= filippo.io/edwards25519 v1.0.0/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns=
+1 -1
View File
@@ -16,7 +16,7 @@ require (
cosmossdk.io/x/evidence v0.1.1 cosmossdk.io/x/evidence v0.1.1
cosmossdk.io/x/feegrant v0.1.1 cosmossdk.io/x/feegrant v0.1.1
cosmossdk.io/x/nft v0.1.1 cosmossdk.io/x/nft v0.1.1
cosmossdk.io/x/tx v0.13.5 cosmossdk.io/x/tx v0.13.6-0.20241003112805-ff8789a02871
cosmossdk.io/x/upgrade v0.1.4 cosmossdk.io/x/upgrade v0.1.4
github.com/cometbft/cometbft v0.38.12 github.com/cometbft/cometbft v0.38.12
github.com/cosmos/cosmos-db v1.1.0 github.com/cosmos/cosmos-db v1.1.0
+2 -2
View File
@@ -210,8 +210,8 @@ cosmossdk.io/x/feegrant v0.1.1 h1:EKFWOeo/pup0yF0svDisWWKAA9Zags6Zd0P3nRvVvw8=
cosmossdk.io/x/feegrant v0.1.1/go.mod h1:2GjVVxX6G2fta8LWj7pC/ytHjryA6MHAJroBWHFNiEQ= cosmossdk.io/x/feegrant v0.1.1/go.mod h1:2GjVVxX6G2fta8LWj7pC/ytHjryA6MHAJroBWHFNiEQ=
cosmossdk.io/x/nft v0.1.1 h1:pslAVS8P5NkW080+LWOamInjDcq+v2GSCo+BjN9sxZ8= cosmossdk.io/x/nft v0.1.1 h1:pslAVS8P5NkW080+LWOamInjDcq+v2GSCo+BjN9sxZ8=
cosmossdk.io/x/nft v0.1.1/go.mod h1:Kac6F6y2gsKvoxU+fy8uvxRTi4BIhLOor2zgCNQwVgY= cosmossdk.io/x/nft v0.1.1/go.mod h1:Kac6F6y2gsKvoxU+fy8uvxRTi4BIhLOor2zgCNQwVgY=
cosmossdk.io/x/tx v0.13.5 h1:FdnU+MdmFWn1pTsbfU0OCf2u6mJ8cqc1H4OMG418MLw= cosmossdk.io/x/tx v0.13.6-0.20241003112805-ff8789a02871 h1:+lRwWQRVvB3jgRgdqrgeFUJ45BoXZh/UeeAV5f/m2Gk=
cosmossdk.io/x/tx v0.13.5/go.mod h1:V6DImnwJMTq5qFjeGWpXNiT/fjgE4HtmclRmTqRVM3w= cosmossdk.io/x/tx v0.13.6-0.20241003112805-ff8789a02871/go.mod h1:V6DImnwJMTq5qFjeGWpXNiT/fjgE4HtmclRmTqRVM3w=
cosmossdk.io/x/upgrade v0.1.4 h1:/BWJim24QHoXde8Bc64/2BSEB6W4eTydq0X/2f8+g38= cosmossdk.io/x/upgrade v0.1.4 h1:/BWJim24QHoXde8Bc64/2BSEB6W4eTydq0X/2f8+g38=
cosmossdk.io/x/upgrade v0.1.4/go.mod h1:9v0Aj+fs97O+Ztw+tG3/tp5JSlrmT7IcFhAebQHmOPo= cosmossdk.io/x/upgrade v0.1.4/go.mod h1:9v0Aj+fs97O+Ztw+tG3/tp5JSlrmT7IcFhAebQHmOPo=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
+2 -1
View File
@@ -14,7 +14,7 @@ require (
cosmossdk.io/x/evidence v0.1.1 cosmossdk.io/x/evidence v0.1.1
cosmossdk.io/x/feegrant v0.1.1 cosmossdk.io/x/feegrant v0.1.1
cosmossdk.io/x/nft v0.1.1 // indirect cosmossdk.io/x/nft v0.1.1 // indirect
cosmossdk.io/x/tx v0.13.5 cosmossdk.io/x/tx v0.13.6-0.20241003112805-ff8789a02871
cosmossdk.io/x/upgrade v0.1.4 cosmossdk.io/x/upgrade v0.1.4
github.com/cometbft/cometbft v0.38.12 github.com/cometbft/cometbft v0.38.12
github.com/cosmos/cosmos-db v1.1.0 github.com/cosmos/cosmos-db v1.1.0
@@ -202,6 +202,7 @@ require (
// replace ( // replace (
// <temporary replace> // <temporary replace>
// ) // )
replace cosmossdk.io/x/tx => ../x/tx
// Below are the long-lived replace for tests. // Below are the long-lived replace for tests.
replace ( replace (
-2
View File
@@ -208,8 +208,6 @@ cosmossdk.io/x/feegrant v0.1.1 h1:EKFWOeo/pup0yF0svDisWWKAA9Zags6Zd0P3nRvVvw8=
cosmossdk.io/x/feegrant v0.1.1/go.mod h1:2GjVVxX6G2fta8LWj7pC/ytHjryA6MHAJroBWHFNiEQ= cosmossdk.io/x/feegrant v0.1.1/go.mod h1:2GjVVxX6G2fta8LWj7pC/ytHjryA6MHAJroBWHFNiEQ=
cosmossdk.io/x/nft v0.1.1 h1:pslAVS8P5NkW080+LWOamInjDcq+v2GSCo+BjN9sxZ8= cosmossdk.io/x/nft v0.1.1 h1:pslAVS8P5NkW080+LWOamInjDcq+v2GSCo+BjN9sxZ8=
cosmossdk.io/x/nft v0.1.1/go.mod h1:Kac6F6y2gsKvoxU+fy8uvxRTi4BIhLOor2zgCNQwVgY= cosmossdk.io/x/nft v0.1.1/go.mod h1:Kac6F6y2gsKvoxU+fy8uvxRTi4BIhLOor2zgCNQwVgY=
cosmossdk.io/x/tx v0.13.5 h1:FdnU+MdmFWn1pTsbfU0OCf2u6mJ8cqc1H4OMG418MLw=
cosmossdk.io/x/tx v0.13.5/go.mod h1:V6DImnwJMTq5qFjeGWpXNiT/fjgE4HtmclRmTqRVM3w=
cosmossdk.io/x/upgrade v0.1.4 h1:/BWJim24QHoXde8Bc64/2BSEB6W4eTydq0X/2f8+g38= cosmossdk.io/x/upgrade v0.1.4 h1:/BWJim24QHoXde8Bc64/2BSEB6W4eTydq0X/2f8+g38=
cosmossdk.io/x/upgrade v0.1.4/go.mod h1:9v0Aj+fs97O+Ztw+tG3/tp5JSlrmT7IcFhAebQHmOPo= cosmossdk.io/x/upgrade v0.1.4/go.mod h1:9v0Aj+fs97O+Ztw+tG3/tp5JSlrmT7IcFhAebQHmOPo=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
-1
View File
@@ -231,7 +231,6 @@ var (
NonsignableTypes = []GeneratedType{ NonsignableTypes = []GeneratedType{
GenType(&authtypes.Params{}, &authapi.Params{}, GenOpts), GenType(&authtypes.Params{}, &authapi.Params{}, GenOpts),
GenType(&authtypes.BaseAccount{}, &authapi.BaseAccount{}, GenOpts.WithAnyTypes(&ed25519.PubKey{})), GenType(&authtypes.BaseAccount{}, &authapi.BaseAccount{}, GenOpts.WithAnyTypes(&ed25519.PubKey{})),
GenType(&authtypes.ModuleAccount{}, &authapi.ModuleAccount{}, GenOpts.WithAnyTypes(&ed25519.PubKey{})),
GenType(&authtypes.ModuleCredential{}, &authapi.ModuleCredential{}, GenOpts), GenType(&authtypes.ModuleCredential{}, &authapi.ModuleCredential{}, GenOpts),
GenType(&authztypes.GenericAuthorization{}, &authzapi.GenericAuthorization{}, GenOpts), GenType(&authztypes.GenericAuthorization{}, &authzapi.GenericAuthorization{}, GenOpts),
@@ -2,6 +2,7 @@ package aminojson
import ( import (
"context" "context"
"encoding/json"
"fmt" "fmt"
"reflect" "reflect"
"testing" "testing"
@@ -97,7 +98,7 @@ func TestAminoJSON_Equivalence(t *testing.T) {
gov.AppModuleBasic{}, groupmodule.AppModuleBasic{}, mint.AppModuleBasic{}, params.AppModuleBasic{}, gov.AppModuleBasic{}, groupmodule.AppModuleBasic{}, mint.AppModuleBasic{}, params.AppModuleBasic{},
slashing.AppModuleBasic{}, staking.AppModuleBasic{}, upgrade.AppModuleBasic{}, vesting.AppModuleBasic{}) slashing.AppModuleBasic{}, staking.AppModuleBasic{}, upgrade.AppModuleBasic{}, vesting.AppModuleBasic{})
legacytx.RegressionTestingAminoCodec = encCfg.Amino legacytx.RegressionTestingAminoCodec = encCfg.Amino
aj := aminojson.NewEncoder(aminojson.EncoderOptions{DoNotSortFields: true}) aj := aminojson.NewEncoder(aminojson.EncoderOptions{})
for _, tt := range rapidgen.DefaultGeneratedTypes { for _, tt := range rapidgen.DefaultGeneratedTypes {
desc := tt.Pulsar.ProtoReflect().Descriptor() desc := tt.Pulsar.ProtoReflect().Descriptor()
@@ -131,6 +132,7 @@ func TestAminoJSON_Equivalence(t *testing.T) {
legacyAminoJSON, err := encCfg.Amino.MarshalJSON(gogo) legacyAminoJSON, err := encCfg.Amino.MarshalJSON(gogo)
require.NoError(t, err) require.NoError(t, err)
legacyAminoJSON = sortJSON(t, legacyAminoJSON)
aminoJSON, err := aj.Marshal(msg) aminoJSON, err := aj.Marshal(msg)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, string(legacyAminoJSON), string(aminoJSON)) require.Equal(t, string(legacyAminoJSON), string(aminoJSON))
@@ -219,19 +221,21 @@ func TestAminoJSON_LegacyParity(t *testing.T) {
// represent the array as nil, and a subsequent marshal to JSON represent the array as null instead of empty. // represent the array as nil, and a subsequent marshal to JSON represent the array as null instead of empty.
roundTripUnequal bool roundTripUnequal bool
// pulsar does not support marshaling a math.Dec as anything except a string. Therefore, we cannot unmarshal // sort JSON bytes before comparison. for certain types (like ModuleAccount) x/tx is not able to provide an
// a pulsar encoded Math.dec (the string representation of a Decimal) into a gogo Math.dec (expecting an int64). // unsorted version. note that the legacy amino signer always sorted JSON bytes by round tripping them to/from
protoUnmarshalFails bool // JSON before signing over them.
sortJSON bool
}{ }{
"auth/params": {gogo: &authtypes.Params{TxSigLimit: 10}, pulsar: &authapi.Params{TxSigLimit: 10}}, "auth/params": {gogo: &authtypes.Params{TxSigLimit: 10}, pulsar: &authapi.Params{TxSigLimit: 10}},
"auth/module_account": { "auth/module_account": {
gogo: &authtypes.ModuleAccount{ gogo: &authtypes.ModuleAccount{
BaseAccount: authtypes.NewBaseAccountWithAddress(addr1), Permissions: []string{}, BaseAccount: authtypes.NewBaseAccountWithAddress(addr1),
}, },
pulsar: &authapi.ModuleAccount{ pulsar: &authapi.ModuleAccount{
BaseAccount: &authapi.BaseAccount{Address: addr1.String()}, Permissions: []string{}, BaseAccount: &authapi.BaseAccount{Address: addr1.String()},
}, },
roundTripUnequal: true, roundTripUnequal: true,
sortJSON: true,
}, },
"auth/base_account": { "auth/base_account": {
gogo: &authtypes.BaseAccount{Address: addr1.String(), PubKey: pubkeyAny}, gogo: &authtypes.BaseAccount{Address: addr1.String(), PubKey: pubkeyAny},
@@ -258,9 +262,8 @@ func TestAminoJSON_LegacyParity(t *testing.T) {
pulsar: &distapi.DelegatorStartingInfo{}, pulsar: &distapi.DelegatorStartingInfo{},
}, },
"distribution/delegator_starting_info/non_zero_dec": { "distribution/delegator_starting_info/non_zero_dec": {
gogo: &disttypes.DelegatorStartingInfo{Stake: math.LegacyNewDec(10)}, gogo: &disttypes.DelegatorStartingInfo{Stake: math.LegacyNewDec(10)},
pulsar: &distapi.DelegatorStartingInfo{Stake: "10.000000000000000000"}, pulsar: &distapi.DelegatorStartingInfo{Stake: string(dec10bz)},
protoUnmarshalFails: true,
}, },
"distribution/delegation_delegator_reward": { "distribution/delegation_delegator_reward": {
gogo: &disttypes.DelegationDelegatorReward{}, gogo: &disttypes.DelegationDelegatorReward{},
@@ -282,13 +285,17 @@ func TestAminoJSON_LegacyParity(t *testing.T) {
gogo: &secp256k1types.PubKey{Key: []byte("key")}, gogo: &secp256k1types.PubKey{Key: []byte("key")},
pulsar: &secp256k1.PubKey{Key: []byte("key")}, pulsar: &secp256k1.PubKey{Key: []byte("key")},
}, },
"crypto/legacy_amino_pubkey": { "crypto/legacy_amino_pubkey/filled": {
gogo: &multisig.LegacyAminoPubKey{PubKeys: []*codectypes.Any{pubkeyAny}}, gogo: &multisig.LegacyAminoPubKey{PubKeys: []*codectypes.Any{pubkeyAny}},
pulsar: &multisigapi.LegacyAminoPubKey{PublicKeys: []*anypb.Any{pubkeyAnyPulsar}}, pulsar: &multisigapi.LegacyAminoPubKey{PublicKeys: []*anypb.Any{pubkeyAnyPulsar}},
sortJSON: true,
roundTripUnequal: true,
}, },
"crypto/legacy_amino_pubkey/empty": { "crypto/legacy_amino_pubkey/empty": {
gogo: &multisig.LegacyAminoPubKey{}, gogo: &multisig.LegacyAminoPubKey{},
pulsar: &multisigapi.LegacyAminoPubKey{}, pulsar: &multisigapi.LegacyAminoPubKey{},
sortJSON: true,
roundTripUnequal: true,
}, },
"consensus/evidence_params/duration": { "consensus/evidence_params/duration": {
gogo: &gov_v1beta1_types.VotingParams{VotingPeriod: 1e9 + 7}, gogo: &gov_v1beta1_types.VotingParams{VotingPeriod: 1e9 + 7},
@@ -339,6 +346,14 @@ func TestAminoJSON_LegacyParity(t *testing.T) {
// This test cases demonstrates the expected contract and proper way to set a cosmos.Dec field represented // This test cases demonstrates the expected contract and proper way to set a cosmos.Dec field represented
// as bytes in protobuf message, namely: // as bytes in protobuf message, namely:
// dec10bz, _ := types.NewDec(10).Marshal() // dec10bz, _ := types.NewDec(10).Marshal()
"gov/v1_params": {
gogo: &gov_v1_types.Params{
Quorum: math.LegacyMustNewDecFromStr("0.33").String(),
},
pulsar: &gov_v1_api.Params{
Quorum: math.LegacyMustNewDecFromStr("0.33").String(),
},
},
"slashing/params/dec": { "slashing/params/dec": {
gogo: &slashingtypes.Params{ gogo: &slashingtypes.Params{
DowntimeJailDuration: 1e9 + 7, DowntimeJailDuration: 1e9 + 7,
@@ -407,6 +422,9 @@ func TestAminoJSON_LegacyParity(t *testing.T) {
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
gogoBytes, err := encCfg.Amino.MarshalJSON(tc.gogo) gogoBytes, err := encCfg.Amino.MarshalJSON(tc.gogo)
require.NoError(t, err) require.NoError(t, err)
if tc.sortJSON {
gogoBytes = sortJSON(t, gogoBytes)
}
pulsarBytes, err := aj.Marshal(tc.pulsar) pulsarBytes, err := aj.Marshal(tc.pulsar)
if tc.pulsarMarshalFails { if tc.pulsarMarshalFails {
@@ -426,10 +444,6 @@ func TestAminoJSON_LegacyParity(t *testing.T) {
newGogo := reflect.New(gogoType).Interface().(gogoproto.Message) newGogo := reflect.New(gogoType).Interface().(gogoproto.Message)
err = encCfg.Codec.Unmarshal(pulsarProtoBytes, newGogo) err = encCfg.Codec.Unmarshal(pulsarProtoBytes, newGogo)
if tc.protoUnmarshalFails {
require.Error(t, err)
return
}
require.NoError(t, err) require.NoError(t, err)
newGogoBytes, err := encCfg.Amino.MarshalJSON(newGogo) newGogoBytes, err := encCfg.Amino.MarshalJSON(newGogo)
@@ -573,3 +587,12 @@ func postFixPulsarMessage(msg proto.Message) {
} }
} }
} }
func sortJSON(t require.TestingT, bz []byte) []byte {
var c interface{}
err := json.Unmarshal(bz, &c)
require.NoError(t, err)
bz, err = json.Marshal(c)
require.NoError(t, err)
return bz
}
+4
View File
@@ -33,8 +33,12 @@ Since v0.13.0, x/tx follows Cosmos SDK semver: https://github.com/cosmos/cosmos-
## [Unreleased] ## [Unreleased]
## [v0.13.6](https://github.com/cosmos/cosmos-sdk/releases/tag/x/tx/v0.13.6) - 2024-10-XX
### Bug Fixes ### Bug Fixes
* [#22161](https://github.com/cosmos/cosmos-sdk/pull/22161) Add special case for string represented decimals.
* [#21825](https://github.com/cosmos/cosmos-sdk/pull/21825) Fix decimal encoding and field ordering in Amino JSON encoder.
* [#21782](https://github.com/cosmos/cosmos-sdk/pull/21782) Fix JSON attribute sort order on messages with oneof fields. * [#21782](https://github.com/cosmos/cosmos-sdk/pull/21782) Fix JSON attribute sort order on messages with oneof fields.
## [v0.13.5](https://github.com/cosmos/cosmos-sdk/releases/tag/x/tx/v0.13.5) - 2024-09-18 ## [v0.13.5](https://github.com/cosmos/cosmos-sdk/releases/tag/x/tx/v0.13.5) - 2024-09-18
+51 -28
View File
@@ -43,7 +43,7 @@ func cosmosIntEncoder(_ *Encoder, v protoreflect.Value, w io.Writer) error {
} }
} }
// cosmosDecEncoder provides legacy compatible encoding for cosmos.Dec and cosmos.Int types. These are sometimes // cosmosDecEncoder provides legacy compatible encoding for cosmos.Dec types. These are sometimes
// represented as strings in pulsar messages and sometimes as bytes. This encoder handles both cases. // represented as strings in pulsar messages and sometimes as bytes. This encoder handles both cases.
func cosmosDecEncoder(_ *Encoder, v protoreflect.Value, w io.Writer) error { func cosmosDecEncoder(_ *Encoder, v protoreflect.Value, w io.Writer) error {
switch val := v.Interface().(type) { switch val := v.Interface().(type) {
@@ -51,7 +51,12 @@ func cosmosDecEncoder(_ *Encoder, v protoreflect.Value, w io.Writer) error {
if val == "" { if val == "" {
return jsonMarshal(w, "0") return jsonMarshal(w, "0")
} }
return jsonMarshal(w, val) var dec math.LegacyDec
err := dec.Unmarshal([]byte(val))
if err != nil {
return fmt.Errorf("failed to unmarshal for Amino JSON encoding; string %q into Dec: %w", val, err)
}
return jsonMarshal(w, dec.String())
case []byte: case []byte:
if len(val) == 0 { if len(val) == 0 {
return jsonMarshal(w, "0") return jsonMarshal(w, "0")
@@ -125,27 +130,40 @@ func keyFieldEncoder(_ *Encoder, msg protoreflect.Message, w io.Writer) error {
} }
type moduleAccountPretty struct { type moduleAccountPretty struct {
Address string `json:"address"`
PubKey string `json:"public_key"`
AccountNumber uint64 `json:"account_number"` AccountNumber uint64 `json:"account_number"`
Sequence uint64 `json:"sequence"` Address string `json:"address"`
Name string `json:"name"` Name string `json:"name"`
Permissions []string `json:"permissions"` Permissions []string `json:"permissions"`
PubKey string `json:"public_key"`
Sequence uint64 `json:"sequence"`
} }
// moduleAccountEncoder replicates the behavior in // moduleAccountEncoder replicates the behavior in
// https://github.com/cosmos/cosmos-sdk/blob/41a3dfeced2953beba3a7d11ec798d17ee19f506/x/auth/types/account.go#L230-L254 // https://github.com/cosmos/cosmos-sdk/blob/41a3dfeced2953beba3a7d11ec798d17ee19f506/x/auth/types/account.go#L230-L254
func moduleAccountEncoder(_ *Encoder, msg protoreflect.Message, w io.Writer) error { func moduleAccountEncoder(_ *Encoder, msg protoreflect.Message, w io.Writer) error {
ma := msg.Interface().(*authapi.ModuleAccount) ma := &authapi.ModuleAccount{}
pretty := moduleAccountPretty{ msgDesc := msg.Descriptor()
PubKey: "", if msgDesc.FullName() != ma.ProtoReflect().Descriptor().FullName() {
Name: ma.Name, return errors.New("moduleAccountEncoder: msg not a auth.ModuleAccount")
Permissions: ma.Permissions,
} }
if ma.BaseAccount != nil { fields := msgDesc.Fields()
pretty.Address = ma.BaseAccount.Address
pretty.AccountNumber = ma.BaseAccount.AccountNumber pretty := moduleAccountPretty{
pretty.Sequence = ma.BaseAccount.Sequence PubKey: "",
Name: msg.Get(fields.ByName("name")).String(),
}
permissions := msg.Get(fields.ByName("permissions")).List()
for i := 0; i < permissions.Len(); i++ {
pretty.Permissions = append(pretty.Permissions, permissions.Get(i).String())
}
if msg.Has(fields.ByName("base_account")) {
baseAccount := msg.Get(fields.ByName("base_account"))
baMsg := baseAccount.Message()
bamdFields := baMsg.Descriptor().Fields()
pretty.Address = baMsg.Get(bamdFields.ByName("address")).String()
pretty.AccountNumber = baMsg.Get(bamdFields.ByName("account_number")).Uint()
pretty.Sequence = baMsg.Get(bamdFields.ByName("sequence")).Uint()
} else { } else {
pretty.Address = "" pretty.Address = ""
pretty.AccountNumber = 0 pretty.AccountNumber = 0
@@ -166,29 +184,34 @@ func moduleAccountEncoder(_ *Encoder, msg protoreflect.Message, w io.Writer) err
// also see: // also see:
// https://github.com/cosmos/cosmos-sdk/blob/b49f948b36bc991db5be431607b475633aed697e/proto/cosmos/crypto/multisig/keys.proto#L15/ // https://github.com/cosmos/cosmos-sdk/blob/b49f948b36bc991db5be431607b475633aed697e/proto/cosmos/crypto/multisig/keys.proto#L15/
func thresholdStringEncoder(enc *Encoder, msg protoreflect.Message, w io.Writer) error { func thresholdStringEncoder(enc *Encoder, msg protoreflect.Message, w io.Writer) error {
pk, ok := msg.Interface().(*multisig.LegacyAminoPubKey) pk := &multisig.LegacyAminoPubKey{}
if !ok { msgDesc := msg.Descriptor()
fields := msgDesc.Fields()
if msgDesc.FullName() != pk.ProtoReflect().Descriptor().FullName() {
return errors.New("thresholdStringEncoder: msg not a multisig.LegacyAminoPubKey") return errors.New("thresholdStringEncoder: msg not a multisig.LegacyAminoPubKey")
} }
_, err := fmt.Fprintf(w, `{"threshold":"%d","pubkeys":`, pk.Threshold)
if err != nil {
return err
}
if len(pk.PublicKeys) == 0 {
_, err = io.WriteString(w, `[]}`)
return err
}
fields := msg.Descriptor().Fields()
pubkeysField := fields.ByName("public_keys") pubkeysField := fields.ByName("public_keys")
pubkeys := msg.Get(pubkeysField).List() pubkeys := msg.Get(pubkeysField).List()
err = enc.marshalList(pubkeys, pubkeysField, w) _, err := io.WriteString(w, `{"pubkeys":`)
if err != nil { if err != nil {
return err return err
} }
_, err = io.WriteString(w, `}`) if pubkeys.Len() == 0 {
_, err := io.WriteString(w, `[]`)
if err != nil {
return err
}
} else {
err := enc.marshalList(pubkeys, pubkeysField, w)
if err != nil {
return err
}
}
threshold := fields.ByName("threshold")
_, err = fmt.Fprintf(w, `,"threshold":"%d"}`, msg.Get(threshold).Uint())
return err return err
} }
+5 -3
View File
@@ -16,6 +16,8 @@ import (
"cosmossdk.io/x/tx/signing" "cosmossdk.io/x/tx/signing"
) )
const cosmosDecType = "cosmos.Dec"
// MessageEncoder is a function that can encode a protobuf protoreflect.Message to JSON. // MessageEncoder is a function that can encode a protobuf protoreflect.Message to JSON.
type MessageEncoder func(*Encoder, protoreflect.Message, io.Writer) error type MessageEncoder func(*Encoder, protoreflect.Message, io.Writer) error
@@ -68,8 +70,8 @@ func NewEncoder(options EncoderOptions) Encoder {
} }
enc := Encoder{ enc := Encoder{
cosmosProtoScalarEncoders: map[string]FieldEncoder{ cosmosProtoScalarEncoders: map[string]FieldEncoder{
"cosmos.Dec": cosmosDecEncoder, cosmosDecType: cosmosDecEncoder,
"cosmos.Int": cosmosIntEncoder, "cosmos.Int": cosmosIntEncoder,
}, },
aminoMessageEncoders: map[string]MessageEncoder{ aminoMessageEncoders: map[string]MessageEncoder{
"key_field": keyFieldEncoder, "key_field": keyFieldEncoder,
@@ -387,7 +389,7 @@ func (enc Encoder) marshalMessage(msg protoreflect.Message, writer io.Writer) er
} }
// encode value // encode value
if encoder := enc.getFieldEncoding(f); encoder != nil { if encoder := enc.getFieldEncoder(f); encoder != nil {
err = encoder(&enc, v, writer) err = encoder(&enc, v, writer)
if err != nil { if err != nil {
return err return err
+30 -1
View File
@@ -2,11 +2,15 @@ package aminojson
import ( import (
cosmos_proto "github.com/cosmos/cosmos-proto" cosmos_proto "github.com/cosmos/cosmos-proto"
gogo "github.com/cosmos/gogoproto/gogoproto"
gogoproto "github.com/cosmos/gogoproto/proto" gogoproto "github.com/cosmos/gogoproto/proto"
"github.com/iancoleman/strcase" "github.com/iancoleman/strcase"
"github.com/pkg/errors" "github.com/pkg/errors"
"google.golang.org/protobuf/proto" "google.golang.org/protobuf/proto"
"google.golang.org/protobuf/reflect/protoreflect" "google.golang.org/protobuf/reflect/protoreflect"
"google.golang.org/protobuf/reflect/protoregistry"
"google.golang.org/protobuf/runtime/protoimpl"
"google.golang.org/protobuf/types/descriptorpb"
"cosmossdk.io/api/amino" "cosmossdk.io/api/amino"
) )
@@ -100,7 +104,20 @@ func (enc Encoder) getMessageEncoder(message protoreflect.Message) MessageEncode
return nil return nil
} }
func (enc Encoder) getFieldEncoding(field protoreflect.FieldDescriptor) FieldEncoder { var customTypeExtension = &protoimpl.ExtensionInfo{
ExtendedType: (*descriptorpb.FieldOptions)(nil),
ExtensionType: gogo.E_Customtype.ExtensionType,
Field: gogo.E_Customtype.Field,
Name: gogo.E_Customtype.Name,
Tag: gogo.E_Customtype.Tag,
Filename: gogo.E_Customtype.Filename,
}
func init() {
protoregistry.GlobalTypes.RegisterExtension(customTypeExtension)
}
func (enc Encoder) getFieldEncoder(field protoreflect.FieldDescriptor) FieldEncoder {
opts := field.Options() opts := field.Options()
if proto.HasExtension(opts, amino.E_Encoding) { if proto.HasExtension(opts, amino.E_Encoding) {
encoding := proto.GetExtension(opts, amino.E_Encoding).(string) encoding := proto.GetExtension(opts, amino.E_Encoding).(string)
@@ -110,6 +127,18 @@ func (enc Encoder) getFieldEncoding(field protoreflect.FieldDescriptor) FieldEnc
} }
if proto.HasExtension(opts, cosmos_proto.E_Scalar) { if proto.HasExtension(opts, cosmos_proto.E_Scalar) {
scalar := proto.GetExtension(opts, cosmos_proto.E_Scalar).(string) scalar := proto.GetExtension(opts, cosmos_proto.E_Scalar).(string)
// do not handle encoding of fields tagged only with scalar which are not backed by a
// LegacyDec custom type. This types are handled by the default encoding, as they are
// expected to already be encoded as their human readable string representation
// containing a radix, i.e. "1.2345".
// For example:
// https://github.com/cosmos/cosmos-sdk/blob/9076487d035e43d39fe54e8498da1ce31b9c845c/x/gov/proto/cosmos/gov/v1/gov.proto#L274
if scalar == cosmosDecType {
customType := proto.GetExtension(opts, customTypeExtension)
if customType != "cosmossdk.io/math.LegacyDec" {
return nil
}
}
if fn, ok := enc.cosmosProtoScalarEncoders[scalar]; ok { if fn, ok := enc.cosmosProtoScalarEncoders[scalar]; ok {
return fn return fn
} }