github.com/Finschia/finschia-sdk@v0.48.1/server/start.go (about)

     1  package server
     2  
     3  // DONTCOVER
     4  
     5  import (
     6  	"fmt"
     7  	"net/http"
     8  	"os"
     9  	"runtime/pprof"
    10  	"strings"
    11  	"time"
    12  
    13  	"github.com/spf13/cobra"
    14  	"google.golang.org/grpc"
    15  
    16  	"github.com/Finschia/ostracon/abci/server"
    17  	ostcmd "github.com/Finschia/ostracon/cmd/ostracon/commands"
    18  	"github.com/Finschia/ostracon/config"
    19  	ostos "github.com/Finschia/ostracon/libs/os"
    20  	"github.com/Finschia/ostracon/node"
    21  	"github.com/Finschia/ostracon/p2p"
    22  	pvm "github.com/Finschia/ostracon/privval"
    23  	"github.com/Finschia/ostracon/proxy"
    24  	"github.com/Finschia/ostracon/rpc/client/local"
    25  
    26  	"github.com/Finschia/finschia-sdk/client"
    27  	"github.com/Finschia/finschia-sdk/client/flags"
    28  	"github.com/Finschia/finschia-sdk/codec"
    29  	"github.com/Finschia/finschia-sdk/server/api"
    30  	serverconfig "github.com/Finschia/finschia-sdk/server/config"
    31  	servergrpc "github.com/Finschia/finschia-sdk/server/grpc"
    32  	"github.com/Finschia/finschia-sdk/server/rosetta"
    33  	crgserver "github.com/Finschia/finschia-sdk/server/rosetta/lib/server"
    34  	"github.com/Finschia/finschia-sdk/server/types"
    35  	"github.com/Finschia/finschia-sdk/store/cache"
    36  	"github.com/Finschia/finschia-sdk/store/iavl"
    37  	storetypes "github.com/Finschia/finschia-sdk/store/types"
    38  	"github.com/Finschia/finschia-sdk/telemetry"
    39  )
    40  
    41  // Ostracon full-node start flags
    42  const (
    43  	flagWithOstracon        = "with-ostracon"
    44  	flagAddress             = "address"
    45  	flagTransport           = "transport"
    46  	flagTraceStore          = "trace-store"
    47  	flagCPUProfile          = "cpu-profile"
    48  	FlagMinGasPrices        = "minimum-gas-prices"
    49  	FlagHaltHeight          = "halt-height"
    50  	FlagHaltTime            = "halt-time"
    51  	FlagInterBlockCache     = "inter-block-cache"
    52  	FlagInterBlockCacheSize = "inter-block-cache-size"
    53  	FlagUnsafeSkipUpgrades  = "unsafe-skip-upgrades"
    54  	FlagTrace               = "trace"
    55  	FlagInvCheckPeriod      = "inv-check-period"
    56  	FlagPrometheus          = "prometheus"
    57  	FlagChanCheckTxSize     = "chan-check-tx-size"
    58  
    59  	FlagPruning           = "pruning"
    60  	FlagPruningKeepRecent = "pruning-keep-recent"
    61  	FlagPruningKeepEvery  = "pruning-keep-every"
    62  	FlagPruningInterval   = "pruning-interval"
    63  	FlagIndexEvents       = "index-events"
    64  	FlagMinRetainBlocks   = "min-retain-blocks"
    65  	FlagIAVLCacheSize     = "iavl-cache-size"
    66  	FlagIAVLFastNode      = "iavl-disable-fastnode"
    67  
    68  	// state sync-related flags
    69  	FlagStateSyncSnapshotInterval   = "state-sync.snapshot-interval"
    70  	FlagStateSyncSnapshotKeepRecent = "state-sync.snapshot-keep-recent"
    71  
    72  	// gRPC-related flags
    73  	flagGRPCOnly       = "grpc-only"
    74  	flagGRPCEnable     = "grpc.enable"
    75  	flagGRPCAddress    = "grpc.address"
    76  	flagGRPCWebEnable  = "grpc-web.enable"
    77  	flagGRPCWebAddress = "grpc-web.address"
    78  )
    79  
    80  // StartCmd runs the service passed in, either stand-alone or in-process with
    81  // Ostracon.
    82  func StartCmd(appCreator types.AppCreator, defaultNodeHome string) *cobra.Command {
    83  	cmd := &cobra.Command{
    84  		Use:   "start",
    85  		Short: "Run the full node",
    86  		Long: `Run the full node application with Ostracon in or out of process. By
    87  default, the application will run with Ostracon in process.
    88  
    89  Pruning options can be provided via the '--pruning' flag or alternatively with '--pruning-keep-recent',
    90  'pruning-keep-every', and 'pruning-interval' together.
    91  
    92  For '--pruning' the options are as follows:
    93  
    94  default: the last 100 states are kept in addition to every 500th state; pruning at 10 block intervals
    95  nothing: all historic states will be saved, nothing will be deleted (i.e. archiving node)
    96  everything: all saved states will be deleted, storing only the current and previous state; pruning at 10 block intervals
    97  custom: allow pruning options to be manually specified through 'pruning-keep-recent', 'pruning-keep-every', and 'pruning-interval'
    98  
    99  Node halting configurations exist in the form of two flags: '--halt-height' and '--halt-time'. During
   100  the ABCI Commit phase, the node will check if the current block height is greater than or equal to
   101  the halt-height or if the current block time is greater than or equal to the halt-time. If so, the
   102  node will attempt to gracefully shutdown and the block will not be committed. In addition, the node
   103  will not be able to commit subsequent blocks.
   104  
   105  For profiling and benchmarking purposes, CPU profiling can be enabled via the '--cpu-profile' flag
   106  which accepts a path for the resulting pprof file.
   107  
   108  The node may be started in a 'query only' mode where only the gRPC and JSON HTTP
   109  API services are enabled via the 'grpc-only' flag. In this mode, Tendermint is
   110  bypassed and can be used when legacy queries are needed after an on-chain upgrade
   111  is performed. Note, when enabled, gRPC will also be automatically enabled.
   112  `,
   113  		PreRunE: func(cmd *cobra.Command, _ []string) error {
   114  			serverCtx := GetServerContextFromCmd(cmd)
   115  
   116  			// Bind flags to the Context's Viper so the app construction can set
   117  			// options accordingly.
   118  			if err := serverCtx.Viper.BindPFlags(cmd.Flags()); err != nil {
   119  				return err
   120  			}
   121  
   122  			_, err := GetPruningOptionsFromFlags(serverCtx.Viper)
   123  			return err
   124  		},
   125  		RunE: func(cmd *cobra.Command, _ []string) error {
   126  			serverCtx := GetServerContextFromCmd(cmd)
   127  			clientCtx, err := client.GetClientQueryContext(cmd)
   128  			if err != nil {
   129  				return err
   130  			}
   131  
   132  			withOST, _ := cmd.Flags().GetBool(flagWithOstracon)
   133  			if !withOST {
   134  				serverCtx.Logger.Info("starting ABCI without Ostracon")
   135  				return startStandAlone(serverCtx, appCreator)
   136  			}
   137  
   138  			serverCtx.Logger.Info("starting ABCI with Ostracon")
   139  
   140  			// amino is needed here for backwards compatibility of REST routes
   141  			err = startInProcess(serverCtx, clientCtx, appCreator)
   142  			errCode, ok := err.(ErrorCode)
   143  			if !ok {
   144  				return err
   145  			}
   146  
   147  			serverCtx.Logger.Debug(fmt.Sprintf("received quit signal: %d", errCode.Code))
   148  			return nil
   149  		},
   150  	}
   151  
   152  	cmd.Flags().String(flags.FlagHome, defaultNodeHome, "The application home directory")
   153  	cmd.Flags().Bool(flagWithOstracon, true, "Run abci app embedded in-process with ostracon")
   154  	cmd.Flags().String(flagAddress, "tcp://0.0.0.0:26658", "Listen address")
   155  	cmd.Flags().String(flagTransport, "socket", "Transport protocol: socket, grpc")
   156  	cmd.Flags().String(flagTraceStore, "", "Enable KVStore tracing to an output file")
   157  	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)")
   158  	cmd.Flags().IntSlice(FlagUnsafeSkipUpgrades, []int{}, "Skip a set of upgrade heights to continue the old binary")
   159  	cmd.Flags().Uint64(FlagHaltHeight, 0, "Block height at which to gracefully halt the chain and shutdown the node")
   160  	cmd.Flags().Uint64(FlagHaltTime, 0, "Minimum block time (in Unix seconds) at which to gracefully halt the chain and shutdown the node")
   161  	cmd.Flags().Bool(FlagInterBlockCache, true, "Enable inter-block caching")
   162  	cmd.Flags().Int(FlagInterBlockCacheSize, cache.DefaultCommitKVStoreCacheSize, "The maximum bytes size of the inter-block cache")
   163  	cmd.Flags().Int(FlagIAVLCacheSize, iavl.DefaultIAVLCacheSize, "The maximum units size of the iavl node cache (1 unit is 128 bytes).")
   164  	cmd.Flags().String(flagCPUProfile, "", "Enable CPU profiling and write to the provided file")
   165  	cmd.Flags().Bool(FlagTrace, false, "Provide full stack traces for errors in ABCI Log")
   166  	cmd.Flags().String(FlagPruning, storetypes.PruningOptionDefault, "Pruning strategy (default|nothing|everything|custom)")
   167  	cmd.Flags().Uint64(FlagPruningKeepRecent, 0, "Number of recent heights to keep on disk (ignored if pruning is not 'custom')")
   168  	cmd.Flags().Uint64(FlagPruningKeepEvery, 0, "Offset heights to keep on disk after 'keep-every' (ignored if pruning is not 'custom')")
   169  	cmd.Flags().Uint64(FlagPruningInterval, 0, "Height interval at which pruned heights are removed from disk (ignored if pruning is not 'custom')")
   170  	cmd.Flags().Uint(FlagInvCheckPeriod, 0, "Assert registered invariants every N blocks")
   171  	cmd.Flags().Uint64(FlagMinRetainBlocks, 0, "Minimum block height offset during ABCI commit to prune Ostracon blocks")
   172  
   173  	cmd.Flags().Bool(flagGRPCOnly, false, "Start the node in gRPC query only mode (no Tendermint process is started)")
   174  	cmd.Flags().Bool(flagGRPCEnable, true, "Define if the gRPC server should be enabled")
   175  	cmd.Flags().String(flagGRPCAddress, serverconfig.DefaultGRPCAddress, "the gRPC server address to listen on")
   176  
   177  	cmd.Flags().Bool(flagGRPCWebEnable, true, "Define if the gRPC-Web server should be enabled. (Note: gRPC must also be enabled.)")
   178  	cmd.Flags().String(flagGRPCWebAddress, serverconfig.DefaultGRPCWebAddress, "The gRPC-Web server address to listen on")
   179  
   180  	cmd.Flags().Uint64(FlagStateSyncSnapshotInterval, 0, "State sync snapshot interval")
   181  	cmd.Flags().Uint32(FlagStateSyncSnapshotKeepRecent, 2, "State sync snapshot to keep")
   182  
   183  	cmd.Flags().Bool(FlagIAVLFastNode, true, "Enable fast node for IAVL tree")
   184  
   185  	cmd.Flags().Bool(FlagPrometheus, false, "Enable prometheus metric for app")
   186  
   187  	cmd.Flags().Uint(FlagChanCheckTxSize, serverconfig.DefaultChanCheckTxSize, "The size of the channel check tx")
   188  
   189  	// add support for all Ostracon-specific command line options
   190  	ostcmd.AddNodeFlags(cmd)
   191  	return cmd
   192  }
   193  
   194  func startStandAlone(ctx *Context, appCreator types.AppCreator) error {
   195  	addr := ctx.Viper.GetString(flagAddress)
   196  	transport := ctx.Viper.GetString(flagTransport)
   197  	home := ctx.Viper.GetString(flags.FlagHome)
   198  
   199  	db, err := openDB(home)
   200  	if err != nil {
   201  		return err
   202  	}
   203  
   204  	traceWriterFile := ctx.Viper.GetString(flagTraceStore)
   205  	traceWriter, err := openTraceWriter(traceWriterFile)
   206  	if err != nil {
   207  		return err
   208  	}
   209  
   210  	app := appCreator(ctx.Logger, db, traceWriter, ctx.Viper)
   211  
   212  	config, err := serverconfig.GetConfig(ctx.Viper)
   213  	if err != nil {
   214  		return err
   215  	}
   216  
   217  	_, err = startTelemetry(config)
   218  	if err != nil {
   219  		return err
   220  	}
   221  
   222  	svr, err := server.NewServer(addr, transport, app)
   223  	if err != nil {
   224  		return fmt.Errorf("error creating listener: %v", err)
   225  	}
   226  
   227  	svr.SetLogger(ctx.Logger.With("module", "abci-server"))
   228  
   229  	err = svr.Start()
   230  	if err != nil {
   231  		ostos.Exit(err.Error())
   232  	}
   233  
   234  	defer func() {
   235  		if err = svr.Stop(); err != nil {
   236  			ostos.Exit(err.Error())
   237  		}
   238  	}()
   239  
   240  	// Wait for SIGINT or SIGTERM signal
   241  	return WaitForQuitSignals()
   242  }
   243  
   244  func startInProcess(ctx *Context, clientCtx client.Context, appCreator types.AppCreator) error {
   245  	cfg := ctx.Config
   246  	home := cfg.RootDir
   247  	var cpuProfileCleanup func()
   248  
   249  	if cpuProfile := ctx.Viper.GetString(flagCPUProfile); cpuProfile != "" {
   250  		f, err := os.Create(cpuProfile)
   251  		if err != nil {
   252  			return err
   253  		}
   254  
   255  		ctx.Logger.Info("starting CPU profiler", "profile", cpuProfile)
   256  		if err := pprof.StartCPUProfile(f); err != nil {
   257  			return err
   258  		}
   259  
   260  		cpuProfileCleanup = func() {
   261  			ctx.Logger.Info("stopping CPU profiler", "profile", cpuProfile)
   262  			pprof.StopCPUProfile()
   263  			f.Close()
   264  		}
   265  	}
   266  
   267  	traceWriterFile := ctx.Viper.GetString(flagTraceStore)
   268  	db, err := openDB(home)
   269  	if err != nil {
   270  		return err
   271  	}
   272  
   273  	traceWriter, err := openTraceWriter(traceWriterFile)
   274  	if err != nil {
   275  		return err
   276  	}
   277  
   278  	config, err := serverconfig.GetConfig(ctx.Viper)
   279  	if err != nil {
   280  		return err
   281  	}
   282  
   283  	if err := config.ValidateBasic(); err != nil {
   284  		ctx.Logger.Error("WARNING: The minimum-gas-prices config in app.toml is set to the empty string. " +
   285  			"This defaults to 0 in the current version, but will error in the next version " +
   286  			"(SDK v0.45). Please explicitly put the desired minimum-gas-prices in your app.toml.")
   287  	}
   288  
   289  	app := appCreator(ctx.Logger, db, traceWriter, ctx.Viper)
   290  
   291  	nodeKey, err := p2p.LoadOrGenNodeKey(cfg.NodeKeyFile())
   292  	if err != nil {
   293  		return err
   294  	}
   295  
   296  	genDocProvider := node.DefaultGenesisDocProviderFunc(cfg)
   297  
   298  	var (
   299  		ocNode   *node.Node
   300  		gRPCOnly = ctx.Viper.GetBool(flagGRPCOnly)
   301  	)
   302  
   303  	if gRPCOnly {
   304  		ctx.Logger.Info("starting node in gRPC only mode; Ostracon is disabled")
   305  		config.GRPC.Enable = true
   306  	} else {
   307  		ctx.Logger.Info("starting node with ABCI Ostracon in-process")
   308  
   309  		pv := genPvFileOnlyWhenKmsAddressEmpty(cfg)
   310  
   311  		ocNode, err = node.NewNode(
   312  			cfg,
   313  			pv,
   314  			nodeKey,
   315  			proxy.NewLocalClientCreator(app),
   316  			genDocProvider,
   317  			node.DefaultDBProvider,
   318  			node.DefaultMetricsProvider(cfg.Instrumentation),
   319  			ctx.Logger,
   320  		)
   321  		if err != nil {
   322  			return err
   323  		}
   324  		ctx.Logger.Debug("initialization: ocNode created")
   325  		if err := ocNode.Start(); err != nil {
   326  			return err
   327  		}
   328  		ctx.Logger.Debug("initialization: ocNode started")
   329  	}
   330  
   331  	// Add the tx service to the gRPC router. We only need to register this
   332  	// service if API or gRPC is enabled, and avoid doing so in the general
   333  	// case, because it spawns a new local ostracon RPC client.
   334  	if (config.API.Enable || config.GRPC.Enable) && ocNode != nil {
   335  		clientCtx = clientCtx.WithClient(local.New(ocNode))
   336  
   337  		app.RegisterTxService(clientCtx)
   338  		app.RegisterTendermintService(clientCtx)
   339  
   340  		if a, ok := app.(types.ApplicationQueryService); ok {
   341  			a.RegisterNodeService(clientCtx)
   342  		}
   343  	}
   344  
   345  	metrics, err := startTelemetry(config)
   346  	if err != nil {
   347  		return err
   348  	}
   349  
   350  	var apiSrv *api.Server
   351  	if config.API.Enable {
   352  		genDoc, err := genDocProvider()
   353  		if err != nil {
   354  			return err
   355  		}
   356  
   357  		clientCtx := clientCtx.WithHomeDir(home).WithChainID(genDoc.ChainID)
   358  
   359  		apiSrv = api.New(clientCtx, ctx.Logger.With("module", "api-server"))
   360  		app.RegisterAPIRoutes(apiSrv, config.API)
   361  		if config.Telemetry.Enabled {
   362  			apiSrv.SetTelemetry(metrics)
   363  		}
   364  		errCh := make(chan error)
   365  
   366  		go func() {
   367  			if err := apiSrv.Start(config); err != nil {
   368  				errCh <- err
   369  			}
   370  		}()
   371  
   372  		select {
   373  		case err := <-errCh:
   374  			return err
   375  
   376  		case <-time.After(types.ServerStartTime): // assume server started successfully
   377  		}
   378  	}
   379  
   380  	var (
   381  		grpcSrv    *grpc.Server
   382  		grpcWebSrv *http.Server
   383  	)
   384  
   385  	if config.GRPC.Enable {
   386  		grpcSrv, err = servergrpc.StartGRPCServer(clientCtx, app, config.GRPC)
   387  		if err != nil {
   388  			return err
   389  		}
   390  
   391  		if config.GRPCWeb.Enable {
   392  			grpcWebSrv, err = servergrpc.StartGRPCWeb(grpcSrv, config)
   393  			if err != nil {
   394  				ctx.Logger.Error("failed to start grpc-web http server: ", err)
   395  				return err
   396  			}
   397  		}
   398  	}
   399  
   400  	// At this point it is safe to block the process if we're in gRPC only mode as
   401  	// we do not need to start Rosetta or handle any Tendermint related processes.
   402  	if gRPCOnly {
   403  		// wait for signal capture and gracefully return
   404  		return WaitForQuitSignals()
   405  	}
   406  
   407  	var rosettaSrv crgserver.Server
   408  	if config.Rosetta.Enable {
   409  		offlineMode := config.Rosetta.Offline
   410  
   411  		// If GRPC is not enabled rosetta cannot work in online mode, so it works in
   412  		// offline mode.
   413  		if !config.GRPC.Enable {
   414  			offlineMode = true
   415  		}
   416  
   417  		conf := &rosetta.Config{
   418  			Blockchain:        config.Rosetta.Blockchain,
   419  			Network:           config.Rosetta.Network,
   420  			TendermintRPC:     ctx.Config.RPC.ListenAddress,
   421  			GRPCEndpoint:      config.GRPC.Address,
   422  			Addr:              config.Rosetta.Address,
   423  			Retries:           config.Rosetta.Retries,
   424  			Offline:           offlineMode,
   425  			Codec:             clientCtx.Codec.(*codec.ProtoCodec),
   426  			InterfaceRegistry: clientCtx.InterfaceRegistry,
   427  		}
   428  
   429  		rosettaSrv, err = rosetta.ServerFromConfig(conf)
   430  		if err != nil {
   431  			return err
   432  		}
   433  
   434  		errCh := make(chan error)
   435  		go func() {
   436  			if err := rosettaSrv.Start(); err != nil {
   437  				errCh <- err
   438  			}
   439  		}()
   440  
   441  		select {
   442  		case err := <-errCh:
   443  			return err
   444  
   445  		case <-time.After(types.ServerStartTime): // assume server started successfully
   446  		}
   447  	}
   448  
   449  	defer func() {
   450  		if ocNode.IsRunning() {
   451  			_ = ocNode.Stop()
   452  		}
   453  
   454  		if cpuProfileCleanup != nil {
   455  			cpuProfileCleanup()
   456  		}
   457  
   458  		if apiSrv != nil {
   459  			_ = apiSrv.Close()
   460  		}
   461  
   462  		if grpcSrv != nil {
   463  			grpcSrv.Stop()
   464  			if grpcWebSrv != nil {
   465  				grpcWebSrv.Close()
   466  			}
   467  		}
   468  
   469  		ctx.Logger.Info("exiting...")
   470  	}()
   471  
   472  	// wait for signal capture and gracefully return
   473  	return WaitForQuitSignals()
   474  }
   475  
   476  func genPvFileOnlyWhenKmsAddressEmpty(cfg *config.Config) *pvm.FilePV {
   477  	if len(strings.TrimSpace(cfg.PrivValidatorListenAddr)) == 0 {
   478  		return pvm.LoadOrGenFilePV(cfg.PrivValidatorKeyFile(), cfg.PrivValidatorStateFile())
   479  	}
   480  	return nil
   481  }
   482  
   483  func startTelemetry(cfg serverconfig.Config) (*telemetry.Metrics, error) {
   484  	if !cfg.Telemetry.Enabled {
   485  		return nil, nil
   486  	}
   487  	return telemetry.New(cfg.Telemetry)
   488  }