diff --git a/x/vaas/provider/keeper/consumer_equivocation.go b/x/vaas/provider/keeper/consumer_equivocation.go index 3df3bb0..f30e1d8 100644 --- a/x/vaas/provider/keeper/consumer_equivocation.go +++ b/x/vaas/provider/keeper/consumer_equivocation.go @@ -215,32 +215,6 @@ func (k Keeper) HandleConsumerDowntime(ctx sdk.Context, consumerId uint64, evide providerAddr := k.GetProviderAddrFromConsumerAddr(ctx, consumerId, consumerAddr) - // Check that the consumer chain is outside its downtime grace period. - // During the grace period after launch, downtime evidence is suppressed to give - // validators time to spin up their consumer chain nodes. - infractionParams := k.GetInfractionParams(ctx) - if infractionParams.DowntimeGracePeriod > 0 { - initParams, err := k.GetConsumerInitializationParameters(ctx, consumerId) - if err != nil { - return errorsmod.Wrapf( - vaastypes.ErrInvalidConsumerState, - "cannot get initialization parameters for consumer chain %d: %s", - consumerId, err, - ) - } - gracePeriodEnd := initParams.SpawnTime.Add(infractionParams.DowntimeGracePeriod) - if ctx.BlockTime().Before(gracePeriodEnd) { - return errorsmod.Wrapf( - vaastypes.ErrInvalidPacketData, - "consumer chain %d is still in downtime grace period (launched %s, grace ends %s, now %s)", - consumerId, - initParams.SpawnTime.UTC(), - gracePeriodEnd.UTC(), - ctx.BlockTime().UTC(), - ) - } - } - // Verify the infraction height is not too old. minHeight := k.GetEquivocationEvidenceMinHeight(ctx, consumerId) if uint64(evidencePacket.InfractionHeight) < minHeight { @@ -265,7 +239,8 @@ func (k Keeper) HandleConsumerDowntime(ctx sdk.Context, consumerId uint64, evide } consensusHeight := ibcclienttypes.NewHeight(0, uint64(evidencePacket.InfractionHeight)) - if _, ok := k.clientKeeper.GetClientConsensusState(ctx, clientId, consensusHeight); !ok { + consensusState, ok := k.clientKeeper.GetClientConsensusState(ctx, clientId, consensusHeight) + if !ok { return errorsmod.Wrapf( vaastypes.ErrInvalidPacketData, "no consensus state for consumer chain %d at infraction height %d: cannot verify downtime", @@ -274,6 +249,32 @@ func (k Keeper) HandleConsumerDowntime(ctx sdk.Context, consumerId uint64, evide ) } + // Check that the consumer chain is outside its downtime grace period. + // During the grace period after launch, downtime evidence is suppressed to give + // validators time to spin up their consumer chain nodes. + infractionParams := k.GetInfractionParams(ctx) + if infractionParams.DowntimeGracePeriod > 0 { + initParams, err := k.GetConsumerInitializationParameters(ctx, consumerId) + if err != nil { + return errorsmod.Wrapf( + vaastypes.ErrInvalidConsumerState, + "cannot get initialization parameters for consumer chain %d: %s", + consumerId, err, + ) + } + gracePeriodEnd := initParams.SpawnTime.Add(infractionParams.DowntimeGracePeriod) + if consumerTime := consensusState.GetTimestamp(); consumerTime < uint64(gracePeriodEnd.UnixNano()) { //nolint:staticcheck + return errorsmod.Wrapf( + vaastypes.ErrInvalidPacketData, + "consumer chain %d is still in downtime grace period (launched %d, grace ends %d, infraction time %d)", + consumerId, + initParams.SpawnTime.UnixNano(), + gracePeriodEnd.UnixNano(), + consumerTime, + ) + } + } + // Verify the validator was part of the consumer's validator set. validator, found := k.GetConsumerValidator(ctx, consumerId, providerAddr) if !found { diff --git a/x/vaas/provider/keeper/consumer_equivocation_test.go b/x/vaas/provider/keeper/consumer_equivocation_test.go index 97510ff..26f66a7 100644 --- a/x/vaas/provider/keeper/consumer_equivocation_test.go +++ b/x/vaas/provider/keeper/consumer_equivocation_test.go @@ -1119,12 +1119,14 @@ func TestEvidencePacketDataJSONRoundTrip(t *testing.T) { func TestHandleConsumerDowntimeRejectsDuringGracePeriod(t *testing.T) { keeperParams := testkeeper.NewInMemKeeperParams(t) - providerKeeper, ctx, ctrl, _ := testkeeper.GetProviderKeeperAndCtx(t, keeperParams) + providerKeeper, ctx, ctrl, mocks := testkeeper.GetProviderKeeperAndCtx(t, keeperParams) defer ctrl.Finish() consumerId := uint64(0) providerKeeper.SetConsumerPhase(ctx, consumerId, types.CONSUMER_PHASE_LAUNCHED) providerKeeper.SetConsumerChainId(ctx, consumerId, "consumer-chain") + providerKeeper.SetConsumerClientId(ctx, consumerId, "07-tendermint-0") + providerKeeper.SetEquivocationEvidenceMinHeight(ctx, consumerId, 1) spawnTime := time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC) gracePeriod := 24 * time.Hour @@ -1155,6 +1157,13 @@ func TestHandleConsumerDowntimeRejectsDuringGracePeriod(t *testing.T) { stakingtypes.Infraction_INFRACTION_DOWNTIME, ) + consensusStateTimestamp := spawnTime.Add(12 * time.Hour) + mocks.MockClientKeeper.EXPECT(). + GetClientConsensusState(ctx, "07-tendermint-0", ibcclienttypes.NewHeight(0, 100)). + Return(ibcexported.ConsensusState(&ibctmtypes.ConsensusState{ + Timestamp: consensusStateTimestamp, + }), true) + err := providerKeeper.HandleConsumerEvidencePacket(ctx, consumerId, evidencePacket) require.Error(t, err) require.Contains(t, err.Error(), "grace period")