cosmos-sdk/x/tx/decode/decode.go
Alex | Interchain Labs e7bcfc23b4
fix(x/tx): add feePayer as signer (#22311) BACKPORT (#24408)
Co-authored-by: Julián Toledano <JulianToledano@users.noreply.github.com>
2025-04-09 20:58:23 +00:00

141 lines
3.8 KiB
Go

package decode
import (
"errors"
"github.com/cosmos/cosmos-proto/anyutil"
"google.golang.org/protobuf/proto"
v1beta1 "cosmossdk.io/api/cosmos/tx/v1beta1"
errorsmod "cosmossdk.io/errors"
"cosmossdk.io/x/tx/signing"
)
// DecodedTx contains the decoded transaction, its signers, and other flags.
type DecodedTx struct {
Messages []proto.Message
Tx *v1beta1.Tx
TxRaw *v1beta1.TxRaw
Signers [][]byte
TxBodyHasUnknownNonCriticals bool
}
// Decoder contains the dependencies required for decoding transactions.
type Decoder struct {
signingCtx *signing.Context
}
// Options are options for creating a Decoder.
type Options struct {
SigningContext *signing.Context
}
// 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")
}
return &Decoder{
signingCtx: options.SigningContext,
}, 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)
if err != nil {
return nil, errorsmod.Wrap(ErrTxDecode, err.Error())
}
var raw v1beta1.TxRaw
// reject all unknown proto fields in the root TxRaw
fileResolver := d.signingCtx.FileResolver()
err = RejectUnknownFieldsStrict(txBytes, raw.ProtoReflect().Descriptor(), fileResolver)
if err != nil {
return nil, errorsmod.Wrap(ErrTxDecode, err.Error())
}
err = proto.Unmarshal(txBytes, &raw)
if err != nil {
return nil, err
}
var body v1beta1.TxBody
// allow non-critical unknown fields in TxBody
txBodyHasUnknownNonCriticals, err := RejectUnknownFields(raw.BodyBytes, body.ProtoReflect().Descriptor(), true, fileResolver)
if err != nil {
return nil, errorsmod.Wrap(ErrTxDecode, err.Error())
}
err = proto.Unmarshal(raw.BodyBytes, &body)
if err != nil {
return nil, errorsmod.Wrap(ErrTxDecode, err.Error())
}
var authInfo v1beta1.AuthInfo
// reject all unknown proto fields in AuthInfo
err = RejectUnknownFieldsStrict(raw.AuthInfoBytes, authInfo.ProtoReflect().Descriptor(), fileResolver)
if err != nil {
return nil, errorsmod.Wrap(ErrTxDecode, err.Error())
}
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
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, errorsmod.Wrap(ErrTxDecode, signerErr.Error())
}
msgs = append(msgs, msg)
ss, signerErr := d.signingCtx.GetSigners(msg)
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{}{}
}
}
// If a fee payer is specified in the AuthInfo, it must be added to the list of signers
if authInfo.Fee != nil && authInfo.Fee.Payer != "" {
feeAddr, err := d.signingCtx.AddressCodec().StringToBytes(authInfo.Fee.Payer)
if err != nil {
return nil, errorsmod.Wrap(ErrTxDecode, err.Error())
}
if _, seen := seenSigners[string(feeAddr)]; !seen {
signers = append(signers, feeAddr)
}
}
return &DecodedTx{
Messages: msgs,
Tx: theTx,
TxRaw: &raw,
TxBodyHasUnknownNonCriticals: txBodyHasUnknownNonCriticals,
Signers: signers,
}, nil
}