382 lines
8.9 KiB
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
|
|
}
|