Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test: migrate e2e/tx tests to systemtest #22152

Merged
merged 20 commits into from
Oct 24, 2024
Merged
Prev Previous commit
Next Next commit
test grpc
hieuvubk committed Oct 7, 2024
commit 5d2292f1b07329ec3e4611fb8f2c1f53015f8c61
434 changes: 432 additions & 2 deletions tests/systemtests/tx_test.go
Original file line number Diff line number Diff line change
@@ -3,12 +3,24 @@
package systemtests

import (
"context"
"encoding/base64"

"fmt"

"testing"

"github.com/cosmos/cosmos-sdk/types/tx"
"github.com/stretchr/testify/require"
"github.com/tidwall/gjson"

sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/query"
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
)

var bankMsgSendEventAction = "message.action='/cosmos.bank.v1beta1.MsgSend'"

func TestQueryBySig(t *testing.T) {
sut.ResetChain(t)

@@ -24,14 +36,432 @@ func TestQueryBySig(t *testing.T) {

sut.StartChain(t)

// qc := tx.NewServiceClient(sut.RPCClient(t))
qc := tx.NewServiceClient(sut.RPCClient(t))

// create unsign tx
bankSendCmdArgs := []string{"tx", "bank", "send", valAddr, receiverAddr, fmt.Sprintf("%d%s", transferAmount, denom), "--fees=10stake", "--sign-mode=direct", "--generate-only"}
res := cli.RunCommandWithArgs(bankSendCmdArgs...)
txFile := StoreTempFile(t, []byte(res))
fmt.Println("txFile", txFile)

res = cli.RunCommandWithArgs("tx", "sign", txFile.Name(), fmt.Sprintf("--from=%s", valAddr), fmt.Sprintf("--chain-id=%s",sut.chainID), fmt.Sprintf("--output-document=%s", txFile.Name()))
res = cli.RunCommandWithArgs("tx", "sign", txFile.Name(), fmt.Sprintf("--from=%s", valAddr), fmt.Sprintf("--chain-id=%s", sut.chainID), "--keyring-backend=test", "--home=./testnet/node0/simd")
fmt.Println("res", res)
sig := gjson.Get(res, "signatures.0").String()
fmt.Println("sig", sig)
signedTxFile := StoreTempFile(t, []byte(res))

res = cli.Run("tx", "broadcast", signedTxFile.Name())
fmt.Println("res", res)
RequireTxSuccess(t, res)

sigFormatted := fmt.Sprintf("%s.%s='%s'", sdk.EventTypeTx, sdk.AttributeKeySignature, sig)
resp, err := qc.GetTxsEvent(context.Background(), &tx.GetTxsEventRequest{
Query: sigFormatted,
OrderBy: 0,
Page: 0,
Limit: 10,
})
require.NoError(t, err)
require.Len(t, resp.Txs, 1)
require.Len(t, resp.Txs[0].Signatures, 1)
}

func TestSimulateTx_GRPC(t *testing.T) {
sut.ResetChain(t)

cli := NewCLIWrapper(t, sut, verbose)
// get validator address
valAddr := cli.GetKeyAddr("node0")
require.NotEmpty(t, valAddr)

// add new key
receiverAddr := cli.AddKey("account1")
denom := "stake"
var transferAmount int64 = 1000

sut.StartChain(t)

qc := tx.NewServiceClient(sut.RPCClient(t))

// create unsign tx
bankSendCmdArgs := []string{"tx", "bank", "send", valAddr, receiverAddr, fmt.Sprintf("%d%s", transferAmount, denom), "--fees=10stake", "--sign-mode=direct", "--generate-only"}
res := cli.RunCommandWithArgs(bankSendCmdArgs...)
txFile := StoreTempFile(t, []byte(res))

res = cli.RunCommandWithArgs("tx", "sign", txFile.Name(), fmt.Sprintf("--from=%s", valAddr), fmt.Sprintf("--chain-id=%s", sut.chainID), "--keyring-backend=test", "--home=./testnet/node0/simd")
signedTxFile := StoreTempFile(t, []byte(res))

res = cli.RunCommandWithArgs("tx", "encode", signedTxFile.Name())
txBz, err := base64.StdEncoding.DecodeString(res)
require.NoError(t, err)

testCases := []struct {
name string
req *tx.SimulateRequest
expErr bool
expErrMsg string
}{
{"nil request", nil, true, "request cannot be nil"},
{"empty request", &tx.SimulateRequest{}, true, "empty txBytes is not allowed"},
{"valid request with tx_bytes", &tx.SimulateRequest{TxBytes: txBz}, false, ""},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
// Broadcast the tx via gRPC via the validator's clientCtx (which goes
// through Tendermint).
res, err := qc.Simulate(context.Background(), tc.req)
if tc.expErr {
require.Error(t, err)
require.Contains(t, err.Error(), tc.expErrMsg)
} else {
require.NoError(t, err)
// Check the result and gas used are correct.
//
// The 12 events are:
// - Sending Fee to the pool: coin_spent, coin_received and transfer
// - tx.* events: tx.fee, tx.acc_seq, tx.signature
// - Sending Amount to recipient: coin_spent, coin_received and transfer
// - Msg events: message.module=bank, message.action=/cosmos.bank.v1beta1.MsgSend and message.sender=<val1> (in one message)
require.Equal(t, 10, len(res.GetResult().GetEvents()))
Comment on lines +125 to +130
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Correct the expected number of events in the assertion or comment.

The comment mentions that there are 12 events expected, but the assertion checks for 10 events. This inconsistency should be addressed by updating the comment or the assertion to match the correct number of events.

require.True(t, res.GetGasInfo().GetGasUsed() > 0) // Gas used sometimes change, just check it's not empty.
}
})
}


}

func TestGetTxEvents_GRPC(t *testing.T) {
sut.ResetChain(t)

cli := NewCLIWrapper(t, sut, verbose)
// get validator address
valAddr := cli.GetKeyAddr("node0")
require.NotEmpty(t, valAddr)

// add new key
receiverAddr := cli.AddKey("account1")
denom := "stake"
var transferAmount int64 = 1000

sut.StartChain(t)

qc := tx.NewServiceClient(sut.RPCClient(t))
rsp := cli.Run("tx", "bank", "send", valAddr, receiverAddr, fmt.Sprintf("%d%s", transferAmount, denom), "--note=foobar", "--fees=1stake")
txResult, found := cli.AwaitTxCommitted(rsp)
require.True(t, found)
RequireTxSuccess(t, txResult)

rsp = cli.Run("tx", "bank", "send", valAddr, receiverAddr, fmt.Sprintf("%d%s", transferAmount, denom), "--fees=1stake")
txResult, found = cli.AwaitTxCommitted(rsp)
require.True(t, found)
RequireTxSuccess(t, txResult)

testCases := []struct {
name string
req *tx.GetTxsEventRequest
expErr bool
expErrMsg string
expLen int
}{
{
"nil request",
nil,
true,
"request cannot be nil",
0,
},
{
"empty request",
&tx.GetTxsEventRequest{},
true,
"query cannot be empty",
0,
},
{
"request with dummy event",
&tx.GetTxsEventRequest{Query: "foobar"},
true,
"failed to search for txs",
0,
},
{
"request with order-by",
&tx.GetTxsEventRequest{
Query: bankMsgSendEventAction,
OrderBy: tx.OrderBy_ORDER_BY_ASC,
},
false,
"",
2,
},
{
"without pagination",
&tx.GetTxsEventRequest{
Query: bankMsgSendEventAction,
},
false,
"",
2,
},
{
"with pagination",
&tx.GetTxsEventRequest{
Query: bankMsgSendEventAction,
Page: 1,
Limit: 1,
},
false,
"",
1,
},
{
"with multi events",
&tx.GetTxsEventRequest{
Query: fmt.Sprintf("%s AND message.module='bank'", bankMsgSendEventAction),
},
false,
"",
2,
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
// Query the tx via gRPC.
grpcRes, err := qc.GetTxsEvent(context.Background(), tc.req)
if tc.expErr {
require.Error(t, err)
require.Contains(t, err.Error(), tc.expErrMsg)
} else {
require.NoError(t, err)
require.GreaterOrEqual(t, len(grpcRes.Txs), 1)
require.Equal(t, "foobar", grpcRes.Txs[0].Body.Memo)
require.Equal(t, tc.expLen, len(grpcRes.Txs))

// Make sure fields are populated.
// ref: https://github.com/cosmos/cosmos-sdk/issues/8680
// ref: https://github.com/cosmos/cosmos-sdk/issues/8681
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems these links are closed. Please remove if not necessary.

require.NotEmpty(t, grpcRes.TxResponses[0].Timestamp)
require.Empty(t, grpcRes.TxResponses[0].RawLog) // logs are empty if the transactions are successful
}
})
}



}

func TestGetTx_GRPC(t *testing.T) {
sut.ResetChain(t)

cli := NewCLIWrapper(t, sut, verbose)
// get validator address
valAddr := cli.GetKeyAddr("node0")
require.NotEmpty(t, valAddr)

// add new key
receiverAddr := cli.AddKey("account1")
denom := "stake"
var transferAmount int64 = 1000

sut.StartChain(t)

qc := tx.NewServiceClient(sut.RPCClient(t))

rsp := cli.Run("tx", "bank", "send", valAddr, receiverAddr, fmt.Sprintf("%d%s", transferAmount, denom), "--fees=1stake", "--note=foobar")
txResult, found := cli.AwaitTxCommitted(rsp)
require.True(t, found)
RequireTxSuccess(t, txResult)
txHash := gjson.Get(txResult, "txhash").String()

testCases := []struct {
name string
req *tx.GetTxRequest
expErr bool
expErrMsg string
}{
{"nil request", nil, true, "request cannot be nil"},
{"empty request", &tx.GetTxRequest{}, true, "tx hash cannot be empty"},
{"request with dummy hash", &tx.GetTxRequest{Hash: "deadbeef"}, true, "code = NotFound desc = tx not found: deadbeef"},
{"good request", &tx.GetTxRequest{Hash: txHash}, false, ""},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
// Query the tx via gRPC.
grpcRes, err := qc.GetTx(context.Background(), tc.req)
if tc.expErr {
require.Error(t, err)
require.Contains(t, err.Error(), tc.expErrMsg)
} else {
require.NoError(t, err)
require.Equal(t, "foobar", grpcRes.Tx.Body.Memo)
}
})
}
}

func TestGetBlockWithTxs_GRPC(t *testing.T) {
sut.ResetChain(t)

cli := NewCLIWrapper(t, sut, verbose)
// get validator address
valAddr := cli.GetKeyAddr("node0")
require.NotEmpty(t, valAddr)

// add new key
receiverAddr := cli.AddKey("account1")
denom := "stake"
var transferAmount int64 = 1000

sut.StartChain(t)

qc := tx.NewServiceClient(sut.RPCClient(t))

rsp := cli.Run("tx", "bank", "send", valAddr, receiverAddr, fmt.Sprintf("%d%s", transferAmount, denom), "--fees=1stake", "--note=foobar")
txResult, found := cli.AwaitTxCommitted(rsp)
require.True(t, found)
RequireTxSuccess(t, txResult)
height := gjson.Get(txResult, "height").Int()

testCases := []struct {
name string
req *tx.GetBlockWithTxsRequest
expErr bool
expErrMsg string
expTxsLen int
}{
{"nil request", nil, true, "request cannot be nil", 0},
{"empty request", &tx.GetBlockWithTxsRequest{}, true, "height must not be less than 1 or greater than the current height", 0},
{"bad height", &tx.GetBlockWithTxsRequest{Height: 99999999}, true, "height must not be less than 1 or greater than the current height", 0},
{"bad pagination", &tx.GetBlockWithTxsRequest{Height: height, Pagination: &query.PageRequest{Offset: 1000, Limit: 100}}, true, "out of range", 0},
{"good request", &tx.GetBlockWithTxsRequest{Height: height}, false, "", 1},
{"with pagination request", &tx.GetBlockWithTxsRequest{Height: height, Pagination: &query.PageRequest{Offset: 0, Limit: 1}}, false, "", 1},
{"page all request", &tx.GetBlockWithTxsRequest{Height: height, Pagination: &query.PageRequest{Offset: 0, Limit: 100}}, false, "", 1},
{"block with 0 tx", &tx.GetBlockWithTxsRequest{Height: height - 1, Pagination: &query.PageRequest{Offset: 0, Limit: 100}}, false, "", 0},
}
for _, tc := range testCases {
t.Run(tc.name, func(t * testing.T) {
// Query the tx via gRPC.
grpcRes, err := qc.GetBlockWithTxs(context.Background(), tc.req)
if tc.expErr {
require.Error(t, err)
require.Contains(t, err.Error(), tc.expErrMsg)
} else {
require.NoError(t, err)
if tc.expTxsLen > 0 {
require.Equal(t, "foobar", grpcRes.Txs[0].Body.Memo)
}
require.Equal(t, grpcRes.Block.Header.Height, tc.req.Height)
if tc.req.Pagination != nil {
require.LessOrEqual(t, len(grpcRes.Txs), int(tc.req.Pagination.Limit))
}
}
})
}
}

func TestTxEncode_GRPC(t *testing.T) {
sut.ResetChain(t)

cli := NewCLIWrapper(t, sut, verbose)
// get validator address
valAddr := cli.GetKeyAddr("node0")
require.NotEmpty(t, valAddr)

sut.StartChain(t)

qc := tx.NewServiceClient(sut.RPCClient(t))

protoTx := &tx.Tx{
Body: &tx.TxBody{
Messages: []*codectypes.Any{},
},
AuthInfo: &tx.AuthInfo{},
Signatures: [][]byte{},
}

testCases := []struct {
name string
req *tx.TxEncodeRequest
expErr bool
expErrMsg string
}{
{"nil request", nil, true, "request cannot be nil"},
{"empty request", &tx.TxEncodeRequest{}, true, "invalid empty tx"},
{"valid tx request", &tx.TxEncodeRequest{Tx: protoTx}, false, ""},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
res, err := qc.TxEncode(context.Background(), tc.req)
if tc.expErr {
require.Error(t, err)
require.Contains(t, err.Error(), tc.expErrMsg)
require.Empty(t, res)
} else {
require.NoError(t, err)
}
})
}
}

func TestTxDecode_GRPC(t *testing.T) {
sut.ResetChain(t)

cli := NewCLIWrapper(t, sut, verbose)
// get validator address
valAddr := cli.GetKeyAddr("node0")
require.NotEmpty(t, valAddr)

// add new key
receiverAddr := cli.AddKey("account1")
denom := "stake"
var transferAmount int64 = 1000

sut.StartChain(t)

qc := tx.NewServiceClient(sut.RPCClient(t))

// create unsign tx
bankSendCmdArgs := []string{"tx", "bank", "send", valAddr, receiverAddr, fmt.Sprintf("%d%s", transferAmount, denom), "--fees=10stake", "--sign-mode=direct", "--generate-only"}
res := cli.RunCommandWithArgs(bankSendCmdArgs...)
txFile := StoreTempFile(t, []byte(res))
fmt.Println("txFile", txFile)

res = cli.RunCommandWithArgs("tx", "sign", txFile.Name(), fmt.Sprintf("--from=%s", valAddr), fmt.Sprintf("--chain-id=%s", sut.chainID), "--keyring-backend=test", "--home=./testnet/node0/simd")
signedTxFile := StoreTempFile(t, []byte(res))

Comment on lines +745 to +748
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Check for errors after signing and encoding transactions

In the TestTxDecode_GRPC function, after running cli.RunCommandWithArgs to sign and encode transactions, there are no error checks to confirm the commands executed successfully. Explicit error handling can improve test reliability.

Add error checks after signing and encoding:

res, err := cli.RunCommandWithArgs("tx", "sign", /* arguments */)
require.NoError(t, err)

res, err = cli.RunCommandWithArgs("tx", "encode", /* arguments */)
require.NoError(t, err)

res = cli.RunCommandWithArgs("tx", "encode", signedTxFile.Name())
fmt.Println("res", res)
txBz, err := base64.StdEncoding.DecodeString(res)
fmt.Println("txBz", txBz, err)
invalidTxBytes := append(txBz, byte(0o00))

testCases := []struct {
name string
req *tx.TxDecodeRequest
expErr bool
expErrMsg string
}{
{"nil request", nil, true, "request cannot be nil"},
{"empty request", &tx.TxDecodeRequest{}, true, "invalid empty tx bytes"},
{"invalid tx bytes", &tx.TxDecodeRequest{TxBytes: invalidTxBytes}, true, "tx parse error"},
{"valid request with tx bytes", &tx.TxDecodeRequest{TxBytes: txBz}, false, ""},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
res, err := qc.TxDecode(context.Background(), tc.req)
if tc.expErr {
require.Error(t, err)
require.Contains(t, err.Error(), tc.expErrMsg)
require.Empty(t, res)
} else {
require.NoError(t, err)
require.NotEmpty(t, res.GetTx())
}
})
}
}