cosmos-sdk/x/auth/tx/builder.go

382 lines
8.9 KiB
Go

package tx
import (
"fmt"
"github.com/gogo/protobuf/proto"
"github.com/tendermint/tendermint/crypto"
"github.com/cosmos/cosmos-sdk/client"
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
"github.com/cosmos/cosmos-sdk/crypto/types"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/cosmos/cosmos-sdk/types/tx"
"github.com/cosmos/cosmos-sdk/types/tx/signing"
"github.com/cosmos/cosmos-sdk/x/auth/ante"
authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing"
)
// wrapper is a wrapper around the tx.Tx proto.Message which retain the raw
// body and auth_info bytes.
type wrapper struct {
tx *tx.Tx
// bodyBz represents the protobuf encoding of TxBody. This should be encoding
// from the client using TxRaw if the tx was decoded from the wire
bodyBz []byte
// authInfoBz represents the protobuf encoding of TxBody. This should be encoding
// from the client using TxRaw if the tx was decoded from the wire
authInfoBz []byte
// pubKeys represents the cached crypto.PubKey's that were set either from tx decoding
// or decoded from AuthInfo when GetPubKey's was called
pubKeys []crypto.PubKey
pubkeyCodec types.PublicKeyCodec
txBodyHasUnknownNonCriticals bool
}
var (
_ authsigning.Tx = &wrapper{}
_ client.TxBuilder = &wrapper{}
_ ante.HasExtensionOptionsTx = &wrapper{}
_ ExtensionOptionsTxBuilder = &wrapper{}
)
// ExtensionOptionsTxBuilder defines a TxBuilder that can also set extensions.
type ExtensionOptionsTxBuilder interface {
client.TxBuilder
SetExtensionOptions(...*codectypes.Any)
SetNonCriticalExtensionOptions(...*codectypes.Any)
}
func newBuilder(pubkeyCodec types.PublicKeyCodec) *wrapper {
return &wrapper{
tx: &tx.Tx{
Body: &tx.TxBody{},
AuthInfo: &tx.AuthInfo{
Fee: &tx.Fee{},
},
},
pubkeyCodec: pubkeyCodec,
}
}
func (w *wrapper) GetMsgs() []sdk.Msg {
if w.tx == nil || w.tx.Body == nil {
return nil
}
anys := w.tx.Body.Messages
res := make([]sdk.Msg, len(anys))
for i, any := range anys {
msg := any.GetCachedValue().(sdk.Msg)
res[i] = msg
}
return res
}
// MaxGasWanted defines the max gas allowed.
const MaxGasWanted = uint64((1 << 63) - 1)
func (w *wrapper) ValidateBasic() error {
theTx := w.tx
if theTx == nil {
return fmt.Errorf("bad Tx")
}
body := w.tx.Body
if body == nil {
return fmt.Errorf("missing TxBody")
}
authInfo := w.tx.AuthInfo
if authInfo == nil {
return fmt.Errorf("missing AuthInfo")
}
fee := authInfo.Fee
if fee == nil {
return fmt.Errorf("missing fee")
}
if fee.GasLimit > MaxGasWanted {
return sdkerrors.Wrapf(
sdkerrors.ErrInvalidRequest,
"invalid gas supplied; %d > %d", fee.GasLimit, MaxGasWanted,
)
}
if fee.Amount.IsAnyNegative() {
return sdkerrors.Wrapf(
sdkerrors.ErrInsufficientFee,
"invalid fee provided: %s", fee.Amount,
)
}
sigs := theTx.Signatures
if len(sigs) == 0 {
return sdkerrors.ErrNoSignatures
}
if len(sigs) != len(w.GetSigners()) {
return sdkerrors.Wrapf(
sdkerrors.ErrUnauthorized,
"wrong number of signers; expected %d, got %d", w.GetSigners(), len(sigs),
)
}
return nil
}
func (w *wrapper) getBodyBytes() []byte {
if len(w.bodyBz) == 0 {
// if bodyBz is empty, then marshal the body. bodyBz will generally
// be set to nil whenever SetBody is called so the result of calling
// this method should always return the correct bytes. Note that after
// decoding bodyBz is derived from TxRaw so that it matches what was
// transmitted over the wire
var err error
w.bodyBz, err = proto.Marshal(w.tx.Body)
if err != nil {
panic(err)
}
}
return w.bodyBz
}
func (w *wrapper) getAuthInfoBytes() []byte {
if len(w.authInfoBz) == 0 {
// if authInfoBz is empty, then marshal the body. authInfoBz will generally
// be set to nil whenever SetAuthInfo is called so the result of calling
// this method should always return the correct bytes. Note that after
// decoding authInfoBz is derived from TxRaw so that it matches what was
// transmitted over the wire
var err error
w.authInfoBz, err = proto.Marshal(w.tx.AuthInfo)
if err != nil {
panic(err)
}
}
return w.authInfoBz
}
func (w *wrapper) GetSigners() []sdk.AccAddress {
var signers []sdk.AccAddress
seen := map[string]bool{}
for _, msg := range w.GetMsgs() {
for _, addr := range msg.GetSigners() {
if !seen[addr.String()] {
signers = append(signers, addr)
seen[addr.String()] = true
}
}
}
return signers
}
func (w *wrapper) GetPubKeys() []crypto.PubKey {
if w.pubKeys == nil {
signerInfos := w.tx.AuthInfo.SignerInfos
pubKeys := make([]crypto.PubKey, len(signerInfos))
for i, si := range signerInfos {
var err error
pk := si.PublicKey
if pk != nil {
pubKeys[i], err = w.pubkeyCodec.Decode(si.PublicKey)
if err != nil {
panic(err)
}
}
}
w.pubKeys = pubKeys
}
return w.pubKeys
}
func (w *wrapper) GetGas() uint64 {
return w.tx.AuthInfo.Fee.GasLimit
}
func (w *wrapper) GetFee() sdk.Coins {
return w.tx.AuthInfo.Fee.Amount
}
func (w *wrapper) FeePayer() sdk.AccAddress {
return w.GetSigners()[0]
}
func (w *wrapper) GetMemo() string {
return w.tx.Body.Memo
}
func (w *wrapper) GetSignatures() [][]byte {
return w.tx.Signatures
}
// GetTimeoutHeight returns the transaction's timeout height (if set).
func (w *wrapper) GetTimeoutHeight() uint64 {
return w.tx.Body.TimeoutHeight
}
func (w *wrapper) GetSignaturesV2() ([]signing.SignatureV2, error) {
signerInfos := w.tx.AuthInfo.SignerInfos
sigs := w.tx.Signatures
pubKeys := w.GetPubKeys()
n := len(signerInfos)
res := make([]signing.SignatureV2, n)
for i, si := range signerInfos {
var err error
sigData, err := ModeInfoAndSigToSignatureData(si.ModeInfo, sigs[i])
if err != nil {
return nil, err
}
res[i] = signing.SignatureV2{
PubKey: pubKeys[i],
Data: sigData,
Sequence: si.GetSequence(),
}
}
return res, nil
}
func (w *wrapper) SetMsgs(msgs ...sdk.Msg) error {
anys := make([]*codectypes.Any, len(msgs))
for i, msg := range msgs {
var err error
anys[i], err = codectypes.NewAnyWithValue(msg)
if err != nil {
return err
}
}
w.tx.Body.Messages = anys
// set bodyBz to nil because the cached bodyBz no longer matches tx.Body
w.bodyBz = nil
return nil
}
// SetTimeoutHeight sets the transaction's height timeout.
func (w *wrapper) SetTimeoutHeight(height uint64) {
w.tx.Body.TimeoutHeight = height
// set bodyBz to nil because the cached bodyBz no longer matches tx.Body
w.bodyBz = nil
}
func (w *wrapper) SetMemo(memo string) {
w.tx.Body.Memo = memo
// set bodyBz to nil because the cached bodyBz no longer matches tx.Body
w.bodyBz = nil
}
func (w *wrapper) SetGasLimit(limit uint64) {
if w.tx.AuthInfo.Fee == nil {
w.tx.AuthInfo.Fee = &tx.Fee{}
}
w.tx.AuthInfo.Fee.GasLimit = limit
// set authInfoBz to nil because the cached authInfoBz no longer matches tx.AuthInfo
w.authInfoBz = nil
}
func (w *wrapper) SetFeeAmount(coins sdk.Coins) {
if w.tx.AuthInfo.Fee == nil {
w.tx.AuthInfo.Fee = &tx.Fee{}
}
w.tx.AuthInfo.Fee.Amount = coins
// set authInfoBz to nil because the cached authInfoBz no longer matches tx.AuthInfo
w.authInfoBz = nil
}
func (w *wrapper) SetSignatures(signatures ...signing.SignatureV2) error {
n := len(signatures)
signerInfos := make([]*tx.SignerInfo, n)
rawSigs := make([][]byte, n)
for i, sig := range signatures {
var modeInfo *tx.ModeInfo
modeInfo, rawSigs[i] = SignatureDataToModeInfoAndSig(sig.Data)
pk, err := w.pubkeyCodec.Encode(sig.PubKey)
if err != nil {
return err
}
signerInfos[i] = &tx.SignerInfo{
PublicKey: pk,
ModeInfo: modeInfo,
Sequence: sig.Sequence,
}
}
w.setSignerInfos(signerInfos)
w.setSignatures(rawSigs)
return nil
}
func (w *wrapper) setSignerInfos(infos []*tx.SignerInfo) {
w.tx.AuthInfo.SignerInfos = infos
// set authInfoBz to nil because the cached authInfoBz no longer matches tx.AuthInfo
w.authInfoBz = nil
// set cached pubKeys to nil because they no longer match tx.AuthInfo
w.pubKeys = nil
}
func (w *wrapper) setSignatures(sigs [][]byte) {
w.tx.Signatures = sigs
}
func (w *wrapper) GetTx() authsigning.Tx {
return w
}
// GetProtoTx returns the tx as a proto.Message.
func (w *wrapper) GetProtoTx() *tx.Tx {
return w.tx
}
// WrapTx creates a TxBuilder wrapper around a tx.Tx proto message.
func WrapTx(protoTx *tx.Tx, pubkeyCodec types.PublicKeyCodec) client.TxBuilder {
return &wrapper{
tx: protoTx,
pubkeyCodec: pubkeyCodec,
}
}
func (w *wrapper) GetExtensionOptions() []*codectypes.Any {
return w.tx.Body.ExtensionOptions
}
func (w *wrapper) GetNonCriticalExtensionOptions() []*codectypes.Any {
return w.tx.Body.NonCriticalExtensionOptions
}
func (w *wrapper) SetExtensionOptions(extOpts ...*codectypes.Any) {
w.tx.Body.ExtensionOptions = extOpts
w.bodyBz = nil
}
func (w *wrapper) SetNonCriticalExtensionOptions(extOpts ...*codectypes.Any) {
w.tx.Body.NonCriticalExtensionOptions = extOpts
w.bodyBz = nil
}