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 }