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

Merged
merged 1 commit into from
Oct 14, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
// (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 now see your validator in one of the Cosmos Hub explorers. You are lo
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

### 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
// (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