github.com/cosmos/cosmos-sdk@v0.50.10/client/tx/tx_test.go (about)

     1  package tx
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"strings"
     7  	"testing"
     8  
     9  	"github.com/stretchr/testify/require"
    10  	"google.golang.org/grpc"
    11  
    12  	"github.com/cosmos/cosmos-sdk/client"
    13  	"github.com/cosmos/cosmos-sdk/codec"
    14  	codectypes "github.com/cosmos/cosmos-sdk/codec/types"
    15  	"github.com/cosmos/cosmos-sdk/crypto/hd"
    16  	"github.com/cosmos/cosmos-sdk/crypto/keyring"
    17  	cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
    18  	"github.com/cosmos/cosmos-sdk/testutil/testdata"
    19  	sdk "github.com/cosmos/cosmos-sdk/types"
    20  	moduletestutil "github.com/cosmos/cosmos-sdk/types/module/testutil"
    21  	txtypes "github.com/cosmos/cosmos-sdk/types/tx"
    22  	signingtypes "github.com/cosmos/cosmos-sdk/types/tx/signing"
    23  	ante "github.com/cosmos/cosmos-sdk/x/auth/ante"
    24  	"github.com/cosmos/cosmos-sdk/x/auth/signing"
    25  	authtx "github.com/cosmos/cosmos-sdk/x/auth/tx"
    26  	banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
    27  )
    28  
    29  func newTestTxConfig() (client.TxConfig, codec.Codec) {
    30  	encodingConfig := moduletestutil.MakeTestEncodingConfig()
    31  	return authtx.NewTxConfig(codec.NewProtoCodec(encodingConfig.InterfaceRegistry), authtx.DefaultSignModes), encodingConfig.Codec
    32  }
    33  
    34  // mockContext is a mock client.Context to return abitrary simulation response, used to
    35  // unit test CalculateGas.
    36  type mockContext struct {
    37  	gasUsed uint64
    38  	wantErr bool
    39  }
    40  
    41  func (m mockContext) Invoke(_ context.Context, _ string, _, reply interface{}, _ ...grpc.CallOption) (err error) {
    42  	if m.wantErr {
    43  		return fmt.Errorf("mock err")
    44  	}
    45  
    46  	*(reply.(*txtypes.SimulateResponse)) = txtypes.SimulateResponse{
    47  		GasInfo: &sdk.GasInfo{GasUsed: m.gasUsed, GasWanted: m.gasUsed},
    48  		Result:  &sdk.Result{Data: []byte("tx data"), Log: "log"},
    49  	}
    50  
    51  	return nil
    52  }
    53  
    54  func (mockContext) NewStream(context.Context, *grpc.StreamDesc, string, ...grpc.CallOption) (grpc.ClientStream, error) {
    55  	panic("not implemented")
    56  }
    57  
    58  func TestCalculateGas(t *testing.T) {
    59  	type args struct {
    60  		mockGasUsed uint64
    61  		mockWantErr bool
    62  		adjustment  float64
    63  	}
    64  
    65  	testCases := []struct {
    66  		name         string
    67  		args         args
    68  		wantEstimate uint64
    69  		wantAdjusted uint64
    70  		expPass      bool
    71  	}{
    72  		{"error", args{0, true, 1.2}, 0, 0, false},
    73  		{"adjusted gas", args{10, false, 1.2}, 10, 12, true},
    74  	}
    75  
    76  	for _, tc := range testCases {
    77  		stc := tc
    78  		txCfg, _ := newTestTxConfig()
    79  		defaultSignMode, err := signing.APISignModeToInternal(txCfg.SignModeHandler().DefaultMode())
    80  		require.NoError(t, err)
    81  
    82  		txf := Factory{}.
    83  			WithChainID("test-chain").
    84  			WithTxConfig(txCfg).WithSignMode(defaultSignMode)
    85  
    86  		t.Run(stc.name, func(t *testing.T) {
    87  			mockClientCtx := mockContext{
    88  				gasUsed: tc.args.mockGasUsed,
    89  				wantErr: tc.args.mockWantErr,
    90  			}
    91  			simRes, gotAdjusted, err := CalculateGas(mockClientCtx, txf.WithGasAdjustment(stc.args.adjustment))
    92  			if stc.expPass {
    93  				require.NoError(t, err)
    94  				require.Equal(t, simRes.GasInfo.GasUsed, stc.wantEstimate)
    95  				require.Equal(t, gotAdjusted, stc.wantAdjusted)
    96  				require.NotNil(t, simRes.Result)
    97  			} else {
    98  				require.Error(t, err)
    99  				require.Nil(t, simRes)
   100  			}
   101  		})
   102  	}
   103  }
   104  
   105  func mockTxFactory(txCfg client.TxConfig) Factory {
   106  	return Factory{}.
   107  		WithTxConfig(txCfg).
   108  		WithAccountNumber(50).
   109  		WithSequence(23).
   110  		WithFees("50stake").
   111  		WithMemo("memo").
   112  		WithChainID("test-chain")
   113  }
   114  
   115  func TestBuildSimTx(t *testing.T) {
   116  	txCfg, cdc := newTestTxConfig()
   117  	defaultSignMode, err := signing.APISignModeToInternal(txCfg.SignModeHandler().DefaultMode())
   118  	require.NoError(t, err)
   119  
   120  	kb, err := keyring.New(t.Name(), "test", t.TempDir(), nil, cdc)
   121  	require.NoError(t, err)
   122  
   123  	path := hd.CreateHDPath(118, 0, 0).String()
   124  	_, _, err = kb.NewMnemonic("test_key1", keyring.English, path, keyring.DefaultBIP39Passphrase, hd.Secp256k1)
   125  	require.NoError(t, err)
   126  
   127  	txf := mockTxFactory(txCfg).WithSignMode(defaultSignMode).WithKeybase(kb)
   128  	msg := banktypes.NewMsgSend(sdk.AccAddress("from"), sdk.AccAddress("to"), nil)
   129  	bz, err := txf.BuildSimTx(msg)
   130  	require.NoError(t, err)
   131  	require.NotNil(t, bz)
   132  }
   133  
   134  func TestBuildUnsignedTx(t *testing.T) {
   135  	txConfig, cdc := newTestTxConfig()
   136  	kb, err := keyring.New(t.Name(), "test", t.TempDir(), nil, cdc)
   137  	require.NoError(t, err)
   138  
   139  	path := hd.CreateHDPath(118, 0, 0).String()
   140  
   141  	_, _, err = kb.NewMnemonic("test_key1", keyring.English, path, keyring.DefaultBIP39Passphrase, hd.Secp256k1)
   142  	require.NoError(t, err)
   143  	txf := mockTxFactory(txConfig).WithKeybase(kb)
   144  	msg := banktypes.NewMsgSend(sdk.AccAddress("from"), sdk.AccAddress("to"), nil)
   145  	tx, err := txf.BuildUnsignedTx(msg)
   146  	require.NoError(t, err)
   147  	require.NotNil(t, tx)
   148  
   149  	sigs, err := tx.GetTx().(signing.SigVerifiableTx).GetSignaturesV2()
   150  	require.NoError(t, err)
   151  	require.Empty(t, sigs)
   152  }
   153  
   154  func TestBuildUnsignedTxWithWithExtensionOptions(t *testing.T) {
   155  	txCfg := moduletestutil.MakeBuilderTestTxConfig()
   156  	extOpts := []*codectypes.Any{
   157  		{
   158  			TypeUrl: "/test",
   159  			Value:   []byte("test"),
   160  		},
   161  	}
   162  	txf := mockTxFactory(txCfg).WithExtensionOptions(extOpts...)
   163  	msg := banktypes.NewMsgSend(sdk.AccAddress("from"), sdk.AccAddress("to"), nil)
   164  	tx, err := txf.BuildUnsignedTx(msg)
   165  	require.NoError(t, err)
   166  	require.NotNil(t, tx)
   167  	txb := tx.(*moduletestutil.TestTxBuilder)
   168  	require.Equal(t, extOpts, txb.ExtOptions)
   169  }
   170  
   171  func TestMnemonicInMemo(t *testing.T) {
   172  	txConfig, cdc := newTestTxConfig()
   173  	kb, err := keyring.New(t.Name(), "test", t.TempDir(), nil, cdc)
   174  	require.NoError(t, err)
   175  
   176  	path := hd.CreateHDPath(118, 0, 0).String()
   177  
   178  	_, seed, err := kb.NewMnemonic("test_key1", keyring.English, path, keyring.DefaultBIP39Passphrase, hd.Secp256k1)
   179  	require.NoError(t, err)
   180  
   181  	testCases := []struct {
   182  		name  string
   183  		memo  string
   184  		error bool
   185  	}{
   186  		{name: "bare seed", memo: seed, error: true},
   187  		{name: "padding bare seed", memo: fmt.Sprintf("   %s", seed), error: true},
   188  		{name: "prefixed", memo: fmt.Sprintf("%s: %s", "prefixed: ", seed), error: false},
   189  		{name: "normal memo", memo: "this is a memo", error: false},
   190  		{name: "empty memo", memo: "", error: false},
   191  		{name: "invalid mnemonic", memo: strings.Repeat("egg", 24), error: false},
   192  		{name: "caps", memo: strings.ToUpper(seed), error: true},
   193  	}
   194  
   195  	for _, tc := range testCases {
   196  		t.Run(tc.name, func(t *testing.T) {
   197  			txf := Factory{}.
   198  				WithTxConfig(txConfig).
   199  				WithAccountNumber(50).
   200  				WithSequence(23).
   201  				WithFees("50stake").
   202  				WithMemo(tc.memo).
   203  				WithChainID("test-chain").
   204  				WithKeybase(kb)
   205  
   206  			msg := banktypes.NewMsgSend(sdk.AccAddress("from"), sdk.AccAddress("to"), nil)
   207  			tx, err := txf.BuildUnsignedTx(msg)
   208  			if tc.error {
   209  				require.Error(t, err)
   210  				require.ErrorContains(t, err, "mnemonic")
   211  				require.Nil(t, tx)
   212  			} else {
   213  				require.NoError(t, err)
   214  				require.NotNil(t, tx)
   215  			}
   216  		})
   217  	}
   218  }
   219  
   220  func TestSign(t *testing.T) {
   221  	txConfig, cdc := newTestTxConfig()
   222  	requireT := require.New(t)
   223  	path := hd.CreateHDPath(118, 0, 0).String()
   224  	kb, err := keyring.New(t.Name(), "test", t.TempDir(), nil, cdc)
   225  	requireT.NoError(err)
   226  
   227  	from1 := "test_key1"
   228  	from2 := "test_key2"
   229  
   230  	// create a new key using a mnemonic generator and test if we can reuse seed to recreate that account
   231  	_, seed, err := kb.NewMnemonic(from1, keyring.English, path, keyring.DefaultBIP39Passphrase, hd.Secp256k1)
   232  	requireT.NoError(err)
   233  	requireT.NoError(kb.Delete(from1))
   234  	k1, _, err := kb.NewMnemonic(from1, keyring.English, path, keyring.DefaultBIP39Passphrase, hd.Secp256k1)
   235  	requireT.NoError(err)
   236  
   237  	k2, err := kb.NewAccount(from2, seed, "", path, hd.Secp256k1)
   238  	requireT.NoError(err)
   239  
   240  	pubKey1, err := k1.GetPubKey()
   241  	requireT.NoError(err)
   242  	pubKey2, err := k2.GetPubKey()
   243  	requireT.NoError(err)
   244  	requireT.NotEqual(pubKey1.Bytes(), pubKey2.Bytes())
   245  	t.Log("Pub keys:", pubKey1, pubKey2)
   246  
   247  	txfNoKeybase := mockTxFactory(txConfig)
   248  	txfDirect := txfNoKeybase.
   249  		WithKeybase(kb).
   250  		WithSignMode(signingtypes.SignMode_SIGN_MODE_DIRECT)
   251  	txfAmino := txfDirect.
   252  		WithSignMode(signingtypes.SignMode_SIGN_MODE_LEGACY_AMINO_JSON)
   253  	addr1, err := k1.GetAddress()
   254  	requireT.NoError(err)
   255  	addr2, err := k2.GetAddress()
   256  	requireT.NoError(err)
   257  	msg1 := banktypes.NewMsgSend(addr1, sdk.AccAddress("to"), nil)
   258  	msg2 := banktypes.NewMsgSend(addr2, sdk.AccAddress("to"), nil)
   259  	txb, err := txfNoKeybase.BuildUnsignedTx(msg1, msg2)
   260  	requireT.NoError(err)
   261  	txb2, err := txfNoKeybase.BuildUnsignedTx(msg1, msg2)
   262  	requireT.NoError(err)
   263  	txbSimple, err := txfNoKeybase.BuildUnsignedTx(msg2)
   264  	requireT.NoError(err)
   265  
   266  	testCases := []struct {
   267  		name         string
   268  		txf          Factory
   269  		txb          client.TxBuilder
   270  		from         string
   271  		overwrite    bool
   272  		expectedPKs  []cryptotypes.PubKey
   273  		matchingSigs []int // if not nil, check matching signature against old ones.
   274  	}{
   275  		{
   276  			"should fail if txf without keyring",
   277  			txfNoKeybase, txb, from1, true, nil, nil,
   278  		},
   279  		{
   280  			"should fail for non existing key",
   281  			txfAmino, txb, "unknown", true, nil, nil,
   282  		},
   283  		{
   284  			"amino: should succeed with keyring",
   285  			txfAmino, txbSimple, from1, true,
   286  			[]cryptotypes.PubKey{pubKey1},
   287  			nil,
   288  		},
   289  		{
   290  			"direct: should succeed with keyring",
   291  			txfDirect, txbSimple, from1, true,
   292  			[]cryptotypes.PubKey{pubKey1},
   293  			nil,
   294  		},
   295  
   296  		/**** test double sign Amino mode ****/
   297  		{
   298  			"amino: should sign multi-signers tx",
   299  			txfAmino, txb, from1, true,
   300  			[]cryptotypes.PubKey{pubKey1},
   301  			nil,
   302  		},
   303  		{
   304  			"amino: should append a second signature and not overwrite",
   305  			txfAmino, txb, from2, false,
   306  			[]cryptotypes.PubKey{pubKey1, pubKey2},
   307  			[]int{0, 0},
   308  		},
   309  		{
   310  			"amino: should overwrite a signature",
   311  			txfAmino, txb, from2, true,
   312  			[]cryptotypes.PubKey{pubKey2},
   313  			[]int{1, 0},
   314  		},
   315  
   316  		/**** test double sign Direct mode
   317  		  signing transaction with 2 or more DIRECT signers should fail in DIRECT mode ****/
   318  		{
   319  			"direct: should  append a DIRECT signature with existing AMINO",
   320  			// txb already has 1 AMINO signature
   321  			txfDirect, txb, from1, false,
   322  			[]cryptotypes.PubKey{pubKey2, pubKey1},
   323  			nil,
   324  		},
   325  		{
   326  			"direct: should add single DIRECT sig in multi-signers tx",
   327  			txfDirect, txb2, from1, false,
   328  			[]cryptotypes.PubKey{pubKey1},
   329  			nil,
   330  		},
   331  		{
   332  			"direct: should fail to append 2nd DIRECT sig in multi-signers tx",
   333  			txfDirect, txb2, from2, false,
   334  			[]cryptotypes.PubKey{},
   335  			nil,
   336  		},
   337  		{
   338  			"amino: should append 2nd AMINO sig in multi-signers tx with 1 DIRECT sig",
   339  			// txb2 already has 1 DIRECT signature
   340  			txfAmino, txb2, from2, false,
   341  			[]cryptotypes.PubKey{},
   342  			nil,
   343  		},
   344  		{
   345  			"direct: should overwrite multi-signers tx with DIRECT sig",
   346  			txfDirect, txb2, from1, true,
   347  			[]cryptotypes.PubKey{pubKey1},
   348  			nil,
   349  		},
   350  	}
   351  
   352  	var prevSigs []signingtypes.SignatureV2
   353  	for _, tc := range testCases {
   354  		t.Run(tc.name, func(t *testing.T) {
   355  			err = Sign(context.TODO(), tc.txf, tc.from, tc.txb, tc.overwrite)
   356  			if len(tc.expectedPKs) == 0 {
   357  				requireT.Error(err)
   358  			} else {
   359  				requireT.NoError(err)
   360  				sigs := testSigners(requireT, tc.txb.GetTx(), tc.expectedPKs...)
   361  				if tc.matchingSigs != nil {
   362  					requireT.Equal(prevSigs[tc.matchingSigs[0]], sigs[tc.matchingSigs[1]])
   363  				}
   364  				prevSigs = sigs
   365  			}
   366  		})
   367  	}
   368  }
   369  
   370  func TestPreprocessHook(t *testing.T) {
   371  	_, _, addr2 := testdata.KeyTestPubAddr()
   372  
   373  	txConfig, cdc := newTestTxConfig()
   374  	requireT := require.New(t)
   375  	path := hd.CreateHDPath(118, 0, 0).String()
   376  	kb, err := keyring.New(t.Name(), "test", t.TempDir(), nil, cdc)
   377  	requireT.NoError(err)
   378  
   379  	from := "test_key"
   380  	kr, _, err := kb.NewMnemonic(from, keyring.English, path, keyring.DefaultBIP39Passphrase, hd.Secp256k1)
   381  	requireT.NoError(err)
   382  
   383  	extVal := &testdata.Cat{
   384  		Moniker: "einstein",
   385  		Lives:   9,
   386  	}
   387  	extAny, err := codectypes.NewAnyWithValue(extVal)
   388  	requireT.NoError(err)
   389  
   390  	preprocessHook := client.PreprocessTxFn(func(chainID string, key keyring.KeyType, tx client.TxBuilder) error {
   391  		extensionBuilder, ok := tx.(authtx.ExtensionOptionsTxBuilder)
   392  		requireT.True(ok)
   393  
   394  		// Set new extension and tip
   395  		extensionBuilder.SetExtensionOptions(extAny)
   396  
   397  		return nil
   398  	})
   399  
   400  	txfDirect := mockTxFactory(txConfig).
   401  		WithKeybase(kb).
   402  		WithSignMode(signingtypes.SignMode_SIGN_MODE_DIRECT).
   403  		WithPreprocessTxHook(preprocessHook)
   404  
   405  	addr1, err := kr.GetAddress()
   406  	requireT.NoError(err)
   407  	msg1 := banktypes.NewMsgSend(addr1, sdk.AccAddress("to"), nil)
   408  	msg2 := banktypes.NewMsgSend(addr2, sdk.AccAddress("to"), nil)
   409  	txb, err := txfDirect.BuildUnsignedTx(msg1, msg2)
   410  	requireT.NoError(err)
   411  
   412  	err = Sign(context.TODO(), txfDirect, from, txb, false)
   413  	requireT.NoError(err)
   414  
   415  	// Run preprocessing
   416  	err = txfDirect.PreprocessTx(from, txb)
   417  	requireT.NoError(err)
   418  
   419  	hasExtOptsTx, ok := txb.(ante.HasExtensionOptionsTx)
   420  	requireT.True(ok)
   421  
   422  	hasOneExt := len(hasExtOptsTx.GetExtensionOptions()) == 1
   423  	requireT.True(hasOneExt)
   424  
   425  	opt := hasExtOptsTx.GetExtensionOptions()[0]
   426  	requireT.Equal(opt, extAny)
   427  }
   428  
   429  func testSigners(require *require.Assertions, tr signing.Tx, pks ...cryptotypes.PubKey) []signingtypes.SignatureV2 {
   430  	sigs, err := tr.GetSignaturesV2()
   431  	require.Len(sigs, len(pks))
   432  	require.NoError(err)
   433  	require.Len(sigs, len(pks))
   434  	for i := range pks {
   435  		require.True(sigs[i].PubKey.Equals(pks[i]), "Signature is signed with a wrong pubkey. Got: %s, expected: %s", sigs[i].PubKey, pks[i])
   436  	}
   437  	return sigs
   438  }