From c8d84b4df4f18eb5652ffc13a0f71bb83540aff0 Mon Sep 17 00:00:00 2001 From: Alexander Bezobchuk Date: Mon, 9 Sep 2019 10:08:10 -0400 Subject: [PATCH] Merge PR #5005: Add support for halt-time --- CHANGELOG.md | 5 +++++ baseapp/abci.go | 48 +++++++++++++++++++++++++++++++++++------ baseapp/baseapp.go | 13 ++++++++--- baseapp/options.go | 11 +++++++--- server/config/config.go | 22 +++++++++++++++---- server/config/toml.go | 18 ++++++++++++++-- server/start.go | 22 ++++++++++++++++++- 7 files changed, 119 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 71d0b2569..a07ffd1cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -57,6 +57,9 @@ via the `--cpu-profile` flag. * (store) [\#4724](https://github.com/cosmos/cosmos-sdk/issues/4724) Multistore supports substore migrations upon load. New `rootmulti.Store.LoadLatestVersionAndUpgrade` method in `Baseapp` supports `StoreLoader` to enable various upgrade strategies. It no longer panics if the store to load contains substores that we didn't explicitly mount. +* [\#4979](https://github.com/cosmos/cosmos-sdk/issues/4979) Introduce a new `halt-time` config and +CLI option to the `start` command. When provided, an application will halt during `Commit` when the +block time is >= the `halt-time`. ### Improvements @@ -90,6 +93,8 @@ to detail this new feature and how state transitions occur. * (cli) [\#4763](https://github.com/cosmos/cosmos-sdk/issues/4763) Fix flag `--min-self-delegation` for staking `EditValidator` * (keys) Fix ledger custom coin type support bug +* [\#4979](https://github.com/cosmos/cosmos-sdk/issues/4979) Use `Signal(os.Interrupt)` over +`os.Exit(0)` during configured halting to allow any `defer` calls to be executed. ## [v0.37.0] - 2019-08-21 diff --git a/baseapp/abci.go b/baseapp/abci.go index ac24a06a6..7ce52664d 100644 --- a/baseapp/abci.go +++ b/baseapp/abci.go @@ -5,6 +5,7 @@ import ( "os" "sort" "strings" + "syscall" abci "github.com/tendermint/tendermint/abci/types" @@ -214,6 +215,24 @@ func (app *BaseApp) DeliverTx(req abci.RequestDeliverTx) (res abci.ResponseDeliv func (app *BaseApp) Commit() (res abci.ResponseCommit) { header := app.deliverState.ctx.BlockHeader() + var halt bool + + switch { + case app.haltHeight > 0 && uint64(header.Height) >= app.haltHeight: + halt = true + + case app.haltTime > 0 && header.Time.Unix() >= int64(app.haltTime): + halt = true + } + + if halt { + app.halt() + + // Note: State is not actually committed when halted. Logs from Tendermint + // can be ignored. + return abci.ResponseCommit{} + } + // Write the DeliverTx state which is cache-wrapped and commit the MultiStore. // The write to the DeliverTx state writes all state transitions to the root // MultiStore (app.cms) so when Commit() is called is persists those values. @@ -230,18 +249,33 @@ func (app *BaseApp) Commit() (res abci.ResponseCommit) { // empty/reset the deliver state app.deliverState = nil - defer func() { - if app.haltHeight > 0 && uint64(header.Height) == app.haltHeight { - app.logger.Info("halting node per configuration", "height", app.haltHeight) - os.Exit(0) - } - }() - return abci.ResponseCommit{ Data: commitID.Hash, } } +// halt attempts to gracefully shutdown the node via SIGINT and SIGTERM falling +// back on os.Exit if both fail. +func (app *BaseApp) halt() { + app.logger.Info("halting node per configuration", "height", app.haltHeight, "time", app.haltTime) + + p, err := os.FindProcess(os.Getpid()) + if err == nil { + // attempt cascading signals in case SIGINT fails (os dependent) + sigIntErr := p.Signal(syscall.SIGINT) + sigTermErr := p.Signal(syscall.SIGTERM) + + if sigIntErr == nil || sigTermErr == nil { + return + } + } + + // Resort to exiting immediately if the process could not be found or killed + // via SIGINT/SIGTERM signals. + app.logger.Info("failed to send SIGINT/SIGTERM; exiting...") + os.Exit(0) +} + // Query implements the ABCI interface. It delegates to CommitMultiStore if it // implements Queryable. func (app *BaseApp) Query(req abci.RequestQuery) (res abci.ResponseQuery) { diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index 23868f71c..6b91e3d3f 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -100,9 +100,12 @@ type BaseApp struct { // flag for sealing options and parameters to a BaseApp sealed bool - // height at which to halt the chain and gracefully shutdown + // block height at which to halt the chain and gracefully shutdown haltHeight uint64 + // minimum block time (in Unix seconds) at which to halt the chain and gracefully shutdown + haltTime uint64 + // application's version string appVersion string } @@ -341,8 +344,12 @@ func (app *BaseApp) setMinGasPrices(gasPrices sdk.DecCoins) { app.minGasPrices = gasPrices } -func (app *BaseApp) setHaltHeight(height uint64) { - app.haltHeight = height +func (app *BaseApp) setHaltHeight(haltHeight uint64) { + app.haltHeight = haltHeight +} + +func (app *BaseApp) setHaltTime(haltTime uint64) { + app.haltTime = haltTime } func (app *BaseApp) setInterBlockCache(cache sdk.MultiStorePersistentCache) { diff --git a/baseapp/options.go b/baseapp/options.go index db94bac33..ba4cf2692 100644 --- a/baseapp/options.go +++ b/baseapp/options.go @@ -29,9 +29,14 @@ func SetMinGasPrices(gasPricesStr string) func(*BaseApp) { return func(bap *BaseApp) { bap.setMinGasPrices(gasPrices) } } -// SetHaltHeight returns a BaseApp option function that sets the halt height. -func SetHaltHeight(height uint64) func(*BaseApp) { - return func(bap *BaseApp) { bap.setHaltHeight(height) } +// SetHaltHeight returns a BaseApp option function that sets the halt block height. +func SetHaltHeight(blockHeight uint64) func(*BaseApp) { + return func(bap *BaseApp) { bap.setHaltHeight(blockHeight) } +} + +// SetHaltTime returns a BaseApp option function that sets the halt block time. +func SetHaltTime(haltTime uint64) func(*BaseApp) { + return func(bap *BaseApp) { bap.setHaltTime(haltTime) } } // SetInterBlockCache provides a BaseApp option function that sets the diff --git a/server/config/config.go b/server/config/config.go index 150799934..a368df17b 100644 --- a/server/config/config.go +++ b/server/config/config.go @@ -18,9 +18,23 @@ type BaseConfig struct { // specified in this config (e.g. 0.25token1;0.0001token2). MinGasPrices string `mapstructure:"minimum-gas-prices"` - // HaltHeight contains a non-zero height at which a node will gracefully halt - // and shutdown that can be used to assist upgrades and testing. + // HaltHeight contains a non-zero block height at which a node will gracefully + // halt and shutdown that can be used to assist upgrades and testing. + // + // Note: State will not be committed on the corresponding height and any logs + // indicating such can be safely ignored. HaltHeight uint64 `mapstructure:"halt-height"` + + // HaltTime contains a non-zero minimum block time (in Unix seconds) at which + // a node will gracefully halt and shutdown that can be used to assist + // upgrades and testing. + // + // Note: State will not be committed on the corresponding height and any logs + // indicating such can be safely ignored. + HaltTime uint64 `mapstructure:"halt-time"` + + // InterBlockCache enables inter-block caching. + InterBlockCache bool `mapstructure:"inter-block-cache"` } // Config defines the server's top level configuration @@ -59,8 +73,8 @@ func (c *Config) GetMinGasPrices() sdk.DecCoins { func DefaultConfig() *Config { return &Config{ BaseConfig{ - MinGasPrices: defaultMinGasPrices, - HaltHeight: 0, + MinGasPrices: defaultMinGasPrices, + InterBlockCache: true, }, } } diff --git a/server/config/toml.go b/server/config/toml.go index e381ee204..403420183 100644 --- a/server/config/toml.go +++ b/server/config/toml.go @@ -18,9 +18,23 @@ const defaultConfigTemplate = `# This is a TOML config file. # specified in this config (e.g. 0.25token1;0.0001token2). minimum-gas-prices = "{{ .BaseConfig.MinGasPrices }}" -# HaltHeight contains a non-zero height at which a node will gracefully halt -# and shutdown that can be used to assist upgrades and testing. +# HaltHeight contains a non-zero block height at which a node will gracefully +# halt and shutdown that can be used to assist upgrades and testing. +# +# Note: State will not be committed on the corresponding height and any logs +# indicating such can be safely ignored. halt-height = {{ .BaseConfig.HaltHeight }} + +# HaltTime contains a non-zero minimum block time (in Unix seconds) at which +# a node will gracefully halt and shutdown that can be used to assist upgrades +# and testing. +# +# Note: State will not be committed on the corresponding height and any logs +# indicating such can be safely ignored. +halt-time = {{ .BaseConfig.HaltTime }} + +# InterBlockCache enables inter-block caching. +inter-block-cache = {{ .BaseConfig.InterBlockCache }} ` var configTemplate *template.Template diff --git a/server/start.go b/server/start.go index 83142539f..560d1614a 100644 --- a/server/start.go +++ b/server/start.go @@ -27,6 +27,7 @@ const ( flagCPUProfile = "cpu-profile" FlagMinGasPrices = "minimum-gas-prices" FlagHaltHeight = "halt-height" + FlagHaltTime = "halt-time" FlagInterBlockCache = "inter-block-cache" ) @@ -36,6 +37,24 @@ func StartCmd(ctx *Context, appCreator AppCreator) *cobra.Command { cmd := &cobra.Command{ Use: "start", Short: "Run the full node", + Long: `Run the full node application with Tendermint in or out of process. By +default, the application will run with Tendermint in process. + +Pruning options can be provided via the '--pruning' flag. The options are as follows: + +syncable: only those states not needed for state syncing will be deleted (keeps last 100 + every 10000th) +nothing: all historic states will be saved, nothing will be deleted (i.e. archiving node) +everything: all saved states will be deleted, storing only the current state + +Node halting configurations exist in the form of two flags: '--halt-height' and '--halt-time'. During +the ABCI Commit phase, the node will check if the current block height is greater than or equal to +the halt-height or if the current block time is greater than or equal to the halt-time. If so, the +node will attempt to gracefully shutdown and the block will not be committed. In addition, the node +will not be able to commit subsequent blocks. + +For profiling and benchmarking purposes, CPU profiling can be enabled via the '--cpu-profile' flag +which accepts a path for the resulting pprof file. +`, RunE: func(cmd *cobra.Command, args []string) error { if !viper.GetBool(flagWithTendermint) { ctx.Logger.Info("starting ABCI without Tendermint") @@ -58,7 +77,8 @@ func StartCmd(ctx *Context, appCreator AppCreator) *cobra.Command { FlagMinGasPrices, "", "Minimum gas prices to accept for transactions; Any fee in a tx must meet this minimum (e.g. 0.01photino;0.0001stake)", ) - cmd.Flags().Uint64(FlagHaltHeight, 0, "Height at which to gracefully halt the chain and shutdown the node") + cmd.Flags().Uint64(FlagHaltHeight, 0, "Block height at which to gracefully halt the chain and shutdown the node") + cmd.Flags().Uint64(FlagHaltTime, 0, "Minimum block time (in Unix seconds) at which to gracefully halt the chain and shutdown the node") cmd.Flags().Bool(FlagInterBlockCache, true, "Enable inter-block caching") cmd.Flags().String(flagCPUProfile, "", "Enable CPU profiling and write to the provided file")