Skip to content

Commit

Permalink
fix: halt-height behavior is not deterministic (#305)
Browse files Browse the repository at this point in the history
Port of cosmos#16639

Co-authored-by: yihuang <[email protected]>
  • Loading branch information
2 people authored and JeancarloBarrios committed Sep 28, 2024
1 parent de4473c commit c006bf2
Show file tree
Hide file tree
Showing 2 changed files with 80 additions and 42 deletions.
63 changes: 21 additions & 42 deletions baseapp/abci.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,8 @@ import (
"encoding/json"
"errors"
"fmt"
"os"
"sort"
"strings"
"syscall"
"time"

abcitypes "github.com/cometbft/cometbft/abci/types"
Expand Down Expand Up @@ -180,6 +178,8 @@ func (app *BaseApp) BeginBlock(req abci.RequestBeginBlock) (res abci.ResponseBeg
))
}

app.checkHalt(req.Header.Height, req.Header.Time)

if err := app.validateHeight(req); err != nil {
panic(err)
}
Expand Down Expand Up @@ -355,6 +355,23 @@ func (app *BaseApp) deliverTxWithoutEventHistory(req abci.RequestDeliverTx) (res
}
}

// checkHalt checkes if height or time exceeds halt-height or halt-time respectively.
func (app *BaseApp) checkHalt(height int64, time time.Time) {
var halt bool
switch {
case app.haltHeight > 0 && uint64(height) > app.haltHeight:
halt = true

case app.haltTime > 0 && time.Unix() > int64(app.haltTime):
halt = true
}

if halt {
app.logger.Info("halt per configuration", "height", app.haltHeight, "time", app.haltTime)
panic(errors.New("halt application"))
}
}

// 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
Expand Down Expand Up @@ -411,51 +428,13 @@ func (app *BaseApp) CommitWithoutSnapshot() (_res abci.ResponseCommit, snapshotH
// empty/reset the deliver state
app.deliverState = nil

var halt bool

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

case app.haltTime > 0 && header.Time.Unix() >= int64(app.haltTime):
halt = true
}

if halt {
// Halt the binary and allow Tendermint to receive the ResponseCommit
// response with the commit ID hash. This will allow the node to successfully
// restart and process blocks assuming the halt configuration has been
// reset or moved to a more distant value.
app.halt()
if app.snapshotManager.ShouldTakeSnapshot(header.Height) {
snapshotHeight = header.Height
}

go app.snapshotManager.SnapshotIfApplicable(header.Height)

return res
}

// 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)
}

// Snapshot takes a snapshot of the current state and prunes any old snapshottypes.
// It should be started as a goroutine
func (app *BaseApp) Snapshot(height int64) {
Expand Down
59 changes: 59 additions & 0 deletions baseapp/abci_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ package baseapp_test

import (
"encoding/json"
"fmt"
"strings"
"testing"
"time"

abci "github.com/cometbft/cometbft/api/cometbft/abci/v1"
cmtprotocrypto "github.com/cometbft/cometbft/api/cometbft/crypto/v1"
Expand Down Expand Up @@ -2804,3 +2807,59 @@ func (ps *paramStore) Get(_ sdk.Context, key []byte, ptr interface{}) {
panic(err)
}
}

func TestABCI_HaltChain(t *testing.T) {
logger := defaultLogger()
db := dbm.NewMemDB()
name := t.Name()

testCases := []struct {
name string
haltHeight uint64
haltTime uint64
blockHeight int64
blockTime int64
expHalt bool
}{
{"default", 0, 0, 10, 0, false},
{"halt-height-edge", 10, 0, 10, 0, false},
{"halt-height", 10, 0, 11, 0, true},
{"halt-time-edge", 0, 10, 1, 10, false},
{"halt-time", 0, 10, 1, 11, true},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
defer func() {
rec := recover()
var err error
if rec != nil {
err = rec.(error)
}
if !tc.expHalt {
require.NoError(t, err)
} else {
require.Error(t, err)
require.True(t, strings.HasPrefix(err.Error(), "halt application"))
}
}()

app := NewBaseApp(
name, logger, db, nil,
SetHaltHeight(tc.haltHeight),
SetHaltTime(tc.haltTime),
)

app.InitChain(abci.RequestInitChain{
InitialHeight: tc.blockHeight,
})

app.BeginBlock(abci.RequestBeginBlock{
Header: tmproto.Header{
Height: tc.blockHeight,
Time: time.Unix(tc.blockTime, 0),
},
})
})
}
}

0 comments on commit c006bf2

Please sign in to comment.