cosmos-sdk/x/auth/tx/builder.go
son trinh 585335690b
feat(tx)!: make timeout_height time based (#20870)
Co-authored-by: Marko <marko@baricevic.me>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2024-07-29 11:53:27 +00:00

375 lines
10 KiB
Go

package tx
import (
"errors"
"fmt"
"time"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/known/anypb"
"google.golang.org/protobuf/types/known/timestamppb"
basev1beta1 "cosmossdk.io/api/cosmos/base/v1beta1"
multisigv1beta1 "cosmossdk.io/api/cosmos/crypto/multisig/v1beta1"
signingv1beta1 "cosmossdk.io/api/cosmos/tx/signing/v1beta1"
txv1beta1 "cosmossdk.io/api/cosmos/tx/v1beta1"
"cosmossdk.io/core/address"
authsign "cosmossdk.io/x/auth/signing"
"cosmossdk.io/x/tx/decode"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/codec"
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/tx"
"github.com/cosmos/cosmos-sdk/types/tx/signing"
)
var (
_ client.TxBuilder = &builder{}
_ ExtensionOptionsTxBuilder = &builder{}
)
func newBuilder(addressCodec address.Codec, decoder *decode.Decoder, codec codec.BinaryCodec) *builder {
return &builder{addressCodec: addressCodec, decoder: decoder, codec: codec}
}
func newBuilderFromDecodedTx(
addrCodec address.Codec, decoder *decode.Decoder, codec codec.BinaryCodec, decoded *gogoTxWrapper,
) (*builder, error) {
signatures := make([][]byte, len(decoded.Tx.Signatures))
copy(signatures, decoded.Tx.Signatures)
sigInfos := make([]*tx.SignerInfo, len(decoded.Tx.AuthInfo.SignerInfos))
for i, sigInfo := range decoded.Tx.AuthInfo.SignerInfos {
modeInfoV1 := new(tx.ModeInfo)
fromV2ModeInfo(sigInfo.ModeInfo, modeInfoV1)
sigInfos[i] = &tx.SignerInfo{
PublicKey: intoAnyV1([]*anypb.Any{sigInfo.PublicKey})[0],
ModeInfo: modeInfoV1,
Sequence: sigInfo.Sequence,
}
}
var payer []byte
if decoded.feePayer != nil {
payer = decoded.feePayer
}
return &builder{
addressCodec: addrCodec,
decoder: decoder,
codec: codec,
msgs: decoded.Messages,
timeoutHeight: decoded.GetTimeoutHeight(),
granter: decoded.FeeGranter(),
payer: payer,
unordered: decoded.GetUnordered(),
memo: decoded.GetMemo(),
gasLimit: decoded.GetGas(),
fees: decoded.GetFee(),
signerInfos: sigInfos,
signatures: signatures,
extensionOptions: decoded.GetExtensionOptions(),
nonCriticalExtensionOptions: decoded.GetNonCriticalExtensionOptions(),
}, nil
}
type builder struct {
addressCodec address.Codec
decoder *decode.Decoder
codec codec.BinaryCodec
msgs []sdk.Msg
timeoutHeight uint64
timeoutTimestamp time.Time
granter []byte
payer []byte
unordered bool
memo string
gasLimit uint64
fees sdk.Coins
signerInfos []*tx.SignerInfo
signatures [][]byte
extensionOptions []*codectypes.Any
nonCriticalExtensionOptions []*codectypes.Any
}
func (w *builder) GetTx() authsign.Tx {
buildTx, err := w.getTx()
if err != nil {
panic(err)
}
return buildTx
}
var marshalOption = proto.MarshalOptions{
Deterministic: true,
}
func (w *builder) getTx() (*gogoTxWrapper, error) {
anyMsgs, err := msgsV1toAnyV2(w.msgs)
if err != nil {
return nil, err
}
body := &txv1beta1.TxBody{
Messages: anyMsgs,
Memo: w.memo,
TimeoutHeight: w.timeoutHeight,
TimeoutTimestamp: timestamppb.New(w.timeoutTimestamp),
Unordered: w.unordered,
ExtensionOptions: intoAnyV2(w.extensionOptions),
NonCriticalExtensionOptions: intoAnyV2(w.nonCriticalExtensionOptions),
}
fee, err := w.getFee()
if err != nil {
return nil, fmt.Errorf("unable to parse fee: %w", err)
}
authInfo := &txv1beta1.AuthInfo{
SignerInfos: intoV2SignerInfo(w.signerInfos),
Fee: fee,
Tip: nil, // deprecated
}
bodyBytes, err := marshalOption.Marshal(body)
if err != nil {
return nil, err
}
authInfoBytes, err := marshalOption.Marshal(authInfo)
if err != nil {
return nil, err
}
txRawBytes, err := marshalOption.Marshal(&txv1beta1.TxRaw{
BodyBytes: bodyBytes,
AuthInfoBytes: authInfoBytes,
Signatures: w.signatures,
})
if err != nil {
return nil, err
}
decodedTx, err := w.decoder.Decode(txRawBytes)
if err != nil {
return nil, err
}
return newWrapperFromDecodedTx(w.addressCodec, w.codec, decodedTx)
}
func msgsV1toAnyV2(msgs []sdk.Msg) ([]*anypb.Any, error) {
anys := make([]*codectypes.Any, len(msgs))
for i, msg := range msgs {
anyMsg, err := codectypes.NewAnyWithValue(msg)
if err != nil {
return nil, err
}
anys[i] = anyMsg
}
return intoAnyV2(anys), nil
}
func intoV2Fees(fees sdk.Coins) []*basev1beta1.Coin {
coins := make([]*basev1beta1.Coin, len(fees))
for i, c := range fees {
coins[i] = &basev1beta1.Coin{
Denom: c.Denom,
Amount: c.Amount.String(),
}
}
return coins
}
func (w *builder) SetMsgs(msgs ...sdk.Msg) error {
w.msgs = msgs
return nil
}
// SetTimeoutHeight sets the transaction's height timeout.
func (w *builder) SetTimeoutHeight(height uint64) { w.timeoutHeight = height }
func (w *builder) SetTimeoutTimestamp(timestamp time.Time) { w.timeoutTimestamp = timestamp }
func (w *builder) SetUnordered(v bool) { w.unordered = v }
func (w *builder) SetMemo(memo string) { w.memo = memo }
func (w *builder) SetGasLimit(limit uint64) { w.gasLimit = limit }
func (w *builder) SetFeeAmount(coins sdk.Coins) { w.fees = coins }
func (w *builder) SetFeePayer(feePayer sdk.AccAddress) { w.payer = feePayer }
func (w *builder) SetFeeGranter(feeGranter sdk.AccAddress) { w.granter = feeGranter }
func (w *builder) 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
pubKey *codectypes.Any
err error
)
modeInfo, rawSigs[i] = SignatureDataToModeInfoAndSig(sig.Data)
if sig.PubKey != nil {
pubKey, err = codectypes.NewAnyWithValue(sig.PubKey)
if err != nil {
return err
}
}
signerInfos[i] = &tx.SignerInfo{
PublicKey: pubKey,
ModeInfo: modeInfo,
Sequence: sig.Sequence,
}
}
w.setSignerInfos(signerInfos)
w.setSignatures(rawSigs)
return nil
}
func (w *builder) setSignerInfos(infos []*tx.SignerInfo) { w.signerInfos = infos }
func (w *builder) setSignatures(sigs [][]byte) { w.signatures = sigs }
func (w *builder) SetExtensionOptions(extOpts ...*codectypes.Any) { w.extensionOptions = extOpts }
func (w *builder) SetNonCriticalExtensionOptions(extOpts ...*codectypes.Any) {
w.nonCriticalExtensionOptions = extOpts
}
func (w *builder) AddAuxSignerData(data tx.AuxSignerData) error { return errors.New("not supported") }
func (w *builder) getFee() (fee *txv1beta1.Fee, err error) {
granterStr := ""
if w.granter != nil {
granterStr, err = w.addressCodec.BytesToString(w.granter)
if err != nil {
return nil, err
}
}
payerStr := ""
if w.payer != nil {
payerStr, err = w.addressCodec.BytesToString(w.payer)
if err != nil {
return nil, err
}
}
fee = &txv1beta1.Fee{
Amount: intoV2Fees(w.fees),
GasLimit: w.gasLimit,
Payer: payerStr,
Granter: granterStr,
}
return fee, nil
}
func intoAnyV2(v1s []*codectypes.Any) []*anypb.Any {
v2s := make([]*anypb.Any, len(v1s))
for i, v1 := range v1s {
v2s[i] = &anypb.Any{
TypeUrl: v1.TypeUrl,
Value: v1.Value,
}
}
return v2s
}
func intoV2SignerInfo(v1s []*tx.SignerInfo) []*txv1beta1.SignerInfo {
v2s := make([]*txv1beta1.SignerInfo, len(v1s))
for i, v1 := range v1s {
modeInfoV2 := new(txv1beta1.ModeInfo)
intoV2ModeInfo(v1.ModeInfo, modeInfoV2)
v2 := &txv1beta1.SignerInfo{
PublicKey: intoAnyV2([]*codectypes.Any{v1.PublicKey})[0],
ModeInfo: modeInfoV2,
Sequence: v1.Sequence,
}
v2s[i] = v2
}
return v2s
}
func intoV2ModeInfo(v1 *tx.ModeInfo, v2 *txv1beta1.ModeInfo) {
// handle nil modeInfo. this is permissible through the code path:
// https://github.com/cosmos/cosmos-sdk/blob/4a6a1e3cb8de459891cb0495052589673d14ef51/x/auth/tx/builder.go#L295
// -> https://github.com/cosmos/cosmos-sdk/blob/b7841e3a76a38d069c1b9cb3d48368f7a67e9c26/x/auth/tx/sigs.go#L15-L17
// when signature.Data is nil.
if v1 == nil {
return
}
switch mi := v1.Sum.(type) {
case *tx.ModeInfo_Single_:
v2.Sum = &txv1beta1.ModeInfo_Single_{
Single: &txv1beta1.ModeInfo_Single{
Mode: signingv1beta1.SignMode(v1.GetSingle().Mode),
},
}
case *tx.ModeInfo_Multi_:
multiModeInfos := v1.GetMulti().ModeInfos
modeInfos := make([]*txv1beta1.ModeInfo, len(multiModeInfos))
for i, modeInfo := range multiModeInfos {
modeInfos[i] = new(txv1beta1.ModeInfo)
intoV2ModeInfo(modeInfo, modeInfos[i])
}
v2.Sum = &txv1beta1.ModeInfo_Multi_{
Multi: &txv1beta1.ModeInfo_Multi{
Bitarray: &multisigv1beta1.CompactBitArray{
Elems: mi.Multi.Bitarray.Elems,
ExtraBitsStored: mi.Multi.Bitarray.ExtraBitsStored,
},
ModeInfos: modeInfos,
},
}
}
}
func fromV2ModeInfo(v2 *txv1beta1.ModeInfo, v1 *tx.ModeInfo) {
// Check if v2 is nil. If so, return as there's nothing to convert.
if v2 == nil {
return
}
switch mi := v2.Sum.(type) {
case *txv1beta1.ModeInfo_Single_:
// Convert from v2 single mode to v1 single mode
v1.Sum = &tx.ModeInfo_Single_{
Single: &tx.ModeInfo_Single{
Mode: signing.SignMode(mi.Single.Mode),
},
}
case *txv1beta1.ModeInfo_Multi_:
// Convert from v2 multi mode to v1 multi mode
multiModeInfos := mi.Multi.ModeInfos
modeInfos := make([]*tx.ModeInfo, len(multiModeInfos))
// Recursively convert each modeInfo
for i, modeInfo := range multiModeInfos {
modeInfos[i] = &tx.ModeInfo{}
fromV2ModeInfo(modeInfo, modeInfos[i])
}
v1.Sum = &tx.ModeInfo_Multi_{
Multi: &tx.ModeInfo_Multi{
Bitarray: &cryptotypes.CompactBitArray{
Elems: mi.Multi.Bitarray.Elems,
ExtraBitsStored: mi.Multi.Bitarray.ExtraBitsStored,
},
ModeInfos: modeInfos,
},
}
}
}