diff --git a/contracts/provider/external-staking/src/state.rs b/contracts/provider/external-staking/src/state.rs index 338de097..03229e17 100644 --- a/contracts/provider/external-staking/src/state.rs +++ b/contracts/provider/external-staking/src/state.rs @@ -94,6 +94,8 @@ impl Stake { /// Slashes all the entries in `pending_unbonds`, returning total slashed amount. pub fn slash_pending(&mut self, info: &BlockInfo, slash_ratio: Decimal) -> Uint128 { + // TODO: Only slash undelegations that started after the misbehaviour's time. This is not + // possible right now, because we don't have access to the misbehaviour's time. (#177) self.pending_unbonds .iter_mut() .filter(|pending| pending.release_at > info.time) diff --git a/contracts/provider/native-staking-proxy/src/contract.rs b/contracts/provider/native-staking-proxy/src/contract.rs index a633b06a..fc7d83ef 100644 --- a/contracts/provider/native-staking-proxy/src/contract.rs +++ b/contracts/provider/native-staking-proxy/src/contract.rs @@ -159,6 +159,7 @@ impl NativeStakingProxyContract<'_> { } // Build undelegate messages + // FIXME: Use an "immediate unbonding" message for undelegation let mut undelegate_msgs = vec![]; for (validator, burn_amount) in burns { let undelegate_msg = StakingMsg::Undelegate { diff --git a/contracts/provider/native-staking/src/contract.rs b/contracts/provider/native-staking/src/contract.rs index e5ac4159..513f32d6 100644 --- a/contracts/provider/native-staking/src/contract.rs +++ b/contracts/provider/native-staking/src/contract.rs @@ -105,7 +105,6 @@ impl NativeStakingContract<'_> { } for validator in jailed { // Slash the validator (if bonded) - // TODO: Slash with a different slash ratio! (downtime / offline slash ratio) let slash_msg = self.handle_slashing(&mut deps, &cfg, validator, SlashingReason::Offline)?; if let Some(msg) = slash_msg { @@ -157,7 +156,6 @@ impl NativeStakingContract<'_> { if delegation.is_zero() { // Maintenance: Remove delegator from map in passing - // TODO: Remove zero amount delegations from delegators map periodically self.delegators.remove(deps.storage, (validator, owner)); continue; } diff --git a/docs/ibc/Slashing.md b/docs/ibc/Slashing.md index 985eeea1..48598d61 100644 --- a/docs/ibc/Slashing.md +++ b/docs/ibc/Slashing.md @@ -1,8 +1,11 @@ # Slashing Evidence Handling -Note: Slashing will not be part of the MVP rollout, and first implemented in V1. However, we define +**Note**: Slashing will not be part of the MVP rollout, and first implemented in V1. However, we define proper slashing mechanics here. +**Note 2**: As of V1, we will assume that the Consumer chain is not Byzantine, and therefore +we will not implement a mechanism to verify slashing evidence. This will be implemented in V2. + ## General Architecture We are worried about a Byzantine consumer chain slashing arbitrary validators on the provider @@ -15,10 +18,28 @@ Rather than submit IBC packets for Slashing, we require submission of the duplic signatures from the Tendermint headers on the Consumer chain. These cannot be forged unless the private key is compromised. -As of V2, the external staker must have a method to allow submitting such evidence of +As of V2, the external staker (on the Provider side) must have a method to allow submitting such evidence of double-signing which can be verified and immediately slash all delegators of that validator. +This can be done by following the approach and implementation of InterChain Security (ICS) for +[slashing](https://cosmos.github.io/interchain-security/adrs/adr-013-equivocation-slashing). At the time of writing, +there's already an implementation of ICS misbehaviour handling on the [Hermes relayer](https://github.com/informalsystems/hermes). +We can therefore use that as a reference. + +The general idea is that the Relayer will submit the misbehaviour evidence to the Provider chain, +which will then slash the associated delegators. The evidence will be verified by the +Provider chain, and only valid evidence will be accepted and processed. + +The submission mechanism is straightforward: the Relayer submits the slashing evidence from the +Consumer to the Provider, and broadcasts it as or as part of a blockchain transaction. So, the Provider +will need to have a slashing module that monitors the chain for a specific "Slashing evidence" +transaction type. The slashing evidence can then be submitted to a smart contract (as a sudo message by example) for +verification and processing. + +Please note that the actual slashing implementation will not change. Only the slashing evidence +handling, submission and verification will need to be implemented as part of V2. + ## Detecting Byzantine Chains The IBC light clients have a @@ -67,6 +88,9 @@ evidence. Or we just accept any age and just use the age of the evidence to deci of the two votes may be wildly different, and they really shouldn't have trusted this cheating validator in the first place. +**Note**: Here we can again refer to the ICS specs / impl for reference: +https://cosmos.github.io/interchain-security/adrs/adr-005-cryptographic-equivocation-verification + ## Trust Assumptions For the V1 implementation, we will assume not only that the Tendermint headers are valid and can be trusted, but @@ -75,6 +99,8 @@ having to implement a different / independent communication channel for misbehav Once that mechanism is established and implemented, by example as part of ICS, we can revisit this and adapt our implementation to receive and verify misbehaviour evidence from the Consumer chain on the Provider. +**Note**: As of this update (28/11/23) this mechanism has already been implemented as part of the Hermes relayer. + So, this is concerned with a malicious validator on the Consumer chain, double-signing to slash associated delegators on the Provider chain. @@ -83,7 +109,7 @@ A user delegating to a malicious validator and then getting slashed is part of t the delegator is getting staking rewards. As a mitigating factor, the amount of slashing for misbehaviour is defined by the slashing ratio. -Another possibility is, a malicious validator on the Consumer double signing for profit, and trying to **avoid** being slashed. +Another possibility is, a malicious validator on the Consumer double sign intentionally, and try to **avoid** being slashed. He could, by example, allocate all or most of his funds through cross-delegators on the Provider, and then tamper with the validator set updates, so that his public key, or associated block height and times, are invalid. This would prevent the Provider from slashing him, as the **provided evidence for misbehaviour would fail to verify**. @@ -91,7 +117,11 @@ This last scenario is only possible if the entire Consumer chain goes Byzantine, It shows that the trust assumptions extend beyond the misbehaviour's evidence, and should include the validator set updates as well. Along with the Consumer chain itself. -This indicates that, barring Byzantine Consumer chains, it makes sense to re-utilize the same infrastructure and mechanisms +**Note**: As of this update (28/11/23) AFAIK this scenario is already being handled by the Hermes relayer. +The misbehaviour evidence is verified against the validator set at the time of the misbehaviour, and the +validator public key, which is needed for verification, is included in the evidence itself. + +All this indicates that, barring Byzantine Consumer chains, it makes sense to re-utilize the same infrastructure and mechanisms that are used for communication between the Provider and the Consumer, for the specific case of slashing processing. Both, slashing evidence handling and submission, and validator set updates, share similar trust assumptions and concerns. They can and must then be part of the same security model. @@ -101,6 +131,15 @@ separate from the rest of the Mesh Security infrastructure (in their own smart c then we should make sure that slashing evidence handling and submission is done by the same entity that is responsible for validator set updates. So that they can be audited together, and the same trust assumptions apply to both. +**Note**: As of this update (28/11/23) this is already the case. The Hermes relayer is responsible for both, validator public key +and misbehaviour evidence handling and submission. + +**Note 2**: Another possibility in this scenario is a malicious validator on the Consumer relaying and submitting false / forged misbehaviour evidence to the Provider, +through the Relayer. That is, tricking the Relayer into submitting false slashing evidence to the Provider, in order to slash associated Provider's delegators. +This is in principle possible, and called a "Nothing at Stake" attack (since the validator has in principle nothing to lose on the Consumer chain when doing this). +To prevent against this, the Relayer must broadcast / replay the slashing evidence it gets on the Consumer chain. This way, if a validator forged +the evidenced, it will be slashed on the Consumer side anyway; so that it's no longer a "nothing at stake" scenario. + ## Slashing Handling As mentioned, for V1, and for simplicity reasons, we will implement a slashing mechanism as part of the existing infrastructure, @@ -132,10 +171,17 @@ The `vault` contract will need to be able to handle this scenario. In the case of a slashed cross-delegator that doesn't have enough liquidity on the vault, it will need to unbond the funds from the Provider chain first, and then burn them to execute the slashing. +**Note**: As of this update (28/11/23) this is the current setup. Instead of burning the funds, they are unbonded from the Provider chain, +and kept in the Native staking contract. An accounting trick is being done, to avoid the need to burn the funds +and to discount the slashing amount from the total collateral of the delegator. + A kind of "immediate unbonding" mechanism (and associated permission) could be needed for this. Alternatively, the vault can unbond the funds from the Provider chain and wait for the unbonding period to expire in order to slash them. This will be effectively the same, as during unbonding those funds are both, blocked from withdrawal, and not providing rewards. +**Note**: This is currently (V1) being done using a normal unbonding period, which is not ideal, as the funds are being doubly discounted +by the slashing amount during the unbonding period. This will be fixed in V2, by implementing "immediate unbonding" on the Provider chain. + Another option would be for the vault to delegate the slashing in these cases to the blockchain itself. That is, by using and interacting with the Provider chain's Staking / Slashing module. This may be complicated to implement, as the slashing evidence and origin are effectively from another chain. @@ -149,6 +195,8 @@ From the point of view of slashing, both local and cross slashing events must be The only difference being that local slashing events don't need to be verified. But local slashing events must be processed, and its effects on collateral must be updated in the vault for each of the affected delegators. +**Note**: This is not yet implemented as of V1. It will be implemented as part of V2. + ## Slashing Propagation Slashing affects the amount of total collateral that every affected delegator has on the vault (or natively staked). @@ -158,7 +206,9 @@ After validating the slashing evidence (in the case of cross-slashing) and execu that is, discounting the slashing amount from the total collateral each affected delegator has, the associated invariants must be checked and adjusted if needed. In the case of a broken invariant, a rebalance (unbonding of the now lacking or insufficient funds) must be done to restore it. -This must be checked and rebalanced if needed over all the chains that the affected delegator has funds on. +This must be checked and rebalanced if needed, over all the chains that the affected delegator has funds on. + +**Note**: Both Slashing accounting and Slashing propagation accounting have been implemented as part of V1. ## Collateral Unbonding and Slashing Propagation Examples @@ -338,25 +388,48 @@ to the aggregated slash ratios of all the lien holders. - `new slashable amount: 78.125 * 0.1 + 68.125 * 0.5 + 58.125 * 0.5 + 78.125 * 0.5 = 7.8125 + 34.0625 + 29.0625 + 39.0625 = 110.0` (recalculated) - `new free collateral: 110 - max(110, 78.125) = 110 - 110 = 0` (invariants restored) -**TODO** +## Slashing Process Summary + +We can see that there are two processes potentially at play, at the smart contracts level, during slashing: + +1) Slashing accounting itself, which is done by the `vault` contract, over the users associated with +the slashed validator, on the corresponding lien holder. +Slashing accounting is also adjusted "in passing" (while forwarding the slashing to the `vault` contract), +in both the `virtual-staking` contract on the Consumer, and the `external-staking` contract on the Provider. + +2) Slashing propagation, which is done by the `vault` contract over the other lien holders associated +with the delegator or delegators that are associated in turn to the slashed validator. This happens +in case the slashed collateral in point 1), is now less than the new max lien, or the new slashable amount. + +This process starts at the `vault` contract, and is propagated to the `virtual-staking` contract on the Consumer. +It can also involve the native staking contracts on the Provider, if the slashed collateral is natively staked. +This in turn involves a kind of on-chain **burn** mechanism for the slashed funds; which is currently being done +by unbonding them, and discounting them from the total collateral of the delegator. + +**Note**: This unbonding mechanism is already "immediate unbonding" on the Consumer side, and will be implemented +as such on the Provider side as well, as part of V2 (or earlier). -### Slashing Process Summary +Depending on which is the reason for the broken invariant (either max lien greater than collateral, or total slashable amount greater than collateral), +slashing propagation will be done differently: +Either, by **adjusting the offending liens** to be below the collateral. Or, by **proportionally adjusting all the liens**, so that the sum of +the resulting sum of slashable amounts is below the collateral. -We can see that there are two processes potentially at play during slashing: +### Native vs. Cross Slashing -1) Slashing itself, which is done by the `vault` contract over the users associated with the slashed validator, -on the corresponding lien holder. -2) Slashing propagation, which is done by the `vault` contract over the other lien holders associated with the delegator, -in case the slashed collateral is now less than the new max lien or the new slashable amount. +Native vs. Cross Slashing processing and effects are similar, and are being implemented in the same way. +The main difference is that as of V1, we currently lack a **native** `x/meshsecurity` module (that is, for the Provider blockchain), +and therefore cannot do immediate unbonding. This will be implemented as part of V2. -Depending on which of the two is the reason for the broken invariant, slashing propagation will be done differently: -Either by adjusting the liens to be below the collateral, or by proportionally adjusting the liens, so that the sum of -the resulting slashable amounts is below the collateral. +### Effects of Validator Tombstoning During Slashing -### Native vs. Cross Slashing Process Details +Validator tombstoning, when or as a consequence of double signing, permanently removes the validator from the validator set. +Validator jailing, when or as a consequence of offline detection, also temporarily removes the validator from the active validator set. -**TODO** +Both events also lead to slashing, with different slash ratios for each misbehaviour. Only active validators +can be slashed, and this is a check that is currently done as part of the slashing process. -### Effects of Validator Tomstoning During Slashing +This means that if a validator is tombstoned or jailed during slashing, it has to be slashed first, and then tombstoned or jailed. +This is because the slashing process is done over the active validator set, and the validator must be active at the time of slashing. -**TODO** +This is currently being implemented this way, for cross-validators, in both the `virtual-staking` contract on the Consumer, +and the `external-staking` contract on the Provider.