Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
11 changes: 11 additions & 0 deletions gno.land/cmd/gnoland/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ type startCfg struct {
logLevel string
logFormat string
earlyStart bool
haltHeight int64
}

func newStartCmd(io commands.IO) *commands.Command {
Expand Down Expand Up @@ -171,6 +172,13 @@ func (c *startCfg) RegisterFlags(fs *flag.FlagSet) {
false,
"[experimental] start RPC and P2P before genesis time, deferring only consensus",
)

fs.Int64Var(
&c.haltHeight,
"halt-height",
0,
"halt the node after committing this block height (0 = run indefinitely)",
)
}

func execStart(ctx context.Context, c *startCfg, io commands.IO) error {
Expand Down Expand Up @@ -274,6 +282,9 @@ func execStart(ctx context.Context, c *startCfg, io commands.IO) error {
if c.earlyStart {
opts = append(opts, node.WithEarlyStart())
}
if c.haltHeight > 0 {
opts = append(opts, node.WithHaltHeight(c.haltHeight))
}
gnoNode, err := node.DefaultNewNode(cfg, genesisPath, evsw, logger, opts...)
if err != nil {
return fmt.Errorf("unable to create the Gnoland node, %w", err)
Expand Down
17 changes: 17 additions & 0 deletions tm2/pkg/bft/consensus/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,9 @@ type ConsensusState struct {
doPrevote func(height int64, round int)
setProposal func(proposal *types.Proposal) error

// if non-zero, the node will stop after committing this height
haltHeight int64

// closed when we finish shutting down
done chan struct{}
}
Expand Down Expand Up @@ -195,6 +198,12 @@ func (cs *ConsensusState) SetLogger(l *slog.Logger) {
cs.timeoutTicker.SetLogger(l)
}

// SetHaltHeight sets the height at which the node will stop after committing.
// If set to 0, the node runs indefinitely.
func (cs *ConsensusState) SetHaltHeight(height int64) {
cs.haltHeight = height
}

// SetEventSwitch sets event bus.
func (cs *ConsensusState) SetEventSwitch(evsw events.EventSwitch) {
cs.evsw = evsw
Expand Down Expand Up @@ -1402,6 +1411,14 @@ func (cs *ConsensusState) finalizeCommit(height int64) {

// Log the telemetry
cs.logTelemetry(block)

// Check if we should halt at this height
if cs.haltHeight > 0 && height >= cs.haltHeight {
cs.Logger.Info("Halt height reached, shutting down", "height", height, "halt_height", cs.haltHeight)
if err := osm.Kill(); err != nil {
cs.Logger.Error("Failed to halt node", "err", err)
}
}
}

// -----------------------------------------------------------------------------
Expand Down
23 changes: 23 additions & 0 deletions tm2/pkg/bft/consensus/state_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1807,3 +1807,26 @@ func TestStateOutputVoteStats(t *testing.T) {
func subscribe(evsw events.EventSwitch, protoevent events.Event) <-chan events.Event {
return events.SubscribeToEvent(evsw, testSubscriber, protoevent)
}

func TestSetHaltHeight(t *testing.T) {
t.Helper()
Comment thread
moul marked this conversation as resolved.
Outdated

cs := &ConsensusState{}

// Default should be 0 (no halt)
if cs.haltHeight != 0 {
t.Fatalf("expected default haltHeight to be 0, got %d", cs.haltHeight)
}

// Set halt height
cs.SetHaltHeight(100)
if cs.haltHeight != 100 {
t.Fatalf("expected haltHeight to be 100, got %d", cs.haltHeight)
}

// Set to 0 to disable
cs.SetHaltHeight(0)
if cs.haltHeight != 0 {
t.Fatalf("expected haltHeight to be 0, got %d", cs.haltHeight)
}
}
18 changes: 17 additions & 1 deletion tm2/pkg/bft/node/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,15 @@ func WithEarlyStart() Option {
}
}

// WithHaltHeight configures the node to stop after committing the given block height.
// This is useful for coordinated chain upgrades where all validators need to stop
// at the same height before upgrading the binary.
func WithHaltHeight(height int64) Option {
return func(n *Node) {
n.haltHeight = height
}
}

// ------------------------------------------------------------------------------

// Node is the highest level interface to a full Tendermint node.
Expand Down Expand Up @@ -190,7 +199,8 @@ type Node struct {
eventStoreService *eventstore.Service
firstBlockSignal <-chan struct{}

earlyStart bool // start RPC+P2P before genesis time, defer only consensus
earlyStart bool // start RPC+P2P before genesis time, defer only consensus
haltHeight int64 // if non-zero, halt after committing this block height
}

func initDBs(config *cfg.Config, dbProvider DBProvider) (blockStore *store.BlockStore, stateDB dbm.DB, err error) {
Expand Down Expand Up @@ -577,6 +587,12 @@ func NewNode(config *cfg.Config,
option(node)
}

// Apply halt height to consensus state after options are processed
if node.haltHeight > 0 {
node.consensusState.SetHaltHeight(node.haltHeight)
logger.Info("Halt height configured", "height", node.haltHeight)
}

return node, nil
}

Expand Down
Loading