diff --git a/packages/docs/pages/introduction/protocol-intro/cubic-slashing.mdx b/packages/docs/pages/introduction/protocol-intro/cubic-slashing.mdx new file mode 100644 index 00000000..3d52c4f8 --- /dev/null +++ b/packages/docs/pages/introduction/protocol-intro/cubic-slashing.mdx @@ -0,0 +1,125 @@ +# Cubic slashing + +The slashing mechanism in Namada is designed to heavily disincentivize coordinated validator misbehavior, particularly among different operator accounts. +It is known as *cubic* slashing because the slashed token amount scales with the cube of the infracting voting power or stake. + +Typical examples of such misbehaviors or infractions include double signing a block or proposing an invalid block. + +## How slashes are processed + +The Namada protocol receives data from CometBFT each block that might contain some piece of evidence of validator misbehavior. +Namada ensures that the evidence is not outdated and then enqueues the slash for future processing at a parameterizable number of epochs after the infraction epoch. + +Slashes are processed in the future to allow for sufficient time for the blockchain to detect infractions that might have occurred. +During this period, the protocol ensures that no delegators to a slashed validator may unbond or redelegate. + +## Cubic slashing algorithm + +When the slash is ready for processing, the slash rate for a given slash is computed as + ``` + min(param_min_slash_rate, cubic_slash_rate) + ``` +where `param_min_slash_rate` is the minimum slash rate for a given slash type as set in the network's protocol parameters. +These values can be queried from a network with the following (and example output): +``` +> namadac query-protocol-parameters --node $NODE | grep 'slash rate' + Duplicate vote minimum slash rate: 0.001 + Light client attack minimum slash rate: 0.001 +``` + +The `cubic_slash_rate` for a given slash or infraction is computed by considering other nearby slashes as well. +Through this, repeated validator misbehavior or coordinated misbehavior by several malicious operators can be heavily penalized. + +In particular, the cubic slash rate is determined by first computing the sum of the fractional voting power of each infraction relative to the total voting power of the consensus validator set, +for all infractions in a window of epochs around the epoch of the current slash being processed. + +If `current_epoch` is the epoch of the current slash, then the window is `[current_epoch - cubic_slashing_window_len, current_epoch + cubic_slashing_window_len]`. +The value of `cubic_slashing_window_len` can also be obtained from the protocol parameters: +``` +> namadac query-protocol-parameters --node $NODE | grep 'Cubic slashing' + Cubic slashing window length: 1 epoch +``` + +With this summed fractional voting power computed, the cubic slash rate is then the square of this number scaled by `9`. +This is done such that one malicious validator with at least 1/3 of the consensus voting power will be slashed 100%. + +The following is some Rust-like pseudocode that performs the cubic slashing computation: +```rust +/// Calculate the cubic slash rate for a slash in the current epoch +fn calculate_cubic_slash_rate( + current_epoch: Epoch, + cubic_window_len: u64, + slashes: Map>, +) -> Decimal { + let start_epoch = current_epoch - cubic_window_len; + let end_epoch = current_epoch + cubic_window_len; + + let mut sum_vp_frac = Dec::zero(); + + for epoch in start_epoch..=end_epoch { + let cur_slashes = slashes.get(epoch); + let total_consensus_voting_power = get_consensus_voting_power(epoch); + + let mut vp_frac_this_epoch = Dec::zero(); + for slash in cur_slashes { + vp_frac_this_epoch += (slash.validator_voting_power / total_consensus_voting_power) + } + + vp_frac_sum += vp_frac_this_epoch; + } + + let rate = cmp::min( + Decimal::ONE, + cmp::max( + slash.param_min_slash_rate, + 9 * vp_frac_sum * vp_frac_sum, + ), + ); + rate +} + +/// Generic slash object with the misbehaving validator and infraction type +struct Slash { + validator_voting_power: Amount, + param_min_slash_rate: Dec, +} +``` + +The cubic slashing rate itself can be expressed in concise mathematical form as: +$$ + +9*\left(\sum_e \sum_{i^e}\frac{\text{v}_{i^e}}{\text{v}_{\text{tot}}^e}\right)^2 + +$$ + +where $\text{v}$ is voting power, $e$ includes all epochs in the cubic slashing window, and $i^e$ runs over all known infractions in the epoch $e$. + +## After getting the slash rate + +Once the slash rate is computed for a given slash, the appropriate number of tokens is deducted from the validator's stake and the slash data is stored on-chain. + +The amount of tokens that is ultimately slashed is further determined such that a validator is slashed only considering stake that was used to commit the misbehavior. +This means that if a given bond of staked tokens was never itself contributing to voting power when an infraction occurs, then those tokens can never be slashed. + +## Examples +Consider some examples to help demonstrate how cubic slashing works: + +### Single validator with a single slash + +Assume that validator `A` has `10%` of the consensus voting power and misbehaves once. The minimum slash rate is `0.1%`. + +The slash rate is `min ( 0.001 , 9 * (0.1)^2 ) = 0.09 = 9%` + +### Single validator with two nearby slashes + +Assume that validator `A` had `10%` of the consensus voting power in epoch `e` and `5%` of the voting power in epoch `e+1`. +The cubic slashing window length is `1`, so both slashes are considered for the computation. + +The cubic slash rate is computed as `9 * (0.1 + 0.05)^2 = 9 * 0.15^2 = 0.2025 = 20.25%`. +Since there are no other slashes, this will be the slash rate for each of these slashes. + +We then apply the slash rate of each slash to the voting power used to commit the infraction. +So say that there are `100` staked tokens in consensus, then the number of tokens slashed after both are processed: +``` +slashed = (10)(0.2025) + (5)(0.2025) = ~ 3 tokens +``` \ No newline at end of file diff --git a/packages/docs/pages/operators/validators/staking.mdx b/packages/docs/pages/operators/validators/staking.mdx index ae03f452..f263267d 100644 --- a/packages/docs/pages/operators/validators/staking.mdx +++ b/packages/docs/pages/operators/validators/staking.mdx @@ -75,6 +75,8 @@ Should a validator exhibit punishable behavior, the bonds towards this validator namada client slashes ``` +Details on Namada's cubic slashing mechanism are described in detail [here](../introduction/protocol-intro/cubic-slashing.mdx). + ## Unbonding While tokens are bonded, they are locked-in the PoS system and hence are not liquid until the bonder withdraw them. To do that, the bonder first need to send a transaction to “unbond” its tokens. A user can unbond any amount, up to the sum of all its bonds to the given validator, even before the bonds become active. diff --git a/packages/docs/pages/users/delegators.mdx b/packages/docs/pages/users/delegators.mdx index 04bf5299..66a0c0af 100644 --- a/packages/docs/pages/users/delegators.mdx +++ b/packages/docs/pages/users/delegators.mdx @@ -133,4 +133,15 @@ When the validator that holds a bond is jailed, there are restrictions on what c Bonding tokens is always permitted to any validator, even if it is jailed. -Unbonding and redelegating from a jailed validator is not permitted if that validator is enqueued to be slashed (it is *frozen*), as described in [this section on jailing validators for protocol misbehaviors](../operators/validators/jailing.mdx#jailing-for-protocol-misbehavior). Otherwise, unbonding and redelegating is permitted. \ No newline at end of file +Unbonding and redelegating from a jailed validator is not permitted if that validator is enqueued to be slashed (it is *frozen*), as described in [this section on jailing validators for protocol misbehaviors](../operators/validators/jailing.mdx#jailing-for-protocol-misbehavior). Otherwise, unbonding and redelegating is permitted. + +## Slashing + +Delegators should be aware of the slashing risks associated with a validator when choosing to stake tokens. +Namada's native slashing mechanism makes it increasingly risky to delegate to a validator with a large amount of stake. +Please read details of Namada's cubic slashing mechanism [here](../introduction/protocol-intro/cubic-slashing.mdx). + +If a validator to whom you are staked is slashed, they will be first be jailed and then have staked tokens confiscated. +As a delegator, you will have some of your staked tokens held by the validator confiscated from you as well by consequence. + +You can avoid risk to high slash rates by delegating to validators with lower stake, thereby also enhancing network decentralization of stake. \ No newline at end of file diff --git a/packages/specs/pages/modules/proof-of-stake/cubic-slashing.mdx b/packages/specs/pages/modules/proof-of-stake/cubic-slashing.mdx index b663c7fa..304fa60a 100644 --- a/packages/specs/pages/modules/proof-of-stake/cubic-slashing.mdx +++ b/packages/specs/pages/modules/proof-of-stake/cubic-slashing.mdx @@ -12,54 +12,15 @@ The cubic slash rate is a function of all infractions in a given epoch and its n 2. Sum the fractional voting powers (relative to the total voting power of all consensus validators) of the misbehaving validator for each of the collected infractions. {/* The total voting powers include all validators in one of the validator sets and all jailed validators (more on this later). */} 3. The cubic slash rate is then proportional to this sum. Using $\text{vp}_i$ to indicate the validator voting power used to commit the infraction $i$ and $\text{vp}_{\text{tot}}^i$ to indicate the total consensus voting power at the epoch of infraction $i$, the cubic rate is expressed as: -$$ 9*\left(\sum_{i \in \text{infractions}}\frac{\text{vp}_i}{\text{vp}_{\text{tot}}^i}\right)^2. $$ +$$ + +9*\left(\sum_{i \in \text{infractions}}\frac{\text{vp}_i}{\text{vp}_{\text{tot}}^i}\right)^2. + +$$ For each individual slash, the rate is ultimately determined as the maximum of the cubic slash rate and some nominal minimum rate that is dependent on the infraction type (see [system parameters](./objects-and-txs.mdx#system-parameters)) and is capped at 1.0. The amount of slashed tokens is the rate multiplied by the stake (voting power) used to commit the infraction. The "cubic" in cubic slashing thus comes from the product of the rate, which is quadratic in the voting power, and the voting power itself. -Expressed in pseudocode: - - - ```haskell = - calculateSlashRate :: [Slash] -> Float - - calculateSlashRate slashes = - let votingPowerFraction = sum [ votingPowerFraction (validator slash) | slash <- slashes] - in max 0.01 (min 1 (votingPowerFraction**2)*9) - -- minimum slash rate is 1% - -- then exponential between 0 & 1/3 voting power - -- we can make this a more complex function later - ``` - - - ```python - class PoS: - def __init__(self, genesis_validators : list): - self.update_validators(genesis_validators) - - def update_validators(self, new_validators): - self.validators = new_validators - self.total_voting_power = sum(validator.voting_power for validator in self.validators) - - def slash(self, slashed_validators : list): - for slashed_validator in slashed_validators: - voting_power_fraction = slashed_validator.voting_power / self.total_voting_power - slash_rate = calc_slash_rate(voting_power_fraction) - slashed_validator.voting_power *= (1 - slash_rate) - - def get_voting_power(self): - for i in range(min(10, len(self.validators))): - print(self.validators[i]) - - @staticmethod - def calc_slash_rate(voting_power_fraction): - slash_rate = max(0.01, (voting_power_fraction ** 2) * 9) - return slash_rate - ``` - - - - -### Rust implementation +Expressed in Rust-like pseudocode: ```rust // Infraction type, where inner field is the slash rate for the type enum Infraction { @@ -82,7 +43,6 @@ struct Slash { // Calculate the cubic slash rate for a slash in the current epoch fn calculate_cubic_slash_rate( current_epoch: u64, - nominal_slash_rate: Decimal, cubic_window_width: u64, slashes: Map>, total_voting_power: u64