github.com/Finschia/finschia-sdk@v0.48.1/client/tx/tx_test.go (about)

     1  package tx_test
     2  
     3  import (
     4  	gocontext "context"
     5  	"fmt"
     6  	"testing"
     7  
     8  	"github.com/stretchr/testify/require"
     9  	"google.golang.org/grpc"
    10  
    11  	"github.com/Finschia/finschia-sdk/client"
    12  	"github.com/Finschia/finschia-sdk/client/tx"
    13  	"github.com/Finschia/finschia-sdk/crypto/hd"
    14  	"github.com/Finschia/finschia-sdk/crypto/keyring"
    15  	cryptotypes "github.com/Finschia/finschia-sdk/crypto/types"
    16  	"github.com/Finschia/finschia-sdk/simapp"
    17  	"github.com/Finschia/finschia-sdk/testutil/network"
    18  	sdk "github.com/Finschia/finschia-sdk/types"
    19  	txtypes "github.com/Finschia/finschia-sdk/types/tx"
    20  	signingtypes "github.com/Finschia/finschia-sdk/types/tx/signing"
    21  	"github.com/Finschia/finschia-sdk/x/auth/signing"
    22  	authtypes "github.com/Finschia/finschia-sdk/x/auth/types"
    23  	banktypes "github.com/Finschia/finschia-sdk/x/bank/types"
    24  )
    25  
    26  func NewTestTxConfig() client.TxConfig {
    27  	cfg := simapp.MakeTestEncodingConfig()
    28  	return cfg.TxConfig
    29  }
    30  
    31  // mockContext is a mock client.Context to return abitrary simulation response, used to
    32  // unit test CalculateGas.
    33  type mockContext struct {
    34  	gasUsed uint64
    35  	wantErr bool
    36  }
    37  
    38  func (m mockContext) Invoke(grpcCtx gocontext.Context, method string, req, reply interface{}, opts ...grpc.CallOption) (err error) {
    39  	if m.wantErr {
    40  		return fmt.Errorf("mock err")
    41  	}
    42  
    43  	*(reply.(*txtypes.SimulateResponse)) = txtypes.SimulateResponse{
    44  		GasInfo: &sdk.GasInfo{GasUsed: m.gasUsed, GasWanted: m.gasUsed},
    45  		Result:  &sdk.Result{Data: []byte("tx data"), Log: "log"},
    46  	}
    47  
    48  	return nil
    49  }
    50  
    51  func (mockContext) NewStream(gocontext.Context, *grpc.StreamDesc, string, ...grpc.CallOption) (grpc.ClientStream, error) {
    52  	panic("not implemented")
    53  }
    54  
    55  func TestCalculateGas(t *testing.T) {
    56  	type args struct {
    57  		mockGasUsed uint64
    58  		mockWantErr bool
    59  		adjustment  float64
    60  	}
    61  
    62  	testCases := []struct {
    63  		name         string
    64  		args         args
    65  		wantEstimate uint64
    66  		wantAdjusted uint64
    67  		expPass      bool
    68  	}{
    69  		{"error", args{0, true, 1.2}, 0, 0, false},
    70  		{"adjusted gas", args{10, false, 1.2}, 10, 12, true},
    71  	}
    72  
    73  	for _, tc := range testCases {
    74  		stc := tc
    75  		txCfg := NewTestTxConfig()
    76  
    77  		txf := tx.Factory{}.
    78  			WithChainID("test-chain").
    79  			WithTxConfig(txCfg).WithSignMode(txCfg.SignModeHandler().DefaultMode())
    80  
    81  		t.Run(stc.name, func(t *testing.T) {
    82  			mockClientCtx := mockContext{
    83  				gasUsed: tc.args.mockGasUsed,
    84  				wantErr: tc.args.mockWantErr,
    85  			}
    86  			simRes, gotAdjusted, err := tx.CalculateGas(mockClientCtx, txf.WithGasAdjustment(stc.args.adjustment))
    87  			if stc.expPass {
    88  				require.NoError(t, err)
    89  				require.Equal(t, simRes.GasInfo.GasUsed, stc.wantEstimate)
    90  				require.Equal(t, gotAdjusted, stc.wantAdjusted)
    91  				require.NotNil(t, simRes.Result)
    92  			} else {
    93  				require.Error(t, err)
    94  				require.Nil(t, simRes)
    95  			}
    96  		})
    97  	}
    98  }
    99  
   100  func TestBuildSimTx(t *testing.T) {
   101  	txCfg := NewTestTxConfig()
   102  
   103  	msg := banktypes.NewMsgSend(sdk.AccAddress("from"), sdk.AccAddress("to"), nil)
   104  	kb, err := keyring.New(t.Name(), "test", t.TempDir(), nil)
   105  	require.NoError(t, err)
   106  
   107  	txf := tx.Factory{}.
   108  		WithTxConfig(txCfg).
   109  		WithAccountNumber(50).
   110  		WithSequence(23).
   111  		WithFees("50stake").
   112  		WithMemo("memo").
   113  		WithChainID("test-chain").
   114  		WithSignMode(txCfg.SignModeHandler().DefaultMode()).
   115  		WithKeybase(kb)
   116  
   117  	// empty keybase list
   118  	bz, err := tx.BuildSimTx(txf, msg)
   119  	require.Error(t, err)
   120  	require.Nil(t, bz)
   121  
   122  	// with keybase list
   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  	txf = txf.WithKeybase(kb)
   127  	bz, err = tx.BuildSimTx(txf, msg)
   128  	require.NoError(t, err)
   129  	require.NotNil(t, bz)
   130  
   131  	// no ChainID
   132  	txf = txf.WithChainID("")
   133  	bz, err = tx.BuildSimTx(txf, msg)
   134  	require.Error(t, err)
   135  	require.Nil(t, bz)
   136  }
   137  
   138  func TestBuildUnsignedTx(t *testing.T) {
   139  	kb, err := keyring.New(t.Name(), "test", t.TempDir(), nil)
   140  	require.NoError(t, err)
   141  
   142  	path := hd.CreateHDPath(118, 0, 0).String()
   143  
   144  	_, _, err = kb.NewMnemonic("test_key1", keyring.English, path, keyring.DefaultBIP39Passphrase, hd.Secp256k1)
   145  	require.NoError(t, err)
   146  
   147  	txf := tx.Factory{}.
   148  		WithTxConfig(NewTestTxConfig()).
   149  		WithAccountNumber(50).
   150  		WithSequence(23).
   151  		WithGasPrices("50stake,50cony").
   152  		WithMemo("memo").
   153  		WithChainID("test-chain")
   154  
   155  	msg := banktypes.NewMsgSend(sdk.AccAddress("from"), sdk.AccAddress("to"), nil)
   156  	txBuiler, err := tx.BuildUnsignedTx(txf, msg)
   157  	require.NoError(t, err)
   158  	require.NotNil(t, txBuiler)
   159  
   160  	sigs, err := txBuiler.GetTx().(signing.SigVerifiableTx).GetSignaturesV2()
   161  	require.NoError(t, err)
   162  	require.Empty(t, sigs)
   163  
   164  	// no ChainID
   165  	txf = txf.WithChainID("")
   166  	txBuiler, err = tx.BuildUnsignedTx(txf, msg)
   167  	require.Nil(t, txBuiler)
   168  	require.Error(t, err)
   169  
   170  	// both fees and gas prices
   171  	txf = txf.
   172  		WithChainID("test-chain").
   173  		WithFees("50stake")
   174  	txBuiler, err = tx.BuildUnsignedTx(txf, msg)
   175  	require.Nil(t, txBuiler)
   176  	require.Error(t, err)
   177  }
   178  
   179  func TestPrintUnsignedTx(t *testing.T) {
   180  	txConfig := NewTestTxConfig()
   181  	txf := tx.Factory{}.
   182  		WithTxConfig(txConfig).
   183  		WithAccountNumber(50).
   184  		WithSequence(23).
   185  		WithFees("50stake").
   186  		WithMemo("memo").
   187  		WithChainID("test-chain")
   188  
   189  	msg := banktypes.NewMsgSend(sdk.AccAddress("from"), sdk.AccAddress("to"), nil)
   190  	clientCtx := client.Context{}.
   191  		WithTxConfig(txConfig)
   192  	err := txf.PrintUnsignedTx(clientCtx, msg)
   193  	require.NoError(t, err)
   194  
   195  	// no ChainID
   196  	txf = txf.WithChainID("")
   197  	err = txf.PrintUnsignedTx(clientCtx, msg)
   198  	require.Error(t, err)
   199  
   200  	// SimulateAndExecute
   201  	// failed at CaculateGas
   202  	txf = txf.WithSimulateAndExecute(true)
   203  	err = txf.PrintUnsignedTx(clientCtx, msg)
   204  	require.Error(t, err)
   205  
   206  	// Offline
   207  	clientCtx = clientCtx.WithOffline(true)
   208  	err = txf.PrintUnsignedTx(clientCtx, msg)
   209  	require.Error(t, err)
   210  }
   211  
   212  func TestSign(t *testing.T) {
   213  	requireT := require.New(t)
   214  	path := hd.CreateHDPath(118, 0, 0).String()
   215  	kr, err := keyring.New(t.Name(), "test", t.TempDir(), nil)
   216  	requireT.NoError(err)
   217  
   218  	from1 := "test_key1"
   219  	from2 := "test_key2"
   220  
   221  	// create a new key using a mnemonic generator and test if we can reuse seed to recreate that account
   222  	_, seed, err := kr.NewMnemonic(from1, keyring.English, path, keyring.DefaultBIP39Passphrase, hd.Secp256k1)
   223  	requireT.NoError(err)
   224  	requireT.NoError(kr.Delete(from1))
   225  	info1, _, err := kr.NewMnemonic(from1, keyring.English, path, keyring.DefaultBIP39Passphrase, hd.Secp256k1)
   226  	requireT.NoError(err)
   227  
   228  	info2, err := kr.NewAccount(from2, seed, "", path, hd.Secp256k1)
   229  	requireT.NoError(err)
   230  
   231  	pubKey1 := info1.GetPubKey()
   232  	pubKey2 := info2.GetPubKey()
   233  	requireT.NotEqual(pubKey1.Bytes(), pubKey2.Bytes())
   234  	t.Log("Pub keys:", pubKey1, pubKey2)
   235  
   236  	txfNoKeybase := tx.Factory{}.
   237  		WithTxConfig(NewTestTxConfig()).
   238  		WithAccountNumber(50).
   239  		WithSequence(23).
   240  		WithFees("50stake").
   241  		WithMemo("memo").
   242  		WithChainID("test-chain")
   243  	txfDirect := txfNoKeybase.
   244  		WithKeybase(kr).
   245  		WithSignMode(signingtypes.SignMode_SIGN_MODE_DIRECT)
   246  	txfAmino := txfDirect.
   247  		WithSignMode(signingtypes.SignMode_SIGN_MODE_LEGACY_AMINO_JSON)
   248  	msg1 := banktypes.NewMsgSend(info1.GetAddress(), sdk.AccAddress("to"), nil)
   249  	msg2 := banktypes.NewMsgSend(info2.GetAddress(), sdk.AccAddress("to"), nil)
   250  
   251  	txb, err := tx.BuildUnsignedTx(txfNoKeybase, msg1, msg2)
   252  	requireT.NoError(err)
   253  	txb2, err := tx.BuildUnsignedTx(txfNoKeybase, msg1, msg2)
   254  	requireT.NoError(err)
   255  	txbSimple, err := tx.BuildUnsignedTx(txfNoKeybase, msg2)
   256  	requireT.NoError(err)
   257  
   258  	testCases := []struct {
   259  		name         string
   260  		txf          tx.Factory
   261  		txb          client.TxBuilder
   262  		from         string
   263  		overwrite    bool
   264  		expectedPKs  []cryptotypes.PubKey
   265  		matchingSigs []int // if not nil, check matching signature against old ones.
   266  	}{
   267  		{
   268  			"should fail if txf without keyring",
   269  			txfNoKeybase, txb, from1, true, nil, nil,
   270  		},
   271  		{
   272  			"should fail for non existing key",
   273  			txfAmino, txb, "unknown", true, nil, nil,
   274  		},
   275  		{
   276  			"amino: should succeed with keyring",
   277  			txfAmino, txbSimple, from1, true,
   278  			[]cryptotypes.PubKey{pubKey1},
   279  			nil,
   280  		},
   281  		{
   282  			"direct: should succeed with keyring",
   283  			txfDirect, txbSimple, from1, true,
   284  			[]cryptotypes.PubKey{pubKey1},
   285  			nil,
   286  		},
   287  
   288  		/**** test double sign Amino mode ****/
   289  		{
   290  			"amino: should sign multi-signers tx",
   291  			txfAmino, txb, from1, true,
   292  			[]cryptotypes.PubKey{pubKey1},
   293  			nil,
   294  		},
   295  		{
   296  			"amino: should append a second signature and not overwrite",
   297  			txfAmino, txb, from2, false,
   298  			[]cryptotypes.PubKey{pubKey1, pubKey2},
   299  			[]int{0, 0},
   300  		},
   301  		{
   302  			"amino: should overwrite a signature",
   303  			txfAmino, txb, from2, true,
   304  			[]cryptotypes.PubKey{pubKey2},
   305  			[]int{1, 0},
   306  		},
   307  
   308  		/**** test double sign Direct mode
   309  		  signing transaction with 2 or more DIRECT signers should fail in DIRECT mode ****/
   310  		{
   311  			"direct: should append a DIRECT signature with existing AMINO",
   312  			// txb already has 1 AMINO signature
   313  			txfDirect, txb, from1, false,
   314  			[]cryptotypes.PubKey{pubKey2, pubKey1},
   315  			nil,
   316  		},
   317  		{
   318  			"direct: should add single DIRECT sig in multi-signers tx",
   319  			txfDirect, txb2, from1, false,
   320  			[]cryptotypes.PubKey{pubKey1},
   321  			nil,
   322  		},
   323  		{
   324  			"direct: should fail to append 2nd DIRECT sig in multi-signers tx",
   325  			txfDirect, txb2, from2, false,
   326  			[]cryptotypes.PubKey{},
   327  			nil,
   328  		},
   329  		{
   330  			"amino: should append 2nd AMINO sig in multi-signers tx with 1 DIRECT sig",
   331  			// txb2 already has 1 DIRECT signature
   332  			txfAmino, txb2, from2, false,
   333  			[]cryptotypes.PubKey{},
   334  			nil,
   335  		},
   336  		{
   337  			"direct: should overwrite multi-signers tx with DIRECT sig",
   338  			txfDirect, txb2, from1, true,
   339  			[]cryptotypes.PubKey{pubKey1},
   340  			nil,
   341  		},
   342  	}
   343  	var prevSigs []signingtypes.SignatureV2
   344  	for _, tc := range testCases {
   345  		t.Run(tc.name, func(_ *testing.T) {
   346  			err = tx.Sign(tc.txf, tc.from, tc.txb, tc.overwrite)
   347  			if len(tc.expectedPKs) == 0 {
   348  				requireT.Error(err)
   349  			} else {
   350  				requireT.NoError(err)
   351  				sigs := testSigners(requireT, tc.txb.GetTx(), tc.expectedPKs...)
   352  				if tc.matchingSigs != nil {
   353  					requireT.Equal(prevSigs[tc.matchingSigs[0]], sigs[tc.matchingSigs[1]])
   354  				}
   355  				prevSigs = sigs
   356  			}
   357  		})
   358  	}
   359  }
   360  
   361  func testSigners(require *require.Assertions, tr signing.Tx, pks ...cryptotypes.PubKey) []signingtypes.SignatureV2 {
   362  	sigs, err := tr.GetSignaturesV2()
   363  	require.Len(sigs, len(pks))
   364  	require.NoError(err)
   365  	require.Len(sigs, len(pks))
   366  	for i := range pks {
   367  		require.True(sigs[i].PubKey.Equals(pks[i]), "Signature is signed with a wrong pubkey. Got: %s, expected: %s", sigs[i].PubKey, pks[i])
   368  	}
   369  	return sigs
   370  }
   371  
   372  func TestPrepare(t *testing.T) {
   373  	txf := tx.Factory{}.
   374  		WithAccountRetriever(authtypes.AccountRetriever{})
   375  
   376  	cfg := network.DefaultConfig()
   377  	cfg.NumValidators = 1
   378  
   379  	network := network.New(t, cfg)
   380  	defer network.Cleanup()
   381  
   382  	_, err := network.WaitForHeight(3)
   383  	require.NoError(t, err)
   384  
   385  	val := network.Validators[0]
   386  	clientCtx := val.ClientCtx.
   387  		WithHeight(2).
   388  		WithFromAddress(val.Address)
   389  
   390  	_, err = txf.Prepare(clientCtx)
   391  	require.NoError(t, err)
   392  }