github.com/cosmos/cosmos-sdk@v0.50.10/server/start.go (about)

     1  package server
     2  
     3  import (
     4  	"bufio"
     5  	"context"
     6  	"fmt"
     7  	"io"
     8  	"net"
     9  	"os"
    10  	"runtime/pprof"
    11  	"strings"
    12  	"time"
    13  
    14  	"github.com/cometbft/cometbft/abci/server"
    15  	cmtcmd "github.com/cometbft/cometbft/cmd/cometbft/commands"
    16  	cmtcfg "github.com/cometbft/cometbft/config"
    17  	cmtjson "github.com/cometbft/cometbft/libs/json"
    18  	"github.com/cometbft/cometbft/node"
    19  	"github.com/cometbft/cometbft/p2p"
    20  	pvm "github.com/cometbft/cometbft/privval"
    21  	cmtstate "github.com/cometbft/cometbft/proto/tendermint/state"
    22  	cmtproto "github.com/cometbft/cometbft/proto/tendermint/types"
    23  	"github.com/cometbft/cometbft/proxy"
    24  	rpchttp "github.com/cometbft/cometbft/rpc/client/http"
    25  	"github.com/cometbft/cometbft/rpc/client/local"
    26  	sm "github.com/cometbft/cometbft/state"
    27  	"github.com/cometbft/cometbft/store"
    28  	cmttypes "github.com/cometbft/cometbft/types"
    29  	dbm "github.com/cosmos/cosmos-db"
    30  	"github.com/hashicorp/go-metrics"
    31  	"github.com/spf13/cobra"
    32  	"github.com/spf13/pflag"
    33  	"golang.org/x/sync/errgroup"
    34  	"google.golang.org/grpc"
    35  	"google.golang.org/grpc/credentials/insecure"
    36  
    37  	pruningtypes "cosmossdk.io/store/pruning/types"
    38  
    39  	"github.com/cosmos/cosmos-sdk/client"
    40  	"github.com/cosmos/cosmos-sdk/client/flags"
    41  	"github.com/cosmos/cosmos-sdk/codec"
    42  	"github.com/cosmos/cosmos-sdk/server/api"
    43  	serverconfig "github.com/cosmos/cosmos-sdk/server/config"
    44  	servergrpc "github.com/cosmos/cosmos-sdk/server/grpc"
    45  	servercmtlog "github.com/cosmos/cosmos-sdk/server/log"
    46  	"github.com/cosmos/cosmos-sdk/server/types"
    47  	"github.com/cosmos/cosmos-sdk/telemetry"
    48  	"github.com/cosmos/cosmos-sdk/types/mempool"
    49  	"github.com/cosmos/cosmos-sdk/version"
    50  	genutiltypes "github.com/cosmos/cosmos-sdk/x/genutil/types"
    51  )
    52  
    53  const (
    54  	// CometBFT full-node start flags
    55  	flagWithComet          = "with-comet"
    56  	flagAddress            = "address"
    57  	flagTransport          = "transport"
    58  	flagTraceStore         = "trace-store"
    59  	flagCPUProfile         = "cpu-profile"
    60  	FlagMinGasPrices       = "minimum-gas-prices"
    61  	FlagQueryGasLimit      = "query-gas-limit"
    62  	FlagHaltHeight         = "halt-height"
    63  	FlagHaltTime           = "halt-time"
    64  	FlagInterBlockCache    = "inter-block-cache"
    65  	FlagUnsafeSkipUpgrades = "unsafe-skip-upgrades"
    66  	FlagTrace              = "trace"
    67  	FlagInvCheckPeriod     = "inv-check-period"
    68  
    69  	FlagPruning             = "pruning"
    70  	FlagPruningKeepRecent   = "pruning-keep-recent"
    71  	FlagPruningInterval     = "pruning-interval"
    72  	FlagIndexEvents         = "index-events"
    73  	FlagMinRetainBlocks     = "min-retain-blocks"
    74  	FlagIAVLCacheSize       = "iavl-cache-size"
    75  	FlagDisableIAVLFastNode = "iavl-disable-fastnode"
    76  	FlagShutdownGrace       = "shutdown-grace"
    77  
    78  	// state sync-related flags
    79  	FlagStateSyncSnapshotInterval   = "state-sync.snapshot-interval"
    80  	FlagStateSyncSnapshotKeepRecent = "state-sync.snapshot-keep-recent"
    81  
    82  	// api-related flags
    83  	FlagAPIEnable             = "api.enable"
    84  	FlagAPISwagger            = "api.swagger"
    85  	FlagAPIAddress            = "api.address"
    86  	FlagAPIMaxOpenConnections = "api.max-open-connections"
    87  	FlagRPCReadTimeout        = "api.rpc-read-timeout"
    88  	FlagRPCWriteTimeout       = "api.rpc-write-timeout"
    89  	FlagRPCMaxBodyBytes       = "api.rpc-max-body-bytes"
    90  	FlagAPIEnableUnsafeCORS   = "api.enabled-unsafe-cors"
    91  
    92  	// gRPC-related flags
    93  	flagGRPCOnly      = "grpc-only"
    94  	flagGRPCEnable    = "grpc.enable"
    95  	flagGRPCAddress   = "grpc.address"
    96  	flagGRPCWebEnable = "grpc-web.enable"
    97  
    98  	// mempool flags
    99  	FlagMempoolMaxTxs = "mempool.max-txs"
   100  
   101  	// testnet keys
   102  	KeyIsTestnet             = "is-testnet"
   103  	KeyNewChainID            = "new-chain-ID"
   104  	KeyNewOpAddr             = "new-operator-addr"
   105  	KeyNewValAddr            = "new-validator-addr"
   106  	KeyUserPubKey            = "user-pub-key"
   107  	KeyTriggerTestnetUpgrade = "trigger-testnet-upgrade"
   108  )
   109  
   110  // StartCmdOptions defines options that can be customized in `StartCmdWithOptions`,
   111  type StartCmdOptions struct {
   112  	// DBOpener can be used to customize db opening, for example customize db options or support different db backends,
   113  	// default to the builtin db opener.
   114  	DBOpener func(rootDir string, backendType dbm.BackendType) (dbm.DB, error)
   115  	// PostSetup can be used to setup extra services under the same cancellable context,
   116  	// it's not called in stand-alone mode, only for in-process mode.
   117  	PostSetup func(svrCtx *Context, clientCtx client.Context, ctx context.Context, g *errgroup.Group) error
   118  	// PostSetupStandalone can be used to setup extra services under the same cancellable context,
   119  	PostSetupStandalone func(svrCtx *Context, clientCtx client.Context, ctx context.Context, g *errgroup.Group) error
   120  	// AddFlags add custom flags to start cmd
   121  	AddFlags func(cmd *cobra.Command)
   122  	// StartCommandHanlder can be used to customize the start command handler
   123  	StartCommandHandler func(svrCtx *Context, clientCtx client.Context, appCreator types.AppCreator, inProcessConsensus bool, opts StartCmdOptions) error
   124  }
   125  
   126  // StartCmd runs the service passed in, either stand-alone or in-process with
   127  // CometBFT.
   128  func StartCmd(appCreator types.AppCreator, defaultNodeHome string) *cobra.Command {
   129  	return StartCmdWithOptions(appCreator, defaultNodeHome, StartCmdOptions{})
   130  }
   131  
   132  // StartCmdWithOptions runs the service passed in, either stand-alone or in-process with
   133  // CometBFT.
   134  func StartCmdWithOptions(appCreator types.AppCreator, defaultNodeHome string, opts StartCmdOptions) *cobra.Command {
   135  	if opts.DBOpener == nil {
   136  		opts.DBOpener = openDB
   137  	}
   138  
   139  	if opts.StartCommandHandler == nil {
   140  		opts.StartCommandHandler = start
   141  	}
   142  
   143  	cmd := &cobra.Command{
   144  		Use:   "start",
   145  		Short: "Run the full node",
   146  		Long: `Run the full node application with CometBFT in or out of process. By
   147  default, the application will run with CometBFT in process.
   148  
   149  Pruning options can be provided via the '--pruning' flag or alternatively with '--pruning-keep-recent', and
   150  'pruning-interval' together.
   151  
   152  For '--pruning' the options are as follows:
   153  
   154  default: the last 362880 states are kept, pruning at 10 block intervals
   155  nothing: all historic states will be saved, nothing will be deleted (i.e. archiving node)
   156  everything: 2 latest states will be kept; pruning at 10 block intervals.
   157  custom: allow pruning options to be manually specified through 'pruning-keep-recent', and 'pruning-interval'
   158  
   159  Node halting configurations exist in the form of two flags: '--halt-height' and '--halt-time'. During
   160  the ABCI Commit phase, the node will check if the current block height is greater than or equal to
   161  the halt-height or if the current block time is greater than or equal to the halt-time. If so, the
   162  node will attempt to gracefully shutdown and the block will not be committed. In addition, the node
   163  will not be able to commit subsequent blocks.
   164  
   165  For profiling and benchmarking purposes, CPU profiling can be enabled via the '--cpu-profile' flag
   166  which accepts a path for the resulting pprof file.
   167  
   168  The node may be started in a 'query only' mode where only the gRPC and JSON HTTP
   169  API services are enabled via the 'grpc-only' flag. In this mode, CometBFT is
   170  bypassed and can be used when legacy queries are needed after an on-chain upgrade
   171  is performed. Note, when enabled, gRPC will also be automatically enabled.
   172  `,
   173  		PreRunE: func(cmd *cobra.Command, _ []string) error {
   174  			serverCtx := GetServerContextFromCmd(cmd)
   175  
   176  			// Bind flags to the Context's Viper so the app construction can set
   177  			// options accordingly.
   178  			if err := serverCtx.Viper.BindPFlags(cmd.Flags()); err != nil {
   179  				return err
   180  			}
   181  
   182  			_, err := GetPruningOptionsFromFlags(serverCtx.Viper)
   183  			return err
   184  		},
   185  		RunE: func(cmd *cobra.Command, _ []string) error {
   186  			serverCtx := GetServerContextFromCmd(cmd)
   187  			clientCtx, err := client.GetClientQueryContext(cmd)
   188  			if err != nil {
   189  				return err
   190  			}
   191  
   192  			withCMT, _ := cmd.Flags().GetBool(flagWithComet)
   193  			if !withCMT {
   194  				serverCtx.Logger.Info("starting ABCI without CometBFT")
   195  			}
   196  
   197  			err = wrapCPUProfile(serverCtx, func() error {
   198  				return opts.StartCommandHandler(serverCtx, clientCtx, appCreator, withCMT, opts)
   199  			})
   200  
   201  			serverCtx.Logger.Debug("received quit signal")
   202  			graceDuration, _ := cmd.Flags().GetDuration(FlagShutdownGrace)
   203  			if graceDuration > 0 {
   204  				serverCtx.Logger.Info("graceful shutdown start", FlagShutdownGrace, graceDuration)
   205  				<-time.After(graceDuration)
   206  				serverCtx.Logger.Info("graceful shutdown complete")
   207  			}
   208  
   209  			return err
   210  		},
   211  	}
   212  
   213  	cmd.Flags().String(flags.FlagHome, defaultNodeHome, "The application home directory")
   214  	addStartNodeFlags(cmd, opts)
   215  	return cmd
   216  }
   217  
   218  func start(svrCtx *Context, clientCtx client.Context, appCreator types.AppCreator, withCmt bool, opts StartCmdOptions) error {
   219  	svrCfg, err := getAndValidateConfig(svrCtx)
   220  	if err != nil {
   221  		return err
   222  	}
   223  
   224  	app, appCleanupFn, err := startApp(svrCtx, appCreator, opts)
   225  	if err != nil {
   226  		return err
   227  	}
   228  	defer appCleanupFn()
   229  
   230  	metrics, err := startTelemetry(svrCfg)
   231  	if err != nil {
   232  		return err
   233  	}
   234  
   235  	emitServerInfoMetrics()
   236  
   237  	if !withCmt {
   238  		return startStandAlone(svrCtx, svrCfg, clientCtx, app, metrics, opts)
   239  	}
   240  	return startInProcess(svrCtx, svrCfg, clientCtx, app, metrics, opts)
   241  }
   242  
   243  func startStandAlone(svrCtx *Context, svrCfg serverconfig.Config, clientCtx client.Context, app types.Application, metrics *telemetry.Metrics, opts StartCmdOptions) error {
   244  	addr := svrCtx.Viper.GetString(flagAddress)
   245  	transport := svrCtx.Viper.GetString(flagTransport)
   246  
   247  	cmtApp := NewCometABCIWrapper(app)
   248  	svr, err := server.NewServer(addr, transport, cmtApp)
   249  	if err != nil {
   250  		return fmt.Errorf("error creating listener: %v", err)
   251  	}
   252  
   253  	svr.SetLogger(servercmtlog.CometLoggerWrapper{Logger: svrCtx.Logger.With("module", "abci-server")})
   254  
   255  	g, ctx := getCtx(svrCtx, false)
   256  
   257  	// Add the tx service to the gRPC router. We only need to register this
   258  	// service if API or gRPC is enabled, and avoid doing so in the general
   259  	// case, because it spawns a new local CometBFT RPC client.
   260  	if svrCfg.API.Enable || svrCfg.GRPC.Enable {
   261  		// create tendermint client
   262  		// assumes the rpc listen address is where tendermint has its rpc server
   263  		rpcclient, err := rpchttp.New(svrCtx.Config.RPC.ListenAddress, "/websocket")
   264  		if err != nil {
   265  			return err
   266  		}
   267  		// re-assign for making the client available below
   268  		// do not use := to avoid shadowing clientCtx
   269  		clientCtx = clientCtx.WithClient(rpcclient)
   270  
   271  		// use the provided clientCtx to register the services
   272  		app.RegisterTxService(clientCtx)
   273  		app.RegisterTendermintService(clientCtx)
   274  		app.RegisterNodeService(clientCtx, svrCfg)
   275  	}
   276  
   277  	grpcSrv, clientCtx, err := startGrpcServer(ctx, g, svrCfg.GRPC, clientCtx, svrCtx, app)
   278  	if err != nil {
   279  		return err
   280  	}
   281  
   282  	err = startAPIServer(ctx, g, svrCfg, clientCtx, svrCtx, app, svrCtx.Config.RootDir, grpcSrv, metrics)
   283  	if err != nil {
   284  		return err
   285  	}
   286  
   287  	if opts.PostSetupStandalone != nil {
   288  		if err := opts.PostSetupStandalone(svrCtx, clientCtx, ctx, g); err != nil {
   289  			return err
   290  		}
   291  	}
   292  
   293  	g.Go(func() error {
   294  		if err := svr.Start(); err != nil {
   295  			svrCtx.Logger.Error("failed to start out-of-process ABCI server", "err", err)
   296  			return err
   297  		}
   298  
   299  		// Wait for the calling process to be canceled or close the provided context,
   300  		// so we can gracefully stop the ABCI server.
   301  		<-ctx.Done()
   302  		svrCtx.Logger.Info("stopping the ABCI server...")
   303  		return svr.Stop()
   304  	})
   305  
   306  	return g.Wait()
   307  }
   308  
   309  func startInProcess(svrCtx *Context, svrCfg serverconfig.Config, clientCtx client.Context, app types.Application,
   310  	metrics *telemetry.Metrics, opts StartCmdOptions,
   311  ) error {
   312  	cmtCfg := svrCtx.Config
   313  	gRPCOnly := svrCtx.Viper.GetBool(flagGRPCOnly)
   314  
   315  	g, ctx := getCtx(svrCtx, true)
   316  
   317  	if gRPCOnly {
   318  		// TODO: Generalize logic so that gRPC only is really in startStandAlone
   319  		svrCtx.Logger.Info("starting node in gRPC only mode; CometBFT is disabled")
   320  		svrCfg.GRPC.Enable = true
   321  	} else {
   322  		svrCtx.Logger.Info("starting node with ABCI CometBFT in-process")
   323  		tmNode, cleanupFn, err := startCmtNode(ctx, cmtCfg, app, svrCtx)
   324  		if err != nil {
   325  			return err
   326  		}
   327  		defer cleanupFn()
   328  
   329  		// Add the tx service to the gRPC router. We only need to register this
   330  		// service if API or gRPC is enabled, and avoid doing so in the general
   331  		// case, because it spawns a new local CometBFT RPC client.
   332  		if svrCfg.API.Enable || svrCfg.GRPC.Enable {
   333  			// Re-assign for making the client available below do not use := to avoid
   334  			// shadowing the clientCtx variable.
   335  			clientCtx = clientCtx.WithClient(local.New(tmNode))
   336  
   337  			app.RegisterTxService(clientCtx)
   338  			app.RegisterTendermintService(clientCtx)
   339  			app.RegisterNodeService(clientCtx, svrCfg)
   340  		}
   341  	}
   342  
   343  	grpcSrv, clientCtx, err := startGrpcServer(ctx, g, svrCfg.GRPC, clientCtx, svrCtx, app)
   344  	if err != nil {
   345  		return err
   346  	}
   347  
   348  	err = startAPIServer(ctx, g, svrCfg, clientCtx, svrCtx, app, cmtCfg.RootDir, grpcSrv, metrics)
   349  	if err != nil {
   350  		return err
   351  	}
   352  
   353  	if opts.PostSetup != nil {
   354  		if err := opts.PostSetup(svrCtx, clientCtx, ctx, g); err != nil {
   355  			return err
   356  		}
   357  	}
   358  
   359  	// wait for signal capture and gracefully return
   360  	// we are guaranteed to be waiting for the "ListenForQuitSignals" goroutine.
   361  	return g.Wait()
   362  }
   363  
   364  // TODO: Move nodeKey into being created within the function.
   365  func startCmtNode(
   366  	ctx context.Context,
   367  	cfg *cmtcfg.Config,
   368  	app types.Application,
   369  	svrCtx *Context,
   370  ) (tmNode *node.Node, cleanupFn func(), err error) {
   371  	nodeKey, err := p2p.LoadOrGenNodeKey(cfg.NodeKeyFile())
   372  	if err != nil {
   373  		return nil, cleanupFn, err
   374  	}
   375  
   376  	cmtApp := NewCometABCIWrapper(app)
   377  	tmNode, err = node.NewNodeWithContext(
   378  		ctx,
   379  		cfg,
   380  		pvm.LoadOrGenFilePV(cfg.PrivValidatorKeyFile(), cfg.PrivValidatorStateFile()),
   381  		nodeKey,
   382  		proxy.NewLocalClientCreator(cmtApp),
   383  		getGenDocProvider(cfg),
   384  		cmtcfg.DefaultDBProvider,
   385  		node.DefaultMetricsProvider(cfg.Instrumentation),
   386  		servercmtlog.CometLoggerWrapper{Logger: svrCtx.Logger},
   387  	)
   388  	if err != nil {
   389  		return tmNode, cleanupFn, err
   390  	}
   391  
   392  	if err := tmNode.Start(); err != nil {
   393  		return tmNode, cleanupFn, err
   394  	}
   395  
   396  	cleanupFn = func() {
   397  		if tmNode != nil && tmNode.IsRunning() {
   398  			_ = tmNode.Stop()
   399  		}
   400  	}
   401  
   402  	return tmNode, cleanupFn, nil
   403  }
   404  
   405  func getAndValidateConfig(svrCtx *Context) (serverconfig.Config, error) {
   406  	config, err := serverconfig.GetConfig(svrCtx.Viper)
   407  	if err != nil {
   408  		return config, err
   409  	}
   410  
   411  	if err := config.ValidateBasic(); err != nil {
   412  		return config, err
   413  	}
   414  	return config, nil
   415  }
   416  
   417  // returns a function which returns the genesis doc from the genesis file.
   418  func getGenDocProvider(cfg *cmtcfg.Config) func() (*cmttypes.GenesisDoc, error) {
   419  	return func() (*cmttypes.GenesisDoc, error) {
   420  		appGenesis, err := genutiltypes.AppGenesisFromFile(cfg.GenesisFile())
   421  		if err != nil {
   422  			return nil, err
   423  		}
   424  
   425  		return appGenesis.ToGenesisDoc()
   426  	}
   427  }
   428  
   429  func setupTraceWriter(svrCtx *Context) (traceWriter io.WriteCloser, cleanup func(), err error) {
   430  	// clean up the traceWriter when the server is shutting down
   431  	cleanup = func() {}
   432  
   433  	traceWriterFile := svrCtx.Viper.GetString(flagTraceStore)
   434  	traceWriter, err = openTraceWriter(traceWriterFile)
   435  	if err != nil {
   436  		return traceWriter, cleanup, err
   437  	}
   438  
   439  	// if flagTraceStore is not used then traceWriter is nil
   440  	if traceWriter != nil {
   441  		cleanup = func() {
   442  			if err = traceWriter.Close(); err != nil {
   443  				svrCtx.Logger.Error("failed to close trace writer", "err", err)
   444  			}
   445  		}
   446  	}
   447  
   448  	return traceWriter, cleanup, nil
   449  }
   450  
   451  func startGrpcServer(
   452  	ctx context.Context,
   453  	g *errgroup.Group,
   454  	config serverconfig.GRPCConfig,
   455  	clientCtx client.Context,
   456  	svrCtx *Context,
   457  	app types.Application,
   458  ) (*grpc.Server, client.Context, error) {
   459  	if !config.Enable {
   460  		// return grpcServer as nil if gRPC is disabled
   461  		return nil, clientCtx, nil
   462  	}
   463  	_, _, err := net.SplitHostPort(config.Address)
   464  	if err != nil {
   465  		return nil, clientCtx, err
   466  	}
   467  
   468  	maxSendMsgSize := config.MaxSendMsgSize
   469  	if maxSendMsgSize == 0 {
   470  		maxSendMsgSize = serverconfig.DefaultGRPCMaxSendMsgSize
   471  	}
   472  
   473  	maxRecvMsgSize := config.MaxRecvMsgSize
   474  	if maxRecvMsgSize == 0 {
   475  		maxRecvMsgSize = serverconfig.DefaultGRPCMaxRecvMsgSize
   476  	}
   477  
   478  	// if gRPC is enabled, configure gRPC client for gRPC gateway
   479  	grpcClient, err := grpc.Dial( //nolint: staticcheck // ignore this line for this linter
   480  		config.Address,
   481  		grpc.WithTransportCredentials(insecure.NewCredentials()),
   482  		grpc.WithDefaultCallOptions(
   483  			grpc.ForceCodec(codec.NewProtoCodec(clientCtx.InterfaceRegistry).GRPCCodec()),
   484  			grpc.MaxCallRecvMsgSize(maxRecvMsgSize),
   485  			grpc.MaxCallSendMsgSize(maxSendMsgSize),
   486  		),
   487  	)
   488  	if err != nil {
   489  		return nil, clientCtx, err
   490  	}
   491  
   492  	clientCtx = clientCtx.WithGRPCClient(grpcClient)
   493  	svrCtx.Logger.Debug("gRPC client assigned to client context", "target", config.Address)
   494  
   495  	grpcSrv, err := servergrpc.NewGRPCServer(clientCtx, app, config)
   496  	if err != nil {
   497  		return nil, clientCtx, err
   498  	}
   499  
   500  	// Start the gRPC server in a goroutine. Note, the provided ctx will ensure
   501  	// that the server is gracefully shut down.
   502  	g.Go(func() error {
   503  		return servergrpc.StartGRPCServer(ctx, svrCtx.Logger.With("module", "grpc-server"), config, grpcSrv)
   504  	})
   505  	return grpcSrv, clientCtx, nil
   506  }
   507  
   508  func startAPIServer(
   509  	ctx context.Context,
   510  	g *errgroup.Group,
   511  	svrCfg serverconfig.Config,
   512  	clientCtx client.Context,
   513  	svrCtx *Context,
   514  	app types.Application,
   515  	home string,
   516  	grpcSrv *grpc.Server,
   517  	metrics *telemetry.Metrics,
   518  ) error {
   519  	if !svrCfg.API.Enable {
   520  		return nil
   521  	}
   522  
   523  	clientCtx = clientCtx.WithHomeDir(home)
   524  
   525  	apiSrv := api.New(clientCtx, svrCtx.Logger.With("module", "api-server"), grpcSrv)
   526  	app.RegisterAPIRoutes(apiSrv, svrCfg.API)
   527  
   528  	if svrCfg.Telemetry.Enabled {
   529  		apiSrv.SetTelemetry(metrics)
   530  	}
   531  
   532  	g.Go(func() error {
   533  		return apiSrv.Start(ctx, svrCfg)
   534  	})
   535  	return nil
   536  }
   537  
   538  func startTelemetry(cfg serverconfig.Config) (*telemetry.Metrics, error) {
   539  	return telemetry.New(cfg.Telemetry)
   540  }
   541  
   542  // wrapCPUProfile starts CPU profiling, if enabled, and executes the provided
   543  // callbackFn in a separate goroutine, then will wait for that callback to
   544  // return.
   545  //
   546  // NOTE: We expect the caller to handle graceful shutdown and signal handling.
   547  func wrapCPUProfile(svrCtx *Context, callbackFn func() error) error {
   548  	if cpuProfile := svrCtx.Viper.GetString(flagCPUProfile); cpuProfile != "" {
   549  		f, err := os.Create(cpuProfile)
   550  		if err != nil {
   551  			return err
   552  		}
   553  
   554  		svrCtx.Logger.Info("starting CPU profiler", "profile", cpuProfile)
   555  
   556  		if err := pprof.StartCPUProfile(f); err != nil {
   557  			return err
   558  		}
   559  
   560  		defer func() {
   561  			svrCtx.Logger.Info("stopping CPU profiler", "profile", cpuProfile)
   562  			pprof.StopCPUProfile()
   563  
   564  			if err := f.Close(); err != nil {
   565  				svrCtx.Logger.Info("failed to close cpu-profile file", "profile", cpuProfile, "err", err.Error())
   566  			}
   567  		}()
   568  	}
   569  
   570  	return callbackFn()
   571  }
   572  
   573  // emitServerInfoMetrics emits server info related metrics using application telemetry.
   574  func emitServerInfoMetrics() {
   575  	var ls []metrics.Label
   576  
   577  	versionInfo := version.NewInfo()
   578  	if len(versionInfo.GoVersion) > 0 {
   579  		ls = append(ls, telemetry.NewLabel("go", versionInfo.GoVersion))
   580  	}
   581  	if len(versionInfo.CosmosSdkVersion) > 0 {
   582  		ls = append(ls, telemetry.NewLabel("version", versionInfo.CosmosSdkVersion))
   583  	}
   584  
   585  	if len(ls) == 0 {
   586  		return
   587  	}
   588  
   589  	telemetry.SetGaugeWithLabels([]string{"server", "info"}, 1, ls)
   590  }
   591  
   592  func getCtx(svrCtx *Context, block bool) (*errgroup.Group, context.Context) {
   593  	ctx, cancelFn := context.WithCancel(context.Background())
   594  	g, ctx := errgroup.WithContext(ctx)
   595  	// listen for quit signals so the calling parent process can gracefully exit
   596  	ListenForQuitSignals(g, block, cancelFn, svrCtx.Logger)
   597  	return g, ctx
   598  }
   599  
   600  func startApp(svrCtx *Context, appCreator types.AppCreator, opts StartCmdOptions) (app types.Application, cleanupFn func(), err error) {
   601  	traceWriter, traceCleanupFn, err := setupTraceWriter(svrCtx)
   602  	if err != nil {
   603  		return app, traceCleanupFn, err
   604  	}
   605  
   606  	home := svrCtx.Config.RootDir
   607  	db, err := opts.DBOpener(home, GetAppDBBackend(svrCtx.Viper))
   608  	if err != nil {
   609  		return app, traceCleanupFn, err
   610  	}
   611  
   612  	if isTestnet, ok := svrCtx.Viper.Get(KeyIsTestnet).(bool); ok && isTestnet {
   613  		app, err = testnetify(svrCtx, appCreator, db, traceWriter)
   614  		if err != nil {
   615  			return app, traceCleanupFn, err
   616  		}
   617  	} else {
   618  		app = appCreator(svrCtx.Logger, db, traceWriter, svrCtx.Viper)
   619  	}
   620  
   621  	cleanupFn = func() {
   622  		traceCleanupFn()
   623  		if localErr := app.Close(); localErr != nil {
   624  			svrCtx.Logger.Error(localErr.Error())
   625  		}
   626  	}
   627  	return app, cleanupFn, nil
   628  }
   629  
   630  // InPlaceTestnetCreator utilizes the provided chainID and operatorAddress as well as the local private validator key to
   631  // control the network represented in the data folder. This is useful to create testnets nearly identical to your
   632  // mainnet environment.
   633  func InPlaceTestnetCreator(testnetAppCreator types.AppCreator) *cobra.Command {
   634  	opts := StartCmdOptions{}
   635  	if opts.DBOpener == nil {
   636  		opts.DBOpener = openDB
   637  	}
   638  
   639  	if opts.StartCommandHandler == nil {
   640  		opts.StartCommandHandler = start
   641  	}
   642  
   643  	cmd := &cobra.Command{
   644  		Use:   "in-place-testnet [newChainID] [newOperatorAddress]",
   645  		Short: "Create and start a testnet from current local state",
   646  		Long: `Create and start a testnet from current local state.
   647  After utilizing this command the network will start. If the network is stopped,
   648  the normal "start" command should be used. Re-using this command on state that
   649  has already been modified by this command could result in unexpected behavior.
   650  
   651  Additionally, the first block may take up to one minute to be committed, depending
   652  on how old the block is. For instance, if a snapshot was taken weeks ago and we want
   653  to turn this into a testnet, it is possible lots of pending state needs to be committed
   654  (expiring locks, etc.). It is recommended that you should wait for this block to be committed
   655  before stopping the daemon.
   656  
   657  If the --trigger-testnet-upgrade flag is set, the upgrade handler specified by the flag will be run
   658  on the first block of the testnet.
   659  
   660  Regardless of whether the flag is set or not, if any new stores are introduced in the daemon being run,
   661  those stores will be registered in order to prevent panics. Therefore, you only need to set the flag if
   662  you want to test the upgrade handler itself.
   663  `,
   664  		Example: "in-place-testnet localosmosis osmo12smx2wdlyttvyzvzg54y2vnqwq2qjateuf7thj",
   665  		Args:    cobra.ExactArgs(2),
   666  		RunE: func(cmd *cobra.Command, args []string) error {
   667  			serverCtx := GetServerContextFromCmd(cmd)
   668  			_, err := GetPruningOptionsFromFlags(serverCtx.Viper)
   669  			if err != nil {
   670  				return err
   671  			}
   672  
   673  			clientCtx, err := client.GetClientQueryContext(cmd)
   674  			if err != nil {
   675  				return err
   676  			}
   677  
   678  			withCMT, _ := cmd.Flags().GetBool(flagWithComet)
   679  			if !withCMT {
   680  				serverCtx.Logger.Info("starting ABCI without CometBFT")
   681  			}
   682  
   683  			newChainID := args[0]
   684  			newOperatorAddress := args[1]
   685  
   686  			skipConfirmation, _ := cmd.Flags().GetBool("skip-confirmation")
   687  
   688  			if !skipConfirmation {
   689  				// Confirmation prompt to prevent accidental modification of state.
   690  				reader := bufio.NewReader(os.Stdin)
   691  				fmt.Println("This operation will modify state in your data folder and cannot be undone. Do you want to continue? (y/n)")
   692  				text, _ := reader.ReadString('\n')
   693  				response := strings.TrimSpace(strings.ToLower(text))
   694  				if response != "y" && response != "yes" {
   695  					fmt.Println("Operation canceled.")
   696  					return nil
   697  				}
   698  			}
   699  
   700  			// Set testnet keys to be used by the application.
   701  			// This is done to prevent changes to existing start API.
   702  			serverCtx.Viper.Set(KeyIsTestnet, true)
   703  			serverCtx.Viper.Set(KeyNewChainID, newChainID)
   704  			serverCtx.Viper.Set(KeyNewOpAddr, newOperatorAddress)
   705  
   706  			err = wrapCPUProfile(serverCtx, func() error {
   707  				return opts.StartCommandHandler(serverCtx, clientCtx, testnetAppCreator, withCMT, opts)
   708  			})
   709  
   710  			serverCtx.Logger.Debug("received quit signal")
   711  			graceDuration, _ := cmd.Flags().GetDuration(FlagShutdownGrace)
   712  			if graceDuration > 0 {
   713  				serverCtx.Logger.Info("graceful shutdown start", FlagShutdownGrace, graceDuration)
   714  				<-time.After(graceDuration)
   715  				serverCtx.Logger.Info("graceful shutdown complete")
   716  			}
   717  
   718  			return err
   719  		},
   720  	}
   721  
   722  	addStartNodeFlags(cmd, opts)
   723  	cmd.Flags().String(KeyTriggerTestnetUpgrade, "", "If set (example: \"v21\"), triggers the v21 upgrade handler to run on the first block of the testnet")
   724  	cmd.Flags().Bool("skip-confirmation", false, "Skip the confirmation prompt")
   725  	return cmd
   726  }
   727  
   728  // testnetify modifies both state and blockStore, allowing the provided operator address and local validator key to control the network
   729  // that the state in the data folder represents. The chainID of the local genesis file is modified to match the provided chainID.
   730  func testnetify(ctx *Context, testnetAppCreator types.AppCreator, db dbm.DB, traceWriter io.WriteCloser) (types.Application, error) {
   731  	config := ctx.Config
   732  
   733  	newChainID, ok := ctx.Viper.Get(KeyNewChainID).(string)
   734  	if !ok {
   735  		return nil, fmt.Errorf("expected string for key %s", KeyNewChainID)
   736  	}
   737  
   738  	// Modify app genesis chain ID and save to genesis file.
   739  	genFilePath := config.GenesisFile()
   740  	appGen, err := genutiltypes.AppGenesisFromFile(genFilePath)
   741  	if err != nil {
   742  		return nil, err
   743  	}
   744  	appGen.ChainID = newChainID
   745  	if err := appGen.ValidateAndComplete(); err != nil {
   746  		return nil, err
   747  	}
   748  	if err := appGen.SaveAs(genFilePath); err != nil {
   749  		return nil, err
   750  	}
   751  
   752  	// Load the comet genesis doc provider.
   753  	genDocProvider := node.DefaultGenesisDocProviderFunc(config)
   754  
   755  	// Initialize blockStore and stateDB.
   756  	blockStoreDB, err := cmtcfg.DefaultDBProvider(&cmtcfg.DBContext{ID: "blockstore", Config: config})
   757  	if err != nil {
   758  		return nil, err
   759  	}
   760  	blockStore := store.NewBlockStore(blockStoreDB)
   761  
   762  	stateDB, err := cmtcfg.DefaultDBProvider(&cmtcfg.DBContext{ID: "state", Config: config})
   763  	if err != nil {
   764  		return nil, err
   765  	}
   766  
   767  	defer blockStore.Close()
   768  	defer stateDB.Close()
   769  
   770  	privValidator := pvm.LoadOrGenFilePV(config.PrivValidatorKeyFile(), config.PrivValidatorStateFile())
   771  	userPubKey, err := privValidator.GetPubKey()
   772  	if err != nil {
   773  		return nil, err
   774  	}
   775  	validatorAddress := userPubKey.Address()
   776  
   777  	stateStore := sm.NewStore(stateDB, sm.StoreOptions{
   778  		DiscardABCIResponses: config.Storage.DiscardABCIResponses,
   779  	})
   780  
   781  	state, genDoc, err := node.LoadStateFromDBOrGenesisDocProvider(stateDB, genDocProvider)
   782  	if err != nil {
   783  		return nil, err
   784  	}
   785  
   786  	ctx.Viper.Set(KeyNewValAddr, validatorAddress)
   787  	ctx.Viper.Set(KeyUserPubKey, userPubKey)
   788  	testnetApp := testnetAppCreator(ctx.Logger, db, traceWriter, ctx.Viper)
   789  
   790  	// We need to create a temporary proxyApp to get the initial state of the application.
   791  	// Depending on how the node was stopped, the application height can differ from the blockStore height.
   792  	// This height difference changes how we go about modifying the state.
   793  	cmtApp := NewCometABCIWrapper(testnetApp)
   794  	_, context := getCtx(ctx, true)
   795  	clientCreator := proxy.NewLocalClientCreator(cmtApp)
   796  	metrics := node.DefaultMetricsProvider(cmtcfg.DefaultConfig().Instrumentation)
   797  	_, _, _, _, proxyMetrics, _, _ := metrics(genDoc.ChainID)
   798  	proxyApp := proxy.NewAppConns(clientCreator, proxyMetrics)
   799  	if err := proxyApp.Start(); err != nil {
   800  		return nil, fmt.Errorf("error starting proxy app connections: %v", err)
   801  	}
   802  	res, err := proxyApp.Query().Info(context, proxy.RequestInfo)
   803  	if err != nil {
   804  		return nil, fmt.Errorf("error calling Info: %v", err)
   805  	}
   806  	err = proxyApp.Stop()
   807  	if err != nil {
   808  		return nil, err
   809  	}
   810  	appHash := res.LastBlockAppHash
   811  	appHeight := res.LastBlockHeight
   812  
   813  	var block *cmttypes.Block
   814  	switch {
   815  	case appHeight == blockStore.Height():
   816  		block = blockStore.LoadBlock(blockStore.Height())
   817  		// If the state's last blockstore height does not match the app and blockstore height, we likely stopped with the halt height flag.
   818  		if state.LastBlockHeight != appHeight {
   819  			state.LastBlockHeight = appHeight
   820  			block.AppHash = appHash
   821  			state.AppHash = appHash
   822  		} else {
   823  			// Node was likely stopped via SIGTERM, delete the next block's seen commit
   824  			err := blockStoreDB.Delete([]byte(fmt.Sprintf("SC:%v", blockStore.Height()+1)))
   825  			if err != nil {
   826  				return nil, err
   827  			}
   828  		}
   829  	case blockStore.Height() > state.LastBlockHeight:
   830  		// This state usually occurs when we gracefully stop the node.
   831  		err = blockStore.DeleteLatestBlock()
   832  		if err != nil {
   833  			return nil, err
   834  		}
   835  		block = blockStore.LoadBlock(blockStore.Height())
   836  	default:
   837  		// If there is any other state, we just load the block
   838  		block = blockStore.LoadBlock(blockStore.Height())
   839  	}
   840  
   841  	block.ChainID = newChainID
   842  	state.ChainID = newChainID
   843  
   844  	block.LastBlockID = state.LastBlockID
   845  	block.LastCommit.BlockID = state.LastBlockID
   846  
   847  	// Create a vote from our validator
   848  	vote := cmttypes.Vote{
   849  		Type:             cmtproto.PrecommitType,
   850  		Height:           state.LastBlockHeight,
   851  		Round:            0,
   852  		BlockID:          state.LastBlockID,
   853  		Timestamp:        time.Now(),
   854  		ValidatorAddress: validatorAddress,
   855  		ValidatorIndex:   0,
   856  		Signature:        []byte{},
   857  	}
   858  
   859  	// Sign the vote, and copy the proto changes from the act of signing to the vote itself
   860  	voteProto := vote.ToProto()
   861  	err = privValidator.SignVote(newChainID, voteProto)
   862  	if err != nil {
   863  		return nil, err
   864  	}
   865  	vote.Signature = voteProto.Signature
   866  	vote.Timestamp = voteProto.Timestamp
   867  
   868  	// Modify the block's lastCommit to be signed only by our validator
   869  	block.LastCommit.Signatures[0].ValidatorAddress = validatorAddress
   870  	block.LastCommit.Signatures[0].Signature = vote.Signature
   871  	block.LastCommit.Signatures = []cmttypes.CommitSig{block.LastCommit.Signatures[0]}
   872  
   873  	// Load the seenCommit of the lastBlockHeight and modify it to be signed from our validator
   874  	seenCommit := blockStore.LoadSeenCommit(state.LastBlockHeight)
   875  	seenCommit.BlockID = state.LastBlockID
   876  	seenCommit.Round = vote.Round
   877  	seenCommit.Signatures[0].Signature = vote.Signature
   878  	seenCommit.Signatures[0].ValidatorAddress = validatorAddress
   879  	seenCommit.Signatures[0].Timestamp = vote.Timestamp
   880  	seenCommit.Signatures = []cmttypes.CommitSig{seenCommit.Signatures[0]}
   881  	err = blockStore.SaveSeenCommit(state.LastBlockHeight, seenCommit)
   882  	if err != nil {
   883  		return nil, err
   884  	}
   885  
   886  	// Create ValidatorSet struct containing just our valdiator.
   887  	newVal := &cmttypes.Validator{
   888  		Address:     validatorAddress,
   889  		PubKey:      userPubKey,
   890  		VotingPower: 900000000000000,
   891  	}
   892  	newValSet := &cmttypes.ValidatorSet{
   893  		Validators: []*cmttypes.Validator{newVal},
   894  		Proposer:   newVal,
   895  	}
   896  
   897  	// Replace all valSets in state to be the valSet with just our validator.
   898  	state.Validators = newValSet
   899  	state.LastValidators = newValSet
   900  	state.NextValidators = newValSet
   901  	state.LastHeightValidatorsChanged = blockStore.Height()
   902  
   903  	err = stateStore.Save(state)
   904  	if err != nil {
   905  		return nil, err
   906  	}
   907  
   908  	// Create a ValidatorsInfo struct to store in stateDB.
   909  	valSet, err := state.Validators.ToProto()
   910  	if err != nil {
   911  		return nil, err
   912  	}
   913  	valInfo := &cmtstate.ValidatorsInfo{
   914  		ValidatorSet:      valSet,
   915  		LastHeightChanged: state.LastBlockHeight,
   916  	}
   917  	buf, err := valInfo.Marshal()
   918  	if err != nil {
   919  		return nil, err
   920  	}
   921  
   922  	// Modfiy Validators stateDB entry.
   923  	err = stateDB.Set([]byte(fmt.Sprintf("validatorsKey:%v", blockStore.Height())), buf)
   924  	if err != nil {
   925  		return nil, err
   926  	}
   927  
   928  	// Modify LastValidators stateDB entry.
   929  	err = stateDB.Set([]byte(fmt.Sprintf("validatorsKey:%v", blockStore.Height()-1)), buf)
   930  	if err != nil {
   931  		return nil, err
   932  	}
   933  
   934  	// Modify NextValidators stateDB entry.
   935  	err = stateDB.Set([]byte(fmt.Sprintf("validatorsKey:%v", blockStore.Height()+1)), buf)
   936  	if err != nil {
   937  		return nil, err
   938  	}
   939  
   940  	// Since we modified the chainID, we set the new genesisDoc in the stateDB.
   941  	b, err := cmtjson.Marshal(genDoc)
   942  	if err != nil {
   943  		return nil, err
   944  	}
   945  	if err := stateDB.SetSync([]byte("genesisDoc"), b); err != nil {
   946  		return nil, err
   947  	}
   948  
   949  	return testnetApp, err
   950  }
   951  
   952  // addStartNodeFlags should be added to any CLI commands that start the network.
   953  func addStartNodeFlags(cmd *cobra.Command, opts StartCmdOptions) {
   954  	cmd.Flags().Bool(flagWithComet, true, "Run abci app embedded in-process with CometBFT")
   955  	cmd.Flags().String(flagAddress, "tcp://127.0.0.1:26658", "Listen address")
   956  	cmd.Flags().String(flagTransport, "socket", "Transport protocol: socket, grpc")
   957  	cmd.Flags().String(flagTraceStore, "", "Enable KVStore tracing to an output file")
   958  	cmd.Flags().String(FlagMinGasPrices, "", "Minimum gas prices to accept for transactions; Any fee in a tx must meet this minimum (e.g. 0.01photino;0.0001stake)")
   959  	cmd.Flags().Uint64(FlagQueryGasLimit, 0, "Maximum gas a Rest/Grpc query can consume. Blank and 0 imply unbounded.")
   960  	cmd.Flags().IntSlice(FlagUnsafeSkipUpgrades, []int{}, "Skip a set of upgrade heights to continue the old binary")
   961  	cmd.Flags().Uint64(FlagHaltHeight, 0, "Block height at which to gracefully halt the chain and shutdown the node")
   962  	cmd.Flags().Uint64(FlagHaltTime, 0, "Minimum block time (in Unix seconds) at which to gracefully halt the chain and shutdown the node")
   963  	cmd.Flags().Bool(FlagInterBlockCache, true, "Enable inter-block caching")
   964  	cmd.Flags().String(flagCPUProfile, "", "Enable CPU profiling and write to the provided file")
   965  	cmd.Flags().Bool(FlagTrace, false, "Provide full stack traces for errors in ABCI Log")
   966  	cmd.Flags().String(FlagPruning, pruningtypes.PruningOptionDefault, "Pruning strategy (default|nothing|everything|custom)")
   967  	cmd.Flags().Uint64(FlagPruningKeepRecent, 0, "Number of recent heights to keep on disk (ignored if pruning is not 'custom')")
   968  	cmd.Flags().Uint64(FlagPruningInterval, 0, "Height interval at which pruned heights are removed from disk (ignored if pruning is not 'custom')")
   969  	cmd.Flags().Uint(FlagInvCheckPeriod, 0, "Assert registered invariants every N blocks")
   970  	cmd.Flags().Uint64(FlagMinRetainBlocks, 0, "Minimum block height offset during ABCI commit to prune CometBFT blocks")
   971  	cmd.Flags().Bool(FlagAPIEnable, false, "Define if the API server should be enabled")
   972  	cmd.Flags().Bool(FlagAPISwagger, false, "Define if swagger documentation should automatically be registered (Note: the API must also be enabled)")
   973  	cmd.Flags().String(FlagAPIAddress, serverconfig.DefaultAPIAddress, "the API server address to listen on")
   974  	cmd.Flags().Uint(FlagAPIMaxOpenConnections, 1000, "Define the number of maximum open connections")
   975  	cmd.Flags().Uint(FlagRPCReadTimeout, 10, "Define the CometBFT RPC read timeout (in seconds)")
   976  	cmd.Flags().Uint(FlagRPCWriteTimeout, 0, "Define the CometBFT RPC write timeout (in seconds)")
   977  	cmd.Flags().Uint(FlagRPCMaxBodyBytes, 1000000, "Define the CometBFT maximum request body (in bytes)")
   978  	cmd.Flags().Bool(FlagAPIEnableUnsafeCORS, false, "Define if CORS should be enabled (unsafe - use it at your own risk)")
   979  	cmd.Flags().Bool(flagGRPCOnly, false, "Start the node in gRPC query only mode (no CometBFT process is started)")
   980  	cmd.Flags().Bool(flagGRPCEnable, true, "Define if the gRPC server should be enabled")
   981  	cmd.Flags().String(flagGRPCAddress, serverconfig.DefaultGRPCAddress, "the gRPC server address to listen on")
   982  	cmd.Flags().Bool(flagGRPCWebEnable, true, "Define if the gRPC-Web server should be enabled. (Note: gRPC must also be enabled)")
   983  	cmd.Flags().Uint64(FlagStateSyncSnapshotInterval, 0, "State sync snapshot interval")
   984  	cmd.Flags().Uint32(FlagStateSyncSnapshotKeepRecent, 2, "State sync snapshot to keep")
   985  	cmd.Flags().Bool(FlagDisableIAVLFastNode, false, "Disable fast node for IAVL tree")
   986  	cmd.Flags().Int(FlagMempoolMaxTxs, mempool.DefaultMaxTx, "Sets MaxTx value for the app-side mempool")
   987  	cmd.Flags().Duration(FlagShutdownGrace, 0*time.Second, "On Shutdown, duration to wait for resource clean up")
   988  
   989  	// support old flags name for backwards compatibility
   990  	cmd.Flags().SetNormalizeFunc(func(f *pflag.FlagSet, name string) pflag.NormalizedName {
   991  		if name == "with-tendermint" {
   992  			name = flagWithComet
   993  		}
   994  
   995  		return pflag.NormalizedName(name)
   996  	})
   997  
   998  	// add support for all CometBFT-specific command line options
   999  	cmtcmd.AddNodeFlags(cmd)
  1000  
  1001  	if opts.AddFlags != nil {
  1002  		opts.AddFlags(cmd)
  1003  	}
  1004  }