diff --git a/cmd/rollapp/start/start.go b/cmd/rollapp/start/start.go index d04b8ce9b..3244a3254 100644 --- a/cmd/rollapp/start/start.go +++ b/cmd/rollapp/start/start.go @@ -10,6 +10,7 @@ import ( "slices" "strings" + sdkmath "cosmossdk.io/math" "github.com/pterm/pterm" "github.com/spf13/cobra" @@ -284,19 +285,45 @@ func PrintOutput( pterm.Warning.Println("failed to retrieve sequencer params:", paramsErr) } else if params != nil { minBalance := params.Params.LivenessSlashMinAbsolute - fmt.Printf( - "Liveness Slash Minimum: %s %s\n", - minBalance.Amount.String(), - minBalance.Denom, - ) - - if seqBalance.Amount.LT(minBalance.Amount) { - warnMsg := fmt.Sprintf( + seqAmt := seqBalance.Amount + minAmt := minBalance.Amount + + shouldHighlight := false + var warnDetail string + if seqAmt.LT(minAmt) { + shouldHighlight = true + warnDetail = fmt.Sprintf( "Sequencer balance %s is below the liveness slash minimum %s.", seqBalance.String(), minBalance.String(), ) - pterm.Warning.Println(warnMsg, "Please top up to avoid slashing.") + } else if minAmt.GT(sdkmath.ZeroInt()) { + diff := seqAmt.Sub(minAmt).Abs() + tolerance := minAmt.QuoRaw(10) // 10% band + if tolerance.IsZero() { + tolerance = sdkmath.NewInt(1) + } + if diff.LTE(tolerance) { + shouldHighlight = true + warnDetail = fmt.Sprintf( + "Sequencer balance %s is close to the liveness slash minimum %s.", + seqBalance.String(), + minBalance.String(), + ) + } + } + + if shouldHighlight { + fmt.Printf( + "Liveness Slash Minimum: %s %s\n", + minBalance.Amount.String(), + minBalance.Denom, + ) + pterm.Warning.Println( + "The liveness slash minimum is the balance required to avoid downtime slashing.", + warnDetail, + "Consider topping up to maintain a safety buffer.", + ) } } } diff --git a/cmd/services/restart/restart.go b/cmd/services/restart/restart.go index e1171c763..639c103cc 100644 --- a/cmd/services/restart/restart.go +++ b/cmd/services/restart/restart.go @@ -40,6 +40,11 @@ func Cmd(services []string) *cobra.Command { pterm.Error.Println("failed to check sequencer balance: ", err) return } + + warnErr := sequencerutils.WarnIfSequencerBelowLivenessSlashMin(rollappConfig) + if warnErr != nil { + pterm.Warning.Println("failed to evaluate liveness slash minimum:", warnErr) + } } if rollappConfig.HubData.ID != consts.MockHubID { diff --git a/cmd/services/start/start.go b/cmd/services/start/start.go index 1db2d110c..916a253ad 100644 --- a/cmd/services/start/start.go +++ b/cmd/services/start/start.go @@ -75,6 +75,11 @@ func RollappCmd() *cobra.Command { pterm.Error.Println("failed to check sequencer balance: ", err) return } + + warnErr := sequencerutils.WarnIfSequencerBelowLivenessSlashMin(rollappConfig) + if warnErr != nil { + pterm.Warning.Println("failed to evaluate liveness slash minimum:", warnErr) + } } if rollappConfig.HubData.ID != consts.MockHubID { diff --git a/utils/sequencer/sequencer.go b/utils/sequencer/sequencer.go index 54483b5cb..0eca84f88 100644 --- a/utils/sequencer/sequencer.go +++ b/utils/sequencer/sequencer.go @@ -12,6 +12,7 @@ import ( "strconv" "strings" + sdkmath "cosmossdk.io/math" cosmossdktypes "github.com/cosmos/cosmos-sdk/types" dymrollapptypes "github.com/dymensionxyz/dymension/v3/x/rollapp/types" "github.com/pterm/pterm" @@ -487,6 +488,76 @@ func GetSequencerData(cfg roller.RollappConfig) ([]keys.AccountData, error) { }, nil } +func WarnIfSequencerBelowLivenessSlashMin(cfg roller.RollappConfig) error { + if cfg.NodeType != "sequencer" || + cfg.HubData.ID == consts.MockHubID || + cfg.HubData.RpcUrl == "" { + return nil + } + + seqAccounts, err := GetSequencerData(cfg) + if err != nil { + return err + } + if len(seqAccounts) == 0 { + return fmt.Errorf("no sequencer account data found") + } + + params, err := GetSequencerParams(cfg.HubData) + if err != nil { + return err + } + if params == nil { + return nil + } + + seqBalance := seqAccounts[0].Balance + minBalance := params.Params.LivenessSlashMinAbsolute + seqAmt := seqBalance.Amount + minAmt := minBalance.Amount + + shouldHighlight := false + var warnDetail string + if seqAmt.LT(minAmt) { + shouldHighlight = true + warnDetail = fmt.Sprintf( + "Sequencer balance %s is below the liveness slash minimum %s.", + seqBalance.String(), + minBalance.String(), + ) + } else if minAmt.GT(sdkmath.ZeroInt()) { + diff := seqAmt.Sub(minAmt).Abs() + tolerance := minAmt.QuoRaw(10) + if tolerance.IsZero() { + tolerance = sdkmath.NewInt(1) + } + + if diff.LTE(tolerance) { + shouldHighlight = true + warnDetail = fmt.Sprintf( + "Sequencer balance %s is close to the liveness slash minimum %s.", + seqBalance.String(), + minBalance.String(), + ) + } + } + + if shouldHighlight { + fmt.Printf( + "Liveness Slash Minimum: %s %s\n", + minBalance.Amount.String(), + minBalance.Denom, + ) + pterm.Warning.Println( + "The liveness slash minimum is the balance required to avoid downtime slashing.", + warnDetail, + "Consider topping up to maintain a safety buffer.", + ) + } + + return nil +} + func GetSequencerBond(address string, hd consts.HubData) (*cosmossdktypes.Coins, error) { c := exec.Command( consts.Executables.Dymension,