Merge PR #4059: Add support for graceful halt via server config
This commit is contained in:
parent
4bf9d2b3ec
commit
c6cb84c558
|
@ -0,0 +1,2 @@
|
||||||
|
#3981 Add support to gracefully halt a node at a given height
|
||||||
|
via the node's `halt-height` config or CLI value.
|
|
@ -3,6 +3,7 @@ package baseapp
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -81,6 +82,9 @@ type BaseApp struct {
|
||||||
|
|
||||||
// flag for sealing options and parameters to a BaseApp
|
// flag for sealing options and parameters to a BaseApp
|
||||||
sealed bool
|
sealed bool
|
||||||
|
|
||||||
|
// height at which to halt the chain and gracefully shutdown
|
||||||
|
haltHeight uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ abci.Application = (*BaseApp)(nil)
|
var _ abci.Application = (*BaseApp)(nil)
|
||||||
|
@ -230,6 +234,10 @@ func (app *BaseApp) setMinGasPrices(gasPrices sdk.DecCoins) {
|
||||||
app.minGasPrices = gasPrices
|
app.minGasPrices = gasPrices
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (app *BaseApp) setHaltHeight(height uint64) {
|
||||||
|
app.haltHeight = height
|
||||||
|
}
|
||||||
|
|
||||||
// Router returns the router of the BaseApp.
|
// Router returns the router of the BaseApp.
|
||||||
func (app *BaseApp) Router() Router {
|
func (app *BaseApp) Router() Router {
|
||||||
if app.sealed {
|
if app.sealed {
|
||||||
|
@ -885,7 +893,13 @@ func (app *BaseApp) EndBlock(req abci.RequestEndBlock) (res abci.ResponseEndBloc
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Commit implements the ABCI interface.
|
// Commit implements the ABCI interface. It will commit all state that exists in
|
||||||
|
// the deliver state's multi-store and includes the resulting commit ID in the
|
||||||
|
// returned abci.ResponseCommit. Commit will set the check state based on the
|
||||||
|
// latest header and reset the deliver state. Also, if a non-zero halt height is
|
||||||
|
// defined in config, Commit will execute a deferred function call to check
|
||||||
|
// against that height and gracefully halt if it matches the latest committed
|
||||||
|
// height.
|
||||||
func (app *BaseApp) Commit() (res abci.ResponseCommit) {
|
func (app *BaseApp) Commit() (res abci.ResponseCommit) {
|
||||||
header := app.deliverState.ctx.BlockHeader()
|
header := app.deliverState.ctx.BlockHeader()
|
||||||
|
|
||||||
|
@ -896,13 +910,20 @@ func (app *BaseApp) Commit() (res abci.ResponseCommit) {
|
||||||
|
|
||||||
// Reset the Check state to the latest committed.
|
// Reset the Check state to the latest committed.
|
||||||
//
|
//
|
||||||
// NOTE: safe because Tendermint holds a lock on the mempool for Commit.
|
// NOTE: This is safe because Tendermint holds a lock on the mempool for
|
||||||
// Use the header from this latest block.
|
// Commit. Use the header from this latest block.
|
||||||
app.setCheckState(header)
|
app.setCheckState(header)
|
||||||
|
|
||||||
// empty/reset the deliver state
|
// empty/reset the deliver state
|
||||||
app.deliverState = nil
|
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{
|
return abci.ResponseCommit{
|
||||||
Data: commitID.Hash,
|
Data: commitID.Hash,
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,6 +28,11 @@ func SetMinGasPrices(gasPricesStr string) func(*BaseApp) {
|
||||||
return func(bap *BaseApp) { bap.setMinGasPrices(gasPrices) }
|
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) }
|
||||||
|
}
|
||||||
|
|
||||||
func (app *BaseApp) SetName(name string) {
|
func (app *BaseApp) SetName(name string) {
|
||||||
if app.sealed {
|
if app.sealed {
|
||||||
panic("SetName() on sealed BaseApp")
|
panic("SetName() on sealed BaseApp")
|
||||||
|
|
|
@ -70,6 +70,7 @@ func newApp(logger log.Logger, db dbm.DB, traceStore io.Writer) abci.Application
|
||||||
logger, db, traceStore, true, invCheckPeriod,
|
logger, db, traceStore, true, invCheckPeriod,
|
||||||
baseapp.SetPruning(store.NewPruningOptionsFromString(viper.GetString("pruning"))),
|
baseapp.SetPruning(store.NewPruningOptionsFromString(viper.GetString("pruning"))),
|
||||||
baseapp.SetMinGasPrices(viper.GetString(server.FlagMinGasPrices)),
|
baseapp.SetMinGasPrices(viper.GetString(server.FlagMinGasPrices)),
|
||||||
|
baseapp.SetHaltHeight(uint64(viper.GetInt(server.FlagHaltHeight))),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -236,6 +236,8 @@ func initTestnet(config *tmconfig.Config, cdc *codec.Codec) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Rename config file to server.toml as it's not particular to Gaia
|
||||||
|
// (REF: https://github.com/cosmos/cosmos-sdk/issues/4125).
|
||||||
gaiaConfigFilePath := filepath.Join(nodeDir, "config/gaiad.toml")
|
gaiaConfigFilePath := filepath.Join(nodeDir, "config/gaiad.toml")
|
||||||
srvconfig.WriteConfigFile(gaiaConfigFilePath, gaiaConfig)
|
srvconfig.WriteConfigFile(gaiaConfigFilePath, gaiaConfig)
|
||||||
}
|
}
|
||||||
|
|
|
@ -165,6 +165,15 @@ You should also be able to see your validator on the [Explorer](https://explorec
|
||||||
To be in the validator set, you need to have more total voting power than the 100th validator.
|
To be in the validator set, you need to have more total voting power than the 100th validator.
|
||||||
:::
|
:::
|
||||||
|
|
||||||
|
## Halting Your Validator
|
||||||
|
|
||||||
|
When attempting to perform routine maintenance or planning for an upcoming coordinated
|
||||||
|
upgrade, it can be useful to have your validator systematically and gracefully halt.
|
||||||
|
You can achieve this by either setting the `halt-height` to the height at which
|
||||||
|
you want your node to shutdown or by passing the `--halt-height` flag to `gaiad`.
|
||||||
|
The node will shutdown with a zero exit code at that given height after committing
|
||||||
|
the block.
|
||||||
|
|
||||||
## Common Problems
|
## Common Problems
|
||||||
|
|
||||||
### Problem #1: My validator has `voting_power: 0`
|
### Problem #1: My validator has `voting_power: 0`
|
||||||
|
|
|
@ -17,6 +17,10 @@ type BaseConfig struct {
|
||||||
// transaction. A transaction's fees must meet the minimum of any denomination
|
// transaction. A transaction's fees must meet the minimum of any denomination
|
||||||
// specified in this config (e.g. 0.25token1;0.0001token2).
|
// specified in this config (e.g. 0.25token1;0.0001token2).
|
||||||
MinGasPrices string `mapstructure:"minimum-gas-prices"`
|
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 uint64 `mapstructure:"halt-height"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Config defines the server's top level configuration
|
// Config defines the server's top level configuration
|
||||||
|
@ -56,6 +60,7 @@ func DefaultConfig() *Config {
|
||||||
return &Config{
|
return &Config{
|
||||||
BaseConfig{
|
BaseConfig{
|
||||||
MinGasPrices: defaultMinGasPrices,
|
MinGasPrices: defaultMinGasPrices,
|
||||||
|
HaltHeight: 0,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,10 @@ const defaultConfigTemplate = `# This is a TOML config file.
|
||||||
# transaction. A transaction's fees must meet the minimum of any denomination
|
# transaction. A transaction's fees must meet the minimum of any denomination
|
||||||
# specified in this config (e.g. 0.25token1;0.0001token2).
|
# specified in this config (e.g. 0.25token1;0.0001token2).
|
||||||
minimum-gas-prices = "{{ .BaseConfig.MinGasPrices }}"
|
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.
|
||||||
|
halt-height = {{ .BaseConfig.HaltHeight }}
|
||||||
`
|
`
|
||||||
|
|
||||||
var configTemplate *template.Template
|
var configTemplate *template.Template
|
||||||
|
|
|
@ -23,6 +23,7 @@ const (
|
||||||
flagTraceStore = "trace-store"
|
flagTraceStore = "trace-store"
|
||||||
flagPruning = "pruning"
|
flagPruning = "pruning"
|
||||||
FlagMinGasPrices = "minimum-gas-prices"
|
FlagMinGasPrices = "minimum-gas-prices"
|
||||||
|
FlagHaltHeight = "halt-height"
|
||||||
)
|
)
|
||||||
|
|
||||||
// StartCmd runs the service passed in, either stand-alone or in-process with
|
// StartCmd runs the service passed in, either stand-alone or in-process with
|
||||||
|
@ -53,6 +54,7 @@ func StartCmd(ctx *Context, appCreator AppCreator) *cobra.Command {
|
||||||
FlagMinGasPrices, "",
|
FlagMinGasPrices, "",
|
||||||
"Minimum gas prices to accept for transactions; Any fee in a tx must meet this minimum (e.g. 0.01photino;0.0001stake)",
|
"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")
|
||||||
|
|
||||||
// add support for all Tendermint-specific command line options
|
// add support for all Tendermint-specific command line options
|
||||||
tcmd.AddNodeFlags(cmd)
|
tcmd.AddNodeFlags(cmd)
|
||||||
|
|
|
@ -105,7 +105,10 @@ func interceptLoadConfig() (conf *cfg.Config, err error) {
|
||||||
conf, err = tcmd.ParseConfig() // NOTE: ParseConfig() creates dir/files as necessary.
|
conf, err = tcmd.ParseConfig() // NOTE: ParseConfig() creates dir/files as necessary.
|
||||||
}
|
}
|
||||||
|
|
||||||
// create a default gaia config file if it does not exist
|
// create a default Gaia config file if it does not exist
|
||||||
|
//
|
||||||
|
// TODO: Rename config file to server.toml as it's not particular to Gaia
|
||||||
|
// (REF: https://github.com/cosmos/cosmos-sdk/issues/4125).
|
||||||
gaiaConfigFilePath := filepath.Join(rootDir, "config/gaiad.toml")
|
gaiaConfigFilePath := filepath.Join(rootDir, "config/gaiad.toml")
|
||||||
if _, err := os.Stat(gaiaConfigFilePath); os.IsNotExist(err) {
|
if _, err := os.Stat(gaiaConfigFilePath); os.IsNotExist(err) {
|
||||||
gaiaConf, _ := config.ParseConfig()
|
gaiaConf, _ := config.ParseConfig()
|
||||||
|
|
Loading…
Reference in New Issue