-
Notifications
You must be signed in to change notification settings - Fork 340
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(invariants): add several invariants across modules (#1514)
Co-authored-by: zale144 <[email protected]>
- Loading branch information
Showing
28 changed files
with
750 additions
and
201 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
// Packet uinv provides a simple way to define and register invariants in Cosmos SDK modules. | ||
// Invariants should be written using normal code style, by returning errors containing context. | ||
// Use NewErr to wrap errors in a way that they can be handled as invariant breaking. | ||
package uinv |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
package uinv | ||
|
||
import ( | ||
"errors" | ||
|
||
errorsmod "cosmossdk.io/errors" | ||
sdk "github.com/cosmos/cosmos-sdk/types" | ||
"github.com/dymensionxyz/gerr-cosmos/gerrc" | ||
) | ||
|
||
var ErrBroken = gerrc.ErrInternal.Wrap("invariant broken") | ||
|
||
// Wrap an error to mark as invariant breaking. If the error is nil, it will return nil. | ||
func Breaking(err error) error { | ||
if err == nil { | ||
return nil | ||
} | ||
return errors.Join(ErrBroken, err) | ||
} | ||
|
||
// If any error that your function returns is invariant breaking, use this function to wrap | ||
// it to reduce verbosity. | ||
func AnyErrorIsBreaking(f Func) Func { | ||
return func(ctx sdk.Context) error { | ||
return Breaking(f(ctx)) | ||
} | ||
} | ||
|
||
// Should return an ErrBroken if invariant is broken. Other errors are logged. | ||
type Func = func(sdk.Context) error | ||
|
||
type NamedFunc[K any] struct { | ||
Name string | ||
Func func(K) Func | ||
} | ||
|
||
func (nf NamedFunc[K]) Exec(ctx sdk.Context, module string, keeper K) (string, bool) { | ||
err := nf.Func(keeper)(ctx) | ||
broken := false | ||
var msg string | ||
if err != nil { | ||
broken = errorsmod.IsOf(err, ErrBroken) | ||
if !broken { | ||
ctx.Logger().Error("Invariant function error but not breaking.", "module", module, "name", nf.Name, "error", err) | ||
// Note that if it is broken the SDK wil take care of logging the error somewhere else | ||
} | ||
msg = sdk.FormatInvariant(module, nf.Name, err.Error()) | ||
} | ||
return msg, broken | ||
} | ||
|
||
type NamedFuncsList[K any] []NamedFunc[K] | ||
|
||
func (l NamedFuncsList[K]) RegisterInvariants(module string, ir sdk.InvariantRegistry, keeper K) { | ||
for _, f := range l { | ||
ir.RegisterRoute(module, f.Name, func(ctx sdk.Context) (string, bool) { | ||
return f.Exec(ctx, module, keeper) | ||
}) | ||
} | ||
} | ||
|
||
// Should be called in a function func AllInvariants(k Keeper) sdk.Invariant within your own module namespace. | ||
func (l NamedFuncsList[K]) All(module string, keeper K) sdk.Invariant { | ||
return func(ctx sdk.Context) (string, bool) { | ||
for _, invar := range l { | ||
s, stop := invar.Exec(ctx, module, keeper) | ||
if stop { | ||
return s, stop | ||
} | ||
} | ||
return "", false | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
package uinv | ||
|
||
import ( | ||
"errors" | ||
"testing" | ||
|
||
errorsmod "cosmossdk.io/errors" | ||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
func TestSanityCheckErrorTypes(t *testing.T) { | ||
baseErr := errors.New("base") | ||
var nilErr error | ||
|
||
t.Run("breaking", func(t *testing.T) { | ||
require.True(t, errorsmod.IsOf(Breaking(baseErr), ErrBroken)) | ||
require.False(t, errorsmod.IsOf(Breaking(nilErr), ErrBroken)) | ||
}) | ||
|
||
t.Run("join", func(t *testing.T) { | ||
joinedBase := errors.Join(baseErr, baseErr) | ||
joinedNil := errors.Join(nil, nil) | ||
require.True(t, errorsmod.IsOf(Breaking(joinedBase), ErrBroken)) | ||
require.False(t, errorsmod.IsOf(Breaking(joinedNil), ErrBroken)) | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,64 +1,72 @@ | ||
package keeper | ||
|
||
import ( | ||
"errors" | ||
"fmt" | ||
|
||
errorsmod "cosmossdk.io/errors" | ||
sdk "github.com/cosmos/cosmos-sdk/types" | ||
|
||
"github.com/dymensionxyz/dymension/v3/utils/uinv" | ||
commontypes "github.com/dymensionxyz/dymension/v3/x/common/types" | ||
"github.com/dymensionxyz/dymension/v3/x/delayedack/types" | ||
rtypes "github.com/dymensionxyz/dymension/v3/x/rollapp/types" | ||
) | ||
|
||
const ( | ||
routeFinalizedPacket = "rollapp-finalized-packet" | ||
) | ||
var invs = uinv.NamedFuncsList[Keeper]{ | ||
{Name: "proof-height", Func: InvariantProofHeight}, | ||
} | ||
|
||
// RegisterInvariants registers the delayedack module invariants | ||
func (k Keeper) RegisterInvariants(ir sdk.InvariantRegistry) { | ||
// INVARIANTS DISABLED SINCE LAZY FINALIZATION FEATURE | ||
func RegisterInvariants(ir sdk.InvariantRegistry, k Keeper) { | ||
invs.RegisterInvariants(types.ModuleName, ir, k) | ||
} | ||
|
||
// PacketsFinalizationCorrespondsToFinalizationHeight checks that all rollapp packets stored are set to | ||
// finalized status for all heights up to the latest height. | ||
func PacketsFinalizationCorrespondsToFinalizationHeight(k Keeper) sdk.Invariant { | ||
return func(ctx sdk.Context) (string, bool) { | ||
var ( | ||
broken bool | ||
msg string | ||
) | ||
// DO NOT DELETE | ||
func AllInvariants(k Keeper) sdk.Invariant { | ||
return invs.All(types.ModuleName, k) | ||
} | ||
|
||
for _, rollapp := range k.rollappKeeper.GetAllRollapps(ctx) { | ||
msg = k.checkRollapp(ctx, rollapp) | ||
if msg != "" { | ||
msg += fmt.Sprintf("rollapp: %s, msg: %s\n", rollapp.RollappId, msg) | ||
broken = true | ||
} | ||
// ensures packet not finalized before proof height is finalized | ||
func InvariantProofHeight(k Keeper) uinv.Func { | ||
return uinv.AnyErrorIsBreaking(func(ctx sdk.Context) error { | ||
var errs []error | ||
for _, ra := range k.rollappKeeper.GetAllRollapps(ctx) { | ||
err := k.checkRollapp(ctx, ra) | ||
err = errorsmod.Wrapf(err, "rollapp: %s", ra.RollappId) | ||
errs = append(errs, err) | ||
} | ||
|
||
return sdk.FormatInvariant(types.ModuleName, routeFinalizedPacket, msg), broken | ||
} | ||
return errors.Join(errs...) | ||
}) | ||
} | ||
|
||
func (k Keeper) checkRollapp(ctx sdk.Context, rollapp rtypes.Rollapp) (msg string) { | ||
// will stay 0 if no state is found | ||
func (k Keeper) checkRollapp(ctx sdk.Context, ra rtypes.Rollapp) error { | ||
// will stay 0 if no state is ok | ||
// but will still check packets | ||
var latestFinalizedHeight uint64 | ||
|
||
latestFinalizedStateIndex, found := k.rollappKeeper.GetLatestFinalizedStateIndex(ctx, rollapp.RollappId) | ||
if !found { | ||
return | ||
latestFinalizedStateIndex, ok := k.rollappKeeper.GetLatestFinalizedStateIndex(ctx, ra.RollappId) | ||
if !ok { | ||
return nil | ||
} | ||
|
||
latestFinalizedStateInfo := k.rollappKeeper.MustGetStateInfo(ctx, rollapp.RollappId, latestFinalizedStateIndex.Index) | ||
latestFinalizedStateInfo := k.rollappKeeper.MustGetStateInfo(ctx, ra.RollappId, latestFinalizedStateIndex.Index) | ||
latestFinalizedHeight = latestFinalizedStateInfo.GetLatestHeight() | ||
|
||
packets := k.ListRollappPackets(ctx, types.ByRollappID(rollapp.RollappId)) | ||
for _, packet := range packets { | ||
if packet.ProofHeight > latestFinalizedHeight && packet.Status == commontypes.Status_FINALIZED { | ||
return fmt.Sprintf("rollapp packet for the height should not be in finalized status. height=%d, rollapp=%s, status=%s\n", | ||
packet.ProofHeight, packet.RollappId, packet.Status) | ||
} | ||
packets := k.ListRollappPackets(ctx, types.ByRollappID(ra.RollappId)) | ||
var errs []error | ||
for _, p := range packets { | ||
err := k.checkPacket(p, latestFinalizedHeight) | ||
err = errorsmod.Wrapf(err, "packet: %s", p.RollappId) | ||
errs = append(errs, err) | ||
} | ||
return errors.Join(errs...) | ||
} | ||
|
||
func (k Keeper) checkPacket(p commontypes.RollappPacket, latestFinalizedHeight uint64) error { | ||
finalizedTooEarly := latestFinalizedHeight < p.ProofHeight && p.Status == commontypes.Status_FINALIZED | ||
if finalizedTooEarly { | ||
return fmt.Errorf("finalized too early height=%d, rollapp=%s, status=%s\n", | ||
p.ProofHeight, p.RollappId, p.Status, | ||
) | ||
} | ||
return | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.