diff --git a/CHANGELOG.md b/CHANGELOG.md index f4cd7c8d97e1..37d3a871613e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -70,6 +70,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ ### Improvements +* [#14609](https://github.com/cosmos/cosmos-sdk/pull/14609) Add RetryForBlocks method to use in tests that require waiting for a transaction to be included in a block. * [#14017](https://github.com/cosmos/cosmos-sdk/pull/14017) Simplify ADR-028 and `address.Module`. * This updates the [ADR-028](https://docs.cosmos.network/main/architecture/adr-028-public-key-addresses) and enhance the `address.Module` API to support module addresses and sub-module addresses in a backward compatible way. * [#14529](https://github.com/cosmos/cosmos-sdk/pull/14529) Add new property `BondDenom` to `SimulationState` struct. diff --git a/client/rpc/rpc_test.go b/client/rpc/rpc_test.go index 21dbd2b8cd3a..27cda1f2a4a5 100644 --- a/client/rpc/rpc_test.go +++ b/client/rpc/rpc_test.go @@ -3,6 +3,7 @@ package rpc_test import ( "context" "fmt" + "strconv" "testing" "github.com/stretchr/testify/suite" @@ -61,7 +62,9 @@ func (s *IntegrationTestSuite) TestCLIQueryConn() { s.NoError(err) blockHeight := header.Get(grpctypes.GRPCBlockHeightHeader) - s.Require().Equal([]string{"1"}, blockHeight) + height, err := strconv.Atoi(blockHeight[0]) + s.Require().NoError(err) + s.Require().GreaterOrEqual(height, 1) // at least the 1st block s.Equal("hello", res.Message) } diff --git a/tests/e2e/auth/suite.go b/tests/e2e/auth/suite.go index ac5eca9a1c5f..c3f926403c91 100644 --- a/tests/e2e/auth/suite.go +++ b/tests/e2e/auth/suite.go @@ -310,7 +310,6 @@ func (s *E2ETestSuite) TestCLISignBatch() { ) s.Require().NoError(err) s.Require().NoError(s.network.WaitForNextBlock()) - s.Require().NoError(s.network.WaitForNextBlock()) // fetch the sequence after a tx, should be incremented. _, seq1, err := val.ClientCtx.AccountRetriever.GetAccountNumberSequence(val.ClientCtx, val.Address) @@ -530,9 +529,15 @@ func (s *E2ETestSuite) TestCLIQueryTxCmdByHash() { s.Run(tc.name, func() { cmd := authcli.QueryTxCmd() clientCtx := val.ClientCtx + var ( + out testutil.BufferWriter + err error + ) - out, err := clitestutil.ExecTestCLICmd(clientCtx, cmd, tc.args) - + err = s.network.RetryForBlocks(func() error { + out, err = clitestutil.ExecTestCLICmd(clientCtx, cmd, tc.args) + return err + }, 2) if tc.expectErr { s.Require().Error(err) s.Require().NotEqual("internal", err.Error()) @@ -568,7 +573,10 @@ func (s *E2ETestSuite) TestCLIQueryTxCmdByEvents() { s.Require().NoError(s.network.WaitForNextBlock()) // Query the tx by hash to get the inner tx. - out, err = clitestutil.ExecTestCLICmd(val.ClientCtx, authcli.QueryTxCmd(), []string{txRes.TxHash, fmt.Sprintf("--%s=json", flags.FlagOutput)}) + err = s.network.RetryForBlocks(func() error { + out, err = clitestutil.ExecTestCLICmd(val.ClientCtx, authcli.QueryTxCmd(), []string{txRes.TxHash, fmt.Sprintf("--%s=json", flags.FlagOutput)}) + return err + }, 3) s.Require().NoError(err) s.Require().NoError(val.ClientCtx.Codec.UnmarshalJSON(out.Bytes(), &txRes)) protoTx := txRes.GetTx().(*tx.Tx) @@ -685,7 +693,10 @@ func (s *E2ETestSuite) TestCLIQueryTxsCmdByEvents() { s.Require().NoError(s.network.WaitForNextBlock()) // Query the tx by hash to get the inner tx. - out, err = clitestutil.ExecTestCLICmd(val.ClientCtx, authcli.QueryTxCmd(), []string{txRes.TxHash, fmt.Sprintf("--%s=json", flags.FlagOutput)}) + err = s.network.RetryForBlocks(func() error { + out, err = clitestutil.ExecTestCLICmd(val.ClientCtx, authcli.QueryTxCmd(), []string{txRes.TxHash, fmt.Sprintf("--%s=json", flags.FlagOutput)}) + return err + }, 3) s.Require().NoError(err) s.Require().NoError(val.ClientCtx.Codec.UnmarshalJSON(out.Bytes(), &txRes)) @@ -865,7 +876,10 @@ func (s *E2ETestSuite) TestCLISendGenerateSignAndBroadcast() { s.Require().NoError(s.network.WaitForNextBlock()) // Ensure destiny account state - resp, err = clitestutil.QueryBalancesExec(val1.ClientCtx, addr) + err = s.network.RetryForBlocks(func() error { + resp, err = clitestutil.QueryBalancesExec(val1.ClientCtx, addr) + return err + }, 3) s.Require().NoError(err) err = val1.ClientCtx.Codec.UnmarshalJSON(resp.Bytes(), &balRes) @@ -1154,7 +1168,9 @@ func (s *E2ETestSuite) TestCLIMultisign() { s.Require().NoError(err) var balRes banktypes.QueryAllBalancesResponse - err = val1.ClientCtx.Codec.UnmarshalJSON(resp.Bytes(), &balRes) + err = s.network.RetryForBlocks(func() error { + return val1.ClientCtx.Codec.UnmarshalJSON(resp.Bytes(), &balRes) + }, 3) s.Require().NoError(err) s.Require().True(sendTokens.Amount.Equal(balRes.Balances.AmountOf(s.cfg.BondDenom))) @@ -1683,7 +1699,6 @@ func (s *E2ETestSuite) TestSignWithMultiSignersAminoJSON() { ) require.NoError(err) require.NoError(s.network.WaitForNextBlock()) - require.NoError(s.network.WaitForNextBlock()) var txRes sdk.TxResponse require.NoError(val0.ClientCtx.Codec.UnmarshalJSON(res.Bytes(), &txRes)) diff --git a/tests/e2e/distribution/withdraw_all_suite.go b/tests/e2e/distribution/withdraw_all_suite.go index f1dc83a97a79..25a431d54c1f 100644 --- a/tests/e2e/distribution/withdraw_all_suite.go +++ b/tests/e2e/distribution/withdraw_all_suite.go @@ -10,6 +10,7 @@ import ( "github.com/cosmos/cosmos-sdk/client/flags" "github.com/cosmos/cosmos-sdk/crypto/hd" "github.com/cosmos/cosmos-sdk/crypto/keyring" + "github.com/cosmos/cosmos-sdk/testutil" clitestutil "github.com/cosmos/cosmos-sdk/testutil/cli" "github.com/cosmos/cosmos-sdk/testutil/network" sdk "github.com/cosmos/cosmos-sdk/types" @@ -94,21 +95,31 @@ func (s *WithdrawAllTestSuite) TestNewWithdrawAllRewardsGenerateOnly() { } _, err = clitestutil.ExecTestCLICmd(clientCtx, cmd, args) require.NoError(err) - require.NoError(s.network.WaitForNextBlock()) - args = []string{ - fmt.Sprintf("--%s=%s", flags.FlagFrom, newAddr.String()), - fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation), - fmt.Sprintf("--%s=true", flags.FlagGenerateOnly), - fmt.Sprintf("--%s=1", cli.FlagMaxMessagesPerTx), - fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastSync), - fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))).String()), - } - cmd = cli.NewWithdrawAllRewardsCmd() - out, err := clitestutil.ExecTestCLICmd(clientCtx, cmd, args) + var out testutil.BufferWriter + err = s.network.RetryForBlocks(func() error { + args = []string{ + fmt.Sprintf("--%s=%s", flags.FlagFrom, newAddr.String()), + fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation), + fmt.Sprintf("--%s=true", flags.FlagGenerateOnly), + fmt.Sprintf("--%s=1", cli.FlagMaxMessagesPerTx), + fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastSync), + fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))).String()), + } + cmd = cli.NewWithdrawAllRewardsCmd() + out, err := clitestutil.ExecTestCLICmd(clientCtx, cmd, args) + if err != nil { + return err + } + + // expect 2 transactions in the generated file when --max-msgs in a tx set 1. + txLen := len(strings.Split(strings.Trim(out.String(), "\n"), "\n")) + if txLen != 2 { + return fmt.Errorf("expected 2 transactions in the generated file, got %d", txLen) + } + return nil + }, 3) require.NoError(err) - // expect 2 transactions in the generated file when --max-msgs in a tx set 1. - s.Require().Equal(2, len(strings.Split(strings.Trim(out.String(), "\n"), "\n"))) args = []string{ fmt.Sprintf("--%s=%s", flags.FlagFrom, newAddr.String()), diff --git a/tests/e2e/feegrant/suite.go b/tests/e2e/feegrant/suite.go index a81758ecb2c6..f5ac365212b6 100644 --- a/tests/e2e/feegrant/suite.go +++ b/tests/e2e/feegrant/suite.go @@ -102,7 +102,6 @@ func (s *E2ETestSuite) createGrant(granter, grantee sdk.Address) { _, err := clitestutil.ExecTestCLICmd(clientCtx, cmd, args) s.Require().NoError(err) s.Require().NoError(s.network.WaitForNextBlock()) - s.Require().NoError(s.network.WaitForNextBlock()) } func (s *E2ETestSuite) TearDownSuite() { diff --git a/tests/e2e/gov/deposits.go b/tests/e2e/gov/deposits.go index 13f4fcdf3805..0e8f5e6ed0b4 100644 --- a/tests/e2e/gov/deposits.go +++ b/tests/e2e/gov/deposits.go @@ -2,6 +2,7 @@ package gov import ( "fmt" + "strconv" "time" "github.com/stretchr/testify/suite" @@ -19,10 +20,8 @@ import ( type DepositTestSuite struct { suite.Suite - cfg network.Config - network *network.Network - deposits sdk.Coins - proposalIDs []string + cfg network.Config + network *network.Network } func NewDepositTestSuite(cfg network.Config) *DepositTestSuite { @@ -35,38 +34,9 @@ func (s *DepositTestSuite) SetupSuite() { var err error s.network, err = network.New(s.T(), s.T().TempDir(), s.cfg) s.Require().NoError(err) - - s.Require().NoError(s.network.WaitForNextBlock()) - - val := s.network.Validators[0] - - deposits := sdk.Coins{ - sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(0)), - sdk.NewCoin(s.cfg.BondDenom, v1.DefaultMinDepositTokens), - sdk.NewCoin(s.cfg.BondDenom, v1.DefaultMinDepositTokens.Sub(sdk.NewInt(50))), - } - s.deposits = deposits - - // create 2 proposals for testing - for i := 0; i < len(deposits); i++ { - id := i + 1 - deposit := deposits[i] - - s.submitProposal(val, deposit, id) - s.proposalIDs = append(s.proposalIDs, fmt.Sprintf("%d", id)) - } } -func (s *DepositTestSuite) SetupNewSuite() { - s.T().Log("setting up new test suite") - - var err error - s.network, err = network.New(s.T(), s.T().TempDir(), s.cfg) - s.Require().NoError(err) - s.Require().NoError(s.network.WaitForNextBlock()) -} - -func (s *DepositTestSuite) submitProposal(val *network.Validator, initialDeposit sdk.Coin, id int) { +func (s *DepositTestSuite) submitProposal(val *network.Validator, initialDeposit sdk.Coin, name string) uint64 { var exactArgs []string if !initialDeposit.IsZero() { @@ -76,13 +46,25 @@ func (s *DepositTestSuite) submitProposal(val *network.Validator, initialDeposit _, err := govclitestutil.MsgSubmitLegacyProposal( val.ClientCtx, val.Address.String(), - fmt.Sprintf("Text Proposal %d", id), + fmt.Sprintf("Text Proposal %s", name), "Where is the title!?", v1beta1.ProposalTypeText, exactArgs..., ) s.Require().NoError(err) s.Require().NoError(s.network.WaitForNextBlock()) + + // query proposals, return the last's id + cmd := cli.GetCmdQueryProposals() + args := []string{fmt.Sprintf("--%s=json", flags.FlagOutput)} + res, err := clitestutil.ExecTestCLICmd(val.ClientCtx, cmd, args) + s.Require().NoError(err) + + var proposals v1.QueryProposalsResponse + err = s.cfg.Codec.UnmarshalJSON(res.Bytes(), &proposals) + s.Require().NoError(err) + + return proposals.Proposals[len(proposals.Proposals)-1].Id } func (s *DepositTestSuite) TearDownSuite() { @@ -93,7 +75,10 @@ func (s *DepositTestSuite) TearDownSuite() { func (s *DepositTestSuite) TestQueryDepositsWithoutInitialDeposit() { val := s.network.Validators[0] clientCtx := val.ClientCtx - proposalID := s.proposalIDs[0] + + // submit proposal without initial deposit + id := s.submitProposal(val, sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(0)), "TestQueryDepositsWithoutInitialDeposit") + proposalID := strconv.FormatUint(id, 10) // deposit amount depositAmount := sdk.NewCoin(s.cfg.BondDenom, v1.DefaultMinDepositTokens.Add(sdk.NewInt(50))).String() @@ -105,7 +90,6 @@ func (s *DepositTestSuite) TestQueryDepositsWithoutInitialDeposit() { deposit := s.queryDeposit(val, proposalID, false, "") s.Require().NotNil(deposit) s.Require().Equal(sdk.Coins(deposit.Amount).String(), depositAmount) - s.Require().NoError(s.network.WaitForNextBlock()) // query deposits deposits := s.queryDeposits(val, proposalID, false, "") @@ -117,12 +101,16 @@ func (s *DepositTestSuite) TestQueryDepositsWithoutInitialDeposit() { func (s *DepositTestSuite) TestQueryDepositsWithInitialDeposit() { val := s.network.Validators[0] - proposalID := s.proposalIDs[1] + depositAmount := sdk.NewCoin(s.cfg.BondDenom, v1.DefaultMinDepositTokens) + + // submit proposal with an initial deposit + id := s.submitProposal(val, depositAmount, "TestQueryDepositsWithInitialDeposit") + proposalID := strconv.FormatUint(id, 10) // query deposit deposit := s.queryDeposit(val, proposalID, false, "") s.Require().NotNil(deposit) - s.Require().Equal(sdk.Coins(deposit.Amount).String(), s.deposits[1].String()) + s.Require().Equal(sdk.Coins(deposit.Amount).String(), depositAmount.String()) s.Require().NoError(s.network.WaitForNextBlock()) // query deposits @@ -130,25 +118,33 @@ func (s *DepositTestSuite) TestQueryDepositsWithInitialDeposit() { s.Require().NotNil(deposits) s.Require().Len(deposits.Deposits, 1) // verify initial deposit - s.Require().Equal(sdk.Coins(deposits.Deposits[0].Amount).String(), s.deposits[1].String()) + s.Require().Equal(sdk.Coins(deposits.Deposits[0].Amount).String(), depositAmount.String()) } func (s *DepositTestSuite) TestQueryProposalAfterVotingPeriod() { val := s.network.Validators[0] - clientCtx := val.ClientCtx - proposalID := s.proposalIDs[2] + depositAmount := sdk.NewCoin(s.cfg.BondDenom, v1.DefaultMinDepositTokens.Sub(sdk.NewInt(50))) + + // submit proposal with an initial deposit + id := s.submitProposal(val, depositAmount, "TestQueryProposalAfterVotingPeriod") + proposalID := strconv.FormatUint(id, 10) + + args := []string{fmt.Sprintf("--%s=json", flags.FlagOutput)} + cmd := cli.GetCmdQueryProposals() + _, err := clitestutil.ExecTestCLICmd(val.ClientCtx, cmd, args) + s.Require().NoError(err) // query proposal - args := []string{proposalID, fmt.Sprintf("--%s=json", flags.FlagOutput)} - cmd := cli.GetCmdQueryProposal() - _, err := clitestutil.ExecTestCLICmd(clientCtx, cmd, args) + args = []string{proposalID, fmt.Sprintf("--%s=json", flags.FlagOutput)} + cmd = cli.GetCmdQueryProposal() + _, err = clitestutil.ExecTestCLICmd(val.ClientCtx, cmd, args) s.Require().NoError(err) // waiting for deposit and voting period to end - time.Sleep(20 * time.Second) + time.Sleep(25 * time.Second) // query proposal - _, err = clitestutil.ExecTestCLICmd(clientCtx, cmd, args) + _, err = clitestutil.ExecTestCLICmd(val.ClientCtx, cmd, args) s.Require().Error(err) s.Require().Contains(err.Error(), fmt.Sprintf("proposal %s doesn't exist", proposalID)) diff --git a/tests/e2e/staking/suite.go b/tests/e2e/staking/suite.go index bef1512bbbf9..6bcc5c6a7c6a 100644 --- a/tests/e2e/staking/suite.go +++ b/tests/e2e/staking/suite.go @@ -2,6 +2,7 @@ package testutil import ( "context" + "errors" "fmt" "strings" "testing" @@ -47,7 +48,6 @@ func (s *E2ETestSuite) SetupSuite() { var err error s.network, err = network.New(s.T(), s.T().TempDir(), s.cfg) s.Require().NoError(err) - s.Require().NoError(s.network.WaitForNextBlock()) unbond, err := sdk.ParseCoinNormalized("10stake") s.Require().NoError(err) @@ -1460,8 +1460,6 @@ func (s *E2ETestSuite) TestBlockResults() { require.NoError(s.network.WaitForNextBlock()) // Use CLI to create a delegation from the new account to validator `val`. - delHeight, err := s.network.LatestHeight() - require.NoError(err) cmd := cli.NewDelegateCmd() _, err = clitestutil.ExecTestCLICmd(val.ClientCtx, cmd, []string{ val.ValAddress.String(), @@ -1480,31 +1478,27 @@ func (s *E2ETestSuite) TestBlockResults() { // Loop until we find a block result with the correct validator updates. // By experience, it happens around 2 blocks after `delHeight`. - for { + s.network.RetryForBlocks(func() error { latestHeight, err := s.network.LatestHeight() require.NoError(err) - - // Wait maximum 10 blocks, or else fail test. - if latestHeight > delHeight+10 { - s.Fail("timeout reached") + res, err := rpcClient.BlockResults(context.Background(), &latestHeight) + if err != nil { + return err } - res, err := rpcClient.BlockResults(context.Background(), &latestHeight) - require.NoError(err) + if len(res.ValidatorUpdates) == 0 { + return errors.New("validator update not found yet") + } - if len(res.ValidatorUpdates) > 0 { - valUpdate := res.ValidatorUpdates[0] - require.Equal( - valUpdate.GetPubKey().Sum.(*crypto.PublicKey_Ed25519).Ed25519, - val.PubKey.Bytes(), - ) + valUpdate := res.ValidatorUpdates[0] + require.Equal( + valUpdate.GetPubKey().Sum.(*crypto.PublicKey_Ed25519).Ed25519, + val.PubKey.Bytes(), + ) - // We got our validator update, test passed. - break - } + return nil - s.network.WaitForNextBlock() - } + }, 10) } // https://github.com/cosmos/cosmos-sdk/issues/10660 diff --git a/tests/e2e/tx/service_test.go b/tests/e2e/tx/service_test.go index 38450dc40725..c30129073be6 100644 --- a/tests/e2e/tx/service_test.go +++ b/tests/e2e/tx/service_test.go @@ -101,10 +101,9 @@ func (s *E2ETestSuite) SetupSuite() { s.Require().NoError(val.ClientCtx.Codec.UnmarshalJSON(out.Bytes(), &tr)) s.Require().Equal(uint32(0), tr.Code) - s.Require().NoError(s.network.WaitForNextBlock()) - height, err := s.network.LatestHeight() + resp, err := cli.GetTxResponse(s.network, val.ClientCtx, tr.TxHash) s.Require().NoError(err) - s.txHeight = height + s.txHeight = resp.Height } func (s *E2ETestSuite) TearDownSuite() { @@ -121,7 +120,6 @@ func (s *E2ETestSuite) TestQueryBySig() { s.Require().NoError(err) s.Require().NotEmpty(resp.TxResponse.TxHash) - s.Require().NoError(s.network.WaitForNextBlock()) s.Require().NoError(s.network.WaitForNextBlock()) // get the signature out of the builder diff --git a/testutil/network/network.go b/testutil/network/network.go index 07658d23d79e..9305ca26a107 100644 --- a/testutil/network/network.go +++ b/testutil/network/network.go @@ -27,6 +27,7 @@ import ( "github.com/cosmos/cosmos-sdk/baseapp" "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/grpc/tmservice" "github.com/cosmos/cosmos-sdk/client/tx" "github.com/cosmos/cosmos-sdk/codec" codectypes "github.com/cosmos/cosmos-sdk/codec/types" @@ -602,12 +603,27 @@ func (n *Network) LatestHeight() (int64, error) { return 0, errors.New("no validators available") } - status, err := n.Validators[0].RPCClient.Status(context.Background()) - if err != nil { - return 0, err - } + ticker := time.NewTicker(time.Second) + defer ticker.Stop() + + timeout := time.NewTimer(time.Second * 5) + defer timeout.Stop() - return status.SyncInfo.LatestBlockHeight, nil + var latestHeight int64 + val := n.Validators[0] + queryClient := tmservice.NewServiceClient(val.ClientCtx) + + for { + select { + case <-timeout.C: + return latestHeight, errors.New("timeout exceeded waiting for block") + case <-ticker.C: + res, err := queryClient.GetLatestBlock(context.Background(), &tmservice.GetLatestBlockRequest{}) + if err == nil && res != nil { + return res.SdkBlock.Header.Height, nil + } + } + } } // WaitForHeight performs a blocking check where it waits for a block to be @@ -632,15 +648,17 @@ func (n *Network) WaitForHeightWithTimeout(h int64, t time.Duration) (int64, err var latestHeight int64 val := n.Validators[0] + queryClient := tmservice.NewServiceClient(val.ClientCtx) for { select { case <-timeout.C: return latestHeight, errors.New("timeout exceeded waiting for block") case <-ticker.C: - status, err := val.RPCClient.Status(context.Background()) - if err == nil && status != nil { - latestHeight = status.SyncInfo.LatestBlockHeight + + res, err := queryClient.GetLatestBlock(context.Background(), &tmservice.GetLatestBlockRequest{}) + if err == nil && res != nil { + latestHeight = res.GetSdkBlock().Header.Height if latestHeight >= h { return latestHeight, nil } @@ -649,6 +667,24 @@ func (n *Network) WaitForHeightWithTimeout(h int64, t time.Duration) (int64, err } } +// RetryForBlocks will wait for the next block and execute the function provided. +// It will do this until the function returns a nil error or until the number of +// blocks has been reached. +func (n *Network) RetryForBlocks(retryFunc func() error, blocks int) error { + for i := 0; i < blocks; i++ { + n.WaitForNextBlock() + err := retryFunc() + if err == nil { + return nil + } + // we've reached the last block to wait, return the error + if i == blocks-1 { + return err + } + } + return nil +} + // WaitForNextBlock waits for the next block to be committed, returning an error // upon failure. func (n *Network) WaitForNextBlock() error {