Skip to content

Commit

Permalink
fix(client/v2): *big.Int unmarshal (#21853)
Browse files Browse the repository at this point in the history
(cherry picked from commit 43c41be)

# Conflicts:
#	client/v2/CHANGELOG.md
#	x/tx/decode/decode.go
  • Loading branch information
julienrbrt authored and mergify[bot] committed Oct 8, 2024
1 parent 3ecd143 commit 7064f48
Show file tree
Hide file tree
Showing 5 changed files with 300 additions and 5 deletions.
19 changes: 19 additions & 0 deletions client/v2/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,25 @@ Ref: https://keepachangelog.com/en/1.0.0/

* [#18626](https://github.com/cosmos/cosmos-sdk/pull/18626) Support for off-chain signing and verification of a file.
* [#18461](https://github.com/cosmos/cosmos-sdk/pull/18461) Support governance proposals.
<<<<<<< HEAD
=======
* [#20623](https://github.com/cosmos/cosmos-sdk/pull/20623) Introduce client/v2 tx factory.
* [#20623](https://github.com/cosmos/cosmos-sdk/pull/20623) Extend client/v2 keyring interface with `KeyType` and `KeyInfo`.

### Improvements

* [#21936](https://github.com/cosmos/cosmos-sdk/pull/21936) Print possible enum values in error message after an invalid input was provided.

### API Breaking Changes

* [#17709](https://github.com/cosmos/cosmos-sdk/pull/17709) Address codecs have been removed from `autocli.AppOptions` and `flag.Builder`. Instead client/v2 uses the address codecs present in the context (introduced in [#17503](https://github.com/cosmos/cosmos-sdk/pull/17503)).

### Bug Fixes

* [#21853](https://github.com/cosmos/cosmos-sdk/pull/21853) Fix `*big.Int` unmarshalling in txs.

## [v2.0.0-beta.5] - 2024-09-18
>>>>>>> 43c41be13 (fix(client/v2): *big.Int unmarshal (#21853))
### Improvements

Expand Down
2 changes: 2 additions & 0 deletions client/v2/autocli/flag/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ const (
ValidatorAddressStringScalarType = "cosmos.ValidatorAddressString"
ConsensusAddressStringScalarType = "cosmos.ConsensusAddressString"
PubkeyScalarType = "cosmos.Pubkey"
DecScalarType = "cosmos.Dec"
)

// Builder manages options for building pflag flags for protobuf messages.
Expand Down Expand Up @@ -67,6 +68,7 @@ func (b *Builder) init() {
b.scalarFlagTypes[ValidatorAddressStringScalarType] = validatorAddressStringType{}
b.scalarFlagTypes[ConsensusAddressStringScalarType] = consensusAddressStringType{}
b.scalarFlagTypes[PubkeyScalarType] = pubkeyType{}
b.scalarFlagTypes[DecScalarType] = decType{}
}
}

Expand Down
48 changes: 48 additions & 0 deletions client/v2/autocli/flag/legacy_dec.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package flag

import (
"context"

"google.golang.org/protobuf/reflect/protoreflect"

"cosmossdk.io/math"
)

type decType struct{}

func (a decType) NewValue(_ *context.Context, _ *Builder) Value {
return &decValue{}
}

func (a decType) DefaultValue() string {
return "0"
}

type decValue struct {
value string
}

func (a decValue) Get(protoreflect.Value) (protoreflect.Value, error) {
return protoreflect.ValueOf(a.value), nil
}

func (a decValue) String() string {
return a.value
}

func (a *decValue) Set(s string) error {
dec, err := math.LegacyNewDecFromStr(s)
if err != nil {
return err
}

// we need to convert from float representation to non-float representation using default precision
// 0.5 -> 500000000000000000
a.value = dec.BigInt().String()

return nil
}

func (a decValue) Type() string {
return "cosmos.Dec"
}
10 changes: 5 additions & 5 deletions x/auth/tx/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ var marshalOption = proto.MarshalOptions{
func (w *builder) getTx() (*gogoTxWrapper, error) {
anyMsgs, err := msgsV1toAnyV2(w.msgs)
if err != nil {
return nil, err
return nil, fmt.Errorf("unable to convert messages: %w", err)
}
body := &txv1beta1.TxBody{
Messages: anyMsgs,
Expand All @@ -136,12 +136,12 @@ func (w *builder) getTx() (*gogoTxWrapper, error) {

bodyBytes, err := marshalOption.Marshal(body)
if err != nil {
return nil, err
return nil, fmt.Errorf("unable to marshal body: %w", err)
}

authInfoBytes, err := marshalOption.Marshal(authInfo)
if err != nil {
return nil, err
return nil, fmt.Errorf("unable to marshal auth info: %w", err)
}

txRawBytes, err := marshalOption.Marshal(&txv1beta1.TxRaw{
Expand All @@ -150,12 +150,12 @@ func (w *builder) getTx() (*gogoTxWrapper, error) {
Signatures: w.signatures,
})
if err != nil {
return nil, err
return nil, fmt.Errorf("unable to marshal tx raw: %w", err)
}

decodedTx, err := w.decoder.Decode(txRawBytes)
if err != nil {
return nil, err
return nil, fmt.Errorf("unable to decode tx: %w", err)
}

return newWrapperFromDecodedTx(w.addressCodec, w.codec, decodedTx)
Expand Down
226 changes: 226 additions & 0 deletions x/tx/decode/decode.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
package decode

import (
"crypto/sha256"
"errors"
"fmt"
"reflect"
"strings"

gogoproto "github.com/cosmos/gogoproto/proto"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/reflect/protoreflect"
"google.golang.org/protobuf/types/dynamicpb"

v1beta1 "cosmossdk.io/api/cosmos/tx/v1beta1"
"cosmossdk.io/core/transaction"
errorsmod "cosmossdk.io/errors"
"cosmossdk.io/x/tx/signing"
)

// DecodedTx contains the decoded transaction, its signers, and other flags.
type DecodedTx struct {
DynamicMessages []proto.Message
Messages []gogoproto.Message
Tx *v1beta1.Tx
TxRaw *v1beta1.TxRaw
Signers [][]byte
TxBodyHasUnknownNonCriticals bool

// Cache for hash and full bytes
cachedHash [32]byte
cachedBytes []byte
cachedHashed bool
}

var _ transaction.Tx = &DecodedTx{}

type gogoProtoCodec interface {
Unmarshal([]byte, gogoproto.Message) error
}

// Decoder contains the dependencies required for decoding transactions.
type Decoder struct {
signingCtx *signing.Context
codec gogoProtoCodec
}

// Options are options for creating a Decoder.
type Options struct {
SigningContext *signing.Context
ProtoCodec gogoProtoCodec
}

// NewDecoder creates a new Decoder for decoding transactions.
func NewDecoder(options Options) (*Decoder, error) {
if options.SigningContext == nil {
return nil, errors.New("signing context is required")
}
if options.ProtoCodec == nil {
return nil, errors.New("proto codec is required for unmarshalling gogoproto messages")
}
return &Decoder{
signingCtx: options.SigningContext,
codec: options.ProtoCodec,
}, nil
}

// Decode decodes raw protobuf encoded transaction bytes into a DecodedTx.
func (d *Decoder) Decode(txBytes []byte) (*DecodedTx, error) {
// Make sure txBytes follow ADR-027.
err := rejectNonADR027TxRaw(txBytes)

Check failure on line 71 in x/tx/decode/decode.go

View workflow job for this annotation

GitHub Actions / dependency-review

undefined: rejectNonADR027TxRaw

Check failure on line 71 in x/tx/decode/decode.go

View workflow job for this annotation

GitHub Actions / golangci-lint

undefined: rejectNonADR027TxRaw

Check failure on line 71 in x/tx/decode/decode.go

View workflow job for this annotation

GitHub Actions / tests (03)

undefined: rejectNonADR027TxRaw
if err != nil {
return nil, errorsmod.Wrap(ErrTxDecode, err.Error())

Check failure on line 73 in x/tx/decode/decode.go

View workflow job for this annotation

GitHub Actions / dependency-review

undefined: ErrTxDecode

Check failure on line 73 in x/tx/decode/decode.go

View workflow job for this annotation

GitHub Actions / golangci-lint

undefined: ErrTxDecode

Check failure on line 73 in x/tx/decode/decode.go

View workflow job for this annotation

GitHub Actions / tests (03)

undefined: ErrTxDecode
}

var raw v1beta1.TxRaw

// reject all unknown proto fields in the root TxRaw
fileResolver := d.signingCtx.FileResolver()
err = RejectUnknownFieldsStrict(txBytes, raw.ProtoReflect().Descriptor(), fileResolver)

Check failure on line 80 in x/tx/decode/decode.go

View workflow job for this annotation

GitHub Actions / dependency-review

undefined: RejectUnknownFieldsStrict

Check failure on line 80 in x/tx/decode/decode.go

View workflow job for this annotation

GitHub Actions / golangci-lint

undefined: RejectUnknownFieldsStrict

Check failure on line 80 in x/tx/decode/decode.go

View workflow job for this annotation

GitHub Actions / tests (03)

undefined: RejectUnknownFieldsStrict
if err != nil {
return nil, errorsmod.Wrap(ErrTxDecode, err.Error())

Check failure on line 82 in x/tx/decode/decode.go

View workflow job for this annotation

GitHub Actions / dependency-review

undefined: ErrTxDecode

Check failure on line 82 in x/tx/decode/decode.go

View workflow job for this annotation

GitHub Actions / golangci-lint

undefined: ErrTxDecode

Check failure on line 82 in x/tx/decode/decode.go

View workflow job for this annotation

GitHub Actions / tests (03)

undefined: ErrTxDecode
}

err = proto.Unmarshal(txBytes, &raw)
if err != nil {
return nil, errorsmod.Wrap(ErrTxDecode, err.Error())

Check failure on line 87 in x/tx/decode/decode.go

View workflow job for this annotation

GitHub Actions / dependency-review

undefined: ErrTxDecode

Check failure on line 87 in x/tx/decode/decode.go

View workflow job for this annotation

GitHub Actions / golangci-lint

undefined: ErrTxDecode

Check failure on line 87 in x/tx/decode/decode.go

View workflow job for this annotation

GitHub Actions / tests (03)

undefined: ErrTxDecode
}

var body v1beta1.TxBody

// allow non-critical unknown fields in TxBody
txBodyHasUnknownNonCriticals, err := RejectUnknownFields(raw.BodyBytes, body.ProtoReflect().Descriptor(), true, fileResolver)

Check failure on line 93 in x/tx/decode/decode.go

View workflow job for this annotation

GitHub Actions / dependency-review

undefined: RejectUnknownFields

Check failure on line 93 in x/tx/decode/decode.go

View workflow job for this annotation

GitHub Actions / golangci-lint

undefined: RejectUnknownFields

Check failure on line 93 in x/tx/decode/decode.go

View workflow job for this annotation

GitHub Actions / tests (03)

undefined: RejectUnknownFields
if err != nil {
return nil, errorsmod.Wrap(ErrTxDecode, err.Error())

Check failure on line 95 in x/tx/decode/decode.go

View workflow job for this annotation

GitHub Actions / dependency-review

undefined: ErrTxDecode

Check failure on line 95 in x/tx/decode/decode.go

View workflow job for this annotation

GitHub Actions / golangci-lint

undefined: ErrTxDecode

Check failure on line 95 in x/tx/decode/decode.go

View workflow job for this annotation

GitHub Actions / tests (03)

undefined: ErrTxDecode
}

err = proto.Unmarshal(raw.BodyBytes, &body)
if err != nil {
return nil, errorsmod.Wrap(ErrTxDecode, err.Error())

Check failure on line 100 in x/tx/decode/decode.go

View workflow job for this annotation

GitHub Actions / dependency-review

undefined: ErrTxDecode

Check failure on line 100 in x/tx/decode/decode.go

View workflow job for this annotation

GitHub Actions / golangci-lint

undefined: ErrTxDecode

Check failure on line 100 in x/tx/decode/decode.go

View workflow job for this annotation

GitHub Actions / tests (03)

undefined: ErrTxDecode
}

var authInfo v1beta1.AuthInfo

// reject all unknown proto fields in AuthInfo
err = RejectUnknownFieldsStrict(raw.AuthInfoBytes, authInfo.ProtoReflect().Descriptor(), fileResolver)

Check failure on line 106 in x/tx/decode/decode.go

View workflow job for this annotation

GitHub Actions / dependency-review

undefined: RejectUnknownFieldsStrict

Check failure on line 106 in x/tx/decode/decode.go

View workflow job for this annotation

GitHub Actions / golangci-lint

undefined: RejectUnknownFieldsStrict

Check failure on line 106 in x/tx/decode/decode.go

View workflow job for this annotation

GitHub Actions / tests (03)

undefined: RejectUnknownFieldsStrict
if err != nil {
return nil, errorsmod.Wrap(ErrTxDecode, err.Error())

Check failure on line 108 in x/tx/decode/decode.go

View workflow job for this annotation

GitHub Actions / dependency-review

undefined: ErrTxDecode

Check failure on line 108 in x/tx/decode/decode.go

View workflow job for this annotation

GitHub Actions / golangci-lint

undefined: ErrTxDecode

Check failure on line 108 in x/tx/decode/decode.go

View workflow job for this annotation

GitHub Actions / tests (03)

undefined: ErrTxDecode
}

err = proto.Unmarshal(raw.AuthInfoBytes, &authInfo)
if err != nil {
return nil, errorsmod.Wrap(ErrTxDecode, err.Error())
}

theTx := &v1beta1.Tx{
Body: &body,
AuthInfo: &authInfo,
Signatures: raw.Signatures,
}

var (
signers [][]byte
dynamicMsgs []proto.Message
msgs []gogoproto.Message
)
seenSigners := map[string]struct{}{}
for _, anyMsg := range body.Messages {
typeURL := strings.TrimPrefix(anyMsg.TypeUrl, "/")

// unmarshal into dynamic message
msgDesc, err := fileResolver.FindDescriptorByName(protoreflect.FullName(typeURL))
if err != nil {
return nil, fmt.Errorf("protoFiles does not have descriptor %s: %w", anyMsg.TypeUrl, err)
}
dynamicMsg := dynamicpb.NewMessageType(msgDesc.(protoreflect.MessageDescriptor)).New().Interface()
err = anyMsg.UnmarshalTo(dynamicMsg)
if err != nil {
return nil, errorsmod.Wrap(ErrTxDecode, fmt.Sprintf("cannot unmarshal Any message: %v", err))
}
dynamicMsgs = append(dynamicMsgs, dynamicMsg)

// unmarshal into gogoproto message
gogoType := gogoproto.MessageType(typeURL)
if gogoType == nil {
return nil, fmt.Errorf("cannot find type: %s", anyMsg.TypeUrl)
}
msg := reflect.New(gogoType.Elem()).Interface().(gogoproto.Message)
err = d.codec.Unmarshal(anyMsg.Value, msg)
if err != nil {
return nil, errorsmod.Wrap(ErrTxDecode, err.Error())
}
msgs = append(msgs, msg)

// fetch signers with dynamic message
ss, signerErr := d.signingCtx.GetSigners(dynamicMsg)
if signerErr != nil {
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{}{}
}
}

return &DecodedTx{
Messages: msgs,
DynamicMessages: dynamicMsgs,
Tx: theTx,
TxRaw: &raw,
TxBodyHasUnknownNonCriticals: txBodyHasUnknownNonCriticals,
Signers: signers,
}, nil
}

// Hash implements the interface for the Tx interface.
func (dtx *DecodedTx) Hash() [32]byte {
if !dtx.cachedHashed {
dtx.computeHashAndBytes()
}
return dtx.cachedHash
}

func (dtx *DecodedTx) GetGasLimit() (uint64, error) {
if dtx == nil || dtx.Tx == nil || dtx.Tx.AuthInfo == nil || dtx.Tx.AuthInfo.Fee == nil {
return 0, errors.New("gas limit not available or one or more required fields are nil")
}
return dtx.Tx.AuthInfo.Fee.GasLimit, nil
}

func (dtx *DecodedTx) GetMessages() ([]transaction.Msg, error) {
if dtx == nil || dtx.Messages == nil {
return nil, errors.New("messages not available or are nil")
}

return dtx.Messages, nil
}

func (dtx *DecodedTx) GetSenders() ([][]byte, error) {
if dtx == nil || dtx.Signers == nil {
return nil, errors.New("senders not available or are nil")
}
return dtx.Signers, nil
}

func (dtx *DecodedTx) Bytes() []byte {
if !dtx.cachedHashed {
dtx.computeHashAndBytes()
}
return dtx.cachedBytes
}

func (dtx *DecodedTx) computeHashAndBytes() {
bz, err := proto.Marshal(dtx.TxRaw)
if err != nil {
panic(err)
}

dtx.cachedBytes = bz
dtx.cachedHash = sha256.Sum256(bz)
dtx.cachedHashed = true
}

0 comments on commit 7064f48

Please sign in to comment.