Skip to content
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
10 changes: 9 additions & 1 deletion gno.land/cmd/gnoland/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import (
"github.com/gnolang/gno/tm2/pkg/events"
osm "github.com/gnolang/gno/tm2/pkg/os"
p2pTypes "github.com/gnolang/gno/tm2/pkg/p2p/types"

"github.com/gnolang/gno/tm2/pkg/sdk"
"github.com/gnolang/gno/tm2/pkg/std"
"github.com/gnolang/gno/tm2/pkg/telemetry"
"go.uber.org/zap/zapcore"
Expand Down Expand Up @@ -269,6 +269,14 @@ func execStart(ctx context.Context, c *startCfg, io commands.IO) error {
return fmt.Errorf("unable to create the Gnoland app, %w", err)
}

// Apply halt height from config to the application
if cfg.BaseConfig.HaltHeight > 0 {
if baseApp, ok := cfg.LocalApp.(*sdk.BaseApp); ok {
baseApp.SetHaltHeight(uint64(cfg.BaseConfig.HaltHeight))
logger.Info("Halt height configured", "height", cfg.BaseConfig.HaltHeight)
}
}

// Create a default node, with the given setup
opts := []node.Option{}
if c.earlyStart {
Expand Down
4 changes: 4 additions & 0 deletions tm2/pkg/bft/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,10 @@ type BaseConfig struct {

// TCP or UNIX socket address for the profiling server to listen on
ProfListenAddress string `toml:"prof_laddr" comment:"TCP or UNIX socket address for the profiling server to listen on"`

// If non-zero, the node will halt after committing this block height.
// Useful for coordinated chain upgrades.
HaltHeight int64 `toml:"halt_height" comment:"If non-zero, the node will halt after committing this block height.\n Useful for coordinated chain upgrades."`
}

// DefaultBaseConfig returns a default base configuration for a Tendermint node
Expand Down
54 changes: 11 additions & 43 deletions tm2/pkg/sdk/baseapp.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,9 @@ package sdk
import (
"fmt"
"log/slog"
"os"
"runtime/debug"
"sort"
"strings"
"syscall"

"github.com/gnolang/gno/tm2/pkg/amino"
abci "github.com/gnolang/gno/tm2/pkg/bft/abci/types"
Expand Down Expand Up @@ -68,9 +66,6 @@ type BaseApp struct {
// 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
}
Expand Down Expand Up @@ -526,6 +521,13 @@ func (app *BaseApp) BeginBlock(req abci.RequestBeginBlock) (res abci.ResponseBeg
panic(err)
}

// Check if we should halt before processing this block.
// We halt at the beginning of the block *after* haltHeight,
// so the block at haltHeight is fully committed.
if app.haltHeight > 0 && uint64(req.Header.GetHeight()) > app.haltHeight {
panic(fmt.Sprintf("halt height %d reached, node shutting down", app.haltHeight))
}

// Initialize the DeliverTx state. If this is the first block, it should
// already be initialized in InitChain. Otherwise app.deliverState will be
// nil, since it is reset on Commit.
Expand Down Expand Up @@ -884,24 +886,6 @@ func (app *BaseApp) EndBlock(req abci.RequestEndBlock) (res abci.ResponseEndBloc
func (app *BaseApp) Commit() (res abci.ResponseCommit) {
header := app.deliverState.ctx.BlockHeader()

var halt bool

switch {
case app.haltHeight > 0 && uint64(header.GetHeight()) >= app.haltHeight:
halt = true

case app.haltTime > 0 && header.GetTime().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.
Expand Down Expand Up @@ -929,29 +913,13 @@ func (app *BaseApp) Commit() (res abci.ResponseCommit) {

// return.
res.Data = commitID.Hash

return
}

// 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)
// SetHaltHeight sets the block height at which the node will halt after committing.
func (app *BaseApp) SetHaltHeight(height uint64) {
app.haltHeight = height
}

func (app *BaseApp) Close() error {
Expand Down
78 changes: 78 additions & 0 deletions tm2/pkg/sdk/baseapp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1273,3 +1273,81 @@ func TestGetMaximumBlockGas(t *testing.T) {
app.setConsensusParams(&abci.ConsensusParams{Block: &abci.BlockParams{MaxGas: -5000000}})
require.Panics(t, func() { app.getMaximumBlockGas() })
}

func TestHaltHeight(t *testing.T) {
t.Parallel()

tests := []struct {
name string
haltHeight uint64
blockHeight int64
shouldPanic bool
}{
{
name: "no halt configured",
haltHeight: 0,
blockHeight: 10,
shouldPanic: false,
},
{
name: "block before halt height",
haltHeight: 5,
blockHeight: 4,
shouldPanic: false,
},
{
name: "block at halt height processes normally",
haltHeight: 5,
blockHeight: 5,
shouldPanic: false,
},
{
name: "block after halt height panics",
haltHeight: 5,
blockHeight: 6,
shouldPanic: true,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()

app := setupBaseApp(t)
app.SetHaltHeight(tt.haltHeight)

// Process blocks up to the target height.
for h := int64(1); h < tt.blockHeight; h++ {
header := &bft.Header{ChainID: "test-chain", Height: h}
app.BeginBlock(abci.RequestBeginBlock{Header: header})
app.Commit()
}

// Process the target block.
header := &bft.Header{ChainID: "test-chain", Height: tt.blockHeight}
if tt.shouldPanic {
require.Panics(t, func() {
app.BeginBlock(abci.RequestBeginBlock{Header: header})
})
} else {
require.NotPanics(t, func() {
app.BeginBlock(abci.RequestBeginBlock{Header: header})
})
app.Commit()
}
})
}
}

func TestSetHaltHeight(t *testing.T) {
t.Parallel()

app := setupBaseApp(t)
require.Equal(t, uint64(0), app.haltHeight)

app.SetHaltHeight(100)
require.Equal(t, uint64(100), app.haltHeight)

app.SetHaltHeight(0)
require.Equal(t, uint64(0), app.haltHeight)
}
Loading