diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d8fdac92ef0..09bef4b4a38e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -62,6 +62,7 @@ Every module contains its own CHANGELOG.md. Please refer to the module you are i ### API Breaking Changes +* (testutil) [#22392](https://github.com/cosmos/cosmos-sdk/pull/22392) Remove `testutil/network` package. Use the integration framework or systemtests framework instead. * (client) [#22775](https://github.com/cosmos/cosmos-sdk/pull/22775) Removed client prompt validations. ### Deprecated diff --git a/client/grpc/cmtservice/status_test.go b/client/grpc/cmtservice/status_test.go deleted file mode 100644 index d7bfa6bdc192..000000000000 --- a/client/grpc/cmtservice/status_test.go +++ /dev/null @@ -1,34 +0,0 @@ -package cmtservice_test - -import ( - "fmt" - "testing" - - "github.com/stretchr/testify/require" - - "cosmossdk.io/depinject" - - "github.com/cosmos/cosmos-sdk/server" - clitestutil "github.com/cosmos/cosmos-sdk/testutil/cli" - "github.com/cosmos/cosmos-sdk/testutil/network" -) - -func TestStatusCommand(t *testing.T) { - t.Skip() // Rewrite as system test - - cfg, err := network.DefaultConfigWithAppConfig(depinject.Configs() /* TODO, test skipped anyway */) - require.NoError(t, err) - - network, err := network.New(t, t.TempDir(), cfg) - require.NoError(t, err) - require.NoError(t, network.WaitForNextBlock()) - - val0 := network.GetValidators()[0] - cmd := server.StatusCommand() - - out, err := clitestutil.ExecTestCLICmd(val0.GetClientCtx(), cmd, []string{}) - require.NoError(t, err) - - // Make sure the output has the validator moniker. - require.Contains(t, out.String(), fmt.Sprintf("\"moniker\":\"%s\"", val0.GetMoniker())) -} diff --git a/server/grpc/server.go b/server/grpc/server.go index f04b7cd20e5b..ae4c376191d2 100644 --- a/server/grpc/server.go +++ b/server/grpc/server.go @@ -5,6 +5,7 @@ import ( "fmt" "net" + gogogrpcserver "github.com/cosmos/gogoproto/grpc" "google.golang.org/grpc" "cosmossdk.io/log" @@ -14,13 +15,12 @@ import ( "github.com/cosmos/cosmos-sdk/server/config" "github.com/cosmos/cosmos-sdk/server/grpc/gogoreflection" reflection "github.com/cosmos/cosmos-sdk/server/grpc/reflection/v2alpha1" - "github.com/cosmos/cosmos-sdk/server/types" _ "github.com/cosmos/cosmos-sdk/types/tx/amino" // Import amino.proto file for reflection ) // NewGRPCServer returns a correctly configured and initialized gRPC server. // Note, the caller is responsible for starting the server. See StartGRPCServer. -func NewGRPCServer(clientCtx client.Context, app types.Application, cfg config.GRPCConfig) (*grpc.Server, error) { +func NewGRPCServer(clientCtx client.Context, app interface{ RegisterGRPCServer(gogogrpcserver.Server) }, cfg config.GRPCConfig) (*grpc.Server, error) { maxSendMsgSize := cfg.MaxSendMsgSize if maxSendMsgSize == 0 { maxSendMsgSize = config.DefaultGRPCMaxSendMsgSize diff --git a/simapp/simd/cmd/testnet.go b/simapp/simd/cmd/testnet.go index e060976d1376..3a432efd4966 100644 --- a/simapp/simd/cmd/testnet.go +++ b/simapp/simd/cmd/testnet.go @@ -4,9 +4,15 @@ import ( "bufio" "encoding/json" "fmt" + "io" "net" + "net/url" "os" + "os/exec" + "os/signal" "path/filepath" + "strconv" + "strings" "time" cmtconfig "github.com/cometbft/cometbft/config" @@ -16,7 +22,6 @@ import ( "cosmossdk.io/math" "cosmossdk.io/math/unsafe" - "cosmossdk.io/simapp" banktypes "cosmossdk.io/x/bank/types" stakingtypes "cosmossdk.io/x/staking/types" @@ -29,7 +34,6 @@ import ( "github.com/cosmos/cosmos-sdk/server" srvconfig "github.com/cosmos/cosmos-sdk/server/config" "github.com/cosmos/cosmos-sdk/testutil" - "github.com/cosmos/cosmos-sdk/testutil/network" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/module" "github.com/cosmos/cosmos-sdk/version" @@ -53,6 +57,15 @@ var ( flagStakingDenom = "staking-denom" flagCommitTimeout = "commit-timeout" flagSingleHost = "single-host" + + // default values + defaultRPCPort = 26657 + defaultAPIPort = 1317 + defaultGRPCPort = 9090 + defaultListenIPAddress = "127.0.0.1" + defaultStartingIPAddress = "192.168.0.1" + defaultNodeDirPrefix = "node" + defaultNodeDaemonHome = "simd" ) type initArgs struct { @@ -68,20 +81,15 @@ type initArgs struct { listenIPAddress string singleMachine bool bondTokenDenom string -} -type startArgs struct { - algo string - apiAddress string - chainID string - enableLogging bool - grpcAddress string - minGasPrices string - numValidators int - outputDir string - printMnemonic bool - rpcAddress string - timeoutCommit time.Duration + // start command arguments + apiListenAddress string + grpcListenAddress string + rpcPort int + apiPort int + grpcPort int + enableLogging bool + printMnemonic bool } func addTestnetFlagsToCmd(cmd *cobra.Command) { @@ -112,7 +120,7 @@ func NewTestnetCmd(mm *module.Manager) *cobra.Command { RunE: client.ValidateCmd, } - testnetCmd.AddCommand(testnetStartCmd()) + testnetCmd.AddCommand(testnetStartCmd(mm)) testnetCmd.AddCommand(testnetInitFilesCmd(mm)) return testnetCmd @@ -142,7 +150,13 @@ Example: config := client.GetConfigFromCmd(cmd) - args := initArgs{} + args := initArgs{ + rpcPort: defaultRPCPort, + apiPort: defaultAPIPort, + grpcPort: defaultGRPCPort, + apiListenAddress: defaultListenIPAddress, + grpcListenAddress: defaultListenIPAddress, + } args.outputDir, _ = cmd.Flags().GetString(flagOutputDir) args.keyringBackend, _ = cmd.Flags().GetString(flags.FlagKeyringBackend) args.chainID, _ = cmd.Flags().GetString(flags.FlagChainID) @@ -160,15 +174,19 @@ Example: return err } + if args.chainID == "" { + args.chainID = "chain-" + unsafe.Str(6) + } + return initTestnetFiles(clientCtx, cmd, config, mm, args) }, } addTestnetFlagsToCmd(cmd) - cmd.Flags().String(flagNodeDirPrefix, "node", "Prefix for the name of per-validator subdirectories (to be number-suffixed like node0, node1, ...)") - cmd.Flags().String(flagNodeDaemonHome, "simd", "Home directory of the node's daemon configuration") - cmd.Flags().String(flagStartingIPAddress, "192.168.0.1", "Starting IP address (192.168.0.1 results in persistent peers list ID0@192.168.0.1:46656, ID1@192.168.0.2:46656, ...)") - cmd.Flags().String(flagListenIPAddress, "127.0.0.1", "TCP or UNIX socket IP address for the RPC server to listen on") + cmd.Flags().String(flagNodeDirPrefix, defaultNodeDirPrefix, "Prefix for the name of per-validator subdirectories (to be number-suffixed like node0, node1, ...)") + cmd.Flags().String(flagNodeDaemonHome, defaultNodeDaemonHome, "Home directory of the node's daemon configuration") + cmd.Flags().String(flagStartingIPAddress, defaultStartingIPAddress, "Starting IP address (192.168.0.1 results in persistent peers list ID0@192.168.0.1:46656, ID1@192.168.0.2:46656, ...)") + cmd.Flags().String(flagListenIPAddress, defaultListenIPAddress, "TCP or UNIX socket IP address for the RPC server to listen on") cmd.Flags().String(flags.FlagKeyringBackend, flags.DefaultKeyringBackend, "Select keyring's backend (os|file|test)") cmd.Flags().Duration(flagCommitTimeout, 5*time.Second, "Time to wait after a block commit before starting on the new height") cmd.Flags().Bool(flagSingleHost, false, "Cluster runs on a single host machine with different ports") @@ -178,7 +196,7 @@ Example: } // testnetStartCmd returns a cmd to start multi validator in-process testnet -func testnetStartCmd() *cobra.Command { +func testnetStartCmd(mm *module.Manager) *cobra.Command { cmd := &cobra.Command{ Use: "start", Short: "Launch an in-process multi-validator testnet", @@ -190,19 +208,56 @@ Example: %s testnet --validator-count4 --output-dir ./.testnets `, version.AppName), RunE: func(cmd *cobra.Command, _ []string) (err error) { - args := startArgs{} + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + + config := client.GetConfigFromCmd(cmd) + + args := initArgs{ + singleMachine: true, + bondTokenDenom: sdk.DefaultBondDenom, + nodeDaemonHome: defaultNodeDaemonHome, + nodeDirPrefix: defaultNodeDirPrefix, + keyringBackend: keyring.BackendTest, + } args.outputDir, _ = cmd.Flags().GetString(flagOutputDir) args.chainID, _ = cmd.Flags().GetString(flags.FlagChainID) args.minGasPrices, _ = cmd.Flags().GetString(server.FlagMinGasPrices) args.numValidators, _ = cmd.Flags().GetInt(flagNumValidators) args.algo, _ = cmd.Flags().GetString(flags.FlagKeyType) args.enableLogging, _ = cmd.Flags().GetBool(flagEnableLogging) - args.rpcAddress, _ = cmd.Flags().GetString(flagRPCAddress) - args.apiAddress, _ = cmd.Flags().GetString(flagAPIAddress) - args.grpcAddress, _ = cmd.Flags().GetString(flagGRPCAddress) + + rpcAddress, _ := cmd.Flags().GetString(flagRPCAddress) + args.listenIPAddress, args.rpcPort, err = parseURL(rpcAddress) + if err != nil { + return fmt.Errorf("invalid rpc address: %w", err) + } + + apiAddress, _ := cmd.Flags().GetString(flagAPIAddress) + args.apiListenAddress, args.apiPort, err = parseURL(apiAddress) + if err != nil { + return fmt.Errorf("invalid api address: %w", err) + } + + grpcAddress, _ := cmd.Flags().GetString(flagGRPCAddress) + // add scheme to avoid issues with parsing + if !strings.Contains(grpcAddress, "://") { + grpcAddress = "tcp://" + grpcAddress + } + args.grpcListenAddress, args.grpcPort, err = parseURL(grpcAddress) + if err != nil { + return fmt.Errorf("invalid grpc address: %w", err) + } + args.printMnemonic, _ = cmd.Flags().GetBool(flagPrintMnemonic) - return startTestnet(cmd, args) + if args.chainID == "" { + args.chainID = "chain-" + unsafe.Str(6) + } + + return startTestnet(clientCtx, cmd, config, mm, args) }, } @@ -215,6 +270,18 @@ Example: return cmd } +func parseURL(str string) (host string, port int, err error) { + u, err := url.Parse(str) + if err != nil { + return + } + + host = u.Hostname() + + port, err = strconv.Atoi(u.Port()) + return +} + const nodeDirPerm = 0o755 // initTestnetFiles initializes testnet files for a testnet to be run in a separate process @@ -225,15 +292,13 @@ func initTestnetFiles( mm *module.Manager, args initArgs, ) error { - if args.chainID == "" { - args.chainID = "chain-" + unsafe.Str(6) - } nodeIDs := make([]string, args.numValidators) valPubKeys := make([]cryptotypes.PubKey, args.numValidators) appConfig := srvconfig.DefaultConfig() appConfig.MinGasPrices = args.minGasPrices appConfig.API.Enable = true + appConfig.GRPC.Enable = true appConfig.Telemetry.Enabled = true appConfig.Telemetry.PrometheusRetentionTime = 60 appConfig.Telemetry.EnableHostnameLabel = false @@ -244,10 +309,10 @@ func initTestnetFiles( genBalances []banktypes.Balance genFiles []string ) - const ( - rpcPort = 26657 - apiPort = 1317 - grpcPort = 9090 + var ( + rpcPort = args.rpcPort + apiPort = args.apiPort + grpcPort = args.grpcPort ) p2pPortStart := 26656 @@ -261,12 +326,11 @@ func initTestnetFiles( nodeConfig.P2P.AddrBookStrict = false nodeConfig.P2P.PexReactor = false nodeConfig.P2P.AllowDuplicateIP = true - appConfig.API.Address = fmt.Sprintf("tcp://127.0.0.1:%d", apiPort+portOffset) - appConfig.GRPC.Address = fmt.Sprintf("127.0.0.1:%d", grpcPort+portOffset) + appConfig.API.Address = fmt.Sprintf("tcp://%s:%d", args.apiListenAddress, apiPort+portOffset) + appConfig.GRPC.Address = fmt.Sprintf("%s:%d", args.grpcListenAddress, grpcPort+portOffset) } - nodeDirName := fmt.Sprintf("%s%d", args.nodeDirPrefix, i) - nodeDir := filepath.Join(args.outputDir, nodeDirName, args.nodeDaemonHome) + nodeDirName, nodeDir := getNodeDir(args, i) gentxsDir := filepath.Join(args.outputDir, "gentxs") nodeConfig.SetRoot(nodeDir) @@ -317,6 +381,12 @@ func initTestnetFiles( return err } + // if PrintMnemonic is set to true, we print the first validator node's secret to the network's logger + // for debugging and manual testing + if args.printMnemonic && i == 0 { + printMnemonic(secret) + } + info := map[string]string{"secret": secret} cliPrint, err := json.Marshal(info) @@ -552,46 +622,132 @@ func writeFile(name, dir string, contents []byte) error { return os.WriteFile(file, contents, 0o600) } -// startTestnet starts an in-process testnet -func startTestnet(cmd *cobra.Command, args startArgs) error { - networkConfig := network.DefaultConfig(simapp.NewTestNetworkFixture) +// printMnemonic prints a provided mnemonic seed phrase on a network logger +// for debugging and manual testing +func printMnemonic(secret string) { + lines := []string{ + "THIS MNEMONIC IS FOR TESTING PURPOSES ONLY", + "DO NOT USE IN PRODUCTION", + "", + strings.Join(strings.Fields(secret)[0:8], " "), + strings.Join(strings.Fields(secret)[8:16], " "), + strings.Join(strings.Fields(secret)[16:24], " "), + } - // Default networkConfig.ChainID is random, and we should only override it if chainID provided - // is non-empty - if args.chainID != "" { - networkConfig.ChainID = args.chainID + lineLengths := make([]int, len(lines)) + for i, line := range lines { + lineLengths[i] = len(line) } - networkConfig.SigningAlgo = args.algo - networkConfig.MinGasPrices = args.minGasPrices - networkConfig.NumValidators = args.numValidators - networkConfig.EnableLogging = args.enableLogging - networkConfig.RPCAddress = args.rpcAddress - networkConfig.APIAddress = args.apiAddress - networkConfig.GRPCAddress = args.grpcAddress - networkConfig.PrintMnemonic = args.printMnemonic - networkConfig.TimeoutCommit = args.timeoutCommit - networkLogger := network.NewCLILogger(cmd) - - baseDir := fmt.Sprintf("%s/%s", args.outputDir, networkConfig.ChainID) - if _, err := os.Stat(baseDir); !os.IsNotExist(err) { - return fmt.Errorf( - "testnests directory already exists for chain-id '%s': %s, please remove or select a new --chain-id", - networkConfig.ChainID, baseDir) + + maxLineLength := 0 + for _, lineLen := range lineLengths { + if lineLen > maxLineLength { + maxLineLength = lineLen + } } - testnet, err := network.New(networkLogger, baseDir, networkConfig) + fmt.Printf("\n\n") + fmt.Println(strings.Repeat("+", maxLineLength+8)) + for _, line := range lines { + fmt.Printf("++ %s ++\n", centerText(line, maxLineLength)) + } + fmt.Println(strings.Repeat("+", maxLineLength+8)) + fmt.Printf("\n\n") +} + +// centerText centers text across a fixed width, filling either side with whitespace buffers +func centerText(text string, width int) string { + textLen := len(text) + leftBuffer := strings.Repeat(" ", (width-textLen)/2) + rightBuffer := strings.Repeat(" ", (width-textLen)/2+(width-textLen)%2) + + return fmt.Sprintf("%s%s%s", leftBuffer, text, rightBuffer) +} + +func getNodeDir(args initArgs, nodeID int) (nodeDirName, nodeDir string) { + nodeDirName = fmt.Sprintf("%s%d", args.nodeDirPrefix, nodeID) + nodeDir = filepath.Join(args.outputDir, nodeDirName, args.nodeDaemonHome) + return +} + +// startTestnet starts an in-process testnet +func startTestnet( + clientCtx client.Context, + cmd *cobra.Command, + nodeConfig *cmtconfig.Config, + mm *module.Manager, + args initArgs, +) error { + fmt.Printf(`Preparing test network with chain-id "%s"`, args.chainID) + + args.outputDir = fmt.Sprintf("%s/%s", args.outputDir, args.chainID) + err := initTestnetFiles(clientCtx, cmd, nodeConfig, mm, args) if err != nil { return err } - if _, err := testnet.WaitForHeight(1); err != nil { - return err + // slice to keep track of validator processes + var processes []*exec.Cmd + + // channel to signal shutdown + shutdownCh := make(chan struct{}) + + fmt.Println("Starting test network...") + // Start each validator in a separate process + for i := 0; i < args.numValidators; i++ { + _, nodeDir := getNodeDir(args, i) + + // run start command + cmdArgs := []string{"start", fmt.Sprintf("--%s=%s", flags.FlagHome, nodeDir)} + runCmd := exec.Command(os.Args[0], cmdArgs...) // spawn new process + + // Set stdout and stderr based on enableLogging flag + if args.enableLogging { + runCmd.Stdout = os.Stdout + runCmd.Stderr = os.Stderr + } else { + runCmd.Stdout = io.Discard // discard output when logging is disabled + runCmd.Stderr = io.Discard + } + + if err := runCmd.Start(); err != nil { + return fmt.Errorf("failed to start validator %d: %w", i, err) + } + fmt.Printf("Started Validator %d\n", i+1) + processes = append(processes, runCmd) // add to processes slice } - cmd.Println("press the Enter Key to terminate") - if _, err := fmt.Scanln(); err != nil { // wait for Enter Key - return err + + // goroutine to listen for Enter key press + go func() { + fmt.Println("Press the Enter Key to terminate all validator processes") + if _, err := fmt.Scanln(); err == nil { + close(shutdownCh) // Signal shutdown + } + }() + + // goroutine to listen for Ctrl+C (SIGINT) + sigCh := make(chan os.Signal, 1) + signal.Notify(sigCh, os.Interrupt) + go func() { + <-sigCh // Wait for Ctrl+C + fmt.Println("\nCtrl+C detected, terminating validator processes...") + close(shutdownCh) // Signal shutdown + }() + + // block until shutdown signal is received + <-shutdownCh + + // terminate all validator processes + fmt.Println("Shutting down validator processes...") + for i, p := range processes { + if err := p.Process.Kill(); err != nil { + fmt.Printf("Failed to terminate validator %d process: %v\n", i+1, err) + } else { + fmt.Printf("Validator %d terminated\n", i+1) + } } - testnet.Cleanup() + _ = os.RemoveAll(args.outputDir) // Clean up the output directory + fmt.Println("Finished cleaning up test network") return nil } diff --git a/simapp/test_helpers.go b/simapp/test_helpers.go index 7d3c5131466b..aceebaf59b7b 100644 --- a/simapp/test_helpers.go +++ b/simapp/test_helpers.go @@ -2,8 +2,6 @@ package simapp import ( "encoding/json" - "fmt" - "os" "testing" abci "github.com/cometbft/cometbft/api/cometbft/abci/v1" @@ -15,21 +13,16 @@ import ( coretesting "cosmossdk.io/core/testing" "cosmossdk.io/log" sdkmath "cosmossdk.io/math" - pruningtypes "cosmossdk.io/store/pruning/types" banktypes "cosmossdk.io/x/bank/types" minttypes "cosmossdk.io/x/mint/types" - bam "github.com/cosmos/cosmos-sdk/baseapp" - "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/flags" "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" "github.com/cosmos/cosmos-sdk/server" servertypes "github.com/cosmos/cosmos-sdk/server/types" "github.com/cosmos/cosmos-sdk/testutil/mock" - "github.com/cosmos/cosmos-sdk/testutil/network" simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/types/module/testutil" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" ) @@ -226,35 +219,3 @@ func initAccountWithCoins(app *SimApp, ctx sdk.Context, addr sdk.AccAddress, coi panic(err) } } - -// NewTestNetworkFixture returns a new simapp AppConstructor for network simulation tests -func NewTestNetworkFixture() network.TestFixture { - dir, err := os.MkdirTemp("", "simapp") - if err != nil { - panic(fmt.Sprintf("failed creating temporary directory: %v", err)) - } - defer os.RemoveAll(dir) - - app := NewSimApp(log.NewNopLogger(), coretesting.NewMemDB(), nil, true, simtestutil.NewAppOptionsWithFlagHome(dir)) - - appCtr := func(val network.ValidatorI) servertypes.Application { - return NewSimApp( - val.GetLogger(), coretesting.NewMemDB(), nil, true, - simtestutil.NewAppOptionsWithFlagHome(client.GetConfigFromViper(val.GetViper()).RootDir), - bam.SetPruning(pruningtypes.NewPruningOptionsFromString(val.GetAppConfig().Pruning)), - bam.SetMinGasPrices(val.GetAppConfig().MinGasPrices), - bam.SetChainID(val.GetViper().GetString(flags.FlagChainID)), - ) - } - - return network.TestFixture{ - AppConstructor: appCtr, - GenesisState: app.DefaultGenesis(), - EncodingConfig: testutil.TestEncodingConfig{ - InterfaceRegistry: app.InterfaceRegistry(), - Codec: app.AppCodec(), - TxConfig: app.TxConfig(), - Amino: app.LegacyAmino(), - }, - } -} diff --git a/simapp/testutil_network_test.go b/simapp/testutil_network_test.go deleted file mode 100644 index 6d551b1dfd47..000000000000 --- a/simapp/testutil_network_test.go +++ /dev/null @@ -1,43 +0,0 @@ -package simapp_test - -import ( - "testing" - "time" - - "github.com/stretchr/testify/suite" - - "cosmossdk.io/simapp" - - "github.com/cosmos/cosmos-sdk/testutil/network" -) - -type IntegrationTestSuite struct { - suite.Suite - - network network.NetworkI -} - -func (s *IntegrationTestSuite) SetupSuite() { - s.T().Log("setting up integration test suite") - - var err error - s.network, err = network.New(s.T(), s.T().TempDir(), network.DefaultConfig(simapp.NewTestNetworkFixture)) - s.Require().NoError(err) - - h, err := s.network.WaitForHeight(1) - s.Require().NoError(err, "stalled at height %d", h) -} - -func (s *IntegrationTestSuite) TearDownSuite() { - s.T().Log("tearing down integration test suite") - s.network.Cleanup() -} - -func (s *IntegrationTestSuite) TestNetwork_Liveness() { - h, err := s.network.WaitForHeightWithTimeout(10, time.Minute) - s.Require().NoError(err, "expected to reach 10 blocks; got %d", h) -} - -func TestIntegrationTestSuite(t *testing.T) { - suite.Run(t, new(IntegrationTestSuite)) -} diff --git a/tests/integration/auth/keeper/account_retriever_test.go b/tests/integration/auth/keeper/account_retriever_test.go index 68481cdebc5c..823a2ade21a1 100644 --- a/tests/integration/auth/keeper/account_retriever_test.go +++ b/tests/integration/auth/keeper/account_retriever_test.go @@ -1,45 +1,86 @@ package keeper_test import ( + "context" "testing" "github.com/stretchr/testify/require" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" - authTest "github.com/cosmos/cosmos-sdk/tests/integration/auth/keeper" - "github.com/cosmos/cosmos-sdk/testutil/network" + "cosmossdk.io/math" + minttypes "cosmossdk.io/x/mint/types" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/codec" + srvconfig "github.com/cosmos/cosmos-sdk/server/config" + servergrpc "github.com/cosmos/cosmos-sdk/server/grpc" + simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth/keeper" "github.com/cosmos/cosmos-sdk/x/auth/types" ) func TestAccountRetriever(t *testing.T) { - cfg, err := network.DefaultConfigWithAppConfig(authTest.AppConfig) - require.NoError(t, err) - cfg.NumValidators = 1 + t.Skip() // TODO: https://github.com/cosmos/cosmos-sdk/issues/22825 - network, err := network.New(t, t.TempDir(), cfg) - require.NoError(t, err) - defer network.Cleanup() + f := initFixture(t, nil) + + grpcSrv := grpc.NewServer(grpc.ForceServerCodec(codec.NewProtoCodec(f.encodingCfg.InterfaceRegistry).GRPCCodec())) + + types.RegisterQueryServer(f.app.GRPCQueryRouter(), keeper.NewQueryServer(f.authKeeper)) + f.app.RegisterGRPCServer(grpcSrv) + + grpcCfg := srvconfig.DefaultConfig().GRPC - _, err = network.WaitForHeight(3) + go func() { + require.NoError(t, servergrpc.StartGRPCServer(context.Background(), f.app.Logger(), grpcCfg, grpcSrv)) + }() + + conn, err := grpc.NewClient( + grpcCfg.Address, + grpc.WithTransportCredentials(insecure.NewCredentials()), + grpc.WithDefaultCallOptions(grpc.ForceCodec(codec.NewProtoCodec(f.encodingCfg.InterfaceRegistry).GRPCCodec())), + ) require.NoError(t, err) - val := network.GetValidators()[0] - clientCtx := val.GetClientCtx() + defer conn.Close() + + pubkeys := simtestutil.CreateTestPubKeys(1) + addr := sdk.AccAddress(pubkeys[0].Address()) + + newAcc := types.BaseAccount{ + Address: addr.String(), + PubKey: nil, + AccountNumber: 2, + Sequence: 7, + } + + updatedAcc := f.authKeeper.NewAccount(f.ctx, &newAcc) + f.authKeeper.SetAccount(f.ctx, updatedAcc) + + amount := sdk.NewCoins(sdk.NewCoin("stake", math.NewInt(10000))) + require.NoError(t, f.bankKeeper.MintCoins(f.ctx, minttypes.ModuleName, amount)) + require.NoError(t, f.bankKeeper.SendCoinsFromModuleToAccount(f.ctx, minttypes.ModuleName, addr, amount)) + ar := types.AccountRetriever{} - clientCtx = clientCtx.WithHeight(2) + clientCtx := client.Context{}. + WithGRPCClient(conn). + WithAddressPrefix(sdk.Bech32MainPrefix) - acc, err := ar.GetAccount(clientCtx, val.GetAddress()) + acc, err := ar.GetAccount(clientCtx, addr) require.NoError(t, err) require.NotNil(t, acc) - acc, height, err := ar.GetAccountWithHeight(clientCtx, val.GetAddress()) + acc, height, err := ar.GetAccountWithHeight(clientCtx, addr) require.NoError(t, err) require.NotNil(t, acc) require.Equal(t, height, int64(2)) - require.NoError(t, ar.EnsureExists(clientCtx, val.GetAddress())) + require.NoError(t, ar.EnsureExists(clientCtx, addr)) - accNum, accSeq, err := ar.GetAccountNumberSequence(clientCtx, val.GetAddress()) + accNum, accSeq, err := ar.GetAccountNumberSequence(clientCtx, addr) require.NoError(t, err) require.Equal(t, accNum, uint64(0)) require.Equal(t, accSeq, uint64(1)) diff --git a/tests/integration/auth/keeper/fixture_test.go b/tests/integration/auth/keeper/fixture_test.go index d32d1d0787bf..d549bae86cd8 100644 --- a/tests/integration/auth/keeper/fixture_test.go +++ b/tests/integration/auth/keeper/fixture_test.go @@ -3,6 +3,8 @@ package keeper_test import ( "testing" + cmtabcitypes "github.com/cometbft/cometbft/api/cometbft/abci/v1" + "github.com/stretchr/testify/require" "gotest.tools/v3/assert" "cosmossdk.io/core/appmodule" @@ -35,7 +37,9 @@ import ( type fixture struct { app *integration.App - cdc codec.Codec + cdc codec.Codec + ctx sdk.Context + encodingCfg moduletestutil.TestEncodingConfig authKeeper authkeeper.AccountKeeper accountsKeeper accounts.Keeper @@ -126,11 +130,24 @@ func initFixture(t *testing.T, extraAccs map[string]accountstd.Interface) *fixtu banktypes.RegisterMsgServer(router, bankkeeper.NewMsgServerImpl(bankKeeper)) + // commit and finalize block + defer func() { + _, err := integrationApp.Commit() + if err != nil { + panic(err) + } + }() + height := integrationApp.LastBlockHeight() + 1 + _, err = integrationApp.FinalizeBlock(&cmtabcitypes.FinalizeBlockRequest{Height: height, DecidedLastCommit: cmtabcitypes.CommitInfo{Votes: []cmtabcitypes.VoteInfo{{}}}}) + require.NoError(t, err) + return &fixture{ app: integrationApp, cdc: cdc, + ctx: sdk.UnwrapSDKContext(integrationApp.Context()), accountsKeeper: accountsKeeper, authKeeper: authKeeper, bankKeeper: bankKeeper, + encodingCfg: encodingCfg, } } diff --git a/tests/integration/distribution/cli_tx_test.go b/tests/integration/distribution/cli_tx_test.go index 0eb26d978c2f..654feeea48ac 100644 --- a/tests/integration/distribution/cli_tx_test.go +++ b/tests/integration/distribution/cli_tx_test.go @@ -11,7 +11,6 @@ import ( sdkmath "cosmossdk.io/math" "cosmossdk.io/x/distribution/client/cli" - minttypes "cosmossdk.io/x/mint/types" "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/flags" @@ -20,7 +19,6 @@ import ( "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" testutilmod "github.com/cosmos/cosmos-sdk/types/module/testutil" ) @@ -61,23 +59,6 @@ func (s *CLITestSuite) SetupSuite() { return s.baseCtx.WithClient(c) } s.clientCtx = ctxGen() - - cfg, err := network.DefaultConfigWithAppConfig(AppConfig) - s.Require().NoError(err) - - genesisState := cfg.GenesisState - var mintData minttypes.GenesisState - s.Require().NoError(cfg.Codec.UnmarshalJSON(genesisState[minttypes.ModuleName], &mintData)) - - inflation := sdkmath.LegacyMustNewDecFromStr("1.0") - mintData.Minter.Inflation = inflation - mintData.Params.InflationMin = inflation - mintData.Params.InflationMax = inflation - - mintDataBz, err := cfg.Codec.MarshalJSON(&mintData) - s.Require().NoError(err) - genesisState[minttypes.ModuleName] = mintDataBz - cfg.GenesisState = genesisState } func (s *CLITestSuite) TestTxWithdrawAllRewardsCmd() { diff --git a/tests/integration/genutil/init_test.go b/tests/integration/genutil/init_test.go index b49dbc6a7458..a1027a56fe1a 100644 --- a/tests/integration/genutil/init_test.go +++ b/tests/integration/genutil/init_test.go @@ -5,6 +5,7 @@ import ( "context" "fmt" "io" + "net" "os" "testing" "time" @@ -27,7 +28,6 @@ import ( servercmtlog "github.com/cosmos/cosmos-sdk/server/log" "github.com/cosmos/cosmos-sdk/server/mock" "github.com/cosmos/cosmos-sdk/testutil" - "github.com/cosmos/cosmos-sdk/testutil/network" genutilhelpers "github.com/cosmos/cosmos-sdk/testutil/x/genutil" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/module" @@ -220,7 +220,7 @@ func TestStartStandAlone(t *testing.T) { app, err := mock.NewApp(home, logger) require.NoError(t, err) - svrAddr, _, closeFn, err := network.FreeTCPAddr() + svrAddr, _, closeFn, err := freeTCPAddr() require.NoError(t, err) require.NoError(t, closeFn()) @@ -392,3 +392,21 @@ func writeAndTrackDefaultConfig(v *viper.Viper, home string) error { } return genutilhelpers.WriteAndTrackCometConfig(v, home, cfg) } + +// Get a free address for a test CometBFT server +// protocol is either tcp, http, etc +func freeTCPAddr() (addr, port string, closeFn func() error, err error) { + l, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + return "", "", nil, err + } + + closeFn = func() error { + return l.Close() + } + + portI := l.Addr().(*net.TCPAddr).Port + port = fmt.Sprintf("%d", portI) + addr = fmt.Sprintf("tcp://127.0.0.1:%s", port) + return +} diff --git a/tests/integration/server/grpc/out_of_gas_test.go b/tests/integration/server/grpc/out_of_gas_test.go deleted file mode 100644 index eabd41fc754d..000000000000 --- a/tests/integration/server/grpc/out_of_gas_test.go +++ /dev/null @@ -1,100 +0,0 @@ -package grpc_test - -import ( - "context" - "fmt" - "testing" - - "github.com/stretchr/testify/suite" - "google.golang.org/grpc" - "google.golang.org/grpc/metadata" - - _ "cosmossdk.io/x/accounts" - _ "cosmossdk.io/x/bank" - banktypes "cosmossdk.io/x/bank/types" - _ "cosmossdk.io/x/consensus" - _ "cosmossdk.io/x/staking" - - "github.com/cosmos/cosmos-sdk/baseapp" - "github.com/cosmos/cosmos-sdk/codec" - "github.com/cosmos/cosmos-sdk/testutil/configurator" - "github.com/cosmos/cosmos-sdk/testutil/network" - "github.com/cosmos/cosmos-sdk/testutil/testdata" - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" - _ "github.com/cosmos/cosmos-sdk/x/auth" - _ "github.com/cosmos/cosmos-sdk/x/auth/tx/config" -) - -type IntegrationTestOutOfGasSuite struct { - suite.Suite - - cfg network.Config - network network.NetworkI - conn *grpc.ClientConn -} - -func (s *IntegrationTestOutOfGasSuite) SetupSuite() { - var err error - s.T().Log("setting up integration test suite") - - s.cfg, err = network.DefaultConfigWithAppConfig(configurator.NewAppConfig( - configurator.AccountsModule(), - configurator.AuthModule(), - configurator.BankModule(), - configurator.GenutilModule(), - configurator.StakingModule(), - configurator.ConsensusModule(), - configurator.TxModule(), - configurator.ValidateModule(), - ), baseapp.SetQueryGasLimit(10)) - s.NoError(err) - s.cfg.NumValidators = 1 - - s.network, err = network.New(s.T(), s.T().TempDir(), s.cfg) - s.Require().NoError(err) - - _, err = s.network.WaitForHeight(2) - s.Require().NoError(err) - - val0 := s.network.GetValidators()[0] - s.conn, err = grpc.NewClient( - val0.GetAppConfig().GRPC.Address, - grpc.WithInsecure(), //nolint:staticcheck // ignore SA1019, we don't need to use a secure connection for tests - grpc.WithDefaultCallOptions(grpc.ForceCodec(codec.NewProtoCodec(s.cfg.InterfaceRegistry).GRPCCodec())), - ) - s.Require().NoError(err) -} - -func (s *IntegrationTestOutOfGasSuite) TearDownSuite() { - s.T().Log("tearing down integration test suite") - s.conn.Close() - s.network.Cleanup() -} - -func (s *IntegrationTestOutOfGasSuite) TestGRPCServer_TestService() { - // gRPC query to test service should work - testClient := testdata.NewQueryClient(s.conn) - testRes, err := testClient.Echo(context.Background(), &testdata.EchoRequest{Message: "hello"}) - s.Require().NoError(err) - s.Require().Equal("hello", testRes.Message) -} - -func (s *IntegrationTestOutOfGasSuite) TestGRPCServer_BankBalance_OutOfGas() { - val0 := s.network.GetValidators()[0] - - // gRPC query to bank service should work - denom := fmt.Sprintf("%stoken", val0.GetMoniker()) - bankClient := banktypes.NewQueryClient(s.conn) - var header metadata.MD - _, err := bankClient.Balance( - context.Background(), - &banktypes.QueryBalanceRequest{Address: val0.GetAddress().String(), Denom: denom}, - grpc.Header(&header), // Also fetch grpc header - ) - - s.Require().ErrorContains(err, sdkerrors.ErrOutOfGas.Error()) -} - -func TestIntegrationTestOutOfGasSuite(t *testing.T) { - suite.Run(t, new(IntegrationTestOutOfGasSuite)) -} diff --git a/tests/integration/server/grpc/server_test.go b/tests/integration/server/grpc/server_test.go deleted file mode 100644 index e8d6d180b435..000000000000 --- a/tests/integration/server/grpc/server_test.go +++ /dev/null @@ -1,291 +0,0 @@ -package grpc_test - -import ( - "context" - "fmt" - "testing" - "time" - - "github.com/jhump/protoreflect/grpcreflect" - "github.com/stretchr/testify/require" - "github.com/stretchr/testify/suite" - "google.golang.org/grpc" - "google.golang.org/grpc/metadata" - - _ "cosmossdk.io/x/accounts" - _ "cosmossdk.io/x/bank" - banktypes "cosmossdk.io/x/bank/types" - _ "cosmossdk.io/x/consensus" - _ "cosmossdk.io/x/staking" - stakingtypes "cosmossdk.io/x/staking/types" - - "github.com/cosmos/cosmos-sdk/client" - reflectionv1 "github.com/cosmos/cosmos-sdk/client/grpc/reflection" - clienttx "github.com/cosmos/cosmos-sdk/client/tx" - "github.com/cosmos/cosmos-sdk/codec" - reflectionv2 "github.com/cosmos/cosmos-sdk/server/grpc/reflection/v2alpha1" - "github.com/cosmos/cosmos-sdk/testutil/configurator" - "github.com/cosmos/cosmos-sdk/testutil/network" - "github.com/cosmos/cosmos-sdk/testutil/testdata" - sdk "github.com/cosmos/cosmos-sdk/types" - grpctypes "github.com/cosmos/cosmos-sdk/types/grpc" - txtypes "github.com/cosmos/cosmos-sdk/types/tx" - "github.com/cosmos/cosmos-sdk/types/tx/signing" - _ "github.com/cosmos/cosmos-sdk/x/auth" - authclient "github.com/cosmos/cosmos-sdk/x/auth/client" - _ "github.com/cosmos/cosmos-sdk/x/auth/tx/config" -) - -type IntegrationTestSuite struct { - suite.Suite - - cfg network.Config - network network.NetworkI - conn *grpc.ClientConn -} - -func (s *IntegrationTestSuite) SetupSuite() { - var err error - s.T().Log("setting up integration test suite") - - s.cfg, err = network.DefaultConfigWithAppConfig(configurator.NewAppConfig( - configurator.AccountsModule(), - configurator.AuthModule(), - configurator.BankModule(), - configurator.GenutilModule(), - configurator.StakingModule(), - configurator.ConsensusModule(), - configurator.TxModule(), - configurator.ValidateModule(), - )) - s.NoError(err) - s.cfg.NumValidators = 1 - - s.network, err = network.New(s.T(), s.T().TempDir(), s.cfg) - s.Require().NoError(err) - - _, err = s.network.WaitForHeight(2) - s.Require().NoError(err) - - val0 := s.network.GetValidators()[0] - s.conn, err = grpc.NewClient( - val0.GetAppConfig().GRPC.Address, - grpc.WithInsecure(), //nolint:staticcheck // ignore SA1019, we don't need to use a secure connection for tests - grpc.WithDefaultCallOptions(grpc.ForceCodec(codec.NewProtoCodec(s.cfg.InterfaceRegistry).GRPCCodec())), - ) - s.Require().NoError(err) -} - -func (s *IntegrationTestSuite) TearDownSuite() { - s.T().Log("tearing down integration test suite") - s.conn.Close() - s.network.Cleanup() -} - -func (s *IntegrationTestSuite) TestGRPCServer_TestService() { - // gRPC query to test service should work - testClient := testdata.NewQueryClient(s.conn) - testRes, err := testClient.Echo(context.Background(), &testdata.EchoRequest{Message: "hello"}) - s.Require().NoError(err) - s.Require().Equal("hello", testRes.Message) -} - -func (s *IntegrationTestSuite) TestGRPCServer_BankBalance() { - val0 := s.network.GetValidators()[0] - - // gRPC query to bank service should work - denom := fmt.Sprintf("%stoken", val0.GetMoniker()) - bankClient := banktypes.NewQueryClient(s.conn) - var header metadata.MD - bankRes, err := bankClient.Balance( - context.Background(), - &banktypes.QueryBalanceRequest{Address: val0.GetAddress().String(), Denom: denom}, - grpc.Header(&header), // Also fetch grpc header - ) - s.Require().NoError(err) - s.Require().Equal( - sdk.NewCoin(denom, s.cfg.AccountTokens), - *bankRes.GetBalance(), - ) - blockHeight := header.Get(grpctypes.GRPCBlockHeightHeader) - s.Require().NotEmpty(blockHeight[0]) // Should contain the block height - - // Request metadata should work - _, err = bankClient.Balance( - metadata.AppendToOutgoingContext(context.Background(), grpctypes.GRPCBlockHeightHeader, "1"), // Add metadata to request - &banktypes.QueryBalanceRequest{Address: val0.GetAddress().String(), Denom: denom}, - grpc.Header(&header), - ) - s.Require().NoError(err) - blockHeight = header.Get(grpctypes.GRPCBlockHeightHeader) - s.Require().Equal([]string{"1"}, blockHeight) -} - -func (s *IntegrationTestSuite) TestGRPCServer_Reflection() { - // Test server reflection - ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) - defer cancel() - // NOTE(fdymylja): we use grpcreflect because it solves imports too - // so that we can always assert that given a reflection server it is - // possible to fully query all the methods, without having any context - // on the proto registry - rc := grpcreflect.NewClientAuto(ctx, s.conn) - - services, err := rc.ListServices() - s.Require().NoError(err) - s.Require().Greater(len(services), 0) - - for _, svc := range services { - file, err := rc.FileContainingSymbol(svc) - s.Require().NoError(err) - sd := file.FindSymbol(svc) - s.Require().NotNil(sd) - } -} - -func (s *IntegrationTestSuite) TestGRPCServer_InterfaceReflection() { - // this tests the application reflection capabilities and compatibility between v1 and v2 - ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) - defer cancel() - - clientV2 := reflectionv2.NewReflectionServiceClient(s.conn) - clientV1 := reflectionv1.NewReflectionServiceClient(s.conn) - codecDesc, err := clientV2.GetCodecDescriptor(ctx, nil) - s.Require().NoError(err) - - interfaces, err := clientV1.ListAllInterfaces(ctx, nil) - s.Require().NoError(err) - s.Require().Equal(len(codecDesc.Codec.Interfaces), len(interfaces.InterfaceNames)) - s.Require().Equal(len(s.cfg.InterfaceRegistry.ListAllInterfaces()), len(codecDesc.Codec.Interfaces)) - - for _, iface := range interfaces.InterfaceNames { - impls, err := clientV1.ListImplementations(ctx, &reflectionv1.ListImplementationsRequest{InterfaceName: iface}) - s.Require().NoError(err) - - s.Require().ElementsMatch(impls.ImplementationMessageNames, s.cfg.InterfaceRegistry.ListImplementations(iface)) - } -} - -func (s *IntegrationTestSuite) TestGRPCServer_GetTxsEvent() { - // Query the tx via gRPC without pagination. This used to panic, see - // https://github.com/cosmos/cosmos-sdk/issues/8038. - txServiceClient := txtypes.NewServiceClient(s.conn) - _, err := txServiceClient.GetTxsEvent( - context.Background(), - &txtypes.GetTxsEventRequest{ - Query: "message.action='send'", - }, - ) - s.Require().NoError(err) -} - -func (s *IntegrationTestSuite) TestGRPCServer_BroadcastTx() { - val0 := s.network.GetValidators()[0] - - txBuilder := s.mkTxBuilder() - - txBytes, err := val0.GetClientCtx().TxConfig.TxEncoder()(txBuilder.GetTx()) - s.Require().NoError(err) - - // Broadcast the tx via gRPC. - queryClient := txtypes.NewServiceClient(s.conn) - - grpcRes, err := queryClient.BroadcastTx( - context.Background(), - &txtypes.BroadcastTxRequest{ - Mode: txtypes.BroadcastMode_BROADCAST_MODE_SYNC, - TxBytes: txBytes, - }, - ) - s.Require().NoError(err) - s.Require().Equal(uint32(0), grpcRes.TxResponse.Code) -} - -// Test and enforce that we upfront reject any connections to baseapp containing -// invalid initial x-cosmos-block-height that aren't positive and in the range [0, max(int64)] -// See issue https://github.com/cosmos/cosmos-sdk/issues/7662. -func (s *IntegrationTestSuite) TestGRPCServerInvalidHeaderHeights() { - t := s.T() - - // We should reject connections with invalid block heights off the bat. - invalidHeightStrs := []struct { - value string - wantErr string - }{ - {"-1", "height < 0"}, - {"9223372036854775808", "value out of range"}, // > max(int64) by 1 - {"-10", "height < 0"}, - {"18446744073709551615", "value out of range"}, // max uint64, which is > max(int64) - {"-9223372036854775809", "value out of range"}, // Out of the range of for negative int64 - } - for _, tt := range invalidHeightStrs { - t.Run(tt.value, func(t *testing.T) { - testClient := testdata.NewQueryClient(s.conn) - ctx := metadata.AppendToOutgoingContext(context.Background(), grpctypes.GRPCBlockHeightHeader, tt.value) - testRes, err := testClient.Echo(ctx, &testdata.EchoRequest{Message: "hello"}) - require.Error(t, err) - require.Nil(t, testRes) - require.Contains(t, err.Error(), tt.wantErr) - }) - } -} - -// TestGRPCUnpacker - tests the grpc endpoint for Validator and using the interface registry unpack and extract the -// ConsAddr. (ref: https://github.com/cosmos/cosmos-sdk/issues/8045) -func (s *IntegrationTestSuite) TestGRPCUnpacker() { - ir := s.cfg.InterfaceRegistry - queryClient := stakingtypes.NewQueryClient(s.conn) - validator, err := queryClient.Validator(context.Background(), - &stakingtypes.QueryValidatorRequest{ValidatorAddr: s.network.GetValidators()[0].GetValAddress().String()}) - require.NoError(s.T(), err) - - // no unpacked interfaces yet, so ConsAddr will be nil - nilAddr, err := validator.Validator.GetConsAddr() - require.Error(s.T(), err) - require.Nil(s.T(), nilAddr) - - // unpack the interfaces and now ConsAddr is not nil - err = validator.Validator.UnpackInterfaces(ir) - require.NoError(s.T(), err) - addr, err := validator.Validator.GetConsAddr() - require.NotNil(s.T(), addr) - require.NoError(s.T(), err) -} - -// mkTxBuilder creates a TxBuilder containing a signed tx from validator 0. -func (s *IntegrationTestSuite) mkTxBuilder() client.TxBuilder { - val := s.network.GetValidators()[0] - s.Require().NoError(s.network.WaitForNextBlock()) - - // prepare txBuilder with msg - txBuilder := val.GetClientCtx().TxConfig.NewTxBuilder() - feeAmount := sdk.Coins{sdk.NewInt64Coin(s.cfg.BondDenom, 10)} - gasLimit := testdata.NewTestGasLimit() - s.Require().NoError( - txBuilder.SetMsgs(&banktypes.MsgSend{ - FromAddress: val.GetAddress().String(), - ToAddress: val.GetAddress().String(), - Amount: sdk.Coins{sdk.NewInt64Coin(s.cfg.BondDenom, 10)}, - }), - ) - txBuilder.SetFeeAmount(feeAmount) - txBuilder.SetGasLimit(gasLimit) - txBuilder.SetMemo("foobar") - - // setup txFactory - txFactory := clienttx.Factory{}. - WithChainID(val.GetClientCtx().ChainID). - WithKeybase(val.GetClientCtx().Keyring). - WithTxConfig(val.GetClientCtx().TxConfig). - WithSignMode(signing.SignMode_SIGN_MODE_DIRECT) - - // Sign Tx. - err := authclient.SignTx(txFactory, val.GetClientCtx(), val.GetMoniker(), txBuilder, false, true) - s.Require().NoError(err) - - return txBuilder -} - -func TestIntegrationTestSuite(t *testing.T) { - suite.Run(t, new(IntegrationTestSuite)) -} diff --git a/tests/integration/server/grpc_test.go b/tests/integration/server/grpc_test.go new file mode 100644 index 000000000000..571f3fafe7e3 --- /dev/null +++ b/tests/integration/server/grpc_test.go @@ -0,0 +1,321 @@ +package grpc_test + +import ( + "context" + "testing" + "time" + + "github.com/jhump/protoreflect/grpcreflect" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" + "go.uber.org/mock/gomock" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" + "google.golang.org/grpc/metadata" + + "cosmossdk.io/core/appmodule" + "cosmossdk.io/log" + storetypes "cosmossdk.io/store/types" + "cosmossdk.io/x/bank" + bankkeeper "cosmossdk.io/x/bank/keeper" + banktypes "cosmossdk.io/x/bank/types" + minttypes "cosmossdk.io/x/mint/types" + "cosmossdk.io/x/staking" + stakingkeeper "cosmossdk.io/x/staking/keeper" + stakingtypes "cosmossdk.io/x/staking/types" + + "github.com/cosmos/cosmos-sdk/baseapp" + "github.com/cosmos/cosmos-sdk/client" + reflectionv1 "github.com/cosmos/cosmos-sdk/client/grpc/reflection" + "github.com/cosmos/cosmos-sdk/codec" + addresscodec "github.com/cosmos/cosmos-sdk/codec/address" + codectestutil "github.com/cosmos/cosmos-sdk/codec/testutil" + "github.com/cosmos/cosmos-sdk/runtime" + srvconfig "github.com/cosmos/cosmos-sdk/server/config" + servergrpc "github.com/cosmos/cosmos-sdk/server/grpc" + reflectionv2 "github.com/cosmos/cosmos-sdk/server/grpc/reflection/v2alpha1" + "github.com/cosmos/cosmos-sdk/testutil/integration" + simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims" + "github.com/cosmos/cosmos-sdk/testutil/testdata" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + grpctypes "github.com/cosmos/cosmos-sdk/types/grpc" + moduletestutil "github.com/cosmos/cosmos-sdk/types/module/testutil" + "github.com/cosmos/cosmos-sdk/x/auth" + authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper" + authsims "github.com/cosmos/cosmos-sdk/x/auth/simulation" + authtestutil "github.com/cosmos/cosmos-sdk/x/auth/testutil" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" +) + +type IntegrationTestSuite struct { + suite.Suite + + codec codec.Codec + conn *grpc.ClientConn + address sdk.AccAddress +} + +func (s *IntegrationTestSuite) SetupSuite() { + s.T().Log("setting up integration test suite") + + keys := storetypes.NewKVStoreKeys(authtypes.StoreKey, banktypes.StoreKey, stakingtypes.StoreKey) + encodingCfg := moduletestutil.MakeTestEncodingConfig(codectestutil.CodecOptions{}, auth.AppModule{}, bank.AppModule{}) + s.codec = encodingCfg.Codec + + logger := log.NewTestLogger(s.T()) + authority := authtypes.NewModuleAddress("gov") + + // gomock initializations + ctrl := gomock.NewController(s.T()) + acctsModKeeper := authtestutil.NewMockAccountsModKeeper(ctrl) + accNum := uint64(0) + acctsModKeeper.EXPECT().NextAccountNumber(gomock.Any()).AnyTimes().DoAndReturn(func(ctx context.Context) (uint64, error) { + currentNum := accNum + accNum++ + return currentNum, nil + }) + + accountKeeper := authkeeper.NewAccountKeeper( + runtime.NewEnvironment(runtime.NewKVStoreService(keys[authtypes.StoreKey]), log.NewNopLogger()), + s.codec, + authtypes.ProtoBaseAccount, + acctsModKeeper, + map[string][]string{ + minttypes.ModuleName: {authtypes.Minter}, + stakingtypes.BondedPoolName: {authtypes.Burner, stakingtypes.ModuleName}, + stakingtypes.NotBondedPoolName: {authtypes.Burner, stakingtypes.ModuleName}, + }, + addresscodec.NewBech32Codec(sdk.Bech32MainPrefix), + sdk.Bech32MainPrefix, + authority.String(), + ) + + blockedAddresses := map[string]bool{ + accountKeeper.GetAuthority(): false, + } + bankKeeper := bankkeeper.NewBaseKeeper( + runtime.NewEnvironment(runtime.NewKVStoreService(keys[banktypes.StoreKey]), log.NewNopLogger()), + s.codec, + accountKeeper, + blockedAddresses, + authority.String(), + ) + + stakingKeeper := stakingkeeper.NewKeeper(s.codec, runtime.NewEnvironment(runtime.NewKVStoreService(keys[stakingtypes.StoreKey]), log.NewNopLogger()), accountKeeper, bankKeeper, nil, authority.String(), encodingCfg.InterfaceRegistry.SigningContext().ValidatorAddressCodec(), addresscodec.NewBech32Codec("cosmosvaloper"), nil) + + authModule := auth.NewAppModule(s.codec, accountKeeper, acctsModKeeper, authsims.RandomGenesisAccounts, nil) + bankModule := bank.NewAppModule(s.codec, bankKeeper, accountKeeper) + stakingModule := staking.NewAppModule(s.codec, stakingKeeper) + + integrationApp := integration.NewIntegrationApp(logger, keys, s.codec, + encodingCfg.InterfaceRegistry.SigningContext().AddressCodec(), + encodingCfg.InterfaceRegistry.SigningContext().ValidatorAddressCodec(), + map[string]appmodule.AppModule{ + authtypes.ModuleName: authModule, + banktypes.ModuleName: bankModule, + stakingtypes.ModuleName: stakingModule, + }, + baseapp.NewMsgServiceRouter(), + baseapp.NewGRPCQueryRouter(), + baseapp.SetQueryGasLimit(50), + ) + + pubkeys := simtestutil.CreateTestPubKeys(2) + s.address = sdk.AccAddress(pubkeys[0].Address()) + addr2 := sdk.AccAddress(pubkeys[1].Address()) + + sdkCtx := sdk.UnwrapSDKContext(integrationApp.Context()) + + // mint some tokens + amount := sdk.NewCoins(sdk.NewInt64Coin("stake", 100)) + s.Require().NoError(bankKeeper.MintCoins(sdkCtx, minttypes.ModuleName, amount)) + s.Require().NoError(bankKeeper.SendCoinsFromModuleToAccount(sdkCtx, minttypes.ModuleName, addr2, amount)) + + // Register MsgServer and QueryServer + banktypes.RegisterMsgServer(integrationApp.MsgServiceRouter(), bankkeeper.NewMsgServerImpl(bankKeeper)) + banktypes.RegisterQueryServer(integrationApp.GRPCQueryRouter(), bankkeeper.NewQuerier(&bankKeeper)) + testdata.RegisterQueryServer(integrationApp.GRPCQueryRouter(), testdata.QueryImpl{}) + banktypes.RegisterInterfaces(encodingCfg.InterfaceRegistry) + stakingtypes.RegisterQueryServer(integrationApp.GRPCQueryRouter(), stakingkeeper.NewQuerier(stakingKeeper)) + + _, err := integrationApp.RunMsg( + &banktypes.MsgSend{ + FromAddress: addr2.String(), + ToAddress: s.address.String(), + Amount: sdk.NewCoins(sdk.NewInt64Coin("stake", 50)), + }, + integration.WithAutomaticFinalizeBlock(), + integration.WithAutomaticCommit(), + ) + s.Require().NoError(err) + s.Require().Equal(integrationApp.LastBlockHeight(), int64(2)) + + resp, err := bankKeeper.Balance(integrationApp.Context(), &banktypes.QueryBalanceRequest{Address: s.address.String(), Denom: "stake"}) + s.Require().NoError(err) + s.Require().Equal(int64(50), resp.Balance.Amount.Int64()) + + grpcCfg := srvconfig.DefaultConfig().GRPC + grpcSrv, err := servergrpc.NewGRPCServer((client.Context{}). + WithChainID(integrationApp.ChainID()). + WithInterfaceRegistry(encodingCfg.InterfaceRegistry). + WithTxConfig(encodingCfg.TxConfig), + integrationApp, + grpcCfg, + ) + s.Require().NoError(err) + + go func() { + err := servergrpc.StartGRPCServer( + integrationApp.Context(), + integrationApp.Logger(), + grpcCfg, + grpcSrv, + ) + s.Require().NoError(err) + defer grpcSrv.GracefulStop() + }() + + s.conn, err = grpc.NewClient( + grpcCfg.Address, + grpc.WithTransportCredentials(insecure.NewCredentials()), + grpc.WithDefaultCallOptions(grpc.ForceCodec(codec.NewProtoCodec(encodingCfg.InterfaceRegistry).GRPCCodec())), + ) + s.Require().NoError(err) +} + +func (s *IntegrationTestSuite) TearDownSuite() { + s.T().Log("tearing down integration test suite") + s.conn.Close() +} + +func (s *IntegrationTestSuite) TestGRPCServer_TestService() { + // gRPC query to test service should work + testClient := testdata.NewQueryClient(s.conn) + testRes, err := testClient.Echo( + context.Background(), + &testdata.EchoRequest{Message: "hello"}) + s.Require().NoError(err) + s.Require().Equal("hello", testRes.Message) +} + +func (s *IntegrationTestSuite) TestGRPCServer_BankBalance_OutOfGas() { + // gRPC query to bank service should work + bankClient := banktypes.NewQueryClient(s.conn) + + _, err := bankClient.Balance( + context.Background(), + &banktypes.QueryBalanceRequest{Address: s.address.String(), Denom: "stake"}, + ) + s.Require().ErrorContains(err, sdkerrors.ErrOutOfGas.Error()) +} + +// Test and enforce that we upfront reject any connections to baseapp containing +// invalid initial x-cosmos-block-height that aren't positive and in the range [0, max(int64)] +// See issue https://github.com/cosmos/cosmos-sdk/issues/7662. +func (s *IntegrationTestSuite) TestGRPCServerInvalidHeaderHeights() { + t := s.T() + + // We should reject connections with invalid block heights off the bat. + invalidHeightStrs := []struct { + value string + wantErr string + }{ + {"-1", "height < 0"}, + {"9223372036854775808", "value out of range"}, // > max(int64) by 1 + {"-10", "height < 0"}, + {"18446744073709551615", "value out of range"}, // max uint64, which is > max(int64) + {"-9223372036854775809", "value out of range"}, // Out of the range of for negative int64 + } + for _, tt := range invalidHeightStrs { + t.Run(tt.value, func(t *testing.T) { + testClient := testdata.NewQueryClient(s.conn) + ctx := metadata.AppendToOutgoingContext(context.Background(), grpctypes.GRPCBlockHeightHeader, tt.value) + testRes, err := testClient.Echo(ctx, &testdata.EchoRequest{Message: "hello"}) + require.Error(t, err) + require.Nil(t, testRes) + require.Contains(t, err.Error(), tt.wantErr) + }) + } +} + +func (s *IntegrationTestSuite) TestGRPCServer_Reflection() { + // Test server reflection + ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) + defer cancel() + // NOTE(fdymylja): we use grpcreflect because it solves imports too + // so that we can always assert that given a reflection server it is + // possible to fully query all the methods, without having any context + // on the proto registry + rc := grpcreflect.NewClientAuto(ctx, s.conn) + + services, err := rc.ListServices() + s.Require().NoError(err) + s.Require().Greater(len(services), 0) + + for _, svc := range services { + file, err := rc.FileContainingSymbol(svc) + s.Require().NoError(err) + sd := file.FindSymbol(svc) + s.Require().NotNil(sd) + } +} + +func (s *IntegrationTestSuite) TestGRPCServer_InterfaceReflection() { + s.T().Skip() // TODO: fix this test at https://github.com/cosmos/cosmos-sdk/issues/22825 + + // this tests the application reflection capabilities and compatibility between v1 and v2 + ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) + defer cancel() + + clientV2 := reflectionv2.NewReflectionServiceClient(s.conn) + clientV1 := reflectionv1.NewReflectionServiceClient(s.conn) + codecDesc, err := clientV2.GetCodecDescriptor(ctx, nil) + s.Require().NoError(err) + + interfaces, err := clientV1.ListAllInterfaces(ctx, nil) + s.Require().NoError(err) + s.Require().Equal(len(codecDesc.Codec.Interfaces), len(interfaces.InterfaceNames)) + s.Require().Equal(len(s.codec.InterfaceRegistry().ListAllInterfaces()), len(codecDesc.Codec.Interfaces)) + + for _, iface := range interfaces.InterfaceNames { + impls, err := clientV1.ListImplementations(ctx, &reflectionv1.ListImplementationsRequest{InterfaceName: iface}) + s.Require().NoError(err) + + s.Require().ElementsMatch(impls.ImplementationMessageNames, s.codec.InterfaceRegistry().ListImplementations(iface)) + } +} + +// TestGRPCUnpacker - tests the grpc endpoint for Validator and using the interface registry unpack and extract the +// ConsAddr. (ref: https://github.com/cosmos/cosmos-sdk/issues/8045) +func (s *IntegrationTestSuite) TestGRPCUnpacker() { + queryClient := stakingtypes.NewQueryClient(s.conn) + validators, err := queryClient.Validators(context.Background(), &stakingtypes.QueryValidatorsRequest{}) + require.NoError(s.T(), err) + + if len(validators.Validators) == 0 { + s.T().Skip("no validators found") + } + + validator, err := queryClient.Validator( + context.Background(), + &stakingtypes.QueryValidatorRequest{ValidatorAddr: validators.Validators[0].OperatorAddress}, + ) + require.NoError(s.T(), err) + + // no unpacked interfaces yet, so ConsAddr will be nil + nilAddr, err := validator.Validator.GetConsAddr() + require.Error(s.T(), err) + require.Nil(s.T(), nilAddr) + + // unpack the interfaces and now ConsAddr is not nil + err = validator.Validator.UnpackInterfaces(s.codec.InterfaceRegistry()) + require.NoError(s.T(), err) + addr, err := validator.Validator.GetConsAddr() + require.NotNil(s.T(), addr) + require.NoError(s.T(), err) +} + +func TestIntegrationTestSuite(t *testing.T) { + suite.Run(t, new(IntegrationTestSuite)) +} diff --git a/tests/integration/tx/benchmark/benchmarks_test.go b/tests/integration/tx/benchmark/benchmarks_test.go deleted file mode 100644 index 4d26bc3e3d53..000000000000 --- a/tests/integration/tx/benchmark/benchmarks_test.go +++ /dev/null @@ -1,191 +0,0 @@ -package benchmark_test - -import ( - "context" - "testing" - - "gotest.tools/v3/assert" - - sdkmath "cosmossdk.io/math" - "cosmossdk.io/simapp" - banktypes "cosmossdk.io/x/bank/types" - - "github.com/cosmos/cosmos-sdk/client" - clienttx "github.com/cosmos/cosmos-sdk/client/tx" - "github.com/cosmos/cosmos-sdk/testutil/cli" - "github.com/cosmos/cosmos-sdk/testutil/network" - "github.com/cosmos/cosmos-sdk/testutil/testdata" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/types/tx" - "github.com/cosmos/cosmos-sdk/types/tx/signing" - authclient "github.com/cosmos/cosmos-sdk/x/auth/client" -) - -type TxBenchmarkSuite struct { - cfg network.Config - network network.NetworkI - - txHeight int64 - queryClient tx.ServiceClient -} - -// BenchmarkTx is lifted from TestSuite from this package, with irrelevant state checks removed. -// -// Benchmark results: -// -// On a M1 macOS, on commit ab77fe20d3c00ef4cb73dfd0c803c4593a3c8233: -// -// BenchmarkTx-8 4108 268935 ns/op -// -// By comparison, the benchmark modified for v0.47.0: -// -// BenchmarkTx-8 3772 301750 ns/op -func BenchmarkTx(b *testing.B) { - s := NewTxBenchmarkSuite(b) - b.Cleanup(s.Close) - - val := s.network.GetValidators()[0] - txBuilder := mkTxBuilder(b, s) - // Convert the txBuilder to a tx.Tx. - protoTx, err := txBuilder.GetTx().(interface{ AsTx() (*tx.Tx, error) }).AsTx() - assert.NilError(b, err) - // Encode the txBuilder to txBytes. - txBytes, err := val.GetClientCtx().TxConfig.TxEncoder()(txBuilder.GetTx()) - assert.NilError(b, err) - - testCases := []struct { - name string - req *tx.SimulateRequest - }{ - {"valid request with proto tx (deprecated)", &tx.SimulateRequest{Tx: protoTx}}, - {"valid request with tx_bytes", &tx.SimulateRequest{TxBytes: txBytes}}, - } - - b.ResetTimer() - b.ReportAllocs() - for i := 0; i < b.N; i++ { - for _, tc := range testCases { - // Broadcast the tx via gRPC via the validator's clientCtx (which goes - // through Tendermint). - res, err := s.queryClient.Simulate(context.Background(), tc.req) - assert.NilError(b, err) - // Check the result and gas used are correct. - // - // The 10 events are: - // - Sending Fee to the pool (3 events): coin_spent, coin_received, transfer - // - tx.* events (3 events): tx.fee, tx.acc_seq, tx.signature - // - Sending Amount to recipient (3 events): coin_spent, coin_received, transfer and message.sender= - // - Msg events (1 event): message.module=bank, message.action=/cosmos.bank.v1beta1.MsgSend and message.sender= (all in one event) - assert.Equal(b, 10, len(res.GetResult().GetEvents())) - assert.Assert(b, res.GetGasInfo().GetGasUsed() > 0) // Gas used sometimes change, just check it's not empty. - } - } -} - -func NewTxBenchmarkSuite(tb testing.TB) *TxBenchmarkSuite { - tb.Helper() - - s := new(TxBenchmarkSuite) - - cfg := network.DefaultConfig(simapp.NewTestNetworkFixture) - cfg.NumValidators = 1 - s.cfg = cfg - - var err error - s.network, err = network.New(tb, tb.TempDir(), s.cfg) - assert.NilError(tb, err) - - val := s.network.GetValidators()[0] - assert.NilError(tb, s.network.WaitForNextBlock()) - - s.queryClient = tx.NewServiceClient(val.GetClientCtx()) - - msgSend := &banktypes.MsgSend{ - FromAddress: val.GetAddress().String(), - ToAddress: val.GetAddress().String(), - Amount: sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdkmath.NewInt(10))), - } - - // Create a new MsgSend tx from val to itself. - out, err := cli.SubmitTestTx( - val.GetClientCtx(), - msgSend, - val.GetAddress(), - cli.TestTxConfig{ - Memo: "foobar", - }, - ) - - assert.NilError(tb, err) - - var txRes sdk.TxResponse - assert.NilError(tb, val.GetClientCtx().Codec.UnmarshalJSON(out.Bytes(), &txRes)) - assert.Equal(tb, uint32(0), txRes.Code, txRes) - - msgSend1 := &banktypes.MsgSend{ - FromAddress: val.GetAddress().String(), - ToAddress: val.GetAddress().String(), - Amount: sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdkmath.NewInt(1))), - } - - out, err = cli.SubmitTestTx( - val.GetClientCtx(), - msgSend1, - val.GetAddress(), - cli.TestTxConfig{ - Offline: true, - AccNum: 0, - Seq: 2, - Memo: "foobar", - }, - ) - - assert.NilError(tb, err) - var tr sdk.TxResponse - assert.NilError(tb, val.GetClientCtx().Codec.UnmarshalJSON(out.Bytes(), &tr)) - assert.Equal(tb, uint32(0), tr.Code) - - resp, err := cli.GetTxResponse(s.network, val.GetClientCtx(), tr.TxHash) - assert.NilError(tb, err) - s.txHeight = resp.Height - return s -} - -func (s *TxBenchmarkSuite) Close() { - s.network.Cleanup() -} - -func mkTxBuilder(tb testing.TB, s *TxBenchmarkSuite) client.TxBuilder { - tb.Helper() - - val := s.network.GetValidators()[0] - assert.NilError(tb, s.network.WaitForNextBlock()) - - // prepare txBuilder with msg - txBuilder := val.GetClientCtx().TxConfig.NewTxBuilder() - feeAmount := sdk.Coins{sdk.NewInt64Coin(s.cfg.BondDenom, 10)} - gasLimit := testdata.NewTestGasLimit() - assert.NilError(tb, - txBuilder.SetMsgs(&banktypes.MsgSend{ - FromAddress: val.GetAddress().String(), - ToAddress: val.GetAddress().String(), - Amount: sdk.Coins{sdk.NewInt64Coin(s.cfg.BondDenom, 10)}, - }), - ) - txBuilder.SetFeeAmount(feeAmount) - txBuilder.SetGasLimit(gasLimit) - txBuilder.SetMemo("foobar") - - // setup txFactory - txFactory := clienttx.Factory{}. - WithChainID(val.GetClientCtx().ChainID). - WithKeybase(val.GetClientCtx().Keyring). - WithTxConfig(val.GetClientCtx().TxConfig). - WithSignMode(signing.SignMode_SIGN_MODE_DIRECT) - - // Sign Tx. - err := authclient.SignTx(txFactory, val.GetClientCtx(), val.GetMoniker(), txBuilder, false, true) - assert.NilError(tb, err) - - return txBuilder -} diff --git a/tests/systemtests/cometbft_client_test.go b/tests/systemtests/cometbft_client_test.go index ae8ae2da9797..895a03a98c62 100644 --- a/tests/systemtests/cometbft_client_test.go +++ b/tests/systemtests/cometbft_client_test.go @@ -22,6 +22,17 @@ import ( qtypes "github.com/cosmos/cosmos-sdk/types/query" ) +func TestQueryStatus(t *testing.T) { + systest.Sut.ResetChain(t) + cli := systest.NewCLIWrapper(t, systest.Sut, systest.Verbose) + systest.Sut.StartChain(t) + + resp := cli.CustomQuery("status") + + // make sure the output has the validator moniker. + assert.Contains(t, resp, "\"moniker\":\"node0\"") +} + func TestQueryNodeInfo(t *testing.T) { systest.Sut.ResetChain(t) systest.Sut.StartChain(t) diff --git a/testutil/cli/tx.go b/testutil/cli/tx.go deleted file mode 100644 index 73f0f8867670..000000000000 --- a/testutil/cli/tx.go +++ /dev/null @@ -1,63 +0,0 @@ -package cli - -import ( - "fmt" - - "github.com/cosmos/cosmos-sdk/client" - "github.com/cosmos/cosmos-sdk/client/flags" - "github.com/cosmos/cosmos-sdk/testutil/network" - sdk "github.com/cosmos/cosmos-sdk/types" - authcli "github.com/cosmos/cosmos-sdk/x/auth/client/cli" -) - -// CheckTxCode verifies that the transaction result returns a specific code -// Takes a network, wait for two blocks and fetch the transaction from its hash -func CheckTxCode(network network.NetworkI, clientCtx client.Context, txHash string, expectedCode uint32) error { - // wait for 2 blocks - for i := 0; i < 2; i++ { - if err := network.WaitForNextBlock(); err != nil { - return fmt.Errorf("failed to wait for next block: %w", err) - } - } - - cmd := authcli.QueryTxCmd() - out, err := ExecTestCLICmd(clientCtx, cmd, []string{txHash, fmt.Sprintf("--%s=json", flags.FlagOutput)}) - if err != nil { - return err - } - - var response sdk.TxResponse - if err := clientCtx.Codec.UnmarshalJSON(out.Bytes(), &response); err != nil { - return err - } - - if response.Code != expectedCode { - return fmt.Errorf("expected code %d, got %d", expectedCode, response.Code) - } - - return nil -} - -// GetTxResponse returns queries the transaction response of a transaction from its hash -// Takes a network, wait for two blocks and fetch the transaction from its hash -func GetTxResponse(network network.NetworkI, clientCtx client.Context, txHash string) (sdk.TxResponse, error) { - // wait for 2 blocks - for i := 0; i < 2; i++ { - if err := network.WaitForNextBlock(); err != nil { - return sdk.TxResponse{}, fmt.Errorf("failed to wait for next block: %w", err) - } - } - - cmd := authcli.QueryTxCmd() - out, err := ExecTestCLICmd(clientCtx, cmd, []string{txHash, fmt.Sprintf("--%s=json", flags.FlagOutput)}) - if err != nil { - return sdk.TxResponse{}, err - } - - var response sdk.TxResponse - if err := clientCtx.Codec.UnmarshalJSON(out.Bytes(), &response); err != nil { - return sdk.TxResponse{}, err - } - - return response, nil -} diff --git a/testutil/network/doc.go b/testutil/network/doc.go deleted file mode 100644 index bb92b93510cc..000000000000 --- a/testutil/network/doc.go +++ /dev/null @@ -1,66 +0,0 @@ -/* -Package network implements and exposes a fully operational in-process CometBFT -test network that consists of at least one or potentially many validators. This -test network can be used primarily for integration tests or unit test suites. - -The test network utilizes SimApp as the ABCI application and uses all the modules -defined in the Cosmos SDK. An in-process test network can be configured with any -number of validators as well as account funds and even custom genesis state. - -When creating a test network, a series of Validator objects are returned. Each -Validator object has useful information such as their address and public key. A -Validator will also provide its RPC, P2P, and API addresses that can be useful -for integration testing. In addition, a CometBFT local RPC client is also provided -which can be handy for making direct RPC calls to CometBFT. - -Note, due to limitations in concurrency and the design of the RPC layer in -CometBFT, only the first Validator object will have an RPC and API client -exposed. Due to this exact same limitation, only a single test network can exist -at a time. A caller must be certain it calls Cleanup after it no longer needs -the network. - -A typical testing flow might look like the following: - - type IntegrationTestSuite struct { - suite.Suite - - cfg network.Config - network *network.Network - } - - func (s *IntegrationTestSuite) SetupSuite() { - s.T().Log("setting up integration test suite") - - cfg := network.DefaultConfig() - cfg.NumValidators = 1 - s.cfg = cfg - - var err error - s.network, err = network.New(s.T(), s.T().TempDir(), cfg) - s.Require().NoError(err) - - s.Require().NoError(s.network.WaitForNextBlock()) - } - - func (s *IntegrationTestSuite) TearDownSuite() { - s.T().Log("tearing down integration test suite") - - // This is important and must be called to ensure other tests can create - // a network! - s.network.Cleanup() - } - - func (s *IntegrationTestSuite) TestQueryBalancesRequestHandlerFn() { - val := s.network.Validators[0] - baseURL := val.APIAddress - - // Use baseURL to make API HTTP requests or use val.RPCClient to make direct - // CometBFT RPC calls. - // ... - } - - func TestIntegrationTestSuite(t *testing.T) { - suite.Run(t, new(IntegrationTestSuite)) - } -*/ -package network diff --git a/testutil/network/interface.go b/testutil/network/interface.go deleted file mode 100644 index 37356ea525c8..000000000000 --- a/testutil/network/interface.go +++ /dev/null @@ -1,48 +0,0 @@ -package network - -import ( - "time" - - "github.com/spf13/viper" - - "cosmossdk.io/log" - - "github.com/cosmos/cosmos-sdk/client" - cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" - srvconfig "github.com/cosmos/cosmos-sdk/server/config" - sdk "github.com/cosmos/cosmos-sdk/types" -) - -// NetworkI is an interface for a network of validators. -// It is used to abstract over the different network types (in-process, docker, etc.). -// if used there is a requirement to expose query and tx client for the nodes -type NetworkI interface { - // GetValidators returns the validators in the network - GetValidators() []ValidatorI - // WaitForNextBlock waits for the network to reach the next block - WaitForNextBlock() error - // WaitForHeight waits for the network to reach the given height - WaitForHeight(height int64) (int64, error) - // WaitForHeightWithTimeout waits for the network to reach the given height or times out - WaitForHeightWithTimeout(int64, time.Duration) (int64, error) - // RetryForBlocks retries the given function until it returns no error or the given number of blocks has passed - RetryForBlocks(retryFunc func() error, blocks int) error - // LatestHeight returns the latest height of the network - LatestHeight() (int64, error) - - Cleanup() -} - -// ValidatorI expose a validator's context and configuration -type ValidatorI interface { - GetViper() *viper.Viper - GetLogger() log.Logger - GetAppConfig() *srvconfig.Config - GetAddress() sdk.AccAddress - GetValAddress() sdk.ValAddress - GetClientCtx() client.Context - GetAPIAddress() string - GetRPCAddress() string - GetPubKey() cryptotypes.PubKey - GetMoniker() string -} diff --git a/testutil/network/network.go b/testutil/network/network.go deleted file mode 100644 index 994e7d5ef366..000000000000 --- a/testutil/network/network.go +++ /dev/null @@ -1,847 +0,0 @@ -package network - -import ( - "bufio" - "context" - "encoding/json" - "errors" - "fmt" - "net/url" - "os" - "os/signal" - "path/filepath" - "strings" - "sync" - "sync/atomic" - "syscall" - "testing" - "time" - - cmtcfg "github.com/cometbft/cometbft/config" - "github.com/spf13/cobra" - "github.com/spf13/viper" - - "cosmossdk.io/core/address" - "cosmossdk.io/core/registry" - coretesting "cosmossdk.io/core/testing" - "cosmossdk.io/depinject" - "cosmossdk.io/log" - sdkmath "cosmossdk.io/math" - "cosmossdk.io/math/unsafe" - pruningtypes "cosmossdk.io/store/pruning/types" - banktypes "cosmossdk.io/x/bank/types" - stakingtypes "cosmossdk.io/x/staking/types" - - "github.com/cosmos/cosmos-sdk/baseapp" - "github.com/cosmos/cosmos-sdk/client" - "github.com/cosmos/cosmos-sdk/client/flags" - "github.com/cosmos/cosmos-sdk/client/grpc/cmtservice" - "github.com/cosmos/cosmos-sdk/client/tx" - "github.com/cosmos/cosmos-sdk/codec" - addresscodec "github.com/cosmos/cosmos-sdk/codec/address" - codectypes "github.com/cosmos/cosmos-sdk/codec/types" - "github.com/cosmos/cosmos-sdk/crypto/hd" - "github.com/cosmos/cosmos-sdk/crypto/keyring" - "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" - cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" - "github.com/cosmos/cosmos-sdk/runtime" - srvconfig "github.com/cosmos/cosmos-sdk/server/config" - servertypes "github.com/cosmos/cosmos-sdk/server/types" - "github.com/cosmos/cosmos-sdk/testutil" - simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims" - "github.com/cosmos/cosmos-sdk/testutil/testdata" - sdk "github.com/cosmos/cosmos-sdk/types" - moduletestutil "github.com/cosmos/cosmos-sdk/types/module/testutil" - authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" - "github.com/cosmos/cosmos-sdk/x/genutil" -) - -// package-wide network lock to only allow one test network at a time -var ( - lock = new(sync.Mutex) - portPool = make(chan string, 200) -) - -func init() { - closeFns := []func() error{} - for i := 0; i < 200; i++ { - _, port, closeFn, err := FreeTCPAddr() - if err != nil { - panic(err) - } - - portPool <- port - closeFns = append(closeFns, closeFn) - } - - for _, closeFn := range closeFns { - err := closeFn() - if err != nil { - panic(err) - } - } -} - -// AppConstructor defines a function which accepts a network configuration and -// creates an ABCI Application to provide to CometBFT. -type ( - AppConstructor = func(val ValidatorI) servertypes.Application - TestFixtureFactory = func() TestFixture -) - -type TestFixture struct { - AppConstructor AppConstructor - GenesisState map[string]json.RawMessage - EncodingConfig moduletestutil.TestEncodingConfig -} - -// Config defines the necessary configuration used to bootstrap and start an -// in-process local testing network. -type Config struct { - Codec codec.Codec - LegacyAmino *codec.LegacyAmino // TODO: Remove! - InterfaceRegistry codectypes.InterfaceRegistry - - TxConfig client.TxConfig - AccountRetriever client.AccountRetriever - AppConstructor AppConstructor // the ABCI application constructor - GenesisState map[string]json.RawMessage // custom genesis state to provide - GenesisTime time.Time // the genesis time - TimeoutCommit time.Duration // the consensus commitment timeout - ChainID string // the network chain-id - NumValidators int // the total number of validators to create and bond - Mnemonics []string // custom user-provided validator operator mnemonics - BondDenom string // the staking bond denomination - MinGasPrices string // the minimum gas prices each validator will accept - AccountTokens sdkmath.Int // the amount of unique validator tokens (e.g. 1000node0) - StakingTokens sdkmath.Int // the amount of tokens each validator has available to stake - BondedTokens sdkmath.Int // the amount of tokens each validator stakes - PruningStrategy string // the pruning strategy each validator will have - EnableLogging bool // enable logging to STDOUT - CleanupDir bool // remove base temporary directory during cleanup - SigningAlgo string // signing algorithm for keys - KeyringOptions []keyring.Option // keyring configuration options - RPCAddress string // RPC listen address (including port) - APIAddress string // REST API listen address (including port) - GRPCAddress string // GRPC server listen address (including port) - PrintMnemonic bool // print the mnemonic of first validator as log output for testing - - // Address codecs - AddressCodec address.Codec // address codec - ValidatorAddressCodec address.ValidatorAddressCodec // validator address codec - ConsensusAddressCodec address.ConsensusAddressCodec // consensus address codec -} - -// DefaultConfig returns a sane default configuration suitable for nearly all -// testing requirements. -func DefaultConfig(factory TestFixtureFactory) Config { - fixture := factory() - - return Config{ - Codec: fixture.EncodingConfig.Codec, - TxConfig: fixture.EncodingConfig.TxConfig, - LegacyAmino: fixture.EncodingConfig.Amino, - InterfaceRegistry: fixture.EncodingConfig.InterfaceRegistry, - AccountRetriever: authtypes.AccountRetriever{}, - AppConstructor: fixture.AppConstructor, - GenesisState: fixture.GenesisState, - TimeoutCommit: 2 * time.Second, - ChainID: "chain-" + unsafe.Str(6), - NumValidators: 4, - BondDenom: sdk.DefaultBondDenom, - MinGasPrices: fmt.Sprintf("0.000006%s", sdk.DefaultBondDenom), - AccountTokens: sdk.TokensFromConsensusPower(1000, sdk.DefaultPowerReduction), - StakingTokens: sdk.TokensFromConsensusPower(500, sdk.DefaultPowerReduction), - BondedTokens: sdk.TokensFromConsensusPower(100, sdk.DefaultPowerReduction), - PruningStrategy: pruningtypes.PruningOptionNothing, - CleanupDir: true, - SigningAlgo: string(hd.Secp256k1Type), - KeyringOptions: []keyring.Option{}, - PrintMnemonic: false, - AddressCodec: addresscodec.NewBech32Codec("cosmos"), - ValidatorAddressCodec: addresscodec.NewBech32Codec("cosmosvaloper"), - ConsensusAddressCodec: addresscodec.NewBech32Codec("cosmosvalcons"), - } -} - -func DefaultConfigWithAppConfig(appConfig depinject.Config, baseappOpts ...func(*baseapp.BaseApp)) (Config, error) { - var ( - appBuilder *runtime.AppBuilder - txConfig client.TxConfig - legacyAmino registry.AminoRegistrar - cdc codec.Codec - interfaceRegistry codectypes.InterfaceRegistry - addressCodec address.Codec - validatorAddressCodec address.ValidatorAddressCodec - consensusAddressCodec address.ConsensusAddressCodec - ) - - if err := depinject.Inject( - depinject.Configs( - appConfig, - depinject.Supply(log.NewNopLogger()), - ), - &appBuilder, - &txConfig, - &cdc, - &legacyAmino, - &interfaceRegistry, - &addressCodec, - &validatorAddressCodec, - &consensusAddressCodec, - ); err != nil { - return Config{}, err - } - - cfg := DefaultConfig(func() TestFixture { - return TestFixture{} - }) - cfg.Codec = cdc - cfg.TxConfig = txConfig - amino, ok := legacyAmino.(*codec.LegacyAmino) - if !ok { - return Config{}, errors.New("legacyAmino must be a *codec.LegacyAmino") - } - cfg.LegacyAmino = amino - cfg.InterfaceRegistry = interfaceRegistry - cfg.GenesisState = appBuilder.DefaultGenesis() - cfg.AppConstructor = func(val ValidatorI) servertypes.Application { - // we build a unique app instance for every validator here - var appBuilder *runtime.AppBuilder - if err := depinject.Inject( - depinject.Configs( - appConfig, - depinject.Supply( - val.GetLogger(), - simtestutil.NewAppOptionsWithFlagHome(val.GetViper().GetString(flags.FlagHome)), - ), - ), - &appBuilder); err != nil { - panic(err) - } - app := appBuilder.Build( - coretesting.NewMemDB(), - nil, - append(baseappOpts, - baseapp.SetPruning(pruningtypes.NewPruningOptionsFromString(val.GetAppConfig().Pruning)), - baseapp.SetMinGasPrices(val.GetAppConfig().MinGasPrices), - baseapp.SetChainID(cfg.ChainID), - )..., - ) - - testdata.RegisterQueryServer(app.GRPCQueryRouter(), testdata.QueryImpl{}) - - if err := app.Load(true); err != nil { - panic(err) - } - - return app - } - - cfg.AddressCodec = addressCodec - cfg.ValidatorAddressCodec = validatorAddressCodec - cfg.ConsensusAddressCodec = consensusAddressCodec - - return cfg, nil -} - -type ( - // Network defines a local in-process testing network using SimApp. It can be - // configured to start any number of validators, each with its own RPC and API - // clients. Typically, this test network would be used in client and integration - // testing where user input is expected. - // - // Note, due to CometBFT constraints in regards to RPC functionality, there - // may only be one test network running at a time. Thus, any caller must be - // sure to Cleanup after testing is finished in order to allow other tests - // to create networks. In addition, only the first validator will have a valid - // RPC and API server/client. - Network struct { - Logger Logger - BaseDir string - Validators []*Validator - - Config Config - } - - // Logger is a network logger interface that exposes testnet-level Log() methods for an in-process testing network - // This is not to be confused with logging that may happen at an individual node or validator level - Logger interface { - Log(args ...interface{}) - Logf(format string, args ...interface{}) - } -) - -var ( - _ Logger = (*testing.T)(nil) - _ Logger = (*CLILogger)(nil) -) - -// CLILogger wraps a cobra.Command and provides command logging methods. -type CLILogger struct { - cmd *cobra.Command -} - -// Log logs given args. -func (s CLILogger) Log(args ...interface{}) { - s.cmd.Println(args...) -} - -// Logf logs given args according to a format specifier. -func (s CLILogger) Logf(format string, args ...interface{}) { - s.cmd.Printf(format, args...) -} - -// NewCLILogger creates a new CLILogger. -func NewCLILogger(cmd *cobra.Command) CLILogger { - return CLILogger{cmd} -} - -// New creates a new Network for integration tests or in-process testnets run via the CLI -func New(l Logger, baseDir string, cfg Config) (NetworkI, error) { - // only one caller/test can create and use a network at a time - l.Log("acquiring test network lock") - lock.Lock() - - network := &Network{ - Logger: l, - BaseDir: baseDir, - Validators: make([]*Validator, cfg.NumValidators), - Config: cfg, - } - - l.Logf("preparing test network with chain-id \"%s\"\n", cfg.ChainID) - - monikers := make([]string, cfg.NumValidators) - nodeIDs := make([]string, cfg.NumValidators) - valPubKeys := make([]cryptotypes.PubKey, cfg.NumValidators) - cmtConfigs := make([]*cmtcfg.Config, cfg.NumValidators) - - var ( - genAccounts []authtypes.GenesisAccount - genBalances []banktypes.Balance - genFiles []string - ) - - buf := bufio.NewReader(os.Stdin) - - // generate private keys, node IDs, and initial transactions - for i := 0; i < cfg.NumValidators; i++ { - appCfg := srvconfig.DefaultConfig() - appCfg.Pruning = cfg.PruningStrategy - appCfg.MinGasPrices = cfg.MinGasPrices - appCfg.API.Enable = true - appCfg.API.Swagger = false - appCfg.Telemetry.Enabled = false - - viper := viper.New() - // Create default cometbft config for each validator - cmtCfg := client.GetConfigFromViper(viper) - - cmtCfg.Consensus.TimeoutCommit = cfg.TimeoutCommit - - // Only allow the first validator to expose an RPC, API and gRPC - // server/client due to CometBFT in-process constraints. - apiAddr := "" - cmtCfg.RPC.ListenAddress = "" - appCfg.GRPC.Enable = false - apiListenAddr := "" - if i == 0 { - if cfg.APIAddress != "" { - apiListenAddr = cfg.APIAddress - } else { - if len(portPool) == 0 { - return nil, errors.New("failed to get port for API server") - } - port := <-portPool - apiListenAddr = fmt.Sprintf("tcp://127.0.0.1:%s", port) - } - - appCfg.API.Address = apiListenAddr - apiURL, err := url.Parse(apiListenAddr) - if err != nil { - return nil, err - } - apiAddr = fmt.Sprintf("http://%s:%s", apiURL.Hostname(), apiURL.Port()) - - if cfg.RPCAddress != "" { - cmtCfg.RPC.ListenAddress = cfg.RPCAddress - } else { - if len(portPool) == 0 { - return nil, errors.New("failed to get port for RPC server") - } - port := <-portPool - cmtCfg.RPC.ListenAddress = fmt.Sprintf("tcp://127.0.0.1:%s", port) - } - - if cfg.GRPCAddress != "" { - appCfg.GRPC.Address = cfg.GRPCAddress - } else { - if len(portPool) == 0 { - return nil, errors.New("failed to get port for GRPC server") - } - port := <-portPool - appCfg.GRPC.Address = fmt.Sprintf("127.0.0.1:%s", port) - } - appCfg.GRPC.Enable = true - } - - logger := log.NewNopLogger() - if cfg.EnableLogging { - logger = log.NewLogger(os.Stdout) // TODO(mr): enable selection of log destination. - } - - nodeDirName := fmt.Sprintf("node%d", i) - nodeDir := filepath.Join(network.BaseDir, nodeDirName, "simd") - clientDir := filepath.Join(network.BaseDir, nodeDirName, "simcli") - gentxsDir := filepath.Join(network.BaseDir, "gentxs") - - err := os.MkdirAll(filepath.Join(nodeDir, "config"), 0o755) - if err != nil { - return nil, err - } - - err = os.MkdirAll(clientDir, 0o755) - if err != nil { - return nil, err - } - - cmtCfg.SetRoot(nodeDir) - cmtCfg.Moniker = nodeDirName - monikers[i] = nodeDirName - - if len(portPool) == 0 { - return nil, errors.New("failed to get port for Proxy server") - } - port := <-portPool - proxyAddr := fmt.Sprintf("tcp://127.0.0.1:%s", port) - cmtCfg.ProxyApp = proxyAddr - - if len(portPool) == 0 { - return nil, errors.New("failed to get port for Proxy server") - } - port = <-portPool - p2pAddr := fmt.Sprintf("tcp://127.0.0.1:%s", port) - cmtCfg.P2P.ListenAddress = p2pAddr - cmtCfg.P2P.AddrBookStrict = false - cmtCfg.P2P.AllowDuplicateIP = true - - cmtConfigs[i] = cmtCfg - - var mnemonic string - if i < len(cfg.Mnemonics) { - mnemonic = cfg.Mnemonics[i] - } - - nodeID, pubKey, err := genutil.InitializeNodeValidatorFilesFromMnemonic(cmtCfg, mnemonic, ed25519.PrivKeyName) - if err != nil { - return nil, err - } - - nodeIDs[i] = nodeID - valPubKeys[i] = pubKey - - kb, err := keyring.New(sdk.KeyringServiceName(), keyring.BackendTest, clientDir, buf, cfg.Codec, cfg.KeyringOptions...) - if err != nil { - return nil, err - } - - keyringAlgos, _ := kb.SupportedAlgorithms() - algo, err := keyring.NewSigningAlgoFromString(cfg.SigningAlgo, keyringAlgos) - if err != nil { - return nil, err - } - - addr, secret, err := testutil.GenerateSaveCoinKey(kb, nodeDirName, mnemonic, true, algo, sdk.GetFullBIP44Path()) - if err != nil { - return nil, err - } - - // if PrintMnemonic is set to true, we print the first validator node's secret to the network's logger - // for debugging and manual testing - if cfg.PrintMnemonic && i == 0 { - printMnemonic(l, secret) - } - - info := map[string]string{"secret": secret} - infoBz, err := json.Marshal(info) - if err != nil { - return nil, err - } - - // save private key seed words - err = writeFile(fmt.Sprintf("%v.json", "key_seed"), clientDir, infoBz) - if err != nil { - return nil, err - } - - balances := sdk.NewCoins( - sdk.NewCoin(fmt.Sprintf("%stoken", nodeDirName), cfg.AccountTokens), - sdk.NewCoin(cfg.BondDenom, cfg.StakingTokens), - ) - - genFiles = append(genFiles, cmtCfg.GenesisFile()) - genBalances = append(genBalances, banktypes.Balance{Address: addr.String(), Coins: balances.Sort()}) - genAccounts = append(genAccounts, authtypes.NewBaseAccount(addr, nil, 0, 0)) - - commission, err := sdkmath.LegacyNewDecFromStr("0.5") - if err != nil { - return nil, err - } - - createValMsg, err := stakingtypes.NewMsgCreateValidator( - sdk.ValAddress(addr).String(), - valPubKeys[i], - sdk.NewCoin(cfg.BondDenom, cfg.BondedTokens), - stakingtypes.NewDescription(nodeDirName, "", "", "", "", &stakingtypes.Metadata{}), - stakingtypes.NewCommissionRates(commission, sdkmath.LegacyOneDec(), sdkmath.LegacyOneDec()), - sdkmath.OneInt(), - ) - if err != nil { - return nil, err - } - - p2pURL, err := url.Parse(p2pAddr) - if err != nil { - return nil, err - } - - memo := fmt.Sprintf("%s@%s:%s", nodeIDs[i], p2pURL.Hostname(), p2pURL.Port()) - fee := sdk.NewCoins(sdk.NewCoin(fmt.Sprintf("%stoken", nodeDirName), sdkmath.NewInt(0))) - txBuilder := cfg.TxConfig.NewTxBuilder() - err = txBuilder.SetMsgs(createValMsg) - if err != nil { - return nil, err - } - txBuilder.SetFeeAmount(fee) // Arbitrary fee - txBuilder.SetGasLimit(1000000) // Need at least 100386 - txBuilder.SetMemo(memo) - - txFactory := tx.Factory{} - txFactory = txFactory. - WithChainID(cfg.ChainID). - WithMemo(memo). - WithKeybase(kb). - WithTxConfig(cfg.TxConfig) - - clientCtx := client.Context{}. - WithKeyringDir(clientDir). - WithKeyring(kb). - WithHomeDir(cmtCfg.RootDir). - WithChainID(cfg.ChainID). - WithInterfaceRegistry(cfg.InterfaceRegistry). - WithCodec(cfg.Codec). - WithLegacyAmino(cfg.LegacyAmino). - WithTxConfig(cfg.TxConfig). - WithAccountRetriever(cfg.AccountRetriever). - WithAddressCodec(cfg.AddressCodec). - WithValidatorAddressCodec(cfg.ValidatorAddressCodec). - WithConsensusAddressCodec(cfg.ConsensusAddressCodec). - WithNodeURI(cmtCfg.RPC.ListenAddress). - WithCmdContext(context.TODO()) - - if err := tx.Sign(clientCtx, txFactory, nodeDirName, txBuilder, true); err != nil { - return nil, err - } - - txBz, err := cfg.TxConfig.TxJSONEncoder()(txBuilder.GetTx()) - if err != nil { - return nil, err - } - err = writeFile(fmt.Sprintf("%v.json", nodeDirName), gentxsDir, txBz) - if err != nil { - return nil, err - } - err = srvconfig.WriteConfigFile(filepath.Join(nodeDir, "config", "app.toml"), appCfg) - if err != nil { - return nil, err - } - - // Provide ChainID here since we can't modify it in the Comet config. - viper.Set(flags.FlagChainID, cfg.ChainID) - - network.Validators[i] = &Validator{ - AppConfig: appCfg, - clientCtx: clientCtx, - viper: viper, - logger: logger, - dir: filepath.Join(network.BaseDir, nodeDirName), - nodeID: nodeID, - pubKey: pubKey, - moniker: nodeDirName, - rPCAddress: cmtCfg.RPC.ListenAddress, - p2PAddress: cmtCfg.P2P.ListenAddress, - aPIAddress: apiAddr, - address: addr, - valAddress: sdk.ValAddress(addr), - } - } - - err := initGenFiles(cfg, genAccounts, genBalances, genFiles) - if err != nil { - return nil, err - } - err = collectGenFiles(cfg, network.Validators, cmtConfigs, network.BaseDir) - if err != nil { - return nil, err - } - - l.Log("starting test network...") - for idx, v := range network.Validators { - if err := startInProcess(cfg, v); err != nil { - return nil, err - } - l.Log("started validator", idx) - } - - height, err := network.LatestHeight() - if err != nil { - return nil, err - } - - l.Log("started test network at height:", height) - - // Ensure we cleanup in case any test was abruptly halted (e.g. SIGINT) as any - // defer in a test would not be called. - trapSignal(network.Cleanup) - - return network, nil -} - -// trapSignal traps SIGINT and SIGTERM and calls os.Exit once a signal is received. -func trapSignal(cleanupFunc func()) { - sigs := make(chan os.Signal, 1) - signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) - - go func() { - sig := <-sigs - - if cleanupFunc != nil { - cleanupFunc() - } - exitCode := 128 - - switch sig { - case syscall.SIGINT: - exitCode += int(syscall.SIGINT) - case syscall.SIGTERM: - exitCode += int(syscall.SIGTERM) - } - - os.Exit(exitCode) - }() -} - -// LatestHeight returns the latest height of the network or an error if the -// query fails or no validators exist. -func (n *Network) LatestHeight() (int64, error) { - if len(n.Validators) == 0 { - return 0, errors.New("no validators available") - } - - ticker := time.NewTicker(time.Second) - defer ticker.Stop() - - timeout := time.NewTimer(time.Second * 5) - defer timeout.Stop() - - var latestHeight atomic.Int64 - val := n.Validators[0] - queryClient := cmtservice.NewServiceClient(val.clientCtx) - - for { - select { - case <-timeout.C: - return latestHeight.Load(), errors.New("timeout exceeded waiting for block") - case <-ticker.C: - done := make(chan struct{}) - go func() { - res, err := queryClient.GetLatestBlock(context.Background(), &cmtservice.GetLatestBlockRequest{}) - if err == nil && res != nil { - latestHeight.Store(res.SdkBlock.Header.Height) - } - done <- struct{}{} - }() - select { - case <-timeout.C: - return latestHeight.Load(), errors.New("timeout exceeded waiting for block") - case <-done: - if latestHeight.Load() != 0 { - return latestHeight.Load(), nil - } - } - } - } -} - -// WaitForHeight performs a blocking check where it waits for a block to be -// committed after a given block. If that height is not reached within a timeout, -// an error is returned. Regardless, the latest height queried is returned. -func (n *Network) WaitForHeight(h int64) (int64, error) { - return n.WaitForHeightWithTimeout(h, 10*time.Second) -} - -func (n *Network) GetValidators() []ValidatorI { - var vals []ValidatorI - for _, val := range n.Validators { - vals = append(vals, val) - } - return vals -} - -// WaitForHeightWithTimeout is the same as WaitForHeight except the caller can -// provide a custom timeout. -func (n *Network) WaitForHeightWithTimeout(h int64, t time.Duration) (int64, error) { - ticker := time.NewTicker(time.Second) - defer ticker.Stop() - - timeout := time.NewTimer(t) - defer timeout.Stop() - - if len(n.Validators) == 0 { - return 0, errors.New("no validators available") - } - - var latestHeight int64 - val := n.Validators[0] - queryClient := cmtservice.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(), &cmtservice.GetLatestBlockRequest{}) - if err == nil && res != nil { - latestHeight = res.GetSdkBlock().Header.Height - if latestHeight >= h { - return latestHeight, nil - } - } - } - } -} - -// 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 { - lastBlock, err := n.LatestHeight() - if err != nil { - return err - } - - _, err = n.WaitForHeight(lastBlock + 1) - if err != nil { - return err - } - - return err -} - -// Cleanup removes the root testing (temporary) directory and stops both the -// CometBFT and API services. It allows other callers to create and start -// test networks. This method must be called when a test is finished, typically -// in a defer. -func (n *Network) Cleanup() { - defer func() { - lock.Unlock() - n.Logger.Log("released test network lock") - }() - - n.Logger.Log("cleaning up test network...") - - for _, v := range n.Validators { - // cancel the validator's context which will signal to the gRPC and API - // goroutines that they should gracefully exit. - v.cancelFn() - - if err := v.errGroup.Wait(); err != nil { - n.Logger.Log("unexpected error waiting for validator gRPC and API processes to exit", "err", err) - } - - if v.tmNode != nil && v.tmNode.IsRunning() { - if err := v.tmNode.Stop(); err != nil { - n.Logger.Log("failed to stop validator CometBFT node", "err", err) - } - } - - if v.grpcWeb != nil { - _ = v.grpcWeb.Close() - } - - if v.app != nil { - if err := v.app.Close(); err != nil { - n.Logger.Log("failed to stop validator ABCI application", "err", err) - } - } - } - - time.Sleep(100 * time.Millisecond) - - if n.Config.CleanupDir { - _ = os.RemoveAll(n.BaseDir) - } - - n.Logger.Log("finished cleaning up test network") -} - -// printMnemonic prints a provided mnemonic seed phrase on a network logger -// for debugging and manual testing -func printMnemonic(l Logger, secret string) { - lines := []string{ - "THIS MNEMONIC IS FOR TESTING PURPOSES ONLY", - "DO NOT USE IN PRODUCTION", - "", - strings.Join(strings.Fields(secret)[0:8], " "), - strings.Join(strings.Fields(secret)[8:16], " "), - strings.Join(strings.Fields(secret)[16:24], " "), - } - - lineLengths := make([]int, len(lines)) - for i, line := range lines { - lineLengths[i] = len(line) - } - - maxLineLength := 0 - for _, lineLen := range lineLengths { - if lineLen > maxLineLength { - maxLineLength = lineLen - } - } - - l.Log("\n") - l.Log(strings.Repeat("+", maxLineLength+8)) - for _, line := range lines { - l.Logf("++ %s ++\n", centerText(line, maxLineLength)) - } - l.Log(strings.Repeat("+", maxLineLength+8)) - l.Log("\n") -} - -// centerText centers text across a fixed width, filling either side with whitespace buffers -func centerText(text string, width int) string { - textLen := len(text) - leftBuffer := strings.Repeat(" ", (width-textLen)/2) - rightBuffer := strings.Repeat(" ", (width-textLen)/2+(width-textLen)%2) - - return fmt.Sprintf("%s%s%s", leftBuffer, text, rightBuffer) -} diff --git a/testutil/network/util.go b/testutil/network/util.go deleted file mode 100644 index 08ee1b0c5d99..000000000000 --- a/testutil/network/util.go +++ /dev/null @@ -1,269 +0,0 @@ -package network - -import ( - "context" - "encoding/json" - "fmt" - "net" - "os" - "path/filepath" - - cmtcfg "github.com/cometbft/cometbft/config" - cmtcrypto "github.com/cometbft/cometbft/crypto" - "github.com/cometbft/cometbft/crypto/ed25519" - "github.com/cometbft/cometbft/node" - "github.com/cometbft/cometbft/p2p" - pvm "github.com/cometbft/cometbft/privval" - "github.com/cometbft/cometbft/proxy" - "github.com/cometbft/cometbft/rpc/client/local" - cmttime "github.com/cometbft/cometbft/types/time" - "golang.org/x/sync/errgroup" - - "cosmossdk.io/log" - banktypes "cosmossdk.io/x/bank/types" - - "github.com/cosmos/cosmos-sdk/client" - "github.com/cosmos/cosmos-sdk/client/flags" - "github.com/cosmos/cosmos-sdk/server" - "github.com/cosmos/cosmos-sdk/server/api" - servergrpc "github.com/cosmos/cosmos-sdk/server/grpc" - servercmtlog "github.com/cosmos/cosmos-sdk/server/log" - "github.com/cosmos/cosmos-sdk/testutil" - authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" - "github.com/cosmos/cosmos-sdk/x/genutil" - genutiltypes "github.com/cosmos/cosmos-sdk/x/genutil/types" -) - -func startInProcess(cfg Config, val *Validator) error { - logger := val.GetLogger() - cmtCfg := client.GetConfigFromViper(val.GetViper()) - cmtCfg.Instrumentation.Prometheus = false - - if err := val.AppConfig.ValidateBasic(); err != nil { - return err - } - - nodeKey, err := p2p.LoadOrGenNodeKey(cmtCfg.NodeKeyFile()) - if err != nil { - return err - } - - app := cfg.AppConstructor(val) - val.app = app - - appGenesisProvider := func() (node.ChecksummedGenesisDoc, error) { - appGenesis, err := genutiltypes.AppGenesisFromFile(cmtCfg.GenesisFile()) - if err != nil { - return node.ChecksummedGenesisDoc{ - Sha256Checksum: []byte{}, - }, err - } - gen, err := appGenesis.ToGenesisDoc() - if err != nil { - return node.ChecksummedGenesisDoc{ - Sha256Checksum: []byte{}, - }, err - } - return node.ChecksummedGenesisDoc{GenesisDoc: gen, Sha256Checksum: make([]byte, 0)}, nil - } - - cmtApp := server.NewCometABCIWrapper(app) - pv, err := pvm.LoadOrGenFilePV(cmtCfg.PrivValidatorKeyFile(), cmtCfg.PrivValidatorStateFile(), func() (cmtcrypto.PrivKey, error) { - return ed25519.GenPrivKey(), nil - }) - if err != nil { - return err - } - - tmNode, err := node.NewNode( //resleak:notresource - context.TODO(), - cmtCfg, - pv, - nodeKey, - proxy.NewLocalClientCreator(cmtApp), - appGenesisProvider, - cmtcfg.DefaultDBProvider, - node.DefaultMetricsProvider(cmtCfg.Instrumentation), - servercmtlog.CometLoggerWrapper{Logger: logger.With("module", val.moniker)}, - ) - if err != nil { - return err - } - - if err := tmNode.Start(); err != nil { - return err - } - val.tmNode = tmNode - - if val.rPCAddress != "" { - val.rPCClient = local.New(tmNode) - } - - // We'll need a RPC client if the validator exposes a gRPC or REST endpoint. - if val.aPIAddress != "" || val.AppConfig.GRPC.Enable { - val.clientCtx = val.clientCtx. - WithClient(val.rPCClient) - - app.RegisterTxService(val.clientCtx) - app.RegisterTendermintService(val.clientCtx) - app.RegisterNodeService(val.clientCtx, *val.AppConfig) - } - - ctx := context.Background() - ctx, val.cancelFn = context.WithCancel(ctx) - val.errGroup, ctx = errgroup.WithContext(ctx) - - grpcCfg := val.AppConfig.GRPC - - if grpcCfg.Enable { - grpcSrv, err := servergrpc.NewGRPCServer(val.clientCtx, app, grpcCfg) - if err != nil { - return err - } - - // Start the gRPC server in a goroutine. Note, the provided ctx will ensure - // that the server is gracefully shut down. - val.errGroup.Go(func() error { - return servergrpc.StartGRPCServer(ctx, logger.With(log.ModuleKey, "grpc-server"), grpcCfg, grpcSrv) - }) - - val.grpc = grpcSrv - } - - if val.aPIAddress != "" { - apiSrv := api.New(val.clientCtx, logger.With(log.ModuleKey, "api-server"), val.grpc) - app.RegisterAPIRoutes(apiSrv, val.AppConfig.API) - - val.errGroup.Go(func() error { - return apiSrv.Start(ctx, *val.AppConfig) - }) - - val.api = apiSrv - } - - return nil -} - -func collectGenFiles(cfg Config, vals []*Validator, cmtConfigs []*cmtcfg.Config, outputDir string) error { - genTime := cfg.GenesisTime - if genTime.IsZero() { - genTime = cmttime.Now() - } - - for i := 0; i < cfg.NumValidators; i++ { - cmtCfg := cmtConfigs[i] - - nodeDir := filepath.Join(outputDir, vals[i].moniker, "simd") - gentxsDir := filepath.Join(outputDir, "gentxs") - - cmtCfg.Moniker = vals[i].moniker - cmtCfg.SetRoot(nodeDir) - - initCfg := genutiltypes.NewInitConfig(cfg.ChainID, gentxsDir, vals[i].nodeID, vals[i].pubKey) - - genFile := cmtCfg.GenesisFile() - appGenesis, err := genutiltypes.AppGenesisFromFile(genFile) - if err != nil { - return err - } - - appState, err := genutil.GenAppStateFromConfig(cfg.Codec, cfg.TxConfig, - cmtCfg, initCfg, appGenesis, genutiltypes.DefaultMessageValidator, - cfg.ValidatorAddressCodec, cfg.AddressCodec) - if err != nil { - return err - } - - // overwrite each validator's genesis file to have a canonical genesis time - if err := genutil.ExportGenesisFileWithTime(genFile, cfg.ChainID, nil, appState, genTime); err != nil { - return err - } - - v := vals[i].GetViper() - v.Set(flags.FlagHome, nodeDir) - v.SetConfigType("toml") - v.SetConfigName("config") - v.AddConfigPath(filepath.Join(nodeDir, "config")) - err = v.ReadInConfig() - if err != nil { - return err - } - } - - return nil -} - -func initGenFiles(cfg Config, genAccounts []authtypes.GenesisAccount, genBalances []banktypes.Balance, genFiles []string) error { - // set the accounts in the genesis state - var authGenState authtypes.GenesisState - cfg.Codec.MustUnmarshalJSON(cfg.GenesisState[testutil.AuthModuleName], &authGenState) - - accounts, err := authtypes.PackAccounts(genAccounts) - if err != nil { - return err - } - - authGenState.Accounts = append(authGenState.Accounts, accounts...) - cfg.GenesisState[testutil.AuthModuleName] = cfg.Codec.MustMarshalJSON(&authGenState) - - // set the balances in the genesis state - var bankGenState banktypes.GenesisState - cfg.Codec.MustUnmarshalJSON(cfg.GenesisState[testutil.BankModuleName], &bankGenState) - - bankGenState.Balances = append(bankGenState.Balances, genBalances...) - cfg.GenesisState[testutil.BankModuleName] = cfg.Codec.MustMarshalJSON(&bankGenState) - - appGenStateJSON, err := json.MarshalIndent(cfg.GenesisState, "", " ") - if err != nil { - return err - } - - appGenesis := genutiltypes.AppGenesis{ - ChainID: cfg.ChainID, - AppState: appGenStateJSON, - Consensus: &genutiltypes.ConsensusGenesis{ - Validators: nil, - }, - } - - // generate empty genesis files for each validator and save - for i := 0; i < cfg.NumValidators; i++ { - if err := appGenesis.SaveAs(genFiles[i]); err != nil { - return err - } - } - - return nil -} - -func writeFile(name, dir string, contents []byte) error { - file := filepath.Join(dir, name) - - if err := os.MkdirAll(dir, 0o755); err != nil { - return fmt.Errorf("could not create directory %q: %w", dir, err) - } - - if err := os.WriteFile(file, contents, 0o600); err != nil { - return err - } - - return nil -} - -// Get a free address for a test CometBFT server -// protocol is either tcp, http, etc -func FreeTCPAddr() (addr, port string, closeFn func() error, err error) { - l, err := net.Listen("tcp", "127.0.0.1:0") - if err != nil { - return "", "", nil, err - } - - closeFn = func() error { - return l.Close() - } - - portI := l.Addr().(*net.TCPAddr).Port - port = fmt.Sprintf("%d", portI) - addr = fmt.Sprintf("tcp://127.0.0.1:%s", port) - return -} diff --git a/testutil/network/validator.go b/testutil/network/validator.go deleted file mode 100644 index d3ba6b40fad5..000000000000 --- a/testutil/network/validator.go +++ /dev/null @@ -1,91 +0,0 @@ -package network - -import ( - "context" - "net/http" - - "github.com/cometbft/cometbft/node" - cmtclient "github.com/cometbft/cometbft/rpc/client" - "github.com/spf13/viper" - "golang.org/x/sync/errgroup" - "google.golang.org/grpc" - - "cosmossdk.io/log" - - "github.com/cosmos/cosmos-sdk/client" - cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" - "github.com/cosmos/cosmos-sdk/server/api" - srvconfig "github.com/cosmos/cosmos-sdk/server/config" - servertypes "github.com/cosmos/cosmos-sdk/server/types" - sdk "github.com/cosmos/cosmos-sdk/types" -) - -// Validator defines an in-process CometBFT validator node. Through this object, -// a client can make RPC and API calls and interact with any client command -// or handler. -type Validator struct { - AppConfig *srvconfig.Config - clientCtx client.Context - viper *viper.Viper - logger log.Logger - dir string - nodeID string - pubKey cryptotypes.PubKey - moniker string - aPIAddress string - rPCAddress string - p2PAddress string - address sdk.AccAddress - valAddress sdk.ValAddress - rPCClient cmtclient.Client - - app servertypes.Application - tmNode *node.Node - api *api.Server - grpc *grpc.Server - grpcWeb *http.Server - errGroup *errgroup.Group - cancelFn context.CancelFunc -} - -var _ ValidatorI = &Validator{} - -func (v *Validator) GetViper() *viper.Viper { - return v.viper -} - -func (v *Validator) GetLogger() log.Logger { - return v.logger -} - -func (v *Validator) GetClientCtx() client.Context { - return v.clientCtx -} - -func (v *Validator) GetAppConfig() *srvconfig.Config { - return v.AppConfig -} - -func (v *Validator) GetAddress() sdk.AccAddress { - return v.address -} - -func (v *Validator) GetValAddress() sdk.ValAddress { - return v.valAddress -} - -func (v *Validator) GetAPIAddress() string { - return v.aPIAddress -} - -func (v *Validator) GetRPCAddress() string { - return v.rPCAddress -} - -func (v *Validator) GetPubKey() cryptotypes.PubKey { - return v.pubKey -} - -func (v *Validator) GetMoniker() string { - return v.moniker -}