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 }