cosmos-sdk/simsx/v2/txutils.go
2025-01-22 11:15:49 +00:00

203 lines
5.2 KiB
Go

package v2
import (
"context"
"errors"
"fmt"
"math/rand"
"google.golang.org/protobuf/types/known/anypb"
"cosmossdk.io/core/transaction"
txsigning "cosmossdk.io/x/tx/signing"
"github.com/cosmos/cosmos-sdk/client"
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
"github.com/cosmos/cosmos-sdk/simsx"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/simulation"
"github.com/cosmos/cosmos-sdk/types/tx/signing"
authsign "github.com/cosmos/cosmos-sdk/x/auth/signing"
)
const DefaultGenTxGas = 10_000_000
type Tx = transaction.Tx
// TXBuilder abstract transaction builder
type TXBuilder[T Tx] interface {
// Build creates a signed transaction
Build(ctx context.Context,
ak simsx.AccountSource,
senders []simsx.SimAccount,
msg sdk.Msg,
r *rand.Rand,
chainID string,
) (T, error)
}
var _ TXBuilder[Tx] = TXBuilderFn[Tx](nil)
// TXBuilderFn adapter that implements the TXBuilder interface
type TXBuilderFn[T Tx] func(ctx context.Context, ak simsx.AccountSource, senders []simsx.SimAccount, msg sdk.Msg, r *rand.Rand, chainID string) (T, error)
func (b TXBuilderFn[T]) Build(ctx context.Context, ak simsx.AccountSource, senders []simsx.SimAccount, msg sdk.Msg, r *rand.Rand, chainID string) (T, error) {
return b(ctx, ak, senders, msg, r, chainID)
}
// NewSDKTXBuilder constructor to create a signed transaction builder for sdk.Tx type.
func NewSDKTXBuilder[T Tx](txConfig client.TxConfig, defaultGas uint64) TXBuilder[T] {
return TXBuilderFn[T](func(ctx context.Context, ak simsx.AccountSource, senders []simsx.SimAccount, msg sdk.Msg, r *rand.Rand, chainID string) (tx T, err error) {
accountNumbers := make([]uint64, len(senders))
sequenceNumbers := make([]uint64, len(senders))
for i := 0; i < len(senders); i++ {
acc := ak.GetAccount(ctx, senders[i].Address)
accountNumbers[i] = acc.GetAccountNumber()
sequenceNumbers[i] = acc.GetSequence()
}
fees := senders[0].LiquidBalance().RandFees()
sdkTx, err := GenSignedMockTx(
r,
txConfig,
[]sdk.Msg{msg},
fees,
defaultGas,
chainID,
accountNumbers,
sequenceNumbers,
simsx.Collect(senders, func(a simsx.SimAccount) cryptotypes.PrivKey { return a.PrivKey })...,
)
if err != nil {
return tx, err
}
out, ok := sdkTx.(T)
if !ok {
return out, errors.New("unexpected Tx type")
}
return out, nil
})
}
// GenSignedMockTx generates a signed mock transaction.
func GenSignedMockTx(
r *rand.Rand,
txConfig client.TxConfig,
msgs []sdk.Msg,
feeAmt sdk.Coins,
gas uint64,
chainID string,
accNums, accSeqs []uint64,
priv ...cryptotypes.PrivKey,
) (sdk.Tx, error) {
sigs := make([]signing.SignatureV2, len(priv))
// create a random length memo
memo := simulation.RandStringOfLength(r, simulation.RandIntBetween(r, 0, 100))
// 1st round: set SignatureV2 with empty signatures, to set correct
// signer infos.
for i, p := range priv {
sigs[i] = signing.SignatureV2{
PubKey: p.PubKey(),
Data: &signing.SingleSignatureData{
SignMode: txConfig.SignModeHandler().DefaultMode(),
},
Sequence: accSeqs[i],
}
}
tx := txConfig.NewTxBuilder()
err := tx.SetMsgs(msgs...)
if err != nil {
return nil, err
}
err = tx.SetSignatures(sigs...)
if err != nil {
return nil, err
}
tx.SetMemo(memo)
tx.SetFeeAmount(feeAmt)
tx.SetGasLimit(gas)
// 2nd round: once all signer infos are set, every signer can sign.
for i, p := range priv {
anyPk, err := codectypes.NewAnyWithValue(p.PubKey())
if err != nil {
return nil, err
}
signerData := txsigning.SignerData{
Address: sdk.AccAddress(p.PubKey().Address()).String(),
ChainID: chainID,
AccountNumber: accNums[i],
Sequence: accSeqs[i],
PubKey: &anypb.Any{TypeUrl: anyPk.TypeUrl, Value: anyPk.Value},
}
signBytes, err := authsign.GetSignBytesAdapter(
context.Background(), txConfig.SignModeHandler(), txConfig.SignModeHandler().DefaultMode(), signerData,
tx.GetTx())
if err != nil {
return nil, fmt.Errorf("sign bytes: %w", err)
}
sig, err := p.Sign(signBytes)
if err != nil {
return nil, fmt.Errorf("sign: %w", err)
}
sigs[i].Data.(*signing.SingleSignatureData).Signature = sig
}
if err = tx.SetSignatures(sigs...); err != nil {
return nil, fmt.Errorf("signature: %w", err)
}
return tx.GetTx(), nil
}
var _ transaction.Codec[Tx] = &GenericTxDecoder[Tx]{}
// GenericTxDecoder Encoder type that implements transaction.Codec
type GenericTxDecoder[T Tx] struct {
txConfig client.TxConfig
}
// NewGenericTxDecoder constructor
func NewGenericTxDecoder[T Tx](txConfig client.TxConfig) *GenericTxDecoder[T] {
return &GenericTxDecoder[T]{txConfig: txConfig}
}
// Decode implements transaction.Codec.
func (t *GenericTxDecoder[T]) Decode(bz []byte) (T, error) {
var out T
tx, err := t.txConfig.TxDecoder()(bz)
if err != nil {
return out, err
}
var ok bool
out, ok = tx.(T)
if !ok {
return out, errors.New("unexpected Tx type")
}
return out, nil
}
// DecodeJSON implements transaction.Codec.
func (t *GenericTxDecoder[T]) DecodeJSON(bz []byte) (T, error) {
var out T
tx, err := t.txConfig.TxJSONDecoder()(bz)
if err != nil {
return out, err
}
var ok bool
out, ok = tx.(T)
if !ok {
return out, errors.New("unexpected Tx type")
}
return out, nil
}