cosmos-sdk/tests/systemtests/tx_test.go
mergify[bot] 96c05451f4
test(test/systemtests): Add GasImprovement test (backport #22927) (#23067)
Co-authored-by: Hieu Vu <72878483+hieuvubk@users.noreply.github.com>
Co-authored-by: Julien Robert <julien@rbrt.fr>
2025-01-06 13:24:35 +01:00

1203 lines
37 KiB
Go

//go:build system_test
package systemtests
import (
"context"
"encoding/base64"
"encoding/json"
"fmt"
"os"
"strings"
"testing"
"github.com/stretchr/testify/require"
"github.com/tidwall/gjson"
systest "cosmossdk.io/systemtests"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/codec/legacy"
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
"github.com/cosmos/cosmos-sdk/std"
"github.com/cosmos/cosmos-sdk/testutil"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/query"
"github.com/cosmos/cosmos-sdk/types/tx"
"github.com/cosmos/cosmos-sdk/x/auth/migrations/legacytx"
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
)
var (
bankMsgSendEventAction = "message.action='/cosmos.bank.v1beta1.MsgSend'"
denom = "stake"
transferAmount int64 = 1000
)
func TestQueryBySig(t *testing.T) {
systest.Sut.ResetChain(t)
cli := systest.NewCLIWrapper(t, systest.Sut, systest.Verbose)
// get validator address
valAddr := cli.GetKeyAddr("node0")
require.NotEmpty(t, valAddr)
// add new key
receiverAddr := cli.AddKey("account1")
systest.Sut.StartChain(t)
qc := tx.NewServiceClient(systest.Sut.RPCClient(t))
// create unsign tx
bankSendCmdArgs := []string{"tx", "bank", "send", valAddr, receiverAddr, fmt.Sprintf("%d%s", transferAmount, denom), "--fees=10stake", "--chain-id=" + cli.ChainID(), "--sign-mode=direct", "--generate-only"}
unsignedTx := cli.RunCommandWithArgs(bankSendCmdArgs...)
txFile := systest.StoreTempFile(t, []byte(unsignedTx))
signedTx := cli.RunCommandWithArgs("tx", "sign", txFile.Name(), "--from="+valAddr, "--chain-id="+cli.ChainID(), "--keyring-backend=test", "--home="+systest.Sut.NodeDir(0))
sig := gjson.Get(signedTx, "signatures.0").String()
signedTxFile := systest.StoreTempFile(t, []byte(signedTx))
res := cli.Run("tx", "broadcast", signedTxFile.Name())
systest.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) {
systest.Sut.ResetChain(t)
cli := systest.NewCLIWrapper(t, systest.Sut, systest.Verbose)
// get validator address
valAddr := cli.GetKeyAddr("node0")
require.NotEmpty(t, valAddr)
// add new key
receiverAddr := cli.AddKey("account1")
systest.Sut.StartChain(t)
qc := tx.NewServiceClient(systest.Sut.RPCClient(t))
// create unsign tx
bankSendCmdArgs := []string{"tx", "bank", "send", valAddr, receiverAddr, fmt.Sprintf("%d%s", transferAmount, denom), "--chain-id=" + cli.ChainID(), "--fees=10stake", "--sign-mode=direct", "--generate-only"}
res := cli.RunCommandWithArgs(bankSendCmdArgs...)
txFile := systest.StoreTempFile(t, []byte(res))
res = cli.RunCommandWithArgs("tx", "sign", txFile.Name(), "--from="+valAddr, "--chain-id="+cli.ChainID(), "--keyring-backend=test", "--home="+systest.Sut.NodeDir(0))
signedTxFile := systest.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 10 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()))
require.True(t, res.GetGasInfo().GetGasUsed() > 0) // Gas used sometimes change, just check it's not empty.
}
})
}
}
func TestSimulateTx_GRPCGateway(t *testing.T) {
systest.Sut.ResetChain(t)
cli := systest.NewCLIWrapper(t, systest.Sut, systest.Verbose)
// get validator address
valAddr := cli.GetKeyAddr("node0")
require.NotEmpty(t, valAddr)
// add new key
receiverAddr := cli.AddKey("account1")
systest.Sut.StartChain(t)
// qc := tx.NewServiceClient(sut.RPCClient(t))
baseURL := systest.Sut.APIAddress()
// create unsign tx
bankSendCmdArgs := []string{"tx", "bank", "send", valAddr, receiverAddr, fmt.Sprintf("%d%s", transferAmount, denom), "--chain-id=" + cli.ChainID(), "--fees=10stake", "--sign-mode=direct", "--generate-only"}
res := cli.RunCommandWithArgs(bankSendCmdArgs...)
txFile := systest.StoreTempFile(t, []byte(res))
res = cli.RunCommandWithArgs("tx", "sign", txFile.Name(), "--from="+valAddr, "--chain-id="+cli.ChainID(), "--keyring-backend=test", "--home="+systest.Sut.NodeDir(0))
signedTxFile := systest.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
}{
{"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) {
reqBz, err := json.Marshal(tc.req)
require.NoError(t, err)
res, err := testutil.PostRequest(fmt.Sprintf("%s/cosmos/tx/v1beta1/simulate", baseURL), "application/json", reqBz)
require.NoError(t, err)
if tc.expErr {
require.Contains(t, string(res), tc.expErrMsg)
} else {
require.NoError(t, err)
msgResponses := gjson.Get(string(res), "result.msg_responses").Array()
require.Equal(t, len(msgResponses), 1)
events := gjson.Get(string(res), "result.events").Array()
require.Equal(t, len(events), 10)
gasUsed := gjson.Get(string(res), "gas_info.gas_used").Int()
require.True(t, gasUsed > 0)
}
})
}
}
func TestGetTxEvents_GRPC(t *testing.T) {
systest.Sut.ResetChain(t)
cli := systest.NewCLIWrapper(t, systest.Sut, systest.Verbose)
// get validator address
valAddr := cli.GetKeyAddr("node0")
require.NotEmpty(t, valAddr)
// add new key
receiverAddr := cli.AddKey("account1")
systest.Sut.StartChain(t)
qc := tx.NewServiceClient(systest.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)
systest.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)
systest.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.
require.NotEmpty(t, grpcRes.TxResponses[0].Timestamp)
require.Empty(t, grpcRes.TxResponses[0].RawLog) // logs are empty if the transactions are successful
}
})
}
}
func TestGetTxEvents_GRPCGateway(t *testing.T) {
systest.Sut.ResetChain(t)
cli := systest.NewCLIWrapper(t, systest.Sut, systest.Verbose)
// get validator address
valAddr := cli.GetKeyAddr("node0")
require.NotEmpty(t, valAddr)
// add new key
receiverAddr := cli.AddKey("account1")
systest.Sut.StartChain(t)
// qc := tx.NewServiceClient(sut.RPCClient(t))
baseURL := systest.Sut.APIAddress()
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)
systest.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)
systest.RequireTxSuccess(t, txResult)
testCases := []struct {
name string
url string
expErr bool
expErrMsg string
expLen int
}{
{
"empty params",
fmt.Sprintf("%s/cosmos/tx/v1beta1/txs", baseURL),
true,
"query cannot be empty", 0,
},
{
"without pagination",
fmt.Sprintf("%s/cosmos/tx/v1beta1/txs?query=%s", baseURL, bankMsgSendEventAction),
false,
"", 2,
},
{
"with pagination",
fmt.Sprintf("%s/cosmos/tx/v1beta1/txs?query=%s&page=%d&limit=%d", baseURL, bankMsgSendEventAction, 1, 1),
false,
"", 1,
},
{
"valid request: order by asc",
fmt.Sprintf("%s/cosmos/tx/v1beta1/txs?query=%s&query=%s&order_by=ORDER_BY_ASC", baseURL, bankMsgSendEventAction, "message.module='bank'"),
false,
"", 2,
},
{
"valid request: order by desc",
fmt.Sprintf("%s/cosmos/tx/v1beta1/txs?query=%s&query=%s&order_by=ORDER_BY_DESC", baseURL, bankMsgSendEventAction, "message.module='bank'"),
false,
"", 2,
},
{
"invalid request: invalid order by",
fmt.Sprintf("%s/cosmos/tx/v1beta1/txs?query=%s&query=%s&order_by=invalid_order", baseURL, bankMsgSendEventAction, "message.module='bank'"),
true,
"is not a valid tx.OrderBy", 0,
},
{
"expect pass with multiple-events",
fmt.Sprintf("%s/cosmos/tx/v1beta1/txs?query=%s&query=%s", baseURL, bankMsgSendEventAction, "message.module='bank'"),
false,
"", 2,
},
{
"expect pass with escape event",
fmt.Sprintf("%s/cosmos/tx/v1beta1/txs?query=%s", baseURL, "message.action%3D'/cosmos.bank.v1beta1.MsgSend'"),
false,
"", 2,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
res, err := testutil.GetRequest(tc.url)
require.NoError(t, err)
if tc.expErr {
require.Contains(t, string(res), tc.expErrMsg)
} else {
require.NoError(t, err)
txs := gjson.Get(string(res), "txs").Array()
require.Equal(t, len(txs), tc.expLen)
}
})
}
}
func TestGetTx_GRPC(t *testing.T) {
systest.Sut.ResetChain(t)
cli := systest.NewCLIWrapper(t, systest.Sut, systest.Verbose)
// get validator address
valAddr := cli.GetKeyAddr("node0")
require.NotEmpty(t, valAddr)
// add new key
receiverAddr := cli.AddKey("account1")
systest.Sut.StartChain(t)
qc := tx.NewServiceClient(systest.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)
systest.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 TestGetTx_GRPCGateway(t *testing.T) {
systest.Sut.ResetChain(t)
cli := systest.NewCLIWrapper(t, systest.Sut, systest.Verbose)
// get validator address
valAddr := cli.GetKeyAddr("node0")
require.NotEmpty(t, valAddr)
// add new key
receiverAddr := cli.AddKey("account1")
systest.Sut.StartChain(t)
baseURL := systest.Sut.APIAddress()
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)
systest.RequireTxSuccess(t, txResult)
txHash := gjson.Get(txResult, "txhash").String()
testCases := []struct {
name string
url string
expErr bool
expErrMsg string
}{
{
"empty params",
fmt.Sprintf("%s/cosmos/tx/v1beta1/txs/", baseURL),
true, "tx hash cannot be empty",
},
{
"dummy hash",
fmt.Sprintf("%s/cosmos/tx/v1beta1/txs/%s", baseURL, "deadbeef"),
true, "tx not found",
},
{
"good hash",
fmt.Sprintf("%s/cosmos/tx/v1beta1/txs/%s", baseURL, txHash),
false, "",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
res, err := testutil.GetRequest(tc.url)
require.NoError(t, err)
if tc.expErr {
require.Contains(t, string(res), tc.expErrMsg)
} else {
timestamp := gjson.Get(string(res), "tx_response.timestamp").String()
require.NotEmpty(t, timestamp)
height := gjson.Get(string(res), "tx_response.height").Int()
require.NotZero(t, height)
rawLog := gjson.Get(string(res), "tx_response.raw_log").String()
require.Empty(t, rawLog)
}
})
}
}
func TestGetBlockWithTxs_GRPC(t *testing.T) {
systest.Sut.ResetChain(t)
cli := systest.NewCLIWrapper(t, systest.Sut, systest.Verbose)
// get validator address
valAddr := cli.GetKeyAddr("node0")
require.NotEmpty(t, valAddr)
// add new key
receiverAddr := cli.AddKey("account1")
systest.Sut.StartChain(t)
qc := tx.NewServiceClient(systest.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)
systest.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 TestGetBlockWithTxs_GRPCGateway(t *testing.T) {
systest.Sut.ResetChain(t)
cli := systest.NewCLIWrapper(t, systest.Sut, systest.Verbose)
// get validator address
valAddr := cli.GetKeyAddr("node0")
require.NotEmpty(t, valAddr)
// add new key
receiverAddr := cli.AddKey("account1")
systest.Sut.StartChain(t)
baseUrl := systest.Sut.APIAddress()
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)
systest.RequireTxSuccess(t, txResult)
height := gjson.Get(txResult, "height").Int()
testCases := []struct {
name string
url string
expErr bool
expErrMsg string
}{
{
"empty params",
fmt.Sprintf("%s/cosmos/tx/v1beta1/txs/block/0", baseUrl),
true, "height must not be less than 1 or greater than the current height",
},
{
"bad height",
fmt.Sprintf("%s/cosmos/tx/v1beta1/txs/block/%d", baseUrl, 9999999),
true, "height must not be less than 1 or greater than the current height",
},
{
"good request",
fmt.Sprintf("%s/cosmos/tx/v1beta1/txs/block/%d", baseUrl, height),
false, "",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
res, err := testutil.GetRequest(tc.url)
require.NoError(t, err)
if tc.expErr {
require.Contains(t, string(res), tc.expErrMsg)
} else {
memo := gjson.Get(string(res), "txs.0.body.memo").String()
require.Equal(t, memo, "foobar")
respHeight := gjson.Get(string(res), "block.header.height").Int()
require.Equal(t, respHeight, height)
}
})
}
}
func TestTxEncode_GRPC(t *testing.T) {
systest.Sut.ResetChain(t)
cli := systest.NewCLIWrapper(t, systest.Sut, systest.Verbose)
// get validator address
valAddr := cli.GetKeyAddr("node0")
require.NotEmpty(t, valAddr)
systest.Sut.StartChain(t)
qc := tx.NewServiceClient(systest.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 TestTxEncode_GRPCGateway(t *testing.T) {
systest.Sut.ResetChain(t)
cli := systest.NewCLIWrapper(t, systest.Sut, systest.Verbose)
// get validator address
valAddr := cli.GetKeyAddr("node0")
require.NotEmpty(t, valAddr)
systest.Sut.StartChain(t)
baseUrl := systest.Sut.APIAddress()
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
}{
{"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) {
reqBz, err := json.Marshal(tc.req)
require.NoError(t, err)
res, err := testutil.PostRequest(fmt.Sprintf("%s/cosmos/tx/v1beta1/encode", baseUrl), "application/json", reqBz)
require.NoError(t, err)
if tc.expErr {
require.Contains(t, string(res), tc.expErrMsg)
}
})
}
}
func TestTxDecode_GRPC(t *testing.T) {
systest.Sut.ResetChain(t)
cli := systest.NewCLIWrapper(t, systest.Sut, systest.Verbose)
// get validator address
valAddr := cli.GetKeyAddr("node0")
require.NotEmpty(t, valAddr)
// add new key
receiverAddr := cli.AddKey("account1")
systest.Sut.StartChain(t)
qc := tx.NewServiceClient(systest.Sut.RPCClient(t))
// create unsign tx
bankSendCmdArgs := []string{"tx", "bank", "send", valAddr, receiverAddr, fmt.Sprintf("%d%s", transferAmount, denom), "--chain-id=" + cli.ChainID(), "--fees=10stake", "--sign-mode=direct", "--generate-only"}
res := cli.RunCommandWithArgs(bankSendCmdArgs...)
txFile := systest.StoreTempFile(t, []byte(res))
res = cli.RunCommandWithArgs("tx", "sign", txFile.Name(), "--from="+valAddr, "--chain-id="+cli.ChainID(), "--keyring-backend=test", "--home="+systest.Sut.NodeDir(0))
signedTxFile := systest.StoreTempFile(t, []byte(res))
res = cli.RunCommandWithArgs("tx", "encode", signedTxFile.Name())
txBz, err := base64.StdEncoding.DecodeString(res)
require.NoError(t, 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())
}
})
}
}
func TestTxDecode_GRPCGateway(t *testing.T) {
systest.Sut.ResetChain(t)
cli := systest.NewCLIWrapper(t, systest.Sut, systest.Verbose)
// get validator address
valAddr := cli.GetKeyAddr("node0")
require.NotEmpty(t, valAddr)
// add new key
receiverAddr := cli.AddKey("account1")
systest.Sut.StartChain(t)
basrUrl := systest.Sut.APIAddress()
// create unsign tx
bankSendCmdArgs := []string{"tx", "bank", "send", valAddr, receiverAddr, fmt.Sprintf("%d%s", transferAmount, denom), "--chain-id=" + cli.ChainID(), "--fees=10stake", "--sign-mode=direct", "--generate-only"}
res := cli.RunCommandWithArgs(bankSendCmdArgs...)
txFile := systest.StoreTempFile(t, []byte(res))
res = cli.RunCommandWithArgs("tx", "sign", txFile.Name(), "--from="+valAddr, "--chain-id="+cli.ChainID(), "--keyring-backend=test", "--home="+systest.Sut.NodeDir(0))
signedTxFile := systest.StoreTempFile(t, []byte(res))
res = cli.RunCommandWithArgs("tx", "encode", signedTxFile.Name())
txBz, err := base64.StdEncoding.DecodeString(res)
require.NoError(t, err)
invalidTxBytes := append(txBz, byte(0o00))
testCases := []struct {
name string
req *tx.TxDecodeRequest
expErr bool
expErrMsg string
}{
{"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) {
reqBz, err := json.Marshal(tc.req)
require.NoError(t, err)
res, err := testutil.PostRequest(fmt.Sprintf("%s/cosmos/tx/v1beta1/decode", basrUrl), "application/json", reqBz)
require.NoError(t, err)
if tc.expErr {
require.Contains(t, string(res), tc.expErrMsg)
} else {
signatures := gjson.Get(string(res), "tx.signatures").Array()
require.Equal(t, len(signatures), 1)
}
})
}
}
func TestTxEncodeAmino_GRPC(t *testing.T) {
systest.Sut.ResetChain(t)
systest.Sut.StartChain(t)
legacyAmino := codec.NewLegacyAmino()
std.RegisterLegacyAminoCodec(legacyAmino)
legacytx.RegisterLegacyAminoCodec(legacyAmino)
legacy.RegisterAminoMsg(legacyAmino, &banktypes.MsgSend{}, "cosmos-sdk/MsgSend")
qc := tx.NewServiceClient(systest.Sut.RPCClient(t))
txJSONBytes, stdTx := readTestAminoTxJSON(t, legacyAmino)
testCases := []struct {
name string
req *tx.TxEncodeAminoRequest
expErr bool
expErrMsg string
}{
{"nil request", nil, true, "request cannot be nil"},
{"empty request", &tx.TxEncodeAminoRequest{}, true, "invalid empty tx json"},
{"invalid request", &tx.TxEncodeAminoRequest{AminoJson: "invalid tx json"}, true, "invalid request"},
{"valid request with amino-json", &tx.TxEncodeAminoRequest{AminoJson: string(txJSONBytes)}, false, ""},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
res, err := qc.TxEncodeAmino(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.GetAminoBinary())
var decodedTx legacytx.StdTx
err = legacyAmino.Unmarshal(res.AminoBinary, &decodedTx)
require.NoError(t, err)
require.Equal(t, decodedTx.GetMsgs(), stdTx.GetMsgs())
}
})
}
}
func TestTxEncodeAmino_GRPCGateway(t *testing.T) {
systest.Sut.ResetChain(t)
systest.Sut.StartChain(t)
legacyAmino := codec.NewLegacyAmino()
std.RegisterLegacyAminoCodec(legacyAmino)
legacytx.RegisterLegacyAminoCodec(legacyAmino)
legacy.RegisterAminoMsg(legacyAmino, &banktypes.MsgSend{}, "cosmos-sdk/MsgSend")
baseUrl := systest.Sut.APIAddress()
txJSONBytes, stdTx := readTestAminoTxJSON(t, legacyAmino)
testCases := []struct {
name string
req *tx.TxEncodeAminoRequest
expErr bool
expErrMsg string
}{
{"empty request", &tx.TxEncodeAminoRequest{}, true, "invalid empty tx json"},
{"invalid request", &tx.TxEncodeAminoRequest{AminoJson: "invalid tx json"}, true, "cannot parse disfix JSON wrapper"},
{"valid request with amino-json", &tx.TxEncodeAminoRequest{AminoJson: string(txJSONBytes)}, false, ""},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
reqBz, err := json.Marshal(tc.req)
require.NoError(t, err)
res, err := testutil.PostRequest(fmt.Sprintf("%s/cosmos/tx/v1beta1/encode/amino", baseUrl), "application/json", reqBz)
require.NoError(t, err)
if tc.expErr {
require.Contains(t, string(res), tc.expErrMsg)
} else {
var result tx.TxEncodeAminoResponse
err := json.Unmarshal(res, &result)
require.NoError(t, err)
var decodedTx legacytx.StdTx
err = legacyAmino.Unmarshal(result.AminoBinary, &decodedTx)
require.NoError(t, err)
require.Equal(t, decodedTx.GetMsgs(), stdTx.GetMsgs())
}
})
}
}
func TestTxDecodeAmino_GRPC(t *testing.T) {
systest.Sut.ResetChain(t)
systest.Sut.StartChain(t)
legacyAmino := codec.NewLegacyAmino()
std.RegisterLegacyAminoCodec(legacyAmino)
legacytx.RegisterLegacyAminoCodec(legacyAmino)
legacy.RegisterAminoMsg(legacyAmino, &banktypes.MsgSend{}, "cosmos-sdk/MsgSend")
qc := tx.NewServiceClient(systest.Sut.RPCClient(t))
encodedTx, stdTx := readTestAminoTxBinary(t, legacyAmino)
invalidTxBytes := append(encodedTx, byte(0o00))
testCases := []struct {
name string
req *tx.TxDecodeAminoRequest
expErr bool
expErrMsg string
}{
{"nil request", nil, true, "request cannot be nil"},
{"empty request", &tx.TxDecodeAminoRequest{}, true, "invalid empty tx bytes"},
{"invalid tx bytes", &tx.TxDecodeAminoRequest{AminoBinary: invalidTxBytes}, true, "unmarshal to legacytx.StdTx failed"},
{"valid request with tx bytes", &tx.TxDecodeAminoRequest{AminoBinary: encodedTx}, false, ""},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
res, err := qc.TxDecodeAmino(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.GetAminoJson())
var decodedTx legacytx.StdTx
err = legacyAmino.UnmarshalJSON([]byte(res.GetAminoJson()), &decodedTx)
require.NoError(t, err)
require.Equal(t, stdTx.GetMsgs(), decodedTx.GetMsgs())
}
})
}
}
func TestTxDecodeAmino_GRPCGateway(t *testing.T) {
systest.Sut.ResetChain(t)
systest.Sut.StartChain(t)
legacyAmino := codec.NewLegacyAmino()
std.RegisterLegacyAminoCodec(legacyAmino)
legacytx.RegisterLegacyAminoCodec(legacyAmino)
legacy.RegisterAminoMsg(legacyAmino, &banktypes.MsgSend{}, "cosmos-sdk/MsgSend")
baseUrl := systest.Sut.APIAddress()
encodedTx, stdTx := readTestAminoTxBinary(t, legacyAmino)
invalidTxBytes := append(encodedTx, byte(0o00))
testCases := []struct {
name string
req *tx.TxDecodeAminoRequest
expErr bool
expErrMsg string
}{
{"empty request", &tx.TxDecodeAminoRequest{}, true, "invalid empty tx bytes"},
{"invalid tx bytes", &tx.TxDecodeAminoRequest{AminoBinary: invalidTxBytes}, true, "unmarshal to legacytx.StdTx failed"},
{"valid request with tx bytes", &tx.TxDecodeAminoRequest{AminoBinary: encodedTx}, false, ""},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
reqBz, err := json.Marshal(tc.req)
require.NoError(t, err)
res, err := testutil.PostRequest(fmt.Sprintf("%s/cosmos/tx/v1beta1/decode/amino", baseUrl), "application/json", reqBz)
require.NoError(t, err)
if tc.expErr {
require.Contains(t, string(res), tc.expErrMsg)
} else {
var result tx.TxDecodeAminoResponse
err := json.Unmarshal(res, &result)
require.NoError(t, err)
var decodedTx legacytx.StdTx
err = legacyAmino.UnmarshalJSON([]byte(result.AminoJson), &decodedTx)
require.NoError(t, err)
require.Equal(t, stdTx.GetMsgs(), decodedTx.GetMsgs())
}
})
}
}
func TestSimMultiSigTx(t *testing.T) {
t.Skip() // waiting for @hieuvubk fix
systest.Sut.ResetChain(t)
cli := systest.NewCLIWrapper(t, systest.Sut, systest.Verbose)
// get validator address
valAddr := cli.GetKeyAddr("node0")
require.NotEmpty(t, valAddr)
// add new key
_ = cli.AddKey("account1")
_ = cli.AddKey("account2")
systest.Sut.StartChain(t)
multiSigName := "multisig"
cli.RunCommandWithArgs("keys", "add", multiSigName, "--multisig=account1,account2", "--multisig-threshold=2", "--keyring-backend=test", "--home=./testnet")
multiSigAddr := cli.GetKeyAddr(multiSigName)
// Send from validator to multisig addr
rsp := cli.Run("tx", "bank", "send", valAddr, multiSigAddr, fmt.Sprintf("%d%s", transferAmount, denom), "--fees=1stake")
txResult, found := cli.AwaitTxCommitted(rsp)
require.True(t, found)
systest.RequireTxSuccess(t, txResult)
multiSigBalance := cli.QueryBalance(multiSigAddr, denom)
require.Equal(t, multiSigBalance, transferAmount)
// Send from multisig to validator
// create unsign tx
var newTransferAmount int64 = 100
bankSendCmdArgs := []string{"tx", "bank", "send", multiSigAddr, valAddr, fmt.Sprintf("%d%s", newTransferAmount, denom), "--chain-id=" + cli.ChainID(), "--fees=10stake", "--sign-mode=direct", "--generate-only"}
res := cli.RunCommandWithArgs(bankSendCmdArgs...)
txFile := systest.StoreTempFile(t, []byte(res))
res = cli.RunCommandWithArgs("tx", "sign", txFile.Name(), fmt.Sprintf("--from=%s", "account1"), fmt.Sprintf("--multisig=%s", multiSigAddr), "--chain-id="+cli.ChainID(), "--keyring-backend=test", "--home=./testnet")
account1Signed := systest.StoreTempFile(t, []byte(res))
res = cli.RunCommandWithArgs("tx", "sign", txFile.Name(), fmt.Sprintf("--from=%s", "account2"), fmt.Sprintf("--multisig=%s", multiSigAddr), "--chain-id="+cli.ChainID(), "--keyring-backend=test", "--home=./testnet")
account2Signed := systest.StoreTempFile(t, []byte(res))
res = cli.RunCommandWithArgs("tx", "multisign-batch", txFile.Name(), multiSigName, account1Signed.Name(), account2Signed.Name(), "--chain-id="+cli.ChainID(), "--keyring-backend=test", "--home=./testnet")
txSignedFile := systest.StoreTempFile(t, []byte(res))
res = cli.Run("tx", "broadcast", txSignedFile.Name())
systest.RequireTxSuccess(t, res)
multiSigBalance = cli.QueryBalance(multiSigAddr, denom)
require.Equal(t, multiSigBalance, transferAmount-newTransferAmount-10)
}
func readTestAminoTxJSON(t *testing.T, aminoCodec *codec.LegacyAmino) ([]byte, *legacytx.StdTx) {
txJSONBytes, err := os.ReadFile("testdata/tx_amino1.json")
require.NoError(t, err)
var stdTx legacytx.StdTx
err = aminoCodec.UnmarshalJSON(txJSONBytes, &stdTx)
require.NoError(t, err)
return txJSONBytes, &stdTx
}
func readTestAminoTxBinary(t *testing.T, aminoCodec *codec.LegacyAmino) ([]byte, *legacytx.StdTx) {
txJSONBytes, err := os.ReadFile("testdata/tx_amino1.bin")
require.NoError(t, err)
var stdTx legacytx.StdTx
err = aminoCodec.Unmarshal(txJSONBytes, &stdTx)
require.NoError(t, err)
return txJSONBytes, &stdTx
}
func TestSimulateTx_GasImprovements(t *testing.T) {
if !systest.IsV2() {
t.Skip("This test was made for testing gas improvements for v2. It doesn't work as is for v1, because of how the v1 handles out of gas issues compared to v2 (server error instead of proper response). This makes this test not compatible with v1.")
}
systest.Sut.ResetChain(t)
cli := systest.NewCLIWrapper(t, systest.Sut, systest.Verbose)
// get validator address
valAddr := cli.GetKeyAddr("node0")
// use node 1 as granter
granter := cli.GetKeyAddr("node1")
require.NotEmpty(t, valAddr)
// add new key
receiverAddr := cli.AddKey("account1")
systest.Sut.StartChain(t)
baseURL := systest.Sut.APIAddress()
// node1 grant to node0
grantCmdArgs := []string{"tx", "feegrant", "grant", granter, valAddr, "--chain-id=" + cli.ChainID()}
rsp := cli.Run(grantCmdArgs...)
txResult, found := cli.AwaitTxCommitted(rsp)
require.True(t, found)
systest.RequireTxSuccess(t, txResult)
sendSimulateCmdArgs := []string{"tx", "bank", "send", valAddr, receiverAddr, fmt.Sprintf("%d%s", transferAmount, denom), "--chain-id=" + cli.ChainID(), "--sign-mode=direct", "--generate-only"}
bankSendCmdArgs := []string{"tx", "bank", "send", valAddr, receiverAddr, fmt.Sprintf("%d%s", transferAmount, denom), "--chain-id=" + cli.ChainID()}
testCases := []struct {
name string
simulateArgs []string
txArgs []string
}{
{
"simulate without fees",
sendSimulateCmdArgs,
bankSendCmdArgs,
},
{
"simulate with fees",
append(sendSimulateCmdArgs, "--fees=1stake"),
bankSendCmdArgs,
},
{
"simulate without fee-granter",
sendSimulateCmdArgs,
append(bankSendCmdArgs, fmt.Sprintf("--fee-granter=%s", granter)),
},
{
"simulate with fee-granter",
append(sendSimulateCmdArgs, fmt.Sprintf("--fee-granter=%s", granter)),
append(bankSendCmdArgs, fmt.Sprintf("--fee-granter=%s", granter)),
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
txlen := 1
gasSimulated := make([]int64, txlen)
gasAdjustment := make([]float64, txlen)
for i := 0; i < txlen; i++ {
// create unsign tx
res := cli.RunCommandWithArgs(tc.simulateArgs...)
txFile := systest.StoreTempFile(t, []byte(res))
res = cli.RunCommandWithArgs("tx", "sign", txFile.Name(), "--from="+valAddr, "--chain-id="+cli.ChainID(), "--keyring-backend=test", "--home="+systest.Sut.NodeDir(0))
signedTxFile := systest.StoreTempFile(t, []byte(res))
res = cli.RunCommandWithArgs("tx", "encode", signedTxFile.Name())
txBz, err := base64.StdEncoding.DecodeString(res)
require.NoError(t, err)
reqBz, err := json.Marshal(&tx.SimulateRequest{TxBytes: txBz})
require.NoError(t, err)
resBz, err := testutil.PostRequest(fmt.Sprintf("%s/cosmos/tx/v1beta1/simulate", baseURL), "application/json", reqBz)
require.NoError(t, err)
gasUsed := gjson.Get(string(resBz), "gas_info.gas_used").Int()
require.True(t, gasUsed > 0)
gasSimulated[i] = gasUsed
}
for i := 0; i < txlen; i++ {
// Submit tx with gas return from simulate
gasAdjustment[i] = 0.99
// test valid transaction
shouldRerun := true
for shouldRerun {
gasAdjustment[i] = gasAdjustment[i] + 0.01
gasUse := int64(gasAdjustment[i] * float64(gasSimulated[i]))
rsp := cli.Run(append(tc.txArgs, fmt.Sprintf("--gas=%d", gasUse))...)
// rerun if tx err and increase gas-adjustment by 1%
shouldRerun = strings.Contains(gjson.Get(rsp, "raw_log").String(), "out of gas") || gjson.Get(rsp, "gas_used").Int() == int64(0) || gjson.Get(rsp, "code").Int() != 0
fmt.Println("gasAdjustment", i, gasAdjustment[i])
}
}
// Calculate average adjustments
total := 0.0
for i := 0; i < txlen; i++ {
total = total + gasAdjustment[i]
}
average := total / float64(txlen)
result := fmt.Sprintf("Test case %s average gas adjustment is: %f", tc.name, average)
fmt.Println(result)
})
}
}