Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for graceful halt via server config #4059

Merged
merged 11 commits into from
Apr 23, 2019
Original file line number Diff line number Diff line change
@@ -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.
27 changes: 24 additions & 3 deletions baseapp/baseapp.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package baseapp
import (
"fmt"
"io"
"os"
"reflect"
"runtime/debug"
"strings"
Expand Down Expand Up @@ -81,6 +82,9 @@ type BaseApp struct {

// flag for sealing options and parameters to a BaseApp
sealed bool

// height at which to halt the chain and gracefully shutdown
haltHeight uint64
}

var _ abci.Application = (*BaseApp)(nil)
Expand Down Expand Up @@ -230,6 +234,10 @@ func (app *BaseApp) setMinGasPrices(gasPrices sdk.DecCoins) {
app.minGasPrices = gasPrices
}

func (app *BaseApp) setHaltHeight(height uint64) {
app.haltHeight = height
}

// Router returns the router of the BaseApp.
func (app *BaseApp) Router() Router {
if app.sealed {
Expand Down Expand Up @@ -885,7 +893,13 @@ func (app *BaseApp) EndBlock(req abci.RequestEndBlock) (res abci.ResponseEndBloc
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) {
header := app.deliverState.ctx.BlockHeader()

Expand All @@ -896,13 +910,20 @@ func (app *BaseApp) Commit() (res abci.ResponseCommit) {

// Reset the Check state to the latest committed.
//
// NOTE: safe because Tendermint holds a lock on the mempool for Commit.
// Use the header from this latest block.
// NOTE: This is safe because Tendermint holds a lock on the mempool for
// Commit. Use the header from this latest block.
app.setCheckState(header)

// 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,
}
Expand Down
5 changes: 5 additions & 0 deletions baseapp/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ 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) }
}

func (app *BaseApp) SetName(name string) {
if app.sealed {
panic("SetName() on sealed BaseApp")
Expand Down
1 change: 1 addition & 0 deletions cmd/gaia/cmd/gaiad/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ func newApp(logger log.Logger, db dbm.DB, traceStore io.Writer) abci.Application
logger, db, traceStore, true, invCheckPeriod,
baseapp.SetPruning(store.NewPruningOptionsFromString(viper.GetString("pruning"))),
baseapp.SetMinGasPrices(viper.GetString(server.FlagMinGasPrices)),
baseapp.SetHaltHeight(uint64(viper.GetInt(server.FlagHaltHeight))),
)
}

Expand Down
2 changes: 2 additions & 0 deletions cmd/gaia/init/testnet.go
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,8 @@ func initTestnet(config *tmconfig.Config, cdc *codec.Codec) error {
return err
}

// TODO: Rename config file to server.toml as it's not particular to Gaia
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

// (REF: https://github.com/cosmos/cosmos-sdk/issues/4125).
gaiaConfigFilePath := filepath.Join(nodeDir, "config/gaiad.toml")
srvconfig.WriteConfigFile(gaiaConfigFilePath, gaiaConfig)
}
Expand Down
9 changes: 9 additions & 0 deletions docs/cosmos-hub/validators/validator-setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
:::

## 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.

Copy link
Contributor

@sabau sabau Apr 23, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be set only at boot time right? Do you think will be possible to undo this at runtime, or to set/change this flag at runtime, let's say a governance proposal that proposed to update at height X got downvoted but I started a node with this this --halt-height X

Probably even if it's true it makes sense to do it in a separate PR

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question @sabau. This value can be set at any time really but the node would have to be restarted for it to take effect. There currently isn't a way to do this at runtime. I think there is some crossover functionality between this and the software upgrade module/process we plan to have. Essentially, to do this at runtime, it would have to be parameter somewhere.

## Common Problems

### Problem #1: My validator has `voting_power: 0`
Expand Down
5 changes: 5 additions & 0 deletions server/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ type BaseConfig struct {
// transaction. A transaction's fees must meet the minimum of any denomination
// 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 uint64 `mapstructure:"halt-height"`
}

// Config defines the server's top level configuration
Expand Down Expand Up @@ -56,6 +60,7 @@ func DefaultConfig() *Config {
return &Config{
BaseConfig{
MinGasPrices: defaultMinGasPrices,
HaltHeight: 0,
},
}
}
4 changes: 4 additions & 0 deletions server/config/toml.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ const defaultConfigTemplate = `# This is a TOML config file.
# transaction. A transaction's fees must meet the minimum of any denomination
# 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.
halt-height = {{ .BaseConfig.HaltHeight }}
`

var configTemplate *template.Template
Expand Down
2 changes: 2 additions & 0 deletions server/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const (
flagTraceStore = "trace-store"
flagPruning = "pruning"
FlagMinGasPrices = "minimum-gas-prices"
FlagHaltHeight = "halt-height"
)

// StartCmd runs the service passed in, either stand-alone or in-process with
Expand Down Expand Up @@ -53,6 +54,7 @@ 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")

// add support for all Tendermint-specific command line options
tcmd.AddNodeFlags(cmd)
Expand Down
5 changes: 4 additions & 1 deletion server/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,10 @@ func interceptLoadConfig() (conf *cfg.Config, err error) {
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
alexanderbez marked this conversation as resolved.
Show resolved Hide resolved
// (REF: https://github.com/cosmos/cosmos-sdk/issues/4125).
gaiaConfigFilePath := filepath.Join(rootDir, "config/gaiad.toml")
if _, err := os.Stat(gaiaConfigFilePath); os.IsNotExist(err) {
gaiaConf, _ := config.ParseConfig()
Expand Down