Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

epoching: copy/paste necessary code from staking/evidence/slashing and make modifications #28

Merged
merged 17 commits into from
Jun 29, 2022
Merged
Show file tree
Hide file tree
Changes from 6 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
8 changes: 2 additions & 6 deletions app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -371,8 +371,7 @@ func NewBabylonApp(
// NOTE: staking module is required if HistoricalEntries param > 0
// NOTE: capability module's beginblocker must come before any modules using capabilities (e.g. IBC)
app.mm.SetOrderBeginBlockers(
upgradetypes.ModuleName, capabilitytypes.ModuleName, minttypes.ModuleName, distrtypes.ModuleName, slashingtypes.ModuleName,
evidencetypes.ModuleName, stakingtypes.ModuleName,
upgradetypes.ModuleName, capabilitytypes.ModuleName, minttypes.ModuleName, distrtypes.ModuleName, stakingtypes.ModuleName,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both evidence and slashing are removed, but staking is kept? Can you explain what's the idea?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Staking's BeginBlock processing only includes TrackHistoricalInfo (see https://github.com/cosmos/cosmos-sdk/blob/v0.45.5/x/staking/abci.go), which basically does some cleanup job. Keeping it seems to be not harmful.

authtypes.ModuleName, banktypes.ModuleName, govtypes.ModuleName, crisistypes.ModuleName, genutiltypes.ModuleName,
authz.ModuleName, feegrant.ModuleName,
paramstypes.ModuleName, vestingtypes.ModuleName,
Expand All @@ -382,15 +381,12 @@ func NewBabylonApp(
checkpointingtypes.ModuleName,
)
app.mm.SetOrderEndBlockers(
crisistypes.ModuleName, govtypes.ModuleName, stakingtypes.ModuleName,
crisistypes.ModuleName, govtypes.ModuleName,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suspect this will not work, because SetOrderEndBlockers will panic if you forget to include a module that is otherwise registered in the app. The way I see it is that you can call this first to get the benefit of this check, then remove the staking module from OrderEndBlockers, which is thankfully public.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point! Didn't know they have an assert there... will fix

capabilitytypes.ModuleName, authtypes.ModuleName, banktypes.ModuleName, distrtypes.ModuleName,
slashingtypes.ModuleName, minttypes.ModuleName,
genutiltypes.ModuleName, evidencetypes.ModuleName, authz.ModuleName,
feegrant.ModuleName,
paramstypes.ModuleName, upgradetypes.ModuleName, vestingtypes.ModuleName,
// TODO: BBL doesn't want the staking module to update the validator set at the end of each block. We consider two approaches to fix this:
// - remove stakingtypes.ModuleName from here, and let `epoching.EndBlock` do everything
// - call `epoching.EndBlock` first but only to dequeue the delayed staking requests, then let `staking.EndBlock` take care of executing them and return the changeset.
epochingtypes.ModuleName,
btclightclienttypes.ModuleName,
btccheckpointtypes.ModuleName,
Expand Down
36 changes: 36 additions & 0 deletions x/epoching/abci.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package epoching

import (
"time"

"github.com/babylonchain/babylon/x/epoching/keeper"
"github.com/babylonchain/babylon/x/epoching/types"

evidencetypes "github.com/cosmos/cosmos-sdk/x/evidence/types"
slashingtypes "github.com/cosmos/cosmos-sdk/x/slashing/types"
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"

"github.com/cosmos/cosmos-sdk/telemetry"
sdk "github.com/cosmos/cosmos-sdk/types"
abci "github.com/tendermint/tendermint/abci/types"
)

func BeginBlocker(ctx sdk.Context, k keeper.Keeper, req abci.RequestBeginBlock) {
defer telemetry.ModuleMeasureSince(types.ModuleName, time.Now(), telemetry.MetricKeyBeginBlocker)
defer telemetry.ModuleMeasureSince(evidencetypes.ModuleName, time.Now(), telemetry.MetricKeyBeginBlocker)
defer telemetry.ModuleMeasureSince(slashingtypes.ModuleName, time.Now(), telemetry.MetricKeyBeginBlocker)

// TODO: unimplemented:
// - slashing equivocating/unlive validators without jailing them
}

// Called every block, update validator set
func EndBlocker(ctx sdk.Context, k keeper.Keeper) []abci.ValidatorUpdate {
defer telemetry.ModuleMeasureSince(types.ModuleName, time.Now(), telemetry.MetricKeyEndBlocker)
defer telemetry.ModuleMeasureSince(stakingtypes.ModuleName, time.Now(), telemetry.MetricKeyEndBlocker)

// TODO: unimplemented:
// - if an epoch is newly checkpointed, make unbonding validators/delegations in this epoch unbonded
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I [put this in the diagram to happen when the BTC checkpoint is confirmed, mostly because this is the easiest in terms of book-keeping: I can clearly see when a checkpoint is going confirmed the first time. To do it at the end would be more difficult, need to capture before and after.

I also thought it's not actually the epoch's job to know about checkpointing, and that release is related to checkpointing, so it should be in the checkpointing module.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was meaning that the epoching module gets the epoch's chechpoint status from the checkpointing module, rather than judging the status by itself. Is my understanding correct and consistent with yours?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but I mean I'm not sure that the epoching module even needs a notion of status. To put it differently, it doesn't have to know that it's being checkpointed. It knows that it has ended, and it can tell other modules what the last block was, but those dependant modules can figure out on their own what they need to do. It just so happens that the checkpointing module needs the last commit hash for its own purposes, and it will use the checkpointing status as a requirement to release unbonding tokens. But the epoching can be oblivious to that.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It just so happens that the checkpointing module needs the last commit hash for its own purposes, and it will use the checkpointing status as a requirement to release unbonding tokens. But the epoching can be oblivious to that.

Checkpoint-assisted unbonding is a part of the epoching module rather than checkpointing, right?

One of the main reasons that checkpoint-assisted unbonding fits the epoching module better is that, the checkpoint-assisted unbonding depends on the staking module (especially the "mature" queues), maintained by the epoching module.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we agreed with @fishermanymc that the release of unbonding tokens doesn't need to be tied to the epoch.

You can imagine using just epochs, with 21 days unbonding, it's fine. But because we are doing checkpointing, we can release tokens earlier, as soon as the checkpoints are confirmed. Therefore it's associated with checkpoints, not epochs. It happens in the middle of the next epoch, depends on BTC, not the next epoch boundary.

Epochs manage the delayed staking indeed, but they only manage the inputs. The maturing queues are managed by the staking module itself, it doesn't need further input from the epochs. It just happens to be that we can use the epochs as a bucket for the confirmation height and the time step function - instead of maturing in 21 days, we take manual control of setting the height which we consider mature. But again, this doesn't depend on any active participation from the epoching module.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about we do the following?

  • The epoching module maintains the modified version of staking (e.g., the ApplyMatureUnbonding thing)
  • Whenever an epoch is checkpointed, the checkpointing module invokes epochingKeeper.ApplyMatureUnbonding in order to unbond mature validators/delegations?

This way each module does its own thing, and we only have a dependency from checkpointing to epoching (instead of being the other way where epoching hooks to the EpochConfirmed hook in the checkpointing module).

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @SebastianElvis and @aakoshh . The BTC/Babylon-specific bond release condition is that all the epochs up to the epoch where the unbonding request is submitted are confirmed. The bond release does not have to happen at the epoch boundary unless there are implementation level limitations preventing us from doing this.

// - if reaching an epoch boundary, execute validator-related msgs (bonded -> unbonding)
return nil
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't matter too much, but you could return an empty array like the rest of the modules. I say doesn't matter because if we never call the staking module at all the app won't work anyway. Might as well panic, to make sure this doesn't go unnoticed.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we really need to copy paste slashing and evidence handling? If we want those to be processed immediately, why not just let nature take its course?

Default operations in slashing/evidence handling include both slashing and jailing. We want the slashing part but not the jailing part, right?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I don't know much about jailing. What would happen if we allow it to go through?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When a validator is jailed, then:

  1. It loses all voting power (https://github.com/cosmos/cosmos-sdk/blob/v0.45.5/x/staking/keeper/val_state_change.go#L259-L268).
  2. The validator's status Jailed becomes true. To be unjailed, the validator needs to submit a message MsgUnjail and redo self-delegation (https://github.com/cosmos/cosmos-sdk/blob/v0.45.5/x/slashing/keeper/unjail.go#L9-L63).

If we allow jailing, then 1 will change the validator set within an epoch, and MsgUnjail in 2 needs to be delayed as well. The proposal 4 at https://babylon-chain.atlassian.net/wiki/spaces/BABYLON/pages/14745602/Four+proposals+on+handling+slashed+validators that we agreed upon gets rid of the jailing part due to such complexity.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, so jailing does remove the power and does change the validator set, but only temporarily, without slashing.

I don't know if this has a big impact on our epochs. I'm a bit lost, I thought we can let slashing happen immediately, which changes some of the power, and it sounds like jailing has a similar effect, so as long as it's done in moderation, the system should keep working.

Because you can't replicate the whole behaviour with copy-pasting, I would not include that part in this PR. What we are doing is not functionally equivalent to what the SDK is doing, therefore it is unacceptable IMO. We can't ignore certain bits on the basis that we may or may not be able to fix it later, when we don't even understand what we are breaking.

I would first restrict ourselves to dealing with the delayed staking, and have another go at consulting with Frojdi about whether we can just let jailing happen in the middle of the epoch.

@fishermanymc wdyt?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know if this has a big impact on our epochs. I'm a bit lost, I thought we can let slashing happen immediately, which changes some of the power, and it sounds like jailing has a similar effect, so as long as it's done in moderation, the system should keep working.

A potential issue is as follows. Assuming the system slashes only a part of its stake (say, 50%) of a validator, then:

  • if we jail it, then it is removed from the validator set from a node's view, but remains in the validator set from a checkpoint's view (as it's included in the bitmap).
  • if we don't jail it, then the validator set is consistent from a node or a checkpoint's view.

Because you can't replicate the whole behaviour with copy-pasting, I would not include that part in this PR.

Agree with this one. At the moment we have limited understanding on this part and the modification might break the system. At least the vanilla evidence/slashing modules will still work when only few validators are slashed.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes you are right, if we don't slash/jail then the validator set won't change from what it was at the beginning of the epoch and everyone in the bitmap is still okay to present a BLS signature.

To our advantage in this is that

  • we only need +1/3 of the validators to send BLS signatures
  • what needs to be signed comes from the Babylon chain, produced by the remaining validators, so the BLS signatures of the slashed validators should still be okay

Is that true? If we slash +1/3 then these dishonest validators can produce a BLS signature on their own, but in order to convince anyone's Tendermint node to go to an adversarial fork, you would need +2/3 to be dishonest.

What the slashed validators can do is to submit what looks like a "bogus" checkpoint, with their valid signatures, and send the application down this path. Looks like we have to be careful in that situation not to over react and kill Tendermint.

My understanding so far has been that:

  1. We start Epoch_n, and take a snapshot of who the ValidatorSet_n is, and their BLS signatures.
  2. We start Epoch_n+1; the commit hash over Epoch_n becomes known, produced by +2/3 of ValidatorSet_n.
  3. We collect BLS signatures from +1/3 members of ValidatorSet_n; we produce the raw checkpoint.

If we took the snapshot for ValidatorSet_n at the end of Epoch_n, we would have only the people who weren't slashed. Is it important to use the validators from the beginning? If we already know that only slashing can influence it during an epoch?

I suppose we can always discard BLS signatures from slashed validators during collection, and thus reject checkpoints signed by them as well, so we would know that an honest Tendermint validator set would not produce such a checkpoint.

Ultimately, in each epoch we assume that +2/3rd are honest, and if we have to slash +1/3rd we have just proven that assumption to be wrong. Not sure if at that point if the +1/3rd dishonest people need to produce a real alternative fork, or just pretend to have signed a hidden fork, for us to conclude that Babylon failed.

@fishermanymc wdyt?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a complicated topic.

Option-1: If slashing is 100%, then BLS signatures from the slashed validators are useless and should be ignored during signature aggregation. This is because even if they create an attacking QC and get caught later, they have nothing more to be slashed. These are the lunatic malicious validators here: https://babylon-chain.atlassian.net/wiki/spaces/BABYLON/pages/15597569/How+Many+Validators+Can+Babylon+Slash+During+an+Epoch+WIP#3.-Tendermint%E2%80%99s-Native-Slashing-Capacity-in-One-Block

Option-2: If slashing is less than 100%, then the slashed nodes still have some stake in the system and thus are motivated to sign genuine BLS to get unbonded.

There seems also theoretical tradeoff between how many to slash and how many BLS signatures should be collected, which affects the security - liveness tradeoff. David and Nusret are working on this.

As our first pass, let's 1) not delay any slashing, and 2) ask the checkpointing module to only accept BLS signatures from unslashed validators from the original validator set at the beginning of the epoch. The aggregated power should be min(1/3, 1-X), where X is the slashed power. @gitferry , you are also a stakeholder.

Copy link
Member Author

@SebastianElvis SebastianElvis Jun 28, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the insightful discussions Akosh and Fisher! I also have a feeling that how many to slash and whether to jail seem to have connection with some fundamental trade-offs. Given that we are at the MVP phase and David and Nusret are working on related issues, at the moment let's follow the minimalist 2/3-secure approach proposed above, at least in this PR:

  • Follow Cosmos SDK's original evidence/slashing implementations with both jailing and slashing
  • Reject all BLS signatures from unslashed validators, even if they were in the validator set at the epoch beginning

}
126 changes: 126 additions & 0 deletions x/epoching/keeper/modified_evidence.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
package keeper

import (
"fmt"

sdk "github.com/cosmos/cosmos-sdk/types"

evidencekeeper "github.com/cosmos/cosmos-sdk/x/evidence/keeper"
evidencetypes "github.com/cosmos/cosmos-sdk/x/evidence/types"
slashingkeeper "github.com/cosmos/cosmos-sdk/x/slashing/keeper"
stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper"
)

// HandleEquivocationEvidence implements an equivocation evidence handler. Assuming the
// evidence is valid, the validator committing the misbehavior will be slashed and tombstoned.
// Once tombstoned, the validator will not be able to recover.
// Note, the evidence contains the block time and height at the time of the equivocation.
//
// The evidence is considered invalid if:
// - the evidence is too old
// - the validator is unbonded or does not exist
// - the signing info does not exist (will panic)
// - is already tombstoned
//
// TODO: Some of the invalid constraints listed above may need to be reconsidered
// in the case of a lunatic attack.
// (adapted from https://github.com/cosmos/cosmos-sdk/blob/v0.45.5/x/evidence/keeper/infraction.go#L11-L123)
func HandleEquivocationEvidence(ctx sdk.Context, ek *evidencekeeper.Keeper, slk *slashingkeeper.Keeper, stk *stakingkeeper.Keeper, evidence *evidencetypes.Equivocation) {
logger := ek.Logger(ctx)
consAddr := evidence.GetConsensusAddress()

if _, err := slk.GetPubkey(ctx, consAddr.Bytes()); err != nil {
// Ignore evidence that cannot be handled.
//
// NOTE: We used to panic with:
// `panic(fmt.Sprintf("Validator consensus-address %v not found", consAddr))`,
// but this couples the expectations of the app to both Tendermint and
// the simulator. Both are expected to provide the full range of
// allowable but none of the disallowed evidence types. Instead of
// getting this coordination right, it is easier to relax the
// constraints and ignore evidence that cannot be handled.
return
}

// calculate the age of the evidence
infractionHeight := evidence.GetHeight()
infractionTime := evidence.GetTime()
ageDuration := ctx.BlockHeader().Time.Sub(infractionTime)
ageBlocks := ctx.BlockHeader().Height - infractionHeight

// Reject evidence if the double-sign is too old. Evidence is considered stale
// if the difference in time and number of blocks is greater than the allowed
// parameters defined.
cp := ctx.ConsensusParams()
if cp != nil && cp.Evidence != nil {
if ageDuration > cp.Evidence.MaxAgeDuration && ageBlocks > cp.Evidence.MaxAgeNumBlocks {
logger.Info(
"ignored equivocation; evidence too old",
"validator", consAddr,
"infraction_height", infractionHeight,
"max_age_num_blocks", cp.Evidence.MaxAgeNumBlocks,
"infraction_time", infractionTime,
"max_age_duration", cp.Evidence.MaxAgeDuration,
)
return
}
}

validator := stk.ValidatorByConsAddr(ctx, consAddr)
if validator == nil || validator.IsUnbonded() {
// Defensive: Simulation doesn't take unbonding periods into account, and
// Tendermint might break this assumption at some point.
return
}

if ok := slk.HasValidatorSigningInfo(ctx, consAddr); !ok {
panic(fmt.Sprintf("expected signing info for validator %s but not found", consAddr))
}

// ignore if the validator is already tombstoned
if slk.IsTombstoned(ctx, consAddr) {
logger.Info(
"ignored equivocation; validator already tombstoned",
"validator", consAddr,
"infraction_height", infractionHeight,
"infraction_time", infractionTime,
)
return
}

logger.Info(
"confirmed equivocation",
"validator", consAddr,
"infraction_height", infractionHeight,
"infraction_time", infractionTime,
)

// We need to retrieve the stake distribution which signed the block, so we
// subtract ValidatorUpdateDelay from the evidence height.
// Note, that this *can* result in a negative "distributionHeight", up to
// -ValidatorUpdateDelay, i.e. at the end of the
// pre-genesis block (none) = at the beginning of the genesis block.
// That's fine since this is just used to filter unbonding delegations & redelegations.
distributionHeight := infractionHeight - sdk.ValidatorUpdateDelay

// Slash validator. The `power` is the int64 power of the validator as provided
// to/by Tendermint. This value is validator.Tokens as sent to Tendermint via
// ABCI, and now received as evidence. The fraction is passed in to separately
// to slash unbonding and rebonding delegations.
slk.Slash(
ctx,
consAddr,
slk.SlashFractionDoubleSign(ctx),
evidence.GetValidatorPower(), distributionHeight,
)

// // Jail the validator if not already jailed. This will begin unbonding the
// // validator if not already unbonding (tombstoned).
// if !validator.IsJailed() {
// slk.Jail(ctx, consAddr)
// }

// slk.JailUntil(ctx, consAddr, evidencetypes.DoubleSignJailEndTime)
slk.Tombstone(ctx, consAddr)
ek.SetEvidence(ctx, evidence)
}
133 changes: 133 additions & 0 deletions x/epoching/keeper/modified_slashing.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
package keeper

import (
"fmt"

cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
sdk "github.com/cosmos/cosmos-sdk/types"

slashingkeeper "github.com/cosmos/cosmos-sdk/x/slashing/keeper"
slashingtypes "github.com/cosmos/cosmos-sdk/x/slashing/types"
stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper"
)

// HandleValidatorSignature handles a validator signature (for slashing equivocating validators)
// called once per validator per block.
// (adapted from https://github.com/cosmos/cosmos-sdk/blob/v0.45.5/x/slashing/keeper/infractions.go#L11-L126)
func HandleValidatorSignature(ctx sdk.Context, slk *slashingkeeper.Keeper, stk *stakingkeeper.Keeper, addr cryptotypes.Address, power int64, signed bool) {
logger := slk.Logger(ctx)
height := ctx.BlockHeight()

// fetch the validator public key
consAddr := sdk.ConsAddress(addr)
if _, err := slk.GetPubkey(ctx, addr); err != nil {
panic(fmt.Sprintf("Validator consensus-address %s not found", consAddr))
}

// fetch signing info
signInfo, found := slk.GetValidatorSigningInfo(ctx, consAddr)
if !found {
panic(fmt.Sprintf("Expected signing info for validator %s but not found", consAddr))
}

// this is a relative index, so it counts blocks the validator *should* have signed
// will use the 0-value default signing info if not present, except for start height
index := signInfo.IndexOffset % slk.SignedBlocksWindow(ctx)
signInfo.IndexOffset++

// Update signed block bit array & counter
// This counter just tracks the sum of the bit array
// That way we avoid needing to read/write the whole array each time
previous := slk.GetValidatorMissedBlockBitArray(ctx, consAddr, index)
missed := !signed
switch {
case !previous && missed:
// Array value has changed from not missed to missed, increment counter
slk.SetValidatorMissedBlockBitArray(ctx, consAddr, index, true)
signInfo.MissedBlocksCounter++
case previous && !missed:
// Array value has changed from missed to not missed, decrement counter
slk.SetValidatorMissedBlockBitArray(ctx, consAddr, index, false)
signInfo.MissedBlocksCounter--
default:
// Array value at this index has not changed, no need to update counter
}

minSignedPerWindow := slk.MinSignedPerWindow(ctx)

if missed {
ctx.EventManager().EmitEvent(
sdk.NewEvent(
slashingtypes.EventTypeLiveness,
sdk.NewAttribute(slashingtypes.AttributeKeyAddress, consAddr.String()),
sdk.NewAttribute(slashingtypes.AttributeKeyMissedBlocks, fmt.Sprintf("%d", signInfo.MissedBlocksCounter)),
sdk.NewAttribute(slashingtypes.AttributeKeyHeight, fmt.Sprintf("%d", height)),
),
)

logger.Debug(
"absent validator",
"height", height,
"validator", consAddr.String(),
"missed", signInfo.MissedBlocksCounter,
"threshold", minSignedPerWindow,
)
}

minHeight := signInfo.StartHeight + slk.SignedBlocksWindow(ctx)
maxMissed := slk.SignedBlocksWindow(ctx) - minSignedPerWindow

// if we are past the minimum height and the validator has missed too many blocks, punish them
if height > minHeight && signInfo.MissedBlocksCounter > maxMissed {
validator := stk.ValidatorByConsAddr(ctx, consAddr)
if validator != nil && !validator.IsJailed() {
// Downtime confirmed: slash the validator
// We need to retrieve the stake distribution which signed the block, so we subtract ValidatorUpdateDelay from the evidence height,
// and subtract an additional 1 since this is the LastCommit.
// Note that this *can* result in a negative "distributionHeight" up to -ValidatorUpdateDelay-1,
// i.e. at the end of the pre-genesis block (none) = at the beginning of the genesis block.
// That's fine since this is just used to filter unbonding delegations & redelegations.
distributionHeight := height - sdk.ValidatorUpdateDelay - 1

ctx.EventManager().EmitEvent(
sdk.NewEvent(
slashingtypes.EventTypeSlash,
sdk.NewAttribute(slashingtypes.AttributeKeyAddress, consAddr.String()),
sdk.NewAttribute(slashingtypes.AttributeKeyPower, fmt.Sprintf("%d", power)),
sdk.NewAttribute(slashingtypes.AttributeKeyReason, slashingtypes.AttributeValueMissingSignature),
// sdk.NewAttribute(slashingtypes.AttributeKeyJailed, consAddr.String()),
),
)
stk.Slash(ctx, consAddr, distributionHeight, power, slk.SlashFractionDowntime(ctx))
// stk.Jail(ctx, consAddr)

// signInfo.JailedUntil = ctx.BlockHeader().Time.Add(slk.DowntimeJailDuration(ctx))

// We need to reset the counter & array so that the validator won't be immediately slashed for downtime upon rebonding.
signInfo.MissedBlocksCounter = 0
signInfo.IndexOffset = 0
// TODO: commented the line below as it's a private function. Find a way to work around this.
// slk.clearValidatorMissedBlockBitArray(ctx, consAddr)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vitsalis this seems to contradict the concept of being able to copy paste.

It's also easy to miss this comment (maybe instead of a TODO it can be a FIXME), thanks for mentioning it in the PR description. I would not try to check code in with this kind of commented logic, which we don't know the consequences of. It looks like it might lead to slashing more times for missed blocks, or who knows.

I didn't expect we have to copy paste slashing, though, only staking.


logger.Info(
// "slashing and jailing validator due to liveness fault",
"slashing validator due to liveness fault",
"height", height,
"validator", consAddr.String(),
"min_height", minHeight,
"threshold", minSignedPerWindow,
"slashed", slk.SlashFractionDowntime(ctx).String(),
// "jailed_until", signInfo.JailedUntil,
)
} else {
// validator was (a) not found or (b) already jailed so we do not slash
logger.Info(
"validator would have been slashed for downtime, but was either not found in store or already jailed",
"validator", consAddr.String(),
)
}
}

// Set the updated signing info
slk.SetValidatorSigningInfo(ctx, consAddr, signInfo)
}
Loading